declare-cc 1.0.4 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -37,7 +37,7 @@ Run it:
37
37
  npm run plan
38
38
  ```
39
39
 
40
- This auto-initializes a `.planning/` directory if one doesn't exist, starts the Declare server, writes the port to `.planning/server.port`, and opens the dashboard in your browser.
40
+ This auto-initializes a `.planning/` directory if one doesn't exist, starts the Declare server on a random free port, writes the port to `.planning/server.port`, and prints the dashboard URL.
41
41
 
42
42
  Or run directly:
43
43
 
@@ -59,7 +59,7 @@ You declare present-tense statements of fact about your project's future. The sy
59
59
 
60
60
  ## How It Works
61
61
 
62
- Everything happens through the dashboard. `dcl` opens it, and you drive the workflow with keyboard shortcuts and card-based UI.
62
+ Everything happens through the dashboard. `dcl` starts the server and prints the URL click it to open.
63
63
 
64
64
  ### 1. Declare Futures
65
65
 
@@ -125,9 +125,94 @@ Cards are grouped into stages at each level:
125
125
  | **Ctrl+Shift+A** | Approve all visible |
126
126
  | **C** | Command bar |
127
127
 
128
- ### Server Discovery
128
+ ---
129
+
130
+ ## Server & Port Discovery
131
+
132
+ The Declare server uses OS-assigned ports so multiple projects can run simultaneously without collisions.
133
+
134
+ ### How it works
135
+
136
+ 1. `dcl` (or `dcl serve`) starts the server on **port 0** — the OS assigns a random free port
137
+ 2. After the server is listening, it writes the port number to `.planning/server.port` (plain text, e.g. `62964`)
138
+ 3. On shutdown (SIGINT, SIGTERM, process exit), the file is deleted
139
+ 4. Next time `dcl` runs, it reads the port file. If the server is still alive, it reuses it. If the file is stale (server crashed), it cleans up and starts fresh.
140
+
141
+ ### For external tools
142
+
143
+ To discover a running Declare server for a project:
144
+
145
+ ```js
146
+ const port = fs.readFileSync('<project>/.planning/server.port', 'utf8').trim();
147
+ // Verify it's alive:
148
+ // GET http://localhost:<port>/api/graph → 200 = running
149
+ // Embed dashboard:
150
+ // <iframe src="http://localhost:<port>/" />
151
+ ```
152
+
153
+ If the file doesn't exist, the server isn't running. Start it:
154
+
155
+ ```bash
156
+ cd <project> && npx dcl serve
157
+ ```
158
+
159
+ Then poll for `.planning/server.port` to appear (~1 second).
160
+
161
+ ### Explicit port
162
+
163
+ If you need a specific port:
164
+
165
+ ```bash
166
+ dcl serve --port 4000
167
+ ```
168
+
169
+ ---
170
+
171
+ ## For Agents
172
+
173
+ If you're an AI agent (Claude Code, Cursor, etc.) working in a project that uses Declare, here's what you need to know:
174
+
175
+ ### Project state lives in `.planning/`
176
+
177
+ - **`FUTURE.md`** — Declared futures (present-tense statements about what's true when the project succeeds)
178
+ - **`MILESTONES.md`** — Milestones derived backward from futures
179
+ - **`.planning/milestones/M-XX-slug/PLAN.md`** — Actions for each milestone (title, produces, causes)
180
+ - **`.planning/STATE.md`** — Current project state and decisions
181
+ - **`.planning/PROJECT.md`** — Project context and background
182
+
183
+ ### The DAG structure
184
+
185
+ Declarations (D-XX) → Milestones (M-XX) → Actions (A-XX). Each layer links to the one above via `realizes` (milestones → declarations) and `causes` (actions → milestones). Read the graph with:
186
+
187
+ ```bash
188
+ node_modules/.bin/declare-cc load-graph
189
+ ```
190
+
191
+ Returns JSON with `declarations`, `milestones`, `actions` arrays.
192
+
193
+ ### Slash commands available to you
194
+
195
+ If you're running inside Claude Code with declare-cc installed, these slash commands are available:
196
+
197
+ - `/declare:status` — See where the project stands
198
+ - `/declare:execute M-XX` — Execute actions for a milestone
199
+ - `/declare:verify M-XX` — Validate deliverables
200
+ - `/declare:trace A-XX` — Understand why an action exists (walk the why-chain)
201
+ - `/declare:progress` — Find the next thing to work on
202
+ - `/declare:help` — See all commands
203
+
204
+ ### Dashboard API
205
+
206
+ If the server is running (check `.planning/server.port`), you can use the HTTP API:
129
207
 
130
- On startup, the server writes the port number to `.planning/server.port` (plain text, e.g. `3847`). On shutdown, it deletes this file. External tools can read this file to embed the dashboard in an iframe.
208
+ | Method | Path | Returns |
209
+ |--------|------|---------|
210
+ | GET | `/api/graph` | Full DAG (declarations, milestones, actions) |
211
+ | GET | `/api/status` | Integrity/alignment metrics |
212
+ | GET | `/api/agents` | Running/completed agents |
213
+ | GET | `/api/events` | SSE stream (real-time updates) |
214
+ | POST | `/api/review` | Approve/reject a node |
215
+ | POST | `/api/action/:id/execute` | Execute an action |
131
216
 
132
217
  ---
133
218
 
@@ -145,7 +230,7 @@ The dashboard is the primary interface. All operations are also available as sla
145
230
  | `/declare:audit M-XX` | Cross-reference against declarations |
146
231
  | `/declare:trace A-XX` | Walk the why-chain to its declaration |
147
232
  | `/declare:status` | Graph health and layer counts |
148
- | `/declare:dashboard` | Open the dashboard |
233
+ | `/declare:dashboard` | Start server and print URL |
149
234
  | `/declare:help` | Show all commands |
150
235
 
151
236
  ---
@@ -1552,7 +1552,7 @@ var require_help = __commonJS({
1552
1552
  usage: "/declare:help"
1553
1553
  }
1554
1554
  ],
1555
- version: "1.0.4"
1555
+ version: "1.0.7"
1556
1556
  };
1557
1557
  }
1558
1558
  module2.exports = { runHelp: runHelp2 };
@@ -4302,7 +4302,7 @@ var require_ai_runner = __commonJS({
4302
4302
  const env = { ...process.env };
4303
4303
  delete env.CLAUDECODE;
4304
4304
  const queryOpts = {
4305
- model: opts.model || "haiku",
4305
+ model: opts.model || "sonnet",
4306
4306
  maxTurns: opts.maxTurns || 1,
4307
4307
  cwd: opts.cwd || process.cwd(),
4308
4308
  abortController,
@@ -4712,7 +4712,7 @@ data: ${JSON.stringify(data)}
4712
4712
  sessions.set(sessionId, { milestoneId: milestone.id, agentId, abortController, startTime: Date.now() });
4713
4713
  runAI(prompt, {
4714
4714
  cwd,
4715
- model: "haiku",
4715
+ model: "opus",
4716
4716
  maxTurns: 1,
4717
4717
  abortController,
4718
4718
  onText: (text) => {
@@ -7396,7 +7396,7 @@ If the current version is already good, output exactly: LGTM \u2014 no changes n
7396
7396
  const isWriteMode = mode === "write";
7397
7397
  runAI(prompt, {
7398
7398
  cwd,
7399
- model: "haiku",
7399
+ model: "opus",
7400
7400
  maxTurns: isWriteMode ? 10 : 1,
7401
7401
  withTools: isWriteMode,
7402
7402
  abortController,
@@ -7608,7 +7608,7 @@ The options array is optional \u2014 include it only when there are clear altern
7608
7608
  discussSession = { sessionId, nodeId: id, abortController, agentId };
7609
7609
  runAI(prompt, {
7610
7610
  cwd,
7611
- model: "haiku",
7611
+ model: "opus",
7612
7612
  maxTurns: 1,
7613
7613
  abortController,
7614
7614
  onText: (text) => {
@@ -7944,7 +7944,7 @@ The options array is optional \u2014 include it only when there are clear altern
7944
7944
  persistOnboardSession(cwd);
7945
7945
  runAI(prompt, {
7946
7946
  cwd,
7947
- model: "haiku",
7947
+ model: "opus",
7948
7948
  maxTurns: 1,
7949
7949
  abortController,
7950
7950
  onText: (text) => {
@@ -8723,6 +8723,10 @@ data: ${JSON.stringify({ reason: "delete", nodeId: id })}
8723
8723
  req.on("close", () => sseClients.delete(res));
8724
8724
  return;
8725
8725
  }
8726
+ if (urlPath === "/api/version") {
8727
+ sendJson(res, 200, { version: true ? "1.0.7" : "dev" });
8728
+ return;
8729
+ }
8726
8730
  if (urlPath === "/api/graph") {
8727
8731
  handleGraph(res, cwd);
8728
8732
  return;
@@ -8722,8 +8722,6 @@ function cancelOnboard() {
8722
8722
  }
8723
8723
 
8724
8724
  function startOnboardApproving(mode) {
8725
- onboardPhase = 'approving';
8726
- onboardApproveIndex = 0;
8727
8725
  if (mode === 'all') {
8728
8726
  approveAllOnboard();
8729
8727
  } else {
@@ -8731,6 +8729,62 @@ function startOnboardApproving(mode) {
8731
8729
  }
8732
8730
  }
8733
8731
 
8732
+ async function approveOnboardByIndex(idx) {
8733
+ if (!onboardProposals || idx >= onboardProposals.length) return;
8734
+ const proposal = onboardProposals[idx];
8735
+ if (proposal.approvedId) return; // already approved
8736
+ try {
8737
+ const resp = await fetch('/api/onboard/approve', {
8738
+ method: 'POST',
8739
+ headers: { 'Content-Type': 'application/json' },
8740
+ body: JSON.stringify({ title: proposal.title, statement: proposal.statement }),
8741
+ });
8742
+ const data = await resp.json();
8743
+ if (data.id) proposal.approvedId = data.id;
8744
+ } catch (_) {}
8745
+
8746
+ // Check if all done
8747
+ const remaining = onboardProposals.filter(p => !p.approvedId).length;
8748
+ if (remaining === 0) {
8749
+ onboardPhase = 'idle';
8750
+ onboardPrompt = null;
8751
+ onboardQuestions = null;
8752
+ onboardProposals = null;
8753
+ fetch('/api/onboard/complete', { method: 'POST' }).catch(() => {});
8754
+ loadData().then(() => renderDrillView());
8755
+ loadActivity();
8756
+ } else {
8757
+ renderOnboardUI();
8758
+ }
8759
+ }
8760
+
8761
+ function editOnboardByIndex(idx) {
8762
+ if (!onboardProposals || idx >= onboardProposals.length) return;
8763
+ const proposal = onboardProposals[idx];
8764
+ const card = document.querySelector(`[data-onboard-idx="${idx}"]`);
8765
+ if (!card) return;
8766
+ const body = card.querySelector('.drill-card-body');
8767
+ if (!body) return;
8768
+ body.innerHTML = `
8769
+ <input class="onboard-title" value="${escHtml(proposal.title)}" style="width:100%;margin-bottom:4px" />
8770
+ <textarea class="onboard-statement" rows="2" style="width:100%">${escHtml(proposal.statement)}</textarea>
8771
+ <div style="margin-top:6px">
8772
+ <button class="drill-review-btn approve-btn onboard-save-btn" data-onboard-idx="${idx}">Save</button>
8773
+ <button class="drill-action-btn onboard-cancel-edit-btn" data-onboard-idx="${idx}">Cancel</button>
8774
+ </div>
8775
+ `;
8776
+ const titleInput = body.querySelector('.onboard-title');
8777
+ if (titleInput) titleInput.focus();
8778
+ body.querySelector('.onboard-save-btn').addEventListener('click', () => {
8779
+ proposal.title = body.querySelector('.onboard-title').value.trim() || proposal.title;
8780
+ proposal.statement = body.querySelector('.onboard-statement').value.trim() || proposal.statement;
8781
+ renderOnboardUI();
8782
+ });
8783
+ body.querySelector('.onboard-cancel-edit-btn').addEventListener('click', () => {
8784
+ renderOnboardUI();
8785
+ });
8786
+ }
8787
+
8734
8788
  async function approveCurrentOnboard() {
8735
8789
  if (!onboardProposals || onboardApproveIndex >= onboardProposals.length) return;
8736
8790
 
@@ -8853,53 +8907,62 @@ function renderOnboardUI() {
8853
8907
  `;
