forge-workflow 0.0.2 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (175) hide show
  1. package/.claude/commands/dev.md +26 -0
  2. package/.claude/commands/plan.md +141 -9
  3. package/.claude/commands/premerge.md +0 -3
  4. package/.claude/commands/rollback.md +4 -4
  5. package/.claude/commands/ship.md +71 -41
  6. package/.claude/commands/status.md +9 -38
  7. package/.claude/commands/validate.md +47 -2
  8. package/.cline/workflows/dev.md +337 -0
  9. package/.cline/workflows/plan.md +518 -0
  10. package/.cline/workflows/premerge.md +173 -0
  11. package/.cline/workflows/research.md +39 -0
  12. package/.cline/workflows/review.md +439 -0
  13. package/.cline/workflows/rollback.md +718 -0
  14. package/.cline/workflows/ship.md +161 -0
  15. package/.cline/workflows/sonarcloud.md +146 -0
  16. package/.cline/workflows/status.md +45 -0
  17. package/.cline/workflows/validate.md +279 -0
  18. package/.cline/workflows/verify.md +218 -0
  19. package/.codex/config.toml +11 -0
  20. package/.codex/skills/dev/SKILL.md +340 -0
  21. package/.codex/skills/plan/SKILL.md +521 -0
  22. package/.codex/skills/premerge/SKILL.md +176 -0
  23. package/.codex/skills/research/SKILL.md +42 -0
  24. package/.codex/skills/review/SKILL.md +442 -0
  25. package/.codex/skills/rollback/SKILL.md +721 -0
  26. package/.codex/skills/ship/SKILL.md +164 -0
  27. package/.codex/skills/sonarcloud/SKILL.md +149 -0
  28. package/.codex/skills/status/SKILL.md +48 -0
  29. package/.codex/skills/validate/SKILL.md +282 -0
  30. package/.codex/skills/verify/SKILL.md +221 -0
  31. package/.cursor/commands/dev.md +337 -0
  32. package/.cursor/commands/plan.md +518 -0
  33. package/.cursor/commands/premerge.md +173 -0
  34. package/.cursor/commands/research.md +39 -0
  35. package/.cursor/commands/review.md +439 -0
  36. package/.cursor/commands/rollback.md +718 -0
  37. package/.cursor/commands/ship.md +161 -0
  38. package/.cursor/commands/sonarcloud.md +146 -0
  39. package/.cursor/commands/status.md +45 -0
  40. package/.cursor/commands/validate.md +279 -0
  41. package/.cursor/commands/verify.md +218 -0
  42. package/.cursor/hooks/state/continual-learning-index.json +19 -0
  43. package/.cursor/hooks/state/continual-learning.json +8 -0
  44. package/.cursor/rules/permissions-guidance.mdc +37 -0
  45. package/.github/prompts/dev.prompt.md +342 -0
  46. package/.github/prompts/plan.prompt.md +523 -0
  47. package/.github/prompts/premerge.prompt.md +178 -0
  48. package/.github/prompts/research.prompt.md +44 -0
  49. package/.github/prompts/review.prompt.md +444 -0
  50. package/.github/prompts/rollback.prompt.md +723 -0
  51. package/.github/prompts/ship.prompt.md +166 -0
  52. package/.github/prompts/sonarcloud.prompt.md +151 -0
  53. package/.github/prompts/status.prompt.md +50 -0
  54. package/.github/prompts/validate.prompt.md +284 -0
  55. package/.github/prompts/verify.prompt.md +223 -0
  56. package/.kilocode/workflows/dev.md +341 -0
  57. package/.kilocode/workflows/plan.md +522 -0
  58. package/.kilocode/workflows/premerge.md +177 -0
  59. package/.kilocode/workflows/research.md +43 -0
  60. package/.kilocode/workflows/review.md +443 -0
  61. package/.kilocode/workflows/rollback.md +722 -0
  62. package/.kilocode/workflows/ship.md +165 -0
  63. package/.kilocode/workflows/sonarcloud.md +150 -0
  64. package/.kilocode/workflows/status.md +49 -0
  65. package/.kilocode/workflows/validate.md +283 -0
  66. package/.kilocode/workflows/verify.md +222 -0
  67. package/.opencode/commands/dev.md +340 -0
  68. package/.opencode/commands/plan.md +521 -0
  69. package/.opencode/commands/premerge.md +176 -0
  70. package/.opencode/commands/research.md +42 -0
  71. package/.opencode/commands/review.md +442 -0
  72. package/.opencode/commands/rollback.md +721 -0
  73. package/.opencode/commands/ship.md +164 -0
  74. package/.opencode/commands/sonarcloud.md +149 -0
  75. package/.opencode/commands/status.md +48 -0
  76. package/.opencode/commands/validate.md +282 -0
  77. package/.opencode/commands/verify.md +221 -0
  78. package/.roo/commands/dev.md +341 -0
  79. package/.roo/commands/plan.md +522 -0
  80. package/.roo/commands/premerge.md +177 -0
  81. package/.roo/commands/research.md +43 -0
  82. package/.roo/commands/review.md +443 -0
  83. package/.roo/commands/rollback.md +722 -0
  84. package/.roo/commands/ship.md +165 -0
  85. package/.roo/commands/sonarcloud.md +150 -0
  86. package/.roo/commands/status.md +49 -0
  87. package/.roo/commands/validate.md +283 -0
  88. package/.roo/commands/verify.md +222 -0
  89. package/AGENTS.md +7 -1
  90. package/CLAUDE.md +5 -4
  91. package/README.md +21 -19
  92. package/bin/{forge-validate.js → forge-preflight.js} +21 -15
  93. package/bin/forge.js +209 -138
  94. package/docs/AGENT_INSTALL_PROMPT.md +1 -1
  95. package/docs/BEADS_GITHUB_SYNC.md +251 -0
  96. package/docs/ENHANCED_ONBOARDING.md +8 -8
  97. package/docs/EXAMPLES.md +4 -4
  98. package/docs/GREPTILE_SETUP.md +1 -1
  99. package/docs/MANUAL_REVIEW_GUIDE.md +1 -1
  100. package/docs/ROADMAP.md +6 -6
  101. package/docs/SETUP.md +1 -2
  102. package/docs/TOOLCHAIN.md +15 -234
  103. package/docs/VALIDATION.md +11 -11
  104. package/install.sh +33 -39
  105. package/lib/agents-config.js +3 -3
  106. package/lib/commands/plan.js +11 -15
  107. package/lib/commands/recommend.js +2 -2
  108. package/lib/dep-guard/analyzer.js +294 -0
  109. package/lib/dep-guard/behavior-detector.js +98 -0
  110. package/lib/dep-guard/contract-detector.js +162 -0
  111. package/lib/dep-guard/import-detector.js +498 -0
  112. package/lib/dep-guard/path-utils.js +13 -0
  113. package/lib/dep-guard/rubric.js +120 -0
  114. package/lib/dep-guard/task-parser.js +318 -0
  115. package/lib/detect-agent.js +191 -0
  116. package/lib/detect-worktree.js +47 -0
  117. package/lib/file-hash.js +26 -0
  118. package/lib/plugin-catalog.js +18 -28
  119. package/lib/setup-action-log.js +139 -0
  120. package/lib/setup-summary-renderer.js +106 -0
  121. package/lib/setup.js +75 -1
  122. package/lib/workflow-profiles.js +5 -11
  123. package/package.json +17 -7
  124. package/skills/parallel-deep-research/SKILL.md +108 -0
  125. package/skills/parallel-deep-research/evals/README.md +27 -0
  126. package/skills/parallel-deep-research/evals/evals.json +62 -0
  127. package/skills/sonarcloud-analysis/SKILL.md +171 -0
  128. package/skills/sonarcloud-analysis/evals/README.md +27 -0
  129. package/skills/sonarcloud-analysis/evals/evals.json +50 -0
  130. package/skills/sonarcloud-analysis/references/api-reference.md +466 -0
  131. package/docs/WORKFLOW.md +0 -400
  132. package/docs/planning/PROGRESS.md +0 -396
  133. package/docs/plans/.gitkeep +0 -0
  134. package/docs/plans/2026-02-27-forge-test-suite-v2-decisions.md +0 -21
  135. package/docs/plans/2026-02-27-forge-test-suite-v2-design.md +0 -362
  136. package/docs/plans/2026-02-27-forge-test-suite-v2-tasks.md +0 -343
  137. package/docs/plans/2026-03-02-superpowers-gaps-decisions.md +0 -26
  138. package/docs/plans/2026-03-02-superpowers-gaps-design.md +0 -239
  139. package/docs/plans/2026-03-02-superpowers-gaps-tasks.md +0 -260
  140. package/docs/plans/2026-03-04-agent-command-parity-design.md +0 -163
  141. package/docs/plans/2026-03-04-verify-worktree-cleanup-decisions.md +0 -7
  142. package/docs/plans/2026-03-04-verify-worktree-cleanup-design.md +0 -165
  143. package/docs/plans/2026-03-05-forge-uto-decisions.md +0 -6
  144. package/docs/plans/2026-03-05-forge-uto-design.md +0 -116
  145. package/docs/plans/2026-03-05-forge-uto-tasks.md +0 -244
  146. package/docs/plans/2026-03-10-command-creator-and-eval-decisions.md +0 -52
  147. package/docs/plans/2026-03-10-command-creator-and-eval-design.md +0 -350
  148. package/docs/plans/2026-03-10-command-creator-and-eval-tasks.md +0 -426
  149. package/docs/plans/2026-03-10-stale-workflow-refs-decisions.md +0 -8
  150. package/docs/plans/2026-03-10-stale-workflow-refs-design.md +0 -80
  151. package/docs/plans/2026-03-10-stale-workflow-refs-tasks.md +0 -90
  152. package/docs/plans/2026-03-14-beads-plan-context-decisions.md +0 -9
  153. package/docs/plans/2026-03-14-beads-plan-context-design.md +0 -171
  154. package/docs/plans/2026-03-14-beads-plan-context-tasks.md +0 -160
  155. package/docs/plans/2026-03-14-skill-eval-loop-decisions.md +0 -33
  156. package/docs/plans/2026-03-14-skill-eval-loop-design.md +0 -118
  157. package/docs/plans/2026-03-14-skill-eval-loop-results.md +0 -78
  158. package/docs/plans/2026-03-14-skill-eval-loop-tasks.md +0 -160
  159. package/docs/plans/2026-03-15-agent-command-parity-v2-decisions.md +0 -11
  160. package/docs/plans/2026-03-15-agent-command-parity-v2-design.md +0 -145
  161. package/docs/plans/2026-03-15-agent-command-parity-v2-tasks.md +0 -211
  162. package/docs/research/TEMPLATE.md +0 -292
  163. package/docs/research/advanced-testing.md +0 -297
  164. package/docs/research/agent-permissions.md +0 -167
  165. package/docs/research/dependency-chain.md +0 -328
  166. package/docs/research/forge-workflow-v2.md +0 -550
  167. package/docs/research/plugin-architecture.md +0 -772
  168. package/docs/research/pr4-cli-automation.md +0 -326
  169. package/docs/research/premerge-verify-restructure.md +0 -205
  170. package/docs/research/skills-restructure.md +0 -508
  171. package/docs/research/sonarcloud-perfection-plan.md +0 -166
  172. package/docs/research/sonarcloud-quality-gate.md +0 -184
  173. package/docs/research/superpowers-integration.md +0 -403
  174. package/docs/research/superpowers.md +0 -319
  175. package/docs/research/test-environment.md +0 -519
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Plan Command - OpenSpec & Beads Integration
2
+ * Plan Command - Beads Integration
3
3
  * Creates implementation plan after research is complete
