create-agent-rig 0.1.0 → 0.3.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 +134 -0
- package/README.md +112 -30
- package/package.json +9 -2
- package/packages/cli/dist/commands/create.js +8 -2
- package/packages/cli/dist/commands/init.js +72 -0
- package/packages/cli/dist/index.js +44 -2
- package/packages/cli/dist/lib/copy-tree.js +2 -0
- package/packages/cli/dist/lib/summary.js +19 -5
- package/templates/agent-os/stack/aws-cdk/.claude/rules/aws-cdk.md +46 -0
- package/templates/agent-os/stack/aws-cdk/.claude/skills/post-deploy-verify/SKILL.md +24 -11
- package/templates/agent-os/stack/aws-cdk/.claude/skills/ro-debug/SKILL.md +117 -0
- package/templates/agent-os/stack/node-ts/.claude/hooks/dod-checks.json +1 -0
- package/templates/agent-os/stack/node-ts/.claude/rules/node-ts.md +18 -0
- package/templates/agent-os/universal/.claude/hooks/block-no-verify.mjs +12 -2
- package/templates/agent-os/universal/.claude/hooks/gate-stop-dod.mjs +66 -0
- package/templates/agent-os/universal/.claude/hooks/guard-bash.mjs +808 -0
- package/templates/agent-os/universal/.claude/hooks/inject-rules.mjs +36 -0
- package/templates/agent-os/universal/.claude/queue.json +3 -0
- package/templates/agent-os/universal/.claude/rules/architecture.md +7 -0
- package/templates/agent-os/universal/.claude/rules/autonomy.md +43 -0
- package/templates/agent-os/universal/.claude/rules/invariants.md +169 -0
- package/templates/agent-os/universal/.claude/rules/workflow.md +33 -7
- package/templates/agent-os/universal/.claude/scripts/detect-missed-gate.mjs +489 -0
- package/templates/agent-os/universal/.claude/scripts/preflight.mjs +161 -0
- package/templates/agent-os/universal/.claude/scripts/queue/core.mjs +305 -0
- package/templates/agent-os/universal/.claude/scripts/queue/github-issues.mjs +231 -0
- package/templates/agent-os/universal/.claude/scripts/queue/index.mjs +175 -0
- package/templates/agent-os/universal/.claude/scripts/queue/jira.mjs +345 -0
- package/templates/agent-os/universal/.claude/scripts/queue/plan-md.mjs +239 -0
- package/templates/agent-os/universal/.claude/scripts/reconcile-external-prs.mjs +280 -0
- package/templates/agent-os/universal/.claude/scripts/stop-flag.mjs +62 -0
- package/templates/agent-os/universal/.claude/settings.json +24 -0
- package/templates/agent-os/universal/.claude/skills/loop/SKILL.md +302 -0
- package/templates/agent-os/universal/.claude/skills/new-invariant/SKILL.md +102 -0
- package/templates/agent-os/universal/.claude/skills/new-invariant/guard-invariant.example.mjs +78 -0
- package/templates/agent-os/universal/.claude/skills/new-invariant/guard-invariant.example.test.mjs +89 -0
- package/templates/agent-os/universal/.claude/skills/worktree-task/SKILL.md +73 -0
- package/templates/agent-os/universal/CLAUDE.md +64 -4
- package/templates/agent-os/universal/PLAN.md +53 -0
- package/templates/agent-os/universal/layers.json +40 -0
- package/templates/skeleton/aws-serverless/.github/workflows/ci.yml +6 -1
- package/templates/skeleton/aws-serverless/.github/workflows/deploy.yml +57 -0
- package/templates/skeleton/aws-serverless/README.md +28 -7
- package/templates/skeleton/aws-serverless/gitignore +8 -0
- package/templates/skeleton/node-service/.github/workflows/ci.yml +6 -1
- package/templates/skeleton/node-service/.github/workflows/deploy.yml +29 -0
- package/templates/skeleton/node-service/README.md +21 -1
- package/templates/skeleton/node-service/gitignore +9 -0
- package/templates/skeleton/node-service/package.json +4 -2
- package/templates/skeleton/node-service/pnpm-lock.yaml +3 -0
- package/templates/skeleton/node-service/scripts/build-artifact.mjs +34 -0
- package/templates/skeleton/node-service/services/api/test/artifact.test.ts +93 -0
|
@@ -10,24 +10,37 @@ You verify runtime health after a deploy and return a **verdict**, not a vibe.
|
|
|
10
10
|
You are read-only: you observe, you never fix. The autonomy rules
|
|
11
11
|
(`.claude/rules/autonomy.md`, "Post-deploy verification") consume your verdict.
|
|
12
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
|
+
|
|
13
16
|
## Steps — evidence for each, in order
|
|
14
17
|
|
|
15
|
-
1. **
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
|
22
28
|
command). Expect the documented success response (201 with a body).
|
|
23
|
-
|
|
29
|
+
4. **The async path.** Confirm the worker consumed the event this smoke
|
|
24
30
|
produced: `aws logs filter-log-events` on the worker's log group for the
|
|
25
31
|
processed-marker within the last few minutes.
|
|
26
|
-
|
|
32
|
+
5. **Queue discipline.** The skeleton's DLQ is empty and its alarm is quiet:
|
|
27
33
|
`aws sqs get-queue-attributes` (ApproximateNumberOfMessages = 0) and
|
|
28
34
|
`aws cloudwatch describe-alarms` (state OK, not ALARM).
|
|
29
|
-
|
|
30
|
-
|
|
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.
|
|
31
44
|
|
|
32
45
|
## Verdict — the only two answers
|
|
33
46
|
|
|
@@ -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.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
["pnpm lint", "pnpm typecheck", "pnpm test"]
|
|
@@ -37,3 +37,21 @@ are; this file says how they are expressed in TypeScript.
|
|
|
37
37
|
string-match messages.
|
|
38
38
|
- Logs are structured JSON lines through the shared logger — no bare
|
|
39
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
|
+
|
|
@@ -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
|
-
//
|
|
30
|
-
|
|
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. ' +
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// Stop hook: the Definition of Done as a mechanical gate. The session may not
|
|
2
|
+
// end while a named DoD check fails — the checklist stops being a wish.
|
|
3
|
+
//
|
|
4
|
+
// The stack layer supplies the checks (.claude/hooks/dod-checks.json — an
|
|
5
|
+
// array of shell commands, cheap and deterministic). Universal supplies only
|
|
6
|
+
// the mechanism: no config → nothing to gate.
|
|
7
|
+
//
|
|
8
|
+
// Anti-loop discipline (the classic Stop-hook trap):
|
|
9
|
+
// - stop_hook_active in the payload means we already blocked this stop once
|
|
10
|
+
// — never block again, or an agent that cannot go green spins forever;
|
|
11
|
+
// - a clean git tree stops instantly: nothing changed, nothing to gate;
|
|
12
|
+
// - fail open on any hook error — a crashed gate must not make the session
|
|
13
|
+
// unquittable.
|
|
14
|
+
import { execSync } from 'node:child_process';
|
|
15
|
+
import { readFileSync } from 'node:fs';
|
|
16
|
+
|
|
17
|
+
function main() {
|
|
18
|
+
let input;
|
|
19
|
+
try {
|
|
20
|
+
input = JSON.parse(readFileSync(0, 'utf8'));
|
|
21
|
+
} catch {
|
|
22
|
+
return 0;
|
|
23
|
+
}
|
|
24
|
+
if (input.hook_event_name !== 'Stop' && input.hook_event_name !== 'SubagentStop') return 0;
|
|
25
|
+
if (input.stop_hook_active) return 0;
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const status = execSync('git status --porcelain', {
|
|
29
|
+
encoding: 'utf8',
|
|
30
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
31
|
+
});
|
|
32
|
+
if (status.trim() === '') return 0;
|
|
33
|
+
} catch {
|
|
34
|
+
// not a git repo — run the checks anyway
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
let checks;
|
|
38
|
+
try {
|
|
39
|
+
checks = JSON.parse(readFileSync(new URL('./dod-checks.json', import.meta.url), 'utf8'));
|
|
40
|
+
} catch {
|
|
41
|
+
return 0;
|
|
42
|
+
}
|
|
43
|
+
if (!Array.isArray(checks) || checks.length === 0) return 0;
|
|
44
|
+
|
|
45
|
+
for (const command of checks) {
|
|
46
|
+
try {
|
|
47
|
+
execSync(command, { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
48
|
+
} catch (error) {
|
|
49
|
+
const tail = String(error.stdout ?? '')
|
|
50
|
+
.split('\n')
|
|
51
|
+
.slice(-15)
|
|
52
|
+
.join('\n');
|
|
53
|
+
process.stderr.write(
|
|
54
|
+
`STOP GATED — a Definition of Done check fails: ${command}\n` +
|
|
55
|
+
(tail.trim() ? `${tail}\n` : '') +
|
|
56
|
+
`Fix the failure before ending the session. If this failure has resisted ` +
|
|
57
|
+
`repeated attempts, follow the stop rules instead: end with a written ` +
|
|
58
|
+
`diagnosis (.claude/rules/autonomy.md). This gate never fires twice in a row.\n`,
|
|
59
|
+
);
|
|
60
|
+
return 2;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return 0;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
process.exit(main());
|