@turri-dev/cli 0.0.0-stage → 0.1.2-next.48dacfc

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 (315) hide show
  1. package/.pi/themes/turri-dark.json +80 -0
  2. package/.pi/themes/turri-light.json +82 -0
  3. package/LICENSE +21 -0
  4. package/README.md +272 -2
  5. package/assets/jellyfish/dark-00.png +0 -0
  6. package/assets/jellyfish/dark-01.png +0 -0
  7. package/assets/jellyfish/dark-02.png +0 -0
  8. package/assets/jellyfish/dark-03.png +0 -0
  9. package/assets/jellyfish/dark-04.png +0 -0
  10. package/assets/jellyfish/dark-05.png +0 -0
  11. package/assets/jellyfish/dark-06.png +0 -0
  12. package/assets/jellyfish/dark-07.png +0 -0
  13. package/assets/jellyfish/dark-08.png +0 -0
  14. package/assets/jellyfish/dark-09.png +0 -0
  15. package/assets/jellyfish/dark-10.png +0 -0
  16. package/assets/jellyfish/dark-11.png +0 -0
  17. package/assets/jellyfish/dark-12.png +0 -0
  18. package/assets/jellyfish/dark-13.png +0 -0
  19. package/assets/jellyfish/dark-14.png +0 -0
  20. package/assets/jellyfish/fallback.txt +252 -0
  21. package/assets/jellyfish/light-00.png +0 -0
  22. package/assets/jellyfish/light-01.png +0 -0
  23. package/assets/jellyfish/light-02.png +0 -0
  24. package/assets/jellyfish/light-03.png +0 -0
  25. package/assets/jellyfish/light-04.png +0 -0
  26. package/assets/jellyfish/light-05.png +0 -0
  27. package/assets/jellyfish/light-06.png +0 -0
  28. package/assets/jellyfish/light-07.png +0 -0
  29. package/assets/jellyfish/light-08.png +0 -0
  30. package/assets/jellyfish/light-09.png +0 -0
  31. package/assets/jellyfish/light-10.png +0 -0
  32. package/assets/jellyfish/light-11.png +0 -0
  33. package/assets/jellyfish/light-12.png +0 -0
  34. package/assets/jellyfish/light-13.png +0 -0
  35. package/assets/jellyfish/light-14.png +0 -0
  36. package/bin/turri.mjs +29 -0
  37. package/dist/ask.js +149 -0
  38. package/dist/brand.js +50 -0
  39. package/dist/btw.js +174 -0
  40. package/dist/chat-models.js +37 -0
  41. package/dist/chat-skills.js +18 -0
  42. package/dist/cli.js +178 -0
  43. package/dist/config-command.js +197 -0
  44. package/dist/config-edit.js +367 -0
  45. package/dist/config.js +489 -0
  46. package/dist/conform-cli.js +29 -0
  47. package/dist/cultivate-cli.js +110 -0
  48. package/dist/drift-cli.js +25 -0
  49. package/dist/environment.js +70 -0
  50. package/dist/extension.js +2372 -0
  51. package/dist/genome.js +301 -0
  52. package/dist/graph.js +246 -0
  53. package/dist/identity.js +35 -0
  54. package/dist/init-config.js +511 -0
  55. package/dist/judge.js +62 -0
  56. package/dist/lineage-cli.js +13 -0
  57. package/dist/loop-cli.js +132 -0
  58. package/dist/loop-tools.js +225 -0
  59. package/dist/mature-tools.js +193 -0
  60. package/dist/model-check.js +117 -0
  61. package/dist/module-report.js +9 -0
  62. package/dist/pi-launch.js +31 -0
  63. package/dist/pi.js +813 -0
  64. package/dist/plain/check.js +76 -0
  65. package/dist/plain/concepts.js +213 -0
  66. package/dist/plain/errors.js +43 -0
  67. package/dist/plain/files.js +57 -0
  68. package/dist/plain/highlight.js +195 -0
  69. package/dist/plain/index.js +15 -0
  70. package/dist/plain/liquid.js +129 -0
  71. package/dist/plain/markdown.js +208 -0
  72. package/dist/plain/parser.js +752 -0
  73. package/dist/plain/plain.tmLanguage.json +124 -0
  74. package/dist/plain/spec.js +291 -0
  75. package/dist/plain/stdlib.js +44 -0
  76. package/dist/question-dialog.js +125 -0
  77. package/dist/questioning.js +109 -0
  78. package/dist/quiet.js +125 -0
  79. package/dist/regenerate/activity.js +187 -0
  80. package/dist/regenerate/agent-env.js +190 -0
  81. package/dist/regenerate/agent-shell.js +114 -0
  82. package/dist/regenerate/arbiter.js +133 -0
  83. package/dist/regenerate/audit.js +203 -0
  84. package/dist/regenerate/cases.js +83 -0
  85. package/dist/regenerate/change-view.js +102 -0
  86. package/dist/regenerate/choose-conformance.js +117 -0
  87. package/dist/regenerate/chunk-plan.js +625 -0
  88. package/dist/regenerate/codebase.js +75 -0
  89. package/dist/regenerate/command-check.js +312 -0
  90. package/dist/regenerate/compaction.js +20 -0
  91. package/dist/regenerate/confine.js +310 -0
  92. package/dist/regenerate/conform.js +133 -0
  93. package/dist/regenerate/conformance-agents.js +955 -0
  94. package/dist/regenerate/conformance.js +936 -0
  95. package/dist/regenerate/conversation-writes.js +100 -0
  96. package/dist/regenerate/credentials.js +88 -0
  97. package/dist/regenerate/debug-log.js +228 -0
  98. package/dist/regenerate/debugger.js +90 -0
  99. package/dist/regenerate/delivery.js +19 -0
  100. package/dist/regenerate/diagnose-job.js +40 -0
  101. package/dist/regenerate/diagnose.js +81 -0
  102. package/dist/regenerate/diff-view.js +240 -0
  103. package/dist/regenerate/drift.js +194 -0
  104. package/dist/regenerate/engine.js +118 -0
  105. package/dist/regenerate/environment-error.js +220 -0
  106. package/dist/regenerate/job-log.js +102 -0
  107. package/dist/regenerate/job-panel.js +336 -0
  108. package/dist/regenerate/jobs-tree.js +300 -0
  109. package/dist/regenerate/jobs.js +427 -0
  110. package/dist/regenerate/lanes.js +32 -0
  111. package/dist/regenerate/lineage.js +384 -0
  112. package/dist/regenerate/loop.js +1435 -0
  113. package/dist/regenerate/masking.js +95 -0
  114. package/dist/regenerate/notes.js +397 -0
  115. package/dist/regenerate/os-sandbox.js +243 -0
  116. package/dist/regenerate/paths.js +165 -0
  117. package/dist/regenerate/plain-palette.js +83 -0
  118. package/dist/regenerate/prompt.js +704 -0
  119. package/dist/regenerate/records.js +294 -0
  120. package/dist/regenerate/regenerate.js +598 -0
  121. package/dist/regenerate/regression.js +181 -0
  122. package/dist/regenerate/release.js +262 -0
  123. package/dist/regenerate/requires.js +241 -0
  124. package/dist/regenerate/resources.js +164 -0
  125. package/dist/regenerate/self-check.js +111 -0
  126. package/dist/regenerate/services.js +155 -0
  127. package/dist/regenerate/shell.js +320 -0
  128. package/dist/regenerate/side-panel.js +278 -0
  129. package/dist/regenerate/spec-map-view.js +574 -0
  130. package/dist/regenerate/spec-map.js +561 -0
  131. package/dist/regenerate/spec-steps.js +527 -0
  132. package/dist/regenerate/state.js +10 -0
  133. package/dist/regenerate/steer.js +91 -0
  134. package/dist/regenerate/steering.js +119 -0
  135. package/dist/regenerate/stop-report.js +161 -0
  136. package/dist/regenerate/submit-work.js +71 -0
  137. package/dist/regenerate/test-kinds.js +124 -0
  138. package/dist/regenerate/test-scripts.js +695 -0
  139. package/dist/regenerate/testing.js +406 -0
  140. package/dist/regenerate/transcript-view.js +1 -0
  141. package/dist/regenerate/unit-gate.js +20 -0
  142. package/dist/regenerate/unit-review.js +347 -0
  143. package/dist/regenerate/web.js +256 -0
  144. package/dist/regenerate/workspace.js +99 -0
  145. package/dist/regenerate-cli.js +68 -0
  146. package/dist/release-agent.js +114 -0
  147. package/dist/release-cli.js +131 -0
  148. package/dist/release-tools.js +148 -0
  149. package/dist/review/live.js +165 -0
  150. package/dist/rules.js +47 -0
  151. package/dist/self-invocation.js +110 -0
  152. package/dist/setup.js +245 -0
  153. package/dist/skill-progress.js +78 -0
  154. package/dist/skill.js +149 -0
  155. package/dist/skills.js +74 -0
  156. package/dist/spec-diff-cli.js +37 -0
  157. package/dist/spec-tools.js +155 -0
  158. package/dist/stack.js +158 -0
  159. package/dist/stacks.js +97 -0
  160. package/dist/stdlib.js +100 -0
  161. package/dist/terminal-images.js +46 -0
  162. package/dist/tools.js +242 -0
  163. package/dist/validate-cli.js +52 -0
  164. package/docs/README.md +58 -0
  165. package/docs/authoring-plain.md +245 -0
  166. package/docs/config.md +353 -0
  167. package/docs/deploy.md +109 -0
  168. package/docs/development.md +88 -0
  169. package/docs/headless.md +148 -0
  170. package/docs/images/how-turri-works.svg +50 -0
  171. package/docs/images/turri-banner.png +0 -0
  172. package/docs/installation.md +119 -0
  173. package/docs/parser.md +108 -0
  174. package/docs/regenerating.md +1116 -0
  175. package/docs/sandbox.md +233 -0
  176. package/docs/skills-and-gates.md +254 -0
  177. package/docs/theming.md +76 -0
  178. package/docs/using.md +454 -0
  179. package/genome/rules/bullet-continuation.md +69 -0
  180. package/genome/rules/concise-specs-examples.md +431 -0
  181. package/genome/rules/concise-specs-integration-examples.md +270 -0
  182. package/genome/rules/concise-specs.md +80 -0
  183. package/genome/rules/definitions.md +107 -0
  184. package/genome/rules/examples/integration-embedded-testing/run_conformance_tests_java.sh +163 -0
  185. package/genome/rules/examples/integration-embedded-testing/run_unittests_java.sh +51 -0
  186. package/genome/rules/exported-concepts.md +39 -0
  187. package/genome/rules/func-specs.md +129 -0
  188. package/genome/rules/impl-reqs.md +68 -0
  189. package/genome/rules/import-modules.md +48 -0
  190. package/genome/rules/integration-embedded-testing.md +276 -0
  191. package/genome/rules/integration-embedded.md +171 -0
  192. package/genome/rules/integration-standalone.md +135 -0
  193. package/genome/rules/integrations.md +137 -0
  194. package/genome/rules/linked-resources.md +69 -0
  195. package/genome/rules/module-structure.md +105 -0
  196. package/genome/rules/project-model.md +88 -0
  197. package/genome/rules/required-concepts.md +44 -0
  198. package/genome/rules/requires-modules.md +80 -0
  199. package/genome/rules/stdlib.md +73 -0
  200. package/genome/rules/test-reqs.md +57 -0
  201. package/genome/skills/add-acceptance-test/SKILL.md +100 -0
  202. package/genome/skills/add-concept/SKILL.md +71 -0
  203. package/genome/skills/add-functional-spec/SKILL.md +84 -0
  204. package/genome/skills/add-functional-specs/SKILL.md +118 -0
  205. package/genome/skills/add-implementation-requirement/SKILL.md +86 -0
  206. package/genome/skills/add-resource/SKILL.md +105 -0
  207. package/genome/skills/add-template/SKILL.md +64 -0
  208. package/genome/skills/add-test-requirement/SKILL.md +73 -0
  209. package/genome/skills/analyze-2-func-specs/SKILL.md +101 -0
  210. package/genome/skills/analyze-func-specs/SKILL.md +123 -0
  211. package/genome/skills/analyze-if-func-spec-too-complex/SKILL.md +211 -0
  212. package/genome/skills/break-down-func-spec/SKILL.md +205 -0
  213. package/genome/skills/brood/SKILL.md +229 -0
  214. package/genome/skills/brood/references/intent.md +35 -0
  215. package/genome/skills/brood/references/product.md +101 -0
  216. package/genome/skills/brood/references/stack.md +61 -0
  217. package/genome/skills/brood/references/testing.md +56 -0
  218. package/genome/skills/bud/SKILL.md +148 -0
  219. package/genome/skills/bud/references/checklist.md +44 -0
  220. package/genome/skills/bud/references/intent.md +30 -0
  221. package/genome/skills/check-plain-env/SKILL.md +285 -0
  222. package/genome/skills/consolidate-concepts/SKILL.md +192 -0
  223. package/genome/skills/create-import-module/SKILL.md +104 -0
  224. package/genome/skills/create-requires-module/SKILL.md +109 -0
  225. package/genome/skills/debug-specs/SKILL.md +188 -0
  226. package/genome/skills/implement-conformance-testing-script/SKILL.md +238 -0
  227. package/genome/skills/implement-conformance-testing-script/assets/run_conformance_tests_cypress.ps1 +337 -0
  228. package/genome/skills/implement-conformance-testing-script/assets/run_conformance_tests_golang.ps1 +111 -0
  229. package/genome/skills/implement-conformance-testing-script/assets/run_conformance_tests_java.sh +136 -0
  230. package/genome/skills/implement-conformance-testing-script/assets/run_conformance_tests_python.ps1 +103 -0
  231. package/genome/skills/implement-conformance-testing-script/assets/run_conformance_tests_python.sh +150 -0
  232. package/genome/skills/implement-unit-testing-script/SKILL.md +160 -0
  233. package/genome/skills/implement-unit-testing-script/assets/run_unittests_flutter.ps1 +87 -0
  234. package/genome/skills/implement-unit-testing-script/assets/run_unittests_golang.ps1 +73 -0
  235. package/genome/skills/implement-unit-testing-script/assets/run_unittests_java.sh +72 -0
  236. package/genome/skills/implement-unit-testing-script/assets/run_unittests_python.ps1 +81 -0
  237. package/genome/skills/implement-unit-testing-script/assets/run_unittests_python.sh +98 -0
  238. package/genome/skills/implement-unit-testing-script/assets/run_unittests_react.ps1 +87 -0
  239. package/genome/skills/init-plain-project/SKILL.md +117 -0
  240. package/genome/skills/init-plain-project/references/checklist.md +30 -0
  241. package/genome/skills/mature/SKILL.md +126 -0
  242. package/genome/skills/refactor-module/SKILL.md +196 -0
  243. package/genome/skills/resolve-section-ownership/SKILL.md +64 -0
  244. package/genome/skills/resolve-spec-conflict/SKILL.md +96 -0
  245. package/genome/skills/spawn/SKILL.md +163 -0
  246. package/package.json +68 -4
  247. package/stdlib/backend/common.plain +25 -0
  248. package/stdlib/backend/conformance/common.plain +12 -0
  249. package/stdlib/backend/conformance/go-gin.plain +9 -0
  250. package/stdlib/backend/conformance/go-nethttp.plain +9 -0
  251. package/stdlib/backend/conformance/java-springboot.plain +9 -0
  252. package/stdlib/backend/conformance/python-django.plain +9 -0
  253. package/stdlib/backend/conformance/python-fastapi.plain +9 -0
  254. package/stdlib/backend/conformance/python-flask.plain +9 -0
  255. package/stdlib/backend/conformance/rust-axum.plain +9 -0
  256. package/stdlib/backend/conformance/typescript-express.plain +9 -0
  257. package/stdlib/backend/conformance/typescript-fastify.plain +9 -0
  258. package/stdlib/backend/conformance/typescript-nestjs.plain +9 -0
  259. package/stdlib/backend/go-gin.plain +18 -0
  260. package/stdlib/backend/go-nethttp.plain +18 -0
  261. package/stdlib/backend/java-springboot.plain +19 -0
  262. package/stdlib/backend/python-django.plain +19 -0
  263. package/stdlib/backend/python-fastapi.plain +23 -0
  264. package/stdlib/backend/python-flask.plain +23 -0
  265. package/stdlib/backend/rust-axum.plain +17 -0
  266. package/stdlib/backend/typescript-express.plain +19 -0
  267. package/stdlib/backend/typescript-fastify.plain +19 -0
  268. package/stdlib/backend/typescript-nestjs.plain +19 -0
  269. package/stdlib/cli/common.plain +17 -0
  270. package/stdlib/cli/conformance/common.plain +11 -0
  271. package/stdlib/cli/conformance/go-cobra.plain +9 -0
  272. package/stdlib/cli/conformance/go-flag.plain +9 -0
  273. package/stdlib/cli/conformance/java-picocli.plain +9 -0
  274. package/stdlib/cli/conformance/python-argparse.plain +9 -0
  275. package/stdlib/cli/conformance/python-click.plain +9 -0
  276. package/stdlib/cli/conformance/python-typer.plain +9 -0
  277. package/stdlib/cli/conformance/rust-clap.plain +9 -0
  278. package/stdlib/cli/conformance/typescript-commander.plain +9 -0
  279. package/stdlib/cli/go-cobra.plain +14 -0
  280. package/stdlib/cli/go-flag.plain +14 -0
  281. package/stdlib/cli/java-picocli.plain +15 -0
  282. package/stdlib/cli/python-argparse.plain +18 -0
  283. package/stdlib/cli/python-click.plain +16 -0
  284. package/stdlib/cli/python-typer.plain +16 -0
  285. package/stdlib/cli/rust-clap.plain +14 -0
  286. package/stdlib/cli/typescript-commander.plain +17 -0
  287. package/stdlib/frontend/angular.plain +17 -0
  288. package/stdlib/frontend/common.plain +17 -0
  289. package/stdlib/frontend/conformance/angular.plain +5 -0
  290. package/stdlib/frontend/conformance/common.plain +11 -0
  291. package/stdlib/frontend/conformance/react-vite.plain +5 -0
  292. package/stdlib/frontend/conformance/sveltekit-spa.plain +5 -0
  293. package/stdlib/frontend/conformance/vue-vite.plain +5 -0
  294. package/stdlib/frontend/react-vite.plain +16 -0
  295. package/stdlib/frontend/sveltekit-spa.plain +16 -0
  296. package/stdlib/frontend/vue-vite.plain +16 -0
  297. package/stdlib/lang/go.plain +13 -0
  298. package/stdlib/lang/java.plain +12 -0
  299. package/stdlib/lang/python.plain +18 -0
  300. package/stdlib/lang/rust.plain +12 -0
  301. package/stdlib/lang/typescript.plain +13 -0
  302. package/stdlib/library/common.plain +15 -0
  303. package/stdlib/library/conformance/common.plain +9 -0
  304. package/stdlib/library/conformance/go.plain +9 -0
  305. package/stdlib/library/conformance/java-gradle.plain +9 -0
  306. package/stdlib/library/conformance/java-maven.plain +9 -0
  307. package/stdlib/library/conformance/python.plain +9 -0
  308. package/stdlib/library/conformance/rust.plain +9 -0
  309. package/stdlib/library/conformance/typescript.plain +9 -0
  310. package/stdlib/library/go.plain +12 -0
  311. package/stdlib/library/java-gradle.plain +13 -0
  312. package/stdlib/library/java-maven.plain +13 -0
  313. package/stdlib/library/python.plain +13 -0
  314. package/stdlib/library/rust.plain +12 -0
  315. package/stdlib/library/typescript.plain +15 -0
