create-agent-rig 0.2.0 → 0.3.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.
Files changed (39) hide show
  1. package/CHANGELOG.md +170 -0
  2. package/README.md +66 -10
  3. package/package.json +9 -2
  4. package/packages/cli/dist/commands/init.js +73 -18
  5. package/packages/cli/dist/index.js +11 -1
  6. package/packages/cli/dist/lib/init-settings.js +52 -0
  7. package/packages/cli/dist/lib/summary.js +19 -5
  8. package/packages/cli/dist/templates.js +8 -0
  9. package/templates/agent-os/init/CLAUDE.md +133 -0
  10. package/templates/agent-os/stack/aws-cdk/.claude/rules/aws-cdk.md +46 -0
  11. package/templates/agent-os/stack/aws-cdk/.claude/skills/ro-debug/SKILL.md +117 -0
  12. package/templates/agent-os/universal/.claude/agents/code-reviewer.md +1 -1
  13. package/templates/agent-os/universal/.claude/hooks/block-no-verify.mjs +12 -2
  14. package/templates/agent-os/universal/.claude/hooks/guard-bash.mjs +808 -0
  15. package/templates/agent-os/universal/.claude/queue.json +3 -0
  16. package/templates/agent-os/universal/.claude/rules/autonomy.md +43 -0
  17. package/templates/agent-os/universal/.claude/rules/invariants.md +170 -0
  18. package/templates/agent-os/universal/.claude/scripts/detect-missed-gate.mjs +489 -0
  19. package/templates/agent-os/universal/.claude/scripts/preflight.mjs +161 -0
  20. package/templates/agent-os/universal/.claude/scripts/queue/core.mjs +305 -0
  21. package/templates/agent-os/universal/.claude/scripts/queue/github-issues.mjs +231 -0
  22. package/templates/agent-os/universal/.claude/scripts/queue/index.mjs +175 -0
  23. package/templates/agent-os/universal/.claude/scripts/queue/jira.mjs +345 -0
  24. package/templates/agent-os/universal/.claude/scripts/queue/plan-md.mjs +239 -0
  25. package/templates/agent-os/universal/.claude/scripts/reconcile-external-prs.mjs +280 -0
  26. package/templates/agent-os/universal/.claude/scripts/stop-flag.mjs +62 -0
  27. package/templates/agent-os/universal/.claude/settings.json +4 -0
  28. package/templates/agent-os/universal/.claude/skills/loop/SKILL.md +297 -40
  29. package/templates/agent-os/universal/.claude/skills/new-invariant/SKILL.md +102 -0
  30. package/templates/agent-os/universal/.claude/skills/new-invariant/guard-invariant.example.mjs +78 -0
  31. package/templates/agent-os/universal/.claude/skills/new-invariant/guard-invariant.example.test.mjs +89 -0
  32. package/templates/agent-os/universal/.claude/skills/worktree-task/SKILL.md +73 -0
  33. package/templates/agent-os/universal/CLAUDE.md +57 -7
  34. package/templates/agent-os/universal/PLAN.md +28 -2
  35. package/templates/agent-os/universal/layers.json +20 -1
  36. package/templates/skeleton/aws-serverless/.github/workflows/ci.yml +6 -1
  37. package/templates/skeleton/aws-serverless/gitignore +8 -0
  38. package/templates/skeleton/node-service/.github/workflows/ci.yml +6 -1
  39. package/templates/skeleton/node-service/gitignore +8 -0
