@dendelion/paper-camp 0.3.0 → 0.4.0

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/dist/cli/index.js CHANGED
@@ -1,90 +1,95 @@
1
1
  #!/usr/bin/env node
2
- import { spawn as V, spawnSync as ht } from "node:child_process";
3
- import { stat as it, readFile as U, lstat as Ut, unlink as Wt, mkdir as q, writeFile as E } from "node:fs/promises";
4
- import { join as N, dirname as zt, extname as Yt, basename as Qt, resolve as nt } from "node:path";
5
- import { createInterface as jt } from "node:readline";
6
- import { Command as Xt } from "commander";
7
- import { AGENT_OPTIONS as Pt, coerceAgentConfig as B, DEFAULT_AGENTS as kt, PLAN_KINDS as wt, AGENT_IDS as Ot } from "../types/index.js";
8
- import { K as Q, D as dt, L as Tt, M as ct, e as Bt, o as at, O as Z, d as Zt, j as Ft, E as vt, J as xt, B as Jt, x as Et, c as Vt, C as It, i as qt, b as te, m as ee, q as Ht, k as _t, F as ne, w as Dt, h as ae, P as ie, t as se, A as oe } from "../chunks/serializer.DL7H4ZTd.js";
9
- import { createServer as re } from "node:http";
10
- import { fileURLToPath as de } from "node:url";
11
- import { watch as Ct, readFileSync as ce } from "node:fs";
12
- function Mt(e) {
13
- const i = e.phases.map((n, h) => `${h + 1}. [${n.done ? "x" : " "}] ${n.text}`).join(`
14
- `), a = e.log && e.log.length > 0 ? e.log.map((n) => `- ${n.date}: ${n.text}`).join(`
15
- `) : "(none)", p = e.clarifications && e.clarifications.length > 0 ? e.clarifications.map((n) => `- ${n.date}: ${n.text}`).join(`
2
+ import { spawn as it, spawnSync as It } from "node:child_process";
3
+ import { stat as ht, readFile as at, mkdir as ut, writeFile as q, lstat as ce, unlink as Xt } from "node:fs/promises";
4
+ import { join as $, dirname as de, sep as le, extname as ue, basename as pe, resolve as ft } from "node:path";
5
+ import { createInterface as Tt } from "node:readline";
6
+ import { Command as fe } from "commander";
7
+ import { AGENT_OPTIONS as qt, coerceAgentConfig as z, DEFAULT_AGENTS as _t, AGENT_IDS as Mt, PLAN_KINDS as Et } from "../types/index.js";
8
+ import { K as lt, D as mt, L as Ct, M as gt, q as Lt, k as Zt, o as xt, O as ot, E as Vt, J as te, B as ee, C as jt, i as he, b as me, m as ge, x as Jt, c as we, j as ne, e as ae, d as ye, F as $e, w as Kt, h as ke, P as be, t as Se, A as ve } from "../chunks/serializer.B8u-L8BI.js";
9
+ import { createServer as xe } from "node:http";
10
+ import { fileURLToPath as Ie } from "node:url";
11
+ import { watch as Ot, readFileSync as Pe } from "node:fs";
12
+ function ie(t) {
13
+ const a = t.phases.map((s, e) => `${e + 1}. [${s.done ? "x" : " "}] ${s.text}`).join(`
14
+ `), i = t.log && t.log.length > 0 ? t.log.map((s) => `- ${s.date}: ${s.text}`).join(`
15
+ `) : "(none)", r = t.clarifications && t.clarifications.length > 0 ? t.clarifications.map((s) => `- ${s.date}: ${s.text}`).join(`
16
16
  `) : "(none)";
17
- return `You're auditing the plan "${e.title}" (${e.id ?? "no id"}), stored as a single file at papercamp/plans/${e.id ?? "<ID>"}.md (or papercamp/plans/archive/${e.id ?? "<ID>"}.md if it's already done/dropped — check both). Edit only that file.
17
+ return `You are auditing the plan "${t.title}" (${t.id ?? "no id"}) for missing phases. The plan is a single file at papercamp/plans/${t.id ?? "<ID>"}.md if it is not there, it is archived at papercamp/plans/archive/${t.id ?? "<ID>"}.md. Edit only that file.
18
18
 
19
- Plan body: ${e.body}
19
+ Plan body: ${t.body}
20
20
 
21
21
  Current phases:
22
- ${i}
23
-
24
- Log entries (issues, bugs, and review notes documented so far):
25
22
  ${a}
26
23
 
27
- Clarifications (answered questions about scope and design):
28
- ${p}
24
+ Log entries (issues, bugs, and review notes recorded so far):
25
+ ${i}
29
26
 
30
- Read this plan's phases, body, Log entries, and Clarifications, then inspect the current repo state. The Log entries often document bugs, UX issues, or missing functionality that should be turned into new phases. Append any phase that's clearly required but missing as a normal \`- [ ]\` line at the end of the \`### Phases\` list — optionally with the existing indented description format. Explicitly never touch existing lines: never reorder, check, uncheck, or rewrite anything already there, no matter how stale or redundant it looks.
27
+ Clarifications (answered scope/design questions):
28
+ ${r}
31
29
 
32
- If you append anything, finish with exactly one new \`### Log\` line (date: summary) describing what was found and appended.
30
+ Task:
31
+ 1. Read the plan above, then inspect the relevant code in this repo.
32
+ 2. Identify work that is clearly required to fulfil this plan but is covered by no existing phase. The Log entries are the best source — they often record bugs or gaps that were never turned into phases.
33
+ 3. If you found missing work: append one \`- [ ] <short phase title>\` line per item at the end of the \`### Phases\` list. You may add detail below a checkbox line as continuation lines indented with 6 spaces. Then append exactly one line to the \`### Log\` section, formatted \`- YYYY-MM-DD: <what was found and appended>\`, creating that section after the Phases list if it does not exist.
34
+ 4. If nothing is missing: make no edits at all — no Log line, no empty heading, no "nothing found" note. This audit re-runs regularly and must leave no trace when there is nothing to add.
33
35
 
34
- If nothing is missing, write nothing at all — not even an empty heading or a Log line. The audit must be safe to re-run anytime without producing log spam.`;
36
+ Rules:
37
+ - Never modify, reorder, check, uncheck, or delete any existing line, even if it looks stale, wrong, or redundant.
38
+ - Never touch the YAML frontmatter.
39
+ - Append only: new unchecked phases at the end of the list, plus the single Log line.`;
35
40
  }
36
- function pe(e, i) {
37
- const a = [
41
+ function Ae(t, a) {
42
+ const i = [
38
43
  "-p",
39
- e,
44
+ t,
40
45
  "--output-format",
41
46
  "stream-json",
42
47
  "--verbose",
43
48
  "--permission-mode",
44
49
  "auto"
45
50
  ];
46
- return i != null && i.model && a.push("--model", i.model), i != null && i.effort && a.push("--effort", i.effort), a;
51
+ return a != null && a.model && i.push("--model", a.model), a != null && a.effort && i.push("--effort", a.effort), i;
47
52
  }
48
- function ue(e) {
49
- let i;
53
+ function Te(t) {
54
+ let a;
50
55
  try {
51
- i = JSON.parse(e);
56
+ a = JSON.parse(t);
52
57
  } catch {
53
58
  return null;
54
59
  }
55
- switch (i.type) {
60
+ switch (a.type) {
56
61
  case "system":
57
- return i.subtype === "init" ? { text: "Agent session started" } : i.subtype === "post_turn_summary" && typeof i.status_detail == "string" ? { text: i.status_detail } : null;
62
+ return a.subtype === "init" ? { text: "Agent session started" } : a.subtype === "post_turn_summary" && typeof a.status_detail == "string" ? { text: a.status_detail } : null;
58
63
  case "rate_limit_event":
59
64
  return null;
60
65
  case "assistant": {
61
- const a = i.message, p = (a == null ? void 0 : a.content) ?? [];
62
- for (const n of p) {
63
- const h = n;
64
- if (h.type === "tool_use")
65
- return { text: `Running ${h.name ?? "a tool"}…` };
66
- if (h.type === "text" && typeof h.text == "string" && h.text.trim())
67
- return { text: h.text.trim() };
66
+ const i = a.message, r = (i == null ? void 0 : i.content) ?? [];
67
+ for (const s of r) {
68
+ const e = s;
69
+ if (e.type === "tool_use")
70
+ return { text: `Running ${e.name ?? "a tool"}…` };
71
+ if (e.type === "text" && typeof e.text == "string" && e.text.trim())
72
+ return { text: e.text.trim() };
68
73
  }
69
74
  return null;
70
75
  }
71
76
  case "user": {
72
- const a = i.message, p = a == null ? void 0 : a.content, n = Array.isArray(p) ? p[0] : void 0;
73
- return n != null && n.is_error ? { text: `Error: ${typeof n.content == "string" ? n.content : "Tool call failed"}`, error: !0 } : { text: "Tool finished" };
77
+ const i = a.message, r = i == null ? void 0 : i.content, s = Array.isArray(r) ? r[0] : void 0;
78
+ return s != null && s.is_error ? { text: `Error: ${typeof s.content == "string" ? s.content : "Tool call failed"}`, error: !0 } : { text: "Tool finished" };
74
79
  }
75
80
  case "result": {
76
- const a = !!i.is_error;
77
- return { text: (typeof i.result == "string" ? i.result.trim() : "") || (a ? "Agent run failed" : "Agent run finished"), done: !0, error: a };
81
+ const i = !!a.is_error;
82
+ return { text: (typeof a.result == "string" ? a.result.trim() : "") || (i ? "Agent run failed" : "Agent run finished"), done: !0, error: i };
78
83
  }
79
84
  default:
80
85
  return { text: "Agent is working…" };
81
86
  }
82
87
  }
83
- function le(e, i) {
84
- const a = ["run", e, "--format", "json"];
85
- return i != null && i.model && a.push("-m", i.model), i != null && i.effort && a.push("--variant", i.effort), a;
88
+ function Ee(t, a) {
89
+ const i = ["run", t, "--format", "json"];
90
+ return a != null && a.model && i.push("-m", a.model), a != null && a.effort && i.push("--variant", a.effort), i;
86
91
  }
87
- const fe = {
92
+ const De = {
88
93
  bash: "Running command",
89
94
  read: "Reading file",
90
95
  edit: "Editing file",
@@ -95,465 +100,589 @@ const fe = {
95
100
  webfetch: "Fetching URL",
96
101
  question: "Asking for input"
97
102
  };
98
- function ge(e) {
99
- let i;
103
+ function Oe(t) {
104
+ let a;
100
105
  try {
101
- i = JSON.parse(e);
106
+ a = JSON.parse(t);
102
107
  } catch {
103
108
  return null;
104
109
  }
105
- const a = i.type, p = i.part;
106
- if (!a || !p) return null;
107
- switch (a) {
110
+ const i = a.type, r = a.part;
111
+ if (!i || !r) return null;
112
+ switch (i) {
108
113
  case "step_start":
109
114
  return null;
110
115
  case "text": {
111
- const n = p.text;
112
- return n != null && n.trim() ? { text: n.trim() } : null;
116
+ const s = r.text;
117
+ return s != null && s.trim() ? { text: s.trim() } : null;
113
118
  }
114
119
  case "tool_use": {
115
- const n = p.tool, h = p.state ? p.state.input : void 0, m = h ? h.description : void 0, $ = n ? fe[n] : "Running tool", u = typeof m == "string" && m.trim() ? `: ${m.trim()}` : "";
116
- return n ? { text: `${$}${u}…` } : null;
120
+ const s = r.tool, e = r.state ? r.state.input : void 0, n = e ? e.description : void 0, l = s ? De[s] : "Running tool", c = typeof n == "string" && n.trim() ? `: ${n.trim()}` : "";
121
+ return s ? { text: `${l}${c}…` } : null;
117
122
  }
118
123
  case "step_finish": {
119
- const n = p.reason, h = n === "tool-calls" ? null : n === "stop" ? "Done" : "Step finished";
120
- return h ? { text: h } : null;
124
+ const s = r.reason, e = s === "tool-calls" ? null : s === "stop" ? "Done" : "Step finished";
125
+ return e ? { text: e } : null;
121
126
  }
122
127
  default:
123
128
  return null;
124
129
  }
125
130
  }
126
- const Rt = "claude-code", lt = {
131
+ const Gt = "claude-code", bt = {
127
132
  "claude-code": {
128
133
  command: "claude",
129
- buildArgs: pe,
130
- parseLine: ue,
131
- options: Pt["claude-code"]
134
+ buildArgs: Ae,
135
+ parseLine: Te,
136
+ options: qt["claude-code"]
132
137
  },
133
138
  opencode: {
134
139
  command: "opencode",
135
- buildArgs: le,
136
- parseLine: ge,
137
- options: Pt.opencode
140
+ buildArgs: Ee,
141
+ parseLine: Oe,
142
+ options: qt.opencode
138
143
  }
139
- }, me = {
144
+ }, Ne = {
140
145
  phase: "phase",
141
146
  audit: "phase",
142
147
  "batch-audit": "phase",
148
+ "run-all": "phase",
149
+ sync: "phase",
150
+ reconcile: "phase",
143
151
  draft: "planDraft",
144
152
  extend: "ideaExtend",
145
153
  "commit-suggest": "commitSuggest"
146
154
  };
147
- function St(e) {
148
- const { agentId: i, defaultAgents: a, taskKind: p } = e;
149
- if (i && i in lt) return { id: i, adapter: lt[i] };
150
- if (p && a) {
151
- const n = me[p], h = a[n];
152
- if (h) {
153
- const m = h.agent;
154
- if (m in lt)
155
- return { id: m, adapter: lt[m], model: h.model, effort: h.effort };
156
- }
157
- }
158
- return { id: Rt, adapter: lt[Rt] };
155
+ function St(t) {
156
+ const { agentId: a, defaultAgents: i, taskKind: r } = t;
157
+ if (a && a in bt) return { id: a, adapter: bt[a] };
158
+ if (r && i) {
159
+ const s = Ne[r], e = i[s];
160
+ if (e) {
161
+ const n = e.agent;
162
+ if (n in bt)
163
+ return { id: n, adapter: bt[n], model: e.model, effort: e.effort };
164
+ }
165
+ }
166
+ return { id: Gt, adapter: bt[Gt] };
159
167
  }
160
- function Kt(e, i) {
161
- return e.map((a) => {
162
- if (!a.id)
163
- return { ...a, status: "planned" };
164
- const p = i.filter((h) => h.idea === a.id);
165
- if (p.length === 0)
166
- return { ...a, status: "planned" };
167
- const n = p.every((h) => h.status === "done" || h.status === "dropped");
168
- return { ...a, status: n ? "done" : "planned" };
168
+ function se(t, a) {
169
+ return t.map((i) => {
170
+ if (!i.id)
171
+ return { ...i, status: "planned" };
172
+ const r = a.filter((e) => e.idea === i.id);
173
+ if (r.length === 0)
174
+ return { ...i, status: "planned" };
175
+ const s = r.every((e) => e.status === "done" || e.status === "dropped");
176
+ return { ...i, status: s ? "done" : "planned" };
169
177
  });
170
178
  }
171
- const he = (e, i) => {
172
- if (e)
173
- return e.find((a) => a.status === "in-progress" || a.status === "review");
174
- };
175
- function ye(e) {
176
- const i = /* @__PURE__ */ new Set();
177
- let a = null;
178
- function p() {
179
- const n = `data: ${JSON.stringify({
179
+ function Re(t) {
180
+ const a = /* @__PURE__ */ new Set();
181
+ let i = null;
182
+ function r() {
183
+ const s = `data: ${JSON.stringify({
180
184
  message: "changed",
181
185
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
182
186
  })}
183
187
 
184
188
  `;
185
- for (const h of i)
189
+ for (const e of a)
186
190
  try {
187
- h.write(n);
191
+ e.write(s);
188
192
  } catch {
189
- i.delete(h);
193
+ a.delete(e);
190
194
  }
191
195
  }
192
196
  try {
193
- Ct(N(e, "papercamp"), { recursive: !0 }, () => {
194
- a && clearTimeout(a), a = setTimeout(p, 300);
197
+ Ot($(t, "papercamp"), { recursive: !0 }, () => {
198
+ i && clearTimeout(i), i = setTimeout(r, 300);
195
199
  });
196
200
  } catch {
197
201
  }
198
202
  return {
199
- subscribe(n) {
200
- i.add(n);
201
- const h = JSON.stringify({
203
+ subscribe(s) {
204
+ a.add(s);
205
+ const e = JSON.stringify({
202
206
  message: "Watching for changes…",
203
207
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
204
208
  });
205
- n.write(`data: ${h}
209
+ s.write(`data: ${e}
206
210
 
207
- `), n.on("close", () => i.delete(n));
211
+ `), s.on("close", () => a.delete(s));
208
212
  }
209
213
  };
210
214
  }
211
- const we = 50;
212
- function Nt(e) {
215
+ const Be = 50, Ht = 30 * 60 * 1e3;
216
+ function Pt(t) {
213
217
  try {
214
- const i = ce(N(e, "papercamp", "config.json"), "utf-8"), a = JSON.parse(i), p = a.defaultAgents;
215
- if (p)
218
+ const a = Pe($(t, "papercamp", "config.json"), "utf-8"), i = JSON.parse(a), r = i.defaultAgents;
219
+ if (r)
216
220
  return {
217
- phase: B(p.phase),
218
- planDraft: B(p.planDraft),
219
- ideaExtend: B(p.ideaExtend),
220
- commitSuggest: B(p.commitSuggest)
221
+ phase: z(r.phase),
222
+ planDraft: z(r.planDraft),
223
+ ideaExtend: z(r.ideaExtend),
224
+ commitSuggest: z(r.commitSuggest)
221
225
  };
222
- if (a.defaultAgent) {
223
- const n = a.defaultAgent;
226
+ if (i.defaultAgent) {
227
+ const s = i.defaultAgent;
224
228
  return {
225
- phase: { agent: n },
226
- planDraft: { agent: n },
227
- ideaExtend: { agent: n },
228
- commitSuggest: { agent: n }
229
+ phase: { agent: s },
230
+ planDraft: { agent: s },
231
+ ideaExtend: { agent: s },
232
+ commitSuggest: { agent: s }
229
233
  };
230
234
  }
231
- return kt;
235
+ return _t;
232
236
  } catch {
233
- return kt;
237
+ return _t;
234
238
  }
235
239
  }
236
- function Se(e, i, a) {
237
- return `You're working on phase ${a + 1} ("${i.text}") of the plan "${e.title}" (${e.id ?? "no id"}), stored as a single file at papercamp/plans/${e.id ?? "<ID>"}.md.
240
+ function Wt(t, a, i) {
241
+ const r = a.description ? `Phase details:
242
+ ${a.description}
238
243
 
239
- ${i.description ?? ""}
244
+ ` : "";
245
+ return `You are executing exactly one phase of the plan "${t.title}" (${t.id ?? "no id"}): phase ${i + 1}, "${a.text}". The plan is a single file at papercamp/plans/${t.id ?? "<ID>"}.md.
240
246
 
241
- Plan context: ${e.body}
247
+ ${r}Plan context: ${t.body}
242
248
 
243
- Do only this phase. When done, check it off in that file's \`### Phases\` list (- [ ] -> - [x]) and append what you did to progress.md. If this was the last unchecked phase, set the plan's \`status:\` frontmatter field to \`review\`, not \`done\`, per this repo's AGENTS.md.`;
249
+ Do only this phase do not start any other phase, even if it looks quick.
250
+
251
+ When the work is done:
252
+ 1. In the plan file's \`### Phases\` list, change this phase's checkbox from \`- [ ]\` to \`- [x]\`. Do not change any other line.
253
+ 2. Add one bullet describing what you did under today's \`## YYYY-MM-DD\` heading at the top of progress.md (create the heading at the top if today's is not there yet — newest day stays first).
254
+ 3. If every phase in the list is now checked, set the plan's \`status:\` frontmatter field to \`review\` — never \`done\`; per this repo's AGENTS.md a human promotes plans to done.`;
244
255
  }
245
- function Ce(e, i = () => {
246
- }, a) {
247
- const p = /* @__PURE__ */ new Set();
256
+ function Ce(t, a = () => {
257
+ }, i, r, s) {
258
+ const e = /* @__PURE__ */ new Set();
248
259
  let n = null;
249
- function h(s) {
250
- const w = `data: ${JSON.stringify({ message: s, timestamp: (/* @__PURE__ */ new Date()).toISOString(), type: "agent" })}
260
+ function l(o) {
261
+ const k = `data: ${JSON.stringify({ message: o, timestamp: (/* @__PURE__ */ new Date()).toISOString(), type: "agent" })}
251
262
 
252
263
  `;
253
- for (const C of p)
264
+ for (const v of e)
254
265
  try {
255
- C.write(w);
266
+ v.write(k);
256
267
  } catch {
257
- p.delete(C);
268
+ e.delete(v);
258
269
  }
259
270
  }
260
- function m(s, w) {
261
- s.lines.push(w), s.lines.length > we && s.lines.shift(), h(w);
271
+ function c(o, k) {
272
+ o.lines.push(k), o.lines.length > Be && o.lines.shift(), l(k);
262
273
  }
263
- function $(s, w) {
264
- s.status = w, h(`agent: ${w}`);
274
+ function d(o, k) {
275
+ o.status = k, l(`agent: ${k}`);
265
276
  }
266
- async function u(s) {
267
- var w, C;
277
+ async function u(o) {
278
+ var k, v;
268
279
  try {
269
- if (s.taskKind === "extend") {
270
- const k = N(e, "papercamp", "ideas"), { entries: W } = await Tt(k, N(e, "papercamp", "ideas.md")), z = W.find((F) => F.id === s.ideaId);
271
- return !z || s.ideaBodyBaseline === void 0 ? null : z.body !== s.ideaBodyBaseline;
280
+ if (o.taskKind === "extend") {
281
+ const x = $(t, "papercamp", "ideas"), { entries: W } = await Ct(x, $(t, "papercamp", "ideas.md")), Q = W.find((b) => b.id === o.ideaId);
282
+ return !Q || o.ideaBodyBaseline === void 0 ? null : Q.body !== o.ideaBodyBaseline;
283
+ }
284
+ if (o.taskKind === "reconcile") {
285
+ const { entries: x } = await gt(
286
+ $(t, "papercamp", "plans"),
287
+ $(t, "papercamp", "plans.md")
288
+ ), W = x.find((Q) => Q.id === o.planId);
289
+ return !W || o.reconcileBaseline === void 0 ? null : JSON.stringify({ body: W.body, phases: W.phases.map((Q) => Q.text) }) !== o.reconcileBaseline;
272
290
  }
273
- const O = N(e, "papercamp", "plans"), { entries: A } = await ct(O, N(e, "papercamp", "plans.md"));
274
- if (s.ideaId !== void 0)
275
- return A.some((k) => k.idea === s.ideaId);
276
- const H = A.find((k) => k.id === s.planId) ?? A.find((k) => k.title === s.planTitle);
277
- return H ? s.phaseIndex !== void 0 ? ((w = H.phases[s.phaseIndex]) == null ? void 0 : w.done) ?? null : s.planBaseline ? H.phases.length > s.planBaseline.phases || (((C = H.log) == null ? void 0 : C.length) ?? 0) > s.planBaseline.log : null : null;
291
+ const N = $(t, "papercamp", "plans"), { entries: X } = await gt(N, $(t, "papercamp", "plans.md"));
292
+ if (o.ideaId !== void 0)
293
+ return X.some((x) => x.idea === o.ideaId);
294
+ const G = X.find((x) => x.id === o.planId) ?? X.find((x) => x.title === o.planTitle);
295
+ return G ? o.phaseIndex !== void 0 ? ((k = G.phases[o.phaseIndex]) == null ? void 0 : k.done) ?? null : o.planBaseline ? G.phases.length > o.planBaseline.phases || (((v = G.log) == null ? void 0 : v.length) ?? 0) > o.planBaseline.log : null : null;
278
296
  } catch {
279
297
  return null;
280
298
  }
281
299
  }
282
- function t(s, w) {
283
- $(s, w ? "error" : "done"), !w && u(s).then((C) => {
284
- if (n === s && C === !1) {
285
- const O = s.taskKind === "extend" ? `Warning: agent finished but the idea body for ${s.ideaId} did not change — verify manually` : s.ideaId !== void 0 ? `Warning: agent finished but no plan linking idea: ${s.ideaId} appeared in papercamp/plans/ — verify manually` : s.phaseIndex !== void 0 ? "Warning: agent finished but did not check off this phase in the plan file — verify manually" : "Warning: agent finished but appended nothing to Phases or Log — verify manually";
286
- m(s, O);
300
+ function f(o, k) {
301
+ d(o, k ? "error" : "done"), !k && u(o).then((v) => {
302
+ if (n === o && v === !1) {
303
+ const N = o.taskKind === "extend" ? `Warning: agent finished but the idea body for ${o.ideaId} did not change — verify manually` : o.taskKind === "reconcile" ? "Warning: agent finished but the plan body and phase text did not change — verify manually" : o.ideaId !== void 0 ? `Warning: agent finished but no plan linking idea: ${o.ideaId} appeared in papercamp/plans/ — verify manually` : o.phaseIndex !== void 0 ? "Warning: agent finished but did not check off this phase in the plan file — verify manually" : "Warning: agent finished but appended nothing to Phases or Log — verify manually";
304
+ c(o, N);
287
305
  }
288
- n === s && s.taskKind === "audit" && s.planId && C === !0 && (a == null || a(s.planId, 0).catch(() => {
306
+ n === o && o.taskKind === "audit" && o.planId && v === !0 && (i == null || i(o.planId, 0).catch(() => {
289
307
  }));
290
308
  });
291
309
  }
292
- function J(s) {
293
- if (!s.proc.stdout) return;
294
- jt({ input: s.proc.stdout }).on("line", (C) => {
295
- if (n !== s || !C.trim()) return;
296
- const O = s.adapter.parseLine(C);
297
- O && (m(s, O.text), O.done && t(s, !!O.error));
298
- }), s.proc.on("close", (C) => {
299
- n === s && (s.status === "starting" || s.status === "running" ? t(s, C !== 0) : s.status === "stopping" && $(s, "done"));
300
- }), s.proc.on("error", (C) => {
301
- n === s && (m(s, `Failed to spawn agent: ${C.message}`), $(s, "error"));
310
+ function h(o) {
311
+ if (!o.proc.stdout) return;
312
+ Tt({ input: o.proc.stdout }).on("line", (v) => {
313
+ if (n !== o || !v.trim()) return;
314
+ const N = o.adapter.parseLine(v);
315
+ N && (c(o, N.text), N.done && f(o, !!N.error));
316
+ }), o.proc.on("close", (v) => {
317
+ n === o && (o.status === "starting" || o.status === "running" ? f(o, v !== 0) : o.status === "stopping" && d(o, "done"));
318
+ }), o.proc.on("error", (v) => {
319
+ n === o && (c(o, `Failed to spawn agent: ${v.message}`), d(o, "error"));
302
320
  });
303
321
  }
304
- function g(s, w) {
305
- return V(s.command, w, {
306
- cwd: e,
322
+ function y(o, k) {
323
+ return it(o.command, k, {
324
+ cwd: t,
307
325
  stdio: ["ignore", "pipe", "pipe"]
308
326
  });
309
327
  }
310
- function j() {
328
+ function g() {
311
329
  return n !== null && n.status !== "done" && n.status !== "error";
312
330
  }
313
- function r(s, w, C) {
314
- if (j())
331
+ function E(o, k, v) {
332
+ if (g())
315
333
  return { ok: !1, error: "An agent task is already running" };
316
- const O = Nt(e), {
317
- id: A,
318
- adapter: H,
319
- model: k,
334
+ const N = Pt(t), {
335
+ id: X,
336
+ adapter: G,
337
+ model: x,
320
338
  effort: W
321
339
  } = St({
322
- agentId: s.agentOverride,
323
- defaultAgents: O,
324
- taskKind: C.taskKind
325
- }), z = g(H, H.buildArgs(w, { model: k, effort: W })), F = {
326
- planTitle: s.planTitle,
327
- planId: s.planId,
340
+ agentId: o.agentOverride,
341
+ defaultAgents: N,
342
+ taskKind: v.taskKind
343
+ }), Q = y(G, G.buildArgs(k, { model: x, effort: W })), b = {
344
+ planTitle: o.planTitle,
345
+ planId: o.planId,
328
346
  status: "starting",
329
- agentId: A,
330
- adapter: H,
331
- proc: z,
347
+ agentId: X,
348
+ adapter: G,
349
+ proc: Q,
332
350
  lines: [],
333
- ...C
351
+ ...v
334
352
  };
335
- return n = F, J(F), $(F, "running"), { ok: !0 };
353
+ return n = b, h(b), d(b, "running"), { ok: !0 };
336
354
  }
337
- function d(s, w) {
338
- if (j())
355
+ function F(o, k) {
356
+ if (g())
339
357
  return { ok: !1, error: "An agent task is already running" };
340
- const C = s.phases[w];
341
- if (!C)
358
+ const v = o.phases[k];
359
+ if (!v)
342
360
  return { ok: !1, error: "Phase not found" };
343
- i(s);
344
- const O = Se(s, C, w);
345
- return r({ planTitle: s.title, planId: s.id, agentOverride: s.agent }, O, {
361
+ a(o);
362
+ const N = Wt(o, v, k);
363
+ return E({ planTitle: o.title, planId: o.id, agentOverride: o.agent }, N, {
346
364
  taskKind: "phase",
347
- phaseIndex: w
365
+ phaseIndex: k
348
366
  });
349
367
  }
350
- function y(s, w) {
351
- var C;
352
- return r({ planTitle: s.title, planId: s.id, agentOverride: s.agent }, w, {
353
- taskKind: "audit",
354
- planBaseline: { phases: s.phases.length, log: ((C = s.log) == null ? void 0 : C.length) ?? 0 }
368
+ function D(o, k, v = "audit") {
369
+ var N;
370
+ return E({ planTitle: o.title, planId: o.id, agentOverride: o.agent }, k, {
371
+ taskKind: v,
372
+ ...v === "reconcile" ? {
373
+ reconcileBaseline: JSON.stringify({
374
+ body: o.body,
375
+ phases: o.phases.map((X) => X.text)
376
+ })
377
+ } : { planBaseline: { phases: o.phases.length, log: ((N = o.log) == null ? void 0 : N.length) ?? 0 } }
355
378
  });
356
379
  }
357
- function l(s, w) {
358
- return s.id ? r({ planTitle: `Draft plan for ${s.id}` }, w, {
380
+ function S(o, k) {
381
+ return o.id ? E({ planTitle: `Draft plan for ${o.id}` }, k, {
359
382
  taskKind: "draft",
360
- ideaId: s.id
383
+ ideaId: o.id
361
384
  }) : { ok: !1, error: "Idea has no id to link a drafted plan back to" };
362
385
  }
363
- function T(s, w) {
364
- return s.id ? r({ planTitle: `Extend ${s.id}` }, w, {
386
+ function m(o, k) {
387
+ return o.id ? E({ planTitle: `Extend ${o.id}` }, k, {
365
388
  taskKind: "extend",
366
- ideaId: s.id,
367
- ideaBodyBaseline: s.body
389
+ ideaId: o.id,
390
+ ideaBodyBaseline: o.body
368
391
  }) : { ok: !1, error: "Idea has no id to extend" };
369
392
  }
370
- async function c(s, w) {
371
- const C = N(s, `${w}.md`);
393
+ function w(o) {
394
+ return E({ planTitle: "Sync to main" }, o, { taskKind: "sync" });
395
+ }
396
+ async function I(o, k) {
397
+ const v = $(o, `${k}.md`);
372
398
  try {
373
- return await it(C), C;
399
+ return await ht(v), v;
374
400
  } catch {
375
401
  }
376
- const O = N(s, "archive", `${w}.md`);
402
+ const N = $(o, "archive", `${k}.md`);
377
403
  try {
378
- return await it(O), O;
404
+ return await ht(N), N;
379
405
  } catch {
380
406
  }
381
407
  return null;
382
408
  }
383
- function o() {
384
- if (j())
409
+ function T() {
410
+ if (g())
385
411
  return { ok: !1, error: "An agent task is already running" };
386
- const s = Nt(e), {
387
- id: w,
388
- adapter: C,
389
- model: O,
390
- effort: A
391
- } = St({ defaultAgents: s, taskKind: "audit" }), H = V("sh", ["-c", "exit 0"], {
392
- cwd: e,
412
+ const o = Pt(t), {
413
+ id: k,
414
+ adapter: v,
415
+ model: N,
416
+ effort: X
417
+ } = St({ defaultAgents: o, taskKind: "audit" }), G = it("sh", ["-c", "exit 0"], {
418
+ cwd: t,
393
419
  stdio: "ignore"
394
- }), k = {
420
+ }), x = {
395
421
  taskKind: "batch-audit",
396
422
  planTitle: "Batch audit",
397
423
  status: "starting",
398
- agentId: w,
399
- adapter: C,
400
- proc: H,
424
+ agentId: k,
425
+ adapter: v,
426
+ proc: G,
401
427
  lines: []
402
428
  };
403
- return n = k, $(k, "running"), (async () => {
404
- var W, z;
429
+ return n = x, d(x, "running"), (async () => {
430
+ var W, Q;
405
431
  try {
406
- const F = N(e, "papercamp", "plans"), { entries: K } = await Q(F), R = K.filter((D) => D.status === "review" || D.status === "done");
407
- if (R.length === 0) {
408
- n === k && (m(k, 'No plans with status "review" or "done" to audit.'), $(k, "done"));
432
+ const b = $(t, "papercamp", "plans"), { entries: K } = await lt(b), C = K.filter((A) => A.status === "review" || A.status === "done");
433
+ if (C.length === 0) {
434
+ n === x && (c(x, 'No plans with status "review" or "done" to audit.'), d(x, "done"));
409
435
  return;
410
436
  }
411
- m(k, `Auditing ${R.length} plan(s)…`);
412
- let pt = 0, X = 0, ut = 0;
413
- for (const D of R) {
414
- if (n !== k || k.status === "stopping") break;
415
- if (!D.id) {
416
- X++;
437
+ c(x, `Auditing ${C.length} plan(s)…`);
438
+ let st = 0, Z = 0, tt = 0;
439
+ for (const A of C) {
440
+ if (n !== x || x.status === "stopping") break;
441
+ if (!A.id) {
442
+ Z++;
417
443
  continue;
418
444
  }
419
- const ot = await c(F, D.id);
420
- if (!ot) {
421
- X++;
445
+ const ct = await I(b, A.id);
446
+ if (!ct) {
447
+ Z++;
422
448
  continue;
423
449
  }
424
- if (D.audited) {
425
- const _ = await it(ot).catch(() => null), G = _ ? _.mtime.toISOString().slice(0, 10) : null;
426
- if (G && D.audited >= G) {
427
- m(k, `[skip] ${D.id} — up to date`), X++;
450
+ if (A.audited) {
451
+ const H = await ht(ct).catch(() => null), V = H ? H.mtime.toISOString().slice(0, 10) : null;
452
+ if (V && A.audited >= V) {
453
+ c(x, `[skip] ${A.id} — up to date`), Z++;
428
454
  continue;
429
455
  }
430
456
  }
431
- m(k, `[audit] ${D.id} ${D.title}`);
432
- const gt = Mt(D), Y = V(C.command, C.buildArgs(gt, { model: O, effort: A }), {
433
- cwd: e,
457
+ c(x, `[audit] ${A.id} ${A.title}`);
458
+ const U = ie(A), et = it(v.command, v.buildArgs(U, { model: N, effort: X }), {
459
+ cwd: t,
434
460
  stdio: ["ignore", "pipe", "pipe"]
435
461
  });
436
- k.proc = Y, Y.stdout && jt({ input: Y.stdout }).on("line", (G) => {
437
- if (n !== k) return;
438
- const L = C.parseLine(G);
439
- L != null && L.text && L.text !== "Agent is working…" && m(k, ` ${L.text}`);
440
- }), (W = Y.stderr) == null || W.on("data", () => {
462
+ x.proc = et, et.stdout && Tt({ input: et.stdout }).on("line", (V) => {
463
+ if (n !== x) return;
464
+ const L = v.parseLine(V);
465
+ L != null && L.text && L.text !== "Agent is working…" && c(x, ` ${L.text}`);
466
+ }), (W = et.stderr) == null || W.on("data", () => {
441
467
  });
442
- const mt = await new Promise((_) => {
443
- Y.on("close", (G) => _(G === 0)), Y.on("error", () => _(!1));
468
+ const $t = await new Promise((H) => {
469
+ et.on("close", (V) => H(V === 0)), et.on("error", () => H(!1));
444
470
  });
445
- if (n !== k) return;
446
- if (mt) {
447
- let _ = 0;
471
+ if (n !== x) return;
472
+ if ($t) {
473
+ let H = 0;
448
474
  try {
449
- const G = await U(ot, "utf-8"), rt = ((z = dt(G).entries[0]) == null ? void 0 : z.phases.length) ?? D.phases.length;
450
- _ = Math.max(0, rt - D.phases.length);
475
+ const V = await at(ct, "utf-8"), nt = ((Q = mt(V).entries[0]) == null ? void 0 : Q.phases.length) ?? A.phases.length;
476
+ H = Math.max(0, nt - A.phases.length);
451
477
  } catch {
452
478
  }
453
- await (a == null ? void 0 : a(D.id, _)), pt++, m(
454
- k,
455
- _ > 0 ? `[done] ${D.id} — ${_} gap phase${_ === 1 ? "" : "s"} added` : `[done] ${D.id}`
479
+ await (i == null ? void 0 : i(A.id, H)), st++, c(
480
+ x,
481
+ H > 0 ? `[done] ${A.id} — ${H} gap phase${H === 1 ? "" : "s"} added` : `[done] ${A.id}`
456
482
  );
457
483
  } else
458
- ut++, m(k, `[fail] ${D.id} — agent error`);
484
+ tt++, c(x, `[fail] ${A.id} — agent error`);
485
+ }
486
+ if (n !== x) return;
487
+ if (x.status === "stopping") {
488
+ d(x, "done");
489
+ return;
490
+ }
491
+ c(x, `Audit complete — ${st} audited, ${Z} skipped, ${tt} failed`), d(x, tt > 0 ? "error" : "done");
492
+ } catch (b) {
493
+ n === x && (c(x, `Batch audit failed: ${b.message}`), d(x, "error"));
494
+ }
495
+ })(), { ok: !0 };
496
+ }
497
+ function O(o, k) {
498
+ if (g())
499
+ return { ok: !1, error: "An agent task is already running" };
500
+ const v = o.phases.map((K, C) => ({ phase: K, i: C })).filter(({ phase: K }) => !K.done);
501
+ if (v.length === 0)
502
+ return { ok: !1, error: "No unchecked phases to run" };
503
+ a(o);
504
+ const N = Pt(t), {
505
+ id: X,
506
+ adapter: G,
507
+ model: x,
508
+ effort: W
509
+ } = St({ agentId: o.agent, defaultAgents: N, taskKind: "run-all" }), Q = it("sh", ["-c", "exit 0"], { cwd: t, stdio: "ignore" }), b = {
510
+ taskKind: "run-all",
511
+ planTitle: o.title,
512
+ planId: o.id,
513
+ status: "starting",
514
+ agentId: X,
515
+ adapter: G,
516
+ proc: Q,
517
+ lines: []
518
+ };
519
+ return n = b, d(b, "running"), (async () => {
520
+ var K;
521
+ try {
522
+ const C = o.phases.length;
523
+ let st = 0, Z = 0;
524
+ for (const { phase: tt, i: A } of v) {
525
+ if (n !== b || b.status === "stopping") break;
526
+ b.phaseIndex = A, c(b, `[phase ${A + 1}/${C}] ${tt.text}`);
527
+ const ct = Wt(o, tt, A), U = it(G.command, G.buildArgs(ct, { model: x, effort: W }), {
528
+ cwd: t,
529
+ stdio: ["ignore", "pipe", "pipe"]
530
+ });
531
+ b.proc = U, U.stdout && Tt({ input: U.stdout }).on("line", (kt) => {
532
+ if (n !== b) return;
533
+ const dt = G.parseLine(kt);
534
+ dt != null && dt.text && dt.text !== "Agent is working…" && c(b, ` ${dt.text}`);
535
+ }), (K = U.stderr) == null || K.on("data", () => {
536
+ });
537
+ let et = !1;
538
+ const $t = new Promise((nt) => {
539
+ U.on("close", (kt) => nt(kt === 0)), U.on("error", () => nt(!1));
540
+ }), H = setTimeout(() => {
541
+ et = !0, U.killed || U.kill("SIGTERM"), setTimeout(() => {
542
+ U.exitCode === null && U.signalCode === null && U.kill("SIGKILL");
543
+ }, 5e3);
544
+ }, Ht), V = await $t;
545
+ if (clearTimeout(H), n !== b) return;
546
+ if (et) {
547
+ Z++, c(
548
+ b,
549
+ `[timeout] phase ${A + 1} — no progress for ${Ht / 6e4}min, stopping`
550
+ );
551
+ break;
552
+ }
553
+ if (!V) {
554
+ Z++, c(b, `[fail] phase ${A + 1} — agent error, stopping`);
555
+ break;
556
+ }
557
+ const L = await u(b);
558
+ if (!L) {
559
+ Z++, c(
560
+ b,
561
+ L === null ? `[fail] phase ${A + 1} — could not read plan after run, stopping` : `[fail] phase ${A + 1} — phase checkbox did not flip, stopping`
562
+ );
563
+ break;
564
+ }
565
+ if (k) {
566
+ c(b, `[verify] phase ${A + 1} — running lint/format/test`);
567
+ const nt = await k();
568
+ if (n !== b) return;
569
+ if (!nt) {
570
+ Z++, c(b, `[fail] phase ${A + 1} — project checks failed, stopping`);
571
+ break;
572
+ }
573
+ }
574
+ st++, r && (c(b, `[commit] phase ${A + 1} — ${tt.text}`), await r(o, tt, A));
459
575
  }
460
- if (n !== k) return;
461
- if (k.status === "stopping") {
462
- $(k, "done");
576
+ if (n !== b) return;
577
+ if (b.status === "stopping") {
578
+ d(b, "done");
463
579
  return;
464
580
  }
465
- m(k, `Audit complete — ${pt} audited, ${X} skipped, ${ut} failed`), $(k, ut > 0 ? "error" : "done");
466
- } catch (F) {
467
- n === k && (m(k, `Batch audit failed: ${F.message}`), $(k, "error"));
581
+ if (Z > 0)
582
+ c(b, `Run stopped after ${st} phase(s) completed, 1 failed`), d(b, "error");
583
+ else {
584
+ if (c(b, `All ${st} phase(s) completed`), s)
585
+ try {
586
+ c(b, "[review] setting plan status to review"), await s(o);
587
+ } catch (tt) {
588
+ c(
589
+ b,
590
+ `Warning: could not set plan status to review: ${tt.message}`
591
+ );
592
+ }
593
+ d(b, "done");
594
+ }
595
+ } catch (C) {
596
+ n === b && (c(b, `Run all phases failed: ${C.message}`), d(b, "error"));
468
597
  }
469
598
  })(), { ok: !0 };
470
599
  }
471
- const f = 6e4, S = 10 * 1024 * 1024;
472
- function b(s) {
473
- if (j())
600
+ const M = 6e4, B = 10 * 1024 * 1024;
601
+ function J(o) {
602
+ if (g())
474
603
  return Promise.reject(new Error("An agent task is already running"));
475
- if (Buffer.byteLength(s, "utf-8") > S)
604
+ if (Buffer.byteLength(o, "utf-8") > B)
476
605
  return Promise.reject(new Error("Commit suggestion prompt exceeds the 10MB stdin limit"));
477
- const w = Nt(e), {
478
- id: C,
479
- adapter: O,
480
- model: A,
481
- effort: H
606
+ const k = Pt(t), {
607
+ id: v,
608
+ adapter: N,
609
+ model: X,
610
+ effort: G
482
611
  } = St({
483
- defaultAgents: w,
612
+ defaultAgents: k,
484
613
  taskKind: "commit-suggest"
485
- }), k = C === "claude-code", W = k ? ["-p", "--output-format", "json"] : ["run", "--format", "json"];
486
- return A && W.push(k ? "--model" : "-m", A), H && W.push(k ? "--effort" : "--variant", H), new Promise((z, F) => {
487
- var gt, Y, mt, _, G;
488
- const K = V(O.command, W, {
489
- cwd: e,
614
+ }), x = v === "claude-code", W = x ? ["-p", "--output-format", "json"] : ["run", "--format", "json"];
615
+ return X && W.push(x ? "--model" : "-m", X), G && W.push(x ? "--effort" : "--variant", G), new Promise((Q, b) => {
616
+ var U, et, $t, H, V;
617
+ const K = it(N.command, W, {
618
+ cwd: t,
490
619
  stdio: ["pipe", "pipe", "pipe"]
491
- }), R = {
620
+ }), C = {
492
621
  taskKind: "commit-suggest",
493
622
  planTitle: "Suggest commit message",
494
623
  status: "starting",
495
- agentId: C,
496
- adapter: O,
624
+ agentId: v,
625
+ adapter: N,
497
626
  proc: K,
498
627
  lines: []
499
628
  };
500
- n = R, $(R, "running");
501
- let pt = !1;
502
- const X = (L) => {
503
- pt || (pt = !0, clearTimeout(ut), L());
504
- }, ut = setTimeout(() => {
505
- X(() => {
506
- n === R && (m(R, "Commit suggestion timed out"), $(R, "error"), K.killed || K.kill("SIGTERM")), F(new Error("Commit suggestion timed out"));
629
+ n = C, d(C, "running");
630
+ let st = !1;
631
+ const Z = (L) => {
632
+ st || (st = !0, clearTimeout(tt), L());
633
+ }, tt = setTimeout(() => {
634
+ Z(() => {
635
+ n === C && (c(C, "Commit suggestion timed out"), d(C, "error"), K.killed || K.kill("SIGTERM")), b(new Error("Commit suggestion timed out"));
507
636
  });
508
- }, f);
509
- (gt = K.stdin) == null || gt.on("error", () => {
510
- }), (Y = K.stdin) == null || Y.write(s), (mt = K.stdin) == null || mt.end();
511
- let D = "", ot = "";
512
- (_ = K.stdout) == null || _.on("data", (L) => {
513
- D += L.toString();
514
- }), (G = K.stderr) == null || G.on("data", (L) => {
515
- ot += L.toString();
637
+ }, M);
638
+ (U = K.stdin) == null || U.on("error", () => {
639
+ }), (et = K.stdin) == null || et.write(o), ($t = K.stdin) == null || $t.end();
640
+ let A = "", ct = "";
641
+ (H = K.stdout) == null || H.on("data", (L) => {
642
+ A += L.toString();
643
+ }), (V = K.stderr) == null || V.on("data", (L) => {
644
+ ct += L.toString();
516
645
  }), K.on("close", (L) => {
517
- n === R && X(() => {
518
- if (R.status === "stopping") {
519
- $(R, "done"), F(new Error("Stopped"));
646
+ n === C && Z(() => {
647
+ if (C.status === "stopping") {
648
+ d(C, "done"), b(new Error("Stopped"));
520
649
  return;
521
650
  }
522
651
  if (L === 0) {
523
- $(R, "done");
524
- const rt = k ? D : D.split(`
525
- `).map((Gt) => {
526
- var At;
652
+ d(C, "done");
653
+ const nt = x ? A : A.split(`
654
+ `).map((kt) => {
655
+ var dt;
527
656
  try {
528
- const tt = JSON.parse(Gt);
529
- if ((tt == null ? void 0 : tt.type) === "text" && ((At = tt == null ? void 0 : tt.part) != null && At.text)) return tt.part.text;
657
+ const pt = JSON.parse(kt);
658
+ if ((pt == null ? void 0 : pt.type) === "text" && ((dt = pt == null ? void 0 : pt.part) != null && dt.text)) return pt.part.text;
530
659
  } catch {
531
660
  }
532
661
  return null;
533
662
  }).filter(Boolean).join(`
534
663
  `);
535
- z(rt);
664
+ Q(nt);
536
665
  } else {
537
- const rt = ot || `${O.command} exited with code ${L}`;
538
- m(R, rt), $(R, "error"), F(new Error(rt));
666
+ const nt = ct || `${N.command} exited with code ${L}`;
667
+ c(C, nt), d(C, "error"), b(new Error(nt));
539
668
  }
540
669
  });
541
670
  }), K.on("error", (L) => {
542
- n === R && X(() => {
543
- m(R, `Failed to spawn agent: ${L.message}`), $(R, "error"), F(L);
671
+ n === C && Z(() => {
672
+ c(C, `Failed to spawn agent: ${L.message}`), d(C, "error"), b(L);
544
673
  });
545
674
  });
546
675
  });
547
676
  }
548
- function I() {
677
+ function _() {
549
678
  if (!n)
550
679
  return { ok: !1, error: "No agent task running" };
551
- const s = n;
552
- return $(s, "stopping"), s.proc.killed || s.proc.kill("SIGTERM"), setTimeout(() => {
553
- n === s && s.status === "stopping" && s.proc.kill("SIGKILL");
680
+ const o = n;
681
+ return d(o, "stopping"), o.proc.killed || o.proc.kill("SIGTERM"), setTimeout(() => {
682
+ n === o && o.status === "stopping" && o.proc.kill("SIGKILL");
554
683
  }, 5e3), { ok: !0 };
555
684
  }
556
- function v() {
685
+ function R() {
557
686
  return n ? {
558
687
  status: n.status,
559
688
  taskKind: n.taskKind,
@@ -566,1161 +695,1464 @@ function Ce(e, i = () => {
566
695
  } : null;
567
696
  }
568
697
  return {
569
- start: d,
570
- startForPlan: y,
571
- startForIdea: l,
572
- startForIdeaExtend: T,
573
- startBatchAudit: o,
574
- runCommitSuggest: b,
575
- stop: I,
576
- getStatus: v,
577
- subscribe(s) {
578
- p.add(s), s.on("close", () => p.delete(s));
698
+ start: F,
699
+ startForPlan: D,
700
+ startForIdea: S,
701
+ startForIdeaExtend: m,
702
+ startBatchAudit: T,
703
+ startRunAllPhases: O,
704
+ startSync: w,
705
+ runCommitSuggest: J,
706
+ stop: _,
707
+ getStatus: R,
708
+ subscribe(o) {
709
+ e.add(o), o.on("close", () => e.delete(o));
579
710
  },
580
711
  killCurrent() {
581
712
  n != null && n.proc && !n.proc.killed && n.proc.kill();
582
713
  }
583
714
  };
584
715
  }
585
- function $e(e, i) {
586
- return `You are writing a single git commit message for the diff below. Do not use any tools, do not read or edit any files — base your answer only on the diff text given.
587
-
588
- Follow this repo's commit convention: \`type(scope): Description\`, where type is one of feat|fix|chore|docs|refactor and scope is a subsystem area from this fixed list: core, cli, app, server, agent, plans, ideas, docs, settings, stack, ui, ci, config, deps, repo. Pick the area the diff most affects. Keep the whole title under 100 characters and do not end it with a period.${i ? `
589
- This work belongs to plan ${i}. Do NOT put the plan id in the scope — instead end the message body with a \`Refs: ${i}\` line as its final line.` : ""}
590
-
591
- Respond with ONLY a single JSON object, no prose, no code fences, no markdown — exactly this shape:
592
- {"title": "type(scope): Description", "message": "${i ? `longer body describing what changed and why, ending with a \`Refs: ${i}\` line` : "optional longer body describing what changed and why, or an empty string if the title alone is clear enough"}"}
593
-
594
- Diff:
595
- ${e}`;
716
+ async function j(t) {
717
+ try {
718
+ return await at(t, "utf-8");
719
+ } catch (a) {
720
+ if (a.code === "ENOENT") return "";
721
+ throw a;
722
+ }
596
723
  }
597
- async function Te(e, i, a) {
598
- if (!e.trim())
599
- throw new Error("No changes to summarize — select at least one file first");
600
- const p = $e(e, i), n = await a(p);
601
- let h = n;
724
+ async function wt(t) {
602
725
  try {
603
- const t = JSON.parse(n);
604
- typeof t.result == "string" && (h = t.result);
726
+ return await ht(t), !0;
605
727
  } catch {
728
+ return !1;
606
729
  }
607
- const m = h.match(/\{[\s\S]*\}/);
608
- if (!m) throw new Error("Agent did not return a parseable commit message");
609
- const $ = JSON.parse(m[0]);
610
- if (!$.title) throw new Error("Agent response missing a title");
611
- let u = $.message ?? "";
612
- return i && !/Refs:\s*\S/.test(u) && (u = u ? `${u}
730
+ }
731
+ const P = (t, a) => $(t, "papercamp", a);
732
+ function Nt(t, a = {}) {
733
+ return {
734
+ id: t.id ?? "",
735
+ title: t.title,
736
+ kind: t.kind ?? "feat",
737
+ status: t.status,
738
+ idea: t.idea,
739
+ agent: t.agent,
740
+ created: t.created,
741
+ updated: t.updated,
742
+ audited: t.audited,
743
+ tags: t.tags,
744
+ body: t.body,
745
+ phases: t.phases,
746
+ log: t.log,
747
+ clarifications: t.clarifications,
748
+ ...a
749
+ };
750
+ }
751
+ async function Rt(t, a) {
752
+ await q(t, `${xt(a)}
753
+ `, "utf-8");
754
+ }
755
+ async function Dt(t) {
756
+ const a = P(t, "plans"), i = P(t, "ideas"), [r, s] = await Promise.all([
757
+ gt(a, P(t, "plans.md")),
758
+ Ct(i, P(t, "ideas.md"))
759
+ ]), e = se(s.entries, r.entries);
760
+ await ut(a, { recursive: !0 }), await ut(i, { recursive: !0 }), await Promise.all([
761
+ q($(a, "index.md"), Lt(r.entries)),
762
+ q($(i, "index.md"), Zt(e))
763
+ ]);
764
+ }
765
+ async function rt(t, a, i) {
766
+ const r = a.getFeatureBranchPlanId();
767
+ if (i && r === i || !r) return null;
768
+ if (await a.getBranchHygieneStatus() === "stale-merged")
769
+ return "You're on a merged branch — switch to main before starting another plan";
770
+ const e = P(t, "plans"), { entries: n } = await gt(e, P(t, "plans.md")), l = n.find((c) => c.id === r);
771
+ return !l || l.status === "done" || l.status === "dropped" ? null : `Finish \`${r}\` — ${l.title} — before starting another plan`;
772
+ }
773
+ function Fe(t, a) {
774
+ async function i(n) {
775
+ const l = P(t, "progress.md"), d = `## ${ot()}`, u = await j(l);
776
+ if (u.startsWith(`${d}
777
+ `))
778
+ await q(
779
+ l,
780
+ `${d}
781
+ - ${n}
782
+ ${u.slice(d.length + 1)}`,
783
+ "utf-8"
784
+ );
785
+ else {
786
+ const f = u.trimEnd(), h = f ? `${d}
787
+ - ${n}
613
788
 
614
- Refs: ${i}` : `Refs: ${i}`), { title: $.title, message: u };
789
+ ${f}
790
+ ` : `${d}
791
+ - ${n}
792
+ `;
793
+ await q(l, h, "utf-8");
794
+ }
795
+ }
796
+ async function r(n, l) {
797
+ const c = P(t, "plans"), d = $(c, `${n}.md`), u = $(c, "archive", `${n}.md`), f = await wt(d) ? d : await wt(u) ? u : null;
798
+ if (!f) return;
799
+ const h = await j(f);
800
+ if (!h) return;
801
+ const y = mt(h);
802
+ if (y.entries.length === 0) return;
803
+ const g = y.entries[0];
804
+ if (await Rt(f, Nt(g, { id: n, audited: ot() })), l > 0) {
805
+ const E = `gap phase${l === 1 ? "" : "s"}`;
806
+ await i(
807
+ `Batch audit of ${n} (${g.title}) — ${l} ${E} appended`
808
+ );
809
+ }
810
+ }
811
+ async function s(n, l) {
812
+ var f;
813
+ const c = ((f = n.tags) == null ? void 0 : f[0]) ?? "plans", d = `${n.kind ?? "feat"}(${c}): ${l.text}`, u = n.id ? `Refs: ${n.id}` : void 0;
814
+ await a.stageAll(), await a.commit([], d, u);
815
+ }
816
+ async function e(n) {
817
+ var g;
818
+ if (!n.id) return;
819
+ const l = P(t, "plans"), c = $(l, `${n.id}.md`), d = await j(c);
820
+ if (!d) return;
821
+ const u = mt(d);
822
+ if (u.entries.length === 0) return;
823
+ const f = u.entries[0];
824
+ if (f.status === "review" || f.status === "done" || f.status === "dropped") return;
825
+ await Rt(
826
+ c,
827
+ Nt(f, {
828
+ id: f.id ?? n.id,
829
+ status: "review",
830
+ updated: ot()
831
+ })
832
+ );
833
+ const h = ((g = n.tags) == null ? void 0 : g[0]) ?? "plans", y = n.id ? `Refs: ${n.id}` : void 0;
834
+ await a.stageAll(), await a.commit([], `${f.kind ?? "feat"}(${h}): mark ${n.id} review`, y);
835
+ }
836
+ return { stampAuditDate: r, commitPhase: s, setRunReview: e };
615
837
  }
616
- const Lt = [/(^|\/)\.env(\.|$)/i, /\.(pem|key|p12|crt)$/i], bt = (e) => `:(literal)${e}`;
617
- function Oe(e) {
838
+ const Ut = [/(^|\/)\.env(\.|$)/i, /\.(pem|key|p12|crt)$/i], Ft = (t) => `:(literal)${t}`;
839
+ function _e(t, a = {}) {
618
840
  const i = /* @__PURE__ */ new Set();
619
- function a(o) {
620
- const f = `data: ${JSON.stringify(o)}
841
+ function r(w) {
842
+ const I = `data: ${JSON.stringify(w)}
621
843
 
622
844
  `;
623
- for (const S of i)
845
+ for (const T of i)
624
846
  try {
625
- S.write(f);
847
+ T.write(I);
626
848
  } catch {
627
- i.delete(S);
849
+ i.delete(T);
628
850
  }
629
851
  }
630
- function p(o) {
631
- const f = [];
632
- for (const S of o.split(`
852
+ function s(w) {
853
+ const I = [];
854
+ for (const T of w.split(`
633
855
  `)) {
634
- if (!S.trim()) continue;
635
- const b = S[0] ?? " ", I = S[1] ?? " ", v = S.slice(3), s = v.split(" -> "), w = s.pop() ?? v;
636
- f.push({
637
- path: w,
638
- status: `${b}${I}`,
639
- staged: b !== " " && b !== "?",
640
- renameSource: (b === "R" || b === "C") && s.length > 0 ? s[0] : void 0
856
+ if (!T.trim()) continue;
857
+ const O = T[0] ?? " ", M = T[1] ?? " ", B = T.slice(3), J = B.split(" -> "), _ = J.pop() ?? B;
858
+ I.push({
859
+ path: _,
860
+ status: `${O}${M}`,
861
+ staged: O !== " " && O !== "?",
862
+ renameSource: (O === "R" || O === "C") && J.length > 0 ? J[0] : void 0
641
863
  });
642
864
  }
643
- return f;
865
+ return I;
644
866
  }
645
- function n(o) {
646
- return new Promise((f, S) => {
647
- var s, w;
648
- const b = V("git", o, {
649
- cwd: e,
867
+ function e(w) {
868
+ return new Promise((I, T) => {
869
+ var J, _;
870
+ const O = it("git", ["-c", "core.quotepath=off", ...w], {
871
+ cwd: t,
650
872
  stdio: ["ignore", "pipe", "pipe"]
651
873
  });
652
- let I = "", v = "";
653
- (s = b.stdout) == null || s.on("data", (C) => {
654
- I += C.toString();
655
- }), (w = b.stderr) == null || w.on("data", (C) => {
656
- v += C.toString();
657
- }), b.on("close", (C) => {
658
- C === 0 ? f(I) : S(new Error(v || `git ${o[0]} exited with code ${C}`));
659
- }), b.on("error", S);
874
+ let M = "", B = "";
875
+ (J = O.stdout) == null || J.on("data", (R) => {
876
+ M += R.toString();
877
+ }), (_ = O.stderr) == null || _.on("data", (R) => {
878
+ B += R.toString();
879
+ }), O.on("close", (R) => {
880
+ R === 0 ? I(M) : T(new Error(B || `git ${w[0]} exited with code ${R}`));
881
+ }), O.on("error", T);
660
882
  });
661
883
  }
662
- function h() {
663
- return n(["status", "--porcelain=v1"]).then(p);
884
+ function n() {
885
+ return e(["status", "--porcelain=v1"]).then(s);
664
886
  }
665
- async function m(o, f, S) {
666
- if (o.length > 0) {
667
- const I = await h(), v = new Map(I.map((w) => [w.path, w.status])), s = o.filter((w) => {
668
- const C = v.get(w);
669
- return C === void 0 || C[1] !== " ";
887
+ async function l(w, I, T) {
888
+ let O = /* @__PURE__ */ new Map();
889
+ if (w.length > 0) {
890
+ const B = await n(), J = new Map(B.map((R) => [R.path, R.status]));
891
+ O = new Map(
892
+ B.filter((R) => !!R.renameSource).map((R) => [R.path, R.renameSource])
893
+ );
894
+ const _ = w.filter((R) => {
895
+ const o = J.get(R);
896
+ return o === void 0 || o[1] !== " ";
670
897
  });
671
- s.length > 0 && await n(["add", "--", ...s.map(bt)]);
898
+ _.length > 0 && await e(["add", "--", ..._.map(Ft)]);
672
899
  }
673
- const b = ["commit", "-m", f];
674
- S && b.push("-m", S), o.length > 0 && b.push("--", ...o.map(bt)), await n(b);
900
+ const M = ["commit", "-m", I];
901
+ if (T && M.push("-m", T), w.length > 0) {
902
+ const B = w.flatMap((J) => {
903
+ const _ = O.get(J);
904
+ return _ ? [J, _] : [J];
905
+ });
906
+ M.push("--", ...B.map(Ft));
907
+ }
908
+ await e(M);
675
909
  }
676
- function $(o) {
677
- if (!o.kind || !o.id) return;
678
- const f = o.kind.toLowerCase(), S = o.id.toLowerCase(), b = o.title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, ""), I = `${f}/${S}-${b}`, v = ht("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd: e });
679
- if (v.status !== 0)
910
+ function c(w) {
911
+ if (!w.kind || !w.id) return;
912
+ const I = w.kind.toLowerCase(), T = w.id.toLowerCase(), O = w.title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, ""), M = `${I}/${T}-${O}`, B = It("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd: t });
913
+ if (B.status !== 0)
680
914
  throw new Error(
681
- v.stderr.toString().trim() || "Unable to read current git branch"
915
+ B.stderr.toString().trim() || "Unable to read current git branch"
682
916
  );
683
- if (v.stdout.toString().trim() === I) return;
684
- const w = ht("git", ["checkout", "-b", I, "main"], { cwd: e });
685
- if (w.status !== 0) {
686
- const C = ht("git", ["checkout", I], { cwd: e });
687
- if (C.status !== 0)
917
+ if (B.stdout.toString().trim() === M) return;
918
+ const _ = It("git", ["checkout", "-b", M, "main"], { cwd: t });
919
+ if (_.status !== 0) {
920
+ const R = It("git", ["checkout", M], { cwd: t });
921
+ if (R.status !== 0)
688
922
  throw new Error(
689
- C.stderr.toString().trim() || w.stderr.toString().trim() || `Unable to check out ${I}`
923
+ R.stderr.toString().trim() || _.stderr.toString().trim() || `Unable to check out ${M}`
690
924
  );
691
925
  }
692
926
  }
693
- async function u() {
927
+ async function d() {
694
928
  try {
695
- await h(), a({
929
+ await n(), r({
696
930
  message: "Working tree status updated",
697
931
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
698
932
  });
699
933
  } catch {
700
934
  }
701
935
  }
702
- const t = N(e, ".git");
703
- let J = null;
704
- try {
705
- Ct(t, { recursive: !0 }, (o, f) => {
706
- f === "index" && (J && clearTimeout(J), J = setTimeout(u, 500));
707
- });
708
- } catch {
709
- }
710
- const g = N(e, "src");
711
- let j = null;
712
- try {
713
- Ct(g, { recursive: !0 }, () => {
714
- j && clearTimeout(j), j = setTimeout(u, 500);
715
- });
716
- } catch {
936
+ if (a.watch !== !1) {
937
+ const w = $(t, ".git");
938
+ let I = null;
939
+ try {
940
+ Ot(w, { recursive: !0 }, (M, B) => {
941
+ B === "index" && (I && clearTimeout(I), I = setTimeout(d, 500));
942
+ });
943
+ } catch {
944
+ }
945
+ const T = $(t, "src");
946
+ let O = null;
947
+ try {
948
+ Ot(T, { recursive: !0 }, () => {
949
+ O && clearTimeout(O), O = setTimeout(d, 500);
950
+ });
951
+ } catch {
952
+ }
717
953
  }
718
- async function r() {
954
+ async function u() {
719
955
  try {
720
- return await n(["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"]), !0;
956
+ return await e(["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"]), !0;
721
957
  } catch {
722
958
  return !1;
723
959
  }
724
960
  }
725
- async function d() {
961
+ async function f() {
726
962
  try {
727
- const o = await r() ? ["rev-list", "--count", "@{u}..HEAD"] : ["rev-list", "--count", "HEAD", "--not", "--remotes"], f = await n(o);
728
- return Number.parseInt(f.trim(), 10) || 0;
963
+ const w = await u() ? ["rev-list", "--count", "@{u}..HEAD"] : ["rev-list", "--count", "HEAD", "--not", "--remotes"], I = await e(w);
964
+ return Number.parseInt(I.trim(), 10) || 0;
729
965
  } catch {
730
966
  return 0;
731
967
  }
732
968
  }
733
- async function y() {
734
- await r() ? await n(["push"]) : await n(["push", "--set-upstream", "origin", l()]);
969
+ async function h() {
970
+ await u() ? await e(["push"]) : await e(["push", "--set-upstream", "origin", y()]);
735
971
  }
736
- function l() {
737
- return ht("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd: e }).stdout.toString().trim();
972
+ function y() {
973
+ return It("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd: t }).stdout.toString().trim();
738
974
  }
739
- function T() {
740
- const f = l().match(/^[a-z]+\/([a-z]+-\d+)-/);
741
- return f ? f[1].toUpperCase() : null;
742
- }
743
- async function c(o, f = 12e3) {
744
- if (o.length === 0) return "";
745
- const S = o.find((O) => Lt.some((A) => A.test(O)));
746
- if (S)
747
- throw new Error(`Refusing to send sensitive file "${S}" to commit suggestion`);
748
- const b = await h(), I = new Set(b.filter((O) => O.status === "??").map((O) => O.path)), v = new Map(
749
- b.filter((O) => !!O.renameSource).map((O) => [O.path, O.renameSource])
750
- ), s = o.filter((O) => {
751
- if (I.has(O)) return !1;
752
- const A = v.get(O);
753
- return !(A && Lt.some((H) => H.test(A)));
754
- }), w = [];
755
- if (s.length > 0) {
756
- const O = await n([
975
+ async function g() {
976
+ const w = y();
977
+ if (w === "main" || w === "master") return !1;
978
+ try {
979
+ return (await e(["branch", "--merged", "main"])).split(`
980
+ `).some((T) => T.trim().replace(/^\*\s+/, "") === w);
981
+ } catch {
982
+ return !1;
983
+ }
984
+ }
985
+ async function E() {
986
+ const w = y(), T = (await n()).length > 0;
987
+ return w === "main" || w === "master" ? T ? "dirty" : "clean-on-main" : await g() && await e(["rev-list", "--count", "HEAD..main"]).then((M) => Number.parseInt(M.trim(), 10) || 0).catch(() => 0) > 0 ? "stale-merged" : T ? "dirty" : "fine";
988
+ }
989
+ function F() {
990
+ const I = y().match(/^[a-z]+\/([a-z]+-\d+)-/);
991
+ return I ? I[1].toUpperCase() : null;
992
+ }
993
+ async function D(w, I = 12e3) {
994
+ if (w.length === 0) return "";
995
+ const T = w.find((o) => Ut.some((k) => k.test(o)));
996
+ if (T)
997
+ throw new Error(`Refusing to send sensitive file "${T}" to commit suggestion`);
998
+ const O = await n(), M = new Set(O.filter((o) => o.status === "??").map((o) => o.path)), B = new Map(
999
+ O.filter((o) => !!o.renameSource).map((o) => [o.path, o.renameSource])
1000
+ ), J = w.filter((o) => {
1001
+ if (M.has(o)) return !1;
1002
+ const k = B.get(o);
1003
+ return !(k && Ut.some((v) => v.test(k)));
1004
+ }), _ = [];
1005
+ if (J.length > 0) {
1006
+ const o = await e([
757
1007
  "diff",
758
1008
  "HEAD",
759
1009
  "--",
760
- ...s.map(bt)
1010
+ ...J.map(Ft)
761
1011
  ]).catch(() => "");
762
- O && w.push(O);
763
- }
764
- for (const O of o.filter((A) => I.has(A))) {
765
- const A = N(e, O), H = await Ut(A).catch(() => null);
766
- if (!H) continue;
767
- if (H.isSymbolicLink()) {
768
- w.push(`--- /dev/null
769
- +++ b/${O}
1012
+ o && _.push(o);
1013
+ }
1014
+ for (const o of w.filter((k) => M.has(k))) {
1015
+ const k = $(t, o), v = await ce(k).catch(() => null);
1016
+ if (!v) continue;
1017
+ if (v.isSymbolicLink()) {
1018
+ _.push(`--- /dev/null
1019
+ +++ b/${o}
770
1020
  (new file omitted: symlink)`);
771
1021
  continue;
772
1022
  }
773
- if (H.size > f) {
774
- w.push(`--- /dev/null
775
- +++ b/${O}
1023
+ if (v.size > I) {
1024
+ _.push(`--- /dev/null
1025
+ +++ b/${o}
776
1026
  (new file omitted: exceeds diff size cap)`);
777
1027
  continue;
778
1028
  }
779
- const k = await U(A, "utf-8").catch(() => "");
780
- k && w.push(`--- /dev/null
781
- +++ b/${O}
1029
+ const N = await at(k, "utf-8").catch(() => "");
1030
+ N && _.push(`--- /dev/null
1031
+ +++ b/${o}
782
1032
  (new file)
783
- ${k}`);
1033
+ ${N}`);
784
1034
  }
785
- const C = w.join(`
1035
+ const R = _.join(`
786
1036
 
787
1037
  `);
788
- return C.length > f ? `${C.slice(0, f)}
789
- ... (truncated)` : C;
790
- }
791
- return {
792
- async getStatus() {
793
- return h();
794
- },
795
- getCurrentBranch: l,
796
- commit: m,
797
- diff: c,
798
- ensureBranch: $,
799
- getFeatureBranchPlanId: T,
800
- getAheadCount: d,
801
- push: y,
802
- subscribe(o) {
803
- i.add(o), o.on("close", () => i.delete(o));
804
- }
805
- };
806
- }
807
- const Ne = {
808
- lint: "npx biome lint .",
809
- format: "npx biome format .",
810
- test: "npx vitest run"
811
- };
812
- function be(e) {
813
- const i = /* @__PURE__ */ new Set(), a = {
814
- lint: { status: "stale", lastRun: null, output: "" },
815
- format: { status: "stale", lastRun: null, output: "" },
816
- test: { status: "stale", lastRun: null, output: "" }
817
- }, p = /* @__PURE__ */ new Set(), n = /* @__PURE__ */ new Set();
818
- function h(g) {
819
- const j = `data: ${JSON.stringify(g)}
820
-
821
- `;
822
- for (const r of i)
823
- try {
824
- r.write(j);
825
- } catch {
826
- i.delete(r);
827
- }
828
- }
829
- function m(g, j, r) {
830
- a[g] = { status: j, lastRun: (/* @__PURE__ */ new Date()).toISOString(), output: r }, h({
831
- message: `${g}: ${j}`,
832
- timestamp: a[g].lastRun
833
- }), j !== "running" && n.has(g) && (n.delete(g), $(g));
834
- }
835
- function $(g) {
836
- var l, T;
837
- if (p.has(g)) {
838
- n.add(g);
839
- return;
840
- }
841
- p.add(g), m(g, "running", "");
842
- const j = Ne[g], r = V(j, {
843
- cwd: e,
844
- stdio: ["ignore", "pipe", "pipe"],
845
- shell: !0
846
- });
847
- let d = "", y = "";
848
- (l = r.stdout) == null || l.on("data", (c) => {
849
- d += c.toString();
850
- }), (T = r.stderr) == null || T.on("data", (c) => {
851
- y += c.toString();
852
- }), r.on("close", (c) => {
853
- p.delete(g);
854
- const o = d + y;
855
- c === 0 ? m(g, "pass", o) : m(g, "fail", o);
856
- }), r.on("error", (c) => {
857
- p.delete(g), m(g, "fail", `Failed to spawn process: ${c.message}`);
858
- });
1038
+ return R.length > I ? `${R.slice(0, I)}
1039
+ ... (truncated)` : R;
859
1040
  }
860
- function u() {
861
- if (p.has("lint") || p.has("format")) return;
862
- m("lint", "running", "Applying automatic fixes…"), m("format", "running", "Applying automatic fixes…");
863
- const g = V("npx biome check . --write", {
864
- cwd: e,
865
- stdio: ["ignore", "pipe", "pipe"],
866
- shell: !0
867
- });
868
- g.on("close", () => {
869
- $("lint"), $("format");
870
- }), g.on("error", (j) => {
871
- const r = `Failed to spawn fix process: ${j.message}`;
872
- m("lint", "fail", r), m("format", "fail", r);
873
- });
1041
+ async function S() {
1042
+ await e(["add", "-A"]);
874
1043
  }
875
- const t = N(e, "src");
876
- let J = null;
877
- try {
878
- Ct(t, { recursive: !0 }, () => {
879
- J && clearTimeout(J), J = setTimeout(() => {
880
- $("lint"), $("format");
881
- }, 1e3);
882
- });
883
- } catch {
1044
+ async function m() {
1045
+ await e(["checkout", "main"]), await e(["fetch", "--prune"]), await e(["merge", "--ff-only", "origin/main"]);
884
1046
  }
885
1047
  return {
886
- getStatus() {
887
- return {
888
- lint: { ...a.lint },
889
- format: { ...a.format },
890
- test: { ...a.test }
891
- };
1048
+ async getStatus() {
1049
+ return n();
892
1050
  },
893
- runCheck: $,
894
- runQualityFix: u,
895
- subscribe(g) {
896
- i.add(g);
897
- for (const j of ["lint", "format", "test"]) {
898
- const r = a[j];
899
- r.status !== "stale" && g.write(
900
- `data: ${JSON.stringify({ message: `${j}: ${r.status}`, timestamp: r.lastRun })}
901
-
902
- `
903
- );
904
- }
905
- g.on("close", () => i.delete(g));
1051
+ getCurrentBranch: y,
1052
+ commit: l,
1053
+ stageAll: S,
1054
+ diff: D,
1055
+ ensureBranch: c,
1056
+ getFeatureBranchPlanId: F,
1057
+ getAheadCount: f,
1058
+ push: h,
1059
+ isMergedIntoMain: g,
1060
+ getBranchHygieneStatus: E,
1061
+ runGitSync: m,
1062
+ subscribe(w) {
1063
+ i.add(w), w.on("close", () => i.delete(w));
906
1064
  }
907
1065
  };
908
1066
  }
909
- async function P(e) {
910
- try {
911
- return await U(e, "utf-8");
912
- } catch (i) {
913
- if (i.code === "ENOENT") return "";
914
- throw i;
915
- }
916
- }
917
- async function ft(e) {
918
- try {
919
- return await it(e), !0;
920
- } catch {
921
- return !1;
922
- }
1067
+ function p(t, a, i) {
1068
+ t.statusCode = a, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify(i));
923
1069
  }
924
- async function M(e) {
925
- return new Promise((i, a) => {
926
- let p = "";
927
- e.on("data", (n) => {
928
- p += n;
929
- }), e.on("end", () => i(p)), e.on("error", a);
1070
+ async function Y(t) {
1071
+ return new Promise((a, i) => {
1072
+ let r = "";
1073
+ t.on("data", (s) => {
1074
+ r += s;
1075
+ }), t.on("end", () => a(r)), t.on("error", i);
930
1076
  });
931
1077
  }
932
- const x = (e, i) => N(e, "papercamp", i), je = [
933
- "biome.json",
934
- "tsconfig.json",
935
- "tailwind.config.ts",
936
- "vite.config.ts",
937
- "vite.app.config.ts",
938
- "postcss.config.js",
939
- "package.json"
940
- ];
941
- async function et(e, i, a) {
942
- const p = i.getFeatureBranchPlanId();
943
- if (!p || a && p === a) return null;
944
- const n = x(e, "plans"), { entries: h } = await ct(n, x(e, "plans.md")), m = h.find(($) => $.id === p);
945
- return !m || m.status === "done" || m.status === "dropped" ? null : `Finish \`${p}\` — ${m.title} — before starting another plan`;
1078
+ function vt(t) {
1079
+ return new URL(t.url ?? "", `http://${t.headers.host ?? "localhost"}`);
946
1080
  }
947
- async function yt(e) {
948
- const i = x(e, "plans"), a = x(e, "ideas"), [p, n] = await Promise.all([
949
- ct(i, x(e, "plans.md")),
950
- Tt(a, x(e, "ideas.md"))
951
- ]), h = Kt(n.entries, p.entries);
952
- await q(i, { recursive: !0 }), await q(a, { recursive: !0 }), await Promise.all([
953
- E(N(i, "index.md"), Ht(p.entries)),
954
- E(N(a, "index.md"), _t(h))
955
- ]);
1081
+ async function Me(t, a) {
1082
+ const i = $(P(t, "plans"), `${a}.md`);
1083
+ if (await wt(i)) return i;
1084
+ const r = $(P(t, "plans"), "archive", `${a}.md`);
1085
+ return await wt(r) ? r : null;
956
1086
  }
957
- const ke = [
958
- {
959
- path: "/api/package-name",
960
- handler: async (e) => {
961
- const i = await P(N(e, "package.json"));
962
- if (!i) return null;
963
- try {
964
- return JSON.parse(i).name ?? null;
965
- } catch {
966
- return null;
967
- }
968
- }
969
- },
970
- {
971
- path: "/api/plans",
972
- handler: async (e) => ct(x(e, "plans"), x(e, "plans.md"))
973
- },
974
- {
975
- path: "/api/progress",
976
- handler: async (e) => ({
977
- entries: ne(await P(x(e, "progress.md")))
978
- })
979
- },
980
- {
981
- path: "/api/decisions",
982
- handler: async (e) => Dt(await P(x(e, "decisions.md")))
983
- },
984
- {
985
- path: "/api/open-questions",
986
- handler: async (e) => It(await P(x(e, "open-questions.md")))
987
- },
988
- {
989
- path: "/api/ideas",
990
- handler: async (e) => Tt(x(e, "ideas"), x(e, "ideas.md"))
991
- },
992
- {
993
- path: "/api/consistency",
994
- handler: async (e) => {
995
- const [i, a, p] = await Promise.all([
996
- P(x(e, "decisions.md")),
997
- P(x(e, "open-questions.md")),
998
- ct(x(e, "plans"), x(e, "plans.md"))
999
- ]), n = Dt(i), h = It(a);
1000
- return ae(n.entries, h.entries, p.entries);
1001
- }
1002
- },
1003
- {
1004
- path: "/api/config",
1005
- handler: async (e) => {
1006
- const i = await P(N(e, "papercamp", "config.json"));
1007
- if (!i) return null;
1008
- const a = JSON.parse(i);
1009
- return a != null && a.defaultAgents && (a.defaultAgents = {
1010
- phase: B(a.defaultAgents.phase),
1011
- planDraft: B(a.defaultAgents.planDraft),
1012
- ideaExtend: B(a.defaultAgents.ideaExtend),
1013
- commitSuggest: B(a.defaultAgents.commitSuggest)
1014
- }), a;
1015
- }
1016
- },
1017
- {
1018
- path: "/api/docs",
1019
- handler: async (e) => {
1020
- const i = ["MAIN.md", "README.md", "CHANGELOG.md", "LICENSE"], a = [];
1021
- for (const p of i) {
1022
- const n = await P(N(e, p));
1023
- n && a.push({ name: p, content: n });
1024
- }
1025
- return { files: a };
1026
- }
1027
- },
1028
- {
1029
- path: "/api/configs",
1030
- handler: async (e) => {
1031
- const i = [
1032
- "biome.json",
1033
- "tsconfig.json",
1034
- "tailwind.config.ts",
1035
- "vite.config.ts",
1036
- "vite.app.config.ts",
1037
- "postcss.config.js",
1038
- "package.json"
1039
- ], a = [];
1040
- for (const p of i)
1041
- await P(N(e, p)) && a.push(p);
1042
- return { files: a };
1043
- }
1044
- }
1087
+ const je = [
1088
+ ["`plans.md`", "`papercamp/plans/`"],
1089
+ ["`ideas.md`", "`papercamp/ideas/`"],
1090
+ [".paper-camp/", "papercamp/"],
1091
+ ["FocusTaskItem", "FocusPhaseItem"],
1092
+ ["TaskItem", "PhaseItem"],
1093
+ ["taskProgress", "phaseProgress"],
1094
+ ["taskPercentage", "phasePercentage"]
1045
1095
  ];
1046
- function ve(e) {
1047
- const i = ye(e), a = Oe(e), p = be(e);
1048
- async function n(u) {
1049
- const t = x(e, "progress.md"), g = `## ${Z()}`, j = await P(t);
1050
- if (j.startsWith(`${g}
1051
- `))
1052
- await E(
1053
- t,
1054
- `${g}
1055
- - ${u}
1056
- ${j.slice(g.length + 1)}`,
1057
- "utf-8"
1058
- );
1059
- else {
1060
- const r = j.trimEnd(), d = r ? `${g}
1061
- - ${u}
1062
-
1063
- ${r}
1064
- ` : `${g}
1065
- - ${u}
1066
- `;
1067
- await E(t, d, "utf-8");
1096
+ function Le(t) {
1097
+ const a = t.startsWith(`---
1098
+ `) ? t.indexOf(`
1099
+ ---`, 4) + 4 : -1, i = a >= 4 ? t.slice(0, a) : "", s = (a >= 4 ? t.slice(a) : t).split(`
1100
+ `);
1101
+ let e = !1, n = !1;
1102
+ for (let l = 0; l < s.length; l++) {
1103
+ const c = s[l];
1104
+ if (/^- \[x\]/.test(c)) {
1105
+ n = !0;
1106
+ continue;
1068
1107
  }
1069
- }
1070
- async function h(u, t) {
1071
- const J = x(e, "plans"), g = N(J, `${u}.md`), j = N(J, "archive", `${u}.md`), r = await ft(g) ? g : await ft(j) ? j : null;
1072
- if (!r) return;
1073
- const d = await P(r);
1074
- if (!d) return;
1075
- const y = dt(d);
1076
- if (y.entries.length === 0) return;
1077
- const l = y.entries[0], T = {
1078
- id: u,
1079
- title: l.title,
1080
- kind: l.kind ?? "feat",
1081
- status: l.status,
1082
- idea: l.idea,
1083
- agent: l.agent,
1084
- created: l.created,
1085
- updated: l.updated,
1086
- audited: Z(),
1087
- tags: l.tags,
1088
- body: l.body,
1089
- phases: l.phases,
1090
- log: l.log,
1091
- clarifications: l.clarifications
1092
- };
1093
- if (await E(r, `${at(T)}
1094
- `, "utf-8"), t > 0) {
1095
- const c = `gap phase${t === 1 ? "" : "s"}`;
1096
- await n(
1097
- `Batch audit of ${u} (${l.title}) — ${t} ${c} appended`
1098
- );
1108
+ if (n) {
1109
+ if (c.trim() === "" || /^\s+\S/.test(c)) continue;
1110
+ n = !1;
1099
1111
  }
1112
+ let d = c;
1113
+ for (const [u, f] of je)
1114
+ d.includes(u) && (d = d.split(u).join(f));
1115
+ d !== c && (e = !0, s[l] = d);
1100
1116
  }
1101
- const m = Ce(e, (u) => a.ensureBranch(u), h), $ = async (u, t, J) => {
1102
- const g = (u.url ?? "").split("?")[0];
1103
- if (u.method === "DELETE" && g === "/api/plans") {
1104
- try {
1105
- const d = new URL(u.url ?? "", `http://${u.headers.host ?? "localhost"}`).searchParams.get("title");
1106
- if (!(d != null && d.trim())) {
1107
- t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "title is required" }));
1117
+ return { content: i + s.join(`
1118
+ `), changed: e };
1119
+ }
1120
+ async function At(t, a) {
1121
+ const { entries: i } = await lt(P(t, "plans")), r = i.find((e) => e.id === a);
1122
+ return r || Vt(await j(P(t, "plans.md"))).entries.find((e) => e.id === a);
1123
+ }
1124
+ async function Yt(t, a) {
1125
+ const { entries: i } = await te(P(t, "ideas")), r = i.find((e) => e.id === a);
1126
+ return r || ee(await j(P(t, "ideas.md"))).find((e) => e.id === a);
1127
+ }
1128
+ function qe({ root: t, git: a, status: i, agent: r }) {
1129
+ return [
1130
+ // GET /api/agent/status — current agent task state, if any
1131
+ {
1132
+ method: "GET",
1133
+ path: "/api/agent/status",
1134
+ handle: (s, e) => {
1135
+ p(e, 200, r.getStatus());
1136
+ }
1137
+ },
1138
+ // POST /api/agent/launch — start a headless agent on one plan phase
1139
+ {
1140
+ method: "POST",
1141
+ path: "/api/agent/launch",
1142
+ handle: async (s, e) => {
1143
+ const n = await Y(s), { planId: l, phaseIndex: c } = JSON.parse(n);
1144
+ if (!l || typeof c != "number") {
1145
+ p(e, 400, { error: "planId and phaseIndex are required" });
1108
1146
  return;
1109
1147
  }
1110
- const y = x(e, "plans"), l = d.trim(), { entries: T } = await Q(y), c = T.find((f) => f.title === l || f.id === l);
1111
- if (!(c != null && c.id)) {
1112
- t.statusCode = 404, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "plan not found in per-file storage" }));
1148
+ const d = await At(t, l);
1149
+ if (!d) {
1150
+ p(e, 404, { error: "plan not found" });
1151
+ return;
1152
+ }
1153
+ const u = await rt(t, a, d.id);
1154
+ if (u) {
1155
+ p(e, 409, { error: u });
1113
1156
  return;
1114
1157
  }
1115
- const o = N(y, `${c.id}.md`);
1116
- if (!await ft(o)) {
1117
- t.statusCode = 404, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "plan file not found" }));
1158
+ const f = r.start(d, c);
1159
+ if (!f.ok) {
1160
+ p(e, 409, { error: f.error });
1118
1161
  return;
1119
1162
  }
1120
- await Wt(o), await yt(e), t.statusCode = 200, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ ok: !0 }));
1121
- } catch (r) {
1122
- t.statusCode = 500, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: r.message }));
1163
+ p(e, 202, { ok: !0 });
1123
1164
  }
1124
- return;
1125
- }
1126
- if (u.method === "POST" && g === "/api/plans") {
1127
- try {
1128
- const r = await M(u), { title: d, content: y, kind: l } = JSON.parse(r);
1129
- if (!(d != null && d.trim())) {
1130
- t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "title is required" }));
1165
+ },
1166
+ // POST /api/agent/launch-audit — start a headless agent on a plan-scoped convergence audit
1167
+ {
1168
+ method: "POST",
1169
+ path: "/api/agent/launch-audit",
1170
+ handle: async (s, e) => {
1171
+ const n = await Y(s), { planId: l, prompt: c } = JSON.parse(n);
1172
+ if (!l || !c) {
1173
+ p(e, 400, { error: "planId and prompt are required" });
1131
1174
  return;
1132
1175
  }
1133
- const T = l && wt.includes(l) ? l : "feat", c = N(e, "papercamp", "config.json"), o = await Bt(c, T);
1134
- if (!o) {
1135
- t.statusCode = 500, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "could not assign plan ID" }));
1176
+ const d = await At(t, l);
1177
+ if (!d) {
1178
+ p(e, 404, { error: "plan not found" });
1136
1179
  return;
1137
1180
  }
1138
- const f = x(e, "plans");
1139
- await q(f, { recursive: !0 });
1140
- const S = at({
1141
- id: o,
1142
- title: d.trim(),
1143
- kind: T,
1144
- status: "idea",
1145
- created: Z(),
1146
- body: y == null ? void 0 : y.trim()
1147
- });
1148
- await E(N(f, `${o}.md`), `${S}
1149
- `, "utf-8"), await yt(e), t.statusCode = 201, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ ok: !0, id: o }));
1150
- } catch (r) {
1151
- t.statusCode = 500, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: r.message }));
1152
- }
1153
- return;
1154
- }
1155
- if (u.method === "PATCH" && g === "/api/plans") {
1156
- try {
1157
- const d = new URL(u.url ?? "", `http://${u.headers.host ?? "localhost"}`).searchParams.get("title");
1158
- if (!(d != null && d.trim())) {
1159
- t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "title is required" }));
1181
+ const u = await rt(t, a, d.id);
1182
+ if (u) {
1183
+ p(e, 409, { error: u });
1160
1184
  return;
1161
1185
  }
1162
- const y = await M(u), l = JSON.parse(y);
1163
- if (l.agent && !Ot.includes(l.agent)) {
1164
- t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "agent must be a known agent id" }));
1186
+ const f = r.startForPlan(d, c);
1187
+ if (!f.ok) {
1188
+ p(e, 409, { error: f.error });
1165
1189
  return;
1166
1190
  }
1167
- const T = x(e, "plans"), { entries: c } = await Q(T), o = d.trim(), f = c.find((w) => w.title === o || w.id === o);
1168
- if (!(f != null && f.id)) {
1169
- t.statusCode = 404, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "plan not found" }));
1191
+ p(e, 202, { ok: !0 });
1192
+ }
1193
+ },
1194
+ // POST /api/agent/launch-reconcile — start a headless agent that reconciles stale prose in a plan
1195
+ {
1196
+ method: "POST",
1197
+ path: "/api/agent/launch-reconcile",
1198
+ handle: async (s, e) => {
1199
+ const n = await Y(s), { planId: l, prompt: c } = JSON.parse(n);
1200
+ if (!l || !c) {
1201
+ p(e, 400, { error: "planId and prompt are required" });
1170
1202
  return;
1171
1203
  }
1172
- const S = N(T, `${f.id}.md`), b = await P(S);
1173
- if (!b) {
1174
- t.statusCode = 404, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "plan file not found" }));
1204
+ const d = await At(t, l);
1205
+ if (!d) {
1206
+ p(e, 404, { error: "plan not found" });
1175
1207
  return;
1176
1208
  }
1177
- const I = dt(b);
1178
- if (I.entries.length === 0) {
1179
- t.statusCode = 500, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "failed to parse plan file" }));
1209
+ const u = await rt(t, a, d.id);
1210
+ if (u) {
1211
+ p(e, 409, { error: u });
1180
1212
  return;
1181
1213
  }
1182
- const v = {
1183
- ...I.entries[0],
1184
- ...l.status !== void 0 && { status: l.status },
1185
- ...l.phases !== void 0 && { phases: l.phases },
1186
- ...l.log !== void 0 && { log: l.log },
1187
- ...l.agent !== void 0 && { agent: l.agent ?? void 0 },
1188
- updated: Z()
1189
- };
1190
- if (l.status === "done" || l.status === "dropped") {
1191
- const w = await et(e, a, f.id);
1192
- if (w) {
1193
- t.statusCode = 409, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: w }));
1194
- return;
1195
- }
1214
+ const f = await Me(t, l);
1215
+ if (f) {
1216
+ const y = await at(f, "utf-8"), { content: g, changed: E } = Le(y);
1217
+ E && await q(f, g, "utf-8");
1196
1218
  }
1197
- if (l.status === "in-progress") {
1198
- const w = await Q(T);
1199
- for (const C of w.entries)
1200
- if (C.id !== f.id && C.status === "in-progress") {
1201
- const O = N(T, `${C.id}.md`), A = await P(O);
1202
- if (A) {
1203
- const H = dt(A);
1204
- if (H.entries.length > 0) {
1205
- const k = {
1206
- id: H.entries[0].id ?? C.id,
1207
- title: H.entries[0].title,
1208
- kind: H.entries[0].kind ?? "feat",
1209
- status: "planned",
1210
- created: H.entries[0].created,
1211
- updated: Z(),
1212
- audited: H.entries[0].audited,
1213
- body: H.entries[0].body,
1214
- phases: H.entries[0].phases,
1215
- log: H.entries[0].log,
1216
- clarifications: H.entries[0].clarifications
1217
- };
1218
- await E(O, `${at(k)}
1219
- `, "utf-8");
1220
- }
1221
- }
1222
- }
1219
+ const h = r.startForPlan(d, c, "reconcile");
1220
+ if (!h.ok) {
1221
+ p(e, 409, { error: h.error });
1222
+ return;
1223
1223
  }
1224
- const s = {
1225
- id: v.id ?? f.id,
1226
- title: v.title,
1227
- kind: v.kind ?? "feat",
1228
- status: v.status,
1229
- idea: v.idea,
1230
- agent: v.agent,
1231
- created: v.created,
1232
- updated: v.updated,
1233
- audited: v.audited,
1234
- tags: v.tags,
1235
- body: v.body,
1236
- phases: v.phases,
1237
- log: v.log,
1238
- clarifications: v.clarifications
1239
- };
1240
- if (await E(S, `${at(s)}
1241
- `, "utf-8"), await yt(e), l.status === "done" || l.status === "dropped") {
1242
- await Zt(e, f.id);
1243
- try {
1244
- a.ensureBranch(v);
1245
- } catch {
1246
- }
1247
- }
1248
- t.statusCode = 200, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ ok: !0 }));
1249
- } catch (r) {
1250
- t.statusCode = 500, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: r.message }));
1224
+ p(e, 202, { ok: !0 });
1251
1225
  }
