@manyoars/oars 0.4.1 → 0.5.1

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 (183) hide show
  1. package/README.md +79 -29
  2. package/dist/bin.js +3 -1
  3. package/dist/cli.d.ts +2 -0
  4. package/dist/cli.js +206 -20
  5. package/dist/club.d.ts +19 -0
  6. package/dist/club.js +132 -0
  7. package/dist/gate/hook-fill-main.js +1 -1
  8. package/dist/gate/hook-fill.d.ts +1 -1
  9. package/dist/gate/hook-fill.js +1 -1
  10. package/dist/gate/hook-main.js +7 -5
  11. package/dist/gate/hook.d.ts +12 -1
  12. package/dist/gate/hook.js +38 -7
  13. package/dist/hook-bin.js +23 -4
  14. package/dist/main.js +5 -0
  15. package/dist/node-floor.d.ts +10 -0
  16. package/dist/node-floor.js +23 -0
  17. package/dist/oars-hook.d.ts +2 -0
  18. package/dist/oars-hook.js +7 -0
  19. package/dist/sessions/allow.d.ts +1 -0
  20. package/dist/sessions/allow.js +1 -1
  21. package/dist/sessions/call-shape.d.ts +111 -0
  22. package/dist/sessions/call-shape.js +464 -0
  23. package/dist/sessions/candidates.d.ts +65 -0
  24. package/dist/sessions/candidates.js +236 -0
  25. package/dist/sessions/card-command.d.ts +24 -0
  26. package/dist/sessions/card-command.js +71 -0
  27. package/dist/sessions/card-font.d.ts +6 -0
  28. package/dist/sessions/card-font.js +36 -0
  29. package/dist/sessions/card-line.d.ts +30 -0
  30. package/dist/sessions/card-line.js +105 -0
  31. package/dist/sessions/card-render.d.ts +15 -0
  32. package/dist/sessions/card-render.js +146 -0
  33. package/dist/sessions/card.d.ts +59 -0
  34. package/dist/sessions/card.js +107 -0
  35. package/dist/sessions/cell-increments.d.ts +14 -3
  36. package/dist/sessions/cell-increments.js +79 -12
  37. package/dist/sessions/cells.d.ts +42 -4
  38. package/dist/sessions/cells.js +72 -18
  39. package/dist/sessions/codex-hook-bin.d.ts +1 -0
  40. package/dist/sessions/codex-hook-bin.js +24 -0
  41. package/dist/sessions/codex-hook-install.d.ts +21 -0
  42. package/dist/sessions/codex-hook-install.js +47 -0
  43. package/dist/sessions/codex-hook-view.d.ts +20 -0
  44. package/dist/sessions/codex-hook-view.js +20 -0
  45. package/dist/sessions/codex-hook.d.ts +77 -0
  46. package/dist/sessions/codex-hook.js +255 -0
  47. package/dist/sessions/codex.d.ts +9 -0
  48. package/dist/sessions/codex.js +104 -18
  49. package/dist/sessions/command.d.ts +17 -2
  50. package/dist/sessions/command.js +145 -33
  51. package/dist/sessions/copilot.d.ts +3 -0
  52. package/dist/sessions/copilot.js +12 -4
  53. package/dist/sessions/derive.js +2 -1
  54. package/dist/sessions/empty-state.d.ts +17 -0
  55. package/dist/sessions/empty-state.js +38 -0
  56. package/dist/sessions/exclusion.js +48 -3
  57. package/dist/sessions/extract.d.ts +16 -3
  58. package/dist/sessions/extract.js +281 -24
  59. package/dist/sessions/gemini.d.ts +2 -0
  60. package/dist/sessions/gemini.js +29 -2
  61. package/dist/sessions/harness-files.d.ts +27 -0
  62. package/dist/sessions/harness-files.js +109 -0
  63. package/dist/sessions/head-only.d.ts +7 -0
  64. package/dist/sessions/head-only.js +21 -0
  65. package/dist/sessions/hook-calls.js +4 -3
  66. package/dist/sessions/hooks.d.ts +1 -1
  67. package/dist/sessions/hooks.js +6 -2
  68. package/dist/sessions/identity.d.ts +6 -0
  69. package/dist/sessions/injected.d.ts +13 -0
  70. package/dist/sessions/injected.js +212 -0
  71. package/dist/sessions/install.d.ts +31 -20
  72. package/dist/sessions/install.js +80 -82
  73. package/dist/sessions/load-status.d.ts +41 -0
  74. package/dist/sessions/load-status.js +156 -0
  75. package/dist/sessions/machine-child-deps.d.ts +5 -0
  76. package/dist/sessions/machine-child-deps.js +83 -0
  77. package/dist/sessions/machine-child.d.ts +43 -0
  78. package/dist/sessions/machine-child.js +120 -0
  79. package/dist/sessions/machine-credential.d.ts +10 -0
  80. package/dist/sessions/machine-credential.js +20 -0
  81. package/dist/sessions/machine-enroll.d.ts +47 -0
  82. package/dist/sessions/machine-enroll.js +230 -0
  83. package/dist/sessions/machine-hold.d.ts +49 -0
  84. package/dist/sessions/machine-hold.js +88 -0
  85. package/dist/sessions/machine-ids.d.ts +6 -0
  86. package/dist/sessions/machine-ids.js +9 -0
  87. package/dist/sessions/machine-notice.d.ts +21 -0
  88. package/dist/sessions/machine-notice.js +89 -0
  89. package/dist/sessions/machine-pull.d.ts +40 -0
  90. package/dist/sessions/machine-pull.js +220 -0
  91. package/dist/sessions/machine-repo.d.ts +7 -0
  92. package/dist/sessions/machine-repo.js +30 -0
  93. package/dist/sessions/machine-scan-cache.d.ts +12 -0
  94. package/dist/sessions/machine-scan-cache.js +47 -0
  95. package/dist/sessions/machine-send.d.ts +12 -0
  96. package/dist/sessions/machine-send.js +156 -0
  97. package/dist/sessions/machine-stop.d.ts +22 -0
  98. package/dist/sessions/machine-stop.js +53 -0
  99. package/dist/sessions/machine.d.ts +41 -0
  100. package/dist/sessions/machine.js +89 -0
  101. package/dist/sessions/opencode.d.ts +2 -0
  102. package/dist/sessions/opencode.js +6 -0
  103. package/dist/sessions/otel.d.ts +21 -0
  104. package/dist/sessions/otel.js +109 -38
  105. package/dist/sessions/owner-only.d.ts +7 -0
  106. package/dist/sessions/owner-only.js +34 -0
  107. package/dist/sessions/pi.d.ts +13 -1
  108. package/dist/sessions/pi.js +27 -13
  109. package/dist/sessions/pool-books.d.ts +1 -1
  110. package/dist/sessions/pool-books.js +1 -1
  111. package/dist/sessions/pool-choices.d.ts +4 -2
  112. package/dist/sessions/pool-choices.js +4 -2
  113. package/dist/sessions/pool-command.d.ts +46 -6
  114. package/dist/sessions/pool-command.js +577 -133
  115. package/dist/sessions/pool-delta.d.ts +7 -0
  116. package/dist/sessions/pool-delta.js +28 -5
  117. package/dist/sessions/pool-due.d.ts +43 -0
  118. package/dist/sessions/pool-due.js +113 -0
  119. package/dist/sessions/pool-nightly.d.ts +9 -1
  120. package/dist/sessions/pool-nightly.js +65 -21
  121. package/dist/sessions/pool-outbox.d.ts +51 -5
  122. package/dist/sessions/pool-outbox.js +217 -24
  123. package/dist/sessions/pool-print.d.ts +1 -1
  124. package/dist/sessions/pool-raw.js +4 -3
  125. package/dist/sessions/pool-replica.d.ts +4 -4
  126. package/dist/sessions/pool-replica.js +26 -7
  127. package/dist/sessions/pool-send-lock.js +1 -1
  128. package/dist/sessions/pool-sent.d.ts +2 -0
  129. package/dist/sessions/pool-sent.js +21 -4
  130. package/dist/sessions/pool-wire.d.ts +172 -12
  131. package/dist/sessions/pool-wire.js +284 -30
  132. package/dist/sessions/pool.js +4 -2
  133. package/dist/sessions/public-vocabulary.d.ts +5 -0
  134. package/dist/sessions/public-vocabulary.js +16 -0
  135. package/dist/sessions/read-call-shape.js +12 -6
  136. package/dist/sessions/read-fit.d.ts +5 -0
  137. package/dist/sessions/read-fit.js +23 -2
  138. package/dist/sessions/read.d.ts +1 -1
  139. package/dist/sessions/recall-consequence.d.ts +2 -2
  140. package/dist/sessions/recall-consequence.js +14 -9
  141. package/dist/sessions/recall-object.d.ts +9 -2
  142. package/dist/sessions/receipt.d.ts +8 -0
  143. package/dist/sessions/receipt.js +78 -0
  144. package/dist/sessions/recovery-calls.d.ts +3 -2
  145. package/dist/sessions/recovery-calls.js +9 -3
  146. package/dist/sessions/redact.d.ts +2 -2
  147. package/dist/sessions/redact.js +116 -14
  148. package/dist/sessions/reversion.d.ts +8 -0
  149. package/dist/sessions/reversion.js +114 -0
  150. package/dist/sessions/schema.d.ts +78 -4
  151. package/dist/sessions/schema.js +31 -1
  152. package/dist/sessions/session-shape.d.ts +2 -1
  153. package/dist/sessions/session-shape.js +15 -0
  154. package/dist/sessions/sources.d.ts +9 -0
  155. package/dist/sessions/sources.js +16 -5
  156. package/dist/sessions/steps.js +3 -2
  157. package/dist/sessions/store.d.ts +41 -2
  158. package/dist/sessions/store.js +68 -16
  159. package/dist/sessions/try-demo.d.ts +8 -0
  160. package/dist/sessions/try-demo.js +90 -0
  161. package/dist/sessions/try.d.ts +13 -0
  162. package/dist/sessions/try.js +50 -0
  163. package/dist/sessions/vscode-copilot.d.ts +4 -0
  164. package/dist/sessions/vscode-copilot.js +25 -18
  165. package/dist/telemetry/command.js +12 -6
  166. package/dist/telemetry/consent.d.ts +9 -3
  167. package/dist/telemetry/consent.js +25 -9
  168. package/dist/telemetry/fields.d.ts +11 -0
  169. package/dist/telemetry/fields.js +56 -0
  170. package/dist/telemetry/measures.d.ts +13 -0
  171. package/dist/telemetry/measures.js +30 -0
  172. package/dist/telemetry/observe.d.ts +15 -2
  173. package/dist/telemetry/observe.js +34 -7
  174. package/dist/telemetry/region.d.ts +6 -0
  175. package/dist/telemetry/region.js +28 -0
  176. package/dist/telemetry/report.js +1 -1
  177. package/dist/telemetry/schema.d.ts +3 -0
  178. package/dist/telemetry/schema.js +11 -1
  179. package/dist/telemetry/send.d.ts +5 -2
  180. package/dist/telemetry/send.js +15 -7
  181. package/dist/telemetry/words.d.ts +6 -0
  182. package/dist/telemetry/words.js +6 -0
  183. package/package.json +23 -4
