compounding-loop 0.0.0-stage → 0.2.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/LICENSE +21 -0
- package/README.md +123 -3
- package/bin/loop.js +2 -0
- package/dist/cli/checkLanes.js +60 -0
- package/dist/cli/checkLanes.js.map +1 -0
- package/dist/cli/dashboard.js +52 -0
- package/dist/cli/dashboard.js.map +1 -0
- package/dist/cli/doctor.js +54 -0
- package/dist/cli/doctor.js.map +1 -0
- package/dist/cli/gh.js +29 -0
- package/dist/cli/gh.js.map +1 -0
- package/dist/cli/index.js +228 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/init.js +73 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/laneStatus.js +52 -0
- package/dist/cli/laneStatus.js.map +1 -0
- package/dist/cli/lanesInit.js +56 -0
- package/dist/cli/lanesInit.js.map +1 -0
- package/dist/cli/lock.js +42 -0
- package/dist/cli/lock.js.map +1 -0
- package/dist/cli/loops.js +92 -0
- package/dist/cli/loops.js.map +1 -0
- package/dist/cli/new.js +54 -0
- package/dist/cli/new.js.map +1 -0
- package/dist/cli/run.js +169 -0
- package/dist/cli/run.js.map +1 -0
- package/dist/core/blocked.js +42 -0
- package/dist/core/blocked.js.map +1 -0
- package/dist/core/brief.js +91 -0
- package/dist/core/brief.js.map +1 -0
- package/dist/core/doctor.js +49 -0
- package/dist/core/doctor.js.map +1 -0
- package/dist/core/gh.js +2 -0
- package/dist/core/gh.js.map +1 -0
- package/dist/core/init.js +59 -0
- package/dist/core/init.js.map +1 -0
- package/dist/core/laneStatus.js +82 -0
- package/dist/core/laneStatus.js.map +1 -0
- package/dist/core/lanes.js +139 -0
- package/dist/core/lanes.js.map +1 -0
- package/dist/core/lanesInit.js +143 -0
- package/dist/core/lanesInit.js.map +1 -0
- package/dist/core/repo.js +81 -0
- package/dist/core/repo.js.map +1 -0
- package/dist/core/rounds.js +50 -0
- package/dist/core/rounds.js.map +1 -0
- package/dist/core/runPrompt.js +31 -0
- package/dist/core/runPrompt.js.map +1 -0
- package/dist/core/status.js +21 -0
- package/dist/core/status.js.map +1 -0
- package/dist/core/table.js +43 -0
- package/dist/core/table.js.map +1 -0
- package/dist/core/template.js +29 -0
- package/dist/core/template.js.map +1 -0
- package/dist/core/types.js +3 -0
- package/dist/core/types.js.map +1 -0
- package/dist/server/data.js +50 -0
- package/dist/server/data.js.map +1 -0
- package/dist/server/events.js +106 -0
- package/dist/server/events.js.map +1 -0
- package/dist/server/five-sites.json +3071 -0
- package/dist/server/health.js +193 -0
- package/dist/server/health.js.map +1 -0
- package/dist/server/index.js +134 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/loops.js +145 -0
- package/dist/server/loops.js.map +1 -0
- package/dist/server/settings.js +25 -0
- package/dist/server/settings.js.map +1 -0
- package/dist/server/static-export.js +28 -0
- package/dist/server/static-export.js.map +1 -0
- package/dist/server/static.js +80 -0
- package/dist/server/static.js.map +1 -0
- package/dist/server/this-repo.json +371 -0
- package/dist/web/assets/index-B16QCSnY.css +2 -0
- package/dist/web/assets/index-CS82DKbF.js +49 -0
- package/dist/web/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
- package/dist/web/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
- package/dist/web/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
- package/dist/web/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
- package/dist/web/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- package/dist/web/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- package/dist/web/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
- package/dist/web/favicon.svg +1 -0
- package/dist/web/index.html +14 -0
- package/dist/web/logo.svg +1 -0
- package/method/COMPOUNDING_LOOP.md +603 -0
- package/method/example/K-0001.md +16 -0
- package/method/example/K-0002.md +16 -0
- package/method/example/K-0003.md +16 -0
- package/method/knowledge-index.md +5 -0
- package/method/operating-card.md +40 -0
- package/method/retro.md +10 -0
- package/method/reviewer-prompt.md +13 -0
- package/method/stuck-protocol.md +12 -0
- package/method/task-template.md +41 -0
- package/package.json +76 -4
- package/plugin/.claude-plugin/plugin.json +7 -0
- package/plugin/commands/handoff.md +10 -0
- package/plugin/commands/loop-init.md +13 -0
- package/plugin/commands/retro.md +11 -0
- package/plugin/commands/stuck.md +10 -0
- package/runners/github-actions/lanes.yml +103 -0
- package/runners/github-actions/loop.yml +51 -0
- package/runners/local/README.md +9 -0
- package/runners/local/crontab.txt +6 -0
- package/runners/local/launchd.plist +36 -0
- package/runners/local/task-scheduler.xml +32 -0
- package/runners/routine/README.md +56 -0
- package/runners/routine/control-room-prompt.md +16 -0
- package/runners/routine/lane-prompt.md +12 -0
- package/runners/routine/prompt.md +13 -0
- package/templates/chrome-extension/CLAUDE.md +18 -0
- package/templates/chrome-extension/IDEA.md +22 -0
- package/templates/chrome-extension/extension/background.js +5 -0
- package/templates/chrome-extension/extension/icons/icon128.png +0 -0
- package/templates/chrome-extension/extension/icons/icon16.png +0 -0
- package/templates/chrome-extension/extension/icons/icon48.png +0 -0
- package/templates/chrome-extension/extension/manifest.json +25 -0
- package/templates/chrome-extension/extension/popup.css +3 -0
- package/templates/chrome-extension/extension/popup.html +15 -0
- package/templates/chrome-extension/extension/popup.js +16 -0
- package/templates/chrome-extension/gitignore +6 -0
- package/templates/chrome-extension/package.json +12 -0
- package/templates/chrome-extension/playwright.config.js +22 -0
- package/templates/chrome-extension/scripts/check.mjs +51 -0
- package/templates/chrome-extension/scripts/test.mjs +59 -0
- package/templates/chrome-extension/tests/popup.spec.js +37 -0
- package/templates/lanes/README.md +23 -0
- package/templates/lanes/ci-check-lanes.yml +38 -0
- package/templates/lanes/claude-section.md +17 -0
- package/templates/lanes/control-room.md +3 -0
- package/templates/lanes/outbox.md +5 -0
- package/templates/lanes/task.md +36 -0
- package/templates/static-site/.ai/index.md +5 -0
- package/templates/static-site/.ai/log.md +3 -0
- package/templates/static-site/.ai/loop-prompt.md +10 -0
- package/templates/static-site/.ai/task.md +27 -0
- package/templates/static-site/.gitattributes +1 -0
- package/templates/static-site/CLAUDE.md +113 -0
- package/templates/static-site/IDEA.md +36 -0
- package/templates/static-site/gitignore +6 -0
- package/templates/static-site/netlify.toml +14 -0
- package/templates/static-site/package.json +14 -0
- package/templates/static-site/playwright.config.js +27 -0
- package/templates/static-site/scripts/check.mjs +188 -0
- package/templates/static-site/scripts/serve.mjs +44 -0
- package/templates/static-site/scripts/test.mjs +59 -0
- package/templates/static-site/site/index.html +23 -0
- package/templates/static-site/site/robots.txt +4 -0
- package/templates/static-site/site/sitemap.xml +4 -0
- package/templates/static-site/tests/pages.spec.js +21 -0
- package/templates/static-site/vercel.json +19 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
You are the scheduled "control room" run for this repository ({{name}}). The lanes ({{lanes}}) push to `main` on their own schedules; you do the supervision the owner would otherwise do by hand. Nobody is watching; the owner reads your final summary later. Be brief and cheap. Read CLAUDE.md (lane table, hard rules, Unattended mode) and IDEA.md (especially "Done when").
|
|
2
|
+
|
|
3
|
+
0. Run `date -u`. `git checkout main && git pull --ff-only`. Push only to `main`; if a push is rejected, `git pull --rebase` and push again. Never force-push. Verify claims on a fresh pull: a stale clone is not evidence, and a lane blaming a force-push gets believed only after `git log` shows one.
|
|
4
|
+
1. If root `DONE.md` exists, give the summary (step 6) and stop.
|
|
5
|
+
2. Run the install and test commands from CLAUDE.md. If the tests are red: find the commit that broke it (`git log`, `git bisect` only if quick). If the fix is small and obvious, make it, run the tests, commit with the trailer `Lane: control` and push. Otherwise add a note to `.ai/control-room.md` addressed to the owning lane with the failing command and the likely commit. Also check lane ownership: run `git fetch --unshallow` if the clone is shallow, then `npx compounding-loop check-lanes --range <commit from 4 hours ago>..HEAD` (or `loop check-lanes` if installed), and report each violation to the lane that caused it. If the command is unavailable or errors, say so in the summary and carry on.
|
|
6
|
+
3. Leak check: the published package must contain nothing from `.ai/`, the brief, CLAUDE.md or tests (`npm pack --dry-run`, or your project's equivalent). Grep the repository for secrets (API keys, tokens, private keys) and for the owner's email address and local paths. Fix any leak immediately with the smallest change, push, and say so in the summary.
|
|
7
|
+
4. For each lane read `.ai/lanes/<lane>/task.md` (Run counter, Handoff), its outbox, and whether its `DONE.md` or `BLOCKED.md` exists. Then:
|
|
8
|
+
- BLOCKED with "run budget reached": if the lane made real progress (commits that pushed tests or features) and its task file has no line "Budget extended by control room", raise the limit by 20, add that line with the date under Decisions, `git rm` the BLOCKED.md, commit and push. If already extended once, leave it for the owner.
|
|
9
|
+
- BLOCKED with a question: if it can be answered safely from the brief and CLAUDE.md (never anything that spends money, creates accounts, publishes, posts, changes repo visibility or settings, or adds secrets), write the answer under Decisions in that lane's task file as "Answer from control room (date): ...", `git rm` the BLOCKED.md, commit and push. Otherwise leave it and put the question in your summary.
|
|
10
|
+
- A lane waiting on a lane that is already DONE: tell the waiting lane in `.ai/control-room.md` to stop waiting, list the gap as a next improvement in its DONE.md, and finish.
|
|
11
|
+
- A message in any outbox that has waited more than 3 hours for a lane that has since run without answering: copy it into `.ai/control-room.md` addressed to that lane.
|
|
12
|
+
- Lanes with no commit in the last 4 hours and no DONE/BLOCKED: note "stalled" in the summary. Runs that end in about a second are usually a plan or rate limit, not a bug.
|
|
13
|
+
5. When every `.ai/lanes/*/DONE.md` exists, check every line of the brief's "Done when" for the whole product by running its commands. If all pass, write root `DONE.md`: what was built, test results, anything unsure, and the owner's launch checklist. Commit and push. If a line fails, add a note to `.ai/control-room.md` for the owning lane, `git rm` that lane's DONE.md so it resumes, commit and push.
|
|
14
|
+
6. Commit only `.ai/` notes and the smallest fixes, each with the trailer `Lane: control`. End with one line per lane: `lane · Run N/LIMIT · building|blocked|done|stalled · last commit time · note`, then one line for tests and the leak check, then any question for the owner.
|
|
15
|
+
|
|
16
|
+
Never spend money, sign up for anything, create accounts, publish anything (no package publish, no posting, no repo visibility or settings changes, no releases), add API keys, secrets, ads, analytics or tracking, force-push, or touch anything outside this repository.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
You are one scheduled run of the **{{lane}}** lane of an unattended build loop for this repository ({{name}}). The other lanes push to the same `main` at other minutes of the hour, so expect new commits between your pulls. Nobody is watching; the owner reads the results later. Read CLAUDE.md and follow it exactly, especially the lane table, the hard rules and "Unattended mode". IDEA.md is the brief. Your task file is `.ai/lanes/{{lane}}/task.md`; wherever the Operating Card or `.ai/method.md` says `.ai/task.md`, use your task file. Every commit message you write ends with the trailer line `Lane: {{lane}}`.
|
|
2
|
+
|
|
3
|
+
0. Run `date -u` and note it as this run's start time. Run `git checkout main && git pull --ff-only`. Commit all work on `main` and push it with `git push origin main`. Never create or push a `claude/` branch. If a push is rejected, run `git pull --rebase` (follow the lockfile rule in CLAUDE.md), re-run the tests if the incoming commits touched code, and push again. If pushing to main is refused by permissions, write that under Tried in your task file, push to a branch named `claude/{{lane}}-blocked`, and stop.
|
|
4
|
+
1. Stop files: if root `DONE.md` or `BLOCKED.md` exists, or `.ai/lanes/{{lane}}/DONE.md` or `.ai/lanes/{{lane}}/BLOCKED.md` exists, reply "Nothing to do" and stop. Session lock: if `.ai/lanes/{{lane}}/session.lock` holds a UTC time less than 90 minutes old and the runner's run note does not say it is your own, another session is working this lane: reply "Nothing to do" and stop. An older lock is stale; ignore it.
|
|
5
|
+
2. Run the install command from CLAUDE.md (`npm install` unless it says otherwise). If it fails, write `.ai/lanes/{{lane}}/BLOCKED.md` with the error and stop. If the tests need a browser, install it the way CLAUDE.md says (a blocked download or a Chrome that will not launch is covered under "Commands" there). If CLAUDE.md names no commands, derive them from `package.json` and record them under Decisions.
|
|
6
|
+
3. Read `.ai/control-room.md` and every other lane's `.ai/lanes/*/outbox.md` for messages to {{lane}} or all. Run the resume protocol (.ai/method.md section 9.2) on your task file and add 1 to its run counter. If the counter has reached its limit, follow "Run budget" in CLAUDE.md.
|
|
7
|
+
4. 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" in CLAUDE.md), whichever lane owns the failure (commit that fix with the trailer `Lane: control`, which `loop check-lanes` allows to touch any path, and tell the owner in your outbox). Otherwise take the next unit: a message addressed to you that another lane is waiting on, else the next item in your task file's units list. Edit only the paths your lane owns, plus the smallest needed edits to shared files. Then run the tests.
|
|
8
|
+
5. Green: commit, have a reviewer subagent check the diff against IDEA.md and your lane contract ("find the three most likely ways this is wrong for a real user, and check each"), fix any blockers, run the tests again, pull, then push to `main`. Run the reviewer in the foreground if you can. If it runs in the background you are resumed when it reports; then finish this step and continue with step 6. Ending your turn ends the run unless a subagent is still running, so never end it to wait for anything else. Red: write the attempt and the reason under Tried in your task file, then discard the unit's uncommitted changes. The same failure twice means the stuck protocol (§9.3); its last step is `.ai/lanes/{{lane}}/BLOCKED.md`.
|
|
9
|
+
6. A pushed unit does not end the run. Go straight back to step 4 with the next unit and keep going until one of these is true: 40 minutes have passed since the start time (check `date -u` after each push), your context is about 60% full, your lane has no units left, or every remaining unit is blocked. Each unit still gets its own tests, review and push.
|
|
10
|
+
7. Then run the handoff (.ai/method.md section 9.1) in your task file so a fresh run with zero memory can continue at full quality, and add a line to your outbox for any lane affected by what you shipped (new types, routes, files they are waiting for). If every done-when in your lane contract passes and the reviewer finds no blockers, follow "Lane finished" in CLAUDE.md instead: read the other lanes' outboxes first, run the retro, write `.ai/lanes/{{lane}}/DONE.md`. Commit, push to `main`, and stop.
|
|
11
|
+
|
|
12
|
+
Never spend money, sign up for anything, create accounts, publish anything (no package publish, no posting, no repo visibility or settings changes, no releases, no version bump), add API keys, secrets, ads, analytics or tracking, copy other projects' content without a compatible licence, force-push, rewrite history, edit another lane's paths beyond the "main stays green" rule, or touch anything outside this repository.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
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`.
|
|
2
|
+
|
|
3
|
+
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.
|
|
4
|
+
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.
|
|
5
|
+
2. If `DONE.md` or `BLOCKED.md` exists, reply "Nothing to do" and stop.
|
|
6
|
+
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.
|
|
7
|
+
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.
|
|
8
|
+
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.
|
|
9
|
+
6. After each unit run the test command from CLAUDE.md (`npm test` unless it says otherwise). Green: commit, have a reviewer subagent that did not write the code check the diff against IDEA.md ("find the three most likely ways this is wrong for a real user, and check each"), fix any blockers, run the tests again, pull, then push to `main`. Red: write the attempt and the reason under Tried in the task file, then discard the unit's uncommitted changes. The same failure twice means the stuck protocol (§9.3); its last step is `BLOCKED.md`.
|
|
10
|
+
7. A pushed unit does not end the run. Keep taking units until one of these is true: 40 minutes have passed since the start time (check `date -u` after each push), your context is about 60% full, or no unit is left.
|
|
11
|
+
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.
|
|
12
|
+
|
|
13
|
+
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.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
## Project core
|
|
4
|
+
|
|
5
|
+
**What this is:** a Chrome extension (Manifest V3) built by an unattended AI loop. The goal and must-haves are in [IDEA.md](IDEA.md).
|
|
6
|
+
|
|
7
|
+
**Commands**
|
|
8
|
+
- Install: `npm install`, then `npx playwright install chromium`. If the download is blocked, `playwright.config.js` finds a preinstalled Chromium or system Chrome.
|
|
9
|
+
- Test (the only judge of "done"): `npm test` = `check` + the Playwright extension test.
|
|
10
|
+
- Fast: `npm run check` (manifest, permissions, MV3 rules; no browser).
|
|
11
|
+
|
|
12
|
+
**Hard rules**
|
|
13
|
+
- All extension files live in `extension/`. No inline scripts or handlers (MV3 blocks them) and no remotely hosted code.
|
|
14
|
+
- Narrowest permissions only. Anything beyond those listed in IDEA.md needs the owner.
|
|
15
|
+
- Service workers stop when idle: keep state in `chrome.storage`, never in module variables.
|
|
16
|
+
- Every must-have in IDEA.md gets a Playwright test in `tests/` that loads the unpacked extension.
|
|
17
|
+
- No telemetry, analytics, ads or calls home. No secrets in the repository.
|
|
18
|
+
- Never publish to the Chrome Web Store, spend money or create accounts.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# IDEA
|
|
2
|
+
|
|
3
|
+
> The owner fills this in. It's the only input the loop gets, so the more specific, the better.
|
|
4
|
+
|
|
5
|
+
## The extension in one sentence
|
|
6
|
+
<e.g. "A popup that shows how many tabs are open and closes duplicates.">
|
|
7
|
+
|
|
8
|
+
## Who it's for
|
|
9
|
+
<e.g. "People who keep dozens of tabs open while researching.">
|
|
10
|
+
|
|
11
|
+
## Must have (v1)
|
|
12
|
+
- <the main feature, observable in the popup or on a page>
|
|
13
|
+
- <...>
|
|
14
|
+
|
|
15
|
+
## Permissions allowed
|
|
16
|
+
<List each permission the extension may ask for, and why. Anything not listed needs the owner.>
|
|
17
|
+
|
|
18
|
+
## Nice to have (only after v1 works)
|
|
19
|
+
- <...>
|
|
20
|
+
|
|
21
|
+
## Run budget
|
|
22
|
+
30
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// Service workers stop after ~30 s idle, so keep state in chrome.storage, never in module variables.
|
|
2
|
+
chrome.runtime.onInstalled.addListener(async () => {
|
|
3
|
+
const { clicks } = await chrome.storage.local.get('clicks');
|
|
4
|
+
if (clicks === undefined) await chrome.storage.local.set({ clicks: 0 });
|
|
5
|
+
});
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"manifest_version": 3,
|
|
3
|
+
"name": "{{name}}",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "A Chrome extension built and verified by an unattended Compounding Loop.",
|
|
6
|
+
"action": {
|
|
7
|
+
"default_popup": "popup.html",
|
|
8
|
+
"default_title": "{{name}}",
|
|
9
|
+
"default_icon": {
|
|
10
|
+
"16": "icons/icon16.png",
|
|
11
|
+
"48": "icons/icon48.png"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"background": {
|
|
15
|
+
"service_worker": "background.js"
|
|
16
|
+
},
|
|
17
|
+
"permissions": [
|
|
18
|
+
"storage"
|
|
19
|
+
],
|
|
20
|
+
"icons": {
|
|
21
|
+
"16": "icons/icon16.png",
|
|
22
|
+
"48": "icons/icon48.png",
|
|
23
|
+
"128": "icons/icon128.png"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>{{name}}</title>
|
|
7
|
+
<link rel="stylesheet" href="popup.css">
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<h1>{{name}}</h1>
|
|
11
|
+
<p>Clicks so far: <strong id="count">0</strong></p>
|
|
12
|
+
<button id="click" type="button">Click me</button>
|
|
13
|
+
<script type="module" src="popup.js"></script>
|
|
14
|
+
</body>
|
|
15
|
+
</html>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// MV3 forbids inline scripts, so the popup logic lives in this file.
|
|
2
|
+
const countEl = document.getElementById('count');
|
|
3
|
+
const button = document.getElementById('click');
|
|
4
|
+
|
|
5
|
+
async function render() {
|
|
6
|
+
const { clicks = 0 } = await chrome.storage.local.get('clicks');
|
|
7
|
+
countEl.textContent = String(clicks);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
button.addEventListener('click', async () => {
|
|
11
|
+
const { clicks = 0 } = await chrome.storage.local.get('clicks');
|
|
12
|
+
await chrome.storage.local.set({ clicks: clicks + 1 });
|
|
13
|
+
await render();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
render();
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { defineConfig, chromium } from '@playwright/test';
|
|
2
|
+
import { existsSync, readdirSync } from 'node:fs';
|
|
3
|
+
|
|
4
|
+
// Cloud sandboxes can block Playwright's own browser download. If that browser is missing, use a preinstalled
|
|
5
|
+
// Playwright Chromium (Claude's cloud VMs have one in /opt/pw-browsers) or a system Chrome.
|
|
6
|
+
const bundled = (() => { try { return chromium.executablePath(); } catch { return ''; } })();
|
|
7
|
+
const preinstalled = ['/opt/pw-browsers', `${process.env.HOME}/.cache/ms-playwright`].flatMap((dir) => {
|
|
8
|
+
try {
|
|
9
|
+
return readdirSync(dir).filter((d) => /^chromium-\d+$/.test(d)).sort((a, b) => b.localeCompare(a, 'en', { numeric: true }))
|
|
10
|
+
.flatMap((d) => [`${dir}/${d}/chrome-linux64/chrome`, `${dir}/${d}/chrome-linux/chrome`]);
|
|
11
|
+
} catch { return []; }
|
|
12
|
+
});
|
|
13
|
+
const systemChrome = [process.env.CHROME_PATH, ...preinstalled, '/usr/bin/google-chrome', '/usr/bin/google-chrome-stable', '/usr/bin/chromium', '/usr/bin/chromium-browser', '/opt/google/chrome/chrome']
|
|
14
|
+
.find((p) => p && existsSync(p));
|
|
15
|
+
// Read by tests/popup.spec.js, which launches its own persistent context (extensions need one).
|
|
16
|
+
process.env.CL_CHROME = bundled && existsSync(bundled) ? bundled : systemChrome ?? '';
|
|
17
|
+
|
|
18
|
+
export default defineConfig({
|
|
19
|
+
testDir: 'tests',
|
|
20
|
+
timeout: 30_000,
|
|
21
|
+
workers: 1,
|
|
22
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// Fast structural checks for extension/. No dependencies, no browser. Messages say how to fix each problem.
|
|
2
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
3
|
+
import { join, resolve } from 'node:path';
|
|
4
|
+
|
|
5
|
+
const DIR = resolve(process.argv[2] ?? 'extension');
|
|
6
|
+
const errors = [];
|
|
7
|
+
const fail = (file, msg) => errors.push(`${file}: ${msg}`);
|
|
8
|
+
|
|
9
|
+
const manifestPath = join(DIR, 'manifest.json');
|
|
10
|
+
let manifest = null;
|
|
11
|
+
try { manifest = JSON.parse(readFileSync(manifestPath, 'utf8')); } catch { fail('manifest.json', 'missing or not valid JSON.'); }
|
|
12
|
+
|
|
13
|
+
if (manifest) {
|
|
14
|
+
if (manifest.manifest_version !== 3) fail('manifest.json', '"manifest_version" must be 3; Chrome no longer accepts version 2.');
|
|
15
|
+
for (const key of ['name', 'version', 'description']) if (!manifest[key]) fail('manifest.json', `add "${key}".`);
|
|
16
|
+
if (manifest.version && !/^\d+(\.\d+){0,3}$/.test(manifest.version)) fail('manifest.json', `"version" ${manifest.version} must be 1-4 dot-separated integers.`);
|
|
17
|
+
if (manifest.description && manifest.description.length > 132) fail('manifest.json', 'description is over 132 characters; the Chrome Web Store rejects it.');
|
|
18
|
+
|
|
19
|
+
const files = [manifest.action?.default_popup, manifest.background?.service_worker, ...Object.values(manifest.icons ?? {})].filter(Boolean);
|
|
20
|
+
for (const f of files) if (!existsSync(join(DIR, f))) fail('manifest.json', `references "${f}", which does not exist in extension/.`);
|
|
21
|
+
|
|
22
|
+
// Least privilege: broad host access and risky permissions need a reason from the owner.
|
|
23
|
+
const risky = [...(manifest.host_permissions ?? []), ...(manifest.permissions ?? [])].filter((p) => /<all_urls>|\*:\/\/\*\/\*|^(tabs|history|cookies|webRequest|debugger)$/.test(p));
|
|
24
|
+
if (risky.length) fail('manifest.json', `broad permissions (${risky.join(', ')}). Use the narrowest permission that works, or ask the owner.`);
|
|
25
|
+
if (manifest.content_security_policy?.extension_pages && /unsafe-(inline|eval)/.test(manifest.content_security_policy.extension_pages)) fail('manifest.json', 'CSP allows unsafe-inline or unsafe-eval.');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// MV3 blocks inline scripts and remote code.
|
|
29
|
+
const popup = manifest?.action?.default_popup;
|
|
30
|
+
if (popup && existsSync(join(DIR, popup))) {
|
|
31
|
+
const html = readFileSync(join(DIR, popup), 'utf8');
|
|
32
|
+
for (const [tag] of html.matchAll(/<script\b[^>]*>/gi)) {
|
|
33
|
+
const src = tag.match(/\ssrc="([^"]+)"/i)?.[1];
|
|
34
|
+
if (!src) fail(popup, 'inline <script>. Move it to a .js file; MV3 blocks inline scripts.');
|
|
35
|
+
else if (/^(https?:)?\/\//i.test(src)) fail(popup, `remote script "${src}". MV3 forbids remotely hosted code; bundle it in extension/.`);
|
|
36
|
+
}
|
|
37
|
+
if (/\son[a-z]+\s*=\s*["']/i.test(html)) fail(popup, 'inline event handler. Use addEventListener in a .js file.');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Secrets must never be committed.
|
|
41
|
+
const SECRET = /(\bsk-(ant-)?[A-Za-z0-9_-]{20,}|\bAKIA[0-9A-Z]{16}|\bgh[pousr]_[A-Za-z0-9]{30,}|-----BEGIN [A-Z ]*PRIVATE KEY-----)/;
|
|
42
|
+
for (const f of ['background.js', 'popup.js', 'popup.html', 'manifest.json']) {
|
|
43
|
+
const p = join(DIR, f);
|
|
44
|
+
if (existsSync(p) && SECRET.test(readFileSync(p, 'utf8'))) fail(f, 'looks like it contains an API key or private key. Remove it.');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (errors.length) {
|
|
48
|
+
console.error(`check: ${errors.length} problem(s)\n- ${errors.join('\n- ')}`);
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
console.log('check: OK');
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// `npm test`: runs each test step in order (stopping at the first failure, like `&&`) and writes the
|
|
2
|
+
// totals to .ai/last-test.json, which the Compounding Loop dashboard's "failing tests" health check reads.
|
|
3
|
+
// Commit that file with your work so the dashboard sees it whichever machine ran the tests.
|
|
4
|
+
import { spawn } from 'node:child_process';
|
|
5
|
+
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
|
|
9
|
+
/** Totals from test-runner output: node:test (`ℹ pass 3` or TAP `# pass 3`) and Playwright (`12 passed`, `1 failed`). */
|
|
10
|
+
export function countResults(output) {
|
|
11
|
+
const plain = output.replace(/\x1b\[[0-9;]*m/g, '');
|
|
12
|
+
const sum = (re) => [...plain.matchAll(re)].reduce((n, m) => n + Number(m[1]), 0);
|
|
13
|
+
const passed = sum(/^\s*(?:ℹ|#) pass (\d+)\s*$/gm) + sum(/^\s*(\d+) passed\b/gm);
|
|
14
|
+
const failed = sum(/^\s*(?:ℹ|#) fail (\d+)\s*$/gm) + sum(/^\s*(\d+) failed\b/gm);
|
|
15
|
+
return passed + failed > 0 ? { passed, failed } : undefined;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Steps this repo has, as [label, node arguments]. Node runs them directly, so no shell or .cmd shim is involved. */
|
|
19
|
+
function steps() {
|
|
20
|
+
const list = [['check', ['scripts/check.mjs']]];
|
|
21
|
+
if (existsSync('tests/unit')) list.push(['unit', ['--test', 'tests/unit/**/*.test.mjs']]);
|
|
22
|
+
if (existsSync('playwright.config.js')) list.push(['browser', [path.join('node_modules', '@playwright', 'test', 'cli.js'), 'test', ...process.argv.slice(2)]]);
|
|
23
|
+
return list;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function run(args) {
|
|
27
|
+
return new Promise((resolve) => {
|
|
28
|
+
let output = '';
|
|
29
|
+
const child = spawn(process.execPath, args, { stdio: ['inherit', 'pipe', 'pipe'] });
|
|
30
|
+
child.stdout.on('data', (chunk) => { output += chunk; process.stdout.write(chunk); });
|
|
31
|
+
child.stderr.on('data', (chunk) => { output += chunk; process.stderr.write(chunk); });
|
|
32
|
+
child.on('error', (error) => { output += String(error); resolve({ code: 1, output }); });
|
|
33
|
+
child.on('close', (code) => resolve({ code: code ?? 1, output }));
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function main() {
|
|
38
|
+
const total = { passed: 0, failed: 0 };
|
|
39
|
+
let code = 0;
|
|
40
|
+
for (const [label, args] of steps()) {
|
|
41
|
+
const result = await run(args);
|
|
42
|
+
// A step without countable output (the check script) counts as one test.
|
|
43
|
+
const counts = countResults(result.output) ?? (result.code === 0 ? { passed: 1, failed: 0 } : { passed: 0, failed: 1 });
|
|
44
|
+
if (result.code !== 0 && counts.failed === 0) counts.failed = 1;
|
|
45
|
+
total.passed += counts.passed;
|
|
46
|
+
total.failed += counts.failed;
|
|
47
|
+
if (result.code !== 0) {
|
|
48
|
+
console.error(`\n${label} failed (exit ${result.code}); later steps skipped.`);
|
|
49
|
+
code = result.code;
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
mkdirSync('.ai', { recursive: true });
|
|
54
|
+
writeFileSync(path.join('.ai', 'last-test.json'), `${JSON.stringify({ ...total, at: new Date().toISOString() })}\n`);
|
|
55
|
+
console.log(`\n${total.passed} passed, ${total.failed} failed (written to .ai/last-test.json)`);
|
|
56
|
+
process.exitCode = code;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) await main();
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Loads the unpacked extension in a real Chromium and drives its popup.
|
|
2
|
+
import { test, expect, chromium } from '@playwright/test';
|
|
3
|
+
import { mkdtempSync, rmSync } from 'node:fs';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { join, resolve } from 'node:path';
|
|
6
|
+
|
|
7
|
+
const extension = resolve('extension');
|
|
8
|
+
|
|
9
|
+
test('popup counts clicks and keeps them in chrome.storage', async () => {
|
|
10
|
+
const userDataDir = mkdtempSync(join(tmpdir(), 'ext-'));
|
|
11
|
+
const context = await chromium.launchPersistentContext(userDataDir, {
|
|
12
|
+
...(process.env.CL_CHROME ? { executablePath: process.env.CL_CHROME } : {}),
|
|
13
|
+
args: [`--disable-extensions-except=${extension}`, `--load-extension=${extension}`, '--headless=new'],
|
|
14
|
+
});
|
|
15
|
+
try {
|
|
16
|
+
const worker = context.serviceWorkers()[0] ?? (await context.waitForEvent('serviceworker'));
|
|
17
|
+
const id = new URL(worker.url()).host;
|
|
18
|
+
|
|
19
|
+
const problems = [];
|
|
20
|
+
const popup = await context.newPage();
|
|
21
|
+
popup.on('pageerror', (e) => problems.push(`JS error: ${e.message}`));
|
|
22
|
+
popup.on('console', (m) => m.type() === 'error' && problems.push(`console error: ${m.text()}`));
|
|
23
|
+
await popup.goto(`chrome-extension://${id}/popup.html`);
|
|
24
|
+
|
|
25
|
+
await expect(popup.locator('h1')).toBeVisible();
|
|
26
|
+
await expect(popup.locator('#count')).toHaveText('0');
|
|
27
|
+
await popup.locator('#click').click();
|
|
28
|
+
await expect(popup.locator('#count')).toHaveText('1');
|
|
29
|
+
|
|
30
|
+
await popup.reload();
|
|
31
|
+
await expect(popup.locator('#count'), 'the count must survive a reload').toHaveText('1');
|
|
32
|
+
expect(problems, 'fix these errors').toEqual([]);
|
|
33
|
+
} finally {
|
|
34
|
+
await context.close();
|
|
35
|
+
rmSync(userDataDir, { recursive: true, force: true });
|
|
36
|
+
}
|
|
37
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Lane templates
|
|
2
|
+
|
|
3
|
+
Files `loop init --lanes` copies into a repository that runs several lanes. A lane is one scheduled run that owns one part of the codebase (see `method/COMPOUNDING_LOOP.md`, chapter "Lanes").
|
|
4
|
+
|
|
5
|
+
| File | Written to | Once per |
|
|
6
|
+
|---|---|---|
|
|
7
|
+
| `task.md` | `.ai/lanes/<lane>/task.md` | lane |
|
|
8
|
+
| `outbox.md` | `.ai/lanes/<lane>/outbox.md` | lane |
|
|
9
|
+
| `control-room.md` | `.ai/control-room.md` | repo |
|
|
10
|
+
| `claude-section.md` | appended to `CLAUDE.md` and `AGENTS.md`; `loop init --lanes` wraps it in its own markers, so a second run replaces the block | repo |
|
|
11
|
+
| `ci-check-lanes.yml` | `.github/workflows/check-lanes.yml` | repo |
|
|
12
|
+
|
|
13
|
+
## Placeholders
|
|
14
|
+
|
|
15
|
+
Written `{{lane}}`. `loop init --lanes` fills only the ones listed below (`control-room.md` and `ci-check-lanes.yml` have none). A template never ships with one unfilled: `tests/unit/kit/lanes-templates.test.ts` fills every documented placeholder and fails on any `{{x}}` left over. GitHub Actions expressions (`${{ ... }}`) are not placeholders and are left alone.
|
|
16
|
+
|
|
17
|
+
| Placeholder | Meaning | Example |
|
|
18
|
+
|---|---|---|
|
|
19
|
+
| `{{lane}}` | Lane name (lowercase letters, digits, `-`) | `api` |
|
|
20
|
+
| `{{owns}}` | The lane's owned globs, comma separated | `src/api/**, tests/api/**` |
|
|
21
|
+
| `{{lanes}}` | All lane names, comma separated | `api, web, docs` |
|
|
22
|
+
|
|
23
|
+
`.ai/lanes.json` stays the machine-readable source of truth; `claude-section.md` points to it instead of copying the lane table, so the two cannot drift.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Fails a push or pull request whose commits touch another lane's paths.
|
|
2
|
+
# Needs compounding-loop 0.2.0 or later (the release that adds `check-lanes`). Copy to .github/workflows/check-lanes.yml. Commits without a `Lane:` trailer are reported, not failed.
|
|
3
|
+
name: check-lanes
|
|
4
|
+
|
|
5
|
+
on:
|
|
6
|
+
push:
|
|
7
|
+
branches: [main]
|
|
8
|
+
pull_request:
|
|
9
|
+
|
|
10
|
+
permissions:
|
|
11
|
+
contents: read
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
check-lanes:
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
with:
|
|
19
|
+
fetch-depth: 0 # a shallow clone skips its oldest commit
|
|
20
|
+
persist-credentials: false
|
|
21
|
+
- uses: actions/setup-node@v4
|
|
22
|
+
with:
|
|
23
|
+
node-version: 22
|
|
24
|
+
- name: Check lane ownership
|
|
25
|
+
env:
|
|
26
|
+
EVENT: ${{ github.event_name }}
|
|
27
|
+
BASE_REF: ${{ github.base_ref }}
|
|
28
|
+
BEFORE: ${{ github.event.before }}
|
|
29
|
+
SHA: ${{ github.sha }}
|
|
30
|
+
run: |
|
|
31
|
+
if [ "$EVENT" = "pull_request" ]; then
|
|
32
|
+
RANGE="origin/$BASE_REF..HEAD"
|
|
33
|
+
elif git cat-file -e "$BEFORE^{commit}" 2>/dev/null; then
|
|
34
|
+
RANGE="$BEFORE..$SHA" # not the case on a first push or after a force-push
|
|
35
|
+
else
|
|
36
|
+
RANGE="$SHA~1..$SHA"
|
|
37
|
+
fi
|
|
38
|
+
npx --yes compounding-loop check-lanes --range "$RANGE"
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
## Lanes
|
|
2
|
+
|
|
3
|
+
This repository is built by parallel **lanes**. Lanes in this repo: {{lanes}}. Each lane is a scheduled run that owns one part of the codebase. `.ai/lanes.json` says which paths each lane owns and which files are shared; read it first. Each lane's task file is `.ai/lanes/<lane>/task.md`.
|
|
4
|
+
|
|
5
|
+
- **Edit only the paths your lane owns.** Read anything. **Shared files** (`shared` in `.ai/lanes.json`) may be edited by any lane, with the smallest possible edit.
|
|
6
|
+
- **Commit trailer:** every commit message ends with a line `Lane: <your lane>` (the control room uses `Lane: control`). `loop check-lanes` fails a commit that touches another lane's paths.
|
|
7
|
+
- **Talking to other lanes:** write only to your own `.ai/lanes/<you>/outbox.md`, newest first, `YYYY-MM-DD HH:MM UTC · to <lane|all> · message`. At the start of each run read every other lane's outbox and `.ai/control-room.md` for messages to you or `all`, and act on them or say in your task file why not.
|
|
8
|
+
- **Main stays green.** If the tests fail on a fresh pull before you change anything, fixing that is your first unit, whichever lane owns the failure. Make the smallest fix, commit it with the trailer `Lane: control` (the only trailer `loop check-lanes` lets touch any path), and tell the owning lane in your outbox.
|
|
9
|
+
- **Git:** work on `main`. Start every run with `git checkout main && git pull --ff-only`, pull again right before each push, and on a rejected push `git pull --rebase`. If a rebase conflicts in a file you do not own, `git rebase --abort`, record it under Tried, pull fresh, redo your small change on top. Never force-push.
|
|
10
|
+
- **Run budget:** your lane's task file holds `Run: N / LIMIT`. Add 1 at the start of every run; at the limit write `.ai/lanes/<lane>/BLOCKED.md` ("run budget reached") and stop.
|
|
11
|
+
- **Stop files:** if root `DONE.md` or `BLOCKED.md`, or your lane's `DONE.md` or `BLOCKED.md`, exists at the start of a run, do nothing and stop. A lane writes its own `DONE.md` when every done-when in its contract passes and the reviewer finds no blockers. Root `DONE.md` is written only by the control room.
|
|
12
|
+
- **Stuck:** the same failure twice means the stuck protocol; its last step is the lane's `BLOCKED.md` with what you tried, the specific question and your best guess.
|
|
13
|
+
- **Review before every push to `main`:** the tests must pass, then a reviewer subagent that did not write the code checks the diff against the brief ("find the three most likely ways this is wrong for a real user, and check each"). Fix blockers first.
|
|
14
|
+
- **Questions:** no human is watching. Decide from the brief and common sense, write the decision and the reason under **Decisions** in your task file, and pick the option that is easiest to reverse.
|
|
15
|
+
- **Lane finished:** read the other lanes' outboxes first; if anyone waits on you, do that before writing `DONE.md`.
|
|
16
|
+
- **Shared files and shared types** are add-only for other lanes: add fields, never rename or remove, and say so in your outbox.
|
|
17
|
+
- **Never:** spend money, sign up for anything, publish anything, add secrets, telemetry or tracking, or touch anything outside this repository.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Outbox: {{lane}}
|
|
2
|
+
|
|
3
|
+
Written only by the {{lane}} lane. Newest first. Format: `YYYY-MM-DD HH:MM UTC · to <lane|all> · message`.
|
|
4
|
+
Every lane reads every outbox at the start of a run. Say what you shipped that another lane waits on (new types, routes, files), and what you need from them. Point to code with `path:line`; do not paste it.
|
|
5
|
+
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Lane: {{lane}}
|
|
2
|
+
|
|
3
|
+
Run: 0 / 20
|
|
4
|
+
Status: not started
|
|
5
|
+
|
|
6
|
+
## Contract
|
|
7
|
+
**Goal:** <one sentence: what this lane delivers>
|
|
8
|
+
**Owns (globs from `.ai/lanes.json`):** {{owns}}
|
|
9
|
+
**Done when:**
|
|
10
|
+
- `npm test` passes (or the project's test command from CLAUDE.md).
|
|
11
|
+
- <a checkable line per deliverable, each provable by a command>
|
|
12
|
+
**Constraints:** edit only the paths you own, plus the smallest edit to shared files (CLAUDE.md "Lanes"). Read anything.
|
|
13
|
+
**Out of scope:** <what belongs to another lane>
|
|
14
|
+
|
|
15
|
+
## Units, in order
|
|
16
|
+
1. **Audit** (`.ai/audit/{{lane}}.md`): review your area as a demanding specialist; findings ranked blocker / major / minor, each with evidence and a one-line fix.
|
|
17
|
+
2. <first real unit>
|
|
18
|
+
|
|
19
|
+
## Decisions
|
|
20
|
+
<decision → reason. Written instead of asking a human.>
|
|
21
|
+
|
|
22
|
+
## Confirmed
|
|
23
|
+
<fact → how we know>
|
|
24
|
+
|
|
25
|
+
## Guesses
|
|
26
|
+
<assumption → what would prove it. Never treat a GUESS from an earlier window as fact.>
|
|
27
|
+
|
|
28
|
+
## Tried
|
|
29
|
+
<attempt → exact error → why it failed>
|
|
30
|
+
|
|
31
|
+
## Don't
|
|
32
|
+
- Edit paths another lane owns (a fix to a red `main` is the one exception; say so in your outbox).
|
|
33
|
+
- Change the Operating Card, the version, or publish anything.
|
|
34
|
+
|
|
35
|
+
## Handoff
|
|
36
|
+
<Rewritten at the end of every run: commit, whether the tree is clean, the single next step, what to read first.>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
You are one run of an unattended build loop for this repository. Nobody is watching. Follow CLAUDE.md exactly, especially "Unattended mode".
|
|
2
|
+
|
|
3
|
+
0. Run `git checkout main && git pull --ff-only`. Every commit in this run goes to `main` and is pushed with `git push origin main`. Never push to a `claude/` branch: the next run starts from `main` and would never see it.
|
|
4
|
+
1. 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 run note at the end of this prompt says so when the loop runner created it): reply "Nothing to do" and stop. If `DONE.md` or `BLOCKED.md` exists, reply "Nothing to do" and stop.
|
|
5
|
+
2. Run `npm install`, then `npx playwright install chromium`. If the download is blocked, see "Commands" in CLAUDE.md.
|
|
6
|
+
3. If `.ai/task.md` exists, run the resume protocol (.ai/method.md §9.2) and add 1 to its `Run: N / 30` counter. If the counter has reached its limit, follow "Run budget" in CLAUDE.md. Otherwise start the task from IDEA.md.
|
|
7
|
+
4. Do one unit of work, then run `npm test`.
|
|
8
|
+
5. Green: commit, have a reviewer subagent check the diff, fix any blockers, run `npm test` again, then push to `main`. Red: write the attempt and the reason under Tried in `.ai/task.md`, then discard the unit's uncommitted changes to the site and tests.
|
|
9
|
+
6. Repeat 4–5 with the next unit while your context is under ~50% full.
|
|
10
|
+
7. Then run the handoff (.ai/method.md §9.1): update `.ai/task.md` so a fresh run with zero memory of this one can continue at full quality. Commit it, push it to `main`, and stop.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# TASK: Build v1 of the site described in IDEA.md (window 1)
|
|
2
|
+
|
|
3
|
+
Run: 0 / 30
|
|
4
|
+
|
|
5
|
+
## Contract
|
|
6
|
+
<Window 1 writes this from IDEA.md before writing any site code.>
|
|
7
|
+
- Goal:
|
|
8
|
+
- Done when: `npm test` passes, including a feature test in `tests/` for every "Must have" in IDEA.md, unit tests for every file in `site/js/lib/`, and one page per keyword cluster in IDEA.md.
|
|
9
|
+
- Constraints: see CLAUDE.md hard rules.
|
|
10
|
+
- Out of scope: everything under "Nice to have" in IDEA.md.
|
|
11
|
+
|
|
12
|
+
## Current state (rewrite every window)
|
|
13
|
+
- Fresh repo from the starter kit. Nothing built yet.
|
|
14
|
+
- Next step: confirm `npm install` and `npm test` work in this environment (GUESS: Playwright's browser download may be blocked in cloud sandboxes; the config then falls back to a system Chrome. If neither works, record it under Decisions and use `npm run check && npm run unit` as the gate). Then write the contract and a plan in `.ai/plans/v1.md` (have a reviewer subagent attack the plan), then build page by page, the main tool first.
|
|
15
|
+
- Read first: IDEA.md, CLAUDE.md.
|
|
16
|
+
|
|
17
|
+
## Decisions (made without the owner: decision → reason)
|
|
18
|
+
|
|
19
|
+
## Confirmed (with how we know)
|
|
20
|
+
|
|
21
|
+
## Guesses (test before building on them)
|
|
22
|
+
|
|
23
|
+
## Tried (append-only: approach → result → why)
|
|
24
|
+
|
|
25
|
+
## Don't
|
|
26
|
+
|
|
27
|
+
## promote? (for the retro)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
* text=auto eol=lf
|