fix(#1437): add phase.list-plans to gsd-tools (#1485)

* fix(#1437): add phase.list-plans to gsd-tools

Register phase.list-plans in PHASE_COMMAND_ALIASES, implement
cmdPhaseListPlans in src/phase.cts (uses findPhaseInternal + scanPhasePlans
to return plan_count/has_plans/plans/phase_dir), and wire the handler in
phase-command-router. Previously every call produced "Unknown phase
subcommand".

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(#1437): register new test file in lint-test-file-count allowlist

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(#1437): rename test to fix-NNN convention; update file-count allowlist

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-06-20 13:37:22 -04:00
committed by GitHub
parent ba3181204b
commit fa1ffb4824
7 changed files with 199 additions and 16 deletions

View File

@@ -458,6 +458,14 @@ export const PHASE_COMMAND_ALIASES: CommandAlias[] = [
],
"subcommand": "scaffold",
"mutation": true
},
{
"canonical": "phase.list-plans",
"aliases": [
"phase list-plans"
],
"subcommand": "list-plans",
"mutation": false
}
];

View File

@@ -33,6 +33,7 @@ interface PhaseHandlers {
cmdPhaseRemove: (cwd: string, phaseNum: string, opts: { force: boolean }, raw: boolean) => void;
cmdPhaseComplete: (cwd: string, phaseNum: string | undefined, raw: boolean) => void;
cmdPhaseUatPassed: (cwd: string, phaseNum: string | undefined, raw: boolean, opts?: { policy?: { requireVerification?: boolean } }) => void;
cmdPhaseListPlans: (cwd: string, phaseNum: string | undefined, raw: boolean) => void;
}
interface RoutePhaseCommandOptions {
@@ -182,6 +183,11 @@ function routePhaseCommand({ phase, args, cwd, raw, error }: RoutePhaseCommandOp
phase.cmdPhaseUatPassed(cwd, positional[0], raw, { policy: { requireVerification } });
return { ok: true as const, data: null };
},
// #1437 — list plan files for a phase
'list-plans': (_ctx: Record<string, unknown>): { ok: true; data: null } => {
phase.cmdPhaseListPlans(cwd, args[2], raw);
return { ok: true as const, data: null };
},
},
};

View File

@@ -1825,6 +1825,40 @@ function cmdPhaseUatPassed(
output({ phase: phaseNum, ...report }, raw);
}
// #1437 — phase.list-plans: list plan files for a given phase number.
// Returns the full scan result from scanPhasePlans so callers can read plan
// paths without re-discovering the phase directory themselves.
// eslint-disable-next-line @typescript-eslint/no-require-imports -- plan-scan.cjs is an export= CommonJS module
import planScanMod = require('./plan-scan.cjs');
const { scanPhasePlans } = planScanMod;
function cmdPhaseListPlans(cwd: string, phaseNum: string | undefined, raw: boolean): void {
if (!phaseNum) {
error('phase number required for phase list-plans');
}
const phaseInfo = findPhaseInternal(cwd, phaseNum!);
if (!phaseInfo) {
output({ phase: phaseNum, plan_count: 0, has_plans: false, plans: [], phase_dir: null }, raw);
return;
}
const phaseDir = path.join(cwd, (phaseInfo as unknown as Record<string, unknown>)['directory'] as string);
const scan = scanPhasePlans(phaseDir);
const phaseRel = (phaseInfo as unknown as Record<string, unknown>)['directory'] as string;
// Build absolute-usable relative paths for each plan file.
const plans = scan.planFiles.map((f: string) => toPosixPath(path.join(phaseRel, f)));
output({
phase: phaseNum,
phase_dir: phaseRel,
plan_count: scan.planCount,
has_plans: scan.planCount > 0,
plans,
}, raw);
}
export = {
cmdPhasesList,
cmdPhaseNextDecimal,
@@ -1837,5 +1871,6 @@ export = {
cmdPhaseRemove,
cmdPhaseComplete,
cmdPhaseUatPassed,
cmdPhaseListPlans,
computeDependencyLevels,
};