@dendelion/paper-camp 0.2.1 → 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.
Files changed (40) hide show
  1. package/README.md +16 -7
  2. package/dist/app/assets/main-6nYButky.js +232 -0
  3. package/dist/app/assets/main-6nYButky.js.map +1 -0
  4. package/dist/app/assets/main-DUnpdvQF.css +1 -0
  5. package/dist/app/index.html +2 -2
  6. package/dist/chunks/serializer.B8u-L8BI.js +5249 -0
  7. package/dist/chunks/serializer.B8u-L8BI.js.map +1 -0
  8. package/dist/cli/index.js +2150 -932
  9. package/dist/cli/index.js.map +1 -1
  10. package/dist/core/frontmatter-schemas.d.ts +11 -0
  11. package/dist/core/frontmatter-schemas.d.ts.map +1 -0
  12. package/dist/core/frontmatter.test.d.ts +2 -0
  13. package/dist/core/frontmatter.test.d.ts.map +1 -0
  14. package/dist/core/idea-status.d.ts +7 -0
  15. package/dist/core/idea-status.d.ts.map +1 -0
  16. package/dist/core/index.d.ts +1 -0
  17. package/dist/core/index.d.ts.map +1 -1
  18. package/dist/core/index.js +48 -29
  19. package/dist/core/parser.d.ts +24 -3
  20. package/dist/core/parser.d.ts.map +1 -1
  21. package/dist/core/readers.d.ts +27 -0
  22. package/dist/core/readers.d.ts.map +1 -0
  23. package/dist/core/readers.test.d.ts +2 -0
  24. package/dist/core/readers.test.d.ts.map +1 -0
  25. package/dist/core/scaffold.d.ts +4 -2
  26. package/dist/core/scaffold.d.ts.map +1 -1
  27. package/dist/core/schemas.d.ts +75 -12
  28. package/dist/core/schemas.d.ts.map +1 -1
  29. package/dist/core/serializer.d.ts +52 -2
  30. package/dist/core/serializer.d.ts.map +1 -1
  31. package/dist/types/index.d.ts +24 -4
  32. package/dist/types/index.d.ts.map +1 -1
  33. package/dist/types/index.js +32 -10
  34. package/dist/types/index.js.map +1 -1
  35. package/package.json +2 -1
  36. package/dist/app/assets/main-B61tEYvn.css +0 -1
  37. package/dist/app/assets/main-tAEncq68.js +0 -272
  38. package/dist/app/assets/main-tAEncq68.js.map +0 -1
  39. package/dist/chunks/serializer.D1Ts_p8h.js +0 -462
  40. package/dist/chunks/serializer.D1Ts_p8h.js.map +0 -1
package/dist/cli/index.js CHANGED
@@ -1,150 +1,95 @@
1
1
  #!/usr/bin/env node
2
- import { join as O, dirname as ct, extname as dt, basename as pt, resolve as Y } from "node:path";
3
- import { Command as ut } from "commander";
4
- import { t as nt, r as z, m as Q, s as k, q as M, j as Z, c as at, i as it, w as B, a as ot, n as V, b as lt, f as ft, P as gt, l as mt, A as yt } from "../chunks/serializer.D1Ts_p8h.js";
5
- import { DEFAULT_AGENTS as X, PLAN_KINDS as F, AGENT_IDS as U } from "../types/index.js";
6
- import { readFile as $, writeFile as I, mkdir as ht, stat as st } from "node:fs/promises";
7
- import { createServer as Ct } from "node:http";
8
- import { fileURLToPath as wt } from "node:url";
9
- import { watch as G, readFileSync as St } from "node:fs";
10
- import { spawn as K, spawnSync as L } from "node:child_process";
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";
11
5
  import { createInterface as Tt } from "node:readline";
