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,12 +0,0 @@
1
- {
2
- "name": "@app/worker",
3
- "version": "0.1.0",
4
- "private": true,
5
- "type": "module",
6
- "main": "src/main.ts",
7
- "types": "src/main.ts",
8
- "dependencies": {
9
- "@app/core": "workspace:*",
10
- "@app/shared": "workspace:*"
11
- }
12
- }
@@ -1,15 +0,0 @@
1
- import type { SQSEvent } from 'aws-lambda';
2
- import type { ProcessDeps } from '../usecases/process-note-created.js';
3
- import { processNoteCreated } from '../usecases/process-note-created.js';
4
-
5
- /**
6
- * The event source uses batchSize 1, so a throw fails exactly this message and
7
- * SQS redrives it (then the DLQ takes over). Keep the handler a thin loop.
8
- */
9
- export function makeNoteCreatedHandler(deps: ProcessDeps) {
10
- return async (event: SQSEvent): Promise<void> => {
11
- for (const record of event.Records) {
12
- await processNoteCreated(record.body, deps);
13
- }
14
- };
15
- }
@@ -1,7 +0,0 @@
1
- // Composition root for the worker Lambda.
2
- import { createLogger } from '@app/shared';
3
- import { makeNoteCreatedHandler } from './handlers/note-created.js';
4
-
5
- export const handler = makeNoteCreatedHandler({
6
- log: createLogger({ service: 'worker' }),
7
- });
@@ -1,37 +0,0 @@
1
- // Queue discipline: validate the message against the shared event schema; a
2
- // message that does not parse is poison — throw, let SQS retry it, and after
3
- // maxReceiveCount the DLQ (with its alarm) catches it. Never swallow poison.
4
- import { NoteCreatedEventSchema, type NoteCreatedEvent } from '@app/core';
5
- import { ValidationError, type Logger } from '@app/shared';
6
-
7
- export interface ProcessDeps {
8
- log: Logger;
9
- }
10
-
11
- export function parseNoteCreatedEvent(raw: string): NoteCreatedEvent {
12
- let candidate: unknown;
13
- try {
14
- candidate = JSON.parse(raw);
15
- } catch {
16
- throw new ValidationError('message body is not JSON');
17
- }
18
- const parsed = NoteCreatedEventSchema.safeParse(candidate);
19
- if (!parsed.success) {
20
- throw new ValidationError(
21
- 'message is not a note.created event',
22
- parsed.error.issues.map((issue) => `${issue.path.join('.')}: ${issue.message}`),
23
- );
24
- }
25
- return parsed.data;
26
- }
27
-
28
- export async function processNoteCreated(raw: string, deps: ProcessDeps): Promise<void> {
29
- const event = parseNoteCreatedEvent(raw);
30
- // The skeleton's "processing" is deliberately small: acknowledge the note.
31
- // Replace this with real downstream work; keep the validate-first shape.
32
- deps.log.info('note.created processed', {
33
- noteId: event.note.id,
34
- slug: event.note.slug,
35
- tagCount: event.note.tags.length,
36
- });
37
- }
@@ -1,61 +0,0 @@
1
- import type { SQSEvent } from 'aws-lambda';
2
- import { createNote, makeNoteCreatedEvent } from '@app/core';
3
- import { ValidationError, createLogger } from '@app/shared';
4
- import { describe, expect, it } from 'vitest';
5
- import { makeNoteCreatedHandler } from '../src/handlers/note-created.js';
6
- import { parseNoteCreatedEvent, processNoteCreated } from '../src/usecases/process-note-created.js';
7
-
8
- const note = createNote({ title: 'Hello' }, { id: 'n1', createdAt: '2024-01-01T00:00:00.000Z' });
9
- const validBody = JSON.stringify(makeNoteCreatedEvent(note));
10
-
11
- const sqsEvent = (...bodies: string[]): SQSEvent =>
12
- ({ Records: bodies.map((body) => ({ body })) }) as SQSEvent;
13
-
14
- function collectLogs() {
15
- const lines: string[] = [];
16
- return { log: createLogger({}, (line) => lines.push(line)), lines };
17
- }
18
-
19
- describe('parseNoteCreatedEvent', () => {
20
- it('parses a valid event', () => {
21
- expect(parseNoteCreatedEvent(validBody).note.id).toBe('n1');
22
- });
23
-
24
- it('throws ValidationError for non-JSON bodies', () => {
25
- expect(() => parseNoteCreatedEvent('not json')).toThrow(ValidationError);
26
- });
27
-
28
- it('throws ValidationError for JSON that is not a note.created event', () => {
29
- expect(() => parseNoteCreatedEvent(JSON.stringify({ type: 'other' }))).toThrow(
30
- ValidationError,
31
- );
32
- });
33
- });
34
-
35
- describe('processNoteCreated', () => {
36
- it('logs the processed note', async () => {
37
- const { log, lines } = collectLogs();
38
- await processNoteCreated(validBody, { log });
39
- expect(lines.join('\n')).toContain('n1');
40
- });
41
-
42
- it('lets poison messages throw — the DLQ is the safety net, not a catch block', async () => {
43
- const { log } = collectLogs();
44
- await expect(processNoteCreated('poison', { log })).rejects.toThrow(ValidationError);
45
- });
46
- });
47
-
48
- describe('makeNoteCreatedHandler', () => {
49
- it('processes every record in the batch', async () => {
50
- const { log, lines } = collectLogs();
51
- await makeNoteCreatedHandler({ log })(sqsEvent(validBody, validBody));
52
- expect(lines).toHaveLength(2);
53
- });
54
-
55
- it('propagates the failure of a poison record', async () => {
56
- const { log } = collectLogs();
57
- await expect(makeNoteCreatedHandler({ log })(sqsEvent('poison'))).rejects.toThrow(
58
- ValidationError,
59
- );
60
- });
61
- });
@@ -1,15 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "NodeNext",
5
- "moduleResolution": "NodeNext",
6
- "strict": true,
7
- "noUncheckedIndexedAccess": true,
8
- "esModuleInterop": true,
9
- "isolatedModules": true,
10
- "resolveJsonModule": true,
11
- "skipLibCheck": true,
12
- "forceConsistentCasingInFileNames": true,
13
- "types": ["node"]
14
- }
15
- }
@@ -1,16 +0,0 @@
1
- {
2
- "extends": "./tsconfig.base.json",
3
- "compilerOptions": {
4
- "noEmit": true
5
- },
6
- "include": [
7
- "packages/*/src/**/*.ts",
8
- "packages/*/test/**/*.ts",
9
- "services/*/src/**/*.ts",
10
- "services/*/test/**/*.ts",
11
- "infra/bin/**/*.ts",
12
- "infra/lib/**/*.ts",
13
- "infra/test/**/*.ts",
14
- "vitest.config.ts"
15
- ]
16
- }
@@ -1,14 +0,0 @@
1
- import { defineConfig } from 'vitest/config';
2
-
3
- export default defineConfig({
4
- test: {
5
- include: [
6
- 'packages/*/test/**/*.test.ts',
7
- 'services/*/test/**/*.test.ts',
8
- 'apps/*/test/**/*.test.ts',
9
- 'infra/test/**/*.test.ts',
10
- ],
11
- // CDK assertion tests bundle the Lambda entries with esbuild.
12
- testTimeout: 120_000,
13
- },
14
- });
@@ -1,27 +0,0 @@
1
- name: CI
2
-
3
- # The default token is read-only for this job: CI needs nothing else, and a
4
- # workflow that can write is a workflow a compromised dependency can write with.
5
- permissions:
6
- contents: read
7
-
8
- on:
9
- push:
10
- pull_request:
11
-
12
- jobs:
13
- ci:
14
- runs-on: ubuntu-latest
15
- steps:
16
- - uses: actions/checkout@v4
17
- - uses: pnpm/action-setup@v4
18
- with:
19
- version: 11
20
- - uses: actions/setup-node@v4
21
- with:
22
- node-version: 22
23
- cache: pnpm
24
- - run: pnpm install --frozen-lockfile
25
- - run: pnpm lint
26
- - run: pnpm typecheck
27
- - run: pnpm test
@@ -1,29 +0,0 @@
1
- name: Build artifact
2
-
3
- # node-service ships NO deploy destination — a container registry, a VPS, a
4
- # PaaS is the project owner's choice, not the starter's. This builds a runnable
5
- # artifact (dist/) and stops short of delivery: it produces something real,
6
- # then leaves shipping to you. Production is a HUMAN step (see README).
7
- on:
8
- push:
9
- branches: [main, master]
10
- workflow_dispatch:
11
-
12
- jobs:
13
- build-artifact:
14
- runs-on: ubuntu-latest
15
- steps:
16
- - uses: actions/checkout@v4
17
- - uses: pnpm/action-setup@v4
18
- - uses: actions/setup-node@v4
19
- with:
20
- node-version: 22
21
- cache: pnpm
22
- - run: pnpm install --frozen-lockfile
23
- - run: pnpm build:web
24
- - run: pnpm build:artifact
25
- - uses: actions/upload-artifact@v4
26
- with:
27
- name: server-bundle
28
- path: dist/
29
- - run: echo "Artifact built (dist/). Deploy destination is your choice — see README (Deploy)."
@@ -1,104 +0,0 @@
1
- # __PROJECT_NAME__
2
-
3
- A plain Node service generated by `create-agent-rig`, target
4
- `node-service`. Same architecture as every target — pure core, mandatory
5
- usecase layer, single-owner storage model, queue with DLQ discipline — with no
6
- cloud: HTTP via `node:http`, storage in a JSON file, the queue as a spool
7
- directory, the worker as a process. This project is yours: **delete what you
8
- don't need.**
9
-
10
- ## Layout
11
-
12
- ```
13
- packages/core/ pure domain: zod schemas + createNote() (no I/O — hook-enforced)
14
- packages/shared/ logger, loadEnv(zod), typed errors
15
- packages/db/ JsonFileNoteStore — the only code touching the data file
16
- services/api/ POST /notes + GET /notes: payload → handler → usecase → model;
17
- also serves the built web bundle (no second runtime)
18
- services/worker/ spool consumer (3 attempts → var/dlq + ALARM log line)
19
- apps/web/ static Next export; validates with the SAME core schema the
20
- server trusts (imports core+shared only — hook-enforced)
21
- .claude/ the agent operating system: rules, gates, blocking hooks
22
- ```
23
-
24
- The agent-facing map is `CLAUDE.md`; the rules live in `.claude/rules/`.
25
-
26
- ## Run it
27
-
28
- ```sh
29
- pnpm install
30
- pnpm test
31
- pnpm lint
32
- pnpm typecheck
33
- pnpm check # all of the above
34
- ```
35
-
36
- Start the service (two processes; build the web bundle first to get the UI):
37
-
38
- ```sh
39
- pnpm build:web # static Next export → apps/web/out
40
- pnpm start:api # PORT=3000; serves the API and apps/web/out at /
41
- pnpm start:worker # polls var/queue, dead-letters into var/dlq
42
- ```
43
-
44
- Open http://localhost:3000/ — the form validates with the same core schema
45
- the server applies again on POST.
46
-
47
- Configuration is environment-first (`PORT`, `DATA_DIR`, `QUEUE_DIR`, `DLQ_DIR`,
48
- `POLL_INTERVAL_MS`, `STATIC_DIR`) with working defaults — see each service's
49
- `main.ts`.
50
-
51
- ## Deploy it
52
-
53
- This target ships **no deploy destination** — a container registry, a VPS, a
54
- PaaS is your choice, not the starter's. What it ships is a **build that
55
- produces a runnable artifact** and stops there:
56
-
57
- ```sh
58
- pnpm build:artifact # → dist/server.mjs + dist/public/
59
- STATIC_DIR=dist/public node dist/server.mjs # run the artifact anywhere Node runs
60
- ```
61
-
62
- `.github/workflows/deploy.yml` runs this on every push and uploads `dist/` as a
63
- build artifact — it produces something real and delivers it nowhere. Point it
64
- at your host of choice (add a `docker build`, an `scp`, a PaaS deploy step).
65
-
66
- **Production is a human step, on purpose.** The agent operating system's Never
67
- tier forbids an agent from triggering a production deploy, so no automated prod
68
- path ships here — promoting the artifact to production is your reviewed action.
69
-
70
- ## Verify runtime health (CI-green ≠ runtime-healthy)
71
-
72
- ```sh
73
- curl -s -X POST localhost:3000/notes \
74
- -H 'content-type: application/json' \
75
- -d '{"title":"smoke test","tags":["deploy"]}'
76
- # expect: HTTP 201 with { "note": { … } }
77
- ```
78
-
79
- Then confirm the pipeline: the worker logs `note.created processed`, and
80
- **`var/dlq/` stays empty** (an `ALARM` log line means a message was
81
- dead-lettered). On regression: roll back to the previous revision first,
82
- diagnose second.
83
-
84
- ## Boundaries (the short version)
85
-
86
- - Every request follows `payload → handler → usecase → model`. The usecase
87
- layer is mandatory even when it looks like ceremony.
88
- - `packages/core` is pure — the `guard-core-purity` hook refuses I/O, clock,
89
- randomness, and environment access at the tool layer.
90
- - `packages/db` is the only module that touches the stored data. It is a
91
- **single-instance store**: writes serialise inside one `JsonFileNoteStore`
92
- object, and that is the whole of it. Two stores over the same file lose
93
- notes to each other whether they sit in one process or two — share the one
94
- instance, and reach for a real lock before you share the file.
95
- A hard kill between the temp write and the rename leaves a `*.tmp` file
96
- behind; nothing sweeps them.
97
- - `POST /notes` buffers at most **1 MB** of request body and answers `413` past
98
- that. The cap lives in that route, not in the shell: a new route that reads a
99
- body adds its own. Raise it in `services/api/src/server.ts` if your payloads
100
- are bigger; do not remove it.
101
- - A failing queue message is poison: it throws, the spool retries ×3, then the
102
- DLQ gets it and the ALARM line fires. Never wrap the worker in a broad catch.
103
-
104
- See `.claude/rules/architecture.md` for the full rules.
@@ -1,17 +0,0 @@
1
- /** @type {import('next').NextConfig} */
2
- const nextConfig = {
3
- // Static export: one build, no second runtime — the API server (or a CDN)
4
- // serves the bundle. See README for how this target serves it.
5
- output: 'export',
6
- // The core ships as TypeScript source; Next transpiles it for the browser.
7
- transpilePackages: ['@app/core'],
8
- // The core uses NodeNext-style relative imports ("./note.js" resolving to
9
- // note.ts). Turbopack cannot map that, so the build runs webpack (see the
10
- // build script) with the standard extension alias.
11
- webpack: (config) => {
12
- config.resolve.extensionAlias = { '.js': ['.ts', '.tsx', '.js'] };
13
- return config;
14
- },
15
- };
16
-
17
- export default nextConfig;
@@ -1,19 +0,0 @@
1
- {
2
- "name": "@app/web",
3
- "version": "0.1.0",
4
- "private": true,
5
- "scripts": {
6
- "dev": "next dev --port 3001 --webpack",
7
- "build": "next build --webpack"
8
- },
9
- "dependencies": {
10
- "@app/core": "workspace:*",
11
- "next": "^16.2.11",
12
- "react": "^19.2.8",
13
- "react-dom": "^19.2.8"
14
- },
15
- "devDependencies": {
16
- "@types/react": "^19.2.17",
17
- "@types/react-dom": "^19.2.3"
18
- }
19
- }
@@ -1,17 +0,0 @@
1
- import type { ReactNode } from 'react';
2
-
3
- // Deliberately unstyled: a starter's frontend is scaffolding, not a product.
4
- // Polish belongs in the mechanism (the shared validation, the boundary hook),
5
- // not in the pixels — replace all of this freely.
6
- export const metadata = {
7
- title: '__PROJECT_NAME__',
8
- description: 'Notes — generated by create-agent-rig',
9
- };
10
-
11
- export default function RootLayout({ children }: { children: ReactNode }) {
12
- return (
13
- <html lang="en">
14
- <body>{children}</body>
15
- </html>
16
- );
17
- }
@@ -1,96 +0,0 @@
1
- 'use client';
2
- // One page, one point: the SAME domain schema validates here (instant
3
- // feedback) and on the server (trust). See src/lib/validate.ts.
4
- import { useCallback, useEffect, useState } from 'react';
5
- import type { Note } from '@app/core';
6
- import { createNote, listNotes } from '../lib/api';
7
- import { validateNewNote } from '../lib/validate';
8
-
9
- export default function NotesPage() {
10
- const [title, setTitle] = useState('');
11
- const [tags, setTags] = useState('');
12
- const [issues, setIssues] = useState<string[]>([]);
13
- const [notes, setNotes] = useState<Note[]>([]);
14
- const [status, setStatus] = useState<string>('');
15
-
16
- const refresh = useCallback(async () => {
17
- try {
18
- setNotes(await listNotes());
19
- setStatus('');
20
- } catch (error) {
21
- setStatus(`could not load notes: ${String(error)}`);
22
- }
23
- }, []);
24
-
25
- useEffect(() => {
26
- void refresh();
27
- }, [refresh]);
28
-
29
- async function onSubmit(event: React.FormEvent) {
30
- event.preventDefault();
31
- const input = {
32
- title,
33
- tags: tags
34
- .split(',')
35
- .map((tag) => tag.trim())
36
- .filter(Boolean),
37
- };
38
- // Client-side: the same core schema the server will apply again.
39
- const verdict = validateNewNote(input);
40
- setIssues(verdict.issues);
41
- if (!verdict.ok) return;
42
- try {
43
- await createNote(input);
44
- setTitle('');
45
- setTags('');
46
- await refresh();
47
- } catch (error) {
48
- setIssues([String(error)]);
49
- }
50
- }
51
-
52
- return (
53
- <main>
54
- <h1>Notes</h1>
55
- <p>
56
- The form validates with the <em>same</em> core function the server
57
- trusts — one schema, both sides of the wire.
58
- </p>
59
-
60
- <form onSubmit={onSubmit}>
61
- <label>
62
- Title{' '}
63
- <input
64
- name="title"
65
- value={title}
66
- onChange={(event) => setTitle(event.target.value)}
67
- />
68
- </label>{' '}
69
- <label>
70
- Tags (comma-separated){' '}
71
- <input name="tags" value={tags} onChange={(event) => setTags(event.target.value)} />
72
- </label>{' '}
73
- <button type="submit">Create</button>
74
- </form>
75
-
76
- {issues.length > 0 && (
77
- <ul role="alert">
78
- {issues.map((issue) => (
79
- <li key={issue}>{issue}</li>
80
- ))}
81
- </ul>
82
- )}
83
- {status && <p role="status">{status}</p>}
84
-
85
- <h2>Existing</h2>
86
- <ul>
87
- {notes.map((note) => (
88
- <li key={note.id}>
89
- <strong>{note.title}</strong> <code>{note.slug}</code>{' '}
90
- <small>{note.tags.join(', ')}</small>
91
- </li>
92
- ))}
93
- </ul>
94
- </main>
95
- );
96
- }
@@ -1,29 +0,0 @@
1
- // The web talks to the backend over HTTP only — never by importing it
2
- // (the guard-web-boundary hook refuses such imports at the tool layer).
3
- import type { Note } from '@app/core';
4
-
5
- // Same-origin by default (the API server serves this bundle); set
6
- // NEXT_PUBLIC_API_URL at build time when the API lives elsewhere.
7
- const base = process.env.NEXT_PUBLIC_API_URL ?? '';
8
-
9
- export async function createNote(input: { title: string; tags: string[] }): Promise<Note> {
10
- const response = await fetch(`${base}/notes`, {
11
- method: 'POST',
12
- headers: { 'content-type': 'application/json' },
13
- body: JSON.stringify(input),
14
- });
15
- const body = (await response.json()) as { note?: Note; error?: string; issues?: string[] };
16
- if (!response.ok || !body.note) {
17
- throw new Error(body.issues?.join('; ') ?? body.error ?? `HTTP ${response.status}`);
18
- }
19
- return body.note;
20
- }
21
-
22
- export async function listNotes(): Promise<Note[]> {
23
- const response = await fetch(`${base}/notes`);
24
- const body = (await response.json()) as { notes?: Note[]; error?: string };
25
- if (!response.ok || !body.notes) {
26
- throw new Error(body.error ?? `HTTP ${response.status}`);
27
- }
28
- return body.notes;
29
- }
@@ -1,23 +0,0 @@
1
- // The load-bearing import of this whole app: the browser validates with the
2
- // SAME schema the server trusts. Client-side for instant feedback,
3
- // server-side for trust — one function, two sides of the wire. This is what
4
- // core purity buys: `@app/core` has no I/O, so it runs anywhere.
5
- import { NewNoteSchema } from '@app/core';
6
-
7
- export interface ValidationResult {
8
- ok: boolean;
9
- issues: string[];
10
- }
11
-
12
- export function validateNewNote(input: unknown): ValidationResult {
13
- const parsed = NewNoteSchema.safeParse(input);
14
- if (parsed.success) {
15
- return { ok: true, issues: [] };
16
- }
17
- return {
18
- ok: false,
19
- issues: parsed.error.issues.map(
20
- (issue) => `${issue.path.join('.') || 'input'}: ${issue.message}`,
21
- ),
22
- };
23
- }
@@ -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
- '**/coverage/**',
11
- '**/var/**',
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
- );