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
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { execFileSync, spawnSync } = require('node:child_process');
|
|
4
|
+
const fs = require('node:fs');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
const forgeToken = require('../../scripts/check-forge-token');
|
|
7
|
+
|
|
8
|
+
const isWindows = process.platform === 'win32';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Detect package manager from lock files.
|
|
12
|
+
* Same priority order as scripts/test.js.
|
|
13
|
+
*
|
|
14
|
+
* @param {string} projectRoot - Absolute path to project root
|
|
15
|
+
* @param {function} [existsSync] - Injected fs.existsSync for testing
|
|
16
|
+
* @returns {string} Package manager command name
|
|
17
|
+
*/
|
|
18
|
+
function detectPackageManager(projectRoot, existsSync = fs.existsSync) {
|
|
19
|
+
if (existsSync(path.join(projectRoot, 'bun.lockb')) || existsSync(path.join(projectRoot, 'bun.lock'))) return 'bun';
|
|
20
|
+
if (existsSync(path.join(projectRoot, 'pnpm-lock.yaml'))) return 'pnpm';
|
|
21
|
+
if (existsSync(path.join(projectRoot, 'yarn.lock'))) return 'yarn';
|
|
22
|
+
return 'npm';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Get the current git branch name.
|
|
27
|
+
*
|
|
28
|
+
* @param {function} execFn - execFileSync or mock
|
|
29
|
+
* @returns {string} Current branch name
|
|
30
|
+
*/
|
|
31
|
+
function getCurrentBranch(execFn) {
|
|
32
|
+
try {
|
|
33
|
+
return execFn('git', ['branch', '--show-current'], { encoding: 'utf8' }).trim();
|
|
34
|
+
} catch (_err) { /* intentional: fallback to 'unknown' */ // NOSONAR S2486
|
|
35
|
+
return 'unknown';
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Check if this is the first push to the current branch.
|
|
41
|
+
* If `git rev-list --count origin/<branch>` throws, the remote tracking
|
|
42
|
+
* branch doesn't exist yet — meaning this is a first push.
|
|
43
|
+
*
|
|
44
|
+
* @param {string} branch - Branch name
|
|
45
|
+
* @param {function} execFn - execFileSync or mock
|
|
46
|
+
* @returns {boolean} True if first push (no remote tracking branch)
|
|
47
|
+
*/
|
|
48
|
+
function isFirstPush(branch, execFn) {
|
|
49
|
+
try {
|
|
50
|
+
execFn('git', ['rev-list', '--count', 'origin/' + branch], { encoding: 'utf8' });
|
|
51
|
+
return false;
|
|
52
|
+
} catch (_err) { /* intentional: no remote tracking branch means first push */ // NOSONAR S2486
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Run branch protection check as a subprocess.
|
|
59
|
+
* @param {function} execFn - execFileSync or mock
|
|
60
|
+
* @param {string} projectRoot - Absolute path to project root
|
|
61
|
+
* @param {function} log - Logger function
|
|
62
|
+
* @returns {boolean} True if branch protection passed
|
|
63
|
+
*/
|
|
64
|
+
function runBranchProtection(execFn, projectRoot, log) {
|
|
65
|
+
try {
|
|
66
|
+
execFn('node', [path.join(projectRoot, 'scripts', 'branch-protection.js')]);
|
|
67
|
+
return true;
|
|
68
|
+
} catch (_err) { /* intentional: fallback to failure */ // NOSONAR S2486
|
|
69
|
+
log('Branch protection check failed — push aborted.');
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Run lint via the detected package manager.
|
|
76
|
+
* @param {function} spawnFn - spawnSync or mock
|
|
77
|
+
* @param {string} pkgManager - Package manager command
|
|
78
|
+
* @param {string} projectRoot - Absolute path to project root
|
|
79
|
+
* @returns {boolean} True if lint passed
|
|
80
|
+
*/
|
|
81
|
+
function runLint(spawnFn, pkgManager, projectRoot) {
|
|
82
|
+
const lintResult = spawnFn(pkgManager, ['run', 'lint'], {
|
|
83
|
+
stdio: 'inherit',
|
|
84
|
+
shell: isWindows,
|
|
85
|
+
cwd: projectRoot,
|
|
86
|
+
});
|
|
87
|
+
return lintResult.status === 0;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Run tests unless in quick mode, logging warnings for first push.
|
|
92
|
+
* @param {function} spawnFn - spawnSync or mock
|
|
93
|
+
* @param {function} execFn - execFileSync or mock
|
|
94
|
+
* @param {string} pkgManager - Package manager command
|
|
95
|
+
* @param {string} projectRoot - Absolute path to project root
|
|
96
|
+
* @param {boolean} quickMode - Whether to skip tests
|
|
97
|
+
* @param {function} log - Logger function
|
|
98
|
+
* @returns {boolean|undefined} True if tests passed, undefined if skipped
|
|
99
|
+
*/
|
|
100
|
+
function runTests(spawnFn, execFn, pkgManager, projectRoot, quickMode, log) {
|
|
101
|
+
if (quickMode) {
|
|
102
|
+
log('Tests skipped (--quick) — CI will run full suite on GitHub');
|
|
103
|
+
const branch = getCurrentBranch(execFn);
|
|
104
|
+
if (isFirstPush(branch, execFn)) {
|
|
105
|
+
log('Warning: First push to this branch — consider `forge push` (full suite) before merge');
|
|
106
|
+
}
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const testResult = spawnFn(pkgManager, ['run', 'test'], {
|
|
111
|
+
stdio: 'inherit',
|
|
112
|
+
shell: isWindows,
|
|
113
|
+
cwd: projectRoot,
|
|
114
|
+
timeout: 120000,
|
|
115
|
+
});
|
|
116
|
+
return testResult.status === 0;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
module.exports = {
|
|
120
|
+
name: 'push',
|
|
121
|
+
description: 'Push with quality gates (branch protection + lint + tests)',
|
|
122
|
+
usage: 'forge push [--quick] [-- <git-push-args>]',
|
|
123
|
+
flags: {
|
|
124
|
+
'--quick': 'Skip tests (lint-only) for review-cycle pushes',
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Push handler — runs branch protection, lint, optionally tests,
|
|
129
|
+
* then forwards to git push.
|
|
130
|
+
*
|
|
131
|
+
* @param {string[]} args - Passthrough args for git push (e.g. ['-u', 'origin', 'feat/slug'])
|
|
132
|
+
* @param {Object} flags - Parsed flags (e.g. { '--quick': true })
|
|
133
|
+
* @param {string} projectRoot - Absolute path to project root
|
|
134
|
+
* @param {Object} [deps] - Dependency injection for testing
|
|
135
|
+
* @returns {Promise<{success: boolean, quickMode: boolean, lintPassed: boolean, testsPassed?: boolean, pushed: boolean}>}
|
|
136
|
+
*/
|
|
137
|
+
handler: async (args, flags, projectRoot, deps) => {
|
|
138
|
+
const execFn = deps?.execFileSync || execFileSync;
|
|
139
|
+
const spawnFn = deps?.spawnSync || spawnSync;
|
|
140
|
+
const existsFn = deps?.existsSync || fs.existsSync;
|
|
141
|
+
const log = deps?.log || console.log;
|
|
142
|
+
const tokenWrite = deps?.writeForgeToken || forgeToken.write;
|
|
143
|
+
|
|
144
|
+
const quickMode = !!(flags && (flags['--quick'] || flags.quick));
|
|
145
|
+
const pkgManager = detectPackageManager(projectRoot, existsFn);
|
|
146
|
+
|
|
147
|
+
// Step 1: Branch protection
|
|
148
|
+
const branchOk = runBranchProtection(execFn, projectRoot, log);
|
|
149
|
+
if (!branchOk) {
|
|
150
|
+
return { success: false, quickMode, lintPassed: false, pushed: false };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Step 2: Lint
|
|
154
|
+
const lintPassed = runLint(spawnFn, pkgManager, projectRoot);
|
|
155
|
+
if (!lintPassed) {
|
|
156
|
+
log('Lint failed — push aborted.');
|
|
157
|
+
return { success: false, quickMode, lintPassed: false, pushed: false };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Step 3: Tests (skip in quick mode)
|
|
161
|
+
const testsPassed = runTests(spawnFn, execFn, pkgManager, projectRoot, quickMode, log);
|
|
162
|
+
if (!quickMode && !testsPassed) {
|
|
163
|
+
return { success: false, quickMode, lintPassed: true, testsPassed: false, pushed: false };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Step 4: Write nonce token so lefthook skips pre-push hooks
|
|
167
|
+
try {
|
|
168
|
+
tokenWrite(projectRoot);
|
|
169
|
+
} catch (_err) { /* intentional: non-fatal, lefthook will re-run hooks */ // NOSONAR S2486
|
|
170
|
+
log('Warning: Could not write forge push token — lefthook hooks will run again');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Step 5: git push with passthrough args
|
|
174
|
+
const gitArgs = args.filter(a => a !== '--quick');
|
|
175
|
+
try {
|
|
176
|
+
execFn('git', ['push', ...gitArgs], { stdio: 'inherit' });
|
|
177
|
+
} catch (_pushErr) { // NOSONAR S2486
|
|
178
|
+
log('git push failed.');
|
|
179
|
+
return {
|
|
180
|
+
success: false,
|
|
181
|
+
quickMode,
|
|
182
|
+
lintPassed: true,
|
|
183
|
+
...(quickMode ? {} : { testsPassed: true }),
|
|
184
|
+
pushed: false,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
success: true,
|
|
190
|
+
quickMode,
|
|
191
|
+
lintPassed: true,
|
|
192
|
+
...(quickMode ? {} : { testsPassed: true }),
|
|
193
|
+
pushed: true,
|
|
194
|
+
};
|
|
195
|
+
},
|
|
196
|
+
};
|
|
@@ -1,119 +1,119 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Recommend Command
|
|
3
|
-
*
|
|
4
|
-
* Displays tool recommendations based on detected tech stack and budget mode.
|
|
5
|
-
* Read-only — no installations, no side effects.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
const { recommend } = require('../plugin-recommender');
|
|
9
|
-
const { detectTechStack } = require('../project-discovery');
|
|
10
|
-
const { BUDGET_MODES, STAGES } = require('../plugin-catalog');
|
|
11
|
-
|
|
12
|
-
const TIER_LABELS = {
|
|
13
|
-
free: '[F]',
|
|
14
|
-
'free-public': '[FP]',
|
|
15
|
-
'free-limited': '[FL]',
|
|
16
|
-
paid: '[P]',
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
const STAGE_NAMES = {
|
|
20
|
-
research: 'Research',
|
|
21
|
-
plan: 'Plan',
|
|
22
|
-
dev: 'Dev',
|
|
23
|
-
validate: 'Validate',
|
|
24
|
-
ship: 'Ship',
|
|
25
|
-
review: 'Review',
|
|
26
|
-
premerge: 'Premerge',
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Format the skipped tools section.
|
|
31
|
-
* @param {Object[]} skipped
|
|
32
|
-
* @returns {string[]}
|
|
33
|
-
*/
|
|
34
|
-
function formatSkippedSection(skipped) {
|
|
35
|
-
if (skipped.length === 0) return [];
|
|
36
|
-
const lines = [
|
|
37
|
-
'',
|
|
38
|
-
` Skipped (${skipped.length} tools not matching budget):`,
|
|
39
|
-
...skipped.slice(0, 5).map((tool) => ` ${tool.name}: ${tool.reason}`),
|
|
40
|
-
];
|
|
41
|
-
if (skipped.length > 5) {
|
|
42
|
-
lines.push(` ... and ${skipped.length - 5} more`);
|
|
43
|
-
}
|
|
44
|
-
return lines;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Format recommendations into displayable text.
|
|
49
|
-
* @param {{ recommended: Object[], skipped: Object[] }} recommendations
|
|
50
|
-
* @returns {string}
|
|
51
|
-
*/
|
|
52
|
-
function formatRecommendations(recommendations) {
|
|
53
|
-
const { recommended, skipped } = recommendations;
|
|
54
|
-
|
|
55
|
-
if (recommended.length === 0) {
|
|
56
|
-
return 'No tools recommended for current configuration.';
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Group by stage
|
|
60
|
-
const byStage = {};
|
|
61
|
-
for (const stage of Object.values(STAGES)) {
|
|
62
|
-
byStage[stage] = recommended.filter((t) => t.stage === stage);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const lines = [];
|
|
66
|
-
for (const [stage, tools] of Object.entries(byStage)) {
|
|
67
|
-
if (tools.length === 0) continue;
|
|
68
|
-
lines.push(
|
|
69
|
-
'',
|
|
70
|
-
` ${STAGE_NAMES[stage] || stage}`,
|
|
71
|
-
` ${'─'.repeat(40)}`,
|
|
72
|
-
);
|
|
73
|
-
for (const tool of tools) {
|
|
74
|
-
const tier = TIER_LABELS[tool.tier] || `[${tool.tier}]`;
|
|
75
|
-
if (tool.install.cmdCurl) {
|
|
76
|
-
lines.push(
|
|
77
|
-
` ${tier.padEnd(5)} ${tool.name}`,
|
|
78
|
-
` CLI (recommended): ${tool.install.cmd}`,
|
|
79
|
-
` Curl (no install): ${tool.install.cmdCurl}`,
|
|
80
|
-
);
|
|
81
|
-
} else {
|
|
82
|
-
lines.push(` ${tier.padEnd(5)} ${tool.name.padEnd(25)} ${tool.install.cmd}`);
|
|
83
|
-
}
|
|
84
|
-
if (tool.alternatives && tool.alternatives.length > 0) {
|
|
85
|
-
const altNames = tool.alternatives.map((a) => `${a.tool} (${a.tier})`).join(', ');
|
|
86
|
-
lines.push(` Free alternatives: ${altNames}`);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
lines.push(...formatSkippedSection(skipped), '');
|
|
92
|
-
return lines.join('\n');
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Handle the recommend command.
|
|
97
|
-
* @param {Object} flags - Parsed CLI flags
|
|
98
|
-
* @param {string} [projectPath] - Project path (defaults to cwd)
|
|
99
|
-
* @returns {{ budgetMode: string, recommendations: Object, error?: string }}
|
|
100
|
-
*/
|
|
101
|
-
function handleRecommend(flags, projectPath) {
|
|
102
|
-
const budgetMode = flags.budget || '
|
|
103
|
-
|
|
104
|
-
// Validate budget mode
|
|
105
|
-
if (!BUDGET_MODES[budgetMode]) {
|
|
106
|
-
return {
|
|
107
|
-
budgetMode,
|
|
108
|
-
recommendations: { recommended: [], skipped: [] },
|
|
109
|
-
error: `Invalid budget mode: '${budgetMode}'. Valid: ${Object.keys(BUDGET_MODES).join(', ')}`,
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const techStack = detectTechStack(projectPath || process.cwd());
|
|
114
|
-
const recommendations = recommend(techStack, budgetMode);
|
|
115
|
-
|
|
116
|
-
return { budgetMode, recommendations };
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
module.exports = { formatRecommendations, handleRecommend };
|
|
1
|
+
/**
|
|
2
|
+
* Recommend Command
|
|
3
|
+
*
|
|
4
|
+
* Displays tool recommendations based on detected tech stack and budget mode.
|
|
5
|
+
* Read-only — no installations, no side effects.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { recommend } = require('../plugin-recommender');
|
|
9
|
+
const { detectTechStack } = require('../project-discovery');
|
|
10
|
+
const { BUDGET_MODES, STAGES } = require('../plugin-catalog');
|
|
11
|
+
|
|
12
|
+
const TIER_LABELS = {
|
|
13
|
+
free: '[F]',
|
|
14
|
+
'free-public': '[FP]',
|
|
15
|
+
'free-limited': '[FL]',
|
|
16
|
+
paid: '[P]',
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const STAGE_NAMES = {
|
|
20
|
+
research: 'Research',
|
|
21
|
+
plan: 'Plan',
|
|
22
|
+
dev: 'Dev',
|
|
23
|
+
validate: 'Validate',
|
|
24
|
+
ship: 'Ship',
|
|
25
|
+
review: 'Review',
|
|
26
|
+
premerge: 'Premerge',
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Format the skipped tools section.
|
|
31
|
+
* @param {Object[]} skipped
|
|
32
|
+
* @returns {string[]}
|
|
33
|
+
*/
|
|
34
|
+
function formatSkippedSection(skipped) {
|
|
35
|
+
if (skipped.length === 0) return [];
|
|
36
|
+
const lines = [
|
|
37
|
+
'',
|
|
38
|
+
` Skipped (${skipped.length} tools not matching budget):`,
|
|
39
|
+
...skipped.slice(0, 5).map((tool) => ` ${tool.name}: ${tool.reason}`),
|
|
40
|
+
];
|
|
41
|
+
if (skipped.length > 5) {
|
|
42
|
+
lines.push(` ... and ${skipped.length - 5} more`);
|
|
43
|
+
}
|
|
44
|
+
return lines;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Format recommendations into displayable text.
|
|
49
|
+
* @param {{ recommended: Object[], skipped: Object[] }} recommendations
|
|
50
|
+
* @returns {string}
|
|
51
|
+
*/
|
|
52
|
+
function formatRecommendations(recommendations) {
|
|
53
|
+
const { recommended, skipped } = recommendations;
|
|
54
|
+
|
|
55
|
+
if (recommended.length === 0) {
|
|
56
|
+
return 'No tools recommended for current configuration.';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Group by stage
|
|
60
|
+
const byStage = {};
|
|
61
|
+
for (const stage of Object.values(STAGES)) {
|
|
62
|
+
byStage[stage] = recommended.filter((t) => t.stage === stage);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const lines = [];
|
|
66
|
+
for (const [stage, tools] of Object.entries(byStage)) {
|
|
67
|
+
if (tools.length === 0) continue;
|
|
68
|
+
lines.push(
|
|
69
|
+
'',
|
|
70
|
+
` ${STAGE_NAMES[stage] || stage}`,
|
|
71
|
+
` ${'─'.repeat(40)}`,
|
|
72
|
+
);
|
|
73
|
+
for (const tool of tools) {
|
|
74
|
+
const tier = TIER_LABELS[tool.tier] || `[${tool.tier}]`;
|
|
75
|
+
if (tool.install.cmdCurl) {
|
|
76
|
+
lines.push(
|
|
77
|
+
` ${tier.padEnd(5)} ${tool.name}`,
|
|
78
|
+
` CLI (recommended): ${tool.install.cmd}`,
|
|
79
|
+
` Curl (no install): ${tool.install.cmdCurl}`,
|
|
80
|
+
);
|
|
81
|
+
} else {
|
|
82
|
+
lines.push(` ${tier.padEnd(5)} ${tool.name.padEnd(25)} ${tool.install.cmd}`);
|
|
83
|
+
}
|
|
84
|
+
if (tool.alternatives && tool.alternatives.length > 0) {
|
|
85
|
+
const altNames = tool.alternatives.map((a) => `${a.tool} (${a.tier})`).join(', ');
|
|
86
|
+
lines.push(` Free alternatives: ${altNames}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
lines.push(...formatSkippedSection(skipped), '');
|
|
92
|
+
return lines.join('\n');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Handle the recommend command.
|
|
97
|
+
* @param {Object} flags - Parsed CLI flags
|
|
98
|
+
* @param {string} [projectPath] - Project path (defaults to cwd)
|
|
99
|
+
* @returns {{ budgetMode: string, recommendations: Object, error?: string }}
|
|
100
|
+
*/
|
|
101
|
+
function handleRecommend(flags, projectPath) {
|
|
102
|
+
const budgetMode = flags.budget || 'free';
|
|
103
|
+
|
|
104
|
+
// Validate budget mode
|
|
105
|
+
if (!BUDGET_MODES[budgetMode]) {
|
|
106
|
+
return {
|
|
107
|
+
budgetMode,
|
|
108
|
+
recommendations: { recommended: [], skipped: [] },
|
|
109
|
+
error: `Invalid budget mode: '${budgetMode}'. Valid: ${Object.keys(BUDGET_MODES).join(', ')}`,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const techStack = detectTechStack(projectPath || process.cwd());
|
|
114
|
+
const recommendations = recommend(techStack, budgetMode);
|
|
115
|
+
|
|
116
|
+
return { budgetMode, recommendations };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
module.exports = { formatRecommendations, handleRecommend };
|