1252
- return;
1253
- }
1254
- if (u.method === "POST" && g === "/api/ideas") {
1255
- try {
1256
- const r = await M(u), { title: d, content: y } = JSON.parse(r);
1257
- if (!(d != null && d.trim())) {
1258
- t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "title is required" }));
1226
+ },
1227
+ // POST /api/agent/launch-draft — start a headless agent that drafts a new plan from an idea
1228
+ {
1229
+ method: "POST",
1230
+ path: "/api/agent/launch-draft",
1231
+ handle: async (s, e) => {
1232
+ const n = await Y(s), { ideaId: l, prompt: c } = JSON.parse(n);
1233
+ if (!l || !c) {
1234
+ p(e, 400, { error: "ideaId and prompt are required" });
1259
1235
  return;
1260
1236
  }
1261
- const l = x(e, "ideas"), o = `IDEA-${(await Tt(l, x(e, "ideas.md"))).entries.reduce((S, b) => {
1262
- if (b.id) {
1263
- const I = Number.parseInt(b.id.replace("IDEA-", ""), 10);
1264
- return Number.isNaN(I) ? S : Math.max(S, I);
1265
- }
1266
- return S;
1267
- }, 0) + 1}`;
1268
- await q(l, { recursive: !0 });
1269
- const f = Ft({
1270
- id: o,
1271
- title: d.trim(),
1272
- body: y == null ? void 0 : y.trim()
1273
- });
1274
- await E(N(l, `${o}.md`), `${f}
1275
- `, "utf-8"), await yt(e), t.statusCode = 201, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ ok: !0, id: o }));
1276
- } catch (r) {
1277
- t.statusCode = 500, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: r.message }));
1237
+ const d = await Yt(t, l);
1238
+ if (!d) {
1239
+ p(e, 404, { error: "idea not found" });
1240
+ return;
1241
+ }
1242
+ const u = await rt(t, a);
1243
+ if (u) {
1244
+ p(e, 409, { error: u });
1245
+ return;
1246
+ }
1247
+ const f = r.startForIdea(d, c);
1248
+ if (!f.ok) {
1249
+ p(e, 409, { error: f.error });
1250
+ return;
1251
+ }
1252
+ p(e, 202, { ok: !0 });
1278
1253
  }
