@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,351 @@
1
+ // The canonical action of a tool call (recall spec Section 2.2). Every harness names its tools differently, so one table per adapter
2
+ // maps its native names onto one closed list, and a step reads alike in any harness. A new adapter adds a table here and changes nothing in the
3
+ // extractor. A native name no table lists becomes `other`, and its name stays out of class P (`LITERAL`): the tool record keeps it, class A.
4
+ import { LITERAL } from './public-vocabulary.js';
5
+ export const CANONICAL_ACTIONS = [
6
+ 'file.read',
7
+ 'file.edit',
8
+ 'file.write',
9
+ 'file.search',
10
+ 'shell.exec',
11
+ 'web.search',
12
+ 'web.fetch',
13
+ 'mcp.call',
14
+ 'skill.run',
15
+ 'agent.spawn',
16
+ 'plan.update',
17
+ 'ask.user',
18
+ 'recall.pull',
19
+ 'recall.push',
20
+ 'other',
21
+ ];
22
+ export const ACTION_TABLES = {
23
+ 'claude-code': {
24
+ rows: {
25
+ Read: 'file.read',
26
+ Edit: 'file.edit',
27
+ MultiEdit: 'file.edit',
28
+ NotebookEdit: 'file.edit',
29
+ Write: 'file.write',
30
+ Grep: 'file.search',
31
+ Glob: 'file.search',
32
+ LSP: 'file.search',
33
+ Bash: 'shell.exec',
34
+ PowerShell: 'shell.exec',
35
+ WebSearch: 'web.search',
36
+ WebFetch: 'web.fetch',
37
+ Skill: 'skill.run',
38
+ Agent: 'agent.spawn',
39
+ Task: 'agent.spawn',
40
+ TodoWrite: 'plan.update',
41
+ TaskCreate: 'plan.update',
42
+ TaskUpdate: 'plan.update',
43
+ TaskList: 'plan.update',
44
+ TaskGet: 'plan.update',
45
+ EnterPlanMode: 'plan.update',
46
+ ExitPlanMode: 'plan.update',
47
+ AskUserQuestion: 'ask.user',
48
+ mcp__oars__oars_recall: 'recall.pull',
49
+ 'mcp__oars-sessions__oars_recall': 'recall.pull',
50
+ StructuredOutput: 'other',
51
+ ToolSearch: 'other',
52
+ SendMessage: 'other',
53
+ ListAgents: 'other',
54
+ Monitor: 'other',
55
+ TaskStop: 'other',
56
+ TaskOutput: 'other',
57
+ BashOutput: 'other',
58
+ KillShell: 'other',
59
+ ScheduleWakeup: 'other',
60
+ Workflow: 'other',
61
+ EnterWorktree: 'other',
62
+ ExitWorktree: 'other',
63
+ },
64
+ mcp: /^mcp__(?<server>.+?)__(?<tool>.+)$/,
65
+ toolDelimited: true,
66
+ shells: { Bash: 'bash', PowerShell: 'powershell' },
67
+ },
68
+ cursor: {
69
+ rows: {
70
+ read_file: 'file.read',
71
+ read_file_v2: 'file.read',
72
+ edit_file: 'file.edit',
73
+ edit_file_v2: 'file.edit',
74
+ search_replace: 'file.edit',
75
+ reapply: 'file.edit',
76
+ write: 'file.write',
77
+ grep_search: 'file.search',
78
+ grep: 'file.search',
79
+ codebase_search: 'file.search',
80
+ file_search: 'file.search',
81
+ glob_file_search: 'file.search',
82
+ list_dir: 'file.search',
83
+ list_dir_v2: 'file.search',
84
+ run_terminal_cmd: 'shell.exec',
85
+ web_search: 'web.search',
86
+ web_fetch: 'web.fetch',
87
+ mcp_oars_oars_recall: 'recall.pull',
88
+ todo_write: 'plan.update',
89
+ create_plan: 'plan.update',
90
+ ask_question: 'ask.user',
91
+ task_v2: 'agent.spawn',
92
+ delete_file: 'other',
93
+ read_lints: 'other',
94
+ },
95
+ mcp: /^mcp_(?<server>[^_]+)_(?<tool>.+)$/,
96
+ toolDelimited: false,
97
+ shells: {},
98
+ },
99
+ // Codex's own names: its adapter names a shell and a patch call in the Claude Code vocabulary for the extractor and keeps the Codex name in `native`.
100
+ // `write_stdin` feeds a running process (it runs no command), so it is `other`, as Claude Code's `BashOutput` is; `web_search` is the adapter's name for a
101
+ // persisted `web_search_call`.
102
+ codex: {
103
+ rows: {
104
+ shell: 'shell.exec',
105
+ shell_command: 'shell.exec',
106
+ exec_command: 'shell.exec',
107
+ local_shell: 'shell.exec',
108
+ 'container.exec': 'shell.exec',
109
+ write_stdin: 'other',
110
+ apply_patch: 'file.edit',
111
+ read_file: 'file.read',
112
+ web_search: 'web.search',
113
+ update_plan: 'plan.update',
114
+ },
115
+ mcp: /^mcp__(?<server>.+?)__(?<tool>.+)$/,
116
+ toolDelimited: true,
117
+ shells: {},
118
+ },
119
+ // Copilot CLI's names are not listed by its event schema; these are the ones the SDK's own hook docs use (docs/hooks/*.md). A shell call and an edit are
120
+ // also found by shape (`shellToolInfo`, `shellExecution`, `fileEdits`) or by name and arrive in the transcript as `Bash` and `Edit` with the original name
121
+ // in `native`, which is what this table looks up; so a name this table lacks still reads as a shell call or an edit in the tool log, but its step is `other`.
122
+ copilot: {
123
+ rows: {
124
+ shell: 'shell.exec',
125
+ bash: 'shell.exec',
126
+ powershell: 'shell.exec',
127
+ local_shell: 'shell.exec',
128
+ read_file: 'file.read',
129
+ view: 'file.read',
130
+ glob: 'file.search',
131
+ grep: 'file.search',
132
+ edit: 'file.edit',
133
+ str_replace_editor: 'file.edit',
134
+ apply_patch: 'file.edit',
135
+ write_file: 'file.write',
136
+ delete_file: 'other',
137
+ task: 'agent.spawn',
138
+ web_fetch: 'web.fetch',
139
+ },
140
+ mcp: /^mcp__(?<server>.+?)__(?<tool>.+)$/,
141
+ toolDelimited: true,
142
+ shells: { bash: 'bash', powershell: 'powershell' },
143
+ },
144
+ // VS Code Copilot Chat's tools (microsoft/vscode @ f92b8f3, extensions/copilot/src/extension/tools/common/toolNames.ts): a tool call's `toolId` is the bare name or the
145
+ // contributed `copilot_` one, so both spellings are rows. The adapter names a terminal call `Bash`, an edit `Edit`, and a file creation `Write`, and keeps the toolId in `native`.
146
+ 'vscode-copilot': {
147
+ rows: {
148
+ run_in_terminal: 'shell.exec',
149
+ create_and_run_task: 'shell.exec',
150
+ run_task: 'shell.exec',
151
+ read_file: 'file.read',
152
+ copilot_readFile: 'file.read',
153
+ list_dir: 'file.search',
154
+ copilot_listDirectory: 'file.search',
155
+ file_search: 'file.search',
156
+ copilot_findFiles: 'file.search',
157
+ grep_search: 'file.search',
158
+ copilot_findTextInFiles: 'file.search',
159
+ semantic_search: 'file.search',
160
+ copilot_searchCodebase: 'file.search',
161
+ search_workspace_symbols: 'file.search',
162
+ copilot_searchWorkspaceSymbols: 'file.search',
163
+ test_search: 'file.search',
164
+ copilot_findTestFiles: 'file.search',
165
+ apply_patch: 'file.edit',
166
+ copilot_applyPatch: 'file.edit',
167
+ replace_string_in_file: 'file.edit',
168
+ copilot_replaceString: 'file.edit',
169
+ multi_replace_string_in_file: 'file.edit',
170
+ copilot_multiReplaceString: 'file.edit',
171
+ insert_edit_into_file: 'file.edit',
172
+ copilot_insertEdit: 'file.edit',
173
+ create_file: 'file.write',
174
+ copilot_createFile: 'file.write',
175
+ runSubagent: 'agent.spawn',
176
+ search_subagent: 'agent.spawn',
177
+ explore_subagent: 'agent.spawn',
178
+ execution_subagent: 'agent.spawn',
179
+ fetch_webpage: 'web.fetch',
180
+ copilot_fetchWebPage: 'web.fetch',
181
+ manage_todo_list: 'plan.update',
182
+ // The rest of toolNames.ts, so no public name reads as an unlisted one with its name withheld. Notebook edits are edits, a directory or a new notebook is a write,
183
+ // the tools that drive a terminal or a kernel feed a running process and run no command of their own (as `write_stdin` is `other`), and a skill is a skill.
184
+ edit_notebook_file: 'file.edit',
185
+ copilot_editNotebook: 'file.edit',
186
+ create_new_jupyter_notebook: 'file.write',
187
+ copilot_createNewJupyterNotebook: 'file.write',
188
+ create_directory: 'file.write',
189
+ copilot_createDirectory: 'file.write',
190
+ read_notebook_cell_output: 'file.read',
191
+ copilot_readNotebookCellOutput: 'file.read',
192
+ copilot_getNotebookSummary: 'file.read',
193
+ view_image: 'file.read',
194
+ copilot_viewImage: 'file.read',
195
+ read_project_structure: 'file.search',
196
+ copilot_readProjectStructure: 'file.search',
197
+ skill: 'skill.run',
198
+ send_to_terminal: 'other',
199
+ get_terminal_output: 'other',
200
+ kill_terminal: 'other',
201
+ terminal_selection: 'other',
202
+ terminal_last_command: 'other',
203
+ get_task_output: 'other',
204
+ run_notebook_cell: 'other',
205
+ copilot_runNotebookCell: 'other',
206
+ runTests: 'other',
207
+ testFailure: 'other',
208
+ edit_files: 'other',
209
+ copilot_editFiles: 'other',
210
+ get_vscode_api: 'other',
211
+ copilot_getVSCodeAPI: 'other',
212
+ get_errors: 'other',
213
+ copilot_getErrors: 'other',
214
+ get_changed_files: 'other',
215
+ copilot_getChangedFiles: 'other',
216
+ memory: 'other',
217
+ copilot_memory: 'other',
218
+ resolve_memory_file_uri: 'other',
219
+ copilot_resolveMemoryFileUri: 'other',
220
+ github_repo: 'other',
221
+ copilot_githubRepo: 'other',
222
+ github_text_search: 'other',
223
+ copilot_githubTextSearch: 'other',
224
+ session_store_sql: 'other',
225
+ copilot_sessionStoreSql: 'other',
226
+ switch_agent: 'other',
227
+ copilot_switchAgent: 'other',
228
+ tool_search: 'other',
229
+ vscode_get_confirmation: 'other',
230
+ vscode_get_confirmation_with_options: 'other',
231
+ vscode_reviewPlan: 'other',
232
+ vscode_get_terminal_confirmation: 'other',
233
+ vscode_askQuestions: 'ask.user',
234
+ open_browser_page: 'other',
235
+ click_element: 'other',
236
+ screenshot_page: 'other',
237
+ navigate_page: 'other',
238
+ read_page: 'other',
239
+ hover_element: 'other',
240
+ drag_element: 'other',
241
+ type_in_page: 'other',
242
+ handle_dialog: 'other',
243
+ run_playwright_code: 'other',
244
+ },
245
+ // No pattern: VS Code's spelling of an MCP tool id has not been seen in a real file, so an MCP call reads as `other` with its name withheld until the hand run shows it.
246
+ mcp: null,
247
+ toolDelimited: false,
248
+ shells: {},
249
+ },
250
+ // Gemini CLI's tool names (google-gemini/gemini-cli @ 89e9d25d, packages/core/src/tools/tool-names.ts). Its adapter names the shell, edit, read, and search
251
+ // calls in the Claude Code vocabulary for the extractor and keeps the Gemini name in `native`, which is what this table looks up. An MCP tool is
252
+ // `mcp_<server>_<tool>`, a spelling that does not mark where the server name ends, so its tool stays out of class P.
253
+ gemini: {
254
+ rows: {
255
+ read_file: 'file.read',
256
+ read_many_files: 'file.read',
257
+ glob: 'file.search',
258
+ grep_search: 'file.search',
259
+ search_file_content: 'file.search',
260
+ list_directory: 'file.search',
261
+ replace: 'file.edit',
262
+ write_file: 'file.write',
263
+ run_shell_command: 'shell.exec',
264
+ PowerShell: 'shell.exec',
265
+ google_web_search: 'web.search',
266
+ web_fetch: 'web.fetch',
267
+ activate_skill: 'skill.run',
268
+ invoke_agent: 'agent.spawn',
269
+ write_todos: 'plan.update',
270
+ enter_plan_mode: 'plan.update',
271
+ exit_plan_mode: 'plan.update',
272
+ tracker_create_task: 'plan.update',
273
+ tracker_update_task: 'plan.update',
274
+ tracker_get_task: 'plan.update',
275
+ tracker_list_tasks: 'plan.update',
276
+ tracker_add_dependency: 'plan.update',
277
+ tracker_visualize: 'plan.update',
278
+ ask_user: 'ask.user',
279
+ mcp_oars_oars_recall: 'recall.pull',
280
+ 'mcp_oars-sessions_oars_recall': 'recall.pull',
281
+ get_internal_docs: 'other',
282
+ update_topic: 'other',
283
+ complete_task: 'other',
284
+ read_mcp_resource: 'other',
285
+ list_mcp_resources: 'other',
286
+ },
287
+ mcp: /^mcp_(?<server>[^_]+)_(?<tool>.+)$/,
288
+ toolDelimited: false,
289
+ shells: { run_shell_command: 'bash', PowerShell: 'powershell' },
290
+ },
291
+ // Grok Build's own names (xai-org/grok-build @ 2bdd1d6, crates/codegen/xai-grok-tools): a tool's `title` is its name. A shell call and an edit reach the
292
+ // transcript as `Bash` and `Edit` with the original name in `native`, which is what this table looks up. A name the table lacks keeps its own and its step
293
+ // is `other`. An MCP call is `<server>__<tool>` in Grok Build; the reader spells it `mcp__<server>__<tool>`.
294
+ grok: {
295
+ rows: {
296
+ run_terminal_cmd: 'shell.exec',
297
+ run_terminal_command: 'shell.exec',
298
+ bash: 'shell.exec',
299
+ search_replace: 'file.edit',
300
+ apply_patch: 'file.edit',
301
+ edit: 'file.edit',
302
+ hashline_edit: 'file.edit',
303
+ write: 'file.write',
304
+ read_file: 'file.read',
305
+ read: 'file.read',
306
+ hashline_read: 'file.read',
307
+ list_dir: 'file.search',
308
+ grep: 'file.search',
309
+ grep_files: 'file.search',
310
+ glob: 'file.search',
311
+ hashline_grep: 'file.search',
312
+ web_search: 'web.search',
313
+ // The backend-hosted X search, named from its `variant` by the grok reader (acp_session_impl/tool_dispatch.rs:94-105).
314
+ x_search: 'web.search',
315
+ web_fetch: 'web.fetch',
316
+ ask_user_question: 'ask.user',
317
+ enter_plan_mode: 'plan.update',
318
+ exit_plan_mode: 'plan.update',
319
+ task: 'agent.spawn',
320
+ // The spawn tool also arrives under these wire names (grok_build/task/mod.rs:193-196).
321
+ Task: 'agent.spawn',
322
+ spawn_subagent: 'agent.spawn',
323
+ todo_write: 'plan.update',
324
+ todowrite: 'plan.update',
325
+ skill: 'skill.run',
326
+ },
327
+ mcp: /^mcp__(?<server>.+?)__(?<tool>.+)$/,
328
+ toolDelimited: true,
329
+ shells: { bash: 'bash', run_terminal_cmd: 'bash', run_terminal_command: 'bash' },
330
+ },
331
+ };
332
+ /** The MCP call a native name spells, in full, as the transcript wrote it; undefined for any other name. */
333
+ export const mcpOf = (table, native) => {
334
+ const groups = table.mcp?.exec(native)?.groups;
335
+ const server = groups?.server;
336
+ const tool = groups?.tool;
337
+ return server === undefined || tool === undefined ? undefined : { server, tool };
338
+ };
339
+ /**
340
+ * What a native name becomes on a step. Rows win over the MCP pattern, so a harness can name a recall that travels over MCP. `native` is the
341
+ * name itself only when a row lists it; an MCP name holds a server name, so `callIdentity` decides whether it is shown.
342
+ */
343
+ export const actionOf = (table, native) => {
344
+ // `hasOwn`: a tool named `constructor` must not read the object's prototype.
345
+ const listed = Object.hasOwn(table.rows, native) ? table.rows[native] : undefined;
346
+ if (listed !== undefined)
347
+ return { action: listed, native };
348
+ return { action: mcpOf(table, native) === undefined ? 'other' : 'mcp.call', native: LITERAL };
349
+ };
350
+ /** The shell a native tool runs, or null when the table does not say. */
351
+ export const shellOf = (table, native) => (Object.hasOwn(table.shells, native) ? (table.shells[native] ?? null) : null);
@@ -0,0 +1,14 @@
1
+ /** A leading `~` is the home folder, expanded here because a quoted entry or one read from a list file reaches us unexpanded by any shell. */
2
+ export declare const expandHome: (entry: string) => string;
3
+ /** Claude Code names a project folder after its working directory with every non-alphanumeric character as `-`. */
4
+ export declare const slugOf: (folder: string) => string;
5
+ export interface Allow {
6
+ /** Whether a project folder name is on the allowlist. */
7
+ allows: (name: string) => boolean;
8
+ /** The entries that matched none of `names`. */
9
+ unmatched: (names: readonly string[]) => string[];
10
+ }
11
+ export declare const compileAllow: (entries: readonly string[]) => Allow;
12
+ /** One entry per line; blank lines and lines starting with `#` are skipped. */
13
+ export declare const parseAllowList: (text: string) => string[];
14
+ export declare const requireEntries: (entries: readonly string[]) => void;
@@ -0,0 +1,51 @@
1
+ // What `--allow` accepts. An entry names project folders by one of:
2
+ // a folder or slug `~/work/app` or `-home-me-work-app`: that one project folder
3
+ // a folder prefix `~/work/` (trailing slash): that folder and everything under it
4
+ // a glob `*-work-*`, `C:\work\app-*`: `*` any run of characters, `?` one character
5
+ // A path is matched through its slug, because Claude Code names a project folder after its working directory with every
6
+ // non-alphanumeric character as `-`.
7
+ import { homedir } from 'node:os';
8
+ import { resolve } from 'node:path';
9
+ import { CliError, ExitCode } from '../errors.js';
10
+ /** A leading `~` is the home folder, expanded here because a quoted entry or one read from a list file reaches us unexpanded by any shell. */
11
+ export const expandHome = (entry) => (/^~(?:[\\/]|$)/.test(entry) ? homedir() + entry.slice(1) : entry);
12
+ /** Claude Code names a project folder after its working directory with every non-alphanumeric character as `-`. */
13
+ export const slugOf = (folder) => resolve(expandHome(folder)).replace(/[^A-Za-z0-9]/g, '-');
14
+ const GLOB = /[*?]/;
15
+ /** A glob over a path or slug, as an anchored expression over slugs: separators become `-`, wildcards stay wildcards. */
16
+ const globPattern = (entry) => {
17
+ // Every character that is neither alphanumeric nor a wildcard becomes the slug's `-`; then the wildcards become expressions.
18
+ const body = entry.replace(/[^A-Za-z0-9*?]/g, '-').replace(/\*/g, '.*').replace(/\?/g, '.');
19
+ return new RegExp(`^${body}$`);
20
+ };
21
+ /** The test a project folder name must pass for one entry. */
22
+ const matcherOf = (entry) => {
23
+ if (GLOB.test(entry)) {
24
+ const pattern = globPattern(entry);
25
+ return (name) => pattern.test(name);
26
+ }
27
+ if (/[\\/]$/.test(entry)) {
28
+ const prefix = slugOf(entry);
29
+ // `resolve` drops the trailing separator, so the prefix is the folder itself; a project under it extends the slug with `-`.
30
+ return (name) => name === prefix || name.startsWith(`${prefix}-`);
31
+ }
32
+ const slug = slugOf(entry);
33
+ return (name) => name === entry || name === slug;
34
+ };
35
+ export const compileAllow = (entries) => {
36
+ const compiled = entries.map((entry) => ({ entry, matches: matcherOf(expandHome(entry)) }));
37
+ return {
38
+ allows: (name) => compiled.some((item) => item.matches(name)),
39
+ unmatched: (names) => compiled.filter((item) => !names.some((name) => item.matches(name))).map((item) => item.entry),
40
+ };
41
+ };
42
+ /** One entry per line; blank lines and lines starting with `#` are skipped. */
43
+ export const parseAllowList = (text) => text
44
+ .split(/\r?\n/)
45
+ .map((line) => line.trim())
46
+ .filter((line) => line !== '' && !line.startsWith('#'));
47
+ export const requireEntries = (entries) => {
48
+ if (entries.length === 0) {
49
+ throw new CliError('Nothing to read: pass at least one --allow <folder> or --allow-file <list>. Nothing is read from a folder you have not named.', ExitCode.USAGE, 'usage');
50
+ }
51
+ };
@@ -0,0 +1,11 @@
1
+ export type Archived = 'linked' | 'copied' | 'present';
2
+ /** The file operations, injectable so a test can force the cross-volume fallback. */
3
+ export interface ArchiveOps {
4
+ link: (source: string, target: string) => Promise<void>;
5
+ copyFile: (source: string, target: string) => Promise<void>;
6
+ }
7
+ /**
8
+ * Archives `source` at `target`, idempotently. A target that is the same file (a hard link) or a copy at least as new and the same size
9
+ * is left alone; one the harness has since rewritten or grown past is replaced.
10
+ */
11
+ export declare const archiveRaw: (source: string, target: string, ops?: ArchiveOps) => Promise<Archived>;
@@ -0,0 +1,38 @@
1
+ // oars' own raw archive (Matt 2026-11-01, retention point 4c): every deposited transcript is hard-linked under `<store>/raw/`, so the
2
+ // raw behind the log outlives the harness's cleanup without a second copy on disk while both exist. Across volumes, where a hard link
3
+ // cannot reach, it is copied. Local only: nothing here is ever shared.
4
+ import { copyFile, link, mkdir, rm, stat } from 'node:fs/promises';
5
+ import { dirname } from 'node:path';
6
+ const NODE_OPS = { link, copyFile };
7
+ const isCode = (error, ...codes) => error instanceof Error && 'code' in error && typeof error.code === 'string' && codes.includes(error.code);
8
+ // EXDEV: another volume. EPERM, ENOTSUP: a file system without hard links. EMLINK: the source's link count is at its limit.
9
+ const CANNOT_LINK = ['EXDEV', 'EPERM', 'ENOTSUP', 'EMLINK'];
10
+ /**
11
+ * Archives `source` at `target`, idempotently. A target that is the same file (a hard link) or a copy at least as new and the same size
12
+ * is left alone; one the harness has since rewritten or grown past is replaced.
13
+ */
14
+ export const archiveRaw = async (source, target, ops = NODE_OPS) => {
15
+ const from = await stat(source, { bigint: true });
16
+ const to = await stat(target, { bigint: true }).catch((error) => {
17
+ if (isCode(error, 'ENOENT'))
18
+ return undefined;
19
+ throw error;
20
+ });
21
+ if (to !== undefined) {
22
+ const sameFile = to.ino === from.ino && to.dev === from.dev;
23
+ if (sameFile || (to.size === from.size && to.mtimeMs >= from.mtimeMs))
24
+ return 'present';
25
+ await rm(target, { force: true });
26
+ }
27
+ await mkdir(dirname(target), { recursive: true });
28
+ try {
29
+ await ops.link(source, target);
30
+ return 'linked';
31
+ }
32
+ catch (error) {
33
+ if (!isCode(error, ...CANNOT_LINK))
34
+ throw error;
35
+ }
36
+ await ops.copyFile(source, target);
37
+ return 'copied';
38
+ };
@@ -0,0 +1,42 @@
1
+ import type { SessionObservation, Skeleton } from './schema.js';
2
+ import { type SessionsRuntime, type State } from './store.js';
3
+ /** The T0 result table of spec 2.4, one name per row. */
4
+ export declare const T0_RESULTS: readonly ["root matches and the counts bind", "changed since deposit", "root mismatch", "counts do not bind", "not run"];
5
+ export type T0Result = (typeof T0_RESULTS)[number];
6
+ export interface SessionAudit {
7
+ session: string;
8
+ result: T0Result;
9
+ /** `none` means `W` is 0 until a new capture writes a new root; every other T0 result holds the tier at `skeleton`. */
10
+ tier: 'skeleton' | 'none';
11
+ /** How `raw/` holds the file: a hard link to the harness's transcript, or a copy (cross-volume, or a link whose source is gone). Null when there is no raw file or the counts failed first. */
12
+ mode: 'link' | 'copy' | null;
13
+ leaves: number;
14
+ /** Under {@link MIN_ROOT_LEAVES}: the root joins no cluster and the session counts toward no `n` (spec 2.5). */
15
+ belowMinLeaves: boolean;
16
+ changedSinceDeposit: boolean;
17
+ /** A copy whose size differs from `state.json`'s: a copy changes only when `archiveRaw` refreshes it, so growth is not an honest explanation (spec 2.4). */
18
+ possibleTampering: boolean;
19
+ bindings: {
20
+ leaves: number;
21
+ sessionLines: number;
22
+ skeletonLines: number;
23
+ badLines: number;
24
+ bind: boolean;
25
+ };
26
+ }
27
+ /**
28
+ * One session's T0 result. The count bindings come first and need no raw file: a record set that disagrees with itself is `none` whatever `raw/`
29
+ * holds. `state` is `state.json`, read once by {@link auditStore}.
30
+ */
31
+ export declare const auditSession: (runtime: SessionsRuntime, session: SessionObservation, skeleton: Skeleton | undefined, state?: State) => Promise<SessionAudit>;
32
+ /**
33
+ * Every session's T0 result, in the order given, then one `not run` for each skeleton with no capture record (a torn or removed observations line), so
34
+ * the table never shrinks without a signal. A session with no skeleton, or whose raw file cannot be read, is also `not run`.
35
+ */
36
+ export declare const auditStore: (runtime: SessionsRuntime, sessions: readonly SessionObservation[], skeletons: readonly Skeleton[]) => Promise<SessionAudit[]>;
37
+ export type T0Counts = {
38
+ [result in T0Result]: number;
39
+ };
40
+ export declare const auditCounts: (audits: readonly SessionAudit[]) => T0Counts;
41
+ /** A count line for every row of the T0 table, then one FAIL line per session at tier `none` and one note per session that changed. */
42
+ export declare const formatAudit: (audits: readonly SessionAudit[]) => string;
@@ -0,0 +1,104 @@
1
+ // Layer 4, T0, local (spec 2.4): does the raw file still match the root capture wrote, and do the capture records agree with each other?
2
+ // Local only: a local match commits nobody, so no result here raises a tier. It reads `raw/` and `state.json`, and prints ids and counts, never content.
3
+ import { readFile, stat } from 'node:fs/promises';
4
+ import { join } from 'node:path';
5
+ import { merkleRootOf, MIN_ROOT_LEAVES, rawLinesOf } from './merkle.js';
6
+ import { paths, readState, relativeOfSession } from './store.js';
7
+ /** The T0 result table of spec 2.4, one name per row. */
8
+ export const T0_RESULTS = ['root matches and the counts bind', 'changed since deposit', 'root mismatch', 'counts do not bind', 'not run'];
9
+ const statOrUndefined = (file) => stat(file, { bigint: true }).catch((error) => {
10
+ if (error instanceof Error && 'code' in error && error.code === 'ENOENT')
11
+ return undefined;
12
+ throw error;
13
+ });
14
+ /** The count bindings of spec 2.4: the skeleton's leaves, the session's lines, and the skeleton's lines plus the session's bad lines must all agree. No skeleton is zero leaves and zero lines. */
15
+ const bindingsOf = (session, skeleton) => {
16
+ const leaves = skeleton?.leaves ?? 0;
17
+ const skeletonLines = skeleton?.lines.length ?? 0;
18
+ return { leaves, sessionLines: session.lines, skeletonLines, badLines: session.badLines, bind: leaves === session.lines && session.lines === skeletonLines + session.badLines };
19
+ };
20
+ const unrun = (session, skeleton, bindings) => ({
21
+ session,
22
+ result: 'not run',
23
+ tier: 'skeleton',
24
+ mode: null,
25
+ leaves: skeleton?.leaves ?? 0,
26
+ belowMinLeaves: (skeleton?.leaves ?? 0) < MIN_ROOT_LEAVES,
27
+ changedSinceDeposit: false,
28
+ possibleTampering: false,
29
+ bindings,
30
+ });
31
+ /**
32
+ * One session's T0 result. The count bindings come first and need no raw file: a record set that disagrees with itself is `none` whatever `raw/`
33
+ * holds. `state` is `state.json`, read once by {@link auditStore}.
34
+ */
35
+ export const auditSession = async (runtime, session, skeleton, state) => {
36
+ const bindings = bindingsOf(session, skeleton);
37
+ const base = { session: session.session, leaves: bindings.leaves, belowMinLeaves: bindings.leaves < MIN_ROOT_LEAVES, changedSinceDeposit: false, possibleTampering: false, bindings };
38
+ if (skeleton === undefined)
39
+ return { ...base, result: 'not run', tier: 'skeleton', mode: null };
40
+ if (!bindings.bind)
41
+ return { ...base, result: 'counts do not bind', tier: 'none', mode: null };
42
+ const relative = join(session.project, ...relativeOfSession(session.session));
43
+ const rawFile = join(paths(runtime.storeDir).raw, relative);
44
+ const raw = await statOrUndefined(rawFile);
45
+ if (raw === undefined)
46
+ return { ...base, result: 'not run', tier: 'skeleton', mode: null };
47
+ // The mode is read from the file: with the source's inode it is the link, otherwise a copy (or a link whose source is gone).
48
+ const source = await statOrUndefined(join(runtime.projectsRoot, relative));
49
+ const mode = source !== undefined && source.ino === raw.ino && source.dev === raw.dev ? 'link' : 'copy';
50
+ const held = (state ?? (await readState(runtime.storeDir))).files[relative.split('\\').join('/')];
51
+ // `state.json` holds float milliseconds from a plain `stat`; the match compares against the same reading.
52
+ const plain = await stat(rawFile);
53
+ const sizeHeld = held !== undefined && plain.size === held.size;
54
+ const matches = held !== undefined && sizeHeld && (mode === 'link' ? plain.mtimeMs === held.mtimeMs : plain.mtimeMs >= held.mtimeMs);
55
+ if (!matches)
56
+ return { ...base, result: 'changed since deposit', tier: 'skeleton', mode, changedSinceDeposit: true, possibleTampering: mode === 'copy' && held !== undefined && !sizeHeld };
57
+ const lines = rawLinesOf(await readFile(rawFile, 'utf8'));
58
+ const recomputed = lines.length < skeleton.leaves ? undefined : merkleRootOf(lines.slice(0, skeleton.leaves));
59
+ return recomputed === skeleton.merkleRoot ? { ...base, result: 'root matches and the counts bind', tier: 'skeleton', mode } : { ...base, result: 'root mismatch', tier: 'none', mode };
60
+ };
61
+ /**
62
+ * Every session's T0 result, in the order given, then one `not run` for each skeleton with no capture record (a torn or removed observations line), so
63
+ * the table never shrinks without a signal. A session with no skeleton, or whose raw file cannot be read, is also `not run`.
64
+ */
65
+ export const auditStore = async (runtime, sessions, skeletons) => {
66
+ const state = await readState(runtime.storeDir);
67
+ const bySession = new Map(skeletons.map((skeleton) => [skeleton.session, skeleton]));
68
+ const audits = [];
69
+ for (const session of sessions) {
70
+ const skeleton = bySession.get(session.session);
71
+ // One unreadable raw file (EACCES, EBUSY, too large for a string) costs that session its result, never the others'.
72
+ audits.push(await auditSession(runtime, session, skeleton, state).catch(() => unrun(session.session, skeleton, bindingsOf(session, skeleton))));
73
+ }
74
+ const recorded = new Set(sessions.map((session) => session.session));
75
+ for (const skeleton of skeletons) {
76
+ if (!recorded.has(skeleton.session))
77
+ audits.push(unrun(skeleton.session, skeleton, { leaves: skeleton.leaves, sessionLines: 0, skeletonLines: skeleton.lines.length, badLines: 0, bind: false }));
78
+ }
79
+ return audits;
80
+ };
81
+ export const auditCounts = (audits) => {
82
+ const count = (result) => audits.filter((audit) => audit.result === result).length;
83
+ return {
84
+ 'root matches and the counts bind': count('root matches and the counts bind'),
85
+ 'changed since deposit': count('changed since deposit'),
86
+ 'root mismatch': count('root mismatch'),
87
+ 'counts do not bind': count('counts do not bind'),
88
+ 'not run': count('not run'),
89
+ };
90
+ };
91
+ /** A count line for every row of the T0 table, then one FAIL line per session at tier `none` and one note per session that changed. */
92
+ export const formatAudit = (audits) => {
93
+ const counts = auditCounts(audits);
94
+ const tampering = audits.filter((audit) => audit.possibleTampering).length;
95
+ const small = audits.filter((audit) => audit.belowMinLeaves).length;
96
+ return [
97
+ `${String(audits.length)} sessions audited against raw (T0, local: a local match commits nobody)`,
98
+ ...T0_RESULTS.map((result) => ` ${result}: ${String(counts[result])}`),
99
+ ...(tampering > 0 ? [` possible tampering (a copy of a different size): ${String(tampering)}`] : []),
100
+ ...(small > 0 ? [` under ${String(MIN_ROOT_LEAVES)} leaves, so joined to no cluster and counted toward no n: ${String(small)}`] : []),
101
+ ...audits.filter((audit) => audit.tier === 'none').map((audit) => `FAIL ${audit.session}: ${audit.result}; weight is 0 until a new capture writes a new root`),
102
+ ...audits.filter((audit) => audit.changedSinceDeposit).map((audit) => ` ${audit.session}: changed since deposit (${audit.mode ?? 'no mode'}${audit.possibleTampering ? ', possible tampering' : ''})`),
103
+ ].join('\n');
104
+ };