forge-workflow 0.0.2 → 0.0.3
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/plan.md +93 -4
- package/.cline/workflows/dev.md +311 -0
- package/.cline/workflows/plan.md +475 -0
- package/.cline/workflows/premerge.md +176 -0
- package/.cline/workflows/research.md +39 -0
- package/.cline/workflows/review.md +439 -0
- package/.cline/workflows/rollback.md +718 -0
- package/.cline/workflows/ship.md +131 -0
- package/.cline/workflows/sonarcloud.md +146 -0
- package/.cline/workflows/status.md +74 -0
- package/.cline/workflows/validate.md +234 -0
- package/.cline/workflows/verify.md +218 -0
- package/.codex/config.toml +11 -0
- package/.codex/skills/dev/SKILL.md +314 -0
- package/.codex/skills/plan/SKILL.md +478 -0
- package/.codex/skills/premerge/SKILL.md +179 -0
- package/.codex/skills/research/SKILL.md +42 -0
- package/.codex/skills/review/SKILL.md +442 -0
- package/.codex/skills/rollback/SKILL.md +721 -0
- package/.codex/skills/ship/SKILL.md +134 -0
- package/.codex/skills/sonarcloud/SKILL.md +149 -0
- package/.codex/skills/status/SKILL.md +77 -0
- package/.codex/skills/validate/SKILL.md +237 -0
- package/.codex/skills/verify/SKILL.md +221 -0
- package/.cursor/commands/dev.md +311 -0
- package/.cursor/commands/plan.md +475 -0
- package/.cursor/commands/premerge.md +176 -0
- package/.cursor/commands/research.md +39 -0
- package/.cursor/commands/review.md +439 -0
- package/.cursor/commands/rollback.md +718 -0
- package/.cursor/commands/ship.md +131 -0
- package/.cursor/commands/sonarcloud.md +146 -0
- package/.cursor/commands/status.md +74 -0
- package/.cursor/commands/validate.md +234 -0
- package/.cursor/commands/verify.md +218 -0
- package/.cursor/rules/permissions-guidance.mdc +37 -0
- package/.github/prompts/dev.prompt.md +316 -0
- package/.github/prompts/plan.prompt.md +480 -0
- package/.github/prompts/premerge.prompt.md +181 -0
- package/.github/prompts/research.prompt.md +44 -0
- package/.github/prompts/review.prompt.md +444 -0
- package/.github/prompts/rollback.prompt.md +723 -0
- package/.github/prompts/ship.prompt.md +136 -0
- package/.github/prompts/sonarcloud.prompt.md +151 -0
- package/.github/prompts/status.prompt.md +79 -0
- package/.github/prompts/validate.prompt.md +239 -0
- package/.github/prompts/verify.prompt.md +223 -0
- package/.kilocode/workflows/dev.md +315 -0
- package/.kilocode/workflows/plan.md +479 -0
- package/.kilocode/workflows/premerge.md +180 -0
- package/.kilocode/workflows/research.md +43 -0
- package/.kilocode/workflows/review.md +443 -0
- package/.kilocode/workflows/rollback.md +722 -0
- package/.kilocode/workflows/ship.md +135 -0
- package/.kilocode/workflows/sonarcloud.md +150 -0
- package/.kilocode/workflows/status.md +78 -0
- package/.kilocode/workflows/validate.md +238 -0
- package/.kilocode/workflows/verify.md +222 -0
- package/.opencode/commands/dev.md +314 -0
- package/.opencode/commands/plan.md +478 -0
- package/.opencode/commands/premerge.md +179 -0
- package/.opencode/commands/research.md +42 -0
- package/.opencode/commands/review.md +442 -0
- package/.opencode/commands/rollback.md +721 -0
- package/.opencode/commands/ship.md +134 -0
- package/.opencode/commands/sonarcloud.md +149 -0
- package/.opencode/commands/status.md +77 -0
- package/.opencode/commands/validate.md +237 -0
- package/.opencode/commands/verify.md +221 -0
- package/.roo/commands/dev.md +315 -0
- package/.roo/commands/plan.md +479 -0
- package/.roo/commands/premerge.md +180 -0
- package/.roo/commands/research.md +43 -0
- package/.roo/commands/review.md +443 -0
- package/.roo/commands/rollback.md +722 -0
- package/.roo/commands/ship.md +135 -0
- package/.roo/commands/sonarcloud.md +150 -0
- package/.roo/commands/status.md +78 -0
- package/.roo/commands/validate.md +238 -0
- package/.roo/commands/verify.md +222 -0
- package/LICENSE +21 -21
- package/docs/ENHANCED_ONBOARDING.md +2 -2
- package/docs/TOOLCHAIN.md +15 -234
- package/install.sh +32 -36
- package/lib/commands/plan.js +11 -15
- package/lib/commands/recommend.js +2 -2
- package/lib/dep-guard/analyzer.js +294 -0
- package/lib/dep-guard/behavior-detector.js +98 -0
- package/lib/dep-guard/contract-detector.js +162 -0
- package/lib/dep-guard/import-detector.js +498 -0
- package/lib/dep-guard/path-utils.js +13 -0
- package/lib/dep-guard/rubric.js +120 -0
- package/lib/dep-guard/task-parser.js +318 -0
- package/lib/plugin-catalog.js +18 -28
- package/lib/workflow-profiles.js +5 -11
- package/package.json +15 -4
- package/skills/parallel-deep-research/SKILL.md +108 -0
- package/skills/parallel-deep-research/evals/README.md +27 -0
- package/skills/parallel-deep-research/evals/evals.json +62 -0
- package/skills/sonarcloud-analysis/SKILL.md +171 -0
- package/skills/sonarcloud-analysis/evals/README.md +27 -0
- package/skills/sonarcloud-analysis/evals/evals.json +50 -0
- package/skills/sonarcloud-analysis/references/api-reference.md +466 -0
- package/docs/planning/PROGRESS.md +0 -396
- package/docs/plans/.gitkeep +0 -0
- package/docs/plans/2026-02-27-forge-test-suite-v2-decisions.md +0 -21
- package/docs/plans/2026-02-27-forge-test-suite-v2-design.md +0 -362
- package/docs/plans/2026-02-27-forge-test-suite-v2-tasks.md +0 -343
- package/docs/plans/2026-03-02-superpowers-gaps-decisions.md +0 -26
- package/docs/plans/2026-03-02-superpowers-gaps-design.md +0 -239
- package/docs/plans/2026-03-02-superpowers-gaps-tasks.md +0 -260
- package/docs/plans/2026-03-04-agent-command-parity-design.md +0 -163
- package/docs/plans/2026-03-04-verify-worktree-cleanup-decisions.md +0 -7
- package/docs/plans/2026-03-04-verify-worktree-cleanup-design.md +0 -165
- package/docs/plans/2026-03-05-forge-uto-decisions.md +0 -6
- package/docs/plans/2026-03-05-forge-uto-design.md +0 -116
- package/docs/plans/2026-03-05-forge-uto-tasks.md +0 -244
- package/docs/plans/2026-03-10-command-creator-and-eval-decisions.md +0 -52
- package/docs/plans/2026-03-10-command-creator-and-eval-design.md +0 -350
- package/docs/plans/2026-03-10-command-creator-and-eval-tasks.md +0 -426
- package/docs/plans/2026-03-10-stale-workflow-refs-decisions.md +0 -8
- package/docs/plans/2026-03-10-stale-workflow-refs-design.md +0 -80
- package/docs/plans/2026-03-10-stale-workflow-refs-tasks.md +0 -90
- package/docs/plans/2026-03-14-beads-plan-context-decisions.md +0 -9
- package/docs/plans/2026-03-14-beads-plan-context-design.md +0 -171
- package/docs/plans/2026-03-14-beads-plan-context-tasks.md +0 -160
- package/docs/plans/2026-03-14-skill-eval-loop-decisions.md +0 -33
- package/docs/plans/2026-03-14-skill-eval-loop-design.md +0 -118
- package/docs/plans/2026-03-14-skill-eval-loop-results.md +0 -78
- package/docs/plans/2026-03-14-skill-eval-loop-tasks.md +0 -160
- package/docs/plans/2026-03-15-agent-command-parity-v2-decisions.md +0 -11
- package/docs/plans/2026-03-15-agent-command-parity-v2-design.md +0 -145
- package/docs/plans/2026-03-15-agent-command-parity-v2-tasks.md +0 -211
- package/docs/research/TEMPLATE.md +0 -292
- package/docs/research/advanced-testing.md +0 -297
- package/docs/research/agent-permissions.md +0 -167
- package/docs/research/dependency-chain.md +0 -328
- package/docs/research/forge-workflow-v2.md +0 -550
- package/docs/research/plugin-architecture.md +0 -772
- package/docs/research/pr4-cli-automation.md +0 -326
- package/docs/research/premerge-verify-restructure.md +0 -205
- package/docs/research/skills-restructure.md +0 -508
- package/docs/research/sonarcloud-perfection-plan.md +0 -166
- package/docs/research/sonarcloud-quality-gate.md +0 -184
- package/docs/research/superpowers-integration.md +0 -403
- package/docs/research/superpowers.md +0 -319
- package/docs/research/test-environment.md +0 -519
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
const WEIGHTS = {
|
|
2
|
+
importCallChain: 3,
|
|
3
|
+
contractDependencies: 3,
|
|
4
|
+
behavioralDependencies: 2,
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
const CONFLICT_MESSAGES = {
|
|
8
|
+
materialDisagreement: 'Detector scores disagree materially across dependency categories.',
|
|
9
|
+
behavioralUncertainty: 'Behavioral signals are heuristic and need manual review.',
|
|
10
|
+
};
|
|
11
|
+
const MAX_DETECTOR_STRENGTH = 3;
|
|
12
|
+
const MAX_TOTAL = Object.values(WEIGHTS)
|
|
13
|
+
.reduce((sum, weight) => sum + (weight * MAX_DETECTOR_STRENGTH), 0);
|
|
14
|
+
|
|
15
|
+
function aggregateRubricScores(detectors, options = {}) {
|
|
16
|
+
const confidenceThreshold = options.confidenceThreshold ?? 0.7;
|
|
17
|
+
const dimensions = {};
|
|
18
|
+
let total = 0;
|
|
19
|
+
let hasMaterialDisagreement = false;
|
|
20
|
+
let hasUncertainty = false;
|
|
21
|
+
|
|
22
|
+
for (const [name, weight] of Object.entries(WEIGHTS)) {
|
|
23
|
+
const raw = detectors[name]?.score ?? 0;
|
|
24
|
+
const normalized = Math.min(raw, MAX_DETECTOR_STRENGTH);
|
|
25
|
+
const weighted = normalized * weight;
|
|
26
|
+
dimensions[name] = { raw, normalized, weighted };
|
|
27
|
+
total += weighted;
|
|
28
|
+
if (detectors[name]?.uncertain) {
|
|
29
|
+
hasUncertainty = true;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (total === 0) {
|
|
34
|
+
return {
|
|
35
|
+
result: 'PASS',
|
|
36
|
+
score: 0,
|
|
37
|
+
total: 0,
|
|
38
|
+
dimensions,
|
|
39
|
+
weights: { ...WEIGHTS },
|
|
40
|
+
confidence: {
|
|
41
|
+
score: 1,
|
|
42
|
+
threshold: confidenceThreshold,
|
|
43
|
+
belowThreshold: false,
|
|
44
|
+
reasons: [],
|
|
45
|
+
},
|
|
46
|
+
detectorConflicts: [],
|
|
47
|
+
summary: 'No detector findings yet.',
|
|
48
|
+
reasons: [],
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const rawScores = Object.values(dimensions).map((dimension) => dimension.raw);
|
|
53
|
+
const nonZeroScores = rawScores.filter((score) => score > 0);
|
|
54
|
+
const detectorConflicts = [];
|
|
55
|
+
|
|
56
|
+
if (nonZeroScores.length >= 2 && (Math.max(...nonZeroScores) - Math.min(...nonZeroScores) >= 2)) {
|
|
57
|
+
hasMaterialDisagreement = true;
|
|
58
|
+
detectorConflicts.push(CONFLICT_MESSAGES.materialDisagreement);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (hasUncertainty) {
|
|
62
|
+
detectorConflicts.push(CONFLICT_MESSAGES.behavioralUncertainty);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let confidenceScore = total / MAX_TOTAL;
|
|
66
|
+
if (hasMaterialDisagreement) {
|
|
67
|
+
confidenceScore -= 0.2;
|
|
68
|
+
}
|
|
69
|
+
if (hasUncertainty) {
|
|
70
|
+
confidenceScore -= 0.15;
|
|
71
|
+
}
|
|
72
|
+
confidenceScore = Math.max(0, Math.min(1, confidenceScore));
|
|
73
|
+
const belowThreshold = confidenceScore < confidenceThreshold;
|
|
74
|
+
|
|
75
|
+
const reasons = [];
|
|
76
|
+
if (detectorConflicts.length > 0) {
|
|
77
|
+
reasons.push(...detectorConflicts);
|
|
78
|
+
}
|
|
79
|
+
if (belowThreshold) {
|
|
80
|
+
reasons.push(`Confidence ${confidenceScore.toFixed(2)} is below the ${(confidenceThreshold * 100).toFixed(0)}% threshold.`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
let result = 'PASS';
|
|
84
|
+
if (detectorConflicts.length > 0) {
|
|
85
|
+
result = 'INCONCLUSIVE';
|
|
86
|
+
} else if (belowThreshold) {
|
|
87
|
+
result = 'WEAK';
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
result,
|
|
92
|
+
score: total,
|
|
93
|
+
total,
|
|
94
|
+
dimensions,
|
|
95
|
+
weights: { ...WEIGHTS },
|
|
96
|
+
confidence: {
|
|
97
|
+
score: confidenceScore,
|
|
98
|
+
threshold: confidenceThreshold,
|
|
99
|
+
belowThreshold,
|
|
100
|
+
reasons,
|
|
101
|
+
},
|
|
102
|
+
detectorConflicts,
|
|
103
|
+
summary: reasons.join(' ') || 'Detector findings are consistent.',
|
|
104
|
+
reasons,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function needsUserEscalation(rubricResult) {
|
|
109
|
+
return (
|
|
110
|
+
rubricResult.result === 'INCONCLUSIVE'
|
|
111
|
+
|| rubricResult.confidence.belowThreshold
|
|
112
|
+
|| rubricResult.detectorConflicts.length > 0
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
module.exports = {
|
|
117
|
+
WEIGHTS,
|
|
118
|
+
aggregateRubricScores,
|
|
119
|
+
needsUserEscalation,
|
|
120
|
+
};
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
const fs = require('node:fs');
|
|
2
|
+
const path = require('node:path');
|
|
3
|
+
const { normalizeRepoPath } = require('./path-utils.js');
|
|
4
|
+
|
|
5
|
+
function ensureTaskFile(taskFile) {
|
|
6
|
+
if (!taskFile || typeof taskFile !== 'string') {
|
|
7
|
+
throw new Error('Task file path is required');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const absolutePath = path.resolve(taskFile);
|
|
11
|
+
if (!fs.existsSync(absolutePath)) {
|
|
12
|
+
throw new Error(`Task file does not exist: ${absolutePath}`);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return absolutePath;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function parseFilesLine(value) {
|
|
19
|
+
if (!value) {
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return value
|
|
24
|
+
.split(',')
|
|
25
|
+
.map((file) => file.replaceAll('`', '').trim())
|
|
26
|
+
.filter(Boolean);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function flushTask(tasks, currentTask) {
|
|
30
|
+
if (!currentTask) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
tasks.push({
|
|
35
|
+
number: currentTask.number,
|
|
36
|
+
title: currentTask.title,
|
|
37
|
+
files: currentTask.files,
|
|
38
|
+
whatToImplement: currentTask.whatToImplement.trim(),
|
|
39
|
+
expectedOutput: currentTask.expectedOutput.trim(),
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function isIdentifierStart(character) {
|
|
44
|
+
return /[A-Za-z_]/.test(character);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function isIdentifierPart(character) {
|
|
48
|
+
return /\w/.test(character);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function extractSymbols(text) {
|
|
52
|
+
const symbols = new Set();
|
|
53
|
+
let searchIndex = 0;
|
|
54
|
+
|
|
55
|
+
while (searchIndex < text.length) {
|
|
56
|
+
const callIndex = text.indexOf('()', searchIndex);
|
|
57
|
+
if (callIndex === -1) {
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
let startIndex = callIndex - 1;
|
|
62
|
+
while (startIndex >= 0 && isIdentifierPart(text[startIndex])) {
|
|
63
|
+
startIndex -= 1;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const symbol = text.slice(startIndex + 1, callIndex);
|
|
67
|
+
if (symbol && isIdentifierStart(symbol[0])) {
|
|
68
|
+
symbols.add(symbol);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
searchIndex = callIndex + 2;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
for (const match of text.match(/`([^`\r\n]+)`/g) ?? []) {
|
|
75
|
+
const token = match.slice(1, -1).trim();
|
|
76
|
+
if (!token || /[/.\\\s]/.test(token)) {
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (/^[A-Za-z_][A-Za-z0-9_-]*$/.test(token)) {
|
|
81
|
+
symbols.add(token);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return Array.from(symbols);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function detectContractType(symbol, annotation = '') {
|
|
89
|
+
if (annotation === 'data-format' || annotation === 'command-contract' || annotation === 'return-shape') {
|
|
90
|
+
return annotation;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (/Schema$|Format$/i.test(symbol)) {
|
|
94
|
+
return 'data-format';
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (/Contract$/i.test(symbol)) {
|
|
98
|
+
return 'command-contract';
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (symbol.includes('-')) {
|
|
102
|
+
return 'command-contract';
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (/Shape$/i.test(symbol)) {
|
|
106
|
+
return 'return-shape';
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return 'modified';
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function extractContractMentions(text) {
|
|
113
|
+
const mentions = new Map();
|
|
114
|
+
for (const symbol of extractSymbols(text)) {
|
|
115
|
+
mentions.set(symbol, {
|
|
116
|
+
symbol,
|
|
117
|
+
annotation: text.includes(`${symbol}()`) ? 'modified' : detectContractType(symbol),
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
for (const match of text.match(/\b[A-Za-z_]\w*(?:Schema|Format|Contract|Shape)\b/g) ?? []) {
|
|
122
|
+
if (mentions.has(match)) {
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
mentions.set(match, {
|
|
127
|
+
symbol: match,
|
|
128
|
+
annotation: detectContractType(match),
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return Array.from(mentions.values());
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function escapeRegExp(value) {
|
|
136
|
+
return value.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function resolveRepositoryFile(repositoryRoot, filePath) {
|
|
140
|
+
if (!repositoryRoot || !filePath || path.isAbsolute(filePath)) {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const absoluteRoot = path.resolve(repositoryRoot);
|
|
145
|
+
const absolutePath = path.resolve(absoluteRoot, filePath);
|
|
146
|
+
const relativePath = path.relative(absoluteRoot, absolutePath);
|
|
147
|
+
if (
|
|
148
|
+
relativePath === ''
|
|
149
|
+
|| relativePath.startsWith(`..${path.sep}`)
|
|
150
|
+
|| relativePath === '..'
|
|
151
|
+
|| path.isAbsolute(relativePath)
|
|
152
|
+
) {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return absolutePath;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function fileContainsSymbol(repositoryRoot, filePath, symbol) {
|
|
160
|
+
const absolutePath = resolveRepositoryFile(repositoryRoot, filePath);
|
|
161
|
+
if (!absolutePath) {
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (!fs.existsSync(absolutePath)) {
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const content = fs.readFileSync(absolutePath, 'utf8');
|
|
170
|
+
if (/^[A-Za-z_]\w*$/.test(symbol)) {
|
|
171
|
+
return new RegExp(String.raw`\b${escapeRegExp(symbol)}\b`).test(content);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return content.includes(symbol);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function resolveMentionFiles(task, mention, repositoryRoot) {
|
|
178
|
+
const files = task.files ?? [];
|
|
179
|
+
if (files.length === 0) {
|
|
180
|
+
return [];
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (!repositoryRoot) {
|
|
184
|
+
return files.map((file) => normalizeRepoPath(file));
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const matchingFiles = files
|
|
188
|
+
.filter((file) => fileContainsSymbol(repositoryRoot, file, mention.symbol))
|
|
189
|
+
.map((file) => normalizeRepoPath(file));
|
|
190
|
+
return matchingFiles;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function extractTaskContracts(taskContext, options = {}) {
|
|
194
|
+
if (!taskContext || !Array.isArray(taskContext.tasks)) {
|
|
195
|
+
return [];
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const contracts = new Set();
|
|
199
|
+
const repositoryRoot = options.repositoryRoot;
|
|
200
|
+
|
|
201
|
+
for (const task of taskContext.tasks) {
|
|
202
|
+
const mentions = extractContractMentions(task.whatToImplement ?? '');
|
|
203
|
+
for (const mention of mentions) {
|
|
204
|
+
for (const file of resolveMentionFiles(task, mention, repositoryRoot)) {
|
|
205
|
+
contracts.add(`${file}:${mention.symbol}(${mention.annotation})`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return Array.from(contracts).sort((left, right) => left.localeCompare(right));
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function isTaskHeader(line) {
|
|
214
|
+
return line.startsWith('## Task ');
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function parseTaskHeader(line) {
|
|
218
|
+
if (!isTaskHeader(line)) {
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const prefixLength = '## Task '.length;
|
|
223
|
+
const separatorIndex = line.indexOf(':', prefixLength);
|
|
224
|
+
if (separatorIndex === -1) {
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const numberText = line.slice(prefixLength, separatorIndex).trim();
|
|
229
|
+
if (!numberText || Array.from(numberText).some((character) => character < '0' || character > '9')) {
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const title = line.slice(separatorIndex + 1).trim();
|
|
234
|
+
if (!title) {
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return {
|
|
239
|
+
number: Number(numberText),
|
|
240
|
+
title,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function parseTaskField(currentTask, line, currentField) {
|
|
245
|
+
if (line.startsWith('File(s):')) {
|
|
246
|
+
currentTask.files = parseFilesLine(line.slice('File(s):'.length).trim());
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (line.startsWith('What to implement:')) {
|
|
251
|
+
currentTask.whatToImplement = line.slice('What to implement:'.length).trim();
|
|
252
|
+
return 'whatToImplement';
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (line.startsWith('Expected output:')) {
|
|
256
|
+
currentTask.expectedOutput = line.slice('Expected output:'.length).trim();
|
|
257
|
+
return 'expectedOutput';
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (line === 'TDD steps:' || line === '---') {
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (currentField && line.trim()) {
|
|
265
|
+
currentTask[currentField] = `${currentTask[currentField]} ${line.trim()}`.trim();
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return currentField;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function parseTaskFile(taskFile) {
|
|
272
|
+
const absolutePath = ensureTaskFile(taskFile);
|
|
273
|
+
const content = fs.readFileSync(absolutePath, 'utf8');
|
|
274
|
+
const lines = content.split(/\r?\n/);
|
|
275
|
+
const tasks = [];
|
|
276
|
+
let currentTask = null;
|
|
277
|
+
let currentField = null;
|
|
278
|
+
|
|
279
|
+
for (const rawLine of lines) {
|
|
280
|
+
const line = rawLine.trimEnd();
|
|
281
|
+
const taskHeader = parseTaskHeader(line);
|
|
282
|
+
|
|
283
|
+
if (taskHeader) {
|
|
284
|
+
flushTask(tasks, currentTask);
|
|
285
|
+
currentTask = {
|
|
286
|
+
number: taskHeader.number,
|
|
287
|
+
title: taskHeader.title,
|
|
288
|
+
files: [],
|
|
289
|
+
whatToImplement: '',
|
|
290
|
+
expectedOutput: '',
|
|
291
|
+
};
|
|
292
|
+
currentField = null;
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (!currentTask) {
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
currentField = parseTaskField(currentTask, line, currentField);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
flushTask(tasks, currentTask);
|
|
304
|
+
|
|
305
|
+
return {
|
|
306
|
+
path: absolutePath,
|
|
307
|
+
taskCount: tasks.length,
|
|
308
|
+
tasks,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
module.exports = {
|
|
313
|
+
detectContractType,
|
|
314
|
+
extractContractMentions,
|
|
315
|
+
extractSymbols,
|
|
316
|
+
extractTaskContracts,
|
|
317
|
+
parseTaskFile,
|
|
318
|
+
};
|
package/lib/plugin-catalog.js
CHANGED
|
@@ -24,10 +24,10 @@ const STAGES = Object.freeze({
|
|
|
24
24
|
RESEARCH: 'research',
|
|
25
25
|
PLAN: 'plan',
|
|
26
26
|
DEV: 'dev',
|
|
27
|
-
|
|
27
|
+
VALIDATE: 'validate',
|
|
28
28
|
SHIP: 'ship',
|
|
29
29
|
REVIEW: 'review',
|
|
30
|
-
|
|
30
|
+
PREMERGE: 'premerge',
|
|
31
31
|
});
|
|
32
32
|
|
|
33
33
|
const BUDGET_MODES = Object.freeze({
|
|
@@ -101,16 +101,6 @@ const CATALOG = Object.freeze({
|
|
|
101
101
|
detectWhen: [],
|
|
102
102
|
install: { method: 'npm', cmd: 'bun add -g @beads/bd' },
|
|
103
103
|
},
|
|
104
|
-
openspec: {
|
|
105
|
-
name: 'OpenSpec',
|
|
106
|
-
type: 'cli',
|
|
107
|
-
tier: 'free',
|
|
108
|
-
stage: 'plan',
|
|
109
|
-
description: 'Spec-driven development proposals',
|
|
110
|
-
detectWhen: [],
|
|
111
|
-
install: { method: 'npm', cmd: 'bun add -g @fission-ai/openspec' },
|
|
112
|
-
},
|
|
113
|
-
|
|
114
104
|
// ── Dev ──
|
|
115
105
|
'typescript-lsp': {
|
|
116
106
|
name: 'TypeScript LSP',
|
|
@@ -155,7 +145,7 @@ const CATALOG = Object.freeze({
|
|
|
155
145
|
name: 'ESLint',
|
|
156
146
|
type: 'cli',
|
|
157
147
|
tier: 'free',
|
|
158
|
-
stage: '
|
|
148
|
+
stage: 'validate',
|
|
159
149
|
description: 'JavaScript/TypeScript linting',
|
|
160
150
|
detectWhen: ['file:eslint.config.js', 'dep:eslint'],
|
|
161
151
|
install: { method: 'npm', cmd: 'bun add -D eslint', dev: true },
|
|
@@ -164,7 +154,7 @@ const CATALOG = Object.freeze({
|
|
|
164
154
|
name: 'Biome',
|
|
165
155
|
type: 'cli',
|
|
166
156
|
tier: 'free',
|
|
167
|
-
stage: '
|
|
157
|
+
stage: 'validate',
|
|
168
158
|
description: 'Fast formatter and linter',
|
|
169
159
|
detectWhen: ['file:biome.json'],
|
|
170
160
|
install: { method: 'npm', cmd: 'bun add -D @biomejs/biome', dev: true },
|
|
@@ -173,7 +163,7 @@ const CATALOG = Object.freeze({
|
|
|
173
163
|
name: 'Prettier',
|
|
174
164
|
type: 'cli',
|
|
175
165
|
tier: 'free',
|
|
176
|
-
stage: '
|
|
166
|
+
stage: 'validate',
|
|
177
167
|
description: 'Opinionated code formatter',
|
|
178
168
|
detectWhen: ['file:.prettierrc'],
|
|
179
169
|
install: { method: 'npm', cmd: 'bun add -D prettier', dev: true },
|
|
@@ -182,7 +172,7 @@ const CATALOG = Object.freeze({
|
|
|
182
172
|
name: 'ESLint Security Plugin',
|
|
183
173
|
type: 'config',
|
|
184
174
|
tier: 'free',
|
|
185
|
-
stage: '
|
|
175
|
+
stage: 'validate',
|
|
186
176
|
description: 'Security-focused ESLint rules',
|
|
187
177
|
detectWhen: ['dep:express', 'dep:fastify'],
|
|
188
178
|
install: { method: 'npm', cmd: 'bun add -D eslint-plugin-security', dev: true },
|
|
@@ -191,7 +181,7 @@ const CATALOG = Object.freeze({
|
|
|
191
181
|
name: 'SonarCloud Analysis',
|
|
192
182
|
type: 'skill',
|
|
193
183
|
tier: 'free-public',
|
|
194
|
-
stage: '
|
|
184
|
+
stage: 'validate',
|
|
195
185
|
description: 'Code quality and security analysis via SonarCloud REST API',
|
|
196
186
|
detectWhen: [],
|
|
197
187
|
install: {
|
|
@@ -204,7 +194,7 @@ const CATALOG = Object.freeze({
|
|
|
204
194
|
name: 'Sonar Scanner',
|
|
205
195
|
type: 'cli',
|
|
206
196
|
tier: 'free-public',
|
|
207
|
-
stage: '
|
|
197
|
+
stage: 'validate',
|
|
208
198
|
description: 'SonarCloud/SonarQube CLI scanner',
|
|
209
199
|
detectWhen: ['file:sonar-project.properties'],
|
|
210
200
|
install: { method: 'npm', cmd: 'bun add -D sonarqube-scanner', dev: true },
|
|
@@ -214,7 +204,7 @@ const CATALOG = Object.freeze({
|
|
|
214
204
|
name: 'CodeQL',
|
|
215
205
|
type: 'cli',
|
|
216
206
|
tier: 'free-public',
|
|
217
|
-
stage: '
|
|
207
|
+
stage: 'validate',
|
|
218
208
|
description: 'Semantic code analysis by GitHub',
|
|
219
209
|
detectWhen: [],
|
|
220
210
|
install: { method: 'binary', cmd: 'GitHub-provided (github.com/github/codeql-action)' },
|
|
@@ -224,7 +214,7 @@ const CATALOG = Object.freeze({
|
|
|
224
214
|
name: 'npm audit',
|
|
225
215
|
type: 'cli',
|
|
226
216
|
tier: 'free',
|
|
227
|
-
stage: '
|
|
217
|
+
stage: 'validate',
|
|
228
218
|
description: 'Dependency vulnerability scanning',
|
|
229
219
|
detectWhen: [],
|
|
230
220
|
install: { method: 'npm', cmd: 'built-in (bun pm audit)' },
|
|
@@ -233,7 +223,7 @@ const CATALOG = Object.freeze({
|
|
|
233
223
|
name: 'Vitest',
|
|
234
224
|
type: 'cli',
|
|
235
225
|
tier: 'free',
|
|
236
|
-
stage: '
|
|
226
|
+
stage: 'validate',
|
|
237
227
|
description: 'Vite-native test runner',
|
|
238
228
|
detectWhen: ['dep:vitest', 'file:vitest.config.ts'],
|
|
239
229
|
install: { method: 'npm', cmd: 'bun add -D vitest', dev: true },
|
|
@@ -242,7 +232,7 @@ const CATALOG = Object.freeze({
|
|
|
242
232
|
name: 'Jest',
|
|
243
233
|
type: 'cli',
|
|
244
234
|
tier: 'free',
|
|
245
|
-
stage: '
|
|
235
|
+
stage: 'validate',
|
|
246
236
|
description: 'Delightful JavaScript testing',
|
|
247
237
|
detectWhen: ['dep:jest', 'file:jest.config.js'],
|
|
248
238
|
install: { method: 'npm', cmd: 'bun add -D jest', dev: true },
|
|
@@ -251,7 +241,7 @@ const CATALOG = Object.freeze({
|
|
|
251
241
|
name: 'Playwright',
|
|
252
242
|
type: 'cli',
|
|
253
243
|
tier: 'free',
|
|
254
|
-
stage: '
|
|
244
|
+
stage: 'validate',
|
|
255
245
|
description: 'End-to-end browser testing',
|
|
256
246
|
detectWhen: ['dep:@playwright/test'],
|
|
257
247
|
install: { method: 'npm', cmd: 'bun add -D @playwright/test', dev: true },
|
|
@@ -260,7 +250,7 @@ const CATALOG = Object.freeze({
|
|
|
260
250
|
name: 'c8',
|
|
261
251
|
type: 'cli',
|
|
262
252
|
tier: 'free',
|
|
263
|
-
stage: '
|
|
253
|
+
stage: 'validate',
|
|
264
254
|
description: 'Native V8 code coverage',
|
|
265
255
|
detectWhen: [],
|
|
266
256
|
install: { method: 'npm', cmd: 'bun add -D c8', dev: true },
|
|
@@ -269,7 +259,7 @@ const CATALOG = Object.freeze({
|
|
|
269
259
|
name: 'Stryker',
|
|
270
260
|
type: 'cli',
|
|
271
261
|
tier: 'free',
|
|
272
|
-
stage: '
|
|
262
|
+
stage: 'validate',
|
|
273
263
|
description: 'Mutation testing for JavaScript',
|
|
274
264
|
detectWhen: [],
|
|
275
265
|
install: { method: 'npm', cmd: 'bun add -D @stryker-mutator/core', dev: true },
|
|
@@ -278,7 +268,7 @@ const CATALOG = Object.freeze({
|
|
|
278
268
|
name: 'Oxlint',
|
|
279
269
|
type: 'cli',
|
|
280
270
|
tier: 'free',
|
|
281
|
-
stage: '
|
|
271
|
+
stage: 'validate',
|
|
282
272
|
description: 'Blazing-fast Rust-based linter',
|
|
283
273
|
detectWhen: [],
|
|
284
274
|
install: { method: 'npm', cmd: 'bun add -D oxlint', dev: true },
|
|
@@ -341,7 +331,7 @@ const CATALOG = Object.freeze({
|
|
|
341
331
|
name: 'Changesets',
|
|
342
332
|
type: 'cli',
|
|
343
333
|
tier: 'free',
|
|
344
|
-
stage: '
|
|
334
|
+
stage: 'premerge',
|
|
345
335
|
description: 'Versioning and changelog management',
|
|
346
336
|
detectWhen: ['file:package.json'],
|
|
347
337
|
install: { method: 'npm', cmd: 'bun add -D @changesets/cli', dev: true },
|
|
@@ -350,7 +340,7 @@ const CATALOG = Object.freeze({
|
|
|
350
340
|
name: 'Release Please',
|
|
351
341
|
type: 'config',
|
|
352
342
|
tier: 'free',
|
|
353
|
-
stage: '
|
|
343
|
+
stage: 'premerge',
|
|
354
344
|
description: 'Automated release PRs by Google',
|
|
355
345
|
detectWhen: [],
|
|
356
346
|
install: { method: 'config', cmd: 'GitHub Action (google-github-actions/release-please-action)' },
|
package/lib/workflow-profiles.js
CHANGED
|
@@ -12,29 +12,26 @@
|
|
|
12
12
|
const PROFILES = {
|
|
13
13
|
critical: {
|
|
14
14
|
name: 'Critical (Maximum Rigor)',
|
|
15
|
-
stages: ['/status', '/
|
|
15
|
+
stages: ['/status', '/plan', '/dev', '/validate', '/ship', '/review', '/premerge', '/verify'],
|
|
16
16
|
tdd: 'strict',
|
|
17
17
|
research: 'required',
|
|
18
|
-
openspec: 'required-strategic',
|
|
19
18
|
description: 'Security, auth, payments, data integrity, breaking changes',
|
|
20
19
|
keywords: ['auth', 'security', 'payment', 'crypto', 'password', 'token', 'session', 'data-migration', 'breaking']
|
|
21
20
|
},
|
|
22
21
|
|
|
23
22
|
standard: {
|
|
24
23
|
name: 'Standard (Full Workflow)',
|
|
25
|
-
stages: ['/status', '/plan', '/dev', '/validate', '/ship', '/
|
|
24
|
+
stages: ['/status', '/plan', '/dev', '/validate', '/ship', '/review', '/premerge'],
|
|
26
25
|
tdd: 'required',
|
|
27
26
|
research: 'optional',
|
|
28
|
-
openspec: 'strategic',
|
|
29
27
|
description: 'Most features and non-critical enhancements'
|
|
30
28
|
},
|
|
31
29
|
|
|
32
30
|
simple: {
|
|
33
31
|
name: 'Simple (Streamlined)',
|
|
34
|
-
stages: ['/dev', '/validate', '/ship', '/
|
|
32
|
+
stages: ['/dev', '/validate', '/ship', '/premerge'],
|
|
35
33
|
tdd: 'recommended',
|
|
36
34
|
research: 'skip',
|
|
37
|
-
openspec: 'no',
|
|
38
35
|
description: 'UI tweaks, small improvements, minor fixes'
|
|
39
36
|
},
|
|
40
37
|
|
|
@@ -43,26 +40,23 @@ const PROFILES = {
|
|
|
43
40
|
stages: ['/dev', '/validate', '/ship'],
|
|
44
41
|
tdd: 'required', // Must reproduce bug
|
|
45
42
|
research: 'skip',
|
|
46
|
-
openspec: 'no',
|
|
47
43
|
description: 'Production bugs, urgent fixes',
|
|
48
44
|
keywords: ['urgent', 'production', 'emergency', 'hotfix', 'critical']
|
|
49
45
|
},
|
|
50
46
|
|
|
51
47
|
docs: {
|
|
52
48
|
name: 'Docs (Documentation Only)',
|
|
53
|
-
stages: ['/verify', '/ship', '/
|
|
49
|
+
stages: ['/verify', '/ship', '/premerge'],
|
|
54
50
|
tdd: 'no',
|
|
55
51
|
research: 'no',
|
|
56
|
-
openspec: 'no',
|
|
57
52
|
description: 'Documentation updates, README changes'
|
|
58
53
|
},
|
|
59
54
|
|
|
60
55
|
refactor: {
|
|
61
56
|
name: 'Refactor (Behavior-Preserving)',
|
|
62
|
-
stages: ['/plan', '/dev', '/validate', '/ship', '/
|
|
57
|
+
stages: ['/plan', '/dev', '/validate', '/ship', '/premerge'],
|
|
63
58
|
tdd: 'strict', // Must preserve behavior
|
|
64
59
|
research: 'optional',
|
|
65
|
-
openspec: 'architectural',
|
|
66
60
|
description: 'Code cleanup, optimization, behavior-preserving improvements'
|
|
67
61
|
}
|
|
68
62
|
};
|