compounding-loop 0.2.0 → 0.5.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 +19 -3
- package/dist/cli/claim.js +84 -0
- package/dist/cli/claim.js.map +1 -0
- package/dist/cli/draft.js +69 -0
- package/dist/cli/draft.js.map +1 -0
- package/dist/cli/index.js +284 -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/lanesInit.js +8 -2
- package/dist/cli/lanesInit.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/pace.js +41 -0
- package/dist/cli/pace.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 +118 -0
- package/dist/cli/runs.js.map +1 -0
- package/dist/cli/schedule.js +177 -0
- package/dist/cli/schedule.js.map +1 -0
- package/dist/cli/shipCheck.js +59 -0
- package/dist/cli/shipCheck.js.map +1 -0
- package/dist/core/brief.js +35 -12
- package/dist/core/brief.js.map +1 -1
- package/dist/core/draftBrief.js +68 -0
- package/dist/core/draftBrief.js.map +1 -0
- package/dist/core/laneRound.js +63 -0
- package/dist/core/laneRound.js.map +1 -0
- package/dist/core/lanes.js +15 -2
- package/dist/core/lanes.js.map +1 -1
- package/dist/core/lanesInit.js +19 -0
- package/dist/core/lanesInit.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 +17 -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/outcomes.js +51 -0
- package/dist/core/parse/outcomes.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/policy.js +56 -0
- package/dist/core/parse/policy.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/schedule.js +101 -0
- package/dist/core/parse/schedule.js.map +1 -0
- package/dist/core/parse/steer.js +68 -0
- package/dist/core/parse/steer.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 +18 -8
- 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/actionBody.js +54 -0
- package/dist/server/actionBody.js.map +1 -0
- package/dist/server/checkLabels.js +32 -0
- package/dist/server/checkLabels.js.map +1 -0
- package/dist/server/claude.js +18 -0
- package/dist/server/claude.js.map +1 -0
- package/dist/server/controls.js +99 -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/draftInputs.js +33 -0
- package/dist/server/draftInputs.js.map +1 -0
- package/dist/server/health.js +9 -30
- package/dist/server/health.js.map +1 -1
- package/dist/server/index.js +77 -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/laneSchedule.js +15 -0
- package/dist/server/laneSchedule.js.map +1 -0
- package/dist/server/live/build.js +178 -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 +11 -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 +61 -0
- package/dist/server/live/urls.js.map +1 -0
- package/dist/server/loops.js +263 -41
- package/dist/server/loops.js.map +1 -1
- package/dist/server/paceNote.js +13 -0
- package/dist/server/paceNote.js.map +1 -0
- package/dist/server/roundState.js +51 -0
- package/dist/server/roundState.js.map +1 -0
- package/dist/server/runs.js +144 -0
- package/dist/server/runs.js.map +1 -0
- package/dist/server/shipped.js +35 -0
- package/dist/server/shipped.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 +2818 -106
- package/dist/web/assets/index-BozIcdeb.js +88 -0
- package/dist/web/assets/index-Te_olhNA.css +2 -0
- package/dist/web/index.html +2 -2
- package/method/COMPOUNDING_LOOP.md +26 -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 +55 -2
- 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 +5 -1
- package/runners/routine/control-room-prompt.md +4 -14
- package/runners/routine/lane-prompt.md +4 -10
- package/runners/routine/prompt.md +2 -2
- package/templates/lanes/README.md +5 -1
- package/templates/lanes/claude-section.md +5 -1
- package/templates/lanes/control-run.md +37 -0
- package/templates/lanes/lane-run.md +33 -0
- package/templates/lanes/schedule.json +4 -0
- package/templates/lanes/steer.md +3 -0
- 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
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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shipCheck.js","sourceRoot":"","sources":["../../src/core/shipCheck.ts"],"names":[],"mappings":"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"}
|
package/dist/core/table.js
CHANGED
|
@@ -28,14 +28,16 @@ export function formatLanes(lanes, now = new Date()) {
|
|
|
28
28
|
const minutes = Math.max(0, Math.round((now.getTime() - new Date(iso).getTime()) / 60_000));
|
|
29
29
|
return minutes < 60 ? `${minutes}m ago` : minutes < 2880 ? `${Math.round(minutes / 60)}h ago` : `${Math.round(minutes / 1440)}d ago`;
|
|
30
30
|
};
|
|
31
|
+
const anyClaim = lanes.some((l) => l.claim);
|
|
31
32
|
const rows = lanes.map((l) => [
|
|
32
33
|
l.name,
|
|
33
34
|
`${LANE_ICON[l.state]} ${l.state}`,
|
|
34
35
|
l.limit > 0 ? `run ${l.run}/${l.limit}` : 'run -',
|
|
35
36
|
l.lastCommit ? `${ago(l.lastCommit.at)}: ${l.lastCommit.subject.slice(0, 48)}` : 'no lane commits yet',
|
|
36
37
|
l.waitingOn?.length ? `waits on ${l.waitingOn.join(', ')}` : l.unanswered.length ? `${l.unanswered.length} unanswered` : '',
|
|
38
|
+
...(anyClaim ? [l.claim ? `${l.claim.name} until ${l.claim.expiresAt.slice(11, 16)} UTC` : ''] : []),
|
|
37
39
|
]);
|
|
38
|
-
const header = ['LANE', 'STATUS', 'RUN', 'LAST COMMIT', 'MESSAGES'];
|
|
40
|
+
const header = ['LANE', 'STATUS', 'RUN', 'LAST COMMIT', 'MESSAGES', ...(anyClaim ? ['CLAIM'] : [])];
|
|
39
41
|
const widths = header.map((h, i) => Math.max(h.length, ...rows.map((r) => (r[i] ?? '').length)));
|
|
40
42
|
const render = (cols) => cols.map((c, i) => c.padEnd(widths[i] ?? 0)).join(' ').trimEnd();
|
|
41
43
|
return [render(header), ...rows.map(render)].join('\n');
|
package/dist/core/table.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"table.js","sourceRoot":"","sources":["../../src/core/table.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"table.js","sourceRoot":"","sources":["../../src/core/table.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// Pure: turns the JSON body of a steer or schedule POST into a typed action, or says what is wrong with it (HTTP 400).
|
|
2
|
+
import { PACES, isPace } from '../core/parse/policy.js';
|
|
3
|
+
const nonEmpty = (value) => typeof value === 'string' && value.trim() !== '';
|
|
4
|
+
const asRecord = (body) => (typeof body === 'object' && body !== null ? body : {});
|
|
5
|
+
export function parseSteerBody(body) {
|
|
6
|
+
const { lane, text, standing, author } = asRecord(body);
|
|
7
|
+
if (!nonEmpty(lane))
|
|
8
|
+
return { ok: false, error: 'lane must be a non-empty string' };
|
|
9
|
+
// `all` only means every lane for a schedule wake; it is never a lane to steer.
|
|
10
|
+
if (lane === 'all')
|
|
11
|
+
return { ok: false, error: 'steer one lane at a time; "all" is not a lane' };
|
|
12
|
+
if (!nonEmpty(text))
|
|
13
|
+
return { ok: false, error: 'text must be a non-empty string' };
|
|
14
|
+
if (standing !== undefined && typeof standing !== 'boolean')
|
|
15
|
+
return { ok: false, error: 'standing must be true or false' };
|
|
16
|
+
return { ok: true, value: { lane, text, standing: standing === true, ...(nonEmpty(author) && { author }) } };
|
|
17
|
+
}
|
|
18
|
+
export function parseScheduleBody(body) {
|
|
19
|
+
const { action, lane, until, reason } = asRecord(body);
|
|
20
|
+
if (!nonEmpty(lane))
|
|
21
|
+
return { ok: false, error: 'lane must be a non-empty string' };
|
|
22
|
+
const why = nonEmpty(reason) ? { reason } : {};
|
|
23
|
+
switch (action) {
|
|
24
|
+
case 'pause':
|
|
25
|
+
return { ok: true, value: { action, lane, ...why } };
|
|
26
|
+
case 'snooze':
|
|
27
|
+
if (!nonEmpty(until))
|
|
28
|
+
return { ok: false, error: 'until must be an ISO time, a date or a span such as 3h' };
|
|
29
|
+
return { ok: true, value: { action, lane, until, ...why } };
|
|
30
|
+
case 'resume':
|
|
31
|
+
case 'wake':
|
|
32
|
+
return { ok: true, value: { action, lane } };
|
|
33
|
+
default:
|
|
34
|
+
return { ok: false, error: 'action must be pause, resume, snooze or wake' };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export function parsePaceBody(body) {
|
|
38
|
+
const { pace, lane } = asRecord(body);
|
|
39
|
+
if (!isPace(pace))
|
|
40
|
+
return { ok: false, error: `pace must be one of: ${PACES.join(', ')}` };
|
|
41
|
+
if (lane !== undefined && (!nonEmpty(lane) || lane === 'all'))
|
|
42
|
+
return { ok: false, error: 'lane must be a lane name; leave it out to set the whole loop' };
|
|
43
|
+
return { ok: true, value: { pace, ...(lane !== undefined && { lane }) } };
|
|
44
|
+
}
|
|
45
|
+
/** An optional `model` for `claude -p --model`; it is an argument, never a shell string, but keep it to model-id characters anyway. */
|
|
46
|
+
export function parseDraftBody(body) {
|
|
47
|
+
const { model } = asRecord(body);
|
|
48
|
+
if (model === undefined)
|
|
49
|
+
return { ok: true, value: {} };
|
|
50
|
+
if (typeof model !== 'string' || !/^[A-Za-z0-9][A-Za-z0-9._:[\]-]{0,63}$/.test(model))
|
|
51
|
+
return { ok: false, error: 'model must be a model id such as claude-opus-5-5' };
|
|
52
|
+
return { ok: true, value: { model } };
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=actionBody.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"actionBody.js","sourceRoot":"","sources":["../../src/server/actionBody.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const LABELS = {
|
|
2
|
+
leak: ['No leaks', 'Leak found'],
|
|
3
|
+
'stale-lock': ['No stale lock', 'Stale lock'],
|
|
4
|
+
'short-runs': ['Runs do real work', 'Runs end in seconds'],
|
|
5
|
+
'failing-tests': ['Tests pass', 'Tests failing'],
|
|
6
|
+
'runner-silent': ['Runner active', 'Runner silent'],
|
|
7
|
+
'lane-stalled': ['Lanes moving', 'Lane stalled'],
|
|
8
|
+
'lane-waiting-on-finished': ['No lane waits on a finished lane', 'Lane waits on a finished lane'],
|
|
9
|
+
};
|
|
10
|
+
/** What a check states as its result, never just its name ("Tests failing", not "OK · Tests failing"). */
|
|
11
|
+
export function checkLabel(kind, ok) {
|
|
12
|
+
return LABELS[kind][ok ? 0 : 1];
|
|
13
|
+
}
|
|
14
|
+
const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
|
15
|
+
const two = (n) => String(n).padStart(2, '0');
|
|
16
|
+
/** `11 Oct 14:53 UTC` for an ISO time; anything that is not a date is returned as given. */
|
|
17
|
+
export function formatUtc(iso) {
|
|
18
|
+
const d = typeof iso === 'string' ? new Date(iso) : iso;
|
|
19
|
+
if (Number.isNaN(d.getTime()))
|
|
20
|
+
return String(iso);
|
|
21
|
+
return `${d.getUTCDate()} ${MONTHS[d.getUTCMonth()]} ${two(d.getUTCHours())}:${two(d.getUTCMinutes())} UTC`;
|
|
22
|
+
}
|
|
23
|
+
/** The three-way result: an explicit `state` wins, else it follows `ok`. */
|
|
24
|
+
export function checkState(check) {
|
|
25
|
+
return check.state ?? (check.ok ? 'pass' : 'fail');
|
|
26
|
+
}
|
|
27
|
+
/** Adds the result-stating `label` and the `state` to a check (with or without its `loopId`); an existing label is kept. */
|
|
28
|
+
export function withLabel(check) {
|
|
29
|
+
const state = checkState(check);
|
|
30
|
+
return { ...check, state, label: check.label ?? (state === 'unchecked' ? 'Not checked' : checkLabel(check.kind, check.ok)) };
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=checkLabels.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checkLabels.js","sourceRoot":"","sources":["../../src/server/checkLabels.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,GAAuE;IACjF,IAAI,EAAE,CAAC,UAAU,EAAE,YAAY,CAAC;IAChC,YAAY,EAAE,CAAC,eAAe,EAAE,YAAY,CAAC;IAC7C,YAAY,EAAE,CAAC,mBAAmB,EAAE,qBAAqB,CAAC;IAC1D,eAAe,EAAE,CAAC,YAAY,EAAE,eAAe,CAAC;IAChD,eAAe,EAAE,CAAC,eAAe,EAAE,eAAe,CAAC;IACnD,cAAc,EAAE,CAAC,cAAc,EAAE,cAAc,CAAC;IAChD,0BAA0B,EAAE,CAAC,kCAAkC,EAAE,+BAA+B,CAAC;CAClG,CAAC;AAEF,0GAA0G;AAC1G,MAAM,UAAU,UAAU,CAAC,IAAyB,EAAE,EAAW;IAC/D,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAClC,CAAC;AAED,MAAM,MAAM,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,CAAU,CAAC;AAC7G,MAAM,GAAG,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;AAEtD,4FAA4F;AAC5F,MAAM,UAAU,SAAS,CAAC,GAAkB;IAC1C,MAAM,CAAC,GAAG,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;IACxD,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;QAAE,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;IAClD,OAAO,GAAG,CAAC,CAAC,UAAU,EAAE,IAAI,MAAM,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC,MAAM,CAAC;AAC9G,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,UAAU,CAAC,KAAiE;IAC1F,OAAO,KAAK,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;AACrD,CAAC;AAED,4HAA4H;AAC5H,MAAM,UAAU,SAAS,CAAkH,KAAQ;IACjJ,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;IAChC,OAAO,EAAE,GAAG,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,IAAI,CAAC,KAAK,KAAK,WAAW,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;AAC/H,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { resolveInvocation } from '../cli/run.js';
|
|
3
|
+
export const abortableClaude = async (rawArgs, cwd, signal) => {
|
|
4
|
+
if (signal?.aborted)
|
|
5
|
+
throw new Error('The draft was stopped.');
|
|
6
|
+
const { command, args } = await resolveInvocation('claude', rawArgs);
|
|
7
|
+
return new Promise((resolve, reject) => {
|
|
8
|
+
execFile(command, args, { cwd, maxBuffer: 16 * 1024 * 1024, timeout: 15 * 60_000, ...(signal && { signal }) }, (error, stdout) => {
|
|
9
|
+
if (!error)
|
|
10
|
+
resolve(stdout);
|
|
11
|
+
else if (signal?.aborted)
|
|
12
|
+
reject(new Error('The draft was stopped.'));
|
|
13
|
+
else
|
|
14
|
+
reject(new Error(`Could not run \`claude -p\`: ${error.message}. Is Claude Code installed, on your PATH and signed in? \`loop next-round --draft --print-prompt\` prints the prompt to paste yourself.`));
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
};
|
|
18
|
+
//# sourceMappingURL=claude.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude.js","sourceRoot":"","sources":["../../src/server/claude.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAKlD,MAAM,CAAC,MAAM,eAAe,GAAoB,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE;IAC7E,IAAI,MAAM,EAAE,OAAO;QAAE,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC;IAC/D,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,MAAM,iBAAiB,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IACrE,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,QAAQ,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,SAAS,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI,EAAE,OAAO,EAAE,EAAE,GAAG,MAAM,EAAE,GAAG,CAAC,MAAM,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE;YAC/H,IAAI,CAAC,KAAK;gBAAE,OAAO,CAAC,MAAM,CAAC,CAAC;iBACvB,IAAI,MAAM,EAAE,OAAO;gBAAE,MAAM,CAAC,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC,CAAC;;gBACjE,MAAM,CAAC,IAAI,KAAK,CAAC,gCAAgC,KAAK,CAAC,OAAO,yIAAyI,CAAC,CAAC,CAAC;QACjN,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// Pure (no Node imports): the GitHub pages the static dashboard opens instead of holding a token. Every URL is a
|
|
2
|
+
// function of the repo slug, so a user with write access to the repo is the only one who can finish the action.
|
|
3
|
+
import { POLICY_FILE, formatPaused, formatPolicy, formatSchedule, withPace, formatSteerEntry, PAUSED_FILE, SCHEDULE_FILE, steerPath } from '../core/parse/index.js';
|
|
4
|
+
import { isRepoSlug } from './live/urls.js';
|
|
5
|
+
export const RELEASE_WORKFLOW = 'release.yml';
|
|
6
|
+
export const NEXT_ROUND_FILE = '.ai/next-round.md';
|
|
7
|
+
const GITHUB = 'https://github.com';
|
|
8
|
+
const API = 'https://api.github.com';
|
|
9
|
+
export const NPM_REGISTRY = 'https://registry.npmjs.org';
|
|
10
|
+
function checkedSlug(slug) {
|
|
11
|
+
if (!isRepoSlug(slug))
|
|
12
|
+
throw new Error(`not a GitHub repo slug: ${slug}`);
|
|
13
|
+
return slug;
|
|
14
|
+
}
|
|
15
|
+
/** A repo-relative path with no way out of the repo (no `..`, no leading slash, no backslash). */
|
|
16
|
+
function checkedPath(file) {
|
|
17
|
+
if (file === '' || file.startsWith('/') || file.includes('\\') || file.split('/').some((part) => part === '..' || part === ''))
|
|
18
|
+
throw new Error(`not a repo file path: ${file}`);
|
|
19
|
+
return file;
|
|
20
|
+
}
|
|
21
|
+
/** A lane name is a single path segment of simple characters (the same rule core applies). */
|
|
22
|
+
function checkedLane(lane) {
|
|
23
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9_-]*$/.test(lane))
|
|
24
|
+
throw new Error(`not a lane name: ${lane}`);
|
|
25
|
+
return lane;
|
|
26
|
+
}
|
|
27
|
+
/** GitHub answers 414 above roughly 8 KB; stay well under it. */
|
|
28
|
+
export const MAX_URL_LENGTH = 7500;
|
|
29
|
+
/** A branch name keeps its slashes (`feature/x`) and encodes every other character. */
|
|
30
|
+
const encodeBranch = (branch) => branch.split('/').map(encodeURIComponent).join('/');
|
|
31
|
+
const oneLine = (text) => text.replace(/\s+/g, ' ').trim();
|
|
32
|
+
const encodePath = (file) => checkedPath(file).split('/').map(encodeURIComponent).join('/');
|
|
33
|
+
/** GitHub's "new file" page with the file name and content filled in. */
|
|
34
|
+
export function newFileUrl(slug, file, content, branch = 'main') {
|
|
35
|
+
const url = `${GITHUB}/${checkedSlug(slug)}/new/${encodeBranch(branch)}?filename=${encodeURIComponent(checkedPath(file))}&value=${encodeURIComponent(content)}`;
|
|
36
|
+
// The caller falls back to "copy the text, then open the blank new-file page".
|
|
37
|
+
if (url.length > MAX_URL_LENGTH)
|
|
38
|
+
throw new RangeError(`content too long for a prefilled GitHub URL (${url.length} > ${MAX_URL_LENGTH})`);
|
|
39
|
+
return url;
|
|
40
|
+
}
|
|
41
|
+
/** GitHub's delete page for a file (the confirmation commit happens on GitHub). */
|
|
42
|
+
export function deleteFileUrl(slug, file, branch = 'main') {
|
|
43
|
+
return `${GITHUB}/${checkedSlug(slug)}/delete/${encodeBranch(branch)}/${encodePath(file)}`;
|
|
44
|
+
}
|
|
45
|
+
/** GitHub's web editor for a file, e.g. BLOCKED.md. */
|
|
46
|
+
export function editFileUrl(slug, file, branch = 'main') {
|
|
47
|
+
return `${GITHUB}/${checkedSlug(slug)}/edit/${encodeBranch(branch)}/${encodePath(file)}`;
|
|
48
|
+
}
|
|
49
|
+
/** Pause: the new-file page for `.ai/PAUSED` with the one-line content core writes (`who · paused <ISO>[ · reason]`). */
|
|
50
|
+
export function pauseUrl(slug, by, now, reason, branch = 'main') {
|
|
51
|
+
return newFileUrl(slug, PAUSED_FILE, formatPaused(oneLine(by) || 'dashboard', now, (reason && oneLine(reason)) || undefined), branch);
|
|
52
|
+
}
|
|
53
|
+
/** Resume: the delete page for `.ai/PAUSED`. */
|
|
54
|
+
export const resumeUrl = (slug, branch = 'main') => deleteFileUrl(slug, PAUSED_FILE, branch);
|
|
55
|
+
/** Start next round: the new-file page for `.ai/next-round.md` holding the brief; the next-round workflow takes over on push. */
|
|
56
|
+
export function nextRoundUrl(slug, brief, branch = 'main') {
|
|
57
|
+
return newFileUrl(slug, NEXT_ROUND_FILE, brief.endsWith('\n') ? brief : `${brief}\n`, branch);
|
|
58
|
+
}
|
|
59
|
+
/** Steer, file missing: the new-file page for the lane's `steer.md` holding the intro line and the first entry. */
|
|
60
|
+
export function steerNewFileUrl(slug, lane, entry, branch = 'main') {
|
|
61
|
+
return newFileUrl(slug, steerPath(checkedLane(lane)), `# Steering for the ${lane} lane\n\n${formatSteerEntry(entry)}`, branch);
|
|
62
|
+
}
|
|
63
|
+
/** Steer, file exists: GitHub cannot prefill an edit, so the UI copies `formatSteerEntry` and opens this editor page. */
|
|
64
|
+
export const steerEditUrl = (slug, lane, branch = 'main') => editFileUrl(slug, steerPath(checkedLane(lane)), branch);
|
|
65
|
+
/** Schedule edit: the editor page for `.ai/schedule.json`; the UI copies `scheduleEditText` to paste over the file. */
|
|
66
|
+
export const scheduleEditUrl = (slug, branch = 'main') => editFileUrl(slug, SCHEDULE_FILE, branch);
|
|
67
|
+
/** The whole `schedule.json` text after the edit, in the exact format `loop schedule` writes. */
|
|
68
|
+
export const scheduleEditText = (schedule) => formatSchedule(schedule);
|
|
69
|
+
/** Pace edit: the editor page for `.ai/policy.json`; the UI copies `paceEditText` to paste over the file. */
|
|
70
|
+
export const paceEditUrl = (slug, branch = 'main') => editFileUrl(slug, POLICY_FILE, branch);
|
|
71
|
+
/** The whole `policy.json` text after setting the loop's pace (or one lane's), in the exact format `loop pace` writes. */
|
|
72
|
+
export const paceEditText = (policy, pace, lane) => formatPolicy(withPace(policy, pace, lane));
|
|
73
|
+
/** The Actions page where the owner presses "Run workflow" for the release workflow. */
|
|
74
|
+
export const releaseWorkflowUrl = (slug) => `${GITHUB}/${checkedSlug(slug)}/actions/workflows/${RELEASE_WORKFLOW}`;
|
|
75
|
+
/** API URL for the latest run of the release workflow (the dashboard shows its result). */
|
|
76
|
+
export const latestReleaseRunApiUrl = (slug) => `${API}/repos/${checkedSlug(slug)}/actions/workflows/${RELEASE_WORKFLOW}/runs?per_page=1`;
|
|
77
|
+
/** API URL listing the repo's tags (for the ship-check "tag does not exist yet" item). */
|
|
78
|
+
export const tagsApiUrl = (slug) => `${API}/repos/${checkedSlug(slug)}/tags?per_page=100`;
|
|
79
|
+
/** The npm registry's package document (`dist-tags`, `versions`); it sends `Access-Control-Allow-Origin: *`, so the static demo may read it. Scoped names keep their `@` and encode the slash. */
|
|
80
|
+
export const npmPackageUrl = (name) => {
|
|
81
|
+
if (!/^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/.test(name))
|
|
82
|
+
throw new Error(`not an npm package name: ${name}`);
|
|
83
|
+
return `${NPM_REGISTRY}/${name.replace('/', '%2f')}`;
|
|
84
|
+
};
|
|
85
|
+
/** API URL matching one release tag by prefix; unlike `tagsApiUrl` it is not capped by the repo's first 100 tags. Response items carry `ref: "refs/tags/<name>"`. */
|
|
86
|
+
export const tagLookupApiUrl = (slug, version) => `${API}/repos/${checkedSlug(slug)}/git/matching-refs/tags/v${encodeURIComponent(version)}`;
|
|
87
|
+
/** True when `nextRoundUrl` can carry this brief; otherwise the UI offers Copy and the blank new-file page. */
|
|
88
|
+
export function briefFitsUrl(slug, brief) {
|
|
89
|
+
try {
|
|
90
|
+
nextRoundUrl(slug, brief);
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
if (error instanceof RangeError)
|
|
95
|
+
return false;
|
|
96
|
+
throw error;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=controls.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"controls.js","sourceRoot":"","sources":["../../src/server/controls.ts"],"names":[],"mappings":"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"}
|
package/dist/server/data.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { withLabel } from './checkLabels.js';
|
|
2
3
|
/** Fleet order from the contract: what needs the owner first. */
|
|
3
4
|
const ATTENTION_ORDER = ['failing', 'blocked', 'building', 'waiting', 'done'];
|
|
4
5
|
export function sortByAttention(loops) {
|
|
@@ -6,15 +7,20 @@ export function sortByAttention(loops) {
|
|
|
6
7
|
}
|
|
7
8
|
/** The detail-only fields stay out of the fleet list. */
|
|
8
9
|
export function toSummary(detail) {
|
|
9
|
-
const { timeline: _timeline, contract: _contract, knowledge: _knowledge, decisions: _decisions, history: _history, ...summary } = detail;
|
|
10
|
+
const { controlNotes: _controlNotes, releases: _releases, ship: _ship, runs: _runs, nextRuns: _nextRuns, schedule: _schedule, pace: _pace, idleRuns: _idleRuns, steer: _steer, laneNotes: _laneNotes, timeline: _timeline, contract: _contract, knowledge: _knowledge, decisions: _decisions, history: _history, ...summary } = detail;
|
|
10
11
|
return summary;
|
|
11
12
|
}
|
|
13
|
+
/** Marks every loop of a bundled snapshot with when it was taken, so the UI says "snapshot 6 Oct" and never "Live". */
|
|
14
|
+
export function stampSnapshot(snapshot) {
|
|
15
|
+
return { ...snapshot, loops: snapshot.loops.map((loop) => ({ ...loop, snapshotAt: loop.snapshotAt ?? snapshot.generatedAt })) };
|
|
16
|
+
}
|
|
12
17
|
export function snapshotSource(snapshot) {
|
|
18
|
+
const checks = snapshot.checks.map(withLabel);
|
|
13
19
|
return {
|
|
14
20
|
loops: async () => sortByAttention(snapshot.loops.map(toSummary)),
|
|
15
21
|
loop: async (id) => snapshot.loops.find((l) => l.id === id),
|
|
16
22
|
inbox: async () => snapshot.inbox,
|
|
17
|
-
checks: async () =>
|
|
23
|
+
checks: async () => checks,
|
|
18
24
|
};
|
|
19
25
|
}
|
|
20
26
|
async function readFirst(candidates) {
|
|
@@ -45,6 +51,6 @@ export async function loadDemoSnapshot(candidates = [new URL('./five-sites.json'
|
|
|
45
51
|
if (!snapshot)
|
|
46
52
|
throw new Error('demo snapshot not found (run `node demo/build-demo.mjs`)');
|
|
47
53
|
const self = await readFirst(selfCandidates);
|
|
48
|
-
return self ? withSelfLoop(snapshot, self) : snapshot;
|
|
54
|
+
return stampSnapshot(self ? withSelfLoop(snapshot, self) : snapshot);
|
|
49
55
|
}
|
|
50
56
|
//# sourceMappingURL=data.js.map
|
package/dist/server/data.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"data.js","sourceRoot":"","sources":["../../src/server/data.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"data.js","sourceRoot":"","sources":["../../src/server/data.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { readFile, readdir } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { extractOpenFindings, extractOutOfScope, nextRoundNumber } from '../core/draftBrief.js';
|
|
4
|
+
import { parseLanesConfig } from '../core/lanes.js';
|
|
5
|
+
import { parseSteer, steerPath, unacknowledged } from '../core/parse/steer.js';
|
|
6
|
+
import { extractNextItems } from '../core/rounds.js';
|
|
7
|
+
const read = (dir, rel) => readFile(path.join(dir, ...rel.split('/')), 'utf8').catch(() => undefined);
|
|
8
|
+
/**
|
|
9
|
+
* The ingredients `loop next-round --draft` puts in its prompt, for the dashboard's next-round dialog.
|
|
10
|
+
* Mirrors `gatherDraftPrompt` in src/cli/draft.ts (core's), which does not export its inputs yet.
|
|
11
|
+
*/
|
|
12
|
+
export async function gatherDraftInputs(dir) {
|
|
13
|
+
const idea = (await read(dir, 'IDEA.md')) ?? '';
|
|
14
|
+
const lanes = parseLanesConfig((await read(dir, '.ai/lanes.json')) ?? '').config?.lanes.map((l) => l.name) ?? [];
|
|
15
|
+
const next = [];
|
|
16
|
+
for (const source of ['DONE.md', ...lanes.map((l) => `.ai/lanes/${l}/DONE.md`)]) {
|
|
17
|
+
const items = extractNextItems((await read(dir, source)) ?? '');
|
|
18
|
+
if (items.length)
|
|
19
|
+
next.push({ source, items });
|
|
20
|
+
}
|
|
21
|
+
const findings = [];
|
|
22
|
+
for (const file of (await readdir(path.join(dir, '.ai', 'audit')).catch(() => [])).filter((f) => f.endsWith('.md')).sort()) {
|
|
23
|
+
findings.push(...extractOpenFindings(`.ai/audit/${file}`, (await read(dir, `.ai/audit/${file}`)) ?? ''));
|
|
24
|
+
}
|
|
25
|
+
const steer = [];
|
|
26
|
+
for (const lane of lanes) {
|
|
27
|
+
const entries = unacknowledged(parseSteer((await read(dir, steerPath(lane))) ?? ''));
|
|
28
|
+
if (entries.length)
|
|
29
|
+
steer.push({ lane, entries });
|
|
30
|
+
}
|
|
31
|
+
return { name: path.basename(dir), round: nextRoundNumber(idea), next, findings, steer, outOfScope: extractOutOfScope(idea) };
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=draftInputs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"draftInputs.js","sourceRoot":"","sources":["../../src/server/draftInputs.ts"],"names":[],"mappings":"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"}
|
package/dist/server/health.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { readdir, readFile } from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { LOCK_MINUTES } from '../core/status.js';
|
|
4
|
+
import { formatUtc, withLabel } from './checkLabels.js';
|
|
5
|
+
import { laneChecks } from './laneChecks.js';
|
|
6
|
+
export { laneChecks };
|
|
4
7
|
const minutesBetween = (later, earlier) => (later.getTime() - earlier.getTime()) / 60_000;
|
|
5
8
|
/** Every runner in `runners/` fires hourly; a loop on another schedule passes its own period. */
|
|
6
9
|
export const DEFAULT_CRON_PERIOD_MINUTES = 60;
|
|
@@ -18,7 +21,7 @@ export function leakCheck(netlifyToml) {
|
|
|
18
21
|
const publish = /^\s*publish\s*=\s*["']([^"']*)["']/m.exec(netlifyToml)?.[1];
|
|
19
22
|
// Only an explicit root counts: a missing publish may be set in the Netlify UI, so it is unknown, not a leak.
|
|
20
23
|
if (publish === undefined)
|
|
21
|
-
return { kind: 'leak', ok: true, reason: 'netlify.toml sets no publish folder (unknown)', proof: 'publish not set in netlify.toml' };
|
|
24
|
+
return { kind: 'leak', ok: true, reason: 'netlify.toml sets no publish folder (unknown)', proof: 'publish not set in netlify.toml', state: 'unchecked', howToCheck: 'Set `publish` in netlify.toml (or check the Netlify site settings) to a folder that is not the repo root' };
|
|
22
25
|
const root = ['', '.', './', '/'].includes(publish.trim());
|
|
23
26
|
return root
|
|
24
27
|
? { kind: 'leak', ok: false, reason: 'netlify.toml publishes the repo root, so .ai/ and CLAUDE.md are public', proof: `publish = ${JSON.stringify(publish)}` }
|
|
@@ -69,7 +72,7 @@ export function staleLockCheck(facts, now) {
|
|
|
69
72
|
kind: 'stale-lock',
|
|
70
73
|
ok: !stale,
|
|
71
74
|
reason: stale ? `lock is ${Math.round(age)} min old and nothing was committed since` : 'lock is fresh or work followed it',
|
|
72
|
-
proof: `lock at ${lockAt
|
|
75
|
+
proof: `lock at ${formatUtc(lockAt)}, window ${LOCK_MINUTES} min`,
|
|
73
76
|
};
|
|
74
77
|
}
|
|
75
78
|
/** Parses `.ai/runs.jsonl` (one `{"startedAt","endedAt"}` object per line); bad lines are skipped. */
|
|
@@ -92,7 +95,7 @@ export function parseRunLog(text) {
|
|
|
92
95
|
/** Short runs: the last few runs all ended within seconds, the signature of a rate-limited account. */
|
|
93
96
|
export function shortRunsCheck(runs) {
|
|
94
97
|
if (runs.length === 0)
|
|
95
|
-
return { kind: 'short-runs', ok: true, reason: 'no run log yet', proof: '.ai/runs.jsonl not found or empty (`loop run` writes it)' };
|
|
98
|
+
return { kind: 'short-runs', ok: true, reason: 'no run log yet', proof: '.ai/runs.jsonl not found or empty (`loop run` writes it)', state: 'unchecked', howToCheck: 'Run the loop with `loop run`; it writes .ai/runs.jsonl' };
|
|
96
99
|
const recent = runs.slice(-SHORT_RUN_STREAK);
|
|
97
100
|
const seconds = recent.map((r) => (r.endedAt.getTime() - r.startedAt.getTime()) / 1000);
|
|
98
101
|
const failing = recent.length === SHORT_RUN_STREAK && seconds.every((s) => s < SHORT_RUN_SECONDS);
|
|
@@ -115,7 +118,7 @@ export function parseTestRecord(text) {
|
|
|
115
118
|
}
|
|
116
119
|
export function failingTestsCheck(record) {
|
|
117
120
|
if (!record)
|
|
118
|
-
return { kind: 'failing-tests', ok: true, reason: 'no test result recorded yet', proof: '.ai/last-test.json not found (the template `npm test` writes it; commit it)' };
|
|
121
|
+
return { kind: 'failing-tests', ok: true, reason: 'no test result recorded yet', proof: '.ai/last-test.json not found (the template `npm test` writes it; commit it)', state: 'unchecked', howToCheck: 'Run `npm test` with the template script and commit .ai/last-test.json' };
|
|
119
122
|
return {
|
|
120
123
|
kind: 'failing-tests',
|
|
121
124
|
ok: record.failed === 0,
|
|
@@ -136,33 +139,9 @@ export function runnerSilentCheck(facts, now, periodMinutes = DEFAULT_CRON_PERIO
|
|
|
136
139
|
kind: 'runner-silent',
|
|
137
140
|
ok: silent <= limit,
|
|
138
141
|
reason: silent <= limit ? 'runner is active' : `no commit or lock for ${Math.round(silent)} min (limit ${limit})`,
|
|
139
|
-
proof: `last activity ${new Date(latest)
|
|
142
|
+
proof: `last activity ${formatUtc(new Date(latest))}, period ${periodMinutes} min`,
|
|
140
143
|
};
|
|
141
144
|
}
|
|
142
|
-
/**
|
|
143
|
-
* Lane health for a laned loop. A lane with no commit or lock for more than two cron periods and no stop file
|
|
144
|
-
* is stalled (core's `deriveLaneState` decides). A lane waiting on a finished lane never gets its answer:
|
|
145
|
-
* the failure round 1 hit when the server lane was DONE and web kept waiting for it.
|
|
146
|
-
*/
|
|
147
|
-
export function laneChecks(lanes) {
|
|
148
|
-
const done = new Set(lanes.filter((l) => l.state === 'done').map((l) => l.name));
|
|
149
|
-
const stalled = lanes.filter((l) => l.state === 'stalled');
|
|
150
|
-
const orphaned = lanes.flatMap((l) => (l.state === 'done' ? [] : (l.waitingOn ?? []).filter((w) => done.has(w)).map((w) => `${l.name} waits on ${w}`)));
|
|
151
|
-
return [
|
|
152
|
-
{
|
|
153
|
-
kind: 'lane-stalled',
|
|
154
|
-
ok: stalled.length === 0,
|
|
155
|
-
reason: stalled.length === 0 ? 'no lane is stalled' : `stalled: ${stalled.map((l) => l.name).join(', ')} (no commit or lock for over 2 cron periods)`,
|
|
156
|
-
proof: lanes.map((l) => `${l.name}=${l.state}`).join(', '),
|
|
157
|
-
},
|
|
158
|
-
{
|
|
159
|
-
kind: 'lane-waiting-on-finished',
|
|
160
|
-
ok: orphaned.length === 0,
|
|
161
|
-
reason: orphaned.length === 0 ? 'no lane waits on a finished lane' : `waiting on finished lane: ${orphaned.join('; ')}`,
|
|
162
|
-
proof: `done lanes: ${[...done].join(', ') || 'none'}`,
|
|
163
|
-
},
|
|
164
|
-
];
|
|
165
|
-
}
|
|
166
145
|
async function readOptional(file) {
|
|
167
146
|
return readFile(file, 'utf8').catch(() => undefined);
|
|
168
147
|
}
|
|
@@ -188,6 +167,6 @@ export async function checkLoop(dir, facts, now, periodMinutes, lanes) {
|
|
|
188
167
|
runnerSilentCheck(facts, now, periodMinutes),
|
|
189
168
|
...(lanes ? laneChecks(lanes) : []),
|
|
190
169
|
];
|
|
191
|
-
return checks.map((c) => ({ loopId, ...c }));
|
|
170
|
+
return checks.map((c) => ({ loopId, ...withLabel(c) }));
|
|
192
171
|
}
|
|
193
172
|
//# sourceMappingURL=health.js.map
|