svamp-cli 0.2.342 → 0.2.356

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/bin/skills/artifact/SKILL.md +19 -5
  2. package/bin/skills/crew/SKILL.md +62 -13
  3. package/dist/DefaultTransport-vfp78fug.mjs +47 -0
  4. package/dist/GeminiTransport-3OkhtFRO.mjs +130 -0
  5. package/dist/KimiTransport-D8n009Tr.mjs +141 -0
  6. package/dist/acpAgentConfig-CObOPlA8.mjs +48 -0
  7. package/dist/acpBackend-UaBD4zL3.mjs +925 -0
  8. package/dist/{adminCommands-CRTBDZan.mjs → adminCommands-BvsYnRDA.mjs} +2 -18
  9. package/dist/{agentCommands-BuO988tJ.mjs → agentCommands-D91RcjqQ.mjs} +6 -23
  10. package/dist/api-BHjyC0Ph.mjs +157 -0
  11. package/dist/{auth--WS0wezs.mjs → auth-CRz8TsEl.mjs} +1 -22
  12. package/dist/bootStatus-DqtNgJch.mjs +143 -0
  13. package/dist/claudeAuth-C0wc1GTM.mjs +291 -0
  14. package/dist/cli.mjs +2948 -5
  15. package/dist/codexAppServerBackend-VGsYtbqg.mjs +786 -0
  16. package/dist/codexProvider-BtxYc5Rj.mjs +68 -0
  17. package/dist/{commands-DDuGBSsz.mjs → commands-B-yczgz_.mjs} +115 -49
  18. package/dist/{commands-DU4B-4Gh.mjs → commands-BMeDrpSv.mjs} +55 -25
  19. package/dist/{commands-DXLOKJOj.mjs → commands-BbmypSgx.mjs} +62 -27
  20. package/dist/{commands-C0HQiu-S.mjs → commands-BkdbelFo.mjs} +78 -38
  21. package/dist/{commands-DXM17M6n.mjs → commands-C13-yE2R.mjs} +42 -19
  22. package/dist/{commands-BKb1g8Gq.mjs → commands-CoOc4qiw.mjs} +20 -30
  23. package/dist/{commands-BsKgZ5sQ.mjs → commands-DW5RnHZw.mjs} +2 -19
  24. package/dist/{commands-Cwxy-C5N.mjs → commands-sPvRcG3J.mjs} +10 -32
  25. package/dist/cron-hHGdb5kU.mjs +132 -0
  26. package/dist/detectIsolation-CrV8IFDv.mjs +339 -0
  27. package/dist/envFlag-COOuJ3AH.mjs +11 -0
  28. package/dist/fileLock-BMPwCrB6.mjs +80 -0
  29. package/dist/{fleet-BHzlAvnu.mjs → fleet-Ca-UjJNL.mjs} +122 -36
  30. package/dist/flushExit-C-i6SGjj.mjs +37 -0
  31. package/dist/friendlyName-1UZcyDhu.mjs +586 -0
  32. package/dist/{headlessCli-C2gvOpOt.mjs → headlessCli-C8HzQvis.mjs} +3 -21
  33. package/dist/hookSettings-FFc1pi8j.mjs +10546 -0
  34. package/dist/{httpServer-Be1Hcddr.mjs → httpServer-BzEDbOGn.mjs} +14 -5
  35. package/dist/hyphaClient-ybivOjZo.mjs +49 -0
  36. package/dist/index.mjs +40 -8
  37. package/dist/instance-CeHz5C8v.mjs +203 -0
  38. package/dist/kimiProvider-DbxODebR.mjs +69 -0
  39. package/dist/kimiSetup-NGsoYB5h.mjs +48 -0
  40. package/dist/loopVerify-DALtTk2V.mjs +444 -0
  41. package/dist/machineTools-DygM1jXW.mjs +580 -0
  42. package/dist/model-CUHojegn.mjs +66 -0
  43. package/dist/{notifyCommands-0aZUwKQK.mjs → notifyCommands-CCUE18Q-.mjs} +3 -18
  44. package/dist/package-BrsiNutS.mjs +64 -0
  45. package/dist/pinnedCodex-XW4qvOAe.mjs +11 -0
  46. package/dist/pinnedNono-5ZHb6328.mjs +15 -0
  47. package/dist/{rpc-rAQQ1q8t.mjs → rpc-CBkSkzsI.mjs} +38 -6
  48. package/dist/{rpc-B2Lc2BAB.mjs → rpc-ccY7xvlo.mjs} +33 -22
  49. package/dist/{run-C7QVfyw4.mjs → run-DCq3aEiF.mjs} +17 -9
  50. package/dist/run-DDmeV9K3.mjs +9826 -0
  51. package/dist/sandboxDetect-Cdzsj5TC.mjs +19 -0
  52. package/dist/{scheduler-CzF4lOe_.mjs → scheduler-BkVJ27Ki.mjs} +38 -6
  53. package/dist/securityContext-Cm5UTCuD.mjs +218 -0
  54. package/dist/serveAuth-4M0vwMyj.mjs +460 -0
  55. package/dist/{serveCommands-EIEqdaVu.mjs → serveCommands-CLhkccJM.mjs} +13 -34
  56. package/dist/sharingNotificationSync-qGIQ-3yf.mjs +359 -0
  57. package/dist/{sideband-BoutiRJi.mjs → sideband-68HhGT0i.mjs} +1 -21
  58. package/dist/store-B4E6NzG6.mjs +356 -0
  59. package/dist/store-fi0f-ff1.mjs +184 -0
  60. package/dist/types-57J3Re0n.mjs +17 -0
  61. package/dist/wantsHelp-6bMOeNUz.mjs +5 -0
  62. package/dist/wrapCommand-CdNawSaa.mjs +117 -0
  63. package/package.json +3 -3
  64. package/dist/cli-B24CmIli.mjs +0 -2780
  65. package/dist/package-CTbGYaJG.mjs +0 -64
  66. package/dist/run-CF4kAVPW.mjs +0 -26508
  67. package/dist/sandboxDetect-DNTcbgWD.mjs +0 -12
