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
|
@@ -1,294 +1,294 @@
|
|
|
1
|
-
const { scoreImportDependencies } = require('./import-detector.js');
|
|
2
|
-
const { parseTaskFile } = require('./task-parser.js');
|
|
3
|
-
const { scoreContractDependencies } = require('./contract-detector.js');
|
|
4
|
-
const { scoreBehavioralDependencies } = require('./behavior-detector.js');
|
|
5
|
-
const { WEIGHTS: RUBRIC_WEIGHTS, aggregateRubricScores, needsUserEscalation } = require('./rubric.js');
|
|
6
|
-
const { normalizeRepoPath } = require('./path-utils.js');
|
|
7
|
-
|
|
8
|
-
const CONFIDENCE_THRESHOLD = 0.7;
|
|
9
|
-
const DETECTOR_KEYS = Object.freeze([
|
|
10
|
-
'importCallChain',
|
|
11
|
-
'contractDependencies',
|
|
12
|
-
'behavioralDependencies',
|
|
13
|
-
]);
|
|
14
|
-
|
|
15
|
-
function createZeroedDetectorMap() {
|
|
16
|
-
return DETECTOR_KEYS.reduce((result, key) => {
|
|
17
|
-
result[key] = 0;
|
|
18
|
-
return result;
|
|
19
|
-
}, {});
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function createEmptyDetectorEvidence() {
|
|
23
|
-
return DETECTOR_KEYS.reduce((result, key) => {
|
|
24
|
-
result[key] = [];
|
|
25
|
-
return result;
|
|
26
|
-
}, {});
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function normalizeContractToken(contract) {
|
|
30
|
-
if (typeof contract !== 'string') {
|
|
31
|
-
return null;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const separatorIndex = contract.indexOf(':');
|
|
35
|
-
if (separatorIndex === -1) {
|
|
36
|
-
return contract;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
return `${normalizeRepoPath(contract.slice(0, separatorIndex))}${contract.slice(separatorIndex)}`;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function normalizeContractNotes(notes = '') {
|
|
43
|
-
return notes
|
|
44
|
-
.split(/\r?\n/)
|
|
45
|
-
.map((line) => {
|
|
46
|
-
const marker = ': ';
|
|
47
|
-
if (!line.startsWith('contracts@') || !line.includes(marker)) {
|
|
48
|
-
return line;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const separatorIndex = line.indexOf(marker);
|
|
52
|
-
const prefix = line.slice(0, separatorIndex + marker.length);
|
|
53
|
-
const payload = line.slice(separatorIndex + marker.length);
|
|
54
|
-
const normalizedPayload = payload
|
|
55
|
-
.split(/\s+/)
|
|
56
|
-
.map(normalizeContractToken)
|
|
57
|
-
.join(' ');
|
|
58
|
-
return `${prefix}${normalizedPayload}`;
|
|
59
|
-
})
|
|
60
|
-
.join('\n');
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function normalizeIssue(issue, label) {
|
|
64
|
-
if (!issue || typeof issue !== 'object') {
|
|
65
|
-
throw new Error(`${label} is required`);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if (!issue.id || typeof issue.id !== 'string') {
|
|
69
|
-
throw new Error(`${label} id is required`);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
return {
|
|
73
|
-
id: issue.id,
|
|
74
|
-
title: typeof issue.title === 'string' ? issue.title : '',
|
|
75
|
-
description: typeof issue.description === 'string' ? issue.description : '',
|
|
76
|
-
status: typeof issue.status === 'string' ? issue.status : '',
|
|
77
|
-
contracts: Array.isArray(issue.contracts)
|
|
78
|
-
? issue.contracts.map(normalizeContractToken).filter((contract) => typeof contract === 'string')
|
|
79
|
-
: [],
|
|
80
|
-
files: Array.isArray(issue.files)
|
|
81
|
-
? issue.files
|
|
82
|
-
.filter((file) => typeof file === 'string')
|
|
83
|
-
.map((file) => normalizeRepoPath(file))
|
|
84
|
-
: [],
|
|
85
|
-
notes: typeof issue.notes === 'string' ? normalizeContractNotes(issue.notes) : '',
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function normalizePhase3Input(input = {}) {
|
|
90
|
-
const currentIssue = normalizeIssue(input.currentIssue, 'Current issue');
|
|
91
|
-
const openIssues = Array.isArray(input.openIssues)
|
|
92
|
-
? input.openIssues.map((issue) => normalizeIssue(issue, 'Open issue'))
|
|
93
|
-
: [];
|
|
94
|
-
const taskContext = input.taskContext ?? parseTaskFile(input.taskFile);
|
|
95
|
-
|
|
96
|
-
return {
|
|
97
|
-
currentIssue,
|
|
98
|
-
openIssues,
|
|
99
|
-
taskContext,
|
|
100
|
-
repositoryRoot: typeof input.repositoryRoot === 'string' && input.repositoryRoot
|
|
101
|
-
? input.repositoryRoot
|
|
102
|
-
: process.cwd(),
|
|
103
|
-
thresholds: {
|
|
104
|
-
confidence: CONFIDENCE_THRESHOLD,
|
|
105
|
-
},
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
function createScaffoldResult(normalizedInput) {
|
|
110
|
-
const detectorScores = createZeroedDetectorMap();
|
|
111
|
-
|
|
112
|
-
return {
|
|
113
|
-
currentIssue: normalizedInput.currentIssue,
|
|
114
|
-
taskContext: normalizedInput.taskContext,
|
|
115
|
-
issues: [],
|
|
116
|
-
scores: {
|
|
117
|
-
...detectorScores,
|
|
118
|
-
rubric: 0,
|
|
119
|
-
},
|
|
120
|
-
rubric: {
|
|
121
|
-
score: 0,
|
|
122
|
-
threshold: normalizedInput.thresholds.confidence,
|
|
123
|
-
summary: 'No detector findings yet.',
|
|
124
|
-
weights: RUBRIC_WEIGHTS,
|
|
125
|
-
reasons: [],
|
|
126
|
-
},
|
|
127
|
-
detectorEvidence: createEmptyDetectorEvidence(),
|
|
128
|
-
detectorConflicts: [],
|
|
129
|
-
importCallChain: {
|
|
130
|
-
score: 0,
|
|
131
|
-
evidence: [],
|
|
132
|
-
findings: [],
|
|
133
|
-
},
|
|
134
|
-
contractDependencies: {
|
|
135
|
-
score: 0,
|
|
136
|
-
evidence: [],
|
|
137
|
-
findings: [],
|
|
138
|
-
},
|
|
139
|
-
behavioralDependencies: {
|
|
140
|
-
score: 0,
|
|
141
|
-
evidence: [],
|
|
142
|
-
findings: [],
|
|
143
|
-
uncertain: false,
|
|
144
|
-
},
|
|
145
|
-
confidence: {
|
|
146
|
-
score: 1,
|
|
147
|
-
threshold: normalizedInput.thresholds.confidence,
|
|
148
|
-
belowThreshold: false,
|
|
149
|
-
reasons: [],
|
|
150
|
-
},
|
|
151
|
-
proposals: [],
|
|
152
|
-
needsUserDecision: false,
|
|
153
|
-
metadata: {
|
|
154
|
-
analyzerVersion: 'phase3-v1-scaffold',
|
|
155
|
-
parser: '@babel/parser',
|
|
156
|
-
},
|
|
157
|
-
};
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
function inferDetectorType(finding) {
|
|
161
|
-
if (finding.behavioralSignal) {
|
|
162
|
-
return 'behavioralDependencies';
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
const firstEvidenceType = finding.evidence?.[0]?.type;
|
|
166
|
-
if (firstEvidenceType === 'contract') {
|
|
167
|
-
return 'contractDependencies';
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
return 'importCallChain';
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
function formatDetectorList(detectorTypes) {
|
|
174
|
-
const labels = detectorTypes.map((type) => {
|
|
175
|
-
if (type === 'importCallChain') {
|
|
176
|
-
return 'import/call-chain evidence';
|
|
177
|
-
}
|
|
178
|
-
if (type === 'contractDependencies') {
|
|
179
|
-
return 'contract evidence';
|
|
180
|
-
}
|
|
181
|
-
return 'behavioral evidence';
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
return labels.join(', ');
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
function buildDependencyProposals(normalizedInput, findings, rubricResult) {
|
|
188
|
-
if (findings.length === 0) {
|
|
189
|
-
return [];
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
const groupedFindings = new Map();
|
|
193
|
-
for (const finding of findings) {
|
|
194
|
-
const entry = groupedFindings.get(finding.targetIssueId) ?? {
|
|
195
|
-
targetIssueId: finding.targetIssueId,
|
|
196
|
-
detectorTypes: new Set(),
|
|
197
|
-
hasBehavioralUncertainty: false,
|
|
198
|
-
};
|
|
199
|
-
entry.detectorTypes.add(inferDetectorType(finding));
|
|
200
|
-
entry.hasBehavioralUncertainty = entry.hasBehavioralUncertainty || Boolean(finding.uncertain);
|
|
201
|
-
groupedFindings.set(finding.targetIssueId, entry);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
return Array.from(groupedFindings.values()).map((entry) => {
|
|
205
|
-
const detectorTypes = Array.from(entry.detectorTypes);
|
|
206
|
-
const reasons = [
|
|
207
|
-
`${entry.targetIssueId} appears to consume or rely on logic being changed in ${normalizedInput.currentIssue.id} through ${formatDetectorList(detectorTypes)}.`,
|
|
208
|
-
];
|
|
209
|
-
const cons = [
|
|
210
|
-
'Adds coordination overhead and may delay the dependent issue.',
|
|
211
|
-
];
|
|
212
|
-
|
|
213
|
-
if (entry.hasBehavioralUncertainty) {
|
|
214
|
-
reasons.push('Behavioral evidence is heuristic and should be confirmed before applying the dependency.');
|
|
215
|
-
cons.push('Behavioral evidence is heuristic and may be noisy until a human confirms the dependency.');
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
if (rubricResult.confidence.belowThreshold) {
|
|
219
|
-
reasons.push(`Overall confidence ${rubricResult.confidence.score.toFixed(2)} is below the review threshold.`);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
return {
|
|
223
|
-
action: 'add-dependency',
|
|
224
|
-
dependentIssueId: entry.targetIssueId,
|
|
225
|
-
dependsOnIssueId: normalizedInput.currentIssue.id,
|
|
226
|
-
detectorTypes,
|
|
227
|
-
requiresApproval: true,
|
|
228
|
-
confidence: rubricResult.confidence.score,
|
|
229
|
-
reasons,
|
|
230
|
-
pros: [
|
|
231
|
-
'Preserves issue independence claims by sequencing affected work explicitly.',
|
|
232
|
-
'Reduces merge-order surprises for downstream logic consumers.',
|
|
233
|
-
],
|
|
234
|
-
cons,
|
|
235
|
-
};
|
|
236
|
-
});
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
function analyzePhase3Dependencies(input = {}) {
|
|
240
|
-
const normalizedInput = normalizePhase3Input(input);
|
|
241
|
-
const result = createScaffoldResult(normalizedInput);
|
|
242
|
-
const importResult = scoreImportDependencies(normalizedInput);
|
|
243
|
-
const contractResult = scoreContractDependencies(normalizedInput);
|
|
244
|
-
const behavioralResult = scoreBehavioralDependencies(normalizedInput);
|
|
245
|
-
const rubricResult = aggregateRubricScores({
|
|
246
|
-
importCallChain: importResult,
|
|
247
|
-
contractDependencies: contractResult,
|
|
248
|
-
behavioralDependencies: behavioralResult,
|
|
249
|
-
}, {
|
|
250
|
-
confidenceThreshold: normalizedInput.thresholds.confidence,
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
result.issues = [
|
|
254
|
-
...importResult.findings,
|
|
255
|
-
...contractResult.findings,
|
|
256
|
-
...behavioralResult.findings,
|
|
257
|
-
];
|
|
258
|
-
result.scores.importCallChain = importResult.score;
|
|
259
|
-
result.scores.contractDependencies = contractResult.score;
|
|
260
|
-
result.scores.behavioralDependencies = behavioralResult.score;
|
|
261
|
-
result.scores.rubric = rubricResult.total;
|
|
262
|
-
result.rubric = rubricResult;
|
|
263
|
-
result.detectorEvidence.importCallChain = importResult.evidence;
|
|
264
|
-
result.detectorEvidence.contractDependencies = contractResult.evidence;
|
|
265
|
-
result.detectorEvidence.behavioralDependencies = behavioralResult.evidence;
|
|
266
|
-
result.detectorConflicts = rubricResult.detectorConflicts;
|
|
267
|
-
result.confidence = rubricResult.confidence;
|
|
268
|
-
result.needsUserDecision = needsUserEscalation(rubricResult);
|
|
269
|
-
result.proposals = buildDependencyProposals(normalizedInput, result.issues, rubricResult);
|
|
270
|
-
result.importCallChain = {
|
|
271
|
-
score: importResult.score,
|
|
272
|
-
evidence: importResult.evidence,
|
|
273
|
-
findings: importResult.findings,
|
|
274
|
-
};
|
|
275
|
-
result.contractDependencies = {
|
|
276
|
-
score: contractResult.score,
|
|
277
|
-
evidence: contractResult.evidence,
|
|
278
|
-
findings: contractResult.findings,
|
|
279
|
-
};
|
|
280
|
-
result.behavioralDependencies = {
|
|
281
|
-
score: behavioralResult.score,
|
|
282
|
-
evidence: behavioralResult.evidence,
|
|
283
|
-
findings: behavioralResult.findings,
|
|
284
|
-
uncertain: behavioralResult.uncertain,
|
|
285
|
-
};
|
|
286
|
-
|
|
287
|
-
return result;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
module.exports = {
|
|
291
|
-
analyzePhase3Dependencies,
|
|
292
|
-
DETECTOR_KEYS,
|
|
293
|
-
normalizePhase3Input,
|
|
294
|
-
};
|
|
1
|
+
const { scoreImportDependencies } = require('./import-detector.js');
|
|
2
|
+
const { parseTaskFile } = require('./task-parser.js');
|
|
3
|
+
const { scoreContractDependencies } = require('./contract-detector.js');
|
|
4
|
+
const { scoreBehavioralDependencies } = require('./behavior-detector.js');
|
|
5
|
+
const { WEIGHTS: RUBRIC_WEIGHTS, aggregateRubricScores, needsUserEscalation } = require('./rubric.js');
|
|
6
|
+
const { normalizeRepoPath } = require('./path-utils.js');
|
|
7
|
+
|
|
8
|
+
const CONFIDENCE_THRESHOLD = 0.7;
|
|
9
|
+
const DETECTOR_KEYS = Object.freeze([
|
|
10
|
+
'importCallChain',
|
|
11
|
+
'contractDependencies',
|
|
12
|
+
'behavioralDependencies',
|
|
13
|
+
]);
|
|
14
|
+
|
|
15
|
+
function createZeroedDetectorMap() {
|
|
16
|
+
return DETECTOR_KEYS.reduce((result, key) => {
|
|
17
|
+
result[key] = 0;
|
|
18
|
+
return result;
|
|
19
|
+
}, {});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function createEmptyDetectorEvidence() {
|
|
23
|
+
return DETECTOR_KEYS.reduce((result, key) => {
|
|
24
|
+
result[key] = [];
|
|
25
|
+
return result;
|
|
26
|
+
}, {});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function normalizeContractToken(contract) {
|
|
30
|
+
if (typeof contract !== 'string') {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const separatorIndex = contract.indexOf(':');
|
|
35
|
+
if (separatorIndex === -1) {
|
|
36
|
+
return contract;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return `${normalizeRepoPath(contract.slice(0, separatorIndex))}${contract.slice(separatorIndex)}`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function normalizeContractNotes(notes = '') {
|
|
43
|
+
return notes
|
|
44
|
+
.split(/\r?\n/)
|
|
45
|
+
.map((line) => {
|
|
46
|
+
const marker = ': ';
|
|
47
|
+
if (!line.startsWith('contracts@') || !line.includes(marker)) {
|
|
48
|
+
return line;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const separatorIndex = line.indexOf(marker);
|
|
52
|
+
const prefix = line.slice(0, separatorIndex + marker.length);
|
|
53
|
+
const payload = line.slice(separatorIndex + marker.length);
|
|
54
|
+
const normalizedPayload = payload
|
|
55
|
+
.split(/\s+/)
|
|
56
|
+
.map(normalizeContractToken)
|
|
57
|
+
.join(' ');
|
|
58
|
+
return `${prefix}${normalizedPayload}`;
|
|
59
|
+
})
|
|
60
|
+
.join('\n');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function normalizeIssue(issue, label) {
|
|
64
|
+
if (!issue || typeof issue !== 'object') {
|
|
65
|
+
throw new Error(`${label} is required`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (!issue.id || typeof issue.id !== 'string') {
|
|
69
|
+
throw new Error(`${label} id is required`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
id: issue.id,
|
|
74
|
+
title: typeof issue.title === 'string' ? issue.title : '',
|
|
75
|
+
description: typeof issue.description === 'string' ? issue.description : '',
|
|
76
|
+
status: typeof issue.status === 'string' ? issue.status : '',
|
|
77
|
+
contracts: Array.isArray(issue.contracts)
|
|
78
|
+
? issue.contracts.map(normalizeContractToken).filter((contract) => typeof contract === 'string')
|
|
79
|
+
: [],
|
|
80
|
+
files: Array.isArray(issue.files)
|
|
81
|
+
? issue.files
|
|
82
|
+
.filter((file) => typeof file === 'string')
|
|
83
|
+
.map((file) => normalizeRepoPath(file))
|
|
84
|
+
: [],
|
|
85
|
+
notes: typeof issue.notes === 'string' ? normalizeContractNotes(issue.notes) : '',
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function normalizePhase3Input(input = {}) {
|
|
90
|
+
const currentIssue = normalizeIssue(input.currentIssue, 'Current issue');
|
|
91
|
+
const openIssues = Array.isArray(input.openIssues)
|
|
92
|
+
? input.openIssues.map((issue) => normalizeIssue(issue, 'Open issue'))
|
|
93
|
+
: [];
|
|
94
|
+
const taskContext = input.taskContext ?? parseTaskFile(input.taskFile);
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
currentIssue,
|
|
98
|
+
openIssues,
|
|
99
|
+
taskContext,
|
|
100
|
+
repositoryRoot: typeof input.repositoryRoot === 'string' && input.repositoryRoot
|
|
101
|
+
? input.repositoryRoot
|
|
102
|
+
: process.cwd(),
|
|
103
|
+
thresholds: {
|
|
104
|
+
confidence: CONFIDENCE_THRESHOLD,
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function createScaffoldResult(normalizedInput) {
|
|
110
|
+
const detectorScores = createZeroedDetectorMap();
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
currentIssue: normalizedInput.currentIssue,
|
|
114
|
+
taskContext: normalizedInput.taskContext,
|
|
115
|
+
issues: [],
|
|
116
|
+
scores: {
|
|
117
|
+
...detectorScores,
|
|
118
|
+
rubric: 0,
|
|
119
|
+
},
|
|
120
|
+
rubric: {
|
|
121
|
+
score: 0,
|
|
122
|
+
threshold: normalizedInput.thresholds.confidence,
|
|
123
|
+
summary: 'No detector findings yet.',
|
|
124
|
+
weights: RUBRIC_WEIGHTS,
|
|
125
|
+
reasons: [],
|
|
126
|
+
},
|
|
127
|
+
detectorEvidence: createEmptyDetectorEvidence(),
|
|
128
|
+
detectorConflicts: [],
|
|
129
|
+
importCallChain: {
|
|
130
|
+
score: 0,
|
|
131
|
+
evidence: [],
|
|
132
|
+
findings: [],
|
|
133
|
+
},
|
|
134
|
+
contractDependencies: {
|
|
135
|
+
score: 0,
|
|
136
|
+
evidence: [],
|
|
137
|
+
findings: [],
|
|
138
|
+
},
|
|
139
|
+
behavioralDependencies: {
|
|
140
|
+
score: 0,
|
|
141
|
+
evidence: [],
|
|
142
|
+
findings: [],
|
|
143
|
+
uncertain: false,
|
|
144
|
+
},
|
|
145
|
+
confidence: {
|
|
146
|
+
score: 1,
|
|
147
|
+
threshold: normalizedInput.thresholds.confidence,
|
|
148
|
+
belowThreshold: false,
|
|
149
|
+
reasons: [],
|
|
150
|
+
},
|
|
151
|
+
proposals: [],
|
|
152
|
+
needsUserDecision: false,
|
|
153
|
+
metadata: {
|
|
154
|
+
analyzerVersion: 'phase3-v1-scaffold',
|
|
155
|
+
parser: '@babel/parser',
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function inferDetectorType(finding) {
|
|
161
|
+
if (finding.behavioralSignal) {
|
|
162
|
+
return 'behavioralDependencies';
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const firstEvidenceType = finding.evidence?.[0]?.type;
|
|
166
|
+
if (firstEvidenceType === 'contract') {
|
|
167
|
+
return 'contractDependencies';
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return 'importCallChain';
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function formatDetectorList(detectorTypes) {
|
|
174
|
+
const labels = detectorTypes.map((type) => {
|
|
175
|
+
if (type === 'importCallChain') {
|
|
176
|
+
return 'import/call-chain evidence';
|
|
177
|
+
}
|
|
178
|
+
if (type === 'contractDependencies') {
|
|
179
|
+
return 'contract evidence';
|
|
180
|
+
}
|
|
181
|
+
return 'behavioral evidence';
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
return labels.join(', ');
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function buildDependencyProposals(normalizedInput, findings, rubricResult) {
|
|
188
|
+
if (findings.length === 0) {
|
|
189
|
+
return [];
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const groupedFindings = new Map();
|
|
193
|
+
for (const finding of findings) {
|
|
194
|
+
const entry = groupedFindings.get(finding.targetIssueId) ?? {
|
|
195
|
+
targetIssueId: finding.targetIssueId,
|
|
196
|
+
detectorTypes: new Set(),
|
|
197
|
+
hasBehavioralUncertainty: false,
|
|
198
|
+
};
|
|
199
|
+
entry.detectorTypes.add(inferDetectorType(finding));
|
|
200
|
+
entry.hasBehavioralUncertainty = entry.hasBehavioralUncertainty || Boolean(finding.uncertain);
|
|
201
|
+
groupedFindings.set(finding.targetIssueId, entry);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return Array.from(groupedFindings.values()).map((entry) => {
|
|
205
|
+
const detectorTypes = Array.from(entry.detectorTypes);
|
|
206
|
+
const reasons = [
|
|
207
|
+
`${entry.targetIssueId} appears to consume or rely on logic being changed in ${normalizedInput.currentIssue.id} through ${formatDetectorList(detectorTypes)}.`,
|
|
208
|
+
];
|
|
209
|
+
const cons = [
|
|
210
|
+
'Adds coordination overhead and may delay the dependent issue.',
|
|
211
|
+
];
|
|
212
|
+
|
|
213
|
+
if (entry.hasBehavioralUncertainty) {
|
|
214
|
+
reasons.push('Behavioral evidence is heuristic and should be confirmed before applying the dependency.');
|
|
215
|
+
cons.push('Behavioral evidence is heuristic and may be noisy until a human confirms the dependency.');
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (rubricResult.confidence.belowThreshold) {
|
|
219
|
+
reasons.push(`Overall confidence ${rubricResult.confidence.score.toFixed(2)} is below the review threshold.`);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return {
|
|
223
|
+
action: 'add-dependency',
|
|
224
|
+
dependentIssueId: entry.targetIssueId,
|
|
225
|
+
dependsOnIssueId: normalizedInput.currentIssue.id,
|
|
226
|
+
detectorTypes,
|
|
227
|
+
requiresApproval: true,
|
|
228
|
+
confidence: rubricResult.confidence.score,
|
|
229
|
+
reasons,
|
|
230
|
+
pros: [
|
|
231
|
+
'Preserves issue independence claims by sequencing affected work explicitly.',
|
|
232
|
+
'Reduces merge-order surprises for downstream logic consumers.',
|
|
233
|
+
],
|
|
234
|
+
cons,
|
|
235
|
+
};
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function analyzePhase3Dependencies(input = {}) {
|
|
240
|
+
const normalizedInput = normalizePhase3Input(input);
|
|
241
|
+
const result = createScaffoldResult(normalizedInput);
|
|
242
|
+
const importResult = scoreImportDependencies(normalizedInput);
|
|
243
|
+
const contractResult = scoreContractDependencies(normalizedInput);
|
|
244
|
+
const behavioralResult = scoreBehavioralDependencies(normalizedInput);
|
|
245
|
+
const rubricResult = aggregateRubricScores({
|
|
246
|
+
importCallChain: importResult,
|
|
247
|
+
contractDependencies: contractResult,
|
|
248
|
+
behavioralDependencies: behavioralResult,
|
|
249
|
+
}, {
|
|
250
|
+
confidenceThreshold: normalizedInput.thresholds.confidence,
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
result.issues = [
|
|
254
|
+
...importResult.findings,
|
|
255
|
+
...contractResult.findings,
|
|
256
|
+
...behavioralResult.findings,
|
|
257
|
+
];
|
|
258
|
+
result.scores.importCallChain = importResult.score;
|
|
259
|
+
result.scores.contractDependencies = contractResult.score;
|
|
260
|
+
result.scores.behavioralDependencies = behavioralResult.score;
|
|
261
|
+
result.scores.rubric = rubricResult.total;
|
|
262
|
+
result.rubric = rubricResult;
|
|
263
|
+
result.detectorEvidence.importCallChain = importResult.evidence;
|
|
264
|
+
result.detectorEvidence.contractDependencies = contractResult.evidence;
|
|
265
|
+
result.detectorEvidence.behavioralDependencies = behavioralResult.evidence;
|
|
266
|
+
result.detectorConflicts = rubricResult.detectorConflicts;
|
|
267
|
+
result.confidence = rubricResult.confidence;
|
|
268
|
+
result.needsUserDecision = needsUserEscalation(rubricResult);
|
|
269
|
+
result.proposals = buildDependencyProposals(normalizedInput, result.issues, rubricResult);
|
|
270
|
+
result.importCallChain = {
|
|
271
|
+
score: importResult.score,
|
|
272
|
+
evidence: importResult.evidence,
|
|
273
|
+
findings: importResult.findings,
|
|
274
|
+
};
|
|
275
|
+
result.contractDependencies = {
|
|
276
|
+
score: contractResult.score,
|
|
277
|
+
evidence: contractResult.evidence,
|
|
278
|
+
findings: contractResult.findings,
|
|
279
|
+
};
|
|
280
|
+
result.behavioralDependencies = {
|
|
281
|
+
score: behavioralResult.score,
|
|
282
|
+
evidence: behavioralResult.evidence,
|
|
283
|
+
findings: behavioralResult.findings,
|
|
284
|
+
uncertain: behavioralResult.uncertain,
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
return result;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
module.exports = {
|
|
291
|
+
analyzePhase3Dependencies,
|
|
292
|
+
DETECTOR_KEYS,
|
|
293
|
+
normalizePhase3Input,
|
|
294
|
+
};
|