@dendelion/paper-camp 0.44.0 → 0.45.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 (68) hide show
  1. package/dist/chunks/{daemon-state.Evil_jr0.js → daemon-state.BEsOWFz5.js} +79 -75
  2. package/dist/chunks/daemon-state.BEsOWFz5.js.map +1 -0
  3. package/dist/chunks/{dev-port.BLsETEnM.js → dev-port.CKE4LWyl.js} +2 -2
  4. package/dist/chunks/{dev-port.BLsETEnM.js.map → dev-port.CKE4LWyl.js.map} +1 -1
  5. package/dist/chunks/phase-progress.BCqVUFLp.js +4094 -0
  6. package/dist/chunks/phase-progress.BCqVUFLp.js.map +1 -0
  7. package/dist/cli/index.js +7353 -6320
  8. package/dist/cli/index.js.map +1 -1
  9. package/dist/core/assets.d.ts +11 -0
  10. package/dist/core/assets.d.ts.map +1 -0
  11. package/dist/core/batch-readiness.d.ts +43 -0
  12. package/dist/core/batch-readiness.d.ts.map +1 -0
  13. package/dist/core/batch-readiness.test.d.ts +2 -0
  14. package/dist/core/batch-readiness.test.d.ts.map +1 -0
  15. package/dist/core/batch-run.d.ts +106 -0
  16. package/dist/core/batch-run.d.ts.map +1 -0
  17. package/dist/core/batch-run.test.d.ts +2 -0
  18. package/dist/core/batch-run.test.d.ts.map +1 -0
  19. package/dist/core/capacity.d.ts +6 -2
  20. package/dist/core/capacity.d.ts.map +1 -1
  21. package/dist/core/corpus.d.ts +13 -1
  22. package/dist/core/corpus.d.ts.map +1 -1
  23. package/dist/core/doctor/checks/assets.d.ts +3 -0
  24. package/dist/core/doctor/checks/assets.d.ts.map +1 -0
  25. package/dist/core/doctor/checks/assets.test.d.ts +2 -0
  26. package/dist/core/doctor/checks/assets.test.d.ts.map +1 -0
  27. package/dist/core/doctor/checks/local-state.d.ts +3 -0
  28. package/dist/core/doctor/checks/local-state.d.ts.map +1 -0
  29. package/dist/core/doctor/checks/local-state.test.d.ts +2 -0
  30. package/dist/core/doctor/checks/local-state.test.d.ts.map +1 -0
  31. package/dist/core/doctor/doctor.d.ts +12 -0
  32. package/dist/core/doctor/doctor.d.ts.map +1 -1
  33. package/dist/core/doctor/rules.d.ts +31 -1
  34. package/dist/core/doctor/rules.d.ts.map +1 -1
  35. package/dist/core/entity-view.d.ts.map +1 -1
  36. package/dist/core/git-log.d.ts +4 -0
  37. package/dist/core/git-log.d.ts.map +1 -1
  38. package/dist/core/git-pr/pr-lookup.d.ts.map +1 -1
  39. package/dist/core/index.js +154 -149
  40. package/dist/core/local-paths.d.ts +17 -0
  41. package/dist/core/local-paths.d.ts.map +1 -0
  42. package/dist/core/machine-registry/machine-registry.d.ts +3 -0
  43. package/dist/core/machine-registry/machine-registry.d.ts.map +1 -1
  44. package/dist/core/night-health.d.ts.map +1 -1
  45. package/dist/core/parse/schemas.d.ts +4 -0
  46. package/dist/core/parse/schemas.d.ts.map +1 -1
  47. package/dist/core/project-config.d.ts +2 -0
  48. package/dist/core/project-config.d.ts.map +1 -1
  49. package/dist/core/readers.d.ts.map +1 -1
  50. package/dist/core/release-notes.d.ts +7 -1
  51. package/dist/core/release-notes.d.ts.map +1 -1
  52. package/dist/core/scaffold/scaffold.d.ts +1 -0
  53. package/dist/core/scaffold/scaffold.d.ts.map +1 -1
  54. package/dist/core/stats.d.ts +3 -0
  55. package/dist/core/stats.d.ts.map +1 -1
  56. package/dist/core/trail.d.ts +6 -0
  57. package/dist/core/trail.d.ts.map +1 -1
  58. package/dist/toolbar/toolbar.js +3341 -3257
  59. package/dist/toolbar/toolbar.js.map +1 -1
  60. package/dist/types/index.d.ts +51 -5
  61. package/dist/types/index.d.ts.map +1 -1
  62. package/dist/types/index.js +24 -19
  63. package/dist/types/index.js.map +1 -1
  64. package/dist/vite/index.js +1 -1
  65. package/package.json +2 -1
  66. package/dist/chunks/daemon-state.Evil_jr0.js.map +0 -1
  67. package/dist/chunks/needs.BmY0GqQY.js +0 -3630
  68. package/dist/chunks/needs.BmY0GqQY.js.map +0 -1
