@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.
Files changed (230) hide show
  1. package/CONTRIBUTING.md +22 -6
  2. package/README.md +5 -1
  3. package/docs/README.md +19 -15
  4. package/docs/architecture.md +138 -17
  5. package/docs/assets/README.md +5 -4
  6. package/docs/builtin-graders-v1.md +17 -3
  7. package/docs/codex-host.md +155 -49
  8. package/docs/contracts.md +2 -1
  9. package/docs/creating-cases.md +9 -2
  10. package/docs/development-cli.md +187 -19
  11. package/docs/documentation-quality.md +12 -2
  12. package/docs/evaluation-workflow.md +110 -0
  13. package/docs/extension-protocol-v1.md +26 -3
  14. package/docs/getting-started.md +23 -2
  15. package/docs/guide-evaluation.md +33 -1
  16. package/docs/identity-v1.md +25 -2
  17. package/docs/installing.md +11 -11
  18. package/docs/native-controls.md +76 -5
  19. package/docs/native-hosts.md +196 -7
  20. package/docs/reading-results.md +102 -0
  21. package/docs/recipes.md +489 -0
  22. package/docs/releases.md +31 -12
  23. package/docs/report-v1.md +29 -1
  24. package/docs/results-v1.md +141 -4
  25. package/docs/run-settings-v1.md +147 -0
  26. package/docs/runtime-v1.md +307 -18
  27. package/docs/troubleshooting.md +12 -12
  28. package/docs/typescript-quality.md +89 -26
  29. package/examples/recipes/candidate.ts +26 -0
  30. package/examples/recipes/cases/coding-change.json +21 -0
  31. package/examples/recipes/cases/host-tools.json +21 -0
  32. package/examples/recipes/cases/mise.json +21 -0
  33. package/examples/recipes/cases/node.json +21 -0
  34. package/examples/recipes/cases/python-uv.json +21 -0
  35. package/examples/recipes/cases/semantic.json +17 -0
  36. package/examples/recipes/coding-change/check.ts +23 -0
  37. package/examples/recipes/extension-artifacts.ts +65 -0
  38. package/examples/recipes/extension-methods.ts +174 -0
  39. package/examples/recipes/extension.ts +37 -0
  40. package/examples/recipes/repository-skill/.agents/skills/startup-guide/SKILL.md +7 -0
  41. package/examples/recipes/repository-skill/.claude/skills/startup-guide/SKILL.md +7 -0
  42. package/examples/recipes/repository-skill/README.md +3 -0
  43. package/examples/recipes/run-inputs.ts +188 -0
  44. package/examples/recipes/run-options.ts +131 -0
  45. package/examples/recipes/run.ts +63 -0
  46. package/examples/recipes/runtime/empty.json +1 -0
  47. package/examples/recipes/runtime/goals.json +1 -0
  48. package/examples/recipes/runtime/mise.json +6 -0
  49. package/examples/recipes/runtime/path.json +1 -0
  50. package/examples/recipes/runtime/plugins.json +4 -0
  51. package/examples/recipes/runtime/python-uv.json +10 -0
  52. package/examples/recipes/semantic-judge.ts +29 -0
  53. package/examples/templates/coding/adapter.ts +17 -0
  54. package/examples/templates/coding/case.json +15 -0
  55. package/examples/templates/repository-skill/.agents/skills/startup-guide/SKILL.md +6 -0
  56. package/examples/templates/repository-skill/extension-data.ts +64 -0
  57. package/examples/templates/repository-skill/extension.ts +31 -0
  58. package/examples/templates/response/adapter.ts +12 -0
  59. package/examples/templates/response/case.json +13 -0
  60. package/package.json +11 -5
  61. package/schemas/report-v1.schema.json +9 -0
  62. package/schemas/run-evidence-v1.schema.json +2 -1
  63. package/schemas/runtime-v1.schema.json +63 -7
  64. package/src/advisory.ts +2 -2
  65. package/src/candidate-artifacts.ts +206 -0
  66. package/src/candidate-codex-state.ts +185 -0
  67. package/src/case-validation.ts +73 -50
  68. package/src/cli/cancellation.ts +20 -0
  69. package/src/cli/command.ts +87 -0
  70. package/src/cli/completion.ts +15 -0
  71. package/src/cli/demo.ts +60 -0
  72. package/src/cli/evaluation.ts +55 -0
  73. package/src/cli/extension.ts +79 -0
  74. package/src/cli/help.ts +198 -0
  75. package/src/cli/hosts.ts +55 -0
  76. package/src/cli/init-settings.ts +30 -0
  77. package/src/cli/init.ts +111 -0
  78. package/src/{cli-invocation.ts → cli/invocation.ts} +214 -218
  79. package/src/cli/loading.ts +151 -0
  80. package/src/cli/preparation.ts +154 -0
  81. package/src/cli/presentation.ts +93 -0
  82. package/src/cli/progress.ts +6 -0
  83. package/src/cli/run-manifest.ts +128 -0
  84. package/src/cli/run-options.ts +403 -0
  85. package/src/cli/run-overrides.ts +69 -0
  86. package/src/cli/settings.ts +154 -0
  87. package/src/cli/summary.ts +43 -0
  88. package/src/cli.ts +13 -591
  89. package/src/contract-types.ts +94 -0
  90. package/src/engine.ts +13 -5
  91. package/src/evaluation/advisory.ts +150 -0
  92. package/src/{evaluation-check-plan.ts → evaluation/check-plan.ts} +103 -67
  93. package/src/evaluation/collection.ts +88 -0
  94. package/src/evaluation/context.ts +10 -0
  95. package/src/{evaluation-grading-host.ts → evaluation/grading-host.ts} +5 -5
  96. package/src/evaluation/grading.ts +235 -0
  97. package/src/{evaluation-invocation-plan.ts → evaluation/invocation-plan.ts} +147 -58
  98. package/src/{evaluation-plan.ts → evaluation/plan.ts} +208 -141
  99. package/src/evaluation/progress.ts +19 -0
  100. package/src/evaluation/resources.ts +241 -0
  101. package/src/evaluation/run.ts +206 -0
  102. package/src/{evaluation-semantic.ts → evaluation/semantic.ts} +133 -115
  103. package/src/evaluation/trial.ts +800 -0
  104. package/src/evaluation/types.ts +207 -0
  105. package/src/evidence/host-results.ts +85 -0
  106. package/src/evidence/retained-artifacts.ts +18 -0
  107. package/src/evidence/trial-writer.ts +203 -0
  108. package/src/evidence/usage.ts +17 -0
  109. package/src/extension-client.ts +34 -96
  110. package/src/extension-session.ts +24 -48
  111. package/src/{advisory-fixture.ts → fixtures/advisory.ts} +15 -76
  112. package/src/{fixture-bin.ts → fixtures/bin.ts} +1 -1
  113. package/src/{fixture-cleanup.ts → fixtures/cleanup.ts} +13 -7
  114. package/src/fixtures/create.ts +183 -0
  115. package/src/{generated-fixture.ts → fixtures/generated.ts} +10 -53
  116. package/src/{git-hooks.ts → fixtures/git-hooks.ts} +1 -1
  117. package/src/{preparation.ts → fixtures/preparation.ts} +6 -16
  118. package/src/{repository-fixture.ts → fixtures/repository.ts} +30 -64
  119. package/src/{fixture-setup.ts → fixtures/setup.ts} +33 -51
  120. package/src/{fixture-tools.ts → fixtures/tools.ts} +1 -1
  121. package/src/fixtures/workspace-state.ts +212 -0
  122. package/src/generated/cli-result.d.cts +2 -1
  123. package/src/generated/extension.d.cts +2 -1
  124. package/src/generated/report.cjs +1 -1
  125. package/src/generated/report.d.cts +2 -1
  126. package/src/generated/run-evidence.cjs +1 -1
  127. package/src/generated/run-evidence.d.cts +2 -1
  128. package/src/generated/runtime.cjs +1 -1
  129. package/src/generated/runtime.d.cts +2 -1
  130. package/src/git.ts +93 -0
  131. package/src/graders/git-head.ts +29 -84
  132. package/src/graders/shell.ts +57 -68
  133. package/src/host-diagnostic-redaction.ts +52 -0
  134. package/src/host-diagnostics.ts +331 -0
  135. package/src/hosts/claude-continuation.ts +57 -40
  136. package/src/hosts/claude-credential.ts +160 -63
  137. package/src/hosts/claude-events.ts +34 -18
  138. package/src/hosts/claude-keychain.ts +78 -21
  139. package/src/hosts/claude-login-recovery.ts +85 -0
  140. package/src/hosts/claude-login-refresh.ts +178 -0
  141. package/src/hosts/claude-login-store.ts +162 -0
  142. package/src/hosts/claude-login.ts +107 -0
  143. package/src/hosts/claude-native-goal.ts +15 -12
  144. package/src/hosts/claude-process.ts +10 -50
  145. package/src/hosts/claude-refresh-lock.ts +81 -0
  146. package/src/hosts/claude-repository-invocation.ts +9 -34
  147. package/src/hosts/claude-settings.ts +91 -14
  148. package/src/hosts/claude-skill-activations.ts +169 -0
  149. package/src/hosts/claude-state.ts +46 -16
  150. package/src/hosts/claude-tool-calls.ts +13 -10
  151. package/src/hosts/claude.ts +122 -73
  152. package/src/hosts/codex-app-server.ts +78 -52
  153. package/src/hosts/codex-configuration.ts +5 -10
  154. package/src/hosts/codex-exec-controls.ts +516 -0
  155. package/src/hosts/codex-marketplace.ts +12 -10
  156. package/src/hosts/codex-native-calls.ts +15 -122
  157. package/src/hosts/codex-process.ts +10 -54
  158. package/src/hosts/codex-profile.ts +50 -21
  159. package/src/hosts/codex-session-files.ts +1 -9
  160. package/src/hosts/codex-state.ts +141 -19
  161. package/src/hosts/codex.ts +221 -179
  162. package/src/hosts/isolation-common.ts +15 -11
  163. package/src/hosts/isolation-roots.ts +13 -20
  164. package/src/hosts/linux-sandbox.ts +27 -3
  165. package/src/hosts/mac-sandbox.ts +111 -18
  166. package/src/hosts/native-controls.ts +72 -22
  167. package/src/hosts/native-request.ts +55 -0
  168. package/src/hosts/process-lifecycle.ts +221 -45
  169. package/src/hosts/protected-worktrees.ts +1 -1
  170. package/src/hosts/skill-hook-catalog.ts +131 -0
  171. package/src/hosts/skill-hook-handler.ts +374 -0
  172. package/src/hosts/skill-hooks.ts +577 -0
  173. package/src/instrumentation.ts +7 -8
  174. package/src/native-transcript-state.ts +22 -3
  175. package/src/native-transcript-view.ts +52 -13
  176. package/src/native-transcripts.ts +34 -15
  177. package/src/paths.ts +22 -0
  178. package/src/process-lifecycle.ts +150 -0
  179. package/src/provenance.ts +27 -7
  180. package/src/prune.ts +300 -0
  181. package/src/report.ts +63 -36
  182. package/src/resolved-case.ts +1 -1
  183. package/src/results-lock.ts +42 -0
  184. package/src/results.ts +14 -18
  185. package/src/run-owner.ts +4 -4
  186. package/src/{runtime-config.ts → runtime/config.ts} +182 -44
  187. package/src/{runtime-discovery-apple.ts → runtime/discovery-apple.ts} +3 -3
  188. package/src/{runtime-discovery-providers.ts → runtime/discovery-providers.ts} +3 -9
  189. package/src/{runtime-discovery.ts → runtime/discovery.ts} +4 -4
  190. package/src/{runtime-hook-processes.ts → runtime/hook-processes.ts} +1 -1
  191. package/src/{runtime-hooks.ts → runtime/hooks.ts} +33 -22
  192. package/src/{runtime-paths.ts → runtime/paths.ts} +6 -18
  193. package/src/{runtime-query.ts → runtime/query.ts} +5 -5
  194. package/src/{runtime-seeds.ts → runtime/seeds.ts} +3 -3
  195. package/src/runtime/shared-cache.ts +130 -0
  196. package/src/{runtime-state.ts → runtime/state.ts} +28 -11
  197. package/src/schema-types.ts +73 -161
  198. package/src/shell.ts +4 -0
  199. package/src/transcript-retention.ts +146 -0
  200. package/src/value-guards.ts +22 -0
  201. package/docs/assets/sevro-logo.png +0 -0
  202. package/docs/assets/sevro-symbol.svg +0 -1
  203. package/docs/assets/sevro-wordmark.svg +0 -1
  204. package/docs/cache-ownership-validation.md +0 -49
  205. package/docs/claude-continuation-validation.md +0 -47
  206. package/docs/concurrency-validation.md +0 -86
  207. package/docs/configuration-root-validation.md +0 -70
  208. package/docs/engine-slice.md +0 -114
  209. package/docs/evidence/runtime-startup-30/README.md +0 -185
  210. package/docs/evidence/runtime-startup-30/after.json +0 -319
  211. package/docs/evidence/runtime-startup-30/baseline-timeout.jsonl +0 -2
  212. package/docs/evidence/runtime-startup-30/before.json +0 -403
  213. package/docs/evidence/runtime-startup-30/consumer-smoke.json +0 -63
  214. package/docs/evidence/runtime-startup-30/full-policy-diagnostic.jsonl +0 -3
  215. package/docs/evidence/runtime-startup-30/minimal-policy-diagnostic.jsonl +0 -3
  216. package/docs/evidence/runtime-startup-30/plugin-template-assessment.json +0 -20
  217. package/docs/evidence/runtime-startup-30/seedless-timeout.jsonl +0 -2
  218. package/docs/evidence.md +0 -17
  219. package/docs/fixture-cleanup-validation.md +0 -70
  220. package/docs/guide-verification.json +0 -239
  221. package/src/evaluation-context.ts +0 -257
  222. package/src/evaluation-fixture.ts +0 -313
  223. package/src/evaluation-run.ts +0 -198
  224. package/src/evaluation-trial.ts +0 -946
  225. package/src/evaluation-types.ts +0 -149
  226. package/src/hosts/claude-nested-skills.ts +0 -272
  227. package/src/hosts/codex-command-output.ts +0 -510
  228. package/src/hosts/codex-executor-command.ts +0 -291
  229. package/src/hosts/codex-skill-reads.ts +0 -609
  230. /package/src/{trial-scheduler.ts → evaluation/scheduler.ts} +0 -0
