forge-workflow 0.0.3 → 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.
Files changed (209) hide show
  1. package/.claude/commands/dev.md +340 -314
  2. package/.claude/commands/plan.md +521 -478
  3. package/.claude/commands/premerge.md +176 -179
  4. package/.claude/commands/research.md +42 -42
  5. package/.claude/commands/review.md +442 -442
  6. package/.claude/commands/rollback.md +721 -721
  7. package/.claude/commands/ship.md +164 -134
  8. package/.claude/commands/sonarcloud.md +152 -152
  9. package/.claude/commands/status.md +48 -77
  10. package/.claude/commands/validate.md +282 -237
  11. package/.claude/commands/verify.md +221 -221
  12. package/.claude/rules/greptile-review-process.md +285 -285
  13. package/.claude/rules/workflow.md +105 -105
  14. package/.claude/scripts/greptile-resolve.sh +526 -526
  15. package/.claude/scripts/load-env.sh +32 -32
  16. package/.cline/workflows/dev.md +337 -311
  17. package/.cline/workflows/plan.md +518 -475
  18. package/.cline/workflows/premerge.md +173 -176
  19. package/.cline/workflows/research.md +39 -39
  20. package/.cline/workflows/review.md +439 -439
  21. package/.cline/workflows/rollback.md +718 -718
  22. package/.cline/workflows/ship.md +161 -131
  23. package/.cline/workflows/sonarcloud.md +146 -146
  24. package/.cline/workflows/status.md +45 -74
  25. package/.cline/workflows/validate.md +279 -234
  26. package/.cline/workflows/verify.md +218 -218
  27. package/.codex/config.toml +11 -11
  28. package/.codex/skills/dev/SKILL.md +340 -314
  29. package/.codex/skills/plan/SKILL.md +521 -478
  30. package/.codex/skills/premerge/SKILL.md +176 -179
  31. package/.codex/skills/research/SKILL.md +42 -42
  32. package/.codex/skills/review/SKILL.md +442 -442
  33. package/.codex/skills/rollback/SKILL.md +721 -721
  34. package/.codex/skills/ship/SKILL.md +164 -134
  35. package/.codex/skills/sonarcloud/SKILL.md +149 -149
  36. package/.codex/skills/status/SKILL.md +48 -77
  37. package/.codex/skills/validate/SKILL.md +282 -237
  38. package/.codex/skills/verify/SKILL.md +221 -221
  39. package/.cursor/commands/dev.md +337 -311
  40. package/.cursor/commands/plan.md +518 -475
  41. package/.cursor/commands/premerge.md +173 -176
  42. package/.cursor/commands/research.md +39 -39
  43. package/.cursor/commands/review.md +439 -439
  44. package/.cursor/commands/rollback.md +718 -718
  45. package/.cursor/commands/ship.md +161 -131
  46. package/.cursor/commands/sonarcloud.md +146 -146
  47. package/.cursor/commands/status.md +45 -74
  48. package/.cursor/commands/validate.md +279 -234
  49. package/.cursor/commands/verify.md +218 -218
  50. package/.cursor/rules/permissions-guidance.mdc +37 -37
  51. package/.forge/hooks/check-tdd.js +240 -240
  52. package/.github/PLUGIN_TEMPLATE.json +32 -32
  53. package/.github/prompts/dev.prompt.md +342 -316
  54. package/.github/prompts/plan.prompt.md +523 -480
  55. package/.github/prompts/premerge.prompt.md +178 -181
  56. package/.github/prompts/research.prompt.md +44 -44
  57. package/.github/prompts/review.prompt.md +444 -444
  58. package/.github/prompts/rollback.prompt.md +723 -723
  59. package/.github/prompts/ship.prompt.md +166 -136
  60. package/.github/prompts/sonarcloud.prompt.md +151 -151
  61. package/.github/prompts/status.prompt.md +50 -79
  62. package/.github/prompts/validate.prompt.md +284 -239
  63. package/.github/prompts/verify.prompt.md +223 -223
  64. package/.github/workflows/beads-to-github.yml +56 -0
  65. package/.github/workflows/github-to-beads.yml +97 -0
  66. package/.kilocode/workflows/dev.md +341 -315
  67. package/.kilocode/workflows/plan.md +522 -479
  68. package/.kilocode/workflows/premerge.md +177 -180
  69. package/.kilocode/workflows/research.md +43 -43
  70. package/.kilocode/workflows/review.md +443 -443
  71. package/.kilocode/workflows/rollback.md +722 -722
  72. package/.kilocode/workflows/ship.md +165 -135
  73. package/.kilocode/workflows/sonarcloud.md +150 -150
  74. package/.kilocode/workflows/status.md +49 -78
  75. package/.kilocode/workflows/validate.md +283 -238
  76. package/.kilocode/workflows/verify.md +222 -222
  77. package/.mcp.json.example +12 -12
  78. package/.opencode/commands/dev.md +340 -314
  79. package/.opencode/commands/plan.md +521 -478
  80. package/.opencode/commands/premerge.md +176 -179
  81. package/.opencode/commands/research.md +42 -42
  82. package/.opencode/commands/review.md +442 -442
  83. package/.opencode/commands/rollback.md +721 -721
  84. package/.opencode/commands/ship.md +164 -134
  85. package/.opencode/commands/sonarcloud.md +149 -149
  86. package/.opencode/commands/status.md +48 -77
  87. package/.opencode/commands/validate.md +282 -237
  88. package/.opencode/commands/verify.md +221 -221
  89. package/.roo/commands/dev.md +341 -315
  90. package/.roo/commands/plan.md +522 -479
  91. package/.roo/commands/premerge.md +177 -180
  92. package/.roo/commands/research.md +43 -43
  93. package/.roo/commands/review.md +443 -443
  94. package/.roo/commands/rollback.md +722 -722
  95. package/.roo/commands/ship.md +165 -135
  96. package/.roo/commands/sonarcloud.md +150 -150
  97. package/.roo/commands/status.md +49 -78
  98. package/.roo/commands/validate.md +283 -238
  99. package/.roo/commands/verify.md +222 -222
  100. package/AGENTS.md +175 -169
  101. package/CLAUDE.md +100 -99
  102. package/LICENSE +21 -21
  103. package/README.md +429 -414
  104. package/bin/forge-cmd.js +313 -313
  105. package/bin/{forge-validate.js → forge-preflight.js} +309 -303
  106. package/bin/forge.js +4596 -4232
  107. package/docs/AGENT_INSTALL_PROMPT.md +342 -342
  108. package/docs/BEADS_GITHUB_SYNC.md +251 -0
  109. package/docs/ENHANCED_ONBOARDING.md +602 -602
  110. package/docs/EXAMPLES.md +482 -482
  111. package/docs/GREPTILE_SETUP.md +400 -400
  112. package/docs/MANUAL_REVIEW_GUIDE.md +106 -106
  113. package/docs/ROADMAP.md +359 -359
  114. package/docs/SETUP.md +663 -632
  115. package/docs/TOOLCHAIN.md +630 -630
  116. package/docs/VALIDATION.md +363 -363
  117. package/install.sh +40 -1058
  118. package/lefthook.yml +39 -39
  119. package/lib/agents/README.md +198 -198
  120. package/lib/agents/claude.plugin.json +28 -28
  121. package/lib/agents/cline.plugin.json +22 -22
  122. package/lib/agents/codex.plugin.json +19 -19
  123. package/lib/agents/copilot.plugin.json +24 -24
  124. package/lib/agents/cursor.plugin.json +25 -25
  125. package/lib/agents/kilocode.plugin.json +22 -22
  126. package/lib/agents/opencode.plugin.json +20 -20
  127. package/lib/agents/roo.plugin.json +23 -23
  128. package/lib/agents-config.js +2112 -2112
  129. package/lib/beads-health-check.js +143 -0
  130. package/lib/beads-setup.js +341 -0
  131. package/lib/beads-sync-scaffold.js +260 -0
  132. package/lib/commands/dev.js +513 -513
  133. package/lib/commands/plan.js +692 -692
  134. package/lib/commands/recommend.js +119 -119
  135. package/lib/commands/ship.js +377 -377
  136. package/lib/commands/status.js +378 -378
  137. package/lib/commands/validate.js +602 -602
  138. package/lib/context-merge.js +359 -359
  139. package/lib/dep-guard/analyzer.js +294 -294
  140. package/lib/dep-guard/behavior-detector.js +98 -98
  141. package/lib/dep-guard/contract-detector.js +162 -162
  142. package/lib/dep-guard/import-detector.js +498 -498
  143. package/lib/dep-guard/path-utils.js +13 -13
  144. package/lib/dep-guard/rubric.js +120 -120
  145. package/lib/dep-guard/task-parser.js +318 -318
  146. package/lib/detect-agent.js +191 -0
  147. package/lib/detect-worktree.js +47 -0
  148. package/lib/file-hash.js +26 -0
  149. package/lib/husky-migration.js +450 -0
  150. package/lib/lefthook-check.js +65 -0
  151. package/lib/pat-setup.js +207 -0
  152. package/lib/plugin-catalog.js +350 -350
  153. package/lib/plugin-manager.js +166 -166
  154. package/lib/plugin-recommender.js +141 -141
  155. package/lib/project-discovery.js +491 -491
  156. package/lib/setup-action-log.js +139 -0
  157. package/lib/setup-summary-renderer.js +106 -0
  158. package/lib/setup-utils.js +96 -0
  159. package/lib/setup.js +192 -118
  160. package/lib/smart-merge.js +64 -0
  161. package/lib/symlink-utils.js +81 -0
  162. package/lib/workflow-profiles.js +197 -197
  163. package/package.json +131 -129
  164. package/scripts/beads-context.sh +291 -0
  165. package/scripts/beads-context.test.js +563 -0
  166. package/scripts/behavioral-judge.sh +378 -0
  167. package/scripts/benchmark.js +85 -0
  168. package/scripts/branch-protection.js +183 -0
  169. package/scripts/check-agents.js +172 -0
  170. package/scripts/commitlint.js +42 -0
  171. package/scripts/conflict-detect.sh +323 -0
  172. package/scripts/dep-guard-analyze.js +71 -0
  173. package/scripts/dep-guard.sh +811 -0
  174. package/scripts/eval_win.py +249 -0
  175. package/scripts/file-index.sh +399 -0
  176. package/scripts/github-beads-sync/comment.mjs +64 -0
  177. package/scripts/github-beads-sync/config.mjs +148 -0
  178. package/scripts/github-beads-sync/github-api.mjs +131 -0
  179. package/scripts/github-beads-sync/index.mjs +332 -0
  180. package/scripts/github-beads-sync/label-mapper.mjs +54 -0
  181. package/scripts/github-beads-sync/mapping.mjs +78 -0
  182. package/scripts/github-beads-sync/reverse-sync-cli.mjs +31 -0
  183. package/scripts/github-beads-sync/reverse-sync.mjs +138 -0
  184. package/scripts/github-beads-sync/run-bd.mjs +159 -0
  185. package/scripts/github-beads-sync/sanitize.mjs +121 -0
  186. package/scripts/github-beads-sync.config.json +26 -0
  187. package/scripts/improve-command.js +375 -0
  188. package/scripts/lib/eval-runner.js +229 -0
  189. package/scripts/lib/eval-schema.js +135 -0
  190. package/scripts/lib/eval-storage.js +78 -0
  191. package/scripts/lib/grading.js +203 -0
  192. package/scripts/lib/transcript-parser.js +63 -0
  193. package/scripts/lint.js +47 -0
  194. package/scripts/migrate-to-bun-test.js +412 -0
  195. package/scripts/run-command-eval.js +236 -0
  196. package/scripts/smart-status.sh +782 -0
  197. package/scripts/sync-commands.js +571 -0
  198. package/scripts/sync-utils.sh +460 -0
  199. package/scripts/test-dashboard.js +123 -0
  200. package/scripts/test.js +44 -0
  201. package/scripts/validate.sh +94 -0
  202. package/skills/parallel-deep-research/SKILL.md +108 -108
  203. package/skills/parallel-deep-research/evals/README.md +27 -27
  204. package/skills/parallel-deep-research/evals/evals.json +62 -62
  205. package/skills/sonarcloud-analysis/SKILL.md +171 -171
  206. package/skills/sonarcloud-analysis/evals/README.md +27 -27
  207. package/skills/sonarcloud-analysis/evals/evals.json +50 -50
  208. package/skills/sonarcloud-analysis/references/api-reference.md +466 -466
  209. package/docs/WORKFLOW.md +0 -400
