create-agent-rig 0.1.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.
Files changed (146) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +87 -0
  3. package/package.json +54 -0
  4. package/packages/cli/dist/commands/create.js +109 -0
  5. package/packages/cli/dist/index.js +102 -0
  6. package/packages/cli/dist/lib/colors.js +14 -0
  7. package/packages/cli/dist/lib/composition.js +20 -0
  8. package/packages/cli/dist/lib/copy-tree.js +91 -0
  9. package/packages/cli/dist/lib/prompts.js +24 -0
  10. package/packages/cli/dist/lib/substitute.js +22 -0
  11. package/packages/cli/dist/lib/summary.js +41 -0
  12. package/packages/cli/dist/lib/targets.js +14 -0
  13. package/packages/cli/dist/templates.js +21 -0
  14. package/scripts/prepare.mjs +29 -0
  15. package/templates/agent-os/stack/aws-cdk/.claude/agents/cdk-diff-reviewer.md +49 -0
  16. package/templates/agent-os/stack/aws-cdk/.claude/rules/aws-cdk.md +59 -0
  17. package/templates/agent-os/stack/aws-cdk/.claude/skills/post-deploy-verify/SKILL.md +51 -0
  18. package/templates/agent-os/stack/node-ts/.claude/rules/node-ts.md +39 -0
  19. package/templates/agent-os/universal/.claude/agents/code-reviewer.md +36 -0
  20. package/templates/agent-os/universal/.claude/agents/security-scanner.md +40 -0
  21. package/templates/agent-os/universal/.claude/agents/test-writer.md +36 -0
  22. package/templates/agent-os/universal/.claude/hooks/block-no-verify.mjs +43 -0
  23. package/templates/agent-os/universal/.claude/hooks/guard-core-purity.mjs +79 -0
  24. package/templates/agent-os/universal/.claude/hooks/guard-web-boundary.mjs +53 -0
  25. package/templates/agent-os/universal/.claude/rules/architecture.md +74 -0
  26. package/templates/agent-os/universal/.claude/rules/autonomy.md +81 -0
  27. package/templates/agent-os/universal/.claude/rules/workflow.md +62 -0
  28. package/templates/agent-os/universal/.claude/settings.json +28 -0
  29. package/templates/agent-os/universal/.claude/skills/pr-ship/SKILL.md +53 -0
  30. package/templates/agent-os/universal/CLAUDE.md +62 -0
  31. package/templates/skeleton/aws-serverless/.github/workflows/ci.yml +23 -0
  32. package/templates/skeleton/aws-serverless/README.md +78 -0
  33. package/templates/skeleton/aws-serverless/apps/web/next.config.mjs +17 -0
  34. package/templates/skeleton/aws-serverless/apps/web/package.json +19 -0
  35. package/templates/skeleton/aws-serverless/apps/web/src/app/layout.tsx +17 -0
  36. package/templates/skeleton/aws-serverless/apps/web/src/app/page.tsx +96 -0
  37. package/templates/skeleton/aws-serverless/apps/web/src/lib/api.ts +29 -0
  38. package/templates/skeleton/aws-serverless/apps/web/src/lib/validate.ts +23 -0
  39. package/templates/skeleton/aws-serverless/apps/web/test/shared-validation.test.ts +38 -0
  40. package/templates/skeleton/aws-serverless/apps/web/tsconfig.json +14 -0
  41. package/templates/skeleton/aws-serverless/eslint.config.mjs +20 -0
  42. package/templates/skeleton/aws-serverless/gitignore +9 -0
  43. package/templates/skeleton/aws-serverless/infra/bin/app.ts +19 -0
  44. package/templates/skeleton/aws-serverless/infra/cdk.json +3 -0
  45. package/templates/skeleton/aws-serverless/infra/lib/app-stack.ts +116 -0
  46. package/templates/skeleton/aws-serverless/infra/lib/web-stack.ts +32 -0
  47. package/templates/skeleton/aws-serverless/infra/package.json +18 -0
  48. package/templates/skeleton/aws-serverless/infra/test/app-stack.test.ts +104 -0
  49. package/templates/skeleton/aws-serverless/infra/test/web-stack.test.ts +41 -0
  50. package/templates/skeleton/aws-serverless/package.json +30 -0
  51. package/templates/skeleton/aws-serverless/packages/core/package.json +11 -0
  52. package/templates/skeleton/aws-serverless/packages/core/src/events.ts +14 -0
  53. package/templates/skeleton/aws-serverless/packages/core/src/index.ts +15 -0
  54. package/templates/skeleton/aws-serverless/packages/core/src/note.ts +69 -0
  55. package/templates/skeleton/aws-serverless/packages/core/test/events.test.ts +23 -0
  56. package/templates/skeleton/aws-serverless/packages/core/test/note.test.ts +101 -0
  57. package/templates/skeleton/aws-serverless/packages/db/package.json +14 -0
  58. package/templates/skeleton/aws-serverless/packages/db/src/client.ts +17 -0
  59. package/templates/skeleton/aws-serverless/packages/db/src/index.ts +2 -0
  60. package/templates/skeleton/aws-serverless/packages/db/src/note-model.ts +52 -0
  61. package/templates/skeleton/aws-serverless/packages/db/test/note-model.test.ts +91 -0
  62. package/templates/skeleton/aws-serverless/packages/shared/package.json +11 -0
  63. package/templates/skeleton/aws-serverless/packages/shared/src/env.ts +17 -0
  64. package/templates/skeleton/aws-serverless/packages/shared/src/errors.ts +33 -0
  65. package/templates/skeleton/aws-serverless/packages/shared/src/index.ts +3 -0
  66. package/templates/skeleton/aws-serverless/packages/shared/src/logger.ts +20 -0
  67. package/templates/skeleton/aws-serverless/packages/shared/test/env.test.ts +26 -0
  68. package/templates/skeleton/aws-serverless/packages/shared/test/errors.test.ts +28 -0
  69. package/templates/skeleton/aws-serverless/packages/shared/test/logger.test.ts +19 -0
  70. package/templates/skeleton/aws-serverless/pnpm-lock.yaml +2855 -0
  71. package/templates/skeleton/aws-serverless/pnpm-workspace.yaml +14 -0
  72. package/templates/skeleton/aws-serverless/services/api/package.json +15 -0
  73. package/templates/skeleton/aws-serverless/services/api/src/adapters/sqs-publisher.ts +26 -0
  74. package/templates/skeleton/aws-serverless/services/api/src/handlers/create-note.ts +42 -0
  75. package/templates/skeleton/aws-serverless/services/api/src/handlers/list-notes.ts +24 -0
  76. package/templates/skeleton/aws-serverless/services/api/src/list-main.ts +12 -0
  77. package/templates/skeleton/aws-serverless/services/api/src/main.ts +21 -0
  78. package/templates/skeleton/aws-serverless/services/api/src/usecases/create-note.ts +30 -0
  79. package/templates/skeleton/aws-serverless/services/api/src/usecases/list-notes.ts +14 -0
  80. package/templates/skeleton/aws-serverless/services/api/test/create-note.handler.test.ts +92 -0
  81. package/templates/skeleton/aws-serverless/services/api/test/create-note.usecase.test.ts +45 -0
  82. package/templates/skeleton/aws-serverless/services/api/test/list-notes.test.ts +51 -0
  83. package/templates/skeleton/aws-serverless/services/api/test/sqs-publisher.test.ts +22 -0
  84. package/templates/skeleton/aws-serverless/services/worker/package.json +12 -0
  85. package/templates/skeleton/aws-serverless/services/worker/src/handlers/note-created.ts +15 -0
  86. package/templates/skeleton/aws-serverless/services/worker/src/main.ts +7 -0
  87. package/templates/skeleton/aws-serverless/services/worker/src/usecases/process-note-created.ts +37 -0
  88. package/templates/skeleton/aws-serverless/services/worker/test/note-created.test.ts +61 -0
  89. package/templates/skeleton/aws-serverless/tsconfig.base.json +15 -0
  90. package/templates/skeleton/aws-serverless/tsconfig.json +16 -0
  91. package/templates/skeleton/aws-serverless/vitest.config.ts +14 -0
  92. package/templates/skeleton/node-service/.github/workflows/ci.yml +22 -0
  93. package/templates/skeleton/node-service/README.md +74 -0
  94. package/templates/skeleton/node-service/apps/web/next.config.mjs +17 -0
  95. package/templates/skeleton/node-service/apps/web/package.json +19 -0
  96. package/templates/skeleton/node-service/apps/web/src/app/layout.tsx +17 -0
  97. package/templates/skeleton/node-service/apps/web/src/app/page.tsx +96 -0
  98. package/templates/skeleton/node-service/apps/web/src/lib/api.ts +29 -0
  99. package/templates/skeleton/node-service/apps/web/src/lib/validate.ts +23 -0
  100. package/templates/skeleton/node-service/apps/web/test/shared-validation.test.ts +38 -0
  101. package/templates/skeleton/node-service/apps/web/tsconfig.json +14 -0
  102. package/templates/skeleton/node-service/eslint.config.mjs +20 -0
  103. package/templates/skeleton/node-service/gitignore +9 -0
  104. package/templates/skeleton/node-service/package.json +28 -0
  105. package/templates/skeleton/node-service/packages/core/package.json +11 -0
  106. package/templates/skeleton/node-service/packages/core/src/events.ts +14 -0
  107. package/templates/skeleton/node-service/packages/core/src/index.ts +15 -0
  108. package/templates/skeleton/node-service/packages/core/src/note.ts +69 -0
  109. package/templates/skeleton/node-service/packages/core/test/events.test.ts +23 -0
  110. package/templates/skeleton/node-service/packages/core/test/note.test.ts +101 -0
  111. package/templates/skeleton/node-service/packages/db/package.json +12 -0
  112. package/templates/skeleton/node-service/packages/db/src/index.ts +1 -0
  113. package/templates/skeleton/node-service/packages/db/src/note-store.ts +63 -0
  114. package/templates/skeleton/node-service/packages/db/test/note-store.test.ts +80 -0
  115. package/templates/skeleton/node-service/packages/shared/package.json +11 -0
  116. package/templates/skeleton/node-service/packages/shared/src/env.ts +17 -0
  117. package/templates/skeleton/node-service/packages/shared/src/errors.ts +33 -0
  118. package/templates/skeleton/node-service/packages/shared/src/index.ts +3 -0
  119. package/templates/skeleton/node-service/packages/shared/src/logger.ts +20 -0
  120. package/templates/skeleton/node-service/packages/shared/test/env.test.ts +26 -0
  121. package/templates/skeleton/node-service/packages/shared/test/errors.test.ts +28 -0
  122. package/templates/skeleton/node-service/packages/shared/test/logger.test.ts +19 -0
  123. package/templates/skeleton/node-service/pnpm-lock.yaml +2399 -0
  124. package/templates/skeleton/node-service/pnpm-workspace.yaml +13 -0
  125. package/templates/skeleton/node-service/services/api/package.json +17 -0
  126. package/templates/skeleton/node-service/services/api/src/adapters/spool-publisher.ts +23 -0
  127. package/templates/skeleton/node-service/services/api/src/handlers/create-note.ts +40 -0
  128. package/templates/skeleton/node-service/services/api/src/handlers/list-notes.ts +23 -0
  129. package/templates/skeleton/node-service/services/api/src/main.ts +47 -0
  130. package/templates/skeleton/node-service/services/api/src/server.ts +89 -0
  131. package/templates/skeleton/node-service/services/api/src/usecases/create-note.ts +30 -0
  132. package/templates/skeleton/node-service/services/api/src/usecases/list-notes.ts +14 -0
  133. package/templates/skeleton/node-service/services/api/test/create-note.handler.test.ts +64 -0
  134. package/templates/skeleton/node-service/services/api/test/create-note.usecase.test.ts +43 -0
  135. package/templates/skeleton/node-service/services/api/test/list-notes.test.ts +48 -0
  136. package/templates/skeleton/node-service/services/api/test/server.test.ts +123 -0
  137. package/templates/skeleton/node-service/services/api/test/spool-publisher.test.ts +32 -0
  138. package/templates/skeleton/node-service/services/worker/package.json +16 -0
  139. package/templates/skeleton/node-service/services/worker/src/main.ts +28 -0
  140. package/templates/skeleton/node-service/services/worker/src/spool.ts +60 -0
  141. package/templates/skeleton/node-service/services/worker/src/usecases/process-note-created.ts +38 -0
  142. package/templates/skeleton/node-service/services/worker/test/process-note-created.test.ts +34 -0
  143. package/templates/skeleton/node-service/services/worker/test/spool.test.ts +76 -0
  144. package/templates/skeleton/node-service/tsconfig.base.json +15 -0
  145. package/templates/skeleton/node-service/tsconfig.json +13 -0
  146. package/templates/skeleton/node-service/vitest.config.ts +12 -0
