chore: sweep Kimi mentions from comments and notes
Some checks failed
Tests / PR mergeability (push) Successful in 19s
Tests / Base branch health (push) Successful in 10s
Tests / Detect test scope (push) Successful in 17s
Tests / lint-tests (push) Failing after 1m43s
Tests / plugin-validate (push) Successful in 1m7s
Tests / test (ubuntu-latest, 24, shard 1/3) (push) Failing after 18s
Tests / test (ubuntu-latest, 24, shard 2/3) (push) Failing after 19s
Tests / test (ubuntu-latest, 24, shard 3/3) (push) Failing after 19s
Tests / test (ubuntu-latest, 24) (push) Failing after 17s
Tests / test (inert CI) (push) Has been skipped
Tests / QA loop walk (smell ratchet) (push) Failing after 18s
Tests / Coverage gate (merged shards) (push) Has been skipped
Tests / Publish emitted-baseline artifact (push) Has been skipped
Dismiss Unauthorized PR Approvals / dismiss-unauthorized-approval (push) Successful in 8s
Tests / Required tests (push) Has been cancelled
Tests / conformance test (macos-latest, 24) (push) Has been cancelled
Tests / conformance test (windows-latest, 24, shard 1/3) (push) Has been cancelled
Tests / conformance test (windows-latest, 24, shard 2/3) (push) Has been cancelled
Tests / conformance test (windows-latest, 24, shard 3/3) (push) Has been cancelled
Some checks failed
Tests / PR mergeability (push) Successful in 19s
Tests / Base branch health (push) Successful in 10s
Tests / Detect test scope (push) Successful in 17s
Tests / lint-tests (push) Failing after 1m43s
Tests / plugin-validate (push) Successful in 1m7s
Tests / test (ubuntu-latest, 24, shard 1/3) (push) Failing after 18s
Tests / test (ubuntu-latest, 24, shard 2/3) (push) Failing after 19s
Tests / test (ubuntu-latest, 24, shard 3/3) (push) Failing after 19s
Tests / test (ubuntu-latest, 24) (push) Failing after 17s
Tests / test (inert CI) (push) Has been skipped
Tests / QA loop walk (smell ratchet) (push) Failing after 18s
Tests / Coverage gate (merged shards) (push) Has been skipped
Tests / Publish emitted-baseline artifact (push) Has been skipped
Dismiss Unauthorized PR Approvals / dismiss-unauthorized-approval (push) Successful in 8s
Tests / Required tests (push) Has been cancelled
Tests / conformance test (macos-latest, 24) (push) Has been cancelled
Tests / conformance test (windows-latest, 24, shard 1/3) (push) Has been cancelled
Tests / conformance test (windows-latest, 24, shard 2/3) (push) Has been cancelled
Tests / conformance test (windows-latest, 24, shard 3/3) (push) Has been cancelled
This commit is contained in:
21
CONTEXT.md
21
CONTEXT.md
File diff suppressed because one or more lines are too long
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"count": 286,
|
||||
"count": 285,
|
||||
"classes": {
|
||||
"ARCH": 1,
|
||||
"CI": 2,
|
||||
"CONFIG": 5,
|
||||
"CONFIG": 4,
|
||||
"EXEC": 8,
|
||||
"LEARNING": 1,
|
||||
"LIVE-CONFIG": 6,
|
||||
@@ -46,11 +46,6 @@
|
||||
"klass": "CONFIG",
|
||||
"value": "TEST_ENV_BASE reaches CHILD env only; a test calling install() IN-PROCESS must additionally use helpers.scrubConfigLocationEnv() in beforeEach + its restorer in afterEach — HOME/USERPROFILE sandboxing is NOT sufficient because getGlobalConfigDir is env-FIRST"
|
||||
},
|
||||
{
|
||||
"id": "CONFIG.LOCATION.SEAM.kimi-two-homes",
|
||||
"klass": "CONFIG",
|
||||
"value": "kimi declares TWO config-location vars: KIMI_CONFIG_DIR (registry, generic Agent-Skills root via resolveKimiGlobalDir) and KIMI_SHARE_DIR (KIMI_HOOKS_TOML_DESCRIPTOR, kimi's OWN native config.toml carrying MSD's [[hooks]] block via resolveKimiHooksTomlDir); a registry-only derivation covers the first and silently misses the second"
|
||||
},
|
||||
{
|
||||
"id": "CONFIG.LOCATION.SEAM.scrub-set",
|
||||
"klass": "CONFIG",
|
||||
@@ -124,7 +119,7 @@
|
||||
{
|
||||
"id": "LIVE-CONFIG.GUARD.SEAM.non-root-targets",
|
||||
"klass": "LIVE-CONFIG",
|
||||
"value": "resolveExtraWatchTargets covers THREE live write surfaces that are not runtime config ROOTS (skills bases are a DELIBERATE non-target — the config-root layout misfires beneath them, so they need their own layout): $MSD_HOME/.msd watched WHOLESALE (exclusively MSD-owned, so the shared-root trap does not apply) plus ONE config.toml per NON_REGISTRY_CONFIG_HOME_DESCRIPTORS entry, each watched as a SINGLE FILE (those roots belong to their products) — today three targets, since #2755 split Kimi CLI (~/.kimi, KIMI_SHARE_DIR) from Kimi Code (~/.kimi-code, KIMI_CODE_HOME); the targets are DERIVED by iterating that array, never by calling a named resolver, so a further descriptor is picked up without editing the guard PROVIDED it owns the same NON_REGISTRY_OWNED_FILE ('config.toml') — one that owns a different filename needs a per-descriptor mapping, the named residual the guard states at its own definition. SECOND RESIDUAL: config.toml is not all MSD writes into those roots — installSharedHooksBundle also populates <root>/hooks/, which is UNWATCHED; closing it is a layout decision, like skills bases; passed to snapshotLiveConfig explicitly so a fixture-root caller cannot pull the real ~/.msd into its snapshot"
|
||||
"value": "resolveExtraWatchTargets covers THREE live write surfaces that are not runtime config ROOTS (skills bases are a DELIBERATE non-target — the config-root layout misfires beneath them, so they need their own layout): $MSD_HOME/.msd watched WHOLESALE (exclusively MSD-owned, so the shared-root trap does not apply) plus ONE config.toml per NON_REGISTRY_CONFIG_HOME_DESCRIPTORS entry, each watched as a SINGLE FILE (those roots belong to their products); the targets are DERIVED by iterating that array, never by calling a named resolver, so a further descriptor is picked up without editing the guard PROVIDED it owns the same NON_REGISTRY_OWNED_FILE ('config.toml') — one that owns a different filename needs a per-descriptor mapping, the named residual the guard states at its own definition. SECOND RESIDUAL: config.toml is not all MSD writes into those roots — installSharedHooksBundle also populates <root>/hooks/, which is UNWATCHED; closing it is a layout decision, like skills bases; passed to snapshotLiveConfig explicitly so a fixture-root caller cannot pull the real ~/.msd into its snapshot"
|
||||
},
|
||||
{
|
||||
"id": "LIVE-CONFIG.GUARD.SEAM.scope",
|
||||
|
||||
@@ -60,17 +60,15 @@ const PATH_RETURNING_FNS = [
|
||||
// #2088 (ADR-1239 upgrade 3): resolves the on-disk skills-install dir honoring
|
||||
// a skills-kind `home` override (e.g. Codex → $HOME/.agents/skills).
|
||||
'_resolveSkillsRootDir',
|
||||
// #3664: shared kind-destination resolver (skills/agents/kimi-agents kinds).
|
||||
// #3664: shared kind-destination resolver (skills/agents kinds).
|
||||
'_kindDestDir',
|
||||
'getGlobalSkillDir',
|
||||
'getGlobalSkillDisplayPath',
|
||||
'resolveSkillsBaseFromDescriptor',
|
||||
'resolveConfigHomeFromDescriptor',
|
||||
'resolveKimiGlobalDir',
|
||||
// #2095 (EoS/kimi): resolves the directory holding Kimi CLI's OWN native
|
||||
// config.toml (~/.kimi by default, KIMI_SHARE_DIR override) — a sibling of,
|
||||
// and deliberately separate from, resolveKimiGlobalDir's generic
|
||||
// Agent-Skills root above.
|
||||
// #2095: resolves the directory holding a host's OWN native config.toml — a
|
||||
// sibling of, and deliberately separate from, the generic Agent-Skills root above.
|
||||
'resolveKimiHooksTomlDir',
|
||||
'resolveAntigravityGlobalDir',
|
||||
'getGlobalDir',
|
||||
@@ -190,7 +188,7 @@ function isPathReturningCall(node) {
|
||||
if (PATH_RETURNING_FNS.includes(dotted)) return true;
|
||||
}
|
||||
|
||||
// Bare call: getGlobalConfigDir(), resolveKimiGlobalDir(), etc.
|
||||
// Bare call: getGlobalConfigDir(), getGlobalSkillsBase(), etc.
|
||||
if (callee.type === 'Identifier') {
|
||||
if (PATH_RETURNING_FNS.includes(callee.name)) return true;
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@ function isContentPathReturningCall(node) {
|
||||
if (CONTENT_PATH_FNS.has(dotted)) return true;
|
||||
}
|
||||
|
||||
// Bare call: getGlobalConfigDir(), resolveKimiGlobalDir(), etc.
|
||||
// Bare call: getGlobalConfigDir(), getGlobalSkillsBase(), etc.
|
||||
if (callee.type === 'Identifier') {
|
||||
if (CONTENT_PATH_FNS.has(callee.name)) return true;
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -900,7 +900,7 @@ const VALID_SANDBOX_TIERS = new Set(['none', 'codex-agent-sandbox']);
|
||||
const VALID_ARTIFACT_KIND_NAMES = new Set(['commands', 'agents', 'skills']);
|
||||
const VALID_ARTIFACT_NESTINGS = new Set(['flat', 'nested']);
|
||||
// #2871 Phase 2 — only `commands` and `skills` are trigger-bearing (a `/msd-<name>`
|
||||
// the USER types). `agents` and `kimi-agents` are a separate dispatch interface
|
||||
// the USER types). `agents` is a separate dispatch interface
|
||||
// point (subagent invocation via `subagent_type`/named dispatch), never a trigger —
|
||||
// see 40-design.md's "agents are not trigger-bearing" correction. A narrower set
|
||||
// than VALID_ARTIFACT_KIND_NAMES on purpose: an artifact KIND can be agents; a
|
||||
@@ -1719,7 +1719,7 @@ function validateRuntimeBody(cap) {
|
||||
|
||||
// promptFlag — optional; string or null (#2627, Phase 3). `null`/absent
|
||||
// means the host takes the executor prompt positionally (codex, opencode);
|
||||
// a string names the flag that carries it (kimi/kimi-code: --prompt).
|
||||
// a string names the flag that carries it (e.g. --prompt).
|
||||
if (oe.promptFlag !== undefined && oe.promptFlag !== null && typeof oe.promptFlag !== 'string') {
|
||||
errors.push(
|
||||
'runtime.orchestratorExec.promptFlag must be a string or null (got: ' + JSON.stringify(oe.promptFlag) + ')',
|
||||
@@ -2459,10 +2459,9 @@ function validateReviewerBodyFields(cap) {
|
||||
* ADR-2782 D7 — probe.kind is a closed enum WIDER than existence, and every
|
||||
* probe that starts a process or a connection MUST be bounded.
|
||||
*
|
||||
* `command-exists` alone is structurally insufficient: `kimi` is claimed by both
|
||||
* Kimi Code CLI and the legacy Python kimi-cli (a separate first-party runtime
|
||||
* capability in this repo), so an existence-only probe registers the wrong tool.
|
||||
* The unbounded form of that probe was a live instance of this repo's named
|
||||
* `command-exists` alone is structurally insufficient: one binary name can be
|
||||
* claimed by more than one tool, so an existence-only probe may register the
|
||||
* wrong tool. The unbounded form of such a probe was a live instance of this repo's named
|
||||
* Unbounded Subprocesses defect — it ran on EVERY /msd:review invocation
|
||||
* regardless of which flags were passed, so a binary waiting on a first-run auth
|
||||
* prompt hung every future review, including reviews that never asked for it.
|
||||
|
||||
@@ -392,8 +392,8 @@ const RUNTIME_WORD_RE = new RegExp(
|
||||
// Positive evidence that the line is on the MODEL axis. Required, not merely
|
||||
// the absence of a runtime word: a reviewer demonstrated that "absence of a
|
||||
// veto word" is not evidence, with `The installer now offers Gemini 3.`,
|
||||
// `MSD installs cleanly on Gemini 3, Kimi, and Codex.` and
|
||||
// `Supported agents include Gemini 3, Kimi, and Cursor.` all exiting 0 — the
|
||||
// `MSD installs cleanly on Gemini 3, Cline, and Codex.` and
|
||||
// `Supported agents include Gemini 3, Cline, and Cursor.` all exiting 0 — the
|
||||
// exact laundering class this guard exists to catch. Every real model-axis
|
||||
// line in this repo names a model explicitly, so requiring it costs nothing
|
||||
// and inverts the failure direction from "silently allow" to "flag".
|
||||
|
||||
@@ -408,7 +408,7 @@ function runSmoke({
|
||||
lifecycleCommands = ['init', 'discuss-phase', 'plan-phase', 'execute-phase'],
|
||||
// claude and codex cover the two top-level config surfaces this scan knows
|
||||
// how to read (settings.json, and hooks.json + config.toml). Most other
|
||||
// runtimes reuse one of those two shapes; Cline and Kimi do not (see
|
||||
// runtimes reuse one of those two shapes; Cline does not (see
|
||||
// RUNTIME_CONFIG_FILES), so they are out of scope here rather than covered.
|
||||
entrypointRuntimes = ['claude', 'codex'],
|
||||
dryRun = false,
|
||||
|
||||
@@ -1736,7 +1736,7 @@ function main() {
|
||||
if (liveConfigGuardEnabled) {
|
||||
liveConfigRoots = resolveLiveConfigRoots();
|
||||
// #2665 round 3: $MSD_HOME/.msd, and one native config.toml per non-registry
|
||||
// config-home descriptor (Kimi CLI's and, since #2755, Kimi Code's), are live
|
||||
// config-home descriptor, are live
|
||||
// write surfaces that are not runtime config ROOTS, so they are invisible to the
|
||||
// line above. Watched independently — and note the OR: the extras alone are
|
||||
// reason enough to snapshot, so an unbuilt tree that yields zero roots no
|
||||
|
||||
@@ -489,8 +489,8 @@ function stageSkillsForProfile(srcDir: string, resolvedProfile: ResolvedProfile)
|
||||
* markers. It is still called, by `bin/install.js`'s `_stageAgents`, whose output
|
||||
* feeds the inline agent loop and `installCodexConfig`; both of those compose the
|
||||
* content themselves before writing, so the raw output never reaches disk. What
|
||||
* changed in #2995 is that `agentsKind` and `kimiAgentsKind` no longer use it —
|
||||
* they route through `stageAgentsForRuntimeWithConverter`, which composes.
|
||||
* changed in #2995 is that `agentsKind` no longer uses it —
|
||||
* it routes through `stageAgentsForRuntimeWithConverter`, which composes.
|
||||
*
|
||||
* The invariant to preserve: anything that takes this function's output and WRITES
|
||||
* it as a runtime artifact must call `composeWorkflow` on each file first, or it
|
||||
|
||||
@@ -282,8 +282,8 @@ const SPAWN_STDIN_STDOUT = {
|
||||
*
|
||||
* The `gemini` lane was retired by #4709: Google sunset Gemini CLI on 2026-06-18 (the same
|
||||
* sunset that removed the gemini RUNTIME in #1928/1.8.0), so the lane spawned a binary that no
|
||||
* longer serves the free/Pro/Ultra tiers that are MSD's audience. The `qwen` and `kimi-code`
|
||||
* lanes were retired together with their runtimes.
|
||||
* longer serves the free/Pro/Ultra tiers that are MSD's audience. The `qwen`
|
||||
* lane was retired together with its runtime.
|
||||
*/
|
||||
export const REVIEWER_LANES: ReadonlyArray<ReviewerLane> = Object.freeze([
|
||||
{
|
||||
|
||||
@@ -53,14 +53,10 @@
|
||||
*
|
||||
* 3. Both scopes are exercised for every runtime that declares a
|
||||
* per-scope `agents` kind (claude, codex, opencode × global+local). The prior revision was global-only, which
|
||||
* is exactly the class of gap that let two separate agents-drop
|
||||
* regressions reach `next` undetected: cline-local (fixed alongside
|
||||
* is exactly the class of gap that let an agents-drop
|
||||
* regression reach `next` undetected: cline-local (fixed alongside
|
||||
* this rewrite — capabilities/cline/capability.json's `local`
|
||||
* artifactLayout now declares an `agents` kind) and kimi-code-local
|
||||
* (fixed the same way — its `local` artifactLayout previously declared
|
||||
* no `agents` kind at all, so the deleted inline loop's implicit
|
||||
* scope-gate was silently replaced with NO gate, dropping every
|
||||
* kimi-code local install's agents/msd-*.md entirely).
|
||||
* artifactLayout now declares an `agents` kind).
|
||||
*
|
||||
* H8 is mandatory, not optional: a parity harness never demonstrated failing
|
||||
* is decoration. It feeds the oracle a DELIBERATELY WRONG (but real,
|
||||
|
||||
@@ -21,10 +21,9 @@
|
||||
*
|
||||
* 1. bin/install.js's inline agent loop — non-descriptor runtimes
|
||||
* 2. stageAgentsForRuntimeWithConverter — the 9 descriptor runtimes
|
||||
* 3. kimiAgentsKind's own readFileSync — kimi
|
||||
* 4. agentsKind (converter: null) — claude(local), zcode
|
||||
* 3. agentsKind (converter: null) — claude(local), zcode
|
||||
*
|
||||
* Point 4 never reads content into JS at all: `stageAgentsForProfile` returns a
|
||||
* Point 3 never reads content into JS at all: `stageAgentsForProfile` returns a
|
||||
* raw byte copy — or, under the DEFAULT `full` profile, the real unstaged
|
||||
* `agents/` directory itself — and `_copyStaged` copies bytes.
|
||||
*
|
||||
@@ -69,7 +68,7 @@ const MARKER_TOKEN = 'msd:section';
|
||||
|
||||
/** Path fragment identifying artifacts derived from the probe agent. Emitted
|
||||
* artifacts are named after the source agent on every runtime that emits them
|
||||
* (`.md`, Codex's `.toml`, Kimi's `subagents/<name>.{yaml,md}`), so this is the
|
||||
* (`.md`, Codex's `.toml`), so this is the
|
||||
* precise derived-artifact filter.
|
||||
*
|
||||
* Scoping matters: several SHIPPED library files under `msd-core/bin/lib/`
|
||||
|
||||
@@ -3899,7 +3899,7 @@ describe('ADR-1016 phase 5a: validateConfigHome unit tests', () => {
|
||||
});
|
||||
|
||||
test('valid generic-agents-root with probe → no errors', () => {
|
||||
const errors = validateConfigHome('test', { kind: 'generic-agents-root', name: 'agents', env: ['KIMI_CONFIG_DIR'], probe: ['~/.config/agents'] });
|
||||
const errors = validateConfigHome('test', { kind: 'generic-agents-root', name: 'agents', env: ['EXAMPLE_CONFIG_DIR'], probe: ['~/.config/agents'] });
|
||||
assert.deepEqual(errors, []);
|
||||
});
|
||||
|
||||
@@ -4183,7 +4183,7 @@ describe('FIX 3: tightened runtime validator — ArtifactKind field type checks'
|
||||
describe('FIX 3: tightened runtime validator — probeExists optional string', () => {
|
||||
test('probeExists: "skills" is accepted', () => {
|
||||
const errors = validateConfigHome('test', {
|
||||
kind: 'generic-agents-root', name: 'agents', env: ['KIMI_CONFIG_DIR'],
|
||||
kind: 'generic-agents-root', name: 'agents', env: ['EXAMPLE_CONFIG_DIR'],
|
||||
probe: ['~/.config/agents', '~/.agents'],
|
||||
probeExists: 'skills',
|
||||
});
|
||||
@@ -4214,7 +4214,7 @@ describe('FIX 3: tightened runtime validator — probeExists optional string', (
|
||||
|
||||
test('probeExists absent → no error (optional)', () => {
|
||||
const errors = validateConfigHome('test', {
|
||||
kind: 'generic-agents-root', name: 'agents', env: ['KIMI_CONFIG_DIR'],
|
||||
kind: 'generic-agents-root', name: 'agents', env: ['EXAMPLE_CONFIG_DIR'],
|
||||
probe: ['~/.config/agents'],
|
||||
});
|
||||
const probeExistsErrors = errors.filter((e) => e.includes('probeExists'));
|
||||
|
||||
@@ -1252,7 +1252,7 @@ describe('#3906: terminateNow', () => {
|
||||
assert.deepEqual(JSON.parse(r.stdout), { reason: 'blocked-by-guard', detail: 'x' });
|
||||
});
|
||||
|
||||
test('HOOK_DENY writes the SAME payload to both stdout and stderr (Kimi reads exit-2 output from stderr)', () => {
|
||||
test('HOOK_DENY writes the SAME payload to both stdout and stderr (some hosts read exit-2 output from stderr)', () => {
|
||||
const r = spawnTerminateNow([
|
||||
`const c = require(${JSON.stringify(BUILT_CLI_EXIT_PATH)});`,
|
||||
`c.terminateNow('HOOK_DENY', { reason: 'blocked' });`,
|
||||
|
||||
@@ -168,7 +168,7 @@ test('upgrade 3 — the pre-move location is migrated (stale ~/.codex/skills/msd
|
||||
const oldDir = install._resolveMovedSkillsOldDir('codex', codexHome, 'global');
|
||||
assert.equal(oldDir, path.join(codexHome, 'skills'), 'the pre-move location is $CODEX_HOME/skills');
|
||||
// A runtime with no home override yields null (no false migration).
|
||||
assert.equal(install._resolveMovedSkillsOldDir('kimi', path.join(os.homedir(), '.kimi'), 'global'), null);
|
||||
assert.equal(install._resolveMovedSkillsOldDir('claude', path.join(os.homedir(), '.claude'), 'global'), null);
|
||||
|
||||
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'codex-migrate-'));
|
||||
const skillsDir = path.join(tmp, 'skills');
|
||||
|
||||
@@ -93,7 +93,7 @@ test('a real Antigravity install emits a msd command/skill surface (invocable)',
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// #2096 EoS/antigravity — AC3/AC5: fail-closed negotiation + validator
|
||||
// acceptance + the folded descriptor (mirrors kimi/codex reference tests).
|
||||
// acceptance + the folded descriptor (mirrors the codex reference tests).
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// -- AC5: negotiation fails CLOSED on a corrupted descriptor ------------------
|
||||
|
||||
@@ -747,8 +747,8 @@ describe('#2615: the matrix documents the effortSurface axis', () => {
|
||||
|
||||
const declared = registry.runtimes[id].runtime.hostIntegration.effortSurface;
|
||||
if (declared === undefined) {
|
||||
// kimi-code declares no value: its mechanism (`/effort`) is interactive-only
|
||||
// and neither `argv` nor `none` describes it. The matrix must say so rather
|
||||
// A runtime may declare no value when its mechanism (e.g. an interactive-only
|
||||
// `/effort`) is neither `argv` nor `none`. The matrix must say so rather
|
||||
// than invent a value.
|
||||
assert.match(documented, /not declared/i,
|
||||
`${id}: an absent descriptor value must be documented as absent, not guessed (#2615)`);
|
||||
|
||||
@@ -202,8 +202,7 @@ test('a trailing-slash source entry matches by prefix, segment-aware', () => {
|
||||
assert.equal(sourceSatisfiedBy('a/b.md', new Set(['a/b.md'])), 'a/b.md');
|
||||
assert.equal(sourceSatisfiedBy('a/b.md', new Set(['a/b.md.bak'])), null);
|
||||
|
||||
// No shipped provenance rule declares a prefix source any more (the Kimi root agent
|
||||
// that did was removed with its runtime), so drive the REAL diffEmitted against a
|
||||
// No shipped provenance rule declares a prefix source any more, so drive the REAL diffEmitted against a
|
||||
// synthetic rule by swapping attributeEmittedPath in a freshly loaded emitted-diff.
|
||||
const provPath = require.resolve('./helpers/emitted-provenance.cjs');
|
||||
const diffPath = require.resolve('./helpers/emitted-diff.cjs');
|
||||
@@ -1622,11 +1621,11 @@ test('formatReport truncation is exact at limit-1 / limit / limit+1', () => {
|
||||
// ─── Independence + purity ───────────────────────────────────────────────────
|
||||
|
||||
test('the differential covers every runtime present in either manifest', () => {
|
||||
const baseline = { claude: { [WORKFLOW_KEY]: 'a' }, kimi: { [WORKFLOW_KEY]: 'a' } };
|
||||
const baseline = { claude: { [WORKFLOW_KEY]: 'a' }, cursor: { [WORKFLOW_KEY]: 'a' } };
|
||||
const current = { claude: { [WORKFLOW_KEY]: 'b' }, opencode: { [WORKFLOW_KEY]: 'c' } };
|
||||
const r = diffEmitted({ baseline, current, changedPaths: [] });
|
||||
const seen = new Set([...r.unattributable, ...r.attributed, ...r.removed].map((x) => x.runtime));
|
||||
assert.deepEqual([...seen].sort(), ['claude', 'kimi', 'opencode'],
|
||||
assert.deepEqual([...seen].sort(), ['claude', 'cursor', 'opencode'],
|
||||
'a runtime present on only one side must still be evaluated');
|
||||
});
|
||||
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
* The residual this does NOT close is false attribution — a rule can point at the
|
||||
* WRONG source and still be total. The spot-checks below pin the pairs where that
|
||||
* is most likely, and ADR-2719 designates the Phase 3 (#2723) dual-run as the
|
||||
* mitigation for the rest. Three real instances of that class were caught while
|
||||
* building this table (Copilot's `<name>.agent.md` rename, Kimi's `agents/msd.md`
|
||||
* root agent, and Copilot's `hooks/msd-session.json`), all of which passed totality
|
||||
* mitigation for the rest. Two real instances of that class were caught while
|
||||
* building this table (Copilot's `<name>.agent.md` rename and Copilot's
|
||||
* `hooks/msd-session.json`), both of which passed totality
|
||||
* while resolving to repo files that do not exist — which is why the
|
||||
* "every attributed source exists" test below is a first-class gate, not a nicety.
|
||||
*
|
||||
|
||||
@@ -81,7 +81,7 @@ test('install-shared.cjs exports the canonical buildParityManifest + exclusion c
|
||||
// ALL FIVE identifiers are guarded, not just buildParityManifest + VOLATILE_FILES:
|
||||
// the drift that shipped broken fixtures was a MISSING exclusion-constant entry
|
||||
// (#2100 = generator's HOOK_CONFIG_FILES copy lacked settings.local.json; #2095 =
|
||||
// kimi's HOOK_CONFIG_RELATIVE_PATHS entry), so a re-declared HOOK_CONFIG_FILES /
|
||||
// a runtime-specific HOOK_CONFIG_RELATIVE_PATHS entry), so a re-declared HOOK_CONFIG_FILES /
|
||||
// HOOK_CONFIG_RELATIVE_PATHS / EXCLUDED_PREFIXES is exactly the failure class this
|
||||
// guard exists to prevent — checking only two of four would leave that gap open.
|
||||
const FORBIDDEN_INLINE = [
|
||||
@@ -277,8 +277,8 @@ describe('#1575 — surface path: no prune data-loss over pre-existing legacy ag
|
||||
|
||||
test('runMinimalInstall resolves local config dirs from RUNTIME_META alone (#3031)', () => {
|
||||
// install-shared.cjs used to carry a SECOND, hand-maintained local-dir map
|
||||
// beside RUNTIME_META. It drifted: four runtimes present in RUNTIME_META
|
||||
// (hermes, kimi, kimi-code, zcode) were missing from it, so `scope: 'local'`
|
||||
// beside RUNTIME_META. It drifted: several runtimes present in RUNTIME_META
|
||||
// (e.g. hermes, zcode) were missing from it, so `scope: 'local'`
|
||||
// for any of them resolved `path.join(root, undefined)` and threw a bare
|
||||
// TypeError naming neither the runtime nor the map at fault. #3023 had
|
||||
// already hit this for `pi` and fixed it by adding one more entry, which
|
||||
|
||||
@@ -106,9 +106,9 @@ describe('withIsolatedProcessState', () => {
|
||||
// all of them, and they stay green.
|
||||
//
|
||||
// That is not hypothetical — it is how round 2 found MSD_HOME and
|
||||
// KIMI_SHARE_DIR while this block was fully green. MSD_HOME belonged to no
|
||||
// enumeration at all (it is MSD's own store root, not a runtime configHome);
|
||||
// KIMI_SHARE_DIR sat inside a function body where nothing could enumerate it.
|
||||
// a runtime-specific config-location var while this block was fully green.
|
||||
// MSD_HOME belonged to no enumeration at all (it is MSD's own store root, not a
|
||||
// runtime configHome); the other var sat inside a function body where nothing could enumerate it.
|
||||
// Round 3's fix was to make both enumerable rather than to add two assertions,
|
||||
// precisely because an assertion added per reviewer-named var is the
|
||||
// hand-maintained list wearing a test's clothes.
|
||||
@@ -250,7 +250,7 @@ describe('#2665: TEST_ENV_BASE config-location coverage', () => {
|
||||
// Named literally rather than derived from the family constant on purpose: a
|
||||
// test that reads WRITE_ESCAPE_PERMISSION_ENV_KEYS and asserts over it shrinks
|
||||
// its own expectation when the family is emptied — the enumeration-relative
|
||||
// failure this suite already documents, and the one that let the kimi-code
|
||||
// failure this suite already documents, and the one that let a
|
||||
// descriptor go unwatched. Naming it is what makes removal fail loudly.
|
||||
assert.strictEqual(
|
||||
TEST_ENV_BASE.MSD_ALLOW_SYMLINKED_DEST,
|
||||
|
||||
@@ -70,7 +70,7 @@ function builtLib() {
|
||||
// MSD_WORKSTREAM — planningDir() workstream segment (src/planning-workspace.cts)
|
||||
//
|
||||
// #2665 round 3: this list shrinks as sources become enumerable, and that direction
|
||||
// is the point. KIMI_SHARE_DIR was NOT added here — it now derives from
|
||||
// is the point. Descriptor-resolved vars were NOT added here — they now derive from
|
||||
// NON_REGISTRY_CONFIG_HOME_DESCRIPTORS, because hand-adding each var a reviewer
|
||||
// names is precisely what reopened this bug three times.
|
||||
const NON_REGISTRY_CONFIG_LOCATION_ENV_KEYS = [
|
||||
@@ -133,8 +133,8 @@ function configLocationEnvKeys() {
|
||||
...Object.values(runtimes).flatMap(
|
||||
(r) => r?.runtime?.configHome?.skillsHome?.env ?? [],
|
||||
),
|
||||
// 2. Descriptor-shaped config homes resolved OUTSIDE the registry (kimi's
|
||||
// native config.toml home via KIMI_SHARE_DIR). Derived, not hand-listed.
|
||||
// 2. Descriptor-shaped config homes resolved OUTSIDE the registry (a host's
|
||||
// native config.toml home). Derived, not hand-listed.
|
||||
// Same skillsHome walk as rung 1 — a descriptor is a descriptor.
|
||||
...NON_REGISTRY_CONFIG_HOME_DESCRIPTORS.flatMap((d) => [
|
||||
...(d?.env ?? []),
|
||||
|
||||
@@ -178,8 +178,8 @@ const REMEDIATION = Object.freeze({
|
||||
/**
|
||||
* Does a changed repo path satisfy a provenance `sources` entry?
|
||||
*
|
||||
* A trailing `/` marks a PREFIX (Phase 2's SOURCE_PREFIX_SUFFIX contract) — e.g. Kimi's
|
||||
* root agent aggregates all of `agents/`. Prefix matching is SEGMENT-AWARE on purpose:
|
||||
* A trailing `/` marks a PREFIX (Phase 2's SOURCE_PREFIX_SUFFIX contract) — e.g. an
|
||||
* aggregate artifact built from all of `agents/`. Prefix matching is SEGMENT-AWARE on purpose:
|
||||
* a bare `startsWith('agents/')` would also accept `agentsfoo/x.md` under a source of
|
||||
* `agents`, and silently over-attribute. Exact entries compare exactly.
|
||||
*/
|
||||
|
||||
@@ -200,7 +200,7 @@ const RUNTIME_NOTE_FILTERED_FAMILIES = new Set(
|
||||
/**
|
||||
* A `sources` entry ending in `/` is a PREFIX, not a file: it means "any repo path
|
||||
* under this directory legitimately explains this emitted path". Used where an
|
||||
* emitted artifact aggregates a whole directory (Kimi's root agent enumerates every
|
||||
* emitted artifact aggregates a whole directory (a root agent enumerating every
|
||||
* staged agent). Phase 3 must honor the trailing slash when testing a changed-path
|
||||
* set against these sources; a plain string is an exact path.
|
||||
*/
|
||||
@@ -338,8 +338,8 @@ const PROVENANCE_RULES = [
|
||||
// per-runtime `kind`) and why this wholesale reclassification was chosen instead.
|
||||
kind: 'derived',
|
||||
roots: ['agents'],
|
||||
// Excludes `msd.md`: that is Kimi's ROOT agent, built from a code literal and
|
||||
// NOT a repo agent file. Without the exclusion it matched here and resolved to
|
||||
// Excludes `msd.md`: that is a ROOT agent built from a code literal, NOT a
|
||||
// repo agent file. Without the exclusion it matched here and resolved to
|
||||
// `agents/msd.md`, which does not exist — a false attribution that still passed
|
||||
// totality, i.e. the exact residual ADR-2719 records. Every repo agent is
|
||||
// `msd-<name>.md`, so excluding the bare `msd.md` is precise.
|
||||
@@ -441,8 +441,7 @@ const PROVENANCE_RULES = [
|
||||
// Sources are scoped PER ROOT, not declared as one flat union. Each root has
|
||||
// exactly one writer besides the shared marker module, and a flat list would
|
||||
// attribute every root to all of them — `extensions/package.json` to the
|
||||
// hooks-surface writer that never touches it, `.kimi/hooks/package.json` to
|
||||
// the native-plugin writer, and so on. That matters because
|
||||
// hooks-surface writer that never touches it, and so on. That matters because
|
||||
// `emitted-diff.cjs` accepts the FIRST satisfied source: a flat list
|
||||
// containing `bin/install.js` lets any change anywhere in that 13k-line file
|
||||
// authorise marker drift for every root — the blanket escape hatch this
|
||||
@@ -451,7 +450,6 @@ const PROVENANCE_RULES = [
|
||||
// hooks/ (shared bundle) -> bin/install.js (installSharedHooksBundle)
|
||||
// hooks/ (#2717 runtimes) -> src/runtime-hooks-surface.cts + bin/install.js
|
||||
// (the codex copy block calls the exported helper)
|
||||
// .kimi/hooks/ -> bin/install.js (the kimi hooks-root bundle)
|
||||
// plugins/, extensions/ -> src/install-engine.cts
|
||||
// (_installNativePluginIfDeclared)
|
||||
//
|
||||
@@ -467,7 +465,7 @@ const PROVENANCE_RULES = [
|
||||
if (root === 'plugins' || root === 'extensions') {
|
||||
return [COMMONJS_MARKER_SRC, INSTALL_ENGINE_SRC];
|
||||
}
|
||||
// Both Kimi products install the shared bundle into their own native hook
|
||||
// Some products install the shared bundle into their own native hook
|
||||
// root rather than under the generic Agent-Skills configDir (#2755).
|
||||
if (root === '.kimi/hooks' || root === '.kimi-code/hooks') {
|
||||
return [COMMONJS_MARKER_SRC, INSTALLER_SRC];
|
||||
@@ -561,7 +559,7 @@ const PROVENANCE_RULES = [
|
||||
// real global subdirectory those files sit outside the walked configDir,
|
||||
// the rules matched nothing, and the totality guard's dead-rule arm fired
|
||||
// — exactly as designed. The files are still written, still covered by the
|
||||
// existence/config suites (kimi-upgrades, codex-config, install suites);
|
||||
// existence/config suites (codex-config, install suites);
|
||||
// they are simply no longer manifest members to attribute.
|
||||
|
||||
// ── Synthesized: install-time / environment state, no repo content source ──
|
||||
@@ -569,10 +567,6 @@ const PROVENANCE_RULES = [
|
||||
id: 'synthesized-install-metadata',
|
||||
kind: 'synthesized',
|
||||
roots: null,
|
||||
// `.kimi/package.json` and `.kimi-code/package.json` are the same literal
|
||||
// `{"type":"commonjs"}` CommonJS-mode marker as the root one, written into
|
||||
// each Kimi product's separate hooks root (installSharedHooksBundle; the
|
||||
// per-runtime root split is #2755).
|
||||
pattern: /^(\.msd-profile|package\.json|msd-core\/VERSION|msd-core\/\.msd-runtime)$/,
|
||||
sources: () => [],
|
||||
},
|
||||
|
||||
@@ -169,30 +169,16 @@ const VOLATILE_FILES = new Set([
|
||||
// node-runner command as settings.json, so excluded for the same reason (#2086).
|
||||
const HOOK_CONFIG_FILES = new Set(['settings.json', 'settings.local.json', 'hooks.json']);
|
||||
|
||||
// Kimi's native config.toml (#2095 EoS/kimi Upgrade 1) embeds the same
|
||||
// platform-varying node-runner command as the HOOK_CONFIG_FILES above (via the
|
||||
// same buildHookCommand/projectManagedHookCommand machinery), so it needs the
|
||||
// same exclusion — but it is NOT matched by basename like HOOK_CONFIG_FILES:
|
||||
// Codex's OWN config.toml (installSurface 'codex-toml') is a stable, tracked
|
||||
// top-level `config.toml` entry in its golden fixture (it only ever gets a
|
||||
// platform-stable `[features] hooks = true` flag — the real hook commands
|
||||
// live in Codex's separate hooks.json, already excluded above). Blanket-
|
||||
// excluding the 'config.toml' basename would silently blind Codex's fixture
|
||||
// to any future regression there — and it would blind kimi-code's too: since
|
||||
// #3547 the harness installs into each runtime's REAL global subdirectory, so
|
||||
// kimi-code's hooks config.toml sits at its configDir root (rel `config.toml`)
|
||||
// and is legitimately manifest-visible. Tracking it is safe now: the
|
||||
// install-tree fixture carries paths only, and the ADR-2719 differential
|
||||
// compares base-vs-current on the same machine, so the platform-varying
|
||||
// node-runner command embedded in the TOML never crosses platforms inside a
|
||||
// gate (that was a golden-content-era hazard, and the goldens are gone).
|
||||
// Kimi CLI's config.toml (KIMI_SHARE_DIR root ~/.kimi) lives OUTSIDE its MSD
|
||||
// configDir (~/.config/agents) and never enters the walk. The pre-#3547
|
||||
// relative-path exclusions ('.kimi/config.toml', '.kimi-code/config.toml')
|
||||
// existed only for the collapsed shape — where the walked root was the HOME
|
||||
// itself and those HOME-level siblings were inside it; with no walker rooting
|
||||
// at HOME anymore they matched nothing and were removed (#3547). kimi-code
|
||||
// resolves its own root since #2755.
|
||||
// Native config.toml files are deliberately NOT excluded by basename like the
|
||||
// HOOK_CONFIG_FILES above: Codex's OWN config.toml (installSurface 'codex-toml') is
|
||||
// a stable, tracked top-level `config.toml` entry in its golden fixture (it only
|
||||
// ever gets a platform-stable `[features] hooks = true` flag — the real hook
|
||||
// commands live in Codex's separate hooks.json, already excluded above).
|
||||
// Blanket-excluding the 'config.toml' basename would silently blind Codex's
|
||||
// fixture to any future regression there. Relative-path exclusions existed only
|
||||
// for the pre-#3547 collapsed shape, where the walked root was the HOME itself
|
||||
// and HOME-level siblings were inside it; with no walker rooting at HOME anymore
|
||||
// they matched nothing and were removed (#3547).
|
||||
const HOOK_CONFIG_RELATIVE_PATHS = new Set();
|
||||
|
||||
// Path prefixes excluded from the parity manifest. `msd-core/bin/lib/` holds the
|
||||
@@ -637,7 +623,7 @@ function runMinimalInstall({ runtime, scope, extraArgs = [], installScript = INS
|
||||
cwd = root;
|
||||
// #3031: local scope reads RUNTIME_META.localDir — the SAME table the
|
||||
// global branch above reads — instead of a second hand-maintained map.
|
||||
// That duplicate map was missing four runtimes (hermes, kimi, kimi-code,
|
||||
// That duplicate map was missing several runtimes (e.g. hermes,
|
||||
// zcode), so `scope: 'local'` for any of them resolved
|
||||
// `path.join(root, undefined)` and threw a bare TypeError naming neither
|
||||
// the runtime nor the map at fault. #3023 fixed exactly this for `pi` by
|
||||
|
||||
@@ -30,8 +30,7 @@
|
||||
*
|
||||
* Crash trigger: malformed JSON on stdin ('{not json'). Every one of the 9
|
||||
* hooks that declares an ON_CRASH policy parses its stdin payload as the
|
||||
* FIRST statement inside its outer try — `JSON.parse(input)` (or the Kimi-
|
||||
* normalized `normalizeKimiPayload(JSON.parse(input))`) — so a syntax error
|
||||
* FIRST statement inside its outer try — `JSON.parse(input)` — so a syntax error
|
||||
* there throws before any applicability logic runs and is a real, hook-
|
||||
* authored crash, not a synthetic fault injected by this suite.
|
||||
*/
|
||||
|
||||
@@ -1587,24 +1587,21 @@ describe('resolveOrchestratorExec — the 4 shipped orchestrator-worktree descri
|
||||
assert.equal(result.cwd, CWD);
|
||||
});
|
||||
|
||||
// #2627: `args` carries --print because kimi's working mode is otherwise the
|
||||
// #2627: `args` carries --print because some hosts' working mode is otherwise the
|
||||
// interactive TUI — an orchestrator spawning a TUI hangs forever rather than
|
||||
// returning a completed plan.
|
||||
test('kimi: --print --work-dir <cwd> (headless flag leads)', () => {
|
||||
const result = resolveOrchestratorExec({ command: 'kimi', args: ['--print'], cwdFlag: '--work-dir' }, CWD);
|
||||
test('--print --work-dir <cwd> (headless flag leads)', () => {
|
||||
const result = resolveOrchestratorExec({ command: 'hostcli', args: ['--print'], cwdFlag: '--work-dir' }, CWD);
|
||||
assert.equal(result.ok, true);
|
||||
assert.equal(result.command, 'kimi');
|
||||
assert.equal(result.command, 'hostcli');
|
||||
assert.deepEqual(result.args, ['--print', '--work-dir', CWD]);
|
||||
assert.equal(result.cwd, CWD);
|
||||
});
|
||||
|
||||
// #2627: the binary is `kimi`, NOT `kimi-code` — Moonshot's TypeScript Kimi
|
||||
// Code installs its binary as `kimi`; `kimi-code` is only the npm package and
|
||||
// config-home name, so spawning it is an immediate ENOENT.
|
||||
test('kimi-code: command is "kimi"; cwdFlag:null appends NO flag, cwd still returned (process-cwd case)', () => {
|
||||
const result = resolveOrchestratorExec({ command: 'kimi', args: [], cwdFlag: null }, CWD);
|
||||
test('cwdFlag:null appends NO flag, cwd still returned (process-cwd case)', () => {
|
||||
const result = resolveOrchestratorExec({ command: 'hostcli', args: [], cwdFlag: null }, CWD);
|
||||
assert.equal(result.ok, true);
|
||||
assert.equal(result.command, 'kimi');
|
||||
assert.equal(result.command, 'hostcli');
|
||||
assert.deepEqual(result.args, []);
|
||||
assert.equal(result.cwd, CWD);
|
||||
});
|
||||
@@ -1630,17 +1627,17 @@ describe('resolveOrchestratorExec — prompt passing (#2627, Phase 3)', () => {
|
||||
assert.deepEqual(result.args, ['run', '--dir', CWD, PROMPT]);
|
||||
});
|
||||
|
||||
test('promptFlag string → [flag, prompt] appended (kimi shape)', () => {
|
||||
test('promptFlag string → [flag, prompt] appended (flag-carried prompt shape)', () => {
|
||||
const result = resolveOrchestratorExec(
|
||||
{ command: 'kimi', args: ['--print'], cwdFlag: '--work-dir', promptFlag: '--prompt' }, CWD, PROMPT,
|
||||
{ command: 'hostcli', args: ['--print'], cwdFlag: '--work-dir', promptFlag: '--prompt' }, CWD, PROMPT,
|
||||
);
|
||||
assert.equal(result.ok, true);
|
||||
assert.deepEqual(result.args, ['--print', '--work-dir', CWD, '--prompt', PROMPT]);
|
||||
});
|
||||
|
||||
test('promptFlag string + cwdFlag null → prompt flag only, cwd via process cwd (kimi-code shape)', () => {
|
||||
test('promptFlag string + cwdFlag null → prompt flag only, cwd via process cwd (process-cwd shape)', () => {
|
||||
const result = resolveOrchestratorExec(
|
||||
{ command: 'kimi', args: [], cwdFlag: null, promptFlag: '--prompt' }, CWD, PROMPT,
|
||||
{ command: 'hostcli', args: [], cwdFlag: null, promptFlag: '--prompt' }, CWD, PROMPT,
|
||||
);
|
||||
assert.equal(result.ok, true);
|
||||
assert.deepEqual(result.args, ['--prompt', PROMPT]);
|
||||
@@ -2045,7 +2042,7 @@ describe('#2584 orchestratorExec — validator', () => {
|
||||
|
||||
test('cwdFlag: null is valid (no error)', () => {
|
||||
const cap = shippedCodexCapabilityWithoutOrchestratorExec();
|
||||
cap.runtime.orchestratorExec = { command: 'kimi-code', args: [], cwdFlag: null };
|
||||
cap.runtime.orchestratorExec = { command: 'hostcli', args: [], cwdFlag: null };
|
||||
const errors = validateCapability(cap, 'codex');
|
||||
const oeErrors = errors.filter((e) => e.includes('orchestratorExec'));
|
||||
assert.deepEqual(oeErrors, []);
|
||||
@@ -2227,10 +2224,10 @@ describe('#3714 resolveOrchestratorExec — modelFlag/model seam (mechanical, RE
|
||||
});
|
||||
|
||||
// MATRIX row 7 [CONTROL]: a host with no modelFlag is byte-identical to
|
||||
// today whether or not a model is passed — kimi-code/opencode never get a
|
||||
// today whether or not a model is passed — opencode never gets a
|
||||
// 5th positional token nor a flag pair injected.
|
||||
test('row 7 CONTROL: a host descriptor with NO modelFlag key resolves identically whether or not a model is passed', () => {
|
||||
const descriptor = { command: 'kimi', args: ['--print'], cwdFlag: '--work-dir', promptFlag: '--prompt' };
|
||||
const descriptor = { command: 'hostcli', args: ['--print'], cwdFlag: '--work-dir', promptFlag: '--prompt' };
|
||||
const withoutModelArg = resolveOrchestratorExec(descriptor, CWD, PROMPT);
|
||||
const withModelArg = resolveOrchestratorExec(descriptor, CWD, PROMPT, 'some-model');
|
||||
assert.deepEqual(withModelArg, withoutModelArg,
|
||||
|
||||
@@ -249,9 +249,9 @@ describe('resolveReportedRuntime: precedence', () => {
|
||||
|
||||
test('invalid MSD_RUNTIME falls through to config', (t) => {
|
||||
const tmpDir = withProject(t);
|
||||
writeConfig(tmpDir, JSON.stringify({ runtime: 'kimi' }));
|
||||
writeConfig(tmpDir, JSON.stringify({ runtime: 'cursor' }));
|
||||
const result = resolveReportedRuntime(tmpDir, { env: { MSD_RUNTIME: ' ' } });
|
||||
assert.strictEqual(result, 'kimi');
|
||||
assert.strictEqual(result, 'cursor');
|
||||
});
|
||||
|
||||
test('explicit config runtime outranks detection (#2517 preserved)', (t) => {
|
||||
@@ -307,10 +307,10 @@ describe('resolveReportedRuntime: precedence', () => {
|
||||
|
||||
test('crlf config json is unaffected', (t) => {
|
||||
const tmpDir = withProject(t);
|
||||
const crlfConfig = ['{', ' "runtime": "kimi"', '}'].join('\r\n');
|
||||
const crlfConfig = ['{', ' "runtime": "cursor"', '}'].join('\r\n');
|
||||
writeConfig(tmpDir, crlfConfig);
|
||||
const result = resolveReportedRuntime(tmpDir, { env: { CODEX_SANDBOX: 'seatbelt' } });
|
||||
assert.strictEqual(result, 'kimi');
|
||||
assert.strictEqual(result, 'cursor');
|
||||
});
|
||||
|
||||
// Deliberately NOT a "windows-shaped CODEX_HOME" test computed with
|
||||
@@ -447,8 +447,8 @@ describe('runtime-slash: frozen contract (#3245 extraction must not move it)', (
|
||||
writeConfig(tmpDir, JSON.stringify({ runtime: 'opencode' }));
|
||||
assert.strictEqual(resolveRuntime(tmpDir), 'opencode');
|
||||
|
||||
process.env.MSD_RUNTIME = 'kimi';
|
||||
assert.strictEqual(resolveRuntime(tmpDir), 'kimi');
|
||||
process.env.MSD_RUNTIME = 'cursor';
|
||||
assert.strictEqual(resolveRuntime(tmpDir), 'cursor');
|
||||
});
|
||||
|
||||
test('resolveRuntime ignores a codex session signal', (t) => {
|
||||
@@ -644,11 +644,11 @@ describe('resolveReportedRuntime: install-marker rung (#4717)', () => {
|
||||
|
||||
test('explicit config runtime still outranks the marker (#4717 ladder rung 1)', (t) => {
|
||||
const tmpDir = withProject4717(t);
|
||||
writeConfig4717(tmpDir, 'kimi');
|
||||
writeConfig4717(tmpDir, 'cursor');
|
||||
slash._setInstallRuntimeMarkerForTests('codex');
|
||||
t.after(() => slash._resetInstallRuntimeMarkerCacheForTests());
|
||||
const result = resolveReportedRuntime(tmpDir, { env: {} });
|
||||
assert.strictEqual(result, 'kimi');
|
||||
assert.strictEqual(result, 'cursor');
|
||||
});
|
||||
|
||||
test('no marker: host detection unchanged (dev/source trees, pre-#2297 installs)', (t) => {
|
||||
|
||||
@@ -647,13 +647,13 @@ describe('lint-retired-runtime-name — a version number never launders a claim'
|
||||
['a version ending the sentence', 'docs/guides/b1.md',
|
||||
`The installer now offers ${RETIRED_NAME} 3.`],
|
||||
['a version inside a runtime list', 'docs/guides/b2.md',
|
||||
`MSD installs cleanly on ${RETIRED_NAME} 3, Kimi, and Codex.`],
|
||||
`MSD installs cleanly on ${RETIRED_NAME} 3, Cline, and Codex.`],
|
||||
['a version in parentheses', 'docs/guides/b3.md',
|
||||
`Pick your coding tool (${RETIRED_NAME} 3) during setup.`],
|
||||
['a "plugins" list — "plugin" is not a runtime word', 'docs/guides/c3.md',
|
||||
`Supported plugins: Claude Code, Codex, ${RETIRED_NAME} 3.`],
|
||||
['an "agents" list — "agent" is deliberately not a runtime word', 'docs/guides/c4.md',
|
||||
`Supported agents include ${RETIRED_NAME} 3, Kimi, and Cursor.`],
|
||||
`Supported agents include ${RETIRED_NAME} 3, Cline, and Cursor.`],
|
||||
];
|
||||
|
||||
for (const [label, relPath, body] of CASES) {
|
||||
|
||||
@@ -496,7 +496,7 @@ describe('#2665: guard watches non-root write surfaces', () => {
|
||||
// Every descriptor in the array must contribute a target. Calling one
|
||||
// named resolver instead would cover one of today's two entries and silently
|
||||
// miss tomorrow's — the same partial-enumeration defect that put
|
||||
// KIMI_SHARE_DIR outside the scrub set, one layer over.
|
||||
// a config-location var outside the scrub set, one layer over.
|
||||
//
|
||||
// SCOPE BOUNDARY (per round-2 Nit 7, and it bites here): this asserts one
|
||||
// target PER DESCRIPTOR and nothing about whether one target per descriptor
|
||||
|
||||
@@ -183,7 +183,7 @@ describe('msd-agent-isolation-guard.js: applicability matrix (#3045)', () => {
|
||||
assert.equal(out.decision, 'block');
|
||||
assert.match(out.reason, /harness-worktree/);
|
||||
assert.match(out.reason, /isolation="worktree"/);
|
||||
assert.equal(r.stderr, out.reason, 'stderr must carry the same reason (Kimi reads stderr on exit 2)');
|
||||
assert.equal(r.stderr, out.reason, 'stderr must carry the same reason (some hosts read stderr on exit 2)');
|
||||
});
|
||||
|
||||
test('row 2: isolation="worktree" present -> allow', () => {
|
||||
|
||||
@@ -211,7 +211,7 @@ describe('plan-review-convergence: --agy/--antigravity reviewer whitelist (#2293
|
||||
// #2800: the runtime contract used to be a hand-written grep-accumulation
|
||||
// block — one `grep -q '\-\-<flag>'` line per recognized flag. Absence meant
|
||||
// the flag was silently dropped, and that whitelist drifted three separate
|
||||
// times (--coderabbit, then --qwen/--cursor/--kimi-code, then the unanchored
|
||||
// times (--coderabbit, then --qwen/--cursor, then the unanchored
|
||||
// --agy pattern matching inside --antigravity). The fix derives the whitelist
|
||||
// structurally from the declared lane roster via `msd_run review-lane flags`,
|
||||
// so --agy/--antigravity (and every other lane flag) are guaranteed reachable
|
||||
|
||||
@@ -49,7 +49,7 @@ const INSTALL_JS_PATH_HELPERS = [
|
||||
// #2088 (ADR-1239 upgrade 3): resolves the skills-install dir honoring a
|
||||
// skills-kind `home` override (e.g. Codex → $HOME/.agents/skills).
|
||||
'_resolveSkillsRootDir',
|
||||
// #3664: shared kind-destination resolver (skills/agents/kimi-agents kinds).
|
||||
// #3664: shared kind-destination resolver (skills/agents kinds).
|
||||
'_kindDestDir',
|
||||
];
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ describe('resolveDispatchType (pure function, #2508 Phase 4 Option A)', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('built-in-only runtimes (namedDispatch: false, e.g. kimi-code)', () => {
|
||||
describe('built-in-only runtimes (namedDispatch: false, e.g. hosts with only built-in agents)', () => {
|
||||
test('maps -planner / -roadmapper / -selector / -spec suffixes to plan', () => {
|
||||
assert.equal(resolveDispatchType('msd-planner', { namedDispatch: false }), 'plan');
|
||||
assert.equal(resolveDispatchType('msd-roadmapper', { namedDispatch: false }), 'plan');
|
||||
|
||||
@@ -107,9 +107,9 @@ describe('reviewer lane parity — the shipped repo', () => {
|
||||
|
||||
describe('reviewer lane parity — descriptor vs roster', () => {
|
||||
test('a roster slug with no descriptor entry is a violation', () => {
|
||||
const r = check({ roster: [...KNOWN_REVIEWER_SLUGS, 'kimi_code'] });
|
||||
const r = check({ roster: [...KNOWN_REVIEWER_SLUGS, 'unknown_lane'] });
|
||||
assert.deepStrictEqual(reasons(r), [
|
||||
`${PARITY_VIOLATION.ROSTER_SLUG_UNDECLARED}:kimi_code`,
|
||||
`${PARITY_VIOLATION.ROSTER_SLUG_UNDECLARED}:unknown_lane`,
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
@@ -170,7 +170,7 @@ describe('runner — probe (ADR-2782 D7)', () => {
|
||||
});
|
||||
|
||||
test('a capability probe that times out reports unavailable, never hangs', async () => {
|
||||
// The original probe (closed PR #2776) was an unbounded `kimi --help | grep` that ran on EVERY
|
||||
// The original probe (closed PR #2776) was an unbounded `<binary> --help | grep` that ran on EVERY
|
||||
// review regardless of flags — a live instance of the named Unbounded Subprocesses defect.
|
||||
const p = capabilityPlan();
|
||||
const r = await probeLane(p, deps({
|
||||
|
||||
@@ -387,7 +387,7 @@ describe('exclusivity gate sees dynamic patterns (#2797)', () => {
|
||||
//
|
||||
// Two independent defects, per .msd/bug/fix-3691-reviewer-prompt-budget/10-diagnosis.md:
|
||||
// (1) every `transport: spawn` lane (claude, coderabbit, antigravity, cursor, gemini,
|
||||
// codex, kimi-code, opencode, qwen) declares `promptBudgetKey: null`, so
|
||||
// codex, opencode, qwen) declares `promptBudgetKey: null`, so
|
||||
// `budgetFor` (msd-core/bin/msd-tools.cjs) returns null for them unconditionally;
|
||||
// (2) `review.max_prompt_tokens` is documented and in validKeys but
|
||||
// `config-defaults.manifest.json` has no `review` section, so the resolved
|
||||
|
||||
@@ -413,17 +413,17 @@ describe('reviewer docs parity — HTML comment stripping (CodeQL js/incomplete-
|
||||
test('aNestedCommentCannotSmuggleAFlagPastTheStrip', () => {
|
||||
// `<!-` + `<!--DUMMY-->` + `-| `--opencode` | d |-->`: one pass strips the self-contained
|
||||
// `<!--DUMMY-->` in the middle, which joins the leftover `<!-` and `-` into a brand-new
|
||||
// `<!--` opener immediately followed by the kimi-code row and a real `-->` closer. A
|
||||
// `<!--` opener immediately followed by the `--opencode` row and a real `-->` closer. A
|
||||
// single-pass strip leaves that whole new span — row included — untouched in the output, so
|
||||
// the row reads as documented. Regressed exactly here: verified against the pre-fix
|
||||
// single-pass implementation, only `TABLE_ROW_MISSING` fired and `DOC_FLAG_MISSING` did not.
|
||||
const joinLine = '<!-<!--DUMMY-->-| `--opencode` | d |-->';
|
||||
const doc = ['### `/msd-review`', '', ...NON_VICTIM_ROWS, '', joinLine, ''].join('\n');
|
||||
const r = checkReviewerDocsParity({ descriptor: REVIEWER_LANES, docs: { d: doc } });
|
||||
const kimi = r.violations.filter((v) => v.subject === '--opencode');
|
||||
const flagViolations = r.violations.filter((v) => v.subject === '--opencode');
|
||||
assert.ok(
|
||||
kimi.some((v) => v.reason === DOCS_PARITY_VIOLATION.DOC_FLAG_MISSING)
|
||||
|| kimi.some((v) => v.reason === DOCS_PARITY_VIOLATION.TABLE_ROW_MISSING),
|
||||
flagViolations.some((v) => v.reason === DOCS_PARITY_VIOLATION.DOC_FLAG_MISSING)
|
||||
|| flagViolations.some((v) => v.reason === DOCS_PARITY_VIOLATION.TABLE_ROW_MISSING),
|
||||
'--opencode must still be reported missing once the join-trick comment is fully stripped',
|
||||
);
|
||||
});
|
||||
@@ -439,8 +439,8 @@ describe('reviewer docs parity — HTML comment stripping (CodeQL js/incomplete-
|
||||
'',
|
||||
].join('\n');
|
||||
const r = checkReviewerDocsParity({ descriptor: REVIEWER_LANES, docs: { d: doc } });
|
||||
const kimi = r.violations.filter((v) => v.subject === '--opencode');
|
||||
assert.ok(kimi.length > 0, '--opencode sits inside an open comment and must be reported missing');
|
||||
const flagViolations = r.violations.filter((v) => v.subject === '--opencode');
|
||||
assert.ok(flagViolations.length > 0, '--opencode sits inside an open comment and must be reported missing');
|
||||
});
|
||||
|
||||
test('aCommentClosedOnALaterLineResumesCorrectly', () => {
|
||||
@@ -458,16 +458,16 @@ describe('reviewer docs parity — HTML comment stripping (CodeQL js/incomplete-
|
||||
'',
|
||||
].join('\n');
|
||||
const r = checkReviewerDocsParity({ descriptor: REVIEWER_LANES, docs: { d: doc } });
|
||||
const kimi = r.violations.filter((v) => v.subject === '--opencode');
|
||||
assert.deepStrictEqual(kimi, [], 'content after a real comment close must not be treated as commented out');
|
||||
const flagViolations = r.violations.filter((v) => v.subject === '--opencode');
|
||||
assert.deepStrictEqual(flagViolations, [], 'content after a real comment close must not be treated as commented out');
|
||||
});
|
||||
|
||||
test('multipleIndependentCommentsOnOneLineAreAllStripped', () => {
|
||||
const line = 'A <!-- first --> B <!-- | `--opencode` | d | -->';
|
||||
const doc = ['### `/msd-review`', '', ...NON_VICTIM_ROWS, '', line, ''].join('\n');
|
||||
const r = checkReviewerDocsParity({ descriptor: REVIEWER_LANES, docs: { d: doc } });
|
||||
const kimi = r.violations.filter((v) => v.subject === '--opencode');
|
||||
assert.ok(kimi.length > 0, 'both independent comment spans on one line must be stripped');
|
||||
const flagViolations = r.violations.filter((v) => v.subject === '--opencode');
|
||||
assert.ok(flagViolations.length > 0, 'both independent comment spans on one line must be stripped');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
* the old switch's scope-agnostic behaviour.
|
||||
*
|
||||
* For runtimes that had explicit scope branches in the old switch
|
||||
* (claude: distinct local=commands+agents; cline: local=[]; kimi: local=[]),
|
||||
* (claude: distinct local=commands+agents; cline: local=[]),
|
||||
* the STEP-0 golden matches the descriptor exactly and is left unchanged.
|
||||
*
|
||||
* Unknown runtime case:
|
||||
|
||||
@@ -1381,7 +1381,7 @@ describe('skills-kind destination parity: installer vs surface-apply (#2911)', (
|
||||
return; // runtime/scope combination not supported
|
||||
}
|
||||
const skillsKind = layout.kinds.find((k) => k.kind === 'skills');
|
||||
if (!skillsKind) return; // e.g. cline/kimi at local scope: no skills kind
|
||||
if (!skillsKind) return; // e.g. cline at local scope: no skills kind
|
||||
|
||||
if (typeof skillsKind.home === 'string' && skillsKind.home !== '') {
|
||||
discriminatingRuntimes++;
|
||||
|
||||
@@ -24,11 +24,10 @@ const EXPECTED_FLAGS = [
|
||||
const NON_INSTALLABLE_RUNTIMES = new Set(['vscode']);
|
||||
|
||||
test('runtimeFlags: every known non-claude runtime sets exactly its own flag true', () => {
|
||||
// Convert a flag name (camelCase, e.g. isKimiCode) to the runtime id it
|
||||
// represents (kimi-code). Strip the 'is' prefix, then split camelCase
|
||||
// Convert a flag name (camelCase, e.g. isFooBar) to the runtime id it
|
||||
// represents (foo-bar). Strip the 'is' prefix, then split camelCase
|
||||
// boundaries into hyphen-separated lowercase words. The simpler
|
||||
// slice(2).toLowerCase() worked while every runtime id was a single word;
|
||||
// kimi-code is the first hyphenated id.
|
||||
// slice(2).toLowerCase() only works while every runtime id is a single word.
|
||||
const flagToId = (flag) => flag.slice(2)
|
||||
.replace(/[A-Z]/g, (m, i) => (i > 0 ? '-' : '') + m.toLowerCase());
|
||||
const idToFlag = (id) => 'is' + id.charAt(0).toUpperCase() + id.slice(1).replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
||||
@@ -66,7 +65,7 @@ test('runtimeFlags drift guard: covers every registry runtime except claude and
|
||||
// flag by design and must not trip this guard.
|
||||
//
|
||||
// #2454: the flag-name → runtime-id conversion must fold PascalCase boundaries
|
||||
// back to kebab-case (isKimiCode → kimi-code, not 'kimicode').
|
||||
// back to kebab-case (isFooBar → foo-bar, not 'foobar').
|
||||
const flagToId = (flag) => flag.slice(2)
|
||||
.replace(/[A-Z]/g, (m, i) => (i > 0 ? '-' : '') + m.toLowerCase());
|
||||
const registryNonClaude = Object.keys(registry.runtimes)
|
||||
|
||||
@@ -78,8 +78,6 @@ function restoreEnvKeys(saved) {
|
||||
// ── STEP 0: golden scenarios captured from old switch BEFORE edits ────────────
|
||||
|
||||
// GOLDEN DEFAULTS (no env vars set, no existsSync probe hits).
|
||||
// kimi is NOT included here because it depends on real filesystem probing —
|
||||
// its probe-miss/hit scenarios are covered separately via injected existsSync.
|
||||
// antigravity default also depends on probing; the default assumes NO dirs exist.
|
||||
const GOLDEN_DEFAULTS = {
|
||||
claude: path.join(HOME, '.claude'),
|
||||
@@ -304,11 +302,11 @@ describe('#3023 review finding 1: whitespace-only env override falls back to def
|
||||
{
|
||||
kind: 'generic-agents-root',
|
||||
name: 'agents',
|
||||
env: ['KIMI_CONFIG_DIR'],
|
||||
env: ['EXAMPLE_CONFIG_DIR'],
|
||||
probe: ['~/.config/agents', '~/.agents'],
|
||||
probeExists: 'skills',
|
||||
},
|
||||
{ env: { KIMI_CONFIG_DIR: ' ' }, home: '/home/u', existsSync: () => false },
|
||||
{ env: { EXAMPLE_CONFIG_DIR: ' ' }, home: '/home/u', existsSync: () => false },
|
||||
);
|
||||
assert.strictEqual(result, path.join('/home/u', '.config', 'agents'));
|
||||
});
|
||||
@@ -676,7 +674,7 @@ describe('descriptor-driven equivalence: explicitDir short-circuit', () => {
|
||||
test('explicitDir absolute path returned as-is (any runtime)', () => {
|
||||
assert.strictEqual(String(getGlobalConfigDir('claude', '/tmp/explicit')).replace(/\\/g, '/'), '/tmp/explicit');
|
||||
assert.strictEqual(String(getGlobalConfigDir('opencode', '/tmp/explicit')).replace(/\\/g, '/'), '/tmp/explicit');
|
||||
assert.strictEqual(String(getGlobalConfigDir('kimi', '/tmp/explicit')).replace(/\\/g, '/'), '/tmp/explicit');
|
||||
assert.strictEqual(String(getGlobalConfigDir('cursor', '/tmp/explicit')).replace(/\\/g, '/'), '/tmp/explicit');
|
||||
assert.strictEqual(String(getGlobalConfigDir('grok', '/tmp/explicit')).replace(/\\/g, '/'), '/tmp/explicit');
|
||||
});
|
||||
|
||||
@@ -769,7 +767,6 @@ describe('descriptor-driven parity: 13 non-probe registry runtimes × no-env-var
|
||||
// This is the hardest assertion: it drives getGlobalConfigDir() (which calls
|
||||
// the registry internally) and compares against GOLDEN_DEFAULTS captured from
|
||||
// the old switch. Any discrepancy means a regression.
|
||||
// kimi is excluded because its default depends on real filesystem probing.
|
||||
// antigravity is excluded because it also depends on real fs probing — a machine
|
||||
// with ~/.gemini/antigravity-ide or ~/.gemini/antigravity-cli (but not
|
||||
// ~/.gemini/antigravity) gets a different result. Probe scenarios are covered in
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
* snapshot here.
|
||||
*
|
||||
* Voice: these SHORT UI labels are intentionally distinct from the descriptor
|
||||
* `title` (the long product name). Two prior-chain inconsistencies are resolved
|
||||
* by the canonical map: kimi → 'Kimi CLI'; cline → 'Cline'.
|
||||
* `title` (the long product name). One prior-chain inconsistency is resolved
|
||||
* by the canonical map: cline → 'Cline'.
|
||||
*
|
||||
* ADR-1239 Phase B (#1679). Behavioral tests only: assert on returned values.
|
||||
*/
|
||||
|
||||
@@ -99,8 +99,7 @@ describe('findSlugDerivationDrift — T2: the canonical owner (src/core-utils.ct
|
||||
});
|
||||
});
|
||||
|
||||
// ─── T3-T5: the sanctioned sites are exempted BY the allowlist
|
||||
// (normalizeKimiSkillName left with the retired kimi runtime — prune-runtimes) ──────────
|
||||
// ─── T3-T5: the sanctioned sites are exempted BY the allowlist ──────────
|
||||
|
||||
describe('findSlugDerivationDrift — T3-T5: sanctioned sites are exempted BY the allowlist, not by accident', () => {
|
||||
const sanctioned = [
|
||||
|
||||
@@ -12,8 +12,7 @@
|
||||
* `tools:` frontmatter as a REQUIRED MCP server and throws CONFIGURATION_ERROR on
|
||||
* spawn when it is not connected. Claude Code treats the same grants as an optional
|
||||
* allowlist, which is why the MSD sources carry them. The install must therefore
|
||||
* filter `mcp__*` entries out of the tools list for ZCode — the same outcome Kimi
|
||||
* already gets via its own conversion path — while leaving Claude Code's verbatim
|
||||
* filter `mcp__*` entries out of the tools list for ZCode while leaving Claude Code's verbatim
|
||||
* copy untouched.
|
||||
*
|
||||
* Every row spawns a REAL installer and asserts on the parsed frontmatter of what
|
||||
|
||||
Reference in New Issue
Block a user