@erclx/aitk 3.51.1 → 3.52.1
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-orchestrate/scripts/watch.sh +74 -3
- package/docs/agents/audits.md +1 -1
- package/docs/agents/commands.md +2 -0
- package/docs/agents/gate.md +84 -0
- package/docs/agents/index.md +1 -0
- package/docs/agents/markdown-audit.md +3 -3
- package/docs/agents/sessions.md +8 -0
- package/docs/agents/state-scoped-risk.md +1 -1
- 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 +4 -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/sessions.ts +27 -1
- package/src/gate/measures.ts +682 -0
- package/src/gate/sequencer.ts +386 -0
- package/src/gate/stages.ts +412 -0
- package/src/sessions/registry.ts +17 -0
- package/src/sessions/resolve.ts +44 -2
- package/scripts/core/verify.sh +0 -694
|
@@ -5,7 +5,12 @@
|
|
|
5
5
|
# holds. This answers "what just changed" on a loop of its own, and it exists
|
|
6
6
|
# because the two readings a dispatch needs are not one reading: a worker that
|
|
7
7
|
# finishes goes idle and a worker that crashes vanishes, so a watch matching
|
|
8
|
-
# only the pull request list stays silent through the second.
|
|
8
|
+
# only the pull request list stays silent through the second. A third case is
|
|
9
|
+
# neither: a worker that stops on a question or a prompt neither finishes nor
|
|
10
|
+
# crashes, and sits reading "waiting" beside the two statuses that resolve on
|
|
11
|
+
# their own until something reports it, which is the WORKER-STOPPED line
|
|
12
|
+
# below, or WORKER-UNMEASURABLE for a "waiting" row too old to carry a stamp
|
|
13
|
+
# this reads.
|
|
9
14
|
#
|
|
10
15
|
# `set -e` is deliberately not set. This runs for hours, and one transient `gh`
|
|
11
16
|
# failure aborting the loop is a silent stop rather than a reported one. Every
|
|
@@ -17,6 +22,28 @@ set -uo pipefail
|
|
|
17
22
|
# in a project whose workers run long.
|
|
18
23
|
INTERVAL=60
|
|
19
24
|
|
|
25
|
+
# Seconds a worker can sit in "waiting" before this reports it as stopped
|
|
26
|
+
# rather than folding it into the ordinary status-change lines below. "busy"
|
|
27
|
+
# and "idle" resolve on their own; "waiting" does not, so a dwell that keeps
|
|
28
|
+
# growing there is a session blocked on something outside itself.
|
|
29
|
+
#
|
|
30
|
+
# Measured against the live session registry: 341 usable records carried a
|
|
31
|
+
# status at all, and exactly one carried "waiting", too sparse a sample to fit
|
|
32
|
+
# a distribution. The number is picked from the two bounds the measurement can
|
|
33
|
+
# still name rather than from a round guess: well above INTERVAL, so a handful
|
|
34
|
+
# of passes confirm the row before it reports rather than one slow tool call
|
|
35
|
+
# tripping it, and well inside the ten-to-thirty-minute span a dispatched build
|
|
36
|
+
# ordinarily runs, so a real stall is caught with most of that window still
|
|
37
|
+
# open to act on it.
|
|
38
|
+
#
|
|
39
|
+
# That one record carried `waitingFor: "approve Bash"`, a permission prompt
|
|
40
|
+
# rather than a question, and the two clear on different schedules: a prompt
|
|
41
|
+
# resolves the moment a person approves it, where a question can sit
|
|
42
|
+
# legitimately while a controller finishes a turn. This number is defensible
|
|
43
|
+
# for the second and generous for the first, and the gap stays open rather
|
|
44
|
+
# than splitting the constant on a sample of one.
|
|
45
|
+
STALL_THRESHOLD_S=300
|
|
46
|
+
|
|
20
47
|
# Resolving the main worktree root rather than this file's folder keeps a watch
|
|
21
48
|
# started from a linked worktree reading the same repository as one started from
|
|
22
49
|
# main. The repository field on a roster row is that root's `.git`.
|
|
@@ -40,13 +67,19 @@ BASE_BRANCH="${BASE_REF#origin/}"
|
|
|
40
67
|
# worker, whoever launched it. The prototype matched the `orchestrator-` prefix
|
|
41
68
|
# instead, which reads a dispatched worker and misses every hand-launched one,
|
|
42
69
|
# and those are the ordinary shape whenever the operator is launching.
|
|
70
|
+
#
|
|
71
|
+
# The row goes out tab-separated and comes back read with IFS set to a tab. A
|
|
72
|
+
# name this client actually writes carries spaces, `Update session markdown
|
|
73
|
+
# and check runnable commands (3)` among them, and splitting on whitespace
|
|
74
|
+
# took that one apart into a wrong name, branch, status, and dwell.
|
|
43
75
|
read_workers() {
|
|
44
76
|
aitk sessions list --json 2>/dev/null | tail -1 | jq -r \
|
|
45
77
|
--arg repository "$REPOSITORY" --arg base "$BASE_BRANCH" '
|
|
46
78
|
.sessions[]?
|
|
47
79
|
| select(.repository == $repository)
|
|
48
80
|
| select(.branch != null and .branch != $base)
|
|
49
|
-
|
|
|
81
|
+
| [.name, .branch, .status, (.statusDwellMs // -1 | tostring)]
|
|
82
|
+
| @tsv
|
|
50
83
|
' 2>/dev/null | sort
|
|
51
84
|
}
|
|
52
85
|
|
|
@@ -70,6 +103,15 @@ prev_names=""
|
|
|
70
103
|
pulls_seen=0
|
|
71
104
|
workers_seen=0
|
|
72
105
|
|
|
106
|
+
# One row per worker name currently reported, waiting past STALL_THRESHOLD_S
|
|
107
|
+
# or waiting with no stamp to measure, so a reader watching the log is told
|
|
108
|
+
# once rather than on every remaining pass. The prototype for that shape is
|
|
109
|
+
# the WORKER-GONE/WORKER pair above, tracked in the same state a status change
|
|
110
|
+
# is: a name drops out the pass it stops reading "waiting", which lets the
|
|
111
|
+
# same worker report a second stall later in its life rather than being
|
|
112
|
+
# marked forever by its first one.
|
|
113
|
+
declare -A stalled
|
|
114
|
+
|
|
73
115
|
while true; do
|
|
74
116
|
pulls="$(read_pulls)"
|
|
75
117
|
pulls_read=$?
|
|
@@ -83,7 +125,7 @@ while true; do
|
|
|
83
125
|
echo "watch: the session roster failed to load, so no worker is classified this pass"
|
|
84
126
|
fi
|
|
85
127
|
|
|
86
|
-
names="$(printf '%s\n' "$workers" | awk 'NF {print $1}' | sort -u)"
|
|
128
|
+
names="$(printf '%s\n' "$workers" | awk -F'\t' 'NF {print $1}' | sort -u)"
|
|
87
129
|
|
|
88
130
|
if [ "$pulls_seen" -eq 1 ] && [ "$pulls_read" -eq 0 ]; then
|
|
89
131
|
comm -13 <(printf '%s\n' "$prev_pulls") <(printf '%s\n' "$pulls") | grep . || true
|
|
@@ -96,6 +138,35 @@ while true; do
|
|
|
96
138
|
grep . | sed 's/^/WORKER-GONE /' || true
|
|
97
139
|
fi
|
|
98
140
|
|
|
141
|
+
if [ "$workers_read" -eq 0 ]; then
|
|
142
|
+
while IFS=$'\t' read -r w_name w_branch w_status w_dwell_ms; do
|
|
143
|
+
[ -z "$w_name" ] && continue
|
|
144
|
+
|
|
145
|
+
if [ "$w_status" != "waiting" ]; then
|
|
146
|
+
unset "stalled[$w_name]"
|
|
147
|
+
continue
|
|
148
|
+
fi
|
|
149
|
+
|
|
150
|
+
if [ "$w_dwell_ms" = "-1" ]; then
|
|
151
|
+
if [ -z "${stalled[$w_name]:-}" ]; then
|
|
152
|
+
echo "WORKER-UNMEASURABLE $w_name $w_branch"
|
|
153
|
+
stalled[$w_name]=1
|
|
154
|
+
fi
|
|
155
|
+
continue
|
|
156
|
+
fi
|
|
157
|
+
|
|
158
|
+
w_dwell_s=$((w_dwell_ms / 1000))
|
|
159
|
+
if [ "$w_dwell_s" -ge "$STALL_THRESHOLD_S" ]; then
|
|
160
|
+
if [ -z "${stalled[$w_name]:-}" ]; then
|
|
161
|
+
echo "WORKER-STOPPED $w_name $w_branch ${w_dwell_s}s"
|
|
162
|
+
stalled[$w_name]=1
|
|
163
|
+
fi
|
|
164
|
+
else
|
|
165
|
+
unset "stalled[$w_name]"
|
|
166
|
+
fi
|
|
167
|
+
done <<<"$workers"
|
|
168
|
+
fi
|
|
169
|
+
|
|
99
170
|
if [ "$pulls_read" -eq 0 ]; then
|
|
100
171
|
prev_pulls="$pulls"
|
|
101
172
|
pulls_seen=1
|
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
|
@@ -64,6 +64,7 @@ Full help: `aitk <command> --help`. Behavior notes for the install and sync verb
|
|
|
64
64
|
| `aitk census [path]` | Report tracked file count, a breakdown by extension, and a line total that skips whatever reads as binary (`--json`) |
|
|
65
65
|
| `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
66
|
| `aitk audits list` | List every audit the set runs, with the corpus each reads and whether it gates (`--json`) |
|
|
67
|
+
| `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
68
|
| `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
69
|
| `aitk capture [source]` | Render HTML capture sources to PNG, toolkit-only and absent from an installed package |
|
|
69
70
|
| `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 +117,7 @@ Each domain exposes a consistent shape where applicable: `list`, `install`, `syn
|
|
|
116
117
|
| `labels` | `audit` |
|
|
117
118
|
| `autoship` | `classify` |
|
|
118
119
|
| `audits` | `run`, `list` |
|
|
120
|
+
| `gate` | `run` |
|
|
119
121
|
|
|
120
122
|
Common patterns:
|
|
121
123
|
|
|
@@ -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,6 +18,7 @@ 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
|
|
@@ -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
|
|
package/docs/agents/sessions.md
CHANGED
|
@@ -105,6 +105,14 @@ Every report states how liveness was decided, on a pass as well as a failure.
|
|
|
105
105
|
|
|
106
106
|
The registry holds one record per session and is never pruned, so it accumulates thousands of entries. On the `unverified` path a stale record whose pid has been reused reads as live, which is why the field is reported rather than assumed.
|
|
107
107
|
|
|
108
|
+
## The status dwell
|
|
109
|
+
|
|
110
|
+
Every row carries `statusUpdatedAt`, the stamp a client writes beside `status` at the moment it last changed, and `statusDwellMs`, the elapsed milliseconds since that stamp. Measured over the live registry, 23 of 341 usable records carry `statusUpdatedAt`, so `null` is the ordinary answer rather than an edge case, and the absence tracks a client version rather than a record's age alone: the one record ever measured carrying `status: "waiting"` is among the 318 without it.
|
|
111
|
+
|
|
112
|
+
`statusDwellMs` falls back to the coarser `updatedAt` stamp when `statusUpdatedAt` is absent, so it is `null` only where a record carries neither. `statusUpdatedAt` itself is never backfilled from the fallback and stays `null` in that case, since it names the exact stamp rather than an estimate. A stamp ahead of the reading clock clamps the dwell to zero rather than reporting a negative one.
|
|
113
|
+
|
|
114
|
+
The dwell is what separates a status that resolves on its own from one that does not. `busy` and `idle` transition without help, so a long dwell there is ordinary. `waiting` does not: a session in that state is blocked on something outside itself, and a dwell that keeps growing past the ordinary span of a prompt is a session stalled rather than paused. `aitk sessions list` renders the dwell beside the status at the coarsest unit that keeps it a whole number, and the JSON record carries both fields on every row.
|
|
115
|
+
|
|
108
116
|
## What the read depends on
|
|
109
117
|
|
|
110
118
|
The records live under the Claude Code configuration directory, which the verb resolves from `CLAUDE_CONFIG_DIR` and falls back to `~/.claude`. Their location, their filenames, and the fields inside them are a client implementation detail rather than a published interface, so a client change can move them. The verb reports an absent registry as a refusal rather than as a machine running no sessions, which is what surfaces the move instead of burying it in an empty roster.
|
|
@@ -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/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@erclx/aitk",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "3.
|
|
4
|
+
"version": "3.52.1",
|
|
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,6 +30,7 @@ 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'
|
|
@@ -84,6 +85,7 @@ function showHelp(): void {
|
|
|
84
85
|
`${GREY}│${NC} autoship [cmd] ${GREY}# Decide whether a changed set needs the review pass (classify)${NC}`,
|
|
85
86
|
`${GREY}│${NC} census [path] ${GREY}# Report tracked file count, extension breakdown, and line totals${NC}`,
|
|
86
87
|
`${GREY}│${NC} audits [cmd] ${GREY}# Run every health check as one set (run, list)${NC}`,
|
|
88
|
+
`${GREY}│${NC} gate [cmd] ${GREY}# Run the merge gate stage by stage (run)${NC}`,
|
|
87
89
|
`${GREY}│${NC} upgrade ${GREY}# Reinstall the CLI globally with the manager that installed it${NC}`,
|
|
88
90
|
`${GREY}│${NC}`,
|
|
89
91
|
`${GREY}│${NC} ${WHITE}Sandbox:${NC}`,
|
|
@@ -132,6 +134,7 @@ function showHelp(): void {
|
|
|
132
134
|
`${GREY}│${NC} aitk labels audit --json`,
|
|
133
135
|
`${GREY}│${NC} aitk census --json`,
|
|
134
136
|
`${GREY}│${NC} aitk audits run --json`,
|
|
137
|
+
`${GREY}│${NC} aitk gate run --all --no-write`,
|
|
135
138
|
`${GREY}│${NC} aitk upgrade --json`,
|
|
136
139
|
`${GREY}└${NC}`,
|
|
137
140
|
]
|
|
@@ -186,6 +189,7 @@ labels(program)
|
|
|
186
189
|
autoship(program)
|
|
187
190
|
census(program)
|
|
188
191
|
audits(program)
|
|
192
|
+
gate(program)
|
|
189
193
|
upgrade(program)
|
|
190
194
|
|
|
191
195
|
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 {
|