4
4
  *
5
5
  * Security: Uses execFileSync instead of exec/execSync to prevent command injection
@@ -123,18 +123,18 @@ function readResearchDoc(featureSlug) {
123
123
  * 3. Default to tactical if no clear signals
124
124
  *
125
125
  * @param {string} researchContent - Research document markdown content
126
- * @returns {{type: 'tactical'|'strategic', requiresOpenSpec: boolean, reason: string}} Scope analysis result
126
+ * @returns {{type: 'tactical'|'strategic', requiresDesignDoc: boolean, reason: string}} Scope analysis result
127
127
  * @example
128
128
  * const scope = detectScope(researchMarkdown);
129
129
  * if (scope.type === 'strategic') {
130
- * console.log('Requires OpenSpec proposal:', scope.reason);
130
+ * console.log('Requires design doc:', scope.reason);
131
131
  * }
132
132
  */
133
133
  function detectScope(researchContent) {
134
134
  if (!researchContent || typeof researchContent !== 'string') {
135
135
  return {
136
136
  type: 'tactical',
137
- requiresOpenSpec: false,
137
+ requiresDesignDoc: false,
138
138
  reason: 'Invalid research content (defaulting to tactical)',
139
139
  };
140
140
  }
@@ -166,7 +166,7 @@ function detectScope(researchContent) {
166
166
  const type = scopeMatch[1].toLowerCase();
167
167
  return {
168
168
  type,
169
- requiresOpenSpec: type === 'strategic',
169
+ requiresDesignDoc: type === 'strategic',
170
170
  reason: `Explicit scope declaration: ${scopeMatch[1]}`,
171
171
  };
172
172
  }
@@ -190,7 +190,7 @@ function detectScope(researchContent) {
190
190
  if (foundKeywords.length > 0) {
191
191
  return {
192
192
  type: 'strategic',
193
- requiresOpenSpec: true,
193
+ requiresDesignDoc: true,
194
194
  reason: `Strategic keywords detected: ${foundKeywords.join(', ')}`,
195
195
  };
196
196
  }
@@ -198,7 +198,7 @@ function detectScope(researchContent) {
198
198
  // Default to tactical (safe fallback)
199
199
  return {
200
200
  type: 'tactical',
201
- requiresOpenSpec: false,
201
+ requiresDesignDoc: false,
202
202
  reason: 'No strategic indicators found (tactical by default)',
203
203
  };
204
204
  }
@@ -244,7 +244,7 @@ function createBeadsIssue(featureName, researchPath, scope) {
244
244
  if (!slugValidation.valid) {
245
245
  return { success: false, error: `Cannot generate valid slug from feature name: ${slugValidation.error}` };
246
246
  }
247
- description += `\n\nOpenSpec: openspec/changes/${featureSlug}/`;
247
+ description += `\n\nDesign: docs/plans/${featureSlug}-design.md`;
248
248
  }
249
249
 
250
250
  // Execute bd create command using execFileSync for safety (OWASP A03)
@@ -585,12 +585,9 @@ function applyYAGNIFilter({ task, tasks, designDoc } = {}) {
585
585
  * Strategic workflow (architecture changes, >1 day):
586
586
  * 1. Read research document
587
587
  * 2. Detect scope (strategic)
588
- * 3. Create Beads issue with OpenSpec link
588
+ * 3. Create Beads issue with design doc link
589
589
  * 4. Create feature branch
590
- * 5. Generate OpenSpec proposal (proposal.md, tasks.md, design.md)
591
- * 6. Commit and push proposal
592
- * 7. Create proposal PR
593
- * → Next: Wait for proposal approval
590
+ * Next: Create design doc, then /dev command
594
591
  *
595
592
  * @param {string} featureName - Feature name (human-readable, e.g., "Payment Integration")
596
593
  * @returns {Promise<{
@@ -598,8 +595,7 @@ function applyYAGNIFilter({ task, tasks, designDoc } = {}) {
598
595
  * scope?: 'tactical'|'strategic',
599
596
  * beadsIssueId?: string,
600
597
  * branchName?: string,
601
- * openSpecCreated?: boolean,
602
- * proposalPR?: {url: string, number: number},
598
+ *
603
599
  * summary?: string,
604
600
  * nextCommand?: string,
605
601
  * error?: string
@@ -20,10 +20,10 @@ const STAGE_NAMES = {
20
20
  research: 'Research',
21
21
  plan: 'Plan',
22
22
  dev: 'Dev',
23
- check: 'Check',
23
+ validate: 'Validate',
24
24
  ship: 'Ship',
25
25
  review: 'Review',
26
- merge: 'Merge',
26
+ premerge: 'Premerge',
27
27
  };
28
28
 
29
29
  /**
@@ -0,0 +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
+ };
@@ -0,0 +1,98 @@
1
+ const STOP_WORDS = new Set([
2
+ 'the', 'a', 'an', 'and', 'or', 'is', 'in', 'to', 'for', 'of', 'with', 'on',
3
+ 'at', 'by', 'from', 'add', 'fix', 'update', 'implement', 'create', 'remove',
4
+ 'delete', 'make', 'use', 'get', 'set', 'run', 'test', 'check', 'this', 'that',
5
+ 'it', 'be', 'as', 'not', 'no', 'but', 'if', 'do', 'we', 'they', 'are', 'was',
6
+ 'were', 'been', 'have', 'has', 'had', 'will', 'would', 'could', 'should',
7
+ 'may', 'can', 'each', 'every', 'both', 'also', 'into', 'than', 'then', 'when',
8
+ 'where', 'which', 'what', 'how', 'why', 'who', 'its', 'new', 'first', 'last',
9
+ 'same', 'other',
10
+ ]);
11
+
12
+ const BEHAVIOR_TERMS = new Set([
13
+ 'approval', 'behavior', 'confidence', 'default', 'manual', 'ordering',
14
+ 'output', 'policy', 'review', 'rules', 'rule', 'threshold', 'validation',
15
+ 'workflow', 'state',
16
+ ]);
17
+ const STRONG_SIGNAL_TERMS = new Set([
18
+ 'approval',
19
+ 'confidence',
20
+ 'manual',
21
+ 'policy',
22
+ 'review',
23
+ 'rule',
24
+ 'rules',
25
+ 'threshold',
26
+ ]);
27
+
28
+ function tokenize(text) {
29
+ return Array.from(new Set(
30
+ text
31
+ .toLowerCase()
32
+ .split(/[^a-z0-9]+/)
33
+ .filter((term) => term.length >= 3 && !STOP_WORDS.has(term)),
34
+ ));
35
+ }
36
+
37
+ function scoreBehavioralDependencies(normalizedInput) {
38
+ const taskText = normalizedInput.taskContext.tasks
39
+ .map((task) => task.whatToImplement)
40
+ .join(' ');
41
+ const taskTerms = tokenize(taskText).filter((term) => BEHAVIOR_TERMS.has(term));
42
+
43
+ if (taskTerms.length === 0) {
44
+ return {
45
+ score: 0,
46
+ findings: [],
47
+ evidence: [],
48
+ uncertain: false,
49
+ };
50
+ }
51
+
52
+ const findings = [];
53
+ const evidence = [];
54
+
55
+ for (const issue of normalizedInput.openIssues) {
56
+ const issueText = [issue.title, issue.description, issue.notes].filter(Boolean).join(' ');
57
+ const issueTerms = new Set(tokenize(issueText));
58
+ const sharedTerms = taskTerms.filter((term) => issueTerms.has(term));
59
+
60
+ if (sharedTerms.length < 2) {
61
+ continue;
62
+ }
63
+
64
+ const strongSignalCount = sharedTerms.filter((term) => STRONG_SIGNAL_TERMS.has(term)).length;
65
+ const isUncertain = strongSignalCount < 2 || sharedTerms.length < 3;
66
+ const signal = sharedTerms.some((term) => ['rule', 'rules', 'policy', 'approval'].includes(term))
67
+ ? 'rule-change-overlap'
68
+ : 'behavior-change-overlap';
69
+ const issueEvidence = sharedTerms.map((term) => ({
70
+ type: 'behavior',
71
+ targetIssueId: issue.id,
72
+ sharedTerms: sharedTerms,
73
+ term,
74
+ scoreContribution: 1,
75
+ }));
76
+
77
+ findings.push({
78
+ sourceIssueId: normalizedInput.currentIssue.id,
79
+ targetIssueId: issue.id,
80
+ score: sharedTerms.length,
81
+ behavioralSignal: signal,
82
+ uncertain: isUncertain,
83
+ evidence: issueEvidence,
84
+ });
85
+ evidence.push(...issueEvidence);
86
+ }
87
+
88
+ return {
89
+ score: findings.reduce((total, finding) => total + finding.score, 0),
90
+ findings,
91
+ evidence,
92
+ uncertain: findings.some((finding) => finding.uncertain),
93
+ };
94
+ }
95
+
96
+ module.exports = {
97
+ scoreBehavioralDependencies,
98
+ };
@@ -0,0 +1,162 @@
1
+ const { detectContractType, extractTaskContracts } = require('./task-parser.js');
2
+ const { normalizeRepoPath } = require('./path-utils.js');
3
+
4
+ function isIdentifier(name) {
5
+ return typeof name === 'string' && /^[A-Za-z_]\w*$/.test(name);
6
+ }
7
+
8
+ function isCommandContractName(name) {
9
+ return typeof name === 'string' && /^[A-Za-z_][\w-]*$/.test(name);
10
+ }
11
+
12
+ function parseContractToken(token) {
13
+ if (typeof token !== 'string') {
14
+ return null;
15
+ }
16
+
17
+ const trimmed = token.trim();
18
+ if (!trimmed) {
19
+ return null;
20
+ }
21
+
22
+ let annotation = '';
23
+ let contractTarget = trimmed;
24
+ const openParenIndex = trimmed.lastIndexOf('(');
25
+ if (openParenIndex !== -1 && trimmed.endsWith(')')) {
26
+ annotation = trimmed.slice(openParenIndex + 1, -1);
27
+ contractTarget = trimmed.slice(0, openParenIndex);
28
+ }
29
+
30
+ const separatorIndex = contractTarget.lastIndexOf(':');
31
+ if (separatorIndex <= 0 || separatorIndex === contractTarget.length - 1) {
32
+ return null;
33
+ }
34
+
35
+ const file = normalizeRepoPath(contractTarget.slice(0, separatorIndex));
36
+ const symbol = contractTarget.slice(separatorIndex + 1);
37
+ const contractType = detectContractType(symbol, annotation);
38
+
39
+ const validSymbol = contractType === 'command-contract'
40
+ ? isCommandContractName(symbol)
41
+ : isIdentifier(symbol);
42
+ if (!validSymbol) {
43
+ return null;
44
+ }
45
+
46
+ return {
47
+ contract: `${file}:${symbol}(${contractType})`,
48
+ file,
49
+ symbol,
50
+ contractType,
51
+ };
52
+ }
53
+
54
+ function parseStoredContractsFromNotes(notes = '') {
55
+ if (!notes || typeof notes !== 'string') {
56
+ return [];
57
+ }
58
+
59
+ const contractLines = notes
60
+ .split(/\r?\n/)
61
+ .filter((line) => line.startsWith('contracts@') && line.includes(': '));
62
+ if (contractLines.length === 0) {
63
+ return [];
64
+ }
65
+
66
+ contractLines.sort((left, right) => left.localeCompare(right));
67
+ const latestLine = contractLines.at(-1);
68
+ const latestPayload = latestLine.slice(latestLine.indexOf(': ') + 2);
69
+
70
+ return latestPayload
71
+ .split(/\s+/)
72
+ .map((token) => token.trim())
73
+ .filter(Boolean);
74
+ }
75
+
76
+ function mergeEvidence(evidence) {
77
+ const seen = new Set();
78
+ return evidence.filter((item) => {
79
+ const key = `${item.contract}:${item.storedContract}:${item.targetIssueId}`;
80
+ if (seen.has(key)) {
81
+ return false;
82
+ }
83
+ seen.add(key);
84
+ return true;
85
+ });
86
+ }
87
+
88
+ function scoreContractDependencies(normalizedInput) {
89
+ const currentContracts = extractTaskContracts(normalizedInput.taskContext, {
90
+ repositoryRoot: normalizedInput.repositoryRoot,
91
+ })
92
+ .map(parseContractToken)
93
+ .filter(Boolean);
94
+
95
+ if (currentContracts.length === 0) {
96
+ return {
97
+ score: 0,
98
+ findings: [],
99
+ evidence: [],
100
+ };
101
+ }
102
+
103
+ const findings = [];
104
+ const evidence = [];
105
+
106
+ for (const issue of normalizedInput.openIssues) {
107
+ const issueContracts = Array.from(new Set([
108
+ ...issue.contracts,
109
+ ...parseStoredContractsFromNotes(issue.notes),
110
+ ]))
111
+ .map(parseContractToken)
112
+ .filter(Boolean);
113
+
114
+ const issueEvidence = [];
115
+ for (const currentContract of currentContracts) {
116
+ for (const storedContract of issueContracts) {
117
+ if (
118
+ currentContract.file !== storedContract.file
119
+ || currentContract.symbol !== storedContract.symbol
120
+ || currentContract.contractType !== storedContract.contractType
121
+ ) {
122
+ continue;
123
+ }
124
+
125
+ issueEvidence.push({
126
+ type: 'contract',
127
+ sourceIssueId: normalizedInput.currentIssue.id,
128
+ targetIssueId: issue.id,
129
+ contract: currentContract.contract,
130
+ storedContract: storedContract.contract,
131
+ symbol: currentContract.symbol,
132
+ contractType: currentContract.contractType,
133
+ scoreContribution: 1,
134
+ });
135
+ }
136
+ }
137
+
138
+ if (issueEvidence.length === 0) {
139
+ continue;
140
+ }
141
+
142
+ const mergedEvidence = mergeEvidence(issueEvidence);
143
+ findings.push({
144
+ sourceIssueId: normalizedInput.currentIssue.id,
145
+ targetIssueId: issue.id,
146
+ score: mergedEvidence.length,
147
+ evidence: mergedEvidence,
148
+ });
149
+ evidence.push(...mergedEvidence);
150
+ }
151
+
152
+ return {
153
+ score: findings.reduce((total, finding) => total + finding.score, 0),
154
+ findings,
155
+ evidence: mergeEvidence(evidence),
156
+ };
157
+ }
158
+
159
+ module.exports = {
160
+ parseStoredContractsFromNotes,
161
+ scoreContractDependencies,
162
+ };