declare-cc 0.4.3 → 0.4.8

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/bin/install.js CHANGED
@@ -1465,8 +1465,9 @@ function install(isGlobal, runtime = 'claude') {
1465
1465
  console.log(` ${green}✓${reset} Installed workflows/`);
1466
1466
  }
1467
1467
 
1468
- // Copy dashboard static files (src/server/public/) → .claude/server/public/
1469
- const publicSrc = path.join(src, 'src', 'server', 'public');
1468
+ // Copy dashboard static files (dist/public/) → .claude/server/public/
1469
+ // dist/public/ ships in the npm package (built by esbuild.config.js)
1470
+ const publicSrc = path.join(src, 'dist', 'public');
1470
1471
  if (fs.existsSync(publicSrc)) {
1471
1472
  const publicDest = path.join(targetDir, 'server', 'public');
1472
1473
  fs.mkdirSync(publicDest, { recursive: true });
@@ -1563,6 +1564,9 @@ function install(isGlobal, runtime = 'claude') {
1563
1564
  const updateCheckCommand = isGlobal
1564
1565
  ? buildHookCommand(targetDir, 'declare-check-update.js')
1565
1566
  : 'node ' + dirName + '/hooks/declare-check-update.js';
1567
+ const activityCommand = isGlobal
1568
+ ? buildHookCommand(targetDir, 'declare-activity.js')
1569
+ : 'node ' + dirName + '/hooks/declare-activity.js';
1566
1570
 
1567
1571
  // Enable experimental agents for Gemini CLI (required for custom sub-agents)
1568
1572
  if (isGemini) {
@@ -1599,6 +1603,21 @@ function install(isGlobal, runtime = 'claude') {
1599
1603
  });
1600
1604
  console.log(` ${green}✓${reset} Configured update check hook`);
1601
1605
  }
1606
+
1607
+ // Configure PreToolUse + PostToolUse hooks for activity feed (Claude Code only)
1608
+ if (!isOpencode && !isGemini) {
1609
+ if (!settings.hooks.PreToolUse) settings.hooks.PreToolUse = [];
1610
+ if (!settings.hooks.PostToolUse) settings.hooks.PostToolUse = [];
1611
+
1612
+ const hasActivityPre = settings.hooks.PreToolUse.some(e =>
1613
+ e.hooks && e.hooks.some(h => h.command && h.command.includes('declare-activity'))
1614
+ );
1615
+ if (!hasActivityPre) {
1616
+ settings.hooks.PreToolUse.push({ hooks: [{ type: 'command', command: activityCommand }] });
1617
+ settings.hooks.PostToolUse.push({ hooks: [{ type: 'command', command: activityCommand }] });
1618
+ console.log(` ${green}✓${reset} Configured activity feed hooks`);
1619
+ }
1620
+ }
1602
1621
  }
1603
1622
 
1604
1623
  // Write file manifest for future modification detection
@@ -95,11 +95,16 @@ After all milestones processed:
95
95
  node dist/declare-tools.cjs load-graph
96
96
  ```
97
97
 
98
- 2. Show summary: milestones processed, plans created, total actions derived.
99
- 3. Suggest the next step clearly:
98
+ 2. Start the dashboard if not already running:
99
+ ```bash
100
+ curl -sf http://localhost:3847/api/graph -o /dev/null || (node dist/declare-tools.cjs serve --port 3847 > /tmp/declare-dashboard.log 2>&1 & sleep 1 && open http://localhost:3847 2>/dev/null || true)
101
+ ```
102
+
103
+ 3. Show summary: milestones processed, plans created, total actions derived.
104
+ 4. Suggest the next step clearly:
100
105
 
101
106
  ```
102
- Actions defined. Next: create executable plans.
107
+ Actions and edges are live in the dashboard → http://localhost:3847
103
108
 
104
109
  /declare:plan M-XX — research + planner + checker loop → EXEC-PLAN files
105
110
  /declare:execute M-XX — once plans exist, execute with wave scheduling
@@ -188,25 +188,28 @@ If `--confirm` flag was set, pause after successful verification:
188
188
  - "Wave N complete and verified. Proceed to Wave N+1? (yes/no)"
