compounding-loop 0.1.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 +40 -6
- package/dist/cli/checkLanes.js +60 -0
- package/dist/cli/checkLanes.js.map +1 -0
- package/dist/cli/claim.js +84 -0
- package/dist/cli/claim.js.map +1 -0
- package/dist/cli/doctor.js +54 -0
- package/dist/cli/doctor.js.map +1 -0
- package/dist/cli/index.js +253 -4
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/init.js +2 -1
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/laneRound.js +96 -0
- package/dist/cli/laneRound.js.map +1 -0
- package/dist/cli/laneStatus.js +54 -0
- package/dist/cli/laneStatus.js.map +1 -0
- package/dist/cli/lanesInit.js +56 -0
- package/dist/cli/lanesInit.js.map +1 -0
- package/dist/cli/lock.js +62 -0
- package/dist/cli/lock.js.map +1 -0
- package/dist/cli/loops.js +30 -3
- package/dist/cli/loops.js.map +1 -1
- package/dist/cli/new.js +5 -5
- package/dist/cli/new.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 +119 -19
- 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/doctor.js +49 -0
- package/dist/core/doctor.js.map +1 -0
- package/dist/core/laneRound.js +63 -0
- package/dist/core/laneRound.js.map +1 -0
- package/dist/core/laneStatus.js +82 -0
- package/dist/core/laneStatus.js.map +1 -0
- package/dist/core/lanes.js +149 -0
- package/dist/core/lanes.js.map +1 -0
- package/dist/core/lanesInit.js +143 -0
- package/dist/core/lanesInit.js.map +1 -0
- 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 +9 -3
- 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 +22 -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 +28 -8
- package/dist/server/data.js.map +1 -1
- package/dist/server/events.js +1 -0
- package/dist/server/events.js.map +1 -1
- package/dist/server/health.js +47 -8
- 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 +146 -39
- 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 +1574 -0
- package/dist/web/assets/index-BOPytDNU.js +53 -0
- package/dist/web/assets/index-BSHEI7fa.css +2 -0
- package/dist/web/index.html +3 -3
- package/method/COMPOUNDING_LOOP.md +55 -0
- package/method/example/K-0002.md +16 -0
- package/method/example/K-0003.md +16 -0
- package/method/retro.md +7 -0
- package/package.json +6 -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 +156 -0
- package/runners/local/README.md +4 -1
- package/runners/local/compounding-loop.service +20 -0
- package/runners/local/compounding-loop.timer +11 -0
- package/runners/local/launchd.plist +36 -0
- package/runners/routine/README.md +38 -1
- package/runners/routine/control-room-prompt.md +16 -0
- package/runners/routine/lane-prompt.md +12 -0
- package/runners/routine/prompt.md +3 -3
- package/templates/chrome-extension/extension/icons/icon128.png +0 -0
- package/templates/chrome-extension/extension/icons/icon16.png +0 -0
- package/templates/chrome-extension/extension/icons/icon48.png +0 -0
- package/templates/chrome-extension/extension/manifest.json +19 -3
- package/templates/chrome-extension/gitignore +1 -1
- package/templates/lanes/README.md +23 -0
- package/templates/lanes/ci-check-lanes.yml +38 -0
- package/templates/lanes/claude-section.md +20 -0
- package/templates/lanes/control-room.md +3 -0
- package/templates/lanes/outbox.md +5 -0
- package/templates/lanes/task.md +36 -0
- package/templates/static-site/.ai/loop-prompt.md +2 -2
- package/templates/static-site/CLAUDE.md +2 -0
- package/templates/static-site/gitignore +1 -1
- package/dist/web/assets/index-BDdtnG33.js +0 -49
- package/dist/web/assets/index-D3-N8DN4.css +0 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"laneRound.js","sourceRoot":"","sources":["../../src/core/laneRound.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// Pure derivation of one lane's state from the files and commits around it.
|
|
2
|
+
import { LOCK_MINUTES } from './status.js';
|
|
3
|
+
/** A lane with neither a commit nor a lock for this long is stalled (same rule as the dashboard's health check). */
|
|
4
|
+
export const STALL_MINUTES = 120;
|
|
5
|
+
/** Rough minutes between runs of a 5-field cron expression; undefined when it cannot be read. */
|
|
6
|
+
export function cronPeriodMinutes(cron) {
|
|
7
|
+
const fields = cron.trim().split(/\s+/);
|
|
8
|
+
if (fields.length !== 5)
|
|
9
|
+
return undefined;
|
|
10
|
+
const [minute, hour, , , weekday] = fields;
|
|
11
|
+
const step = (field) => /^\*\/(\d+)$/.exec(field)?.[1];
|
|
12
|
+
const count = (field) => field.split(',').length;
|
|
13
|
+
const minuteStep = step(minute);
|
|
14
|
+
if (minuteStep)
|
|
15
|
+
return Number(minuteStep) || undefined;
|
|
16
|
+
if (minute === '*')
|
|
17
|
+
return 1;
|
|
18
|
+
const hourStep = step(hour);
|
|
19
|
+
if (hourStep)
|
|
20
|
+
return Number(hourStep) * 60 || undefined;
|
|
21
|
+
if (hour === '*')
|
|
22
|
+
return 60;
|
|
23
|
+
const perDay = 1440 / count(hour);
|
|
24
|
+
return weekday === '*' ? perDay : 10_080 / count(weekday) / count(hour);
|
|
25
|
+
}
|
|
26
|
+
/** A lane is stalled after missing two scheduled runs; `STALL_MINUTES` when it has no readable cron. */
|
|
27
|
+
export function stallMinutesFor(cron) {
|
|
28
|
+
const period = cron === undefined ? undefined : cronPeriodMinutes(cron);
|
|
29
|
+
return period === undefined ? STALL_MINUTES : period * 2;
|
|
30
|
+
}
|
|
31
|
+
const minutesSince = (now, then) => (now.getTime() - then.getTime()) / 60_000;
|
|
32
|
+
export function deriveLaneState(facts, now = new Date()) {
|
|
33
|
+
if (facts.hasBlocked)
|
|
34
|
+
return 'blocked';
|
|
35
|
+
if (facts.hasDone)
|
|
36
|
+
return 'done';
|
|
37
|
+
if (facts.lockAt && minutesSince(now, facts.lockAt) < LOCK_MINUTES)
|
|
38
|
+
return 'building';
|
|
39
|
+
const latest = [facts.lockAt, facts.lastCommitAt].filter((d) => d !== undefined).map((d) => d.getTime());
|
|
40
|
+
if (latest.length > 0 && minutesSince(now, new Date(Math.max(...latest))) > (facts.stallMinutes ?? STALL_MINUTES))
|
|
41
|
+
return 'stalled';
|
|
42
|
+
return 'waiting';
|
|
43
|
+
}
|
|
44
|
+
const OUTBOX_LINE = /^(\d{4}-\d\d-\d\d) (\d\d:\d\d) UTC · to ([^·]+?) · (.+)$/;
|
|
45
|
+
/** Parses `YYYY-MM-DD HH:MM UTC · to <lane|a, b|all> · message` lines; other lines are ignored. */
|
|
46
|
+
export function parseOutbox(from, text) {
|
|
47
|
+
const messages = [];
|
|
48
|
+
for (const line of text.split(/\r?\n/)) {
|
|
49
|
+
const m = OUTBOX_LINE.exec(line.trim());
|
|
50
|
+
if (!m)
|
|
51
|
+
continue;
|
|
52
|
+
const [, date, time, to, body] = m;
|
|
53
|
+
messages.push({ from, to: to.split(/\s*,\s*|\s+and\s+/).map((t) => t.trim().toLowerCase()).filter(Boolean), at: `${date}T${time}:00.000Z`, text: body.trim() });
|
|
54
|
+
}
|
|
55
|
+
return messages;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* A message to a named lane counts as unanswered until that lane writes any later outbox entry.
|
|
59
|
+
* A later commit by that lane (`lastCommitAt`, ISO) also counts as a reply, since a lane often answers by shipping.
|
|
60
|
+
* This is a heuristic, so it only ever informs; it never fails anything.
|
|
61
|
+
*/
|
|
62
|
+
export function summarizeOutboxes(outboxes, lastCommitAt = new Map()) {
|
|
63
|
+
const lastWrite = new Map();
|
|
64
|
+
for (const [lane, messages] of outboxes)
|
|
65
|
+
lastWrite.set(lane, [...messages.map((m) => m.at), lastCommitAt.get(lane) ?? ''].sort().at(-1) ?? '');
|
|
66
|
+
const unanswered = new Map();
|
|
67
|
+
const waitingOn = new Map();
|
|
68
|
+
for (const [from, messages] of outboxes) {
|
|
69
|
+
for (const message of messages) {
|
|
70
|
+
for (const to of new Set(message.to)) {
|
|
71
|
+
if (to === 'all' || to === from || !outboxes.has(to))
|
|
72
|
+
continue;
|
|
73
|
+
if ((lastWrite.get(to) ?? '') >= message.at)
|
|
74
|
+
continue;
|
|
75
|
+
unanswered.set(to, [...(unanswered.get(to) ?? []), { from, to, at: message.at, text: message.text }]);
|
|
76
|
+
waitingOn.set(from, [...new Set([...(waitingOn.get(from) ?? []), to])]);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return { unanswered, waitingOn };
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=laneStatus.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"laneStatus.js","sourceRoot":"","sources":["../../src/core/laneStatus.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG3C,oHAAoH;AACpH,MAAM,CAAC,MAAM,aAAa,GAAG,GAAG,CAAC;AAEjC,iGAAiG;AACjG,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACxC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAC1C,MAAM,CAAC,MAAM,EAAE,IAAI,EAAE,AAAD,EAAG,AAAD,EAAG,OAAO,CAAC,GAAG,MAAkD,CAAC;IACvF,MAAM,IAAI,GAAG,CAAC,KAAa,EAAE,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAC/D,MAAM,KAAK,GAAG,CAAC,KAAa,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC;IACzD,MAAM,UAAU,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC;IAChC,IAAI,UAAU;QAAE,OAAO,MAAM,CAAC,UAAU,CAAC,IAAI,SAAS,CAAC;IACvD,IAAI,MAAM,KAAK,GAAG;QAAE,OAAO,CAAC,CAAC;IAC7B,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC;IAC5B,IAAI,QAAQ;QAAE,OAAO,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,IAAI,SAAS,CAAC;IACxD,IAAI,IAAI,KAAK,GAAG;QAAE,OAAO,EAAE,CAAC;IAC5B,MAAM,MAAM,GAAG,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC;IAClC,OAAO,OAAO,KAAK,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC;AAC1E,CAAC;AAED,wGAAwG;AACxG,MAAM,UAAU,eAAe,CAAC,IAAa;IAC3C,MAAM,MAAM,GAAG,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC;IACxE,OAAO,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;AAC3D,CAAC;AAWD,MAAM,YAAY,GAAG,CAAC,GAAS,EAAE,IAAU,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC,GAAG,MAAM,CAAC;AAE1F,MAAM,UAAU,eAAe,CAAC,KAAgB,EAAE,GAAG,GAAS,IAAI,IAAI,EAAE;IACtE,IAAI,KAAK,CAAC,UAAU;QAAE,OAAO,SAAS,CAAC;IACvC,IAAI,KAAK,CAAC,OAAO;QAAE,OAAO,MAAM,CAAC;IACjC,IAAI,KAAK,CAAC,MAAM,IAAI,YAAY,CAAC,GAAG,EAAE,KAAK,CAAC,MAAM,CAAC,GAAG,YAAY;QAAE,OAAO,UAAU,CAAC;IACtF,MAAM,MAAM,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAa,EAAE,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;IACpH,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,YAAY,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,YAAY,IAAI,aAAa,CAAC;QAAE,OAAO,SAAS,CAAC;IACpI,OAAO,SAAS,CAAC;AACnB,CAAC;AAUD,MAAM,WAAW,GAAG,0DAA0D,CAAC;AAE/E,mGAAmG;AACnG,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,IAAY;IACpD,MAAM,QAAQ,GAAoB,EAAE,CAAC;IACrC,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;QACvC,MAAM,CAAC,GAAG,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;QACxC,IAAI,CAAC,CAAC;YAAE,SAAS;QACjB,MAAM,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,CAAC,GAAG,CAAwD,CAAC;QAC1F,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,CAAC,KAAK,CAAC,mBAAmB,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,GAAG,IAAI,IAAI,IAAI,UAAU,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IAClK,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AASD;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,QAAuD,EAAE,YAAY,GAAgC,IAAI,GAAG,EAAE;IAC9I,MAAM,SAAS,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC5C,KAAK,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,QAAQ;QAAE,SAAS,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IAC/I,MAAM,UAAU,GAAG,IAAI,GAAG,EAA4B,CAAC;IACvD,MAAM,SAAS,GAAG,IAAI,GAAG,EAAoB,CAAC;IAC9C,KAAK,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,QAAQ,EAAE,CAAC;QACxC,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;YAC/B,KAAK,MAAM,EAAE,IAAI,IAAI,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC;gBACrC,IAAI,EAAE,KAAK,KAAK,IAAI,EAAE,KAAK,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;oBAAE,SAAS;gBAC/D,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,IAAI,OAAO,CAAC,EAAE;oBAAE,SAAS;gBACtD,UAAU,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;gBACtG,SAAS,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;YAC1E,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,CAAC;AACnC,CAAC"}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
// Lane config: who owns which paths. Pure (no Node imports) so the dashboard can reuse it.
|
|
2
|
+
/** The supervising routine's commit trailer; it may touch anything. */
|
|
3
|
+
export const CONTROL_LANE = 'control';
|
|
4
|
+
const LANE_NAME = /^[a-z][a-z0-9-]{0,30}$/;
|
|
5
|
+
export function isValidLaneName(name) {
|
|
6
|
+
return LANE_NAME.test(name) && name !== CONTROL_LANE;
|
|
7
|
+
}
|
|
8
|
+
/** Backslashes to slashes, no leading `./` or `/`, so Windows-style paths match POSIX globs. */
|
|
9
|
+
export function normalizePath(file) {
|
|
10
|
+
return file.replace(/\\/g, '/').replace(/^(?:\.?\/)+/, '');
|
|
11
|
+
}
|
|
12
|
+
const REGEX_SPECIAL = /[.+^${}()|[\]\\]/g;
|
|
13
|
+
/** Compiles `**` (any depth), `*` (one segment), `?` (one character) and literals to an anchored regex. */
|
|
14
|
+
function globToRegExp(glob) {
|
|
15
|
+
const g = normalizePath(glob);
|
|
16
|
+
let out = '';
|
|
17
|
+
for (let i = 0; i < g.length; i++) {
|
|
18
|
+
const c = g[i];
|
|
19
|
+
if (c === '*' && g[i + 1] === '*') {
|
|
20
|
+
const slashAfter = g[i + 2] === '/';
|
|
21
|
+
out += slashAfter ? '(?:.*/)?' : '.*';
|
|
22
|
+
i += slashAfter ? 2 : 1;
|
|
23
|
+
}
|
|
24
|
+
else if (c === '*')
|
|
25
|
+
out += '[^/]*';
|
|
26
|
+
else if (c === '?')
|
|
27
|
+
out += '[^/]';
|
|
28
|
+
else
|
|
29
|
+
out += c.replace(REGEX_SPECIAL, '\\$&');
|
|
30
|
+
}
|
|
31
|
+
return new RegExp(`^${out}$`);
|
|
32
|
+
}
|
|
33
|
+
export function matchGlob(glob, file) {
|
|
34
|
+
return globToRegExp(glob).test(normalizePath(file));
|
|
35
|
+
}
|
|
36
|
+
function matchesAny(globs, file) {
|
|
37
|
+
return globs.some((glob) => matchGlob(glob, file));
|
|
38
|
+
}
|
|
39
|
+
/** A concrete path the glob matches, used to detect two globs claiming the same files. */
|
|
40
|
+
function sampleFor(glob) {
|
|
41
|
+
return normalizePath(glob).replace(/\*\*\/?/g, 'x/').replace(/\/$/, '').replace(/[*?]/g, 'x');
|
|
42
|
+
}
|
|
43
|
+
function globsOverlap(a, b) {
|
|
44
|
+
return matchGlob(a, sampleFor(b)) || matchGlob(b, sampleFor(a));
|
|
45
|
+
}
|
|
46
|
+
function stringArray(value) {
|
|
47
|
+
return Array.isArray(value) && value.every((v) => typeof v === 'string' && v.trim() !== '') ? value : undefined;
|
|
48
|
+
}
|
|
49
|
+
/** Validates an already-parsed JSON value. Returns every problem found, not just the first. */
|
|
50
|
+
export function validateLanesConfig(raw) {
|
|
51
|
+
const errors = [];
|
|
52
|
+
if (typeof raw !== 'object' || raw === null || Array.isArray(raw))
|
|
53
|
+
return { errors: ['.ai/lanes.json must be an object with "lanes" and "shared".'] };
|
|
54
|
+
const obj = raw;
|
|
55
|
+
if (!Array.isArray(obj.lanes) || obj.lanes.length === 0)
|
|
56
|
+
return { errors: ['.ai/lanes.json needs at least one lane in "lanes".'] };
|
|
57
|
+
const lanes = [];
|
|
58
|
+
obj.lanes.forEach((item, index) => {
|
|
59
|
+
const at = `lanes[${index}]`;
|
|
60
|
+
if (typeof item !== 'object' || item === null)
|
|
61
|
+
return void errors.push(`${at} must be an object.`);
|
|
62
|
+
const entry = item;
|
|
63
|
+
const name = typeof entry.name === 'string' ? entry.name : '';
|
|
64
|
+
if (!isValidLaneName(name))
|
|
65
|
+
errors.push(`${at}: name "${name}" is invalid. Use lowercase letters, digits and dashes, starting with a letter ("${CONTROL_LANE}" is reserved).`);
|
|
66
|
+
else if (lanes.some((l) => l.name === name))
|
|
67
|
+
errors.push(`${at}: lane "${name}" is listed twice.`);
|
|
68
|
+
const owns = stringArray(entry.owns);
|
|
69
|
+
if (!owns || owns.length === 0)
|
|
70
|
+
errors.push(`${at} (${name || '?'}): "owns" must be a non-empty list of globs.`);
|
|
71
|
+
if (entry.cron !== undefined && typeof entry.cron !== 'string')
|
|
72
|
+
errors.push(`${at} (${name || '?'}): "cron" must be a string.`);
|
|
73
|
+
if (name && owns)
|
|
74
|
+
lanes.push({ name, owns, ...(typeof entry.cron === 'string' ? { cron: entry.cron } : {}) });
|
|
75
|
+
});
|
|
76
|
+
const shared = obj.shared === undefined ? [] : stringArray(obj.shared);
|
|
77
|
+
if (!shared)
|
|
78
|
+
errors.push('"shared" must be a list of globs.');
|
|
79
|
+
for (let i = 0; i < lanes.length; i++) {
|
|
80
|
+
for (let j = i + 1; j < lanes.length; j++) {
|
|
81
|
+
for (const a of lanes[i].owns) {
|
|
82
|
+
for (const b of lanes[j].owns) {
|
|
83
|
+
if (globsOverlap(a, b))
|
|
84
|
+
errors.push(`Lanes "${lanes[i].name}" and "${lanes[j].name}" both own files matching "${a}" / "${b}". Make the globs disjoint or move the path to "shared".`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return errors.length > 0 || !shared ? { errors } : { config: { lanes, shared }, errors };
|
|
90
|
+
}
|
|
91
|
+
export function parseLanesConfig(text) {
|
|
92
|
+
try {
|
|
93
|
+
return validateLanesConfig(JSON.parse(text));
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return { errors: ['.ai/lanes.json is not valid JSON.'] };
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/** The lane that owns the file, "shared" for a shared path, or undefined when nobody does. */
|
|
100
|
+
export function laneForPath(config, file) {
|
|
101
|
+
const lane = config.lanes.find((l) => matchesAny(l.owns, file) || matchGlob(`.ai/lanes/${l.name}/**`, file));
|
|
102
|
+
if (lane)
|
|
103
|
+
return lane.name;
|
|
104
|
+
return matchesAny(config.shared, file) ? 'shared' : undefined;
|
|
105
|
+
}
|
|
106
|
+
/** Reads the last `Lane: <name>` line of a commit message (it may be followed by other trailers); undefined when there is none. */
|
|
107
|
+
export function parseLaneTrailer(message) {
|
|
108
|
+
const lines = message.trimEnd().split(/\r?\n/);
|
|
109
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
110
|
+
const line = lines[i].trim();
|
|
111
|
+
const match = /^lane:\s*(\S+)$/i.exec(line);
|
|
112
|
+
if (match)
|
|
113
|
+
return match[1].toLowerCase();
|
|
114
|
+
}
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
/** Release commits (version bump, CHANGELOG) and control commits (pause, next round) are not lanes; each may touch only these paths. */
|
|
118
|
+
export const RELEASE_LANE = 'release';
|
|
119
|
+
const RELEASE_PATHS = ['package.json', 'package-lock.json', 'plugin/**/plugin.json', 'CHANGELOG.md'];
|
|
120
|
+
const CONTROL_PATHS = ['.ai/**', 'IDEA.md', 'DONE.md'];
|
|
121
|
+
function restrictedLaneViolations(lane, files) {
|
|
122
|
+
const allowed = lane === RELEASE_LANE ? RELEASE_PATHS : CONTROL_PATHS;
|
|
123
|
+
return files
|
|
124
|
+
.filter((file) => !matchesAny(allowed, file))
|
|
125
|
+
.map((file) => ({ file: normalizePath(file), message: `lane "${lane}" touched ${normalizePath(file)}, but it may touch only ${allowed.join(', ')}.` }));
|
|
126
|
+
}
|
|
127
|
+
/** Files a commit with the given `Lane:` trailer may not touch, each with a fix-it message. */
|
|
128
|
+
export function checkCommitFiles(config, lane, files) {
|
|
129
|
+
if (lane === CONTROL_LANE || lane === RELEASE_LANE)
|
|
130
|
+
return restrictedLaneViolations(lane, files);
|
|
131
|
+
const self = config.lanes.find((l) => l.name === lane);
|
|
132
|
+
if (!self) {
|
|
133
|
+
const known = config.lanes.map((l) => l.name).join(', ');
|
|
134
|
+
return [{ file: '(trailer)', message: `unknown lane "${lane}". Use one of: ${known}, or fix the trailer.` }];
|
|
135
|
+
}
|
|
136
|
+
const violations = [];
|
|
137
|
+
for (const file of files) {
|
|
138
|
+
const owner = laneForPath(config, file);
|
|
139
|
+
// A path listed in "shared" is open to every lane even when an "owns" glob also covers it (e.g. src/core/types.ts).
|
|
140
|
+
if (owner === self.name || owner === 'shared' || matchesAny(config.shared, file))
|
|
141
|
+
continue;
|
|
142
|
+
const fix = owner
|
|
143
|
+
? `it belongs to lane "${owner}". Ask that lane through your outbox (.ai/lanes/${self.name}/outbox.md) instead of editing it.`
|
|
144
|
+
: `no lane owns it. Add it to a lane's "owns" or to "shared" in .ai/lanes.json.`;
|
|
145
|
+
violations.push({ file: normalizePath(file), message: `lane "${self.name}" touched ${normalizePath(file)}, but ${fix}` });
|
|
146
|
+
}
|
|
147
|
+
return violations;
|
|
148
|
+
}
|
|
149
|
+
//# sourceMappingURL=lanes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lanes.js","sourceRoot":"","sources":["../../src/core/lanes.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
// Planning for `loop init --lanes` and `loop lanes add`: decides what to write, without touching the disk.
|
|
2
|
+
import { fillPlaceholders } from './template.js';
|
|
3
|
+
import { isValidLaneName, normalizePath, validateLanesConfig } from './lanes.js';
|
|
4
|
+
export const LANES_START = '<!-- compounding-loop:lanes:start -->';
|
|
5
|
+
export const LANES_END = '<!-- compounding-loop:lanes:end -->';
|
|
6
|
+
/** Shared files every new laned repo starts with; the user edits `.ai/lanes.json` to taste. */
|
|
7
|
+
export const DEFAULT_SHARED = ['package.json', 'package-lock.json', '.gitignore', '.ai/audit/**'];
|
|
8
|
+
// Used when the package has no templates/lanes/ files, so init never fails on a half-installed kit.
|
|
9
|
+
export const FALLBACK_LANE_TEMPLATES = {
|
|
10
|
+
task: `# Lane: {{lane}}
|
|
11
|
+
|
|
12
|
+
Run: 0 / 20
|
|
13
|
+
Status: not started
|
|
14
|
+
|
|
15
|
+
## Contract
|
|
16
|
+
**Goal:**
|
|
17
|
+
**Done when:**
|
|
18
|
+
**Constraints:** edit only the paths this lane owns in \`.ai/lanes.json\`: {{owns}}
|
|
19
|
+
**Out of scope:**
|
|
20
|
+
|
|
21
|
+
## Units, in order
|
|
22
|
+
1.
|
|
23
|
+
|
|
24
|
+
## Decisions
|
|
25
|
+
|
|
26
|
+
## Confirmed
|
|
27
|
+
|
|
28
|
+
## Guesses
|
|
29
|
+
|
|
30
|
+
## Tried
|
|
31
|
+
|
|
32
|
+
## Handoff
|
|
33
|
+
`,
|
|
34
|
+
outbox: `# Outbox: {{lane}}
|
|
35
|
+
|
|
36
|
+
Newest first. Format: \`YYYY-MM-DD HH:MM UTC · to <lane|all> · message\`.
|
|
37
|
+
`,
|
|
38
|
+
controlRoom: `# Control room notes
|
|
39
|
+
|
|
40
|
+
Written only by the control room routine. Every lane reads it at the start of a run. Newest first. Format: \`YYYY-MM-DD HH:MM UTC · to <lane|all> · message\`.
|
|
41
|
+
`,
|
|
42
|
+
claudeSection: `## Lanes
|
|
43
|
+
|
|
44
|
+
Lanes in this repo: {{lanes}}. \`.ai/lanes.json\` says who owns which paths; read it first. Edit only your lane's paths (and the shared ones). Every commit ends with a trailer line \`Lane: <your lane>\`; \`loop check-lanes\` fails a commit that crosses lanes.
|
|
45
|
+
|
|
46
|
+
Talk to other lanes by writing to your own \`.ai/lanes/<you>/outbox.md\` (newest first). At the start of each run read every other lane's outbox and \`.ai/control-room.md\`. Your task file is \`.ai/lanes/<you>/task.md\`.
|
|
47
|
+
`,
|
|
48
|
+
};
|
|
49
|
+
/** Staggered hourly cron minutes (7, 19, 31, 43, 55, ...) so lanes never start together. */
|
|
50
|
+
export function staggeredCron(index) {
|
|
51
|
+
return `${(7 + 12 * index) % 60} * * * *`;
|
|
52
|
+
}
|
|
53
|
+
/** Parses `a,b,c` into lane names; returns the first problem as `error`. */
|
|
54
|
+
export function parseLaneList(list) {
|
|
55
|
+
const names = list.split(',').map((n) => n.trim()).filter(Boolean);
|
|
56
|
+
if (names.length === 0)
|
|
57
|
+
return { names, error: 'Give at least one lane name, e.g. --lanes core,web.' };
|
|
58
|
+
for (const name of names) {
|
|
59
|
+
if (!isValidLaneName(name))
|
|
60
|
+
return { names, error: `"${name}" is not a valid lane name: use lowercase letters, digits and dashes, starting with a letter ("control" is reserved).` };
|
|
61
|
+
}
|
|
62
|
+
const dupe = names.find((n, i) => names.indexOf(n) !== i);
|
|
63
|
+
return dupe ? { names, error: `Lane "${dupe}" is listed twice.` } : { names };
|
|
64
|
+
}
|
|
65
|
+
/** Slash-separated and repo-relative: a Windows-style or absolute glob would be stored but could never match. */
|
|
66
|
+
function checkedGlob(lane, glob) {
|
|
67
|
+
const normalized = normalizePath(glob);
|
|
68
|
+
if (/^[a-z]:/i.test(normalized) || glob.trim().startsWith('/') || normalized.split('/').includes('..')) {
|
|
69
|
+
throw new Error(`Lane "${lane}": "${glob}" must be a path relative to the repo root, like src/api/**.`);
|
|
70
|
+
}
|
|
71
|
+
return normalized;
|
|
72
|
+
}
|
|
73
|
+
/** Lanes to add to `existing` (undefined = new config); throws with the reason when the result would be invalid. */
|
|
74
|
+
export function addLanes(existing, additions) {
|
|
75
|
+
const base = existing ?? { lanes: [], shared: [...DEFAULT_SHARED] };
|
|
76
|
+
additions = additions.map((lane) => ({ ...lane, owns: lane.owns.map((glob) => checkedGlob(lane.name, glob)) }));
|
|
77
|
+
for (const lane of additions) {
|
|
78
|
+
if (base.lanes.some((l) => l.name === lane.name))
|
|
79
|
+
throw new Error(`Lane "${lane.name}" already exists in .ai/lanes.json. Pick another name or edit the file.`);
|
|
80
|
+
}
|
|
81
|
+
const next = { lanes: [...base.lanes, ...additions], shared: base.shared };
|
|
82
|
+
const { errors } = validateLanesConfig(next);
|
|
83
|
+
if (errors.length > 0)
|
|
84
|
+
throw new Error(errors.join('\n'));
|
|
85
|
+
return next;
|
|
86
|
+
}
|
|
87
|
+
/** Same shape as `mergeCard`: re-running is a no-op, and a broken marker pair is reported, not guessed at. */
|
|
88
|
+
export function mergeLanesSection(existing, section, fileName = 'CLAUDE.md') {
|
|
89
|
+
const block = `${LANES_START}\n${section.trim()}\n${LANES_END}\n`;
|
|
90
|
+
if (existing === undefined || existing.trim() === '')
|
|
91
|
+
return block;
|
|
92
|
+
const start = existing.indexOf(LANES_START);
|
|
93
|
+
const end = existing.indexOf(LANES_END);
|
|
94
|
+
if ((start === -1) !== (end === -1) || (start !== -1 && end < start)) {
|
|
95
|
+
throw new Error(`${fileName} has a broken ${LANES_START} / ${LANES_END} pair: fix or remove the markers, then re-run.`);
|
|
96
|
+
}
|
|
97
|
+
if (start !== -1)
|
|
98
|
+
return existing.slice(0, start) + block + existing.slice(end + LANES_END.length).replace(/^\r?\n/, '');
|
|
99
|
+
return `${existing.replace(/\s*$/, '')}\n\n${block}`;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Files for a laned repo. Config is rewritten only when it changed; task, outbox and control-room files are loop
|
|
103
|
+
* state, created if absent and never overwritten, even with `force`.
|
|
104
|
+
*/
|
|
105
|
+
export function planLanes({ config, newLanes, templates, existing, force }) {
|
|
106
|
+
const actions = [];
|
|
107
|
+
const json = `${JSON.stringify(config, null, 2)}\n`;
|
|
108
|
+
const current = existing.get('.ai/lanes.json');
|
|
109
|
+
if (current === undefined)
|
|
110
|
+
actions.push({ dest: '.ai/lanes.json', kind: 'create', content: json });
|
|
111
|
+
else if (current === json)
|
|
112
|
+
actions.push({ dest: '.ai/lanes.json', kind: 'skip', reason: 'already up to date' });
|
|
113
|
+
else if (force || newLanes.length > 0)
|
|
114
|
+
actions.push({ dest: '.ai/lanes.json', kind: 'update', content: json });
|
|
115
|
+
else
|
|
116
|
+
actions.push({ dest: '.ai/lanes.json', kind: 'skip', reason: 'exists with different lanes (use --force to replace, or `loop lanes add`)' });
|
|
117
|
+
const state = (dest, content) => {
|
|
118
|
+
if (existing.has(dest))
|
|
119
|
+
actions.push({ dest, kind: 'skip', reason: 'loop state, never overwritten' });
|
|
120
|
+
else
|
|
121
|
+
actions.push({ dest, kind: 'create', content });
|
|
122
|
+
};
|
|
123
|
+
for (const name of newLanes) {
|
|
124
|
+
const lane = config.lanes.find((l) => l.name === name);
|
|
125
|
+
const vars = { lane: name, owns: lane ? lane.owns.join(', ') : '', lanes: config.lanes.map((l) => l.name).join(', ') };
|
|
126
|
+
state(`.ai/lanes/${name}/task.md`, fillPlaceholders(templates.task, vars));
|
|
127
|
+
state(`.ai/lanes/${name}/outbox.md`, fillPlaceholders(templates.outbox, vars));
|
|
128
|
+
}
|
|
129
|
+
state('.ai/control-room.md', templates.controlRoom);
|
|
130
|
+
const section = fillPlaceholders(templates.claudeSection, { lanes: config.lanes.map((l) => l.name).join(', ') });
|
|
131
|
+
// AGENTS.md is merged only when the repo already has one; CLAUDE.md is always installed.
|
|
132
|
+
const instructionFiles = existing.has('AGENTS.md') ? ['CLAUDE.md', 'AGENTS.md'] : ['CLAUDE.md'];
|
|
133
|
+
for (const dest of instructionFiles) {
|
|
134
|
+
const current = existing.get(dest);
|
|
135
|
+
const merged = mergeLanesSection(current, section, dest);
|
|
136
|
+
if (merged === current)
|
|
137
|
+
actions.push({ dest, kind: 'skip', reason: 'lane section already installed' });
|
|
138
|
+
else
|
|
139
|
+
actions.push({ dest, kind: current === undefined ? 'create' : 'update', content: merged });
|
|
140
|
+
}
|
|
141
|
+
return actions;
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=lanesInit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lanesInit.js","sourceRoot":"","sources":["../../src/core/lanesInit.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// `loop next`: what is open across lanes and what to pick. Pure.
|
|
2
|
+
import { activeClaim } from './parse/claim.js';
|
|
3
|
+
import { doneUnits, parseUnits } from './parse/task.js';
|
|
4
|
+
export function planNext(lanes, now = new Date()) {
|
|
5
|
+
const rows = lanes.map((lane) => {
|
|
6
|
+
const claim = activeClaim(lane.claimText, now);
|
|
7
|
+
const text = lane.taskText ?? '';
|
|
8
|
+
const done = doneUnits(text);
|
|
9
|
+
const open = lane.state === 'done' ? [] : parseUnits(text).filter((u) => !done.has(u.number));
|
|
10
|
+
return { name: lane.name, state: lane.state, ...(claim && { claim: { name: claim.name, expiresAt: claim.expiresAt.toISOString() } }), openUnits: open };
|
|
11
|
+
});
|
|
12
|
+
const pick = rows.find((r) => !r.claim && r.state !== 'done' && r.state !== 'blocked' && r.openUnits.length > 0);
|
|
13
|
+
const first = pick?.openUnits[0];
|
|
14
|
+
return { lanes: rows, ...(pick && first && { recommended: { lane: pick.name, unit: first } }) };
|
|
15
|
+
}
|
|
16
|
+
export function formatNext(report) {
|
|
17
|
+
const lines = [];
|
|
18
|
+
for (const lane of report.lanes) {
|
|
19
|
+
const tags = [lane.state, ...(lane.claim ? [`claimed by ${lane.claim.name} until ${lane.claim.expiresAt.slice(0, 16).replace('T', ' ')} UTC`] : [])];
|
|
20
|
+
lines.push(`${lane.name} (${tags.join(', ')})`);
|
|
21
|
+
if (lane.openUnits.length === 0)
|
|
22
|
+
lines.push(lane.state === 'done' ? ' done' : ' no open units listed');
|
|
23
|
+
for (const unit of lane.openUnits.slice(0, 3))
|
|
24
|
+
lines.push(` ${unit.number}. ${unit.text}`);
|
|
25
|
+
if (lane.openUnits.length > 3)
|
|
26
|
+
lines.push(` ... and ${lane.openUnits.length - 3} more`);
|
|
27
|
+
}
|
|
28
|
+
lines.push('');
|
|
29
|
+
lines.push(report.recommended
|
|
30
|
+
? `Recommended: ${report.recommended.lane}, unit ${report.recommended.unit.number}. Take it with \`loop claim ${report.recommended.lane}\`, then \`loop run --lane ${report.recommended.lane}\`.`
|
|
31
|
+
: 'Nothing to recommend: every lane is done, blocked, claimed or has no open unit.');
|
|
32
|
+
return lines.join('\n');
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=next.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"next.js","sourceRoot":"","sources":["../../src/core/next.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// CHANGELOG.md (Keep a Changelog) into releases. Pure and browser-safe.
|
|
2
|
+
const VERSION_HEADING = /^##\s+\[?([^\]\s]+)\]?(?:\s+[-–—]\s+(\d{4}-\d\d-\d\d))?\s*$/;
|
|
3
|
+
/** Releases newest first, as written in the file. Link reference lines (`[0.3.0]: https://...`) are ignored. */
|
|
4
|
+
export function parseChangelog(text) {
|
|
5
|
+
const releases = [];
|
|
6
|
+
let current;
|
|
7
|
+
let section;
|
|
8
|
+
for (const line of text.split(/\r?\n/)) {
|
|
9
|
+
const heading = VERSION_HEADING.exec(line);
|
|
10
|
+
if (heading) {
|
|
11
|
+
current = { version: heading[1], ...(heading[2] && { date: heading[2] }), sections: [], items: [] };
|
|
12
|
+
section = undefined;
|
|
13
|
+
releases.push(current);
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
if (!current)
|
|
17
|
+
continue;
|
|
18
|
+
const sub = /^###\s+(.+?)\s*$/.exec(line);
|
|
19
|
+
if (sub) {
|
|
20
|
+
section = { title: sub[1], items: [] };
|
|
21
|
+
current.sections.push(section);
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
const bullet = /^\s*[-*+]\s+(.+?)\s*$/.exec(line);
|
|
25
|
+
if (bullet) {
|
|
26
|
+
if (!section) {
|
|
27
|
+
section = { title: '', items: [] };
|
|
28
|
+
current.sections.push(section);
|
|
29
|
+
}
|
|
30
|
+
section.items.push(bullet[1]);
|
|
31
|
+
current.items.push(bullet[1]);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return releases;
|
|
35
|
+
}
|
|
36
|
+
/** The release for `version` (a leading `v` is ignored), or undefined. */
|
|
37
|
+
export function releaseFor(releases, version) {
|
|
38
|
+
const wanted = version.replace(/^v/, '');
|
|
39
|
+
return releases.find((r) => r.version.toLowerCase() === wanted.toLowerCase());
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=changelog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"changelog.js","sourceRoot":"","sources":["../../../src/core/parse/changelog.ts"],"names":[],"mappings":"AAAA,wEAAwE;AAkBxE,MAAM,eAAe,GAAG,6DAA6D,CAAC;AAEtF,gHAAgH;AAChH,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,MAAM,QAAQ,GAAc,EAAE,CAAC;IAC/B,IAAI,OAA4B,CAAC;IACjC,IAAI,OAAqC,CAAC;IAC1C,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;QACvC,MAAM,OAAO,GAAG,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC3C,IAAI,OAAO,EAAE,CAAC;YACZ,OAAO,GAAG,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAW,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;YAC9G,OAAO,GAAG,SAAS,CAAC;YACpB,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACvB,SAAS;QACX,CAAC;QACD,IAAI,CAAC,OAAO;YAAE,SAAS;QACvB,MAAM,GAAG,GAAG,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC1C,IAAI,GAAG,EAAE,CAAC;YACR,OAAO,GAAG,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC,CAAW,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;YACjD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAC/B,SAAS;QACX,CAAC;QACD,MAAM,MAAM,GAAG,uBAAuB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClD,IAAI,MAAM,EAAE,CAAC;YACX,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,OAAO,GAAG,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;gBACnC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACjC,CAAC;YACD,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAW,CAAC,CAAC;YACxC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAW,CAAC,CAAC;QAC1C,CAAC;IACH,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,UAAU,CAAC,QAA4B,EAAE,OAAe;IACtE,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IACzC,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,KAAK,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC;AAChF,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Lane claims: `.ai/lanes/<lane>/claim` holds one line, `name · expires <UTC ISO>`. Pure.
|
|
2
|
+
const SEPARATOR = ' · ';
|
|
3
|
+
export function formatClaim(name, expiresAt) {
|
|
4
|
+
return `${name}${SEPARATOR}expires ${expiresAt.toISOString()}\n`;
|
|
5
|
+
}
|
|
6
|
+
/** Parses a claim file; undefined when it is empty or malformed (a malformed claim counts as none). */
|
|
7
|
+
export function parseClaim(text) {
|
|
8
|
+
const line = text.split(/\r?\n/).find((l) => l.trim() !== '')?.trim();
|
|
9
|
+
if (!line)
|
|
10
|
+
return undefined;
|
|
11
|
+
const match = /^(.+?)\s+·\s+expires\s+(\S+)$/.exec(line);
|
|
12
|
+
if (!match)
|
|
13
|
+
return undefined;
|
|
14
|
+
const expiresAt = new Date(match[2]);
|
|
15
|
+
if (Number.isNaN(expiresAt.getTime()))
|
|
16
|
+
return undefined;
|
|
17
|
+
return { name: match[1].trim(), expiresAt };
|
|
18
|
+
}
|
|
19
|
+
/** The claim if it is still in force at `now`; an expired or missing claim is no claim. */
|
|
20
|
+
export function activeClaim(text, now = new Date()) {
|
|
21
|
+
const claim = text === undefined ? undefined : parseClaim(text);
|
|
22
|
+
return claim && claim.expiresAt.getTime() > now.getTime() ? claim : undefined;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=claim.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claim.js","sourceRoot":"","sources":["../../../src/core/parse/claim.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAO1F,MAAM,SAAS,GAAG,KAAK,CAAC;AAExB,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,SAAe;IACvD,OAAO,GAAG,IAAI,GAAG,SAAS,WAAW,SAAS,CAAC,WAAW,EAAE,IAAI,CAAC;AACnE,CAAC;AAED,uGAAuG;AACvG,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC;IACtE,IAAI,CAAC,IAAI;QAAE,OAAO,SAAS,CAAC;IAC5B,MAAM,KAAK,GAAG,+BAA+B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACzD,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAC;IAC7B,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,CAAW,CAAC,CAAC;IAC/C,IAAI,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC;QAAE,OAAO,SAAS,CAAC;IACxD,OAAO,EAAE,IAAI,EAAG,KAAK,CAAC,CAAC,CAAY,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,CAAC;AAC1D,CAAC;AAED,2FAA2F;AAC3F,MAAM,UAAU,WAAW,CAAC,IAAwB,EAAE,GAAG,GAAS,IAAI,IAAI,EAAE;IAC1E,MAAM,KAAK,GAAG,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;IAChE,OAAO,KAAK,IAAI,KAAK,CAAC,SAAS,CAAC,OAAO,EAAE,GAAG,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AAChF,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// The control room's notes (`.ai/control-room.md`) per lane, and which lane a commit belongs to. Pure and browser-safe.
|
|
2
|
+
import { parseLaneTrailer } from '../lanes.js';
|
|
3
|
+
import { parseOutbox } from '../laneStatus.js';
|
|
4
|
+
/**
|
|
5
|
+
* For each lane, the newest note addressed to it by name or to `all`. Lanes with no note are absent.
|
|
6
|
+
* Ties on the minute go to the line written first, because the file is newest first.
|
|
7
|
+
*/
|
|
8
|
+
export function latestControlNotes(controlRoomMd, lanes) {
|
|
9
|
+
const notes = parseOutbox('control', controlRoomMd);
|
|
10
|
+
const result = {};
|
|
11
|
+
for (const lane of lanes) {
|
|
12
|
+
let best;
|
|
13
|
+
for (const note of notes) {
|
|
14
|
+
if (!note.to.includes(lane.toLowerCase()) && !note.to.includes('all'))
|
|
15
|
+
continue;
|
|
16
|
+
if (!best || note.at > best.at)
|
|
17
|
+
best = note;
|
|
18
|
+
}
|
|
19
|
+
if (best)
|
|
20
|
+
result[lane] = best;
|
|
21
|
+
}
|
|
22
|
+
return result;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The lane a commit belongs to: its `Lane:` trailer, else a `<lane>:` subject prefix, else `release` for a
|
|
26
|
+
* `Release x.y.z` subject, else `other`. Pass the lane names from `.ai/lanes.json`; `control` and `release` are always known.
|
|
27
|
+
*/
|
|
28
|
+
export function commitLane(message, lanes) {
|
|
29
|
+
const trailer = parseLaneTrailer(message);
|
|
30
|
+
if (trailer)
|
|
31
|
+
return trailer;
|
|
32
|
+
const subject = message.split(/\r?\n/, 1)[0] ?? '';
|
|
33
|
+
if (/^release\s+v?\d/i.test(subject))
|
|
34
|
+
return 'release';
|
|
35
|
+
const prefix = /^([a-z][a-z0-9-]*)(?:\s+room)?:/i.exec(subject)?.[1]?.toLowerCase();
|
|
36
|
+
return prefix && [...lanes.map((l) => l.toLowerCase()), 'control', 'release'].includes(prefix) ? prefix : 'other';
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=controlRoom.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"controlRoom.js","sourceRoot":"","sources":["../../../src/core/parse/controlRoom.ts"],"names":[],"mappings":"AAAA,wHAAwH;AACxH,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAsB,MAAM,kBAAkB,CAAC;AAEnE;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,aAAqB,EAAE,KAAwB;IAChF,MAAM,KAAK,GAAG,WAAW,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC;IACpD,MAAM,MAAM,GAAkC,EAAE,CAAC;IACjD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAA+B,CAAC;QACpC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC;gBAAE,SAAS;YAChF,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC,EAAE;gBAAE,IAAI,GAAG,IAAI,CAAC;QAC9C,CAAC;QACD,IAAI,IAAI;YAAE,MAAM,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAChC,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,OAAe,EAAE,KAAwB;IAClE,MAAM,OAAO,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;IAC1C,IAAI,OAAO;QAAE,OAAO,OAAO,CAAC;IAC5B,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACnD,IAAI,kBAAkB,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,OAAO,SAAS,CAAC;IACvD,MAAM,MAAM,GAAG,kCAAkC,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC;IACpF,OAAO,MAAM,IAAI,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC;AACpH,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Browser-safe parsers: nothing here (or in anything it imports) may use `node:` modules.
|
|
2
|
+
// Server and web import from this one entry point.
|
|
3
|
+
export { CONTROL_LANE, laneForPath, matchGlob, parseLaneTrailer, parseLanesConfig, validateLanesConfig } from '../lanes.js';
|
|
4
|
+
export { parseOutbox } from '../laneStatus.js';
|
|
5
|
+
export { doneUnits, openUnits, parseRunCounter, parseTaskFile, parseUnits, sectionOf } from './task.js';
|
|
6
|
+
export { parseStopMarkers } from './markers.js';
|
|
7
|
+
export { activeClaim, formatClaim, parseClaim } from './claim.js';
|
|
8
|
+
export { describePaused, formatPaused, isPaused, parsePaused, PAUSED_FILE } from './paused.js';
|
|
9
|
+
export { expectedSlots, formatRunEntry, matchSlots, missedInARow, parseRunLog, runLogPath } from './runs.js';
|
|
10
|
+
export { parseChangelog, releaseFor } from './changelog.js';
|
|
11
|
+
export { commitLane, latestControlNotes } from './controlRoom.js';
|
|
12
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/core/parse/index.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAC1F,mDAAmD;AAEnD,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,SAAS,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAE5H,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE/C,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,eAAe,EAAE,aAAa,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAExG,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAEhD,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAElE,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,QAAQ,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/F,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAE7G,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAE5D,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// DONE.md / BLOCKED.md detection from a plain list of repo-relative file paths. Pure.
|
|
2
|
+
import { normalizePath } from '../lanes.js';
|
|
3
|
+
export function parseStopMarkers(files) {
|
|
4
|
+
const markers = { rootDone: false, rootBlocked: false, paused: false, doneLanes: [], blockedLanes: [] };
|
|
5
|
+
for (const file of files.map(normalizePath)) {
|
|
6
|
+
if (file === 'DONE.md')
|
|
7
|
+
markers.rootDone = true;
|
|
8
|
+
else if (file === 'BLOCKED.md')
|
|
9
|
+
markers.rootBlocked = true;
|
|
10
|
+
else if (file === '.ai/PAUSED')
|
|
11
|
+
markers.paused = true;
|
|
12
|
+
else {
|
|
13
|
+
const match = /^\.ai\/lanes\/([^/]+)\/(DONE|BLOCKED)\.md$/.exec(file);
|
|
14
|
+
if (match)
|
|
15
|
+
(match[2] === 'DONE' ? markers.doneLanes : markers.blockedLanes).push(match[1]);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return markers;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=markers.js.map
|