@bjoernrochel/sevro 0.1.0-rc.2 → 0.1.0-rc.21
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 +170 -0
- package/LICENSE +63 -136
- package/README.md +83 -66
- package/docs/README.md +44 -0
- package/docs/acknowledgements.md +14 -0
- package/docs/architecture.md +146 -0
- package/docs/assets/README.md +27 -0
- package/docs/assets/sevro-logo.png +0 -0
- package/docs/assets/sevro-symbol.svg +1 -0
- package/docs/assets/sevro-wordmark.svg +1 -0
- package/docs/builtin-graders-v1.md +41 -13
- package/docs/codex-host.md +184 -52
- package/docs/contracts.md +19 -0
- package/docs/creating-cases.md +139 -0
- package/docs/development-cli.md +190 -30
- package/docs/documentation-quality.md +61 -0
- package/docs/evaluation-workflow.md +110 -0
- package/docs/evidence/code-mode-controls-71.md +64 -0
- package/docs/evidence/code-mode-programs-85.md +64 -0
- package/docs/evidence/codex-state-73/README.md +204 -0
- package/docs/evidence/codex-state-73/darrow-migration.patch +182 -0
- package/docs/evidence/codex-state-73/results.json +661 -0
- package/docs/evidence/local-http-74.md +104 -0
- package/docs/evidence/runtime-startup-30/README.md +185 -0
- package/docs/evidence/runtime-startup-30/after.json +319 -0
- package/docs/evidence/runtime-startup-30/baseline-timeout.jsonl +2 -0
- package/docs/evidence/runtime-startup-30/before.json +403 -0
- package/docs/evidence/runtime-startup-30/consumer-smoke.json +63 -0
- package/docs/evidence/runtime-startup-30/full-policy-diagnostic.jsonl +3 -0
- package/docs/evidence/runtime-startup-30/minimal-policy-diagnostic.jsonl +3 -0
- package/docs/evidence/runtime-startup-30/plugin-template-assessment.json +20 -0
- package/docs/evidence/runtime-startup-30/seedless-timeout.jsonl +2 -0
- package/docs/evidence/skill-reads-70/results.json +823 -0
- package/docs/evidence.md +21 -0
- package/docs/extension-protocol-v1.md +50 -16
- package/docs/fixture-cleanup-validation.md +3 -0
- package/docs/getting-started.md +102 -0
- package/docs/guide-evaluation.md +89 -0
- package/docs/guide-verification.json +239 -0
- package/docs/identity-v1.md +35 -1
- package/docs/installing.md +88 -0
- package/docs/licenses/BUSL-1.1-0.1.0-rc.2.txt +159 -0
- package/docs/licensing.md +42 -0
- package/docs/native-controls.md +76 -5
- package/docs/native-hosts.md +228 -0
- package/docs/reading-results.md +141 -0
- package/docs/recipes.md +489 -0
- package/docs/releases.md +81 -40
- package/docs/report-v1.md +29 -1
- package/docs/results-v1.md +180 -0
- package/docs/run-settings-v1.md +147 -0
- package/docs/running-evaluations.md +31 -0
- package/docs/runtime-v1.md +614 -0
- package/docs/specs/repository-guide.md +58 -0
- package/docs/troubleshooting.md +30 -0
- package/docs/typescript-quality.md +234 -0
- package/docs/usability-study.md +114 -0
- package/examples/basic/host.ts +2 -2
- 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 +39 -6
- package/schemas/report-v1.schema.json +9 -0
- package/schemas/run-evidence-v1.schema.json +2 -1
- package/schemas/runtime-v1.schema.json +193 -0
- package/src/advisory.ts +10 -5
- package/src/candidate-artifacts.ts +208 -0
- package/src/candidate-codex-state.ts +185 -0
- package/src/case-validation.ts +105 -0
- package/src/cli/cancellation.ts +20 -0
- package/src/cli/command.ts +81 -0
- package/src/cli/completion.ts +15 -0
- package/src/cli/demo.ts +59 -0
- package/src/cli/evaluation.ts +55 -0
- package/src/cli/extension.ts +79 -0
- package/src/cli/help.ts +222 -0
- package/src/cli/hosts.ts +59 -0
- package/src/cli/init-settings.ts +30 -0
- package/src/cli/init.ts +111 -0
- package/src/cli/invocation.ts +627 -0
- package/src/cli/loading.ts +151 -0
- package/src/cli/preparation.ts +163 -0
- package/src/cli/presentation.ts +91 -0
- package/src/cli/progress.ts +6 -0
- package/src/cli/run-manifest.ts +133 -0
- package/src/cli/run-options.ts +101 -0
- package/src/cli/run-overrides.ts +83 -0
- package/src/cli/settings.ts +160 -0
- package/src/cli/summary.ts +43 -0
- package/src/cli.ts +13 -843
- package/src/contract-types.ts +94 -0
- package/src/engine.ts +19 -2373
- package/src/evaluation/advisory.ts +150 -0
- package/src/evaluation/check-plan.ts +510 -0
- package/src/evaluation/collection.ts +88 -0
- package/src/evaluation/context.ts +10 -0
- package/src/evaluation/grading-host.ts +39 -0
- package/src/evaluation/grading.ts +258 -0
- package/src/evaluation/invocation-plan.ts +548 -0
- package/src/evaluation/plan.ts +736 -0
- package/src/evaluation/progress.ts +19 -0
- package/src/evaluation/resources.ts +241 -0
- package/src/evaluation/run.ts +206 -0
- package/src/{trial-scheduler.ts → evaluation/scheduler.ts} +3 -1
- package/src/evaluation/semantic.ts +312 -0
- package/src/evaluation/trial.ts +866 -0
- package/src/evaluation/types.ts +207 -0
- package/src/evidence/host-results.ts +92 -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 +228 -148
- package/src/extension-session.ts +326 -195
- package/src/fixtures/advisory.ts +338 -0
- package/src/fixtures/bin.ts +57 -0
- package/src/{fixture-cleanup.ts → fixtures/cleanup.ts} +13 -7
- package/src/fixtures/create.ts +187 -0
- package/src/fixtures/generated.ts +300 -0
- package/src/fixtures/git-hooks.ts +24 -0
- package/src/fixtures/preparation.ts +270 -0
- package/src/{repository-fixture.ts → fixtures/repository.ts} +207 -85
- package/src/fixtures/setup.ts +224 -0
- package/src/fixtures/tools.ts +88 -0
- package/src/fixtures/workspace-state.ts +212 -0
- package/src/generated/cli-result.cjs +2 -0
- package/src/generated/cli-result.d.cts +3 -0
- package/src/generated/extension.cjs +2 -0
- package/src/generated/extension.d.cts +3 -0
- package/src/generated/report.cjs +2 -0
- package/src/generated/report.d.cts +3 -0
- package/src/generated/run-evidence.cjs +2 -0
- package/src/generated/run-evidence.d.cts +3 -0
- package/src/generated/runtime.cjs +2 -0
- package/src/generated/runtime.d.cts +3 -0
- package/src/graders/artifact.ts +56 -23
- package/src/graders/git-head.ts +79 -75
- package/src/graders/output.ts +222 -140
- package/src/graders/semantic.ts +102 -43
- package/src/graders/shell.ts +379 -207
- package/src/host-diagnostic-redaction.ts +52 -0
- package/src/host-diagnostics.ts +319 -0
- package/src/hosts/claude-continuation.ts +63 -18
- package/src/hosts/claude-credential.ts +43 -62
- package/src/hosts/claude-events.ts +86 -51
- package/src/hosts/claude-keychain.ts +59 -0
- package/src/hosts/claude-native-goal.ts +57 -33
- package/src/hosts/claude-process.ts +111 -0
- package/src/hosts/claude-repository-invocation.ts +208 -117
- package/src/hosts/claude-settings.ts +240 -21
- package/src/hosts/claude-skill-activations.ts +166 -0
- package/src/hosts/claude-state.ts +403 -0
- package/src/hosts/claude-tool-calls.ts +171 -106
- package/src/hosts/claude.ts +561 -451
- package/src/hosts/codex-app-server.ts +326 -178
- package/src/hosts/codex-configuration.ts +44 -18
- package/src/hosts/codex-events.ts +97 -60
- package/src/hosts/codex-exec-controls.ts +516 -0
- package/src/hosts/codex-invocation.ts +70 -0
- package/src/hosts/codex-marketplace.ts +283 -0
- package/src/hosts/codex-native-calls.ts +546 -500
- package/src/hosts/codex-process.ts +202 -0
- package/src/hosts/codex-profile.ts +257 -28
- package/src/hosts/codex-session-files.ts +106 -0
- package/src/hosts/codex-state.ts +492 -0
- package/src/hosts/codex.ts +783 -832
- package/src/hosts/isolation-common.ts +94 -0
- package/src/hosts/isolation-roots.ts +54 -27
- package/src/hosts/isolation.ts +10 -0
- package/src/hosts/linux-sandbox.ts +159 -0
- package/src/hosts/mac-sandbox.ts +187 -61
- package/src/hosts/native-controls.ts +152 -62
- package/src/hosts/process-lifecycle.ts +37 -0
- package/src/hosts/protected-worktrees.ts +51 -19
- package/src/hosts/skill-hook-catalog.ts +131 -0
- package/src/hosts/skill-hook-handler.ts +374 -0
- package/src/hosts/skill-hooks.ts +580 -0
- package/src/hosts/workspace-fingerprint.ts +55 -35
- package/src/identity.ts +102 -48
- package/src/instrumentation.ts +109 -48
- package/src/native-transcript-state.ts +131 -0
- package/src/native-transcript-view.ts +90 -0
- package/src/native-transcripts.ts +286 -0
- package/src/process-lifecycle.ts +150 -0
- package/src/provenance.ts +161 -81
- package/src/prune.ts +215 -0
- package/src/report.ts +172 -140
- package/src/resolved-case.ts +33 -0
- package/src/results-lock.ts +29 -0
- package/src/results.ts +133 -86
- package/src/run-owner.ts +154 -63
- package/src/runtime/config.ts +456 -0
- package/src/runtime/discovery-apple.ts +90 -0
- package/src/runtime/discovery-providers.ts +63 -0
- package/src/runtime/discovery.ts +70 -0
- package/src/runtime/hook-processes.ts +97 -0
- package/src/runtime/hooks.ts +476 -0
- package/src/runtime/paths.ts +313 -0
- package/src/runtime/query.ts +85 -0
- package/src/runtime/seeds.ts +134 -0
- package/src/runtime/shared-cache.ts +143 -0
- package/src/runtime/state.ts +143 -0
- package/src/schema-types.ts +198 -0
- package/src/schema.ts +15 -19
- package/src/transcript-retention.ts +146 -0
- package/src/value-guards.ts +20 -0
- package/src/advisory-fixture.ts +0 -282
- package/src/fixture-bin.ts +0 -87
- package/src/fixture-setup.ts +0 -163
- package/src/generated-fixture.ts +0 -220
- package/src/git-hooks.ts +0 -63
- package/src/hosts/claude-nested-skills.ts +0 -218
- package/src/hosts/codex-command-output.ts +0 -399
- package/src/hosts/codex-executor-command.ts +0 -195
- package/src/hosts/codex-skill-reads.ts +0 -391
- package/src/preparation.ts +0 -211
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import type { Stats } from "node:fs";
|
|
1
2
|
import { lstat, readFile, realpath } from "node:fs/promises";
|
|
2
3
|
import { join } from "node:path";
|
|
3
4
|
import type { HostAdapter } from "../engine";
|
|
5
|
+
import { isRecord } from "../value-guards";
|
|
4
6
|
import type { ClaudeToolCallsObservation } from "./claude-tool-calls";
|
|
5
7
|
|
|
6
8
|
type Entry = Record<string, unknown>;
|
|
@@ -11,27 +13,23 @@ export interface ClaudeRepositoryInvocation {
|
|
|
11
13
|
prompt: string;
|
|
12
14
|
}
|
|
13
15
|
|
|
14
|
-
function record(value: unknown): value is Entry {
|
|
15
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
16
|
function entries(text: string): Entry[] {
|
|
19
17
|
return text
|
|
20
18
|
.split(/\r?\n/)
|
|
21
19
|
.filter((line) => line.trim())
|
|
22
20
|
.map((line) => {
|
|
23
21
|
const value: unknown = JSON.parse(line);
|
|
24
|
-
if (!
|
|
22
|
+
if (!isRecord(value)) throw new Error("invalid Claude session entry");
|
|
25
23
|
return value;
|
|
26
24
|
});
|
|
27
25
|
}
|
|
28
26
|
|
|
29
27
|
function content(entry: Entry): string {
|
|
30
|
-
const blocks =
|
|
28
|
+
const blocks = isRecord(entry.message) ? entry.message.content : null;
|
|
31
29
|
if (typeof blocks === "string") return blocks;
|
|
32
30
|
if (!Array.isArray(blocks)) return "";
|
|
33
31
|
return blocks
|
|
34
|
-
.filter(
|
|
32
|
+
.filter(isRecord)
|
|
35
33
|
.filter((block) => block.type === "text")
|
|
36
34
|
.map((block) => (typeof block.text === "string" ? block.text : ""))
|
|
37
35
|
.join("\n");
|
|
@@ -42,18 +40,9 @@ export async function verifyClaudeRepositoryInvocation(
|
|
|
42
40
|
request: Parameters<HostAdapter["run"]>[0],
|
|
43
41
|
): Promise<ClaudeRepositoryInvocation> {
|
|
44
42
|
const selected = request.explicitSkillInvocation;
|
|
45
|
-
|
|
46
|
-
throw new Error("Claude repository invocation is not declared");
|
|
43
|
+
requireRepositorySelection(selected);
|
|
47
44
|
const { skillName, token } = selected;
|
|
48
|
-
if (
|
|
49
|
-
!/^[A-Za-z0-9._-]+$/.test(skillName) ||
|
|
50
|
-
[".", ".."].includes(skillName) ||
|
|
51
|
-
token !== `/${skillName}` ||
|
|
52
|
-
request.prompt.split(token).length - 1 !== 1 ||
|
|
53
|
-
(request.prompt !== token &&
|
|
54
|
-
!request.prompt.startsWith(token + " ") &&
|
|
55
|
-
!request.prompt.startsWith(token + "\n"))
|
|
56
|
-
)
|
|
45
|
+
if (!validRepositoryDeclaration(skillName, token, request.prompt))
|
|
57
46
|
throw new Error("invalid Claude repository skill invocation");
|
|
58
47
|
let path = await realpath(request.workspace);
|
|
59
48
|
for (const [index, part] of [
|
|
@@ -64,13 +53,7 @@ export async function verifyClaudeRepositoryInvocation(
|
|
|
64
53
|
].entries()) {
|
|
65
54
|
path = join(path, part);
|
|
66
55
|
const entry = await lstat(path).catch(() => null);
|
|
67
|
-
if (
|
|
68
|
-
!entry ||
|
|
69
|
-
entry.isSymbolicLink() ||
|
|
70
|
-
(index === 3
|
|
71
|
-
? !entry.isFile() || entry.size > 1024 * 1024
|
|
72
|
-
: !entry.isDirectory())
|
|
73
|
-
)
|
|
56
|
+
if (!safeSkillEntry(entry, index))
|
|
74
57
|
throw new Error(
|
|
75
58
|
"invoked Claude repository skill is unavailable or unsafe",
|
|
76
59
|
);
|
|
@@ -83,76 +66,159 @@ export async function verifyClaudeRepositoryInvocation(
|
|
|
83
66
|
};
|
|
84
67
|
}
|
|
85
68
|
|
|
69
|
+
function requireRepositorySelection(
|
|
70
|
+
selected: Parameters<HostAdapter["run"]>[0]["explicitSkillInvocation"],
|
|
71
|
+
): asserts selected is Extract<
|
|
72
|
+
NonNullable<Parameters<HostAdapter["run"]>[0]["explicitSkillInvocation"]>,
|
|
73
|
+
{ scope: "repository" }
|
|
74
|
+
> {
|
|
75
|
+
if (!selected || selected.scope !== "repository")
|
|
76
|
+
throw new Error("Claude repository invocation is not declared");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
type Receipt = { accepted: boolean | null; reason: string };
|
|
80
|
+
type MatchInput = ClaudeRepositoryInvocation & {
|
|
81
|
+
transcript: string;
|
|
82
|
+
sessionId: string;
|
|
83
|
+
};
|
|
84
|
+
type AssistantBoundary = { before: Entry[] } | { receipt: Receipt };
|
|
85
|
+
type CommandBoundary = { called: Entry } | { receipt: Receipt };
|
|
86
|
+
|
|
87
|
+
function validRepositoryDeclaration(
|
|
88
|
+
skillName: string,
|
|
89
|
+
token: string,
|
|
90
|
+
prompt: string,
|
|
91
|
+
): boolean {
|
|
92
|
+
return (
|
|
93
|
+
validRepositorySkillName(skillName) &&
|
|
94
|
+
token === `/${skillName}` &&
|
|
95
|
+
prompt.split(token).length - 1 === 1 &&
|
|
96
|
+
repositoryPromptMatches(prompt, token)
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
function validRepositorySkillName(name: string): boolean {
|
|
100
|
+
return /^[A-Za-z0-9._-]+$/.test(name) && ![".", ".."].includes(name);
|
|
101
|
+
}
|
|
102
|
+
function repositoryPromptMatches(prompt: string, token: string): boolean {
|
|
103
|
+
return (
|
|
104
|
+
prompt === token ||
|
|
105
|
+
prompt.startsWith(token + " ") ||
|
|
106
|
+
prompt.startsWith(token + "\n")
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
function safeSkillEntry(entry: Stats | null, index: number): boolean {
|
|
110
|
+
if (!entry || entry.isSymbolicLink()) return false;
|
|
111
|
+
return index === 3
|
|
112
|
+
? entry.isFile() && entry.size <= 1024 * 1024
|
|
113
|
+
: entry.isDirectory();
|
|
114
|
+
}
|
|
115
|
+
|
|
86
116
|
/** Match one exact command and complete body before the assistant starts. */
|
|
87
|
-
export function matchClaudeRepositoryInvocation(
|
|
88
|
-
input: ClaudeRepositoryInvocation & {
|
|
89
|
-
transcript: string;
|
|
90
|
-
sessionId: string;
|
|
91
|
-
},
|
|
92
|
-
): { accepted: boolean | null; reason: string } {
|
|
117
|
+
export function matchClaudeRepositoryInvocation(input: MatchInput): Receipt {
|
|
93
118
|
let native: Entry[];
|
|
94
119
|
try {
|
|
95
120
|
native = entries(input.transcript);
|
|
96
121
|
} catch {
|
|
97
122
|
return { accepted: null, reason: "malformed native session" };
|
|
98
123
|
}
|
|
124
|
+
const boundary = assistantBoundary(native, input.sessionId);
|
|
125
|
+
if ("receipt" in boundary) return boundary.receipt;
|
|
126
|
+
return matchCommandAndBody(boundary.before, input);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function assistantBoundary(
|
|
130
|
+
native: Entry[],
|
|
131
|
+
sessionId: string,
|
|
132
|
+
): AssistantBoundary {
|
|
99
133
|
const firstAssistant = native.findIndex(
|
|
100
134
|
(entry) => entry.type === "assistant",
|
|
101
135
|
);
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
if (assistant.sessionId !==
|
|
106
|
-
return {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
)
|
|
114
|
-
return { accepted: false, reason: "prompt differs from mounted command" };
|
|
136
|
+
const assistant = native.at(firstAssistant);
|
|
137
|
+
if (firstAssistant < 0 || assistant === undefined)
|
|
138
|
+
return { receipt: { accepted: null, reason: "missing assistant turn" } };
|
|
139
|
+
if (assistant.sessionId !== sessionId || assistant.isSidechain === true)
|
|
140
|
+
return {
|
|
141
|
+
receipt: { accepted: false, reason: "assistant session differs" },
|
|
142
|
+
};
|
|
143
|
+
return { before: native.slice(0, firstAssistant) };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function nativeCommand(input: MatchInput, token: string): string {
|
|
115
147
|
const args = input.prompt.slice(token.length).trim();
|
|
116
|
-
|
|
148
|
+
return (
|
|
117
149
|
`<command-message>${input.skillName}</command-message>\n<command-name>${token}</command-name>` +
|
|
118
|
-
(args ? `\n<command-args>${args}</command-args>` : "")
|
|
150
|
+
(args ? `\n<command-args>${args}</command-args>` : "")
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function matchCommandAndBody(before: Entry[], input: MatchInput): Receipt {
|
|
155
|
+
const token = `/${input.skillName}`;
|
|
156
|
+
if (!repositoryPromptMatches(input.prompt, token))
|
|
157
|
+
return { accepted: false, reason: "prompt differs from mounted command" };
|
|
158
|
+
const command = commandBoundary(before, input, token);
|
|
159
|
+
if ("receipt" in command) return command.receipt;
|
|
160
|
+
return mountedBodyReceipt(before, input, command.called);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function commandBoundary(
|
|
164
|
+
before: Entry[],
|
|
165
|
+
input: MatchInput,
|
|
166
|
+
token: string,
|
|
167
|
+
): CommandBoundary {
|
|
119
168
|
const commands = before.filter(
|
|
120
169
|
(entry) =>
|
|
121
170
|
entry.type === "user" &&
|
|
122
171
|
content(entry).includes(`<command-name>${token}</command-name>`),
|
|
123
172
|
);
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
)
|
|
133
|
-
return {
|
|
173
|
+
const [called] = commands;
|
|
174
|
+
if (commands.length !== 1 || called === undefined)
|
|
175
|
+
return {
|
|
176
|
+
receipt: {
|
|
177
|
+
accepted: false,
|
|
178
|
+
reason: "missing or duplicate native command",
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
if (!commandCorrelated(called, input, nativeCommand(input, token)))
|
|
182
|
+
return {
|
|
183
|
+
receipt: {
|
|
184
|
+
accepted: false,
|
|
185
|
+
reason: "command session or arguments differ",
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
return { called };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function commandCorrelated(
|
|
192
|
+
called: Entry,
|
|
193
|
+
input: MatchInput,
|
|
194
|
+
command: string,
|
|
195
|
+
): boolean {
|
|
196
|
+
return (
|
|
197
|
+
called.sessionId === input.sessionId &&
|
|
198
|
+
called.isMeta !== true &&
|
|
199
|
+
called.isSidechain !== true &&
|
|
200
|
+
content(called) === command
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function mountedBodyReceipt(
|
|
205
|
+
before: Entry[],
|
|
206
|
+
input: MatchInput,
|
|
207
|
+
called: Entry,
|
|
208
|
+
): Receipt {
|
|
134
209
|
const body = input.skillText
|
|
135
210
|
.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n/, "")
|
|
136
211
|
.trim();
|
|
137
212
|
if (!body) return { accepted: null, reason: "empty mounted body" };
|
|
138
213
|
const prefix = `Base directory for this skill: ${input.skillDir}\n\n${body}`;
|
|
139
|
-
const expanded = before.filter(
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
(content(entry) === prefix || content(entry).startsWith(prefix + "\n")),
|
|
143
|
-
);
|
|
144
|
-
if (expanded.length !== 1)
|
|
214
|
+
const expanded = before.filter((entry) => mountedBodyMatches(entry, prefix));
|
|
215
|
+
const [loaded] = expanded;
|
|
216
|
+
if (expanded.length !== 1 || loaded === undefined)
|
|
145
217
|
return {
|
|
146
218
|
accepted: false,
|
|
147
219
|
reason: "missing, partial, or duplicate mounted body",
|
|
148
220
|
};
|
|
149
|
-
|
|
150
|
-
if (
|
|
151
|
-
loaded.sessionId !== input.sessionId ||
|
|
152
|
-
loaded.isMeta !== true ||
|
|
153
|
-
loaded.isSidechain === true ||
|
|
154
|
-
before.indexOf(loaded) <= before.indexOf(called)
|
|
155
|
-
)
|
|
221
|
+
if (!bodyCorrelated(loaded, input.sessionId, before, called))
|
|
156
222
|
return { accepted: false, reason: "mounted body is uncorrelated" };
|
|
157
223
|
return {
|
|
158
224
|
accepted: true,
|
|
@@ -160,6 +226,27 @@ export function matchClaudeRepositoryInvocation(
|
|
|
160
226
|
};
|
|
161
227
|
}
|
|
162
228
|
|
|
229
|
+
function mountedBodyMatches(entry: Entry, prefix: string): boolean {
|
|
230
|
+
return (
|
|
231
|
+
entry.type === "user" &&
|
|
232
|
+
(content(entry) === prefix || content(entry).startsWith(prefix + "\n"))
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function bodyCorrelated(
|
|
237
|
+
loaded: Entry,
|
|
238
|
+
sessionId: string,
|
|
239
|
+
before: Entry[],
|
|
240
|
+
called: Entry,
|
|
241
|
+
): boolean {
|
|
242
|
+
return (
|
|
243
|
+
loaded.sessionId === sessionId &&
|
|
244
|
+
loaded.isMeta === true &&
|
|
245
|
+
loaded.isSidechain !== true &&
|
|
246
|
+
before.indexOf(loaded) > before.indexOf(called)
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
|
|
163
250
|
/** Read only the completed bound native session; retain no arguments or body. */
|
|
164
251
|
export async function claudeRepositoryInvocationObservation(input: {
|
|
165
252
|
invocation: ClaudeRepositoryInvocation;
|
|
@@ -173,55 +260,10 @@ export async function claudeRepositoryInvocationObservation(input: {
|
|
|
173
260
|
reason: "native session evidence unavailable",
|
|
174
261
|
};
|
|
175
262
|
try {
|
|
176
|
-
|
|
177
|
-
...new Set(
|
|
178
|
-
entries(input.stream)
|
|
179
|
-
.filter(
|
|
180
|
-
(entry) =>
|
|
181
|
-
entry.type === "result" ||
|
|
182
|
-
(entry.type === "system" && entry.subtype === "init"),
|
|
183
|
-
)
|
|
184
|
-
.map((entry) => entry.session_id),
|
|
185
|
-
),
|
|
186
|
-
];
|
|
187
|
-
if (
|
|
188
|
-
sessions.length === 1 &&
|
|
189
|
-
typeof sessions[0] === "string" &&
|
|
190
|
-
/^[A-Za-z0-9_-]{1,128}$/.test(sessions[0])
|
|
191
|
-
) {
|
|
192
|
-
const workspace = await realpath(input.workspace);
|
|
193
|
-
const path = join(
|
|
194
|
-
await realpath(input.configRoot),
|
|
195
|
-
"projects",
|
|
196
|
-
workspace.replace(/[^A-Za-z0-9]/g, "-"),
|
|
197
|
-
`${sessions[0]}.jsonl`,
|
|
198
|
-
);
|
|
199
|
-
const entry = await lstat(path);
|
|
200
|
-
if (
|
|
201
|
-
entry.isFile() &&
|
|
202
|
-
!entry.isSymbolicLink() &&
|
|
203
|
-
entry.size <= 8 * 1024 * 1024 &&
|
|
204
|
-
(await realpath(path)) === path
|
|
205
|
-
) {
|
|
206
|
-
receipt = matchClaudeRepositoryInvocation({
|
|
207
|
-
...input.invocation,
|
|
208
|
-
sessionId: sessions[0],
|
|
209
|
-
transcript: await readFile(path, "utf8"),
|
|
210
|
-
});
|
|
211
|
-
}
|
|
212
|
-
}
|
|
263
|
+
receipt = await repositoryReceipt(input);
|
|
213
264
|
} catch {
|
|
214
265
|
/* Unavailable or malformed native evidence stays partial. */
|
|
215
266
|
}
|
|
216
|
-
const observed = input.tools.data.calls
|
|
217
|
-
.filter((call) => call.name === "Skill")
|
|
218
|
-
.map((call) => call.skill);
|
|
219
|
-
const observedSkills = [
|
|
220
|
-
...new Set([
|
|
221
|
-
...(receipt.accepted === true ? [input.invocation.skillName] : []),
|
|
222
|
-
...observed,
|
|
223
|
-
]),
|
|
224
|
-
];
|
|
225
267
|
return {
|
|
226
268
|
id: "sevro.claude.repository-invocation" as const,
|
|
227
269
|
completeness:
|
|
@@ -232,8 +274,57 @@ export async function claudeRepositoryInvocationObservation(input: {
|
|
|
232
274
|
method: "native_repository_command",
|
|
233
275
|
...receipt,
|
|
234
276
|
skill: input.invocation.skillName,
|
|
235
|
-
primarySkill: observedSkills[0] ?? null,
|
|
236
|
-
observedSkills,
|
|
237
277
|
},
|
|
238
278
|
};
|
|
239
279
|
}
|
|
280
|
+
|
|
281
|
+
function repositorySessionId(stream: string): string | null {
|
|
282
|
+
const sessions = [
|
|
283
|
+
...new Set(
|
|
284
|
+
entries(stream)
|
|
285
|
+
.filter(
|
|
286
|
+
(entry) =>
|
|
287
|
+
entry.type === "result" ||
|
|
288
|
+
(entry.type === "system" && entry.subtype === "init"),
|
|
289
|
+
)
|
|
290
|
+
.map((entry) => entry.session_id),
|
|
291
|
+
),
|
|
292
|
+
];
|
|
293
|
+
const [id] = sessions;
|
|
294
|
+
if (
|
|
295
|
+
sessions.length !== 1 ||
|
|
296
|
+
typeof id !== "string" ||
|
|
297
|
+
!/^[A-Za-z0-9_-]{1,128}$/.test(id)
|
|
298
|
+
)
|
|
299
|
+
return null;
|
|
300
|
+
return id;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
async function repositoryReceipt(
|
|
304
|
+
input: Parameters<typeof claudeRepositoryInvocationObservation>[0],
|
|
305
|
+
): Promise<Receipt> {
|
|
306
|
+
const sessionId = repositorySessionId(input.stream);
|
|
307
|
+
if (sessionId === null)
|
|
308
|
+
return { accepted: null, reason: "native session evidence unavailable" };
|
|
309
|
+
const workspace = await realpath(input.workspace);
|
|
310
|
+
const path = join(
|
|
311
|
+
await realpath(input.configRoot),
|
|
312
|
+
"projects",
|
|
313
|
+
workspace.replace(/[^A-Za-z0-9]/g, "-"),
|
|
314
|
+
`${sessionId}.jsonl`,
|
|
315
|
+
);
|
|
316
|
+
const entry = await lstat(path);
|
|
317
|
+
if (!regularTranscript(entry) || (await realpath(path)) !== path)
|
|
318
|
+
return { accepted: null, reason: "native session evidence unavailable" };
|
|
319
|
+
return matchClaudeRepositoryInvocation({
|
|
320
|
+
...input.invocation,
|
|
321
|
+
sessionId,
|
|
322
|
+
transcript: await readFile(path, "utf8"),
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function regularTranscript(entry: Stats): boolean {
|
|
327
|
+
return (
|
|
328
|
+
entry.isFile() && !entry.isSymbolicLink() && entry.size <= 8 * 1024 * 1024
|
|
329
|
+
);
|
|
330
|
+
}
|