done-done-axi 0.4.4 → 0.4.5
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 +1 -0
- package/dist/config.js +2 -1
- package/dist/lint.js +14 -12
- package/docs/reference.md +2 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -219,6 +219,7 @@ const COMMANDS = {
|
|
|
219
219
|
non-generated lines fails (lockfiles, vendor/, dist/, LICENSE-type files, deletions and moved lines do not count); a bot PR passes.
|
|
220
220
|
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
|
|
221
221
|
a "moved-from: <owner>/<repo>@<ref> <dst>=<src>" body line says came from a repo of the same owner; both are off by default.
|
|
222
|
+
checks.generated_paths: ["gen/**", "*.snap"] adds your own globs to the generated files above; the default is none.
|
|
222
223
|
Every run is appended to lint.log next to the store. Exit 0 PASS, 1 FAIL, 2 usage error. Needs gh for a PR URL`],
|
|
223
224
|
},
|
|
224
225
|
status: {
|
package/dist/config.js
CHANGED
|
@@ -71,6 +71,7 @@ const KEYS = {
|
|
|
71
71
|
'checks.fix_title': { scope: 'repo', type: 'string' },
|
|
72
72
|
'checks.upstream_catch_up': { scope: 'repo', type: 'boolean' },
|
|
73
73
|
'checks.moved_from': { scope: 'repo', type: 'boolean' },
|
|
74
|
+
'checks.generated_paths': { scope: 'repo', type: 'string[]' },
|
|
74
75
|
'judge.model': { scope: 'global', type: 'string' },
|
|
75
76
|
'judge.route': { scope: 'global', type: 'route' },
|
|
76
77
|
'judge.floor': { scope: 'global', type: 'number' },
|
|
@@ -275,7 +276,7 @@ function defaultConfig(repo) {
|
|
|
275
276
|
base_ref: '',
|
|
276
277
|
evidence: { local_root: join(home(), 'evidence'), root: '', allow_dirty: false, ignore_paths: [] },
|
|
277
278
|
secrets: { patterns: {}, allow: [] },
|
|
278
|
-
checks: { fix_title: '^(fix|bugfix|hotfix)\\b', upstream_catch_up: false, moved_from: false },
|
|
279
|
+
checks: { fix_title: '^(fix|bugfix|hotfix)\\b', upstream_catch_up: false, moved_from: false, generated_paths: [] },
|
|
279
280
|
judge: { route: '', model: '', floor: 0.7, env_file: '' },
|
|
280
281
|
trusted: false,
|
|
281
282
|
from: {},
|
package/dist/lint.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
// `done-done lint`: the deterministic merge gate on a worker's claim. It reads the newest "## Checks" block of a PR body
|
|
2
2
|
// or report file and fails an unproven, reused or oversized claim. No judge, no model; the repo config only switches on
|
|
3
|
-
// the two optional size rules (checks.upstream_catch_up, checks.moved_from).
|
|
3
|
+
// the two optional size rules (checks.upstream_catch_up, checks.moved_from) and extra generated paths (checks.generated_paths).
|
|
4
4
|
import { spawnSync } from 'node:child_process';
|
|
5
5
|
import { createHash } from 'node:crypto';
|
|
6
6
|
import { appendFileSync, existsSync, mkdirSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
|
7
|
-
import { basename, dirname, isAbsolute, join, resolve } from 'node:path';
|
|
7
|
+
import { basename, dirname, isAbsolute, join, matchesGlob, resolve } from 'node:path';
|
|
8
8
|
// Third-party, legal text and committed build output are not reviewable code.
|
|
9
9
|
const GENERATED = /(^|\/)(package-lock\.json|pnpm-lock\.yaml|yarn\.lock|Cargo\.lock|go\.sum|[^/]*\.lock)$|(^|\/)vendor\/|(^|\/)dist\/|(^|\/)(LICENSE|LICENSES|NOTICE|COPYING)[^/]*$/;
|
|
10
|
+
const generatedBy = (globs = []) => (p) => GENERATED.test(p) || globs.some((g) => matchesGlob(p, g));
|
|
10
11
|
export const SIZE_LIMIT = 1000;
|
|
11
12
|
const FIX_TITLE = /^(fix|bugfix|hotfix)\b/i;
|
|
12
13
|
const NOT_APPLICABLE = /^n\/?a\b/i;
|
|
@@ -14,12 +15,12 @@ const gh = (...args) => {
|
|
|
14
15
|
const r = spawnSync('gh', args, { encoding: 'utf8', timeout: 60_000, maxBuffer: 1 << 28 });
|
|
15
16
|
return { status: r.error ? 1 : r.status, stdout: r.stdout ?? '', stderr: r.error?.message ?? r.stderr ?? '' };
|
|
16
17
|
};
|
|
17
|
-
function netLines(diff) {
|
|
18
|
+
function netLines(diff, isGenerated) {
|
|
18
19
|
const net = new Map();
|
|
19
20
|
let generated = false;
|
|
20
21
|
for (const l of diff.split('\n')) {
|
|
21
22
|
if (l.startsWith('+++ ')) {
|
|
22
|
-
generated =
|
|
23
|
+
generated = isGenerated(l.startsWith('+++ b/') ? l.slice(6) : l.slice(4));
|
|
23
24
|
continue;
|
|
24
25
|
}
|
|
25
26
|
if (l.startsWith('--- ') || generated || !l.slice(1).trim())
|
|
@@ -42,21 +43,21 @@ const stdout = (r) => (r.status === 0 ? r.stdout.trim() : '');
|
|
|
42
43
|
// checks.upstream_catch_up: lines the fork's parent repo already shipped are not authored here, so subtract the upstream delta the PR
|
|
43
44
|
// 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
|
|
44
45
|
// is empty. Any API failure keeps the full count.
|
|
45
|
-
function upstreamDelta(run, nwo, head, base) {
|
|
46
|
+
function upstreamDelta(run, nwo, head, base, isGenerated) {
|
|
46
47
|
const parent = stdout(run('api', `repos/${nwo}`, '--jq', '.parent | select(.) | .owner.login + ":" + .default_branch'));
|
|
47
48
|
const tip = parent && stdout(run('api', `repos/${nwo}/compare/${parent}...${head}`, '--jq', '.merge_base_commit.sha'));
|
|
48
49
|
const diff = tip && run('api', '-H', 'Accept: application/vnd.github.diff', `repos/${nwo}/compare/${base}...${tip}`);
|
|
49
|
-
return diff && diff.status === 0 ? netLines(diff.stdout) : new Map();
|
|
50
|
+
return diff && diff.status === 0 ? netLines(diff.stdout, isGenerated) : new Map();
|
|
50
51
|
}
|
|
51
52
|
// checks.moved_from: a body line "moved-from: <owner>/<repo>@<ref> <dst-prefix>=<src-prefix>..." cancels added lines that byte-match
|
|
52
53
|
// the mapped source files in a repo of the PR's own owner; authored lines still count.
|
|
53
|
-
function movedLines(run, nwo, body, paths) {
|
|
54
|
+
function movedLines(run, nwo, body, paths, isGenerated) {
|
|
54
55
|
const mv = /^\s*moved-from\s*:\s*([\w.-]+)\/([\w.-]+)@(\S+)\s+(.+)$/m.exec(body);
|
|
55
56
|
const rules = mv && mv[1] === nwo.split('/')[0] ? mv[4].split(/\s+/).filter((r) => r.includes('=')).map((r) => [r.slice(0, r.indexOf('=')), r.slice(r.indexOf('=') + 1)]) : [];
|
|
56
57
|
const moved = new Map();
|
|
57
58
|
for (const p of paths) {
|
|
58
59
|
const rule = rules.find(([dst]) => p.startsWith(dst));
|
|
59
|
-
if (!mv || !rule ||
|
|
60
|
+
if (!mv || !rule || isGenerated(p))
|
|
60
61
|
continue;
|
|
61
62
|
const src = run('api', '-H', 'Accept: application/vnd.github.raw', `repos/${mv[1]}/${mv[2]}/contents/${rule[1]}${p.slice(rule[0].length)}?ref=${mv[3]}`);
|
|
62
63
|
if (src.status === 0)
|
|
@@ -69,6 +70,7 @@ function movedLines(run, nwo, body, paths) {
|
|
|
69
70
|
const sha256 = (p) => createHash('sha256').update(readFileSync(p)).digest('hex');
|
|
70
71
|
// src: a PR URL or a report file; ev: the folder the evidence names are relative to; dir: where the hash store and log live.
|
|
71
72
|
export function lint(src, ev, dir, rules = {}, run = gh) {
|
|
73
|
+
const isGenerated = generatedBy(rules.generated_paths);
|
|
72
74
|
const log = (v) => {
|
|
73
75
|
mkdirSync(dir, { recursive: true });
|
|
74
76
|
appendFileSync(join(dir, 'lint.log'), `${new Date().toISOString()}\t${src}\t${v.head}\t${v.ok ? 'pass' : 'fail'}\t${v.why}\n`);
|
|
@@ -90,16 +92,16 @@ export function lint(src, ev, dir, rules = {}, run = gh) {
|
|
|
90
92
|
const author = pr.author?.login ?? '';
|
|
91
93
|
if (/^(app\/)?(dependabot|renovate)(\[bot\])?$/.test(author))
|
|
92
94
|
return log({ ok: true, why: 'bot', head });
|
|
93
|
-
let size = (pr.files ?? []).reduce((n, f) => n + (
|
|
95
|
+
let size = (pr.files ?? []).reduce((n, f) => n + (isGenerated(f.path ?? '') ? 0 : f.additions ?? 0), 0);
|
|
94
96
|
if (size >= SIZE_LIMIT) {
|
|
95
97
|
const d = run('pr', 'diff', src);
|
|
96
98
|
if (!d.status) {
|
|
97
|
-
let net = netLines(d.stdout);
|
|
99
|
+
let net = netLines(d.stdout, isGenerated);
|
|
98
100
|
const nwo = /^https:\/\/github\.com\/([^/]+\/[^/]+)\/pull\//.exec(src)?.[1];
|
|
99
101
|
if (nwo && rules.upstream_catch_up && total(net) >= SIZE_LIMIT)
|
|
100
|
-
net = without(net, upstreamDelta(run, nwo, head, pr.baseRefOid ?? ''));
|
|
102
|
+
net = without(net, upstreamDelta(run, nwo, head, pr.baseRefOid ?? '', isGenerated));
|
|
101
103
|
if (nwo && rules.moved_from && total(net) >= SIZE_LIMIT)
|
|
102
|
-
net = without(net, movedLines(run, nwo, body, (pr.files ?? []).map((f) => f.path ?? '')));
|
|
104
|
+
net = without(net, movedLines(run, nwo, body, (pr.files ?? []).map((f) => f.path ?? ''), isGenerated));
|
|
103
105
|
size = total(net);
|
|
104
106
|
}
|
|
105
107
|
}
|
package/docs/reference.md
CHANGED
|
@@ -234,6 +234,7 @@ journeys:
|
|
|
234
234
|
- 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.
|
|
235
235
|
- `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.
|
|
236
236
|
- `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.
|
|
237
|
+
- `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.
|
|
237
238
|
|
|
238
239
|
### Base-ref before
|
|
239
240
|
|
|
@@ -576,6 +577,7 @@ An `agent_config` entry merges with the defaults field by field, so `claude: { m
|
|
|
576
577
|
| `evidence.ignore_paths` | `[]` |
|
|
577
578
|
| `checks.fix_title` | `^(fix\|bugfix\|hotfix)\b`: titles that need a repro |
|
|
578
579
|
| `checks.upstream_catch_up` | `false`; `true` lets `lint` subtract a fork's upstream catch-up lines from the size count, see [Lint check](#lint-check) |
|
|
580
|
+
| `checks.generated_paths` | `[]`; globs `lint` counts as generated beside lockfiles, `vendor/`, `dist/` and `LICENSE`-type files, see [Lint check](#lint-check) |
|
|
579
581
|
| `checks.moved_from` | `false`; `true` lets `lint` subtract lines a PR moves from another repo of the same owner, see [Lint check](#lint-check) |
|
|
580
582
|
| `agent` | as in the global file; set here, it wins |
|
|
581
583
|
| `auto_fix.maintain` | as in the global file (`0`, report only); set here, it wins |
|
package/package.json
CHANGED