1279
- return;
1280
- }
1281
- if (u.method === "GET" && g === "/api/icon") {
1282
- const r = N(e, "papercamp", "assets"), d = {
1283
- svg: "image/svg+xml",
1284
- png: "image/png",
1285
- jpg: "image/jpeg",
1286
- jpeg: "image/jpeg",
1287
- gif: "image/gif",
1288
- webp: "image/webp"
1289
- };
1290
- for (const [y, l] of Object.entries(d))
1291
- try {
1292
- const T = await U(N(r, `icon.${y}`));
1293
- t.statusCode = 200, t.setHeader("Content-Type", l), t.setHeader("Cache-Control", "no-cache"), t.end(T);
1254
+ },
1255
+ // POST /api/agent/launch-extend — start a headless agent to extend an idea's body
1256
+ {
1257
+ method: "POST",
1258
+ path: "/api/agent/launch-extend",
1259
+ handle: async (s, e) => {
1260
+ const n = await Y(s), { ideaId: l, prompt: c } = JSON.parse(n);
1261
+ if (!l || !c) {
1262
+ p(e, 400, { error: "ideaId and prompt are required" });
1294
1263
  return;
1295
- } catch {
1296
1264
  }
1297
- t.statusCode = 404, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "no icon uploaded" }));
1298
- return;
1299
- }
1300
- if (u.method === "POST" && g === "/api/icon") {
1301
- try {
1302
- const r = await M(u), { dataUri: d } = JSON.parse(r);
1265
+ const d = await Yt(t, l);
1303
1266
  if (!d) {
1304
- t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "dataUri is required" }));
1267
+ p(e, 404, { error: "idea not found" });
1305
1268
  return;