12
- const E = (n, c) => O(n, "papercamp", c), _ = (n) => $(n, "utf-8").catch(() => "");
13
- async function q(n) {
14
- const [c, o, s, u] = await Promise.all([
15
- _(E(n, "plans.md")),
16
- _(E(n, "decisions.md")),
17
- _(E(n, "open-questions.md")),
18
- _(E(n, "progress.md"))
19
- ]);
20
- return {
21
- plans: k(c).entries,
22
- decisions: Q(o).entries,
23
- openQuestions: z(s).entries,
24
- progress: nt(u)
25
- };
26
- }
27
- function Nt(n, c) {
28
- const o = [], s = (/* @__PURE__ */ new Date()).toISOString(), u = new Map(n.plans.map((e) => [e.title, e])), y = new Map(c.plans.map((e) => [e.title, e]));
29
- for (const [e, d] of y) {
30
- const f = u.get(e);
31
- if (!f)
32
- o.push({ message: `New plan added: ${e}`, timestamp: s });
33
- else if (f.status !== d.status)
34
- o.push({ message: `Plan "${e}" marked ${d.status}`, timestamp: s });
35
- else
36
- for (let p = 0; p < d.phases.length; p++) {
37
- const l = f.phases[p], m = d.phases[p];
38
- l && l.done !== m.done && o.push({
39
- message: m.done ? `Phase ${p + 1}/${d.phases.length} checked off in "${e}"` : `Phase ${p + 1}/${d.phases.length} unchecked in "${e}"`,
40
- timestamp: s
41
- });
42
- }
43
- }
44
- for (const [e] of u)
45
- y.has(e) || o.push({ message: `Plan removed: ${e}`, timestamp: s });
46
- const r = new Map(n.decisions.map((e) => [e.title, e])), t = new Map(c.decisions.map((e) => [e.title, e]));
47
- for (const [e] of t)
48
- r.has(e) || o.push({ message: `New decision: ${e}`, timestamp: s });
49
- const b = new Map(n.openQuestions.map((e) => [e.title, e])), C = new Map(c.openQuestions.map((e) => [e.title, e]));
50
- for (const [e] of C)
51
- b.has(e) || o.push({ message: `New open question: ${e}`, timestamp: s });
52
- const S = new Map(n.progress.map((e) => [e.date, e])), i = new Map(c.progress.map((e) => [e.date, e]));
53
- for (const [e] of i)
54
- S.has(e) || o.push({ message: `Progress logged: ${e}`, timestamp: s });
55
- return o;
56
- }
57
- function Ot(n) {
58
- const c = /* @__PURE__ */ new Set();
59
- let o = null, s = null;
60
- const u = ["plans.md", "decisions.md", "open-questions.md", "progress.md"];
61
- async function y() {
62
- try {
63
- const r = await q(n);
64
- if (o) {
65
- const t = Nt(o, r);
66
- for (const b of t) {
67
- const C = `data: ${JSON.stringify(b)}
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
+ `) : "(none)";
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.
68
18
 
69
- `;
70
- for (const S of c)
71
- try {
72
- S.write(C);
73
- } catch {
74
- c.delete(S);
75
- }
76
- }
77
- }
78
- o = r;
79
- } catch {
80
- }
81
- }
82
- for (const r of u) {
83
- const t = E(n, r);
84
- try {
85
- G(t, () => {
86
- s && clearTimeout(s), s = setTimeout(y, 300);
87
- });
88
- } catch {
89
- }
90
- }
91
- return q(n).then((r) => {
92
- o = r;
93
- }), {
94
- subscribe(r) {
95
- c.add(r);
96
- const t = JSON.stringify({
97
- message: "Watching for changes…",
98
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
99
- });
100
- r.write(`data: ${t}
19
+ Plan body: ${t.body}
101
20
 
102
- `), r.on("close", () => c.delete(r));
103
- }
104
- };
21
+ Current phases:
22
+ ${a}
23
+
24
+ Log entries (issues, bugs, and review notes recorded so far):
25
+ ${i}
26
+
27
+ Clarifications (answered scope/design questions):
28
+ ${r}
29
+
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.
35
+
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.`;
105
40
  }
106
- function jt(n) {
107
- return ["-p", n, "--output-format", "stream-json", "--verbose", "--permission-mode", "auto"];
41
+ function Ae(t, a) {
42
+ const i = [
43
+ "-p",
44
+ t,
45
+ "--output-format",
46
+ "stream-json",
47
+ "--verbose",
48
+ "--permission-mode",
49
+ "auto"
50
+ ];
51
+ return a != null && a.model && i.push("--model", a.model), a != null && a.effort && i.push("--effort", a.effort), i;
108
52
  }
109
- function bt(n) {
110
- let c;
53
+ function Te(t) {
54
+ let a;
111
55
  try {
112
- c = JSON.parse(n);
56
+ a = JSON.parse(t);
113
57
  } catch {
114
58
  return null;
115
59
  }
116
- switch (c.type) {
60
+ switch (a.type) {
117
61
  case "system":
118
- return c.subtype === "init" ? { text: "Agent session started" } : c.subtype === "post_turn_summary" && typeof c.status_detail == "string" ? { text: c.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;
119
63
  case "rate_limit_event":
120
64
  return null;
121
65
  case "assistant": {
122
- const o = c.message, s = (o == null ? void 0 : o.content) ?? [];
123
- for (const u of s) {
124
- const y = u;
125
- if (y.type === "tool_use")
126
- return { text: `Running ${y.name ?? "a tool"}…` };
127
- if (y.type === "text" && typeof y.text == "string" && y.text.trim())
128
- return { text: y.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() };
129
73
  }
130
74
  return null;
131
75
  }
132
76
  case "user": {
133
- const o = c.message, s = o == null ? void 0 : o.content, u = Array.isArray(s) ? s[0] : void 0;
134
- return u != null && u.is_error ? { text: `Error: ${typeof u.content == "string" ? u.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" };
135
79
  }
136
80
  case "result": {
137
- const o = !!c.is_error;
138
- return { text: (typeof c.result == "string" ? c.result.trim() : "") || (o ? "Agent run failed" : "Agent run finished"), done: !0, error: o };
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 };
139
83
  }
140
84
  default:
141
85
  return { text: "Agent is working…" };
142
86
  }
143
87
  }
144
- function Jt(n) {
145
- return ["run", n, "--format", "json"];
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;
146
91
  }
147
- const vt = {
92
+ const De = {
148
93
  bash: "Running command",
149
94
  read: "Reading file",
150
95
  edit: "Editing file",
@@ -155,1009 +100,2059 @@ const vt = {
155
100
  webfetch: "Fetching URL",
156
101
  question: "Asking for input"
157
102
  };
158
- function Ht(n) {
159
- let c;
103
+ function Oe(t) {
104
+ let a;
160
105
  try {
161
- c = JSON.parse(n);
106
+ a = JSON.parse(t);
162
107
  } catch {
163
108
  return null;
164
109
  }
165
- const o = c.type, s = c.part;
166
- if (!o || !s) return null;
167
- switch (o) {
110
+ const i = a.type, r = a.part;
111
+ if (!i || !r) return null;
112
+ switch (i) {
168
113
  case "step_start":
169
114
  return null;
170
115
  case "text": {
171
- const u = s.text;
172
- return u != null && u.trim() ? { text: u.trim() } : null;
116
+ const s = r.text;
117
+ return s != null && s.trim() ? { text: s.trim() } : null;
173
118
  }
174
119
  case "tool_use": {
175
- const u = s.tool, y = s.state ? s.state.input : void 0, r = y ? y.description : void 0, t = u ? vt[u] : "Running tool", b = typeof r == "string" && r.trim() ? `: ${r.trim()}` : "";
176
- return u ? { text: `${t}${b}…` } : 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;
177
122
  }
178
123
  case "step_finish": {
179
- const u = s.reason, y = u === "tool-calls" ? null : u === "stop" ? "Done" : "Step finished";
180
- return y ? { text: y } : null;
124
+ const s = r.reason, e = s === "tool-calls" ? null : s === "stop" ? "Done" : "Step finished";
125
+ return e ? { text: e } : null;
181
126
  }
182
127
  default:
183
128
  return null;
184
129
  }
185
130
  }
186
- const tt = "claude-code", P = {
131
+ const Gt = "claude-code", bt = {
187
132
  "claude-code": {
188
133
  command: "claude",
189
- buildArgs: jt,
190
- parseLine: bt
134
+ buildArgs: Ae,
135
+ parseLine: Te,
136
+ options: qt["claude-code"]
191
137
  },
192
138
  opencode: {
193
139
  command: "opencode",
194
- buildArgs: Jt,
195
- parseLine: Ht
140
+ buildArgs: Ee,
141
+ parseLine: Oe,
142
+ options: qt.opencode
196
143
  }
197
- }, xt = {
144
+ }, Ne = {
198
145
  phase: "phase",
199
146
  audit: "phase",
147
+ "batch-audit": "phase",
148
+ "run-all": "phase",
149
+ sync: "phase",
150
+ reconcile: "phase",
200
151
  draft: "planDraft",
201
- extend: "ideaExtend"
152
+ extend: "ideaExtend",
153
+ "commit-suggest": "commitSuggest"
202
154
  };
203
- function kt(n) {
204
- const { agentId: c, defaultAgents: o, taskKind: s } = n;
205
- if (c && c in P) return { id: c, adapter: P[c] };
206
- if (s && o) {
207
- const u = xt[s], y = o[u];
208
- if (y && y in P) return { id: y, adapter: P[y] };
209
- }
210
- return { id: tt, adapter: P[tt] };
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] };
167
+ }
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" };
177
+ });
178
+ }
179
+ function Re(t) {
180
+ const a = /* @__PURE__ */ new Set();
181
+ let i = null;
182
+ function r() {
183
+ const s = `data: ${JSON.stringify({
184
+ message: "changed",
185
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
186
+ })}
187
+
188
+ `;
189
+ for (const e of a)
190
+ try {
191
+ e.write(s);
192
+ } catch {
193
+ a.delete(e);
194
+ }
195
+ }
196
+ try {
197
+ Ot($(t, "papercamp"), { recursive: !0 }, () => {
198
+ i && clearTimeout(i), i = setTimeout(r, 300);
199
+ });
200
+ } catch {
201
+ }
202
+ return {
203
+ subscribe(s) {
204
+ a.add(s);
205
+ const e = JSON.stringify({
206
+ message: "Watching for changes…",
207
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
208
+ });
209
+ s.write(`data: ${e}
210
+
211
+ `), s.on("close", () => a.delete(s));
212
+ }
213
+ };
211
214
  }
212
- const $t = 50;
213
- function It(n) {
215
+ const Be = 50, Ht = 30 * 60 * 1e3;
216
+ function Pt(t) {
214
217
  try {
215
- const c = St(O(n, ".paper-camp", "config.json"), "utf-8"), o = JSON.parse(c);
216
- return o.defaultAgents ? o.defaultAgents : o.defaultAgent ? {
217
- phase: o.defaultAgent,
218
- planDraft: o.defaultAgent,
219
- ideaExtend: o.defaultAgent
220
- } : X;
218
+ const a = Pe($(t, "papercamp", "config.json"), "utf-8"), i = JSON.parse(a), r = i.defaultAgents;
219
+ if (r)
220
+ return {
221
+ phase: z(r.phase),
222
+ planDraft: z(r.planDraft),
223
+ ideaExtend: z(r.ideaExtend),
224
+ commitSuggest: z(r.commitSuggest)
225
+ };
226
+ if (i.defaultAgent) {
227
+ const s = i.defaultAgent;
228
+ return {
229
+ phase: { agent: s },
230
+ planDraft: { agent: s },
231
+ ideaExtend: { agent: s },
232
+ commitSuggest: { agent: s }
233
+ };
234
+ }
235
+ return _t;
221
236
  } catch {
222
- return X;
237
+ return _t;
223
238
  }
224
239
  }
225
- function Pt(n, c, o) {
226
- return `You're working on phase ${o + 1} ("${c.text}") of the plan "${n.title}" (${n.id ?? "no id"}) in papercamp/plans.md.
240
+ function Wt(t, a, i) {
241
+ const r = a.description ? `Phase details:
242
+ ${a.description}
243
+
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.
227
246
 
228
- ${c.description ?? ""}
247
+ ${r}Plan context: ${t.body}
229
248
 
230
- Plan context: ${n.body}
249
+ Do only this phase — do not start any other phase, even if it looks quick.
231
250
 
232
- Do only this phase. When done, check it off in plans.md (- [ ] -> - [x]) and append what you did to progress.md. If this was the last unchecked phase, set the plan's Status to \`review\`, not \`done\`, per this repo's AGENTS.md.`;
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.`;
233
255
  }
234
- function At(n, c = () => {
235
- }) {
236
- const o = /* @__PURE__ */ new Set();
237
- let s = null;
238
- function u(a) {
239
- const g = `data: ${JSON.stringify({ message: a, timestamp: (/* @__PURE__ */ new Date()).toISOString(), type: "agent" })}
256
+ function Ce(t, a = () => {
257
+ }, i, r, s) {
258
+ const e = /* @__PURE__ */ new Set();
259
+ let n = null;
260
+ function l(o) {
261
+ const k = `data: ${JSON.stringify({ message: o, timestamp: (/* @__PURE__ */ new Date()).toISOString(), type: "agent" })}
240
262
 
241
263
  `;
242
- for (const w of o)
264
+ for (const v of e)
243
265
  try {
244
- w.write(g);
266
+ v.write(k);
245
267
  } catch {
246
- o.delete(w);
268
+ e.delete(v);
247
269
  }
248
270
  }
249
- function y(a, g) {
250
- a.lines.push(g), a.lines.length > $t && a.lines.shift(), u(g);
271
+ function c(o, k) {
272
+ o.lines.push(k), o.lines.length > Be && o.lines.shift(), l(k);
251
273
  }
252
- function r(a, g) {
253
- a.status = g, u(`agent: ${g}`);
274
+ function d(o, k) {
275
+ o.status = k, l(`agent: ${k}`);
254
276
  }
255
- async function t(a) {
256
- var g, w;
277
+ async function u(o) {
278
+ var k, v;
257
279
  try {
258
- if (a.taskKind === "extend") {
259
- const x = await $(O(n, "papercamp", "ideas.md"), "utf-8"), W = M(x).find((rt) => rt.id === a.ideaId);
260
- return !W || a.ideaBodyBaseline === void 0 ? null : W.body !== a.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;
261
283
  }
262
- const T = await $(O(n, "papercamp", "plans.md"), "utf-8"), { entries: j } = k(T);
263
- if (a.ideaId !== void 0)
264
- return j.some((x) => x.idea === a.ideaId);
265
- const v = j.find((x) => x.id === a.planId) ?? j.find((x) => x.title === a.planTitle);
266
- return v ? a.phaseIndex !== void 0 ? ((g = v.phases[a.phaseIndex]) == null ? void 0 : g.done) ?? null : a.planBaseline ? v.phases.length > a.planBaseline.phases || (((w = v.log) == null ? void 0 : w.length) ?? 0) > a.planBaseline.log : null : null;
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;
290
+ }
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;
267
296
  } catch {
268
297
  return null;
269
298
  }
270
299
  }
271
- function b(a, g) {
272
- r(a, g ? "error" : "done"), !g && t(a).then((w) => {
273
- if (s === a && w === !1) {
274
- const T = a.taskKind === "extend" ? `Warning: agent finished but the idea body for ${a.ideaId} did not change — verify manually` : a.ideaId !== void 0 ? `Warning: agent finished but no plan linking idea: ${a.ideaId} appeared in plans.md — verify manually` : a.phaseIndex !== void 0 ? "Warning: agent finished but did not check off this phase in plans.md — verify manually" : "Warning: agent finished but appended nothing to Phases or Log — verify manually";
275
- y(a, T);
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);
276
305
  }
306
+ n === o && o.taskKind === "audit" && o.planId && v === !0 && (i == null || i(o.planId, 0).catch(() => {
307
+ }));
277
308
  });
278
309
  }
279
- function C(a) {
280
- if (!a.proc.stdout) return;
281
- Tt({ input: a.proc.stdout }).on("line", (w) => {
282
- if (s !== a || !w.trim()) return;
283
- const T = a.adapter.parseLine(w);
284
- T && (y(a, T.text), T.done && b(a, !!T.error));
285
- }), a.proc.on("close", (w) => {
286
- s === a && (a.status === "starting" || a.status === "running" ? b(a, w !== 0) : a.status === "stopping" && r(a, "done"));
287
- }), a.proc.on("error", (w) => {
288
- s === a && (y(a, `Failed to spawn agent: ${w.message}`), r(a, "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"));
289
320
  });
290
321
  }
291
- function S(a, g) {
292
- return K(a.command, g, {
293
- cwd: n,
322
+ function y(o, k) {
323
+ return it(o.command, k, {
324
+ cwd: t,
294
325
  stdio: ["ignore", "pipe", "pipe"]
295
326
  });
296
327
  }
297
- function i() {
298
- return s !== null && s.status !== "done" && s.status !== "error";
328
+ function g() {
329
+ return n !== null && n.status !== "done" && n.status !== "error";
299
330
  }
300
- function e(a, g, w) {
301
- if (i())
331
+ function E(o, k, v) {
332
+ if (g())
302
333
  return { ok: !1, error: "An agent task is already running" };
303
- const T = It(n), { id: j, adapter: v } = kt({
304
- agentId: a.agentOverride,
305
- defaultAgents: T,
306
- taskKind: w.taskKind
307
- }), x = S(v, v.buildArgs(g)), R = {
308
- planTitle: a.planTitle,
309
- planId: a.planId,
334
+ const N = Pt(t), {
335
+ id: X,
336
+ adapter: G,
337
+ model: x,
338
+ effort: W
339
+ } = St({
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,
310
346
  status: "starting",
311
- agentId: j,
312
- adapter: v,
313
- proc: x,
347
+ agentId: X,
348
+ adapter: G,
349
+ proc: Q,
314
350
  lines: [],
315
- ...w
351
+ ...v
316
352
  };
317
- return s = R, C(R), r(R, "running"), { ok: !0 };
353
+ return n = b, h(b), d(b, "running"), { ok: !0 };
318
354
  }
319
- function d(a, g) {
320
- if (i())
355
+ function F(o, k) {
356
+ if (g())
321
357
  return { ok: !1, error: "An agent task is already running" };
322
- const w = a.phases[g];
323
- if (!w)
358
+ const v = o.phases[k];
359
+ if (!v)
324
360
  return { ok: !1, error: "Phase not found" };
325
- c(a);
326
- const T = Pt(a, w, g);
327
- return e({ planTitle: a.title, planId: a.id, agentOverride: a.agent }, T, {
361
+ a(o);
362
+ const N = Wt(o, v, k);
363
+ return E({ planTitle: o.title, planId: o.id, agentOverride: o.agent }, N, {
328
364
  taskKind: "phase",
329
- phaseIndex: g
365
+ phaseIndex: k
330
366
  });
331
367
  }
332
- function f(a, g) {
333
- var w;
334
- return e({ planTitle: a.title, planId: a.id, agentOverride: a.agent }, g, {
335
- taskKind: "audit",
336
- planBaseline: { phases: a.phases.length, log: ((w = a.log) == null ? void 0 : w.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 } }
337
378
  });
338
379
  }
339
- function p(a, g) {
340
- return a.id ? e({ planTitle: `Draft plan for ${a.id}` }, g, {
380
+ function S(o, k) {
381
+ return o.id ? E({ planTitle: `Draft plan for ${o.id}` }, k, {
341
382
  taskKind: "draft",
342
- ideaId: a.id
383
+ ideaId: o.id
343
384
  }) : { ok: !1, error: "Idea has no id to link a drafted plan back to" };
344
385
  }
345
- function l(a, g) {
346
- return a.id ? e({ planTitle: `Extend ${a.id}` }, g, {
386
+ function m(o, k) {
387
+ return o.id ? E({ planTitle: `Extend ${o.id}` }, k, {
347
388
  taskKind: "extend",
348
- ideaId: a.id,
349
- ideaBodyBaseline: a.body
389
+ ideaId: o.id,
390
+ ideaBodyBaseline: o.body
350
391
  }) : { ok: !1, error: "Idea has no id to extend" };
351
392
  }
352
- function m() {
353
- if (!s)
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`);
398
+ try {
399
+ return await ht(v), v;
400
+ } catch {
401
+ }
402
+ const N = $(o, "archive", `${k}.md`);
403
+ try {
404
+ return await ht(N), N;
405
+ } catch {
406
+ }
407
+ return null;
408
+ }
409
+ function T() {
410
+ if (g())
411
+ return { ok: !1, error: "An agent task is already running" };
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,
419
+ stdio: "ignore"
420
+ }), x = {
421
+ taskKind: "batch-audit",
422
+ planTitle: "Batch audit",
423
+ status: "starting",
424
+ agentId: k,
425
+ adapter: v,
426
+ proc: G,
427
+ lines: []
428
+ };
429
+ return n = x, d(x, "running"), (async () => {
430
+ var W, Q;
431
+ try {
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"));
435
+ return;
436
+ }
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++;
443
+ continue;
444
+ }
445
+ const ct = await I(b, A.id);
446
+ if (!ct) {
447
+ Z++;
448
+ continue;
449
+ }
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++;
454
+ continue;
455
+ }
456
+ }
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,
460
+ stdio: ["ignore", "pipe", "pipe"]
461
+ });
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", () => {
467
+ });
468
+ const $t = await new Promise((H) => {
469
+ et.on("close", (V) => H(V === 0)), et.on("error", () => H(!1));
470
+ });
471
+ if (n !== x) return;
472
+ if ($t) {
473
+ let H = 0;
474
+ try {
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);
477
+ } catch {
478
+ }
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}`
482
+ );
483
+ } else
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));
575
+ }
576
+ if (n !== b) return;
577
+ if (b.status === "stopping") {
578
+ d(b, "done");
579
+ return;
580
+ }
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"));
597
+ }
598
+ })(), { ok: !0 };
599
+ }
600
+ const M = 6e4, B = 10 * 1024 * 1024;
601
+ function J(o) {
602
+ if (g())
603
+ return Promise.reject(new Error("An agent task is already running"));
604
+ if (Buffer.byteLength(o, "utf-8") > B)
605
+ return Promise.reject(new Error("Commit suggestion prompt exceeds the 10MB stdin limit"));
606
+ const k = Pt(t), {
607
+ id: v,
608
+ adapter: N,
609
+ model: X,
610
+ effort: G
611
+ } = St({
612
+ defaultAgents: k,
613
+ taskKind: "commit-suggest"
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,
619
+ stdio: ["pipe", "pipe", "pipe"]
620
+ }), C = {
621
+ taskKind: "commit-suggest",
622
+ planTitle: "Suggest commit message",
623
+ status: "starting",
624
+ agentId: v,
625
+ adapter: N,
626
+ proc: K,
627
+ lines: []
628
+ };
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"));
636
+ });
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();
645
+ }), K.on("close", (L) => {
646
+ n === C && Z(() => {
647
+ if (C.status === "stopping") {
648
+ d(C, "done"), b(new Error("Stopped"));
649
+ return;
650
+ }
651
+ if (L === 0) {
652
+ d(C, "done");
653
+ const nt = x ? A : A.split(`
654
+ `).map((kt) => {
655
+ var dt;
656
+ try {
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;
659
+ } catch {
660
+ }
661
+ return null;
662
+ }).filter(Boolean).join(`
663
+ `);
664
+ Q(nt);
665
+ } else {
666
+ const nt = ct || `${N.command} exited with code ${L}`;
667
+ c(C, nt), d(C, "error"), b(new Error(nt));
668
+ }
669
+ });
670
+ }), K.on("error", (L) => {
671
+ n === C && Z(() => {
672
+ c(C, `Failed to spawn agent: ${L.message}`), d(C, "error"), b(L);
673
+ });
674
+ });
675
+ });
676
+ }
677
+ function _() {
678
+ if (!n)
354
679
  return { ok: !1, error: "No agent task running" };
355
- const a = s;
356
- return r(a, "stopping"), a.proc.killed || a.proc.kill("SIGTERM"), setTimeout(() => {
357
- s === a && a.status === "stopping" && a.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");
358
683
  }, 5e3), { ok: !0 };
359
684
  }
360
- function h() {
361
- return s ? {
362
- status: s.status,
363
- taskKind: s.taskKind,
364
- planTitle: s.planTitle,
365
- planId: s.planId,
366
- phaseIndex: s.phaseIndex,
367
- ideaId: s.ideaId,
368
- agentId: s.agentId,
369
- lines: [...s.lines]
685
+ function R() {
686
+ return n ? {
687
+ status: n.status,
688
+ taskKind: n.taskKind,
689
+ planTitle: n.planTitle,
690
+ planId: n.planId,
691
+ phaseIndex: n.phaseIndex,
692
+ ideaId: n.ideaId,
693
+ agentId: n.agentId,
694
+ lines: [...n.lines]
370
695
  } : null;
371
696
  }
372
697
  return {
373
- start: d,
374
- startForPlan: f,
375
- startForIdea: p,
376
- startForIdeaExtend: l,
377
- stop: m,
378
- getStatus: h,
379
- subscribe(a) {
380
- o.add(a), a.on("close", () => o.delete(a));
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));
381
710
  },
382
711
  killCurrent() {
383
- s != null && s.proc && !s.proc.killed && s.proc.kill();
712
+ n != null && n.proc && !n.proc.killed && n.proc.kill();
384
713
  }
385
714
  };
386
715
  }
387
- function Et(n) {
388
- const c = /* @__PURE__ */ new Set();
389
- function o(p) {
390
- const l = `data: ${JSON.stringify(p)}
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
+ }
723
+ }
724
+ async function wt(t) {
725
+ try {
726
+ return await ht(t), !0;
727
+ } catch {
728
+ return !1;
729
+ }
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}
788
+
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 };
837
+ }
838
+ const Ut = [/(^|\/)\.env(\.|$)/i, /\.(pem|key|p12|crt)$/i], Ft = (t) => `:(literal)${t}`;
839
+ function _e(t, a = {}) {
840
+ const i = /* @__PURE__ */ new Set();
841
+ function r(w) {
842
+ const I = `data: ${JSON.stringify(w)}
391
843
 
392
844
  `;
393
- for (const m of c)
845
+ for (const T of i)
394
846
  try {
395
- m.write(l);
847
+ T.write(I);
396
848
  } catch {
397
- c.delete(m);
849
+ i.delete(T);
398
850
  }
399
851
  }
400
- function s(p) {
401
- const l = [];
402
- for (const m of p.split(`
852
+ function s(w) {
853
+ const I = [];
854
+ for (const T of w.split(`
403
855
  `)) {
404
- if (!m.trim()) continue;
405
- const h = m[0] ?? " ", a = m[1] ?? " ", g = m.slice(3), w = g.split(" -> ").pop() ?? g;
406
- l.push({
407
- path: w,
408
- status: `${h}${a}`,
409
- staged: h !== " " && h !== "?"
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
410
863
  });
411
864
  }
412
- return l;
865
+ return I;
413
866
  }
414
- function u(p) {
415
- return new Promise((l, m) => {
416
- var w, T;
417
- const h = K("git", p, {
418
- cwd: n,
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,
419
872
  stdio: ["ignore", "pipe", "pipe"]
420
873
  });
421
- let a = "", g = "";
422
- (w = h.stdout) == null || w.on("data", (j) => {
423
- a += j.toString();
424
- }), (T = h.stderr) == null || T.on("data", (j) => {
425
- g += j.toString();
426
- }), h.on("close", (j) => {
427
- j === 0 ? l(a) : m(new Error(g || `git ${p[0]} exited with code ${j}`));
428
- }), h.on("error", m);
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);
429
882
  });
430
883
  }
431
- function y() {
432
- return u(["status", "--porcelain=v1"]).then(s);
884
+ function n() {
885
+ return e(["status", "--porcelain=v1"]).then(s);
433
886
  }
434
- async function r(p, l, m) {
435
- p.length > 0 && await u(["add", "--", ...p]);
436
- const h = ["commit", "-m", l];
437
- m && h.push("-m", m), await u(h);
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] !== " ";
897
+ });
898
+ _.length > 0 && await e(["add", "--", ..._.map(Ft)]);
899
+ }
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);
438
909
  }
439
- function t(p) {
440
- if (!p.kind || !p.id) return;
441
- const l = p.kind.toLowerCase(), m = p.id.toLowerCase(), h = p.title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, ""), a = `${l}/${m}-${h}`, g = L("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd: n });
442
- if (g.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)
443
914
  throw new Error(
444
- g.stderr.toString().trim() || "Unable to read current git branch"
915
+ B.stderr.toString().trim() || "Unable to read current git branch"
445
916
  );
446
- if (g.stdout.toString().trim() === a) return;
447
- const T = L("git", ["checkout", "-b", a, "main"], { cwd: n });
448
- if (T.status !== 0) {
449
- const j = L("git", ["checkout", a], { cwd: n });
450
- if (j.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)
451
922
  throw new Error(
452
- j.stderr.toString().trim() || T.stderr.toString().trim() || `Unable to check out ${a}`
923
+ R.stderr.toString().trim() || _.stderr.toString().trim() || `Unable to check out ${M}`
453
924
  );
454
925
  }
455
926
  }
456
- async function b() {
927
+ async function d() {
457
928
  try {
458
- await y(), o({
929
+ await n(), r({
459
930
  message: "Working tree status updated",
460
931
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
461
932
  });
462
933
  } catch {
463
934
  }
464
935
  }
465
- const C = O(n, ".git");
466
- let S = null;
467
- try {
468
- G(C, { recursive: !0 }, (p, l) => {
469
- l === "index" && (S && clearTimeout(S), S = setTimeout(b, 500));
470
- });
471
- } 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
+ }
472
953
  }
473
- const i = O(n, "src");
474
- let e = null;
475
- try {
476
- G(i, { recursive: !0 }, () => {
477
- e && clearTimeout(e), e = setTimeout(b, 500);
478
- });
479
- } catch {
954
+ async function u() {
955
+ try {
956
+ return await e(["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"]), !0;
957
+ } catch {
958
+ return !1;
959
+ }
480
960
  }
481
- function d() {
482
- return L("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd: n }).stdout.toString().trim();
961
+ async function f() {
962
+ try {
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;
965
+ } catch {
966
+ return 0;
967
+ }
483
968
  }
484
- function f() {
485
- const l = d().match(/^[a-z]+\/([a-z]+-\d+)-/);
486
- return l ? l[1].toUpperCase() : null;
969
+ async function h() {
970
+ await u() ? await e(["push"]) : await e(["push", "--set-upstream", "origin", y()]);
487
971
  }
488
- return {
489
- async getStatus() {
490
- return y();
491
- },
492
- getCurrentBranch: d,
493
- commit: r,
494
- ensureBranch: t,
495
- getFeatureBranchPlanId: f,
496
- subscribe(p) {
497
- c.add(p), p.on("close", () => c.delete(p));
972
+ function y() {
973
+ return It("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd: t }).stdout.toString().trim();
974
+ }
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;
498
983
  }
499
- };
500
- }
501
- const Dt = {
502
- lint: "npx biome lint .",
503
- format: "npx biome format .",
504
- test: "npx vitest run"
505
- };
506
- function Rt(n) {
507
- const c = /* @__PURE__ */ new Set(), o = {
508
- lint: { status: "stale", lastRun: null, output: "" },
509
- format: { status: "stale", lastRun: null, output: "" },
510
- test: { status: "stale", lastRun: null, output: "" }
511
- }, s = /* @__PURE__ */ new Set(), u = /* @__PURE__ */ new Set();
512
- function y(i) {
513
- const e = `data: ${JSON.stringify(i)}
514
-
515
- `;
516
- for (const d of c)
517
- try {
518
- d.write(e);
519
- } catch {
520
- c.delete(d);
521
- }
522
984
  }
523
- function r(i, e, d) {
524
- o[i] = { status: e, lastRun: (/* @__PURE__ */ new Date()).toISOString(), output: d }, y({
525
- message: `${i}: ${e}`,
526
- timestamp: o[i].lastRun
527
- }), e !== "running" && u.has(i) && (u.delete(i), t(i));
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";
528
988
  }
529
- function t(i) {
530
- var l, m;
531
- if (s.has(i)) {
532
- u.add(i);
533
- return;
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([
1007
+ "diff",
1008
+ "HEAD",
1009
+ "--",
1010
+ ...J.map(Ft)
1011
+ ]).catch(() => "");
1012
+ o && _.push(o);
534
1013
  }
535
- s.add(i), r(i, "running", "");
536
- const e = Dt[i], d = K(e, {
537
- cwd: n,
538
- stdio: ["ignore", "pipe", "pipe"],
539
- shell: !0
540
- });
541
- let f = "", p = "";
542
- (l = d.stdout) == null || l.on("data", (h) => {
543
- f += h.toString();
544
- }), (m = d.stderr) == null || m.on("data", (h) => {
545
- p += h.toString();
546
- }), d.on("close", (h) => {
547
- s.delete(i);
548
- const a = f + p;
549
- h === 0 ? r(i, "pass", a) : r(i, "fail", a);
550
- }), d.on("error", (h) => {
551
- s.delete(i), r(i, "fail", `Failed to spawn process: ${h.message}`);
552
- });
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}
1020
+ (new file omitted: symlink)`);
1021
+ continue;
1022
+ }
1023
+ if (v.size > I) {
1024
+ _.push(`--- /dev/null
1025
+ +++ b/${o}
1026
+ (new file omitted: exceeds diff size cap)`);
1027
+ continue;
1028
+ }
1029
+ const N = await at(k, "utf-8").catch(() => "");
1030
+ N && _.push(`--- /dev/null
1031
+ +++ b/${o}
1032
+ (new file)
1033
+ ${N}`);
1034
+ }
1035
+ const R = _.join(`
1036
+
1037
+ `);
1038
+ return R.length > I ? `${R.slice(0, I)}
1039
+ ... (truncated)` : R;
553
1040
  }
554
- function b() {
555
- if (s.has("lint") || s.has("format")) return;
556
- r("lint", "running", "Applying automatic fixes…"), r("format", "running", "Applying automatic fixes…");
557
- const i = K("npx biome check . --write", {
558
- cwd: n,
559
- stdio: ["ignore", "pipe", "pipe"],
560
- shell: !0
561
- });
562
- i.on("close", () => {
563
- t("lint"), t("format");
564
- }), i.on("error", (e) => {
565
- const d = `Failed to spawn fix process: ${e.message}`;
566
- r("lint", "fail", d), r("format", "fail", d);
567
- });
1041
+ async function S() {
1042
+ await e(["add", "-A"]);
568
1043
  }
569
- const C = O(n, "src");
570
- let S = null;
571
- try {
572
- G(C, { recursive: !0 }, () => {
573
- S && clearTimeout(S), S = setTimeout(() => {
574
- t("lint"), t("format");
575
- }, 1e3);
576
- });
577
- } catch {
1044
+ async function m() {
1045
+ await e(["checkout", "main"]), await e(["fetch", "--prune"]), await e(["merge", "--ff-only", "origin/main"]);
578
1046
  }
579
1047
  return {
580
- getStatus() {
581
- return {
582
- lint: { ...o.lint },
583
- format: { ...o.format },
584
- test: { ...o.test }
585
- };
1048
+ async getStatus() {
1049
+ return n();
586
1050
  },
587
- runCheck: t,
588
- runQualityFix: b,
589
- subscribe(i) {
590
- c.add(i);
591
- for (const e of ["lint", "format", "test"]) {
592
- const d = o[e];
593
- d.status !== "stale" && i.write(
594
- `data: ${JSON.stringify({ message: `${e}: ${d.status}`, timestamp: d.lastRun })}
595
-
596
- `
597
- );
598
- }
599
- i.on("close", () => c.delete(i));
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));
600
1064
  }
601
1065
  };
602
1066
  }
603
- async function N(n) {
604
- try {
605
- return await $(n, "utf-8");
606
- } catch (c) {
607
- if (c.code === "ENOENT") return "";
608
- throw c;
609
- }
610
- }
611
- async function et(n) {
612
- try {
613
- return await st(n), !0;
614
- } catch {
615
- return !1;
616
- }
1067
+ function p(t, a, i) {
1068
+ t.statusCode = a, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify(i));
617
1069
  }
618
- async function H(n) {
619
- return new Promise((c, o) => {
620
- let s = "";
621
- n.on("data", (u) => {
622
- s += u;
623
- }), n.on("end", () => c(s)), n.on("error", o);
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);
624
1076
  });
625
1077
  }
626
- const J = (n, c) => O(n, "papercamp", c), Lt = [
627
- "biome.json",
628
- "tsconfig.json",
629
- "tailwind.config.ts",
630
- "vite.config.ts",
631
- "vite.app.config.ts",
632
- "postcss.config.js",
633
- "package.json"
1078
+ function vt(t) {
1079
+ return new URL(t.url ?? "", `http://${t.headers.host ?? "localhost"}`);
1080
+ }
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;
1086
+ }
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"]
634
1095
  ];
635
- async function A(n, c, o) {
636
- const s = c.getFeatureBranchPlanId();
637
- if (!s || o && s === o) return null;
638
- const u = await N(J(n, "plans.md"));
639
- if (!u) return null;
640
- const { entries: y } = k(u), r = y.find((t) => t.id === s);
641
- return !r || r.status === "done" || r.status === "dropped" ? null : `Finish \`${s}\` — ${r.title} — before starting another plan`;
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;
1107
+ }
1108
+ if (n) {
1109
+ if (c.trim() === "" || /^\s+\S/.test(c)) continue;
1110
+ n = !1;
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);
1116
+ }
1117
+ return { content: i + s.join(`
1118
+ `), changed: e };
642
1119
  }
643
- const _t = [
644
- {
645
- path: "/api/package-name",
646
- handler: async (n) => {
647
- const c = await N(O(n, "package.json"));
648
- if (!c) return null;
649
- try {
650
- return JSON.parse(c).name ?? null;
651
- } catch {
652
- return null;
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());
653
1136
  }
654
- }
655
- },
656
- {
657
- path: "/api/plans",
658
- handler: async (n) => k(await N(J(n, "plans.md")))
659
- },
660
- {
661
- path: "/api/progress",
662
- handler: async (n) => ({
663
- entries: nt(await N(J(n, "progress.md")))
664
- })
665
- },
666
- {
667
- path: "/api/decisions",
668
- handler: async (n) => Q(await N(J(n, "decisions.md")))
669
- },
670
- {
671
- path: "/api/open-questions",
672
- handler: async (n) => z(await N(J(n, "open-questions.md")))
673
- },
674
- {
675
- path: "/api/ideas",
676
- handler: async (n) => ({
677
- content: await N(J(n, "ideas.md"))
678
- })
679
- },
680
- {
681
- path: "/api/consistency",
682
- handler: async (n) => {
683
- const [c, o, s] = await Promise.all([
684
- N(J(n, "decisions.md")),
685
- N(J(n, "open-questions.md")),
686
- N(J(n, "plans.md"))
687
- ]), u = Q(c), y = z(o), r = k(s);
688
- return ft(u.entries, y.entries, r.entries);
689
- }
690
- },
691
- {
692
- path: "/api/config",
693
- handler: async (n) => {
694
- const c = await N(O(n, ".paper-camp", "config.json"));
695
- return c ? JSON.parse(c) : null;
696
- }
697
- },
698
- {
699
- path: "/api/docs",
700
- handler: async (n) => {
701
- const c = ["MAIN.md", "README.md", "CHANGELOG.md", "LICENSE"], o = [];
702
- for (const s of c) {
703
- const u = await N(O(n, s));
704
- u && o.push({ name: s, content: u });
705
- }
706
- return { files: o };
707
- }
708
- },
709
- {
710
- path: "/api/configs",
711
- handler: async (n) => {
712
- const c = [
713
- "biome.json",
714
- "tsconfig.json",
715
- "tailwind.config.ts",
716
- "vite.config.ts",
717
- "vite.app.config.ts",
718
- "postcss.config.js",
719
- "package.json"
720
- ], o = [];
721
- for (const s of c)
722
- await N(O(n, s)) && o.push(s);
723
- return { files: o };
724
- }
725
- }
726
- ];
727
- function Mt(n) {
728
- const c = Ot(n), o = Et(n), s = Rt(n), u = At(n, (r) => o.ensureBranch(r)), y = async (r, t, b) => {
729
- const C = (r.url ?? "").split("?")[0];
730
- if (r.method === "DELETE" && C === "/api/plans") {
731
- try {
732
- const e = new URL(r.url ?? "", `http://${r.headers.host ?? "localhost"}`).searchParams.get("title");
733
- if (!(e != null && e.trim())) {
734
- t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "title is required" }));
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" });
735
1146
  return;
736
1147
  }
737
- const d = J(n, "plans.md"), f = k(await N(d)), p = e.trim(), l = f.entries.filter((m) => m.title !== p);
738
- if (l.length === f.entries.length) {
739
- t.statusCode = 404, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "plan not found" }));
1148
+ const d = await At(t, l);
1149
+ if (!d) {
1150
+ p(e, 404, { error: "plan not found" });
740
1151
  return;
741
1152
  }
742
- await I(d, Z(l)), t.statusCode = 200, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ ok: !0 }));
743
- } catch (i) {
744
- t.statusCode = 500, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: i.message }));
745
- }
746
- return;
747
- }
748
- if (r.method === "POST" && C === "/api/plans") {
749
- try {
750
- const i = await H(r), { title: e, content: d, kind: f } = JSON.parse(i);
751
- if (!(e != null && e.trim())) {
752
- t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "title is required" }));
1153
+ const u = await rt(t, a, d.id);
1154
+ if (u) {
1155
+ p(e, 409, { error: u });
753
1156
  return;
754
1157
  }
755
- const p = f && F.includes(f) ? f : "feat", l = O(n, ".paper-camp", "config.json"), m = await at(l, p), h = it({
756
- title: e.trim(),
757
- status: "idea",
758
- kind: p,
759
- id: m,
760
- created: B(),
761
- body: d == null ? void 0 : d.trim()
762
- });
763
- await ot(J(n, "plans.md"), h), t.statusCode = 201, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ ok: !0, id: m }));
764
- } catch (i) {
765
- t.statusCode = 500, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: i.message }));
766
- }
767
- return;
768
- }
769
- if (r.method === "PATCH" && C === "/api/plans") {
770
- try {
771
- const e = new URL(r.url ?? "", `http://${r.headers.host ?? "localhost"}`).searchParams.get("title");
772
- if (!(e != null && e.trim())) {
773
- t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "title is required" }));
1158
+ const f = r.start(d, c);
1159
+ if (!f.ok) {
1160
+ p(e, 409, { error: f.error });
774
1161
  return;
775
1162
  }
