@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.
Files changed (243) hide show
  1. package/CONTRIBUTING.md +170 -0
  2. package/LICENSE +63 -136
  3. package/README.md +83 -66
  4. package/docs/README.md +44 -0
  5. package/docs/acknowledgements.md +14 -0
  6. package/docs/architecture.md +146 -0
  7. package/docs/assets/README.md +27 -0
  8. package/docs/assets/sevro-logo.png +0 -0
  9. package/docs/assets/sevro-symbol.svg +1 -0
  10. package/docs/assets/sevro-wordmark.svg +1 -0
  11. package/docs/builtin-graders-v1.md +41 -13
  12. package/docs/codex-host.md +184 -52
  13. package/docs/contracts.md +19 -0
  14. package/docs/creating-cases.md +139 -0
  15. package/docs/development-cli.md +190 -30
  16. package/docs/documentation-quality.md +61 -0
  17. package/docs/evaluation-workflow.md +110 -0
  18. package/docs/evidence/code-mode-controls-71.md +64 -0
  19. package/docs/evidence/code-mode-programs-85.md +64 -0
  20. package/docs/evidence/codex-state-73/README.md +204 -0
  21. package/docs/evidence/codex-state-73/darrow-migration.patch +182 -0
  22. package/docs/evidence/codex-state-73/results.json +661 -0
  23. package/docs/evidence/local-http-74.md +104 -0
  24. package/docs/evidence/runtime-startup-30/README.md +185 -0
  25. package/docs/evidence/runtime-startup-30/after.json +319 -0
  26. package/docs/evidence/runtime-startup-30/baseline-timeout.jsonl +2 -0
  27. package/docs/evidence/runtime-startup-30/before.json +403 -0
  28. package/docs/evidence/runtime-startup-30/consumer-smoke.json +63 -0
  29. package/docs/evidence/runtime-startup-30/full-policy-diagnostic.jsonl +3 -0
  30. package/docs/evidence/runtime-startup-30/minimal-policy-diagnostic.jsonl +3 -0
  31. package/docs/evidence/runtime-startup-30/plugin-template-assessment.json +20 -0
  32. package/docs/evidence/runtime-startup-30/seedless-timeout.jsonl +2 -0
  33. package/docs/evidence/skill-reads-70/results.json +823 -0
  34. package/docs/evidence.md +21 -0
  35. package/docs/extension-protocol-v1.md +50 -16
  36. package/docs/fixture-cleanup-validation.md +3 -0
  37. package/docs/getting-started.md +102 -0
  38. package/docs/guide-evaluation.md +89 -0
  39. package/docs/guide-verification.json +239 -0
  40. package/docs/identity-v1.md +35 -1
  41. package/docs/installing.md +88 -0
  42. package/docs/licenses/BUSL-1.1-0.1.0-rc.2.txt +159 -0
  43. package/docs/licensing.md +42 -0
  44. package/docs/native-controls.md +76 -5
  45. package/docs/native-hosts.md +228 -0
  46. package/docs/reading-results.md +141 -0
  47. package/docs/recipes.md +489 -0
  48. package/docs/releases.md +81 -40
  49. package/docs/report-v1.md +29 -1
  50. package/docs/results-v1.md +180 -0
  51. package/docs/run-settings-v1.md +147 -0
  52. package/docs/running-evaluations.md +31 -0
  53. package/docs/runtime-v1.md +614 -0
  54. package/docs/specs/repository-guide.md +58 -0
  55. package/docs/troubleshooting.md +30 -0
  56. package/docs/typescript-quality.md +234 -0
  57. package/docs/usability-study.md +114 -0
  58. package/examples/basic/host.ts +2 -2
  59. package/examples/recipes/candidate.ts +26 -0
  60. package/examples/recipes/cases/coding-change.json +21 -0
  61. package/examples/recipes/cases/host-tools.json +21 -0
  62. package/examples/recipes/cases/mise.json +21 -0
  63. package/examples/recipes/cases/node.json +21 -0
  64. package/examples/recipes/cases/python-uv.json +21 -0
  65. package/examples/recipes/cases/semantic.json +17 -0
  66. package/examples/recipes/coding-change/check.ts +23 -0
  67. package/examples/recipes/extension-artifacts.ts +65 -0
  68. package/examples/recipes/extension-methods.ts +174 -0
  69. package/examples/recipes/extension.ts +37 -0
  70. package/examples/recipes/repository-skill/.agents/skills/startup-guide/SKILL.md +7 -0
  71. package/examples/recipes/repository-skill/.claude/skills/startup-guide/SKILL.md +7 -0
  72. package/examples/recipes/repository-skill/README.md +3 -0
  73. package/examples/recipes/run-inputs.ts +188 -0
  74. package/examples/recipes/run-options.ts +131 -0
  75. package/examples/recipes/run.ts +63 -0
  76. package/examples/recipes/runtime/empty.json +1 -0
  77. package/examples/recipes/runtime/goals.json +1 -0
  78. package/examples/recipes/runtime/mise.json +6 -0
  79. package/examples/recipes/runtime/path.json +1 -0
  80. package/examples/recipes/runtime/plugins.json +4 -0
  81. package/examples/recipes/runtime/python-uv.json +10 -0
  82. package/examples/recipes/semantic-judge.ts +29 -0
  83. package/examples/templates/coding/adapter.ts +17 -0
  84. package/examples/templates/coding/case.json +15 -0
  85. package/examples/templates/repository-skill/.agents/skills/startup-guide/SKILL.md +6 -0
  86. package/examples/templates/repository-skill/extension-data.ts +64 -0
  87. package/examples/templates/repository-skill/extension.ts +31 -0
  88. package/examples/templates/response/adapter.ts +12 -0
  89. package/examples/templates/response/case.json +13 -0
  90. package/package.json +39 -6
  91. package/schemas/report-v1.schema.json +9 -0
  92. package/schemas/run-evidence-v1.schema.json +2 -1
  93. package/schemas/runtime-v1.schema.json +193 -0
  94. package/src/advisory.ts +10 -5
  95. package/src/candidate-artifacts.ts +208 -0
  96. package/src/candidate-codex-state.ts +185 -0
  97. package/src/case-validation.ts +105 -0
  98. package/src/cli/cancellation.ts +20 -0
  99. package/src/cli/command.ts +81 -0
  100. package/src/cli/completion.ts +15 -0
  101. package/src/cli/demo.ts +59 -0
  102. package/src/cli/evaluation.ts +55 -0
  103. package/src/cli/extension.ts +79 -0
  104. package/src/cli/help.ts +222 -0
  105. package/src/cli/hosts.ts +59 -0
  106. package/src/cli/init-settings.ts +30 -0
  107. package/src/cli/init.ts +111 -0
  108. package/src/cli/invocation.ts +627 -0
  109. package/src/cli/loading.ts +151 -0
  110. package/src/cli/preparation.ts +163 -0
  111. package/src/cli/presentation.ts +91 -0
  112. package/src/cli/progress.ts +6 -0
  113. package/src/cli/run-manifest.ts +133 -0
  114. package/src/cli/run-options.ts +101 -0
  115. package/src/cli/run-overrides.ts +83 -0
  116. package/src/cli/settings.ts +160 -0
  117. package/src/cli/summary.ts +43 -0
  118. package/src/cli.ts +13 -843
  119. package/src/contract-types.ts +94 -0
  120. package/src/engine.ts +19 -2373
  121. package/src/evaluation/advisory.ts +150 -0
  122. package/src/evaluation/check-plan.ts +510 -0
  123. package/src/evaluation/collection.ts +88 -0
  124. package/src/evaluation/context.ts +10 -0
  125. package/src/evaluation/grading-host.ts +39 -0
  126. package/src/evaluation/grading.ts +258 -0
  127. package/src/evaluation/invocation-plan.ts +548 -0
  128. package/src/evaluation/plan.ts +736 -0
  129. package/src/evaluation/progress.ts +19 -0
  130. package/src/evaluation/resources.ts +241 -0
  131. package/src/evaluation/run.ts +206 -0
  132. package/src/{trial-scheduler.ts → evaluation/scheduler.ts} +3 -1
  133. package/src/evaluation/semantic.ts +312 -0
  134. package/src/evaluation/trial.ts +866 -0
  135. package/src/evaluation/types.ts +207 -0
  136. package/src/evidence/host-results.ts +92 -0
  137. package/src/evidence/retained-artifacts.ts +18 -0
  138. package/src/evidence/trial-writer.ts +203 -0
  139. package/src/evidence/usage.ts +17 -0
  140. package/src/extension-client.ts +228 -148
  141. package/src/extension-session.ts +326 -195
  142. package/src/fixtures/advisory.ts +338 -0
  143. package/src/fixtures/bin.ts +57 -0
  144. package/src/{fixture-cleanup.ts → fixtures/cleanup.ts} +13 -7
  145. package/src/fixtures/create.ts +187 -0
  146. package/src/fixtures/generated.ts +300 -0
  147. package/src/fixtures/git-hooks.ts +24 -0
  148. package/src/fixtures/preparation.ts +270 -0
  149. package/src/{repository-fixture.ts → fixtures/repository.ts} +207 -85
  150. package/src/fixtures/setup.ts +224 -0
  151. package/src/fixtures/tools.ts +88 -0
  152. package/src/fixtures/workspace-state.ts +212 -0
  153. package/src/generated/cli-result.cjs +2 -0
  154. package/src/generated/cli-result.d.cts +3 -0
  155. package/src/generated/extension.cjs +2 -0
  156. package/src/generated/extension.d.cts +3 -0
  157. package/src/generated/report.cjs +2 -0
  158. package/src/generated/report.d.cts +3 -0
  159. package/src/generated/run-evidence.cjs +2 -0
  160. package/src/generated/run-evidence.d.cts +3 -0
  161. package/src/generated/runtime.cjs +2 -0
  162. package/src/generated/runtime.d.cts +3 -0
  163. package/src/graders/artifact.ts +56 -23
  164. package/src/graders/git-head.ts +79 -75
  165. package/src/graders/output.ts +222 -140
  166. package/src/graders/semantic.ts +102 -43
  167. package/src/graders/shell.ts +379 -207
  168. package/src/host-diagnostic-redaction.ts +52 -0
  169. package/src/host-diagnostics.ts +319 -0
  170. package/src/hosts/claude-continuation.ts +63 -18
  171. package/src/hosts/claude-credential.ts +43 -62
  172. package/src/hosts/claude-events.ts +86 -51
  173. package/src/hosts/claude-keychain.ts +59 -0
  174. package/src/hosts/claude-native-goal.ts +57 -33
  175. package/src/hosts/claude-process.ts +111 -0
  176. package/src/hosts/claude-repository-invocation.ts +208 -117
  177. package/src/hosts/claude-settings.ts +240 -21
  178. package/src/hosts/claude-skill-activations.ts +166 -0
  179. package/src/hosts/claude-state.ts +403 -0
  180. package/src/hosts/claude-tool-calls.ts +171 -106
  181. package/src/hosts/claude.ts +561 -451
  182. package/src/hosts/codex-app-server.ts +326 -178
  183. package/src/hosts/codex-configuration.ts +44 -18
  184. package/src/hosts/codex-events.ts +97 -60
  185. package/src/hosts/codex-exec-controls.ts +516 -0
  186. package/src/hosts/codex-invocation.ts +70 -0
  187. package/src/hosts/codex-marketplace.ts +283 -0
  188. package/src/hosts/codex-native-calls.ts +546 -500
  189. package/src/hosts/codex-process.ts +202 -0
  190. package/src/hosts/codex-profile.ts +257 -28
  191. package/src/hosts/codex-session-files.ts +106 -0
  192. package/src/hosts/codex-state.ts +492 -0
  193. package/src/hosts/codex.ts +783 -832
  194. package/src/hosts/isolation-common.ts +94 -0
  195. package/src/hosts/isolation-roots.ts +54 -27
  196. package/src/hosts/isolation.ts +10 -0
  197. package/src/hosts/linux-sandbox.ts +159 -0
  198. package/src/hosts/mac-sandbox.ts +187 -61
  199. package/src/hosts/native-controls.ts +152 -62
  200. package/src/hosts/process-lifecycle.ts +37 -0
  201. package/src/hosts/protected-worktrees.ts +51 -19
  202. package/src/hosts/skill-hook-catalog.ts +131 -0
  203. package/src/hosts/skill-hook-handler.ts +374 -0
  204. package/src/hosts/skill-hooks.ts +580 -0
  205. package/src/hosts/workspace-fingerprint.ts +55 -35
  206. package/src/identity.ts +102 -48
  207. package/src/instrumentation.ts +109 -48
  208. package/src/native-transcript-state.ts +131 -0
  209. package/src/native-transcript-view.ts +90 -0
  210. package/src/native-transcripts.ts +286 -0
  211. package/src/process-lifecycle.ts +150 -0
  212. package/src/provenance.ts +161 -81
  213. package/src/prune.ts +215 -0
  214. package/src/report.ts +172 -140
  215. package/src/resolved-case.ts +33 -0
  216. package/src/results-lock.ts +29 -0
  217. package/src/results.ts +133 -86
  218. package/src/run-owner.ts +154 -63
  219. package/src/runtime/config.ts +456 -0
  220. package/src/runtime/discovery-apple.ts +90 -0
  221. package/src/runtime/discovery-providers.ts +63 -0
  222. package/src/runtime/discovery.ts +70 -0
  223. package/src/runtime/hook-processes.ts +97 -0
  224. package/src/runtime/hooks.ts +476 -0
  225. package/src/runtime/paths.ts +313 -0
  226. package/src/runtime/query.ts +85 -0
  227. package/src/runtime/seeds.ts +134 -0
  228. package/src/runtime/shared-cache.ts +143 -0
  229. package/src/runtime/state.ts +143 -0
  230. package/src/schema-types.ts +198 -0
  231. package/src/schema.ts +15 -19
  232. package/src/transcript-retention.ts +146 -0
  233. package/src/value-guards.ts +20 -0
  234. package/src/advisory-fixture.ts +0 -282
  235. package/src/fixture-bin.ts +0 -87
  236. package/src/fixture-setup.ts +0 -163
  237. package/src/generated-fixture.ts +0 -220
  238. package/src/git-hooks.ts +0 -63
  239. package/src/hosts/claude-nested-skills.ts +0 -218
  240. package/src/hosts/codex-command-output.ts +0 -399
  241. package/src/hosts/codex-executor-command.ts +0 -195
  242. package/src/hosts/codex-skill-reads.ts +0 -391
  243. package/src/preparation.ts +0 -211
