diff --git a/agents/msd-intel-updater.compact.md b/agents/msd-intel-updater.compact.md index 71fe87226..226cbcdc6 100644 --- a/agents/msd-intel-updater.compact.md +++ b/agents/msd-intel-updater.compact.md @@ -39,32 +39,9 @@ Spawned by `/msd:map-codebase --query`, which has already confirmed `intel.enabl ## Project Scope - - -**Runtime layout detection (MSD framework repo only):** if `package.json` `"name"` equals `"@golem15/msd-core"`, this project IS the MSD framework — detect the runtime root to choose canonical paths: -```bash -if [[ "$(jq -r '.name // ""' package.json 2>/dev/null)" == "@golem15/msd-core" ]]; then - ls -d .kilo 2>/dev/null && echo "kilo" || (ls -d .claude/msd-core 2>/dev/null && echo "claude") || echo "unknown" -fi -``` -For all other projects, skip this step and go to Step 1. - -Use the detected root (when applicable) to resolve canonical paths: - -| Source type | Standard `.claude` layout | `.kilo` layout | -|-------------|--------------------------|----------------| -| Agent files | `agents/*.md` | `.kilo/agents/*.md` | -| Command files | `commands/msd/*.md` | `.kilo/command/*.md` | -| CLI tooling | `msd-core/bin/` | `.kilo/msd-core/bin/` | -| Workflow files | `msd-core/workflows/` | `.kilo/msd-core/workflows/` | -| Reference docs | `msd-core/references/` | `.kilo/msd-core/references/` | -| Hook files | `hooks/*.js` | `.kilo/hooks/*.js` | - -When analyzing this project, use ONLY the canonical source locations matching the detected layout — do not fall back to standard layout paths if `.kilo` is detected (those paths will be empty, producing semantically empty intel). - EXCLUDE from counts/analysis: `.planning/` (planning docs, not project code); `node_modules/`, `dist/`, `build/`, `.git/`. -**Count accuracy:** when reporting component counts (stack.json, arch-decisions.json), always derive counts by running Glob on the layout-resolved canonical locations, never from memory or CLAUDE.md. E.g. standard: `Glob("agents/*.md")`; kilo: `Glob(".kilo/agents/*.md")`. +**Count accuracy:** when reporting component counts (stack.json, arch-decisions.json), always derive counts by running Glob on the canonical source locations (agents, commands, hooks, workflows), never from memory or CLAUDE.md. E.g. `Glob("agents/*.md")`. ## Forbidden Files NEVER read or include in output: `.env` files (except `.env.example`/`.env.template`); `*.key`, `*.pem`, `*.pfx`, `*.p12`; files with `credential`/`secret` in their name; `*.keystore`, `*.jks`; `id_rsa`, `id_ed25519`; `node_modules/`, `.git/`, `dist/`, `build/` directories. If encountered, skip silently — do NOT include contents. diff --git a/agents/msd-intel-updater.md b/agents/msd-intel-updater.md index 8735a25ea..5459c4120 100644 --- a/agents/msd-intel-updater.md +++ b/agents/msd-intel-updater.md @@ -57,40 +57,14 @@ The /msd:map-codebase --query command has already confirmed that intel.enabled i ## Project Scope - - -**Runtime layout detection (MSD framework repo only):** If `package.json` `"name"` equals `"@golem15/msd-core"`, this project IS the MSD framework. In that case, detect the runtime root to choose canonical paths: - -```bash -# Only run layout detection when analysing the MSD framework repo itself. -if [[ "$(jq -r '.name // ""' package.json 2>/dev/null)" == "@golem15/msd-core" ]]; then - ls -d .kilo 2>/dev/null && echo "kilo" || (ls -d .claude/msd-core 2>/dev/null && echo "claude") || echo "unknown" -fi -``` - -For all other projects, skip this step and proceed directly to Step 1. - -Use the detected root (when applicable) to resolve all canonical paths below: - -| Source type | Standard `.claude` layout | `.kilo` layout | -|-------------|--------------------------|----------------| -| Agent files | `agents/*.md` | `.kilo/agents/*.md` | -| Command files | `commands/msd/*.md` | `.kilo/command/*.md` | -| CLI tooling | `msd-core/bin/` | `.kilo/msd-core/bin/` | -| Workflow files | `msd-core/workflows/` | `.kilo/msd-core/workflows/` | -| Reference docs | `msd-core/references/` | `.kilo/msd-core/references/` | -| Hook files | `hooks/*.js` | `.kilo/hooks/*.js` | - -When analyzing this project, use ONLY the canonical source locations matching the detected layout. Do not fall back to the standard layout paths if the `.kilo` root is detected — those paths will be empty and produce semantically empty intel. - EXCLUDE from counts and analysis: - `.planning/` -- Planning docs, not project code - `node_modules/`, `dist/`, `build/`, `.git/` **Count accuracy:** When reporting component counts in stack.json or arch-decisions.json, always derive -counts by running Glob on the layout-resolved canonical locations above, not from memory or CLAUDE.md. -Example (standard layout): `Glob("agents/*.md")`. Example (kilo): `Glob(".kilo/agents/*.md")`. +counts by running Glob on the canonical source locations (agents, commands, hooks, workflows), not from memory or CLAUDE.md. +Example: `Glob("agents/*.md")`. ## Forbidden Files diff --git a/docs/reference/capability-matrix.md b/docs/reference/capability-matrix.md index 7aff3f4d3..6278cbc25 100644 --- a/docs/reference/capability-matrix.md +++ b/docs/reference/capability-matrix.md @@ -98,7 +98,7 @@ Reviewer capabilities declare a cross-AI **reviewer lane** — one external CLI model endpoint `/msd-review` hands a plan to (ADR-2782 D3). They are not install targets: they emit no skills, agents, hooks or surface files, so their extension-point and hook-kind cells are `—`. A host that is *also* a reviewer -(Claude, Codex, Cursor, OpenCode, Qwen, Antigravity) keeps one manifest and +(Claude, Codex, Cursor, OpenCode, Antigravity) keeps one manifest and appears under **runtime** above, carrying its lane alongside its runtime body; only lanes that MSD never installs into appear here. diff --git a/eslint-rules/lib/portability-vocab.cjs b/eslint-rules/lib/portability-vocab.cjs index 359750226..4af82258e 100644 --- a/eslint-rules/lib/portability-vocab.cjs +++ b/eslint-rules/lib/portability-vocab.cjs @@ -66,14 +66,9 @@ const PATH_RETURNING_FNS = [ 'getGlobalSkillDisplayPath', 'resolveSkillsBaseFromDescriptor', 'resolveConfigHomeFromDescriptor', - 'resolveKimiGlobalDir', - // #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', 'getConfigDirFromHome', - 'resolveKiloConfigPath', 'resolveOpencodeConfigPath', 'computePathPrefix', 'expandHome', diff --git a/eslint-rules/normalize-path-in-content.cjs b/eslint-rules/normalize-path-in-content.cjs index 23c3f70ed..a12abc664 100644 --- a/eslint-rules/normalize-path-in-content.cjs +++ b/eslint-rules/normalize-path-in-content.cjs @@ -13,7 +13,7 @@ * The canonical defect is computePathPrefix returning `${resolvedTarget}/` * verbatim on Windows (PR #1622) — backslashes leaked into `@~/.claude/...` * markdown content, breaking cross-platform substring checks and producing - * malformed @-references in Windsurf workflow files. + * malformed @-references in installed workflow files. * * References: * DEFECT.WINDOWS-PATH-LEAK-IN-MARKDOWN-CONTENT (CONTEXT.md) diff --git a/msd-core/bin/lib/capability-validator.cjs b/msd-core/bin/lib/capability-validator.cjs index 9d7a5f71b..3466eebca 100644 --- a/msd-core/bin/lib/capability-validator.cjs +++ b/msd-core/bin/lib/capability-validator.cjs @@ -838,48 +838,25 @@ function validateTaskContentResolverFields(cap) { return errors; } -// ADR-857 phase 5e: Closed ConverterName enum — complete set used across 16 runtime descriptors, +// ADR-857 phase 5e: Closed ConverterName enum — complete set used across the runtime descriptors, // all exported by bin/install.js (commands/skills) and src/runtime-artifact-conversion.cts (agents). // Any ArtifactKind with a non-null converter must use one of these. const VALID_CONVERTER_NAMES = new Set([ // commands / skills converters (pre-existing) 'convertClaudeCommandToAntigravitySkill', - 'convertClaudeCommandToAugmentSkill', - 'convertClaudeCommandToClineSkill', 'convertClaudeCommandToClaudeSkill', - 'convertClaudeCommandToCodebuddyCommand', - 'convertClaudeCommandToCodebuddySkill', 'convertClaudeCommandToCodexSkill', - 'convertClaudeCommandToCopilotSkill', 'convertClaudeCommandToCursorSkill', - 'convertClaudeCommandToKiloSkill', - 'convertClaudeCommandToKimiSkill', - 'convertClaudeCommandToKimiCodeSkill', 'convertClaudeCommandToOpencodeSkill', - 'convertClaudeCommandToTraeSkill', - 'convertClaudeCommandToWindsurfSkill', - 'convertClaudeCommandToWindsurfWorkflow', // agent converters (#1173 — descriptor-driven agent conversion wiring) - 'convertClaudeAgentToCopilotAgent', 'convertClaudeAgentToAntigravityAgent', 'convertClaudeAgentToCursorAgent', - 'convertClaudeAgentToWindsurfAgent', - 'convertClaudeAgentToAugmentAgent', - 'convertClaudeAgentToTraeAgent', - 'convertClaudeAgentToCodebuddyAgent', - 'convertClaudeAgentToClineAgent', 'convertClaudeAgentToCodexAgent', - // ADR-1239 / #2092 Phase B Upgrade 1 — native .qwen/agents/*.md subagent projection. - 'convertClaudeAgentToQwenAgent', // #3384 — ZCode agents are Claude-shaped but its dispatcher treats mcp__* tools // grants as required MCP servers; this converter strips them at install time. 'convertClaudeAgentToZcodeAgent', - // #2875 Part 2 (the agents-bypass closure) — data-driven Hermes branding - // converter (reads hostBehaviors.brandingRewrites rather than a hardcode), - // and the kilo/opencode agent converters (shared with those runtimes' - // commands-kind entries, options-bag signature `(content, {isAgent, modelOverride})`). - 'convertClaudeAgentToHermesAgent', - 'convertClaudeToKiloFrontmatter', + // opencode agent converter (shared with the runtime's commands-kind entries, + // options-bag signature `(content, {isAgent, modelOverride})`). 'convertClaudeToOpencodeFrontmatter', ]); @@ -893,7 +870,7 @@ const VALID_HOOKS_SURFACES = new Set(['settings-json', 'codex-hooks-json', 'curs const VALID_HOOK_EVENTS = new Set(['claude', 'gemini']); // extensionEvents — the plugin/extension-system event dialect (ADR-1239 amendment / #1943). // DISTINCT from hookEvents (managed-hook dialect): extensionEvents describes the -// plugin-owned event subset imperative hosts expose (opencode / pi); 'none' = the +// plugin-owned event subset imperative hosts expose (opencode); 'none' = the // host exposes no extension surface (engine owns the bus, e.g. VS Code). const VALID_EXTENSION_EVENTS = new Set(['opencode', 'none']); const VALID_SANDBOX_TIERS = new Set(['none', 'codex-agent-sandbox']); @@ -910,8 +887,8 @@ const VALID_TRIGGER_PRECEDENCE_KINDS = new Set(['commands', 'skills']); // descriptor omits the axis (see validateRuntimeBody's required-with-default // handling below). Not invented: `['skills', 'commands']` is the ordering every // in-tree runtime that emits both kinds (from the same trigger stems) wants — -// claude's local/global collision and the same-scope collision (codebuddy, kilo, -// opencode, zcode) all resolve to skills winning. claude's shipped descriptor +// claude's local/global collision and the same-scope collision (opencode, +// zcode) all resolve to skills winning. claude's shipped descriptor // declares this SAME value explicitly, and // tests/runtime-artifact-layout-trigger-surface.test.cjs asserts the two agree // (a parity assertion — two surfaces reading one rule must not silently drift). @@ -922,8 +899,7 @@ const FEATURE_FIELDS_FORBIDDEN_ON_RUNTIME = ['skills', 'agents', 'steps', 'contr const VALID_INSTALL_SURFACES = new Set(['settings-json', 'codex-toml', 'cursor-hooks-json', 'profile-marker-only', 'none']); // 'antigravity' added #2096 Phase B Upgrade 1 — settings.json permissions.allow writer. const VALID_PERMISSION_WRITERS = new Set(['opencode', 'antigravity']); -// SubagentStart added #2092 Phase B Upgrade 2 (qwen-only today — see -// capabilities/qwen/capability.json's extendedHookEvents). +// SubagentStart added #2092 Phase B Upgrade 2. const VALID_EXTENDED_HOOK_EVENTS = new Set(['SubagentStop', 'Stop', 'PreCompact', 'FileChanged', 'BeforeAgent', 'AfterAgent', 'BeforeModel', 'SubagentStart']); // ADR-1239 Phase A: hostIntegration axes (MUST stay parity-identical to HOST_INTEGRATION_AXES in src/host-integration.cts) @@ -1078,7 +1054,7 @@ const INSTALL_SURFACE_TO_ALLOWED_HOOKS_SURFACES = new Map([ // Gemini agent-events require hookEvents='gemini'; Claude-family events require hookEvents='claude'. const GEMINI_AGENT_EVENTS = new Set(['BeforeAgent', 'AfterAgent', 'BeforeModel']); // SubagentStart added #2092 Phase B Upgrade 2 — Claude hook-event dialect -// counterpart of SubagentStop (qwen-only today). +// counterpart of SubagentStop. const CLAUDE_FAMILY_EVENTS = new Set(['SubagentStop', 'Stop', 'PreCompact', 'FileChanged', 'SubagentStart']); /** diff --git a/msd-core/bin/lib/legacy-cleanup.cjs b/msd-core/bin/lib/legacy-cleanup.cjs index c09ffc168..1b1751fff 100644 --- a/msd-core/bin/lib/legacy-cleanup.cjs +++ b/msd-core/bin/lib/legacy-cleanup.cjs @@ -59,7 +59,7 @@ const MSD_SKILL_DIR_PREFIX = 'msd-'; /** * File extensions eligible for the stale-skill-path scan. - * SKILL.md is the only file in a codex/cursor/kilo/etc skill directory that + * SKILL.md is the only file in a codex/cursor/etc skill directory that * embeds an @-import path to the MSD runtime config tree. */ const SKILL_MD_EXTENSIONS = new Set(['.md']); diff --git a/msd-core/bin/lib/stale-bake-guard.cjs b/msd-core/bin/lib/stale-bake-guard.cjs index 3c6230ed9..2b087ef44 100644 --- a/msd-core/bin/lib/stale-bake-guard.cjs +++ b/msd-core/bin/lib/stale-bake-guard.cjs @@ -5,8 +5,7 @@ * * Runtimes `codex` and `opencode` bake the resolved * model ID into each agent's static config at install time (bin/install.js - * ~5667-5767 for codex, ~10008-10026 for opencode, and the adjacent kilo - * branch added by #2093). Their task/spawn_agent interfaces do not accept + * ~5667-5767 for codex, ~10008-10026 for opencode). Their task/spawn_agent interfaces do not accept * an inline `model` parameter, so editing `model_overrides` in * `.planning/config.json` or `~/.msd/defaults.json` has NO effect until the * user re-runs `msd install ` (or `msd update`). The failure is @@ -26,8 +25,7 @@ const os = require('os'); * Runtimes whose agent config is static frontmatter/TOML baked at install time. * MUST stay in sync with the bake paths in bin/install.js. The parity test in * tests/stale-bake-guard.test.cjs asserts this matches the runtimes that - * actually emit a baked model: line id #2256 (opencode), #49/#2256 (codex), - * and #2093 (kilo). + * actually emit a baked model: line id #2256 (opencode) and #49/#2256 (codex). */ const STATIC_FRONTMATTER_RUNTIMES = Object.freeze(['codex', 'opencode']); @@ -87,7 +85,7 @@ function resolveRuntimeFromConfig(config) { /** * Resolve the install root for a runtime's agent files, honoring the same env - * vars the installer does (CODEX_HOME, OPENCODE_CONFIG_DIR, KILO_CONFIG_DIR). + * vars the installer does (CODEX_HOME, OPENCODE_CONFIG_DIR). * Returns the absolute directory or `null` for unsupported runtimes. */ function resolveAgentDir(runtime, { env = process.env, homedir = os.homedir } = {}) { diff --git a/scripts/gen-capability-matrix.cjs b/scripts/gen-capability-matrix.cjs index 35db8dfb6..e706320ed 100644 --- a/scripts/gen-capability-matrix.cjs +++ b/scripts/gen-capability-matrix.cjs @@ -193,7 +193,7 @@ Reviewer capabilities declare a cross-AI **reviewer lane** — one external CLI model endpoint \`/msd-review\` hands a plan to (ADR-2782 D3). They are not install targets: they emit no skills, agents, hooks or surface files, so their extension-point and hook-kind cells are \`—\`. A host that is *also* a reviewer -(Claude, Codex, Cursor, OpenCode, Qwen, Antigravity) keeps one manifest and +(Claude, Codex, Cursor, OpenCode, Antigravity) keeps one manifest and appears under **runtime** above, carrying its lane alongside its runtime body; only lanes that MSD never installs into appear here. diff --git a/scripts/lint-retired-runtime-name.cjs b/scripts/lint-retired-runtime-name.cjs index 48d10e170..78a3b8cf9 100644 --- a/scripts/lint-retired-runtime-name.cjs +++ b/scripts/lint-retired-runtime-name.cjs @@ -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, Cline, and Codex.` and -// `Supported agents include Gemini 3, Cline, and Cursor.` all exiting 0 — the +// `MSD installs cleanly on Gemini 3, Cursor, and Codex.` and +// `Supported agents include Gemini 3, OpenCode, 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". diff --git a/scripts/lint-slug-derivation-drift.cjs b/scripts/lint-slug-derivation-drift.cjs index 4098ff750..72a0a8ca3 100644 --- a/scripts/lint-slug-derivation-drift.cjs +++ b/scripts/lint-slug-derivation-drift.cjs @@ -116,13 +116,6 @@ * SHARED `transliterateForSlug` primitive, so this is not an independent * re-derivation of the transliteration step — only of the collapse/trim * shape it deliberately keeps un-consolidated. - * - `src/runtime-artifact-conversion.cts` `normalizeKimiSkillName` — a Kimi - * runtime skill-name normalizer in a completely different domain (CLI - * skill invocation names, never a `.planning/` phase/plan/milestone - * slug); its negated class (`[^a-z0-9-]`) deliberately PRESERVES - * hyphens (a skill name may already contain them), the opposite of the - * slug seam's contract. Shaped like the re-derivation textually; not one - * by domain. * - `scripts/generate-package-identity.cjs` `slugifyPackageName` — * npm-scope-name-to-cache-filename prep. Runs PRE-BUILD (`npm run * generate:identity`, step 1 of `npm run build`, before `build:lib` @@ -209,7 +202,6 @@ const TOP_LEVEL_FUNCTION_RE = /^(?:export\s+)?function\s+([A-Za-z0-9_]+)\s*\(/; const FUNCTION_SCOPED_EXEMPTIONS = new Map([ [path.join('src', 'core-utils.cts'), new Set(['generateSlugInternal'])], [path.join('src', 'gsd2-import.cts'), new Set(['slugify'])], - [path.join('src', 'runtime-artifact-conversion.cts'), new Set(['normalizeKimiSkillName'])], [path.join('scripts', 'generate-package-identity.cjs'), new Set(['slugifyPackageName'])], ]); diff --git a/scripts/live-config-guard.cjs b/scripts/live-config-guard.cjs index 7514df899..e858bc1df 100644 --- a/scripts/live-config-guard.cjs +++ b/scripts/live-config-guard.cjs @@ -150,8 +150,8 @@ const MSD_OWNED_NESTED = [ * them, for the same reason TEST_ENV_BASE derives its keys: a hand-list is only * ever as complete as its author's recall, and this one was measurably not. * - * Round 5's adversarial review found the hand-list missing Kilo's SINGULAR - * `command/`, `workflows/`, and Hermes' `skills/msd` -- the last being a whole + * Round 5's adversarial review found the hand-list missing a SINGULAR + * `command/` dir, `workflows/`, and a `skills/msd` dir -- the last being a whole * directory whose name carries no `msd-` prefix, so no prefix rule reaches it. * A capability that declares a new destSubpath now extends this set in the same * commit that declares it. diff --git a/scripts/release-tarball-smoke.cjs b/scripts/release-tarball-smoke.cjs index 1d8cb3057..6de8cedf8 100644 --- a/scripts/release-tarball-smoke.cjs +++ b/scripts/release-tarball-smoke.cjs @@ -408,8 +408,8 @@ 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 does not (see - // RUNTIME_CONFIG_FILES), so they are out of scope here rather than covered. + // runtimes reuse one of those two shapes; any that do not are out of scope + // here rather than covered. entrypointRuntimes = ['claude', 'codex'], dryRun = false, npmEnv = undefined, diff --git a/src/installer-migrations/010-antigravity-retire-confighome-artifacts.cts b/src/installer-migrations/010-antigravity-retire-confighome-artifacts.cts index c9b245288..7744e7ddd 100644 --- a/src/installer-migrations/010-antigravity-retire-confighome-artifacts.cts +++ b/src/installer-migrations/010-antigravity-retire-confighome-artifacts.cts @@ -126,7 +126,7 @@ const migration: InstallerMigration = { destructive: true, plan: (ctx: MigrationPlanContext): MigrationAction[] => { // Defense in depth ahead of the framework's own runtimes/scope filters: - // a claude/kimi/etc. skills/ or agents/ directory is a live install + // a claude/etc. skills/ or agents/ directory is a live install // surface, never a retirement target, and so is antigravity's LOCAL // .agents/skills layout. if (ctx.runtime !== 'antigravity') return []; diff --git a/tests/agent-descriptor-parity.install.test.cjs b/tests/agent-descriptor-parity.install.test.cjs index c1721127d..a0242254d 100644 --- a/tests/agent-descriptor-parity.install.test.cjs +++ b/tests/agent-descriptor-parity.install.test.cjs @@ -54,9 +54,7 @@ * 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 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). + * regression reach `next` undetected. * * H8 is mandatory, not optional: a parity harness never demonstrated failing * is decoration. It feeds the oracle a DELIBERATELY WRONG (but real, @@ -556,7 +554,7 @@ test('agent-descriptor-parity: J8 — opencode resolves model overrides through * "runtime"` capability in the REAL capability-registry that declares an * `agents` kind (either scope), the REAL production entry point * (`installRuntimeArtifacts` — which internally routes combinedFamilyInstall - * runtimes like kilo/opencode through `installOpencodeFamilyAgents`, #2875 + * runtimes like opencode through `installOpencodeFamilyAgents`, #2875 * Part 2 Task A) actually materializes agents/ on disk. If any runtime were * still silently depending on the deleted inline loop, this call would write * nothing to agents/ for it (the deleted code was the ONLY thing that used to diff --git a/tests/agent-fragments-emission.install.test.cjs b/tests/agent-fragments-emission.install.test.cjs index 3f79c508b..9269cac81 100644 --- a/tests/agent-fragments-emission.install.test.cjs +++ b/tests/agent-fragments-emission.install.test.cjs @@ -106,7 +106,7 @@ function scopesForRuntime(registry, runtime) { if (layout) { for (const scope of ['global', 'local']) { for (const entry of layout[scope] || []) { - if (entry.kind === 'agents' || entry.kind === 'kimi-agents') scopes.add(scope); + if (entry.kind === 'agents') scopes.add(scope); } } } diff --git a/tests/agent-frontmatter.test.cjs b/tests/agent-frontmatter.test.cjs index 7f755e536..a38c6e46b 100644 --- a/tests/agent-frontmatter.test.cjs +++ b/tests/agent-frontmatter.test.cjs @@ -216,13 +216,13 @@ describe('SPAWN: spawn type consistency', () => { } }); - test('execute-phase has Copilot sequential fallback in runtime_compatibility', () => { + test('execute-phase has a sequential inline fallback in runtime_compatibility', () => { const content = fs.readFileSync( path.join(WORKFLOWS_DIR, 'execute-phase.md'), 'utf-8' ); assert.ok( content.includes('sequential inline execution'), - 'execute-phase must document sequential inline execution as Copilot fallback' + 'execute-phase must document sequential inline execution as a fallback' ); assert.ok( content.includes('spot-check'), diff --git a/tests/agent-tools.install.test.cjs b/tests/agent-tools.install.test.cjs index 299e95f50..0fa6b0fb0 100644 --- a/tests/agent-tools.install.test.cjs +++ b/tests/agent-tools.install.test.cjs @@ -221,7 +221,7 @@ test('every installable runtime accepts a configured MCP grant without crashing // only 6 have deep per-runtime assertions elsewhere in this file. This locks // in that the other runtimes' own converters don't choke or mangle output // when a canonical grant is appended into their frontmatter dialect. - // scope: 'global' — universally supported (cline is global-only; local + // scope: 'global' — universally supported (local // support varies per runtime, global does not). Search from `install.home`, // not `install.configDir`: a nested-home runtime (e.g. antigravity) places // agents in a sibling directory outside its own configDir subtree. diff --git a/tests/antigravity-upgrades.test.cjs b/tests/antigravity-upgrades.test.cjs index e5ef7b79e..e6c39bd62 100644 --- a/tests/antigravity-upgrades.test.cjs +++ b/tests/antigravity-upgrades.test.cjs @@ -19,11 +19,11 @@ * UPGRADE 2 — MCP companion config: `configureAntigravityMcpConfig` writes * a standalone `mcp_config.json` (antigravity.google/docs/cli/gcli-migration) * registering the `msd` MCP server (`bin/msd-mcp-server.js`, the SAME - * companion OpenCode/Kilo document/wire), non-destructively preserving any + * companion OpenCode documents/wires), non-destructively preserving any * other user-configured `mcpServers` entries. Uninstall removes only the * `msd` entry. * - * Both writers are NOT MSD_TEST_MODE-gated (mirrors Kilo's dispatch, not + * Both writers are NOT MSD_TEST_MODE-gated (unlike * OpenCode's) — runMinimalInstall strips MSD_TEST_MODE from the spawned * installer's env, so both fire during a "minimal" install too. */ diff --git a/tests/capability-registry.test.cjs b/tests/capability-registry.test.cjs index c7fcb14fd..43735cf3b 100644 --- a/tests/capability-registry.test.cjs +++ b/tests/capability-registry.test.cjs @@ -3926,16 +3926,16 @@ describe('ADR-1016 phase 5a: validateConfigHome unit tests', () => { test('skillsHome with valid kind → no errors', () => { const errors = validateConfigHome('test', { - kind: 'xdg', name: 'kilo', env: [], - skillsHome: { kind: 'dot-home', name: '.kilo', env: [] }, + kind: 'xdg', name: 'opencode', env: [], + skillsHome: { kind: 'dot-home', name: '.opencode', env: [] }, }); assert.deepEqual(errors, []); }); test('skillsHome with bad kind → error', () => { const errors = validateConfigHome('test', { - kind: 'xdg', name: 'kilo', env: [], - skillsHome: { kind: 'exotic', name: '.kilo', env: [] }, + kind: 'xdg', name: 'opencode', env: [], + skillsHome: { kind: 'exotic', name: '.opencode', env: [] }, }); assert.ok(errors.some((e) => e.includes('skillsHome') && e.includes('kind'))); }); @@ -4164,12 +4164,12 @@ describe('FIX 3: tightened runtime validator — ArtifactKind field type checks' assert.deepEqual(converterErrors, [], 'converter: null must be accepted, got: ' + JSON.stringify(converterErrors)); }); - test('ArtifactKind with converter: "convertClaudeCommandToKiloSkill" (string) → no error', () => { + test('ArtifactKind with converter: "convertClaudeCommandToOpencodeSkill" (string) → no error', () => { const cap = makeRuntimeCap({ id: 'test-rt', runtime: { artifactLayout: { - global: [{ kind: 'skills', destSubpath: 'skills', prefix: 'msd-', nesting: 'flat', recursive: true, converter: 'convertClaudeCommandToKiloSkill' }], + global: [{ kind: 'skills', destSubpath: 'skills', prefix: 'msd-', nesting: 'flat', recursive: true, converter: 'convertClaudeCommandToOpencodeSkill' }], local: [], }, }, @@ -4294,57 +4294,26 @@ describe('ADR-1016 phase 5a: closed-vocab set exports', () => { // ─── 25. ADR-857 phase 5e: closed ConverterName enum (Part B) ───────────────── describe('ADR-857 phase 5e: VALID_CONVERTER_NAMES closed enum', () => { - // #2875 Part 2 (the agents-bypass closure): 3 converters added — - // convertClaudeAgentToHermesAgent (data-driven Hermes branding converter, - // reads hostBehaviors.brandingRewrites) and convertClaudeToKiloFrontmatter / - // convertClaudeToOpencodeFrontmatter (kilo/opencode's agents-kind - // converters — shared by name with those runtimes' commands-kind entries, - // options-bag signature `(content, {isAgent, modelOverride})`). All three - // are genuinely new agent converters (not renamed/leftover), so the agent - // count grows from 11 to 14; the 16 command/skill/workflow converters are - // unchanged. - test('VALID_CONVERTER_NAMES has exactly 30 entries (16 command/skill/workflow + 14 agent converters)', () => { + test('VALID_CONVERTER_NAMES has exactly 10 entries (5 command/skill + 5 agent converters)', () => { assert.ok(VALID_CONVERTER_NAMES instanceof Set, 'VALID_CONVERTER_NAMES must be a Set'); - assert.strictEqual(VALID_CONVERTER_NAMES.size, 30, 'VALID_CONVERTER_NAMES must have exactly 30 entries, got: ' + VALID_CONVERTER_NAMES.size); + assert.strictEqual(VALID_CONVERTER_NAMES.size, 10, 'VALID_CONVERTER_NAMES must have exactly 10 entries, got: ' + VALID_CONVERTER_NAMES.size); }); test('VALID_CONVERTER_NAMES contains all expected converter names', () => { const expected = [ // command/skill converters (pre-existing) 'convertClaudeCommandToAntigravitySkill', - 'convertClaudeCommandToAugmentSkill', - 'convertClaudeCommandToClineSkill', 'convertClaudeCommandToClaudeSkill', - 'convertClaudeCommandToCodebuddyCommand', - 'convertClaudeCommandToCodebuddySkill', 'convertClaudeCommandToCodexSkill', - 'convertClaudeCommandToCopilotSkill', 'convertClaudeCommandToCursorSkill', - 'convertClaudeCommandToKiloSkill', - 'convertClaudeCommandToKimiSkill', 'convertClaudeCommandToOpencodeSkill', - 'convertClaudeCommandToTraeSkill', - 'convertClaudeCommandToWindsurfSkill', - 'convertClaudeCommandToWindsurfWorkflow', // agent converters (#1173 — descriptor-driven agent conversion wiring) - 'convertClaudeAgentToCopilotAgent', 'convertClaudeAgentToAntigravityAgent', 'convertClaudeAgentToCursorAgent', - 'convertClaudeAgentToWindsurfAgent', - 'convertClaudeAgentToAugmentAgent', - 'convertClaudeAgentToTraeAgent', - 'convertClaudeAgentToCodebuddyAgent', - 'convertClaudeAgentToClineAgent', 'convertClaudeAgentToCodexAgent', - // ADR-1239 / #2092 Phase B Upgrade 1 — native .qwen/agents/*.md subagent projection. - 'convertClaudeAgentToQwenAgent', // #3384 — ZCode agent converter (strips mcp__* grants at install time). 'convertClaudeAgentToZcodeAgent', - // #2875 Part 2 (the agents-bypass closure) — data-driven Hermes branding - // converter, and the kilo/opencode agent converters (shared name with - // those runtimes' commands-kind entries). - 'convertClaudeAgentToHermesAgent', - 'convertClaudeToKiloFrontmatter', + // opencode agent converter (shared name with the runtime's commands-kind entries). 'convertClaudeToOpencodeFrontmatter', ]; for (const name of expected) { @@ -4379,8 +4348,8 @@ describe('ADR-857 phase 5e: validateArtifactKindEntry — ConverterName enum (FA }); // Valid known name must be accepted - test('ACCEPTED: converter "convertClaudeCommandToKiloSkill" is a known ConverterName', () => { - const entry = makeArtifactEntry({ converter: 'convertClaudeCommandToKiloSkill' }); + test('ACCEPTED: converter "convertClaudeCommandToOpencodeSkill" is a known ConverterName', () => { + const entry = makeArtifactEntry({ converter: 'convertClaudeCommandToOpencodeSkill' }); const errors = validateArtifactKindEntry('test-cap', entry, 'artifactLayout.global[0]'); const converterErrors = errors.filter((e) => e.includes('converter')); assert.deepEqual(converterErrors, [], 'Known converter name must be accepted, got: ' + JSON.stringify(converterErrors)); @@ -4666,7 +4635,7 @@ describe('ADR-857 phase 5f: cross-field consistency gate rejection tests (DEFECT ); }); - test('REJECTS: permissionWriter not null and not in {opencode,kilo} → throws validation error', () => { + test('REJECTS: permissionWriter not null and not in {opencode,antigravity} → throws validation error', () => { const cap = makeValidRuntimeCap({ runtime: { permissionWriter: 'notarealwriter' } }); const errors = validateRuntimeBody(cap); assert.ok( @@ -4800,7 +4769,7 @@ describe('ADR-857 phase 5f: cross-field consistency gate rejection tests (DEFECT test('VALID_EXTENDED_HOOK_EVENTS covers all 8 known extended events', () => { assert.ok(VALID_EXTENDED_HOOK_EVENTS instanceof Set, 'Must be a Set'); assert.strictEqual(VALID_EXTENDED_HOOK_EVENTS.size, 8, 'Must cover 8 extended hook events'); - // SubagentStart added #2092 Phase B Upgrade 2 (qwen-only today). + // SubagentStart added #2092 Phase B Upgrade 2. for (const ev of ['SubagentStop', 'Stop', 'PreCompact', 'FileChanged', 'BeforeAgent', 'AfterAgent', 'BeforeModel', 'SubagentStart']) { assert.ok(VALID_EXTENDED_HOOK_EVENTS.has(ev), 'Must include event "' + ev + '"'); } diff --git a/tests/check-glossary-refs.test.cjs b/tests/check-glossary-refs.test.cjs index c418022a9..feff714fd 100644 --- a/tests/check-glossary-refs.test.cjs +++ b/tests/check-glossary-refs.test.cjs @@ -25,8 +25,7 @@ const SCRIPT_REL = path.join('scripts', 'check-glossary-refs.cjs'); // The real bin/install.js allRuntimes array, mirrored here so fixtures can // build both a matching and a deliberately-drifted CONTEXT.md against it. const REAL_RUNTIMES = [ - 'claude', 'antigravity', 'augment', 'cline', 'codebuddy', 'codex', 'copilot', - 'cursor', 'hermes', 'kimi', 'kilo', 'opencode', 'pi', 'qwen', 'trae', 'windsurf', 'zcode', + 'claude', 'antigravity', 'codex', 'cursor', 'opencode', 'zcode', ]; function allRuntimesSentence(count, members) { @@ -80,7 +79,7 @@ test('a clean CONTEXT.md whose refs resolve and whose allRuntimes matches passes '', 'See `src/real-module.cts` for details.', '', - `${allRuntimesSentence(17, REAL_RUNTIMES)}.`, + `${allRuntimesSentence(6, REAL_RUNTIMES)}.`, '', ].join('\n'); const root = makeRepo(t, { contextBody: context }); @@ -96,7 +95,7 @@ test('a reference to a nonexistent tracked file fails --check and names the toke '', 'See `src/does-not-exist.cts` for details.', '', - `${allRuntimesSentence(17, REAL_RUNTIMES)}.`, + `${allRuntimesSentence(6, REAL_RUNTIMES)}.`, '', ].join('\n'); const root = makeRepo(t, { contextBody: context }); @@ -107,24 +106,24 @@ test('a reference to a nonexistent tracked file fails --check and names the toke }); test('allRuntimes count and membership drift is caught', (t) => { - // Mirrors the real-world case: CONTEXT.md says 15 while bin/install.js has 17. - const claimed15 = REAL_RUNTIMES.filter((r) => r !== 'pi' && r !== 'zcode'); - assert.equal(claimed15.length, 15); + // Mirrors the real-world case: CONTEXT.md says 4 while bin/install.js has 6. + const claimed4 = REAL_RUNTIMES.filter((r) => r !== 'cursor' && r !== 'zcode'); + assert.equal(claimed4.length, 4); const context = [ '# Context', '', 'See `src/real-module.cts` for details.', '', - `${allRuntimesSentence(15, claimed15)}.`, + `${allRuntimesSentence(4, claimed4)}.`, '', ].join('\n'); - const root = makeRepo(t, { contextBody: context }); // bin/install.js defaults to the real 17 + const root = makeRepo(t, { contextBody: context }); // bin/install.js defaults to the real 6 const res = run(root, ['--check']); assert.equal(res.status, 1); - assert.match(res.stderr, /claims 15 values/); - assert.match(res.stderr, /has 17/); - assert.match(res.stderr, /pi/); + assert.match(res.stderr, /claims 4 values/); + assert.match(res.stderr, /has 6/); + assert.match(res.stderr, /cursor/); assert.match(res.stderr, /zcode/); }); @@ -134,7 +133,7 @@ test('a reference to a nonexistent msd-core/bin/lib/*.cjs path is skipped (gener '', 'Generated router lives at `msd-core/bin/lib/does-not-exist.cjs`.', '', - `${allRuntimesSentence(17, REAL_RUNTIMES)}.`, + `${allRuntimesSentence(6, REAL_RUNTIMES)}.`, '', ].join('\n'); const root = makeRepo(t, { contextBody: context }); @@ -154,7 +153,7 @@ test('an intentionally-absent documented path is skipped, not asserted missing', '', 'The escape hatch is a committed acknowledgment (`tests/emitted-drift-ack.json`).', '', - `${allRuntimesSentence(17, REAL_RUNTIMES)}.`, + `${allRuntimesSentence(6, REAL_RUNTIMES)}.`, '', ].join('\n'); const root = makeRepo(t, { contextBody: context }); @@ -175,7 +174,7 @@ test('a sibling tests/ path that is merely missing is still caught', (t) => { '', 'See `tests/emitted-drift-ack-typo.json` for details.', '', - `${allRuntimesSentence(17, REAL_RUNTIMES)}.`, + `${allRuntimesSentence(6, REAL_RUNTIMES)}.`, '', ].join('\n'); const root = makeRepo(t, { contextBody: context }); @@ -191,7 +190,7 @@ test('a ~/-rooted path and a bare filename are both skipped', (t) => { '', 'See `~/.claude/x.md` and `core.cjs` for details.', '', - `${allRuntimesSentence(17, REAL_RUNTIMES)}.`, + `${allRuntimesSentence(6, REAL_RUNTIMES)}.`, '', ].join('\n'); const root = makeRepo(t, { contextBody: context }); @@ -211,7 +210,7 @@ test('a `..`-traversal token cannot escape ROOT into a filesystem-existence prob '', 'Escape attempt: `src/../../../../../../etc/passwd` and `src/../../etc/hosts`.', '', - `${allRuntimesSentence(17, REAL_RUNTIMES)}.`, + `${allRuntimesSentence(6, REAL_RUNTIMES)}.`, '', ].join('\n'); const root = makeRepo(t, { contextBody: context }); @@ -255,7 +254,7 @@ test('a broken tracked reference after a RULESET predicate with inner backticks '', 'Coverage lives in `src/does-not-exist.cts`.', '', - `${allRuntimesSentence(17, REAL_RUNTIMES)}.`, + `${allRuntimesSentence(6, REAL_RUNTIMES)}.`, '', ].join('\n'); const root = makeRepo(t, { contextBody: context }); @@ -279,7 +278,7 @@ test('tracked paths inside an outer-wrapped predicate span are extracted (#3604) '', '`WORKTREE.SEAM.demo-anchor=src/real-module.cts + tests/missing-anchor.test.cjs`', '', - `${allRuntimesSentence(17, REAL_RUNTIMES)}.`, + `${allRuntimesSentence(6, REAL_RUNTIMES)}.`, '', ].join('\n'); const root = makeRepo(t, { contextBody: context }); @@ -307,7 +306,7 @@ test('glob and NNNN template mentions are not drift (#3604)', (t) => { 'A fresh ADR starts from `docs/adr/NNNN.md`.', 'Retired scanners were `scripts/lint-*.cjs`.', '', - `${allRuntimesSentence(17, REAL_RUNTIMES)}.`, + `${allRuntimesSentence(6, REAL_RUNTIMES)}.`, '', ].join('\n'); const root = makeRepo(t, { contextBody: context }); @@ -335,7 +334,7 @@ test('retired-path exemptions are exact, not a blanket hole (#3604)', (t) => { '`RULESET.TESTS.harness=local plugin at eslint-rules/ (repo root, NOT scripts/eslint-rules/)`', 'A typo sibling `tests/golden-install-parity-typo.test.cjs` is real drift.', '', - `${allRuntimesSentence(17, REAL_RUNTIMES)}.`, + `${allRuntimesSentence(6, REAL_RUNTIMES)}.`, '', ].join('\n'); const root = makeRepo(t, { contextBody: context }); diff --git a/tests/claude-md.test.cjs b/tests/claude-md.test.cjs index 6c5165e65..ba88abe5b 100644 --- a/tests/claude-md.test.cjs +++ b/tests/claude-md.test.cjs @@ -41,7 +41,7 @@ describe('generate-claude-md', () => { const content = fs.readFileSync(claudePath, 'utf-8'); assert.ok(content.includes('## MSD Workflow Enforcement')); // #3584: generated CLAUDE.md must emit the runtime-routable hyphen-form - // (Claude/Cursor/OpenCode/Kilo etc.); the legacy colon form is no longer + // (Claude/Cursor/OpenCode etc.); the legacy colon form is no longer // dispatched by current skill installs. assert.ok(content.includes('/msd-quick')); assert.ok(content.includes('/msd-debug')); diff --git a/tests/claude-orchestration.test.cjs b/tests/claude-orchestration.test.cjs index e36fc06c5..28b9d11a6 100644 --- a/tests/claude-orchestration.test.cjs +++ b/tests/claude-orchestration.test.cjs @@ -128,7 +128,7 @@ describe('detectWorkflowBackend', () => { }); test('non-Claude runtime -> inline (criterion 6: no change to non-Claude loop)', () => { - for (const runtimeId of ['codex', 'cursor', 'opencode', 'copilot', ' Windsurf'.trim()]) { + for (const runtimeId of ['codex', 'cursor', 'opencode', 'zcode', 'antigravity']) { const r = detectWorkflowBackend({ runtimeId, hostIntegration: CAPABLE_HOST, diff --git a/tests/codex-config-install.test.cjs b/tests/codex-config-install.test.cjs index 4c4c53fb3..87a6e32a2 100644 --- a/tests/codex-config-install.test.cjs +++ b/tests/codex-config-install.test.cjs @@ -219,8 +219,8 @@ process.env.MSD_TEST_MODE = '1'; * --codex --global`. * * Fix: re-wire copyCommandsAsCodexSkills() back into the install dispatch path - * so the same skill-shape that Claude / Copilot / Antigravity / Cursor / - * Windsurf / Augment / Trae installs produce is also produced for Codex. + * so the same skill-shape that Claude / Antigravity / Cursor + * installs produce is also produced for Codex. */ const { describe, test, beforeEach, afterEach } = require('node:test'); @@ -1966,7 +1966,7 @@ process.env.MSD_TEST_MODE = '1'; * the right one per its HookHandlerConfig schema * (codex-rs/config/src/hook_config.rs: commandWindows / command_windows alias). * - * Note: UserPromptSubmit is NOT wired (same rationale as Qwen #788 — the + * Note: UserPromptSubmit is NOT wired (same rationale as #788 — the * msd-prompt-guard handler exits unless tool_name is Write|Edit, so it would be * a silent no-op for the UserPromptSubmit payload shape). * diff --git a/tests/codex-config.test.cjs b/tests/codex-config.test.cjs index 67a45266a..c8bcb7eeb 100644 --- a/tests/codex-config.test.cjs +++ b/tests/codex-config.test.cjs @@ -648,7 +648,7 @@ description: An unknown agent with no declared tools }); test('gates a provider-namespaced anthropic/claude-* model from the runtime-resolver path (#2310 review)', () => { - // Catalog assigns anthropic/claude-* to opencode/hermes/kilo. A mixed-runtime config + // Catalog assigns anthropic/claude-* to opencode. A mixed-runtime config // (runtime: opencode) + Codex install resolves those; they must NOT reach the .toml. const runtimeResolver = { runtime: 'opencode', resolve: () => ({ model: 'anthropic/claude-opus-4-8' }) }; const result = generateCodexAgentToml('msd-executor', sampleAgent, null, runtimeResolver); diff --git a/tests/commonjs-marker.test.cjs b/tests/commonjs-marker.test.cjs index 0bd8a8e62..e0beefa40 100644 --- a/tests/commonjs-marker.test.cjs +++ b/tests/commonjs-marker.test.cjs @@ -5,7 +5,7 @@ * * `installSharedHooksBundle` used to write `{"type":"commonjs"}` over * `/package.json` unconditionally — no existence check, no merge, - * no backup. On OpenCode and Kilo that file is documented, user-writable + * no backup. On OpenCode that file is documented, user-writable * territory (it is where local-plugin npm dependencies are declared), so every * install and every `/msd-update` destroyed the user's `name`, `type`, * `dependencies`, and `scripts`. @@ -522,14 +522,12 @@ describe('#2544 regression: install must not clobber the config-root package.jso // `staging zero hook scripts` tests above are the ones that pin a real // staged-nothing gate, on the #2717 writers. // - // ZCode, not Windsurf. Windsurf was the original choice because - // hostBehaviors.skipSharedHooksInstall kept it out of the shared bundle — - // but #2717 then began staging cursor/windsurf/codex .js hooks via dedicated - // paths and writing the marker beside them, so for those three MSD now DOES - // fill hooks/ and the marker is correct. ZCode is the durable choice: per - // #1821 it has hooksSurface:'none' AND no plugin surface to spawn hooks, so - // MSD stages no .js there by either route. (Measured on this tree: zcode - // stages 0 .js hooks and gets no marker; windsurf stages 2 and gets one.) + // ZCode: cursor/codex stage .js hooks via dedicated paths (#2717) and write + // the marker beside them, so for those MSD now DOES fill hooks/ and the + // marker is correct. ZCode is the durable choice: per #1821 it has + // hooksSurface:'none' AND no plugin surface to spawn hooks, so MSD stages + // no .js there by either route. (Measured on this tree: zcode stages 0 .js + // hooks and gets no marker.) const userHooks = path.join(root, 'hooks'); fs.mkdirSync(userHooks, { recursive: true }); fs.writeFileSync(path.join(userHooks, 'my-hook.js'), '// user-authored\n'); diff --git a/tests/configured-entrypoint-validation.test.cjs b/tests/configured-entrypoint-validation.test.cjs index b74803cfd..c2683be90 100644 --- a/tests/configured-entrypoint-validation.test.cjs +++ b/tests/configured-entrypoint-validation.test.cjs @@ -230,7 +230,7 @@ test('an EPERM from statSync (Windows equivalent of EACCES on a parent directory ]); }); -test('a selfExecutable + interpreterCandidates entry checks both the execute bit and the interpreter (#4249 CodeRabbit review — Cline\'s hybrid `env node` shebang)', (t) => { +test('a selfExecutable + interpreterCandidates entry checks both the execute bit and the interpreter (#4249 CodeRabbit review — hybrid `env node` shebang)', (t) => { const root = fs.mkdtempSync(path.join(os.tmpdir(), 'configured-entrypoint-self-exec-')); t.after(() => helpers.cleanup(root)); const okScript = path.join(root, 'ok.js'); @@ -239,8 +239,8 @@ test('a selfExecutable + interpreterCandidates entry checks both the execute bit fs.writeFileSync(notExecScript, '#!/usr/bin/env node\n'); const makeEntry = (scriptPath) => ({ - runtime: 'cline', - configPath: path.join(root, '.clinerules', 'hooks', 'PreToolUse'), + runtime: 'cursor', + configPath: path.join(root, '.cursor', 'hooks', 'PreToolUse'), scriptPath, interpreterCandidates: ['node'], selfExecutable: true, diff --git a/tests/cursor-conversion.test.cjs b/tests/cursor-conversion.test.cjs index 0206736ee..0fcb21b2d 100644 --- a/tests/cursor-conversion.test.cjs +++ b/tests/cursor-conversion.test.cjs @@ -90,7 +90,7 @@ Planner body // inside backtick spans, before punctuation) survived and triggered the // post-install audit "Found N unreplaced .claude path reference(s)". // -// Fix: add three bare-form rewrites (mirroring augment/windsurf/trae) using +// Fix: add three bare-form rewrites using // (?![\w-]) to avoid corrupting .claude-plugin / .claudeignore. // // TDD proof: these assertions FAIL before the fix and PASS after. diff --git a/tests/cursor-subagent-isolation.test.cjs b/tests/cursor-subagent-isolation.test.cjs index b8c6da314..c1599a9d7 100644 --- a/tests/cursor-subagent-isolation.test.cjs +++ b/tests/cursor-subagent-isolation.test.cjs @@ -107,7 +107,7 @@ describe('msd-cursor-subagent-start.js: isolation guard applicability (#3045)', let linkedWorktree; // real `git worktree add` of harnessProject, NOT under Cursor's managed // worktree root — a hand-made worktree the user opened themselves. // #3045 finding 3: this is no longer treated as isolation proof. - let noneProject; // config.json { runtime: 'windsurf' } -> resolves to 'none' + let noneProject; // config.json { runtime: 'zcode' } -> resolves to 'none' let orchestratorEnvProject; // exercised via MSD_RUNTIME=codex -> 'orchestrator-worktree' let noMsdDir; // no .planning at all let unreadableConfigProject; // config.json is a directory (EISDIR) @@ -126,7 +126,7 @@ describe('msd-cursor-subagent-start.js: isolation guard applicability (#3045)', git(['worktree', 'add', linkedPath, '-b', 'agent-msd-cs-iso-test'], harnessProject); linkedWorktree = linkedPath; - noneProject = makeGitProject('msd-cs-none-', JSON.stringify({ runtime: 'windsurf' })); + noneProject = makeGitProject('msd-cs-none-', JSON.stringify({ runtime: 'zcode' })); orchestratorEnvProject = makeGitProject('msd-cs-orch-', JSON.stringify({})); noMsdDir = createTempDir('msd-cs-nomsd-'); @@ -184,7 +184,7 @@ describe('msd-cursor-subagent-start.js: isolation guard applicability (#3045)', assert.equal(out.permission, undefined); }); - test('resolved mode none (config.json runtime=windsurf) -> allow', () => { + test('resolved mode none (config.json runtime=zcode) -> allow', () => { const r = runHook(subagentPayload([noneProject])); assert.equal(r.status, 0, `stdout: ${r.stdout}`); const out = JSON.parse(r.stdout); diff --git a/tests/declarative-reference-zcode.test.cjs b/tests/declarative-reference-zcode.test.cjs index f97cf3ffc..43e92eb3f 100644 --- a/tests/declarative-reference-zcode.test.cjs +++ b/tests/declarative-reference-zcode.test.cjs @@ -14,10 +14,9 @@ * block was previously empty. ZCode's golden install tree has ZERO hook * files (verified), so folding this onto `hostBehaviors.skipSharedHooksInstall` * is byte-parity — the same fold already done for - * windsurf/copilot/cursor/cline/kilo/trae (#2089/#2090/#2093/#2094/#2099/#2100). + * cursor (#2089/#2090/#2093/#2094/#2099/#2100). * - * This test is the reference-host dogfood mirroring - * tests/declarative-reference-windsurf.test.cjs: it (1) classifies ZCode's + * This test is the reference-host dogfood: it (1) classifies ZCode's * profile via profileOf, (2) confirms the public declarative adapter * classifies it as declarative, (3) round-trips a real install proving a * msd surface is emitted, (4) proves negotiation fails CLOSED on a corrupted @@ -120,7 +119,7 @@ test('a real ZCode install emits an invocable msd skill/command/agent surface', // --------------------------------------------------------------------------- // #2101 EoS/zcode — fail-closed negotiation + validator acceptance + -// the folded descriptor (mirrors codebuddy/windsurf/augment reference tests). +// the folded descriptor. // --------------------------------------------------------------------------- test('negotiateHostCapabilities never throws for zcode, even fully corrupted', () => { @@ -204,8 +203,8 @@ test('#4002 negative space: the project-local shim fallback survives the zcode r // AC-style proof: the 2 still-`undocumented` dispatch sub-axes (nested/ // maxDepth) must degrade to the most-restrictive KNOWN value, never their -// optimistic value. Unlike augment (3 undocumented sub-axes) or antigravity -// (4), zcode documents namedDispatch/background/subagentToolkit/ +// optimistic value. Unlike antigravity +// (4 undocumented sub-axes), zcode documents namedDispatch/background/subagentToolkit/ // backgroundDispatch, leaving only nested + maxDepth undocumented. Real // values confirmed via: // node -e "const {negotiateHostCapabilities}=require('./msd-core/bin/lib/host-integration.cjs'); diff --git a/tests/emitted-caps.test.cjs b/tests/emitted-caps.test.cjs deleted file mode 100644 index acd17e4dc..000000000 --- a/tests/emitted-caps.test.cjs +++ /dev/null @@ -1,434 +0,0 @@ -'use strict'; - -/** - * emitted-caps.test.cjs — the per-runtime emitted-byte cap decision - * (issue #2931, epic #1671, Phase 4). Exercises `tests/helpers/emitted-caps.cjs` - * per `.msd/phase/chore-2931-emitted-byte-caps/50-test-matrix.md` section A. - * - * Assertion discipline: every check compares typed structured values - * (`REASON` enum members, numeric fields) — never rendered prose - * (CONTRIBUTING.md, "Prohibited: Raw Text Matching on Test Outputs"). - */ - -const test = require('node:test'); -const assert = require('node:assert/strict'); -const fc = require('./helpers/fast-check-setup.cjs'); - -const { REASON, EMITTED_CAPS, evaluateEmittedCaps } = require('./helpers/emitted-caps.cjs'); - -const WINDSURF_PATTERN = 'workflows/*.md'; -const WINDSURF_CAP = 12000; - -// ─── A1-A8: happy path + boundaries + independence ─────────────────────────── - -test('passesRuntimeWithNoDeclaredCap', () => { - // `windsurf` must also report SOME sizes here: EMITTED_CAPS declares a - // windsurf cap, and a runtime the cap table names but `sizes` never - // mentions is REASON.UNKNOWN_RUNTIME (a distinct, more specific error — - // see A10/`errorsOnCapRuleForUnknownRuntime`), not the "no declared cap" - // path this test targets. Giving windsurf a compliant artifact keeps that - // orthogonal path out of this fixture while still proving `claude` (which - // truly has no cap entry) is recorded unmeasured and passes. - const r = evaluateEmittedCaps({ - sizes: { - claude: { 'workflows/plan-phase.md': 999999 }, - windsurf: { 'workflows/satisfies-the-rule.md': 100 }, - }, - capTable: EMITTED_CAPS, - }); - assert.equal(r.errors.length, 0); - assert.equal(r.violations.length, 0); - assert.equal(r.unmeasured.length, 1); - assert.equal(r.unmeasured[0].runtime, 'claude'); - assert.equal(r.compliant.length, 1); - assert.equal(r.compliant[0].runtime, 'windsurf'); - assert.ok(r.ok); -}); - -test('passesArtifactUnderCap', () => { - const r = evaluateEmittedCaps({ - sizes: { windsurf: { 'workflows/a.md': 100 } }, - }); - assert.equal(r.violations.length, 0); - assert.equal(r.compliant.length, 1); - assert.equal(r.compliant[0].bytes, 100); - assert.equal(r.compliant[0].cap, WINDSURF_CAP); - assert.ok(r.ok); -}); - -test('passesAtCapMinusOne', () => { - const r = evaluateEmittedCaps({ sizes: { windsurf: { 'workflows/a.md': WINDSURF_CAP - 1 } } }); - assert.equal(r.violations.length, 0); - assert.equal(r.compliant.length, 1); - assert.ok(r.ok); -}); - -test('passesAtExactlyCap', () => { - const r = evaluateEmittedCaps({ sizes: { windsurf: { 'workflows/a.md': WINDSURF_CAP } } }); - assert.equal(r.violations.length, 0, 'inclusive <= means the cap itself passes'); - assert.equal(r.compliant.length, 1); - assert.equal(r.compliant[0].bytes, WINDSURF_CAP); - assert.ok(r.ok); -}); - -test('failsAtCapPlusOne', () => { - const r = evaluateEmittedCaps({ sizes: { windsurf: { 'workflows/a.md': WINDSURF_CAP + 1 } } }); - assert.equal(r.compliant.length, 0); - assert.equal(r.violations.length, 1); - const v = r.violations[0]; - assert.equal(v.runtime, 'windsurf'); - assert.equal(v.rel, 'workflows/a.md'); - assert.equal(v.bytes, WINDSURF_CAP + 1); - assert.equal(v.cap, WINDSURF_CAP); - assert.equal(v.delta, 1); - assert.equal(v.reason, REASON.CAP_EXCEEDED); - assert.ok(!r.ok); -}); - -test('passesZeroByteArtifact', () => { - const r = evaluateEmittedCaps({ sizes: { windsurf: { 'workflows/empty.md': 0 } } }); - assert.equal(r.violations.length, 0, 'empty is not oversize'); - assert.equal(r.compliant.length, 1); - assert.equal(r.compliant[0].bytes, 0); - assert.ok(r.ok); -}); - -test('ignoresPathMatchingNoCapRule', () => { - // The sole windsurf rule (`workflows/*.md`) must ALSO match something in - // this fixture, or it is a dead rule across the whole run (A13/A14 — a - // deliberate hard error, see `errorsOnDeadCapRuleMatchingNothing`) and - // this test would be asserting two different failure modes at once. Give - // it a compliant match so the ONLY thing under test is: a path the rule - // doesn't match is ignored (unmeasured), not that the rule is dead. - const r = evaluateEmittedCaps({ - sizes: { - windsurf: { - 'skills/msd-add-tests/SKILL.md': 999999, - 'workflows/satisfies-the-rule.md': 100, - }, - }, - }); - assert.equal(r.violations.length, 0); - assert.equal(r.unmeasured.length, 1); - assert.equal(r.unmeasured[0].rel, 'skills/msd-add-tests/SKILL.md'); - assert.equal(r.compliant.length, 1); - assert.equal(r.deadRules.length, 0); - assert.ok(r.ok); -}); - -test('failsOnlyOffendingRuntimeForSharedRelPath', () => { - const capTable = { - windsurf: [{ pattern: WINDSURF_PATTERN, maxBytes: 100, note: 'test' }], - otherRuntime: [{ pattern: WINDSURF_PATTERN, maxBytes: 100, note: 'test' }], - }; - const r = evaluateEmittedCaps({ - sizes: { - windsurf: { 'workflows/shared.md': 200 }, // over - otherRuntime: { 'workflows/shared.md': 50 }, // under - }, - capTable, - }); - assert.equal(r.violations.length, 1); - assert.equal(r.violations[0].runtime, 'windsurf'); - assert.equal(r.compliant.length, 1); - assert.equal(r.compliant[0].runtime, 'otherRuntime'); - assert.ok(!r.ok); -}); - -// ─── A9-A20: negative / hostile ────────────────────────────────────────────── - -test('errorsOnDeadCapRuleMatchingNothing', () => { - const capTable = { windsurf: [{ pattern: WINDSURF_PATTERN, maxBytes: WINDSURF_CAP, note: 'x' }] }; - const r = evaluateEmittedCaps({ - sizes: { windsurf: { 'skills/other.md': 10 } }, // never matches the workflows/*.md rule - capTable, - }); - assert.equal(r.deadRules.length, 1); - assert.equal(r.deadRules[0].runtime, 'windsurf'); - assert.equal(r.deadRules[0].pattern, WINDSURF_PATTERN); - assert.equal(r.deadRules[0].reason, REASON.DEAD_RULE); - assert.ok(!r.ok, 'a cap guarding nothing is a hard error'); -}); - -test('errorsOnCapRuleForUnknownRuntime', () => { - const capTable = { 'ghost-runtime': [{ pattern: '*.md', maxBytes: 100, note: 'x' }] }; - const r = evaluateEmittedCaps({ - sizes: { claude: { 'a.md': 10 } }, - capTable, - }); - assert.equal(r.errors.length, 1); - assert.equal(r.errors[0].reason, REASON.UNKNOWN_RUNTIME); - assert.equal(r.errors[0].runtime, 'ghost-runtime'); - assert.ok(!r.ok); -}); - -test('errorsWhenRuntimeProducedNoArtifacts', () => { - const r = evaluateEmittedCaps({ sizes: { windsurf: {} } }); - assert.equal(r.errors.length, 1); - assert.equal(r.errors[0].reason, REASON.NO_ARTIFACTS); - assert.equal(r.errors[0].runtime, 'windsurf'); - assert.ok(!r.ok, 'never read "nothing to check" as "pass"'); -}); - -test('errorsOnMissingSizesMap', () => { - for (const bad of [null, undefined]) { - const r = evaluateEmittedCaps({ sizes: bad }); - assert.equal(r.errors.length, 1, `${bad} must be rejected`); - assert.equal(r.errors[0].reason, REASON.INVALID_SIZES); - assert.ok(!r.ok); - } -}); - -test('errorsOnNonObjectSizesMap', () => { - for (const bad of [0, 'str', [], true]) { - const r = evaluateEmittedCaps({ sizes: bad }); - assert.equal(r.errors.length, 1, `${JSON.stringify(bad)} must be rejected`); - assert.equal(r.errors[0].reason, REASON.INVALID_SIZES); - assert.equal(r.errors[0].receivedType, Array.isArray(bad) ? 'array' : typeof bad); - assert.ok(!r.ok); - } -}); - -test('errorsOnNonObjectCapTable', () => { - for (const bad of [null, [], 0, 'str']) { - const r = evaluateEmittedCaps({ sizes: { windsurf: { 'a.md': 1 } }, capTable: bad }); - assert.equal(r.errors.length, 1, `${JSON.stringify(bad)} must be rejected`); - assert.equal(r.errors[0].reason, REASON.INVALID_CAP_TABLE); - assert.ok(!r.ok); - } -}); - -test('treatsZeroCapAsAlwaysViolating', () => { - const capTable = { windsurf: [{ pattern: WINDSURF_PATTERN, maxBytes: 0, note: 'zero cap' }] }; - const violating = evaluateEmittedCaps({ sizes: { windsurf: { 'workflows/a.md': 1 } }, capTable }); - assert.equal(violating.violations.length, 1); - assert.equal(violating.violations[0].cap, 0); - assert.equal(violating.violations[0].delta, 1); - assert.equal(violating.errors.length, 0, 'maxBytes: 0 is a LEGAL table entry'); - - const stillPasses = evaluateEmittedCaps({ sizes: { windsurf: { 'workflows/a.md': 0 } }, capTable }); - assert.equal(stillPasses.violations.length, 0, 'a genuinely empty artifact still passes a zero cap'); - assert.equal(stillPasses.compliant.length, 1); -}); - -test('errorsOnNonPositiveIntegerCap', () => { - for (const bad of [-1, NaN, Infinity, 1.5]) { - const capTable = { windsurf: [{ pattern: WINDSURF_PATTERN, maxBytes: bad, note: 'x' }] }; - const r = evaluateEmittedCaps({ sizes: { windsurf: { 'skills/unrelated.md': 5 } }, capTable }); - const err = r.errors.find((e) => e.reason === REASON.INVALID_CAP_VALUE); - assert.ok(err, `${bad} must be rejected at table validation`); - assert.ok( - Number.isNaN(bad) ? Number.isNaN(err.value) : err.value === bad, - 'the raw offending value must be surfaced', - ); - assert.equal(r.deadRules.length, 0, 'an invalid rule must never also be reported as merely dead'); - assert.ok(!r.ok); - } -}); - -test('errorsOnStringCapValue', () => { - const capTable = { windsurf: [{ pattern: WINDSURF_PATTERN, maxBytes: '12000', note: 'x' }] }; - const r = evaluateEmittedCaps({ sizes: { windsurf: { 'workflows/a.md': 5 } }, capTable }); - const err = r.errors.find((e) => e.reason === REASON.INVALID_CAP_VALUE); - assert.ok(err); - assert.equal(err.value, '12000'); - assert.equal(typeof err.value, 'string', 'no implicit coercion — the raw string is surfaced, not 12000'); - assert.ok(!r.ok); -}); - -test('rejectsReservedKeysInSizesMap', () => { - // Genuine OWN properties named __proto__/constructor/prototype, built the - // way a real ingest (JSON.parse) would — not the object-literal special - // case that would set the prototype instead of a key. - for (const key of ['__proto__', 'constructor', 'prototype']) { - const topLevel = JSON.parse(JSON.stringify({ [key]: { 'a.md': 10 } })); - const runtimeLevel = JSON.parse(JSON.stringify({ windsurf: { [key]: 10 } })); - - const rTop = evaluateEmittedCaps({ sizes: topLevel, capTable: {} }); - const topErr = rTop.errors.find((e) => e.reason === REASON.RESERVED_KEY && e.scope === 'sizes-runtime'); - assert.ok(topErr, `${key} as a runtime key must be rejected loudly`); - assert.equal(topErr.key, key); - assert.ok(!rTop.ok); - - const rRel = evaluateEmittedCaps({ sizes: runtimeLevel, capTable: {} }); - const relErr = rRel.errors.find((e) => e.reason === REASON.RESERVED_KEY && e.scope === 'sizes-rel'); - assert.ok(relErr, `${key} as a rel key must be rejected loudly`); - assert.equal(relErr.key, key); - assert.equal(relErr.runtime, 'windsurf'); - assert.ok(!rRel.ok); - } - - // The brief's "in either map" also covers capTable's runtime keys. Must use - // the COMPUTED key form (`{ ['__proto__']: ... }`), matching the genuine - // OWN-property construction above: the literal object-initializer form - // `{ __proto__: ... }` is special-cased by the language to set the - // object's [[Prototype]] instead of creating an own property, so it would - // produce an object with ZERO own keys (nothing for JSON.stringify to - // serialize, and nothing for Object.keys(capTable) to ever see) — testing - // nothing at all rather than the hostile-key case this asserts on. - const capTableWithReservedRuntime = JSON.parse( - JSON.stringify({ ['__proto__']: [{ pattern: '*.md', maxBytes: 10, note: 'x' }] }), - ); - const rCapTable = evaluateEmittedCaps({ sizes: { windsurf: { 'a.md': 1 } }, capTable: capTableWithReservedRuntime }); - const capErr = rCapTable.errors.find((e) => e.reason === REASON.RESERVED_KEY && e.scope === 'capTable-runtime'); - assert.ok(capErr, '__proto__ as a capTable runtime key must be rejected loudly'); - assert.equal(capErr.key, '__proto__'); - assert.ok(!rCapTable.ok); -}); - -test('rejectsTraversalInCapPattern', () => { - for (const pattern of ['../workflows/*.md', '/workflows/*.md']) { - const capTable = { windsurf: [{ pattern, maxBytes: WINDSURF_CAP, note: 'x' }] }; - const r = evaluateEmittedCaps({ sizes: { windsurf: { 'workflows/a.md': 5 } }, capTable }); - const err = r.errors.find((e) => e.reason === REASON.UNSAFE_PATTERN); - assert.ok(err, `"${pattern}" must be rejected`); - assert.equal(err.pattern, pattern); - assert.ok(!r.ok); - } -}); - -test('errorsOnRuntimeWithEmptyArtifactSet', () => { - const r = evaluateEmittedCaps({ - sizes: { windsurf: {}, claude: { 'foo.md': 5 } }, - }); - const err = r.errors.find((e) => e.reason === REASON.NO_ARTIFACTS); - assert.ok(err, 'must not be excused just because another runtime has real content'); - assert.equal(err.runtime, 'windsurf'); - assert.equal(r.unmeasured.length, 1, 'the other runtime is still processed normally'); - assert.equal(r.unmeasured[0].runtime, 'claude'); - assert.ok(!r.ok); -}); - -// ─── A21-A23: shipping shape, idempotence, determinism ─────────────────────── - -test('usesShippingCallerShapeWithNoOptions', () => { - const sizes = { windsurf: { 'workflows/a.md': 100 } }; - const withNoOptions = evaluateEmittedCaps({ sizes }); - const withExplicitDefault = evaluateEmittedCaps({ sizes, capTable: EMITTED_CAPS }); - assert.deepEqual(withNoOptions, withExplicitDefault); -}); - -test('isIdempotentAcrossRepeatedEvaluation', () => { - const sizes = { windsurf: { 'workflows/a.md': WINDSURF_CAP + 1 }, claude: { 'x.md': 5 } }; - const capTable = { windsurf: [{ pattern: WINDSURF_PATTERN, maxBytes: WINDSURF_CAP, note: 'x' }] }; - const sizesBefore = JSON.stringify(sizes); - const capTableBefore = JSON.stringify(capTable); - - const r1 = evaluateEmittedCaps({ sizes, capTable }); - const r2 = evaluateEmittedCaps({ sizes, capTable }); - - assert.deepEqual(r1, r2); - assert.equal(JSON.stringify(sizes), sizesBefore, 'sizes must not be mutated'); - assert.equal(JSON.stringify(capTable), capTableBefore, 'capTable must not be mutated'); -}); - -test('returnsViolationsInStableSortedOrder', () => { - const capTable = { - zeta: [{ pattern: '*.md', maxBytes: 1, note: 'x' }], - alpha: [{ pattern: '*.md', maxBytes: 1, note: 'x' }], - }; - // Inserted deliberately out of sorted order so the assertion bites. - const r = evaluateEmittedCaps({ - sizes: { zeta: { 'z.md': 99 }, alpha: { 'a.md': 99 } }, - capTable, - }); - assert.equal(r.violations.length, 2); - assert.deepEqual(r.violations.map((v) => v.runtime), ['alpha', 'zeta']); -}); - -// ─── A24-A25: fast-check property tests ────────────────────────────────────── - -test('propertyUnderCapNeverViolates', () => { - fc.assert( - fc.property( - fc.nat({ max: 50000 }), - fc.nat({ max: 50000 }), - (bytes, maxBytes) => { - const capTable = { windsurf: [{ pattern: 'workflows/probe.md', maxBytes, note: 'x' }] }; - const r = evaluateEmittedCaps({ sizes: { windsurf: { 'workflows/probe.md': bytes } }, capTable }); - assert.equal(r.errors.length, 0); - for (const v of r.violations) { - assert.ok(v.bytes > v.cap, 'no artifact <= its cap may ever appear in violations'); - } - if (bytes <= maxBytes) { - assert.equal(r.violations.length, 0); - assert.equal(r.compliant.length, 1); - } else { - assert.equal(r.violations.length, 1); - assert.equal(r.compliant.length, 0); - } - }, - ), - ); -}); - -test('propertyEveryArtifactLandsInExactlyOneBucket', () => { - const runtimeArb = fc.constantFrom('windsurf', 'cursor', 'claude', 'trae', 'roo'); - const relArb = fc - .tuple( - fc.constantFrom('workflows', 'agents', 'skills', 'commands'), - fc.constantFrom('alpha', 'beta', 'gamma', 'delta', 'epsilon'), - fc.constantFrom('md', 'yaml', 'toml'), - ) - .map(([dir, name, ext]) => `${dir}/${name}.${ext}`); - - fc.assert( - fc.property( - fc.array( - fc.record({ runtime: runtimeArb, rel: relArb, bytes: fc.nat({ max: 20000 }) }), - { minLength: 0, maxLength: 25 }, - ), - fc.array( - fc.record({ - runtime: runtimeArb, - patternKind: fc.constantFrom('exact', 'wildcard'), - dir: fc.constantFrom('workflows', 'agents', 'skills', 'commands'), - maxBytes: fc.nat({ max: 20000 }), - }), - { minLength: 0, maxLength: 8 }, - ), - (entries, ruleSpecs) => { - const sizes = {}; - const expectedKeys = new Set(); - for (const { runtime, rel, bytes } of entries) { - sizes[runtime] = sizes[runtime] || {}; - sizes[runtime][rel] = bytes; - expectedKeys.add(`${runtime}::${rel}`); - } - // Every runtime named in sizes must have at least one artifact, or - // evaluateEmittedCaps correctly reports NO_ARTIFACTS instead of - // conserving it — filtered out here since the property is about the - // WELL-FORMED subset (see the module's conservation-law comment). - for (const runtime of Object.keys(sizes)) { - if (Object.keys(sizes[runtime]).length === 0) delete sizes[runtime]; - } - fc.pre(Object.keys(sizes).length > 0); // nothing to conserve this run - - const capTable = {}; - for (const { runtime, patternKind, dir, maxBytes } of ruleSpecs) { - capTable[runtime] = capTable[runtime] || []; - const pattern = patternKind === 'exact' ? `${dir}/fixed.md` : `${dir}/*.md`; - capTable[runtime].push({ pattern, maxBytes, note: 'property' }); - } - // Only reference runtimes that are actually present in sizes, so this - // run never trips UNKNOWN_RUNTIME noise unrelated to the conservation - // law under test. - for (const runtime of Object.keys(capTable)) { - if (!Object.prototype.hasOwnProperty.call(sizes, runtime)) delete capTable[runtime]; - } - - const r = evaluateEmittedCaps({ sizes, capTable }); - - const seen = new Set(); - for (const bucket of [r.violations, r.unmeasured, r.compliant]) { - for (const rec of bucket) { - const key = `${rec.runtime}::${rec.rel}`; - assert.ok(!seen.has(key), `${key} appeared in more than one bucket`); - seen.add(key); - } - } - assert.deepEqual([...seen].sort(), [...expectedKeys].sort()); - }, - ), - ); -}); diff --git a/tests/emitted-provenance.test.cjs b/tests/emitted-provenance.test.cjs index 68bd76a94..fb2dfcbab 100644 --- a/tests/emitted-provenance.test.cjs +++ b/tests/emitted-provenance.test.cjs @@ -18,8 +18,7 @@ * 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. Two real instances of that class were caught while - * building this table (Copilot's `.agent.md` rename and Copilot's - * `hooks/msd-session.json`), both of which passed totality + * building this table, 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. * diff --git a/tests/execute-phase-active-flags.test.cjs b/tests/execute-phase-active-flags.test.cjs index 0147f5639..7f7a3df8e 100644 --- a/tests/execute-phase-active-flags.test.cjs +++ b/tests/execute-phase-active-flags.test.cjs @@ -573,7 +573,7 @@ describe('#3177: matrix section extraction is bounded by its heading', () => { assert.equal(matrixField(doc, 'claude', '__definitely_not_a_field__'), null); assert.notEqual( matrixField(doc, 'claude', 'effortSurface'), - matrixField(doc, 'kilo', 'effortSurface'), + matrixField(doc, 'antigravity', 'effortSurface'), 'two hosts with different values must not resolve to the same cell', ); }); diff --git a/tests/execute-phase-completion-reconciliation.test.cjs b/tests/execute-phase-completion-reconciliation.test.cjs index 9a5b13f73..ec8d34454 100644 --- a/tests/execute-phase-completion-reconciliation.test.cjs +++ b/tests/execute-phase-completion-reconciliation.test.cjs @@ -227,7 +227,7 @@ describe('execute-phase completion reconciliation (#4217 — split A of #3754)', assert.ok(content.includes('spot-check'), 'execute-phase must keep spot-check fallback vocabulary'); assert.ok( content.includes('sequential inline execution'), - 'execute-phase must keep the Copilot sequential inline fallback wording' + 'execute-phase must keep the sequential inline fallback wording' ); }); diff --git a/tests/feat-2483-review-claude-mds-guard.test.cjs b/tests/feat-2483-review-claude-mds-guard.test.cjs index 7e412e519..f1d2933e3 100644 --- a/tests/feat-2483-review-claude-mds-guard.test.cjs +++ b/tests/feat-2483-review-claude-mds-guard.test.cjs @@ -7,9 +7,9 @@ * * That made it the only reviewer seeing anything beyond the prompt file: the prompt is assembled * once (PROJECT.md, the roadmap section, every PLAN file, CONTEXT.md, RESEARCH.md, REQUIREMENTS.md) - * before any lane runs, qwen receives only that prompt, and codex runs `--ephemeral`. Beyond the + * before any lane runs, the other lanes receive only that prompt, and codex runs `--ephemeral`. Beyond the * measured injection cost, the asymmetry cuts at the workflow's premise — "independent review" - * meant something different for the claude lane than for the other two. + * meant something different for the claude lane than for the others. * * The fix is declared data, not a handler: the claude lane carries `invoke.env`, the resolver folds * it into the plan, and the runner merges it over the inherited environment for that ONE child. @@ -121,7 +121,7 @@ describe('#2483 the claude reviewer lane suppresses CLAUDE.md + auto-memory inje 'the claude lane must declare BOTH CLAUDE_CODE_DISABLE_CLAUDE_MDS=1 and ' + 'CLAUDE_CODE_DISABLE_AUTO_MEMORY=1 — CLAUDE.md loading and auto-memory are ' + 'independently-toggled mechanisms, and a lane missing either re-inherits that half of the ' + - 'context, reintroducing the asymmetry against the prompt-fed qwen and codex lanes' + 'context, reintroducing the asymmetry against the prompt-fed lanes' ); }); diff --git a/tests/gen-context-index.test.cjs b/tests/gen-context-index.test.cjs index b8001fc51..6ea69c258 100644 --- a/tests/gen-context-index.test.cjs +++ b/tests/gen-context-index.test.cjs @@ -8,10 +8,9 @@ * * The committed artifact is plain JSON (not a `.cjs` CommonJS module): a * shipped runtime module is the wrong place for ~120 KB of arbitrary - * CONTEXT.md prose, and embedding it there tripped both - * tests/cline-install.test.cjs (leaked `.claude/hooks/...` path literals) and + * CONTEXT.md prose, and embedding it there tripped * tests/package-name-single-source.test.cjs (hardcoded package-name - * literals) — both true positives against runtime-code content scanning. + * literals) — a true positive against runtime-code content scanning. * docs/CONTEXT-INDEX.json mirrors docs/INVENTORY-MANIFEST.json's precedent: * a committed, generated, `--check`-guarded JSON manifest that is not * runtime code. diff --git a/tests/golden-parity-single-source.test.cjs b/tests/golden-parity-single-source.test.cjs index 65a4f722b..2e427e8f0 100644 --- a/tests/golden-parity-single-source.test.cjs +++ b/tests/golden-parity-single-source.test.cjs @@ -278,11 +278,9 @@ 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: several runtimes present in RUNTIME_META - // (e.g. hermes, zcode) were missing from it, so `scope: 'local'` + // (e.g. 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 - // left the divergence itself intact for the next runtime to rediscover. + // TypeError naming neither the runtime nor the map at fault. // // Same anti-divergence pattern as the buildParityManifest guard above: the // duplicate is gone, and this asserts it does not come back. diff --git a/tests/helpers.cjs b/tests/helpers.cjs index 4c4b0cbb3..986484e95 100644 --- a/tests/helpers.cjs +++ b/tests/helpers.cjs @@ -126,7 +126,7 @@ function configLocationEnvKeys() { // 1. Every runtime descriptor the capability registry carries — including // the nested skillsHome descriptor, which resolves independently of // configHome (resolveSkillsBaseFromDescriptor) and can carry its own - // env array. Inert today (only kilo declares skillsHome, with env: []), + // env array. Inert today (no runtime declares skillsHome env), // but walking configHome.env alone is the identical gap-shape this PR // closed twice already, one field over. (#2665 round 4) ...Object.values(runtimes).flatMap((r) => r?.runtime?.configHome?.env ?? []), diff --git a/tests/helpers/emitted-caps.cjs b/tests/helpers/emitted-caps.cjs deleted file mode 100644 index 4cd30da30..000000000 --- a/tests/helpers/emitted-caps.cjs +++ /dev/null @@ -1,462 +0,0 @@ -'use strict'; - -/** - * emitted-caps.cjs — the per-runtime emitted-byte cap decision (issue #2931, - * epic #1671, Phase 4). Sibling law to `emitted-diff.cjs`'s conservation law: - * same pure/IO split, same error-accumulation shape, same reserved-key guard. - * - * ── Why this module is pure ────────────────────────────────────────────────── - * No fs, no git, no clock, no process. The expensive part — spawning an - * install and measuring real emitted bytes — lives elsewhere; this module only - * decides, given a `{ [runtime]: { [rel]: bytes } }` map and a cap table, - * which artifacts violate, which are unmeasured, and which comply. Keeping the - * decision pure makes every boundary (`cap-1`/`cap`/`cap+1`) a millisecond - * table test and keeps the Stryker gate able to bite. - * - * ── Why the cap table is HARD-CODED here, not read from capability.json ───── - * ADR-2719's guiding principle (already load-bearing in `emitted-diff.cjs`: - * it never re-derives a byte, because asserting `emitted == transform(source)` - * is the tautology ADR-2264's Amendment rejected) applies here too, one level - * up: a cap that is DERIVED from the same descriptor it is meant to guard - * (`capabilities//capability.json`) would silently follow any edit to that - * descriptor. Bump the descriptor, the guard bumps with it, and a real - * regression sails through unnoticed. `EMITTED_CAPS` is a second, independent - * source of truth, edited deliberately by a human who has to look Windsurf's - * actual 12,000-byte platform limit in the eye — exactly the friction a guard - * exists to provide. - */ - -// ─── REASON enum ─────────────────────────────────────────────────────────── - -const REASON = Object.freeze({ - CAP_EXCEEDED: 'cap_exceeded', - DEAD_RULE: 'dead_rule', - UNKNOWN_RUNTIME: 'unknown_runtime', - NO_ARTIFACTS: 'no_artifacts', - INVALID_SIZES: 'invalid_sizes', - INVALID_CAP_TABLE: 'invalid_cap_table', - INVALID_CAP_VALUE: 'invalid_cap_value', - RESERVED_KEY: 'reserved_key', - UNSAFE_PATTERN: 'unsafe_pattern', -}); - -/** - * Keys that can never legitimately name a runtime or an emitted path, and - * that also happen to be the JS-object footguns. Mirrors - * `emitted-diff.cjs`'s `RESERVED_ACK_KEYS`: rejected LOUDLY (REASON.RESERVED_KEY), - * never silently dropped. - */ -const RESERVED_KEYS = new Set(['__proto__', 'constructor', 'prototype']); - -/** - * The declared per-runtime emitted-byte cap table. Exactly one real entry - * today: Windsurf hard-caps a workspace workflow file at 12,000 bytes. - * - * Shape: `{ [runtime]: Array<{ pattern, maxBytes, note }> }`. `pattern` is a - * simple glob, evaluated against the emitted-relative path: `*` matches - * within one path segment (never crosses `/`), `**` matches across segments. - * Frozen two levels deep so a test cannot mutate the shipped table out from - * under a later assertion in the same run. - */ -const EMITTED_CAPS = Object.freeze({ - windsurf: Object.freeze([ - Object.freeze({ - pattern: 'workflows/*.md', - maxBytes: 12000, - note: 'Windsurf hard-caps workspace workflow files at 12,000 bytes.', - }), - ]), -}); - -// ─── Small predicates ──────────────────────────────────────────────────────── - -function isPlainObject(value) { - return value !== null && typeof value === 'object' && !Array.isArray(value); -} - -function typeNameOf(value) { - if (value === null) return 'null'; - if (Array.isArray(value)) return 'array'; - return typeof value; -} - -/** A legal byte count or cap value: a `number`, never a coerced string, never - * negative, NaN, Infinity, or fractional. `Number.isSafeInteger` alone - * already excludes NaN/Infinity/non-integers; `>= 0` excludes negatives. */ -function isNonNegativeSafeInteger(value) { - return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0; -} - -/** `..` traversal or a leading `/` in a cap-table pattern is never legitimate - * — every emitted-relative path in this repo is already relative and - * segment-clean, so either shape can only be an authoring mistake or a - * hostile table entry. */ -function isUnsafePattern(pattern) { - return pattern.includes('..') || pattern.startsWith('/'); -} - -/** - * Translate a `pattern` (already validated safe) into an anchored RegExp. - * `*` -> one path segment (`[^/]*`); `**` -> across segments (`.*`). Every - * other character is escaped, so a pattern is never accidentally read as a - * richer regex than the two glob tokens it declares. - */ -function compileGlobPattern(pattern) { - let out = ''; - for (let i = 0; i < pattern.length; i += 1) { - const ch = pattern[i]; - if (ch === '*' && pattern[i + 1] === '*') { - out += '.*'; - i += 1; - } else if (ch === '*') { - out += '[^/]*'; - } else if (/[.+^${}()|[\]\\]/.test(ch)) { - out += `\\${ch}`; - } else { - out += ch; - } - } - return new RegExp(`^${out}$`); -} - -/** Stable comparator: runtime, then rel/pattern. Plain `<`/`>` on strings, - * not `localeCompare`, so ordering is locale-independent and reproducible. */ -function byRuntimeThen(field) { - return (a, b) => { - if (a.runtime !== b.runtime) return a.runtime < b.runtime ? -1 : 1; - if (a[field] === b[field]) return 0; - return a[field] < b[field] ? -1 : 1; - }; -} - -// ─── The decision ──────────────────────────────────────────────────────────── - -/** - * Evaluate every measured emitted artifact against the declared cap table. - * - * ── The conservation law (by construction) ─────────────────────────────── - * Every `(runtime, rel)` key in `sizes` whose byte value is a legal - * non-negative safe integer, and whose `runtime`/`rel` are not reserved - * keys, is placed into EXACTLY ONE of `violations`, `unmeasured`, or - * `compliant` — the single walk below assigns each such key to precisely one - * push. A key excluded by a RESERVED_KEY or INVALID_SIZES error is not a - * legitimate size measurement at all (its own error already names it), so it - * is not counted as a fourth bucket; the law is over the WELL-FORMED subset, - * exactly as `diffEmitted`'s conservation law is over the entries `parseAck` - * accepted. - * - * Internal bookkeeping (which rule matched which path, for dead-rule - * detection) is kept as index-based tracking, never as an object keyed by an - * external string — so a hostile `rel`/`runtime` value cannot pollute - * anything even transiently. - * - * @param {object} opts - * @param {object} opts.sizes { [runtime]: { [rel]: bytes } } - * @param {object} [opts.capTable] defaults to the shipped `EMITTED_CAPS` - * @returns {{ - * violations: Array, unmeasured: Array, compliant: Array, - * deadRules: Array, errors: Array, ok: boolean - * }} - */ -function evaluateEmittedCaps({ sizes, capTable = EMITTED_CAPS } = {}) { - const errors = []; - - if (sizes === null || sizes === undefined) { - errors.push({ - reason: REASON.INVALID_SIZES, - receivedType: sizes === null ? 'null' : 'undefined', - message: `sizes is required, got ${sizes === null ? 'null' : 'undefined'}`, - }); - } else if (!isPlainObject(sizes)) { - errors.push({ - reason: REASON.INVALID_SIZES, - receivedType: typeNameOf(sizes), - message: `sizes must be a plain object keyed by runtime, got ${typeNameOf(sizes)}`, - }); - } - - if (capTable === null || capTable === undefined || !isPlainObject(capTable)) { - errors.push({ - reason: REASON.INVALID_CAP_TABLE, - receivedType: typeNameOf(capTable), - message: `capTable must be a plain object keyed by runtime, got ${typeNameOf(capTable)}`, - }); - } - - if (errors.length) { - return { violations: [], unmeasured: [], compliant: [], deadRules: [], errors, ok: false }; - } - - const violations = []; - const unmeasured = []; - const compliant = []; - const deadRules = []; - - // ── capTable validation, once, up front — never at compare time ────────── - // Maps `runtime -> Array<{ regex, maxBytes, note, pattern, matches: number }>` - // for runtimes that ARE known (present in `sizes`) and whose rules parsed - // cleanly. Keyed on `runtime`, but only ever assigned via `Map.set`, never - // bracket-property assignment — a hostile `__proto__` runtime name cannot - // pollute anything here either. - const compiledRules = new Map(); - const knownRuntimeNoArtifacts = new Set(); - - for (const runtime of Object.keys(capTable)) { - if (RESERVED_KEYS.has(runtime)) { - errors.push({ - reason: REASON.RESERVED_KEY, - scope: 'capTable-runtime', - key: runtime, - message: `capTable key "${runtime}" is reserved and can never be a real runtime name`, - }); - continue; - } - - if (!Object.prototype.hasOwnProperty.call(sizes, runtime)) { - // A cap declared for a runtime that never even reported sizes. Not - // installable/measured in this run at all — a hard error, distinct - // from "measured but produced zero files" (REASON.NO_ARTIFACTS below). - errors.push({ - reason: REASON.UNKNOWN_RUNTIME, - runtime, - message: `capTable declares caps for "${runtime}", but sizes has no entry for it`, - }); - continue; - } - - const rules = capTable[runtime]; - if (!Array.isArray(rules)) { - errors.push({ - reason: REASON.INVALID_CAP_TABLE, - runtime, - message: `capTable.${runtime} must be an array of rules, got ${typeNameOf(rules)}`, - }); - continue; - } - - const parsed = []; - rules.forEach((rule, index) => { - if (!isPlainObject(rule)) { - errors.push({ - reason: REASON.INVALID_CAP_TABLE, - runtime, - index, - message: `capTable.${runtime}[${index}] must be an object with pattern/maxBytes`, - }); - return; - } - - const { pattern, maxBytes, note } = rule; - - let patternOk = true; - if (typeof pattern !== 'string' || pattern.length === 0) { - errors.push({ - reason: REASON.INVALID_CAP_TABLE, - runtime, - index, - message: `capTable.${runtime}[${index}].pattern must be a non-empty string`, - }); - patternOk = false; - } else if (isUnsafePattern(pattern)) { - errors.push({ - reason: REASON.UNSAFE_PATTERN, - runtime, - pattern, - message: `capTable.${runtime}[${index}].pattern "${pattern}" contains a ".." traversal or a leading "/"`, - }); - patternOk = false; - } - - let capOk = true; - if (!isNonNegativeSafeInteger(maxBytes)) { - errors.push({ - reason: REASON.INVALID_CAP_VALUE, - runtime, - pattern: typeof pattern === 'string' ? pattern : null, - value: maxBytes, - message: - `capTable.${runtime}[${index}].maxBytes must be a non-negative safe integer, ` - + `got ${JSON.stringify(maxBytes)} (${typeNameOf(maxBytes)})`, - }); - capOk = false; - } - - if (patternOk && capOk) { - parsed.push({ - pattern, - regex: compileGlobPattern(pattern), - maxBytes, - note: typeof note === 'string' ? note : undefined, - matches: 0, - }); - } - }); - - compiledRules.set(runtime, parsed); - } - - // ── Walk sizes, sorted, and assign every well-formed key exactly once ──── - for (const runtime of Object.keys(sizes).sort()) { - if (RESERVED_KEYS.has(runtime)) { - errors.push({ - reason: REASON.RESERVED_KEY, - scope: 'sizes-runtime', - key: runtime, - message: `sizes key "${runtime}" is reserved and can never be a real runtime name`, - }); - continue; - } - - const artifacts = sizes[runtime]; - if (!isPlainObject(artifacts)) { - errors.push({ - reason: REASON.INVALID_SIZES, - runtime, - message: `sizes.${runtime} must be a plain object of { rel: bytes }, got ${typeNameOf(artifacts)}`, - }); - continue; - } - - const rels = Object.keys(artifacts); - if (rels.length === 0) { - errors.push({ - reason: REASON.NO_ARTIFACTS, - runtime, - message: `sizes.${runtime} produced no artifacts — never read "nothing to check" as "pass"`, - }); - knownRuntimeNoArtifacts.add(runtime); - continue; - } - - const rules = compiledRules.get(runtime) || []; - - for (const rel of rels.sort()) { - if (RESERVED_KEYS.has(rel)) { - errors.push({ - reason: REASON.RESERVED_KEY, - scope: 'sizes-rel', - runtime, - key: rel, - message: `sizes.${runtime} key "${rel}" is reserved and can never be a real emitted path`, - }); - continue; - } - - const bytes = artifacts[rel]; - if (!isNonNegativeSafeInteger(bytes)) { - errors.push({ - reason: REASON.INVALID_SIZES, - runtime, - rel, - message: - `sizes.${runtime}["${rel}"] must be a non-negative safe integer, ` - + `got ${JSON.stringify(bytes)} (${typeNameOf(bytes)})`, - }); - continue; - } - - const rule = rules.find((r) => r.regex.test(rel)); - if (!rule) { - unmeasured.push({ runtime, rel, bytes }); - continue; - } - - rule.matches += 1; - if (bytes <= rule.maxBytes) { - compliant.push({ runtime, rel, bytes, cap: rule.maxBytes }); - } else { - violations.push({ - runtime, - rel, - bytes, - cap: rule.maxBytes, - delta: bytes - rule.maxBytes, - reason: REASON.CAP_EXCEEDED, - note: rule.note, - }); - } - } - } - - // ── Dead rules: matched nothing across the WHOLE run ────────────────────── - // Skipped for runtimes already flagged UNKNOWN_RUNTIME or NO_ARTIFACTS — - // those are more specific, more actionable errors, and a rule for a - // runtime with zero measured artifacts trivially matches nothing for a - // reason this walk already named. - for (const [runtime, rules] of compiledRules) { - if (knownRuntimeNoArtifacts.has(runtime)) continue; - for (const rule of rules) { - if (rule.matches === 0) { - deadRules.push({ - runtime, - pattern: rule.pattern, - cap: rule.maxBytes, - reason: REASON.DEAD_RULE, - message: `capTable.${runtime} rule "${rule.pattern}" matched zero emitted paths — a cap guarding nothing is rot`, - }); - } - } - } - - violations.sort(byRuntimeThen('rel')); - unmeasured.sort(byRuntimeThen('rel')); - compliant.sort(byRuntimeThen('rel')); - deadRules.sort(byRuntimeThen('pattern')); - - const ok = errors.length === 0 && violations.length === 0 && deadRules.length === 0; - - return { violations, unmeasured, compliant, deadRules, errors, ok }; -} - -// ─── Rendering ──────────────────────────────────────────────────────────────── - -/** - * Pure renderer. Tests assert on `evaluateEmittedCaps`'s structured result, - * never on this string (CONTRIBUTING.md, "Prohibited: Raw Text Matching on - * Test Outputs"). - * - * The CAP_EXCEEDED message deliberately warns against the Goodhart's-Law - * escape hatch: moving bytes behind an EAGERLY `@`-imported reference file - * changes where the bytes are typed, not how many bytes load. An eager - * `@`-import is inlined at load time, so the cap would read green while the - * runtime still pays every byte — gaming the metric, not complying with it - * (this exact failure mode is recorded in CONTEXT.md - * `RULESET.WORKFLOW_SIZE_BUDGET`). - */ -function formatCapReport(result) { - const parts = []; - - if (result.errors.length) { - parts.push( - `${result.errors.length} error(s):\n ${result.errors.map((e) => e.message).join('\n ')}`, - ); - } - - if (result.violations.length) { - const list = result.violations.map( - (v) => ` ${v.runtime}: ${v.rel} is ${v.bytes} bytes — exceeds the ${v.cap}-byte cap by ${v.delta}` - + (v.note ? ` (${v.note})` : ''), - ); - parts.push( - `${result.violations.length} emitted artifact(s) exceed their declared cap:\n${list.join('\n')}\n\n` - + 'Moving these bytes behind an EAGERLY `@`-imported reference file is gaming this ' - + 'metric, not complying with it: an eager @-import is inlined before the cap is ever ' - + 'measured, so the runtime still pays every byte at load time (CONTEXT.md ' - + 'RULESET.WORKFLOW_SIZE_BUDGET). The fix is to reduce what actually loads.', - ); - } - - if (result.deadRules.length) { - const list = result.deadRules.map((r) => ` ${r.runtime}: "${r.pattern}" (cap ${r.cap}) matched nothing`); - parts.push(`${result.deadRules.length} cap rule(s) matched zero emitted paths — a cap guarding nothing is rot:\n${list.join('\n')}`); - } - - return parts.join('\n\n'); -} - -module.exports = { - REASON, - EMITTED_CAPS, - evaluateEmittedCaps, - formatCapReport, -}; diff --git a/tests/helpers/emitted-provenance.cjs b/tests/helpers/emitted-provenance.cjs index 96f7e1371..505b7e2cf 100644 --- a/tests/helpers/emitted-provenance.cjs +++ b/tests/helpers/emitted-provenance.cjs @@ -59,8 +59,7 @@ const { MANIFEST_FAMILIES, runMinimalInstall, buildParityManifest } = require('. const EXPECTED_MANIFEST_COUNT = MANIFEST_FAMILIES.length; // ─── Emitted roots ──────────────────────────────────────────────────────────── -// Longest-first: `skills/msd` (hermes category dir) must win over `skills` for -// `skills/msd/...`, and `.agents/skills` must win over `.agents`. +// Longest-first: `.agents/skills` must win over `.agents`. const SKILLS_ROOTS = ['.agents/skills', 'skills']; const HOOKS_ROOTS = ['hooks']; @@ -68,16 +67,10 @@ const HOOKS_ROOTS = ['hooks']; /** Source-of-truth command dir every skill/command surface converts from. */ const COMMANDS_SRC = 'commands/msd'; -/** Installer source file that emits the Cline/AGENTS.md instruction bodies as - * code literals (buildClineRulesBody / buildClineAgentsMdBody / - * buildClinePreToolUseHook). */ -const CLINE_BODY_SRC = 'src/runtime-hooks-surface.cts'; - /** * Installer source file that GENERATES the Windows-only `hooks/.cmd` shim * wrapping a Codex hook's `.js` script (#3426). Same physical file as - * CLINE_BODY_SRC — kept as its own named constant because the two constants - * attribute unrelated transform code that happens to live in one file: + * Transform code: * buildCodexHookWindowsShimIR / ensureCodexHooksJsonSessionStart / * ensureCodexHooksJsonEvent (verified via Memtrace: these are the ONLY writers * of a `.cmd` file anywhere in the installer — no other runtime's hook surface @@ -85,8 +78,7 @@ const CLINE_BODY_SRC = 'src/runtime-hooks-surface.cts'; */ const HOOKS_WINDOWS_SHIM_SRC = 'src/runtime-hooks-surface.cts'; -/** Installer source file that emits the Hermes skill-category DESCRIPTION.md - * (writeHermesCategoryDescription) as a code literal. */ +/** Installer source file that emits shared code-literal artifacts. */ const INSTALLER_SRC = 'bin/install.js'; /** Module owning the #2544 `{"type":"commonjs"}` marker literal and the @@ -97,9 +89,6 @@ const COMMONJS_MARKER_SRC = 'src/commonjs-marker.cts'; * beside it (_installNativePluginIfDeclared). */ const INSTALL_ENGINE_SRC = 'src/install-engine.cts'; -/** Source file holding the Kimi root-agent literal (runtime-artifact-layout.cts:303). */ -const KIMI_ROOT_AGENT_SRC = 'src/runtime-artifact-layout.cts'; - /** * Transform sources for the per-runtime agent-content pipeline (#2757). * @@ -187,7 +176,7 @@ const ZCODE_BODY_TRANSFORM_SRCS = [ 'bin/install.js', ]; -// #4482: every non-Copilot runtime filters audience-specific notes through the +// #4482: every runtime filters audience-specific notes through the // conversion module and the published installer copy path. const RUNTIME_NOTE_FILTER_TRANSFORM_SRCS = [ 'src/runtime-artifact-conversion.cts', @@ -282,7 +271,7 @@ const PROVENANCE_RULES = [ { id: 'msd-core-verbatim', // Markdown payloads pass through copyWithPathReplacement's audience - // filter for non-Copilot runtimes. Non-Markdown payloads remain byte-for- + // filter for every runtime. Non-Markdown payloads remain byte-for- // byte copies, but one rule has one kind; the match-specific transform // list below keeps the causal attribution precise. kind: 'derived', @@ -343,10 +332,7 @@ const PROVENANCE_RULES = [ // `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-.md`, so excluding the bare `msd.md` is precise. - // `.agent.md` is likewise excluded — Copilot emits a RENAMED copy - // (`.agent.md`) whose source is `agents/.md`; matching it here - // resolved to a file that does not exist. Same false-attribution class. - pattern: /^(?!msd\.md$)(?!.*\.agent\.md$)[^/]+\.md$/, + pattern: /^(?!msd\.md$)[^/]+\.md$/, sources: (m) => [`agents/${m[0]}`], transforms: AGENT_TRANSFORM_SRCS, }, @@ -381,11 +367,8 @@ const PROVENANCE_RULES = [ roots: HOOKS_ROOTS, // Emitted from hooks/dist/, which scripts/build-hooks.js builds from hooks/. // Attribute to the REPO source a PR actually edits, not the build artifact. - // Excludes Copilot's hook-registration JSON (next rule) — that is a code - // literal, not a built script, and attributing it here resolved to a - // nonexistent `hooks/msd-session.json`. `package.json` is excluded for the - // same reason (the #2544 `commonjs-marker` rule below): there is no - // `hooks/package.json` in the repo to attribute to. + // `package.json` is excluded (the #2544 `commonjs-marker` rule below): + // there is no `hooks/package.json` in the repo to attribute to. // // `.cmd` shims are a SEPARATE, Windows-only emission path folded into this // SAME rule rather than a dedicated one (see the `transforms` doc above for @@ -400,11 +383,11 @@ const PROVENANCE_RULES = [ // same source file. The wrapped `.js` file's NAME flows into the `.cmd` // bytes; its CONTENT never does — see the `sources` comment below for why // that rules out attributing to `hooks/.js`. - pattern: /^(?!msd-session\.json$|package\.json$).+$/, + pattern: /^(?!package\.json$).+$/, // `package.json` (the CommonJS marker) is excluded here and owned by the // dedicated `commonjs-marker` rule below. #2717 attributed it inside THIS // rule, routing it to HOOKS_WINDOWS_SHIM_SRC because at that point - // src/runtime-hooks-surface.cts was its only emitter (cursor/windsurf, plus + // src/runtime-hooks-surface.cts was its only emitter (cursor, plus // the codex copy block calling the same exported helper). #2544 adds a // second emitter — src/commonjs-marker.cts, via installSharedHooksBundle and // _installNativePluginIfDeclared — and two roots this rule does not cover @@ -418,8 +401,7 @@ const PROVENANCE_RULES = [ // source file), but its CONTENT never does, so attributing `sources` to // `hooks/.js` would be a false byte-provenance claim. Point `sources` // at the same file as `transforms`, matching the `code-derived` convention - // used elsewhere in this table (copilot-hook-registration, cline-rules- - // code-derived, hermes-category-description). The redundancy between + // used elsewhere in this table. The redundancy between // `sources` and `transforms` here is harmless — the mis-attribution was not. // No `package.json` arm here: the pattern above excludes it, so the branch // #2717 added for it is unreachable from this rule. @@ -434,8 +416,8 @@ const PROVENANCE_RULES = [ roots: [...HOOKS_ROOTS, 'plugins', 'extensions'], // #2544: a `{"type":"commonjs"}` module-type marker, written as a code // literal so Node's ancestor walk resolves MSD's staged `.js` files as - // CommonJS under an ambient `"type": "module"`. Like the Copilot - // registration JSON above it is emitted, never built — there is no + // CommonJS under an ambient `"type": "module"`. It is emitted, + // never built — there is no // `hooks/package.json` or `plugins/package.json` in the repo, so the // built-script rule would attribute it to a path that does not exist. // Sources are scoped PER ROOT, not declared as one flat union. Each root has @@ -465,13 +447,8 @@ const PROVENANCE_RULES = [ if (root === 'plugins' || root === 'extensions') { return [COMMONJS_MARKER_SRC, INSTALL_ENGINE_SRC]; } - // 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]; - } // 'hooks' — written by the shared bundle for most runtimes and by the - // #2717 dedicated paths for cursor/windsurf/codex. + // #2717 dedicated paths for cursor/codex. return [COMMONJS_MARKER_SRC, INSTALLER_SRC, HOOKS_WINDOWS_SHIM_SRC]; }, }, @@ -533,8 +510,7 @@ const PROVENANCE_RULES = [ kind: 'descriptor', roots: ['plugins', 'extensions'], pattern: /^[^/]+\.(js|cjs|mjs)$/, - // Source is per-runtime (opencode -> .opencode/…, kilo -> .kilo/…, - // pi -> pi/msd.cjs), so attribution is a function of (rel, runtime). + // Source is per-runtime (opencode -> .opencode/…), so attribution is a function of (rel, runtime). sources: (m, ctx) => { const np = nativePluginDescriptor(ctx.runtime); if (!np || !np.source) { @@ -554,8 +530,7 @@ const PROVENANCE_RULES = [ // ^AGENTS\.md$) and `synthesized-msd-defaults` (^\.msd/defaults\.json$): // both paths only appeared in a manifest while the harness collapsed // configDir onto the sandbox HOME, walking HOME-level siblings - // (cline's ~/.agents/AGENTS.md, every non-Claude runtime's - // ~/.msd/defaults.json). With the harness installing into each runtime's + // (every non-Claude runtime's ~/.msd/defaults.json). With the harness installing into each runtime's // 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 @@ -863,13 +838,11 @@ module.exports = { EXPECTED_MANIFEST_COUNT, PROVENANCE_RULES, SKILLS_ROOTS, - KIMI_ROOT_AGENT_SRC, AGENT_TRANSFORM_SRCS, RUNTIME_NOTE_FILTER_TRANSFORM_SRCS, SOURCE_PREFIX_SUFFIX, HOOKS_ROOTS, COMMANDS_SRC, - CLINE_BODY_SRC, HOOKS_WINDOWS_SHIM_SRC, INSTALLER_SRC, stripSkillPrefix, diff --git a/tests/helpers/install-shared.cjs b/tests/helpers/install-shared.cjs index 5b7a6c0e8..b53ae6dc0 100644 --- a/tests/helpers/install-shared.cjs +++ b/tests/helpers/install-shared.cjs @@ -163,7 +163,7 @@ const VOLATILE_FILES = new Set([ // varies by platform and cannot be normalized to a single sentinel reliably. // Their content is asserted directly by the dedicated hook tests // (install-minimal-hooks, sh-hook-paths, codex-config, etc.). Matched by basename. -// settings.json = Claude/Antigravity/Augment/etc. hook surface; hooks.json = +// settings.json = Claude/Antigravity hook surface; hooks.json = // Codex/Cursor hook surface — both embed the platform-varying node-runner command. // settings.local.json = Claude LOCAL hook surface (#338): same platform-varying // node-runner command as settings.json, so excluded for the same reason (#2086). @@ -235,8 +235,7 @@ const SEMVER_ISH_RE = /^\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$/; * - `"version": ""` — JSON manifests (plugin/extension/capability-style) * embedding the package version as a string field. * - `version: ""` / `version: ` — YAML frontmatter version fields (e.g. - * skill frontmatter's `yamlQuote(pkg.version)`, Hermes' category - * `DESCRIPTION.md`). + * skill frontmatter's `yamlQuote(pkg.version)`). * - `@golem15/msd-core@` — pinned package-spec references. * - a file whose ENTIRE trimmed content IS the version (`msd-core/VERSION`). * Each pattern only matches when the version in the content EQUALS the supplied @@ -605,7 +604,7 @@ function runMinimalInstall({ runtime, scope, extraArgs = [], installScript = INS // includes rewritten on live installs, zero manifest/fixture diffs). The // explicit flag stays: hermeticity-by-override is immune to ambient // redirect envs (CI runners export XDG_CONFIG_HOME, which redefines the - // opencode/kilo XDG descriptors' resolution when no explicit dir wins). + // opencode XDG descriptor's resolution when no explicit dir wins). const globalMeta = RUNTIME_META[runtime]; if (!globalMeta || !globalMeta.globalSuffix) { // #3023 lesson: a silent `path.join(root, undefined)` here throws a @@ -623,21 +622,18 @@ 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 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 - // adding one more entry, which left the divergence itself in place; the - // table is now single-source so a new runtime cannot reintroduce it. - // `cline` keeps its ternary: its local artifacts land at the project root - // itself, which is a genuine exception rather than a directory name. + // That duplicate map was missing several runtimes (e.g. 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; the table is now single-source so a new runtime cannot + // reintroduce it. const localMeta = RUNTIME_META[runtime]; - if (runtime !== 'cline' && (!localMeta || !localMeta.localDir)) { + if (!localMeta || !localMeta.localDir) { throw new Error( `runMinimalInstall: no RUNTIME_META.localDir for runtime "${runtime}" — refusing to guess a local config dir (#3031)`, ); } - configDir = runtime === 'cline' ? root : path.join(root, localMeta.localDir); + configDir = path.join(root, localMeta.localDir); } args.push(...extraArgs); const result = runNode(args, { @@ -680,7 +676,7 @@ function manifestSkillSet(manifest) { const seg = key.split('/')[1].replace(/^msd-/, '').replace(/\.md$/, ''); out.add(seg); } else if (key.startsWith('command/')) { - // OpenCode/Kilo: command/msd-.md + // OpenCode: command/msd-.md const file = key.split('/')[1]; out.add(file.replace(/^msd-/, '').replace(/\.md$/, '')); } else if (key.startsWith('commands/msd/')) { diff --git a/tests/helpers/nested-layout.cjs b/tests/helpers/nested-layout.cjs index c96748ccf..b81c49c06 100644 --- a/tests/helpers/nested-layout.cjs +++ b/tests/helpers/nested-layout.cjs @@ -47,8 +47,8 @@ for (const routerStem of ROUTER_STEMS) { /** * Nested SKILL.md path for a concrete skill stem: * //skills//SKILL.md - * prefix is '' for runtimes that nest under a skills/msd parent dir (hermes), - * or 'msd-' for flat-prefixed runtimes (claude, cline, qwen, …). + * prefix is '' for runtimes that nest under a bare parent dir, or 'msd-' for + * flat-prefixed runtimes (claude, …). */ function nestedSkillPath(skillsRoot, prefix, stem) { const router = CHILD_ROUTER[stem]; diff --git a/tests/host-integration.test.cjs b/tests/host-integration.test.cjs index ab9ce201b..1c8d4b480 100644 --- a/tests/host-integration.test.cjs +++ b/tests/host-integration.test.cjs @@ -2875,12 +2875,12 @@ describe('#2627 dispatch-isolation CLI route', () => { test('raw output is the bare negotiated value for each isolation model', () => { assert.equal(query('claude').trim(), 'harness-worktree'); assert.equal(query('codex').trim(), 'orchestrator-worktree'); - assert.equal(query('pi').trim(), 'none'); + assert.equal(query('zcode').trim(), 'none'); }); test('an undocumented isolation declaration degrades to none (fail-closed)', () => { - // cline declares isolation:"undocumented" — the corpus-wide sentinel. - assert.equal(query('cline').trim(), 'none'); + // antigravity declares isolation:"undocumented" — the corpus-wide sentinel. + assert.equal(query('antigravity').trim(), 'none'); }); test('an unknown runtime degrades to none rather than erroring', () => { diff --git a/tests/install-minimal-hooks.test.cjs b/tests/install-minimal-hooks.test.cjs index 56a203ebf..a8856b86d 100644 --- a/tests/install-minimal-hooks.test.cjs +++ b/tests/install-minimal-hooks.test.cjs @@ -1,4 +1,4 @@ -// docs-guard-exempt: docs/... substrings are external URL citations (qwenlm/code.claude.com) in comments, not repo paths. +// docs-guard-exempt: docs/... substrings are external URL citations (code.claude.com) in comments, not repo paths. /** * Installer Module — Sections 9–11 + 13. * @@ -651,18 +651,12 @@ describe('#1755: .sh hooks are copied and executable after install', () => { // ─── #1821/#2305: hooks staged iff a surface consumes them ───────────────────── // // #1821 reported dead hook scripts staged for runtimes with hooksSurface:'none'. -// OpenCode, pi — and, corrected by #2305, Kilo — ALSO declare -// hooksSurface:'none', but each has a native plugin adapter that spawns the -// staged hooks/*.js scripts as subprocesses (OpenCode's #1914 -// plugins/msd-core.js via OpenCode's event bus; pi's #2102 Stage 2 pi/msd.cjs -// → extensions/msd.js via pi.on(...) bridges; Kilo's plugins/msd-core.js, -// byte-identical to OpenCode's) — so for all three, the hooks are LIVE and +// OpenCode ALSO declares hooksSurface:'none', but has a native plugin adapter +// that spawns the staged hooks/*.js scripts as subprocesses (OpenCode's #1914 +// plugins/msd-core.js via OpenCode's event bus) — so its hooks are LIVE and // must keep being copied. ZCode has no plugin surface at all, so its staged -// hooks are genuinely dead: that is the case #1821's fix removes. (#1821 -// originally excluded Kilo too, on the false premise that it had no plugin -// surface — #2305 reversed that: the skip flag silently no-opped every guard -// hook Kilo's plugin spawns.) These tests assert the split: ZCode gets no -// hooks; Kilo/OpenCode/pi (and Claude) do. +// hooks are genuinely dead: that is the case #1821's fix removes. These tests +// assert the split: ZCode gets no hooks; OpenCode (and Claude) do. describe('#1821/#2305: ZCode receives no dead hook files; OpenCode/Claude keep their hooks', () => { function msdHookFilesUnder(configDir, hooksDirName) { @@ -686,7 +680,7 @@ describe('#1821/#2305: ZCode receives no dead hook files; OpenCode/Claude keep t // Collect results while targetDir still exists — cleanup() below removes it. const pluginRelPath = opts.pluginRelPath || path.join('plugins', 'msd-core.js'); // #3023: the shared hooks bundle's staged directory name is per-runtime - // (hostBehaviors.sharedHooksDirName; pi renames it to `msd-hooks/`) — + // (hostBehaviors.sharedHooksDirName) — // resolve it the same way the installer does rather than hardcoding // 'hooks', or every non-default runtime would look hookless. const hooksDirName = resolveSharedHooksDirName(runtime); @@ -1183,7 +1177,7 @@ process.env.MSD_TEST_MODE = '1'; * extendedHookEvents descriptor field, not hardcoded runtime-name checks. * * Before this change: - * - SubagentStop/Stop/PreCompact were wired only when (isQwen || runtime==='claude') + * - SubagentStop/Stop/PreCompact were wired only when (runtime==='claude') * - FileChanged was wired only when (runtime === 'claude') * - BeforeAgent/AfterAgent/BeforeModel were wired only when (isGemini) * @@ -1348,9 +1342,9 @@ describe('enh-1076 phase 5f-3: claude extendedHookEvents → SubagentStop/Stop/P }); }); -// ─── Suite 2: qwen shape (SubagentStop+Stop+PreCompact, no FileChanged) ─────── +// ─── Suite 2: SubagentStop+Stop+PreCompact shape (no FileChanged) ───────────── -describe('enh-1076 phase 5f-3: qwen extendedHookEvents → SubagentStop/Stop/PreCompact only', () => { +describe('enh-1076 phase 5f-3: extendedHookEvents → SubagentStop/Stop/PreCompact only', () => { let targetDir; let settings; @@ -1358,7 +1352,7 @@ describe('enh-1076 phase 5f-3: qwen extendedHookEvents → SubagentStop/Stop/Pre targetDir = createStubTargetDir(); settings = { hooks: {} }; const opts = buildOpts(targetDir, { - runtime: 'qwen', + runtime: 'hypothetical-stop-shape', extendedHookEvents: ['SubagentStop', 'Stop', 'PreCompact'], }); applySettingsJsonHooks(settings, opts); @@ -1380,7 +1374,7 @@ describe('enh-1076 phase 5f-3: qwen extendedHookEvents → SubagentStop/Stop/Pre assert.strictEqual( hasHooksFor(settings, 'FileChanged'), false, - `FileChanged must NOT be wired for qwen shape; hooks keys: ${JSON.stringify(Object.keys(settings.hooks || {}))}` + `FileChanged must NOT be wired for the SubagentStop/Stop/PreCompact shape; hooks keys: ${JSON.stringify(Object.keys(settings.hooks || {}))}` ); }); @@ -1491,7 +1485,7 @@ describe('enh-1076 phase 5f-3: arbitrary runtime with SubagentStop in descriptor targetDir = createStubTargetDir(); settings = { hooks: {} }; const opts = buildOpts(targetDir, { - runtime: 'hypothetical', // NOT 'claude' or 'qwen' — would have been skipped before + runtime: 'hypothetical', // NOT 'claude' — would have been skipped before extendedHookEvents: ['SubagentStop'], }); applySettingsJsonHooks(settings, opts); @@ -1753,7 +1747,7 @@ describe('enh-1077 phase 5f-2: gemini hookEvents dialect → AfterTool/BeforeToo // ─── Suite 2: Claude-dialect runtimes use PostToolUse/PreToolUse ────────────── // -// Registry runtimes with hookEvents='claude': claude, augment +// Registry runtimes with hookEvents='claude': claude describe('enh-1077 phase 5f-2: claude hookEvents dialect → PostToolUse/PreToolUse', () => { // ── claude ── @@ -1837,7 +1831,7 @@ describe('enh-1077 phase 5f-2: registry-parity — hookEvents descriptor drives } // Runtimes that have settings.json surfaces and a hookEvents descriptor - const SETTINGS_JSON_RUNTIMES = ['claude', 'antigravity', 'augment', 'qwen', 'hermes', 'codebuddy']; + const SETTINGS_JSON_RUNTIMES = ['claude', 'antigravity']; const failures = []; @@ -1908,11 +1902,10 @@ describe('enh-1077 phase 5f-2: registry-parity — hookEvents descriptor drives process.env.MSD_TEST_MODE = '1'; /** - * Enhancement #788: Expand Qwen Code hook-event coverage. + * Enhancement #788: Expand hook-event coverage. * - * Qwen Code supports 15 hook events; msd previously registered only - * SessionStart and PostToolUse. This suite asserts that a Qwen install - * registers the 3 new high-value events: + * msd previously registered only SessionStart and PostToolUse. This suite + * asserts that an install registers the 3 new high-value events: * - SubagentStop — subagent lifecycle finalisation (context tracking) * - Stop — model stop / final-response hook (context tracking) * - PreCompact — pre-compaction awareness (context tracking) @@ -1923,11 +1916,6 @@ process.env.MSD_TEST_MODE = '1'; * Note: UserPromptSubmit is NOT wired — msd-prompt-guard exits unless * tool_name is Write|Edit (PreToolUse shape), so it would be a no-op for * the UserPromptSubmit payload. Deferred to a follow-on issue. - * - * Also asserts the inverse: Claude Code installs do NOT gain these events - * (strict isQwen scope guard). - * - * Source: https://qwenlm.github.io/qwen-code-docs/en/users/features/hooks/ */ const { test, describe, beforeEach, afterEach } = require('node:test'); @@ -1977,8 +1965,7 @@ function stubHooksIntoTarget(targetDir) { } // ─── Suite 2: Claude install DOES get the context events (since #770) ─────── -// Note: Prior to #770, these were Qwen-only events. #770 extended them to -// Claude Code. This suite is updated to match the new expected behavior. +// Note: #770 extended these events to Claude Code. describe('enh-788 (updated by #770): Claude install registers context lifecycle events', () => { let tmpDir; @@ -2095,10 +2082,10 @@ process.env.MSD_TEST_MODE = '1'; * PreCompact / FileChanged). * * Claude Code now supports the same SubagentStop, Stop, and PreCompact events - * that were wired for Qwen Code in #788. This suite asserts: + * that were wired in #788. This suite asserts: * * 1. Claude Code installs register SubagentStop, Stop, and PreCompact, each - * wired to msd-context-monitor.js (same as Qwen). + * wired to msd-context-monitor.js. * 2. Claude Code installs register a FileChanged hook for .planning/config.json * wired to msd-config-reload.js (new hook; hot-reloads msd config). * 3. All four registrations are idempotent (reinstall does not duplicate). @@ -2683,9 +2670,7 @@ describe('#4087 regression: Codex install stages the hook helpers its hooks requ // statusline hook, which Codex never installs, so every registered Codex // event was a guaranteed silent no-op. The #4087 bug class this describe // block guards (a staged hook requiring an unshipped hooks/lib/ helper) - // remains covered live via Windsurf's own guards — see the - // "#4087 review: Windsurf install..." describe block below, unaffected - // by this change. + // remains covered by the grammar-level rows below. const hooksDir = installCodex(tmpDir); const libDir = path.join(hooksDir, 'lib'); @@ -2724,8 +2709,8 @@ describe('#4087 regression: Codex install stages the hook helpers its hooks requ // dependency closure is correctly empty. This row still proves the // GRAMMAR holds (whatever IS required must be staged) — it is just that // "whatever is required" is now the empty set for Codex specifically. - // The non-trivial case (closure size > 0) is covered live by the - // "#4087 review: Windsurf install..." describe block below. + // The non-trivial case (closure size > 0) is covered by the grammar-level + // rows below. assert.strictEqual(required.size, 0, 'no staged Codex hook should require a ./lib/ helper post-#2586 — if this becomes non-zero, ' + 'extend this row (do not just raise the bar back to ">0") so the new dependency stays proven'); @@ -2756,8 +2741,8 @@ describe('#4087 regression: Codex install stages the hook helpers its hooks requ // #2586: Codex's closure is now legitimately empty (msd-context-monitor.js, // the only staged Codex hook that ever required a helper, is no longer // staged) — the deepStrictEqual below is still the real assertion and - // holds for the empty case too; the non-empty case remains covered live - // by the "#4087 review: Windsurf install..." describe block below. + // holds for the empty case too; the non-empty case remains covered by the + // grammar-level rows below. assert.strictEqual(stagedLibs.length, 0, 'no helpers should be staged for Codex post-#2586'); // Derive the closure independently of the installer. diff --git a/tests/install-runtime-artifacts.test.cjs b/tests/install-runtime-artifacts.test.cjs index 875a18788..09dc29589 100644 --- a/tests/install-runtime-artifacts.test.cjs +++ b/tests/install-runtime-artifacts.test.cjs @@ -1,4 +1,4 @@ -// docs-guard-exempt: codebuddy.ai/docs/... is an external URL and docs/adr/58-...md is a comment citation; neither is read. +// docs-guard-exempt: docs/adr/58-...md is a comment citation; it is not read. // allow-test-rule: source-text-is-the-product // Reads .md/.json/.yml product files whose deployed text IS what the // runtime loads — testing text content tests the deployed contract. @@ -640,7 +640,7 @@ describe('installOpencodeFamilySkills — emits skills//SKILL.md (#784)', !body.includes(`~/${defaultBase}/`), `${skillName}: must not leak hardcoded ~/${defaultBase}/ — should use install target`, ); - // Regression guard for the prefix-overlap double-rewrite (e.g. kilo-alt-alt). + // Regression guard for the prefix-overlap double-rewrite (e.g. opencode-alt-alt). assert.ok( !new RegExp(`${escapeRegex(defaultBase)}-[^/\\s]*-`).test(body), `${skillName}: must not contain a doubled config-dir suffix`, @@ -668,7 +668,7 @@ describe('installOpencodeFamilySkills — emits skills//SKILL.md (#784)', } }); -// ─── #2362: OpenCode/Kilo combined-family INSTALL path drops the capability +// ─── #2362: OpenCode combined-family INSTALL path drops the capability // registry — an installed+registered+surfaced+active third-party capability // skill never materializes ─────────────────────────────────────────────── // @@ -1353,7 +1353,7 @@ describe('convertClaudeToAntigravityContent bare path replacement (#2418)', () = // ──────────────────────────────────────────────────────────────────────── // Folded from tests/enh-789-codebuddy-commands.test.cjs — consolidation epic #1969 (B1 #1970) -// (codebuddy runtime retired; the Cursor #2644 guard that lived in the same file is kept) +// (the Cursor #2644 guard that lived in the same file is kept) // ──────────────────────────────────────────────────────────────────────── { const { describe: __foldDescribe } = require('node:test'); @@ -3186,9 +3186,7 @@ describe('#443 Source purity: agents/msd-planner.md has no effort: key', () => { // relocation of pure rewrite-engine helpers out of hand-authored bin/install.js. // - getDirName -> msd-core/bin/lib/runtime-name-policy.cjs // - processAttribution -> msd-core/bin/lib/runtime-artifact-conversion.cjs -// getCommitAttribution stays in install.js (impure install-time config I/O); the -// convertClaudeToAugmentMarkdown duplicate dedup is deferred to Phase 2's cleanup -// (entangled converter cluster; not required to unblock Phase 2). +// getCommitAttribution stays in install.js (impure install-time config I/O). // These tests exercise the REAL relocated functions at their new home (the // generated .cjs). #2876 (epic #2866 Phase 7) retired install.js's re-export // of both names — a repo-wide audit found zero production consumers of the @@ -3390,10 +3388,10 @@ describe('_computePathPrefix', () => { isGlobal: false, isOpencode: false, isWindowsHost: true, - resolvedTarget: 'C:\\Users\\runner\\AppData\\Local\\Temp\\msd-1615-windsurf', + resolvedTarget: 'C:\\Users\\runner\\AppData\\Local\\Temp\\msd-1615-fixture', homeDir: 'C:\\Users\\runner', }); - assert.strictEqual(prefix, 'C:/Users/runner/AppData/Local/Temp/msd-1615-windsurf/'); + assert.strictEqual(prefix, 'C:/Users/runner/AppData/Local/Temp/msd-1615-fixture/'); assert.ok(!prefix.includes('\\'), `prefix must not contain backslashes: ${prefix}`); }); }); @@ -5281,7 +5279,7 @@ describe('#2873 C1-C6 — install-time shadow report (spawned installer wiring)' // // readMsdRuntimeProfileResolver probes for the project's // .planning/config.json by walking up from the install's targetDir. A GLOBAL -// OpenCode/Kilo install (targetDir ~/.config/) can never reach the +// OpenCode install (targetDir ~/.config/) can never reach the // consuming project, and ~/.msd/defaults.json never carries model_profile // (writeNonClaudeDefaults writes only resolve_model_ids + runtime), so the // resolver silently fell back to 'balanced' and baked e.g. @@ -5295,7 +5293,6 @@ describe('#2873 C1-C6 — install-time shadow report (spawned installer wiring)' // - a found config keeps the documented 'balanced' default (local installs). // - a profile (or model_overrides pin) declared in ~/.msd/defaults.json is // machine-level and still bakes on a global install. -// - Kilo mirrors OpenCode (static-frontmatter twin, #2093). { const { describe: __d3543, test: __t3543, beforeEach: __be3543, afterEach: __ae3543 } = require('node:test'); const { install: __install3543 } = require('../bin/install.js'); diff --git a/tests/install-scope.test.cjs b/tests/install-scope.test.cjs index 487761c34..784b4ccda 100644 --- a/tests/install-scope.test.cjs +++ b/tests/install-scope.test.cjs @@ -175,7 +175,7 @@ describe('resolveScope', () => { }); // Row 14 - test('preserves opencode/kilo config-file precedence', () => { + test('preserves opencode config-file precedence', () => { const filePath = '/home/x/custom/opencode-config.json'; const result = resolveScope(fixture({ id: 'global', diff --git a/tests/install-write-confinement.test.cjs b/tests/install-write-confinement.test.cjs index 588fe106c..08e924a6f 100644 --- a/tests/install-write-confinement.test.cjs +++ b/tests/install-write-confinement.test.cjs @@ -2872,7 +2872,7 @@ describe('#2875: user-artifact-staging confinement (E1-E5)', () => { ); // Run from a cwd that resolves the relative destDir straight back to - // configDir (mirrors the real cline-local call shape, where targetDir + // configDir (mirrors a local call shape where targetDir // === process.cwd()) — the exact case that must NOT be treated as // "correctly resolved" just because the coincidence lines up. const previousCwd = process.cwd(); diff --git a/tests/install.test.cjs b/tests/install.test.cjs index 86c67d0c3..1d4a2dcdd 100644 --- a/tests/install.test.cjs +++ b/tests/install.test.cjs @@ -103,7 +103,7 @@ describe('getGlobalConfigDir — all runtimes default paths', () => { } }); - for (const runtime of allRuntimes.filter(runtime => runtime !== 'kimi')) { + for (const runtime of allRuntimes) { test(`getGlobalConfigDir('${runtime}') returns expected home-relative path`, () => { const expected = path.join(os.homedir(), RUNTIME_META[runtime].globalSuffix); assert.strictEqual(getGlobalConfigDir(runtime), expected); @@ -665,12 +665,6 @@ describe('antigravity local install writes to .agents/ canonical dir (#791)', () } }); }); -// ─── Section 6: Windsurf / devin-desktop alias (#792) ─────────────────────── - -// ─── Section N: Windsurf workflow slash-command install (#1615) ───────────── -// Reads deployed workflow .md files whose text IS the product surface the -// Windsurf runtime loads at startup (path references, command names). - // ─── Section N+1: #767 — disallowedTools injection for read-only agents ────── // // Verifies (installer-behavioral test — drives install() to a temp dir): @@ -4915,7 +4909,7 @@ describe('bug #505: dead SDK verification subsystem removed from bin/install.js' * Fix: the `.js` branch of the hook-copy loop now applies * `content.replace(/msd:/gi, 'msd-')` when * `shouldNormalizeHyphenNamespaceInAgentBody(runtime)` is true (covers - * claude, qwen, hermes). + * claude). * * Test plan: * 1. Claude install to tmp prefix — installed .js hook files must contain @@ -5432,7 +5426,7 @@ test('install-effort-resolver.cjs tier-defaults object has exactly the same keys * the directory name as a namespace, so `commands/msd/update.md` became `/msd:update`. * * Fix: write each command as `msd-.md` directly in `commands/` (flat layout). - * This is the same approach used for OpenCode/Kilo (see `copyFlattenedCommands`). + * This is the same approach used for OpenCode (see `copyFlattenedCommands`). */ 'use strict'; diff --git a/tests/installed-surface-resolver.test.cjs b/tests/installed-surface-resolver.test.cjs index 57ee2a575..58ff76c5d 100644 --- a/tests/installed-surface-resolver.test.cjs +++ b/tests/installed-surface-resolver.test.cjs @@ -22,7 +22,7 @@ * (`resolveRuntimeArtifactLayoutFromRegistry`) and `resolveTriggerSurface` — * never `resolveScope`, which always consults the REAL * `capability-registry.cjs`. So every test below uses a REAL registered - * runtime id (`claude`, `cursor`, `cline`, `windsurf`) for scope resolution, + * runtime id (`claude`, `cursor`, `codex`, `zcode`) for scope resolution, * and reaches for `opts.registry` only when a row needs a layout shape the * real registry does not currently ship (namespaced-by-dir commands, B2/B8). * C7/C8 (the all-runtimes sweep) are asserted against the real registry only diff --git a/tests/installer-migration-install.integration.test.cjs b/tests/installer-migration-install.integration.test.cjs index 73238374f..88364ccd8 100644 --- a/tests/installer-migration-install.integration.test.cjs +++ b/tests/installer-migration-install.integration.test.cjs @@ -30,49 +30,16 @@ const SUPPORTED_RUNTIMES = installModule.allRuntimes; const RUNTIME_INSTALL_CONTRACTS = { claude: { surface: 'flat-skills', settings: true, hooksPackageJson: true }, antigravity: { surface: 'flat-skills', settings: true, hooksPackageJson: true }, - augment: { surface: 'flat-skills', settings: true, hooksPackageJson: true }, - cline: { surface: 'clinerules', settings: false, hooksPackageJson: false }, - codebuddy: { surface: 'flat-skills', settings: true, hooksPackageJson: true }, - // codex/cursor/windsurf: #2717 stages their .js hooks via dedicated paths + // codex/cursor: #2717 stages their .js hooks via dedicated paths // (skipSharedHooksInstall / the !isCodex gate keep them out of the shared // bundle) and writes the marker beside those scripts, so hooks/package.json - // is expected for all three. Measured on this tree: codex stages 3 .js hooks, - // cursor 6, windsurf 2 — each with the marker. + // is expected for both. Measured on this tree: codex stages 3 .js hooks, + // cursor 6 — each with the marker. codex: { surface: 'flat-skills', settings: false, hooksPackageJson: true, codexConfig: true }, - copilot: { surface: 'flat-skills', settings: false, hooksPackageJson: false, copilotInstructions: true }, cursor: { surface: 'flat-skills', settings: false, hooksPackageJson: true }, gemini: { surface: 'commands-msd', settings: true, hooksPackageJson: true }, - hermes: { surface: 'hermes-skills', settings: true, hooksPackageJson: true }, - kimi: { surface: 'kimi-skills-agents', settings: false, hooksPackageJson: false }, - // #2454: Kimi Code (Node CLI) has NO custom named subagents (per official - // docs), so its install surface is skills-only (flat-skills), NOT - // kimi-skills-agents. The kimi-agents YAML layout is Python kimi-cli only. - 'kimi-code': { surface: 'flat-skills', settings: false, hooksPackageJson: false }, - // #2305: Kilo's native plugin spawns the staged guard hooks, so it receives - // the shared hooks bundle + the CommonJS package.json marker, like OpenCode. - // (#1821 excluded Kilo on the false premise that it had no plugin surface.) - kilo: { surface: 'flat-command', settings: false, hooksPackageJson: true }, - // #2329: OpenCode discovers commands from the PLURAL `commands/` dir — the - // singular `command/` (still correct for Kilo) made all /msd-* commands - // invisible to OpenCode. commandDirName overrides the flat-command default. + // #2329: OpenCode discovers commands from the PLURAL `commands/` dir. opencode: { surface: 'flat-command', settings: true, hooksPackageJson: true, commandDirName: 'commands' }, - // #2102 Stage 1/2: pi is a PLUGIN-ONLY install (hostBehaviors.pluginOnlyInstall) - // for commands/agents/skills — NO commands/, agents/, or skills/ dir. pi's - // /msd command is registered programmatically by the native extension - // (extensions/msd.cjs) and dispatches via a bounded subprocess to - // msd-tools.cjs; it has no host-read markdown surface. Stage 2 (adversarial- - // review fix): pi's native extension DOES spawn the shared hooks/*.js bundle - // as bounded subprocesses (session_start/before_agent_start/session_before_ - // compact bridges) and its /msd tokenizer requires hooks/lib/git-cmd.js, so - // `hostBehaviors.skipSharedHooksInstall` was removed — pi now receives - // hooks/ + hooks/lib/ + the {"type":"commonjs"} package.json marker, exactly - // like OpenCode and (since #2305) Kilo — architecturally identical: - // hooksSurface:'none' + a native plugin that spawns the staged hooks — NOT - // like ZCode (no plugin surface, where the same hooks are dead weight). - pi: { surface: 'plugin-only', settings: false, hooksPackageJson: true }, - qwen: { surface: 'flat-skills', settings: true, hooksPackageJson: true }, - trae: { surface: 'flat-skills', settings: false, hooksPackageJson: false }, - windsurf: { surface: 'global-artifacts-noop', settings: false, hooksPackageJson: true }, // #1821: ZCode (hooksSurface:none, no plugin surface) no longer receives the // dead hook scripts or the CommonJS package.json marker. zcode: { surface: 'flat-skills', settings: false, hooksPackageJson: false }, @@ -237,40 +204,31 @@ function assertFreshInstallContract(runtime, targetDir) { const contract = RUNTIME_INSTALL_CONTRACTS[runtime]; assert.ok(contract, `missing runtime install contract for ${runtime}`); // #3023: the shared hooks bundle's staged directory name is per-runtime - // (hostBehaviors.sharedHooksDirName; pi renames it to `msd-hooks/` to avoid - // pi's own host-reserved `hooks/`) — resolve it the same way the installer + // (hostBehaviors.sharedHooksDirName) — resolve it the same way the installer // does rather than hardcoding 'hooks', which is only the default. const hooksDirName = installModule.resolveSharedHooksDirName(runtime); - if (contract.workflowPayload !== false) { - assert.equal( - fs.readFileSync(path.join(targetDir, 'msd-core', 'VERSION'), 'utf8'), - pkg.version, - `${runtime} should install the package VERSION` - ); - assert.ok( - fs.existsSync(path.join(targetDir, 'msd-core', 'bin', 'msd-tools.cjs')), - `${runtime} should install the MSD tool payload` - ); - assert.ok( - fs.existsSync(path.join(targetDir, 'msd-file-manifest.json')), - `${runtime} should write the install manifest` - ); + assert.equal( + fs.readFileSync(path.join(targetDir, 'msd-core', 'VERSION'), 'utf8'), + pkg.version, + `${runtime} should install the package VERSION` + ); + assert.ok( + fs.existsSync(path.join(targetDir, 'msd-core', 'bin', 'msd-tools.cjs')), + `${runtime} should install the MSD tool payload` + ); + assert.ok( + fs.existsSync(path.join(targetDir, 'msd-file-manifest.json')), + `${runtime} should write the install manifest` + ); - const manifest = JSON.parse(fs.readFileSync(path.join(targetDir, 'msd-file-manifest.json'), 'utf8')); - assert.equal(manifest.version, pkg.version, `${runtime} manifest should record the package version`); - assert.equal(manifest.mode, 'full', `${runtime} manifest should record a full install`); - assert.ok( - manifest.files['msd-core/VERSION'], - `${runtime} manifest should track the installed VERSION file` - ); - } else { - assert.equal( - fs.existsSync(path.join(targetDir, 'msd-core')), - false, - `${runtime} should not install the MSD workflow payload` - ); - } + const manifest = JSON.parse(fs.readFileSync(path.join(targetDir, 'msd-file-manifest.json'), 'utf8')); + assert.equal(manifest.version, pkg.version, `${runtime} manifest should record the package version`); + assert.equal(manifest.mode, 'full', `${runtime} manifest should record a full install`); + assert.ok( + manifest.files['msd-core/VERSION'], + `${runtime} manifest should track the installed VERSION file` + ); if (contract.surface === 'flat-skills') { if (runtime === 'codex') { @@ -295,131 +253,33 @@ function assertFreshInstallContract(runtime, targetDir) { // the same flat-skills surface as the other runtimes. assertHasMsdDirectory(targetDir, 'skills'); } - } else if (contract.surface === 'hermes-skills') { - // Hermes layout uses prefix: '' — skill dirs have bare stem names (no msd- prefix). - // Assert that the category dir contains at least one skill dir with SKILL.md. - const hermesMsdDir = path.join(targetDir, 'skills', 'msd'); - const hermesSkillCount = fs.existsSync(hermesMsdDir) - ? fs.readdirSync(hermesMsdDir, { withFileTypes: true }) - .filter(e => e.isDirectory() && fs.existsSync(path.join(hermesMsdDir, e.name, 'SKILL.md'))) - .length - : 0; - assert.ok(hermesSkillCount > 0, `skills/msd should contain generated MSD entries (got ${hermesSkillCount})`); - assert.ok( - fs.existsSync(path.join(targetDir, 'skills', 'msd', 'DESCRIPTION.md')), - 'Hermes should install the nested MSD category description' - ); } else if (contract.surface === 'flat-command') { - // #2329: dir name is per-runtime (opencode='commands', kilo stays 'command'). + // #2329: dir name is per-runtime (opencode='commands'). const commandDirName = contract.commandDirName || 'command'; assert.ok( listDirNames(targetDir, commandDirName).some((name) => name.startsWith('msd-') && name.endsWith('.md')), `${runtime} should install flattened command markdown files in ${commandDirName}/` ); - } else if (contract.surface === 'plugin-only') { - // #2102 Stage 1/2: pi — PLUGIN-ONLY install for commands/agents/skills - // (hostBehaviors.pluginOnlyInstall). pi's /msd command is registered - // programmatically by the native extension and dispatches via a bounded - // subprocess to msd-tools.cjs — pi has no host-read markdown surface, so - // NO commands/, agents/, or skills/ dir is written. The extension DOES - // spawn the shared hooks bundle as bounded subprocesses (Stage 2 - // adversarial-review fix — hooksSurface:'none' no longer implies - // skipSharedHooksInstall for pi, mirroring OpenCode), so the bundle + the - // git-cmd.js tokenizer helper ARE part of the artifact surface now. #3023: - // that bundle is staged under `msd-hooks/` for pi (hooksDirName above), not - // the generic `hooks/` — pi reserves `hooks/` for its own deprecated - // extension location. - // #2470: the dest filename comes from pi's descriptor, and must satisfy - // pi's isExtensionFile() auto-discovery filter (.ts/.js only) — otherwise - // the file installs but pi never loads it and /msd never registers. - const piNativePlugin = JSON.parse( - fs.readFileSync(path.join(__dirname, '..', 'capabilities', 'pi', 'capability.json'), 'utf8') - ).runtime.hostBehaviors.nativePlugin; - assert.ok( - piNativePlugin.file.endsWith('.ts') || piNativePlugin.file.endsWith('.js'), - `${runtime}'s extension "${piNativePlugin.file}" must end in .ts or .js for pi to discover it (#2470)` - ); - assert.ok( - fs.existsSync(path.join(targetDir, piNativePlugin.dir, piNativePlugin.file)), - `${runtime} should install the native extension file at ${piNativePlugin.dir}/${piNativePlugin.file}` - ); - assert.ok( - fs.existsSync(path.join(targetDir, hooksDirName, 'msd-ensure-canonical-path.js')), - `${runtime} should install the shared ${hooksDirName}/ bundle (spawned by the native extension's event bridges)` - ); - assert.ok( - fs.existsSync(path.join(targetDir, hooksDirName, 'lib', 'git-cmd.js')), - `${runtime} should install ${hooksDirName}/lib/git-cmd.js (the /msd command tokenizer)` - ); - assert.equal( - fs.existsSync(path.join(targetDir, 'commands')), - false, - `${runtime} should NOT install a commands/ dir (plugin-only, no host-read markdown surface)` - ); - assert.equal( - fs.existsSync(path.join(targetDir, 'agents')), - false, - `${runtime} should NOT install an agents/ dir (plugin-only, no named-dispatch toolkit)` - ); - assert.equal( - fs.existsSync(path.join(targetDir, 'skills')), - false, - `${runtime} should NOT install a skills/ dir (plugin-only)` - ); } else if (contract.surface === 'commands-msd') { assert.ok( listDirNames(targetDir, path.join('commands', 'msd')).length > 0, `${runtime} should install commands/msd entries` ); - } else if (contract.surface === 'kimi-skills-agents') { - assertHasMsdDirectory(targetDir, 'skills'); - assert.ok( - fs.existsSync(path.join(targetDir, 'agents', 'msd.yaml')), - 'Kimi should install the root agent YAML' - ); - assert.ok( - fs.existsSync(path.join(targetDir, 'agents', 'msd.md')), - 'Kimi should install the root agent prompt' - ); - assert.ok( - fs.existsSync(path.join(targetDir, 'agents', 'subagents', 'msd-executor.yaml')), - 'Kimi should install MSD subagent YAML' - ); - } else if (contract.surface === 'clinerules') { - // #787: Cline now uses the .clinerules/ directory form (rules at msd.md). - assert.match( - fs.readFileSync(path.join(targetDir, '.clinerules', 'msd.md'), 'utf8'), - /MSD workflows live in `msd-core\/workflows\/`/, - 'Cline should install .clinerules/msd.md guidance' - ); - } else if (contract.surface === 'global-artifacts-noop') { - assert.equal( - fs.existsSync(path.join(targetDir, 'skills')), - false, - `${runtime} should not install unsupported global skills artifacts` - ); - assert.equal( - fs.existsSync(path.join(targetDir, 'workflows')), - false, - `${runtime} should not install unsupported global workflow artifacts` - ); } - if (contract.surface !== 'kimi-skills-agents' && contract.surface !== 'global-artifacts-noop' && contract.surface !== 'plugin-only') { - if (runtime === 'antigravity') { - // #3738: antigravity agents install under the sandboxed home's - // .gemini/config root (see the flat-skills branch above), not targetDir. - const agySandboxHomeForAgents = path.join(path.dirname(targetDir), 'home'); - assert.ok( - listDirNames(path.join(agySandboxHomeForAgents, '.gemini', 'config'), 'agents').some((name) => name.startsWith('msd-')), - `${runtime} full install should install agents` - ); - } else { - assert.ok( - listDirNames(targetDir, 'agents').some((name) => name.startsWith('msd-')), - `${runtime} full install should install agents` - ); - } + if (runtime === 'antigravity') { + // #3738: antigravity agents install under the sandboxed home's + // .gemini/config root (see the flat-skills branch above), not targetDir. + const agySandboxHomeForAgents = path.join(path.dirname(targetDir), 'home'); + assert.ok( + listDirNames(path.join(agySandboxHomeForAgents, '.gemini', 'config'), 'agents').some((name) => name.startsWith('msd-')), + `${runtime} full install should install agents` + ); + } else { + assert.ok( + listDirNames(targetDir, 'agents').some((name) => name.startsWith('msd-')), + `${runtime} full install should install agents` + ); } assert.equal( @@ -428,9 +288,9 @@ function assertFreshInstallContract(runtime, targetDir) { `${runtime} settings.json presence should match the runtime contract` ); // #2544: the CommonJS marker lives in the shared hooks dir (the dir MSD - // fills with its own .js scripts — `msd-hooks/` for pi, `hooks/` for every - // other runtime, #3023), never at the config root — that file is user-owned - // territory on OpenCode/Kilo and was being clobbered on every install. + // fills with its own .js scripts — `hooks/` unless a runtime overrides it, + // #3023), never at the config root — that file is user-owned + // territory on OpenCode and was being clobbered on every install. assert.equal( fs.existsSync(path.join(targetDir, hooksDirName, 'package.json')), contract.hooksPackageJson, @@ -449,14 +309,6 @@ function assertFreshInstallContract(runtime, targetDir) { 'Codex should install config.toml with the MSD marker' ); } - - if (contract.copilotInstructions) { - assert.match( - fs.readFileSync(path.join(targetDir, 'copilot-instructions.md'), 'utf8'), - /MSD Configuration/, - 'Copilot should install managed copilot instructions' - ); - } } describe('installer migration install integration', { concurrency: false }, () => { @@ -618,11 +470,7 @@ describe('installer migration install integration', { concurrency: false }, () = assert.match(output, /Installing for /); assert.match(output, /Installer migrations/); assert.match(output, /removed\s+hooks\/statusline\.js/); - if (runtime === 'kimi') { - assert.match(output, /Generated Kimi root agent/); - } else { - assert.match(output, /Installed workflow assets/); - } + assert.match(output, /Installed workflow assets/); assert.match(output, /Done!/); assert.equal(fs.existsSync(path.join(targetDir, 'hooks/statusline.js')), false); diff --git a/tests/installer-migrations.test.cjs b/tests/installer-migrations.test.cjs index fbc6d7b54..4f9b2a460 100644 --- a/tests/installer-migrations.test.cjs +++ b/tests/installer-migrations.test.cjs @@ -428,29 +428,26 @@ test('OpenCode commands/ baseline is idempotent — a second run does not re-pla } }); -test('OpenCode commands/ baseline migration is scoped to opencode and never plans for Kilo', () => { +test('OpenCode commands/ baseline migration is scoped to opencode and never plans for other runtimes', () => { const configDir = createTempInstall(); try { - // Kilo's descriptor keeps the singular `command/` dir; a `commands/` (plural) - // directory here would be unrelated to Kilo's install surface. This proves the - // migration's `runtimes: ['opencode']` scoping keeps Kilo installs untouched. + // This proves the migration's `runtimes: ['opencode']` scoping keeps other + // runtimes' installs untouched. writeFile(configDir, 'commands/msd-plan-phase.md', 'managed command\n'); - writeFile(configDir, 'command/msd-plan-phase.md', 'kilo managed command\n'); writeManifest(configDir, { 'commands/msd-plan-phase.md': sha256('managed command\n'), - 'command/msd-plan-phase.md': sha256('kilo managed command\n'), }); const result = runInstallerMigrations({ configDir, - runtime: 'kilo', + runtime: 'claude', scope: 'global', migrations: [opencodeBaselineCommandsDirMigration], baselineScan: true, now: () => '2026-07-17T00:00:03.000Z', }); - // migrationMatchesContext filters this migration out entirely for kilo + // migrationMatchesContext filters this migration out entirely for claude // (runtimes: ['opencode']) before plan() is ever invoked: it is not // "pending", produces zero actions, is never applied, and no install-state // file is written for this run at all. @@ -458,7 +455,6 @@ test('OpenCode commands/ baseline migration is scoped to opencode and never plan assert.deepEqual(result.appliedMigrationIds, []); assert.equal(fs.existsSync(path.join(configDir, INSTALL_STATE_NAME)), false); assert.equal(fs.readFileSync(path.join(configDir, 'commands/msd-plan-phase.md'), 'utf8'), 'managed command\n'); - assert.equal(fs.readFileSync(path.join(configDir, 'command/msd-plan-phase.md'), 'utf8'), 'kilo managed command\n'); } finally { cleanup(configDir); } @@ -1692,7 +1688,7 @@ test('shipped installer-migration checksums are locked to a committed baseline ( // kinds install under the .gemini/config home override and this migration // converges upgraded installs. Global scope only — local .agents is live. '2026-08-26-antigravity-retire-confighome-artifacts': - 'sha256:0011d160a7fe07230a9d89de8ce39215e004fff1077961dc687781cb9fe103ba', + 'sha256:9d19e13048455df7727960e68473cedd24f162918362b21e792ddf881a496e01', }; const { DEFAULT_MIGRATIONS_DIR, migrationChecksum: computeChecksum } = require('../msd-core/bin/lib/installer-migrations.cjs'); diff --git a/tests/intel.test.cjs b/tests/intel.test.cjs index 0e331bf4d..fec41e642 100644 --- a/tests/intel.test.cjs +++ b/tests/intel.test.cjs @@ -1829,285 +1829,3 @@ describe('#3258: no stale /msd-intel slash-command references in product source }); } -// ──────────────────────────────────────────────────────────────────────── -// Folded from tests/bug-2351-intel-kilo-layout.test.cjs — consolidation epic #1969 (B7 #1976) -// ──────────────────────────────────────────────────────────────────────── -{ - const { describe: __foldDescribe } = require('node:test'); - __foldDescribe("folded:bug-2351-intel-kilo-layout (consolidation epic #1969 B7 #1976)", () => { -/** - * Regression test for bug #2351 - * - * msd-intel-updater used hardcoded canonical paths (`agents/*.md`, - * `commands/msd/*.md`, `hooks/*.js`, etc.) that assumed the standard - * `.claude/` runtime layout. Under a `.kilo` install, the runtime root is - * `.kilo/`, and the command directory is `command/` (not `commands/msd/`). - * Globs against the old paths returned no results, producing semantically - * empty intel files (`"entries": {}`). - * - * Fix: add runtime layout detection and a mapping table so the agent - * resolves paths against the correct root. - */ - -'use strict'; - -const { test, describe } = require('node:test'); -const assert = require('node:assert/strict'); -const fs = require('fs'); -const path = require('path'); - -const AGENT_PATH = path.join(__dirname, '..', 'agents', 'msd-intel-updater.md'); - -describe('bug #2351: intel updater kilo layout support', () => { - let content; - - test('agent file exists', () => { - assert.ok(fs.existsSync(AGENT_PATH), 'agents/msd-intel-updater.md must exist'); - content = fs.readFileSync(AGENT_PATH, 'utf-8'); - }); - - test('scope section includes layout detection step', () => { - content = content || fs.readFileSync(AGENT_PATH, 'utf-8'); - const hasDetection = - content.includes('ls -d .kilo') || - content.includes('Runtime layout detection') || - content.includes('detected layout') || - content.includes('layout detection'); - assert.ok( - hasDetection, - 'msd-intel-updater.md must instruct the agent to detect the runtime layout ' + - '(.kilo vs .claude) before resolving canonical paths (#2351)' - ); - }); - - test('scope section maps .kilo/agents path', () => { - content = content || fs.readFileSync(AGENT_PATH, 'utf-8'); - assert.ok( - content.includes('.kilo/agents'), - 'scope section must include the .kilo/agents/*.md path so agent count is correct under kilo layout' - ); - }); - - test('scope section maps .kilo/command path (not commands/msd)', () => { - content = content || fs.readFileSync(AGENT_PATH, 'utf-8'); - assert.ok( - content.includes('.kilo/command'), - 'scope section must include .kilo/command path — kilo uses "command/" not "commands/msd/"' - ); - }); - - test('scope section maps .kilo/hooks path', () => { - content = content || fs.readFileSync(AGENT_PATH, 'utf-8'); - assert.ok( - content.includes('.kilo/hooks'), - 'scope section must include .kilo/hooks path for hook file counts' - ); - }); - - test('scope section retains standard layout paths for .claude installs', () => { - content = content || fs.readFileSync(AGENT_PATH, 'utf-8'); - assert.ok( - content.includes('agents/*.md') || content.includes('Standard `.claude` layout'), - 'scope section must still document the standard .claude layout paths for non-kilo installs' - ); - }); -}); - }); -} - - -// ──────────────────────────────────────────────────────────────────────── -// Folded from tests/bug-3290-intel-updater-layout-block.test.cjs — consolidation epic #1969 (B7 #1976) -// ──────────────────────────────────────────────────────────────────────── -{ - const { describe: __foldDescribe } = require('node:test'); - __foldDescribe("folded:bug-3290-intel-updater-layout-block (consolidation epic #1969 B7 #1976)", () => { -// allow-test-rule: source-text-is-the-product (see #3290) -// agents/msd-intel-updater.md IS the deployed agent instruction set. Asserting -// its text content tests the deployed behaviour contract, not internal implementation. - -'use strict'; - -/** - * Regression tests for bug #3290. - * - * The "Runtime layout detection" block in msd-intel-updater.md ran - * unconditionally on every project analysed, emitting: - * - * Layout detection returned "unknown" — this project is not a MSD-system - * installation (no `.claude/msd-core/` or `.kilo/` runtime root). - * - * for every ordinary (non-MSD-framework) user project. The verdict was already - * ignored by Steps 2-6 on non-MSD projects. The block was dead-but-noisy. - * - * Fix: gate the runtime bash detection on a positive "is-this-the-framework- - * repo" check (package.json name === "@golem15/msd-core") so it runs ONLY when - * analysing the MSD framework's own repo, OR remove the block entirely if no - * downstream consumers exist. - * - * Group A — gating contract: - * The unconditional bash detection invocation must be absent OR wrapped in a - * framework-repo guard. A bare `ls -d .kilo ... || echo "unknown"` with no - * surrounding gate is the defect signature. - * - * Group B — no orphan consumers: - * Confirm no other agent, command, or workflow file reads/consumes the layout- - * detection verdict emitted by this block. - */ - -const { describe, test } = require('node:test'); -const assert = require('node:assert/strict'); -const fs = require('node:fs'); -const path = require('node:path'); - -const ROOT = path.join(__dirname, '..'); -const AGENT_PATH = path.join(ROOT, 'agents', 'msd-intel-updater.md'); - -// ─── helpers ───────────────────────────────────────────────────────────────── - -/** Walk a directory recursively and return absolute paths of all .md files. */ -function walkMd(dir) { - const results = []; - if (!fs.existsSync(dir)) return results; - for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { - const abs = path.join(dir, entry.name); - if (entry.isDirectory()) { - results.push(...walkMd(abs)); - } else if (entry.isFile() && entry.name.endsWith('.md')) { - results.push(abs); - } - } - return results; -} - -// ─── Group A — gating contract ─────────────────────────────────────────────── - -describe('bug #3290 — Group A: layout-detection block must be gated or absent', () => { - let content; - - test('agent file exists', () => { - assert.ok(fs.existsSync(AGENT_PATH), 'agents/msd-intel-updater.md must exist'); - content = fs.readFileSync(AGENT_PATH, 'utf-8'); - }); - - test( - 'bare unconditional detection invocation is absent — ' + - 'the "ls -d .kilo ... || echo unknown" must not appear outside a framework-repo gate', - () => { - content = content || fs.readFileSync(AGENT_PATH, 'utf-8'); - - // The defect signature: the bash block runs unconditionally. - // We look for the exact shell one-liner that emits the verdict. - const bareDetectionPattern = - /ls -d \.kilo\b.*\|\|.*echo "?unknown"?/; - - const hasBareDetection = bareDetectionPattern.test(content); - - if (!hasBareDetection) { - // Block is fully removed — option B — pass. - return; - } - - // Block is still present. Verify it is surrounded by a framework-repo gate. - // A valid gate checks package.json name or an equivalent positive signal - // that the current project IS the MSD framework's own repo. - const hasFrameworkGate = - content.includes('@golem15/msd-core') || - content.includes('is-this-the-framework') || - content.includes('framework repo') || - content.includes('Only run') || - /if.*package\.json.*msd-core/i.test(content) || - /Only.*layout detection.*MSD framework/i.test(content) || - /Only.*layout detection.*framework/i.test(content); - - assert.ok( - hasFrameworkGate, - 'agents/msd-intel-updater.md contains a bare unconditional layout-detection ' + - 'bash block (`ls -d .kilo ... || echo unknown`) with no surrounding ' + - 'framework-repo gate (#3290). ' + - 'Either remove the block entirely, or wrap it in a check like:\n' + - ' if [[ "$(jq -r \'.name // ""\' package.json 2>/dev/null)" == "@golem15/msd-core" ]]; then\n' + - ' # ... detection block ...\n' + - ' fi' - ); - } - ); -}); - -// ─── Group B — no orphan downstream consumers ──────────────────────────────── - -describe('bug #3290 — Group B: layout-detection verdict has no downstream consumers', () => { - const SOURCE_DIRS = [ - path.join(ROOT, 'agents'), - path.join(ROOT, 'commands', 'msd'), - path.join(ROOT, 'msd-core', 'workflows'), - ]; - - /** - * Lines that reference the three possible verdict values emitted by the - * detection block: "claude", "kilo", "unknown" — ONLY as the verdict output - * of the msd-intel-updater layout detection (not general runtime references). - * - * We look for the specific phrase "Layout detection returned" which is the - * sentinel the noisy output line uses. - */ - test('no file contains "Layout detection returned" (the noisy verdict phrase)', () => { - const matches = []; - - for (const dir of SOURCE_DIRS) { - const files = walkMd(dir); - for (const file of files) { - const rel = path.relative(ROOT, file); - const src = fs.readFileSync(file, 'utf-8'); - if (src.includes('Layout detection returned')) { - // Collect matching lines for the error message - const lines = src.split(/\r?\n/) - .map((l, i) => ({ line: l, n: i + 1 })) - .filter(({ line }) => line.includes('Layout detection returned')); - matches.push({ rel, lines }); - } - } - } - - assert.strictEqual( - matches.length, - 0, - 'Expected zero files to contain "Layout detection returned" (the noisy verdict ' + - 'phrase from the msd-intel-updater layout-detection block). Found:\n' + - matches.map(({ rel, lines }) => - ` ${rel}:\n${lines.map(({ n, line }) => ` L${n}: ${line.trim()}`).join('\n')}` - ).join('\n') - ); - }); - - test('no agent or workflow instructs reading the layout-detection verdict output', () => { - // The verdict was: echo "kilo" | echo "claude" | echo "unknown" - // If any file references "Layout detection returned unknown" as an instruction - // to consume, that would be a consumer. We verify none exist outside of - // the producing file (msd-intel-updater.md). - const verdictConsumerPattern = /Layout detection returned.*(unknown|claude|kilo)/i; - const consumers = []; - - for (const dir of SOURCE_DIRS) { - const files = walkMd(dir); - for (const file of files) { - // Exclude the producer itself — it defines the message, not consumes it - if (path.basename(file) === 'msd-intel-updater.md') continue; - const src = fs.readFileSync(file, 'utf-8'); - if (verdictConsumerPattern.test(src)) { - consumers.push(path.relative(ROOT, file)); - } - } - } - - assert.deepStrictEqual( - consumers, - [], - 'Expected no downstream consumer of the layout-detection verdict. Found:\n' + - consumers.map((f) => ` ${f}`).join('\n') + - '\nIf a consumer exists, use option A (gate) not option B (remove).' - ); - }); -}); - }); -} diff --git a/tests/lint-retired-runtime-name.test.cjs b/tests/lint-retired-runtime-name.test.cjs index 17c66046c..efbdb9d21 100644 --- a/tests/lint-retired-runtime-name.test.cjs +++ b/tests/lint-retired-runtime-name.test.cjs @@ -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, Cline, and Codex.`], + `MSD installs cleanly on ${RETIRED_NAME} 3, Cursor, 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, Cline, and Cursor.`], + `Supported agents include ${RETIRED_NAME} 3, OpenCode, and Cursor.`], ]; for (const [label, relPath, body] of CASES) { diff --git a/tests/model-catalog.unit.test.cjs b/tests/model-catalog.unit.test.cjs index 05752afa8..549cb8b01 100644 --- a/tests/model-catalog.unit.test.cjs +++ b/tests/model-catalog.unit.test.cjs @@ -428,7 +428,7 @@ describe('model-catalog: AGENT_TO_PHASE_TYPE / AGENT_DEFAULT_TIERS (#3915)', () describe('model-catalog: RUNTIME_PROFILE_MAP filtering (#3915)', () => { // The catalog's runtimeTierDefaults has runtimes whose opus/sonnet/haiku - // entries are ALL null (e.g. 'cline') as a deliberate "no defaults yet" + // entries are ALL null as a deliberate "no defaults yet" // sentinel, and runtimes fully populated (e.g. 'claude'). This pair is // the exact boundary the filter's `Object.keys(filtered).length > 0` // check exists for. diff --git a/tests/model-omit-when-inherit-guard.test.cjs b/tests/model-omit-when-inherit-guard.test.cjs index feffc2e3b..9b8083cc1 100644 --- a/tests/model-omit-when-inherit-guard.test.cjs +++ b/tests/model-omit-when-inherit-guard.test.cjs @@ -31,9 +31,9 @@ const OMIT_RULE_MARKER = ""; function statesOmitRule(content) { // Canonical form: the marker block, which links the rule's single source of truth. // Preferred for new files because it is unambiguous and greppable, and because it - // carries no literal `model=` token — the installed Hermes copy of a workflow is - // asserted to contain none outside string literals (delegate_task has no per-call - // model parameter at all), so the older phrasing cannot be used everywhere. + // carries no literal `model=` token — some installed workflow copies are + // asserted to contain none outside string literals, so the older phrasing cannot + // be used everywhere. if (content.includes(OMIT_RULE_MARKER)) return true; // Legacy form: the rule stated inline in the file's own words. All four files that // predate the marker (plan-phase, execute-phase, scan, ship) match this branch, and diff --git a/tests/msd-agent-isolation-guard.test.cjs b/tests/msd-agent-isolation-guard.test.cjs index 2672ff35e..51d7bf664 100644 --- a/tests/msd-agent-isolation-guard.test.cjs +++ b/tests/msd-agent-isolation-guard.test.cjs @@ -1289,8 +1289,7 @@ describe('#3045 MAJOR — --harness-flag can now accept a bare CLI-flag value (C // fabricated registry precondition), not a production defect — corrected // here to prove the `--harness-flag=` parser generalizes to any // bare-CLI-flag-shaped value, not merely Cursor's specific '--worktree' - // string (which the sub-test above already pins). (Originally pinned on - // windsurf, retired in prune-runtimes.) + // string (which the sub-test above already pins). assert.equal( runtimes.zcode.runtime.harnessIsolationFlag, undefined, diff --git a/tests/msd-tools-path-refs.test.cjs b/tests/msd-tools-path-refs.test.cjs index 1ea937a9e..edab13228 100644 --- a/tests/msd-tools-path-refs.test.cjs +++ b/tests/msd-tools-path-refs.test.cjs @@ -92,7 +92,7 @@ const { formatMsdSlash, resolveRuntime } = require( const { cleanup } = require('./helpers.cjs'); describe('formatMsdSlash — runtime-aware slash command formatter', () => { - describe('hyphen-form runtimes (claude, cursor, opencode, kilo, etc.)', () => { + describe('hyphen-form runtimes (claude, cursor, opencode, etc.)', () => { test('emits /msd- for claude', () => { assert.strictEqual(formatMsdSlash('execute-phase', 'claude'), '/msd-execute-phase'); }); @@ -105,10 +105,6 @@ describe('formatMsdSlash — runtime-aware slash command formatter', () => { assert.strictEqual(formatMsdSlash('discuss-phase', 'opencode'), '/msd-discuss-phase'); }); - test('emits /msd- for kilo', () => { - assert.strictEqual(formatMsdSlash('health', 'kilo'), '/msd-health'); - }); - test('unknown runtime defaults to hyphen form', () => { assert.strictEqual( formatMsdSlash('new-project', 'some-future-runtime'), diff --git a/tests/opencode-plugin-adapter.test.cjs b/tests/opencode-plugin-adapter.test.cjs index 8958a46ea..15bce38bf 100644 --- a/tests/opencode-plugin-adapter.test.cjs +++ b/tests/opencode-plugin-adapter.test.cjs @@ -17,7 +17,7 @@ * adapter's own error handling swallows spawn failures by design (a broken hook * must never break a tool call). A MISSING hook script likewise never breaks * the tool call, but since #2305 it warns loudly (once per hook file) — a - * silently-absent guard script was exactly how every Kilo guard no-opped. + * silently-absent guard script was exactly how every guard no-opped. */ const { test } = require('node:test'); @@ -333,7 +333,7 @@ test('tool.execute.after: Read content rewriting maps ~/.claude/msd-core paths', test('missing hook script warns loudly but still allows (never breaks the tool call, #2305)', async (t) => { // No hook stubs written at all → every runHook finds no file → allow, but // each absent guard script must be warned about (once per hook file): a - // silently-missing guard is how #2305 no-opped every Kilo guard. + // silently-missing guard is how #2305 no-opped every guard. const { mod } = buildInstalledLayout(t, {}); const warnings = []; const realConsoleError = console.error; diff --git a/tests/plan-review-convergence.test.cjs b/tests/plan-review-convergence.test.cjs index 6b6fca582..56f99974d 100644 --- a/tests/plan-review-convergence.test.cjs +++ b/tests/plan-review-convergence.test.cjs @@ -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 '\-\-'` line per recognized flag. Absence meant // the flag was silently dropped, and that whitelist drifted three separate - // times (--coderabbit, then --qwen/--cursor, then the unanchored + // times (--coderabbit, then --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 diff --git a/tests/product-name-purity.test.cjs b/tests/product-name-purity.test.cjs index 4c2d54457..c35ac5a17 100644 --- a/tests/product-name-purity.test.cjs +++ b/tests/product-name-purity.test.cjs @@ -21,8 +21,8 @@ const ROOT = path.join(__dirname, '..'); // Product names that appear in install blocks as comment headers const PRODUCTS = [ - 'Claude Code', 'Claude', 'OpenCode', 'Kilo', 'Codex', 'Copilot', - 'Cursor', 'Windsurf', 'Antigravity', 'Trae', 'Cline', 'Augment', + 'Claude Code', 'Claude', 'OpenCode', 'Codex', + 'Cursor', 'Antigravity', 'Gemini', 'Gemini CLI', ]; diff --git a/tests/project-instruction-file-parity.test.cjs b/tests/project-instruction-file-parity.test.cjs index 03aab8b5c..c17afdfca 100644 --- a/tests/project-instruction-file-parity.test.cjs +++ b/tests/project-instruction-file-parity.test.cjs @@ -51,9 +51,6 @@ const RUNTIMES = [ 'claude', 'codex', 'opencode', - 'kilo', - 'kimi', - 'copilot', 'antigravity', // 'gemini' intentionally excluded from this loop — #4709 AC#1 made it // REFUSE (RetiredRuntimeError) on both surfaces instead of agreeing on a diff --git a/tests/repo-layout.test.cjs b/tests/repo-layout.test.cjs index 4f451b519..48af2811d 100644 --- a/tests/repo-layout.test.cjs +++ b/tests/repo-layout.test.cjs @@ -7,7 +7,7 @@ * (such as AGENTS.md) that would become an untracked source of truth running * in parallel with the canonical CONTEXT.md and docs/adr/ records. * - * Context: historically bin/install.js (the since-retired local Copilot install + * Context: historically bin/install.js (the since-retired local install * path, issue #786) wrote an AGENTS.md to process.cwd() when run inside a repo * checkout. If such a file is ever committed, editors and AI tools that auto-load * repo-root instruction files will silently pick up an installer-generated @@ -25,7 +25,7 @@ const ROOT = path.resolve(__dirname, '..'); test('repo-layout: root AGENTS.md is not git-tracked — no ad-hoc AI instruction file committed alongside CONTEXT.md', () => { // An installer-generated AGENTS.md may exist on disk after a local install - // (historically the Copilot path, issue #786). What must NOT happen is + // (historically a retired install path, issue #786). What must NOT happen is // committing it to git, where editors and AI tools would silently pick up // the installer-generated stub instead of CONTEXT.md. const r = runGit(['ls-files', '--error-unmatch', 'AGENTS.md'], { diff --git a/tests/review-lane-descriptor.test.cjs b/tests/review-lane-descriptor.test.cjs index 5968c33f6..aeb9ec27a 100644 --- a/tests/review-lane-descriptor.test.cjs +++ b/tests/review-lane-descriptor.test.cjs @@ -207,7 +207,7 @@ describe('reviewer lane parity — not-corruption (must NOT fire)', () => { // (ADR-2782 D8). Instances take no part in the roster, the flag set, or uniqueness. const withNewInstance = WORKFLOW_TEXT.replace( '## Consensus Summary', - '## Qwen Review (qwen-turbo)\n\n{x}\n\n---\n\n## Consensus Summary', + '## Example Review (example-turbo)\n\n{x}\n\n---\n\n## Consensus Summary', ); assert.deepStrictEqual(check({ workflowText: withNewInstance }).violations, []); }); @@ -245,11 +245,11 @@ describe('reviewer lane parity — cross-platform and hostile input', () => { const crlf = asCrlf( WORKFLOW_TEXT.replace( '', - '\n', + '\n', ), ); assert.deepStrictEqual(reasons(check({ workflowText: crlf })), [ - `${PARITY_VIOLATION.BESPOKE_LEG_PRESENT}:qwen`, + `${PARITY_VIOLATION.BESPOKE_LEG_PRESENT}:example`, ]); }); diff --git a/tests/review-lane-invocation.test.cjs b/tests/review-lane-invocation.test.cjs index aa0b5f8c6..abf1a4c6d 100644 --- a/tests/review-lane-invocation.test.cjs +++ b/tests/review-lane-invocation.test.cjs @@ -4,7 +4,7 @@ * THE GOLDEN TABLE IS THE POINT OF THIS FILE. Phase 5b deleted ~640 lines of hand-authored per-CLI * bash, and every one of those legs encoded a hard-won fix (#2494/#2605 empty output, #1698 Codex * stdout teardown noise, #1936 OpenCode zero-output turns, #2073 Antigravity's three modes, #2176 - * repo-root anchoring, #2589 no jq on stock Windows, #2794 Qwen's missing sidecar). Old and new + * repo-root anchoring, #2589 no jq on stock Windows). Old and new * cannot literally run in parallel, so the golden table below IS the strangler-fig substitute: each * row is the invocation the bash leg produced, and the resolver must reproduce it exactly. * diff --git a/tests/review-lane-runner.test.cjs b/tests/review-lane-runner.test.cjs index 7bdd3a94f..f6e750601 100644 --- a/tests/review-lane-runner.test.cjs +++ b/tests/review-lane-runner.test.cjs @@ -1079,10 +1079,10 @@ describe('#2605 local OpenAI-compatible lanes produce diagnosable output', () => }); }); -// Folded from tests/fix-2794-review-qwen-empty-guard (qwen retired; cursor stands in).test.cjs (#3334/H3). +// Folded from tests/fix-2794-review-*-empty-guard.test.cjs (#3334/H3). // -// #2794 — the qwen reviewer leg was the last one still sending stderr to /dev/null. Every other lane -// captured stderr to a `.err` sidecar and appended it to the stub (#2494/#2605); qwen wrote a bare +// #2794 — one reviewer leg was the last still sending stderr to /dev/null. Every other lane +// captured stderr to a `.err` sidecar and appended it to the stub (#2494/#2605); that leg wrote a bare // "failed or returned empty output." with no diagnostic at all, so a missing binary, an auth prompt // and a rate-limit were indistinguishable from each other AND from a clean empty review. describe('#2794 cursor reviewer stderr capture', () => { diff --git a/tests/review-lane-windows-spawn-resolution.test.cjs b/tests/review-lane-windows-spawn-resolution.test.cjs index 546f1b470..8402a03c2 100644 --- a/tests/review-lane-windows-spawn-resolution.test.cjs +++ b/tests/review-lane-windows-spawn-resolution.test.cjs @@ -5,7 +5,7 @@ * * Root cause: `routeReviewLane`'s `deps.spawn` (msd-core/bin/msd-tools.cjs) mediate * Windows `.cmd`/`.bat` shims through cmd.exe only when the binary name ALREADY - * carries the extension, but lane descriptors declare BARE names ('codex', 'kimi', + * carries the extension, but lane descriptors declare BARE names ('codex', * 'agy') and nothing resolved them to the on-disk extensioned form — while * `deps.hasBinary` scanned PATH WITH PATHEXT, so probes reported the lane * available for a spawn that could never start. @@ -14,10 +14,10 @@ * from msd-tools.cjs. This file exercises: * - the resolver itself, portably, against staged fake-bin dirs (P1–P8); * - the real CLI end-to-end on Windows: staged `codex.CMD` (primary invocation - * path, file-arg) and `kimi.CMD` (capability-probe path) invoked through - * `msd-tools review-lane invoke` with the staged dir first on PATH (W1–W2). + * path, file-arg) invoked through + * `msd-tools review-lane invoke` with the staged dir first on PATH (W1). * - * The E2E cases stage an extensionless POSIX sh shim NEXT to each `.CMD` on + * The E2E case stages an extensionless POSIX sh shim NEXT to the `.CMD` on * purpose: an `['', ...PATHEXT]` resolver resolves to it and re-opens the exact * ENOENT (field-reported on Windows 11), so its presence pins the ordering. * @@ -151,8 +151,7 @@ describe('resolveSpawnBinary (#3275)', () => { // ──────────────────────────────────────────────────────────────────────── // Windows-gated end-to-end: the REAL CLI seam (no injected deps), staged // shims first on PATH, mirroring how `npm install -g` lays down CLIs. -// Pre-fix, W1 stubs with `[spawn error: ENOENT]` and W2 fails its -// capability probe — the two symptoms the issue reports. +// Pre-fix, W1 stubs with `[spawn error: ENOENT]`. // ──────────────────────────────────────────────────────────────────────── describe('review-lane invoke on Windows (#3275)', () => { const isWin = process.platform === 'win32'; @@ -226,43 +225,9 @@ describe('review-lane invoke on Windows (#3275)', () => { } }); - test('W2: kimi-code lane passes its capability probe and runs through a staged kimi.CMD shim', { skip: !isWin }, () => { - const binDir = createTempDir('msd-3275-w2-bin-'); - const runDir = createTempDir('msd-3275-w2-run-'); - const repoRoot = createTempDir('msd-3275-w2-repo-'); - try { - stageBin(binDir, { - kimi: '#!/bin/sh\nexit 1\n', - 'kimi.CMD': [ - '@echo off', - 'if /I "%~1"=="--help" (', - ' echo Usage: kimi [-m MODEL] [-p PROMPT] --output-format FORMAT', - ' exit /b 0', - ')', - 'echo fake kimi-code review from the #3275 Windows shim', - 'exit /b 0', - ].join('\r\n'), - }); - const { result } = invokeLane({ slug: 'kimi-code', binDir, runDir, repoRoot }); - - assert.equal(result.ok, true, `lane failed: ${result.reason} ${result.detail}`); - assert.equal(result.stubbed, false, `review was stubbed: ${JSON.stringify(result)}`); - - const review = fs.readFileSync(path.join(runDir, 'msd-review-kimi-code.md'), 'utf8'); - assert.ok( - review.includes('#3275 Windows shim'), - `review file should carry the shim's output, got: ${review}`, - ); - } finally { - cleanup(binDir); - cleanup(runDir); - cleanup(repoRoot); - } - }); - test('E2E wiring is aimed at the real CLI seam', () => { // Guards the test itself: if TOOLS_PATH or the review-lane verb ever moves, - // W1/W2 would silently skip their assertions behind the win32 gate. + // W1 would silently skip their assertions behind the win32 gate. assert.ok(fs.existsSync(TOOLS_PATH), `msd-tools.cjs not found at ${TOOLS_PATH}`); }); }); diff --git a/tests/review-plan-coverage-manifest.test.cjs b/tests/review-plan-coverage-manifest.test.cjs index edd09a462..96601422d 100644 --- a/tests/review-plan-coverage-manifest.test.cjs +++ b/tests/review-plan-coverage-manifest.test.cjs @@ -381,11 +381,11 @@ describe('#3301 write_reviews grades each lane against the plan coverage manifes .filter((id) => id !== '07') .map((id) => `## ${id}\n\ncovered\n`) .join('\n'); - const fx = buildCoverageFixture(ids, { qwen: body }); + const fx = buildCoverageFixture(ids, { cursor: body }); t.after(() => cleanup(fx.root)); const res = runScript(shell, extractCoverageCheckBlock(), fx.root, fx.runDir, fx.env, REPO_ROOT); assert.strictEqual(res.status, 0, `block exited ${res.status}: ${res.stderr}`); - const cov = readCoverageJson(fx.runDir, 'qwen'); + const cov = readCoverageJson(fx.runDir, 'cursor'); assert.strictEqual(cov.complete, false); assert.deepEqual(cov.missing_ids, ['07']); assert.strictEqual(cov.total, 7); diff --git a/tests/reviewer-config-federation.test.cjs b/tests/reviewer-config-federation.test.cjs index 4edf9495f..4d22fbd08 100644 --- a/tests/reviewer-config-federation.test.cjs +++ b/tests/reviewer-config-federation.test.cjs @@ -101,20 +101,20 @@ describe('reviewer config federation — provenance actually moved (#2797)', () }); test('a lane with no model flag and no host owns no MODEL or HOST key (#3691 narrows #2797)', () => { - // Absent-safe (ADR-2782 D4): qwen and coderabbit take neither a model + // Absent-safe (ADR-2782 D4): coderabbit takes neither a model // argument nor a host. Under #2797 that meant "declares nothing" — the only // way a lane owned a config key was via a model flag or a host. #3691 gave // every CLI lane a `review.max_prompt_tokens_per_reviewer.` key, a - // third legitimate reason to own a key, so qwen/coderabbit now legitimately - // own their own budget key. `cursor` gained a real model flag (#3653) and + // third legitimate reason to own a key, so coderabbit now legitimately + // owns its own budget key. `cursor` gained a real model flag (#3653) and // now legitimately owns `review.models.cursor` too, so it is excluded from // this loop. The part of the #2797 invariant that still holds — a lane must // never own a MODEL or HOST key it has no use for, or another lane's budget - // key — is what this asserts directly for the two lanes that still have + // key — is what this asserts directly for the lane that still has // neither. for (const [key, entry] of Object.entries(registry.configSchema || {})) { const owner = entry && entry.owner; - if (!['qwen', 'coderabbit'].includes(owner)) continue; + if (owner !== 'coderabbit') continue; assert.ok( !key.startsWith('review.models.') && !key.endsWith('_host'), `${owner} must not own a model or host key, but owns "${key}"`, @@ -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, opencode, qwen) declares `promptBudgetKey: null`, so +// codex, opencode) 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 diff --git a/tests/reviewer-manifest-body.test.cjs b/tests/reviewer-manifest-body.test.cjs index 23f09623f..eb113142e 100644 --- a/tests/reviewer-manifest-body.test.cjs +++ b/tests/reviewer-manifest-body.test.cjs @@ -983,7 +983,7 @@ describe('F. Lane scalars', () => { // Behavioural parity, not just source equality: the same inputs must get the // same verdict from both surfaces. - for (const slug of ['gemini', 'lm_studio', 'llama_cpp', '4o-mini', '2b-local', 'kimi-code']) { + for (const slug of ['gemini', 'lm_studio', 'llama_cpp', '4o-mini', '2b-local']) { const lane = laneOverride((l) => { l.slug = slug; }); const accepted = validateReviewerBody({ id: 'x', reviewer: lane }).length === 0; assert.equal( diff --git a/tests/runtime-artifact-layout-descriptor-drive.test.cjs b/tests/runtime-artifact-layout-descriptor-drive.test.cjs index 3af8a9727..8cc0e3a13 100644 --- a/tests/runtime-artifact-layout-descriptor-drive.test.cjs +++ b/tests/runtime-artifact-layout-descriptor-drive.test.cjs @@ -11,15 +11,14 @@ * 3. layout.runtime === runtime, layout.configDir === configDir, layout.scope === scope. * * SCOPE-FALL-THROUGH NOTE: - * The old switch() had no scope branches for 12 runtimes (cursor, codex, - * copilot, antigravity, windsurf, augment, trae, qwen, hermes, codebuddy, opencode, - * kilo), meaning scope='local' returned the same kinds as scope='global'. The 5a - * descriptors incorrectly set local:[] for those runtimes, causing 31 local-install - * test regressions. The 5b backfill sets local == global for these 12, restoring - * the old switch's scope-agnostic behaviour. + * The old switch() had no scope branches for several runtimes (cursor, codex, + * antigravity, opencode), meaning scope='local' returned the same kinds as + * scope='global'. The 5a descriptors incorrectly set local:[] for those runtimes, + * causing 31 local-install test regressions. The 5b backfill sets local == global + * for these, restoring 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=[]), + * (claude: distinct local=commands+agents), * the STEP-0 golden matches the descriptor exactly and is left unchanged. * * Unknown runtime case: @@ -44,7 +43,7 @@ const FAKE_DIR = '/tmp/fake-config-dir-dd'; // ── STEP-0 golden (captured from switch BEFORE edits) ──────────────────────── // Format: { kind, destSubpath, prefix } for each entry in kinds[]. // 'function' means we assert typeof kind.stage === 'function'. -// ADR-1235 step 1 (#1763): cursor, windsurf, augment, trae, codebuddy each gained +// ADR-1235 step 1 (#1763): cursor gained // an `agents` kind (appended last). Goldens consciously updated post-cutover. const GOLDEN = { diff --git a/tests/runtime-artifact-layout-surface.test.cjs b/tests/runtime-artifact-layout-surface.test.cjs index 5e97531d8..485e5fc4b 100644 --- a/tests/runtime-artifact-layout-surface.test.cjs +++ b/tests/runtime-artifact-layout-surface.test.cjs @@ -308,10 +308,10 @@ describe('applySurface', () => { assert.ok(files.includes('msd-help.md'), 'msd-help.md should be present after applySurface on missing dest'); }); - test('Hermes profile shrink: stale MSD skill dirs are removed; user skills preserved', (t) => { + test('Empty-prefix profile shrink: stale MSD skill dirs are removed; user skills preserved', (t) => { const { _syncMsdDir } = require('../msd-core/bin/lib/surface.cjs'); - const base = createTempDir('msd-surface-hermes-shrink-'); + const base = createTempDir('msd-surface-empty-prefix-shrink-'); t.after(() => cleanup(base)); const stagedDir = path.join(base, 'staged'); const destDir = path.join(base, 'dest'); @@ -332,8 +332,8 @@ describe('applySurface', () => { ['msd-planner', []], ]); - const hermesKind = { kind: 'skills', destSubpath: 'skills/msd', prefix: '', stage: () => stagedDir }; - _syncMsdDir(stagedDir, destDir, hermesKind, manifest); + const emptyPrefixKind = { kind: 'skills', destSubpath: 'skills/msd', prefix: '', stage: () => stagedDir }; + _syncMsdDir(stagedDir, destDir, emptyPrefixKind, manifest); assert.ok( fs.existsSync(path.join(destDir, 'msd-executor', 'SKILL.md')), @@ -349,10 +349,10 @@ describe('applySurface', () => { ); }); - test('_syncMsdDir skills kind (hermes): preserves non-MSD user dir under skills/msd/ when kindPrefix is empty', (t) => { + test('_syncMsdDir skills kind (empty prefix): preserves non-MSD user dir under skills/msd/ when kindPrefix is empty', (t) => { const { _syncMsdDir } = require('../msd-core/bin/lib/surface.cjs'); - const base = createTempDir('msd-surface-hermes-'); + const base = createTempDir('msd-surface-empty-prefix-'); t.after(() => cleanup(base)); const stagedDir = path.join(base, 'staged'); const destDir = path.join(base, 'dest'); @@ -366,10 +366,10 @@ describe('applySurface', () => { fs.mkdirSync(userDir, { recursive: true }); fs.writeFileSync(path.join(userDir, 'SKILL.md'), '# user custom\n', 'utf8'); - const hermesKind = { kind: 'skills', destSubpath: 'skills/msd', prefix: '', stage: () => stagedDir }; - _syncMsdDir(stagedDir, destDir, hermesKind); + const emptyPrefixKind = { kind: 'skills', destSubpath: 'skills/msd', prefix: '', stage: () => stagedDir }; + _syncMsdDir(stagedDir, destDir, emptyPrefixKind); - assert.ok(fs.existsSync(userDir), 'user-custom-skill dir must be preserved when kindPrefix is empty (Hermes)'); + assert.ok(fs.existsSync(userDir), 'user-custom-skill dir must be preserved when kindPrefix is empty'); assert.ok(fs.existsSync(path.join(destDir, stem1, 'SKILL.md')), 'MSD help/SKILL.md must be copied'); }); @@ -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 at local scope: no skills kind + if (!skillsKind) return; // e.g. a local scope with no skills kind if (typeof skillsKind.home === 'string' && skillsKind.home !== '') { discriminatingRuntimes++; @@ -1535,8 +1535,8 @@ describe('codex skills-kind destination: home override (#2911)', () => { // installRuntimeArtifacts, and createRuntimeArtifactUninstallPlan — all three of // which honor `skillsKindEntry.home ?? `. This writer originally did // not, and would silently reproduce the #2911 duplicate-tree symptom the moment -// any combined-family runtime (opencode, kilo) gains a `home` override. Neither -// declares one today, so this test exercises the REAL production code path +// any combined-family runtime (opencode) gains a `home` override. It does not +// declare one today, so this test exercises the REAL production code path // (installOpencodeFamilySkills, via the module-ref call convention documented at // src/install-engine.cts:37-38) under a synthetic `home` override injected by // monkeypatching resolveRuntimeArtifactLayout's shared module export — the same diff --git a/tests/runtime-config-adapter-registry.test.cjs b/tests/runtime-config-adapter-registry.test.cjs index e085bef6c..0c99fe7f8 100644 --- a/tests/runtime-config-adapter-registry.test.cjs +++ b/tests/runtime-config-adapter-registry.test.cjs @@ -134,7 +134,7 @@ describe('writesSharedSettings — descriptor-driven equivalence', () => { }); // --------------------------------------------------------------------------- -// Test 4: finishPermissionWriter — opencode/kilo are non-null, the rest null. +// Test 4: finishPermissionWriter — opencode/antigravity are non-null, the rest null. // Spot-check the two non-null writers (stable curated values) plus the // descriptor-derived null set. // --------------------------------------------------------------------------- diff --git a/tests/runtime-converters.test.cjs b/tests/runtime-converters.test.cjs index 539278147..188e603c9 100644 --- a/tests/runtime-converters.test.cjs +++ b/tests/runtime-converters.test.cjs @@ -341,7 +341,7 @@ describe('neutralizeAgentReferences', () => { test('uses different instruction file per runtime', () => { const input = 'Read CLAUDE.md for instructions.'; assert.ok(neutralizeAgentReferences(input, 'GEMINI.md').includes('GEMINI.md')); - assert.ok(neutralizeAgentReferences(input, 'copilot-instructions.md').includes('copilot-instructions.md')); + assert.ok(neutralizeAgentReferences(input, 'OTHER-INSTRUCTIONS.md').includes('OTHER-INSTRUCTIONS.md')); assert.ok(neutralizeAgentReferences(input, 'AGENTS.md').includes('AGENTS.md')); }); @@ -564,7 +564,7 @@ describe('feat-1173: stageAgentsForRuntimeWithConverter', () => { const calls = []; const converter = (content) => { calls.push(content); - return content.replace('~/.claude/', '~/.copilot/'); + return content.replace('~/.claude/', '~/.opencode/'); }; const resolvedProfile = { name: 'full', skills: '*', agents: new Set() }; @@ -574,11 +574,11 @@ describe('feat-1173: stageAgentsForRuntimeWithConverter', () => { const stagedFiles = fs.readdirSync(stagedDir).sort(); assert.deepStrictEqual(stagedFiles, ['msd-executor.md', 'msd-planner.md']); - // Converter replaced ~/.claude/ with ~/.copilot/ in all staged files + // Converter replaced ~/.claude/ with ~/.opencode/ in all staged files for (const file of stagedFiles) { const content = fs.readFileSync(path.join(stagedDir, file), 'utf8'); assert.ok(!content.includes('~/.claude/'), `${file}: converter must have replaced ~/.claude/`); - assert.ok(content.includes('~/.copilot/'), `${file}: converter must have injected ~/.copilot/`); + assert.ok(content.includes('~/.opencode/'), `${file}: converter must have injected ~/.opencode/`); } }); @@ -1894,7 +1894,7 @@ test('manager.md and autonomous.md no longer contain old "not claude" background * description: [BETA] Offload plan phase to Claude Code's ultraplan… * * YAML 1.2 treats a leading `[` as the start of a flow sequence, so any - * downstream parser (gh-copilot, JetBrains' kit, etc.) fails with + * downstream parser (JetBrains' kit, etc.) fails with * "Unexpected scalar at node end". The Antigravity * skill+agent converters in `bin/install.js` re-emit the description * unquoted; the Claude variant `yamlQuote(...)`s it. Bring the others diff --git a/tests/runtime-homes-descriptor-drive.test.cjs b/tests/runtime-homes-descriptor-drive.test.cjs index dfb3e5bed..d5807a0a7 100644 --- a/tests/runtime-homes-descriptor-drive.test.cjs +++ b/tests/runtime-homes-descriptor-drive.test.cjs @@ -159,8 +159,8 @@ describe('descriptor-driven equivalence: tilde expansion in env overrides', () = // // `expandTilde` used to hardcode `os.homedir()` and ignore the `home` that // `resolveConfigHomeFromDescriptor` had already resolved from `opts.home`. -// Every configHome.env override (claude's CLAUDE_CONFIG_DIR, pi's -// PI_CODING_AGENT_DIR, antigravity, windsurf, ...) routes a tilde-prefixed +// Every configHome.env override (claude's CLAUDE_CONFIG_DIR, +// antigravity, ...) routes a tilde-prefixed // value through this seam. A caller that injects a sandbox `home` — exactly // what hermetic tests do to keep installs inside a temp dir — silently got // the developer's REAL home directory back instead, both a correctness bug @@ -206,7 +206,7 @@ describe('expandTilde honors an injected opts.home (regression)', () => { // ── #3023 review finding 1: whitespace-only env override must fall back ────── // // expandTilde's old call sites gated on a bare `if (val)`, which is falsy -// only for `''`. A whitespace-only value (e.g. `PI_CODING_AGENT_DIR=' '`, +// only for `''`. A whitespace-only value (e.g. `CLAUDE_CONFIG_DIR=' '`, // which a broken shell template can produce when a substitution is blank but // still quoted) passed the truthy check and resolved to the literal // three-space string instead of falling back to the descriptor default. The @@ -214,7 +214,7 @@ describe('expandTilde honors an injected opts.home (regression)', () => { // resolveConfigHomeFromDescriptor on `hasNonBlankOverride` (real string, at // least one non-whitespace char) instead of bare truthiness — covering // dot-home, dot-home-nested, all three xdg steps, and generic-agents-root -// alike (same class, same fix, not just pi's branch). +// alike (same class, same fix). // // Leading/trailing whitespace on an otherwise non-blank value is deliberately // NOT trimmed (see hasNonBlankOverride's doc comment in runtime-homes.cts): @@ -315,7 +315,7 @@ describe('#3023 review finding 1: whitespace-only env override falls back to def // ── GOLDEN XDG SCENARIOS ────────────────────────────────────────────────────── -describe('descriptor-driven equivalence: xdg runtimes (opencode, kilo)', () => { +describe('descriptor-driven equivalence: xdg runtimes (opencode)', () => { // opencode test('opencode: OPENCODE_CONFIG (file-path) → dirname', () => { const saved = clearAllEnvKeys(); @@ -810,8 +810,6 @@ describe('descriptor-driven parity: 13 non-probe registry runtimes × no-env-var // // Fix introduces msd-core/bin/lib/runtime-homes.cjs with first-class // support for every supported runtime, including: -// - hermes: nested skills/msd// layout (#2841) -// - cline: rules-based, returns null (no skills directory) // - CLAUDE_CONFIG_DIR env var for Claude (was missing) // - All other runtime-specific env vars @@ -953,7 +951,7 @@ describe('bug #3126: runtime-homes getGlobalSkillDir', () => { }); }); -describe('getGlobalConfigDir — explicitDir override and opencode/kilo file-path precedence', () => { +describe('getGlobalConfigDir — explicitDir override and opencode file-path precedence', () => { // ── explicitDir override ────────────────────────────────────────────────── test('explicitDir absolute path is returned as-is (claude)', () => { assert.strictEqual(String(getGlobalConfigDir('claude', '/tmp/x')).replace(/\\/g, '/'), '/tmp/x'); @@ -1107,5 +1105,3 @@ describe('bug #3126: buildAgentSkillsBlock resolves the agent-skills path per ru }); }); } - -// ── resolveKimiHooksTomlDir: per-runtime hooks root (#2755) ────────────────── diff --git a/tests/runtime-label-policy.test.cjs b/tests/runtime-label-policy.test.cjs index 1ca207a5c..567b3b27a 100644 --- a/tests/runtime-label-policy.test.cjs +++ b/tests/runtime-label-policy.test.cjs @@ -7,7 +7,7 @@ * * Because 1.7.0 (ADR-1016 / ADR-1239) makes runtimes pluggable data, the label * table is CURATED (a runtime id → short label mapping that cannot be derived - * from the id alone, e.g. "Claude Code", "Qwen Code", "ZCode"). This test + * from the id alone, e.g. "Claude Code", "ZCode"). This test * enforces the COVERAGE CONTRACT rather than a frozen per-runtime snapshot: * * - every runtime in the capability registry MUST resolve to a distinct, @@ -21,8 +21,7 @@ * snapshot here. * * Voice: these SHORT UI labels are intentionally distinct from the descriptor - * `title` (the long product name). One prior-chain inconsistency is resolved - * by the canonical map: cline → 'Cline'. + * `title` (the long product name). * * ADR-1239 Phase B (#1679). Behavioral tests only: assert on returned values. */ diff --git a/tests/settings-integrations.test.cjs b/tests/settings-integrations.test.cjs index ae8de0d9e..8c1028f38 100644 --- a/tests/settings-integrations.test.cjs +++ b/tests/settings-integrations.test.cjs @@ -218,7 +218,7 @@ describe('#3651 workflow — review.models settable-set rule', () => { // Lanes whose capability declares modelConfigKey: null — the workflow used // to walk users into writing these keys, and config-set rejects them. // `cursor` gained a real modelConfigKey (#3653) and is no longer keyless. - for (const keyless of ['qwen', 'coderabbit']) { + for (const keyless of ['coderabbit']) { assert.ok( !isValidConfigKey(`review.models.${keyless}`), `review.models.${keyless} must not validate (lane declares no modelConfigKey)` @@ -234,7 +234,7 @@ describe('#3651 workflow — review.models settable-set rule', () => { const tmp = createTempProject(); t.after(() => cleanup(tmp)); runMsdTools(['config-ensure-section'], tmp); - const r = runMsdTools(['config-set', 'review.models.qwen', 'qwen-model'], tmp); + const r = runMsdTools(['config-set', 'review.models.coderabbit', 'coderabbit-model'], tmp); assert.ok( !r.success, 'config-set must reject a keyless lane — the exact error the old workflow steered users into' diff --git a/tests/skill-manifest.test.cjs b/tests/skill-manifest.test.cjs index 5099c12dd..1840dfda8 100644 --- a/tests/skill-manifest.test.cjs +++ b/tests/skill-manifest.test.cjs @@ -152,7 +152,7 @@ describe('skill-manifest', () => { // bug-929: nested layout discovery test('bug-929: discovers concrete skills nested under msd-ns-* routers', () => { // Mirrors the on-disk shape that stageSkillsForRuntimeAsSkills emits for - // cline/qwen/hermes/augment/trae/antigravity when nested=true: + // antigravity when nested=true: // /msd-ns-workflow/SKILL.md — router (top-level) // /msd-ns-workflow/skills/plan/SKILL.md — concrete // /msd-ns-workflow/skills/execute/SKILL.md — concrete diff --git a/tests/slash-command-namespace.test.cjs b/tests/slash-command-namespace.test.cjs index ae01a6456..dd989c01d 100644 --- a/tests/slash-command-namespace.test.cjs +++ b/tests/slash-command-namespace.test.cjs @@ -226,18 +226,16 @@ describe('slash-command namespace invariant (#3443)', () => { /** * Regression for #3677 — installed agent bodies leak `/msd:` colon refs - * for Claude / Qwen / Hermes (unroutable since #2808). + * for Claude (unroutable since #2808). * * Root cause: `bin/install.js` agent install loop (around line 8350-8447) * reads each agent .md, runs runtime-specific transforms via * `convertClaudeAgentToXAgent()`, then writes the result. For: - * - Self-converting runtimes (Copilot/Codex/Cursor/Windsurf/Augment/Trae/ - * Codebuddy/Cline/Antigravity/Opencode/Kilo): their converters handle + * - Self-converting runtimes (Codex/Cursor/Antigravity/Opencode): their converters handle * namespace themselves. * - Gemini: intentionally uses colon namespace. - * - Claude-default / Qwen / Hermes: register hyphen-form `name:` (#2808) - * but copy bodies verbatim (Qwen/Hermes do branding-only swaps; Claude - * does no namespace work). The retired `/msd:` colon refs leak. + * - Claude-default: registers hyphen-form `name:` (#2808) + * but copies bodies verbatim (Claude does no namespace work). The retired `/msd:` colon refs leak. * * Sibling fixes #3583 (SKILL.md, via #3629) and #3584 (runtime emissions, via * #3606) covered the other two surfaces. This is the agent-body surface. diff --git a/tests/test-timings.json b/tests/test-timings.json index 6ab4aa7b9..60a05a5c5 100644 --- a/tests/test-timings.json +++ b/tests/test-timings.json @@ -7,7 +7,7 @@ "test-events-linux-node24.jsonl", "manual: codex-config.test.cjs/codex-config-hooks.test.cjs re-measured locally (node --test --test-reporter=tap, max of 3 runs) after next split codex-config.test.cjs (#4139/#4540) — the old 127783ms entry predated the split and codex-config-hooks.test.cjs, which now holds the #3245/#4249 install()-pipeline blocks, had no entry at all (PR #4249)." ], - "file_count": 771, + "file_count": 657, "timings": { "active-workstream-store.test.cjs": 81, "active-workstream-store.unit.test.cjs": 364, @@ -54,8 +54,6 @@ "atomic-write-coverage.test.cjs": 47, "audit-command-cutover.test.cjs": 6076, "audit-fix-command.test.cjs": 179, - "augment-conversion.test.cjs": 77, - "augment-upgrades.test.cjs": 10336, "autonomous-allowed-tools.test.cjs": 41, "autonomous-converge.test.cjs": 524, "autonomous-decomposition.test.cjs": 43, @@ -111,12 +109,6 @@ "claude-skills-migration.test.cjs": 201, "cleanup-branch-pruning.test.cjs": 190, "cli-exit.test.cjs": 1316, - "cline-beforetool-upgrade.test.cjs": 72, - "cline-dispatch-degradation.test.cjs": 40, - "cline-imperative-reference.test.cjs": 76, - "cline-install.test.cjs": 3511, - "cline-model-override-upgrade.test.cjs": 51, - "cline-support.test.cjs": 82, "clock-seam.test.cjs": 10986, "close-phase-todos-padded-resolves.test.cjs": 798, "close-phase-todos-stage-deletion.test.cjs": 32, @@ -128,8 +120,6 @@ "code-review-summary-parser.test.cjs": 85, "code-review.test.cjs": 5022, "codebase-mapper-date-restamp.test.cjs": 138, - "codebuddy-install.test.cjs": 3763, - "codebuddy-upgrades.test.cjs": 2932, "codex-config-hooks.test.cjs": 3454, "codex-config.test.cjs": 189, "codex-declarative-reference.test.cjs": 205, @@ -164,8 +154,6 @@ "contributor-standards.test.cjs": 169, "conventional-title.property.test.cjs": 229, "conventional-title.test.cjs": 261, - "copilot-install.test.cjs": 55244, - "copilot-upgrades.test.cjs": 6069, "core-utils.test.cjs": 270, "coverage-metadata-parser.test.cjs": 10242, "coverage-uat-routing.test.cjs": 2153, @@ -187,10 +175,6 @@ "debugger-semantic-recall.test.cjs": 106, "decisions.test.cjs": 13530, "declarative-reference-antigravity.test.cjs": 1582, - "declarative-reference-augment.test.cjs": 2079, - "declarative-reference-codebuddy.test.cjs": 1824, - "declarative-reference-copilot.test.cjs": 1492, - "declarative-reference-windsurf.test.cjs": 2161, "declarative-reference-zcode.test.cjs": 2119, "defaults-json-fallback.test.cjs": 188, "derive-progress.property.test.cjs": 396, @@ -211,8 +195,6 @@ "effort-surface-axis.test.cjs": 5485, "effort-sync-installed-runtime.test.cjs": 1763, "emitted-attribution.test.cjs": 189330, - "emitted-caps-gate.test.cjs": 2351, - "emitted-caps.test.cjs": 231, "emitted-provenance.test.cjs": 38803, "emitted-sizes.test.cjs": 3269, "ensure-runtime-build.test.cjs": 7800, @@ -245,42 +227,6 @@ "federated-config-loadconfig.test.cjs": 620, "federated-config.test.cjs": 261, "few-shot-calibration.test.cjs": 87, - "fix-1700-spike-manifest-idea-scoping.test.cjs": 107, - "fix-1941-quick-worktree-stale-base.test.cjs": 68, - "fix-2068-resolve-execution-dynamic-routing.test.cjs": 5270, - "fix-2136-clock-local-today.test.cjs": 692, - "fix-2138-ship-note-lost-on-merge.test.cjs": 70, - "fix-2194-review-timeout-guidance.test.cjs": 49, - "fix-2196-debug-agent-handoff.test.cjs": 35, - "fix-2257-debug-nonterminal-resume.test.cjs": 76, - "fix-2284-hermes-agent-delegate-task-projection.test.cjs": 9484, - "fix-2285-claude-orchestration-wiring.test.cjs": 4486, - "fix-2287-deferred-items-reader.test.cjs": 2890, - "fix-2289-context-monitor-event-allowlist.test.cjs": 5841, - "fix-2297-resolve-model-ids-runtime-scoping.test.cjs": 354, - "fix-2337-add-todo-severity.test.cjs": 61, - "fix-2358-review-temp-path-scoping.test.cjs": 93, - "fix-2494-review-claude-gemini-empty-guard.test.cjs": 119, - "fix-2587-cursor-hook-workspace-roots.test.cjs": 4678, - "fix-2589-workflow-jq-dependency.test.cjs": 120, - "fix-2590-workflow-script-contract.test.cjs": 1990, - "fix-2598-opencode-background-dispatch.test.cjs": 150, - "fix-2603-kimi-code-host-matrix.test.cjs": 86, - "fix-2605-review-local-server-empty-guard.test.cjs": 168, - "fix-2608-commit-staging-failure.test.cjs": 9605, - "fix-2615-effortsurface-matrix-parity.test.cjs": 188, - "fix-2649-diagnose-issues-worktree-stale-base.test.cjs": 67, - "fix-2650-plan-phase-stall-detection.test.cjs": 44503, - "fix-2657-untrack-compiled-artifacts.test.cjs": 381, - "fix-2658-trae-runtime-detection-and-instruction-path.test.cjs": 2083, - "fix-2717-cursor-windsurf-codex-commonjs-marker.test.cjs": 9093, - "fix-2766-audit-uat-archived-and-table-shapes.test.cjs": 5786, - "fix-2794-review-qwen-empty-guard.test.cjs": 83, - "fix-2830-halted-plan-dependents.test.cjs": 11301, - "fix-2847-gap-closure-frontmatter.test.cjs": 62, - "fix-2855-phase-locator-workstream-archive-scope.test.cjs": 132, - "fix-3045-cursor-subagent-isolation.test.cjs": 14459, - "fix-3045-dispatch-isolation-resolver.test.cjs": 7355, "fixture-builder.test.cjs": 324, "forensics.test.cjs": 164, "format-github-release-notes.test.cjs": 69, @@ -329,9 +275,6 @@ "health-validation.test.cjs": 17036, "helpers-cleanup.test.cjs": 110, "helpers-process-isolation.test.cjs": 37, - "hermes-dispatch-upgrade.test.cjs": 33, - "hermes-imperative-reference.test.cjs": 75, - "hermes-skills-migration.test.cjs": 121, "hook-bus.test.cjs": 50, "hook-validation.test.cjs": 84, "hooks-opt-in.test.cjs": 8614, @@ -358,7 +301,6 @@ "installer-migration-authoring.test.cjs": 136, "installer-migration-config-root-marker.test.cjs": 94, "installer-migration-install.integration.test.cjs": 74165, - "installer-migration-pi-extension-ext.test.cjs": 61, "installer-migration-prune-stale-pristine.test.cjs": 82, "installer-migration-rename-msd-core.test.cjs": 265, "installer-migration-report.test.cjs": 339, @@ -370,53 +312,8 @@ "inventory-nested-families.test.cjs": 192, "io.test.cjs": 2567, "ios-scaffold-safety.test.cjs": 71, - "issue-1575-agent-descriptor-parity.test.cjs": 4530, - "issue-1855-marketplace-manifest.test.cjs": 309, - "issue-2045-third-party-skills-surface.test.cjs": 3282, - "issue-2517-runtime-aware-profiles.test.cjs": 2761, - "issue-2639-codex-toml-neutralization.test.cjs": 85, - "issue-2695-codex-hook-set.test.cjs": 27204, - "issue-2701-nul-corrupted-validators.test.cjs": 6250, - "issue-2762-plan-reviews-chunked.test.cjs": 37, - "issue-2765-brace-expansion-lockfile.test.cjs": 1918, - "issue-2771-advisor-subagent-type.test.cjs": 43, - "issue-2772-discuss-phase-text-inconsistencies.test.cjs": 74, - "issue-2828-flat-roadmap-total-phases.test.cjs": 445, - "issue-2834-codex-install-model-ordering.test.cjs": 30, - "issue-2927-reviewer-lane-overlay-invocation.test.cjs": 4610, - "issue-2939-dispatch-flatten-maxdepth.test.cjs": 62, - "issue-2940-codex-config-merge-trailing.test.cjs": 57, - "issue-2945-phase-complete-checkbox-rollback.test.cjs": 2489, - "issue-2949-phase-complete-stage3-sentinel.test.cjs": 2857, - "issue-2977-frontmatter-bom.test.cjs": 202, - "issue-429-comment-text-gate.test.cjs": 1204, - "issue-498-identity-drift-lint.test.cjs": 371, - "issue-498-package-identity.test.cjs": 127, - "issue-498-update-backup-runtime-dir.test.cjs": 30, - "issue-498-update-context.test.cjs": 608, - "issue-57-runtime-install-no-drift.test.cjs": 66, - "issue-607-cache-lineage.test.cjs": 90, - "issue-607-installer-dry-run.install.test.cjs": 959, - "issue-607-legacy-cleanup.test.cjs": 259, - "issue-69-surface-keeps-nested.test.cjs": 402, - "issue-766-plugin-manifest.test.cjs": 412, - "issue-787-cline-hooks-agents.test.cjs": 10063, - "issue-815-update-next-channel.test.cjs": 51, - "issue-844-manifest-version-sync.test.cjs": 269, "issue-dedupe.test.cjs": 357, "issue-version-gate.test.cjs": 286, - "kilo-imperative-reference.test.cjs": 118, - "kilo-upgrades.test.cjs": 15771, - "kimi-agent-converter.test.cjs": 10214, - "kimi-guard-normalization-parity.test.cjs": 43, - "kimi-guard-typed-payload-reads.test.cjs": 172, - "kimi-imperative-reference.test.cjs": 88, - "kimi-normalize-payload.property.test.cjs": 614, - "kimi-payload-field-shadowing.security.test.cjs": 1643, - "kimi-skill-converter.test.cjs": 34, - "kimi-tool-mapping.test.cjs": 35, - "kimi-upgrades.test.cjs": 28880, - "kimi-variant-disambiguation.test.cjs": 3560, "learnings.test.cjs": 2925, "lint-docs-command-form.test.cjs": 2986, "lint-docs-required.test.cjs": 210, @@ -527,9 +424,6 @@ "phase6-planning-capabilities.test.cjs": 72, "phase6-review-capabilities.test.cjs": 55, "phases-command-router.test.cjs": 1972, - "pi-extension-reachability.test.cjs": 1686, - "pi-imperative-reference.test.cjs": 65, - "pi-upgrades.test.cjs": 309, "pick-flag.test.cjs": 4706, "plan-bounce.test.cjs": 1658, "plan-phase-drift-guard.test.cjs": 487, @@ -586,9 +480,6 @@ "quick-commit-boundary.test.cjs": 73, "quick-research.test.cjs": 4873, "quick-session-management.test.cjs": 98, - "qwen-imperative-reference.test.cjs": 330, - "qwen-skills-migration.test.cjs": 1047, - "qwen-upgrades.test.cjs": 8100, "reachability-check.test.cjs": 44, "read-guard.test.cjs": 5618, "read-injection-scanner.property.test.cjs": 22710, @@ -719,8 +610,6 @@ "todos-done-rename-guard.test.cjs": 290, "trace-correlation.test.cjs": 54, "tracer-bullet.test.cjs": 2886, - "trae-imperative-reference.test.cjs": 121, - "trae-upgrades.test.cjs": 7465, "transition-verification-gate.test.cjs": 51, "trim-safety.test.cjs": 143, "tsconfig-noemit.test.cjs": 7162, @@ -755,9 +644,6 @@ "vscode-lm-tools.test.cjs": 2453, "vscode-subagent-dispatch.test.cjs": 2682, "windows-robustness.test.cjs": 238, - "windsurf-conversion.test.cjs": 203, - "windsurf-hooks-bridge.test.cjs": 4388, - "windsurf-install.test.cjs": 121, "workflow-compat.test.cjs": 296, "workflow-fragments-emission.install.test.cjs": 113902, "workflow-fragments.property.test.cjs": 281, diff --git a/tests/update-context.test.cjs b/tests/update-context.test.cjs index 9c167d8ca..2dd2b5391 100644 --- a/tests/update-context.test.cjs +++ b/tests/update-context.test.cjs @@ -148,10 +148,10 @@ describe('resolveUpdateContext: runtime probing + env overrides', () => { const fs = fakeFs({ [ver(custom)]: '1.41.0\n', [marker(custom)]: 'x' }); const r = resolveUpdateContext({ home: HOME, cwd: CWD, env: {}, fs, - preferredConfigDir: custom, preferredRuntime: 'kilo', + preferredConfigDir: custom, preferredRuntime: 'opencode', }); assert.equal(r.scope, 'GLOBAL'); - assert.equal(r.runtime, 'kilo'); + assert.equal(r.runtime, 'opencode'); assert.ok(sameDir(r.msdDir, custom), `msdDir was ${r.msdDir}`); assert.equal(r.installedVersion, '1.41.0'); }); @@ -269,7 +269,7 @@ describe('msd-tools update-context (CLI): emits the JSON contract', () => { nodeFs.writeFileSync(path.join(tmp, 'msd-core', 'VERSION'), '1.42.0\n'); nodeFs.writeFileSync(path.join(tmp, 'msd-core', 'workflows', 'update.md'), 'x'); const r = runNode( - [MSD_TOOLS, 'update-context', '--config-dir', tmp, '--runtime', 'kilo', '--json'], + [MSD_TOOLS, 'update-context', '--config-dir', tmp, '--runtime', 'opencode', '--json'], { env: { ...process.env, MSD_TEST_MODE: '1' }, timeoutMs: PROBE_TIMEOUT_MS }, ); throwIfFailed(r, 'msd-tools update-context --json'); @@ -277,7 +277,7 @@ describe('msd-tools update-context (CLI): emits the JSON contract', () => { assert.deepEqual(Object.keys(ctx).sort(), ['installedVersion', 'msdDir', 'runtime', 'scope']); assert.equal(ctx.installedVersion, '1.42.0'); assert.equal(ctx.scope, 'GLOBAL'); - assert.equal(ctx.runtime, 'kilo'); + assert.equal(ctx.runtime, 'opencode'); } finally { cleanup(tmp); } @@ -318,7 +318,7 @@ describe('resolveUpdateContext: parity with the old inline bash (adversarial-rev const custom = '/opt/msd-partial'; const fs = fakeFs({ [ver(custom)]: '1.41.0\n' }); // marker absent const r = resolveUpdateContext({ - home: HOME, cwd: CWD, env: {}, fs, preferredConfigDir: custom, preferredRuntime: 'kilo', + home: HOME, cwd: CWD, env: {}, fs, preferredConfigDir: custom, preferredRuntime: 'opencode', }); assert.equal(r.installedVersion, '0.0.0', 'VERSION-only fast path must not be trusted'); assert.equal(r.scope, 'GLOBAL'); diff --git a/tests/user-artifact-staging.test.cjs b/tests/user-artifact-staging.test.cjs index e5d57d0ff..3517f007e 100644 --- a/tests/user-artifact-staging.test.cjs +++ b/tests/user-artifact-staging.test.cjs @@ -7,7 +7,7 @@ * * Confinement rows (E1-E5) live in tests/install-write-confinement.test.cjs * (this suite's own file-count ratchet keeps them there — see 50-test-matrix.md - * "Suites"). Call-site integration (Hermes/Claude dev-preferences migration, + * "Suites"). Call-site integration (Claude dev-preferences migration, * the mainline msd-core copy, and C7 production-reachability) lives in * tests/install-runtime-artifacts.test.cjs. * diff --git a/tests/workflow-fragments-emission.install.test.cjs b/tests/workflow-fragments-emission.install.test.cjs index 390604bd7..73b5ec01f 100644 --- a/tests/workflow-fragments-emission.install.test.cjs +++ b/tests/workflow-fragments-emission.install.test.cjs @@ -290,7 +290,7 @@ test('noSectionMarkerLeaksIntoEmittedArtifacts', (t) => { // #4570: assert against the real installed workflow bytes for every runtime, // after composition and runtime conversion. The background parameter itself - // is runtime vocabulary (`run_in_background` becomes `background` on Hermes), + // is runtime vocabulary (it can be renamed per runtime), // so parity is pinned on the shared gate and its two semantic branches. for (const relativePath of [PLAN_PHASE_REL, CHUNKED_PLAN_REL, STALL_HELPERS_REL]) { const installedPath = path.join(configDir, relativePath); diff --git a/tests/workflow-guard-registration.test.cjs b/tests/workflow-guard-registration.test.cjs index a02f4cbef..200e03391 100644 --- a/tests/workflow-guard-registration.test.cjs +++ b/tests/workflow-guard-registration.test.cjs @@ -43,12 +43,6 @@ const MODULE_OWNED_HOOKS = new Set([ 'msd-cursor-stop.js', 'msd-cursor-subagent-start.js', 'msd-cursor-subagent-stop.js', - // Windsurf/Cascade blocking hooks — registered by writeWindsurfHooksJson via the - // WINDSURF_EVENT_SCRIPT_MAP indirection (src/runtime-hooks-surface.cts), never a - // literal buildHookCommand(..., '', ...) call this source-scan matches. - // Validated behaviorally by tests/windsurf-hooks-bridge.test.cjs. - 'msd-windsurf-pre-write.js', - 'msd-windsurf-pre-command.js', // msd-check-update-worker.js is an implementation detail of msd-check-update.js // (spawned internally via child_process.spawn), never itself registered as a // hook entry point.