@turri-dev/cli 0.1.27 → 0.2.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 (216) hide show
  1. package/README.md +6 -6
  2. package/dist/advisor-chat.d.ts +63 -0
  3. package/dist/advisor-chat.js +290 -0
  4. package/dist/background-skills.d.ts +53 -0
  5. package/dist/background-skills.js +121 -0
  6. package/dist/bedrock-region.d.ts +66 -0
  7. package/dist/bedrock-region.js +144 -0
  8. package/dist/btw.d.ts +1 -1
  9. package/dist/btw.js +1 -1
  10. package/dist/change-plain-cli.js +20 -13
  11. package/dist/chat-idle.d.ts +7 -0
  12. package/dist/chat-idle.js +20 -0
  13. package/dist/chats.d.ts +10 -0
  14. package/dist/chats.js +23 -0
  15. package/dist/cli-options.d.ts +1 -2
  16. package/dist/cli-options.js +2 -7
  17. package/dist/cli.js +11 -3
  18. package/dist/config-command.d.ts +14 -0
  19. package/dist/config-command.js +35 -15
  20. package/dist/config-edit.d.ts +3 -3
  21. package/dist/config-edit.js +16 -12
  22. package/dist/config.d.ts +54 -34
  23. package/dist/config.js +40 -44
  24. package/dist/conform-cli.js +12 -10
  25. package/dist/conformance-switch.d.ts +34 -0
  26. package/dist/conformance-switch.js +108 -0
  27. package/dist/cultivate-cli.js +15 -5
  28. package/dist/drift-cli.js +2 -0
  29. package/dist/extension.d.ts +15 -0
  30. package/dist/extension.js +260 -83
  31. package/dist/host-session.d.ts +26 -0
  32. package/dist/host-session.js +30 -2
  33. package/dist/host.d.ts +5 -2
  34. package/dist/host.js +7 -1
  35. package/dist/init-config.d.ts +1 -1
  36. package/dist/init-config.js +20 -11
  37. package/dist/loop-cli.d.ts +3 -3
  38. package/dist/loop-cli.js +13 -16
  39. package/dist/loop-tools.js +18 -10
  40. package/dist/model-check.d.ts +8 -3
  41. package/dist/model-check.js +13 -8
  42. package/dist/new-project.js +12 -8
  43. package/dist/{validate-cli.js → parse-cli.js} +11 -5
  44. package/dist/parse-tool.d.ts +9 -0
  45. package/dist/parse-tool.js +33 -0
  46. package/dist/pi-launch.js +1 -1
  47. package/dist/plain/check.d.ts +53 -6
  48. package/dist/plain/check.js +217 -24
  49. package/dist/plain/concepts.d.ts +8 -0
  50. package/dist/plain/concepts.js +66 -23
  51. package/dist/plain/cycles.d.ts +21 -0
  52. package/dist/plain/cycles.js +190 -0
  53. package/dist/plain/errors.d.ts +4 -0
  54. package/dist/plain/errors.js +7 -0
  55. package/dist/plain/files.d.ts +18 -1
  56. package/dist/plain/files.js +39 -2
  57. package/dist/plain/graph.d.ts +33 -4
  58. package/dist/plain/graph.js +60 -12
  59. package/dist/plain/highlight.d.ts +3 -2
  60. package/dist/plain/highlight.js +4 -3
  61. package/dist/plain/index.d.ts +8 -5
  62. package/dist/plain/index.js +8 -5
  63. package/dist/plain/liquid.js +1 -6
  64. package/dist/plain/markdown.d.ts +32 -3
  65. package/dist/plain/markdown.js +146 -24
  66. package/dist/plain/module-concepts.js +2 -2
  67. package/dist/plain/parser.d.ts +83 -28
  68. package/dist/plain/parser.js +381 -260
  69. package/dist/plain/plain.tmLanguage.json +29 -3
  70. package/dist/plain/project.d.ts +48 -0
  71. package/dist/plain/project.js +155 -0
  72. package/dist/plain/spec.d.ts +90 -28
  73. package/dist/plain/spec.js +107 -133
  74. package/dist/plain/test-plain-file.d.ts +59 -0
  75. package/dist/plain/test-plain-file.js +232 -0
  76. package/dist/regenerate/activity.d.ts +20 -9
  77. package/dist/regenerate/activity.js +11 -4
  78. package/dist/regenerate/advisor-records.d.ts +93 -0
  79. package/dist/regenerate/advisor-records.js +110 -0
  80. package/dist/regenerate/advisor-runs.d.ts +87 -0
  81. package/dist/regenerate/advisor-runs.js +327 -0
  82. package/dist/regenerate/base-folder.d.ts +68 -0
  83. package/dist/regenerate/base-folder.js +168 -0
  84. package/dist/regenerate/cases.d.ts +9 -6
  85. package/dist/regenerate/cases.js +70 -14
  86. package/dist/regenerate/change-view.d.ts +8 -3
  87. package/dist/regenerate/change-view.js +71 -22
  88. package/dist/regenerate/chunk-plan.d.ts +36 -15
  89. package/dist/regenerate/chunk-plan.js +165 -59
  90. package/dist/regenerate/conform.d.ts +3 -0
  91. package/dist/regenerate/conform.js +6 -5
  92. package/dist/regenerate/conformance-agents.d.ts +18 -39
  93. package/dist/regenerate/conformance-agents.js +60 -88
  94. package/dist/regenerate/conformance.d.ts +23 -13
  95. package/dist/regenerate/conformance.js +119 -82
  96. package/dist/regenerate/diff-view.d.ts +1 -1
  97. package/dist/regenerate/diff-view.js +20 -4
  98. package/dist/regenerate/drift.d.ts +47 -15
  99. package/dist/regenerate/drift.js +101 -30
  100. package/dist/regenerate/jobs.d.ts +2 -2
  101. package/dist/regenerate/jobs.js +1 -1
  102. package/dist/regenerate/ledger.d.ts +19 -0
  103. package/dist/regenerate/loop.d.ts +62 -43
  104. package/dist/regenerate/loop.js +472 -341
  105. package/dist/regenerate/paths.d.ts +4 -0
  106. package/dist/regenerate/paths.js +7 -1
  107. package/dist/regenerate/plain-palette.js +2 -0
  108. package/dist/regenerate/prompt.d.ts +57 -19
  109. package/dist/regenerate/prompt.js +157 -122
  110. package/dist/regenerate/records.d.ts +43 -12
  111. package/dist/regenerate/records.js +102 -88
  112. package/dist/regenerate/regenerate.js +29 -11
  113. package/dist/regenerate/regression.d.ts +4 -4
  114. package/dist/regenerate/regression.js +14 -10
  115. package/dist/regenerate/renames.d.ts +42 -0
  116. package/dist/regenerate/renames.js +185 -0
  117. package/dist/regenerate/requires.d.ts +1 -1
  118. package/dist/regenerate/requires.js +10 -3
  119. package/dist/regenerate/resources.d.ts +5 -3
  120. package/dist/regenerate/resources.js +4 -4
  121. package/dist/regenerate/scripts-being-written.d.ts +19 -0
  122. package/dist/regenerate/scripts-being-written.js +30 -0
  123. package/dist/regenerate/spec-map-view.d.ts +2 -2
  124. package/dist/regenerate/spec-map-view.js +46 -15
  125. package/dist/regenerate/spec-map.d.ts +27 -5
  126. package/dist/regenerate/spec-map.js +92 -60
  127. package/dist/regenerate/spec-steps.d.ts +48 -5
  128. package/dist/regenerate/spec-steps.js +213 -28
  129. package/dist/regenerate/state.d.ts +4 -3
  130. package/dist/regenerate/stop-report.d.ts +1 -1
  131. package/dist/regenerate/stop-report.js +10 -10
  132. package/dist/regenerate/test-advisor.d.ts +101 -0
  133. package/dist/regenerate/test-advisor.js +379 -0
  134. package/dist/regenerate/test-kinds.d.ts +71 -28
  135. package/dist/regenerate/test-kinds.js +110 -63
  136. package/dist/regenerate/test-scripts.d.ts +2 -0
  137. package/dist/regenerate/test-scripts.js +4 -0
  138. package/dist/regenerate/testing-steps.d.ts +41 -0
  139. package/dist/regenerate/testing-steps.js +110 -0
  140. package/dist/regenerate/testing.d.ts +1 -1
  141. package/dist/regenerate/testing.js +5 -4
  142. package/dist/regenerate/unit-review.d.ts +1 -1
  143. package/dist/regenerate/unit-review.js +3 -3
  144. package/dist/regenerate-cli.js +11 -12
  145. package/dist/release-agent.js +4 -0
  146. package/dist/release-cli.js +2 -0
  147. package/dist/release-tools.js +2 -0
  148. package/dist/run-report.d.ts +2 -2
  149. package/dist/run-report.js +4 -3
  150. package/dist/self-invocation.d.ts +1 -1
  151. package/dist/self-invocation.js +1 -1
  152. package/dist/sentinel.js +1 -1
  153. package/dist/setup.d.ts +10 -0
  154. package/dist/setup.js +52 -4
  155. package/dist/skill.d.ts +10 -0
  156. package/dist/skill.js +6 -0
  157. package/dist/skills.js +3 -1
  158. package/dist/spec-tools.js +21 -31
  159. package/dist/test-plain-files.d.ts +41 -0
  160. package/dist/test-plain-files.js +177 -0
  161. package/docs/README.md +3 -1
  162. package/docs/authoring-plain.md +46 -13
  163. package/docs/chats.md +3 -1
  164. package/docs/config.md +20 -15
  165. package/docs/deploy.md +1 -1
  166. package/docs/development.md +2 -1
  167. package/docs/headless.md +15 -18
  168. package/docs/hosting.md +28 -12
  169. package/docs/installation.md +1 -0
  170. package/docs/parser.md +88 -27
  171. package/docs/regenerating.md +87 -52
  172. package/docs/sentinel.md +1 -1
  173. package/docs/skills-and-gates.md +3 -1
  174. package/docs/test-advisor.md +110 -0
  175. package/docs/theming.md +1 -1
  176. package/docs/using.md +17 -15
  177. package/genome/rules/bullet-continuation.md +8 -8
  178. package/genome/rules/concise-specs-examples.md +111 -80
  179. package/genome/rules/concise-specs-integration-examples.md +56 -41
  180. package/genome/rules/concise-specs.md +17 -16
  181. package/genome/rules/definitions.md +5 -5
  182. package/genome/rules/examples/integration-embedded-testing/run_conformance_tests_java.sh +1 -1
  183. package/genome/rules/func-specs.md +69 -74
  184. package/genome/rules/impl-reqs.md +4 -4
  185. package/genome/rules/integration-embedded-testing.md +20 -20
  186. package/genome/rules/integration-embedded.md +31 -31
  187. package/genome/rules/integration-standalone.md +9 -9
  188. package/genome/rules/integrations.md +18 -18
  189. package/genome/rules/linked-resources.md +19 -16
  190. package/genome/rules/module-structure.md +21 -14
  191. package/genome/rules/project-model.md +18 -17
  192. package/genome/rules/required-concepts.md +1 -1
  193. package/genome/rules/requires-modules.md +6 -5
  194. package/genome/rules/semantic-ids.md +49 -0
  195. package/genome/rules/stdlib.md +4 -5
  196. package/genome/rules/test-plain-files.md +130 -0
  197. package/genome/rules/test-reqs.md +8 -8
  198. package/genome/rules/turri-regeneration.md +23 -8
  199. package/genome/skills/analyze-2-func-specs/SKILL.md +36 -21
  200. package/genome/skills/analyze-func-specs/SKILL.md +46 -32
  201. package/genome/skills/break-down-func-spec/SKILL.md +91 -67
  202. package/genome/skills/consolidate-concepts/SKILL.md +10 -10
  203. package/genome/skills/convert-old-syntax/SKILL.md +68 -0
  204. package/genome/skills/convert-old-syntax/references/rubric.md +11 -0
  205. package/genome/skills/debug-specs/SKILL.md +62 -56
  206. package/genome/skills/implement-conformance-testing-script/SKILL.md +19 -15
  207. package/genome/skills/implement-conformance-testing-script/assets/run_conformance_tests_java.sh +2 -2
  208. package/genome/skills/implement-conformance-testing-script/assets/run_conformance_tests_python.sh +2 -2
  209. package/genome/skills/implement-unit-testing-script/SKILL.md +14 -10
  210. package/genome/skills/refactor-module/SKILL.md +35 -35
  211. package/genome/skills/resolve-section-ownership/SKILL.md +9 -9
  212. package/genome/skills/resolve-spec-conflict/SKILL.md +35 -29
  213. package/package.json +7 -6
  214. package/dist/regenerate/choose-conformance.d.ts +0 -32
  215. package/dist/regenerate/choose-conformance.js +0 -117
  216. /package/dist/{validate-cli.d.ts → parse-cli.d.ts} +0 -0
