compounding-loop 0.1.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. package/README.md +40 -6
  2. package/dist/cli/checkLanes.js +60 -0
  3. package/dist/cli/checkLanes.js.map +1 -0
  4. package/dist/cli/claim.js +84 -0
  5. package/dist/cli/claim.js.map +1 -0
  6. package/dist/cli/doctor.js +54 -0
  7. package/dist/cli/doctor.js.map +1 -0
  8. package/dist/cli/index.js +253 -4
  9. package/dist/cli/index.js.map +1 -1
  10. package/dist/cli/init.js +2 -1
  11. package/dist/cli/init.js.map +1 -1
  12. package/dist/cli/laneRound.js +96 -0
  13. package/dist/cli/laneRound.js.map +1 -0
  14. package/dist/cli/laneStatus.js +54 -0
  15. package/dist/cli/laneStatus.js.map +1 -0
  16. package/dist/cli/lanesInit.js +56 -0
  17. package/dist/cli/lanesInit.js.map +1 -0
  18. package/dist/cli/lock.js +62 -0
  19. package/dist/cli/lock.js.map +1 -0
  20. package/dist/cli/loops.js +30 -3
  21. package/dist/cli/loops.js.map +1 -1
  22. package/dist/cli/new.js +5 -5
  23. package/dist/cli/new.js.map +1 -1
  24. package/dist/cli/next.js +18 -0
  25. package/dist/cli/next.js.map +1 -0
  26. package/dist/cli/pause.js +81 -0
  27. package/dist/cli/pause.js.map +1 -0
  28. package/dist/cli/run.js +119 -19
  29. package/dist/cli/run.js.map +1 -1
  30. package/dist/cli/runs.js +83 -0
  31. package/dist/cli/runs.js.map +1 -0
  32. package/dist/cli/shipCheck.js +59 -0
  33. package/dist/cli/shipCheck.js.map +1 -0
  34. package/dist/core/doctor.js +49 -0
  35. package/dist/core/doctor.js.map +1 -0
  36. package/dist/core/laneRound.js +63 -0
  37. package/dist/core/laneRound.js.map +1 -0
  38. package/dist/core/laneStatus.js +82 -0
  39. package/dist/core/laneStatus.js.map +1 -0
  40. package/dist/core/lanes.js +149 -0
  41. package/dist/core/lanes.js.map +1 -0
  42. package/dist/core/lanesInit.js +143 -0
  43. package/dist/core/lanesInit.js.map +1 -0
  44. package/dist/core/next.js +34 -0
  45. package/dist/core/next.js.map +1 -0
  46. package/dist/core/parse/changelog.js +41 -0
  47. package/dist/core/parse/changelog.js.map +1 -0
  48. package/dist/core/parse/claim.js +24 -0
  49. package/dist/core/parse/claim.js.map +1 -0
  50. package/dist/core/parse/controlRoom.js +38 -0
  51. package/dist/core/parse/controlRoom.js.map +1 -0
  52. package/dist/core/parse/index.js +12 -0
  53. package/dist/core/parse/index.js.map +1 -0
  54. package/dist/core/parse/markers.js +20 -0
  55. package/dist/core/parse/markers.js.map +1 -0
  56. package/dist/core/parse/paused.js +26 -0
  57. package/dist/core/parse/paused.js.map +1 -0
  58. package/dist/core/parse/runs.js +132 -0
  59. package/dist/core/parse/runs.js.map +1 -0
  60. package/dist/core/parse/task.js +96 -0
  61. package/dist/core/parse/task.js.map +1 -0
  62. package/dist/core/repo.js +2 -7
  63. package/dist/core/repo.js.map +1 -1
  64. package/dist/core/runPrompt.js +9 -3
  65. package/dist/core/runPrompt.js.map +1 -1
  66. package/dist/core/shipCheck.js +45 -0
  67. package/dist/core/shipCheck.js.map +1 -0
  68. package/dist/core/table.js +22 -1
  69. package/dist/core/table.js.map +1 -1
  70. package/dist/server/checkLabels.js +27 -0
  71. package/dist/server/checkLabels.js.map +1 -0
  72. package/dist/server/controls.js +72 -0
  73. package/dist/server/controls.js.map +1 -0
  74. package/dist/server/data.js +28 -8
  75. package/dist/server/data.js.map +1 -1
  76. package/dist/server/events.js +1 -0
  77. package/dist/server/events.js.map +1 -1
  78. package/dist/server/health.js +47 -8
  79. package/dist/server/health.js.map +1 -1
  80. package/dist/server/index.js +29 -0
  81. package/dist/server/index.js.map +1 -1
  82. package/dist/server/laneChecks.js +25 -0
  83. package/dist/server/laneChecks.js.map +1 -0
  84. package/dist/server/live/build.js +163 -0
  85. package/dist/server/live/build.js.map +1 -0
  86. package/dist/server/live/github.js +54 -0
  87. package/dist/server/live/github.js.map +1 -0
  88. package/dist/server/live/index.js +9 -0
  89. package/dist/server/live/index.js.map +1 -0
  90. package/dist/server/live/merge.js +16 -0
  91. package/dist/server/live/merge.js.map +1 -0
  92. package/dist/server/live/urls.js +59 -0
  93. package/dist/server/live/urls.js.map +1 -0
  94. package/dist/server/loops.js +146 -39
  95. package/dist/server/loops.js.map +1 -1
  96. package/dist/server/roundState.js +27 -0
  97. package/dist/server/roundState.js.map +1 -0
  98. package/dist/server/runs.js +81 -0
  99. package/dist/server/runs.js.map +1 -0
  100. package/dist/server/static-export.js +22 -1
  101. package/dist/server/static-export.js.map +1 -1
  102. package/dist/server/taskBullets.js +33 -0
  103. package/dist/server/taskBullets.js.map +1 -0
  104. package/dist/server/this-repo.json +1574 -0
  105. package/dist/web/assets/index-BOPytDNU.js +53 -0
  106. package/dist/web/assets/index-BSHEI7fa.css +2 -0
  107. package/dist/web/index.html +3 -3
  108. package/method/COMPOUNDING_LOOP.md +55 -0
  109. package/method/example/K-0002.md +16 -0
  110. package/method/example/K-0003.md +16 -0
  111. package/method/retro.md +7 -0
  112. package/package.json +6 -1
  113. package/plugin/.claude-plugin/plugin.json +1 -1
  114. package/plugin/commands/loop-lanes.md +11 -0
  115. package/plugin/commands/loop-next.md +11 -0
  116. package/plugin/commands/loop-pause.md +10 -0
  117. package/runners/github-actions/lanes.yml +156 -0
  118. package/runners/local/README.md +4 -1
  119. package/runners/local/compounding-loop.service +20 -0
  120. package/runners/local/compounding-loop.timer +11 -0
  121. package/runners/local/launchd.plist +36 -0
  122. package/runners/routine/README.md +38 -1
  123. package/runners/routine/control-room-prompt.md +16 -0
  124. package/runners/routine/lane-prompt.md +12 -0
  125. package/runners/routine/prompt.md +3 -3
  126. package/templates/chrome-extension/extension/icons/icon128.png +0 -0
  127. package/templates/chrome-extension/extension/icons/icon16.png +0 -0
  128. package/templates/chrome-extension/extension/icons/icon48.png +0 -0
  129. package/templates/chrome-extension/extension/manifest.json +19 -3
  130. package/templates/chrome-extension/gitignore +1 -1
  131. package/templates/lanes/README.md +23 -0
  132. package/templates/lanes/ci-check-lanes.yml +38 -0
  133. package/templates/lanes/claude-section.md +20 -0
  134. package/templates/lanes/control-room.md +3 -0
  135. package/templates/lanes/outbox.md +5 -0
  136. package/templates/lanes/task.md +36 -0
  137. package/templates/static-site/.ai/loop-prompt.md +2 -2
  138. package/templates/static-site/CLAUDE.md +2 -0
  139. package/templates/static-site/gitignore +1 -1
  140. package/dist/web/assets/index-BDdtnG33.js +0 -49
  141. package/dist/web/assets/index-D3-N8DN4.css +0 -2
