forge-dev-framework 1.1.0 → 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.
- package/.claude/commands/forge/README.md +156 -189
- package/.claude/commands/forge/add-phase.md +4 -3
- package/.claude/commands/forge/complete-milestone.md +1 -1
- package/.claude/commands/forge/convert.md +31 -0
- package/.claude/commands/forge/debug.md +12 -154
- package/.claude/commands/forge/discuss.md +60 -107
- package/.claude/commands/forge/execute.md +67 -142
- package/.claude/commands/forge/generate.md +8 -107
- package/.claude/commands/forge/help.md +9 -114
- package/.claude/commands/forge/init.md +10 -74
- package/.claude/commands/forge/insert-phase.md +4 -3
- package/.claude/commands/forge/new-milestone.md +1 -1
- package/.claude/commands/forge/new-project.md +12 -91
- package/.claude/commands/forge/pause-work.md +2 -2
- package/.claude/commands/forge/plan.md +114 -129
- package/.claude/commands/forge/quick.md +17 -106
- package/.claude/commands/forge/remove-phase.md +3 -2
- package/.claude/commands/forge/resume.md +22 -0
- package/.claude/commands/forge/team-add.md +24 -0
- package/.claude/commands/forge/team-create.md +22 -0
- package/.claude/commands/forge/team-remove.md +24 -0
- package/.claude/commands/forge/team-start.md +22 -0
- package/.claude/commands/forge/team-view.md +18 -0
- package/.claude/commands/forge/verify.md +68 -147
- package/.claude/hooks/forge-context-cleanup.cjs +79 -0
- package/.claude/hooks/forge-event-guard.cjs +36 -0
- package/.claude/hooks/forge-size-guard.cjs +55 -0
- package/.claude/rules/api-patterns.md +13 -98
- package/.claude/rules/context-efficiency.md +10 -0
- package/.claude/rules/security-baseline.md +18 -204
- package/.claude/rules/testing-standards.md +16 -177
- package/.claude/rules/ui-conventions.md +17 -142
- package/bin/forge.js +5 -3
- package/dist/bin/forge.js +5 -3
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +15 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/commands/convert.d.ts +6 -0
- package/dist/commands/convert.d.ts.map +1 -0
- package/dist/commands/convert.js +132 -0
- package/dist/commands/convert.js.map +1 -0
- package/dist/commands/generate.d.ts.map +1 -1
- package/dist/commands/generate.js +3 -2
- package/dist/commands/generate.js.map +1 -1
- package/dist/commands/index.d.ts +4 -4
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +4 -4
- package/dist/commands/index.js.map +1 -1
- package/dist/generators/gsd-converter.d.ts +100 -0
- package/dist/generators/gsd-converter.d.ts.map +1 -0
- package/dist/generators/gsd-converter.js +335 -0
- package/dist/generators/gsd-converter.js.map +1 -0
- package/dist/templates/.claude/rules/api-patterns.md.template +212 -0
- package/dist/templates/.claude/rules/security-baseline.md.template +322 -0
- package/dist/templates/.claude/rules/testing-standards.md.template +280 -0
- package/dist/templates/.claude/rules/ui-conventions.md.template +264 -0
- package/dist/templates/.planning/forge.config.json.template +75 -0
- package/dist/templates/CLAUDE.md.template +161 -0
- package/dist/templates/PLAN.md.template +177 -0
- package/dist/templates/PROJECT.md.template +156 -0
- package/dist/templates/REQUIREMENTS.md.template +221 -0
- package/dist/templates/ROADMAP.md.template +130 -0
- package/dist/types/index.d.ts +2 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +2 -2
- package/dist/types/index.js.map +1 -1
- package/dist/utils/index.d.ts +5 -5
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +5 -5
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/template-client.d.ts.map +1 -1
- package/dist/utils/template-client.js +3 -2
- package/dist/utils/template-client.js.map +1 -1
- package/package.json +6 -4
- package/.claude/commands/forge/resume-work.md +0 -122
- package/dist/git/__tests__/worktree.test.d.ts +0 -5
- package/dist/git/__tests__/worktree.test.d.ts.map +0 -1
- package/dist/git/__tests__/worktree.test.js +0 -121
- package/dist/git/__tests__/worktree.test.js.map +0 -1
- package/dist/git/codeowners.d.ts +0 -101
- package/dist/git/codeowners.d.ts.map +0 -1
- package/dist/git/codeowners.js +0 -216
- package/dist/git/codeowners.js.map +0 -1
- package/dist/git/commit.d.ts +0 -135
- package/dist/git/commit.d.ts.map +0 -1
- package/dist/git/commit.js +0 -223
- package/dist/git/commit.js.map +0 -1
- package/dist/git/hooks/commit-msg.d.ts +0 -8
- package/dist/git/hooks/commit-msg.d.ts.map +0 -1
- package/dist/git/hooks/commit-msg.js +0 -34
- package/dist/git/hooks/commit-msg.js.map +0 -1
- package/dist/git/hooks/pre-commit.d.ts +0 -8
- package/dist/git/hooks/pre-commit.d.ts.map +0 -1
- package/dist/git/hooks/pre-commit.js +0 -34
- package/dist/git/hooks/pre-commit.js.map +0 -1
- package/dist/git/pre-commit-hooks.d.ts +0 -117
- package/dist/git/pre-commit-hooks.d.ts.map +0 -1
- package/dist/git/pre-commit-hooks.js +0 -270
- package/dist/git/pre-commit-hooks.js.map +0 -1
- package/dist/git/wipe-protocol.d.ts +0 -281
- package/dist/git/wipe-protocol.d.ts.map +0 -1
- package/dist/git/wipe-protocol.js +0 -237
- package/dist/git/wipe-protocol.js.map +0 -1
- package/dist/git/worktree.d.ts +0 -69
- package/dist/git/worktree.d.ts.map +0 -1
- package/dist/git/worktree.js +0 -202
- package/dist/git/worktree.js.map +0 -1
|
@@ -1,204 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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`
|
|
@@ -1,142 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
- Green
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
### Error Messages
|
|
19
|
-
|
|
20
|
-
- Red ✗ or [ERROR] prefix
|
|
21
|
-
- What went wrong + why + how to fix
|
|
22
|
-
- Stack traces only in --debug mode
|
|
23
|
-
|
|
24
|
-
```bash
|
|
25
|
-
✗ Failed to merge events: Invalid event format in evt-003.json
|
|
26
|
-
→ Run 'forge validate' to check event schemas
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
### Warning Messages
|
|
30
|
-
|
|
31
|
-
- Yellow ⚠ prefix
|
|
32
|
-
- Not blocking, but noteworthy
|
|
33
|
-
|
|
34
|
-
```bash
|
|
35
|
-
⚠ Task api-003 is blocked by api-001 (in_progress)
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
### Info Messages
|
|
39
|
-
|
|
40
|
-
- Blue → prefix for steps
|
|
41
|
-
- Show progress for long operations
|
|
42
|
-
|
|
43
|
-
```bash
|
|
44
|
-
→ Loading events from state/events/...
|
|
45
|
-
→ Found 47 events, merging...
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
## Progress Indicators
|
|
49
|
-
|
|
50
|
-
For operations >2 seconds, show progress:
|
|
51
|
-
|
|
52
|
-
```typescript
|
|
53
|
-
import ora from 'ora';
|
|
54
|
-
|
|
55
|
-
const spinner = ora('Initializing project...').start();
|
|
56
|
-
// ... work ...
|
|
57
|
-
spinner.succeed('Project initialized');
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
## Help Text Format
|
|
61
|
-
|
|
62
|
-
```bash
|
|
63
|
-
forge init Initialize a new FORGE project
|
|
64
|
-
|
|
65
|
-
USAGE:
|
|
66
|
-
forge init [options]
|
|
67
|
-
|
|
68
|
-
OPTIONS:
|
|
69
|
-
--name <name> Project name (default: current directory)
|
|
70
|
-
--quick Skip interactive prompts (use defaults)
|
|
71
|
-
|
|
72
|
-
EXAMPLES:
|
|
73
|
-
$ forge init
|
|
74
|
-
$ forge init --name my-app --quick
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
## Interactive Prompts
|
|
78
|
-
|
|
79
|
-
When prompting users:
|
|
80
|
-
- Show current value in brackets: `[default: my-app]`
|
|
81
|
-
- Allow (y/N) confirmations
|
|
82
|
-
- Validate input before accepting
|
|
83
|
-
|
|
84
|
-
```typescript
|
|
85
|
-
import inquirer from 'inquirer';
|
|
86
|
-
|
|
87
|
-
const answers = await inquirer.prompt([
|
|
88
|
-
{
|
|
89
|
-
type: 'input',
|
|
90
|
-
name: 'projectName',
|
|
91
|
-
message: 'Project name:',
|
|
92
|
-
default: path.basename(process.cwd()),
|
|
93
|
-
},
|
|
94
|
-
{
|
|
95
|
-
type: 'confirm',
|
|
96
|
-
name: 'confirm',
|
|
97
|
-
message: 'Initialize project?',
|
|
98
|
-
default: true,
|
|
99
|
-
},
|
|
100
|
-
]);
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
## Tables and Lists
|
|
104
|
-
|
|
105
|
-
For structured output (status, tasks, events):
|
|
106
|
-
|
|
107
|
-
```bash
|
|
108
|
-
CURRENT TASKS:
|
|
109
|
-
|
|
110
|
-
ID STATUS OWNER TITLE
|
|
111
|
-
──────────────────────────────────────────────────────
|
|
112
|
-
api-001 completed backend User CRUD API
|
|
113
|
-
api-002 in_progress backend Auth service
|
|
114
|
-
ui-001 pending frontend Login form
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
## ASCII Art Banner
|
|
118
|
-
|
|
119
|
-
Show on startup:
|
|
120
|
-
|
|
121
|
-
```bash
|
|
122
|
-
█ ██ ██
|
|
123
|
-
█ █ █ █ █ █
|
|
124
|
-
███ █ █
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
## Exit Codes
|
|
128
|
-
|
|
129
|
-
- `0`: Success
|
|
130
|
-
- `1`: General error
|
|
131
|
-
- `2`: Invalid arguments
|
|
132
|
-
- `3`: State corruption
|
|
133
|
-
- `4`: Git error
|
|
134
|
-
|
|
135
|
-
## Verbose Flag
|
|
136
|
-
|
|
137
|
-
Support `-v` / `--verbose` for debug output:
|
|
138
|
-
|
|
139
|
-
```bash
|
|
140
|
-
forge merge -v
|
|
141
|
-
# → Shows detailed event replay, validation errors, etc.
|
|
142
|
-
```
|
|
1
|
+
---
|
|
2
|
+
globs:
|
|
3
|
+
- "src/cli/**/*.ts"
|
|
4
|
+
- "src/commands/**/*.ts"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# CLI UI Conventions
|
|
8
|
+
|
|
9
|
+
- **Success:** Green ✓ + concise message + what changed
|
|
10
|
+
- **Error:** Red ✗ + what went wrong + how to fix (stack traces only with `--debug`)
|
|
11
|
+
- **Warning:** Yellow ⚠ + non-blocking info
|
|
12
|
+
- **Info:** Blue → + progress steps
|
|
13
|
+
- **Progress:** Use `ora` spinner for ops >2 seconds
|
|
14
|
+
- **Help:** `USAGE → OPTIONS → EXAMPLES` format
|
|
15
|
+
- **Tables:** Header row + separator line for structured output (status, tasks)
|
|
16
|
+
- **Exit codes:** 0=success, 1=error, 2=bad args, 3=state corruption, 4=git error
|
|
17
|
+
- **Verbose:** Support `-v`/`--verbose` for debug output
|
package/bin/forge.js
CHANGED
|
@@ -3,12 +3,14 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* FORGE CLI Executable
|
|
5
5
|
* Entry point for the `forge` command
|
|
6
|
-
* This is the
|
|
6
|
+
* This is the source version — build copies it to dist/bin/
|
|
7
|
+
* and adjusts the import path via build:bin
|
|
7
8
|
*/
|
|
8
9
|
|
|
9
|
-
import {
|
|
10
|
+
import { ForgeCLI } from '../cli/index.js';
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
const cli = new ForgeCLI();
|
|
13
|
+
cli.run().catch((error) => {
|
|
12
14
|
console.error('FORGE CLI crashed:', error);
|
|
13
15
|
process.exit(1);
|
|
14
16
|
});
|
package/dist/bin/forge.js
CHANGED
|
@@ -3,12 +3,14 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* FORGE CLI Executable
|
|
5
5
|
* Entry point for the `forge` command
|
|
6
|
-
* This is the
|
|
6
|
+
* This is the source version — build copies it to dist/bin/
|
|
7
|
+
* and adjusts the import path via build:bin
|
|
7
8
|
*/
|
|
8
9
|
|
|
9
|
-
import {
|
|
10
|
+
import { ForgeCLI } from '../cli/index.js';
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
const cli = new ForgeCLI();
|
|
13
|
+
cli.run().catch((error) => {
|
|
12
14
|
console.error('FORGE CLI crashed:', error);
|
|
13
15
|
process.exit(1);
|
|
14
16
|
});
|
package/dist/cli/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";AAEA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";AAEA;;;GAGG;AAiCH,cAAM,QAAQ;IACZ,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,MAAM,CAAS;;IAQvB,OAAO,CAAC,KAAK;IAqBb,OAAO,CAAC,gBAAgB;IA8BxB;;OAEG;IACH,UAAU,IAAI,IAAI;IAalB;;OAEG;IACG,GAAG,CAAC,IAAI,GAAE,MAAM,EAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;CAoBxD;AAQD,OAAO,EAAE,QAAQ,EAAE,CAAC"}
|
package/dist/cli/index.js
CHANGED
|
@@ -14,10 +14,23 @@ import { StatusCommand } from '../commands/status.js';
|
|
|
14
14
|
import { ConfigCommand } from '../commands/config.js';
|
|
15
15
|
import { InitCommand } from '../commands/init.js';
|
|
16
16
|
import { GenerateCommand } from '../commands/generate.js';
|
|
17
|
+
import { ConvertCommand } from '../commands/convert.js';
|
|
17
18
|
import { createStubCommand } from '../commands/stubs.js';
|
|
18
19
|
import { Logger } from '../utils/logger.js';
|
|
20
|
+
import { readFileSync } from 'fs';
|
|
19
21
|
const __filename = fileURLToPath(import.meta.url);
|
|
20
22
|
const __dirname = path.dirname(__filename);
|
|
23
|
+
// Read version from package.json
|
|
24
|
+
function getVersion() {
|
|
25
|
+
try {
|
|
26
|
+
const pkgPath = path.resolve(__dirname, '..', '..', 'package.json');
|
|
27
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
28
|
+
return pkg.version || '0.0.0';
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return '0.0.0';
|
|
32
|
+
}
|
|
33
|
+
}
|
|
21
34
|
class ForgeCLI {
|
|
22
35
|
program;
|
|
23
36
|
logger;
|
|
@@ -31,7 +44,7 @@ class ForgeCLI {
|
|
|
31
44
|
this.program
|
|
32
45
|
.name('forge')
|
|
33
46
|
.description('Full Orchestration for Rapid Git Engineering')
|
|
34
|
-
.version(
|
|
47
|
+
.version(getVersion())
|
|
35
48
|
.option('-q, --quiet', 'Suppress non-error output')
|
|
36
49
|
.option('-v, --verbose', 'Enable verbose output')
|
|
37
50
|
.option('--debug', 'Enable debug mode');
|
|
@@ -50,6 +63,7 @@ class ForgeCLI {
|
|
|
50
63
|
new StatusCommand(this.program).register();
|
|
51
64
|
new ConfigCommand(this.program).register();
|
|
52
65
|
new GenerateCommand(this.program).register();
|
|
66
|
+
new ConvertCommand(this.program).register();
|
|
53
67
|
new HelpCommand(this.program).register();
|
|
54
68
|
// Stub commands for future milestones
|
|
55
69
|
createStubCommand('scan', 'Map existing codebase (brownfield projects)')(this.program);
|
package/dist/cli/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";AAEA;;;GAGG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AAEpC,kBAAkB;AAClB,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"convert.d.ts","sourceRoot":"","sources":["../../src/commands/convert.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAKzC,qBAAa,cAAe,SAAQ,YAAY;IAC9C,QAAQ,IAAI,IAAI;IAUV,OAAO,CAAC,OAAO,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;CAkI3C"}
|