fix(#3964): route init's waiting-signal, codebase, and skill-manifest paths through the project-aware resolver (#3971)
* test(#3964): waiting_signal, codebase_dir, and skill-manifest must be project-scoped * fix(#3964): route waiting_signal, codebase_dir, and skill-manifest through the project-aware resolver * chore(#3964): changeset fragment (pr number backfilled after PR creation) * chore(#3964): backfill changeset PR number (3971) * test(#3964): assert on the POSIX-normalized codebase_dir across platforms --------- Co-authored-by: sim <sim@local>
This commit is contained in:
@@ -28,6 +28,7 @@ export const CANONICAL_EXACT: ReadonlySet<string> = new Set([
|
||||
'STATE-ARCHIVE.md', // state.cts's cmdStatePrune writes this at the .planning/ root
|
||||
'milestone.lock', // #3311: milestone (phase + session) claim (src/milestone-lock.cts); persistent, unlike the transient STATE.md.lock/WAITING.json
|
||||
'state.json', // #3227: machine-readable state contract published at step boundaries (src/state-contract.cts)
|
||||
'skill-manifest.json', // init.cts routeSkillManifest --write (project-scoped planning root, #3964)
|
||||
]);
|
||||
|
||||
// Pattern-match canonical file names (regex tests on the basename)
|
||||
|
||||
26
src/init.cts
26
src/init.cts
@@ -2308,7 +2308,12 @@ function cmdInitMilestoneOp(cwd: string, raw: boolean): void {
|
||||
function cmdInitMapCodebase(cwd: string, raw: boolean): void {
|
||||
const config = loadConfig(cwd);
|
||||
|
||||
const codebaseDir = path.join(planningRoot(cwd), 'codebase');
|
||||
// #3964: scoped like the payload's own codebase_dir/codebase_dir_exists
|
||||
// below (and verify.cts's codebase drift check) — has_maps/existing_maps
|
||||
// reading the flat root made the same payload claim a scoped codebase dir
|
||||
// exists while reporting zero maps, so map-codebase's Refresh/Skip gate
|
||||
// always forced a re-map under GSD_PROJECT.
|
||||
const codebaseDir = path.join(planningDir(cwd), 'codebase');
|
||||
let existingMaps: string[] = [];
|
||||
try {
|
||||
existingMaps = fs.readdirSync(codebaseDir).filter((f) => f.endsWith('.md'));
|
||||
@@ -2328,13 +2333,16 @@ function cmdInitMapCodebase(cwd: string, raw: boolean): void {
|
||||
timestamp: realClock.nowIso(),
|
||||
|
||||
// #2376: absolute — see comment on phase_dir in cmdInitExecutePhase.
|
||||
codebase_dir: toPosixPath(path.join(planningRoot(cwd), 'codebase')),
|
||||
// #3964: scoped like verify.cts's codebase drift check (planningDir, not
|
||||
// the flat planningRoot) so the two surfaces cannot disagree under
|
||||
// GSD_PROJECT.
|
||||
codebase_dir: toPosixPath(path.join(planningDir(cwd), 'codebase')),
|
||||
|
||||
existing_maps: existingMaps,
|
||||
has_maps: existingMaps.length > 0,
|
||||
|
||||
planning_exists: pathExistsInternal(cwd, '.planning'),
|
||||
codebase_dir_exists: pathExistsInternal(cwd, '.planning/codebase'),
|
||||
codebase_dir_exists: pathExistsInternal(cwd, toPosixPath(path.relative(cwd, path.join(planningDir(cwd), 'codebase')))),
|
||||
};
|
||||
|
||||
output(withProjectRoot(cwd, result), raw);
|
||||
@@ -2584,7 +2592,13 @@ function cmdInitManager(cwd: string, raw: boolean): void {
|
||||
|
||||
let waitingSignal: unknown = null;
|
||||
try {
|
||||
const waitingPath = path.join(cwd, '.planning', 'WAITING.json');
|
||||
// #3964: mirror cmdSignalWaiting's write locations exactly — `.gsd/`
|
||||
// first when it exists, else the project-aware planning dir — so the
|
||||
// signal is read from the project (and location) it is written to.
|
||||
const gsdWaiting = path.join(cwd, '.gsd', 'WAITING.json');
|
||||
const waitingPath = fs.existsSync(path.join(cwd, '.gsd'))
|
||||
? gsdWaiting
|
||||
: path.join(planningDir(cwd), 'WAITING.json');
|
||||
const waitingRaw = platformReadSync(waitingPath);
|
||||
if (waitingRaw !== null) {
|
||||
waitingSignal = JSON.parse(waitingRaw);
|
||||
@@ -4001,7 +4015,9 @@ function cmdSkillManifest(cwd: string, args: string[], raw: boolean): void {
|
||||
const manifest = buildSkillManifest(cwd, skillsDir);
|
||||
|
||||
if (args.includes('--write')) {
|
||||
const planDir = path.join(cwd, '.planning');
|
||||
// #3964: write beside the project's own artifacts (planningDir is
|
||||
// project- and workstream-aware), not the flat root.
|
||||
const planDir = planningDir(cwd);
|
||||
if (fs.existsSync(planDir)) {
|
||||
const manifestPath = path.join(planDir, 'skill-manifest.json');
|
||||
platformWriteSync(manifestPath, JSON.stringify(manifest, null, 2));
|
||||
|
||||
@@ -200,7 +200,11 @@ function listPlanningDocCandidates(cwd: string): string[] {
|
||||
}
|
||||
|
||||
function listCodebaseMapFiles(cwd: string): string[] {
|
||||
const codebaseDir = path.join(planningRoot(cwd), 'codebase');
|
||||
// #3964: project-scoped, agreeing with init's map-codebase surface and
|
||||
// verify.cts's codebase drift check — a flat-root read made
|
||||
// has_codebase_map/needs_codebase_map answer for the wrong project under
|
||||
// GSD_PROJECT.
|
||||
const codebaseDir = path.join(planningDir(cwd), 'codebase');
|
||||
if (!fs.existsSync(codebaseDir)) return [];
|
||||
return REQUIRED_CODEBASE_MAP_FILES.filter((file) =>
|
||||
fs.existsSync(path.join(codebaseDir, file)),
|
||||
|
||||
Reference in New Issue
Block a user