Merge pull request #1574 from open-gsd/fix/1529-gsd-new-project-emits-claude-style-proje
fix(#1529): emit runtime-native instruction file from new-project
This commit is contained in:
5
.changeset/proud-sloths-glide.md
Normal file
5
.changeset/proud-sloths-glide.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
type: Fixed
|
||||||
|
pr: 1574
|
||||||
|
---
|
||||||
|
**OpenCode and other AGENTS-native runtimes now get a root `AGENTS.md` from `/gsd:new-project`** — the workflow hardcoded a codex-only branch that sent every other runtime to `.claude/CLAUDE.md`, a location OpenCode never loads. A shared `getProjectInstructionFile(runtime)` policy (claude→`.claude/CLAUDE.md`, codex/opencode/kilo/kimi→`AGENTS.md`, copilot→`.github/copilot-instructions.md`, antigravity/gemini→`GEMINI.md`) is now the single source of truth consumed by both the new-project workflow and the generate-claude-md path, with a parity test guarding drift.
|
||||||
@@ -638,7 +638,7 @@ async function main() {
|
|||||||
'from-gsd2, frontmatter, gap-analysis, generate-claude-md, generate-claude-profile, ' +
|
'from-gsd2, frontmatter, gap-analysis, generate-claude-md, generate-claude-profile, ' +
|
||||||
'generate-dev-preferences, generate-slug, graphify, history-digest, init, intel, ' +
|
'generate-dev-preferences, generate-slug, graphify, history-digest, init, intel, ' +
|
||||||
'capability, classify-confidence, git, learnings, list-seeds, list-todos, loop, milestone, package-legitimacy, phase, phase-plan-index, phases, profile-questionnaire, ' +
|
'capability, classify-confidence, git, learnings, list-seeds, list-todos, loop, milestone, package-legitimacy, phase, phase-plan-index, phases, profile-questionnaire, ' +
|
||||||
'profile-sample, progress, prompt-budget, requirements, research-plan, research-store, resolve-granularity, resolve-model, roadmap, scaffold, state, ' +
|
'profile-sample, progress, project-instruction-file, prompt-budget, requirements, research-plan, research-store, resolve-granularity, resolve-model, roadmap, scaffold, state, ' +
|
||||||
'task, template, user-story, validate, verify, verify-path-exists, verify-summary, workstream, worktree\n\n' +
|
'task, template, user-story, validate, verify, verify-path-exists, verify-summary, workstream, worktree\n\n' +
|
||||||
'Global flags:\n' +
|
'Global flags:\n' +
|
||||||
' --raw Emit raw output without post-processing\n' +
|
' --raw Emit raw output without post-processing\n' +
|
||||||
@@ -689,6 +689,10 @@ async function main() {
|
|||||||
'worktree', 'prompt-budget',
|
'worktree', 'prompt-budget',
|
||||||
'research-store', 'research-plan', 'package-legitimacy', 'classify-confidence',
|
'research-store', 'research-plan', 'package-legitimacy', 'classify-confidence',
|
||||||
'user-story', // pure string validation — no .planning/ access needed
|
'user-story', // pure string validation — no .planning/ access needed
|
||||||
|
// #1529: pure runtime→filename projection via getProjectInstructionFile; no
|
||||||
|
// .planning/ access needed, and resolving project root would break workflow
|
||||||
|
// invocations that run before .planning/ exists (new-project Step 1).
|
||||||
|
'project-instruction-file',
|
||||||
]);
|
]);
|
||||||
if (!SKIP_ROOT_RESOLUTION.has(command)) {
|
if (!SKIP_ROOT_RESOLUTION.has(command)) {
|
||||||
cwd = findProjectRoot(cwd);
|
cwd = findProjectRoot(cwd);
|
||||||
@@ -1103,6 +1107,29 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'project-instruction-file': {
|
||||||
|
// #1529: pure runtime→filename projection. Backs the
|
||||||
|
// `gsd_run query project-instruction-file --runtime <r>` call in
|
||||||
|
// new-project.md so the bash workflow and profile-output.cjs share one
|
||||||
|
// source of truth (getProjectInstructionFile in runtime-name-policy.cjs).
|
||||||
|
// No SDK bridge — pure local lookup, runs before .planning/ exists.
|
||||||
|
const { getProjectInstructionFile } = require('./lib/runtime-name-policy.cjs');
|
||||||
|
// Parse --runtime <value> (space or = form); default to empty so the
|
||||||
|
// safe AGENTS.md cross-agent default applies.
|
||||||
|
const pifArgs = args.slice(1);
|
||||||
|
let pifRuntime = '';
|
||||||
|
for (let i = 0; i < pifArgs.length; i++) {
|
||||||
|
const a = pifArgs[i];
|
||||||
|
if (a === '--runtime' && pifArgs[i + 1] !== undefined) { pifRuntime = pifArgs[++i]; continue; }
|
||||||
|
if (a.startsWith('--runtime=')) { pifRuntime = a.slice('--runtime='.length); continue; }
|
||||||
|
// First positional that isn't a flag also works (lenient); otherwise ignore unknown flags.
|
||||||
|
if (!a.startsWith('-') && !pifRuntime) { pifRuntime = a; }
|
||||||
|
}
|
||||||
|
const filename = getProjectInstructionFile(pifRuntime);
|
||||||
|
process.stdout.write(filename + '\n');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case 'list-todos': {
|
case 'list-todos': {
|
||||||
commands.cmdListTodos(cwd, args[1], raw);
|
commands.cmdListTodos(cwd, args[1], raw);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -109,9 +109,9 @@ elif [ -n "$OPENCODE_CONFIG_DIR" ] || [ -n "$OPENCODE_CONFIG" ]; then RUNTIME="o
|
|||||||
else RUNTIME="claude"; fi
|
else RUNTIME="claude"; fi
|
||||||
```
|
```
|
||||||
|
|
||||||
Set the instruction file variable:
|
Set the instruction file variable via the shared runtime-name policy adapter (`gsd-tools query project-instruction-file`, backed by `getProjectInstructionFile` in `runtime-name-policy.cjs` — the single source of truth shared with `profile-output.cjs`):
|
||||||
```bash
|
```bash
|
||||||
if [ "$RUNTIME" = "codex" ]; then INSTRUCTION_FILE="AGENTS.md"; else INSTRUCTION_FILE=".claude/CLAUDE.md"; fi
|
INSTRUCTION_FILE=$(gsd_run query project-instruction-file --runtime "$RUNTIME")
|
||||||
```
|
```
|
||||||
|
|
||||||
All subsequent references to the project instruction file use `$INSTRUCTION_FILE`.
|
All subsequent references to the project instruction file use `$INSTRUCTION_FILE`.
|
||||||
@@ -1533,7 +1533,7 @@ PHASE1_HAS_UI=$(echo "$PHASE1_SECTION" | grep -qi "UI hint.*yes" && echo "true"
|
|||||||
- `.planning/REQUIREMENTS.md`
|
- `.planning/REQUIREMENTS.md`
|
||||||
- `.planning/ROADMAP.md`
|
- `.planning/ROADMAP.md`
|
||||||
- `.planning/STATE.md`
|
- `.planning/STATE.md`
|
||||||
- `$INSTRUCTION_FILE` (`AGENTS.md` for Codex, `.claude/CLAUDE.md` for all other runtimes)
|
- `$INSTRUCTION_FILE` (runtime-derived via the shared `getProjectInstructionFile` policy: `AGENTS.md` for codex/opencode/kilo/kimi, `.github/copilot-instructions.md` for copilot, `GEMINI.md` for gemini/antigravity, `.claude/CLAUDE.md` for claude)
|
||||||
|
|
||||||
</output>
|
</output>
|
||||||
|
|
||||||
@@ -1555,7 +1555,7 @@ PHASE1_HAS_UI=$(echo "$PHASE1_SECTION" | grep -qi "UI hint.*yes" && echo "true"
|
|||||||
- [ ] ROADMAP.md created with phases, requirement mappings, success criteria
|
- [ ] ROADMAP.md created with phases, requirement mappings, success criteria
|
||||||
- [ ] STATE.md initialized
|
- [ ] STATE.md initialized
|
||||||
- [ ] REQUIREMENTS.md traceability updated
|
- [ ] REQUIREMENTS.md traceability updated
|
||||||
- [ ] `$INSTRUCTION_FILE` generated with GSD workflow guidance (AGENTS.md for Codex, `.claude/CLAUDE.md` otherwise; an existing hand-crafted file without GSD markers is left untouched unless `--force`)
|
- [ ] `$INSTRUCTION_FILE` generated with GSD workflow guidance (runtime-derived via the shared `getProjectInstructionFile` policy — `AGENTS.md` for codex/opencode/kilo/kimi, `.github/copilot-instructions.md` for copilot, `GEMINI.md` for gemini/antigravity, `.claude/CLAUDE.md` for claude; an existing hand-crafted file without GSD markers is left untouched unless `--force`)
|
||||||
- [ ] User knows next step is `/gsd:discuss-phase 1`
|
- [ ] User knows next step is `/gsd:discuss-phase 1`
|
||||||
|
|
||||||
**Atomic commits:** Each phase commits its artifacts immediately. If context is lost, artifacts persist.
|
**Atomic commits:** Each phase commits its artifacts immediately. If context is lost, artifacts persist.
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ const { loadConfig } = configLoader;
|
|||||||
import { platformReadSync as safeReadFile, platformWriteSync, platformEnsureDir } from './shell-command-projection.cjs';
|
import { platformReadSync as safeReadFile, platformWriteSync, platformEnsureDir } from './shell-command-projection.cjs';
|
||||||
import { getGlobalSkillDir, getGlobalConfigDir } from './runtime-homes.cjs';
|
import { getGlobalSkillDir, getGlobalConfigDir } from './runtime-homes.cjs';
|
||||||
import { formatGsdSlash, resolveRuntime } from './runtime-slash.cjs';
|
import { formatGsdSlash, resolveRuntime } from './runtime-slash.cjs';
|
||||||
import { resolveRuntimeNameFromCandidates } from './runtime-name-policy.cjs';
|
import { resolveRuntimeNameFromCandidates, getProjectInstructionFile } from './runtime-name-policy.cjs';
|
||||||
|
|
||||||
// ─── Types ────────────────────────────────────────────────────────────────────
|
// ─── Types ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -1120,20 +1120,32 @@ function cmdGenerateClaudeMd(cwd: string, options: CmdGenerateClaudeMdOptions, r
|
|||||||
// repo-root `CLAUDE.md`, so generated GSD content does not land next to — or
|
// repo-root `CLAUDE.md`, so generated GSD content does not land next to — or
|
||||||
// pollute — a hand-crafted repo-root CLAUDE.md. An explicit `claude_md_path`
|
// pollute — a hand-crafted repo-root CLAUDE.md. An explicit `claude_md_path`
|
||||||
// config value or `--output` still wins.
|
// config value or `--output` still wins.
|
||||||
let configClaudeMdPath = './.claude/CLAUDE.md';
|
let configClaudeMdPath = '.claude/CLAUDE.md';
|
||||||
try {
|
try {
|
||||||
const config = loadConfig(cwd);
|
const config = loadConfig(cwd);
|
||||||
if (config['claude_md_path']) configClaudeMdPath = config['claude_md_path'] as string;
|
if (config['claude_md_path']) configClaudeMdPath = config['claude_md_path'] as string;
|
||||||
if (config['claude_md_assembly']) assemblyConfig = config['claude_md_assembly'] as Record<string, unknown>;
|
if (config['claude_md_assembly']) assemblyConfig = config['claude_md_assembly'] as Record<string, unknown>;
|
||||||
// #3163: When runtime is codex, override the output target to AGENTS.md
|
// #1529: When no explicit --output is provided, derive the instruction
|
||||||
// regardless of claude_md_path, so Codex projects never write to CLAUDE.md.
|
// file from the runtime via the shared `getProjectInstructionFile` policy
|
||||||
// GSD_RUNTIME env var takes precedence over config.runtime, mirroring detectRuntime().
|
// (single source of truth in runtime-name-policy.cjs, shared with the
|
||||||
|
// new-project.md bash workflow via `gsd-tools query
|
||||||
|
// project-instruction-file`). Previously this was a codex-only override
|
||||||
|
// (#3163) that left AGENTS-native runtimes (opencode/kilo/kimi) emitting
|
||||||
|
// CLAUDE.md; copilot now resolves to .github/copilot-instructions.md, and
|
||||||
|
// antigravity/gemini to GEMINI.md. GSD_RUNTIME env var takes precedence
|
||||||
|
// over config.runtime, mirroring detectRuntime().
|
||||||
|
//
|
||||||
|
// Non-claude runtimes always win over a stale `claude_md_path` (the #3163
|
||||||
|
// rationale: a Codex/AGENTS-native project must never write to CLAUDE.md
|
||||||
|
// even if a prior Claude setup left a `claude_md_path` behind). For the
|
||||||
|
// claude runtime, `claude_md_path` config is honored — it IS the
|
||||||
|
// Claude-specific output setting (per #1098 and the #3163 non-codex test).
|
||||||
const effectiveRuntime = resolveRuntimeNameFromCandidates(
|
const effectiveRuntime = resolveRuntimeNameFromCandidates(
|
||||||
process.env['GSD_RUNTIME'],
|
process.env['GSD_RUNTIME'],
|
||||||
config['runtime']
|
config['runtime']
|
||||||
);
|
);
|
||||||
if (!options.output && effectiveRuntime === 'codex') {
|
if (!options.output && effectiveRuntime && effectiveRuntime !== 'claude') {
|
||||||
configClaudeMdPath = './AGENTS.md';
|
configClaudeMdPath = getProjectInstructionFile(effectiveRuntime);
|
||||||
}
|
}
|
||||||
} catch { /* use default */ }
|
} catch { /* use default */ }
|
||||||
|
|
||||||
|
|||||||
@@ -89,6 +89,48 @@ export function resolveRuntimeNameFromCandidates(...candidates: unknown[]): stri
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map a runtime id to its project instruction file path (relative to project
|
||||||
|
* root). Bug #1529: this is the SINGLE source of truth shared by both
|
||||||
|
* consumption surfaces —
|
||||||
|
* (A) the Node surface: profile-output.cjs (generate-claude-md handler)
|
||||||
|
* (B) the bash surface: `gsd-tools query project-instruction-file --runtime <r>`,
|
||||||
|
* consumed by gsd-core/workflows/new-project.md to set $INSTRUCTION_FILE
|
||||||
|
*
|
||||||
|
* Mapping table (per the #1529 issue contract):
|
||||||
|
*
|
||||||
|
* claude → .claude/CLAUDE.md
|
||||||
|
* codex, opencode, kilo, kimi → AGENTS.md
|
||||||
|
* copilot → .github/copilot-instructions.md
|
||||||
|
* antigravity, gemini → GEMINI.md
|
||||||
|
* unknown / future runtimes → AGENTS.md (safe cross-agent default)
|
||||||
|
*
|
||||||
|
* Source-of-truth references for each runtime's read path:
|
||||||
|
* - copilot: GitHub Docs — repository-wide custom instructions are read ONLY
|
||||||
|
* from `.github/copilot-instructions.md`; a root `copilot-instructions.md`
|
||||||
|
* is not a read path. `AGENTS.md` is also read (agent instructions).
|
||||||
|
* https://docs.github.com/en/copilot/how-tos/configure-custom-instructions/add-repository-instructions
|
||||||
|
* (Installer parity: runtime-config-adapter-registry.cts installSurface
|
||||||
|
* 'copilot-instructions' writes the same `.github/copilot-instructions.md`.)
|
||||||
|
* - codex/opencode/kilo/kimi: AGENTS.md is the documented cross-agent
|
||||||
|
* instruction file (agentsmd/agents.md convention).
|
||||||
|
* - antigravity/gemini: GEMINI.md is Gemini CLI's contextFileName.
|
||||||
|
*
|
||||||
|
* Aliases are normalized via `canonicalizeRuntimeName` first, so inputs like
|
||||||
|
* `codex-cli` resolve to `codex` → `AGENTS.md`. Replaces the prior codex-only
|
||||||
|
* override in profile-output.cjs (#3163) which left AGENTS-native runtimes
|
||||||
|
* (opencode/kilo/kimi) incorrectly emitting `.claude/CLAUDE.md`. Pure: no I/O.
|
||||||
|
*/
|
||||||
|
export function getProjectInstructionFile(runtime: unknown): string {
|
||||||
|
const canonical = canonicalizeRuntimeName(runtime);
|
||||||
|
if (canonical === 'claude') return '.claude/CLAUDE.md';
|
||||||
|
if (canonical === 'copilot') return '.github/copilot-instructions.md';
|
||||||
|
if (canonical === 'antigravity' || canonical === 'gemini') return 'GEMINI.md';
|
||||||
|
// codex, opencode, kilo, kimi, AND unknown/future runtimes all default to
|
||||||
|
// root AGENTS.md (the safe cross-agent instruction file).
|
||||||
|
return 'AGENTS.md';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Map a canonical runtime id to its on-disk local config directory name
|
* Map a canonical runtime id to its on-disk local config directory name
|
||||||
* (e.g. `cursor` -> `.cursor`, `windsurf` -> `.devin`). Unknown/empty inputs
|
* (e.g. `cursor` -> `.cursor`, `windsurf` -> `.devin`). Unknown/empty inputs
|
||||||
|
|||||||
111
tests/project-instruction-file-parity.test.cjs
Normal file
111
tests/project-instruction-file-parity.test.cjs
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bug #1529 parity / drift guard.
|
||||||
|
*
|
||||||
|
* The runtime → project-instruction-file mapping is shared between two
|
||||||
|
* parallel surfaces:
|
||||||
|
* (A) the Node surface — `getProjectInstructionFile` in runtime-name-policy.cjs,
|
||||||
|
* consumed by profile-output.cjs (the generate-claude-md handler).
|
||||||
|
* (B) the bash surface — `gsd-tools query project-instruction-file --runtime <r>`,
|
||||||
|
* consumed by gsd-core/workflows/new-project.md to set $INSTRUCTION_FILE.
|
||||||
|
*
|
||||||
|
* Per DEFECT.GENERATIVE-FIX, any shared mapping between two surfaces MUST
|
||||||
|
* carry a parity assertion that fails when they diverge. This test is that
|
||||||
|
* guard: it asserts (A) and (B) return the same filename for every runtime,
|
||||||
|
* AND that the new-project.md workflow derives $INSTRUCTION_FILE from the
|
||||||
|
* shared query rather than a hardcoded codex-only branch (the original bug).
|
||||||
|
*
|
||||||
|
* Boundary coverage (per RULESET.TESTS.boundary-coverage): claude (the
|
||||||
|
* kept-as-is case) and an unknown runtime (the AGENTS.md default) are both
|
||||||
|
* exercised alongside every runtime family in the mapping table.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { describe, test } = require('node:test');
|
||||||
|
const assert = require('node:assert/strict');
|
||||||
|
const fs = require('node:fs');
|
||||||
|
const path = require('node:path');
|
||||||
|
const { execFileSync } = require('node:child_process');
|
||||||
|
|
||||||
|
const ROOT = path.join(__dirname, '..');
|
||||||
|
const RUNTIME_NAME_POLICY_PATH = path.join(
|
||||||
|
ROOT,
|
||||||
|
'gsd-core',
|
||||||
|
'bin',
|
||||||
|
'lib',
|
||||||
|
'runtime-name-policy.cjs',
|
||||||
|
);
|
||||||
|
const GSD_TOOLS_PATH = path.join(ROOT, 'gsd-core', 'bin', 'gsd-tools.cjs');
|
||||||
|
const NEW_PROJECT_WORKFLOW_PATH = path.join(
|
||||||
|
ROOT,
|
||||||
|
'gsd-core',
|
||||||
|
'workflows',
|
||||||
|
'new-project.md',
|
||||||
|
);
|
||||||
|
|
||||||
|
const { getProjectInstructionFile } = require(RUNTIME_NAME_POLICY_PATH);
|
||||||
|
|
||||||
|
const RUNTIMES = [
|
||||||
|
'claude',
|
||||||
|
'codex',
|
||||||
|
'opencode',
|
||||||
|
'kilo',
|
||||||
|
'kimi',
|
||||||
|
'copilot',
|
||||||
|
'antigravity',
|
||||||
|
'gemini',
|
||||||
|
'future-runtime-xyz',
|
||||||
|
'',
|
||||||
|
];
|
||||||
|
|
||||||
|
function queryInstructionFile(runtime) {
|
||||||
|
const args = [
|
||||||
|
GSD_TOOLS_PATH,
|
||||||
|
'query',
|
||||||
|
'project-instruction-file',
|
||||||
|
'--runtime',
|
||||||
|
runtime,
|
||||||
|
];
|
||||||
|
return execFileSync('node', args, {
|
||||||
|
cwd: ROOT,
|
||||||
|
encoding: 'utf8',
|
||||||
|
env: { ...process.env, GSD_RUNTIME: '' },
|
||||||
|
}).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('bug #1529: getProjectInstructionFile ↔ gsd-tools query parity', () => {
|
||||||
|
for (const runtime of RUNTIMES) {
|
||||||
|
const label = runtime === '' ? '<empty>' : runtime;
|
||||||
|
test(`Node function and CLI query agree for runtime=${label}`, () => {
|
||||||
|
const fromFunction = getProjectInstructionFile(runtime);
|
||||||
|
const fromQuery = queryInstructionFile(runtime);
|
||||||
|
assert.strictEqual(
|
||||||
|
fromQuery,
|
||||||
|
fromFunction,
|
||||||
|
`gsd-tools query project-instruction-file --runtime ${label} returned "${fromQuery}" but getProjectInstructionFile() returned "${fromFunction}"; the two surfaces drifted.`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('bug #1529: new-project.md workflow uses the shared policy query', () => {
|
||||||
|
// allow-test-rule: structural drift guard for #1529 — the workflow's bash block MUST invoke the
|
||||||
|
// shared `gsd_run query project-instruction-file` query rather than a hardcoded
|
||||||
|
// codex-only `if/else` branch; there is no typed IR for "this bash block calls a
|
||||||
|
// specific gsd-tools query instead of a hardcoded mapping".
|
||||||
|
const workflow = fs.readFileSync(NEW_PROJECT_WORKFLOW_PATH, 'utf8');
|
||||||
|
|
||||||
|
test('workflow derives INSTRUCTION_FILE from the shared query', () => {
|
||||||
|
assert.ok(
|
||||||
|
/INSTRUCTION_FILE=\$\(gsd_run query project-instruction-file --runtime "\$RUNTIME"\)/.test(workflow),
|
||||||
|
'new-project.md must derive INSTRUCTION_FILE via `gsd_run query project-instruction-file --runtime "$RUNTIME"` (the shared policy adapter)',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('workflow no longer hardcodes the codex-only branch', () => {
|
||||||
|
assert.ok(
|
||||||
|
!/if \[ "\$RUNTIME" = "codex" \]; then INSTRUCTION_FILE="AGENTS\.md"; else INSTRUCTION_FILE="\.claude\/CLAUDE\.md"; fi/.test(workflow),
|
||||||
|
'new-project.md must not contain the retired codex-only `if [ "$RUNTIME" = "codex" ]; then INSTRUCTION_FILE="AGENTS.md"; else INSTRUCTION_FILE=".claude/CLAUDE.md"; fi` branch (#1529 regression guard)',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -9,6 +9,7 @@ const ROOT = path.join(__dirname, '..');
|
|||||||
const {
|
const {
|
||||||
canonicalizeRuntimeName,
|
canonicalizeRuntimeName,
|
||||||
resolveRuntimeNameFromCandidates,
|
resolveRuntimeNameFromCandidates,
|
||||||
|
getProjectInstructionFile,
|
||||||
} = require(path.join(ROOT, 'gsd-core', 'bin', 'lib', 'runtime-name-policy.cjs'));
|
} = require(path.join(ROOT, 'gsd-core', 'bin', 'lib', 'runtime-name-policy.cjs'));
|
||||||
|
|
||||||
describe('runtime-name-policy canonical runtime ids', () => {
|
describe('runtime-name-policy canonical runtime ids', () => {
|
||||||
@@ -71,3 +72,55 @@ describe('runtime-name-policy windsurf alias parity — manifest vs FALLBACK_ALI
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('runtime-name-policy getProjectInstructionFile (#1529)', () => {
|
||||||
|
test('claude maps to .claude/CLAUDE.md (kept-as-is boundary case)', () => {
|
||||||
|
assert.strictEqual(getProjectInstructionFile('claude'), '.claude/CLAUDE.md');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('codex maps to AGENTS.md', () => {
|
||||||
|
assert.strictEqual(getProjectInstructionFile('codex'), 'AGENTS.md');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('opencode maps to AGENTS.md (the #1529 bug surface)', () => {
|
||||||
|
assert.strictEqual(getProjectInstructionFile('opencode'), 'AGENTS.md');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('kilo maps to AGENTS.md', () => {
|
||||||
|
assert.strictEqual(getProjectInstructionFile('kilo'), 'AGENTS.md');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('kimi maps to AGENTS.md', () => {
|
||||||
|
assert.strictEqual(getProjectInstructionFile('kimi'), 'AGENTS.md');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('copilot maps to .github/copilot-instructions.md (GitHub docs read path)', () => {
|
||||||
|
assert.strictEqual(getProjectInstructionFile('copilot'), '.github/copilot-instructions.md');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('gemini maps to GEMINI.md', () => {
|
||||||
|
assert.strictEqual(getProjectInstructionFile('gemini'), 'GEMINI.md');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('antigravity maps to GEMINI.md', () => {
|
||||||
|
assert.strictEqual(getProjectInstructionFile('antigravity'), 'GEMINI.md');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('unknown runtime maps to AGENTS.md (safe cross-agent default, boundary case)', () => {
|
||||||
|
assert.strictEqual(getProjectInstructionFile('future-runtime-xyz'), 'AGENTS.md');
|
||||||
|
assert.strictEqual(getProjectInstructionFile(''), 'AGENTS.md');
|
||||||
|
assert.strictEqual(getProjectInstructionFile(null), 'AGENTS.md');
|
||||||
|
assert.strictEqual(getProjectInstructionFile(undefined), 'AGENTS.md');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('aliases normalize via canonicalizeRuntimeName before mapping', () => {
|
||||||
|
// codex-cli is an alias for codex; it must resolve to the codex mapping.
|
||||||
|
assert.strictEqual(getProjectInstructionFile('codex-cli'), 'AGENTS.md');
|
||||||
|
// opencode-cli is an alias for opencode.
|
||||||
|
assert.strictEqual(getProjectInstructionFile('opencode-cli'), 'AGENTS.md');
|
||||||
|
// gemini-cli is an alias for gemini.
|
||||||
|
assert.strictEqual(getProjectInstructionFile('gemini-cli'), 'GEMINI.md');
|
||||||
|
// github-copilot is an alias for copilot.
|
||||||
|
assert.strictEqual(getProjectInstructionFile('github-copilot'), '.github/copilot-instructions.md');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -45,7 +45,7 @@
|
|||||||
"milestone-summary.md": 11774,
|
"milestone-summary.md": 11774,
|
||||||
"mvp-phase.md": 13582,
|
"mvp-phase.md": 13582,
|
||||||
"new-milestone.md": 32422,
|
"new-milestone.md": 32422,
|
||||||
"new-project.md": 61802,
|
"new-project.md": 62324,
|
||||||
"new-workspace.md": 11254,
|
"new-workspace.md": 11254,
|
||||||
"next.md": 20094,
|
"next.md": 20094,
|
||||||
"node-repair.md": 4173,
|
"node-repair.md": 4173,
|
||||||
|
|||||||
Reference in New Issue
Block a user