@bhargavvc/sdd-cc 1.30.1 → 1.35.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/README.ja-JP.md +144 -110
- package/README.ko-KR.md +143 -107
- package/README.md +183 -112
- package/README.pt-BR.md +90 -52
- package/README.zh-CN.md +141 -101
- package/agents/sdd-advisor-researcher.md +23 -0
- package/agents/sdd-ai-researcher.md +133 -0
- package/agents/sdd-code-fixer.md +516 -0
- package/agents/sdd-code-reviewer.md +355 -0
- package/agents/sdd-codebase-mapper.md +3 -3
- package/agents/sdd-debugger.md +17 -5
- package/agents/sdd-doc-verifier.md +201 -0
- package/agents/sdd-doc-writer.md +602 -0
- package/agents/sdd-domain-researcher.md +153 -0
- package/agents/sdd-eval-auditor.md +164 -0
- package/agents/sdd-eval-planner.md +154 -0
- package/agents/sdd-executor.md +87 -4
- package/agents/sdd-framework-selector.md +160 -0
- package/agents/sdd-intel-updater.md +314 -0
- package/agents/sdd-nyquist-auditor.md +1 -1
- package/agents/sdd-phase-researcher.md +71 -4
- package/agents/sdd-plan-checker.md +100 -6
- package/agents/sdd-planner.md +145 -206
- package/agents/sdd-project-researcher.md +25 -2
- package/agents/sdd-research-synthesizer.md +3 -3
- package/agents/sdd-roadmapper.md +6 -6
- package/agents/sdd-security-auditor.md +128 -0
- package/agents/sdd-ui-auditor.md +43 -3
- package/agents/sdd-ui-checker.md +5 -5
- package/agents/sdd-ui-researcher.md +27 -4
- package/agents/sdd-user-profiler.md +2 -2
- package/agents/sdd-verifier.md +142 -22
- package/bin/install.js +2145 -545
- package/commands/sdd/add-backlog.md +5 -5
- package/commands/sdd/add-tests.md +2 -2
- package/commands/sdd/ai-integration-phase.md +36 -0
- package/commands/sdd/analyze-dependencies.md +34 -0
- package/commands/sdd/audit-fix.md +33 -0
- package/commands/sdd/autonomous.md +7 -2
- package/commands/sdd/cleanup.md +5 -0
- package/commands/sdd/code-review-fix.md +52 -0
- package/commands/sdd/code-review.md +55 -0
- package/commands/sdd/complete-milestone.md +6 -6
- package/commands/sdd/debug.md +22 -9
- package/commands/sdd/discuss-phase.md +7 -2
- package/commands/sdd/do.md +1 -1
- package/commands/sdd/docs-update.md +48 -0
- package/commands/sdd/eval-review.md +32 -0
- package/commands/sdd/execute-phase.md +4 -0
- package/commands/sdd/explore.md +27 -0
- package/commands/sdd/fast.md +2 -2
- package/commands/sdd/from-sdd2.md +45 -0
- package/commands/sdd/help.md +2 -0
- package/commands/sdd/import.md +36 -0
- package/commands/sdd/intel.md +179 -0
- package/commands/sdd/join-discord.md +2 -1
- package/commands/sdd/manager.md +1 -0
- package/commands/sdd/map-codebase.md +3 -3
- package/commands/sdd/new-milestone.md +1 -1
- package/commands/sdd/new-project.md +5 -1
- package/commands/sdd/new-workspace.md +1 -1
- package/commands/sdd/next.md +2 -0
- package/commands/sdd/plan-milestone-gaps.md +2 -2
- package/commands/sdd/plan-phase.md +6 -1
- package/commands/sdd/plant-seed.md +1 -1
- package/commands/sdd/profile-user.md +1 -1
- package/commands/sdd/quick.md +5 -3
- package/commands/sdd/reapply-patches.md +230 -42
- package/commands/sdd/research-phase.md +3 -3
- package/commands/sdd/review-backlog.md +1 -0
- package/commands/sdd/review.md +6 -3
- package/commands/sdd/scan.md +26 -0
- package/commands/sdd/secure-phase.md +35 -0
- package/commands/sdd/ship.md +1 -1
- package/commands/sdd/thread.md +5 -5
- package/commands/sdd/undo.md +34 -0
- package/commands/sdd/verify-work.md +1 -1
- package/commands/sdd/workstreams.md +17 -11
- package/hooks/dist/sdd-check-update.js +33 -8
- package/hooks/dist/sdd-context-monitor.js +17 -8
- package/hooks/dist/sdd-phase-boundary.sh +27 -0
- package/hooks/dist/sdd-prompt-guard.js +1 -0
- package/hooks/dist/sdd-read-guard.js +82 -0
- package/hooks/dist/sdd-session-state.sh +33 -0
- package/hooks/dist/sdd-statusline.js +137 -15
- package/hooks/dist/sdd-validate-commit.sh +47 -0
- package/hooks/dist/sdd-workflow-guard.js +4 -4
- package/hooks/sdd-check-update.js +139 -0
- package/hooks/sdd-context-monitor.js +165 -0
- package/hooks/sdd-phase-boundary.sh +27 -0
- package/hooks/sdd-prompt-guard.js +97 -0
- package/hooks/sdd-read-guard.js +82 -0
- package/hooks/sdd-session-state.sh +33 -0
- package/hooks/sdd-statusline.js +241 -0
- package/hooks/sdd-validate-commit.sh +47 -0
- package/hooks/sdd-workflow-guard.js +94 -0
- package/package.json +3 -3
- package/scripts/build-hooks.js +18 -7
- package/scripts/prompt-injection-scan.sh +1 -0
- package/scripts/rebrand-gsd-to-sdd.sh +221 -220
- package/scripts/run-tests.cjs +5 -1
- package/scripts/sync-upstream.sh +1 -1
- package/sdd/bin/lib/commands.cjs +79 -17
- package/sdd/bin/lib/config.cjs +90 -48
- package/sdd/bin/lib/core.cjs +452 -87
- package/sdd/bin/lib/docs.cjs +267 -0
- package/sdd/bin/lib/frontmatter.cjs +381 -336
- package/sdd/bin/lib/init.cjs +110 -16
- package/sdd/bin/lib/intel.cjs +660 -0
- package/sdd/bin/lib/learnings.cjs +378 -0
- package/sdd/bin/lib/milestone.cjs +42 -11
- package/sdd/bin/lib/model-profiles.cjs +17 -15
- package/sdd/bin/lib/phase.cjs +367 -288
- package/sdd/bin/lib/profile-output.cjs +106 -10
- package/sdd/bin/lib/roadmap.cjs +146 -115
- package/sdd/bin/lib/schema-detect.cjs +238 -0
- package/sdd/bin/lib/sdd2-import.cjs +511 -0
- package/sdd/bin/lib/security.cjs +124 -3
- package/sdd/bin/lib/state.cjs +648 -264
- package/sdd/bin/lib/template.cjs +8 -4
- package/sdd/bin/lib/verify.cjs +209 -28
- package/sdd/bin/lib/workstream.cjs +7 -3
- package/sdd/bin/sdd-tools.cjs +184 -12
- package/sdd/contexts/dev.md +21 -0
- package/sdd/contexts/research.md +22 -0
- package/sdd/contexts/review.md +22 -0
- package/sdd/references/agent-contracts.md +79 -0
- package/sdd/references/ai-evals.md +156 -0
- package/sdd/references/ai-frameworks.md +186 -0
- package/sdd/references/artifact-types.md +113 -0
- package/sdd/references/common-bug-patterns.md +114 -0
- package/sdd/references/context-budget.md +49 -0
- package/sdd/references/continuation-format.md +25 -25
- package/sdd/references/domain-probes.md +125 -0
- package/sdd/references/few-shot-examples/plan-checker.md +73 -0
- package/sdd/references/few-shot-examples/verifier.md +109 -0
- package/sdd/references/gate-prompts.md +100 -0
- package/sdd/references/gates.md +70 -0
- package/sdd/references/git-integration.md +1 -1
- package/sdd/references/ios-scaffold.md +123 -0
- package/sdd/references/model-profile-resolution.md +2 -0
- package/sdd/references/model-profiles.md +24 -18
- package/sdd/references/planner-gap-closure.md +62 -0
- package/sdd/references/planner-reviews.md +39 -0
- package/sdd/references/planner-revision.md +87 -0
- package/sdd/references/planning-config.md +252 -0
- package/sdd/references/revision-loop.md +97 -0
- package/sdd/references/thinking-models-debug.md +44 -0
- package/sdd/references/thinking-models-execution.md +50 -0
- package/sdd/references/thinking-models-planning.md +62 -0
- package/sdd/references/thinking-models-research.md +50 -0
- package/sdd/references/thinking-models-verification.md +55 -0
- package/sdd/references/thinking-partner.md +96 -0
- package/sdd/references/ui-brand.md +4 -4
- package/sdd/references/universal-anti-patterns.md +63 -0
- package/sdd/references/verification-overrides.md +227 -0
- package/sdd/references/workstream-flag.md +56 -3
- package/sdd/templates/AI-SPEC.md +246 -0
- package/sdd/templates/DEBUG.md +1 -1
- package/sdd/templates/SECURITY.md +61 -0
- package/sdd/templates/UAT.md +4 -4
- package/sdd/templates/VALIDATION.md +4 -4
- package/sdd/templates/claude-md.md +32 -9
- package/sdd/templates/config.json +4 -0
- package/sdd/templates/debug-subagent-prompt.md +1 -1
- package/sdd/templates/dev-preferences.md +1 -1
- package/sdd/templates/discovery.md +2 -2
- package/sdd/templates/phase-prompt.md +1 -1
- package/sdd/templates/planner-subagent-prompt.md +3 -3
- package/sdd/templates/project.md +1 -1
- package/sdd/templates/research.md +1 -1
- package/sdd/templates/state.md +2 -2
- package/sdd/workflows/add-phase.md +8 -8
- package/sdd/workflows/add-tests.md +12 -9
- package/sdd/workflows/add-todo.md +5 -3
- package/sdd/workflows/ai-integration-phase.md +284 -0
- package/sdd/workflows/analyze-dependencies.md +96 -0
- package/sdd/workflows/audit-fix.md +157 -0
- package/sdd/workflows/audit-milestone.md +11 -11
- package/sdd/workflows/audit-uat.md +2 -2
- package/sdd/workflows/autonomous.md +195 -27
- package/sdd/workflows/check-todos.md +12 -10
- package/sdd/workflows/cleanup.md +2 -0
- package/sdd/workflows/code-review-fix.md +497 -0
- package/sdd/workflows/code-review.md +515 -0
- package/sdd/workflows/complete-milestone.md +56 -22
- package/sdd/workflows/diagnose-issues.md +10 -3
- package/sdd/workflows/discovery-phase.md +5 -3
- package/sdd/workflows/discuss-phase-assumptions.md +24 -6
- package/sdd/workflows/discuss-phase-power.md +291 -0
- package/sdd/workflows/discuss-phase.md +173 -21
- package/sdd/workflows/do.md +23 -21
- package/sdd/workflows/docs-update.md +1155 -0
- package/sdd/workflows/eval-review.md +155 -0
- package/sdd/workflows/execute-phase.md +594 -38
- package/sdd/workflows/execute-plan.md +67 -96
- package/sdd/workflows/explore.md +139 -0
- package/sdd/workflows/fast.md +5 -5
- package/sdd/workflows/forensics.md +2 -2
- package/sdd/workflows/health.md +4 -4
- package/sdd/workflows/help.md +122 -119
- package/sdd/workflows/import.md +276 -0
- package/sdd/workflows/inbox.md +387 -0
- package/sdd/workflows/insert-phase.md +7 -7
- package/sdd/workflows/list-phase-assumptions.md +4 -4
- package/sdd/workflows/list-workspaces.md +2 -2
- package/sdd/workflows/manager.md +35 -32
- package/sdd/workflows/map-codebase.md +7 -5
- package/sdd/workflows/milestone-summary.md +2 -2
- package/sdd/workflows/new-milestone.md +17 -9
- package/sdd/workflows/new-project.md +50 -25
- package/sdd/workflows/new-workspace.md +7 -5
- package/sdd/workflows/next.md +67 -11
- package/sdd/workflows/note.md +9 -7
- package/sdd/workflows/pause-work.md +75 -12
- package/sdd/workflows/plan-milestone-gaps.md +8 -8
- package/sdd/workflows/plan-phase.md +294 -42
- package/sdd/workflows/plant-seed.md +6 -3
- package/sdd/workflows/pr-branch.md +42 -14
- package/sdd/workflows/profile-user.md +9 -7
- package/sdd/workflows/progress.md +45 -45
- package/sdd/workflows/quick.md +195 -47
- package/sdd/workflows/remove-phase.md +6 -6
- package/sdd/workflows/remove-workspace.md +3 -1
- package/sdd/workflows/research-phase.md +2 -2
- package/sdd/workflows/resume-project.md +12 -12
- package/sdd/workflows/review.md +109 -9
- package/sdd/workflows/scan.md +102 -0
- package/sdd/workflows/secure-phase.md +166 -0
- package/sdd/workflows/session-report.md +2 -2
- package/sdd/workflows/settings.md +38 -12
- package/sdd/workflows/ship.md +21 -9
- package/sdd/workflows/stats.md +1 -1
- package/sdd/workflows/transition.md +23 -23
- package/sdd/workflows/ui-phase.md +15 -7
- package/sdd/workflows/ui-review.md +29 -4
- package/sdd/workflows/undo.md +314 -0
- package/sdd/workflows/update.md +171 -20
- package/sdd/workflows/validate-phase.md +6 -4
- package/sdd/workflows/verify-phase.md +210 -6
- package/sdd/workflows/verify-work.md +83 -9
- package/sdd/commands/sdd/workstreams.md +0 -63
|
@@ -173,19 +173,23 @@ const CLAUDE_INSTRUCTIONS = {
|
|
|
173
173
|
};
|
|
174
174
|
|
|
175
175
|
const CLAUDE_MD_FALLBACKS = {
|
|
176
|
-
project: 'Project not yet initialized. Run /sdd
|
|
176
|
+
project: 'Project not yet initialized. Run /sdd-new-project to set up.',
|
|
177
177
|
stack: 'Technology stack not yet documented. Will populate after codebase mapping or first phase.',
|
|
178
178
|
conventions: 'Conventions not yet established. Will populate as patterns emerge during development.',
|
|
179
179
|
architecture: 'Architecture not yet mapped. Follow existing patterns found in the codebase.',
|
|
180
|
+
skills: 'No project skills found. Add skills to any of: `.claude/skills/`, `.agents/skills/`, `.cursor/skills/`, or `.github/skills/` with a `SKILL.md` index file.',
|
|
180
181
|
};
|
|
181
182
|
|
|
183
|
+
// Directories where project skills may live (checked in order)
|
|
184
|
+
const SKILL_SEARCH_DIRS = ['.claude/skills', '.agents/skills', '.cursor/skills', '.github/skills'];
|
|
185
|
+
|
|
182
186
|
const CLAUDE_MD_WORKFLOW_ENFORCEMENT = [
|
|
183
187
|
'Before using Edit, Write, or other file-changing tools, start work through a SDD command so planning artifacts and execution context stay in sync.',
|
|
184
188
|
'',
|
|
185
189
|
'Use these entry points:',
|
|
186
|
-
'- `/sdd
|
|
187
|
-
'- `/sdd
|
|
188
|
-
'- `/sdd
|
|
190
|
+
'- `/sdd-quick` for small fixes, doc updates, and ad-hoc tasks',
|
|
191
|
+
'- `/sdd-debug` for investigation and bug fixing',
|
|
192
|
+
'- `/sdd-execute-phase` for planned phase work',
|
|
189
193
|
'',
|
|
190
194
|
'Do not make direct repo edits outside a SDD workflow unless the user explicitly asks to bypass it.',
|
|
191
195
|
].join('\n');
|
|
@@ -194,7 +198,7 @@ const CLAUDE_MD_PROFILE_PLACEHOLDER = [
|
|
|
194
198
|
'<!-- SDD:profile-start -->',
|
|
195
199
|
'## Developer Profile',
|
|
196
200
|
'',
|
|
197
|
-
'> Profile not yet configured. Run `/sdd
|
|
201
|
+
'> Profile not yet configured. Run `/sdd-profile-user` to generate your developer profile.',
|
|
198
202
|
'> This section is managed by `generate-claude-profile` -- do not edit manually.',
|
|
199
203
|
'<!-- SDD:profile-end -->',
|
|
200
204
|
].join('\n');
|
|
@@ -375,6 +379,96 @@ function generateWorkflowSection() {
|
|
|
375
379
|
};
|
|
376
380
|
}
|
|
377
381
|
|
|
382
|
+
/**
|
|
383
|
+
* Discover project skills from standard directories and extract frontmatter
|
|
384
|
+
* (name + description) for each. Returns a table summary for CLAUDE.md so
|
|
385
|
+
* agents know which skills are available at session startup (Layer 1 discovery).
|
|
386
|
+
*/
|
|
387
|
+
function generateSkillsSection(cwd) {
|
|
388
|
+
const discovered = [];
|
|
389
|
+
|
|
390
|
+
for (const dir of SKILL_SEARCH_DIRS) {
|
|
391
|
+
const absDir = path.join(cwd, dir);
|
|
392
|
+
if (!fs.existsSync(absDir)) continue;
|
|
393
|
+
|
|
394
|
+
let entries;
|
|
395
|
+
try {
|
|
396
|
+
entries = fs.readdirSync(absDir, { withFileTypes: true });
|
|
397
|
+
} catch {
|
|
398
|
+
continue;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
for (const entry of entries) {
|
|
402
|
+
if (!entry.isDirectory()) continue;
|
|
403
|
+
// Skip SDD's own installed skills — only surface project-specific skills
|
|
404
|
+
if (entry.name.startsWith('sdd-')) continue;
|
|
405
|
+
|
|
406
|
+
const skillMdPath = path.join(absDir, entry.name, 'SKILL.md');
|
|
407
|
+
if (!fs.existsSync(skillMdPath)) continue;
|
|
408
|
+
|
|
409
|
+
const content = safeReadFile(skillMdPath);
|
|
410
|
+
if (!content) continue;
|
|
411
|
+
|
|
412
|
+
const frontmatter = extractSkillFrontmatter(content);
|
|
413
|
+
const name = frontmatter.name || entry.name;
|
|
414
|
+
const description = frontmatter.description || '';
|
|
415
|
+
|
|
416
|
+
// Avoid duplicates when same skill dir is symlinked from multiple locations
|
|
417
|
+
if (discovered.some(s => s.name === name)) continue;
|
|
418
|
+
|
|
419
|
+
discovered.push({ name, description, path: `${dir}/${entry.name}` });
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (discovered.length === 0) {
|
|
424
|
+
return { content: CLAUDE_MD_FALLBACKS.skills, source: 'skills/', hasFallback: true };
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const lines = ['| Skill | Description | Path |', '|-------|-------------|------|'];
|
|
428
|
+
for (const skill of discovered) {
|
|
429
|
+
// Sanitize table cell content (escape pipes)
|
|
430
|
+
const desc = skill.description.replace(/\|/g, '\\|').replace(/\n/g, ' ').trim();
|
|
431
|
+
const safeName = skill.name.replace(/\|/g, '\\|');
|
|
432
|
+
lines.push(`| ${safeName} | ${desc} | \`${skill.path}/SKILL.md\` |`);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
return { content: lines.join('\n'), source: 'skills/', hasFallback: false };
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Extract name and description from YAML-like frontmatter in a SKILL.md file.
|
|
440
|
+
* Handles multi-line description values (continuation lines indented with spaces).
|
|
441
|
+
*/
|
|
442
|
+
function extractSkillFrontmatter(content) {
|
|
443
|
+
const result = { name: '', description: '' };
|
|
444
|
+
const fmMatch = content.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
445
|
+
if (!fmMatch) return result;
|
|
446
|
+
|
|
447
|
+
const fmBlock = fmMatch[1];
|
|
448
|
+
const lines = fmBlock.split('\n');
|
|
449
|
+
|
|
450
|
+
let currentKey = '';
|
|
451
|
+
for (const line of lines) {
|
|
452
|
+
// Top-level key: value
|
|
453
|
+
const kvMatch = line.match(/^(\w[\w-]*):\s*(.*)/);
|
|
454
|
+
if (kvMatch) {
|
|
455
|
+
currentKey = kvMatch[1];
|
|
456
|
+
const value = kvMatch[2].trim();
|
|
457
|
+
if (currentKey === 'name') result.name = value;
|
|
458
|
+
if (currentKey === 'description') result.description = value;
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
// Continuation line (indented) for multi-line values
|
|
462
|
+
if (currentKey === 'description' && /^\s+/.test(line)) {
|
|
463
|
+
result.description += ' ' + line.trim();
|
|
464
|
+
} else {
|
|
465
|
+
currentKey = '';
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
return result;
|
|
470
|
+
}
|
|
471
|
+
|
|
378
472
|
// ─── Commands ─────────────────────────────────────────────────────────────────
|
|
379
473
|
|
|
380
474
|
function cmdWriteProfile(cwd, options, raw) {
|
|
@@ -671,7 +765,7 @@ function cmdGenerateDevPreferences(cwd, options, raw) {
|
|
|
671
765
|
|
|
672
766
|
let stackBlock;
|
|
673
767
|
if (analysis.data_source === 'questionnaire') {
|
|
674
|
-
stackBlock = 'Stack preferences not available (questionnaire-only profile). Run `/sdd
|
|
768
|
+
stackBlock = 'Stack preferences not available (questionnaire-only profile). Run `/sdd-profile-user --refresh` with session data to populate.';
|
|
675
769
|
} else if (options.stack) {
|
|
676
770
|
stackBlock = options.stack;
|
|
677
771
|
} else {
|
|
@@ -691,7 +785,7 @@ function cmdGenerateDevPreferences(cwd, options, raw) {
|
|
|
691
785
|
|
|
692
786
|
const result = {
|
|
693
787
|
command_path: outputPath,
|
|
694
|
-
command_name: '/sdd
|
|
788
|
+
command_name: '/sdd-dev-preferences',
|
|
695
789
|
dimensions_included: dimensionsIncluded,
|
|
696
790
|
source: analysis.data_source || 'session_analysis',
|
|
697
791
|
};
|
|
@@ -757,7 +851,7 @@ function cmdGenerateClaudeProfile(cwd, options, raw) {
|
|
|
757
851
|
'<!-- SDD:profile-start -->',
|
|
758
852
|
'## Developer Profile',
|
|
759
853
|
'',
|
|
760
|
-
`> Generated by SDD from ${dataSource}. Run \`/sdd
|
|
854
|
+
`> Generated by SDD from ${dataSource}. Run \`/sdd-profile-user --refresh\` to update.`,
|
|
761
855
|
'',
|
|
762
856
|
'| Dimension | Rating | Confidence |',
|
|
763
857
|
'|-----------|--------|------------|',
|
|
@@ -815,12 +909,13 @@ function cmdGenerateClaudeProfile(cwd, options, raw) {
|
|
|
815
909
|
}
|
|
816
910
|
|
|
817
911
|
function cmdGenerateClaudeMd(cwd, options, raw) {
|
|
818
|
-
const MANAGED_SECTIONS = ['project', 'stack', 'conventions', 'architecture', 'workflow'];
|
|
912
|
+
const MANAGED_SECTIONS = ['project', 'stack', 'conventions', 'architecture', 'skills', 'workflow'];
|
|
819
913
|
const generators = {
|
|
820
914
|
project: generateProjectSection,
|
|
821
915
|
stack: generateStackSection,
|
|
822
916
|
conventions: generateConventionsSection,
|
|
823
917
|
architecture: generateArchitectureSection,
|
|
918
|
+
skills: generateSkillsSection,
|
|
824
919
|
workflow: generateWorkflowSection,
|
|
825
920
|
};
|
|
826
921
|
const sectionHeadings = {
|
|
@@ -828,6 +923,7 @@ function cmdGenerateClaudeMd(cwd, options, raw) {
|
|
|
828
923
|
stack: '## Technology Stack',
|
|
829
924
|
conventions: '## Conventions',
|
|
830
925
|
architecture: '## Architecture',
|
|
926
|
+
skills: '## Project Skills',
|
|
831
927
|
workflow: '## SDD Workflow Enforcement',
|
|
832
928
|
};
|
|
833
929
|
|
|
@@ -925,7 +1021,7 @@ function cmdGenerateClaudeMd(cwd, options, raw) {
|
|
|
925
1021
|
let message = `Generated ${genCount}/${totalManaged} sections.`;
|
|
926
1022
|
if (sectionsFallback.length > 0) message += ` Fallback: ${sectionsFallback.join(', ')}.`;
|
|
927
1023
|
if (sectionsSkipped.length > 0) message += ` Skipped (manually edited): ${sectionsSkipped.join(', ')}.`;
|
|
928
|
-
if (profileStatus === 'placeholder_added') message += ' Run /sdd
|
|
1024
|
+
if (profileStatus === 'placeholder_added') message += ' Run /sdd-profile-user to unlock Developer Profile.';
|
|
929
1025
|
|
|
930
1026
|
const result = {
|
|
931
1027
|
claude_md_path: outputPath,
|
package/sdd/bin/lib/roadmap.cjs
CHANGED
|
@@ -4,7 +4,73 @@
|
|
|
4
4
|
|
|
5
5
|
const fs = require('fs');
|
|
6
6
|
const path = require('path');
|
|
7
|
-
const { escapeRegex, normalizePhaseName, planningPaths, output, error, findPhaseInternal, stripShippedMilestones, extractCurrentMilestone, replaceInCurrentMilestone } = require('./core.cjs');
|
|
7
|
+
const { escapeRegex, normalizePhaseName, planningPaths, withPlanningLock, output, error, findPhaseInternal, stripShippedMilestones, extractCurrentMilestone, replaceInCurrentMilestone, phaseTokenMatches, atomicWriteFileSync } = require('./core.cjs');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Search for a phase header (and its section) within the given content string.
|
|
11
|
+
* Returns a result object if found (either a full match or a malformed_roadmap
|
|
12
|
+
* checklist-only match), or null if the phase is not present at all.
|
|
13
|
+
*/
|
|
14
|
+
function searchPhaseInContent(content, escapedPhase, phaseNum) {
|
|
15
|
+
// Match "## Phase X:", "### Phase X:", or "#### Phase X:" with optional name
|
|
16
|
+
const phasePattern = new RegExp(
|
|
17
|
+
`#{2,4}\\s*Phase\\s+${escapedPhase}:\\s*([^\\n]+)`,
|
|
18
|
+
'i'
|
|
19
|
+
);
|
|
20
|
+
const headerMatch = content.match(phasePattern);
|
|
21
|
+
|
|
22
|
+
if (!headerMatch) {
|
|
23
|
+
// Fallback: check if phase exists in summary list but missing detail section
|
|
24
|
+
const checklistPattern = new RegExp(
|
|
25
|
+
`-\\s*\\[[ x]\\]\\s*\\*\\*Phase\\s+${escapedPhase}:\\s*([^*]+)\\*\\*`,
|
|
26
|
+
'i'
|
|
27
|
+
);
|
|
28
|
+
const checklistMatch = content.match(checklistPattern);
|
|
29
|
+
|
|
30
|
+
if (checklistMatch) {
|
|
31
|
+
return {
|
|
32
|
+
found: false,
|
|
33
|
+
phase_number: phaseNum,
|
|
34
|
+
phase_name: checklistMatch[1].trim(),
|
|
35
|
+
error: 'malformed_roadmap',
|
|
36
|
+
message: `Phase ${phaseNum} exists in summary list but missing "### Phase ${phaseNum}:" detail section. ROADMAP.md needs both formats.`
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const phaseName = headerMatch[1].trim();
|
|
44
|
+
const headerIndex = headerMatch.index;
|
|
45
|
+
|
|
46
|
+
// Find the end of this section (next ## or ### phase header, or end of file)
|
|
47
|
+
const restOfContent = content.slice(headerIndex);
|
|
48
|
+
const nextHeaderMatch = restOfContent.match(/\n#{2,4}\s+Phase\s+\d/i);
|
|
49
|
+
const sectionEnd = nextHeaderMatch
|
|
50
|
+
? headerIndex + nextHeaderMatch.index
|
|
51
|
+
: content.length;
|
|
52
|
+
|
|
53
|
+
const section = content.slice(headerIndex, sectionEnd).trim();
|
|
54
|
+
|
|
55
|
+
// Extract goal if present (supports both **Goal:** and **Goal**: formats)
|
|
56
|
+
const goalMatch = section.match(/\*\*Goal(?::\*\*|\*\*:)\s*([^\n]+)/i);
|
|
57
|
+
const goal = goalMatch ? goalMatch[1].trim() : null;
|
|
58
|
+
|
|
59
|
+
// Extract success criteria as structured array
|
|
60
|
+
const criteriaMatch = section.match(/\*\*Success Criteria\*\*[^\n]*:\s*\n((?:\s*\d+\.\s*[^\n]+\n?)+)/i);
|
|
61
|
+
const success_criteria = criteriaMatch
|
|
62
|
+
? criteriaMatch[1].trim().split('\n').map(line => line.replace(/^\s*\d+\.\s*/, '').trim()).filter(Boolean)
|
|
63
|
+
: [];
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
found: true,
|
|
67
|
+
phase_number: phaseNum,
|
|
68
|
+
phase_name: phaseName,
|
|
69
|
+
goal,
|
|
70
|
+
success_criteria,
|
|
71
|
+
section,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
8
74
|
|
|
9
75
|
function cmdRoadmapGetPhase(cwd, phaseNum, raw) {
|
|
10
76
|
const roadmapPath = planningPaths(cwd).roadmap;
|
|
@@ -15,76 +81,32 @@ function cmdRoadmapGetPhase(cwd, phaseNum, raw) {
|
|
|
15
81
|
}
|
|
16
82
|
|
|
17
83
|
try {
|
|
18
|
-
const
|
|
84
|
+
const rawContent = fs.readFileSync(roadmapPath, 'utf-8');
|
|
85
|
+
const milestoneContent = extractCurrentMilestone(rawContent, cwd);
|
|
19
86
|
|
|
20
87
|
// Escape special regex chars in phase number, handle decimal
|
|
21
88
|
const escapedPhase = escapeRegex(phaseNum);
|
|
22
89
|
|
|
23
|
-
//
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
);
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
// Fallback: check if phase exists in summary list but missing detail section
|
|
32
|
-
const checklistPattern = new RegExp(
|
|
33
|
-
`-\\s*\\[[ x]\\]\\s*\\*\\*Phase\\s+${escapedPhase}:\\s*([^*]+)\\*\\*`,
|
|
34
|
-
'i'
|
|
35
|
-
);
|
|
36
|
-
const checklistMatch = content.match(checklistPattern);
|
|
37
|
-
|
|
38
|
-
if (checklistMatch) {
|
|
39
|
-
// Phase exists in summary but missing detail section - malformed ROADMAP
|
|
40
|
-
output({
|
|
41
|
-
found: false,
|
|
42
|
-
phase_number: phaseNum,
|
|
43
|
-
phase_name: checklistMatch[1].trim(),
|
|
44
|
-
error: 'malformed_roadmap',
|
|
45
|
-
message: `Phase ${phaseNum} exists in summary list but missing "### Phase ${phaseNum}:" detail section. ROADMAP.md needs both formats.`
|
|
46
|
-
}, raw, '');
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
90
|
+
// Search the current milestone slice first, then fall back to full roadmap.
|
|
91
|
+
// A malformed_roadmap result (checklist-only) from the milestone should not
|
|
92
|
+
// block finding a full header match in the wider roadmap content.
|
|
93
|
+
const fullContent = stripShippedMilestones(rawContent);
|
|
94
|
+
const milestoneResult = searchPhaseInContent(milestoneContent, escapedPhase, phaseNum);
|
|
95
|
+
const result = (milestoneResult && !milestoneResult.error)
|
|
96
|
+
? milestoneResult
|
|
97
|
+
: searchPhaseInContent(fullContent, escapedPhase, phaseNum) || milestoneResult;
|
|
49
98
|
|
|
99
|
+
if (!result) {
|
|
50
100
|
output({ found: false, phase_number: phaseNum }, raw, '');
|
|
51
101
|
return;
|
|
52
102
|
}
|
|
53
103
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const restOfContent = content.slice(headerIndex);
|
|
59
|
-
const nextHeaderMatch = restOfContent.match(/\n#{2,4}\s+Phase\s+\d/i);
|
|
60
|
-
const sectionEnd = nextHeaderMatch
|
|
61
|
-
? headerIndex + nextHeaderMatch.index
|
|
62
|
-
: content.length;
|
|
63
|
-
|
|
64
|
-
const section = content.slice(headerIndex, sectionEnd).trim();
|
|
65
|
-
|
|
66
|
-
// Extract goal if present (supports both **Goal:** and **Goal**: formats)
|
|
67
|
-
const goalMatch = section.match(/\*\*Goal(?::\*\*|\*\*:)\s*([^\n]+)/i);
|
|
68
|
-
const goal = goalMatch ? goalMatch[1].trim() : null;
|
|
69
|
-
|
|
70
|
-
// Extract success criteria as structured array
|
|
71
|
-
const criteriaMatch = section.match(/\*\*Success Criteria\*\*[^\n]*:\s*\n((?:\s*\d+\.\s*[^\n]+\n?)+)/i);
|
|
72
|
-
const success_criteria = criteriaMatch
|
|
73
|
-
? criteriaMatch[1].trim().split('\n').map(line => line.replace(/^\s*\d+\.\s*/, '').trim()).filter(Boolean)
|
|
74
|
-
: [];
|
|
104
|
+
if (result.error) {
|
|
105
|
+
output(result, raw, '');
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
75
108
|
|
|
76
|
-
output(
|
|
77
|
-
{
|
|
78
|
-
found: true,
|
|
79
|
-
phase_number: phaseNum,
|
|
80
|
-
phase_name: phaseName,
|
|
81
|
-
goal,
|
|
82
|
-
success_criteria,
|
|
83
|
-
section,
|
|
84
|
-
},
|
|
85
|
-
raw,
|
|
86
|
-
section
|
|
87
|
-
);
|
|
109
|
+
output(result, raw, result.section);
|
|
88
110
|
} catch (e) {
|
|
89
111
|
error('Failed to read ROADMAP.md: ' + e.message);
|
|
90
112
|
}
|
|
@@ -107,6 +129,15 @@ function cmdRoadmapAnalyze(cwd, raw) {
|
|
|
107
129
|
const phases = [];
|
|
108
130
|
let match;
|
|
109
131
|
|
|
132
|
+
// Build phase directory lookup once (O(1) readdir instead of O(N) per phase)
|
|
133
|
+
const _phaseDirNames = (() => {
|
|
134
|
+
try {
|
|
135
|
+
return fs.readdirSync(phasesDir, { withFileTypes: true })
|
|
136
|
+
.filter(e => e.isDirectory())
|
|
137
|
+
.map(e => e.name);
|
|
138
|
+
} catch { return []; }
|
|
139
|
+
})();
|
|
140
|
+
|
|
110
141
|
while ((match = phasePattern.exec(content)) !== null) {
|
|
111
142
|
const phaseNum = match[1];
|
|
112
143
|
const phaseName = match[2].replace(/\(INSERTED\)/i, '').trim();
|
|
@@ -133,9 +164,7 @@ function cmdRoadmapAnalyze(cwd, raw) {
|
|
|
133
164
|
let hasResearch = false;
|
|
134
165
|
|
|
135
166
|
try {
|
|
136
|
-
const
|
|
137
|
-
const dirs = entries.filter(e => e.isDirectory()).map(e => e.name);
|
|
138
|
-
const dirMatch = dirs.find(d => d.startsWith(normalized + '-') || d === normalized);
|
|
167
|
+
const dirMatch = _phaseDirNames.find(d => phaseTokenMatches(d, normalized));
|
|
139
168
|
|
|
140
169
|
if (dirMatch) {
|
|
141
170
|
const phaseFiles = fs.readdirSync(path.join(phasesDir, dirMatch));
|
|
@@ -254,64 +283,66 @@ function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
|
|
|
254
283
|
return;
|
|
255
284
|
}
|
|
256
285
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
const tableRowPattern = new RegExp(
|
|
262
|
-
`^(\\|\\s*${phaseEscaped}\\.?\\s[^|]*(?:\\|[^\\n]*))$`,
|
|
263
|
-
'im'
|
|
264
|
-
);
|
|
265
|
-
const dateField = isComplete ? ` ${today} ` : ' ';
|
|
266
|
-
roadmapContent = roadmapContent.replace(tableRowPattern, (fullRow) => {
|
|
267
|
-
const cells = fullRow.split('|').slice(1, -1); // drop leading/trailing empty from split
|
|
268
|
-
if (cells.length === 5) {
|
|
269
|
-
// 5-col: Phase | Milestone | Plans | Status | Completed
|
|
270
|
-
cells[2] = ` ${summaryCount}/${planCount} `;
|
|
271
|
-
cells[3] = ` ${status.padEnd(11)}`;
|
|
272
|
-
cells[4] = dateField;
|
|
273
|
-
} else if (cells.length === 4) {
|
|
274
|
-
// 4-col: Phase | Plans | Status | Completed
|
|
275
|
-
cells[1] = ` ${summaryCount}/${planCount} `;
|
|
276
|
-
cells[2] = ` ${status.padEnd(11)}`;
|
|
277
|
-
cells[3] = dateField;
|
|
278
|
-
}
|
|
279
|
-
return '|' + cells.join('|') + '|';
|
|
280
|
-
});
|
|
286
|
+
// Wrap entire read-modify-write in lock to prevent concurrent corruption
|
|
287
|
+
withPlanningLock(cwd, () => {
|
|
288
|
+
let roadmapContent = fs.readFileSync(roadmapPath, 'utf-8');
|
|
289
|
+
const phaseEscaped = escapeRegex(phaseNum);
|
|
281
290
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
);
|
|
287
|
-
const planCountText = isComplete
|
|
288
|
-
? `${summaryCount}/${planCount} plans complete`
|
|
289
|
-
: `${summaryCount}/${planCount} plans executed`;
|
|
290
|
-
roadmapContent = replaceInCurrentMilestone(roadmapContent, planCountPattern, `$1${planCountText}`);
|
|
291
|
-
|
|
292
|
-
// If complete: check checkbox
|
|
293
|
-
if (isComplete) {
|
|
294
|
-
const checkboxPattern = new RegExp(
|
|
295
|
-
`(-\\s*\\[)[ ](\\]\\s*.*Phase\\s+${phaseEscaped}[:\\s][^\\n]*)`,
|
|
296
|
-
'i'
|
|
291
|
+
// Progress table row: update Plans/Status/Date columns (handles 4 or 5 column tables)
|
|
292
|
+
const tableRowPattern = new RegExp(
|
|
293
|
+
`^(\\|\\s*${phaseEscaped}\\.?\\s[^|]*(?:\\|[^\\n]*))$`,
|
|
294
|
+
'im'
|
|
297
295
|
);
|
|
298
|
-
|
|
299
|
-
|
|
296
|
+
const dateField = isComplete ? ` ${today} ` : ' ';
|
|
297
|
+
roadmapContent = roadmapContent.replace(tableRowPattern, (fullRow) => {
|
|
298
|
+
const cells = fullRow.split('|').slice(1, -1); // drop leading/trailing empty from split
|
|
299
|
+
if (cells.length === 5) {
|
|
300
|
+
// 5-col: Phase | Milestone | Plans | Status | Completed
|
|
301
|
+
cells[2] = ` ${summaryCount}/${planCount} `;
|
|
302
|
+
cells[3] = ` ${status.padEnd(11)}`;
|
|
303
|
+
cells[4] = dateField;
|
|
304
|
+
} else if (cells.length === 4) {
|
|
305
|
+
// 4-col: Phase | Plans | Status | Completed
|
|
306
|
+
cells[1] = ` ${summaryCount}/${planCount} `;
|
|
307
|
+
cells[2] = ` ${status.padEnd(11)}`;
|
|
308
|
+
cells[3] = dateField;
|
|
309
|
+
}
|
|
310
|
+
return '|' + cells.join('|') + '|';
|
|
311
|
+
});
|
|
300
312
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
if (!planId) continue;
|
|
305
|
-
const planEscaped = escapeRegex(planId);
|
|
306
|
-
const planCheckboxPattern = new RegExp(
|
|
307
|
-
`(-\\s*\\[) (\\]\\s*${planEscaped})`,
|
|
313
|
+
// Update plan count in phase detail section
|
|
314
|
+
const planCountPattern = new RegExp(
|
|
315
|
+
`(#{2,4}\\s*Phase\\s+${phaseEscaped}[\\s\\S]*?\\*\\*Plans:\\*\\*\\s*)[^\\n]+`,
|
|
308
316
|
'i'
|
|
309
317
|
);
|
|
310
|
-
|
|
311
|
-
|
|
318
|
+
const planCountText = isComplete
|
|
319
|
+
? `${summaryCount}/${planCount} plans complete`
|
|
320
|
+
: `${summaryCount}/${planCount} plans executed`;
|
|
321
|
+
roadmapContent = replaceInCurrentMilestone(roadmapContent, planCountPattern, `$1${planCountText}`);
|
|
322
|
+
|
|
323
|
+
// If complete: check checkbox
|
|
324
|
+
if (isComplete) {
|
|
325
|
+
const checkboxPattern = new RegExp(
|
|
326
|
+
`(-\\s*\\[)[ ](\\]\\s*.*Phase\\s+${phaseEscaped}[:\\s][^\\n]*)`,
|
|
327
|
+
'i'
|
|
328
|
+
);
|
|
329
|
+
roadmapContent = replaceInCurrentMilestone(roadmapContent, checkboxPattern, `$1x$2 (completed ${today})`);
|
|
330
|
+
}
|
|
312
331
|
|
|
313
|
-
|
|
332
|
+
// Mark completed plan checkboxes (e.g. "- [ ] 50-01-PLAN.md", "- [ ] 50-01:", or "- [ ] **50-01**")
|
|
333
|
+
for (const summaryFile of phaseInfo.summaries) {
|
|
334
|
+
const planId = summaryFile.replace('-SUMMARY.md', '').replace('SUMMARY.md', '');
|
|
335
|
+
if (!planId) continue;
|
|
336
|
+
const planEscaped = escapeRegex(planId);
|
|
337
|
+
const planCheckboxPattern = new RegExp(
|
|
338
|
+
`(-\\s*\\[) (\\]\\s*(?:\\*\\*)?${planEscaped}(?:\\*\\*)?)`,
|
|
339
|
+
'i'
|
|
340
|
+
);
|
|
341
|
+
roadmapContent = roadmapContent.replace(planCheckboxPattern, '$1x$2');
|
|
342
|
+
}
|
|
314
343
|
|
|
344
|
+
atomicWriteFileSync(roadmapPath, roadmapContent, 'utf-8');
|
|
345
|
+
});
|
|
315
346
|
output({
|
|
316
347
|
updated: true,
|
|
317
348
|
phase: phaseNum,
|