@@ -0,0 +1,4094 @@
1
+ var In = Object.defineProperty;
2
+ var An = (e, t, n) => t in e ? In(e, t, { enumerable: !0, configurable: !0, writable: !0, value: n }) : e[t] = n;
3
+ var Ye = (e, t, n) => An(e, typeof t != "symbol" ? t + "" : t, n);
4
+ import { readFileSync as Ee, existsSync as ke } from "node:fs";
5
+ import { join as f, basename as mt, extname as _n, dirname as C } from "node:path";
6
+ import { parse as se, stringify as Rn } from "yaml";
7
+ import { readdir as M, stat as O, readFile as v, writeFile as b, mkdir as $, rename as gt, access as Tn } from "node:fs/promises";
8
+ import { z as Pn, A as Nn, n as ae, e as ie, a as xn, u as Cn } from "./daemon-state.BEsOWFz5.js";
9
+ import { createHash as On } from "node:crypto";
10
+ import { spawn as S } from "node:child_process";
11
+ import { AGENT_IDS as Y, NIGHT_CHECK_IDS as Dn, NOTIFICATION_SETTING_KINDS as Ln, PLAN_KINDS as Mn } from "../types/index.js";
12
+ import { fileURLToPath as jn } from "node:url";
13
+ import { z as u } from "zod";
14
+ import "node:os";
15
+ const Se = /^#{2,3}\s+/, X = /^[-*]\s+\[([ xX])\]\s+(.*)$/, Fn = /^\[(review|manual|issue)\]\s+(.*)$/, Hn = /^run:\s*(.+)$/, qe = /^-\s+(\d{4}-\d{2}-\d{2}):\s*(.*)$/, Bn = /^\[(?:phase:(\d+)|body)\]\s+(?:\[(decision|question)\]\s+)?(.*)$/, yt = /^-\s+\[([ xX])\]\s+(?:(\d{4}-\d{2}-\d{2}(?:T\d{2}:\d{2})?)\s+)?\[(log|clarification|review|note|decision|question|chat)\]\s+(\[agent\]\s+)?(?:\[\[([A-Za-z]+-\d+)\]\]\s+)?(.*)$/, Gn = ["note", "decision", "question"], Un = /^option:\s*(.*)$/i, Yn = /^context:\s*(.*)$/i;
16
+ function wt(e, t, n, s) {
17
+ const a = [];
18
+ let i = t;
19
+ for (; i < n; ) {
20
+ const r = e[i];
21
+ if (r.trim() === "" || !/^\s/.test(r)) break;
22
+ const o = r.trimStart();
23
+ if (s.test(o) || Se.test(o)) break;
24
+ a.push(o), i++;
25
+ }
26
+ return { text: a.join(" "), next: i };
27
+ }
28
+ function $e(e, t) {
29
+ return t ? `${e.trim()} ${t}` : e.trim();
30
+ }
31
+ function bt(e, t, n) {
32
+ const s = [];
33
+ let a = t;
34
+ for (; a < n; ) {
35
+ const i = e[a].match(X);
36
+ if (i) {
37
+ const r = i[1].toLowerCase() === "x", o = i[2].trim(), c = o.match(Fn), d = c ? c[2].trim() : o, l = c ? c[1] : void 0, p = [];
38
+ let h;
39
+ for (a++; a < n; ) {
40
+ const g = e[a];
41
+ if (g.trim() === "" || X.test(g) || Se.test(g)) break;
42
+ if (/^\s/.test(g)) {
43
+ const w = g.trimStart(), m = w.match(Hn), y = m ? Pn(m[1]) : void 0;
44
+ y ? h = y : p.push(w), a++;
45
+ } else
46
+ break;
47
+ }
48
+ s.push({
49
+ done: r,
50
+ text: d,
51
+ description: p.length > 0 ? p.join(`
52
+ `) : void 0,
53
+ source: l,
54
+ run: h
55
+ });
56
+ } else
57
+ a++;
58
+ }
59
+ return s;
60
+ }
61
+ function Ie(e, t, n) {
62
+ const s = [];
63
+ let a = t;
64
+ for (; a < n; ) {
65
+ const i = e[a].match(qe);
66
+ if (!i) {
67
+ a++;
68
+ continue;
69
+ }
70
+ const r = wt(e, a + 1, n, qe);
71
+ s.push({ date: i[1], text: $e(i[2], r.text) }), a = r.next;
72
+ }
73
+ return s;
74
+ }
75
+ function vt(e, t) {
76
+ const n = [e];
77
+ for (const s of t) {
78
+ const a = s.source ? `[${s.source}] ${s.text}` : s.text;
79
+ if (n.push(`- [${s.done ? "x" : " "}] ${a}`), s.description)
80
+ for (const i of s.description.split(`
81
+ `)) n.push(` ${i}`);
82
+ s.run && n.push(` run: ${Nn(s.run)}`);
83
+ }
84
+ return n;
85
+ }
86
+ function Ae(e, t) {
87
+ return [e, ...t.map((n) => `- ${n.date}: ${n.text}`)];
88
+ }
89
+ function qn(e) {
90
+ return e.kind === "phase" ? `[phase:${e.index}]` : "[body]";
91
+ }
92
+ function Jn(e, t, n) {
93
+ const s = [];
94
+ let a = t;
95
+ for (; a < n; ) {
96
+ const i = e[a].match(X), r = i ? i[2].trim().match(Bn) : null;
97
+ if (!i || !r) {
98
+ a++;
99
+ continue;
100
+ }
101
+ const o = r[1] ? { kind: "phase", index: Number(r[1]) } : { kind: "body" }, c = r[2], d = wt(e, a + 1, n, X);
102
+ s.push({
103
+ anchor: o,
104
+ prose: $e(r[3], d.text),
105
+ state: i[1].toLowerCase() === "x" ? "resolved" : "open",
106
+ ...c ? { kind: c } : {}
107
+ }), a = d.next;
108
+ }
109
+ return s;
110
+ }
111
+ function zn(e) {
112
+ const t = ["### Notes"];
113
+ for (const n of e) {
114
+ const s = n.kind && n.kind !== "note" ? `[${n.kind}] ` : "";
115
+ t.push(
116
+ `- [${n.state === "resolved" ? "x" : " "}] ${qn(n.anchor)} ${s}${n.prose}`
117
+ );
118
+ }
119
+ return t;
120
+ }
121
+ const re = {
122
+ headingRe: /^#{2,3}\s+Phases\s*$/i,
123
+ parseEntries: bt,
124
+ formatLines: (e) => vt("### Phases", e)
125
+ }, Et = {
126
+ headingRe: /^#{2,3}\s+Fixes\s*$/i,
127
+ parseEntries: bt,
128
+ formatLines: (e) => vt("### Fixes", e)
129
+ }, q = {
130
+ headingRe: /^#{2,3}\s+Log\s*$/i,
131
+ parseEntries: Ie,
132
+ formatLines: (e) => Ae("### Log", e)
133
+ }, _e = {
134
+ headingRe: /^#{2,3}\s+Clarifications\s*$/i,
135
+ parseEntries: Ie,
136
+ formatLines: (e) => Ae("### Clarifications", e)
137
+ }, Re = {
138
+ headingRe: /^#{2,3}\s+Notes\s*$/i,
139
+ parseEntries: Jn,
140
+ formatLines: zn
141
+ }, Kn = {
142
+ headingRe: /^#{2,3}\s+Review\s*$/i,
143
+ parseEntries: Ie,
144
+ formatLines: (e) => Ae("### Review", e)
145
+ };
146
+ function Wn(e, t, n) {
147
+ const s = [], a = [];
148
+ let i, r = t;
149
+ for (; r < n; ) {
150
+ const o = e[r];
151
+ if (o.trim() === "" || !/^\s/.test(o)) break;
152
+ const c = o.trimStart();
153
+ if (yt.test(c) || Se.test(c)) break;
154
+ const d = c.match(Un), l = c.match(Yn);
155
+ if (d) {
156
+ const [p, h] = d[1].split("—").map((g) => g.trim());
157
+ a.push({ label: p || d[1].trim(), consequence: h ?? "" });
158
+ } else l ? i = l[1].trim() : s.push(c);
159
+ r++;
160
+ }
161
+ return { text: s.join(" "), options: a, context: i, next: r };
162
+ }
163
+ function Vn(e, t, n) {
164
+ const s = [];
165
+ let a = t;
166
+ for (; a < n; ) {
167
+ const i = e[a].match(yt);
168
+ if (!i) {
169
+ a++;
170
+ continue;
171
+ }
172
+ const r = i[3], o = Wn(e, a + 1, n), c = { kind: r, text: $e(i[6], o.text) };
173
+ i[2] && (c.date = i[2]), i[4] && (c.from = "agent"), i[5] && (c.entityId = i[5]), o.options.length > 0 && (c.options = o.options), o.context !== void 0 && (c.context = o.context), Gn.includes(r) && (c.state = i[1].toLowerCase() === "x" ? "resolved" : "open"), s.push(c), a = o.next;
174
+ }
175
+ return s;
176
+ }
177
+ function Xn(e) {
178
+ const t = ["### Thread"];
179
+ for (const n of e) {
180
+ const s = n.state ? n.state === "resolved" : !0, a = n.date ? `${n.date} ` : "", i = n.from === "agent" ? "[agent] " : "", r = n.entityId ? `[[${n.entityId}]] ` : "";
181
+ t.push(`- [${s ? "x" : " "}] ${a}[${n.kind}] ${i}${r}${n.text}`);
182
+ for (const o of n.options ?? [])
183
+ t.push(` option: ${o.label} — ${o.consequence}`);
184
+ n.context && t.push(` context: ${n.context}`);
185
+ }
186
+ return t;
187
+ }
188
+ const kt = {
189
+ headingRe: /^#{2,3}\s+Thread\s*$/i,
190
+ parseEntries: Vn,
191
+ formatLines: Xn
192
+ };
193
+ function Qn(e = [], t = [], n = [], s = []) {
194
+ const a = [
195
+ ...e.map((r) => ({ kind: "log", date: r.date, text: r.text })),
196
+ ...t.map((r) => ({ kind: "clarification", date: r.date, text: r.text })),
197
+ ...s.map((r) => ({ kind: "review", date: r.date, text: r.text }))
198
+ ].sort((r, o) => r.date.localeCompare(o.date)), i = n.map((r) => ({
199
+ kind: r.kind ?? "note",
200
+ text: r.anchor.kind === "phase" ? `[phase ${r.anchor.index}] ${r.prose}` : r.prose,
201
+ state: r.state
202
+ }));
203
+ return [...a, ...i];
204
+ }
205
+ function Te(e, t) {
206
+ return e.filter((n) => n.kind === t).map((n) => ({ date: n.date ?? "", text: n.text }));
207
+ }
208
+ function St(e = []) {
209
+ return Te(e, "log");
210
+ }
211
+ function Zn(e = []) {
212
+ return Te(e, "clarification");
213
+ }
214
+ function es(e = []) {
215
+ return Te(e, "review");
216
+ }
217
+ function ts(e = []) {
218
+ return e.filter(
219
+ (t) => t.kind === "note" || t.kind === "decision" || t.kind === "question"
220
+ ).map((t) => ({
221
+ anchor: { kind: "body" },
222
+ prose: t.text,
223
+ state: t.state ?? "open",
224
+ kind: t.kind
225
+ }));
226
+ }
227
+ function uo(e, t, n) {
228
+ return [...(e ?? []).filter((s) => !t.includes(s.kind)), ...n];
229
+ }
230
+ function lo(e, t, n, s) {
231
+ return e.map((a, i) => {
232
+ if (i !== t) return a;
233
+ const r = s ? `${a.text}
234
+
235
+ ${s}` : a.text;
236
+ if (n === "decision") return { ...a, kind: n, state: "open", text: r };
237
+ const { state: o, ...c } = a;
238
+ return { ...c, kind: n, text: r };
239
+ });
240
+ }
241
+ function po(e = []) {
242
+ const t = e.reduce((n, s, a) => s.kind === "log" ? a : n, -1);
243
+ return e.slice(t + 1).filter((n) => n.kind === "chat");
244
+ }
245
+ const _ = u.preprocess(
246
+ (e) => typeof e == "string" ? { agent: e } : e,
247
+ u.object({
248
+ agent: u.enum(Y),
249
+ model: u.string().optional(),
250
+ effort: u.string().optional()
251
+ })
252
+ ), R = u.string().regex(/^\d{4}-\d{2}-\d{2}$/, "expected YYYY-MM-DD"), ns = u.object({
253
+ status: u.enum(["idea", "planned", "in-progress", "review", "done", "dropped"]),
254
+ kind: u.enum(["feat", "fix", "chore", "docs", "refactor"]).optional(),
255
+ id: u.string().optional(),
256
+ idea: u.string().optional(),
257
+ agent: u.enum(Y).optional(),
258
+ created: R,
259
+ updated: R.optional(),
260
+ tags: u.string().optional()
261
+ }), ss = u.object({
262
+ id: u.string().describe("Permanent plan ID, e.g. FEAT-24"),
263
+ title: u.string().describe('Human-readable plan name, e.g. "Plan storage architecture"'),
264
+ kind: u.enum(["feat", "fix", "chore", "docs", "refactor"]).describe("Plan kind matching Conventional Commits types"),
265
+ status: u.enum(["idea", "planned", "in-progress", "review", "done", "dropped"]).describe("Current lifecycle status"),
266
+ idea: u.string().optional().describe("IDEA-N backlink if this plan grew out of an idea"),
267
+ agent: u.enum(Y).optional().describe("Per-plan agent override"),
268
+ created: R.describe("Creation date (YYYY-MM-DD)"),
269
+ updated: R.optional().describe("Last significant update date (YYYY-MM-DD)"),
270
+ audited: R.optional().describe("Date of last successful convergence audit (YYYY-MM-DD)"),
271
+ "audited-hash": u.string().optional().describe("Content hash of the plan at last audit, used to detect edits regardless of mtime"),
272
+ tags: u.array(u.string()).optional().describe("Tagging categories")
273
+ }).passthrough(), as = u.object({
274
+ id: u.string().describe("Permanent idea ID, e.g. IDEA-20"),
275
+ title: u.string().describe("Short idea headline (3-6 words)"),
276
+ kind: u.enum(["idea", "note"]).optional().describe('"note" for ideas that never need a plan; omitted means a plan-bearing idea'),
277
+ status: u.enum(["open", "done", "dropped"]).optional().describe("Manual lifecycle, valid only on notes — plan-bearing ideas carry no status")
278
+ }).passthrough().refine((e) => e.status === void 0 || e.kind === "note", {
279
+ message: "status is only valid on ideas with kind: note",
280
+ path: ["status"]
281
+ }), $t = u.object({
282
+ id: u.string().describe("Permanent lifetime entity ID, e.g. IDEA-45 — never changes"),
283
+ title: u.string().describe("Human-readable entity name"),
284
+ type: u.enum(["feat", "fix", "chore", "docs", "refactor"]).optional().describe(
285
+ "Work classification (Conventional Commits values) driving commit types and branch prefixes; usually set once a plan is drafted"
286
+ ),
287
+ kind: u.enum(["note", "fix", "board", "ticket"]).optional().describe(
288
+ '"note" marks an entity that never grows phases; "fix" is a follow-up entity linked to a done/archived parent via `idea`; "board" decomposes into "ticket" children instead of phases; "ticket" is a full entity linked back to its board via `idea`; omitted for normal ideas'
289
+ ),
290
+ status: u.enum(["idea", "planned", "in-progress", "review", "done", "dropped", "open"]).optional().describe(
291
+ "Stored override, no longer the source of truth: most lifecycle states derive from phases/branch/PR. Only dropped, a planless idea/note being closed, or the offline fallback need this set."
292
+ ),
293
+ agent: u.enum(Y).optional().describe("Per-entity agent override"),
294
+ created: R.describe("Creation date (YYYY-MM-DD)"),
295
+ updated: R.optional().describe("Last significant update date (YYYY-MM-DD)"),
296
+ audited: R.optional().describe("Date of last successful convergence audit (YYYY-MM-DD)"),
297
+ "audited-hash": u.string().optional().describe(
298
+ "Content hash of the entity at last audit, used to detect edits regardless of mtime"
299
+ ),
300
+ released: u.string().optional().describe(
301
+ "Version tag (e.g. v0.13.1) that first shipped this idea, stamped from the release train"
302
+ ),
303
+ tags: u.array(u.string()).optional().describe("Tagging categories"),
304
+ needs: u.array(u.string()).optional().describe(
305
+ "Other entities this one waits on before it can run: IDEA-N for the same project, <slug>/IDEA-N for another project registered on the same machine"
306
+ ),
307
+ idea: u.string().optional().describe("IDEA-N backlink to the parent this fix addresses; required when kind: fix"),
308
+ subject: u.string().optional().describe('Subject group name; absent renders as the virtual "No subject" group'),
309
+ order: u.number().int().optional().describe("Run order; absent means unordered, sorting after ordered entries"),
310
+ issueSource: u.string().optional().describe(
311
+ "sourceKind:sourceKey of the Issue (IDEA-192) this entity was promoted from, if any"
312
+ )
313
+ }).passthrough(), is = new Set(
314
+ Object.keys($t.shape)
315
+ ), It = $t.refine(
316
+ (e) => e.kind !== "note" || e.status === void 0 || ["open", "done", "dropped"].includes(e.status),
317
+ {
318
+ message: "a note entity must use status open, done, or dropped",
319
+ path: ["status"]
320
+ }
321
+ ).refine((e) => e.kind === "note" || e.status !== "open", {
322
+ message: "status open is only valid on entities with kind: note",
323
+ path: ["status"]
324
+ }).refine((e) => e.kind !== "fix" || e.idea !== void 0, {
325
+ message: "a kind: fix entity requires an idea: link to its parent",
326
+ path: ["idea"]
327
+ }).refine((e) => e.kind !== "ticket" || e.idea !== void 0, {
328
+ message: "a kind: ticket entity requires an idea: link to its board",
329
+ path: ["idea"]
330
+ }), rs = u.object({
331
+ name: u.string(),
332
+ cmd: u.string(),
333
+ port: u.number().int().positive().optional(),
334
+ healthcheck: u.string().optional()
335
+ }), os = u.object({
336
+ name: u.string(),
337
+ cmd: u.string(),
338
+ fixCmd: u.string().optional()
339
+ }), cs = u.object({
340
+ repo: u.string(),
341
+ branch: u.string().optional(),
342
+ releasePlease: u.boolean().optional(),
343
+ draftPr: u.boolean().optional()
344
+ }), At = u.object({
345
+ services: u.array(rs).optional(),
346
+ checks: u.array(os).optional(),
347
+ ci: cs.optional()
348
+ }), ds = u.object({
349
+ from: u.string(),
350
+ to: u.string()
351
+ }), us = u.object({
352
+ name: u.string(),
353
+ prompt: u.string()
354
+ }), ls = u.object({
355
+ ceiling: u.number().optional(),
356
+ floor: u.number().optional(),
357
+ window: ds.optional(),
358
+ roots: u.array(u.string()).optional(),
359
+ maxChunks: u.number().int().positive().optional(),
360
+ threshold: u.number().min(0).max(100).optional(),
361
+ checks: u.record(u.enum(Dn), u.boolean()).optional(),
362
+ customChecks: u.array(us).optional(),
363
+ maxTurns: u.number().int().positive().optional(),
364
+ maxCostUsd: u.number().positive().optional()
365
+ }), ps = u.object({
366
+ kinds: u.record(u.enum(Ln), u.boolean()).optional()
367
+ }), fs = u.object({
368
+ endpoint: u.string(),
369
+ keys: u.object({ p256dh: u.string(), auth: u.string() })
370
+ }), fo = u.discriminatedUnion("transport", [
371
+ u.object({
372
+ transport: u.literal("webpush"),
373
+ name: u.string(),
374
+ subscription: fs
375
+ }),
376
+ u.object({ transport: u.literal("expo"), name: u.string(), token: u.string() })
377
+ ]), ho = u.discriminatedUnion("transport", [
378
+ u.object({ transport: u.literal("webpush"), endpoint: u.string() }),
379
+ u.object({ transport: u.literal("expo"), token: u.string() })
380
+ ]), hs = u.object({
381
+ version: u.number().int().describe("Corpus format version (see CORPUS_FORMAT_VERSION), not the package version"),
382
+ projectName: u.string(),
383
+ initializedAt: u.string(),
384
+ nextId: u.object({
385
+ // idea: unified-entity counter minting lifetime IDEA-N ids. ticket: board/ticket
386
+ // counter (IDEA-201) minting TICKET-N ids. The rest are legacy per-kind counters.
387
+ idea: u.number().optional(),
388
+ ticket: u.number().optional(),
389
+ feat: u.number().optional(),
390
+ fix: u.number().optional(),
391
+ chore: u.number().optional(),
392
+ docs: u.number().optional(),
393
+ refactor: u.number().optional()
394
+ }).optional(),
395
+ defaultAgent: u.enum(Y).optional(),
396
+ defaultAgents: u.object({
397
+ phase: _,
398
+ planDraft: _,
399
+ ideaExtend: _,
400
+ commitSuggest: _,
401
+ feedback: _,
402
+ codeReview: _,
403
+ deskDiscovery: _,
404
+ nightShift: _
405
+ }).optional(),
406
+ desk: At.optional(),
407
+ night: ls.optional(),
408
+ notifications: ps.optional()
409
+ }), ms = u.object({
410
+ id: u.string(),
411
+ kind: u.enum([
412
+ "completed",
413
+ "reply",
414
+ "check-failed",
415
+ "pr-review-changes-requested",
416
+ "night-review-findings",
417
+ "service-stopped"
418
+ ]),
419
+ entityId: u.string(),
420
+ entityTitle: u.string(),
421
+ text: u.string(),
422
+ date: u.string(),
423
+ read: u.boolean(),
424
+ outcome: u.enum(["done", "error"]).optional(),
425
+ push: u.boolean().optional().default(!0)
426
+ }), Je = /^##\s+(.+?)\s*$/, gs = /^\*\*([A-Za-z][A-Za-z-]*):\*\*\s*(.*)$/, ys = /^#{2,3}\s+/;
427
+ function Q(e, t) {
428
+ const n = e.split(`
429
+ `), s = n.findIndex((o) => t.headingRe.test(o));
430
+ if (s === -1) return { body: e, entries: [] };
431
+ let a = n.length;
432
+ for (let o = s + 1; o < n.length; o++)
433
+ if (ys.test(n[o])) {
434
+ a = o;
435
+ break;
436
+ }
437
+ const i = t.parseEntries(n, s + 1, a);
438
+ return { body: [...n.slice(0, s), ...n.slice(a)].join(`
439
+ `).trim(), entries: i };
440
+ }
441
+ const ze = {
442
+ phases: re,
443
+ log: q,
444
+ clarifications: _e,
445
+ notes: Re,
446
+ review: Kn
447
+ };
448
+ function Pe(e) {
449
+ const t = {
450
+ phases: [],
451
+ log: [],
452
+ clarifications: [],
453
+ notes: [],
454
+ review: []
455
+ };
456
+ let n = e;
457
+ for (const s of Object.keys(ze)) {
458
+ const a = Q(n, ze[s]);
459
+ n = a.body, t[s] = a.entries;
460
+ }
461
+ return { body: n, ...t };
462
+ }
463
+ function ws(e) {
464
+ const t = e.split(`
465
+ `), n = [];
466
+ for (let a = 0; a < t.length; a++)
467
+ Je.test(t[a]) && n.push(a);
468
+ const s = [];
469
+ for (let a = 0; a < n.length; a++) {
470
+ const i = n[a], r = a + 1 < n.length ? n[a + 1] : t.length, o = t[i].match(Je)[1], c = t.slice(i + 1, r);
471
+ let d = 0;
472
+ for (; d < c.length && c[d].trim() === ""; ) d++;
473
+ const l = {};
474
+ for (; d < c.length; ) {
475
+ const K = c[d].match(gs);
476
+ if (!K) break;
477
+ l[K[1].toLowerCase()] = K[2].trim(), d++;
478
+ }
479
+ for (; d < c.length && c[d].trim() === ""; ) d++;
480
+ const p = c.slice(d).join(`
481
+ `).trim(), { body: h, phases: g, log: w, clarifications: m, notes: y, review: E } = Pe(p);
482
+ s.push({ title: o, fields: l, body: h, phases: g, log: w, clarifications: m, notes: y, review: E });
483
+ }
484
+ return s;
485
+ }
486
+ function mo(e) {
487
+ const t = [], n = [];
488
+ for (const s of ws(e)) {
489
+ const a = ns.safeParse(s.fields);
490
+ if (!a.success) {
491
+ n.push({
492
+ title: s.title,
493
+ message: a.error.issues.map((r) => r.message).join("; ")
494
+ });
495
+ continue;
496
+ }
497
+ const i = a.data;
498
+ t.push({
499
+ title: s.title,
500
+ status: i.status,
501
+ kind: i.kind,
502
+ id: i.id,
503
+ idea: i.idea,
504
+ agent: i.agent,
505
+ created: i.created,
506
+ updated: i.updated,
507
+ tags: i.tags ? i.tags.split(",").map((r) => r.trim()).filter(Boolean) : [],
508
+ needs: [],
509
+ body: s.body,
510
+ phases: s.phases,
511
+ log: s.log,
512
+ clarifications: s.clarifications,
513
+ notes: s.notes,
514
+ review: s.review
515
+ });
516
+ }
517
+ return { entries: t, warnings: n };
518
+ }
519
+ const bs = /^---\s*\r?\n([\s\S]*?)\r?\n---\s*(?:\r?\n)?/;
520
+ function Ne(e, t) {
521
+ const n = [], s = e.match(bs);
522
+ if (!s)
523
+ return { data: null, body: e.trim(), warnings: n };
524
+ const a = s[1], i = e.slice(s[0].length).trim();
525
+ let r;
526
+ try {
527
+ r = se(a);
528
+ } catch (c) {
529
+ return n.push({
530
+ title: "(frontmatter)",
531
+ message: `Invalid YAML frontmatter: ${c.message}`
532
+ }), { data: null, body: i, warnings: n };
533
+ }
534
+ if (typeof r != "object" || r === null || Array.isArray(r))
535
+ return n.push({
536
+ title: "(frontmatter)",
537
+ message: "YAML frontmatter did not produce an object"
538
+ }), { data: null, body: i, warnings: n };
539
+ const o = t.safeParse(r);
540
+ if (!o.success) {
541
+ const c = r.id;
542
+ return n.push({
543
+ title: c ?? "(frontmatter)",
544
+ message: o.error.issues.map((d) => d.message).join("; ")
545
+ }), { data: null, body: i, warnings: n };
546
+ }
547
+ return { data: o.data, body: i, warnings: n };
548
+ }
549
+ function vs(e, t) {
550
+ const n = Object.fromEntries(
551
+ Object.entries(e).filter(([s]) => !t.has(s))
552
+ );
553
+ return Object.keys(n).length > 0 ? n : void 0;
554
+ }
555
+ function xe(e) {
556
+ const t = [], {
557
+ data: n,
558
+ body: s,
559
+ warnings: a
560
+ } = Ne(e, It);
561
+ if (t.push(...a), !n)
562
+ return { entries: [], warnings: t };
563
+ const {
564
+ body: i,
565
+ phases: r,
566
+ log: o,
567
+ clarifications: c,
568
+ notes: d,
569
+ review: l
570
+ } = Pe(s), { body: p, entries: h } = Q(i, Et), { body: g, entries: w } = Q(p, kt), m = [...w, ...Qn(o, c, d, l)];
571
+ n.kind === "note" && r.length > 0 && t.push({
572
+ title: n.title,
573
+ message: "note entities must not carry a Phases section — kept, but fix the file"
574
+ });
575
+ const y = vs(n, is);
576
+ return { entries: [{
577
+ id: n.id,
578
+ title: n.title,
579
+ type: n.type,
580
+ kind: n.kind,
581
+ status: n.status,
582
+ idea: n.idea,
583
+ agent: n.agent,
584
+ created: n.created,
585
+ updated: n.updated,
586
+ audited: n.audited,
587
+ auditedHash: n["audited-hash"],
588
+ released: n.released,
589
+ tags: n.tags ?? [],
590
+ needs: n.needs ?? [],
591
+ subject: n.subject,
592
+ order: n.order,
593
+ issueSource: n.issueSource,
594
+ body: g,
595
+ phases: r,
596
+ ...h.length > 0 && { fixes: h },
597
+ ...m.length > 0 && { thread: m },
598
+ ...y && { unknownFrontmatter: y }
599
+ }], warnings: t };
600
+ }
601
+ function go(e) {
602
+ const t = [], {
603
+ data: n,
604
+ body: s,
605
+ warnings: a
606
+ } = Ne(e, ss);
607
+ if (t.push(...a), !n)
608
+ return { entries: [], warnings: t };
609
+ const { body: i, phases: r, log: o, clarifications: c, notes: d, review: l } = Pe(s);
610
+ return { entries: [{
611
+ title: n.title,
612
+ status: n.status,
613
+ kind: n.kind,
614
+ id: n.id,
615
+ idea: n.idea,
616
+ agent: n.agent,
617
+ created: n.created,
618
+ updated: n.updated,
619
+ audited: n.audited,
620
+ auditedHash: n["audited-hash"],
621
+ tags: n.tags ?? [],
622
+ needs: [],
623
+ body: i,
624
+ phases: r,
625
+ log: o,
626
+ clarifications: c,
627
+ notes: d,
628
+ review: l
629
+ }], warnings: t };
630
+ }
631
+ function yo(e) {
632
+ const {
633
+ data: t,
634
+ body: n,
635
+ warnings: s
636
+ } = Ne(e, as);
637
+ if (!t)
638
+ return { entries: [], warnings: s };
639
+ const { body: a, entries: i } = Q(n, q);
640
+ return { entries: [{
641
+ id: t.id,
642
+ title: t.title,
643
+ body: a || "",
644
+ ...t.kind && { kind: t.kind },
645
+ ...t.status && { status: t.status },
646
+ ...i.length > 0 && { log: i }
647
+ }], warnings: s };
648
+ }
649
+ const Es = /^(IDEA-\d+):\s*/, ks = /\n---+\n/;
650
+ function wo(e) {
651
+ return e.split(ks).filter(Boolean).map((n) => {
652
+ var c;
653
+ const s = n.match(/^#{1,3}\s+(.+)/m), a = s ? s[1].trim() : ((c = n.trim().split(`
654
+ `)[0]) == null ? void 0 : c.trim()) ?? "Untitled", i = a.match(Es), r = (i == null ? void 0 : i[1]) ?? null, o = r ? a.slice(i[0].length) : a;
655
+ return { id: r, title: o, body: n.trim() };
656
+ });
657
+ }
658
+ const Ke = 40;
659
+ function Ss(e, t = []) {
660
+ const n = [];
661
+ for (const s of e)
662
+ if (!s.archived && (t.length > 0 && s.subject && !t.includes(s.subject) && n.push({
663
+ kind: "orphan-subject",
664
+ section: "plans",
665
+ title: s.title,
666
+ planId: s.id,
667
+ message: `Subject "${s.subject}" isn't in the roadmap vocabulary — "${s.title}"`
668
+ }), s.status !== "done" && s.status !== "dropped")) {
669
+ const a = [];
670
+ s.title.length > Ke && a.push(`${s.title.length} chars over the ${Ke}-char limit`), s.title.includes("—") && a.push("em-dash subtitle"), a.length > 0 && n.push({
671
+ kind: "title-style",
672
+ section: "plans",
673
+ title: s.title,
674
+ planId: s.id,
675
+ message: `Title style — ${a.join(", ")}; the detail belongs in the body: "${s.title}"`
676
+ });
677
+ }
678
+ return n;
679
+ }
680
+ function $s(e) {
681
+ const t = /* @__PURE__ */ new Map(), n = [];
682
+ for (const s of e.split(`
683
+ `)) {
684
+ const a = s.trim();
685
+ if (!a) continue;
686
+ let i;
687
+ try {
688
+ i = JSON.parse(a);
689
+ } catch {
690
+ continue;
691
+ }
692
+ const r = t.get(i.id);
693
+ r ? t.set(i.id, { ...r, ...i }) : (t.set(i.id, i), n.push(i.id));
694
+ }
695
+ return n.map((s) => t.get(s));
696
+ }
697
+ function bo(e) {
698
+ const t = [];
699
+ for (const n of e.split(`
700
+ `)) {
701
+ const s = n.trim();
702
+ if (s)
703
+ try {
704
+ const a = ms.safeParse(JSON.parse(s));
705
+ a.success && t.push(a.data);
706
+ } catch {
707
+ }
708
+ }
709
+ return t;
710
+ }
711
+ const _t = /^-\s+(\d{4}-\d{2}-\d{2}):\s+(.+?)\s+—\s+(.*)$/;
712
+ function vo(e) {
713
+ const t = [];
714
+ for (const n of e.split(`
715
+ `)) {
716
+ const s = n.match(_t);
717
+ s && t.push({ date: s[1], title: s[2].trim(), description: s[3].trim() });
718
+ }
719
+ return t;
720
+ }
721
+ function Eo(e) {
722
+ let t;
723
+ try {
724
+ t = Ee(f(e, "papercamp", "config.json"), "utf-8");
725
+ } catch {
726
+ return {};
727
+ }
728
+ try {
729
+ const n = At.safeParse(JSON.parse(t).desk ?? {});
730
+ return n.success ? n.data : {};
731
+ } catch {
732
+ return {};
733
+ }
734
+ }
735
+ const Is = /^([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/;
736
+ function As(e) {
737
+ return e.length >= 2 && (e.startsWith('"') && e.endsWith('"') || e.startsWith("'") && e.endsWith("'")) ? e.slice(1, -1) : e;
738
+ }
739
+ function We(e) {
740
+ return e === "" || /[\s#"]/.test(e) ? `"${e.replace(/"/g, '\\"')}"` : e;
741
+ }
742
+ function Rt(e) {
743
+ const t = e.trim();
744
+ if (!t || t.startsWith("#")) return null;
745
+ const n = t.match(Is);
746
+ return n ? { key: n[1], value: As(n[2]) } : null;
747
+ }
748
+ function ko(e) {
749
+ const t = [];
750
+ for (const n of e.split(`
751
+ `)) {
752
+ const s = Rt(n);
753
+ s && t.push(s);
754
+ }
755
+ return t;
756
+ }
757
+ function So(e, t) {
758
+ const n = new Map(t.map((i) => [i.key, i.value])), s = e.length > 0 ? e.split(`
759
+ `) : [], a = [];
760
+ for (const i of s) {
761
+ const r = Rt(i);
762
+ if (!r) {
763
+ a.push(i);
764
+ continue;
765
+ }
766
+ n.has(r.key) && (a.push(`${r.key}=${We(n.get(r.key) ?? "")}`), n.delete(r.key));
767
+ }
768
+ for (const [i, r] of n)
769
+ a.push(`${i}=${We(r)}`);
770
+ for (; a.length > 0 && a[a.length - 1] === ""; ) a.pop();
771
+ return a.length > 0 ? `${a.join(`
772
+ `)}
773
+ ` : "";
774
+ }
775
+ const _s = {
776
+ png: "image/png",
777
+ jpg: "image/jpeg",
778
+ jpeg: "image/jpeg",
779
+ gif: "image/gif",
780
+ webp: "image/webp",
781
+ svg: "image/svg+xml",
782
+ html: "text/html",
783
+ htm: "text/html",
784
+ txt: "text/plain",
785
+ md: "text/markdown",
786
+ json: "application/json",
787
+ csv: "text/csv",
788
+ pdf: "application/pdf",
789
+ log: "text/plain"
790
+ }, Rs = "application/octet-stream", Ts = /* @__PURE__ */ new Set(["text/html", "image/svg+xml"]);
791
+ function Ps(e) {
792
+ const t = _n(e).slice(1).toLowerCase();
793
+ return _s[t] ?? Rs;
794
+ }
795
+ function $o(e) {
796
+ return Ts.has(e) ? "sandbox allow-scripts" : void 0;
797
+ }
798
+ function Io(e) {
799
+ return e.length > 0 && e !== "." && e !== ".." && e === mt(e);
800
+ }
801
+ function Ns(e, t) {
802
+ return f(e, t);
803
+ }
804
+ async function xs(e, t) {
805
+ const n = Ns(e, t);
806
+ let s;
807
+ try {
808
+ s = await M(n);
809
+ } catch {
810
+ return [];
811
+ }
812
+ return (await Promise.all(
813
+ s.map(async (i) => {
814
+ const r = await O(f(n, i)).catch(() => {
815
+ });
816
+ return !r || !r.isFile() ? void 0 : {
817
+ name: i,
818
+ size: r.size,
819
+ type: Ps(i),
820
+ modified: r.mtime.toISOString()
821
+ };
822
+ })
823
+ )).filter((i) => i !== void 0);
824
+ }
825
+ const fe = " — ";
826
+ function Cs(e) {
827
+ return e.split(`
828
+ `).map((t) => t.trim()).filter((t) => t.length > 0).map((t) => {
829
+ const n = t.indexOf(fe);
830
+ return n === -1 ? { id: t, title: "" } : {
831
+ id: t.slice(0, n).trim(),
832
+ title: t.slice(n + fe.length).trim()
833
+ };
834
+ });
835
+ }
836
+ function Ao(e) {
837
+ return e.length === 0 ? "" : `${e.map((t) => `${t.id}${fe}${t.title}`).join(`
838
+ `)}
839
+ `;
840
+ }
841
+ function Tt(e) {
842
+ return e.archived === !0 || e.status === "done";
843
+ }
844
+ function Ve(e) {
845
+ return e.filter((t) => t.source !== "manual").every((t) => t.done);
846
+ }
847
+ function pe(e) {
848
+ const t = e.phases.length > 0 && Ve(e.phases), n = Ve(e.fixes ?? []);
849
+ return t && n;
850
+ }
851
+ function oe(e, t, n, s = !1) {
852
+ return e.kind === "note" || e.status === "dropped" ? e.status : e.archived ? "done" : t ? t.state === "merged" ? pe(e) ? "done" : "planned" : t.state === "closed" ? "dropped" : pe(e) ? "review" : "in-progress" : n ? e.status === "review" || e.status === "done" ? e.status : s && e.phases.length > 0 ? pe(e) ? "review" : "in-progress" : e.phases.length > 0 ? "planned" : "idea" : e.status ?? (e.phases.length > 0 ? "planned" : "idea");
853
+ }
854
+ function Os(e, t, n) {
855
+ return e.kind === "note" || e.kind === "board" || e.status === "dropped" || e.archived || t ? !1 : !n;
856
+ }
857
+ function _o(e, t) {
858
+ if (e.kind === "note" || e.archived || (t == null ? void 0 : t.state) !== "merged") return !1;
859
+ const n = oe(e, t, !0);
860
+ return n === "review" || n === "done";
861
+ }
862
+ function Xe(e) {
863
+ return e.length === 0 ? "idea" : e.every((t) => t === "done" || t === "dropped") ? "review" : e.some((t) => t !== "idea" && t !== "planned") ? "in-progress" : "planned";
864
+ }
865
+ function Pt(e, t, n = !1, s = !1) {
866
+ return {
867
+ title: e.title,
868
+ // Non-note entities can't carry the note-only 'open' (schema-enforced).
869
+ status: oe(e, t, n, s),
870
+ statusFallback: Os(e, t, n),
871
+ kind: e.type,
872
+ entityKind: e.kind === "note" ? void 0 : e.kind,
873
+ id: e.id,
874
+ idea: e.idea,
875
+ agent: e.agent,
876
+ created: e.created,
877
+ updated: e.updated,
878
+ audited: e.audited,
879
+ auditedHash: e.auditedHash,
880
+ released: e.released,
881
+ tags: e.tags,
882
+ needs: e.needs,
883
+ subject: e.subject,
884
+ order: e.order,
885
+ issueSource: e.issueSource,
886
+ body: e.body,
887
+ phases: e.phases,
888
+ fixes: e.fixes,
889
+ log: St(e.thread),
890
+ clarifications: Zn(e.thread),
891
+ notes: ts(e.thread),
892
+ review: es(e.thread),
893
+ thread: e.thread,
894
+ archived: e.archived,
895
+ pr: t,
896
+ assets: e.assets
897
+ };
898
+ }
899
+ function Ds(e) {
900
+ return {
901
+ id: e.id,
902
+ title: e.title,
903
+ body: e.body,
904
+ kind: "note",
905
+ status: e.status,
906
+ subject: e.subject,
907
+ order: e.order,
908
+ created: e.created,
909
+ log: St(e.thread),
910
+ assets: e.assets
911
+ };
912
+ }
913
+ async function Ls(e) {
914
+ try {
915
+ return await M(e);
916
+ } catch {
917
+ return [];
918
+ }
919
+ }
920
+ async function Nt(e) {
921
+ try {
922
+ return await v(e, "utf-8");
923
+ } catch {
924
+ return "";
925
+ }
926
+ }
927
+ async function Ms(e) {
928
+ const t = await Nt(f(e, "..", ".local", "run-order.md"));
929
+ return new Map(Cs(t).map((n, s) => [n.id, s + 1]));
930
+ }
931
+ async function ce(e) {
932
+ const t = [], n = [];
933
+ let s = 0;
934
+ const a = await Ms(e);
935
+ for (const o of [e, f(e, "archive")]) {
936
+ const c = o !== e, d = (await Ls(o)).filter((p) => p.endsWith(".md") && p !== "index.md");
937
+ s += d.length;
938
+ const l = await Promise.all(
939
+ d.map(async (p) => {
940
+ const h = await Nt(f(o, p));
941
+ return h ? xe(h) : { warnings: [{ title: p, message: "Could not read entity file" }] };
942
+ })
943
+ );
944
+ for (const p of l)
945
+ "entries" in p && t.push(
946
+ ...p.entries.map((h) => ({
947
+ ...h,
948
+ archived: c,
949
+ storedOrder: h.order,
950
+ order: a.get(h.id) ?? h.order
951
+ }))
952
+ ), n.push(...p.warnings);
953
+ }
954
+ const i = f(e, "..", "assets");
955
+ return { entries: await Promise.all(
956
+ t.map(async (o) => {
957
+ const c = await xs(i, o.id);
958
+ return c.length > 0 ? { ...o, assets: c } : o;
959
+ })
960
+ ), warnings: n, fileCount: s };
961
+ }
962
+ const js = [
963
+ {
964
+ id: "frontmatter-schema",
965
+ category: "metadata",
966
+ severity: "error",
967
+ summary: "Frontmatter fails entityFrontmatterSchema — missing required field, bad enum, malformed date, or a violated note/status refinement."
968
+ },
969
+ {
970
+ id: "filename-id-mismatch",
971
+ category: "metadata",
972
+ severity: "error",
973
+ summary: "Frontmatter id does not match the file name (IDEA-N.md); references break silently."
974
+ },
975
+ {
976
+ id: "duplicate-id",
977
+ category: "metadata",
978
+ severity: "error",
979
+ summary: "Two entity files share one id; whichever loads last wins and the other disappears."
980
+ },
981
+ {
982
+ id: "id-counter-stale",
983
+ category: "metadata",
984
+ severity: "error",
985
+ summary: "An entity id is greater than or equal to config.nextId.idea, so the next mint collides with an existing file."
986
+ },
987
+ {
988
+ id: "corpus-format-too-new",
989
+ category: "metadata",
990
+ severity: "error",
991
+ summary: "config.version is newer than the format this paper-camp understands; reads stay tolerant but writes are refused until it upgrades."
992
+ },
993
+ {
994
+ id: "phases-list-split",
995
+ category: "structural",
996
+ severity: "error",
997
+ summary: "A checkbox line lives outside the Phases section — a mid-file heading split the list and orphaned phases, so the entity parses as fewer phases than it has."
998
+ },
999
+ {
1000
+ id: "note-has-phases",
1001
+ category: "structural",
1002
+ severity: "error",
1003
+ summary: "A kind: note entity carries a Phases section; notes never grow phases."
1004
+ },
1005
+ {
1006
+ id: "duplicate-phases-section",
1007
+ category: "structural",
1008
+ severity: "error",
1009
+ summary: "A file carries more than one Phases heading — a redraft appended instead of replacing the existing list in place."
1010
+ },
1011
+ {
1012
+ id: "no-agent-phase",
1013
+ category: "structural",
1014
+ severity: "warning",
1015
+ summary: "A planned/in-progress/review entity has no phase an agent can run — every phase is `[manual]` or the Phases section is empty, so the idea can never move on its own."
1016
+ },
1017
+ {
1018
+ id: "archive-placement",
1019
+ category: "structural",
1020
+ severity: "warning",
1021
+ summary: "File location contradicts status — a done/dropped entity still under ideas/, or an active entity already under ideas/archive/."
1022
+ },
1023
+ {
1024
+ id: "dangling-link",
1025
+ category: "structural",
1026
+ severity: "warning",
1027
+ summary: "A [[IDEA-N]] wikilink targets an id that no entity in the corpus defines."
1028
+ },
1029
+ {
1030
+ id: "missing-permissions-allow",
1031
+ category: "tooling",
1032
+ severity: "warning",
1033
+ summary: ".claude/settings.json has no permissions.allow list, so a headless run denies every edit and shell command outside the defaults — run `paper-camp init --settings` to add it."
1034
+ },
1035
+ {
1036
+ id: "no-default-agents",
1037
+ category: "tooling",
1038
+ severity: "warning",
1039
+ summary: "config.json has no defaultAgents, so every run uses the built-in agent and model — choose them in Settings → General so a project never runs on a fallback it did not pick."
1040
+ },
1041
+ {
1042
+ id: "missing-mcp-entry",
1043
+ category: "tooling",
1044
+ severity: "warning",
1045
+ summary: ".mcp.json or opencode.json has no paper-camp entry, so runs and sessions in this project never see the paper-camp MCP server — run `paper-camp init --settings` to add it."
1046
+ },
1047
+ {
1048
+ id: "draft-pr-template-outdated",
1049
+ category: "tooling",
1050
+ severity: "warning",
1051
+ summary: "desk.ci.draftPr is on but .github/workflows/draft-pr.yml or .github/pull_request_template.md is missing or carries an older paper-camp template version — run `paper-camp doctor --fix` to rewrite it."
1052
+ },
1053
+ {
1054
+ id: "draft-pr-template-unmanaged",
1055
+ category: "tooling",
1056
+ severity: "warning",
1057
+ summary: "desk.ci.draftPr is on but the workflow or PR template file has no paper-camp template header, so it looks hand-written — paper-camp will not overwrite it."
1058
+ },
1059
+ {
1060
+ id: "local-state-tracked-outside-plan",
1061
+ category: "local-state",
1062
+ severity: "warning",
1063
+ summary: "A file under papercamp/ is tracked by git but is not part of the committed plan (ideas/, config.json, suggestions.md, chat.md, about.md, assets/) — run `paper-camp doctor --fix` to move it to papercamp/.local/ or the account config dir and untrack it."
1064
+ },
1065
+ {
1066
+ id: "local-state-missing-local-gitignore",
1067
+ category: "local-state",
1068
+ severity: "warning",
1069
+ summary: "papercamp/.local/.gitignore is missing, so nothing under papercamp/.local/ is protected from being committed — run `paper-camp doctor --fix` to create it."
1070
+ },
1071
+ {
1072
+ id: "local-state-missing-gitattributes-block",
1073
+ category: "local-state",
1074
+ severity: "warning",
1075
+ summary: ".gitattributes has no paper-camp managed block, so two clones appending to papercamp/chat.md will conflict on pull instead of merging — run `paper-camp doctor --fix` to add it."
1076
+ },
1077
+ {
1078
+ id: "asset-orphan-folder",
1079
+ category: "assets",
1080
+ severity: "warning",
1081
+ summary: "A folder under papercamp/assets/ has an id that matches no entity in the corpus — dead files left behind by a dropped or renamed idea."
1082
+ },
1083
+ {
1084
+ id: "asset-oversized",
1085
+ category: "assets",
1086
+ severity: "warning",
1087
+ summary: "A file under papercamp/assets/<ID>/ is over the configured per-file limit (config.json assets.maxFileSizeMb, default 5MB)."
1088
+ },
1089
+ {
1090
+ id: "asset-dangling-link",
1091
+ category: "assets",
1092
+ severity: "warning",
1093
+ summary: "An idea links to `assets/<name>` or `../assets/<ID>/<name>` but no such file exists under papercamp/assets/."
1094
+ }
1095
+ ], Fs = Object.fromEntries(
1096
+ js.map((e) => [e.id, e])
1097
+ );
1098
+ function Z(e) {
1099
+ return Fs[e.rule].severity;
1100
+ }
1101
+ const Qe = { error: 0, warning: 1 };
1102
+ function xt(e) {
1103
+ return [...e].sort(
1104
+ (t, n) => t.file.localeCompare(n.file) || t.line - n.line || Qe[Z(t)] - Qe[Z(n)] || t.rule.localeCompare(n.rule)
1105
+ );
1106
+ }
1107
+ function Ro(e) {
1108
+ const t = xt(e).map((n) => ({
1109
+ ...n,
1110
+ severity: Z(n)
1111
+ }));
1112
+ return {
1113
+ findings: t,
1114
+ errorCount: t.filter((n) => n.severity === "error").length,
1115
+ warningCount: t.filter((n) => n.severity === "warning").length
1116
+ };
1117
+ }
1118
+ function To(e) {
1119
+ const t = xt(e);
1120
+ let n = 0, s = 0;
1121
+ const a = t.map((r) => {
1122
+ const o = Z(r);
1123
+ return o === "error" ? n++ : s++, `${r.file}:${r.line} ${o.padEnd(7)} ${r.rule} ${r.message}`;
1124
+ }), i = t.length === 0 ? "paper-camp doctor: no issues found." : `paper-camp doctor: ${n} error(s), ${s} warning(s).`;
1125
+ return {
1126
+ text: [...a, ...a.length > 0 ? [""] : [], i].join(`
1127
+ `),
1128
+ errorCount: n,
1129
+ warningCount: s
1130
+ };
1131
+ }
1132
+ const T = 1;
1133
+ class Hs extends Error {
1134
+ constructor(t) {
1135
+ super(
1136
+ `This corpus is in format version ${t}, newer than the format version ${T} this paper-camp understands. Writes are refused so a field this version doesn’t recognise is never dropped — upgrade paper-camp to edit this corpus.`
1137
+ ), this.name = "CorpusTooNewError";
1138
+ }
1139
+ }
1140
+ async function Ct(e) {
1141
+ let t;
1142
+ try {
1143
+ t = await v(e, "utf-8");
1144
+ } catch {
1145
+ return null;
1146
+ }
1147
+ try {
1148
+ const n = JSON.parse(t);
1149
+ return n && typeof n == "object" && !Array.isArray(n) ? n : null;
1150
+ } catch {
1151
+ return null;
1152
+ }
1153
+ }
1154
+ function Ot(e) {
1155
+ const t = e == null ? void 0 : e.version;
1156
+ return typeof t == "number" ? t : null;
1157
+ }
1158
+ async function Dt(e) {
1159
+ const t = Ot(await Ct(e));
1160
+ if (t !== null && t > T)
1161
+ throw new Hs(t);
1162
+ }
1163
+ async function Po(e) {
1164
+ const t = await Ct(e);
1165
+ if (!t) return null;
1166
+ const n = Ot(t);
1167
+ return n !== null && n >= T ? null : (t.version = T, await b(e, `${JSON.stringify(t, null, 2)}
1168
+ `, "utf-8"), { from: n, to: T });
1169
+ }
1170
+ const Bs = /^[0-9a-f]{4,40}$/i, Gs = /^Merge pull request #\d+ from \S+\/([a-z]+-\d+)-/i;
1171
+ function k(e, t) {
1172
+ return new Promise((n) => {
1173
+ var i;
1174
+ const s = S("git", t, { cwd: e, stdio: ["ignore", "pipe", "ignore"] });
1175
+ let a = "";
1176
+ (i = s.stdout) == null || i.on("data", (r) => {
1177
+ a += r.toString();
1178
+ }), s.on("close", () => n(a)), s.on("error", () => n(""));
1179
+ });
1180
+ }
1181
+ async function Lt(e) {
1182
+ var n;
1183
+ return ((n = (await k(e, ["symbolic-ref", "refs/remotes/origin/HEAD"])).trim().match(/refs\/remotes\/origin\/(.+)/)) == null ? void 0 : n[1]) ?? "main";
1184
+ }
1185
+ function he(e) {
1186
+ const t = e.match(/^Refs:\s*([A-Za-z]+-\d+)\s*$/m);
1187
+ if (t) return t[1].toUpperCase();
1188
+ const n = e.split(`
1189
+ `, 1)[0], s = n.match(/\(([A-Za-z]+-\d+)\)/);
1190
+ if (s) return s[1].toUpperCase();
1191
+ const a = n.match(Gs);
1192
+ return a ? a[1].toUpperCase() : null;
1193
+ }
1194
+ function Us(e) {
1195
+ return e.length > 0 && e.every((t) => t.startsWith("papercamp/"));
1196
+ }
1197
+ async function Ys(e, t) {
1198
+ const n = await k(e, ["log", "--format=%x02%B%x00", "--name-only", t]), s = /* @__PURE__ */ new Set();
1199
+ for (const a of n.split("")) {
1200
+ if (!a) continue;
1201
+ const [i, r = ""] = a.split("\0"), o = r.split(`
1202
+ `).map((d) => d.trim()).filter(Boolean);
1203
+ if (Us(o)) continue;
1204
+ const c = he(i.trim());
1205
+ c && s.add(c);
1206
+ }
1207
+ return s;
1208
+ }
1209
+ const W = /* @__PURE__ */ new Map();
1210
+ async function Mt(e) {
1211
+ const t = await Lt(e), n = (await k(e, ["rev-parse", t])).trim(), s = W.get(e);
1212
+ if (s && s.sha === n) return s.promise.then((i) => ({ ids: i, sha: n }));
1213
+ const a = Ys(e, t).catch((i) => {
1214
+ var r;
1215
+ throw ((r = W.get(e)) == null ? void 0 : r.sha) === n && W.delete(e), i;
1216
+ });
1217
+ return W.set(e, { sha: n, promise: a }), a.then((i) => ({ ids: i, sha: n }));
1218
+ }
1219
+ const jt = "", F = "", qs = `${jt}%H${F}%s${F}%cI${F}%D${F}%(trailers:key=Refs,valueonly)`, Js = /^([a-z]+(?:\([^)]*\))?):\s*(.+)$/i;
1220
+ function Ce(e) {
1221
+ const t = e.match(Js);
1222
+ return t ? { prefix: t[1], subject: t[2] } : { prefix: "", subject: e };
1223
+ }
1224
+ function zs(e, t) {
1225
+ const n = [];
1226
+ let s = !1;
1227
+ for (const a of e.split(",")) {
1228
+ const i = a.trim().replace(/^HEAD -> /, "");
1229
+ i && (i.startsWith("tag: ") ? n.push(i.slice(5)) : t && i === t && (s = !0));
1230
+ }
1231
+ return { tags: n, isUpstreamHead: s };
1232
+ }
1233
+ function Ks(e) {
1234
+ const t = e.trim();
1235
+ return /^[A-Za-z]+-\d+$/.test(t) ? t.toUpperCase() : null;
1236
+ }
1237
+ function Ws(e, t, n) {
1238
+ return e.split(jt).map((s) => s.trim()).filter(Boolean).map((s) => {
1239
+ const [a, i = "", r = "", o = "", c = ""] = s.split(F), { prefix: d, subject: l } = Ce(i), { tags: p, isUpstreamHead: h } = zs(o, t);
1240
+ return {
1241
+ hash: a,
1242
+ subject: l,
1243
+ prefix: d,
1244
+ date: r,
1245
+ tags: p,
1246
+ isUpstreamHead: h,
1247
+ pushed: t !== null && !n.has(a),
1248
+ ideaId: Ks(c)
1249
+ };
1250
+ });
1251
+ }
1252
+ async function Vs(e) {
1253
+ return (await k(e, ["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"])).trim() || null;
1254
+ }
1255
+ const Xs = [
1256
+ "%(refname:short)",
1257
+ "%(HEAD)",
1258
+ "%(upstream:short)",
1259
+ "%(upstream:track,nobracket)",
1260
+ "%(committerdate:iso-strict)"
1261
+ ].join("%09"), Qs = /(?:^|\/)(idea|ticket)-(\d+)(?:-|$)/i;
1262
+ function Zs(e) {
1263
+ var d, l;
1264
+ const [t, n, s, a, i] = e.split(" ");
1265
+ if (!t) return null;
1266
+ const r = Number(((d = a == null ? void 0 : a.match(/ahead (\d+)/)) == null ? void 0 : d[1]) ?? 0), o = Number(((l = a == null ? void 0 : a.match(/behind (\d+)/)) == null ? void 0 : l[1]) ?? 0), c = t.match(Qs);
1267
+ return {
1268
+ name: t,
1269
+ current: n === "*",
1270
+ upstream: s || null,
1271
+ ahead: r,
1272
+ behind: o,
1273
+ gone: a === "gone",
1274
+ date: i ?? "",
1275
+ ideaId: c ? `${c[1].toUpperCase()}-${c[2]}` : null
1276
+ };
1277
+ }
1278
+ async function No(e) {
1279
+ return (await k(e, [
1280
+ "for-each-ref",
1281
+ "refs/heads",
1282
+ "--sort=-committerdate",
1283
+ `--format=${Xs}`
1284
+ ])).split(`
1285
+ `).map(Zs).filter((n) => n !== null);
1286
+ }
1287
+ async function xo(e, t, n = 30) {
1288
+ const s = await Vs(e), a = await k(e, [
1289
+ "log",
1290
+ "--first-parent",
1291
+ `--max-count=${n + 1}`,
1292
+ `--skip=${t}`,
1293
+ `--format=${qs}`
1294
+ ]), i = /* @__PURE__ */ new Set();
1295
+ if (s) {
1296
+ const c = await k(e, ["rev-list", `${s}..HEAD`]);
1297
+ for (const d of c.split(`
1298
+ `)) {
1299
+ const l = d.trim();
1300
+ l && i.add(l);
1301
+ }
1302
+ }
1303
+ const r = Ws(a, s, i), o = r.length > n;
1304
+ return { commits: r.slice(0, n), upstream: s, hasMore: o };
1305
+ }
1306
+ const Ft = /* @__PURE__ */ new Set([
1307
+ "ideas",
1308
+ "config.json",
1309
+ "suggestions.md",
1310
+ "chat.md",
1311
+ "about.md",
1312
+ "assets"
1313
+ ]), D = (e, t) => {
1314
+ if (!Ft.has(t))
1315
+ throw new Error(`planFile: "${t}" is not part of the plan — use localFile or machineFile`);
1316
+ return f(e, "papercamp", t);
1317
+ }, Ht = (e, t) => f(e, "papercamp", ".local", t), ea = "# paper-camp:start", Co = "# paper-camp:end", Oo = "papercamp/chat.md merge=union", A = /* @__PURE__ */ new Map(), ta = 5 * 60 * 1e3, Oe = (e) => Ht(e, "pr-map.json");
1318
+ async function na(e, t, n) {
1319
+ const s = Oe(e);
1320
+ try {
1321
+ const a = { fetchedAt: n, prs: Object.fromEntries(t) };
1322
+ await $(C(s), { recursive: !0 }), await b(s, `${JSON.stringify(a, null, 2)}
1323
+ `, "utf-8");
1324
+ } catch (a) {
1325
+ console.error(`papercamp: could not persist PR map for ${e}:`, a);
1326
+ }
1327
+ }
1328
+ async function Bt(e) {
1329
+ try {
1330
+ const t = await v(Oe(e), "utf-8"), n = JSON.parse(t);
1331
+ return { prs: new Map(Object.entries(n.prs)), fetchedAt: n.fetchedAt };
1332
+ } catch {
1333
+ return;
1334
+ }
1335
+ }
1336
+ const sa = {
1337
+ APPROVED: "approved",
1338
+ CHANGES_REQUESTED: "changes-requested",
1339
+ REVIEW_REQUIRED: "review-required"
1340
+ };
1341
+ function aa(e) {
1342
+ const t = e.state === "MERGED" ? "merged" : e.state === "CLOSED" ? "closed" : e.isDraft ? "draft" : "open", n = sa[e.reviewDecision];
1343
+ return {
1344
+ number: e.number,
1345
+ url: e.url,
1346
+ state: t,
1347
+ ...n && { reviewDecision: n },
1348
+ ...e.headRefOid && { headSha: e.headRefOid },
1349
+ ...e.headRefName && { headBranch: e.headRefName }
1350
+ };
1351
+ }
1352
+ const Ze = { merged: 4, open: 3, draft: 2, closed: 1 };
1353
+ function Gt(e, t) {
1354
+ const n = e == null ? void 0 : e.match(/\*\*Plan:\*\*\s*`?([A-Za-z]+-\d+)`?/);
1355
+ if (n) return n[1].toUpperCase();
1356
+ const s = t == null ? void 0 : t.match(/^[a-z]+\/([a-z]+-\d+)-/);
1357
+ return s ? s[1].toUpperCase() : null;
1358
+ }
1359
+ const ia = `
1360
+ query($owner: String!, $repo: String!, $number: Int!) {
1361
+ repository(owner: $owner, name: $repo) {
1362
+ pullRequest(number: $number) {
1363
+ reviewThreads(first: 100) { nodes { isResolved } }
1364
+ commits(last: 1) { nodes { commit { committedDate } } }
1365
+ comments(last: 1) { nodes { createdAt } }
1366
+ reviews(last: 20) { nodes { createdAt body } }
1367
+ }
1368
+ }
1369
+ }`;
1370
+ function ra(e, t) {
1371
+ return `Paper Scout · ${e} · ${t.slice(0, 7)}`;
1372
+ }
1373
+ function J(e) {
1374
+ const t = e.match(/github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)/);
1375
+ if (!t) return null;
1376
+ const [, n, s, a] = t;
1377
+ return { owner: n, repo: s, number: a };
1378
+ }
1379
+ const oa = 15e3;
1380
+ function Ut(e, t) {
1381
+ return new Promise((n) => {
1382
+ var c, d;
1383
+ const s = S("gh", t, { cwd: e, stdio: ["ignore", "pipe", "pipe"] });
1384
+ let a = !1;
1385
+ const i = (l) => {
1386
+ a || (a = !0, clearTimeout(r), l());
1387
+ }, r = setTimeout(() => {
1388
+ s.kill(), i(() => n(void 0));
1389
+ }, oa);
1390
+ let o = "";
1391
+ (c = s.stdout) == null || c.on("data", (l) => {
1392
+ o += l.toString();
1393
+ }), (d = s.stderr) == null || d.on("data", () => {
1394
+ }), s.on("close", (l) => {
1395
+ i(() => {
1396
+ if (l !== 0) {
1397
+ n(void 0);
1398
+ return;
1399
+ }
1400
+ try {
1401
+ n(JSON.parse(o));
1402
+ } catch {
1403
+ n(void 0);
1404
+ }
1405
+ });
1406
+ }), s.on("error", () => i(() => n(void 0)));
1407
+ });
1408
+ }
1409
+ function Yt(e, t, n, s, a) {
1410
+ return Jt(e, [
1411
+ "-f",
1412
+ `query=${t}`,
1413
+ "-f",
1414
+ `owner=${n}`,
1415
+ "-f",
1416
+ `repo=${s}`,
1417
+ "-F",
1418
+ `number=${a}`
1419
+ ]);
1420
+ }
1421
+ function qt(e, t, n) {
1422
+ const s = ["-f", `query=${t}`];
1423
+ for (const [a, i] of Object.entries(n))
1424
+ s.push("-f", `${a}=${i}`);
1425
+ return Jt(e, s);
1426
+ }
1427
+ function Jt(e, t) {
1428
+ return Ut(e, ["api", "graphql", ...t]);
1429
+ }
1430
+ async function ca(e, t) {
1431
+ var l, p, h, g, w;
1432
+ const n = J(t);
1433
+ if (!n) return;
1434
+ const s = await Yt(e, ia, n.owner, n.repo, n.number), a = (p = (l = s == null ? void 0 : s.data) == null ? void 0 : l.repository) == null ? void 0 : p.pullRequest;
1435
+ if (!a) return;
1436
+ const i = a.reviewThreads.nodes.filter((m) => !m.isResolved).length, r = (h = a.commits.nodes[0]) == null ? void 0 : h.commit.committedDate, o = [(g = a.comments.nodes[0]) == null ? void 0 : g.createdAt, (w = a.reviews.nodes.at(-1)) == null ? void 0 : w.createdAt].filter((m) => !!m).sort().at(-1), c = !!(r && o && o > r), d = a.reviews.nodes.map((m) => m.body);
1437
+ return { unresolvedThreadCount: i, hasNewCommentsSincePush: c, reviewBodies: d };
1438
+ }
1439
+ const da = `
1440
+ query($owner: String!, $repo: String!, $number: Int!) {
1441
+ repository(owner: $owner, name: $repo) {
1442
+ pullRequest(number: $number) {
1443
+ reviewThreads(first: 100) {
1444
+ nodes {
1445
+ id
1446
+ isResolved
1447
+ comments(first: 1) { nodes { path line body author { login } } }
1448
+ }
1449
+ }
1450
+ }
1451
+ }
1452
+ }`;
1453
+ async function Do(e, t) {
1454
+ var i, r, o;
1455
+ const n = J(t);
1456
+ if (!n) return [];
1457
+ const s = await Yt(e, da, n.owner, n.repo, n.number);
1458
+ return (((o = (r = (i = s == null ? void 0 : s.data) == null ? void 0 : i.repository) == null ? void 0 : r.pullRequest) == null ? void 0 : o.reviewThreads.nodes) ?? []).filter((c) => !c.isResolved).map((c) => ({ id: c.id, comment: c.comments.nodes[0] })).filter((c) => !!c.comment).map(({ id: c, comment: d }) => {
1459
+ var l;
1460
+ return {
1461
+ id: c,
1462
+ ...d.path ? { path: d.path } : {},
1463
+ ...d.line != null ? { line: d.line } : {},
1464
+ ...(l = d.author) != null && l.login ? { author: d.author.login } : {},
1465
+ body: d.body
1466
+ };
1467
+ });
1468
+ }
1469
+ const ua = `
1470
+ mutation($threadId: ID!) {
1471
+ resolveReviewThread(input: { threadId: $threadId }) {
1472
+ thread { id isResolved }
1473
+ }
1474
+ }`, la = `
1475
+ mutation($threadId: ID!, $body: String!) {
1476
+ addPullRequestReviewThreadReply(input: { pullRequestReviewThreadId: $threadId, body: $body }) {
1477
+ comment { id }
1478
+ }
1479
+ }`;
1480
+ async function Lo(e, t) {
1481
+ var s, a, i;
1482
+ const n = await qt(e, ua, { threadId: t });
1483
+ return !!((i = (a = (s = n == null ? void 0 : n.data) == null ? void 0 : s.resolveReviewThread) == null ? void 0 : a.thread) != null && i.isResolved);
1484
+ }
1485
+ async function Mo(e, t, n) {
1486
+ var a, i, r;
1487
+ const s = await qt(e, la, { threadId: t, body: n });
1488
+ return !!((r = (i = (a = s == null ? void 0 : s.data) == null ? void 0 : a.addPullRequestReviewThreadReply) == null ? void 0 : i.comment) != null && r.id);
1489
+ }
1490
+ function zt(e, t, n) {
1491
+ return new Promise((s) => {
1492
+ var r, o, c;
1493
+ const a = S("gh", ["api", t, "-X", "POST", "--input", "-"], {
1494
+ cwd: e,
1495
+ stdio: ["pipe", "ignore", "pipe"]
1496
+ });
1497
+ let i = "";
1498
+ (r = a.stderr) == null || r.on("data", (d) => {
1499
+ i += d;
1500
+ }), a.on(
1501
+ "close",
1502
+ (d) => s(d === 0 ? { delivered: !0 } : { delivered: !1, body: i.trim() })
1503
+ ), a.on("error", (d) => s({ delivered: !1, body: d.message })), (o = a.stdin) == null || o.on("error", () => {
1504
+ }), (c = a.stdin) == null || c.end(JSON.stringify(n));
1505
+ });
1506
+ }
1507
+ async function jo(e, t, n) {
1508
+ const s = J(t);
1509
+ return s ? zt(
1510
+ e,
1511
+ `repos/${s.owner}/${s.repo}/pulls/${s.number}/reviews`,
1512
+ n
1513
+ ) : { delivered: !1 };
1514
+ }
1515
+ async function Fo(e, t, n) {
1516
+ const s = J(t);
1517
+ return s ? zt(e, `repos/${s.owner}/${s.repo}/dispatches`, {
1518
+ event_type: "paper-camp-review",
1519
+ client_payload: { review: { number: Number(s.number), ...n } }
1520
+ }) : { delivered: !1 };
1521
+ }
1522
+ async function pa(e, t) {
1523
+ const n = [...t.entries()].filter(
1524
+ ([, a]) => a.state === "open" || a.state === "draft"
1525
+ ), s = await Promise.all(n.map(([, a]) => ca(e, a.url)));
1526
+ n.forEach(([a, i], r) => {
1527
+ const o = s[r];
1528
+ if (!o) return;
1529
+ const { reviewBodies: c, ...d } = o, l = !!i.headSha && c.some((p) => p.includes(ra(a, i.headSha)));
1530
+ t.set(a, { ...i, ...d, ...l && { scoutReviewObserved: l } });
1531
+ });
1532
+ }
1533
+ async function fa(e) {
1534
+ const t = await Ut(e, [
1535
+ "pr",
1536
+ "list",
1537
+ "--state",
1538
+ "all",
1539
+ "--limit",
1540
+ "2000",
1541
+ "--json",
1542
+ "number,url,state,isDraft,headRefName,headRefOid,body,reviewDecision"
1543
+ ]);
1544
+ if (!t) return;
1545
+ const n = /* @__PURE__ */ new Map();
1546
+ for (const s of t) {
1547
+ const a = Gt(s.body, s.headRefName);
1548
+ if (!a) continue;
1549
+ const i = aa(s), r = n.get(a);
1550
+ (!r || Ze[i.state] > Ze[r.state]) && n.set(a, i);
1551
+ }
1552
+ return await pa(e, n), n;
1553
+ }
1554
+ async function Kt(e, t = ta) {
1555
+ let n = A.get(e);
1556
+ if (!n) {
1557
+ const a = await Bt(e);
1558
+ a && (A.set(e, a), n = a);
1559
+ }
1560
+ if (n && Date.now() - n.fetchedAt < t) return n.prs;
1561
+ if (!Number.isFinite(t)) return n == null ? void 0 : n.prs;
1562
+ const s = await fa(e);
1563
+ if (s !== void 0) {
1564
+ const a = Date.now();
1565
+ return A.set(e, { prs: s, fetchedAt: a }), await na(e, s, a), s;
1566
+ }
1567
+ return n == null ? void 0 : n.prs;
1568
+ }
1569
+ function Ho(e) {
1570
+ var t;
1571
+ return (t = A.get(e)) == null ? void 0 : t.prs;
1572
+ }
1573
+ function Bo(e, t) {
1574
+ const n = (a) => `${a.number}:${a.headSha ?? "unknown"}`, s = [];
1575
+ for (const [a, i] of t) {
1576
+ if (i.reviewDecision !== "changes-requested") continue;
1577
+ const r = e == null ? void 0 : e.get(a);
1578
+ ((r == null ? void 0 : r.reviewDecision) === "changes-requested" ? n(r) : void 0) !== n(i) && s.push({ entityId: a, pr: i });
1579
+ }
1580
+ return s;
1581
+ }
1582
+ async function ha(e) {
1583
+ let t = A.get(e);
1584
+ if (!t) {
1585
+ const n = await Bt(e);
1586
+ n && (A.set(e, n), t = n);
1587
+ }
1588
+ return (t == null ? void 0 : t.fetchedAt) ?? null;
1589
+ }
1590
+ async function Go(e) {
1591
+ if (e === void 0) {
1592
+ A.clear();
1593
+ return;
1594
+ }
1595
+ const t = A.get(e);
1596
+ t && A.set(e, { ...t, fetchedAt: 0 });
1597
+ const n = Oe(e);
1598
+ try {
1599
+ const s = await v(n, "utf-8"), a = JSON.parse(s);
1600
+ a.fetchedAt = 0, await b(n, `${JSON.stringify(a, null, 2)}
1601
+ `, "utf-8");
1602
+ } catch {
1603
+ }
1604
+ }
1605
+ function De(e) {
1606
+ return e.found && !e.done;
1607
+ }
1608
+ function Uo(e) {
1609
+ return (e ?? []).some(De);
1610
+ }
1611
+ function ma(e) {
1612
+ const t = e.indexOf("/");
1613
+ return t === -1 ? { raw: e, id: e } : { raw: e, projectSlug: e.slice(0, t), id: e.slice(t + 1) };
1614
+ }
1615
+ async function Le(e, t, n, s, a = /* @__PURE__ */ new Map()) {
1616
+ if (t.length === 0) return [];
1617
+ const i = s ?? await ae(ie());
1618
+ async function r(c) {
1619
+ const d = a.get(c);
1620
+ if (d) return d;
1621
+ const { entries: l } = await n(c), p = new Map(l.map((h) => [h.id, h]));
1622
+ return a.set(c, p), p;
1623
+ }
1624
+ const o = [];
1625
+ for (const c of t) {
1626
+ const d = ma(c), l = d.projectSlug ? i.projects.find((h) => h.slug === d.projectSlug) : void 0;
1627
+ if (d.projectSlug && !l) {
1628
+ o.push({ raw: c, id: d.id, projectSlug: d.projectSlug, found: !1 });
1629
+ continue;
1630
+ }
1631
+ const p = (await r((l == null ? void 0 : l.path) ?? e)).get(d.id);
1632
+ if (!p) {
1633
+ o.push({ raw: c, id: d.id, projectSlug: d.projectSlug, found: !1 });
1634
+ continue;
1635
+ }
1636
+ o.push({
1637
+ raw: c,
1638
+ id: d.id,
1639
+ projectSlug: d.projectSlug,
1640
+ found: !0,
1641
+ done: Tt(p),
1642
+ title: p.title,
1643
+ projectName: l == null ? void 0 : l.name
1644
+ });
1645
+ }
1646
+ return o;
1647
+ }
1648
+ async function ga(e, t, n, s = /* @__PURE__ */ new Map()) {
1649
+ if (t.every((i) => i.needs.length === 0)) return t;
1650
+ const a = await ae(ie());
1651
+ return Promise.all(
1652
+ t.map(async (i) => i.needs.length === 0 ? i : {
1653
+ ...i,
1654
+ resolvedNeeds: await Le(
1655
+ e,
1656
+ i.needs,
1657
+ n,
1658
+ a,
1659
+ s
1660
+ )
1661
+ })
1662
+ );
1663
+ }
1664
+ async function Yo(e, t, n, s = /* @__PURE__ */ new Map()) {
1665
+ const a = t.filter((o) => o.needs.length > 0);
1666
+ if (a.length === 0) return /* @__PURE__ */ new Set();
1667
+ const i = await ae(ie()), r = /* @__PURE__ */ new Set();
1668
+ for (const o of a)
1669
+ (await Le(
1670
+ e,
1671
+ o.needs,
1672
+ n,
1673
+ i,
1674
+ s
1675
+ )).some(De) && r.add(o.id);
1676
+ return r;
1677
+ }
1678
+ async function qo(e, t, n) {
1679
+ if (t.length === 0) return null;
1680
+ const s = (await Le(e, t, n)).find(De);
1681
+ return s ? `waits for ${s.projectName ? `${s.projectName} ` : ""}${s.id}` : null;
1682
+ }
1683
+ function de(e, t) {
1684
+ for (const { entries: n, section: s } of t)
1685
+ n && n.length > 0 && e.push(s.formatLines(n).join(`
1686
+ `));
1687
+ }
1688
+ function ue() {
1689
+ return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
1690
+ }
1691
+ function Jo() {
1692
+ return (/* @__PURE__ */ new Date()).toISOString().slice(0, 16);
1693
+ }
1694
+ function zo(e, t = "log", n, s, a = ue()) {
1695
+ return {
1696
+ kind: t,
1697
+ date: a,
1698
+ text: e,
1699
+ from: "agent",
1700
+ ...t === "question" ? { state: "open" } : {},
1701
+ ...n ? { entityId: n } : {},
1702
+ ...s != null && s.options && s.options.length > 0 ? { options: s.options } : {},
1703
+ ...s != null && s.context ? { context: s.context } : {}
1704
+ };
1705
+ }
1706
+ function Ko(e) {
1707
+ const t = [`## ${e.title}`, "", `**Status:** ${e.status}`];
1708
+ e.kind && t.push(`**Kind:** ${e.kind}`), e.id && t.push(`**Id:** ${e.id}`), e.idea && t.push(`**Idea:** ${e.idea}`), e.agent && t.push(`**Agent:** ${e.agent}`), t.push(`**Created:** ${e.created}`), e.updated && t.push(`**Updated:** ${e.updated}`), e.tags && e.tags.length > 0 && t.push(`**Tags:** ${e.tags.join(", ")}`);
1709
+ const n = [t.join(`
1710
+ `)];
1711
+ return e.body && n.push(e.body), de(n, [
1712
+ { entries: e.clarifications, section: _e },
1713
+ { entries: e.phases, section: re },
1714
+ { entries: e.log, section: q },
1715
+ { entries: e.notes, section: Re }
1716
+ ]), n.join(`
1717
+
1718
+ `).trimEnd();
1719
+ }
1720
+ function Me(e) {
1721
+ return `---
1722
+ ${Rn(e).trimEnd()}
1723
+ ---`;
1724
+ }
1725
+ function Wo(e) {
1726
+ const t = {
1727
+ id: e.id,
1728
+ title: e.title,
1729
+ kind: e.kind,
1730
+ status: e.status,
1731
+ created: e.created
1732
+ };
1733
+ e.idea && (t.idea = e.idea), e.agent && (t.agent = e.agent), e.updated && (t.updated = e.updated), e.audited && (t.audited = e.audited), e.auditedHash && (t["audited-hash"] = e.auditedHash), e.tags && e.tags.length > 0 && (t.tags = e.tags);
1734
+ const n = [Me(t)];
1735
+ return e.body && n.push(e.body), de(n, [
1736
+ { entries: e.clarifications, section: _e },
1737
+ { entries: e.phases, section: re },
1738
+ { entries: e.log, section: q },
1739
+ { entries: e.notes, section: Re }
1740
+ ]), n.join(`
1741
+
1742
+ `).trimEnd();
1743
+ }
1744
+ function je(e) {
1745
+ const t = {
1746
+ id: e.id,
1747
+ title: e.title
1748
+ };
1749
+ e.type && (t.type = e.type), e.kind && (t.kind = e.kind), e.status && (t.status = e.status), e.idea && (t.idea = e.idea), t.created = e.created, e.agent && (t.agent = e.agent), e.updated && (t.updated = e.updated), e.audited && (t.audited = e.audited), e.auditedHash && (t["audited-hash"] = e.auditedHash), e.released && (t.released = e.released), e.tags && e.tags.length > 0 && (t.tags = e.tags), e.needs && e.needs.length > 0 && (t.needs = e.needs), e.subject && (t.subject = e.subject), e.order !== void 0 && (t.order = e.order), e.issueSource && (t.issueSource = e.issueSource);
1750
+ for (const s of Object.keys(e.unknownFrontmatter ?? {}).sort())
1751
+ t[s] = e.unknownFrontmatter[s];
1752
+ const n = [Me(t)];
1753
+ return e.body && n.push(e.body), de(n, [
1754
+ { entries: e.phases, section: re },
1755
+ { entries: e.fixes, section: Et },
1756
+ { entries: e.thread, section: kt }
1757
+ ]), n.join(`
1758
+
1759
+ `).trimEnd();
1760
+ }
1761
+ function Vo(e) {
1762
+ const t = {
1763
+ id: e.id,
1764
+ title: e.title
1765
+ };
1766
+ e.kind && (t.kind = e.kind), e.status && (t.status = e.status);
1767
+ const n = [Me(t)], s = `## ${e.id}: ${e.title}`;
1768
+ return n.push(e.body ? `${s}
1769
+
1770
+ ${e.body}` : s), de(n, [{ entries: e.log, section: q }]), n.join(`
1771
+
1772
+ `).trimEnd();
1773
+ }
1774
+ function Xo(e, t) {
1775
+ let n = !1;
1776
+ const s = e.split(`
1777
+ `).filter((a) => {
1778
+ if (!n) {
1779
+ const i = a.match(_t);
1780
+ if (i && i[1] === t.date && i[2].trim() === t.title && i[3].trim() === t.description)
1781
+ return n = !0, !1;
1782
+ }
1783
+ return !0;
1784
+ });
1785
+ return n ? s.join(`
1786
+ `) : e;
1787
+ }
1788
+ let ee = Promise.resolve();
1789
+ async function ya(e, t) {
1790
+ const n = new RegExp(`^${t.toUpperCase()}-(\\d+)\\.md$`), s = [
1791
+ f(e, "ideas"),
1792
+ f(e, "ideas", "archive"),
1793
+ f(e, "plans"),
1794
+ f(e, "plans", "archive")
1795
+ ];
1796
+ let a = 0;
1797
+ for (const i of s) {
1798
+ let r;
1799
+ try {
1800
+ r = await M(i);
1801
+ } catch {
1802
+ continue;
1803
+ }
1804
+ for (const o of r) {
1805
+ const c = n.exec(o);
1806
+ c && (a = Math.max(a, Number(c[1])));
1807
+ }
1808
+ }
1809
+ return a;
1810
+ }
1811
+ async function Wt(e, t) {
1812
+ const n = ee.then(async () => {
1813
+ let s = null;
1814
+ try {
1815
+ s = JSON.parse(await v(e, "utf-8"));
1816
+ } catch {
1817
+ return;
1818
+ }
1819
+ if (!(s != null && s.nextId)) return;
1820
+ const a = s.nextId[t] ?? 1, i = await ya(C(e), t), r = Math.max(a, i + 1), o = `${t.toUpperCase()}-${r}`;
1821
+ return s.nextId[t] = r + 1, await b(e, `${JSON.stringify(s, null, 2)}
1822
+ `), o;
1823
+ });
1824
+ return ee = n.catch(() => {
1825
+ }), n;
1826
+ }
1827
+ async function wa(e) {
1828
+ return Wt(e, "idea");
1829
+ }
1830
+ async function Qo(e) {
1831
+ return Wt(e, "ticket");
1832
+ }
1833
+ async function Zo(e, t) {
1834
+ const n = ee.then(async () => {
1835
+ let s = null;
1836
+ try {
1837
+ s = JSON.parse(await v(e, "utf-8"));
1838
+ } catch {
1839
+ return;
1840
+ }
1841
+ const a = s.subjects ?? [];
1842
+ a.includes(t) || (s.subjects = [...a, t], await b(e, `${JSON.stringify(s, null, 2)}
1843
+ `));
1844
+ });
1845
+ return ee = n.catch(() => {
1846
+ }), n;
1847
+ }
1848
+ async function ba(e, t) {
1849
+ const n = f(e, "papercamp", "ideas"), s = f(n, "archive"), a = f(n, `${t}.md`), i = f(s, `${t}.md`);
1850
+ await $(s, { recursive: !0 });
1851
+ try {
1852
+ return await gt(a, i), !0;
1853
+ } catch (r) {
1854
+ if (r.code === "ENOENT") return !1;
1855
+ throw r;
1856
+ }
1857
+ }
1858
+ async function va(e, t) {
1859
+ const n = f(e, "papercamp", "ideas"), s = f(n, "archive", `${t}.md`), a = f(n, `${t}.md`);
1860
+ try {
1861
+ return await gt(s, a), !0;
1862
+ } catch (i) {
1863
+ if (i.code === "ENOENT") return !1;
1864
+ throw i;
1865
+ }
1866
+ }
1867
+ function Ea(e) {
1868
+ const t = JSON.stringify({
1869
+ body: e.body,
1870
+ phases: e.phases.map((n) => ({
1871
+ done: n.done,
1872
+ text: n.text,
1873
+ description: n.description ?? null,
1874
+ source: n.source ?? null
1875
+ }))
1876
+ });
1877
+ return On("sha256").update(t).digest("hex");
1878
+ }
1879
+ async function et(e) {
1880
+ try {
1881
+ const t = await O(e);
1882
+ return `${e}:${t.mtimeMs}:${t.size}`;
1883
+ } catch {
1884
+ return `${e}:-`;
1885
+ }
1886
+ }
1887
+ async function ka(e) {
1888
+ const t = [];
1889
+ for (const n of [e, f(e, "archive")]) {
1890
+ let s = [];
1891
+ try {
1892
+ s = await M(n);
1893
+ } catch {
1894
+ t.push(`${n}:-`);
1895
+ continue;
1896
+ }
1897
+ const a = s.filter((i) => i.endsWith(".md")).sort();
1898
+ t.push(...await Promise.all(a.map((i) => et(f(n, i)))));
1899
+ }
1900
+ return t.push(await et(f(e, "..", ".local", "run-order.md"))), t.join(`
1901
+ `);
1902
+ }
1903
+ async function Sa(e, t, n, s, a, i) {
1904
+ const { entries: r, warnings: o } = await ce(t), c = r.map((m) => ({
1905
+ ...m,
1906
+ status: oe(m, s == null ? void 0 : s.get(m.id), a, i.has(m.id))
1907
+ })), d = c.map((m) => {
1908
+ if (m.kind !== "board" || m.archived || m.status === "done" || m.status === "dropped") return m;
1909
+ const y = c.filter((E) => E.kind === "ticket" && E.idea === m.id).map((E) => E.status ?? "idea");
1910
+ return { ...m, status: Xe(y) };
1911
+ }), l = d.filter((m) => m.kind !== "note").map((m) => Pt(m, s == null ? void 0 : s.get(m.id), a, i.has(m.id))), p = l.map((m) => {
1912
+ if (m.entityKind !== "board" || m.archived || m.status === "done" || m.status === "dropped")
1913
+ return m;
1914
+ const y = l.filter((E) => E.entityKind === "ticket" && E.idea === m.id).map((E) => E.status);
1915
+ return { ...m, status: Xe(y) };
1916
+ }), h = /* @__PURE__ */ new Map([[e, new Map(d.map((m) => [m.id, m]))]]), g = await ga(e, p, $a, h), w = d.filter((m) => m.kind === "note").map(Ds);
1917
+ return { entries: d, work: g, notes: w, warnings: o, prs: s, resolved: a, mainActivityIds: i, fingerprint: n };
1918
+ }
1919
+ const H = /* @__PURE__ */ new Map();
1920
+ function Vt(e) {
1921
+ H.delete(e);
1922
+ }
1923
+ const tt = /* @__PURE__ */ new Map();
1924
+ function Xt(e, t) {
1925
+ const s = (tt.get(e) ?? Promise.resolve()).then(t);
1926
+ return tt.set(
1927
+ e,
1928
+ s.catch(() => {
1929
+ })
1930
+ ), s;
1931
+ }
1932
+ async function $a(e, t = {}) {
1933
+ const { live: n = !1 } = t, s = f(e, "papercamp", "ideas"), a = n ? void 0 : Number.POSITIVE_INFINITY, [i, r, o] = await Promise.all([
1934
+ ka(s),
1935
+ Kt(e, a),
1936
+ Mt(e)
1937
+ ]), { ids: c, sha: d } = o, l = r !== void 0, p = await ha(e) ?? 0, h = `${i}:${p}:${d}`, g = H.get(e);
1938
+ if (g && g.key === h) return g.promise;
1939
+ const w = Sa(e, s, i, r, l, c).catch(
1940
+ (m) => {
1941
+ var y;
1942
+ throw ((y = H.get(e)) == null ? void 0 : y.key) === h && H.delete(e), m;
1943
+ }
1944
+ );
1945
+ return H.set(e, { key: h, promise: w }), w;
1946
+ }
1947
+ async function Ia(e) {
1948
+ try {
1949
+ return await v(e, "utf-8");
1950
+ } catch (t) {
1951
+ if (t.code === "ENOENT") return "";
1952
+ throw t;
1953
+ }
1954
+ }
1955
+ async function me(e) {
1956
+ try {
1957
+ return await O(e), !0;
1958
+ } catch {
1959
+ return !1;
1960
+ }
1961
+ }
1962
+ function ec(e) {
1963
+ return e ? e.trackedOutsidePlan.length + (e.hasLocalGitignore ? 0 : 1) + (e.hasGitattributesBlock ? 0 : 1) : 0;
1964
+ }
1965
+ async function Aa(e) {
1966
+ if (!((await k(e, ["rev-parse", "--is-inside-work-tree"])).trim() === "true")) return null;
1967
+ const s = (await k(e, ["ls-files", "--", "papercamp"])).split(`
1968
+ `).map((o) => o.trim()).filter(Boolean).filter((o) => {
1969
+ const c = o.slice(10).split("/")[0];
1970
+ return !Ft.has(c);
1971
+ }), a = await me(f(e, "papercamp", ".local", ".gitignore")), r = (await v(f(e, ".gitattributes"), "utf-8").catch(() => "")).includes(ea);
1972
+ return { trackedOutsidePlan: s, hasLocalGitignore: a, hasGitattributesBlock: r };
1973
+ }
1974
+ function _a(e, t = {}) {
1975
+ return {
1976
+ id: e.id,
1977
+ title: e.title,
1978
+ type: e.type,
1979
+ kind: e.kind,
1980
+ status: e.status,
1981
+ idea: e.idea,
1982
+ agent: e.agent,
1983
+ created: e.created,
1984
+ updated: e.updated,
1985
+ audited: e.audited,
1986
+ auditedHash: e.auditedHash,
1987
+ released: e.released,
1988
+ tags: e.tags,
1989
+ needs: e.needs,
1990
+ subject: e.subject,
1991
+ order: e.storedOrder ?? e.order,
1992
+ issueSource: e.issueSource,
1993
+ body: e.body,
1994
+ phases: e.phases,
1995
+ fixes: e.fixes,
1996
+ thread: e.thread,
1997
+ unknownFrontmatter: e.unknownFrontmatter,
1998
+ ...t
1999
+ };
2000
+ }
2001
+ async function Ra(e, t, n) {
2002
+ await Dt(D(e, "config.json")), await b(t, `${je(n)}
2003
+ `, "utf-8");
2004
+ }
2005
+ class B extends Error {
2006
+ constructor(n, s = 409) {
2007
+ super(n);
2008
+ Ye(this, "status");
2009
+ this.name = "CorpusError", this.status = s;
2010
+ }
2011
+ }
2012
+ async function Ta(e, t) {
2013
+ const n = D(e, "ideas"), s = f(n, `${t}.md`);
2014
+ if (await me(s)) return s;
2015
+ const a = f(n, "archive", `${t}.md`);
2016
+ return await me(a) ? a : null;
2017
+ }
2018
+ async function Pa(e, t) {
2019
+ const n = await Ia(e);
2020
+ if (!n) throw new B(`entity file not found: ${e}`, 404);
2021
+ const s = xe(n).entries[0];
2022
+ if (!s) throw new B(`failed to parse entity file: ${e}`, 404);
2023
+ return { ...s, archived: t, storedOrder: s.order };
2024
+ }
2025
+ async function Na(e, t, n) {
2026
+ return Xt(e, async () => {
2027
+ var p, h, g;
2028
+ const s = await Ta(e, t);
2029
+ if (!s) throw new B(`entity "${t}" not found`, 404);
2030
+ const a = D(e, "ideas"), i = s.startsWith(f(a, "archive")), r = await Pa(s, i), o = n(r);
2031
+ if (o === void 0) return r;
2032
+ if (Tt(r)) {
2033
+ const w = (((p = o.phases) == null ? void 0 : p.length) ?? 0) > r.phases.length, m = (((h = o.fixes) == null ? void 0 : h.length) ?? 0) > (((g = r.fixes) == null ? void 0 : g.length) ?? 0), y = Object.hasOwn(o, "status") && o.status !== void 0 && o.status !== "done" && o.status !== "dropped", E = Object.hasOwn(o, "body");
2034
+ if (w || m || y || E)
2035
+ throw new B(
2036
+ "a done/archived entity is read-only — new work spawns its own fix entity"
2037
+ );
2038
+ }
2039
+ const c = o.updated ?? ue(), d = _a(r, { ...o, updated: c });
2040
+ await Ra(e, s, d);
2041
+ const l = r.kind === "note" && o.status === "done";
2042
+ return (o.status === "dropped" || l) && !r.archived ? await ba(e, t) : Object.hasOwn(o, "status") && o.status === void 0 && r.archived && await va(e, t), Vt(e), { ...r, ...d };
2043
+ });
2044
+ }
2045
+ async function tc(e, t, n) {
2046
+ return Na(e, t, (s) => ({
2047
+ thread: [...s.thread ?? [], n]
2048
+ }));
2049
+ }
2050
+ async function nc(e, t) {
2051
+ return Xt(e, async () => {
2052
+ var r;
2053
+ const n = D(e, "config.json");
2054
+ await Dt(n);
2055
+ const s = await wa(n);
2056
+ if (!s) throw new B("could not assign entity ID", 500);
2057
+ const a = D(e, "ideas");
2058
+ await $(a, { recursive: !0 });
2059
+ const i = je({
2060
+ id: s,
2061
+ title: t.title.trim(),
2062
+ kind: t.kind,
2063
+ type: t.type,
2064
+ status: t.status ?? (t.kind === void 0 ? "idea" : void 0),
2065
+ idea: t.idea,
2066
+ issueSource: t.issueSource,
2067
+ created: ue(),
2068
+ subject: t.subject,
2069
+ needs: t.needs,
2070
+ body: (r = t.content) == null ? void 0 : r.trim(),
2071
+ phases: t.phases
2072
+ });
2073
+ return await b(f(a, `${s}.md`), `${i}
2074
+ `, "utf-8"), Vt(e), s;
2075
+ });
2076
+ }
2077
+ const xa = /\]\(assets\/([^)/]+)\)/g, Ca = /\]\(\.\.\/assets\/([^)/]+)\/([^)/]+)\)/g, Oa = 5 * 1024 * 1024;
2078
+ function nt(e, t, n, s) {
2079
+ return { file: e.path, line: t, rule: n, message: s };
2080
+ }
2081
+ const Da = ({ files: e, assetFolders: t }) => {
2082
+ if (!(t != null && t.length)) return [];
2083
+ const n = new Set(e.map((s) => s.id));
2084
+ return t.filter((s) => !n.has(s.id)).map((s) => ({
2085
+ file: `papercamp/assets/${s.id}`,
2086
+ line: 1,
2087
+ rule: "asset-orphan-folder",
2088
+ message: `folder id "${s.id}" matches no entity in the corpus`
2089
+ }));
2090
+ }, La = ({ assetFolders: e, assetMaxBytes: t }) => {
2091
+ if (!(e != null && e.length)) return [];
2092
+ const n = t ?? Oa, s = n / (1024 * 1024);
2093
+ return e.flatMap(
2094
+ (a) => a.files.filter((i) => i.size > n).map((i) => ({
2095
+ file: `papercamp/assets/${a.id}/${i.name}`,
2096
+ line: 1,
2097
+ rule: "asset-oversized",
2098
+ message: `${(i.size / (1024 * 1024)).toFixed(1)}MB exceeds the ${s}MB per-file limit`
2099
+ }))
2100
+ );
2101
+ }, Ma = ({ files: e, assetFolders: t }) => {
2102
+ const n = new Map(
2103
+ (t ?? []).map((s) => [s.id, new Set(s.files.map((a) => a.name))])
2104
+ );
2105
+ return e.flatMap((s) => {
2106
+ var r, o;
2107
+ const a = s.content.split(`
2108
+ `), i = [];
2109
+ for (let c = 0; c < a.length; c++) {
2110
+ for (const d of a[c].matchAll(xa)) {
2111
+ const [, l] = d;
2112
+ (r = n.get(s.id)) != null && r.has(l) || i.push(nt(s, c + 1, "asset-dangling-link", `assets/${l} does not exist`));
2113
+ }
2114
+ for (const d of a[c].matchAll(Ca)) {
2115
+ const [, l, p] = d;
2116
+ (o = n.get(l)) != null && o.has(p) || i.push(
2117
+ nt(s, c + 1, "asset-dangling-link", `../assets/${l}/${p} does not exist`)
2118
+ );
2119
+ }
2120
+ }
2121
+ return i;
2122
+ });
2123
+ }, ja = [
2124
+ Da,
2125
+ La,
2126
+ Ma
2127
+ ], Fa = ({ localStateDrift: e }) => ((e == null ? void 0 : e.trackedOutsidePlan) ?? []).map((t) => ({
2128
+ file: t,
2129
+ line: 1,
2130
+ rule: "local-state-tracked-outside-plan",
2131
+ message: "tracked in git but not part of the plan — run `paper-camp doctor --fix` to move it under papercamp/.local/ (or the account config dir) and untrack it."
2132
+ })), Ha = ({ localStateDrift: e }) => e && !e.hasLocalGitignore ? [
2133
+ {
2134
+ file: "papercamp/.local/.gitignore",
2135
+ line: 1,
2136
+ rule: "local-state-missing-local-gitignore",
2137
+ message: "missing — nothing under papercamp/.local/ is protected from being committed; run `paper-camp doctor --fix` to create it."
2138
+ }
2139
+ ] : [], Ba = ({ localStateDrift: e }) => e && !e.hasGitattributesBlock ? [
2140
+ {
2141
+ file: ".gitattributes",
2142
+ line: 1,
2143
+ rule: "local-state-missing-gitattributes-block",
2144
+ message: "missing the paper-camp managed block — two clones appending to papercamp/chat.md will conflict on pull; run `paper-camp doctor --fix` to add it."
2145
+ }
2146
+ ] : [], Ga = [
2147
+ Fa,
2148
+ Ha,
2149
+ Ba
2150
+ ], Qt = /^---\s*\r?\n([\s\S]*?)\r?\n---\s*(?:\r?\n)?/, Ua = 2;
2151
+ function Fe(e) {
2152
+ const t = e.content.match(Qt);
2153
+ if (!t) return null;
2154
+ let n = null;
2155
+ try {
2156
+ const s = se(t[1]);
2157
+ s && typeof s == "object" && !Array.isArray(s) && (n = s);
2158
+ } catch {
2159
+ }
2160
+ return { yaml: t[1], data: n };
2161
+ }
2162
+ function Zt(e, t) {
2163
+ const n = new RegExp(`^\\s*${t}:`), s = e.split(`
2164
+ `).findIndex((a) => n.test(a));
2165
+ return s === -1 ? 1 : Ua + s;
2166
+ }
2167
+ function He(e) {
2168
+ const t = Fe(e);
2169
+ return t ? Zt(t.yaml, "id") : 1;
2170
+ }
2171
+ function en(e) {
2172
+ var n, s;
2173
+ const t = (s = (n = Fe(e)) == null ? void 0 : n.data) == null ? void 0 : s.id;
2174
+ return typeof t == "string" ? t : e.id;
2175
+ }
2176
+ function P(e, t, n, s) {
2177
+ return { file: e.path, line: t, rule: n, message: s };
2178
+ }
2179
+ const Ya = ({ files: e }) => e.flatMap((t) => {
2180
+ const n = t.content.match(Qt);
2181
+ if (!n)
2182
+ return [P(t, 1, "frontmatter-schema", "missing YAML frontmatter block")];
2183
+ let s;
2184
+ try {
2185
+ s = se(n[1]);
2186
+ } catch (i) {
2187
+ return [P(t, 1, "frontmatter-schema", `invalid YAML: ${i.message}`)];
2188
+ }
2189
+ if (typeof s != "object" || s === null || Array.isArray(s))
2190
+ return [P(t, 1, "frontmatter-schema", "frontmatter is not a key/value mapping")];
2191
+ const a = It.safeParse(s);
2192
+ return a.success ? [] : a.error.issues.map((i) => {
2193
+ const r = String(i.path[0] ?? ""), o = i.path.length > 0 ? `${i.path.join(".")}: ${i.message}` : i.message;
2194
+ return P(t, r ? Zt(n[1], r) : 1, "frontmatter-schema", o);
2195
+ });
2196
+ }), qa = ({ files: e }) => e.flatMap((t) => {
2197
+ var s, a;
2198
+ const n = (a = (s = Fe(t)) == null ? void 0 : s.data) == null ? void 0 : a.id;
2199
+ return typeof n != "string" || n === t.id ? [] : [
2200
+ P(
2201
+ t,
2202
+ He(t),
2203
+ "filename-id-mismatch",
2204
+ `frontmatter id "${n}" does not match file name ${t.id}.md`
2205
+ )
2206
+ ];
2207
+ }), Ja = ({ files: e }) => {
2208
+ const t = /* @__PURE__ */ new Map();
2209
+ for (const s of e) {
2210
+ const a = en(s), i = t.get(a);
2211
+ i ? i.push(s) : t.set(a, [s]);
2212
+ }
2213
+ const n = [];
2214
+ for (const [s, a] of t) {
2215
+ if (a.length < 2) continue;
2216
+ const i = a.map((r) => r.path).join(", ");
2217
+ for (const r of a)
2218
+ n.push(
2219
+ P(
2220
+ r,
2221
+ He(r),
2222
+ "duplicate-id",
2223
+ `id ${s} is defined by ${a.length} files: ${i}`
2224
+ )
2225
+ );
2226
+ }
2227
+ return n;
2228
+ }, za = ({ files: e, config: t }) => {
2229
+ var s;
2230
+ const n = (s = t == null ? void 0 : t.nextId) == null ? void 0 : s.idea;
2231
+ return n == null ? [] : e.flatMap((a) => {
2232
+ const i = en(a), r = i.match(/^IDEA-(\d+)$/);
2233
+ return r ? Number(r[1]) < n ? [] : [
2234
+ P(
2235
+ a,
2236
+ He(a),
2237
+ "id-counter-stale",
2238
+ `id ${i} is >= config nextId.idea (${n}); the next minted id would collide`
2239
+ )
2240
+ ] : [];
2241
+ });
2242
+ }, Ka = ({ config: e }) => {
2243
+ const t = e == null ? void 0 : e.version;
2244
+ return typeof t != "number" || t <= T ? [] : [
2245
+ {
2246
+ file: "papercamp/config.json",
2247
+ line: 1,
2248
+ rule: "corpus-format-too-new",
2249
+ message: `config.version ${t} is newer than the format version ${T} this paper-camp understands; writes are refused until it's upgraded`
2250
+ }
2251
+ ];
2252
+ }, Wa = [
2253
+ Ya,
2254
+ qa,
2255
+ Ja,
2256
+ za,
2257
+ Ka
2258
+ ], Va = /^---\s*\r?\n([\s\S]*?)\r?\n---\s*(?:\r?\n)?/, tn = /^#{1,6}\s+(.+?)\s*$/, nn = /^[-*]\s+\[[ xX]\]\s+(.*)$/, Xa = /^(phases|fixes)$/i, sn = /^#{1,6}\s+Phases\s*$/i, Qa = /^(?:\d{4}-\d{2}-\d{2}\s+)?\[(?:log|clarification|review|note|decision|question|chat)\]/, Za = /^\[(?:phase:\d+|body)\]/, ei = /\[\[\s*((?:IDEA|TICKET)-\d+)(?:\s*\|[^\]]*)?\s*\]\]/g, ti = /* @__PURE__ */ new Set(["idea", "planned", "in-progress", "review", "open"]), ni = /* @__PURE__ */ new Set(["done", "dropped"]), si = /* @__PURE__ */ new Set(["planned", "in-progress", "review"]), ai = /^\[manual\]\s+/;
2259
+ function le(e) {
2260
+ const t = e.match(Va);
2261
+ if (!t) return null;
2262
+ try {
2263
+ const n = se(t[1]);
2264
+ if (n && typeof n == "object" && !Array.isArray(n))
2265
+ return n;
2266
+ } catch {
2267
+ }
2268
+ return null;
2269
+ }
2270
+ function an(e, t) {
2271
+ const n = e.split(`
2272
+ `).findIndex((s) => t.test(s));
2273
+ return n === -1 ? 1 : n + 1;
2274
+ }
2275
+ function N(e, t, n, s) {
2276
+ return { file: e.path, line: t, rule: n, message: s };
2277
+ }
2278
+ const ii = ({ files: e }) => e.flatMap((t) => {
2279
+ const n = t.content.split(`
2280
+ `), s = [];
2281
+ let a = !1;
2282
+ for (let i = 0; i < n.length; i++) {
2283
+ const r = n[i].match(tn);
2284
+ if (r) {
2285
+ a = Xa.test(r[1].trim());
2286
+ continue;
2287
+ }
2288
+ if (a) continue;
2289
+ const o = n[i].match(nn);
2290
+ if (!o) continue;
2291
+ const c = o[1].trim();
2292
+ Qa.test(c) || Za.test(c) || s.push(
2293
+ N(
2294
+ t,
2295
+ i + 1,
2296
+ "phases-list-split",
2297
+ `orphaned phase checkbox outside any Phases section: "${n[i].trim()}"`
2298
+ )
2299
+ );
2300
+ }
2301
+ return s;
2302
+ }), ri = ({ files: e }) => e.flatMap((t) => {
2303
+ var a;
2304
+ if (((a = le(t.content)) == null ? void 0 : a.kind) !== "note") return [];
2305
+ const s = t.content.split(`
2306
+ `).findIndex((i) => sn.test(i));
2307
+ return s === -1 ? [] : [N(t, s + 1, "note-has-phases", "a kind: note entity carries a Phases section")];
2308
+ }), oi = ({ files: e }) => e.flatMap((t) => t.content.split(`
2309
+ `).flatMap((a, i) => sn.test(a) ? [i] : []).slice(1).map(
2310
+ (a) => N(
2311
+ t,
2312
+ a + 1,
2313
+ "duplicate-phases-section",
2314
+ "a second Phases heading exists — a redraft must replace the existing list in place, not append a new one"
2315
+ )
2316
+ )), ci = ({ files: e }) => e.flatMap((t) => {
2317
+ const n = le(t.content), s = typeof (n == null ? void 0 : n.kind) == "string" ? n.kind : void 0;
2318
+ if (s === "note" || s === "board") return [];
2319
+ const a = typeof (n == null ? void 0 : n.status) == "string" ? n.status : void 0;
2320
+ if (!a || !si.has(a)) return [];
2321
+ const i = t.content.split(`
2322
+ `);
2323
+ let r = !1, o = -1, c = !1;
2324
+ for (let l = 0; l < i.length; l++) {
2325
+ const p = i[l].match(tn);
2326
+ if (p) {
2327
+ r = /^phases$/i.test(p[1].trim()), r && o === -1 && (o = l);
2328
+ continue;
2329
+ }
2330
+ if (!r) continue;
2331
+ const h = i[l].match(nn);
2332
+ h && (ai.test(h[1].trim()) || (c = !0));
2333
+ }
2334
+ if (c) return [];
2335
+ const d = o === -1 ? an(t.content, /^status:/) : o + 1;
2336
+ return [
2337
+ N(
2338
+ t,
2339
+ d,
2340
+ "no-agent-phase",
2341
+ `status "${a}" but no agent-runnable phase — only manual phases (or none at all), which reads as a note wearing an idea's clothes`
2342
+ )
2343
+ ];
2344
+ }), di = ({ files: e }) => e.flatMap((t) => {
2345
+ var a;
2346
+ const n = (a = le(t.content)) == null ? void 0 : a.status;
2347
+ if (typeof n != "string") return [];
2348
+ const s = an(t.content, /^status:/);
2349
+ return t.archived && ti.has(n) ? [
2350
+ N(
2351
+ t,
2352
+ s,
2353
+ "archive-placement",
2354
+ `status "${n}" is active but the file is under ideas/archive/`
2355
+ )
2356
+ ] : !t.archived && ni.has(n) ? [
2357
+ N(
2358
+ t,
2359
+ s,
2360
+ "archive-placement",
2361
+ `status "${n}" is closed but the file is still under ideas/ — it should be archived`
2362
+ )
2363
+ ] : [];
2364
+ }), ui = ({ files: e }) => {
2365
+ var n;
2366
+ const t = /* @__PURE__ */ new Set();
2367
+ for (const s of e) {
2368
+ t.add(s.id);
2369
+ const a = (n = le(s.content)) == null ? void 0 : n.id;
2370
+ typeof a == "string" && t.add(a);
2371
+ }
2372
+ return e.flatMap((s) => {
2373
+ const a = s.content.split(`
2374
+ `), i = [];
2375
+ for (let r = 0; r < a.length; r++)
2376
+ for (const o of a[r].matchAll(ei))
2377
+ t.has(o[1]) || i.push(
2378
+ N(
2379
+ s,
2380
+ r + 1,
2381
+ "dangling-link",
2382
+ `[[${o[1]}]] points at an id no entity in the corpus defines`
2383
+ )
2384
+ );
2385
+ return i;
2386
+ });
2387
+ }, li = [
2388
+ ii,
2389
+ ri,
2390
+ oi,
2391
+ ci,
2392
+ di,
2393
+ ui
2394
+ ], pi = [
2395
+ ["pnpm-lock.yaml", "pnpm"],
2396
+ ["yarn.lock", "yarn"],
2397
+ ["bun.lockb", "bun"],
2398
+ ["package-lock.json", "npm"]
2399
+ ];
2400
+ function rn(e) {
2401
+ for (const [t, n] of pi)
2402
+ if (ke(f(e, t))) return n;
2403
+ return "npm";
2404
+ }
2405
+ function fi(e) {
2406
+ const t = f(e, "package.json");
2407
+ if (ke(t))
2408
+ try {
2409
+ return JSON.parse(Ee(t, "utf-8"));
2410
+ } catch {
2411
+ return;
2412
+ }
2413
+ }
2414
+ const st = "@dendelion/paper-camp";
2415
+ function hi(e) {
2416
+ const t = fi(e);
2417
+ if (!t) return !1;
2418
+ const n = t.dependencies, s = t.devDependencies;
2419
+ return !!(n != null && n[st] || s != null && s[st]);
2420
+ }
2421
+ const mi = ["*.json", "*.ts", "*.tsx", "*.md", "*.config.*"], gi = ["src/**", "app/**", "scripts/**", "papercamp/**"], yi = ["git status*", "git diff*", "git log*"], wi = ["ls*", "cat*", "wc*"], bi = ["run *", "test*", "install*", "view *", "pack*", "ls*"], vi = ["npx tsc*", "npx biome check*", "npx vitest*", "npx jest*", "npx expo *"];
2422
+ function on(e) {
2423
+ const t = gi.flatMap((a) => [`Edit(${a})`, `Write(${a})`]), n = mi.flatMap((a) => [`Edit(${a})`, `Write(${a})`]), s = [
2424
+ ...yi,
2425
+ ...wi,
2426
+ ...bi.map((a) => `${e} ${a}`),
2427
+ ...vi
2428
+ ].map((a) => `Bash(${a})`);
2429
+ return [...t, ...n, ...s];
2430
+ }
2431
+ function Ei(e) {
2432
+ return e ? `"${Ti}" session-focus` : "paper-camp session-focus";
2433
+ }
2434
+ function ki(e, t) {
2435
+ let n;
2436
+ try {
2437
+ n = JSON.parse(e);
2438
+ } catch {
2439
+ return e;
2440
+ }
2441
+ const s = on(rn(t)), a = n.permissions ?? {}, i = Array.isArray(a.allow) ? a.allow : [], r = s.filter((o) => !i.includes(o));
2442
+ return r.length === 0 && n.permissions !== void 0 ? e : (n.permissions = { ...a, allow: [...i, ...r] }, `${JSON.stringify(n, null, 2)}
2443
+ `);
2444
+ }
2445
+ function Si(e) {
2446
+ const t = rn(e), n = Ei(hi(e));
2447
+ return `${JSON.stringify(
2448
+ {
2449
+ permissions: {
2450
+ allow: on(t)
2451
+ },
2452
+ hooks: {
2453
+ SessionStart: [
2454
+ {
2455
+ matcher: "*",
2456
+ hooks: [{ type: "command", command: n }]
2457
+ }
2458
+ ]
2459
+ }
2460
+ },
2461
+ null,
2462
+ 2
2463
+ )}
2464
+ `;
2465
+ }
2466
+ const ge = { command: "paper-camp", args: ["mcp"] };
2467
+ function $i() {
2468
+ return `${JSON.stringify({ mcpServers: { "paper-camp": ge } }, null, 2)}
2469
+ `;
2470
+ }
2471
+ function Ii(e) {
2472
+ let t;
2473
+ try {
2474
+ t = JSON.parse(e);
2475
+ } catch {
2476
+ return e;
2477
+ }
2478
+ const n = t.mcpServers ?? {};
2479
+ return JSON.stringify(n["paper-camp"]) === JSON.stringify(ge) ? e : (t.mcpServers = { ...n, "paper-camp": ge }, `${JSON.stringify(t, null, 2)}
2480
+ `);
2481
+ }
2482
+ const ye = { type: "local", command: ["paper-camp", "mcp"], enabled: !0 };
2483
+ function Ai() {
2484
+ return `${JSON.stringify({ mcp: { "paper-camp": ye } }, null, 2)}
2485
+ `;
2486
+ }
2487
+ function _i(e) {
2488
+ let t;
2489
+ try {
2490
+ t = JSON.parse(e);
2491
+ } catch {
2492
+ return e;
2493
+ }
2494
+ const n = t.mcp ?? {};
2495
+ return JSON.stringify(n["paper-camp"]) === JSON.stringify(ye) ? e : (t.mcp = { ...n, "paper-camp": ye }, `${JSON.stringify(t, null, 2)}
2496
+ `);
2497
+ }
2498
+ const Ri = `---
2499
+ name: paper-camp
2500
+ description: Work inside a project that has a papercamp/ folder — its unified idea entities (each holding its plan as a section), with decisions and open questions logged as notes on the idea they bind. Use this whenever the working directory contains papercamp/ (papercamp/ideas/), and especially before starting, continuing, or completing any plan phase, drafting an idea, logging a decision, or answering "what are we working on / what's next".
2501
+ ---
2502
+
2503
+ # Paper Camp
2504
+
2505
+ Paper Camp is this project's planning methodology: plans and ideas are one
2506
+ thing, a unified entity file under \`papercamp/ideas/\`, with decisions and
2507
+ open questions logged as notes bound to the idea they concern. This skill
2508
+ tells you how to read that state before acting and how to keep it honest as
2509
+ you work.
2510
+
2511
+ The \`papercamp\` MCP server is present in every run Paper Camp launches and in
2512
+ every session opened in an initialised project — prefer its tools (list/get
2513
+ plans, update phase, etc.) over raw file access, since they enforce the same
2514
+ guards (id allocation, branch conflicts) that the file grammar below assumes.
2515
+ Everything in this skill still applies conceptually; only the mechanism
2516
+ changes.
2517
+
2518
+ ## Before doing any work
2519
+
2520
+ Read, in this order, whatever exists:
2521
+
2522
+ 1. \`papercamp/ideas/\` — one file per entity, named by id. Ideas and plans are
2523
+ one thing: an entity is an *idea* for its whole life, and its plan is a
2524
+ \`### Phases\` section inside the same file.
2525
+ 2. The specific entity file at \`papercamp/ideas/<ID>.md\` (e.g. \`IDEA-43.md\`)
2526
+ for the work you're about to do — YAML frontmatter (\`id\`, \`title\`,
2527
+ \`type\`, \`status\`, \`tags\`, ...) plus prose rationale, then optionally a
2528
+ \`### Phases\` list of \`- [ ]\`/\`- [x]\` checkboxes with an indented
2529
+ description under each. Done/dropped entities live in
2530
+ \`papercamp/ideas/archive/\`.
2531
+ Its notes carry any decision or open question bound to it — settled calls
2532
+ you shouldn't re-litigate without flagging it to the user first, and
2533
+ unresolved questions that might block or redirect the work you're about
2534
+ to start.
2535
+
2536
+ Skip files that don't exist yet (a fresh project may have empty logs).
2537
+
2538
+ ## While working
2539
+
2540
+ - Work one plan phase at a time unless told otherwise — don't cascade into
2541
+ later phases just because they look quick.
2542
+ - If a phase's boundary or intent is unclear, ask before continuing.
2543
+ - Prose in plans and ideas is more current than your memory of past
2544
+ conversations. If they conflict, the files win — say so and resync.
2545
+
2546
+ ## Keep the project current as you go
2547
+
2548
+ - **Plan phases**: when you finish a phase, flip its checkbox from \`- [ ]\` to
2549
+ \`- [x]\` in the plan's frontmatter file. Change only that line — don't touch
2550
+ other phases or prose.
2551
+ - **Plan status**: keep the plan's \`status:\` frontmatter honest
2552
+ (\`planned\` / \`in-progress\` / \`review\` / \`done\`). When every phase is
2553
+ checked, set status to \`review\` — never \`done\`. \`done\` is a human-only
2554
+ promotion after review; an agent finishing the last phase does not close
2555
+ the plan itself.
2556
+ - **Decisions / open questions**: if you settle something ambiguous while
2557
+ working, log it as a decision note on the idea it bounds; if you surface a
2558
+ question you can't resolve yourself, add it as a question note on that idea
2559
+ rather than guessing silently.
2560
+
2561
+ ## What this skill deliberately does not do
2562
+
2563
+ It does not maintain a separate "current focus" file — that's derived at
2564
+ session start from live plan data, not hand-maintained here. It does
2565
+ not define the file formats in full; treat the existing files under
2566
+ \`papercamp/\` as the grammar reference (mirror their structure exactly rather
2567
+ than inventing a new shape).
2568
+ `, Ti = "$CLAUDE_PROJECT_DIR/node_modules/.bin/paper-camp", te = 1, cn = `# paper-camp draft-pr template v${te}`, dn = `<!-- paper-camp draft-pr template v${te} -->`, Pi = /paper-camp draft-pr template v(\d+)/;
2569
+ function Ni(e) {
2570
+ const t = e.split(`
2571
+ `, 1)[0].match(Pi);
2572
+ return t ? Number(t[1]) : null;
2573
+ }
2574
+ const un = `${cn}
2575
+ name: Draft PR
2576
+
2577
+ on:
2578
+ push:
2579
+ branches:
2580
+ - feat/*
2581
+ - fix/*
2582
+ - refactor/*
2583
+ - chore/*
2584
+ - docs/*
2585
+
2586
+ permissions:
2587
+ contents: read
2588
+ pull-requests: write
2589
+
2590
+ jobs:
2591
+ draft-pr:
2592
+ name: Draft PR
2593
+ runs-on: ubuntu-latest
2594
+ steps:
2595
+ - name: Checkout
2596
+ uses: actions/checkout@v4
2597
+ with:
2598
+ persist-credentials: false
2599
+
2600
+ - name: Generate Scout app token
2601
+ id: app-token
2602
+ uses: actions/create-github-app-token@v1
2603
+ with:
2604
+ app-id: \${{ secrets.SCOUT_APP_ID }}
2605
+ private-key: \${{ secrets.SCOUT_PRIVATE_KEY }}
2606
+ permission-contents: read
2607
+ permission-pull-requests: write
2608
+
2609
+ - name: Create draft PR if not exists
2610
+ env:
2611
+ GITHUB_TOKEN: \${{ steps.app-token.outputs.token }}
2612
+ BRANCH: \${{ github.ref_name }}
2613
+ run: |
2614
+ PLAN_ID=$(echo "$BRANCH" | grep -oP '[a-z]+-\\d+' | head -1 || true)
2615
+ if [ -z "$PLAN_ID" ]; then
2616
+ echo "Branch '$BRANCH' does not match plan-ID pattern — skipping PR creation"
2617
+ exit 0
2618
+ fi
2619
+
2620
+ DISPLAY_ID=$(echo "$PLAN_ID" | sed 's/^\\([a-z]*\\)/\\U\\1/' | sed 's/\\-[a-z]/\\U&/')
2621
+
2622
+ TEMPLATE=".github/pull_request_template.md"
2623
+ PLAN_FILE="papercamp/ideas/\${DISPLAY_ID}.md"
2624
+ if [ ! -f "$PLAN_FILE" ] && [ -f "papercamp/ideas/archive/\${DISPLAY_ID}.md" ]; then
2625
+ PLAN_FILE="papercamp/ideas/archive/\${DISPLAY_ID}.md"
2626
+ fi
2627
+
2628
+ TITLE=$(awk '
2629
+ /^---[[:space:]]*$/ { n++; next }
2630
+ n == 1 && /^title:[[:space:]]/ {
2631
+ sub(/^title:[[:space:]]*/, "")
2632
+ gsub(/^["'\\'']|["'\\'']$/, "")
2633
+ print
2634
+ exit
2635
+ }
2636
+ ' "$PLAN_FILE" 2>/dev/null || true)
2637
+
2638
+ if [ -z "$TITLE" ]; then
2639
+ echo "No title: in '$PLAN_FILE' — falling back to the branch slug"
2640
+ TITLE=$(echo "$BRANCH" | sed 's|^[^/]*/||' | sed 's/^[a-z]*-[0-9]*-//' | sed 's/-/ /g')
2641
+ fi
2642
+
2643
+ FULL_TITLE="\${DISPLAY_ID}: \${TITLE}"
2644
+
2645
+ PHASES=$(awk '/^### Phases/{f=1;next} /^### /{f=0} f' "$PLAN_FILE" 2>/dev/null \\
2646
+ | grep -E '^- \\[[ x]\\] ' || true)
2647
+ if [ -z "$PHASES" ]; then
2648
+ PHASES="- [ ] _No phases listed in \\\`\${PLAN_FILE}\\\` yet._"
2649
+ fi
2650
+
2651
+ EXISTING=$(gh pr list --head "$BRANCH" --state all --json number --jq '.[0].number' 2>/dev/null || echo "")
2652
+
2653
+ if [ -z "$EXISTING" ]; then
2654
+ BODY=$(awk -v id="$DISPLAY_ID" -v phases="$PHASES" '
2655
+ /^- \\[ \\] _Phases go here/ { print phases; next }
2656
+ { gsub(/IDEA-N/, id); print }
2657
+ ' "$TEMPLATE")
2658
+ if [ -z "$BODY" ]; then
2659
+ BODY="**Idea:** \\\`\${DISPLAY_ID}\\\` — \\\`\${PLAN_FILE}\\\`"
2660
+ fi
2661
+ gh pr create \\
2662
+ --title "$FULL_TITLE" \\
2663
+ --body "$BODY" \\
2664
+ --draft \\
2665
+ --base main
2666
+ echo "Created draft PR for $FULL_TITLE"
2667
+ else
2668
+ echo "PR #$EXISTING already exists for branch '$BRANCH' — skipping"
2669
+ fi
2670
+ `, ln = `${dn}
2671
+ **Idea:** \`IDEA-N\` — \`papercamp/ideas/IDEA-N.md\`
2672
+
2673
+ _One or two lines on what this idea changes, for whoever reviews it._
2674
+
2675
+ ### Phases
2676
+
2677
+ - [ ] _Phases go here, copied from the idea file — one commit each._
2678
+
2679
+ ### Before merge
2680
+
2681
+ - [ ] Every phase above is checked and the idea's \`status:\` is \`review\`
2682
+ - [ ] CI is green
2683
+ - [ ] Owner has walked the affected components through the live showcase
2684
+
2685
+ See [CONTRIBUTING.md](../blob/main/CONTRIBUTING.md) for the full flow.
2686
+ `, xi = ".claude/settings.json", Ci = ".mcp.json", Oi = "opencode.json", Di = ".github/workflows/draft-pr.yml", Li = ".github/pull_request_template.md", Mi = ({ hasPermissionsAllow: e }) => e === !1 ? [
2687
+ {
2688
+ file: xi,
2689
+ line: 1,
2690
+ rule: "missing-permissions-allow",
2691
+ message: "no permissions.allow list — headless runs deny every edit and shell command outside the defaults; run `paper-camp init --settings` to add it."
2692
+ }
2693
+ ] : [], ji = ({ hasMcpJsonEntry: e, hasOpencodeJsonEntry: t }) => {
2694
+ const n = [];
2695
+ return e === !1 && n.push({
2696
+ file: Ci,
2697
+ line: 1,
2698
+ rule: "missing-mcp-entry",
2699
+ message: "no paper-camp entry — headless claude-code runs still get it inline, but an interactive session opened here does not; run `paper-camp init --settings` to add it."
2700
+ }), t === !1 && n.push({
2701
+ file: Oi,
2702
+ line: 1,
2703
+ rule: "missing-mcp-entry",
2704
+ message: "no paper-camp entry — opencode runs in this project never see the paper-camp MCP server; run `paper-camp init --settings` to add it."
2705
+ }), n;
2706
+ }, Fi = ({ config: e }) => e && !e.defaultAgents ? [
2707
+ {
2708
+ file: "papercamp/config.json",
2709
+ line: 1,
2710
+ rule: "no-default-agents",
2711
+ message: "no defaultAgents — every run uses the built-in agent and model; choose them in Settings → General."
2712
+ }
2713
+ ] : [];
2714
+ function at(e, t) {
2715
+ if (t === void 0) return [];
2716
+ if (t === null)
2717
+ return [
2718
+ {
2719
+ file: e,
2720
+ line: 1,
2721
+ rule: "draft-pr-template-outdated",
2722
+ message: "missing — run `paper-camp doctor --fix` to write it, or re-save the Draft PR setting."
2723
+ }
2724
+ ];
2725
+ const n = Ni(t);
2726
+ return n === null ? [
2727
+ {
2728
+ file: e,
2729
+ line: 1,
2730
+ rule: "draft-pr-template-unmanaged",
2731
+ message: "exists without the paper-camp template header — this looks hand-written, so it will not be overwritten."
2732
+ }
2733
+ ] : n < te ? [
2734
+ {
2735
+ file: e,
2736
+ line: 1,
2737
+ rule: "draft-pr-template-outdated",
2738
+ message: `carries template v${n}, older than the bundled v${te} — run \`paper-camp doctor --fix\` to update it.`
2739
+ }
2740
+ ] : [];
2741
+ }
2742
+ const Hi = ({
2743
+ config: e,
2744
+ draftPrWorkflowContent: t,
2745
+ pullRequestTemplateContent: n
2746
+ }) => {
2747
+ var s, a;
2748
+ return (a = (s = e == null ? void 0 : e.desk) == null ? void 0 : s.ci) != null && a.draftPr ? [
2749
+ ...at(Di, t),
2750
+ ...at(Li, n)
2751
+ ] : [];
2752
+ }, Bi = [
2753
+ Mi,
2754
+ ji,
2755
+ Fi,
2756
+ Hi
2757
+ ], Gi = [
2758
+ ...Wa,
2759
+ ...li,
2760
+ ...Bi,
2761
+ ...Ga,
2762
+ ...ja
2763
+ ], Ui = 5;
2764
+ async function Yi(e) {
2765
+ const t = f(e, "assets"), n = await we(t), s = [];
2766
+ for (const a of n) {
2767
+ const i = f(t, a), r = await O(i).catch(() => {
2768
+ });
2769
+ if (!r || !r.isDirectory()) continue;
2770
+ const o = await we(i), c = [];
2771
+ for (const d of o) {
2772
+ const l = await O(f(i, d)).catch(() => {
2773
+ });
2774
+ l != null && l.isFile() && c.push({ name: d, size: l.size });
2775
+ }
2776
+ s.push({ id: a, files: c });
2777
+ }
2778
+ return s;
2779
+ }
2780
+ function z(e) {
2781
+ return (e == null ? void 0 : e.code) === "ENOENT";
2782
+ }
2783
+ async function we(e) {
2784
+ try {
2785
+ return await M(e);
2786
+ } catch (t) {
2787
+ if (z(t)) return [];
2788
+ throw t;
2789
+ }
2790
+ }
2791
+ async function qi(e) {
2792
+ let t;
2793
+ try {
2794
+ t = await v(e, "utf-8");
2795
+ } catch (n) {
2796
+ if (z(n)) return null;
2797
+ throw n;
2798
+ }
2799
+ try {
2800
+ return JSON.parse(t);
2801
+ } catch {
2802
+ return null;
2803
+ }
2804
+ }
2805
+ async function Ji(e) {
2806
+ var n;
2807
+ let t;
2808
+ try {
2809
+ t = await v(f(e, ".claude", "settings.json"), "utf-8");
2810
+ } catch (s) {
2811
+ if (z(s)) return !1;
2812
+ throw s;
2813
+ }
2814
+ try {
2815
+ const s = JSON.parse(t);
2816
+ return Array.isArray((n = s.permissions) == null ? void 0 : n.allow) && s.permissions.allow.length > 0;
2817
+ } catch {
2818
+ return !1;
2819
+ }
2820
+ }
2821
+ async function it(e, t) {
2822
+ var s;
2823
+ let n;
2824
+ try {
2825
+ n = await v(e, "utf-8");
2826
+ } catch (a) {
2827
+ if (z(a)) return !1;
2828
+ throw a;
2829
+ }
2830
+ try {
2831
+ return !!((s = JSON.parse(n)[t]) != null && s["paper-camp"]);
2832
+ } catch {
2833
+ return !1;
2834
+ }
2835
+ }
2836
+ async function rt(e) {
2837
+ try {
2838
+ return await v(e, "utf-8");
2839
+ } catch (t) {
2840
+ if (z(t)) return null;
2841
+ throw t;
2842
+ }
2843
+ }
2844
+ async function zi(e) {
2845
+ var r;
2846
+ const t = f(e, "ideas"), n = [], s = [
2847
+ { dir: t, archived: !1, names: await M(t) },
2848
+ {
2849
+ dir: f(t, "archive"),
2850
+ archived: !0,
2851
+ names: await we(f(t, "archive"))
2852
+ }
2853
+ ];
2854
+ for (const { dir: o, archived: c, names: d } of s) {
2855
+ const l = d.filter((p) => p.endsWith(".md") && p !== "index.md").sort();
2856
+ for (const p of l)
2857
+ n.push({
2858
+ id: mt(p, ".md"),
2859
+ path: `papercamp/ideas${c ? "/archive" : ""}/${p}`,
2860
+ content: await v(f(o, p), "utf-8"),
2861
+ archived: c
2862
+ });
2863
+ }
2864
+ const a = C(e), i = await qi(f(e, "config.json"));
2865
+ return {
2866
+ files: n,
2867
+ config: i,
2868
+ hasPermissionsAllow: await Ji(a),
2869
+ hasMcpJsonEntry: await it(f(a, ".mcp.json"), "mcpServers"),
2870
+ hasOpencodeJsonEntry: await it(f(a, "opencode.json"), "mcp"),
2871
+ draftPrWorkflowContent: await rt(
2872
+ f(a, ".github", "workflows", "draft-pr.yml")
2873
+ ),
2874
+ pullRequestTemplateContent: await rt(
2875
+ f(a, ".github", "pull_request_template.md")
2876
+ ),
2877
+ localStateDrift: await Aa(a),
2878
+ assetFolders: await Yi(e),
2879
+ assetMaxBytes: (((r = i == null ? void 0 : i.assets) == null ? void 0 : r.maxFileSizeMb) ?? Ui) * 1024 * 1024
2880
+ };
2881
+ }
2882
+ function Ki(e) {
2883
+ return Gi.flatMap((t) => t(e));
2884
+ }
2885
+ async function sc(e) {
2886
+ return Ki(await zi(e));
2887
+ }
2888
+ const Wi = {
2889
+ rule: "archive-placement",
2890
+ fix: (e, t) => t ? {
2891
+ kind: "move",
2892
+ from: t.path,
2893
+ to: t.archived ? t.path.replace("/archive/", "/") : t.path.replace(/\/(?=[^/]+$)/, "/archive/")
2894
+ } : null
2895
+ }, Vi = {
2896
+ ".github/workflows/draft-pr.yml": un,
2897
+ ".github/pull_request_template.md": ln
2898
+ }, Xi = {
2899
+ rule: "draft-pr-template-outdated",
2900
+ fix: (e) => {
2901
+ const t = Vi[e.file];
2902
+ return t ? { kind: "rewrite", path: e.file, content: t } : null;
2903
+ }
2904
+ }, Qi = [Wi, Xi], Zi = new Map(
2905
+ Qi.map((e) => [e.rule, e])
2906
+ );
2907
+ function ac(e, t) {
2908
+ const n = new Map(e.files.map((o) => [o.path, o])), s = [], a = [], i = [], r = [];
2909
+ for (const o of t) {
2910
+ const c = Zi.get(o.rule), d = n.get(o.file), l = c ? c.fix(o, d) : null;
2911
+ if (!l) {
2912
+ i.push(o);
2913
+ continue;
2914
+ }
2915
+ if (l.kind === "move" && n.has(l.to)) {
2916
+ r.push(o);
2917
+ continue;
2918
+ }
2919
+ s.push(l), a.push(o);
2920
+ }
2921
+ return { actions: s, fixed: a, unfixable: i, rejected: r };
2922
+ }
2923
+ async function G(e) {
2924
+ try {
2925
+ return await v(e, "utf-8");
2926
+ } catch {
2927
+ return "";
2928
+ }
2929
+ }
2930
+ function pn(e, t) {
2931
+ var n;
2932
+ return (n = e.split(`
2933
+ `).find((s) => s.includes(`(${t})`))) == null ? void 0 : n.trim();
2934
+ }
2935
+ const er = /^## \[.+?\]\(.*\/compare\/(.+?)\.\.\.(.+?)\)/;
2936
+ function tr(e) {
2937
+ return e.split(`
2938
+ `).map((t) => er.exec(t)).filter((t) => t !== null).map((t) => ({ version: t[2], range: `${t[1]}..${t[2]}` }));
2939
+ }
2940
+ function nr(e, t) {
2941
+ return e.split(`
2942
+ `).find((n) => {
2943
+ const s = n.match(/\/commit\/([0-9a-f]+)\)/i);
2944
+ return s ? s[1].startsWith(t) || t.startsWith(s[1]) : !1;
2945
+ });
2946
+ }
2947
+ const sr = /^[a-z]+\/([a-z]+-\d+)-/;
2948
+ async function ar(e, t) {
2949
+ var a;
2950
+ const n = await k(e, [
2951
+ "for-each-ref",
2952
+ "--format=%(refname)",
2953
+ "refs/heads",
2954
+ "refs/remotes"
2955
+ ]), s = t.toLowerCase();
2956
+ for (const i of n.split(`
2957
+ `)) {
2958
+ const r = i.replace(/^refs\/heads\//, "").replace(/^refs\/remotes\/[^/]+\//, "");
2959
+ if (((a = sr.exec(r)) == null ? void 0 : a[1]) === s) return r;
2960
+ }
2961
+ }
2962
+ async function ir(e, t) {
2963
+ const n = await ar(e, t);
2964
+ if (!n) return [];
2965
+ const s = await Lt(e);
2966
+ return (await k(e, ["log", "--format=%s", `${s}..${n}`])).split(`
2967
+ `).map((i) => i.trim()).filter(Boolean);
2968
+ }
2969
+ async function fn(e, t) {
2970
+ if (!Bs.test(t)) return null;
2971
+ const n = await k(e, ["log", "-1", "--format=%B", t]), s = n ? he(n) : null;
2972
+ if (s) return s;
2973
+ const a = await G(f(e, "CHANGELOG.md")), i = nr(a, t);
2974
+ return i ? he(i) : null;
2975
+ }
2976
+ async function ic(e, t) {
2977
+ const s = (await k(e, ["log", "--format=%H", "--reverse", t])).split(`
2978
+ `).map((r) => r.trim()).filter(Boolean), a = await Promise.all(s.map((r) => fn(e, r))), i = /* @__PURE__ */ new Map();
2979
+ return s.forEach((r, o) => {
2980
+ const c = a[o];
2981
+ if (!c) return;
2982
+ const d = i.get(c);
2983
+ d ? d.push(r) : i.set(c, [r]);
2984
+ }), i;
2985
+ }
2986
+ async function rr(e, t) {
2987
+ const s = (await k(e, ["log", "--format=%H%x1f%s", "--reverse", t])).split(`
2988
+ `).map((a) => a.trim()).filter(Boolean);
2989
+ return Promise.all(
2990
+ s.map(async (a) => {
2991
+ const [i, r = ""] = a.split(""), o = await fn(e, i);
2992
+ return { sha: i, id: o, subject: r };
2993
+ })
2994
+ );
2995
+ }
2996
+ async function rc(e, t) {
2997
+ const [{ entries: n }, s, a, i, r, o] = await Promise.all([
2998
+ ce(D(e, "ideas")),
2999
+ Kt(e),
3000
+ G(Ht(e, "tasks.log")),
3001
+ G(f(e, "CHANGELOG.md")),
3002
+ ir(e, t),
3003
+ Mt(e)
3004
+ ]), c = o.ids, d = n.find((g) => g.id === t), l = s == null ? void 0 : s.get(t), p = $s(a).filter((g) => g.planId === t), h = pn(i, t);
3005
+ return {
3006
+ id: t,
3007
+ idea: d ? {
3008
+ reached: !0,
3009
+ data: {
3010
+ title: d.title,
3011
+ status: oe(d, l, s !== void 0, c.has(t)),
3012
+ type: d.type
3013
+ }
3014
+ } : { reached: !1 },
3015
+ phases: d ? { reached: d.phases.length > 0, data: d.phases } : { reached: !1 },
3016
+ taskRuns: p.length > 0 ? { reached: !0, data: p } : { reached: !1 },
3017
+ commits: r.length > 0 ? { reached: !0, data: r } : { reached: !1 },
3018
+ pr: l ? { reached: !0, data: l } : { reached: !1 },
3019
+ releaseLine: h ? { reached: !0, data: h } : { reached: !1 }
3020
+ };
3021
+ }
3022
+ const V = /^##\s+/, or = /^##\s+The goal\s*$/i, hn = /^##\s+(Horizon\s+\d+\s*[—-].*)\r?$/i, cr = /^##\s+Standing concerns\s*$/i, L = /^-\s+\*\*(.+?)\*\*\s+[—-]\s+(.*)\r?$/, dr = /^\s+-\s+→\s+(.+?)\r?$/, ur = /^([A-Z]+-\d+)/, mn = /^\s+-\s+✓\s+shipped\s+(.+?)\r?$/, Be = /^\s+-\s+(.+?)\r?$/, Ge = /^\s+\S/;
3023
+ function ot(e, t, n) {
3024
+ var i;
3025
+ const s = [];
3026
+ let a = t;
3027
+ for (; a < n; ) {
3028
+ const r = e[a].match(L);
3029
+ if (!r) {
3030
+ a++;
3031
+ continue;
3032
+ }
3033
+ const o = [r[2].trim()], c = [], d = [];
3034
+ let l;
3035
+ for (a++; a < n && !L.test(e[a]); ) {
3036
+ const p = e[a].match(dr);
3037
+ if (p) {
3038
+ const w = p[1].trim();
3039
+ d.push(((i = w.match(ur)) == null ? void 0 : i[1]) ?? w), a++;
3040
+ continue;
3041
+ }
3042
+ const h = e[a].match(mn);
3043
+ if (h) {
3044
+ l = h[1].trim(), a++;
3045
+ continue;
3046
+ }
3047
+ const g = e[a].match(Be);
3048
+ if (g) {
3049
+ c.push(g[1].trim()), a++;
3050
+ continue;
3051
+ }
3052
+ if (!Ge.test(e[a])) break;
3053
+ o.push(e[a].trim()), a++;
3054
+ }
3055
+ s.push({
3056
+ name: r[1].trim(),
3057
+ description: o.join(" "),
3058
+ candidates: c,
3059
+ linked: d,
3060
+ ...l !== void 0 ? { shippedOn: l } : {}
3061
+ });
3062
+ }
3063
+ return s;
3064
+ }
3065
+ function lr(e) {
3066
+ const t = e.split(`
3067
+ `);
3068
+ let n = "";
3069
+ const s = [];
3070
+ let a = [];
3071
+ for (let i = 0; i < t.length; i++) {
3072
+ if (or.test(t[i])) {
3073
+ let o = i + 1;
3074
+ for (; o < t.length && !V.test(t[o]); ) o++;
3075
+ n = t.slice(i + 1, o).join(`
3076
+ `).trim(), i = o - 1;
3077
+ continue;
3078
+ }
3079
+ const r = t[i].match(hn);
3080
+ if (r) {
3081
+ let o = i + 1;
3082
+ for (; o < t.length && !V.test(t[o]); ) o++;
3083
+ let c = i + 1;
3084
+ for (; c < o && !L.test(t[c]); ) c++;
3085
+ const d = t.slice(i + 1, c).join(`
3086
+ `).trim();
3087
+ s.push({
3088
+ title: r[1].trim(),
3089
+ intro: d,
3090
+ items: ot(t, i + 1, o)
3091
+ }), i = o - 1;
3092
+ continue;
3093
+ }
3094
+ if (cr.test(t[i])) {
3095
+ let o = i + 1;
3096
+ for (; o < t.length && !V.test(t[o]); ) o++;
3097
+ a = ot(t, i + 1, o), i = o - 1;
3098
+ }
3099
+ }
3100
+ return { goal: n, horizons: s, standingConcerns: a };
3101
+ }
3102
+ function ne(e, t) {
3103
+ for (let n = 0; n < e.length; n++) {
3104
+ const s = e[n].match(hn);
3105
+ if (!s || s[1].trim() !== t) continue;
3106
+ let a = n + 1;
3107
+ for (; a < e.length && !V.test(e[a]); ) a++;
3108
+ return { start: n + 1, end: a };
3109
+ }
3110
+ }
3111
+ function j(e, t, n) {
3112
+ const s = ne(e, t);
3113
+ if (s)
3114
+ for (let a = s.start; a < s.end; a++) {
3115
+ const i = e[a].match(L);
3116
+ if (!i || i[1].trim() !== n) continue;
3117
+ let r = a + 1;
3118
+ for (; r < s.end && !L.test(e[r]) && Ge.test(e[r]); )
3119
+ r++;
3120
+ return { start: a, end: r };
3121
+ }
3122
+ }
3123
+ function oc(e, t, n, s) {
3124
+ const a = e.split(`
3125
+ `), i = j(a, t, n);
3126
+ if (!i) return e;
3127
+ if (s === void 0)
3128
+ return a.splice(i.start, i.end - i.start), a.join(`
3129
+ `);
3130
+ for (let r = i.start + 1; r < i.end; r++) {
3131
+ const o = a[r].match(Be);
3132
+ if (o && o[1].trim() === s)
3133
+ return a.splice(r, 1), a.join(`
3134
+ `);
3135
+ }
3136
+ return e;
3137
+ }
3138
+ function cc(e, t, n, s) {
3139
+ var d, l;
3140
+ const a = e.split(`
3141
+ `), i = j(a, t, n);
3142
+ if (!i) return e;
3143
+ const r = a[i.start].match(L);
3144
+ if (!r) return e;
3145
+ const o = ((d = s.name) == null ? void 0 : d.trim()) ?? r[1].trim(), c = ((l = s.description) == null ? void 0 : l.trim()) ?? r[2].trim();
3146
+ if (a[i.start] = `- **${o}** — ${c}`, s.description !== void 0)
3147
+ for (let p = i.end - 1; p > i.start; p--)
3148
+ Ge.test(a[p]) && !Be.test(a[p]) && a.splice(p, 1);
3149
+ return a.join(`
3150
+ `);
3151
+ }
3152
+ function dc(e, t, n, s) {
3153
+ if (t === s) return e;
3154
+ const a = e.split(`
3155
+ `), i = j(a, t, n);
3156
+ if (!i || !ne(a, s)) return e;
3157
+ const r = a.splice(i.start, i.end - i.start), o = ne(a, s);
3158
+ if (!o) return e;
3159
+ let c = o.end;
3160
+ for (; c > o.start && a[c - 1].trim() === ""; ) c--;
3161
+ return a.splice(c, 0, ...r), a.join(`
3162
+ `);
3163
+ }
3164
+ function uc(e, t, n, s) {
3165
+ const a = e.split(`
3166
+ `), i = j(a, t, n);
3167
+ if (!i) return e;
3168
+ let r = i.end;
3169
+ for (let o = i.start + 1; o < r; o++)
3170
+ if (mn.test(a[o])) {
3171
+ a.splice(o, 1), r--;
3172
+ break;
3173
+ }
3174
+ return s !== void 0 && a.splice(r, 0, ` - ✓ shipped ${s}`), a.join(`
3175
+ `);
3176
+ }
3177
+ function lc(e, t, n, s) {
3178
+ const a = e.split(`
3179
+ `), i = ne(a, t);
3180
+ if (!i) return e;
3181
+ let r = i.end;
3182
+ for (; r > i.start && a[r - 1].trim() === ""; ) r--;
3183
+ return a.splice(r, 0, `- **${n}** — ${s}`), a.join(`
3184
+ `);
3185
+ }
3186
+ function pc(e, t, n, s) {
3187
+ const a = e.split(`
3188
+ `), i = j(a, t, n);
3189
+ return i ? (a.splice(i.end, 0, ` - ${s}`), a.join(`
3190
+ `)) : e;
3191
+ }
3192
+ function fc(e, t, n, s) {
3193
+ const a = e.split(`
3194
+ `), i = j(a, t, n);
3195
+ return i ? (a.splice(i.end, 0, ` - → ${s}`), a.join(`
3196
+ `)) : e;
3197
+ }
3198
+ function gn(e) {
3199
+ return [
3200
+ ...e.horizons.flatMap((t) => t.items.map((n) => n.name)),
3201
+ ...e.standingConcerns.map((t) => t.name)
3202
+ ];
3203
+ }
3204
+ function pr(e, t, n, s) {
3205
+ const a = /* @__PURE__ */ new Map();
3206
+ for (const i of t.get(e.name) ?? [])
3207
+ i.id && a.set(i.id, i);
3208
+ for (const i of e.linked) {
3209
+ const r = n.get(i);
3210
+ r && a.set(i, r);
3211
+ }
3212
+ return [...a.entries()].map(([i, r]) => ({
3213
+ id: i,
3214
+ title: r.title,
3215
+ status: r.status,
3216
+ pr: r.pr,
3217
+ released: pn(s, i) !== void 0,
3218
+ created: r.created,
3219
+ phases: r.phases
3220
+ }));
3221
+ }
3222
+ function fr(e) {
3223
+ const t = e.filter((s) => s.status !== "dropped"), n = t.filter((s) => s.status === "done").length;
3224
+ return { total: t.length, done: n, open: t.length - n };
3225
+ }
3226
+ function hr(e, t) {
3227
+ return e.shippedOn !== void 0 ? { state: "shipped", readyToShip: !1 } : t.total === 0 ? { state: "not-started", readyToShip: !1 } : { state: "in-progress", readyToShip: t.done === t.total };
3228
+ }
3229
+ function ct(e, t, n, s) {
3230
+ const a = pr(e, t, n, s), i = fr(a), { state: r, readyToShip: o } = hr(e, i);
3231
+ return { ...e, ideas: a, rollup: i, state: r, readyToShip: o };
3232
+ }
3233
+ function hc(e, t, n = "") {
3234
+ const s = new Map(t.filter((d) => d.id).map((d) => [d.id, d])), a = /* @__PURE__ */ new Map();
3235
+ for (const d of t) {
3236
+ if (!d.subject) continue;
3237
+ const l = a.get(d.subject);
3238
+ l ? l.push(d) : a.set(d.subject, [d]);
3239
+ }
3240
+ const i = e.horizons.map((d) => {
3241
+ const l = d.items.map(
3242
+ (h) => ct(h, a, s, n)
3243
+ ), p = l.reduce(
3244
+ (h, g) => ({
3245
+ total: h.total + g.rollup.total,
3246
+ done: h.done + g.rollup.done,
3247
+ open: h.open + g.rollup.open
3248
+ }),
3249
+ { total: 0, done: 0, open: 0 }
3250
+ );
3251
+ return { title: d.title, intro: d.intro, items: l, rollup: p };
3252
+ }), r = e.standingConcerns.map(
3253
+ (d) => ct(d, a, s, n)
3254
+ ), o = new Set(gn(e)), c = t.filter(
3255
+ (d) => !d.subject || !o.has(d.subject)
3256
+ );
3257
+ return { goal: e.goal, horizons: i, standingConcerns: r, unfiled: c };
3258
+ }
3259
+ const Ue = /* @__PURE__ */ new Set([
3260
+ "core",
3261
+ "cli",
3262
+ "app",
3263
+ "server",
3264
+ "agent",
3265
+ "audit",
3266
+ "plans",
3267
+ "ideas",
3268
+ "docs",
3269
+ "settings",
3270
+ "stack",
3271
+ "ui",
3272
+ "ci",
3273
+ "config",
3274
+ "deps",
3275
+ "repo",
3276
+ "release",
3277
+ "main"
3278
+ ]);
3279
+ function mr(e, t) {
3280
+ return (e == null ? void 0 : e.find((n) => Ue.has(n))) ?? t;
3281
+ }
3282
+ function yn(e, t) {
3283
+ return new Promise((n) => {
3284
+ var i, r;
3285
+ const s = S(
3286
+ "gh",
3287
+ ["pr", "view", t, "--json", "title,body,headRefName,labels,isDraft,state,url,comments"],
3288
+ {
3289
+ cwd: e,
3290
+ stdio: ["ignore", "pipe", "pipe"]
3291
+ }
3292
+ );
3293
+ let a = "";
3294
+ (i = s.stdout) == null || i.on("data", (o) => {
3295
+ a += o.toString();
3296
+ }), (r = s.stderr) == null || r.on("data", () => {
3297
+ }), s.on("close", (o) => {
3298
+ if (o !== 0) {
3299
+ n(void 0);
3300
+ return;
3301
+ }
3302
+ try {
3303
+ n(JSON.parse(a));
3304
+ } catch {
3305
+ n(void 0);
3306
+ }
3307
+ }), s.on("error", () => n(void 0));
3308
+ });
3309
+ }
3310
+ const gr = 6e4;
3311
+ function mc(e, t, n = gr) {
3312
+ return new Promise((s) => {
3313
+ var r, o;
3314
+ const a = S("gh", ["pr", "diff", t], { cwd: e, stdio: ["ignore", "pipe", "pipe"] });
3315
+ let i = "";
3316
+ (r = a.stdout) == null || r.on("data", (c) => {
3317
+ i += c.toString();
3318
+ }), (o = a.stderr) == null || o.on("data", () => {
3319
+ }), a.on("close", (c) => {
3320
+ if (c !== 0) {
3321
+ s(void 0);
3322
+ return;
3323
+ }
3324
+ s(
3325
+ i.length > n ? `${i.slice(0, n)}
3326
+ ... (diff truncated, exceeds size cap)` : i
3327
+ );
3328
+ }), a.on("error", () => s(void 0));
3329
+ });
3330
+ }
3331
+ async function yr(e, t) {
3332
+ for (const n of ["ideas", f("ideas", "archive")]) {
3333
+ const s = f(e, "papercamp", n, `${t}.md`);
3334
+ try {
3335
+ return await O(s), s;
3336
+ } catch {
3337
+ }
3338
+ }
3339
+ return null;
3340
+ }
3341
+ async function x(e, t) {
3342
+ const n = await yn(e, t), s = Gt(n == null ? void 0 : n.body, (n == null ? void 0 : n.headRefName) ?? t);
3343
+ if (!s) return;
3344
+ const a = await yr(e, s);
3345
+ if (!a) return;
3346
+ const { entries: i } = xe(await v(a, "utf-8")), r = i[0];
3347
+ if (r)
3348
+ return { id: s, view: n, entry: r };
3349
+ }
3350
+ async function gc(e, t) {
3351
+ const n = await x(e, t);
3352
+ if (!n) return;
3353
+ const { id: s, entry: a } = n;
3354
+ return { id: s, kind: a.type, tags: a.tags, phases: a.phases };
3355
+ }
3356
+ const wn = "<!-- papercamp:phases:start -->", bn = "<!-- papercamp:phases:end -->", dt = new RegExp(`${wn}[\\s\\S]*?${bn}`);
3357
+ function wr(e, t) {
3358
+ const n = t.map((i) => `- [${i.done ? "x" : " "}] ${i.text}`).join(`
3359
+ `), s = `${wn}
3360
+ ### Phases
3361
+ ${n}
3362
+ ${bn}`;
3363
+ if (dt.test(e)) return e.replace(dt, () => s);
3364
+ const a = e.trimEnd();
3365
+ return a.length > 0 ? `${a}
3366
+
3367
+ ${s}` : s;
3368
+ }
3369
+ function br(e, t, n) {
3370
+ return new Promise((s) => {
3371
+ var i, r;
3372
+ const a = S("gh", ["pr", "edit", t, "--body-file", "-"], {
3373
+ cwd: e,
3374
+ stdio: ["pipe", "ignore", "pipe"]
3375
+ });
3376
+ (i = a.stderr) == null || i.on("data", () => {
3377
+ }), a.on("close", (o) => s(o === 0)), a.on("error", () => s(!1)), (r = a.stdin) == null || r.end(n);
3378
+ });
3379
+ }
3380
+ async function yc(e, t) {
3381
+ const n = await x(e, t);
3382
+ if (!(n != null && n.view)) return "unresolved";
3383
+ const { view: s, entry: a } = n, i = wr(s.body, a.phases);
3384
+ return i === s.body ? "unchanged" : await br(e, t, i) ? "updated" : "unresolved";
3385
+ }
3386
+ function vr(e) {
3387
+ const t = /* @__PURE__ */ new Set();
3388
+ e.kind && t.add(e.kind);
3389
+ for (const n of e.tags)
3390
+ Ue.has(n) && t.add(n);
3391
+ return [...t];
3392
+ }
3393
+ function Er(e) {
3394
+ return new Promise((t) => {
3395
+ var a, i;
3396
+ const n = S("gh", ["label", "list", "--limit", "200", "--json", "name"], {
3397
+ cwd: e,
3398
+ stdio: ["ignore", "pipe", "pipe"]
3399
+ });
3400
+ let s = "";
3401
+ (a = n.stdout) == null || a.on("data", (r) => {
3402
+ s += r.toString();
3403
+ }), (i = n.stderr) == null || i.on("data", () => {
3404
+ }), n.on("close", (r) => {
3405
+ if (r !== 0) {
3406
+ t(void 0);
3407
+ return;
3408
+ }
3409
+ try {
3410
+ const o = JSON.parse(s);
3411
+ t(new Set(o.map((c) => c.name)));
3412
+ } catch {
3413
+ t(void 0);
3414
+ }
3415
+ }), n.on("error", () => t(void 0));
3416
+ });
3417
+ }
3418
+ function kr(e, t) {
3419
+ return new Promise((n) => {
3420
+ var a;
3421
+ const s = S("gh", ["label", "create", t, "--color", "ededed"], {
3422
+ cwd: e,
3423
+ stdio: ["ignore", "ignore", "pipe"]
3424
+ });
3425
+ (a = s.stderr) == null || a.on("data", () => {
3426
+ }), s.on("close", (i) => n(i === 0)), s.on("error", () => n(!1));
3427
+ });
3428
+ }
3429
+ function Sr(e, t, n) {
3430
+ return new Promise((s) => {
3431
+ var i;
3432
+ const a = S("gh", ["pr", "edit", t, "--add-label", n.join(",")], {
3433
+ cwd: e,
3434
+ stdio: ["ignore", "ignore", "pipe"]
3435
+ });
3436
+ (i = a.stderr) == null || i.on("data", () => {
3437
+ }), a.on("close", (r) => s(r === 0)), a.on("error", () => s(!1));
3438
+ });
3439
+ }
3440
+ async function wc(e, t) {
3441
+ const n = await x(e, t);
3442
+ if (!(n != null && n.view)) return "unresolved";
3443
+ const { view: s, entry: a } = n, i = vr({ kind: a.type, tags: a.tags }), r = new Set((s.labels ?? []).map((l) => l.name)), o = i.filter((l) => !r.has(l));
3444
+ if (o.length === 0) return "unchanged";
3445
+ const c = await Er(e);
3446
+ if (c) {
3447
+ const l = o.filter((p) => !c.has(p));
3448
+ for (const p of l)
3449
+ await kr(e, p);
3450
+ }
3451
+ return await Sr(e, t, o) ? "updated" : "unresolved";
3452
+ }
3453
+ function vn(e, t) {
3454
+ const n = t.type ?? "feat", s = mr(t.tags, "repo");
3455
+ return `${n}(${s}): ${t.title} (${e})`;
3456
+ }
3457
+ const $r = new RegExp(`^(${Mn.join("|")})\\(([a-z-]+)\\): .+$`);
3458
+ function Ir(e) {
3459
+ const t = e.match($r);
3460
+ return !!t && Ue.has(t[2]);
3461
+ }
3462
+ function Ar(e, t, n) {
3463
+ return new Promise((s) => {
3464
+ var i;
3465
+ const a = S("gh", ["pr", "edit", t, "--title", n], {
3466
+ cwd: e,
3467
+ stdio: ["ignore", "ignore", "pipe"]
3468
+ });
3469
+ (i = a.stderr) == null || i.on("data", () => {
3470
+ }), a.on("close", (r) => s(r === 0)), a.on("error", () => s(!1));
3471
+ });
3472
+ }
3473
+ async function bc(e, t) {
3474
+ const n = await x(e, t);
3475
+ if (!(n != null && n.view)) return "unresolved";
3476
+ const { id: s, view: a, entry: i } = n, r = vn(s, i);
3477
+ return a.title === r ? "unchanged" : await Ar(e, t, r) ? "updated" : "unresolved";
3478
+ }
3479
+ async function vc(e, t) {
3480
+ const n = await yn(e, t);
3481
+ if (!n) return "no-pr";
3482
+ const s = await x(e, t);
3483
+ return s ? n.title === vn(s.id, s.entry) ? "valid" : "invalid" : Ir(n.title) ? "valid" : "invalid";
3484
+ }
3485
+ function Ec(e, t) {
3486
+ return new Promise((n) => {
3487
+ var i;
3488
+ const s = S("gh", ["pr", "merge", t, "--squash"], {
3489
+ cwd: e,
3490
+ stdio: ["ignore", "ignore", "pipe"]
3491
+ });
3492
+ let a = "";
3493
+ (i = s.stderr) == null || i.on("data", (r) => {
3494
+ a += r.toString();
3495
+ }), s.on("close", (r) => {
3496
+ n(r === 0 ? { ok: !0 } : {
3497
+ ok: !1,
3498
+ message: a.trim() || `gh pr merge ${t} --squash exited with code ${r}`
3499
+ });
3500
+ }), s.on("error", (r) => n({ ok: !1, message: r.message }));
3501
+ });
3502
+ }
3503
+ function _r(e, t) {
3504
+ return new Promise((n) => {
3505
+ var a;
3506
+ const s = S("gh", ["pr", "ready", t], {
3507
+ cwd: e,
3508
+ stdio: ["ignore", "ignore", "pipe"]
3509
+ });
3510
+ (a = s.stderr) == null || a.on("data", () => {
3511
+ }), s.on("close", (i) => n(i === 0)), s.on("error", () => n(!1));
3512
+ });
3513
+ }
3514
+ function Rr(e, t) {
3515
+ return new Promise((n) => {
3516
+ var a;
3517
+ const s = S("gh", ["pr", "close", t], {
3518
+ cwd: e,
3519
+ stdio: ["ignore", "ignore", "pipe"]
3520
+ });
3521
+ (a = s.stderr) == null || a.on("data", () => {
3522
+ }), s.on("close", (i) => n(i === 0)), s.on("error", () => n(!1));
3523
+ });
3524
+ }
3525
+ async function kc(e, t) {
3526
+ const n = await x(e, t);
3527
+ if (!(n != null && n.view)) return "unresolved";
3528
+ const { view: s, entry: a } = n;
3529
+ return a.status === "dropped" ? s.state !== "OPEN" ? "unchanged" : await Rr(e, t) ? "closed" : "unresolved" : !(a.phases.length > 0 && a.phases.every((o) => o.done)) || !s.isDraft ? "unchanged" : await _r(e, t) ? "ready" : "unresolved";
3530
+ }
3531
+ const En = "<!-- papercamp:consistency:start -->", Tr = "<!-- papercamp:consistency:end -->";
3532
+ function Pr(e, t) {
3533
+ const n = ["### Paper Camp checks", ""];
3534
+ if (e.length === 0)
3535
+ n.push("No consistency issues found.");
3536
+ else
3537
+ for (const s of e)
3538
+ n.push(`- **${s.kind}** (${s.section}): ${s.message}`);
3539
+ return t && (n.push(""), n.push(
3540
+ t.stale ? `Convergence audit: last run \`${t.audited}\`, plan has changed since — due for a re-audit.` : `Convergence audit: last run \`${t.audited}\`, still current.`
3541
+ )), `${En}
3542
+ ${n.join(`
3543
+ `)}
3544
+ ${Tr}`;
3545
+ }
3546
+ function Nr(e, t, n) {
3547
+ return new Promise((s) => {
3548
+ var i, r;
3549
+ const a = S("gh", ["pr", "comment", t, "--body-file", "-"], {
3550
+ cwd: e,
3551
+ stdio: ["pipe", "ignore", "pipe"]
3552
+ });
3553
+ (i = a.stderr) == null || i.on("data", () => {
3554
+ }), a.on("close", (o) => s(o === 0)), a.on("error", () => s(!1)), (r = a.stdin) == null || r.end(n);
3555
+ });
3556
+ }
3557
+ function xr(e, t, n, s, a) {
3558
+ return new Promise((i) => {
3559
+ var o, c;
3560
+ const r = S(
3561
+ "gh",
3562
+ [
3563
+ "api",
3564
+ `repos/${t}/${n}/issues/comments/${s}`,
3565
+ "-X",
3566
+ "PATCH",
3567
+ "-F",
3568
+ "body=@-"
3569
+ ],
3570
+ { cwd: e, stdio: ["pipe", "ignore", "pipe"] }
3571
+ );
3572
+ (o = r.stderr) == null || o.on("data", () => {
3573
+ }), r.on("close", (d) => i(d === 0)), r.on("error", () => i(!1)), (c = r.stdin) == null || c.end(a);
3574
+ });
3575
+ }
3576
+ async function Sc(e, t) {
3577
+ var w, m;
3578
+ const n = await x(e, t);
3579
+ if (!((w = n == null ? void 0 : n.view) != null && w.url)) return "unresolved";
3580
+ const { view: s, entry: a } = n, [{ entries: i }, r] = await Promise.all([
3581
+ ce(f(e, "papercamp", "ideas")),
3582
+ v(f(e, "ROADMAP.md"), "utf-8").catch(() => "")
3583
+ ]), o = i.filter((y) => y.kind !== "note").map((y) => Pt(y)), c = r ? gn(lr(r)) : [], d = Ss(o, c), l = a.audited && a.auditedHash ? {
3584
+ audited: a.audited,
3585
+ stale: Ea(a) !== a.auditedHash
3586
+ } : void 0, p = Pr(d, l), h = (s.comments ?? []).find((y) => y.body.includes(En));
3587
+ if (h) {
3588
+ if (h.body === p) return "unchanged";
3589
+ const y = J(s.url), E = (m = h.url.match(/issuecomment-(\d+)/)) == null ? void 0 : m[1];
3590
+ return !y || !E ? "unresolved" : await xr(e, y.owner, y.repo, E, p) ? "updated" : "unresolved";
3591
+ }
3592
+ return await Nr(e, t, p) ? "created" : "unresolved";
3593
+ }
3594
+ function $c(e, t, n) {
3595
+ if (!e) return;
3596
+ const s = (t ?? "feat").toLowerCase(), a = n.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, ""), i = a.length <= 40 ? a : a[40] === "-" ? a.slice(0, 40) : a.slice(0, 40).replace(/-[^-]*$/, "");
3597
+ return `${s}/${e.toLowerCase()}-${i}`;
3598
+ }
3599
+ const Cr = 5;
3600
+ function Or() {
3601
+ try {
3602
+ let e = C(jn(import.meta.url));
3603
+ for (let t = 0; t < Cr; t++) {
3604
+ const n = f(e, "package.json");
3605
+ if (ke(n))
3606
+ return JSON.parse(Ee(n, "utf-8")).version ?? "0.0.0";
3607
+ e = C(e);
3608
+ }
3609
+ return "0.0.0";
3610
+ } catch {
3611
+ return "0.0.0";
3612
+ }
3613
+ }
3614
+ const Ic = Or();
3615
+ class Dr extends Error {
3616
+ constructor(t) {
3617
+ super(`Paper Camp is already initialized in ${t} (papercamp/config.json exists).`);
3618
+ }
3619
+ }
3620
+ async function I(e) {
3621
+ try {
3622
+ return await Tn(e), !0;
3623
+ } catch {
3624
+ return !1;
3625
+ }
3626
+ }
3627
+ const Lr = ["suggestions.md"];
3628
+ async function Ac(e, t) {
3629
+ const n = f(e, "papercamp"), s = f(n, "config.json");
3630
+ if (await I(s))
3631
+ throw new Dr(e);
3632
+ const a = {
3633
+ version: T,
3634
+ projectName: t.projectName,
3635
+ initializedAt: (/* @__PURE__ */ new Date()).toISOString(),
3636
+ nextId: { idea: 2 }
3637
+ };
3638
+ hs.parse(a), await $(n, { recursive: !0 });
3639
+ const i = f(n, "ideas");
3640
+ await $(i, { recursive: !0 });
3641
+ const r = f(i, "archive");
3642
+ await $(r, { recursive: !0 });
3643
+ const o = f(i, "IDEA-1.md");
3644
+ if (!await I(o)) {
3645
+ const c = je({
3646
+ id: "IDEA-1",
3647
+ title: "Capture your first idea",
3648
+ status: "idea",
3649
+ created: ue(),
3650
+ body: `This is an example idea, in the format Paper Camp expects: a short title, a status, and a paragraph of context. Read it, then archive or delete it once you've written your own.
3651
+
3652
+ Use the New idea button to capture what you're building next, or ask an agent for suggestions.`
3653
+ });
3654
+ await b(o, `${c}
3655
+ `, "utf-8");
3656
+ }
3657
+ for (const c of Lr) {
3658
+ const d = f(n, c);
3659
+ await I(d) || await b(d, "", "utf-8");
3660
+ }
3661
+ await b(s, `${JSON.stringify(a, null, 2)}
3662
+ `, "utf-8"), await jr(n), await Fr(e), await Mr(e, t.projectName);
3663
+ }
3664
+ async function Mr(e, t) {
3665
+ const n = ie(), s = await ae(n), { registry: a, created: i } = xn(s, e, t);
3666
+ i && await Cn(n, a);
3667
+ }
3668
+ const _c = [
3669
+ "papercamp/.pairing.json",
3670
+ "papercamp/.task-logs/",
3671
+ "papercamp/tasks.log",
3672
+ "papercamp/notifications.log",
3673
+ "papercamp/pr-reviews.json",
3674
+ "papercamp/pr-map.json",
3675
+ "papercamp/night.json",
3676
+ "papercamp/run-order.md"
3677
+ ];
3678
+ async function jr(e) {
3679
+ const t = f(e, ".local");
3680
+ await $(t, { recursive: !0 });
3681
+ const n = f(t, ".gitignore");
3682
+ await I(n) || await b(n, `*
3683
+ `, "utf-8");
3684
+ }
3685
+ async function Fr(e) {
3686
+ const t = f(e, ".claude", "skills", "paper-camp");
3687
+ await $(t, { recursive: !0 });
3688
+ const n = f(t, "SKILL.md");
3689
+ await I(n) || await b(n, Ri, "utf-8");
3690
+ const s = f(e, ".claude");
3691
+ await $(s, { recursive: !0 });
3692
+ const a = f(s, "settings.json");
3693
+ if (await I(a)) {
3694
+ const o = await v(a, "utf-8"), c = ki(o, e);
3695
+ c !== o && await b(a, c, "utf-8");
3696
+ } else
3697
+ await b(a, Si(e), "utf-8");
3698
+ const i = f(e, ".mcp.json");
3699
+ if (await I(i)) {
3700
+ const o = await v(i, "utf-8"), c = Ii(o);
3701
+ c !== o && await b(i, c, "utf-8");
3702
+ } else
3703
+ await b(i, $i(), "utf-8");
3704
+ const r = f(e, "opencode.json");
3705
+ if (await I(r)) {
3706
+ const o = await v(r, "utf-8"), c = _i(o);
3707
+ c !== o && await b(r, c, "utf-8");
3708
+ } else
3709
+ await b(r, Ai(), "utf-8");
3710
+ }
3711
+ async function ut(e, t, n) {
3712
+ if (await I(e)) {
3713
+ const s = await v(e, "utf-8");
3714
+ if (!s.startsWith(n) || s === t) return;
3715
+ }
3716
+ await $(C(e), { recursive: !0 }), await b(e, t, "utf-8");
3717
+ }
3718
+ async function Rc(e) {
3719
+ await ut(
3720
+ f(e, ".github", "workflows", "draft-pr.yml"),
3721
+ un,
3722
+ cn
3723
+ ), await ut(
3724
+ f(e, ".github", "pull_request_template.md"),
3725
+ ln,
3726
+ dn
3727
+ );
3728
+ }
3729
+ const U = {
3730
+ feat: "Features",
3731
+ fix: "Bug Fixes",
3732
+ refactor: "Code Refactoring",
3733
+ docs: "Documentation",
3734
+ chore: "Miscellaneous"
3735
+ }, Hr = [U.feat, U.fix];
3736
+ async function Br(e, t) {
3737
+ const [n, s] = t.split("..");
3738
+ return (await k(e, ["rev-parse", "--verify", "--quiet", `refs/tags/${s}`])).trim() === "" ? `${n}..HEAD` : t;
3739
+ }
3740
+ function Gr(e) {
3741
+ const { prefix: t } = Ce(e), n = t.split("(", 1)[0].toLowerCase();
3742
+ return n in U ? n : "chore";
3743
+ }
3744
+ function lt(e, t, n) {
3745
+ const s = e.get(t) ?? [];
3746
+ s.push(n), e.set(t, s);
3747
+ }
3748
+ async function Ur(e, t) {
3749
+ const n = await G(f(e, "CHANGELOG.md")), s = tr(n).find((c) => c.version === t);
3750
+ if (!s) return null;
3751
+ const [{ entries: a }, i] = await Promise.all([
3752
+ ce(f(e, "papercamp", "ideas")),
3753
+ Br(e, s.range).then((c) => rr(e, c))
3754
+ ]);
3755
+ if (i.length === 0) return null;
3756
+ const r = /* @__PURE__ */ new Map(), o = /* @__PURE__ */ new Set();
3757
+ for (const c of i)
3758
+ if (c.id) {
3759
+ if (o.has(c.id)) continue;
3760
+ const d = a.find((l) => l.id === c.id);
3761
+ if (!d) continue;
3762
+ o.add(c.id), lt(r, U[d.type ?? "chore"], {
3763
+ id: d.id,
3764
+ title: d.title
3765
+ });
3766
+ } else {
3767
+ const d = U[Gr(c.subject)];
3768
+ lt(r, d, { title: Ce(c.subject).subject });
3769
+ }
3770
+ return Hr.filter((c) => r.has(c)).map((c) => ({
3771
+ label: c,
3772
+ ideas: r.get(c)
3773
+ }));
3774
+ }
3775
+ function Yr(e) {
3776
+ return e.id ? `- **${e.title}** (${e.id})` : `- ${e.title}`;
3777
+ }
3778
+ function qr(e, t) {
3779
+ const n = [`## ${e}`];
3780
+ for (const s of t) {
3781
+ n.push("", `### ${s.label}`, "");
3782
+ for (const a of s.ideas)
3783
+ n.push(Yr(a));
3784
+ }
3785
+ return n.join(`
3786
+ `);
3787
+ }
3788
+ const Jr = /^## \[.+?\]\(.*\/compare\/(.+?)\.\.\.(.+?)\)/;
3789
+ async function Tc(e, t) {
3790
+ const n = f(e, "CHANGELOG.md"), a = (await G(n)).split(`
3791
+ `), i = a.findIndex(
3792
+ (h) => {
3793
+ var g;
3794
+ return ((g = Jr.exec(h)) == null ? void 0 : g[2]) === t;
3795
+ }
3796
+ );
3797
+ if (i === -1) return null;
3798
+ const r = await Ur(e, t);
3799
+ if (!r) return null;
3800
+ let o = a.findIndex((h, g) => g > i && /^## /.test(h));
3801
+ o === -1 && (o = a.length);
3802
+ const c = a[i], d = qr(t, r).split(`
3803
+ `).slice(1), l = a.slice(o), p = [...a.slice(0, i), c, ...d, "", ...l].join(`
3804
+ `);
3805
+ return await b(n, p), [c, ...d].join(`
3806
+ `);
3807
+ }
3808
+ const pt = "needs a decision: ";
3809
+ function zr(e) {
3810
+ const t = e.lastIndexOf(pt);
3811
+ return t === -1 ? { question: e } : {
3812
+ phaseLine: e.slice(0, t).replace(/[—-]\s*$/, "").trim() || void 0,
3813
+ question: e.slice(t + pt.length).trim()
3814
+ };
3815
+ }
3816
+ function Kr(e) {
3817
+ return Math.floor((Date.now() - Date.parse(e)) / 864e5);
3818
+ }
3819
+ const kn = (e, t) => e === "question" && (t ?? "open") === "open", Sn = (e) => !!e.archived || e.status === "done" || e.status === "dropped";
3820
+ function Wr(e) {
3821
+ return e.filter((t) => !Sn(t)).flatMap(
3822
+ (t) => (t.thread ?? []).filter((n) => kn(n.kind, n.state)).map((n) => ({
3823
+ entityId: t.id,
3824
+ entityTitle: t.title,
3825
+ text: zr(n.text).question,
3826
+ date: n.date,
3827
+ ageDays: n.date ? Kr(n.date) : Number.POSITIVE_INFINITY
3828
+ }))
3829
+ ).sort((t, n) => n.ageDays - t.ageDays);
3830
+ }
3831
+ function Pc(e) {
3832
+ return Wr(e);
3833
+ }
3834
+ function Nc(e, t) {
3835
+ const n = new Set(t.filter(Sn).map((s) => s.id));
3836
+ return e.map((s) => kn(s.kind, s.state) && s.entityId && n.has(s.entityId) ? { ...s, state: "resolved" } : s);
3837
+ }
3838
+ const be = 0.95, $n = 3e4, ve = {
3839
+ implement: { from: 0, to: 0.6 },
3840
+ verify: { from: 0.6, to: 0.9 },
3841
+ checkbox: { from: 0.95, to: 0.95 }
3842
+ }, ft = {
3843
+ implement: 0,
3844
+ verify: 1,
3845
+ checkbox: 2
3846
+ };
3847
+ function ht(e, ...t) {
3848
+ for (const n of t)
3849
+ if (typeof (e == null ? void 0 : e[n]) == "string") return e[n];
3850
+ return "";
3851
+ }
3852
+ function xc(e, t) {
3853
+ const n = e.toLowerCase();
3854
+ if (n === "bash")
3855
+ return (typeof (t == null ? void 0 : t.command) == "string" ? t.command : "").includes("check-types") ? "verify" : null;
3856
+ if (n === "edit" || n === "multiedit" || n === "write") {
3857
+ const s = ht(t, "old_string", "oldString"), a = ht(t, "new_string", "newString");
3858
+ return s.includes("- [ ]") && a.includes("- [x]") ? "checkbox" : "implement";
3859
+ }
3860
+ return null;
3861
+ }
3862
+ function Cc(e, t) {
3863
+ return e ? ft[t] > ft[e] ? t : e : t;
3864
+ }
3865
+ function Oc(e, t) {
3866
+ return e === void 0 ? !1 : t - e >= $n;
3867
+ }
3868
+ function Vr(e) {
3869
+ const t = [...e].sort((s, a) => s - a), n = Math.floor(t.length / 2);
3870
+ return t.length % 2 === 0 ? (t[n - 1] + t[n]) / 2 : t[n];
3871
+ }
3872
+ function Dc(e) {
3873
+ const t = e.filter((a) => a.taskKind === "phase" && a.outcome === "done").map(
3874
+ (a) => (a.endedAt ? Date.parse(a.endedAt) : Number.NaN) - Date.parse(a.startedAt)
3875
+ ).filter((a) => Number.isFinite(a) && a > 0);
3876
+ if (t.length === 0) return {};
3877
+ const n = Vr(t), s = {};
3878
+ for (const a of ["implement", "verify"]) {
3879
+ const i = ve[a].to - ve[a].from;
3880
+ s[a] = n * i / be;
3881
+ }
3882
+ return s;
3883
+ }
3884
+ function Lc({
3885
+ anchor: e,
3886
+ anchorEnteredAt: t,
3887
+ lastStreamAt: n,
3888
+ now: s,
3889
+ medians: a
3890
+ }) {
3891
+ if (!e) return 0;
3892
+ const i = ve[e], r = Math.min(i.from, be), o = a == null ? void 0 : a[e];
3893
+ if (!o || o <= 0 || t === void 0 || i.to === i.from)
3894
+ return r;
3895
+ const c = n === void 0 ? s : Math.min(s, n + $n), d = Math.min(1, Math.max(0, c - t) / o);
3896
+ return Math.min(i.from + d * (i.to - i.from), be);
3897
+ }
3898
+ export {
3899
+ rn as $,
3900
+ Dr as A,
3901
+ $i as B,
3902
+ Ue as C,
3903
+ _c as D,
3904
+ Ai as E,
3905
+ on as F,
3906
+ Ei as G,
3907
+ qo as H,
3908
+ xc as I,
3909
+ Go as J,
3910
+ zi as K,
3911
+ Wr as L,
3912
+ Ke as M,
3913
+ Yo as N,
3914
+ Ea as O,
3915
+ Ic as P,
3916
+ vn as Q,
3917
+ jo as R,
3918
+ Ri as S,
3919
+ R as T,
3920
+ vr as U,
3921
+ gn as V,
3922
+ Ro as W,
3923
+ os as X,
3924
+ cs as Y,
3925
+ At as Z,
3926
+ rs as _,
3927
+ Bs as a,
3928
+ oc as a$,
3929
+ Fo as a0,
3930
+ Ni as a1,
3931
+ jr as a2,
3932
+ Zo as a3,
3933
+ Bo as a4,
3934
+ is as a5,
3935
+ It as a6,
3936
+ Ds as a7,
3937
+ Pt as a8,
3938
+ mc as a9,
3939
+ ds as aA,
3940
+ ps as aB,
3941
+ Jo as aC,
3942
+ hs as aD,
3943
+ xe as aE,
3944
+ ko as aF,
3945
+ Ne as aG,
3946
+ yo as aH,
3947
+ wo as aI,
3948
+ ma as aJ,
3949
+ bo as aK,
3950
+ go as aL,
3951
+ mo as aM,
3952
+ ws as aN,
3953
+ lr as aO,
3954
+ vo as aP,
3955
+ Ho as aQ,
3956
+ Lc as aR,
3957
+ ac as aS,
3958
+ ns as aT,
3959
+ ss as aU,
3960
+ fo as aV,
3961
+ ho as aW,
3962
+ ce as aX,
3963
+ G as aY,
3964
+ Pc as aZ,
3965
+ $s as a_,
3966
+ Do as aa,
3967
+ Ss as ab,
3968
+ pn as ac,
3969
+ Z as ad,
3970
+ je as ae,
3971
+ Vo as af,
3972
+ Ko as ag,
3973
+ Wo as ah,
3974
+ qr as ai,
3975
+ ha as aj,
3976
+ Uo as ak,
3977
+ hi as al,
3978
+ as as am,
3979
+ Ac as an,
3980
+ De as ao,
3981
+ Ir as ap,
3982
+ Oc as aq,
3983
+ fc as ar,
3984
+ Eo as as,
3985
+ Dc as at,
3986
+ ki as au,
3987
+ Ii as av,
3988
+ _i as aw,
3989
+ dc as ax,
3990
+ ls as ay,
3991
+ us as az,
3992
+ Gi as b,
3993
+ uo as b$,
3994
+ Xo as b0,
3995
+ Pr as b1,
3996
+ wr as b2,
3997
+ Mo as b3,
3998
+ To as b4,
3999
+ Nc as b5,
4000
+ fn as b6,
4001
+ rc as b7,
4002
+ he as b8,
4003
+ ic as b9,
4004
+ cc as bA,
4005
+ vc as bB,
4006
+ fs as bC,
4007
+ Tc as bD,
4008
+ kt as bE,
4009
+ Cs as bF,
4010
+ Ia as bG,
4011
+ Ht as bH,
4012
+ Ao as bI,
4013
+ D as bJ,
4014
+ Ft as bK,
4015
+ ea as bL,
4016
+ Oo as bM,
4017
+ Co as bN,
4018
+ $a as bO,
4019
+ Vt as bP,
4020
+ tc as bQ,
4021
+ Na as bR,
4022
+ B as bS,
4023
+ St as bT,
4024
+ ka as bU,
4025
+ k as bV,
4026
+ Tt as bW,
4027
+ Ta as bX,
4028
+ nc as bY,
4029
+ po as bZ,
4030
+ lo as b_,
4031
+ Le as ba,
4032
+ gc as bb,
4033
+ mr as bc,
4034
+ Kt as bd,
4035
+ rr as be,
4036
+ Ur as bf,
4037
+ tr as bg,
4038
+ Lo as bh,
4039
+ hc as bi,
4040
+ sc as bj,
4041
+ Ki as bk,
4042
+ Fr as bl,
4043
+ Rc as bm,
4044
+ ra as bn,
4045
+ Me as bo,
4046
+ uc as bp,
4047
+ xt as bq,
4048
+ Ec as br,
4049
+ ms as bs,
4050
+ Sc as bt,
4051
+ yc as bu,
4052
+ wc as bv,
4053
+ kc as bw,
4054
+ bc as bx,
4055
+ ue as by,
4056
+ va as bz,
4057
+ Qi as c,
4058
+ xs as c0,
4059
+ Io as c1,
4060
+ Ps as c2,
4061
+ $o as c3,
4062
+ No as c4,
4063
+ xo as c5,
4064
+ _o as c6,
4065
+ me as c7,
4066
+ ec as c8,
4067
+ Aa as c9,
4068
+ Qn as ca,
4069
+ Po as cb,
4070
+ js as d,
4071
+ Fs as e,
4072
+ te as f,
4073
+ un as g,
4074
+ cn as h,
4075
+ be as i,
4076
+ $n as j,
4077
+ ln as k,
4078
+ dn as l,
4079
+ _t as m,
4080
+ pc as n,
4081
+ lc as o,
4082
+ Cc as p,
4083
+ _ as q,
4084
+ zo as r,
4085
+ So as s,
4086
+ ba as t,
4087
+ wa as u,
4088
+ Wt as v,
4089
+ Qo as w,
4090
+ ga as x,
4091
+ $c as y,
4092
+ Si as z
4093
+ };
4094
+ //# sourceMappingURL=phase-progress.BCqVUFLp.js.map