@spexcode/spec-eval 0.6.7 → 0.7.0-next.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/cli.js +1 -1
- package/dist/evaltab.d.ts +1 -0
- package/dist/evaltab.js +6 -1
- package/dist/freshness.d.ts +1 -1
- package/dist/freshness.js +12 -12
- package/dist/host.d.ts +2 -1
- package/dist/sessioneval.d.ts +2 -1
- package/dist/sessioneval.js +20 -3
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -203,7 +203,7 @@ async function scan(args = []) {
|
|
|
203
203
|
}
|
|
204
204
|
// an anchor is a claim a named unit exists — dead/ambiguous/unextractable is LOUD, and until it is
|
|
205
205
|
// repaired the probe issues no verdict, so the reading stays conservatively stale.
|
|
206
|
-
for (const p of [...axis.problems, ...anchorProblems(root, axis.entries)]) {
|
|
206
|
+
for (const p of [...axis.problems, ...(await anchorProblems(root, axis.entries))]) {
|
|
207
207
|
malformed++;
|
|
208
208
|
findings.push(` • eval-schema: '${s.id}' scenario '${sc.name}' ${p} — fix ${y.evalPath}`);
|
|
209
209
|
}
|
package/dist/evaltab.d.ts
CHANGED
|
@@ -83,6 +83,7 @@ export type EvalContext = {
|
|
|
83
83
|
export declare function evalContext(root: string, specs: Awaited<ReturnType<typeof loadSpecs>>, idx: DriftIndex, hidx: HistoryIndex, remarks?: Map<string, RemarkTrack>, ynodes?: EvalNode[]): Promise<EvalContext>;
|
|
84
84
|
export declare function evalTimelines(ids: readonly string[], ctx?: EvalContext, opts?: {
|
|
85
85
|
order?: boolean;
|
|
86
|
+
latestOnly?: boolean;
|
|
86
87
|
}): Promise<EvalTimeline[]>;
|
|
87
88
|
export declare function evalTimeline(id: string, ctx?: EvalContext): Promise<EvalTimeline>;
|
|
88
89
|
export type BlobResult = {
|
package/dist/evaltab.js
CHANGED
|
@@ -50,9 +50,14 @@ export async function evalTimelines(ids, ctx, opts = {}) {
|
|
|
50
50
|
const codeEntries = specs.find((s) => dirname(s.path) === relative(root, ynode.dir))?.codeEntries ?? [];
|
|
51
51
|
const byName = new Map(ynode.scenarios.map((s) => [s.name, s]));
|
|
52
52
|
const { readings, retractions, oks } = readSidecar(ynode.sidecarPath);
|
|
53
|
-
const
|
|
53
|
+
const allRows = applyRetractions(readings, retractions).map((reading) => ({
|
|
54
54
|
reading, axis: scenarioCodeAxis(byName.get(reading.scenario)?.code, codeEntries),
|
|
55
55
|
}));
|
|
56
|
+
// The board only renders the latest state per scenario. Historical rows remain available to the
|
|
57
|
+
// session/eval detail paths, but sending them through freshness here multiplies immutable Git probes.
|
|
58
|
+
const rows = opts.latestOnly
|
|
59
|
+
? [...allRows.reduce((latest, row) => latest.set(row.reading.scenario, row), new Map()).values()]
|
|
60
|
+
: allRows;
|
|
56
61
|
return { id, ynode, codeEntries, rows, retractions, oks };
|
|
57
62
|
});
|
|
58
63
|
// an order-only read stops HERE, before a single probe: the rows above are already the whole answer.
|
package/dist/freshness.d.ts
CHANGED
|
@@ -30,7 +30,7 @@ export type AnchorDemand = {
|
|
|
30
30
|
entries: readonly RelationEntry[];
|
|
31
31
|
};
|
|
32
32
|
export declare function anchorProbeFor(root: string, idx: DriftIndex): AnchorProbe;
|
|
33
|
-
export declare function anchorProblems(root: string, entries: readonly RelationEntry[]): string[]
|
|
33
|
+
export declare function anchorProblems(root: string, entries: readonly RelationEntry[]): Promise<string[]>;
|
|
34
34
|
export type RemarkSignal = {
|
|
35
35
|
resolved: boolean;
|
|
36
36
|
resolvedAt?: string;
|
package/dist/freshness.js
CHANGED
|
@@ -497,7 +497,7 @@ const currentTreeUnitMemo = new Map();
|
|
|
497
497
|
// caller retaining a verdict derived from these units names it by the same identity that decides the parse
|
|
498
498
|
// ([[selector-anchor-scope]]). Returning the key rather than re-deriving it elsewhere is what keeps the two
|
|
499
499
|
// from drifting apart.
|
|
500
|
-
function currentTreeImage(root, x, path) {
|
|
500
|
+
async function currentTreeImage(root, x, path) {
|
|
501
501
|
const source = readFileSync(join(root, path), 'utf8');
|
|
502
502
|
const key = `${x.memoKey(path)}\0${createHash('sha1').update(source).digest('hex')}`;
|
|
503
503
|
const hit = currentTreeUnitMemo.get(key);
|
|
@@ -508,7 +508,7 @@ function currentTreeImage(root, x, path) {
|
|
|
508
508
|
}
|
|
509
509
|
let entry;
|
|
510
510
|
try {
|
|
511
|
-
entry = { units: x.extract(source, path) };
|
|
511
|
+
entry = { units: await x.extract(source, path) };
|
|
512
512
|
}
|
|
513
513
|
catch (err) {
|
|
514
514
|
entry = { failed: err?.message ?? String(err) };
|
|
@@ -520,15 +520,15 @@ function currentTreeImage(root, x, path) {
|
|
|
520
520
|
throw new Error(entry.failed);
|
|
521
521
|
return { key, units: entry.units };
|
|
522
522
|
}
|
|
523
|
-
function entryImage(root, regs, path, selector) {
|
|
523
|
+
async function entryImage(root, regs, path, selector) {
|
|
524
524
|
const x = extractorFor(regs, extOf(path));
|
|
525
525
|
if (!x)
|
|
526
526
|
return { problem: `\`code\` selector \`${path}#${selector}\` — no designated extractor for that language; drop the #anchor or add a language row` };
|
|
527
|
-
const ready = x.ready();
|
|
527
|
+
const ready = await x.ready();
|
|
528
528
|
if (ready !== true)
|
|
529
529
|
return { problem: `\`code\` anchors on ${path} are unverified: ${ready}` };
|
|
530
530
|
try {
|
|
531
|
-
return currentTreeImage(root, x, path);
|
|
531
|
+
return await currentTreeImage(root, x, path);
|
|
532
532
|
}
|
|
533
533
|
catch (err) {
|
|
534
534
|
return { problem: `\`code\` anchors on ${path} are unverified: ${err?.message ?? String(err)}` };
|
|
@@ -550,8 +550,8 @@ function selectorProblem(units, path, selectors) {
|
|
|
550
550
|
}
|
|
551
551
|
return null;
|
|
552
552
|
}
|
|
553
|
-
function entryUnverifiable(root, regs, entry) {
|
|
554
|
-
const image = entryImage(root, regs, entry.path, entry.selectors[0]);
|
|
553
|
+
async function entryUnverifiable(root, regs, entry) {
|
|
554
|
+
const image = await entryImage(root, regs, entry.path, entry.selectors[0]);
|
|
555
555
|
return 'problem' in image ? image.problem : selectorProblem(image.units, entry.path, entry.selectors);
|
|
556
556
|
}
|
|
557
557
|
// @@@ the verify sweep must reach the MACROTASK queue - resolving every reading's `code:` selectors against
|
|
@@ -603,10 +603,10 @@ export function anchorProbeFor(root, idx) {
|
|
|
603
603
|
// one coherent read of each source per sweep: every entry on a path resolves against the same bytes,
|
|
604
604
|
// and the read that names the verdict is the read the verdict was derived from.
|
|
605
605
|
const images = new Map();
|
|
606
|
-
const imageOf = (path, selector) => {
|
|
606
|
+
const imageOf = async (path, selector) => {
|
|
607
607
|
let hit = images.get(path);
|
|
608
608
|
if (hit === undefined) {
|
|
609
|
-
hit = entryImage(root, regs, path, selector);
|
|
609
|
+
hit = await entryImage(root, regs, path, selector);
|
|
610
610
|
images.set(path, hit);
|
|
611
611
|
}
|
|
612
612
|
return hit;
|
|
@@ -630,7 +630,7 @@ export function anchorProbeFor(root, idx) {
|
|
|
630
630
|
const key = anchorKey(sinceSha, e.path, e.selectors);
|
|
631
631
|
if (verdicts.has(key) || queued.has(key))
|
|
632
632
|
continue;
|
|
633
|
-
const image = imageOf(e.path, e.selectors[0]);
|
|
633
|
+
const image = await imageOf(e.path, e.selectors[0]);
|
|
634
634
|
const name = 'problem' in image ? null : `${registry}\x1f${image.key}\x1f${key}`;
|
|
635
635
|
const remembered = scope && name !== null ? scope.anchorVerdicts.get(name) : undefined;
|
|
636
636
|
if (remembered !== undefined) {
|
|
@@ -666,13 +666,13 @@ export function anchorProbeFor(root, idx) {
|
|
|
666
666
|
// the LOUD half: a selector is a claim that a named unit EXISTS, held to the same standard as a ghost path.
|
|
667
667
|
// Dead, ambiguous, unparseable, or no designated extractor — each names itself and its repair, and until
|
|
668
668
|
// repaired the probe issues no verdict, so the reading stays stale. Over-warn, never a silent pass.
|
|
669
|
-
export function anchorProblems(root, entries) {
|
|
669
|
+
export async function anchorProblems(root, entries) {
|
|
670
670
|
const regs = extractors(root);
|
|
671
671
|
const out = [];
|
|
672
672
|
for (const e of entries) {
|
|
673
673
|
if (!e.selectors.length)
|
|
674
674
|
continue;
|
|
675
|
-
const problem = entryUnverifiable(root, regs, e);
|
|
675
|
+
const problem = await entryUnverifiable(root, regs, e);
|
|
676
676
|
if (problem)
|
|
677
677
|
out.push(problem);
|
|
678
678
|
}
|
package/dist/host.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { RemarkTrack } from './remarks.js';
|
|
2
|
+
import type { ReviewDiffFile } from '@spexcode/spec-core';
|
|
2
3
|
export type EvalHost = {
|
|
3
4
|
loadConfig?: (root: string) => any;
|
|
4
5
|
trackedSourceFiles?: (root: string, roots: string[], policy: any) => string[];
|
|
@@ -19,7 +20,7 @@ export type ReviewPayload = {
|
|
|
19
20
|
label: string;
|
|
20
21
|
ahead: number;
|
|
21
22
|
dirtyNonRuntime: number;
|
|
22
|
-
diff:
|
|
23
|
+
diff: ReviewDiffFile[];
|
|
23
24
|
gates: {
|
|
24
25
|
conflictsWithMain: boolean;
|
|
25
26
|
lint: {
|
package/dist/sessioneval.d.ts
CHANGED
|
@@ -218,7 +218,7 @@ export type SessionEvalRevision = {
|
|
|
218
218
|
export type SessionEvalProjection = {
|
|
219
219
|
epoch: string;
|
|
220
220
|
generation: number;
|
|
221
|
-
phase: 'loading' | 'updating' | 'ready' | 'error';
|
|
221
|
+
phase: 'loading' | 'updating' | 'ready' | 'error' | 'dormant';
|
|
222
222
|
revision?: string;
|
|
223
223
|
value?: SessionEvalSummary;
|
|
224
224
|
lastKnown?: {
|
|
@@ -279,6 +279,7 @@ export declare class SessionEvalProjectionCache {
|
|
|
279
279
|
demand<T>(id: string, path: string, run: () => Promise<T>): Promise<T>;
|
|
280
280
|
idle(): Promise<void>;
|
|
281
281
|
accept(id: string, generation: number, revision: string, value: SessionEvalSummary): boolean;
|
|
282
|
+
private dormantEntry;
|
|
282
283
|
private project;
|
|
283
284
|
private matches;
|
|
284
285
|
private publish;
|
package/dist/sessioneval.js
CHANGED
|
@@ -116,7 +116,7 @@ async function validateSelectorEntry(context, revision, entry, sourceView) {
|
|
|
116
116
|
if (!x) {
|
|
117
117
|
throw new SessionImpactUnavailableError(`session impact selector '${entry.path}#${entry.selectors[0]}' is unavailable: no designated extractor for '.${extOf(entry.path)}'`);
|
|
118
118
|
}
|
|
119
|
-
const ready = x.ready();
|
|
119
|
+
const ready = await x.ready();
|
|
120
120
|
if (ready !== true) {
|
|
121
121
|
throw new SessionImpactUnavailableError(`session impact selectors on '${entry.path}' are unavailable: ${ready}`);
|
|
122
122
|
}
|
|
@@ -132,7 +132,7 @@ async function validateSelectorEntry(context, revision, entry, sourceView) {
|
|
|
132
132
|
throw new SessionImpactUnavailableError(`session impact selector '${entry.path}#${entry.selectors[0]}' is dead at ${revision.slice(0, 8)}: the file does not exist`);
|
|
133
133
|
}
|
|
134
134
|
try {
|
|
135
|
-
return { extractor: x, units: x.extract(source, entry.path) };
|
|
135
|
+
return { extractor: x, units: await x.extract(source, entry.path) };
|
|
136
136
|
}
|
|
137
137
|
catch (error) {
|
|
138
138
|
throw new SessionImpactUnavailableError(`session impact selectors on '${entry.path}' are unextractable at ${revision.slice(0, 8)}: ${error?.message ?? String(error)}`);
|
|
@@ -1636,6 +1636,16 @@ export class SessionEvalProjectionCache {
|
|
|
1636
1636
|
promise.finally(() => {
|
|
1637
1637
|
if (this.demands.get(id) === promise)
|
|
1638
1638
|
this.demands.delete(id);
|
|
1639
|
+
// The cancel above exists ONLY so the warmup does not duplicate the build this demand is about to run.
|
|
1640
|
+
// Once the demand has settled that reason is spent — and a demand that REJECTED published nothing, so
|
|
1641
|
+
// leaving the mark set kept `authorize` refusing this generation forever and the entry stuck at
|
|
1642
|
+
// `loading`/`updating` with no build ever scheduled. A demand that published is already `ready` and
|
|
1643
|
+
// needs no second build.
|
|
1644
|
+
if (this.entries.get(id) !== entry || entry.demandCancelledGeneration !== entry.generation)
|
|
1645
|
+
return;
|
|
1646
|
+
entry.demandCancelledGeneration = null;
|
|
1647
|
+
this.authorize(entry);
|
|
1648
|
+
queueMicrotask(() => this.startScheduled());
|
|
1639
1649
|
}).catch(() => { });
|
|
1640
1650
|
return promise;
|
|
1641
1651
|
}
|
|
@@ -1660,12 +1670,19 @@ export class SessionEvalProjectionCache {
|
|
|
1660
1670
|
this.notify();
|
|
1661
1671
|
return true;
|
|
1662
1672
|
}
|
|
1673
|
+
// Dormant offline history is demand-only BY POLICY: `authorize` refuses to schedule an offline entry, so
|
|
1674
|
+
// nothing will move it until a demand builds it. `loading`/`updating` are arrival phases — a reader that
|
|
1675
|
+
// renders them as progress waits forever. This names the difference at the one place that knows it.
|
|
1676
|
+
dormantEntry(entry) {
|
|
1677
|
+
return entry.liveness === 'offline' && entry.running == null && entry.scheduled == null
|
|
1678
|
+
&& entry.observerHolds.size === 0 && entry.phase !== 'ready' && entry.phase !== 'error';
|
|
1679
|
+
}
|
|
1663
1680
|
project(entry) {
|
|
1664
1681
|
const stable = entry.current;
|
|
1665
1682
|
return {
|
|
1666
1683
|
epoch: this.epoch,
|
|
1667
1684
|
generation: entry.generation,
|
|
1668
|
-
phase: entry.phase,
|
|
1685
|
+
phase: this.dormantEntry(entry) ? 'dormant' : entry.phase,
|
|
1669
1686
|
...(entry.phase === 'ready' && stable
|
|
1670
1687
|
? { revision: stable.revision, value: stable.value }
|
|
1671
1688
|
: stable ? { lastKnown: stable } : {}),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spexcode/spec-eval",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0-next.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./dist/index.js",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"test": "tsx --import ../scripts/test-home.mjs --test src/*.test.ts"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@spexcode/spec-core": "0.
|
|
29
|
+
"@spexcode/spec-core": "0.7.0-next.0"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@types/node": "^20.16.0",
|