forge-workflow 0.0.4 → 0.0.5
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 +340 -340
- package/.claude/commands/plan.md +521 -521
- package/.claude/commands/premerge.md +176 -176
- package/.claude/commands/research.md +42 -42
- package/.claude/commands/review.md +442 -442
- package/.claude/commands/rollback.md +721 -721
- package/.claude/commands/ship.md +164 -164
- package/.claude/commands/sonarcloud.md +152 -152
- package/.claude/commands/status.md +48 -48
- package/.claude/commands/validate.md +282 -282
- package/.claude/commands/verify.md +221 -221
- package/.claude/rules/greptile-review-process.md +285 -285
- package/.claude/rules/workflow.md +105 -105
- package/.claude/scripts/greptile-resolve.sh +526 -526
- package/.claude/scripts/load-env.sh +32 -32
- package/.cline/workflows/dev.md +337 -337
- package/.cline/workflows/plan.md +518 -518
- package/.cline/workflows/premerge.md +173 -173
- package/.cline/workflows/research.md +39 -39
- package/.cline/workflows/review.md +439 -439
- package/.cline/workflows/rollback.md +718 -718
- package/.cline/workflows/ship.md +161 -161
- package/.cline/workflows/sonarcloud.md +146 -146
- package/.cline/workflows/status.md +45 -45
- package/.cline/workflows/validate.md +279 -279
- package/.cline/workflows/verify.md +218 -218
- package/.codex/config.toml +11 -11
- package/.codex/skills/dev/SKILL.md +340 -340
- package/.codex/skills/plan/SKILL.md +521 -521
- package/.codex/skills/premerge/SKILL.md +176 -176
- package/.codex/skills/research/SKILL.md +42 -42
- package/.codex/skills/review/SKILL.md +442 -442
- package/.codex/skills/rollback/SKILL.md +721 -721
- package/.codex/skills/ship/SKILL.md +164 -164
- package/.codex/skills/sonarcloud/SKILL.md +149 -149
- package/.codex/skills/status/SKILL.md +48 -48
- package/.codex/skills/validate/SKILL.md +282 -282
- package/.codex/skills/verify/SKILL.md +221 -221
- package/.cursor/commands/dev.md +337 -337
- package/.cursor/commands/plan.md +518 -518
- package/.cursor/commands/premerge.md +173 -173
- package/.cursor/commands/research.md +39 -39
- package/.cursor/commands/review.md +439 -439
- package/.cursor/commands/rollback.md +718 -718
- package/.cursor/commands/ship.md +161 -161
- package/.cursor/commands/sonarcloud.md +146 -146
- package/.cursor/commands/status.md +45 -45
- package/.cursor/commands/validate.md +279 -279
- package/.cursor/commands/verify.md +218 -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 +342 -342
- package/.github/prompts/plan.prompt.md +523 -523
- package/.github/prompts/premerge.prompt.md +178 -178
- package/.github/prompts/research.prompt.md +44 -44
- package/.github/prompts/review.prompt.md +444 -444
- package/.github/prompts/rollback.prompt.md +723 -723
- package/.github/prompts/ship.prompt.md +166 -166
- package/.github/prompts/sonarcloud.prompt.md +151 -151
- package/.github/prompts/status.prompt.md +50 -50
- package/.github/prompts/validate.prompt.md +284 -284
- package/.github/prompts/verify.prompt.md +223 -223
- package/.github/workflows/beads-to-github.yml +56 -0
- package/.github/workflows/github-to-beads.yml +97 -0
- package/.kilocode/workflows/dev.md +341 -341
- package/.kilocode/workflows/plan.md +522 -522
- package/.kilocode/workflows/premerge.md +177 -177
- package/.kilocode/workflows/research.md +43 -43
- package/.kilocode/workflows/review.md +443 -443
- package/.kilocode/workflows/rollback.md +722 -722
- package/.kilocode/workflows/ship.md +165 -165
- package/.kilocode/workflows/sonarcloud.md +150 -150
- package/.kilocode/workflows/status.md +49 -49
- package/.kilocode/workflows/validate.md +283 -283
- package/.kilocode/workflows/verify.md +222 -222
- package/.mcp.json.example +12 -12
- package/.opencode/commands/dev.md +340 -340
- package/.opencode/commands/plan.md +521 -521
- package/.opencode/commands/premerge.md +176 -176
- package/.opencode/commands/research.md +42 -42
- package/.opencode/commands/review.md +442 -442
- package/.opencode/commands/rollback.md +721 -721
- package/.opencode/commands/ship.md +164 -164
- package/.opencode/commands/sonarcloud.md +149 -149
- package/.opencode/commands/status.md +48 -48
- package/.opencode/commands/validate.md +282 -282
- package/.opencode/commands/verify.md +221 -221
- package/.roo/commands/dev.md +341 -341
- package/.roo/commands/plan.md +522 -522
- package/.roo/commands/premerge.md +177 -177
- package/.roo/commands/research.md +43 -43
- package/.roo/commands/review.md +443 -443
- package/.roo/commands/rollback.md +722 -722
- package/.roo/commands/ship.md +165 -165
- package/.roo/commands/sonarcloud.md +150 -150
- package/.roo/commands/status.md +49 -49
- package/.roo/commands/validate.md +283 -283
- package/.roo/commands/verify.md +222 -222
- package/AGENTS.md +175 -175
- package/CLAUDE.md +100 -100
- package/README.md +429 -416
- package/bin/forge-cmd.js +313 -313
- package/bin/forge-preflight.js +309 -309
- package/bin/forge.js +4596 -4303
- package/docs/AGENT_INSTALL_PROMPT.md +342 -342
- package/docs/BEADS_GITHUB_SYNC.md +251 -251
- package/docs/ENHANCED_ONBOARDING.md +602 -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 +630 -630
- package/docs/VALIDATION.md +363 -363
- package/install.sh +40 -1056
- package/lefthook.yml +39 -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/dev.js +513 -513
- package/lib/commands/plan.js +692 -692
- package/lib/commands/recommend.js +119 -119
- package/lib/commands/ship.js +377 -377
- package/lib/commands/status.js +378 -378
- package/lib/commands/validate.js +602 -602
- 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/file-hash.js +26 -26
- 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/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/workflow-profiles.js +197 -197
- package/package.json +131 -128
- package/scripts/beads-context.sh +291 -0
- package/scripts/beads-context.test.js +563 -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/commitlint.js +42 -0
- package/scripts/conflict-detect.sh +323 -0
- package/scripts/dep-guard-analyze.js +71 -0
- package/scripts/dep-guard.sh +811 -0
- package/scripts/eval_win.py +249 -0
- package/scripts/file-index.sh +399 -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 +229 -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/transcript-parser.js +63 -0
- package/scripts/lint.js +47 -0
- package/scripts/migrate-to-bun-test.js +412 -0
- package/scripts/run-command-eval.js +236 -0
- package/scripts/smart-status.sh +782 -0
- package/scripts/sync-commands.js +571 -0
- package/scripts/sync-utils.sh +460 -0
- package/scripts/test-dashboard.js +123 -0
- package/scripts/test.js +44 -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/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 };
|
|
@@ -1,106 +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 };
|
|
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 };
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Foundational utilities for `forge setup` command.
|
|
3
|
+
*
|
|
4
|
+
* - `ActionCollector` — collects planned/completed file actions for dry-run
|
|
5
|
+
* summaries and normal-mode logging.
|
|
6
|
+
* - `isNonInteractive()` — detects CI environments and piped stdin so
|
|
7
|
+
* interactive prompts can fall back to defaults.
|
|
8
|
+
*
|
|
9
|
+
* @module setup-utils
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Collects file-level actions during setup.
|
|
14
|
+
*
|
|
15
|
+
* Used by `--dry-run` to collect planned actions and by normal mode
|
|
16
|
+
* to log what was actually done.
|
|
17
|
+
*/
|
|
18
|
+
class ActionCollector {
|
|
19
|
+
constructor() {
|
|
20
|
+
/** @type {Array<{type: string, path: string, description: string}>} */
|
|
21
|
+
this._actions = [];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Record a setup action.
|
|
26
|
+
*
|
|
27
|
+
* @param {'create'|'modify'|'skip'} type - Action type
|
|
28
|
+
* @param {string} path - File path that was (or would be) acted on
|
|
29
|
+
* @param {string} description - Human-readable explanation
|
|
30
|
+
*/
|
|
31
|
+
add(type, path, description) {
|
|
32
|
+
this._actions.push({ type, path, description });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Return a copy of all recorded actions.
|
|
37
|
+
*
|
|
38
|
+
* @returns {Array<{type: string, path: string, description: string}>}
|
|
39
|
+
*/
|
|
40
|
+
list() {
|
|
41
|
+
return [...this._actions];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Print a formatted summary of all recorded actions to stdout.
|
|
46
|
+
*/
|
|
47
|
+
print() {
|
|
48
|
+
if (this._actions.length === 0) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const icons = {
|
|
53
|
+
create: '+',
|
|
54
|
+
modify: '~',
|
|
55
|
+
skip: '-'
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
for (const { type, path, description } of this._actions) {
|
|
59
|
+
const icon = icons[type] || '?';
|
|
60
|
+
process.stdout.write(` [${icon}] ${type.padEnd(6)} ${path} — ${description}\n`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Detect whether the current environment is non-interactive.
|
|
67
|
+
*
|
|
68
|
+
* Returns `true` if any of:
|
|
69
|
+
* - `process.env.CI` is truthy (non-empty)
|
|
70
|
+
* - `process.env.GITHUB_ACTIONS` exists
|
|
71
|
+
* - `process.env.GITLAB_CI` exists
|
|
72
|
+
* - `process.stdin.isTTY` is falsy
|
|
73
|
+
* - `--non-interactive` flag was passed in `process.argv`
|
|
74
|
+
*
|
|
75
|
+
* @returns {boolean}
|
|
76
|
+
*/
|
|
77
|
+
function isNonInteractive() {
|
|
78
|
+
if (process.env.CI && process.env.CI !== '') {
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
if (process.env.GITHUB_ACTIONS !== undefined) {
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
if (process.env.GITLAB_CI !== undefined) {
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
if (!process.stdin.isTTY) {
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
if (process.argv.includes('--non-interactive')) {
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
module.exports = { ActionCollector, isNonInteractive };
|