declare-cc 0.4.8 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/install.js +11 -6
- package/dist/declare-tools.cjs +8 -1
- package/dist/public/app.js +113 -13
- package/dist/public/index.html +44 -0
- package/hooks/declare-activity.js +106 -0
- package/hooks/declare-check-update.js +62 -0
- package/hooks/declare-statusline.js +91 -0
- package/package.json +2 -1
package/bin/install.js
CHANGED
|
@@ -1636,11 +1636,13 @@ function install(isGlobal, runtime = 'claude') {
|
|
|
1636
1636
|
function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallStatusline, runtime = 'claude', isGlobal = true) {
|
|
1637
1637
|
const isOpencode = runtime === 'opencode';
|
|
1638
1638
|
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1639
|
+
// Statusline is a global UI element — only configure it for global installs.
|
|
1640
|
+
// Local installs must not write statusLine: it would point to a project-specific
|
|
1641
|
+
// path that breaks when the project moves or is deleted.
|
|
1642
|
+
// Users who only do local installs should run `npx declare-cc --claude --global`
|
|
1643
|
+
// once to get the statusline, or configure it manually.
|
|
1644
|
+
if (shouldInstallStatusline && !isOpencode && isGlobal) {
|
|
1645
|
+
settings.statusLine = { type: 'command', command: statuslineCommand };
|
|
1644
1646
|
console.log(` ${green}✓${reset} Configured statusline`);
|
|
1645
1647
|
}
|
|
1646
1648
|
|
|
@@ -1657,9 +1659,12 @@ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallS
|
|
|
1657
1659
|
if (runtime === 'gemini') program = 'Gemini';
|
|
1658
1660
|
|
|
1659
1661
|
const command = isOpencode ? '/declare-help' : '/declare:help';
|
|
1662
|
+
const statuslineNote = (!isGlobal && !isOpencode)
|
|
1663
|
+
? `\n ${yellow}Tip:${reset} For the context-window statusline, run once globally:\n ${dim}npx declare-cc --claude --global${reset}\n`
|
|
1664
|
+
: '';
|
|
1660
1665
|
console.log(`
|
|
1661
1666
|
${green}Done!${reset} Launch ${program} and run ${cyan}${command}${reset}.
|
|
1662
|
-
|
|
1667
|
+
${statuslineNote}
|
|
1663
1668
|
${cyan}Docs & source:${reset} https://github.com/decocms/declare-cc
|
|
1664
1669
|
`);
|
|
1665
1670
|
}
|
package/dist/declare-tools.cjs
CHANGED
|
@@ -1329,7 +1329,7 @@ var require_help = __commonJS({
|
|
|
1329
1329
|
usage: "/declare:help"
|
|
1330
1330
|
}
|
|
1331
1331
|
],
|
|
1332
|
-
version: "0.
|
|
1332
|
+
version: "0.5.1"
|
|
1333
1333
|
};
|
|
1334
1334
|
}
|
|
1335
1335
|
module2.exports = { runHelp: runHelp2 };
|
|
@@ -2987,6 +2987,13 @@ var require_sync_status = __commonJS({
|
|
|
2987
2987
|
actionResults.push({ id: action.id, milestone: m.id, changed: false, reason: "already DONE" });
|
|
2988
2988
|
continue;
|
|
2989
2989
|
}
|
|
2990
|
+
const summaryPath = join(folderPath, `${action.id}-SUMMARY.md`);
|
|
2991
|
+
if (existsSync(summaryPath)) {
|
|
2992
|
+
planContent = updateActionStatus(planContent, action.id, "DONE");
|
|
2993
|
+
planDirty = true;
|
|
2994
|
+
actionResults.push({ id: action.id, milestone: m.id, changed: true, reason: "SUMMARY.md exists" });
|
|
2995
|
+
continue;
|
|
2996
|
+
}
|
|
2990
2997
|
if (milestoneAlreadyDone) {
|
|
2991
2998
|
planContent = updateActionStatus(planContent, action.id, "DONE");
|
|
2992
2999
|
planDirty = true;
|
package/dist/public/app.js
CHANGED
|
@@ -203,24 +203,103 @@ function renderStatusBar() {
|
|
|
203
203
|
|
|
204
204
|
// ─── Node element builder ─────────────────────────────────────────────────────
|
|
205
205
|
|
|
206
|
+
const COMPLETED = new Set(['DONE','KEPT','HONORED']);
|
|
207
|
+
const IN_PROGRESS_STORED = new Set(['ACTIVE']);
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Compute derived workflow status for a milestone from its action statuses.
|
|
211
|
+
* This overrides the stored MILESTONES.md status so the dashboard always
|
|
212
|
+
* reflects reality even if sync-status hasn't been called.
|
|
213
|
+
*
|
|
214
|
+
* @param {{ id: string, status: string, hasPlan: boolean }} milestone
|
|
215
|
+
* @param {Array<{ id: string, status: string, causes: string[] }>} allActions
|
|
216
|
+
* @returns {{ displayStatus: string, doneCount: number, totalCount: number }}
|
|
217
|
+
*/
|
|
218
|
+
function deriveMilestoneStatus(milestone, allActions) {
|
|
219
|
+
// Authoritative integrity/terminal states — always trust these
|
|
220
|
+
if (['KEPT','HONORED','BROKEN','RENEGOTIATED'].includes(milestone.status)) {
|
|
221
|
+
const myActions = allActions.filter(a => (a.causes||[]).includes(milestone.id));
|
|
222
|
+
return { displayStatus: milestone.status, doneCount: myActions.filter(a=>COMPLETED.has(a.status)).length, totalCount: myActions.length };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const myActions = allActions.filter(a => (a.causes||[]).includes(milestone.id));
|
|
226
|
+
const doneCount = myActions.filter(a => COMPLETED.has(a.status)).length;
|
|
227
|
+
const totalCount = myActions.length;
|
|
228
|
+
|
|
229
|
+
let displayStatus;
|
|
230
|
+
if (totalCount === 0) {
|
|
231
|
+
displayStatus = milestone.hasPlan ? 'PLANNED' : 'PENDING';
|
|
232
|
+
} else if (doneCount === totalCount) {
|
|
233
|
+
displayStatus = 'DONE';
|
|
234
|
+
} else if (doneCount > 0) {
|
|
235
|
+
displayStatus = 'EXECUTING';
|
|
236
|
+
} else {
|
|
237
|
+
displayStatus = 'PLANNED';
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return { displayStatus, doneCount, totalCount };
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Compute derived workflow status for a declaration from its milestone statuses.
|
|
245
|
+
* @param {{ id: string, status: string, milestones: string[] }} declaration
|
|
246
|
+
* @param {Array<{ id: string, displayStatus: string }>} enrichedMilestones
|
|
247
|
+
* @returns {string}
|
|
248
|
+
*/
|
|
249
|
+
function deriveDeclarationStatus(declaration, enrichedMilestones) {
|
|
250
|
+
if (['KEPT','HONORED','BROKEN','RENEGOTIATED'].includes(declaration.status)) return declaration.status;
|
|
251
|
+
|
|
252
|
+
const myMilestones = enrichedMilestones.filter(m => (declaration.milestones||[]).includes(m.id));
|
|
253
|
+
if (myMilestones.length === 0) return 'PENDING';
|
|
254
|
+
|
|
255
|
+
const doneCount = myMilestones.filter(m => COMPLETED.has(m.displayStatus)).length;
|
|
256
|
+
const executingCount = myMilestones.filter(m => m.displayStatus === 'EXECUTING').length;
|
|
257
|
+
const plannedCount = myMilestones.filter(m => m.displayStatus === 'PLANNED').length;
|
|
258
|
+
|
|
259
|
+
if (doneCount === myMilestones.length) return 'DONE';
|
|
260
|
+
if (executingCount > 0 || doneCount > 0) return 'EXECUTING';
|
|
261
|
+
if (plannedCount > 0) return 'PLANNED';
|
|
262
|
+
return 'PENDING';
|
|
263
|
+
}
|
|
264
|
+
|
|
206
265
|
/**
|
|
207
266
|
* Build a node DOM element.
|
|
208
|
-
* @param {
|
|
267
|
+
* @param {object} item
|
|
209
268
|
* @param {'declaration'|'milestone'|'action'} type
|
|
269
|
+
* @param {{ displayStatus?: string, doneCount?: number, totalCount?: number }} [derived]
|
|
210
270
|
* @returns {HTMLElement}
|
|
211
271
|
*/
|
|
212
|
-
function buildNodeEl(item, type) {
|
|
272
|
+
function buildNodeEl(item, type, derived = {}) {
|
|
273
|
+
const displayStatus = derived.displayStatus || item.status || 'PENDING';
|
|
213
274
|
const el = document.createElement('div');
|
|
214
|
-
el.className = `node node-${type} status-${statusClass(
|
|
215
|
-
el.dataset.nodeId
|
|
275
|
+
el.className = `node node-${type} status-${statusClass(displayStatus)}`;
|
|
276
|
+
el.dataset.nodeId = item.id;
|
|
216
277
|
el.dataset.nodeType = type;
|
|
217
278
|
|
|
218
279
|
const title = item.title || item.statement || item.id;
|
|
219
280
|
|
|
281
|
+
// Progress bar for milestones with actions
|
|
282
|
+
let progressHtml = '';
|
|
283
|
+
if (type === 'milestone' && derived.totalCount > 0) {
|
|
284
|
+
const pct = Math.round((derived.doneCount / derived.totalCount) * 100);
|
|
285
|
+
const countLabel = `${derived.doneCount}/${derived.totalCount}`;
|
|
286
|
+
progressHtml = `
|
|
287
|
+
<div class="node-progress" title="${countLabel} actions done">
|
|
288
|
+
<div class="node-progress-fill" style="width:${pct}%"></div>
|
|
289
|
+
</div>`;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Badge label — show progress count for executing milestones
|
|
293
|
+
let badgeLabel = displayStatus;
|
|
294
|
+
if (type === 'milestone' && displayStatus === 'EXECUTING' && derived.totalCount > 0) {
|
|
295
|
+
badgeLabel = `${derived.doneCount}/${derived.totalCount} DONE`;
|
|
296
|
+
}
|
|
297
|
+
|
|
220
298
|
el.innerHTML = `
|
|
221
299
|
<div class="node-id">${item.id}</div>
|
|
222
300
|
<div class="node-title">${truncate(title, 55)}</div>
|
|
223
|
-
<span class="status-badge">${
|
|
301
|
+
<span class="status-badge">${badgeLabel}</span>
|
|
302
|
+
${progressHtml}
|
|
224
303
|
`;
|
|
225
304
|
|
|
226
305
|
el.addEventListener('click', () => selectNode(item.id, type));
|
|
@@ -234,22 +313,37 @@ function renderGraph() {
|
|
|
234
313
|
|
|
235
314
|
const { declarations, milestones, actions } = graphData;
|
|
236
315
|
|
|
316
|
+
// ── Compute derived statuses from action data (always reflects reality) ──────
|
|
317
|
+
// Milestones
|
|
318
|
+
const enrichedMilestones = (milestones || []).map(m => ({
|
|
319
|
+
...m,
|
|
320
|
+
...deriveMilestoneStatus(m, actions || []),
|
|
321
|
+
}));
|
|
322
|
+
|
|
323
|
+
// Declarations
|
|
324
|
+
const enrichedDeclarations = (declarations || []).map(d => ({
|
|
325
|
+
...d,
|
|
326
|
+
displayStatus: deriveDeclarationStatus(d, enrichedMilestones),
|
|
327
|
+
}));
|
|
328
|
+
|
|
237
329
|
// Clear containers
|
|
238
330
|
$nodesDecls.innerHTML = '';
|
|
239
331
|
$nodesMiles.innerHTML = '';
|
|
240
332
|
$nodesActs.innerHTML = '';
|
|
241
333
|
|
|
242
|
-
// Render
|
|
243
|
-
|
|
244
|
-
$nodesDecls.appendChild(buildNodeEl(d, 'declaration'));
|
|
334
|
+
// Render
|
|
335
|
+
enrichedDeclarations.forEach(d => {
|
|
336
|
+
$nodesDecls.appendChild(buildNodeEl(d, 'declaration', { displayStatus: d.displayStatus }));
|
|
245
337
|
});
|
|
246
338
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
339
|
+
enrichedMilestones.forEach(m => {
|
|
340
|
+
$nodesMiles.appendChild(buildNodeEl(m, 'milestone', {
|
|
341
|
+
displayStatus: m.displayStatus,
|
|
342
|
+
doneCount: m.doneCount,
|
|
343
|
+
totalCount: m.totalCount,
|
|
344
|
+
}));
|
|
250
345
|
});
|
|
251
346
|
|
|
252
|
-
// Render actions
|
|
253
347
|
(actions || []).forEach(a => {
|
|
254
348
|
$nodesActs.appendChild(buildNodeEl(a, 'action'));
|
|
255
349
|
});
|
|
@@ -1230,7 +1324,13 @@ const COMPLETED_STATES = new Set(['DONE', 'KEPT', 'HONORED']);
|
|
|
1230
1324
|
function checkProjectComplete(graph) {
|
|
1231
1325
|
if (confettiFired) return;
|
|
1232
1326
|
if (!graph || !graph.declarations || graph.declarations.length === 0) return;
|
|
1233
|
-
|
|
1327
|
+
// Use derived statuses (computed from actions) not stored MILESTONES.md status
|
|
1328
|
+
const enriched = (graph.milestones || []).map(m => ({
|
|
1329
|
+
...m, ...deriveMilestoneStatus(m, graph.actions || []),
|
|
1330
|
+
}));
|
|
1331
|
+
const allDone = graph.declarations.every(d =>
|
|
1332
|
+
COMPLETED_STATES.has(deriveDeclarationStatus(d, enriched))
|
|
1333
|
+
);
|
|
1234
1334
|
if (!allDone) return;
|
|
1235
1335
|
confettiFired = true;
|
|
1236
1336
|
fireConfetti();
|
package/dist/public/index.html
CHANGED
|
@@ -41,6 +41,15 @@
|
|
|
41
41
|
--act-done-bg: #08180f;
|
|
42
42
|
--act-done-border: #123428;
|
|
43
43
|
|
|
44
|
+
/* workflow progress tones */
|
|
45
|
+
--planned-color: #5ba3ff;
|
|
46
|
+
--planned-bg: #091828;
|
|
47
|
+
--planned-border: #12305a;
|
|
48
|
+
|
|
49
|
+
--executing-color: #fbbf24;
|
|
50
|
+
--executing-bg: #1a1200;
|
|
51
|
+
--executing-border:#3d2c00;
|
|
52
|
+
|
|
44
53
|
--broken-color: #ff4d6d;
|
|
45
54
|
--broken-bg: #2a0a10;
|
|
46
55
|
--broken-border: #5a1520;
|
|
@@ -264,6 +273,41 @@
|
|
|
264
273
|
opacity: 0.82;
|
|
265
274
|
}
|
|
266
275
|
|
|
276
|
+
/* Workflow progress states — computed from action data, not MILESTONES.md */
|
|
277
|
+
.node.status-planned {
|
|
278
|
+
background: var(--planned-bg);
|
|
279
|
+
border-color: var(--planned-border);
|
|
280
|
+
color: var(--planned-color);
|
|
281
|
+
opacity: 0.9;
|
|
282
|
+
}
|
|
283
|
+
.node.status-executing {
|
|
284
|
+
background: var(--executing-bg);
|
|
285
|
+
border-color: var(--executing-border);
|
|
286
|
+
color: var(--executing-color);
|
|
287
|
+
box-shadow: 0 0 0 1px var(--executing-border), 0 0 12px rgba(251,191,36,0.15);
|
|
288
|
+
}
|
|
289
|
+
.node.status-planned .node-title { color: var(--planned-color); }
|
|
290
|
+
.node.status-executing .node-title { color: var(--executing-color); }
|
|
291
|
+
|
|
292
|
+
/* Progress bar inside milestone node */
|
|
293
|
+
.node-progress {
|
|
294
|
+
margin-top: 7px;
|
|
295
|
+
height: 3px;
|
|
296
|
+
background: rgba(255,255,255,0.08);
|
|
297
|
+
border-radius: 2px;
|
|
298
|
+
overflow: hidden;
|
|
299
|
+
}
|
|
300
|
+
.node-progress-fill {
|
|
301
|
+
height: 100%;
|
|
302
|
+
border-radius: 2px;
|
|
303
|
+
transition: width 0.4s ease;
|
|
304
|
+
}
|
|
305
|
+
.status-executing .node-progress-fill { background: var(--executing-color); }
|
|
306
|
+
.status-planned .node-progress-fill { background: var(--planned-color); opacity:0.3; }
|
|
307
|
+
.status-done .node-progress-fill,
|
|
308
|
+
.status-kept .node-progress-fill,
|
|
309
|
+
.status-honored .node-progress-fill { background: var(--act-done-color); }
|
|
310
|
+
|
|
267
311
|
.node.status-broken {
|
|
268
312
|
background: var(--broken-bg);
|
|
269
313
|
border-color: var(--broken-border);
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Declare activity hook — PreToolUse + PostToolUse
|
|
3
|
+
// Writes interesting tool events to .planning/activity.jsonl
|
|
4
|
+
// Server watches .planning/ via fs.watch and pushes SSE events to dashboard.
|
|
5
|
+
//
|
|
6
|
+
// Installed for PreToolUse and PostToolUse hook events.
|
|
7
|
+
// Runs fast: read stdin → decide → append one line → exit.
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const os = require('os');
|
|
14
|
+
|
|
15
|
+
const cwd = process.cwd();
|
|
16
|
+
const planningDir = path.join(cwd, '.planning');
|
|
17
|
+
const activityFile = path.join(planningDir, 'activity.jsonl');
|
|
18
|
+
|
|
19
|
+
// Only write if .planning/ exists (i.e. this is a Declare project)
|
|
20
|
+
if (!fs.existsSync(planningDir)) process.exit(0);
|
|
21
|
+
|
|
22
|
+
let raw = '';
|
|
23
|
+
process.stdin.setEncoding('utf8');
|
|
24
|
+
process.stdin.on('data', c => raw += c);
|
|
25
|
+
process.stdin.on('end', () => {
|
|
26
|
+
try {
|
|
27
|
+
const data = JSON.parse(raw);
|
|
28
|
+
const event = buildEvent(data);
|
|
29
|
+
if (!event) process.exit(0);
|
|
30
|
+
|
|
31
|
+
// Ensure file exists
|
|
32
|
+
if (!fs.existsSync(activityFile)) fs.writeFileSync(activityFile, '');
|
|
33
|
+
|
|
34
|
+
// Append event + trim to last 200 lines
|
|
35
|
+
const line = JSON.stringify(event) + '\n';
|
|
36
|
+
fs.appendFileSync(activityFile, line);
|
|
37
|
+
|
|
38
|
+
// Trim to last 200 lines to avoid unbounded growth
|
|
39
|
+
const content = fs.readFileSync(activityFile, 'utf8');
|
|
40
|
+
const lines = content.split('\n').filter(Boolean);
|
|
41
|
+
if (lines.length > 200) {
|
|
42
|
+
fs.writeFileSync(activityFile, lines.slice(-200).join('\n') + '\n');
|
|
43
|
+
}
|
|
44
|
+
} catch (_) {
|
|
45
|
+
// Silent fail — never block Claude
|
|
46
|
+
}
|
|
47
|
+
process.exit(0);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Build an activity event from a hook payload, or return null to skip.
|
|
52
|
+
* @param {any} data
|
|
53
|
+
* @returns {object|null}
|
|
54
|
+
*/
|
|
55
|
+
function buildEvent(data) {
|
|
56
|
+
const tool = data.tool_name || '';
|
|
57
|
+
const input = data.tool_input || {};
|
|
58
|
+
const response = data.tool_response;
|
|
59
|
+
const hookEvent = data.hook_event_name || ''; // PreToolUse or PostToolUse
|
|
60
|
+
const ts = Date.now();
|
|
61
|
+
const phase = hookEvent === 'PostToolUse' ? 'done' : 'start';
|
|
62
|
+
|
|
63
|
+
// Task spawns — most important for agent visibility
|
|
64
|
+
if (tool === 'Task') {
|
|
65
|
+
return {
|
|
66
|
+
ts, phase, tool: 'Task',
|
|
67
|
+
desc: input.description || '',
|
|
68
|
+
agent: input.subagent_type || '',
|
|
69
|
+
// truncate prompt to avoid massive payloads
|
|
70
|
+
prompt: (input.prompt || '').slice(0, 200),
|
|
71
|
+
bg: hookEvent === 'PostToolUse',
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Bash commands that involve declare-tools (execution steps)
|
|
76
|
+
if (tool === 'Bash') {
|
|
77
|
+
const cmd = input.command || '';
|
|
78
|
+
if (cmd.includes('declare-tools') || cmd.includes('/declare:')) {
|
|
79
|
+
return { ts, phase, tool: 'Bash', cmd: cmd.slice(0, 200) };
|
|
80
|
+
}
|
|
81
|
+
return null; // skip noisy general bash
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Write tool — track planning file changes + auto-sync on SUMMARY.md writes
|
|
85
|
+
if (tool === 'Write' && hookEvent === 'PostToolUse') {
|
|
86
|
+
const fp = input.file_path || '';
|
|
87
|
+
if (fp.includes('.planning/')) {
|
|
88
|
+
// When an executor writes A-XX-SUMMARY.md, auto-run sync-status so
|
|
89
|
+
// PLAN.md and MILESTONES.md update immediately without manual intervention
|
|
90
|
+
if (fp.includes('-SUMMARY.md')) {
|
|
91
|
+
const { execSync } = require('child_process');
|
|
92
|
+
try {
|
|
93
|
+
execSync(`node "${path.join(cwd, '.claude', 'declare-tools.cjs')}" sync-status`, {
|
|
94
|
+
cwd,
|
|
95
|
+
timeout: 10000,
|
|
96
|
+
stdio: 'ignore',
|
|
97
|
+
});
|
|
98
|
+
} catch (_) { /* silent — never block Claude */ }
|
|
99
|
+
}
|
|
100
|
+
return { ts, phase: 'done', tool: 'Write', file: fp.replace(cwd, '.') };
|
|
101
|
+
}
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Check for Declare updates in background, write result to cache
|
|
3
|
+
// Called by SessionStart hook - runs once per session
|
|
4
|
+
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const os = require('os');
|
|
8
|
+
const { spawn } = require('child_process');
|
|
9
|
+
|
|
10
|
+
const homeDir = os.homedir();
|
|
11
|
+
const cwd = process.cwd();
|
|
12
|
+
const cacheDir = path.join(homeDir, '.claude', 'cache');
|
|
13
|
+
const cacheFile = path.join(cacheDir, 'declare-update-check.json');
|
|
14
|
+
|
|
15
|
+
// VERSION file locations (check project first, then global)
|
|
16
|
+
const projectVersionFile = path.join(cwd, '.claude', 'declare', 'VERSION');
|
|
17
|
+
const globalVersionFile = path.join(homeDir, '.claude', 'declare', 'VERSION');
|
|
18
|
+
|
|
19
|
+
// Ensure cache directory exists
|
|
20
|
+
if (!fs.existsSync(cacheDir)) {
|
|
21
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Run check in background (spawn background process, windowsHide prevents console flash)
|
|
25
|
+
const child = spawn(process.execPath, ['-e', `
|
|
26
|
+
const fs = require('fs');
|
|
27
|
+
const { execSync } = require('child_process');
|
|
28
|
+
|
|
29
|
+
const cacheFile = ${JSON.stringify(cacheFile)};
|
|
30
|
+
const projectVersionFile = ${JSON.stringify(projectVersionFile)};
|
|
31
|
+
const globalVersionFile = ${JSON.stringify(globalVersionFile)};
|
|
32
|
+
|
|
33
|
+
// Check project directory first (local install), then global
|
|
34
|
+
let installed = '0.0.0';
|
|
35
|
+
try {
|
|
36
|
+
if (fs.existsSync(projectVersionFile)) {
|
|
37
|
+
installed = fs.readFileSync(projectVersionFile, 'utf8').trim();
|
|
38
|
+
} else if (fs.existsSync(globalVersionFile)) {
|
|
39
|
+
installed = fs.readFileSync(globalVersionFile, 'utf8').trim();
|
|
40
|
+
}
|
|
41
|
+
} catch (e) {}
|
|
42
|
+
|
|
43
|
+
let latest = null;
|
|
44
|
+
try {
|
|
45
|
+
latest = execSync('npm view declare-cc version', { encoding: 'utf8', timeout: 10000, windowsHide: true }).trim();
|
|
46
|
+
} catch (e) {}
|
|
47
|
+
|
|
48
|
+
const result = {
|
|
49
|
+
update_available: latest && installed !== latest,
|
|
50
|
+
installed,
|
|
51
|
+
latest: latest || 'unknown',
|
|
52
|
+
checked: Math.floor(Date.now() / 1000)
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
fs.writeFileSync(cacheFile, JSON.stringify(result));
|
|
56
|
+
`], {
|
|
57
|
+
stdio: 'ignore',
|
|
58
|
+
windowsHide: true,
|
|
59
|
+
detached: true // Required on Windows for proper process detachment
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
child.unref();
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Claude Code Statusline - Declare Edition
|
|
3
|
+
// Shows: model | current task | directory | context usage
|
|
4
|
+
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const os = require('os');
|
|
8
|
+
|
|
9
|
+
// Read JSON from stdin
|
|
10
|
+
let input = '';
|
|
11
|
+
process.stdin.setEncoding('utf8');
|
|
12
|
+
process.stdin.on('data', chunk => input += chunk);
|
|
13
|
+
process.stdin.on('end', () => {
|
|
14
|
+
try {
|
|
15
|
+
const data = JSON.parse(input);
|
|
16
|
+
const model = data.model?.display_name || 'Claude';
|
|
17
|
+
const dir = data.workspace?.current_dir || process.cwd();
|
|
18
|
+
const session = data.session_id || '';
|
|
19
|
+
const remaining = data.context_window?.remaining_percentage;
|
|
20
|
+
|
|
21
|
+
// Context window display (shows USED percentage scaled to 80% limit)
|
|
22
|
+
// Claude Code enforces an 80% context limit, so we scale to show 100% at that point
|
|
23
|
+
let ctx = '';
|
|
24
|
+
if (remaining != null) {
|
|
25
|
+
const rem = Math.round(remaining);
|
|
26
|
+
const rawUsed = Math.max(0, Math.min(100, 100 - rem));
|
|
27
|
+
// Scale: 80% real usage = 100% displayed
|
|
28
|
+
const used = Math.min(100, Math.round((rawUsed / 80) * 100));
|
|
29
|
+
|
|
30
|
+
// Build progress bar (10 segments)
|
|
31
|
+
const filled = Math.floor(used / 10);
|
|
32
|
+
const bar = '█'.repeat(filled) + '░'.repeat(10 - filled);
|
|
33
|
+
|
|
34
|
+
// Color based on scaled usage (thresholds adjusted for new scale)
|
|
35
|
+
if (used < 63) { // ~50% real
|
|
36
|
+
ctx = ` \x1b[32m${bar} ${used}%\x1b[0m`;
|
|
37
|
+
} else if (used < 81) { // ~65% real
|
|
38
|
+
ctx = ` \x1b[33m${bar} ${used}%\x1b[0m`;
|
|
39
|
+
} else if (used < 95) { // ~76% real
|
|
40
|
+
ctx = ` \x1b[38;5;208m${bar} ${used}%\x1b[0m`;
|
|
41
|
+
} else {
|
|
42
|
+
ctx = ` \x1b[5;31m💀 ${bar} ${used}%\x1b[0m`;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Current task from todos
|
|
47
|
+
let task = '';
|
|
48
|
+
const homeDir = os.homedir();
|
|
49
|
+
const todosDir = path.join(homeDir, '.claude', 'todos');
|
|
50
|
+
if (session && fs.existsSync(todosDir)) {
|
|
51
|
+
try {
|
|
52
|
+
const files = fs.readdirSync(todosDir)
|
|
53
|
+
.filter(f => f.startsWith(session) && f.includes('-agent-') && f.endsWith('.json'))
|
|
54
|
+
.map(f => ({ name: f, mtime: fs.statSync(path.join(todosDir, f)).mtime }))
|
|
55
|
+
.sort((a, b) => b.mtime - a.mtime);
|
|
56
|
+
|
|
57
|
+
if (files.length > 0) {
|
|
58
|
+
try {
|
|
59
|
+
const todos = JSON.parse(fs.readFileSync(path.join(todosDir, files[0].name), 'utf8'));
|
|
60
|
+
const inProgress = todos.find(t => t.status === 'in_progress');
|
|
61
|
+
if (inProgress) task = inProgress.activeForm || '';
|
|
62
|
+
} catch (e) {}
|
|
63
|
+
}
|
|
64
|
+
} catch (e) {
|
|
65
|
+
// Silently fail on file system errors - don't break statusline
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Declare update available?
|
|
70
|
+
let gsdUpdate = '';
|
|
71
|
+
const cacheFile = path.join(homeDir, '.claude', 'cache', 'declare-update-check.json');
|
|
72
|
+
if (fs.existsSync(cacheFile)) {
|
|
73
|
+
try {
|
|
74
|
+
const cache = JSON.parse(fs.readFileSync(cacheFile, 'utf8'));
|
|
75
|
+
if (cache.update_available) {
|
|
76
|
+
gsdUpdate = '\x1b[33m⬆ /declare:update\x1b[0m │ ';
|
|
77
|
+
}
|
|
78
|
+
} catch (e) {}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Output
|
|
82
|
+
const dirname = path.basename(dir);
|
|
83
|
+
if (task) {
|
|
84
|
+
process.stdout.write(`${gsdUpdate}\x1b[2m${model}\x1b[0m │ \x1b[1m${task}\x1b[0m │ \x1b[2m${dirname}\x1b[0m${ctx}`);
|
|
85
|
+
} else {
|
|
86
|
+
process.stdout.write(`${gsdUpdate}\x1b[2m${model}\x1b[0m │ \x1b[2m${dirname}\x1b[0m${ctx}`);
|
|
87
|
+
}
|
|
88
|
+
} catch (e) {
|
|
89
|
+
// Silent fail - don't break statusline on parse errors
|
|
90
|
+
}
|
|
91
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "declare-cc",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.2",
|
|
4
4
|
"description": "A future-driven meta-prompting engine for agentic development, rooted in declared futures and causal graph structure.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"declare-cc": "bin/install.js"
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"commands",
|
|
11
11
|
"agents",
|
|
12
12
|
"dist",
|
|
13
|
+
"hooks",
|
|
13
14
|
"scripts",
|
|
14
15
|
"workflows",
|
|
15
16
|
"templates"
|