forge-workflow 0.0.4 → 0.0.6
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 +345 -340
- package/.claude/commands/plan.md +566 -521
- package/.claude/commands/premerge.md +186 -176
- package/.claude/commands/research.md +42 -42
- package/.claude/commands/review.md +448 -442
- package/.claude/commands/rollback.md +721 -721
- package/.claude/commands/ship.md +212 -164
- package/.claude/commands/sonarcloud.md +152 -152
- package/.claude/commands/status.md +90 -48
- package/.claude/commands/validate.md +288 -282
- package/.claude/commands/verify.md +269 -221
- package/.claude/rules/greptile-review-process.md +285 -285
- package/.claude/rules/workflow.md +121 -105
- package/.claude/scripts/greptile-resolve.sh +558 -526
- package/.claude/scripts/load-env.sh +32 -32
- package/.cline/workflows/dev.md +342 -337
- package/.cline/workflows/plan.md +563 -518
- package/.cline/workflows/premerge.md +183 -173
- package/.cline/workflows/research.md +39 -39
- package/.cline/workflows/review.md +445 -439
- package/.cline/workflows/rollback.md +718 -718
- package/.cline/workflows/ship.md +209 -161
- package/.cline/workflows/sonarcloud.md +146 -146
- package/.cline/workflows/status.md +87 -45
- package/.cline/workflows/validate.md +285 -279
- package/.cline/workflows/verify.md +266 -218
- package/.codex/config.toml +11 -11
- package/.codex/skills/dev/SKILL.md +345 -340
- package/.codex/skills/plan/SKILL.md +566 -521
- package/.codex/skills/premerge/SKILL.md +186 -176
- package/.codex/skills/research/SKILL.md +42 -42
- package/.codex/skills/review/SKILL.md +448 -442
- package/.codex/skills/rollback/SKILL.md +721 -721
- package/.codex/skills/ship/SKILL.md +212 -164
- package/.codex/skills/sonarcloud/SKILL.md +149 -149
- package/.codex/skills/status/SKILL.md +90 -48
- package/.codex/skills/validate/SKILL.md +288 -282
- package/.codex/skills/verify/SKILL.md +269 -221
- package/.cursor/commands/dev.md +342 -337
- package/.cursor/commands/plan.md +563 -518
- package/.cursor/commands/premerge.md +183 -173
- package/.cursor/commands/research.md +39 -39
- package/.cursor/commands/review.md +445 -439
- package/.cursor/commands/rollback.md +718 -718
- package/.cursor/commands/ship.md +209 -161
- package/.cursor/commands/sonarcloud.md +146 -146
- package/.cursor/commands/status.md +87 -45
- package/.cursor/commands/validate.md +285 -279
- package/.cursor/commands/verify.md +266 -218
- package/.cursor/rules/permissions-guidance.mdc +37 -37
- package/.forge/hooks/check-tdd.js +240 -240
- package/.github/PLUGIN_TEMPLATE.json +32 -32
- package/.github/prompts/dev.prompt.md +347 -342
- package/.github/prompts/plan.prompt.md +568 -523
- package/.github/prompts/premerge.prompt.md +188 -178
- package/.github/prompts/research.prompt.md +44 -44
- package/.github/prompts/review.prompt.md +450 -444
- package/.github/prompts/rollback.prompt.md +723 -723
- package/.github/prompts/ship.prompt.md +214 -166
- package/.github/prompts/sonarcloud.prompt.md +151 -151
- package/.github/prompts/status.prompt.md +92 -50
- package/.github/prompts/validate.prompt.md +290 -284
- package/.github/prompts/verify.prompt.md +271 -223
- package/.github/workflows/beads-to-github.yml +56 -0
- package/.github/workflows/github-to-beads.yml +97 -0
- package/.kilocode/workflows/dev.md +346 -341
- package/.kilocode/workflows/plan.md +567 -522
- package/.kilocode/workflows/premerge.md +187 -177
- package/.kilocode/workflows/research.md +43 -43
- package/.kilocode/workflows/review.md +449 -443
- package/.kilocode/workflows/rollback.md +722 -722
- package/.kilocode/workflows/ship.md +213 -165
- package/.kilocode/workflows/sonarcloud.md +150 -150
- package/.kilocode/workflows/status.md +91 -49
- package/.kilocode/workflows/validate.md +289 -283
- package/.kilocode/workflows/verify.md +270 -222
- package/.mcp.json.example +12 -12
- package/.opencode/commands/dev.md +345 -340
- package/.opencode/commands/plan.md +566 -521
- package/.opencode/commands/premerge.md +186 -176
- package/.opencode/commands/research.md +42 -42
- package/.opencode/commands/review.md +448 -442
- package/.opencode/commands/rollback.md +721 -721
- package/.opencode/commands/ship.md +212 -164
- package/.opencode/commands/sonarcloud.md +149 -149
- package/.opencode/commands/status.md +90 -48
- package/.opencode/commands/validate.md +288 -282
- package/.opencode/commands/verify.md +269 -221
- package/.roo/commands/dev.md +346 -341
- package/.roo/commands/plan.md +567 -522
- package/.roo/commands/premerge.md +187 -177
- package/.roo/commands/research.md +43 -43
- package/.roo/commands/review.md +449 -443
- package/.roo/commands/rollback.md +722 -722
- package/.roo/commands/ship.md +213 -165
- package/.roo/commands/sonarcloud.md +150 -150
- package/.roo/commands/status.md +91 -49
- package/.roo/commands/validate.md +289 -283
- package/.roo/commands/verify.md +270 -222
- package/AGENTS.md +272 -175
- package/CLAUDE.md +110 -100
- package/README.md +429 -416
- package/bin/forge-cmd.js +317 -313
- package/bin/forge-preflight.js +322 -309
- package/bin/forge.js +4765 -4303
- package/docs/AGENT_INSTALL_PROMPT.md +342 -342
- package/docs/BEADS_GITHUB_SYNC.md +251 -251
- package/docs/ENHANCED_ONBOARDING.md +612 -602
- package/docs/EXAMPLES.md +482 -482
- package/docs/GREPTILE_SETUP.md +400 -400
- package/docs/MANUAL_REVIEW_GUIDE.md +106 -106
- package/docs/ROADMAP.md +359 -359
- package/docs/SETUP.md +663 -631
- package/docs/TOOLCHAIN.md +653 -630
- package/docs/VALIDATION.md +363 -363
- package/install.sh +40 -1056
- package/lefthook.yml +50 -39
- package/lib/agents/README.md +198 -198
- package/lib/agents/claude.plugin.json +28 -28
- package/lib/agents/cline.plugin.json +22 -22
- package/lib/agents/codex.plugin.json +19 -19
- package/lib/agents/copilot.plugin.json +24 -24
- package/lib/agents/cursor.plugin.json +25 -25
- package/lib/agents/kilocode.plugin.json +22 -22
- package/lib/agents/opencode.plugin.json +20 -20
- package/lib/agents/roo.plugin.json +23 -23
- package/lib/agents-config.js +2112 -2112
- package/lib/beads-health-check.js +143 -0
- package/lib/beads-setup.js +341 -0
- package/lib/beads-sync-scaffold.js +260 -0
- package/lib/commands/_registry.js +134 -0
- package/lib/commands/clean.js +181 -0
- package/lib/commands/dev.js +571 -513
- package/lib/commands/plan.js +692 -692
- package/lib/commands/push.js +196 -0
- package/lib/commands/recommend.js +119 -119
- package/lib/commands/ship.js +377 -377
- package/lib/commands/status.js +378 -378
- package/lib/commands/sync.js +55 -0
- package/lib/commands/team.js +37 -0
- package/lib/commands/test.js +207 -0
- package/lib/commands/validate.js +602 -602
- package/lib/commands/worktree.js +310 -0
- package/lib/context-merge.js +359 -359
- package/lib/dep-guard/analyzer.js +294 -294
- package/lib/dep-guard/behavior-detector.js +98 -98
- package/lib/dep-guard/contract-detector.js +162 -162
- package/lib/dep-guard/import-detector.js +498 -498
- package/lib/dep-guard/path-utils.js +13 -13
- package/lib/dep-guard/rubric.js +120 -120
- package/lib/dep-guard/task-parser.js +318 -318
- package/lib/detect-agent.js +191 -191
- package/lib/detect-worktree.js +47 -47
- package/lib/docs-command.js +51 -0
- package/lib/docs-copy.js +50 -0
- package/lib/file-hash.js +26 -26
- package/lib/freshness-token.js +148 -0
- package/lib/greptile-match.js +80 -0
- package/lib/husky-migration.js +450 -0
- package/lib/lefthook-check.js +65 -0
- package/lib/pat-setup.js +207 -0
- package/lib/plugin-catalog.js +350 -350
- package/lib/plugin-manager.js +166 -166
- package/lib/plugin-recommender.js +141 -141
- package/lib/project-discovery.js +491 -491
- package/lib/reset.js +309 -0
- package/lib/setup-action-log.js +139 -139
- package/lib/setup-summary-renderer.js +106 -106
- package/lib/setup-utils.js +96 -0
- package/lib/setup.js +192 -192
- package/lib/smart-merge.js +64 -0
- package/lib/symlink-utils.js +81 -0
- package/lib/task-ownership.js +117 -0
- package/lib/workflow-profiles.js +197 -197
- package/package.json +131 -128
- package/scripts/beads-context.sh +426 -0
- package/scripts/beads-context.test.js +567 -0
- package/scripts/behavioral-judge.sh +378 -0
- package/scripts/benchmark.js +85 -0
- package/scripts/branch-protection.js +183 -0
- package/scripts/check-agents.js +172 -0
- package/scripts/check-forge-token.js +98 -0
- package/scripts/commitlint.js +42 -0
- package/scripts/conflict-detect.sh +323 -0
- package/scripts/dep-guard-analyze.js +71 -0
- package/scripts/dep-guard.sh +789 -0
- package/scripts/eval_win.py +249 -0
- package/scripts/file-index.sh +493 -0
- package/scripts/forge-team/index.sh +86 -0
- package/scripts/forge-team/lib/agent-prompt.sh +52 -0
- package/scripts/forge-team/lib/claim.sh +256 -0
- package/scripts/forge-team/lib/dashboard.sh +341 -0
- package/scripts/forge-team/lib/epic.sh +332 -0
- package/scripts/forge-team/lib/hooks.sh +253 -0
- package/scripts/forge-team/lib/identity.sh +235 -0
- package/scripts/forge-team/lib/sync-github.sh +317 -0
- package/scripts/forge-team/lib/verify.sh +284 -0
- package/scripts/forge-team/lib/workload.sh +296 -0
- package/scripts/forge-team/tests/agent-prompt.test.sh +72 -0
- package/scripts/forge-team/tests/claim.test.sh +179 -0
- package/scripts/forge-team/tests/dashboard.test.sh +170 -0
- package/scripts/forge-team/tests/dispatcher.test.sh +79 -0
- package/scripts/forge-team/tests/epic.test.sh +176 -0
- package/scripts/forge-team/tests/hooks.test.sh +239 -0
- package/scripts/forge-team/tests/identity.test.sh +176 -0
- package/scripts/forge-team/tests/integration.test.sh +371 -0
- package/scripts/forge-team/tests/sync-github.test.sh +209 -0
- package/scripts/forge-team/tests/verify.test.sh +314 -0
- package/scripts/forge-team/tests/workflow-integration.test.sh +43 -0
- package/scripts/forge-team/tests/workload.test.sh +209 -0
- package/scripts/github-beads-sync/comment.mjs +64 -0
- package/scripts/github-beads-sync/config.mjs +148 -0
- package/scripts/github-beads-sync/github-api.mjs +131 -0
- package/scripts/github-beads-sync/index.mjs +332 -0
- package/scripts/github-beads-sync/label-mapper.mjs +54 -0
- package/scripts/github-beads-sync/mapping.mjs +78 -0
- package/scripts/github-beads-sync/reverse-sync-cli.mjs +31 -0
- package/scripts/github-beads-sync/reverse-sync.mjs +138 -0
- package/scripts/github-beads-sync/run-bd.mjs +159 -0
- package/scripts/github-beads-sync/sanitize.mjs +121 -0
- package/scripts/github-beads-sync.config.json +26 -0
- package/scripts/improve-command.js +375 -0
- package/scripts/lib/eval-runner.js +268 -0
- package/scripts/lib/eval-schema.js +135 -0
- package/scripts/lib/eval-storage.js +78 -0
- package/scripts/lib/grading.js +203 -0
- package/scripts/lib/jsonl-lock.sh +48 -0
- package/scripts/lib/sanitize.sh +116 -0
- package/scripts/lib/transcript-parser.js +63 -0
- package/scripts/lint.js +47 -0
- package/scripts/migrate-to-bun-test.js +412 -0
- package/scripts/pr-coordinator.sh +706 -0
- package/scripts/run-command-eval.js +236 -0
- package/scripts/smart-status.sh +809 -0
- package/scripts/sync-commands.js +571 -0
- package/scripts/sync-utils.sh +455 -0
- package/scripts/test-dashboard.js +123 -0
- package/scripts/test.js +46 -0
- package/scripts/validate.sh +94 -0
- package/skills/parallel-deep-research/SKILL.md +108 -108
- package/skills/parallel-deep-research/evals/README.md +27 -27
- package/skills/parallel-deep-research/evals/evals.json +62 -62
- package/skills/sonarcloud-analysis/SKILL.md +171 -171
- package/skills/sonarcloud-analysis/evals/README.md +27 -27
- package/skills/sonarcloud-analysis/evals/evals.json +50 -50
- package/skills/sonarcloud-analysis/references/api-reference.md +466 -466
- package/.cursor/hooks/state/continual-learning-index.json +0 -19
- package/.cursor/hooks/state/continual-learning.json +0 -8
package/lib/reset.js
ADDED
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
const fs = require('node:fs');
|
|
2
|
+
const path = require('node:path');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Known forge-created file patterns.
|
|
6
|
+
* Used to distinguish forge files from user-created files.
|
|
7
|
+
*/
|
|
8
|
+
const FORGE_COMMANDS = [
|
|
9
|
+
'.claude/commands/plan.md',
|
|
10
|
+
'.claude/commands/dev.md',
|
|
11
|
+
'.claude/commands/validate.md',
|
|
12
|
+
'.claude/commands/ship.md',
|
|
13
|
+
'.claude/commands/review.md',
|
|
14
|
+
'.claude/commands/premerge.md',
|
|
15
|
+
'.claude/commands/verify.md',
|
|
16
|
+
'.claude/commands/status.md',
|
|
17
|
+
'.claude/commands/preflight.md',
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
const FORGE_RULES = [
|
|
21
|
+
'.claude/rules/workflow.md',
|
|
22
|
+
'.claude/rules/greptile-review-process.md',
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
const FORGE_SCRIPTS = [
|
|
26
|
+
'.claude/scripts/greptile-resolve.sh',
|
|
27
|
+
'.claude/scripts/validate.sh',
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
const FORGE_WORKFLOWS = [
|
|
31
|
+
'.github/workflows/beads-to-github.yml',
|
|
32
|
+
'.github/workflows/github-to-beads.yml',
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
const FORGE_DOCS = [
|
|
36
|
+
'docs/forge/TOOLCHAIN.md',
|
|
37
|
+
'docs/forge/VALIDATION.md',
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
const FORGE_AGENT_DIRS = [
|
|
41
|
+
'.cursor',
|
|
42
|
+
'.cline',
|
|
43
|
+
'.roo',
|
|
44
|
+
'.codex',
|
|
45
|
+
'.kilocode',
|
|
46
|
+
'.opencode',
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
const FORGE_SYNC_SCRIPTS_DIR = 'scripts/github-beads-sync';
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Get categorized lists of forge-managed files in a project.
|
|
53
|
+
* Only returns files that actually exist on disk and are known forge templates.
|
|
54
|
+
* User-created files are excluded.
|
|
55
|
+
*
|
|
56
|
+
* @param {string} projectRoot - Project root directory
|
|
57
|
+
* @returns {{
|
|
58
|
+
* config: string[],
|
|
59
|
+
* commands: string[],
|
|
60
|
+
* rules: string[],
|
|
61
|
+
* scripts: string[],
|
|
62
|
+
* docs: string[],
|
|
63
|
+
* agentDirs: string[],
|
|
64
|
+
* workflows: string[],
|
|
65
|
+
* syncScripts: string[]
|
|
66
|
+
* }}
|
|
67
|
+
*/
|
|
68
|
+
function getForgeFiles(projectRoot) {
|
|
69
|
+
const result = {
|
|
70
|
+
config: [],
|
|
71
|
+
commands: [],
|
|
72
|
+
rules: [],
|
|
73
|
+
scripts: [],
|
|
74
|
+
docs: [],
|
|
75
|
+
agentDirs: [],
|
|
76
|
+
workflows: [],
|
|
77
|
+
syncScripts: [],
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// .forge/ directory
|
|
81
|
+
if (fs.existsSync(path.join(projectRoot, '.forge'))) {
|
|
82
|
+
result.config.push('.forge');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Known forge commands (static list)
|
|
86
|
+
for (const f of FORGE_COMMANDS) {
|
|
87
|
+
if (fs.existsSync(path.join(projectRoot, f))) {
|
|
88
|
+
result.commands.push(f);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Dynamic discovery: scan the package's .claude/commands/ directory to find
|
|
93
|
+
// any commands that setup may have copied but aren't in the static list.
|
|
94
|
+
// Only files that exist in both the package source AND the project are included.
|
|
95
|
+
const packageCommandsDir = path.join(__dirname, '..', '.claude', 'commands');
|
|
96
|
+
try {
|
|
97
|
+
const packageEntries = fs.readdirSync(packageCommandsDir);
|
|
98
|
+
for (const entry of packageEntries) {
|
|
99
|
+
if (entry.endsWith('.md')) {
|
|
100
|
+
const relPath = `.claude/commands/${entry}`;
|
|
101
|
+
if (!result.commands.includes(relPath) &&
|
|
102
|
+
fs.existsSync(path.join(projectRoot, relPath))) {
|
|
103
|
+
result.commands.push(relPath);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
} catch (_error) {
|
|
108
|
+
// Package commands directory read failed, skip
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Known forge rules
|
|
112
|
+
for (const f of FORGE_RULES) {
|
|
113
|
+
if (fs.existsSync(path.join(projectRoot, f))) {
|
|
114
|
+
result.rules.push(f);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Known forge scripts
|
|
119
|
+
for (const f of FORGE_SCRIPTS) {
|
|
120
|
+
if (fs.existsSync(path.join(projectRoot, f))) {
|
|
121
|
+
result.scripts.push(f);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Known forge docs
|
|
126
|
+
for (const f of FORGE_DOCS) {
|
|
127
|
+
if (fs.existsSync(path.join(projectRoot, f))) {
|
|
128
|
+
result.docs.push(f);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Agent directories (only if they exist)
|
|
133
|
+
for (const dir of FORGE_AGENT_DIRS) {
|
|
134
|
+
if (fs.existsSync(path.join(projectRoot, dir))) {
|
|
135
|
+
result.agentDirs.push(dir);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Beads workflow files
|
|
140
|
+
for (const f of FORGE_WORKFLOWS) {
|
|
141
|
+
if (fs.existsSync(path.join(projectRoot, f))) {
|
|
142
|
+
result.workflows.push(f);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Sync scripts directory
|
|
147
|
+
const syncDir = path.join(projectRoot, FORGE_SYNC_SCRIPTS_DIR);
|
|
148
|
+
if (fs.existsSync(syncDir)) {
|
|
149
|
+
try {
|
|
150
|
+
const entries = fs.readdirSync(syncDir);
|
|
151
|
+
for (const entry of entries) {
|
|
152
|
+
const relPath = [FORGE_SYNC_SCRIPTS_DIR, entry].join('/');
|
|
153
|
+
result.syncScripts.push(relPath);
|
|
154
|
+
}
|
|
155
|
+
} catch (_error) {
|
|
156
|
+
// Directory read failed, skip
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return result;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Remove .forge/ directory only (soft reset).
|
|
165
|
+
* Requires force=true to proceed.
|
|
166
|
+
*
|
|
167
|
+
* @param {string} projectRoot - Project root directory
|
|
168
|
+
* @param {{ force?: boolean }} options
|
|
169
|
+
* @returns {{ removed: string[], preserved: string[] }}
|
|
170
|
+
*/
|
|
171
|
+
function resetSoft(projectRoot, { force = false } = {}) {
|
|
172
|
+
if (!force) {
|
|
173
|
+
throw new Error('Soft reset requires --force flag. This will remove .forge/ directory.');
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const removed = [];
|
|
177
|
+
const preserved = [];
|
|
178
|
+
|
|
179
|
+
const forgePath = path.join(projectRoot, '.forge');
|
|
180
|
+
if (fs.existsSync(forgePath)) {
|
|
181
|
+
fs.rmSync(forgePath, { recursive: true, force: true });
|
|
182
|
+
removed.push('.forge');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Record what was preserved
|
|
186
|
+
const inventory = getForgeFiles(projectRoot);
|
|
187
|
+
for (const f of [...inventory.commands, ...inventory.rules, ...inventory.scripts]) {
|
|
188
|
+
preserved.push(f);
|
|
189
|
+
}
|
|
190
|
+
for (const dir of inventory.agentDirs) {
|
|
191
|
+
preserved.push(dir);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return { removed, preserved };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Remove ALL forge-managed files (hard reset).
|
|
199
|
+
* Preserves user-created files not in forge template list.
|
|
200
|
+
* Requires force=true to proceed.
|
|
201
|
+
*
|
|
202
|
+
* @param {string} projectRoot - Project root directory
|
|
203
|
+
* @param {{ force?: boolean }} options
|
|
204
|
+
* @returns {{ removed: string[], preserved: string[] }}
|
|
205
|
+
*/
|
|
206
|
+
function resetHard(projectRoot, { force = false } = {}) {
|
|
207
|
+
if (!force) {
|
|
208
|
+
throw new Error('Hard reset requires --force flag. This will remove ALL forge files.');
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const removed = [];
|
|
212
|
+
const preserved = [];
|
|
213
|
+
const inventory = getForgeFiles(projectRoot);
|
|
214
|
+
|
|
215
|
+
// Remove .forge/ directory
|
|
216
|
+
for (const dir of inventory.config) {
|
|
217
|
+
const fullPath = path.join(projectRoot, dir);
|
|
218
|
+
if (fs.existsSync(fullPath)) {
|
|
219
|
+
fs.rmSync(fullPath, { recursive: true, force: true });
|
|
220
|
+
removed.push(dir);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Remove individual forge files
|
|
225
|
+
const allFiles = [
|
|
226
|
+
...inventory.commands,
|
|
227
|
+
...inventory.rules,
|
|
228
|
+
...inventory.scripts,
|
|
229
|
+
...inventory.docs,
|
|
230
|
+
...inventory.workflows,
|
|
231
|
+
...inventory.syncScripts,
|
|
232
|
+
];
|
|
233
|
+
|
|
234
|
+
for (const f of allFiles) {
|
|
235
|
+
const fullPath = path.join(projectRoot, f);
|
|
236
|
+
if (fs.existsSync(fullPath)) {
|
|
237
|
+
fs.rmSync(fullPath, { recursive: true, force: true });
|
|
238
|
+
removed.push(f);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Remove sync scripts directory if it still exists (empty after file removal)
|
|
243
|
+
const syncDir = path.join(projectRoot, FORGE_SYNC_SCRIPTS_DIR);
|
|
244
|
+
if (fs.existsSync(syncDir)) {
|
|
245
|
+
fs.rmSync(syncDir, { recursive: true, force: true });
|
|
246
|
+
removed.push(FORGE_SYNC_SCRIPTS_DIR);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Remove docs/forge/ directory if empty after file removal
|
|
250
|
+
const docsForgeDir = path.join(projectRoot, 'docs', 'forge');
|
|
251
|
+
if (fs.existsSync(docsForgeDir)) {
|
|
252
|
+
try {
|
|
253
|
+
const remaining = fs.readdirSync(docsForgeDir);
|
|
254
|
+
if (remaining.length === 0) {
|
|
255
|
+
fs.rmSync(docsForgeDir, { recursive: true, force: true });
|
|
256
|
+
removed.push('docs/forge');
|
|
257
|
+
}
|
|
258
|
+
} catch (_error) {
|
|
259
|
+
// Directory read failed, skip
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Remove agent directories
|
|
264
|
+
for (const dir of inventory.agentDirs) {
|
|
265
|
+
const fullPath = path.join(projectRoot, dir);
|
|
266
|
+
if (fs.existsSync(fullPath)) {
|
|
267
|
+
fs.rmSync(fullPath, { recursive: true, force: true });
|
|
268
|
+
removed.push(dir);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return { removed, preserved };
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Remove all forge files and re-run setup (reinstall).
|
|
277
|
+
* Accepts a setupFn parameter for testability.
|
|
278
|
+
*
|
|
279
|
+
* @param {string} projectRoot - Project root directory
|
|
280
|
+
* @param {{ force?: boolean, setupFn?: Function }} options
|
|
281
|
+
* @returns {Promise<{ resetResult: object, setupResult: any }>}
|
|
282
|
+
*/
|
|
283
|
+
async function reinstall(projectRoot, { force = false, setupFn } = {}) {
|
|
284
|
+
if (!force) {
|
|
285
|
+
throw new Error('Reinstall requires --force flag.');
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const resetResult = resetHard(projectRoot, { force: true });
|
|
289
|
+
|
|
290
|
+
let setupResult = null;
|
|
291
|
+
if (setupFn) {
|
|
292
|
+
setupResult = await setupFn(projectRoot);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return { resetResult, setupResult };
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
module.exports = {
|
|
299
|
+
getForgeFiles,
|
|
300
|
+
resetSoft,
|
|
301
|
+
resetHard,
|
|
302
|
+
reinstall,
|
|
303
|
+
FORGE_COMMANDS,
|
|
304
|
+
FORGE_RULES,
|
|
305
|
+
FORGE_SCRIPTS,
|
|
306
|
+
FORGE_DOCS,
|
|
307
|
+
FORGE_WORKFLOWS,
|
|
308
|
+
FORGE_AGENT_DIRS,
|
|
309
|
+
};
|
package/lib/setup-action-log.js
CHANGED
|
@@ -1,139 +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 };
|
|
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 };
|