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.
- package/.claude/commands/dev.md +340 -314
- package/.claude/commands/plan.md +521 -478
- package/.claude/commands/premerge.md +176 -179
- 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 -134
- package/.claude/commands/sonarcloud.md +152 -152
- package/.claude/commands/status.md +48 -77
- package/.claude/commands/validate.md +282 -237
- 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 -311
- package/.cline/workflows/plan.md +518 -475
- package/.cline/workflows/premerge.md +173 -176
- 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 -131
- package/.cline/workflows/sonarcloud.md +146 -146
- package/.cline/workflows/status.md +45 -74
- package/.cline/workflows/validate.md +279 -234
- package/.cline/workflows/verify.md +218 -218
- package/.codex/config.toml +11 -11
- package/.codex/skills/dev/SKILL.md +340 -314
- package/.codex/skills/plan/SKILL.md +521 -478
- package/.codex/skills/premerge/SKILL.md +176 -179
- 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 -134
- package/.codex/skills/sonarcloud/SKILL.md +149 -149
- package/.codex/skills/status/SKILL.md +48 -77
- package/.codex/skills/validate/SKILL.md +282 -237
- package/.codex/skills/verify/SKILL.md +221 -221
- package/.cursor/commands/dev.md +337 -311
- package/.cursor/commands/plan.md +518 -475
- package/.cursor/commands/premerge.md +173 -176
- 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 -131
- package/.cursor/commands/sonarcloud.md +146 -146
- package/.cursor/commands/status.md +45 -74
- package/.cursor/commands/validate.md +279 -234
- 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 -316
- package/.github/prompts/plan.prompt.md +523 -480
- package/.github/prompts/premerge.prompt.md +178 -181
- 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 -136
- package/.github/prompts/sonarcloud.prompt.md +151 -151
- package/.github/prompts/status.prompt.md +50 -79
- package/.github/prompts/validate.prompt.md +284 -239
- 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 -315
- package/.kilocode/workflows/plan.md +522 -479
- package/.kilocode/workflows/premerge.md +177 -180
- 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 -135
- package/.kilocode/workflows/sonarcloud.md +150 -150
- package/.kilocode/workflows/status.md +49 -78
- package/.kilocode/workflows/validate.md +283 -238
- package/.kilocode/workflows/verify.md +222 -222
- package/.mcp.json.example +12 -12
- package/.opencode/commands/dev.md +340 -314
- package/.opencode/commands/plan.md +521 -478
- package/.opencode/commands/premerge.md +176 -179
- 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 -134
- package/.opencode/commands/sonarcloud.md +149 -149
- package/.opencode/commands/status.md +48 -77
- package/.opencode/commands/validate.md +282 -237
- package/.opencode/commands/verify.md +221 -221
- package/.roo/commands/dev.md +341 -315
- package/.roo/commands/plan.md +522 -479
- package/.roo/commands/premerge.md +177 -180
- 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 -135
- package/.roo/commands/sonarcloud.md +150 -150
- package/.roo/commands/status.md +49 -78
- package/.roo/commands/validate.md +283 -238
- package/.roo/commands/verify.md +222 -222
- package/AGENTS.md +175 -169
- package/CLAUDE.md +100 -99
- package/LICENSE +21 -21
- package/README.md +429 -414
- package/bin/forge-cmd.js +313 -313
- package/bin/{forge-validate.js → forge-preflight.js} +309 -303
- package/bin/forge.js +4596 -4232
- package/docs/AGENT_INSTALL_PROMPT.md +342 -342
- package/docs/BEADS_GITHUB_SYNC.md +251 -0
- 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 -632
- package/docs/TOOLCHAIN.md +630 -630
- package/docs/VALIDATION.md +363 -363
- package/install.sh +40 -1058
- 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 -0
- package/lib/detect-worktree.js +47 -0
- package/lib/file-hash.js +26 -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/setup-action-log.js +139 -0
- package/lib/setup-summary-renderer.js +106 -0
- package/lib/setup-utils.js +96 -0
- package/lib/setup.js +192 -118
- 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 -129
- 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/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 };
|
package/lib/workflow-profiles.js
CHANGED
|
@@ -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
|
+
};
|