1306
1269
  }
1307
- const y = d.match(/^data:(image\/[a-z0-9+.-]+);base64,(.+)$/);
1308
- if (!y) {
1309
- t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "invalid data URI" }));
1270
+ const u = await rt(t, a);
1271
+ if (u) {
1272
+ p(e, 409, { error: u });
1310
1273
  return;
1311
1274
  }
1312
- const l = y[1], T = l === "image/svg+xml" ? "svg" : l.split("/")[1], c = Buffer.from(y[2], "base64"), o = N(e, "papercamp", "assets");
1313
- await q(o, { recursive: !0 }), await E(N(o, `icon.${T}`), c), t.statusCode = 200, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ ok: !0 }));
1314
- } catch (r) {
1315
- t.statusCode = 500, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: r.message }));
1316
- }
1317
- return;
1318
- }
1319
- if (u.method === "GET" && g === "/api/git/status") {
1320
- try {
1321
- const r = await a.getStatus(), d = a.getCurrentBranch(), y = await a.getAheadCount();
1322
- t.statusCode = 200, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ branch: d, entries: r, ahead: y }));
1323
- } catch (r) {
1324
- t.statusCode = 500, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: r.message }));
1325
- }
1326
- return;
1327
- }
1328
- if (u.method === "POST" && g === "/api/git/push") {
1329
- try {
1330
- await a.push(), t.statusCode = 200, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ ok: !0 }));
1331
- } catch (r) {
1332
- t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: r.message }));
1333
- }
1334
- return;
1335
- }
1336
- if (u.method === "POST" && g === "/api/git/commit") {
1337
- try {
1338
- const r = await M(u), { files: d, title: y, message: l } = JSON.parse(r);
1339
- if (!(y != null && y.trim())) {
1340
- t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "title is required" }));
1275
+ const f = r.startForIdeaExtend(d, c);
1276
+ if (!f.ok) {
1277
+ p(e, 409, { error: f.error });
1341
1278
  return;
1342
1279
  }
