create-agent-rig 0.6.1 → 0.6.2
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/CHANGELOG.md +48 -0
- package/package.json +3 -1
- package/templates/agent-os/universal/.agents/skills/loop/SKILL.md +5 -1
- package/templates/agent-os/universal/.claude/hooks/block-no-verify.mjs +3 -7
- package/templates/agent-os/universal/.claude/hooks/gate-stop-dod.mjs +3 -6
- package/templates/agent-os/universal/.claude/hooks/guard-bash.mjs +4 -7
- package/templates/agent-os/universal/.claude/hooks/guard-core-purity.mjs +3 -7
- package/templates/agent-os/universal/.claude/hooks/guard-rulebook.mjs +20 -11
- package/templates/agent-os/universal/.claude/hooks/guard-secret-file.mjs +3 -7
- package/templates/agent-os/universal/.claude/hooks/guard-web-boundary.mjs +3 -7
- package/templates/agent-os/universal/.claude/hooks/inject-rules.mjs +3 -6
- package/templates/agent-os/universal/.claude/hooks/lib/hook-input.mjs +55 -0
- package/templates/agent-os/universal/.claude/scripts/queue/jira.mjs +20 -8
- package/templates/agent-os/universal/.claude/scripts/unattended-flag.mjs +33 -19
- package/templates/agent-os/universal/.claude/skills/loop/SKILL.md +5 -1
- package/templates/agent-os/universal/.codex/hooks.json +16 -16
- package/templates/agent-os/universal/docs/decisions/spacing-rations-mechanisms.md +15 -0
- package/templates/agent-os/universal/layers.json +1 -0
- package/templates/hash-history.json +44 -22
- package/templates/release-ledger.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,54 @@ Numbering is ordinary semver — **additive is a minor, a fix is a patch** — s
|
|
|
11
11
|
that "I only take minors" remains a usable policy; 0.3.2 shipped additive
|
|
12
12
|
content as a patch by the owner's call and stays recorded as one.
|
|
13
13
|
|
|
14
|
+
## 0.6.2
|
|
15
|
+
|
|
16
|
+
**Patch hardening for the Agent OS shipped by 0.6.1.** This release closes six
|
|
17
|
+
downstream-found governance and transport defects without changing the public
|
|
18
|
+
CLI or adding a dependency.
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- **A UTF-8 BOM on a hook's stdin no longer disarms it.** PowerShell prepends
|
|
23
|
+
one on some Windows hosts, `JSON.parse` throws on a leading U+FEFF, and every
|
|
24
|
+
hook resolved that to its documented fail-open — so a well-formed refusal
|
|
25
|
+
became an allow. On such a host all eight hooks failed open together,
|
|
26
|
+
`guard-bash` among them, which carries the Never tier and the kill switch.
|
|
27
|
+
They now read through one shared `.claude/hooks/lib/hook-input.mjs`. Pinned in
|
|
28
|
+
`test/template/hook-stdin.test.ts` › "blocks the same command when PowerShell
|
|
29
|
+
prepends a UTF-8 BOM" and › "reads stdin through the one shared reader, in
|
|
30
|
+
every hook that reads it".
|
|
31
|
+
- **Windows 8.3 short paths no longer hide a rulebook edit from the guard.**
|
|
32
|
+
`realpathSync` normalises separators but leaves a short name (`RUNNER~1`,
|
|
33
|
+
`SERHII~1`) unexpanded, so a checkout reached by two spellings hashed to two
|
|
34
|
+
unattended-flag names and compared as two directories. Both
|
|
35
|
+
`guard-rulebook` and `unattended-flag` now canonicalise with
|
|
36
|
+
`realpathSync.native`. Pinned in `test/template/unattended-flag.test.ts` ›
|
|
37
|
+
"scopes the flag by the checkout, so two spellings of one directory arm one
|
|
38
|
+
file".
|
|
39
|
+
- **Codex hooks carry the canonical repository root in `CLAUDE_PROJECT_DIR` on
|
|
40
|
+
POSIX and Windows.** A session started in a nested directory therefore judges
|
|
41
|
+
a rulebook edit against the checkout the hook came from. Pinned in
|
|
42
|
+
`test/template/codex.test.ts` › "anchors a nested-cwd Codex rulebook edit to
|
|
43
|
+
the canonical repository root"; the same test file decodes and checks the
|
|
44
|
+
Windows command.
|
|
45
|
+
- **Jira retry is limited to safe reads and the semantically read-only search
|
|
46
|
+
POST.** Comment, transition, issue-create and issue-update mutations return
|
|
47
|
+
the first ambiguous transient failure instead of replaying the write. Pinned
|
|
48
|
+
in `test/template/queue-jira.test.ts` › "does not retry %s" and › "retries a
|
|
49
|
+
semantically read-only search POST after a 429".
|
|
50
|
+
- **`.claude/doctor-exemptions.json` is protected as rulebook input.** An
|
|
51
|
+
unattended edit is refused unless the current item's allow-list names that
|
|
52
|
+
exact file. Pinned in `test/template/guard-rulebook.test.ts` › "allows doctor
|
|
53
|
+
exemptions only when the item names that exact rulebook file" and the guarded
|
|
54
|
+
path table in the same suite.
|
|
55
|
+
- **`lastCompletedTier` is explicitly repository-global across board switches.**
|
|
56
|
+
A selector change cannot reset the spacing brake and admit a second elevated
|
|
57
|
+
mechanism change in the same checkout. The ruling is in
|
|
58
|
+
`docs/decisions/spacing-rations-mechanisms.md`, pinned by
|
|
59
|
+
`test/template/queue-board.test.ts` › "keeps completed-tier spacing
|
|
60
|
+
repository-global when the active board switches".
|
|
61
|
+
|
|
14
62
|
## 0.6.1
|
|
15
63
|
|
|
16
64
|
**Security and upgrade hardening for the Agent OS shipped by 0.6.0.** This patch
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-agent-rig",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"description": "Scaffold a new project with an agent operating system (rules, gates, hooks) and a runnable code skeleton",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"create",
|
|
@@ -51,6 +51,8 @@
|
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"@eslint/js": "^10.0.1",
|
|
53
53
|
"@types/node": "^26.1.1",
|
|
54
|
+
"ajv": "^8.20.0",
|
|
55
|
+
"ajv-formats": "^3.0.1",
|
|
54
56
|
"eslint": "^10.7.0",
|
|
55
57
|
"eslint-config-prettier": "^10.1.8",
|
|
56
58
|
"globals": "^17.7.0",
|
|
@@ -59,7 +59,11 @@ and the `board` command itself refuses a switch while the checkout is unattended
|
|
|
59
59
|
does not prevent an arbitrary direct shell write to the selector — edit-tool
|
|
60
60
|
hooks cannot see one. `.claude/queue.state.json` stays per config, not per board:
|
|
61
61
|
the tier the last close recorded rations the next selection whichever board it
|
|
62
|
-
lands on.
|
|
62
|
+
lands on. This is repository-risk state, not tracker metadata: switching from
|
|
63
|
+
one independent queue to another must not turn a previous mechanism close into
|
|
64
|
+
permission for a second one in the same checkout. Pinned in the generator's
|
|
65
|
+
`test/template/queue-board.test.ts` (absent in a generated rig) › "keeps
|
|
66
|
+
completed-tier spacing repository-global when the active board switches".
|
|
63
67
|
|
|
64
68
|
Adding a fourth is an adapter, not a rewrite: `core.mjs` holds every selection
|
|
65
69
|
decision and each adapter only maps its tracker's records onto the neutral shape.
|
|
@@ -12,15 +12,11 @@
|
|
|
12
12
|
// to tokenise: it owns exactly one invariant and stays readable because of it
|
|
13
13
|
// (see .claude/rules/invariants.md, "One invariant per hook"). Use a file rather
|
|
14
14
|
// than a heredoc, or quote the example.
|
|
15
|
-
import {
|
|
15
|
+
import { readHookInput } from './lib/hook-input.mjs';
|
|
16
16
|
|
|
17
17
|
function main() {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
input = JSON.parse(readFileSync(0, 'utf8'));
|
|
21
|
-
} catch {
|
|
22
|
-
return 0;
|
|
23
|
-
}
|
|
18
|
+
const input = readHookInput();
|
|
19
|
+
if (input === null) return 0;
|
|
24
20
|
if (input.tool_name !== 'Bash') return 0;
|
|
25
21
|
const raw = String(input.tool_input?.command ?? '');
|
|
26
22
|
|
|
@@ -66,6 +66,7 @@ import { readFileSync, realpathSync } from 'node:fs';
|
|
|
66
66
|
import { fileURLToPath } from 'node:url';
|
|
67
67
|
|
|
68
68
|
import { withoutGitLocation } from '../scripts/git-env.mjs';
|
|
69
|
+
import { readHookInput } from './lib/hook-input.mjs';
|
|
69
70
|
|
|
70
71
|
// 🔴 The tree this gate measures is the project the hook BELONGS to — the
|
|
71
72
|
// directory above `.claude/hooks/` — never the directory the session happens
|
|
@@ -166,12 +167,8 @@ function budgetMs(env) {
|
|
|
166
167
|
}
|
|
167
168
|
|
|
168
169
|
function main() {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
input = JSON.parse(readFileSync(0, 'utf8'));
|
|
172
|
-
} catch (error) {
|
|
173
|
-
return failOpen(`the Stop payload could not be read: ${error.message}`);
|
|
174
|
-
}
|
|
170
|
+
const input = readHookInput();
|
|
171
|
+
if (input === null) return failOpen('the Stop payload could not be read');
|
|
175
172
|
if (input.hook_event_name !== 'Stop' && input.hook_event_name !== 'SubagentStop') return 0;
|
|
176
173
|
if (input.stop_hook_active) return 0;
|
|
177
174
|
|
|
@@ -77,9 +77,10 @@
|
|
|
77
77
|
// Contract (Claude Code): JSON on stdin; exit 0 = allow, exit 2 = block, and
|
|
78
78
|
// stderr is shown to the agent as the reason. Fails open on anything it cannot
|
|
79
79
|
// parse — a crashed guard must never make the session unusable.
|
|
80
|
-
import {
|
|
80
|
+
import { realpathSync } from 'node:fs';
|
|
81
81
|
import { fileURLToPath } from 'node:url';
|
|
82
82
|
import { brakeIsOn } from '../scripts/stop-flag.mjs';
|
|
83
|
+
import { readHookInput } from './lib/hook-input.mjs';
|
|
83
84
|
|
|
84
85
|
/** Branches that are shared by definition. */
|
|
85
86
|
const PROTECTED_BRANCH = /^(main|master|develop|development|trunk)$/;
|
|
@@ -804,12 +805,8 @@ export const inspect = (raw, brake, depth = 0) => {
|
|
|
804
805
|
};
|
|
805
806
|
|
|
806
807
|
function main() {
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
input = JSON.parse(readFileSync(0, 'utf8'));
|
|
810
|
-
} catch {
|
|
811
|
-
return 0;
|
|
812
|
-
}
|
|
808
|
+
const input = readHookInput();
|
|
809
|
+
if (input === null) return 0;
|
|
813
810
|
if (input.tool_name !== 'Bash') return 0;
|
|
814
811
|
const commandValue = input.tool_input?.command;
|
|
815
812
|
if (typeof commandValue !== 'string') return 0;
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
// Generator-owned coverage for the neutral bounded-inspection refusal lives upstream in
|
|
9
9
|
// codex.test.ts › "$guard blocks with a neutral, actionable size-limit refusal"; generated
|
|
10
10
|
// projects do not carry that suite, and a downstream edit requires a local replacement test.
|
|
11
|
-
import { readFileSync } from 'node:fs';
|
|
12
11
|
import { editFragments } from './lib/edit-input.mjs';
|
|
12
|
+
import { readHookInput } from './lib/hook-input.mjs';
|
|
13
13
|
|
|
14
14
|
/** The only non-relative import the core may use: its schema/validation library. */
|
|
15
15
|
const ALLOWED_PACKAGES = ['zod'];
|
|
@@ -29,12 +29,8 @@ const BANNED = [
|
|
|
29
29
|
];
|
|
30
30
|
|
|
31
31
|
function main() {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
input = JSON.parse(readFileSync(0, 'utf8'));
|
|
35
|
-
} catch {
|
|
36
|
-
return 0; // unparseable payload: not ours to judge
|
|
37
|
-
}
|
|
32
|
+
const input = readHookInput();
|
|
33
|
+
if (input === null) return 0; // unparseable payload: not ours to judge
|
|
38
34
|
const fragments = editFragments(input);
|
|
39
35
|
const blocked = fragments.find(
|
|
40
36
|
({ inspectionRefusal, appliesToAll }) => appliesToAll && inspectionRefusal,
|
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
//
|
|
5
5
|
// The rulebook is the set of files that decide what this session may do — hooks
|
|
6
6
|
// and settings wiring, the queue config and selector, all scripts, rules, skills
|
|
7
|
-
// and agents, the `.codex/` configuration, the integrity manifest,
|
|
8
|
-
// and `CLAUDE.md`. Every one of
|
|
7
|
+
// and agents, the `.codex/` configuration, the integrity manifest,
|
|
8
|
+
// `.claude/doctor-exemptions.json`, `AGENTS.md` and `CLAUDE.md`. Every one of
|
|
9
|
+
// them was editable by the run it governs: a
|
|
9
10
|
// Write to `.claude/hooks/dod-checks.json` with `[]` switched the stop gate
|
|
10
11
|
// off, and nothing refused it until the merge sweep, long after.
|
|
11
12
|
//
|
|
@@ -60,10 +61,11 @@
|
|
|
60
61
|
// adversary.
|
|
61
62
|
//
|
|
62
63
|
// The rule it enforces is stated in `.claude/rules/autonomy.md`, "Never".
|
|
63
|
-
import {
|
|
64
|
+
import { realpathSync } from 'node:fs';
|
|
64
65
|
import { basename, dirname, join, resolve } from 'node:path';
|
|
65
66
|
import { editFragments } from './lib/edit-input.mjs';
|
|
66
67
|
import { RULEBOOK_PREFIXES, isRulebookPath, readUnattended } from '../scripts/unattended-flag.mjs';
|
|
68
|
+
import { readHookInput } from './lib/hook-input.mjs';
|
|
67
69
|
|
|
68
70
|
export { RULEBOOK_PREFIXES, isRulebookPath };
|
|
69
71
|
|
|
@@ -71,9 +73,20 @@ const EDIT_TOOLS = new Set(['Write', 'Edit', 'MultiEdit', 'NotebookEdit', 'apply
|
|
|
71
73
|
|
|
72
74
|
const toPosix = (value) => String(value ?? '').replaceAll('\\', '/');
|
|
73
75
|
|
|
76
|
+
// RP-54: `.native`, never plain `realpathSync`. Both resolve symlinks and
|
|
77
|
+
// normalise separators; only the native one expands a Windows 8.3 short name,
|
|
78
|
+
// so `C:\Users\RUNNER~1\…` and `C:\Users\runneradmin\…` otherwise survive as
|
|
79
|
+
// two spellings of one directory. The root arrives from
|
|
80
|
+
// `git rev-parse --show-toplevel` (long) while a payload path arrives however
|
|
81
|
+
// the tool spelled it (short under an 8.3 temp or home), and a root that
|
|
82
|
+
// matches no spelling of the payload makes this fail-open guard allow the
|
|
83
|
+
// rulebook edit it exists to refuse. Both sites take the same canonicaliser or
|
|
84
|
+
// the comparison is between two different normalisations. Measured on Windows
|
|
85
|
+
// by codex.test.ts (absent in a generated rig) › "anchors a nested-cwd Windows
|
|
86
|
+
// Codex rulebook edit to the canonical repository root".
|
|
74
87
|
const canonicalRoot = (root) => {
|
|
75
88
|
try {
|
|
76
|
-
return realpathSync(root);
|
|
89
|
+
return realpathSync.native(root);
|
|
77
90
|
} catch {
|
|
78
91
|
return root;
|
|
79
92
|
}
|
|
@@ -85,7 +98,7 @@ const canonicalPath = (filePath) => {
|
|
|
85
98
|
const tail = [];
|
|
86
99
|
for (;;) {
|
|
87
100
|
try {
|
|
88
|
-
return join(realpathSync(cursor), ...tail);
|
|
101
|
+
return join(realpathSync.native(cursor), ...tail);
|
|
89
102
|
} catch {
|
|
90
103
|
const parent = dirname(cursor);
|
|
91
104
|
if (parent === cursor) return filePath;
|
|
@@ -112,12 +125,8 @@ const protectedRelative = (roots, filePath) =>
|
|
|
112
125
|
.find(isRulebookPath);
|
|
113
126
|
|
|
114
127
|
function main() {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
input = JSON.parse(readFileSync(0, 'utf8'));
|
|
118
|
-
} catch {
|
|
119
|
-
return 0; // unparseable payload: not ours to judge
|
|
120
|
-
}
|
|
128
|
+
const input = readHookInput();
|
|
129
|
+
if (input === null) return 0; // unparseable payload: not ours to judge
|
|
121
130
|
if (!EDIT_TOOLS.has(input?.tool_name)) return 0;
|
|
122
131
|
|
|
123
132
|
const selectedRoot = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
@@ -82,10 +82,10 @@
|
|
|
82
82
|
// by an explicit per-line candidate cap rather than running to exhaustion. Any
|
|
83
83
|
// unbounded work in a fail-open guard is a total bypass of every rule at once,
|
|
84
84
|
// not just of this one.
|
|
85
|
-
import { readFileSync } from 'node:fs';
|
|
86
85
|
|
|
87
86
|
import { findSecretValues, isCredentialPath } from '../scripts/lib/secrets.mjs';
|
|
88
87
|
import { editFragments } from './lib/edit-input.mjs';
|
|
88
|
+
import { readHookInput } from './lib/hook-input.mjs';
|
|
89
89
|
|
|
90
90
|
/** Where a refusal points the agent, so the block is actionable rather than a wall. */
|
|
91
91
|
const WHERE_CREDENTIALS_BELONG =
|
|
@@ -93,12 +93,8 @@ const WHERE_CREDENTIALS_BELONG =
|
|
|
93
93
|
'and reach the process through the environment — see .claude/rules/autonomy.md, "Never".';
|
|
94
94
|
|
|
95
95
|
function main() {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
input = JSON.parse(readFileSync(0, 'utf8'));
|
|
99
|
-
} catch {
|
|
100
|
-
return 0; // unparseable payload: not ours to judge
|
|
101
|
-
}
|
|
96
|
+
const input = readHookInput();
|
|
97
|
+
if (input === null) return 0; // unparseable payload: not ours to judge
|
|
102
98
|
|
|
103
99
|
const editTools = new Set(['Write', 'Edit', 'MultiEdit', 'NotebookEdit', 'apply_patch']);
|
|
104
100
|
if (!editTools.has(input?.tool_name)) return 0;
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
// Generator-owned coverage for the neutral bounded-inspection refusal lives upstream in
|
|
9
9
|
// codex.test.ts › "$guard blocks with a neutral, actionable size-limit refusal"; generated
|
|
10
10
|
// projects do not carry that suite, and a downstream edit requires a local replacement test.
|
|
11
|
-
import { readFileSync } from 'node:fs';
|
|
12
11
|
import { editFragments } from './lib/edit-input.mjs';
|
|
12
|
+
import { readHookInput } from './lib/hook-input.mjs';
|
|
13
13
|
|
|
14
14
|
const WEB_PATH = /(^|\/)apps\/web\//;
|
|
15
15
|
const CODE_FILE = /\.(ts|tsx|js|jsx|mjs|cjs)$/;
|
|
@@ -20,12 +20,8 @@ const FORBIDDEN_WORKSPACE = /^@[^/]+\/(db|api|worker)$/;
|
|
|
20
20
|
const FORBIDDEN_RELATIVE = /(^|\/)(packages\/db|services)(\/|$)/;
|
|
21
21
|
|
|
22
22
|
function main() {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
input = JSON.parse(readFileSync(0, 'utf8'));
|
|
26
|
-
} catch {
|
|
27
|
-
return 0; // unparseable payload: not ours to judge
|
|
28
|
-
}
|
|
23
|
+
const input = readHookInput();
|
|
24
|
+
if (input === null) return 0; // unparseable payload: not ours to judge
|
|
29
25
|
const violations = [];
|
|
30
26
|
const importRe =
|
|
31
27
|
/(?:\bfrom\s*|\bimport\s*\(\s*|\brequire\s*\(\s*|^\s*import\s+)['"]([^'"]+)['"]/gm;
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
// is why every ambiguity resolves toward injecting more.
|
|
31
31
|
import { readFileSync, realpathSync } from 'node:fs';
|
|
32
32
|
import { fileURLToPath } from 'node:url';
|
|
33
|
+
import { readHookInput } from './lib/hook-input.mjs';
|
|
33
34
|
|
|
34
35
|
// Regions the rule file marks as not worth injecting. The marker is explicit
|
|
35
36
|
// and lives in the rule file itself, where the person editing it can see it —
|
|
@@ -150,12 +151,8 @@ function invokedDirectly() {
|
|
|
150
151
|
}
|
|
151
152
|
|
|
152
153
|
function main() {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
input = JSON.parse(readFileSync(0, 'utf8'));
|
|
156
|
-
} catch {
|
|
157
|
-
return 0;
|
|
158
|
-
}
|
|
154
|
+
const input = readHookInput();
|
|
155
|
+
if (input === null) return 0;
|
|
159
156
|
if (input.hook_event_name !== 'SessionStart') return 0;
|
|
160
157
|
|
|
161
158
|
let rules;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// The one place a hook reads its payload — `invariants.md`, "One mechanism,
|
|
2
|
+
// one implementation". Eight hooks each parsing stdin is eight chances for one
|
|
3
|
+
// of them to keep a defect the others fixed, and the one nobody looks at is the
|
|
4
|
+
// one that will.
|
|
5
|
+
//
|
|
6
|
+
// 🔴 Why this module exists at all (RP-54). A hook that cannot parse its
|
|
7
|
+
// payload allows the tool call — that is the documented fail-open, and it is
|
|
8
|
+
// correct: a crashed guard must not make the session unusable. But it means an
|
|
9
|
+
// unreadable payload silently disarms EVERY rule the hook carries, so what
|
|
10
|
+
// counts as unreadable has to be as narrow as the format honestly allows.
|
|
11
|
+
//
|
|
12
|
+
// Measured on the hosted `windows-unit` runner, through the generated Codex
|
|
13
|
+
// `commandWindows` wrapper (CI run 33281160544): the wrapper delivers stdin and
|
|
14
|
+
// propagates the child's exit code, but the guard received **290 bytes for a
|
|
15
|
+
// 287-byte payload**. PowerShell prepends a UTF-8 BOM on that host. `JSON.parse`
|
|
16
|
+
// throws on a leading U+FEFF, so every guard resolved a well-formed payload to
|
|
17
|
+
// "not ours to judge" and allowed the edit — including `guard-bash`, which
|
|
18
|
+
// carries the Never tier and the kill switch. A Windows host whose PowerShell
|
|
19
|
+
// does not add the BOM parses the same payload fine, which is why this looked
|
|
20
|
+
// like a runner-only mystery for two days.
|
|
21
|
+
//
|
|
22
|
+
// A BOM is a byte-order mark, not content: stripping one leading U+FEFF is what
|
|
23
|
+
// the format means, not a tolerance added to get a test green.
|
|
24
|
+
//
|
|
25
|
+
// Bounded work, because a fail-open reader is a total bypass if it can throw or
|
|
26
|
+
// spin: one read, one character comparison, one slice, one parse. No loop, no
|
|
27
|
+
// recursion, no rescanning.
|
|
28
|
+
//
|
|
29
|
+
// Limits, stated rather than implied:
|
|
30
|
+
// - Only a BOM at position 0 is stripped. A payload with other leading bytes is
|
|
31
|
+
// still unreadable, and still resolves to `null`.
|
|
32
|
+
// - `null` means "no payload this hook can judge" and every caller keeps its own
|
|
33
|
+
// fail-open on it. This module does not decide policy; it only removes the
|
|
34
|
+
// spelling difference between two hosts.
|
|
35
|
+
// Pinned in hook-stdin.test.ts (absent in a generated rig) ›
|
|
36
|
+
// "blocks the same command when PowerShell prepends a UTF-8 BOM" and ›
|
|
37
|
+
// "reads stdin through the one shared reader, in every hook that reads it".
|
|
38
|
+
|
|
39
|
+
import { readFileSync } from 'node:fs';
|
|
40
|
+
|
|
41
|
+
/** The hook payload as an object, or `null` when there is none this hook can read. */
|
|
42
|
+
export const readHookInput = () => {
|
|
43
|
+
let raw;
|
|
44
|
+
try {
|
|
45
|
+
raw = readFileSync(0, 'utf8');
|
|
46
|
+
} catch {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
if (raw.charCodeAt(0) === 0xfeff) raw = raw.slice(1);
|
|
50
|
+
try {
|
|
51
|
+
return JSON.parse(raw);
|
|
52
|
+
} catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
};
|
|
@@ -286,22 +286,32 @@ const retryDelayMs = (response, attempt) => {
|
|
|
286
286
|
* - a timeout (`timeoutMs`, default 20 s) through an AbortController — a stalled
|
|
287
287
|
* connection used to block selection forever, and a loop that cannot read its
|
|
288
288
|
* queue must stop, not hang;
|
|
289
|
-
* -
|
|
290
|
-
*
|
|
291
|
-
*
|
|
289
|
+
* - safe reads retry 429/502/503/504 at most `MAX_ATTEMPTS`, honouring
|
|
290
|
+
* `Retry-After`; mutating calls do not retry an ambiguous response, because
|
|
291
|
+
* Jira may have applied the write before a proxy returned the error. The
|
|
292
|
+
* search endpoint opts in explicitly: it is a POST at the transport layer and
|
|
293
|
+
* a read at the operation layer;
|
|
292
294
|
* - `retry.sleep` injectable, so a test measures the delay it would have waited
|
|
293
295
|
* instead of waiting it.
|
|
294
296
|
*
|
|
295
297
|
* Pinned in the generator's `test/template/queue-jira.test.ts` (absent in a
|
|
296
298
|
* generated rig) › "hands fetch an AbortSignal", › "rejects naming the timeout
|
|
297
|
-
* and the route when fetch never resolves", › "
|
|
298
|
-
*
|
|
299
|
+
* and the route when fetch never resolves", › "retries a semantically read-only
|
|
300
|
+
* search POST after a 429", › "keeps bounded retry for a safe issue GET", ›
|
|
301
|
+
* "does not retry %s", › "sleeps for the Retry-After the 429 carried, in
|
|
299
302
|
* milliseconds", › "gives up after four consecutive 503s, naming the status and
|
|
300
303
|
* the attempts" and › "does not retry a 401 — a bad credential is not transient".
|
|
301
304
|
*/
|
|
302
305
|
const request = async (
|
|
303
306
|
route,
|
|
304
|
-
{
|
|
307
|
+
{
|
|
308
|
+
method = 'GET',
|
|
309
|
+
body = null,
|
|
310
|
+
env = process.env,
|
|
311
|
+
timeoutMs = DEFAULT_TIMEOUT_MS,
|
|
312
|
+
retry = {},
|
|
313
|
+
retryTransient = method === 'GET',
|
|
314
|
+
} = {},
|
|
305
315
|
) => {
|
|
306
316
|
const { baseUrl, email, token } = requireCredentials(env);
|
|
307
317
|
const sleep = retry.sleep ?? defaultSleep;
|
|
@@ -334,13 +344,14 @@ const request = async (
|
|
|
334
344
|
clearTimeout(timer);
|
|
335
345
|
}
|
|
336
346
|
if (response.ok) return payload;
|
|
337
|
-
|
|
347
|
+
const retryable = retryTransient && TRANSIENT.has(response.status);
|
|
348
|
+
if (retryable && attempt < MAX_ATTEMPTS) {
|
|
338
349
|
await sleep(retryDelayMs(response, attempt));
|
|
339
350
|
continue;
|
|
340
351
|
}
|
|
341
352
|
// The status alone; never echo the response body, which can carry the token
|
|
342
353
|
// back in an error envelope.
|
|
343
|
-
const attempts =
|
|
354
|
+
const attempts = retryable ? ` after ${attempt} attempts` : '';
|
|
344
355
|
throw new Error(`jira ${method} ${route} failed: ${response.status} ${response.statusText}${attempts}`);
|
|
345
356
|
}
|
|
346
357
|
};
|
|
@@ -443,6 +454,7 @@ export const search = async ({
|
|
|
443
454
|
pages += 1;
|
|
444
455
|
const page = await request('/rest/api/3/search/jql', {
|
|
445
456
|
method: 'POST',
|
|
457
|
+
retryTransient: true,
|
|
446
458
|
body: {
|
|
447
459
|
jql: query,
|
|
448
460
|
maxResults: limit,
|
|
@@ -80,6 +80,7 @@ export const MAX_ALLOW_ENTRIES = 64;
|
|
|
80
80
|
export const RULEBOOK_PREFIXES = Object.freeze([
|
|
81
81
|
'.agents/',
|
|
82
82
|
'.claude/.rig-manifest.json',
|
|
83
|
+
'.claude/doctor-exemptions.json',
|
|
83
84
|
'.claude/agents/',
|
|
84
85
|
'.claude/hooks/',
|
|
85
86
|
'.claude/settings.json',
|
|
@@ -116,16 +117,38 @@ export const isWidening = (entry) =>
|
|
|
116
117
|
entry === '.codex/' ||
|
|
117
118
|
RULEBOOK_PREFIXES.some((prefix) => prefix !== entry && prefix.startsWith(entry));
|
|
118
119
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
120
|
+
/**
|
|
121
|
+
* One spelling for one directory — the single canonicaliser this file compares
|
|
122
|
+
* and hashes with (`invariants.md`, "One mechanism, one implementation").
|
|
123
|
+
*
|
|
124
|
+
* RP-54: it is `realpathSync.native`, not `realpathSync`, and on Windows those
|
|
125
|
+
* differ. Both normalise separators; only the native one expands an 8.3 short
|
|
126
|
+
* name, so `C:\Users\RUNNER~1\…` and `C:\Users\runneradmin\…` survive
|
|
127
|
+
* `realpathSync` as two strings for one directory. The flag is named by a hash
|
|
128
|
+
* of this value while the generated Codex Windows hook supplies
|
|
129
|
+
* `git rev-parse --show-toplevel` — a different spelling of the same checkout —
|
|
130
|
+
* so the guard looked for a file nobody wrote and, being fail-open, allowed the
|
|
131
|
+
* rulebook edit it exists to refuse. Proven by
|
|
132
|
+
* unattended-flag.test.ts › "scopes the flag by the checkout, so two spellings
|
|
133
|
+
* of one directory arm one file".
|
|
134
|
+
*
|
|
135
|
+
* A path that does not exist has no real path; `resolve` is the fallback, and
|
|
136
|
+
* it is the same one on both sides of every comparison below.
|
|
137
|
+
*/
|
|
138
|
+
const canonicalPath = (p) => {
|
|
122
139
|
try {
|
|
123
|
-
return realpathSync(
|
|
140
|
+
return realpathSync.native(p);
|
|
124
141
|
} catch {
|
|
125
|
-
return resolve(
|
|
142
|
+
return resolve(p);
|
|
126
143
|
}
|
|
127
144
|
};
|
|
128
145
|
|
|
146
|
+
const canonicalCheckout = (env) => {
|
|
147
|
+
const declared = typeof env.CLAUDE_PROJECT_DIR === 'string' ? env.CLAUDE_PROJECT_DIR.trim() : '';
|
|
148
|
+
if (declared === '') return null;
|
|
149
|
+
return canonicalPath(declared);
|
|
150
|
+
};
|
|
151
|
+
|
|
129
152
|
const checkoutId = (env) => {
|
|
130
153
|
const canonical = canonicalCheckout(env);
|
|
131
154
|
if (canonical === null) return null;
|
|
@@ -290,12 +313,10 @@ export const writeUnattended = ({ item, runDir = null, allow = [] } = {}, env =
|
|
|
290
313
|
};
|
|
291
314
|
|
|
292
315
|
const pathBelongsToCheckout = (candidate, checkout) => {
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
resolved = resolve(candidate);
|
|
298
|
-
}
|
|
316
|
+
// `checkout` is `canonicalCheckout`'s output, so the candidate takes the same
|
|
317
|
+
// canonicaliser: two spellings compared here would put an in-checkout runDir
|
|
318
|
+
// outside its own checkout.
|
|
319
|
+
const resolved = canonicalPath(candidate);
|
|
299
320
|
const rel = relative(checkout, resolved);
|
|
300
321
|
return rel === '' || (!rel.startsWith('..') && !isAbsolute(rel));
|
|
301
322
|
};
|
|
@@ -364,14 +385,7 @@ export const clearLegacyUnattended = (selectedPath) => {
|
|
|
364
385
|
|
|
365
386
|
const invokedDirectly = () => {
|
|
366
387
|
if (!process.argv[1]) return false;
|
|
367
|
-
|
|
368
|
-
try {
|
|
369
|
-
return realpathSync(p);
|
|
370
|
-
} catch {
|
|
371
|
-
return p;
|
|
372
|
-
}
|
|
373
|
-
};
|
|
374
|
-
return real(fileURLToPath(import.meta.url)) === real(process.argv[1]);
|
|
388
|
+
return canonicalPath(fileURLToPath(import.meta.url)) === canonicalPath(process.argv[1]);
|
|
375
389
|
};
|
|
376
390
|
|
|
377
391
|
if (invokedDirectly()) {
|
|
@@ -59,7 +59,11 @@ and the `board` command itself refuses a switch while the checkout is unattended
|
|
|
59
59
|
does not prevent an arbitrary direct shell write to the selector — edit-tool
|
|
60
60
|
hooks cannot see one. `.claude/queue.state.json` stays per config, not per board:
|
|
61
61
|
the tier the last close recorded rations the next selection whichever board it
|
|
62
|
-
lands on.
|
|
62
|
+
lands on. This is repository-risk state, not tracker metadata: switching from
|
|
63
|
+
one independent queue to another must not turn a previous mechanism close into
|
|
64
|
+
permission for a second one in the same checkout. Pinned in the generator's
|
|
65
|
+
`test/template/queue-board.test.ts` (absent in a generated rig) › "keeps
|
|
66
|
+
completed-tier spacing repository-global when the active board switches".
|
|
63
67
|
|
|
64
68
|
Adding a fourth is an adapter, not a rewrite: `core.mjs` holds every selection
|
|
65
69
|
decision and each adapter only maps its tracker's records onto the neutral shape.
|
|
@@ -7,23 +7,23 @@
|
|
|
7
7
|
"hooks": [
|
|
8
8
|
{
|
|
9
9
|
"type": "command",
|
|
10
|
-
"command": "
|
|
11
|
-
"commandWindows": "powershell.exe -NoProfile -NonInteractive -EncodedCommand
|
|
10
|
+
"command": "repoRoot=\"$(git rev-parse --show-toplevel)\" && CLAUDE_PROJECT_DIR=\"$repoRoot\" node \"$repoRoot/.claude/hooks/guard-core-purity.mjs\"",
|
|
11
|
+
"commandWindows": "powershell.exe -NoProfile -NonInteractive -EncodedCommand JABFAHIAcgBvAHIAQQBjAHQAaQBvAG4AUAByAGUAZgBlAHIAZQBuAGMAZQAgAD0AIAAnAFMAdABvAHAAJwA7ACAAJAByAGUAcABvAFIAbwBvAHQAIAA9ACAAZwBpAHQAIAByAGUAdgAtAHAAYQByAHMAZQAgAC0ALQBzAGgAbwB3AC0AdABvAHAAbABlAHYAZQBsADsAIABpAGYAIAAoACQATABBAFMAVABFAFgASQBUAEMATwBEAEUAIAAtAG4AZQAgADAAKQAgAHsAIABlAHgAaQB0ACAAJABMAEEAUwBUAEUAWABJAFQAQwBPAEQARQAgAH0AOwAgACQAZQBuAHYAOgBDAEwAQQBVAEQARQBfAFAAUgBPAEoARQBDAFQAXwBEAEkAUgAgAD0AIAAkAHIAZQBwAG8AUgBvAG8AdAA7ACAAJABoAG8AbwBrAFAAYQB0AGgAIAA9ACAASgBvAGkAbgAtAFAAYQB0AGgAIAAkAHIAZQBwAG8AUgBvAG8AdAAgACcALgBjAGwAYQB1AGQAZQAvAGgAbwBvAGsAcwAvAGcAdQBhAHIAZAAtAGMAbwByAGUALQBwAHUAcgBpAHQAeQAuAG0AagBzACcAOwAgACQAcwB0AGEAcgB0AEkAbgBmAG8AIAA9ACAATgBlAHcALQBPAGIAagBlAGMAdAAgAFMAeQBzAHQAZQBtAC4ARABpAGEAZwBuAG8AcwB0AGkAYwBzAC4AUAByAG8AYwBlAHMAcwBTAHQAYQByAHQASQBuAGYAbwA7ACAAJABzAHQAYQByAHQASQBuAGYAbwAuAEYAaQBsAGUATgBhAG0AZQAgAD0AIAAnAG4AbwBkAGUAJwA7ACAAJABzAHQAYQByAHQASQBuAGYAbwAuAEEAcgBnAHUAbQBlAG4AdABzACAAPQAgACcAIgAnACAAKwAgACQAaABvAG8AawBQAGEAdABoACAAKwAgACcAIgAnADsAIAAkAHMAdABhAHIAdABJAG4AZgBvAC4AVQBzAGUAUwBoAGUAbABsAEUAeABlAGMAdQB0AGUAIAA9ACAAJABmAGEAbABzAGUAOwAgACQAcwB0AGEAcgB0AEkAbgBmAG8ALgBSAGUAZABpAHIAZQBjAHQAUwB0AGEAbgBkAGEAcgBkAEkAbgBwAHUAdAAgAD0AIAAkAHQAcgB1AGUAOwAgACQAYwBoAGkAbABkACAAPQAgAFsAUwB5AHMAdABlAG0ALgBEAGkAYQBnAG4AbwBzAHQAaQBjAHMALgBQAHIAbwBjAGUAcwBzAF0AOgA6AFMAdABhAHIAdAAoACQAcwB0AGEAcgB0AEkAbgBmAG8AKQA7ACAAWwBDAG8AbgBzAG8AbABlAF0AOgA6AE8AcABlAG4AUwB0AGEAbgBkAGEAcgBkAEkAbgBwAHUAdAAoACkALgBDAG8AcAB5AFQAbwAoACQAYwBoAGkAbABkAC4AUwB0AGEAbgBkAGEAcgBkAEkAbgBwAHUAdAAuAEIAYQBzAGUAUwB0AHIAZQBhAG0AKQA7ACAAJABjAGgAaQBsAGQALgBTAHQAYQBuAGQAYQByAGQASQBuAHAAdQB0AC4AQwBsAG8AcwBlACgAKQA7ACAAJABjAGgAaQBsAGQALgBXAGEAaQB0AEYAbwByAEUAeABpAHQAKAApADsAIABlAHgAaQB0ACAAJABjAGgAaQBsAGQALgBFAHgAaQB0AEMAbwBkAGUA"
|
|
12
12
|
},
|
|
13
13
|
{
|
|
14
14
|
"type": "command",
|
|
15
|
-
"command": "
|
|
16
|
-
"commandWindows": "powershell.exe -NoProfile -NonInteractive -EncodedCommand
|
|
15
|
+
"command": "repoRoot=\"$(git rev-parse --show-toplevel)\" && CLAUDE_PROJECT_DIR=\"$repoRoot\" node \"$repoRoot/.claude/hooks/guard-web-boundary.mjs\"",
|
|
16
|
+
"commandWindows": "powershell.exe -NoProfile -NonInteractive -EncodedCommand JABFAHIAcgBvAHIAQQBjAHQAaQBvAG4AUAByAGUAZgBlAHIAZQBuAGMAZQAgAD0AIAAnAFMAdABvAHAAJwA7ACAAJAByAGUAcABvAFIAbwBvAHQAIAA9ACAAZwBpAHQAIAByAGUAdgAtAHAAYQByAHMAZQAgAC0ALQBzAGgAbwB3AC0AdABvAHAAbABlAHYAZQBsADsAIABpAGYAIAAoACQATABBAFMAVABFAFgASQBUAEMATwBEAEUAIAAtAG4AZQAgADAAKQAgAHsAIABlAHgAaQB0ACAAJABMAEEAUwBUAEUAWABJAFQAQwBPAEQARQAgAH0AOwAgACQAZQBuAHYAOgBDAEwAQQBVAEQARQBfAFAAUgBPAEoARQBDAFQAXwBEAEkAUgAgAD0AIAAkAHIAZQBwAG8AUgBvAG8AdAA7ACAAJABoAG8AbwBrAFAAYQB0AGgAIAA9ACAASgBvAGkAbgAtAFAAYQB0AGgAIAAkAHIAZQBwAG8AUgBvAG8AdAAgACcALgBjAGwAYQB1AGQAZQAvAGgAbwBvAGsAcwAvAGcAdQBhAHIAZAAtAHcAZQBiAC0AYgBvAHUAbgBkAGEAcgB5AC4AbQBqAHMAJwA7ACAAJABzAHQAYQByAHQASQBuAGYAbwAgAD0AIABOAGUAdwAtAE8AYgBqAGUAYwB0ACAAUwB5AHMAdABlAG0ALgBEAGkAYQBnAG4AbwBzAHQAaQBjAHMALgBQAHIAbwBjAGUAcwBzAFMAdABhAHIAdABJAG4AZgBvADsAIAAkAHMAdABhAHIAdABJAG4AZgBvAC4ARgBpAGwAZQBOAGEAbQBlACAAPQAgACcAbgBvAGQAZQAnADsAIAAkAHMAdABhAHIAdABJAG4AZgBvAC4AQQByAGcAdQBtAGUAbgB0AHMAIAA9ACAAJwAiACcAIAArACAAJABoAG8AbwBrAFAAYQB0AGgAIAArACAAJwAiACcAOwAgACQAcwB0AGEAcgB0AEkAbgBmAG8ALgBVAHMAZQBTAGgAZQBsAGwARQB4AGUAYwB1AHQAZQAgAD0AIAAkAGYAYQBsAHMAZQA7ACAAJABzAHQAYQByAHQASQBuAGYAbwAuAFIAZQBkAGkAcgBlAGMAdABTAHQAYQBuAGQAYQByAGQASQBuAHAAdQB0ACAAPQAgACQAdAByAHUAZQA7ACAAJABjAGgAaQBsAGQAIAA9ACAAWwBTAHkAcwB0AGUAbQAuAEQAaQBhAGcAbgBvAHMAdABpAGMAcwAuAFAAcgBvAGMAZQBzAHMAXQA6ADoAUwB0AGEAcgB0ACgAJABzAHQAYQByAHQASQBuAGYAbwApADsAIABbAEMAbwBuAHMAbwBsAGUAXQA6ADoATwBwAGUAbgBTAHQAYQBuAGQAYQByAGQASQBuAHAAdQB0ACgAKQAuAEMAbwBwAHkAVABvACgAJABjAGgAaQBsAGQALgBTAHQAYQBuAGQAYQByAGQASQBuAHAAdQB0AC4AQgBhAHMAZQBTAHQAcgBlAGEAbQApADsAIAAkAGMAaABpAGwAZAAuAFMAdABhAG4AZABhAHIAZABJAG4AcAB1AHQALgBDAGwAbwBzAGUAKAApADsAIAAkAGMAaABpAGwAZAAuAFcAYQBpAHQARgBvAHIARQB4AGkAdAAoACkAOwAgAGUAeABpAHQAIAAkAGMAaABpAGwAZAAuAEUAeABpAHQAQwBvAGQAZQA="
|
|
17
17
|
},
|
|
18
18
|
{
|
|
19
19
|
"type": "command",
|
|
20
|
-
"command": "
|
|
21
|
-
"commandWindows": "powershell.exe -NoProfile -NonInteractive -EncodedCommand
|
|
20
|
+
"command": "repoRoot=\"$(git rev-parse --show-toplevel)\" && CLAUDE_PROJECT_DIR=\"$repoRoot\" node \"$repoRoot/.claude/hooks/guard-secret-file.mjs\"",
|
|
21
|
+
"commandWindows": "powershell.exe -NoProfile -NonInteractive -EncodedCommand JABFAHIAcgBvAHIAQQBjAHQAaQBvAG4AUAByAGUAZgBlAHIAZQBuAGMAZQAgAD0AIAAnAFMAdABvAHAAJwA7ACAAJAByAGUAcABvAFIAbwBvAHQAIAA9ACAAZwBpAHQAIAByAGUAdgAtAHAAYQByAHMAZQAgAC0ALQBzAGgAbwB3AC0AdABvAHAAbABlAHYAZQBsADsAIABpAGYAIAAoACQATABBAFMAVABFAFgASQBUAEMATwBEAEUAIAAtAG4AZQAgADAAKQAgAHsAIABlAHgAaQB0ACAAJABMAEEAUwBUAEUAWABJAFQAQwBPAEQARQAgAH0AOwAgACQAZQBuAHYAOgBDAEwAQQBVAEQARQBfAFAAUgBPAEoARQBDAFQAXwBEAEkAUgAgAD0AIAAkAHIAZQBwAG8AUgBvAG8AdAA7ACAAJABoAG8AbwBrAFAAYQB0AGgAIAA9ACAASgBvAGkAbgAtAFAAYQB0AGgAIAAkAHIAZQBwAG8AUgBvAG8AdAAgACcALgBjAGwAYQB1AGQAZQAvAGgAbwBvAGsAcwAvAGcAdQBhAHIAZAAtAHMAZQBjAHIAZQB0AC0AZgBpAGwAZQAuAG0AagBzACcAOwAgACQAcwB0AGEAcgB0AEkAbgBmAG8AIAA9ACAATgBlAHcALQBPAGIAagBlAGMAdAAgAFMAeQBzAHQAZQBtAC4ARABpAGEAZwBuAG8AcwB0AGkAYwBzAC4AUAByAG8AYwBlAHMAcwBTAHQAYQByAHQASQBuAGYAbwA7ACAAJABzAHQAYQByAHQASQBuAGYAbwAuAEYAaQBsAGUATgBhAG0AZQAgAD0AIAAnAG4AbwBkAGUAJwA7ACAAJABzAHQAYQByAHQASQBuAGYAbwAuAEEAcgBnAHUAbQBlAG4AdABzACAAPQAgACcAIgAnACAAKwAgACQAaABvAG8AawBQAGEAdABoACAAKwAgACcAIgAnADsAIAAkAHMAdABhAHIAdABJAG4AZgBvAC4AVQBzAGUAUwBoAGUAbABsAEUAeABlAGMAdQB0AGUAIAA9ACAAJABmAGEAbABzAGUAOwAgACQAcwB0AGEAcgB0AEkAbgBmAG8ALgBSAGUAZABpAHIAZQBjAHQAUwB0AGEAbgBkAGEAcgBkAEkAbgBwAHUAdAAgAD0AIAAkAHQAcgB1AGUAOwAgACQAYwBoAGkAbABkACAAPQAgAFsAUwB5AHMAdABlAG0ALgBEAGkAYQBnAG4AbwBzAHQAaQBjAHMALgBQAHIAbwBjAGUAcwBzAF0AOgA6AFMAdABhAHIAdAAoACQAcwB0AGEAcgB0AEkAbgBmAG8AKQA7ACAAWwBDAG8AbgBzAG8AbABlAF0AOgA6AE8AcABlAG4AUwB0AGEAbgBkAGEAcgBkAEkAbgBwAHUAdAAoACkALgBDAG8AcAB5AFQAbwAoACQAYwBoAGkAbABkAC4AUwB0AGEAbgBkAGEAcgBkAEkAbgBwAHUAdAAuAEIAYQBzAGUAUwB0AHIAZQBhAG0AKQA7ACAAJABjAGgAaQBsAGQALgBTAHQAYQBuAGQAYQByAGQASQBuAHAAdQB0AC4AQwBsAG8AcwBlACgAKQA7ACAAJABjAGgAaQBsAGQALgBXAGEAaQB0AEYAbwByAEUAeABpAHQAKAApADsAIABlAHgAaQB0ACAAJABjAGgAaQBsAGQALgBFAHgAaQB0AEMAbwBkAGUA"
|
|
22
22
|
},
|
|
23
23
|
{
|
|
24
24
|
"type": "command",
|
|
25
|
-
"command": "
|
|
26
|
-
"commandWindows": "powershell.exe -NoProfile -NonInteractive -EncodedCommand
|
|
25
|
+
"command": "repoRoot=\"$(git rev-parse --show-toplevel)\" && CLAUDE_PROJECT_DIR=\"$repoRoot\" node \"$repoRoot/.claude/hooks/guard-rulebook.mjs\"",
|
|
26
|
+
"commandWindows": "powershell.exe -NoProfile -NonInteractive -EncodedCommand JABFAHIAcgBvAHIAQQBjAHQAaQBvAG4AUAByAGUAZgBlAHIAZQBuAGMAZQAgAD0AIAAnAFMAdABvAHAAJwA7ACAAJAByAGUAcABvAFIAbwBvAHQAIAA9ACAAZwBpAHQAIAByAGUAdgAtAHAAYQByAHMAZQAgAC0ALQBzAGgAbwB3AC0AdABvAHAAbABlAHYAZQBsADsAIABpAGYAIAAoACQATABBAFMAVABFAFgASQBUAEMATwBEAEUAIAAtAG4AZQAgADAAKQAgAHsAIABlAHgAaQB0ACAAJABMAEEAUwBUAEUAWABJAFQAQwBPAEQARQAgAH0AOwAgACQAZQBuAHYAOgBDAEwAQQBVAEQARQBfAFAAUgBPAEoARQBDAFQAXwBEAEkAUgAgAD0AIAAkAHIAZQBwAG8AUgBvAG8AdAA7ACAAJABoAG8AbwBrAFAAYQB0AGgAIAA9ACAASgBvAGkAbgAtAFAAYQB0AGgAIAAkAHIAZQBwAG8AUgBvAG8AdAAgACcALgBjAGwAYQB1AGQAZQAvAGgAbwBvAGsAcwAvAGcAdQBhAHIAZAAtAHIAdQBsAGUAYgBvAG8AawAuAG0AagBzACcAOwAgACQAcwB0AGEAcgB0AEkAbgBmAG8AIAA9ACAATgBlAHcALQBPAGIAagBlAGMAdAAgAFMAeQBzAHQAZQBtAC4ARABpAGEAZwBuAG8AcwB0AGkAYwBzAC4AUAByAG8AYwBlAHMAcwBTAHQAYQByAHQASQBuAGYAbwA7ACAAJABzAHQAYQByAHQASQBuAGYAbwAuAEYAaQBsAGUATgBhAG0AZQAgAD0AIAAnAG4AbwBkAGUAJwA7ACAAJABzAHQAYQByAHQASQBuAGYAbwAuAEEAcgBnAHUAbQBlAG4AdABzACAAPQAgACcAIgAnACAAKwAgACQAaABvAG8AawBQAGEAdABoACAAKwAgACcAIgAnADsAIAAkAHMAdABhAHIAdABJAG4AZgBvAC4AVQBzAGUAUwBoAGUAbABsAEUAeABlAGMAdQB0AGUAIAA9ACAAJABmAGEAbABzAGUAOwAgACQAcwB0AGEAcgB0AEkAbgBmAG8ALgBSAGUAZABpAHIAZQBjAHQAUwB0AGEAbgBkAGEAcgBkAEkAbgBwAHUAdAAgAD0AIAAkAHQAcgB1AGUAOwAgACQAYwBoAGkAbABkACAAPQAgAFsAUwB5AHMAdABlAG0ALgBEAGkAYQBnAG4AbwBzAHQAaQBjAHMALgBQAHIAbwBjAGUAcwBzAF0AOgA6AFMAdABhAHIAdAAoACQAcwB0AGEAcgB0AEkAbgBmAG8AKQA7ACAAWwBDAG8AbgBzAG8AbABlAF0AOgA6AE8AcABlAG4AUwB0AGEAbgBkAGEAcgBkAEkAbgBwAHUAdAAoACkALgBDAG8AcAB5AFQAbwAoACQAYwBoAGkAbABkAC4AUwB0AGEAbgBkAGEAcgBkAEkAbgBwAHUAdAAuAEIAYQBzAGUAUwB0AHIAZQBhAG0AKQA7ACAAJABjAGgAaQBsAGQALgBTAHQAYQBuAGQAYQByAGQASQBuAHAAdQB0AC4AQwBsAG8AcwBlACgAKQA7ACAAJABjAGgAaQBsAGQALgBXAGEAaQB0AEYAbwByAEUAeABpAHQAKAApADsAIABlAHgAaQB0ACAAJABjAGgAaQBsAGQALgBFAHgAaQB0AEMAbwBkAGUA"
|
|
27
27
|
}
|
|
28
28
|
]
|
|
29
29
|
},
|
|
@@ -32,13 +32,13 @@
|
|
|
32
32
|
"hooks": [
|
|
33
33
|
{
|
|
34
34
|
"type": "command",
|
|
35
|
-
"command": "
|
|
36
|
-
"commandWindows": "powershell.exe -NoProfile -NonInteractive -EncodedCommand
|
|
35
|
+
"command": "repoRoot=\"$(git rev-parse --show-toplevel)\" && CLAUDE_PROJECT_DIR=\"$repoRoot\" node \"$repoRoot/.claude/hooks/block-no-verify.mjs\"",
|
|
36
|
+
"commandWindows": "powershell.exe -NoProfile -NonInteractive -EncodedCommand JABFAHIAcgBvAHIAQQBjAHQAaQBvAG4AUAByAGUAZgBlAHIAZQBuAGMAZQAgAD0AIAAnAFMAdABvAHAAJwA7ACAAJAByAGUAcABvAFIAbwBvAHQAIAA9ACAAZwBpAHQAIAByAGUAdgAtAHAAYQByAHMAZQAgAC0ALQBzAGgAbwB3AC0AdABvAHAAbABlAHYAZQBsADsAIABpAGYAIAAoACQATABBAFMAVABFAFgASQBUAEMATwBEAEUAIAAtAG4AZQAgADAAKQAgAHsAIABlAHgAaQB0ACAAJABMAEEAUwBUAEUAWABJAFQAQwBPAEQARQAgAH0AOwAgACQAZQBuAHYAOgBDAEwAQQBVAEQARQBfAFAAUgBPAEoARQBDAFQAXwBEAEkAUgAgAD0AIAAkAHIAZQBwAG8AUgBvAG8AdAA7ACAAJABoAG8AbwBrAFAAYQB0AGgAIAA9ACAASgBvAGkAbgAtAFAAYQB0AGgAIAAkAHIAZQBwAG8AUgBvAG8AdAAgACcALgBjAGwAYQB1AGQAZQAvAGgAbwBvAGsAcwAvAGIAbABvAGMAawAtAG4AbwAtAHYAZQByAGkAZgB5AC4AbQBqAHMAJwA7ACAAJABzAHQAYQByAHQASQBuAGYAbwAgAD0AIABOAGUAdwAtAE8AYgBqAGUAYwB0ACAAUwB5AHMAdABlAG0ALgBEAGkAYQBnAG4AbwBzAHQAaQBjAHMALgBQAHIAbwBjAGUAcwBzAFMAdABhAHIAdABJAG4AZgBvADsAIAAkAHMAdABhAHIAdABJAG4AZgBvAC4ARgBpAGwAZQBOAGEAbQBlACAAPQAgACcAbgBvAGQAZQAnADsAIAAkAHMAdABhAHIAdABJAG4AZgBvAC4AQQByAGcAdQBtAGUAbgB0AHMAIAA9ACAAJwAiACcAIAArACAAJABoAG8AbwBrAFAAYQB0AGgAIAArACAAJwAiACcAOwAgACQAcwB0AGEAcgB0AEkAbgBmAG8ALgBVAHMAZQBTAGgAZQBsAGwARQB4AGUAYwB1AHQAZQAgAD0AIAAkAGYAYQBsAHMAZQA7ACAAJABzAHQAYQByAHQASQBuAGYAbwAuAFIAZQBkAGkAcgBlAGMAdABTAHQAYQBuAGQAYQByAGQASQBuAHAAdQB0ACAAPQAgACQAdAByAHUAZQA7ACAAJABjAGgAaQBsAGQAIAA9ACAAWwBTAHkAcwB0AGUAbQAuAEQAaQBhAGcAbgBvAHMAdABpAGMAcwAuAFAAcgBvAGMAZQBzAHMAXQA6ADoAUwB0AGEAcgB0ACgAJABzAHQAYQByAHQASQBuAGYAbwApADsAIABbAEMAbwBuAHMAbwBsAGUAXQA6ADoATwBwAGUAbgBTAHQAYQBuAGQAYQByAGQASQBuAHAAdQB0ACgAKQAuAEMAbwBwAHkAVABvACgAJABjAGgAaQBsAGQALgBTAHQAYQBuAGQAYQByAGQASQBuAHAAdQB0AC4AQgBhAHMAZQBTAHQAcgBlAGEAbQApADsAIAAkAGMAaABpAGwAZAAuAFMAdABhAG4AZABhAHIAZABJAG4AcAB1AHQALgBDAGwAbwBzAGUAKAApADsAIAAkAGMAaABpAGwAZAAuAFcAYQBpAHQARgBvAHIARQB4AGkAdAAoACkAOwAgAGUAeABpAHQAIAAkAGMAaABpAGwAZAAuAEUAeABpAHQAQwBvAGQAZQA="
|
|
37
37
|
},
|
|
38
38
|
{
|
|
39
39
|
"type": "command",
|
|
40
|
-
"command": "
|
|
41
|
-
"commandWindows": "powershell.exe -NoProfile -NonInteractive -EncodedCommand
|
|
40
|
+
"command": "repoRoot=\"$(git rev-parse --show-toplevel)\" && CLAUDE_PROJECT_DIR=\"$repoRoot\" node \"$repoRoot/.claude/hooks/guard-bash.mjs\"",
|
|
41
|
+
"commandWindows": "powershell.exe -NoProfile -NonInteractive -EncodedCommand JABFAHIAcgBvAHIAQQBjAHQAaQBvAG4AUAByAGUAZgBlAHIAZQBuAGMAZQAgAD0AIAAnAFMAdABvAHAAJwA7ACAAJAByAGUAcABvAFIAbwBvAHQAIAA9ACAAZwBpAHQAIAByAGUAdgAtAHAAYQByAHMAZQAgAC0ALQBzAGgAbwB3AC0AdABvAHAAbABlAHYAZQBsADsAIABpAGYAIAAoACQATABBAFMAVABFAFgASQBUAEMATwBEAEUAIAAtAG4AZQAgADAAKQAgAHsAIABlAHgAaQB0ACAAJABMAEEAUwBUAEUAWABJAFQAQwBPAEQARQAgAH0AOwAgACQAZQBuAHYAOgBDAEwAQQBVAEQARQBfAFAAUgBPAEoARQBDAFQAXwBEAEkAUgAgAD0AIAAkAHIAZQBwAG8AUgBvAG8AdAA7ACAAJABoAG8AbwBrAFAAYQB0AGgAIAA9ACAASgBvAGkAbgAtAFAAYQB0AGgAIAAkAHIAZQBwAG8AUgBvAG8AdAAgACcALgBjAGwAYQB1AGQAZQAvAGgAbwBvAGsAcwAvAGcAdQBhAHIAZAAtAGIAYQBzAGgALgBtAGoAcwAnADsAIAAkAHMAdABhAHIAdABJAG4AZgBvACAAPQAgAE4AZQB3AC0ATwBiAGoAZQBjAHQAIABTAHkAcwB0AGUAbQAuAEQAaQBhAGcAbgBvAHMAdABpAGMAcwAuAFAAcgBvAGMAZQBzAHMAUwB0AGEAcgB0AEkAbgBmAG8AOwAgACQAcwB0AGEAcgB0AEkAbgBmAG8ALgBGAGkAbABlAE4AYQBtAGUAIAA9ACAAJwBuAG8AZABlACcAOwAgACQAcwB0AGEAcgB0AEkAbgBmAG8ALgBBAHIAZwB1AG0AZQBuAHQAcwAgAD0AIAAnACIAJwAgACsAIAAkAGgAbwBvAGsAUABhAHQAaAAgACsAIAAnACIAJwA7ACAAJABzAHQAYQByAHQASQBuAGYAbwAuAFUAcwBlAFMAaABlAGwAbABFAHgAZQBjAHUAdABlACAAPQAgACQAZgBhAGwAcwBlADsAIAAkAHMAdABhAHIAdABJAG4AZgBvAC4AUgBlAGQAaQByAGUAYwB0AFMAdABhAG4AZABhAHIAZABJAG4AcAB1AHQAIAA9ACAAJAB0AHIAdQBlADsAIAAkAGMAaABpAGwAZAAgAD0AIABbAFMAeQBzAHQAZQBtAC4ARABpAGEAZwBuAG8AcwB0AGkAYwBzAC4AUAByAG8AYwBlAHMAcwBdADoAOgBTAHQAYQByAHQAKAAkAHMAdABhAHIAdABJAG4AZgBvACkAOwAgAFsAQwBvAG4AcwBvAGwAZQBdADoAOgBPAHAAZQBuAFMAdABhAG4AZABhAHIAZABJAG4AcAB1AHQAKAApAC4AQwBvAHAAeQBUAG8AKAAkAGMAaABpAGwAZAAuAFMAdABhAG4AZABhAHIAZABJAG4AcAB1AHQALgBCAGEAcwBlAFMAdAByAGUAYQBtACkAOwAgACQAYwBoAGkAbABkAC4AUwB0AGEAbgBkAGEAcgBkAEkAbgBwAHUAdAAuAEMAbABvAHMAZQAoACkAOwAgACQAYwBoAGkAbABkAC4AVwBhAGkAdABGAG8AcgBFAHgAaQB0ACgAKQA7ACAAZQB4AGkAdAAgACQAYwBoAGkAbABkAC4ARQB4AGkAdABDAG8AZABlAA=="
|
|
42
42
|
}
|
|
43
43
|
]
|
|
44
44
|
}
|
|
@@ -48,9 +48,9 @@
|
|
|
48
48
|
"hooks": [
|
|
49
49
|
{
|
|
50
50
|
"type": "command",
|
|
51
|
-
"command": "
|
|
51
|
+
"command": "repoRoot=\"$(git rev-parse --show-toplevel)\" && CLAUDE_PROJECT_DIR=\"$repoRoot\" node \"$repoRoot/.claude/hooks/gate-stop-dod.mjs\"",
|
|
52
52
|
"timeout": 900,
|
|
53
|
-
"commandWindows": "powershell.exe -NoProfile -NonInteractive -EncodedCommand
|
|
53
|
+
"commandWindows": "powershell.exe -NoProfile -NonInteractive -EncodedCommand JABFAHIAcgBvAHIAQQBjAHQAaQBvAG4AUAByAGUAZgBlAHIAZQBuAGMAZQAgAD0AIAAnAFMAdABvAHAAJwA7ACAAJAByAGUAcABvAFIAbwBvAHQAIAA9ACAAZwBpAHQAIAByAGUAdgAtAHAAYQByAHMAZQAgAC0ALQBzAGgAbwB3AC0AdABvAHAAbABlAHYAZQBsADsAIABpAGYAIAAoACQATABBAFMAVABFAFgASQBUAEMATwBEAEUAIAAtAG4AZQAgADAAKQAgAHsAIABlAHgAaQB0ACAAJABMAEEAUwBUAEUAWABJAFQAQwBPAEQARQAgAH0AOwAgACQAZQBuAHYAOgBDAEwAQQBVAEQARQBfAFAAUgBPAEoARQBDAFQAXwBEAEkAUgAgAD0AIAAkAHIAZQBwAG8AUgBvAG8AdAA7ACAAJABoAG8AbwBrAFAAYQB0AGgAIAA9ACAASgBvAGkAbgAtAFAAYQB0AGgAIAAkAHIAZQBwAG8AUgBvAG8AdAAgACcALgBjAGwAYQB1AGQAZQAvAGgAbwBvAGsAcwAvAGcAYQB0AGUALQBzAHQAbwBwAC0AZABvAGQALgBtAGoAcwAnADsAIAAkAHMAdABhAHIAdABJAG4AZgBvACAAPQAgAE4AZQB3AC0ATwBiAGoAZQBjAHQAIABTAHkAcwB0AGUAbQAuAEQAaQBhAGcAbgBvAHMAdABpAGMAcwAuAFAAcgBvAGMAZQBzAHMAUwB0AGEAcgB0AEkAbgBmAG8AOwAgACQAcwB0AGEAcgB0AEkAbgBmAG8ALgBGAGkAbABlAE4AYQBtAGUAIAA9ACAAJwBuAG8AZABlACcAOwAgACQAcwB0AGEAcgB0AEkAbgBmAG8ALgBBAHIAZwB1AG0AZQBuAHQAcwAgAD0AIAAnACIAJwAgACsAIAAkAGgAbwBvAGsAUABhAHQAaAAgACsAIAAnACIAJwA7ACAAJABzAHQAYQByAHQASQBuAGYAbwAuAFUAcwBlAFMAaABlAGwAbABFAHgAZQBjAHUAdABlACAAPQAgACQAZgBhAGwAcwBlADsAIAAkAHMAdABhAHIAdABJAG4AZgBvAC4AUgBlAGQAaQByAGUAYwB0AFMAdABhAG4AZABhAHIAZABJAG4AcAB1AHQAIAA9ACAAJAB0AHIAdQBlADsAIAAkAGMAaABpAGwAZAAgAD0AIABbAFMAeQBzAHQAZQBtAC4ARABpAGEAZwBuAG8AcwB0AGkAYwBzAC4AUAByAG8AYwBlAHMAcwBdADoAOgBTAHQAYQByAHQAKAAkAHMAdABhAHIAdABJAG4AZgBvACkAOwAgAFsAQwBvAG4AcwBvAGwAZQBdADoAOgBPAHAAZQBuAFMAdABhAG4AZABhAHIAZABJAG4AcAB1AHQAKAApAC4AQwBvAHAAeQBUAG8AKAAkAGMAaABpAGwAZAAuAFMAdABhAG4AZABhAHIAZABJAG4AcAB1AHQALgBCAGEAcwBlAFMAdAByAGUAYQBtACkAOwAgACQAYwBoAGkAbABkAC4AUwB0AGEAbgBkAGEAcgBkAEkAbgBwAHUAdAAuAEMAbABvAHMAZQAoACkAOwAgACQAYwBoAGkAbABkAC4AVwBhAGkAdABGAG8AcgBFAHgAaQB0ACgAKQA7ACAAZQB4AGkAdAAgACQAYwBoAGkAbABkAC4ARQB4AGkAdABDAG8AZABlAA=="
|
|
54
54
|
}
|
|
55
55
|
]
|
|
56
56
|
}
|
|
@@ -60,8 +60,8 @@
|
|
|
60
60
|
"hooks": [
|
|
61
61
|
{
|
|
62
62
|
"type": "command",
|
|
63
|
-
"command": "
|
|
64
|
-
"commandWindows": "powershell.exe -NoProfile -NonInteractive -EncodedCommand
|
|
63
|
+
"command": "repoRoot=\"$(git rev-parse --show-toplevel)\" && CLAUDE_PROJECT_DIR=\"$repoRoot\" node \"$repoRoot/.claude/hooks/inject-rules.mjs\"",
|
|
64
|
+
"commandWindows": "powershell.exe -NoProfile -NonInteractive -EncodedCommand JABFAHIAcgBvAHIAQQBjAHQAaQBvAG4AUAByAGUAZgBlAHIAZQBuAGMAZQAgAD0AIAAnAFMAdABvAHAAJwA7ACAAJAByAGUAcABvAFIAbwBvAHQAIAA9ACAAZwBpAHQAIAByAGUAdgAtAHAAYQByAHMAZQAgAC0ALQBzAGgAbwB3AC0AdABvAHAAbABlAHYAZQBsADsAIABpAGYAIAAoACQATABBAFMAVABFAFgASQBUAEMATwBEAEUAIAAtAG4AZQAgADAAKQAgAHsAIABlAHgAaQB0ACAAJABMAEEAUwBUAEUAWABJAFQAQwBPAEQARQAgAH0AOwAgACQAZQBuAHYAOgBDAEwAQQBVAEQARQBfAFAAUgBPAEoARQBDAFQAXwBEAEkAUgAgAD0AIAAkAHIAZQBwAG8AUgBvAG8AdAA7ACAAJABoAG8AbwBrAFAAYQB0AGgAIAA9ACAASgBvAGkAbgAtAFAAYQB0AGgAIAAkAHIAZQBwAG8AUgBvAG8AdAAgACcALgBjAGwAYQB1AGQAZQAvAGgAbwBvAGsAcwAvAGkAbgBqAGUAYwB0AC0AcgB1AGwAZQBzAC4AbQBqAHMAJwA7ACAAJABzAHQAYQByAHQASQBuAGYAbwAgAD0AIABOAGUAdwAtAE8AYgBqAGUAYwB0ACAAUwB5AHMAdABlAG0ALgBEAGkAYQBnAG4AbwBzAHQAaQBjAHMALgBQAHIAbwBjAGUAcwBzAFMAdABhAHIAdABJAG4AZgBvADsAIAAkAHMAdABhAHIAdABJAG4AZgBvAC4ARgBpAGwAZQBOAGEAbQBlACAAPQAgACcAbgBvAGQAZQAnADsAIAAkAHMAdABhAHIAdABJAG4AZgBvAC4AQQByAGcAdQBtAGUAbgB0AHMAIAA9ACAAJwAiACcAIAArACAAJABoAG8AbwBrAFAAYQB0AGgAIAArACAAJwAiACcAOwAgACQAcwB0AGEAcgB0AEkAbgBmAG8ALgBVAHMAZQBTAGgAZQBsAGwARQB4AGUAYwB1AHQAZQAgAD0AIAAkAGYAYQBsAHMAZQA7ACAAJABzAHQAYQByAHQASQBuAGYAbwAuAFIAZQBkAGkAcgBlAGMAdABTAHQAYQBuAGQAYQByAGQASQBuAHAAdQB0ACAAPQAgACQAdAByAHUAZQA7ACAAJABjAGgAaQBsAGQAIAA9ACAAWwBTAHkAcwB0AGUAbQAuAEQAaQBhAGcAbgBvAHMAdABpAGMAcwAuAFAAcgBvAGMAZQBzAHMAXQA6ADoAUwB0AGEAcgB0ACgAJABzAHQAYQByAHQASQBuAGYAbwApADsAIABbAEMAbwBuAHMAbwBsAGUAXQA6ADoATwBwAGUAbgBTAHQAYQBuAGQAYQByAGQASQBuAHAAdQB0ACgAKQAuAEMAbwBwAHkAVABvACgAJABjAGgAaQBsAGQALgBTAHQAYQBuAGQAYQByAGQASQBuAHAAdQB0AC4AQgBhAHMAZQBTAHQAcgBlAGEAbQApADsAIAAkAGMAaABpAGwAZAAuAFMAdABhAG4AZABhAHIAZABJAG4AcAB1AHQALgBDAGwAbwBzAGUAKAApADsAIAAkAGMAaABpAGwAZAAuAFcAYQBpAHQARgBvAHIARQB4AGkAdAAoACkAOwAgAGUAeABpAHQAIAAkAGMAaABpAGwAZAAuAEUAeABpAHQAQwBvAGQAZQA="
|
|
65
65
|
}
|
|
66
66
|
]
|
|
67
67
|
}
|
|
@@ -116,6 +116,21 @@ with no refusal, and then **holds** the elevated item with `causes: ["spacing"]`
|
|
|
116
116
|
It holds because `core.mjs` is the layer that reads every unrecognised value
|
|
117
117
|
restrictively — the same layer that holds when `selectNext` is called directly.
|
|
118
118
|
|
|
119
|
+
## A board switch does not reset repository risk
|
|
120
|
+
|
|
121
|
+
`lastCompletedTier` is intentionally checkout-global, not board-scoped. Boards
|
|
122
|
+
partition ownership and selection; they do not partition the repository a merge
|
|
123
|
+
changes. If switching the selector chose a fresh tier slot, a run could land an
|
|
124
|
+
unreviewed mechanism from one board, switch to an independent queue, and land a
|
|
125
|
+
second mechanism in the same checkout — exactly the back-to-back chain the
|
|
126
|
+
ration exists to stop.
|
|
127
|
+
|
|
128
|
+
The selector therefore changes only the adapter options. The tier stays in the
|
|
129
|
+
one `.claude/queue.state.json` paired with the checkout's config, and the next
|
|
130
|
+
selection reads it whichever board is active. This is pinned in the generator's
|
|
131
|
+
`test/template/queue-board.test.ts` (absent in a generated rig) › "keeps
|
|
132
|
+
completed-tier spacing repository-global when the active board switches".
|
|
133
|
+
|
|
119
134
|
## What this does not fix
|
|
120
135
|
|
|
121
136
|
The livelock where *every* remaining item is elevated-by-mechanism still exists —
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
".claude/hooks/guard-secret-file.mjs",
|
|
18
18
|
".claude/hooks/guard-rulebook.mjs",
|
|
19
19
|
".claude/hooks/lib/edit-input.mjs",
|
|
20
|
+
".claude/hooks/lib/hook-input.mjs",
|
|
20
21
|
".claude/skills/pr-ship/SKILL.md",
|
|
21
22
|
".claude/skills/loop/SKILL.md",
|
|
22
23
|
".claude/skills/check-premises/SKILL.md",
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
"0.3.2",
|
|
7
7
|
"0.4.0",
|
|
8
8
|
"0.5.0",
|
|
9
|
-
"0.6.0"
|
|
9
|
+
"0.6.0",
|
|
10
|
+
"0.6.1"
|
|
10
11
|
],
|
|
11
12
|
"files": {
|
|
12
13
|
".agents/skills/check-premises/SKILL.md": {
|
|
@@ -20,7 +21,8 @@
|
|
|
20
21
|
"since": "0.5.0",
|
|
21
22
|
"hashes": [
|
|
22
23
|
"eb52ef41269018107534d8dc2f552c57698e1677a1e76b5e666b5d09c007cac0",
|
|
23
|
-
"47922b182915cb92696f851174f2e88c4ae8c67dc119ef39a3a597ef1300fab5"
|
|
24
|
+
"47922b182915cb92696f851174f2e88c4ae8c67dc119ef39a3a597ef1300fab5",
|
|
25
|
+
"57f6add0504db45e7577474f97705fe474310e4cd3310cdb348a2c64eabe88cd"
|
|
24
26
|
]
|
|
25
27
|
},
|
|
26
28
|
".agents/skills/new-invariant/SKILL.md": {
|
|
@@ -90,7 +92,8 @@
|
|
|
90
92
|
"hashes": [
|
|
91
93
|
"8d5762849afd328fdc1aabf053ad207f2e306e013665e3ff934b06cbb1bbda38",
|
|
92
94
|
"0e4307cd9cf8ef723d79e98b1af46a34c6912a6aa925e2179d939cb83c8f2a44",
|
|
93
|
-
"7e8eaa2b4af9d65e1d10bf50c32c1d138fb2f8b9ba6e6d0880cade0524324d9d"
|
|
95
|
+
"7e8eaa2b4af9d65e1d10bf50c32c1d138fb2f8b9ba6e6d0880cade0524324d9d",
|
|
96
|
+
"ed1c05d3938c0b4f277f1bf4af8641460def830569049ff0697c9bda14518b78"
|
|
94
97
|
]
|
|
95
98
|
},
|
|
96
99
|
".claude/agents/security-scanner.md": {
|
|
@@ -126,14 +129,16 @@
|
|
|
126
129
|
"de1f331eaf93bad6ef0e1734789fd3c4ab07550a85ed2d8ff8888bc6f0bcfb0f",
|
|
127
130
|
"9fb8fad4d95756fb2424a97550aa85dfab0ca8dbcf91e6d0385263ba3b929135",
|
|
128
131
|
"7852205961cb1647b49de5f7e79820c1735b4be897adcf8f5bc3eeb49318ff78",
|
|
129
|
-
"33edb11904e5c4f88626b41c7e649b85d20957a11ff7e1062538917647c3e7f0"
|
|
132
|
+
"33edb11904e5c4f88626b41c7e649b85d20957a11ff7e1062538917647c3e7f0",
|
|
133
|
+
"19202a28355dac1d7a95d29411668cf970006b3044d89a772768ce21793931aa"
|
|
130
134
|
]
|
|
131
135
|
},
|
|
132
136
|
".claude/hooks/guard-bash.mjs": {
|
|
133
137
|
"since": "0.3.0",
|
|
134
138
|
"hashes": [
|
|
135
139
|
"4d396f412fc53094188b14348c5af4e79c7542a182630c20c4687d79899c5b0e",
|
|
136
|
-
"e7a513216ebbceaa7f11ec8fb611de8bb5d5925dfdfd75b779dcb7c0ee6e83cc"
|
|
140
|
+
"e7a513216ebbceaa7f11ec8fb611de8bb5d5925dfdfd75b779dcb7c0ee6e83cc",
|
|
141
|
+
"78047f42dda65076dd2cdb73ad530553db3d4a6f3ea7f0729590ac7286323cb1"
|
|
137
142
|
]
|
|
138
143
|
},
|
|
139
144
|
".claude/hooks/guard-core-purity.mjs": {
|
|
@@ -146,13 +151,15 @@
|
|
|
146
151
|
".claude/hooks/guard-rulebook.mjs": {
|
|
147
152
|
"since": "0.6.0",
|
|
148
153
|
"hashes": [
|
|
149
|
-
"4524d715f2e41f4cfdd3d0f9c858817f913aaca5192bda97dff876b4d8c51f19"
|
|
154
|
+
"4524d715f2e41f4cfdd3d0f9c858817f913aaca5192bda97dff876b4d8c51f19",
|
|
155
|
+
"f9e3e4b7c22c4a64f521402419707ff382b05c8deb6310f62e5545db87f7cb4f"
|
|
150
156
|
]
|
|
151
157
|
},
|
|
152
158
|
".claude/hooks/guard-secret-file.mjs": {
|
|
153
159
|
"since": "0.5.0",
|
|
154
160
|
"hashes": [
|
|
155
|
-
"f6b81cb7640924d2e56aced58e6b48f65df6638fb5a9099985fe5d673245c3ad"
|
|
161
|
+
"f6b81cb7640924d2e56aced58e6b48f65df6638fb5a9099985fe5d673245c3ad",
|
|
162
|
+
"8c333879dba1f2bf87bbb8e7b257ac51315a74a4cb45252408ca615e2a42d7b5"
|
|
156
163
|
]
|
|
157
164
|
},
|
|
158
165
|
".claude/hooks/guard-web-boundary.mjs": {
|
|
@@ -173,7 +180,8 @@
|
|
|
173
180
|
"since": "0.5.0",
|
|
174
181
|
"hashes": [
|
|
175
182
|
"4f836bc3ff75a868ffe035b179ac9e7dc1ad7715d520677ff9f6aa5bfdb200eb",
|
|
176
|
-
"0c191d326a7bb0bbfc110e2210f44b4aa39276e8db3779aaa4545cffd4e11863"
|
|
183
|
+
"0c191d326a7bb0bbfc110e2210f44b4aa39276e8db3779aaa4545cffd4e11863",
|
|
184
|
+
"572c671f8f2cad190fe2f144071954ac4d3b1357cab80a19bad584dd3b3768c8"
|
|
177
185
|
]
|
|
178
186
|
},
|
|
179
187
|
".claude/queue.json": {
|
|
@@ -194,7 +202,8 @@
|
|
|
194
202
|
"c3aa6ea4487adf6d542174f491bf469d8f8563ec7c8c4de63fcd82d6a5ca4fba",
|
|
195
203
|
"ce2104ce697d3e5ed9210e85fe0daeb66b432a36868493f06fec54073a5fff31",
|
|
196
204
|
"530f63a4b29a5cc67867fa7b23575db81d42609ae401bfc4089b05194c586c21",
|
|
197
|
-
"a7359408d56e6bdbf268019ad9193d5fbf4a872385a65ba08564204e6a8f5b5f"
|
|
205
|
+
"a7359408d56e6bdbf268019ad9193d5fbf4a872385a65ba08564204e6a8f5b5f",
|
|
206
|
+
"619999500386b3d2867bbd4799f86a800fe7e168083c6e35fb2e629a2c926c5f"
|
|
198
207
|
]
|
|
199
208
|
},
|
|
200
209
|
".claude/rules/aws-cdk.md": {
|
|
@@ -211,7 +220,8 @@
|
|
|
211
220
|
"b24bbf3e596033719eff463d317ba1e933d0661e48bfa8eafba8d6dca1453a59",
|
|
212
221
|
"518d9b3d7cbae50f30c86d33b2b4bbea11ab3ba6cfef270876e92604b2af5f50",
|
|
213
222
|
"389484f0846c3dd8207c0f41bd56409103fbcc7f58b94b1bf4bd1d81b5af0e87",
|
|
214
|
-
"a2e10499a0bcd2cdc32f3986d27b59077c86e524d40323faabb2e5d78afc002c"
|
|
223
|
+
"a2e10499a0bcd2cdc32f3986d27b59077c86e524d40323faabb2e5d78afc002c",
|
|
224
|
+
"c94c33d7ccbd46f191948325dfc7f68ce13b0d3a9470215de1de3f06b1499111"
|
|
215
225
|
]
|
|
216
226
|
},
|
|
217
227
|
".claude/rules/node-ts.md": {
|
|
@@ -233,7 +243,8 @@
|
|
|
233
243
|
"since": "0.5.0",
|
|
234
244
|
"hashes": [
|
|
235
245
|
"783b5b455a56c6a3e3723f3c6da861e0a945562acb686b86ab9d0e16dbd552a6",
|
|
236
|
-
"c7a045d3687429c5047f09b4033ffffcecfede2edba736106f5e1048eb3cf27f"
|
|
246
|
+
"c7a045d3687429c5047f09b4033ffffcecfede2edba736106f5e1048eb3cf27f",
|
|
247
|
+
"8e9fe877949955e8f698701f969784b288bc95943c3bcea3bd4368db82e8e43e"
|
|
237
248
|
]
|
|
238
249
|
},
|
|
239
250
|
".claude/scripts/detect-missed-gate.mjs": {
|
|
@@ -247,13 +258,15 @@
|
|
|
247
258
|
".claude/scripts/doctor.mjs": {
|
|
248
259
|
"since": "0.6.0",
|
|
249
260
|
"hashes": [
|
|
250
|
-
"eb31d2726ab95d6b8fd091b1daf8a21f403a46c801590baf4fecee5c1bec64b9"
|
|
261
|
+
"eb31d2726ab95d6b8fd091b1daf8a21f403a46c801590baf4fecee5c1bec64b9",
|
|
262
|
+
"5154ad9abdcb37fea58b0da78fd71842c705774ffe48fd8050c302da17e73913"
|
|
251
263
|
]
|
|
252
264
|
},
|
|
253
265
|
".claude/scripts/git-env.mjs": {
|
|
254
266
|
"since": "0.5.0",
|
|
255
267
|
"hashes": [
|
|
256
|
-
"ff9f3c848a3881b67a8f795a4d172f407d896447e21dd3a9c4903365977d4519"
|
|
268
|
+
"ff9f3c848a3881b67a8f795a4d172f407d896447e21dd3a9c4903365977d4519",
|
|
269
|
+
"b022399f287cdce1bcb07329c0aab44bf99714b5f0515f274c962f41ea072b86"
|
|
257
270
|
]
|
|
258
271
|
},
|
|
259
272
|
".claude/scripts/lib/gate-coverage.mjs": {
|
|
@@ -265,13 +278,15 @@
|
|
|
265
278
|
".claude/scripts/lib/revalidation-points.mjs": {
|
|
266
279
|
"since": "0.6.0",
|
|
267
280
|
"hashes": [
|
|
268
|
-
"41c4f70e5914636bccec7995172c322c875774027e0c626abb7c593c43fddd65"
|
|
281
|
+
"41c4f70e5914636bccec7995172c322c875774027e0c626abb7c593c43fddd65",
|
|
282
|
+
"b48e4959862906e279eca481f484f07a7fd4aa082f2778224a6574a7871bc9d8"
|
|
269
283
|
]
|
|
270
284
|
},
|
|
271
285
|
".claude/scripts/lib/secrets.mjs": {
|
|
272
286
|
"since": "0.5.0",
|
|
273
287
|
"hashes": [
|
|
274
|
-
"dc4a300646f12459c0c37e380b325451474e1ec113482381e0fad80aceb88049"
|
|
288
|
+
"dc4a300646f12459c0c37e380b325451474e1ec113482381e0fad80aceb88049",
|
|
289
|
+
"60fab2cc9f2028bccf6139e6a744406b4a86a9039d1367f3cef409902e9b327d"
|
|
275
290
|
]
|
|
276
291
|
},
|
|
277
292
|
".claude/scripts/lib/verdict.mjs": {
|
|
@@ -332,7 +347,8 @@
|
|
|
332
347
|
"hashes": [
|
|
333
348
|
"cd149245dc29ef7489ae0e5394fa5a063dea2cc017bd3cbbface1ace6f8d7b46",
|
|
334
349
|
"7db3c17b781e6876b4822afd09e6affa97e46607b63d443975d987daf1f14464",
|
|
335
|
-
"9edd70b5960f367958e8e7500150b4edd285c99869cca803e40e0537d2d347b0"
|
|
350
|
+
"9edd70b5960f367958e8e7500150b4edd285c99869cca803e40e0537d2d347b0",
|
|
351
|
+
"4cf917ac06708e1068dfe9f8bbebc9376c4748526bd669b9bea32929ba996d0a"
|
|
336
352
|
]
|
|
337
353
|
},
|
|
338
354
|
".claude/scripts/queue/jira.mjs": {
|
|
@@ -341,7 +357,8 @@
|
|
|
341
357
|
"13e187a4005136a9a1fae5fb5a3d2a61c3cf5dc097b467c12622ad82a0fd5d52",
|
|
342
358
|
"86591a48b9264d641c2530238ea3720705f12e17af2ec8dccecabfa3a742a2e7",
|
|
343
359
|
"13c301f828d4402fcf81e7e0b2b1d22472cbdb50f6fd606770056af4ec215e51",
|
|
344
|
-
"b8be5855809c0d44ece94858456098663296c17c5ad9abb1ea2cf1a8f48ca491"
|
|
360
|
+
"b8be5855809c0d44ece94858456098663296c17c5ad9abb1ea2cf1a8f48ca491",
|
|
361
|
+
"3fc8ff9a06a2e22355a660a31e2943a97f50869f7e5726f768a7931d17b64b5b"
|
|
345
362
|
]
|
|
346
363
|
},
|
|
347
364
|
".claude/scripts/queue/plan-md.mjs": {
|
|
@@ -368,13 +385,15 @@
|
|
|
368
385
|
".claude/scripts/revalidate.mjs": {
|
|
369
386
|
"since": "0.6.0",
|
|
370
387
|
"hashes": [
|
|
371
|
-
"32f847a2a3a5136cabcb42f6447da9f8cf98e2d20512212ee5201461128d7798"
|
|
388
|
+
"32f847a2a3a5136cabcb42f6447da9f8cf98e2d20512212ee5201461128d7798",
|
|
389
|
+
"b62348a27bcaa1d62a0e4ddd53873fab6cb75f9af8e8c69d24f6ce81c592c4e3"
|
|
372
390
|
]
|
|
373
391
|
},
|
|
374
392
|
".claude/scripts/revalidation-report.mjs": {
|
|
375
393
|
"since": "0.6.0",
|
|
376
394
|
"hashes": [
|
|
377
|
-
"b99a3537158c2851698bfac027f7061811d7d428c3eea57f3ce0f58ea75dc249"
|
|
395
|
+
"b99a3537158c2851698bfac027f7061811d7d428c3eea57f3ce0f58ea75dc249",
|
|
396
|
+
"78adac5c8f696f2c9cc86f404abf1d55604ccb10f11dd612501c14d4cf5935b1"
|
|
378
397
|
]
|
|
379
398
|
},
|
|
380
399
|
".claude/scripts/run-journal.mjs": {
|
|
@@ -400,7 +419,8 @@
|
|
|
400
419
|
".claude/scripts/unattended-flag.mjs": {
|
|
401
420
|
"since": "0.6.0",
|
|
402
421
|
"hashes": [
|
|
403
|
-
"060b001927d8b471f1c1a8c8851fbb4266d3b2edd674cccbd6c16c44b57b91dd"
|
|
422
|
+
"060b001927d8b471f1c1a8c8851fbb4266d3b2edd674cccbd6c16c44b57b91dd",
|
|
423
|
+
"7721ee19fd1bcf6902be3d359def40742d79b88f5d83e7e3ca22ba45ca3211aa"
|
|
404
424
|
]
|
|
405
425
|
},
|
|
406
426
|
".claude/scripts/verdict.mjs": {
|
|
@@ -435,7 +455,8 @@
|
|
|
435
455
|
"8f687f92fa60d4cc679fa13bff997cec92227f6bc0b90c1dc5b8a5400f2abef0",
|
|
436
456
|
"e442ca58c4249ac10d5759bba2d8360544642e03b2b158e6253e71f5be3767fb",
|
|
437
457
|
"eb52ef41269018107534d8dc2f552c57698e1677a1e76b5e666b5d09c007cac0",
|
|
438
|
-
"47922b182915cb92696f851174f2e88c4ae8c67dc119ef39a3a597ef1300fab5"
|
|
458
|
+
"47922b182915cb92696f851174f2e88c4ae8c67dc119ef39a3a597ef1300fab5",
|
|
459
|
+
"57f6add0504db45e7577474f97705fe474310e4cd3310cdb348a2c64eabe88cd"
|
|
439
460
|
]
|
|
440
461
|
},
|
|
441
462
|
".claude/skills/new-invariant/SKILL.md": {
|
|
@@ -503,7 +524,8 @@
|
|
|
503
524
|
"since": "0.5.0",
|
|
504
525
|
"hashes": [
|
|
505
526
|
"80b4631502d95c619ad060ce02cc8702b12574758ba7df8337ed63efa15d764d",
|
|
506
|
-
"cfd516f72acdfd197c59ac3cbdd39d7b175976dbd3d9eef52825dfdc4f1e5b60"
|
|
527
|
+
"cfd516f72acdfd197c59ac3cbdd39d7b175976dbd3d9eef52825dfdc4f1e5b60",
|
|
528
|
+
"8687eacf0ad707a4f887e0714e82fca23fd005fa9ec8824faedfe99ca46333e1"
|
|
507
529
|
]
|
|
508
530
|
},
|
|
509
531
|
".codex/agents/security-scanner.toml": {
|
|
@@ -6,5 +6,6 @@
|
|
|
6
6
|
"0.3.2": "bbcb24fd8ff5290826ee3b152b5130f6bd4697da",
|
|
7
7
|
"0.4.0": "c77c6c3e5d991a55b733a05a3b5ae01e2a55e578",
|
|
8
8
|
"0.5.0": "d78d0e77307a2be128de46ff4f64e3c146b5df2b",
|
|
9
|
-
"0.6.0": "29ca0543368e922349945e52541a92b46ddcc5bd"
|
|
9
|
+
"0.6.0": "29ca0543368e922349945e52541a92b46ddcc5bd",
|
|
10
|
+
"0.6.1": "f1d1e3dbd2161545d77ee5e7f90fa8e74b8a9f3e"
|
|
10
11
|
}
|