done-done-axi 0.6.15 → 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/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,files,headRefOid,headRefName,baseRefOid');
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.stderr.trim().split('\n').pop()?.slice(0, 160) || '?'}`, head });
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 attrs = nwo && [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);
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
- let size = (pr.files ?? []).reduce((n, f) => n + (isGenerated(f.path ?? '') ? 0 : f.additions ?? 0), 0);
130
- if (size >= SIZE_LIMIT) {
131
- const d = run('pr', 'diff', src);
132
- if (!d.status) {
133
- let net = netLines(d.stdout, isGenerated);
134
- if (nwo && rules.upstream_catch_up && total(net) >= SIZE_LIMIT)
135
- net = without(net, upstreamDelta(run, nwo, head, pr.baseRefOid ?? '', isGenerated));
136
- if (nwo && rules.moved_from && total(net) >= SIZE_LIMIT)
137
- net = without(net, movedLines(run, nwo, body, (pr.files ?? []).map((f) => f.path ?? ''), isGenerated));
138
- size = total(net);
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/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, and an API failure keeps the full count.
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.
package/docs/releasing.md CHANGED
@@ -1,5 +1,5 @@
1
- # 0.6.15 patch release
1
+ # 0.6.16 patch release
2
2
 
3
- This release carries the audited README to the npm page: the hero GIF opens on the finished screen, the four lower terminal pictures sit one per row at one width, and the journey and Firstmate commands wrap so no line is clipped in the README column.
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.15 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.
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.15",
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",