@ai-sdlc/orchestrator 0.6.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (209) hide show
  1. package/dist/__test-helpers/git-env.d.ts +40 -0
  2. package/dist/__test-helpers/git-env.js +73 -0
  3. package/dist/admission-composite.d.ts +89 -0
  4. package/dist/admission-composite.js +241 -0
  5. package/dist/admission-enrichment.d.ts +142 -0
  6. package/dist/admission-enrichment.js +341 -0
  7. package/dist/admission-hc.d.ts +81 -0
  8. package/dist/admission-hc.js +94 -0
  9. package/dist/admission-score.d.ts +109 -5
  10. package/dist/admission-score.js +90 -11
  11. package/dist/analysis/analyzer.js +3 -2
  12. package/dist/analysis/convention-detector.d.ts +85 -2
  13. package/dist/analysis/convention-detector.js +375 -70
  14. package/dist/analysis/diff-analyzer.d.ts +33 -0
  15. package/dist/analysis/diff-analyzer.js +122 -0
  16. package/dist/analysis/hotspot-analyzer.js +3 -1
  17. package/dist/analysis/index.d.ts +2 -1
  18. package/dist/analysis/index.js +2 -1
  19. package/dist/artifacts/index.d.ts +65 -0
  20. package/dist/artifacts/index.js +142 -0
  21. package/dist/backlog-adapter.d.ts +121 -0
  22. package/dist/backlog-adapter.js +438 -0
  23. package/dist/calibration.d.ts +43 -0
  24. package/dist/calibration.js +76 -0
  25. package/dist/cli/commands/agents.d.ts +24 -0
  26. package/dist/cli/commands/agents.js +66 -1
  27. package/dist/cli/commands/git-remote.d.ts +74 -0
  28. package/dist/cli/commands/git-remote.js +170 -0
  29. package/dist/cli/commands/health.d.ts +4 -0
  30. package/dist/cli/commands/health.js +13 -1
  31. package/dist/cli/commands/init-features.d.ts +208 -0
  32. package/dist/cli/commands/init-features.js +473 -0
  33. package/dist/cli/commands/init-templates.d.ts +104 -0
  34. package/dist/cli/commands/init-templates.js +399 -0
  35. package/dist/cli/commands/init.d.ts +48 -0
  36. package/dist/cli/commands/init.js +322 -23
  37. package/dist/cli/commands/mcp-setup.d.ts +31 -0
  38. package/dist/cli/commands/mcp-setup.js +78 -8
  39. package/dist/cli/formatters/table.js +15 -2
  40. package/dist/cli/index.d.ts +14 -1
  41. package/dist/cli/index.js +81 -20
  42. package/dist/cli/versions.d.ts +57 -0
  43. package/dist/cli/versions.js +128 -0
  44. package/dist/code-area-classifier.d.ts +21 -0
  45. package/dist/code-area-classifier.js +48 -0
  46. package/dist/config.d.ts +33 -1
  47. package/dist/config.js +78 -8
  48. package/dist/database/adapters/external.d.ts +24 -0
  49. package/dist/database/adapters/external.js +80 -0
  50. package/dist/database/adapters/neon.d.ts +41 -0
  51. package/dist/database/adapters/neon.js +98 -0
  52. package/dist/database/adapters/pg-snapshot-restore.d.ts +28 -0
  53. package/dist/database/adapters/pg-snapshot-restore.js +68 -0
  54. package/dist/database/adapters/sqlite-copy.d.ts +32 -0
  55. package/dist/database/adapters/sqlite-copy.js +145 -0
  56. package/dist/database/connection-injection.d.ts +35 -0
  57. package/dist/database/connection-injection.js +93 -0
  58. package/dist/database/index.d.ts +12 -0
  59. package/dist/database/index.js +23 -0
  60. package/dist/database/registry.d.ts +13 -0
  61. package/dist/database/registry.js +27 -0
  62. package/dist/database/topology.d.ts +52 -0
  63. package/dist/database/topology.js +44 -0
  64. package/dist/database/types.d.ts +89 -0
  65. package/dist/database/types.js +26 -0
  66. package/dist/design-authority.d.ts +53 -0
  67. package/dist/design-authority.js +84 -0
  68. package/dist/design-lookahead.d.ts +64 -0
  69. package/dist/design-lookahead.js +86 -0
  70. package/dist/design-quality-trend.d.ts +87 -0
  71. package/dist/design-quality-trend.js +190 -0
  72. package/dist/design-system-context.d.ts +46 -0
  73. package/dist/design-system-context.js +80 -0
  74. package/dist/design-system-correction-loop.d.ts +64 -0
  75. package/dist/design-system-correction-loop.js +128 -0
  76. package/dist/design-system-metrics.d.ts +61 -0
  77. package/dist/design-system-metrics.js +104 -0
  78. package/dist/design-system-stewardship.d.ts +22 -0
  79. package/dist/design-system-stewardship.js +85 -0
  80. package/dist/design-system-validation.d.ts +37 -0
  81. package/dist/design-system-validation.js +88 -0
  82. package/dist/dispatch/index.d.ts +4 -0
  83. package/dist/dispatch/index.js +4 -0
  84. package/dist/dispatch/merge-gate.d.ts +46 -0
  85. package/dist/dispatch/merge-gate.js +90 -0
  86. package/dist/dispatch/requeue.d.ts +57 -0
  87. package/dist/dispatch/requeue.js +131 -0
  88. package/dist/dispatch/worker-pool.d.ts +62 -0
  89. package/dist/dispatch/worker-pool.js +60 -0
  90. package/dist/execute.d.ts +40 -0
  91. package/dist/execute.js +188 -15
  92. package/dist/fix-ci.js +8 -1
  93. package/dist/fix-review.js +8 -1
  94. package/dist/harness/adapters/claude-code.d.ts +29 -0
  95. package/dist/harness/adapters/claude-code.js +191 -0
  96. package/dist/harness/adapters/codex.d.ts +25 -0
  97. package/dist/harness/adapters/codex.js +61 -0
  98. package/dist/harness/independence.d.ts +51 -0
  99. package/dist/harness/independence.js +67 -0
  100. package/dist/harness/index.d.ts +14 -0
  101. package/dist/harness/index.js +20 -0
  102. package/dist/harness/registry.d.ts +17 -0
  103. package/dist/harness/registry.js +31 -0
  104. package/dist/harness/types.d.ts +123 -0
  105. package/dist/harness/types.js +8 -0
  106. package/dist/harness/version-probe.d.ts +14 -0
  107. package/dist/harness/version-probe.js +123 -0
  108. package/dist/index.d.ts +30 -4
  109. package/dist/index.js +35 -2
  110. package/dist/models/classifier.d.ts +78 -0
  111. package/dist/models/classifier.js +277 -0
  112. package/dist/models/index.d.ts +3 -0
  113. package/dist/models/index.js +3 -0
  114. package/dist/models/registry.d.ts +97 -0
  115. package/dist/models/registry.js +173 -0
  116. package/dist/pillar-breakdown.d.ts +85 -0
  117. package/dist/pillar-breakdown.js +162 -0
  118. package/dist/priority.d.ts +5 -0
  119. package/dist/priority.js +18 -5
  120. package/dist/review-meta.d.ts +65 -0
  121. package/dist/review-meta.js +149 -0
  122. package/dist/review.d.ts +4 -0
  123. package/dist/review.js +12 -2
  124. package/dist/runners/claude-code-sdk.d.ts +31 -0
  125. package/dist/runners/claude-code-sdk.js +238 -0
  126. package/dist/runners/claude-code.d.ts +3 -0
  127. package/dist/runners/claude-code.js +30 -66
  128. package/dist/runners/codex.js +4 -1
  129. package/dist/runners/copilot.js +4 -1
  130. package/dist/runners/cursor.js +4 -1
  131. package/dist/runners/git-utils.d.ts +81 -0
  132. package/dist/runners/git-utils.js +201 -0
  133. package/dist/runners/index.d.ts +3 -0
  134. package/dist/runners/index.js +3 -0
  135. package/dist/runners/review-agent.d.ts +20 -0
  136. package/dist/runners/review-agent.js +171 -41
  137. package/dist/runners/runner-registry.js +10 -0
  138. package/dist/runners/sdk-review-runner.d.ts +65 -0
  139. package/dist/runners/sdk-review-runner.js +185 -0
  140. package/dist/runners/security-triage.d.ts +20 -4
  141. package/dist/runners/security-triage.js +39 -15
  142. package/dist/runners/types.d.ts +6 -0
  143. package/dist/runtime/attestations.d.ts +766 -0
  144. package/dist/runtime/attestations.js +1195 -0
  145. package/dist/runtime/git-env.d.ts +53 -0
  146. package/dist/runtime/git-env.js +60 -0
  147. package/dist/runtime/index.d.ts +7 -0
  148. package/dist/runtime/index.js +7 -0
  149. package/dist/runtime/parallelism-flag.d.ts +28 -0
  150. package/dist/runtime/parallelism-flag.js +39 -0
  151. package/dist/runtime/port-allocator.d.ts +32 -0
  152. package/dist/runtime/port-allocator.js +96 -0
  153. package/dist/runtime/worktree-pool.d.ts +86 -0
  154. package/dist/runtime/worktree-pool.js +204 -0
  155. package/dist/runtime/worktree.d.ts +25 -0
  156. package/dist/runtime/worktree.js +111 -0
  157. package/dist/sa-scoring/auto-calibrate.d.ts +69 -0
  158. package/dist/sa-scoring/auto-calibrate.js +107 -0
  159. package/dist/sa-scoring/c1-sa2-computable.d.ts +42 -0
  160. package/dist/sa-scoring/c1-sa2-computable.js +59 -0
  161. package/dist/sa-scoring/composite.d.ts +107 -0
  162. package/dist/sa-scoring/composite.js +139 -0
  163. package/dist/sa-scoring/depparse-client.d.ts +79 -0
  164. package/dist/sa-scoring/depparse-client.js +187 -0
  165. package/dist/sa-scoring/did-compiler.d.ts +122 -0
  166. package/dist/sa-scoring/did-compiler.js +286 -0
  167. package/dist/sa-scoring/drift-monitor.d.ts +84 -0
  168. package/dist/sa-scoring/drift-monitor.js +186 -0
  169. package/dist/sa-scoring/exemplar-bank.d.ts +78 -0
  170. package/dist/sa-scoring/exemplar-bank.js +154 -0
  171. package/dist/sa-scoring/feedback-store.d.ts +100 -0
  172. package/dist/sa-scoring/feedback-store.js +156 -0
  173. package/dist/sa-scoring/index.d.ts +71 -0
  174. package/dist/sa-scoring/index.js +158 -0
  175. package/dist/sa-scoring/layer1-deterministic.d.ts +115 -0
  176. package/dist/sa-scoring/layer1-deterministic.js +298 -0
  177. package/dist/sa-scoring/layer2-structural.d.ts +71 -0
  178. package/dist/sa-scoring/layer2-structural.js +151 -0
  179. package/dist/sa-scoring/layer3-llm.d.ts +86 -0
  180. package/dist/sa-scoring/layer3-llm.js +282 -0
  181. package/dist/sa-scoring/rescore-orchestrator.d.ts +52 -0
  182. package/dist/sa-scoring/rescore-orchestrator.js +47 -0
  183. package/dist/scheduling/burn-down.d.ts +27 -0
  184. package/dist/scheduling/burn-down.js +43 -0
  185. package/dist/scheduling/calibration.d.ts +42 -0
  186. package/dist/scheduling/calibration.js +150 -0
  187. package/dist/scheduling/index.d.ts +8 -0
  188. package/dist/scheduling/index.js +8 -0
  189. package/dist/scheduling/ledger.d.ts +59 -0
  190. package/dist/scheduling/ledger.js +216 -0
  191. package/dist/scheduling/off-peak.d.ts +27 -0
  192. package/dist/scheduling/off-peak.js +112 -0
  193. package/dist/scheduling/schedule-decision.d.ts +39 -0
  194. package/dist/scheduling/schedule-decision.js +89 -0
  195. package/dist/scheduling/tier-analysis.d.ts +47 -0
  196. package/dist/scheduling/tier-analysis.js +81 -0
  197. package/dist/scheduling/types.d.ts +140 -0
  198. package/dist/scheduling/types.js +11 -0
  199. package/dist/shared.d.ts +13 -0
  200. package/dist/shared.js +32 -0
  201. package/dist/state/schema.d.ts +5 -1
  202. package/dist/state/schema.js +223 -1
  203. package/dist/state/store.d.ts +55 -1
  204. package/dist/state/store.js +342 -7
  205. package/dist/state/types.d.ts +139 -0
  206. package/dist/types.d.ts +1 -1
  207. package/dist/validate-agent-output.js +4 -1
  208. package/dist/watch.js +6 -0
  209. package/package.json +3 -2
