@bjoernrochel/sevro 0.1.0-rc.7 → 1.0.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/CONTRIBUTING.md +22 -6
- package/README.md +5 -1
- package/docs/README.md +19 -15
- package/docs/architecture.md +138 -17
- package/docs/assets/README.md +5 -4
- package/docs/builtin-graders-v1.md +17 -3
- package/docs/codex-host.md +155 -49
- package/docs/contracts.md +2 -1
- package/docs/creating-cases.md +9 -2
- package/docs/development-cli.md +187 -19
- package/docs/documentation-quality.md +12 -2
- package/docs/evaluation-workflow.md +110 -0
- package/docs/extension-protocol-v1.md +26 -3
- package/docs/getting-started.md +23 -2
- package/docs/guide-evaluation.md +33 -1
- package/docs/identity-v1.md +25 -2
- package/docs/installing.md +11 -11
- package/docs/native-controls.md +76 -5
- package/docs/native-hosts.md +196 -7
- package/docs/reading-results.md +102 -0
- package/docs/recipes.md +489 -0
- package/docs/releases.md +31 -12
- package/docs/report-v1.md +29 -1
- package/docs/results-v1.md +141 -4
- package/docs/run-settings-v1.md +147 -0
- package/docs/runtime-v1.md +307 -18
- package/docs/troubleshooting.md +12 -12
- package/docs/typescript-quality.md +89 -26
- package/examples/recipes/candidate.ts +26 -0
- package/examples/recipes/cases/coding-change.json +21 -0
- package/examples/recipes/cases/host-tools.json +21 -0
- package/examples/recipes/cases/mise.json +21 -0
- package/examples/recipes/cases/node.json +21 -0
- package/examples/recipes/cases/python-uv.json +21 -0
- package/examples/recipes/cases/semantic.json +17 -0
- package/examples/recipes/coding-change/check.ts +23 -0
- package/examples/recipes/extension-artifacts.ts +65 -0
- package/examples/recipes/extension-methods.ts +174 -0
- package/examples/recipes/extension.ts +37 -0
- package/examples/recipes/repository-skill/.agents/skills/startup-guide/SKILL.md +7 -0
- package/examples/recipes/repository-skill/.claude/skills/startup-guide/SKILL.md +7 -0
- package/examples/recipes/repository-skill/README.md +3 -0
- package/examples/recipes/run-inputs.ts +188 -0
- package/examples/recipes/run-options.ts +131 -0
- package/examples/recipes/run.ts +63 -0
- package/examples/recipes/runtime/empty.json +1 -0
- package/examples/recipes/runtime/goals.json +1 -0
- package/examples/recipes/runtime/mise.json +6 -0
- package/examples/recipes/runtime/path.json +1 -0
- package/examples/recipes/runtime/plugins.json +4 -0
- package/examples/recipes/runtime/python-uv.json +10 -0
- package/examples/recipes/semantic-judge.ts +29 -0
- package/examples/templates/coding/adapter.ts +17 -0
- package/examples/templates/coding/case.json +15 -0
- package/examples/templates/repository-skill/.agents/skills/startup-guide/SKILL.md +6 -0
- package/examples/templates/repository-skill/extension-data.ts +64 -0
- package/examples/templates/repository-skill/extension.ts +31 -0
- package/examples/templates/response/adapter.ts +12 -0
- package/examples/templates/response/case.json +13 -0
- package/package.json +11 -5
- package/schemas/report-v1.schema.json +9 -0
- package/schemas/run-evidence-v1.schema.json +2 -1
- package/schemas/runtime-v1.schema.json +63 -7
- package/src/advisory.ts +2 -2
- package/src/candidate-artifacts.ts +206 -0
- package/src/candidate-codex-state.ts +185 -0
- package/src/case-validation.ts +73 -50
- package/src/cli/cancellation.ts +20 -0
- package/src/cli/command.ts +87 -0
- package/src/cli/completion.ts +15 -0
- package/src/cli/demo.ts +60 -0
- package/src/cli/evaluation.ts +55 -0
- package/src/cli/extension.ts +79 -0
- package/src/cli/help.ts +198 -0
- package/src/cli/hosts.ts +55 -0
- package/src/cli/init-settings.ts +30 -0
- package/src/cli/init.ts +111 -0
- package/src/{cli-invocation.ts → cli/invocation.ts} +214 -218
- package/src/cli/loading.ts +151 -0
- package/src/cli/preparation.ts +154 -0
- package/src/cli/presentation.ts +93 -0
- package/src/cli/progress.ts +6 -0
- package/src/cli/run-manifest.ts +128 -0
- package/src/cli/run-options.ts +403 -0
- package/src/cli/run-overrides.ts +69 -0
- package/src/cli/settings.ts +154 -0
- package/src/cli/summary.ts +43 -0
- package/src/cli.ts +13 -591
- package/src/contract-types.ts +94 -0
- package/src/engine.ts +13 -5
- package/src/evaluation/advisory.ts +150 -0
- package/src/{evaluation-check-plan.ts → evaluation/check-plan.ts} +103 -67
- package/src/evaluation/collection.ts +88 -0
- package/src/evaluation/context.ts +10 -0
- package/src/{evaluation-grading-host.ts → evaluation/grading-host.ts} +5 -5
- package/src/evaluation/grading.ts +235 -0
- package/src/{evaluation-invocation-plan.ts → evaluation/invocation-plan.ts} +147 -58
- package/src/{evaluation-plan.ts → evaluation/plan.ts} +208 -141
- package/src/evaluation/progress.ts +19 -0
- package/src/evaluation/resources.ts +241 -0
- package/src/evaluation/run.ts +206 -0
- package/src/{evaluation-semantic.ts → evaluation/semantic.ts} +133 -115
- package/src/evaluation/trial.ts +800 -0
- package/src/evaluation/types.ts +207 -0
- package/src/evidence/host-results.ts +85 -0
- package/src/evidence/retained-artifacts.ts +18 -0
- package/src/evidence/trial-writer.ts +203 -0
- package/src/evidence/usage.ts +17 -0
- package/src/extension-client.ts +34 -96
- package/src/extension-session.ts +24 -48
- package/src/{advisory-fixture.ts → fixtures/advisory.ts} +15 -76
- package/src/{fixture-bin.ts → fixtures/bin.ts} +1 -1
- package/src/{fixture-cleanup.ts → fixtures/cleanup.ts} +13 -7
- package/src/fixtures/create.ts +183 -0
- package/src/{generated-fixture.ts → fixtures/generated.ts} +10 -53
- package/src/{git-hooks.ts → fixtures/git-hooks.ts} +1 -1
- package/src/{preparation.ts → fixtures/preparation.ts} +6 -16
- package/src/{repository-fixture.ts → fixtures/repository.ts} +30 -64
- package/src/{fixture-setup.ts → fixtures/setup.ts} +33 -51
- package/src/{fixture-tools.ts → fixtures/tools.ts} +1 -1
- package/src/fixtures/workspace-state.ts +212 -0
- package/src/generated/cli-result.d.cts +2 -1
- package/src/generated/extension.d.cts +2 -1
- package/src/generated/report.cjs +1 -1
- package/src/generated/report.d.cts +2 -1
- package/src/generated/run-evidence.cjs +1 -1
- package/src/generated/run-evidence.d.cts +2 -1
- package/src/generated/runtime.cjs +1 -1
- package/src/generated/runtime.d.cts +2 -1
- package/src/git.ts +93 -0
- package/src/graders/git-head.ts +29 -84
- package/src/graders/shell.ts +57 -68
- package/src/host-diagnostic-redaction.ts +52 -0
- package/src/host-diagnostics.ts +331 -0
- package/src/hosts/claude-continuation.ts +57 -40
- package/src/hosts/claude-credential.ts +160 -63
- package/src/hosts/claude-events.ts +34 -18
- package/src/hosts/claude-keychain.ts +78 -21
- package/src/hosts/claude-login-recovery.ts +85 -0
- package/src/hosts/claude-login-refresh.ts +178 -0
- package/src/hosts/claude-login-store.ts +162 -0
- package/src/hosts/claude-login.ts +107 -0
- package/src/hosts/claude-native-goal.ts +15 -12
- package/src/hosts/claude-process.ts +10 -50
- package/src/hosts/claude-refresh-lock.ts +81 -0
- package/src/hosts/claude-repository-invocation.ts +9 -34
- package/src/hosts/claude-settings.ts +91 -14
- package/src/hosts/claude-skill-activations.ts +169 -0
- package/src/hosts/claude-state.ts +46 -16
- package/src/hosts/claude-tool-calls.ts +13 -10
- package/src/hosts/claude.ts +122 -73
- package/src/hosts/codex-app-server.ts +78 -52
- package/src/hosts/codex-configuration.ts +5 -10
- package/src/hosts/codex-exec-controls.ts +516 -0
- package/src/hosts/codex-marketplace.ts +12 -10
- package/src/hosts/codex-native-calls.ts +15 -122
- package/src/hosts/codex-process.ts +10 -54
- package/src/hosts/codex-profile.ts +50 -21
- package/src/hosts/codex-session-files.ts +1 -9
- package/src/hosts/codex-state.ts +141 -19
- package/src/hosts/codex.ts +221 -179
- package/src/hosts/isolation-common.ts +15 -11
- package/src/hosts/isolation-roots.ts +13 -20
- package/src/hosts/linux-sandbox.ts +27 -3
- package/src/hosts/mac-sandbox.ts +111 -18
- package/src/hosts/native-controls.ts +72 -22
- package/src/hosts/native-request.ts +55 -0
- package/src/hosts/process-lifecycle.ts +221 -45
- package/src/hosts/protected-worktrees.ts +1 -1
- package/src/hosts/skill-hook-catalog.ts +131 -0
- package/src/hosts/skill-hook-handler.ts +374 -0
- package/src/hosts/skill-hooks.ts +577 -0
- package/src/instrumentation.ts +7 -8
- package/src/native-transcript-state.ts +22 -3
- package/src/native-transcript-view.ts +52 -13
- package/src/native-transcripts.ts +34 -15
- package/src/paths.ts +22 -0
- package/src/process-lifecycle.ts +150 -0
- package/src/provenance.ts +27 -7
- package/src/prune.ts +300 -0
- package/src/report.ts +63 -36
- package/src/resolved-case.ts +1 -1
- package/src/results-lock.ts +42 -0
- package/src/results.ts +14 -18
- package/src/run-owner.ts +4 -4
- package/src/{runtime-config.ts → runtime/config.ts} +182 -44
- package/src/{runtime-discovery-apple.ts → runtime/discovery-apple.ts} +3 -3
- package/src/{runtime-discovery-providers.ts → runtime/discovery-providers.ts} +3 -9
- package/src/{runtime-discovery.ts → runtime/discovery.ts} +4 -4
- package/src/{runtime-hook-processes.ts → runtime/hook-processes.ts} +1 -1
- package/src/{runtime-hooks.ts → runtime/hooks.ts} +33 -22
- package/src/{runtime-paths.ts → runtime/paths.ts} +6 -18
- package/src/{runtime-query.ts → runtime/query.ts} +5 -5
- package/src/{runtime-seeds.ts → runtime/seeds.ts} +3 -3
- package/src/runtime/shared-cache.ts +130 -0
- package/src/{runtime-state.ts → runtime/state.ts} +28 -11
- package/src/schema-types.ts +73 -161
- package/src/shell.ts +4 -0
- package/src/transcript-retention.ts +146 -0
- package/src/value-guards.ts +22 -0
- package/docs/assets/sevro-logo.png +0 -0
- package/docs/assets/sevro-symbol.svg +0 -1
- package/docs/assets/sevro-wordmark.svg +0 -1
- package/docs/cache-ownership-validation.md +0 -49
- package/docs/claude-continuation-validation.md +0 -47
- package/docs/concurrency-validation.md +0 -86
- package/docs/configuration-root-validation.md +0 -70
- package/docs/engine-slice.md +0 -114
- package/docs/evidence/runtime-startup-30/README.md +0 -185
- package/docs/evidence/runtime-startup-30/after.json +0 -319
- package/docs/evidence/runtime-startup-30/baseline-timeout.jsonl +0 -2
- package/docs/evidence/runtime-startup-30/before.json +0 -403
- package/docs/evidence/runtime-startup-30/consumer-smoke.json +0 -63
- package/docs/evidence/runtime-startup-30/full-policy-diagnostic.jsonl +0 -3
- package/docs/evidence/runtime-startup-30/minimal-policy-diagnostic.jsonl +0 -3
- package/docs/evidence/runtime-startup-30/plugin-template-assessment.json +0 -20
- package/docs/evidence/runtime-startup-30/seedless-timeout.jsonl +0 -2
- package/docs/evidence.md +0 -17
- package/docs/fixture-cleanup-validation.md +0 -70
- package/docs/guide-verification.json +0 -239
- package/src/evaluation-context.ts +0 -257
- package/src/evaluation-fixture.ts +0 -313
- package/src/evaluation-run.ts +0 -198
- package/src/evaluation-trial.ts +0 -946
- package/src/evaluation-types.ts +0 -149
- package/src/hosts/claude-nested-skills.ts +0 -272
- package/src/hosts/codex-command-output.ts +0 -510
- package/src/hosts/codex-executor-command.ts +0 -291
- package/src/hosts/codex-skill-reads.ts +0 -609
- /package/src/{trial-scheduler.ts → evaluation/scheduler.ts} +0 -0
|
@@ -1,609 +0,0 @@
|
|
|
1
|
-
import { isRecord, isUnknownArray } from "../value-guards";
|
|
2
|
-
import type { RecoveredCommandOutput } from "./codex-command-output";
|
|
3
|
-
import { nativeCommandOutputs } from "./codex-command-output";
|
|
4
|
-
import { readFile, realpath } from "node:fs/promises";
|
|
5
|
-
import { isAbsolute, relative, resolve, sep } from "node:path";
|
|
6
|
-
|
|
7
|
-
const MAX_SKILL_BYTES = 1024 * 1024;
|
|
8
|
-
const MAX_NATIVE_READ_ATTEMPTS = 64;
|
|
9
|
-
const SKILL_PATH = /^\.agents\/skills\/([A-Za-z0-9._-]+)\/SKILL\.md$/;
|
|
10
|
-
const PLUGIN_SKILL_PATH = /^skills\/([A-Za-z0-9._-]+)\/SKILL\.md$/;
|
|
11
|
-
|
|
12
|
-
interface CodexItem {
|
|
13
|
-
id?: unknown;
|
|
14
|
-
type?: unknown;
|
|
15
|
-
command?: unknown;
|
|
16
|
-
status?: unknown;
|
|
17
|
-
exit_code?: unknown;
|
|
18
|
-
aggregated_output?: unknown;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
interface CodexEvent {
|
|
22
|
-
type?: unknown;
|
|
23
|
-
item?: CodexItem;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function shellPayload(command: string): string {
|
|
27
|
-
let payload = command;
|
|
28
|
-
for (let depth = 0; depth < 2; depth++) {
|
|
29
|
-
const wrapper = payload.match(
|
|
30
|
-
/^(?:\/bin\/(?:ba|z)?sh\s+-l?c|lean-ctx\s+-c)\s+(["'])([\s\S]*)\1$/,
|
|
31
|
-
);
|
|
32
|
-
if (!wrapper) break;
|
|
33
|
-
payload = wrapper.slice(2, 3).join("");
|
|
34
|
-
}
|
|
35
|
-
return payload;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
interface DirectRead {
|
|
39
|
-
path: string;
|
|
40
|
-
firstLine?: number;
|
|
41
|
-
lastLine?: number;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function capturedReadPath(match: RegExpMatchArray, first: number): string {
|
|
45
|
-
return match
|
|
46
|
-
.slice(first, first + 3)
|
|
47
|
-
.filter(Boolean)
|
|
48
|
-
.join("");
|
|
49
|
-
}
|
|
50
|
-
function validReadLines(firstLine: number, lastLine: number): boolean {
|
|
51
|
-
return (
|
|
52
|
-
Number.isSafeInteger(firstLine) &&
|
|
53
|
-
firstLine >= 1 &&
|
|
54
|
-
Number.isSafeInteger(lastLine) &&
|
|
55
|
-
lastLine >= firstLine
|
|
56
|
-
);
|
|
57
|
-
}
|
|
58
|
-
function sedRead(payload: string, path: string): DirectRead | null {
|
|
59
|
-
const sed = payload.match(
|
|
60
|
-
new RegExp(`^(?:/bin/)?sed\\s+-n\\s+['"]?(\\d+),(\\d+)p['"]?\\s+${path}$`),
|
|
61
|
-
);
|
|
62
|
-
if (!sed) return null;
|
|
63
|
-
const firstLine = Number(sed[1]),
|
|
64
|
-
lastLine = Number(sed[2]);
|
|
65
|
-
if (!validReadLines(firstLine, lastLine)) return null;
|
|
66
|
-
return { path: capturedReadPath(sed, 3), firstLine, lastLine };
|
|
67
|
-
}
|
|
68
|
-
function directRead(command: string): DirectRead | null {
|
|
69
|
-
const payload = shellPayload(command).trim();
|
|
70
|
-
const path = "(?:\"([^\"]+)\"|'([^']+)'|([^\\s'\"`$;&|<>]+))";
|
|
71
|
-
return catRead(payload, path) ?? sedRead(payload, path);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function catRead(payload: string, path: string): DirectRead | null {
|
|
75
|
-
const cat = payload.match(
|
|
76
|
-
new RegExp(`^(?:/bin/)?cat\\s+(?:--\\s+)?(${path}(?:\\s+${path})*)$`),
|
|
77
|
-
);
|
|
78
|
-
if (!cat) return null;
|
|
79
|
-
const paths = [
|
|
80
|
-
...cat.slice(1, 2).join("").matchAll(new RegExp(path, "g")),
|
|
81
|
-
].map((match) => capturedReadPath(match, 1));
|
|
82
|
-
const skills = paths.filter((value) => /(?:^|\/)SKILL\.md$/.test(value));
|
|
83
|
-
if (skills.length !== 1 || paths.length > MAX_NATIVE_READ_ATTEMPTS)
|
|
84
|
-
return null;
|
|
85
|
-
return { path: skills.join("") };
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function readRange(body: string, read: DirectRead): [number, number] {
|
|
89
|
-
if (read.firstLine === undefined || read.lastLine === undefined)
|
|
90
|
-
return [0, body.length];
|
|
91
|
-
const lines = body.match(/[^\n]*\n|[^\n]+$/g) ?? [];
|
|
92
|
-
const start = lines.slice(0, read.firstLine - 1).join("").length;
|
|
93
|
-
const end = lines.slice(0, read.lastLine).join("").length;
|
|
94
|
-
return [start, end];
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
type VerifiedRead = {
|
|
98
|
-
skill: string;
|
|
99
|
-
path: string;
|
|
100
|
-
range: [number, number];
|
|
101
|
-
bodyLength: number;
|
|
102
|
-
};
|
|
103
|
-
function readDeclaration(
|
|
104
|
-
item: CodexItem,
|
|
105
|
-
): { read: DirectRead; output: string } | null {
|
|
106
|
-
if (typeof item.command !== "string") return null;
|
|
107
|
-
const read = directRead(item.command);
|
|
108
|
-
if (!read || typeof item.aggregated_output !== "string") return null;
|
|
109
|
-
return { read, output: item.aggregated_output };
|
|
110
|
-
}
|
|
111
|
-
async function verifiedSkillRead(
|
|
112
|
-
item: CodexItem,
|
|
113
|
-
workspace: string,
|
|
114
|
-
pluginRoots: string[],
|
|
115
|
-
commandCwd = workspace,
|
|
116
|
-
): Promise<VerifiedRead | null> {
|
|
117
|
-
const declaration = readDeclaration(item);
|
|
118
|
-
if (!declaration) return null;
|
|
119
|
-
return resolvedSkillRead(declaration, workspace, pluginRoots, commandCwd);
|
|
120
|
-
}
|
|
121
|
-
function mountedSkillName(
|
|
122
|
-
actual: string,
|
|
123
|
-
workspace: string,
|
|
124
|
-
pluginRoots: string[],
|
|
125
|
-
): string | null {
|
|
126
|
-
const mounted = relative(workspace, actual)
|
|
127
|
-
.split(sep)
|
|
128
|
-
.join("/")
|
|
129
|
-
.match(SKILL_PATH);
|
|
130
|
-
const plugin = pluginRoots
|
|
131
|
-
.map((root) =>
|
|
132
|
-
relative(root, actual).split(sep).join("/").match(PLUGIN_SKILL_PATH),
|
|
133
|
-
)
|
|
134
|
-
.find((match) => match !== null);
|
|
135
|
-
const match = mounted ?? plugin;
|
|
136
|
-
return match ? match.slice(1, 2).join("") : null;
|
|
137
|
-
}
|
|
138
|
-
async function verifiedSkillBody(
|
|
139
|
-
actual: string,
|
|
140
|
-
skill: string,
|
|
141
|
-
): Promise<string | null> {
|
|
142
|
-
const body = await readFile(actual).catch(() => null);
|
|
143
|
-
if (!body || body.byteLength > MAX_SKILL_BYTES) return null;
|
|
144
|
-
const text = body.toString("utf8");
|
|
145
|
-
if (!text.startsWith("---\n") || !text.includes(`\nname: ${skill}\n`))
|
|
146
|
-
return null;
|
|
147
|
-
return text;
|
|
148
|
-
}
|
|
149
|
-
async function resolvedSkillRead(
|
|
150
|
-
declaration: { read: DirectRead; output: string },
|
|
151
|
-
workspace: string,
|
|
152
|
-
pluginRoots: string[],
|
|
153
|
-
commandCwd: string,
|
|
154
|
-
): Promise<VerifiedRead | null> {
|
|
155
|
-
const actual = await realpath(
|
|
156
|
-
resolve(commandCwd, declaration.read.path),
|
|
157
|
-
).catch(() => null);
|
|
158
|
-
if (!actual) return null;
|
|
159
|
-
const skill = mountedSkillName(actual, workspace, pluginRoots);
|
|
160
|
-
if (skill === null) return null;
|
|
161
|
-
const text = await verifiedSkillBody(actual, skill);
|
|
162
|
-
if (text === null) return null;
|
|
163
|
-
return skillReadReceipt(actual, skill, declaration, text);
|
|
164
|
-
}
|
|
165
|
-
function skillReadReceipt(
|
|
166
|
-
actual: string,
|
|
167
|
-
skill: string,
|
|
168
|
-
declaration: { read: DirectRead; output: string },
|
|
169
|
-
text: string,
|
|
170
|
-
): VerifiedRead | null {
|
|
171
|
-
const range = readRange(text, declaration.read);
|
|
172
|
-
if (range[0] === range[1]) return null;
|
|
173
|
-
if (!declaration.output.includes(text.slice(...range))) return null;
|
|
174
|
-
return { skill, path: actual, range, bodyLength: text.length };
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
function fullCoverage(
|
|
178
|
-
ranges: Array<[number, number]>,
|
|
179
|
-
length: number,
|
|
180
|
-
): boolean {
|
|
181
|
-
const ordered = ranges.sort((left, right) => left[0] - right[0]);
|
|
182
|
-
let covered = 0;
|
|
183
|
-
for (const [start, end] of ordered) {
|
|
184
|
-
if (start > covered) return false;
|
|
185
|
-
covered = Math.max(covered, end);
|
|
186
|
-
}
|
|
187
|
-
return covered === length;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
interface NativeEntry {
|
|
191
|
-
ordinal: number;
|
|
192
|
-
payload: Record<string, unknown>;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
export interface NativeReadDiagnostic {
|
|
196
|
-
completeness: "complete" | "partial";
|
|
197
|
-
observedSkills: string[];
|
|
198
|
-
completedReads: Array<{ skill: string; ordinal: number }>;
|
|
199
|
-
recoverySources: Array<{
|
|
200
|
-
ordinal: number;
|
|
201
|
-
nativeOutput: boolean;
|
|
202
|
-
yieldedChunks: number;
|
|
203
|
-
completedCall: boolean;
|
|
204
|
-
literalCommandCall: boolean;
|
|
205
|
-
}>;
|
|
206
|
-
commandExecutions: number;
|
|
207
|
-
readAttempts: number;
|
|
208
|
-
truncated: boolean;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
function nativeShellArguments(value: string[]): boolean {
|
|
212
|
-
return value.length >= 3 && (value[1] === "-lc" || value[1] === "-c");
|
|
213
|
-
}
|
|
214
|
-
function nativeCommandText(value: unknown): string | null {
|
|
215
|
-
if (typeof value === "string") return value;
|
|
216
|
-
if (
|
|
217
|
-
!isUnknownArray(value) ||
|
|
218
|
-
!value.every((part): part is string => typeof part === "string")
|
|
219
|
-
)
|
|
220
|
-
return null;
|
|
221
|
-
return nativeShellArguments(value)
|
|
222
|
-
? value.slice(2).join(" ")
|
|
223
|
-
: value.join(" ");
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
type Coverage = { ranges: Array<[number, number]>; bodyLength: number };
|
|
227
|
-
interface CoverageState {
|
|
228
|
-
attempted: Map<string, Coverage>;
|
|
229
|
-
observedSkills: string[];
|
|
230
|
-
}
|
|
231
|
-
interface NativeReadState extends CoverageState {
|
|
232
|
-
completedReads: NativeReadDiagnostic["completedReads"];
|
|
233
|
-
recoverySources: NativeReadDiagnostic["recoverySources"];
|
|
234
|
-
commandExecutions: number;
|
|
235
|
-
readAttempts: number;
|
|
236
|
-
partial: boolean;
|
|
237
|
-
truncated: boolean;
|
|
238
|
-
}
|
|
239
|
-
interface ReadContext {
|
|
240
|
-
workspace: string;
|
|
241
|
-
pluginRoots: string[];
|
|
242
|
-
recovered: Map<number, RecoveredCommandOutput>;
|
|
243
|
-
}
|
|
244
|
-
function nativeReadState(): NativeReadState {
|
|
245
|
-
return {
|
|
246
|
-
attempted: new Map(),
|
|
247
|
-
observedSkills: [],
|
|
248
|
-
completedReads: [],
|
|
249
|
-
recoverySources: [],
|
|
250
|
-
commandExecutions: 0,
|
|
251
|
-
readAttempts: 0,
|
|
252
|
-
partial: false,
|
|
253
|
-
truncated: false,
|
|
254
|
-
};
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
/** Verify native skill-file reads without retaining commands or output. */
|
|
258
|
-
export async function codexNativeReadDiagnostic(
|
|
259
|
-
entries: NativeEntry[],
|
|
260
|
-
workspace: string,
|
|
261
|
-
installedPluginRoots: string[] = [],
|
|
262
|
-
): Promise<NativeReadDiagnostic> {
|
|
263
|
-
const canonicalWorkspace = await realpath(workspace);
|
|
264
|
-
const pluginRoots = await Promise.all(
|
|
265
|
-
installedPluginRoots.map((root) => realpath(root)),
|
|
266
|
-
);
|
|
267
|
-
const context: ReadContext = {
|
|
268
|
-
workspace: canonicalWorkspace,
|
|
269
|
-
pluginRoots,
|
|
270
|
-
recovered: nativeCommandOutputs(entries),
|
|
271
|
-
};
|
|
272
|
-
const state = nativeReadState();
|
|
273
|
-
for (const entry of entries)
|
|
274
|
-
if (!(await nativeReadEntry(entry, context, state))) break;
|
|
275
|
-
return nativeReadResult(state);
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
function nativeCommandItem(
|
|
279
|
-
payload: Record<string, unknown>,
|
|
280
|
-
): (CodexItem & { cwd?: unknown }) | null {
|
|
281
|
-
if (
|
|
282
|
-
payload.type !== "item_completed" ||
|
|
283
|
-
!isRecord(payload.item) ||
|
|
284
|
-
payload.item.type !== "CommandExecution"
|
|
285
|
-
)
|
|
286
|
-
return null;
|
|
287
|
-
return payload.item;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
async function nativeReadEntry(
|
|
291
|
-
{ ordinal, payload }: NativeEntry,
|
|
292
|
-
context: ReadContext,
|
|
293
|
-
state: NativeReadState,
|
|
294
|
-
): Promise<boolean> {
|
|
295
|
-
const item = nativeCommandItem(payload);
|
|
296
|
-
if (!item) return true;
|
|
297
|
-
state.commandExecutions++;
|
|
298
|
-
const command = nativeCommandText(item.command);
|
|
299
|
-
if (!command) {
|
|
300
|
-
state.partial = true;
|
|
301
|
-
return true;
|
|
302
|
-
}
|
|
303
|
-
return processNativeRead(item, command, ordinal, context, state);
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
function countNativeRead(state: NativeReadState): boolean {
|
|
307
|
-
state.readAttempts++;
|
|
308
|
-
if (state.readAttempts <= MAX_NATIVE_READ_ATTEMPTS) return true;
|
|
309
|
-
state.readAttempts = MAX_NATIVE_READ_ATTEMPTS;
|
|
310
|
-
state.truncated = true;
|
|
311
|
-
state.partial = true;
|
|
312
|
-
return false;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
async function processNativeRead(
|
|
316
|
-
item: CodexItem & { cwd?: unknown },
|
|
317
|
-
command: string,
|
|
318
|
-
ordinal: number,
|
|
319
|
-
context: ReadContext,
|
|
320
|
-
state: NativeReadState,
|
|
321
|
-
): Promise<boolean> {
|
|
322
|
-
if (!command.includes("SKILL.md")) return true;
|
|
323
|
-
if (!countNativeRead(state)) return false;
|
|
324
|
-
const recovered = context.recovered.get(ordinal);
|
|
325
|
-
state.recoverySources.push({
|
|
326
|
-
ordinal,
|
|
327
|
-
nativeOutput: hasNativeOutput(item),
|
|
328
|
-
...recoveryFlags(recovered),
|
|
329
|
-
});
|
|
330
|
-
await completeNativeRead(item, command, ordinal, context, state);
|
|
331
|
-
return true;
|
|
332
|
-
}
|
|
333
|
-
function hasNativeOutput(item: CodexItem): boolean {
|
|
334
|
-
return (
|
|
335
|
-
typeof item.aggregated_output === "string" &&
|
|
336
|
-
item.aggregated_output.length > 0
|
|
337
|
-
);
|
|
338
|
-
}
|
|
339
|
-
function recoveryFlags(recovered: RecoveredCommandOutput | undefined) {
|
|
340
|
-
return {
|
|
341
|
-
yieldedChunks: recovered?.chunks ?? 0,
|
|
342
|
-
completedCall: recovered?.completedCall === true,
|
|
343
|
-
literalCommandCall: recovered?.literalCommandCall === true,
|
|
344
|
-
};
|
|
345
|
-
}
|
|
346
|
-
function successfulReadCommand(item: CodexItem): boolean {
|
|
347
|
-
return typeof item.exit_code === "number" && item.status === "completed";
|
|
348
|
-
}
|
|
349
|
-
async function nativeReadCwd(
|
|
350
|
-
cwd: unknown,
|
|
351
|
-
workspace: string,
|
|
352
|
-
): Promise<string | null> {
|
|
353
|
-
if (cwd === undefined) return workspace;
|
|
354
|
-
if (typeof cwd !== "string" || !isAbsolute(cwd)) return null;
|
|
355
|
-
return realpath(cwd).catch(() => null);
|
|
356
|
-
}
|
|
357
|
-
function nativeOutput(item: CodexItem): string {
|
|
358
|
-
return typeof item.aggregated_output === "string"
|
|
359
|
-
? item.aggregated_output
|
|
360
|
-
: "";
|
|
361
|
-
}
|
|
362
|
-
function combinedNativeOutput(
|
|
363
|
-
item: CodexItem,
|
|
364
|
-
recovered: RecoveredCommandOutput | undefined,
|
|
365
|
-
): string {
|
|
366
|
-
return (recovered?.output ?? "") + nativeOutput(item);
|
|
367
|
-
}
|
|
368
|
-
async function completeNativeRead(
|
|
369
|
-
item: CodexItem & { cwd?: unknown },
|
|
370
|
-
command: string,
|
|
371
|
-
ordinal: number,
|
|
372
|
-
context: ReadContext,
|
|
373
|
-
state: NativeReadState,
|
|
374
|
-
): Promise<void> {
|
|
375
|
-
if (!successfulReadCommand(item)) {
|
|
376
|
-
state.partial = true;
|
|
377
|
-
return;
|
|
378
|
-
}
|
|
379
|
-
const cwd = await nativeReadCwd(item.cwd, context.workspace);
|
|
380
|
-
if (!cwd) {
|
|
381
|
-
state.partial = true;
|
|
382
|
-
return;
|
|
383
|
-
}
|
|
384
|
-
const read = await verifiedSkillRead(
|
|
385
|
-
{
|
|
386
|
-
...item,
|
|
387
|
-
command,
|
|
388
|
-
aggregated_output: combinedNativeOutput(
|
|
389
|
-
item,
|
|
390
|
-
context.recovered.get(ordinal),
|
|
391
|
-
),
|
|
392
|
-
},
|
|
393
|
-
context.workspace,
|
|
394
|
-
context.pluginRoots,
|
|
395
|
-
cwd,
|
|
396
|
-
);
|
|
397
|
-
if (!read) {
|
|
398
|
-
state.partial = true;
|
|
399
|
-
return;
|
|
400
|
-
}
|
|
401
|
-
retainNativeRead(state, read, ordinal);
|
|
402
|
-
}
|
|
403
|
-
function continuingNativeCoverage(
|
|
404
|
-
state: NativeReadState,
|
|
405
|
-
read: VerifiedRead,
|
|
406
|
-
): Coverage {
|
|
407
|
-
const prior = state.attempted.get(read.path);
|
|
408
|
-
return prior && !fullCoverage(prior.ranges, prior.bodyLength)
|
|
409
|
-
? prior
|
|
410
|
-
: { ranges: [], bodyLength: read.bodyLength };
|
|
411
|
-
}
|
|
412
|
-
function appendReadCoverage(
|
|
413
|
-
state: CoverageState,
|
|
414
|
-
read: VerifiedRead,
|
|
415
|
-
coverage: Coverage,
|
|
416
|
-
): boolean {
|
|
417
|
-
coverage.ranges.push(read.range);
|
|
418
|
-
state.attempted.set(read.path, coverage);
|
|
419
|
-
return fullCoverage(coverage.ranges, coverage.bodyLength);
|
|
420
|
-
}
|
|
421
|
-
function retainNativeRead(
|
|
422
|
-
state: NativeReadState,
|
|
423
|
-
read: VerifiedRead,
|
|
424
|
-
ordinal: number,
|
|
425
|
-
): void {
|
|
426
|
-
const coverage = continuingNativeCoverage(state, read);
|
|
427
|
-
if (appendReadCoverage(state, read, coverage)) {
|
|
428
|
-
state.completedReads.push({ skill: read.skill, ordinal });
|
|
429
|
-
if (!state.observedSkills.includes(read.skill))
|
|
430
|
-
state.observedSkills.push(read.skill);
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
function completeCoverage(state: CoverageState): boolean {
|
|
434
|
-
return [...state.attempted.values()].every(({ ranges, bodyLength }) =>
|
|
435
|
-
fullCoverage(ranges, bodyLength),
|
|
436
|
-
);
|
|
437
|
-
}
|
|
438
|
-
function nativeReadResult(state: NativeReadState): NativeReadDiagnostic {
|
|
439
|
-
return {
|
|
440
|
-
completeness:
|
|
441
|
-
!state.partial && completeCoverage(state) ? "complete" : "partial",
|
|
442
|
-
observedSkills: state.observedSkills,
|
|
443
|
-
completedReads: state.completedReads,
|
|
444
|
-
recoverySources: state.recoverySources,
|
|
445
|
-
commandExecutions: state.commandExecutions,
|
|
446
|
-
readAttempts: state.readAttempts,
|
|
447
|
-
truncated: state.truncated,
|
|
448
|
-
};
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
interface StreamReadState extends CoverageState {
|
|
452
|
-
pending: Set<string>;
|
|
453
|
-
partial: boolean;
|
|
454
|
-
completedTurn: boolean;
|
|
455
|
-
}
|
|
456
|
-
type RecoveredRead = { command: string; output: string };
|
|
457
|
-
function streamReadState(): StreamReadState {
|
|
458
|
-
return {
|
|
459
|
-
attempted: new Map(),
|
|
460
|
-
observedSkills: [],
|
|
461
|
-
pending: new Set(),
|
|
462
|
-
partial: false,
|
|
463
|
-
completedTurn: false,
|
|
464
|
-
};
|
|
465
|
-
}
|
|
466
|
-
function readEvent(line: string): CodexEvent {
|
|
467
|
-
const value: unknown = JSON.parse(line);
|
|
468
|
-
if (!isRecord(value)) throw new Error("Invalid Codex skill-read event");
|
|
469
|
-
return {
|
|
470
|
-
type: value.type,
|
|
471
|
-
item: isRecord(value.item) ? value.item : undefined,
|
|
472
|
-
};
|
|
473
|
-
}
|
|
474
|
-
function recoveredReadItem(
|
|
475
|
-
item: CodexItem | undefined,
|
|
476
|
-
recoveredReads: Map<string, RecoveredRead>,
|
|
477
|
-
): CodexItem | undefined {
|
|
478
|
-
if (typeof item?.id !== "string") return item;
|
|
479
|
-
return correlatedReadItem(item, recoveredReads.get(item.id));
|
|
480
|
-
}
|
|
481
|
-
function correlatedReadItem(
|
|
482
|
-
item: CodexItem,
|
|
483
|
-
recovered: RecoveredRead | undefined,
|
|
484
|
-
): CodexItem {
|
|
485
|
-
if (
|
|
486
|
-
!recovered ||
|
|
487
|
-
typeof item.command !== "string" ||
|
|
488
|
-
shellPayload(item.command) !== shellPayload(recovered.command)
|
|
489
|
-
)
|
|
490
|
-
return item;
|
|
491
|
-
return { ...item, aggregated_output: recovered.output + nativeOutput(item) };
|
|
492
|
-
}
|
|
493
|
-
function observeStreamTurn(event: CodexEvent, state: StreamReadState): void {
|
|
494
|
-
if (event.type === "turn.completed") state.completedTurn = true;
|
|
495
|
-
if (event.type === "turn.failed") state.partial = true;
|
|
496
|
-
}
|
|
497
|
-
async function streamReadEvent(
|
|
498
|
-
event: CodexEvent,
|
|
499
|
-
item: CodexItem | undefined,
|
|
500
|
-
context: Pick<ReadContext, "workspace" | "pluginRoots">,
|
|
501
|
-
state: StreamReadState,
|
|
502
|
-
): Promise<void> {
|
|
503
|
-
observeStreamTurn(event, state);
|
|
504
|
-
if (!item || item.type !== "command_execution") return;
|
|
505
|
-
if (event.type === "item.started") {
|
|
506
|
-
pendingStreamRead(item, state);
|
|
507
|
-
return;
|
|
508
|
-
}
|
|
509
|
-
if (event.type !== "item.completed") return;
|
|
510
|
-
await completeStreamRead(item, context, state);
|
|
511
|
-
}
|
|
512
|
-
function pendingStreamRead(item: CodexItem, state: StreamReadState): void {
|
|
513
|
-
if (typeof item.command === "string" && item.command.includes("SKILL.md")) {
|
|
514
|
-
if (typeof item.id === "string") state.pending.add(item.id);
|
|
515
|
-
else state.partial = true;
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
function eligibleCompletedRead(
|
|
519
|
-
item: CodexItem,
|
|
520
|
-
state: StreamReadState,
|
|
521
|
-
): boolean {
|
|
522
|
-
if (typeof item.id === "string") state.pending.delete(item.id);
|
|
523
|
-
if (typeof item.command !== "string") {
|
|
524
|
-
state.partial = true;
|
|
525
|
-
return false;
|
|
526
|
-
}
|
|
527
|
-
if (!item.command.includes("SKILL.md")) return false;
|
|
528
|
-
if (!successfulReadCommand(item)) {
|
|
529
|
-
state.partial = true;
|
|
530
|
-
return false;
|
|
531
|
-
}
|
|
532
|
-
return true;
|
|
533
|
-
}
|
|
534
|
-
async function completeStreamRead(
|
|
535
|
-
item: CodexItem,
|
|
536
|
-
context: Pick<ReadContext, "workspace" | "pluginRoots">,
|
|
537
|
-
state: StreamReadState,
|
|
538
|
-
): Promise<void> {
|
|
539
|
-
if (!eligibleCompletedRead(item, state)) return;
|
|
540
|
-
const read = await verifiedSkillRead(
|
|
541
|
-
item,
|
|
542
|
-
context.workspace,
|
|
543
|
-
context.pluginRoots,
|
|
544
|
-
);
|
|
545
|
-
if (!read) {
|
|
546
|
-
state.partial = true;
|
|
547
|
-
return;
|
|
548
|
-
}
|
|
549
|
-
const coverage = state.attempted.get(read.path) ?? {
|
|
550
|
-
ranges: [],
|
|
551
|
-
bodyLength: read.bodyLength,
|
|
552
|
-
};
|
|
553
|
-
retainStreamRead(state, read, coverage);
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
function retainStreamRead(
|
|
557
|
-
state: StreamReadState,
|
|
558
|
-
read: VerifiedRead,
|
|
559
|
-
coverage: Coverage,
|
|
560
|
-
): void {
|
|
561
|
-
if (
|
|
562
|
-
appendReadCoverage(state, read, coverage) &&
|
|
563
|
-
!state.observedSkills.includes(read.skill)
|
|
564
|
-
)
|
|
565
|
-
state.observedSkills.push(read.skill);
|
|
566
|
-
}
|
|
567
|
-
function streamReadCompleteness(
|
|
568
|
-
state: StreamReadState,
|
|
569
|
-
): "complete" | "partial" {
|
|
570
|
-
return state.completedTurn &&
|
|
571
|
-
!state.partial &&
|
|
572
|
-
state.pending.size === 0 &&
|
|
573
|
-
completeCoverage(state)
|
|
574
|
-
? "complete"
|
|
575
|
-
: "partial";
|
|
576
|
-
}
|
|
577
|
-
function streamReadResult(state: StreamReadState) {
|
|
578
|
-
return {
|
|
579
|
-
id: "sevro.codex.skill-reads" as const,
|
|
580
|
-
completeness: streamReadCompleteness(state),
|
|
581
|
-
data: {
|
|
582
|
-
method: "skill_file_read_probe",
|
|
583
|
-
primarySkill: state.observedSkills[0] ?? null,
|
|
584
|
-
observedSkills: state.observedSkills,
|
|
585
|
-
},
|
|
586
|
-
};
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
/** Retain only ordered mounted-skill names, never commands or skill bodies. */
|
|
590
|
-
export async function codexSkillReadObservation(
|
|
591
|
-
stream: string,
|
|
592
|
-
workspace: string,
|
|
593
|
-
installedPluginRoots: string[] = [],
|
|
594
|
-
recoveredReads: Map<string, RecoveredRead> = new Map(),
|
|
595
|
-
) {
|
|
596
|
-
const state = streamReadState();
|
|
597
|
-
const canonicalWorkspace = await realpath(workspace);
|
|
598
|
-
const pluginRoots = await Promise.all(
|
|
599
|
-
installedPluginRoots.map((root) => realpath(root)),
|
|
600
|
-
);
|
|
601
|
-
const context = { workspace: canonicalWorkspace, pluginRoots };
|
|
602
|
-
for (const line of stream.split("\n")) {
|
|
603
|
-
if (!line.trim()) continue;
|
|
604
|
-
const event = readEvent(line);
|
|
605
|
-
const item = recoveredReadItem(event.item, recoveredReads);
|
|
606
|
-
await streamReadEvent(event, item, context, state);
|
|
607
|
-
}
|
|
608
|
-
return streamReadResult(state);
|
|
609
|
-
}
|
|
File without changes
|