776
- const d = await H(r), f = JSON.parse(d);
777
- if (f.agent && !U.includes(f.agent)) {
778
- t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "agent must be a known agent id" }));
1163
+ p(e, 202, { ok: !0 });
1164
+ }
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" });
779
1174
  return;
780
1175
  }
781
- const p = J(n, "plans.md"), l = k(await N(p)), m = e.trim();
782
- let h = !1;
783
- const a = l.entries.map((g) => g.title === m ? (h = !0, {
784
- ...g,
785
- ...f.status !== void 0 && { status: f.status },
786
- ...f.phases !== void 0 && { phases: f.phases },
787
- ...f.log !== void 0 && { log: f.log },
788
- ...f.agent !== void 0 && { agent: f.agent ?? void 0 },
789
- updated: B()
790
- }) : f.status === "in-progress" && g.status === "in-progress" ? { ...g, status: "planned", updated: B() } : g);
791
- if (!h) {
792
- t.statusCode = 404, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "plan not found" }));
1176
+ const d = await At(t, l);
1177
+ if (!d) {
1178
+ p(e, 404, { error: "plan not found" });
793
1179
  return;
794
1180
  }
795
- if (f.status === "done") {
796
- const g = l.entries.find((T) => T.title === m), w = await A(n, o, g == null ? void 0 : g.id);
797
- if (w) {
798
- t.statusCode = 409, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: w }));
799
- return;
800
- }
801
- }
802
- if (await I(p, Z(a)), f.status === "done") {
803
- const g = a.find((w) => w.title === m);
804
- if (g)
805
- try {
806
- o.ensureBranch(g);
807
- } catch {
808
- }
1181
+ const u = await rt(t, a, d.id);
1182
+ if (u) {
1183
+ p(e, 409, { error: u });
1184
+ return;
809
1185
  }
810
- t.statusCode = 200, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ ok: !0 }));
811
- } catch (i) {
812
- t.statusCode = 500, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: i.message }));
813
- }
814
- return;
815
- }
816
- if (r.method === "POST" && C === "/api/ideas") {
817
- try {
818
- const i = await H(r), { title: e, content: d } = JSON.parse(i);
819
- if (!(e != null && e.trim())) {
820
- t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "title is required" }));
1186
+ const f = r.startForPlan(d, c);
1187
+ if (!f.ok) {
1188
+ p(e, 409, { error: f.error });
821
1189
  return;
822
1190
  }
