@ktpartners/dgs-platform 2.6.2
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 +38 -0
- package/README.md +851 -0
- package/agents/dgs-codebase-cross-analyzer.md +183 -0
- package/agents/dgs-codebase-mapper.md +782 -0
- package/agents/dgs-codebase-synthesizer.md +156 -0
- package/agents/dgs-debugger.md +1256 -0
- package/agents/dgs-executor.md +550 -0
- package/agents/dgs-integration-checker.md +481 -0
- package/agents/dgs-nyquist-auditor.md +178 -0
- package/agents/dgs-phase-researcher.md +563 -0
- package/agents/dgs-phase-verifier.md +450 -0
- package/agents/dgs-plan-checker.md +708 -0
- package/agents/dgs-planner.md +1324 -0
- package/agents/dgs-project-researcher.md +631 -0
- package/agents/dgs-research-synthesizer.md +249 -0
- package/agents/dgs-roadmapper.md +652 -0
- package/agents/dgs-verifier.md +607 -0
- package/bin/install.js +2073 -0
- package/commands/dgs/add-doc.md +45 -0
- package/commands/dgs/add-idea.md +38 -0
- package/commands/dgs/add-phase.md +43 -0
- package/commands/dgs/add-repo.md +54 -0
- package/commands/dgs/add-tests.md +41 -0
- package/commands/dgs/add-todo.md +47 -0
- package/commands/dgs/approve-spec.md +38 -0
- package/commands/dgs/audit-milestone.md +36 -0
- package/commands/dgs/audit-phase.md +37 -0
- package/commands/dgs/cancel-job.md +23 -0
- package/commands/dgs/capture-principle.md +143 -0
- package/commands/dgs/check-todos.md +45 -0
- package/commands/dgs/cleanup.md +18 -0
- package/commands/dgs/complete-milestone.md +136 -0
- package/commands/dgs/complete-project.md +70 -0
- package/commands/dgs/consolidate-ideas.md +50 -0
- package/commands/dgs/create-milestone-job.md +37 -0
- package/commands/dgs/debug.md +164 -0
- package/commands/dgs/develop-idea.md +53 -0
- package/commands/dgs/discuss-idea.md +41 -0
- package/commands/dgs/discuss-phase.md +83 -0
- package/commands/dgs/execute-phase.md +41 -0
- package/commands/dgs/fast.md +38 -0
- package/commands/dgs/find-related-ideas.md +43 -0
- package/commands/dgs/health.md +28 -0
- package/commands/dgs/help.md +22 -0
- package/commands/dgs/import-spec.md +36 -0
- package/commands/dgs/init-product.md +28 -0
- package/commands/dgs/insert-phase.md +32 -0
- package/commands/dgs/join-discord.md +18 -0
- package/commands/dgs/list-docs.md +40 -0
- package/commands/dgs/list-ideas.md +42 -0
- package/commands/dgs/list-jobs.md +22 -0
- package/commands/dgs/list-phase-assumptions.md +46 -0
- package/commands/dgs/list-projects.md +57 -0
- package/commands/dgs/list-specs.md +40 -0
- package/commands/dgs/map-codebase.md +92 -0
- package/commands/dgs/new-milestone.md +44 -0
- package/commands/dgs/new-project.md +42 -0
- package/commands/dgs/node-repair.md +26 -0
- package/commands/dgs/overlap-check.md +20 -0
- package/commands/dgs/pause-work.md +38 -0
- package/commands/dgs/plan-milestone-gaps.md +34 -0
- package/commands/dgs/plan-phase.md +44 -0
- package/commands/dgs/progress.md +24 -0
- package/commands/dgs/quick.md +41 -0
- package/commands/dgs/reactivate-project.md +70 -0
- package/commands/dgs/reapply-patches.md +110 -0
- package/commands/dgs/refine-spec.md +38 -0
- package/commands/dgs/reject-idea.md +43 -0
- package/commands/dgs/remove-doc.md +44 -0
- package/commands/dgs/remove-phase.md +31 -0
- package/commands/dgs/remove-repo.md +69 -0
- package/commands/dgs/research-idea.md +43 -0
- package/commands/dgs/research-phase.md +189 -0
- package/commands/dgs/restore-idea.md +45 -0
- package/commands/dgs/resume-work.md +40 -0
- package/commands/dgs/rollback-job.md +24 -0
- package/commands/dgs/run-job.md +35 -0
- package/commands/dgs/search.md +40 -0
- package/commands/dgs/set-profile.md +34 -0
- package/commands/dgs/settings.md +38 -0
- package/commands/dgs/switch-project.md +58 -0
- package/commands/dgs/undo-consolidation.md +42 -0
- package/commands/dgs/update-idea.md +44 -0
- package/commands/dgs/update.md +37 -0
- package/commands/dgs/validate-phase.md +35 -0
- package/commands/dgs/verify-work.md +39 -0
- package/commands/dgs/write-spec.md +49 -0
- package/deliver-great-systems/.planning/phases/09-backend-wiring-and-error-handling/09-01-SUMMARY.md +84 -0
- package/deliver-great-systems/.planning/phases/09-backend-wiring-and-error-handling/09-02-SUMMARY.md +86 -0
- package/deliver-great-systems/.planning/phases/10-v1-to-v2-migration-flow/10-01-SUMMARY.md +85 -0
- package/deliver-great-systems/bin/dgs-tools.cjs +1444 -0
- package/deliver-great-systems/bin/lib/auto-test.cjs +1365 -0
- package/deliver-great-systems/bin/lib/commands.cjs +570 -0
- package/deliver-great-systems/bin/lib/config.cjs +417 -0
- package/deliver-great-systems/bin/lib/conflict-agent.cjs +1063 -0
- package/deliver-great-systems/bin/lib/conflict-agent.test.cjs +554 -0
- package/deliver-great-systems/bin/lib/context.cjs +929 -0
- package/deliver-great-systems/bin/lib/context.test.cjs +693 -0
- package/deliver-great-systems/bin/lib/core.cjs +744 -0
- package/deliver-great-systems/bin/lib/core.test.cjs +822 -0
- package/deliver-great-systems/bin/lib/docs.cjs +919 -0
- package/deliver-great-systems/bin/lib/docs.test.cjs +211 -0
- package/deliver-great-systems/bin/lib/execution.cjs +705 -0
- package/deliver-great-systems/bin/lib/execution.test.cjs +1472 -0
- package/deliver-great-systems/bin/lib/frontmatter.cjs +324 -0
- package/deliver-great-systems/bin/lib/ideas.cjs +1406 -0
- package/deliver-great-systems/bin/lib/ideas.test.cjs +1417 -0
- package/deliver-great-systems/bin/lib/identity.cjs +125 -0
- package/deliver-great-systems/bin/lib/init.cjs +1114 -0
- package/deliver-great-systems/bin/lib/init.test.cjs +1271 -0
- package/deliver-great-systems/bin/lib/jobs.cjs +2015 -0
- package/deliver-great-systems/bin/lib/jobs.test.cjs +2619 -0
- package/deliver-great-systems/bin/lib/merge-conflicts.cjs +654 -0
- package/deliver-great-systems/bin/lib/merge-conflicts.test.cjs +370 -0
- package/deliver-great-systems/bin/lib/migration.cjs +352 -0
- package/deliver-great-systems/bin/lib/migration.test.cjs +582 -0
- package/deliver-great-systems/bin/lib/milestone.cjs +243 -0
- package/deliver-great-systems/bin/lib/overlap.cjs +437 -0
- package/deliver-great-systems/bin/lib/overlap.test.cjs +747 -0
- package/deliver-great-systems/bin/lib/path-audit.test.cjs +384 -0
- package/deliver-great-systems/bin/lib/paths.cjs +144 -0
- package/deliver-great-systems/bin/lib/paths.test.cjs +486 -0
- package/deliver-great-systems/bin/lib/phase.cjs +910 -0
- package/deliver-great-systems/bin/lib/projects.cjs +691 -0
- package/deliver-great-systems/bin/lib/projects.test.cjs +871 -0
- package/deliver-great-systems/bin/lib/repos.cjs +1432 -0
- package/deliver-great-systems/bin/lib/repos.test.cjs +1882 -0
- package/deliver-great-systems/bin/lib/roadmap.cjs +305 -0
- package/deliver-great-systems/bin/lib/search.cjs +570 -0
- package/deliver-great-systems/bin/lib/specs.cjs +1303 -0
- package/deliver-great-systems/bin/lib/state.cjs +893 -0
- package/deliver-great-systems/bin/lib/template.cjs +228 -0
- package/deliver-great-systems/bin/lib/test-helpers.cjs +291 -0
- package/deliver-great-systems/bin/lib/verify.cjs +796 -0
- package/deliver-great-systems/references/checkpoints.md +776 -0
- package/deliver-great-systems/references/conflict-resolution.md +66 -0
- package/deliver-great-systems/references/context-tiers.md +166 -0
- package/deliver-great-systems/references/continuation-format.md +249 -0
- package/deliver-great-systems/references/decimal-phase-calculation.md +67 -0
- package/deliver-great-systems/references/git-integration.md +250 -0
- package/deliver-great-systems/references/git-planning-commit.md +40 -0
- package/deliver-great-systems/references/model-profile-resolution.md +36 -0
- package/deliver-great-systems/references/model-profiles.md +95 -0
- package/deliver-great-systems/references/phase-argument-parsing.md +61 -0
- package/deliver-great-systems/references/planning-config.md +224 -0
- package/deliver-great-systems/references/questioning.md +162 -0
- package/deliver-great-systems/references/spec-review-loop.md +177 -0
- package/deliver-great-systems/references/tdd.md +265 -0
- package/deliver-great-systems/references/ui-brand.md +160 -0
- package/deliver-great-systems/references/verification-patterns.md +612 -0
- package/deliver-great-systems/templates/DEBUG.md +166 -0
- package/deliver-great-systems/templates/UAT.md +251 -0
- package/deliver-great-systems/templates/VALIDATION.md +95 -0
- package/deliver-great-systems/templates/claude-md.md +74 -0
- package/deliver-great-systems/templates/codebase/architecture.md +257 -0
- package/deliver-great-systems/templates/codebase/concerns.md +312 -0
- package/deliver-great-systems/templates/codebase/conventions.md +309 -0
- package/deliver-great-systems/templates/codebase/integrations.md +282 -0
- package/deliver-great-systems/templates/codebase/stack.md +188 -0
- package/deliver-great-systems/templates/codebase/structure.md +287 -0
- package/deliver-great-systems/templates/codebase/testing.md +482 -0
- package/deliver-great-systems/templates/config.json +38 -0
- package/deliver-great-systems/templates/context.md +354 -0
- package/deliver-great-systems/templates/continue-here.md +80 -0
- package/deliver-great-systems/templates/debug-subagent-prompt.md +93 -0
- package/deliver-great-systems/templates/discovery.md +148 -0
- package/deliver-great-systems/templates/milestone-archive.md +125 -0
- package/deliver-great-systems/templates/milestone.md +117 -0
- package/deliver-great-systems/templates/phase-prompt.md +615 -0
- package/deliver-great-systems/templates/planner-subagent-prompt.md +119 -0
- package/deliver-great-systems/templates/project.md +186 -0
- package/deliver-great-systems/templates/requirements.md +233 -0
- package/deliver-great-systems/templates/research-project/ARCHITECTURE.md +206 -0
- package/deliver-great-systems/templates/research-project/FEATURES.md +149 -0
- package/deliver-great-systems/templates/research-project/PITFALLS.md +202 -0
- package/deliver-great-systems/templates/research-project/STACK.md +122 -0
- package/deliver-great-systems/templates/research-project/SUMMARY.md +172 -0
- package/deliver-great-systems/templates/research.md +554 -0
- package/deliver-great-systems/templates/retrospective.md +54 -0
- package/deliver-great-systems/templates/roadmap.md +204 -0
- package/deliver-great-systems/templates/state.md +178 -0
- package/deliver-great-systems/templates/summary-complex.md +59 -0
- package/deliver-great-systems/templates/summary-minimal.md +41 -0
- package/deliver-great-systems/templates/summary-standard.md +48 -0
- package/deliver-great-systems/templates/summary.md +253 -0
- package/deliver-great-systems/templates/user-setup.md +313 -0
- package/deliver-great-systems/templates/verification-report.md +324 -0
- package/deliver-great-systems/workflows/add-doc.md +151 -0
- package/deliver-great-systems/workflows/add-idea.md +96 -0
- package/deliver-great-systems/workflows/add-phase.md +120 -0
- package/deliver-great-systems/workflows/add-tests.md +359 -0
- package/deliver-great-systems/workflows/add-todo.md +162 -0
- package/deliver-great-systems/workflows/approve-spec.md +194 -0
- package/deliver-great-systems/workflows/audit-milestone.md +364 -0
- package/deliver-great-systems/workflows/audit-phase.md +462 -0
- package/deliver-great-systems/workflows/cancel-job.md +108 -0
- package/deliver-great-systems/workflows/check-todos.md +181 -0
- package/deliver-great-systems/workflows/cleanup.md +247 -0
- package/deliver-great-systems/workflows/codereview.md +526 -0
- package/deliver-great-systems/workflows/complete-milestone.md +1298 -0
- package/deliver-great-systems/workflows/consolidate-ideas.md +365 -0
- package/deliver-great-systems/workflows/create-milestone-job.md +177 -0
- package/deliver-great-systems/workflows/develop-idea.md +544 -0
- package/deliver-great-systems/workflows/diagnose-issues.md +231 -0
- package/deliver-great-systems/workflows/discovery-phase.md +301 -0
- package/deliver-great-systems/workflows/discuss-idea.md +263 -0
- package/deliver-great-systems/workflows/discuss-phase.md +733 -0
- package/deliver-great-systems/workflows/execute-phase.md +571 -0
- package/deliver-great-systems/workflows/execute-plan.md +592 -0
- package/deliver-great-systems/workflows/find-related-ideas.md +271 -0
- package/deliver-great-systems/workflows/health.md +173 -0
- package/deliver-great-systems/workflows/help.md +997 -0
- package/deliver-great-systems/workflows/import-spec.md +381 -0
- package/deliver-great-systems/workflows/init-product.md +767 -0
- package/deliver-great-systems/workflows/insert-phase.md +138 -0
- package/deliver-great-systems/workflows/list-docs.md +119 -0
- package/deliver-great-systems/workflows/list-ideas.md +154 -0
- package/deliver-great-systems/workflows/list-jobs.md +89 -0
- package/deliver-great-systems/workflows/list-phase-assumptions.md +192 -0
- package/deliver-great-systems/workflows/list-specs.md +101 -0
- package/deliver-great-systems/workflows/map-codebase.md +621 -0
- package/deliver-great-systems/workflows/new-milestone.md +591 -0
- package/deliver-great-systems/workflows/new-project.md +1113 -0
- package/deliver-great-systems/workflows/node-repair.md +94 -0
- package/deliver-great-systems/workflows/overlap-check.md +86 -0
- package/deliver-great-systems/workflows/pause-work.md +134 -0
- package/deliver-great-systems/workflows/plan-milestone-gaps.md +306 -0
- package/deliver-great-systems/workflows/plan-phase.md +698 -0
- package/deliver-great-systems/workflows/progress.md +386 -0
- package/deliver-great-systems/workflows/quick.md +845 -0
- package/deliver-great-systems/workflows/refine-spec.md +275 -0
- package/deliver-great-systems/workflows/reject-idea.md +109 -0
- package/deliver-great-systems/workflows/remove-doc.md +117 -0
- package/deliver-great-systems/workflows/remove-phase.md +163 -0
- package/deliver-great-systems/workflows/research-idea.md +325 -0
- package/deliver-great-systems/workflows/research-phase.md +81 -0
- package/deliver-great-systems/workflows/restore-idea.md +101 -0
- package/deliver-great-systems/workflows/resume-project.md +311 -0
- package/deliver-great-systems/workflows/rollback-job.md +130 -0
- package/deliver-great-systems/workflows/run-job.md +498 -0
- package/deliver-great-systems/workflows/search.md +130 -0
- package/deliver-great-systems/workflows/set-profile.md +83 -0
- package/deliver-great-systems/workflows/settings.md +470 -0
- package/deliver-great-systems/workflows/transition.md +563 -0
- package/deliver-great-systems/workflows/undo-consolidation.md +155 -0
- package/deliver-great-systems/workflows/update-idea.md +157 -0
- package/deliver-great-systems/workflows/update.md +242 -0
- package/deliver-great-systems/workflows/validate-phase.md +177 -0
- package/deliver-great-systems/workflows/verify-phase.md +253 -0
- package/deliver-great-systems/workflows/verify-work.md +671 -0
- package/deliver-great-systems/workflows/write-spec.md +450 -0
- package/hooks/dist/dgs-check-update.js +62 -0
- package/hooks/dist/dgs-context-monitor.js +141 -0
- package/hooks/dist/dgs-statusline.js +115 -0
- package/package.json +60 -0
- package/scripts/build-hooks.js +43 -0
|
@@ -0,0 +1,691 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Projects — Project lifecycle: subfolder creation, state reading,
|
|
3
|
+
* PROJECTS.md regeneration, repo tag scanning, project completion
|
|
4
|
+
*
|
|
5
|
+
* This module is the foundation for all Phase 3 commands: new-project,
|
|
6
|
+
* switch-project, list-projects, complete-project, and the projects CLI subcommand.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const { safeReadFile, getProjectFolders, generateSlugInternal, isV2Install, output, error, loadConfig, getProjectDir } = require('./core.cjs');
|
|
12
|
+
const { writeConfigField } = require('./config.cjs');
|
|
13
|
+
const { getPlanningRoot } = require('./paths.cjs');
|
|
14
|
+
|
|
15
|
+
// ─── Constants ──────────────────────────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
const STANDARD_DIRS = ['phases', 'research', 'todos', 'quick', 'debug'];
|
|
18
|
+
|
|
19
|
+
// ─── Project Subfolder Creation ─────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Create a project subfolder under .planning/projects/<slug>/ with all standard
|
|
23
|
+
* planning files and directories.
|
|
24
|
+
*
|
|
25
|
+
* @param {string} cwd - Working directory (product root)
|
|
26
|
+
* @param {string} slug - Project slug (lowercase, hyphens, no special chars)
|
|
27
|
+
* @param {string} name - Human-readable project name
|
|
28
|
+
* @param {Object} [options] - Optional settings
|
|
29
|
+
* @param {string} [options.initial_repos] - Comma-separated initial repo names
|
|
30
|
+
* @returns {{ created: boolean, path?: string, reason?: string }}
|
|
31
|
+
*/
|
|
32
|
+
function createProjectSubfolder(cwd, slug, name, options) {
|
|
33
|
+
const projectDir = getProjectDir(cwd, slug);
|
|
34
|
+
// Ensure .planning/projects/ container exists
|
|
35
|
+
fs.mkdirSync(path.dirname(projectDir), { recursive: true });
|
|
36
|
+
|
|
37
|
+
// Don't overwrite existing project
|
|
38
|
+
if (fs.existsSync(projectDir)) {
|
|
39
|
+
return { created: false, reason: 'already_exists' };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Create project directory
|
|
43
|
+
fs.mkdirSync(projectDir, { recursive: true });
|
|
44
|
+
|
|
45
|
+
// Create standard subdirectories
|
|
46
|
+
for (const dir of STANDARD_DIRS) {
|
|
47
|
+
fs.mkdirSync(path.join(projectDir, dir), { recursive: true });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Create standard files with templates
|
|
51
|
+
const opts = options || {};
|
|
52
|
+
|
|
53
|
+
let projectMd = `# Project: ${name}\n\n## What This Is\n\n[Describe the project scope and goals]\n`;
|
|
54
|
+
if (opts.initial_repos) {
|
|
55
|
+
projectMd += `\n## Initial Repos\n\n${opts.initial_repos}\n`;
|
|
56
|
+
}
|
|
57
|
+
fs.writeFileSync(path.join(projectDir, 'PROJECT.md'), projectMd);
|
|
58
|
+
|
|
59
|
+
fs.writeFileSync(
|
|
60
|
+
path.join(projectDir, 'REQUIREMENTS.md'),
|
|
61
|
+
`# Requirements: ${name}\n\n## v1 Requirements\n\n`
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
fs.writeFileSync(
|
|
65
|
+
path.join(projectDir, 'ROADMAP.md'),
|
|
66
|
+
`# Roadmap: ${name}\n\n## Phases\n\n`
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
fs.writeFileSync(
|
|
70
|
+
path.join(projectDir, 'STATE.md'),
|
|
71
|
+
`# Project State\n\n## Current Position\n\nPhase: Not started\nStatus: New\nProgress: [░░░░░░░░░░] 0%\n`
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
return { created: true, path: projectDir };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ─── Project State Reading ──────────────────────────────────────────────────
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Read and parse a project's STATE.md at .planning/projects/<slug>/STATE.md
|
|
81
|
+
* to extract status information.
|
|
82
|
+
*
|
|
83
|
+
* Extracts:
|
|
84
|
+
* - phase: From "Phase: ..." line
|
|
85
|
+
* - status: From "Status: ..." line
|
|
86
|
+
* - progress: From "Progress: [...] NN%" line (integer)
|
|
87
|
+
* - completed_date: From "Completed: YYYY-MM-DD" line
|
|
88
|
+
*
|
|
89
|
+
* @param {string} cwd - Working directory (product root)
|
|
90
|
+
* @param {string} slug - Project slug
|
|
91
|
+
* @returns {{ phase: string, status: string, progress: number, completed_date: string|null } | null}
|
|
92
|
+
*/
|
|
93
|
+
function readProjectState(cwd, slug) {
|
|
94
|
+
const statePath = path.join(getProjectDir(cwd, slug), 'STATE.md');
|
|
95
|
+
const content = safeReadFile(statePath);
|
|
96
|
+
if (!content) return null;
|
|
97
|
+
|
|
98
|
+
const phaseMatch = content.match(/Phase:\s*(.+)/i);
|
|
99
|
+
const statusMatch = content.match(/Status:\s*(.+)/i);
|
|
100
|
+
const progressMatch = content.match(/Progress:\s*\[[^\]]*\]\s*(\d+)%/);
|
|
101
|
+
const completedMatch = content.match(/Completed:\s*(\d{4}-\d{2}-\d{2})/);
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
phase: phaseMatch ? phaseMatch[1].trim() : 'Unknown',
|
|
105
|
+
status: statusMatch ? statusMatch[1].trim() : 'Unknown',
|
|
106
|
+
progress: progressMatch ? parseInt(progressMatch[1], 10) : 0,
|
|
107
|
+
completed_date: completedMatch ? completedMatch[1] : null,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ─── Repo Tag Scanning ──────────────────────────────────────────────────────
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Scan a project's plan files for <repos>...</repos> tags and extract
|
|
115
|
+
* all unique repo names referenced.
|
|
116
|
+
*
|
|
117
|
+
* Scans all PLAN.md files under .planning/projects/<slug>/phases/ subdirectories.
|
|
118
|
+
*
|
|
119
|
+
* @param {string} cwd - Working directory (product root)
|
|
120
|
+
* @param {string} slug - Project slug
|
|
121
|
+
* @returns {string[]} Sorted, deduplicated array of repo names
|
|
122
|
+
*/
|
|
123
|
+
function scanProjectReposTags(cwd, slug) {
|
|
124
|
+
const phasesDir = path.join(getProjectDir(cwd, slug), 'phases');
|
|
125
|
+
const allRepos = new Set();
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
const phaseDirs = fs.readdirSync(phasesDir, { withFileTypes: true })
|
|
129
|
+
.filter(e => e.isDirectory())
|
|
130
|
+
.map(e => e.name);
|
|
131
|
+
|
|
132
|
+
for (const phaseDir of phaseDirs) {
|
|
133
|
+
const fullPhaseDir = path.join(phasesDir, phaseDir);
|
|
134
|
+
let planFiles;
|
|
135
|
+
try {
|
|
136
|
+
planFiles = fs.readdirSync(fullPhaseDir)
|
|
137
|
+
.filter(f => f.endsWith('-PLAN.md') || f === 'PLAN.md');
|
|
138
|
+
} catch {
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
for (const planFile of planFiles) {
|
|
143
|
+
const content = safeReadFile(path.join(fullPhaseDir, planFile));
|
|
144
|
+
if (!content) continue;
|
|
145
|
+
|
|
146
|
+
const matches = content.match(/<repos>([^<]+)<\/repos>/g);
|
|
147
|
+
if (!matches) continue;
|
|
148
|
+
|
|
149
|
+
for (const match of matches) {
|
|
150
|
+
const inner = match.replace(/<\/?repos>/g, '');
|
|
151
|
+
inner
|
|
152
|
+
.split(',')
|
|
153
|
+
.map(n => n.trim())
|
|
154
|
+
.filter(n => n)
|
|
155
|
+
.forEach(n => allRepos.add(n));
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
} catch {
|
|
160
|
+
// phases directory doesn't exist or other error
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return Array.from(allRepos).sort();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// ─── PROJECTS.md Regeneration ───────────────────────────────────────────────
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Regenerate .planning/PROJECTS.md by scanning all project subfolders.
|
|
170
|
+
*
|
|
171
|
+
* Reads each project's STATE.md for status/phase/progress, scans plan
|
|
172
|
+
* <repos> tags for repos touched, and writes the derived PROJECTS.md.
|
|
173
|
+
*
|
|
174
|
+
* Ghost project guard: projects whose STATE.md is missing or unreadable
|
|
175
|
+
* are warned about and omitted from the output.
|
|
176
|
+
*
|
|
177
|
+
* @param {string} cwd - Working directory (product root)
|
|
178
|
+
* @returns {{ projects: Array, warnings: string[] }}
|
|
179
|
+
*/
|
|
180
|
+
function regenerateProjectsMd(cwd) {
|
|
181
|
+
const slugs = getProjectFolders(cwd);
|
|
182
|
+
const warnings = [];
|
|
183
|
+
const projects = [];
|
|
184
|
+
|
|
185
|
+
for (const slug of slugs) {
|
|
186
|
+
const state = readProjectState(cwd, slug);
|
|
187
|
+
if (!state) {
|
|
188
|
+
warnings.push(`Ghost project: ${slug} (STATE.md missing or unreadable)`);
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const reposTouched = scanProjectReposTags(cwd, slug);
|
|
193
|
+
projects.push({
|
|
194
|
+
name: slug,
|
|
195
|
+
status: state.status,
|
|
196
|
+
repos_touched: reposTouched.join(', '),
|
|
197
|
+
current_phase: state.phase,
|
|
198
|
+
completed_date: state.completed_date || '',
|
|
199
|
+
progress: state.progress,
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Write PROJECTS.md
|
|
204
|
+
let content = '# Projects\n\n';
|
|
205
|
+
content += '## Active\n\n';
|
|
206
|
+
content += '| Project | Status | Repos Touched | Current Phase |\n';
|
|
207
|
+
content += '|---------|--------|---------------|---------------|\n';
|
|
208
|
+
|
|
209
|
+
const active = projects.filter(p => !p.status.toLowerCase().includes('completed'));
|
|
210
|
+
for (const proj of active) {
|
|
211
|
+
content += `| ${proj.name} | ${proj.status} | ${proj.repos_touched} | ${proj.current_phase} |\n`;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
content += '\n## Completed\n\n';
|
|
215
|
+
content += '| Project | Completed | Duration |\n';
|
|
216
|
+
content += '|---------|-----------|----------|\n';
|
|
217
|
+
|
|
218
|
+
const completed = projects.filter(p => p.status.toLowerCase().includes('completed'));
|
|
219
|
+
for (const proj of completed) {
|
|
220
|
+
content += `| ${proj.name} | ${proj.completed_date} | |\n`;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
fs.writeFileSync(path.join(getPlanningRoot(cwd), 'PROJECTS.md'), content);
|
|
224
|
+
|
|
225
|
+
return { projects, warnings };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// ─── Project Completion ─────────────────────────────────────────────────────
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Mark a project as completed by updating its STATE.md at
|
|
232
|
+
* .planning/projects/<slug>/STATE.md.
|
|
233
|
+
*
|
|
234
|
+
* - Updates "Status:" line to "completed"
|
|
235
|
+
* - Adds "Completed: YYYY-MM-DD" line with today's date
|
|
236
|
+
* - Preserves existing STATE.md content structure
|
|
237
|
+
*
|
|
238
|
+
* @param {string} cwd - Working directory (product root)
|
|
239
|
+
* @param {string} slug - Project slug
|
|
240
|
+
* @returns {{ completed: boolean, date?: string, error?: string }}
|
|
241
|
+
*/
|
|
242
|
+
function completeProject(cwd, slug) {
|
|
243
|
+
const projectDir = getProjectDir(cwd, slug);
|
|
244
|
+
const statePath = path.join(projectDir, 'STATE.md');
|
|
245
|
+
|
|
246
|
+
// Check project directory exists
|
|
247
|
+
if (!fs.existsSync(projectDir)) {
|
|
248
|
+
return { completed: false, error: 'not_found' };
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Check STATE.md exists
|
|
252
|
+
const content = safeReadFile(statePath);
|
|
253
|
+
if (!content) {
|
|
254
|
+
return { completed: false, error: 'no_state_md' };
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Check if already completed
|
|
258
|
+
if (/Status:\s*completed/i.test(content)) {
|
|
259
|
+
return { completed: false, error: 'already_completed' };
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
263
|
+
let updated = content;
|
|
264
|
+
|
|
265
|
+
// Update or add Status line
|
|
266
|
+
if (/Status:\s*.+/i.test(updated)) {
|
|
267
|
+
updated = updated.replace(/Status:\s*.+/i, 'Status: completed');
|
|
268
|
+
} else {
|
|
269
|
+
updated += '\nStatus: completed\n';
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Add or update Completed date
|
|
273
|
+
if (/Completed:\s*\d{4}-\d{2}-\d{2}/.test(updated)) {
|
|
274
|
+
updated = updated.replace(/Completed:\s*\d{4}-\d{2}-\d{2}/, `Completed: ${today}`);
|
|
275
|
+
} else {
|
|
276
|
+
// Add after Status line
|
|
277
|
+
updated = updated.replace(/(Status: completed)/, `$1\nCompleted: ${today}`);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
fs.writeFileSync(statePath, updated);
|
|
281
|
+
|
|
282
|
+
return { completed: true, date: today };
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// ─── Project Reactivation ────────────────────────────────────────────────────
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Reactivate a completed project by updating its STATE.md at
|
|
289
|
+
* .planning/projects/<slug>/STATE.md.
|
|
290
|
+
*
|
|
291
|
+
* - Changes "Status: completed" back to "Status: In progress"
|
|
292
|
+
* - Removes the "Completed: YYYY-MM-DD" line entirely
|
|
293
|
+
* - Preserves all other STATE.md content
|
|
294
|
+
*
|
|
295
|
+
* @param {string} cwd - Working directory (product root)
|
|
296
|
+
* @param {string} slug - Project slug
|
|
297
|
+
* @returns {{ reactivated: boolean, error?: string }}
|
|
298
|
+
*/
|
|
299
|
+
function reactivateProject(cwd, slug) {
|
|
300
|
+
const projectDir = getProjectDir(cwd, slug);
|
|
301
|
+
const statePath = path.join(projectDir, 'STATE.md');
|
|
302
|
+
|
|
303
|
+
// Check project directory exists
|
|
304
|
+
if (!fs.existsSync(projectDir)) {
|
|
305
|
+
return { reactivated: false, error: 'not_found' };
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Check STATE.md exists
|
|
309
|
+
const content = safeReadFile(statePath);
|
|
310
|
+
if (!content) {
|
|
311
|
+
return { reactivated: false, error: 'no_state_md' };
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Check project IS completed
|
|
315
|
+
if (!/Status:\s*completed/i.test(content)) {
|
|
316
|
+
return { reactivated: false, error: 'not_completed' };
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
let updated = content;
|
|
320
|
+
|
|
321
|
+
// Change Status back to In progress
|
|
322
|
+
updated = updated.replace(/Status:\s*completed/i, 'Status: In progress');
|
|
323
|
+
|
|
324
|
+
// Remove the Completed date line entirely (including its trailing newline)
|
|
325
|
+
updated = updated.replace(/Completed:\s*\d{4}-\d{2}-\d{2}\n?/, '');
|
|
326
|
+
|
|
327
|
+
fs.writeFileSync(statePath, updated);
|
|
328
|
+
|
|
329
|
+
return { reactivated: true };
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// ─── PROJECTS.md Parsing ────────────────────────────────────────────────────
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Parse .planning/PROJECTS.md into structured data.
|
|
336
|
+
*
|
|
337
|
+
* Returns null if the file doesn't exist or doesn't start with '# Projects'.
|
|
338
|
+
*
|
|
339
|
+
* @param {string} cwd - Working directory (product root)
|
|
340
|
+
* @returns {{ active: Array<{name, status, repos_touched, current_phase}>, completed: Array<{name, completed, duration}> } | null}
|
|
341
|
+
*/
|
|
342
|
+
function parseProjectsMd(cwd) {
|
|
343
|
+
const filePath = path.join(getPlanningRoot(cwd), 'PROJECTS.md');
|
|
344
|
+
const content = safeReadFile(filePath);
|
|
345
|
+
if (!content) return null;
|
|
346
|
+
|
|
347
|
+
// v2 marker check
|
|
348
|
+
if (!content.startsWith('# Projects')) return null;
|
|
349
|
+
|
|
350
|
+
const active = [];
|
|
351
|
+
const completed = [];
|
|
352
|
+
|
|
353
|
+
// Split into Active and Completed sections
|
|
354
|
+
const sections = content.split('## Completed');
|
|
355
|
+
const activeSection = sections[0] || '';
|
|
356
|
+
const completedSection = sections[1] || '';
|
|
357
|
+
|
|
358
|
+
// Parse Active table
|
|
359
|
+
const activeLines = activeSection.split('\n');
|
|
360
|
+
let inActiveTable = false;
|
|
361
|
+
for (const line of activeLines) {
|
|
362
|
+
if (line.startsWith('|') && line.includes('Project') && line.includes('Status')) {
|
|
363
|
+
inActiveTable = true;
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
if (inActiveTable && line.startsWith('|---')) {
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
369
|
+
if (inActiveTable && line.startsWith('|')) {
|
|
370
|
+
const cells = line.split('|').map(c => c.trim());
|
|
371
|
+
if (cells.length >= 5 && cells[1]) {
|
|
372
|
+
active.push({
|
|
373
|
+
name: cells[1],
|
|
374
|
+
status: cells[2],
|
|
375
|
+
repos_touched: cells[3],
|
|
376
|
+
current_phase: cells[4],
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
} else if (inActiveTable) {
|
|
380
|
+
inActiveTable = false;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Parse Completed table
|
|
385
|
+
const completedLines = completedSection.split('\n');
|
|
386
|
+
let inCompletedTable = false;
|
|
387
|
+
for (const line of completedLines) {
|
|
388
|
+
if (line.startsWith('|') && line.includes('Project') && line.includes('Completed')) {
|
|
389
|
+
inCompletedTable = true;
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
if (inCompletedTable && line.startsWith('|---')) {
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
if (inCompletedTable && line.startsWith('|')) {
|
|
396
|
+
const cells = line.split('|').map(c => c.trim());
|
|
397
|
+
if (cells.length >= 4 && cells[1]) {
|
|
398
|
+
completed.push({
|
|
399
|
+
name: cells[1],
|
|
400
|
+
completed: cells[2],
|
|
401
|
+
duration: cells[3],
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
} else if (inCompletedTable) {
|
|
405
|
+
inCompletedTable = false;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
return { active, completed };
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// ─── Slug Prefix Collision Detection ─────────────────────────────────────────
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Check if a new project slug has a prefix collision with any existing slug.
|
|
416
|
+
*
|
|
417
|
+
* A prefix collision occurs when:
|
|
418
|
+
* - An existing slug is a prefix of the new slug (e.g., "api-v2" is prefix of "api-v2-hotfix")
|
|
419
|
+
* - The new slug is a prefix of an existing slug (e.g., "api" is prefix of "api-v2")
|
|
420
|
+
*
|
|
421
|
+
* @param {string} newSlug - The new project slug to check
|
|
422
|
+
* @param {string[]} existingSlugs - Array of existing project slugs
|
|
423
|
+
* @returns {{ collision: boolean, collidingSlug?: string }}
|
|
424
|
+
*/
|
|
425
|
+
function checkSlugPrefixCollision(newSlug, existingSlugs) {
|
|
426
|
+
for (const existing of existingSlugs) {
|
|
427
|
+
if (existing === newSlug) continue; // Exact match is handled by createProjectSubfolder
|
|
428
|
+
if (existing.startsWith(newSlug) || newSlug.startsWith(existing)) {
|
|
429
|
+
return { collision: true, collidingSlug: existing };
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
return { collision: false };
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// ─── CLI Command Functions ───────────────────────────────────────────────────
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* CLI: Create a new project subfolder and set it as the active project.
|
|
439
|
+
*
|
|
440
|
+
* - Requires v2 install
|
|
441
|
+
* - Generates slug from name
|
|
442
|
+
* - Creates subfolder with standard structure
|
|
443
|
+
* - Sets current_project in config.json
|
|
444
|
+
* - Regenerates PROJECTS.md
|
|
445
|
+
*
|
|
446
|
+
* @param {string} cwd - Working directory (product root)
|
|
447
|
+
* @param {string} name - Human-readable project name
|
|
448
|
+
* @param {Object} [options] - Optional settings
|
|
449
|
+
* @param {string} [options.initial_repos] - Comma-separated initial repo names
|
|
450
|
+
* @param {boolean} raw - Raw output mode
|
|
451
|
+
*/
|
|
452
|
+
function cmdProjectsCreate(cwd, name, options, raw) {
|
|
453
|
+
if (!isV2Install(cwd)) error('v2 install required. Run /dgs:init-product first.');
|
|
454
|
+
if (!name) error('Project name required. Usage: projects create <name>');
|
|
455
|
+
|
|
456
|
+
const slug = generateSlugInternal(name);
|
|
457
|
+
if (!slug) error('Invalid project name — could not generate slug.');
|
|
458
|
+
|
|
459
|
+
// Check for slug prefix collisions before creating
|
|
460
|
+
const existingSlugs = getProjectFolders(cwd);
|
|
461
|
+
const prefixCheck = checkSlugPrefixCollision(slug, existingSlugs);
|
|
462
|
+
|
|
463
|
+
const result = createProjectSubfolder(cwd, slug, name, options);
|
|
464
|
+
if (!result.created) {
|
|
465
|
+
error(`Project "${slug}" already exists.`);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
writeConfigField(cwd, 'current_project', slug);
|
|
469
|
+
regenerateProjectsMd(cwd);
|
|
470
|
+
|
|
471
|
+
const outputData = { created: true, slug, name, path: result.path };
|
|
472
|
+
if (prefixCheck.collision) {
|
|
473
|
+
outputData.warning = `Slug '${slug}' shares prefix with existing project '${prefixCheck.collidingSlug}'. Branch names may be ambiguous.`;
|
|
474
|
+
}
|
|
475
|
+
output(outputData, raw);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* CLI: Switch the active project context.
|
|
480
|
+
*
|
|
481
|
+
* - Requires v2 install
|
|
482
|
+
* - Matches by exact slug or case-insensitive partial match
|
|
483
|
+
* - Updates current_project in config.json
|
|
484
|
+
*
|
|
485
|
+
* @param {string} cwd - Working directory (product root)
|
|
486
|
+
* @param {string} name - Project slug or name to switch to
|
|
487
|
+
* @param {boolean} raw - Raw output mode
|
|
488
|
+
*/
|
|
489
|
+
function cmdProjectsSwitch(cwd, name, raw) {
|
|
490
|
+
if (!isV2Install(cwd)) error('v2 install required. Run /dgs:init-product first.');
|
|
491
|
+
if (!name) error('Project name required. Usage: projects switch <name>');
|
|
492
|
+
|
|
493
|
+
const projects = getProjectFolders(cwd);
|
|
494
|
+
if (projects.length === 0) {
|
|
495
|
+
error('No projects found. Create one first with: projects create <name>');
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// Try exact slug match first
|
|
499
|
+
let matchedSlug = projects.find(p => p === name);
|
|
500
|
+
|
|
501
|
+
// Try case-insensitive match on slug
|
|
502
|
+
if (!matchedSlug) {
|
|
503
|
+
const lower = name.toLowerCase();
|
|
504
|
+
matchedSlug = projects.find(p => p.toLowerCase() === lower);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// Try slug generated from name
|
|
508
|
+
if (!matchedSlug) {
|
|
509
|
+
const generatedSlug = generateSlugInternal(name);
|
|
510
|
+
if (generatedSlug) {
|
|
511
|
+
matchedSlug = projects.find(p => p === generatedSlug);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
if (!matchedSlug) {
|
|
516
|
+
error(`Project "${name}" not found. Available: ${projects.join(', ')}`);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// Guard: reject switching to completed projects
|
|
520
|
+
const state = readProjectState(cwd, matchedSlug);
|
|
521
|
+
if (state && state.status.toLowerCase().includes('completed')) {
|
|
522
|
+
error(`Project "${matchedSlug}" is completed. Use /dgs:reactivate-project to resume work on it.`);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
writeConfigField(cwd, 'current_project', matchedSlug);
|
|
526
|
+
|
|
527
|
+
output({ switched: true, slug: matchedSlug, status: state }, raw);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* CLI: List all projects with their status and repos touched.
|
|
532
|
+
*
|
|
533
|
+
* - Requires v2 install
|
|
534
|
+
* - Regenerates PROJECTS.md to ensure freshness
|
|
535
|
+
* - Returns active and completed project lists
|
|
536
|
+
*
|
|
537
|
+
* @param {string} cwd - Working directory (product root)
|
|
538
|
+
* @param {boolean} raw - Raw output mode
|
|
539
|
+
*/
|
|
540
|
+
function cmdProjectsList(cwd, raw) {
|
|
541
|
+
if (!isV2Install(cwd)) error('v2 install required. Run /dgs:init-product first.');
|
|
542
|
+
|
|
543
|
+
const result = regenerateProjectsMd(cwd);
|
|
544
|
+
const active = result.projects.filter(p => !p.status.toLowerCase().includes('completed'));
|
|
545
|
+
const completed = result.projects.filter(p => p.status.toLowerCase().includes('completed'));
|
|
546
|
+
|
|
547
|
+
output({ active, completed, warnings: result.warnings }, raw);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* CLI: Mark a project as completed.
|
|
552
|
+
*
|
|
553
|
+
* - Requires v2 install
|
|
554
|
+
* - Uses options.slug or falls back to current_project from config
|
|
555
|
+
* - Marks project as completed in STATE.md
|
|
556
|
+
* - Clears current_project in config.json
|
|
557
|
+
* - Regenerates PROJECTS.md
|
|
558
|
+
* - Reports remaining active projects
|
|
559
|
+
*
|
|
560
|
+
* @param {string} cwd - Working directory (product root)
|
|
561
|
+
* @param {Object} options - Options
|
|
562
|
+
* @param {string} [options.slug] - Specific project slug to complete
|
|
563
|
+
* @param {boolean} raw - Raw output mode
|
|
564
|
+
*/
|
|
565
|
+
function cmdProjectsComplete(cwd, options, raw) {
|
|
566
|
+
if (!isV2Install(cwd)) error('v2 install required. Run /dgs:init-product first.');
|
|
567
|
+
|
|
568
|
+
const opts = options || {};
|
|
569
|
+
let slug = opts.slug;
|
|
570
|
+
|
|
571
|
+
if (!slug) {
|
|
572
|
+
const cfg = loadConfig(cwd);
|
|
573
|
+
slug = cfg.current_project;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
if (!slug) {
|
|
577
|
+
error('No active project. Switch to one first with: projects switch <name>');
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
const result = completeProject(cwd, slug);
|
|
581
|
+
if (!result.completed) {
|
|
582
|
+
if (result.error === 'not_found') {
|
|
583
|
+
error(`Project "${slug}" not found.`);
|
|
584
|
+
} else if (result.error === 'no_state_md') {
|
|
585
|
+
error(`Project "${slug}" has no STATE.md file.`);
|
|
586
|
+
} else if (result.error === 'already_completed') {
|
|
587
|
+
error(`Project "${slug}" is already completed.`);
|
|
588
|
+
} else {
|
|
589
|
+
error(`Failed to complete project "${slug}": ${result.error}`);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
const currentCfg = loadConfig(cwd);
|
|
594
|
+
if (currentCfg.current_project === slug) {
|
|
595
|
+
writeConfigField(cwd, 'current_project', null);
|
|
596
|
+
}
|
|
597
|
+
const regenResult = regenerateProjectsMd(cwd);
|
|
598
|
+
const remainingActive = regenResult.projects.filter(p => !p.status.toLowerCase().includes('completed'));
|
|
599
|
+
|
|
600
|
+
output({ completed: true, slug, date: result.date, remaining_active: remainingActive }, raw);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
* CLI: Reactivate a completed project.
|
|
605
|
+
*
|
|
606
|
+
* - Requires v2 install
|
|
607
|
+
* - Uses options.slug or falls back to current_project from config
|
|
608
|
+
* - Restores project status to "In progress" in STATE.md
|
|
609
|
+
* - Removes Completed date from STATE.md
|
|
610
|
+
* - Optionally sets the project as current_project in config.json
|
|
611
|
+
* - Regenerates PROJECTS.md
|
|
612
|
+
* - Reports remaining active projects
|
|
613
|
+
*
|
|
614
|
+
* @param {string} cwd - Working directory (product root)
|
|
615
|
+
* @param {Object} options - Options
|
|
616
|
+
* @param {string} [options.slug] - Specific project slug to reactivate
|
|
617
|
+
* @param {boolean} [options.set_current] - Set as current project after reactivation
|
|
618
|
+
* @param {boolean} raw - Raw output mode
|
|
619
|
+
*/
|
|
620
|
+
function cmdProjectsReactivate(cwd, options, raw) {
|
|
621
|
+
if (!isV2Install(cwd)) error('v2 install required. Run /dgs:init-product first.');
|
|
622
|
+
|
|
623
|
+
const opts = options || {};
|
|
624
|
+
let slug = opts.slug;
|
|
625
|
+
|
|
626
|
+
if (!slug) {
|
|
627
|
+
const cfg = loadConfig(cwd);
|
|
628
|
+
slug = cfg.current_project;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
if (!slug) {
|
|
632
|
+
error('No active project. Use --slug <name> to specify which project to reactivate.');
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
const result = reactivateProject(cwd, slug);
|
|
636
|
+
if (!result.reactivated) {
|
|
637
|
+
if (result.error === 'not_found') {
|
|
638
|
+
error(`Project "${slug}" not found.`);
|
|
639
|
+
} else if (result.error === 'no_state_md') {
|
|
640
|
+
error(`Project "${slug}" has no STATE.md file.`);
|
|
641
|
+
} else if (result.error === 'not_completed') {
|
|
642
|
+
error(`Project "${slug}" is not completed.`);
|
|
643
|
+
} else {
|
|
644
|
+
error(`Failed to reactivate project "${slug}": ${result.error}`);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
if (opts.set_current) {
|
|
649
|
+
writeConfigField(cwd, 'current_project', slug);
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
const regenResult = regenerateProjectsMd(cwd);
|
|
653
|
+
const remainingActive = regenResult.projects.filter(p => !p.status.toLowerCase().includes('completed'));
|
|
654
|
+
|
|
655
|
+
output({ reactivated: true, slug, set_as_current: !!opts.set_current, remaining_active: remainingActive.length }, raw);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
/**
|
|
659
|
+
* CLI: Regenerate PROJECTS.md from disk.
|
|
660
|
+
*
|
|
661
|
+
* - Requires v2 install
|
|
662
|
+
* - Scans all project folders and re-derives PROJECTS.md
|
|
663
|
+
*
|
|
664
|
+
* @param {string} cwd - Working directory (product root)
|
|
665
|
+
* @param {boolean} raw - Raw output mode
|
|
666
|
+
*/
|
|
667
|
+
function cmdProjectsRegenerate(cwd, raw) {
|
|
668
|
+
if (!isV2Install(cwd)) error('v2 install required. Run /dgs:init-product first.');
|
|
669
|
+
|
|
670
|
+
const result = regenerateProjectsMd(cwd);
|
|
671
|
+
output({ regenerated: true, projects: result.projects, warnings: result.warnings }, raw);
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
// ─── Exports ────────────────────────────────────────────────────────────────
|
|
675
|
+
|
|
676
|
+
module.exports = {
|
|
677
|
+
createProjectSubfolder,
|
|
678
|
+
readProjectState,
|
|
679
|
+
scanProjectReposTags,
|
|
680
|
+
regenerateProjectsMd,
|
|
681
|
+
completeProject,
|
|
682
|
+
reactivateProject,
|
|
683
|
+
parseProjectsMd,
|
|
684
|
+
checkSlugPrefixCollision,
|
|
685
|
+
cmdProjectsCreate,
|
|
686
|
+
cmdProjectsSwitch,
|
|
687
|
+
cmdProjectsList,
|
|
688
|
+
cmdProjectsComplete,
|
|
689
|
+
cmdProjectsReactivate,
|
|
690
|
+
cmdProjectsRegenerate,
|
|
691
|
+
};
|