forge-workflow 0.0.2 → 0.0.4
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/dev.md +26 -0
- package/.claude/commands/plan.md +141 -9
- package/.claude/commands/premerge.md +0 -3
- package/.claude/commands/rollback.md +4 -4
- package/.claude/commands/ship.md +71 -41
- package/.claude/commands/status.md +9 -38
- package/.claude/commands/validate.md +47 -2
- package/.cline/workflows/dev.md +337 -0
- package/.cline/workflows/plan.md +518 -0
- package/.cline/workflows/premerge.md +173 -0
- package/.cline/workflows/research.md +39 -0
- package/.cline/workflows/review.md +439 -0
- package/.cline/workflows/rollback.md +718 -0
- package/.cline/workflows/ship.md +161 -0
- package/.cline/workflows/sonarcloud.md +146 -0
- package/.cline/workflows/status.md +45 -0
- package/.cline/workflows/validate.md +279 -0
- package/.cline/workflows/verify.md +218 -0
- package/.codex/config.toml +11 -0
- package/.codex/skills/dev/SKILL.md +340 -0
- package/.codex/skills/plan/SKILL.md +521 -0
- package/.codex/skills/premerge/SKILL.md +176 -0
- package/.codex/skills/research/SKILL.md +42 -0
- package/.codex/skills/review/SKILL.md +442 -0
- package/.codex/skills/rollback/SKILL.md +721 -0
- package/.codex/skills/ship/SKILL.md +164 -0
- package/.codex/skills/sonarcloud/SKILL.md +149 -0
- package/.codex/skills/status/SKILL.md +48 -0
- package/.codex/skills/validate/SKILL.md +282 -0
- package/.codex/skills/verify/SKILL.md +221 -0
- package/.cursor/commands/dev.md +337 -0
- package/.cursor/commands/plan.md +518 -0
- package/.cursor/commands/premerge.md +173 -0
- package/.cursor/commands/research.md +39 -0
- package/.cursor/commands/review.md +439 -0
- package/.cursor/commands/rollback.md +718 -0
- package/.cursor/commands/ship.md +161 -0
- package/.cursor/commands/sonarcloud.md +146 -0
- package/.cursor/commands/status.md +45 -0
- package/.cursor/commands/validate.md +279 -0
- package/.cursor/commands/verify.md +218 -0
- package/.cursor/hooks/state/continual-learning-index.json +19 -0
- package/.cursor/hooks/state/continual-learning.json +8 -0
- package/.cursor/rules/permissions-guidance.mdc +37 -0
- package/.github/prompts/dev.prompt.md +342 -0
- package/.github/prompts/plan.prompt.md +523 -0
- package/.github/prompts/premerge.prompt.md +178 -0
- package/.github/prompts/research.prompt.md +44 -0
- package/.github/prompts/review.prompt.md +444 -0
- package/.github/prompts/rollback.prompt.md +723 -0
- package/.github/prompts/ship.prompt.md +166 -0
- package/.github/prompts/sonarcloud.prompt.md +151 -0
- package/.github/prompts/status.prompt.md +50 -0
- package/.github/prompts/validate.prompt.md +284 -0
- package/.github/prompts/verify.prompt.md +223 -0
- package/.kilocode/workflows/dev.md +341 -0
- package/.kilocode/workflows/plan.md +522 -0
- package/.kilocode/workflows/premerge.md +177 -0
- package/.kilocode/workflows/research.md +43 -0
- package/.kilocode/workflows/review.md +443 -0
- package/.kilocode/workflows/rollback.md +722 -0
- package/.kilocode/workflows/ship.md +165 -0
- package/.kilocode/workflows/sonarcloud.md +150 -0
- package/.kilocode/workflows/status.md +49 -0
- package/.kilocode/workflows/validate.md +283 -0
- package/.kilocode/workflows/verify.md +222 -0
- package/.opencode/commands/dev.md +340 -0
- package/.opencode/commands/plan.md +521 -0
- package/.opencode/commands/premerge.md +176 -0
- package/.opencode/commands/research.md +42 -0
- package/.opencode/commands/review.md +442 -0
- package/.opencode/commands/rollback.md +721 -0
- package/.opencode/commands/ship.md +164 -0
- package/.opencode/commands/sonarcloud.md +149 -0
- package/.opencode/commands/status.md +48 -0
- package/.opencode/commands/validate.md +282 -0
- package/.opencode/commands/verify.md +221 -0
- package/.roo/commands/dev.md +341 -0
- package/.roo/commands/plan.md +522 -0
- package/.roo/commands/premerge.md +177 -0
- package/.roo/commands/research.md +43 -0
- package/.roo/commands/review.md +443 -0
- package/.roo/commands/rollback.md +722 -0
- package/.roo/commands/ship.md +165 -0
- package/.roo/commands/sonarcloud.md +150 -0
- package/.roo/commands/status.md +49 -0
- package/.roo/commands/validate.md +283 -0
- package/.roo/commands/verify.md +222 -0
- package/AGENTS.md +7 -1
- package/CLAUDE.md +5 -4
- package/README.md +21 -19
- package/bin/{forge-validate.js → forge-preflight.js} +21 -15
- package/bin/forge.js +209 -138
- package/docs/AGENT_INSTALL_PROMPT.md +1 -1
- package/docs/BEADS_GITHUB_SYNC.md +251 -0
- package/docs/ENHANCED_ONBOARDING.md +8 -8
- package/docs/EXAMPLES.md +4 -4
- package/docs/GREPTILE_SETUP.md +1 -1
- package/docs/MANUAL_REVIEW_GUIDE.md +1 -1
- package/docs/ROADMAP.md +6 -6
- package/docs/SETUP.md +1 -2
- package/docs/TOOLCHAIN.md +15 -234
- package/docs/VALIDATION.md +11 -11
- package/install.sh +33 -39
- package/lib/agents-config.js +3 -3
- package/lib/commands/plan.js +11 -15
- package/lib/commands/recommend.js +2 -2
- package/lib/dep-guard/analyzer.js +294 -0
- package/lib/dep-guard/behavior-detector.js +98 -0
- package/lib/dep-guard/contract-detector.js +162 -0
- package/lib/dep-guard/import-detector.js +498 -0
- package/lib/dep-guard/path-utils.js +13 -0
- package/lib/dep-guard/rubric.js +120 -0
- package/lib/dep-guard/task-parser.js +318 -0
- package/lib/detect-agent.js +191 -0
- package/lib/detect-worktree.js +47 -0
- package/lib/file-hash.js +26 -0
- package/lib/plugin-catalog.js +18 -28
- package/lib/setup-action-log.js +139 -0
- package/lib/setup-summary-renderer.js +106 -0
- package/lib/setup.js +75 -1
- package/lib/workflow-profiles.js +5 -11
- package/package.json +17 -7
- package/skills/parallel-deep-research/SKILL.md +108 -0
- package/skills/parallel-deep-research/evals/README.md +27 -0
- package/skills/parallel-deep-research/evals/evals.json +62 -0
- package/skills/sonarcloud-analysis/SKILL.md +171 -0
- package/skills/sonarcloud-analysis/evals/README.md +27 -0
- package/skills/sonarcloud-analysis/evals/evals.json +50 -0
- package/skills/sonarcloud-analysis/references/api-reference.md +466 -0
- package/docs/WORKFLOW.md +0 -400
- package/docs/planning/PROGRESS.md +0 -396
- package/docs/plans/.gitkeep +0 -0
- package/docs/plans/2026-02-27-forge-test-suite-v2-decisions.md +0 -21
- package/docs/plans/2026-02-27-forge-test-suite-v2-design.md +0 -362
- package/docs/plans/2026-02-27-forge-test-suite-v2-tasks.md +0 -343
- package/docs/plans/2026-03-02-superpowers-gaps-decisions.md +0 -26
- package/docs/plans/2026-03-02-superpowers-gaps-design.md +0 -239
- package/docs/plans/2026-03-02-superpowers-gaps-tasks.md +0 -260
- package/docs/plans/2026-03-04-agent-command-parity-design.md +0 -163
- package/docs/plans/2026-03-04-verify-worktree-cleanup-decisions.md +0 -7
- package/docs/plans/2026-03-04-verify-worktree-cleanup-design.md +0 -165
- package/docs/plans/2026-03-05-forge-uto-decisions.md +0 -6
- package/docs/plans/2026-03-05-forge-uto-design.md +0 -116
- package/docs/plans/2026-03-05-forge-uto-tasks.md +0 -244
- package/docs/plans/2026-03-10-command-creator-and-eval-decisions.md +0 -52
- package/docs/plans/2026-03-10-command-creator-and-eval-design.md +0 -350
- package/docs/plans/2026-03-10-command-creator-and-eval-tasks.md +0 -426
- package/docs/plans/2026-03-10-stale-workflow-refs-decisions.md +0 -8
- package/docs/plans/2026-03-10-stale-workflow-refs-design.md +0 -80
- package/docs/plans/2026-03-10-stale-workflow-refs-tasks.md +0 -90
- package/docs/plans/2026-03-14-beads-plan-context-decisions.md +0 -9
- package/docs/plans/2026-03-14-beads-plan-context-design.md +0 -171
- package/docs/plans/2026-03-14-beads-plan-context-tasks.md +0 -160
- package/docs/plans/2026-03-14-skill-eval-loop-decisions.md +0 -33
- package/docs/plans/2026-03-14-skill-eval-loop-design.md +0 -118
- package/docs/plans/2026-03-14-skill-eval-loop-results.md +0 -78
- package/docs/plans/2026-03-14-skill-eval-loop-tasks.md +0 -160
- package/docs/plans/2026-03-15-agent-command-parity-v2-decisions.md +0 -11
- package/docs/plans/2026-03-15-agent-command-parity-v2-design.md +0 -145
- package/docs/plans/2026-03-15-agent-command-parity-v2-tasks.md +0 -211
- package/docs/research/TEMPLATE.md +0 -292
- package/docs/research/advanced-testing.md +0 -297
- package/docs/research/agent-permissions.md +0 -167
- package/docs/research/dependency-chain.md +0 -328
- package/docs/research/forge-workflow-v2.md +0 -550
- package/docs/research/plugin-architecture.md +0 -772
- package/docs/research/pr4-cli-automation.md +0 -326
- package/docs/research/premerge-verify-restructure.md +0 -205
- package/docs/research/skills-restructure.md +0 -508
- package/docs/research/sonarcloud-perfection-plan.md +0 -166
- package/docs/research/sonarcloud-quality-gate.md +0 -184
- package/docs/research/superpowers-integration.md +0 -403
- package/docs/research/superpowers.md +0 -319
- package/docs/research/test-environment.md +0 -519
package/lib/plugin-catalog.js
CHANGED
|
@@ -24,10 +24,10 @@ const STAGES = Object.freeze({
|
|
|
24
24
|
RESEARCH: 'research',
|
|
25
25
|
PLAN: 'plan',
|
|
26
26
|
DEV: 'dev',
|
|
27
|
-
|
|
27
|
+
VALIDATE: 'validate',
|
|
28
28
|
SHIP: 'ship',
|
|
29
29
|
REVIEW: 'review',
|
|
30
|
-
|
|
30
|
+
PREMERGE: 'premerge',
|
|
31
31
|
});
|
|
32
32
|
|
|
33
33
|
const BUDGET_MODES = Object.freeze({
|
|
@@ -101,16 +101,6 @@ const CATALOG = Object.freeze({
|
|
|
101
101
|
detectWhen: [],
|
|
102
102
|
install: { method: 'npm', cmd: 'bun add -g @beads/bd' },
|
|
103
103
|
},
|
|
104
|
-
openspec: {
|
|
105
|
-
name: 'OpenSpec',
|
|
106
|
-
type: 'cli',
|
|
107
|
-
tier: 'free',
|
|
108
|
-
stage: 'plan',
|
|
109
|
-
description: 'Spec-driven development proposals',
|
|
110
|
-
detectWhen: [],
|
|
111
|
-
install: { method: 'npm', cmd: 'bun add -g @fission-ai/openspec' },
|
|
112
|
-
},
|
|
113
|
-
|
|
114
104
|
// ── Dev ──
|
|
115
105
|
'typescript-lsp': {
|
|
116
106
|
name: 'TypeScript LSP',
|
|
@@ -155,7 +145,7 @@ const CATALOG = Object.freeze({
|
|
|
155
145
|
name: 'ESLint',
|
|
156
146
|
type: 'cli',
|
|
157
147
|
tier: 'free',
|
|
158
|
-
stage: '
|
|
148
|
+
stage: 'validate',
|
|
159
149
|
description: 'JavaScript/TypeScript linting',
|
|
160
150
|
detectWhen: ['file:eslint.config.js', 'dep:eslint'],
|
|
161
151
|
install: { method: 'npm', cmd: 'bun add -D eslint', dev: true },
|
|
@@ -164,7 +154,7 @@ const CATALOG = Object.freeze({
|
|
|
164
154
|
name: 'Biome',
|
|
165
155
|
type: 'cli',
|
|
166
156
|
tier: 'free',
|
|
167
|
-
stage: '
|
|
157
|
+
stage: 'validate',
|
|
168
158
|
description: 'Fast formatter and linter',
|
|
169
159
|
detectWhen: ['file:biome.json'],
|
|
170
160
|
install: { method: 'npm', cmd: 'bun add -D @biomejs/biome', dev: true },
|
|
@@ -173,7 +163,7 @@ const CATALOG = Object.freeze({
|
|
|
173
163
|
name: 'Prettier',
|
|
174
164
|
type: 'cli',
|
|
175
165
|
tier: 'free',
|
|
176
|
-
stage: '
|
|
166
|
+
stage: 'validate',
|
|
177
167
|
description: 'Opinionated code formatter',
|
|
178
168
|
detectWhen: ['file:.prettierrc'],
|
|
179
169
|
install: { method: 'npm', cmd: 'bun add -D prettier', dev: true },
|
|
@@ -182,7 +172,7 @@ const CATALOG = Object.freeze({
|
|
|
182
172
|
name: 'ESLint Security Plugin',
|
|
183
173
|
type: 'config',
|
|
184
174
|
tier: 'free',
|
|
185
|
-
stage: '
|
|
175
|
+
stage: 'validate',
|
|
186
176
|
description: 'Security-focused ESLint rules',
|
|
187
177
|
detectWhen: ['dep:express', 'dep:fastify'],
|
|
188
178
|
install: { method: 'npm', cmd: 'bun add -D eslint-plugin-security', dev: true },
|
|
@@ -191,7 +181,7 @@ const CATALOG = Object.freeze({
|
|
|
191
181
|
name: 'SonarCloud Analysis',
|
|
192
182
|
type: 'skill',
|
|
193
183
|
tier: 'free-public',
|
|
194
|
-
stage: '
|
|
184
|
+
stage: 'validate',
|
|
195
185
|
description: 'Code quality and security analysis via SonarCloud REST API',
|
|
196
186
|
detectWhen: [],
|
|
197
187
|
install: {
|
|
@@ -204,7 +194,7 @@ const CATALOG = Object.freeze({
|
|
|
204
194
|
name: 'Sonar Scanner',
|
|
205
195
|
type: 'cli',
|
|
206
196
|
tier: 'free-public',
|
|
207
|
-
stage: '
|
|
197
|
+
stage: 'validate',
|
|
208
198
|
description: 'SonarCloud/SonarQube CLI scanner',
|
|
209
199
|
detectWhen: ['file:sonar-project.properties'],
|
|
210
200
|
install: { method: 'npm', cmd: 'bun add -D sonarqube-scanner', dev: true },
|
|
@@ -214,7 +204,7 @@ const CATALOG = Object.freeze({
|
|
|
214
204
|
name: 'CodeQL',
|
|
215
205
|
type: 'cli',
|
|
216
206
|
tier: 'free-public',
|
|
217
|
-
stage: '
|
|
207
|
+
stage: 'validate',
|
|
218
208
|
description: 'Semantic code analysis by GitHub',
|
|
219
209
|
detectWhen: [],
|
|
220
210
|
install: { method: 'binary', cmd: 'GitHub-provided (github.com/github/codeql-action)' },
|
|
@@ -224,7 +214,7 @@ const CATALOG = Object.freeze({
|
|
|
224
214
|
name: 'npm audit',
|
|
225
215
|
type: 'cli',
|
|
226
216
|
tier: 'free',
|
|
227
|
-
stage: '
|
|
217
|
+
stage: 'validate',
|
|
228
218
|
description: 'Dependency vulnerability scanning',
|
|
229
219
|
detectWhen: [],
|
|
230
220
|
install: { method: 'npm', cmd: 'built-in (bun pm audit)' },
|
|
@@ -233,7 +223,7 @@ const CATALOG = Object.freeze({
|
|
|
233
223
|
name: 'Vitest',
|
|
234
224
|
type: 'cli',
|
|
235
225
|
tier: 'free',
|
|
236
|
-
stage: '
|
|
226
|
+
stage: 'validate',
|
|
237
227
|
description: 'Vite-native test runner',
|
|
238
228
|
detectWhen: ['dep:vitest', 'file:vitest.config.ts'],
|
|
239
229
|
install: { method: 'npm', cmd: 'bun add -D vitest', dev: true },
|
|
@@ -242,7 +232,7 @@ const CATALOG = Object.freeze({
|
|
|
242
232
|
name: 'Jest',
|
|
243
233
|
type: 'cli',
|
|
244
234
|
tier: 'free',
|
|
245
|
-
stage: '
|
|
235
|
+
stage: 'validate',
|
|
246
236
|
description: 'Delightful JavaScript testing',
|
|
247
237
|
detectWhen: ['dep:jest', 'file:jest.config.js'],
|
|
248
238
|
install: { method: 'npm', cmd: 'bun add -D jest', dev: true },
|
|
@@ -251,7 +241,7 @@ const CATALOG = Object.freeze({
|
|
|
251
241
|
name: 'Playwright',
|
|
252
242
|
type: 'cli',
|
|
253
243
|
tier: 'free',
|
|
254
|
-
stage: '
|
|
244
|
+
stage: 'validate',
|
|
255
245
|
description: 'End-to-end browser testing',
|
|
256
246
|
detectWhen: ['dep:@playwright/test'],
|
|
257
247
|
install: { method: 'npm', cmd: 'bun add -D @playwright/test', dev: true },
|
|
@@ -260,7 +250,7 @@ const CATALOG = Object.freeze({
|
|
|
260
250
|
name: 'c8',
|
|
261
251
|
type: 'cli',
|
|
262
252
|
tier: 'free',
|
|
263
|
-
stage: '
|
|
253
|
+
stage: 'validate',
|
|
264
254
|
description: 'Native V8 code coverage',
|
|
265
255
|
detectWhen: [],
|
|
266
256
|
install: { method: 'npm', cmd: 'bun add -D c8', dev: true },
|
|
@@ -269,7 +259,7 @@ const CATALOG = Object.freeze({
|
|
|
269
259
|
name: 'Stryker',
|
|
270
260
|
type: 'cli',
|
|
271
261
|
tier: 'free',
|
|
272
|
-
stage: '
|
|
262
|
+
stage: 'validate',
|
|
273
263
|
description: 'Mutation testing for JavaScript',
|
|
274
264
|
detectWhen: [],
|
|
275
265
|
install: { method: 'npm', cmd: 'bun add -D @stryker-mutator/core', dev: true },
|
|
@@ -278,7 +268,7 @@ const CATALOG = Object.freeze({
|
|
|
278
268
|
name: 'Oxlint',
|
|
279
269
|
type: 'cli',
|
|
280
270
|
tier: 'free',
|
|
281
|
-
stage: '
|
|
271
|
+
stage: 'validate',
|
|
282
272
|
description: 'Blazing-fast Rust-based linter',
|
|
283
273
|
detectWhen: [],
|
|
284
274
|
install: { method: 'npm', cmd: 'bun add -D oxlint', dev: true },
|
|
@@ -341,7 +331,7 @@ const CATALOG = Object.freeze({
|
|
|
341
331
|
name: 'Changesets',
|
|
342
332
|
type: 'cli',
|
|
343
333
|
tier: 'free',
|
|
344
|
-
stage: '
|
|
334
|
+
stage: 'premerge',
|
|
345
335
|
description: 'Versioning and changelog management',
|
|
346
336
|
detectWhen: ['file:package.json'],
|
|
347
337
|
install: { method: 'npm', cmd: 'bun add -D @changesets/cli', dev: true },
|
|
@@ -350,7 +340,7 @@ const CATALOG = Object.freeze({
|
|
|
350
340
|
name: 'Release Please',
|
|
351
341
|
type: 'config',
|
|
352
342
|
tier: 'free',
|
|
353
|
-
stage: '
|
|
343
|
+
stage: 'premerge',
|
|
354
344
|
description: 'Automated release PRs by Google',
|
|
355
345
|
detectWhen: [],
|
|
356
346
|
install: { method: 'config', cmd: 'GitHub Action (google-github-actions/release-please-action)' },
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized action log for setup operations.
|
|
3
|
+
*
|
|
4
|
+
* Collects file-level actions (created, skipped, merged, etc.) during
|
|
5
|
+
* `forge setup` so the CLI can display a structured summary at the end.
|
|
6
|
+
*
|
|
7
|
+
* @module setup-action-log
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** Map of directory prefixes to human-readable agent names. */
|
|
11
|
+
const AGENT_PREFIXES = {
|
|
12
|
+
'.claude/': 'Claude Code',
|
|
13
|
+
'.cursor/': 'Cursor',
|
|
14
|
+
'.windsurf/': 'Windsurf',
|
|
15
|
+
'.cline/': 'Cline',
|
|
16
|
+
'.codex/': 'Codex',
|
|
17
|
+
'.opencode/': 'OpenCode',
|
|
18
|
+
'.kilocode/': 'Kilocode',
|
|
19
|
+
'.roo/': 'Roo Code',
|
|
20
|
+
'.github/prompts/': 'GitHub Copilot'
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Detect the agent name from a file path.
|
|
25
|
+
*
|
|
26
|
+
* @param {string} filePath - Relative file path (e.g. `.claude/settings.json`)
|
|
27
|
+
* @returns {string} Agent name or `'General'` if no agent prefix matches
|
|
28
|
+
*/
|
|
29
|
+
function detectAgent(filePath) {
|
|
30
|
+
const normalized = filePath.replace(/\\/g, '/');
|
|
31
|
+
for (const [prefix, name] of Object.entries(AGENT_PREFIXES)) {
|
|
32
|
+
if (normalized.startsWith(prefix)) {
|
|
33
|
+
return name;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return 'General';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Strip the agent directory prefix from a file path.
|
|
41
|
+
*
|
|
42
|
+
* @param {string} filePath - Relative file path
|
|
43
|
+
* @returns {string} Path with the leading agent directory removed
|
|
44
|
+
*/
|
|
45
|
+
function stripAgentPrefix(filePath) {
|
|
46
|
+
const normalized = filePath.replace(/\\/g, '/');
|
|
47
|
+
for (const prefix of Object.keys(AGENT_PREFIXES)) {
|
|
48
|
+
if (normalized.startsWith(prefix)) {
|
|
49
|
+
return normalized.slice(prefix.length);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return normalized;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
class SetupActionLog {
|
|
56
|
+
constructor() {
|
|
57
|
+
/** @type {Array<{file: string, action: string, detail: string|null}>} */
|
|
58
|
+
this.actions = [];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Record a setup action.
|
|
63
|
+
*
|
|
64
|
+
* @param {string} file - Relative file path that was acted on
|
|
65
|
+
* @param {string} action - One of: created, skipped, merged, conflict, removed, force-created
|
|
66
|
+
* @param {string|null} [detail=null] - Optional human-readable detail
|
|
67
|
+
*/
|
|
68
|
+
add(file, action, detail = null) {
|
|
69
|
+
this.actions.push({ file, action, detail });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Get counts grouped by action type.
|
|
74
|
+
*
|
|
75
|
+
* @returns {Record<string, number>} e.g. `{ created: 4, skipped: 2 }`
|
|
76
|
+
*/
|
|
77
|
+
getSummary() {
|
|
78
|
+
const counts = {};
|
|
79
|
+
for (const { action } of this.actions) {
|
|
80
|
+
counts[action] = (counts[action] || 0) + 1;
|
|
81
|
+
}
|
|
82
|
+
return counts;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Get the full ordered list of actions.
|
|
87
|
+
*
|
|
88
|
+
* @returns {Array<{file: string, action: string, detail: string|null}>}
|
|
89
|
+
*/
|
|
90
|
+
getVerbose() {
|
|
91
|
+
return this.actions;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Group files by detected agent name.
|
|
96
|
+
*
|
|
97
|
+
* Each agent key maps to an object whose keys are action types and whose
|
|
98
|
+
* values are arrays of file paths (with the agent prefix stripped).
|
|
99
|
+
*
|
|
100
|
+
* @returns {Record<string, Record<string, string[]>>}
|
|
101
|
+
*/
|
|
102
|
+
getAgentSummary() {
|
|
103
|
+
const agents = {};
|
|
104
|
+
for (const { file, action } of this.actions) {
|
|
105
|
+
const agent = detectAgent(file);
|
|
106
|
+
const stripped = stripAgentPrefix(file);
|
|
107
|
+
|
|
108
|
+
if (!agents[agent]) {
|
|
109
|
+
agents[agent] = {};
|
|
110
|
+
}
|
|
111
|
+
if (!agents[agent][action]) {
|
|
112
|
+
agents[agent][action] = [];
|
|
113
|
+
}
|
|
114
|
+
agents[agent][action].push(stripped);
|
|
115
|
+
}
|
|
116
|
+
return agents;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Filter actions by type.
|
|
121
|
+
*
|
|
122
|
+
* @param {string} action - The action type to filter on
|
|
123
|
+
* @returns {Array<{file: string, action: string, detail: string|null}>}
|
|
124
|
+
*/
|
|
125
|
+
getByAction(action) {
|
|
126
|
+
return this.actions.filter(a => a.action === action);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Total number of recorded actions.
|
|
131
|
+
*
|
|
132
|
+
* @returns {number}
|
|
133
|
+
*/
|
|
134
|
+
get length() {
|
|
135
|
+
return this.actions.length;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
module.exports = { SetupActionLog };
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Renders a clean summary from SetupActionLog data.
|
|
3
|
+
*
|
|
4
|
+
* Default mode: 3-line concise output.
|
|
5
|
+
* Verbose mode: file-by-file detail grouped by agent.
|
|
6
|
+
*
|
|
7
|
+
* @module setup-summary-renderer
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Capitalize the first letter of a string.
|
|
12
|
+
*
|
|
13
|
+
* @param {string} str
|
|
14
|
+
* @returns {string}
|
|
15
|
+
*/
|
|
16
|
+
function capitalize(str) {
|
|
17
|
+
if (!str) return str;
|
|
18
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Render setup summary as a string.
|
|
23
|
+
*
|
|
24
|
+
* @param {import('./setup-action-log').SetupActionLog} actionLog - The populated action log
|
|
25
|
+
* @param {string[]} agentNames - List of configured agent slugs (e.g. ['claude', 'cursor'])
|
|
26
|
+
* @param {boolean} verbose - Whether to show file-by-file detail
|
|
27
|
+
* @returns {string} The formatted summary output
|
|
28
|
+
*/
|
|
29
|
+
function renderSetupSummary(actionLog, agentNames, verbose) {
|
|
30
|
+
if (verbose) {
|
|
31
|
+
return renderVerbose(actionLog, agentNames);
|
|
32
|
+
}
|
|
33
|
+
return renderDefault(actionLog, agentNames);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Render the default 3-line concise summary.
|
|
38
|
+
*
|
|
39
|
+
* @param {import('./setup-action-log').SetupActionLog} actionLog
|
|
40
|
+
* @param {string[]} agentNames
|
|
41
|
+
* @returns {string}
|
|
42
|
+
*/
|
|
43
|
+
function renderDefault(actionLog, agentNames) {
|
|
44
|
+
const agentCount = agentNames.length;
|
|
45
|
+
const agentLabel = agentCount === 1 ? '1 agent' : `${agentCount} agents`;
|
|
46
|
+
const agentList = agentNames.length > 0 ? ` (${agentNames.join(', ')})` : '';
|
|
47
|
+
|
|
48
|
+
const summary = actionLog.getSummary();
|
|
49
|
+
|
|
50
|
+
// Build counts line — only include non-zero actions
|
|
51
|
+
const displayOrder = ['created', 'skipped', 'merged', 'force-created', 'updated', 'conflict', 'removed'];
|
|
52
|
+
const parts = [];
|
|
53
|
+
for (const action of displayOrder) {
|
|
54
|
+
if (summary[action] && summary[action] > 0) {
|
|
55
|
+
parts.push(`${capitalize(action)}: ${summary[action]} ${summary[action] === 1 ? 'file' : 'files'}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// Include any actions not in displayOrder
|
|
59
|
+
for (const [action, count] of Object.entries(summary)) {
|
|
60
|
+
if (!displayOrder.includes(action) && count > 0) {
|
|
61
|
+
parts.push(`${capitalize(action)}: ${count} ${count === 1 ? 'file' : 'files'}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const lines = [];
|
|
66
|
+
lines.push(`Forge setup complete — ${agentLabel} configured${agentList}`);
|
|
67
|
+
|
|
68
|
+
if (parts.length > 0) {
|
|
69
|
+
lines.push(` ${parts.join(' | ')}`);
|
|
70
|
+
} else {
|
|
71
|
+
lines.push(' 0 files changed');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
lines.push(' Run forge setup --verbose to see all files');
|
|
75
|
+
|
|
76
|
+
return lines.join('\n');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Render verbose file-by-file output grouped by agent.
|
|
81
|
+
*
|
|
82
|
+
* @param {import('./setup-action-log').SetupActionLog} actionLog
|
|
83
|
+
* @param {string[]} _agentNames - Not used in verbose (agents come from log data)
|
|
84
|
+
* @returns {string}
|
|
85
|
+
*/
|
|
86
|
+
function renderVerbose(actionLog, _agentNames) {
|
|
87
|
+
const agentSummary = actionLog.getAgentSummary();
|
|
88
|
+
const lines = [];
|
|
89
|
+
|
|
90
|
+
for (const [agent, actions] of Object.entries(agentSummary)) {
|
|
91
|
+
for (const [action, files] of Object.entries(actions)) {
|
|
92
|
+
const fileCount = files.length;
|
|
93
|
+
const fileLabel = fileCount === 1 ? '1 file' : `${fileCount} files`;
|
|
94
|
+
const fileList = files.join(', ');
|
|
95
|
+
lines.push(`${agent}: ${fileList} (${fileLabel}) [${action}]`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (lines.length === 0) {
|
|
100
|
+
return 'No file operations recorded.';
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return lines.join('\n');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
module.exports = { renderSetupSummary };
|
package/lib/setup.js
CHANGED
|
@@ -109,10 +109,84 @@ async function markStepComplete(projectPath, stepName) {
|
|
|
109
109
|
await saveSetupState(projectPath, state);
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
+
/**
|
|
113
|
+
* Scaffold GitHub-Beads sync files into a target project.
|
|
114
|
+
* Copies workflow, config, and mapping template — never overwrites existing files.
|
|
115
|
+
*
|
|
116
|
+
* @param {string} projectPath - Target project root
|
|
117
|
+
* @param {string} pkgDir - Forge package directory (source of template files)
|
|
118
|
+
* @returns {Promise<{created: string[], skipped: string[]}>}
|
|
119
|
+
*/
|
|
120
|
+
async function scaffoldGithubBeadsSync(projectPath, pkgDir) {
|
|
121
|
+
const created = [];
|
|
122
|
+
const skipped = [];
|
|
123
|
+
|
|
124
|
+
// Sync script modules that workflows depend on at runtime
|
|
125
|
+
const syncScripts = [
|
|
126
|
+
'config.mjs', 'mapping.mjs', 'comment.mjs', 'github-api.mjs',
|
|
127
|
+
'sanitize.mjs', 'run-bd.mjs', 'label-mapper.mjs', 'index.mjs',
|
|
128
|
+
'reverse-sync.mjs', 'reverse-sync-cli.mjs',
|
|
129
|
+
];
|
|
130
|
+
|
|
131
|
+
const files = [
|
|
132
|
+
// Phase 1: GitHub → Beads
|
|
133
|
+
{
|
|
134
|
+
src: path.join(pkgDir, '.github', 'workflows', 'github-to-beads.yml'),
|
|
135
|
+
dest: path.join('.github', 'workflows', 'github-to-beads.yml'),
|
|
136
|
+
},
|
|
137
|
+
// Phase 2: Beads → GitHub
|
|
138
|
+
{
|
|
139
|
+
src: path.join(pkgDir, '.github', 'workflows', 'beads-to-github.yml'),
|
|
140
|
+
dest: path.join('.github', 'workflows', 'beads-to-github.yml'),
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
src: path.join(pkgDir, 'scripts', 'github-beads-sync.config.json'),
|
|
144
|
+
dest: path.join('scripts', 'github-beads-sync.config.json'),
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
src: null, // generated in-place (empty mapping template)
|
|
148
|
+
dest: path.join('.github', 'beads-mapping.json'),
|
|
149
|
+
content: '{}',
|
|
150
|
+
},
|
|
151
|
+
// Sync script modules
|
|
152
|
+
...syncScripts.map((name) => ({
|
|
153
|
+
src: path.join(pkgDir, 'scripts', 'github-beads-sync', name),
|
|
154
|
+
dest: path.join('scripts', 'github-beads-sync', name),
|
|
155
|
+
})),
|
|
156
|
+
];
|
|
157
|
+
|
|
158
|
+
for (const file of files) {
|
|
159
|
+
const destPath = path.join(projectPath, file.dest);
|
|
160
|
+
|
|
161
|
+
// Never overwrite existing files — preserve user customizations
|
|
162
|
+
if (fs.existsSync(destPath)) {
|
|
163
|
+
skipped.push(file.dest);
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Ensure parent directory exists
|
|
168
|
+
const destDir = path.dirname(destPath);
|
|
169
|
+
await fs.promises.mkdir(destDir, { recursive: true });
|
|
170
|
+
|
|
171
|
+
if (file.src) {
|
|
172
|
+
// Copy from template
|
|
173
|
+
await fs.promises.copyFile(file.src, destPath);
|
|
174
|
+
} else {
|
|
175
|
+
// Write generated content
|
|
176
|
+
await fs.promises.writeFile(destPath, file.content, 'utf-8');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
created.push(file.dest);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return { created, skipped };
|
|
183
|
+
}
|
|
184
|
+
|
|
112
185
|
module.exports = {
|
|
113
186
|
saveSetupState,
|
|
114
187
|
loadSetupState,
|
|
115
188
|
isSetupComplete,
|
|
116
189
|
getNextStep,
|
|
117
|
-
markStepComplete
|
|
190
|
+
markStepComplete,
|
|
191
|
+
scaffoldGithubBeadsSync
|
|
118
192
|
};
|
package/lib/workflow-profiles.js
CHANGED
|
@@ -12,29 +12,26 @@
|
|
|
12
12
|
const PROFILES = {
|
|
13
13
|
critical: {
|
|
14
14
|
name: 'Critical (Maximum Rigor)',
|
|
15
|
-
stages: ['/status', '/
|
|
15
|
+
stages: ['/status', '/plan', '/dev', '/validate', '/ship', '/review', '/premerge', '/verify'],
|
|
16
16
|
tdd: 'strict',
|
|
17
17
|
research: 'required',
|
|
18
|
-
openspec: 'required-strategic',
|
|
19
18
|
description: 'Security, auth, payments, data integrity, breaking changes',
|
|
20
19
|
keywords: ['auth', 'security', 'payment', 'crypto', 'password', 'token', 'session', 'data-migration', 'breaking']
|
|
21
20
|
},
|
|
22
21
|
|
|
23
22
|
standard: {
|
|
24
23
|
name: 'Standard (Full Workflow)',
|
|
25
|
-
stages: ['/status', '/plan', '/dev', '/validate', '/ship', '/
|
|
24
|
+
stages: ['/status', '/plan', '/dev', '/validate', '/ship', '/review', '/premerge'],
|
|
26
25
|
tdd: 'required',
|
|
27
26
|
research: 'optional',
|
|
28
|
-
openspec: 'strategic',
|
|
29
27
|
description: 'Most features and non-critical enhancements'
|
|
30
28
|
},
|
|
31
29
|
|
|
32
30
|
simple: {
|
|
33
31
|
name: 'Simple (Streamlined)',
|
|
34
|
-
stages: ['/dev', '/validate', '/ship', '/
|
|
32
|
+
stages: ['/dev', '/validate', '/ship', '/premerge'],
|
|
35
33
|
tdd: 'recommended',
|
|
36
34
|
research: 'skip',
|
|
37
|
-
openspec: 'no',
|
|
38
35
|
description: 'UI tweaks, small improvements, minor fixes'
|
|
39
36
|
},
|
|
40
37
|
|
|
@@ -43,26 +40,23 @@ const PROFILES = {
|
|
|
43
40
|
stages: ['/dev', '/validate', '/ship'],
|
|
44
41
|
tdd: 'required', // Must reproduce bug
|
|
45
42
|
research: 'skip',
|
|
46
|
-
openspec: 'no',
|
|
47
43
|
description: 'Production bugs, urgent fixes',
|
|
48
44
|
keywords: ['urgent', 'production', 'emergency', 'hotfix', 'critical']
|
|
49
45
|
},
|
|
50
46
|
|
|
51
47
|
docs: {
|
|
52
48
|
name: 'Docs (Documentation Only)',
|
|
53
|
-
stages: ['/verify', '/ship', '/
|
|
49
|
+
stages: ['/verify', '/ship', '/premerge'],
|
|
54
50
|
tdd: 'no',
|
|
55
51
|
research: 'no',
|
|
56
|
-
openspec: 'no',
|
|
57
52
|
description: 'Documentation updates, README changes'
|
|
58
53
|
},
|
|
59
54
|
|
|
60
55
|
refactor: {
|
|
61
56
|
name: 'Refactor (Behavior-Preserving)',
|
|
62
|
-
stages: ['/plan', '/dev', '/validate', '/ship', '/
|
|
57
|
+
stages: ['/plan', '/dev', '/validate', '/ship', '/premerge'],
|
|
63
58
|
tdd: 'strict', // Must preserve behavior
|
|
64
59
|
research: 'optional',
|
|
65
|
-
openspec: 'architectural',
|
|
66
60
|
description: 'Code cleanup, optimization, behavior-preserving improvements'
|
|
67
61
|
}
|
|
68
62
|
};
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "forge-workflow",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"description": "7-stage TDD workflow for ALL AI coding agents (Claude, Cursor, Cline, OpenCode, Copilot, Kilo Code, Roo Code, Codex)",
|
|
5
5
|
"bin": {
|
|
6
6
|
"forge": "bin/forge.js",
|
|
7
|
-
"forge-
|
|
7
|
+
"forge-preflight": "bin/forge-preflight.js"
|
|
8
8
|
},
|
|
9
9
|
"workspaces": [
|
|
10
10
|
"packages/*"
|
|
@@ -15,9 +15,8 @@
|
|
|
15
15
|
"test:all": "bun run test:setup && bun test",
|
|
16
16
|
"test:coverage": "c8 --check-coverage bun test",
|
|
17
17
|
"typecheck": "echo 'No TypeScript in project - skipping type check'",
|
|
18
|
-
"lint": "eslint .",
|
|
18
|
+
"lint": "eslint . --max-warnings 0",
|
|
19
19
|
"check": "bash scripts/validate.sh",
|
|
20
|
-
"postinstall": "node ./bin/forge.js",
|
|
21
20
|
"prepare": "lefthook install || echo 'Note: lefthook not installed. Git hooks disabled. Run: bun add -d lefthook'",
|
|
22
21
|
"setup": "node ./bin/forge.js setup",
|
|
23
22
|
"help": "node ./bin/forge.js --help",
|
|
@@ -39,7 +38,7 @@
|
|
|
39
38
|
"@commitlint/config-conventional": "^20.4.1",
|
|
40
39
|
"@eslint/js": "^10.0.1",
|
|
41
40
|
"@microsoft/eslint-formatter-sarif": "^3.1.0",
|
|
42
|
-
"@stryker-mutator/core": "^9.
|
|
41
|
+
"@stryker-mutator/core": "^9.6.0",
|
|
43
42
|
"c8": "^11.0.0",
|
|
44
43
|
"eslint": "^10.0.2",
|
|
45
44
|
"globals": "^17.3.0",
|
|
@@ -76,12 +75,21 @@
|
|
|
76
75
|
"files": [
|
|
77
76
|
"bin/",
|
|
78
77
|
"lib/",
|
|
78
|
+
"skills/",
|
|
79
79
|
".claude/commands/",
|
|
80
80
|
".claude/rules/",
|
|
81
81
|
".claude/scripts/",
|
|
82
|
-
".
|
|
82
|
+
".claude/skills/",
|
|
83
|
+
".cursor/",
|
|
84
|
+
".cline/",
|
|
85
|
+
".roo/",
|
|
86
|
+
".codex/",
|
|
87
|
+
".kilocode/",
|
|
88
|
+
".opencode/",
|
|
89
|
+
".github/prompts/",
|
|
83
90
|
".github/PLUGIN_TEMPLATE.json",
|
|
84
|
-
"
|
|
91
|
+
".forge/hooks/",
|
|
92
|
+
"docs/*.md",
|
|
85
93
|
"install.sh",
|
|
86
94
|
"lefthook.yml",
|
|
87
95
|
"AGENTS.md",
|
|
@@ -89,10 +97,12 @@
|
|
|
89
97
|
".mcp.json.example"
|
|
90
98
|
],
|
|
91
99
|
"dependencies": {
|
|
100
|
+
"@babel/parser": "^7.29.2",
|
|
92
101
|
"fastest-levenshtein": "^1.0.16"
|
|
93
102
|
},
|
|
94
103
|
"overrides": {
|
|
95
104
|
"eslint": "^10.0.2",
|
|
105
|
+
"flatted": "3.4.2",
|
|
96
106
|
"minimatch": "10.2.4"
|
|
97
107
|
},
|
|
98
108
|
"c8": {
|