@erclx/aitk 3.52.0 → 3.53.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/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/claude-pr-review/SKILL.md +8 -0
- package/docs/agents/audits.md +1 -1
- package/docs/agents/commands.md +3 -0
- package/docs/agents/gate.md +84 -0
- package/docs/agents/index.md +2 -0
- package/docs/agents/key-changes.md +103 -0
- package/docs/agents/markdown-audit.md +3 -3
- package/docs/agents/state-scoped-risk.md +1 -1
- package/docs/operating-model.md +8 -0
- package/package.json +3 -3
- package/scripts/core/list-seed-roots.sh +18 -0
- package/scripts/core/repair-bare-flag.sh +19 -0
- package/scripts/core/update.sh +3 -3
- package/src/audits/catalog.ts +2 -2
- package/src/audits/run.ts +2 -1
- package/src/cli.ts +7 -0
- package/src/commands/claude.ts +2 -2
- package/src/commands/context.ts +2 -2
- package/src/commands/gate.ts +189 -0
- package/src/commands/pr.ts +411 -0
- package/src/gate/measures.ts +682 -0
- package/src/gate/sequencer.ts +386 -0
- package/src/gate/stages.ts +412 -0
- package/src/pr/bijection.ts +145 -0
- package/src/pr/paths.ts +335 -0
- package/scripts/core/verify.sh +0 -694
|
@@ -117,6 +117,14 @@ Test every unchecked box against the testing discipline in `${CLAUDE_SKILL_DIR}/
|
|
|
117
117
|
|
|
118
118
|
Test every ticked box too, bounded to one naming a file or a command. Confirm what it names still exists at `<headRefOid>`, `git show <headRefOid>:<path>` for a file or `git grep <command> <headRefOid>` for a command, rather than re-running what it claims. A box naming neither stays untested, since nothing here can confirm a claim carrying no artifact. A box ticked before a later commit removed or replaced what it names is a `should-fix` finding on the body itself, filed under a `**PR body**` block ahead of the file blocks, since what it corrupts is the merge record rather than a file in the diff.
|
|
119
119
|
|
|
120
|
+
Test what `## Key Changes` claims against the diff it describes, with `aitk pr key-changes <number> --json`. This repository squash-merges, so that section becomes the commit message and the record on the trunk once the branch is gone, which is the same thing a stale ticked box corrupts. Branch on the record rather than on the exit code, which a shell function wrapping `aitk` can flatten to zero.
|
|
121
|
+
|
|
122
|
+
Read `unmet` as a `should-fix` finding under the same `**PR body**` block the stale box takes, one entry per path, quoting the bullet the record carries on `preview` and naming the `head` the comparison ran at. Read the bullet before filing it, since one class survives the reader: a bullet citing where something is defined while claiming an edit elsewhere, which puts a real path in the claim region and points the change at a locative the path does not name. Report that as the class rather than as a stale claim. A body is edited between pushes, so the reading is true at that commit and can be false a minute later. Read `unnamed` as a question rather than a finding, since the class covers a real omission and equally a lockfile, a generated asset, or a regenerated index that earns no bullet, and raise it only where a reader would want the bullet. Report `unresolved` nowhere: a path written partially was judged in neither direction and says nothing about the body.
|
|
123
|
+
|
|
124
|
+
A `reason` of `no-claims` means the reader resolved no path out of a section that exists, which is the extractor failing over prose rather than the body being wrong. Say so and raise nothing. `no-section` is the body carrying no such heading, which is a finding only where the pull request template asks for one.
|
|
125
|
+
|
|
126
|
+
Compose this on the body read Step 4 already performs rather than opening a second one. That step scans the title and body for a leaked phase label, and both reads answer from the same text at the same head.
|
|
127
|
+
|
|
120
128
|
Ask rather than grade. Whether a human is genuinely required is a reading the branch author may hold a reason this session cannot see, so the question carries no severity and enters no count. What it does carry is the heading and the dispatch, on the rule Step 4 states, because a question the author never receives corrects nothing and the author is the only party who can answer it. Answering it closes it, and the answer may be that the requirement holds.
|
|
121
129
|
|
|
122
130
|
Read `## For the reviewer` the same way, bounded to the bullets under that heading rather than the Summary or the Technical Context around it. Those two carry the author's argument for the change, and reading them while judging it is most of what an independent pass exists to avoid, so the read stops at the section itself.
|
package/docs/agents/audits.md
CHANGED
|
@@ -34,7 +34,7 @@ Each is invoked as the CLI the caller is running rather than as a global `aitk`.
|
|
|
34
34
|
|
|
35
35
|
Four findings fail the run: an unresolved context citation, a banned character, word, or spelling, a skill folder carrying no `REQUIREMENT.md`, and a credential-shaped value in the tree the package ships. Each is a fact with no false-positive class behind it.
|
|
36
36
|
|
|
37
|
-
Three of the four are the ones `
|
|
37
|
+
Three of the four are the ones `aitk gate run` already fails a push on. The secret scan is the one entry gating without a stage behind it, added on that same fact-or-judgment test rather than as a side effect of registering a measure, and the architecture record already ranks content leaving the repository above content that stays.
|
|
38
38
|
|
|
39
39
|
Everything else reports. A heavy bullet, a long entry, a board row nothing resolves, a degradation term in a comment, and an implementation reaching history ahead of its test are judgments a reader settles. A push failing on one of those teaches contributors to route around the stage, which is the split recorded across every audit here and the one this command inherits rather than moves.
|
|
40
40
|
|
package/docs/agents/commands.md
CHANGED
|
@@ -61,9 +61,11 @@ Full help: `aitk <command> --help`. Behavior notes for the install and sync verb
|
|
|
61
61
|
| `aitk labels audit` | Report the labels a changed set earns from the pull request label map and the paths no row reaches (`--json`) |
|
|
62
62
|
| `aitk labels scan` | Fail a pull request whose title or body carries a phase label, sorting a release pull request's tokens as semver rather than as a leak (`--event`, `--json`) |
|
|
63
63
|
| `aitk autoship classify` | Decide whether a changed set needs the review pass, naming the file and the test that decided it (`--json`) |
|
|
64
|
+
| `aitk pr key-changes` | Compare the files a pull request body's Key Changes names against its own diff, in both directions (`--body`, `--base`, `--json`) |
|
|
64
65
|
| `aitk census [path]` | Report tracked file count, a breakdown by extension, and a line total that skips whatever reads as binary (`--json`) |
|
|
65
66
|
| `aitk audits run` | Run every audit as one set, report per check under one verdict, and compare each count to the recorded baseline (`--json`, `--record`) |
|
|
66
67
|
| `aitk audits list` | List every audit the set runs, with the corpus each reads and whether it gates (`--json`) |
|
|
68
|
+
| `aitk gate run` | Run every stage that guards a branch here, scoping shell, types, and tests to the changed set (`--all`, `--no-write`, `--nested`, `--json`) |
|
|
67
69
|
| `aitk inventory [subject]` | Walk every route a project declares and group its elements by the property each computes, as a listing rather than a gate (`--json`) |
|
|
68
70
|
| `aitk capture [source]` | Render HTML capture sources to PNG, toolkit-only and absent from an installed package |
|
|
69
71
|
| `aitk serve [dir]` | Serve a directory on the loopback interface and print the link that opens it, running until interrupted (`--port`, `--entry`, `--json`) |
|
|
@@ -116,6 +118,7 @@ Each domain exposes a consistent shape where applicable: `list`, `install`, `syn
|
|
|
116
118
|
| `labels` | `audit` |
|
|
117
119
|
| `autoship` | `classify` |
|
|
118
120
|
| `audits` | `run`, `list` |
|
|
121
|
+
| `gate` | `run` |
|
|
119
122
|
|
|
120
123
|
Common patterns:
|
|
121
124
|
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Merge gate
|
|
3
|
+
description: Running the gate this repository verifies a branch with, what the stage table holds and what stays a script, how the changed set scopes three stages, and why a stage that cannot read its input reports rather than passing
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Merge gate
|
|
7
|
+
|
|
8
|
+
`aitk gate run` runs every stage that guards a branch here, in order, stopping at the first stage that finds a fact. `bun run check` and `bun run check:ci` resolve to it, and `scripts/core/update.sh` calls it with `--nested` after a dependency update.
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
aitk gate run
|
|
12
|
+
aitk gate run --all --no-write
|
|
13
|
+
aitk gate run --json
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
| Option | Behavior |
|
|
17
|
+
| ------------ | ----------------------------------------------------------------------------- |
|
|
18
|
+
| `--all` | Run every stage instead of scoping shell, types, and tests to the changed set |
|
|
19
|
+
| `--no-write` | Check formatting instead of applying it, which is what a merge gate wants |
|
|
20
|
+
| `--nested` | Suppress the outer frame when a calling script has already opened one |
|
|
21
|
+
| `--json` | Add a machine-readable record on stdout |
|
|
22
|
+
|
|
23
|
+
## What the command owns and what it runs
|
|
24
|
+
|
|
25
|
+
Three things sit in the command: the stage table in `src/gate/stages.ts`, the changed-file scoping and the run loop in `src/gate/sequencer.ts`, and every threshold comparison in `src/gate/measures.ts`. Each individual check is the script or the verb it already was, under `scripts/core/` or behind another `aitk` command, and the move changed none of them. Sequencing, scoping, and comparison are where the recurring defects were, and a check whose behavior changed while its sequencing moved would make any regression impossible to attribute.
|
|
26
|
+
|
|
27
|
+
A stage is a list of checks and a check is one of four kinds:
|
|
28
|
+
|
|
29
|
+
| Kind | What it is |
|
|
30
|
+
| --------- | ------------------------------------------------------------------------------- |
|
|
31
|
+
| `command` | Any binary, run from the project root |
|
|
32
|
+
| `cli` | This checkout's own `src/cli.ts`, never a globally installed `aitk` |
|
|
33
|
+
| `drift` | A regenerated pathspec asserted against the index and against the untracked set |
|
|
34
|
+
| `measure` | A reading whose verdict is a comparison rather than an exit code |
|
|
35
|
+
|
|
36
|
+
A `cli` check runs the source rather than the binary because a globally installed `aitk` resolves to the main checkout no matter which worktree is running, so a gate reading through it would measure the wrong tree and pass a branch it never opened.
|
|
37
|
+
|
|
38
|
+
Every check is an argument vector rather than a shell line, so no stage carries a quoting hazard and a `drift` pathspec reaches git exactly as the table spells it.
|
|
39
|
+
|
|
40
|
+
## Scoping
|
|
41
|
+
|
|
42
|
+
Shell, types, and tests read the changed set. Everything else always runs, because its input is diffuse enough that no path predicts it.
|
|
43
|
+
|
|
44
|
+
The changed set unions the branch diff against the merge base with `origin/main`, the working tree, and untracked files, which is what a pull request will contain. The baseline is the remote ref and not local `main`, since on `main` itself the local ref is HEAD and every unpushed commit would drop out. Every fallback widens rather than narrows: no merge base at all runs every stage, and a local baseline equal to HEAD does the same. `--all` turns scoping off outright, which is what `bun run check:ci` passes so CI stays the backstop for a wrong local scoping decision.
|
|
45
|
+
|
|
46
|
+
## A stage that cannot read its input
|
|
47
|
+
|
|
48
|
+
A stage reports one of four states. It passed, it was scoped out, it found a fact, or it could not measure its input at all.
|
|
49
|
+
|
|
50
|
+
The fourth is the one worth naming. An absent tool, a catalog that did not report, a corpus with nothing under it: each used to print a line that read like a pass. Now the run records it as unmeasured, the closing line says how many stages measured nothing, and the reader is not told a verdict nobody took.
|
|
51
|
+
|
|
52
|
+
What happens next depends on where the run is. On a contributor's machine it warns and the run still exits 0, because an absent tool there is somebody mid-setup. Under CI, read off `CI=true`, it refuses, because the same absence on a runner is a broken workflow step.
|
|
53
|
+
|
|
54
|
+
## Exit codes
|
|
55
|
+
|
|
56
|
+
| Code | Meaning |
|
|
57
|
+
| ---- | ------------------------------------------------------------- |
|
|
58
|
+
| `0` | every stage that ran reported, and none found a fact |
|
|
59
|
+
| `1` | a stage found a fact, or could not measure its input under CI |
|
|
60
|
+
|
|
61
|
+
One code for a failure, which is what a `bun run` caller and a git hook both read. An unmeasured stage takes no code of its own, since it has already refused under CI and reports on a contributor's machine, so a second code would name a state no caller branches on.
|
|
62
|
+
|
|
63
|
+
## The record
|
|
64
|
+
|
|
65
|
+
`--json` puts one record on stdout and keeps every diagnostic on stderr:
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"ok": true,
|
|
70
|
+
"root": "/path/to/checkout",
|
|
71
|
+
"scoped": true,
|
|
72
|
+
"changed": 12,
|
|
73
|
+
"summary": {
|
|
74
|
+
"ran": 23,
|
|
75
|
+
"passed": 22,
|
|
76
|
+
"skipped": 1,
|
|
77
|
+
"unmeasured": 0,
|
|
78
|
+
"failed": 0
|
|
79
|
+
},
|
|
80
|
+
"stages": [{ "id": "indexes", "label": "Indexes", "status": "passed" }]
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
A failing stage carries its remedy in `failure`, and the same line goes to stderr so a caller reading neither the record nor the frame is still told what to fix.
|
package/docs/agents/index.md
CHANGED
|
@@ -18,9 +18,11 @@ CLI catalog and invocation rules for agents, split by command domain. Start with
|
|
|
18
18
|
- [Self-stated counts](counts.md): Reading a sentence that asserts a closed catalog's size, how a match is decided, the plausibility filter that keeps a generic word from matching a subset, and why the sweep reports rather than gates
|
|
19
19
|
- [Demo](demo.md): Compiling a screencast draft into a runnable plan, driving a served application to a recording and a still, the pointer the recording paints, and what each refusal reports
|
|
20
20
|
- [Docs](docs.md): How aitk docs resolves the toolkit's own reference surface from an install root, and how a split domain is named
|
|
21
|
+
- [Merge gate](gate.md): Running the gate this repository verifies a branch with, what the stage table holds and what stays a script, how the changed set scopes three stages, and why a stage that cannot read its input reports rather than passing
|
|
21
22
|
- [Indexes](indexes.md): Flags, exit codes, and JSON shape for aitk indexes regen, plus when it auto-stages what it rewrote
|
|
22
23
|
- [Install and sync](install-and-sync.md): What each install and sync verb writes, refuses, or leaves alone, and how drift is attributed in a target project
|
|
23
24
|
- [Intake](intake.md): Reading intake folder counts and items, the three read states an item can be in, landing a batch of selections in one cluster, the refusal reasons, and why a call is scoped to one file
|
|
25
|
+
- [Key Changes bijection](key-changes.md): Comparing the files a pull request body's Key Changes names against its own diff, the two directions and why they are graded apart, the span rules the extractor was measured into, and the three refusals that separate a clean pass from a read that produced nothing
|
|
24
26
|
- [Label coverage](label-coverage.md): Reading a changed set against the pull request label map, the two tables it matches, how a gap is separated from a decision, and why an absent map is an answer
|
|
25
27
|
- [Markdown audit](markdown-audit.md): Running the audit over any markdown path, where its bans and checkpoints are read from, what each check reports, and why the ban half gates while the structural half reports
|
|
26
28
|
- [Output shape](output-shape.md): Two framed shapes every command renders into, how JSON and --names modes keep stdout clean, and the exit discipline that lets piped output drain
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Key Changes bijection
|
|
3
|
+
description: Comparing the files a pull request body's Key Changes names against its own diff, the two directions and why they are graded apart, the span rules the extractor was measured into, and the three refusals that separate a clean pass from a read that produced nothing
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Key Changes bijection
|
|
7
|
+
|
|
8
|
+
`aitk pr key-changes` reads the `## Key Changes` section of a pull request body, lifts the paths its bullets claim, and compares that set against the files the pull request actually changed. This repository squash-merges, so the body becomes the commit message and the record on the trunk once the branch is gone.
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
aitk pr key-changes
|
|
12
|
+
aitk pr key-changes 1265 --json
|
|
13
|
+
aitk pr key-changes --body .claude/.tmp/pr/body.md --base origin/main
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
The positional is the pull request to read, defaulting to the one open on this branch.
|
|
17
|
+
|
|
18
|
+
| Option | Behavior |
|
|
19
|
+
| --------------- | ------------------------------------------------------------------ |
|
|
20
|
+
| `--body <path>` | Read the body from a file, taking the changed set from git instead |
|
|
21
|
+
| `--base <ref>` | Far side of the range when `--body` supplies the body |
|
|
22
|
+
| `--root <path>` | Repository to read, defaulting to the cwd |
|
|
23
|
+
| `--json` | Add a machine-readable record on stdout, keeping the frame |
|
|
24
|
+
|
|
25
|
+
`--body` decides where both halves come from, so a number passed beside it is never read. The body comes off disk and the changed set from the local range, which is the shape a fixture and a body still being drafted both need.
|
|
26
|
+
|
|
27
|
+
Without `--body` the body, the file list, and the head commit come back from one `gh pr view` call, because the three have to describe the same commit and reading them apart leaves a window where a push between them compares a body against another head's files.
|
|
28
|
+
|
|
29
|
+
That view caps its file list at 100 rows and says nothing about having done so, which was measured against `#1250`: the pull request carries 101 files and the view reports 100. A pull request at the cap therefore takes a second read through the paginated endpoint, and a failure there refuses with `gh-truncated` rather than comparing against a set known to be short. A set silently one file short is the worst input this takes, since the missing file is exactly what a correct bullet would then be accused of inventing.
|
|
30
|
+
|
|
31
|
+
## The two directions
|
|
32
|
+
|
|
33
|
+
They are reported apart because they want different tolerances.
|
|
34
|
+
|
|
35
|
+
- **`unmet`** is a whole path the body claims and the diff does not carry. This is the graded direction and it sets the exit code. A bullet naming an untouched file is wrong more often than not, and it corrupts the record that reaches the trunk.
|
|
36
|
+
- **`unnamed`** is a changed file no bullet reached. Reported with no grade, since the class covers a real omission and equally a lockfile, a generated asset, or a regenerated index that earns no bullet. Grading it would fire on nearly every branch.
|
|
37
|
+
- **`unresolved`** is a path written partially, such as `claude-worker/SKILL.md` for a file under `claude/skills/`. It is judged in neither direction.
|
|
38
|
+
|
|
39
|
+
A partial path can credit a changed file and can never accuse one. Nothing separates a path written short from a path written wrong, so the asymmetry keeps the accusing direction to spans the tree can anchor.
|
|
40
|
+
|
|
41
|
+
## What counts as a claim
|
|
42
|
+
|
|
43
|
+
Only `## Key Changes` is read. `## Technical Context` legitimately names files a branch never touched, such as an install stamp inside a target, so widening the read manufactures a finding out of every argument an author made for the change.
|
|
44
|
+
|
|
45
|
+
Inside the section, the claim region of a bullet ends at its first comma outside a backticked span. That one lever was chosen by measurement. Over the 23 merged pull requests in this repository carrying the section, reading whole bullets reported 16 paths as claimed-but-untouched and every one was a file the body named for context. Cutting at the comma left 110 claims of the original 149 and took the false reports to 2. A list of sixteen clause-opening words tried beside it removed nothing the comma had not already removed, because this corpus punctuates every one of them.
|
|
46
|
+
|
|
47
|
+
A span inside the claim region has to survive all of these:
|
|
48
|
+
|
|
49
|
+
| Rule | What it keeps out |
|
|
50
|
+
| ------------------------------------------------- | ------------------------------------------------------ |
|
|
51
|
+
| No whitespace, `<`, `>`, `$`, `*`, `\|`, `?`, `^` | A backticked command, a placeholder, a glob, a pattern |
|
|
52
|
+
| No `://` and no leading `/`, `~`, `@`, `#`, `!` | A URL, an absolute path, a module alias |
|
|
53
|
+
| Contains `/` | A bare filename with no folder around it |
|
|
54
|
+
| Extension starts with a letter, or ends with `/` | A dotted number such as an address |
|
|
55
|
+
| Not a single top-level folder | `src/`, which nobody claims to have rewritten whole |
|
|
56
|
+
| A `file:line` span leads its bullet | A citation into a file the bullet is describing |
|
|
57
|
+
| The region carries no no-change marker | A bullet recording a file it deliberately left alone |
|
|
58
|
+
|
|
59
|
+
The last three rows earn their place from the corpus rather than from a rule. Every body that spelled a lone top-level folder was naming where something lives, and a line citation that follows another claim in the same bullet points into the file being described rather than naming a second one. A line citation leading its bullet stays a claim, which is how a body names the exact line it rewrote.
|
|
60
|
+
|
|
61
|
+
The no-change marker is the one rule the region cut cannot substitute for. A body writes "Leave `x` untouched, since the decision keeps it" to record a change it declined, and the path sits ahead of the first comma, so a stricter cut would not reach it and a looser one would find more. Since `keep` and `leave` each open a real claim often enough, the marker decides it rather than the leading verb. Three words carry the set: `untouched`, `unchanged`, and `as written`. `in place` was measured and dropped, because rewriting a file in place is an ordinary claim, and `no other line` was dropped because a correct bullet writes "as one insertion that touches no other line" about a change it did make. `alone` shipped in the set too, until review found every corpus occurrence sitting past the first comma, where the cut already excludes it, so the word voided no true claim there. Restrictive use is the more common one in this repository's own prose, and a comma-free bullet exposed it: "Move the threshold read into `src/gate/stages.ts` alone" asserts an edit, and the marker voided it while the word was still in the set. Over the 40-pull-request corpus the rule still voids no true claim.
|
|
62
|
+
|
|
63
|
+
A folder claim covers every changed file beneath it. A bare filename drops outright: resolved as a sibling of a path earlier in its bullet it produced seven wrong paths across this corpus against two right ones, because a compound bullet names a sibling folder as often as a sibling file.
|
|
64
|
+
|
|
65
|
+
## Anchoring
|
|
66
|
+
|
|
67
|
+
A claim is anchored when its first segment names a folder the tree holds. The roots come from the tracked file list joined with the first segment of every changed path, and the second half is what admits a folder the branch created. Reading the tree alone would mark every claim under a new top-level directory unanchored, and an unanchored claim never accuses, so the first branch to open one would lose the graded direction with nothing reporting it.
|
|
68
|
+
|
|
69
|
+
## Exit codes and refusals
|
|
70
|
+
|
|
71
|
+
| Code | Meaning |
|
|
72
|
+
| ---- | ------------------------------------------------- |
|
|
73
|
+
| `0` | every claimed path is in the diff |
|
|
74
|
+
| `1` | refused, with `reason` naming the cause |
|
|
75
|
+
| `2` | at least one claimed path is absent from the diff |
|
|
76
|
+
|
|
77
|
+
Branch on the record rather than on the exit code. A shell function wrapping `aitk` takes its status from whatever it runs last, so every non-zero exit can reach a caller as zero.
|
|
78
|
+
|
|
79
|
+
Three refusals separate a clean pass from a read that produced nothing:
|
|
80
|
+
|
|
81
|
+
- `no-section`. The body carries no such heading, so it claims nothing.
|
|
82
|
+
- `no-claims`. The heading is there and no span resolved, which is the extractor failing over prose rather than the body being wrong. Nothing is raised.
|
|
83
|
+
- `no-changes`. The pull request changed no files, so no claim has anything to answer.
|
|
84
|
+
|
|
85
|
+
An empty extraction read as a clean pass is the failure shape this repository has recorded twice, which is why the middle one is its own reason rather than a zero count.
|
|
86
|
+
|
|
87
|
+
`gh-missing`, `gh-failed`, `gh-truncated`, `unreadable-body`, `unreadable-tree`, `no-base`, `bad-base`, and `unreadable-changes` cover the reads that never reached a comparison.
|
|
88
|
+
|
|
89
|
+
## What it was measured against
|
|
90
|
+
|
|
91
|
+
Driven over the 40 most recent merged pull requests in this repository, 23 of which carry the section, the shipped verb reported zero unmet paths and one `no-claims` refusal. The naive extractor it replaced reported 16.
|
|
92
|
+
|
|
93
|
+
The first body written after that corpus closed produced two, which is what the no-change marker and the open class above come from. One was the disclaiming bullet and the rule now covers it at no cost to the corpus. The other is the definition-site class, left open with the report naming it.
|
|
94
|
+
|
|
95
|
+
The bound is that the corpus is one repository writing to one house style. A project that punctuates differently loses claims to the comma cut rather than gaining false ones, since every rule above drops rather than invents, so the check degrades toward silence rather than toward noise.
|
|
96
|
+
|
|
97
|
+
What it cannot see is a bullet claiming a change to a file the branch did touch for another reason, a path written without backticks, and a claim in a second coordinated clause after the comma. The last of those falls to the ungraded direction rather than out of the reading.
|
|
98
|
+
|
|
99
|
+
One class stays open and is named rather than closed. A bullet can cite where something is defined while claiming an edit somewhere else, as `#1274` does with "Name the slug transform in `standards/slug.md` inline in the same step". The path is a definition site, the edit target is the step, and separating the two needs the sentence parsed rather than cut. A finding on such a bullet is this class rather than a stale claim, and a reader weighing an `unmet` path checks whether the bullet's real target is a locative the path does not name.
|
|
100
|
+
|
|
101
|
+
## Where it runs
|
|
102
|
+
|
|
103
|
+
`claude-pr-review` Step 3 calls it and files an `unmet` path as a `should-fix` finding under the `**PR body**` block the stale ticked box already takes, since what both corrupt is the merge record rather than a file in the diff. A body is edited between pushes, so a finding names the head the comparison ran at.
|
|
@@ -126,9 +126,9 @@ The condition on that was something identifying a finite verb rather than guessi
|
|
|
126
126
|
|
|
127
127
|
Exit codes are `0` for a completed run with no gating finding, `1` for a refusal, `2` for a ban hit, and `3` for a shipped ban set that arrived empty. A banned character, word, or spelling fails the run. Bullet, paragraph, and depth weight are judgments a reader settles, and cadence is a distribution whose healthy range moves with the surface, so all four report under every code.
|
|
128
128
|
|
|
129
|
-
`3` is separate from `1` because the two want different responses from a caller. A refusal means no corpus was built, and the `Markdown bans` stage in `
|
|
129
|
+
`3` is separate from `1` because the two want different responses from a caller. A refusal means no corpus was built, and the `Markdown bans` stage in `aitk gate run` is right to report it as unmeasured rather than as a pass. An empty set means the corpus was walked and nothing was looked for, so that stage fails the push on `3` rather than skipping.
|
|
130
130
|
|
|
131
|
-
`2` rather than `1` for the gate keeps a measurement that succeeded and found something distinct from the audit declining to measure at all. A caller reading one as the other sends a reader hunting a defect that does not exist, which is the distinction `aitk context audit` and the
|
|
131
|
+
`2` rather than `1` for the gate keeps a measurement that succeeded and found something distinct from the audit declining to measure at all. A caller reading one as the other sends a reader hunting a defect that does not exist, which is the distinction `aitk context audit` and the gate's own seed stage already draw between the same two codes.
|
|
132
132
|
|
|
133
133
|
A banned character is a fact rather than a judgment, which is the test that admits it to a gate. What held it back was that gating on day one against a corpus never checked mechanically fails loudly on work nobody has had a chance to fix. The order was to land the verb reporting, measure the corpus once, fix what it finds, and turn the gate on as its own change, and the gate is the last of the four.
|
|
134
134
|
|
|
@@ -148,7 +148,7 @@ A hit the closed set cannot separate from correct prose is the case with no thir
|
|
|
148
148
|
|
|
149
149
|
### Where the rules are enforced
|
|
150
150
|
|
|
151
|
-
Four surfaces apply the ban sets and three of them go through this verb. `.claude/hooks/standards-audit.sh` runs it against a single file after each markdown edit, the seed copy a project installs does the same, and the `Markdown bans` stage in `
|
|
151
|
+
Four surfaces apply the ban sets and three of them go through this verb. `.claude/hooks/standards-audit.sh` runs it against a single file after each markdown edit, the seed copy a project installs does the same, and the `Markdown bans` stage in `aitk gate run` runs it across the whole corpus before a push. Each hook parsed its own copy of the word bans in awk before that, which left a British spelling passing at edit time and failing the push with nothing in between explaining the difference.
|
|
152
152
|
|
|
153
153
|
The seed copy moved onto the verb when the sets became data, since its awk had nothing left to parse. It resolves one runner where the toolkit copy resolves two, looking for no checkout source, and a machine carrying no `aitk` gets a report naming the binary to install rather than a silent pass. `scripts/core/check-seed-independence.sh` scopes its walk to markdown and leaves the seed hooks outside it, which its own comment records as deliberate.
|
|
154
154
|
|
|
@@ -59,7 +59,7 @@ The exemption travels with the line rather than sitting in a path list away from
|
|
|
59
59
|
|
|
60
60
|
Exit codes are `0` when the shipped tree carries no credential-shaped value, `1` for a refusal, and `2` for at least one value found.
|
|
61
61
|
|
|
62
|
-
This is the one entry in `aitk audits run` that gates without
|
|
62
|
+
This is the one entry in `aitk audits run` that gates without an `aitk gate run` stage behind it. A credential in the published tree is a fact rather than a judgment, which is the test the catalog asks any gating addition to pass, and the architecture record already ranks content leaving the repository above content that stays.
|
|
63
63
|
|
|
64
64
|
A refusal is never a clean tree. Five reasons produce one, and each exits `1`, because zero findings over zero files reads in the report exactly like zero findings over the whole shipped tree.
|
|
65
65
|
|
package/docs/operating-model.md
CHANGED
|
@@ -73,6 +73,14 @@ They collide only if the worker also runs a deep pass. Keep the worker's review
|
|
|
73
73
|
light and let the orchestrator own the deep, independent one. The human read at
|
|
74
74
|
merge is the final gate. No layer repeats another.
|
|
75
75
|
|
|
76
|
+
The orchestrator's pass also reads the pull request body against itself, which is
|
|
77
|
+
a vantage the worker never has. This repository squash-merges, so the body
|
|
78
|
+
becomes the commit message and the record on the trunk once the branch is gone.
|
|
79
|
+
`aitk pr key-changes` compares the paths the body's Key Changes claims against
|
|
80
|
+
its own changed-file list, and a claim the diff does not carry is a finding on
|
|
81
|
+
the body rather than on a file. See
|
|
82
|
+
[Key Changes bijection](agents/key-changes.md).
|
|
83
|
+
|
|
76
84
|
## The review channel
|
|
77
85
|
|
|
78
86
|
Findings travel on the PR. `claude-pr-review` posts them there.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@erclx/aitk",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "3.
|
|
4
|
+
"version": "3.53.0",
|
|
5
5
|
"description": "Infrastructure and quality tooling for developer workflows",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"bin": {
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
"check:install": "./scripts/core/install-check.sh",
|
|
35
35
|
"test": "bun --bun vitest run",
|
|
36
36
|
"prepare": "husky",
|
|
37
|
-
"check": "
|
|
38
|
-
"check:ci": "
|
|
37
|
+
"check": "bun src/cli.ts gate run",
|
|
38
|
+
"check:ci": "bun src/cli.ts gate run --all --no-write",
|
|
39
39
|
"update": "./scripts/core/update.sh",
|
|
40
40
|
"clean": "./scripts/core/clean.sh",
|
|
41
41
|
"snapshot": "./scripts/core/snapshot.sh",
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Prints every seed root carrying a `.claude/`, one per line and relative to
|
|
3
|
+
# the project root.
|
|
4
|
+
#
|
|
5
|
+
# The discovery rule has one definition, `collect_seed_roots` in
|
|
6
|
+
# `scripts/lib/tooling.sh`, which `check-seed-independence.sh` already reads.
|
|
7
|
+
# This file is the route a caller outside bash takes to that same answer, so a
|
|
8
|
+
# stack seeding `.claude/` later reaches both readers and the two stages
|
|
9
|
+
# measuring seed content cannot disagree about which roots exist.
|
|
10
|
+
set -e
|
|
11
|
+
set -o pipefail
|
|
12
|
+
|
|
13
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
14
|
+
PROJECT_ROOT="${PROJECT_ROOT:-$(cd "$SCRIPT_DIR/../.." && pwd)}"
|
|
15
|
+
|
|
16
|
+
source "$PROJECT_ROOT/scripts/lib/tooling.sh"
|
|
17
|
+
|
|
18
|
+
collect_seed_roots
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Repairs `core.bare`, which Claude Code's worktree entry leaves set in the
|
|
3
|
+
# shared config with nothing restoring it. The flag strands the main worktree
|
|
4
|
+
# and breaks the git reads that scope a verification run, so this runs ahead of
|
|
5
|
+
# every stage rather than as one of them.
|
|
6
|
+
#
|
|
7
|
+
# The rule itself lives in `scripts/lib/worktree.sh`, the one bash function in
|
|
8
|
+
# this repository under test, so this file is the invocation and never a second
|
|
9
|
+
# copy of the guard that spares a genuinely bare repository.
|
|
10
|
+
set -e
|
|
11
|
+
set -o pipefail
|
|
12
|
+
|
|
13
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
14
|
+
PROJECT_ROOT="${PROJECT_ROOT:-$(cd "$SCRIPT_DIR/../.." && pwd)}"
|
|
15
|
+
|
|
16
|
+
source "$PROJECT_ROOT/scripts/lib/ui.sh"
|
|
17
|
+
source "$PROJECT_ROOT/scripts/lib/worktree.sh"
|
|
18
|
+
|
|
19
|
+
repair_bare_flag "$PROJECT_ROOT"
|
package/scripts/core/update.sh
CHANGED
|
@@ -21,11 +21,11 @@ main() {
|
|
|
21
21
|
bun update --interactive
|
|
22
22
|
|
|
23
23
|
log_step "Verifying project health"
|
|
24
|
-
if [ -f "$
|
|
25
|
-
|
|
24
|
+
if [ -f "$PROJECT_ROOT/src/cli.ts" ]; then
|
|
25
|
+
bun "$PROJECT_ROOT/src/cli.ts" gate run --nested
|
|
26
26
|
log_info "All checks passed"
|
|
27
27
|
else
|
|
28
|
-
log_warn "Verification
|
|
28
|
+
log_warn "Verification entry point not found, skipping."
|
|
29
29
|
fi
|
|
30
30
|
|
|
31
31
|
echo -e "${GREY}└${NC}\n"
|
package/src/audits/catalog.ts
CHANGED
|
@@ -469,8 +469,8 @@ const RECORD_KINDS: readonly (readonly [string, Corpus])[] = [
|
|
|
469
469
|
/**
|
|
470
470
|
* Every audit the aggregate runs.
|
|
471
471
|
*
|
|
472
|
-
* `context`, `markdown`, and `skills` gate because
|
|
473
|
-
* already fails a push on each. `secrets` is the one entry that gates without
|
|
472
|
+
* `context`, `markdown`, and `skills` gate because the merge gate in
|
|
473
|
+
* `src/gate/stages.ts` already fails a push on each. `secrets` is the one entry that gates without
|
|
474
474
|
* a stage behind it, added deliberately rather than as a side effect, since a
|
|
475
475
|
* credential in the published tree is a fact and the split this repository
|
|
476
476
|
* records gates a fact and reports a judgment. Weigh any further addition
|
package/src/audits/run.ts
CHANGED
|
@@ -32,7 +32,8 @@ export type Spawn = (spec: AuditSpec) => Promise<SpawnResult>
|
|
|
32
32
|
* Runs each verb out of the checkout this CLI is executing from.
|
|
33
33
|
*
|
|
34
34
|
* `process.execPath` and the resolved `cli.ts` rather than a bare `aitk`, for
|
|
35
|
-
* the reason `
|
|
35
|
+
* the reason `cliRunner` in `src/gate/sequencer.ts` already names: a globally
|
|
36
|
+
* installed binary resolves to
|
|
36
37
|
* the main checkout no matter which worktree is running, so the aggregate would
|
|
37
38
|
* measure the wrong tree and report a pass over a branch it never read.
|
|
38
39
|
*
|
package/src/cli.ts
CHANGED
|
@@ -30,10 +30,12 @@ import { register as records } from '@/commands/records'
|
|
|
30
30
|
import { register as sessions } from '@/commands/sessions'
|
|
31
31
|
import { register as worktrees } from '@/commands/worktrees'
|
|
32
32
|
import { register as audits } from '@/commands/audits'
|
|
33
|
+
import { register as gate } from '@/commands/gate'
|
|
33
34
|
import { register as secrets } from '@/commands/secrets'
|
|
34
35
|
import { register as deps } from '@/commands/deps'
|
|
35
36
|
import { register as labels } from '@/commands/labels'
|
|
36
37
|
import { register as autoship } from '@/commands/autoship'
|
|
38
|
+
import { register as pr } from '@/commands/pr'
|
|
37
39
|
import { register as census } from '@/commands/census'
|
|
38
40
|
import { register as targets } from '@/commands/targets'
|
|
39
41
|
import { register as upgrade } from '@/commands/upgrade'
|
|
@@ -82,8 +84,10 @@ function showHelp(): void {
|
|
|
82
84
|
`${GREY}│${NC} deps [cmd] ${GREY}# Read the resolved dependency set for advisories (audit)${NC}`,
|
|
83
85
|
`${GREY}│${NC} labels [cmd] ${GREY}# Read a changed set against the pull request label map (audit)${NC}`,
|
|
84
86
|
`${GREY}│${NC} autoship [cmd] ${GREY}# Decide whether a changed set needs the review pass (classify)${NC}`,
|
|
87
|
+
`${GREY}│${NC} pr [cmd] ${GREY}# Read a pull request body against its own diff (key-changes)${NC}`,
|
|
85
88
|
`${GREY}│${NC} census [path] ${GREY}# Report tracked file count, extension breakdown, and line totals${NC}`,
|
|
86
89
|
`${GREY}│${NC} audits [cmd] ${GREY}# Run every health check as one set (run, list)${NC}`,
|
|
90
|
+
`${GREY}│${NC} gate [cmd] ${GREY}# Run the merge gate stage by stage (run)${NC}`,
|
|
87
91
|
`${GREY}│${NC} upgrade ${GREY}# Reinstall the CLI globally with the manager that installed it${NC}`,
|
|
88
92
|
`${GREY}│${NC}`,
|
|
89
93
|
`${GREY}│${NC} ${WHITE}Sandbox:${NC}`,
|
|
@@ -132,6 +136,7 @@ function showHelp(): void {
|
|
|
132
136
|
`${GREY}│${NC} aitk labels audit --json`,
|
|
133
137
|
`${GREY}│${NC} aitk census --json`,
|
|
134
138
|
`${GREY}│${NC} aitk audits run --json`,
|
|
139
|
+
`${GREY}│${NC} aitk gate run --all --no-write`,
|
|
135
140
|
`${GREY}│${NC} aitk upgrade --json`,
|
|
136
141
|
`${GREY}└${NC}`,
|
|
137
142
|
]
|
|
@@ -184,8 +189,10 @@ secrets(program)
|
|
|
184
189
|
deps(program)
|
|
185
190
|
labels(program)
|
|
186
191
|
autoship(program)
|
|
192
|
+
pr(program)
|
|
187
193
|
census(program)
|
|
188
194
|
audits(program)
|
|
195
|
+
gate(program)
|
|
189
196
|
upgrade(program)
|
|
190
197
|
|
|
191
198
|
program.parse()
|
package/src/commands/claude.ts
CHANGED
|
@@ -1136,8 +1136,8 @@ function refuseAudit(
|
|
|
1136
1136
|
/**
|
|
1137
1137
|
* Prints nothing when every skill carries a requirement.
|
|
1138
1138
|
*
|
|
1139
|
-
* `--requirements-only` is what
|
|
1140
|
-
*
|
|
1139
|
+
* `--requirements-only` is what the merge gate runs on every push, and it pipes
|
|
1140
|
+
* a stage's whole output into its own frame. A passing gate that printed
|
|
1141
1141
|
* its frame would nest one inside the other on every contributor's push.
|
|
1142
1142
|
*/
|
|
1143
1143
|
function reportRequirementGate(report: SkillsAudit): void {
|
package/src/commands/context.ts
CHANGED
|
@@ -349,8 +349,8 @@ function refuse(
|
|
|
349
349
|
/**
|
|
350
350
|
* Prints nothing when every path resolves.
|
|
351
351
|
*
|
|
352
|
-
* `--citations-only` is what
|
|
353
|
-
*
|
|
352
|
+
* `--citations-only` is what the merge gate runs on every push, and it pipes
|
|
353
|
+
* a stage's whole output into its own frame. A passing gate that printed
|
|
354
354
|
* its frame would nest one inside the other on every contributor's push.
|
|
355
355
|
*/
|
|
356
356
|
function reportGate(report: ScannedCitations): void {
|