@bjoernrochel/sevro 0.1.0-rc.8 → 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 (228) 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 +9 -2
  7. package/docs/codex-host.md +155 -57
  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 +122 -4
  25. package/docs/run-settings-v1.md +147 -0
  26. package/docs/runtime-v1.md +255 -22
  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 +43 -7
  64. package/src/advisory.ts +2 -2
  65. package/src/candidate-artifacts.ts +42 -11
  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 +9 -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} +77 -87
  103. package/src/evaluation/trial.ts +800 -0
  104. package/src/{evaluation-types.ts → evaluation/types.ts} +79 -29
  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} +12 -72
  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/{workspace-state.ts → fixtures/workspace-state.ts} +5 -5
  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 +50 -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 +46 -9
  148. package/src/hosts/claude-skill-activations.ts +169 -0
  149. package/src/hosts/claude-state.ts +28 -15
  150. package/src/hosts/claude-tool-calls.ts +13 -10
  151. package/src/hosts/claude.ts +121 -81
  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 -183
  158. package/src/hosts/codex-profile.ts +36 -21
  159. package/src/hosts/codex-session-files.ts +1 -9
  160. package/src/hosts/codex-state.ts +134 -18
  161. package/src/hosts/codex.ts +210 -183
  162. package/src/hosts/isolation-common.ts +8 -11
  163. package/src/hosts/isolation-roots.ts +2 -2
  164. package/src/hosts/linux-sandbox.ts +15 -2
  165. package/src/hosts/mac-sandbox.ts +87 -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 +216 -48
  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 +26 -11
  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/{runtime-config.ts → runtime/config.ts} +160 -46
  186. package/src/{runtime-discovery-apple.ts → runtime/discovery-apple.ts} +3 -3
  187. package/src/{runtime-discovery-providers.ts → runtime/discovery-providers.ts} +3 -9
  188. package/src/{runtime-discovery.ts → runtime/discovery.ts} +4 -4
  189. package/src/{runtime-hook-processes.ts → runtime/hook-processes.ts} +1 -1
  190. package/src/{runtime-hooks.ts → runtime/hooks.ts} +32 -22
  191. package/src/{runtime-paths.ts → runtime/paths.ts} +5 -19
  192. package/src/{runtime-query.ts → runtime/query.ts} +5 -5
  193. package/src/{runtime-seeds.ts → runtime/seeds.ts} +3 -3
  194. package/src/runtime/shared-cache.ts +130 -0
  195. package/src/{runtime-state.ts → runtime/state.ts} +28 -11
  196. package/src/schema-types.ts +73 -161
  197. package/src/shell.ts +4 -0
  198. package/src/transcript-retention.ts +146 -0
  199. package/src/value-guards.ts +22 -0
  200. package/docs/assets/sevro-logo.png +0 -0
  201. package/docs/assets/sevro-symbol.svg +0 -1
  202. package/docs/assets/sevro-wordmark.svg +0 -1
  203. package/docs/cache-ownership-validation.md +0 -49
  204. package/docs/claude-continuation-validation.md +0 -47
  205. package/docs/concurrency-validation.md +0 -86
  206. package/docs/configuration-root-validation.md +0 -70
  207. package/docs/engine-slice.md +0 -114
  208. package/docs/evidence/runtime-startup-30/README.md +0 -185
  209. package/docs/evidence/runtime-startup-30/after.json +0 -319
  210. package/docs/evidence/runtime-startup-30/baseline-timeout.jsonl +0 -2
  211. package/docs/evidence/runtime-startup-30/before.json +0 -403
  212. package/docs/evidence/runtime-startup-30/consumer-smoke.json +0 -63
  213. package/docs/evidence/runtime-startup-30/full-policy-diagnostic.jsonl +0 -3
  214. package/docs/evidence/runtime-startup-30/minimal-policy-diagnostic.jsonl +0 -3
  215. package/docs/evidence/runtime-startup-30/plugin-template-assessment.json +0 -20
  216. package/docs/evidence/runtime-startup-30/seedless-timeout.jsonl +0 -2
  217. package/docs/evidence.md +0 -17
  218. package/docs/fixture-cleanup-validation.md +0 -73
  219. package/docs/guide-verification.json +0 -239
  220. package/src/evaluation-context.ts +0 -259
  221. package/src/evaluation-fixture.ts +0 -302
  222. package/src/evaluation-run.ts +0 -200
  223. package/src/evaluation-trial.ts +0 -1016
  224. package/src/hosts/claude-nested-skills.ts +0 -272
  225. package/src/hosts/codex-command-output.ts +0 -510
  226. package/src/hosts/codex-executor-command.ts +0 -291
  227. package/src/hosts/codex-skill-reads.ts +0 -783
  228. /package/src/{trial-scheduler.ts → evaluation/scheduler.ts} +0 -0
