no-mistakes(review): Format onboard handoffs by runtime
This commit is contained in:
committed by
Codesmith
parent
a845a7d231
commit
2f2d33aea7
@@ -24,6 +24,7 @@ if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
||||
Parse JSON fields from `INIT`:
|
||||
|
||||
- `next_action.kind`, `next_action.command`, `next_action.reason`, `next_action.missing`, `next_action.summary_path`
|
||||
- `handoff_commands.ingest_docs`, `handoff_commands.manager`, `handoff_commands.new_project`, `handoff_commands.onboard`
|
||||
- `map_readiness`, `codebase_map_summary_status`, `codebase_map_final_status`
|
||||
- `planning_exists`, `project_exists`, `requirements_exists`, `roadmap_exists`, `state_exists`
|
||||
- `is_brownfield`, `fast_mode`, `has_codebase_map`, `has_fast_codebase_map`
|
||||
@@ -69,12 +70,12 @@ Run from worktree root {ONBOARDING_ROOT}:
|
||||
|
||||
{next_action.command}
|
||||
|
||||
Then rerun /gsd:onboard from the same worktree root.
|
||||
Then rerun {handoff_commands.onboard} from the same worktree root.
|
||||
```
|
||||
|
||||
Exit. If the user skips mapping:
|
||||
|
||||
- If `planning_exists && (!project_exists || !requirements_exists || !roadmap_exists || !state_exists)`, route the skip to the partial planning guard instead:
|
||||
- If `(project_exists || requirements_exists || roadmap_exists || state_exists) && (!project_exists || !requirements_exists || !roadmap_exists || !state_exists)`, route the skip to the partial planning guard instead:
|
||||
|
||||
```text
|
||||
Skipping codebase mapping may give downstream steps weaker context, but project planning exists and is incomplete.
|
||||
@@ -84,7 +85,7 @@ REQUIREMENTS.md: {requirements_exists ? "present" : "missing"}
|
||||
ROADMAP.md: {roadmap_exists ? "present" : "missing"}
|
||||
STATE.md: {state_exists ? "present" : "missing"}
|
||||
|
||||
Run the appropriate lower-level command to fill the missing planning artifact(s), then rerun /gsd:onboard.
|
||||
Run the appropriate lower-level command to fill the missing planning artifact(s), then rerun {handoff_commands.onboard}.
|
||||
```
|
||||
|
||||
Exit.
|
||||
@@ -92,11 +93,11 @@ Exit.
|
||||
- If `has_docs_candidates && !project_exists`, route the skip to docs ingest instead:
|
||||
|
||||
```text
|
||||
Skipping codebase mapping may give downstream steps weaker context, but existing ADR/PRD/SPEC/RFC documents should still be ingested before /gsd:new-project.
|
||||
Skipping codebase mapping may give downstream steps weaker context, but existing ADR/PRD/SPEC/RFC documents should still be ingested before {handoff_commands.new_project}.
|
||||
|
||||
Run from worktree root {ONBOARDING_ROOT}:
|
||||
|
||||
/gsd:ingest-docs
|
||||
{handoff_commands.ingest_docs}
|
||||
```
|
||||
|
||||
Exit.
|
||||
@@ -104,11 +105,11 @@ Exit.
|
||||
- Otherwise print:
|
||||
|
||||
```text
|
||||
Skipping codebase mapping may give /gsd:new-project weaker context.
|
||||
Skipping codebase mapping may give {handoff_commands.new_project} weaker context.
|
||||
|
||||
Run from worktree root {ONBOARDING_ROOT}:
|
||||
|
||||
/gsd:new-project
|
||||
{handoff_commands.new_project}
|
||||
```
|
||||
|
||||
Exit.
|
||||
@@ -124,7 +125,7 @@ If `next_action.kind == "ingest-docs"`:
|
||||
Detected {doc_candidate_count} possible ADR/PRD/SPEC/RFC document(s).
|
||||
|
||||
1. Ingest docs first — run {next_action.command} from worktree root {ONBOARDING_ROOT} (Recommended)
|
||||
2. Skip docs ingest — continue to /gsd:new-project
|
||||
2. Skip docs ingest — continue to {handoff_commands.new_project}
|
||||
|
||||
Enter number:
|
||||
```
|
||||
@@ -134,7 +135,7 @@ Enter number:
|
||||
- question: "Detected {doc_candidate_count} possible ADR/PRD/SPEC/RFC document(s). Ingest them first?"
|
||||
- options:
|
||||
- "Ingest docs first" — Run `{next_action.command}` from worktree root `{ONBOARDING_ROOT}` (Recommended)
|
||||
- "Skip docs ingest" — Continue to `/gsd:new-project`
|
||||
- "Skip docs ingest" — Continue to `{handoff_commands.new_project}`
|
||||
|
||||
If the user chooses ingest, print:
|
||||
|
||||
@@ -143,17 +144,17 @@ Run from worktree root {ONBOARDING_ROOT}:
|
||||
|
||||
{next_action.command}
|
||||
|
||||
Then rerun /gsd:onboard from the same worktree root.
|
||||
Then rerun {handoff_commands.onboard} from the same worktree root.
|
||||
```
|
||||
|
||||
Exit. If the user skips docs ingest, print:
|
||||
|
||||
```text
|
||||
Skipping docs ingest may omit existing ADR/PRD/SPEC/RFC context from /gsd:new-project.
|
||||
Skipping docs ingest may omit existing ADR/PRD/SPEC/RFC context from {handoff_commands.new_project}.
|
||||
|
||||
Run from worktree root {ONBOARDING_ROOT}:
|
||||
|
||||
/gsd:new-project
|
||||
{handoff_commands.new_project}
|
||||
```
|
||||
|
||||
Exit.
|
||||
@@ -169,7 +170,7 @@ Run from worktree root {ONBOARDING_ROOT}:
|
||||
|
||||
{next_action.command}
|
||||
|
||||
Then rerun /gsd:onboard from the same worktree root.
|
||||
Then rerun {handoff_commands.onboard} from the same worktree root.
|
||||
```
|
||||
|
||||
Exit.
|
||||
@@ -200,7 +201,7 @@ REQUIREMENTS.md: {requirements_exists ? "present" : "missing"}
|
||||
ROADMAP.md: {roadmap_exists ? "present" : "missing"}
|
||||
STATE.md: {state_exists ? "present" : "missing"}
|
||||
|
||||
Run the appropriate lower-level command to fill the missing planning artifact(s), then rerun /gsd:onboard.
|
||||
Run the appropriate lower-level command to fill the missing planning artifact(s), then rerun {handoff_commands.onboard}.
|
||||
```
|
||||
|
||||
Exit.
|
||||
@@ -262,7 +263,7 @@ Onboarding status:
|
||||
- Codebase map: {codebase_map_final_status}
|
||||
- Onboarding summary: present
|
||||
|
||||
Next recommended command: /gsd:manager
|
||||
Next recommended command: {handoff_commands.manager}
|
||||
```
|
||||
|
||||
Do not run implementation execution or shipping from onboarding.
|
||||
|
||||
@@ -5,6 +5,7 @@ import path from 'node:path';
|
||||
import coreUtils = require('./core-utils.cjs');
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports -- planning-workspace.cjs is an export= CommonJS module
|
||||
import planningWorkspace = require('./planning-workspace.cjs');
|
||||
import { formatGsdSlash, resolveRuntime } from './runtime-slash.cjs';
|
||||
|
||||
const { pathExistsInternal, toPosixPath } = coreUtils;
|
||||
const { planningDir, planningRoot } = planningWorkspace;
|
||||
@@ -42,6 +43,15 @@ const PLANNING_DOC_SEGMENTS = new Set([
|
||||
|
||||
type MapReadiness = 'none' | 'fast' | 'complete';
|
||||
|
||||
interface OnboardHandoffCommands {
|
||||
ingest_docs: string;
|
||||
manager: string;
|
||||
map_codebase: string;
|
||||
map_codebase_fast: string;
|
||||
new_project: string;
|
||||
onboard: string;
|
||||
}
|
||||
|
||||
type OnboardNextAction =
|
||||
| { kind: 'map-codebase'; command: string; reason: string }
|
||||
| { kind: 'ingest-docs'; command: string; reason: string }
|
||||
@@ -68,6 +78,8 @@ interface OnboardProjection {
|
||||
state_exists: boolean;
|
||||
config_exists: boolean;
|
||||
|
||||
handoff_commands: OnboardHandoffCommands;
|
||||
|
||||
has_existing_code: boolean;
|
||||
has_package_file: boolean;
|
||||
is_brownfield: boolean;
|
||||
@@ -237,11 +249,12 @@ function nextAction(params: {
|
||||
onboardingSummaryExists: boolean;
|
||||
hasPlanningArtifacts: boolean;
|
||||
missingPlanningFiles: string[];
|
||||
handoffCommands: OnboardHandoffCommands;
|
||||
}): OnboardNextAction {
|
||||
if (params.isBrownfield && params.needsOnboardCodebaseMap) {
|
||||
return {
|
||||
kind: 'map-codebase',
|
||||
command: params.fastMode ? '/gsd:map-codebase --fast' : '/gsd:map-codebase',
|
||||
command: params.fastMode ? params.handoffCommands.map_codebase_fast : params.handoffCommands.map_codebase,
|
||||
reason: 'Existing code was detected, but the required .planning/codebase/ map is missing.',
|
||||
};
|
||||
}
|
||||
@@ -249,7 +262,7 @@ function nextAction(params: {
|
||||
if (!params.projectExists && params.fastMode && params.mapReadiness === 'fast') {
|
||||
return {
|
||||
kind: 'complete-map-before-new-project',
|
||||
command: '/gsd:map-codebase',
|
||||
command: params.handoffCommands.map_codebase,
|
||||
reason: 'The fast map is enough for lightweight onboarding, but project setup still requires the complete codebase map.',
|
||||
};
|
||||
}
|
||||
@@ -265,7 +278,7 @@ function nextAction(params: {
|
||||
if (params.hasDocsCandidates && !params.projectExists) {
|
||||
return {
|
||||
kind: 'ingest-docs',
|
||||
command: '/gsd:ingest-docs',
|
||||
command: params.handoffCommands.ingest_docs,
|
||||
reason: 'Detected existing ADR/PRD/SPEC/RFC document(s) before project setup.',
|
||||
};
|
||||
}
|
||||
@@ -273,7 +286,7 @@ function nextAction(params: {
|
||||
if (!params.isBrownfield && !params.projectExists && !params.hasDocsCandidates) {
|
||||
return {
|
||||
kind: 'new-project',
|
||||
command: '/gsd:new-project',
|
||||
command: params.handoffCommands.new_project,
|
||||
reason: 'No existing code or planning docs were detected.',
|
||||
};
|
||||
}
|
||||
@@ -281,7 +294,7 @@ function nextAction(params: {
|
||||
if (!params.projectExists) {
|
||||
return {
|
||||
kind: 'new-project',
|
||||
command: '/gsd:new-project',
|
||||
command: params.handoffCommands.new_project,
|
||||
reason: 'Codebase context is ready for project initialization.',
|
||||
};
|
||||
}
|
||||
@@ -300,7 +313,20 @@ function nextAction(params: {
|
||||
};
|
||||
}
|
||||
|
||||
function buildHandoffCommands(cwd: string): OnboardHandoffCommands {
|
||||
const runtime = resolveRuntime(cwd);
|
||||
return {
|
||||
ingest_docs: formatGsdSlash('ingest-docs', runtime) as string,
|
||||
manager: formatGsdSlash('manager', runtime) as string,
|
||||
map_codebase: formatGsdSlash('map-codebase', runtime) as string,
|
||||
map_codebase_fast: formatGsdSlash('map-codebase --fast', runtime) as string,
|
||||
new_project: formatGsdSlash('new-project', runtime) as string,
|
||||
onboard: formatGsdSlash('onboard', runtime) as string,
|
||||
};
|
||||
}
|
||||
|
||||
function buildOnboardProjection(cwd: string, options: BuildOnboardProjectionOptions): OnboardProjection {
|
||||
const handoffCommands = buildHandoffCommands(cwd);
|
||||
const codebaseMapFiles = listCodebaseMapFiles(cwd);
|
||||
const missingCodebaseMapFiles = REQUIRED_CODEBASE_MAP_FILES.filter(
|
||||
(file) => !codebaseMapFiles.includes(file),
|
||||
@@ -342,6 +368,8 @@ function buildOnboardProjection(cwd: string, options: BuildOnboardProjectionOpti
|
||||
state_exists: stateExists,
|
||||
config_exists: fs.existsSync(path.join(planningDir(cwd), 'config.json')),
|
||||
|
||||
handoff_commands: handoffCommands,
|
||||
|
||||
has_existing_code: hasCode,
|
||||
has_package_file: hasPackageFile,
|
||||
is_brownfield: isBrownfield,
|
||||
@@ -357,6 +385,7 @@ function buildOnboardProjection(cwd: string, options: BuildOnboardProjectionOpti
|
||||
onboardingSummaryExists,
|
||||
hasPlanningArtifacts,
|
||||
missingPlanningFiles,
|
||||
handoffCommands,
|
||||
}),
|
||||
needs_codebase_map: needsCodebaseMap,
|
||||
needs_fast_codebase_map: needsFastCodebaseMap,
|
||||
|
||||
@@ -163,7 +163,7 @@ describe('init onboard public CLI projection', () => {
|
||||
assert.strictEqual(parsed.needs_codebase_map, true);
|
||||
assert.strictEqual(parsed.needs_fast_codebase_map, false);
|
||||
assert.strictEqual(parsed.next_action.kind, 'complete-map-before-new-project');
|
||||
assert.strictEqual(parsed.next_action.command, '/gsd:map-codebase');
|
||||
assert.strictEqual(parsed.next_action.command, '/gsd-map-codebase');
|
||||
assert.match(parsed.next_action.reason, /complete codebase map/i);
|
||||
assert.deepStrictEqual(parsed.fast_codebase_map_files_required, [
|
||||
'STACK.md',
|
||||
@@ -188,7 +188,7 @@ describe('init onboard public CLI projection', () => {
|
||||
|
||||
const parsed = JSON.parse(result.output);
|
||||
assert.strictEqual(parsed.next_action.kind, 'complete-map-before-new-project');
|
||||
assert.strictEqual(parsed.next_action.command, '/gsd:map-codebase');
|
||||
assert.strictEqual(parsed.next_action.command, '/gsd-map-codebase');
|
||||
});
|
||||
|
||||
test('routes planning artifacts without PROJECT.md to partial planning', () => {
|
||||
@@ -212,7 +212,7 @@ describe('init onboard public CLI projection', () => {
|
||||
assert.ok(result.success, `init onboard should succeed: ${result.error}`);
|
||||
assert.deepStrictEqual(JSON.parse(result.output).next_action, {
|
||||
kind: 'map-codebase',
|
||||
command: '/gsd:map-codebase',
|
||||
command: '/gsd-map-codebase',
|
||||
reason: 'Existing code was detected, but the required .planning/codebase/ map is missing.',
|
||||
});
|
||||
|
||||
@@ -224,7 +224,7 @@ describe('init onboard public CLI projection', () => {
|
||||
assert.ok(result.success, `init onboard should succeed: ${result.error}`);
|
||||
assert.deepStrictEqual(JSON.parse(result.output).next_action, {
|
||||
kind: 'ingest-docs',
|
||||
command: '/gsd:ingest-docs',
|
||||
command: '/gsd-ingest-docs',
|
||||
reason: 'Detected existing ADR/PRD/SPEC/RFC document(s) before project setup.',
|
||||
});
|
||||
|
||||
@@ -234,7 +234,7 @@ describe('init onboard public CLI projection', () => {
|
||||
assert.ok(result.success, `init onboard should succeed: ${result.error}`);
|
||||
assert.deepStrictEqual(JSON.parse(result.output).next_action, {
|
||||
kind: 'new-project',
|
||||
command: '/gsd:new-project',
|
||||
command: '/gsd-new-project',
|
||||
reason: 'No existing code or planning docs were detected.',
|
||||
});
|
||||
|
||||
@@ -318,6 +318,29 @@ describe('init onboard public CLI projection', () => {
|
||||
assert.strictEqual(parsed.needs_codebase_map, true);
|
||||
});
|
||||
|
||||
|
||||
test('formats onboard handoff commands for the resolved runtime', () => {
|
||||
fs.writeFileSync(path.join(tmpDir, 'package.json'), '{"name":"fixture"}\n');
|
||||
|
||||
const result = runGsdTools(['init', 'onboard', '--raw'], tmpDir, {
|
||||
HOME: tmpDir,
|
||||
GSD_RUNTIME: 'codex',
|
||||
});
|
||||
assert.ok(result.success, `init onboard should succeed: ${result.error}`);
|
||||
|
||||
const parsed = JSON.parse(result.output);
|
||||
assert.strictEqual(parsed.next_action.kind, 'map-codebase');
|
||||
assert.strictEqual(parsed.next_action.command, '$gsd-map-codebase');
|
||||
assert.deepStrictEqual(parsed.handoff_commands, {
|
||||
map_codebase: '$gsd-map-codebase',
|
||||
map_codebase_fast: '$gsd-map-codebase --fast',
|
||||
ingest_docs: '$gsd-ingest-docs',
|
||||
manager: '$gsd-manager',
|
||||
new_project: '$gsd-new-project',
|
||||
onboard: '$gsd-onboard',
|
||||
});
|
||||
});
|
||||
|
||||
test('dotted query init.onboard matches direct init onboard', () => {
|
||||
fs.writeFileSync(path.join(tmpDir, 'package.json'), '{"name":"fixture"}\n');
|
||||
|
||||
@@ -376,7 +399,7 @@ describe('/gsd:onboard command contract', () => {
|
||||
'skip mapping must hand off explicitly instead of falling through to summary creation',
|
||||
);
|
||||
const partialPlanningSkipGuard =
|
||||
'If `planning_exists && (!project_exists || !requirements_exists || !roadmap_exists || !state_exists)`, route the skip to the partial planning guard instead:';
|
||||
'If `(project_exists || requirements_exists || roadmap_exists || state_exists) && (!project_exists || !requirements_exists || !roadmap_exists || !state_exists)`, route the skip to the partial planning guard instead:';
|
||||
const docsIngestSkipGuard =
|
||||
'If `has_docs_candidates && !project_exists`, route the skip to docs ingest instead:';
|
||||
assert.ok(
|
||||
@@ -392,11 +415,11 @@ describe('/gsd:onboard command contract', () => {
|
||||
'skip mapping must check partial planning before docs ingest',
|
||||
);
|
||||
assert.ok(
|
||||
content.includes('/gsd:ingest-docs'),
|
||||
'skip mapping docs-preserving handoff must name the docs ingest command',
|
||||
content.includes('{handoff_commands.ingest_docs}'),
|
||||
'skip mapping docs-preserving handoff must use the projected docs ingest command',
|
||||
);
|
||||
assert.ok(
|
||||
content.includes('Skipping codebase mapping may give /gsd:new-project weaker context.'),
|
||||
content.includes('Skipping codebase mapping may give {handoff_commands.new_project} weaker context.'),
|
||||
'skip mapping must warn about weaker context',
|
||||
);
|
||||
assert.ok(
|
||||
@@ -404,12 +427,15 @@ describe('/gsd:onboard command contract', () => {
|
||||
'skip docs ingest must hand off explicitly instead of falling through to summary creation',
|
||||
);
|
||||
assert.ok(
|
||||
content.includes('Skipping docs ingest may omit existing ADR/PRD/SPEC/RFC context from /gsd:new-project.'),
|
||||
content.includes('Skipping docs ingest may omit existing ADR/PRD/SPEC/RFC context from {handoff_commands.new_project}.'),
|
||||
'skip docs ingest must warn about omitted docs context',
|
||||
);
|
||||
assert.match(content, /do not overwrite/i, 'workflow must protect existing summary/planning');
|
||||
assert.match(content, /query commit "docs: create onboarding summary" --files \.planning\/onboarding\/SUMMARY\.md/);
|
||||
assert.ok(!content.includes('execute-phase'), 'onboarding must not execute implementation phases');
|
||||
assert.ok(!content.includes('gsd:ship'), 'onboarding must not ship work');
|
||||
assert.ok(content.includes('Next recommended command: {handoff_commands.manager}'));
|
||||
assert.ok(!content.includes('/gsd:ingest-docs'), 'skip handoffs must not hard-code legacy ingest command');
|
||||
assert.ok(!content.includes('/gsd:new-project'), 'skip handoffs must not hard-code legacy new-project command');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user