refactor: drop 12 runtimes, keep Claude, Codex, OpenCode, Cursor, ZCode, Antigravity

Removes kilo, kimi, kimi-code, copilot, windsurf, augment, trae, qwen, hermes,
cline, codebuddy and pi end to end: capability descriptors, installer branches
and converters (bin/install.js 14.9k -> 11.2k lines), TypeScript converters,
hook surfaces and runtime homes, review lanes qwen/kimi-code, the two pi
migrations, Kimi payload normalization in the hook guards, dead hostBehaviors
vocabulary, launcher home probes, fixtures, runtime-specific tests and the
prose that presented them as supported.

Installer output for the six kept runtimes is byte-identical to before the
prune. The Kimi tool-vocabulary tests in workflow-guard, read-guard and
read-injection-scanner are left in place pending a decision.
This commit is contained in:
Jakub Zych
2026-10-06 20:02:40 +02:00
parent 12ee75a509
commit 6cfa0c55d2
509 changed files with 1869 additions and 47737 deletions

View File

@@ -44,9 +44,6 @@ const HOOKS_TO_COPY = [
'msd-cursor-stop.js',
'msd-cursor-subagent-start.js',
'msd-cursor-subagent-stop.js',
// Windsurf/Cascade lifecycle hooks (ADR-1239/#2100 Stage 2): 2 blocking events
'msd-windsurf-pre-write.js',
'msd-windsurf-pre-command.js',
// Claude Code FileChanged hook (#770) — hot-reloads msd config when
// .planning/config.json changes mid-session. Must ship to dist so the
// installer can copy it to the target hooks/ dir and register FileChanged.

View File

@@ -316,7 +316,6 @@ const DOCS_GUARD_TESTS = {
'tests/intel.test.cjs': ['*'],
'tests/inventory-headings-countfree.test.cjs': ['docs/INVENTORY.md'],
'tests/inventory-manifest-sync.test.cjs': ['docs/INVENTORY.md', 'docs/INVENTORY-MANIFEST.json'],
'tests/kilo-upgrades.test.cjs': ['docs/how-to/connect-msd-mcp-server.md'],
'tests/live-config-guard.test.cjs': ['docs/TESTING-SUITES.md'],
// #3726: pins the `milestone complete` synopsis (English + four localized
// mirrors), the `--confirm` flag row and the guard-override instructions —

View File

@@ -24,7 +24,7 @@
* `--check`-guarded JSON manifest that is NOT runtime code. It previously
* lived at msd-core/bin/lib/context-index.cjs — a shipped runtime module is
* the wrong place for ~120 KB of arbitrary CONTEXT.md prose: it tripped both
* tests/cline-install.test.cjs's leaked-`.claude`-path guard and
* the leaked-`.claude`-path guard and
* tests/package-name-single-source.test.cjs's hardcoded-package-name guard,
* both true positives against runtime-code content scanning. Moving the
* artifact to docs/ (never scanned as runtime code) fixes both without

View File

@@ -407,7 +407,7 @@ function runMain(main) {
* like `payload`. This exists because `hooks/msd-write-guard.js`'s
* emitBlock does NOT write the same bytes to both streams today — it
* writes the full JSON `output` to stdout but only the plain-text
* `output.reason` STRING to stderr, because Kimi's native hook bus reads
* `output.reason` STRING to stderr, because a native hook bus may read
* stderr verbatim back to the model on exit 2. Migrating that call site
* onto terminateNow requires a way to say "fd 2 gets this different,
* plain-text value" — `stderrPayload` is that seam. Ignored entirely for

View File

@@ -38,7 +38,6 @@ module.exports = {
"tests/chunked-planning-parallel.test.cjs",
"tests/ci-docs-guard-registry.test.cjs",
"tests/ci-test-scope.test.cjs",
"tests/cline-install.test.cjs",
"tests/code-review-pipeline-regression.test.cjs",
"tests/codex-config-agents.test.cjs",
"tests/codex-config-hooks.test.cjs",
@@ -98,7 +97,6 @@ module.exports = {
"tests/install.test.cjs",
"tests/installer-migration-antigravity-retire-confighome-artifacts.test.cjs",
"tests/installer-migration-config-root-marker.test.cjs",
"tests/installer-migration-pi-retire-hooks-dir.test.cjs",
"tests/installer-migration-prune-stale-pristine.test.cjs",
"tests/installer-migration-rename-msd-core.test.cjs",
"tests/installer-migrations.test.cjs",
@@ -106,7 +104,6 @@ module.exports = {
"tests/inventory-nested-families.test.cjs",
"tests/isolation-sentinel.test.cjs",
"tests/issue-version-gate.test.cjs",
"tests/kimi-upgrades.test.cjs",
"tests/lint-docs-command-form.test.cjs",
"tests/lint-retired-runtime-name.test.cjs",
"tests/lint-workflow-shellcheck-fetch.test.cjs",

View File

@@ -6,7 +6,6 @@ module.exports = {
"tests/adr-index-gate.test.cjs",
"tests/adr857-core-without-capabilities.test.cjs",
"tests/agent-install-check.test.cjs",
"tests/agent-install-validation.test.cjs",
"tests/agent-skills.test.cjs",
"tests/antigravity-upgrades.test.cjs",
"tests/api-coverage-gate-e2e.test.cjs",
@@ -14,7 +13,6 @@ module.exports = {
"tests/assumption-delta-checkpoint-e2e.test.cjs",
"tests/assumption-delta.test.cjs",
"tests/audit-command-cutover.test.cjs",
"tests/augment-upgrades.test.cjs",
"tests/broken-windows.test.cjs",
"tests/capability-cli.test.cjs",
"tests/capability-command-dispatch.test.cjs",
@@ -40,7 +38,6 @@ module.exports = {
"tests/ci-test-scope.test.cjs",
"tests/cjs-command-router-adapter.test.cjs",
"tests/claude-md.test.cjs",
"tests/cline-install.test.cjs",
"tests/close-phase-todos-padded-resolves.test.cjs",
"tests/code-review-fix-pipeline-regression.test.cjs",
"tests/code-review-pipeline-regression.test.cjs",
@@ -64,8 +61,6 @@ module.exports = {
"tests/config-schema.property.test.cjs",
"tests/config.test.cjs",
"tests/configured-entrypoint-validation.test.cjs",
"tests/copilot-install.test.cjs",
"tests/copilot-upgrades.test.cjs",
"tests/core-utils.test.cjs",
"tests/cursor-hook-workspace-roots.test.cjs",
"tests/cursor-hooks.test.cjs",
@@ -108,7 +103,6 @@ module.exports = {
"tests/health-diagnostic.test.cjs",
"tests/helpers-cleanup.test.cjs",
"tests/helpers-process-isolation.test.cjs",
"tests/hermes-skills-migration.test.cjs",
"tests/hooks-commonjs-marker.test.cjs",
"tests/hooks-crash-policy.test.cjs",
"tests/hooks-opt-in.test.cjs",
@@ -124,7 +118,6 @@ module.exports = {
"tests/install.test.cjs",
"tests/installer-migration-antigravity-retire-confighome-artifacts.test.cjs",
"tests/installer-migration-config-root-marker.test.cjs",
"tests/installer-migration-pi-retire-hooks-dir.test.cjs",
"tests/installer-migration-prune-stale-pristine.test.cjs",
"tests/installer-migration-rename-msd-core.test.cjs",
"tests/installer-migrations.test.cjs",
@@ -132,10 +125,6 @@ module.exports = {
"tests/inventory-nested-families.test.cjs",
"tests/io.test.cjs",
"tests/isolation-sentinel.test.cjs",
"tests/kilo-upgrades.test.cjs",
"tests/kimi-agent-converter.test.cjs",
"tests/kimi-upgrades.test.cjs",
"tests/kimi-variant-disambiguation.test.cjs",
"tests/lint-workflow-shellcheck-fetch.test.cjs",
"tests/live-config-guard.test.cjs",
"tests/lockfile-cve-audit.test.cjs",
@@ -185,7 +174,6 @@ module.exports = {
"tests/phase-locator.test.cjs",
"tests/phase.test.cjs",
"tests/phase6-capstone-conformance.test.cjs",
"tests/pi-config-dir-env-override.test.cjs",
"tests/plan-count-single-owner.test.cjs",
"tests/plan-phase-stall-detection.test.cjs",
"tests/plan-pre-hook-e2e.test.cjs",
@@ -216,7 +204,6 @@ module.exports = {
"tests/quick-review-scope-tip-bound.test.cjs",
"tests/read-guard.test.cjs",
"tests/reapply-verify-hunks.test.cjs",
"tests/repo-layout.test.cjs",
"tests/representative-corpus.test.cjs",
"tests/require-fs-op-fallback.rule.test.cjs",
"tests/require-full-tmpdir-triad.rule.test.cjs",
@@ -269,7 +256,6 @@ module.exports = {
"tests/verify-archive-dirs-live-path.test.cjs",
"tests/verify-command-grounding.test.cjs",
"tests/verify.test.cjs",
"tests/windsurf-hooks-bridge.test.cjs",
"tests/workflow-guard.test.cjs",
"tests/workflow-shell-pinning.test.cjs",
"tests/workstream-inventory.test.cjs",

View File

@@ -17,19 +17,15 @@
"tests/changeset-cli.test.cjs :: source-text-is-the-product",
"tests/claude-md.test.cjs :: source-text-is-the-product",
"tests/cleanup-branch-pruning.test.cjs :: source-text-is-the-product",
"tests/cline-install.test.cjs :: source-text-is-the-product",
"tests/cline-support.test.cjs :: source-text-is-the-product",
"tests/code-review-agent-skills.test.cjs :: source-text-is-the-product",
"tests/code-review-command.test.cjs :: source-text-is-the-product",
"tests/code-review-pipeline-regression.test.cjs :: source-text-is-the-product",
"tests/code-review.test.cjs :: source-text-is-the-product",
"tests/codebuddy-install.test.cjs :: source-text-is-the-product",
"tests/command-contract.test.cjs :: source-text-is-the-product",
"tests/commands.test.cjs :: source-text-is-the-product",
"tests/config-field-docs.test.cjs :: docs-parity",
"tests/context-enrichment.test.cjs :: source-text-is-the-product",
"tests/contributor-standards.test.cjs :: source-text-is-the-product",
"tests/copilot-install.test.cjs :: integration-test-input",
"tests/cursor-hooks.test.cjs :: source-text-is-the-product",
"tests/cursor-reviewer.test.cjs :: source-text-is-the-product",
"tests/debug-session-management.test.cjs :: source-text-is-the-product",
@@ -55,7 +51,6 @@
"tests/msd-researcher-flow-diagram.test.cjs :: source-text-is-the-product",
"tests/msd-settings-advanced.test.cjs :: source-text-is-the-product",
"tests/helpers/live-command-registry.cjs :: source-text-is-the-product",
"tests/hermes-skills-migration.test.cjs :: source-text-is-the-product",
"tests/import-command.test.cjs :: source-text-is-the-product",
"tests/ingest-docs.test.cjs :: source-text-is-the-product",
"tests/inline-plan-threshold.test.cjs :: source-text-is-the-product",
@@ -91,7 +86,6 @@
"tests/profile-output.test.cjs :: source-text-is-the-product",
"tests/progress-forensic.test.cjs :: source-text-is-the-product",
"tests/prompt-thinning.test.cjs :: source-text-is-the-product",
"tests/qwen-skills-migration.test.cjs :: source-text-is-the-product",
"tests/read-guard.test.cjs :: source-text-is-the-product",
"tests/reapply-patches.test.cjs :: source-text-is-the-product",
"tests/reapply-verify-hunks.test.cjs :: source-text-is-the-product",

View File

@@ -76,9 +76,9 @@
*
* `local/no-source-grep` is registered by `eslint.config.mjs` on several
* glob blocks, not only `tests/** /*.cjs`: also `scripts/** /*.cjs`,
* `eslint-rules/** /*.cjs`, `bin/lib/** /*.cjs`, `pi/** /*.cjs`,
* `eslint-rules/** /*.cjs`, `bin/lib/** /*.cjs`,
* `examples/** /*.cjs`, `msd-core/bin/** /*.cjs`, `vscode/*.js`,
* `.kilo/plugins/*.js`, and `.opencode/plugins/*.js`. A prior version of
* and `.opencode/plugins/*.js`. A prior version of
* this script only walked `tests/** /*.test.cjs`, which silently dropped
* every non-`.test.cjs` file under `tests/` AND every file under every one
* of those other blocks from BOTH reported numbers — a marker there would
@@ -168,8 +168,8 @@ const ROOT = path.join(__dirname, '..');
const ESLINT_CONFIG_PATH = path.join(ROOT, 'eslint.config.mjs');
const TESTS_DIR = process.env.MSD_LINT_ALLOW_TEST_RULE_TESTS_DIR || path.join(ROOT, 'tests');
// Overrides the ROOT that non-`tests/` glob base directories (scripts/,
// eslint-rules/, bin/lib/, pi/, examples/, msd-core/bin/, vscode/,
// .kilo/plugins/, .opencode/plugins/ — see deriveNoSourceGrepGlobs) are
// eslint-rules/, bin/lib/, examples/, msd-core/bin/, vscode/,
// .opencode/plugins/ — see deriveNoSourceGrepGlobs) are
// resolved under. TESTS_DIR already has its own override (above) for the
// `tests/**/*.cjs` block; this is the equivalent knob for every OTHER block
// that registers `local/no-source-grep`, so sandbox fixtures can exercise
@@ -246,9 +246,9 @@ function extractGenuineMarkerReasons(allComments) {
* set, which silently missed every non-`.test.cjs` file under `tests/`
* (`tests/helpers/**`, `tests/qa/**`, `tests/fixtures/**`, ...) AND every one
* of the other config blocks the rule is registered on (`scripts/** /*.cjs`,
* `eslint-rules/** /*.cjs`, `bin/lib/** /*.cjs`, `pi/** /*.cjs`,
* `eslint-rules/** /*.cjs`, `bin/lib/** /*.cjs`,
* `examples/** /*.cjs`, `msd-core/bin/** /*.cjs`, `vscode/*.js`,
* `.kilo/plugins/*.js`, `.opencode/plugins/*.js`). A marker in any of those
* `.opencode/plugins/*.js`). A marker in any of those
* would vanish from BOTH reported numbers instead of tripping anything.
*
* Rather than hand-maintain a second copy of that glob list (the exact
@@ -294,7 +294,7 @@ async function deriveNoSourceGrepGlobs() {
/**
* The literal (non-wildcard) leading path segments of a glob — the
* directory that must actually be walked on disk to find candidate files.
* e.g. `tests/** /*.cjs` -> `tests`, `.kilo/plugins/*.js` -> `.kilo/plugins`.
* e.g. `tests/** /*.cjs` -> `tests`, `.opencode/plugins/*.js` -> `.opencode/plugins`.
*
* The LAST segment is always excluded from consideration even when it has
* no wildcard of its own (e.g. the single-file fixture globs

View File

@@ -1,4 +1,4 @@
{
"maxSites": 84,
"maxSites": 81,
"grace": 2
}

View File

@@ -40,10 +40,8 @@ const DOCS_GUARD_EXEMPT_BASELINE = [
'capability-validator-task-content-resolver.test.cjs',
'ci-docs-guard-registry.test.cjs',
'ci-test-scope.test.cjs',
'cline-install.test.cjs',
'code-review-depth.test.cjs',
'code-review-pipeline-regression.test.cjs',
'codebuddy-upgrades.test.cjs',
'commands.test.cjs',
'commit-docs-bypass.test.cjs',
'compact-content-partition-guard.test.cjs',
@@ -58,13 +56,10 @@ const DOCS_GUARD_EXEMPT_BASELINE = [
'gen-context-index.test.cjs',
'gen-registry.test.cjs',
'msd-agent-isolation-guard.test.cjs',
'hermes-dispatch-upgrade.test.cjs',
'install-minimal-hooks.test.cjs',
'install-runtime-artifacts.test.cjs',
'installer-migration-config-root-marker.test.cjs',
'installer-migration-pi-extension-ext.test.cjs',
'installer-migrations.test.cjs',
'kimi-upgrades.test.cjs',
'lint-allow-test-rule-refs.test.cjs',
'lint-docs-command-form.test.cjs',
'lint-docs-required.test.cjs',
@@ -84,7 +79,6 @@ const DOCS_GUARD_EXEMPT_BASELINE = [
'reviewer-manifest-body.test.cjs',
'reviewer-step-dispatch.test.cjs',
'run-tests-harness.test.cjs',
'runtime-name-policy.test.cjs',
'security-prompt-injection.security.test.cjs',
'shipped-reference-cites.test.cjs',
'state.test.cjs',
@@ -134,10 +128,8 @@ const DOCS_GUARD_EXEMPT_DOCS_PATHS = {
'docs/adr/4641-windows-selector-consolidation.md', 'docs/how-to/configure-model-profiles.md',
'docs/installer-migrations.md', 'docs/ja-JP', 'docs/ja-JP/USAGE.md', 'docs/usage.md', 'docs/x.md',
],
'cline-install.test.cjs': ['docs/guide.md'],
'code-review-depth.test.cjs': ['docs/src/auth/x.ts'],
'code-review-pipeline-regression.test.cjs': ['docs/DEVELOPMENT.md'],
'codebuddy-upgrades.test.cjs': ['docs/cli/sub-agents'],
'commands.test.cjs': ['docs/x.md'],
'commit-docs-bypass.test.cjs': [
'docs/40-design.md', 'docs/CONFIGURATION.md', 'docs/readme.md', 'docs/tracked-var-mentioning',
@@ -166,13 +158,10 @@ const DOCS_GUARD_EXEMPT_DOCS_PATHS = {
'gen-context-index.test.cjs': ['docs/CONTEXT-INDEX.json', 'docs/INVENTORY-MANIFEST.json'],
'gen-registry.test.cjs': ['docs/registries', 'docs/registries/reviewers.json'],
'msd-agent-isolation-guard.test.cjs': ['docs/adr/1239-...md', 'docs/adr/1239-msd-embeddable-orchestration-engine.md'],
'hermes-dispatch-upgrade.test.cjs': ['docs/guides/delegation-patterns.md'],
'install-minimal-hooks.test.cjs': ['docs/en/hooks', 'docs/en/users/features/hooks'],
'install-runtime-artifacts.test.cjs': ['docs/CONFIGURATION.md', 'docs/adr/58-...md', 'docs/adr/58-runtime-install-policy-module.md', 'docs/cli/slash-commands'],
'install-runtime-artifacts.test.cjs': ['docs/CONFIGURATION.md', 'docs/adr/58-...md', 'docs/adr/58-runtime-install-policy-module.md'],
'installer-migration-config-root-marker.test.cjs': ['docs/installer-migrations.md'],
'installer-migration-pi-extension-ext.test.cjs': ['docs/installer-migrations.md'],
'installer-migrations.test.cjs': ['docs/installer-migrations.md'],
'kimi-upgrades.test.cjs': ['docs/reference/host-integration-capability-matrix.md'],
'lint-allow-test-rule-refs.test.cjs': ['docs/readme.md'],
'lint-docs-command-form.test.cjs': ['docs/adr', 'docs/adr/999-example.md', 'docs/how-to/example.md'],
'lint-docs-required.test.cjs': [
@@ -230,7 +219,6 @@ const DOCS_GUARD_EXEMPT_DOCS_PATHS = {
// review). Neither is a real filesystem read — the file never reads any docs/ file.
'reviewer-step-dispatch.test.cjs': ['docs/adr/456-test-rigor-architecture.md', 'docs/spec.md'],
'run-tests-harness.test.cjs': ['docs/TESTING-SUITES.md'],
'runtime-name-policy.test.cjs': ['docs/customize/skills'],
'security-prompt-injection.security.test.cjs': ['docs/notes.md'],
'shipped-reference-cites.test.cjs': [],
// #4186: the record-session usage-contract test cites the documented

View File

@@ -42,20 +42,18 @@
* entries and MISSED a real leak into `<live>/skills/msd-dev-preferences/`.
*
* KNOWN GAP — a leak into a file MSD does not own (e.g. mutating the host's own
* `.claude.json`, `settings.json`, `hooks.json`, `kilo.json`, `opencode.json`) is
* `.claude.json`, `settings.json`, `hooks.json`, `opencode.json`) is
* outside this guard by construction. Closing it would require watching shared
* files, which is the false-positive trap above.
*
* NAMED RESIDUALS — stated rather than implied, because two successive rounds
* asserted this list was complete and both were refuted. Still NOT watched:
* - `agents/subagents/**` (kimi stages `subagents/msd-executor.yaml` under an
* UNPREFIXED intermediate dir, so no prefix scan of `agents/` reaches it);
* - the loose capability generators copied to `<root>/scripts/*.cjs`
* (`fix-slash-commands.cjs` is watched by name; the generators are not);
* - `extensions/package.json` and `plugins/package.json` — CommonJS markers in
* dirs MSD fills but does not own, so they fall under the shared-ground rule
* below rather than being watched;
* - the shared-hooks bundle in a NON-registry root's `<root>/hooks/` (kimi) —
* - the shared-hooks bundle in a NON-registry root's `<root>/hooks/` —
* see resolveExtraWatchTargets; closing it is a layout decision.
* DELIBERATELY not watched, which is a different thing from missed: `hooks/lib`,
* `hooks/package.json`, `scripts/lib` and `scripts/changeset`. The installer
@@ -125,7 +123,7 @@ const NON_REGISTRY_ARTIFACT_PARENTS = ['hooks', 'plugins', 'scripts', 'extension
/**
* Prefixes used for a parent with no registry-declared one. BOTH forms are the
* point: MSD writes `msd-`-hyphen artifacts (`hooks/msd-check-update.js`) AND
* bare `msd.`-dotted ones (pi's `extensions/msd.js`), and a lone `msd-` sees
* bare `msd.`-dotted ones (`plugins/msd.js`), and a lone `msd-` sees
* only the first.
*/
const DEFAULT_ARTIFACT_PREFIXES = ['msd-', 'msd.'];
@@ -176,13 +174,13 @@ function deriveArtifactTargets(runtimes) {
const dest = layout?.destSubpath;
if (typeof dest !== 'string' || !dest) continue;
const last = dest.split('/').pop() || '';
// A destination whose own final segment is MSD's (hermes' `skills/msd`) is
// A destination whose own final segment is MSD's (`skills/msd`) is
// owned wholesale — that directory is ours, not shared.
if (last.startsWith('msd')) { owned.add(dest); continue; }
// The layout declares its OWN prefix, and it varies: kimi's `kimi-agents`
// layout declares `msd` (no hyphen) and writes `agents/msd.yaml` +
// `agents/msd.md`, invisible to a fixed `msd-` scan. The same destSubpath
// also carries different prefixes across runtimes, so a parent maps to a SET.
// The layout declares its OWN prefix, and it may vary: a layout declaring
// `msd` (no hyphen) would be invisible to a fixed `msd-` scan. The same
// destSubpath also carries different prefixes across runtimes, so a
// parent maps to a SET.
const declared = typeof layout?.prefix === 'string' && layout.prefix
? [layout.prefix]
: DEFAULT_ARTIFACT_PREFIXES;
@@ -214,12 +212,10 @@ function artifactTargets(deps = {}) {
/**
* The file MSD writes into a NON-REGISTRY config home.
*
* Both current descriptors are Kimi's — Kimi CLI's `~/.kimi` (KIMI_SHARE_DIR) and
* Kimi Code's `~/.kimi-code` (KIMI_CODE_HOME) — and MSD writes its native
* `[[hooks]]` block into `config.toml` in each, so the single filename below holds
* for both. NAMED RESIDUAL: this assumes every non-registry descriptor is written
* the same way. That assumption is now load-bearing rather than vacuous — it is
* carrying two descriptors, not one — and a future descriptor whose owned file
* NON_REGISTRY_CONFIG_HOME_DESCRIPTORS is empty today (its former entries were
* native `config.toml` hook homes of since-retired runtimes), so this filename
* is currently unused. NAMED RESIDUAL: this assumes every non-registry
* descriptor is written the same way; a future descriptor whose owned file
* differs needs a per-descriptor mapping here. The consequence of getting it wrong
* is under-watching (a missed leak), not a false positive, so it fails in the quiet
* direction and is called out rather than left to be discovered.
@@ -339,8 +335,8 @@ function resolveLiveConfigRoots(deps = {}) {
* #2665 round 3: resolveLiveConfigRoots enumerates getGlobalConfigDir per registry
* runtime plus grok. A live write surface that is not a config ROOT is invisible to
* that shape, so a leak on one passed through this guard — the PR's own safety net —
* silently. There are THREE today ($MSD_HOME/.msd, plus one config.toml per entry in
* NON_REGISTRY_CONFIG_HOME_DESCRIPTORS, which #2755 took from one entry to two):
* silently. Today: $MSD_HOME/.msd, plus one config.toml per entry in
* NON_REGISTRY_CONFIG_HOME_DESCRIPTORS (currently none):
*
* $MSD_HOME/.msd — MSD's user-owned store (consent.json, defaults.json, capability
* overlays). Watched WHOLESALE: unlike ~/.claude this root is
@@ -348,9 +344,8 @@ function resolveLiveConfigRoots(deps = {}) {
* SCOPE above does not apply and an ownership filter would only
* narrow the guard for nothing.
* <non-registry home>/config.toml — the file MSD writes its native [[hooks]] block
* into, one per NON_REGISTRY_CONFIG_HOME_DESCRIPTORS entry: Kimi
* CLI's ~/.kimi (KIMI_SHARE_DIR) and, since #2755, Kimi Code's
* ~/.kimi-code (KIMI_CODE_HOME). The INVERSE case: those roots
* into, one per NON_REGISTRY_CONFIG_HOME_DESCRIPTORS entry
* (none today). The INVERSE case: those roots
* belong to their products, so the root is never watched
* wholesale. This is the KNOWN GAP above accepted deliberately
* in one direction — MSD demonstrably writes these files
@@ -384,19 +379,18 @@ function resolveExtraWatchTargets(deps = {}) {
resolveConfigHomeFromDescriptor,
} = require(path.join(libDir, 'runtime-homes.cjs'));
// ITERATE the descriptor array rather than naming one resolver. Calling
// resolveKimiHooksTomlDir directly 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 in the first place, reintroduced one
// layer over. TEST_ENV_BASE derives its keys from this array; deriving the
// guard's paths from it keeps the two halves from drifting apart.
// ITERATE the descriptor array rather than naming one resolver — a
// partial enumeration would silently miss a future entry. TEST_ENV_BASE
// derives its keys from this array; deriving the guard's paths from it
// keeps the two halves from drifting apart.
//
// Thread the SAME injected env/home used above: resolving bare would read
// process.env and os.homedir() regardless of `deps`, leaving the seam
// untestable and the targets resolved against different worlds.
for (const descriptor of NON_REGISTRY_CONFIG_HOME_DESCRIPTORS) {
// The fallback leg, per the note above: a child that blanks KIMI_SHARE_DIR /
// KIMI_CODE_HOME writes to the HOME-derived root instead of the ambient one.
// The fallback leg, per the note above: a child that blanks the
// descriptor's env var writes to the HOME-derived root instead of the
// ambient one.
const fallbackDir = resolveConfigHomeFromDescriptor(descriptor, { env: {}, home: homedir() });
if (typeof fallbackDir === 'string' && fallbackDir.length > 0) {
targets.push(path.resolve(path.join(fallbackDir, NON_REGISTRY_OWNED_FILE)));
@@ -405,12 +399,11 @@ function resolveExtraWatchTargets(deps = {}) {
// The root belongs to the runtime, so it is never watched wholesale — only
// the named file below.
//
// NAMED RESIDUAL (#2665, found pre-push while rebasing): config.toml is NOT
// the only thing MSD writes here. bin/install.js also calls
// installSharedHooksBundle(kimiHooksRoot), which populates <root>/hooks/
// with MSD's hook scripts and a CommonJS marker. That subtree is UNWATCHED,
// so a suite-produced leak of a hook bundle into a developer's real ~/.kimi
// or ~/.kimi-code passes this guard silently. Closing it needs a layout
// NAMED RESIDUAL (#2665, found pre-push while rebasing): config.toml may
// NOT be the only thing MSD writes here — an installer may also populate
// <root>/hooks/ with MSD's hook scripts and a CommonJS marker. That
// subtree is UNWATCHED, so a suite-produced leak of a hook bundle into a
// developer's real non-registry root passes this guard silently. Closing it needs a layout
// decision, not one more path: the same reason getGlobalSkillsBase is a
// deliberate non-target above. Under-watching fails quiet, like the
// NON_REGISTRY_OWNED_FILE residual it sits beside.
@@ -507,8 +500,8 @@ function snapshotLiveConfig(roots, extraTargets = [], limits = {}) {
for (const root of roots) {
for (const entry of MSD_OWNED_ENTRIES) record(path.join(root, entry));
// Paths whose own name is MSD's, nested inside a shared root (hermes'
// `skills/msd`, `hooks/lib`, `scripts/lib`, …) — no prefix rule sees these.
// Paths whose own name is MSD's, nested inside a shared root
// (`skills/msd`, `hooks/lib`, `scripts/lib`, …) — no prefix rule sees these.
for (const entry of watchOwned) record(path.join(root, ...entry.split('/')));
// Shared dirs: enumerate only msd-prefixed children. A child that appears

View File

@@ -137,10 +137,6 @@ ALLOWLIST=(
# ("ignore previous instructions", "you are now…") as examples agents must
# NOT comply with — it is the defense, not an attack vector.
'references/untrusted-input-boundary.md'
# Security regression tests for input validators — fixtures must contain
# real injection payloads to prove the validator rejects them. See
# DEFECT.PROMPT-INJECTION-SCAN-COLLISION in CONTEXT.md.
'tests/windsurf-conversion.test.cjs'
# RuleTester fixtures for the local/no-unguarded-nonportable-exec ESLint rule
# contain shell-exec command strings (exec("sh -c …"), execFileSync('bash',['-c',…]))
# as test DATA the rule must lint — not attack vectors. ADR-1703 Phase 3 (#1720).
@@ -149,12 +145,6 @@ ALLOWLIST=(
# exec command strings (execFileSync('npm', ['install'])) as test DATA the rule
# must lint — not attack vectors. ADR-1703 Phase 4 (#1726).
'tests/no-bare-npm-exec.rule.test.cjs'
# #2547 — the Kimi field-shadowing regression proves msd-prompt-guard still
# SCANS the reconstructed edit[].new content when a model-supplied new_string
# tries to shadow it. The fixture must be a real injection phrase or the test
# asserts nothing: it is the payload the guard is required to catch, carried
# as test DATA. Same class as the read-injection-scanner suites above.
'tests/kimi-payload-field-shadowing.security.test.cjs'
# Phase-ID grammar regression tests exercise `RegExp.prototype.exec` via
# `re.exec('<phase-id>')` against fixtures like 'MANIFOLD-64-auth' / 'CK-64-auth'.
# The scanner's `exec('` code-execution pattern matches that benign method call,

View File

@@ -295,13 +295,9 @@ function scanWorkflowColonLeak(filePath, cmdNames) {
* settings.json, codex into hooks.json and config.toml.
*
* This is NOT an exhaustive map of where MSD writes launch paths across all
* runtimes, and the scan is top-level only by design. Two known surfaces sit
* outside it: Cline registers its hook at `.clinerules/hooks/PreToolUse` (a
* subdirectory, and not one of these names — see writeClineArtifacts in
* src/runtime-hooks-surface.cts), and Kimi's native `[[hooks]]` config.toml
* lives under `resolveKimiHooksTomlDir()` (`~/.kimi`), a directory separate
* from Kimi's own MSD configDir. Adding either runtime to entrypointRuntimes
* requires teaching scanConfiguredEntrypoints about its surface first,
* runtimes, and the scan is top-level only by design. A runtime whose hook
* surface lives in a subdirectory or outside its MSD configDir must first be
* taught to scanConfiguredEntrypoints before being added to entrypointRuntimes,
* otherwise the scan reports zero entrypoints and silently proves nothing.
*/
const RUNTIME_CONFIG_FILES = Object.freeze(['settings.json', 'hooks.json', 'config.toml']);