@@ -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
+ }
@@ -0,0 +1,131 @@
1
+ import { delimiter, isAbsolute, join } from "node:path";
2
+ import { homedir } from "node:os";
3
+
4
+ export const recipeNames = [
5
+ "coding-change",
6
+ "host-tools",
7
+ "node",
8
+ "python-uv",
9
+ "mise",
10
+ "semantic",
11
+ "lifecycle",
12
+ "repository-skill",
13
+ "plugins",
14
+ ];
15
+
16
+ function validateSelection(
17
+ recipe: string,
18
+ route: string,
19
+ flags: string[],
20
+ ): void {
21
+ if (!recipeNames.includes(recipe))
22
+ throw new Error(`Choose a recipe: ${recipeNames.join(", ")}`);
23
+ if (!["deterministic", "codex", "claude"].includes(route))
24
+ throw new Error("Choose deterministic, codex, or claude");
25
+ if (flags.some((flag) => !["--dry", "--fail", "--goals-only"].includes(flag)))
26
+ throw new Error("Supported flags: --dry --fail --goals-only");
27
+ }
28
+
29
+ function validateNativeRecipe(recipe: string, route: string): void {
30
+ if (
31
+ ["repository-skill", "plugins"].includes(recipe) &&
32
+ route === "deterministic"
33
+ )
34
+ throw new Error(
35
+ "Skill and plugin recipes require codex or claude (add --dry to prepare without model calls)",
36
+ );
37
+ }
38
+
39
+ export function selectedOptions() {
40
+ const [recipe = "", route = "deterministic", ...flags] =
41
+ process.argv.slice(2);
42
+ validateSelection(recipe, route, flags);
43
+ validateNativeRecipe(recipe, route);
44
+ validateGoalsStep(flags);
45
+ return {
46
+ recipe,
47
+ route,
48
+ dry: flags.includes("--dry"),
49
+ fail: flags.includes("--fail"),
50
+ goalsOnly: flags.includes("--goals-only"),
51
+ };
52
+ }
53
+ export type RecipeOptions = ReturnType<typeof selectedOptions>;
54
+
55
+ function validateGoalsStep(flags: string[]): void {
56
+ if (flags.includes("--goals-only") && !flags.includes("--dry"))
57
+ throw new Error(
58
+ "--goals-only is a dry preparation step; omit it for the complete hook recipe",
59
+ );
60
+ }
61
+
62
+ export function requireTool(name: string): string {
63
+ const path = Bun.which(name);
64
+ if (!path)
65
+ throw new Error(
66
+ `Recipe prerequisite missing: install ${name} and put it on PATH`,
67
+ );
68
+ return path;
69
+ }
70
+
71
+ export function runtimeFile(options: RecipeOptions): string {
72
+ const files: Record<string, string> = {
73
+ "coding-change": "path",
74
+ "host-tools": "path",
75
+ node: "path",
76
+ "python-uv": "python-uv",
77
+ mise: "mise",
78
+ plugins: options.goalsOnly ? "goals" : "plugins",
79
+ };
80
+ return join(
81
+ import.meta.dir,
82
+ "runtime",
83
+ `${files[options.recipe] ?? "empty"}.json`,
84
+ );
85
+ }
86
+
87
+ function languagePrerequisites(recipe: string): void {
88
+ if (recipe === "python-uv")
89
+ process.env.RECIPE_PYTHON ??= requireTool("python3");
90
+ if (recipe === "mise") selectMiseNode();
91
+ if (recipe === "host-tools") requireStandaloneRipgrep();
92
+ }
93
+
94
+ function requireStandaloneRipgrep(): void {
95
+ const roots = [
96
+ process.env.CODEX_HOME ?? join(homedir(), ".codex"),
97
+ process.env.CLAUDE_CONFIG_DIR ?? join(homedir(), ".claude"),
98
+ ];
99
+ const paths = (process.env.PATH ?? "")
100
+ .split(delimiter)
101
+ .filter((entry) => !roots.some((root) => entry.startsWith(root)));
102
+ if (!Bun.which("rg", { PATH: paths.join(delimiter) }))
103
+ throw new Error(
104
+ "Install standalone ripgrep outside Codex/Claude credential directories, then put it on PATH (for example brew install ripgrep or apt-get install ripgrep)",
105
+ );
106
+ }
107
+
108
+ function selectMiseNode(): void {
109
+ const result = Bun.spawnSync([requireTool("mise"), "where", "node@24.13.0"], {
110
+ stdout: "pipe",
111
+ stderr: "pipe",
112
+ });
113
+ const installation = result.stdout.toString().trim();
114
+ if (result.exitCode !== 0 || !isAbsolute(installation))
115
+ throw new Error(
116
+ "Install the recipe's Node with mise install node@24.13.0 before running it",
117
+ );
118
+ process.env.PATH = `${join(installation, "bin")}${delimiter}${process.env.PATH ?? "/usr/bin:/bin"}`;
119
+ }
120
+
121
+ export function runtimePrerequisites(recipe: string): void {
122
+ const tools: Record<string, string[]> = {
123
+ "coding-change": ["node"],
124
+ "host-tools": ["rg"],
125
+ node: ["node"],
126
+ "python-uv": ["uv", "python3"],
127
+ mise: ["mise"],
128
+ };
129
+ for (const tool of tools[recipe] ?? []) requireTool(tool);
130
+ languagePrerequisites(recipe);
131
+ }
@@ -0,0 +1,63 @@
1
+ import { mkdir, realpath, writeFile } from "node:fs/promises";
2
+ import { join, resolve } from "node:path";
3
+ import { hostArguments, recipeInputs, semanticArguments } from "./run-inputs";
4
+ import {
5
+ runtimeFile,
6
+ runtimePrerequisites,
7
+ selectedOptions,
8
+ } from "./run-options";
9
+
10
+ const options = selectedOptions();
11
+ runtimePrerequisites(options.recipe);
12
+ const mode = options.dry ? "dry" : options.fail ? "fail" : "pass";
13
+ const state = resolve(
14
+ ".sevro-recipes",
15
+ options.recipe,
16
+ options.route,
17
+ options.goalsOnly ? "goals" : mode,
18
+ );
19
+ await mkdir(join(state, "project"), { recursive: true });
20
+ await mkdir(join(state, "config"), { recursive: true });
21
+ const root = await realpath(state);
22
+ const cli =
23
+ process.env.SEVRO_RECIPE_CLI ?? resolve(import.meta.dir, "../../src/cli.ts");
24
+ const args = [
25
+ process.execPath,
26
+ cli,
27
+ "run",
28
+ "--json",
29
+ "--project-root",
30
+ join(root, "project"),
31
+ "--config-root",
32
+ join(root, "config"),
33
+ "--results-root",
34
+ join(root, "results"),
35
+ "--runtime-config-file",
36
+ runtimeFile(options),
37
+ "--condition",
38
+ "passive",
39
+ "--trials",
40
+ "1",
41
+ "--threshold",
42
+ "1",
43
+ "--shell-isolation",
44
+ "--protected-root",
45
+ import.meta.dir,
46
+ ...(await recipeInputs(options, root)),
47
+ ...(await hostArguments(options, root)),
48
+ ...(await semanticArguments(options, root)),
49
+ ...(options.dry ? ["--dry"] : []),
50
+ ];
51
+ const child = Bun.spawn(args, {
52
+ env: { ...process.env, SEVRO_RECIPE_FAIL: options.fail ? "1" : "0" },
53
+ stdout: "pipe",
54
+ stderr: "inherit",
55
+ });
56
+ const [output, code] = await Promise.all([
57
+ new Response(child.stdout).text(),
58
+ child.exited,
59
+ ]);
60
+ await writeFile(join(root, "result.json"), output);
61
+ process.stderr.write(`Recipe result: ${join(root, "result.json")}\n`);
62
+ process.stdout.write(output);
63
+ process.exit(code);
@@ -0,0 +1 @@
1
+ { "format": "sevro.runtime.v1" }
@@ -0,0 +1 @@
1
+ { "format": "sevro.runtime.v1", "hooks": { "nativeGoal": true } }
@@ -0,0 +1,6 @@
1
+ {
2
+ "format": "sevro.runtime.v1",
3
+ "environment": {
4
+ "inherit": ["PATH"]
5
+ }
6
+ }
@@ -0,0 +1 @@
1
+ { "format": "sevro.runtime.v1", "environment": { "inherit": ["PATH"] } }