8854
8908
  } else {
8855
8909
  let html = '<div class="onboard-phase-label">Proposed Declarations</div>';
8910
+ html += '<div class="drill-cards onboard-cards">';
8856
8911
  onboardProposals.forEach((p, i) => {
8857
- html += `<div class="onboard-proposal">
8858
- <div class="op-header">
8859
- <span class="op-index">${i + 1}.</span>
8912
+ const isApproved = !!p.approvedId;
8913
+ html += `<div class="drill-card${isApproved ? '' : ' needs-review'}${!isApproved && i === 0 ? ' current-review' : ''}" data-onboard-idx="${i}">
8914
+ <div class="drill-card-top">
8915
+ <span class="drill-card-id">${isApproved ? escHtml(p.approvedId) : (i + 1) + '.'}</span>
8916
+ <div class="drill-card-body">
8917
+ <div class="drill-card-title">${escHtml(p.title)}</div>
8918
+ <div class="drill-card-desc">${escHtml(p.statement)}</div>
8919
+ ${p.reasoning ? `<div class="drill-card-desc" style="opacity:0.5;font-style:italic;margin-top:4px">${escHtml(p.reasoning)}</div>` : ''}
8920
+ </div>
8921
+ </div>
8922
+ <div class="drill-card-actions">
8923
+ ${isApproved
8924
+ ? '<span style="color:var(--approved-color);font-weight:600;font-size:12px">Approved</span>'
8925
+ : `<button class="drill-review-btn approve-btn" data-onboard-action="approve" data-onboard-idx="${i}"><kbd>A</kbd> Approve</button>
8926
+ <button class="drill-action-btn" data-onboard-action="edit" data-onboard-idx="${i}"><kbd>E</kbd> Edit</button>
8927
+ <button class="drill-action-btn drill-action-danger" data-onboard-action="delete" data-onboard-idx="${i}"><kbd>D</kbd> Delete</button>`
8928
+ }
8860
8929
  </div>
8861
- <input class="onboard-title" value="${escHtml(p.title)}" />
8862
- <textarea class="onboard-statement" rows="2">${escHtml(p.statement)}</textarea>
8863
- <div class="onboard-reason">${escHtml(p.reasoning || '')}</div>
8864
8930
  </div>`;
8865
8931
  });
8866
- html += `<div class="onboard-actions">
8867
- <button class="onboard-btn-primary" onclick="startOnboardApproving('all')">Approve All</button>
8868
- <button class="onboard-btn-secondary" onclick="startOnboardApproving('one')">One by One</button>
8869
- <button class="onboard-btn-secondary" onclick="cancelOnboard()">Cancel</button>
8870
- </div>`;
8932
+ html += '</div>';
8933
+ const unapprovedCount = onboardProposals.filter(p => !p.approvedId).length;
8934
+ if (unapprovedCount > 0) {
8935
+ html += `<div class="onboard-actions">
8936
+ <button class="onboard-btn-primary" onclick="startOnboardApproving('all')"><kbd>⌃⇧A</kbd> Approve All (${unapprovedCount})</button>
8937
+ <button class="onboard-btn-secondary" onclick="cancelOnboard()">Cancel</button>
8938
+ </div>`;
8939
+ }
8871
8940
  container.innerHTML = html;
8941
+
8942
+ // Wire card buttons
8943
+ setTimeout(() => {
8944
+ container.querySelectorAll('[data-onboard-action]').forEach(btn => {
8945
+ btn.addEventListener('click', (e) => {
8946
+ e.stopPropagation();
8947
+ const idx = parseInt(btn.dataset.onboardIdx);
8948
+ const action = btn.dataset.onboardAction;
8949
+ if (action === 'approve') {
8950
+ approveOnboardByIndex(idx);
8951
+ } else if (action === 'edit') {
8952
+ editOnboardByIndex(idx);
8953
+ } else if (action === 'delete') {
8954
+ onboardProposals.splice(idx, 1);
8955
+ renderOnboardUI();
8956
+ }
8957
+ });
8958
+ });
8959
+ }, 0);
8872
8960
  }
8873
8961
  } else if (onboardPhase === 'approving') {
8874
- if (!onboardProposals) return;
8875
- let html = `<div class="onboard-phase-label">Approving Declarations</div>`;
8876
- html += `<div class="onboard-progress">${onboardApproveIndex} of ${onboardProposals.length} approved</div>`;
8877
- onboardProposals.forEach((p, i) => {
8878
- const isApproved = i < onboardApproveIndex || p.approvedId;
8879
- const isCurrent = i === onboardApproveIndex && !p.approvedId;
8880
- const cls = isApproved ? 'approved' : isCurrent ? 'current' : '';
8881
- html += `<div class="onboard-proposal ${cls}">
8882
- <div class="op-header">
8883
- ${isApproved ? '<span class="op-check">\u2713</span>' : `<span class="op-index">${i + 1}.</span>`}
8884
- ${p.approvedId ? `<span class="op-id">${escHtml(p.approvedId)}</span>` : ''}
8885
- </div>
8886
- ${isCurrent ? `
8887
- <input class="onboard-title" value="${escHtml(p.title)}" />
8888
- <textarea class="onboard-statement" rows="2">${escHtml(p.statement)}</textarea>
8889
- ` : `
8890
- <div style="font-weight:600;font-size:13px">${escHtml(p.title)}</div>
8891
- <div style="font-size:12px;color:var(--text-dim);margin-top:4px">${escHtml(p.statement)}</div>
8892
- `}
8893
- <div class="onboard-reason">${escHtml(p.reasoning || '')}</div>
8894
- </div>`;
8895
- });
8896
- if (onboardApproveIndex < onboardProposals.length) {
8897
- html += `<div class="onboard-actions">
8898
- <button class="onboard-btn-primary" onclick="approveCurrentOnboard()">Approve &amp; Next</button>
8899
- <button class="onboard-btn-secondary" onclick="cancelOnboard()">Cancel</button>
8900
- </div>`;
8901
- }
8902
- container.innerHTML = html;
8962
+ // Redirect to proposals view — approving is now inline
8963
+ onboardPhase = 'proposals';
8964
+ renderOnboardUI();
8965
+ return;
8903
8966
  }
8904
8967
 
8905
8968
  $drillList.appendChild(container);
@@ -9013,5 +9076,11 @@ loadData().then(() => {
9013
9076
  loadActivity();
9014
9077
  loadAgentCards();
9015
9078
 
9079
+ // Version label
9080
+ fetch('/api/version').then(r => r.json()).then(d => {
9081
+ const el = document.getElementById('version-label');
9082
+ if (el && d.version) el.textContent = 'v' + d.version;
9083
+ }).catch(() => {});
9084
+
9016
9085
  // Poll agent cards every 3s as fallback if SSE agent events are missed
9017
9086
  setInterval(loadAgentCards, 3000);
@@ -1187,6 +1187,15 @@
1187
1187
  @keyframes derive-spin {
1188
1188
  to { transform: rotate(360deg); }
1189
1189
  }
1190
+ #version-label {
1191
+ position: fixed;
1192
+ bottom: 6px;
1193
+ left: 8px;
1194
+ font-size: 10px;
1195
+ color: #555;
1196
+ pointer-events: none;
1197
+ z-index: 1;
1198
+ }
1190
1199
  .derive-card-status {
1191
1200
  display: inline-flex;
1192
1201
  align-items: center;
@@ -4277,6 +4286,7 @@
4277
4286
  </div>
4278
4287
  </div>
4279
4288
 
4280
- <script src="/public/app.js?v=25"></script>
4289
+ <div id="version-label"></div>
4290
+ <script src="/public/app.js?v=27"></script>
4281
4291
  </body>
4282
4292
  </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "declare-cc",
3
- "version": "1.0.4",
3
+ "version": "1.0.7",
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",