@@ -38,6 +38,7 @@ import { readdir, readFile, stat } from 'node:fs/promises';
38
38
  import { homedir } from 'node:os';
39
39
  import { basename, join } from 'node:path';
40
40
  import { createInterface } from 'node:readline';
41
+ import { holdsOnlyKinds } from './head-only.js';
41
42
  /** The Copilot home for an environment; a test passes its own so it never reads the real one. */
42
43
  export const copilotRoot = (env, home = homedir()) => {
43
44
  const set = env.COPILOT_HOME;
@@ -91,6 +92,9 @@ const iso = (value) => {
91
92
  };
92
93
  /** The non-blank lines that are not JSON (a torn write), kept so `captureSession` counts them and the deposit line reports them. */
93
94
  export const eventBadLines = (text) => text.split('\n').filter((raw) => raw.trim() !== '' && parse(raw) === undefined);
95
+ /** Event types that carry no turn by design: the session's own start, resume, context, model, and shutdown records, its journal link, usage checkpoint, error, and abort. A log of these alone is a session started and quit. */
96
+ const EVENT_HEAD_KINDS = new Set(['session.start', 'session.resume', 'session.context_changed', 'session.model_change', 'session.shutdown', 'session.journal_link', 'session.usage_checkpoint', 'session.error', 'abort']);
97
+ export const eventHeadOnly = (text) => holdsOnlyKinds(text, EVENT_HEAD_KINDS);
94
98
  /**
95
99
  * The exit code of a shell call: `shellExecution.exitCode` (session-events.ts, ToolExecutionCompleteData, "Experimental shell completion facts"). The
96
100
  * schema's `result.contents` also has `shell_exit` parts, but it says `shellExecution` is captured before persisted result contents are stripped, and no
@@ -365,6 +369,11 @@ const joinFiles = (texts) => {
365
369
  });
366
370
  return kept.join('\n') + (kept.length === 0 ? '' : '\n');
367
371
  };
372
+ /** The session's events as one text; a session with no archives is read as it is, and only a rotated one has replayed events to drop. */
373
+ const logText = async (session) => {
374
+ const texts = await Promise.all([...session.archives, session.file].map((path) => readFile(path, 'utf8')));
375
+ return session.archives.length === 0 ? (texts[0] ?? '') : joinFiles(texts);
376
+ };
368
377
  export const openCopilotStore = async (root) => {
369
378
  const base = join(root, 'session-state');
370
379
  const unreadable = [];
@@ -375,7 +384,7 @@ export const openCopilotStore = async (root) => {
375
384
  catch (error) {
376
385
  if (missing(error))
377
386
  return undefined;
378
- return { sessions: [], transcript: () => Promise.reject(new Error('no sessions')), rejected: 0, unreadable: [{ file: base, reason: reasonOf(error, base) }] };
387
+ return { sessions: [], transcript: () => Promise.reject(new Error('no sessions')), headOnly: () => Promise.resolve(false), rejected: 0, unreadable: [{ file: base, reason: reasonOf(error, base) }] };
379
388
  }
380
389
  const sessions = [];
381
390
  let rejected = 0;
@@ -421,10 +430,9 @@ export const openCopilotStore = async (root) => {
421
430
  sessions,
422
431
  rejected,
423
432
  unreadable,
433
+ headOnly: async (session) => eventHeadOnly(await logText(session)),
424
434
  transcript: async (session) => {
425
- const texts = await Promise.all([...session.archives, session.file].map((path) => readFile(path, 'utf8')));
426
- // A session with no archives is read as it is; only a rotated one has replayed events to drop.
427
- const text = session.archives.length === 0 ? (texts[0] ?? '') : joinFiles(texts);
435
+ const text = await logText(session);
428
436
  const lines = [...eventLines(text, session.id).map((line) => JSON.stringify(line)), ...eventBadLines(text)];
429
437
  return lines.join('\n') + (lines.length === 0 ? '' : '\n');
430
438
  },
@@ -263,7 +263,8 @@ export const deriveSession = (capture, redact) => {
263
263
  example: cut(error.line, ERROR_EXAMPLE_CHARS),
264
264
  toolUseId: error.toolUseId,
265
265
  next: tools.slice(error.after, error.after + 2).map((tool) => tool.tool),
266
- editFollowed: tools.slice(error.after, error.after + 6).some((tool) => EDITS.has(tool.tool)),
266
+ // `isEdit` skips an edit whose own result was an error, so the stored row and `recoveryCalls` (the wire's value) read one rule.
267
+ editFollowed: tools.slice(error.after, error.after + 6).some(isEdit),
267
268
  prompt: error.prompt,
268
269
  cls: error.prompt === null ? null : (classOf.get(error.prompt) ?? null),
269
270
  }));
@@ -0,0 +1,17 @@
1
+ import type { Source } from './sources.js';
2
+ import type { DepositResult, SessionsRuntime } from './store.js';
3
+ /** `recall` over a store with no sessions in it. */
4
+ export declare const EMPTY_RECALL = "Your store is empty, so there is nothing to recall. See it work on made-up sessions with `oars sessions try`, or fill it with `oars sessions deposit`, which reads this machine's coding-agent session logs into a local store and archives a copy of each transcript.";
5
+ /** `books` over a store with no sessions in it; `what` is "prompts" or "pulls". */
6
+ export declare const emptyBooks: (what: string) => string;
7
+ /**
8
+ * The lines a deposit that read nothing adds: every place it looked and what it found there. A source with no root on this machine was
9
+ * not looked in, and neither was one the run's `read` list leaves out, so neither is listed. Paths are the reader's own, printed on their own terminal.
10
+ */
11
+ export declare const nothingFoundLines: (result: DepositResult, runtime: SessionsRuntime, table: readonly Source[], read: readonly string[]) => string[];
12
+ /**
13
+ * One line when this run read from entries it was not given: they came from the list an earlier deposit saved (`state.json`), so a plain
14
+ * `oars sessions deposit` is not reading every source. `applied` is what the run reads, `given` what it was handed. A saved denial (`--deny`)
15
+ * is not an allow entry, so it is not named here.
16
+ */
17
+ export declare const savedListNote: (applied: readonly string[], given: readonly string[], file: string) => string[];
@@ -0,0 +1,38 @@
1
+ // What `deposit`, `recall`, and `books` say when there is nothing to show, so a first run reads as "nothing found here" and not as a broken tool.
2
+ import { compileAllow, isDenial } from './allow.js';
3
+ /** The two commands that get someone out of an empty store: watch it work on made-up sessions first with `sessions try`, or fill it from their own sessions with `sessions deposit`. */
4
+ const WAYS_OUT = "See it work on made-up sessions with `oars sessions try`, or fill it with `oars sessions deposit`, which reads this machine's coding-agent session logs into a local store and archives a copy of each transcript.";
5
+ /** `recall` over a store with no sessions in it. */
6
+ export const EMPTY_RECALL = `Your store is empty, so there is nothing to recall. ${WAYS_OUT}`;
7
+ /** `books` over a store with no sessions in it; `what` is "prompts" or "pulls". */
8
+ export const emptyBooks = (what) => `No ${what} in the store: it is empty. ${WAYS_OUT}`;
9
+ /**
10
+ * The lines a deposit that read nothing adds: every place it looked and what it found there. A source with no root on this machine was
11
+ * not looked in, and neither was one the run's `read` list leaves out, so neither is listed. Paths are the reader's own, printed on their own terminal.
12
+ */
13
+ export const nothingFoundLines = (result, runtime, table, read) => {
14
+ const allow = compileAllow(read);
15
+ const places = [
16
+ `Claude Code: ${runtime.projectsRoot} (${String(result.folders.length)} project folders read, ${String(result.skippedFolders)} not on the allowlist)`,
17
+ ...table.flatMap((source) => {
18
+ const root = runtime.sourceRoots?.[source.name];
19
+ return root === undefined || !allow.allows(source.name) ? [] : [`${source.label}: ${root} (${String(result.sources[source.name]?.read ?? 0)} sessions read)`];
20
+ }),
21
+ ];
22
+ return ['Found no sessions. Looked in:', ...places.map((place) => ` ${place}`), 'Run it after a coding-agent session has run on this machine, or see oars work first with `oars sessions try`.'];
23
+ };
24
+ /** Entries named in the note before it says "and N more". */
25
+ const NOTE_ENTRIES = 5;
26
+ /**
27
+ * One line when this run read from entries it was not given: they came from the list an earlier deposit saved (`state.json`), so a plain
28
+ * `oars sessions deposit` is not reading every source. `applied` is what the run reads, `given` what it was handed. A saved denial (`--deny`)
29
+ * is not an allow entry, so it is not named here.
30
+ */
31
+ export const savedListNote = (applied, given, file) => {
32
+ const saved = applied.filter((entry) => !isDenial(entry) && !given.includes(entry));
33
+ if (saved.length === 0)
34
+ return [];
35
+ const shown = saved.slice(0, NOTE_ENTRIES).join(', ');
36
+ const more = saved.length > NOTE_ENTRIES ? `, and ${String(saved.length - NOTE_ENTRIES)} more` : '';
37
+ return [`Used the --allow list saved by an earlier run (${shown}${more}), kept in ${file}. \`--allow\` adds folders to it and \`--deny\` leaves one out.`];
38
+ };
@@ -7,7 +7,7 @@ import { chmod, lstat, mkdir, readdir, readFile, realpath, rename, rm, stat, wri
7
7
  import { dirname, join } from 'node:path';
8
8
  import { createInterface } from 'node:readline';
9
9
  import { CliError, ExitCode } from '../errors.js';
10
- import { loadStore, paths } from './store.js';
10
+ import { loadStore, paths, sourceRoots, sourcesOf } from './store.js';
11
11
  /** `OARS_EXCLUSION_TERMS` names the term file; the default sits beside the claude config's other local-only session-commons files. */
12
12
  export const exclusionPaths = (projectsRoot, env, out) => {
13
13
  const commons = join(dirname(projectsRoot), 'session-commons');
@@ -141,7 +141,51 @@ const filesIn = async (root, source, project, session, found) => {
141
141
  found.push({ path, key: `${session}/subagents/${path.slice(subagents.length + 1).split('\\').join('/')}`, main: false, source });
142
142
  return unreadable;
143
143
  };
144
- /** Both the harness's folder and oars' raw archive, because either can be the only copy left. */
144
+ /**
145
+ * Where each session of a non-Claude source keeps its file, by the store's session id (`<source>-<id>`), read once per runtime and source: opening a source's store walks all of it.
146
+ * Only a transcript file counts: a source that keeps its sessions in a database names the database for every session, and that is no text the gate can read.
147
+ */
148
+ const sourceFileCache = new WeakMap();
149
+ const sourceFileMap = (runtime, name, open) => {
150
+ const known = sourceFileCache.get(runtime) ?? new Map();
151
+ sourceFileCache.set(runtime, known);
152
+ const found = known.get(name) ?? open();
153
+ known.set(name, found);
154
+ return found;
155
+ };
156
+ /** The harness's own file for a session of a source the runtime reads (a Codex rollout): the only copy there is, since oars archives nothing for a source. */
157
+ const sourceFilesOf = async (runtime, session, found) => {
158
+ const roots = sourceRoots(runtime);
159
+ const source = sourcesOf(runtime).find((one) => roots.has(one.name) && session.startsWith(`${one.name}-`));
160
+ const root = source === undefined ? undefined : roots.get(source.name);
161
+ if (source === undefined || root === undefined)
162
+ return false;
163
+ try {
164
+ const files = await sourceFileMap(runtime, source.name, async () => {
165
+ const store = await source.open(root);
166
+ const byId = new Map();
167
+ if (store === undefined)
168
+ return byId;
169
+ try {
170
+ for (const one of store.sessions)
171
+ byId.set(`${source.name}-${one.id}`, store.where(one));
172
+ }
173
+ finally {
174
+ store.close();
175
+ }
176
+ return byId;
177
+ });
178
+ const path = files.get(session);
179
+ // A hit in it is counted with the harness's folders: it is the harness's own file, as a Claude Code transcript is.
180
+ if (path !== undefined && path.endsWith('.jsonl') && (await isFile(path)))
181
+ found.push({ path, key: `${session}.jsonl`, main: true, source: 'projects' });
182
+ return false;
183
+ }
184
+ catch {
185
+ return true;
186
+ }
187
+ };
188
+ /** Both the harness's folder and oars' raw archive, because either can be the only copy left; a source's session also has its own file. */
145
189
  const filesOf = async (runtime, project, session) => {
146
190
  const files = [];
147
191
  const raw = paths(runtime.storeDir).raw;
@@ -152,7 +196,8 @@ const filesOf = async (runtime, project, session) => {
152
196
  unreadable = true;
153
197
  const first = await filesIn(runtime.projectsRoot, 'projects', project, session, files);
154
198
  const second = await filesIn(raw, 'raw', project, session, files);
155
- return { files, unreadable: unreadable || first || second };
199
+ const third = await sourceFilesOf(runtime, session, files);
200
+ return { files, unreadable: unreadable || first || second || third };
156
201
  };
157
202
  /** The transcript files of one session, the main one first, one per path under the project folder (the harness's copy before oars' raw archive's): what a raw copy is built from, after the gate has cleared them. */
158
203
  export const transcriptFilesOf = async (runtime, project, session) => {
@@ -1,3 +1,4 @@
1
+ import { type CommandWords } from './call-shape.js';
1
2
  import { type RepoSize } from './public-vocabulary.js';
2
3
  import { type WordOfCommand } from './identity.js';
3
4
  import type { Redactor } from './redact.js';
@@ -56,6 +57,8 @@ export interface ExtractInput {
56
57
  redact: Redactor;
57
58
  /** The per-account salt for path targets; the store keeps it beside the log. */
58
59
  salt?: string;
60
+ /** The salt a repository's key is hashed under, derived from the account salt so a machine given it by enrollment keys a repository as the trusted machine does; absent means `salt`. */
61
+ repoSalt?: string;
59
62
  /** The salt's `sid` (first 8 hex of its SHA-256), stamped on every capture record; null when no salt is in play. */
60
63
  sid?: string | null;
61
64
  /** The repository root holding a working directory (worktree segments already stripped), or null; absent means no repository is known. */
@@ -85,6 +88,7 @@ export interface Capture {
85
88
  errorLines: ErrorLineObservation[];
86
89
  skeleton: Skeleton;
87
90
  }
91
+ export declare const LOCAL_COMMAND_TAGS: ReadonlySet<string>;
88
92
  export declare const INJECTED_TAGS: ReadonlySet<string>;
89
93
  export declare const LEADING_TAG: RegExp;
90
94
  export interface PromptOpening {
@@ -125,6 +129,9 @@ type ShellLink = 'and' | 'pipe' | 'break' | 'end';
125
129
  export interface ShellCommand {
126
130
  program: string;
127
131
  args: ShellWord[];
132
+ /** The words before the program (assignments, wrappers, block words) and the program's own word, as written: the name read takes variable names from them. */
133
+ before: ShellWord[];
134
+ head: ShellWord;
128
135
  read: boolean;
129
136
  /** How the next command joins this one. */
130
137
  link: ShellLink;
@@ -144,17 +151,23 @@ export interface PlannedCommit {
144
151
  readBack: boolean;
145
152
  }
146
153
  export interface ShellRead {
154
+ /** Every command's words, for the name read (`argNamesOf`). */
155
+ words: CommandWords[];
147
156
  segments: ShellSegment[];
148
157
  /** The same commands as `segments`, in public names, apart from a pull's package name, which is kept as it is after redaction, with the redacted text they compare by. */
149
158
  steps: ShellStepRead[];
150
159
  pulls: ToolPull[];
151
160
  commitPlan: PlannedCommit[];
161
+ /** The paths a `git restore` or `git checkout --` names, as written, in the commands whose status the call reports. Not in `segments`: a slashed path stays out of `run`. */
162
+ restores: string[];
152
163
  }
164
+ /** The shell a command is written for, which decides its escapes alone: a call whose tool the harness's `shells` table maps to `powershell` (Claude Code's `PowerShell`, and the `powershell` that Copilot, Pi, and Gemini name) reads a backslash as plain and a backtick as the escape, every other call by POSIX's. */
165
+ type ShellDialect = 'posix' | 'powershell';
153
166
  export declare const CHANGES_FOLDER: ReadonlySet<string>;
154
- /** Every simple command of a shell call, in order, after the bodies are dropped and the command is redacted. `oars gate` reads the same commands as capture. */
155
- export declare const shellCommands: (command: string, redact: Redactor) => ShellCommand[];
167
+ /** Every simple command of a shell call, in order, after the bodies are dropped and the command is redacted. Capture passes the dialect of the tool; `gate/checks.ts:524` and `projection.ts:95` call it with the POSIX default. */
168
+ export declare const shellCommands: (command: string, redact: Redactor, dialect?: ShellDialect) => ShellCommand[];
156
169
  /** A shell call's segments, pulls, and the commits it holds, from the command alone. The command is redacted before anything is kept. */
157
- export declare const readShell: (command: string, redact: Redactor) => ShellRead;
170
+ export declare const readShell: (command: string, redact: Redactor, dialect?: ShellDialect) => ShellRead;
158
171
  /**
159
172
  * What a clean result says about the planned commits: one entry per commit made, the sha when its `[branch sha]` line or a read-back
160
173
  * names it, else null, since a count may stand without a sha. A refused commit makes no entry: the call can be clean behind `; echo done`.