forge-dev-framework 1.0.1 → 1.2.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 (109) hide show
  1. package/.claude/commands/forge/README.md +281 -0
  2. package/.claude/commands/forge/add-phase.md +90 -0
  3. package/.claude/commands/forge/complete-milestone.md +130 -0
  4. package/.claude/commands/forge/config.md +115 -0
  5. package/.claude/commands/forge/convert.md +31 -0
  6. package/.claude/commands/forge/debug.md +31 -0
  7. package/.claude/commands/forge/discuss.md +78 -0
  8. package/.claude/commands/forge/execute.md +85 -0
  9. package/.claude/commands/forge/generate.md +21 -0
  10. package/.claude/commands/forge/help.md +18 -0
  11. package/.claude/commands/forge/init.md +21 -0
  12. package/.claude/commands/forge/insert-phase.md +99 -0
  13. package/.claude/commands/forge/new-milestone.md +114 -0
  14. package/.claude/commands/forge/new-project.md +24 -0
  15. package/.claude/commands/forge/pause-work.md +111 -0
  16. package/.claude/commands/forge/plan.md +129 -0
  17. package/.claude/commands/forge/quick.md +41 -0
  18. package/.claude/commands/forge/remove-phase.md +92 -0
  19. package/.claude/commands/forge/resume.md +22 -0
  20. package/.claude/commands/forge/status.md +87 -0
  21. package/.claude/commands/forge/team-add.md +24 -0
  22. package/.claude/commands/forge/team-create.md +22 -0
  23. package/.claude/commands/forge/team-remove.md +24 -0
  24. package/.claude/commands/forge/team-start.md +22 -0
  25. package/.claude/commands/forge/team-view.md +18 -0
  26. package/.claude/commands/forge/verify.md +95 -0
  27. package/.claude/hooks/forge-context-cleanup.cjs +79 -0
  28. package/.claude/hooks/forge-event-guard.cjs +36 -0
  29. package/.claude/hooks/forge-size-guard.cjs +55 -0
  30. package/.claude/rules/api-patterns.md +13 -98
  31. package/.claude/rules/context-efficiency.md +10 -0
  32. package/.claude/rules/security-baseline.md +18 -204
  33. package/.claude/rules/testing-standards.md +16 -177
  34. package/.claude/rules/ui-conventions.md +17 -142
  35. package/README.md +1 -0
  36. package/bin/forge.js +5 -3
  37. package/dist/bin/forge.js +5 -3
  38. package/dist/cli/index.d.ts.map +1 -1
  39. package/dist/cli/index.js +15 -1
  40. package/dist/cli/index.js.map +1 -1
  41. package/dist/commands/convert.d.ts +6 -0
  42. package/dist/commands/convert.d.ts.map +1 -0
  43. package/dist/commands/convert.js +132 -0
  44. package/dist/commands/convert.js.map +1 -0
  45. package/dist/commands/generate.d.ts.map +1 -1
  46. package/dist/commands/generate.js +3 -2
  47. package/dist/commands/generate.js.map +1 -1
  48. package/dist/commands/index.d.ts +4 -4
  49. package/dist/commands/index.d.ts.map +1 -1
  50. package/dist/commands/index.js +4 -4
  51. package/dist/commands/index.js.map +1 -1
  52. package/dist/generators/gsd-converter.d.ts +100 -0
  53. package/dist/generators/gsd-converter.d.ts.map +1 -0
  54. package/dist/generators/gsd-converter.js +335 -0
  55. package/dist/generators/gsd-converter.js.map +1 -0
  56. package/dist/templates/.claude/rules/api-patterns.md.template +212 -0
  57. package/dist/templates/.claude/rules/security-baseline.md.template +322 -0
  58. package/dist/templates/.claude/rules/testing-standards.md.template +280 -0
  59. package/dist/templates/.claude/rules/ui-conventions.md.template +264 -0
  60. package/dist/templates/.planning/forge.config.json.template +75 -0
  61. package/dist/templates/CLAUDE.md.template +161 -0
  62. package/dist/templates/PLAN.md.template +177 -0
  63. package/dist/templates/PROJECT.md.template +156 -0
  64. package/dist/templates/REQUIREMENTS.md.template +221 -0
  65. package/dist/templates/ROADMAP.md.template +130 -0
  66. package/dist/types/index.d.ts +2 -2
  67. package/dist/types/index.d.ts.map +1 -1
  68. package/dist/types/index.js +2 -2
  69. package/dist/types/index.js.map +1 -1
  70. package/dist/utils/index.d.ts +5 -5
  71. package/dist/utils/index.d.ts.map +1 -1
  72. package/dist/utils/index.js +5 -5
  73. package/dist/utils/index.js.map +1 -1
  74. package/dist/utils/template-client.d.ts.map +1 -1
  75. package/dist/utils/template-client.js +3 -2
  76. package/dist/utils/template-client.js.map +1 -1
  77. package/package.json +6 -4
  78. package/dist/git/__tests__/worktree.test.d.ts +0 -5
  79. package/dist/git/__tests__/worktree.test.d.ts.map +0 -1
  80. package/dist/git/__tests__/worktree.test.js +0 -121
  81. package/dist/git/__tests__/worktree.test.js.map +0 -1
  82. package/dist/git/codeowners.d.ts +0 -101
  83. package/dist/git/codeowners.d.ts.map +0 -1
  84. package/dist/git/codeowners.js +0 -216
  85. package/dist/git/codeowners.js.map +0 -1
  86. package/dist/git/commit.d.ts +0 -135
  87. package/dist/git/commit.d.ts.map +0 -1
  88. package/dist/git/commit.js +0 -223
  89. package/dist/git/commit.js.map +0 -1
  90. package/dist/git/hooks/commit-msg.d.ts +0 -8
  91. package/dist/git/hooks/commit-msg.d.ts.map +0 -1
  92. package/dist/git/hooks/commit-msg.js +0 -34
  93. package/dist/git/hooks/commit-msg.js.map +0 -1
  94. package/dist/git/hooks/pre-commit.d.ts +0 -8
  95. package/dist/git/hooks/pre-commit.d.ts.map +0 -1
  96. package/dist/git/hooks/pre-commit.js +0 -34
  97. package/dist/git/hooks/pre-commit.js.map +0 -1
  98. package/dist/git/pre-commit-hooks.d.ts +0 -117
  99. package/dist/git/pre-commit-hooks.d.ts.map +0 -1
  100. package/dist/git/pre-commit-hooks.js +0 -270
  101. package/dist/git/pre-commit-hooks.js.map +0 -1
  102. package/dist/git/wipe-protocol.d.ts +0 -281
  103. package/dist/git/wipe-protocol.d.ts.map +0 -1
  104. package/dist/git/wipe-protocol.js +0 -237
  105. package/dist/git/wipe-protocol.js.map +0 -1
  106. package/dist/git/worktree.d.ts +0 -69
  107. package/dist/git/worktree.d.ts.map +0 -1
  108. package/dist/git/worktree.js +0 -202
  109. package/dist/git/worktree.js.map +0 -1
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env node
2
+ // FORGE Hook: Event Immutability Guard
3
+ // PreToolUse on Edit — prevents modifying existing event files in state/events/
4
+ // Events are append-only per FORGE architecture
5
+
6
+ const path = require('path');
7
+
8
+ let input = '';
9
+ process.stdin.setEncoding('utf8');
10
+ process.stdin.on('data', chunk => input += chunk);
11
+ process.stdin.on('end', () => {
12
+ try {
13
+ const data = JSON.parse(input);
14
+ const toolName = data.tool_name || '';
15
+ const filePath = data.tool_input?.file_path || '';
16
+
17
+ // Only guard Edit operations on event files
18
+ if (toolName === 'Edit' && filePath) {
19
+ const resolved = path.resolve(filePath);
20
+ const eventsDir = path.resolve(process.cwd(), 'state', 'events');
21
+
22
+ if (resolved.startsWith(eventsDir) && resolved.endsWith('.json')) {
23
+ process.stdout.write(JSON.stringify({
24
+ decision: 'block',
25
+ reason: 'FORGE: Events are append-only. Cannot edit files in state/events/. Write new events instead.'
26
+ }));
27
+ return;
28
+ }
29
+ }
30
+
31
+ process.stdout.write(JSON.stringify({ decision: 'approve' }));
32
+ } catch (e) {
33
+ // Don't block on parse errors
34
+ process.stdout.write(JSON.stringify({ decision: 'approve' }));
35
+ }
36
+ });
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env node
2
+ // FORGE Hook: File Size Guard
3
+ // PostToolUse on Write/Edit — warns when artifacts exceed token limits
4
+ // Limits: CLAUDE.md ~2000t, rules ~1000t, REQUIREMENTS.md ~4000t, PLAN.md ~6000t
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+
9
+ // Approximate tokens as chars/4
10
+ const LIMITS = {
11
+ 'CLAUDE.md': { tokens: 2000, chars: 8000 },
12
+ 'REQUIREMENTS.md': { tokens: 4000, chars: 16000 },
13
+ 'PLAN.md': { tokens: 6000, chars: 24000 },
14
+ };
15
+ const RULES_LIMIT = { tokens: 1000, chars: 4000 };
16
+
17
+ let input = '';
18
+ process.stdin.setEncoding('utf8');
19
+ process.stdin.on('data', chunk => input += chunk);
20
+ process.stdin.on('end', () => {
21
+ try {
22
+ const data = JSON.parse(input);
23
+ const filePath = data.tool_input?.file_path || data.tool_result?.file_path || '';
24
+
25
+ if (!filePath) {
26
+ process.stdout.write(JSON.stringify({ decision: 'approve' }));
27
+ return;
28
+ }
29
+
30
+ const basename = path.basename(filePath);
31
+ const resolved = path.resolve(filePath);
32
+
33
+ // Check specific file limits
34
+ let limit = LIMITS[basename];
35
+
36
+ // Check rules directory
37
+ if (!limit && resolved.includes('.claude/rules/') && resolved.endsWith('.md')) {
38
+ limit = RULES_LIMIT;
39
+ }
40
+
41
+ if (limit && fs.existsSync(resolved)) {
42
+ const size = fs.statSync(resolved).size;
43
+ if (size > limit.chars) {
44
+ const approxTokens = Math.round(size / 4);
45
+ process.stderr.write(
46
+ `\x1b[33m⚠ FORGE: ${basename} is ~${approxTokens} tokens (limit: ~${limit.tokens}). Consider trimming.\x1b[0m\n`
47
+ );
48
+ }
49
+ }
50
+
51
+ process.stdout.write(JSON.stringify({ decision: 'approve' }));
52
+ } catch (e) {
53
+ process.stdout.write(JSON.stringify({ decision: 'approve' }));
54
+ }
55
+ });
@@ -1,98 +1,13 @@
1
- # FORGE API Design Patterns
2
-
3
- > **Scope:** Internal FORGE APIs and interfaces between components.
4
-
5
- ## File Naming
6
-
7
- - Use `kebab-case.ts` for files
8
- - Use `PascalCase` for types, interfaces, classes
9
- - Use `camelCase` for functions, variables
10
-
11
- ## Module Structure
12
-
13
- Each module exports:
14
- 1. Types/interfaces first
15
- 2. Functions/operations
16
- 3. Constants at the end
17
-
18
- ```typescript
19
- // src/state/event-types.ts
20
- export interface StateEvent { ... }
21
- export interface TaskStartedEvent extends StateEvent { ... }
22
-
23
- export function createEvent(type: EventType, payload: unknown): StateEvent { ... }
24
-
25
- export const EVENT_TYPES = ['TASK_STARTED', 'TASK_COMPLETED', ...] as const;
26
- ```
27
-
28
- ## Error Handling
29
-
30
- - Define custom error types extending `Error`
31
- - Include error codes for programmatic handling
32
- - Provide context in error messages
33
-
34
- ```typescript
35
- export class StateValidationError extends Error {
36
- constructor(
37
- public schema: string,
38
- public errors: z.ZodError,
39
- ) {
40
- super(`State validation failed for ${schema}: ${errors.message}`);
41
- this.name = 'StateValidationError';
42
- }
43
- }
44
- ```
45
-
46
- ## Async Operations
47
-
48
- - Use `async/await`, not Promises directly
49
- - Return typed promises: `Promise<T>`
50
- - Handle rejections with try/catch
51
-
52
- ```typescript
53
- export async function loadState(path: string): Promise<State> {
54
- try {
55
- const content = await fs.readFile(path, 'utf-8');
56
- return JSON.parse(content);
57
- } catch (error) {
58
- throw new StateLoadError(path, error);
59
- }
60
- }
61
- ```
62
-
63
- ## Input Validation
64
-
65
- - Use Zod schemas for all public APIs
66
- - Validate at module boundaries
67
- - Return typed results, never `any`
68
-
69
- ```typescript
70
- import { z } from 'zod';
71
-
72
- const EventSchema = z.object({
73
- eventId: z.string(),
74
- timestamp: z.string().datetime(),
75
- taskId: z.string(),
76
- actor: z.string(),
77
- type: z.enum(EVENT_TYPES),
78
- payload: z.unknown(),
79
- });
80
-
81
- export function parseEvent(data: unknown): StateEvent {
82
- return EventSchema.parse(data);
83
- }
84
- ```
85
-
86
- ## File I/O
87
-
88
- - Use absolute paths from project root
89
- - Check file existence before reading
90
- - Use atomic writes (write to temp, then rename)
91
-
92
- ```typescript
93
- export async function writeState(path: string, state: State): Promise<void> {
94
- const tmpPath = `${path}.tmp`;
95
- await fs.writeFile(tmpPath, JSON.stringify(state, null, 2));
96
- await fs.rename(tmpPath, path);
97
- }
98
- ```
1
+ ---
2
+ globs:
3
+ - "src/**/*.ts"
4
+ ---
5
+
6
+ # API Design Patterns
7
+
8
+ - **Files:** `kebab-case.ts` | **Types:** `PascalCase` | **Functions:** `camelCase`
9
+ - **Module exports order:** types/interfaces → functions → constants
10
+ - **Errors:** Custom error classes extending `Error` with error codes and context
11
+ - **Async:** Use `async/await` with typed `Promise<T>` returns, try/catch for rejections
12
+ - **Validation:** Zod schemas at all public API boundaries — never return `any`
13
+ - **File I/O:** Absolute paths from project root, check existence, atomic writes (write to temp → rename)
@@ -0,0 +1,10 @@
1
+ # Context Efficiency
2
+
3
+ - **Read selectively:** Use `offset`/`limit` for large files — don't read entire files when only a section matters
4
+ - **Minimize re-reads:** Cache key info in your working memory instead of re-reading the same file multiple times
5
+ - **Short outputs:** Prefer concise tool output — pipe through `head`/`tail` when full output isn't needed
6
+ - **Atomic tasks:** One task = one focused context. Don't accumulate unrelated work in a single session
7
+ - **Summarize early:** After reading large files, extract what you need and move on — don't keep raw content in context
8
+ - **Event hygiene:** Old events auto-archive after 7 days (SessionStart hook). Don't read archived events unless replaying state
9
+ - **File size limits:** CLAUDE.md ~2000t, rules ~1000t each, REQUIREMENTS.md ~4000t, PLAN.md ~6000t (enforced by PostToolUse hook)
10
+ - **Team messages:** Keep inter-agent messages concise — include only actionable information
@@ -1,204 +1,18 @@
1
- # FORGE Security Baseline
2
-
3
- > **Scope:** Input validation, secrets management, dependency security, file operations.
4
-
5
- ## Input Validation
6
-
7
- **All user input MUST be validated** with Zod schemas:
8
-
9
- ```typescript
10
- const ConfigSchema = z.object({
11
- mode: z.enum(['yolo', 'interactive']),
12
- depth: z.enum(['quick', 'standard', 'comprehensive']),
13
- maxTeammates: z.number().int().min(2).max(6),
14
- });
15
-
16
- export function loadConfig(path: string): Config {
17
- const raw = JSON.parse(fs.readFileSync(path, 'utf-8'));
18
- return ConfigSchema.parse(raw);
19
- }
20
- ```
21
-
22
- ### Path Validation
23
-
24
- Prevent directory traversal:
25
-
26
- ```typescript
27
- import { resolve, normalize } from 'path';
28
-
29
- export function safeJoin(base: string, userPath: string): string {
30
- const resolved = resolve(base, userPath);
31
- if (!resolved.startsWith(base)) {
32
- throw new Error('Invalid path: outside base directory');
33
- }
34
- return resolved;
35
- }
36
- ```
37
-
38
- ## Secrets Management
39
-
40
- **NEVER hardcode secrets.** Use environment variables:
41
-
42
- ```typescript
43
- export function getApiKey(): string {
44
- const key = process.env.FORGE_API_KEY;
45
- if (!key) {
46
- throw new Error('FORGE_API_KEY environment variable not set');
47
- }
48
- return key;
49
- }
50
- ```
51
-
52
- ### Git Ignore
53
-
54
- Add to `.gitignore`:
55
-
56
- ```
57
- # FORGE
58
- state/events/*.json # Optional: don't commit event logs
59
- .planning/local/ # User-specific config
60
- .env # Environment variables
61
- *.tmp # Temp files
62
- ```
63
-
64
- ## Command Injection Prevention
65
-
66
- **NEVER pass user input directly to shell commands.** Use array arguments:
67
-
68
- ```typescript
69
- // BAD
70
- await execa(`git checkout ${userBranch}`);
71
-
72
- // GOOD
73
- await execa('git', ['checkout', userBranch]);
74
- ```
75
-
76
- ## File Permissions
77
-
78
- Respect user's umask, don't over-permission:
79
-
80
- ```typescript
81
- import { constants } from 'fs';
82
-
83
- await fs.writeFile(path, content, {
84
- mode: 0o644, // rw-r--r--
85
- });
86
- ```
87
-
88
- For sensitive files:
89
-
90
- ```typescript
91
- await fs.writeFile(path, secrets, {
92
- mode: 0o600, // rw-------
93
- });
94
- ```
95
-
96
- ## Dependency Security
97
-
98
- Run `npm audit` in CI:
99
-
100
- ```yaml
101
- # .github/workflows/ci.yml
102
- - name: Audit dependencies
103
- run: npm audit --audit-level=moderate
104
- ```
105
-
106
- ### Locked Dependencies
107
-
108
- Commit `package-lock.json`. Always install with exact versions:
109
-
110
- ```bash
111
- npm ci # Use lock file, don't update
112
- ```
113
-
114
- ## Code Execution Safety
115
-
116
- **NO `eval()`, no dynamic imports from untrusted sources:**
117
-
118
- ```typescript
119
- // BAD
120
- const module = await import(userPath);
121
-
122
- // GOOD — validate against whitelist
123
- const ALLOWED = ['state', 'cli', 'templates'];
124
- if (ALLOWED.includes(userPath)) {
125
- const module = await import(`./${userPath}`);
126
- }
127
- ```
128
-
129
- ## Event Log Integrity
130
-
131
- Events should be **append-only**. Never modify existing events:
132
-
133
- ```typescript
134
- export async function writeEvent(path: string, event: StateEvent): Promise<void> {
135
- if (await fs.pathExists(path)) {
136
- throw new Error(`Event file already exists: ${path}`);
137
- }
138
- await fs.writeFile(path, JSON.stringify(event, null, 2), { mode: 0o444 }); // read-only
139
- }
140
- ```
141
-
142
- ## State Validation
143
-
144
- Validate `STATE.json` against schema on every read:
145
-
146
- ```typescript
147
- export async function loadState(path: string): Promise<State> {
148
- const content = await fs.readFile(path, 'utf-8');
149
- const raw = JSON.parse(content);
150
- return StateSchema.parse(raw); // Zod validation
151
- }
152
- ```
153
-
154
- ## Git Worktree Isolation
155
-
156
- Ensure worktrees can't access each other's files:
157
-
158
- ```typescript
159
- export function validateWorktreePath(worktreePath: string): void {
160
- if (!worktreePath.startsWith('.worktrees/')) {
161
- throw new Error('Invalid worktree: must be under .worktrees/');
162
- }
163
- }
164
- ```
165
-
166
- ## Error Messages
167
-
168
- **Don't leak sensitive info in errors:**
169
-
170
- ```typescript
171
- // BAD — leaks file path
172
- throw new Error(`Failed to read ${filePath}`);
173
-
174
- // GOOD — generic error
175
- throw new Error('Failed to read configuration file');
176
- ```
177
-
178
- ## CI/CD Security
179
-
180
- ```yaml
181
- # .github/workflows/ci.yml
182
- permissions:
183
- contents: read # No write access
184
- issues: read
185
- pull-requests: read
186
-
187
- - name: Run tests
188
- run: npm test
189
- ```
190
-
191
- ## Third-Party Code
192
-
193
- Review all dependencies before adding:
194
-
195
- ```bash
196
- npm audit
197
- npm ls <package>
198
- ```
199
-
200
- Prefer well-maintained packages with:
201
- - Active development
202
- - Security policy
203
- - TypeScript types
204
- - Low dependency count
1
+ ---
2
+ globs:
3
+ - "src/**/*.ts"
4
+ - "*.json"
5
+ ---
6
+
7
+ # Security Baseline
8
+
9
+ - **Input validation:** All user input validated with Zod schemas
10
+ - **Path validation:** Prevent directory traversal — resolve and check `startsWith(base)`
11
+ - **Secrets:** Never hardcode — use environment variables only
12
+ - **Command injection:** Never pass user input to shell strings — use array args: `execa('git', ['checkout', branch])`
13
+ - **File permissions:** `0o644` default, `0o600` for sensitive files, respect user umask
14
+ - **No `eval()`** or dynamic imports from untrusted sources
15
+ - **Events are append-only** — never modify, write as read-only (`0o444`)
16
+ - **Validate STATE.json** against schema on every read
17
+ - **Error messages:** Don't leak file paths or sensitive info
18
+ - **Dependencies:** `npm audit` in CI, commit `package-lock.json`, use `npm ci`
@@ -1,177 +1,16 @@
1
- # FORGE Testing Standards
2
-
3
- > **Scope:** Test coverage, test structure, what to test and how.
4
-
5
- ## Test Structure
6
-
7
- Place tests next to source files with `.test.ts` suffix:
8
-
9
- ```
10
- src/state/
11
- event-types.ts
12
- event-types.test.ts
13
- src/cli/
14
- init.ts
15
- init.test.ts
16
- ```
17
-
18
- ## Unit Tests
19
-
20
- Test **pure functions** and **business logic**:
21
-
22
- ```typescript
23
- // event-types.test.ts
24
- import { describe, it, expect } from 'jest';
25
- import { parseEvent, createEvent } from './event-types';
26
-
27
- describe('parseEvent', () => {
28
- it('parses valid TASK_STARTED event', () => {
29
- const event = parseEvent({
30
- eventId: 'evt-001',
31
- timestamp: '2026-02-11T21:10:00Z',
32
- taskId: 'api-003',
33
- actor: 'backend',
34
- type: 'TASK_STARTED',
35
- payload: {},
36
- });
37
-
38
- expect(event.type).toBe('TASK_STARTED');
39
- });
40
-
41
- it('throws on missing eventId', () => {
42
- expect(() => parseEvent({})).toThrow('eventId');
43
- });
44
-
45
- it('throws on invalid timestamp format', () => {
46
- expect(() => parseEvent({
47
- eventId: 'evt-001',
48
- timestamp: 'invalid',
49
- taskId: 'api-003',
50
- actor: 'backend',
51
- type: 'TASK_STARTED',
52
- payload: {},
53
- })).toThrow('timestamp');
54
- });
55
- });
56
- ```
57
-
58
- ## Integration Tests
59
-
60
- Test **workflows end-to-end**:
61
-
62
- ```typescript
63
- // state-merge.integration.test.ts
64
- describe('State Steward merge', () => {
65
- const testDir = tmpdir();
66
-
67
- beforeEach(async () => {
68
- await fs.mkdir(`${testDir}/state/events`, { recursive: true });
69
- });
70
-
71
- it('merges events in timestamp order', async () => {
72
- await writeEvent(`${testDir}/state/events/evt-002.json`, {
73
- timestamp: '2026-02-11T21:11:00Z',
74
- type: 'TASK_COMPLETED',
75
- });
76
- await writeEvent(`${testDir}/state/events/evt-001.json`, {
77
- timestamp: '2026-02-11T21:10:00Z',
78
- type: 'TASK_STARTED',
79
- });
80
-
81
- const state = await mergeEvents(testDir);
82
-
83
- expect(state.tasks[0].status).toBe('completed');
84
- });
85
- });
86
- ```
87
-
88
- ## Coverage Targets
89
-
90
- - **Core logic** (state, events, templates): >90%
91
- - **CLI commands**: >70%
92
- - **Git operations**: >80%
93
-
94
- Run: `npm run test:coverage`
95
-
96
- ## Test Data
97
-
98
- Create fixtures in `test/fixtures/`:
99
-
100
- ```
101
- test/fixtures/
102
- events/
103
- task-started.json
104
- task-completed.json
105
- state/
106
- initial-state.json
107
- ```
108
-
109
- ## Mocking
110
-
111
- Mock external dependencies (git, fs, network):
112
-
113
- ```typescript
114
- import { jest } from '@jest/globals';
115
-
116
- jest.mock('execa', () => ({
117
- execa: jest.fn(),
118
- }));
119
-
120
- import { execa } from 'execa';
121
-
122
- it('creates git worktree', async () => {
123
- (execa as jest.Mock).mockResolvedValue({ stdout: '' });
124
-
125
- await createWorktree('api-003', 'forge/api-003');
126
-
127
- expect(execa).toHaveBeenCalledWith('git', [
128
- 'worktree', 'add', '.worktrees/api-003', '-b', 'forge/api-003',
129
- ]);
130
- });
131
- ```
132
-
133
- ## Test Organization
134
-
135
- Use `describe` for logical groups, `it` for individual tests:
136
-
137
- ```typescript
138
- describe('State Steward', () => {
139
- describe('mergeEvents', () => {
140
- it('handles empty event directory');
141
- it('sorts events by timestamp');
142
- it('handles duplicate eventIds');
143
- it('skips malformed events');
144
- });
145
- });
146
- ```
147
-
148
- ## Async Tests
149
-
150
- Always `await` async operations:
151
-
152
- ```typescript
153
- it('merges events', async () => {
154
- const state = await mergeEvents('/path/to/events');
155
- expect(state).toBeDefined();
156
- });
157
- ```
158
-
159
- ## Clean Up
160
-
161
- Use `beforeEach` / `afterEach` for test isolation:
162
-
163
- ```typescript
164
- import { tmpdir } from 'os';
165
- import { rimraf } from 'rimraf';
166
-
167
- let testDir: string;
168
-
169
- beforeEach(async () => {
170
- testDir = path.join(tmpdir(), `forge-test-${Date.now()}`);
171
- await fs.mkdir(testDir, { recursive: true });
172
- });
173
-
174
- afterEach(async () => {
175
- await rimraf(testDir);
176
- });
177
- ```
1
+ ---
2
+ globs:
3
+ - "**/*.test.ts"
4
+ - "**/*.spec.ts"
5
+ - "src/**/*.ts"
6
+ ---
7
+
8
+ # Testing Standards
9
+
10
+ - **Placement:** Tests next to source files with `.test.ts` suffix
11
+ - **Coverage:** Core logic >90%, CLI >70%, Git ops >80%
12
+ - **Fixtures:** In `test/fixtures/` (events, state samples)
13
+ - **Mocking:** Mock externals (git, fs, network) with `jest.mock()`
14
+ - **Structure:** `describe` for groups, `it` for cases, always `await` async ops
15
+ - **Cleanup:** `beforeEach`/`afterEach` with temp dirs, `rimraf` after
16
+ - **Run:** `npm test` / `npm run test:coverage`