@@ -0,0 +1,81 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { mkdir, readFile, rm, writeFile } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { promisify } from 'node:util';
5
+ import { formatPaused, PAUSED_FILE, parsePaused } from '../core/parse/paused.js';
6
+ import { CONTROL_LANE } from '../core/lanes.js';
7
+ const execFileAsync = promisify(execFile);
8
+ const git = (cwd, ...args) => execFileAsync('git', args, { cwd });
9
+ const pausedFile = (dir) => path.join(dir, ...PAUSED_FILE.split('/'));
10
+ /** The current pause, or undefined when `.ai/PAUSED` is absent. */
11
+ export async function readPaused(dir) {
12
+ const text = await readFile(pausedFile(dir), 'utf8').catch(() => undefined);
13
+ return text === undefined ? undefined : parsePaused(text);
14
+ }
15
+ async function pushIfWanted(dir, result, push) {
16
+ if (push === false)
17
+ return;
18
+ try {
19
+ try {
20
+ await git(dir, 'push');
21
+ }
22
+ catch {
23
+ // Five lanes push to one branch, so a rejection is routine: rebase once and retry.
24
+ await git(dir, 'pull', '--rebase', '--autostash', '-q');
25
+ await git(dir, 'push');
26
+ }
27
+ result.pushed = true;
28
+ }
29
+ catch (error) {
30
+ result.pushed = false;
31
+ const lines = (error instanceof Error ? error.message : String(error)).split('\n').map((l) => l.trim()).filter(Boolean);
32
+ result.pushError = lines.find((l) => /rejected|fatal|error:/i.test(l)) ?? lines[0] ?? 'git push failed';
33
+ }
34
+ }
35
+ /** The dashboard writes PAUSED on GitHub, so look at the remote first. Offline or without a remote, the local view stands. */
36
+ async function syncFirst(dir, push) {
37
+ if (push === false)
38
+ return;
39
+ await git(dir, 'pull', '--rebase', '--autostash', '-q').catch(() => undefined);
40
+ }
41
+ /** `loop pause`: writes `.ai/PAUSED`, commits it with `Lane: control` and (by default) pushes. Pausing twice is a no-op. */
42
+ export async function runPause(dir, options = {}) {
43
+ await syncFirst(dir, options.push);
44
+ const held = await readPaused(dir);
45
+ if (held)
46
+ return { changed: false, paused: true, by: held.by };
47
+ const by = (options.as ?? (await git(dir, 'config', 'user.name').then((r) => r.stdout.trim(), () => ''))).trim();
48
+ if (!by || by.includes('·') || /[\r\n]/.test(by))
49
+ throw new Error('Say who you are with --as <name> (git user.name is not set, or contains "·").');
50
+ const reason = options.reason?.replace(/\s+/g, ' ').trim();
51
+ await mkdir(path.dirname(pausedFile(dir)), { recursive: true });
52
+ await writeFile(pausedFile(dir), formatPaused(by, options.now ?? new Date(), reason));
53
+ try {
54
+ await git(dir, 'add', '--', PAUSED_FILE);
55
+ await git(dir, 'commit', '-qm', `Pause all lanes${reason ? `: ${reason}` : ''}\n\nLane: ${CONTROL_LANE}`, '--', PAUSED_FILE);
56
+ }
57
+ catch (error) {
58
+ // An uncommitted PAUSED would look paused here while the lanes never see it.
59
+ await git(dir, 'reset', '-q', '--', PAUSED_FILE).catch(() => undefined);
60
+ await rm(pausedFile(dir), { force: true });
61
+ throw error;
62
+ }
63
+ const result = { changed: true, paused: true, by };
64
+ await pushIfWanted(dir, result, options.push);
65
+ return result;
66
+ }
67
+ /** `loop resume`: removes `.ai/PAUSED` and commits that. Resuming a loop that is not paused is a no-op. */
68
+ export async function runResume(dir, options = {}) {
69
+ await syncFirst(dir, options.push);
70
+ const tracked = (await git(dir, 'ls-files', '--', PAUSED_FILE)).stdout.trim() !== '';
71
+ const present = (await readPaused(dir)) !== undefined;
72
+ await rm(pausedFile(dir), { force: true });
73
+ if (!tracked)
74
+ return { changed: present, paused: false };
75
+ await git(dir, 'add', '--', PAUSED_FILE);
76
+ await git(dir, 'commit', '-qm', `Resume all lanes\n\nLane: ${CONTROL_LANE}`, '--', PAUSED_FILE);
77
+ const result = { changed: true, paused: false };
78
+ await pushIfWanted(dir, result, options.push);
79
+ return result;
80
+ }
81
+ //# sourceMappingURL=pause.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pause.js","sourceRoot":"","sources":["../../src/cli/pause.ts"],"names":[],"mappings":"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"}
package/dist/cli/run.js CHANGED
@@ -3,22 +3,46 @@ import { appendFile, mkdir, readFile, stat } from 'node:fs/promises';
3
3
  import path from 'node:path';
