@mmnto/cli 1.67.1 → 1.69.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.
- package/dist/commands/spine-llm-replay.d.ts +446 -0
- package/dist/commands/spine-llm-replay.d.ts.map +1 -0
- package/dist/commands/spine-llm-replay.js +492 -0
- package/dist/commands/spine-llm-replay.js.map +1 -0
- package/dist/commands/spine-llm-replay.test.d.ts +2 -0
- package/dist/commands/spine-llm-replay.test.d.ts.map +1 -0
- package/dist/commands/spine-llm-replay.test.js +372 -0
- package/dist/commands/spine-llm-replay.test.js.map +1 -0
- package/dist/commands/spine-review-thread-source.d.ts +165 -0
- package/dist/commands/spine-review-thread-source.d.ts.map +1 -0
- package/dist/commands/spine-review-thread-source.js +251 -0
- package/dist/commands/spine-review-thread-source.js.map +1 -0
- package/dist/commands/spine-review-thread-source.test.d.ts +2 -0
- package/dist/commands/spine-review-thread-source.test.d.ts.map +1 -0
- package/dist/commands/spine-review-thread-source.test.js +284 -0
- package/dist/commands/spine-review-thread-source.test.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { classifierInputKey, ClassifierResultLocalSchema, computeArtifactHash, DuplicateRecordError, extractorInputKey, FixtureIntegrityError, RecordingDraftClassifier, RecordingDraftExtractor, REPLAY_ARTIFACT_KIND, ReplayArtifactSchema, ReplayDraftClassifier, ReplayDraftExtractor, ReplayMissError, ReplayRecordSink, serializeReplayArtifact, } from './spine-llm-replay.js';
|
|
3
|
+
// ─── Stub ports (in-memory, deterministic — NO network, NO LLM) ──────────────
|
|
4
|
+
/** A canned `DraftExtractor`: returns a fixed `string[]` keyed by PR (default to a 1-elem draft). */
|
|
5
|
+
class StubExtractor {
|
|
6
|
+
byPr;
|
|
7
|
+
constructor(byPr) {
|
|
8
|
+
this.byPr = byPr;
|
|
9
|
+
}
|
|
10
|
+
draft(content) {
|
|
11
|
+
return Promise.resolve(this.byPr.get(content.pr) ?? [`draft for PR ${content.pr}`]);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/** A canned `DraftClassifier`: returns a fixed result keyed by dslSource (default structural). */
|
|
15
|
+
class StubClassifier {
|
|
16
|
+
byBody;
|
|
17
|
+
constructor(byBody) {
|
|
18
|
+
this.byBody = byBody;
|
|
19
|
+
}
|
|
20
|
+
classify(draft) {
|
|
21
|
+
return Promise.resolve(this.byBody.get(draft.dslSource) ?? {
|
|
22
|
+
disposition: 'structural',
|
|
23
|
+
dispositionSource: 'classified',
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
// ─── Fixture builders ────────────────────────────────
|
|
28
|
+
const MERGE_SHA_A = 'a'.repeat(40);
|
|
29
|
+
const MERGE_SHA_B = 'b'.repeat(40);
|
|
30
|
+
function comment(author, body) {
|
|
31
|
+
return { author, body };
|
|
32
|
+
}
|
|
33
|
+
function thread(opts) {
|
|
34
|
+
return {
|
|
35
|
+
path: opts?.path ?? 'packages/core/src/x.ts',
|
|
36
|
+
isResolved: opts?.isResolved ?? false,
|
|
37
|
+
isOutdated: opts?.isOutdated ?? false,
|
|
38
|
+
comments: opts?.comments ?? [comment('jane', 'a structural note')],
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function content(opts) {
|
|
42
|
+
return {
|
|
43
|
+
pr: opts?.pr ?? 100,
|
|
44
|
+
mergeCommitSha: opts?.mergeCommitSha ?? MERGE_SHA_A,
|
|
45
|
+
threads: opts?.threads ?? [thread()],
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function draft(opts) {
|
|
49
|
+
return {
|
|
50
|
+
provenance: {
|
|
51
|
+
mergedPr: opts?.pr ?? 100,
|
|
52
|
+
reviewThread: `pulls/${opts?.pr ?? 100}/comments`,
|
|
53
|
+
commitSha: opts?.commitSha ?? MERGE_SHA_A,
|
|
54
|
+
},
|
|
55
|
+
dslSource: opts?.dslSource ?? '**Pattern:** no-foo',
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
const STUB_PROVENANCE = {
|
|
59
|
+
promptTemplateHash: 'p'.repeat(64),
|
|
60
|
+
systemPromptHash: 's'.repeat(64),
|
|
61
|
+
provider: 'anthropic',
|
|
62
|
+
model: 'stub-model-1',
|
|
63
|
+
temperature: 0,
|
|
64
|
+
orchestratorVersion: 'stub-orchestrator-0',
|
|
65
|
+
adapterKind: 'extractor+classifier',
|
|
66
|
+
keyVersion: 'v1',
|
|
67
|
+
totemVersion: '0.0.0-test',
|
|
68
|
+
};
|
|
69
|
+
/** Per-draft ref resolver: a stable (pr, dslSource) ordinal-free ref for tests. */
|
|
70
|
+
function draftRefOf(d) {
|
|
71
|
+
return `cand-${d.provenance.mergedPr}-${d.dslSource}`;
|
|
72
|
+
}
|
|
73
|
+
// ─── 1. Record → replay determinism (FM-a) ───────────
|
|
74
|
+
describe('record → replay determinism (FM-a)', () => {
|
|
75
|
+
it('replays byte-identical extractor + classifier outputs recorded from a stub', async () => {
|
|
76
|
+
const sink = new ReplayRecordSink();
|
|
77
|
+
const recExtractor = new RecordingDraftExtractor(new StubExtractor(new Map([[100, ['draft-A', 'draft-B']]])), sink);
|
|
78
|
+
const recClassifier = new RecordingDraftClassifier(new StubClassifier(new Map([['draft-A', { disposition: 'behavioral', dispositionSource: 'classified' }]])), sink, draftRefOf);
|
|
79
|
+
const c = content({ pr: 100 });
|
|
80
|
+
const recordedDrafts = await recExtractor.draft(c);
|
|
81
|
+
const d = draft({ pr: 100, dslSource: 'draft-A' });
|
|
82
|
+
const recordedClass = await recClassifier.classify(d);
|
|
83
|
+
const artifact = sink.freeze(STUB_PROVENANCE);
|
|
84
|
+
const expectedHash = computeArtifactHash(artifact);
|
|
85
|
+
// Round-trip through serialization to prove the on-disk form replays too.
|
|
86
|
+
const reloaded = ReplayArtifactSchema.parse(JSON.parse(serializeReplayArtifact(artifact)));
|
|
87
|
+
const replayExtractor = new ReplayDraftExtractor(reloaded, expectedHash);
|
|
88
|
+
const replayClassifier = new ReplayDraftClassifier(reloaded, expectedHash, draftRefOf);
|
|
89
|
+
expect(await replayExtractor.draft(c)).toEqual(recordedDrafts);
|
|
90
|
+
expect(await replayClassifier.classify(d)).toEqual(recordedClass);
|
|
91
|
+
// And the recorded values are exactly what the stub produced.
|
|
92
|
+
expect(recordedDrafts).toEqual(['draft-A', 'draft-B']);
|
|
93
|
+
expect(recordedClass).toEqual({ disposition: 'behavioral', dispositionSource: 'classified' });
|
|
94
|
+
});
|
|
95
|
+
it('serializes deterministically regardless of record insertion order', async () => {
|
|
96
|
+
const buildSink = async (prs) => {
|
|
97
|
+
const sink = new ReplayRecordSink();
|
|
98
|
+
const rec = new RecordingDraftExtractor(new StubExtractor(new Map(prs.map((p) => [p, [`d-${p}`]]))), sink);
|
|
99
|
+
for (const p of prs)
|
|
100
|
+
await rec.draft(content({ pr: p }));
|
|
101
|
+
return sink.freeze(STUB_PROVENANCE);
|
|
102
|
+
};
|
|
103
|
+
const forward = serializeReplayArtifact(await buildSink([100, 200, 300]));
|
|
104
|
+
const reversed = serializeReplayArtifact(await buildSink([300, 200, 100]));
|
|
105
|
+
expect(forward).toBe(reversed);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
// ─── 2. Drift / mutation red craft → FixtureIntegrityError ───────────────────
|
|
109
|
+
describe('drift / mutation red craft (fold B integrity)', () => {
|
|
110
|
+
it('throws FixtureIntegrityError when a recorded entry is mutated without updating the expected hash', async () => {
|
|
111
|
+
const sink = new ReplayRecordSink();
|
|
112
|
+
const rec = new RecordingDraftExtractor(new StubExtractor(new Map([[100, ['original']]])), sink);
|
|
113
|
+
await rec.draft(content({ pr: 100 }));
|
|
114
|
+
const artifact = sink.freeze(STUB_PROVENANCE);
|
|
115
|
+
const expectedHash = computeArtifactHash(artifact); // frozen at record time
|
|
116
|
+
// Tamper with ONE recorded raw entry, leaving the injected expected hash stale.
|
|
117
|
+
const key = extractorInputKey(content({ pr: 100 }));
|
|
118
|
+
const tampered = {
|
|
119
|
+
...artifact,
|
|
120
|
+
records: {
|
|
121
|
+
...artifact.records,
|
|
122
|
+
extractor: { ...artifact.records.extractor, [key]: ['TAMPERED'] },
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
expect(() => new ReplayDraftExtractor(tampered, expectedHash)).toThrow(FixtureIntegrityError);
|
|
126
|
+
expect(() => new ReplayDraftExtractor(tampered, expectedHash)).toThrow(/expected .* got /);
|
|
127
|
+
// The classifier replay shares the same gate.
|
|
128
|
+
expect(() => new ReplayDraftClassifier(tampered, expectedHash, draftRefOf)).toThrow(FixtureIntegrityError);
|
|
129
|
+
});
|
|
130
|
+
it('constructs cleanly when the expected hash matches the (untampered) records', async () => {
|
|
131
|
+
const sink = new ReplayRecordSink();
|
|
132
|
+
const rec = new RecordingDraftExtractor(new StubExtractor(new Map([[100, ['ok']]])), sink);
|
|
133
|
+
await rec.draft(content({ pr: 100 }));
|
|
134
|
+
const artifact = sink.freeze(STUB_PROVENANCE);
|
|
135
|
+
expect(() => new ReplayDraftExtractor(artifact, computeArtifactHash(artifact))).not.toThrow();
|
|
136
|
+
});
|
|
137
|
+
it('throws FixtureIntegrityError when a PROVENANCE field is mutated (fold F — prompt-hash covered by the gate)', async () => {
|
|
138
|
+
const sink = new ReplayRecordSink();
|
|
139
|
+
const rec = new RecordingDraftExtractor(new StubExtractor(new Map([[100, ['ok']]])), sink);
|
|
140
|
+
await rec.draft(content({ pr: 100 }));
|
|
141
|
+
const artifact = sink.freeze(STUB_PROVENANCE);
|
|
142
|
+
const expectedHash = computeArtifactHash(artifact); // frozen at record time
|
|
143
|
+
// Edit a PROVENANCE field (the prompt-template hash) WITHOUT re-recording or
|
|
144
|
+
// updating the expected hash. The integrity gate covers the whole artifact, so
|
|
145
|
+
// a prompt change must force a re-record — it can NEVER silently serve the same
|
|
146
|
+
// recorded outputs under a changed prompt (fold F). The records are untouched,
|
|
147
|
+
// so a records-only hash would MISS this; the whole-artifact hash catches it.
|
|
148
|
+
const tampered = {
|
|
149
|
+
...artifact,
|
|
150
|
+
provenance: { ...artifact.provenance, promptTemplateHash: 'q'.repeat(64) },
|
|
151
|
+
};
|
|
152
|
+
expect(() => new ReplayDraftExtractor(tampered, expectedHash)).toThrow(FixtureIntegrityError);
|
|
153
|
+
expect(() => new ReplayDraftClassifier(tampered, expectedHash, draftRefOf)).toThrow(FixtureIntegrityError);
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
// ─── 3. Replay miss red craft → ReplayMissError (not a safe-default) ─────────
|
|
157
|
+
describe('replay miss red craft', () => {
|
|
158
|
+
it('throws ReplayMissError on an absent extractor inputKey — NOT a []', async () => {
|
|
159
|
+
const sink = new ReplayRecordSink();
|
|
160
|
+
const rec = new RecordingDraftExtractor(new StubExtractor(new Map([[100, ['recorded']]])), sink);
|
|
161
|
+
await rec.draft(content({ pr: 100 }));
|
|
162
|
+
const artifact = sink.freeze(STUB_PROVENANCE);
|
|
163
|
+
const replay = new ReplayDraftExtractor(artifact, computeArtifactHash(artifact));
|
|
164
|
+
// PR 999 was never recorded → MISS. It REJECTS with ReplayMissError; it must
|
|
165
|
+
// NOT resolve to a safe-default [] (the whole point of the no-fallback rule).
|
|
166
|
+
const missContent = content({ pr: 999 });
|
|
167
|
+
await expect(replay.draft(missContent)).rejects.toBeInstanceOf(ReplayMissError);
|
|
168
|
+
let resolvedTo;
|
|
169
|
+
await replay.draft(missContent).then((v) => {
|
|
170
|
+
resolvedTo = v;
|
|
171
|
+
}, () => {
|
|
172
|
+
/* expected rejection */
|
|
173
|
+
});
|
|
174
|
+
expect(resolvedTo).toBeUndefined();
|
|
175
|
+
});
|
|
176
|
+
it('throws ReplayMissError on an absent classifier inputKey — NOT a {behavioral, error-default}', async () => {
|
|
177
|
+
const sink = new ReplayRecordSink();
|
|
178
|
+
const rec = new RecordingDraftClassifier(new StubClassifier(new Map()), sink, draftRefOf);
|
|
179
|
+
await rec.classify(draft({ pr: 100, dslSource: 'recorded-body' }));
|
|
180
|
+
const artifact = sink.freeze(STUB_PROVENANCE);
|
|
181
|
+
const replay = new ReplayDraftClassifier(artifact, computeArtifactHash(artifact), draftRefOf);
|
|
182
|
+
await expect(replay.classify(draft({ pr: 100, dslSource: 'never-recorded' }))).rejects.toBeInstanceOf(ReplayMissError);
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
// ─── 4. Recorded-empty vs miss ───────────────────────
|
|
186
|
+
describe('recorded-empty vs miss (real rows, distinguishable from absence)', () => {
|
|
187
|
+
it('replays a recorded [] (extractor) as [] — a real row, not a miss', async () => {
|
|
188
|
+
const sink = new ReplayRecordSink();
|
|
189
|
+
const rec = new RecordingDraftExtractor(new StubExtractor(new Map([[100, []]])), sink);
|
|
190
|
+
const recorded = await rec.draft(content({ pr: 100 }));
|
|
191
|
+
expect(recorded).toEqual([]);
|
|
192
|
+
const artifact = sink.freeze(STUB_PROVENANCE);
|
|
193
|
+
const replay = new ReplayDraftExtractor(artifact, computeArtifactHash(artifact));
|
|
194
|
+
// HIT on a recorded [] — returns [], does NOT throw ReplayMissError.
|
|
195
|
+
await expect(replay.draft(content({ pr: 100 }))).resolves.toEqual([]);
|
|
196
|
+
});
|
|
197
|
+
it('replays a recorded {behavioral, error-default} (classifier) as that exact value', async () => {
|
|
198
|
+
const errorDefault = {
|
|
199
|
+
disposition: 'behavioral',
|
|
200
|
+
dispositionSource: 'error-default',
|
|
201
|
+
};
|
|
202
|
+
const sink = new ReplayRecordSink();
|
|
203
|
+
const rec = new RecordingDraftClassifier(new StubClassifier(new Map([['err-body', errorDefault]])), sink, draftRefOf);
|
|
204
|
+
const recorded = await rec.classify(draft({ pr: 100, dslSource: 'err-body' }));
|
|
205
|
+
expect(recorded).toEqual(errorDefault);
|
|
206
|
+
const artifact = sink.freeze(STUB_PROVENANCE);
|
|
207
|
+
const replay = new ReplayDraftClassifier(artifact, computeArtifactHash(artifact), draftRefOf);
|
|
208
|
+
await expect(replay.classify(draft({ pr: 100, dslSource: 'err-body' }))).resolves.toEqual(errorDefault);
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
// ─── 5. Duplicate key → throws ───────────────────────
|
|
212
|
+
describe('duplicate (adapterKind, inputKey) → DuplicateRecordError', () => {
|
|
213
|
+
it('throws when the same extractor input is recorded twice (never last-write-wins)', async () => {
|
|
214
|
+
const sink = new ReplayRecordSink();
|
|
215
|
+
const rec = new RecordingDraftExtractor(new StubExtractor(new Map([[100, ['x']]])), sink);
|
|
216
|
+
await rec.draft(content({ pr: 100 }));
|
|
217
|
+
await expect(rec.draft(content({ pr: 100 }))).rejects.toBeInstanceOf(DuplicateRecordError);
|
|
218
|
+
});
|
|
219
|
+
it('throws when the same classifier input (same draftRef) is recorded twice', async () => {
|
|
220
|
+
const sink = new ReplayRecordSink();
|
|
221
|
+
const rec = new RecordingDraftClassifier(new StubClassifier(new Map()), sink, draftRefOf);
|
|
222
|
+
await rec.classify(draft({ pr: 100, dslSource: 'dup' }));
|
|
223
|
+
await expect(rec.classify(draft({ pr: 100, dslSource: 'dup' }))).rejects.toBeInstanceOf(DuplicateRecordError);
|
|
224
|
+
});
|
|
225
|
+
it('does NOT collide two different sections sharing a coincidental key value', () => {
|
|
226
|
+
// extractor + classifier maps are independent; the same string in both is fine.
|
|
227
|
+
const sink = new ReplayRecordSink();
|
|
228
|
+
sink.recordExtractor('shared-key', ['e']);
|
|
229
|
+
expect(() => sink.recordClassifier('shared-key', {
|
|
230
|
+
disposition: 'structural',
|
|
231
|
+
dispositionSource: 'classified',
|
|
232
|
+
})).not.toThrow();
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
// ─── 6. inputKey semantics (fold D) ──────────────────
|
|
236
|
+
describe('extractorInputKey (fold D)', () => {
|
|
237
|
+
it('is stable for identical eligible content + same mergeCommitSha', () => {
|
|
238
|
+
const a = extractorInputKey(content({ pr: 100, mergeCommitSha: MERGE_SHA_A }));
|
|
239
|
+
const b = extractorInputKey(content({ pr: 100, mergeCommitSha: MERGE_SHA_A }));
|
|
240
|
+
expect(a).toBe(b);
|
|
241
|
+
});
|
|
242
|
+
it('differs when mergeCommitSha differs (provenance identity)', () => {
|
|
243
|
+
const a = extractorInputKey(content({ pr: 100, mergeCommitSha: MERGE_SHA_A }));
|
|
244
|
+
const b = extractorInputKey(content({ pr: 100, mergeCommitSha: MERGE_SHA_B }));
|
|
245
|
+
expect(a).not.toBe(b);
|
|
246
|
+
});
|
|
247
|
+
it('is INVARIANT to thread + comment reordering (normalization holds)', () => {
|
|
248
|
+
const t1 = thread({
|
|
249
|
+
path: 'a.ts',
|
|
250
|
+
comments: [comment('al', 'first'), comment('bo', 'second')],
|
|
251
|
+
});
|
|
252
|
+
const t2 = thread({ path: 'b.ts', comments: [comment('cy', 'third')] });
|
|
253
|
+
const forward = extractorInputKey(content({ threads: [t1, t2] }));
|
|
254
|
+
// Reverse the threads AND reverse t1's comments.
|
|
255
|
+
const t1rev = thread({
|
|
256
|
+
path: 'a.ts',
|
|
257
|
+
comments: [comment('bo', 'second'), comment('al', 'first')],
|
|
258
|
+
});
|
|
259
|
+
const reversed = extractorInputKey(content({ threads: [t2, t1rev] }));
|
|
260
|
+
expect(forward).toBe(reversed);
|
|
261
|
+
});
|
|
262
|
+
it('is INVARIANT to reordering threads on the SAME path that differ in later comments', () => {
|
|
263
|
+
// The bug greptile P1 + CR caught: two threads on ONE path sharing a first
|
|
264
|
+
// comment but differing LATER compared equal under the old (path, first-comment)
|
|
265
|
+
// sort → provider order leaked into the key. The full-canonical-JSON total order
|
|
266
|
+
// fixes it. (comments normalize by (body, author): 'shared' sorts before 'tail-*'.)
|
|
267
|
+
const t1 = thread({
|
|
268
|
+
path: 'same.ts',
|
|
269
|
+
comments: [comment('al', 'shared'), comment('al', 'tail-1')],
|
|
270
|
+
});
|
|
271
|
+
const t2 = thread({
|
|
272
|
+
path: 'same.ts',
|
|
273
|
+
comments: [comment('al', 'shared'), comment('al', 'tail-2')],
|
|
274
|
+
});
|
|
275
|
+
const forward = extractorInputKey(content({ threads: [t1, t2] }));
|
|
276
|
+
const reversed = extractorInputKey(content({ threads: [t2, t1] }));
|
|
277
|
+
expect(forward).toBe(reversed);
|
|
278
|
+
});
|
|
279
|
+
it('excludes resolved/outdated threads from the key (the port never sees them)', () => {
|
|
280
|
+
const eligible = thread({ path: 'a.ts', comments: [comment('al', 'keep')] });
|
|
281
|
+
const resolved = thread({ path: 'z.ts', isResolved: true, comments: [comment('zo', 'drop')] });
|
|
282
|
+
const withResolved = extractorInputKey(content({ threads: [eligible, resolved] }));
|
|
283
|
+
const withoutResolved = extractorInputKey(content({ threads: [eligible] }));
|
|
284
|
+
expect(withResolved).toBe(withoutResolved);
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
describe('ClassifierResultLocalSchema parity with core (GCA #2209, option a)', () => {
|
|
288
|
+
it('accepts / rejects EXACTLY what core ClassifierResultSchema does', async () => {
|
|
289
|
+
// The local CLI-side schema avoids a static runtime import of core's barrel
|
|
290
|
+
// (CLI-startup styleguide). This test is the guard against drift: a test MAY
|
|
291
|
+
// static-import core (the rule is about command-file startup, not tests).
|
|
292
|
+
const { ClassifierResultSchema } = await import('@mmnto/totem');
|
|
293
|
+
const cases = [
|
|
294
|
+
{ disposition: 'structural', dispositionSource: 'classified' }, // valid
|
|
295
|
+
{ disposition: 'behavioral', dispositionSource: 'classified' }, // valid
|
|
296
|
+
{ disposition: 'behavioral', dispositionSource: 'error-default' }, // valid (safe-default)
|
|
297
|
+
{ disposition: 'structural', dispositionSource: 'error-default' }, // refine → invalid
|
|
298
|
+
{ disposition: 'nope', dispositionSource: 'classified' }, // enum → invalid
|
|
299
|
+
{ disposition: 'structural' }, // missing field → invalid
|
|
300
|
+
];
|
|
301
|
+
for (const c of cases) {
|
|
302
|
+
expect(ClassifierResultLocalSchema.safeParse(c).success).toBe(ClassifierResultSchema.safeParse(c).success);
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
describe('classifierInputKey (fold D)', () => {
|
|
307
|
+
it('is stable for the same provenance + dslSource + draftRef', () => {
|
|
308
|
+
const d = draft({ pr: 100, dslSource: 'body' });
|
|
309
|
+
expect(classifierInputKey(d, 'ref-0')).toBe(classifierInputKey(d, 'ref-0'));
|
|
310
|
+
});
|
|
311
|
+
it('two drafts from the SAME provenance with different draftRef do NOT collide', () => {
|
|
312
|
+
const d = draft({ pr: 100, dslSource: 'identical-body' });
|
|
313
|
+
// The classifier does not dedupe — two drafts (same provenance, same body)
|
|
314
|
+
// must key distinctly via draftRef.
|
|
315
|
+
expect(classifierInputKey(d, 'ref-0')).not.toBe(classifierInputKey(d, 'ref-1'));
|
|
316
|
+
});
|
|
317
|
+
it('differs when dslSource differs', () => {
|
|
318
|
+
const a = classifierInputKey(draft({ pr: 100, dslSource: 'body-a' }), 'ref-0');
|
|
319
|
+
const b = classifierInputKey(draft({ pr: 100, dslSource: 'body-b' }), 'ref-0');
|
|
320
|
+
expect(a).not.toBe(b);
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
// ─── 7. Serialization hygiene ────────────────────────
|
|
324
|
+
describe('serialization hygiene', () => {
|
|
325
|
+
it('serializes record keys SORTED (clean git diffs)', async () => {
|
|
326
|
+
const sink = new ReplayRecordSink();
|
|
327
|
+
const rec = new RecordingDraftExtractor(new StubExtractor(new Map([
|
|
328
|
+
[300, ['c']],
|
|
329
|
+
[100, ['a']],
|
|
330
|
+
[200, ['b']],
|
|
331
|
+
])), sink);
|
|
332
|
+
// Record out of key order.
|
|
333
|
+
await rec.draft(content({ pr: 300 }));
|
|
334
|
+
await rec.draft(content({ pr: 100 }));
|
|
335
|
+
await rec.draft(content({ pr: 200 }));
|
|
336
|
+
const serialized = serializeReplayArtifact(sink.freeze(STUB_PROVENANCE));
|
|
337
|
+
const parsed = JSON.parse(serialized);
|
|
338
|
+
const keys = Object.keys(parsed.records.extractor);
|
|
339
|
+
expect(keys).toEqual([...keys].sort());
|
|
340
|
+
expect(keys).toHaveLength(3);
|
|
341
|
+
});
|
|
342
|
+
it('NEVER leaks durationMs / recordedAt / metadata into a record value', async () => {
|
|
343
|
+
const sink = new ReplayRecordSink();
|
|
344
|
+
const rec = new RecordingDraftExtractor(new StubExtractor(new Map([[100, ['only-the-output']]])), sink);
|
|
345
|
+
await rec.draft(content({ pr: 100 }));
|
|
346
|
+
const recC = new RecordingDraftClassifier(new StubClassifier(new Map()), sink, draftRefOf);
|
|
347
|
+
await recC.classify(draft({ pr: 100, dslSource: 'b' }));
|
|
348
|
+
const serialized = serializeReplayArtifact(sink.freeze(STUB_PROVENANCE));
|
|
349
|
+
// No non-deterministic / identifying metadata anywhere in the records block.
|
|
350
|
+
expect(serialized).not.toMatch(/durationMs|recordedAt|runId|run-id|localUser|userId/i);
|
|
351
|
+
const parsed = JSON.parse(serialized);
|
|
352
|
+
// Each record VALUE is strictly the port's output shape — nothing else.
|
|
353
|
+
const extractorVal = Object.values(parsed.records.extractor)[0];
|
|
354
|
+
expect(extractorVal).toEqual(['only-the-output']);
|
|
355
|
+
const classifierVal = Object.values(parsed.records.classifier)[0];
|
|
356
|
+
expect(Object.keys(classifierVal).sort()).toEqual([
|
|
357
|
+
'disposition',
|
|
358
|
+
'dispositionSource',
|
|
359
|
+
]);
|
|
360
|
+
});
|
|
361
|
+
it('carries the artifact kind tag + provenance block OUTSIDE the records map', async () => {
|
|
362
|
+
const sink = new ReplayRecordSink();
|
|
363
|
+
const rec = new RecordingDraftExtractor(new StubExtractor(new Map([[100, ['x']]])), sink);
|
|
364
|
+
await rec.draft(content({ pr: 100 }));
|
|
365
|
+
const artifact = sink.freeze(STUB_PROVENANCE);
|
|
366
|
+
expect(artifact.kind).toBe(REPLAY_ARTIFACT_KIND);
|
|
367
|
+
expect(artifact.provenance).toEqual(STUB_PROVENANCE);
|
|
368
|
+
// Provenance is not inside records.
|
|
369
|
+
expect(JSON.stringify(artifact.records)).not.toContain('anthropic');
|
|
370
|
+
});
|
|
371
|
+
});
|
|
372
|
+
//# sourceMappingURL=spine-llm-replay.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spine-llm-replay.test.js","sourceRoot":"","sources":["../../src/commands/spine-llm-replay.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ADR-111 Stage-1 Extract — the live CLI `ReviewThreadSource` adapter (slice 5a,
|
|
3
|
+
* mmnto-ai/totem#2201).
|
|
4
|
+
*
|
|
5
|
+
* Core defines the `ReviewThreadSource` port and stays network-free + LLM-free +
|
|
6
|
+
* deterministic; THIS file is the IO-at-the-edge implementation that wraps the
|
|
7
|
+
* GitHub GraphQL `reviewThreads` API behind that port. It fetches a PR's merge
|
|
8
|
+
* commit + its review threads (each carrying `isResolved` / `isOutdated`), maps
|
|
9
|
+
* them to the provider-neutral `ReviewThreadContent`, and SURFACES the per-thread
|
|
10
|
+
* resolution flags to core. It does NOT filter resolved/outdated threads — the
|
|
11
|
+
* contract-owner ruling is "surface, don't filter": core decides eligibility and
|
|
12
|
+
* drop-ledgers every rejection (§8 "every rejection ledgered"). A server-side or
|
|
13
|
+
* client-side `isResolved:false` pre-filter is FORBIDDEN here.
|
|
14
|
+
*
|
|
15
|
+
* Error contract (the discriminated `FetchResult`, §6): a per-PR failure is NEVER
|
|
16
|
+
* thrown — the orchestrator iterates the whole train slice and must not abort on
|
|
17
|
+
* one bad PR. Network / not-found → `{kind:'unreachable'}`; a malformed or
|
|
18
|
+
* unmappable payload → `{kind:'unparseable'}`. The mining run continues; the loud
|
|
19
|
+
* drop is core's job.
|
|
20
|
+
*
|
|
21
|
+
* Reuses the established `gh` CLI exec pattern (Tenet-21 — no bespoke GraphQL
|
|
22
|
+
* client). The `exec` seam is injectable so the query-spy + mapping tests run
|
|
23
|
+
* fully offline (no network, CI-locked).
|
|
24
|
+
*/
|
|
25
|
+
import { z } from 'zod';
|
|
26
|
+
import type { FetchResult, ReviewThread, ReviewThreadSource } from '@mmnto/totem';
|
|
27
|
+
declare const GqlReviewThreadSchema: z.ZodObject<{
|
|
28
|
+
isResolved: z.ZodBoolean;
|
|
29
|
+
isOutdated: z.ZodBoolean;
|
|
30
|
+
path: z.ZodString;
|
|
31
|
+
comments: z.ZodObject<{
|
|
32
|
+
pageInfo: z.ZodObject<{
|
|
33
|
+
hasNextPage: z.ZodBoolean;
|
|
34
|
+
}, "strip", z.ZodTypeAny, {
|
|
35
|
+
hasNextPage: boolean;
|
|
36
|
+
}, {
|
|
37
|
+
hasNextPage: boolean;
|
|
38
|
+
}>;
|
|
39
|
+
nodes: z.ZodArray<z.ZodObject<{
|
|
40
|
+
author: z.ZodNullable<z.ZodObject<{
|
|
41
|
+
login: z.ZodString;
|
|
42
|
+
}, "strip", z.ZodTypeAny, {
|
|
43
|
+
login: string;
|
|
44
|
+
}, {
|
|
45
|
+
login: string;
|
|
46
|
+
}>>;
|
|
47
|
+
body: z.ZodString;
|
|
48
|
+
}, "strip", z.ZodTypeAny, {
|
|
49
|
+
body: string;
|
|
50
|
+
author: {
|
|
51
|
+
login: string;
|
|
52
|
+
} | null;
|
|
53
|
+
}, {
|
|
54
|
+
body: string;
|
|
55
|
+
author: {
|
|
56
|
+
login: string;
|
|
57
|
+
} | null;
|
|
58
|
+
}>, "many">;
|
|
59
|
+
}, "strip", z.ZodTypeAny, {
|
|
60
|
+
pageInfo: {
|
|
61
|
+
hasNextPage: boolean;
|
|
62
|
+
};
|
|
63
|
+
nodes: {
|
|
64
|
+
body: string;
|
|
65
|
+
author: {
|
|
66
|
+
login: string;
|
|
67
|
+
} | null;
|
|
68
|
+
}[];
|
|
69
|
+
}, {
|
|
70
|
+
pageInfo: {
|
|
71
|
+
hasNextPage: boolean;
|
|
72
|
+
};
|
|
73
|
+
nodes: {
|
|
74
|
+
body: string;
|
|
75
|
+
author: {
|
|
76
|
+
login: string;
|
|
77
|
+
} | null;
|
|
78
|
+
}[];
|
|
79
|
+
}>;
|
|
80
|
+
}, "strip", z.ZodTypeAny, {
|
|
81
|
+
path: string;
|
|
82
|
+
comments: {
|
|
83
|
+
pageInfo: {
|
|
84
|
+
hasNextPage: boolean;
|
|
85
|
+
};
|
|
86
|
+
nodes: {
|
|
87
|
+
body: string;
|
|
88
|
+
author: {
|
|
89
|
+
login: string;
|
|
90
|
+
} | null;
|
|
91
|
+
}[];
|
|
92
|
+
};
|
|
93
|
+
isResolved: boolean;
|
|
94
|
+
isOutdated: boolean;
|
|
95
|
+
}, {
|
|
96
|
+
path: string;
|
|
97
|
+
comments: {
|
|
98
|
+
pageInfo: {
|
|
99
|
+
hasNextPage: boolean;
|
|
100
|
+
};
|
|
101
|
+
nodes: {
|
|
102
|
+
body: string;
|
|
103
|
+
author: {
|
|
104
|
+
login: string;
|
|
105
|
+
} | null;
|
|
106
|
+
}[];
|
|
107
|
+
};
|
|
108
|
+
isResolved: boolean;
|
|
109
|
+
isOutdated: boolean;
|
|
110
|
+
}>;
|
|
111
|
+
/**
|
|
112
|
+
* Build the `reviewThreads` GraphQL query. It REQUESTS `isResolved` and
|
|
113
|
+
* `isOutdated` per thread (so core HAS the signal to decide on) — it deliberately
|
|
114
|
+
* does NOT add an `isResolved: false` server-side filter (the "surface, don't
|
|
115
|
+
* filter" ruling; the query-spy test asserts both halves). Exported for the
|
|
116
|
+
* query-spy test.
|
|
117
|
+
*/
|
|
118
|
+
export declare function buildReviewThreadsQuery(owner: string, name: string, pr: number): string;
|
|
119
|
+
/**
|
|
120
|
+
* The injectable command-exec seam. Matches the relevant part of `safeExec`'s
|
|
121
|
+
* signature; the tests pass a fake that intercepts the outgoing GraphQL query
|
|
122
|
+
* (no network). When not injected, the adapter lazy-loads `safeExec` (below).
|
|
123
|
+
*/
|
|
124
|
+
export type GhExec = (command: string, args: string[]) => string;
|
|
125
|
+
/**
|
|
126
|
+
* Map a validated GraphQL response to the surviving `ReviewThread[]` with their
|
|
127
|
+
* resolution flags surfaced. Pure — exported for the mapping test. A null author
|
|
128
|
+
* (deleted/ghost) coerces to '' (`isBotIdentity('')` is false, so it stays a
|
|
129
|
+
* human-author '' that core's empty-body / count logic handles correctly; the
|
|
130
|
+
* body still gates inclusion).
|
|
131
|
+
*/
|
|
132
|
+
export declare function mapThreads(nodes: z.infer<typeof GqlReviewThreadSchema>[]): ReviewThread[];
|
|
133
|
+
export interface ReviewThreadSourceAdapterOptions {
|
|
134
|
+
/** The repo to query, as `owner` + `name`. */
|
|
135
|
+
owner: string;
|
|
136
|
+
name: string;
|
|
137
|
+
/** Working directory for the `gh` invocation (real adapter). */
|
|
138
|
+
cwd?: string;
|
|
139
|
+
/** Injectable exec seam (tests). Defaults to a `gh`-backed `safeExec`. */
|
|
140
|
+
exec?: GhExec;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* The live `ReviewThreadSource`. Constructible + exported now (slice 5a); wiring
|
|
144
|
+
* it into the spine orchestrator's `run` command is slice 5c.
|
|
145
|
+
*/
|
|
146
|
+
export declare class ReviewThreadSourceAdapter implements ReviewThreadSource {
|
|
147
|
+
private readonly owner;
|
|
148
|
+
private readonly name;
|
|
149
|
+
private readonly cwd;
|
|
150
|
+
/** Injected exec seam (tests); when absent the default is lazy-loaded once via `execPromise`. */
|
|
151
|
+
private readonly injectedExec;
|
|
152
|
+
/**
|
|
153
|
+
* Memoized lazy-load of the default `gh`-backed exec (real runs). Memoizes the
|
|
154
|
+
* PROMISE, not the resolved value, so concurrent `fetch()` calls on one adapter
|
|
155
|
+
* instance share a single `loadDefaultExec` instead of racing a `!this.exec`
|
|
156
|
+
* guard (CR #2207 — was benign since dynamic import is idempotent, now explicit).
|
|
157
|
+
*/
|
|
158
|
+
private execPromise;
|
|
159
|
+
constructor(opts: ReviewThreadSourceAdapterOptions);
|
|
160
|
+
/** Resolve the exec seam: the injected one (tests), else the memoized lazy-loaded default. */
|
|
161
|
+
private resolveExec;
|
|
162
|
+
fetch(pr: number): Promise<FetchResult>;
|
|
163
|
+
}
|
|
164
|
+
export {};
|
|
165
|
+
//# sourceMappingURL=spine-review-thread-source.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spine-review-thread-source.d.ts","sourceRoot":"","sources":["../../src/commands/spine-review-thread-source.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAMxB,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AA6BlF,QAAA,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAQzB,CAAC;AAsBH;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAuBvF;AAID;;;;GAIG;AACH,MAAM,MAAM,MAAM,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,MAAM,CAAC;AAoBjE;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,EAAE,GAAG,YAAY,EAAE,CAWzF;AAID,MAAM,WAAW,gCAAgC;IAC/C,8CAA8C;IAC9C,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,gEAAgE;IAChE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,0EAA0E;IAC1E,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;GAGG;AACH,qBAAa,yBAA0B,YAAW,kBAAkB;IAClE,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAC7B,iGAAiG;IACjG,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAqB;IAClD;;;;;OAKG;IACH,OAAO,CAAC,WAAW,CAA8B;gBAErC,IAAI,EAAE,gCAAgC;IAOlD,8FAA8F;IAC9F,OAAO,CAAC,WAAW;IAMb,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;CAoF9C"}
|