@@ -0,0 +1,144 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { readProjectConfig, stacksOf } from "./config.js";
5
+ import { bedrockAwsRegion, bedrockNeedsAwsRegion, bedrockPlaceOf } from "./init-config.js";
6
+ import { modelsInUse } from "./model-check.js";
7
+ /** The variable turri marks its own AWS_REGION with, so the pi it starts - and a reload - tells it from the person's. */
8
+ export const TURRI_AWS_REGION = "TURRI_AWS_REGION";
9
+ /** pi's region when nothing names one. */
10
+ const DEFAULT_REGION = "us-east-1";
11
+ /** Whether a region is in a geography: one of the regions Bedrock serves that geography's inference profiles from. */
12
+ export function regionInGeography(region, geography) {
13
+ const exact = {
14
+ jp: ["ap-northeast-1", "ap-northeast-3"],
15
+ au: ["ap-southeast-2", "ap-southeast-4"],
16
+ in: ["ap-south-1", "ap-south-2"],
17
+ };
18
+ if (exact[geography] !== undefined)
19
+ return exact[geography].includes(region);
20
+ const prefix = { eu: "eu-", apac: "ap-", ca: "ca-", "us-gov": "us-gov-", us: "us-" };
21
+ if (prefix[geography] !== undefined)
22
+ return region.startsWith(prefix[geography]) && (geography !== "us" || !region.startsWith("us-gov-"));
23
+ // global and in-region: any region serves them.
24
+ return true;
25
+ }
26
+ /** The region an AWS profile names, from AWS_CONFIG_FILE or ~/.aws/config - undefined when it names none, or the file cannot be read. */
27
+ export function profileRegion(profile, env = process.env) {
28
+ const file = env.AWS_CONFIG_FILE?.trim() || join(homedir(), ".aws", "config");
29
+ if (!existsSync(file))
30
+ return undefined;
31
+ let text;
32
+ try {
33
+ text = readFileSync(file, "utf8");
34
+ }
35
+ catch {
36
+ return undefined;
37
+ }
38
+ const wanted = profile === "default" ? ["default", "profile default"] : [`profile ${profile}`];
39
+ let inside = false;
40
+ for (const raw of text.split(/\r?\n/)) {
41
+ const line = raw.trim();
42
+ const section = /^\[\s*(.+?)\s*\]$/.exec(line);
43
+ if (section !== null) {
44
+ inside = wanted.includes(section[1].replace(/\s+/g, " "));
45
+ continue;
46
+ }
47
+ if (!inside)
48
+ continue;
49
+ const setting = /^region\s*=\s*(\S+)/.exec(line);
50
+ if (setting !== null)
51
+ return setting[1];
52
+ }
53
+ return undefined;
54
+ }
55
+ /** The Bedrock models a project uses: every model its config names - each stack's too - and those given. */
56
+ function bedrockModels(projectDir, also) {
57
+ const configs = [readProjectConfig(projectDir), ...stacksOf(projectDir).map((stack) => readProjectConfig(projectDir, stack))];
58
+ const specs = [...configs.flatMap((config) => modelsInUse(config).map((used) => used.spec)), ...also];
59
+ return [...new Set(specs.filter((spec) => spec.startsWith("amazon-bedrock/")))];
60
+ }
61
+ /**
62
+ * What turri does about the AWS region for a project, from the environment as it is - reading it, setting nothing.
63
+ * `also`: models used besides the config's - named on the command line, chosen in this session.
64
+ */
65
+ export function bedrockRegionFor(projectDir, options = {}) {
66
+ const env = options.env ?? process.env;
67
+ const models = bedrockModels(projectDir, options.also ?? []);
68
+ // The regions the models need, by region: a model served in global, us or in-region needs none.
69
+ const needed = new Map();
70
+ for (const model of models) {
71
+ const { geography } = bedrockPlaceOf(model);
72
+ if (!bedrockNeedsAwsRegion(geography))
73
+ continue;
74
+ const region = bedrockAwsRegion(geography);
75
+ needed.set(region, { geography, models: [...(needed.get(region)?.models ?? []), model] });
76
+ }
77
+ const conflict = needed.size > 1 ? [...needed].map(([region, { geography, models }]) => ({ region, geography, models })) : undefined;
78
+ const [only] = needed.size === 1 ? [...needed] : [];
79
+ const needs = only !== undefined ? { region: only[0], geography: only[1].geography } : undefined;
80
+ const withConflict = { ...(conflict !== undefined ? { conflict } : {}), ...(needs !== undefined ? { needs } : {}) };
81
+ // The person's region wins, always; turri's own is worked out again.
82
+ const ours = env[TURRI_AWS_REGION];
83
+ const set = env.AWS_REGION?.trim() || env.AWS_DEFAULT_REGION?.trim();
84
+ if (set !== undefined && set !== "" && !(ours !== undefined && env.AWS_REGION === ours))
85
+ return { region: set, source: "set", models, ...withConflict };
86
+ const profile = env.AWS_PROFILE?.trim();
87
+ const fromProfile = profile !== undefined && profile !== "" ? profileRegion(profile, env) : undefined;
88
+ // One region needed: the profile's, when it is in that geography; otherwise turri's.
89
+ if (needs !== undefined) {
90
+ if (fromProfile !== undefined && regionInGeography(fromProfile, needs.geography))
91
+ return { region: fromProfile, source: "profile", models, needs };
92
+ return { region: needs.region, source: "turri", models, needs };
93
+ }
94
+ // None needed, or two: pi goes by the profile, or its default - with two, which is not one of them for every model.
95
+ if (fromProfile !== undefined)
96
+ return { region: fromProfile, source: "profile", models, ...withConflict };
97
+ return { ...(models.length > 0 && conflict === undefined ? { region: DEFAULT_REGION } : {}), source: "default", models, ...withConflict };
98
+ }
99
+ /**
100
+ * Set AWS_REGION for a project's Bedrock models, as `bedrockRegionFor` says: when they need a region and the person
101
+ * set none. Done again when the models change, it moves or takes back what turri set before, never the person's.
102
+ */
103
+ export function applyBedrockRegion(projectDir, options = {}) {
104
+ const env = options.env ?? process.env;
105
+ let decided;
106
+ try {
107
+ decided = bedrockRegionFor(projectDir, { ...options, env });
108
+ }
109
+ catch {
110
+ // A config that does not read is the model check's to say: nothing is set from it.
111
+ return { source: "default", models: [] };
112
+ }
113
+ if (decided.source === "turri" && decided.region !== undefined) {
114
+ env.AWS_REGION = decided.region;
115
+ env[TURRI_AWS_REGION] = decided.region;
116
+ }
117
+ else if (decided.source !== "set" && env[TURRI_AWS_REGION] !== undefined) {
118
+ // What turri set before is needed no more - or two regions are now: taken back, not guessed.
119
+ if (env.AWS_REGION === env[TURRI_AWS_REGION])
120
+ delete env.AWS_REGION;
121
+ delete env[TURRI_AWS_REGION];
122
+ }
123
+ return decided;
124
+ }
125
+ /** A line on the region, for a model check: what turri set and how to override it - or the conflict it would not guess. */
126
+ export function describeBedrockRegion(decided) {
127
+ if (decided.conflict !== undefined) {
128
+ const needs = decided.conflict.map((each) => `${each.models.join(", ")} need${each.models.length === 1 ? "s" : ""} ${each.region} (${each.geography})`).join("; ");
129
+ return `the Bedrock models need two AWS regions - ${needs} - and one AWS_REGION serves every model: turri set none. Use models of one geography, or global ones.`;
130
+ }
131
+ if (decided.region === undefined)
132
+ return undefined;
133
+ if (decided.source === "turri")
134
+ return `turri calls Bedrock in ${decided.region}, the region the models' geography needs - AWS_REGION, set, overrides it`;
135
+ if (decided.source === "set") {
136
+ const outside = decided.needs !== undefined && !regionInGeography(decided.region, decided.needs.geography);
137
+ return outside
138
+ ? `pi calls Bedrock in ${decided.region}, set in AWS_REGION or AWS_DEFAULT_REGION - but the models are served in ${decided.needs.geography}: set it to a region there, ${decided.needs.region}, or unset it and turri sets it`
139
+ : `pi calls Bedrock in ${decided.region}, set in AWS_REGION or AWS_DEFAULT_REGION - the models have to be served there`;
140
+ }
141
+ if (decided.source === "profile")
142
+ return `pi calls Bedrock in ${decided.region}, the AWS profile's region - AWS_REGION, set, overrides it`;
143
+ return `pi calls Bedrock in ${decided.region}, its default - AWS_REGION, set, overrides it`;
144
+ }
package/dist/btw.d.ts CHANGED
@@ -12,7 +12,7 @@
12
12
  import type { AskUser, AskUserRequest } from "./ask.ts";