4
4
  import { parseLockTime } from '../core/repo.js';
5
5
  import { LOCK_MINUTES } from '../core/status.js';
6
- import { buildClaudeArgs, buildRunPrompt, DEFAULT_PROMPT } from '../core/runPrompt.js';
6
+ import { buildClaudeArgs, buildRunPrompt, claimRule, DEFAULT_PROMPT, PAUSED_RULE } from '../core/runPrompt.js';
7
+ import { parseLanesConfig } from '../core/lanes.js';
7
8
  import { packageRoot } from './init.js';
9
+ import { hostname } from 'node:os';
10
+ import { describePaused } from '../core/parse/paused.js';
11
+ import { readPaused } from './pause.js';
12
+ import { lockHolderHost, lockHolderPid, pidAlive, withSessionLock } from './lock.js';
8
13
  const exists = (file) => stat(file).then(() => true, () => false);
9
14
  /** First readable prompt: the repo's own, then the routine prompt in the repo, then the package's, then the built-in. */
10
- export async function loadPromptText(dir, root = packageRoot) {
15
+ export async function loadPromptText(dir, root = packageRoot, lane) {
11
16
  const candidates = [
17
+ ...(lane ? [path.join(dir, '.ai', 'lanes', lane, 'prompt.md'), path.join(dir, 'runners', 'routine', 'lane-prompt.md'), path.join(root, 'runners', 'routine', 'lane-prompt.md')] : []),
12
18
  path.join(dir, '.ai', 'loop-prompt.md'),
13
19
  path.join(dir, 'runners', 'routine', 'prompt.md'),
14
20
  path.join(root, 'runners', 'routine', 'prompt.md'),
15
21
  ];
16
- for (const file of candidates) {
22
+ for (const [index, file] of candidates.entries()) {
17
23
  const text = await readFile(file, 'utf8').catch(() => undefined);
18
- if (text !== undefined)
19
- return { text, source: file };
24
+ if (text === undefined)
25
+ continue;
26
+ // The first three candidates are written for a lane; any other prompt is generic and needs the intro.
27
+ const forLane = lane !== undefined && index < 3;
28
+ return { text: lane && !forLane ? `${laneIntro(lane)}\n\n${text}` : text, source: file };
20
29
  }
21
- return { text: DEFAULT_PROMPT, source: 'built-in default' };
30
+ return { text: lane ? `${laneIntro(lane)}\n\n${DEFAULT_PROMPT}` : DEFAULT_PROMPT, source: 'built-in default' };
31
+ }
32
+ /** Used when no lane prompt file exists: points the generic round prompt at this lane's files. */
33
+ export function laneIntro(lane) {
34
+ return `You are the **${lane}** lane of a multi-lane loop. Your lane's task file is \`.ai/lanes/${lane}/task.md\` (wherever this prompt says \`.ai/task.md\`, use it), your outbox is \`.ai/lanes/${lane}/outbox.md\`, your lock is \`.ai/lanes/${lane}/session.lock\`, and your stop files are \`.ai/lanes/${lane}/DONE.md\` and \`BLOCKED.md\`. Edit only the paths \`${lane}\` owns in \`.ai/lanes.json\` (plus shared ones), and end every commit message with the trailer line \`Lane: ${lane}\`. ${PAUSED_RULE} ${claimRule(lane)}`;
35
+ }
36
+ /** Fails early, with the valid names, when `--lane` is not in `.ai/lanes.json`. */
37
+ export async function assertKnownLane(dir, lane) {
38
+ const text = await readFile(path.join(dir, '.ai', 'lanes.json'), 'utf8').catch(() => undefined);
39
+ if (text === undefined)
40
+ throw new Error('No .ai/lanes.json here, so --lane has nothing to refer to. Run `loop init --lanes a,b,c` first, or drop --lane.');
41
+ const { config, errors } = parseLanesConfig(text);
42
+ if (!config)
43
+ throw new Error(`.ai/lanes.json is invalid:\n- ${errors.join('\n- ')}`);
44
+ if (!config.lanes.some((l) => l.name === lane))
45
+ throw new Error(`Unknown lane "${lane}". Lanes in .ai/lanes.json: ${config.lanes.map((l) => l.name).join(', ')}.`);
22
46
  }
23
47
  /**
24
48
  * Finds an executable on PATH. Node cannot start `.cmd` shims without a shell (and we never build shell strings),
@@ -27,6 +51,11 @@ export async function loadPromptText(dir, root = packageRoot) {
27
51
  export async function resolveExecutable(name, platform = process.platform, pathEnv = process.env.PATH ?? '', has = exists) {
28
52
  if (platform !== 'win32')
29
53
  return name;
54
+ if (/\.(?:cmd|bat)$/i.test(name))
55
+ throw new Error(`Node cannot start ${name} safely. Point at a native .exe instead.`);
56
+ // A full path (e.g. process.execPath) needs no PATH search, and appending `.exe` would break it.
57
+ if (path.win32.isAbsolute(name))
58
+ return name;
30
59
  const dirs = pathEnv.split(path.win32.delimiter).filter(Boolean);
31
60
  for (const dir of dirs)
32
61
  if (await has(path.win32.join(dir, `${name}.exe`)))
@@ -38,8 +67,59 @@ export async function resolveExecutable(name, platform = process.platform, pathE
38
67
  }
39
68
  throw new Error(`Could not find ${name}.exe on PATH. Is Claude Code installed?`);
40
69
  }
41
- const realSpawn = async (command, args, cwd) => {
42
- const executable = await resolveExecutable(command);
70
+ /**
71
+ * Where an npm-installed `<name>.cmd` shim really points: the `bin` entry of the package installed next to it.
72
+ * Returns undefined when the layout is not the usual global one.
73
+ */
74
+ export async function shimEntry(shim, packageName, binName, read = (file) => readFile(file, 'utf8').catch(() => undefined)) {
75
+ const packageDir = path.win32.join(path.win32.dirname(shim), 'node_modules', ...packageName.split('/'));
76
+ const text = await read(path.win32.join(packageDir, 'package.json'));
77
+ if (text === undefined)
78
+ return undefined;
79
+ let bin;
80
+ try {
81
+ bin = JSON.parse(text).bin;
82
+ }
83
+ catch {
84
+ return undefined;
85
+ }
86
+ const rel = typeof bin === 'string' ? bin : bin && typeof bin === 'object' ? (bin[binName] ?? Object.values(bin).find((v) => typeof v === 'string')) : undefined;
87
+ return typeof rel === 'string' ? path.win32.join(packageDir, rel) : undefined;
88
+ }
89
+ /**
90
+ * Fallback for other layouts (pnpm and similar; Volta shims call its own launcher and are not covered): npm-style `.cmd` shims name their script as
91
+ * `"%dp0%\\path\\cli.js"` (or `%~dp0`), relative to the shim's own folder.
92
+ */
93
+ export async function shimScript(shim, read = (file) => readFile(file, 'utf8').catch(() => undefined)) {
94
+ const text = await read(shim);
95
+ const match = text === undefined ? null : /%(?:~dp0|dp0%)\\?([^"%\r\n]+\.(?:c|m)?js)"/i.exec(text);
96
+ return match?.[1] ? path.win32.join(path.win32.dirname(shim), match[1]) : undefined;
97
+ }
98
+ /**
99
+ * Like `resolveExecutable`, but also copes with the usual Windows install (`npm i -g @anthropic-ai/claude-code`),
100
+ * which only has `claude.cmd`: we run the package's own JS entry with this Node, no shell involved.
101
+ */
102
+ export async function resolveInvocation(name, args, platform = process.platform, pathEnv = process.env.PATH ?? '', has = exists, read, nodePath = process.execPath) {
103
+ try {
104
+ return { command: await resolveExecutable(name, platform, pathEnv, has), args };
105
+ }
106
+ catch (error) {
107
+ if (platform !== 'win32' || path.win32.isAbsolute(name))
108
+ throw error;
109
+ for (const dir of pathEnv.split(path.win32.delimiter).filter(Boolean)) {
110
+ const shim = path.win32.join(dir, `${name}.cmd`);
111
+ if (!(await has(shim)))
112
+ continue;
113
+ const entry = (await shimEntry(shim, name === 'claude' ? '@anthropic-ai/claude-code' : name, name, read)) ?? (await shimScript(shim, read));
114
+ if (!entry)
115
+ throw error;
116
+ return /\.exe$/i.test(entry) ? { command: entry, args } : { command: nodePath, args: [entry, ...args] };
117
+ }
118
+ throw error;
119
+ }
120
+ }
121
+ const realSpawn = async (command, rawArgs, cwd) => {
122
+ const { command: executable, args } = await resolveInvocation(command, rawArgs);
43
123
  return new Promise((resolve, reject) => {
44
124
  const child = spawn(executable, args, { cwd, stdio: 'inherit' });
45
125
  child.on('error', (error) => reject(new Error(`Could not start \`${command}\`: ${error.message}. Is Claude Code installed and on your PATH?`)));
@@ -47,9 +127,14 @@ const realSpawn = async (command, args, cwd) => {
47
127
  });
48
128
  };
49
129
  /** Minutes left on a fresh `.ai/session.lock`, or undefined when no session holds the repo. */
50
- export function lockMinutesLeft(lockText, now) {
130
+ export function lockMinutesLeft(lockText, now, isAlive = pidAlive, host = hostname()) {
51
131
  const at = lockText === undefined ? undefined : parseLockTime(lockText);
52
- if (!at)
132
+ if (!at || lockText === undefined)
133
+ return undefined;
134
+ // A lock whose holder was killed (SIGKILL skips the cleanup) is stale at once instead of after 90 minutes.
135
+ const pid = lockHolderPid(lockText);
136
+ // Only on the writing machine: in another pid namespace (WSL, a container, a shared drive) a live holder looks dead.
137
+ if (pid !== undefined && lockHolderHost(lockText) === host && !isAlive(pid))
53
138
  return undefined;
54
139
  const left = LOCK_MINUTES - (now.getTime() - at.getTime()) / 60_000;
55
140
  return left > 0 ? Math.ceil(left) : undefined;
@@ -61,21 +146,36 @@ export async function recordRun(dir, startedAt, endedAt) {
61
146
  }
62
147
  /** `loop run`: one build round now, using the same prompt the scheduler uses. */
63
148
  export async function runRound(dir, options, spawner = realSpawn) {
64
- const { text, source } = await loadPromptText(dir);
65
- const prompt = buildRunPrompt(text, { name: path.basename(path.resolve(dir)) });
66
- for (const stop of ['DONE.md', 'BLOCKED.md']) {
149
+ const { lane } = options;
150
+ if (lane)
151
+ await assertKnownLane(dir, lane);
152
+ const laneDir = lane ? path.join('.ai', 'lanes', lane) : '.ai';
153
+ const { text, source } = await loadPromptText(dir, packageRoot, lane);
154
+ const lockFile = path.join(dir, laneDir, 'session.lock');
155
+ const lockNote = `Note from \`loop run\`: it created \`${path.posix.join(...laneDir.split(path.sep), 'session.lock')}\` for this very session. That lock is yours, not another session's: ignore it in the lock step and do not delete it.`;
156
+ const asNote = options.as ? `\nNote from \`loop run\`: you are running as \`${options.as}\`. A claim naming \`${options.as}\` is yours, not a teammate's: ignore it in the claim step.` : '';
157
+ const prompt = `${buildRunPrompt(text, { name: path.basename(path.resolve(dir)), lane: lane ?? '' })}\n${lockNote}${asNote}\n`;
158
+ const paused = await readPaused(dir);
159
+ if (paused)
160
+ return { skipped: `${describePaused(paused)}: nothing to do. Run \`loop resume\` to continue.`, prompt, source };
161
+ const stopFiles = [...(lane ? [path.join(laneDir, 'DONE.md'), path.join(laneDir, 'BLOCKED.md')] : []), 'DONE.md', 'BLOCKED.md'];
162
+ for (const stop of stopFiles) {
67
163
  if (await exists(path.join(dir, stop)))
68
164
  return { skipped: `${stop} exists: nothing to do`, prompt, source };
69
165
  }
70
- const lockLeft = lockMinutesLeft(await readFile(path.join(dir, '.ai', 'session.lock'), 'utf8').catch(() => undefined), new Date());
166
+ const lockLeft = lockMinutesLeft(await readFile(lockFile, 'utf8').catch(() => undefined), new Date());
71
167
  if (lockLeft !== undefined)
72
- return { skipped: `another session holds .ai/session.lock (about ${lockLeft} min left): nothing to do`, prompt, source };
168
+ return { skipped: `another session holds ${path.join(laneDir, 'session.lock')} (about ${lockLeft} min left): nothing to do`, prompt, source };
73
169
  if (options.dryRun)
74
170
  return { prompt, source };
75
- console.error(`Running one round in ${dir} (prompt: ${source})`);
171
+ console.error(`Running one round in ${dir}${lane ? ` as lane ${lane}` : ''} (prompt: ${source})`);
76
172
  const startedAt = new Date();
77
- const exitCode = await spawner('claude', buildClaudeArgs(prompt, { skipPermissions: options.skipPermissions, ...(options.model && { model: options.model }) }), dir);
78
- await recordRun(dir, startedAt, new Date());
79
- return { prompt, source, exitCode };
173
+ try {
174
+ const exitCode = await withSessionLock(lockFile, () => spawner('claude', buildClaudeArgs(prompt, { skipPermissions: options.skipPermissions, ...(options.model && { model: options.model }) }), dir));
175
+ return { prompt, source, exitCode };
176
+ }
177
+ finally {
178
+ await recordRun(dir, startedAt, new Date()); // a failed round still counts for the dashboard's fast-failure check
179
+ }
80
180
  }
81
181
  //# sourceMappingURL=run.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/cli/run.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/cli/run.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,83 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { appendFile, mkdir, readFile } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { promisify } from 'node:util';
5
+ import { parseLanesConfig } from '../core/lanes.js';
6
+ import { expectedSlots, formatRunEntry, matchSlots, missedInARow, parseRunLog, runLogPath } from '../core/parse/runs.js';
7
+ const execFileAsync = promisify(execFile);
8
+ async function laneNames(dir) {
9
+ const text = await readFile(path.join(dir, '.ai', 'lanes.json'), 'utf8').catch(() => undefined);
10
+ if (text === undefined)
11
+ throw new Error('No .ai/lanes.json here. Create one with `loop init --lanes a,b,c`.');
12
+ const { config, errors } = parseLanesConfig(text);
13
+ if (!config)
14
+ throw new Error(`.ai/lanes.json is invalid:\n- ${errors.join('\n- ')}`);
15
+ return { names: config.lanes.map((l) => l.name), crons: new Map(config.lanes.flatMap((l) => (l.cron ? [[l.name, l.cron]] : []))) };
16
+ }
17
+ /** `loop runs log`: appends one line to the lane's own runs.jsonl. The caller commits it with its last commit. */
18
+ export async function logRun(dir, lane, options) {
19
+ const { names } = await laneNames(dir);
20
+ if (!names.includes(lane))
21
+ throw new Error(`No lane "${lane}". Lanes here: ${names.join(', ')}.`);
22
+ if ((options.passed === undefined) !== (options.failed === undefined))
23
+ throw new Error('Pass --passed and --failed together.');
24
+ for (const n of [options.passed, options.failed])
25
+ if (n !== undefined && (!Number.isInteger(n) || n < 0))
26
+ throw new Error('--passed and --failed must be whole numbers, 0 or more.');
27
+ const entry = {
28
+ at: (options.now ?? new Date()).toISOString(),
29
+ lane,
30
+ ...(options.session && { session: options.session }),
31
+ units: options.units ?? [],
32
+ ...(options.passed !== undefined && options.failed !== undefined && { tests: { passed: options.passed, failed: options.failed } }),
33
+ pushed: options.pushed,
34
+ ...(options.note && { note: options.note.replace(/\s+/g, ' ').trim() }),
35
+ };
36
+ const file = path.join(dir, ...runLogPath(lane).split('/'));
37
+ await mkdir(path.dirname(file), { recursive: true });
38
+ await appendFile(file, formatRunEntry(entry));
39
+ return entry;
40
+ }
41
+ /** Commit times carrying `Lane: <lane>` since `since`; empty when git cannot say (no repo, no commits). */
42
+ async function laneCommitTimes(dir, lane, since) {
43
+ try {
44
+ const { stdout } = await execFileAsync('git', ['log', `--since=${since.toISOString()}`, '--format=%cI%x09%(trailers:key=Lane,valueonly,separator=%x2C)'], { cwd: dir });
45
+ return stdout.split('\n').flatMap((line) => {
46
+ const [time, trailer] = line.split('\t');
47
+ return time && trailer?.trim() === lane ? [new Date(time)] : [];
48
+ });
49
+ }
50
+ catch {
51
+ return [];
52
+ }
53
+ }
54
+ /** `loop runs`: each lane's logged runs plus the scheduled slots in the window that left no trace. */
55
+ export async function readRuns(dir, options = {}) {
56
+ const now = options.now ?? new Date();
57
+ const from = new Date(now.getTime() - (options.hours ?? 24) * 3_600_000);
58
+ const { names, crons } = await laneNames(dir);
59
+ const lanes = [];
60
+ for (const lane of names) {
61
+ const entries = parseRunLog(await readFile(path.join(dir, ...runLogPath(lane).split('/')), 'utf8').catch(() => ''));
62
+ const cron = crons.get(lane);
63
+ const slots = cron ? matchSlots(expectedSlots(cron, from, now), entries, await laneCommitTimes(dir, lane, from)) : [];
64
+ lanes.push({ lane, ...(cron && { cron }), entries: entries.filter((e) => new Date(e.at) > from), slots, missedInARow: missedInARow(slots) });
65
+ }
66
+ return { from: from.toISOString(), to: now.toISOString(), lanes };
67
+ }
68
+ const LABEL = { pushed: 'pushed', 'nothing-pushed': 'ran, pushed nothing', 'tests-failed': 'tests failed', 'no-run-recorded': 'no run recorded (usage limit, idle or failed)' };
69
+ const stamp = (iso) => iso.slice(0, 16).replace('T', ' ');
70
+ export function formatRuns(report) {
71
+ const out = [`Runs ${stamp(report.from)} to ${stamp(report.to)} UTC`];
72
+ for (const l of report.lanes) {
73
+ out.push('', `${l.lane}${l.cron ? ` (cron ${l.cron})` : ''}${l.missedInARow >= 3 ? ` ! ${l.missedInARow} scheduled runs in a row left no trace: check your plan's usage limit` : ''}`);
74
+ if (l.slots.length === 0 && l.entries.length === 0)
75
+ out.push(' nothing logged');
76
+ for (const s of l.slots) {
77
+ const e = s.entry;
78
+ out.push(` ${stamp(s.slot)} ${LABEL[s.outcome]}${e ? ` ${e.units.length} unit${e.units.length === 1 ? '' : 's'}${e.tests ? `, tests ${e.tests.passed} passed ${e.tests.failed} failed` : ''}${e.note ? ` ${e.note}` : ''}` : ''}`);
79
+ }
80
+ }
81
+ return out.join('\n');
82
+ }
83
+ //# sourceMappingURL=runs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runs.js","sourceRoot":"","sources":["../../src/cli/runs.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,59 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { readFile } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { promisify } from 'node:util';
5
+ import { parseLanesConfig } from '../core/lanes.js';
6
+ import { parseRunLog, runLogPath } from '../core/parse/runs.js';
7
+ import { shipCheck } from '../core/shipCheck.js';
8
+ const execFileAsync = promisify(execFile);
9
+ const read = (file) => readFile(file, 'utf8').catch(() => undefined);
10
+ const git = async (dir, ...args) => (await execFileAsync('git', args, { cwd: dir })).stdout;
11
+ /** Newest test result: the latest run-log line that carries one, else `.ai/last-test.json`. */
12
+ async function latestTests(dir) {
13
+ const configText = await read(path.join(dir, '.ai', 'lanes.json'));
14
+ const lanes = configText === undefined ? [] : (parseLanesConfig(configText).config?.lanes ?? []);
15
+ const entries = [];
16
+ for (const lane of lanes)
17
+ entries.push(...parseRunLog((await read(path.join(dir, ...runLogPath(lane.name).split('/')))) ?? ''));
18
+ const newest = entries.filter((e) => e.tests).sort((a, b) => b.at.localeCompare(a.at))[0];
19
+ if (newest?.tests)
20
+ return { ...newest.tests, at: newest.at };
21
+ try {
22
+ const record = JSON.parse((await read(path.join(dir, '.ai', 'last-test.json'))) ?? '');
23
+ if (typeof record.passed === 'number' && typeof record.failed === 'number')
24
+ return { passed: record.passed, failed: record.failed, ...(typeof record.at === 'string' && { at: record.at }) };
25
+ }
26
+ catch {
27
+ // no usable result
28
+ }
29
+ return undefined;
30
+ }
31
+ /** `loop ship-check`: gathers the facts from disk and git, then runs the pure checklist. */
32
+ export async function runShipCheck(dir) {
33
+ const pkg = JSON.parse((await read(path.join(dir, 'package.json'))) ?? 'null');
34
+ if (typeof pkg?.version !== 'string')
35
+ throw new Error('No package.json with a "version" here: ship-check needs it for the CHANGELOG and tag checks.');
36
+ const [doneText, changelogText, tags, status, tests] = await Promise.all([
37
+ read(path.join(dir, 'DONE.md')),
38
+ read(path.join(dir, 'CHANGELOG.md')),
39
+ git(dir, 'tag', '-l', `v${pkg.version}`).catch(() => ''),
40
+ git(dir, 'status', '--porcelain').catch(() => 'not a git repo'),
41
+ latestTests(dir),
42
+ ]);
43
+ return shipCheck({
44
+ version: pkg.version,
45
+ ...(doneText !== undefined && { doneText }),
46
+ ...(changelogText !== undefined && { changelogText }),
47
+ tagExists: tags.trim() !== '',
48
+ treeClean: status.trim() === '',
49
+ ...(tests && { tests }),
50
+ });
51
+ }
52
+ export function formatShipCheck(result) {
53
+ const lines = result.items.map((i) => `${i.ok ? '✓' : '✗'} ${i.label}: ${i.detail}`);
54
+ if (result.openFindings.length)
55
+ lines.push('', 'Open findings in DONE.md:', ...result.openFindings.map((f) => ` - ${f}`));
56
+ lines.push('', result.ready ? 'Ready to ship.' : 'Not ready to ship.');
57
+ return lines.join('\n');
58
+ }
59
+ //# sourceMappingURL=shipCheck.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shipCheck.js","sourceRoot":"","sources":["../../src/cli/shipCheck.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ // Pure part of `loop doctor`: turn probe results into checks with a fix for each failure.
2
+ export const MIN_NODE_MAJOR = 20;
3
+ export function parseNodeMajor(version) {
4
+ const major = Number(/^v?(\d+)/.exec(version.trim())?.[1]);
5
+ return Number.isFinite(major) ? major : 0;
6
+ }
7
+ const firstLine = (text) => text.trim().split(/\r?\n/)[0] ?? '';
8
+ export function evaluateDoctor(input) {
9
+ const nodeOk = parseNodeMajor(input.nodeVersion) >= MIN_NODE_MAJOR;
10
+ return [
11
+ {
12
+ id: 'node', label: `Node.js ${MIN_NODE_MAJOR} or newer`, required: true, ok: nodeOk, detail: input.nodeVersion,
13
+ fix: nodeOk ? '' : `Install Node.js ${MIN_NODE_MAJOR}+ from https://nodejs.org (or use nvm/fnm), then open a new terminal.`,
14
+ },
15
+ {
16
+ id: 'git', label: 'git', required: true, ok: input.git.ok, detail: input.git.ok ? firstLine(input.git.output) : 'not found',
17
+ fix: input.git.ok ? '' : 'Install git from https://git-scm.com/downloads and make sure `git --version` works.',
18
+ },
19
+ {
20
+ id: 'claude', label: 'Claude Code (claude)', required: true, ok: input.claude.ok, detail: input.claude.ok ? firstLine(input.claude.output) : firstLine(input.claude.output) || 'not found',
21
+ fix: input.claude.ok ? '' : input.claude.output.includes('.cmd') ? input.claude.output : 'Install Claude Code (https://docs.claude.com/en/docs/claude-code), then run `claude` once to sign in.',
22
+ },
23
+ {
24
+ id: 'gh', label: 'GitHub CLI (gh), needed by `loop new` and for pushing', required: false, ok: input.gh.ok, detail: input.gh.ok ? firstLine(input.gh.output) : 'not found',
25
+ fix: input.gh.ok ? '' : 'Install gh from https://cli.github.com. Without it, use `loop new --dry-run` and create the repo yourself.',
26
+ },
27
+ {
28
+ id: 'gh-auth', label: 'gh is signed in', required: false, ok: input.ghAuth.ok, detail: input.ghAuth.ok ? 'signed in' : input.gh.ok ? 'not signed in' : 'skipped (gh missing)',
29
+ fix: input.ghAuth.ok || !input.gh.ok ? '' : 'Run `gh auth login` and choose GitHub.com.',
30
+ },
31
+ {
32
+ id: 'chromium', label: 'Playwright Chromium, needed by generated sites\' browser tests', required: false, ok: input.chromium !== undefined, detail: input.chromium ?? 'not found',
33
+ fix: input.chromium !== undefined ? '' : 'Run `npx playwright install chromium` (add `npx playwright install-deps chromium` on Linux if Chrome fails to start).',
34
+ },
35
+ ];
36
+ }
37
+ export function doctorPassed(checks) {
38
+ return checks.every((c) => c.ok || !c.required);
39
+ }
40
+ export function formatDoctor(checks) {
41
+ const lines = checks.map((c) => {
42
+ const mark = c.ok ? 'ok ' : c.required ? 'MISSING' : 'missing';
43
+ return `${mark} ${c.label}: ${c.detail}${c.fix ? `\n fix: ${c.fix}` : ''}`;
44
+ });
45
+ const bad = checks.filter((c) => !c.ok && c.required).length;
46
+ lines.push('', bad > 0 ? `${bad} required tool(s) missing. Fix them and run \`loop doctor\` again.` : 'Everything required is installed.');
47
+ return lines.join('\n');
48
+ }
49
+ //# sourceMappingURL=doctor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/core/doctor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,63 @@
1
+ // Starting the next round of a laned repo (IDEA.md Round 4 B3). Pure: takes file texts, returns the changes to make.
2
+ import { lintBrief } from './brief.js';
3
+ import { nextDoneFileName } from './rounds.js';
4
+ import { parseRunCounter } from './parse/task.js';
5
+ import { fillPlaceholders } from './template.js';
6
+ const ROUND_HEADING = /^##\s+Round\s+(\d+)\b/;
7
+ const DEFAULT_RUN_LIMIT = 20;
8
+ /** The number of the round being started: one above the highest `## Round N` heading in IDEA.md. */
9
+ export function nextRoundNumber(ideaText) {
10
+ const numbers = ideaText.split(/\r?\n/).flatMap((line) => {
11
+ const match = ROUND_HEADING.exec(line);
12
+ return match ? [Number(match[1])] : [];
13
+ });
14
+ return Math.max(0, ...numbers) + 1;
15
+ }
16
+ const utcDay = (d) => d.toISOString().slice(0, 10);
17
+ /** Puts `brief` first in IDEA.md and turns the previous "(current)" heading into "(finished <date>)". */
18
+ export function insertBrief(ideaText, brief, round, now) {
19
+ const body = brief.trim();
20
+ const section = /^##\s/.test(body) ? body : `## Round ${round} (current) — started ${utcDay(now)} ${now.toISOString().slice(11, 16)} UTC\n\n${body}`;
21
+ const lines = ideaText.split(/\r?\n/).map((line) => (ROUND_HEADING.test(line) ? line.replace(/\(current\)/i, `(finished ${utcDay(now)})`) : line));
22
+ const firstRound = lines.findIndex((line) => ROUND_HEADING.test(line));
23
+ const at = firstRound !== -1 ? firstRound : Math.min(lines.length, lines.findIndex((l) => /^#\s/.test(l)) + 1);
24
+ const before = lines.slice(0, at).join('\n').trimEnd();
25
+ const after = lines.slice(at).join('\n').trimStart();
26
+ return `${before ? `${before}\n\n` : ''}${section}\n\n---\n\n${after}`.trimEnd() + '\n';
27
+ }
28
+ /** A control-room note, newest first, right under the file's header paragraph. */
29
+ export function addControlNote(controlText, note) {
30
+ const lines = controlText.split(/\r?\n/);
31
+ const firstNote = lines.findIndex((l) => /^\d{4}-\d\d-\d\d \d\d:\d\d UTC · /.test(l));
32
+ if (firstNote !== -1)
33
+ return [...lines.slice(0, firstNote), note, ...lines.slice(firstNote)].join('\n');
34
+ return `${controlText.trimEnd()}\n\n${note}\n`;
35
+ }
36
+ const filesIn = (files, dir) => files.flatMap((f) => (f.startsWith(`${dir}/`) && !f.slice(dir.length + 1).includes('/') ? [f.slice(dir.length + 1)] : []));
37
+ /** Everything `loop next-round --brief` does, as a list of changes. Nothing here touches the disk. */
38
+ export function planLaneRound(input) {
39
+ const { now, lanes, files } = input;
40
+ const round = nextRoundNumber(input.ideaText);
41
+ const changes = [{ kind: 'write', path: 'IDEA.md', content: insertBrief(input.ideaText, input.brief, round, now) }];
42
+ if (files.includes('DONE.md'))
43
+ changes.push({ kind: 'move', from: 'DONE.md', to: `.ai/${nextDoneFileName(filesIn(files, '.ai'))}` });
44
+ const blockedLanes = [];
45
+ for (const lane of lanes) {
46
+ const dir = `.ai/lanes/${lane.name}`;
47
+ const own = filesIn(files, dir);
48
+ if (own.includes('DONE.md'))
49
+ changes.push({ kind: 'move', from: `${dir}/DONE.md`, to: `${dir}/${nextDoneFileName(own)}` });
50
+ if (own.includes('BLOCKED.md'))
51
+ blockedLanes.push(lane.name);
52
+ const limit = parseRunCounter(input.laneTasks[lane.name] ?? '')?.limit ?? DEFAULT_RUN_LIMIT;
53
+ const task = fillPlaceholders(input.taskTemplate, { lane: lane.name, owns: lane.owns.join(', ') }).replace(/^Run:\s*\d+\s*\/\s*\d+/m, `Run: 0 / ${limit}`);
54
+ changes.push({ kind: 'write', path: `${dir}/task.md`, content: task });
55
+ }
56
+ const stamp = `${utcDay(now)} ${now.toISOString().slice(11, 16)} UTC`;
57
+ const note = `${stamp} · to all · Round ${round} has started; the brief is the first section of IDEA.md. Previous DONE files are archived as done-vN.md. First unit for every lane: a Round ${round} section in your audit. Run budgets are reset. (loop next-round)`;
58
+ changes.push({ kind: 'write', path: '.ai/control-room.md', content: addControlNote(input.controlRoomText ?? '# Control room notes\n', note) });
59
+ if (input.briefPath)
60
+ changes.push({ kind: 'delete', path: input.briefPath });
61
+ return { round, brief: lintBrief(input.brief), changes, blockedLanes };
62
+ }
63
+ //# sourceMappingURL=laneRound.js.map