forge-workflow 0.0.5 → 0.0.7

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.
Files changed (164) hide show
  1. package/.claude/commands/dev.md +6 -1
  2. package/.claude/commands/plan.md +59 -14
  3. package/.claude/commands/premerge.md +10 -0
  4. package/.claude/commands/review.md +7 -1
  5. package/.claude/commands/ship.md +95 -47
  6. package/.claude/commands/status.md +42 -0
  7. package/.claude/commands/validate.md +7 -1
  8. package/.claude/commands/verify.md +52 -4
  9. package/.claude/rules/workflow.md +16 -0
  10. package/.claude/scripts/greptile-resolve.sh +32 -0
  11. package/.cline/workflows/dev.md +6 -1
  12. package/.cline/workflows/plan.md +59 -14
  13. package/.cline/workflows/premerge.md +10 -0
  14. package/.cline/workflows/review.md +7 -1
  15. package/.cline/workflows/ship.md +95 -47
  16. package/.cline/workflows/status.md +42 -0
  17. package/.cline/workflows/validate.md +7 -1
  18. package/.cline/workflows/verify.md +52 -4
  19. package/.codex/skills/dev/SKILL.md +6 -1
  20. package/.codex/skills/plan/SKILL.md +59 -14
  21. package/.codex/skills/premerge/SKILL.md +10 -0
  22. package/.codex/skills/review/SKILL.md +7 -1
  23. package/.codex/skills/ship/SKILL.md +95 -47
  24. package/.codex/skills/status/SKILL.md +42 -0
  25. package/.codex/skills/validate/SKILL.md +7 -1
  26. package/.codex/skills/verify/SKILL.md +52 -4
  27. package/.cursor/commands/dev.md +6 -1
  28. package/.cursor/commands/plan.md +59 -14
  29. package/.cursor/commands/premerge.md +10 -0
  30. package/.cursor/commands/review.md +7 -1
  31. package/.cursor/commands/ship.md +95 -47
  32. package/.cursor/commands/status.md +42 -0
  33. package/.cursor/commands/validate.md +7 -1
  34. package/.cursor/commands/verify.md +52 -4
  35. package/.cursorrules +149 -0
  36. package/.github/prompts/dev.prompt.md +6 -1
  37. package/.github/prompts/plan.prompt.md +59 -14
  38. package/.github/prompts/premerge.prompt.md +10 -0
  39. package/.github/prompts/review.prompt.md +7 -1
  40. package/.github/prompts/ship.prompt.md +95 -47
  41. package/.github/prompts/status.prompt.md +42 -0
  42. package/.github/prompts/validate.prompt.md +7 -1
  43. package/.github/prompts/verify.prompt.md +52 -4
  44. package/.kilocode/workflows/dev.md +6 -1
  45. package/.kilocode/workflows/plan.md +59 -14
  46. package/.kilocode/workflows/premerge.md +10 -0
  47. package/.kilocode/workflows/review.md +7 -1
  48. package/.kilocode/workflows/ship.md +95 -47
  49. package/.kilocode/workflows/status.md +42 -0
  50. package/.kilocode/workflows/validate.md +7 -1
  51. package/.kilocode/workflows/verify.md +52 -4
  52. package/.opencode/commands/dev.md +6 -1
  53. package/.opencode/commands/plan.md +59 -14
  54. package/.opencode/commands/premerge.md +10 -0
  55. package/.opencode/commands/review.md +7 -1
  56. package/.opencode/commands/ship.md +95 -47
  57. package/.opencode/commands/status.md +42 -0
  58. package/.opencode/commands/validate.md +7 -1
  59. package/.opencode/commands/verify.md +52 -4
  60. package/.roo/commands/dev.md +6 -1
  61. package/.roo/commands/plan.md +59 -14
  62. package/.roo/commands/premerge.md +10 -0
  63. package/.roo/commands/review.md +7 -1
  64. package/.roo/commands/ship.md +95 -47
  65. package/.roo/commands/status.md +42 -0
  66. package/.roo/commands/validate.md +7 -1
  67. package/.roo/commands/verify.md +52 -4
  68. package/AGENTS.md +97 -0
  69. package/CLAUDE.md +10 -0
  70. package/README.md +2 -2
  71. package/bin/forge-cmd.js +5 -1
  72. package/bin/forge-preflight.js +15 -2
  73. package/bin/forge.js +211 -9
  74. package/docs/ENHANCED_ONBOARDING.md +96 -86
  75. package/docs/ROADMAP.md +2 -2
  76. package/docs/TOOLCHAIN.md +23 -0
  77. package/docs/VALIDATION.md +1 -1
  78. package/lefthook.yml +11 -0
  79. package/lib/agents/README.md +46 -1
  80. package/lib/agents/cline.plugin.json +11 -4
  81. package/lib/agents/codex.plugin.json +2 -2
  82. package/lib/agents/copilot.plugin.json +5 -5
  83. package/lib/agents/cursor.plugin.json +1 -1
  84. package/lib/agents/kilocode.plugin.json +1 -1
  85. package/lib/agents/opencode.plugin.json +7 -4
  86. package/lib/agents/roo.plugin.json +10 -3
  87. package/lib/agents-config.js +129 -81
  88. package/lib/codex-skills.js +50 -0
  89. package/lib/commands/_registry.js +173 -0
  90. package/lib/commands/clean.js +181 -0
  91. package/lib/commands/commands-reset.js +147 -0
  92. package/lib/commands/dev.js +84 -0
  93. package/lib/commands/plan.js +18 -0
  94. package/lib/commands/push.js +196 -0
  95. package/lib/commands/recommend.js +1 -1
  96. package/lib/commands/setup.js +4295 -0
  97. package/lib/commands/ship.js +20 -0
  98. package/lib/commands/status.js +210 -44
  99. package/lib/commands/sync.js +71 -0
  100. package/lib/commands/team.js +37 -0
  101. package/lib/commands/test.js +207 -0
  102. package/lib/commands/validate.js +13 -0
  103. package/lib/commands/worktree.js +310 -0
  104. package/lib/detect-agent.js +38 -8
  105. package/lib/detection-utils.js +405 -0
  106. package/lib/docs-command.js +51 -0
  107. package/lib/docs-copy.js +50 -0
  108. package/lib/file-utils.js +260 -0
  109. package/lib/forge-context.js +42 -0
  110. package/lib/freshness-token.js +148 -0
  111. package/lib/frontmatter.js +79 -0
  112. package/lib/greptile-match.js +80 -0
  113. package/lib/husky-migration.js +113 -12
  114. package/lib/lefthook-check.js +27 -6
  115. package/lib/plugin-manager.js +225 -72
  116. package/lib/project-discovery.js +39 -5
  117. package/lib/reset.js +309 -0
  118. package/lib/runtime-health.js +305 -0
  119. package/lib/shell-utils.js +50 -0
  120. package/lib/task-ownership.js +117 -0
  121. package/lib/ui-utils.js +43 -0
  122. package/lib/validation-utils.js +163 -0
  123. package/lib/workflow/enforce-stage.js +179 -0
  124. package/lib/workflow/stages.js +201 -0
  125. package/lib/workflow/state.js +332 -0
  126. package/opencode.json +67 -0
  127. package/package.json +16 -6
  128. package/scripts/beads-context.sh +165 -22
  129. package/scripts/beads-context.test.js +5 -1
  130. package/scripts/check-agents.js +103 -0
  131. package/scripts/check-forge-token.js +98 -0
  132. package/scripts/conflict-detect.sh +2 -2
  133. package/scripts/dep-guard.sh +6 -28
  134. package/scripts/file-index.sh +117 -23
  135. package/scripts/forge-team/index.sh +86 -0
  136. package/scripts/forge-team/lib/agent-prompt.sh +52 -0
  137. package/scripts/forge-team/lib/claim.sh +256 -0
  138. package/scripts/forge-team/lib/dashboard.sh +341 -0
  139. package/scripts/forge-team/lib/epic.sh +332 -0
  140. package/scripts/forge-team/lib/hooks.sh +253 -0
  141. package/scripts/forge-team/lib/identity.sh +235 -0
  142. package/scripts/forge-team/lib/sync-github.sh +317 -0
  143. package/scripts/forge-team/lib/verify.sh +284 -0
  144. package/scripts/forge-team/lib/workload.sh +296 -0
  145. package/scripts/forge-team/tests/agent-prompt.test.sh +72 -0
  146. package/scripts/forge-team/tests/claim.test.sh +179 -0
  147. package/scripts/forge-team/tests/dashboard.test.sh +170 -0
  148. package/scripts/forge-team/tests/dispatcher.test.sh +79 -0
  149. package/scripts/forge-team/tests/epic.test.sh +176 -0
  150. package/scripts/forge-team/tests/hooks.test.sh +239 -0
  151. package/scripts/forge-team/tests/identity.test.sh +176 -0
  152. package/scripts/forge-team/tests/integration.test.sh +371 -0
  153. package/scripts/forge-team/tests/sync-github.test.sh +209 -0
  154. package/scripts/forge-team/tests/verify.test.sh +314 -0
  155. package/scripts/forge-team/tests/workflow-integration.test.sh +43 -0
  156. package/scripts/forge-team/tests/workload.test.sh +209 -0
  157. package/scripts/lib/eval-runner.js +39 -0
  158. package/scripts/lib/jsonl-lock.sh +48 -0
  159. package/scripts/lib/sanitize.sh +116 -0
  160. package/scripts/pr-coordinator.sh +756 -0
  161. package/scripts/smart-status.sh +58 -21
  162. package/scripts/sync-commands.js +49 -20
  163. package/scripts/sync-utils.sh +24 -29
  164. package/scripts/test.js +18 -1
@@ -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
+ };
@@ -99,7 +99,7 @@ function formatRecommendations(recommendations) {
99
99
  * @returns {{ budgetMode: string, recommendations: Object, error?: string }}
100
100
  */
101
101
  function handleRecommend(flags, projectPath) {
102
- const budgetMode = flags.budget || 'startup';
102
+ const budgetMode = flags.budget || 'free';
103
103
 
104
104
  // Validate budget mode
105
105
  if (!BUDGET_MODES[budgetMode]) {