compounding-loop 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -3
- package/dist/cli/claim.js +84 -0
- package/dist/cli/claim.js.map +1 -0
- package/dist/cli/index.js +166 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/laneRound.js +96 -0
- package/dist/cli/laneRound.js.map +1 -0
- package/dist/cli/laneStatus.js +3 -1
- package/dist/cli/laneStatus.js.map +1 -1
- package/dist/cli/lock.js +22 -2
- package/dist/cli/lock.js.map +1 -1
- package/dist/cli/loops.js +7 -0
- package/dist/cli/loops.js.map +1 -1
- package/dist/cli/next.js +18 -0
- package/dist/cli/next.js.map +1 -0
- package/dist/cli/pause.js +81 -0
- package/dist/cli/pause.js.map +1 -0
- package/dist/cli/run.js +18 -6
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/runs.js +83 -0
- package/dist/cli/runs.js.map +1 -0
- package/dist/cli/shipCheck.js +59 -0
- package/dist/cli/shipCheck.js.map +1 -0
- package/dist/core/laneRound.js +63 -0
- package/dist/core/laneRound.js.map +1 -0
- package/dist/core/lanes.js +12 -2
- package/dist/core/lanes.js.map +1 -1
- package/dist/core/next.js +34 -0
- package/dist/core/next.js.map +1 -0
- package/dist/core/parse/changelog.js +41 -0
- package/dist/core/parse/changelog.js.map +1 -0
- package/dist/core/parse/claim.js +24 -0
- package/dist/core/parse/claim.js.map +1 -0
- package/dist/core/parse/controlRoom.js +38 -0
- package/dist/core/parse/controlRoom.js.map +1 -0
- package/dist/core/parse/index.js +12 -0
- package/dist/core/parse/index.js.map +1 -0
- package/dist/core/parse/markers.js +20 -0
- package/dist/core/parse/markers.js.map +1 -0
- package/dist/core/parse/paused.js +26 -0
- package/dist/core/parse/paused.js.map +1 -0
- package/dist/core/parse/runs.js +132 -0
- package/dist/core/parse/runs.js.map +1 -0
- package/dist/core/parse/task.js +96 -0
- package/dist/core/parse/task.js.map +1 -0
- package/dist/core/repo.js +2 -7
- package/dist/core/repo.js.map +1 -1
- package/dist/core/runPrompt.js +8 -2
- package/dist/core/runPrompt.js.map +1 -1
- package/dist/core/shipCheck.js +45 -0
- package/dist/core/shipCheck.js.map +1 -0
- package/dist/core/table.js +3 -1
- package/dist/core/table.js.map +1 -1
- package/dist/server/checkLabels.js +27 -0
- package/dist/server/checkLabels.js.map +1 -0
- package/dist/server/controls.js +72 -0
- package/dist/server/controls.js.map +1 -0
- package/dist/server/data.js +9 -3
- package/dist/server/data.js.map +1 -1
- package/dist/server/health.js +6 -27
- package/dist/server/health.js.map +1 -1
- package/dist/server/index.js +29 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/laneChecks.js +25 -0
- package/dist/server/laneChecks.js.map +1 -0
- package/dist/server/live/build.js +163 -0
- package/dist/server/live/build.js.map +1 -0
- package/dist/server/live/github.js +54 -0
- package/dist/server/live/github.js.map +1 -0
- package/dist/server/live/index.js +9 -0
- package/dist/server/live/index.js.map +1 -0
- package/dist/server/live/merge.js +16 -0
- package/dist/server/live/merge.js.map +1 -0
- package/dist/server/live/urls.js +59 -0
- package/dist/server/live/urls.js.map +1 -0
- package/dist/server/loops.js +142 -38
- package/dist/server/loops.js.map +1 -1
- package/dist/server/roundState.js +27 -0
- package/dist/server/roundState.js.map +1 -0
- package/dist/server/runs.js +81 -0
- package/dist/server/runs.js.map +1 -0
- package/dist/server/static-export.js +22 -1
- package/dist/server/static-export.js.map +1 -1
- package/dist/server/taskBullets.js +33 -0
- package/dist/server/taskBullets.js.map +1 -0
- package/dist/server/this-repo.json +1288 -85
- package/dist/web/assets/index-BOPytDNU.js +53 -0
- package/dist/web/assets/index-BSHEI7fa.css +2 -0
- package/dist/web/index.html +2 -2
- package/method/COMPOUNDING_LOOP.md +21 -0
- package/method/retro.md +7 -0
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/commands/loop-lanes.md +11 -0
- package/plugin/commands/loop-next.md +11 -0
- package/plugin/commands/loop-pause.md +10 -0
- package/runners/github-actions/lanes.yml +54 -1
- package/runners/local/README.md +3 -1
- package/runners/local/compounding-loop.service +20 -0
- package/runners/local/compounding-loop.timer +11 -0
- package/runners/routine/README.md +1 -1
- package/runners/routine/control-room-prompt.md +2 -2
- package/runners/routine/lane-prompt.md +3 -3
- package/runners/routine/prompt.md +2 -2
- package/templates/lanes/claude-section.md +4 -1
- package/templates/lanes/task.md +1 -1
- package/templates/static-site/.ai/loop-prompt.md +1 -1
- package/templates/static-site/CLAUDE.md +2 -0
- package/dist/web/assets/index-B16QCSnY.css +0 -2
- package/dist/web/assets/index-CS82DKbF.js +0 -49
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// Pure (no Node imports): turns the per-lane run logs, the cron slots and the commits into the Runs tab's data.
|
|
2
|
+
import { expectedSlots, matchSlots, missedInARow, parseRunLog } from '../core/parse/index.js';
|
|
3
|
+
/** How far back the Runs tab looks. */
|
|
4
|
+
export const RUNS_WINDOW_HOURS = 48;
|
|
5
|
+
/** A slot this young may still be running (the log line is written at the end), so it is not "missing" yet. */
|
|
6
|
+
export const SLOT_GRACE_MINUTES = 90;
|
|
7
|
+
/** Missing runs in a row that raise the "lanes are not running" attention item. */
|
|
8
|
+
export const SILENT_STREAK = 3;
|
|
9
|
+
const HOUR = 3_600_000;
|
|
10
|
+
const MINUTE = 60_000;
|
|
11
|
+
function laneRuns(lane, input) {
|
|
12
|
+
const from = new Date(input.now.getTime() - RUNS_WINDOW_HOURS * HOUR);
|
|
13
|
+
const everLogged = parseRunLog(input.logs[lane.name] ?? '');
|
|
14
|
+
const entries = everLogged.filter((e) => new Date(e.at) > from);
|
|
15
|
+
const own = input.commits.filter((c) => c.lane === lane.name);
|
|
16
|
+
const slots = lane.cron ? expectedSlots(lane.cron, from, input.now) : [];
|
|
17
|
+
const next = lane.cron ? expectedSlots(lane.cron, input.now, new Date(input.now.getTime() + 7 * 24 * HOUR))[0]?.toISOString() : undefined;
|
|
18
|
+
const results = slots.length > 0 ? matchSlots(slots, entries, own.map((c) => new Date(c.at))) : [];
|
|
19
|
+
// Entries that answered no slot (a manual run, or a lane without cron) are runs too.
|
|
20
|
+
const claimed = new Set(results.flatMap((r) => (r.entry ? [r.entry.at] : [])));
|
|
21
|
+
const manual = entries.filter((e) => !claimed.has(e.at)).map((entry) => ({ slot: new Date(entry.at).toISOString(), entry, outcome: outcomeOf(entry) }));
|
|
22
|
+
const graceEnd = input.now.getTime() - SLOT_GRACE_MINUTES * MINUTE;
|
|
23
|
+
const pausedFrom = input.paused ? (input.paused.at?.getTime() ?? -Infinity) : Infinity;
|
|
24
|
+
const settled = results.filter((r) => r.outcome !== 'no-run-recorded' || (new Date(r.slot).getTime() <= graceEnd && new Date(r.slot).getTime() < pausedFrom));
|
|
25
|
+
const all = [...settled, ...manual].sort((a, b) => a.slot.localeCompare(b.slot));
|
|
26
|
+
const loggedSessions = new Set(entries.flatMap((e) => (e.session ? [sessionKey(e.session)] : [])));
|
|
27
|
+
const runs = all.map((r, i) => {
|
|
28
|
+
const windowStart = new Date(r.slot).getTime();
|
|
29
|
+
const windowEnd = all[i + 1] ? new Date(all[i + 1].slot).getTime() : Infinity;
|
|
30
|
+
const session = r.entry?.session === undefined ? undefined : sessionKey(r.entry.session);
|
|
31
|
+
const inWindow = (c) => at(c) >= windowStart && at(c) < windowEnd;
|
|
32
|
+
// A commit of this run's session belongs to it; a commit of another logged session belongs to that run; the rest go by time.
|
|
33
|
+
const belongs = (c) => (c.session !== undefined && c.session === session) || (inWindow(c) && !(c.session !== undefined && loggedSessions.has(c.session)));
|
|
34
|
+
const commits = own.filter(belongs);
|
|
35
|
+
return {
|
|
36
|
+
lane: lane.name,
|
|
37
|
+
at: new Date(r.slot).toISOString(),
|
|
38
|
+
outcome: r.outcome,
|
|
39
|
+
...(r.entry && { endedAt: new Date(r.entry.at).toISOString() }),
|
|
40
|
+
units: r.entry?.units ?? [],
|
|
41
|
+
...(r.entry?.tests && { tests: r.entry.tests }),
|
|
42
|
+
commits,
|
|
43
|
+
...(r.entry?.note && { note: r.entry.note }),
|
|
44
|
+
};
|
|
45
|
+
});
|
|
46
|
+
return { runs, ...(next && { next }), // A lane that never logged a run or made a commit is new, not silent.
|
|
47
|
+
missed: !lane.stopped && !input.paused && (everLogged.length > 0 || own.length > 0) ? missedInARow(settled) : 0 };
|
|
48
|
+
}
|
|
49
|
+
const at = (c) => new Date(c.at).getTime();
|
|
50
|
+
const outcomeOf = (e) => (e.tests && e.tests.failed > 0 ? 'tests-failed' : e.pushed ? 'pushed' : 'nothing-pushed');
|
|
51
|
+
/** The last 8 characters of a session id or `Claude-Session:` URL, the key commits and run lines share. */
|
|
52
|
+
export function sessionKey(idOrUrl) {
|
|
53
|
+
return idOrUrl.trim().replace(/\/+$/, '').slice(-8);
|
|
54
|
+
}
|
|
55
|
+
export function buildRuns(input) {
|
|
56
|
+
const perLane = input.lanes.map((lane) => ({ lane, ...laneRuns(lane, input) }));
|
|
57
|
+
return {
|
|
58
|
+
runs: perLane.flatMap((l) => l.runs).sort((a, b) => b.at.localeCompare(a.at)),
|
|
59
|
+
nextRuns: perLane.flatMap((l) => (l.next ? [{ lane: l.lane.name, at: l.next }] : [])),
|
|
60
|
+
silentLanes: perLane.filter((l) => l.missed >= SILENT_STREAK).map((l) => ({ lane: l.lane.name, missed: l.missed })),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/** The attention item for lanes that stopped running, or none. */
|
|
64
|
+
export function silentLanesItem(loopId, silent, since) {
|
|
65
|
+
if (silent.length === 0)
|
|
66
|
+
return undefined;
|
|
67
|
+
return {
|
|
68
|
+
loopId,
|
|
69
|
+
file: '.ai/lanes.json',
|
|
70
|
+
kind: 'lanes-not-running',
|
|
71
|
+
question: `Lanes are not running: ${silent.map((s) => `${s.lane} (${s.missed} missed)`).join(', ')}. Check your plan's usage limit.`,
|
|
72
|
+
bestGuess: 'Wait for the limit to reset, or run a lane by hand',
|
|
73
|
+
since,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/** The newest run of any lane, for the fleet card. Missing runs count: they are what the owner needs to see. */
|
|
77
|
+
export function lastRunOf(runs) {
|
|
78
|
+
const newest = runs[0];
|
|
79
|
+
return newest && { lane: newest.lane, at: newest.at, outcome: newest.outcome };
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=runs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runs.js","sourceRoot":"","sources":["../../src/server/runs.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,24 @@
|
|
|
1
1
|
import { mkdir, writeFile } from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { DEFAULT_RUNNER } from './settings.js';
|
|
4
|
+
import { withLabel } from './checkLabels.js';
|
|
3
5
|
import { sortByAttention, toSummary } from './data.js';
|
|
6
|
+
import { CSP } from './static.js';
|
|
7
|
+
/** The only two hosts the static site may fetch from, besides itself: the live data path (src/server/live). */
|
|
8
|
+
export const LIVE_CONNECT_HOSTS = ['https://api.github.com', 'https://raw.githubusercontent.com'];
|
|
9
|
+
/** The local server's CSP with `connect-src` widened to the live hosts. Everything else stays identical. */
|
|
10
|
+
export const STATIC_CSP = CSP.replace("connect-src 'self'", `connect-src 'self' ${LIVE_CONNECT_HOSTS.join(' ')}`);
|
|
11
|
+
export const SELF_LIVE_CONFIG = { repo: 'Minntooos/Compounding-Loop', loop: 'compounding-loop', pages: 'https://minntooos.github.io/Compounding-Loop/' };
|
|
12
|
+
/**
|
|
13
|
+
* GitHub Pages cannot set headers, so the CSP goes into a meta tag as the first child of `<head>`.
|
|
14
|
+
* (`frame-ancestors` is ignored in a meta tag; Pages sends its own framing rules.)
|
|
15
|
+
*/
|
|
16
|
+
export function staticIndexHtml(html) {
|
|
17
|
+
const meta = `<meta http-equiv="Content-Security-Policy" content="${STATIC_CSP.replace(/; frame-ancestors [^;]+/, '')}">`;
|
|
18
|
+
if (!/<head[^>]*>/i.test(html))
|
|
19
|
+
throw new Error('index.html has no <head>');
|
|
20
|
+
return html.replace(/<head[^>]*>/i, (head) => `${head}\n ${meta}`);
|
|
21
|
+
}
|
|
4
22
|
/**
|
|
5
23
|
* The read-only API as plain files, for hosting the demo on GitHub Pages (no server there).
|
|
6
24
|
* Keys are paths relative to the site root. The web app in static mode fetches these relative URLs.
|
|
@@ -12,7 +30,10 @@ export function staticApiFiles(snapshot, version) {
|
|
|
12
30
|
for (const loop of snapshot.loops)
|
|
13
31
|
files.set(`api/loops/${loop.id}.json`, loop);
|
|
14
32
|
files.set('api/inbox.json', snapshot.inbox);
|
|
15
|
-
files.set('api/checks.json', snapshot.checks);
|
|
33
|
+
files.set('api/checks.json', snapshot.checks.map(withLabel));
|
|
34
|
+
// Same body as the demo server's /api/settings; without the file the static site logged a 404 on every visit.
|
|
35
|
+
files.set('api/settings.json', { demo: true, defaultRunner: DEFAULT_RUNNER });
|
|
36
|
+
files.set('live.json', SELF_LIVE_CONFIG);
|
|
16
37
|
return files;
|
|
17
38
|
}
|
|
18
39
|
export async function writeStaticApi(outDir, snapshot, version) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"static-export.js","sourceRoot":"","sources":["../../src/server/static-export.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"static-export.js","sourceRoot":"","sources":["../../src/server/static-export.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/** Bullet lines ("- x") directly under the first heading or bold label that matches `label`. */
|
|
2
|
+
export function bulletsUnder(markdown, label) {
|
|
3
|
+
const lines = markdown.split(/\r?\n/);
|
|
4
|
+
const start = lines.findIndex((l) => label.test(l.replace(/^#+\s*|\*\*/g, '').trim()));
|
|
5
|
+
if (start === -1)
|
|
6
|
+
return [];
|
|
7
|
+
const bullets = [];
|
|
8
|
+
for (const line of lines.slice(start + 1)) {
|
|
9
|
+
if (/^#+\s/.test(line))
|
|
10
|
+
break;
|
|
11
|
+
const bullet = /^\s*[-*]\s+(.*\S)\s*$/.exec(line);
|
|
12
|
+
if (bullet?.[1])
|
|
13
|
+
bullets.push(bullet[1]);
|
|
14
|
+
else if (bullets.length > 0 && line.trim() === '')
|
|
15
|
+
break;
|
|
16
|
+
}
|
|
17
|
+
return bullets;
|
|
18
|
+
}
|
|
19
|
+
/** The sentence of a `**Done when:** a; b; c.` line, split into its items (lane task files write the contract inline). */
|
|
20
|
+
function inlineDoneWhen(markdown) {
|
|
21
|
+
const text = /^\*\*done when:?\*\*:?\s*(.+?)\s*$/im.exec(markdown)?.[1];
|
|
22
|
+
return text === undefined ? [] : text.split(/;\s+/).map((item) => item.replace(/\.$/, '').trim()).filter(Boolean);
|
|
23
|
+
}
|
|
24
|
+
/** The done-when items of a task.md (bullets, else the inline sentence); an item only counts as passing once the loop wrote DONE.md. */
|
|
25
|
+
export function contractFromTask(taskText, finished) {
|
|
26
|
+
const bullets = bulletsUnder(taskText, /^done when/i);
|
|
27
|
+
return (bullets.length > 0 ? bullets : inlineDoneWhen(taskText)).map((text) => ({ text, pass: finished }));
|
|
28
|
+
}
|
|
29
|
+
/** One lane's contract, decisions and knowledge from its task.md. */
|
|
30
|
+
export function laneNotesFromTask(lane, taskText, finished) {
|
|
31
|
+
return { lane, contract: contractFromTask(taskText, finished), decisions: bulletsUnder(taskText, /^decisions/i), knowledge: bulletsUnder(taskText, /^confirmed/i) };
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=taskBullets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"taskBullets.js","sourceRoot":"","sources":["../../src/server/taskBullets.ts"],"names":[],"mappings":"AAGA,gGAAgG;AAChG,MAAM,UAAU,YAAY,CAAC,QAAgB,EAAE,KAAa;IAC1D,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACtC,MAAM,KAAK,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IACvF,IAAI,KAAK,KAAK,CAAC,CAAC;QAAE,OAAO,EAAE,CAAC;IAC5B,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,EAAE,CAAC;QAC1C,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,MAAM;QAC9B,MAAM,MAAM,GAAG,uBAAuB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClD,IAAI,MAAM,EAAE,CAAC,CAAC,CAAC;YAAE,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;aACpC,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE;YAAE,MAAM;IAC3D,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,0HAA0H;AAC1H,SAAS,cAAc,CAAC,QAAgB;IACtC,MAAM,IAAI,GAAG,sCAAsC,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACxE,OAAO,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;AACpH,CAAC;AAED,wIAAwI;AACxI,MAAM,UAAU,gBAAgB,CAAC,QAAgB,EAAE,QAAiB;IAClE,MAAM,OAAO,GAAG,YAAY,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAC;IACtD,OAAO,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC;AAC7G,CAAC;AAED,qEAAqE;AACrE,MAAM,UAAU,iBAAiB,CAAC,IAAY,EAAE,QAAgB,EAAE,QAAiB;IACjF,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,CAAC,EAAE,SAAS,EAAE,YAAY,CAAC,QAAQ,EAAE,aAAa,CAAC,EAAE,SAAS,EAAE,YAAY,CAAC,QAAQ,EAAE,aAAa,CAAC,EAAE,CAAC;AACtK,CAAC"}
|