@@ -0,0 +1,356 @@
1
+ import { existsSync, readFileSync, statSync, mkdirSync, writeFileSync, readdirSync, unlinkSync, renameSync } from 'node:fs';
2
+ import { join, dirname } from 'node:path';
3
+ import { randomBytes } from 'node:crypto';
4
+ import { execFileSync, execSync, exec } from 'node:child_process';
5
+ import { w as withFileLock } from './fileLock-BMPwCrB6.mjs';
6
+
7
+ function normalizeStatus(s) {
8
+ if (s === "backlog") return "ready";
9
+ if (s === "done") return "archived";
10
+ return s === "ready" || s === "in_progress" || s === "paused" || s === "archived" ? s : "ready";
11
+ }
12
+ const ISSUE_STATUSES = ["ready", "in_progress", "paused", "archived"];
13
+ const ISSUE_SCOPES = ["session", "project"];
14
+ function parseStatusArg(s) {
15
+ return ISSUE_STATUSES.includes(s) ? s : null;
16
+ }
17
+ function parseScopeArg(s) {
18
+ return ISSUE_SCOPES.includes(s) ? s : null;
19
+ }
20
+ const FIELD_ORDER = ["id", "title", "status", "scope", "labels", "verify", "disposition", "triaged", "branch", "session", "owner", "original", "created", "closed", "question", "answer"];
21
+ function resolveProjectRoot(start = process.cwd()) {
22
+ let dir = start;
23
+ for (let i = 0; i < 40; i++) {
24
+ if (existsSync(join(dir, ".git")) || existsSync(join(dir, ".svamp"))) return dir;
25
+ const parent = dirname(dir);
26
+ if (parent === dir) break;
27
+ dir = parent;
28
+ }
29
+ return start;
30
+ }
31
+ function issuesDir(projectRoot) {
32
+ return join(projectRoot, ".svamp", "issues");
33
+ }
34
+ function archiveDir(projectRoot) {
35
+ return join(issuesDir(projectRoot), "archive");
36
+ }
37
+ function issuePath(projectRoot, id, archived = false) {
38
+ return join(archived ? archiveDir(projectRoot) : issuesDir(projectRoot), `${id}.md`);
39
+ }
40
+ function serializeIssue(issue) {
41
+ const lines = ["---"];
42
+ for (const k of FIELD_ORDER) {
43
+ const v = issue[k];
44
+ if (v === void 0) continue;
45
+ lines.push(`${k}: ${JSON.stringify(v)}`);
46
+ }
47
+ lines.push("---", "");
48
+ return lines.join("\n") + (issue.body ? issue.body.replace(/\s+$/, "") + "\n" : "");
49
+ }
50
+ function parseIssue(content) {
51
+ const m = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
52
+ if (!m) return null;
53
+ const fm = {};
54
+ for (const line of m[1].split("\n")) {
55
+ const idx = line.indexOf(": ");
56
+ if (idx === -1) continue;
57
+ const key = line.slice(0, idx).trim();
58
+ const raw = line.slice(idx + 2).trim();
59
+ try {
60
+ fm[key] = JSON.parse(raw);
61
+ } catch {
62
+ fm[key] = raw;
63
+ }
64
+ }
65
+ if (!fm.id) return null;
66
+ return {
67
+ id: String(fm.id),
68
+ title: String(fm.title ?? ""),
69
+ status: normalizeStatus(fm.status),
70
+ scope: fm.scope ?? "session",
71
+ labels: Array.isArray(fm.labels) ? fm.labels.map(String) : [],
72
+ verify: fm.verify ?? null,
73
+ disposition: fm.disposition === "crew" ? "crew" : "inline",
74
+ triaged: fm.triaged === true,
75
+ branch: fm.branch ?? null,
76
+ session: fm.session ?? null,
77
+ owner: fm.owner ?? null,
78
+ original: fm.original ?? null,
79
+ created: String(fm.created ?? (/* @__PURE__ */ new Date()).toISOString()),
80
+ closed: fm.closed ?? null,
81
+ question: fm.question && typeof fm.question === "object" ? fm.question : null,
82
+ answer: typeof fm.answer === "string" && fm.answer.trim() ? fm.answer : null,
83
+ body: (m[2] || "").trim() || void 0
84
+ };
85
+ }
86
+ function readDirWithMtime(dir) {
87
+ if (!existsSync(dir)) return [];
88
+ const out = [];
89
+ for (const name of readdirSync(dir)) {
90
+ if (!name.endsWith(".md")) continue;
91
+ try {
92
+ const p = join(dir, name);
93
+ const issue = parseIssue(readFileSync(p, "utf-8"));
94
+ if (issue) out.push({ issue, mtimeMs: statSync(p).mtimeMs });
95
+ } catch {
96
+ }
97
+ }
98
+ return out;
99
+ }
100
+ function listIssues(projectRoot, opts = {}) {
101
+ const entries = readDirWithMtime(issuesDir(projectRoot));
102
+ if (opts.includeArchived) entries.push(...readDirWithMtime(archiveDir(projectRoot)));
103
+ const byId = /* @__PURE__ */ new Map();
104
+ for (const e of entries) {
105
+ const prev = byId.get(e.issue.id);
106
+ if (!prev || e.mtimeMs >= prev.mtimeMs) byId.set(e.issue.id, e);
107
+ }
108
+ let items = Array.from(byId.values()).map((e) => e.issue);
109
+ if (opts.status) items = items.filter((i) => i.status === opts.status);
110
+ if (opts.label) items = items.filter((i) => i.labels.includes(opts.label));
111
+ if (opts.scope) items = items.filter((i) => i.scope === opts.scope);
112
+ return items.sort((a, b) => Number(a.id) - Number(b.id));
113
+ }
114
+ function isPendingIssue(issue) {
115
+ return issue.status !== "archived" && issue.status !== "paused";
116
+ }
117
+ function applyTriageStatus(projectRoot, id, patch, fail) {
118
+ const current = getIssue(projectRoot, id);
119
+ if (current?.status === "archived") {
120
+ fail(`#${id} is archived (closed). Triage will not reopen it \u2014 use \`svamp issue reopen ${id}\` if that is what you mean.`);
121
+ return;
122
+ }
123
+ if (current?.status === "paused" && current.question) patch.question = null;
124
+ patch.status = "ready";
125
+ }
126
+ function isVisibleTo(issue, session) {
127
+ if (issue.scope === "session") return issue.session === session || issue.owner === session;
128
+ if (issue.owner === session) return true;
129
+ if (issue.session === session) return true;
130
+ if (!issue.owner && !issue.session) return true;
131
+ return false;
132
+ }
133
+ function normalizeIssueRef(id) {
134
+ const s = String(id ?? "").trim().replace(/^#+/, "");
135
+ return /^\d+$/.test(s) ? s.padStart(4, "0") : s;
136
+ }
137
+ function getIssue(projectRoot, id) {
138
+ const padded = normalizeIssueRef(id);
139
+ let best = null;
140
+ for (const p of [issuePath(projectRoot, padded), issuePath(projectRoot, padded, true)]) {
141
+ if (!existsSync(p)) continue;
142
+ try {
143
+ const issue = parseIssue(readFileSync(p, "utf-8"));
144
+ if (!issue) continue;
145
+ const mtimeMs = statSync(p).mtimeMs;
146
+ if (!best || mtimeMs >= best.mtimeMs) best = { issue, mtimeMs };
147
+ } catch {
148
+ }
149
+ }
150
+ if (best) return best.issue;
151
+ try {
152
+ for (const issue of listIssues(projectRoot, { includeArchived: true })) {
153
+ if (normalizeIssueRef(issue.id) === padded) return issue;
154
+ }
155
+ } catch {
156
+ }
157
+ return null;
158
+ }
159
+ function atomicWrite(path, content) {
160
+ mkdirSync(dirname(path), { recursive: true });
161
+ const tmp = `${path}.tmp-${process.pid}-${randomBytes(4).toString("hex")}`;
162
+ writeFileSync(tmp, content);
163
+ renameSync(tmp, path);
164
+ }
165
+ function nextId(projectRoot) {
166
+ const all = listIssues(projectRoot, { includeArchived: true });
167
+ const max = all.reduce((acc, i) => Math.max(acc, Number(i.id) || 0), 0);
168
+ return String(max + 1).padStart(4, "0");
169
+ }
170
+ function addIssue(projectRoot, fields) {
171
+ const issue = {
172
+ id: nextId(projectRoot),
173
+ title: fields.title.trim(),
174
+ status: fields.status ?? "ready",
175
+ scope: fields.scope ?? "session",
176
+ labels: fields.labels ?? [],
177
+ verify: fields.verify ?? null,
178
+ disposition: fields.disposition ?? "inline",
179
+ triaged: fields.triaged ?? false,
180
+ branch: null,
181
+ session: fields.session ?? null,
182
+ owner: fields.owner ?? null,
183
+ original: fields.original ?? null,
184
+ created: (/* @__PURE__ */ new Date()).toISOString(),
185
+ closed: null,
186
+ body: fields.body
187
+ };
188
+ mkdirSync(dirname(issuePath(projectRoot, issue.id)), { recursive: true });
189
+ let n = Number(issue.id);
190
+ for (let attempt = 0; attempt < 1e4; attempt++) {
191
+ issue.id = String(n).padStart(4, "0");
192
+ try {
193
+ writeFileSync(issuePath(projectRoot, issue.id), serializeIssue(issue), { flag: "wx" });
194
+ return issue;
195
+ } catch (err) {
196
+ if (err && err.code === "EEXIST") {
197
+ n++;
198
+ continue;
199
+ }
200
+ throw err;
201
+ }
202
+ }
203
+ throw new Error("addIssue: could not allocate a free issue id");
204
+ }
205
+ function issueLockPath(projectRoot, id) {
206
+ return join(issuesDir(projectRoot), `${normalizeIssueRef(id)}.lock`);
207
+ }
208
+ const ISSUE_LOCK_DEADLINE_MS = 3e3;
209
+ function withIssueLock(projectRoot, id, fn) {
210
+ try {
211
+ mkdirSync(issuesDir(projectRoot), { recursive: true });
212
+ } catch {
213
+ }
214
+ return withFileLock(issueLockPath(projectRoot, id), fn, { deadlineMs: ISSUE_LOCK_DEADLINE_MS });
215
+ }
216
+ function _updateIssueUnlocked(projectRoot, id, patch) {
217
+ const cur = getIssue(projectRoot, id);
218
+ if (!cur) return null;
219
+ const wasArchived = cur.status === "archived";
220
+ const canonicalId = normalizeIssueRef(cur.id);
221
+ const next = { ...cur, ...patch, id: canonicalId };
222
+ if (patch.status) next.status = normalizeStatus(patch.status);
223
+ const nowArchived = next.status === "archived";
224
+ if (nowArchived && !next.closed) next.closed = (/* @__PURE__ */ new Date()).toISOString();
225
+ if (!nowArchived) next.closed = null;
226
+ atomicWrite(issuePath(projectRoot, next.id, nowArchived), serializeIssue(next));
227
+ for (const stale of [
228
+ issuePath(projectRoot, cur.id, wasArchived),
229
+ issuePath(projectRoot, cur.id, nowArchived)
230
+ ]) {
231
+ if (stale === issuePath(projectRoot, next.id, nowArchived)) continue;
232
+ try {
233
+ if (existsSync(stale)) unlinkSync(stale);
234
+ } catch {
235
+ }
236
+ }
237
+ return next;
238
+ }
239
+ function updateIssue(projectRoot, id, patch) {
240
+ return withIssueLock(projectRoot, id, () => _updateIssueUnlocked(projectRoot, id, patch));
241
+ }
242
+ function addComment(projectRoot, id, text) {
243
+ if (!text.trim()) return getIssue(projectRoot, id);
244
+ return withIssueLock(projectRoot, id, () => {
245
+ const cur = getIssue(projectRoot, id);
246
+ if (!cur) return null;
247
+ const entry = `
248
+
249
+ ---
250
+ **Follow-up \xB7 ${(/* @__PURE__ */ new Date()).toISOString()}**
251
+
252
+ ${text.trim()}`;
253
+ const body = (cur.body ? cur.body.replace(/\s+$/, "") : "") + entry;
254
+ return _updateIssueUnlocked(projectRoot, id, { body });
255
+ });
256
+ }
257
+ function pauseIssue(projectRoot, id, question, blocked) {
258
+ const cur = getIssue(projectRoot, id);
259
+ if (!cur) return null;
260
+ const q = question?.text ? { text: question.text.trim(), options: (question.options || []).map((o) => String(o).trim()).filter(Boolean), askedAt: (/* @__PURE__ */ new Date()).toISOString() } : null;
261
+ const why = (blocked || "").trim();
262
+ if (q) {
263
+ const optLine = q.options && q.options.length ? `
264
+ Options: ${q.options.join(" \xB7 ")}` : "";
265
+ const blockLine = why ? `
266
+ Blocked: ${why}` : "";
267
+ addComment(projectRoot, id, `\u23F8 Paused \u2014 needs your input: ${q.text}${optLine}${blockLine}`);
268
+ } else if (why) {
269
+ addComment(projectRoot, id, `\u23F8 Paused \u2014 BLOCKED: ${why}`);
270
+ } else {
271
+ addComment(projectRoot, id, "\u23F8 Paused \u2014 waiting on your input.");
272
+ }
273
+ return updateIssue(projectRoot, id, { status: "paused", question: q, answer: null });
274
+ }
275
+ function resumeIssue(projectRoot, id, answer) {
276
+ const cur = getIssue(projectRoot, id);
277
+ if (!cur) return null;
278
+ const a = answer && answer.trim() ? answer.trim() : "";
279
+ if (a) addComment(projectRoot, id, `\u25B6 Resumed \u2014 your answer: ${a}`);
280
+ else addComment(projectRoot, id, "\u25B6 Resumed.");
281
+ return updateIssue(projectRoot, id, { status: "in_progress", question: null, answer: a || null });
282
+ }
283
+ function routeCrewVerdict(projectRoot, childSessionId, v) {
284
+ const linked = listIssues(projectRoot, { includeArchived: true }).find((i) => i.disposition === "crew" && i.session === childSessionId);
285
+ if (!linked) return null;
286
+ const meta = [v.round != null ? `round ${v.round}` : "", v.judge ? `judge:${v.judge}` : ""].filter(Boolean).join(", ");
287
+ addComment(projectRoot, linked.id, `Crew child ${childSessionId.slice(0, 8)} \u2192 ${v.verdict}${meta ? ` (${meta})` : ""}.${v.guidance ? " " + v.guidance : ""}`);
288
+ return v.verdict === "approved" ? updateIssue(projectRoot, linked.id, { status: "archived" }) ?? null : getIssue(projectRoot, linked.id);
289
+ }
290
+ function resolveCrewIssueOnMerge(projectRoot, childSessionId, detail = {}) {
291
+ const linked = listIssues(projectRoot, { includeArchived: true }).find((i) => i.disposition === "crew" && i.session === childSessionId);
292
+ if (!linked) return null;
293
+ if (linked.status === "archived") return linked;
294
+ const where = detail.branch && detail.base ? ` ${detail.branch} \u2192 ${detail.base}` : "";
295
+ addComment(projectRoot, linked.id, `Merged${where} by the lead; crew child ${childSessionId.slice(0, 8)} archived. Closing.`);
296
+ return updateIssue(projectRoot, linked.id, { status: "archived" }) ?? null;
297
+ }
298
+ function searchIssues(projectRoot, query) {
299
+ const q = query.trim();
300
+ if (!q) return [];
301
+ try {
302
+ const out = execFileSync("rg", ["-l", "-i", "-F", "--no-messages", "-e", q, "--", issuesDir(projectRoot)], { encoding: "utf-8" });
303
+ const files = out.split("\n").filter(Boolean);
304
+ const issues = [];
305
+ for (const f of files) {
306
+ try {
307
+ const i = parseIssue(readFileSync(f, "utf-8"));
308
+ if (i) issues.push(i);
309
+ } catch {
310
+ }
311
+ }
312
+ return issues.sort((a, b) => Number(a.id) - Number(b.id));
313
+ } catch (e) {
314
+ if (e?.status === 1) return [];
315
+ return searchIssuesInMemory(projectRoot, q);
316
+ }
317
+ }
318
+ function searchIssuesInMemory(projectRoot, q) {
319
+ const ql = q.toLowerCase();
320
+ return listIssues(projectRoot, { includeArchived: true }).filter((i) => i.title.toLowerCase().includes(ql) || (i.body || "").toLowerCase().includes(ql) || i.labels.some((l) => l.toLowerCase().includes(ql)));
321
+ }
322
+ function verifyGateCommand(issue) {
323
+ return issue?.verify?.type === "command" ? (issue.verify.text || "").trim() : "";
324
+ }
325
+ const VERIFY_GATE_TIMEOUT_MS = 6e5;
326
+ const VERIFY_GATE_MAX_BUFFER = 64 * 1024 * 1024;
327
+ function verifyGateFailure(cmd, stdout, stderr) {
328
+ const output = (String(stdout || "") + String(stderr || "")).split("\n").slice(-15).join("\n").trim();
329
+ return { ok: false, cmd, output };
330
+ }
331
+ function checkVerifyCommand(projectRoot, issue) {
332
+ const cmd = verifyGateCommand(issue);
333
+ if (!cmd) return { ok: true };
334
+ try {
335
+ execSync(cmd, { cwd: projectRoot, stdio: "pipe", timeout: VERIFY_GATE_TIMEOUT_MS, maxBuffer: VERIFY_GATE_MAX_BUFFER });
336
+ return { ok: true, cmd };
337
+ } catch (e) {
338
+ return verifyGateFailure(cmd, e?.stdout, e?.stderr);
339
+ }
340
+ }
341
+ function checkVerifyCommandAsync(projectRoot, issue) {
342
+ const cmd = verifyGateCommand(issue);
343
+ if (!cmd) return Promise.resolve({ ok: true });
344
+ return new Promise((resolve) => {
345
+ exec(cmd, { cwd: projectRoot, timeout: VERIFY_GATE_TIMEOUT_MS, maxBuffer: VERIFY_GATE_MAX_BUFFER }, (err, stdout, stderr) => {
346
+ if (err) resolve(verifyGateFailure(cmd, stdout, stderr));
347
+ else resolve({ ok: true, cmd });
348
+ });
349
+ });
350
+ }
351
+ function summarize(issues) {
352
+ const by = (s) => issues.filter((i) => i.status === s).length;
353
+ return { total: issues.length, ready: by("ready"), in_progress: by("in_progress"), paused: by("paused"), archived: by("archived") };
354
+ }
355
+
356
+ export { ISSUE_SCOPES as I, applyTriageStatus as a, parseStatusArg as b, checkVerifyCommandAsync as c, ISSUE_STATUSES as d, resumeIssue as e, pauseIssue as f, getIssue as g, addComment as h, addIssue as i, isPendingIssue as j, isVisibleTo as k, listIssues as l, checkVerifyCommand as m, summarize as n, resolveCrewIssueOnMerge as o, parseScopeArg as p, routeCrewVerdict as q, resolveProjectRoot as r, searchIssues as s, updateIssue as u };
@@ -0,0 +1,184 @@
1
+ import { existsSync, unlinkSync, readFileSync, readdirSync, statSync, mkdirSync, writeFileSync, renameSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { randomBytes } from 'node:crypto';
4
+ import { parse, stringify } from 'yaml';
5
+
6
+ function isWorkflowEnabled(wf) {
7
+ return wf.enabled !== false;
8
+ }
9
+ function workflowSteps(wf) {
10
+ return Object.values(wf.jobs || {}).flatMap((j) => j?.steps || []);
11
+ }
12
+ function workflowSchedules(wf) {
13
+ return (wf.on?.schedule || []).filter((s) => !!s?.cron);
14
+ }
15
+ function workflowsDir(projectRoot) {
16
+ return join(projectRoot, ".svamp", "workflows");
17
+ }
18
+ function validateWorkflowName(name) {
19
+ if (typeof name !== "string" || !/^[A-Za-z0-9._-]+$/.test(name) || name === "." || name === "..") {
20
+ throw new Error(`Invalid workflow name "${name}" \u2014 allowed: letters, digits, dot, dash, underscore (no path separators).`);
21
+ }
22
+ return name;
23
+ }
24
+ function workflowPath(projectRoot, name) {
25
+ return join(workflowsDir(projectRoot), `${validateWorkflowName(name)}.yaml`);
26
+ }
27
+ function resolveWorkflowPath(projectRoot, name) {
28
+ const canonical = workflowPath(projectRoot, name);
29
+ if (existsSync(canonical)) return canonical;
30
+ const alt = join(workflowsDir(projectRoot), `${validateWorkflowName(name)}.yml`);
31
+ return existsSync(alt) ? alt : canonical;
32
+ }
33
+ function normalizeOn(on) {
34
+ if (!on || typeof on !== "object") {
35
+ return void 0;
36
+ }
37
+ const out = {};
38
+ if (Array.isArray(on.schedule)) {
39
+ const sched = on.schedule.map((s) => {
40
+ const entry = { cron: String(s?.cron ?? s ?? "") };
41
+ if (s && typeof s === "object" && s.tz != null && String(s.tz).trim()) entry.tz = String(s.tz).trim();
42
+ return entry;
43
+ }).filter((s) => s.cron);
44
+ if (sched.length) out.schedule = sched;
45
+ } else if (typeof on.schedule === "string" && on.schedule.trim()) {
46
+ out.schedule = [{ cron: on.schedule.trim() }];
47
+ }
48
+ if ("workflow_dispatch" in on || on.dispatch === true) out.workflow_dispatch = true;
49
+ if (typeof on.channel === "string" && on.channel) out.channel = on.channel;
50
+ if (Array.isArray(on.issue) && on.issue.length) out.issue = on.issue.map(String);
51
+ if ("idle" in on && on.idle !== false || on.idle === true) out.idle = true;
52
+ return Object.keys(out).length ? out : void 0;
53
+ }
54
+ function normalizeJobs(jobs) {
55
+ if (Array.isArray(jobs)) {
56
+ const steps = jobs.map((j) => ({ run: String(j?.run ?? "") })).filter((s) => s.run);
57
+ return steps.length ? { run: { steps } } : {};
58
+ }
59
+ if (jobs && typeof jobs === "object") {
60
+ const out = {};
61
+ for (const [id, job] of Object.entries(jobs)) {
62
+ const rawSteps = Array.isArray(job?.steps) ? job.steps : job?.run ? [{ run: job.run }] : [];
63
+ const steps = rawSteps.map((s) => ({ run: String(s?.run ?? ""), ...s?.name ? { name: String(s.name) } : {} })).filter((s) => s.run);
64
+ if (steps.length) out[String(id)] = { steps };
65
+ }
66
+ return out;
67
+ }
68
+ return {};
69
+ }
70
+ function parseWorkflow(content) {
71
+ try {
72
+ const o = parse(content);
73
+ if (!o || typeof o !== "object" || !o.name) return null;
74
+ return {
75
+ name: String(o.name),
76
+ on: normalizeOn(o.on),
77
+ jobs: normalizeJobs(o.jobs),
78
+ session: o.session ?? null,
79
+ // Only `enabled: false` disables; anything else (absent/true) is active.
80
+ ...o.enabled === false ? { enabled: false } : {}
81
+ };
82
+ } catch {
83
+ return null;
84
+ }
85
+ }
86
+ function serializeWorkflow(wf) {
87
+ const clean = { name: wf.name };
88
+ if (wf.session) clean.session = wf.session;
89
+ if (wf.enabled === false) clean.enabled = false;
90
+ const on = {};
91
+ if (wf.on?.schedule?.length) on.schedule = wf.on.schedule.map((s) => s.tz ? { cron: s.cron, tz: s.tz } : { cron: s.cron });
92
+ if (wf.on?.workflow_dispatch) on.workflow_dispatch = {};
93
+ if (wf.on?.channel) on.channel = wf.on.channel;
94
+ if (wf.on?.issue?.length) on.issue = wf.on.issue;
95
+ if (wf.on?.idle) on.idle = {};
96
+ if (Object.keys(on).length) clean.on = on;
97
+ const jobs = {};
98
+ for (const [id, job] of Object.entries(wf.jobs || {})) {
99
+ jobs[id] = { steps: (job.steps || []).map((s) => s.name ? { name: s.name, run: s.run } : { run: s.run }) };
100
+ }
101
+ clean.jobs = Object.keys(jobs).length ? jobs : { run: { steps: [] } };
102
+ return stringify(clean);
103
+ }
104
+ function listWorkflows(projectRoot) {
105
+ const dir = workflowsDir(projectRoot);
106
+ if (!existsSync(dir)) return [];
107
+ const out = [];
108
+ for (const name of readdirSync(dir)) {
109
+ if (!name.endsWith(".yaml") && !name.endsWith(".yml")) continue;
110
+ const p = join(dir, name);
111
+ try {
112
+ const wf = parseWorkflow(readFileSync(p, "utf-8"));
113
+ if (wf) {
114
+ try {
115
+ const st = statSync(p);
116
+ wf.created = new Date(st.birthtimeMs || st.mtimeMs).toISOString();
117
+ } catch {
118
+ }
119
+ out.push(wf);
120
+ }
121
+ } catch {
122
+ }
123
+ }
124
+ return out.sort((a, b) => a.name.localeCompare(b.name));
125
+ }
126
+ function getWorkflow(projectRoot, name) {
127
+ let p;
128
+ try {
129
+ p = resolveWorkflowPath(projectRoot, name);
130
+ } catch {
131
+ return null;
132
+ }
133
+ if (!existsSync(p)) return null;
134
+ try {
135
+ return parseWorkflow(readFileSync(p, "utf-8"));
136
+ } catch {
137
+ return null;
138
+ }
139
+ }
140
+ function rawWorkflow(projectRoot, name) {
141
+ let p;
142
+ try {
143
+ p = resolveWorkflowPath(projectRoot, name);
144
+ } catch {
145
+ return null;
146
+ }
147
+ return existsSync(p) ? readFileSync(p, "utf-8") : null;
148
+ }
149
+ function saveWorkflow(projectRoot, wf) {
150
+ const dir = workflowsDir(projectRoot);
151
+ mkdirSync(dir, { recursive: true });
152
+ const path = workflowPath(projectRoot, wf.name);
153
+ const tmp = `${path}.tmp-${process.pid}-${randomBytes(4).toString("hex")}`;
154
+ writeFileSync(tmp, serializeWorkflow(wf));
155
+ renameSync(tmp, path);
156
+ const alt = join(dir, `${validateWorkflowName(wf.name)}.yml`);
157
+ if (alt !== path && existsSync(alt)) {
158
+ try {
159
+ unlinkSync(alt);
160
+ } catch {
161
+ }
162
+ }
163
+ }
164
+ function setWorkflowEnabled(projectRoot, name, enabled) {
165
+ const wf = getWorkflow(projectRoot, name);
166
+ if (!wf) return null;
167
+ const next = { ...wf };
168
+ if (enabled) delete next.enabled;
169
+ else next.enabled = false;
170
+ saveWorkflow(projectRoot, next);
171
+ return next;
172
+ }
173
+ function removeWorkflow(projectRoot, name) {
174
+ const p = resolveWorkflowPath(projectRoot, name);
175
+ if (!existsSync(p)) return false;
176
+ try {
177
+ unlinkSync(p);
178
+ return true;
179
+ } catch {
180
+ return false;
181
+ }
182
+ }
183
+
184
+ export { saveWorkflow as a, rawWorkflow as b, workflowSteps as c, workflowsDir as d, getWorkflow as g, isWorkflowEnabled as i, listWorkflows as l, removeWorkflow as r, setWorkflowEnabled as s, validateWorkflowName as v, workflowSchedules as w };
@@ -0,0 +1,17 @@
1
+ const ROLE_HIERARCHY = {
2
+ view: 0,
3
+ interact: 1,
4
+ admin: 2
5
+ };
6
+ function normalizeAllowedUser(input, addedBy) {
7
+ const role = input.role && input.role in ROLE_HIERARCHY ? input.role : "admin";
8
+ return {
9
+ email: input.email.trim().toLowerCase(),
10
+ role,
11
+ addedAt: typeof input.addedAt === "number" ? input.addedAt : Date.now(),
12
+ addedBy: input.addedBy ?? addedBy
13
+ };
14
+ }
15
+ const ISOLATION_PREFERENCE = ["nono", "docker", "podman"];
16
+
17
+ export { ISOLATION_PREFERENCE as I, ROLE_HIERARCHY as R, normalizeAllowedUser as n };
@@ -0,0 +1,5 @@
1
+ function wantsHelp(args) {
2
+ return args.some((a) => a === "--help" || a === "-h");
3
+ }
4
+
5
+ export { wantsHelp as w };
@@ -0,0 +1,117 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import { join } from 'node:path';
4
+
5
+ function expandHome(p) {
6
+ if (p === "~") return homedir();
7
+ if (p.startsWith("~/")) return join(homedir(), p.slice(2));
8
+ return p;
9
+ }
10
+ function wrapWithIsolation(originalCommand, originalArgs, config) {
11
+ switch (config.method) {
12
+ case "nono":
13
+ return wrapWithNono(originalCommand, originalArgs, config);
14
+ case "docker":
15
+ return wrapWithContainer("docker", originalCommand, originalArgs, config);
16
+ case "podman":
17
+ return wrapWithContainer("podman", originalCommand, originalArgs, config);
18
+ default:
19
+ throw new Error(`Unsupported isolation method: ${config.method}`);
20
+ }
21
+ }
22
+ function wrapWithNono(command, args, config) {
23
+ const nonoArgs = ["run"];
24
+ const env = {};
25
+ if (config.nonoConfig?.profilePath) {
26
+ nonoArgs.push("--profile", config.nonoConfig.profilePath);
27
+ } else if (config.nonoConfig?.profile) {
28
+ nonoArgs.push("--profile", config.nonoConfig.profile);
29
+ }
30
+ nonoArgs.push("--workdir", config.workspacePath);
31
+ nonoArgs.push("--allow-cwd");
32
+ if (config.credentialStagingPath) {
33
+ env.HOME = config.credentialStagingPath;
34
+ const realLocalDir = join(homedir(), ".local");
35
+ if (existsSync(realLocalDir)) {
36
+ nonoArgs.push("--read", realLocalDir);
37
+ }
38
+ const realKeychainDir = join(homedir(), "Library", "Keychains");
39
+ if (existsSync(realKeychainDir)) {
40
+ nonoArgs.push("--read", realKeychainDir);
41
+ }
42
+ }
43
+ if (config.nonoConfig?.allowDirs) {
44
+ for (const dir of config.nonoConfig.allowDirs) {
45
+ nonoArgs.push("--allow", expandHome(dir));
46
+ }
47
+ }
48
+ if (config.nonoConfig?.readDirs) {
49
+ for (const dir of config.nonoConfig.readDirs) {
50
+ nonoArgs.push("--read", expandHome(dir));
51
+ }
52
+ }
53
+ if (config.nonoConfig?.allowFiles) {
54
+ for (const file of config.nonoConfig.allowFiles) {
55
+ nonoArgs.push("--allow-file", expandHome(file));
56
+ }
57
+ }
58
+ if (config.nonoConfig?.readFiles) {
59
+ for (const file of config.nonoConfig.readFiles) {
60
+ nonoArgs.push("--read-file", expandHome(file));
61
+ }
62
+ }
63
+ if (config.nonoConfig?.netBlock) {
64
+ nonoArgs.push("--net-block");
65
+ }
66
+ if (config.nonoConfig?.proxyAllow) {
67
+ for (const host of config.nonoConfig.proxyAllow) {
68
+ nonoArgs.push("--proxy-allow", host);
69
+ }
70
+ }
71
+ if (config.nonoConfig?.silent !== false) {
72
+ nonoArgs.push("-s");
73
+ }
74
+ if (config.nonoConfig?.trustOverride) {
75
+ nonoArgs.push("--trust-override");
76
+ }
77
+ nonoArgs.push("--", command, ...args);
78
+ return {
79
+ command: config.binaryPath,
80
+ args: nonoArgs,
81
+ env: Object.keys(env).length > 0 ? env : void 0
82
+ };
83
+ }
84
+ function wrapWithContainer(runtime, command, args, config) {
85
+ const image = config.containerConfig?.image || "node:lts-slim";
86
+ const networkMode = config.containerConfig?.networkMode || "none";
87
+ const containerArgs = [
88
+ "run",
89
+ "--rm",
90
+ "-i",
91
+ // interactive (for stdin/stdout piping)
92
+ `--network=${networkMode}`,
93
+ "-v",
94
+ `${config.workspacePath}:${config.workspacePath}`,
95
+ "-w",
96
+ config.workspacePath
97
+ ];
98
+ if (config.credentialStagingPath) {
99
+ const stagedClaudeDir = join(config.credentialStagingPath, ".claude");
100
+ containerArgs.push("-v", `${stagedClaudeDir}:/root/.claude:ro`);
101
+ const stagedGitconfig = join(config.credentialStagingPath, ".gitconfig");
102
+ containerArgs.push("-v", `${stagedGitconfig}:/root/.gitconfig:ro`);
103
+ containerArgs.push("-e", "HOME=/root");
104
+ }
105
+ if (config.containerConfig?.extraMounts) {
106
+ for (const mount of config.containerConfig.extraMounts) {
107
+ containerArgs.push("-v", mount);
108
+ }
109
+ }
110
+ containerArgs.push(image, command, ...args);
111
+ return {
112
+ command: config.binaryPath,
113
+ args: containerArgs
114
+ };
115
+ }
116
+
117
+ export { wrapWithIsolation as w };