spexcode 0.6.0 → 0.6.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/README.md +97 -149
- package/package.json +1 -1
- package/spec-cli/hooks/harness.sh +26 -6
- package/spec-cli/src/anchors.ts +27 -5
- package/spec-cli/src/cli.ts +239 -75
- package/spec-cli/src/client.ts +113 -14
- package/spec-cli/src/codex-runtime-generations.ts +97 -7
- package/spec-cli/src/commit-surgery.ts +2 -1
- package/spec-cli/src/contract-filter.ts +58 -42
- package/spec-cli/src/delivery-queue.ts +62 -4
- package/spec-cli/src/doctor.ts +2 -1
- package/spec-cli/src/execution-trace.ts +444 -0
- package/spec-cli/src/file-write.ts +22 -0
- package/spec-cli/src/gateway-hub.ts +10 -8
- package/spec-cli/src/gateway.ts +99 -6
- package/spec-cli/src/git.ts +503 -105
- package/spec-cli/src/graph.ts +56 -6
- package/spec-cli/src/graphCache.ts +86 -17
- package/spec-cli/src/graphStream.ts +79 -11
- package/spec-cli/src/guide.ts +93 -4
- package/spec-cli/src/harness-select.ts +2 -2
- package/spec-cli/src/harness.ts +355 -75
- package/spec-cli/src/help.ts +30 -23
- package/spec-cli/src/host-resources.ts +62 -7
- package/spec-cli/src/identity-presets.js +16 -6
- package/spec-cli/src/index.ts +109 -14
- package/spec-cli/src/init.ts +8 -17
- package/spec-cli/src/layout.ts +151 -40
- package/spec-cli/src/lint.ts +37 -7
- package/spec-cli/src/materialize.ts +160 -112
- package/spec-cli/src/plugin-harness.ts +29 -18
- package/spec-cli/src/process-identity.ts +13 -0
- package/spec-cli/src/reviews.ts +103 -30
- package/spec-cli/src/session-execution.ts +68 -0
- package/spec-cli/src/session-files.ts +121 -0
- package/spec-cli/src/session-follow.ts +4 -4
- package/spec-cli/src/session-reparent.ts +33 -0
- package/spec-cli/src/session-timeline.ts +163 -14
- package/spec-cli/src/session-web.ts +135 -0
- package/spec-cli/src/sessions.ts +748 -111
- package/spec-cli/src/uninstall.ts +3 -2
- package/spec-cli/templates/hooks/post-checkout +3 -0
- package/spec-cli/templates/hooks/post-merge +1 -0
- package/spec-cli/templates/spec/project/.plugins/commands/supervisor/spec.md +1 -1
- package/spec-cli/templates/spec/project/.plugins/core/comment-altitude/spec.md +2 -21
- package/spec-cli/templates/spec/project/.plugins/core/spec.md +12 -13
- package/spec-cli/templates/spec/project/.plugins/core/stop-gate/spec.md +1 -1
- package/spec-cli/templates/spec/project/.plugins/core/stop-gate/stop-gate.sh +2 -2
- package/spec-cli/templates/spec/project/.plugins/prompts/atomic-landing/spec.md +5 -22
- package/spec-cli/templates/spec/project/.plugins/prompts/forge-link/spec.md +2 -1
- package/spec-cli/templates/spec/project/.plugins/prompts/memory-hygiene/spec.md +4 -6
- package/spec-cli/templates/spec/project/.plugins/prompts/reproduce-before-fix/spec.md +3 -9
- package/spec-dashboard/dist/assets/{App-b8Nh0sgk.js → App-B6IbFbGu.js} +2 -2
- package/spec-dashboard/dist/assets/Dashboard-_sDk7Rcg.js +27 -0
- package/spec-dashboard/dist/assets/EvalsPage-BT357Lyt.js +2 -0
- package/spec-dashboard/dist/assets/{IssuesPage-CAP64YWE.js → IssuesPage-BG9rNOgV.js} +1 -1
- package/spec-dashboard/dist/assets/MobileApp-D9yPMR3A.js +2 -0
- package/spec-dashboard/dist/assets/{Modal-Drscez-d.js → Modal-B3EcgeA3.js} +1 -1
- package/spec-dashboard/dist/assets/{PageScroll-qW6uOJL8.js → PageScroll-CNZOugWV.js} +1 -1
- package/spec-dashboard/dist/assets/{ProjectsPage-CjybFBmR.js → ProjectsPage-C2CRHmvs.js} +1 -1
- package/spec-dashboard/dist/assets/SessionInterface-DOEjuT3l.js +40 -0
- package/spec-dashboard/dist/assets/{SessionWindow-iOk0yHoU.js → SessionWindow-1nYgZ4CL.js} +1 -1
- package/spec-dashboard/dist/assets/Settings-7WeTJn0V.js +1 -0
- package/spec-dashboard/dist/assets/Thread-CEbzAFQn.js +13 -0
- package/spec-dashboard/dist/assets/data-SlvxNA2r.js +1 -0
- package/spec-dashboard/dist/assets/{index-paP-z_Vd.js → index-DLVeOHL-.js} +10 -10
- package/spec-dashboard/dist/assets/index-DYLGfPjx.css +1 -0
- package/spec-dashboard/dist/assets/{launch-B-bYdWmh.js → launch-yxCYr64x.js} +29 -29
- package/spec-dashboard/dist/assets/sessionSurface--7l69h4F.js +1 -0
- package/spec-dashboard/dist/index.html +2 -2
- package/spec-dashboard/src/reviewFilters.js +5 -0
- package/spec-dashboard/src/session.js +5 -1
- package/spec-eval/src/cli.ts +13 -9
- package/spec-eval/src/evaltab.ts +32 -10
- package/spec-eval/src/freshness.ts +404 -81
- package/spec-eval/src/scenariofresh.ts +58 -8
- package/spec-eval/src/scenarios.ts +60 -12
- package/spec-eval/src/sessioneval.ts +234 -148
- package/spec-dashboard/dist/assets/Dashboard-CvAjfRC2.js +0 -27
- package/spec-dashboard/dist/assets/EvalsPage-Bz-nMKoS.js +0 -2
- package/spec-dashboard/dist/assets/MobileApp-D9L1Va8Z.js +0 -2
- package/spec-dashboard/dist/assets/SessionInterface-Dl9v0JFM.js +0 -39
- package/spec-dashboard/dist/assets/Settings-BZ1lGRJs.js +0 -1
- package/spec-dashboard/dist/assets/Thread-D_kcDnfd.js +0 -13
- package/spec-dashboard/dist/assets/data-Bwd3kAVL.js +0 -1
- package/spec-dashboard/dist/assets/index-DAbQBBK_.css +0 -1
- package/spec-dashboard/dist/assets/terminalFont-C6oGNKrf.js +0 -1
- package/spec-eval/src/matrix.ts +0 -693
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{P as N}from"./index-DLVeOHL-.js";const u="spexcode.terminal-font-size",T=9,g=18,c=1,r=new Set;function b(){const e=Number.parseFloat(getComputedStyle(document.documentElement).getPropertyValue("--type-terminal"));if(!Number.isFinite(e))throw new Error("Terminal font-size token is missing or invalid");return e}function f(e){const t=Number(e);return Number.isFinite(t)&&t>=T&&t<=g?Math.round(t/c)*c:null}function _(){let e=null;try{e=f(localStorage.getItem(u))}catch{}return e??b()}function p(e){const t=f(e);if(t===null)throw new Error(`Invalid terminal font size: ${e}`);try{localStorage.setItem(u,String(t))}catch{}for(const s of r)s(t);return t}function w(e){return r.add(e),()=>r.delete(e)}const S="terminal",y="conversation",F="spexcode.session-surface.v1",A=new Set([S,y]),o=new Set;let l=null;const d=(e=N)=>`${F}.${e||"root"}`,i=()=>({defaultSurface:S,sessions:{}}),a=e=>A.has(e);function I(e){const t=i();return!e||typeof e!="object"||(a(e.defaultSurface)&&(t.defaultSurface=e.defaultSurface),e.sessions&&typeof e.sessions=="object"&&!Array.isArray(e.sessions)&&(t.sessions=Object.fromEntries(Object.entries(e.sessions).filter(([,s])=>a(s))))),t}function n(){try{const e=localStorage.getItem(d());return e?I(JSON.parse(e)):i()}catch{return l||i()}}function m(e){l=e;try{localStorage.setItem(d(),JSON.stringify(e))}catch{}for(const t of o)t(e);return e}function E(e){if(!a(e))throw new Error(`Invalid session surface: ${e}`);return e}function R(){return n().defaultSurface}function h(e){const t=n();return t.sessions[e]||t.defaultSurface}function z(e){const t=n();return m({...t,defaultSurface:E(e)}).defaultSurface}function M(e,t){if(!e)throw new Error("A session id is required to set its base surface");const s=n();return m({...s,sessions:{...s.sessions,[e]:E(t)}}).sessions[e]}function C(e){return o.add(e),()=>o.delete(e)}export{S,c as T,R as a,y as b,g as c,T as d,z as e,w as f,_ as g,C as h,h as i,M as j,p as s};
|
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
} catch (e) { /* localStorage unavailable — the bare :root default (Minimal) stands */ }
|
|
21
21
|
})()
|
|
22
22
|
</script>
|
|
23
|
-
<script type="module" crossorigin src="/assets/index-
|
|
24
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
23
|
+
<script type="module" crossorigin src="/assets/index-DLVeOHL-.js"></script>
|
|
24
|
+
<link rel="stylesheet" crossorigin href="/assets/index-DYLGfPjx.css">
|
|
25
25
|
</head>
|
|
26
26
|
<body>
|
|
27
27
|
<div id="root"></div>
|
|
@@ -169,6 +169,11 @@ const reviewStateOf = (entry) => (evalIsResult(entry) && entry.fresh && entry.hu
|
|
|
169
169
|
const evalFresh = (entry) => entry.fresh === true
|
|
170
170
|
const freshnessOf = (entry) => (evalIsResult(entry) ? (evalFresh(entry) ? 'fresh' : 'stale') : null)
|
|
171
171
|
export const evalReviewState = (reading) => {
|
|
172
|
+
// an order-only row never had its freshness computed, so it has no verdict to report. Refuse it here
|
|
173
|
+
// rather than let its conservative placeholder be published as a measured `stalePass`/`staleFail`: the
|
|
174
|
+
// whole point of the deferred pass is that these rows exist only to establish sequence.
|
|
175
|
+
if (reading?.freshnessDeferred)
|
|
176
|
+
throw new Error(`eval row ${reading.scenario ?? '?'} has deferred freshness — it can order a list, never state a verdict`)
|
|
172
177
|
const status = reading?.verdict?.status
|
|
173
178
|
if (status !== 'pass' && status !== 'fail') return 'empty'
|
|
174
179
|
if (reading.fresh) return status
|
|
@@ -88,7 +88,11 @@ export const sessionHandle = (s) =>
|
|
|
88
88
|
s?.label || s?.name || s?.node || s?.title || s?.branch || s?.id
|
|
89
89
|
|
|
90
90
|
export const sessionHeadline = (s) =>
|
|
91
|
-
s?.headline || s?.name || s?.activity || s?.promptPreview || s?.node || s?.title || s?.branch || s?.id
|
|
91
|
+
s?.title || s?.headline || s?.name || s?.activity || s?.note || s?.promptPreview || s?.node || s?.raw?.title || s?.branch || s?.id
|
|
92
|
+
|
|
93
|
+
// The visible session name is a single derived `title` on current backends. Keep the old
|
|
94
|
+
// accessor as a compatibility alias while mixed-version clients roll forward.
|
|
95
|
+
export const sessionTitle = sessionHeadline
|
|
92
96
|
|
|
93
97
|
export function nestSessions(sessions) {
|
|
94
98
|
const present = new Set(sessions.map((s) => s?.id))
|
package/spec-eval/src/cli.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { trackedSourceFiles } from '../../spec-cli/src/source-files.js'
|
|
|
7
7
|
import { mainBranch, envSessionId, readRawRecord } from '../../spec-cli/src/layout.js'
|
|
8
8
|
import { evalNodes, evalNodesAt, validateScenarios, resolveEvalNode, scenarioCodeAxis, scenarioHash, scenarioProjection, writeScenarioMeasurementMetadata, EVAL_FILE, type EvalNode, type ScenarioTestReference } from './scenarios.js'
|
|
9
9
|
import { readReadings, readSidecar, appendReading, appendRetraction, latestPerScenario, evidenceOf, isJsonBlob, type Reading, type Verdict, type Evidence, type EvidenceKind, type Retraction } from './sidecar.js'
|
|
10
|
-
import { staleAxes, contentProbeFor, anchorProbeFor, anchorProblems, type AnchorDemand } from './freshness.js'
|
|
10
|
+
import { staleAxes, contentProbeFor, anchorProbeFor, anchorProblems, type AnchorDemand, type ContentProbeDemand } from './freshness.js'
|
|
11
11
|
import { parseRelation, relationClaimsPath } from '../../spec-cli/src/anchors.js'
|
|
12
12
|
import { scenarioIndex } from './scenariofresh.js'
|
|
13
13
|
import { loadEvalRemarkTracks, trackKey } from '../../spec-cli/src/issues.js'
|
|
@@ -144,6 +144,7 @@ async function scan(args: string[] = []): Promise<number> {
|
|
|
144
144
|
// the in-loop primes stay as the correctness backstop and become cache hits.
|
|
145
145
|
const latestByDir = new Map<string, ReturnType<typeof latestPerScenario>>()
|
|
146
146
|
const anchorDemands: AnchorDemand[] = []
|
|
147
|
+
const contentDemands: ContentProbeDemand[] = []
|
|
147
148
|
for (const s of specs) {
|
|
148
149
|
const dirRel = dirname(s.path)
|
|
149
150
|
const y = yByDir.get(dirRel)
|
|
@@ -156,8 +157,12 @@ async function scan(args: string[] = []): Promise<number> {
|
|
|
156
157
|
const axis = scenarioCodeAxis(sc.code, s.codeEntries)
|
|
157
158
|
if (changed && !nodeChanged(dirRel, axis.paths, changed, nodeDirs)) continue
|
|
158
159
|
anchorDemands.push({ sinceSha: r.codeSha, entries: axis.entries })
|
|
160
|
+
if (!commitReachable(idx, r.codeSha))
|
|
161
|
+
contentDemands.push({ anchorSha: r.codeSha, paths: axis.paths, evalPath: y.evalPath })
|
|
159
162
|
}
|
|
160
163
|
}
|
|
164
|
+
await probe.primeMany?.(contentDemands)
|
|
165
|
+
await probe.primeBlocks?.()
|
|
161
166
|
await anchors.prime?.(anchorDemands)
|
|
162
167
|
for (const s of specs) {
|
|
163
168
|
const dirRel = dirname(s.path)
|
|
@@ -213,7 +218,12 @@ async function scan(args: string[] = []): Promise<number> {
|
|
|
213
218
|
}
|
|
214
219
|
if (!driftSelected) continue
|
|
215
220
|
const remSignals = (remarkTracks.get(trackKey(s.id, sc.name))?.remarks ?? []).map((rm) => ({ resolved: !!rm.resolved, resolvedAt: rm.resolvedAt }))
|
|
216
|
-
if (!commitReachable(idx, r.codeSha))
|
|
221
|
+
if (!commitReachable(idx, r.codeSha)) {
|
|
222
|
+
await probe.prime?.(r.codeSha, codeFiles, y.evalPath)
|
|
223
|
+
// lint decides this reading before moving on, so its demand is flushed here rather than pooled;
|
|
224
|
+
// the batched pair still beats the per-revision lookups it replaces.
|
|
225
|
+
await probe.primeBlocks?.()
|
|
226
|
+
}
|
|
217
227
|
await anchors.prime?.([{ sinceSha: r.codeSha, entries: axis.entries }])
|
|
218
228
|
const axes = staleAxes(r, axis.entries, y.evalPath, idx, scidx, remSignals, probe, sc, anchors)
|
|
219
229
|
if (axes.length) {
|
|
@@ -803,19 +813,13 @@ export async function runEval(args: string[]): Promise<number> {
|
|
|
803
813
|
if (sub === 'retract') return retractCmd(args.slice(1))
|
|
804
814
|
if (sub === 'clean') return clean(args.slice(1))
|
|
805
815
|
if (sub === 'ls') return show(args.slice(1))
|
|
806
|
-
if (sub === 'matrix') {
|
|
807
|
-
// the live-behavior matrix runner ([[live-matrix]]) — lazily imported so the heavy session machinery
|
|
808
|
-
// never loads for the plain filing/reading verbs.
|
|
809
|
-
const { runMatrix } = await import('./matrix.js')
|
|
810
|
-
return runMatrix(args.slice(1))
|
|
811
|
-
}
|
|
812
816
|
if (sub === 'scenario') {
|
|
813
817
|
if (args[1] === 'ls') return scenarioLs(args.slice(2))
|
|
814
818
|
if (args[1] === 'write') return scenarioWrite(args.slice(2))
|
|
815
819
|
console.error('spex eval scenario: ls [<node>|.] [--unmeasured] [--json] | write --mutation <json> < eval.md')
|
|
816
820
|
return 2
|
|
817
821
|
}
|
|
818
|
-
console.error('spex eval: add [.|<node>] [--scenario <name>] (--pass|--fail) [--note <text>] [--image <path> …repeatable] [--result <path|->] [--video <path>] [--timeline <json>] | ls [.|<node>] [--json] | ls --session <SEL> [--export] | scenario ls [<node>|.] [--unmeasured] [--json] | scenario write --mutation <json> < eval.md |
|
|
822
|
+
console.error('spex eval: add [.|<node>] [--scenario <name>] (--pass|--fail) [--note <text>] [--image <path> …repeatable] [--result <path|->] [--video <path>] [--timeline <json>] | ls [.|<node>] [--json] | ls --session <SEL> [--export] | scenario ls [<node>|.] [--unmeasured] [--json] | scenario write --mutation <json> < eval.md | lint [--changed] | ok <node> [--scenario <name>] | retract [.|<node>] [--scenario <name>] [--last | --ts <iso>] [--note <why>] | clean [--keep-latest|--all]')
|
|
819
823
|
return 2
|
|
820
824
|
}
|
|
821
825
|
|
package/spec-eval/src/evaltab.ts
CHANGED
|
@@ -38,6 +38,10 @@ export type EvalEntry = {
|
|
|
38
38
|
ts: string
|
|
39
39
|
fresh: boolean
|
|
40
40
|
staleAxes: StaleAxis[]
|
|
41
|
+
// set by an order-only read, whose `fresh`/`staleAxes` are placeholders it never computed. A consumer
|
|
42
|
+
// that ignores the flag reads the CONSERVATIVE direction (stale), and evalReviewState refuses the row
|
|
43
|
+
// outright — so a deferred verdict can neither be published as fresh nor pass silently.
|
|
44
|
+
freshnessDeferred?: true
|
|
41
45
|
blobState: 'present' | 'miss' | 'none'
|
|
42
46
|
codeDrift?: { file: string; behind: number }[]
|
|
43
47
|
remarks?: RemarkView[]
|
|
@@ -98,7 +102,12 @@ export async function evalContext(
|
|
|
98
102
|
// per read rather than once per reading. evalTimelines is the plural the graph build actually wants: it
|
|
99
103
|
// plans every node's rows first (pure fs + in-memory projection), primes the content and anchor probes with
|
|
100
104
|
// the WHOLE demand set, then assembles. Singular evalTimeline is the one-id case of the same path.
|
|
101
|
-
|
|
105
|
+
// @@@ `order` skips the FRESHNESS pass, not the rows - a caller that only needs to know WHICH scenarios are
|
|
106
|
+
// in play and in what sequence (identity, filed time, measured-or-blind) pays for none of the probes: on a
|
|
107
|
+
// 415-node scope the freshness pass is 25s and 1476 git children, the rows themselves are milliseconds.
|
|
108
|
+
// Rows returned this way carry `freshnessDeferred` INSTEAD of `fresh`/`staleAxes`, and evalReviewState
|
|
109
|
+
// refuses them loudly — an order row can never be mistaken for a measured verdict.
|
|
110
|
+
export async function evalTimelines(ids: readonly string[], ctx?: EvalContext, opts: { order?: boolean } = {}): Promise<EvalTimeline[]> {
|
|
102
111
|
const root = ctx?.root ?? repoRoot()
|
|
103
112
|
const ynodes = ctx?.ynodes ?? evalNodes(root)
|
|
104
113
|
const specs = ctx?.specs ?? await loadSpecs()
|
|
@@ -123,13 +132,24 @@ export async function evalTimelines(ids: readonly string[], ctx?: EvalContext):
|
|
|
123
132
|
return { id, ynode, codeEntries, rows, retractions, oks }
|
|
124
133
|
})
|
|
125
134
|
|
|
126
|
-
//
|
|
127
|
-
|
|
128
|
-
|
|
135
|
+
// an order-only read stops HERE, before a single probe: the rows above are already the whole answer.
|
|
136
|
+
if (opts.order)
|
|
137
|
+
return plans.map((plan) => assembleTimeline(plan.id, plan.ynode, plan.rows, plan.retractions, plan.oks, {
|
|
138
|
+
idx, scidx, tracks, probe, anchors, order: true,
|
|
139
|
+
}))
|
|
140
|
+
|
|
141
|
+
// An off-history anchor is the only reading that needs a content verdict. Plan the population first so the
|
|
142
|
+
// probe can answer many anchor/HEAD pairs through bounded plural children instead of one child per anchor.
|
|
143
|
+
const contentDemands = plans.flatMap((plan) => plan.ynode
|
|
129
144
|
? plan.rows.filter((row) => !commitReachable(idx, row.reading.codeSha))
|
|
130
|
-
.map((row) =>
|
|
131
|
-
: [])
|
|
145
|
+
.map((row) => ({ anchorSha: row.reading.codeSha, paths: row.axis.paths, evalPath: plan.ynode!.evalPath }))
|
|
146
|
+
: [])
|
|
147
|
+
if (probe.primeMany) await probe.primeMany(contentDemands)
|
|
148
|
+
else await Promise.all(contentDemands.map((row) => probe.prime?.(row.anchorSha, [...row.paths], row.evalPath)))
|
|
132
149
|
await anchors.prime?.(plans.flatMap((plan) => plan.rows.map((row) => ({ sinceSha: row.reading.codeSha, entries: row.axis.entries }))))
|
|
150
|
+
// the primes above only RECORDED which eval.md blocks the assemble step will compare; one batched pair of
|
|
151
|
+
// children answers the whole read's worth, the same shape the anchor prime above already has.
|
|
152
|
+
await probe.primeBlocks?.()
|
|
133
153
|
|
|
134
154
|
return plans.map((plan) => assembleTimeline(plan.id, plan.ynode, plan.rows, plan.retractions, plan.oks, {
|
|
135
155
|
idx, scidx, tracks, probe, anchors,
|
|
@@ -143,6 +163,7 @@ export async function evalTimeline(id: string, ctx?: EvalContext): Promise<EvalT
|
|
|
143
163
|
type AssembleDeps = {
|
|
144
164
|
idx: DriftIndex; scidx: ScenarioIndex; tracks: Map<string, RemarkTrack>
|
|
145
165
|
probe: ReturnType<typeof contentProbeFor>; anchors: ReturnType<typeof anchorProbeFor>
|
|
166
|
+
order?: boolean // identity/sequence only — emit no freshness claim
|
|
146
167
|
}
|
|
147
168
|
function assembleTimeline(
|
|
148
169
|
id: string,
|
|
@@ -150,7 +171,7 @@ function assembleTimeline(
|
|
|
150
171
|
rows: { reading: Reading; axis: ReturnType<typeof scenarioCodeAxis> }[],
|
|
151
172
|
retractions: Retraction[],
|
|
152
173
|
oks: HumanOk[],
|
|
153
|
-
{ idx, scidx, tracks, probe, anchors }: AssembleDeps,
|
|
174
|
+
{ idx, scidx, tracks, probe, anchors, order }: AssembleDeps,
|
|
154
175
|
): EvalTimeline {
|
|
155
176
|
if (!ynode) return { node: id, hasEvalFile: false, scenarios: [], readings: [], retractions: [], dangling: [] }
|
|
156
177
|
const byName = new Map(ynode.scenarios.map((s) => [s.name, s]))
|
|
@@ -164,9 +185,9 @@ function assembleTimeline(
|
|
|
164
185
|
const readings: EvalEntry[] = []
|
|
165
186
|
for (const { reading: r, axis } of rows) {
|
|
166
187
|
const sc = byName.get(r.scenario)
|
|
167
|
-
const axes = staleAxes(r, axis.entries, ynode.evalPath, idx, scidx,
|
|
188
|
+
const axes = order ? [] : staleAxes(r, axis.entries, ynode.evalPath, idx, scidx,
|
|
168
189
|
remarksFor(r.scenario).map((rm) => ({ resolved: !!rm.resolved, resolvedAt: rm.resolvedAt })), probe, sc, anchors)
|
|
169
|
-
const drift = axes.includes('code') ? codeDrift(idx, r.codeSha, axis.entries, probe) : []
|
|
190
|
+
const drift = !order && axes.includes('code') ? codeDrift(idx, r.codeSha, axis.entries, probe) : []
|
|
170
191
|
const evidence: EvidenceView[] = evidenceOf(r).map((e) => ({ hash: e.hash, kind: e.kind, state: hasBlob(e.hash) ? 'present' : 'miss' }))
|
|
171
192
|
const primary = evidence.find((e) => e.kind === 'video') ?? evidence[0]
|
|
172
193
|
const okRow = humanOkFor(oks, r.scenario, r.ts)
|
|
@@ -182,8 +203,9 @@ function assembleTimeline(
|
|
|
182
203
|
...(r.by ? { by: r.by } : {}),
|
|
183
204
|
...(r.verdict ? { verdict: r.verdict } : {}),
|
|
184
205
|
ts: r.ts,
|
|
185
|
-
fresh: axes.length === 0,
|
|
206
|
+
fresh: !order && axes.length === 0,
|
|
186
207
|
staleAxes: axes,
|
|
208
|
+
...(order ? { freshnessDeferred: true as const } : {}),
|
|
187
209
|
...(drift.length ? { codeDrift: drift } : {}),
|
|
188
210
|
blobState: primary ? primary.state : 'none',
|
|
189
211
|
...(threadFor(r.scenario) ? { thread: threadFor(r.scenario) } : {}),
|