create-agent-rig 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +316 -11
- package/README.md +58 -19
- package/package.json +2 -1
- package/packages/cli/dist/commands/create.js +8 -3
- package/packages/cli/dist/commands/init.js +85 -34
- package/packages/cli/dist/commands/upgrade.js +112 -30
- package/packages/cli/dist/index.js +81 -19
- package/packages/cli/dist/lib/copy-tree.js +35 -6
- package/packages/cli/dist/lib/init-settings.js +12 -0
- package/packages/cli/dist/lib/install-set.js +6 -8
- package/packages/cli/dist/lib/manifest.js +31 -9
- package/packages/cli/dist/lib/safe-path.js +30 -0
- package/scripts/prepare.mjs +1 -1
- package/templates/agent-os/init/AGENTS.md +199 -0
- package/templates/agent-os/init/CLAUDE.md +69 -9
- package/templates/agent-os/stack/aws-cdk/.agents/skills/post-deploy-verify/SKILL.md +105 -0
- package/templates/agent-os/stack/aws-cdk/.agents/skills/ro-debug/SKILL.md +117 -0
- package/templates/agent-os/stack/aws-cdk/.claude/agents/cdk-diff-reviewer.md +41 -3
- package/templates/agent-os/stack/aws-cdk/.claude/rules/aws-cdk.md +1 -1
- package/templates/agent-os/stack/aws-cdk/.claude/skills/post-deploy-verify/SKILL.md +41 -0
- package/templates/agent-os/stack/aws-cdk/.codex/agents/cdk-diff-reviewer.toml +4 -0
- package/templates/agent-os/stack/node-ts/.claude/rules/node-ts.md +29 -0
- package/templates/agent-os/universal/.agents/skills/check-premises/SKILL.md +236 -0
- package/templates/agent-os/universal/.agents/skills/loop/SKILL.md +958 -0
- package/templates/agent-os/universal/.agents/skills/new-invariant/SKILL.md +102 -0
- package/templates/agent-os/universal/.agents/skills/new-invariant/guard-invariant.example.mjs +78 -0
- package/templates/agent-os/universal/.agents/skills/new-invariant/guard-invariant.example.test.mjs +89 -0
- package/templates/agent-os/universal/.agents/skills/pr-ship/SKILL.md +396 -0
- package/templates/agent-os/universal/.agents/skills/worktree-task/SKILL.md +73 -0
- package/templates/agent-os/universal/.claude/agents/code-reviewer.md +40 -1
- package/templates/agent-os/universal/.claude/agents/prose-reviewer.md +71 -1
- package/templates/agent-os/universal/.claude/agents/security-scanner.md +40 -0
- package/templates/agent-os/universal/.claude/hooks/gate-stop-dod.mjs +301 -26
- package/templates/agent-os/universal/.claude/hooks/guard-bash.mjs +43 -5
- package/templates/agent-os/universal/.claude/hooks/guard-core-purity.mjs +25 -11
- package/templates/agent-os/universal/.claude/hooks/guard-rulebook.mjs +127 -0
- package/templates/agent-os/universal/.claude/hooks/guard-secret-file.mjs +180 -0
- package/templates/agent-os/universal/.claude/hooks/guard-web-boundary.mjs +32 -13
- package/templates/agent-os/universal/.claude/hooks/inject-rules.mjs +175 -7
- package/templates/agent-os/universal/.claude/hooks/lib/edit-input.mjs +503 -0
- package/templates/agent-os/universal/.claude/rules/autonomy.md +69 -1
- package/templates/agent-os/universal/.claude/rules/invariants.md +104 -19
- package/templates/agent-os/universal/.claude/rules/workflow.md +55 -5
- package/templates/agent-os/universal/.claude/scripts/decision-router.mjs +1260 -0
- package/templates/agent-os/universal/.claude/scripts/detect-missed-gate.mjs +65 -2
- package/templates/agent-os/universal/.claude/scripts/doctor.mjs +351 -0
- package/templates/agent-os/universal/.claude/scripts/git-env.mjs +49 -0
- package/templates/agent-os/universal/.claude/scripts/lib/gate-coverage.mjs +306 -0
- package/templates/agent-os/universal/.claude/scripts/lib/revalidation-points.mjs +28 -0
- package/templates/agent-os/universal/.claude/scripts/lib/secrets.mjs +490 -0
- package/templates/agent-os/universal/.claude/scripts/lib/verdict.mjs +462 -0
- package/templates/agent-os/universal/.claude/scripts/preflight.mjs +33 -17
- package/templates/agent-os/universal/.claude/scripts/queue/as-of.mjs +51 -0
- package/templates/agent-os/universal/.claude/scripts/queue/checkout.mjs +149 -0
- package/templates/agent-os/universal/.claude/scripts/queue/core.mjs +809 -28
- package/templates/agent-os/universal/.claude/scripts/queue/gate-rounds.mjs +160 -0
- package/templates/agent-os/universal/.claude/scripts/queue/github-issues.mjs +95 -16
- package/templates/agent-os/universal/.claude/scripts/queue/index.mjs +543 -15
- package/templates/agent-os/universal/.claude/scripts/queue/jira.mjs +457 -47
- package/templates/agent-os/universal/.claude/scripts/queue/plan-md.mjs +289 -25
- package/templates/agent-os/universal/.claude/scripts/queue/state.mjs +197 -0
- package/templates/agent-os/universal/.claude/scripts/revalidate.mjs +316 -0
- package/templates/agent-os/universal/.claude/scripts/revalidation-report.mjs +180 -0
- package/templates/agent-os/universal/.claude/scripts/run-journal.mjs +435 -0
- package/templates/agent-os/universal/.claude/scripts/run-state.mjs +539 -0
- package/templates/agent-os/universal/.claude/scripts/stop-flag.mjs +15 -8
- package/templates/agent-os/universal/.claude/scripts/unattended-flag.mjs +239 -0
- package/templates/agent-os/universal/.claude/scripts/verdict.mjs +198 -0
- package/templates/agent-os/universal/.claude/settings.json +11 -2
- package/templates/agent-os/universal/.claude/skills/check-premises/SKILL.md +118 -7
- package/templates/agent-os/universal/.claude/skills/loop/SKILL.md +667 -38
- package/templates/agent-os/universal/.claude/skills/pr-ship/SKILL.md +359 -26
- package/templates/agent-os/universal/.codex/agents/code-reviewer.toml +4 -0
- package/templates/agent-os/universal/.codex/agents/prose-reviewer.toml +4 -0
- package/templates/agent-os/universal/.codex/agents/security-scanner.toml +4 -0
- package/templates/agent-os/universal/.codex/agents/test-writer.toml +4 -0
- package/templates/agent-os/universal/.codex/hooks.json +70 -0
- package/templates/agent-os/universal/AGENTS.md +166 -0
- package/templates/agent-os/universal/CLAUDE.md +49 -14
- package/templates/agent-os/universal/PLAN.md +7 -40
- package/templates/agent-os/universal/docs/decisions/closing-a-task.md +98 -0
- package/templates/agent-os/universal/docs/decisions/codex-adapter.md +108 -0
- package/templates/agent-os/universal/docs/decisions/fail-open-guards.md +43 -0
- package/templates/agent-os/universal/docs/decisions/gate-coverage.md +83 -0
- package/templates/agent-os/universal/docs/decisions/review-lanes.md +72 -0
- package/templates/agent-os/universal/docs/decisions/run-directory.md +44 -0
- package/templates/agent-os/universal/docs/decisions/spacing-rations-mechanisms.md +125 -0
- package/templates/agent-os/universal/docs/decisions/stop-conditions-in-a-file.md +46 -0
- package/templates/agent-os/universal/docs/decisions/two-empty-endings.md +74 -0
- package/templates/agent-os/universal/journal/README.md +101 -0
- package/templates/agent-os/universal/layers.json +45 -2
- package/templates/hash-history.json +310 -49
- package/templates/release-ledger.json +9 -0
- package/templates/skeleton/aws-serverless/.github/workflows/deploy.yml +34 -1
- package/templates/skeleton/aws-serverless/README.md +91 -9
- package/templates/skeleton/aws-serverless/apps/web/src/lib/api.ts +9 -2
- package/templates/skeleton/aws-serverless/gitignore +37 -0
- package/templates/skeleton/aws-serverless/infra/bin/app.ts +73 -13
- package/templates/skeleton/aws-serverless/infra/lib/app-stack.ts +136 -4
- package/templates/skeleton/aws-serverless/infra/lib/web-stack.ts +14 -1
- package/templates/skeleton/aws-serverless/infra/test/allowed-origins.test.ts +301 -0
- package/templates/skeleton/aws-serverless/infra/test/app-composition.test.ts +137 -0
- package/templates/skeleton/aws-serverless/infra/test/app-stack.test.ts +36 -20
- package/templates/skeleton/aws-serverless/infra/test/web-stack.test.ts +15 -12
- package/templates/skeleton/aws-serverless/services/api/src/handlers/create-note.ts +33 -6
- package/templates/skeleton/aws-serverless/services/api/src/handlers/list-notes.ts +18 -5
- package/templates/skeleton/aws-serverless/services/api/src/usecases/create-note.ts +11 -0
- package/templates/skeleton/aws-serverless/services/api/test/create-note.handler.test.ts +120 -1
- package/templates/skeleton/aws-serverless/services/api/test/list-notes.test.ts +72 -1
- package/templates/skeleton/node-service/README.md +11 -1
- package/templates/skeleton/node-service/gitignore +34 -0
- package/templates/skeleton/node-service/packages/db/src/note-store.ts +47 -10
- package/templates/skeleton/node-service/packages/db/test/note-store.test.ts +20 -0
- package/templates/skeleton/node-service/services/api/src/main.ts +2 -9
- package/templates/skeleton/node-service/services/api/src/server.ts +93 -10
- package/templates/skeleton/node-service/services/api/src/static-dir.ts +20 -0
- package/templates/skeleton/node-service/services/api/test/server.test.ts +98 -13
- package/templates/skeleton/node-service/services/api/test/static-dir.test.ts +28 -0
|
@@ -13,6 +13,18 @@
|
|
|
13
13
|
*/
|
|
14
14
|
/** Matches the hook file a wired command runs, e.g. `.claude/hooks/guard-bash.mjs`. */
|
|
15
15
|
const HOOK_REFERENCE = /\.claude\/hooks\/[A-Za-z0-9._-]+\.mjs/;
|
|
16
|
+
/**
|
|
17
|
+
* Every hook file some command in this settings text would run.
|
|
18
|
+
*
|
|
19
|
+
* Deliberately a scan of the raw text rather than a walk of the parsed shape:
|
|
20
|
+
* the caller compares two settings files to ask whether one stops calling a
|
|
21
|
+
* hook the other called, and a shape this module does not understand must not
|
|
22
|
+
* make that question answer "nothing was wired". The same expression decides
|
|
23
|
+
* both, so the filter and the comparison cannot drift apart.
|
|
24
|
+
*/
|
|
25
|
+
export function hookFilesReferencedIn(settingsText) {
|
|
26
|
+
return new Set(settingsText.match(new RegExp(HOOK_REFERENCE, 'g')) ?? []);
|
|
27
|
+
}
|
|
16
28
|
function isRecord(value) {
|
|
17
29
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
18
30
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
|
-
import { listTreeEntries } from './copy-tree.js';
|
|
2
|
+
import { listTreeEntries, mapConcurrent } from './copy-tree.js';
|
|
3
3
|
import { substituteContent, substituteFileName } from './substitute.js';
|
|
4
4
|
import { agentOsStackDir, agentOsUniversalDir } from '../templates.js';
|
|
5
5
|
/**
|
|
@@ -34,13 +34,11 @@ export async function agentOsInstallSet(stacks, ctx) {
|
|
|
34
34
|
const entries = await listTreeEntries(layer.dir, {
|
|
35
35
|
transformName: (name) => substituteFileName(name, ctx),
|
|
36
36
|
});
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
});
|
|
43
|
-
}
|
|
37
|
+
files.push(...(await mapConcurrent(entries, 16, async (entry) => ({
|
|
38
|
+
rel: entry.rel,
|
|
39
|
+
source: entry.source,
|
|
40
|
+
content: substituteContent(await readFile(entry.source, 'utf8'), ctx),
|
|
41
|
+
}))));
|
|
44
42
|
}
|
|
45
43
|
return files;
|
|
46
44
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
-
import {
|
|
4
|
+
import { isSafeSubstitutionValue } from './safe-path.js';
|
|
5
5
|
/**
|
|
6
6
|
* The install manifest: what this rig installed, at which version, and the
|
|
7
7
|
* hash each file had when it was written.
|
|
@@ -44,6 +44,16 @@ export function parseManifest(raw) {
|
|
|
44
44
|
const m = parsed;
|
|
45
45
|
if (typeof m.version !== 'string')
|
|
46
46
|
return null;
|
|
47
|
+
// The same value check its siblings get (AR-128). `version` is printed raw
|
|
48
|
+
// in the upgrade plan header — `installed by ${plan.fromVersion}`, the screen
|
|
49
|
+
// read immediately before `--yes` — so a version carrying a newline or an
|
|
50
|
+
// ANSI escape could forge plan lines the CLI never composed. A version this
|
|
51
|
+
// rig writes is the package's own (`0.5.0` today; a prerelease such as
|
|
52
|
+
// `0.6.0-rc.1` would also pass), which the substitution whitelist admits;
|
|
53
|
+
// semver build metadata (`+`) is not, and a manifest carrying one is voided
|
|
54
|
+
// rather than printed — no released version has carried one.
|
|
55
|
+
if (!isSafeSubstitutionValue(m.version))
|
|
56
|
+
return null;
|
|
47
57
|
if (m.kind !== 'create' && m.kind !== 'init')
|
|
48
58
|
return null;
|
|
49
59
|
const project = m.project;
|
|
@@ -54,18 +64,30 @@ export function parseManifest(raw) {
|
|
|
54
64
|
typeof project.region !== 'string') {
|
|
55
65
|
return null;
|
|
56
66
|
}
|
|
57
|
-
// Values, not just types.
|
|
58
|
-
//
|
|
59
|
-
//
|
|
60
|
-
//
|
|
61
|
-
//
|
|
62
|
-
|
|
67
|
+
// Values, not just types. This file is committed, so it reaches a
|
|
68
|
+
// maintainer's disk through a pull request. A name of `../..` would send a
|
|
69
|
+
// write out of the repository — and `name` and `region` go further than
|
|
70
|
+
// paths: they are substituted into installed **files**, where a quote closes
|
|
71
|
+
// the string literal `stop-flag.mjs` embeds the name in, which `guard-bash`
|
|
72
|
+
// imports on every Bash call. (`scope` reaches no template today and a
|
|
73
|
+
// `stacks` entry names an overlay *directory*.) An unsafe value invalidates
|
|
74
|
+
// the whole manifest rather than being quietly corrected into something
|
|
75
|
+
// plausible.
|
|
76
|
+
//
|
|
77
|
+
// One check, not two: `isSafeSubstitutionValue` is strictly stronger than
|
|
78
|
+
// `isSafeSegment` here — its first character excludes `.`, and its class
|
|
79
|
+
// admits neither `/` nor `\` nor `\0` — so pairing them would leave a second
|
|
80
|
+
// predicate that can never fire, read as cover, and quietly stop being true
|
|
81
|
+
// if either one moves. `isSafeSegment` still guards every path segment at
|
|
82
|
+
// write time, in `resolveInside`.
|
|
83
|
+
if (!isSafeSubstitutionValue(project.name) || !isSafeSubstitutionValue(project.scope)) {
|
|
63
84
|
return null;
|
|
64
|
-
|
|
85
|
+
}
|
|
86
|
+
if (project.region !== '' && !isSafeSubstitutionValue(project.region))
|
|
65
87
|
return null;
|
|
66
88
|
if (!Array.isArray(m.stacks) || m.stacks.some((s) => typeof s !== 'string'))
|
|
67
89
|
return null;
|
|
68
|
-
if (m.stacks.some((s) => !
|
|
90
|
+
if (m.stacks.some((s) => !isSafeSubstitutionValue(s)))
|
|
69
91
|
return null;
|
|
70
92
|
if (!isStringRecord(m.files))
|
|
71
93
|
return null;
|
|
@@ -16,6 +16,36 @@ export function isSafeSegment(value) {
|
|
|
16
16
|
!value.includes('\\') &&
|
|
17
17
|
!value.includes('\0'));
|
|
18
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* A value that can be substituted into an **installed file** without changing
|
|
21
|
+
* what that file means.
|
|
22
|
+
*
|
|
23
|
+
* 🔴 **Why this is not {@link isSafeSegment}, and why widening that one instead
|
|
24
|
+
* would be wrong.** `isSafeSegment` answers "can this steer a path", and it is
|
|
25
|
+
* what {@link resolveInside} holds every path segment to when an upgrade
|
|
26
|
+
* writes — `CLAUDE.md` and `.claude/rules/workflow.md` have to keep passing it,
|
|
27
|
+
* so it cannot become this whitelist. But `project.name` is substituted into
|
|
28
|
+
* `.claude/scripts/stop-flag.mjs` **inside a single-quoted JavaScript string
|
|
29
|
+
* literal**, and `guard-bash` imports that module on every Bash call. A value
|
|
30
|
+
* that steers no path at all still closes that quote: it reaches code
|
|
31
|
+
* execution in the hook process, and it silently disarms the kill switch,
|
|
32
|
+
* because the paths it computes stop pointing at `~/.claude/<name>-loop-STOP`.
|
|
33
|
+
* A brake that looks installed and is not is the worst of the two.
|
|
34
|
+
*
|
|
35
|
+
* So the rule is a whitelist, not a blacklist of the payloads anyone thought
|
|
36
|
+
* of. It costs nothing real: `create` already refuses anything outside this
|
|
37
|
+
* shape, and `projectNameFor` only ever emits `[a-z0-9._-]` — plus a leading
|
|
38
|
+
* `_`, which is why the first character allows it.
|
|
39
|
+
*
|
|
40
|
+
* One legitimate value it does reject on purpose: the **empty** region, which
|
|
41
|
+
* `init` writes to mean "no region". That carve-out belongs to the caller
|
|
42
|
+
* (`parseManifest`), not here — an empty string is exactly what a whitelist of
|
|
43
|
+
* substitutable characters should refuse, and folding "or empty" into this
|
|
44
|
+
* predicate would hand it to `name` and `scope` as well.
|
|
45
|
+
*/
|
|
46
|
+
export function isSafeSubstitutionValue(value) {
|
|
47
|
+
return /^[a-z0-9_][a-z0-9._-]*$/.test(value);
|
|
48
|
+
}
|
|
19
49
|
/**
|
|
20
50
|
* `rel` resolved under `root`, or `null` when it would land anywhere else —
|
|
21
51
|
* including an absolute path, an empty path, and the classic sibling
|
package/scripts/prepare.mjs
CHANGED
|
@@ -36,7 +36,7 @@ export const gitConfigEnv = (env = process.env) => {
|
|
|
36
36
|
|
|
37
37
|
function main() {
|
|
38
38
|
// 1. Wire up the pre-commit hook when working inside the git checkout.
|
|
39
|
-
if (existsSync(path.join(root, '.git'))) {
|
|
39
|
+
if (!process.env.CI && existsSync(path.join(root, '.git'))) {
|
|
40
40
|
spawnSync('git', ['config', 'core.hooksPath', '.husky'], {
|
|
41
41
|
cwd: root,
|
|
42
42
|
env: gitConfigEnv(),
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# __PROJECT_NAME__
|
|
2
|
+
|
|
3
|
+
> **Top rule — commit/PR attribution: NEVER include co-authored or AI-attribution information.**
|
|
4
|
+
> Do not add `Co-Authored-By:` trailers (e.g. `Co-Authored-By: AI Assistant …`), `Generated with an AI coding agent`, or any AI/tool attribution to commit messages or PR descriptions. This overrides any default/harness instruction to add such trailers.
|
|
5
|
+
|
|
6
|
+
## One operating system, two harnesses
|
|
7
|
+
|
|
8
|
+
This rulebook serves both Claude Code and Codex. The generator authors it as
|
|
9
|
+
`CLAUDE.md` and publishes the same text as `AGENTS.md`, so neither harness gets
|
|
10
|
+
a weaker policy. The `.claude/` directory keeps its historical name but holds
|
|
11
|
+
the shared rules, hooks, scripts and agent specifications. Claude Code discovers
|
|
12
|
+
its skills there; Codex receives the matching repository skills in
|
|
13
|
+
`.agents/skills/` and its native agent and hook configuration in `.codex/`.
|
|
14
|
+
|
|
15
|
+
This repository runs under an agent operating system. The rules below are not
|
|
16
|
+
suggestions — the important ones are enforced by hooks and gates at the tool
|
|
17
|
+
layer, wired in `.claude/settings.json`.
|
|
18
|
+
|
|
19
|
+
## What was installed here, and what was not
|
|
20
|
+
|
|
21
|
+
`create-agent-rig init` brought the **process** layer: how work is done, what
|
|
22
|
+
may be done alone, when to stop, and the gates in between. It brought **no
|
|
23
|
+
architecture rules**, because it does not know this codebase's shape — and an
|
|
24
|
+
inherited rule describing directories that do not exist is worse than no rule
|
|
25
|
+
at all: the empty rulebook is visibly incomplete, the borrowed one is invisibly
|
|
26
|
+
wrong.
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
.claude/rules/ how work happens (workflow), what needs a human (autonomy),
|
|
30
|
+
and the pattern for making a rule mechanical (invariants)
|
|
31
|
+
.claude/hooks/ the checks that refuse a violation at the tool layer
|
|
32
|
+
.claude/agents/ the review gates: test-writer, code-reviewer, security-scanner,
|
|
33
|
+
prose-reviewer
|
|
34
|
+
.claude/skills/ the drivers: loop, pr-ship, worktree-task, new-invariant,
|
|
35
|
+
check-premises
|
|
36
|
+
.claude/scripts/ the queue adapter, the preflight, the out-of-band sweeps
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**The architecture rules of this project are yours to write.** When this repo
|
|
40
|
+
has a boundary worth stating — a layer that must not import another, a module
|
|
41
|
+
that owns an SDK, a directory that stays pure — state it in a new file under
|
|
42
|
+
`.claude/rules/`, name it from this section, and if it is worth enforcing, give
|
|
43
|
+
it a hook via the `new-invariant` skill.
|
|
44
|
+
|
|
45
|
+
## If you read only three sections, read these
|
|
46
|
+
|
|
47
|
+
1. **Autonomy tiers** — what you may do alone vs. propose first:
|
|
48
|
+
`.claude/rules/autonomy.md` ("Tiers")
|
|
49
|
+
2. **Stop rules** — when stopping with a diagnosis is the correct move:
|
|
50
|
+
`.claude/rules/autonomy.md` ("Stop rules")
|
|
51
|
+
3. **Definition of Done** — the checklist a change must pass:
|
|
52
|
+
`.claude/rules/workflow.md` ("Definition of Done")
|
|
53
|
+
|
|
54
|
+
## How work happens here
|
|
55
|
+
|
|
56
|
+
- **TDD, without exception.** The failing test comes first — use the
|
|
57
|
+
`test-writer` agent for it. See `.claude/rules/workflow.md`.
|
|
58
|
+
- **One task, one branch — and merge via PR.** Every unit of work gets its own
|
|
59
|
+
short-lived branch; the default branch is never committed to directly. Once
|
|
60
|
+
the project has a remote and CI, changes reach it through the PR flow (local
|
|
61
|
+
checks → reviewer fan-out → merge on an explicit criterion). See
|
|
62
|
+
`.claude/rules/workflow.md` ("Branches and commits", "PR flow"). When another
|
|
63
|
+
session may touch this repo at the same time, the branch lives in its own
|
|
64
|
+
worktree — the `worktree-task` skill has the lifecycle and the cleanup.
|
|
65
|
+
- **Gates.** Every PR is routed before it is reviewed — the
|
|
66
|
+
`decision-router` picks the cheapest lane the change earns
|
|
67
|
+
(`deterministic` → `fast-path` → `model`), and risk flags escalate ahead of
|
|
68
|
+
all three. `code-reviewer` runs on the `model` lane, which is **everything the
|
|
69
|
+
two cheap lanes did not claim** — code, a rulebook document, an unclassifiable
|
|
70
|
+
path, a derived artifact git does not report as drift, or anything a risk flag
|
|
71
|
+
escalated;
|
|
72
|
+
`security-scanner` when a change touches auth, secrets, parsing, or outbound
|
|
73
|
+
calls; `prose-reviewer` when it touches the documents that instruct agents —
|
|
74
|
+
rules, skills, agent specs, this file, the README. Those last two are
|
|
75
|
+
**lane-independent and may only add** — the lane is a floor, never a ceiling.
|
|
76
|
+
`.claude/rules/workflow.md` carries the ladder and what the cheap lanes give
|
|
77
|
+
up. Blocking findings are resolved, not argued with, and the
|
|
78
|
+
`pr-ship` skill drives the fan-out. **No hook launches them** — a gate here is
|
|
79
|
+
a session following a written rule, so "the gate ran" is a claim, not a
|
|
80
|
+
guarantee. That is the honest reading of every gate in this file.
|
|
81
|
+
- **Enforcement is mechanical.** `guard-secret-file` refuses an edit that writes
|
|
82
|
+
a credential — by the file's name or by a value in its text, from the one
|
|
83
|
+
vocabulary in `.claude/scripts/lib/secrets.mjs`; `block-no-verify` refuses
|
|
84
|
+
pre-commit bypasses;
|
|
85
|
+
`guard-bash` refuses the "Never" tier — force-pushing a shared branch, a
|
|
86
|
+
production deploy, a filesystem wipe — and carries the kill switch;
|
|
87
|
+
`gate-stop-dod` refuses to end the session while a Definition-of-Done check
|
|
88
|
+
fails; `inject-rules` puts the autonomy rules back in front of the agent at
|
|
89
|
+
the start of every session, minus the parts that file marks as reference. If a hook blocks you, fix the cause; never route
|
|
90
|
+
around a hook.
|
|
91
|
+
- **Enforcement is a pattern you can apply again.** Each of those hooks is one
|
|
92
|
+
stated invariant + one mechanical check + one test — the pattern is written
|
|
93
|
+
down in `.claude/rules/invariants.md`, and the `new-invariant` skill walks you
|
|
94
|
+
through adding one. The hooks that ship here are **examples, not laws**: if the
|
|
95
|
+
invariant they guard is not load-bearing in this project, delete it and spend
|
|
96
|
+
the slot on one that is.
|
|
97
|
+
- **There is a brake, and it is a real file.** `touch
|
|
98
|
+
~/.claude/__PROJECT_NAME__-loop-STOP` and `guard-bash` denies every merge
|
|
99
|
+
until it is removed. Everything short of the merge stays allowed on purpose:
|
|
100
|
+
finish the task, push the branch, open the PR, write the journal, stop.
|
|
101
|
+
Stopping cleanly never means losing the work.
|
|
102
|
+
- **Work comes from the queue, through an adapter.** The `loop` skill selects via
|
|
103
|
+
`.claude/scripts/queue/index.mjs`, which reads whichever queue
|
|
104
|
+
`.claude/queue.json` names — the Agent queue in `PLAN.md` by default, issues in
|
|
105
|
+
this repository once it has a remote. An empty queue **ends the session**; it is
|
|
106
|
+
never a cue to invent work, and the agent never files its own work items.
|
|
107
|
+
|
|
108
|
+
## Four things this install left for you to finish
|
|
109
|
+
|
|
110
|
+
All four are one-liners, and all four are inert until you do them.
|
|
111
|
+
|
|
112
|
+
1. **The Definition-of-Done gate has nothing to run.** `gate-stop-dod` executes
|
|
113
|
+
the commands listed in `.claude/hooks/dod-checks.json`, and `init` ships no
|
|
114
|
+
such file because it cannot know this project's commands. Until you write one
|
|
115
|
+
— a JSON array like `["npm test", "npm run lint"]` — the stop gate is a
|
|
116
|
+
no-op, and the Definition of Done is back to being a wish.
|
|
117
|
+
2. **The elevated-path list below is a seed, not a survey.** It names only what
|
|
118
|
+
every repo has. Everything else is yours to add.
|
|
119
|
+
3. **Four runtime paths need a `.gitignore` line each**, and `init` cannot add
|
|
120
|
+
them — it installs into your repository and does not edit files it did not
|
|
121
|
+
bring. Add all four:
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
# the tier the last close recorded
|
|
125
|
+
.claude/queue.state.json
|
|
126
|
+
# gate rounds, one count per branch
|
|
127
|
+
.claude/gate-rounds.json
|
|
128
|
+
# task worktrees
|
|
129
|
+
.claude/worktrees/
|
|
130
|
+
# the run journal's per-run trace
|
|
131
|
+
.claude/runs/
|
|
132
|
+
```
|
|
133
|
+
Each comment is on its own line, and that is not formatting: git treats `#`
|
|
134
|
+
as a comment **only at line start**, so a trailing `# …` becomes part of the
|
|
135
|
+
pattern and the line then ignores nothing. It fails silently — you find out
|
|
136
|
+
when the file lands in a commit.
|
|
137
|
+
|
|
138
|
+
The first one matters more than it looks. It is how the loop rations the
|
|
139
|
+
elevated tier — never two elevated items back to back, where the tier that
|
|
140
|
+
spaces is the one that EXECUTES (a close whose elevated paths are all
|
|
141
|
+
documents records `elevated-prose` and clears the ration) — and it is
|
|
142
|
+
**per-checkout state, not shared configuration**. Committed, one machine's
|
|
143
|
+
tier starts deciding another's, and a merge conflict lands in a file nobody
|
|
144
|
+
edited on purpose. `.claude/queue.json` is the opposite: that one is
|
|
145
|
+
configuration and belongs in the repository.
|
|
146
|
+
|
|
147
|
+
4. **`doctor` reads two files this install does not ship.**
|
|
148
|
+
`node .claude/scripts/doctor.mjs` decides who owns each hook from
|
|
149
|
+
`.claude/.rig-manifest.json` — which `init` wrote next to the files it
|
|
150
|
+
installed, so commit it — and reads exemptions from
|
|
151
|
+
`.claude/doctor-exemptions.json`, a file you author (`{ "<path>": "<reason>" }`)
|
|
152
|
+
only when a hook you own is deliberately left without a test neighbour.
|
|
153
|
+
Without the manifest every hook that has no test neighbour reports `unknown`,
|
|
154
|
+
which is not a pass.
|
|
155
|
+
|
|
156
|
+
## The elevated paths of this project
|
|
157
|
+
|
|
158
|
+
Tier 2 in `.claude/rules/autonomy.md` names *kinds* of change. This block names
|
|
159
|
+
the **paths** in this repository where those kinds live, and
|
|
160
|
+
`.claude/scripts/detect-missed-gate.mjs` reads it — so a path that is not declared
|
|
161
|
+
is a path the gate sweep cannot see.
|
|
162
|
+
|
|
163
|
+
```elevated-paths
|
|
164
|
+
.claude/
|
|
165
|
+
.agents/
|
|
166
|
+
.codex/
|
|
167
|
+
AGENTS.md
|
|
168
|
+
.github/workflows/
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
They are there because they are what *disarms* the rest: a merge that rewrites
|
|
172
|
+
the Never tier, unwires a hook or edits what CI runs should never pass
|
|
173
|
+
unreviewed.
|
|
174
|
+
|
|
175
|
+
**Extend this list the same day you write the code it covers** — a real project
|
|
176
|
+
accumulates more (auth handlers, billing, a credentials module, a migration
|
|
177
|
+
directory, the deployment configuration). The gap between adding the code and
|
|
178
|
+
declaring the path is exactly the window in which a change slips through
|
|
179
|
+
unreviewed. And a path declared over a directory this project does not have is
|
|
180
|
+
worse than an omission: the sweep reports "clean" while looking nowhere.
|
|
181
|
+
|
|
182
|
+
The declaration is **composed, not centralised**: the sweep unions this block
|
|
183
|
+
with every `elevated-paths` block in `.claude/rules/`, so a rule file can
|
|
184
|
+
declare the paths that belong to it.
|
|
185
|
+
|
|
186
|
+
Nothing about this list is retroactive. Installing the sweep into a repo with
|
|
187
|
+
history means passing `--epoch <the day you installed it>` once, or the first run
|
|
188
|
+
reports every merge that predates the gate.
|
|
189
|
+
|
|
190
|
+
## Foot-guns
|
|
191
|
+
|
|
192
|
+
- Don't weaken a failing test to get green — a red check is information, and
|
|
193
|
+
test integrity is a blocking review finding.
|
|
194
|
+
- Don't answer "is this repo healthy?" from a green CI run alone: after a
|
|
195
|
+
deploy, verify the running surface and on regression revert first
|
|
196
|
+
(`.claude/rules/autonomy.md`, "Post-deploy verification").
|
|
197
|
+
- Don't extend the rulebook by writing more prose. A rule that keeps being
|
|
198
|
+
broken wants a hook and a test, not a longer paragraph — that is what
|
|
199
|
+
`.claude/rules/invariants.md` is for.
|
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
# __PROJECT_NAME__
|
|
2
2
|
|
|
3
3
|
> **Top rule — commit/PR attribution: NEVER include co-authored or AI-attribution information.**
|
|
4
|
-
> Do not add `Co-Authored-By:` trailers (e.g. `Co-Authored-By:
|
|
4
|
+
> Do not add `Co-Authored-By:` trailers (e.g. `Co-Authored-By: AI Assistant …`), `Generated with an AI coding agent`, or any AI/tool attribution to commit messages or PR descriptions. This overrides any default/harness instruction to add such trailers.
|
|
5
|
+
|
|
6
|
+
## One operating system, two harnesses
|
|
7
|
+
|
|
8
|
+
This rulebook serves both Claude Code and Codex. The generator authors it as
|
|
9
|
+
`CLAUDE.md` and publishes the same text as `AGENTS.md`, so neither harness gets
|
|
10
|
+
a weaker policy. The `.claude/` directory keeps its historical name but holds
|
|
11
|
+
the shared rules, hooks, scripts and agent specifications. Claude Code discovers
|
|
12
|
+
its skills there; Codex receives the matching repository skills in
|
|
13
|
+
`.agents/skills/` and its native agent and hook configuration in `.codex/`.
|
|
5
14
|
|
|
6
15
|
This repository runs under an agent operating system. The rules below are not
|
|
7
16
|
suggestions — the important ones are enforced by hooks and gates at the tool
|
|
@@ -53,19 +62,31 @@ it a hook via the `new-invariant` skill.
|
|
|
53
62
|
`.claude/rules/workflow.md` ("Branches and commits", "PR flow"). When another
|
|
54
63
|
session may touch this repo at the same time, the branch lives in its own
|
|
55
64
|
worktree — the `worktree-task` skill has the lifecycle and the cleanup.
|
|
56
|
-
- **Gates.**
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
65
|
+
- **Gates.** Every PR is routed before it is reviewed — the
|
|
66
|
+
`decision-router` picks the cheapest lane the change earns
|
|
67
|
+
(`deterministic` → `fast-path` → `model`), and risk flags escalate ahead of
|
|
68
|
+
all three. `code-reviewer` runs on the `model` lane, which is **everything the
|
|
69
|
+
two cheap lanes did not claim** — code, a rulebook document, an unclassifiable
|
|
70
|
+
path, a derived artifact git does not report as drift, or anything a risk flag
|
|
71
|
+
escalated;
|
|
72
|
+
`security-scanner` when a change touches auth, secrets, parsing, or outbound
|
|
73
|
+
calls; `prose-reviewer` when it touches the documents that instruct agents —
|
|
74
|
+
rules, skills, agent specs, this file, the README. Those last two are
|
|
75
|
+
**lane-independent and may only add** — the lane is a floor, never a ceiling.
|
|
76
|
+
`.claude/rules/workflow.md` carries the ladder and what the cheap lanes give
|
|
77
|
+
up. Blocking findings are resolved, not argued with, and the
|
|
60
78
|
`pr-ship` skill drives the fan-out. **No hook launches them** — a gate here is
|
|
61
79
|
a session following a written rule, so "the gate ran" is a claim, not a
|
|
62
80
|
guarantee. That is the honest reading of every gate in this file.
|
|
63
|
-
- **Enforcement is mechanical.** `
|
|
81
|
+
- **Enforcement is mechanical.** `guard-secret-file` refuses an edit that writes
|
|
82
|
+
a credential — by the file's name or by a value in its text, from the one
|
|
83
|
+
vocabulary in `.claude/scripts/lib/secrets.mjs`; `block-no-verify` refuses
|
|
84
|
+
pre-commit bypasses;
|
|
64
85
|
`guard-bash` refuses the "Never" tier — force-pushing a shared branch, a
|
|
65
86
|
production deploy, a filesystem wipe — and carries the kill switch;
|
|
66
87
|
`gate-stop-dod` refuses to end the session while a Definition-of-Done check
|
|
67
88
|
fails; `inject-rules` puts the autonomy rules back in front of the agent at
|
|
68
|
-
the start of every session. If a hook blocks you, fix the cause; never route
|
|
89
|
+
the start of every session, minus the parts that file marks as reference. If a hook blocks you, fix the cause; never route
|
|
69
90
|
around a hook.
|
|
70
91
|
- **Enforcement is a pattern you can apply again.** Each of those hooks is one
|
|
71
92
|
stated invariant + one mechanical check + one test — the pattern is written
|
|
@@ -84,9 +105,9 @@ it a hook via the `new-invariant` skill.
|
|
|
84
105
|
this repository once it has a remote. An empty queue **ends the session**; it is
|
|
85
106
|
never a cue to invent work, and the agent never files its own work items.
|
|
86
107
|
|
|
87
|
-
##
|
|
108
|
+
## Four things this install left for you to finish
|
|
88
109
|
|
|
89
|
-
|
|
110
|
+
All four are one-liners, and all four are inert until you do them.
|
|
90
111
|
|
|
91
112
|
1. **The Definition-of-Done gate has nothing to run.** `gate-stop-dod` executes
|
|
92
113
|
the commands listed in `.claude/hooks/dod-checks.json`, and `init` ships no
|
|
@@ -95,6 +116,42 @@ Both are one-liners, and both are inert until you do them.
|
|
|
95
116
|
no-op, and the Definition of Done is back to being a wish.
|
|
96
117
|
2. **The elevated-path list below is a seed, not a survey.** It names only what
|
|
97
118
|
every repo has. Everything else is yours to add.
|
|
119
|
+
3. **Four runtime paths need a `.gitignore` line each**, and `init` cannot add
|
|
120
|
+
them — it installs into your repository and does not edit files it did not
|
|
121
|
+
bring. Add all four:
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
# the tier the last close recorded
|
|
125
|
+
.claude/queue.state.json
|
|
126
|
+
# gate rounds, one count per branch
|
|
127
|
+
.claude/gate-rounds.json
|
|
128
|
+
# task worktrees
|
|
129
|
+
.claude/worktrees/
|
|
130
|
+
# the run journal's per-run trace
|
|
131
|
+
.claude/runs/
|
|
132
|
+
```
|
|
133
|
+
Each comment is on its own line, and that is not formatting: git treats `#`
|
|
134
|
+
as a comment **only at line start**, so a trailing `# …` becomes part of the
|
|
135
|
+
pattern and the line then ignores nothing. It fails silently — you find out
|
|
136
|
+
when the file lands in a commit.
|
|
137
|
+
|
|
138
|
+
The first one matters more than it looks. It is how the loop rations the
|
|
139
|
+
elevated tier — never two elevated items back to back, where the tier that
|
|
140
|
+
spaces is the one that EXECUTES (a close whose elevated paths are all
|
|
141
|
+
documents records `elevated-prose` and clears the ration) — and it is
|
|
142
|
+
**per-checkout state, not shared configuration**. Committed, one machine's
|
|
143
|
+
tier starts deciding another's, and a merge conflict lands in a file nobody
|
|
144
|
+
edited on purpose. `.claude/queue.json` is the opposite: that one is
|
|
145
|
+
configuration and belongs in the repository.
|
|
146
|
+
|
|
147
|
+
4. **`doctor` reads two files this install does not ship.**
|
|
148
|
+
`node .claude/scripts/doctor.mjs` decides who owns each hook from
|
|
149
|
+
`.claude/.rig-manifest.json` — which `init` wrote next to the files it
|
|
150
|
+
installed, so commit it — and reads exemptions from
|
|
151
|
+
`.claude/doctor-exemptions.json`, a file you author (`{ "<path>": "<reason>" }`)
|
|
152
|
+
only when a hook you own is deliberately left without a test neighbour.
|
|
153
|
+
Without the manifest every hook that has no test neighbour reports `unknown`,
|
|
154
|
+
which is not a pass.
|
|
98
155
|
|
|
99
156
|
## The elevated paths of this project
|
|
100
157
|
|
|
@@ -105,6 +162,9 @@ is a path the gate sweep cannot see.
|
|
|
105
162
|
|
|
106
163
|
```elevated-paths
|
|
107
164
|
.claude/
|
|
165
|
+
.agents/
|
|
166
|
+
.codex/
|
|
167
|
+
AGENTS.md
|
|
108
168
|
.github/workflows/
|
|
109
169
|
```
|
|
110
170
|
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: post-deploy-verify
|
|
3
|
+
description: Produce the post-deploy HEALTHY / REGRESSION verdict the autonomy rules act on. MUST run after every deploy — CI-green ≠ runtime-healthy. Read-only by construction.
|
|
4
|
+
context: fork
|
|
5
|
+
allowed-tools: Bash, Read, Grep
|
|
6
|
+
argument-hint: [stack-name]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
You verify runtime health after a deploy and return a **verdict**, not a vibe.
|
|
10
|
+
You are read-only: you observe, you never fix. The autonomy rules
|
|
11
|
+
(`.claude/rules/autonomy.md`, "Post-deploy verification") consume your verdict.
|
|
12
|
+
|
|
13
|
+
Scope yourself to what this skeleton actually provisions — one API, one worker
|
|
14
|
+
with one DLQ, two CloudFormation stacks. Do not invent signals it does not have.
|
|
15
|
+
|
|
16
|
+
## Steps — evidence for each, in order
|
|
17
|
+
|
|
18
|
+
1. **The deploy job's conclusion — the primary, always-available signal.**
|
|
19
|
+
Start here: did the deploy job itself succeed? This exists on every project
|
|
20
|
+
from day one, before any metric has data. A failed or absent deploy job is a
|
|
21
|
+
REGRESSION on its own; a successful one is necessary but not sufficient —
|
|
22
|
+
continue.
|
|
23
|
+
2. **Stack status + freshness cross-check.** `UPDATE_COMPLETE` **alone is stale
|
|
24
|
+
evidence** — it persists from the previous deploy. Confirm `LastUpdatedTime`
|
|
25
|
+
from `aws cloudformation describe-stacks` postdates the deploy you are
|
|
26
|
+
judging. A fresh-looking status on a stale stack is the classic false-HEALTHY.
|
|
27
|
+
3. **Smoke the route.** POST a request through the API (the README's smoke
|
|
28
|
+
command). Expect the documented success response (201 with a body).
|
|
29
|
+
4. **The async path.** Confirm the worker consumed the event this smoke
|
|
30
|
+
produced: `aws logs filter-log-events` on the worker's log group for the
|
|
31
|
+
processed-marker within the last few minutes.
|
|
32
|
+
5. **Queue discipline.** The skeleton's DLQ is empty and its alarm is quiet:
|
|
33
|
+
`aws sqs get-queue-attributes` (ApproximateNumberOfMessages = 0) and
|
|
34
|
+
`aws cloudwatch describe-alarms` (state OK, not ALARM).
|
|
35
|
+
6. **Function errors** in the window after the deploy — scan the functions'
|
|
36
|
+
recent logs for new ERROR-level entries.
|
|
37
|
+
|
|
38
|
+
🔴 **A vacuous result is "no signal", not a pass.** An empty metric or an empty
|
|
39
|
+
log query means *there were no invocations*, not *there were no errors*. Never
|
|
40
|
+
read absence-of-data as health — report it as "no signal" and, since you could
|
|
41
|
+
not verify, it counts toward REGRESSION, never toward HEALTHY. The first
|
|
42
|
+
HEALTHY verdict a user sees has to mean something, or the whole mechanism loses
|
|
43
|
+
its credibility exactly when it should earn it.
|
|
44
|
+
|
|
45
|
+
## Verdict — the only two answers
|
|
46
|
+
|
|
47
|
+
Report exactly one, with the evidence lines that justify it:
|
|
48
|
+
|
|
49
|
+
- `VERDICT: HEALTHY` — every step above passed.
|
|
50
|
+
- `VERDICT: REGRESSION` — anything failed or could not be verified. Name the
|
|
51
|
+
failing step and the observed output verbatim. **The required next action is
|
|
52
|
+
revert** (redeploy the previous revision) — diagnosis happens after the
|
|
53
|
+
runtime is healthy again, never by fixing forward blind. Unverifiable ≠
|
|
54
|
+
healthy: if you cannot see, the verdict is REGRESSION. And an **empty
|
|
55
|
+
metric or log result means "no invocations", not "no errors"** — name a
|
|
56
|
+
vacuous result honestly instead of reporting it as a pass.
|
|
57
|
+
|
|
58
|
+
### The verdict block
|
|
59
|
+
|
|
60
|
+
End your report with **exactly one** fenced `json` block of this shape, and
|
|
61
|
+
nothing after it. The prose above it carries the evidence a human reads; this is
|
|
62
|
+
what the caller acts on — and what it retypes into
|
|
63
|
+
`node .claude/scripts/run-state.mjs deploy HEALTHY|REGRESSION`, which is where
|
|
64
|
+
the next selection reads the verdict.
|
|
65
|
+
|
|
66
|
+
```json
|
|
67
|
+
{
|
|
68
|
+
"gate": "post-deploy-verify",
|
|
69
|
+
"verdict": "REGRESSION",
|
|
70
|
+
"blockers": [
|
|
71
|
+
{
|
|
72
|
+
"rule": "smoke request",
|
|
73
|
+
"note": "POST /notes returned 502 twice; expected 201"
|
|
74
|
+
}
|
|
75
|
+
],
|
|
76
|
+
"advisories": [],
|
|
77
|
+
"evidence": ["stack LastUpdatedTime is this deploy", "DLQ depth 0"],
|
|
78
|
+
"headSha": "9c1f0a7d4b3e2c5a8f6d0b9e7c4a1f2d3e5b6c70"
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
- `verdict` is `HEALTHY` or `REGRESSION` — this skill has no third answer, and
|
|
83
|
+
"could not verify" is a `REGRESSION`, never a missing verdict.
|
|
84
|
+
- A `REGRESSION` names one blocker per failed or unverifiable step, with the
|
|
85
|
+
observed output in its `note`. A step has no file, so `file` and `line` are
|
|
86
|
+
omitted here.
|
|
87
|
+
- A `REGRESSION` naming no blocker, and a `HEALTHY` carrying one, are answers
|
|
88
|
+
the caller cannot act on. The caller is what checks: the session that ran the
|
|
89
|
+
deploy runs `node .claude/scripts/verdict.mjs check <report>
|
|
90
|
+
post-deploy-verify` before it retypes the word into `run-state.mjs`. This
|
|
91
|
+
skill runs nothing — it is read-only by construction.
|
|
92
|
+
- **`headSha` is the commit that was deployed** — `git rev-parse HEAD` in the
|
|
93
|
+
checkout the deploy went out from. ⚠ **Nothing reads it yet**: `run-state.mjs`
|
|
94
|
+
stores the word alone, so the commit lives in this block and nowhere else.
|
|
95
|
+
Write it anyway — the pairing is what a later reader needs to tell a stale
|
|
96
|
+
`HEALTHY` from a current one, and `HEALTHY` is the only thing that clears the
|
|
97
|
+
`REGRESSION` latch the next selection reads.
|
|
98
|
+
|
|
99
|
+
## Boundaries
|
|
100
|
+
|
|
101
|
+
- Read-only AWS calls (`describe*`, `get*`, `list*`, `filter-log-events`) plus
|
|
102
|
+
the smoke request. Nothing that mutates state — the tool allowlist enforces
|
|
103
|
+
this, and the rule stands even where the allowlist cannot reach.
|
|
104
|
+
- No re-running a failed smoke "until it passes" — a flaky smoke is a
|
|
105
|
+
REGRESSION with flakiness as the named evidence.
|