823
- const f = J(n, "ideas.md"), p = await N(f), h = `IDEA-${M(p).reduce((T, j) => {
824
- if (j.id) {
825
- const v = Number.parseInt(j.id.replace("IDEA-", ""), 10);
826
- return Number.isNaN(v) ? T : Math.max(T, v);
827
- }
828
- return T;
829
- }, 0) + 1}`, a = `### ${h}: ${e.trim()}
830
-
831
- ${(d == null ? void 0 : d.trim()) ?? ""}`, g = p.trimEnd(), w = g.length === 0 ? "" : `
832
-
833
- ---
834
-
835
- `;
836
- await I(f, `${g}${w}${a}
837
- `), t.statusCode = 201, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ ok: !0, id: h }));
838
- } catch (i) {
839
- t.statusCode = 500, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: i.message }));
1191
+ p(e, 202, { ok: !0 });
840
1192
  }
841
- return;
842
- }
843
- if (r.method === "GET" && C === "/api/icon") {
844
- const i = O(n, ".paper-camp", "assets"), e = {
845
- svg: "image/svg+xml",
846
- png: "image/png",
847
- jpg: "image/jpeg",
848
- jpeg: "image/jpeg",
849
- gif: "image/gif",
850
- webp: "image/webp"
851
- };
852
- for (const [d, f] of Object.entries(e))
853
- try {
854
- const p = await $(O(i, `icon.${d}`));
855
- t.statusCode = 200, t.setHeader("Content-Type", f), t.setHeader("Cache-Control", "no-cache"), t.end(p);
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" });
856
1202
  return;
857
- } catch {
858
1203
  }
859
- t.statusCode = 404, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "no icon uploaded" }));
860
- return;
861
- }
862
- if (r.method === "POST" && C === "/api/icon") {
863
- try {
864
- const i = await H(r), { dataUri: e } = JSON.parse(i);
865
- if (!e) {
866
- t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "dataUri is required" }));
1204
+ const d = await At(t, l);
1205
+ if (!d) {
1206
+ p(e, 404, { error: "plan not found" });
867
1207
  return;
868
1208
  }
