@manyoars/oars 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +147 -28
- package/dist/api.js +1 -1
- package/dist/bin.js +6 -29
- package/dist/cli.d.ts +10 -4
- package/dist/cli.js +440 -91
- package/dist/commands/keys.d.ts +31 -0
- package/dist/commands/keys.js +65 -0
- package/dist/commands/login.d.ts +1 -1
- package/dist/commands/login.js +3 -3
- package/dist/errors.d.ts +13 -13
- package/dist/errors.js +28 -11
- package/dist/gate/checks.d.ts +68 -0
- package/dist/gate/checks.js +584 -0
- package/dist/gate/command.d.ts +14 -0
- package/dist/gate/command.js +61 -0
- package/dist/gate/hook-fill-main.d.ts +1 -0
- package/dist/gate/hook-fill-main.js +16 -0
- package/dist/gate/hook-fill.d.ts +38 -0
- package/dist/gate/hook-fill.js +94 -0
- package/dist/gate/hook-main.d.ts +1 -0
- package/dist/gate/hook-main.js +47 -0
- package/dist/gate/hook-process.d.ts +24 -0
- package/dist/gate/hook-process.js +112 -0
- package/dist/gate/hook-record.d.ts +3 -0
- package/dist/gate/hook-record.js +8 -0
- package/dist/gate/hook.d.ts +48 -0
- package/dist/gate/hook.js +259 -0
- package/dist/gate/install.d.ts +25 -0
- package/dist/gate/install.js +126 -0
- package/dist/gate/local-bin.d.ts +8 -0
- package/dist/gate/local-bin.js +36 -0
- package/dist/gate/notes.d.ts +19 -0
- package/dist/gate/notes.js +27 -0
- package/dist/gate/novelty.d.ts +35 -0
- package/dist/gate/novelty.js +99 -0
- package/dist/gate/policy.d.ts +25 -0
- package/dist/gate/policy.js +73 -0
- package/dist/gate/record.d.ts +120 -0
- package/dist/gate/record.js +125 -0
- package/dist/gate/registry-state.d.ts +10 -0
- package/dist/gate/registry-state.js +30 -0
- package/dist/gate/registry.d.ts +56 -0
- package/dist/gate/registry.js +94 -0
- package/dist/gate/replay.d.ts +20 -0
- package/dist/gate/replay.js +254 -0
- package/dist/gate/report.d.ts +50 -0
- package/dist/gate/report.js +89 -0
- package/dist/gate/scope.d.ts +7 -0
- package/dist/gate/scope.js +27 -0
- package/dist/gate/transcripts.d.ts +68 -0
- package/dist/gate/transcripts.js +232 -0
- package/dist/hook-bin.d.ts +1 -0
- package/dist/hook-bin.js +50 -0
- package/dist/issuer.d.ts +10 -6
- package/dist/issuer.js +19 -24
- package/dist/main.d.ts +1 -0
- package/dist/main.js +67 -0
- package/dist/oidc.js +1 -1
- package/dist/sessions/actions.d.ts +35 -0
- package/dist/sessions/actions.js +429 -0
- package/dist/sessions/allow.d.ts +23 -0
- package/dist/sessions/allow.js +71 -0
- package/dist/sessions/archive.d.ts +11 -0
- package/dist/sessions/archive.js +38 -0
- package/dist/sessions/audit.d.ts +42 -0
- package/dist/sessions/audit.js +104 -0
- package/dist/sessions/baseline.d.ts +62 -0
- package/dist/sessions/baseline.js +190 -0
- package/dist/sessions/call-shape.d.ts +19 -0
- package/dist/sessions/call-shape.js +85 -0
- package/dist/sessions/capture-runtime.d.ts +10 -0
- package/dist/sessions/capture-runtime.js +12 -0
- package/dist/sessions/cell-increments.d.ts +47 -0
- package/dist/sessions/cell-increments.js +243 -0
- package/dist/sessions/cells.d.ts +109 -0
- package/dist/sessions/cells.js +261 -0
- package/dist/sessions/codex.d.ts +53 -0
- package/dist/sessions/codex.js +745 -0
- package/dist/sessions/command.d.ts +127 -0
- package/dist/sessions/command.js +419 -0
- package/dist/sessions/copilot.d.ts +40 -0
- package/dist/sessions/copilot.js +432 -0
- package/dist/sessions/coverage.d.ts +19 -0
- package/dist/sessions/coverage.js +56 -0
- package/dist/sessions/cursor.d.ts +24 -0
- package/dist/sessions/cursor.js +324 -0
- package/dist/sessions/derive.d.ts +57 -0
- package/dist/sessions/derive.js +386 -0
- package/dist/sessions/exclusion.d.ts +87 -0
- package/dist/sessions/exclusion.js +361 -0
- package/dist/sessions/extract.d.ts +181 -0
- package/dist/sessions/extract.js +1806 -0
- package/dist/sessions/fold.d.ts +17 -0
- package/dist/sessions/fold.js +71 -0
- package/dist/sessions/frame.d.ts +24 -0
- package/dist/sessions/frame.js +95 -0
- package/dist/sessions/gemini.d.ts +37 -0
- package/dist/sessions/gemini.js +613 -0
- package/dist/sessions/gold-arms.d.ts +67 -0
- package/dist/sessions/gold-arms.js +172 -0
- package/dist/sessions/gold.d.ts +197 -0
- package/dist/sessions/gold.js +463 -0
- package/dist/sessions/graph.d.ts +12 -0
- package/dist/sessions/graph.js +34 -0
- package/dist/sessions/grok.d.ts +36 -0
- package/dist/sessions/grok.js +421 -0
- package/dist/sessions/hook-calls.d.ts +3 -0
- package/dist/sessions/hook-calls.js +47 -0
- package/dist/sessions/hook-index.d.ts +66 -0
- package/dist/sessions/hook-index.js +164 -0
- package/dist/sessions/hooks.d.ts +17 -0
- package/dist/sessions/hooks.js +281 -0
- package/dist/sessions/identity.d.ts +27 -0
- package/dist/sessions/identity.js +131 -0
- package/dist/sessions/install.d.ts +70 -0
- package/dist/sessions/install.js +246 -0
- package/dist/sessions/lineage.d.ts +19 -0
- package/dist/sessions/lineage.js +118 -0
- package/dist/sessions/mcp.d.ts +7 -0
- package/dist/sessions/mcp.js +157 -0
- package/dist/sessions/merkle.d.ts +7 -0
- package/dist/sessions/merkle.js +28 -0
- package/dist/sessions/motif.d.ts +44 -0
- package/dist/sessions/motif.js +59 -0
- package/dist/sessions/opencode.d.ts +33 -0
- package/dist/sessions/opencode.js +319 -0
- package/dist/sessions/otel.d.ts +60 -0
- package/dist/sessions/otel.js +494 -0
- package/dist/sessions/package-books.d.ts +48 -0
- package/dist/sessions/package-books.js +120 -0
- package/dist/sessions/package-resolve.d.ts +32 -0
- package/dist/sessions/package-resolve.js +185 -0
- package/dist/sessions/pending.d.ts +34 -0
- package/dist/sessions/pending.js +142 -0
- package/dist/sessions/pi.d.ts +36 -0
- package/dist/sessions/pi.js +353 -0
- package/dist/sessions/pool-books.d.ts +21 -0
- package/dist/sessions/pool-books.js +75 -0
- package/dist/sessions/pool-choices.d.ts +5 -0
- package/dist/sessions/pool-choices.js +11 -0
- package/dist/sessions/pool-command.d.ts +115 -0
- package/dist/sessions/pool-command.js +669 -0
- package/dist/sessions/pool-delta.d.ts +54 -0
- package/dist/sessions/pool-delta.js +190 -0
- package/dist/sessions/pool-nightly-deps.d.ts +3 -0
- package/dist/sessions/pool-nightly-deps.js +11 -0
- package/dist/sessions/pool-nightly.d.ts +10 -0
- package/dist/sessions/pool-nightly.js +85 -0
- package/dist/sessions/pool-outbox.d.ts +76 -0
- package/dist/sessions/pool-outbox.js +313 -0
- package/dist/sessions/pool-print.d.ts +26 -0
- package/dist/sessions/pool-print.js +68 -0
- package/dist/sessions/pool-raw.d.ts +27 -0
- package/dist/sessions/pool-raw.js +68 -0
- package/dist/sessions/pool-replica.d.ts +35 -0
- package/dist/sessions/pool-replica.js +91 -0
- package/dist/sessions/pool-send-lock.d.ts +20 -0
- package/dist/sessions/pool-send-lock.js +87 -0
- package/dist/sessions/pool-sent.d.ts +4 -0
- package/dist/sessions/pool-sent.js +26 -0
- package/dist/sessions/pool-totals.d.ts +10 -0
- package/dist/sessions/pool-totals.js +21 -0
- package/dist/sessions/pool-wire.d.ts +435 -0
- package/dist/sessions/pool-wire.js +1100 -0
- package/dist/sessions/pool.d.ts +32 -0
- package/dist/sessions/pool.js +164 -0
- package/dist/sessions/projection.d.ts +75 -0
- package/dist/sessions/projection.js +165 -0
- package/dist/sessions/public-vocabulary.d.ts +72 -0
- package/dist/sessions/public-vocabulary.js +295 -0
- package/dist/sessions/query.d.ts +123 -0
- package/dist/sessions/query.js +268 -0
- package/dist/sessions/read-adoption.d.ts +2 -0
- package/dist/sessions/read-adoption.js +27 -0
- package/dist/sessions/read-call-shape.d.ts +2 -0
- package/dist/sessions/read-call-shape.js +25 -0
- package/dist/sessions/read-change.d.ts +2 -0
- package/dist/sessions/read-change.js +30 -0
- package/dist/sessions/read-cost.d.ts +4 -0
- package/dist/sessions/read-cost.js +48 -0
- package/dist/sessions/read-day-move.d.ts +3 -0
- package/dist/sessions/read-day-move.js +65 -0
- package/dist/sessions/read-fit.d.ts +7 -0
- package/dist/sessions/read-fit.js +41 -0
- package/dist/sessions/read-interaction.d.ts +5 -0
- package/dist/sessions/read-interaction.js +62 -0
- package/dist/sessions/read-lead-lag.d.ts +15 -0
- package/dist/sessions/read-lead-lag.js +81 -0
- package/dist/sessions/read-live.d.ts +6 -0
- package/dist/sessions/read-live.js +38 -0
- package/dist/sessions/read-method.d.ts +2 -0
- package/dist/sessions/read-method.js +29 -0
- package/dist/sessions/read-next-step.d.ts +2 -0
- package/dist/sessions/read-next-step.js +35 -0
- package/dist/sessions/read-order.d.ts +54 -0
- package/dist/sessions/read-order.js +62 -0
- package/dist/sessions/read-readable.d.ts +8 -0
- package/dist/sessions/read-readable.js +24 -0
- package/dist/sessions/read-recovery-edge.d.ts +2 -0
- package/dist/sessions/read-recovery-edge.js +26 -0
- package/dist/sessions/read-recovery.d.ts +20 -0
- package/dist/sessions/read-recovery.js +97 -0
- package/dist/sessions/read-rows.d.ts +13 -0
- package/dist/sessions/read-rows.js +47 -0
- package/dist/sessions/read-substitution.d.ts +5 -0
- package/dist/sessions/read-substitution.js +51 -0
- package/dist/sessions/read-support.d.ts +48 -0
- package/dist/sessions/read-support.js +73 -0
- package/dist/sessions/read-tool-day.d.ts +2 -0
- package/dist/sessions/read-tool-day.js +26 -0
- package/dist/sessions/read-version-boundary.d.ts +31 -0
- package/dist/sessions/read-version-boundary.js +95 -0
- package/dist/sessions/read.d.ts +86 -0
- package/dist/sessions/read.js +87 -0
- package/dist/sessions/recall-bundle.d.ts +12 -0
- package/dist/sessions/recall-bundle.js +33 -0
- package/dist/sessions/recall-consequence.d.ts +45 -0
- package/dist/sessions/recall-consequence.js +179 -0
- package/dist/sessions/recall-frames.d.ts +18 -0
- package/dist/sessions/recall-frames.js +44 -0
- package/dist/sessions/recall-next.d.ts +145 -0
- package/dist/sessions/recall-next.js +241 -0
- package/dist/sessions/recall-object.d.ts +139 -0
- package/dist/sessions/recall-object.js +78 -0
- package/dist/sessions/recall-pool.d.ts +19 -0
- package/dist/sessions/recall-pool.js +96 -0
- package/dist/sessions/recall-push.d.ts +26 -0
- package/dist/sessions/recall-push.js +47 -0
- package/dist/sessions/recall-reads.d.ts +124 -0
- package/dist/sessions/recall-reads.js +417 -0
- package/dist/sessions/recall-render.d.ts +19 -0
- package/dist/sessions/recall-render.js +168 -0
- package/dist/sessions/recovery-calls.d.ts +21 -0
- package/dist/sessions/recovery-calls.js +25 -0
- package/dist/sessions/redact.d.ts +17 -0
- package/dist/sessions/redact.js +113 -0
- package/dist/sessions/repo-root.d.ts +10 -0
- package/dist/sessions/repo-root.js +101 -0
- package/dist/sessions/schema.d.ts +694 -0
- package/dist/sessions/schema.js +167 -0
- package/dist/sessions/session-shape.d.ts +76 -0
- package/dist/sessions/session-shape.js +355 -0
- package/dist/sessions/sources.d.ts +50 -0
- package/dist/sessions/sources.js +233 -0
- package/dist/sessions/steps.d.ts +105 -0
- package/dist/sessions/steps.js +250 -0
- package/dist/sessions/store.d.ts +309 -0
- package/dist/sessions/store.js +1498 -0
- package/dist/sessions/strata.d.ts +26 -0
- package/dist/sessions/strata.js +77 -0
- package/dist/sessions/survival.d.ts +27 -0
- package/dist/sessions/survival.js +230 -0
- package/dist/sessions/transcript-tail.d.ts +14 -0
- package/dist/sessions/transcript-tail.js +64 -0
- package/dist/sessions/verify.d.ts +53 -0
- package/dist/sessions/verify.js +286 -0
- package/dist/sessions/vocab-load.d.ts +26 -0
- package/dist/sessions/vocab-load.js +61 -0
- package/dist/sessions/vscode-copilot.d.ts +38 -0
- package/dist/sessions/vscode-copilot.js +494 -0
- package/dist/telemetry/command.d.ts +8 -0
- package/dist/telemetry/command.js +85 -0
- package/dist/telemetry/consent.d.ts +17 -0
- package/dist/telemetry/consent.js +63 -0
- package/dist/telemetry/crash.d.ts +8 -0
- package/dist/telemetry/crash.js +52 -0
- package/dist/telemetry/emit.d.ts +31 -0
- package/dist/telemetry/emit.js +42 -0
- package/dist/telemetry/fields.d.ts +16 -0
- package/dist/telemetry/fields.js +26 -0
- package/dist/telemetry/health.d.ts +21 -0
- package/dist/telemetry/health.js +108 -0
- package/dist/telemetry/hook-emit.d.ts +4 -0
- package/dist/telemetry/hook-emit.js +18 -0
- package/dist/telemetry/measures.d.ts +32 -0
- package/dist/telemetry/measures.js +128 -0
- package/dist/telemetry/observe.d.ts +13 -0
- package/dist/telemetry/observe.js +51 -0
- package/dist/telemetry/read-events.d.ts +9 -0
- package/dist/telemetry/read-events.js +17 -0
- package/dist/telemetry/record.d.ts +17 -0
- package/dist/telemetry/record.js +26 -0
- package/dist/telemetry/report.d.ts +77 -0
- package/dist/telemetry/report.js +188 -0
- package/dist/telemetry/schema.d.ts +40 -0
- package/dist/telemetry/schema.js +187 -0
- package/dist/telemetry/send-main.d.ts +1 -0
- package/dist/telemetry/send-main.js +11 -0
- package/dist/telemetry/send.d.ts +36 -0
- package/dist/telemetry/send.js +172 -0
- package/dist/telemetry/spool.d.ts +17 -0
- package/dist/telemetry/spool.js +70 -0
- package/dist/telemetry/wire.d.ts +25 -0
- package/dist/telemetry/wire.js +76 -0
- package/package.json +4 -5
- package/vocab/crates.json +1 -0
- package/vocab/go.json +1 -0
- package/vocab/harness.json +1 -0
- package/vocab/homebrew.json +1 -0
- package/vocab/manifest.json +1 -0
- package/vocab/maven.json +1 -0
- package/vocab/models.json +1 -0
- package/vocab/npm.json +1 -0
- package/vocab/programs.json +1 -0
- package/vocab/pypi.json +1 -0
- package/vocab/rubygems.json +1 -0
- package/dist/commands/deploy-input.d.ts +0 -38
- package/dist/commands/deploy-input.js +0 -150
- package/dist/commands/deploy.d.ts +0 -22
- package/dist/commands/deploy.js +0 -124
- package/dist/commands/status.d.ts +0 -7
- package/dist/commands/status.js +0 -18
- package/dist/commands/templates.d.ts +0 -11
- package/dist/commands/templates.js +0 -32
- package/dist/commands/test-charge.d.ts +0 -11
- package/dist/commands/test-charge.js +0 -34
- package/dist/journal.d.ts +0 -26
- package/dist/journal.js +0 -58
- package/parity/deploy-plans/paid-api.json +0 -34
- package/parity/deploy-plans/per-task-agent.json +0 -34
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
// The exclusion rule (register A38): a session is excluded if any line of its main or subagent transcript holds a listed term,
|
|
2
|
+
// scanned locally before anything is deposited to the pool. A session with no raw to scan is excluded, and so is every session when
|
|
3
|
+
// the term file is missing or empty (fail closed). The term list is a local file and never lives in this repository; nothing here
|
|
4
|
+
// prints, logs, or returns a term or a line of a transcript, only counts and session ids.
|
|
5
|
+
import { createReadStream } from 'node:fs';
|
|
6
|
+
import { chmod, lstat, mkdir, readdir, readFile, realpath, rename, rm, stat, writeFile } from 'node:fs/promises';
|
|
7
|
+
import { dirname, join } from 'node:path';
|
|
8
|
+
import { createInterface } from 'node:readline';
|
|
9
|
+
import { CliError, ExitCode } from '../errors.js';
|
|
10
|
+
import { loadStore, paths } from './store.js';
|
|
11
|
+
/** `OARS_EXCLUSION_TERMS` names the term file; the default sits beside the claude config's other local-only session-commons files. */
|
|
12
|
+
export const exclusionPaths = (projectsRoot, env, out) => {
|
|
13
|
+
const commons = join(dirname(projectsRoot), 'session-commons');
|
|
14
|
+
const terms = env.OARS_EXCLUSION_TERMS;
|
|
15
|
+
return { terms: terms === undefined || terms === '' ? join(commons, 'exclusion-terms.txt') : terms, list: out ?? join(commons, 'exclusion-list.jsonl') };
|
|
16
|
+
};
|
|
17
|
+
/** The terms, lowercased; a missing, unreadable, or undecodable file gives none, which excludes everything. */
|
|
18
|
+
export const loadTerms = async (file) => {
|
|
19
|
+
try {
|
|
20
|
+
const text = await readFile(file, 'utf8');
|
|
21
|
+
// A UTF-16 file (Windows PowerShell 5.1 `>`) decodes to NULs, and other bytes to U+FFFD: terms that never match would read every session as clean.
|
|
22
|
+
if (text.includes('\u0000') || text.includes('\ufffd'))
|
|
23
|
+
return [];
|
|
24
|
+
return text.split(/\r?\n/).map((line) => line.trim().toLowerCase()).filter((line) => line !== '');
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
const isAbsent = (error) => error instanceof Error && 'code' in error && (error.code === 'ENOENT' || error.code === 'ENOTDIR');
|
|
31
|
+
/** A missing folder is empty; any other failure (ENOTDIR for a plain file, EACCES, EPERM, EBUSY) is thrown, so a folder that was not read never reads as clean. */
|
|
32
|
+
const entriesOf = async (dir) => {
|
|
33
|
+
try {
|
|
34
|
+
return await readdir(dir, { withFileTypes: true });
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
if (error instanceof Error && 'code' in error && error.code === 'ENOENT')
|
|
38
|
+
return [];
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* The one rule for a path at any level: a link that leads nowhere, or an entry of the wrong kind (a plain file where a folder belongs, a folder where
|
|
44
|
+
* a transcript belongs), means what is under it was not scanned. Windows reports a listing of a path under a plain file as ENOENT, so the kind is checked here and not left to the listing.
|
|
45
|
+
* An absent path is not one.
|
|
46
|
+
*/
|
|
47
|
+
const badLink = async (path, kind) => {
|
|
48
|
+
let entry;
|
|
49
|
+
try {
|
|
50
|
+
entry = await lstat(path);
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
return !isAbsent(error);
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
const target = entry.isSymbolicLink() ? await stat(path) : entry;
|
|
57
|
+
return kind === 'directory' ? !target.isDirectory() : !target.isFile();
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* A link is followed, as the main file's `stat` follows one; a link that leads nowhere or to the wrong kind, or a plain entry named like a transcript that is not a file, is a bad entry, and so is a folder that cannot be listed
|
|
65
|
+
* and a folder reached a second time (a link back into a folder already walked, which would loop and count a transcript once per pass).
|
|
66
|
+
* The walk goes on past every bad entry and `found` holds every transcript it reached, so the verdict does not depend on the listing order; true when any entry was bad.
|
|
67
|
+
* `seen` holds the real path of every folder entered.
|
|
68
|
+
*/
|
|
69
|
+
const jsonlUnder = async (dir, found, seen) => {
|
|
70
|
+
let bad = false;
|
|
71
|
+
for (const entry of await entriesOf(dir)) {
|
|
72
|
+
const path = join(dir, entry.name);
|
|
73
|
+
const named = entry.name.endsWith('.jsonl');
|
|
74
|
+
// A plain entry named like a transcript goes through the helper too: a folder or a FIFO there is not a file, and what is under it was not scanned.
|
|
75
|
+
if ((entry.isSymbolicLink() || named) && (await badLink(path, named ? 'file' : 'directory'))) {
|
|
76
|
+
bad = true;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
const target = entry.isSymbolicLink() ? await stat(path) : entry;
|
|
80
|
+
if (target.isDirectory()) {
|
|
81
|
+
try {
|
|
82
|
+
const real = await realpath(path);
|
|
83
|
+
if (seen.has(real)) {
|
|
84
|
+
bad = true;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
seen.add(real);
|
|
88
|
+
if (await jsonlUnder(path, found, seen))
|
|
89
|
+
bad = true;
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
bad = true;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
else if (named)
|
|
96
|
+
found.push(path);
|
|
97
|
+
}
|
|
98
|
+
return bad;
|
|
99
|
+
};
|
|
100
|
+
/** A main that is not there is false; any other failure (EACCES, EPERM, ELOOP) is thrown, so a main that was not examined never reads as absent. */
|
|
101
|
+
const isFile = async (path) => {
|
|
102
|
+
try {
|
|
103
|
+
return (await stat(path)).isFile();
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
if (isAbsent(error))
|
|
107
|
+
return false;
|
|
108
|
+
throw error;
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
/** Adds the transcripts of the session in one source (the main file and everything under `<session>/subagents/`) to `found`; true when a level could not be read. */
|
|
112
|
+
const filesIn = async (root, source, project, session, found) => {
|
|
113
|
+
const folder = join(root, project);
|
|
114
|
+
const main = join(folder, `${session}.jsonl`);
|
|
115
|
+
// A link that leads nowhere or a wrong kind of entry reads as an absent one below, so every level is checked first (a directory at the main makes the session unreadable here).
|
|
116
|
+
let unreadable = await badLink(main, 'file');
|
|
117
|
+
for (const path of [folder, join(folder, session), join(folder, session, 'subagents')])
|
|
118
|
+
if (await badLink(path, 'directory'))
|
|
119
|
+
unreadable = true;
|
|
120
|
+
try {
|
|
121
|
+
if (await isFile(main))
|
|
122
|
+
found.push({ path: main, key: `${session}.jsonl`, main: true, source });
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
unreadable = true;
|
|
126
|
+
}
|
|
127
|
+
const subagents = join(folder, session, 'subagents');
|
|
128
|
+
const agents = [];
|
|
129
|
+
try {
|
|
130
|
+
// The folder itself is seen first, so a link back to it is a repeat; a folder that is not there has no real path and nothing to walk.
|
|
131
|
+
const seen = new Set();
|
|
132
|
+
seen.add(await realpath(subagents).catch(() => subagents));
|
|
133
|
+
if (await jsonlUnder(subagents, agents, seen))
|
|
134
|
+
unreadable = true;
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
unreadable = true;
|
|
138
|
+
}
|
|
139
|
+
// Every file the walk reached is scanned, so a hit is counted whatever the listing order.
|
|
140
|
+
for (const path of agents)
|
|
141
|
+
found.push({ path, key: `${session}/subagents/${path.slice(subagents.length + 1).split('\\').join('/')}`, main: false, source });
|
|
142
|
+
return unreadable;
|
|
143
|
+
};
|
|
144
|
+
/** Both the harness's folder and oars' raw archive, because either can be the only copy left. */
|
|
145
|
+
const filesOf = async (runtime, project, session) => {
|
|
146
|
+
const files = [];
|
|
147
|
+
const raw = paths(runtime.storeDir).raw;
|
|
148
|
+
// A root that is a link leading nowhere makes `lstat` of everything under it ENOENT, so the roots are checked here.
|
|
149
|
+
let unreadable = false;
|
|
150
|
+
for (const root of [runtime.projectsRoot, runtime.storeDir, raw])
|
|
151
|
+
if (await badLink(root, 'directory'))
|
|
152
|
+
unreadable = true;
|
|
153
|
+
const first = await filesIn(runtime.projectsRoot, 'projects', project, session, files);
|
|
154
|
+
const second = await filesIn(raw, 'raw', project, session, files);
|
|
155
|
+
return { files, unreadable: unreadable || first || second };
|
|
156
|
+
};
|
|
157
|
+
/** 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
|
+
export const transcriptFilesOf = async (runtime, project, session) => {
|
|
159
|
+
const seen = new Map();
|
|
160
|
+
for (const { key, path, main } of (await filesOf(runtime, project, session)).files)
|
|
161
|
+
if (!seen.has(key))
|
|
162
|
+
seen.set(key, { key, path, main });
|
|
163
|
+
return [...seen.values()].sort((a, b) => (a.main === b.main ? (a.key < b.key ? -1 : 1) : a.main ? -1 : 1));
|
|
164
|
+
};
|
|
165
|
+
/** Whether any line of the file holds a term. Streamed, so a large transcript is never held whole. */
|
|
166
|
+
const holdsTerm = async (path, terms) => {
|
|
167
|
+
const stream = createReadStream(path, { encoding: 'utf8' });
|
|
168
|
+
const lines = createInterface({ input: stream, crlfDelay: Infinity });
|
|
169
|
+
try {
|
|
170
|
+
for await (const line of lines) {
|
|
171
|
+
const lower = line.toLowerCase();
|
|
172
|
+
if (terms.some((term) => lower.includes(term)))
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
finally {
|
|
178
|
+
lines.close();
|
|
179
|
+
// `close` leaves the stream open, so a file that returned early on a hit would keep its descriptor.
|
|
180
|
+
stream.destroy();
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
const judge = async (terms, session) => {
|
|
184
|
+
if (terms.length === 0)
|
|
185
|
+
return { excluded: true, reason: 'no-terms', hits: [] };
|
|
186
|
+
const { files } = session;
|
|
187
|
+
const hits = [];
|
|
188
|
+
let unreadable = session.unreadable;
|
|
189
|
+
for (const file of files) {
|
|
190
|
+
try {
|
|
191
|
+
if (await holdsTerm(file.path, terms))
|
|
192
|
+
hits.push({ key: file.key, main: file.main, source: file.source });
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
unreadable = true;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
if (hits.length > 0)
|
|
199
|
+
return { excluded: true, reason: 'term', hits };
|
|
200
|
+
// Without the main transcript the session cannot be scanned in full, so it is excluded; its subagent files were scanned above, so a hit there counts.
|
|
201
|
+
if (!files.some((file) => file.main))
|
|
202
|
+
return { excluded: true, reason: unreadable ? 'unreadable' : 'no-raw', hits };
|
|
203
|
+
return unreadable ? { excluded: true, reason: 'unreadable', hits } : { excluded: false, reason: 'clean', hits };
|
|
204
|
+
};
|
|
205
|
+
/**
|
|
206
|
+
* When the newest file of a session was last written, in epoch milliseconds, over the same files the gate scans (the main transcript, its subagents, and oars' raw copies), or 0 when it has none.
|
|
207
|
+
* A transcript that kept growing since the last capture is newer here than in the store, so the deposit command reads it to tell a session still in progress from one that has ended.
|
|
208
|
+
*/
|
|
209
|
+
export const lastWritten = async (runtime, project, session) => {
|
|
210
|
+
const { files } = await filesOf(runtime, project, session);
|
|
211
|
+
const times = await Promise.all(files.map(async ({ path }) => (await stat(path)).mtimeMs));
|
|
212
|
+
return Math.max(0, ...times);
|
|
213
|
+
};
|
|
214
|
+
/** The A38 rule for one session; `extra` adds terms to the file's, but never stands in for it, so a missing or empty term file still excludes everything. */
|
|
215
|
+
export const checkSession = async (runtime, termsFile, project, session, extra = []) => {
|
|
216
|
+
const fromFile = await loadTerms(termsFile);
|
|
217
|
+
return judge(fromFile.length === 0 ? [] : [...fromFile, ...extra.map((term) => term.toLowerCase())], await filesOf(runtime, project, session));
|
|
218
|
+
};
|
|
219
|
+
/** The harness names a session folder with a UUID. */
|
|
220
|
+
const SESSION_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
221
|
+
/** A directory, or a link to one; a link that leads nowhere throws, so a folder that was not scanned never reads as clean. */
|
|
222
|
+
const leadsToDirectory = async (path, entry) => (entry.isSymbolicLink() ? (await stat(path)).isDirectory() : entry.isDirectory());
|
|
223
|
+
/** The sessions of one project folder: every entry named like a main file, and every entry named like a session folder. Other directories are returned apart. */
|
|
224
|
+
const sessionIds = async (root, project) => {
|
|
225
|
+
const ids = [];
|
|
226
|
+
const dirs = [];
|
|
227
|
+
for (const entry of await entriesOf(join(root, project))) {
|
|
228
|
+
// An entry named like a main file or a session folder is a session whatever it is: `filesIn` reads one of the wrong kind (a link that leads nowhere,
|
|
229
|
+
// a folder named `<id>.jsonl`, a plain file named `<id>`) as unreadable, so the scan lists it as the gate does.
|
|
230
|
+
if (entry.name.endsWith('.jsonl'))
|
|
231
|
+
ids.push(entry.name.slice(0, -'.jsonl'.length));
|
|
232
|
+
else if (SESSION_ID.test(entry.name) && !entry.isDirectory())
|
|
233
|
+
ids.push(entry.name);
|
|
234
|
+
else if (await leadsToDirectory(join(root, project, entry.name), entry))
|
|
235
|
+
dirs.push(entry.name);
|
|
236
|
+
}
|
|
237
|
+
return { ids: [...ids, ...dirs.filter((name) => SESSION_ID.test(name))], others: dirs.filter((name) => !SESSION_ID.test(name)) };
|
|
238
|
+
};
|
|
239
|
+
const emptyHits = () => ({ main: 0, subagent: 0 });
|
|
240
|
+
/** Scans every session in the harness's folders, the raw archive, and the store's own log. Returns counts and, apart from them, the excluded sessions. */
|
|
241
|
+
export const scanStore = async (runtime, termsFile) => {
|
|
242
|
+
const terms = await loadTerms(termsFile);
|
|
243
|
+
const raw = paths(runtime.storeDir).raw;
|
|
244
|
+
// A root that is a link leading nowhere, or to a file, lists as empty: stop with no list, naming no path.
|
|
245
|
+
for (const root of [runtime.projectsRoot, runtime.storeDir, raw]) {
|
|
246
|
+
if (await badLink(root, 'directory'))
|
|
247
|
+
throw new CliError('A folder that holds the projects is a link that could not be followed, so no list was written.', ExitCode.INVALID, 'unreadable');
|
|
248
|
+
}
|
|
249
|
+
// A store log that is a link leading nowhere would load as an empty store, so a logged session would drop out of the list: stop instead.
|
|
250
|
+
if (await badLink(paths(runtime.storeDir).observations, 'file'))
|
|
251
|
+
throw new CliError('The store log is a link that could not be followed, so no list was written.', ExitCode.INVALID, 'unreadable');
|
|
252
|
+
// A session the store logged stays listed when its raw is gone everywhere: it has nothing to scan, so it is excluded as no raw.
|
|
253
|
+
const logged = new Map();
|
|
254
|
+
for (const session of (await loadStore(runtime.storeDir)).sessions)
|
|
255
|
+
logged.set(session.project, (logged.get(session.project) ?? new Set()).add(session.session));
|
|
256
|
+
const projects = new Set(logged.keys());
|
|
257
|
+
for (const root of [runtime.projectsRoot, raw]) {
|
|
258
|
+
try {
|
|
259
|
+
for (const entry of await entriesOf(root)) {
|
|
260
|
+
// A project folder that is a link to a file was not scanned: stop, as for one that leads nowhere (`stat` throws).
|
|
261
|
+
if (entry.isSymbolicLink() && (await badLink(join(root, entry.name), 'directory')))
|
|
262
|
+
throw new Error('unreadable');
|
|
263
|
+
if (await leadsToDirectory(join(root, entry.name), entry))
|
|
264
|
+
projects.add(entry.name);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
catch {
|
|
268
|
+
throw new CliError('A folder that holds the projects could not be listed, so no list was written.', ExitCode.INVALID, 'unreadable');
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
const counts = {
|
|
272
|
+
sessions: 0,
|
|
273
|
+
excluded: 0,
|
|
274
|
+
kept: 0,
|
|
275
|
+
reasons: { term: 0, noRaw: 0, noTerms: 0, unreadable: 0 },
|
|
276
|
+
notSessions: 0,
|
|
277
|
+
hitFiles: emptyHits(),
|
|
278
|
+
bySource: { projects: { ...emptyHits(), sessions: 0 }, raw: { ...emptyHits(), sessions: 0 } },
|
|
279
|
+
folders: [],
|
|
280
|
+
};
|
|
281
|
+
const excluded = [];
|
|
282
|
+
const distinct = new Set();
|
|
283
|
+
for (const [index, project] of [...projects].sort().entries()) {
|
|
284
|
+
// The platform's message carries the path, and a folder name can carry the term: name the ordinal only.
|
|
285
|
+
const listed = async (root) => {
|
|
286
|
+
try {
|
|
287
|
+
return await sessionIds(root, project);
|
|
288
|
+
}
|
|
289
|
+
catch {
|
|
290
|
+
throw new CliError(`Project folder ${String(index + 1)} could not be listed, so it was not scanned and no list was written.`, ExitCode.INVALID, 'unreadable');
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
const inProjects = await listed(runtime.projectsRoot);
|
|
294
|
+
const inRaw = await listed(raw);
|
|
295
|
+
const ids = new Set([...inProjects.ids, ...inRaw.ids, ...(logged.get(project) ?? [])]);
|
|
296
|
+
counts.notSessions += new Set([...inProjects.others, ...inRaw.others].filter((name) => !ids.has(name))).size;
|
|
297
|
+
const folder = { folder: index + 1, sessions: 0, excluded: 0, hits: { projects: emptyHits(), raw: emptyHits() } };
|
|
298
|
+
for (const session of [...ids].sort()) {
|
|
299
|
+
const verdict = await judge(terms, await filesOf(runtime, project, session));
|
|
300
|
+
folder.sessions += 1;
|
|
301
|
+
const sources = new Set();
|
|
302
|
+
for (const hit of verdict.hits) {
|
|
303
|
+
const kind = hit.main ? 'main' : 'subagent';
|
|
304
|
+
counts.bySource[hit.source][kind] += 1;
|
|
305
|
+
folder.hits[hit.source][kind] += 1;
|
|
306
|
+
sources.add(hit.source);
|
|
307
|
+
const key = `${project}/${hit.key}`;
|
|
308
|
+
if (!distinct.has(key)) {
|
|
309
|
+
distinct.add(key);
|
|
310
|
+
counts.hitFiles[kind] += 1;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
for (const source of sources)
|
|
314
|
+
counts.bySource[source].sessions += 1;
|
|
315
|
+
if (!verdict.excluded)
|
|
316
|
+
continue;
|
|
317
|
+
folder.excluded += 1;
|
|
318
|
+
excluded.push({ project, session });
|
|
319
|
+
if (verdict.reason === 'term')
|
|
320
|
+
counts.reasons.term += 1;
|
|
321
|
+
else if (verdict.reason === 'no-raw')
|
|
322
|
+
counts.reasons.noRaw += 1;
|
|
323
|
+
else if (verdict.reason === 'no-terms')
|
|
324
|
+
counts.reasons.noTerms += 1;
|
|
325
|
+
else
|
|
326
|
+
counts.reasons.unreadable += 1;
|
|
327
|
+
}
|
|
328
|
+
counts.sessions += folder.sessions;
|
|
329
|
+
counts.excluded += folder.excluded;
|
|
330
|
+
counts.folders.push(folder);
|
|
331
|
+
}
|
|
332
|
+
counts.kept = counts.sessions - counts.excluded;
|
|
333
|
+
return { counts, excluded };
|
|
334
|
+
};
|
|
335
|
+
/** `oars sessions exclusions`: writes the excluded sessions to a local file and prints counts only. */
|
|
336
|
+
export const sessionsExclusions = async (runtime, where) => {
|
|
337
|
+
const { counts, excluded } = await scanStore(runtime, where.terms);
|
|
338
|
+
await mkdir(dirname(where.list), { recursive: true });
|
|
339
|
+
// The list holds real folder names, which can carry a term, so it is readable by its owner only (as the store's other local-only files are).
|
|
340
|
+
// Written through a temp file and renamed, as the store does, so a crash mid-write never leaves a short list that reads as complete.
|
|
341
|
+
const temp = `${where.list}.tmp`;
|
|
342
|
+
try {
|
|
343
|
+
await writeFile(temp, excluded.map((entry) => JSON.stringify(entry)).join('\n') + (excluded.length === 0 ? '' : '\n'), { mode: 0o600 });
|
|
344
|
+
await rename(temp, where.list);
|
|
345
|
+
}
|
|
346
|
+
catch (error) {
|
|
347
|
+
// The temp file holds real folder names, so a failed write or rename never leaves it behind.
|
|
348
|
+
await rm(temp, { force: true });
|
|
349
|
+
throw error;
|
|
350
|
+
}
|
|
351
|
+
// `mode` applies only when the file is created, so a list from an earlier run keeps its old one.
|
|
352
|
+
await chmod(where.list, 0o600);
|
|
353
|
+
const { projects, raw } = counts.bySource;
|
|
354
|
+
const lines = [
|
|
355
|
+
`Scanned ${String(counts.sessions)} sessions in ${String(counts.folders.length)} folders: ${String(counts.excluded)} excluded, ${String(counts.kept)} kept; ${String(counts.notSessions)} directories are not sessions.`,
|
|
356
|
+
`Excluded for a term: ${String(counts.reasons.term)}; no raw to scan: ${String(counts.reasons.noRaw)}; no term file: ${String(counts.reasons.noTerms)}; unreadable: ${String(counts.reasons.unreadable)}.`,
|
|
357
|
+
`Files holding a term: ${String(counts.hitFiles.main)} main, ${String(counts.hitFiles.subagent)} subagent (harness folders ${String(projects.main + projects.subagent)} in ${String(projects.sessions)} sessions, raw archive ${String(raw.main + raw.subagent)} in ${String(raw.sessions)} sessions).`,
|
|
358
|
+
'The excluded list is written to a local file outside the repository.',
|
|
359
|
+
];
|
|
360
|
+
return { document: { ok: true, ...counts }, text: lines.join('\n') };
|
|
361
|
+
};
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { type RepoSize } from './public-vocabulary.js';
|
|
2
|
+
import { type WordOfCommand } from './identity.js';
|
|
3
|
+
import type { Redactor } from './redact.js';
|
|
4
|
+
import { type ErrorLineObservation, type Harness, type PromptClass, type PromptOpened, type SessionObservation, type ShellIdentity, type ShellSegment, type Skeleton, type TargetBase, type ToolObservation, type ToolPull, type Usage, type UserPromptObservation } from './schema.js';
|
|
5
|
+
export interface Obj {
|
|
6
|
+
[key: string]: unknown;
|
|
7
|
+
}
|
|
8
|
+
export declare const isObj: (value: unknown) => value is Obj;
|
|
9
|
+
/** Python slices by code point; JavaScript by UTF-16 unit. */
|
|
10
|
+
export declare const cut: (text: string, length: number) => string;
|
|
11
|
+
export declare const EXPLORE: Set<string>;
|
|
12
|
+
export declare const ACT: Set<string>;
|
|
13
|
+
export declare const EDITS: Set<string>;
|
|
14
|
+
export declare const SHELL: Set<string>;
|
|
15
|
+
/** The normalized form of one line: paths, hex, numbers, and quoted strings replaced, cut at 110 characters. */
|
|
16
|
+
export declare const normalize: (line: string) => string;
|
|
17
|
+
/** The first informative line of a tool result: skips blanks, markup lines, "Only you see", and the `Exit code` line. */
|
|
18
|
+
export declare const firstInformative: (text: string) => string;
|
|
19
|
+
/**
|
|
20
|
+
* The key an error is matched on, by deposit and by recall alike: redact, take the first informative line, normalize, redact again. Redacting
|
|
21
|
+
* first keeps a secret or an email whole before normalize turns its digits into `<n>`; the second pass catches what only the cut form shows.
|
|
22
|
+
*/
|
|
23
|
+
export declare const errorKey: (text: string, redact: Redactor) => string;
|
|
24
|
+
/** The signature of an error: `<tool> | <errorKey>`. */
|
|
25
|
+
export declare const signatureOf: (tool: string, text: string, redact: Redactor) => string;
|
|
26
|
+
export declare const resultText: (block: Obj) => string;
|
|
27
|
+
export declare const isErrorResult: (block: Obj, text: string) => boolean;
|
|
28
|
+
/**
|
|
29
|
+
* The `errorCode` of a failed result (spec Section 1.2): an exit-code class from a leading `Exit code N` line, else the harness error class its text
|
|
30
|
+
* names, else an HTTP status, else `other`. The result is always a member of `ERROR_CODES`, picked by a table, so no word of the error text is kept. A
|
|
31
|
+
* harness that reports no exit code (a Cursor non-shell call, a Codex call with no status) reaches `other` unless its text names a class.
|
|
32
|
+
*/
|
|
33
|
+
export declare const errorCodeOf: (text: string) => string;
|
|
34
|
+
export declare const shortName: (name: string) => string;
|
|
35
|
+
export declare const compress: (sequence: readonly string[]) => string;
|
|
36
|
+
/** The prompt class rule shared by prompt_graphs.py and cost_per_class.py. */
|
|
37
|
+
export declare const classify: (sequence: readonly string[]) => PromptClass;
|
|
38
|
+
export declare const emptyUsage: () => Usage;
|
|
39
|
+
/** Where a subagent transcript came from: its `.meta.json` and the folder it sits in. */
|
|
40
|
+
export interface SubagentOrigin {
|
|
41
|
+
parent: string;
|
|
42
|
+
agentType: string | null;
|
|
43
|
+
spawnedBy: string | null;
|
|
44
|
+
/** The `n` of the `spawnedBy` call in the transcript that holds it, when the deposit could read it. */
|
|
45
|
+
spawnedAt?: number | null;
|
|
46
|
+
/** The session id of that transcript: the main session, or a sibling subagent's. */
|
|
47
|
+
spawnedIn?: string | null;
|
|
48
|
+
}
|
|
49
|
+
export interface ExtractInput {
|
|
50
|
+
text: string;
|
|
51
|
+
session: string;
|
|
52
|
+
project: string;
|
|
53
|
+
rev: number;
|
|
54
|
+
at: string;
|
|
55
|
+
bytes: number;
|
|
56
|
+
redact: Redactor;
|
|
57
|
+
/** The per-account salt for path targets; the store keeps it beside the log. */
|
|
58
|
+
salt?: string;
|
|
59
|
+
/** The salt's `sid` (first 8 hex of its SHA-256), stamped on every capture record; null when no salt is in play. */
|
|
60
|
+
sid?: string | null;
|
|
61
|
+
/** The repository root holding a working directory (worktree segments already stripped), or null; absent means no repository is known. */
|
|
62
|
+
repoRoot?: (cwd: string) => string | null;
|
|
63
|
+
/** The identity string of a repository root (`makeRepoIdentity`); absent means the lower-cased root path. */
|
|
64
|
+
repoIdentity?: (root: string) => string;
|
|
65
|
+
/** The oars package, Node.js, and OS this capture ran under; absent reads as `0.0.0` and this process. */
|
|
66
|
+
runtime?: {
|
|
67
|
+
oars: string;
|
|
68
|
+
node: string;
|
|
69
|
+
os: string;
|
|
70
|
+
};
|
|
71
|
+
/** The harness this transcript came from; absent means Claude Code. It names the frame and picks the action table. */
|
|
72
|
+
harness?: Harness;
|
|
73
|
+
/** How many files a repository root holds; absent reads the root's git index. */
|
|
74
|
+
repoFiles?: (root: string) => number | null;
|
|
75
|
+
/** The size bucket an earlier capture of this transcript read, null included; absent on the first capture, which reads it through `repoFiles`. */
|
|
76
|
+
repoSize?: RepoSize | null;
|
|
77
|
+
/** Set for a subagent transcript: every line is the agent's own line, so none is read as a sidechain. */
|
|
78
|
+
subagent?: SubagentOrigin;
|
|
79
|
+
}
|
|
80
|
+
/** What capture writes for one transcript, before any derivation. */
|
|
81
|
+
export interface Capture {
|
|
82
|
+
session: SessionObservation;
|
|
83
|
+
prompts: UserPromptObservation[];
|
|
84
|
+
tools: ToolObservation[];
|
|
85
|
+
errorLines: ErrorLineObservation[];
|
|
86
|
+
skeleton: Skeleton;
|
|
87
|
+
}
|
|
88
|
+
export declare const INJECTED_TAGS: ReadonlySet<string>;
|
|
89
|
+
export declare const LEADING_TAG: RegExp;
|
|
90
|
+
export interface PromptOpening {
|
|
91
|
+
opened: PromptOpened;
|
|
92
|
+
/** What the operator asked: the line as written, or a command's name and arguments. Not yet redacted. */
|
|
93
|
+
request: string;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Whether a main-line user line with text and no tool result opens a prompt in a Claude Code transcript, and how. Capture and the gold reader both number
|
|
97
|
+
* prompts by this, so a gold entry's index is a capture index. `lines` is the whole transcript and `position` this line's place in it.
|
|
98
|
+
*/
|
|
99
|
+
export declare const promptOpening: (text: string, lines: readonly string[], position: number, subagent?: boolean) => PromptOpening | undefined;
|
|
100
|
+
/**
|
|
101
|
+
* What a path is hashed as, and from where. Under the repository root of the line's `cwd` it is relative to that root, with a linked
|
|
102
|
+
* worktree's segment stripped, so one file reads alike from every subdirectory and worktree (base `repo`). A path no repository holds
|
|
103
|
+
* keeps the version-3 rule: relative to `cwd` when it lies under it, else as written (base `cwd`). A relative path is read from `cwd` first.
|
|
104
|
+
*/
|
|
105
|
+
export declare const pathKey: (path: string, cwd: string | undefined, root: string | null) => {
|
|
106
|
+
key: string;
|
|
107
|
+
base: TargetBase;
|
|
108
|
+
};
|
|
109
|
+
/** The per-account salted hash of a file path (16 hex characters, class A). */
|
|
110
|
+
export declare const hashPath: (path: string, cwd: string | undefined, root: string | null, salt: string) => {
|
|
111
|
+
hash: string;
|
|
112
|
+
base: TargetBase;
|
|
113
|
+
key: string;
|
|
114
|
+
};
|
|
115
|
+
/**
|
|
116
|
+
* A shell call's program and subcommand from its first line (`git push`, `just build`, `npm`), as recovery_episodes.py's
|
|
117
|
+
* `shell_target` computes it, so a retry of `git push` after a failed `git push` is the same target and `git status` is not.
|
|
118
|
+
* The program is a basename, lowercased, without `.exe`; the subcommand is the first later token that is not a flag, a path, or
|
|
119
|
+
* a variable, cut to 30 characters, and there is none when a separator comes first. Empty when the line holds no command.
|
|
120
|
+
*/
|
|
121
|
+
export declare const shellTarget: (command: string) => string;
|
|
122
|
+
type ShellWord = WordOfCommand;
|
|
123
|
+
type ShellLink = 'and' | 'pipe' | 'break' | 'end';
|
|
124
|
+
/** One simple command, after its prelude. */
|
|
125
|
+
export interface ShellCommand {
|
|
126
|
+
program: string;
|
|
127
|
+
args: ShellWord[];
|
|
128
|
+
read: boolean;
|
|
129
|
+
/** How the next command joins this one. */
|
|
130
|
+
link: ShellLink;
|
|
131
|
+
/** A pipe follows the group this command sits in, so its status is hidden whatever its own link says. */
|
|
132
|
+
hidden: boolean;
|
|
133
|
+
}
|
|
134
|
+
/** One command of a shell call, as its step reads. `run` compares a retry and is not kept. */
|
|
135
|
+
export interface ShellStepRead {
|
|
136
|
+
identity: ShellIdentity;
|
|
137
|
+
read: boolean;
|
|
138
|
+
run: string;
|
|
139
|
+
}
|
|
140
|
+
/** One `git commit` a command holds. */
|
|
141
|
+
export interface PlannedCommit {
|
|
142
|
+
quiet: boolean;
|
|
143
|
+
/** A later command of the same list reads this commit back, so the result can name its sha. */
|
|
144
|
+
readBack: boolean;
|
|
145
|
+
}
|
|
146
|
+
export interface ShellRead {
|
|
147
|
+
segments: ShellSegment[];
|
|
148
|
+
/** 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
|
+
steps: ShellStepRead[];
|
|
150
|
+
pulls: ToolPull[];
|
|
151
|
+
commitPlan: PlannedCommit[];
|
|
152
|
+
}
|
|
153
|
+
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[];
|
|
156
|
+
/** 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;
|
|
158
|
+
/**
|
|
159
|
+
* 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
|
+
* 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`.
|
|
161
|
+
*/
|
|
162
|
+
export declare const commitsRead: (plan: readonly PlannedCommit[], result: string) => Array<string | null>;
|
|
163
|
+
/**
|
|
164
|
+
* The tool name as a record stores it. An MCP name is `mcp__<server>__<tool>` (Cursor: `mcp_<server>_<tool>`) and the server comes from the
|
|
165
|
+
* operator's config, where a token can sit, so everything after the prefix is redacted wherever the name is kept (the call, its error line,
|
|
166
|
+
* the skeleton line, and the session's count).
|
|
167
|
+
*/
|
|
168
|
+
export declare const storedToolName: (name: string, redact: Redactor) => string;
|
|
169
|
+
/** An MCP call's full server name, which `shortName` cuts for the tool sequence, redacted because a harness takes it from the operator's config. */
|
|
170
|
+
export declare const pullsOfTool: (name: string, redact: Redactor) => ToolPull[];
|
|
171
|
+
/** The `target` of a tool call (see {@link ToolObservation.target}). A path target also returns the `key` it was hashed on, read for its class and then dropped, never stored. */
|
|
172
|
+
export declare const targetOf: (tool: string, input: Obj, cwd: string | undefined, salt: string, redact: Redactor, root?: string | null) => {
|
|
173
|
+
target: string | null;
|
|
174
|
+
base: TargetBase | null;
|
|
175
|
+
key?: string;
|
|
176
|
+
};
|
|
177
|
+
/** The `n` capture gives each call of a transcript, by tool-use id, the first copy of a repeated id winning; a subagent's steps name their parent by it without capturing the parent again. */
|
|
178
|
+
export declare const callPositions: (text: string) => Map<string, number>;
|
|
179
|
+
/** One pass over a transcript into its capture records and skeleton. Never throws on a malformed line: it counts it and goes on. */
|
|
180
|
+
export declare const captureSession: (input: ExtractInput) => Capture;
|
|
181
|
+
export {};
|