@@ -1,149 +0,0 @@
1
- import type {
2
- InstrumentationCapability,
3
- InstrumentationRequest,
4
- } from "./instrumentation";
5
- import type { GeneratedFixture } from "./generated-fixture";
6
- import type { PreparationSources } from "./preparation";
7
- import type { RepositoryFixture } from "./repository-fixture";
8
- import type { EvaluationResult, ExtensionCase } from "./extension-session";
9
- import type { openExtensionSession } from "./extension-session";
10
- import type { Assessment, CheckOutcome } from "./results";
11
- import type { RuntimePolicy } from "./runtime-config";
12
- import type { RuntimeRole } from "./runtime-state";
13
-
14
- export class EvaluationConfigurationError extends Error {
15
- constructor(message: string, options?: ErrorOptions) {
16
- super(message, options);
17
- this.name = "EvaluationConfigurationError";
18
- }
19
- }
20
-
21
- export interface HostResult {
22
- finalMessage: string | null;
23
- complete: boolean;
24
- executionFailed?: boolean;
25
- artifacts?: { id: string; bytes: Uint8Array }[];
26
- observations?: {
27
- id: string;
28
- completeness: "complete" | "partial" | "unavailable";
29
- data: Record<string, unknown>;
30
- }[];
31
- actualCondition?: "passive" | "enforced" | "unknown";
32
- appliedInstrumentation?: InstrumentationRequest[];
33
- inputTokens?: number | null;
34
- outputTokens?: number | null;
35
- costUsd?: number | null;
36
- usageComplete?: boolean;
37
- }
38
-
39
- export interface HostAdapter {
40
- id: string;
41
- model: string;
42
- effort: string;
43
- instrumentation?: InstrumentationCapability[];
44
- hostCapabilities?: string[];
45
- configuration?: Record<string, unknown>;
46
- run(request: {
47
- runtimePolicy?: RuntimePolicy;
48
- runtimeRole?: RuntimeRole;
49
- candidateTranscriptRoot?: string;
50
- prompt: string;
51
- followUpPrompt?: string;
52
- workspace: string;
53
- condition: "passive" | "enforced";
54
- fixtureBinDir?: string;
55
- instrumentation?: InstrumentationRequest[];
56
- codexMarketplace?: {
57
- artifactRoot: string;
58
- marketplaceName: string;
59
- pluginNames: string[];
60
- artifactPaths: string[];
61
- };
62
- claudePluginDirs?: {
63
- artifactRoots: string[];
64
- artifactPaths: string[];
65
- };
66
- explicitSkillInvocation?:
67
- | {
68
- scope?: "plugin";
69
- pluginName: string;
70
- skillName: string;
71
- token: string;
72
- }
73
- | {
74
- scope: "repository";
75
- pluginName?: never;
76
- skillName: string;
77
- token: string;
78
- };
79
- signal?: AbortSignal;
80
- }): Promise<HostResult>;
81
- }
82
-
83
- export interface ResolvedCase {
84
- id: string;
85
- prompt: string;
86
- followUpPrompt?: string;
87
- fixture:
88
- | { files: Record<string, string>; sourceRef?: never }
89
- | Omit<RepositoryFixture, "kind">
90
- | (GeneratedFixture & { sourceRef?: never });
91
- checks: {
92
- id: string;
93
- grader: string;
94
- configuration: Record<string, unknown>;
95
- }[];
96
- requiredEvidence: string[];
97
- }
98
-
99
- export interface EvaluationOptions {
100
- runtimePolicy?: RuntimePolicy;
101
- projectRoot: string;
102
- resultsRoot: string;
103
- runStateRoot?: string;
104
- case: ResolvedCase;
105
- host: HostAdapter;
106
- semanticHost?: HostAdapter;
107
- advisoryHost?: HostAdapter;
108
- advisoryExcludedPaths?: string[];
109
- runnerBuildDigest: string;
110
- runnerCheckoutRoot?: string;
111
- projectDigest: string;
112
- condition: "passive" | "enforced";
113
- trialCount: number;
114
- jobs?: number;
115
- passThreshold: number;
116
- dry?: boolean;
117
- signal?: AbortSignal;
118
- extension?: {
119
- session: Awaited<ReturnType<typeof openExtensionSession>>;
120
- resolvedCase: ExtensionCase;
121
- };
122
- preparationSources?: PreparationSources;
123
- shellIsolation?: {
124
- protectedRoots: string[];
125
- toolchainBinDir?: string;
126
- uvRuntimeCache?: boolean;
127
- };
128
- }
129
-
130
- export interface TrialSummary extends Assessment {
131
- trial: number;
132
- checks: CheckOutcome[];
133
- domainOutcomes: NonNullable<EvaluationResult["domainOutcomes"]>;
134
- artifactPath: string;
135
- }
136
-
137
- export interface CaseSummary extends Assessment {
138
- caseId: string;
139
- trials: TrialSummary[];
140
- }
141
-
142
- export interface CliResult extends Assessment {
143
- format: "sevro.cli-result.v1";
144
- runId: string;
145
- exitCode: number;
146
- evidencePath: string;
147
- cases: CaseSummary[];
148
- diagnostic?: { code: string; message: string };
149
- }
@@ -1,272 +0,0 @@
1
- import { lstat, readFile, readdir, realpath } from "node:fs/promises";
2
- import { join } from "node:path";
3
- import { isRecord } from "../value-guards";
4
-
5
- type Entry = Record<string, unknown>;
6
- const MAX_BYTES = 8 * 1024 * 1024;
7
- const MAX_AGENTS = 64;
8
- const MAX_SKILLS = 128;
9
-
10
- function label(value: unknown): value is string {
11
- return typeof value === "string" && /^[A-Za-z0-9_.:-]{1,256}$/.test(value);
12
- }
13
-
14
- function identity(value: unknown): value is string {
15
- return typeof value === "string" && /^[A-Za-z0-9_-]{1,128}$/.test(value);
16
- }
17
-
18
- function blocks(entry: Entry): Entry[] {
19
- const content = isRecord(entry.message) ? entry.message.content : null;
20
- return Array.isArray(content) ? content.filter(isRecord) : [];
21
- }
22
-
23
- function entries(text: string): Entry[] {
24
- return text
25
- .split(/\r?\n/)
26
- .filter(Boolean)
27
- .map((line) => {
28
- const parsed: unknown = JSON.parse(line);
29
- if (!isRecord(parsed)) throw new Error("invalid Claude session entry");
30
- return parsed;
31
- });
32
- }
33
-
34
- async function readSession(path: string): Promise<string> {
35
- const file = await lstat(path);
36
- if (!file.isFile() || file.isSymbolicLink() || file.size > MAX_BYTES)
37
- throw new Error("Claude session file is unavailable");
38
- return readFile(path, "utf8");
39
- }
40
-
41
- type NestedSkill = {
42
- ancestorToolUseId: string;
43
- skill: string;
44
- invocation: string;
45
- };
46
-
47
- type State = {
48
- sessionId: string;
49
- projectDir: string;
50
- seen: Set<string>;
51
- calls: NestedSkill[];
52
- };
53
-
54
- async function sidecarChild(
55
- state: State,
56
- toolUseId: string,
57
- parentAgentId: string | null,
58
- ): Promise<string> {
59
- const directory = join(state.projectDir, state.sessionId, "subagents");
60
- const names = (await readdir(directory)).filter((name) =>
61
- /^agent-[A-Za-z0-9_-]+\.meta\.json$/.test(name),
62
- );
63
- if (names.length > MAX_AGENTS) throw new Error("excessive Claude agents");
64
- const matches: string[] = [];
65
- for (const name of names) {
66
- const metadata: unknown = JSON.parse(
67
- await readSession(join(directory, name)),
68
- );
69
- if (metadataMatches(metadata, toolUseId, parentAgentId))
70
- matches.push(name.slice(6, -10));
71
- }
72
- return uniqueChildIdentity(matches);
73
- }
74
-
75
- function metadataMatches(
76
- metadata: unknown,
77
- toolUseId: string,
78
- parentAgentId: string | null,
79
- ): boolean {
80
- return (
81
- isRecord(metadata) &&
82
- metadata.toolUseId === toolUseId &&
83
- (metadata.parentAgentId ?? null) === parentAgentId
84
- );
85
- }
86
- function uniqueChildIdentity(matches: string[]): string {
87
- const [id] = matches;
88
- if (matches.length !== 1 || !identity(id))
89
- throw new Error("Claude child identity is unavailable");
90
- return id;
91
- }
92
-
93
- async function childId(
94
- entry: Entry,
95
- state: State,
96
- toolUseId: string,
97
- parentAgentId: string | null,
98
- ): Promise<string> {
99
- const result = isRecord(entry.toolUseResult) ? entry.toolUseResult : null;
100
- if (result) {
101
- if (result.status !== "completed" || !identity(result.agentId))
102
- throw new Error("Claude child did not complete");
103
- return result.agentId;
104
- }
105
- return sidecarChild(state, toolUseId, parentAgentId);
106
- }
107
-
108
- async function childSession(state: State, agentId: string) {
109
- const directory = join(state.projectDir, state.sessionId, "subagents");
110
- const path = join(directory, `agent-${agentId}.jsonl`);
111
- if ((await realpath(path)) !== path)
112
- throw new Error("Claude child session is redirected");
113
- return readSession(path);
114
- }
115
-
116
- function skillCall(block: Entry, ancestorToolUseId: string | null) {
117
- const invocation = isRecord(block.input) ? block.input.skill : null;
118
- if (!label(invocation)) throw new Error("invalid Claude Skill call");
119
- return ancestorToolUseId
120
- ? {
121
- ancestorToolUseId,
122
- skill: invocation.slice(invocation.lastIndexOf(":") + 1),
123
- invocation,
124
- }
125
- : null;
126
- }
127
-
128
- type VisitContext = {
129
- state: State;
130
- agentId: string | null;
131
- ancestorToolUseId: string | null;
132
- pending: Set<string>;
133
- };
134
-
135
- async function visit(
136
- text: string,
137
- state: State,
138
- agentId: string | null,
139
- ancestorToolUseId: string | null,
140
- ): Promise<void> {
141
- const context: VisitContext = {
142
- state,
143
- agentId,
144
- ancestorToolUseId,
145
- pending: new Set(),
146
- };
147
- for (const entry of entries(text)) {
148
- if (entry.type !== "assistant" && entry.type !== "user") continue;
149
- await visitEntry(entry, context);
150
- }
151
- if (context.pending.size) throw new Error("incomplete Claude Agent graph");
152
- }
153
-
154
- async function visitEntry(entry: Entry, context: VisitContext): Promise<void> {
155
- if (
156
- entry.sessionId !== context.state.sessionId ||
157
- (entry.agentId ?? null) !== context.agentId
158
- )
159
- throw new Error("Claude session identity mismatch");
160
- for (const block of blocks(entry)) await visitBlock(entry, block, context);
161
- }
162
-
163
- async function visitBlock(
164
- entry: Entry,
165
- block: Entry,
166
- context: VisitContext,
167
- ): Promise<void> {
168
- if (entry.type === "assistant" && block.type === "tool_use")
169
- assistantBlock(block, context);
170
- if (acceptedChildResult(block, entry, context.pending))
171
- await visitChild(entry, block, context);
172
- }
173
-
174
- function acceptedChildResult(
175
- block: Entry,
176
- entry: Entry,
177
- pending: Set<string>,
178
- ): block is Entry & { tool_use_id: string } {
179
- return (
180
- entry.type === "user" &&
181
- block.type === "tool_result" &&
182
- identity(block.tool_use_id) &&
183
- pending.has(block.tool_use_id)
184
- );
185
- }
186
-
187
- function assistantBlock(block: Entry, context: VisitContext): void {
188
- if (block.name === "Skill") retainSkill(block, context);
189
- if (block.name === "Agent" || block.name === "Task")
190
- registerAgent(block, context.pending);
191
- }
192
-
193
- function retainSkill(block: Entry, context: VisitContext): void {
194
- const call = skillCall(block, context.ancestorToolUseId);
195
- if (call) context.state.calls.push(call);
196
- if (context.state.calls.length > MAX_SKILLS)
197
- throw new Error("excessive Claude Skill calls");
198
- }
199
-
200
- function registerAgent(block: Entry, pending: Set<string>): void {
201
- if (!identity(block.id) || pending.has(block.id))
202
- throw new Error("invalid Claude Agent call");
203
- pending.add(block.id);
204
- }
205
-
206
- async function visitChild(
207
- entry: Entry,
208
- block: Entry & { tool_use_id: string },
209
- context: VisitContext,
210
- ): Promise<void> {
211
- if (block.is_error === true) throw new Error("Claude child call failed");
212
- const { state, agentId, ancestorToolUseId, pending } = context;
213
- const child = await childId(entry, state, block.tool_use_id, agentId);
214
- if (state.seen.has(child) || state.seen.size >= MAX_AGENTS)
215
- throw new Error("repeated Claude child");
216
- state.seen.add(child);
217
- await visit(
218
- await childSession(state, child),
219
- state,
220
- child,
221
- ancestorToolUseId ?? block.tool_use_id,
222
- );
223
- pending.delete(block.tool_use_id);
224
- }
225
-
226
- export async function claudeNestedSkillsObservation(
227
- stream: string,
228
- configRoot: string,
229
- workspace: string,
230
- ) {
231
- const unavailable = {
232
- id: "sevro.claude.nested-skills" as const,
233
- completeness: "partial" as const,
234
- data: { method: "native_session_graph", calls: [] as NestedSkill[] },
235
- };
236
- try {
237
- const sessions = [
238
- ...new Set(
239
- entries(stream)
240
- .filter((entry) => entry.type === "result")
241
- .map((entry) => entry.session_id),
242
- ),
243
- ];
244
- if (sessions.length !== 1 || !identity(sessions[0])) return unavailable;
245
- const canonicalWorkspace = await realpath(workspace);
246
- const canonicalConfigRoot = await realpath(configRoot);
247
- const projectDir = join(
248
- canonicalConfigRoot,
249
- "projects",
250
- canonicalWorkspace.replace(/[^A-Za-z0-9]/g, "-"),
251
- );
252
- const state: State = {
253
- sessionId: sessions[0],
254
- projectDir,
255
- seen: new Set(),
256
- calls: [],
257
- };
258
- await visit(
259
- await readSession(join(projectDir, `${state.sessionId}.jsonl`)),
260
- state,
261
- null,
262
- null,
263
- );
264
- return {
265
- id: "sevro.claude.nested-skills" as const,
266
- completeness: "complete" as const,
267
- data: { method: "native_session_graph", calls: state.calls },
268
- };
269
- } catch {
270
- return unavailable;
271
- }
272
- }