869
- const d = e.match(/^data:(image\/[a-z0-9+.-]+);base64,(.+)$/);
870
- if (!d) {
871
- t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "invalid data URI" }));
1209
+ const u = await rt(t, a, d.id);
1210
+ if (u) {
1211
+ p(e, 409, { error: u });
872
1212
  return;
873
1213
  }
874
- const f = d[1], p = f === "image/svg+xml" ? "svg" : f.split("/")[1], l = Buffer.from(d[2], "base64"), m = O(n, ".paper-camp", "assets");
875
- await ht(m, { recursive: !0 }), await I(O(m, `icon.${p}`), l), t.statusCode = 200, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ ok: !0 }));
876
- } catch (i) {
877
- t.statusCode = 500, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: i.message }));
878
- }
879
- return;
880
- }
881
- if (r.method === "GET" && C === "/api/git/status") {
882
- try {
883
- const i = await o.getStatus(), e = o.getCurrentBranch();
884
- t.statusCode = 200, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ branch: e, entries: i }));
885
- } catch (i) {
886
- t.statusCode = 500, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: i.message }));
887
- }
888
- return;
889
- }
890
- if (r.method === "POST" && C === "/api/git/commit") {
891
- try {
892
- const i = await H(r), { files: e, title: d, message: f } = JSON.parse(i);
893
- if (!(d != null && d.trim())) {
894
- t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "title is required" }));
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");
1218
+ }
1219
+ const h = r.startForPlan(d, c, "reconcile");
1220
+ if (!h.ok) {
1221
+ p(e, 409, { error: h.error });
895
1222
  return;
896
1223
  }
