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.
Files changed (235) hide show
  1. package/CHANGELOG.md +462 -3
  2. package/README.md +267 -305
  3. package/package.json +8 -14
  4. package/packages/cli/dist/commands/create.js +56 -88
  5. package/packages/cli/dist/commands/doctor.js +213 -0
  6. package/packages/cli/dist/commands/init.js +111 -50
  7. package/packages/cli/dist/commands/integrations.js +468 -0
  8. package/packages/cli/dist/commands/setup-wizard.js +61 -0
  9. package/packages/cli/dist/commands/uninstall.js +1223 -0
  10. package/packages/cli/dist/commands/upgrade.js +508 -101
  11. package/packages/cli/dist/index.js +658 -47
  12. package/packages/cli/dist/integrations/declaration.js +158 -0
  13. package/packages/cli/dist/integrations/doctor-guards.js +126 -0
  14. package/packages/cli/dist/integrations/doctor-workflow.js +25 -0
  15. package/packages/cli/dist/integrations/mcp-json.js +107 -0
  16. package/packages/cli/dist/integrations/memory-doctor.js +124 -0
  17. package/packages/cli/dist/integrations/registry.js +22 -0
  18. package/packages/cli/dist/integrations/spawn.js +228 -0
  19. package/packages/cli/dist/integrations/spec-kit.js +280 -0
  20. package/packages/cli/dist/integrations/verify.js +193 -0
  21. package/packages/cli/dist/integrations/windows-job.js +275 -0
  22. package/packages/cli/dist/lib/elevated-paths.js +74 -0
  23. package/packages/cli/dist/lib/install-set.js +1 -44
  24. package/packages/cli/dist/lib/manifest.js +64 -5
  25. package/packages/cli/dist/lib/prompts.js +0 -23
  26. package/packages/cli/dist/lib/safe-path.js +193 -2
  27. package/packages/cli/dist/lib/safe-text.js +78 -0
  28. package/packages/cli/dist/lib/substitute.js +1 -52
  29. package/packages/cli/dist/lib/summary.js +5 -5
  30. package/packages/cli/dist/templates.js +4 -14
  31. package/scripts/prepare.mjs +28 -12
  32. package/templates/agent-os/subagent-routing.json +4 -4
  33. package/templates/agent-os/universal/.agents/skills/check-premises/SKILL.md +45 -12
  34. package/templates/agent-os/universal/.agents/skills/loop/SKILL.md +31 -6
  35. package/templates/agent-os/universal/.agents/skills/new-invariant/SKILL.md +5 -5
  36. package/templates/agent-os/universal/.agents/skills/pr-ship/SKILL.md +1 -1
  37. package/templates/agent-os/universal/.agents/skills/worktree-task/SKILL.md +3 -3
  38. package/templates/agent-os/universal/.claude/agents/code-reviewer.md +8 -6
  39. package/templates/agent-os/universal/.claude/agents/implementation-agent.md +41 -0
  40. package/templates/agent-os/universal/.claude/agents/prose-reviewer.md +8 -4
  41. package/templates/agent-os/universal/.claude/agents/security-scanner.md +4 -2
  42. package/templates/agent-os/universal/.claude/agents/test-writer.md +2 -2
  43. package/templates/agent-os/universal/.claude/hooks/gate-stop-dod.mjs +4 -2
  44. package/templates/agent-os/universal/.claude/hooks/inject-rules.mjs +83 -4
  45. package/templates/agent-os/universal/.claude/rules/autonomy.md +55 -27
  46. package/templates/agent-os/universal/.claude/rules/invariants.md +9 -8
  47. package/templates/agent-os/universal/.claude/rules/workflow.md +59 -22
  48. package/templates/agent-os/universal/.claude/scripts/decision-router.mjs +1 -1
  49. package/templates/agent-os/universal/.claude/scripts/detect-missed-gate.mjs +12 -9
  50. package/templates/agent-os/universal/.claude/scripts/doctor.mjs +100 -2
  51. package/templates/agent-os/universal/.claude/scripts/lib/claim-records.mjs +236 -6
  52. package/templates/agent-os/universal/.claude/scripts/queue/state.mjs +2 -2
  53. package/templates/agent-os/universal/.claude/scripts/reconcile-external-prs.mjs +4 -3
  54. package/templates/agent-os/universal/.claude/scripts/revalidate.mjs +16 -0
  55. package/templates/agent-os/universal/.claude/settings.json +0 -8
  56. package/templates/agent-os/universal/.claude/skills/check-premises/SKILL.md +45 -12
  57. package/templates/agent-os/universal/.claude/skills/loop/SKILL.md +31 -6
  58. package/templates/agent-os/universal/.claude/skills/new-invariant/SKILL.md +5 -5
  59. package/templates/agent-os/universal/.claude/skills/pr-ship/SKILL.md +1 -1
  60. package/templates/agent-os/universal/.claude/skills/worktree-task/SKILL.md +3 -3
  61. package/templates/agent-os/universal/.codex/agents/code-reviewer.toml +2 -2
  62. package/templates/agent-os/universal/.codex/agents/implementation-agent.toml +6 -0
  63. package/templates/agent-os/universal/.codex/agents/prose-reviewer.toml +2 -2
  64. package/templates/agent-os/universal/.codex/agents/security-scanner.toml +1 -1
  65. package/templates/agent-os/universal/.codex/agents/test-writer.toml +1 -1
  66. package/templates/agent-os/universal/.codex/hooks.json +0 -10
  67. package/templates/agent-os/universal/AGENTS.md +208 -107
  68. package/templates/agent-os/universal/CLAUDE.md +15 -162
  69. package/templates/agent-os/universal/PLAN.md +17 -10
  70. package/templates/agent-os/universal/docs/decisions/agents-md-canonical.md +313 -0
  71. package/templates/agent-os/universal/docs/decisions/codex-adapter.md +15 -10
  72. package/templates/agent-os/universal/docs/decisions/review-lanes.md +12 -8
  73. package/templates/agent-os/universal/docs/decisions/session-start-wire-format.md +206 -0
  74. package/templates/agent-os/universal/docs/decisions/subagent-routing.md +6 -3
  75. package/templates/agent-os/universal/docs/decisions/workflow-layer-split.md +235 -0
  76. package/templates/agent-os/universal/layers.json +25 -30
  77. package/templates/hash-history.json +112 -40
  78. package/templates/release-ledger.json +3 -1
  79. package/packages/cli/dist/lib/composition.js +0 -20
  80. package/packages/cli/dist/lib/targets.js +0 -28
  81. package/packages/cli/dist/policy/benchmark/corpus.js +0 -165
  82. package/packages/cli/dist/policy/core/adapter.js +0 -18
  83. package/packages/cli/dist/policy/core/coverage.js +0 -253
  84. package/packages/cli/dist/policy/core/decision-record.js +0 -287
  85. package/packages/cli/dist/policy/core/declaration.js +0 -127
  86. package/packages/cli/dist/policy/core/evidence-matrix.js +0 -94
  87. package/packages/cli/dist/policy/core/probe.js +0 -442
  88. package/packages/cli/dist/policy/core/registry.js +0 -115
  89. package/packages/cli/dist/policy/core/validation.js +0 -275
  90. package/packages/cli/dist/policy/core/vocabulary.js +0 -123
  91. package/packages/cli/dist/policy/harness/claude.js +0 -47
  92. package/packages/cli/dist/policy/harness/codex.js +0 -87
  93. package/packages/cli/dist/policy/harness/index.js +0 -15
  94. package/packages/cli/dist/policy/harness/shared-hooks.js +0 -28
  95. package/packages/cli/dist/policy/index.js +0 -17
  96. package/templates/agent-os/init/AGENTS.md +0 -201
  97. package/templates/agent-os/init/CLAUDE.md +0 -201
  98. package/templates/agent-os/stack/aws-cdk/.agents/skills/post-deploy-verify/SKILL.md +0 -105
  99. package/templates/agent-os/stack/aws-cdk/.agents/skills/ro-debug/SKILL.md +0 -117
  100. package/templates/agent-os/stack/aws-cdk/.claude/agents/cdk-diff-reviewer.md +0 -89
  101. package/templates/agent-os/stack/aws-cdk/.claude/rules/aws-cdk.md +0 -105
  102. package/templates/agent-os/stack/aws-cdk/.claude/skills/post-deploy-verify/SKILL.md +0 -105
  103. package/templates/agent-os/stack/aws-cdk/.claude/skills/ro-debug/SKILL.md +0 -117
  104. package/templates/agent-os/stack/aws-cdk/.codex/agents/cdk-diff-reviewer.toml +0 -6
  105. package/templates/agent-os/stack/node-ts/.claude/hooks/dod-checks.json +0 -1
  106. package/templates/agent-os/stack/node-ts/.claude/rules/node-ts.md +0 -85
  107. package/templates/agent-os/universal/.claude/hooks/guard-core-purity.mjs +0 -89
  108. package/templates/agent-os/universal/.claude/hooks/guard-web-boundary.mjs +0 -68
  109. package/templates/agent-os/universal/.claude/rules/architecture.md +0 -81
  110. package/templates/skeleton/aws-serverless/.github/workflows/ci.yml +0 -28
  111. package/templates/skeleton/aws-serverless/.github/workflows/deploy.yml +0 -90
  112. package/templates/skeleton/aws-serverless/README.md +0 -181
  113. package/templates/skeleton/aws-serverless/apps/web/next.config.mjs +0 -17
  114. package/templates/skeleton/aws-serverless/apps/web/package.json +0 -19
  115. package/templates/skeleton/aws-serverless/apps/web/src/app/layout.tsx +0 -17
  116. package/templates/skeleton/aws-serverless/apps/web/src/app/page.tsx +0 -96
  117. package/templates/skeleton/aws-serverless/apps/web/src/lib/api.ts +0 -36
  118. package/templates/skeleton/aws-serverless/apps/web/src/lib/validate.ts +0 -23
  119. package/templates/skeleton/aws-serverless/apps/web/test/shared-validation.test.ts +0 -38
  120. package/templates/skeleton/aws-serverless/apps/web/tsconfig.json +0 -14
  121. package/templates/skeleton/aws-serverless/eslint.config.mjs +0 -20
  122. package/templates/skeleton/aws-serverless/gitignore +0 -56
  123. package/templates/skeleton/aws-serverless/infra/bin/app.ts +0 -79
  124. package/templates/skeleton/aws-serverless/infra/cdk.json +0 -3
  125. package/templates/skeleton/aws-serverless/infra/lib/app-stack.ts +0 -248
  126. package/templates/skeleton/aws-serverless/infra/lib/web-stack.ts +0 -45
  127. package/templates/skeleton/aws-serverless/infra/package.json +0 -18
  128. package/templates/skeleton/aws-serverless/infra/test/allowed-origins.test.ts +0 -301
  129. package/templates/skeleton/aws-serverless/infra/test/app-composition.test.ts +0 -137
  130. package/templates/skeleton/aws-serverless/infra/test/app-stack.test.ts +0 -120
  131. package/templates/skeleton/aws-serverless/infra/test/web-stack.test.ts +0 -44
  132. package/templates/skeleton/aws-serverless/package.json +0 -30
  133. package/templates/skeleton/aws-serverless/packages/core/package.json +0 -11
  134. package/templates/skeleton/aws-serverless/packages/core/src/events.ts +0 -14
  135. package/templates/skeleton/aws-serverless/packages/core/src/index.ts +0 -15
  136. package/templates/skeleton/aws-serverless/packages/core/src/note.ts +0 -69
  137. package/templates/skeleton/aws-serverless/packages/core/test/events.test.ts +0 -23
  138. package/templates/skeleton/aws-serverless/packages/core/test/note.test.ts +0 -101
  139. package/templates/skeleton/aws-serverless/packages/db/package.json +0 -14
  140. package/templates/skeleton/aws-serverless/packages/db/src/client.ts +0 -17
  141. package/templates/skeleton/aws-serverless/packages/db/src/index.ts +0 -2
  142. package/templates/skeleton/aws-serverless/packages/db/src/note-model.ts +0 -52
  143. package/templates/skeleton/aws-serverless/packages/db/test/note-model.test.ts +0 -91
  144. package/templates/skeleton/aws-serverless/packages/shared/package.json +0 -11
  145. package/templates/skeleton/aws-serverless/packages/shared/src/env.ts +0 -17
  146. package/templates/skeleton/aws-serverless/packages/shared/src/errors.ts +0 -33
  147. package/templates/skeleton/aws-serverless/packages/shared/src/index.ts +0 -3
  148. package/templates/skeleton/aws-serverless/packages/shared/src/logger.ts +0 -20
  149. package/templates/skeleton/aws-serverless/packages/shared/test/env.test.ts +0 -26
  150. package/templates/skeleton/aws-serverless/packages/shared/test/errors.test.ts +0 -28
  151. package/templates/skeleton/aws-serverless/packages/shared/test/logger.test.ts +0 -19
  152. package/templates/skeleton/aws-serverless/pnpm-lock.yaml +0 -2855
  153. package/templates/skeleton/aws-serverless/pnpm-workspace.yaml +0 -14
  154. package/templates/skeleton/aws-serverless/services/api/package.json +0 -15
  155. package/templates/skeleton/aws-serverless/services/api/src/adapters/sqs-publisher.ts +0 -26
  156. package/templates/skeleton/aws-serverless/services/api/src/handlers/create-note.ts +0 -69
  157. package/templates/skeleton/aws-serverless/services/api/src/handlers/list-notes.ts +0 -37
  158. package/templates/skeleton/aws-serverless/services/api/src/list-main.ts +0 -12
  159. package/templates/skeleton/aws-serverless/services/api/src/main.ts +0 -21
  160. package/templates/skeleton/aws-serverless/services/api/src/usecases/create-note.ts +0 -41
  161. package/templates/skeleton/aws-serverless/services/api/src/usecases/list-notes.ts +0 -14
  162. package/templates/skeleton/aws-serverless/services/api/test/create-note.handler.test.ts +0 -211
  163. package/templates/skeleton/aws-serverless/services/api/test/create-note.usecase.test.ts +0 -45
  164. package/templates/skeleton/aws-serverless/services/api/test/list-notes.test.ts +0 -122
  165. package/templates/skeleton/aws-serverless/services/api/test/sqs-publisher.test.ts +0 -22
  166. package/templates/skeleton/aws-serverless/services/worker/package.json +0 -12
  167. package/templates/skeleton/aws-serverless/services/worker/src/handlers/note-created.ts +0 -15
  168. package/templates/skeleton/aws-serverless/services/worker/src/main.ts +0 -7
  169. package/templates/skeleton/aws-serverless/services/worker/src/usecases/process-note-created.ts +0 -37
  170. package/templates/skeleton/aws-serverless/services/worker/test/note-created.test.ts +0 -61
  171. package/templates/skeleton/aws-serverless/tsconfig.base.json +0 -15
  172. package/templates/skeleton/aws-serverless/tsconfig.json +0 -16
  173. package/templates/skeleton/aws-serverless/vitest.config.ts +0 -14
  174. package/templates/skeleton/node-service/.github/workflows/ci.yml +0 -27
  175. package/templates/skeleton/node-service/.github/workflows/deploy.yml +0 -29
  176. package/templates/skeleton/node-service/README.md +0 -104
  177. package/templates/skeleton/node-service/apps/web/next.config.mjs +0 -17
  178. package/templates/skeleton/node-service/apps/web/package.json +0 -19
  179. package/templates/skeleton/node-service/apps/web/src/app/layout.tsx +0 -17
  180. package/templates/skeleton/node-service/apps/web/src/app/page.tsx +0 -96
  181. package/templates/skeleton/node-service/apps/web/src/lib/api.ts +0 -29
  182. package/templates/skeleton/node-service/apps/web/src/lib/validate.ts +0 -23
  183. package/templates/skeleton/node-service/apps/web/test/shared-validation.test.ts +0 -38
  184. package/templates/skeleton/node-service/apps/web/tsconfig.json +0 -14
  185. package/templates/skeleton/node-service/eslint.config.mjs +0 -20
  186. package/templates/skeleton/node-service/gitignore +0 -54
  187. package/templates/skeleton/node-service/package.json +0 -30
  188. package/templates/skeleton/node-service/packages/core/package.json +0 -11
  189. package/templates/skeleton/node-service/packages/core/src/events.ts +0 -14
  190. package/templates/skeleton/node-service/packages/core/src/index.ts +0 -15
  191. package/templates/skeleton/node-service/packages/core/src/note.ts +0 -69
  192. package/templates/skeleton/node-service/packages/core/test/events.test.ts +0 -23
  193. package/templates/skeleton/node-service/packages/core/test/note.test.ts +0 -101
  194. package/templates/skeleton/node-service/packages/db/package.json +0 -12
  195. package/templates/skeleton/node-service/packages/db/src/index.ts +0 -1
  196. package/templates/skeleton/node-service/packages/db/src/note-store.ts +0 -100
  197. package/templates/skeleton/node-service/packages/db/test/note-store.test.ts +0 -100
  198. package/templates/skeleton/node-service/packages/shared/package.json +0 -11
  199. package/templates/skeleton/node-service/packages/shared/src/env.ts +0 -17
  200. package/templates/skeleton/node-service/packages/shared/src/errors.ts +0 -33
  201. package/templates/skeleton/node-service/packages/shared/src/index.ts +0 -3
  202. package/templates/skeleton/node-service/packages/shared/src/logger.ts +0 -20
  203. package/templates/skeleton/node-service/packages/shared/test/env.test.ts +0 -26
  204. package/templates/skeleton/node-service/packages/shared/test/errors.test.ts +0 -28
  205. package/templates/skeleton/node-service/packages/shared/test/logger.test.ts +0 -19
  206. package/templates/skeleton/node-service/pnpm-lock.yaml +0 -2402
  207. package/templates/skeleton/node-service/pnpm-workspace.yaml +0 -13
  208. package/templates/skeleton/node-service/scripts/build-artifact.mjs +0 -34
  209. package/templates/skeleton/node-service/services/api/package.json +0 -17
  210. package/templates/skeleton/node-service/services/api/src/adapters/spool-publisher.ts +0 -23
  211. package/templates/skeleton/node-service/services/api/src/handlers/create-note.ts +0 -40
  212. package/templates/skeleton/node-service/services/api/src/handlers/list-notes.ts +0 -23
  213. package/templates/skeleton/node-service/services/api/src/main.ts +0 -40
  214. package/templates/skeleton/node-service/services/api/src/server.ts +0 -172
  215. package/templates/skeleton/node-service/services/api/src/static-dir.ts +0 -20
  216. package/templates/skeleton/node-service/services/api/src/usecases/create-note.ts +0 -30
  217. package/templates/skeleton/node-service/services/api/src/usecases/list-notes.ts +0 -14
  218. package/templates/skeleton/node-service/services/api/test/artifact.test.ts +0 -92
  219. package/templates/skeleton/node-service/services/api/test/create-note.handler.test.ts +0 -64
  220. package/templates/skeleton/node-service/services/api/test/create-note.usecase.test.ts +0 -43
  221. package/templates/skeleton/node-service/services/api/test/list-notes.test.ts +0 -48
  222. package/templates/skeleton/node-service/services/api/test/package-manager.test.ts +0 -40
  223. package/templates/skeleton/node-service/services/api/test/package-manager.ts +0 -51
  224. package/templates/skeleton/node-service/services/api/test/server.test.ts +0 -208
  225. package/templates/skeleton/node-service/services/api/test/spool-publisher.test.ts +0 -32
  226. package/templates/skeleton/node-service/services/api/test/static-dir.test.ts +0 -29
  227. package/templates/skeleton/node-service/services/worker/package.json +0 -16
  228. package/templates/skeleton/node-service/services/worker/src/main.ts +0 -28
  229. package/templates/skeleton/node-service/services/worker/src/spool.ts +0 -60
  230. package/templates/skeleton/node-service/services/worker/src/usecases/process-note-created.ts +0 -38
  231. package/templates/skeleton/node-service/services/worker/test/process-note-created.test.ts +0 -34
  232. package/templates/skeleton/node-service/services/worker/test/spool.test.ts +0 -76
  233. package/templates/skeleton/node-service/tsconfig.base.json +0 -15
  234. package/templates/skeleton/node-service/tsconfig.json +0 -13
  235. package/templates/skeleton/node-service/vitest.config.ts +0 -12