@@ -0,0 +1,64 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Smart merge for AGENTS.md - preserves USER sections, updates FORGE sections.
5
+ *
6
+ * Handles four cases:
7
+ * 1. No markers at all: Wrap existing content in USER markers, append FORGE section
8
+ * 2. USER markers but no FORGE markers: Keep USER section, insert FORGE section
9
+ * 3. Both markers present: Preserve USER section, update FORGE section (existing behavior)
10
+ * 4. Empty existing content: Return only FORGE section (no empty USER block)
11
+ *
12
+ * @param {string} existingContent - The current AGENTS.md content
13
+ * @param {string} newContent - The new template content containing FORGE section
14
+ * @returns {string} Merged content, or empty string if merge not possible
15
+ */
16
+ function smartMergeAgentsMd(existingContent, newContent) {
17
+ // Extract FORGE section from new content (needed in all cases)
18
+ const forgeStartMatch = (/(<!-- FORGE:START.*?-->[\s\S]*?<!-- FORGE:END -->)/).exec(newContent);
19
+ const forgeSection = forgeStartMatch ? forgeStartMatch[0] : '';
20
+
21
+ // Check if existing content has markers
22
+ const hasUserMarkers = existingContent.includes('<!-- USER:START') && existingContent.includes('<!-- USER:END');
23
+
24
+ let userSection;
25
+
26
+ if (hasUserMarkers) {
27
+ // Extract existing USER section (with markers)
28
+ const userMatch = (/(<!-- USER:START.*?-->[\s\S]*?<!-- USER:END -->)/).exec(existingContent);
29
+ userSection = userMatch ? userMatch[0] : '';
30
+ } else if (existingContent.trim() === '') {
31
+ // Empty existing content: no USER block at all
32
+ userSection = null;
33
+ } else {
34
+ // No markers: wrap entire existing content in USER markers
35
+ userSection = `<!-- USER:START -->\n${existingContent.trim()}\n<!-- USER:END -->`;
36
+ }
37
+
38
+ // Build merged content
39
+ const setupInstructions = newContent.includes('<!-- FORGE:SETUP-INSTRUCTIONS')
40
+ ? (/(<!-- FORGE:SETUP-INSTRUCTIONS[\s\S]*?-->)/).exec(newContent)?.[0] || ''
41
+ : '';
42
+
43
+ let merged = '# AGENTS.md\n\n';
44
+
45
+ // Add setup instructions if this is first-time setup
46
+ if (setupInstructions && !existingContent.includes('FORGE:SETUP-INSTRUCTIONS')) {
47
+ merged += setupInstructions + '\n\n';
48
+ }
49
+
50
+ // Add USER section (skip if empty existing content — no empty USER block)
51
+ if (userSection !== null) {
52
+ merged += userSection + '\n\n';
53
+ }
54
+
55
+ // Add updated FORGE section
56
+ merged += forgeSection + '\n\n';
57
+
58
+ // Add footer
59
+ merged += `---\n\n## Improving This Workflow\n\nEvery time you give the same instruction twice, add it to this file:\n1. User-specific rules: Add to USER:START section above\n2. Forge workflow improvements: Suggest to forge maintainers\n\n**Keep this file updated as you learn about the project.**\n\n---\n\nSee \`AGENTS.md\` for complete workflow guide.\nSee \`docs/TOOLCHAIN.md\` for comprehensive tool reference.\n`;
60
+
61
+ return merged;
62
+ }
63
+
64
+ module.exports = { smartMergeAgentsMd };
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Symlink utilities for Forge setup.
3
+ *
4
+ * Creates CLAUDE.md (or other agent link files) as a symlink to AGENTS.md,
5
+ * with an automatic fallback to copy when symlinks are not available
6
+ * (e.g., Windows without admin privileges).
7
+ *
8
+ * @module symlink-utils
9
+ */
10
+
11
+ const fs = require('node:fs');
12
+ const path = require('node:path');
13
+
14
+ /**
15
+ * Header comment prepended to copies when symlink creation fails.
16
+ * Alerts users that the file is a copy and how to create a proper symlink.
17
+ */
18
+ const HEADER_COMMENT =
19
+ '<!-- This file is a copy of AGENTS.md. Keep in sync manually or use: bunx forge setup --symlink -->';
20
+
21
+ /**
22
+ * Create a symlink from `linkPath` pointing to `target`.
23
+ * If symlink creation fails (e.g., EPERM on Windows without admin),
24
+ * falls back to a file copy with a header comment — unless `symlinkOnly`
25
+ * is true, in which case it reports the error and returns ''.
26
+ *
27
+ * @param {string} target - Absolute path to the source file (e.g., AGENTS.md)
28
+ * @param {string} linkPath - Absolute path for the symlink/copy (e.g., CLAUDE.md)
29
+ * @param {Object} [options={}] - Options
30
+ * @param {boolean} [options.symlinkOnly=false] - When true, skip copy fallback (--symlink flag)
31
+ * @returns {'linked'|'copied'|''} Result indicator
32
+ */
33
+ function createSymlinkOrCopy(target, linkPath, options = {}) {
34
+ try {
35
+ // Ensure target exists
36
+ if (!fs.existsSync(target)) {
37
+ console.error(` ✗ Source file does not exist: ${target}`);
38
+ return '';
39
+ }
40
+
41
+ // Remove existing file/symlink at linkPath
42
+ if (fs.existsSync(linkPath)) {
43
+ const stat = fs.lstatSync(linkPath);
44
+ if (stat.isDirectory()) {
45
+ console.warn(
46
+ ` ⚠ Skipped ${linkPath} (a directory exists at this path). Remove it manually and re-run setup.`
47
+ );
48
+ return '';
49
+ }
50
+ fs.unlinkSync(linkPath);
51
+ }
52
+
53
+ // Ensure parent directory exists
54
+ const linkDir = path.dirname(linkPath);
55
+ if (!fs.existsSync(linkDir)) {
56
+ fs.mkdirSync(linkDir, { recursive: true });
57
+ }
58
+
59
+ // Attempt symlink (relative path for portability)
60
+ try {
61
+ const relPath = path.relative(linkDir, target);
62
+ fs.symlinkSync(relPath, linkPath);
63
+ return 'linked';
64
+ } catch (_symlinkErr) {
65
+ // Expected: symlink creation fails with EPERM on Windows without admin privileges — fall back to copy
66
+ if (options.symlinkOnly) {
67
+ console.warn(` ⚠ Symlink failed for ${linkPath} (--symlink requires symlink support)`);
68
+ return '';
69
+ }
70
+ // Fall back to copy with header
71
+ const content = fs.readFileSync(target, 'utf-8');
72
+ fs.writeFileSync(linkPath, HEADER_COMMENT + '\n' + content, 'utf-8');
73
+ return 'copied';
74
+ }
75
+ } catch (err) {
76
+ console.error(` ✗ Failed to link/copy ${target} -> ${linkPath}: ${err.message}`);
77
+ return '';
78
+ }
79
+ }
80
+
81
+ module.exports = { createSymlinkOrCopy, HEADER_COMMENT };
@@ -1,197 +1,197 @@
1
- /**
2
- * Workflow Profiles
3
- *
4
- * Implements hybrid approach: 4 user-facing types (Feature/Fix/Refactor/Chore)
5
- * map to 6 internal profiles (Critical/Standard/Simple/Hotfix/Docs/Refactor)
6
- * with keyword-based auto-escalation.
7
- */
8
-
9
- /**
10
- * Internal workflow profiles aligned with AGENTS.md specification
11
- */
12
- const PROFILES = {
13
- critical: {
14
- name: 'Critical (Maximum Rigor)',
15
- stages: ['/status', '/plan', '/dev', '/validate', '/ship', '/review', '/premerge', '/verify'],
16
- tdd: 'strict',
17
- research: 'required',
18
- description: 'Security, auth, payments, data integrity, breaking changes',
19
- keywords: ['auth', 'security', 'payment', 'crypto', 'password', 'token', 'session', 'data-migration', 'breaking']
20
- },
21
-
22
- standard: {
23
- name: 'Standard (Full Workflow)',
24
- stages: ['/status', '/plan', '/dev', '/validate', '/ship', '/review', '/premerge'],
25
- tdd: 'required',
26
- research: 'optional',
27
- description: 'Most features and non-critical enhancements'
28
- },
29
-
30
- simple: {
31
- name: 'Simple (Streamlined)',
32
- stages: ['/dev', '/validate', '/ship', '/premerge'],
33
- tdd: 'recommended',
34
- research: 'skip',
35
- description: 'UI tweaks, small improvements, minor fixes'
36
- },
37
-
38
- hotfix: {
39
- name: 'Hotfix (Emergency)',
40
- stages: ['/dev', '/validate', '/ship'],
41
- tdd: 'required', // Must reproduce bug
42
- research: 'skip',
43
- description: 'Production bugs, urgent fixes',
44
- keywords: ['urgent', 'production', 'emergency', 'hotfix', 'critical']
45
- },
46
-
47
- docs: {
48
- name: 'Docs (Documentation Only)',
49
- stages: ['/verify', '/ship', '/premerge'],
50
- tdd: 'no',
51
- research: 'no',
52
- description: 'Documentation updates, README changes'
53
- },
54
-
55
- refactor: {
56
- name: 'Refactor (Behavior-Preserving)',
57
- stages: ['/plan', '/dev', '/validate', '/ship', '/premerge'],
58
- tdd: 'strict', // Must preserve behavior
59
- research: 'optional',
60
- description: 'Code cleanup, optimization, behavior-preserving improvements'
61
- }
62
- };
63
-
64
- /**
65
- * Generic keyword detection helper (DRY utility)
66
- * @param {string} text - Text to analyze
67
- * @param {string[]} keywords - Keywords to search for
68
- * @returns {boolean} - True if any keyword found
69
- */
70
- function containsKeywords(text, keywords) {
71
- if (!text || typeof text !== 'string') return false;
72
- const lowerText = text.toLowerCase();
73
- return keywords.some(keyword => lowerText.includes(keyword));
74
- }
75
-
76
- /**
77
- * Detects if text contains critical keywords (auth, security, payment, etc.)
78
- * @param {string} text - Text to analyze (branch name, commit message, etc.)
79
- * @returns {boolean} - True if critical keywords found
80
- */
81
- function containsCriticalKeywords(text) {
82
- const keywords = ['auth', 'security', 'payment', 'crypto', 'password', 'token', 'session', 'migration', 'breaking'];
83
- return containsKeywords(text, keywords);
84
- }
85
-
86
- /**
87
- * Detects if text contains hotfix keywords (urgent, production, emergency, etc.)
88
- * @param {string} text - Text to analyze (branch name, commit message, etc.)
89
- * @returns {boolean} - True if hotfix keywords found
90
- */
91
- function containsHotfixKeywords(text) {
92
- const keywords = ['urgent', 'production', 'emergency', 'hotfix', 'critical'];
93
- return containsKeywords(text, keywords);
94
- }
95
-
96
- /**
97
- * Detects user-facing work type from context (4 simple types)
98
- * @param {Object} context - Detection context
99
- * @param {string} context.branch - Git branch name
100
- * @returns {string} - User type: 'feature' | 'fix' | 'refactor' | 'chore'
101
- */
102
- function getUserType(context) {
103
- const branch = context.branch || '';
104
- const branchLower = branch.toLowerCase();
105
-
106
- // Detect from branch prefix
107
- if (branchLower.startsWith('feat/')) return 'feature';
108
- if (branchLower.startsWith('fix/')) return 'fix';
109
- if (branchLower.startsWith('hotfix/')) return 'fix';
110
- if (branchLower.startsWith('refactor/')) return 'refactor';
111
- if (branchLower.startsWith('docs/')) return 'chore';
112
- if (branchLower.startsWith('chore/')) return 'chore';
113
-
114
- // Default: feature (safest - full workflow)
115
- return 'feature';
116
- }
117
-
118
- /**
119
- * Maps user type to internal profile with keyword-based escalation (6 granular profiles)
120
- * @param {string} userType - User-facing type: 'feature' | 'fix' | 'refactor' | 'chore'
121
- * @param {Object} context - Detection context
122
- * @param {string[]} context.keywords - Keywords from branch/commit
123
- * @param {string[]} context.files - Modified files
124
- * @returns {string} - Internal profile: 'critical' | 'standard' | 'simple' | 'hotfix' | 'docs' | 'refactor'
125
- */
126
- function getInternalProfile(userType, context) {
127
- const keywords = context.keywords || [];
128
- const files = context.files || [];
129
-
130
- // Combine all keywords into a single string for analysis
131
- const keywordText = keywords.join(' ');
132
-
133
- switch (userType) {
134
- case 'feature':
135
- // Auto-escalate to Critical if security-sensitive
136
- return containsCriticalKeywords(keywordText) ? 'critical' : 'standard';
137
-
138
- case 'fix':
139
- // Auto-escalate to Hotfix if urgent/production
140
- return containsHotfixKeywords(keywordText) ? 'hotfix' : 'simple';
141
-
142
- case 'refactor':
143
- // 1:1 mapping
144
- return 'refactor';
145
-
146
- case 'chore':
147
- // Docs if only markdown files, otherwise Simple
148
- if (files.length > 0 && files.every(f => f.endsWith('.md'))) {
149
- return 'docs';
150
- }
151
- return 'simple';
152
-
153
- default:
154
- return 'standard';
155
- }
156
- }
157
-
158
- /**
159
- * Main detection function - detects work type and selects appropriate profile
160
- * @param {Object} context - Detection context
161
- * @param {string} context.manualProfile - Manual profile override (optional)
162
- * @param {string} context.branch - Git branch name
163
- * @param {string[]} context.keywords - Keywords from branch/commit
164
- * @param {string[]} context.files - Modified files
165
- * @returns {Object} - { userType, profile, source }
166
- */
167
- function detectWorkType(context) {
168
- // Priority 1: Manual override (power users)
169
- if (context.manualProfile) {
170
- return {
171
- userType: null,
172
- profile: context.manualProfile,
173
- source: 'manual'
174
- };
175
- }
176
-
177
- // Priority 2: Detect simple user type (4 options)
178
- const userType = getUserType(context);
179
-
180
- // Priority 3: Auto-select granular internal profile (6 options)
181
- const profile = getInternalProfile(userType, context);
182
-
183
- return {
184
- userType,
185
- profile,
186
- source: 'auto'
187
- };
188
- }
189
-
190
- module.exports = {
191
- PROFILES,
192
- containsCriticalKeywords,
193
- containsHotfixKeywords,
194
- getUserType,
195
- getInternalProfile,
196
- detectWorkType
197
- };
1
+ /**
2
+ * Workflow Profiles
3
+ *
4
+ * Implements hybrid approach: 4 user-facing types (Feature/Fix/Refactor/Chore)
5
+ * map to 6 internal profiles (Critical/Standard/Simple/Hotfix/Docs/Refactor)
6
+ * with keyword-based auto-escalation.
7
+ */
8
+
9
+ /**
10
+ * Internal workflow profiles aligned with AGENTS.md specification
11
+ */
12
+ const PROFILES = {
13
+ critical: {
14
+ name: 'Critical (Maximum Rigor)',
15
+ stages: ['/status', '/plan', '/dev', '/validate', '/ship', '/review', '/premerge', '/verify'],
16
+ tdd: 'strict',
17
+ research: 'required',
18
+ description: 'Security, auth, payments, data integrity, breaking changes',
19
+ keywords: ['auth', 'security', 'payment', 'crypto', 'password', 'token', 'session', 'data-migration', 'breaking']
20
+ },
21
+
22
+ standard: {
23
+ name: 'Standard (Full Workflow)',
24
+ stages: ['/status', '/plan', '/dev', '/validate', '/ship', '/review', '/premerge'],
25
+ tdd: 'required',
26
+ research: 'optional',
27
+ description: 'Most features and non-critical enhancements'
28
+ },
29
+
30
+ simple: {
31
+ name: 'Simple (Streamlined)',
32
+ stages: ['/dev', '/validate', '/ship', '/premerge'],
33
+ tdd: 'recommended',
34
+ research: 'skip',
35
+ description: 'UI tweaks, small improvements, minor fixes'
36
+ },
37
+
38
+ hotfix: {
39
+ name: 'Hotfix (Emergency)',
40
+ stages: ['/dev', '/validate', '/ship'],
41
+ tdd: 'required', // Must reproduce bug
42
+ research: 'skip',
43
+ description: 'Production bugs, urgent fixes',
44
+ keywords: ['urgent', 'production', 'emergency', 'hotfix', 'critical']
45
+ },
46
+
47
+ docs: {
48
+ name: 'Docs (Documentation Only)',
49
+ stages: ['/verify', '/ship', '/premerge'],
50
+ tdd: 'no',
51
+ research: 'no',
52
+ description: 'Documentation updates, README changes'
53
+ },
54
+
55
+ refactor: {
56
+ name: 'Refactor (Behavior-Preserving)',
57
+ stages: ['/plan', '/dev', '/validate', '/ship', '/premerge'],
58
+ tdd: 'strict', // Must preserve behavior
59
+ research: 'optional',
60
+ description: 'Code cleanup, optimization, behavior-preserving improvements'
61
+ }
62
+ };
63
+
64
+ /**
65
+ * Generic keyword detection helper (DRY utility)
66
+ * @param {string} text - Text to analyze
67
+ * @param {string[]} keywords - Keywords to search for
68
+ * @returns {boolean} - True if any keyword found
69
+ */
70
+ function containsKeywords(text, keywords) {
71
+ if (!text || typeof text !== 'string') return false;
72
+ const lowerText = text.toLowerCase();
73
+ return keywords.some(keyword => lowerText.includes(keyword));
74
+ }
75
+
76
+ /**
77
+ * Detects if text contains critical keywords (auth, security, payment, etc.)
78
+ * @param {string} text - Text to analyze (branch name, commit message, etc.)
79
+ * @returns {boolean} - True if critical keywords found
80
+ */
81
+ function containsCriticalKeywords(text) {
82
+ const keywords = ['auth', 'security', 'payment', 'crypto', 'password', 'token', 'session', 'migration', 'breaking'];
83
+ return containsKeywords(text, keywords);
84
+ }
85
+
86
+ /**
87
+ * Detects if text contains hotfix keywords (urgent, production, emergency, etc.)
88
+ * @param {string} text - Text to analyze (branch name, commit message, etc.)
89
+ * @returns {boolean} - True if hotfix keywords found
90
+ */
91
+ function containsHotfixKeywords(text) {
92
+ const keywords = ['urgent', 'production', 'emergency', 'hotfix', 'critical'];
93
+ return containsKeywords(text, keywords);
94
+ }
95
+
96
+ /**
97
+ * Detects user-facing work type from context (4 simple types)
98
+ * @param {Object} context - Detection context
99
+ * @param {string} context.branch - Git branch name
100
+ * @returns {string} - User type: 'feature' | 'fix' | 'refactor' | 'chore'
101
+ */
102
+ function getUserType(context) {
103
+ const branch = context.branch || '';
104
+ const branchLower = branch.toLowerCase();
105
+
106
+ // Detect from branch prefix
107
+ if (branchLower.startsWith('feat/')) return 'feature';
108
+ if (branchLower.startsWith('fix/')) return 'fix';
109
+ if (branchLower.startsWith('hotfix/')) return 'fix';
110
+ if (branchLower.startsWith('refactor/')) return 'refactor';
111
+ if (branchLower.startsWith('docs/')) return 'chore';
112
+ if (branchLower.startsWith('chore/')) return 'chore';
113
+
114
+ // Default: feature (safest - full workflow)
115
+ return 'feature';
116
+ }
117
+
118
+ /**
119
+ * Maps user type to internal profile with keyword-based escalation (6 granular profiles)
120
+ * @param {string} userType - User-facing type: 'feature' | 'fix' | 'refactor' | 'chore'
121
+ * @param {Object} context - Detection context
122
+ * @param {string[]} context.keywords - Keywords from branch/commit
123
+ * @param {string[]} context.files - Modified files
124
+ * @returns {string} - Internal profile: 'critical' | 'standard' | 'simple' | 'hotfix' | 'docs' | 'refactor'
125
+ */
126
+ function getInternalProfile(userType, context) {
127
+ const keywords = context.keywords || [];
128
+ const files = context.files || [];
129
+
130
+ // Combine all keywords into a single string for analysis
131
+ const keywordText = keywords.join(' ');
132
+
133
+ switch (userType) {
134
+ case 'feature':
135
+ // Auto-escalate to Critical if security-sensitive
136
+ return containsCriticalKeywords(keywordText) ? 'critical' : 'standard';
137
+
138
+ case 'fix':
139
+ // Auto-escalate to Hotfix if urgent/production
140
+ return containsHotfixKeywords(keywordText) ? 'hotfix' : 'simple';
141
+
142
+ case 'refactor':
143
+ // 1:1 mapping
144
+ return 'refactor';
145
+
146
+ case 'chore':
147
+ // Docs if only markdown files, otherwise Simple
148
+ if (files.length > 0 && files.every(f => f.endsWith('.md'))) {
149
+ return 'docs';
150
+ }
151
+ return 'simple';
152
+
153
+ default:
154
+ return 'standard';
155
+ }
156
+ }
157
+
158
+ /**
159
+ * Main detection function - detects work type and selects appropriate profile
160
+ * @param {Object} context - Detection context
161
+ * @param {string} context.manualProfile - Manual profile override (optional)
162
+ * @param {string} context.branch - Git branch name
163
+ * @param {string[]} context.keywords - Keywords from branch/commit
164
+ * @param {string[]} context.files - Modified files
165
+ * @returns {Object} - { userType, profile, source }
166
+ */
167
+ function detectWorkType(context) {
168
+ // Priority 1: Manual override (power users)
169
+ if (context.manualProfile) {
170
+ return {
171
+ userType: null,
172
+ profile: context.manualProfile,
173
+ source: 'manual'
174
+ };
175
+ }
176
+
177
+ // Priority 2: Detect simple user type (4 options)
178
+ const userType = getUserType(context);
179
+
180
+ // Priority 3: Auto-select granular internal profile (6 options)
181
+ const profile = getInternalProfile(userType, context);
182
+
183
+ return {
184
+ userType,
185
+ profile,
186
+ source: 'auto'
187
+ };
188
+ }
189
+
190
+ module.exports = {
191
+ PROFILES,
192
+ containsCriticalKeywords,
193
+ containsHotfixKeywords,
194
+ getUserType,
195
+ getInternalProfile,
196
+ detectWorkType
197
+ };