create-agent-rig 0.9.1 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +462 -3
- package/README.md +267 -305
- package/package.json +8 -14
- package/packages/cli/dist/commands/create.js +56 -88
- package/packages/cli/dist/commands/doctor.js +213 -0
- package/packages/cli/dist/commands/init.js +111 -50
- package/packages/cli/dist/commands/integrations.js +468 -0
- package/packages/cli/dist/commands/setup-wizard.js +61 -0
- package/packages/cli/dist/commands/uninstall.js +1223 -0
- package/packages/cli/dist/commands/upgrade.js +508 -101
- package/packages/cli/dist/index.js +658 -47
- package/packages/cli/dist/integrations/declaration.js +158 -0
- package/packages/cli/dist/integrations/doctor-guards.js +126 -0
- package/packages/cli/dist/integrations/doctor-workflow.js +25 -0
- package/packages/cli/dist/integrations/mcp-json.js +107 -0
- package/packages/cli/dist/integrations/memory-doctor.js +124 -0
- package/packages/cli/dist/integrations/registry.js +22 -0
- package/packages/cli/dist/integrations/spawn.js +228 -0
- package/packages/cli/dist/integrations/spec-kit.js +280 -0
- package/packages/cli/dist/integrations/verify.js +193 -0
- package/packages/cli/dist/integrations/windows-job.js +275 -0
- package/packages/cli/dist/lib/elevated-paths.js +74 -0
- package/packages/cli/dist/lib/install-set.js +1 -44
- package/packages/cli/dist/lib/manifest.js +64 -5
- package/packages/cli/dist/lib/prompts.js +0 -23
- package/packages/cli/dist/lib/safe-path.js +193 -2
- package/packages/cli/dist/lib/safe-text.js +78 -0
- package/packages/cli/dist/lib/substitute.js +1 -52
- package/packages/cli/dist/lib/summary.js +5 -5
- package/packages/cli/dist/templates.js +4 -14
- package/scripts/prepare.mjs +28 -12
- package/templates/agent-os/subagent-routing.json +4 -4
- package/templates/agent-os/universal/.agents/skills/check-premises/SKILL.md +45 -12
- package/templates/agent-os/universal/.agents/skills/loop/SKILL.md +31 -6
- package/templates/agent-os/universal/.agents/skills/new-invariant/SKILL.md +5 -5
- package/templates/agent-os/universal/.agents/skills/pr-ship/SKILL.md +1 -1
- package/templates/agent-os/universal/.agents/skills/worktree-task/SKILL.md +3 -3
- package/templates/agent-os/universal/.claude/agents/code-reviewer.md +8 -6
- package/templates/agent-os/universal/.claude/agents/implementation-agent.md +41 -0
- package/templates/agent-os/universal/.claude/agents/prose-reviewer.md +8 -4
- package/templates/agent-os/universal/.claude/agents/security-scanner.md +4 -2
- package/templates/agent-os/universal/.claude/agents/test-writer.md +2 -2
- package/templates/agent-os/universal/.claude/hooks/gate-stop-dod.mjs +4 -2
- package/templates/agent-os/universal/.claude/hooks/inject-rules.mjs +83 -4
- package/templates/agent-os/universal/.claude/rules/autonomy.md +55 -27
- package/templates/agent-os/universal/.claude/rules/invariants.md +9 -8
- package/templates/agent-os/universal/.claude/rules/workflow.md +59 -22
- package/templates/agent-os/universal/.claude/scripts/decision-router.mjs +1 -1
- package/templates/agent-os/universal/.claude/scripts/detect-missed-gate.mjs +12 -9
- package/templates/agent-os/universal/.claude/scripts/doctor.mjs +100 -2
- package/templates/agent-os/universal/.claude/scripts/lib/claim-records.mjs +236 -6
- package/templates/agent-os/universal/.claude/scripts/queue/state.mjs +2 -2
- package/templates/agent-os/universal/.claude/scripts/reconcile-external-prs.mjs +4 -3
- package/templates/agent-os/universal/.claude/scripts/revalidate.mjs +16 -0
- package/templates/agent-os/universal/.claude/settings.json +0 -8
- package/templates/agent-os/universal/.claude/skills/check-premises/SKILL.md +45 -12
- package/templates/agent-os/universal/.claude/skills/loop/SKILL.md +31 -6
- package/templates/agent-os/universal/.claude/skills/new-invariant/SKILL.md +5 -5
- package/templates/agent-os/universal/.claude/skills/pr-ship/SKILL.md +1 -1
- package/templates/agent-os/universal/.claude/skills/worktree-task/SKILL.md +3 -3
- package/templates/agent-os/universal/.codex/agents/code-reviewer.toml +2 -2
- package/templates/agent-os/universal/.codex/agents/implementation-agent.toml +6 -0
- package/templates/agent-os/universal/.codex/agents/prose-reviewer.toml +2 -2
- package/templates/agent-os/universal/.codex/agents/security-scanner.toml +1 -1
- package/templates/agent-os/universal/.codex/agents/test-writer.toml +1 -1
- package/templates/agent-os/universal/.codex/hooks.json +0 -10
- package/templates/agent-os/universal/AGENTS.md +208 -107
- package/templates/agent-os/universal/CLAUDE.md +15 -162
- package/templates/agent-os/universal/PLAN.md +17 -10
- package/templates/agent-os/universal/docs/decisions/agents-md-canonical.md +313 -0
- package/templates/agent-os/universal/docs/decisions/codex-adapter.md +15 -10
- package/templates/agent-os/universal/docs/decisions/review-lanes.md +12 -8
- package/templates/agent-os/universal/docs/decisions/session-start-wire-format.md +206 -0
- package/templates/agent-os/universal/docs/decisions/subagent-routing.md +6 -3
- package/templates/agent-os/universal/docs/decisions/workflow-layer-split.md +235 -0
- package/templates/agent-os/universal/layers.json +25 -30
- package/templates/hash-history.json +112 -40
- package/templates/release-ledger.json +3 -1
- package/packages/cli/dist/lib/composition.js +0 -20
- package/packages/cli/dist/lib/targets.js +0 -28
- package/packages/cli/dist/policy/benchmark/corpus.js +0 -165
- package/packages/cli/dist/policy/core/adapter.js +0 -18
- package/packages/cli/dist/policy/core/coverage.js +0 -253
- package/packages/cli/dist/policy/core/decision-record.js +0 -287
- package/packages/cli/dist/policy/core/declaration.js +0 -127
- package/packages/cli/dist/policy/core/evidence-matrix.js +0 -94
- package/packages/cli/dist/policy/core/probe.js +0 -442
- package/packages/cli/dist/policy/core/registry.js +0 -115
- package/packages/cli/dist/policy/core/validation.js +0 -275
- package/packages/cli/dist/policy/core/vocabulary.js +0 -123
- package/packages/cli/dist/policy/harness/claude.js +0 -47
- package/packages/cli/dist/policy/harness/codex.js +0 -87
- package/packages/cli/dist/policy/harness/index.js +0 -15
- package/packages/cli/dist/policy/harness/shared-hooks.js +0 -28
- package/packages/cli/dist/policy/index.js +0 -17
- package/templates/agent-os/init/AGENTS.md +0 -201
- package/templates/agent-os/init/CLAUDE.md +0 -201
- package/templates/agent-os/stack/aws-cdk/.agents/skills/post-deploy-verify/SKILL.md +0 -105
- package/templates/agent-os/stack/aws-cdk/.agents/skills/ro-debug/SKILL.md +0 -117
- package/templates/agent-os/stack/aws-cdk/.claude/agents/cdk-diff-reviewer.md +0 -89
- package/templates/agent-os/stack/aws-cdk/.claude/rules/aws-cdk.md +0 -105
- package/templates/agent-os/stack/aws-cdk/.claude/skills/post-deploy-verify/SKILL.md +0 -105
- package/templates/agent-os/stack/aws-cdk/.claude/skills/ro-debug/SKILL.md +0 -117
- package/templates/agent-os/stack/aws-cdk/.codex/agents/cdk-diff-reviewer.toml +0 -6
- package/templates/agent-os/stack/node-ts/.claude/hooks/dod-checks.json +0 -1
- package/templates/agent-os/stack/node-ts/.claude/rules/node-ts.md +0 -85
- package/templates/agent-os/universal/.claude/hooks/guard-core-purity.mjs +0 -89
- package/templates/agent-os/universal/.claude/hooks/guard-web-boundary.mjs +0 -68
- package/templates/agent-os/universal/.claude/rules/architecture.md +0 -81
- package/templates/skeleton/aws-serverless/.github/workflows/ci.yml +0 -28
- package/templates/skeleton/aws-serverless/.github/workflows/deploy.yml +0 -90
- package/templates/skeleton/aws-serverless/README.md +0 -181
- package/templates/skeleton/aws-serverless/apps/web/next.config.mjs +0 -17
- package/templates/skeleton/aws-serverless/apps/web/package.json +0 -19
- package/templates/skeleton/aws-serverless/apps/web/src/app/layout.tsx +0 -17
- package/templates/skeleton/aws-serverless/apps/web/src/app/page.tsx +0 -96
- package/templates/skeleton/aws-serverless/apps/web/src/lib/api.ts +0 -36
- package/templates/skeleton/aws-serverless/apps/web/src/lib/validate.ts +0 -23
- package/templates/skeleton/aws-serverless/apps/web/test/shared-validation.test.ts +0 -38
- package/templates/skeleton/aws-serverless/apps/web/tsconfig.json +0 -14
- package/templates/skeleton/aws-serverless/eslint.config.mjs +0 -20
- package/templates/skeleton/aws-serverless/gitignore +0 -56
- package/templates/skeleton/aws-serverless/infra/bin/app.ts +0 -79
- package/templates/skeleton/aws-serverless/infra/cdk.json +0 -3
- package/templates/skeleton/aws-serverless/infra/lib/app-stack.ts +0 -248
- package/templates/skeleton/aws-serverless/infra/lib/web-stack.ts +0 -45
- package/templates/skeleton/aws-serverless/infra/package.json +0 -18
- package/templates/skeleton/aws-serverless/infra/test/allowed-origins.test.ts +0 -301
- package/templates/skeleton/aws-serverless/infra/test/app-composition.test.ts +0 -137
- package/templates/skeleton/aws-serverless/infra/test/app-stack.test.ts +0 -120
- package/templates/skeleton/aws-serverless/infra/test/web-stack.test.ts +0 -44
- package/templates/skeleton/aws-serverless/package.json +0 -30
- package/templates/skeleton/aws-serverless/packages/core/package.json +0 -11
- package/templates/skeleton/aws-serverless/packages/core/src/events.ts +0 -14
- package/templates/skeleton/aws-serverless/packages/core/src/index.ts +0 -15
- package/templates/skeleton/aws-serverless/packages/core/src/note.ts +0 -69
- package/templates/skeleton/aws-serverless/packages/core/test/events.test.ts +0 -23
- package/templates/skeleton/aws-serverless/packages/core/test/note.test.ts +0 -101
- package/templates/skeleton/aws-serverless/packages/db/package.json +0 -14
- package/templates/skeleton/aws-serverless/packages/db/src/client.ts +0 -17
- package/templates/skeleton/aws-serverless/packages/db/src/index.ts +0 -2
- package/templates/skeleton/aws-serverless/packages/db/src/note-model.ts +0 -52
- package/templates/skeleton/aws-serverless/packages/db/test/note-model.test.ts +0 -91
- package/templates/skeleton/aws-serverless/packages/shared/package.json +0 -11
- package/templates/skeleton/aws-serverless/packages/shared/src/env.ts +0 -17
- package/templates/skeleton/aws-serverless/packages/shared/src/errors.ts +0 -33
- package/templates/skeleton/aws-serverless/packages/shared/src/index.ts +0 -3
- package/templates/skeleton/aws-serverless/packages/shared/src/logger.ts +0 -20
- package/templates/skeleton/aws-serverless/packages/shared/test/env.test.ts +0 -26
- package/templates/skeleton/aws-serverless/packages/shared/test/errors.test.ts +0 -28
- package/templates/skeleton/aws-serverless/packages/shared/test/logger.test.ts +0 -19
- package/templates/skeleton/aws-serverless/pnpm-lock.yaml +0 -2855
- package/templates/skeleton/aws-serverless/pnpm-workspace.yaml +0 -14
- package/templates/skeleton/aws-serverless/services/api/package.json +0 -15
- package/templates/skeleton/aws-serverless/services/api/src/adapters/sqs-publisher.ts +0 -26
- package/templates/skeleton/aws-serverless/services/api/src/handlers/create-note.ts +0 -69
- package/templates/skeleton/aws-serverless/services/api/src/handlers/list-notes.ts +0 -37
- package/templates/skeleton/aws-serverless/services/api/src/list-main.ts +0 -12
- package/templates/skeleton/aws-serverless/services/api/src/main.ts +0 -21
- package/templates/skeleton/aws-serverless/services/api/src/usecases/create-note.ts +0 -41
- package/templates/skeleton/aws-serverless/services/api/src/usecases/list-notes.ts +0 -14
- package/templates/skeleton/aws-serverless/services/api/test/create-note.handler.test.ts +0 -211
- package/templates/skeleton/aws-serverless/services/api/test/create-note.usecase.test.ts +0 -45
- package/templates/skeleton/aws-serverless/services/api/test/list-notes.test.ts +0 -122
- package/templates/skeleton/aws-serverless/services/api/test/sqs-publisher.test.ts +0 -22
- package/templates/skeleton/aws-serverless/services/worker/package.json +0 -12
- package/templates/skeleton/aws-serverless/services/worker/src/handlers/note-created.ts +0 -15
- package/templates/skeleton/aws-serverless/services/worker/src/main.ts +0 -7
- package/templates/skeleton/aws-serverless/services/worker/src/usecases/process-note-created.ts +0 -37
- package/templates/skeleton/aws-serverless/services/worker/test/note-created.test.ts +0 -61
- package/templates/skeleton/aws-serverless/tsconfig.base.json +0 -15
- package/templates/skeleton/aws-serverless/tsconfig.json +0 -16
- package/templates/skeleton/aws-serverless/vitest.config.ts +0 -14
- package/templates/skeleton/node-service/.github/workflows/ci.yml +0 -27
- package/templates/skeleton/node-service/.github/workflows/deploy.yml +0 -29
- package/templates/skeleton/node-service/README.md +0 -104
- package/templates/skeleton/node-service/apps/web/next.config.mjs +0 -17
- package/templates/skeleton/node-service/apps/web/package.json +0 -19
- package/templates/skeleton/node-service/apps/web/src/app/layout.tsx +0 -17
- package/templates/skeleton/node-service/apps/web/src/app/page.tsx +0 -96
- package/templates/skeleton/node-service/apps/web/src/lib/api.ts +0 -29
- package/templates/skeleton/node-service/apps/web/src/lib/validate.ts +0 -23
- package/templates/skeleton/node-service/apps/web/test/shared-validation.test.ts +0 -38
- package/templates/skeleton/node-service/apps/web/tsconfig.json +0 -14
- package/templates/skeleton/node-service/eslint.config.mjs +0 -20
- package/templates/skeleton/node-service/gitignore +0 -54
- package/templates/skeleton/node-service/package.json +0 -30
- package/templates/skeleton/node-service/packages/core/package.json +0 -11
- package/templates/skeleton/node-service/packages/core/src/events.ts +0 -14
- package/templates/skeleton/node-service/packages/core/src/index.ts +0 -15
- package/templates/skeleton/node-service/packages/core/src/note.ts +0 -69
- package/templates/skeleton/node-service/packages/core/test/events.test.ts +0 -23
- package/templates/skeleton/node-service/packages/core/test/note.test.ts +0 -101
- package/templates/skeleton/node-service/packages/db/package.json +0 -12
- package/templates/skeleton/node-service/packages/db/src/index.ts +0 -1
- package/templates/skeleton/node-service/packages/db/src/note-store.ts +0 -100
- package/templates/skeleton/node-service/packages/db/test/note-store.test.ts +0 -100
- package/templates/skeleton/node-service/packages/shared/package.json +0 -11
- package/templates/skeleton/node-service/packages/shared/src/env.ts +0 -17
- package/templates/skeleton/node-service/packages/shared/src/errors.ts +0 -33
- package/templates/skeleton/node-service/packages/shared/src/index.ts +0 -3
- package/templates/skeleton/node-service/packages/shared/src/logger.ts +0 -20
- package/templates/skeleton/node-service/packages/shared/test/env.test.ts +0 -26
- package/templates/skeleton/node-service/packages/shared/test/errors.test.ts +0 -28
- package/templates/skeleton/node-service/packages/shared/test/logger.test.ts +0 -19
- package/templates/skeleton/node-service/pnpm-lock.yaml +0 -2402
- package/templates/skeleton/node-service/pnpm-workspace.yaml +0 -13
- package/templates/skeleton/node-service/scripts/build-artifact.mjs +0 -34
- package/templates/skeleton/node-service/services/api/package.json +0 -17
- package/templates/skeleton/node-service/services/api/src/adapters/spool-publisher.ts +0 -23
- package/templates/skeleton/node-service/services/api/src/handlers/create-note.ts +0 -40
- package/templates/skeleton/node-service/services/api/src/handlers/list-notes.ts +0 -23
- package/templates/skeleton/node-service/services/api/src/main.ts +0 -40
- package/templates/skeleton/node-service/services/api/src/server.ts +0 -172
- package/templates/skeleton/node-service/services/api/src/static-dir.ts +0 -20
- package/templates/skeleton/node-service/services/api/src/usecases/create-note.ts +0 -30
- package/templates/skeleton/node-service/services/api/src/usecases/list-notes.ts +0 -14
- package/templates/skeleton/node-service/services/api/test/artifact.test.ts +0 -92
- package/templates/skeleton/node-service/services/api/test/create-note.handler.test.ts +0 -64
- package/templates/skeleton/node-service/services/api/test/create-note.usecase.test.ts +0 -43
- package/templates/skeleton/node-service/services/api/test/list-notes.test.ts +0 -48
- package/templates/skeleton/node-service/services/api/test/package-manager.test.ts +0 -40
- package/templates/skeleton/node-service/services/api/test/package-manager.ts +0 -51
- package/templates/skeleton/node-service/services/api/test/server.test.ts +0 -208
- package/templates/skeleton/node-service/services/api/test/spool-publisher.test.ts +0 -32
- package/templates/skeleton/node-service/services/api/test/static-dir.test.ts +0 -29
- package/templates/skeleton/node-service/services/worker/package.json +0 -16
- package/templates/skeleton/node-service/services/worker/src/main.ts +0 -28
- package/templates/skeleton/node-service/services/worker/src/spool.ts +0 -60
- package/templates/skeleton/node-service/services/worker/src/usecases/process-note-created.ts +0 -38
- package/templates/skeleton/node-service/services/worker/test/process-note-created.test.ts +0 -34
- package/templates/skeleton/node-service/services/worker/test/spool.test.ts +0 -76
- package/templates/skeleton/node-service/tsconfig.base.json +0 -15
- package/templates/skeleton/node-service/tsconfig.json +0 -13
- package/templates/skeleton/node-service/vitest.config.ts +0 -12
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: cdk-diff-reviewer
|
|
3
|
-
description: Reviews an infrastructure change via `cdk diff` BEFORE any deploy. MUST run on every change under infra/ — a deploy without this review is a Never-tier action. Read-only; findings gate the deploy.
|
|
4
|
-
tools: Read, Grep, Glob, Bash
|
|
5
|
-
model: claude-opus-5
|
|
6
|
-
effort: high
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
You review what a deploy would actually do to running infrastructure. Your
|
|
10
|
-
input is the change under `infra/` **and** the synthesized diff (`cdk diff`,
|
|
11
|
-
run it yourself); your output is a verdict. You never fix and never deploy.
|
|
12
|
-
|
|
13
|
-
## How you work
|
|
14
|
-
|
|
15
|
-
1. Run `cdk diff` (and read the changed `infra/` sources for intent). The diff
|
|
16
|
-
is the truth: review what CloudFormation will do, not what the TypeScript
|
|
17
|
-
looks like it does.
|
|
18
|
-
2. Walk every resource change and flag it **by named rule** (below). Findings
|
|
19
|
-
come as **BLOCKERS first, then nits** — one list each, with the resource
|
|
20
|
-
and the rule it violates.
|
|
21
|
-
3. Your message IS the review, not a summary of it: every finding carries the
|
|
22
|
-
resource, the change, the rule, and the smallest fix. End with the verdict
|
|
23
|
-
block below — `SHIP` where `DEPLOY: OK` used to be, `HOLD` where
|
|
24
|
-
`DEPLOY: BLOCKED` did.
|
|
25
|
-
|
|
26
|
-
## Named rules — blockers
|
|
27
|
-
|
|
28
|
-
- **IAM broadening.** Any policy gaining actions, resources widening to `*`,
|
|
29
|
-
or a grant that outruns what a usecase does today. Least privilege is added
|
|
30
|
-
in the same PR as the need, never "for later".
|
|
31
|
-
- **Data loss paths.** A stateful resource (table, bucket, queue) being
|
|
32
|
-
replaced, deleted, or flipping its RemovalPolicy toward DESTROY.
|
|
33
|
-
Logical-id renames on stateful resources are replacements in disguise.
|
|
34
|
-
- **Safety-net removal.** A DLQ detached, an alarm deleted or loosened, a
|
|
35
|
-
retry budget widened to infinity, a dead-letter retention shortened.
|
|
36
|
-
- **Blast-radius growth.** New public surface (endpoints, permissions to
|
|
37
|
-
external principals), broadened network access, cross-stack exports that
|
|
38
|
-
make future changes harder to reverse.
|
|
39
|
-
- **Cost-relevant flips.** On-demand → provisioned capacity, log retention to
|
|
40
|
-
"forever", memory/timeout jumps with no stated reason.
|
|
41
|
-
|
|
42
|
-
## Nits (report, do not block)
|
|
43
|
-
|
|
44
|
-
Naming drift, missing descriptions, constructs that could use the narrower
|
|
45
|
-
grant helper, duplication between stacks.
|
|
46
|
-
|
|
47
|
-
## Boundaries
|
|
48
|
-
|
|
49
|
-
- Read-only: you run `cdk diff` and read code; you never run `cdk deploy`,
|
|
50
|
-
never edit files, never mutate AWS state.
|
|
51
|
-
- An empty diff is a real finding too — say "no infrastructure change" and
|
|
52
|
-
return `SHIP`, so the gate leaves a trace either way.
|
|
53
|
-
|
|
54
|
-
## The verdict block
|
|
55
|
-
|
|
56
|
-
End your report with **exactly one** fenced `json` block of this shape, and
|
|
57
|
-
nothing after it. It is what the calling gate reads.
|
|
58
|
-
|
|
59
|
-
```json
|
|
60
|
-
{
|
|
61
|
-
"gate": "cdk-diff-reviewer",
|
|
62
|
-
"verdict": "HOLD",
|
|
63
|
-
"blockers": [
|
|
64
|
-
{
|
|
65
|
-
"file": "infra/lib/api-stack.ts",
|
|
66
|
-
"line": 88,
|
|
67
|
-
"rule": "data loss",
|
|
68
|
-
"note": "the table's RemovalPolicy went to DESTROY — replacement drops it"
|
|
69
|
-
}
|
|
70
|
-
],
|
|
71
|
-
"advisories": [],
|
|
72
|
-
"evidence": ["cdk diff against the deployed stage"],
|
|
73
|
-
"headSha": "9c1f0a7d4b3e2c5a8f6d0b9e7c4a1f2d3e5b6c70"
|
|
74
|
-
}
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
- `verdict` is `SHIP` (nothing blocking, including an empty diff), `HOLD`, or
|
|
78
|
-
`NOT_APPLICABLE` when the change touches no infrastructure at all.
|
|
79
|
-
- Every blocker names the `rule` it violates, with `file` and `line` when it has
|
|
80
|
-
a location and neither when it does not.
|
|
81
|
-
- A `HOLD` naming no blocker is **refused**, and so is a `SHIP` carrying one:
|
|
82
|
-
`node .claude/scripts/verdict.mjs check <report> cdk-diff-reviewer` is what
|
|
83
|
-
refuses them.
|
|
84
|
-
- **`headSha` is the commit you reviewed** — `git rev-parse HEAD` in the
|
|
85
|
-
checkout you read. It is what lets `node .claude/scripts/verdict.mjs coverage
|
|
86
|
-
<commit>` tell "this gate answered for the commit being merged" from "it
|
|
87
|
-
answered two pushes ago". A verdict naming no commit is counted as neither
|
|
88
|
-
covered nor missing, so `pr-ship` holds on it — and only `pr-ship`: no hook
|
|
89
|
-
runs that check, so a session that skips the gate skips this with it.
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
# Stack rules — AWS + CDK
|
|
2
|
-
|
|
3
|
-
How the universal boundaries land on AWS. If a rule here seems to fight a
|
|
4
|
-
universal rule, that is an invariant conflict — stop and surface it.
|
|
5
|
-
|
|
6
|
-
## The elevated paths this layer adds
|
|
7
|
-
|
|
8
|
-
`CLAUDE.md` declares the project's own elevated paths; this block adds the ones
|
|
9
|
-
that exist only because this layer does. The gate sweep
|
|
10
|
-
(`.claude/scripts/detect-missed-gate.mjs`) unions every declaration it finds, so a
|
|
11
|
-
target without infrastructure never declares a directory it does not have.
|
|
12
|
-
|
|
13
|
-
```elevated-paths
|
|
14
|
-
infra/
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
## Infrastructure is code, and only code
|
|
18
|
-
|
|
19
|
-
- Everything lives in the CDK app under `infra/`. A console change ("click-ops")
|
|
20
|
-
is drift, and drift is a defect — reproduce it in CDK or revert it.
|
|
21
|
-
- **Every change under `infra/` passes the `cdk-diff-reviewer` agent before it
|
|
22
|
-
is deployed.** The review reads the synthesized diff (what CloudFormation
|
|
23
|
-
will do), not just the source. Deploying around that agent's `HOLD` is a
|
|
24
|
-
Never-tier action.
|
|
25
|
-
- `cdk synth` stays region-agnostic and credential-free: synth must work on any
|
|
26
|
-
machine, in CI, with nothing configured.
|
|
27
|
-
- `RemovalPolicy` is always explicit. The skeleton ships DESTROY for easy
|
|
28
|
-
teardown; flip to RETAIN before real data arrives — that flip is a Tier-2
|
|
29
|
-
decision.
|
|
30
|
-
|
|
31
|
-
## Stacks: what may move, and what may never
|
|
32
|
-
|
|
33
|
-
- 🔴 **Never move a stateful construct between stacks.** A Table, Bucket, Secret
|
|
34
|
-
or user pool that changes stack is **deleted and recreated** by
|
|
35
|
-
CloudFormation — that is data loss, not a refactor, and it passes review as
|
|
36
|
-
"tidying" if nobody knows this rule. Stateful constructs stay put and are
|
|
37
|
-
referenced cross-stack.
|
|
38
|
-
- **A stack has a hard resource ceiling (500).** One HTTP route costs several
|
|
39
|
-
resources, so a growing API stack approaches it long before it looks big. When
|
|
40
|
-
it does, split out the least-coupled domain as a **stateless-only** stack —
|
|
41
|
-
functions, roles, routes — attached to the same API cross-stack, with a
|
|
42
|
-
**one-way** dependency. Splitting stateless costs nothing; splitting stateful
|
|
43
|
-
costs the data (above).
|
|
44
|
-
- **Fleet-wide function defaults live in one module, not in a stack.**
|
|
45
|
-
Architecture, runtime, `NODE_OPTIONS`, tracing: one edit there reaches every
|
|
46
|
-
function. A per-stack override of a fleet default is the thing to reject in
|
|
47
|
-
review — it is invisible from anywhere except that stack.
|
|
48
|
-
|
|
49
|
-
## IAM: least privilege, by construction
|
|
50
|
-
|
|
51
|
-
- Use the narrow grant for the operation actually performed
|
|
52
|
-
(`grantWriteData`, `grantSendMessages`) — never `grantFullAccess`, never
|
|
53
|
-
hand-rolled `*` policies.
|
|
54
|
-
- A new permission is added when a usecase needs it, in the same PR, with the
|
|
55
|
-
test asserting it.
|
|
56
|
-
|
|
57
|
-
## Queues: DLQ discipline
|
|
58
|
-
|
|
59
|
-
- Every queue gets a dead-letter queue (small `maxReceiveCount`) **and** an
|
|
60
|
-
alarm on DLQ depth. A queue without a DLQ is an unbounded retry loop.
|
|
61
|
-
- Consumers let poison messages throw. Catch-and-continue in a worker silently
|
|
62
|
-
deletes data — the DLQ + alarm exist precisely so failure is visible.
|
|
63
|
-
|
|
64
|
-
## DynamoDB: single-table, single owner
|
|
65
|
-
|
|
66
|
-
- One table, generic key names (`pk`, `sk`). Key construction and item shapes
|
|
67
|
-
live only in `packages/db` models — no other module composes a key string.
|
|
68
|
-
- Validate items on read (schema parse): the table is an external system, not
|
|
69
|
-
a trusted store.
|
|
70
|
-
- On-demand billing by default; provisioned capacity is a data-driven Tier-2
|
|
71
|
-
change.
|
|
72
|
-
|
|
73
|
-
## Lambda
|
|
74
|
-
|
|
75
|
-
- Each function has exactly one composition root entry file (`src/main.ts`,
|
|
76
|
-
`src/list-main.ts`, …): environment parsed with `loadEnv(zod)` at boot,
|
|
77
|
-
clients constructed once, handler exported. Nothing else imports an entry.
|
|
78
|
-
- Functions stay single-purpose — one route or one event source each. Fan-out
|
|
79
|
-
belongs to infrastructure (queues, topics), not to in-process branching.
|
|
80
|
-
- **SDK clients are constructed at module top level, never inside the handler.**
|
|
81
|
-
Containers are reused across warm invocations, so a client built in the handler
|
|
82
|
-
body is paid on every invocation and defeats connection reuse — the most common
|
|
83
|
-
Lambda performance bug, and invisible in tests because tests are always cold.
|
|
84
|
-
The same applies to anything expensive and stateless: build once per container.
|
|
85
|
-
- Environment is parsed **once, at module scope**, through `loadEnv(zod)`. A
|
|
86
|
-
misconfigured function then fails its cold start loudly instead of misbehaving
|
|
87
|
-
quietly per request, and no code below the entry file reads the environment
|
|
88
|
-
directly.
|
|
89
|
-
|
|
90
|
-
## When the deployed runtime misbehaves
|
|
91
|
-
|
|
92
|
-
Investigate before writing anything: the **`ro-debug` skill** has the read-only
|
|
93
|
-
recipes and, more usefully, the traps that have produced confident wrong
|
|
94
|
-
diagnoses — a stale local branch read as current, `UPDATE_COMPLETE` left over
|
|
95
|
-
from the previous deploy, an empty metric read as healthy when it means the
|
|
96
|
-
function was never invoked.
|
|
97
|
-
|
|
98
|
-
## Post-deploy verification (target-specific means)
|
|
99
|
-
|
|
100
|
-
The universal rule says verify runtime health after deploy; here the
|
|
101
|
-
implementation is the **`post-deploy-verify` skill** (`.claude/skills/`): stack
|
|
102
|
-
freshness, smoke the HTTP route (expect 201), confirm the worker consumed the
|
|
103
|
-
event, confirm the DLQ and its alarm are quiet — ending in the binary
|
|
104
|
-
HEALTHY / REGRESSION verdict. On regression: redeploy the previous revision
|
|
105
|
-
first, diagnose second.
|
|
@@ -1,105 +0,0 @@
|
|
|
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.
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: ro-debug
|
|
3
|
-
description: Investigate the deployed runtime read-only — Lambda logs and error rates, DLQ depth and message age, table reads — with the traps that have produced confidently-wrong diagnoses before. Use when something on the deployed stage misbehaves, and before writing any fix.
|
|
4
|
-
allowed-tools: Bash, Read, Grep
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# Read-only runtime investigation
|
|
8
|
-
|
|
9
|
-
A fix is always a code change through a PR. This skill only **looks** — and it is
|
|
10
|
-
scoped read-only so that a session diagnosing an incident cannot become a session
|
|
11
|
-
mutating production state under pressure, which is when that decision is worst.
|
|
12
|
-
|
|
13
|
-
## The role this assumes, and the honest caveat
|
|
14
|
-
|
|
15
|
-
Every command below wants a **read-only profile**: permission to read logs,
|
|
16
|
-
metrics, queue attributes and table items, and nothing else. No decrypt, no
|
|
17
|
-
secret reads, no mutations — so a credential cannot leak through this path even
|
|
18
|
-
by accident.
|
|
19
|
-
|
|
20
|
-
⚠ **The skeleton does not provision that role.** It ships the application, not
|
|
21
|
-
your account's access model, and minting a role is an **owner action** (a Tier-2
|
|
22
|
-
decision — it is IAM). Until it exists, either create it once with those four
|
|
23
|
-
read scopes, or accept that you are investigating with wider credentials than the
|
|
24
|
-
task needs and say so in the write-up. Do not silently upgrade to an admin
|
|
25
|
-
profile and carry on.
|
|
26
|
-
|
|
27
|
-
```sh
|
|
28
|
-
export AWS_PROFILE=<your read-only profile>
|
|
29
|
-
export AWS_REGION=__REGION__
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
## Before reading ANY code to explain a runtime behaviour
|
|
33
|
-
|
|
34
|
-
```sh
|
|
35
|
-
git fetch origin && git rev-parse HEAD "origin/$(git symbolic-ref --short HEAD)"
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
**If the local branch differs from its remote, diagnose from the remote**
|
|
39
|
-
(`git show origin/<branch>:<file>`, or a worktree). **A fetch does not move your
|
|
40
|
-
checkout** — reading stale local code while describing deployed behaviour is
|
|
41
|
-
the single most reliable way to produce a confident, wrong diagnosis, and it has
|
|
42
|
-
happened more than once. See `.claude/rules/autonomy.md`, "Session staleness".
|
|
43
|
-
|
|
44
|
-
## Recipes
|
|
45
|
-
|
|
46
|
-
**Find the function first** — CDK generates the physical names, so never guess one:
|
|
47
|
-
|
|
48
|
-
```sh
|
|
49
|
-
aws lambda list-functions \
|
|
50
|
-
--query "Functions[?contains(FunctionName,'Notes')].FunctionName" --output text
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
**Errors in a window:**
|
|
54
|
-
|
|
55
|
-
```sh
|
|
56
|
-
aws logs filter-log-events --log-group-name "/aws/lambda/<fn>" \
|
|
57
|
-
--start-time <epoch-ms> --filter-pattern "ERROR" \
|
|
58
|
-
--max-items 20 --query 'events[].message' --output text
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
**DLQ depth, then message age.** Depth comes from the queue; **age does not** —
|
|
62
|
-
`ApproximateAgeOfOldestMessage` is a **CloudWatch metric, not an SQS attribute**,
|
|
63
|
-
and asking for it as an attribute fails with `InvalidAttributeName`:
|
|
64
|
-
|
|
65
|
-
```sh
|
|
66
|
-
aws sqs get-queue-attributes --queue-url <notes-dlq-url> \
|
|
67
|
-
--attribute-names ApproximateNumberOfMessages
|
|
68
|
-
|
|
69
|
-
aws cloudwatch get-metric-statistics --namespace AWS/SQS \
|
|
70
|
-
--metric-name ApproximateAgeOfOldestMessage \
|
|
71
|
-
--dimensions Name=QueueName,Value=<notes-dlq-name> \
|
|
72
|
-
--start-time <iso> --end-time <iso> --period 300 --statistics Maximum
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
Age is what tells you whether a non-empty DLQ **predates** the thing you are
|
|
76
|
-
investigating. A days-old backlog is not your regression, and treating it as one
|
|
77
|
-
sends the whole diagnosis in the wrong direction.
|
|
78
|
-
|
|
79
|
-
**Table reads** — key by whatever `packages/db` composes; nothing else knows the
|
|
80
|
-
key shape:
|
|
81
|
-
|
|
82
|
-
```sh
|
|
83
|
-
aws dynamodb query --table-name <NotesTable output> \
|
|
84
|
-
--key-condition-expression "pk = :pk" \
|
|
85
|
-
--expression-attribute-values '{":pk":{"S":"NOTE#<id>"}}' --max-items 3
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
**Error rate:** `AWS/Lambda` `Errors`, `Sum`, by `FunctionName`, over the suspect
|
|
89
|
-
window.
|
|
90
|
-
|
|
91
|
-
## Interpretation rules — where wrong diagnoses actually come from
|
|
92
|
-
|
|
93
|
-
- 🔴 **An empty metric result is "no signal", never "healthy".** Zero datapoints
|
|
94
|
-
means **no invocations in the window** — the function was not exercised, so the
|
|
95
|
-
metric says nothing about whether it works. Report it as no signal and go find
|
|
96
|
-
a window with traffic. This is the same rule the `post-deploy-verify` skill
|
|
97
|
-
states, and for the same reason: a vacuous pass is worse than a missing one,
|
|
98
|
-
because it gets believed.
|
|
99
|
-
- 🔴 **`StackStatus: UPDATE_COMPLETE` is stale evidence.** It persists from the
|
|
100
|
-
previous deploy, so it is true of a stack whose latest deploy failed. The
|
|
101
|
-
authoritative signals are the **deploy job's conclusion**
|
|
102
|
-
(`gh run list --workflow deploy`) and the stack's `LastUpdatedTime` freshness.
|
|
103
|
-
- **DNS and asset-upload failures are infrastructure flakes, not code
|
|
104
|
-
regressions.** A deploy that died at asset publish or checkout tells you nothing
|
|
105
|
-
about the change. Your own read-only calls can hit the same flake — a failed
|
|
106
|
-
probe is `unknown`, not a finding.
|
|
107
|
-
- **Never work around the profile's denials — that is the point of the profile.**
|
|
108
|
-
Needing a decrypt, a secret or a mutation means the investigation has reached
|
|
109
|
-
its boundary: escalate to the human with what you found
|
|
110
|
-
(`.claude/rules/autonomy.md`, "Escalation format").
|
|
111
|
-
|
|
112
|
-
## What to hand back
|
|
113
|
-
|
|
114
|
-
What was observed (verbatim, not summarised), which window, which signals were
|
|
115
|
-
**unavailable** and why, the current hypothesis, and the narrowest reproduction.
|
|
116
|
-
A read-only investigation that ends in a named uncertainty is a good outcome; one
|
|
117
|
-
that ends in a confident story built on a stale read is not.
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
name = "cdk-diff-reviewer"
|
|
2
|
-
description = "Reviews an infrastructure change via `cdk diff` BEFORE any deploy. MUST run on every change under infra/ — a deploy without this review is a Never-tier action. Read-only; findings gate the deploy."
|
|
3
|
-
model = "gpt-5.6-sol"
|
|
4
|
-
model_reasoning_effort = "high"
|
|
5
|
-
sandbox_mode = "read-only"
|
|
6
|
-
developer_instructions = "You review what a deploy would actually do to running infrastructure. Your\ninput is the change under `infra/` **and** the synthesized diff (`cdk diff`,\nrun it yourself); your output is a verdict. You never fix and never deploy.\n\n## How you work\n\n1. Run `cdk diff` (and read the changed `infra/` sources for intent). The diff\n is the truth: review what CloudFormation will do, not what the TypeScript\n looks like it does.\n2. Walk every resource change and flag it **by named rule** (below). Findings\n come as **BLOCKERS first, then nits** — one list each, with the resource\n and the rule it violates.\n3. Your message IS the review, not a summary of it: every finding carries the\n resource, the change, the rule, and the smallest fix. End with the verdict\n block below — `SHIP` where `DEPLOY: OK` used to be, `HOLD` where\n `DEPLOY: BLOCKED` did.\n\n## Named rules — blockers\n\n- **IAM broadening.** Any policy gaining actions, resources widening to `*`,\n or a grant that outruns what a usecase does today. Least privilege is added\n in the same PR as the need, never \"for later\".\n- **Data loss paths.** A stateful resource (table, bucket, queue) being\n replaced, deleted, or flipping its RemovalPolicy toward DESTROY.\n Logical-id renames on stateful resources are replacements in disguise.\n- **Safety-net removal.** A DLQ detached, an alarm deleted or loosened, a\n retry budget widened to infinity, a dead-letter retention shortened.\n- **Blast-radius growth.** New public surface (endpoints, permissions to\n external principals), broadened network access, cross-stack exports that\n make future changes harder to reverse.\n- **Cost-relevant flips.** On-demand → provisioned capacity, log retention to\n \"forever\", memory/timeout jumps with no stated reason.\n\n## Nits (report, do not block)\n\nNaming drift, missing descriptions, constructs that could use the narrower\ngrant helper, duplication between stacks.\n\n## Boundaries\n\n- Read-only: you run `cdk diff` and read code; you never run `cdk deploy`,\n never edit files, never mutate AWS state.\n- An empty diff is a real finding too — say \"no infrastructure change\" and\n return `SHIP`, so the gate leaves a trace either way.\n\n## The verdict block\n\nEnd your report with **exactly one** fenced `json` block of this shape, and\nnothing after it. It is what the calling gate reads.\n\n```json\n{\n \"gate\": \"cdk-diff-reviewer\",\n \"verdict\": \"HOLD\",\n \"blockers\": [\n {\n \"file\": \"infra/lib/api-stack.ts\",\n \"line\": 88,\n \"rule\": \"data loss\",\n \"note\": \"the table's RemovalPolicy went to DESTROY — replacement drops it\"\n }\n ],\n \"advisories\": [],\n \"evidence\": [\"cdk diff against the deployed stage\"],\n \"headSha\": \"9c1f0a7d4b3e2c5a8f6d0b9e7c4a1f2d3e5b6c70\"\n}\n```\n\n- `verdict` is `SHIP` (nothing blocking, including an empty diff), `HOLD`, or\n `NOT_APPLICABLE` when the change touches no infrastructure at all.\n- Every blocker names the `rule` it violates, with `file` and `line` when it has\n a location and neither when it does not.\n- A `HOLD` naming no blocker is **refused**, and so is a `SHIP` carrying one:\n `node .claude/scripts/verdict.mjs check <report> cdk-diff-reviewer` is what\n refuses them.\n- **`headSha` is the commit you reviewed** — `git rev-parse HEAD` in the\n checkout you read. It is what lets `node .claude/scripts/verdict.mjs coverage\n <commit>` tell \"this gate answered for the commit being merged\" from \"it\n answered two pushes ago\". A verdict naming no commit is counted as neither\n covered nor missing, so `pr-ship` holds on it — and only `pr-ship`: no hook\n runs that check, so a session that skips the gate skips this with it."
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
["pnpm lint", "pnpm typecheck", "pnpm test"]
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
# Stack rules — Node + TypeScript
|
|
2
|
-
|
|
3
|
-
Conventions for this runtime. The universal rules say *what* the boundaries
|
|
4
|
-
are; this file says how they are expressed in TypeScript.
|
|
5
|
-
|
|
6
|
-
## Language
|
|
7
|
-
|
|
8
|
-
- Strict TypeScript everywhere: `strict`, `noUncheckedIndexedAccess`. Lint and
|
|
9
|
-
typecheck are gates, not advice.
|
|
10
|
-
- ESM with NodeNext resolution — relative imports carry the `.js` suffix.
|
|
11
|
-
- `any` is a code smell; `unknown` plus narrowing is the tool. Casts are rare
|
|
12
|
-
and always commented with why.
|
|
13
|
-
- Schemas (zod) sit at every boundary where outside data enters: transport
|
|
14
|
-
payloads, queue messages, environment, storage reads.
|
|
15
|
-
|
|
16
|
-
## Dependencies
|
|
17
|
-
|
|
18
|
-
- Zero-dependency bias: reach for `node:` builtins first. Every new runtime
|
|
19
|
-
dependency is a Tier-2 decision (see `autonomy.md`) — it must be argued for,
|
|
20
|
-
not just installed.
|
|
21
|
-
- Workspace packages export TypeScript source directly (`main: src/index.ts`);
|
|
22
|
-
services bundle at their edge. No internal build step, no `dist/` juggling.
|
|
23
|
-
|
|
24
|
-
## Testing (vitest)
|
|
25
|
-
|
|
26
|
-
- Tests live in each package's `test/`, named `*.test.ts`, and describe
|
|
27
|
-
behavior ("refuses an empty title"), not method names.
|
|
28
|
-
- Test doubles are hand-written structural stubs against consumer-owned
|
|
29
|
-
interfaces — no mocking framework, no patching of module internals.
|
|
30
|
-
- Determinism is non-negotiable: inject the clock and id generation (the pure
|
|
31
|
-
core already forces this). A test that needs a sleep is a design smell.
|
|
32
|
-
|
|
33
|
-
## Errors and logging
|
|
34
|
-
|
|
35
|
-
- Everything that crosses a layer is a typed error from `@…/shared`
|
|
36
|
-
(`AppError` and friends); handlers map types to transport codes, never
|
|
37
|
-
string-match messages.
|
|
38
|
-
- Logs are structured JSON lines through the shared logger — no bare
|
|
39
|
-
`console.log` in service code.
|
|
40
|
-
|
|
41
|
-
## Confirming the merge criterion (GitHub Actions)
|
|
42
|
-
|
|
43
|
-
`workflow.md` states the criterion provider-neutrally: confirm the required
|
|
44
|
-
check completed **for this commit**. Here that is concrete — and it matters
|
|
45
|
-
because `gh pr checks --watch` can exit successfully while checks are still
|
|
46
|
-
*unregistered*, reporting a green wall that has not been built yet.
|
|
47
|
-
|
|
48
|
-
Poll the check runs for the PR's head SHA and require the named check to have
|
|
49
|
-
`conclusion: success`, not merely "not failing":
|
|
50
|
-
|
|
51
|
-
```sh
|
|
52
|
-
SHA=$(gh pr view --json headRefOid -q .headRefOid)
|
|
53
|
-
gh api "repos/{owner}/{repo}/commits/$SHA/check-runs" \
|
|
54
|
-
-q '.check_runs[] | select(.name=="ci") | .conclusion'
|
|
55
|
-
# must print: success (a result set containing only a scanner is NOT done)
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
**A head that gets no run at all is a third state, not a slow one.** A
|
|
59
|
-
`pull_request` push can register no workflow run and emit no failure signal —
|
|
60
|
-
the head sits with a scanner only, and the poll above waits forever while the
|
|
61
|
-
previous head's green sits one line up in the same PR. Tell "not registered"
|
|
62
|
-
apart from "pending" by asking for runs by head, not by PR:
|
|
63
|
-
|
|
64
|
-
```sh
|
|
65
|
-
gh api "repos/{owner}/{repo}/actions/runs?head_sha=$SHA" \
|
|
66
|
-
-q '.workflow_runs[] | "\(.name) \(.event) \(.status) \(.conclusion)"'
|
|
67
|
-
# one line per workflow that ran for THIS sha; a required workflow missing
|
|
68
|
-
# here after a bounded wait (a few minutes) is not registered, not pending
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
⚠ The names differ in case between the two queries: `workflow_runs[].name`
|
|
72
|
-
is the **workflow** name (`CI`, `E2E`), the check-runs filter above reads the
|
|
73
|
-
**job** name (`ci`, `e2e`). Searching the runs list for `ci` finds nothing.
|
|
74
|
-
|
|
75
|
-
Then the rule, **per required check by name**, never per head: a check with
|
|
76
|
-
no run after the wait is **retriggered**, and the PR is **never merged on an
|
|
77
|
-
older head's green**. The retrigger depends on the workflow — `ci.yml` has no
|
|
78
|
-
`workflow_dispatch`, so an empty commit (`git commit --allow-empty`) is the
|
|
79
|
-
simplest trigger it has; `e2e.yml` has one, so
|
|
80
|
-
`gh workflow run e2e.yml --ref <branch>` re-runs it on the same head. Pinned
|
|
81
|
-
in the generator's `test/template/pr-flow.test.ts` — absent in a generated
|
|
82
|
-
rig — › "node-ts names the head that gets no run at all, and says it is
|
|
83
|
-
retriggered rather than waited on" and › "the no-run branch is stated per
|
|
84
|
-
required check, not per head".
|
|
85
|
-
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
// PreToolUse hook: the domain core stays pure — no I/O, no clock, no randomness,
|
|
2
|
-
// no environment. This is enforced here, at the tool layer, not requested in prose:
|
|
3
|
-
// an agent (or a human using the agent) cannot write an impure line into
|
|
4
|
-
// packages/core/src/ even if it wants to.
|
|
5
|
-
//
|
|
6
|
-
// Contract (Claude Code and Codex): JSON on stdin; exit 0 = allow, exit 2 = block, and
|
|
7
|
-
// stderr is shown to the agent as the reason.
|
|
8
|
-
// Generator-owned coverage for the neutral bounded-inspection refusal lives upstream in
|
|
9
|
-
// codex.test.ts (absent in a generated rig) › "$guard blocks with a neutral,
|
|
10
|
-
// actionable size-limit refusal" — a downstream edit requires a local replacement test.
|
|
11
|
-
import { editFragments } from './lib/edit-input.mjs';
|
|
12
|
-
import { readHookInput } from './lib/hook-input.mjs';
|
|
13
|
-
|
|
14
|
-
/** The only non-relative import the core may use: its schema/validation library. */
|
|
15
|
-
const ALLOWED_PACKAGES = ['zod'];
|
|
16
|
-
|
|
17
|
-
const CORE_PATH = /(^|\/)packages\/core\/src\//;
|
|
18
|
-
const CODE_FILE = /\.(ts|tsx|js|jsx|mjs|cjs)$/;
|
|
19
|
-
|
|
20
|
-
const BANNED = [
|
|
21
|
-
[/\bprocess\.env\b/, 'reads the environment — pass values in through the usecase layer'],
|
|
22
|
-
[/\bprocess\.\w+/, 'touches the process — the core must not know it runs in one'],
|
|
23
|
-
[/\bDate\.now\s*\(/, 'reads the clock — take a timestamp as an argument'],
|
|
24
|
-
[/\bnew\s+Date\s*\(/, 'reads the clock — take a timestamp as an argument'],
|
|
25
|
-
[/\bMath\.random\s*\(/, 'uses randomness — take generated values as arguments'],
|
|
26
|
-
[/\bcrypto\.randomUUID\s*\(/, 'uses randomness — take generated ids as arguments'],
|
|
27
|
-
[/\bset(?:Timeout|Interval)\s*\(/, 'schedules work — the core is synchronous and pure'],
|
|
28
|
-
[/\bfetch\s*\(/, 'performs network I/O — that belongs to an adapter'],
|
|
29
|
-
];
|
|
30
|
-
|
|
31
|
-
function main() {
|
|
32
|
-
const input = readHookInput();
|
|
33
|
-
if (input === null) return 0; // unparseable payload: not ours to judge
|
|
34
|
-
const fragments = editFragments(input);
|
|
35
|
-
const blocked = fragments.find(
|
|
36
|
-
({ inspectionRefusal, appliesToAll }) => appliesToAll && inspectionRefusal,
|
|
37
|
-
);
|
|
38
|
-
const globalRefusal = blocked?.inspectionRefusal;
|
|
39
|
-
if (globalRefusal) {
|
|
40
|
-
process.stderr.write(
|
|
41
|
-
`BLOCKED — cannot safely inspect this edit: ${globalRefusal}\n` +
|
|
42
|
-
// The remedy has to match the refusal: splitting cannot change a
|
|
43
|
-
// container shape, and a fixed line sent the agent into a retry loop
|
|
44
|
-
// on the one path it could not retry out of.
|
|
45
|
-
`${blocked.remedy ?? 'Split it into a smaller patch and retry.'}\n`,
|
|
46
|
-
);
|
|
47
|
-
return 2;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const violations = fragments.flatMap(({ filePath, fragment, inspectionRefusal }) => {
|
|
51
|
-
if (!CORE_PATH.test(filePath) || !CODE_FILE.test(filePath)) return [];
|
|
52
|
-
if (inspectionRefusal) return [`cannot safely inspect this move — ${inspectionRefusal}`];
|
|
53
|
-
return findViolations(fragment);
|
|
54
|
-
});
|
|
55
|
-
if (violations.length === 0) return 0;
|
|
56
|
-
|
|
57
|
-
process.stderr.write(
|
|
58
|
-
`BLOCKED — packages/core is a pure module and this change breaks its purity:\n` +
|
|
59
|
-
violations.map((v) => ` - ${v}`).join('\n') +
|
|
60
|
-
`\nMove the impure part behind the usecase layer or into an adapter ` +
|
|
61
|
-
`(see .claude/rules/architecture.md).\n`,
|
|
62
|
-
);
|
|
63
|
-
return 2;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export function findViolations(source) {
|
|
67
|
-
// `import type …` disappears at compile time — purity is untouched.
|
|
68
|
-
const withoutTypeImports = source.replace(/^\s*import\s+type\s[^\n]*$/gm, '');
|
|
69
|
-
const violations = [];
|
|
70
|
-
|
|
71
|
-
const importRe = /(?:\bfrom\s*|\bimport\s*\(\s*|\brequire\s*\(\s*|^\s*import\s+)['"]([^'"]+)['"]/gm;
|
|
72
|
-
for (const match of withoutTypeImports.matchAll(importRe)) {
|
|
73
|
-
const spec = match[1];
|
|
74
|
-
if (spec.startsWith('.')) continue;
|
|
75
|
-
const allowed = ALLOWED_PACKAGES.some((p) => spec === p || spec.startsWith(`${p}/`));
|
|
76
|
-
if (!allowed) {
|
|
77
|
-
violations.push(
|
|
78
|
-
`imports "${spec}" — the core may import only its own modules and: ${ALLOWED_PACKAGES.join(', ')}`,
|
|
79
|
-
);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
for (const [pattern, reason] of BANNED) {
|
|
84
|
-
if (pattern.test(withoutTypeImports)) violations.push(reason);
|
|
85
|
-
}
|
|
86
|
-
return violations;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
process.exit(main());
|