done-done-axi 0.6.14 → 0.6.16
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 +13 -8
- package/dist/cli.js +1 -1
- package/dist/lint.js +49 -16
- package/docs/media/hero.gif +0 -0
- package/docs/reference.md +4 -4
- package/docs/releasing.md +3 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -68,11 +68,13 @@ Every answer is an exit code: **0** pass, **1** fail, **2** usage or config erro
|
|
|
68
68
|
|
|
69
69
|
Four more moments from a demo notes app: a capture for the current commit, a judged pass, a check that refuses an older commit and two bad files, and a config error that stops the run. Real output from the current build; these four use a stub judge that says the screen shows the outcome, so no model runs.
|
|
70
70
|
|
|
71
|
-
<picture><source media="(prefers-color-scheme: dark)" srcset="docs/media/run-dark.svg"><img src="docs/media/run-light.svg" alt="done-done run sync records one screenshot for the current commit." width="
|
|
72
|
-
<picture><source media="(prefers-color-scheme: dark)" srcset="docs/media/pass-dark.svg"><img src="docs/media/pass-light.svg" alt="A Checks block cites the new screenshot, the judge passes it, and done-done check passes." width="400" align="top"></picture>
|
|
71
|
+
<p align="center"><picture><source media="(prefers-color-scheme: dark)" srcset="docs/media/run-dark.svg"><img src="docs/media/run-light.svg" alt="done-done run sync records one screenshot for the current commit." width="505"></picture></p>
|
|
73
72
|
|
|
74
|
-
<picture><source media="(prefers-color-scheme: dark)" srcset="docs/media/
|
|
75
|
-
|
|
73
|
+
<p align="center"><picture><source media="(prefers-color-scheme: dark)" srcset="docs/media/pass-dark.svg"><img src="docs/media/pass-light.svg" alt="A Checks block cites the new screenshot, the judge passes it, and done-done check passes." width="505"></picture></p>
|
|
74
|
+
|
|
75
|
+
<p align="center"><picture><source media="(prefers-color-scheme: dark)" srcset="docs/media/reasons-dark.svg"><img src="docs/media/reasons-light.svg" alt="done-done check fails: one screenshot is from an older commit, one file is empty, one is a blank frame." width="505"></picture></p>
|
|
76
|
+
|
|
77
|
+
<p align="center"><picture><source media="(prefers-color-scheme: dark)" srcset="docs/media/config-dark.svg"><img src="docs/media/config-light.svg" alt="done-done run stops with exit 2 because the config has an unknown key." width="505"></picture></p>
|
|
76
78
|
|
|
77
79
|
The hero above is a real run of the published CLI on a fresh sample repo, replayed line by line: `run` recorded the capture at commit `ca78363`, the Pi judge on `commandcode/deepseek/deepseek-v4.1-flash` read it and passed it with that reason, `receipt` wrote the receipt, and `init` refused a journey whose only command was `npm test`. [The manual walkthrough](docs/manual-cli.md) does the same by hand.
|
|
78
80
|
|
|
@@ -92,8 +94,10 @@ A journey is the saved script that drives your app and captures what a user sees
|
|
|
92
94
|
|
|
93
95
|
```sh
|
|
94
96
|
done-done journey add "greet Umer and check that Hello Umer is printed"
|
|
95
|
-
done-done journey enhance greet-umer-prints-hello
|
|
96
|
-
|
|
97
|
+
done-done journey enhance greet-umer-prints-hello \
|
|
98
|
+
"add checks for an empty name and an unknown command"
|
|
99
|
+
done-done journey improve greet-umer-prints-hello \
|
|
100
|
+
"also check that greeting Zoë prints exactly Hello Zoë"
|
|
97
101
|
```
|
|
98
102
|
|
|
99
103
|
`add` writes the script, its config entry and its feature in the verify skill; `enhance` deepens one journey; `improve` makes one change to one journey. Each exercise runs once on uncommitted files, which is a trial, not proof: read the script, commit it, then `done-done run <name>`. The saved script needs no agent or model, so CI can replay it; generation runs only on your machine ([worked transcript](docs/manual-cli.md)).
|
|
@@ -105,8 +109,9 @@ done-done works the same with or without Firstmate: the skill and CLI behave ide
|
|
|
105
109
|
```sh
|
|
106
110
|
npx skills add umeranjum17/done-done-axi --skill done-done -g -a <agent> -y
|
|
107
111
|
npm install -g done-done-axi
|
|
108
|
-
|
|
109
|
-
|
|
112
|
+
DD="$(npm root -g)/done-done-axi/integrations/firstmate"
|
|
113
|
+
cat "$DD/brief-include.md" >> "$FM_HOME/config/brief-include.md"
|
|
114
|
+
cp "$DD/merge-gate" "$FM_HOME/config/merge-gate"
|
|
110
115
|
```
|
|
111
116
|
|
|
112
117
|
- **`brief-include.md`**: Firstmate adds `config/brief-include.md` to every worker brief. It tells the worker to set up done-done, run the journeys `done-done affected` lists for its committed change, write a `## Checks` block, pass `check`, record a `receipt`, and see `affected` exit 0.
|
package/dist/cli.js
CHANGED
|
@@ -281,7 +281,7 @@ each prints the files it changed and runs the journey once as a dirty-tree trial
|
|
|
281
281
|
when the repo (the current folder) has none; repro is not n/a on a fix PR. evidence names files in the evidence folder (or absolute):
|
|
282
282
|
each exists, is non-empty and is newer than the folder itself; no two are byte-identical; at least one has a hash no other PR used
|
|
283
283
|
(hash store lint.hashes in ~/.done-done, or $DONE_DONE_HOME). "evidence: not proven: <cause>" passes. A PR with 1,000 or more added
|
|
284
|
-
non-generated lines fails (lockfiles, vendor/, dist/, LICENSE-type files, deletions and moved lines do not count); a bot PR passes.
|
|
284
|
+
non-generated lines fails; the whole PR is counted (every page of its file list, its compare diff), and a failed page or diff fails instead of counting part of it (lockfiles, vendor/, dist/, LICENSE-type files, deletions and moved lines do not count); a bot PR passes.
|
|
285
285
|
A PR URL whose diff changes a src/ file and no journeys/ file prints WARN instead of PASS (still exit 0): run the journey and commit it before the PR opens.
|
|
286
286
|
In .done-done.yaml, checks.upstream_catch_up: true also drops a fork's upstream catch-up lines, and checks.moved_from: true drops lines
|
|
287
287
|
a "moved-from: <owner>/<repo>@<ref> <dst>=<src>" body line says came from a repo of the same owner; both are off by default.
|
package/dist/lint.js
CHANGED
|
@@ -61,6 +61,34 @@ function without(a, b) {
|
|
|
61
61
|
}
|
|
62
62
|
const total = (n) => [...n.values()].reduce((sum, c) => sum + c, 0);
|
|
63
63
|
const stdout = (r) => (r.status === 0 ? r.stdout.trim() : '');
|
|
64
|
+
const worst = (r) => r.stderr.trim().split('\n').pop()?.slice(0, 160) || '?';
|
|
65
|
+
// `gh pr view --json files` stops at 100 files, so the whole list comes from the paginated pull-files endpoint instead.
|
|
66
|
+
// GitHub stops that endpoint at 3000 files, so page 30 being full is already past what it will list.
|
|
67
|
+
const MAX_PAGES = 30;
|
|
68
|
+
function prFiles(run, nwo, num) {
|
|
69
|
+
const paths = [];
|
|
70
|
+
for (let page = 1; page <= MAX_PAGES; page++) {
|
|
71
|
+
const call = `repos/${nwo}/pulls/${num}/files?per_page=100&page=${page}`;
|
|
72
|
+
const r = run('api', call);
|
|
73
|
+
if (r.status)
|
|
74
|
+
return { why: `${call} failed: ${worst(r)}` };
|
|
75
|
+
let files;
|
|
76
|
+
try {
|
|
77
|
+
files = JSON.parse(r.stdout);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return { why: `${call} returned no file list` };
|
|
81
|
+
}
|
|
82
|
+
if (!Array.isArray(files))
|
|
83
|
+
return { why: `${call} returned no file list` };
|
|
84
|
+
for (const f of files)
|
|
85
|
+
if (f.path)
|
|
86
|
+
paths.push(f.path);
|
|
87
|
+
if (files.length < 100)
|
|
88
|
+
return { paths };
|
|
89
|
+
}
|
|
90
|
+
return { why: `${nwo}/pulls/${num}/files is still full on page ${MAX_PAGES}: more files than lint can count` };
|
|
91
|
+
}
|
|
64
92
|
// checks.upstream_catch_up: lines the fork's parent repo already shipped are not authored here, so subtract the upstream delta the PR
|
|
65
93
|
// merges in (PR base ... the merge base with the parent's default branch). A plain fork PR merges no new upstream commit, so its delta
|
|
66
94
|
// is empty. Any API failure keeps the full count.
|
|
@@ -107,9 +135,9 @@ export function lint(src, ev, dir, rules = {}, run = gh) {
|
|
|
107
135
|
task = basename(dirname(resolve(src)));
|
|
108
136
|
}
|
|
109
137
|
else {
|
|
110
|
-
const r = run('pr', 'view', src, '--json', 'author,body,title,
|
|
138
|
+
const r = run('pr', 'view', src, '--json', 'author,body,title,headRefOid,headRefName,baseRefOid');
|
|
111
139
|
if (r.status)
|
|
112
|
-
return log({ ok: false, why: `gh pr view failed: ${r
|
|
140
|
+
return log({ ok: false, why: `gh pr view failed: ${worst(r)}`, head });
|
|
113
141
|
const pr = JSON.parse(r.stdout);
|
|
114
142
|
({ body, title } = { body: pr.body ?? '', title: pr.title ?? '' });
|
|
115
143
|
head = pr.headRefOid ?? '-';
|
|
@@ -118,26 +146,31 @@ export function lint(src, ev, dir, rules = {}, run = gh) {
|
|
|
118
146
|
if (/^(app\/)?(dependabot|renovate)(\[bot\])?$/.test(author))
|
|
119
147
|
return log({ ok: true, why: 'bot', head });
|
|
120
148
|
const nwo = /^https:\/\/github\.com\/([^/]+\/[^/]+)\/pull\//.exec(src)?.[1];
|
|
121
|
-
const
|
|
149
|
+
const num = /^https:\/\/github\.com\/[^/]+\/[^/]+\/pull\/(\d+)/.exec(src)?.[1];
|
|
150
|
+
if (!nwo || !num)
|
|
151
|
+
return log({ ok: false, why: `not a GitHub pull URL: ${src.slice(0, 120)}`, head });
|
|
152
|
+
const listed = prFiles(run, nwo, num);
|
|
153
|
+
if (!listed.paths)
|
|
154
|
+
return log({ ok: false, why: listed.why ?? 'the pull file list failed', head });
|
|
155
|
+
const paths = listed.paths;
|
|
156
|
+
const attrs = [head, pr.baseRefOid].map((ref) => ref && run('api', '-H', 'Accept: application/vnd.github.raw', `repos/${nwo}/contents/.gitattributes?ref=${ref}`)).find((a) => a && a.status === 0);
|
|
122
157
|
if (attrs) {
|
|
123
158
|
const byAttr = attrGenerated(attrs.stdout);
|
|
124
159
|
isGenerated = (p) => byConfig(p) || byAttr(p);
|
|
125
160
|
}
|
|
126
|
-
const paths = (pr.files ?? []).map((f) => f.path ?? '');
|
|
127
161
|
if (paths.some((p) => p.startsWith('src/')) && !paths.some((p) => p.startsWith('journeys/')))
|
|
128
162
|
warn = 'the diff changes src/ and no journeys/ file: run the journey, commit it, then open the PR';
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}
|
|
163
|
+
// The PR's whole net comes from the compare diff (three dots: from the merge base), never from the per-file additions.
|
|
164
|
+
const cmp = `repos/${nwo}/compare/${pr.baseRefOid ?? ''}...${head}`;
|
|
165
|
+
const d = run('api', '-H', 'Accept: application/vnd.github.diff', cmp);
|
|
166
|
+
if (d.status)
|
|
167
|
+
return log({ ok: false, why: `${cmp} failed: ${worst(d)}`, head });
|
|
168
|
+
let net = netLines(d.stdout, isGenerated);
|
|
169
|
+
if (rules.upstream_catch_up && total(net) >= SIZE_LIMIT)
|
|
170
|
+
net = without(net, upstreamDelta(run, nwo, head, pr.baseRefOid ?? '', isGenerated));
|
|
171
|
+
if (rules.moved_from && total(net) >= SIZE_LIMIT)
|
|
172
|
+
net = without(net, movedLines(run, nwo, body, paths, isGenerated));
|
|
173
|
+
const size = total(net);
|
|
141
174
|
if (size >= SIZE_LIMIT)
|
|
142
175
|
return log({ ok: false, why: `${size} added non-generated lines (moved lines excluded): split it (limit ${SIZE_LIMIT})`, head });
|
|
143
176
|
}
|
package/docs/media/hero.gif
CHANGED
|
Binary file
|
package/docs/reference.md
CHANGED
|
@@ -314,7 +314,7 @@ journeys:
|
|
|
314
314
|
|
|
315
315
|
### Lint check
|
|
316
316
|
|
|
317
|
-
`done-done lint <PR URL | report file> <evidence folder>` is a deterministic gate on a worker's claim: no judge, no model, and the project config only switches on two optional size rules. It prints `PASS`, `WARN: <reason>` or `FAIL: <reason>` and exits 0 (PASS or WARN), 1, or 2 on a usage error. A PR URL is read with `gh pr view`; a file is read as the report.
|
|
317
|
+
`done-done lint <PR URL | report file> <evidence folder>` is a deterministic gate on a worker's claim: no judge, no model, and the project config only switches on two optional size rules. It prints `PASS`, `WARN: <reason>` or `FAIL: <reason>` and exits 0 (PASS or WARN), 1, or 2 on a usage error. A PR URL is read with `gh pr view` and `gh api`; a file is read as the report.
|
|
318
318
|
|
|
319
319
|
- The newest `## Checks` block needs `outcome:`, `repro:`, `proof:` and `evidence:`.
|
|
320
320
|
- `proof:` names a command or path, and the repo's `done-done-verify-<app>` skill (a legacy `verify-<app>` skill is read too); it may say `no verify skill` only when `.agents/skills/` in the current folder holds none.
|
|
@@ -322,8 +322,8 @@ journeys:
|
|
|
322
322
|
- `evidence: not proven: <cause>` passes.
|
|
323
323
|
- A PR URL whose changed files include one under `src/` and none under `journeys/` prints `WARN` instead of `PASS` (exit 0; a `FAIL` still wins). It needs no config and never applies to a report file. The worker rule it backs: run the journey, commit it, then open the PR.
|
|
324
324
|
- Otherwise each named file (relative to the evidence folder, or absolute) exists, is non-empty and is newer than the evidence folder itself; no two are byte-identical; and at least one has a hash no other PR used. Hashes are kept in `lint.hashes` and each run is appended to `lint.log`, both in `~/.done-done` (`$DONE_DONE_HOME`). A later lint of the same PR is not reuse.
|
|
325
|
-
- A PR with 1,000 or more added non-generated lines fails. Lockfiles, `vendor/`, `dist/` and `LICENSE`-type files do not count, and neither do deletions or lines moved between files. A dependabot or renovate PR passes.
|
|
326
|
-
- Two optional size rules, both off by default and switched on per repo in `.done-done.yaml` (read from the current folder; they apply to a PR URL, never a report file). With both off a big PR is counted exactly as above. Each runs only when the PR is still at 1,000 lines
|
|
325
|
+
- A PR with 1,000 or more added non-generated lines fails. The whole PR is counted: its file list comes from every page of `pulls/<number>/files` (100 a page, up to GitHub's 3,000-file ceiling) and its net lines from the compare diff from the merge base, so a PR of any size is counted whole. Lockfiles, `vendor/`, `dist/` and `LICENSE`-type files do not count, and neither do deletions or lines moved between files. A dependabot or renovate PR passes. A page of the file list or the compare diff that fails makes lint `FAIL` with that call, never a partial count.
|
|
326
|
+
- Two optional size rules, both off by default and switched on per repo in `.done-done.yaml` (read from the current folder; they apply to a PR URL, never a report file). With both off a big PR is counted exactly as above. Each runs only when the PR is still at 1,000 lines.
|
|
327
327
|
- `checks.upstream_catch_up: true`: in a fork, lines the parent repo already shipped are not authored here, so lint subtracts the changes between the PR's base and the merge base with the parent's default branch. A PR that merges in no new upstream commit loses nothing.
|
|
328
328
|
- `checks.moved_from: true`: a line in the PR body `moved-from: <owner>/<repo>@<ref> <dst-prefix>=<src-prefix>` (more `dst=src` pairs may follow) cancels added lines that match, byte for byte after trimming, the file at the same path under `<src-prefix>` in that repo at `<ref>`. The source repo must have the PR's owner; authored lines still count.
|
|
329
329
|
- `checks.generated_paths: ["gen/**", "*.snap"]`: globs for the repo's own generated files, counted as generated beside the built-in ones above, so they stay out of the size count. Default none, so results do not change. Like the two rules above it applies to a PR URL, never a report file.
|
|
@@ -860,6 +860,6 @@ npx -y --allow-git=all github:umeranjum17/done-done-axi --help
|
|
|
860
860
|
|
|
861
861
|
`dist/` is committed, so installing from GitHub needs no build step. `npm test` rebuilds it and fails while the rebuilt `dist/` differs from the commit or has files git does not track, so `git add` the new `dist/` files with your change.
|
|
862
862
|
|
|
863
|
-
The first-screen terminal images (`docs/media/hero-*.png`, and the animated `hero.gif`) are real output from the published CLI, captured in a sample repo. `node docs/media/hero.mjs` rebuilds their pages from the captured transcripts beside it (`hero-run.txt`, `hero-refuse.txt`) and fails when a shown value is not in them; screenshot `.hero/hero-desktop.html` (828x596) and `.hero/hero-phone.html` (418x792) at device scale factor 2, in light and dark. The GIF replays the same lines a step at a time on that page,
|
|
863
|
+
The first-screen terminal images (`docs/media/hero-*.png`, and the animated `hero.gif`) are real output from the published CLI, captured in a sample repo. `node docs/media/hero.mjs` rebuilds their pages from the captured transcripts beside it (`hero-run.txt`, `hero-refuse.txt`) and fails when a shown value is not in them; screenshot `.hero/hero-desktop.html` (828x596) and `.hero/hero-phone.html` (418x792) at device scale factor 2, in light and dark. The GIF replays the same lines a step at a time on that page; its first frame is the finished screen, so a still capture or a social preview never shows an empty terminal, and it ends on the judge's pass and the receipt. The four lower terminal illustrations (`hero`, `run`, `pass`, `reasons`, `config`) use a stub judge, so no model runs; redraw them with `npx tsc && node docs/media/make.mjs`. The logo (`docs/logo.svg`) and the flow picture are hand-made SVGs.
|
|
864
864
|
|
|
865
865
|
The demo notes app behind those pictures: start one with `node docs/media/demo.mjs <fresh-scratch-directory> 0` (the server prints its owned port), open a native run in its `app/` repo, drive the checklist in the browser, save `app.png` at 400×140 DPR 2 into the run's evidence folder, seal it and run `check` with your configured judge. Save the open result as `<scratch>/open.json` and the check result as `<scratch>/check.json`; `/proof` displays that screenshot and verdict and refuses to show a pass without a real passing result. Capture `/proof` at 400×230 DPR 2 with `chrome-devtools-axi`, keep the scratch outside the repo and stop the server afterwards.
|
package/docs/releasing.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
# 0.6.
|
|
1
|
+
# 0.6.16 patch release
|
|
2
2
|
|
|
3
|
-
This release
|
|
3
|
+
This release counts a PR whole, however many files it touches. `lint` reads every page of the PR's file list (GitHub gives `gh pr view` only the first 100 files) and takes its net lines from the compare diff, so a PR over 100 files no longer gets a line count from its first 100 — one 408-file PR was counted as 6,198 added lines when its real net was about 688. The moved-from exclusion, the generated-file rules and the `journeys/` warning now see the whole list, and a failed page or compare diff fails the lint with that call instead of keeping a partial count.
|
|
4
4
|
|
|
5
|
-
Do not publish an unmerged candidate. The existing `release` workflow on main tests and publishes an unpublished package version using npm trusted publishing. After merge, confirm `npm view done-done-axi@0.6.
|
|
5
|
+
Do not publish an unmerged candidate. The existing `release` workflow on main tests and publishes an unpublished package version using npm trusted publishing. After merge, confirm `npm view done-done-axi@0.6.16 version dist.integrity` and smoke-test a clean `npx` install with `done-done --version`. Do not run `npm publish` manually unless the workflow fails and the release is blocked; if so, stop and report.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "done-done-axi",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.16",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "Prove your app really works: done-done drives your app the way a user does, a fresh judge checks every capture, and the proof is tied to a commit.",
|
|
6
6
|
"type": "module",
|