@@ -0,0 +1,60 @@
1
+ // A directory is the queue: one JSON file per message. Retries are encoded in
2
+ // the filename (msg.json → msg.retry1.json → …); after maxAttempts the file
3
+ // moves to the DLQ directory and an alarm line is logged. Same discipline as
4
+ // any managed queue — visible failure, bounded retries, no silent loss.
5
+ import { mkdir, readFile, readdir, rename, rm } from 'node:fs/promises';
6
+ import path from 'node:path';
7
+ import type { Logger } from '@app/shared';
8
+
9
+ export interface SpoolConfig {
10
+ queueDir: string;
11
+ dlqDir: string;
12
+ /** Total attempts a message gets before it is dead-lettered. */
13
+ maxAttempts: number;
14
+ }
15
+
16
+ export interface SpoolDeps {
17
+ log: Logger;
18
+ process(raw: string): Promise<void>;
19
+ }
20
+
21
+ const RETRY_PATTERN = /\.retry(\d+)\.json$/;
22
+
23
+ const attemptOf = (name: string): number => {
24
+ const match = RETRY_PATTERN.exec(name);
25
+ return match ? Number.parseInt(match[1]!, 10) + 1 : 1;
26
+ };
27
+
28
+ const withRetrySuffix = (name: string, attempt: number): string =>
29
+ name.replace(/(\.retry\d+)?\.json$/, `.retry${attempt}.json`);
30
+
31
+ /** One pass over the queue. Returns how many messages were processed successfully. */
32
+ export async function processSpoolOnce(config: SpoolConfig, deps: SpoolDeps): Promise<number> {
33
+ await mkdir(config.queueDir, { recursive: true });
34
+ await mkdir(config.dlqDir, { recursive: true });
35
+ let processed = 0;
36
+
37
+ const entries = (await readdir(config.queueDir)).filter((name) => name.endsWith('.json')).sort();
38
+ for (const name of entries) {
39
+ const file = path.join(config.queueDir, name);
40
+ try {
41
+ await deps.process(await readFile(file, 'utf8'));
42
+ await rm(file);
43
+ processed += 1;
44
+ } catch (error) {
45
+ const attempt = attemptOf(name);
46
+ if (attempt >= config.maxAttempts) {
47
+ await rename(file, path.join(config.dlqDir, name));
48
+ deps.log.error('ALARM: message dead-lettered', {
49
+ file: name,
50
+ attempts: attempt,
51
+ error: String(error),
52
+ });
53
+ } else {
54
+ await rename(file, path.join(config.queueDir, withRetrySuffix(name, attempt)));
55
+ deps.log.warn('message failed, will retry', { file: name, attempt });
56
+ }
57
+ }
58
+ }
59
+ return processed;
60
+ }
@@ -0,0 +1,38 @@
1
+ // Queue discipline: validate the message against the shared event schema; a
2
+ // message that does not parse is poison — throw, let the spool retry it, and
3
+ // after the retry budget the DLQ directory (watched by the alarm log line)
4
+ // catches it. Never swallow poison.
5
+ import { NoteCreatedEventSchema, type NoteCreatedEvent } from '@app/core';
6
+ import { ValidationError, type Logger } from '@app/shared';
7
+
8
+ export interface ProcessDeps {
9
+ log: Logger;
10
+ }
11
+
12
+ export function parseNoteCreatedEvent(raw: string): NoteCreatedEvent {
13
+ let candidate: unknown;
14
+ try {
15
+ candidate = JSON.parse(raw);
16
+ } catch {
17
+ throw new ValidationError('message body is not JSON');
18
+ }
19
+ const parsed = NoteCreatedEventSchema.safeParse(candidate);
20
+ if (!parsed.success) {
21
+ throw new ValidationError(
22
+ 'message is not a note.created event',
23
+ parsed.error.issues.map((issue) => `${issue.path.join('.')}: ${issue.message}`),
24
+ );
25
+ }
26
+ return parsed.data;
27
+ }
28
+
29
+ export async function processNoteCreated(raw: string, deps: ProcessDeps): Promise<void> {
30
+ const event = parseNoteCreatedEvent(raw);
31
+ // The skeleton's "processing" is deliberately small: acknowledge the note.
32
+ // Replace this with real downstream work; keep the validate-first shape.
33
+ deps.log.info('note.created processed', {
34
+ noteId: event.note.id,
35
+ slug: event.note.slug,
36
+ tagCount: event.note.tags.length,
37
+ });
38
+ }
@@ -0,0 +1,34 @@
1
+ import { createNote, makeNoteCreatedEvent } from '@app/core';
2
+ import { ValidationError, createLogger } from '@app/shared';
3
+ import { describe, expect, it } from 'vitest';
4
+ import { parseNoteCreatedEvent, processNoteCreated } from '../src/usecases/process-note-created.js';
5
+
6
+ const note = createNote({ title: 'Hello' }, { id: 'n1', createdAt: '2024-01-01T00:00:00.000Z' });
7
+ const validBody = JSON.stringify(makeNoteCreatedEvent(note));
8
+
9
+ describe('parseNoteCreatedEvent', () => {
10
+ it('parses a valid event', () => {
11
+ expect(parseNoteCreatedEvent(validBody).note.id).toBe('n1');
12
+ });
13
+
14
+ it('throws ValidationError for non-JSON bodies', () => {
15
+ expect(() => parseNoteCreatedEvent('not json')).toThrow(ValidationError);
16
+ });
17
+
18
+ it('throws ValidationError for JSON that is not a note.created event', () => {
19
+ expect(() => parseNoteCreatedEvent(JSON.stringify({ type: 'other' }))).toThrow(ValidationError);
20
+ });
21
+ });
22
+
23
+ describe('processNoteCreated', () => {
24
+ it('logs the processed note', async () => {
25
+ const lines: string[] = [];
26
+ await processNoteCreated(validBody, { log: createLogger({}, (line) => lines.push(line)) });
27
+ expect(lines.join('\n')).toContain('n1');
28
+ });
29
+
30
+ it('lets poison messages throw — the DLQ is the safety net, not a catch block', async () => {
31
+ const log = createLogger({}, () => {});
32
+ await expect(processNoteCreated('poison', { log })).rejects.toThrow(ValidationError);
33
+ });
34
+ });
@@ -0,0 +1,76 @@
1
+ import { mkdtemp, readdir, rm, writeFile } from 'node:fs/promises';
2
+ import { tmpdir } from 'node:os';
3
+ import path from 'node:path';
4
+ import { createNote, makeNoteCreatedEvent } from '@app/core';
5
+ import { createLogger } from '@app/shared';
6
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
7
+ import { processNoteCreated } from '../src/usecases/process-note-created.js';
8
+ import { processSpoolOnce, type SpoolConfig } from '../src/spool.js';
9
+
10
+ const note = createNote({ title: 'Hello' }, { id: 'n1', createdAt: '2024-01-01T00:00:00.000Z' });
11
+ const validBody = JSON.stringify(makeNoteCreatedEvent(note));
12
+
13
+ let dir: string;
14
+ let config: SpoolConfig;
15
+
16
+ beforeEach(async () => {
17
+ dir = await mkdtemp(path.join(tmpdir(), 'spool-worker-'));
18
+ config = {
19
+ queueDir: path.join(dir, 'queue'),
20
+ dlqDir: path.join(dir, 'dlq'),
21
+ maxAttempts: 3,
22
+ };
23
+ });
24
+
25
+ afterEach(async () => {
26
+ await rm(dir, { recursive: true, force: true });
27
+ });
28
+
29
+ function makeDeps() {
30
+ const lines: string[] = [];
31
+ const log = createLogger({}, (line) => lines.push(line));
32
+ return { deps: { log, process: (raw: string) => processNoteCreated(raw, { log }) }, lines };
33
+ }
34
+
35
+ async function spool(name: string, content: string) {
36
+ await writeFile(path.join(config.queueDir, name), content);
37
+ }
38
+
39
+ describe('processSpoolOnce', () => {
40
+ it('processes valid messages and removes them', async () => {
41
+ const { deps, lines } = makeDeps();
42
+ await processSpoolOnce(config, deps); // creates the dirs
43
+ await spool('m1.json', validBody);
44
+ await spool('m2.json', validBody);
45
+
46
+ expect(await processSpoolOnce(config, deps)).toBe(2);
47
+ expect(await readdir(config.queueDir)).toEqual([]);
48
+ expect(await readdir(config.dlqDir)).toEqual([]);
49
+ expect(lines.filter((l) => l.includes('note.created processed'))).toHaveLength(2);
50
+ });
51
+
52
+ it('retries poison, then dead-letters it with an ALARM after maxAttempts', async () => {
53
+ const { deps, lines } = makeDeps();
54
+ await processSpoolOnce(config, deps);
55
+ await spool('poison.json', 'not json');
56
+
57
+ expect(await processSpoolOnce(config, deps)).toBe(0); // attempt 1 → retry1
58
+ expect(await readdir(config.queueDir)).toEqual(['poison.retry1.json']);
59
+ expect(await processSpoolOnce(config, deps)).toBe(0); // attempt 2 → retry2
60
+ expect(await readdir(config.queueDir)).toEqual(['poison.retry2.json']);
61
+ expect(await processSpoolOnce(config, deps)).toBe(0); // attempt 3 → DLQ
62
+ expect(await readdir(config.queueDir)).toEqual([]);
63
+ expect(await readdir(config.dlqDir)).toEqual(['poison.retry2.json']);
64
+ expect(lines.some((line) => line.includes('ALARM'))).toBe(true);
65
+ });
66
+
67
+ it('keeps processing good messages when one is poison', async () => {
68
+ const { deps } = makeDeps();
69
+ await processSpoolOnce(config, deps);
70
+ await spool('a-poison.json', '{broken');
71
+ await spool('b-good.json', validBody);
72
+
73
+ expect(await processSpoolOnce(config, deps)).toBe(1);
74
+ expect(await readdir(config.queueDir)).toEqual(['a-poison.retry1.json']);
75
+ });
76
+ });
@@ -0,0 +1,15 @@
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
+ }
@@ -0,0 +1,13 @@
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
+ "vitest.config.ts"
12
+ ]
13
+ }
@@ -0,0 +1,12 @@
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
+ ],
10
+ testTimeout: 30_000,
11
+ },
12
+ });