1343
- await a.commit(d ?? [], y.trim(), l == null ? void 0 : l.trim()), t.statusCode = 200, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ ok: !0 }));
1344
- } catch (r) {
1345
- t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: r.message }));
1280
+ p(e, 202, { ok: !0 });
1346
1281
  }
1347
- return;
1348
- }
1349
- if (u.method === "POST" && g === "/api/git/suggest-commit-message") {
1350
- try {
1351
- const r = await M(u), { files: d } = JSON.parse(r);
1352
- if (!(d != null && d.length)) {
1353
- t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "files is required" }));
1282
+ },
1283
+ // POST /api/agent/launch-audit-all — start a batch convergence audit across all review/done plans
1284
+ {
1285
+ method: "POST",
1286
+ path: "/api/agent/launch-audit-all",
1287
+ handle: async (s, e) => {
1288
+ const n = await rt(t, a);
1289
+ if (n) {
1290
+ p(e, 409, { error: n });
1354
1291
  return;
1355
1292
  }
1356
- const y = await a.diff(d), { entries: l } = await ct(
1357
- x(e, "plans"),
1358
- x(e, "plans.md")
1359
- ), T = he(l), c = await Te(
1360
- y,
1361
- T == null ? void 0 : T.id,
1362
- m.runCommitSuggest
1363
- );
1364
- t.statusCode = 200, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify(c));
1365
- } catch (r) {
1366
- t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: r.message }));
1367
- }
1368
- return;
1369
- }
1370
- if (u.method === "GET" && g === "/api/status") {
1371
- t.statusCode = 200, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify(p.getStatus()));
1372
- return;
1373
- }
1374
- if (u.method === "POST" && g === "/api/status/check") {
1375
- const d = new URL(u.url ?? "", `http://${u.headers.host ?? "localhost"}`).searchParams.get("name");
1376
- if (d !== "lint" && d !== "format" && d !== "test") {
1377
- t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "name must be lint, format, or test" }));
1378
- return;
1293
+ const l = r.startBatchAudit();
1294
+ if (!l.ok) {
1295
+ p(e, 409, { error: l.error });
1296
+ return;
1297
+ }
1298
+ p(e, 202, { ok: !0 });
1379
1299
  }
1380
- p.runCheck(d), t.statusCode = 202, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ ok: !0 }));
1381
- return;
1382
- }
1383
- if (u.method === "POST" && g === "/api/status/fix") {
1384
- p.runQualityFix(), t.statusCode = 202, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ ok: !0 }));
1385
- return;
1386
- }
1387
- if (u.method === "GET" && g === "/api/activity/stream") {
1388
- t.statusCode = 200, t.setHeader("Content-Type", "text/event-stream"), t.setHeader("Cache-Control", "no-cache"), t.setHeader("Connection", "keep-alive"), t.flushHeaders(), i.subscribe(t), a.subscribe(t), p.subscribe(t), m.subscribe(t);
1389
- return;
1390
- }
1391
- if (u.method === "GET" && g === "/api/agent/status") {
1392
- t.statusCode = 200, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify(m.getStatus()));
1393
- return;
1394
- }
1395
- if (u.method === "POST" && g === "/api/agent/launch") {
1396
- try {
1397
- const r = await M(u), { planId: d, phaseIndex: y } = JSON.parse(r);
1398
- if (!d || typeof y != "number") {
1399
- t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "planId and phaseIndex are required" }));
1300
+ },
1301
+ // POST /api/agent/launch-run-all — run every unchecked phase sequentially with per-phase commits
1302
+ {
1303
+ method: "POST",
1304
+ path: "/api/agent/launch-run-all",
1305
+ handle: async (s, e) => {
1306
+ const n = await Y(s), { planId: l } = JSON.parse(n);
1307
+ if (!l) {
1308
+ p(e, 400, { error: "planId is required" });
1400
1309
  return;
1401
1310
  }
1402
- const l = x(e, "plans"), c = (await Q(l)).entries.find((S) => S.id === d);
1311
+ const c = await At(t, l);
1403
1312
  if (!c) {
1404
- const b = vt(await P(x(e, "plans.md"))).entries.find((s) => s.id === d);
1405
- if (!b) {
1406
- t.statusCode = 404, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "plan not found" }));
1407
- return;
1408
- }
1409
- const I = await et(e, a, b.id);
1410
- if (I) {
1411
- t.statusCode = 409, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: I }));
1412
- return;
1413
- }
1414
- const v = await m.start(b, y);
1415
- if (!v.ok) {
1416
- t.statusCode = 409, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: v.error }));
1417
- return;
1418
- }
1419
- t.statusCode = 202, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ ok: !0 }));
1313
+ p(e, 404, { error: "plan not found" });
1420
1314
  return;
1421
1315
  }
1422
- const o = await et(e, a, c.id);
1423
- if (o) {
1424
- t.statusCode = 409, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: o }));
1316
+ const d = await rt(t, a, c.id);
1317
+ if (d) {
1318
+ p(e, 409, { error: d });
1425
1319
  return;
1426
1320
  }
1427
- const f = await m.start(c, y);
1428
- if (!f.ok) {
1429
- t.statusCode = 409, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: f.error }));
1321
+ const u = r.startRunAllPhases(c, () => i.runChecksAndWait());
1322
+ if (!u.ok) {
1323
+ p(e, 409, { error: u.error });
1430
1324
  return;
1431
1325
  }
1432
- t.statusCode = 202, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ ok: !0 }));
1433
- } catch (r) {
1434
- t.statusCode = 500, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: r.message }));
1326
+ p(e, 202, { ok: !0 });
1435
1327
  }
1436
- return;
1437
- }
1438
- if (u.method === "POST" && g === "/api/agent/launch-audit") {
1439
- try {
1440
- const r = await M(u), { planId: d, prompt: y } = JSON.parse(r);
1441
- if (!d || !y) {
1442
- t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "planId and prompt are required" }));
1328
+ },
1329
+ // POST /api/agent/stop — kill the running agent task
1330
+ {
1331
+ method: "POST",
1332
+ path: "/api/agent/stop",
1333
+ handle: (s, e) => {
1334
+ const n = r.stop();
1335
+ if (!n.ok) {
1336
+ p(e, 409, { error: n.error });
1443
1337
  return;
1444
1338
  }
1445
- const l = x(e, "plans");
1446
- let c = (await Q(l)).entries.find((S) => S.id === d);
1447
- if (c || (c = vt(await P(x(e, "plans.md"))).entries.find((b) => b.id === d)), !c) {
1448
- t.statusCode = 404, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "plan not found" }));
1339
+ p(e, 202, { ok: !0 });
1340
+ }
1341
+ }
1342
+ ];
1343
+ }
1344
+ const re = [
1345
+ "biome.json",
1346
+ "tsconfig.json",
1347
+ "tailwind.config.ts",
1348
+ "vite.config.ts",
1349
+ "vite.app.config.ts",
1350
+ "postcss.config.js",
1351
+ "package.json"
1352
+ ];
1353
+ async function oe(t) {
1354
+ const a = [];
1355
+ for (const i of re)
1356
+ await j($(t, i)) && a.push(i);
1357
+ return { files: a };
1358
+ }
1359
+ function Je({ root: t }) {
1360
+ return [
1361
+ // POST /api/config — update editable fields in papercamp/config.json (port, projectName)
1362
+ {
1363
+ method: "POST",
1364
+ path: "/api/config",
1365
+ handle: async (a, i) => {
1366
+ const r = $(t, "papercamp", "config.json"), s = await j(r);
1367
+ if (!s) {
1368
+ p(i, 404, { error: "config not found" });
1449
1369
  return;
1450
1370
  }
1451
- const o = await et(e, a, c.id);
1452
- if (o) {
1453
- t.statusCode = 409, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: o }));
1371
+ const e = await Y(a), n = JSON.parse(e), { port: l, projectName: c, defaultAgent: d } = n, u = n.defaultAgents;
1372
+ if (l !== void 0 && (!Number.isInteger(l) || l <= 0)) {
1373
+ p(i, 400, { error: "port must be a positive integer" });
1454
1374
  return;
1455
1375
  }
1456
- const f = m.startForPlan(c, y);
1457
- if (!f.ok) {
1458
- t.statusCode = 409, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: f.error }));
1376
+ if (c !== void 0 && c.trim().length === 0) {
1377
+ p(i, 400, { error: "projectName must not be empty" });
1459
1378
  return;
1460
1379
  }
1461
- t.statusCode = 202, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ ok: !0 }));
1462
- } catch (r) {
1463
- t.statusCode = 500, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: r.message }));
1464
- }
1465
- return;
1466
- }
1467
- if (u.method === "POST" && g === "/api/agent/launch-draft") {
1468
- try {
1469
- const r = await M(u), { ideaId: d, prompt: y } = JSON.parse(r);
1470
- if (!d || !y) {
1471
- t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "ideaId and prompt are required" }));
1380
+ if (d !== void 0 && !Mt.includes(d)) {
1381
+ p(i, 400, { error: "defaultAgent must be a known agent id" });
1472
1382
  return;
1473
1383
  }
1474
- const l = x(e, "ideas");
1475
- let c = (await xt(l)).entries.find((S) => S.id === d);
1476
- if (c || (c = Jt(await P(x(e, "ideas.md"))).find((b) => b.id === d)), !c) {
1477
- t.statusCode = 404, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "idea not found" }));
1384
+ if (u !== void 0)
1385
+ for (const S of ["phase", "planDraft", "ideaExtend", "commitSuggest"]) {
1386
+ const m = u[S], w = typeof m == "string" ? m : m == null ? void 0 : m.agent;
1387
+ if (!w || !Mt.includes(w)) {
1388
+ p(i, 400, { error: `defaultAgents.${S} must be a known agent id` });
1389
+ return;
1390
+ }
1391
+ }
1392
+ const f = JSON.parse(s), y = (u ? {
1393
+ phase: z(u.phase),
1394
+ planDraft: z(u.planDraft),
1395
+ ideaExtend: z(u.ideaExtend),
1396
+ commitSuggest: z(u.commitSuggest)
1397
+ } : void 0) ?? (d !== void 0 ? {
1398
+ phase: { agent: d },
1399
+ planDraft: { agent: d },
1400
+ ideaExtend: { agent: d },
1401
+ commitSuggest: { agent: d }
1402
+ } : void 0), g = f, { defaultAgent: E, ...F } = g, D = {
1403
+ ...F,
1404
+ ...l !== void 0 && { port: l },
1405
+ ...c !== void 0 && { projectName: c.trim() },
1406
+ ...y && { defaultAgents: y }
1407
+ };
1408
+ await q(r, `${JSON.stringify(D, null, 2)}
1409
+ `), p(i, 200, { ok: !0 });
1410
+ }
1411
+ },
1412
+ // GET /api/configs — list readable config files, or ?name=... for one file's content
1413
+ {
1414
+ method: "GET",
1415
+ path: "/api/configs",
1416
+ handle: async (a, i) => {
1417
+ const r = vt(a).searchParams.get("name");
1418
+ if (!r) {
1419
+ p(i, 200, await oe(t));
1478
1420
  return;
1479
1421
  }
1480
- const o = await et(e, a);
1481
- if (o) {
1482
- t.statusCode = 409, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: o }));
1422
+ if (!re.includes(r)) {
1423
+ p(i, 400, { error: "invalid config file name" });
1483
1424
  return;
1484
1425
  }
1485
- const f = m.startForIdea(c, y);
1486
- if (!f.ok) {
1487
- t.statusCode = 409, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: f.error }));
1426
+ const s = await j($(t, r));
1427
+ if (!s) {
1428
+ p(i, 404, { error: "config file not found" });
1488
1429
  return;
1489
1430
  }
