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.
- package/CHANGELOG.md +462 -3
- package/README.md +267 -305
- package/package.json +8 -14
- package/packages/cli/dist/commands/create.js +56 -88
- package/packages/cli/dist/commands/doctor.js +213 -0
- package/packages/cli/dist/commands/init.js +111 -50
- package/packages/cli/dist/commands/integrations.js +468 -0
- package/packages/cli/dist/commands/setup-wizard.js +61 -0
- package/packages/cli/dist/commands/uninstall.js +1223 -0
- package/packages/cli/dist/commands/upgrade.js +508 -101
- package/packages/cli/dist/index.js +658 -47
- package/packages/cli/dist/integrations/declaration.js +158 -0
- package/packages/cli/dist/integrations/doctor-guards.js +126 -0
- package/packages/cli/dist/integrations/doctor-workflow.js +25 -0
- package/packages/cli/dist/integrations/mcp-json.js +107 -0
- package/packages/cli/dist/integrations/memory-doctor.js +124 -0
- package/packages/cli/dist/integrations/registry.js +22 -0
- package/packages/cli/dist/integrations/spawn.js +228 -0
- package/packages/cli/dist/integrations/spec-kit.js +280 -0
- package/packages/cli/dist/integrations/verify.js +193 -0
- package/packages/cli/dist/integrations/windows-job.js +275 -0
- package/packages/cli/dist/lib/elevated-paths.js +74 -0
- package/packages/cli/dist/lib/install-set.js +1 -44
- package/packages/cli/dist/lib/manifest.js +64 -5
- package/packages/cli/dist/lib/prompts.js +0 -23
- package/packages/cli/dist/lib/safe-path.js +193 -2
- package/packages/cli/dist/lib/safe-text.js +78 -0
- package/packages/cli/dist/lib/substitute.js +1 -52
- package/packages/cli/dist/lib/summary.js +5 -5
- package/packages/cli/dist/templates.js +4 -14
- package/scripts/prepare.mjs +28 -12
- package/templates/agent-os/subagent-routing.json +4 -4
- package/templates/agent-os/universal/.agents/skills/check-premises/SKILL.md +45 -12
- package/templates/agent-os/universal/.agents/skills/loop/SKILL.md +31 -6
- package/templates/agent-os/universal/.agents/skills/new-invariant/SKILL.md +5 -5
- package/templates/agent-os/universal/.agents/skills/pr-ship/SKILL.md +1 -1
- package/templates/agent-os/universal/.agents/skills/worktree-task/SKILL.md +3 -3
- package/templates/agent-os/universal/.claude/agents/code-reviewer.md +8 -6
- package/templates/agent-os/universal/.claude/agents/implementation-agent.md +41 -0
- package/templates/agent-os/universal/.claude/agents/prose-reviewer.md +8 -4
- package/templates/agent-os/universal/.claude/agents/security-scanner.md +4 -2
- package/templates/agent-os/universal/.claude/agents/test-writer.md +2 -2
- package/templates/agent-os/universal/.claude/hooks/gate-stop-dod.mjs +4 -2
- package/templates/agent-os/universal/.claude/hooks/inject-rules.mjs +83 -4
- package/templates/agent-os/universal/.claude/rules/autonomy.md +55 -27
- package/templates/agent-os/universal/.claude/rules/invariants.md +9 -8
- package/templates/agent-os/universal/.claude/rules/workflow.md +59 -22
- package/templates/agent-os/universal/.claude/scripts/decision-router.mjs +1 -1
- package/templates/agent-os/universal/.claude/scripts/detect-missed-gate.mjs +12 -9
- package/templates/agent-os/universal/.claude/scripts/doctor.mjs +100 -2
- package/templates/agent-os/universal/.claude/scripts/lib/claim-records.mjs +236 -6
- package/templates/agent-os/universal/.claude/scripts/queue/state.mjs +2 -2
- package/templates/agent-os/universal/.claude/scripts/reconcile-external-prs.mjs +4 -3
- package/templates/agent-os/universal/.claude/scripts/revalidate.mjs +16 -0
- package/templates/agent-os/universal/.claude/settings.json +0 -8
- package/templates/agent-os/universal/.claude/skills/check-premises/SKILL.md +45 -12
- package/templates/agent-os/universal/.claude/skills/loop/SKILL.md +31 -6
- package/templates/agent-os/universal/.claude/skills/new-invariant/SKILL.md +5 -5
- package/templates/agent-os/universal/.claude/skills/pr-ship/SKILL.md +1 -1
- package/templates/agent-os/universal/.claude/skills/worktree-task/SKILL.md +3 -3
- package/templates/agent-os/universal/.codex/agents/code-reviewer.toml +2 -2
- package/templates/agent-os/universal/.codex/agents/implementation-agent.toml +6 -0
- package/templates/agent-os/universal/.codex/agents/prose-reviewer.toml +2 -2
- package/templates/agent-os/universal/.codex/agents/security-scanner.toml +1 -1
- package/templates/agent-os/universal/.codex/agents/test-writer.toml +1 -1
- package/templates/agent-os/universal/.codex/hooks.json +0 -10
- package/templates/agent-os/universal/AGENTS.md +208 -107
- package/templates/agent-os/universal/CLAUDE.md +15 -162
- package/templates/agent-os/universal/PLAN.md +17 -10
- package/templates/agent-os/universal/docs/decisions/agents-md-canonical.md +313 -0
- package/templates/agent-os/universal/docs/decisions/codex-adapter.md +15 -10
- package/templates/agent-os/universal/docs/decisions/review-lanes.md +12 -8
- package/templates/agent-os/universal/docs/decisions/session-start-wire-format.md +206 -0
- package/templates/agent-os/universal/docs/decisions/subagent-routing.md +6 -3
- package/templates/agent-os/universal/docs/decisions/workflow-layer-split.md +235 -0
- package/templates/agent-os/universal/layers.json +25 -30
- package/templates/hash-history.json +112 -40
- package/templates/release-ledger.json +3 -1
- package/packages/cli/dist/lib/composition.js +0 -20
- package/packages/cli/dist/lib/targets.js +0 -28
- package/packages/cli/dist/policy/benchmark/corpus.js +0 -165
- package/packages/cli/dist/policy/core/adapter.js +0 -18
- package/packages/cli/dist/policy/core/coverage.js +0 -253
- package/packages/cli/dist/policy/core/decision-record.js +0 -287
- package/packages/cli/dist/policy/core/declaration.js +0 -127
- package/packages/cli/dist/policy/core/evidence-matrix.js +0 -94
- package/packages/cli/dist/policy/core/probe.js +0 -442
- package/packages/cli/dist/policy/core/registry.js +0 -115
- package/packages/cli/dist/policy/core/validation.js +0 -275
- package/packages/cli/dist/policy/core/vocabulary.js +0 -123
- package/packages/cli/dist/policy/harness/claude.js +0 -47
- package/packages/cli/dist/policy/harness/codex.js +0 -87
- package/packages/cli/dist/policy/harness/index.js +0 -15
- package/packages/cli/dist/policy/harness/shared-hooks.js +0 -28
- package/packages/cli/dist/policy/index.js +0 -17
- package/templates/agent-os/init/AGENTS.md +0 -201
- package/templates/agent-os/init/CLAUDE.md +0 -201
- package/templates/agent-os/stack/aws-cdk/.agents/skills/post-deploy-verify/SKILL.md +0 -105
- package/templates/agent-os/stack/aws-cdk/.agents/skills/ro-debug/SKILL.md +0 -117
- package/templates/agent-os/stack/aws-cdk/.claude/agents/cdk-diff-reviewer.md +0 -89
- package/templates/agent-os/stack/aws-cdk/.claude/rules/aws-cdk.md +0 -105
- package/templates/agent-os/stack/aws-cdk/.claude/skills/post-deploy-verify/SKILL.md +0 -105
- package/templates/agent-os/stack/aws-cdk/.claude/skills/ro-debug/SKILL.md +0 -117
- package/templates/agent-os/stack/aws-cdk/.codex/agents/cdk-diff-reviewer.toml +0 -6
- package/templates/agent-os/stack/node-ts/.claude/hooks/dod-checks.json +0 -1
- package/templates/agent-os/stack/node-ts/.claude/rules/node-ts.md +0 -85
- package/templates/agent-os/universal/.claude/hooks/guard-core-purity.mjs +0 -89
- package/templates/agent-os/universal/.claude/hooks/guard-web-boundary.mjs +0 -68
- package/templates/agent-os/universal/.claude/rules/architecture.md +0 -81
- package/templates/skeleton/aws-serverless/.github/workflows/ci.yml +0 -28
- package/templates/skeleton/aws-serverless/.github/workflows/deploy.yml +0 -90
- package/templates/skeleton/aws-serverless/README.md +0 -181
- package/templates/skeleton/aws-serverless/apps/web/next.config.mjs +0 -17
- package/templates/skeleton/aws-serverless/apps/web/package.json +0 -19
- package/templates/skeleton/aws-serverless/apps/web/src/app/layout.tsx +0 -17
- package/templates/skeleton/aws-serverless/apps/web/src/app/page.tsx +0 -96
- package/templates/skeleton/aws-serverless/apps/web/src/lib/api.ts +0 -36
- package/templates/skeleton/aws-serverless/apps/web/src/lib/validate.ts +0 -23
- package/templates/skeleton/aws-serverless/apps/web/test/shared-validation.test.ts +0 -38
- package/templates/skeleton/aws-serverless/apps/web/tsconfig.json +0 -14
- package/templates/skeleton/aws-serverless/eslint.config.mjs +0 -20
- package/templates/skeleton/aws-serverless/gitignore +0 -56
- package/templates/skeleton/aws-serverless/infra/bin/app.ts +0 -79
- package/templates/skeleton/aws-serverless/infra/cdk.json +0 -3
- package/templates/skeleton/aws-serverless/infra/lib/app-stack.ts +0 -248
- package/templates/skeleton/aws-serverless/infra/lib/web-stack.ts +0 -45
- package/templates/skeleton/aws-serverless/infra/package.json +0 -18
- package/templates/skeleton/aws-serverless/infra/test/allowed-origins.test.ts +0 -301
- package/templates/skeleton/aws-serverless/infra/test/app-composition.test.ts +0 -137
- package/templates/skeleton/aws-serverless/infra/test/app-stack.test.ts +0 -120
- package/templates/skeleton/aws-serverless/infra/test/web-stack.test.ts +0 -44
- package/templates/skeleton/aws-serverless/package.json +0 -30
- package/templates/skeleton/aws-serverless/packages/core/package.json +0 -11
- package/templates/skeleton/aws-serverless/packages/core/src/events.ts +0 -14
- package/templates/skeleton/aws-serverless/packages/core/src/index.ts +0 -15
- package/templates/skeleton/aws-serverless/packages/core/src/note.ts +0 -69
- package/templates/skeleton/aws-serverless/packages/core/test/events.test.ts +0 -23
- package/templates/skeleton/aws-serverless/packages/core/test/note.test.ts +0 -101
- package/templates/skeleton/aws-serverless/packages/db/package.json +0 -14
- package/templates/skeleton/aws-serverless/packages/db/src/client.ts +0 -17
- package/templates/skeleton/aws-serverless/packages/db/src/index.ts +0 -2
- package/templates/skeleton/aws-serverless/packages/db/src/note-model.ts +0 -52
- package/templates/skeleton/aws-serverless/packages/db/test/note-model.test.ts +0 -91
- package/templates/skeleton/aws-serverless/packages/shared/package.json +0 -11
- package/templates/skeleton/aws-serverless/packages/shared/src/env.ts +0 -17
- package/templates/skeleton/aws-serverless/packages/shared/src/errors.ts +0 -33
- package/templates/skeleton/aws-serverless/packages/shared/src/index.ts +0 -3
- package/templates/skeleton/aws-serverless/packages/shared/src/logger.ts +0 -20
- package/templates/skeleton/aws-serverless/packages/shared/test/env.test.ts +0 -26
- package/templates/skeleton/aws-serverless/packages/shared/test/errors.test.ts +0 -28
- package/templates/skeleton/aws-serverless/packages/shared/test/logger.test.ts +0 -19
- package/templates/skeleton/aws-serverless/pnpm-lock.yaml +0 -2855
- package/templates/skeleton/aws-serverless/pnpm-workspace.yaml +0 -14
- package/templates/skeleton/aws-serverless/services/api/package.json +0 -15
- package/templates/skeleton/aws-serverless/services/api/src/adapters/sqs-publisher.ts +0 -26
- package/templates/skeleton/aws-serverless/services/api/src/handlers/create-note.ts +0 -69
- package/templates/skeleton/aws-serverless/services/api/src/handlers/list-notes.ts +0 -37
- package/templates/skeleton/aws-serverless/services/api/src/list-main.ts +0 -12
- package/templates/skeleton/aws-serverless/services/api/src/main.ts +0 -21
- package/templates/skeleton/aws-serverless/services/api/src/usecases/create-note.ts +0 -41
- package/templates/skeleton/aws-serverless/services/api/src/usecases/list-notes.ts +0 -14
- package/templates/skeleton/aws-serverless/services/api/test/create-note.handler.test.ts +0 -211
- package/templates/skeleton/aws-serverless/services/api/test/create-note.usecase.test.ts +0 -45
- package/templates/skeleton/aws-serverless/services/api/test/list-notes.test.ts +0 -122
- package/templates/skeleton/aws-serverless/services/api/test/sqs-publisher.test.ts +0 -22
- package/templates/skeleton/aws-serverless/services/worker/package.json +0 -12
- package/templates/skeleton/aws-serverless/services/worker/src/handlers/note-created.ts +0 -15
- package/templates/skeleton/aws-serverless/services/worker/src/main.ts +0 -7
- package/templates/skeleton/aws-serverless/services/worker/src/usecases/process-note-created.ts +0 -37
- package/templates/skeleton/aws-serverless/services/worker/test/note-created.test.ts +0 -61
- package/templates/skeleton/aws-serverless/tsconfig.base.json +0 -15
- package/templates/skeleton/aws-serverless/tsconfig.json +0 -16
- package/templates/skeleton/aws-serverless/vitest.config.ts +0 -14
- package/templates/skeleton/node-service/.github/workflows/ci.yml +0 -27
- package/templates/skeleton/node-service/.github/workflows/deploy.yml +0 -29
- package/templates/skeleton/node-service/README.md +0 -104
- package/templates/skeleton/node-service/apps/web/next.config.mjs +0 -17
- package/templates/skeleton/node-service/apps/web/package.json +0 -19
- package/templates/skeleton/node-service/apps/web/src/app/layout.tsx +0 -17
- package/templates/skeleton/node-service/apps/web/src/app/page.tsx +0 -96
- package/templates/skeleton/node-service/apps/web/src/lib/api.ts +0 -29
- package/templates/skeleton/node-service/apps/web/src/lib/validate.ts +0 -23
- package/templates/skeleton/node-service/apps/web/test/shared-validation.test.ts +0 -38
- package/templates/skeleton/node-service/apps/web/tsconfig.json +0 -14
- package/templates/skeleton/node-service/eslint.config.mjs +0 -20
- package/templates/skeleton/node-service/gitignore +0 -54
- package/templates/skeleton/node-service/package.json +0 -30
- package/templates/skeleton/node-service/packages/core/package.json +0 -11
- package/templates/skeleton/node-service/packages/core/src/events.ts +0 -14
- package/templates/skeleton/node-service/packages/core/src/index.ts +0 -15
- package/templates/skeleton/node-service/packages/core/src/note.ts +0 -69
- package/templates/skeleton/node-service/packages/core/test/events.test.ts +0 -23
- package/templates/skeleton/node-service/packages/core/test/note.test.ts +0 -101
- package/templates/skeleton/node-service/packages/db/package.json +0 -12
- package/templates/skeleton/node-service/packages/db/src/index.ts +0 -1
- package/templates/skeleton/node-service/packages/db/src/note-store.ts +0 -100
- package/templates/skeleton/node-service/packages/db/test/note-store.test.ts +0 -100
- package/templates/skeleton/node-service/packages/shared/package.json +0 -11
- package/templates/skeleton/node-service/packages/shared/src/env.ts +0 -17
- package/templates/skeleton/node-service/packages/shared/src/errors.ts +0 -33
- package/templates/skeleton/node-service/packages/shared/src/index.ts +0 -3
- package/templates/skeleton/node-service/packages/shared/src/logger.ts +0 -20
- package/templates/skeleton/node-service/packages/shared/test/env.test.ts +0 -26
- package/templates/skeleton/node-service/packages/shared/test/errors.test.ts +0 -28
- package/templates/skeleton/node-service/packages/shared/test/logger.test.ts +0 -19
- package/templates/skeleton/node-service/pnpm-lock.yaml +0 -2402
- package/templates/skeleton/node-service/pnpm-workspace.yaml +0 -13
- package/templates/skeleton/node-service/scripts/build-artifact.mjs +0 -34
- package/templates/skeleton/node-service/services/api/package.json +0 -17
- package/templates/skeleton/node-service/services/api/src/adapters/spool-publisher.ts +0 -23
- package/templates/skeleton/node-service/services/api/src/handlers/create-note.ts +0 -40
- package/templates/skeleton/node-service/services/api/src/handlers/list-notes.ts +0 -23
- package/templates/skeleton/node-service/services/api/src/main.ts +0 -40
- package/templates/skeleton/node-service/services/api/src/server.ts +0 -172
- package/templates/skeleton/node-service/services/api/src/static-dir.ts +0 -20
- package/templates/skeleton/node-service/services/api/src/usecases/create-note.ts +0 -30
- package/templates/skeleton/node-service/services/api/src/usecases/list-notes.ts +0 -14
- package/templates/skeleton/node-service/services/api/test/artifact.test.ts +0 -92
- package/templates/skeleton/node-service/services/api/test/create-note.handler.test.ts +0 -64
- package/templates/skeleton/node-service/services/api/test/create-note.usecase.test.ts +0 -43
- package/templates/skeleton/node-service/services/api/test/list-notes.test.ts +0 -48
- package/templates/skeleton/node-service/services/api/test/package-manager.test.ts +0 -40
- package/templates/skeleton/node-service/services/api/test/package-manager.ts +0 -51
- package/templates/skeleton/node-service/services/api/test/server.test.ts +0 -208
- package/templates/skeleton/node-service/services/api/test/spool-publisher.test.ts +0 -32
- package/templates/skeleton/node-service/services/api/test/static-dir.test.ts +0 -29
- package/templates/skeleton/node-service/services/worker/package.json +0 -16
- package/templates/skeleton/node-service/services/worker/src/main.ts +0 -28
- package/templates/skeleton/node-service/services/worker/src/spool.ts +0 -60
- package/templates/skeleton/node-service/services/worker/src/usecases/process-note-created.ts +0 -38
- package/templates/skeleton/node-service/services/worker/test/process-note-created.test.ts +0 -34
- package/templates/skeleton/node-service/services/worker/test/spool.test.ts +0 -76
- package/templates/skeleton/node-service/tsconfig.base.json +0 -15
- package/templates/skeleton/node-service/tsconfig.json +0 -13
- package/templates/skeleton/node-service/vitest.config.ts +0 -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
|
-
}
|
package/templates/skeleton/aws-serverless/services/worker/src/usecases/process-note-created.ts
DELETED
|
@@ -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
|
-
);
|