code-as-plan 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.ja-JP.md +834 -0
- package/README.ko-KR.md +823 -0
- package/README.md +1006 -0
- package/README.pt-BR.md +452 -0
- package/README.zh-CN.md +800 -0
- package/agents/cap-brainstormer.md +154 -0
- package/agents/cap-debugger.md +221 -0
- package/agents/cap-prototyper.md +170 -0
- package/agents/cap-reviewer.md +230 -0
- package/agents/cap-tester.md +193 -0
- package/bin/install.js +5002 -0
- package/cap/bin/gsd-tools.cjs +1141 -0
- package/cap/bin/lib/arc-scanner.cjs +341 -0
- package/cap/bin/lib/cap-feature-map.cjs +506 -0
- package/cap/bin/lib/cap-session.cjs +191 -0
- package/cap/bin/lib/cap-stack-docs.cjs +598 -0
- package/cap/bin/lib/cap-tag-scanner.cjs +458 -0
- package/cap/bin/lib/commands.cjs +959 -0
- package/cap/bin/lib/config.cjs +466 -0
- package/cap/bin/lib/convention-reader.cjs +180 -0
- package/cap/bin/lib/core.cjs +1230 -0
- package/cap/bin/lib/feature-aggregator.cjs +422 -0
- package/cap/bin/lib/frontmatter.cjs +336 -0
- package/cap/bin/lib/init.cjs +1442 -0
- package/cap/bin/lib/manifest-generator.cjs +381 -0
- package/cap/bin/lib/milestone.cjs +252 -0
- package/cap/bin/lib/model-profiles.cjs +68 -0
- package/cap/bin/lib/monorepo-context.cjs +224 -0
- package/cap/bin/lib/monorepo-migrator.cjs +507 -0
- package/cap/bin/lib/phase.cjs +888 -0
- package/cap/bin/lib/profile-output.cjs +952 -0
- package/cap/bin/lib/profile-pipeline.cjs +539 -0
- package/cap/bin/lib/roadmap.cjs +329 -0
- package/cap/bin/lib/security.cjs +382 -0
- package/cap/bin/lib/session-manager.cjs +290 -0
- package/cap/bin/lib/skeleton-generator.cjs +177 -0
- package/cap/bin/lib/state.cjs +1031 -0
- package/cap/bin/lib/template.cjs +222 -0
- package/cap/bin/lib/test-detector.cjs +61 -0
- package/cap/bin/lib/uat.cjs +282 -0
- package/cap/bin/lib/verify.cjs +888 -0
- package/cap/bin/lib/workspace-detector.cjs +369 -0
- package/cap/bin/lib/workstream.cjs +491 -0
- package/cap/commands/gsd/workstreams.md +63 -0
- package/cap/references/arc-standard.md +315 -0
- package/cap/references/cap-agent-architecture.md +102 -0
- package/cap/references/cap-gitignore-template +9 -0
- package/cap/references/cap-zero-deps.md +158 -0
- package/cap/references/checkpoints.md +778 -0
- package/cap/references/continuation-format.md +249 -0
- package/cap/references/decimal-phase-calculation.md +64 -0
- package/cap/references/feature-map-template.md +25 -0
- package/cap/references/git-integration.md +295 -0
- package/cap/references/git-planning-commit.md +38 -0
- package/cap/references/model-profile-resolution.md +36 -0
- package/cap/references/model-profiles.md +139 -0
- package/cap/references/phase-argument-parsing.md +61 -0
- package/cap/references/planning-config.md +202 -0
- package/cap/references/questioning.md +162 -0
- package/cap/references/session-template.json +8 -0
- package/cap/references/tdd.md +263 -0
- package/cap/references/ui-brand.md +160 -0
- package/cap/references/user-profiling.md +681 -0
- package/cap/references/verification-patterns.md +612 -0
- package/cap/references/workstream-flag.md +58 -0
- package/cap/templates/DEBUG.md +164 -0
- package/cap/templates/UAT.md +265 -0
- package/cap/templates/UI-SPEC.md +100 -0
- package/cap/templates/VALIDATION.md +76 -0
- package/cap/templates/claude-md.md +122 -0
- package/cap/templates/codebase/architecture.md +255 -0
- package/cap/templates/codebase/concerns.md +310 -0
- package/cap/templates/codebase/conventions.md +307 -0
- package/cap/templates/codebase/integrations.md +280 -0
- package/cap/templates/codebase/stack.md +186 -0
- package/cap/templates/codebase/structure.md +285 -0
- package/cap/templates/codebase/testing.md +480 -0
- package/cap/templates/config.json +44 -0
- package/cap/templates/context.md +352 -0
- package/cap/templates/continue-here.md +78 -0
- package/cap/templates/copilot-instructions.md +7 -0
- package/cap/templates/debug-subagent-prompt.md +91 -0
- package/cap/templates/dev-preferences.md +21 -0
- package/cap/templates/discovery.md +146 -0
- package/cap/templates/discussion-log.md +63 -0
- package/cap/templates/milestone-archive.md +123 -0
- package/cap/templates/milestone.md +115 -0
- package/cap/templates/phase-prompt.md +610 -0
- package/cap/templates/planner-subagent-prompt.md +117 -0
- package/cap/templates/project.md +186 -0
- package/cap/templates/requirements.md +231 -0
- package/cap/templates/research-project/ARCHITECTURE.md +204 -0
- package/cap/templates/research-project/FEATURES.md +147 -0
- package/cap/templates/research-project/PITFALLS.md +200 -0
- package/cap/templates/research-project/STACK.md +120 -0
- package/cap/templates/research-project/SUMMARY.md +170 -0
- package/cap/templates/research.md +552 -0
- package/cap/templates/retrospective.md +54 -0
- package/cap/templates/roadmap.md +202 -0
- package/cap/templates/state.md +176 -0
- package/cap/templates/summary-complex.md +59 -0
- package/cap/templates/summary-minimal.md +41 -0
- package/cap/templates/summary-standard.md +48 -0
- package/cap/templates/summary.md +248 -0
- package/cap/templates/user-profile.md +146 -0
- package/cap/templates/user-setup.md +311 -0
- package/cap/templates/verification-report.md +322 -0
- package/cap/workflows/add-phase.md +112 -0
- package/cap/workflows/add-tests.md +351 -0
- package/cap/workflows/add-todo.md +158 -0
- package/cap/workflows/audit-milestone.md +340 -0
- package/cap/workflows/audit-uat.md +109 -0
- package/cap/workflows/autonomous.md +891 -0
- package/cap/workflows/check-todos.md +177 -0
- package/cap/workflows/cleanup.md +152 -0
- package/cap/workflows/complete-milestone.md +767 -0
- package/cap/workflows/diagnose-issues.md +231 -0
- package/cap/workflows/discovery-phase.md +289 -0
- package/cap/workflows/discuss-phase-assumptions.md +653 -0
- package/cap/workflows/discuss-phase.md +1049 -0
- package/cap/workflows/do.md +104 -0
- package/cap/workflows/execute-phase.md +846 -0
- package/cap/workflows/execute-plan.md +514 -0
- package/cap/workflows/fast.md +105 -0
- package/cap/workflows/forensics.md +265 -0
- package/cap/workflows/health.md +181 -0
- package/cap/workflows/help.md +660 -0
- package/cap/workflows/insert-phase.md +130 -0
- package/cap/workflows/list-phase-assumptions.md +178 -0
- package/cap/workflows/list-workspaces.md +56 -0
- package/cap/workflows/manager.md +362 -0
- package/cap/workflows/map-codebase.md +377 -0
- package/cap/workflows/milestone-summary.md +223 -0
- package/cap/workflows/new-milestone.md +486 -0
- package/cap/workflows/new-project.md +1250 -0
- package/cap/workflows/new-workspace.md +237 -0
- package/cap/workflows/next.md +97 -0
- package/cap/workflows/node-repair.md +92 -0
- package/cap/workflows/note.md +156 -0
- package/cap/workflows/pause-work.md +176 -0
- package/cap/workflows/plan-milestone-gaps.md +273 -0
- package/cap/workflows/plan-phase.md +859 -0
- package/cap/workflows/plant-seed.md +169 -0
- package/cap/workflows/pr-branch.md +129 -0
- package/cap/workflows/profile-user.md +450 -0
- package/cap/workflows/progress.md +507 -0
- package/cap/workflows/quick.md +757 -0
- package/cap/workflows/remove-phase.md +155 -0
- package/cap/workflows/remove-workspace.md +90 -0
- package/cap/workflows/research-phase.md +82 -0
- package/cap/workflows/resume-project.md +326 -0
- package/cap/workflows/review.md +228 -0
- package/cap/workflows/session-report.md +146 -0
- package/cap/workflows/settings.md +283 -0
- package/cap/workflows/ship.md +228 -0
- package/cap/workflows/stats.md +60 -0
- package/cap/workflows/transition.md +671 -0
- package/cap/workflows/ui-phase.md +302 -0
- package/cap/workflows/ui-review.md +165 -0
- package/cap/workflows/update.md +323 -0
- package/cap/workflows/validate-phase.md +174 -0
- package/cap/workflows/verify-phase.md +254 -0
- package/cap/workflows/verify-work.md +637 -0
- package/commands/cap/annotate.md +165 -0
- package/commands/cap/brainstorm.md +238 -0
- package/commands/cap/debug.md +297 -0
- package/commands/cap/init.md +262 -0
- package/commands/cap/iterate.md +234 -0
- package/commands/cap/prototype.md +281 -0
- package/commands/cap/refresh-docs.md +37 -0
- package/commands/cap/review.md +272 -0
- package/commands/cap/scan.md +249 -0
- package/commands/cap/start.md +234 -0
- package/commands/cap/status.md +189 -0
- package/commands/cap/test.md +250 -0
- package/hooks/dist/gsd-check-update.js +114 -0
- package/hooks/dist/gsd-context-monitor.js +156 -0
- package/hooks/dist/gsd-prompt-guard.js +96 -0
- package/hooks/dist/gsd-statusline.js +119 -0
- package/hooks/dist/gsd-workflow-guard.js +94 -0
- package/package.json +51 -0
- package/scripts/base64-scan.sh +262 -0
- package/scripts/build-hooks.js +82 -0
- package/scripts/cap-removal-checklist.md +202 -0
- package/scripts/prompt-injection-scan.sh +198 -0
- package/scripts/run-tests.cjs +29 -0
- package/scripts/secret-scan.sh +227 -0
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* feature-aggregator — Auto-generates FEATURES.md from PRD acceptance criteria
|
|
3
|
+
* and @gsd-tag status in CODE-INVENTORY.md.
|
|
4
|
+
*
|
|
5
|
+
* Requirements: FMAP-01, FMAP-02, FMAP-03, FMAP-04, FMAP-05
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
// @gsd-context(phase:12) Feature aggregator — reads PRDs and CODE-INVENTORY.md to produce FEATURES.md.
|
|
11
|
+
// Dual-input design: PRD ACs are authoritative, code tags refine completion status.
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const crypto = require('crypto');
|
|
16
|
+
|
|
17
|
+
// ── PRD Parsing ─────────────────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
// @gsd-pattern AC lines in PRDs follow the format: AC-N: description text
|
|
20
|
+
// This regex must match the exact format emitted by /gsd:brainstorm and /gsd:prototype PRD templates.
|
|
21
|
+
const AC_LINE_RE = /^(?:[-*]\s*)?(?:\*\*)?AC-(\d+)(?:\*\*)?:\s*(.+)$/gm;
|
|
22
|
+
|
|
23
|
+
// @gsd-pattern Feature group headings in PRDs use ## or ### markdown headers
|
|
24
|
+
const FEATURE_GROUP_RE = /^#{2,3}\s+(.+)$/gm;
|
|
25
|
+
|
|
26
|
+
// @gsd-pattern Dependency sections in PRDs use "## Dependencies" or "### Dependencies"
|
|
27
|
+
const DEPENDENCY_SECTION_RE = /^#{2,3}\s+Dependencies\s*$/im;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Parse a PRD file and extract acceptance criteria, feature groups, and dependencies.
|
|
31
|
+
*
|
|
32
|
+
* @gsd-api Parameters: prdContent (string) — raw PRD markdown.
|
|
33
|
+
* Returns: { acs: Array<{id, description, group}>, dependencies: Array<{from, to}>, groups: string[] }
|
|
34
|
+
*
|
|
35
|
+
* @param {string} prdContent - Raw PRD Markdown content
|
|
36
|
+
* @param {string} [prdName] - Name of the PRD file (for provenance tracking)
|
|
37
|
+
* @returns {{ acs: Array<{id: string, description: string, group: string, prdSource: string}>, dependencies: Array<{from: string, to: string}>, groups: string[] }}
|
|
38
|
+
*/
|
|
39
|
+
function parsePrd(prdContent, prdName) {
|
|
40
|
+
// @gsd-todo(ref:AC-1) Implement full PRD parsing — extract ACs, feature groups, and dependency sections from PRD markdown
|
|
41
|
+
const acs = [];
|
|
42
|
+
const groups = [];
|
|
43
|
+
const dependencies = [];
|
|
44
|
+
|
|
45
|
+
let currentGroup = '(ungrouped)';
|
|
46
|
+
|
|
47
|
+
const lines = prdContent.split('\n');
|
|
48
|
+
let inDependencySection = false;
|
|
49
|
+
|
|
50
|
+
for (const line of lines) {
|
|
51
|
+
// Detect feature group headings
|
|
52
|
+
const groupMatch = line.match(/^#{2,3}\s+(.+)$/);
|
|
53
|
+
if (groupMatch) {
|
|
54
|
+
const heading = groupMatch[1].trim();
|
|
55
|
+
if (/^dependencies$/i.test(heading)) {
|
|
56
|
+
inDependencySection = true;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
inDependencySection = false;
|
|
60
|
+
currentGroup = heading;
|
|
61
|
+
if (!groups.includes(heading)) {
|
|
62
|
+
groups.push(heading);
|
|
63
|
+
}
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Parse dependency lines (format: "- FeatureA -> FeatureB" or "- FeatureA depends on FeatureB")
|
|
68
|
+
if (inDependencySection) {
|
|
69
|
+
const depArrowMatch = line.match(/^[-*]\s+(.+?)\s*(?:->|→)\s*(.+)$/);
|
|
70
|
+
if (depArrowMatch) {
|
|
71
|
+
dependencies.push({
|
|
72
|
+
from: depArrowMatch[1].trim(),
|
|
73
|
+
to: depArrowMatch[2].trim(),
|
|
74
|
+
});
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const depTextMatch = line.match(/^[-*]\s+(.+?)\s+depends on\s+(.+)$/i);
|
|
78
|
+
if (depTextMatch) {
|
|
79
|
+
dependencies.push({
|
|
80
|
+
from: depTextMatch[1].trim(),
|
|
81
|
+
to: depTextMatch[2].trim(),
|
|
82
|
+
});
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Parse AC lines
|
|
88
|
+
const acMatch = line.match(/^(?:[-*]\s*)?(?:\*\*)?AC-(\d+)(?:\*\*)?:\s*(.+)$/);
|
|
89
|
+
if (acMatch) {
|
|
90
|
+
inDependencySection = false;
|
|
91
|
+
acs.push({
|
|
92
|
+
id: `AC-${acMatch[1]}`,
|
|
93
|
+
description: acMatch[2].trim(),
|
|
94
|
+
group: currentGroup,
|
|
95
|
+
prdSource: prdName || 'unknown',
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return { acs, dependencies, groups };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ── CODE-INVENTORY Parsing ──────────────────────────────────────────────────
|
|
104
|
+
|
|
105
|
+
// @gsd-pattern Open @gsd-todo tags with ref:AC-N metadata indicate incomplete ACs.
|
|
106
|
+
// Absence of such a tag means the AC is considered done.
|
|
107
|
+
const TODO_REF_RE = /ref:(AC-\d+)/;
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Parse CODE-INVENTORY.md to find open @gsd-todo tags with AC references.
|
|
111
|
+
*
|
|
112
|
+
* @gsd-api Parameters: inventoryContent (string) — raw CODE-INVENTORY.md.
|
|
113
|
+
* Returns: Set<string> of open AC IDs (e.g., Set(['AC-1', 'AC-3']))
|
|
114
|
+
*
|
|
115
|
+
* @param {string} inventoryContent - Raw CODE-INVENTORY.md content
|
|
116
|
+
* @returns {Set<string>} Set of AC IDs that still have open @gsd-todo tags
|
|
117
|
+
*/
|
|
118
|
+
function parseOpenTodos(inventoryContent) {
|
|
119
|
+
// @gsd-todo(ref:AC-2) Implement CODE-INVENTORY.md parsing to extract open @gsd-todo(ref:AC-N) tags and determine per-AC completion status
|
|
120
|
+
const openAcIds = new Set();
|
|
121
|
+
const lines = inventoryContent.split('\n');
|
|
122
|
+
|
|
123
|
+
// We look for lines in the @gsd-todo section that contain ref:AC-N metadata
|
|
124
|
+
let inTodoSection = false;
|
|
125
|
+
|
|
126
|
+
for (const line of lines) {
|
|
127
|
+
// Detect the @gsd-todo section heading
|
|
128
|
+
if (/^###\s+@gsd-todo/.test(line)) {
|
|
129
|
+
inTodoSection = true;
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
// Exit todo section on next ### heading
|
|
133
|
+
if (inTodoSection && /^###\s+@gsd-/.test(line)) {
|
|
134
|
+
inTodoSection = false;
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (inTodoSection) {
|
|
139
|
+
const refMatch = line.match(TODO_REF_RE);
|
|
140
|
+
if (refMatch) {
|
|
141
|
+
openAcIds.add(refMatch[1]);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return openAcIds;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ── Cross-Reference Engine ──────────────────────────────────────────────────
|
|
150
|
+
|
|
151
|
+
// @gsd-decision AC completion is derived from tag presence: if a @gsd-todo with ref:AC-N
|
|
152
|
+
// exists in CODE-INVENTORY.md, that AC is "open". If absent, it is "done".
|
|
153
|
+
// This avoids needing explicit "done" markers — the absence of work IS the signal.
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Cross-reference PRD ACs with open @gsd-todo tags to determine completion status.
|
|
157
|
+
*
|
|
158
|
+
* @gsd-api Parameters: acs (Array), openTodoAcIds (Set<string>).
|
|
159
|
+
* Returns: Array of AC objects enriched with `status` field ('done' | 'open').
|
|
160
|
+
*
|
|
161
|
+
* @param {Array<{id: string, description: string, group: string, prdSource: string}>} acs
|
|
162
|
+
* @param {Set<string>} openTodoAcIds - AC IDs that still have open @gsd-todo tags
|
|
163
|
+
* @returns {Array<{id: string, description: string, group: string, prdSource: string, status: string}>}
|
|
164
|
+
*/
|
|
165
|
+
function crossReference(acs, openTodoAcIds) {
|
|
166
|
+
return acs.map(ac => ({
|
|
167
|
+
...ac,
|
|
168
|
+
status: openTodoAcIds.has(ac.id) ? 'open' : 'done',
|
|
169
|
+
}));
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// ── Dependency Extraction ───────────────────────────────────────────────────
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Extract and format dependency relationships from parsed PRD data.
|
|
176
|
+
*
|
|
177
|
+
* @gsd-api Parameters: dependencies (Array<{from, to}>).
|
|
178
|
+
* Returns: string — Markdown-formatted dependency visualization.
|
|
179
|
+
*
|
|
180
|
+
* @param {Array<{from: string, to: string}>} dependencies
|
|
181
|
+
* @returns {string} Markdown dependency section content
|
|
182
|
+
*/
|
|
183
|
+
function formatDependencies(dependencies) {
|
|
184
|
+
// @gsd-todo(ref:AC-3) Implement dependency visualization in FEATURES.md from PRD dependency sections
|
|
185
|
+
if (!dependencies || dependencies.length === 0) {
|
|
186
|
+
return 'No cross-feature dependencies documented.';
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const lines = [];
|
|
190
|
+
lines.push('```');
|
|
191
|
+
for (const dep of dependencies) {
|
|
192
|
+
lines.push(` ${dep.from} --> ${dep.to}`);
|
|
193
|
+
}
|
|
194
|
+
lines.push('```');
|
|
195
|
+
lines.push('');
|
|
196
|
+
|
|
197
|
+
// Also produce a readable list
|
|
198
|
+
for (const dep of dependencies) {
|
|
199
|
+
lines.push(`- **${dep.from}** depends on **${dep.to}**`);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return lines.join('\n');
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// ── FEATURES.md Generation ──────────────────────────────────────────────────
|
|
206
|
+
|
|
207
|
+
// @gsd-constraint FEATURES.md is a derived read-only artifact. It must never be manually edited.
|
|
208
|
+
// The header includes last-updated and source-hash to signal this.
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Generate the complete FEATURES.md content string.
|
|
212
|
+
*
|
|
213
|
+
* @gsd-api Parameters: enrichedAcs (Array), dependencies (Array), groups (string[]).
|
|
214
|
+
* Returns: string — complete FEATURES.md Markdown content with header, status table, and dependencies.
|
|
215
|
+
*
|
|
216
|
+
* @param {Array<{id: string, description: string, group: string, prdSource: string, status: string}>} enrichedAcs
|
|
217
|
+
* @param {Array<{from: string, to: string}>} dependencies
|
|
218
|
+
* @param {string[]} groups
|
|
219
|
+
* @param {string[]} prdSources - List of PRD file paths used as input
|
|
220
|
+
* @returns {string}
|
|
221
|
+
*/
|
|
222
|
+
function generateFeaturesMarkdown(enrichedAcs, dependencies, groups, prdSources) {
|
|
223
|
+
// @gsd-todo(ref:AC-5) Generate FEATURES.md as a derived read-only artifact with last-updated timestamp and source-hash header
|
|
224
|
+
const now = new Date().toISOString();
|
|
225
|
+
|
|
226
|
+
// Compute source hash from input data for staleness detection
|
|
227
|
+
const hashInput = JSON.stringify({ enrichedAcs, dependencies, prdSources });
|
|
228
|
+
const sourceHash = crypto.createHash('sha256').update(hashInput).digest('hex').slice(0, 12);
|
|
229
|
+
|
|
230
|
+
const totalAcs = enrichedAcs.length;
|
|
231
|
+
const doneAcs = enrichedAcs.filter(ac => ac.status === 'done').length;
|
|
232
|
+
const openAcs = totalAcs - doneAcs;
|
|
233
|
+
const completionPct = totalAcs > 0 ? Math.round((doneAcs / totalAcs) * 100) : 0;
|
|
234
|
+
|
|
235
|
+
const lines = [];
|
|
236
|
+
|
|
237
|
+
// ── Header ──
|
|
238
|
+
lines.push('# FEATURES.md');
|
|
239
|
+
lines.push('');
|
|
240
|
+
lines.push('> **This file is auto-generated. Do not edit manually.**');
|
|
241
|
+
lines.push(`> **Last updated:** ${now}`);
|
|
242
|
+
lines.push(`> **Source hash:** ${sourceHash}`);
|
|
243
|
+
lines.push(`> **Sources:** ${prdSources.join(', ')}`);
|
|
244
|
+
lines.push('');
|
|
245
|
+
|
|
246
|
+
// ── Overall Progress ──
|
|
247
|
+
lines.push('## Overall Progress');
|
|
248
|
+
lines.push('');
|
|
249
|
+
lines.push(`| Metric | Value |`);
|
|
250
|
+
lines.push(`|--------|-------|`);
|
|
251
|
+
lines.push(`| Total ACs | ${totalAcs} |`);
|
|
252
|
+
lines.push(`| Done | ${doneAcs} |`);
|
|
253
|
+
lines.push(`| Open | ${openAcs} |`);
|
|
254
|
+
lines.push(`| Completion | ${completionPct}% |`);
|
|
255
|
+
lines.push('');
|
|
256
|
+
|
|
257
|
+
// ── Features by Group ──
|
|
258
|
+
lines.push('## Features by Group');
|
|
259
|
+
lines.push('');
|
|
260
|
+
|
|
261
|
+
// Determine groups to render (use parsed groups, plus catch ungrouped)
|
|
262
|
+
const groupOrder = groups.length > 0 ? [...groups] : ['(ungrouped)'];
|
|
263
|
+
if (!groupOrder.includes('(ungrouped)')) {
|
|
264
|
+
const ungrouped = enrichedAcs.filter(ac => ac.group === '(ungrouped)');
|
|
265
|
+
if (ungrouped.length > 0) {
|
|
266
|
+
groupOrder.push('(ungrouped)');
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
for (const group of groupOrder) {
|
|
271
|
+
const groupAcs = enrichedAcs.filter(ac => ac.group === group);
|
|
272
|
+
if (groupAcs.length === 0) continue;
|
|
273
|
+
|
|
274
|
+
const groupDone = groupAcs.filter(ac => ac.status === 'done').length;
|
|
275
|
+
const groupTotal = groupAcs.length;
|
|
276
|
+
const groupPct = Math.round((groupDone / groupTotal) * 100);
|
|
277
|
+
|
|
278
|
+
lines.push(`### ${group}`);
|
|
279
|
+
lines.push('');
|
|
280
|
+
lines.push(`**Progress:** ${groupDone}/${groupTotal} (${groupPct}%)`);
|
|
281
|
+
lines.push('');
|
|
282
|
+
lines.push('| AC | Status | Description | Source |');
|
|
283
|
+
lines.push('|----|--------|-------------|--------|');
|
|
284
|
+
|
|
285
|
+
for (const ac of groupAcs) {
|
|
286
|
+
const statusIcon = ac.status === 'done' ? 'DONE' : 'OPEN';
|
|
287
|
+
lines.push(`| ${ac.id} | ${statusIcon} | ${ac.description} | ${ac.prdSource} |`);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
lines.push('');
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// ── Dependencies ──
|
|
294
|
+
lines.push('## Dependencies');
|
|
295
|
+
lines.push('');
|
|
296
|
+
lines.push(formatDependencies(dependencies));
|
|
297
|
+
lines.push('');
|
|
298
|
+
|
|
299
|
+
// ── Footer ──
|
|
300
|
+
lines.push('---');
|
|
301
|
+
lines.push('*Generated by feature-aggregator.cjs via `aggregate-features` subcommand.*');
|
|
302
|
+
lines.push('*Regenerated automatically on every `extract-tags` run.*');
|
|
303
|
+
lines.push('');
|
|
304
|
+
|
|
305
|
+
return lines.join('\n');
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// ── File Discovery ──────────────────────────────────────────────────────────
|
|
309
|
+
|
|
310
|
+
// @gsd-decision PRD discovery uses a simple glob: .planning/PRD.md and .planning/PRD-*.md
|
|
311
|
+
// No recursive search needed — PRDs live at the .planning/ root by convention.
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Discover all PRD files in the .planning/ directory.
|
|
315
|
+
*
|
|
316
|
+
* @param {string} planningDir - Path to .planning/ directory
|
|
317
|
+
* @returns {string[]} Array of absolute PRD file paths
|
|
318
|
+
*/
|
|
319
|
+
function discoverPrdFiles(planningDir) {
|
|
320
|
+
const prdFiles = [];
|
|
321
|
+
try {
|
|
322
|
+
const entries = fs.readdirSync(planningDir);
|
|
323
|
+
for (const entry of entries) {
|
|
324
|
+
if (entry === 'PRD.md' || (entry.startsWith('PRD-') && entry.endsWith('.md'))) {
|
|
325
|
+
prdFiles.push(path.join(planningDir, entry));
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
} catch {
|
|
329
|
+
// .planning/ may not exist yet
|
|
330
|
+
}
|
|
331
|
+
return prdFiles;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// ── CLI Entry Point ─────────────────────────────────────────────────────────
|
|
335
|
+
|
|
336
|
+
// @gsd-pattern CLI entry follows arc-scanner.cjs cmdExtractTags pattern:
|
|
337
|
+
// accept cwd + opts, resolve paths, call pure functions, write output.
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* CLI entry point: aggregate features from PRDs and CODE-INVENTORY.md,
|
|
341
|
+
* write .planning/FEATURES.md.
|
|
342
|
+
*
|
|
343
|
+
* Called by gsd-tools.cjs case 'aggregate-features'.
|
|
344
|
+
*
|
|
345
|
+
* @gsd-api CLI entry: cmdAggregateFeatures(cwd, opts).
|
|
346
|
+
* opts.outputFile defaults to .planning/FEATURES.md.
|
|
347
|
+
* opts.inventoryFile defaults to .planning/prototype/CODE-INVENTORY.md.
|
|
348
|
+
*
|
|
349
|
+
* @param {string} cwd - Current working directory
|
|
350
|
+
* @param {Object} [opts] - Options
|
|
351
|
+
* @param {string} [opts.outputFile] - Output path (default: .planning/FEATURES.md)
|
|
352
|
+
* @param {string} [opts.inventoryFile] - CODE-INVENTORY.md path
|
|
353
|
+
*/
|
|
354
|
+
function cmdAggregateFeatures(cwd, opts) {
|
|
355
|
+
// @gsd-todo(ref:AC-4) Wire aggregate-features into extract-tags auto-chain so FEATURES.md regenerates on every extract-tags run
|
|
356
|
+
opts = opts || {};
|
|
357
|
+
|
|
358
|
+
const planningDir = path.join(cwd, '.planning');
|
|
359
|
+
const outputFile = opts.outputFile || path.join(planningDir, 'FEATURES.md');
|
|
360
|
+
const inventoryFile = opts.inventoryFile || path.join(planningDir, 'prototype', 'CODE-INVENTORY.md');
|
|
361
|
+
|
|
362
|
+
// Step 1: Discover and parse PRD files
|
|
363
|
+
const prdFiles = discoverPrdFiles(planningDir);
|
|
364
|
+
if (prdFiles.length === 0) {
|
|
365
|
+
// @gsd-risk No PRDs found — FEATURES.md cannot be generated without at least one PRD.
|
|
366
|
+
// This is expected for projects that have not yet run /gsd:brainstorm or created a PRD manually.
|
|
367
|
+
process.stderr.write('feature-aggregator: No PRD files found in .planning/ — skipping FEATURES.md generation.\n');
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
let allAcs = [];
|
|
372
|
+
let allDependencies = [];
|
|
373
|
+
let allGroups = [];
|
|
374
|
+
const prdSources = [];
|
|
375
|
+
|
|
376
|
+
for (const prdFile of prdFiles) {
|
|
377
|
+
const content = fs.readFileSync(prdFile, 'utf-8');
|
|
378
|
+
const prdName = path.basename(prdFile);
|
|
379
|
+
prdSources.push(prdName);
|
|
380
|
+
|
|
381
|
+
const parsed = parsePrd(content, prdName);
|
|
382
|
+
allAcs = allAcs.concat(parsed.acs);
|
|
383
|
+
allDependencies = allDependencies.concat(parsed.dependencies);
|
|
384
|
+
for (const g of parsed.groups) {
|
|
385
|
+
if (!allGroups.includes(g)) allGroups.push(g);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Step 2: Parse CODE-INVENTORY.md for open @gsd-todo tags
|
|
390
|
+
let openTodoAcIds = new Set();
|
|
391
|
+
try {
|
|
392
|
+
const inventoryContent = fs.readFileSync(inventoryFile, 'utf-8');
|
|
393
|
+
openTodoAcIds = parseOpenTodos(inventoryContent);
|
|
394
|
+
} catch {
|
|
395
|
+
// CODE-INVENTORY.md may not exist yet — treat all ACs as done (no open todos)
|
|
396
|
+
// @gsd-risk If CODE-INVENTORY.md is missing, all ACs appear "done" by default.
|
|
397
|
+
// This is intentional: no code = no open todos. But it may confuse users on first run.
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Step 3: Cross-reference to determine AC completion status
|
|
401
|
+
const enrichedAcs = crossReference(allAcs, openTodoAcIds);
|
|
402
|
+
|
|
403
|
+
// Step 4: Generate FEATURES.md content
|
|
404
|
+
const markdown = generateFeaturesMarkdown(enrichedAcs, allDependencies, allGroups, prdSources);
|
|
405
|
+
|
|
406
|
+
// Step 5: Write output
|
|
407
|
+
const outDir = path.dirname(outputFile);
|
|
408
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
409
|
+
fs.writeFileSync(outputFile, markdown, 'utf-8');
|
|
410
|
+
|
|
411
|
+
process.stderr.write(`feature-aggregator: Wrote ${outputFile} (${allAcs.length} ACs, ${prdSources.length} PRD(s))\n`);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
module.exports = {
|
|
415
|
+
parsePrd,
|
|
416
|
+
parseOpenTodos,
|
|
417
|
+
crossReference,
|
|
418
|
+
formatDependencies,
|
|
419
|
+
generateFeaturesMarkdown,
|
|
420
|
+
discoverPrdFiles,
|
|
421
|
+
cmdAggregateFeatures,
|
|
422
|
+
};
|