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.
Files changed (64) hide show
  1. package/README.md +126 -27
  2. package/agents/declare-codebase-mapper.md +761 -0
  3. package/agents/declare-debugger.md +1198 -0
  4. package/agents/declare-plan-checker.md +608 -0
  5. package/agents/declare-planner.md +1015 -0
  6. package/agents/declare-research-synthesizer.md +309 -0
  7. package/agents/declare-researcher.md +484 -0
  8. package/bin/install.js +33 -38
  9. package/commands/declare/add-todo.md +41 -0
  10. package/commands/declare/audit.md +76 -0
  11. package/commands/declare/check-todos.md +125 -0
  12. package/commands/declare/complete-milestone.md +215 -0
  13. package/commands/declare/dashboard.md +76 -0
  14. package/commands/{gsd → declare}/debug.md +11 -11
  15. package/commands/declare/discuss.md +65 -0
  16. package/commands/declare/health.md +92 -0
  17. package/commands/declare/map-codebase.md +149 -0
  18. package/commands/declare/new-milestone.md +172 -0
  19. package/commands/declare/new-project.md +565 -0
  20. package/commands/declare/pause.md +138 -0
  21. package/commands/declare/plan.md +236 -0
  22. package/commands/declare/progress.md +116 -0
  23. package/commands/declare/quick.md +119 -0
  24. package/commands/declare/reapply-patches.md +178 -0
  25. package/commands/declare/research.md +267 -0
  26. package/commands/declare/resume.md +146 -0
  27. package/commands/declare/set-profile.md +66 -0
  28. package/commands/declare/settings.md +119 -0
  29. package/commands/declare/update.md +251 -0
  30. package/commands/declare/verify.md +64 -0
  31. package/dist/declare-tools.cjs +1234 -3
  32. package/package.json +1 -1
  33. package/workflows/discuss.md +476 -0
  34. package/workflows/verify.md +504 -0
  35. package/commands/gsd/add-phase.md +0 -39
  36. package/commands/gsd/add-todo.md +0 -42
  37. package/commands/gsd/audit-milestone.md +0 -42
  38. package/commands/gsd/check-todos.md +0 -41
  39. package/commands/gsd/cleanup.md +0 -18
  40. package/commands/gsd/complete-milestone.md +0 -136
  41. package/commands/gsd/discuss-phase.md +0 -87
  42. package/commands/gsd/execute-phase.md +0 -42
  43. package/commands/gsd/health.md +0 -22
  44. package/commands/gsd/help.md +0 -22
  45. package/commands/gsd/insert-phase.md +0 -33
  46. package/commands/gsd/join-discord.md +0 -18
  47. package/commands/gsd/list-phase-assumptions.md +0 -50
  48. package/commands/gsd/map-codebase.md +0 -71
  49. package/commands/gsd/new-milestone.md +0 -51
  50. package/commands/gsd/new-project.md +0 -42
  51. package/commands/gsd/new-project.md.bak +0 -1041
  52. package/commands/gsd/pause-work.md +0 -35
  53. package/commands/gsd/plan-milestone-gaps.md +0 -40
  54. package/commands/gsd/plan-phase.md +0 -44
  55. package/commands/gsd/progress.md +0 -24
  56. package/commands/gsd/quick.md +0 -40
  57. package/commands/gsd/reapply-patches.md +0 -110
  58. package/commands/gsd/remove-phase.md +0 -32
  59. package/commands/gsd/research-phase.md +0 -187
  60. package/commands/gsd/resume-work.md +0 -40
  61. package/commands/gsd/set-profile.md +0 -34
  62. package/commands/gsd/settings.md +0 -36
  63. package/commands/gsd/update.md +0 -37
  64. package/commands/gsd/verify-work.md +0 -39
@@ -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
- module2.exports = { parsePlanFile, writePlanFile };
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) {