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,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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markers.js","sourceRoot":"","sources":["../../../src/core/parse/markers.ts"],"names":[],"mappings":"AAAA,sFAAsF;AAEtF,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAa5C,MAAM,UAAU,gBAAgB,CAAC,KAAwB;IACvD,MAAM,OAAO,GAAgB,EAAE,QAAQ,EAAE,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,EAAE,YAAY,EAAE,EAAE,EAAE,CAAC;IACrH,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,GAAG,CAAC,aAAa,CAAC,EAAE,CAAC;QAC5C,IAAI,IAAI,KAAK,SAAS;YAAE,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;aAC3C,IAAI,IAAI,KAAK,YAAY;YAAE,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;aACtD,IAAI,IAAI,KAAK,YAAY;YAAE,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;aACjD,CAAC;YACJ,MAAM,KAAK,GAAG,4CAA4C,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACtE,IAAI,KAAK;gBAAE,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAW,CAAC,CAAC;QACvG,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// `.ai/PAUSED`: one line, `who · paused <UTC ISO>[ · reason]`. Its existence stops every lane run. Pure.
|
|
2
|
+
export const PAUSED_FILE = '.ai/PAUSED';
|
|
3
|
+
export function formatPaused(by, at, reason) {
|
|
4
|
+
return `${by} · paused ${at.toISOString()}${reason ? ` · ${reason}` : ''}\n`;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Reads a PAUSED file. The file's existence is what pauses the loop, so a hand-written or malformed file still
|
|
8
|
+
* yields a value (`by` is then its first line).
|
|
9
|
+
*/
|
|
10
|
+
export function parsePaused(text) {
|
|
11
|
+
const line = text.split(/\r?\n/).find((l) => l.trim() !== '')?.trim() ?? '';
|
|
12
|
+
const match = /^(.+?)\s+·\s+paused\s+(\S+)(?:\s+·\s+(.+))?$/.exec(line);
|
|
13
|
+
const at = match ? new Date(match[2]) : undefined;
|
|
14
|
+
if (!match || !at || Number.isNaN(at.getTime()))
|
|
15
|
+
return { by: line || 'unknown' };
|
|
16
|
+
return { by: match[1].trim(), at, ...(match[3] && { reason: match[3].trim() }) };
|
|
17
|
+
}
|
|
18
|
+
/** True when the repo-relative file list contains `.ai/PAUSED`. */
|
|
19
|
+
export function isPaused(files) {
|
|
20
|
+
return files.some((f) => f.replace(/\\/g, '/') === PAUSED_FILE);
|
|
21
|
+
}
|
|
22
|
+
/** One line for `loop status` and the CLI: who paused and when. */
|
|
23
|
+
export function describePaused(paused) {
|
|
24
|
+
return `Paused by ${paused.by}${paused.at ? ` since ${paused.at.toISOString().slice(0, 16).replace('T', ' ')} UTC` : ''}${paused.reason ? ` (${paused.reason})` : ''}`;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=paused.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paused.js","sourceRoot":"","sources":["../../../src/core/parse/paused.ts"],"names":[],"mappings":"AAAA,yGAAyG;AASzG,MAAM,CAAC,MAAM,WAAW,GAAG,YAAY,CAAC;AAExC,MAAM,UAAU,YAAY,CAAC,EAAU,EAAE,EAAQ,EAAE,MAAe;IAChE,OAAO,GAAG,EAAE,aAAa,EAAE,CAAC,WAAW,EAAE,GAAG,MAAM,CAAC,CAAC,CAAC,MAAM,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC;AAC/E,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,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,IAAI,EAAE,CAAC;IAC5E,MAAM,KAAK,GAAG,8CAA8C,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACxE,MAAM,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,CAAW,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAC5D,IAAI,CAAC,KAAK,IAAI,CAAC,EAAE,IAAI,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC;QAAE,OAAO,EAAE,EAAE,EAAE,IAAI,IAAI,SAAS,EAAE,CAAC;IAClF,OAAO,EAAE,EAAE,EAAG,KAAK,CAAC,CAAC,CAAY,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,CAAC;AAC/F,CAAC;AAED,mEAAmE;AACnE,MAAM,UAAU,QAAQ,CAAC,KAAwB;IAC/C,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,KAAK,WAAW,CAAC,CAAC;AAClE,CAAC;AAED,mEAAmE;AACnE,MAAM,UAAU,cAAc,CAAC,MAAc;IAC3C,OAAO,aAAa,MAAM,CAAC,EAAE,GAAG,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,MAAM,CAAC,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;AACzK,CAAC"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
// The per-lane run log (`.ai/lanes/<lane>/runs.jsonl`) and the scheduled slots a lane's cron promises. Pure.
|
|
2
|
+
export const runLogPath = (lane) => `.ai/lanes/${lane}/runs.jsonl`;
|
|
3
|
+
export function formatRunEntry(entry) {
|
|
4
|
+
return `${JSON.stringify(entry)}\n`;
|
|
5
|
+
}
|
|
6
|
+
const isCount = (v) => typeof v === 'number' && Number.isInteger(v) && v >= 0;
|
|
7
|
+
function toEntry(value) {
|
|
8
|
+
if (!value || typeof value !== 'object')
|
|
9
|
+
return undefined;
|
|
10
|
+
const v = value;
|
|
11
|
+
if (typeof v.at !== 'string' || Number.isNaN(new Date(v.at).getTime()) || typeof v.lane !== 'string' || typeof v.pushed !== 'boolean')
|
|
12
|
+
return undefined;
|
|
13
|
+
const tests = v.tests;
|
|
14
|
+
return {
|
|
15
|
+
at: v.at,
|
|
16
|
+
lane: v.lane,
|
|
17
|
+
...(typeof v.session === 'string' && { session: v.session }),
|
|
18
|
+
units: Array.isArray(v.units) ? v.units.filter((u) => typeof u === 'string') : [],
|
|
19
|
+
...(tests && isCount(tests.passed) && isCount(tests.failed) && { tests: { passed: tests.passed, failed: tests.failed } }),
|
|
20
|
+
pushed: v.pushed,
|
|
21
|
+
...(typeof v.note === 'string' && { note: v.note }),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/** Reads a runs.jsonl; lines that are not valid entries (a half-written line, a merge marker) are skipped. Oldest first. */
|
|
25
|
+
export function parseRunLog(text) {
|
|
26
|
+
const entries = [];
|
|
27
|
+
for (const line of text.split(/\r?\n/)) {
|
|
28
|
+
if (!line.trim())
|
|
29
|
+
continue;
|
|
30
|
+
try {
|
|
31
|
+
const entry = toEntry(JSON.parse(line));
|
|
32
|
+
if (entry)
|
|
33
|
+
entries.push(entry);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
// skipped on purpose: see above
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return entries.sort((a, b) => a.at.localeCompare(b.at));
|
|
40
|
+
}
|
|
41
|
+
// ---- cron ----
|
|
42
|
+
const RANGES = [[0, 59], [0, 23], [1, 31], [1, 12], [0, 7]];
|
|
43
|
+
/** Expands one cron field (star, a number, a list, a range, or a step such as star-slash-15) into the matching numbers; undefined when unreadable. */
|
|
44
|
+
function expandField(field, [min, max]) {
|
|
45
|
+
const out = new Set();
|
|
46
|
+
for (const part of field.split(',')) {
|
|
47
|
+
const match = /^(\*|\d+(?:-\d+)?)(?:\/(\d+))?$/.exec(part);
|
|
48
|
+
if (!match)
|
|
49
|
+
return undefined;
|
|
50
|
+
const step = match[2] === undefined ? 1 : Number(match[2]);
|
|
51
|
+
if (step < 1)
|
|
52
|
+
return undefined;
|
|
53
|
+
let [from, to] = [min, max];
|
|
54
|
+
if (match[1] !== '*') {
|
|
55
|
+
const [a, b] = match[1].split('-');
|
|
56
|
+
from = Number(a);
|
|
57
|
+
to = b === undefined ? (match[2] === undefined ? from : max) : Number(b);
|
|
58
|
+
}
|
|
59
|
+
if (from < min || to > max || from > to)
|
|
60
|
+
return undefined;
|
|
61
|
+
for (let n = from; n <= to; n += step)
|
|
62
|
+
out.add(n);
|
|
63
|
+
}
|
|
64
|
+
return out;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* The UTC minutes in (from, to] at which a 5-field cron expression fires. Empty for an expression this cannot read.
|
|
68
|
+
* Like cron, day-of-month and day-of-week both restrict when both are set (either may match).
|
|
69
|
+
*/
|
|
70
|
+
export function expectedSlots(cron, from, to) {
|
|
71
|
+
const fields = cron.trim().split(/\s+/);
|
|
72
|
+
if (fields.length !== 5)
|
|
73
|
+
return [];
|
|
74
|
+
const sets = fields.map((f, i) => expandField(f, RANGES[i]));
|
|
75
|
+
const [minutes, hours, days, months, weekdays] = sets;
|
|
76
|
+
if (!minutes || !hours || !days || !months || !weekdays)
|
|
77
|
+
return [];
|
|
78
|
+
const dayRestricted = fields[2] !== '*';
|
|
79
|
+
const weekdayRestricted = fields[4] !== '*';
|
|
80
|
+
const slots = [];
|
|
81
|
+
const start = Math.floor(from.getTime() / 60_000) * 60_000 + 60_000;
|
|
82
|
+
for (let t = start; t <= to.getTime() && slots.length < 10_000; t += 60_000) {
|
|
83
|
+
const d = new Date(t);
|
|
84
|
+
if (!minutes.has(d.getUTCMinutes()) || !hours.has(d.getUTCHours()) || !months.has(d.getUTCMonth() + 1))
|
|
85
|
+
continue;
|
|
86
|
+
const dayOk = days.has(d.getUTCDate());
|
|
87
|
+
const weekdayOk = weekdays.has(d.getUTCDay()) || (d.getUTCDay() === 0 && weekdays.has(7));
|
|
88
|
+
if (dayRestricted && weekdayRestricted ? dayOk || weekdayOk : dayOk && weekdayOk)
|
|
89
|
+
slots.push(d);
|
|
90
|
+
}
|
|
91
|
+
return slots;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Pairs each scheduled slot with the run that answered it. A run belongs to the latest slot at or before its end
|
|
95
|
+
* time. A slot with no line but a commit before the next slot still counts as run (the agent forgot the log step)
|
|
96
|
+
* and is reported as `pushed`. Anything else is "no run recorded (usage limit, idle or failed)".
|
|
97
|
+
*/
|
|
98
|
+
export function matchSlots(slots, entries, commitTimes = []) {
|
|
99
|
+
const sorted = [...slots].sort((a, b) => a.getTime() - b.getTime());
|
|
100
|
+
const slotFor = (time) => {
|
|
101
|
+
let found = -1;
|
|
102
|
+
for (let i = 0; i < sorted.length && sorted[i].getTime() <= time; i++)
|
|
103
|
+
found = i;
|
|
104
|
+
return found;
|
|
105
|
+
};
|
|
106
|
+
const bySlot = new Map();
|
|
107
|
+
for (const entry of entries) {
|
|
108
|
+
// A long run can end after the next slot starts: walk back to the latest slot nobody has claimed yet.
|
|
109
|
+
for (let i = slotFor(new Date(entry.at).getTime()); i >= 0; i--) {
|
|
110
|
+
if (!bySlot.has(i)) {
|
|
111
|
+
bySlot.set(i, entry);
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const committed = new Set(commitTimes.map((c) => slotFor(c.getTime())).filter((i) => i >= 0));
|
|
117
|
+
return sorted.map((slot, i) => {
|
|
118
|
+
const entry = bySlot.get(i);
|
|
119
|
+
const iso = slot.toISOString();
|
|
120
|
+
if (entry)
|
|
121
|
+
return { slot: iso, entry, outcome: entry.tests && entry.tests.failed > 0 ? 'tests-failed' : entry.pushed ? 'pushed' : 'nothing-pushed' };
|
|
122
|
+
return { slot: iso, outcome: committed.has(i) ? 'pushed' : 'no-run-recorded' };
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
/** How many scheduled runs in a row, newest first, left no trace. The dashboard raises an attention item at 3. */
|
|
126
|
+
export function missedInARow(results) {
|
|
127
|
+
let count = 0;
|
|
128
|
+
for (let i = results.length - 1; i >= 0 && results[i]?.outcome === 'no-run-recorded'; i--)
|
|
129
|
+
count++;
|
|
130
|
+
return count;
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=runs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runs.js","sourceRoot":"","sources":["../../../src/core/parse/runs.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// Task-file parsing. Pure (no Node imports): the live dashboard runs it in the browser.
|
|
2
|
+
/** Reads the `Run: N / LIMIT` line of a task.md; undefined when the line is missing or malformed. */
|
|
3
|
+
export function parseRunCounter(taskText) {
|
|
4
|
+
const match = /^Run:\s*(\d+)\s*\/\s*(\d+)\b/m.exec(taskText);
|
|
5
|
+
if (!match)
|
|
6
|
+
return undefined;
|
|
7
|
+
return { run: Number(match[1]), limit: Number(match[2]) };
|
|
8
|
+
}
|
|
9
|
+
/** Body of the `## <heading>` section (up to the next `## `), matched by heading prefix, case-insensitive. Handoffs are appended, so callers wanting the newest ask for `last`. */
|
|
10
|
+
export function sectionOf(text, heading, which = 'first') {
|
|
11
|
+
const lines = text.split(/\r?\n/);
|
|
12
|
+
const want = heading.toLowerCase();
|
|
13
|
+
const isMatch = (l) => /^##\s/.test(l) && l.replace(/^##\s+/, '').trim().toLowerCase().startsWith(want);
|
|
14
|
+
const start = which === 'last' ? lines.map(isMatch).lastIndexOf(true) : lines.findIndex(isMatch);
|
|
15
|
+
if (start < 0)
|
|
16
|
+
return undefined;
|
|
17
|
+
const rest = lines.slice(start + 1);
|
|
18
|
+
const end = rest.findIndex((l) => /^##\s/.test(l));
|
|
19
|
+
return (end < 0 ? rest : rest.slice(0, end)).join('\n');
|
|
20
|
+
}
|
|
21
|
+
export function parseUnits(taskText) {
|
|
22
|
+
const body = sectionOf(taskText, 'Units');
|
|
23
|
+
if (!body)
|
|
24
|
+
return [];
|
|
25
|
+
const units = [];
|
|
26
|
+
for (const line of body.split(/\r?\n/)) {
|
|
27
|
+
const match = /^(\d+)\.\s+(.+?)\s*$/.exec(line);
|
|
28
|
+
if (match)
|
|
29
|
+
units.push({ number: Number(match[1]), text: match[2] });
|
|
30
|
+
}
|
|
31
|
+
return units;
|
|
32
|
+
}
|
|
33
|
+
/** Parses the parts of a lane's task.md that status screens need. Missing parts are simply absent. */
|
|
34
|
+
export function parseTaskFile(taskText) {
|
|
35
|
+
const status = /^Status:[ \t]*(.*)$/m.exec(taskText)?.[1]?.trim();
|
|
36
|
+
const handoff = sectionOf(taskText, 'Handoff', 'last')
|
|
37
|
+
?.split(/\r?\n/)
|
|
38
|
+
.map((l) => l.trim())
|
|
39
|
+
.find((l) => l !== '');
|
|
40
|
+
const parsed = { units: parseUnits(taskText) };
|
|
41
|
+
const run = parseRunCounter(taskText);
|
|
42
|
+
if (run)
|
|
43
|
+
parsed.run = run;
|
|
44
|
+
if (status)
|
|
45
|
+
parsed.status = status;
|
|
46
|
+
if (handoff)
|
|
47
|
+
parsed.handoffFirstLine = handoff;
|
|
48
|
+
return parsed;
|
|
49
|
+
}
|
|
50
|
+
const NUMBER_LIST = String.raw `\d+(?:\s*[-\u2013]\s*\d+)?(?:\s*(?:,|and|&)\s*\d+(?:\s*[-\u2013]\s*\d+)?)*`;
|
|
51
|
+
const DONE_AFTER = /\b(?:shipped|finished|completed|done)\b[:\s]+([^\n]*)/gi;
|
|
52
|
+
const UNITS_IN_LINE = new RegExp(String.raw `\bunits?\s+(${NUMBER_LIST})\b(?!\s+(?:partly|partially|not))`, 'gi');
|
|
53
|
+
const BARE_LIST = new RegExp(String.raw `^\s*(?:with\s+)?(${NUMBER_LIST})\b(?!\s+(?:partly|partially|not))`, 'i');
|
|
54
|
+
const DONE_BEFORE = new RegExp(String.raw `\bunits?\s+(${NUMBER_LIST})\s+(?:are\s+|is\s+|were\s+)?(?:done|shipped|finished|complete[d]?)\b`, 'gi');
|
|
55
|
+
function expandNumbers(list, into) {
|
|
56
|
+
for (const part of list.split(/,|and|&/)) {
|
|
57
|
+
const range = /^\s*(\d+)(?:\s*[-\u2013]\s*(\d+))?\s*$/.exec(part);
|
|
58
|
+
if (!range)
|
|
59
|
+
continue;
|
|
60
|
+
const from = Number(range[1]);
|
|
61
|
+
const to = range[2] === undefined ? from : Number(range[2]);
|
|
62
|
+
for (let n = from; n <= Math.min(to, from + 200); n++)
|
|
63
|
+
into.add(n);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Unit numbers a lane says it finished, read from its `Status:` line and newest Handoff, e.g.
|
|
68
|
+
* "units 1-3 done" or "Run 2 shipped units 1, 2 and 4". "unit 5 partly" does not count.
|
|
69
|
+
*/
|
|
70
|
+
export function doneUnits(taskText) {
|
|
71
|
+
const status = /^Status:[ \t]*(.*)$/m.exec(taskText)?.[1] ?? '';
|
|
72
|
+
const handoff = sectionOf(taskText, 'Handoff', 'last') ?? '';
|
|
73
|
+
// Only the lines before "Next:" describe finished work; what follows plans it.
|
|
74
|
+
const finished = handoff.split(/\r?\n/).filter((l) => !/^\s*next\b/i.test(l)).join('\n');
|
|
75
|
+
const done = new Set();
|
|
76
|
+
for (const text of [status, finished]) {
|
|
77
|
+
for (const m of text.matchAll(DONE_BEFORE))
|
|
78
|
+
expandNumbers(m[1], done);
|
|
79
|
+
for (const m of text.matchAll(DONE_AFTER)) {
|
|
80
|
+
const rest = m[1];
|
|
81
|
+
const named = [...rest.matchAll(UNITS_IN_LINE)];
|
|
82
|
+
for (const u of named)
|
|
83
|
+
expandNumbers(u[1], done);
|
|
84
|
+
const bare = named.length === 0 ? BARE_LIST.exec(rest) : null;
|
|
85
|
+
if (bare)
|
|
86
|
+
expandNumbers(bare[1], done);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return done;
|
|
90
|
+
}
|
|
91
|
+
/** Units not yet reported done, in file order. */
|
|
92
|
+
export function openUnits(taskText) {
|
|
93
|
+
const done = doneUnits(taskText);
|
|
94
|
+
return parseUnits(taskText).filter((u) => !done.has(u.number));
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=task.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"task.js","sourceRoot":"","sources":["../../../src/core/parse/task.ts"],"names":[],"mappings":"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"}
|
package/dist/core/repo.js
CHANGED
|
@@ -2,14 +2,9 @@ import { execFile } from 'node:child_process';
|
|
|
2
2
|
import { readdir, readFile, stat } from 'node:fs/promises';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { promisify } from 'node:util';
|
|
5
|
+
import { parseRunCounter } from './parse/task.js';
|
|
5
6
|
const execFileAsync = promisify(execFile);
|
|
6
|
-
|
|
7
|
-
export function parseRunCounter(taskText) {
|
|
8
|
-
const match = /^Run:\s*(\d+)\s*\/\s*(\d+)\b/m.exec(taskText);
|
|
9
|
-
if (!match)
|
|
10
|
-
return undefined;
|
|
11
|
-
return { run: Number(match[1]), limit: Number(match[2]) };
|
|
12
|
-
}
|
|
7
|
+
export { parseRunCounter };
|
|
13
8
|
/** Parses the single UTC timestamp line of `.ai/session.lock`; undefined when it is not a valid date. */
|
|
14
9
|
export function parseLockTime(lockText) {
|
|
15
10
|
const line = lockText.trim().split(/\r?\n/)[0]?.trim();
|
package/dist/core/repo.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"repo.js","sourceRoot":"","sources":["../../src/core/repo.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"repo.js","sourceRoot":"","sources":["../../src/core/repo.ts"],"names":[],"mappings":"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"}
|
package/dist/core/runPrompt.js
CHANGED
|
@@ -2,9 +2,9 @@ import { fillPlaceholders } from './template.js';
|
|
|
2
2
|
/** Used only when neither the repo nor the package ships a prompt file. A copy of runners/routine/prompt.md; a test keeps them equal. */
|
|
3
3
|
export const DEFAULT_PROMPT = `You are one scheduled run of the build loop for this repository ({{name}}). Nobody is watching; the owner reads the results later. Read CLAUDE.md and follow it exactly, especially "Unattended mode". IDEA.md is the brief. Your task file is \`.ai/task.md\`.
|
|
4
4
|
|
|
5
|
-
0. Run \`date -u\` and note it as this run's start time. Run \`git checkout main && git pull --ff-only\`. Every commit goes to \`main\` and is pushed with \`git push origin main\`. Never create or push a \`claude/\` branch: the next run starts from \`main\` and would never see it. If a push is rejected, run \`git pull --rebase\`, re-run the checks if incoming commits touched code, and push again.
|
|
5
|
+
0. Run \`date -u\` and note it as this run's start time. Run \`git checkout main && git pull --ff-only\`. If that pull fails because local \`main\` and \`origin/main\` have diverged or look unrelated, the sandbox started from a stale cached clone; origin \`main\` is never rewritten, so do not reset anything: run \`git fetch origin main && git switch -c lane-run FETCH_HEAD\`, work there, and wherever these steps say pull or push use \`git pull --rebase origin main\` and \`git push origin HEAD:main\`. Every commit goes to \`main\` and is pushed with \`git push origin main\`. Never create or push a \`claude/\` branch: the next run starts from \`main\` and would never see it. If a push is rejected, run \`git pull --rebase\`, re-run the checks if incoming commits touched code, and push again.
|
|
6
6
|
1. Session lock: if \`.ai/session.lock\` holds a UTC time less than 90 minutes old, another session owns the repo, unless the lock is your own (the runner's run note says so when the loop runner created it). Otherwise reply "Nothing to do" and stop. If the file is older than that, it is stale: ignore it.
|
|
7
|
-
2. If \`DONE.md\` or \`BLOCKED.md\` exists, reply "Nothing to do" and stop.
|
|
7
|
+
2. If \`DONE.md\` or \`BLOCKED.md\` exists, reply "Nothing to do" and stop. Pause: if \`.ai/PAUSED\` exists, the owner paused the loop: reply "Paused" and stop.
|
|
8
8
|
3. Run the install command from CLAUDE.md (\`npm install\` unless it says otherwise). If it fails, write \`BLOCKED.md\` with the error and stop.
|
|
9
9
|
4. If \`.ai/task.md\` exists, run the resume protocol (.ai/method.md §9.2) and add 1 to its \`Run: N / 30\` counter. When the counter reaches 30, write \`BLOCKED.md\` saying "run budget reached", push, and stop. If there is no task file, write one from IDEA.md first.
|
|
10
10
|
5. Run the test command from CLAUDE.md (\`npm test\` unless it says otherwise) on the fresh pull. If it fails, fixing it is your first unit ("main stays green"). Otherwise take the next unit from the task file. Do one unit at a time.
|
|
@@ -13,6 +13,12 @@ export const DEFAULT_PROMPT = `You are one scheduled run of the build loop for t
|
|
|
13
13
|
8. Run the handoff (§9.1) in the task file so a fresh run with zero memory can continue at full quality. Commit it, push to \`main\`, and stop. If every done-when in the contract passes and the reviewer finds no blockers, run the retro (§12), write \`DONE.md\` (what was built, test results, anything unsure, the 10 best next improvements ranked by value) and push instead.
|
|
14
14
|
|
|
15
15
|
Never spend money, sign up for anything, create accounts, publish anything, add API keys, secrets, ads, analytics or tracking, copy other projects' content without a compatible licence, force-push, rewrite history, or touch anything outside this repository.`;
|
|
16
|
+
/** The claim rule for a lane run (IDEA.md Round 3): a teammate's unexpired claim means this lane is taken. */
|
|
17
|
+
export function claimRule(lane) {
|
|
18
|
+
return `Claim: if \`.ai/lanes/${lane}/claim\` exists (one line, \`name · expires <UTC ISO>\`), names someone other than you (compare with \`git config user.name\`; an unattended scheduled run has no name, so any unexpired claim stops it), and its expiry is later than \`date -u\`, a teammate is working this lane: reply "Nothing to do" and stop. An expired claim counts as none; leave the file, \`loop claim\` overwrites it.`;
|
|
19
|
+
}
|
|
20
|
+
/** The pause rule (IDEA.md Round 4 B1): `.ai/PAUSED` stops every run like a root DONE.md. */
|
|
21
|
+
export const PAUSED_RULE = 'Pause: if `.ai/PAUSED` exists, the owner paused the loop: reply "Paused" and stop.';
|
|
16
22
|
/** Fills `{{name}}`-style placeholders and normalises the ending. */
|
|
17
23
|
export function buildRunPrompt(promptText, vars) {
|
|
18
24
|
return `${fillPlaceholders(promptText, vars).trim()}\n`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runPrompt.js","sourceRoot":"","sources":["../../src/core/runPrompt.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEjD,yIAAyI;AACzI,MAAM,CAAC,MAAM,cAAc,GAAG;;;;;;;;;;;;kQAYoO,CAAC;AAEnQ,qEAAqE;AACrE,MAAM,UAAU,cAAc,CAAC,UAAkB,EAAE,IAAsC;IACvF,OAAO,GAAG,gBAAgB,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC;AAC1D,CAAC;AAQD,qJAAqJ;AACrJ,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,CAAU,CAAC;AAE5G,oGAAoG;AACpG,MAAM,UAAU,eAAe,CAAC,MAAc,EAAE,OAA0B;IACxE,OAAO;QACL,IAAI;QACJ,MAAM;QACN,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACpD,GAAG,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,gCAAgC,CAAC,CAAC,CAAC,CAAC,CAAC,mBAAmB,EAAE,aAAa,EAAE,gBAAgB,EAAE,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;KAClJ,CAAC;AACJ,CAAC"}
|
|
1
|
+
{"version":3,"file":"runPrompt.js","sourceRoot":"","sources":["../../src/core/runPrompt.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEjD,yIAAyI;AACzI,MAAM,CAAC,MAAM,cAAc,GAAG;;;;;;;;;;;;kQAYoO,CAAC;AAEnQ,8GAA8G;AAC9G,MAAM,UAAU,SAAS,CAAC,IAAY;IACpC,OAAO,yBAAyB,IAAI,qYAAqY,CAAC;AAC5a,CAAC;AAED,6FAA6F;AAC7F,MAAM,CAAC,MAAM,WAAW,GAAG,oFAAoF,CAAC;AAEhH,qEAAqE;AACrE,MAAM,UAAU,cAAc,CAAC,UAAkB,EAAE,IAAsC;IACvF,OAAO,GAAG,gBAAgB,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC;AAC1D,CAAC;AAQD,qJAAqJ;AACrJ,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,CAAU,CAAC;AAE5G,oGAAoG;AACpG,MAAM,UAAU,eAAe,CAAC,MAAc,EAAE,OAA0B;IACxE,OAAO;QACL,IAAI;QACJ,MAAM;QACN,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACpD,GAAG,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,gCAAgC,CAAC,CAAC,CAAC,CAAC,CAAC,mBAAmB,EAAE,aAAa,EAAE,gBAAgB,EAAE,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;KAClJ,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// "Is it ready to ship?" as a checklist (IDEA.md Round 4 B4). Pure and browser-safe: the CLI and the dashboard share it.
|
|
2
|
+
import { listItems } from './brief.js';
|
|
3
|
+
import { parseChangelog, releaseFor } from './parse/changelog.js';
|
|
4
|
+
const FINDINGS_HEADING = /^#{1,6}\s+.*\b(open findings?|minor findings|unsure|not verified|known (?:issues|problems)|still open)\b/i;
|
|
5
|
+
/** Bullets under DONE.md headings that name open or uncertain things. */
|
|
6
|
+
export function openFindings(doneText) {
|
|
7
|
+
const lines = doneText.split(/\r?\n/);
|
|
8
|
+
const found = [];
|
|
9
|
+
for (let i = 0; i < lines.length; i++) {
|
|
10
|
+
if (!FINDINGS_HEADING.test(lines[i]))
|
|
11
|
+
continue;
|
|
12
|
+
const rest = lines.slice(i + 1);
|
|
13
|
+
const end = rest.findIndex((l) => /^#{1,6}\s/.test(l));
|
|
14
|
+
found.push(...listItems((end === -1 ? rest : rest.slice(0, end)).join('\n')));
|
|
15
|
+
}
|
|
16
|
+
return found;
|
|
17
|
+
}
|
|
18
|
+
const NONE = /^(none|nothing|n\/a|no (?:open )?findings?)\b/i;
|
|
19
|
+
export function shipCheck(facts) {
|
|
20
|
+
const findings = facts.doneText === undefined ? [] : openFindings(facts.doneText).filter((f) => !NONE.test(f));
|
|
21
|
+
const blockers = findings.filter((f) => /\bblocker\b/i.test(f) && !/\b(?:no|not an?|non-?|zero|without)\s+(?:open\s+)?blockers?\b/i.test(f));
|
|
22
|
+
const release = facts.changelogText === undefined ? undefined : releaseFor(parseChangelog(facts.changelogText), facts.version);
|
|
23
|
+
const unreleased = facts.changelogText === undefined ? undefined : releaseFor(parseChangelog(facts.changelogText), 'Unreleased');
|
|
24
|
+
const changelogOk = (release !== undefined && release.items.length > 0) || (unreleased !== undefined && unreleased.items.length > 0);
|
|
25
|
+
const tests = facts.tests;
|
|
26
|
+
const items = [
|
|
27
|
+
{ id: 'done', label: 'Round is done', ok: facts.doneText !== undefined, detail: facts.doneText !== undefined ? 'DONE.md is present.' : 'No root DONE.md: the control room writes it when every lane is done.' },
|
|
28
|
+
{
|
|
29
|
+
id: 'findings', label: 'No open blockers', ok: blockers.length === 0,
|
|
30
|
+
detail: blockers.length > 0 ? `${blockers.length} open blocker${blockers.length === 1 ? '' : 's'} in DONE.md.` : findings.length > 0 ? `${findings.length} open finding${findings.length === 1 ? '' : 's'} listed in DONE.md, none a blocker.` : 'DONE.md lists no open findings.',
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
id: 'changelog', label: 'CHANGELOG has this release', ok: changelogOk,
|
|
34
|
+
detail: release && release.items.length > 0 ? `[${facts.version}] has ${release.items.length} entries.` : unreleased && unreleased.items.length > 0 ? `[Unreleased] has ${unreleased.items.length} entries to move under ${facts.version}.` : `No [${facts.version}] section and nothing under [Unreleased].`,
|
|
35
|
+
},
|
|
36
|
+
{ id: 'tag', label: 'Tag is free', ok: !facts.tagExists, detail: facts.tagExists ? `Tag v${facts.version} already exists: bump the version first.` : `Tag v${facts.version} does not exist yet.` },
|
|
37
|
+
{ id: 'clean', label: 'Working tree is clean', ok: facts.treeClean, detail: facts.treeClean ? 'No uncommitted changes.' : 'Uncommitted changes: commit or discard them.' },
|
|
38
|
+
{
|
|
39
|
+
id: 'tests', label: 'Tests pass', ok: tests !== undefined && tests.failed === 0 && tests.passed > 0,
|
|
40
|
+
detail: tests === undefined ? 'No test result yet: lane runs record one with `loop runs log --passed N --failed N`, or `npm test` can write .ai/last-test.json.' : tests.failed > 0 ? `${tests.failed} failing in the latest result${tests.at ? ` (${tests.at})` : ''}.` : tests.passed === 0 ? 'The latest result ran no tests.' : `${tests.passed} passed${tests.at ? ` (${tests.at})` : ''}.`,
|
|
41
|
+
},
|
|
42
|
+
];
|
|
43
|
+
return { ready: items.every((i) => i.ok), items, openFindings: findings };
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=shipCheck.js.map
|