@@ -1,38 +0,0 @@
1
- // THE load-bearing test of the frontend (web brief §5): the browser-side
2
- // validator and the server-side schema must be the same decision procedure.
3
- // No jsdom, no rendering — the boundary is the point, not the pixels.
4
- import { InvalidNoteError, NewNoteSchema, createNote } from '@app/core';
5
- import { describe, expect, it } from 'vitest';
6
- import { validateNewNote } from '../src/lib/validate';
7
-
8
- const identity = { id: 'n1', createdAt: '2024-01-01T00:00:00.000Z' };
9
-
10
- const CASES: Array<{ name: string; input: unknown }> = [
11
- { name: 'valid minimal', input: { title: 'Hello' } },
12
- { name: 'valid with tags', input: { title: 'Hello', tags: ['a', 'b'] } },
13
- { name: 'empty title', input: { title: '' } },
14
- { name: 'whitespace title', input: { title: ' ' } },
15
- { name: 'title too long', input: { title: 'x'.repeat(201) } },
16
- { name: 'too many tags', input: { title: 'T', tags: Array.from({ length: 11 }, (_, i) => `${i}`) } },
17
- { name: 'empty tag', input: { title: 'T', tags: [''] } },
18
- { name: 'wrong types', input: { title: 42 } },
19
- { name: 'not an object', input: 'nope' },
20
- ];
21
-
22
- describe('one schema, both sides of the wire', () => {
23
- for (const { name, input } of CASES) {
24
- it(`web and core agree on: ${name}`, () => {
25
- const webVerdict = validateNewNote(input);
26
- const coreVerdict = NewNoteSchema.safeParse(input);
27
- expect(webVerdict.ok).toBe(coreVerdict.success);
28
-
29
- // …and the domain function (used by the API usecase) agrees with both.
30
- if (coreVerdict.success) {
31
- expect(() => createNote(input, identity)).not.toThrow();
32
- } else {
33
- expect(() => createNote(input, identity)).toThrow(InvalidNoteError);
34
- expect(webVerdict.issues.length).toBeGreaterThan(0);
35
- }
36
- });
37
- }
38
- });
@@ -1,14 +0,0 @@
1
- {
2
- "extends": "../../tsconfig.base.json",
3
- "compilerOptions": {
4
- "module": "esnext",
5
- "moduleResolution": "bundler",
6
- "jsx": "preserve",
7
- "lib": ["dom", "dom.iterable", "es2022"],
8
- "noEmit": true,
9
- "allowJs": true,
10
- "plugins": [{ "name": "next" }]
11
- },
12
- "include": ["src/**/*.ts", "src/**/*.tsx", "test/**/*.ts"],
13
- "exclude": ["node_modules", "out", ".next"]
14
- }
@@ -1,20 +0,0 @@
1
- import eslint from '@eslint/js';
2
- import globals from 'globals';
3
- import tseslint from 'typescript-eslint';
4
-
5
- export default tseslint.config(
6
- {
7
- ignores: [
8
- '**/node_modules/**',
9
- '**/dist/**',
10
- '**/cdk.out/**',
11
- '**/coverage/**',
12
- '**/.next/**',
13
- '**/out/**',
14
- '**/next-env.d.ts',
15
- ],
16
- },
17
- eslint.configs.recommended,
18
- ...tseslint.configs.recommended,
19
- { languageOptions: { globals: globals.node } },
20
- );
@@ -1,56 +0,0 @@
1
- node_modules/
2
- dist/
3
- coverage/
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
8
- *.tsbuildinfo
9
- .DS_Store
10
- .next/
11
- out/
12
- next-env.d.ts
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
- # per-checkout board selector (`queue/index.mjs board <name>`); the boards are in the config
18
- .claude/queue.board
19
- # gate rounds, one count per branch
20
- .claude/gate-rounds.json
21
- # per-run machine trace (.claude/scripts/run-journal.mjs): forensics that churn
22
- # on every gate and collide across concurrent sessions. Unlike a committed
23
- # evidence directory, this one is per-run and stays out of the repository.
24
- .claude/runs/
25
-
26
- # secrets — never commit these
27
- *.env
28
- .env
29
- .env.*
30
- !.env.example
31
- .envrc
32
- .npmrc
33
- .netrc
34
- .pgpass
35
- id_rsa
36
- id_ed25519
37
- *.pem
38
- *.key
39
- *.p12
40
- *.pfx
41
- *.keystore
42
- *.jks
43
- *.secret
44
- *.secrets
45
- *.token
46
- *.tokens
47
- *.password
48
- *.passwords
49
- *.credential
50
- *.credentials
51
- *.apikey
52
- *.apikeys
53
- *.passwd
54
- *.creds
55
- *.jwt
56
- *.bearer
@@ -1,79 +0,0 @@
1
- import { realpathSync } from 'node:fs';
2
- import { fileURLToPath } from 'node:url';
3
- import { App } from 'aws-cdk-lib';
4
- import { AppStack, nothingGiven } from '../lib/app-stack.js';
5
- import { WebStack } from '../lib/web-stack.js';
6
-
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();
14
-
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 };
18
-
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
- });
33
-
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
- }
@@ -1,3 +0,0 @@
1
- {
2
- "app": "npx tsx bin/app.ts"
3
- }
@@ -1,248 +0,0 @@
1
- import path from 'node:path';
2
- import { fileURLToPath } from 'node:url';
3
- import {
4
- Annotations,
5
- CfnOutput,
6
- Duration,
7
- RemovalPolicy,
8
- Stack,
9
- Token,
10
- type StackProps,
11
- } from 'aws-cdk-lib';
12
- import { CorsHttpMethod, HttpApi, HttpMethod } from 'aws-cdk-lib/aws-apigatewayv2';
13
- import { HttpLambdaIntegration } from 'aws-cdk-lib/aws-apigatewayv2-integrations';
14
- import { ComparisonOperator, TreatMissingData } from 'aws-cdk-lib/aws-cloudwatch';
15
- import { AttributeType, BillingMode, Table } from 'aws-cdk-lib/aws-dynamodb';
16
- import { Runtime } from 'aws-cdk-lib/aws-lambda';
17
- import { SqsEventSource } from 'aws-cdk-lib/aws-lambda-event-sources';
18
- import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';
19
- import { Queue } from 'aws-cdk-lib/aws-sqs';
20
- import type { Construct } from 'constructs';
21
-
22
- const here = path.dirname(fileURLToPath(import.meta.url));
23
- const workspaceRoot = path.resolve(here, '..', '..');
24
-
25
- export interface AppStackProps extends StackProps {
26
- /**
27
- * Browser origins allowed to call the API.
28
- *
29
- * `bin/app.ts` passes the CloudFront origin from `WebStack`, so a deployed
30
- * app works without configuring anything. Pass this (or
31
- * `-c allowedOrigins=https://…`, comma-separated, which the entrypoint
32
- * prefers when given) for a custom domain, a staging origin, or several.
33
- */
34
- allowedOrigins?: string[];
35
- }
36
-
37
- /**
38
- * Local dev only: a real origin is a deliberate act, not a default.
39
- *
40
- * The port belongs to `apps/web`'s `dev` script — that is the source of truth,
41
- * and a test reads it from there rather than restating it. A default naming a
42
- * port nothing serves is worse than no default: it looks configured.
43
- */
44
- const DEFAULT_ALLOWED_ORIGINS = ['http://localhost:3001'];
45
-
46
- /**
47
- * Props win, then `-c allowedOrigins=a,b`, then the local default.
48
- *
49
- * Anything given but unusable — an empty list, a context value that trims away
50
- * to nothing, a value that is not a string — throws rather than falling back.
51
- * The failure this prevents is silent: an API synthesised with an empty
52
- * allow-list blocks every browser call, and the symptom appears far from the
53
- * flag that caused it.
54
- *
55
- * Exported for the tests, and only for them — nothing else imports it. The
56
- * alternative was a case per branch built on its own `AppStack`, which cost a
57
- * full synth and three Lambda bundles each and put the generated project's
58
- * suite over its hook timeout.
59
- */
60
- export function resolveAllowedOrigins(fromProps: string[] | undefined, fromContext: unknown): string[] {
61
- if (fromProps !== undefined) {
62
- if (fromProps.length === 0) {
63
- throw new Error('allowedOrigins was given as an empty list — name an origin, or omit it');
64
- }
65
- return fromProps.map(checkedOrigin);
66
- }
67
- if (nothingGiven(fromContext)) return DEFAULT_ALLOWED_ORIGINS;
68
- if (typeof fromContext !== 'string') {
69
- throw new Error(
70
- `allowedOrigins must be a comma-separated string, got ${typeof fromContext} — ` +
71
- 'use -c allowedOrigins=https://example.com',
72
- );
73
- }
74
- const origins = fromContext
75
- .split(',')
76
- .map((origin) => origin.trim())
77
- .filter(Boolean);
78
- if (origins.length === 0) {
79
- throw new Error(`allowedOrigins parsed to no origin at all from ${JSON.stringify(fromContext)}`);
80
- }
81
- return origins.map(checkedOrigin);
82
- }
83
-
84
- /**
85
- * Whether the caller supplied nothing. Exported because `bin/app.ts` decides
86
- * the same question about the same value, and two spellings of "nothing given"
87
- * is how a `null` in `cdk.json` ends up meaning "the operator chose an origin"
88
- * in one file and "use the default" in the other.
89
- */
90
- export function nothingGiven(contextValue: unknown): boolean {
91
- return contextValue === undefined || contextValue === null;
92
- }
93
-
94
- /**
95
- * An origin a browser could actually send: `*`, or a scheme plus a host.
96
- *
97
- * The check exists because the near-misses synthesise green and fail in the
98
- * browser, far from the flag that caused them — `app.example.com` with the
99
- * scheme left off is the likeliest of them, since an `Origin` header always
100
- * carries one.
101
- */
102
- function checkedOrigin(origin: string): string {
103
- if (origin === '*') return origin;
104
- // A cross-stack origin is a token here and a domain only at deploy time —
105
- // there is nothing to parse yet, and refusing it would refuse the wiring
106
- // this stack ships with.
107
- if (Token.isUnresolved(origin)) return origin;
108
- let parsed: URL;
109
- try {
110
- parsed = new URL(origin);
111
- } catch {
112
- throw new Error(
113
- `allowedOrigins entry ${JSON.stringify(origin)} is not an origin — ` +
114
- 'a browser sends scheme://host[:port], as in https://app.example.com',
115
- );
116
- }
117
- if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
118
- throw new Error(`allowedOrigins entry ${JSON.stringify(origin)} is not an http(s) origin`);
119
- }
120
- // The NORMALISED form, not what was typed: a trailing slash, an uppercase
121
- // scheme, a path, a unicode host or a redundant :443 all synthesise green and
122
- // then match no Origin header a browser sends.
123
- return parsed.origin;
124
- }
125
-
126
- export class AppStack extends Stack {
127
- constructor(scope: Construct, id: string, props?: AppStackProps) {
128
- super(scope, id, props);
129
-
130
- const allowedOrigins = resolveAllowedOrigins(
131
- props?.allowedOrigins,
132
- this.node.tryGetContext('allowedOrigins'),
133
- );
134
-
135
- // `*` is honoured — an operator who typed it into a flag has chosen it, and
136
- // refusing would push them to edit this stack instead, which is worse. It
137
- // does not get to be quiet about it: the whole point of not shipping the
138
- // wildcard is that reaching for it should be a visible act.
139
- if (allowedOrigins.includes('*')) {
140
- // V2 because it can be acknowledged: `addWarning` hard-blocks
141
- // `cdk synth --strict`, which would turn "loud" into "refused" for an
142
- // operator who chose the wildcard deliberately.
143
- Annotations.of(this).addWarningV2(
144
- '@app/allowed-origins:wildcard',
145
- 'allowedOrigins includes "*": every site may call this API from a browser. ' +
146
- 'Name the origins instead unless this is a throwaway environment.',
147
- );
148
- }
149
-
150
- // --- storage: one single-table DynamoDB table --------------------------
151
- const table = new Table(this, 'NotesTable', {
152
- partitionKey: { name: 'pk', type: AttributeType.STRING },
153
- sortKey: { name: 'sk', type: AttributeType.STRING },
154
- billingMode: BillingMode.PAY_PER_REQUEST,
155
- // Skeleton default: destroyable. Flip to RETAIN before storing real data.
156
- removalPolicy: RemovalPolicy.DESTROY,
157
- });
158
-
159
- // --- queue with DLQ + alarm -------------------------------------------
160
- const deadLetterQueue = new Queue(this, 'NotesDlq', {
161
- retentionPeriod: Duration.days(14),
162
- });
163
- const queue = new Queue(this, 'NotesQueue', {
164
- visibilityTimeout: Duration.seconds(30),
165
- deadLetterQueue: { queue: deadLetterQueue, maxReceiveCount: 3 },
166
- });
167
- deadLetterQueue
168
- .metricApproximateNumberOfMessagesVisible({ period: Duration.minutes(1) })
169
- .createAlarm(this, 'DlqNotEmptyAlarm', {
170
- alarmDescription: 'A message reached the DLQ — a consumer is failing.',
171
- threshold: 1,
172
- evaluationPeriods: 1,
173
- comparisonOperator: ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD,
174
- treatMissingData: TreatMissingData.NOT_BREACHING,
175
- });
176
-
177
- // --- functions ---------------------------------------------------------
178
- const bundling = { sourceMap: false } as const;
179
- const apiFunction = new NodejsFunction(this, 'CreateNoteFunction', {
180
- entry: path.join(workspaceRoot, 'services', 'api', 'src', 'main.ts'),
181
- handler: 'handler',
182
- runtime: Runtime.NODEJS_22_X,
183
- memorySize: 256,
184
- timeout: Duration.seconds(10),
185
- depsLockFilePath: path.join(workspaceRoot, 'pnpm-lock.yaml'),
186
- bundling,
187
- environment: {
188
- TABLE_NAME: table.tableName,
189
- QUEUE_URL: queue.queueUrl,
190
- },
191
- });
192
-
193
- const listFunction = new NodejsFunction(this, 'ListNotesFunction', {
194
- entry: path.join(workspaceRoot, 'services', 'api', 'src', 'list-main.ts'),
195
- handler: 'handler',
196
- runtime: Runtime.NODEJS_22_X,
197
- memorySize: 256,
198
- timeout: Duration.seconds(10),
199
- depsLockFilePath: path.join(workspaceRoot, 'pnpm-lock.yaml'),
200
- bundling,
201
- environment: {
202
- TABLE_NAME: table.tableName,
203
- },
204
- });
205
-
206
- const workerFunction = new NodejsFunction(this, 'NoteCreatedWorker', {
207
- entry: path.join(workspaceRoot, 'services', 'worker', 'src', 'main.ts'),
208
- handler: 'handler',
209
- runtime: Runtime.NODEJS_22_X,
210
- memorySize: 256,
211
- timeout: Duration.seconds(10),
212
- depsLockFilePath: path.join(workspaceRoot, 'pnpm-lock.yaml'),
213
- bundling,
214
- });
215
- workerFunction.addEventSource(new SqsEventSource(queue, { batchSize: 1 }));
216
-
217
- // --- least-privilege grants: exactly what each function does ----------
218
- table.grantWriteData(apiFunction); // the creator only puts
219
- queue.grantSendMessages(apiFunction); // and publishes
220
- table.grantReadData(listFunction); // the lister only reads
221
-
222
- // --- the HTTP routes ---------------------------------------------------
223
- // CORS: the web bundle is served from another origin (CloudFront), so the
224
- // API has to name who may call it. `*` is not that name — a starter
225
- // multiplies whatever it ships, and a wildcard here becomes the default of
226
- // every project generated from it.
227
- const httpApi = new HttpApi(this, 'NotesApi', {
228
- corsPreflight: {
229
- allowOrigins: allowedOrigins,
230
- allowMethods: [CorsHttpMethod.GET, CorsHttpMethod.POST],
231
- allowHeaders: ['content-type'],
232
- },
233
- });
234
- httpApi.addRoutes({
235
- path: '/notes',
236
- methods: [HttpMethod.POST],
237
- integration: new HttpLambdaIntegration('CreateNoteIntegration', apiFunction),
238
- });
239
- httpApi.addRoutes({
240
- path: '/notes',
241
- methods: [HttpMethod.GET],
242
- integration: new HttpLambdaIntegration('ListNotesIntegration', listFunction),
243
- });
244
-
245
- new CfnOutput(this, 'ApiUrl', { value: httpApi.apiEndpoint });
246
- new CfnOutput(this, 'DlqUrl', { value: deadLetterQueue.queueUrl });
247
- }
248
- }
@@ -1,45 +0,0 @@
1
- // Stateless serving for the static web export: a private S3 bucket behind
2
- // CloudFront. Deliberately no BucketDeployment construct — synth must never
3
- // depend on `next build` having run. The bundle ships via `aws s3 sync`
4
- // (see README), keeping the stack small and the dependency one-way.
5
- import { CfnOutput, RemovalPolicy, Stack, type StackProps } from 'aws-cdk-lib';
6
- import { Distribution, ViewerProtocolPolicy } from 'aws-cdk-lib/aws-cloudfront';
7
- import { S3BucketOrigin } from 'aws-cdk-lib/aws-cloudfront-origins';
8
- import { BlockPublicAccess, Bucket } from 'aws-cdk-lib/aws-s3';
9
- import type { Construct } from 'constructs';
10
-
11
- export class WebStack extends Stack {
12
- /**
13
- * Where the browser loads the app from — and therefore the origin the API
14
- * has to allow. A synth-time token: CDK resolves it across stacks as an
15
- * `Fn::ImportValue`, so `AppStack` can name it without either stack having
16
- * been deployed yet.
17
- */
18
- public readonly origin: string;
19
-
20
- constructor(scope: Construct, id: string, props?: StackProps) {
21
- super(scope, id, props);
22
-
23
- const bucket = new Bucket(this, 'WebBucket', {
24
- blockPublicAccess: BlockPublicAccess.BLOCK_ALL,
25
- // Skeleton default: destroyable. Flip to RETAIN before real traffic.
26
- removalPolicy: RemovalPolicy.DESTROY,
27
- });
28
-
29
- const distribution = new Distribution(this, 'WebDistribution', {
30
- defaultBehavior: {
31
- origin: S3BucketOrigin.withOriginAccessControl(bucket),
32
- viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
33
- },
34
- defaultRootObject: 'index.html',
35
- });
36
-
37
- this.origin = `https://${distribution.domainName}`;
38
-
39
- new CfnOutput(this, 'WebBucketName', { value: bucket.bucketName });
40
- new CfnOutput(this, 'WebUrl', { value: this.origin });
41
- // The upload step needs this to invalidate the edge cache: a synced bucket
42
- // whose distribution still serves the old objects has not deployed.
43
- new CfnOutput(this, 'WebDistributionId', { value: distribution.distributionId });
44
- }
45
- }
@@ -1,18 +0,0 @@
1
- {
2
- "name": "@app/infra",
3
- "version": "0.1.0",
4
- "private": true,
5
- "type": "module",
6
- "scripts": {
7
- "synth": "cdk synth --quiet",
8
- "deploy": "cdk deploy",
9
- "destroy": "cdk destroy"
10
- },
11
- "dependencies": {
12
- "aws-cdk-lib": "^2.261.0",
13
- "constructs": "^10.7.1"
14
- },
15
- "devDependencies": {
16
- "aws-cdk": "^2.1132.0"
17
- }
18
- }