13
13
  /** How many tool calls the side agent may make before it is told to answer. */
14
14
  export declare const SIDE_CALLS = 20;
15
- /** "/btw what is a frid?" -> "what is a frid?"; "/btw" alone -> ""; anything else is not a side question. */
15
+ /** "/btw what is a func-id?" -> "what is a func-id?"; "/btw" alone -> ""; anything else is not a side question. */
16
16
  export declare function sideQuestionOf(text: string): string | undefined;
17
17
  /** The skill running now, as the side agent is told of it. */
18
18
  export interface SkillRunNow {
package/dist/btw.js CHANGED
@@ -19,7 +19,7 @@ export const SIDE_CALLS = 20;
19
19
  /** How much of a transcript the side agent is shown, from its end. */
20
20
  const CONVERSATION_CHARS = 24_000;
21
21
  const SKILL_SESSION_CHARS = 16_000;
22
- /** "/btw what is a frid?" -> "what is a frid?"; "/btw" alone -> ""; anything else is not a side question. */
22
+ /** "/btw what is a func-id?" -> "what is a func-id?"; "/btw" alone -> ""; anything else is not a side question. */
23
23
  export function sideQuestionOf(text) {
24
24
  const match = /^\s*\/btw(?:\s+|$)([\s\S]*)$/i.exec(text);
25
25
  return match === null ? undefined : match[1].trim();
@@ -1,38 +1,45 @@
1
1
  import { readFileSync } from "node:fs";
2
2
  import { resolve } from "node:path";
3
- import { checkPlainFile } from "./plain/check.js";
4
- import { applySpecDiff, revertSpecDiff } from "./regenerate/regenerate.js";
3
+ import { checkTouchedPlain, formatCheckResults } from "./plain/check.js";
4
+ import { applySpecDiff, plainFilesOf, revertSpecDiff } from "./regenerate/regenerate.js";
5
+ import { testPlainFileRefusal } from "./test-plain-files.js";
5
6
  import { parseArgs } from "./cli-options.js";
6
7
  import { headlessOptions, runLoop } from "./loop-cli.js";
7
8
  const USAGE = `usage: turri change-plain <file.plain> <diff-file> [options]
8
9
 
9
- Applies a change to ***plain, given as a git diff of the .plain files, and builds it: the headless
10
+ Applies a change to ***plain, given as a git diff of the .plain files - modules and test plain files - and
11
+ builds it: the headless
10
12
  counterpart of regenerate, given a diff. The diff is applied first, unless it is already in; then, as
11
13
  turri regenerate does, what is left to build is planned into jobs and built.
12
14
 
13
- --harness-model <p/id> config.turri.yaml's harness-model for this run: the planner, the conformance chooser, a diagnosis
15
+ --harness-model <p/id> config.turri.yaml's harness-model for this run: the planner, a diagnosis
14
16
  --coding-model <p/id> its coding-model, --model too: the agents that build
15
17
  --start-over start over, without asking, a codebase regenerated without a module it now requires
16
18
  --judge-model <p/id> its judge-model: the judge and the test review
17
19
  --review <true|false> its review: whether the code and its tests are reviewed once they pass
18
- --conformance-tests <all|chosen|module|false>
19
- its conformance-tests: which conformance tests are written and run
20
20
  --build-dir <dir> as the run ends, export the code there (see turri regenerate --help)
21
21
  --report <file.json> write a report of the run there (see turri regenerate --help)`;
22
- const { positional, options } = parseArgs(process.argv.slice(2), ["--start-over"], USAGE, ["--build-dir", "--report", "--conformance-tests", "--harness-model", "--judge-model", "--review"]);
23
- const { report, buildDir } = headlessOptions(options, true);
22
+ const { positional, options } = parseArgs(process.argv.slice(2), ["--start-over"], USAGE, ["--build-dir", "--report", "--harness-model", "--judge-model", "--review"]);
23
+ const { report, buildDir } = headlessOptions(options);
24
24
  const [plainFile, diffFile] = positional;
25
25
  if (plainFile === undefined || diffFile === undefined) {
26
26
  console.error(USAGE);
27
27
  process.exit(1);
28
28
  }
29
+ const refusal = testPlainFileRefusal(plainFile);
30
+ if (refusal !== undefined) {
31
+ console.error(refusal);
32
+ process.exit(1);
33
+ }
29
34
  const diff = readFileSync(diffFile, "utf8");
30
35
  const cwd = process.cwd();
31
- applySpecDiff(diff, cwd);
32
- const check = checkPlainFile(resolve(plainFile));
33
- if (!check.ok) {
34
- revertSpecDiff(diff, cwd);
35
- console.error(`Nothing was changed: with this diff ${plainFile} no longer parses. ${check.error}`);
36
+ const applied = applySpecDiff(diff, cwd);
37
+ // Every file the diff touched is checked - a test plain file as its module - not only the one named.
38
+ const failed = checkTouchedPlain([plainFile, ...plainFilesOf(diff)].map((file) => resolve(cwd, file))).filter((check) => !check.ok);
39
+ if (failed.length > 0) {
40
+ if (applied === "applied")
41
+ revertSpecDiff(diff, cwd);
42
+ console.error(`Nothing was changed: with this diff the ***plain no longer checks.\n${formatCheckResults(failed)}`);
36
43
  process.exit(1);
37
44
  }
38
45
  runLoop({
@@ -0,0 +1,7 @@
1
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
2
+ import type { ChatLink } from "./chats.ts";
3
+ /**
4
+ * Whether a chat is busy, for what turri tells it of its own accord - a test advisor's verdict, a background skill run's
5
+ * end - and so waits: its agent working, or the person typing - or no session to tell yet. Nothing told so interrupts.
6
+ */
7
+ export declare function chatBusy(link: ChatLink, context: () => ExtensionContext | undefined): boolean;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Whether a chat is busy, for what turri tells it of its own accord - a test advisor's verdict, a background skill run's
3
+ * end - and so waits: its agent working, or the person typing - or no session to tell yet. Nothing told so interrupts.
4
+ */
5
+ export function chatBusy(link, context) {
6
+ const session = link.session();
7
+ if (session === undefined || session.isStreaming || session.isCompacting || session.isRetrying)
8
+ return true;
9
+ const ctx = context();
10
+ try {
11
+ if (ctx !== undefined && (!ctx.isIdle() || ctx.hasPendingMessages()))
12
+ return true;
13
+ if (ctx?.hasUI === true && ctx.ui.getEditorText().trim() !== "")
14
+ return true;
15
+ }
16
+ catch {
17
+ /* a context let go: the session says enough */
18
+ }
19
+ return false;
20
+ }
package/dist/chats.d.ts CHANGED
@@ -6,6 +6,7 @@ import { type SentinelSetting } from "./config.ts";
6
6
  import type { AskUserIO } from "./ask.ts";
7
7
  import type { Job, JobRegistry } from "./regenerate/jobs.ts";
8
8
  import type { ToolDefinition } from "@earendil-works/pi-coding-agent";
9
+ import type { AdvisorVerdict } from "./regenerate/advisor-runs.ts";
9
10
  import { type ChatRole, type Conclusion, type Notice, type NoticeAnswer, type NoticeInput } from "./notice.ts";
10
11
  /**
11
12
  * The chats of a turri session.
@@ -110,12 +111,19 @@ export interface ChatInstance {
110
111
  concluded(conclusion: Conclusion): void;
111
112
  /** The person answered a notice with something to do: the chat's agent started on it. */
112
113
  act(task: string): void;
114
+ /**
115
+ * The test advisor's verdict: once the chat is idle - its agent done, nothing typed - a yes starts a turn that writes its
116
+ * scenarios, and a no is a line. Those that arrive meanwhile are delivered together.
117
+ */
118
+ advised(verdict: AdvisorVerdict): void;
113
119
  /** The jobs changed: what it draws of them is drawn again. */
114
120
  jobsChanged(): void;
115
121
  /** How hard the chat's model thinks now: sub-agents started from it think as hard. */
116
122
  thinkingLevel(): string | undefined;
117
123
  /** The chat's context, as its tools get it - for its model and its providers. */
118
124
  context(): ExtensionContext | undefined;
125
+ /** Conformance tests turned on or off: the chat's tools switched to match - conform and evaluate_tests, or the tool that turns them on. */
126
+ conformanceSwitched?(): void;
119
127
  }
120
128
  /** A chat's extension, linked to its chat. */
121
129
  export interface ChatLink {
@@ -213,6 +221,8 @@ export interface TurriCore {
213
221
  notice(input: NoticeInput): Notice | undefined;
214
222
  /** The sentinel done with a pulse: told to the chat the person looks at. Undefined: no chat to tell. */
215
223
  concluded(conclusion: Conclusion): string | undefined;
224
+ /** The test advisor's verdict, for the chat the person looks at - held until there is one. Undefined: no chat to hand it to yet. */
225
+ advised(verdict: AdvisorVerdict): string | undefined;
216
226
  /** The notices put to the person and not yet answered. */
217
227
  notices(): Notice[];
218
228
  /**
package/dist/chats.js CHANGED
@@ -104,6 +104,8 @@ export function createChats(options) {
104
104
  let consent = "unasked";
105
105
  let asking;
106
106
  const held = [];
107
+ /** The test advisor's verdicts that came with no chat of the person's to hand them to: handed on as one comes up. */
108
+ const unplaced = [];
107
109
  const run = { run: randomUUID().slice(0, 8), pid: process.pid, started: new Date().toISOString(), chats: [] };
108
110
  const changed = () => {
109
111
  for (const listener of listeners) {
@@ -357,6 +359,10 @@ export function createChats(options) {
357
359
  session: () => chat.session,
358
360
  live: (instance) => {
359
361
  chat.instance = instance;
362
+ // The advisor's verdicts that waited for a chat of the person's.
363
+ if (unplaced.length > 0 && personFacing() !== undefined)
364
+ for (const verdict of unplaced.splice(0))
365
+ core.advised(verdict);
360
366
  },
361
367
  };
362
368
  },
@@ -456,6 +462,23 @@ export function createChats(options) {
456
462
  changed();
457
463
  return target.id;
458
464
  },
465
+ advised(verdict) {
466
+ const target = personFacing();
467
+ if (target?.instance === undefined) {
468
+ unplaced.push(verdict);
469
+ return undefined;
470
+ }
471
+ if (target.id !== shownId)
472
+ target.unread = true;
473
+ try {
474
+ target.instance.advised(verdict);
475
+ }
476
+ catch {
477
+ /* a chat let go as it was told: a yes stays undelivered in the advisor's file, for the next start */
478
+ }
479
+ changed();
480
+ return target.id;
481
+ },
459
482
  answer(id, answer) {
460
483
  const notice = open.find((candidate) => candidate.id === id);
461
484
  if (notice === undefined)
@@ -15,11 +15,10 @@ export declare function parseArgs(argv: string[], flags: string[], usage: string
15
15
  export declare function optionValue(options: Record<string, string | undefined>, name: string): string | undefined;
16
16
  /**
17
17
  * The options that stand for the config's own keys - `--harness-model`, `--coding-model` (`--model`), `--judge-model`
18
- * and, as asked, `--review`, `--mutation-testing` and `--conformance-tests` - read as if config.turri.yaml had them, in their place in the
18
+ * and, as asked, `--review` and `--mutation-testing` - read as if config.turri.yaml had them, in their place in the
19
19
  * order the config resolves them in (overrideConfig): a run, or doctor, with them is the same as one with them in the
20
20
  * file. Exits on a value it cannot read.
21
21
  */
22
22
  export declare function configKeyOptions(options: Record<string, string | undefined>, take?: {
23
- conformance?: boolean;
24
23
  review?: boolean;
25
24
  }): void;
@@ -1,4 +1,4 @@
1
- import { booleanOption, conformanceOption, overrideConfig, splitModelSpec } from "./config.js";
1
+ import { booleanOption, overrideConfig, splitModelSpec } from "./config.js";
2
2
  /**
3
3
  * The command line of turri's commands: their words, and the options that stand for the config's own keys - kept apart
4
4
  * from the commands that use them, so a light one (doctor) reads them as a regenerate does without loading it.
@@ -51,7 +51,7 @@ export function optionValue(options, name) {
51
51
  }
52
52
  /**
53
53
  * The options that stand for the config's own keys - `--harness-model`, `--coding-model` (`--model`), `--judge-model`
54
- * and, as asked, `--review`, `--mutation-testing` and `--conformance-tests` - read as if config.turri.yaml had them, in their place in the
54
+ * and, as asked, `--review` and `--mutation-testing` - read as if config.turri.yaml had them, in their place in the
55
55
  * order the config resolves them in (overrideConfig): a run, or doctor, with them is the same as one with them in the
56
56
  * file. Exits on a value it cannot read.
57
57
  */
@@ -63,10 +63,6 @@ export function configKeyOptions(options, take = {}) {
63
63
  failOption(`--${name} takes true or false, not ${given}`);
64
64
  return value;
65
65
  };
66
- const tests = take.conformance === true ? optionValue(options, "conformance-tests") : undefined;
67
- const setting = tests !== undefined ? conformanceOption(tests) : undefined;
68
- if (tests !== undefined && setting === undefined)
69
- failOption(`--conformance-tests takes all, chosen, module or false, not ${tests}`);
70
66
  const review = flag("review");
71
67
  const mutationTesting = flag("mutation-testing");
72
68
  const model = (name, key) => {
@@ -77,7 +73,6 @@ export function configKeyOptions(options, take = {}) {
77
73
  ...model("harness-model", "harnessModel"),
78
74
  ...model("model", "codingModel"),
79
75
  ...model("judge-model", "judgeModel"),
80
- ...(setting !== undefined ? { conformanceTests: setting } : {}),
81
76
  ...(review !== undefined ? { review } : {}),
82
77
  ...(mutationTesting !== undefined ? { mutationTesting } : {}),
83
78
  });
package/dist/cli.js CHANGED
@@ -72,12 +72,12 @@ function chat(args) {
72
72
  process.exit(result.status ?? 1);
73
73
  }
74
74
  const COMMANDS = {
75
- regenerate: { module: "./regenerate-cli.ts", blurb: "regenerate a module and its chain from the spec as it is (with frids: regenerate those again)" },
75
+ regenerate: { module: "./regenerate-cli.ts", blurb: "regenerate a module and its chain from the spec as it is (with module:func-ids: regenerate those again)" },
76
76
  glimpse: { module: "./regenerate-cli.ts", blurb: "plan the work and print the first job's prompt, starting nothing", args: ["--dry-run"] },
77
77
  drift: { module: "./drift-cli.ts", blurb: "what is left to regenerate: how far the code has drifted from its spec" },
78
78
  conform: { module: "./conform-cli.ts", blurb: "write and run a module's conformance tests" },
79
79
  "change-plain": { module: "./change-plain-cli.ts", blurb: "apply a change to ***plain, given as a git diff, and regenerate it" },
80
- validate: { module: "./validate-cli.ts", blurb: "validate a ***plain spec, regenerating nothing" },
80
+ parse: { module: "./parse-cli.ts", blurb: "parse ***plain - its syntax and structure, never its code or tests - regenerating nothing" },
81
81
  lineage: { module: "./lineage-cli.ts", blurb: "a module's lineage: what was regenerated into it, and when" },
82
82
  ledger: { module: "./ledger-cli.ts", blurb: "what turri did here: the last runs and their pulses, or one pulse, from the ledger" },
83
83
  release: { module: "./release-cli.ts", blurb: "release what is regenerated to dist/<module> and activate it on this machine" },
@@ -147,6 +147,14 @@ if (command === "-v" || command === "--version") {
147
147
  console.log(versionLine());
148
148
  process.exit(0);
149
149
  }
150
+ // The AWS region the project's Bedrock models need, set before any model is called - here, and in the pi the chat starts,
151
+ // which inherits it - unless the person set one (bedrock-region.ts). The models named on the command line count too. A
152
+ // command that calls no model is left alone.
153
+ if (!["parse", "drift", "lineage", "ledger", "init-config"].includes(command ?? "")) {
154
+ const { applyBedrockRegion } = await import("./bedrock-region.js");
155
+ const named = argv.flatMap((arg, at) => ["--harness-model", "--coding-model", "--model"].includes(arg) ? [argv[at + 1] ?? ""] : arg.startsWith("--harness-model=") || arg.startsWith("--coding-model=") || arg.startsWith("--model=") ? [arg.slice(arg.indexOf("=") + 1)] : []);
156
+ applyBedrockRegion(process.cwd(), { also: named.filter((model) => model !== "") });
157
+ }
150
158
  // turri alone is the conversation, and so is turri with pi's options - `turri -c`, `turri --harness-model <m>`. A word
151
159
  // turri does not know is an error below, not a conversation started on it.
152
160
  if (command === undefined || command.startsWith("-"))
@@ -175,7 +183,7 @@ process.argv = [
175
183
  // A headless run is stopped by a signal - SIGTERM, SIGHUP, Ctrl-C - whatever else listens for it: it exits, and what
176
184
  // its agents left running goes with it (shell.ts kills every process group on exit). Left to the libraries' own
177
185
  // handlers, one ran on for minutes after it was told to stop, building in the same codebase as the run after it.
178
- if (!["validate", "check", "drift", "lineage", "ledger"].includes(command)) {
186
+ if (!["parse", "drift", "lineage", "ledger"].includes(command)) {
179
187
  for (const [signal, code] of [["SIGTERM", 143], ["SIGHUP", 129], ["SIGINT", 130]]) {
180
188
  process.on(signal, () => {
181
189
  console.error(`\nstopped (${signal})`);
@@ -1,4 +1,5 @@
1
1
  import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
2
+ import { type Setting } from "./config-edit.ts";
2
3
  /**
3
4
  * `/turri-config`: every setting of the project's config.turri.yaml from the chat - `/turri-config` alone lists them,
4
5
  * `/turri-config review` asks for review's value, `/turri-config review false` sets it. Each is written into the file
@@ -23,4 +24,17 @@ export interface TurriConfigOptions {
23
24
  chatSpec: () => string | undefined;
24
25
  live: LiveHooks;
25
26
  }
27
+ /** The models pi can reach here, signed in: provider/id - from a chat's context, the model registry the chats use. */
28
+ export declare function reachable(ctx: ExtensionContext | undefined): string[];
29
+ /**
30
+ * Every value /turri-config offers for a setting after what is typed, in its order - "unset" left out, and uncapped: what
31
+ * its completions show, the first of them, and what a host's settings list shows (host-session.ts `settingChoices`).
32
+ * A model setting's are every model pi can reach, signed in - `models` - and after a colon, that model's thinking levels.
33
+ */
34
+ export declare function settingCandidates(setting: Setting, typed: string, where: {
35
+ cwd: string;
36
+ models: () => string[];
37
+ }): string[];
38
+ /** The ***plain rules there are, by name - what rules-off takes, and its values. */
39
+ export declare function ruleNames(): string[];
26
40
  export declare function registerTurriConfig(pi: ExtensionAPI, options: TurriConfigOptions): void;
@@ -1,12 +1,16 @@
1
+ import { join } from "node:path";
2
+ import { applyBedrockRegion } from "./bedrock-region.js";
1
3
  import { CONFIG_FILE, LEGACY_CONFIG_FILE, THINKING_LEVELS, usesLegacyConfig } from "./config.js";
2
4
  import { currentValue, dotenvFiles, EFFECT_TEXT, overriddenBy, parseValue, pathOptions, readConfigText, settingOf, settings, shownValue, writeSetting, } from "./config-edit.js";
5
+ import { PACKAGE_ROOT } from "./environment.js";
6
+ import { loadRules } from "./rules.js";
3
7
  /** The option that unsets a setting, for a setting. */
4
8
  const unsetOption = (setting) => `unset - ${setting.unset}`;
5
9
  const TYPE_ANOTHER = "type another…";
6
10
  const DONE = "done";
7
11
  const ROLES_ENTRY = "models.<role> - each agent role's own model, thinking and turn limit…";
8
- /** The models pi can reach here, signed in: provider/id. */
9
- function reachable(ctx) {
12
+ /** The models pi can reach here, signed in: provider/id - from a chat's context, the model registry the chats use. */
13
+ export function reachable(ctx) {
10
14
  try {
11
15
  return (ctx?.modelRegistry.getAvailable() ?? []).map((model) => `${model.provider}/${model.id}`).sort();
12
16
  }
@@ -14,6 +18,31 @@ function reachable(ctx) {
14
18
  return [];
15
19
  }
16
20
  }
21
+ /** How many values the terminal's autocomplete shows for a setting: a host reads them whole (`settingCandidates`). */
22
+ const COMPLETIONS_SHOWN = 40;
23
+ /**
24
+ * Every value /turri-config offers for a setting after what is typed, in its order - "unset" left out, and uncapped: what
25
+ * its completions show, the first of them, and what a host's settings list shows (host-session.ts `settingChoices`).
26
+ * A model setting's are every model pi can reach, signed in - `models` - and after a colon, that model's thinking levels.
27
+ */
28
+ export function settingCandidates(setting, typed, where) {
29
+ const candidates = setting.kind === "boolean"
30
+ ? ["true", "false"]
31
+ : setting.kind === "model"
32
+ ? typed.includes(":")
33
+ ? THINKING_LEVELS.map((level) => `${typed.slice(0, typed.lastIndexOf(":"))}:${level}`)
34
+ : where.models()
35
+ : setting.kind === "path"
36
+ ? pathOptions(where.cwd, setting.key)
37
+ : setting.key === "agent-env.files"
38
+ ? dotenvFiles(where.cwd)
39
+ : (setting.options ?? []);
40
+ return candidates.filter((candidate) => candidate.startsWith(typed));
41
+ }
42
+ /** The ***plain rules there are, by name - what rules-off takes, and its values. */
43
+ export function ruleNames() {
44
+ return loadRules(join(PACKAGE_ROOT, "genome", "rules")).map((rule) => rule.name);
45
+ }
17
46
  /** A setting's line in the list: its key, what it is now - or what it is unset - and what it does. */
18
47
  function listLine(setting, text) {
19
48
  const value = currentValue(text, setting.key);
@@ -39,6 +68,9 @@ export function registerTurriConfig(pi, options) {
39
68
  const adopting = usesLegacyConfig(cwd);
40
69
  const text = writeSetting(cwd, setting.key, value);
41
70
  const notes = [];
71
+ // A model changed: the AWS region its Bedrock models need is worked out again - set, moved or taken back by turri.
72
+ if (setting.key.endsWith("-model") || setting.key.startsWith("models."))
73
+ applyBedrockRegion(cwd);
42
74
  // What turri fixed at its start, changed now.
43
75
  if (setting.key === "harness-model" || setting.key === "models.chat") {
44
76
  const said = await options.live.chatModel(options.chatSpec(), ctx);
@@ -83,7 +115,6 @@ export function registerTurriConfig(pi, options) {
83
115
  case "choice":
84
116
  return pick(setting.options ?? [], false);
85
117
  case "number":
86
- case "budget":
87
118
  return pick(setting.options ?? []);
88
119
  case "path":
89
120
  return pick(pathOptions(cwd, setting.key));
@@ -182,18 +213,7 @@ export function registerTurriConfig(pi, options) {
182
213
  if (setting === undefined)
183
214
  return null;
184
215
  const typed = prefix.slice(space + 1).trimStart();
185
- const candidates = setting.kind === "boolean"
186
- ? ["true", "false"]
187
- : setting.kind === "model"
188
- ? typed.includes(":")
189
- ? THINKING_LEVELS.map((level) => `${typed.slice(0, typed.lastIndexOf(":"))}:${level}`)
190
- : reachable(seen)
191
- : setting.kind === "path"
192
- ? pathOptions(cwd, setting.key)
193
- : setting.key === "agent-env.files"
194
- ? dotenvFiles(cwd)
195
- : (setting.options ?? []);
196
- const items = [...candidates, "unset"].filter((candidate) => candidate.startsWith(typed)).slice(0, 40);
216
+ const items = [...settingCandidates(setting, typed, { cwd, models: () => reachable(seen) }), ...("unset".startsWith(typed) ? ["unset"] : [])].slice(0, COMPLETIONS_SHOWN);
197
217
  return items.length === 0 ? null : items.map((candidate) => ({ value: `${setting.key} ${candidate}`, label: candidate }));
198
218
  },
199
219
  handler: async (args, ctx) => {
@@ -6,7 +6,7 @@
6
6
  */
7
7
  /** When a change takes effect. Nothing turri reads needs a restart: what is fixed at its start is changed live. */
8
8
  export type Effect = "now" | "next-message" | "next-pulse" | "next-plan" | "next-test-run" | "next-landing" | "next-regenerate" | "next-start";
9
- export type SettingKind = "boolean" | "choice" | "number" | "model" | "list" | "path" | "budget";
9
+ export type SettingKind = "boolean" | "choice" | "number" | "model" | "list" | "path";
10
10
  export interface Setting {
11
11
  /** As written in the file - a nested one by its path: `models.coding`, `models.coding.turn-limit`, `agent-env.files`. */
12
12
  key: string;
@@ -16,7 +16,7 @@ export interface Setting {
16
16
  /** What it is when it is not set. */
17
17
  unset: string;
18
18
  effect: Effect;
19
- /** The words it takes (choice), or the values offered first (number, budget, list). */
19
+ /** The words it takes (choice), or the values offered first (number, list). */
20
20
  options?: string[];
21
21
  /** The least a number may be. */
22
22
  min?: number;
@@ -65,7 +65,7 @@ export declare function overriddenBy(dir: string, text: string, setting: Setting
65
65
  * file in the old naming when they are what the project is read from - and give back the file as written.
66
66
  */
67
67
  export declare function writeSetting(dir: string, key: string, value: unknown): string;
68
- /** The files a path setting is offered: the testing scripts in the root and test_scripts/, or the .plain files of the folder. */
68
+ /** The files a path setting is offered: the testing scripts in the root and test_scripts/, the .plain files of the folder, or - the test folder - its folders. */
69
69
  export declare function pathOptions(dir: string, key: string): string[];
70
70
  /** The dotenv files of the project root, for agent-env.files. */
71
71
  export declare function dotenvFiles(dir: string): string[];