create-agent-rig 0.3.2 → 0.5.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 +294 -10
- package/README.md +84 -13
- package/package.json +2 -1
- package/packages/cli/dist/commands/create.js +41 -8
- package/packages/cli/dist/commands/init.js +120 -34
- package/packages/cli/dist/commands/upgrade.js +382 -0
- package/packages/cli/dist/index.js +132 -21
- package/packages/cli/dist/lib/copy-tree.js +44 -7
- package/packages/cli/dist/lib/history.js +49 -0
- package/packages/cli/dist/lib/init-settings.js +12 -0
- package/packages/cli/dist/lib/install-set.js +44 -0
- package/packages/cli/dist/lib/manifest.js +111 -0
- package/packages/cli/dist/lib/prompts.js +20 -0
- package/packages/cli/dist/lib/safe-path.js +71 -0
- package/packages/cli/dist/lib/substitute.js +32 -0
- package/packages/cli/dist/lib/version.js +15 -0
- package/templates/agent-os/init/AGENTS.md +191 -0
- package/templates/agent-os/init/CLAUDE.md +61 -9
- package/templates/agent-os/stack/aws-cdk/.agents/skills/post-deploy-verify/SKILL.md +98 -0
- package/templates/agent-os/stack/aws-cdk/.agents/skills/ro-debug/SKILL.md +117 -0
- package/templates/agent-os/stack/aws-cdk/.claude/agents/cdk-diff-reviewer.md +34 -3
- package/templates/agent-os/stack/aws-cdk/.claude/rules/aws-cdk.md +1 -1
- package/templates/agent-os/stack/aws-cdk/.claude/skills/post-deploy-verify/SKILL.md +34 -0
- package/templates/agent-os/stack/aws-cdk/.codex/agents/cdk-diff-reviewer.toml +4 -0
- package/templates/agent-os/universal/.agents/skills/check-premises/SKILL.md +233 -0
- package/templates/agent-os/universal/.agents/skills/loop/SKILL.md +676 -0
- package/templates/agent-os/universal/.agents/skills/new-invariant/SKILL.md +102 -0
- package/templates/agent-os/universal/.agents/skills/new-invariant/guard-invariant.example.mjs +78 -0
- package/templates/agent-os/universal/.agents/skills/new-invariant/guard-invariant.example.test.mjs +89 -0
- package/templates/agent-os/universal/.agents/skills/pr-ship/SKILL.md +338 -0
- package/templates/agent-os/universal/.agents/skills/worktree-task/SKILL.md +73 -0
- package/templates/agent-os/universal/.claude/agents/code-reviewer.md +33 -1
- package/templates/agent-os/universal/.claude/agents/prose-reviewer.md +64 -1
- package/templates/agent-os/universal/.claude/agents/security-scanner.md +33 -0
- package/templates/agent-os/universal/.claude/hooks/gate-stop-dod.mjs +274 -24
- package/templates/agent-os/universal/.claude/hooks/guard-bash.mjs +43 -5
- package/templates/agent-os/universal/.claude/hooks/guard-core-purity.mjs +25 -11
- package/templates/agent-os/universal/.claude/hooks/guard-secret-file.mjs +180 -0
- package/templates/agent-os/universal/.claude/hooks/guard-web-boundary.mjs +32 -13
- package/templates/agent-os/universal/.claude/hooks/inject-rules.mjs +175 -7
- package/templates/agent-os/universal/.claude/hooks/lib/edit-input.mjs +480 -0
- package/templates/agent-os/universal/.claude/rules/autonomy.md +61 -1
- package/templates/agent-os/universal/.claude/rules/invariants.md +71 -16
- package/templates/agent-os/universal/.claude/rules/workflow.md +55 -5
- package/templates/agent-os/universal/.claude/scripts/decision-router.mjs +1242 -0
- package/templates/agent-os/universal/.claude/scripts/detect-missed-gate.mjs +65 -2
- package/templates/agent-os/universal/.claude/scripts/git-env.mjs +49 -0
- package/templates/agent-os/universal/.claude/scripts/lib/secrets.mjs +490 -0
- package/templates/agent-os/universal/.claude/scripts/lib/verdict.mjs +433 -0
- package/templates/agent-os/universal/.claude/scripts/preflight.mjs +6 -16
- package/templates/agent-os/universal/.claude/scripts/queue/checkout.mjs +89 -0
- package/templates/agent-os/universal/.claude/scripts/queue/core.mjs +334 -23
- package/templates/agent-os/universal/.claude/scripts/queue/gate-rounds.mjs +160 -0
- package/templates/agent-os/universal/.claude/scripts/queue/github-issues.mjs +7 -2
- package/templates/agent-os/universal/.claude/scripts/queue/index.mjs +413 -8
- package/templates/agent-os/universal/.claude/scripts/queue/jira.mjs +75 -13
- package/templates/agent-os/universal/.claude/scripts/queue/plan-md.mjs +222 -21
- package/templates/agent-os/universal/.claude/scripts/queue/state.mjs +197 -0
- package/templates/agent-os/universal/.claude/scripts/run-journal.mjs +435 -0
- package/templates/agent-os/universal/.claude/scripts/run-state.mjs +441 -0
- package/templates/agent-os/universal/.claude/scripts/verdict.mjs +101 -0
- package/templates/agent-os/universal/.claude/settings.json +7 -2
- package/templates/agent-os/universal/.claude/skills/check-premises/SKILL.md +115 -7
- package/templates/agent-os/universal/.claude/skills/loop/SKILL.md +395 -33
- package/templates/agent-os/universal/.claude/skills/pr-ship/SKILL.md +300 -25
- package/templates/agent-os/universal/.codex/agents/code-reviewer.toml +4 -0
- package/templates/agent-os/universal/.codex/agents/prose-reviewer.toml +4 -0
- package/templates/agent-os/universal/.codex/agents/security-scanner.toml +4 -0
- package/templates/agent-os/universal/.codex/agents/test-writer.toml +4 -0
- package/templates/agent-os/universal/.codex/hooks.json +65 -0
- package/templates/agent-os/universal/AGENTS.md +164 -0
- package/templates/agent-os/universal/CLAUDE.md +47 -14
- package/templates/agent-os/universal/PLAN.md +7 -29
- package/templates/agent-os/universal/docs/decisions/closing-a-task.md +98 -0
- package/templates/agent-os/universal/docs/decisions/codex-adapter.md +108 -0
- package/templates/agent-os/universal/docs/decisions/fail-open-guards.md +43 -0
- package/templates/agent-os/universal/docs/decisions/review-lanes.md +72 -0
- package/templates/agent-os/universal/docs/decisions/run-directory.md +44 -0
- package/templates/agent-os/universal/docs/decisions/spacing-rations-mechanisms.md +125 -0
- package/templates/agent-os/universal/docs/decisions/stop-conditions-in-a-file.md +46 -0
- package/templates/agent-os/universal/docs/decisions/two-empty-endings.md +62 -0
- package/templates/agent-os/universal/journal/README.md +101 -0
- package/templates/agent-os/universal/layers.json +36 -2
- package/templates/hash-history.json +264 -0
- package/templates/skeleton/aws-serverless/.github/workflows/deploy.yml +34 -1
- package/templates/skeleton/aws-serverless/README.md +91 -9
- package/templates/skeleton/aws-serverless/apps/web/src/lib/api.ts +9 -2
- package/templates/skeleton/aws-serverless/gitignore +37 -0
- package/templates/skeleton/aws-serverless/infra/bin/app.ts +73 -13
- package/templates/skeleton/aws-serverless/infra/lib/app-stack.ts +136 -4
- package/templates/skeleton/aws-serverless/infra/lib/web-stack.ts +14 -1
- package/templates/skeleton/aws-serverless/infra/test/allowed-origins.test.ts +301 -0
- package/templates/skeleton/aws-serverless/infra/test/app-composition.test.ts +137 -0
- package/templates/skeleton/aws-serverless/infra/test/app-stack.test.ts +36 -20
- package/templates/skeleton/aws-serverless/infra/test/web-stack.test.ts +15 -12
- package/templates/skeleton/aws-serverless/services/api/src/handlers/create-note.ts +33 -6
- package/templates/skeleton/aws-serverless/services/api/src/handlers/list-notes.ts +18 -5
- package/templates/skeleton/aws-serverless/services/api/src/usecases/create-note.ts +11 -0
- package/templates/skeleton/aws-serverless/services/api/test/create-note.handler.test.ts +120 -1
- package/templates/skeleton/aws-serverless/services/api/test/list-notes.test.ts +72 -1
- package/templates/skeleton/node-service/README.md +11 -1
- package/templates/skeleton/node-service/gitignore +34 -0
- package/templates/skeleton/node-service/packages/db/src/note-store.ts +47 -10
- package/templates/skeleton/node-service/packages/db/test/note-store.test.ts +20 -0
- package/templates/skeleton/node-service/services/api/src/main.ts +2 -9
- package/templates/skeleton/node-service/services/api/src/server.ts +93 -10
- package/templates/skeleton/node-service/services/api/src/static-dir.ts +20 -0
- package/templates/skeleton/node-service/services/api/test/server.test.ts +98 -13
- package/templates/skeleton/node-service/services/api/test/static-dir.test.ts +28 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
{
|
|
2
|
+
"versions": [
|
|
3
|
+
"0.3.0",
|
|
4
|
+
"0.3.1",
|
|
5
|
+
"0.3.2",
|
|
6
|
+
"0.4.0"
|
|
7
|
+
],
|
|
8
|
+
"files": {
|
|
9
|
+
".claude/agents/cdk-diff-reviewer.md": {
|
|
10
|
+
"since": "0.3.0",
|
|
11
|
+
"hashes": [
|
|
12
|
+
"2286fbc01c0342b9bac9909890876c079615db5b398e3499a0f2fddb1a334098"
|
|
13
|
+
]
|
|
14
|
+
},
|
|
15
|
+
".claude/agents/code-reviewer.md": {
|
|
16
|
+
"since": "0.3.0",
|
|
17
|
+
"hashes": [
|
|
18
|
+
"58969edc0820463298002c618b6ec49913e1cb3aa90dec4aed2aec14c7bcfd3b",
|
|
19
|
+
"be3d2992d1f278dde46b555df1840b02f91403db1ec2fd4d489fb5efa940f019",
|
|
20
|
+
"ec403373ef6bc0575d61c6ca11f82792817b272221fad8317ef6c45388b52628"
|
|
21
|
+
]
|
|
22
|
+
},
|
|
23
|
+
".claude/agents/prose-reviewer.md": {
|
|
24
|
+
"since": "0.3.2",
|
|
25
|
+
"hashes": [
|
|
26
|
+
"8d5762849afd328fdc1aabf053ad207f2e306e013665e3ff934b06cbb1bbda38"
|
|
27
|
+
]
|
|
28
|
+
},
|
|
29
|
+
".claude/agents/security-scanner.md": {
|
|
30
|
+
"since": "0.3.0",
|
|
31
|
+
"hashes": [
|
|
32
|
+
"43e5d59315483e369ad19f3070175f146e88deabd3a12dc09339ac4fac0f2aee"
|
|
33
|
+
]
|
|
34
|
+
},
|
|
35
|
+
".claude/agents/test-writer.md": {
|
|
36
|
+
"since": "0.3.0",
|
|
37
|
+
"hashes": [
|
|
38
|
+
"333feef12383dea51791ae4923c44ba542438de793f0792fb51d96aa15b8b1b8"
|
|
39
|
+
]
|
|
40
|
+
},
|
|
41
|
+
".claude/hooks/block-no-verify.mjs": {
|
|
42
|
+
"since": "0.3.0",
|
|
43
|
+
"hashes": [
|
|
44
|
+
"ece5a81db41bd48695a1a009239837b682d247baf6d19214eaa859a7519ab246"
|
|
45
|
+
]
|
|
46
|
+
},
|
|
47
|
+
".claude/hooks/dod-checks.json": {
|
|
48
|
+
"since": "0.3.0",
|
|
49
|
+
"hashes": [
|
|
50
|
+
"413e59fecca1453e54369b1e302925b4f6843273767ebc33ac311fe825cfc4b2"
|
|
51
|
+
]
|
|
52
|
+
},
|
|
53
|
+
".claude/hooks/gate-stop-dod.mjs": {
|
|
54
|
+
"since": "0.3.0",
|
|
55
|
+
"hashes": [
|
|
56
|
+
"de1f331eaf93bad6ef0e1734789fd3c4ab07550a85ed2d8ff8888bc6f0bcfb0f",
|
|
57
|
+
"9fb8fad4d95756fb2424a97550aa85dfab0ca8dbcf91e6d0385263ba3b929135"
|
|
58
|
+
]
|
|
59
|
+
},
|
|
60
|
+
".claude/hooks/guard-bash.mjs": {
|
|
61
|
+
"since": "0.3.0",
|
|
62
|
+
"hashes": [
|
|
63
|
+
"4d396f412fc53094188b14348c5af4e79c7542a182630c20c4687d79899c5b0e"
|
|
64
|
+
]
|
|
65
|
+
},
|
|
66
|
+
".claude/hooks/guard-core-purity.mjs": {
|
|
67
|
+
"since": "0.3.0",
|
|
68
|
+
"hashes": [
|
|
69
|
+
"e36fbb92ee8395c6686fef582f29f2701747074b3b781bed42fe068f5c000bd7"
|
|
70
|
+
]
|
|
71
|
+
},
|
|
72
|
+
".claude/hooks/guard-web-boundary.mjs": {
|
|
73
|
+
"since": "0.3.0",
|
|
74
|
+
"hashes": [
|
|
75
|
+
"93d4fa0fd1c18261cc8ef2b1ae2abe3a0200cc4b5a7b1d1d4ae433ca92c514b1"
|
|
76
|
+
]
|
|
77
|
+
},
|
|
78
|
+
".claude/hooks/inject-rules.mjs": {
|
|
79
|
+
"since": "0.3.0",
|
|
80
|
+
"hashes": [
|
|
81
|
+
"c472419f1076ea3707d640cd5246c0e69a94c55455f6648e2cbaf300a382c8df"
|
|
82
|
+
]
|
|
83
|
+
},
|
|
84
|
+
".claude/queue.json": {
|
|
85
|
+
"since": "0.3.0",
|
|
86
|
+
"hashes": [
|
|
87
|
+
"00671bfdd83326e7561f1ce955b7ad79dcf566faf2eae3684b4287af0d54c14c"
|
|
88
|
+
]
|
|
89
|
+
},
|
|
90
|
+
".claude/rules/architecture.md": {
|
|
91
|
+
"since": "0.3.0",
|
|
92
|
+
"hashes": [
|
|
93
|
+
"c63ee846180efcf4bacb4a13ca19bdc043c0885103fcea11f702e0685921facf"
|
|
94
|
+
]
|
|
95
|
+
},
|
|
96
|
+
".claude/rules/autonomy.md": {
|
|
97
|
+
"since": "0.3.0",
|
|
98
|
+
"hashes": [
|
|
99
|
+
"ce2104ce697d3e5ed9210e85fe0daeb66b432a36868493f06fec54073a5fff31"
|
|
100
|
+
]
|
|
101
|
+
},
|
|
102
|
+
".claude/rules/aws-cdk.md": {
|
|
103
|
+
"since": "0.3.0",
|
|
104
|
+
"hashes": [
|
|
105
|
+
"e0a283559710d15035668d8b04e47228fce442ac4e37458caca24b429a65b25e"
|
|
106
|
+
]
|
|
107
|
+
},
|
|
108
|
+
".claude/rules/invariants.md": {
|
|
109
|
+
"since": "0.3.0",
|
|
110
|
+
"hashes": [
|
|
111
|
+
"b24bbf3e596033719eff463d317ba1e933d0661e48bfa8eafba8d6dca1453a59",
|
|
112
|
+
"518d9b3d7cbae50f30c86d33b2b4bbea11ab3ba6cfef270876e92604b2af5f50"
|
|
113
|
+
]
|
|
114
|
+
},
|
|
115
|
+
".claude/rules/node-ts.md": {
|
|
116
|
+
"since": "0.3.0",
|
|
117
|
+
"hashes": [
|
|
118
|
+
"3db68a4280cc30655be56fed9385179c378f560f3df55e21952a24066427e845"
|
|
119
|
+
]
|
|
120
|
+
},
|
|
121
|
+
".claude/rules/workflow.md": {
|
|
122
|
+
"since": "0.3.0",
|
|
123
|
+
"hashes": [
|
|
124
|
+
"dbe8025e10583e03fee5056e88aa6b73e4399453711d9156d82528a02d6e0402",
|
|
125
|
+
"9e60d0300e856a9b3f81ab748bf2c0a9de27233ad0aa58a8c7141413fa0f3cae"
|
|
126
|
+
]
|
|
127
|
+
},
|
|
128
|
+
".claude/scripts/detect-missed-gate.mjs": {
|
|
129
|
+
"since": "0.3.0",
|
|
130
|
+
"hashes": [
|
|
131
|
+
"bad5ce92a1c1f97ef2c25ebd216e945329e544337aa33e530631ed922ea8c495",
|
|
132
|
+
"edcebe8ac78cf832a42e3b6fb70fe458a9fec4cb5f8b7b4cf276efc4f9c06a64"
|
|
133
|
+
]
|
|
134
|
+
},
|
|
135
|
+
".claude/scripts/preflight.mjs": {
|
|
136
|
+
"since": "0.3.0",
|
|
137
|
+
"hashes": [
|
|
138
|
+
"21083ff7baeebf8aedbdf85679a353f671276a9e0201a58a8235ae63f55f081c",
|
|
139
|
+
"ceb6d35fa028f9c5e5e2d5965c627095d5ba80c37191a584627b4bc645b6be15"
|
|
140
|
+
]
|
|
141
|
+
},
|
|
142
|
+
".claude/scripts/queue/core.mjs": {
|
|
143
|
+
"since": "0.3.0",
|
|
144
|
+
"hashes": [
|
|
145
|
+
"214f36e39f3809da5a121f404005d16a694280ca672ed10150b1b07d47912f6a",
|
|
146
|
+
"d8a5916bae71efcc6a21978af713112c9ec33a7554fd07f5978bdf2c3633b560"
|
|
147
|
+
]
|
|
148
|
+
},
|
|
149
|
+
".claude/scripts/queue/github-issues.mjs": {
|
|
150
|
+
"since": "0.3.0",
|
|
151
|
+
"hashes": [
|
|
152
|
+
"f473f0ee5fc469f47b2b21ee601310944faa1abed21942e97ed251aba826cd5d",
|
|
153
|
+
"86489ac7fcd06e43e31fb3d0ac9ab23174ef9c95196b08f127f1ae74da620c0c"
|
|
154
|
+
]
|
|
155
|
+
},
|
|
156
|
+
".claude/scripts/queue/index.mjs": {
|
|
157
|
+
"since": "0.3.0",
|
|
158
|
+
"hashes": [
|
|
159
|
+
"cd149245dc29ef7489ae0e5394fa5a063dea2cc017bd3cbbface1ace6f8d7b46"
|
|
160
|
+
]
|
|
161
|
+
},
|
|
162
|
+
".claude/scripts/queue/jira.mjs": {
|
|
163
|
+
"since": "0.3.0",
|
|
164
|
+
"hashes": [
|
|
165
|
+
"13e187a4005136a9a1fae5fb5a3d2a61c3cf5dc097b467c12622ad82a0fd5d52",
|
|
166
|
+
"86591a48b9264d641c2530238ea3720705f12e17af2ec8dccecabfa3a742a2e7"
|
|
167
|
+
]
|
|
168
|
+
},
|
|
169
|
+
".claude/scripts/queue/plan-md.mjs": {
|
|
170
|
+
"since": "0.3.0",
|
|
171
|
+
"hashes": [
|
|
172
|
+
"d137dad23f263b1a12b04b46625247fea648ded36d0fea7c5b4e4f2211a142eb",
|
|
173
|
+
"07eab69bd86b19b7a1f01eb14efe4f8c007481806cbe029d4014fe9bf0703000"
|
|
174
|
+
]
|
|
175
|
+
},
|
|
176
|
+
".claude/scripts/reconcile-external-prs.mjs": {
|
|
177
|
+
"since": "0.3.0",
|
|
178
|
+
"hashes": [
|
|
179
|
+
"a326acd64ea19a50d093c91e711ad0234eb05c76d00066db5657c22f4355c190"
|
|
180
|
+
]
|
|
181
|
+
},
|
|
182
|
+
".claude/scripts/stop-flag.mjs": {
|
|
183
|
+
"since": "0.3.0",
|
|
184
|
+
"hashes": [
|
|
185
|
+
"ff4c8edbb715e7b552e8b5a5299e151871e85416568ce20ff8d8a0b4300a78a8"
|
|
186
|
+
]
|
|
187
|
+
},
|
|
188
|
+
".claude/settings.json": {
|
|
189
|
+
"since": "0.3.0",
|
|
190
|
+
"hashes": [
|
|
191
|
+
"55a6938b4d9967291a26e93301e34e5ffdef74a88f8c773a4d11b5d87d864fff"
|
|
192
|
+
]
|
|
193
|
+
},
|
|
194
|
+
".claude/skills/check-premises/SKILL.md": {
|
|
195
|
+
"since": "0.3.2",
|
|
196
|
+
"hashes": [
|
|
197
|
+
"d69396da276f748c89ca0284fcd606ce70da8a791defafe9c9b27ce5f8ffd29e"
|
|
198
|
+
]
|
|
199
|
+
},
|
|
200
|
+
".claude/skills/loop/SKILL.md": {
|
|
201
|
+
"since": "0.3.0",
|
|
202
|
+
"hashes": [
|
|
203
|
+
"e14318a1bf4cbb19edaaabadcf14482bc70bcdcfdc2057231b15685d149db550",
|
|
204
|
+
"8f687f92fa60d4cc679fa13bff997cec92227f6bc0b90c1dc5b8a5400f2abef0"
|
|
205
|
+
]
|
|
206
|
+
},
|
|
207
|
+
".claude/skills/new-invariant/SKILL.md": {
|
|
208
|
+
"since": "0.3.0",
|
|
209
|
+
"hashes": [
|
|
210
|
+
"b15abfba29cd52b2fc29f30d63803bc0fc2741429c8faab21d2f907ee1ce0828"
|
|
211
|
+
]
|
|
212
|
+
},
|
|
213
|
+
".claude/skills/new-invariant/guard-invariant.example.mjs": {
|
|
214
|
+
"since": "0.3.0",
|
|
215
|
+
"hashes": [
|
|
216
|
+
"0fec41841de1fc07f4da08d023fdda2906c62e3c71b22827c874f2809719b955"
|
|
217
|
+
]
|
|
218
|
+
},
|
|
219
|
+
".claude/skills/new-invariant/guard-invariant.example.test.mjs": {
|
|
220
|
+
"since": "0.3.0",
|
|
221
|
+
"hashes": [
|
|
222
|
+
"7bef9179c5b56d280d462cf21fb2c38dc4cb759b671b114b3995aac93720caf8"
|
|
223
|
+
]
|
|
224
|
+
},
|
|
225
|
+
".claude/skills/post-deploy-verify/SKILL.md": {
|
|
226
|
+
"since": "0.3.0",
|
|
227
|
+
"hashes": [
|
|
228
|
+
"321ae18feff82f1f8a64e19a6170056dc9b18a71e6f2a80d46293510b9748a46"
|
|
229
|
+
]
|
|
230
|
+
},
|
|
231
|
+
".claude/skills/pr-ship/SKILL.md": {
|
|
232
|
+
"since": "0.3.0",
|
|
233
|
+
"hashes": [
|
|
234
|
+
"368a50be809f7be865d6d29ab3e54b9d69acd2ea3ba7929c0edeef161c2521b7",
|
|
235
|
+
"75adb84414a330db621ff4f1363b3926f537133199236a9e25bb9880b935e02e"
|
|
236
|
+
]
|
|
237
|
+
},
|
|
238
|
+
".claude/skills/ro-debug/SKILL.md": {
|
|
239
|
+
"since": "0.3.0",
|
|
240
|
+
"hashes": [
|
|
241
|
+
"4a391a0a42b613683d897e2b4fa46db4eb0e778cbfc1e4d4340bb855073de528"
|
|
242
|
+
]
|
|
243
|
+
},
|
|
244
|
+
".claude/skills/worktree-task/SKILL.md": {
|
|
245
|
+
"since": "0.3.0",
|
|
246
|
+
"hashes": [
|
|
247
|
+
"1eedb9b3429f2d032181606924815dd8befbe7a79ba209655858fcdac189793c"
|
|
248
|
+
]
|
|
249
|
+
},
|
|
250
|
+
"CLAUDE.md": {
|
|
251
|
+
"since": "0.3.0",
|
|
252
|
+
"hashes": [
|
|
253
|
+
"8a4f5fab7cbaf3ba2482035f7ddb3cf6e3a3a3313596d25a856506e970d55db7",
|
|
254
|
+
"a247fdd1d198777f4963da42ccc10dfd9c372f4bb0488d2c0201a49d6bfd1910"
|
|
255
|
+
]
|
|
256
|
+
},
|
|
257
|
+
"PLAN.md": {
|
|
258
|
+
"since": "0.3.0",
|
|
259
|
+
"hashes": [
|
|
260
|
+
"1691fba2791d7d893e366e4a18bc120d4e4a96213bebbfb2a28881e13de74389"
|
|
261
|
+
]
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
@@ -53,5 +53,38 @@ jobs:
|
|
|
53
53
|
role-to-assume: ${{ secrets.AWS_DEPLOY_ROLE_ARN }}
|
|
54
54
|
aws-region: ${{ vars.AWS_REGION || '__REGION__' }}
|
|
55
55
|
|
|
56
|
+
# `--require-approval never` is deliberate and dev-only: this workflow has
|
|
57
|
+
# no production path at all (see the header), so the approval prompt would
|
|
58
|
+
# only ever block an unattended dev deploy on a change nobody is watching.
|
|
56
59
|
- if: steps.guard.outputs.skip != 'true'
|
|
57
|
-
run:
|
|
60
|
+
run: >
|
|
61
|
+
pnpm --filter @app/infra exec cdk deploy AppStack WebStack
|
|
62
|
+
--require-approval never --outputs-file cdk-outputs.json
|
|
63
|
+
|
|
64
|
+
# The bundle is built above and the bucket is created by the deploy — but
|
|
65
|
+
# nothing puts one into the other, so without this step CloudFront keeps
|
|
66
|
+
# serving whatever was uploaded by hand, last time somebody remembered.
|
|
67
|
+
# `web-stack.ts` deliberately does not use BucketDeployment (synth must
|
|
68
|
+
# never depend on `next build` having run), which is why the upload lives
|
|
69
|
+
# here rather than in the stack.
|
|
70
|
+
- if: steps.guard.outputs.skip != 'true'
|
|
71
|
+
name: Upload the web bundle and invalidate the edge cache
|
|
72
|
+
run: |
|
|
73
|
+
# `jq -r` prints the string "null" and exits 0 for a missing key, and
|
|
74
|
+
# GitHub's shell is `bash -e` with no `-u` and no pipefail — so a
|
|
75
|
+
# renamed stack or a half-finished deploy would sync the bundle into
|
|
76
|
+
# `s3://null`, a real bucket in a global namespace someone else owns,
|
|
77
|
+
# and issue --delete against it. `-e` makes jq exit non-zero instead.
|
|
78
|
+
set -euo pipefail
|
|
79
|
+
BUCKET=$(jq -er '.WebStack.WebBucketName' infra/cdk-outputs.json)
|
|
80
|
+
DISTRIBUTION=$(jq -er '.WebStack.WebDistributionId' infra/cdk-outputs.json)
|
|
81
|
+
# An existing-but-empty bundle syncs zero objects, and --delete then
|
|
82
|
+
# empties the live site with a green checkmark.
|
|
83
|
+
[ -n "$(ls -A apps/web/out 2>/dev/null)" ] || {
|
|
84
|
+
echo "apps/web/out is empty — refusing to sync"; exit 1;
|
|
85
|
+
}
|
|
86
|
+
# --delete makes the bucket the bundle's territory alone: anything not
|
|
87
|
+
# in this build is removed from it.
|
|
88
|
+
aws s3 sync apps/web/out "s3://$BUCKET" --delete
|
|
89
|
+
aws cloudfront create-invalidation \
|
|
90
|
+
--distribution-id "$DISTRIBUTION" --paths '/*'
|
|
@@ -45,13 +45,27 @@ do not edit the workflow:
|
|
|
45
45
|
|
|
46
46
|
1. In AWS, create an IAM role your repo can assume via GitHub's OIDC provider
|
|
47
47
|
(`token.actions.githubusercontent.com`) — a short-lived federated role, no
|
|
48
|
-
long-lived access keys anywhere.
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
48
|
+
long-lived access keys anywhere. Beyond what `cdk deploy` needs, the
|
|
49
|
+
workflow uploads the bundle itself, so the role also needs
|
|
50
|
+
**`s3:ListBucket`, `s3:PutObject`, `s3:DeleteObject`** on the web bucket and
|
|
51
|
+
**`cloudfront:CreateInvalidation`** on the distribution. A role scoped only
|
|
52
|
+
to CDK's bootstrap roles gets through the deploy and fails on the upload —
|
|
53
|
+
after both stacks are already up.
|
|
54
|
+
2. Add its ARN as the repository secret **`AWS_DEPLOY_ROLE_ARN`**, and set the
|
|
55
|
+
repo variable **`API_URL`** to the deployed API's URL (the `ApiUrl` output
|
|
56
|
+
of a first deploy). It becomes `NEXT_PUBLIC_API_URL`, which Next **inlines
|
|
57
|
+
into the bundle at build time** — unset, the site calls `/notes` on its own
|
|
58
|
+
CloudFront domain, where nothing answers. `AWS_REGION` is genuinely
|
|
59
|
+
optional. The first deploy is therefore two passes: deploy, read `ApiUrl`
|
|
60
|
+
from the job's `cdk deploy` Outputs, set the variable, then re-run the
|
|
61
|
+
workflow (push, or **Run workflow** — it is `workflow_dispatch`-enabled).
|
|
62
|
+
|
|
63
|
+
The workflow then assumes the role, builds the web bundle, runs
|
|
64
|
+
`cdk deploy AppStack WebStack --outputs-file`, then **uploads `apps/web/out` to
|
|
65
|
+
the web bucket (`aws s3 sync --delete`) and invalidates the CloudFront cache**,
|
|
66
|
+
reading both destinations from the stack outputs. You do not sync anything by
|
|
67
|
+
hand on this path. The site is served from S3 + CloudFront (the `WebUrl`
|
|
68
|
+
output).
|
|
55
69
|
|
|
56
70
|
### Local / manual
|
|
57
71
|
|
|
@@ -59,8 +73,26 @@ The workflow then assumes the role, builds the web bundle, and runs
|
|
|
59
73
|
# needs AWS credentials; region comes from your profile (generator default: __REGION__)
|
|
60
74
|
cd infra
|
|
61
75
|
npx cdk bootstrap # first time per account/region
|
|
62
|
-
npx cdk deploy AppStack WebStack
|
|
63
|
-
|
|
76
|
+
npx cdk deploy AppStack WebStack --outputs-file cdk-outputs.json
|
|
77
|
+
# --delete makes the bucket the bundle's territory alone, so a stale or missing
|
|
78
|
+
# `out/` would empty the live site — or restore last month's. The workflow
|
|
79
|
+
# builds two steps before its sync; by hand, build here.
|
|
80
|
+
# Read the outputs into variables FIRST. `jq -er` exits non-zero on a missing
|
|
81
|
+
# key, but a command substitution inside an assignment or an argument throws
|
|
82
|
+
# that status away — which is how a stale outputs file becomes a bundle built
|
|
83
|
+
# against `null` and an `aws s3 sync … s3://null --delete`.
|
|
84
|
+
API=$(jq -er '.AppStack.ApiUrl' cdk-outputs.json) || { echo "no ApiUrl"; exit 1; }
|
|
85
|
+
BUCKET=$(jq -er '.WebStack.WebBucketName' cdk-outputs.json) || { echo "no WebBucketName"; exit 1; }
|
|
86
|
+
DIST=$(jq -er '.WebStack.WebDistributionId' cdk-outputs.json) || { echo "no WebDistributionId"; exit 1; }
|
|
87
|
+
|
|
88
|
+
# NEXT_PUBLIC_API_URL is inlined at build time: without it the bundle calls its
|
|
89
|
+
# own CloudFront domain instead of the API, and the sync below makes that live.
|
|
90
|
+
(cd .. && NEXT_PUBLIC_API_URL="$API" pnpm build:web)
|
|
91
|
+
[ -f ../apps/web/out/index.html ] || { echo "no web bundle — build failed"; exit 1; }
|
|
92
|
+
aws s3 sync ../apps/web/out "s3://$BUCKET" --delete
|
|
93
|
+
# a synced bucket whose distribution still serves the old objects has not
|
|
94
|
+
# deployed — invalidate, or you are looking at the previous build
|
|
95
|
+
aws cloudfront create-invalidation --paths '/*' --distribution-id "$DIST"
|
|
64
96
|
```
|
|
65
97
|
|
|
66
98
|
### Production — a human step, on purpose
|
|
@@ -76,12 +108,41 @@ your own approval — reusing the dev workflow's OIDC pattern.
|
|
|
76
108
|
After every deploy:
|
|
77
109
|
|
|
78
110
|
```sh
|
|
111
|
+
# From the deploy's outputs file — run this from `infra/`, where the manual
|
|
112
|
+
# section leaves you. Deployed through CI instead? That file is written on the
|
|
113
|
+
# runner and never lands here: take both values from the job's `cdk deploy`
|
|
114
|
+
# Outputs, or re-run `cdk deploy … --outputs-file cdk-outputs.json` locally.
|
|
115
|
+
API_URL=$(jq -er '.AppStack.ApiUrl' cdk-outputs.json)
|
|
116
|
+
WEB_URL=$(jq -er '.WebStack.WebUrl' cdk-outputs.json)
|
|
117
|
+
|
|
79
118
|
curl -s -X POST "$API_URL/notes" \
|
|
80
119
|
-H 'content-type: application/json' \
|
|
81
120
|
-d '{"title":"smoke test","tags":["deploy"]}'
|
|
82
121
|
# expect: HTTP 201 with { "note": { … } }
|
|
83
122
|
```
|
|
84
123
|
|
|
124
|
+
**That checks the API, not the site.** The two fail independently: a bundle
|
|
125
|
+
built without `NEXT_PUBLIC_API_URL` calls its own CloudFront domain, and a
|
|
126
|
+
misnamed origin is refused by the browser — neither is visible to a `curl` that
|
|
127
|
+
sends no `Origin` header. So also open the `WebUrl` output, create a note in
|
|
128
|
+
the form, and reload:
|
|
129
|
+
|
|
130
|
+
```sh
|
|
131
|
+
curl -s -I "$WEB_URL" | head -1 # the bundle is served
|
|
132
|
+
|
|
133
|
+
# API Gateway answers a preflight 204 whether or not the origin matched, so the
|
|
134
|
+
# status code proves nothing here. The browser gates on the echoed header —
|
|
135
|
+
# that is what to look for. `content-type` is sent because the real call uses
|
|
136
|
+
# it, and a non-safelisted header is what makes `allowHeaders` matter.
|
|
137
|
+
curl -s -X OPTIONS "$API_URL/notes" -D - -o /dev/null \
|
|
138
|
+
-H "Origin: $WEB_URL" \
|
|
139
|
+
-H 'access-control-request-method: POST' \
|
|
140
|
+
-H 'access-control-request-headers: content-type' \
|
|
141
|
+
| grep -i '^access-control-allow-origin:'
|
|
142
|
+
# expect: a line echoing $WEB_URL. No line = the browser will refuse the call,
|
|
143
|
+
# whatever the status code said.
|
|
144
|
+
```
|
|
145
|
+
|
|
85
146
|
Then confirm the pipeline: the worker logs `note.created processed`, and the
|
|
86
147
|
**DLQ alarm stays quiet**. If the smoke test regresses: `npx cdk deploy` the
|
|
87
148
|
previous revision (or `git revert` and redeploy) **first**, diagnose second.
|
|
@@ -95,5 +156,26 @@ previous revision (or `git revert` and redeploy) **first**, diagnose second.
|
|
|
95
156
|
- `packages/db` is the only module that touches the storage SDK.
|
|
96
157
|
- A failing queue message is poison: it throws, SQS retries ×3, the DLQ alarm
|
|
97
158
|
fires. Never wrap the worker in a broad catch.
|
|
159
|
+
- **Creating a note is a dual write, and it is not atomic.** `create-note`
|
|
160
|
+
puts to DynamoDB and then publishes to SQS. If the publish fails, the note
|
|
161
|
+
is stored and its event never happened — the worker never runs, and nothing
|
|
162
|
+
compensates; the caller gets a 500 for a note that exists. That is a
|
|
163
|
+
deliberate simplification for a starter. When it starts to matter, the two
|
|
164
|
+
ways out are an **outbox** (write the event alongside the note, relay it
|
|
165
|
+
afterwards) or DynamoDB Streams feeding the worker, which deletes the second
|
|
166
|
+
write instead of coordinating it.
|
|
167
|
+
- **CORS names who may call the API, and that half is wired for you.**
|
|
168
|
+
`bin/app.ts` builds `WebStack` first and passes its CloudFront origin to
|
|
169
|
+
`AppStack`, so the API allows the deployed site without you configuring it.
|
|
170
|
+
The other half is not automatic: the bundle has to know where the API *is*,
|
|
171
|
+
and that is `NEXT_PUBLIC_API_URL` above. For a custom domain or a second
|
|
172
|
+
origin, deploy with `-c allowedOrigins=https://app.example.com` (comma-
|
|
173
|
+
separated for several) — the entrypoint prefers the flag over the wired
|
|
174
|
+
default, and the cross-stack export disappears with it. An `allowedOrigins`
|
|
175
|
+
that parses to nothing, or an entry no browser could send, is refused at
|
|
176
|
+
synth rather than deployed as an API nobody can call. `*` is not the default
|
|
177
|
+
and never will be, but the flag does take it: you get a warning naming the
|
|
178
|
+
consequence, not a refusal, because the alternative is people editing the
|
|
179
|
+
stack.
|
|
98
180
|
|
|
99
181
|
See `.claude/rules/architecture.md` for the full rules.
|
|
@@ -2,8 +2,15 @@
|
|
|
2
2
|
// (the guard-web-boundary hook refuses such imports at the tool layer).
|
|
3
3
|
import type { Note } from '@app/core';
|
|
4
4
|
|
|
5
|
-
//
|
|
6
|
-
//
|
|
5
|
+
// In THIS target the API is never same-origin: the bundle is served from
|
|
6
|
+
// CloudFront and the API is API Gateway, so `NEXT_PUBLIC_API_URL` has to be set
|
|
7
|
+
// at build time — Next inlines it, and an unset one leaves the empty string
|
|
8
|
+
// here, which sends every call to whatever is serving this page.
|
|
9
|
+
//
|
|
10
|
+
// The `?? ''` is a fallback to nothing, not to a working default: there is no
|
|
11
|
+
// dev proxy in this project, so `pnpm --filter @app/web dev` needs the variable
|
|
12
|
+
// too. It exists only so a missing value is a relative URL rather than the
|
|
13
|
+
// string "undefined" in a request path.
|
|
7
14
|
const base = process.env.NEXT_PUBLIC_API_URL ?? '';
|
|
8
15
|
|
|
9
16
|
export async function createNote(input: { title: string; tags: string[] }): Promise<Note> {
|
|
@@ -2,16 +2,53 @@ node_modules/
|
|
|
2
2
|
dist/
|
|
3
3
|
coverage/
|
|
4
4
|
cdk.out/
|
|
5
|
+
# what `cdk deploy --outputs-file` writes: bucket names, distribution ids,
|
|
6
|
+
# endpoints. Harmless today, and the file that accumulates the ones that are not.
|
|
7
|
+
cdk-outputs.json
|
|
5
8
|
*.tsbuildinfo
|
|
6
9
|
.DS_Store
|
|
7
10
|
.next/
|
|
8
11
|
out/
|
|
9
12
|
next-env.d.ts
|
|
10
13
|
.claude/worktrees/
|
|
14
|
+
# per-checkout queue state: the tier the last close recorded. The queue CONFIG
|
|
15
|
+
# is composed and tracked; this is runtime state and must never be committed.
|
|
16
|
+
.claude/queue.state.json
|
|
17
|
+
# gate rounds, one count per branch
|
|
18
|
+
.claude/gate-rounds.json
|
|
19
|
+
# per-run machine trace (.claude/scripts/run-journal.mjs): forensics that churn
|
|
20
|
+
# on every gate and collide across concurrent sessions. Unlike a committed
|
|
21
|
+
# evidence directory, this one is per-run and stays out of the repository.
|
|
22
|
+
.claude/runs/
|
|
11
23
|
|
|
12
24
|
# secrets — never commit these
|
|
25
|
+
*.env
|
|
13
26
|
.env
|
|
14
27
|
.env.*
|
|
15
28
|
!.env.example
|
|
29
|
+
.envrc
|
|
30
|
+
.npmrc
|
|
31
|
+
.netrc
|
|
32
|
+
.pgpass
|
|
33
|
+
id_rsa
|
|
34
|
+
id_ed25519
|
|
16
35
|
*.pem
|
|
17
36
|
*.key
|
|
37
|
+
*.p12
|
|
38
|
+
*.pfx
|
|
39
|
+
*.keystore
|
|
40
|
+
*.jks
|
|
41
|
+
*.secret
|
|
42
|
+
*.secrets
|
|
43
|
+
*.token
|
|
44
|
+
*.tokens
|
|
45
|
+
*.password
|
|
46
|
+
*.passwords
|
|
47
|
+
*.credential
|
|
48
|
+
*.credentials
|
|
49
|
+
*.apikey
|
|
50
|
+
*.apikeys
|
|
51
|
+
*.passwd
|
|
52
|
+
*.creds
|
|
53
|
+
*.jwt
|
|
54
|
+
*.bearer
|
|
@@ -1,19 +1,79 @@
|
|
|
1
|
+
import { realpathSync } from 'node:fs';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
1
3
|
import { App } from 'aws-cdk-lib';
|
|
2
|
-
import { AppStack } from '../lib/app-stack.js';
|
|
4
|
+
import { AppStack, nothingGiven } from '../lib/app-stack.js';
|
|
3
5
|
import { WebStack } from '../lib/web-stack.js';
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
/**
|
|
8
|
+
* The composition, as a function so a test can synthesise the same wiring the
|
|
9
|
+
* CLI gets. A test that re-wires the stacks itself would pass while this file
|
|
10
|
+
* stayed broken, which is the one failure worth catching here.
|
|
11
|
+
*/
|
|
12
|
+
export function createApp(): App {
|
|
13
|
+
const app = new App();
|
|
6
14
|
|
|
7
|
-
// Region-agnostic by default; `cdk deploy` uses your CLI profile. Pin it here
|
|
8
|
-
// when the project settles (the generator's default region is __REGION__).
|
|
9
|
-
const env = { region: process.env.CDK_DEFAULT_REGION };
|
|
15
|
+
// Region-agnostic by default; `cdk deploy` uses your CLI profile. Pin it here
|
|
16
|
+
// when the project settles (the generator's default region is __REGION__).
|
|
17
|
+
const env = { region: process.env.CDK_DEFAULT_REGION };
|
|
10
18
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
19
|
+
// The web stack first: the API has to allow the origin the browser loads the
|
|
20
|
+
// app from, and that origin is a CloudFront domain this stack owns. Passing
|
|
21
|
+
// it makes CORS correct on the first deploy instead of after someone
|
|
22
|
+
// discovers the app is blocked and reaches for `*`.
|
|
23
|
+
//
|
|
24
|
+
// The price, so it is not discovered later: this is a cross-stack export, so
|
|
25
|
+
// WebStack is deploy-ordered before AppStack and cannot be torn down while
|
|
26
|
+
// AppStack still imports it — CDK warns about the reference strength on
|
|
27
|
+
// synth. Pass `allowedOrigins` explicitly (a custom domain, several origins)
|
|
28
|
+
// and the export disappears with it.
|
|
29
|
+
const web = new WebStack(app, 'WebStack', {
|
|
30
|
+
description: 'Static web serving for __PROJECT_NAME__ (generated by create-agent-rig)',
|
|
31
|
+
env,
|
|
32
|
+
});
|
|
15
33
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
34
|
+
// `-c allowedOrigins=…` wins over the wired default — a custom domain is a
|
|
35
|
+
// deliberate act, and props short-circuit the context inside AppStack, so
|
|
36
|
+
// passing them unconditionally would accept the flag and ignore it. Omitting
|
|
37
|
+
// the prop hands the flag to AppStack's own validation instead of dropping
|
|
38
|
+
// it: an unusable value is refused at synth rather than deployed.
|
|
39
|
+
// `nothingGiven` is imported rather than restated: this file and the stack
|
|
40
|
+
// both decide whether the operator supplied an origin, and two spellings of
|
|
41
|
+
// that question is how `"allowedOrigins": null` in cdk.json comes to mean
|
|
42
|
+
// "they chose one" here and "use the default" there — a green synth whose
|
|
43
|
+
// deployed API allows only localhost.
|
|
44
|
+
const override = app.node.tryGetContext('allowedOrigins');
|
|
45
|
+
|
|
46
|
+
new AppStack(app, 'AppStack', {
|
|
47
|
+
description: 'API + worker for __PROJECT_NAME__ (generated by create-agent-rig)',
|
|
48
|
+
env,
|
|
49
|
+
...(nothingGiven(override) ? { allowedOrigins: [web.origin] } : {}),
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
return app;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Only when the CDK CLI runs this file. A test importing `createApp` would
|
|
57
|
+
* otherwise build a second composition — and re-bundle three Lambdas — for an
|
|
58
|
+
* App nobody looks at.
|
|
59
|
+
*
|
|
60
|
+
* Both sides go through realpath: ESM resolves `import.meta.url` through
|
|
61
|
+
* symlinks while `process.argv[1]` keeps the path as typed, so a checkout
|
|
62
|
+
* behind a link (a macOS temp dir, a symlinked home) would fail a plain
|
|
63
|
+
* comparison and synthesise an empty app.
|
|
64
|
+
*/
|
|
65
|
+
function invokedDirectly(): boolean {
|
|
66
|
+
if (!process.argv[1]) return false;
|
|
67
|
+
const real = (p: string) => {
|
|
68
|
+
try {
|
|
69
|
+
return realpathSync(p);
|
|
70
|
+
} catch {
|
|
71
|
+
return p;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
return real(fileURLToPath(import.meta.url)) === real(process.argv[1]);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (invokedDirectly()) {
|
|
78
|
+
createApp();
|
|
79
|
+
}
|