@@ -0,0 +1,511 @@
1
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { availableParallelism } from "node:os";
3
+ import { basename, join, resolve } from "node:path";
4
+ import { createInterface } from "node:readline/promises";
5
+ import { load as loadYaml } from "js-yaml";
6
+ import { adoptLegacyConfig, CONFIG_FILE, CONFIG_KEYS, LEGACY_CONFIG_FILE, ROLE_THINKING } from "./config.js";
7
+ import { withTopLevelValue } from "./config-edit.js";
8
+ import { brand } from "./brand.js";
9
+ /** The geographies Bedrock serves each maker's two models in, as pi knows them - the first the default. */
10
+ export const BEDROCK_REGIONS = {
11
+ anthropic: ["global", "us", "eu", "jp", "au"],
12
+ openai: ["global", "us"],
13
+ };
14
+ /** An AWS region in each geography, for AWS_REGION: pi calls Bedrock there, us-east-1 when none is set. */
15
+ const AWS_REGION_IN = { global: "us-east-1", us: "us-east-1", eu: "eu-central-1", jp: "ap-northeast-1", au: "ap-southeast-2" };
16
+ /** Bedrock's models for a maker in a geography: Opus 5.5 and Haiku 5.5, or GPT-6 Astra and Luna. */
17
+ export function bedrockModels(choice) {
18
+ const at = (id) => `amazon-bedrock/${choice.region}.${id}`;
19
+ return choice.maker === "anthropic"
20
+ ? { harness: at("anthropic.claude-opus-5-5"), coding: at("anthropic.claude-haiku-5-5"), strong: at("anthropic.claude-opus-5-5") }
21
+ : { harness: at("openai.gpt-6-astra"), coding: at("openai.gpt-6-luna"), strong: at("openai.gpt-6.1-sol") };
22
+ }
23
+ /** Bedrock's default: Anthropic's models, anywhere. */
24
+ export const BEDROCK_DEFAULT = { maker: "anthropic", region: "global" };
25
+ export const VENDORS = {
26
+ anthropic: {
27
+ label: "Anthropic",
28
+ harness: "anthropic/claude-opus-5-5",
29
+ coding: "anthropic/claude-haiku-5-5",
30
+ strong: "anthropic/claude-sonnet-5-5",
31
+ keys: [{ vendor: "Anthropic", variable: "ANTHROPIC_API_KEY" }],
32
+ },
33
+ openai: {
34
+ label: "OpenAI",
35
+ harness: "openai/gpt-6-astra",
36
+ coding: "openai/gpt-6-luna",
37
+ strong: "openai/gpt-6.1-sol",
38
+ keys: [{ vendor: "OpenAI", variable: "OPENAI_API_KEY" }],
39
+ },
40
+ google: {
41
+ label: "Google",
42
+ harness: "google/gemini-3.1-pro-preview",
43
+ coding: "google/gemini-3.1-flash-lite",
44
+ strong: "google/gemini-3.1-pro-preview",
45
+ keys: [{ vendor: "Google", variable: "GEMINI_API_KEY" }],
46
+ },
47
+ // Anthropic's or OpenAI's models on AWS - which, and where, is asked: this is the default, Anthropic's anywhere.
48
+ "amazon-bedrock": {
49
+ label: "Amazon Bedrock",
50
+ ...bedrockModels(BEDROCK_DEFAULT),
51
+ keys: [{ vendor: "AWS", variable: "AWS_PROFILE" }],
52
+ },
53
+ // A mix - the strongest to talk to, a cheap one to code - and the starting point for any other provider pi supports.
54
+ other: {
55
+ label: "Other",
56
+ harness: "anthropic/claude-opus-5-5",
57
+ coding: "google/gemini-3.1-flash-lite",
58
+ strong: "google/gemini-3.1-pro-preview",
59
+ keys: [
60
+ { vendor: "Anthropic", variable: "ANTHROPIC_API_KEY" },
61
+ { vendor: "Google", variable: "GEMINI_API_KEY" },
62
+ ],
63
+ },
64
+ };
65
+ /** A config line's text, padded to where its comment starts - at least one space before it. */
66
+ const column = (text, width) => (text.length >= width ? `${text} ` : text.padEnd(width));
67
+ /** How hard the model you talk to thinks, as written: the conversation, the planner and the rest of its side. */
68
+ const HARNESS_THINKING = "medium";
69
+ /** A role's commented-out entry: its model, and the thinking it has unless one is named - as turri runs it unset. */
70
+ function role(name, model, comment) {
71
+ const fields = [...(model !== undefined ? [`model: ${model}`] : []), `thinking: ${ROLE_THINKING[name]}`].join(", ");
72
+ return `${column(`# ${name}: { ${fields} }`, 72)}# ${comment}`;
73
+ }
74
+ /** A vendor's three models - for Bedrock, the maker and geography chosen. */
75
+ function modelsOf(vendor, bedrock) {
76
+ return vendor === "amazon-bedrock" ? bedrockModels(bedrock ?? BEDROCK_DEFAULT) : VENDORS[vendor];
77
+ }
78
+ /** The two model lines: the one you talk to thinking medium, the coding one low - their comments in one column. */
79
+ function modelLines(harness, coding) {
80
+ const width = Math.max(46, `harness-model: ${harness}:${HARNESS_THINKING}`.length + 2, `coding-model: ${coding}:${ROLE_THINKING.coding}`.length + 2);
81
+ return [
82
+ `${column(`harness-model: ${harness}:${HARNESS_THINKING}`, width)}# you talk to it: it writes the specs and plans the work`,
83
+ `${column(`coding-model: ${coding}:${ROLE_THINKING.coding}`, width)}# it writes the code`,
84
+ ];
85
+ }
86
+ /**
87
+ * A config.turri.yaml with its two models set to these lines, and nothing else changed: each key's line - and the lines
88
+ * indented under it, when it is written as a block - replaced where it is, or put at the top when it is not there.
89
+ */
90
+ export function withModels(text, lines) {
91
+ let out = text.split("\n");
92
+ const missing = [];
93
+ for (const [key, line] of [["harness-model", lines[0]], ["coding-model", lines[1]]]) {
94
+ const at = out.findIndex((entry) => entry.startsWith(`${key}:`));
95
+ if (at === -1) {
96
+ missing.push(line);
97
+ continue;
98
+ }
99
+ let end = at + 1;
100
+ while (end < out.length && /^\s+\S/.test(out[end]))
101
+ end += 1;
102
+ out = [...out.slice(0, at), line, ...out.slice(end)];
103
+ }
104
+ return [...missing, ...out].join("\n");
105
+ }
106
+ /**
107
+ * What turri does with each setting the config leaves out, written into the quick start config so it says so. They
108
+ * must stay what the code falls back to - tests/init-config.test.ts holds them to the constants they mirror.
109
+ */
110
+ export const DEFAULTS = {
111
+ conformanceTests: false,
112
+ review: false,
113
+ allowToStopJobs: true,
114
+ releaseAfterPulse: false,
115
+ testingScriptsRunOn: "in-place",
116
+ turnLimit: 120,
117
+ compactAt: 100000,
118
+ maxSpecsPerPulse: 3,
119
+ maxLinesPerPulse: 120,
120
+ };
121
+ /** How many pulses run at once without a setting: half this machine's cores, one to four - as regenerate/jobs.ts has it. */
122
+ export function defaultParallel(cores = availableParallelism()) {
123
+ return Math.min(4, Math.max(1, Math.floor(cores / 2)));
124
+ }
125
+ /** The quick start config.turri.yaml for a vendor's models - for Bedrock, the maker and geography chosen. */
126
+ export function quickStartConfig(vendor = "other", bedrock) {
127
+ const { harness, coding, strong } = modelsOf(vendor, bedrock);
128
+ return [
129
+ "# Any model pi supports works (https://pi.dev): a capable one to talk to, a smaller, cheaper one for the coding.",
130
+ ...modelLines(harness, coding),
131
+ "# The :level is how hard each thinks - off, minimal, low, medium, high, xhigh or max. The reviews and judges run on the",
132
+ "# coding model but think medium, as models: below shows.",
133
+ "",
134
+ "# Every other setting is written out below with the value turri uses for it, so the file says how the project is",
135
+ "# built: change one to change it. The optional ones, which have no value of their own, are commented out - uncomment",
136
+ "# one to use it. docs/config.md has them all.",
137
+ "",
138
+ "# Conformance tests: all (or true) - every functional spec of a module with test reqs; chosen - those with acceptance",
139
+ "# tests and those the chooser picks as each pulse ends; module - the chooser picks, and plans them, once the whole",
140
+ "# module is regenerated; false - none written or run. Those you ask for are written under any of them.",
141
+ `conformance-tests: ${DEFAULTS.conformanceTests}`,
142
+ "",
143
+ "# Whether the code and its tests are reviewed once they pass - two reviews at most, the second weighed by the arbiter.",
144
+ `review: ${DEFAULTS.review}`,
145
+ "",
146
+ "# Whether turri stops a running pulse the spec has moved on from. false: it lands, the change regenerated on top.",
147
+ `allow-to-stop-jobs: ${DEFAULTS.allowToStopJobs}`,
148
+ "",
149
+ "# Whether every pulse that lands code releases it, as the project's release guidance says.",
150
+ `release-after-pulse: ${DEFAULTS.releaseAfterPulse}`,
151
+ "",
152
+ "# What the testing scripts run on: in-place - the code itself; copy - a fresh copy of it.",
153
+ `testing-scripts-run-on: ${DEFAULTS.testingScriptsRunOn}`,
154
+ "",
155
+ "# Seconds each agent turn has, for every role without its own; 0 for none.",
156
+ `turn-limit: ${DEFAULTS.turnLimit}`,
157
+ "",
158
+ "# How many pulses run at once, on modules that share nothing - half this machine's cores, one to four.",
159
+ `parallel-jobs: ${defaultParallel()}`,
160
+ "",
161
+ "# Tokens of work an agent holds before it is compacted between turns; at least 1000.",
162
+ `compact-at: ${DEFAULTS.compactAt}`,
163
+ "",
164
+ "# How big a pulse may be: the most functional specs the planner groups into one, and the most lines of spec - its",
165
+ "# functional specs with the definitions they bring - before a group is split again.",
166
+ `max-specs-per-pulse: ${DEFAULTS.maxSpecsPerPulse}`,
167
+ `max-lines-per-pulse: ${DEFAULTS.maxLinesPerPulse}`,
168
+ "",
169
+ "# Rules left out of the spec-writing agents' prompts - the files of genome/rules/, without .md: [concise-specs].",
170
+ "rules-off: []",
171
+ "",
172
+ "# Credentials the agents' commands need while they work: dotenv files by path, and variables from turri's",
173
+ "# environment by name - files: [.env], vars: [MY_SERVICE_API_KEY]. Their values are masked in everything an agent is shown.",
174
+ "agent-env:",
175
+ " files: []",
176
+ " vars: []",
177
+ "",
178
+ "# Folders of this machine an agent's commands may read beside the usual ones - a toolchain somewhere unusual: [/srv/zig].",
179
+ "toolchains: []",
180
+ "",
181
+ "# The testing scripts, by path from the project root - written here by the skill that writes them. One not named is",
182
+ "# looked for at the root, then in test_scripts/. Each runs the static checks of what it tests - the formatter, the",
183
+ "# linter, the type checker - with the tests, exiting 80 when the tests pass and only the checks do not. The prepare",
184
+ "# script, optional, runs on the build before every conformance testing script run.",
185
+ "# unittests-script: test_scripts/run_unittests_python.sh",
186
+ "# conformance-tests-script: test_scripts/run_conformance_tests_python.sh",
187
+ "# prepare-environment-script: test_scripts/prepare_environment_python.sh",
188
+ "",
189
+ "# Optional: the .plain file turri regenerate builds when none is named.",
190
+ "# module: app.plain",
191
+ "",
192
+ "# Optional, under conformance-tests: chosen or module: how many functional specs of a module may be chosen - a count or a share.",
193
+ "# Without it, any number.",
194
+ "# conformance-budget: 30%",
195
+ "",
196
+ "# Optional: the judge and test review of a conformance run, when not on coding-model. A model is provider/id,",
197
+ "# provider/id:thinking, or { model, thinking } - thinking is off, minimal, low, medium, high, xhigh or max.",
198
+ `# judge-model: ${strong}:high`,
199
+ "",
200
+ "# Optional: each agent role's own model, thinking and turn limit. Without it, every role runs on coding-model and the",
201
+ "# conversation's side on harness-model, each thinking as written here. What an entry leaves out it takes from default.",
202
+ "# models:",
203
+ `# default: ${coding}`,
204
+ `${column(`# chat: ${harness}`, 72)}# the conversation - as harness-model, which it wins over`,
205
+ role("chooser", harness, "chooses what gets conformance tests under turri regenerate"),
206
+ role("planner", harness, "cuts the work into pulses"),
207
+ role("diagnosis", harness, "looks into a pulse that failed for good"),
208
+ role("release", coding, "turri release's agent, and release-after-pulse's"),
209
+ `${column(`# coding: { model: ${coding}, thinking: ${ROLE_THINKING.coding}, turn-limit: 180 }`, 72)}# as coding-model, which it wins over`,
210
+ role("conformance", coding, "writes the conformance tests"),
211
+ role("explorer", coding, "explores the code for a conformance run"),
212
+ role("reviewer", undefined, "reviews the code and its unit tests"),
213
+ role("test-reviewer", strong, "reviews the conformance tests"),
214
+ role("judge", strong, "judges a failing conformance run - as judge-model, which it wins over"),
215
+ role("arbiter", strong, "weighs a second review"),
216
+ role("debugger", strong, "a fresh look at unit tests that keep failing the same way"),
217
+ "",
218
+ ].join("\n");
219
+ }
220
+ /** The quick start config for the mix of vendors: what turri wrote before it asked. */
221
+ export const QUICK_START_CONFIG = quickStartConfig("other");
222
+ /** A vendor from what was typed: its number in the list, or its name. */
223
+ export function vendorOf(answer) {
224
+ const typed = answer.trim().toLowerCase();
225
+ const names = Object.keys(VENDORS);
226
+ const byNumber = /^\d+$/.test(typed) ? names[Number(typed) - 1] : undefined;
227
+ if (typed === "bedrock" || typed === "aws")
228
+ return "amazon-bedrock";
229
+ return byNumber ?? names.find((name) => name === typed || VENDORS[name].label.toLowerCase() === typed);
230
+ }
231
+ /** Bedrock's maker from what was typed: its number - 1 Anthropic, 2 OpenAI - or its name. */
232
+ export function bedrockMakerOf(answer) {
233
+ const typed = answer.trim().toLowerCase();
234
+ if (typed === "1" || typed === "anthropic")
235
+ return "anthropic";
236
+ if (typed === "2" || typed === "openai")
237
+ return "openai";
238
+ return undefined;
239
+ }
240
+ /** A geography the maker's models are served in, from what was typed: its number in the list, or its name. */
241
+ export function bedrockRegionOf(maker, answer) {
242
+ const typed = answer.trim().toLowerCase();
243
+ const regions = BEDROCK_REGIONS[maker];
244
+ return (/^\d+$/.test(typed) ? regions[Number(typed) - 1] : undefined) ?? regions.find((region) => region === typed);
245
+ }
246
+ /** The shell profile an exported key is kept in, for the shell this person uses. */
247
+ function shellProfile() {
248
+ const shell = basename(process.env.SHELL ?? "");
249
+ if (shell === "zsh")
250
+ return "~/.zshrc";
251
+ if (shell === "fish")
252
+ return "~/.config/fish/config.fish";
253
+ return "~/.bashrc";
254
+ }
255
+ /** How to sign in to the vendor's models, as pi takes them: a key in the environment, or /login - a few lines. */
256
+ function signInText(vendor, bedrock) {
257
+ const { keys } = VENDORS[vendor];
258
+ const windows = process.platform === "win32";
259
+ const set = (variable, value = "...") => brand.accent(windows ? `setx ${variable} "${value}"` : `export ${variable}=${value}`);
260
+ // Bedrock's are several; a vendor's own key is one, or the mix's two.
261
+ const several = vendor === "amazon-bedrock" || keys.length > 1;
262
+ const keep = windows
263
+ ? `setx keeps ${several ? "them" : "it"} for new terminals - open one before you run turri.`
264
+ : `Add ${several ? "them" : "it"} to ${shellProfile()} to keep ${several ? "them" : "it"}.`;
265
+ if (vendor === "amazon-bedrock") {
266
+ const region = AWS_REGION_IN[(bedrock ?? BEDROCK_DEFAULT).region] ?? "us-east-1";
267
+ return [
268
+ brand.bold("Sign in to AWS"),
269
+ ` ${set("AWS_PROFILE")} ${brand.muted("your profile (aws configure, or aws sso login)")}`,
270
+ ` ${set("AWS_REGION", region)}`,
271
+ ` ${brand.muted("or, instead of a profile, a Bedrock API key:")} ${set("AWS_BEARER_TOKEN_BEDROCK")}`,
272
+ "",
273
+ brand.muted(keep),
274
+ brand.muted("Your AWS account needs access to the models: Bedrock console, model access."),
275
+ ];
276
+ }
277
+ return [
278
+ brand.bold(`Sign in to ${keys.map((key) => key.vendor).join(" and ")}`),
279
+ ...keys.map((key) => ` ${set(key.variable)}`),
280
+ "",
281
+ brand.muted(`${keep} Or start turri and type /login.`),
282
+ ...(vendor === "other" ? [brand.muted("Another provider: change the two models to any pi supports, and set its key - https://pi.dev.")] : []),
283
+ ];
284
+ }
285
+ /** Whose models these are, in the message: "OpenAI's", "a mix of", "OpenAI's on Amazon Bedrock (global)". */
286
+ function whose(vendor, bedrock) {
287
+ if (vendor === "other")
288
+ return "a mix of";
289
+ if (vendor !== "amazon-bedrock")
290
+ return `${VENDORS[vendor].label}'s`;
291
+ const choice = bedrock ?? BEDROCK_DEFAULT;
292
+ return `${VENDORS[choice.maker].label}'s on Amazon Bedrock (${choice.region})`;
293
+ }
294
+ /**
295
+ * What a config.yaml in `folder` says that turri reads too - codeplain's testing scripts, say - by turri's names
296
+ * (`template_dir` read as `template-dir`), in the file's order; and the keys it leaves, turri reading none of them. The
297
+ * two models are the vendor's, so they are not taken.
298
+ */
299
+ export function sharedWithLegacy(folder) {
300
+ const path = join(folder, LEGACY_CONFIG_FILE);
301
+ if (!existsSync(path))
302
+ return { carried: [], left: [] };
303
+ let raw;
304
+ try {
305
+ raw = loadYaml(readFileSync(path, "utf8"));
306
+ }
307
+ catch {
308
+ return { carried: [], left: [] };
309
+ }
310
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw))
311
+ return { carried: [], left: [] };
312
+ const known = new Set(Object.values(CONFIG_KEYS));
313
+ const models = new Set([CONFIG_KEYS.harnessModel, CONFIG_KEYS.codingModel, "model"]);
314
+ const carried = [];
315
+ const left = [];
316
+ for (const [written, value] of Object.entries(raw)) {
317
+ const key = written.replace(/_/g, "-");
318
+ if (models.has(key) || value === null || value === undefined)
319
+ continue;
320
+ if (known.has(key))
321
+ carried.push([key, value]);
322
+ else
323
+ left.push(written);
324
+ }
325
+ return { carried, left };
326
+ }
327
+ /**
328
+ * Write the quick start config.turri.yaml in `dir`, with what a config.yaml beside it says that turri reads too. One already
329
+ * there keeps everything but its two models, which are set to the vendor's - `force` writes the whole of it again. Says
330
+ * what it did.
331
+ */
332
+ export function initConfig(dir, options = {}) {
333
+ const folder = resolve(dir);
334
+ // The old stacks' files, config.<stack>.yaml, copied to their new names; config.turri.yaml is written below.
335
+ adoptLegacyConfig(folder, { projectFile: false });
336
+ const path = join(folder, CONFIG_FILE);
337
+ const vendor = options.vendor ?? "other";
338
+ const existing = existsSync(path) && options.force !== true;
339
+ const { harness, coding } = modelsOf(vendor, options.bedrock);
340
+ const lines = modelLines(harness, coding);
341
+ // A new file takes what a config.yaml beside it - codeplain's - says that turri reads too: its testing scripts, say.
342
+ const shared = existing ? { carried: [], left: [] } : sharedWithLegacy(folder);
343
+ const config = existing
344
+ ? withModels(readFileSync(path, "utf8"), lines)
345
+ : shared.carried.reduce((text, [key, value]) => withTopLevelValue(text, key, value), quickStartConfig(vendor, options.bedrock));
346
+ writeFileSync(path, config);
347
+ const here = resolve(process.cwd()) === folder;
348
+ const [talk, code] = [harness, coding].map((model, at) => `${model}:${at === 0 ? HARNESS_THINKING : ROLE_THINKING.coding}`);
349
+ return {
350
+ written: true,
351
+ message: [
352
+ "",
353
+ `${brand.success("✓")} ${existing ? `Set the two models in ${brand.bold(path)}` : `Wrote ${brand.bold(path)}`}`,
354
+ ` ${brand.muted(`with ${whose(vendor, options.bedrock)} models${existing ? " - everything else in it left as it was (--force writes it all again)" : ""}`)}`,
355
+ "",
356
+ ` ${brand.muted("you talk to")} ${talk}`,
357
+ ` ${brand.muted("it codes")} ${code}`,
358
+ ...(shared.carried.length > 0
359
+ ? [
360
+ "",
361
+ brand.muted(`Took from ${LEGACY_CONFIG_FILE} what turri reads too - ${shared.carried.map(([key]) => key).join(", ")}.` +
362
+ (shared.left.length > 0 ? ` Not taken, turri does not read them: ${shared.left.join(", ")}.` : "")),
363
+ ]
364
+ : []),
365
+ "",
366
+ ...signInText(vendor, options.bedrock),
367
+ "",
368
+ brand.bold("Then"),
369
+ ...(here ? [] : [` ${brand.accent(`cd ${folder}`)}`]),
370
+ ` ${brand.accent("turri doctor")} ${brand.muted("checks the models answer with your credentials")}`,
371
+ ` ${brand.accent("turri")}`,
372
+ "",
373
+ brand.muted(`turri reads this config when it starts in ${here ? "this folder" : folder} or one below it.`),
374
+ brand.muted(`Every other setting is in the file at its default - docs/config.md has them all.`),
375
+ "",
376
+ ].join("\n"),
377
+ };
378
+ }
379
+ const USAGE = [
380
+ "usage: turri init-config [--vendor anthropic|openai|google|amazon-bedrock|other] [--maker anthropic|openai] [--region <geography>] [--force] [folder]",
381
+ "",
382
+ "Write a quick start config.turri.yaml in the folder, the current one by default: the model you talk to and the one that",
383
+ "writes the code, from the vendor you use. Asked when --vendor is not given and this is a terminal; otherwise other -",
384
+ "a mix of Anthropic and Google, the starting point for any provider pi supports. On amazon-bedrock, --maker and",
385
+ "--region say whose models and where (anthropic: global, us, eu, jp, au; openai: global, us) - asked when not given.",
386
+ "A config.turri.yaml already there keeps everything but its two models; --force writes the whole of it again. A new",
387
+ "one takes from a config.yaml beside it - codeplain's - the keys turri reads too, its testing scripts say, and leaves",
388
+ "config.yaml as it is.",
389
+ ].join("\n");
390
+ /** Ask until the answer is one `read` takes. */
391
+ async function askUntil(rl, prompt, read, again) {
392
+ for (;;) {
393
+ const chosen = read(await rl.question(prompt));
394
+ if (chosen !== undefined)
395
+ return chosen;
396
+ console.log(again);
397
+ }
398
+ }
399
+ /** Ask which vendor's models to use - a number or a name - and, for Amazon Bedrock, whose and where: what is not given. */
400
+ async function askVendor(given) {
401
+ const names = Object.keys(VENDORS);
402
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
403
+ /** A question: a blank line before it, its choices numbered, each with what it is. */
404
+ const ask = (question, choices) => {
405
+ console.log(`\n${brand.bold(brand.accent(question))}`);
406
+ const width = Math.max(...choices.map(([label]) => label.length)) + 3;
407
+ choices.forEach(([label, what], index) => console.log(` ${brand.accent(`${index + 1}`)} ${label.padEnd(width)}${brand.muted(what)}`));
408
+ };
409
+ const choose = (count) => `\n${brand.muted(`Choose 1-${count}:`)} `;
410
+ try {
411
+ let vendor = given.vendor;
412
+ if (vendor === undefined) {
413
+ ask("Which vendor's models do you use?", names.map((name) => {
414
+ const { label, harness, coding } = VENDORS[name];
415
+ const what = name === "amazon-bedrock"
416
+ ? "Anthropic's or OpenAI's models on AWS"
417
+ : `${name === "other" ? "a mix - " : ""}${harness.split("/")[1]} to talk to, ${coding.split("/")[1]} to code`;
418
+ return [label, what];
419
+ }));
420
+ vendor = await askUntil(rl, choose(names.length), vendorOf, `Type a number from 1 to ${names.length}, or a vendor's name.`);
421
+ }
422
+ if (vendor !== "amazon-bedrock")
423
+ return { vendor };
424
+ let maker = given.maker;
425
+ if (maker === undefined) {
426
+ ask("Whose models, on Amazon Bedrock?", [
427
+ ["Anthropic", "Claude Opus 5.5 to talk to, Claude Haiku 5.5 to code"],
428
+ ["OpenAI", "GPT-6 Astra to talk to, GPT-6 Luna to code"],
429
+ ]);
430
+ maker = await askUntil(rl, choose(2), bedrockMakerOf, "Type 1 or 2, or anthropic or openai.");
431
+ }
432
+ const regions = BEDROCK_REGIONS[maker];
433
+ let region = given.region !== undefined ? bedrockRegionOf(maker, given.region) : undefined;
434
+ if (region === undefined) {
435
+ ask("Which region?", regions.map((entry) => [entry, entry === "global" ? "wherever Bedrock has room" : ""]));
436
+ region = await askUntil(rl, choose(regions.length), (answer) => bedrockRegionOf(maker, answer), `Type a number from 1 to ${regions.length}, or one of ${regions.join(", ")}.`);
437
+ }
438
+ return { vendor, bedrock: { maker, region } };
439
+ }
440
+ finally {
441
+ rl.close();
442
+ }
443
+ }
444
+ /** A flag's value: `--name value` or `--name=value`, and where the value was, to leave it out of the folder. */
445
+ function flagOf(args, name) {
446
+ const index = args.findIndex((arg) => arg === `--${name}` || arg.startsWith(`--${name}=`));
447
+ if (index === -1)
448
+ return { at: [] };
449
+ return args[index].includes("=") ? { value: args[index].split("=")[1], at: [index] } : { value: args[index + 1], at: [index, index + 1] };
450
+ }
451
+ /** `turri init-config [--vendor <vendor>] [--maker <maker>] [--region <geography>] [--force] [folder]`. */
452
+ export async function main(args) {
453
+ if (args.includes("-h") || args.includes("--help")) {
454
+ console.log(USAGE);
455
+ return 0;
456
+ }
457
+ const flags = { vendor: flagOf(args, "vendor"), maker: flagOf(args, "maker"), region: flagOf(args, "region") };
458
+ const taken = new Set(Object.values(flags).flatMap((flag) => flag.at));
459
+ const rest = args.filter((arg, index) => !arg.startsWith("-") && !taken.has(index));
460
+ const folder = rest[0] ?? process.cwd();
461
+ if (!existsSync(folder)) {
462
+ console.error(`${folder} does not exist.`);
463
+ return 1;
464
+ }
465
+ let vendor;
466
+ if (flags.vendor.value !== undefined) {
467
+ vendor = vendorOf(flags.vendor.value);
468
+ if (vendor === undefined) {
469
+ console.error(`Not a vendor: ${flags.vendor.value}. One of ${Object.keys(VENDORS).join(", ")}.\n\n${USAGE}`);
470
+ return 1;
471
+ }
472
+ }
473
+ let maker;
474
+ if (flags.maker.value !== undefined) {
475
+ maker = bedrockMakerOf(flags.maker.value);
476
+ if (maker === undefined) {
477
+ console.error(`Not one of Bedrock's makers here: ${flags.maker.value}. One of anthropic, openai.\n\n${USAGE}`);
478
+ return 1;
479
+ }
480
+ }
481
+ const region = flags.region.value;
482
+ if (region !== undefined && bedrockRegionOf(maker ?? BEDROCK_DEFAULT.maker, region) === undefined) {
483
+ console.error(`Not a region ${VENDORS[maker ?? BEDROCK_DEFAULT.maker].label}'s models run in on Bedrock: ${region}. One of ${BEDROCK_REGIONS[maker ?? BEDROCK_DEFAULT.maker].join(", ")}.`);
484
+ return 1;
485
+ }
486
+ const force = args.includes("--force");
487
+ const asking = process.stdin.isTTY && process.stdout.isTTY;
488
+ let chosen;
489
+ try {
490
+ chosen = await chooseVendor();
491
+ }
492
+ catch (error) {
493
+ // Ctrl+C or Ctrl+D at a question: stopped, nothing written - not a crash.
494
+ if (error.code === "ABORT_ERR") {
495
+ console.log(`\n${brand.muted("Stopped - nothing written.")}`);
496
+ return 1;
497
+ }
498
+ throw error;
499
+ }
500
+ async function chooseVendor() {
501
+ return asking && (vendor === undefined || (vendor === "amazon-bedrock" && (maker === undefined || region === undefined)))
502
+ ? await askVendor({ ...(vendor !== undefined ? { vendor } : {}), ...(maker !== undefined ? { maker } : {}), ...(region !== undefined ? { region } : {}) })
503
+ : {
504
+ vendor: vendor ?? "other",
505
+ ...(vendor === "amazon-bedrock" ? { bedrock: { maker: maker ?? BEDROCK_DEFAULT.maker, region: bedrockRegionOf(maker ?? BEDROCK_DEFAULT.maker, region ?? BEDROCK_DEFAULT.region) } } : {}),
506
+ };
507
+ }
508
+ const result = initConfig(folder, { force, vendor: chosen.vendor, ...(chosen.bedrock !== undefined ? { bedrock: chosen.bedrock } : {}) });
509
+ (result.written ? console.log : console.error)(result.message);
510
+ return result.written ? 0 : 1;
511
+ }
package/dist/judge.js ADDED
@@ -0,0 +1,62 @@
1
+ import { ask, BEGIN, createJudge } from "./pi.js";
2
+ import { createVerdictTool } from "./tools.js";
3
+ function judgePrompt(rubric, artifactPath, artifact) {
4
+ return [
5
+ "You are a reviewer. You did not write the artifact below and you have no stake in it.",
6
+ "Judge it against the rubric only. Do not infer intent, do not give credit for effort,",
7
+ "and do not soften a violation because the rest is good.",
8
+ "",
9
+ "<rubric>",
10
+ rubric,
11
+ "</rubric>",
12
+ "",
13
+ `<artifact path="${artifactPath}">`,
14
+ artifact,
15
+ "</artifact>",
16
+ "",
17
+ "Call submit_verdict exactly once. Every violation must quote the offending text verbatim.",
18
+ "Where the rubric asks about files rather than about the text above, read them yourself.",
19
+ ].join("\n");
20
+ }
21
+ export async function runGate(opts) {
22
+ const { review } = opts;
23
+ const results = await Promise.all(Array.from({ length: review.judges }, async (_, index) => {
24
+ let captured;
25
+ const { session, modelNote } = await createJudge({
26
+ cwd: opts.cwd,
27
+ verdictTool: createVerdictTool((v) => {
28
+ captured = v;
29
+ }),
30
+ modelSpec: review.model ?? opts.defaultModel,
31
+ borrowAuthFrom: opts.executor,
32
+ extraTools: review.judgeTools,
33
+ task: judgePrompt(opts.rubric, opts.artifactPath, opts.artifact),
34
+ });
35
+ opts.onJudgeStart?.(index, modelNote);
36
+ try {
37
+ await ask(session, BEGIN);
38
+ }
39
+ finally {
40
+ session.dispose();
41
+ }
42
+ return { index, model: modelNote, verdict: captured };
43
+ }));
44
+ // A judge that returned no verdict counts as a fail. Silence is not consent.
45
+ const passes = results.filter((r) => r.verdict?.verdict === "pass").length;
46
+ const passed = review.quorum === "all"
47
+ ? passes === results.length
48
+ : review.quorum === "majority"
49
+ ? passes * 2 > results.length
50
+ : passes > 0;
51
+ const critique = passed
52
+ ? ""
53
+ : results
54
+ .flatMap((r) => {
55
+ if (!r.verdict)
56
+ return [`- judge ${r.index + 1} returned no verdict; treat as a failure and re-check the rubric yourself`];
57
+ return r.verdict.violations.map((v) => `- [${v.criterion}] found: ${v.evidence} -> ${v.fix}`);
58
+ })
59
+ .filter((line, i, all) => all.indexOf(line) === i)
60
+ .join("\n");
61
+ return { passed, results, critique };
62
+ }
@@ -0,0 +1,13 @@
1
+ import { defaultBuildDir } from "./regenerate/paths.js";
2
+ import { historyLog, historyShow } from "./regenerate/lineage.js";
3
+ const USAGE = `usage: turri lineage <module> [commit]
4
+
5
+ A module's lineage: every generation of its code and tests, and what each was regenerated from, as git commits.
6
+ With a commit, that commit in full.`;
7
+ const [module, commit] = process.argv.slice(2).filter((arg) => !arg.startsWith("-"));
8
+ if (module === undefined || process.argv.includes("--help")) {
9
+ console.log(USAGE);
10
+ process.exit(module === undefined ? 1 : 0);
11
+ }
12
+ const buildDir = defaultBuildDir(module, process.cwd());
13
+ console.log(commit !== undefined ? historyShow(buildDir, commit) : historyLog(buildDir));