189
189
  - Wait for user confirmation before continuing.
190
190
 
191
- **3e. Update action statuses in PLAN.md:**
191
+ **3e. Propagate statuses after each wave:**
192
192
 
193
- After successful wave verification, update each completed action's status in the milestone's PLAN.md file:
193
+ After successful wave verification, run sync-status to update PLAN.md, MILESTONES.md, and FUTURE.md atomically. This keeps the dashboard live as waves complete:
194
194
 
195
- 1. Use the `milestoneFolderPath` from Step 2 to locate the PLAN.md file.
196
- 2. Read the PLAN.md file.
197
- 3. For each action in the completed wave, find `**Status:** PENDING` (or `**Status:** ACTIVE`) for that action and change it to `**Status:** DONE`.
198
- 4. Write the updated PLAN.md back.
195
+ ```bash
196
+ node dist/declare-tools.cjs sync-status
197
+ ```
198
+
199
+ Do not manually edit PLAN.md or MILESTONES.md — sync-status handles all of it correctly.
200
+
201
+ **Step 4: After all waves complete, propagate statuses.**
199
202
 
200
- **Step 4: After all waves complete, check milestone completion.**
203
+ Run sync-status to propagate action milestone → declaration completion automatically:
204
+
205
+ ```bash
206
+ node dist/declare-tools.cjs sync-status
207
+ ```
201
208
 
202
- If `milestoneCompletable` is true from the final verify-wave result:
203
- 1. Read `.planning/MILESTONES.md`.
204
- 2. Find the row for M-XX in the milestones table.
205
- 3. Change its Status from PENDING or ACTIVE to DONE.
206
- 4. Write the updated MILESTONES.md back.
207
- 5. Display: "Milestone M-XX marked as DONE (pending verification)."
209
+ Parse the result. It will show which actions, milestones, and declarations were marked DONE.
210
+ Display: "Status propagated — [summary from sync-status result]."
208
211
 
209
- Proceed to Step 5. Do NOT display a completion banner yet -- that happens in Step 8 after verification.
212
+ Proceed to Step 5. Do NOT display a completion banner yet that happens in Step 8 after verification.
210
213
 
211
214
  **Step 5: Milestone truth verification.**
212
215
 
@@ -44,9 +44,29 @@ node dist/declare-tools.cjs add-declaration --title "Short Title" --statement "F
44
44
 
45
45
  Parse the JSON output to confirm the declaration was created and note its assigned ID (e.g., D-01).
46
46
 
47
- **Step 5: Show summary and suggest next step.**
47
+ **Step 5: Launch dashboard and show summary.**
48
48
 
49
49
  After all declarations are captured:
50
50
 
51
- 1. List all declarations with their IDs and statements.
52
- 2. Suggest: "Run `/declare:milestones` to work backward from these declarations to milestones and actions."
51
+ 1. Start the dashboard server (if not already running):
52
+
53
+ ```bash
54
+ node dist/declare-tools.cjs serve --port 3847 > /tmp/declare-dashboard.log 2>&1 &
55
+ sleep 1 && curl -sf http://localhost:3847/api/graph -o /dev/null && echo "RUNNING" || echo "FAILED"
56
+ ```
57
+
58
+ If RUNNING, open it:
59
+ ```bash
60
+ open http://localhost:3847 2>/dev/null || true
61
+ ```
62
+
63
+ 2. List all declarations with their IDs and statements.
64
+
65
+ 3. Suggest next step:
66
+
67
+ ```
68
+ Your declarations are live in the dashboard → http://localhost:3847
69
+ The graph updates every 5 seconds as you add milestones and actions.
70
+
71
+ Run /declare:milestones to work backward from these declarations.
72
+ ```
@@ -77,5 +77,10 @@ After all declarations processed:
77
77
  node dist/declare-tools.cjs load-graph