1490
- t.statusCode = 202, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ ok: !0 }));
1491
- } catch (r) {
1492
- t.statusCode = 500, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: r.message }));
1431
+ p(i, 200, { name: r, content: s });
1493
1432
  }
1494
- return;
1495
1433
  }
1496
- if (u.method === "POST" && g === "/api/agent/launch-extend") {
1497
- try {
1498
- const r = await M(u), { ideaId: d, prompt: y } = JSON.parse(r);
1499
- if (!d || !y) {
1500
- t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "ideaId and prompt are required" }));
1434
+ ];
1435
+ }
1436
+ function Ke({ root: t }) {
1437
+ return [
1438
+ // POST /api/open-questions/resolve?title=... resolve an open question with a new decision
1439
+ {
1440
+ method: "POST",
1441
+ path: "/api/open-questions/resolve",
1442
+ handle: async (a, i) => {
1443
+ const r = vt(a).searchParams.get("title");
1444
+ if (!(r != null && r.trim())) {
1445
+ p(i, 400, { error: "title is required" });
1501
1446
  return;
1502
1447
  }
1503
- const l = x(e, "ideas");
1504
- let c = (await xt(l)).entries.find((S) => S.id === d);
1505
- if (c || (c = Jt(await P(x(e, "ideas.md"))).find((b) => b.id === d)), !c) {
1506
- t.statusCode = 404, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "idea not found" }));
1448
+ const s = await Y(a), { decision: e, rationale: n } = JSON.parse(s);
1449
+ if (!(e != null && e.trim())) {
1450
+ p(i, 400, { error: "decision is required" });
1507
1451
  return;
1508
1452
  }
1509
- const o = await et(e, a);
1510
- if (o) {
1511
- t.statusCode = 409, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: o }));
1453
+ const l = P(t, "open-questions.md"), c = await j(l);
1454
+ if (!c) {
1455
+ p(i, 404, { error: "open-questions.md not found" });
1512
1456
  return;
1513
1457
  }
1514
- const f = m.startForIdeaExtend(c, y);
1515
- if (!f.ok) {
1516
- t.statusCode = 409, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: f.error }));
1458
+ const d = jt(c);
1459
+ if (d.warnings.length > 0) {
1460
+ p(i, 409, {
1461
+ error: "open-questions.md has parse warnings — resolve them before updating to avoid data loss",
1462
+ warnings: d.warnings.map((g) => `${g.title}: ${g.message}`)
1463
+ });
1517
1464
  return;
1518
1465
  }
1519
- t.statusCode = 202, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ ok: !0 }));
1520
- } catch (r) {
1521
- t.statusCode = 500, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: r.message }));
1466
+ const u = r.trim(), f = d.entries.find((g) => g.title === u);
1467
+ if (!f) {
1468
+ p(i, 404, { error: `open question "${u}" not found` });
1469
+ return;
1470
+ }
1471
+ if (f.status !== "open") {
1472
+ p(i, 409, { error: `open question "${u}" is already ${f.status}` });
1473
+ return;
1474
+ }
1475
+ const h = he({
1476
+ title: e.trim(),
1477
+ date: ot(),
1478
+ status: "decided",
1479
+ body: n == null ? void 0 : n.trim()
1480
+ });
1481
+ await me(P(t, "decisions.md"), h), f.status = "resolved", f.resolvedBy = e.trim();
1482
+ const y = ge(d.entries);
1483
+ await q(l, `${y}
1484
+ `, "utf-8"), p(i, 200, { ok: !0 });
1522
1485
  }
1523
- return;
1524
1486
  }
1525
- if (u.method === "POST" && g === "/api/agent/launch-audit-all") {
1526
- const r = await et(e, a);
1527
- if (r) {
1528
- t.statusCode = 409, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: r }));
1529
- return;
1487
+ ];
1488
+ }
1489
+ function Ge({ root: t }) {
1490
+ return [
1491
+ // GET /api/env — read the project root's .env, masked client-side only
1492
+ {
1493
+ method: "GET",
1494
+ path: "/api/env",
1495
+ handle: async (a, i) => {
1496
+ const r = $(t, ".env"), s = $(t, ".env.example"), [e, n] = await Promise.all([
1497
+ wt(r),
1498
+ wt(s)
1499
+ ]), l = e ? Jt(await j(r)) : [], c = n ? Jt(await j(s)).map((f) => f.key) : [], d = new Set(l.map((f) => f.key)), u = c.filter((f) => !d.has(f));
1500
+ p(i, 200, { exists: e, exampleExists: n, entries: l, missingKeys: u });
1530
1501
  }
1531
- const d = m.startBatchAudit();
1532
- if (!d.ok) {
1533
- t.statusCode = 409, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: d.error }));
1534
- return;
1502
+ },
1503
+ // POST /api/env — write the full desired entry set back to .env
1504
+ {
1505
+ method: "POST",
1506
+ path: "/api/env",
1507
+ handle: async (a, i) => {
1508
+ const r = await Y(a), { entries: s } = JSON.parse(r);
1509
+ if (!Array.isArray(s)) {
1510
+ p(i, 400, { error: "entries is required" });
1511
+ return;
1512
+ }
1513
+ const e = /* @__PURE__ */ new Set();
1514
+ for (const c of s) {
1515
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(c.key)) {
1516
+ p(i, 400, { error: `invalid key: ${c.key}` });
1517
+ return;
1518
+ }
1519
+ if (e.has(c.key)) {
1520
+ p(i, 400, { error: `duplicate key: ${c.key}` });
1521
+ return;
1522
+ }
1523
+ e.add(c.key);
1524
+ }
1525
+ const n = $(t, ".env"), l = await j(n);
1526
+ await q(n, we(l, s)), p(i, 200, { ok: !0 });
1535
1527
  }
1536
- t.statusCode = 202, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ ok: !0 }));
1537
- return;
1538
1528
  }
1539
- if (u.method === "POST" && g === "/api/agent/stop") {
1540
- const r = m.stop();
1541
- if (!r.ok) {
1542
- t.statusCode = 409, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: r.error }));
1543
- return;
1529
+ ];
1530
+ }
1531
+ const He = (t, a) => {
1532
+ if (t)
1533
+ return t.find((i) => i.status === "in-progress" || i.status === "review");
1534
+ };
1535
+ function We(t, a) {
1536
+ return `You are writing a single git commit message for the diff below. Do not use any tools, do not read or edit any files — base your answer only on the diff text given.
1537
+
1538
+ Follow this repo's commit convention: \`type(scope): Description\`, where type is one of feat|fix|chore|docs|refactor and scope is a subsystem area from this fixed list: core, cli, app, server, agent, plans, ideas, docs, settings, stack, ui, ci, config, deps, repo. Pick the area the diff most affects. Keep the whole title under 100 characters and do not end it with a period.${a ? `
1539
+ This work belongs to plan ${a}. Do NOT put the plan id in the scope — instead end the message body with a \`Refs: ${a}\` line as its final line.` : ""}
1540
+
1541
+ Respond with ONLY a single JSON object, no prose, no code fences, no markdown — exactly this shape:
1542
+ {"title": "type(scope): Description", "message": "${a ? `longer body describing what changed and why, ending with a \`Refs: ${a}\` line` : "optional longer body describing what changed and why, or an empty string if the title alone is clear enough"}"}
1543
+
1544
+ Diff:
1545
+ ${t}`;
1546
+ }
1547
+ async function Ue(t, a, i) {
1548
+ if (!t.trim())
1549
+ throw new Error("No changes to summarize — select at least one file first");
1550
+ const r = We(t, a), s = await i(r);
1551
+ let e = s;
1552
+ try {
1553
+ const d = JSON.parse(s);
1554
+ typeof d.result == "string" && (e = d.result);
1555
+ } catch {
1556
+ }
1557
+ const n = e.match(/\{[\s\S]*\}/);
1558
+ if (!n) throw new Error("Agent did not return a parseable commit message");
1559
+ const l = JSON.parse(n[0]);
1560
+ if (!l.title) throw new Error("Agent response missing a title");
1561
+ let c = l.message ?? "";
1562
+ return a && !/Refs:\s*\S/.test(c) && (c = c ? `${c}
1563
+
1564
+ Refs: ${a}` : `Refs: ${a}`), { title: l.title, message: c };
1565
+ }
1566
+ const Ye = "Sync the current branch to main by:\n1. Stashing or committing any uncommitted changes (do not use `git reset --hard` or `git clean -fd` without an explicit confirmation step)\n2. Relocating any mis-filed content (e.g., any new plans written to the legacy `papercamp/plans.md` instead of per-file `papercamp/plans/*.md`)\n3. Checking out main: `git checkout main`\n4. Fetching from origin: `git fetch --prune`\n5. Fast-forwarding the merge: `git merge --ff-only origin/main`\n6. Confirming success\n\nReport the final branch and status to verify the sync completed.";
1567
+ function ze({ root: t, git: a, agent: i }) {
1568
+ return [
1569
+ // GET /api/git/status — return working tree status
1570
+ {
1571
+ method: "GET",
1572
+ path: "/api/git/status",
1573
+ handle: async (r, s) => {
1574
+ const e = await a.getStatus(), n = a.getCurrentBranch(), l = await a.getAheadCount(), c = await a.getBranchHygieneStatus();
1575
+ p(s, 200, { branch: n, entries: e, ahead: l, branchHygiene: c });
1544
1576
  }
1545
- t.statusCode = 202, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ ok: !0 }));
1546
- return;
1547
- }
1548
- if (u.method === "POST" && g === "/api/config") {
1549
- try {
1550
- const r = N(e, "papercamp", "config.json"), d = await P(r);
1551
- if (!d) {
1552
- t.statusCode = 404, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "config not found" }));
1553
- return;
1577
+ },
1578
+ // POST /api/git/push — push the current branch to its upstream
1579
+ {
1580
+ method: "POST",
1581
+ path: "/api/git/push",
1582
+ handle: async (r, s) => {
1583
+ try {
1584
+ await a.push(), p(s, 200, { ok: !0 });
1585
+ } catch (e) {
1586
+ p(s, 400, { error: e.message });
1554
1587
  }
1555
- const y = await M(u), l = JSON.parse(y), { port: T, projectName: c, defaultAgent: o } = l, f = l.defaultAgents;
1556
- if (T !== void 0 && (!Number.isInteger(T) || T <= 0)) {
1557
- t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "port must be a positive integer" }));
1588
+ }
1589
+ },
1590
+ // POST /api/git/commit stage files and create a commit
1591
+ {
1592
+ method: "POST",
1593
+ path: "/api/git/commit",
1594
+ handle: async (r, s) => {
1595
+ try {
1596
+ const e = await Y(r), { files: n, title: l, message: c } = JSON.parse(e);
1597
+ if (!(l != null && l.trim())) {
1598
+ p(s, 400, { error: "title is required" });
1599
+ return;
1600
+ }
1601
+ await a.commit(n ?? [], l.trim(), c == null ? void 0 : c.trim()), p(s, 200, { ok: !0 });
1602
+ } catch (e) {
1603
+ p(s, 400, { error: e.message });
1604
+ }
1605
+ }
1606
+ },
1607
+ // POST /api/git/sync — sync to main (clean: inline, dirty: agent task)
1608
+ {
1609
+ method: "POST",
1610
+ path: "/api/git/sync",
1611
+ handle: async (r, s) => {
1612
+ const e = await Y(r), { mode: n } = JSON.parse(e);
1613
+ if (n !== "clean" && n !== "dirty") {
1614
+ p(s, 400, { error: "mode must be 'clean' or 'dirty'" });
1558
1615
  return;
1559
1616
  }
1560
- if (c !== void 0 && c.trim().length === 0) {
1561
- t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "projectName must not be empty" }));
1617
+ if (n === "clean") {
1618
+ if ((await a.getStatus()).length > 0) {
1619
+ p(s, 409, { error: "Working tree is no longer clean — refresh and retry" });
1620
+ return;
1621
+ }
1622
+ await a.runGitSync(), p(s, 200, { ok: !0 });
1562
1623
  return;
1563
1624
  }
1564
- if (o !== void 0 && !Ot.includes(o)) {
1565
- t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "defaultAgent must be a known agent id" }));
1625
+ const l = i.startSync(Ye);
1626
+ if (!l.ok) {
1627
+ p(s, 409, { error: l.error });
1566
1628
  return;
1567
1629
  }
1568
- if (f !== void 0)
1569
- for (const O of ["phase", "planDraft", "ideaExtend", "commitSuggest"]) {
1570
- const A = f[O], H = typeof A == "string" ? A : A == null ? void 0 : A.agent;
1571
- if (!H || !Ot.includes(H)) {
1572
- t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: `defaultAgents.${O} must be a known agent id` }));
1573
- return;
1574
- }
1630
+ p(s, 202, { ok: !0 });
1631
+ }
1632
+ },
1633
+ // POST /api/git/suggest-commit-message agent-written title/body from the actual diff
1634
+ {
1635
+ method: "POST",
1636
+ path: "/api/git/suggest-commit-message",
1637
+ handle: async (r, s) => {
1638
+ try {
1639
+ const e = await Y(r), { files: n } = JSON.parse(e);
1640
+ if (!(n != null && n.length)) {
1641
+ p(s, 400, { error: "files is required" });
1642
+ return;
1575
1643
  }
1576
- const S = JSON.parse(d), I = (f ? {
1577
- phase: B(f.phase),
1578
- planDraft: B(f.planDraft),
1579
- ideaExtend: B(f.ideaExtend),
1580
- commitSuggest: B(f.commitSuggest)
1581
- } : void 0) ?? (o !== void 0 ? {
1582
- phase: { agent: o },
1583
- planDraft: { agent: o },
1584
- ideaExtend: { agent: o },
1585
- commitSuggest: { agent: o }
1586
- } : void 0), v = S, { defaultAgent: s, ...w } = v, C = {
1587
- ...w,
1588
- ...T !== void 0 && { port: T },
1589
- ...c !== void 0 && { projectName: c.trim() },
1590
- ...I && { defaultAgents: I }
1591
- };
1592
- await E(r, JSON.stringify(C, null, 2)), t.statusCode = 200, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ ok: !0 }));
1593
- } catch (r) {
1594
- t.statusCode = 500, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: r.message }));
1644
+ const l = await a.diff(n), { entries: c } = await gt(
1645
+ P(t, "plans"),
1646
+ P(t, "plans.md")
1647
+ ), d = He(c), u = await Ue(
1648
+ l,
1649
+ d == null ? void 0 : d.id,
1650
+ i.runCommitSuggest
1651
+ );
1652
+ p(s, 200, u);
1653
+ } catch (e) {
1654
+ p(s, 400, { error: e.message });
1655
+ }
1595
1656
  }
1596
- return;
1597
1657
  }
1598
- if (u.method === "GET" && g === "/api/env") {
1599
- try {
1600
- const r = N(e, ".env"), d = N(e, ".env.example"), [y, l] = await Promise.all([
1601
- ft(r),
1602
- ft(d)
1603
- ]), T = y ? Et(await P(r)) : [], c = l ? Et(await P(d)).map((S) => S.key) : [], o = new Set(T.map((S) => S.key)), f = c.filter((S) => !o.has(S));
1604
- t.statusCode = 200, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ exists: y, exampleExists: l, entries: T, missingKeys: f }));
1605
- } catch (r) {
1606
- t.statusCode = 500, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: r.message }));
1658
+ ];
1659
+ }
1660
+ const zt = {
1661
+ svg: "image/svg+xml",
1662
+ png: "image/png",
1663
+ jpg: "image/jpeg",
1664
+ jpeg: "image/jpeg",
1665
+ gif: "image/gif",
1666
+ webp: "image/webp"
1667
+ };
1668
+ function Qe({ root: t }) {
1669
+ return [
1670
+ // GET /api/icon — serve project icon
1671
+ {
1672
+ method: "GET",
1673
+ path: "/api/icon",
1674
+ handle: async (a, i) => {
1675
+ const r = $(t, "papercamp", "assets");
1676
+ for (const [s, e] of Object.entries(zt))
1677
+ try {
1678
+ const n = await at($(r, `icon.${s}`));
1679
+ i.statusCode = 200, i.setHeader("Content-Type", e), i.setHeader("Cache-Control", "no-cache"), i.end(n);
1680
+ return;
1681
+ } catch {
1682
+ }
1683
+ p(i, 404, { error: "no icon uploaded" });
1684
+ }
1685
+ },
1686
+ // POST /api/icon — upload project icon
1687
+ {
1688
+ method: "POST",
1689
+ path: "/api/icon",
1690
+ handle: async (a, i) => {
1691
+ const r = await Y(a), { dataUri: s } = JSON.parse(r);
1692
+ if (!s) {
1693
+ p(i, 400, { error: "dataUri is required" });
1694
+ return;
1695
+ }
1696
+ const e = s.match(/^data:(image\/[a-z0-9+.-]+);base64,(.+)$/);
1697
+ if (!e) {
1698
+ p(i, 400, { error: "invalid data URI" });
1699
+ return;
1700
+ }
1701
+ const n = e[1], l = n === "image/svg+xml" ? "svg" : n.split("/")[1], c = Buffer.from(e[2], "base64"), d = $(t, "papercamp", "assets");
1702
+ await ut(d, { recursive: !0 }), await Promise.all(
1703
+ Object.keys(zt).filter((u) => u !== l).map((u) => Xt($(d, `icon.${u}`)).catch(() => {
1704
+ }))
1705
+ ), await q($(d, `icon.${l}`), c), p(i, 200, { ok: !0 });
1607
1706
  }
1608
- return;
1609
1707
  }
1610
- if (u.method === "POST" && g === "/api/env") {
1611
- try {
1612
- const r = await M(u), { entries: d } = JSON.parse(r);
1613
- if (!Array.isArray(d)) {
1614
- t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "entries is required" }));
1708
+ ];
1709
+ }
1710
+ function Xe({ root: t }) {
1711
+ return [
1712
+ // POST /api/ideas create a new per-file idea entry
1713
+ {
1714
+ method: "POST",
1715
+ path: "/api/ideas",
1716
+ handle: async (a, i) => {
1717
+ const r = await Y(a), { title: s, content: e } = JSON.parse(r);
1718
+ if (!(s != null && s.trim())) {
1719
+ p(i, 400, { error: "title is required" });
1615
1720
  return;
1616
1721
  }
1617
- const y = /* @__PURE__ */ new Set();
1618
- for (const c of d) {
1619
- if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(c.key)) {
1620
- t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: `invalid key: ${c.key}` }));
1621
- return;
1622
- }
1623
- if (y.has(c.key)) {
1624
- t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: `duplicate key: ${c.key}` }));
1625
- return;
1722
+ const n = P(t, "ideas"), d = `IDEA-${(await Ct(n, P(t, "ideas.md"))).entries.reduce((f, h) => {
1723
+ if (h.id) {
1724
+ const y = Number.parseInt(h.id.replace("IDEA-", ""), 10);
1725
+ return Number.isNaN(y) ? f : Math.max(f, y);
1626
1726
  }
1627
- y.add(c.key);
1628
- }
1629
- const l = N(e, ".env"), T = await P(l);
1630
- await E(l, Vt(T, d)), t.statusCode = 200, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ ok: !0 }));
1631
- } catch (r) {
1632
- t.statusCode = 500, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: r.message }));
1727
+ return f;
1728
+ }, 0) + 1}`;
1729
+ await ut(n, { recursive: !0 });
1730
+ const u = ne({
1731
+ id: d,
1732
+ title: s.trim(),
1733
+ body: e == null ? void 0 : e.trim()
1734
+ });
1735
+ await q($(n, `${d}.md`), `${u}
1736
+ `, "utf-8"), await Dt(t), p(i, 201, { ok: !0, id: d });
1633
1737
  }
1634
- return;
1635
1738
  }
1636
- if (u.method === "POST" && g === "/api/open-questions/resolve") {
1637
- try {
1638
- const d = new URL(u.url ?? "", `http://${u.headers.host ?? "localhost"}`).searchParams.get("title");
1639
- if (!(d != null && d.trim())) {
1640
- t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "title is required" }));
1739
+ ];
1740
+ }
1741
+ function Ze({ root: t, git: a }) {
1742
+ return [
1743
+ // DELETE /api/plans?title=... remove a plan entry from per-file storage
1744
+ {
1745
+ method: "DELETE",
1746
+ path: "/api/plans",
1747
+ handle: async (i, r) => {
1748
+ const s = vt(i).searchParams.get("title");
1749
+ if (!(s != null && s.trim())) {
1750
+ p(r, 400, { error: "title is required" });
1641
1751
  return;
1642
1752
  }
1643
- const y = await M(u), { decision: l, rationale: T } = JSON.parse(y);
1644
- if (!(l != null && l.trim())) {
1645
- t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "decision is required" }));
1753
+ const e = P(t, "plans"), n = s.trim(), { entries: l } = await lt(e), c = l.find((u) => u.title === n || u.id === n);
1754
+ if (!(c != null && c.id)) {
1755
+ p(r, 404, { error: "plan not found in per-file storage" });
1646
1756
  return;
1647
1757
  }
1648
- const c = x(e, "open-questions.md"), o = await P(c);
1649
- if (!o) {
1650
- t.statusCode = 404, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "open-questions.md not found" }));
1758
+ const d = $(e, `${c.id}.md`);
1759
+ if (!await wt(d)) {
1760
+ p(r, 404, { error: "plan file not found" });
1651
1761
  return;
1652
1762
  }
1653
- const f = It(o);
1654
- if (f.warnings.length > 0) {
1655
- t.statusCode = 409, t.setHeader("Content-Type", "application/json"), t.end(
1656
- JSON.stringify({
1657
- error: "open-questions.md has parse warnings — resolve them before updating to avoid data loss",
1658
- warnings: f.warnings.map((s) => `${s.title}: ${s.message}`)
1659
- })
1660
- );
1763
+ await Xt(d), await Dt(t), p(r, 200, { ok: !0 });
1764
+ }
1765
+ },
1766
+ // POST /api/plans — create a new per-file plan entry
1767
+ {
1768
+ method: "POST",
1769
+ path: "/api/plans",
1770
+ handle: async (i, r) => {
1771
+ const s = await Y(i), { title: e, content: n, kind: l } = JSON.parse(s);
1772
+ if (!(e != null && e.trim())) {
1773
+ p(r, 400, { error: "title is required" });
1661
1774
  return;
1662
1775
  }
1663
- const S = d.trim(), b = f.entries.find((s) => s.title === S);
1664
- if (!b) {
1665
- t.statusCode = 404, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: `open question "${S}" not found` }));
1776
+ const c = await rt(t, a);
1777
+ if (c) {
1778
+ p(r, 409, { error: c });
1666
1779
  return;
1667
1780
  }