897
- await o.commit(e ?? [], d.trim(), f == null ? void 0 : f.trim()), t.statusCode = 200, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ ok: !0 }));
898
- } catch (i) {
899
- t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: i.message }));
1224
+ p(e, 202, { ok: !0 });
900
1225
  }
901
- return;
902
- }
903
- if (r.method === "GET" && C === "/api/status") {
904
- t.statusCode = 200, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify(s.getStatus()));
905
- return;
906
- }
907
- if (r.method === "POST" && C === "/api/status/check") {
908
- const e = new URL(r.url ?? "", `http://${r.headers.host ?? "localhost"}`).searchParams.get("name");
909
- if (e !== "lint" && e !== "format" && e !== "test") {
910
- t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "name must be lint, format, or test" }));
911
- return;
912
- }
913
- s.runCheck(e), t.statusCode = 202, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ ok: !0 }));
914
- return;
915
- }
916
- if (r.method === "POST" && C === "/api/status/fix") {
917
- s.runQualityFix(), t.statusCode = 202, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ ok: !0 }));
918
- return;
919
- }
920
- if (r.method === "GET" && C === "/api/activity/stream") {
921
- t.statusCode = 200, t.setHeader("Content-Type", "text/event-stream"), t.setHeader("Cache-Control", "no-cache"), t.setHeader("Connection", "keep-alive"), t.flushHeaders(), c.subscribe(t), o.subscribe(t), s.subscribe(t), u.subscribe(t);
922
- return;
923
- }
924
- if (r.method === "GET" && C === "/api/agent/status") {
925
- t.statusCode = 200, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify(u.getStatus()));
926
- return;
927
- }
928
- if (r.method === "POST" && C === "/api/agent/launch") {
929
- try {
930
- const i = await H(r), { planId: e, phaseIndex: d } = JSON.parse(i);
931
- if (!e || typeof d != "number") {
932
- t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "planId and phaseIndex are 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" });
933
1235
  return;
934
1236
  }
935
- const p = k(await N(J(n, "plans.md"))).entries.find((h) => h.id === e);
936
- if (!p) {
937
- t.statusCode = 404, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "plan not found" }));
1237
+ const d = await Yt(t, l);
1238
+ if (!d) {
1239
+ p(e, 404, { error: "idea not found" });
938
1240
  return;
939
1241
  }
940
- const l = await A(n, o, p.id);
941
- if (l) {
942
- t.statusCode = 409, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: l }));
1242
+ const u = await rt(t, a);
1243
+ if (u) {
1244
+ p(e, 409, { error: u });
943
1245
  return;
944
1246
  }
945
- const m = await u.start(p, d);
946
- if (!m.ok) {
947
- t.statusCode = 409, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: m.error }));
1247
+ const f = r.startForIdea(d, c);
1248
+ if (!f.ok) {
1249
+ p(e, 409, { error: f.error });
948
1250
  return;
949
1251
  }
950
- t.statusCode = 202, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ ok: !0 }));
951
- } catch (i) {
952
- t.statusCode = 500, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: i.message }));
1252
+ p(e, 202, { ok: !0 });
953
1253
  }
954
- return;
955
- }
956
- if (r.method === "POST" && C === "/api/agent/launch-audit") {
957
- try {
958
- const i = await H(r), { planId: e, prompt: d } = JSON.parse(i);
959
- if (!e || !d) {
960
- t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "planId and prompt are required" }));
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" });
961
1263
  return;
962
1264
  }
963
- const p = k(await N(J(n, "plans.md"))).entries.find((h) => h.id === e);
964
- if (!p) {
965
- t.statusCode = 404, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "plan not found" }));
1265
+ const d = await Yt(t, l);
1266
+ if (!d) {
1267
+ p(e, 404, { error: "idea not found" });
966
1268
  return;
967
1269
  }
968
- const l = await A(n, o, p.id);
969
- if (l) {
970
- t.statusCode = 409, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: l }));
1270
+ const u = await rt(t, a);
1271
+ if (u) {
1272
+ p(e, 409, { error: u });
971
1273
  return;
972
1274
  }
973
- const m = u.startForPlan(p, d);
974
- if (!m.ok) {
975
- t.statusCode = 409, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: m.error }));
1275
+ const f = r.startForIdeaExtend(d, c);
1276
+ if (!f.ok) {
1277
+ p(e, 409, { error: f.error });
976
1278
  return;
977
1279
  }
978
- t.statusCode = 202, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ ok: !0 }));
979
- } catch (i) {
980
- t.statusCode = 500, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: i.message }));
1280
+ p(e, 202, { ok: !0 });
981
1281
  }
982
- return;
983
- }
984
- if (r.method === "POST" && C === "/api/agent/launch-draft") {
985
- try {
986
- const i = await H(r), { ideaId: e, prompt: d } = JSON.parse(i);
987
- if (!e || !d) {
988
- t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "ideaId and prompt are 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 });
1291
+ return;
1292
+ }
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 });
1299
+ }
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" });
989
1309
  return;
990
1310
  }
991
- const p = M(await N(J(n, "ideas.md"))).find((h) => h.id === e);
992
- if (!p) {
993
- t.statusCode = 404, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "idea not found" }));
1311
+ const c = await At(t, l);
1312
+ if (!c) {
1313
+ p(e, 404, { error: "plan not found" });
994
1314
  return;
995
1315
  }
996
- const l = await A(n, o);
997
- if (l) {
998
- t.statusCode = 409, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: l }));
1316
+ const d = await rt(t, a, c.id);
1317
+ if (d) {
1318
+ p(e, 409, { error: d });
999
1319
  return;
1000
1320
  }
