@dzhechkov/harness-core 0.8.6 → 0.8.11

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 (164) hide show
  1. package/.dz-manifest.json +361 -97
  2. package/README.md +48 -1
  3. package/dist/amendment-trace.d.ts.map +1 -1
  4. package/dist/amendment-trace.js +12 -1
  5. package/dist/amendment-trace.js.map +1 -1
  6. package/dist/codex-invoke.d.ts +73 -0
  7. package/dist/codex-invoke.d.ts.map +1 -0
  8. package/dist/codex-invoke.js +80 -0
  9. package/dist/codex-invoke.js.map +1 -0
  10. package/dist/compounding.d.ts +54 -0
  11. package/dist/compounding.d.ts.map +1 -1
  12. package/dist/compounding.js +221 -1
  13. package/dist/compounding.js.map +1 -1
  14. package/dist/discrimination-gate.d.ts +63 -3
  15. package/dist/discrimination-gate.d.ts.map +1 -1
  16. package/dist/discrimination-gate.js +113 -16
  17. package/dist/discrimination-gate.js.map +1 -1
  18. package/dist/eta.d.ts +92 -0
  19. package/dist/eta.d.ts.map +1 -0
  20. package/dist/eta.js +488 -0
  21. package/dist/eta.js.map +1 -0
  22. package/dist/event-chain.d.ts +30 -0
  23. package/dist/event-chain.d.ts.map +1 -1
  24. package/dist/event-chain.js +24 -0
  25. package/dist/event-chain.js.map +1 -1
  26. package/dist/feature-adr-checkpoints.js +1 -1
  27. package/dist/feature-adr-decision-recall.d.ts +167 -0
  28. package/dist/feature-adr-decision-recall.d.ts.map +1 -0
  29. package/dist/feature-adr-decision-recall.js +519 -0
  30. package/dist/feature-adr-decision-recall.js.map +1 -0
  31. package/dist/feature-adr-landing.d.ts +37 -0
  32. package/dist/feature-adr-landing.d.ts.map +1 -0
  33. package/dist/feature-adr-landing.js +59 -0
  34. package/dist/feature-adr-landing.js.map +1 -0
  35. package/dist/feature-adr-routing.d.ts +2 -2
  36. package/dist/feature-adr-routing.d.ts.map +1 -1
  37. package/dist/feature-adr-routing.js +7 -11
  38. package/dist/feature-adr-routing.js.map +1 -1
  39. package/dist/guard-promotion.d.ts +41 -0
  40. package/dist/guard-promotion.d.ts.map +1 -1
  41. package/dist/guard-promotion.js +218 -4
  42. package/dist/guard-promotion.js.map +1 -1
  43. package/dist/guard-volume.d.ts +108 -0
  44. package/dist/guard-volume.d.ts.map +1 -0
  45. package/dist/guard-volume.js +536 -0
  46. package/dist/guard-volume.js.map +1 -0
  47. package/dist/guard.d.ts +17 -0
  48. package/dist/guard.d.ts.map +1 -1
  49. package/dist/guard.js +92 -4
  50. package/dist/guard.js.map +1 -1
  51. package/dist/index.d.ts +24 -7
  52. package/dist/index.d.ts.map +1 -1
  53. package/dist/index.js +19 -5
  54. package/dist/index.js.map +1 -1
  55. package/dist/integration-apply.d.ts +25 -0
  56. package/dist/integration-apply.d.ts.map +1 -0
  57. package/dist/integration-apply.js +299 -0
  58. package/dist/integration-apply.js.map +1 -0
  59. package/dist/integration-evidence.d.ts +46 -0
  60. package/dist/integration-evidence.d.ts.map +1 -0
  61. package/dist/integration-evidence.js +44 -0
  62. package/dist/integration-evidence.js.map +1 -0
  63. package/dist/integration-probe-worker.d.ts +22 -0
  64. package/dist/integration-probe-worker.d.ts.map +1 -0
  65. package/dist/integration-probe-worker.js +334 -0
  66. package/dist/integration-probe-worker.js.map +1 -0
  67. package/dist/integrations-verify.d.ts +60 -0
  68. package/dist/integrations-verify.d.ts.map +1 -0
  69. package/dist/integrations-verify.js +194 -0
  70. package/dist/integrations-verify.js.map +1 -0
  71. package/dist/lesson-generalization.d.ts +29 -0
  72. package/dist/lesson-generalization.d.ts.map +1 -0
  73. package/dist/lesson-generalization.js +84 -0
  74. package/dist/lesson-generalization.js.map +1 -0
  75. package/dist/mutation-gate.d.ts +39 -36
  76. package/dist/mutation-gate.d.ts.map +1 -1
  77. package/dist/mutation-gate.js +111 -5
  78. package/dist/mutation-gate.js.map +1 -1
  79. package/dist/operations.d.ts +27 -0
  80. package/dist/operations.d.ts.map +1 -1
  81. package/dist/operations.js +186 -7
  82. package/dist/operations.js.map +1 -1
  83. package/dist/patterns.d.ts +27 -1
  84. package/dist/patterns.d.ts.map +1 -1
  85. package/dist/patterns.js +211 -45
  86. package/dist/patterns.js.map +1 -1
  87. package/dist/plugin.d.ts.map +1 -1
  88. package/dist/plugin.js +27 -5
  89. package/dist/plugin.js.map +1 -1
  90. package/dist/recommend.d.ts +4 -5
  91. package/dist/recommend.d.ts.map +1 -1
  92. package/dist/recommend.js +110 -45
  93. package/dist/recommend.js.map +1 -1
  94. package/dist/registry.d.ts +32 -1
  95. package/dist/registry.d.ts.map +1 -1
  96. package/dist/registry.js +165 -9
  97. package/dist/registry.js.map +1 -1
  98. package/dist/run-records.d.ts +3 -0
  99. package/dist/run-records.d.ts.map +1 -1
  100. package/dist/run-records.js +18 -0
  101. package/dist/run-records.js.map +1 -1
  102. package/dist/score.d.ts +95 -0
  103. package/dist/score.d.ts.map +1 -1
  104. package/dist/score.js +274 -2
  105. package/dist/score.js.map +1 -1
  106. package/dist/setup.d.ts.map +1 -1
  107. package/dist/setup.js +20 -17
  108. package/dist/setup.js.map +1 -1
  109. package/dist/skill-selection.d.ts +72 -0
  110. package/dist/skill-selection.d.ts.map +1 -0
  111. package/dist/skill-selection.js +76 -0
  112. package/dist/skill-selection.js.map +1 -0
  113. package/dist/stem.d.ts +12 -0
  114. package/dist/stem.d.ts.map +1 -0
  115. package/dist/stem.js +89 -0
  116. package/dist/stem.js.map +1 -0
  117. package/dist/target-integrations.d.ts +65 -0
  118. package/dist/target-integrations.d.ts.map +1 -0
  119. package/dist/target-integrations.js +152 -0
  120. package/dist/target-integrations.js.map +1 -0
  121. package/dist/telemetry-vocabulary.d.ts +7 -0
  122. package/dist/telemetry-vocabulary.d.ts.map +1 -1
  123. package/dist/telemetry-vocabulary.js +29 -0
  124. package/dist/telemetry-vocabulary.js.map +1 -1
  125. package/dist/vector-tier.d.ts +6 -1
  126. package/dist/vector-tier.d.ts.map +1 -1
  127. package/dist/vector-tier.js +32 -7
  128. package/dist/vector-tier.js.map +1 -1
  129. package/package.json +7 -6
  130. package/sbom.json +772 -112
  131. package/src/amendment-trace.ts +12 -1
  132. package/src/codex-invoke.ts +138 -0
  133. package/src/compounding.ts +300 -1
  134. package/src/discrimination-gate.ts +183 -19
  135. package/src/eta.ts +590 -0
  136. package/src/event-chain.ts +41 -0
  137. package/src/feature-adr-checkpoints.ts +1 -1
  138. package/src/feature-adr-decision-recall.ts +652 -0
  139. package/src/feature-adr-landing.ts +109 -0
  140. package/src/feature-adr-routing.ts +7 -11
  141. package/src/guard-promotion.ts +245 -4
  142. package/src/guard-volume.ts +752 -0
  143. package/src/guard.ts +110 -4
  144. package/src/index.ts +73 -6
  145. package/src/integration-apply.ts +332 -0
  146. package/src/integration-evidence.ts +89 -0
  147. package/src/integration-probe-worker.ts +310 -0
  148. package/src/integration-receipts/claude-code/mcp/2.1.235.json +35 -0
  149. package/src/integrations-verify.ts +258 -0
  150. package/src/lesson-generalization.ts +115 -0
  151. package/src/mutation-gate.ts +165 -5
  152. package/src/operations.ts +207 -7
  153. package/src/patterns.ts +252 -43
  154. package/src/plugin.ts +27 -5
  155. package/src/recommend.ts +116 -46
  156. package/src/registry.ts +144 -11
  157. package/src/run-records.ts +23 -0
  158. package/src/score.ts +361 -3
  159. package/src/setup.ts +20 -17
  160. package/src/skill-selection.ts +111 -0
  161. package/src/stem.ts +87 -0
  162. package/src/target-integrations.ts +225 -0
  163. package/src/telemetry-vocabulary.ts +36 -0
  164. package/src/vector-tier.ts +44 -14