1668
- if (b.status !== "open") {
1669
- t.statusCode = 409, t.setHeader("Content-Type", "application/json"), t.end(
1670
- JSON.stringify({ error: `open question "${S}" is already ${b.status}` })
1671
- );
1781
+ const d = l && Et.includes(l) ? l : "feat", u = $(t, "papercamp", "config.json"), f = await ae(u, d);
1782
+ if (!f) {
1783
+ p(r, 500, { error: "could not assign plan ID" });
1672
1784
  return;
1673
1785
  }
1674
- const I = qt({
1675
- title: l.trim(),
1676
- date: Z(),
1677
- status: "decided",
1678
- body: T == null ? void 0 : T.trim()
1786
+ const h = P(t, "plans");
1787
+ await ut(h, { recursive: !0 });
1788
+ const y = xt({
1789
+ id: f,
1790
+ title: e.trim(),
1791
+ kind: d,
1792
+ status: "idea",
1793
+ created: ot(),
1794
+ body: n == null ? void 0 : n.trim()
1679
1795
  });
1680
- await te(x(e, "decisions.md"), I), b.status = "resolved", b.resolvedBy = l.trim();
1681
- const v = ee(f.entries);
1682
- await E(c, `${v}
1683
- `, "utf-8"), t.statusCode = 200, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ ok: !0 }));
1684
- } catch (r) {
1685
- t.statusCode = 500, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: r.message }));
1796
+ await q($(h, `${f}.md`), `${y}
1797
+ `, "utf-8"), await Dt(t), p(r, 201, { ok: !0, id: f });
1686
1798
  }
1687
- return;
1688
- }
1689
- if (u.method === "GET" && g === "/api/configs") {
1690
- const d = new URL(u.url ?? "", `http://${u.headers.host ?? "localhost"}`).searchParams.get("name");
1691
- if (d)
1692
- try {
1693
- if (!je.includes(d)) {
1694
- t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "invalid config file name" }));
1799
+ },
1800
+ // PATCH /api/plans?title=... — update an existing plan entry
1801
+ {
1802
+ method: "PATCH",
1803
+ path: "/api/plans",
1804
+ handle: async (i, r) => {
1805
+ const s = vt(i).searchParams.get("title");
1806
+ if (!(s != null && s.trim())) {
1807
+ p(r, 400, { error: "title is required" });
1808
+ return;
1809
+ }
1810
+ const e = await Y(i), n = JSON.parse(e);
1811
+ if (n.agent && !Mt.includes(n.agent)) {
1812
+ p(r, 400, { error: "agent must be a known agent id" });
1813
+ return;
1814
+ }
1815
+ const l = P(t, "plans"), { entries: c } = await lt(l), d = s.trim(), u = c.find((E) => E.title === d || E.id === d);
1816
+ if (!(u != null && u.id)) {
1817
+ p(r, 404, { error: "plan not found" });
1818
+ return;
1819
+ }
1820
+ const f = $(l, `${u.id}.md`), h = await j(f);
1821
+ if (!h) {
1822
+ p(r, 404, { error: "plan file not found" });
1823
+ return;
1824
+ }
1825
+ const y = mt(h);
1826
+ if (y.entries.length === 0) {
1827
+ p(r, 500, { error: "failed to parse plan file" });
1828
+ return;
1829
+ }
1830
+ const g = {
1831
+ ...y.entries[0],
1832
+ ...n.body !== void 0 && { body: n.body },
1833
+ ...n.status !== void 0 && { status: n.status },
1834
+ ...n.phases !== void 0 && { phases: n.phases },
1835
+ ...n.log !== void 0 && { log: n.log },
1836
+ ...n.agent !== void 0 && { agent: n.agent ?? void 0 },
1837
+ updated: ot()
1838
+ };
1839
+ if (n.status === "done" || n.status === "dropped") {
1840
+ const E = await rt(t, a, u.id);
1841
+ if (E) {
1842
+ p(r, 409, { error: E });
1695
1843
  return;
1696
1844
  }
1697
- const y = await P(N(e, d));
1698
- if (!y) {
1699
- t.statusCode = 404, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "config file not found" }));
1700
- return;
1845
+ }
1846
+ if (n.status === "in-progress") {
1847
+ const E = await lt(l);
1848
+ for (const F of E.entries)
1849
+ if (F.id !== u.id && F.status === "in-progress") {
1850
+ const D = $(l, `${F.id}.md`), S = await j(D);
1851
+ if (S) {
1852
+ const m = mt(S);
1853
+ m.entries.length > 0 && await Rt(
1854
+ D,
1855
+ Nt(m.entries[0], {
1856
+ id: m.entries[0].id ?? F.id,
1857
+ status: "planned",
1858
+ updated: ot()
1859
+ })
1860
+ );
1861
+ }
1862
+ }
1863
+ }
1864
+ if (await Rt(
1865
+ f,
1866
+ Nt(g, { id: g.id ?? u.id })
1867
+ ), await Dt(t), n.status === "done" || n.status === "dropped") {
1868
+ await ye(t, u.id);
1869
+ try {
1870
+ a.ensureBranch(g);
1871
+ } catch {
1701
1872
  }
1702
- t.statusCode = 200, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ name: d, content: y }));
1703
- return;
1704
- } catch (y) {
1705
- t.statusCode = 500, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: y.message }));
1873
+ }
1874
+ p(r, 200, { ok: !0 });
1875
+ }
1876
+ }
1877
+ ];
1878
+ }
1879
+ function Ve({ activity: t, agent: a, git: i, status: r }) {
1880
+ return [
1881
+ // GET /api/status — return current lint/format/test check results
1882
+ {
1883
+ method: "GET",
1884
+ path: "/api/status",
1885
+ handle: (s, e) => {
1886
+ p(e, 200, r.getStatus());
1887
+ }
1888
+ },
1889
+ // POST /api/status/check?name=lint|format|test — trigger a one-off check run
1890
+ {
1891
+ method: "POST",
1892
+ path: "/api/status/check",
1893
+ handle: (s, e) => {
1894
+ const n = vt(s).searchParams.get("name");
1895
+ if (n !== "lint" && n !== "format" && n !== "test") {
1896
+ p(e, 400, { error: "name must be lint, format, or test" });
1706
1897
  return;
1707
1898
  }
1899
+ r.runCheck(n), p(e, 202, { ok: !0 });
1900
+ }
1901
+ },
1902
+ // POST /api/status/fix — run `biome check . --write` to auto-fix lint/format issues
1903
+ {
1904
+ method: "POST",
1905
+ path: "/api/status/fix",
1906
+ handle: (s, e) => {
1907
+ r.runQualityFix(), p(e, 202, { ok: !0 });
1908
+ }
1909
+ },
1910
+ // GET /api/activity/stream — SSE endpoint for live activity events
1911
+ {
1912
+ method: "GET",
1913
+ path: "/api/activity/stream",
1914
+ handle: (s, e) => {
1915
+ e.statusCode = 200, e.setHeader("Content-Type", "text/event-stream"), e.setHeader("Cache-Control", "no-cache"), e.setHeader("Connection", "keep-alive"), e.flushHeaders(), t.subscribe(e), i.subscribe(e), r.subscribe(e), a.subscribe(e);
1916
+ }
1917
+ }
1918
+ ];
1919
+ }
1920
+ const tn = [
1921
+ {
1922
+ path: "/api/package-name",
1923
+ handler: async (t) => {
1924
+ const a = await j($(t, "package.json"));
1925
+ if (!a) return null;
1926
+ try {
1927
+ return JSON.parse(a).name ?? null;
1928
+ } catch {
1929
+ return null;
1930
+ }
1931
+ }
1932
+ },
1933
+ {
1934
+ path: "/api/plans",
1935
+ handler: async (t) => gt(P(t, "plans"), P(t, "plans.md"))
1936
+ },
1937
+ {
1938
+ path: "/api/progress",
1939
+ handler: async (t) => ({
1940
+ entries: $e(await j(P(t, "progress.md")))
1941
+ })
1942
+ },
1943
+ {
1944
+ path: "/api/decisions",
1945
+ handler: async (t) => Kt(await j(P(t, "decisions.md")))
1946
+ },
1947
+ {
1948
+ path: "/api/open-questions",
1949
+ handler: async (t) => jt(await j(P(t, "open-questions.md")))
1950
+ },
1951
+ {
1952
+ path: "/api/ideas",
1953
+ handler: async (t) => Ct(P(t, "ideas"), P(t, "ideas.md"))
1954
+ },
1955
+ {
1956
+ path: "/api/consistency",
1957
+ handler: async (t) => {
1958
+ const [a, i, r] = await Promise.all([
1959
+ j(P(t, "decisions.md")),
1960
+ j(P(t, "open-questions.md")),
1961
+ gt(P(t, "plans"), P(t, "plans.md"))
1962
+ ]), s = Kt(a), e = jt(i);
1963
+ return ke(s.entries, e.entries, r.entries);
1964
+ }
1965
+ },
1966
+ {
1967
+ path: "/api/config",
1968
+ handler: async (t) => {
1969
+ const a = await j($(t, "papercamp", "config.json"));
1970
+ if (!a) return null;
1971
+ const i = JSON.parse(a);
1972
+ return i != null && i.defaultAgents && (i.defaultAgents = {
1973
+ phase: z(i.defaultAgents.phase),
1974
+ planDraft: z(i.defaultAgents.planDraft),
1975
+ ideaExtend: z(i.defaultAgents.ideaExtend),
1976
+ commitSuggest: z(i.defaultAgents.commitSuggest)
1977
+ }), i;
1978
+ }
1979
+ },
1980
+ {
1981
+ path: "/api/docs",
1982
+ handler: async (t) => {
1983
+ const a = ["MAIN.md", "README.md", "CHANGELOG.md", "LICENSE"], i = [];
1984
+ for (const r of a) {
1985
+ const s = await j($(t, r));
1986
+ s && i.push({ name: r, content: s });
1987
+ }
1988
+ return { files: i };
1989
+ }
1990
+ },
1991
+ {
1992
+ path: "/api/configs",
1993
+ handler: async (t) => oe(t)
1994
+ }
1995
+ ];
1996
+ function en(t) {
1997
+ return [
1998
+ ...Ze(t),
1999
+ ...Xe(t),
2000
+ ...Qe(t),
2001
+ ...ze(t),
2002
+ ...Ve(t),
2003
+ ...qe(t),
2004
+ ...Je(t),
2005
+ ...Ge(t),
2006
+ ...Ke(t)
2007
+ ];
2008
+ }
2009
+ const Qt = {
2010
+ lint: "npx biome lint .",
2011
+ format: "npx biome format .",
2012
+ test: "npx vitest run"
2013
+ };
2014
+ function nn(t) {
2015
+ const a = /* @__PURE__ */ new Set(), i = {
2016
+ lint: { status: "stale", lastRun: null, output: "" },
2017
+ format: { status: "stale", lastRun: null, output: "" },
2018
+ test: { status: "stale", lastRun: null, output: "" }
2019
+ }, r = /* @__PURE__ */ new Set(), s = /* @__PURE__ */ new Set();
2020
+ function e(h) {
2021
+ const y = `data: ${JSON.stringify(h)}
2022
+
2023
+ `;
2024
+ for (const g of a)
2025
+ try {
2026
+ g.write(y);
2027
+ } catch {
2028
+ a.delete(g);
2029
+ }
2030
+ }
2031
+ function n(h, y, g) {
2032
+ i[h] = { status: y, lastRun: (/* @__PURE__ */ new Date()).toISOString(), output: g }, e({
2033
+ message: `${h}: ${y}`,
2034
+ timestamp: i[h].lastRun
2035
+ }), y !== "running" && s.has(h) && (s.delete(h), l(h));
2036
+ }
2037
+ function l(h) {
2038
+ var D, S;
2039
+ if (r.has(h)) {
2040
+ s.add(h);
2041
+ return;
1708
2042
  }
1709
- const j = ke.find((r) => r.path === g);
1710
- if (!j) {
1711
- J();
2043
+ r.add(h), n(h, "running", "");
2044
+ const y = Qt[h], g = it(y, {
2045
+ cwd: t,
2046
+ stdio: ["ignore", "pipe", "pipe"],
2047
+ shell: !0
2048
+ });
2049
+ let E = "", F = "";
2050
+ (D = g.stdout) == null || D.on("data", (m) => {
2051
+ E += m.toString();
2052
+ }), (S = g.stderr) == null || S.on("data", (m) => {
2053
+ F += m.toString();
2054
+ }), g.on("close", (m) => {
2055
+ r.delete(h);
2056
+ const w = E + F;
2057
+ m === 0 ? n(h, "pass", w) : n(h, "fail", w);
2058
+ }), g.on("error", (m) => {
2059
+ r.delete(h), n(h, "fail", `Failed to spawn process: ${m.message}`);
2060
+ });
2061
+ }
2062
+ function c() {
2063
+ if (r.has("lint") || r.has("format")) return;
2064
+ n("lint", "running", "Applying automatic fixes…"), n("format", "running", "Applying automatic fixes…");
2065
+ const h = it("npx biome check . --write", {
2066
+ cwd: t,
2067
+ stdio: ["ignore", "pipe", "pipe"],
2068
+ shell: !0
2069
+ });
2070
+ h.on("close", () => {
2071
+ l("lint"), l("format");
2072
+ }), h.on("error", (y) => {
2073
+ const g = `Failed to spawn fix process: ${y.message}`;
2074
+ n("lint", "fail", g), n("format", "fail", g);
2075
+ });
2076
+ }
2077
+ const d = $(t, "src");
2078
+ let u = null;
2079
+ try {
2080
+ Ot(d, { recursive: !0 }, () => {
2081
+ u && clearTimeout(u), u = setTimeout(() => {
2082
+ l("lint"), l("format");
2083
+ }, 1e3);
2084
+ });
2085
+ } catch {
2086
+ }
2087
+ function f() {
2088
+ return new Promise((h) => {
2089
+ const y = ["lint", "format", "test"], g = { lint: null, format: null, test: null };
2090
+ let E = y.length;
2091
+ function F(D, S) {
2092
+ g[D] = S, E--, E === 0 && h(y.every((m) => g[m] === !0));
2093
+ }
2094
+ for (const D of y) {
2095
+ const S = it(Qt[D], { cwd: t, stdio: "ignore", shell: !0 });
2096
+ S.on("close", (m) => F(D, m === 0)), S.on("error", () => F(D, !1));
2097
+ }
2098
+ });
2099
+ }
2100
+ return {
2101
+ getStatus() {
2102
+ return {
2103
+ lint: { ...i.lint },
2104
+ format: { ...i.format },
2105
+ test: { ...i.test }
2106
+ };
2107
+ },
2108
+ runCheck: l,
2109
+ runChecksAndWait: f,
2110
+ runQualityFix: c,
2111
+ subscribe(h) {
2112
+ a.add(h);
2113
+ for (const y of ["lint", "format", "test"]) {
2114
+ const g = i[y];
2115
+ g.status !== "stale" && h.write(
2116
+ `data: ${JSON.stringify({ message: `${y}: ${g.status}`, timestamp: g.lastRun })}
2117
+
2118
+ `
2119
+ );
2120
+ }
2121
+ h.on("close", () => a.delete(h));
2122
+ }
2123
+ };
2124
+ }
2125
+ function an(t) {
2126
+ const a = Re(t), i = _e(t), r = nn(t), s = Fe(t, i), e = Ce(
2127
+ t,
2128
+ (c) => i.ensureBranch(c),
2129
+ s.stampAuditDate,
2130
+ s.commitPhase,
2131
+ s.setRunReview
2132
+ ), n = en({ root: t, activity: a, agent: e, git: i, status: r }), l = async (c, d, u) => {
2133
+ const f = (c.url ?? "").split("?")[0], h = n.find((g) => g.method === c.method && g.path === f);
2134
+ if (h) {
2135
+ try {
2136
+ await h.handle(c, d);
2137
+ } catch (g) {
2138
+ p(d, 500, { error: g.message });
2139
+ }
2140
+ return;
2141
+ }
2142
+ const y = tn.find((g) => g.path === f);
2143
+ if (!y) {
2144
+ u();
1712
2145
  return;
1713
2146
  }
1714
2147
  try {
1715
- const r = await j.handler(e);
1716
- t.statusCode = 200, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify(r));
1717
- } catch (r) {
1718
- t.statusCode = 500, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: r.message }));
2148
+ p(d, 200, await y.handler(t));
2149
+ } catch (g) {
2150
+ p(d, 500, { error: g.message });
1719
2151
  }
1720
2152
  };
1721
- return $.agent = m, $;
2153
+ return l.agent = e, l;
1722
2154
  }