78
78
  ```
79
79
 
80
- 2. Show summary: declarations processed, milestones derived.
81
- 3. Suggest: "Run `/declare:actions` to derive action plans for each milestone."
80
+ 2. Start the dashboard if not already running (dashboard updates live when files change):
81
+ ```bash
82
+ curl -sf http://localhost:3847/api/graph -o /dev/null || (node dist/declare-tools.cjs serve --port 3847 > /tmp/declare-dashboard.log 2>&1 & sleep 1 && open http://localhost:3847 2>/dev/null || true)
83
+ ```
84
+
85
+ 3. Show summary: declarations processed, milestones derived.
86
+ 4. Suggest: "Milestones are live in the dashboard → http://localhost:3847 — Run `/declare:actions` to derive action plans."
@@ -1329,7 +1329,7 @@ var require_help = __commonJS({
1329
1329
  usage: "/declare:help"
1330
1330
  }
1331
1331
  ],
1332
- version: "0.4.0"
1332
+ version: "0.4.7"
1333
1333
  };
1334
1334
  }
1335
1335
  module2.exports = { runHelp: runHelp2 };
@@ -3096,6 +3096,167 @@ var require_sync_status = __commonJS({
3096
3096
  }
3097
3097
  });
3098
3098
 
3099
+ // src/commands/get-exec-plan.js
3100
+ var require_get_exec_plan = __commonJS({
3101
+ "src/commands/get-exec-plan.js"(exports2, module2) {
3102
+ "use strict";
3103
+ var { existsSync, readFileSync, readdirSync } = require("node:fs");
3104
+ var { join } = require("node:path");
3105
+ var { parseFlag } = require_parse_args();
3106
+ var { buildDagFromDisk } = require_build_dag();
3107
+ var { findMilestoneFolder } = require_milestone_folders();
3108
+ function parseFrontmatter(fmText) {
3109
+ const result = {};
3110
+ const lines = fmText.split("\n");
3111
+ let i = 0;
3112
+ while (i < lines.length) {
3113
+ const line = lines[i];
3114
+ const keyMatch = line.match(/^(\w[\w_]*):\s*(.*)/);
3115
+ if (!keyMatch) {
3116
+ i++;
3117
+ continue;
3118
+ }
3119
+ const [, key, rest] = keyMatch;
3120
+ if (rest.trim() === "") {
3121
+ const children = [];
3122
+ i++;
3123
+ while (i < lines.length && (lines[i].startsWith(" ") || lines[i].trim() === "")) {
3124
+ const child = lines[i];
3125
+ const listItem = child.match(/^\s+-\s+(.*)/);
3126
+ const nestedKey = child.match(/^\s+(\w[\w_]*):\s*(.*)/);
3127
+ if (listItem) {
3128
+ children.push({ type: "item", value: listItem[1].replace(/^["']|["']$/g, "") });
3129
+ } else if (nestedKey) {
3130
+ children.push({ type: "key", key: nestedKey[1], value: nestedKey[2] });
3131
+ }
3132
+ i++;
3133
+ }
3134
+ if (children.length > 0 && children[0].type === "item") {
3135
+ result[key] = children.map((c) => c.value);
3136
+ } else if (children.length > 0 && children[0].type === "key") {
3137
+ result[key] = {};
3138
+ let subKey = null;
3139
+ let subItems = [];
3140
+ for (const c of children) {
3141
+ if (c.type === "key") {
3142
+ if (subKey) result[key][subKey] = subItems;
3143
+ subKey = c.key;
3144
+ subItems = c.value.trim() ? [c.value.replace(/^["']|["']$/g, "")] : [];
3145
+ } else if (c.type === "item") {
3146
+ subItems.push(c.value);
3147
+ }
3148
+ }
3149
+ if (subKey) result[key][subKey] = subItems;
3150
+ }
3151
+ } else {
3152
+ result[key] = rest.trim().replace(/^["']|["']$/g, "");
3153
+ i++;
3154
+ }
3155
+ }
3156
+ return result;
3157
+ }
3158
+ function extractTag(content, tag) {
3159
+ const re = new RegExp(`<${tag}[^>]*>([\\s\\S]*?)<\\/${tag}>`, "i");
3160
+ const m = content.match(re);
3161
+ return m ? m[1].trim() : null;
3162
+ }
3163
+ function parseTasks(tasksContent) {
3164
+ const tasks = [];
3165
+ const taskRe = /<task([^>]*)>([\s\S]*?)<\/task>/gi;
3166
+ let m;
3167
+ while ((m = taskRe.exec(tasksContent)) !== null) {
3168
+ const attrs = m[1];
3169
+ const body = m[2];
3170
+ const typeMatch = attrs.match(/type="([^"]+)"/);
3171
+ const taskType = typeMatch ? typeMatch[1] : "auto";
3172
+ tasks.push({
3173
+ type: taskType,
3174
+ name: extractTag(body, "name") || "",
3175
+ action: extractTag(body, "action"),
3176
+ verify: extractTag(body, "verify"),
3177
+ done: extractTag(body, "done"),
3178
+ howToVerify: extractTag(body, "how-to-verify"),
3179
+ resumeSignal: extractTag(body, "resume-signal"),
3180
+ whatBuilt: extractTag(body, "what-built")
3181
+ });
3182
+ }
3183
+ return tasks;
3184
+ }
3185
+ function findExecPlan(milestoneFolder, actionId) {
3186
+ if (!existsSync(milestoneFolder)) return null;
3187
+ const entries = readdirSync(milestoneFolder);
3188
+ const match = entries.find(
3189
+ (f) => f.toUpperCase().startsWith(actionId.toUpperCase() + "-EXEC-PLAN") || f.toUpperCase().startsWith("EXEC-PLAN-" + actionId.replace(/^A-/, ""))
3190
+ );
3191
+ return match ? join(milestoneFolder, match) : null;
3192
+ }
3193
+ function runGetExecPlan2(cwd, args) {
3194
+ const actionId = parseFlag(args, "action");
3195
+ if (!actionId) {
3196
+ return { error: "Missing --action flag. Usage: get-exec-plan --action A-XX" };
3197
+ }
3198
+ const graphResult = buildDagFromDisk(cwd);
3199
+ if ("error" in graphResult) return graphResult;
3200
+ const { dag } = graphResult;
3201
+ const action = dag.getNode(actionId);
3202
+ if (!action) return { error: `Action not found: ${actionId}` };
3203
+ const upstreamMilestones = dag.getUpstream(actionId).filter((n) => n.type === "milestone");
3204
+ if (upstreamMilestones.length === 0) return { error: `No milestone found for action ${actionId}` };
3205
+ const milestone = upstreamMilestones[0];
3206
+ const planningDir = join(cwd, ".planning");
3207
+ const milestoneFolder = findMilestoneFolder(planningDir, milestone.id);
3208
+ if (!milestoneFolder) {
3209
+ return { error: `Milestone folder not found for ${milestone.id}` };
3210
+ }
3211
+ const execPlanPath = findExecPlan(milestoneFolder, actionId);
3212
+ if (!execPlanPath) {
3213
+ return {
3214
+ actionId,
3215
+ actionTitle: action.title,
3216
+ status: action.status,
3217
+ milestoneId: milestone.id,
3218
+ milestoneTitle: milestone.title,
3219
+ execPlan: null,
3220
+ summaryExists: false
3221
+ };
3222
+ }
3223
+ const raw = readFileSync(execPlanPath, "utf-8");
3224
+ const fmMatch = raw.match(/^---\n([\s\S]*?)\n---/);
3225
+ const frontmatter = fmMatch ? parseFrontmatter(fmMatch[1]) : {};
3226
+ const objective = extractTag(raw, "objective");
3227
+ const tasksRaw = extractTag(raw, "tasks");
3228
+ const tasks = tasksRaw ? parseTasks(tasksRaw) : [];
3229
+ const successCriteria = extractTag(raw, "success_criteria");
3230
+ const verification = extractTag(raw, "verification");
3231
+ const summaryPath = join(milestoneFolder, `${actionId}-SUMMARY.md`);
3232
+ const summaryExists = existsSync(summaryPath);
3233
+ const summaryContent = summaryExists ? readFileSync(summaryPath, "utf-8") : null;
3234
+ return {
3235
+ actionId,
3236
+ actionTitle: action.title,
3237
+ status: action.status,
3238
+ milestoneId: milestone.id,
3239
+ milestoneTitle: milestone.title,
3240
+ execPlan: {
3241
+ wave: frontmatter.wave ? Number(frontmatter.wave) : null,
3242
+ autonomous: frontmatter.autonomous === "true" || frontmatter.autonomous === true,
3243
+ dependsOn: Array.isArray(frontmatter.depends_on) ? frontmatter.depends_on : [],
3244
+ filesModified: Array.isArray(frontmatter.files_modified) ? frontmatter.files_modified : [],
3245
+ declarations: Array.isArray(frontmatter.declarations) ? frontmatter.declarations : [],
3246
+ mustHaves: frontmatter.must_haves || null,
3247
+ objective,
3248
+ tasks,
3249
+ successCriteria,
3250
+ verification
3251
+ },
3252
+ summaryExists,
3253
+ summaryContent
3254
+ };
3255
+ }
3256
+ module2.exports = { runGetExecPlan: runGetExecPlan2 };
3257
+ }
3258
+ });
3259
+
3099
3260
  // src/commands/quick-task.js
3100
3261
  var require_quick_task = __commonJS({
3101
3262
  "src/commands/quick-task.js"(exports2, module2) {
@@ -3666,6 +3827,7 @@ var require_server = __commonJS({
3666
3827
  var path = require("node:path");
3667
3828
  var { runLoadGraph: runLoadGraph2 } = require_load_graph();
3668
3829
  var { runStatus: runStatus2 } = require_status();
3830
+ var { runGetExecPlan: runGetExecPlan2 } = require_get_exec_plan();
3669
3831
  var MIME_TYPES = {
3670
3832
  ".html": "text/html; charset=utf-8",
3671
3833
  ".js": "application/javascript; charset=utf-8",
@@ -3677,7 +3839,9 @@ var require_server = __commonJS({
3677
3839
  };
3678
3840
  function getPublicDir(cwd) {
3679
3841
  const installed = path.join(cwd, ".claude", "server", "public");
3680
- if (require("fs").existsSync(installed)) return installed;
3842
+ if (fs.existsSync(installed)) return installed;
3843
+ const bundled = path.join(__dirname, "public");
3844
+ if (fs.existsSync(bundled)) return bundled;
3681
3845
  return path.join(cwd, "src", "server", "public");
3682
3846
  }
3683
3847
  function sendJson(res, statusCode, data) {
@@ -3760,6 +3924,67 @@ var require_server = __commonJS({
3760
3924
  sendJson(res, 500, { error: String(err) });
3761
3925
  }
3762
3926
  }
3927
+ function handleActivity(res, cwd) {
3928
+ const activityFile = path.join(cwd, ".planning", "activity.jsonl");
3929
+ if (!fs.existsSync(activityFile)) {
3930
+ sendJson(res, 200, { events: [] });
3931
+ return;
3932
+ }
3933
+ try {
3934
+ const lines = fs.readFileSync(activityFile, "utf-8").split("\n").filter(Boolean).slice(-100);
3935
+ const events = lines.map((l) => {
3936
+ try {
3937
+ return JSON.parse(l);
3938
+ } catch {
3939
+ return null;
3940
+ }
3941
+ }).filter(Boolean).reverse();
3942
+ sendJson(res, 200, { events });
3943
+ } catch (err) {
3944
+ sendJson(res, 500, { error: String(err) });
3945
+ }
3946
+ }
3947
+ var sseClients = /* @__PURE__ */ new Set();
3948
+ function broadcastChange() {
3949
+ for (const client of sseClients) {
3950
+ try {
3951
+ client.write("event: change\ndata: {}\n\n");
3952
+ } catch (_) {
3953
+ sseClients.delete(client);
3954
+ }
3955
+ }
3956
+ }
3957
+ function watchPlanning(cwd) {
3958
+ const planningDir = path.join(cwd, ".planning");
3959
+ if (!fs.existsSync(planningDir)) return;
3960
+ let graphTimer = null;
3961
+ let activityTimer = null;
3962
+ const activityFile = path.join(planningDir, "activity.jsonl");
3963
+ try {
3964
+ fs.watch(planningDir, { recursive: true }, (_evt, filename) => {
3965
+ if (filename && filename.endsWith("activity.jsonl")) {
3966
+ if (activityTimer) clearTimeout(activityTimer);
3967
+ activityTimer = setTimeout(() => {
3968
+ for (const client of sseClients) {
3969
+ try {
3970
+ client.write("event: activity\ndata: {}\n\n");
3971
+ } catch {
3972
+ sseClients.delete(client);
3973
+ }
3974
+ }
3975
+ activityTimer = null;
3976
+ }, 50);
3977
+ } else {
3978
+ if (graphTimer) clearTimeout(graphTimer);
3979
+ graphTimer = setTimeout(() => {
3980
+ broadcastChange();
3981
+ graphTimer = null;
3982
+ }, 200);
3983
+ }
3984
+ });
3985
+ } catch (_) {
3986
+ }
3987
+ }
3763
3988
  function route(req, res, cwd) {
3764
3989
  const method = req.method || "GET";
3765
3990
  const url = req.url || "/";
@@ -3777,6 +4002,18 @@ var require_server = __commonJS({
3777
4002
  sendJson(res, 405, { error: "Method Not Allowed" });
3778
4003
  return;
3779
4004
  }
4005
+ if (urlPath === "/events") {
4006
+ res.writeHead(200, {
4007
+ "Content-Type": "text/event-stream",
4008
+ "Cache-Control": "no-cache",
4009
+ "Connection": "keep-alive",
4010
+ "Access-Control-Allow-Origin": "*"
4011
+ });
4012
+ res.write("retry: 3000\n\n");
4013
+ sseClients.add(res);
4014
+ req.on("close", () => sseClients.delete(res));
4015
+ return;
4016
+ }
3780
4017
  if (urlPath === "/api/graph") {
3781
4018
  handleGraph(res, cwd);
3782
4019
  return;
@@ -3790,6 +4027,16 @@ var require_server = __commonJS({
3790
4027
  handleMilestone(res, cwd, milestoneMatch[1]);
3791
4028
  return;
3792
4029
  }
4030
+ if (urlPath === "/api/activity") {
4031
+ handleActivity(res, cwd);
4032
+ return;
4033
+ }
4034
+ const actionMatch = urlPath.match(/^\/api\/action\/([^/]+)$/);
4035
+ if (actionMatch) {
4036
+ const result = runGetExecPlan2(cwd, ["--action", actionMatch[1]]);
4037
+ sendJson(res, result.error ? 404 : 200, result);
4038
+ return;
4039
+ }
3793
4040
  const publicDir = getPublicDir(cwd);
3794
4041
  if (urlPath === "/") {
3795
4042
  const indexPath = path.join(publicDir, "index.html");
@@ -3818,6 +4065,7 @@ var require_server = __commonJS({
3818
4065
  const resolvedPort = port || parseInt(process.env.PORT || "", 10) || 3847;
3819
4066
  const server = createServer(cwd, resolvedPort);
3820
4067
  server.listen(resolvedPort, "127.0.0.1", () => {
4068
+ watchPlanning(cwd);
3821
4069
  });
3822
4070
  const url = `http://localhost:${resolvedPort}`;