@@ -0,0 +1,298 @@
1
+ /**
2
+ * Layer 1 — Deterministic SA scorer (RFC-0008 Addendum B §B.4).
3
+ *
4
+ * Consumes a `CompiledDid` + issue text + state store metrics and
5
+ * produces a fully-deterministic scoring result. Layer 1 never makes
6
+ * network or LLM calls *except* for the dep-parse sidecar (for
7
+ * requirement-construction detection).
8
+ *
9
+ * A `core` out-of-scope match is a **hard gate** — downstream SA-1
10
+ * scoring must treat `hardGated=true` as SA-1 = 0.0 regardless of
11
+ * Layer 2/3 output. Evolving matches surface as warnings but do not
12
+ * gate admission.
13
+ */
14
+ import { DepparseError } from './depparse-client.js';
15
+ // ── Tokenization / matching ──────────────────────────────────────────
16
+ function lower(s) {
17
+ return s.toLowerCase();
18
+ }
19
+ function includesWhole(haystack, needle) {
20
+ if (!needle)
21
+ return false;
22
+ // Whole-word-ish match: bounds on non-alphanumeric.
23
+ const re = new RegExp(`(^|[^a-z0-9])${escapeRegex(lower(needle))}($|[^a-z0-9])`, 'i');
24
+ return re.test(haystack);
25
+ }
26
+ function escapeRegex(s) {
27
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
28
+ }
29
+ // ── Scope gate ───────────────────────────────────────────────────────
30
+ export function checkScopeGate(issueText, compiled) {
31
+ const text = lower(issueText);
32
+ const inHits = [];
33
+ const outHits = [];
34
+ const warnings = [];
35
+ let hardGated = false;
36
+ for (const entry of compiled.scopeLists.inScope) {
37
+ const all = [entry.label, ...entry.synonyms];
38
+ for (const term of all) {
39
+ if (includesWhole(text, term)) {
40
+ inHits.push({
41
+ label: entry.label,
42
+ synonym: term === entry.label ? undefined : term,
43
+ identityClass: entry.identityClass,
44
+ matchedText: term,
45
+ });
46
+ break;
47
+ }
48
+ }
49
+ }
50
+ for (const entry of compiled.scopeLists.outOfScope) {
51
+ const all = [entry.label, ...entry.synonyms];
52
+ for (const term of all) {
53
+ if (includesWhole(text, term)) {
54
+ outHits.push({
55
+ label: entry.label,
56
+ synonym: term === entry.label ? undefined : term,
57
+ identityClass: entry.identityClass,
58
+ matchedText: term,
59
+ });
60
+ if (entry.identityClass === 'core') {
61
+ hardGated = true;
62
+ }
63
+ else {
64
+ warnings.push(`Evolving out-of-scope match: "${entry.label}" via "${term}"`);
65
+ }
66
+ break;
67
+ }
68
+ }
69
+ }
70
+ return { inScopeHits: inHits, outOfScopeHits: outHits, hardGated, warnings };
71
+ }
72
+ // ── Constraint violations (via depparse sidecar) ─────────────────────
73
+ export async function detectConstraintViolations(issueText, compiled, client) {
74
+ const violations = [];
75
+ let depparseSkipped = false;
76
+ for (const rule of compiled.constraintRules) {
77
+ // Only enforce `must-not-require` / `must-not-include` via depparse;
78
+ // `must-require` / `must-include` are positive constraints (enforced
79
+ // downstream as signal requirements, not as violation checks).
80
+ if (rule.relationship !== 'must-not-require' && rule.relationship !== 'must-not-include') {
81
+ continue;
82
+ }
83
+ let response;
84
+ try {
85
+ response = await client.match({
86
+ text: issueText,
87
+ patterns: rule.detectionPatterns,
88
+ });
89
+ }
90
+ catch (err) {
91
+ if (err instanceof DepparseError &&
92
+ (err.kind === 'model-unavailable' || err.kind === 'network' || err.kind === 'timeout')) {
93
+ depparseSkipped = true;
94
+ continue;
95
+ }
96
+ throw err;
97
+ }
98
+ for (const match of response.matches) {
99
+ violations.push(buildViolation(rule, match));
100
+ }
101
+ }
102
+ return { violations, depparseSkipped };
103
+ }
104
+ function buildViolation(rule, match) {
105
+ return {
106
+ constraintId: rule.id,
107
+ concept: rule.concept,
108
+ relationship: rule.relationship,
109
+ pattern: match.pattern,
110
+ matchedText: match.matchedText,
111
+ construction: match.construction,
112
+ depPath: match.depPath,
113
+ identityClass: rule.identityClass,
114
+ };
115
+ }
116
+ // ── Anti-pattern matching ────────────────────────────────────────────
117
+ function matchAntiPatterns(text, list, scope, principleId) {
118
+ const hits = [];
119
+ for (const ap of list) {
120
+ for (const pattern of ap.detectionPatterns) {
121
+ if (includesWhole(text, pattern)) {
122
+ hits.push({
123
+ id: ap.id,
124
+ label: ap.label,
125
+ pattern,
126
+ matchedText: pattern,
127
+ identityClass: ap.identityClass,
128
+ scope,
129
+ ...(principleId ? { principleId } : {}),
130
+ });
131
+ }
132
+ }
133
+ }
134
+ return hits;
135
+ }
136
+ export function detectAntiPatterns(issueText, compiled) {
137
+ const text = lower(issueText);
138
+ const productHits = matchAntiPatterns(text, compiled.antiPatternLists.product, 'product');
139
+ const designHits = [];
140
+ for (const [principleId, list] of Object.entries(compiled.antiPatternLists.perPrinciple)) {
141
+ designHits.push(...matchAntiPatterns(text, list, 'design-principle', principleId));
142
+ }
143
+ designHits.push(...matchAntiPatterns(text, compiled.antiPatternLists.voice, 'voice'));
144
+ designHits.push(...matchAntiPatterns(text, compiled.antiPatternLists.visual, 'visual'));
145
+ return {
146
+ product: { hits: productHits },
147
+ design: { hits: designHits },
148
+ };
149
+ }
150
+ // ── Measurable signal checks ─────────────────────────────────────────
151
+ export function checkMeasurableSignals(observed, compiled) {
152
+ const checks = [];
153
+ let coreFailureCount = 0;
154
+ for (const signal of compiled.measurableSignals) {
155
+ const value = observed[signal.metric];
156
+ let status;
157
+ if (value === undefined || Number.isNaN(value)) {
158
+ status = 'missing';
159
+ }
160
+ else {
161
+ status = evaluateOperator(value, signal.threshold, signal.operator) ? 'pass' : 'fail';
162
+ }
163
+ checks.push({
164
+ id: signal.id,
165
+ metric: signal.metric,
166
+ threshold: signal.threshold,
167
+ operator: signal.operator,
168
+ observedValue: value,
169
+ status,
170
+ identityClass: signal.identityClass,
171
+ });
172
+ if (status === 'fail' && signal.identityClass === 'core')
173
+ coreFailureCount++;
174
+ }
175
+ return { checks, coreFailureCount };
176
+ }
177
+ function evaluateOperator(value, threshold, operator) {
178
+ switch (operator) {
179
+ case 'gte':
180
+ return value >= threshold;
181
+ case 'lte':
182
+ return value <= threshold;
183
+ case 'gt':
184
+ return value > threshold;
185
+ case 'lt':
186
+ return value < threshold;
187
+ case 'eq':
188
+ return value === threshold;
189
+ case 'neq':
190
+ return value !== threshold;
191
+ default:
192
+ return false;
193
+ }
194
+ }
195
+ export async function runLayer1(input) {
196
+ const { issueText, compiled, depparse, observedMetrics = {} } = input;
197
+ const scopeGate = checkScopeGate(issueText, compiled);
198
+ const constraintViolations = await detectConstraintViolations(issueText, compiled, depparse);
199
+ const { product, design } = detectAntiPatterns(issueText, compiled);
200
+ const measurable = checkMeasurableSignals(observedMetrics, compiled);
201
+ // Count core vs evolving violations across all dimensions.
202
+ let coreViolationCount = 0;
203
+ let evolvingViolationCount = 0;
204
+ const bump = (ic) => {
205
+ if (ic === 'core')
206
+ coreViolationCount++;
207
+ else
208
+ evolvingViolationCount++;
209
+ };
210
+ for (const hit of scopeGate.outOfScopeHits)
211
+ bump(hit.identityClass);
212
+ for (const v of constraintViolations.violations)
213
+ bump(v.identityClass);
214
+ for (const h of product.hits)
215
+ bump(h.identityClass);
216
+ for (const h of design.hits)
217
+ bump(h.identityClass);
218
+ coreViolationCount += measurable.coreFailureCount;
219
+ const hardGated = scopeGate.hardGated;
220
+ const preVerifiedSummary = renderPreVerifiedSummary({
221
+ scopeGate,
222
+ constraintViolations,
223
+ product,
224
+ design,
225
+ measurable,
226
+ hardGated,
227
+ coreViolationCount,
228
+ evolvingViolationCount,
229
+ });
230
+ return {
231
+ scopeGate,
232
+ constraintViolations,
233
+ antiPatternHits: product,
234
+ designAntiPatternHits: design,
235
+ measurableSignalChecks: measurable,
236
+ hardGated,
237
+ coreViolationCount,
238
+ evolvingViolationCount,
239
+ preVerifiedSummary,
240
+ };
241
+ }
242
+ export function renderPreVerifiedSummary(input) {
243
+ const lines = [];
244
+ lines.push('## Deterministic verification');
245
+ lines.push('');
246
+ lines.push(`- Hard gated: ${input.hardGated ? 'yes' : 'no'}`);
247
+ lines.push(`- Core violations: ${input.coreViolationCount}`);
248
+ lines.push(`- Evolving violations: ${input.evolvingViolationCount}`);
249
+ lines.push('');
250
+ lines.push('### Scope gate');
251
+ if (input.scopeGate.outOfScopeHits.length === 0) {
252
+ lines.push('- No out-of-scope hits');
253
+ }
254
+ else {
255
+ for (const hit of input.scopeGate.outOfScopeHits) {
256
+ lines.push(`- ${hit.identityClass} OUT — "${hit.label}"` +
257
+ (hit.synonym ? ` via "${hit.synonym}"` : ''));
258
+ }
259
+ }
260
+ lines.push('');
261
+ lines.push('### Constraint violations');
262
+ if (input.constraintViolations.violations.length === 0) {
263
+ lines.push('- None detected');
264
+ if (input.constraintViolations.depparseSkipped) {
265
+ lines.push('- (depparse sidecar unavailable — skipped)');
266
+ }
267
+ }
268
+ else {
269
+ for (const v of input.constraintViolations.violations) {
270
+ lines.push(`- ${v.identityClass} ${v.constraintId} (${v.relationship} ${v.concept}) — "${v.matchedText}"` +
271
+ (v.construction ? ` via ${v.construction}` : ''));
272
+ }
273
+ }
274
+ lines.push('');
275
+ lines.push('### Anti-pattern hits');
276
+ const allAntiHits = [...input.product.hits, ...input.design.hits];
277
+ if (allAntiHits.length === 0) {
278
+ lines.push('- None detected');
279
+ }
280
+ else {
281
+ for (const h of allAntiHits) {
282
+ lines.push(`- ${h.identityClass} [${h.scope}${h.principleId ? `:${h.principleId}` : ''}] ${h.label} — "${h.matchedText}"`);
283
+ }
284
+ }
285
+ lines.push('');
286
+ lines.push('### Measurable signals');
287
+ const failures = input.measurable.checks.filter((c) => c.status === 'fail');
288
+ if (failures.length === 0) {
289
+ lines.push('- No failing signals');
290
+ }
291
+ else {
292
+ for (const c of failures) {
293
+ lines.push(`- ${c.identityClass} ${c.id} (${c.metric} ${c.operator} ${c.threshold}) — observed ${c.observedValue ?? 'n/a'}`);
294
+ }
295
+ }
296
+ return lines.join('\n');
297
+ }
298
+ //# sourceMappingURL=layer1-deterministic.js.map
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Layer 2 — Structural BM25 scorer (RFC-0008 Addendum B §B.5).
3
+ *
4
+ * computeDomainRelevance(issueText, bm25Corpus) → SA-1 [0, 1]
5
+ * computePrincipleCoverage(issueText, principleCorpora) → SA-2 vector
6
+ *
7
+ * Uses a minimal in-process BM25 implementation — no external index
8
+ * library needed. The corpus is tokenized at compile time
9
+ * (`did-compiler.ts`), so scoring is O(|query| × |corpus|) per call.
10
+ *
11
+ * Determinism: pure functions, no randomness, arithmetic-only. Same
12
+ * inputs → bit-exact outputs.
13
+ */
14
+ import type { Bm25Corpus, PrincipleCorpora } from './did-compiler.js';
15
+ interface Bm25Index {
16
+ /** The weighted-document list — each carries its own TF map + length. */
17
+ docs: Array<{
18
+ id: string;
19
+ weight: number;
20
+ length: number;
21
+ termFreq: Map<string, number>;
22
+ }>;
23
+ /** Number of documents that contain each term (for IDF). */
24
+ docFreq: Map<string, number>;
25
+ /** Average document length (weighted). */
26
+ avgDocLength: number;
27
+ }
28
+ declare function buildIndex(corpus: Bm25Corpus): Bm25Index;
29
+ declare function idf(term: string, index: Bm25Index): number;
30
+ declare function scoreDoc(queryTokens: readonly string[], doc: Bm25Index['docs'][number], index: Bm25Index): {
31
+ score: number;
32
+ termContributions: Map<string, number>;
33
+ };
34
+ declare function normalizeScore(rawScore: number): number;
35
+ declare function topTerms(contributions: Map<string, number>, limit: number): ContributingTerm[];
36
+ export interface ContributingTerm {
37
+ term: string;
38
+ score: number;
39
+ }
40
+ export interface DomainRelevanceResult {
41
+ /** Normalized relevance in [0, 1]. */
42
+ score: number;
43
+ /** Raw BM25 sum before normalization (for debugging / calibration). */
44
+ rawScore: number;
45
+ /** Top contributing terms, sorted by contribution score. */
46
+ contributingTerms: ContributingTerm[];
47
+ /** Number of query tokens after stopword stripping. */
48
+ queryLength: number;
49
+ }
50
+ export interface PrincipleCoverageEntry {
51
+ principleId: string;
52
+ coverage: number;
53
+ identityClass: 'core' | 'evolving';
54
+ contributingTerms: ContributingTerm[];
55
+ }
56
+ export interface PrincipleCoverageVector {
57
+ principles: PrincipleCoverageEntry[];
58
+ /** Weighted mean — core principles count twice, evolving once. */
59
+ overallCoverage: number;
60
+ }
61
+ export declare function computeDomainRelevance(issueText: string, corpus: Bm25Corpus): DomainRelevanceResult;
62
+ export declare function computePrincipleCoverage(issueText: string, principleCorpora: PrincipleCorpora): PrincipleCoverageVector;
63
+ export declare const __internals: {
64
+ buildIndex: typeof buildIndex;
65
+ idf: typeof idf;
66
+ scoreDoc: typeof scoreDoc;
67
+ normalizeScore: typeof normalizeScore;
68
+ topTerms: typeof topTerms;
69
+ };
70
+ export {};
71
+ //# sourceMappingURL=layer2-structural.d.ts.map
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Layer 2 — Structural BM25 scorer (RFC-0008 Addendum B §B.5).
3
+ *
4
+ * computeDomainRelevance(issueText, bm25Corpus) → SA-1 [0, 1]
5
+ * computePrincipleCoverage(issueText, principleCorpora) → SA-2 vector
6
+ *
7
+ * Uses a minimal in-process BM25 implementation — no external index
8
+ * library needed. The corpus is tokenized at compile time
9
+ * (`did-compiler.ts`), so scoring is O(|query| × |corpus|) per call.
10
+ *
11
+ * Determinism: pure functions, no randomness, arithmetic-only. Same
12
+ * inputs → bit-exact outputs.
13
+ */
14
+ import { tokenize } from './did-compiler.js';
15
+ // ── BM25 hyper-parameters ────────────────────────────────────────────
16
+ const K1 = 1.2;
17
+ const B = 0.75;
18
+ /** Used to squash raw BM25 score into [0, 1). */
19
+ const NORMALIZE_ALPHA = 5.0;
20
+ const CONTRIBUTING_TERMS_LIMIT = 10;
21
+ function buildIndex(corpus) {
22
+ const docs = corpus.documents.map((doc) => {
23
+ const termFreq = new Map();
24
+ for (const tok of doc.tokens)
25
+ termFreq.set(tok, (termFreq.get(tok) ?? 0) + 1);
26
+ return {
27
+ id: doc.id,
28
+ weight: doc.weight,
29
+ length: doc.tokens.length * doc.weight,
30
+ termFreq,
31
+ };
32
+ });
33
+ const docFreq = new Map();
34
+ for (const doc of docs) {
35
+ for (const term of doc.termFreq.keys()) {
36
+ docFreq.set(term, (docFreq.get(term) ?? 0) + 1);
37
+ }
38
+ }
39
+ const totalLength = docs.reduce((sum, d) => sum + d.length, 0);
40
+ const avgDocLength = docs.length === 0 ? 0 : totalLength / docs.length;
41
+ return { docs, docFreq, avgDocLength };
42
+ }
43
+ function idf(term, index) {
44
+ const n = index.docFreq.get(term) ?? 0;
45
+ const N = index.docs.length;
46
+ // Smoothed BM25 IDF (non-negative even for common terms)
47
+ return Math.log((N - n + 0.5) / (n + 0.5) + 1);
48
+ }
49
+ function scoreDoc(queryTokens, doc, index) {
50
+ let score = 0;
51
+ const contributions = new Map();
52
+ const lengthNorm = index.avgDocLength === 0 ? 1 : 1 - B + B * (doc.length / index.avgDocLength);
53
+ for (const term of queryTokens) {
54
+ const rawTf = doc.termFreq.get(term);
55
+ if (!rawTf)
56
+ continue;
57
+ const tf = rawTf * doc.weight;
58
+ const termIdf = idf(term, index);
59
+ const contribution = (termIdf * (tf * (K1 + 1))) / (tf + K1 * lengthNorm);
60
+ score += contribution;
61
+ contributions.set(term, (contributions.get(term) ?? 0) + contribution);
62
+ }
63
+ return { score, termContributions: contributions };
64
+ }
65
+ function normalizeScore(rawScore) {
66
+ if (rawScore <= 0)
67
+ return 0;
68
+ // Monotonic squash into [0, 1) — `tanh(score / α)` is smooth and
69
+ // deterministic. Tune α to taste (higher = slower saturation).
70
+ return Math.tanh(rawScore / NORMALIZE_ALPHA);
71
+ }
72
+ function topTerms(contributions, limit) {
73
+ return Array.from(contributions.entries())
74
+ .map(([term, score]) => ({ term, score }))
75
+ .sort((a, b) => {
76
+ if (b.score !== a.score)
77
+ return b.score - a.score;
78
+ // Tie-break alphabetically for determinism.
79
+ return a.term < b.term ? -1 : a.term > b.term ? 1 : 0;
80
+ })
81
+ .slice(0, limit);
82
+ }
83
+ // ── Public API ───────────────────────────────────────────────────────
84
+ export function computeDomainRelevance(issueText, corpus) {
85
+ const queryTokens = tokenize(issueText);
86
+ if (queryTokens.length === 0 || corpus.documents.length === 0) {
87
+ return {
88
+ score: 0,
89
+ rawScore: 0,
90
+ contributingTerms: [],
91
+ queryLength: queryTokens.length,
92
+ };
93
+ }
94
+ const index = buildIndex(corpus);
95
+ const aggregateContributions = new Map();
96
+ let totalScore = 0;
97
+ let maxDocScore = 0;
98
+ for (const doc of index.docs) {
99
+ const { score, termContributions } = scoreDoc(queryTokens, doc, index);
100
+ totalScore += score;
101
+ if (score > maxDocScore)
102
+ maxDocScore = score;
103
+ for (const [term, v] of termContributions) {
104
+ aggregateContributions.set(term, (aggregateContributions.get(term) ?? 0) + v);
105
+ }
106
+ }
107
+ // For domainRelevance, use the MAX doc score (the best-matching doc).
108
+ // This avoids diluting a tight match in the mission doc by a long
109
+ // irrelevant experientialTargets doc.
110
+ const rawScore = maxDocScore;
111
+ void totalScore;
112
+ return {
113
+ score: normalizeScore(rawScore),
114
+ rawScore,
115
+ contributingTerms: topTerms(aggregateContributions, CONTRIBUTING_TERMS_LIMIT),
116
+ queryLength: queryTokens.length,
117
+ };
118
+ }
119
+ export function computePrincipleCoverage(issueText, principleCorpora) {
120
+ const entries = [];
121
+ for (const principleId of Object.keys(principleCorpora).sort()) {
122
+ const corpus = principleCorpora[principleId];
123
+ const relevance = computeDomainRelevance(issueText, corpus);
124
+ const firstDoc = corpus.documents[0];
125
+ const identityClass = firstDoc && firstDoc.weight === 2 ? 'core' : 'evolving';
126
+ entries.push({
127
+ principleId,
128
+ coverage: relevance.score,
129
+ identityClass,
130
+ contributingTerms: relevance.contributingTerms,
131
+ });
132
+ }
133
+ // Weighted mean: core = 2, evolving = 1.
134
+ const weighted = entries.reduce((acc, e) => {
135
+ const w = e.identityClass === 'core' ? 2 : 1;
136
+ acc.sum += e.coverage * w;
137
+ acc.weight += w;
138
+ return acc;
139
+ }, { sum: 0, weight: 0 });
140
+ const overallCoverage = weighted.weight === 0 ? 0 : weighted.sum / weighted.weight;
141
+ return { principles: entries, overallCoverage };
142
+ }
143
+ // ── Exported internals for testing ───────────────────────────────────
144
+ export const __internals = {
145
+ buildIndex,
146
+ idf,
147
+ scoreDoc,
148
+ normalizeScore,
149
+ topTerms,
150
+ };
151
+ //# sourceMappingURL=layer2-structural.js.map
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Layer 3 — LLM structured assessment (RFC-0008 Addendum B §B.6).
3
+ *
4
+ * Builds SA-1 (domain-intent) and SA-2 (principle-alignment) prompts
5
+ * with the Layer 1 preVerifiedSummary injected as a "CI-Boundary"
6
+ * block — the LLM is explicitly told NOT to re-assess scope /
7
+ * constraints / anti-patterns / signals (deterministically verified).
8
+ * Parses structured JSON output and applies a confidence filter:
9
+ * findings with `confidence < 0.5` are suppressed.
10
+ *
11
+ * Amendment 2 (v4 §5.2): the SA-2 prompt MUST NOT include
12
+ * `tokenCompliance` or `catalogHealth` — those are covered by the C1
13
+ * computable half of SA-2 and re-including them double-counts.
14
+ */
15
+ import type { DesignIntentDocument, DesignSystemBinding } from '@ai-sdlc/reference';
16
+ export interface LLMClient {
17
+ complete(prompt: string): Promise<string>;
18
+ }
19
+ /** Deterministic fake for tests — no network calls. */
20
+ export declare class RecordedLLMClient implements LLMClient {
21
+ private responseByPrompt;
22
+ private fallbackResponse?;
23
+ readonly promptLog: string[];
24
+ setResponse(promptSubstring: string, response: string): void;
25
+ setFallbackResponse(response: string): void;
26
+ complete(prompt: string): Promise<string>;
27
+ }
28
+ export interface SubtleConflict {
29
+ description: string;
30
+ severity: 'low' | 'medium' | 'high';
31
+ confidence: number;
32
+ }
33
+ export interface SubtleDesignConflict extends SubtleConflict {
34
+ /** Principle this conflict relates to (optional). */
35
+ principleId?: string;
36
+ }
37
+ export interface LLMScoringResult {
38
+ /** SA-1 domain-intent score in [0, 1], 0 if below confidence filter. */
39
+ domainIntent: number;
40
+ domainIntentConfidence: number;
41
+ subtleConflicts: SubtleConflict[];
42
+ /** SA-2 principle-alignment score in [0, 1]. */
43
+ principleAlignment: number;
44
+ principleAlignmentConfidence: number;
45
+ subtleDesignConflicts: SubtleDesignConflict[];
46
+ /** Always true — signals the CI boundary block was applied. */
47
+ preVerifiedBoundaryApplied: true;
48
+ /** Count of findings dropped by the confidence filter (<0.5). */
49
+ suppressedFindings: number;
50
+ }
51
+ export type LayerLlmErrorKind = 'malformed-json' | 'missing-field' | 'invalid-range';
52
+ export declare class LayerLlmError extends Error {
53
+ readonly kind: LayerLlmErrorKind;
54
+ readonly raw?: string;
55
+ constructor(kind: LayerLlmErrorKind, message: string, raw?: string);
56
+ }
57
+ export declare const CONFIDENCE_THRESHOLD = 0.5;
58
+ export declare const CI_BOUNDARY_HEADER = "## CI-Boundary: pre-verified (do not re-assess)";
59
+ /** Guidance line that MUST appear verbatim in both SA-1 and SA-2 prompts. */
60
+ export declare const SCOPE_GUIDANCE = "Do not re-assess scope, constraints, anti-patterns, or measurable signals \u2014 they have been deterministically verified above.";
61
+ /**
62
+ * Wraps user-controlled issue text in a tagged block so the model
63
+ * treats its contents as data, not instructions. Defends against
64
+ * prompt-injection attempts that try to override the CI-Boundary
65
+ * block (e.g. issue bodies starting with `## Task` or `## CI-Boundary`).
66
+ */
67
+ export declare const UNTRUSTED_CONTENT_GUIDANCE: string;
68
+ export interface PromptContext {
69
+ issueText: string;
70
+ did: DesignIntentDocument;
71
+ dsb?: DesignSystemBinding;
72
+ preVerifiedSummary: string;
73
+ }
74
+ export declare function buildSa1Prompt(ctx: PromptContext): string;
75
+ export declare function buildSa2Prompt(ctx: PromptContext): string;
76
+ /** Extract the first JSON object from a raw LLM reply. */
77
+ export declare function extractJson(raw: string): unknown;
78
+ export interface Layer3Input {
79
+ issueText: string;
80
+ did: DesignIntentDocument;
81
+ dsb?: DesignSystemBinding;
82
+ preVerifiedSummary: string;
83
+ llm: LLMClient;
84
+ }
85
+ export declare function runLayer3(input: Layer3Input): Promise<LLMScoringResult>;
86
+ //# sourceMappingURL=layer3-llm.d.ts.map