1723
- const xe = {
2155
+ const sn = {
1724
2156
  ".html": "text/html; charset=utf-8",
1725
2157
  ".js": "text/javascript; charset=utf-8",
1726
2158
  ".mjs": "text/javascript; charset=utf-8",
@@ -1733,301 +2165,304 @@ const xe = {
1733
2165
  ".woff": "font/woff",
1734
2166
  ".woff2": "font/woff2"
1735
2167
  };
1736
- function Je() {
1737
- return N(zt(de(import.meta.url)), "..", "app");
2168
+ function rn() {
2169
+ return $(de(Ie(import.meta.url)), "..", "app");
1738
2170
  }
1739
- async function Ie({ root: e, port: i }) {
1740
- const a = Je(), p = N(a, "index.html"), n = await U(p, "utf-8").catch(() => null);
1741
- if (n === null)
2171
+ async function on({ root: t, port: a }) {
2172
+ const i = rn(), r = $(i, "index.html"), s = await at(r, "utf-8").catch(() => null);
2173
+ if (s === null)
1742
2174
  throw new Error(
1743
- `Dashboard assets not found at ${a}. Run \`pnpm build\` (or reinstall the package) so dist/app exists.`
2175
+ `Dashboard assets not found at ${i}. Run \`pnpm build\` (or reinstall the package) so dist/app exists.`
1744
2176
  );
1745
- const h = ve(e);
1746
- async function m(t, J) {
1747
- const g = decodeURIComponent((t.url ?? "/").split("?")[0]), j = N(a, g === "/" ? "index.html" : g);
2177
+ const e = an(t);
2178
+ async function n(d, u) {
2179
+ const f = decodeURIComponent((d.url ?? "/").split("?")[0]), h = $(i, f === "/" ? "index.html" : f), y = h !== i && !h.startsWith(i + le);
1748
2180
  try {
1749
- if ((await it(j)).isFile()) {
1750
- J.statusCode = 200, J.setHeader("Content-Type", xe[Yt(j)] ?? "application/octet-stream"), J.end(await U(j));
2181
+ const g = y ? null : await ht(h);
2182
+ if (g != null && g.isFile()) {
2183
+ u.statusCode = 200, u.setHeader("Content-Type", sn[ue(h)] ?? "application/octet-stream"), u.end(await at(h));
1751
2184
  return;
1752
2185
  }
1753
2186
  } catch {
1754
2187
  }
1755
- J.statusCode = 200, J.setHeader("Content-Type", "text/html; charset=utf-8"), J.end(n);
2188
+ u.statusCode = 200, u.setHeader("Content-Type", "text/html; charset=utf-8"), u.end(s);
1756
2189
  }
1757
- const $ = re((t, J) => {
1758
- h(t, J, () => {
1759
- m(t, J).catch((g) => {
1760
- J.statusCode = 500, J.end(String(g));
2190
+ const l = xe((d, u) => {
2191
+ e(d, u, () => {
2192
+ n(d, u).catch((f) => {
2193
+ u.statusCode = 500, u.end(String(f));
1761
2194
  });
1762
2195
  });
1763
- }), u = () => {
1764
- h.agent.killCurrent(), process.exit(0);
2196
+ }), c = () => {
2197
+ e.agent.killCurrent(), process.exit(0);
1765
2198
  };
1766
- process.on("SIGINT", u), process.on("SIGTERM", u), await new Promise((t) => $.listen(i, t));
2199
+ process.on("SIGINT", c), process.on("SIGTERM", c), await new Promise((d, u) => {
2200
+ l.once("error", u), l.listen(a, d);
2201
+ });
1767
2202
  }
1768
- async function $t(e) {
2203
+ async function Bt(t) {
1769
2204
  try {
1770
- return await it(e), !0;
2205
+ return await ht(t), !0;
1771
2206
  } catch {
1772
2207
  return !1;
1773
2208
  }
1774
2209
  }
1775
- async function He(e, i) {
1776
- const a = N(e, `${i}.md`);
1777
- if (await $t(a)) return a;
1778
- const p = N(e, "archive", `${i}.md`);
1779
- return await $t(p) ? p : null;
2210
+ async function cn(t, a) {
2211
+ const i = $(t, `${a}.md`);
2212
+ if (await Bt(i)) return i;
2213
+ const r = $(t, "archive", `${a}.md`);
2214
+ return await Bt(r) ? r : null;
1780
2215
  }
1781
- async function Ae(e) {
2216
+ async function dn(t) {
1782
2217
  try {
1783
- return (await it(e)).mtime.toISOString().slice(0, 10);
2218
+ return (await ht(t)).mtime.toISOString().slice(0, 10);
1784
2219
  } catch {
1785
2220
  return null;
1786
2221
  }
1787
2222
  }
1788
- async function Pe(e, i) {
1789
- const a = await U(e, "utf-8"), n = dt(a).entries[0];
1790
- if (!n)
1791
- throw new Error(`Could not parse plan file after audit: ${e}`);
1792
- const h = {
1793
- id: i,
1794
- title: n.title,
1795
- kind: n.kind ?? "feat",
1796
- status: n.status,
1797
- idea: n.idea,
1798
- agent: n.agent,
1799
- created: n.created,
1800
- updated: n.updated,
1801
- audited: Z(),
1802
- tags: n.tags,
1803
- body: n.body,
1804
- phases: n.phases,
1805
- log: n.log,
1806
- clarifications: n.clarifications
2223
+ async function ln(t, a) {
2224
+ const i = await at(t, "utf-8"), s = mt(i).entries[0];
2225
+ if (!s)
2226
+ throw new Error(`Could not parse plan file after audit: ${t}`);
2227
+ const e = {
2228
+ id: a,
2229
+ title: s.title,
2230
+ kind: s.kind ?? "feat",
2231
+ status: s.status,
2232
+ idea: s.idea,
2233
+ agent: s.agent,
2234
+ created: s.created,
2235
+ updated: s.updated,
2236
+ audited: ot(),
2237
+ tags: s.tags,
2238
+ body: s.body,
2239
+ phases: s.phases,
2240
+ log: s.log,
2241
+ clarifications: s.clarifications
1807
2242
  };
1808
- await E(e, `${at(h)}
2243
+ await q(t, `${xt(e)}
1809
2244
  `, "utf-8");
1810
2245
  }
1811
- async function Ee(e, i, a, p) {
1812
- var m;
1813
- const n = Mt(i), h = V(a.command, a.buildArgs(n, p), {
1814
- cwd: e,
2246
+ async function un(t, a, i, r) {
2247
+ var n;
2248
+ const s = ie(a), e = it(i.command, i.buildArgs(s, r), {
2249
+ cwd: t,
1815
2250
  stdio: ["ignore", "pipe", "pipe"]
1816
2251
  });
1817
- return h.stdout && jt({ input: h.stdout }).on("line", (u) => {
1818
- const t = a.parseLine(u);
1819
- t != null && t.text && t.text !== "Agent is working…" && process.stdout.write(` ${t.text}
2252
+ return e.stdout && Tt({ input: e.stdout }).on("line", (c) => {
2253
+ const d = i.parseLine(c);
2254
+ d != null && d.text && d.text !== "Agent is working…" && process.stdout.write(` ${d.text}
1820
2255
  `);
1821
- }), (m = h.stderr) == null || m.on("data", ($) => process.stderr.write($)), new Promise(($) => {
1822
- h.on("close", (u) => $(u === 0)), h.on("error", () => $(!1));
2256
+ }), (n = e.stderr) == null || n.on("data", (l) => process.stderr.write(l)), new Promise((l) => {
2257
+ e.on("close", (c) => l(c === 0)), e.on("error", () => l(!1));
1823
2258
  });
1824
2259
  }
1825
- const st = new Xt();
1826
- st.name("paper-camp").description("Local-first, AI-native project companion.").version(ie);
1827
- st.command("init [project-name]").description("Initialize Paper Camp in the current directory").option("-i, --intent <text>", "one-line description of what you are building").action(async (e, i) => {
1828
- const a = process.cwd(), p = e ?? Qt(a);
2260
+ const yt = new fe();
2261
+ yt.name("paper-camp").description("Local-first, AI-native project companion.").version(be);
2262
+ yt.command("init [project-name]").description("Initialize Paper Camp in the current directory").option("-i, --intent <text>", "one-line description of what you are building").action(async (t, a) => {
2263
+ const i = process.cwd(), r = t ?? pe(i);
1829
2264
  try {
1830
- await se(a, { projectName: p, intent: i.intent }), console.log(`Initialized Paper Camp in ${a}`), console.log(" papercamp/config.json"), console.log(" papercamp/plans/ (per-file plan entries)"), console.log(" papercamp/plans/index.md"), console.log(" papercamp/plans/archive/"), console.log(" papercamp/ideas/ (per-file idea entries)"), console.log(" papercamp/ideas/index.md"), console.log(" papercamp/progress.md, decisions.md, open-questions.md");
1831
- } catch (n) {
1832
- if (n instanceof oe) {
1833
- console.error(n.message), process.exitCode = 1;
2265
+ await Se(i, { projectName: r, intent: a.intent }), console.log(`Initialized Paper Camp in ${i}`), console.log(" papercamp/config.json"), console.log(" papercamp/plans/ (per-file plan entries)"), console.log(" papercamp/plans/index.md"), console.log(" papercamp/plans/archive/"), console.log(" papercamp/ideas/ (per-file idea entries)"), console.log(" papercamp/ideas/index.md"), console.log(" papercamp/progress.md, decisions.md, open-questions.md");
2266
+ } catch (s) {
2267
+ if (s instanceof ve) {
2268
+ console.error(s.message), process.exitCode = 1;
1834
2269
  return;
1835
2270
  }
1836
- throw n;
2271
+ throw s;
1837
2272
  }
1838
2273
  });
1839
- st.command("dev").description("Start the local dashboard").option("-p, --port <number>", "port to listen on", "3333").action(async (e) => {
1840
- const i = Number(e.port), a = process.cwd();
2274
+ yt.command("dev").description("Start the local dashboard").option("-p, --port <number>", "port to listen on", "3333").action(async (t) => {
2275
+ const a = Number(t.port), i = process.cwd();
1841
2276
  try {
1842
- await Ie({ root: a, port: i }), console.log(`Paper Camp dashboard running at http://localhost:${i}`);
1843
- } catch (p) {
1844
- console.error(p.message), process.exitCode = 1;
2277
+ await on({ root: i, port: a }), console.log(`Paper Camp dashboard running at http://localhost:${a}`);
2278
+ } catch (r) {
2279
+ console.error(r.message), process.exit(1);
1845
2280
  }
1846
2281
  });
1847
- st.command("add <type> [name]").description("Add a new entry (currently supports: plan)").option("-k, --kind <kind>", `plan kind (${wt.join("|")})`, "feat").action(async (e, i, a) => {
1848
- if (e !== "plan") {
1849
- console.error(`Unknown type "${e}". Supported types: plan`), process.exitCode = 1;
2282
+ yt.command("add <type> [name]").description("Add a new entry (currently supports: plan)").option("-k, --kind <kind>", `plan kind (${Et.join("|")})`, "feat").action(async (t, a, i) => {
2283
+ if (t !== "plan") {
2284
+ console.error(`Unknown type "${t}". Supported types: plan`), process.exitCode = 1;
1850
2285
  return;
1851
2286
  }
1852
- if (!i) {
2287
+ if (!a) {
1853
2288
  console.error("Usage: paper-camp add plan <name> [--kind feat|fix|chore|docs|refactor]"), process.exitCode = 1;
1854
2289
  return;
1855
2290
  }
1856
- if (!wt.includes(a.kind)) {
1857
- console.error(`Unknown kind "${a.kind}". Supported kinds: ${wt.join(", ")}`), process.exitCode = 1;
2291
+ if (!Et.includes(i.kind)) {
2292
+ console.error(`Unknown kind "${i.kind}". Supported kinds: ${Et.join(", ")}`), process.exitCode = 1;
1858
2293
  return;
1859
2294
  }
1860
- const p = a.kind, n = process.cwd(), h = nt(n, "papercamp", "config.json"), m = await Bt(h, p);
1861
- if (!m) {
2295
+ const r = i.kind, s = process.cwd(), e = ft(s, "papercamp", "config.json"), n = await ae(e, r);
2296
+ if (!n) {
1862
2297
  console.error("Could not assign plan ID — is the project initialized?"), process.exitCode = 1;
1863
2298
  return;
1864
2299
  }
1865
- const $ = nt(n, "papercamp", "plans");
1866
- await q($, { recursive: !0 });
1867
- const u = at({
1868
- id: m,
1869
- title: i,
1870
- kind: p,
2300
+ const l = ft(s, "papercamp", "plans");
2301
+ await ut(l, { recursive: !0 });
2302
+ const c = xt({
2303
+ id: n,
2304
+ title: a,
2305
+ kind: r,
1871
2306
  status: "idea",
1872
- created: Z()
2307
+ created: ot()
1873
2308
  });
1874
- await E(N($, `${m}.md`), `${u}
2309
+ await q($(l, `${n}.md`), `${c}
1875
2310
  `, "utf-8");
1876
- const { entries: t } = await Q($);
1877
- await E(N($, "index.md"), Ht(t), "utf-8"), console.log(`Added plan "${i}" (${m}) to papercamp/plans/${m}.md`);
2311
+ const { entries: d } = await lt(l);
2312
+ await q($(l, "index.md"), Lt(d), "utf-8"), console.log(`Added plan "${a}" (${n}) to papercamp/plans/${n}.md`);
1878
2313
  });
1879
- st.command("migrate").description(
2314
+ yt.command("migrate").description(
1880
2315
  "One-time migration: split monolithic plans.md/ideas.md into per-file YAML frontmatter entries"
1881
2316
  ).action(async () => {
1882
- const e = process.cwd(), i = nt(e, "papercamp", "plans"), a = N(i, "archive"), p = nt(e, "papercamp", "ideas");
1883
- await q(a, { recursive: !0 }), await q(p, { recursive: !0 });
1884
- const n = nt(e, "papercamp", "plans.md"), h = nt(e, "papercamp", "ideas.md");
1885
- let m = 0, $ = 0, u = 0;
1886
- const t = await U(n, "utf-8").catch(() => "");
1887
- if (t.trim()) {
1888
- const { entries: l, warnings: T } = vt(t);
1889
- u = T.length;
1890
- for (const c of T)
1891
- console.warn(` warning: ${c.title}: ${c.message}`);
1892
- for (const c of l) {
1893
- if (!c.id) {
1894
- console.warn(` skipping plan "${c.title}" — no Id assigned, cannot migrate`), $++;
2317
+ const t = process.cwd(), a = ft(t, "papercamp", "plans"), i = $(a, "archive"), r = ft(t, "papercamp", "ideas");
2318
+ await ut(i, { recursive: !0 }), await ut(r, { recursive: !0 });
2319
+ const s = ft(t, "papercamp", "plans.md"), e = ft(t, "papercamp", "ideas.md");
2320
+ let n = 0, l = 0, c = 0;
2321
+ const d = await at(s, "utf-8").catch(() => "");
2322
+ if (d.trim()) {
2323
+ const { entries: F, warnings: D } = Vt(d);
2324
+ c = D.length;
2325
+ for (const S of D)
2326
+ console.warn(` warning: ${S.title}: ${S.message}`);
2327
+ for (const S of F) {
2328
+ if (!S.id) {
2329
+ console.warn(` skipping plan "${S.title}" — no Id assigned, cannot migrate`), l++;
1895
2330
  continue;
1896
2331
  }
1897
- const o = c.status === "done" || c.status === "dropped" ? a : i, f = N(o, `${c.id}.md`);
1898
- if (await $t(f)) {
1899
- $++;
2332
+ const m = S.status === "done" || S.status === "dropped" ? i : a, w = $(m, `${S.id}.md`);
2333
+ if (await Bt(w)) {
2334
+ l++;
1900
2335
  continue;
1901
2336
  }
1902
- const S = at({
1903
- id: c.id,
1904
- title: c.title,
1905
- kind: c.kind ?? "feat",
1906
- status: c.status,
1907
- idea: c.idea,
1908
- agent: c.agent,
1909
- created: c.created,
1910
- updated: c.updated,
1911
- tags: c.tags,
1912
- body: c.body,
1913
- phases: c.phases,
1914
- log: c.log,
1915
- clarifications: c.clarifications
2337
+ const I = xt({
2338
+ id: S.id,
2339
+ title: S.title,
2340
+ kind: S.kind ?? "feat",
2341
+ status: S.status,
2342
+ idea: S.idea,
2343
+ agent: S.agent,
2344
+ created: S.created,
2345
+ updated: S.updated,
2346
+ tags: S.tags,
2347
+ body: S.body,
2348
+ phases: S.phases,
2349
+ log: S.log,
2350
+ clarifications: S.clarifications
1916
2351
  });
1917
- await E(f, `${S}
1918
- `, "utf-8"), m++;
2352
+ await q(w, `${I}
2353
+ `, "utf-8"), n++;
1919
2354
  }
1920
2355
  }
1921
- let J = 0, g = 0;
1922
- const j = await U(h, "utf-8").catch(() => "");
1923
- if (j.trim()) {
1924
- const l = Jt(j);
1925
- for (const T of l) {
1926
- if (!T.id) {
1927
- console.warn(` skipping idea "${T.title}" — no Id assigned, cannot migrate`), g++;
2356
+ let u = 0, f = 0;
2357
+ const h = await at(e, "utf-8").catch(() => "");
2358
+ if (h.trim()) {
2359
+ const F = ee(h);
2360
+ for (const D of F) {
2361
+ if (!D.id) {
2362
+ console.warn(` skipping idea "${D.title}" — no Id assigned, cannot migrate`), f++;
1928
2363
  continue;
1929
2364
  }
1930
- const c = N(p, `${T.id}.md`);
1931
- if (await $t(c)) {
1932
- g++;
2365
+ const S = $(r, `${D.id}.md`);
2366
+ if (await Bt(S)) {
2367
+ f++;
1933
2368
  continue;
1934
2369
  }
1935
- const o = T.body.replace(/^#{1,3}\s+.+(?:\r?\n)?/, "").trim(), f = Ft({ id: T.id, title: T.title, body: o });
1936
- await E(c, `${f}
1937
- `, "utf-8"), J++;
2370
+ const m = D.body.replace(/^#{1,3}\s+.+(?:\r?\n)?/, "").trim(), w = ne({ id: D.id, title: D.title, body: m });
2371
+ await q(S, `${w}
2372
+ `, "utf-8"), u++;
1938
2373
  }
1939
2374
  }
1940
- const { entries: r } = await Q(i);
1941
- await E(N(i, "index.md"), Ht(r), "utf-8");
1942
- const { entries: d } = await xt(p), y = Kt(d, r);
1943
- await E(N(p, "index.md"), _t(y), "utf-8"), m > 0 && $ === 0 && u === 0 && await E(n, "", "utf-8"), J > 0 && g === 0 && await E(h, "", "utf-8"), console.log(
1944
- `Migrated ${m} plans (${$} skipped), ${J} ideas (${g} skipped).`
2375
+ const { entries: y } = await lt(a);
2376
+ await q($(a, "index.md"), Lt(y), "utf-8");
2377
+ const { entries: g } = await te(r), E = se(g, y);
2378
+ await q($(r, "index.md"), Zt(E), "utf-8"), n > 0 && l === 0 && c === 0 && await q(s, "", "utf-8"), u > 0 && f === 0 && await q(e, "", "utf-8"), console.log(
2379
+ `Migrated ${n} plans (${l} skipped), ${u} ideas (${f} skipped).`
1945
2380
  );
1946
2381
  });
1947
- st.command("audit").description("Audit all review/done plans for missing phases").action(async () => {
1948
- var c;
1949
- const e = process.cwd(), i = nt(e, "papercamp", "plans"), { entries: a, warnings: p } = await Q(i);
1950
- for (const o of p)
1951
- console.warn(` warning: ${o.title}: ${o.message}`);
1952
- const n = a.filter((o) => o.status === "review" || o.status === "done");
1953
- if (n.length === 0) {
2382
+ yt.command("audit").description("Audit all review/done plans for missing phases").action(async () => {
2383
+ var S;
2384
+ const t = process.cwd(), a = ft(t, "papercamp", "plans"), { entries: i, warnings: r } = await lt(a);
2385
+ for (const m of r)
2386
+ console.warn(` warning: ${m.title}: ${m.message}`);
2387
+ const s = i.filter((m) => m.status === "review" || m.status === "done");
2388
+ if (s.length === 0) {
1954
2389
  console.log('No plans with status "review" or "done" found.');
1955
2390
  return;
1956
2391
  }
1957
- const h = await U(N(e, "papercamp", "config.json"), "utf-8").catch(
2392
+ const e = await at($(t, "papercamp", "config.json"), "utf-8").catch(
1958
2393
  () => "{}"
1959
2394
  );
1960
- let m;
2395
+ let n;
1961
2396
  try {
1962
- m = JSON.parse(h);
2397
+ n = JSON.parse(e);
1963
2398
  } catch {
1964
2399
  console.error("Invalid papercamp/config.json"), process.exitCode = 1;
1965
2400
  return;
1966
2401
  }
1967
- const $ = m.defaultAgents, u = $ ? {
1968
- phase: B($.phase),
1969
- planDraft: B($.planDraft),
1970
- ideaExtend: B($.ideaExtend),
1971
- commitSuggest: B($.commitSuggest)
1972
- } : kt, { adapter: t, model: J, effort: g } = St({ defaultAgents: u, taskKind: "audit" });
1973
- console.log(`Auditing ${n.length} plan(s):
2402
+ const l = n.defaultAgents, c = l ? {
2403
+ phase: z(l.phase),
2404
+ planDraft: z(l.planDraft),
2405
+ ideaExtend: z(l.ideaExtend),
2406
+ commitSuggest: z(l.commitSuggest)
2407
+ } : _t, { adapter: d, model: u, effort: f } = St({ defaultAgents: c, taskKind: "audit" });
2408
+ console.log(`Auditing ${s.length} plan(s):
1974
2409
  `);
1975
- const j = [];
1976
- for (const o of n) {
1977
- const f = o.id ?? "(no id)", S = f.padEnd(14);
1978
- if (!o.id) {
1979
- console.log(` [skip] ${S} ${o.title} — no id`), j.push({ id: f, title: o.title, status: "skipped", skipReason: "no id" });
2410
+ const h = [];
2411
+ for (const m of s) {
2412
+ const w = m.id ?? "(no id)", I = w.padEnd(14);
2413
+ if (!m.id) {
2414
+ console.log(` [skip] ${I} ${m.title} — no id`), h.push({ id: w, title: m.title, status: "skipped", skipReason: "no id" });
1980
2415
  continue;
1981
2416
  }
1982
- const b = await He(i, o.id);
1983
- if (!b) {
1984
- console.log(` [skip] ${S} ${o.title} — file not found`), j.push({ id: f, title: o.title, status: "skipped", skipReason: "file not found" });
2417
+ const T = await cn(a, m.id);
2418
+ if (!T) {
2419
+ console.log(` [skip] ${I} ${m.title} — file not found`), h.push({ id: w, title: m.title, status: "skipped", skipReason: "file not found" });
1985
2420
  continue;
1986
2421
  }
1987
- if (o.audited) {
1988
- const s = await Ae(b);
1989
- if (s && o.audited >= s) {
2422
+ if (m.audited) {
2423
+ const B = await dn(T);
2424
+ if (B && m.audited >= B) {
1990
2425
  console.log(
1991
- ` [skip] ${S} ${o.title} — audited ${o.audited}, unchanged since`
1992
- ), j.push({
1993
- id: f,
1994
- title: o.title,
2426
+ ` [skip] ${I} ${m.title} — audited ${m.audited}, unchanged since`
2427
+ ), h.push({
2428
+ id: w,
2429
+ title: m.title,
1995
2430
  status: "skipped",
1996
- skipReason: `audited ${o.audited}, unchanged`
2431
+ skipReason: `audited ${m.audited}, unchanged`
1997
2432
  });
1998
2433
  continue;
1999
2434
  }
2000
2435
  }
2001
- const I = o.phases.length;
2002
- if (console.log(` [audit] ${S} ${o.title}`), await Ee(e, o, t, { model: J, effort: g })) {
2436
+ const O = m.phases.length;
2437
+ if (console.log(` [audit] ${I} ${m.title}`), await un(t, m, d, { model: u, effort: f })) {
2003
2438
  try {
2004
- await Pe(b, o.id);
2005
- } catch (A) {
2006
- console.log(` [fail] ${S} ${o.title} — ${A.message}`), process.exitCode = 1, j.push({ id: f, title: o.title, status: "failed" });
2439
+ await ln(T, m.id);
2440
+ } catch (o) {
2441
+ console.log(` [fail] ${I} ${m.title} — ${o.message}`), process.exitCode = 1, h.push({ id: w, title: m.title, status: "failed" });
2007
2442
  continue;
2008
2443
  }
2009
- const s = await U(b, "utf-8").catch(() => ""), C = ((c = dt(s).entries[0]) == null ? void 0 : c.phases.length) ?? I, O = Math.max(0, C - I);
2010
- console.log(` [done] ${S} ${o.title}`), j.push({ id: f, title: o.title, status: "audited", gapPhases: O });
2444
+ const B = await at(T, "utf-8").catch(() => ""), _ = ((S = mt(B).entries[0]) == null ? void 0 : S.phases.length) ?? O, R = Math.max(0, _ - O);
2445
+ console.log(` [done] ${I} ${m.title}`), h.push({ id: w, title: m.title, status: "audited", gapPhases: R });
2011
2446
  } else
2012
- console.log(` [fail] ${S} ${o.title} — agent exited with error`), process.exitCode = 1, j.push({ id: f, title: o.title, status: "failed" });
2447
+ console.log(` [fail] ${I} ${m.title} — agent exited with error`), process.exitCode = 1, h.push({ id: w, title: m.title, status: "failed" });
2013
2448
  }
2014
- const r = j.filter((o) => o.status === "audited"), d = j.filter((o) => o.status === "skipped"), y = j.filter((o) => o.status === "failed"), l = r.reduce((o, f) => o + (f.gapPhases ?? 0), 0), T = "─".repeat(43);
2449
+ const y = h.filter((m) => m.status === "audited"), g = h.filter((m) => m.status === "skipped"), E = h.filter((m) => m.status === "failed"), F = y.reduce((m, w) => m + (w.gapPhases ?? 0), 0), D = "─".repeat(43);
2015
2450
  if (console.log(`
2016
- ${T}`), console.log("Audit summary"), console.log(
2017
- ` Audited : ${r.length} Skipped : ${d.length} Failed : ${y.length}`
2018
- ), r.length > 0)
2019
- if (l > 0) {
2020
- console.log(` Gap phases appended: ${l} total`);
2021
- for (const o of r.filter((f) => (f.gapPhases ?? 0) > 0))
2022
- console.log(` ${o.id.padEnd(14)} +${o.gapPhases} phase(s)`);
2451
+ ${D}`), console.log("Audit summary"), console.log(
2452
+ ` Audited : ${y.length} Skipped : ${g.length} Failed : ${E.length}`
2453
+ ), y.length > 0)
2454
+ if (F > 0) {
2455
+ console.log(` Gap phases appended: ${F} total`);
2456
+ for (const m of y.filter((w) => (w.gapPhases ?? 0) > 0))
2457
+ console.log(` ${m.id.padEnd(14)} +${m.gapPhases} phase(s)`);
2023
2458
  } else
2024
2459
  console.log(" No gap phases appended — all audited plans are complete.");
2025
- if (d.length > 0) {
2460
+ if (g.length > 0) {
2026
2461
  console.log(" Skipped:");
2027
- for (const o of d)
2028
- console.log(` ${o.id.padEnd(14)} ${o.skipReason}`);
2462
+ for (const m of g)
2463
+ console.log(` ${m.id.padEnd(14)} ${m.skipReason}`);
2029
2464
  }
2030
- console.log(T);
2465
+ console.log(D);
2031
2466
  });
2032
- st.parseAsync(process.argv);
2467
+ yt.parseAsync(process.argv);
2033
2468
  //# sourceMappingURL=index.js.map