@feng0531/dsh-code-review 0.87.0 → 0.87.2

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/src/service.mjs CHANGED
@@ -1,3 +1,4 @@
1
+ import { previewValidity } from './preview-validity.mjs';
1
2
  import { configureWireBudget } from './model-wire-budget.mjs';
2
3
  import {REVIEW_RUNTIME} from './runtime-identity.mjs';
3
4
  import { buildResumeState, validateResumeState, resumePreview } from './review-resume.mjs';
@@ -101,12 +102,15 @@ function createSessionReviewService(llm, { capture = captureSnapshot, now = Date
101
102
  prune();
102
103
  while (previews.size >= maxPreviews) previews.delete(previews.keys().next().value);
103
104
  const previewId = randomUUID();
104
- previews.set(previewId, { ...current, target, options, rounds, snapshotId: snapshot.id, resumeState, sourceJobId, expires: now() + ttlMs });
105
+ const expires = now() + ttlMs;
106
+ previews.set(previewId, { ...current, target, options, rounds, snapshotId: snapshot.id, resumeState, sourceJobId, expires });
105
107
  return {
106
108
  ...(options.businessRequirement !== undefined
107
109
  ? { businessRequirement: { text: options.businessRequirement, hash: hash(options.businessRequirement), bytes: Buffer.byteLength(options.businessRequirement) } }
108
110
  : {}),
109
111
  previewId,
112
+ expiresAt: new Date(expires).toISOString(),
113
+ validForMs: ttlMs,
110
114
  ...(resumeState ? { resume: resumePreview(resumeState, sourceJobId) } : {}),
111
115
  plan,
112
116
  snapshotId: snapshot.id,
@@ -151,9 +155,10 @@ function createSessionReviewService(llm, { capture = captureSnapshot, now = Date
151
155
  if (busy) throw new Error('Review service busy');
152
156
  if (!allowModelSending) throw new Error('Model sending is disabled by plugin configuration');
153
157
  if (!args || Object.keys(args).some(k => !['previewId', 'confirmed'].includes(k)) || args.confirmed !== true || typeof args.previewId !== 'string') throw new Error('Confirmed preview is required');
154
- prune();
155
158
  const current = owner(exec), preview = previews.get(args.previewId);
156
- if (!preview || preview.agent !== current.agent || preview.cwd !== current.cwd || JSON.stringify(preview.route) !== JSON.stringify(current.route)) throw new Error('Preview missing, expired or owner/model/directory changed');
159
+ try { previewValidity(preview, current, now()); }
160
+ catch (error) { if (error.code === 'PREVIEW_EXPIRED') previews.delete(args.previewId); throw error; }
161
+ prune();
157
162
  busy = true;
158
163
  previews.delete(args.previewId);
159
164
  try {
@@ -1,5 +1,5 @@
1
1
  import { createHash } from 'node:crypto';
2
- const KINDS = new Set(['verification', 'synthesis', 'interaction']);
2
+ const KINDS = new Set(['primary', 'verification', 'synthesis', 'interaction']);
3
3
  const REASONS = new Set(['READER_CALL_BUDGET_EXCEEDED', 'READER_OUTPUT_BUDGET_EXCEEDED', 'STAGE_INTERRUPTED', 'STAGE_CANCELLED', 'STAGE_TIMEOUT', 'STAGE_NO_PROGRESS', 'STAGE_ATTEMPTS_EXHAUSTED', 'STAGE_FAILED']);
4
4
  const fail = code => Object.assign(new Error(code), { code });
5
5
  const canonical = value => Array.isArray(value) ? value.map(canonical) : value && typeof value === 'object' ? Object.fromEntries(Object.keys(value).sort().filter(key => value[key] !== undefined).map(key => [key, canonical(value[key])])) : value;
@@ -40,7 +40,17 @@ export function createStageContinuation(snapshot, options, { completedPrimary =
40
40
  }
41
41
  }
42
42
  const get = id => { const state=states.get(id); if (!state) throw fail('UNKNOWN_STAGE_UNIT'); return state; };
43
- const dependenciesReady = state => state.fileIds.every(id => primary.includes(id)) && state.dependencies.every(dep => dep.startsWith('primary:') ? primary.includes(dep.slice(8)) : get(dep).status === 'completed');
43
+ // The completedPrimary baseline is immutable. A pending primary may run without
44
+ // belonging to it; secondary work must name a completed primary unit explicitly
45
+ // when relying on primary work performed by this continuation. Reserved
46
+ // primary:<fileId> dependencies still refer only to the completed baseline.
47
+ const dependencyCompleted = dep => dep.startsWith('primary:') ? primary.includes(dep.slice(8)) : get(dep).status === 'completed';
48
+ const primaryCompleted = (state, fileId) => primary.includes(fileId) || state.dependencies.some(dep => {
49
+ if (dep.startsWith('primary:')) return false;
50
+ const prerequisite = get(dep);
51
+ return prerequisite.kind === 'primary' && prerequisite.status === 'completed' && prerequisite.fileIds.includes(fileId);
52
+ });
53
+ const dependenciesReady = state => (state.kind === 'primary' || state.fileIds.every(id => primaryCompleted(state, id))) && state.dependencies.every(dependencyCompleted);
44
54
  for (const state of states.values()) {
45
55
  if (state.status === 'completed' && (!dependenciesReady(state) || state.stopped || state.pendingReason !== null)) throw fail('INVALID_STAGE_CHECKPOINT');
46
56
  }
@@ -0,0 +1,22 @@
1
+ import { prepareStageGenerations } from './review-stage-state.mjs';
2
+
3
+ // Only host-validated generations enter this cache. Failed admission leaves the
4
+ // last good set unchanged; imported checkpoints still fail closed in the validator.
5
+ export function createStageGenerationCache(snapshot, records = [], { maxBytes = 8 * 1024 * 1024 } = {}) {
6
+ let saved = prepareStageGenerations(snapshot, records, { maxBytes });
7
+ return {
8
+ records() { return structuredClone(saved); },
9
+ admit(record) {
10
+ let normalized;
11
+ try { [normalized] = prepareStageGenerations(snapshot, [record]); }
12
+ catch { return { accepted: false, code: 'STAGE_CACHE_RECORD_REJECTED' }; }
13
+ const next = saved.filter(row => row.key !== normalized.key);
14
+ next.push(normalized);
15
+ let checked;
16
+ try { checked = prepareStageGenerations(snapshot, next, { maxBytes }); }
17
+ catch { return { accepted: false, code: 'STAGE_CACHE_CAPACITY_EXCEEDED' }; }
18
+ saved = checked;
19
+ return { accepted: true, record: structuredClone(normalized) };
20
+ },
21
+ };
22
+ }