@@ -0,0 +1,21 @@
1
+ {
2
+ "id": "recipe-coding-change",
3
+ "prompt": "Fix add in math.cjs so it adds its two numeric arguments. Do not modify README.md. Explain how to start the application.",
4
+ "fixture": {
5
+ "files": {
6
+ "README.md": "Fix add in math.cjs. Start the application with bun run dev on port 3000.\n",
7
+ "math.cjs": "exports.add = (a, b) => a - b;\n"
8
+ }
9
+ },
10
+ "checks": [
11
+ {
12
+ "id": "addition",
13
+ "grader": "sevro.shell",
14
+ "configuration": {
15
+ "run": "{{evaluator-test}}",
16
+ "expectExact": "coding checks passed"
17
+ }
18
+ }
19
+ ],
20
+ "requiredEvidence": []
21
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "id": "recipe-host-tools",
3
+ "prompt": "Read app.env with rg, then explain the application's start command and port.",
4
+ "fixture": {
5
+ "files": {
6
+ "README.md": "Start with bun run dev on port 3000.\n",
7
+ "app.env": "port=3000\n"
8
+ }
9
+ },
10
+ "checks": [
11
+ {
12
+ "id": "rg-access",
13
+ "grader": "sevro.shell",
14
+ "configuration": {
15
+ "run": "rg '^port=3000$' app.env",
16
+ "expectExact": "port=3000"
17
+ }
18
+ }
19
+ ],
20
+ "requiredEvidence": []
21
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "id": "recipe-mise",
3
+ "prompt": "Use node from PATH to read app.json, then explain the application's start command and port. The operator has resolved mise's existing node@24.13.0 installation; do not download anything.",
4
+ "fixture": {
5
+ "files": {
6
+ "README.md": "Start with bun run dev on port 3000.\n",
7
+ "app.json": "{\"port\":3000}\n"
8
+ }
9
+ },
10
+ "checks": [
11
+ {
12
+ "id": "mise-node-access",
13
+ "grader": "sevro.shell",
14
+ "configuration": {
15
+ "run": "node -e 'if (process.version !== \"v24.13.0\") process.exit(1); const a = require(\"./app.json\"); if (a.port !== 3000) process.exit(1); console.log(a.port)'",
16
+ "expectExact": "3000"
17
+ }
18
+ }
19
+ ],
20
+ "requiredEvidence": []
21
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "id": "recipe-node",
3
+ "prompt": "Read app.json with Node and explain the application's start command and port.",
4
+ "fixture": {
5
+ "files": {
6
+ "README.md": "Start with bun run dev on port 3000.\n",
7
+ "app.json": "{\"port\":3000}\n"
8
+ }
9
+ },
10
+ "checks": [
11
+ {
12
+ "id": "node-access",
13
+ "grader": "sevro.shell",
14
+ "configuration": {
15
+ "run": "node -e 'const a = require(\"./app.json\"); if (a.port !== 3000) process.exit(1); console.log(a.port)'",
16
+ "expectExact": "3000"
17
+ }
18
+ }
19
+ ],
20
+ "requiredEvidence": []
21
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "id": "recipe-python-uv",
3
+ "prompt": "Use uv run --offline --no-project --python \"$RECIPE_PYTHON\" python to read app.json, then explain the application's start command and port.",
4
+ "fixture": {
5
+ "files": {
6
+ "README.md": "Start with bun run dev on port 3000.\n",
7
+ "app.json": "{\"port\":3000}\n"
8
+ }
9
+ },
10
+ "checks": [
11
+ {
12
+ "id": "uv-python-access",
13
+ "grader": "sevro.shell",
14
+ "configuration": {
15
+ "run": "uv run --offline --no-project --python \"$RECIPE_PYTHON\" python -c 'import json; p=json.load(open(\"app.json\"))[\"port\"]; assert p == 3000; print(p)'",
16
+ "expectExact": "3000"
17
+ }
18
+ }
19
+ ],
20
+ "requiredEvidence": []
21
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "id": "recipe-semantic",
3
+ "prompt": "Read README.md and explain how to start the application, including its port.",
4
+ "fixture": {
5
+ "files": { "README.md": "Start with bun run dev on port 3000.\n" }
6
+ },
7
+ "checks": [
8
+ {
9
+ "id": "startup-meaning",
10
+ "grader": "sevro.semantic",
11
+ "configuration": {
12
+ "proposition": "The response tells the reader to run bun run dev and says that the application listens on port 3000."
13
+ }
14
+ }
15
+ ],
16
+ "requiredEvidence": []
17
+ }
@@ -0,0 +1,23 @@
1
+ import assert from "node:assert/strict";
2
+ import { join } from "node:path";
3
+ import { pathToFileURL } from "node:url";
4
+
5
+ interface Addition {
6
+ add: (left: number, right: number) => number;
7
+ }
8
+ function isAddition(value: unknown): value is Addition {
9
+ return (
10
+ typeof value === "object" &&
11
+ value !== null &&
12
+ "add" in value &&
13
+ typeof value.add === "function"
14
+ );
15
+ }
16
+ const candidate: unknown = await import(
17
+ pathToFileURL(join(process.cwd(), "math.cjs")).href
18
+ );
19
+ assert.ok(isAddition(candidate), "Candidate must export add");
20
+ assert.equal(candidate.add(2, 3), 5);
21
+ assert.equal(candidate.add(-2, 3), 1);
22
+ assert.equal(candidate.add(0, 0), 0);
23
+ console.log("coding checks passed");
@@ -0,0 +1,65 @@
1
+ import { createHash } from "node:crypto";
2
+ import { readFile } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+
5
+ export function artifact(relativePath: string, content: string) {
6
+ return {
7
+ id: `recipes.${createHash("sha256").update(relativePath).digest("hex").slice(0, 16)}`,
8
+ relativePath,
9
+ sha256: createHash("sha256").update(content).digest("hex"),
10
+ contentBase64: Buffer.from(content).toString("base64"),
11
+ gitExclude: true,
12
+ };
13
+ }
14
+
15
+ export async function repositoryArtifacts(host: string) {
16
+ const directory = host === "claude" ? ".claude" : ".agents";
17
+ const relative = `${directory}/skills/startup-guide/SKILL.md`;
18
+ const body = await readFile(
19
+ join(import.meta.dir, "repository-skill", relative),
20
+ "utf8",
21
+ );
22
+ return [artifact(relative, body)];
23
+ }
24
+
25
+ export function pluginArtifacts() {
26
+ const files = {
27
+ "marketplace/.claude-plugin/marketplace.json": JSON.stringify({
28
+ name: "recipes",
29
+ owner: { name: "Sevro examples" },
30
+ plugins: [
31
+ { name: "startup", source: "./plugin", description: "Startup recipe" },
32
+ ],
33
+ }),
34
+ "marketplace/plugin/.claude-plugin/plugin.json": JSON.stringify({
35
+ name: "startup",
36
+ version: "1.0.0",
37
+ }),
38
+ "marketplace/plugin/.codex-plugin/plugin.json": JSON.stringify({
39
+ name: "startup",
40
+ version: "1.0.0",
41
+ skills: "./skills/",
42
+ }),
43
+ "marketplace/plugin/skills/startup-guide/SKILL.md":
44
+ "---\nname: startup-guide\ndescription: Explain application startup.\n---\nRead README.md. Explain its command and port.\n",
45
+ "marketplace/plugin/hooks/hooks.json": JSON.stringify({
46
+ hooks: {
47
+ SessionStart: [
48
+ {
49
+ hooks: [
50
+ {
51
+ type: "command",
52
+ command: '/bin/sh "${CLAUDE_PLUGIN_ROOT}/hooks/start.sh"',
53
+ },
54
+ ],
55
+ },
56
+ ],
57
+ },
58
+ }),
59
+ "marketplace/plugin/hooks/start.sh":
60
+ "#!/bin/sh\nprintf 'hook ran\\n' > \"$PWD/hook-ran.txt\"\n",
61
+ };
62
+ return Object.entries(files).map(([path, content]) =>
63
+ artifact(path, content),
64
+ );
65
+ }
@@ -0,0 +1,174 @@
1
+ import {
2
+ artifact,
3
+ pluginArtifacts,
4
+ repositoryArtifacts,
5
+ } from "./extension-artifacts";
6
+
7
+ export function record(value: unknown): Record<string, unknown> {
8
+ if (value === null || typeof value !== "object" || Array.isArray(value))
9
+ throw new Error("Expected a JSON object");
10
+ return value as Record<string, unknown>;
11
+ }
12
+
13
+ export function describe(mode: string, host: string) {
14
+ const capabilities: Record<string, string[]> = {
15
+ lifecycle: [],
16
+ "repository-skill": [`sevro.${host}.repository-invocation`],
17
+ plugins:
18
+ host === "codex"
19
+ ? ["sevro.codex.plugin-marketplace", "sevro.codex.explicit-invocation"]
20
+ : ["sevro.claude.plugin-dirs", "sevro.claude.explicit-invocation"],
21
+ };
22
+ const requiredCapabilities = capabilities[mode];
23
+ if (!requiredCapabilities) throw new Error("Unknown extension recipe");
24
+ return {
25
+ extension: { id: "recipes.startup", version: "1.0.0" },
26
+ protocols: ["sevro.extension.v1"],
27
+ requiredCapabilities,
28
+ optionalCapabilities: [],
29
+ graders: ["recipes.startup"],
30
+ taskVerdictPolicies: [],
31
+ };
32
+ }
33
+
34
+ function requiredInvocation(mode: string, host: string): string[] {
35
+ if (mode !== "repository-skill") return [];
36
+ return [
37
+ host === "codex"
38
+ ? "sevro.codex.explicit-invocation"
39
+ : "sevro.claude.repository-invocation",
40
+ ];
41
+ }
42
+
43
+ export function resolveCase(mode: string, host: string, fail: boolean) {
44
+ const invocation = mode === "lifecycle" ? "" : "{{sevro.skill_invocation}} ";
45
+ const prompt = fail
46
+ ? "Deliberate negative control: return only wrong."
47
+ : "Read README.md. Explain the application's start command and port. Do not change files.";
48
+ const requiredEvidence = requiredInvocation(mode, host);
49
+ return {
50
+ cases: [
51
+ {
52
+ id: "recipe-extension",
53
+ prompt: invocation + prompt,
54
+ fixture: {
55
+ kind: "generated",
56
+ commits: [
57
+ {
58
+ message: "chore: initialize sample",
59
+ files: { "README.md": "Run bun run dev on port 3000.\n" },
60
+ },
61
+ ],
62
+ },
63
+ checks: [
64
+ {
65
+ id: "startup-command",
66
+ grader: "sevro.regex",
67
+ configuration: { pattern: "bun run dev" },
68
+ },
69
+ {
70
+ id: "prepared-input",
71
+ grader: "sevro.shell",
72
+ configuration: {
73
+ run: "test \"$(cat startup.txt)\" = 'prepared startup data'",
74
+ },
75
+ },
76
+ ...(mode === "plugins"
77
+ ? [
78
+ {
79
+ id: "plugin-hook",
80
+ grader: "sevro.shell",
81
+ configuration: {
82
+ run: "cat hook-ran.txt",
83
+ expectExact: "hook ran",
84
+ },
85
+ },
86
+ ]
87
+ : []),
88
+ {
89
+ id: "recipes.startup.answer",
90
+ grader: "recipes.startup",
91
+ configuration: {},
92
+ },
93
+ ],
94
+ requiredEvidence,
95
+ extensionData: { "recipes.startup": { mode, host } },
96
+ },
97
+ ],
98
+ };
99
+ }
100
+
101
+ export async function prepare(mode: string, host: string) {
102
+ const base = {
103
+ artifacts: [artifact("startup.txt", "prepared startup data\n")],
104
+ requestedInstrumentation: [],
105
+ extensionData: { "recipes.startup": { mode, host } },
106
+ };
107
+ switch (mode) {
108
+ case "repository-skill":
109
+ return {
110
+ ...base,
111
+ artifacts: [...base.artifacts, ...(await repositoryArtifacts(host))],
112
+ ...(host === "codex"
113
+ ? { codexRepositorySkillInvocation: { skillName: "startup-guide" } }
114
+ : {
115
+ claudeRepositorySkillInvocation: { skillName: "startup-guide" },
116
+ }),
117
+ };
118
+ case "plugins":
119
+ return {
120
+ ...base,
121
+ artifacts: [...base.artifacts, ...pluginArtifacts()],
122
+ ...(host === "codex"
123
+ ? {
124
+ codexMarketplace: {
125
+ artifactRoot: "marketplace",
126
+ marketplaceName: "recipes",
127
+ pluginNames: ["startup"],
128
+ },
129
+ codexSkillInvocation: {
130
+ pluginName: "startup",
131
+ skillName: "startup-guide",
132
+ },
133
+ }
134
+ : {
135
+ claudePluginDirs: { artifactRoots: ["marketplace/plugin"] },
136
+ claudeSkillInvocation: {
137
+ pluginName: "startup",
138
+ skillName: "startup-guide",
139
+ },
140
+ }),
141
+ };
142
+ default:
143
+ return base;
144
+ }
145
+ }
146
+
147
+ function answerStatus(observation: Record<string, unknown>) {
148
+ if (observation.completeness !== "complete") return "unavailable";
149
+ const text = record(observation.data).text;
150
+ return typeof text === "string" && /\b3000\b/.test(text)
151
+ ? "passed"
152
+ : "failed";
153
+ }
154
+
155
+ export function evaluate(params: Record<string, unknown>) {
156
+ if (!Array.isArray(params.observations))
157
+ throw new Error("Missing observations");
158
+ const observations: unknown[] = params.observations;
159
+ const observation = observations
160
+ .map(record)
161
+ .find((item) => item.id === "sevro.observation.final-message");
162
+ if (!observation) throw new Error("Missing final-message observation");
163
+ const status = answerStatus(observation);
164
+ return {
165
+ checks: [
166
+ {
167
+ id: "recipes.startup.answer",
168
+ status,
169
+ evidenceRefs: ["sevro.observation.final-message"],
170
+ },
171
+ ],
172
+ metrics: [],
173
+ };
174
+ }
@@ -0,0 +1,37 @@
1
+ import {
2
+ describe,
3
+ evaluate,
4
+ prepare,
5
+ record,
6
+ resolveCase,
7
+ } from "./extension-methods";
8
+
9
+ const mode = process.argv[2] ?? "lifecycle";
10
+ const host = process.argv[3] ?? "deterministic";
11
+ const fail = process.argv[4] === "fail";
12
+ const request = record(JSON.parse(await Bun.stdin.text()) as unknown);
13
+
14
+ async function result() {
15
+ switch (request.method) {
16
+ case "describe":
17
+ return describe(mode, host);
18
+ case "resolve":
19
+ return resolveCase(mode, host, fail);
20
+ case "prepare":
21
+ return await prepare(mode, host);
22
+ case "evaluate":
23
+ return evaluate(record(request.params));
24
+ default:
25
+ throw new Error("Unsupported extension method");
26
+ }
27
+ }
28
+
29
+ // One request per process; stdout contains only the matching protocol response.
30
+ process.stdout.write(
31
+ JSON.stringify({
32
+ protocol: request.protocol,
33
+ id: request.id,
34
+ method: request.method,
35
+ result: await result(),
36
+ }),
37
+ );
@@ -0,0 +1,7 @@
1
+ ---
2
+ name: startup-guide
3
+ description: Explain how to start this sample application and identify its port.
4
+ ---
5
+
6
+ Read README.md. Explain the exact start command and port from that file.
7
+ Do not change files. Do not invent a different command or port.
@@ -0,0 +1,7 @@
1
+ ---
2
+ name: startup-guide
3
+ description: Explain how to start this sample application and identify its port.
4
+ ---
5
+
6
+ Read README.md. Explain the exact start command and port from that file.
7
+ Do not change files. Do not invent a different command or port.
@@ -0,0 +1,3 @@
1
+ # Sample application
2
+
3
+ Run `bun run dev` to start the application on port `3000`.
@@ -0,0 +1,188 @@
1
+ import { readFile, writeFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { record } from "./extension-methods";
4
+ import { requireTool, type RecipeOptions } from "./run-options";
5
+
6
+ function shellQuote(text: string): string {
7
+ return `'${text.replaceAll("'", "'\\''")}'`;
8
+ }
9
+
10
+ async function caseFile(
11
+ options: RecipeOptions,
12
+ state: string,
13
+ ): Promise<string[]> {
14
+ const value = record(
15
+ JSON.parse(
16
+ await readFile(
17
+ join(import.meta.dir, "cases", `${options.recipe}.json`),
18
+ "utf8",
19
+ ),
20
+ ) as unknown,
21
+ );
22
+ if (options.recipe === "coding-change") {
23
+ const test = await readFile(
24
+ join(import.meta.dir, "coding-change/check.ts"),
25
+ "utf8",
26
+ );
27
+ const checks: unknown[] = Array.isArray(value.checks) ? value.checks : [];
28
+ const check = record(checks[0]);
29
+ const javascript = new Bun.Transpiler({ loader: "ts" }).transformSync(test);
30
+ const protectedTest = shellQuote(
31
+ join(import.meta.dir, "coding-change/check.ts"),
32
+ );
33
+ record(check.configuration).run =
34
+ `test ! -r ${protectedTest}\nnode --input-type=module -e ${shellQuote(javascript)}`;
35
+ }
36
+ if (options.fail) negativeFixture(value, options.recipe);
37
+ const path = join(state, "case.json");
38
+ await writeFile(path, JSON.stringify(value));
39
+ return ["--case-file", path];
40
+ }
41
+
42
+ function negativeFixture(value: Record<string, unknown>, recipe: string): void {
43
+ if (["coding-change", "semantic"].includes(recipe)) {
44
+ value.prompt =
45
+ "Deliberate negative control: leave math.cjs unchanged if present and respond only: npm start on port 9000.";
46
+ return;
47
+ }
48
+ const files = record(record(value.fixture).files);
49
+ for (const [path, content] of Object.entries(files)) {
50
+ if (typeof content !== "string")
51
+ throw new Error("Expected inline fixture text");
52
+ files[path] = content.replaceAll("3000", "9000");
53
+ }
54
+ }
55
+
56
+ async function extensionFiles(
57
+ options: RecipeOptions,
58
+ state: string,
59
+ ): Promise<string[]> {
60
+ const command = join(state, "extension-command.json");
61
+ await writeFile(
62
+ command,
63
+ JSON.stringify([
64
+ process.execPath,
65
+ join(import.meta.dir, "extension.ts"),
66
+ options.recipe,
67
+ options.route,
68
+ options.fail ? "fail" : "pass",
69
+ ]),
70
+ );
71
+ const sources = [
72
+ "extension.ts",
73
+ "extension-methods.ts",
74
+ "extension-artifacts.ts",
75
+ "repository-skill/.agents/skills/startup-guide/SKILL.md",
76
+ "repository-skill/.claude/skills/startup-guide/SKILL.md",
77
+ ];
78
+ return [
79
+ "--extension-command-file",
80
+ command,
81
+ "--case-id",
82
+ "recipe-extension",
83
+ ...sources.flatMap((path) => [
84
+ "--extension-source-file",
85
+ join(import.meta.dir, path),
86
+ ]),
87
+ ];
88
+ }
89
+
90
+ export async function recipeInputs(
91
+ options: RecipeOptions,
92
+ state: string,
93
+ ): Promise<string[]> {
94
+ if (["lifecycle", "repository-skill", "plugins"].includes(options.recipe))
95
+ return extensionFiles(options, state);
96
+ return caseFile(options, state);
97
+ }
98
+
99
+ async function codexAuthentication(
100
+ options: RecipeOptions,
101
+ state: string,
102
+ ): Promise<string> {
103
+ if (!options.dry) {
104
+ const path = process.env.SEVRO_RECIPE_CODEX_AUTH;
105
+ if (!path)
106
+ throw new Error(
107
+ "Set SEVRO_RECIPE_CODEX_AUTH to your private Codex auth.json path",
108
+ );
109
+ return path;
110
+ }
111
+ const path = join(state, "dry-auth.json");
112
+ await writeFile(path, "{}\n");
113
+ return path;
114
+ }
115
+
116
+ export async function hostArguments(
117
+ options: RecipeOptions,
118
+ state: string,
119
+ ): Promise<string[]> {
120
+ if (options.route === "deterministic")
121
+ return ["--adapter-module", join(import.meta.dir, "candidate.ts")];
122
+ const model = selectedModel(options.dry, "SEVRO_RECIPE_MODEL");
123
+ if (!model)
124
+ throw new Error(
125
+ "Set SEVRO_RECIPE_MODEL to an available model for the selected native host",
126
+ );
127
+ const args = [
128
+ "--host",
129
+ options.route,
130
+ "--model",
131
+ model,
132
+ "--effort",
133
+ "medium",
134
+ ];
135
+ if (options.route === "codex")
136
+ return [
137
+ ...args,
138
+ "--codex-bin",
139
+ requireTool("codex"),
140
+ "--codex-auth-file",
141
+ await codexAuthentication(options, state),
142
+ ];
143
+ return [
144
+ ...args,
145
+ "--claude-bin",
146
+ requireTool("claude"),
147
+ ...(options.recipe === "repository-skill"
148
+ ? ["--claude-project-settings"]
149
+ : []),
150
+ ];
151
+ }
152
+
153
+ export async function semanticArguments(
154
+ options: RecipeOptions,
155
+ state: string,
156
+ ): Promise<string[]> {
157
+ if (options.recipe !== "semantic") return [];
158
+ if (options.route === "deterministic")
159
+ return [
160
+ "--semantic-adapter-module",
161
+ join(import.meta.dir, "semantic-judge.ts"),
162
+ ];
163
+ const model = selectedModel(options.dry, "SEVRO_RECIPE_JUDGE_MODEL");
164
+ if (!model)
165
+ throw new Error(
166
+ "Set SEVRO_RECIPE_JUDGE_MODEL for the separate Codex judge route",
167
+ );
168
+ const args = [
169
+ "--semantic-host",
170
+ "codex",
171
+ "--semantic-model",
172
+ model,
173
+ "--semantic-effort",
174
+ "medium",
175
+ ];
176
+ if (options.route === "codex") return args;
177
+ return [
178
+ ...args,
179
+ "--codex-bin",
180
+ requireTool("codex"),
181
+ "--codex-auth-file",
182
+ await codexAuthentication(options, state),
183
+ ];
184
+ }
185
+
186
+ function selectedModel(dry: boolean, variable: string): string | undefined {
187
+ return dry ? "dry-model" : process.env[variable];
188
+ }