1001
- const m = u.startForIdea(p, d);
1002
- if (!m.ok) {
1003
- t.statusCode = 409, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: m.error }));
1321
+ const u = r.startRunAllPhases(c, () => i.runChecksAndWait());
1322
+ if (!u.ok) {
1323
+ p(e, 409, { error: u.error });
1004
1324
  return;
1005
1325
  }
1006
- t.statusCode = 202, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ ok: !0 }));
1007
- } catch (i) {
1008
- t.statusCode = 500, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: i.message }));
1326
+ p(e, 202, { ok: !0 });
1327
+ }
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 });
1337
+ return;
1338
+ }
1339
+ p(e, 202, { ok: !0 });
1009
1340
  }
1010
- return;
1011
1341
  }
1012
- if (r.method === "POST" && C === "/api/agent/launch-extend") {
1013
- try {
1014
- const i = await H(r), { ideaId: e, prompt: d } = JSON.parse(i);
1015
- if (!e || !d) {
1016
- t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "ideaId and prompt are required" }));
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" });
1017
1369
  return;
1018
1370
  }
1019
- const p = M(await N(J(n, "ideas.md"))).find((h) => h.id === e);
1020
- if (!p) {
1021
- t.statusCode = 404, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "idea not found" }));
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" });
1022
1374
  return;
1023
1375
  }
1024
- const l = await A(n, o);
1025
- if (l) {
1026
- t.statusCode = 409, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: l }));
1376
+ if (c !== void 0 && c.trim().length === 0) {
1377
+ p(i, 400, { error: "projectName must not be empty" });
1027
1378
  return;
1028
1379
  }
1029
- const m = u.startForIdeaExtend(p, d);
1030
- if (!m.ok) {
1031
- t.statusCode = 409, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: m.error }));
1380
+ if (d !== void 0 && !Mt.includes(d)) {
1381
+ p(i, 400, { error: "defaultAgent must be a known agent id" });
1032
1382
  return;
1033
1383
  }
1034
- t.statusCode = 202, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ ok: !0 }));
1035
- } catch (i) {
1036
- t.statusCode = 500, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: i.message }));
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 });
1037
1410
  }
1038
- return;
1039
- }
1040
- if (r.method === "POST" && C === "/api/agent/stop") {
1041
- const i = u.stop();
1042
- if (!i.ok) {
1043
- t.statusCode = 409, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: i.error }));
1044
- return;
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));
1420
+ return;
1421
+ }
1422
+ if (!re.includes(r)) {
1423
+ p(i, 400, { error: "invalid config file name" });
1424
+ return;
1425
+ }
1426
+ const s = await j($(t, r));
1427
+ if (!s) {
1428
+ p(i, 404, { error: "config file not found" });
1429
+ return;
1430
+ }
1431
+ p(i, 200, { name: r, content: s });
1045
1432
  }
1046
- t.statusCode = 202, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ ok: !0 }));
1047
- return;
1048
1433
  }
1049
- if (r.method === "POST" && C === "/api/config") {
1050
- try {
1051
- const i = O(n, ".paper-camp", "config.json"), e = await N(i);
1052
- if (!e) {
1053
- t.statusCode = 404, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "config not found" }));
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" });
1054
1446
  return;
1055
1447
  }
1056
- const d = await H(r), { port: f, projectName: p, defaultAgent: l, defaultAgents: m } = JSON.parse(d);
1057
- if (f !== void 0 && (!Number.isInteger(f) || f <= 0)) {
1058
- t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "port must be a positive integer" }));
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" });
1059
1451
  return;
1060
1452
  }
1061
- if (p !== void 0 && p.trim().length === 0) {
1062
- t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "projectName must not be empty" }));
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" });
1063
1456
  return;
1064
1457
  }
1065
- if (l !== void 0 && !U.includes(l)) {
1066
- t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "defaultAgent must be a known agent id" }));
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
+ });
1067
1464
  return;
1068
1465
  }
1069
- if (m !== void 0) {
1070
- for (const v of ["phase", "planDraft", "ideaExtend"])
1071
- if (!U.includes(m[v])) {
1072
- t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: `defaultAgents.${v} must be a known agent id` }));
1073
- return;
1074
- }
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;
1075
1470
  }
1076
- const h = JSON.parse(e), a = m ?? (l !== void 0 ? { phase: l, planDraft: l, ideaExtend: l } : void 0), g = h, { defaultAgent: w, ...T } = g, j = {
1077
- ...T,
1078
- ...f !== void 0 && { port: f },
1079
- ...p !== void 0 && { projectName: p.trim() },
1080
- ...a && { defaultAgents: a }
1081
- };
1082
- await I(i, JSON.stringify(j, null, 2)), t.statusCode = 200, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ ok: !0 }));
1083
- } catch (i) {
1084
- t.statusCode = 500, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: i.message }));
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 });
1085
1485
  }
1086
- return;
1087
1486
  }
1088
- if (r.method === "GET" && C === "/api/env") {
1089
- try {
1090
- const i = O(n, ".env"), e = O(n, ".env.example"), [d, f] = await Promise.all([
1091
- et(i),
1092
- et(e)
1093
- ]), p = d ? V(await N(i)) : [], l = f ? V(await N(e)).map((a) => a.key) : [], m = new Set(p.map((a) => a.key)), h = l.filter((a) => !m.has(a));
1094
- t.statusCode = 200, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ exists: d, exampleExists: f, entries: p, missingKeys: h }));
1095
- } catch (i) {
1096
- t.statusCode = 500, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: i.message }));
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 });
1097
1501
  }
1098
- return;
1099
- }
1100
- if (r.method === "POST" && C === "/api/env") {
1101
- try {
1102
- const i = await H(r), { entries: e } = JSON.parse(i);
1103
- if (!Array.isArray(e)) {
1104
- t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "entries is required" }));
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" });
1105
1511
  return;
1106
1512
  }
1107
- const d = /* @__PURE__ */ new Set();
1108
- for (const l of e) {
1109
- if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(l.key)) {
1110
- t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: `invalid key: ${l.key}` }));
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}` });
1111
1517
  return;
1112
1518
  }
1113
- if (d.has(l.key)) {
1114
- t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: `duplicate key: ${l.key}` }));
1519
+ if (e.has(c.key)) {
1520
+ p(i, 400, { error: `duplicate key: ${c.key}` });
1115
1521
  return;
1116
1522
  }
1117
- d.add(l.key);
1523
+ e.add(c.key);
1118
1524
  }
1119
- const f = O(n, ".env"), p = await N(f);
1120
- await I(f, lt(p, e)), t.statusCode = 200, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ ok: !0 }));
1121
- } catch (i) {
1122
- t.statusCode = 500, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: i.message }));
1525
+ const n = $(t, ".env"), l = await j(n);
1526
+ await q(n, we(l, s)), p(i, 200, { ok: !0 });
1123
1527
  }
1124
- return;
1125
1528
  }
1126
- if (r.method === "GET" && C === "/api/configs") {
1127
- const e = new URL(r.url ?? "", `http://${r.headers.host ?? "localhost"}`).searchParams.get("name");
1128
- if (e)
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 });
1576
+ }
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) => {
1129
1583
  try {
1130
- if (!Lt.includes(e)) {
1131
- t.statusCode = 400, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "invalid config file name" }));
1584
+ await a.push(), p(s, 200, { ok: !0 });
1585
+ } catch (e) {
1586
+ p(s, 400, { error: e.message });
1587
+ }
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" });
1132
1599
  return;
1133
1600
  }
1134
- const d = await N(O(n, e));
1135
- if (!d) {
1136
- t.statusCode = 404, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: "config file not found" }));
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'" });
1615
+ return;
1616
+ }
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" });
1137
1620
  return;
1138
1621
  }
1139
- t.statusCode = 200, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ name: e, content: d }));
1622
+ await a.runGitSync(), p(s, 200, { ok: !0 });
1623
+ return;
1624
+ }
1625
+ const l = i.startSync(Ye);
1626
+ if (!l.ok) {
1627
+ p(s, 409, { error: l.error });
1628
+ return;
1629
+ }
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;
1643
+ }
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
+ }
1656
+ }
1657
+ }
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 });
1706
+ }
1707
+ }
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" });
1720
+ return;
1721
+ }
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);
1726
+ }
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 });
1737
+ }
1738
+ }
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" });
1751
+ return;
1752
+ }
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" });
1756
+ return;
1757
+ }
1758
+ const d = $(e, `${c.id}.md`);
1759
+ if (!await wt(d)) {
1760
+ p(r, 404, { error: "plan file not found" });
1761
+ return;
1762
+ }
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" });
1774
+ return;
1775
+ }
1776
+ const c = await rt(t, a);
1777
+ if (c) {
1778
+ p(r, 409, { error: c });
1779
+ return;
1780
+ }
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" });
1784
+ return;
1785
+ }
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()
1795
+ });
1796
+ await q($(h, `${f}.md`), `${y}
1797
+ `, "utf-8"), await Dt(t), p(r, 201, { ok: !0, id: f });
1798
+ }
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" });
1140
1818
  return;
1141
- } catch (d) {
1142
- t.statusCode = 500, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: d.message }));
1819
+ }
1820
+ const f = $(l, `${u.id}.md`), h = await j(f);
1821
+ if (!h) {
1822
+ p(r, 404, { error: "plan file not found" });
1143
1823
  return;
1144
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 });
1843
+ return;
1844
+ }
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 {
1872
+ }
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" });
1897
+ return;
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;
2042
+ }
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));
1145
2122
  }
1146
- const S = _t.find((i) => i.path === C);
1147
- if (!S) {
1148
- b();
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();
1149
2145
  return;
1150
2146
  }
1151
2147
  try {
1152
- const i = await S.handler(n);
1153
- t.statusCode = 200, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify(i));
1154
- } catch (i) {
1155
- t.statusCode = 500, t.setHeader("Content-Type", "application/json"), t.end(JSON.stringify({ error: i.message }));
2148
+ p(d, 200, await y.handler(t));
2149
+ } catch (g) {
2150
+ p(d, 500, { error: g.message });
1156
2151
  }
