@mmnto/cli 1.69.0 → 1.70.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/spine-llm-adapters.d.ts +273 -0
- package/dist/commands/spine-llm-adapters.d.ts.map +1 -0
- package/dist/commands/spine-llm-adapters.js +549 -0
- package/dist/commands/spine-llm-adapters.js.map +1 -0
- package/dist/commands/spine-llm-adapters.test.d.ts +2 -0
- package/dist/commands/spine-llm-adapters.test.d.ts.map +1 -0
- package/dist/commands/spine-llm-adapters.test.js +395 -0
- package/dist/commands/spine-llm-adapters.test.js.map +1 -0
- package/dist/commands/spine-windtunnel.d.ts +54 -0
- package/dist/commands/spine-windtunnel.d.ts.map +1 -1
- package/dist/commands/spine-windtunnel.js +91 -7
- package/dist/commands/spine-windtunnel.js.map +1 -1
- package/dist/commands/spine-windtunnel.test.js +72 -1
- package/dist/commands/spine-windtunnel.test.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { wrapUntrustedXml } from '@mmnto/totem';
|
|
3
|
+
import { assertLiveLlmAllowed, assertPipelineProductive, buildClassifyUserPrompt, buildExtractUserPrompt, buildReplayProvenance, LiveDraftClassifier, LiveDraftExtractor, LiveLlmInCiError, LlmAdapterConfigError, MINER_CLASSIFY_SYSTEM_PROMPT, MINER_EXTRACT_SYSTEM_PROMPT, parseClassifierOutput, parseExtractorOutput, SystemicPipelineError, verifyLlmAdapterConfig, wrapUntrusted, } from './spine-llm-adapters.js';
|
|
4
|
+
import { ClassifierResultLocalSchema, computeArtifactHash, RecordingDraftClassifier, RecordingDraftExtractor, ReplayArtifactSchema, ReplayDraftClassifier, ReplayDraftExtractor, ReplayProvenanceSchema, ReplayRecordSink, } from './spine-llm-replay.js';
|
|
5
|
+
const MERGE_SHA = 'a'.repeat(40);
|
|
6
|
+
// ─── Stub LLM seam (deterministic; NO network, NO real LLM) ──────────────────
|
|
7
|
+
function stubInvoke(content) {
|
|
8
|
+
return () => Promise.resolve({ content, inputTokens: null, outputTokens: null, durationMs: 0 });
|
|
9
|
+
}
|
|
10
|
+
/** A seam that throws — models a dead provider / network failure on every call. */
|
|
11
|
+
function throwingInvoke() {
|
|
12
|
+
return () => Promise.reject(new Error('boom: provider unreachable'));
|
|
13
|
+
}
|
|
14
|
+
/** A seam whose output depends on the prompt — lets one stub serve many inputs. */
|
|
15
|
+
function routedInvoke(route) {
|
|
16
|
+
return (opts) => Promise.resolve({
|
|
17
|
+
content: route(opts.prompt),
|
|
18
|
+
inputTokens: null,
|
|
19
|
+
outputTokens: null,
|
|
20
|
+
durationMs: 0,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
// Construct live adapters with a NON-CI env by default so the fold-H guard does
|
|
24
|
+
// not trip when this very suite runs under CI.
|
|
25
|
+
const NO_CI = {};
|
|
26
|
+
function makeExtractor(invoke, env = NO_CI) {
|
|
27
|
+
return new LiveDraftExtractor({
|
|
28
|
+
invoke,
|
|
29
|
+
model: 'test-model',
|
|
30
|
+
cwd: '/tmp',
|
|
31
|
+
totemDir: '.totem',
|
|
32
|
+
provider: 'anthropic',
|
|
33
|
+
credentialPresent: true,
|
|
34
|
+
env,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
function makeClassifier(invoke, env = NO_CI) {
|
|
38
|
+
return new LiveDraftClassifier({
|
|
39
|
+
invoke,
|
|
40
|
+
model: 'test-model',
|
|
41
|
+
cwd: '/tmp',
|
|
42
|
+
totemDir: '.totem',
|
|
43
|
+
provider: 'anthropic',
|
|
44
|
+
credentialPresent: true,
|
|
45
|
+
env,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
function content(opts) {
|
|
49
|
+
return {
|
|
50
|
+
pr: opts?.pr ?? 100,
|
|
51
|
+
mergeCommitSha: MERGE_SHA,
|
|
52
|
+
threads: opts?.threads ?? [
|
|
53
|
+
{
|
|
54
|
+
path: 'a.ts',
|
|
55
|
+
isResolved: false,
|
|
56
|
+
isOutdated: false,
|
|
57
|
+
comments: [{ author: 'jane', body: 'no exec' }],
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function draft(dslSource = '**Pattern:** no-foo') {
|
|
63
|
+
return {
|
|
64
|
+
provenance: { mergedPr: 100, reviewThread: 'pulls/100/comments', commitSha: MERGE_SHA },
|
|
65
|
+
dslSource,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
// ─── 1. Extractor output parse ────────────────────────
|
|
69
|
+
describe('parseExtractorOutput', () => {
|
|
70
|
+
it('parses a JSON array of DSL bodies', () => {
|
|
71
|
+
expect(parseExtractorOutput('["**Pattern:** a", "**Pattern:** b"]')).toEqual([
|
|
72
|
+
'**Pattern:** a',
|
|
73
|
+
'**Pattern:** b',
|
|
74
|
+
]);
|
|
75
|
+
});
|
|
76
|
+
it('treats the NONE sentinel (any case) as an empty draft', () => {
|
|
77
|
+
expect(parseExtractorOutput('NONE')).toEqual([]);
|
|
78
|
+
expect(parseExtractorOutput(' none ')).toEqual([]);
|
|
79
|
+
});
|
|
80
|
+
it('returns [] on empty / non-JSON / non-array output (fail-soft, never throws)', () => {
|
|
81
|
+
expect(parseExtractorOutput('')).toEqual([]);
|
|
82
|
+
expect(parseExtractorOutput('here are some lessons:')).toEqual([]);
|
|
83
|
+
expect(parseExtractorOutput('{"disposition":"structural"}')).toEqual([]);
|
|
84
|
+
expect(parseExtractorOutput('42')).toEqual([]);
|
|
85
|
+
});
|
|
86
|
+
it('strips a markdown code fence and parses the inner JSON', () => {
|
|
87
|
+
expect(parseExtractorOutput('```json\n["**Pattern:** x"]\n```')).toEqual(['**Pattern:** x']);
|
|
88
|
+
});
|
|
89
|
+
it('filters non-string / empty / whitespace elements and trims', () => {
|
|
90
|
+
expect(parseExtractorOutput('["**Pattern:** a", 7, "", " ", " **Pattern:** b "]')).toEqual([
|
|
91
|
+
'**Pattern:** a',
|
|
92
|
+
'**Pattern:** b',
|
|
93
|
+
]);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
// ─── 2. Classifier output parse (fold G — closed set) ─
|
|
97
|
+
describe('parseClassifierOutput (fold G)', () => {
|
|
98
|
+
it('returns classified for a single unambiguous structural/behavioral label', () => {
|
|
99
|
+
expect(parseClassifierOutput('{"disposition":"structural"}')).toEqual({
|
|
100
|
+
disposition: 'structural',
|
|
101
|
+
dispositionSource: 'classified',
|
|
102
|
+
});
|
|
103
|
+
expect(parseClassifierOutput('```json\n{"disposition":"behavioral"}\n```')).toEqual({
|
|
104
|
+
disposition: 'behavioral',
|
|
105
|
+
dispositionSource: 'classified',
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
it('safe-defaults on refusal / invalid JSON / prose', () => {
|
|
109
|
+
expect(parseClassifierOutput('I cannot classify this.')).toEqual({
|
|
110
|
+
disposition: 'behavioral',
|
|
111
|
+
dispositionSource: 'error-default',
|
|
112
|
+
});
|
|
113
|
+
expect(parseClassifierOutput('')).toEqual({
|
|
114
|
+
disposition: 'behavioral',
|
|
115
|
+
dispositionSource: 'error-default',
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
it('safe-defaults on missing / out-of-set / wrong-typed / multi-valued label', () => {
|
|
119
|
+
const sd = { disposition: 'behavioral', dispositionSource: 'error-default' };
|
|
120
|
+
expect(parseClassifierOutput('{"foo":"bar"}')).toEqual(sd); // missing label
|
|
121
|
+
expect(parseClassifierOutput('{"disposition":"maybe"}')).toEqual(sd); // out of set
|
|
122
|
+
expect(parseClassifierOutput('{"disposition":["structural","behavioral"]}')).toEqual(sd); // multi
|
|
123
|
+
expect(parseClassifierOutput('["structural"]')).toEqual(sd); // array, not object
|
|
124
|
+
expect(parseClassifierOutput('null')).toEqual(sd);
|
|
125
|
+
});
|
|
126
|
+
it('never mints the illegal {structural, error-default} pair (the local schema rejects it)', () => {
|
|
127
|
+
expect(() => ClassifierResultLocalSchema.parse({
|
|
128
|
+
disposition: 'structural',
|
|
129
|
+
dispositionSource: 'error-default',
|
|
130
|
+
})).toThrow();
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
// ─── 2b. Fail-loud on UNEXPECTED parse errors (Tenet 4) ──────────────────────
|
|
134
|
+
describe('fail-loud on unexpected (non-SyntaxError) parse errors', () => {
|
|
135
|
+
afterEach(() => vi.restoreAllMocks());
|
|
136
|
+
it('parseExtractorOutput rethrows a non-SyntaxError (a real bug fails loud, not fail-soft)', () => {
|
|
137
|
+
vi.spyOn(JSON, 'parse').mockImplementationOnce(() => {
|
|
138
|
+
throw new TypeError('unexpected non-syntax failure');
|
|
139
|
+
});
|
|
140
|
+
expect(() => parseExtractorOutput('["**Pattern:** a"]')).toThrow(TypeError);
|
|
141
|
+
});
|
|
142
|
+
it('parseClassifierOutput rethrows a non-SyntaxError', () => {
|
|
143
|
+
vi.spyOn(JSON, 'parse').mockImplementationOnce(() => {
|
|
144
|
+
throw new TypeError('unexpected non-syntax failure');
|
|
145
|
+
});
|
|
146
|
+
expect(() => parseClassifierOutput('{"disposition":"structural"}')).toThrow(TypeError);
|
|
147
|
+
});
|
|
148
|
+
it('an unexpected parser error propagates out of draft (fail-loud on a bug; the per-PR catch covers the INVOKE only)', async () => {
|
|
149
|
+
const ext = makeExtractor(stubInvoke('["**Pattern:** a"]'));
|
|
150
|
+
vi.spyOn(JSON, 'parse').mockImplementationOnce(() => {
|
|
151
|
+
throw new TypeError('unexpected non-syntax failure');
|
|
152
|
+
});
|
|
153
|
+
await expect(ext.draft(content())).rejects.toThrow(TypeError);
|
|
154
|
+
// the invoke SUCCEEDED — it is not miscounted as a dead-provider failure.
|
|
155
|
+
expect(ext.succeeded).toBe(1);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
// ─── 3. Live extractor (stubbed seam) ─────────────────
|
|
159
|
+
describe('LiveDraftExtractor', () => {
|
|
160
|
+
it('drafts parsed DSL bodies from the LLM and counts a success', async () => {
|
|
161
|
+
const ext = makeExtractor(stubInvoke('["**Pattern:** no-exec"]'));
|
|
162
|
+
await expect(ext.draft(content())).resolves.toEqual(['**Pattern:** no-exec']);
|
|
163
|
+
expect(ext.attempts).toBe(1);
|
|
164
|
+
expect(ext.succeeded).toBe(1);
|
|
165
|
+
});
|
|
166
|
+
it('returns [] and counts a failure when the live invoke throws (per-PR fail-soft)', async () => {
|
|
167
|
+
const ext = makeExtractor(throwingInvoke());
|
|
168
|
+
await expect(ext.draft(content())).resolves.toEqual([]);
|
|
169
|
+
expect(ext.attempts).toBe(1);
|
|
170
|
+
expect(ext.succeeded).toBe(0);
|
|
171
|
+
});
|
|
172
|
+
it('a successful-but-empty (NONE) call still counts as succeeded (genuine sparsity ≠ failure)', async () => {
|
|
173
|
+
const ext = makeExtractor(stubInvoke('NONE'));
|
|
174
|
+
await expect(ext.draft(content())).resolves.toEqual([]);
|
|
175
|
+
expect(ext.succeeded).toBe(1);
|
|
176
|
+
});
|
|
177
|
+
it('isolates a per-item failure — other items still succeed, no throw', async () => {
|
|
178
|
+
const ext = makeExtractor(routedInvoke((p) => p.includes('666')
|
|
179
|
+
? (() => {
|
|
180
|
+
throw new Error('x');
|
|
181
|
+
})()
|
|
182
|
+
: '["**Pattern:** ok"]'));
|
|
183
|
+
await expect(ext.draft(content({ pr: 1 }))).resolves.toEqual(['**Pattern:** ok']);
|
|
184
|
+
await expect(ext.draft(content({ pr: 666 }))).resolves.toEqual([]);
|
|
185
|
+
expect(ext.attempts).toBe(2);
|
|
186
|
+
expect(ext.succeeded).toBe(1);
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
// ─── 4. Live classifier (stubbed seam) ────────────────
|
|
190
|
+
describe('LiveDraftClassifier', () => {
|
|
191
|
+
it('classifies from the LLM and counts a success', async () => {
|
|
192
|
+
const clf = makeClassifier(stubInvoke('{"disposition":"structural"}'));
|
|
193
|
+
await expect(clf.classify(draft())).resolves.toEqual({
|
|
194
|
+
disposition: 'structural',
|
|
195
|
+
dispositionSource: 'classified',
|
|
196
|
+
});
|
|
197
|
+
expect(clf.succeeded).toBe(1);
|
|
198
|
+
});
|
|
199
|
+
it('safe-defaults and counts a failure when the invoke throws', async () => {
|
|
200
|
+
const clf = makeClassifier(throwingInvoke());
|
|
201
|
+
await expect(clf.classify(draft())).resolves.toEqual({
|
|
202
|
+
disposition: 'behavioral',
|
|
203
|
+
dispositionSource: 'error-default',
|
|
204
|
+
});
|
|
205
|
+
expect(clf.attempts).toBe(1);
|
|
206
|
+
expect(clf.succeeded).toBe(0);
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
// ─── 5. Fold H — no live LLM in CI ────────────────────
|
|
210
|
+
describe('assertLiveLlmAllowed (fold H)', () => {
|
|
211
|
+
it('throws under CI without the explicit override', () => {
|
|
212
|
+
expect(() => assertLiveLlmAllowed({ CI: 'true' })).toThrow(LiveLlmInCiError);
|
|
213
|
+
});
|
|
214
|
+
it('allows CI with ALLOW_LIVE_LLM_IN_CI, and any non-CI env', () => {
|
|
215
|
+
expect(() => assertLiveLlmAllowed({ CI: 'true', ALLOW_LIVE_LLM_IN_CI: '1' })).not.toThrow();
|
|
216
|
+
expect(() => assertLiveLlmAllowed({})).not.toThrow();
|
|
217
|
+
});
|
|
218
|
+
it('a live adapter refuses to construct under CI (the guard runs at construction)', () => {
|
|
219
|
+
expect(() => makeExtractor(stubInvoke('NONE'), { CI: 'true' })).toThrow(LiveLlmInCiError);
|
|
220
|
+
expect(() => makeClassifier(stubInvoke('NONE'), { CI: 'true' })).toThrow(LiveLlmInCiError);
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
// ─── 6. Fold C — construction-time config + end-of-run floor ──────────────────
|
|
224
|
+
describe('verifyLlmAdapterConfig (fold C, construction-time)', () => {
|
|
225
|
+
const ok = { provider: 'anthropic', model: 'm', credentialPresent: true, systemPrompt: 'sp' };
|
|
226
|
+
it('passes when all preconditions are present', () => {
|
|
227
|
+
expect(() => verifyLlmAdapterConfig(ok)).not.toThrow();
|
|
228
|
+
});
|
|
229
|
+
it('throws naming every missing precondition (no live call)', () => {
|
|
230
|
+
try {
|
|
231
|
+
verifyLlmAdapterConfig({
|
|
232
|
+
provider: '',
|
|
233
|
+
model: '',
|
|
234
|
+
credentialPresent: false,
|
|
235
|
+
systemPrompt: '',
|
|
236
|
+
});
|
|
237
|
+
throw new Error('expected throw');
|
|
238
|
+
}
|
|
239
|
+
catch (err) {
|
|
240
|
+
expect(err).toBeInstanceOf(LlmAdapterConfigError);
|
|
241
|
+
expect(err.problems).toHaveLength(4);
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
it('throws on a missing credential alone (the dead-provider precursor)', () => {
|
|
245
|
+
expect(() => verifyLlmAdapterConfig({ ...ok, credentialPresent: false })).toThrow(LlmAdapterConfigError);
|
|
246
|
+
});
|
|
247
|
+
it('the live adapter constructor enforces the FULL check (greptile #2211) — credential-absent throws AT construction', () => {
|
|
248
|
+
const deps = {
|
|
249
|
+
invoke: stubInvoke('NONE'),
|
|
250
|
+
model: 'm',
|
|
251
|
+
cwd: '/tmp',
|
|
252
|
+
totemDir: '.totem',
|
|
253
|
+
provider: 'anthropic',
|
|
254
|
+
credentialPresent: false,
|
|
255
|
+
env: NO_CI,
|
|
256
|
+
};
|
|
257
|
+
expect(() => new LiveDraftExtractor(deps)).toThrow(LlmAdapterConfigError);
|
|
258
|
+
expect(() => new LiveDraftClassifier(deps)).toThrow(LlmAdapterConfigError);
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
describe('assertPipelineProductive (fold C, end-of-run floor)', () => {
|
|
262
|
+
it('throws when ≥1 attempted and 0 succeeded (dead provider)', () => {
|
|
263
|
+
expect(() => assertPipelineProductive({ attempted: 5, succeeded: 0 })).toThrow(SystemicPipelineError);
|
|
264
|
+
});
|
|
265
|
+
it('does NOT throw on genuine sparsity (calls succeeded, just empty)', () => {
|
|
266
|
+
expect(() => assertPipelineProductive({ attempted: 5, succeeded: 5 })).not.toThrow();
|
|
267
|
+
expect(() => assertPipelineProductive({ attempted: 5, succeeded: 1 })).not.toThrow();
|
|
268
|
+
});
|
|
269
|
+
it('does NOT throw when nothing was attempted', () => {
|
|
270
|
+
expect(() => assertPipelineProductive({ attempted: 0, succeeded: 0 })).not.toThrow();
|
|
271
|
+
});
|
|
272
|
+
it('integrates: an all-failing extractor trips the floor; an all-empty one does not', async () => {
|
|
273
|
+
const dead = makeExtractor(throwingInvoke());
|
|
274
|
+
for (const pr of [1, 2, 3])
|
|
275
|
+
await dead.draft(content({ pr }));
|
|
276
|
+
expect(() => assertPipelineProductive({ attempted: dead.attempts, succeeded: dead.succeeded })).toThrow(SystemicPipelineError);
|
|
277
|
+
const sparse = makeExtractor(stubInvoke('NONE'));
|
|
278
|
+
for (const pr of [1, 2, 3])
|
|
279
|
+
await sparse.draft(content({ pr }));
|
|
280
|
+
expect(() => assertPipelineProductive({ attempted: sparse.attempts, succeeded: sparse.succeeded })).not.toThrow();
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
// ─── 7. Fold F — provenance + integrity coupling ──────
|
|
284
|
+
describe('buildReplayProvenance (fold F)', () => {
|
|
285
|
+
const input = {
|
|
286
|
+
extractSystemPrompt: MINER_EXTRACT_SYSTEM_PROMPT,
|
|
287
|
+
classifySystemPrompt: MINER_CLASSIFY_SYSTEM_PROMPT,
|
|
288
|
+
provider: 'anthropic',
|
|
289
|
+
model: 'claude-x',
|
|
290
|
+
temperature: 0,
|
|
291
|
+
orchestratorVersion: 'orch-1',
|
|
292
|
+
totemVersion: '1.69.0',
|
|
293
|
+
};
|
|
294
|
+
it('is deterministic and schema-valid', () => {
|
|
295
|
+
const a = buildReplayProvenance(input);
|
|
296
|
+
const b = buildReplayProvenance(input);
|
|
297
|
+
expect(a).toEqual(b);
|
|
298
|
+
expect(() => ReplayProvenanceSchema.parse(a)).not.toThrow();
|
|
299
|
+
expect(a.provider).toBe('anthropic');
|
|
300
|
+
expect(a.adapterKind).toBe('extractor+classifier');
|
|
301
|
+
});
|
|
302
|
+
it('a prompt edit flips both prompt hashes (forces a re-record)', () => {
|
|
303
|
+
const base = buildReplayProvenance(input);
|
|
304
|
+
const edited = buildReplayProvenance({
|
|
305
|
+
...input,
|
|
306
|
+
extractSystemPrompt: MINER_EXTRACT_SYSTEM_PROMPT + ' edit',
|
|
307
|
+
});
|
|
308
|
+
expect(edited.promptTemplateHash).not.toBe(base.promptTemplateHash);
|
|
309
|
+
expect(edited.systemPromptHash).not.toBe(base.systemPromptHash);
|
|
310
|
+
});
|
|
311
|
+
it('a provenance edit flips the whole-artifact integrity hash (fold F gate)', () => {
|
|
312
|
+
const sink = new ReplayRecordSink();
|
|
313
|
+
const base = computeArtifactHash(sink.freeze(buildReplayProvenance(input)));
|
|
314
|
+
const edited = computeArtifactHash(sink.freeze(buildReplayProvenance({ ...input, model: 'claude-y' })));
|
|
315
|
+
expect(edited).not.toBe(base);
|
|
316
|
+
});
|
|
317
|
+
});
|
|
318
|
+
// ─── 8. End-to-end: live adapter composes with the 5b-i record/replay scaffold ─
|
|
319
|
+
describe('live adapter ∘ 5b-i record/replay', () => {
|
|
320
|
+
it('records a live draft then replays it byte-identically, zero further LLM calls', async () => {
|
|
321
|
+
const sink = new ReplayRecordSink();
|
|
322
|
+
const live = makeExtractor(stubInvoke('["**Pattern:** no-exec"]'));
|
|
323
|
+
const recording = new RecordingDraftExtractor(live, sink);
|
|
324
|
+
const recorded = await recording.draft(content());
|
|
325
|
+
const provenance = buildReplayProvenance({
|
|
326
|
+
extractSystemPrompt: live.systemPrompt,
|
|
327
|
+
classifySystemPrompt: MINER_CLASSIFY_SYSTEM_PROMPT,
|
|
328
|
+
provider: 'anthropic',
|
|
329
|
+
model: 'test-model',
|
|
330
|
+
temperature: 0,
|
|
331
|
+
orchestratorVersion: 'orch-1',
|
|
332
|
+
totemVersion: '1.69.0',
|
|
333
|
+
});
|
|
334
|
+
const artifact = sink.freeze(provenance);
|
|
335
|
+
const expectedHash = computeArtifactHash(artifact);
|
|
336
|
+
const replay = new ReplayDraftExtractor(ReplayArtifactSchema.parse(artifact), expectedHash);
|
|
337
|
+
await expect(replay.draft(content())).resolves.toEqual(recorded);
|
|
338
|
+
});
|
|
339
|
+
it('records and replays a live classification', async () => {
|
|
340
|
+
const sink = new ReplayRecordSink();
|
|
341
|
+
const live = makeClassifier(stubInvoke('{"disposition":"structural"}'));
|
|
342
|
+
const recording = new RecordingDraftClassifier(live, sink, (d) => d.dslSource);
|
|
343
|
+
const recorded = await recording.classify(draft());
|
|
344
|
+
const artifact = sink.freeze(buildReplayProvenance({
|
|
345
|
+
extractSystemPrompt: MINER_EXTRACT_SYSTEM_PROMPT,
|
|
346
|
+
classifySystemPrompt: live.systemPrompt,
|
|
347
|
+
provider: 'anthropic',
|
|
348
|
+
model: 'test-model',
|
|
349
|
+
temperature: 0,
|
|
350
|
+
orchestratorVersion: 'orch-1',
|
|
351
|
+
totemVersion: '1.69.0',
|
|
352
|
+
}));
|
|
353
|
+
const replay = new ReplayDraftClassifier(ReplayArtifactSchema.parse(artifact), computeArtifactHash(artifact), (d) => d.dslSource);
|
|
354
|
+
await expect(replay.classify(draft())).resolves.toEqual(recorded);
|
|
355
|
+
});
|
|
356
|
+
});
|
|
357
|
+
// ─── 9. Prompt assembly: untrusted content is escaped ─
|
|
358
|
+
describe('prompt assembly', () => {
|
|
359
|
+
it('wraps untrusted review content with entity escaping (no markup breakout)', () => {
|
|
360
|
+
const prompt = buildExtractUserPrompt(content({
|
|
361
|
+
threads: [
|
|
362
|
+
{
|
|
363
|
+
path: 'a.ts',
|
|
364
|
+
isResolved: false,
|
|
365
|
+
isOutdated: false,
|
|
366
|
+
comments: [{ author: 'x', body: 'use </thread> & <script>' }],
|
|
367
|
+
},
|
|
368
|
+
],
|
|
369
|
+
}));
|
|
370
|
+
expect(prompt).not.toContain('</thread> & <script>');
|
|
371
|
+
expect(prompt).toContain('</thread> & <script>');
|
|
372
|
+
});
|
|
373
|
+
it('classify prompt wraps the draft body', () => {
|
|
374
|
+
expect(buildClassifyUserPrompt(draft('**Pattern:** <x>'))).toContain('**Pattern:** <x>');
|
|
375
|
+
});
|
|
376
|
+
});
|
|
377
|
+
// ─── 10. Barrel discipline: local wrap is in parity with core ─────────────────
|
|
378
|
+
describe('wrapUntrusted parity with core wrapUntrustedXml', () => {
|
|
379
|
+
it('matches the canonical helper across inputs', () => {
|
|
380
|
+
const cases = [
|
|
381
|
+
['thread', 'plain'],
|
|
382
|
+
['comment', 'a & b < c > d'],
|
|
383
|
+
['draft', '</draft> nested </draft>'],
|
|
384
|
+
['pr', ''],
|
|
385
|
+
];
|
|
386
|
+
for (const [tag, body] of cases) {
|
|
387
|
+
expect(wrapUntrusted(tag, body)).toBe(wrapUntrustedXml(tag, body));
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
it('rejects an invalid tag, like the canonical helper', () => {
|
|
391
|
+
expect(() => wrapUntrusted('bad tag', 'x')).toThrow();
|
|
392
|
+
expect(() => wrapUntrustedXml('bad tag', 'x')).toThrow();
|
|
393
|
+
});
|
|
394
|
+
});
|
|
395
|
+
//# sourceMappingURL=spine-llm-adapters.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spine-llm-adapters.test.js","sourceRoot":"","sources":["../../src/commands/spine-llm-adapters.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -20,6 +20,40 @@ export interface RunOptions {
|
|
|
20
20
|
lcDir?: string;
|
|
21
21
|
lockPath?: string;
|
|
22
22
|
phase?: string;
|
|
23
|
+
/**
|
|
24
|
+
* 5c-ii injection seam (out of 5c-i scope to populate): the certifying corpus
|
|
25
|
+
* — resolved-PR diffs (corpus + controls), the active compiled rules, and the
|
|
26
|
+
* frozen ground-truth labels. When omitted on a certifying run, the real
|
|
27
|
+
* engine path throws a structured "corpus provider not wired" error rather
|
|
28
|
+
* than silently scoring an empty set. 5c-ii (the orchestrator) supplies the
|
|
29
|
+
* live-recorded corpus here; 5c-i unit tests supply a deterministic fixture.
|
|
30
|
+
*/
|
|
31
|
+
certifyingCorpus?: CertifyingCorpusProvider;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The certifying corpus the real engine scores (5c-ii supplies this; 5c-i
|
|
35
|
+
* defines the seam + the deterministic engine that consumes it). Returns the
|
|
36
|
+
* active compiled rules (archived MUST be excluded — fold-F throws otherwise),
|
|
37
|
+
* the resolved-PR diffs (corpus + positive/negative controls), and the frozen
|
|
38
|
+
* ground-truth labels keyed by firingLabelId.
|
|
39
|
+
*/
|
|
40
|
+
export type CertifyingCorpusProvider = (lock: WindtunnelLock) => Promise<CertifyingCorpus> | CertifyingCorpus;
|
|
41
|
+
export interface CertifyingCorpus {
|
|
42
|
+
rules: import('@mmnto/totem').CompiledRule[];
|
|
43
|
+
prDiffs: import('@mmnto/totem').ResolvedPrDiff[];
|
|
44
|
+
groundTruth: Map<string, import('@mmnto/totem').GroundTruthLabel>;
|
|
45
|
+
}
|
|
46
|
+
/** Internal shape the run command's scorer consumes (engine-agnostic). */
|
|
47
|
+
interface EngineResult {
|
|
48
|
+
mintedRuleIds: string[];
|
|
49
|
+
firings: import('@mmnto/totem').RuleFiring[];
|
|
50
|
+
groundTruth: Map<string, import('@mmnto/totem').GroundTruthLabel>;
|
|
51
|
+
positiveControlTargets: Array<{
|
|
52
|
+
pr: number;
|
|
53
|
+
targetRuleId: string;
|
|
54
|
+
}>;
|
|
55
|
+
/** C2 — real touched-file exposure (0 for the harness mock). */
|
|
56
|
+
filesTouchedInWindow: number;
|
|
23
57
|
}
|
|
24
58
|
/**
|
|
25
59
|
* `totem spine windtunnel run`
|
|
@@ -92,5 +126,25 @@ export declare function enumeratePrMetas(asOfCommit: string, lcDir: string, safe
|
|
|
92
126
|
parseRevertSha: (body: string) => string | undefined;
|
|
93
127
|
isBotIdentity: (author: string) => boolean;
|
|
94
128
|
}): PrMeta[];
|
|
129
|
+
/**
|
|
130
|
+
* Run the REAL engine for the certifying phase (5c-i — #2189 item 1).
|
|
131
|
+
*
|
|
132
|
+
* Replaces the mock for `--phase certifying`: drives each resolved-PR diff
|
|
133
|
+
* through `buildFirings` (core), which runs `enrichWithAstContext` +
|
|
134
|
+
* `applyAstRulesToAdditions` with the shared post-image `readStrategy` (S1/C1)
|
|
135
|
+
* and maps every violation to a `RuleFiring` (content-based labelId). Then:
|
|
136
|
+
* - **fold-F**: `buildFirings` throws if any archived rule is in the scored set
|
|
137
|
+
* (the engine never runs on an archived rule).
|
|
138
|
+
* - **A1 (fold-D)**: `assertUniqueFiringLabels` hard-gates labelId uniqueness
|
|
139
|
+
* BEFORE scoring (throws on collision, surfacing the offending refs).
|
|
140
|
+
* - **C2**: `filesTouchedInWindow` is the real distinct-file exposure.
|
|
141
|
+
* - **fold-H**: neg-control firings flow through as `controlKind:'negative'`;
|
|
142
|
+
* unlabeled firings route to needsAdjudication via the scorer.
|
|
143
|
+
*
|
|
144
|
+
* The corpus itself (resolved-PR diffs + active rules + frozen ground truth) is
|
|
145
|
+
* supplied by the 5c-ii orchestrator via the `certifyingCorpus` seam. 5c-i owns
|
|
146
|
+
* the deterministic engine; it does NOT fetch/compile live data (out of scope).
|
|
147
|
+
*/
|
|
148
|
+
export declare function runCertifyingEngine(lock: WindtunnelLock, readStrategy: (file: string) => Promise<string | null>, corpusProvider?: CertifyingCorpusProvider): Promise<EngineResult>;
|
|
95
149
|
export {};
|
|
96
150
|
//# sourceMappingURL=spine-windtunnel.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spine-windtunnel.d.ts","sourceRoot":"","sources":["../../src/commands/spine-windtunnel.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAuB,cAAc,EAAE,MAAM,cAAc,CAAC;AAShF,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAoFtE;AAID,MAAM,WAAW,UAAU;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"spine-windtunnel.d.ts","sourceRoot":"","sources":["../../src/commands/spine-windtunnel.ts"],"names":[],"mappings":"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"}
|
|
@@ -149,12 +149,16 @@ export async function runCommand(opts) {
|
|
|
149
149
|
// simple null-returning strategy (all files → skip classification = fail-open).
|
|
150
150
|
// When lcDir is provided, resolve post-image blobs from the lc clone.
|
|
151
151
|
const readStrategy = buildReadStrategy(lcDir, lock.corpus.selectionRule.asOfCommit, safeExec);
|
|
152
|
-
//
|
|
153
|
-
//
|
|
154
|
-
//
|
|
155
|
-
//
|
|
156
|
-
//
|
|
157
|
-
|
|
152
|
+
// Run the engine. Harness phase → mock engine (no real rules yet). Certifying
|
|
153
|
+
// phase → the REAL engine path (5c-i): build additions from each resolved-PR
|
|
154
|
+
// diff → enrichWithAstContext + applyAstRulesToAdditions with the shared
|
|
155
|
+
// post-image readStrategy → RuleFiring[] → A1 unique-label hard-gate → score.
|
|
156
|
+
// C2: filesTouchedInWindow is the real exposure computed from the diffs (no
|
|
157
|
+
// longer the hard-coded 0).
|
|
158
|
+
const engineResult = lock.phase === 'certifying'
|
|
159
|
+
? await runCertifyingEngine(lock, readStrategy, opts.certifyingCorpus)
|
|
160
|
+
: await runMockEngineAdapter(lock, readStrategy);
|
|
161
|
+
const { mintedRuleIds, firings, groundTruth, positiveControlTargets, filesTouchedInWindow } = engineResult;
|
|
158
162
|
// Score
|
|
159
163
|
const verdict = scoreWindtunnel({
|
|
160
164
|
firings,
|
|
@@ -169,7 +173,7 @@ export async function runCommand(opts) {
|
|
|
169
173
|
},
|
|
170
174
|
actualExposure: {
|
|
171
175
|
activeRulesEvaluated: mintedRuleIds.length,
|
|
172
|
-
filesTouchedInWindow
|
|
176
|
+
filesTouchedInWindow,
|
|
173
177
|
positiveControlsExercised: positiveControlTargets.length,
|
|
174
178
|
},
|
|
175
179
|
});
|
|
@@ -531,4 +535,84 @@ async function runMockEngine(lock, _readStrategy) {
|
|
|
531
535
|
}
|
|
532
536
|
return { mintedRuleIds, firings, groundTruth, positiveControlTargets };
|
|
533
537
|
}
|
|
538
|
+
/** Adapt the harness mock engine to the EngineResult shape (filesTouched = 0). */
|
|
539
|
+
async function runMockEngineAdapter(lock, readStrategy) {
|
|
540
|
+
const mock = await runMockEngine(lock, readStrategy);
|
|
541
|
+
return { ...mock, filesTouchedInWindow: 0 };
|
|
542
|
+
}
|
|
543
|
+
// ─── Real engine (certifying phase, 5c-i) ────────────
|
|
544
|
+
/**
|
|
545
|
+
* Run the REAL engine for the certifying phase (5c-i — #2189 item 1).
|
|
546
|
+
*
|
|
547
|
+
* Replaces the mock for `--phase certifying`: drives each resolved-PR diff
|
|
548
|
+
* through `buildFirings` (core), which runs `enrichWithAstContext` +
|
|
549
|
+
* `applyAstRulesToAdditions` with the shared post-image `readStrategy` (S1/C1)
|
|
550
|
+
* and maps every violation to a `RuleFiring` (content-based labelId). Then:
|
|
551
|
+
* - **fold-F**: `buildFirings` throws if any archived rule is in the scored set
|
|
552
|
+
* (the engine never runs on an archived rule).
|
|
553
|
+
* - **A1 (fold-D)**: `assertUniqueFiringLabels` hard-gates labelId uniqueness
|
|
554
|
+
* BEFORE scoring (throws on collision, surfacing the offending refs).
|
|
555
|
+
* - **C2**: `filesTouchedInWindow` is the real distinct-file exposure.
|
|
556
|
+
* - **fold-H**: neg-control firings flow through as `controlKind:'negative'`;
|
|
557
|
+
* unlabeled firings route to needsAdjudication via the scorer.
|
|
558
|
+
*
|
|
559
|
+
* The corpus itself (resolved-PR diffs + active rules + frozen ground truth) is
|
|
560
|
+
* supplied by the 5c-ii orchestrator via the `certifyingCorpus` seam. 5c-i owns
|
|
561
|
+
* the deterministic engine; it does NOT fetch/compile live data (out of scope).
|
|
562
|
+
*/
|
|
563
|
+
export async function runCertifyingEngine(lock, readStrategy, corpusProvider) {
|
|
564
|
+
const { buildFirings, assertUniqueFiringLabels, resolveGitRoot, TotemError, FiringLabelCollisionError, } = await import('@mmnto/totem');
|
|
565
|
+
if (!corpusProvider) {
|
|
566
|
+
// No silent empty-set scoring: a certifying run with no corpus provider is a
|
|
567
|
+
// wiring error (5c-ii supplies it). Fail loud + actionable (Tenet 4).
|
|
568
|
+
throw new TotemError('CONFIG_INVALID', 'Wind-tunnel certifying run: no certifying-corpus provider wired (5c-ii orchestration).', 'The deterministic real-engine firing path (5c-i) is in place, but the corpus ' +
|
|
569
|
+
'(resolved-PR diffs + compiled rules + ground truth) is supplied by the 5c-ii ' +
|
|
570
|
+
'orchestrator. Run via the certifying orchestrator once it lands.');
|
|
571
|
+
}
|
|
572
|
+
const corpus = await corpusProvider(lock);
|
|
573
|
+
const cwd = resolveGitRoot(process.cwd()) ?? process.cwd();
|
|
574
|
+
const ruleEngineCtx = {
|
|
575
|
+
logger: { warn: (msg) => console.error(`[WindtunnelRun] ${msg}`) },
|
|
576
|
+
state: { hasWarnedShieldContext: false },
|
|
577
|
+
};
|
|
578
|
+
// buildFirings runs fold-F (archived assert) internally before the engine; its
|
|
579
|
+
// only throw is ArchivedRuleInScopeError, which propagates. A1 (labelId
|
|
580
|
+
// collision) is deliberately NOT raised here — it is the caller's pre-score gate
|
|
581
|
+
// below (assertUniqueFiringLabels), so the structured per-collision report is
|
|
582
|
+
// threaded there rather than swallowed at construction. (greptile #2215 P2.)
|
|
583
|
+
const built = await buildFirings({
|
|
584
|
+
rules: corpus.rules,
|
|
585
|
+
prDiffs: corpus.prDiffs,
|
|
586
|
+
cwd,
|
|
587
|
+
readStrategy,
|
|
588
|
+
ruleEngineCtx,
|
|
589
|
+
onWarn: (msg) => console.error(`[WindtunnelRun] ${msg}`),
|
|
590
|
+
});
|
|
591
|
+
// A1 (fold-D): hard-gate labelId uniqueness BEFORE scoring (Tenet 4).
|
|
592
|
+
try {
|
|
593
|
+
assertUniqueFiringLabels(built.firings);
|
|
594
|
+
}
|
|
595
|
+
catch (err) {
|
|
596
|
+
if (err instanceof FiringLabelCollisionError) {
|
|
597
|
+
console.error(`[WindtunnelRun] A1 firing-label collision (fold-D):`);
|
|
598
|
+
for (const c of err.collisions) {
|
|
599
|
+
console.error(` • ${c.labelId.slice(0, 12)}… ×${c.evidenceRefs.length}`);
|
|
600
|
+
}
|
|
601
|
+
throw new TotemError('CONFIG_INVALID', err.message, 'A firing-label collision voids the run — the labelId→evidenceRef join would overwrite. ' +
|
|
602
|
+
'Measure on the frozen corpus; if collisions occur, extend the label with a diff-hunk span (NOT an ordinal).', err);
|
|
603
|
+
}
|
|
604
|
+
throw err;
|
|
605
|
+
}
|
|
606
|
+
const mintedRuleIds = corpus.rules.map((r) => r.lessonHash);
|
|
607
|
+
console.error(`[WindtunnelRun] Certifying engine: ${mintedRuleIds.length} rule(s), ` +
|
|
608
|
+
`${corpus.prDiffs.length} PR diff(s), ${built.firings.length} firing(s), ` +
|
|
609
|
+
`${built.filesTouchedInWindow} file(s) touched.`);
|
|
610
|
+
return {
|
|
611
|
+
mintedRuleIds,
|
|
612
|
+
firings: built.firings,
|
|
613
|
+
groundTruth: corpus.groundTruth,
|
|
614
|
+
positiveControlTargets: built.positiveControlTargets,
|
|
615
|
+
filesTouchedInWindow: built.filesTouchedInWindow,
|
|
616
|
+
};
|
|
617
|
+
}
|
|
534
618
|
//# sourceMappingURL=spine-windtunnel.js.map
|