@manyoars/oars 0.1.1 → 0.3.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.
Files changed (240) hide show
  1. package/README.md +134 -26
  2. package/dist/api.js +1 -1
  3. package/dist/bin.js +6 -29
  4. package/dist/cli.d.ts +6 -3
  5. package/dist/cli.js +370 -72
  6. package/dist/commands/keys.d.ts +31 -0
  7. package/dist/commands/keys.js +65 -0
  8. package/dist/commands/login.d.ts +1 -1
  9. package/dist/commands/login.js +3 -5
  10. package/dist/errors.d.ts +13 -13
  11. package/dist/errors.js +28 -11
  12. package/dist/gate/checks.d.ts +68 -0
  13. package/dist/gate/checks.js +584 -0
  14. package/dist/gate/command.d.ts +14 -0
  15. package/dist/gate/command.js +61 -0
  16. package/dist/gate/hook-fill-main.d.ts +1 -0
  17. package/dist/gate/hook-fill-main.js +16 -0
  18. package/dist/gate/hook-fill.d.ts +38 -0
  19. package/dist/gate/hook-fill.js +94 -0
  20. package/dist/gate/hook-main.d.ts +1 -0
  21. package/dist/gate/hook-main.js +35 -0
  22. package/dist/gate/hook-process.d.ts +14 -0
  23. package/dist/gate/hook-process.js +64 -0
  24. package/dist/gate/hook.d.ts +46 -0
  25. package/dist/gate/hook.js +256 -0
  26. package/dist/gate/install.d.ts +25 -0
  27. package/dist/gate/install.js +126 -0
  28. package/dist/gate/local-bin.d.ts +8 -0
  29. package/dist/gate/local-bin.js +36 -0
  30. package/dist/gate/notes.d.ts +19 -0
  31. package/dist/gate/notes.js +27 -0
  32. package/dist/gate/novelty.d.ts +35 -0
  33. package/dist/gate/novelty.js +99 -0
  34. package/dist/gate/policy.d.ts +25 -0
  35. package/dist/gate/policy.js +73 -0
  36. package/dist/gate/record.d.ts +120 -0
  37. package/dist/gate/record.js +125 -0
  38. package/dist/gate/registry-state.d.ts +10 -0
  39. package/dist/gate/registry-state.js +30 -0
  40. package/dist/gate/registry.d.ts +56 -0
  41. package/dist/gate/registry.js +94 -0
  42. package/dist/gate/replay.d.ts +20 -0
  43. package/dist/gate/replay.js +257 -0
  44. package/dist/gate/report.d.ts +50 -0
  45. package/dist/gate/report.js +89 -0
  46. package/dist/gate/scope.d.ts +7 -0
  47. package/dist/gate/scope.js +27 -0
  48. package/dist/gate/transcripts.d.ts +68 -0
  49. package/dist/gate/transcripts.js +232 -0
  50. package/dist/hook-bin.d.ts +1 -0
  51. package/dist/hook-bin.js +25 -0
  52. package/dist/issuer.d.ts +16 -0
  53. package/dist/issuer.js +55 -0
  54. package/dist/main.d.ts +1 -0
  55. package/dist/main.js +64 -0
  56. package/dist/oidc.js +1 -1
  57. package/dist/sessions/actions.d.ts +35 -0
  58. package/dist/sessions/actions.js +351 -0
  59. package/dist/sessions/allow.d.ts +14 -0
  60. package/dist/sessions/allow.js +51 -0
  61. package/dist/sessions/archive.d.ts +11 -0
  62. package/dist/sessions/archive.js +38 -0
  63. package/dist/sessions/audit.d.ts +42 -0
  64. package/dist/sessions/audit.js +104 -0
  65. package/dist/sessions/baseline.d.ts +62 -0
  66. package/dist/sessions/baseline.js +188 -0
  67. package/dist/sessions/capture-runtime.d.ts +10 -0
  68. package/dist/sessions/capture-runtime.js +12 -0
  69. package/dist/sessions/cell-increments.d.ts +43 -0
  70. package/dist/sessions/cell-increments.js +209 -0
  71. package/dist/sessions/cells.d.ts +79 -0
  72. package/dist/sessions/cells.js +181 -0
  73. package/dist/sessions/codex.d.ts +53 -0
  74. package/dist/sessions/codex.js +679 -0
  75. package/dist/sessions/command.d.ts +117 -0
  76. package/dist/sessions/command.js +373 -0
  77. package/dist/sessions/copilot.d.ts +40 -0
  78. package/dist/sessions/copilot.js +432 -0
  79. package/dist/sessions/cursor.d.ts +24 -0
  80. package/dist/sessions/cursor.js +324 -0
  81. package/dist/sessions/derive.d.ts +57 -0
  82. package/dist/sessions/derive.js +320 -0
  83. package/dist/sessions/exclusion.d.ts +81 -0
  84. package/dist/sessions/exclusion.js +353 -0
  85. package/dist/sessions/extract.d.ts +179 -0
  86. package/dist/sessions/extract.js +1774 -0
  87. package/dist/sessions/fold.d.ts +17 -0
  88. package/dist/sessions/fold.js +71 -0
  89. package/dist/sessions/frame.d.ts +24 -0
  90. package/dist/sessions/frame.js +95 -0
  91. package/dist/sessions/gemini.d.ts +37 -0
  92. package/dist/sessions/gemini.js +613 -0
  93. package/dist/sessions/gold-arms.d.ts +67 -0
  94. package/dist/sessions/gold-arms.js +172 -0
  95. package/dist/sessions/gold.d.ts +197 -0
  96. package/dist/sessions/gold.js +463 -0
  97. package/dist/sessions/graph.d.ts +12 -0
  98. package/dist/sessions/graph.js +34 -0
  99. package/dist/sessions/grok.d.ts +36 -0
  100. package/dist/sessions/grok.js +421 -0
  101. package/dist/sessions/hook-calls.d.ts +3 -0
  102. package/dist/sessions/hook-calls.js +47 -0
  103. package/dist/sessions/hook-index.d.ts +53 -0
  104. package/dist/sessions/hook-index.js +157 -0
  105. package/dist/sessions/hooks.d.ts +13 -0
  106. package/dist/sessions/hooks.js +170 -0
  107. package/dist/sessions/identity.d.ts +22 -0
  108. package/dist/sessions/identity.js +101 -0
  109. package/dist/sessions/install.d.ts +54 -0
  110. package/dist/sessions/install.js +201 -0
  111. package/dist/sessions/lineage.d.ts +19 -0
  112. package/dist/sessions/lineage.js +118 -0
  113. package/dist/sessions/mcp.d.ts +6 -0
  114. package/dist/sessions/mcp.js +153 -0
  115. package/dist/sessions/merkle.d.ts +7 -0
  116. package/dist/sessions/merkle.js +28 -0
  117. package/dist/sessions/motif.d.ts +43 -0
  118. package/dist/sessions/motif.js +59 -0
  119. package/dist/sessions/package-resolve.d.ts +30 -0
  120. package/dist/sessions/package-resolve.js +183 -0
  121. package/dist/sessions/pending.d.ts +34 -0
  122. package/dist/sessions/pending.js +142 -0
  123. package/dist/sessions/pool-command.d.ts +44 -0
  124. package/dist/sessions/pool-command.js +542 -0
  125. package/dist/sessions/pool-delta.d.ts +54 -0
  126. package/dist/sessions/pool-delta.js +190 -0
  127. package/dist/sessions/pool-outbox.d.ts +63 -0
  128. package/dist/sessions/pool-outbox.js +260 -0
  129. package/dist/sessions/pool-print.d.ts +26 -0
  130. package/dist/sessions/pool-print.js +68 -0
  131. package/dist/sessions/pool-wire.d.ts +225 -0
  132. package/dist/sessions/pool-wire.js +620 -0
  133. package/dist/sessions/pool.d.ts +32 -0
  134. package/dist/sessions/pool.js +164 -0
  135. package/dist/sessions/projection.d.ts +75 -0
  136. package/dist/sessions/projection.js +165 -0
  137. package/dist/sessions/public-vocabulary.d.ts +73 -0
  138. package/dist/sessions/public-vocabulary.js +302 -0
  139. package/dist/sessions/query.d.ts +111 -0
  140. package/dist/sessions/query.js +246 -0
  141. package/dist/sessions/read-adoption.d.ts +2 -0
  142. package/dist/sessions/read-adoption.js +27 -0
  143. package/dist/sessions/read-call-shape.d.ts +2 -0
  144. package/dist/sessions/read-call-shape.js +25 -0
  145. package/dist/sessions/read-change.d.ts +2 -0
  146. package/dist/sessions/read-change.js +30 -0
  147. package/dist/sessions/read-cost.d.ts +4 -0
  148. package/dist/sessions/read-cost.js +37 -0
  149. package/dist/sessions/read-day-move.d.ts +3 -0
  150. package/dist/sessions/read-day-move.js +65 -0
  151. package/dist/sessions/read-fit.d.ts +7 -0
  152. package/dist/sessions/read-fit.js +41 -0
  153. package/dist/sessions/read-interaction.d.ts +5 -0
  154. package/dist/sessions/read-interaction.js +62 -0
  155. package/dist/sessions/read-lead-lag.d.ts +15 -0
  156. package/dist/sessions/read-lead-lag.js +81 -0
  157. package/dist/sessions/read-live.d.ts +6 -0
  158. package/dist/sessions/read-live.js +38 -0
  159. package/dist/sessions/read-method.d.ts +2 -0
  160. package/dist/sessions/read-method.js +29 -0
  161. package/dist/sessions/read-next-step.d.ts +2 -0
  162. package/dist/sessions/read-next-step.js +35 -0
  163. package/dist/sessions/read-order.d.ts +54 -0
  164. package/dist/sessions/read-order.js +62 -0
  165. package/dist/sessions/read-readable.d.ts +8 -0
  166. package/dist/sessions/read-readable.js +24 -0
  167. package/dist/sessions/read-recovery.d.ts +20 -0
  168. package/dist/sessions/read-recovery.js +97 -0
  169. package/dist/sessions/read-rows.d.ts +13 -0
  170. package/dist/sessions/read-rows.js +47 -0
  171. package/dist/sessions/read-substitution.d.ts +5 -0
  172. package/dist/sessions/read-substitution.js +51 -0
  173. package/dist/sessions/read-support.d.ts +48 -0
  174. package/dist/sessions/read-support.js +73 -0
  175. package/dist/sessions/read-tool-day.d.ts +2 -0
  176. package/dist/sessions/read-tool-day.js +26 -0
  177. package/dist/sessions/read-version-boundary.d.ts +31 -0
  178. package/dist/sessions/read-version-boundary.js +95 -0
  179. package/dist/sessions/read.d.ts +79 -0
  180. package/dist/sessions/read.js +84 -0
  181. package/dist/sessions/recall-consequence.d.ts +45 -0
  182. package/dist/sessions/recall-consequence.js +179 -0
  183. package/dist/sessions/recall-object.d.ts +137 -0
  184. package/dist/sessions/recall-object.js +77 -0
  185. package/dist/sessions/recall-pool.d.ts +19 -0
  186. package/dist/sessions/recall-pool.js +96 -0
  187. package/dist/sessions/recall-push.d.ts +26 -0
  188. package/dist/sessions/recall-push.js +95 -0
  189. package/dist/sessions/recall-reads.d.ts +117 -0
  190. package/dist/sessions/recall-reads.js +410 -0
  191. package/dist/sessions/recall-render.d.ts +19 -0
  192. package/dist/sessions/recall-render.js +168 -0
  193. package/dist/sessions/redact.d.ts +6 -0
  194. package/dist/sessions/redact.js +54 -0
  195. package/dist/sessions/repo-root.d.ts +10 -0
  196. package/dist/sessions/repo-root.js +101 -0
  197. package/dist/sessions/schema.d.ts +644 -0
  198. package/dist/sessions/schema.js +148 -0
  199. package/dist/sessions/session-shape.d.ts +73 -0
  200. package/dist/sessions/session-shape.js +342 -0
  201. package/dist/sessions/sources.d.ts +50 -0
  202. package/dist/sessions/sources.js +159 -0
  203. package/dist/sessions/steps.d.ts +105 -0
  204. package/dist/sessions/steps.js +250 -0
  205. package/dist/sessions/store.d.ts +297 -0
  206. package/dist/sessions/store.js +1478 -0
  207. package/dist/sessions/strata.d.ts +26 -0
  208. package/dist/sessions/strata.js +77 -0
  209. package/dist/sessions/survival.d.ts +25 -0
  210. package/dist/sessions/survival.js +230 -0
  211. package/dist/sessions/verify.d.ts +53 -0
  212. package/dist/sessions/verify.js +286 -0
  213. package/dist/sessions/vocab-load.d.ts +26 -0
  214. package/dist/sessions/vocab-load.js +61 -0
  215. package/dist/sessions/vscode-copilot.d.ts +38 -0
  216. package/dist/sessions/vscode-copilot.js +477 -0
  217. package/package.json +4 -5
  218. package/vocab/crates.json +1 -0
  219. package/vocab/go.json +1 -0
  220. package/vocab/harness.json +1 -0
  221. package/vocab/homebrew.json +1 -0
  222. package/vocab/manifest.json +1 -0
  223. package/vocab/maven.json +1 -0
  224. package/vocab/models.json +1 -0
  225. package/vocab/npm.json +1 -0
  226. package/vocab/programs.json +1 -0
  227. package/vocab/pypi.json +1 -0
  228. package/vocab/rubygems.json +1 -0
  229. package/dist/commands/deploy-input.d.ts +0 -35
  230. package/dist/commands/deploy-input.js +0 -107
  231. package/dist/commands/deploy.d.ts +0 -22
  232. package/dist/commands/deploy.js +0 -120
  233. package/dist/commands/status.d.ts +0 -7
  234. package/dist/commands/status.js +0 -18
  235. package/dist/commands/test-charge.d.ts +0 -11
  236. package/dist/commands/test-charge.js +0 -34
  237. package/dist/journal.d.ts +0 -26
  238. package/dist/journal.js +0 -58
  239. package/parity/deploy-plans/paid-api.json +0 -34
  240. package/parity/deploy-plans/per-task-agent.json +0 -34
