diff --git a/gsd-core/workflows/onboard.md b/gsd-core/workflows/onboard.md index f1896fbfe..7166317f5 100644 --- a/gsd-core/workflows/onboard.md +++ b/gsd-core/workflows/onboard.md @@ -72,7 +72,21 @@ Run from worktree root {ONBOARDING_ROOT}: Then rerun /gsd:onboard from the same worktree root. ``` -Exit. If the user skips mapping, print: +Exit. If the user skips mapping: + +- 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. + +Run from worktree root {ONBOARDING_ROOT}: + +/gsd:ingest-docs +``` + +Exit. + +- Otherwise print: ```text Skipping codebase mapping may give /gsd:new-project weaker context. diff --git a/src/onboard-projection.cts b/src/onboard-projection.cts index 8e4ec8f7f..c2501a210 100644 --- a/src/onboard-projection.cts +++ b/src/onboard-projection.cts @@ -75,6 +75,7 @@ interface OnboardProjection { map_readiness: MapReadiness; next_action: OnboardNextAction; needs_codebase_map: boolean; + needs_fast_codebase_map: boolean; has_codebase_map: boolean; has_fast_codebase_map: boolean; codebase_dir_exists: boolean; @@ -229,7 +230,7 @@ function planningMissing( function nextAction(params: { fastMode: boolean; isBrownfield: boolean; - needsCodebaseMap: boolean; + needsOnboardCodebaseMap: boolean; hasDocsCandidates: boolean; projectExists: boolean; mapReadiness: MapReadiness; @@ -237,7 +238,7 @@ function nextAction(params: { hasPlanningArtifacts: boolean; missingPlanningFiles: string[]; }): OnboardNextAction { - if (params.isBrownfield && params.needsCodebaseMap) { + if (params.isBrownfield && params.needsOnboardCodebaseMap) { return { kind: 'map-codebase', command: params.fastMode ? '/gsd:map-codebase --fast' : '/gsd:map-codebase', @@ -314,9 +315,9 @@ function buildOnboardProjection(cwd: string, options: BuildOnboardProjectionOpti const hasCodebaseMap = codebaseMapFiles.length === REQUIRED_CODEBASE_MAP_FILES.length; const hasFastCodebaseMap = missingFastCodebaseMapFiles.length === 0; const mapReadinessValue = getMapReadiness(hasCodebaseMap, hasFastCodebaseMap); - const needsCodebaseMap = isBrownfield && ( - options.fast ? !hasFastCodebaseMap : !hasCodebaseMap - ); + const needsCodebaseMap = isBrownfield && !hasCodebaseMap; + const needsFastCodebaseMap = isBrownfield && !hasFastCodebaseMap; + const needsOnboardCodebaseMap = options.fast ? needsFastCodebaseMap : needsCodebaseMap; const projectExists = pathExistsInternal(cwd, '.planning/PROJECT.md'); const requirementsExists = fs.existsSync(path.join(planningDir(cwd), 'REQUIREMENTS.md')); const roadmapExists = fs.existsSync(path.join(planningDir(cwd), 'ROADMAP.md')); @@ -349,7 +350,7 @@ function buildOnboardProjection(cwd: string, options: BuildOnboardProjectionOpti next_action: nextAction({ fastMode: options.fast, isBrownfield, - needsCodebaseMap, + needsOnboardCodebaseMap, hasDocsCandidates: docCandidates.length > 0, projectExists, mapReadiness: mapReadinessValue, @@ -358,6 +359,7 @@ function buildOnboardProjection(cwd: string, options: BuildOnboardProjectionOpti missingPlanningFiles, }), needs_codebase_map: needsCodebaseMap, + needs_fast_codebase_map: needsFastCodebaseMap, has_codebase_map: hasCodebaseMap, has_fast_codebase_map: hasFastCodebaseMap, codebase_dir_exists: fs.existsSync(path.join(planningRoot(cwd), 'codebase')), diff --git a/tests/onboard-command.test.cjs b/tests/onboard-command.test.cjs index 10851d5e5..762068b74 100644 --- a/tests/onboard-command.test.cjs +++ b/tests/onboard-command.test.cjs @@ -152,7 +152,8 @@ describe('init onboard public CLI projection', () => { assert.strictEqual(parsed.map_readiness, 'fast'); assert.strictEqual(parsed.has_codebase_map, false); assert.strictEqual(parsed.has_fast_codebase_map, true); - assert.strictEqual(parsed.needs_codebase_map, false); + 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.match(parsed.next_action.reason, /complete codebase map/i); @@ -363,9 +364,17 @@ describe('/gsd:onboard command contract', () => { assert.ok(content.includes('AskUserQuestion'), 'workflow must still support interactive choices'); assert.ok(content.includes('--text'), 'workflow must document text-mode fallback'); assert.ok( - content.includes('Exit. If the user skips mapping, print:'), + content.includes('Exit. If the user skips mapping:'), 'skip mapping must hand off explicitly instead of falling through to summary creation', ); + assert.ok( + content.includes('If `has_docs_candidates && !project_exists`, route the skip to docs ingest instead:'), + 'skip mapping must preserve the docs-ingest gate when planning docs are present', + ); + assert.ok( + content.includes('/gsd:ingest-docs'), + 'skip mapping docs-preserving handoff must name the docs ingest command', + ); assert.ok( content.includes('Skipping codebase mapping may give /gsd:new-project weaker context.'), 'skip mapping must warn about weaker context',