@@ -0,0 +1,194 @@
1
+ /** Bounded, non-executing target registration verification. */
2
+ import { spawnSync } from 'node:child_process';
3
+ import { accessSync, constants, existsSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from 'node:fs';
4
+ import { tmpdir } from 'node:os';
5
+ import { delimiter, isAbsolute, join, relative, resolve, sep } from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
7
+ import { CLAUDE_MCP_RECEIPT, assessClaudeMcpEvidence } from './integration-evidence.js';
8
+ import { TARGET_INTEGRATION_POLICY } from './target-integrations.js';
9
+ export const INTEGRATION_PROBE_TIMEOUT_MS = 30_000;
10
+ export const INTEGRATION_PROBE_STREAM_MAX_BYTES = 1024 * 1024;
11
+ export const INTEGRATION_PROBE_AGGREGATE_MAX_BYTES = 2 * 1024 * 1024;
12
+ /** Resolve PATH once and reject a project-controlled probe binary before spawning it. */
13
+ export function resolveIntegrationExecutable(command, cwd, pathValue = process.env['PATH'] ?? '') {
14
+ const candidates = isAbsolute(command)
15
+ ? [command]
16
+ : command.includes('/') || command.includes('\\')
17
+ ? [resolve(cwd, command)]
18
+ : pathValue.split(delimiter).map((dir) => join(dir.length === 0 ? cwd : dir, command));
19
+ for (const candidate of candidates) {
20
+ if (!existsSync(candidate))
21
+ continue;
22
+ try {
23
+ accessSync(candidate, constants.X_OK);
24
+ const actual = realpathSync(candidate);
25
+ const rel = relative(realpathSync(cwd), actual);
26
+ if (rel === '' || (!rel.startsWith(`..${sep}`) && rel !== '..' && !isAbsolute(rel))) {
27
+ return { ok: false, errorCode: 'UNSAFE_EXECUTABLE' };
28
+ }
29
+ return { ok: true, path: actual };
30
+ }
31
+ catch {
32
+ continue;
33
+ }
34
+ }
35
+ return { ok: false, errorCode: 'ENOENT' };
36
+ }
37
+ export const defaultIntegrationProcessPort = {
38
+ run(request) {
39
+ const executable = resolveIntegrationExecutable(request.command, request.cwd);
40
+ if (!executable.ok) {
41
+ return { status: null, signal: null, stdout: new Uint8Array(), stderr: new Uint8Array(), errorCode: executable.errorCode };
42
+ }
43
+ const workerPath = fileURLToPath(new URL('./integration-probe-worker.js', import.meta.url));
44
+ const result = spawnSync(process.execPath, [workerPath], {
45
+ input: JSON.stringify({
46
+ ...request,
47
+ command: executable.path,
48
+ streamMaxBytes: INTEGRATION_PROBE_STREAM_MAX_BYTES,
49
+ aggregateMaxBytes: INTEGRATION_PROBE_AGGREGATE_MAX_BYTES,
50
+ }),
51
+ cwd: request.cwd,
52
+ timeout: request.timeoutMs + 5_000,
53
+ killSignal: 'SIGKILL',
54
+ maxBuffer: 4 * 1024 * 1024,
55
+ encoding: 'utf8',
56
+ windowsHide: true,
57
+ });
58
+ if (result.error !== undefined || result.status !== 0) {
59
+ return {
60
+ status: result.status,
61
+ signal: result.signal,
62
+ stdout: new Uint8Array(),
63
+ stderr: new Uint8Array(),
64
+ errorCode: result.error?.code ?? 'PROCESS_ERROR',
65
+ };
66
+ }
67
+ let observation;
68
+ try {
69
+ observation = JSON.parse(result.stdout);
70
+ }
71
+ catch {
72
+ return { status: null, signal: null, stdout: new Uint8Array(), stderr: new Uint8Array(), errorCode: 'PROCESS_ERROR' };
73
+ }
74
+ return {
75
+ status: observation.status,
76
+ signal: observation.signal,
77
+ stdout: Buffer.from(observation.stdoutBase64, 'base64'),
78
+ stderr: Buffer.from(observation.stderrBase64, 'base64'),
79
+ ...(observation.errorCode !== undefined ? { errorCode: observation.errorCode } : {}),
80
+ };
81
+ },
82
+ };
83
+ function decodeBounded(observation) {
84
+ const stdout = Buffer.from(observation.stdout);
85
+ const stderr = Buffer.from(observation.stderr);
86
+ if (stdout.length > INTEGRATION_PROBE_STREAM_MAX_BYTES || stderr.length > INTEGRATION_PROBE_STREAM_MAX_BYTES ||
87
+ stdout.length + stderr.length > INTEGRATION_PROBE_AGGREGATE_MAX_BYTES)
88
+ return 'LIVE_PROBE_FAILED';
89
+ try {
90
+ const decoder = new TextDecoder('utf-8', { fatal: true });
91
+ return { stdout: decoder.decode(stdout), stderr: decoder.decode(stderr) };
92
+ }
93
+ catch {
94
+ return 'LIVE_PROBE_FAILED';
95
+ }
96
+ }
97
+ function parseClaudeVersion(text) {
98
+ return /(?:Claude Code\s+)?(\d+\.\d+\.\d+)/i.exec(text)?.[1];
99
+ }
100
+ function failed(options, reasonCode, remediation, runtimeVersion) {
101
+ return {
102
+ ok: false,
103
+ target: options.target,
104
+ component: options.component,
105
+ registrations: [],
106
+ reasonCode,
107
+ remediation,
108
+ ...(runtimeVersion !== undefined ? { runtimeVersion } : {}),
109
+ };
110
+ }
111
+ function parseClaudeProjectRegistration(options, id, observation, runtimeVersion) {
112
+ if (observation.errorCode === 'ETIMEDOUT' || observation.signal === 'SIGKILL') {
113
+ return failed(options, 'LIVE_PROBE_TIMEOUT', `Claude mcp get exceeded ${INTEGRATION_PROBE_TIMEOUT_MS}ms`, runtimeVersion);
114
+ }
115
+ if (observation.errorCode !== undefined) {
116
+ return failed(options, 'LIVE_PROBE_FAILED', `Claude registration probe failed before a trustworthy observation (${observation.errorCode})`, runtimeVersion);
117
+ }
118
+ const text = decodeBounded(observation);
119
+ if (typeof text === 'string')
120
+ return failed(options, text, 'Claude registration output was oversized or invalid UTF-8', runtimeVersion);
121
+ if (observation.status !== 0)
122
+ return failed(options, 'POST_WRITE_REGISTRATION_NOT_OBSERVED', `claude mcp get ${id} did not observe the project registration`, runtimeVersion);
123
+ const output = `${text.stdout}\n${text.stderr}`;
124
+ const escapedId = id.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
125
+ // Claude 2.1.235 prints either `Name: <id>` or an exact `<id>:` heading depending on the
126
+ // installation channel. Require one whole attributable line; a prose mention is not evidence.
127
+ if (!new RegExp(`^(?:Name\\s*:\\s*${escapedId}|${escapedId}:)\\s*$`, 'im').test(output) ||
128
+ !/Scope\s*:\s*Project\b/i.test(output)) {
129
+ return failed(options, 'POST_WRITE_REGISTRATION_NOT_OBSERVED', `claude mcp get ${id} did not attribute the exact id to project scope`, runtimeVersion);
130
+ }
131
+ const pending = /pending approval/i.test(output);
132
+ return {
133
+ ok: true,
134
+ target: options.target,
135
+ component: options.component,
136
+ runtimeVersion,
137
+ evidenceVersion: CLAUDE_MCP_RECEIPT.runtimeVersion,
138
+ registrations: [{ id, scope: 'project', registered: true, approval: pending ? 'pending' : 'unknown', ready: !pending }],
139
+ };
140
+ }
141
+ /**
142
+ * Verify only code-owned native list/get surfaces. Manifest command/URL fields
143
+ * are intentionally absent from this API, so they cannot be executed here.
144
+ */
145
+ export function verifyTargetIntegration(options) {
146
+ const port = options.processPort ?? defaultIntegrationProcessPort;
147
+ if (options.target !== 'claude-code' || options.component !== 'mcp') {
148
+ const cell = TARGET_INTEGRATION_POLICY[options.target][options.component];
149
+ return failed(options, cell.reasonCode ?? 'NO_QUALIFYING_LIVE_RECEIPT', 'no qualifying exact-version live receipt exists for this target/component');
150
+ }
151
+ const versionObs = port.run({ command: 'claude', args: ['--version'], cwd: options.projectRoot, timeoutMs: INTEGRATION_PROBE_TIMEOUT_MS });
152
+ if (versionObs.errorCode === 'ENOENT')
153
+ return failed(options, 'TARGET_BINARY_UNAVAILABLE', 'install Claude Code and rerun dz integrations-verify');
154
+ if (versionObs.errorCode === 'ETIMEDOUT' || versionObs.signal === 'SIGKILL')
155
+ return failed(options, 'LIVE_PROBE_TIMEOUT', `Claude version probe exceeded ${INTEGRATION_PROBE_TIMEOUT_MS}ms`);
156
+ if (versionObs.errorCode !== undefined)
157
+ return failed(options, 'LIVE_PROBE_FAILED', `Claude version probe failed before a trustworthy observation (${versionObs.errorCode})`);
158
+ const versionText = decodeBounded(versionObs);
159
+ if (typeof versionText === 'string')
160
+ return failed(options, versionText, 'Claude version output was oversized or invalid UTF-8');
161
+ if (versionObs.status !== 0)
162
+ return failed(options, 'LIVE_PROBE_FAILED', 'Claude version probe exited non-zero');
163
+ const runtimeVersion = parseClaudeVersion(`${versionText.stdout}\n${versionText.stderr}`);
164
+ if (runtimeVersion === undefined)
165
+ return failed(options, 'LIVE_PROBE_FAILED', 'Claude version output did not contain an attributable semantic version');
166
+ const evidence = assessClaudeMcpEvidence(runtimeVersion);
167
+ if (!evidence.eligible)
168
+ return failed(options, evidence.reasonCode ?? 'RECEIPT_STALE', `expected Claude Code ${CLAUDE_MCP_RECEIPT.runtimeVersion}; rerun after a matching receipt is recorded`, runtimeVersion);
169
+ if ((options.phase ?? 'preflight') === 'preflight') {
170
+ const probeId = 'dz-registration-probe';
171
+ const probeRoot = mkdtempSync(join(tmpdir(), 'dz-claude-mcp-probe-'));
172
+ try {
173
+ writeFileSync(join(probeRoot, '.mcp.json'), `${JSON.stringify({
174
+ mcpServers: { [probeId]: { type: 'stdio', command: 'node', args: ['-e', 'process.exit(0)'] } },
175
+ }, null, 2)}\n`, { flag: 'wx', mode: 0o600 });
176
+ const observation = port.run({
177
+ command: 'claude', args: ['mcp', 'get', probeId], cwd: probeRoot, timeoutMs: INTEGRATION_PROBE_TIMEOUT_MS,
178
+ });
179
+ const parsed = parseClaudeProjectRegistration(options, probeId, observation, runtimeVersion);
180
+ return parsed.ok ? { ...parsed, registrations: [] } : parsed;
181
+ }
182
+ finally {
183
+ rmSync(probeRoot, { recursive: true, force: true });
184
+ }
185
+ }
186
+ const id = options.registrationId;
187
+ if (id === undefined)
188
+ return failed(options, 'LIVE_PROBE_FAILED', 'post-write verification requires an exact registration id', runtimeVersion);
189
+ return parseClaudeProjectRegistration(options, id, port.run({ command: 'claude', args: ['mcp', 'get', id], cwd: options.projectRoot, timeoutMs: INTEGRATION_PROBE_TIMEOUT_MS }), runtimeVersion);
190
+ }
191
+ export function runIntegrationsVerify(options) {
192
+ return verifyTargetIntegration(options);
193
+ }
194
+ //# sourceMappingURL=integrations-verify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"integrations-verify.js","sourceRoot":"","sources":["../src/integrations-verify.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ export type LessonForm = 'specific' | 'class';
2
+ export type LessonMatchedForm = 'specific' | 'class' | 'both';
3
+ export interface LessonFormsInput {
4
+ readonly specific: string;
5
+ readonly classForm?: string;
6
+ readonly classAdvisory?: string;
7
+ }
8
+ export interface LessonFormRankedHit<T> {
9
+ readonly key: string;
10
+ readonly value: T;
11
+ readonly matchedForm: LessonForm;
12
+ }
13
+ export interface MergedLessonFormHit<T> {
14
+ readonly key: string;
15
+ readonly value: T;
16
+ readonly matchedForm: LessonMatchedForm;
17
+ readonly score: number;
18
+ }
19
+ export declare function normalizeLessonForms(specific: string, classInput?: unknown): LessonFormsInput;
20
+ export declare function validateClassTemplate(specific: string, classTemplate: string): {
21
+ readonly ok: true;
22
+ } | {
23
+ readonly ok: false;
24
+ readonly reason: string;
25
+ };
26
+ export declare function lessonPairIdOf(specific: string, classTemplate: string, ts: string): string;
27
+ export declare function mergeLessonMatchedForms(left: LessonMatchedForm | undefined, right: LessonMatchedForm | undefined): LessonMatchedForm | undefined;
28
+ export declare function mergeLessonFormHits<T>(specificHits: readonly LessonFormRankedHit<T>[], classHits: readonly LessonFormRankedHit<T>[], limit: number): MergedLessonFormHit<T>[];
29
+ //# sourceMappingURL=lesson-generalization.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lesson-generalization.d.ts","sourceRoot":"","sources":["../src/lesson-generalization.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,UAAU,GAAG,UAAU,GAAG,OAAO,CAAC;AAC9C,MAAM,MAAM,iBAAiB,GAAG,UAAU,GAAG,OAAO,GAAG,MAAM,CAAC;AAE9D,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;CACjC;AAED,MAAM,WAAW,mBAAmB,CAAC,CAAC;IACpC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;IAClB,QAAQ,CAAC,WAAW,EAAE,UAAU,CAAC;CAClC;AAED,MAAM,WAAW,mBAAmB,CAAC,CAAC;IACpC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;IAClB,QAAQ,CAAC,WAAW,EAAE,iBAAiB,CAAC;IACxC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,OAAO,GAAG,gBAAgB,CAQ7F;AAUD,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,GACpB;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAwBzE;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAM1F;AAED,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,iBAAiB,GAAG,SAAS,EACnC,KAAK,EAAE,iBAAiB,GAAG,SAAS,GACnC,iBAAiB,GAAG,SAAS,CAI/B;AAED,wBAAgB,mBAAmB,CAAC,CAAC,EACnC,YAAY,EAAE,SAAS,mBAAmB,CAAC,CAAC,CAAC,EAAE,EAC/C,SAAS,EAAE,SAAS,mBAAmB,CAAC,CAAC,CAAC,EAAE,EAC5C,KAAK,EAAE,MAAM,GACZ,mBAAmB,CAAC,CAAC,CAAC,EAAE,CAsB1B"}
@@ -0,0 +1,84 @@
1
+ import { createHash } from 'node:crypto';
2
+ export function normalizeLessonForms(specific, classInput) {
3
+ if (classInput === undefined || (typeof classInput === 'string' && classInput.trim() === '')) {
4
+ return { specific };
5
+ }
6
+ if (typeof classInput !== 'string') {
7
+ return { specific, classAdvisory: 'class form must be text; the specific lesson was kept' };
8
+ }
9
+ return { specific, classForm: classInput.trim() };
10
+ }
11
+ function normalizedText(value) {
12
+ return value.normalize('NFC').toLocaleLowerCase('und').replace(/\s+/gu, ' ').trim();
13
+ }
14
+ function escapedLiteral(value) {
15
+ return normalizedText(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/\s+/g, '\\s+');
16
+ }
17
+ export function validateClassTemplate(specific, classTemplate) {
18
+ const slot = /:([A-Za-z][A-Za-z0-9_-]*)/g;
19
+ const parts = [];
20
+ let slots = 0;
21
+ let cursor = 0;
22
+ for (const match of classTemplate.matchAll(slot)) {
23
+ const index = match.index ?? 0;
24
+ const literal = classTemplate.slice(cursor, index);
25
+ if (literal.includes(':'))
26
+ return { ok: false, reason: 'invalid :slot name' };
27
+ parts.push(escapedLiteral(literal), '(.+?)');
28
+ slots += 1;
29
+ cursor = index + match[0].length;
30
+ }
31
+ const tail = classTemplate.slice(cursor);
32
+ if (tail.includes(':'))
33
+ return { ok: false, reason: 'invalid :slot name' };
34
+ if (slots === 0)
35
+ return { ok: false, reason: 'class form must contain at least one :slot' };
36
+ parts.push(escapedLiteral(tail));
37
+ try {
38
+ return new RegExp(`^${parts.join('')}$`, 'u').test(normalizedText(specific))
39
+ ? { ok: true }
40
+ : { ok: false, reason: 'class form does not syntactically cover the specific lesson' };
41
+ }
42
+ catch {
43
+ return { ok: false, reason: 'class form contains an invalid template' };
44
+ }
45
+ }
46
+ export function lessonPairIdOf(specific, classTemplate, ts) {
47
+ const digest = createHash('sha256')
48
+ .update(`${specific}\u0000${classTemplate}\u0000${ts}`)
49
+ .digest('hex')
50
+ .slice(0, 16);
51
+ return `lesson-pair:${digest}`;
52
+ }
53
+ export function mergeLessonMatchedForms(left, right) {
54
+ if (left === undefined)
55
+ return right;
56
+ if (right === undefined || left === right)
57
+ return left;
58
+ return 'both';
59
+ }
60
+ export function mergeLessonFormHits(specificHits, classHits, limit) {
61
+ const merged = new Map();
62
+ const add = (hit, rank) => {
63
+ const current = merged.get(hit.key) ?? { value: hit.value, specific: false, classForm: false, score: 0 };
64
+ if (hit.matchedForm === 'specific')
65
+ current.specific = true;
66
+ else
67
+ current.classForm = true;
68
+ current.score += 1 / (60 + rank + 1);
69
+ merged.set(hit.key, current);
70
+ };
71
+ specificHits.forEach(add);
72
+ classHits.forEach(add);
73
+ const safeLimit = Number.isFinite(limit) ? Math.max(0, Math.trunc(limit)) : merged.size;
74
+ return [...merged.entries()]
75
+ .sort((a, b) => b[1].score - a[1].score || a[0].localeCompare(b[0]))
76
+ .slice(0, safeLimit)
77
+ .map(([key, hit]) => ({
78
+ key,
79
+ value: hit.value,
80
+ matchedForm: hit.specific && hit.classForm ? 'both' : hit.classForm ? 'class' : 'specific',
81
+ score: hit.score,
82
+ }));
83
+ }
84
+ //# sourceMappingURL=lesson-generalization.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lesson-generalization.js","sourceRoot":"","sources":["../src/lesson-generalization.ts"],"names":[],"mappings":"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"}
@@ -77,6 +77,10 @@ export interface MutationObservation {
77
77
  readonly rebaselineExitCode?: number | null;
78
78
  /** named no-exit reason for the restored-tree attribution run, when it produced none. */
79
79
  readonly rebaselineFailureReason?: string;
80
+ /** parsed failing files from a RED restored-tree run; absent when no red rebaseline ran. */
81
+ readonly rebaselineAttribution?: BaselineAttribution;
82
+ /** bounded log proving an internal runner failure received at most one retry. */
83
+ readonly internalAttemptLog?: string;
80
84
  }
81
85
  export interface MutationEntryResult {
82
86
  readonly id: string;
@@ -102,6 +106,26 @@ export interface MutationEntryResult {
102
106
  /** human sentence for the report line — names the undefended property on a green suite. */
103
107
  readonly detail: string;
104
108
  }
109
+ export type InternalRunnerAttemptOutcome = 'completed' | 'runner-internal-error';
110
+ export interface InternalRunnerAttempt {
111
+ readonly attempt: 1 | 2;
112
+ readonly outcome: InternalRunnerAttemptOutcome;
113
+ readonly detail: string;
114
+ }
115
+ export interface InternalRunnerRetryResult<T> {
116
+ /** The completed attempt's value. null means both attempts threw internally. */
117
+ readonly value: T | null;
118
+ readonly attempts: readonly InternalRunnerAttempt[];
119
+ /** Closed by construction: a runner receives either zero retries or exactly one. */
120
+ readonly internalRetries: 0 | 1;
121
+ /** Named reason consumed by the existing no-exit → INCONCLUSIVE arm. */
122
+ readonly failureReason?: `runner-internal-error: ${string}`;
123
+ }
124
+ /**
125
+ * Run one internal runner invocation. Only a THROWN internal error is retried; normal green/red
126
+ * observations and ordinary no-exit observations are values and therefore never retried.
127
+ */
128
+ export declare function runWithOneInternalRetry<T>(runner: () => T): InternalRunnerRetryResult<T>;
105
129
  export interface ParsedRegistry {
106
130
  readonly registry: MutationRegistry | null;
107
131
  /** every defect found — ANY error makes the registry unusable (a half-valid registry that runs
@@ -215,50 +239,29 @@ export interface RunFailureClassification {
215
239
  * and never silently passes on output it cannot read.
216
240
  */
217
241
  export declare function classifyRunFailure(rawOutput: string): RunFailureClassification;
242
+ export type BaselineAttributionSource = 'node-test' | 'vitest' | 'unparseable';
243
+ export interface BaselineAttribution {
244
+ readonly parsedFrom: BaselineAttributionSource;
245
+ /** Package-relative failing paths, in first-seen order. */
246
+ readonly failingFiles: readonly string[];
247
+ /** Failing paths that match a registry file exactly (or by package-relative suffix). */
248
+ readonly covered: readonly string[];
249
+ /** Failing paths with no matching registry file. */
250
+ readonly extraneous: readonly string[];
251
+ }
252
+ /** Parse the failing FILE paths already exposed by supported node --test and vitest shapes. */
253
+ export declare function attributeBaselineRedness(rawOutput: string, registryFiles: readonly string[]): BaselineAttribution;
254
+ export type BaselineFailureReason = 'runner-internal-error' | 'runner-no-exit' | 'extraneous-red-in-allowlist' | 'baseline-red-covered-files' | 'baseline-red-files-unparseable';
218
255
  export interface BaselineResult {
219
256
  readonly ok: boolean;
220
257
  readonly detail: string;
258
+ readonly reason?: BaselineFailureReason;
221
259
  }
222
260
  /**
223
261
  * A RED baseline in the scratch copy is a SETUP error, never a mutation result: every subsequent
224
262
  * "red under mutation" would be noise, and every "green" a lie about an unrunnable copy.
225
263
  */
226
- export declare function classifyBaseline(exitCode: number | null, runFailureReason?: string): BaselineResult;
227
- /**
228
- * Classify one entry's observation.
229
- *
230
- * PRECEDENCE LATTICE (checked top to bottom; each verdict is justified by what it invalidates
231
- * BELOW it — a verdict may only outrank another when it makes that other's evidence meaningless):
232
- * 1. NOT_APPLIED — nothing was mutated, so no run observation means anything;
233
- * 2. MUTATION_UNPARSEABLE — the mutated file is not even syntactically a module; a non-parsing
234
- * file never RUNS, so no load- or count-signal can exist (which is
235
- * why it sits above MUTATION_LOAD_FATAL);
236
- * 3. MUTATION_LOAD_FATAL — the run happened, but its own output says a test FILE died at
237
- * load: the redness is structural and every count from that same run
238
- * is non-attributable — so it outranks every count-based verdict AND
239
- * the flaky-rebaseline check (a structurally-broken run needs no
240
- * attribution analysis);
241
- * 4. INCONCLUSIVE (no exit) — the run produced nothing to classify at all;
242
- * 5. RECEIPT_MISMATCH — the harness declared its own receipt contract broken, which
243
- * invalidates BOTH the green reading and every count-based reading
244
- * of the same run;
245
- * 6. UNDEFENDED (exit 0) — the disjoint GREEN arm: mutually exclusive with every red-based
246
- * verdict below;
247
- * 7. INCONCLUSIVE (unrecognised output) — red, but the shape is unreadable: counts parsed out of
248
- * unrecognised output must not reach BELOW_MIN/OVER_FAILING/PROVEN;
249
- * 8. INCONCLUSIVE (flaky rebaseline) — red, readable, but not attributable;
250
- * 9. BELOW_MIN → 10. OVER_FAILING — reliable-count contract checks, both failing;
251
- * 11. PROVEN — applied, red, behavioural, attributable, within bounds.
252
- *
253
- * THE DROP DECISION (SPEC §Reporting, decided here + justified): `failing < observed` but still
254
- * `>= minFailing` is a LOUD WARNING, not a failure. Two reasons, both load-bearing:
255
- * • the count is a BEST-EFFORT secondary parsed from runner output — making it verdict-deciding
256
- * would let a count-parse failure flip red/green, which the design decisions forbid;
257
- * • `observed` is a historical measurement, `minFailing` is the entry's declared CONTRACT. A
258
- * contract violation fails (BELOW_MIN — only ever on a reliable count); history drifting down
259
- * while the contract still holds is the early warning, reported loudly so a human re-pins
260
- * `observed` or investigates — silently normalising it would erase the signal.
261
- */
264
+ export declare function classifyBaseline(exitCode: number | null, runFailureReason?: string, attribution?: BaselineAttribution): BaselineResult;
262
265
  export declare function classifyMutationOutcome(obs: MutationObservation): MutationEntryResult;
263
266
  /** Exit contract: 0 all proven · 1 any entry failed (or red baseline) · (2 = usage/setup, CLI-side). */
264
267
  export declare function mutationGateExitCode(results: readonly MutationEntryResult[], baselineOk: boolean): number;
@@ -1 +1 @@
1
- {"version":3,"file":"mutation-gate.d.ts","sourceRoot":"","sources":["../src/mutation-gate.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"mutation-gate.d.ts","sourceRoot":"","sources":["../src/mutation-gate.ts"],"names":[],"mappings":"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"}
@@ -19,6 +19,39 @@
19
19
  // 3. Never mutate the working tree (enforced by the executor: scratch copy only).
20
20
  // 4. The gate has its own discrimination proof (the deliberately-undefended fixture in
21
21
  // harness-cli/test/fixtures/mutation-gate-undefended — excluded from any real registry).
22
+ function internalRunnerErrorHead(error) {
23
+ const raw = error instanceof Error ? error.message : String(error);
24
+ const firstLine = raw.split(/\r?\n/, 1)[0]?.trim() || 'unknown internal runner error';
25
+ return Array.from(firstLine).slice(0, 160).join('');
26
+ }
27
+ /**
28
+ * Run one internal runner invocation. Only a THROWN internal error is retried; normal green/red
29
+ * observations and ordinary no-exit observations are values and therefore never retried.
30
+ */
31
+ export function runWithOneInternalRetry(runner) {
32
+ const attempts = [];
33
+ for (const attempt of [1, 2]) {
34
+ try {
35
+ const value = runner();
36
+ attempts.push({ attempt, outcome: 'completed', detail: `attempt ${attempt}: completed` });
37
+ return { value, attempts, internalRetries: attempt === 1 ? 0 : 1 };
38
+ }
39
+ catch (error) {
40
+ const head = internalRunnerErrorHead(error);
41
+ attempts.push({
42
+ attempt,
43
+ outcome: 'runner-internal-error',
44
+ detail: `attempt ${attempt}: runner-internal-error: ${head}`,
45
+ });
46
+ }
47
+ }
48
+ return {
49
+ value: null,
50
+ attempts,
51
+ internalRetries: 1,
52
+ failureReason: 'runner-internal-error: persistent after 2/2 attempts',
53
+ };
54
+ }
22
55
  // ── Registry parsing — declarative DATA, validated loudly ─────────────────────────────────────
23
56
  const SAFE_ID = /^[a-z0-9][a-z0-9-]{0,79}$/;
24
57
  /** same shape as discrimination-gate's path rule: package-relative, no traversal, no metacharacters. */
@@ -355,17 +388,81 @@ export function classifyRunFailure(rawOutput) {
355
388
  evidence: 'output matches neither node --test (TAP + # duration_ms) nor vitest (RUN v / Test Files) — no classifier for this runner; structural-vs-behavioural redness cannot be told apart, so the verdict may not be PROVEN',
356
389
  };
357
390
  }
391
+ function normaliseReportedFile(raw) {
392
+ let value = raw.trim().replace(/^['"]|['"]$/g, '').replace(/:\d+(?::\d+)?$/, '');
393
+ value = value.replace(/\\/g, '/');
394
+ const testSegment = value.lastIndexOf('/test/');
395
+ if (testSegment >= 0)
396
+ value = value.slice(testSegment + 1);
397
+ if (!value.includes('/') || !/\.(?:test|spec)\.[cm]?[jt]sx?$/.test(value))
398
+ return null;
399
+ return value;
400
+ }
401
+ /** Parse the failing FILE paths already exposed by supported node --test and vitest shapes. */
402
+ export function attributeBaselineRedness(rawOutput, registryFiles) {
403
+ const output = stripSgr(rawOutput);
404
+ const files = [];
405
+ const add = (raw) => {
406
+ const file = normaliseReportedFile(raw);
407
+ if (file !== null && !files.includes(file))
408
+ files.push(file);
409
+ };
410
+ const vitestMatches = [...output.matchAll(/^\s*FAIL\s+(\S+)/gm)];
411
+ for (const match of vitestMatches)
412
+ add(match[1] ?? '');
413
+ const tapMatches = [...output.matchAll(/^not ok \d+\s+-\s+(.+)$/gm)];
414
+ for (const match of tapMatches)
415
+ add(match[1] ?? '');
416
+ for (const match of output.matchAll(/^\s*location:\s*['"]([^'"]+)['"]\s*$/gm)) {
417
+ add(match[1] ?? '');
418
+ }
419
+ const parsedFrom = files.length === 0
420
+ ? 'unparseable'
421
+ : vitestMatches.length > 0 ? 'vitest' : 'node-test';
422
+ if (parsedFrom === 'unparseable') {
423
+ return { parsedFrom, failingFiles: [], covered: [], extraneous: [] };
424
+ }
425
+ const normalisedRegistry = registryFiles
426
+ .map((file) => normaliseReportedFile(file) ?? file.replace(/\\/g, '/'));
427
+ const covered = files.filter((file) => normalisedRegistry.some((registryFile) => file === registryFile || file.endsWith(`/${registryFile}`) || registryFile.endsWith(`/${file}`)));
428
+ const extraneous = files.filter((file) => !covered.includes(file));
429
+ return { parsedFrom, failingFiles: files, covered, extraneous };
430
+ }
358
431
  /**
359
432
  * A RED baseline in the scratch copy is a SETUP error, never a mutation result: every subsequent
360
433
  * "red under mutation" would be noise, and every "green" a lie about an unrunnable copy.
361
434
  */
362
- export function classifyBaseline(exitCode, runFailureReason) {
435
+ export function classifyBaseline(exitCode, runFailureReason, attribution) {
363
436
  if (exitCode === 0)
364
437
  return { ok: true, detail: 'baseline suite green in the scratch copy' };
365
438
  if (exitCode === null) {
366
- return { ok: false, detail: `baseline suite produced no exit code (${runFailureReason ?? 'unknown timeout/spawn failure'}) the copy is not runnable; fix the copy, do not read this as a mutation result` };
439
+ const internal = runFailureReason?.startsWith('runner-internal-error:') === true;
440
+ return {
441
+ ok: false,
442
+ reason: internal ? 'runner-internal-error' : 'runner-no-exit',
443
+ detail: `baseline INCONCLUSIVE — suite produced no exit code (${runFailureReason ?? 'unknown timeout/spawn failure'}) — the copy is not runnable; do not read this as a mutation result`,
444
+ };
445
+ }
446
+ if (attribution === undefined || attribution.parsedFrom === 'unparseable') {
447
+ return {
448
+ ok: false,
449
+ reason: 'baseline-red-files-unparseable',
450
+ detail: `baseline suite RED (exit ${exitCode}) in the UNMUTATED scratch copy — failing files: unparseable from runner output — the broken copy cannot prove anything`,
451
+ };
452
+ }
453
+ const failing = `failing files: ${attribution.failingFiles.join(', ')}`;
454
+ if (attribution.extraneous.length > 0 && attribution.covered.length === 0) {
455
+ return {
456
+ ok: false,
457
+ reason: 'extraneous-red-in-allowlist',
458
+ detail: `baseline suite RED (exit ${exitCode}) in the UNMUTATED scratch copy — ${failing} — extraneous red in the testCommand allowlist; the registry entries themselves are not disproven`,
459
+ };
367
460
  }
368
- return { ok: false, detail: `baseline suite RED (exit ${exitCode}) in the UNMUTATED scratch copy — a broken copy cannot prove anything; fix the copy (node_modules link? path-dependent test?) before trusting any verdict` };
461
+ return {
462
+ ok: false,
463
+ reason: 'baseline-red-covered-files',
464
+ detail: `baseline suite RED (exit ${exitCode}) in the UNMUTATED scratch copy — ${failing} — broken-copy baseline redness touches registry-covered files; fix the copy before evaluating mutations`,
465
+ };
369
466
  }
370
467
  // ── Classification (rules 1 + 2, and the drop decision) ───────────────────────────────────────
371
468
  /**
@@ -403,7 +500,7 @@ export function classifyBaseline(exitCode, runFailureReason) {
403
500
  * while the contract still holds is the early warning, reported loudly so a human re-pins
404
501
  * `observed` or investigates — silently normalising it would erase the signal.
405
502
  */
406
- export function classifyMutationOutcome(obs) {
503
+ function classifyMutationOutcomeWithoutAttemptLog(obs) {
407
504
  const e = obs.entry;
408
505
  const minFailing = e.minFailing ?? 1;
409
506
  const base = {
@@ -499,12 +596,15 @@ export function classifyMutationOutcome(obs) {
499
596
  // not come back green, so the suite is flaky and an unrelated neighbour may be what went red.
500
597
  // Not attributable ⇒ INCONCLUSIVE (a failure, never a pass).
501
598
  if (obs.rebaselineExitCode !== undefined && obs.rebaselineExitCode !== 0) {
599
+ const failing = obs.rebaselineAttribution === undefined || obs.rebaselineAttribution.parsedFrom === 'unparseable'
600
+ ? 'failing files: unparseable from runner output'
601
+ : `failing files: ${obs.rebaselineAttribution.failingFiles.join(', ')}`;
502
602
  return {
503
603
  ...base,
504
604
  applied: true,
505
605
  verdict: 'INCONCLUSIVE',
506
606
  drop: false,
507
- detail: `suite red under the mutation BUT the restored baseline did not reproduce green (${obs.rebaselineExitCode === null ? `no exit code: ${obs.rebaselineFailureReason ?? 'unknown timeout / spawn failure'}` : `exit ${obs.rebaselineExitCode}`}) — the suite is flaky; the redness is not attributable to the protection and may be an unrelated neighbour`,
607
+ detail: `suite red under the mutation BUT the restored baseline did not reproduce green (${obs.rebaselineExitCode === null ? `no exit code: ${obs.rebaselineFailureReason ?? 'unknown timeout / spawn failure'}` : `exit ${obs.rebaselineExitCode}`}) — mutation did not revert / flaky restored-tree route; ${failing}; the redness is not attributable to the protection`,
508
608
  };
509
609
  }
510
610
  if (obs.failingCount !== null && obs.failingCount < minFailing) {
@@ -540,6 +640,12 @@ export function classifyMutationOutcome(obs) {
540
640
  : `suite red under the mutation (${obs.failingCount === null ? 'failing count unavailable — exit code is the verdict' : `${obs.failingCount} failing`}) — the test discriminates`,
541
641
  };
542
642
  }
643
+ export function classifyMutationOutcome(obs) {
644
+ const result = classifyMutationOutcomeWithoutAttemptLog(obs);
645
+ if (obs.internalAttemptLog === undefined)
646
+ return result;
647
+ return { ...result, detail: `${result.detail}; ${obs.internalAttemptLog}` };
648
+ }
543
649
  /** Verdicts that fail the gate. INCONCLUSIVE and NOT_APPLIED fail (inconclusive ≠ pass). */
544
650
  const FAILING_VERDICTS = new Set(['UNDEFENDED', 'RECEIPT_MISMATCH', 'NOT_APPLIED', 'BELOW_MIN', 'MUTATION_UNPARSEABLE', 'MUTATION_LOAD_FATAL', 'OVER_FAILING', 'INCONCLUSIVE']);
545
651
  /** Exit contract: 0 all proven · 1 any entry failed (or red baseline) · (2 = usage/setup, CLI-side). */