3823
4071
  return { server, port: resolvedPort, url };
@@ -3878,6 +4126,7 @@ var { runComputePerformance } = require_compute_performance();
3878
4126
  var { runRenegotiate } = require_renegotiate();
3879
4127
  var { runCompleteMilestone } = require_complete_milestone();
3880
4128
  var { runSyncStatus } = require_sync_status();
4129
+ var { runGetExecPlan } = require_get_exec_plan();
3881
4130
  var { runQuickTask } = require_quick_task();
3882
4131
  var { runAddTodo, runCheckTodos, runCompleteTodo } = require_todo();
3883
4132
  var { runConfigGet } = require_config_get();
@@ -4054,6 +4303,13 @@ function main() {
4054
4303
  if (result.error) process.exit(1);
4055
4304
  break;
4056
4305
  }
4306
+ case "get-exec-plan": {
4307
+ const cwdGep = parseCwdFlag(args) || process.cwd();
4308
+ const result = runGetExecPlan(cwdGep, args.slice(1));
4309
+ console.log(JSON.stringify(result));
4310
+ if (result.error) process.exit(1);
4311
+ break;
4312
+ }
4057
4313
  case "execute": {
4058
4314
  const cwdExecute = parseCwdFlag(args) || process.cwd();
4059
4315
  const result = runExecute(cwdExecute, args.slice(1));