1157
2152
  };
1158
- return y.agent = u, y;
2153
+ return l.agent = e, l;
1159
2154
  }
1160
- const Bt = {
2155
+ const sn = {
1161
2156
  ".html": "text/html; charset=utf-8",
1162
2157
  ".js": "text/javascript; charset=utf-8",
1163
2158
  ".mjs": "text/javascript; charset=utf-8",
@@ -1170,81 +2165,304 @@ const Bt = {
1170
2165
  ".woff": "font/woff",
1171
2166
  ".woff2": "font/woff2"
1172
2167
  };
1173
- function Ft() {
1174
- return O(ct(wt(import.meta.url)), "..", "app");
2168
+ function rn() {
2169
+ return $(de(Ie(import.meta.url)), "..", "app");
1175
2170
  }
1176
- async function Gt({ root: n, port: c }) {
1177
- const o = Ft(), s = O(o, "index.html"), u = await $(s, "utf-8").catch(() => null);
1178
- if (u === 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)
1179
2174
  throw new Error(
1180
- `Dashboard assets not found at ${o}. 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.`
1181
2176
  );
1182
- const y = Mt(n);
1183
- async function r(C, S) {
1184
- const i = decodeURIComponent((C.url ?? "/").split("?")[0]), e = O(o, i === "/" ? "index.html" : i);
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);
1185
2180
  try {
1186
- if ((await st(e)).isFile()) {
1187
- S.statusCode = 200, S.setHeader("Content-Type", Bt[dt(e)] ?? "application/octet-stream"), S.end(await $(e));
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));
1188
2184
  return;
1189
2185
  }
1190
2186
  } catch {
1191
2187
  }
1192
- S.statusCode = 200, S.setHeader("Content-Type", "text/html; charset=utf-8"), S.end(u);
2188
+ u.statusCode = 200, u.setHeader("Content-Type", "text/html; charset=utf-8"), u.end(s);
1193
2189
  }
1194
- const t = Ct((C, S) => {
1195
- y(C, S, () => {
1196
- r(C, S).catch((i) => {
1197
- S.statusCode = 500, S.end(String(i));
2190
+ const l = xe((d, u) => {
2191
+ e(d, u, () => {
2192
+ n(d, u).catch((f) => {
2193
+ u.statusCode = 500, u.end(String(f));
1198
2194
  });
1199
2195
  });
1200
- }), b = () => {
1201
- y.agent.killCurrent(), process.exit(0);
2196
+ }), c = () => {
2197
+ e.agent.killCurrent(), process.exit(0);
2198
+ };
2199
+ process.on("SIGINT", c), process.on("SIGTERM", c), await new Promise((d, u) => {
2200
+ l.once("error", u), l.listen(a, d);
2201
+ });
2202
+ }
2203
+ async function Bt(t) {
2204
+ try {
2205
+ return await ht(t), !0;
2206
+ } catch {
2207
+ return !1;
2208
+ }
2209
+ }
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;
2215
+ }
2216
+ async function dn(t) {
2217
+ try {
2218
+ return (await ht(t)).mtime.toISOString().slice(0, 10);
2219
+ } catch {
2220
+ return null;
2221
+ }
2222
+ }
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
1202
2242
  };
1203
- process.on("SIGINT", b), process.on("SIGTERM", b), await new Promise((C) => t.listen(c, C));
2243
+ await q(t, `${xt(e)}
2244
+ `, "utf-8");
1204
2245
  }
1205
- const D = new ut();
1206
- D.name("paper-camp").description("Local-first, AI-native project companion.").version(gt);
1207
- D.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 (n, c) => {
1208
- const o = process.cwd(), s = n ?? pt(o);
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,
2250
+ stdio: ["ignore", "pipe", "pipe"]
2251
+ });
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}
2255
+ `);
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));
2258
+ });
2259
+ }
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);
1209
2264
  try {
1210
- await mt(o, { projectName: s, intent: c.intent }), console.log(`Initialized Paper Camp in ${o}`), console.log(" .paper-camp/config.json"), console.log(" papercamp/ideas.md, plans.md, progress.md, decisions.md, open-questions.md");
1211
- } catch (u) {
1212
- if (u instanceof yt) {
1213
- console.error(u.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;
1214
2269
  return;
1215
2270
  }
1216
- throw u;
2271
+ throw s;
1217
2272
  }
1218
2273
  });
1219
- D.command("dev").description("Start the local dashboard").option("-p, --port <number>", "port to listen on", "3333").action(async (n) => {
1220
- const c = Number(n.port), o = 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();
1221
2276
  try {
1222
- await Gt({ root: o, port: c }), console.log(`Paper Camp dashboard running at http://localhost:${c}`);
1223
- } catch (s) {
1224
- console.error(s.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);
1225
2280
  }
1226
2281
  });
1227
- D.command("add <type> [name]").description("Add a new entry (currently supports: plan)").option("-k, --kind <kind>", `plan kind (${F.join("|")})`, "feat").action(async (n, c, o) => {
1228
- if (n !== "plan") {
1229
- console.error(`Unknown type "${n}". 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;
1230
2285
  return;
1231
2286
  }
1232
- if (!c) {
2287
+ if (!a) {
1233
2288
  console.error("Usage: paper-camp add plan <name> [--kind feat|fix|chore|docs|refactor]"), process.exitCode = 1;
1234
2289
  return;
1235
2290
  }
1236
- if (!F.includes(o.kind)) {
1237
- console.error(`Unknown kind "${o.kind}". Supported kinds: ${F.join(", ")}`), process.exitCode = 1;
2291
+ if (!Et.includes(i.kind)) {
2292
+ console.error(`Unknown kind "${i.kind}". Supported kinds: ${Et.join(", ")}`), process.exitCode = 1;
1238
2293
  return;
1239
2294
  }
1240
- const s = o.kind, u = Y(process.cwd(), ".paper-camp", "config.json"), y = await at(u, s), r = Y(process.cwd(), "papercamp", "plans.md"), t = it({
1241
- title: c,
2295
+ const r = i.kind, s = process.cwd(), e = ft(s, "papercamp", "config.json"), n = await ae(e, r);
2296
+ if (!n) {
2297
+ console.error("Could not assign plan ID — is the project initialized?"), process.exitCode = 1;
2298
+ return;
2299
+ }
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,
1242
2306
  status: "idea",
1243
- kind: s,
1244
- id: y,
1245
- created: B()
2307
+ created: ot()
1246
2308
  });
1247
- await ot(r, t), console.log(`Added plan "${c}"${y ? ` (${y})` : ""} to papercamp/plans.md`);
2309
+ await q($(l, `${n}.md`), `${c}
2310
+ `, "utf-8");
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`);
2313
+ });
2314
+ yt.command("migrate").description(
2315
+ "One-time migration: split monolithic plans.md/ideas.md into per-file YAML frontmatter entries"
2316
+ ).action(async () => {
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++;
2330
+ continue;
2331
+ }
2332
+ const m = S.status === "done" || S.status === "dropped" ? i : a, w = $(m, `${S.id}.md`);
2333
+ if (await Bt(w)) {
2334
+ l++;
2335
+ continue;
2336
+ }
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
2351
+ });
2352
+ await q(w, `${I}
2353
+ `, "utf-8"), n++;
2354
+ }
2355
+ }
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++;
2363
+ continue;
2364
+ }
2365
+ const S = $(r, `${D.id}.md`);
2366
+ if (await Bt(S)) {
2367
+ f++;
2368
+ continue;
2369
+ }
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++;
2373
+ }
2374
+ }
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).`
2380
+ );
2381
+ });
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) {
2389
+ console.log('No plans with status "review" or "done" found.');
2390
+ return;
2391
+ }
2392
+ const e = await at($(t, "papercamp", "config.json"), "utf-8").catch(
2393
+ () => "{}"
2394
+ );
2395
+ let n;
2396
+ try {
2397
+ n = JSON.parse(e);
2398
+ } catch {
2399
+ console.error("Invalid papercamp/config.json"), process.exitCode = 1;
2400
+ return;
2401
+ }
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):
2409
+ `);
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" });
2415
+ continue;
2416
+ }
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" });
2420
+ continue;
2421
+ }
2422
+ if (m.audited) {
2423
+ const B = await dn(T);
2424
+ if (B && m.audited >= B) {
2425
+ console.log(
2426
+ ` [skip] ${I} ${m.title} — audited ${m.audited}, unchanged since`
2427
+ ), h.push({
2428
+ id: w,
2429
+ title: m.title,
2430
+ status: "skipped",
2431
+ skipReason: `audited ${m.audited}, unchanged`
2432
+ });
2433
+ continue;
2434
+ }
2435
+ }
2436
+ const O = m.phases.length;
2437
+ if (console.log(` [audit] ${I} ${m.title}`), await un(t, m, d, { model: u, effort: f })) {
2438
+ try {
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" });
2442
+ continue;
2443
+ }
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 });
2446
+ } else
2447
+ console.log(` [fail] ${I} ${m.title} — agent exited with error`), process.exitCode = 1, h.push({ id: w, title: m.title, status: "failed" });
2448
+ }
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);
2450
+ if (console.log(`
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)`);
2458
+ } else
2459
+ console.log(" No gap phases appended — all audited plans are complete.");
2460
+ if (g.length > 0) {
2461
+ console.log(" Skipped:");
2462
+ for (const m of g)
2463
+ console.log(` ${m.id.padEnd(14)} ${m.skipReason}`);
2464
+ }
2465
+ console.log(D);
1248
2466
  });
1249
- D.parseAsync(process.argv);
2467
+ yt.parseAsync(process.argv);
1250
2468
  //# sourceMappingURL=index.js.map