declare-cc 0.2.0 → 0.3.1
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.md +126 -27
- package/agents/declare-codebase-mapper.md +761 -0
- package/agents/declare-debugger.md +1198 -0
- package/agents/declare-plan-checker.md +608 -0
- package/agents/declare-planner.md +1015 -0
- package/agents/declare-research-synthesizer.md +309 -0
- package/agents/declare-researcher.md +484 -0
- package/bin/install.js +33 -38
- package/commands/declare/add-todo.md +41 -0
- package/commands/declare/audit.md +76 -0
- package/commands/declare/check-todos.md +125 -0
- package/commands/declare/complete-milestone.md +215 -0
- package/commands/declare/dashboard.md +76 -0
- package/commands/{gsd → declare}/debug.md +11 -11
- package/commands/declare/discuss.md +65 -0
- package/commands/declare/health.md +92 -0
- package/commands/declare/map-codebase.md +149 -0
- package/commands/declare/new-milestone.md +172 -0
- package/commands/declare/new-project.md +565 -0
- package/commands/declare/pause.md +138 -0
- package/commands/declare/plan.md +236 -0
- package/commands/declare/progress.md +116 -0
- package/commands/declare/quick.md +119 -0
- package/commands/declare/reapply-patches.md +178 -0
- package/commands/declare/research.md +267 -0
- package/commands/declare/resume.md +146 -0
- package/commands/declare/set-profile.md +66 -0
- package/commands/declare/settings.md +119 -0
- package/commands/declare/update.md +251 -0
- package/commands/declare/verify.md +64 -0
- package/dist/declare-tools.cjs +1234 -3
- package/package.json +1 -1
- package/workflows/discuss.md +476 -0
- package/workflows/verify.md +504 -0
- package/commands/gsd/add-phase.md +0 -39
- package/commands/gsd/add-todo.md +0 -42
- package/commands/gsd/audit-milestone.md +0 -42
- package/commands/gsd/check-todos.md +0 -41
- package/commands/gsd/cleanup.md +0 -18
- package/commands/gsd/complete-milestone.md +0 -136
- package/commands/gsd/discuss-phase.md +0 -87
- package/commands/gsd/execute-phase.md +0 -42
- package/commands/gsd/health.md +0 -22
- package/commands/gsd/help.md +0 -22
- package/commands/gsd/insert-phase.md +0 -33
- package/commands/gsd/join-discord.md +0 -18
- package/commands/gsd/list-phase-assumptions.md +0 -50
- package/commands/gsd/map-codebase.md +0 -71
- package/commands/gsd/new-milestone.md +0 -51
- package/commands/gsd/new-project.md +0 -42
- package/commands/gsd/new-project.md.bak +0 -1041
- package/commands/gsd/pause-work.md +0 -35
- package/commands/gsd/plan-milestone-gaps.md +0 -40
- package/commands/gsd/plan-phase.md +0 -44
- package/commands/gsd/progress.md +0 -24
- package/commands/gsd/quick.md +0 -40
- package/commands/gsd/reapply-patches.md +0 -110
- package/commands/gsd/remove-phase.md +0 -32
- package/commands/gsd/research-phase.md +0 -187
- package/commands/gsd/resume-work.md +0 -40
- package/commands/gsd/set-profile.md +0 -34
- package/commands/gsd/settings.md +0 -36
- package/commands/gsd/update.md +0 -37
- package/commands/gsd/verify-work.md +0 -39
package/dist/declare-tools.cjs
CHANGED
|
@@ -75,6 +75,122 @@ var require_commit = __commonJS({
|
|
|
75
75
|
}
|
|
76
76
|
});
|
|
77
77
|
|
|
78
|
+
// src/artifacts/state.js
|
|
79
|
+
var require_state = __commonJS({
|
|
80
|
+
"src/artifacts/state.js"(exports2, module2) {
|
|
81
|
+
"use strict";
|
|
82
|
+
var fs = require("fs");
|
|
83
|
+
var path = require("path");
|
|
84
|
+
function statePath(cwd) {
|
|
85
|
+
return path.join(cwd, ".planning", "STATE.md");
|
|
86
|
+
}
|
|
87
|
+
function readState2(cwd) {
|
|
88
|
+
const filePath = statePath(cwd);
|
|
89
|
+
if (!fs.existsSync(filePath)) return null;
|
|
90
|
+
const raw = fs.readFileSync(filePath, "utf8");
|
|
91
|
+
function extractSection(text, heading) {
|
|
92
|
+
const pattern = new RegExp(`## ${heading}\\s*\\n([\\s\\S]*?)(?=## |$)`, "i");
|
|
93
|
+
const match = text.match(pattern);
|
|
94
|
+
return match ? match[1].trim() : "";
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
raw,
|
|
98
|
+
currentPosition: extractSection(raw, "Current Position"),
|
|
99
|
+
recentWork: extractSection(raw, "Recent Work"),
|
|
100
|
+
decisions: extractSection(raw, "Decisions Made"),
|
|
101
|
+
blockers: extractSection(raw, "Blockers"),
|
|
102
|
+
sessionHistory: extractSection(raw, "Session History")
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function writeState(cwd, data) {
|
|
106
|
+
const planningDir = path.join(cwd, ".planning");
|
|
107
|
+
if (!fs.existsSync(planningDir)) {
|
|
108
|
+
fs.mkdirSync(planningDir, { recursive: true });
|
|
109
|
+
}
|
|
110
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
111
|
+
const content = buildStateContent(today, data);
|
|
112
|
+
fs.writeFileSync(statePath(cwd), content, "utf8");
|
|
113
|
+
}
|
|
114
|
+
function buildStateContent(date, data) {
|
|
115
|
+
const {
|
|
116
|
+
currentPosition = "Project initialized",
|
|
117
|
+
recentWork = "(none yet)",
|
|
118
|
+
decisions = "| Decision | Rationale | Date |\n|----------|-----------|------|\n",
|
|
119
|
+
blockers = "(none)",
|
|
120
|
+
sessionHistory = "| Date | Stopped At | Resume File |\n|------|------------|-------------|"
|
|
121
|
+
} = data;
|
|
122
|
+
return [
|
|
123
|
+
"# Project State",
|
|
124
|
+
"",
|
|
125
|
+
`**Last Updated:** ${date}`,
|
|
126
|
+
`**Current Position:** ${currentPosition}`,
|
|
127
|
+
"",
|
|
128
|
+
"## Recent Work",
|
|
129
|
+
"",
|
|
130
|
+
recentWork,
|
|
131
|
+
"",
|
|
132
|
+
"## Decisions Made",
|
|
133
|
+
"",
|
|
134
|
+
decisions,
|
|
135
|
+
"",
|
|
136
|
+
"## Blockers",
|
|
137
|
+
"",
|
|
138
|
+
blockers,
|
|
139
|
+
"",
|
|
140
|
+
"## Session History",
|
|
141
|
+
"",
|
|
142
|
+
sessionHistory,
|
|
143
|
+
""
|
|
144
|
+
].join("\n");
|
|
145
|
+
}
|
|
146
|
+
function recordSession2(cwd, stoppedAt, resumeFile) {
|
|
147
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
148
|
+
const resumeValue = resumeFile || "\u2014";
|
|
149
|
+
const newRow = `| ${today} | ${stoppedAt} | ${resumeValue} |`;
|
|
150
|
+
const filePath = statePath(cwd);
|
|
151
|
+
if (!fs.existsSync(filePath)) {
|
|
152
|
+
writeState(cwd, {
|
|
153
|
+
currentPosition: stoppedAt,
|
|
154
|
+
sessionHistory: `| Date | Stopped At | Resume File |
|
|
155
|
+
|------|------------|-------------|
|
|
156
|
+
${newRow}`
|
|
157
|
+
});
|
|
158
|
+
return { ok: true, path: filePath };
|
|
159
|
+
}
|
|
160
|
+
let content = fs.readFileSync(filePath, "utf8");
|
|
161
|
+
content = content.replace(
|
|
162
|
+
/\*\*Last Updated:\*\*[^\n]*/,
|
|
163
|
+
`**Last Updated:** ${today}`
|
|
164
|
+
);
|
|
165
|
+
content = content.replace(
|
|
166
|
+
/\*\*Current Position:\*\*[^\n]*/,
|
|
167
|
+
`**Current Position:** ${stoppedAt}`
|
|
168
|
+
);
|
|
169
|
+
const sessionTablePattern = /## Session History\s*\n([\s\S]*?)(?=## |$)/i;
|
|
170
|
+
const match = content.match(sessionTablePattern);
|
|
171
|
+
if (match) {
|
|
172
|
+
const existingSection = match[1];
|
|
173
|
+
const updatedSection = existingSection.trimEnd() + "\n" + newRow + "\n";
|
|
174
|
+
content = content.replace(sessionTablePattern, `## Session History
|
|
175
|
+
|
|
176
|
+
${updatedSection}
|
|
177
|
+
`);
|
|
178
|
+
} else {
|
|
179
|
+
content += `
|
|
180
|
+
## Session History
|
|
181
|
+
|
|
182
|
+
| Date | Stopped At | Resume File |
|
|
183
|
+
|------|------------|-------------|
|
|
184
|
+
${newRow}
|
|
185
|
+
`;
|
|
186
|
+
}
|
|
187
|
+
fs.writeFileSync(filePath, content, "utf8");
|
|
188
|
+
return { ok: true, path: filePath };
|
|
189
|
+
}
|
|
190
|
+
module2.exports = { readState: readState2, writeState, recordSession: recordSession2 };
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
|
|
78
194
|
// src/artifacts/future.js
|
|
79
195
|
var require_future = __commonJS({
|
|
80
196
|
"src/artifacts/future.js"(exports2, module2) {
|
|
@@ -363,7 +479,24 @@ var require_plan = __commonJS({
|
|
|
363
479
|
}
|
|
364
480
|
return lines.join("\n");
|
|
365
481
|
}
|
|
366
|
-
|
|
482
|
+
function updateActionStatus(content, actionId, newStatus) {
|
|
483
|
+
const lines = content.split("\n");
|
|
484
|
+
let inSection = false;
|
|
485
|
+
let patched = false;
|
|
486
|
+
for (let i = 0; i < lines.length; i++) {
|
|
487
|
+
const line = lines[i];
|
|
488
|
+
if (line.startsWith("### ")) {
|
|
489
|
+
inSection = line.startsWith(`### ${actionId}:`);
|
|
490
|
+
if (!inSection) patched = false;
|
|
491
|
+
}
|
|
492
|
+
if (inSection && !patched && /^\*\*Status:\*\*/i.test(line.trim())) {
|
|
493
|
+
lines[i] = `**Status:** ${newStatus}`;
|
|
494
|
+
patched = true;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
return lines.join("\n");
|
|
498
|
+
}
|
|
499
|
+
module2.exports = { parsePlanFile, writePlanFile, updateActionStatus };
|
|
367
500
|
}
|
|
368
501
|
});
|
|
369
502
|
|
|
@@ -2727,8 +2860,999 @@ var require_renegotiate = __commonJS({
|
|
|
2727
2860
|
}
|
|
2728
2861
|
});
|
|
2729
2862
|
|
|
2863
|
+
// src/commands/complete-milestone.js
|
|
2864
|
+
var require_complete_milestone = __commonJS({
|
|
2865
|
+
"src/commands/complete-milestone.js"(exports2, module2) {
|
|
2866
|
+
"use strict";
|
|
2867
|
+
var { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, statSync, copyFileSync } = require("node:fs");
|
|
2868
|
+
var { join, basename } = require("node:path");
|
|
2869
|
+
var { parseFlag } = require_parse_args();
|
|
2870
|
+
function normalizeVersion(raw) {
|
|
2871
|
+
return raw.startsWith("v") ? raw : `v${raw}`;
|
|
2872
|
+
}
|
|
2873
|
+
function copyDir(src, dest) {
|
|
2874
|
+
mkdirSync(dest, { recursive: true });
|
|
2875
|
+
const copied = [];
|
|
2876
|
+
for (const entry of readdirSync(src)) {
|
|
2877
|
+
const srcPath = join(src, entry);
|
|
2878
|
+
const destPath = join(dest, entry);
|
|
2879
|
+
const stat = statSync(srcPath);
|
|
2880
|
+
if (stat.isDirectory()) {
|
|
2881
|
+
const subCopied = copyDir(srcPath, destPath);
|
|
2882
|
+
for (const f of subCopied) copied.push(join(entry, f));
|
|
2883
|
+
} else {
|
|
2884
|
+
copyFileSync(srcPath, destPath);
|
|
2885
|
+
copied.push(entry);
|
|
2886
|
+
}
|
|
2887
|
+
}
|
|
2888
|
+
return copied;
|
|
2889
|
+
}
|
|
2890
|
+
function runCompleteMilestone2(cwd, args) {
|
|
2891
|
+
const versionRaw = parseFlag(args, "version");
|
|
2892
|
+
if (!versionRaw) {
|
|
2893
|
+
return { error: "Missing required flag: --version (e.g., --version v1.0)" };
|
|
2894
|
+
}
|
|
2895
|
+
const version = normalizeVersion(versionRaw);
|
|
2896
|
+
const planningDir = join(cwd, ".planning");
|
|
2897
|
+
const milestonesDir = join(planningDir, "milestones");
|
|
2898
|
+
const archiveDir = join(milestonesDir, version);
|
|
2899
|
+
if (!existsSync(planningDir)) {
|
|
2900
|
+
return { error: ".planning/ directory not found. Run /declare:init first." };
|
|
2901
|
+
}
|
|
2902
|
+
if (existsSync(archiveDir)) {
|
|
2903
|
+
return { error: `Archive already exists for ${version} at .planning/milestones/${version}/. Delete it first to re-archive.` };
|
|
2904
|
+
}
|
|
2905
|
+
const futurePath = join(planningDir, "FUTURE.md");
|
|
2906
|
+
const milestonesFilePath = join(planningDir, "MILESTONES.md");
|
|
2907
|
+
if (!existsSync(futurePath)) {
|
|
2908
|
+
return { error: "FUTURE.md not found in .planning/. Cannot archive." };
|
|
2909
|
+
}
|
|
2910
|
+
if (!existsSync(milestonesFilePath)) {
|
|
2911
|
+
return { error: "MILESTONES.md not found in .planning/. Cannot archive." };
|
|
2912
|
+
}
|
|
2913
|
+
mkdirSync(archiveDir, { recursive: true });
|
|
2914
|
+
const archivedFiles = [];
|
|
2915
|
+
const archiveFuture = join(archiveDir, "FUTURE.md");
|
|
2916
|
+
copyFileSync(futurePath, archiveFuture);
|
|
2917
|
+
archivedFiles.push(`milestones/${version}/FUTURE.md`);
|
|
2918
|
+
const archiveMilestones = join(archiveDir, "MILESTONES.md");
|
|
2919
|
+
copyFileSync(milestonesFilePath, archiveMilestones);
|
|
2920
|
+
archivedFiles.push(`milestones/${version}/MILESTONES.md`);
|
|
2921
|
+
const milestonesFolderBase = milestonesDir;
|
|
2922
|
+
if (existsSync(milestonesFolderBase)) {
|
|
2923
|
+
const entries = readdirSync(milestonesFolderBase);
|
|
2924
|
+
for (const entry of entries) {
|
|
2925
|
+
if (/^M-\d+/.test(entry)) {
|
|
2926
|
+
const srcFolder = join(milestonesFolderBase, entry);
|
|
2927
|
+
const stat = statSync(srcFolder);
|
|
2928
|
+
if (stat.isDirectory()) {
|
|
2929
|
+
const destFolder = join(archiveDir, entry);
|
|
2930
|
+
const copied = copyDir(srcFolder, destFolder);
|
|
2931
|
+
for (const f of copied) {
|
|
2932
|
+
archivedFiles.push(`milestones/${version}/${entry}/${f}`);
|
|
2933
|
+
}
|
|
2934
|
+
}
|
|
2935
|
+
}
|
|
2936
|
+
}
|
|
2937
|
+
}
|
|
2938
|
+
return {
|
|
2939
|
+
version,
|
|
2940
|
+
archivedFiles,
|
|
2941
|
+
gitTagReady: true
|
|
2942
|
+
};
|
|
2943
|
+
}
|
|
2944
|
+
module2.exports = { runCompleteMilestone: runCompleteMilestone2 };
|
|
2945
|
+
}
|
|
2946
|
+
});
|
|
2947
|
+
|
|
2948
|
+
// src/commands/sync-status.js
|
|
2949
|
+
var require_sync_status = __commonJS({
|
|
2950
|
+
"src/commands/sync-status.js"(exports2, module2) {
|
|
2951
|
+
"use strict";
|
|
2952
|
+
var { existsSync, readFileSync, writeFileSync } = require("node:fs");
|
|
2953
|
+
var { join, resolve } = require("node:path");
|
|
2954
|
+
var { buildDagFromDisk, loadActionsFromFolders } = require_build_dag();
|
|
2955
|
+
var { parseMilestonesFile, writeMilestonesFile } = require_milestones();
|
|
2956
|
+
var { parseFutureFile, writeFutureFile } = require_future();
|
|
2957
|
+
var { parsePlanFile, updateActionStatus } = require_plan();
|
|
2958
|
+
var { findMilestoneFolder } = require_milestone_folders();
|
|
2959
|
+
var { isCompleted } = require_engine();
|
|
2960
|
+
function looksLikeFilePath(produces) {
|
|
2961
|
+
if (!produces || !produces.trim()) return false;
|
|
2962
|
+
return /[/\\]/.test(produces) || /\.\w{1,10}$/.test(produces);
|
|
2963
|
+
}
|
|
2964
|
+
function runSyncStatus2(cwd) {
|
|
2965
|
+
const planningDir = join(cwd, ".planning");
|
|
2966
|
+
if (!existsSync(planningDir)) {
|
|
2967
|
+
return { error: "No Declare project found. Run /declare:init first." };
|
|
2968
|
+
}
|
|
2969
|
+
const graphResult = buildDagFromDisk(cwd);
|
|
2970
|
+
if ("error" in graphResult) return graphResult;
|
|
2971
|
+
const { dag, milestones, declarations } = graphResult;
|
|
2972
|
+
const actionResults = [];
|
|
2973
|
+
const milestoneResults = [];
|
|
2974
|
+
const declarationResults = [];
|
|
2975
|
+
for (const m of milestones) {
|
|
2976
|
+
const folderPath = findMilestoneFolder(planningDir, m.id);
|
|
2977
|
+
if (!folderPath) continue;
|
|
2978
|
+
const planPath = join(folderPath, "PLAN.md");
|
|
2979
|
+
if (!existsSync(planPath)) continue;
|
|
2980
|
+
let planContent = readFileSync(planPath, "utf-8");
|
|
2981
|
+
const { actions } = parsePlanFile(planContent);
|
|
2982
|
+
let planDirty = false;
|
|
2983
|
+
const milestoneAlreadyDone = isCompleted(m.status);
|
|
2984
|
+
for (const action of actions) {
|
|
2985
|
+
if (isCompleted(action.status)) {
|
|
2986
|
+
actionResults.push({ id: action.id, milestone: m.id, changed: false, reason: "already DONE" });
|
|
2987
|
+
continue;
|
|
2988
|
+
}
|
|
2989
|
+
if (milestoneAlreadyDone) {
|
|
2990
|
+
planContent = updateActionStatus(planContent, action.id, "DONE");
|
|
2991
|
+
planDirty = true;
|
|
2992
|
+
actionResults.push({ id: action.id, milestone: m.id, changed: true, reason: `milestone ${m.id} is DONE` });
|
|
2993
|
+
continue;
|
|
2994
|
+
}
|
|
2995
|
+
const produces = action.produces || "";
|
|
2996
|
+
if (looksLikeFilePath(produces)) {
|
|
2997
|
+
const filePath = resolve(cwd, produces);
|
|
2998
|
+
if (existsSync(filePath)) {
|
|
2999
|
+
planContent = updateActionStatus(planContent, action.id, "DONE");
|
|
3000
|
+
planDirty = true;
|
|
3001
|
+
actionResults.push({ id: action.id, milestone: m.id, changed: true, reason: `produces exists: ${produces}` });
|
|
3002
|
+
} else {
|
|
3003
|
+
actionResults.push({ id: action.id, milestone: m.id, changed: false, reason: `produces missing: ${produces}` });
|
|
3004
|
+
}
|
|
3005
|
+
} else {
|
|
3006
|
+
actionResults.push({ id: action.id, milestone: m.id, changed: false, reason: "no verifiable produces path" });
|
|
3007
|
+
}
|
|
3008
|
+
}
|
|
3009
|
+
if (planDirty) {
|
|
3010
|
+
writeFileSync(planPath, planContent, "utf-8");
|
|
3011
|
+
}
|
|
3012
|
+
}
|
|
3013
|
+
const freshActions = loadActionsFromFolders(planningDir);
|
|
3014
|
+
const milestoneActionIds = /* @__PURE__ */ new Map();
|
|
3015
|
+
const actionStatusMap = /* @__PURE__ */ new Map();
|
|
3016
|
+
for (const a of freshActions) {
|
|
3017
|
+
actionStatusMap.set(a.id, a.status);
|
|
3018
|
+
for (const mid of a.causes) {
|
|
3019
|
+
if (!milestoneActionIds.has(mid)) milestoneActionIds.set(mid, []);
|
|
3020
|
+
milestoneActionIds.get(mid).push(a.id);
|
|
3021
|
+
}
|
|
3022
|
+
}
|
|
3023
|
+
const milestonesPath = join(planningDir, "MILESTONES.md");
|
|
3024
|
+
const milestonesContent = existsSync(milestonesPath) ? readFileSync(milestonesPath, "utf-8") : "";
|
|
3025
|
+
const { milestones: parsedMilestones } = parseMilestonesFile(milestonesContent);
|
|
3026
|
+
let milestonesDirty = false;
|
|
3027
|
+
const updatedMilestones = parsedMilestones.map((m) => {
|
|
3028
|
+
if (isCompleted(m.status)) {
|
|
3029
|
+
milestoneResults.push({ id: m.id, changed: false, reason: "already DONE" });
|
|
3030
|
+
return m;
|
|
3031
|
+
}
|
|
3032
|
+
const actionIds = milestoneActionIds.get(m.id) || [];
|
|
3033
|
+
if (actionIds.length === 0) {
|
|
3034
|
+
milestoneResults.push({ id: m.id, changed: false, reason: "no actions found" });
|
|
3035
|
+
return m;
|
|
3036
|
+
}
|
|
3037
|
+
const allDone = actionIds.every((aid) => isCompleted(actionStatusMap.get(aid) || "PENDING"));
|
|
3038
|
+
if (allDone) {
|
|
3039
|
+
milestonesDirty = true;
|
|
3040
|
+
milestoneResults.push({ id: m.id, changed: true, reason: `all ${actionIds.length} actions DONE` });
|
|
3041
|
+
return { ...m, status: "DONE" };
|
|
3042
|
+
} else {
|
|
3043
|
+
const doneCount = actionIds.filter((aid) => isCompleted(actionStatusMap.get(aid) || "PENDING")).length;
|
|
3044
|
+
milestoneResults.push({ id: m.id, changed: false, reason: `${doneCount}/${actionIds.length} actions DONE` });
|
|
3045
|
+
return m;
|
|
3046
|
+
}
|
|
3047
|
+
});
|
|
3048
|
+
if (milestonesDirty) {
|
|
3049
|
+
const projectNameMatch = milestonesContent.match(/^# Milestones:\s*(.+)/m);
|
|
3050
|
+
const projectName = projectNameMatch ? projectNameMatch[1].trim() : "Project";
|
|
3051
|
+
writeFileSync(milestonesPath, writeMilestonesFile(updatedMilestones, projectName), "utf-8");
|
|
3052
|
+
}
|
|
3053
|
+
const futurePath = join(planningDir, "FUTURE.md");
|
|
3054
|
+
const futureContent = existsSync(futurePath) ? readFileSync(futurePath, "utf-8") : "";
|
|
3055
|
+
const parsedDeclarations = parseFutureFile(futureContent);
|
|
3056
|
+
const milestoneStatusMap = new Map(updatedMilestones.map((m) => [m.id, m.status]));
|
|
3057
|
+
let futureDirty = false;
|
|
3058
|
+
const updatedDeclarations = parsedDeclarations.map((d) => {
|
|
3059
|
+
if (isCompleted(d.status)) {
|
|
3060
|
+
declarationResults.push({ id: d.id, changed: false, reason: "already DONE" });
|
|
3061
|
+
return d;
|
|
3062
|
+
}
|
|
3063
|
+
const realizingMilestones = updatedMilestones.filter((m) => m.realizes.includes(d.id));
|
|
3064
|
+
if (realizingMilestones.length === 0) {
|
|
3065
|
+
declarationResults.push({ id: d.id, changed: false, reason: "no milestones realize this declaration" });
|
|
3066
|
+
return d;
|
|
3067
|
+
}
|
|
3068
|
+
const allDone = realizingMilestones.every((m) => isCompleted(milestoneStatusMap.get(m.id) || "PENDING"));
|
|
3069
|
+
if (allDone) {
|
|
3070
|
+
futureDirty = true;
|
|
3071
|
+
declarationResults.push({ id: d.id, changed: true, reason: `all ${realizingMilestones.length} milestones DONE` });
|
|
3072
|
+
return { ...d, status: "DONE" };
|
|
3073
|
+
} else {
|
|
3074
|
+
const doneCount = realizingMilestones.filter((m) => isCompleted(milestoneStatusMap.get(m.id) || "PENDING")).length;
|
|
3075
|
+
declarationResults.push({ id: d.id, changed: false, reason: `${doneCount}/${realizingMilestones.length} milestones DONE` });
|
|
3076
|
+
return d;
|
|
3077
|
+
}
|
|
3078
|
+
});
|
|
3079
|
+
if (futureDirty) {
|
|
3080
|
+
const projectNameMatch = futureContent.match(/^# Future:\s*(.+)/m);
|
|
3081
|
+
const projectName = projectNameMatch ? projectNameMatch[1].trim() : "Project";
|
|
3082
|
+
writeFileSync(futurePath, writeFutureFile(updatedDeclarations, projectName), "utf-8");
|
|
3083
|
+
}
|
|
3084
|
+
const actionsDone = actionResults.filter((r) => r.changed).length;
|
|
3085
|
+
const msDone = milestoneResults.filter((r) => r.changed).length;
|
|
3086
|
+
const declsDone = declarationResults.filter((r) => r.changed).length;
|
|
3087
|
+
const summary = [
|
|
3088
|
+
`Actions marked DONE: ${actionsDone}/${actionResults.length}`,
|
|
3089
|
+
`Milestones marked DONE: ${msDone}/${milestoneResults.length}`,
|
|
3090
|
+
`Declarations marked DONE: ${declsDone}/${declarationResults.length}`
|
|
3091
|
+
].join(" | ");
|
|
3092
|
+
return { actions: actionResults, milestones: milestoneResults, declarations: declarationResults, summary };
|
|
3093
|
+
}
|
|
3094
|
+
module2.exports = { runSyncStatus: runSyncStatus2 };
|
|
3095
|
+
}
|
|
3096
|
+
});
|
|
3097
|
+
|
|
3098
|
+
// src/commands/quick-task.js
|
|
3099
|
+
var require_quick_task = __commonJS({
|
|
3100
|
+
"src/commands/quick-task.js"(exports2, module2) {
|
|
3101
|
+
"use strict";
|
|
3102
|
+
var { existsSync, mkdirSync, writeFileSync, readdirSync } = require("node:fs");
|
|
3103
|
+
var { join } = require("node:path");
|
|
3104
|
+
var { commitPlanningDocs: commitPlanningDocs2, loadConfig } = require_commit();
|
|
3105
|
+
var { parseFlag } = require_parse_args();
|
|
3106
|
+
function slugify(text) {
|
|
3107
|
+
return text.toLowerCase().replace(/[^a-z0-9\s-]/g, "").trim().replace(/\s+/g, "-").replace(/-+/g, "-").slice(0, 40);
|
|
3108
|
+
}
|
|
3109
|
+
function nextQuickNumber(quickDir) {
|
|
3110
|
+
if (!existsSync(quickDir)) return "001";
|
|
3111
|
+
const entries = readdirSync(quickDir, { withFileTypes: true });
|
|
3112
|
+
let max = 0;
|
|
3113
|
+
for (const entry of entries) {
|
|
3114
|
+
if (!entry.isDirectory()) continue;
|
|
3115
|
+
const match = entry.name.match(/^(\d{3})-/);
|
|
3116
|
+
if (match) {
|
|
3117
|
+
const n = parseInt(match[1], 10);
|
|
3118
|
+
if (n > max) max = n;
|
|
3119
|
+
}
|
|
3120
|
+
}
|
|
3121
|
+
return String(max + 1).padStart(3, "0");
|
|
3122
|
+
}
|
|
3123
|
+
function runQuickTask2(cwd, args) {
|
|
3124
|
+
const description = parseFlag(args, "description");
|
|
3125
|
+
const slugOverride = parseFlag(args, "slug");
|
|
3126
|
+
if (!description) {
|
|
3127
|
+
return { error: "Missing required flag: --description" };
|
|
3128
|
+
}
|
|
3129
|
+
const quickDir = join(cwd, ".planning", "quick");
|
|
3130
|
+
if (!existsSync(quickDir)) {
|
|
3131
|
+
mkdirSync(quickDir, { recursive: true });
|
|
3132
|
+
}
|
|
3133
|
+
const num = nextQuickNumber(quickDir);
|
|
3134
|
+
const slug = slugOverride ? slugify(slugOverride) : slugify(description);
|
|
3135
|
+
const folderName = `${num}-${slug}`;
|
|
3136
|
+
const folderPath = join(quickDir, folderName);
|
|
3137
|
+
const planPath = join(folderPath, "QUICK-PLAN.md");
|
|
3138
|
+
const relFolderPath = `.planning/quick/${folderName}`;
|
|
3139
|
+
const relPlanPath = `${relFolderPath}/QUICK-PLAN.md`;
|
|
3140
|
+
mkdirSync(folderPath, { recursive: true });
|
|
3141
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
3142
|
+
const content = [
|
|
3143
|
+
`# Quick Task ${num}: ${description}`,
|
|
3144
|
+
"",
|
|
3145
|
+
`**ID:** ${num}`,
|
|
3146
|
+
`**Created:** ${today}`,
|
|
3147
|
+
`**Status:** PENDING`,
|
|
3148
|
+
"",
|
|
3149
|
+
"## Description",
|
|
3150
|
+
"",
|
|
3151
|
+
description,
|
|
3152
|
+
"",
|
|
3153
|
+
"## Tasks",
|
|
3154
|
+
"",
|
|
3155
|
+
"- [ ] <!-- Add tasks here -->",
|
|
3156
|
+
"",
|
|
3157
|
+
"## Notes",
|
|
3158
|
+
"",
|
|
3159
|
+
"<!-- Add notes as you work -->",
|
|
3160
|
+
""
|
|
3161
|
+
].join("\n");
|
|
3162
|
+
writeFileSync(planPath, content, "utf-8");
|
|
3163
|
+
const config = loadConfig(cwd);
|
|
3164
|
+
let committed = false;
|
|
3165
|
+
let hash;
|
|
3166
|
+
if (config.commit_docs !== false) {
|
|
3167
|
+
const result = commitPlanningDocs2(
|
|
3168
|
+
cwd,
|
|
3169
|
+
`declare: add quick task ${num} "${description}"`,
|
|
3170
|
+
[relPlanPath]
|
|
3171
|
+
);
|
|
3172
|
+
committed = result.committed;
|
|
3173
|
+
hash = result.hash;
|
|
3174
|
+
}
|
|
3175
|
+
return {
|
|
3176
|
+
id: num,
|
|
3177
|
+
folder: relFolderPath,
|
|
3178
|
+
planPath: relPlanPath,
|
|
3179
|
+
committed,
|
|
3180
|
+
hash
|
|
3181
|
+
};
|
|
3182
|
+
}
|
|
3183
|
+
module2.exports = { runQuickTask: runQuickTask2 };
|
|
3184
|
+
}
|
|
3185
|
+
});
|
|
3186
|
+
|
|
3187
|
+
// src/commands/todo.js
|
|
3188
|
+
var require_todo = __commonJS({
|
|
3189
|
+
"src/commands/todo.js"(exports2, module2) {
|
|
3190
|
+
"use strict";
|
|
3191
|
+
var {
|
|
3192
|
+
existsSync,
|
|
3193
|
+
mkdirSync,
|
|
3194
|
+
writeFileSync,
|
|
3195
|
+
readFileSync,
|
|
3196
|
+
readdirSync,
|
|
3197
|
+
renameSync
|
|
3198
|
+
} = require("node:fs");
|
|
3199
|
+
var { join, basename } = require("node:path");
|
|
3200
|
+
var { commitPlanningDocs: commitPlanningDocs2, loadConfig } = require_commit();
|
|
3201
|
+
var { parseFlag } = require_parse_args();
|
|
3202
|
+
function slugify(text) {
|
|
3203
|
+
return text.toLowerCase().replace(/[^a-z0-9\s-]/g, "").trim().replace(/\s+/g, "-").replace(/-+/g, "-").slice(0, 40);
|
|
3204
|
+
}
|
|
3205
|
+
function nextTodoNumber(todosDir) {
|
|
3206
|
+
if (!existsSync(todosDir)) return "001";
|
|
3207
|
+
const entries = readdirSync(todosDir, { withFileTypes: true });
|
|
3208
|
+
let max = 0;
|
|
3209
|
+
for (const entry of entries) {
|
|
3210
|
+
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
3211
|
+
const match = entry.name.match(/^(\d{3})-/);
|
|
3212
|
+
if (match) {
|
|
3213
|
+
const n = parseInt(match[1], 10);
|
|
3214
|
+
if (n > max) max = n;
|
|
3215
|
+
}
|
|
3216
|
+
}
|
|
3217
|
+
const completedDir = join(todosDir, "completed");
|
|
3218
|
+
if (existsSync(completedDir)) {
|
|
3219
|
+
const completed = readdirSync(completedDir, { withFileTypes: true });
|
|
3220
|
+
for (const entry of completed) {
|
|
3221
|
+
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
3222
|
+
const match = entry.name.match(/^(\d{3})-/);
|
|
3223
|
+
if (match) {
|
|
3224
|
+
const n = parseInt(match[1], 10);
|
|
3225
|
+
if (n > max) max = n;
|
|
3226
|
+
}
|
|
3227
|
+
}
|
|
3228
|
+
}
|
|
3229
|
+
return String(max + 1).padStart(3, "0");
|
|
3230
|
+
}
|
|
3231
|
+
function parseTodoFile(content) {
|
|
3232
|
+
const lines = content.split("\n");
|
|
3233
|
+
let description = "";
|
|
3234
|
+
let created = "";
|
|
3235
|
+
if (lines[0] === "---") {
|
|
3236
|
+
let i = 1;
|
|
3237
|
+
while (i < lines.length && lines[i] !== "---") {
|
|
3238
|
+
const createdMatch = lines[i].match(/^created:\s*(.+)$/);
|
|
3239
|
+
if (createdMatch) created = createdMatch[1].trim();
|
|
3240
|
+
i++;
|
|
3241
|
+
}
|
|
3242
|
+
}
|
|
3243
|
+
for (const line of lines) {
|
|
3244
|
+
const h1Match = line.match(/^#\s+(.+)$/);
|
|
3245
|
+
if (h1Match) {
|
|
3246
|
+
description = h1Match[1].trim();
|
|
3247
|
+
break;
|
|
3248
|
+
}
|
|
3249
|
+
}
|
|
3250
|
+
return { description, created };
|
|
3251
|
+
}
|
|
3252
|
+
function runAddTodo2(cwd, args) {
|
|
3253
|
+
const description = parseFlag(args, "description");
|
|
3254
|
+
if (!description) {
|
|
3255
|
+
return { error: "Missing required flag: --description" };
|
|
3256
|
+
}
|
|
3257
|
+
const todosDir = join(cwd, ".planning", "todos");
|
|
3258
|
+
if (!existsSync(todosDir)) {
|
|
3259
|
+
mkdirSync(todosDir, { recursive: true });
|
|
3260
|
+
}
|
|
3261
|
+
const num = nextTodoNumber(todosDir);
|
|
3262
|
+
const slug = slugify(description);
|
|
3263
|
+
const fileName = `${num}-${slug}.md`;
|
|
3264
|
+
const filePath = join(todosDir, fileName);
|
|
3265
|
+
const relPath = `.planning/todos/${fileName}`;
|
|
3266
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
3267
|
+
const content = [
|
|
3268
|
+
"---",
|
|
3269
|
+
`created: ${today}`,
|
|
3270
|
+
"status: pending",
|
|
3271
|
+
"---",
|
|
3272
|
+
"",
|
|
3273
|
+
`# ${description}`,
|
|
3274
|
+
"",
|
|
3275
|
+
"## Notes",
|
|
3276
|
+
"",
|
|
3277
|
+
"<!-- Add context and notes here -->",
|
|
3278
|
+
""
|
|
3279
|
+
].join("\n");
|
|
3280
|
+
writeFileSync(filePath, content, "utf-8");
|
|
3281
|
+
const config = loadConfig(cwd);
|
|
3282
|
+
let committed = false;
|
|
3283
|
+
let hash;
|
|
3284
|
+
if (config.commit_docs !== false) {
|
|
3285
|
+
const result = commitPlanningDocs2(
|
|
3286
|
+
cwd,
|
|
3287
|
+
`declare: add todo ${num} "${description}"`,
|
|
3288
|
+
[relPath]
|
|
3289
|
+
);
|
|
3290
|
+
committed = result.committed;
|
|
3291
|
+
hash = result.hash;
|
|
3292
|
+
}
|
|
3293
|
+
return {
|
|
3294
|
+
id: num,
|
|
3295
|
+
path: relPath,
|
|
3296
|
+
committed,
|
|
3297
|
+
hash
|
|
3298
|
+
};
|
|
3299
|
+
}
|
|
3300
|
+
function runCheckTodos2(cwd) {
|
|
3301
|
+
const todosDir = join(cwd, ".planning", "todos");
|
|
3302
|
+
if (!existsSync(todosDir)) {
|
|
3303
|
+
return { todos: [] };
|
|
3304
|
+
}
|
|
3305
|
+
const entries = readdirSync(todosDir, { withFileTypes: true });
|
|
3306
|
+
const todos = [];
|
|
3307
|
+
for (const entry of entries) {
|
|
3308
|
+
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
3309
|
+
const match = entry.name.match(/^(\d{3})-/);
|
|
3310
|
+
if (!match) continue;
|
|
3311
|
+
const id = match[1];
|
|
3312
|
+
const filePath = join(todosDir, entry.name);
|
|
3313
|
+
const relPath = `.planning/todos/${entry.name}`;
|
|
3314
|
+
let content = "";
|
|
3315
|
+
try {
|
|
3316
|
+
content = readFileSync(filePath, "utf-8");
|
|
3317
|
+
} catch {
|
|
3318
|
+
continue;
|
|
3319
|
+
}
|
|
3320
|
+
const { description, created } = parseTodoFile(content);
|
|
3321
|
+
todos.push({
|
|
3322
|
+
id,
|
|
3323
|
+
description: description || entry.name.replace(/^\d{3}-/, "").replace(/\.md$/, "").replace(/-/g, " "),
|
|
3324
|
+
created: created || "",
|
|
3325
|
+
path: relPath
|
|
3326
|
+
});
|
|
3327
|
+
}
|
|
3328
|
+
todos.sort((a, b) => a.id.localeCompare(b.id));
|
|
3329
|
+
return { todos };
|
|
3330
|
+
}
|
|
3331
|
+
function runCompleteTodo2(cwd, args) {
|
|
3332
|
+
const id = parseFlag(args, "id");
|
|
3333
|
+
if (!id) {
|
|
3334
|
+
return { error: "Missing required flag: --id" };
|
|
3335
|
+
}
|
|
3336
|
+
const todosDir = join(cwd, ".planning", "todos");
|
|
3337
|
+
if (!existsSync(todosDir)) {
|
|
3338
|
+
return { error: `No todos directory found at .planning/todos/` };
|
|
3339
|
+
}
|
|
3340
|
+
const entries = readdirSync(todosDir, { withFileTypes: true });
|
|
3341
|
+
let todoFile = null;
|
|
3342
|
+
for (const entry of entries) {
|
|
3343
|
+
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
3344
|
+
if (entry.name.startsWith(`${id}-`) || entry.name === `${id}.md`) {
|
|
3345
|
+
todoFile = entry.name;
|
|
3346
|
+
break;
|
|
3347
|
+
}
|
|
3348
|
+
}
|
|
3349
|
+
if (!todoFile) {
|
|
3350
|
+
return { error: `Todo with ID ${id} not found in .planning/todos/` };
|
|
3351
|
+
}
|
|
3352
|
+
const completedDir = join(todosDir, "completed");
|
|
3353
|
+
if (!existsSync(completedDir)) {
|
|
3354
|
+
mkdirSync(completedDir, { recursive: true });
|
|
3355
|
+
}
|
|
3356
|
+
const fromPath = join(todosDir, todoFile);
|
|
3357
|
+
const toPath = join(completedDir, todoFile);
|
|
3358
|
+
const relFrom = `.planning/todos/${todoFile}`;
|
|
3359
|
+
const relTo = `.planning/todos/completed/${todoFile}`;
|
|
3360
|
+
renameSync(fromPath, toPath);
|
|
3361
|
+
const config = loadConfig(cwd);
|
|
3362
|
+
let committed = false;
|
|
3363
|
+
let hash;
|
|
3364
|
+
if (config.commit_docs !== false) {
|
|
3365
|
+
const result = commitPlanningDocs2(
|
|
3366
|
+
cwd,
|
|
3367
|
+
`declare: complete todo ${id}`,
|
|
3368
|
+
[relTo]
|
|
3369
|
+
);
|
|
3370
|
+
committed = result.committed;
|
|
3371
|
+
hash = result.hash;
|
|
3372
|
+
}
|
|
3373
|
+
return {
|
|
3374
|
+
id,
|
|
3375
|
+
from: relFrom,
|
|
3376
|
+
to: relTo,
|
|
3377
|
+
committed,
|
|
3378
|
+
hash
|
|
3379
|
+
};
|
|
3380
|
+
}
|
|
3381
|
+
module2.exports = { runAddTodo: runAddTodo2, runCheckTodos: runCheckTodos2, runCompleteTodo: runCompleteTodo2 };
|
|
3382
|
+
}
|
|
3383
|
+
});
|
|
3384
|
+
|
|
3385
|
+
// src/commands/config-get.js
|
|
3386
|
+
var require_config_get = __commonJS({
|
|
3387
|
+
"src/commands/config-get.js"(exports2, module2) {
|
|
3388
|
+
"use strict";
|
|
3389
|
+
var { existsSync, readFileSync } = require("node:fs");
|
|
3390
|
+
var { join } = require("node:path");
|
|
3391
|
+
function getAtPath(obj, path) {
|
|
3392
|
+
const parts = path.split(".");
|
|
3393
|
+
let current = obj;
|
|
3394
|
+
for (const part of parts) {
|
|
3395
|
+
if (current === null || typeof current !== "object") {
|
|
3396
|
+
return { found: false, value: void 0 };
|
|
3397
|
+
}
|
|
3398
|
+
const record = (
|
|
3399
|
+
/** @type {Record<string, unknown>} */
|
|
3400
|
+
current
|
|
3401
|
+
);
|
|
3402
|
+
if (!(part in record)) {
|
|
3403
|
+
return { found: false, value: void 0 };
|
|
3404
|
+
}
|
|
3405
|
+
current = record[part];
|
|
3406
|
+
}
|
|
3407
|
+
return { found: true, value: current };
|
|
3408
|
+
}
|
|
3409
|
+
function runConfigGet2(cwd, args) {
|
|
3410
|
+
const keyPath = args && args[0];
|
|
3411
|
+
if (!keyPath) {
|
|
3412
|
+
return { error: 'config-get requires a key path argument (e.g., "workflow.research")' };
|
|
3413
|
+
}
|
|
3414
|
+
const configPath = join(cwd, ".planning", "config.json");
|
|
3415
|
+
if (!existsSync(configPath)) {
|
|
3416
|
+
return { error: "No config.json found. Run /declare:init to initialize the project." };
|
|
3417
|
+
}
|
|
3418
|
+
let config;
|
|
3419
|
+
try {
|
|
3420
|
+
config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
3421
|
+
} catch (err) {
|
|
3422
|
+
return { error: `Failed to parse config.json: ${err.message}` };
|
|
3423
|
+
}
|
|
3424
|
+
const { found, value } = getAtPath(config, keyPath);
|
|
3425
|
+
if (!found) {
|
|
3426
|
+
return { error: `Key not found: ${keyPath}` };
|
|
3427
|
+
}
|
|
3428
|
+
return { key: keyPath, value };
|
|
3429
|
+
}
|
|
3430
|
+
module2.exports = { runConfigGet: runConfigGet2 };
|
|
3431
|
+
}
|
|
3432
|
+
});
|
|
3433
|
+
|
|
3434
|
+
// src/commands/config-set.js
|
|
3435
|
+
var require_config_set = __commonJS({
|
|
3436
|
+
"src/commands/config-set.js"(exports2, module2) {
|
|
3437
|
+
"use strict";
|
|
3438
|
+
var { existsSync, readFileSync, writeFileSync } = require("node:fs");
|
|
3439
|
+
var { join } = require("node:path");
|
|
3440
|
+
function parseValue(raw) {
|
|
3441
|
+
if (raw === "true") return true;
|
|
3442
|
+
if (raw === "false") return false;
|
|
3443
|
+
const num = Number(raw);
|
|
3444
|
+
if (!Number.isNaN(num) && raw.trim() !== "") return num;
|
|
3445
|
+
return raw;
|
|
3446
|
+
}
|
|
3447
|
+
function setAtPath(obj, path, value) {
|
|
3448
|
+
const parts = path.split(".");
|
|
3449
|
+
let current = obj;
|
|
3450
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
3451
|
+
const part = parts[i];
|
|
3452
|
+
if (current[part] === null || typeof current[part] !== "object") {
|
|
3453
|
+
current[part] = {};
|
|
3454
|
+
}
|
|
3455
|
+
current = /** @type {Record<string, unknown>} */
|
|
3456
|
+
current[part];
|
|
3457
|
+
}
|
|
3458
|
+
current[parts[parts.length - 1]] = value;
|
|
3459
|
+
}
|
|
3460
|
+
function parseKeyValueFlags(argv) {
|
|
3461
|
+
let key = null;
|
|
3462
|
+
let value = null;
|
|
3463
|
+
for (let i = 0; i < argv.length; i++) {
|
|
3464
|
+
if (argv[i] === "--key" && i + 1 < argv.length) {
|
|
3465
|
+
key = argv[i + 1];
|
|
3466
|
+
i++;
|
|
3467
|
+
} else if (argv[i] === "--value" && i + 1 < argv.length) {
|
|
3468
|
+
value = argv[i + 1];
|
|
3469
|
+
i++;
|
|
3470
|
+
}
|
|
3471
|
+
}
|
|
3472
|
+
return { key, value };
|
|
3473
|
+
}
|
|
3474
|
+
function runConfigSet2(cwd, args) {
|
|
3475
|
+
const { key: keyPath, value: rawValue } = parseKeyValueFlags(args);
|
|
3476
|
+
if (!keyPath) {
|
|
3477
|
+
return { error: "config-set requires --key <path.to.key>" };
|
|
3478
|
+
}
|
|
3479
|
+
if (rawValue === null) {
|
|
3480
|
+
return { error: "config-set requires --value <value>" };
|
|
3481
|
+
}
|
|
3482
|
+
const configPath = join(cwd, ".planning", "config.json");
|
|
3483
|
+
if (!existsSync(configPath)) {
|
|
3484
|
+
return { error: "No config.json found. Run /declare:init to initialize the project." };
|
|
3485
|
+
}
|
|
3486
|
+
let config;
|
|
3487
|
+
try {
|
|
3488
|
+
config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
3489
|
+
} catch (err) {
|
|
3490
|
+
return { error: `Failed to parse config.json: ${err.message}` };
|
|
3491
|
+
}
|
|
3492
|
+
const parsedValue = parseValue(rawValue);
|
|
3493
|
+
setAtPath(config, keyPath, parsedValue);
|
|
3494
|
+
try {
|
|
3495
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
3496
|
+
} catch (err) {
|
|
3497
|
+
return { error: `Failed to write config.json: ${err.message}` };
|
|
3498
|
+
}
|
|
3499
|
+
return { key: keyPath, value: parsedValue, updated: true };
|
|
3500
|
+
}
|
|
3501
|
+
module2.exports = { runConfigSet: runConfigSet2 };
|
|
3502
|
+
}
|
|
3503
|
+
});
|
|
3504
|
+
|
|
3505
|
+
// src/commands/health-check.js
|
|
3506
|
+
var require_health_check = __commonJS({
|
|
3507
|
+
"src/commands/health-check.js"(exports2, module2) {
|
|
3508
|
+
"use strict";
|
|
3509
|
+
var { existsSync, readFileSync, readdirSync, mkdirSync } = require("node:fs");
|
|
3510
|
+
var { join } = require("node:path");
|
|
3511
|
+
var { parseFutureFile } = require_future();
|
|
3512
|
+
var { parseMilestonesFile } = require_milestones();
|
|
3513
|
+
var { findMilestoneFolder, ensureMilestoneFolder } = require_milestone_folders();
|
|
3514
|
+
function runHealthCheck2(cwd) {
|
|
3515
|
+
const planningDir = join(cwd, ".planning");
|
|
3516
|
+
const milestonesDir = join(planningDir, "milestones");
|
|
3517
|
+
if (!existsSync(planningDir)) {
|
|
3518
|
+
return { error: "No .planning/ directory found. Run /declare:init to initialize the project." };
|
|
3519
|
+
}
|
|
3520
|
+
const issues = [];
|
|
3521
|
+
const futurePath = join(planningDir, "FUTURE.md");
|
|
3522
|
+
let declarations = [];
|
|
3523
|
+
if (!existsSync(futurePath)) {
|
|
3524
|
+
issues.push({
|
|
3525
|
+
type: "missing_file",
|
|
3526
|
+
message: "FUTURE.md is missing",
|
|
3527
|
+
path: ".planning/FUTURE.md",
|
|
3528
|
+
fixable: false
|
|
3529
|
+
});
|
|
3530
|
+
} else {
|
|
3531
|
+
try {
|
|
3532
|
+
const content = readFileSync(futurePath, "utf-8");
|
|
3533
|
+
declarations = parseFutureFile(content);
|
|
3534
|
+
} catch (err) {
|
|
3535
|
+
issues.push({
|
|
3536
|
+
type: "parse_error",
|
|
3537
|
+
message: `FUTURE.md could not be parsed: ${err.message}`,
|
|
3538
|
+
path: ".planning/FUTURE.md",
|
|
3539
|
+
fixable: false
|
|
3540
|
+
});
|
|
3541
|
+
}
|
|
3542
|
+
}
|
|
3543
|
+
const milestonesPath = join(planningDir, "MILESTONES.md");
|
|
3544
|
+
let milestones = [];
|
|
3545
|
+
if (!existsSync(milestonesPath)) {
|
|
3546
|
+
issues.push({
|
|
3547
|
+
type: "missing_file",
|
|
3548
|
+
message: "MILESTONES.md is missing",
|
|
3549
|
+
path: ".planning/MILESTONES.md",
|
|
3550
|
+
fixable: false
|
|
3551
|
+
});
|
|
3552
|
+
} else {
|
|
3553
|
+
try {
|
|
3554
|
+
const content = readFileSync(milestonesPath, "utf-8");
|
|
3555
|
+
const parsed = parseMilestonesFile(content);
|
|
3556
|
+
milestones = parsed.milestones;
|
|
3557
|
+
} catch (err) {
|
|
3558
|
+
issues.push({
|
|
3559
|
+
type: "parse_error",
|
|
3560
|
+
message: `MILESTONES.md could not be parsed: ${err.message}`,
|
|
3561
|
+
path: ".planning/MILESTONES.md",
|
|
3562
|
+
fixable: false
|
|
3563
|
+
});
|
|
3564
|
+
}
|
|
3565
|
+
}
|
|
3566
|
+
const configPath = join(planningDir, "config.json");
|
|
3567
|
+
if (!existsSync(configPath)) {
|
|
3568
|
+
issues.push({
|
|
3569
|
+
type: "missing_file",
|
|
3570
|
+
message: "config.json is missing",
|
|
3571
|
+
path: ".planning/config.json",
|
|
3572
|
+
fixable: false
|
|
3573
|
+
});
|
|
3574
|
+
} else {
|
|
3575
|
+
try {
|
|
3576
|
+
JSON.parse(readFileSync(configPath, "utf-8"));
|
|
3577
|
+
} catch (err) {
|
|
3578
|
+
issues.push({
|
|
3579
|
+
type: "parse_error",
|
|
3580
|
+
message: `config.json could not be parsed: ${err.message}`,
|
|
3581
|
+
path: ".planning/config.json",
|
|
3582
|
+
fixable: false
|
|
3583
|
+
});
|
|
3584
|
+
}
|
|
3585
|
+
}
|
|
3586
|
+
if (milestones.length > 0) {
|
|
3587
|
+
for (const milestone of milestones) {
|
|
3588
|
+
const folder = findMilestoneFolder(planningDir, milestone.id);
|
|
3589
|
+
if (!folder) {
|
|
3590
|
+
issues.push({
|
|
3591
|
+
type: "missing_folder",
|
|
3592
|
+
message: `Milestone ${milestone.id} ("${milestone.title}") has no folder in .planning/milestones/`,
|
|
3593
|
+
path: `.planning/milestones/${milestone.id}-*`,
|
|
3594
|
+
fixable: true,
|
|
3595
|
+
// Store data needed for repair
|
|
3596
|
+
_milestoneId: milestone.id,
|
|
3597
|
+
_milestoneTitle: milestone.title
|
|
3598
|
+
});
|
|
3599
|
+
}
|
|
3600
|
+
}
|
|
3601
|
+
}
|
|
3602
|
+
if (existsSync(milestonesDir)) {
|
|
3603
|
+
let entries;
|
|
3604
|
+
try {
|
|
3605
|
+
entries = readdirSync(milestonesDir, { withFileTypes: true });
|
|
3606
|
+
} catch {
|
|
3607
|
+
entries = [];
|
|
3608
|
+
}
|
|
3609
|
+
const referencedIds = new Set(milestones.map((m) => m.id));
|
|
3610
|
+
const milestoneIdPattern = /^(M-\d+)/;
|
|
3611
|
+
for (const entry of entries) {
|
|
3612
|
+
if (!entry.isDirectory()) continue;
|
|
3613
|
+
if (entry.name.startsWith("_")) continue;
|
|
3614
|
+
const match = entry.name.match(milestoneIdPattern);
|
|
3615
|
+
if (!match) continue;
|
|
3616
|
+
const folderId = match[1];
|
|
3617
|
+
if (!referencedIds.has(folderId)) {
|
|
3618
|
+
issues.push({
|
|
3619
|
+
type: "orphaned_folder",
|
|
3620
|
+
message: `Folder "${entry.name}" has no corresponding milestone in MILESTONES.md`,
|
|
3621
|
+
path: `.planning/milestones/${entry.name}`,
|
|
3622
|
+
fixable: false
|
|
3623
|
+
});
|
|
3624
|
+
}
|
|
3625
|
+
}
|
|
3626
|
+
}
|
|
3627
|
+
return {
|
|
3628
|
+
healthy: issues.length === 0,
|
|
3629
|
+
issues
|
|
3630
|
+
};
|
|
3631
|
+
}
|
|
3632
|
+
function runHealthCheckRepair2(cwd) {
|
|
3633
|
+
const planningDir = join(cwd, ".planning");
|
|
3634
|
+
const result = runHealthCheck2(cwd);
|
|
3635
|
+
if (result.error) return result;
|
|
3636
|
+
const repaired = [];
|
|
3637
|
+
for (const issue of result.issues) {
|
|
3638
|
+
if (!issue.fixable) continue;
|
|
3639
|
+
if (issue.type === "missing_folder" && issue._milestoneId && issue._milestoneTitle) {
|
|
3640
|
+
try {
|
|
3641
|
+
ensureMilestoneFolder(planningDir, issue._milestoneId, issue._milestoneTitle);
|
|
3642
|
+
repaired.push(`Created folder for ${issue._milestoneId}: ${issue._milestoneTitle}`);
|
|
3643
|
+
} catch (err) {
|
|
3644
|
+
}
|
|
3645
|
+
}
|
|
3646
|
+
}
|
|
3647
|
+
const recheck = runHealthCheck2(cwd);
|
|
3648
|
+
if (recheck.error) return recheck;
|
|
3649
|
+
return {
|
|
3650
|
+
healthy: recheck.healthy,
|
|
3651
|
+
issues: recheck.issues,
|
|
3652
|
+
repaired
|
|
3653
|
+
};
|
|
3654
|
+
}
|
|
3655
|
+
module2.exports = { runHealthCheck: runHealthCheck2, runHealthCheckRepair: runHealthCheckRepair2 };
|
|
3656
|
+
}
|
|
3657
|
+
});
|
|
3658
|
+
|
|
3659
|
+
// src/server/index.js
|
|
3660
|
+
var require_server = __commonJS({
|
|
3661
|
+
"src/server/index.js"(exports2, module2) {
|
|
3662
|
+
"use strict";
|
|
3663
|
+
var http = require("node:http");
|
|
3664
|
+
var fs = require("node:fs");
|
|
3665
|
+
var path = require("node:path");
|
|
3666
|
+
var { runLoadGraph: runLoadGraph2 } = require_load_graph();
|
|
3667
|
+
var { runStatus: runStatus2 } = require_status();
|
|
3668
|
+
var MIME_TYPES = {
|
|
3669
|
+
".html": "text/html; charset=utf-8",
|
|
3670
|
+
".js": "application/javascript; charset=utf-8",
|
|
3671
|
+
".css": "text/css; charset=utf-8",
|
|
3672
|
+
".json": "application/json; charset=utf-8",
|
|
3673
|
+
".svg": "image/svg+xml",
|
|
3674
|
+
".png": "image/png",
|
|
3675
|
+
".ico": "image/x-icon"
|
|
3676
|
+
};
|
|
3677
|
+
var PUBLIC_DIR_RELATIVE = path.join("src", "server", "public");
|
|
3678
|
+
function getPublicDir(cwd) {
|
|
3679
|
+
return path.join(cwd, PUBLIC_DIR_RELATIVE);
|
|
3680
|
+
}
|
|
3681
|
+
function sendJson(res, statusCode, data) {
|
|
3682
|
+
const body = JSON.stringify(data, null, 2);
|
|
3683
|
+
res.writeHead(statusCode, {
|
|
3684
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
3685
|
+
"Content-Length": Buffer.byteLength(body),
|
|
3686
|
+
"Access-Control-Allow-Origin": "*",
|
|
3687
|
+
"Access-Control-Allow-Methods": "GET, OPTIONS",
|
|
3688
|
+
"Access-Control-Allow-Headers": "Content-Type"
|
|
3689
|
+
});
|
|
3690
|
+
res.end(body);
|
|
3691
|
+
}
|
|
3692
|
+
function sendFile(res, filePath) {
|
|
3693
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
3694
|
+
const contentType = MIME_TYPES[ext] || "application/octet-stream";
|
|
3695
|
+
fs.readFile(filePath, (err, data) => {
|
|
3696
|
+
if (err) {
|
|
3697
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
3698
|
+
res.end("Not Found");
|
|
3699
|
+
return;
|
|
3700
|
+
}
|
|
3701
|
+
res.writeHead(200, {
|
|
3702
|
+
"Content-Type": contentType,
|
|
3703
|
+
"Content-Length": data.length
|
|
3704
|
+
});
|
|
3705
|
+
res.end(data);
|
|
3706
|
+
});
|
|
3707
|
+
}
|
|
3708
|
+
function handleGraph(res, cwd) {
|
|
3709
|
+
try {
|
|
3710
|
+
const graph = runLoadGraph2(cwd);
|
|
3711
|
+
if ("error" in graph) {
|
|
3712
|
+
sendJson(res, 500, { error: graph.error });
|
|
3713
|
+
return;
|
|
3714
|
+
}
|
|
3715
|
+
sendJson(res, 200, graph);
|
|
3716
|
+
} catch (err) {
|
|
3717
|
+
sendJson(res, 500, { error: String(err) });
|
|
3718
|
+
}
|
|
3719
|
+
}
|
|
3720
|
+
function handleStatus(res, cwd) {
|
|
3721
|
+
try {
|
|
3722
|
+
const status = runStatus2(cwd);
|
|
3723
|
+
if ("error" in status) {
|
|
3724
|
+
sendJson(res, 500, { error: status.error });
|
|
3725
|
+
return;
|
|
3726
|
+
}
|
|
3727
|
+
sendJson(res, 200, status);
|
|
3728
|
+
} catch (err) {
|
|
3729
|
+
sendJson(res, 500, { error: String(err) });
|
|
3730
|
+
}
|
|
3731
|
+
}
|
|
3732
|
+
function handleMilestone(res, cwd, milestoneId) {
|
|
3733
|
+
try {
|
|
3734
|
+
const graph = runLoadGraph2(cwd);
|
|
3735
|
+
if ("error" in graph) {
|
|
3736
|
+
sendJson(res, 500, { error: graph.error });
|
|
3737
|
+
return;
|
|
3738
|
+
}
|
|
3739
|
+
const normalizedId = milestoneId.toUpperCase();
|
|
3740
|
+
const milestone = graph.milestones.find(
|
|
3741
|
+
(m) => m.id.toUpperCase() === normalizedId
|
|
3742
|
+
);
|
|
3743
|
+
if (!milestone) {
|
|
3744
|
+
sendJson(res, 404, { error: `Milestone '${milestoneId}' not found` });
|
|
3745
|
+
return;
|
|
3746
|
+
}
|
|
3747
|
+
const milestoneActions = graph.actions.filter((a) => {
|
|
3748
|
+
if (Array.isArray(a.causes)) {
|
|
3749
|
+
return a.causes.some((c) => c.toUpperCase() === normalizedId);
|
|
3750
|
+
}
|
|
3751
|
+
return false;
|
|
3752
|
+
});
|
|
3753
|
+
sendJson(res, 200, {
|
|
3754
|
+
milestone,
|
|
3755
|
+
actions: milestoneActions
|
|
3756
|
+
});
|
|
3757
|
+
} catch (err) {
|
|
3758
|
+
sendJson(res, 500, { error: String(err) });
|
|
3759
|
+
}
|
|
3760
|
+
}
|
|
3761
|
+
function route(req, res, cwd) {
|
|
3762
|
+
const method = req.method || "GET";
|
|
3763
|
+
const url = req.url || "/";
|
|
3764
|
+
const urlPath = url.split("?")[0];
|
|
3765
|
+
if (method === "OPTIONS") {
|
|
3766
|
+
res.writeHead(204, {
|
|
3767
|
+
"Access-Control-Allow-Origin": "*",
|
|
3768
|
+
"Access-Control-Allow-Methods": "GET, OPTIONS",
|
|
3769
|
+
"Access-Control-Allow-Headers": "Content-Type"
|
|
3770
|
+
});
|
|
3771
|
+
res.end();
|
|
3772
|
+
return;
|
|
3773
|
+
}
|
|
3774
|
+
if (method !== "GET") {
|
|
3775
|
+
sendJson(res, 405, { error: "Method Not Allowed" });
|
|
3776
|
+
return;
|
|
3777
|
+
}
|
|
3778
|
+
if (urlPath === "/api/graph") {
|
|
3779
|
+
handleGraph(res, cwd);
|
|
3780
|
+
return;
|
|
3781
|
+
}
|
|
3782
|
+
if (urlPath === "/api/status") {
|
|
3783
|
+
handleStatus(res, cwd);
|
|
3784
|
+
return;
|
|
3785
|
+
}
|
|
3786
|
+
const milestoneMatch = urlPath.match(/^\/api\/milestone\/([^/]+)$/);
|
|
3787
|
+
if (milestoneMatch) {
|
|
3788
|
+
handleMilestone(res, cwd, milestoneMatch[1]);
|
|
3789
|
+
return;
|
|
3790
|
+
}
|
|
3791
|
+
const publicDir = getPublicDir(cwd);
|
|
3792
|
+
if (urlPath === "/") {
|
|
3793
|
+
const indexPath = path.join(publicDir, "index.html");
|
|
3794
|
+
sendFile(res, indexPath);
|
|
3795
|
+
return;
|
|
3796
|
+
}
|
|
3797
|
+
if (urlPath.startsWith("/public/")) {
|
|
3798
|
+
const relative = urlPath.replace(/^\/public\//, "");
|
|
3799
|
+
const resolved = path.resolve(publicDir, relative);
|
|
3800
|
+
if (!resolved.startsWith(publicDir + path.sep) && resolved !== publicDir) {
|
|
3801
|
+
sendJson(res, 403, { error: "Forbidden" });
|
|
3802
|
+
return;
|
|
3803
|
+
}
|
|
3804
|
+
sendFile(res, resolved);
|
|
3805
|
+
return;
|
|
3806
|
+
}
|
|
3807
|
+
sendJson(res, 404, { error: `Route not found: ${urlPath}` });
|
|
3808
|
+
}
|
|
3809
|
+
function createServer(cwd, port) {
|
|
3810
|
+
const server = http.createServer((req, res) => {
|
|
3811
|
+
route(req, res, cwd);
|
|
3812
|
+
});
|
|
3813
|
+
return server;
|
|
3814
|
+
}
|
|
3815
|
+
function startServer(cwd, port) {
|
|
3816
|
+
const resolvedPort = port || parseInt(process.env.PORT || "", 10) || 3847;
|
|
3817
|
+
const server = createServer(cwd, resolvedPort);
|
|
3818
|
+
server.listen(resolvedPort, "127.0.0.1", () => {
|
|
3819
|
+
});
|
|
3820
|
+
const url = `http://localhost:${resolvedPort}`;
|
|
3821
|
+
return { server, port: resolvedPort, url };
|
|
3822
|
+
}
|
|
3823
|
+
module2.exports = { createServer, startServer };
|
|
3824
|
+
}
|
|
3825
|
+
});
|
|
3826
|
+
|
|
3827
|
+
// src/commands/serve.js
|
|
3828
|
+
var require_serve = __commonJS({
|
|
3829
|
+
"src/commands/serve.js"(exports2, module2) {
|
|
3830
|
+
"use strict";
|
|
3831
|
+
var { startServer } = require_server();
|
|
3832
|
+
function parsePortFlag(args) {
|
|
3833
|
+
const idx = args.indexOf("--port");
|
|
3834
|
+
if (idx === -1 || idx + 1 >= args.length) return void 0;
|
|
3835
|
+
const value = parseInt(args[idx + 1], 10);
|
|
3836
|
+
return Number.isNaN(value) ? void 0 : value;
|
|
3837
|
+
}
|
|
3838
|
+
function runServe2(cwd, args) {
|
|
3839
|
+
const port = parsePortFlag(args) || parseInt(process.env.PORT || "", 10) || 3847;
|
|
3840
|
+
const { server, port: resolvedPort, url } = startServer(cwd, port);
|
|
3841
|
+
process.on("SIGINT", () => {
|
|
3842
|
+
server.close(() => process.exit(0));
|
|
3843
|
+
});
|
|
3844
|
+
process.on("SIGTERM", () => {
|
|
3845
|
+
server.close(() => process.exit(0));
|
|
3846
|
+
});
|
|
3847
|
+
return { url, port: resolvedPort, pid: process.pid };
|
|
3848
|
+
}
|
|
3849
|
+
module2.exports = { runServe: runServe2 };
|
|
3850
|
+
}
|
|
3851
|
+
});
|
|
3852
|
+
|
|
2730
3853
|
// src/declare-tools.js
|
|
2731
3854
|
var { commitPlanningDocs } = require_commit();
|
|
3855
|
+
var { readState, recordSession } = require_state();
|
|
2732
3856
|
var { runInit } = require_init();
|
|
2733
3857
|
var { runStatus } = require_status();
|
|
2734
3858
|
var { runHelp } = require_help();
|
|
@@ -2750,6 +3874,14 @@ var { runCheckDrift } = require_check_drift();
|
|
|
2750
3874
|
var { runCheckOccurrence } = require_check_occurrence();
|
|
2751
3875
|
var { runComputePerformance } = require_compute_performance();
|
|
2752
3876
|
var { runRenegotiate } = require_renegotiate();
|
|
3877
|
+
var { runCompleteMilestone } = require_complete_milestone();
|
|
3878
|
+
var { runSyncStatus } = require_sync_status();
|
|
3879
|
+
var { runQuickTask } = require_quick_task();
|
|
3880
|
+
var { runAddTodo, runCheckTodos, runCompleteTodo } = require_todo();
|
|
3881
|
+
var { runConfigGet } = require_config_get();
|
|
3882
|
+
var { runConfigSet } = require_config_set();
|
|
3883
|
+
var { runHealthCheck, runHealthCheckRepair } = require_health_check();
|
|
3884
|
+
var { runServe } = require_serve();
|
|
2753
3885
|
function parseCwdFlag(argv) {
|
|
2754
3886
|
const idx = argv.indexOf("--cwd");
|
|
2755
3887
|
if (idx === -1 || idx + 1 >= argv.length) return null;
|
|
@@ -2783,11 +3915,16 @@ function parseFilesFlag(argv) {
|
|
|
2783
3915
|
}
|
|
2784
3916
|
return files;
|
|
2785
3917
|
}
|
|
3918
|
+
function parseNamedFlag(argv, flag) {
|
|
3919
|
+
const idx = argv.indexOf(flag);
|
|
3920
|
+
if (idx === -1 || idx + 1 >= argv.length) return null;
|
|
3921
|
+
return argv[idx + 1];
|
|
3922
|
+
}
|
|
2786
3923
|
function main() {
|
|
2787
3924
|
const args = process.argv.slice(2);
|
|
2788
3925
|
const command = args[0];
|
|
2789
3926
|
if (!command) {
|
|
2790
|
-
console.log(JSON.stringify({ error: "No command specified. Use: commit, init, status, add-declaration, add-milestone, add-milestones, create-plan, load-graph, trace, prioritize, visualize, compute-waves, generate-exec-plan, verify-wave, verify-milestone, execute, check-drift, check-occurrence, compute-performance, renegotiate, help" }));
|
|
3927
|
+
console.log(JSON.stringify({ error: "No command specified. Use: commit, init, status, add-declaration, add-milestone, add-milestones, create-plan, load-graph, trace, prioritize, visualize, compute-waves, generate-exec-plan, verify-wave, verify-milestone, execute, check-drift, check-occurrence, compute-performance, renegotiate, complete-milestone, sync-status, serve, record-session, get-state, quick-task, add-todo, check-todos, complete-todo, config-get, config-set, health-check, help" }));
|
|
2791
3928
|
process.exit(1);
|
|
2792
3929
|
}
|
|
2793
3930
|
try {
|
|
@@ -2908,6 +4045,13 @@ function main() {
|
|
|
2908
4045
|
if (result.error) process.exit(1);
|
|
2909
4046
|
break;
|
|
2910
4047
|
}
|
|
4048
|
+
case "sync-status": {
|
|
4049
|
+
const cwdSync = parseCwdFlag(args) || process.cwd();
|
|
4050
|
+
const result = runSyncStatus(cwdSync);
|
|
4051
|
+
console.log(JSON.stringify(result));
|
|
4052
|
+
if (result.error) process.exit(1);
|
|
4053
|
+
break;
|
|
4054
|
+
}
|
|
2911
4055
|
case "execute": {
|
|
2912
4056
|
const cwdExecute = parseCwdFlag(args) || process.cwd();
|
|
2913
4057
|
const result = runExecute(cwdExecute, args.slice(1));
|
|
@@ -2950,8 +4094,95 @@ function main() {
|
|
|
2950
4094
|
if (result.error) process.exit(1);
|
|
2951
4095
|
break;
|
|
2952
4096
|
}
|
|
4097
|
+
case "complete-milestone": {
|
|
4098
|
+
const cwdCompMs = parseCwdFlag(args) || process.cwd();
|
|
4099
|
+
const result = runCompleteMilestone(cwdCompMs, args.slice(1));
|
|
4100
|
+
console.log(JSON.stringify(result));
|
|
4101
|
+
if (result.error) process.exit(1);
|
|
4102
|
+
break;
|
|
4103
|
+
}
|
|
4104
|
+
case "serve": {
|
|
4105
|
+
const cwdServe = parseCwdFlag(args) || process.cwd();
|
|
4106
|
+
const result = runServe(cwdServe, args.slice(1));
|
|
4107
|
+
console.log(JSON.stringify(result));
|
|
4108
|
+
break;
|
|
4109
|
+
}
|
|
4110
|
+
case "record-session": {
|
|
4111
|
+
const cwdRecordSession = parseCwdFlag(args) || process.cwd();
|
|
4112
|
+
const stoppedAt = parseNamedFlag(args, "--stopped-at");
|
|
4113
|
+
const resumeFile = parseNamedFlag(args, "--resume-file");
|
|
4114
|
+
if (!stoppedAt) {
|
|
4115
|
+
console.log(JSON.stringify({ error: "record-session requires --stopped-at argument" }));
|
|
4116
|
+
process.exit(1);
|
|
4117
|
+
}
|
|
4118
|
+
const result = recordSession(cwdRecordSession, stoppedAt, resumeFile || void 0);
|
|
4119
|
+
console.log(JSON.stringify(result));
|
|
4120
|
+
if (!result.ok) process.exit(1);
|
|
4121
|
+
break;
|
|
4122
|
+
}
|
|
4123
|
+
case "get-state": {
|
|
4124
|
+
const cwdGetState = parseCwdFlag(args) || process.cwd();
|
|
4125
|
+
const result = readState(cwdGetState);
|
|
4126
|
+
if (result === null) {
|
|
4127
|
+
console.log(JSON.stringify({ error: "STATE.md not found. Run /declare:new-project to initialize." }));
|
|
4128
|
+
process.exit(1);
|
|
4129
|
+
}
|
|
4130
|
+
console.log(JSON.stringify(result));
|
|
4131
|
+
break;
|
|
4132
|
+
}
|
|
4133
|
+
case "quick-task": {
|
|
4134
|
+
const cwdQuickTask = parseCwdFlag(args) || process.cwd();
|
|
4135
|
+
const result = runQuickTask(cwdQuickTask, args.slice(1));
|
|
4136
|
+
console.log(JSON.stringify(result));
|
|
4137
|
+
if (result.error) process.exit(1);
|
|
4138
|
+
break;
|
|
4139
|
+
}
|
|
4140
|
+
case "add-todo": {
|
|
4141
|
+
const cwdAddTodo = parseCwdFlag(args) || process.cwd();
|
|
4142
|
+
const result = runAddTodo(cwdAddTodo, args.slice(1));
|
|
4143
|
+
console.log(JSON.stringify(result));
|
|
4144
|
+
if (result.error) process.exit(1);
|
|
4145
|
+
break;
|
|
4146
|
+
}
|
|
4147
|
+
case "check-todos": {
|
|
4148
|
+
const cwdCheckTodos = parseCwdFlag(args) || process.cwd();
|
|
4149
|
+
const result = runCheckTodos(cwdCheckTodos);
|
|
4150
|
+
console.log(JSON.stringify(result));
|
|
4151
|
+
if (result.error) process.exit(1);
|
|
4152
|
+
break;
|
|
4153
|
+
}
|
|
4154
|
+
case "complete-todo": {
|
|
4155
|
+
const cwdCompleteTodo = parseCwdFlag(args) || process.cwd();
|
|
4156
|
+
const result = runCompleteTodo(cwdCompleteTodo, args.slice(1));
|
|
4157
|
+
console.log(JSON.stringify(result));
|
|
4158
|
+
if (result.error) process.exit(1);
|
|
4159
|
+
break;
|
|
4160
|
+
}
|
|
4161
|
+
case "config-get": {
|
|
4162
|
+
const cwdConfigGet = parseCwdFlag(args) || process.cwd();
|
|
4163
|
+
const configGetArgs = parsePositionalArgs(args.slice(1));
|
|
4164
|
+
const result = runConfigGet(cwdConfigGet, configGetArgs);
|
|
4165
|
+
console.log(JSON.stringify(result));
|
|
4166
|
+
if (result.error) process.exit(1);
|
|
4167
|
+
break;
|
|
4168
|
+
}
|
|
4169
|
+
case "config-set": {
|
|
4170
|
+
const cwdConfigSet = parseCwdFlag(args) || process.cwd();
|
|
4171
|
+
const result = runConfigSet(cwdConfigSet, args.slice(1));
|
|
4172
|
+
console.log(JSON.stringify(result));
|
|
4173
|
+
if (result.error) process.exit(1);
|
|
4174
|
+
break;
|
|
4175
|
+
}
|
|
4176
|
+
case "health-check": {
|
|
4177
|
+
const cwdHealthCheck = parseCwdFlag(args) || process.cwd();
|
|
4178
|
+
const repair = args.includes("--repair");
|
|
4179
|
+
const result = repair ? runHealthCheckRepair(cwdHealthCheck) : runHealthCheck(cwdHealthCheck);
|
|
4180
|
+
console.log(JSON.stringify(result));
|
|
4181
|
+
if (result.error) process.exit(1);
|
|
4182
|
+
break;
|
|
4183
|
+
}
|
|
2953
4184
|
default:
|
|
2954
|
-
console.log(JSON.stringify({ error: `Unknown command: ${command}. Use: commit, init, status, add-declaration, add-milestone, add-milestones, create-plan, load-graph, trace, prioritize, visualize, compute-waves, generate-exec-plan, verify-wave, verify-milestone, execute, check-drift, check-occurrence, compute-performance, renegotiate, help` }));
|
|
4185
|
+
console.log(JSON.stringify({ error: `Unknown command: ${command}. Use: commit, init, status, add-declaration, add-milestone, add-milestones, create-plan, load-graph, trace, prioritize, visualize, compute-waves, generate-exec-plan, verify-wave, verify-milestone, execute, check-drift, check-occurrence, compute-performance, renegotiate, complete-milestone, sync-status, serve, record-session, get-state, quick-task, add-todo, check-todos, complete-todo, config-get, config-set, health-check, help` }));
|
|
2955
4186
|
process.exit(1);
|
|
2956
4187
|
}
|
|
2957
4188
|
} catch (err) {
|