fix(3584): address codex-review findings on PR #3606

Codex review surfaced five issues in the initial slash-formatter PR — three MED
and two LOW. All five are addressed:

1. (MED) formatter corrupted argument tails under codex runtime. Splitting on
   the first whitespace and lowercasing only the command token preserves
   path-like arguments (`Map-Codebase --paths C:\\Users\\Me\\Project`) and
   case-sensitive flag values. (`runtime-slash.cjs`)

2. (MED) profile-output `cmdGenerateDevPreferences` emitted a hardcoded
   `command_name: '/gsd-dev-preferences'`. Replaced with
   `formatGsdSlash('dev-preferences', resolveRuntime(cwd))` so the structured
   result honors codex/skills runtime distinction. (`profile-output.cjs`)

3. (MED) `drift.detectDrift` → `buildMessage` called `resolveRuntime(null)`,
   ignoring a project's `.planning/config.json` `runtime` setting when
   `GSD_RUNTIME` env var was absent. Threaded `projectDir` through
   `detectDrift({projectDir})` → `buildMessage(..., projectDir)` →
   `resolveRuntime(projectDir)`. `verify.cmdVerifyCodebaseDrift` now passes
   `cwd` into the detect call. (`drift.cjs`, `verify.cjs`)

4. (LOW) `gsd2-import.buildPreview` had the same env-vs-config issue. Threaded
   `cwd` through `buildPreview(..., projectDir)` for parity. (`gsd2-import.cjs`)

5. (LOW) The codex emitter test only asserted absence of `/gsd:` — a regression
   to `/gsd-` (skills) form in codex output would have passed undetected.
   Added positive assertions that every gsd-referencing fix string under
   `GSD_RUNTIME=codex` contains `$gsd-` and contains neither `/gsd-` nor
   `/gsd:`. Also added formatter unit tests pinning the argument-tail
   preservation contract for both hyphen and codex runtimes.

Full suite: 9365/9365 pass (+2 new). Lint clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-05-15 19:42:48 -04:00
parent 034b47c8d0
commit bdf06c04b0
7 changed files with 63 additions and 20 deletions

View File

@@ -260,7 +260,7 @@ describe('bug-3584: validate context recommendation uses hyphen form', () => {
});
describe('bug-3584: validate health uses formatter for codex runtime too', () => {
test('validate health under codex emits $gsd-<cmd> in fix strings', (t) => {
test('validate health under codex emits $gsd-<cmd> in fix strings (positive assertion)', (t) => {
const tmpDir = createTempDir();
t.after(() => cleanup(tmpDir));
@@ -281,20 +281,30 @@ describe('bug-3584: validate health uses formatter for codex runtime too', () =>
.concat(payload.warnings || [])
.concat(payload.info || []);
const fixesWithSlash = allIssues
// Collect fixes that mention any gsd slash-command form so we can lock
// both the absence of legacy forms AND the presence of the codex shape.
const fixesWithGsdRef = allIssues
.map((i) => i.fix)
.filter((f) => typeof f === 'string' && /gsd-/.test(f));
.filter((f) => typeof f === 'string' && /(?:\$|\/)gsd[-:]/.test(f));
// For codex, hyphen-with-slash form `/gsd-<cmd>` should be replaced by
// shell-var `$gsd-<cmd>`. Skip the assertion if no relevant fix was
// produced (test only asserts the contract when something is emitted).
for (const fix of fixesWithSlash) {
assert.ok(
fixesWithGsdRef.length > 0,
'validate health on a bare tmpdir must produce at least one fix hint referencing a gsd command',
);
for (const fix of fixesWithGsdRef) {
assert.ok(
fix.includes('$gsd-'),
`codex validate health fix must use shell-var $gsd- form, got ${JSON.stringify(fix)}`,
);
assert.ok(
!fix.includes('/gsd:'),
`codex validate health fix must not contain /gsd: colon form, got ${JSON.stringify(fix)}`,
);
// Codex emits $gsd- — any /gsd- in the fix means the formatter wasn't applied.
// (We don't have a hard count guarantee — a fix can also reference a plain url etc.)
assert.ok(
!fix.includes('/gsd-'),
`codex validate health fix must not contain /gsd- (skills) form, got ${JSON.stringify(fix)}`,
);
}
});
});

View File

@@ -148,6 +148,29 @@ describe('formatGsdSlash — runtime-aware slash command formatter', () => {
'/gsd-execute-phase 03',
);
});
test('codex form lowercases only the command token, not the argument tail', () => {
// Regression for codex review finding: a previous implementation
// lowercased the full input including arguments, which would corrupt
// Windows paths and case-sensitive flag values passed as args.
assert.strictEqual(
formatGsdSlash('Map-Codebase --paths C:\\Users\\Me\\Project', 'codex'),
'$gsd-map-codebase --paths C:\\Users\\Me\\Project',
);
assert.strictEqual(
formatGsdSlash('execute-phase 03 --Name FooBar', 'codex'),
'$gsd-execute-phase 03 --Name FooBar',
);
});
test('hyphen form preserves token case (it does not get lowercased)', () => {
// Symmetry with codex: only codex lowercases the token. Hyphen-form
// runtimes preserve whatever case the caller supplied for the token.
assert.strictEqual(
formatGsdSlash('Plan-Phase 03', 'claude'),
'/gsd-Plan-Phase 03',
);
});
});
});