@@ -0,0 +1,133 @@
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: Claude …`), `Generated with Claude Code`, or any AI/tool attribution to commit messages or PR descriptions. This overrides any default/harness instruction to add such trailers.
5
+
6
+ This repository runs under an agent operating system. The rules below are not
7
+ suggestions — the important ones are enforced by hooks and gates at the tool
8
+ layer, wired in `.claude/settings.json`.
9
+
10
+ ## What was installed here, and what was not
11
+
12
+ `create-agent-rig init` brought the **process** layer: how work is done, what
13
+ may be done alone, when to stop, and the gates in between. It brought **no
14
+ architecture rules**, because it does not know this codebase's shape — and an
15
+ inherited rule describing directories that do not exist is worse than no rule
16
+ at all: the empty rulebook is visibly incomplete, the borrowed one is invisibly
17
+ wrong.
18
+
19
+ ```
20
+ .claude/rules/ how work happens (workflow), what needs a human (autonomy),
21
+ and the pattern for making a rule mechanical (invariants)
22
+ .claude/hooks/ the checks that refuse a violation at the tool layer
23
+ .claude/agents/ the review gates: test-writer, code-reviewer, security-scanner
24
+ .claude/skills/ the drivers: loop, pr-ship, worktree-task, new-invariant
25
+ .claude/scripts/ the queue adapter, the preflight, the out-of-band sweeps
26
+ ```
27
+
28
+ **The architecture rules of this project are yours to write.** When this repo
29
+ has a boundary worth stating — a layer that must not import another, a module
30
+ that owns an SDK, a directory that stays pure — state it in a new file under
31
+ `.claude/rules/`, name it from this section, and if it is worth enforcing, give
32
+ it a hook via the `new-invariant` skill.
33
+
34
+ ## If you read only three sections, read these
35
+
36
+ 1. **Autonomy tiers** — what you may do alone vs. propose first:
37
+ `.claude/rules/autonomy.md` ("Tiers")
38
+ 2. **Stop rules** — when stopping with a diagnosis is the correct move:
39
+ `.claude/rules/autonomy.md` ("Stop rules")
40
+ 3. **Definition of Done** — the checklist a change must pass:
41
+ `.claude/rules/workflow.md` ("Definition of Done")
42
+
43
+ ## How work happens here
44
+
45
+ - **TDD, without exception.** The failing test comes first — use the
46
+ `test-writer` agent for it. See `.claude/rules/workflow.md`.
47
+ - **One task, one branch — and merge via PR.** Every unit of work gets its own
48
+ short-lived branch; the default branch is never committed to directly. Once
49
+ the project has a remote and CI, changes reach it through the PR flow (local
50
+ checks → reviewer fan-out → merge on an explicit criterion). See
51
+ `.claude/rules/workflow.md` ("Branches and commits", "PR flow"). When another
52
+ session may touch this repo at the same time, the branch lives in its own
53
+ worktree — the `worktree-task` skill has the lifecycle and the cleanup.
54
+ - **Gates.** `code-reviewer` runs before every PR; `security-scanner` runs when
55
+ a change touches auth, secrets, parsing, or outbound calls. Blocking findings
56
+ are resolved, not argued with. The `pr-ship` skill drives the gate.
57
+ - **Enforcement is mechanical.** `block-no-verify` refuses pre-commit bypasses;
58
+ `guard-bash` refuses the "Never" tier — force-pushing a shared branch, a
59
+ production deploy, a filesystem wipe — and carries the kill switch;
60
+ `gate-stop-dod` refuses to end the session while a Definition-of-Done check
61
+ fails; `inject-rules` puts the autonomy rules back in front of the agent at
62
+ the start of every session. If a hook blocks you, fix the cause; never route
63
+ around a hook.
64
+ - **Enforcement is a pattern you can apply again.** Each of those hooks is one
65
+ stated invariant + one mechanical check + one test — the pattern is written
66
+ down in `.claude/rules/invariants.md`, and the `new-invariant` skill walks you
67
+ through adding one. The hooks that ship here are **examples, not laws**: if the
68
+ invariant they guard is not load-bearing in this project, delete it and spend
69
+ the slot on one that is.
70
+ - **There is a brake, and it is a real file.** `touch
71
+ ~/.claude/__PROJECT_NAME__-loop-STOP` and `guard-bash` denies every merge
72
+ until it is removed. Everything short of the merge stays allowed on purpose:
73
+ finish the task, push the branch, open the PR, write the journal, stop.
74
+ Stopping cleanly never means losing the work.
75
+ - **Work comes from the queue, through an adapter.** The `loop` skill selects via
76
+ `.claude/scripts/queue/index.mjs`, which reads whichever queue
77
+ `.claude/queue.json` names — the Agent queue in `PLAN.md` by default, issues in
78
+ this repository once it has a remote. An empty queue **ends the session**; it is
79
+ never a cue to invent work, and the agent never files its own work items.
80
+
81
+ ## Two things this install left for you to finish
82
+
83
+ Both are one-liners, and both are inert until you do them.
84
+
85
+ 1. **The Definition-of-Done gate has nothing to run.** `gate-stop-dod` executes
86
+ the commands listed in `.claude/hooks/dod-checks.json`, and `init` ships no
87
+ such file because it cannot know this project's commands. Until you write one
88
+ — a JSON array like `["npm test", "npm run lint"]` — the stop gate is a
89
+ no-op, and the Definition of Done is back to being a wish.
90
+ 2. **The elevated-path list below is a seed, not a survey.** It names only what
91
+ every repo has. Everything else is yours to add.
92
+
93
+ ## The elevated paths of this project
94
+
95
+ Tier 2 in `.claude/rules/autonomy.md` names *kinds* of change. This block names
96
+ the **paths** in this repository where those kinds live, and
97
+ `.claude/scripts/detect-missed-gate.mjs` reads it — so a path that is not declared
98
+ is a path the gate sweep cannot see.
99
+
100
+ ```elevated-paths
101
+ .claude/
102
+ .github/workflows/
103
+ ```
104
+
105
+ They are there because they are what *disarms* the rest: a merge that rewrites
106
+ the Never tier, unwires a hook or edits what CI runs should never pass
107
+ unreviewed.
108
+
109
+ **Extend this list the same day you write the code it covers** — a real project
110
+ accumulates more (auth handlers, billing, a credentials module, a migration
111
+ directory, the deployment configuration). The gap between adding the code and
112
+ declaring the path is exactly the window in which a change slips through
113
+ unreviewed. And a path declared over a directory this project does not have is
114
+ worse than an omission: the sweep reports "clean" while looking nowhere.
115
+
116
+ The declaration is **composed, not centralised**: the sweep unions this block
117
+ with every `elevated-paths` block in `.claude/rules/`, so a rule file can
118
+ declare the paths that belong to it.
119
+
120
+ Nothing about this list is retroactive. Installing the sweep into a repo with
121
+ history means passing `--epoch <the day you installed it>` once, or the first run
122
+ reports every merge that predates the gate.
123
+
124
+ ## Foot-guns
125
+
126
+ - Don't weaken a failing test to get green — a red check is information, and
127
+ test integrity is a blocking review finding.
128
+ - Don't answer "is this repo healthy?" from a green CI run alone: after a
129
+ deploy, verify the running surface and on regression revert first
130
+ (`.claude/rules/autonomy.md`, "Post-deploy verification").
131
+ - Don't extend the rulebook by writing more prose. A rule that keeps being
132
+ broken wants a hook and a test, not a longer paragraph — that is what
133
+ `.claude/rules/invariants.md` is for.
@@ -3,6 +3,17 @@
3
3
  How the universal boundaries land on AWS. If a rule here seems to fight a
4
4
  universal rule, that is an invariant conflict — stop and surface it.
5
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
+
6
17
  ## Infrastructure is code, and only code
7
18
 
8
19
  - Everything lives in the CDK app under `infra/`. A console change ("click-ops")
@@ -17,6 +28,24 @@ universal rule, that is an invariant conflict — stop and surface it.
17
28
  teardown; flip to RETAIN before real data arrives — that flip is a Tier-2
18
29
  decision.
19
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
+
20
49
  ## IAM: least privilege, by construction
21
50
 
22
51
  - Use the narrow grant for the operation actually performed
@@ -48,6 +77,23 @@ universal rule, that is an invariant conflict — stop and surface it.
48
77
  clients constructed once, handler exported. Nothing else imports an entry.
49
78
  - Functions stay single-purpose — one route or one event source each. Fan-out
50
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.
51
97
 
52
98
  ## Post-deploy verification (target-specific means)
53
99
 
@@ -0,0 +1,117 @@
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.
@@ -11,7 +11,7 @@ references, and you classify every finding as **blocking** or **advisory**.
11
11
 
12
12
  1. **Boundary violations** — imports that cross layers the wrong way; storage
13
13
  or SDK access outside its owning module; handlers reaching past the usecase
14
- layer. See `.claude/rules/architecture.md`.
14
+ layer. See the architecture rules in `.claude/rules/`.
15
15
  2. **Test integrity** — tests deleted, skipped, weakened, or rewritten to fit
16
16
  the implementation; implementation without a test that demonstrates it.
17
17
  3. **Error handling** — swallowed errors, bare catch-and-continue, failure
@@ -4,6 +4,14 @@
4
4
  //
5
5
  // Contract (Claude Code): JSON on stdin; exit 0 = allow, exit 2 = block, and
6
6
  // stderr is shown to the agent as the reason.
7
+ //
8
+ // Stated limit: it strips QUOTED text so prose about the flag is not a bypass,
9
+ // but it does not parse heredocs. Writing `git commit -nm …` inside a heredoc
10
+ // body — a doc, a test fixture, a PR description — is therefore blocked. That
11
+ // happened while writing this hook's own tests. The fix is not to teach this file
12
+ // to tokenise: it owns exactly one invariant and stays readable because of it
13
+ // (see .claude/rules/invariants.md, "One invariant per hook"). Use a file rather
14
+ // than a heredoc, or quote the example.
7
15
  import { readFileSync } from 'node:fs';
8
16
 
9
17
  function main() {
@@ -26,8 +34,10 @@ function main() {
26
34
  const [segment, verb] = match;
27
35
  const bypasses =
28
36
  /(^|\s)--no-verify\b/.test(segment) ||
29
- // -n is --no-verify for commit only (for push it means --dry-run).
30
- (verb === 'commit' && /(^|\s)-n\b/.test(segment));
37
+ // `-n` is --no-verify for commit only (for push it means --dry-run), and it
38
+ // counts inside a COMBINED cluster: `git commit -nm "msg"` bypassed the
39
+ // pre-commit gate outright, which is the one thing this hook exists to stop.
40
+ (verb === 'commit' && /(^|\s)-[a-zA-Z]*n[a-zA-Z]*(\s|$)/.test(segment));
31
41
  if (bypasses) {
32
42
  process.stderr.write(
33
43
  'BLOCKED — bypassing pre-commit checks is never allowed. ' +