@@ -0,0 +1,286 @@
1
+ // `oars sessions verify`: consistency invariants over a skeleton, run locally before any future upload. A skeleton holds ids,
2
+ // timestamps, tool linkage and token counts, never content, so the check needs nothing the store does not already hold.
3
+ import { CAPTURE_VERSION, OLDEST_READABLE_CAPTURE } from './schema.js';
4
+ /** The order the classes print in: the two scopings a check needs to hold, then the two causes of a total above the sum. */
5
+ const CLASSES = [
6
+ { cls: 'resume scope', holds: true },
7
+ { cls: 'subagent transcripts', holds: true },
8
+ { cls: 'harness side calls', holds: false },
9
+ { cls: 'other', holds: false },
10
+ ];
11
+ const emptyClasses = () => CLASSES.map((entry) => ({ ...entry, checks: 0, example: null }));
12
+ export const INVARIANTS = [
13
+ 'tool_result names an earlier tool_use',
14
+ 'parentUuid resolves in the file',
15
+ 'message timestamps never step back more than 1 s',
16
+ 'assistant lines carry a request id; shared ids have consecutive apiBlockIndex',
17
+ 'thinking tokens never exceed output tokens',
18
+ 'cost-state totals equal summed usage since the process start, subagents included',
19
+ ];
20
+ const STEP_BACK_MS = 1000;
21
+ const verifyToolLinkage = (lines) => {
22
+ const seen = new Set();
23
+ let checked = 0;
24
+ let violations = 0;
25
+ for (const line of lines) {
26
+ for (const result of line.results) {
27
+ checked += 1;
28
+ if (result === null || !seen.has(result))
29
+ violations += 1;
30
+ }
31
+ for (const tool of line.tools)
32
+ if (tool.id !== null)
33
+ seen.add(tool.id);
34
+ }
35
+ return { name: INVARIANTS[0], checked, violations };
36
+ };
37
+ const verifyParents = (lines) => {
38
+ const ids = new Set(lines.map((line) => line.uuid).filter((uuid) => uuid !== null));
39
+ const withParent = lines.filter((line) => line.parentUuid !== null);
40
+ return { name: INVARIANTS[1], checked: withParent.length, violations: withParent.filter((line) => line.parentUuid !== null && !ids.has(line.parentUuid)).length };
41
+ };
42
+ const verifyTimestamps = (lines) => {
43
+ const stamps = lines
44
+ .filter((line) => line.role !== null && line.ts !== null)
45
+ .map((line) => Date.parse(line.ts ?? ''))
46
+ .filter((millis) => !Number.isNaN(millis));
47
+ let violations = 0;
48
+ for (let index = 1; index < stamps.length; index += 1)
49
+ if ((stamps[index - 1] ?? 0) - (stamps[index] ?? 0) > STEP_BACK_MS)
50
+ violations += 1;
51
+ return { name: INVARIANTS[2], checked: Math.max(stamps.length - 1, 0), violations };
52
+ };
53
+ const verifyRequests = (lines) => {
54
+ const assistants = lines.filter((line) => line.type === 'assistant');
55
+ const groups = new Map();
56
+ let checked = 0;
57
+ let violations = 0;
58
+ for (const line of assistants) {
59
+ checked += 1;
60
+ if (line.requestId === null) {
61
+ violations += 1;
62
+ continue;
63
+ }
64
+ groups.set(line.requestId, [...(groups.get(line.requestId) ?? []), line]);
65
+ }
66
+ for (const group of groups.values()) {
67
+ if (group.length < 2 || group.some((line) => line.apiBlockIndex === null))
68
+ continue;
69
+ for (let index = 1; index < group.length; index += 1) {
70
+ checked += 1;
71
+ if ((group[index]?.apiBlockIndex ?? 0) !== (group[index - 1]?.apiBlockIndex ?? 0) + 1)
72
+ violations += 1;
73
+ }
74
+ }
75
+ return { name: INVARIANTS[3], checked, violations };
76
+ };
77
+ const verifyThinking = (lines) => {
78
+ const reporting = lines.filter((line) => line.hasThinking && line.usage !== null);
79
+ return { name: INVARIANTS[4], checked: reporting.length, violations: reporting.filter((line) => (line.usage?.thinking ?? 0) > (line.usage?.output ?? 0)).length };
80
+ };
81
+ const KEYS = ['input', 'cacheWrite', 'cacheRead', 'output'];
82
+ /** Assistant usage from `since` on. Every content block of one API response is its own line and repeats its usage: once per request id. */
83
+ const sumUsage = (lines, since) => {
84
+ const sums = { input: 0, cacheWrite: 0, cacheRead: 0, output: 0 };
85
+ const counted = new Set();
86
+ for (const line of lines) {
87
+ if (line.type !== 'assistant' || line.usage === null)
88
+ continue;
89
+ if (line.ts !== null && Date.parse(line.ts) < since)
90
+ continue;
91
+ if (line.requestId !== null) {
92
+ if (counted.has(line.requestId))
93
+ continue;
94
+ counted.add(line.requestId);
95
+ }
96
+ for (const key of KEYS)
97
+ sums[key] += line.usage[key];
98
+ }
99
+ return sums;
100
+ };
101
+ /**
102
+ * The harness's `cost-state` totals (A2, diagnosed on the operator's store 2026-11-01) cover only the process that wrote them
103
+ * (from `startTime`, so a resumed session's earlier lines are outside) and include the session's subagent transcripts. Within
104
+ * that scope the recorded usage must equal the totals, and a sum above them fails. A total above the sum is not a failure: the
105
+ * harness also counts side calls no transcript records (001cf645's `modelUsage` has a `claude-haiku` entry and no assistant line
106
+ * of that model), so it is reported as observed, under that class on a key such a model reported a nonzero amount on, else under `other`.
107
+ * A check that holds only once a scope is applied is counted under that scope.
108
+ */
109
+ const verifyCost = (lines, delegated, session) => {
110
+ const last = [...lines].reverse().find((line) => line.costState !== null)?.costState;
111
+ const observed = emptyClasses();
112
+ if (last === undefined || last === null)
113
+ return { name: INVARIANTS[5], checked: 0, violations: 0, observed };
114
+ const since = last.since === null ? Number.NEGATIVE_INFINITY : Date.parse(last.since);
115
+ const scoped = sumUsage([...lines, ...delegated], since);
116
+ const resumed = sumUsage(lines, since);
117
+ const unscoped = sumUsage(lines, Number.NEGATIVE_INFINITY);
118
+ const named = new Set([...lines, ...delegated].map((line) => line.model));
119
+ // A key is a side call only when a model no line names reported a nonzero amount on it; the entries of an older skeleton carry none.
120
+ const sideKeys = new Set(last.models.filter((entry) => !named.has(entry.model)).flatMap((entry) => entry.keys));
121
+ const note = (cls) => {
122
+ const entry = observed.find((candidate) => candidate.cls === cls);
123
+ if (entry === undefined)
124
+ return;
125
+ entry.checks += 1;
126
+ entry.example ??= session;
127
+ };
128
+ let checked = 0;
129
+ let violations = 0;
130
+ for (const key of KEYS) {
131
+ const total = last[key];
132
+ if (total === null)
133
+ continue;
134
+ checked += 1;
135
+ if (scoped[key] > total)
136
+ violations += 1;
137
+ else if (scoped[key] < total)
138
+ note(sideKeys.has(key) ? 'harness side calls' : 'other');
139
+ else if (unscoped[key] !== total) {
140
+ if (resumed[key] !== unscoped[key])
141
+ note('resume scope');
142
+ if (scoped[key] !== resumed[key])
143
+ note('subagent transcripts');
144
+ }
145
+ }
146
+ return { name: INVARIANTS[5], checked, violations, observed };
147
+ };
148
+ /** `delegated` holds the lines of the session's subagent transcripts, which its cost-state totals include. */
149
+ export const verifySkeleton = (skeleton, delegated = []) => [
150
+ verifyToolLinkage(skeleton.lines),
151
+ verifyParents(skeleton.lines),
152
+ verifyTimestamps(skeleton.lines),
153
+ verifyRequests(skeleton.lines),
154
+ verifyThinking(skeleton.lines),
155
+ verifyCost(skeleton.lines, delegated, skeleton.session),
156
+ ];
157
+ /** A subagent transcript is stored as `<parent>~...~agent-<id>`; a main session's id holds no `~`. */
158
+ const mainSessionOf = (session) => session.split('~')[0] ?? session;
159
+ /** The subagent transcripts' lines, keyed by the main session they belong to. */
160
+ const delegatedLines = (skeletons) => {
161
+ const delegated = new Map();
162
+ for (const skeleton of skeletons) {
163
+ const parent = mainSessionOf(skeleton.session);
164
+ if (parent !== skeleton.session)
165
+ delegated.set(parent, (delegated.get(parent) ?? []).concat(skeleton.lines));
166
+ }
167
+ return delegated;
168
+ };
169
+ /** Sums each invariant over many skeletons, each main session's cost-state checked with its subagent transcripts' lines. */
170
+ export const verifyAll = (skeletons, delegatedFrom = skeletons) => {
171
+ const totals = INVARIANTS.map((name) => ({ name, checked: 0, violations: 0 }));
172
+ // `--session` filters `skeletons` but row 6 still needs the subagent transcripts' lines, so they come from `delegatedFrom`.
173
+ const delegated = delegatedLines(delegatedFrom);
174
+ for (const skeleton of skeletons) {
175
+ for (const [index, result] of verifySkeleton(skeleton, delegated.get(skeleton.session)).entries()) {
176
+ const total = totals[index];
177
+ if (total !== undefined) {
178
+ total.checked += result.checked;
179
+ total.violations += result.violations;
180
+ if (result.observed !== undefined)
181
+ total.observed = mergeClasses(total.observed ?? emptyClasses(), result.observed);
182
+ }
183
+ }
184
+ }
185
+ return totals;
186
+ };
187
+ const mergeClasses = (into, from) => into.map((entry, index) => {
188
+ const other = from[index];
189
+ return other === undefined ? entry : { ...entry, checks: entry.checks + other.checks, example: entry.example ?? other.example };
190
+ });
191
+ const classLine = (label, classes) => {
192
+ const shown = classes.filter((entry) => entry.checks > 0);
193
+ if (shown.length === 0)
194
+ return [];
195
+ const parts = shown.map((entry) => `${String(entry.checks)} ${entry.cls}${entry.example === null ? '' : ` (e.g. ${entry.example})`}`);
196
+ return [` ${label}: ${parts.join(', ')}`];
197
+ };
198
+ const formatResult = (result) => {
199
+ const classes = result.observed ?? [];
200
+ const above = classes.filter((entry) => !entry.holds);
201
+ const observed = above.reduce((sum, entry) => sum + entry.checks, 0);
202
+ const holding = result.checked - result.violations - observed;
203
+ return [
204
+ `${result.violations === 0 ? 'PASS' : 'FAIL'} ${result.name}: ${String(holding)} of ${String(result.checked)} hold${result.violations === 0 ? '' : `, ${String(result.violations)} exceptions`}${observed === 0 ? '' : `, ${String(observed)} observed`}`,
205
+ ...classLine('equal only within scope', classes.filter((entry) => entry.holds)),
206
+ ...classLine('total above the recorded sum, as observed', above),
207
+ ];
208
+ };
209
+ export const formatVerify = (results, sessions) => [`${String(sessions)} skeletons checked`, ...results.flatMap(formatResult)].join('\n');
210
+ export const CAPTURE_INVARIANTS = [
211
+ 'every session is captured at the current capture version (none is cv-behind)',
212
+ 'sessions captured at the current version share one sid',
213
+ 'every fileHash on a session is a path target of one of its tool calls',
214
+ 'a path target names its base; no other target does',
215
+ ];
216
+ const HASH = /^[0-9a-f]{16}$/;
217
+ /**
218
+ * The CAPTURE 4 fields, checked over the store's capture records: cv-behind sessions (a session captured at an older readable
219
+ * version, not yet captured again, unless `frozen` names it: its transcript is gone from the harness and `raw/` (or, for a Cursor
220
+ * session, its composer is gone from Cursor), so it is counted apart, never a violation), a mixed `sid` (two salts in one store, so
221
+ * path joins hold only within each), `fileHashes` that no tool call explains, and `targetBase` set on, or missing from, the wrong
222
+ * kind of target. Skeleton invariants stay in {@link verifyAll}.
223
+ */
224
+ export const verifyCaptured = (sessions, tools, frozen = new Set()) => {
225
+ const current = sessions.filter((session) => session.cv === CAPTURE_VERSION);
226
+ const readable = sessions.filter((session) => Number(session.cv) >= OLDEST_READABLE_CAPTURE);
227
+ const counts = new Map();
228
+ for (const session of current)
229
+ if (session.sid !== null)
230
+ counts.set(session.sid, (counts.get(session.sid) ?? 0) + 1);
231
+ const majority = [...counts.entries()].sort((a, b) => b[1] - a[1])[0]?.[0];
232
+ const targets = new Map();
233
+ for (const tool of tools)
234
+ if (tool.targetBase !== null && tool.target !== null)
235
+ targets.set(tool.session, (targets.get(tool.session) ?? new Set()).add(tool.target));
236
+ const currentTools = tools.filter((tool) => tool.cv === CAPTURE_VERSION);
237
+ return [
238
+ { name: CAPTURE_INVARIANTS[0], checked: readable.length, violations: readable.filter((session) => Number(session.cv) < Number(CAPTURE_VERSION) && !frozen.has(session.session)).length },
239
+ { name: CAPTURE_INVARIANTS[1], checked: current.length, violations: current.filter((session) => session.sid !== majority).length },
240
+ {
241
+ name: CAPTURE_INVARIANTS[2],
242
+ checked: current.length,
243
+ violations: current.filter((session) => session.fileHashes.some((hash) => targets.get(session.session)?.has(hash) !== true)).length,
244
+ },
245
+ {
246
+ name: CAPTURE_INVARIANTS[3],
247
+ checked: currentTools.length,
248
+ violations: currentTools.filter((tool) => (tool.targetBase !== null) !== (tool.target !== null && HASH.test(tool.target))).length,
249
+ },
250
+ ];
251
+ };
252
+ const statusOf = (result) => (result.violations > 0 ? 'fail' : result.checked === 0 ? 'not run' : 'pass');
253
+ /**
254
+ * A result for every session with a skeleton or a capture record: {@link verifySkeleton} with the session's subagent transcripts' lines as `delegated`,
255
+ * then {@link verifyCaptured} over the session's own capture records. A session with no skeleton gets rows 1 to 6 at `checked: 0` (`not run`) and its real
256
+ * rows 7 to 10. Row 8 (one `sid`) is read within the one session, so it cannot fail for a session with a non-null `sid`; a current-version session with a
257
+ * null `sid` fails it. The store-wide row stays in `verifyCaptured` over all sessions.
258
+ */
259
+ export const verifySessions = (skeletons, sessions, tools, frozen = new Set()) => {
260
+ const delegated = delegatedLines(skeletons);
261
+ const byId = (records) => {
262
+ const grouped = new Map();
263
+ for (const record of records)
264
+ grouped.set(record.session, (grouped.get(record.session) ?? []).concat(record));
265
+ return grouped;
266
+ };
267
+ const skeletonOf = new Map(skeletons.map((skeleton) => [skeleton.session, skeleton]));
268
+ const ownSessions = byId(sessions);
269
+ const ownTools = byId(tools);
270
+ const ids = [...new Set([...skeletons.map((skeleton) => skeleton.session), ...sessions.map((session) => session.session)])];
271
+ return ids.map((id) => {
272
+ const skeleton = skeletonOf.get(id);
273
+ const skeletonRows = skeleton === undefined ? INVARIANTS.map((name) => ({ name, checked: 0, violations: 0 })) : verifySkeleton(skeleton, delegated.get(id));
274
+ const results = [...skeletonRows, ...verifyCaptured(ownSessions.get(id) ?? [], ownTools.get(id) ?? [], frozen)];
275
+ return { session: id, invariants: results.map((result) => ({ ...result, status: statusOf(result) })) };
276
+ });
277
+ };
278
+ /** Per-session lines the default text prints; the JSON lists them all. */
279
+ export const SESSION_LINE_CAP = 20;
280
+ /** One line per session with a failing row, naming the rows, capped at {@link SESSION_LINE_CAP} with a count of the rest; empty when none fails. Ids and row names only. */
281
+ export const formatSessionResults = (results) => {
282
+ const lines = results
283
+ .filter((entry) => entry.invariants.some((row) => row.status === 'fail'))
284
+ .map((entry) => ` ${entry.session}: FAIL ${entry.invariants.filter((row) => row.status === 'fail').map((row) => row.name).join('; ')}`);
285
+ return [...lines.slice(0, SESSION_LINE_CAP), ...(lines.length > SESSION_LINE_CAP ? [` and ${String(lines.length - SESSION_LINE_CAP)} more; --json lists all`] : [])].join('\n');
286
+ };
@@ -0,0 +1,26 @@
1
+ /** One package ecosystem: the seeded names with their published versions, and the names that resolve but sit outside `seed_top`. */
2
+ export interface PackageSnapshot {
3
+ ecosystem: string;
4
+ seedTop: number;
5
+ seed: ReadonlyMap<string, ReadonlySet<string>>;
6
+ resolved: ReadonlySet<string>;
7
+ }
8
+ export interface ProgramEntry {
9
+ subs: ReadonlySet<string>;
10
+ flags: ReadonlySet<string>;
11
+ }
12
+ export interface Vocabulary {
13
+ version: string;
14
+ snapshotDate: string;
15
+ packages: ReadonlyMap<string, PackageSnapshot>;
16
+ programs: ReadonlyMap<string, ProgramEntry>;
17
+ models: ReadonlySet<string>;
18
+ harnessVersions: ReadonlyMap<string, ReadonlySet<string>>;
19
+ }
20
+ /** packages/cli/vocab/, from src/sessions/ in a test and from dist/sessions/ once built: the same depth. */
21
+ export declare const VOCAB_DIR: import("url").URL;
22
+ export declare const PACKAGE_FILES: readonly ["npm.json", "pypi.json", "crates.json", "rubygems.json", "homebrew.json", "go.json", "maven.json"];
23
+ /** Reads and checks every snapshot under `dir` (default: the shipped folder). */
24
+ export declare const readVocabulary: (dir?: URL) => Vocabulary;
25
+ /** The shipped vocabulary, read once on first use: reading 6 MB of JSON is not free, and most commands never project. */
26
+ export declare const vocabulary: () => Vocabulary;
@@ -0,0 +1,61 @@
1
+ // Loads the pinned vocabulary snapshots shipped in packages/cli/vocab/ (pool vocabulary spec Section 1.3). The only file here that reads the disk:
2
+ // public-vocabulary.ts stays free of the filesystem so the pool's Worker can bundle it, and projection.ts takes the loaded `Vocabulary` as an argument.
3
+ // A snapshot that does not parse is an error and never an empty list: an empty list would quietly project every name as `private`.
4
+ import { readFileSync } from 'node:fs';
5
+ import { VOCAB_SNAPSHOT_DATE, VOCAB_VERSION } from './public-vocabulary.js';
6
+ /** packages/cli/vocab/, from src/sessions/ in a test and from dist/sessions/ once built: the same depth. */
7
+ export const VOCAB_DIR = new URL('../../vocab/', import.meta.url);
8
+ const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
9
+ const recordAt = (value, where) => {
10
+ if (!isRecord(value))
11
+ throw new Error(`vocabulary ${where}: expected an object`);
12
+ return value;
13
+ };
14
+ const stringsAt = (value, where) => {
15
+ if (!Array.isArray(value) || value.some((item) => typeof item !== 'string'))
16
+ throw new Error(`vocabulary ${where}: expected a list of strings`);
17
+ return value.filter((item) => typeof item === 'string');
18
+ };
19
+ const readJson = (dir, file) => recordAt(JSON.parse(readFileSync(new URL(file, dir), 'utf8')), file);
20
+ const packageSnapshot = (dir, file) => {
21
+ const json = readJson(dir, file);
22
+ const seed = json['seed'];
23
+ if (!Array.isArray(seed))
24
+ throw new Error(`vocabulary ${file}: seed must be a list`);
25
+ const entries = seed.map((pair) => {
26
+ if (!Array.isArray(pair) || typeof pair[0] !== 'string')
27
+ throw new Error(`vocabulary ${file}: a seed entry is [name, versions]`);
28
+ return [pair[0], new Set(stringsAt(pair[1], `${file} versions`))];
29
+ });
30
+ const ecosystem = json['ecosystem'];
31
+ const seedTop = json['seedTop'];
32
+ if (typeof ecosystem !== 'string' || typeof seedTop !== 'number')
33
+ throw new Error(`vocabulary ${file}: ecosystem and seedTop are required`);
34
+ return { ecosystem, seedTop, seed: new Map(entries), resolved: new Set(stringsAt(json['resolved'], `${file} resolved`)) };
35
+ };
36
+ export const PACKAGE_FILES = ['npm.json', 'pypi.json', 'crates.json', 'rubygems.json', 'homebrew.json', 'go.json', 'maven.json'];
37
+ /** Reads and checks every snapshot under `dir` (default: the shipped folder). */
38
+ export const readVocabulary = (dir = VOCAB_DIR) => {
39
+ const manifest = readJson(dir, 'manifest.json');
40
+ const snapshotDate = manifest['snapshotDate'];
41
+ if (snapshotDate !== VOCAB_SNAPSHOT_DATE) {
42
+ throw new Error(`vocabulary snapshots are dated ${String(snapshotDate)} but VOCAB_VERSION ${VOCAB_VERSION} pins ${VOCAB_SNAPSHOT_DATE}: bump both together`);
43
+ }
44
+ const packages = new Map(PACKAGE_FILES.map((file) => packageSnapshot(dir, file)).map((snapshot) => [snapshot.ecosystem, snapshot]));
45
+ const programs = new Map(Object.entries(recordAt(readJson(dir, 'programs.json')['programs'], 'programs')).map(([name, entry]) => {
46
+ const held = recordAt(entry, `programs.${name}`);
47
+ return [name, { subs: new Set(stringsAt(held['subs'], `programs.${name}.subs`)), flags: new Set(stringsAt(held['flags'], `programs.${name}.flags`)) }];
48
+ }));
49
+ const harnesses = recordAt(readJson(dir, 'harness.json')['harnesses'], 'harnesses');
50
+ return {
51
+ version: VOCAB_VERSION,
52
+ snapshotDate,
53
+ packages,
54
+ programs,
55
+ models: new Set(stringsAt(readJson(dir, 'models.json')['ids'], 'models.ids')),
56
+ harnessVersions: new Map(Object.entries(harnesses).map(([harness, versions]) => [harness, new Set(stringsAt(versions, `harness.${harness}`))])),
57
+ };
58
+ };
59
+ let loaded;
60
+ /** The shipped vocabulary, read once on first use: reading 6 MB of JSON is not free, and most commands never project. */
61
+ export const vocabulary = () => (loaded ??= readVocabulary());
@@ -0,0 +1,38 @@
1
+ /** The folder that holds one folder per VS Code build; a test passes its own so it never reads the real one. */
2
+ export declare const vscodeAppData: (platform: NodeJS.Platform, env: {
3
+ [name: string]: string | undefined;
4
+ }, home?: string) => string;
5
+ type Obj = {
6
+ [key: string]: unknown;
7
+ };
8
+ /** A session found in the store, before it is read. */
9
+ export interface VscodeSession {
10
+ /** The file's name without its extension, which is the session id (chatSessionStore.ts:738-741) and names the skeleton. */
11
+ id: string;
12
+ /** The workspace's folder; null for an empty window or a remote workspace. */
13
+ folder: string | null;
14
+ /** The files that hold it, newest first (the `.jsonl` ahead of the `.json` on a tie); the first that reads is the session. */
15
+ files: string[];
16
+ size: number;
17
+ mtimeMs: number;
18
+ }
19
+ export interface VscodeStore {
20
+ sessions: VscodeSession[];
21
+ /** The session as transcript text for `captureSession`; throws when no file of it can be read or holds no parseable entry. */
22
+ transcript: (session: VscodeSession) => Promise<string>;
23
+ /** Files skipped because their name is not a safe file name (it names a skeleton), counted so the skip is never silent. */
24
+ rejected: number;
25
+ /** Folders and files that could not be inspected, reported once each and never fatal; the reason names no path. */
26
+ unreadable: {
27
+ file: string;
28
+ reason: string;
29
+ }[];
30
+ }
31
+ /** The non-blank log lines that are not JSON (a torn write), kept so `captureSession` counts them and the deposit line reports them. */
32
+ export declare const chatBadLines: (text: string) => string[];
33
+ /** The chat data a file holds: the `.json` form is the object itself, the `.jsonl` form is replayed. Throws when nothing parses. */
34
+ export declare const chatData: (text: string, jsonl: boolean) => Obj;
35
+ /** One chat session as the lines `captureSession` reads. */
36
+ export declare const chatLines: (data: Obj, fallbackId: string, folder: string | null) => Obj[];
37
+ export declare const openVscodeCopilotStore: (root: string) => Promise<VscodeStore | undefined>;
38
+ export {};