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