@turri-dev/cli 0.1.27-next.763d091 → 0.1.28-next.d5f2680

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 +71 -15
  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 -20
  95. package/dist/regenerate/conformance.js +118 -121
  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 +61 -49
  104. package/dist/regenerate/loop.js +473 -340
  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 +69 -31
  135. package/dist/regenerate/test-kinds.js +109 -80
  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 +89 -54
  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 +24 -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
@@ -1,6 +1,7 @@
1
1
  import { type AgentSession, type ExtensionFactory, type ExtensionUIContext } from "@earendil-works/pi-coding-agent";
2
2
  import type { AskUserIO } from "./ask.ts";
3
3
  import { type ChatInfo, type TurriHost } from "./chats.ts";
4
+ import { type EvaluateOutcome } from "./advisor-chat.ts";
4
5
  import type { JobRegistry } from "./regenerate/jobs.ts";
5
6
  import type { WorkLoop } from "./regenerate/loop.ts";
6
7
  import type { Notice, NoticeAnswer } from "./notice.ts";
@@ -66,6 +67,31 @@ export interface Turri {
66
67
  * hears each as it is put (`onNotice`) and as it ends (`onNoticeOver`).
67
68
  */
68
69
  notices(): Notice[];
70
+ /**
71
+ * The test advisor asked to evaluate functional specs, as the chat's `evaluate_tests` does - for a host's own way in,
72
+ * a button on a functional spec: `module:func-id`s, or func-ids of `plainFile`'s module, even a text judged already and
73
+ * whatever `test-advisor` says, `reason` given to it as context. It returns at once; each verdict comes to the chat the
74
+ * person looks at, as the advisor's verdicts do - a yes starting a turn that writes the scenarios, a no a line.
75
+ */
76
+ evaluateTests(funcIds: string[], options?: {
77
+ plainFile?: string;
78
+ reason?: string;
79
+ }): EvaluateOutcome;
80
+ /**
81
+ * Every value /turri-config offers for a setting after `typed` - what its completions compute, in their order, but
82
+ * whole: uncapped, and without "unset", which a host draws itself; the values alone, not `<key> <value>`. A model
83
+ * setting's are every model of every provider signed in to, from the model registry the chats use - and after a colon,
84
+ * `harness-model amazon-bedrock/global.anthropic.claude-opus-5-5:`, that model's thinking levels. An unknown key gives none.
85
+ */
86
+ settingChoices(key: string, typed?: string): string[];
87
+ /**
88
+ * Conformance tests turned on, as the chat's turn_on_conformance_tests does: `conformance-tests: true` written where
89
+ * the module's value comes from - its stack's `config.turri.<stack>.yaml` when that sets the key, else
90
+ * `config.turri.yaml` - and every open chat's tools switched (conform and evaluate_tests in, turn_on_conformance_tests
91
+ * out), the test advisor starting at its next look. `plainFile`, absolute or from the project, picks the module; omitted,
92
+ * the project's. Returns the file written, from the project. Starts no conformance pulse: that is `conform`'s.
93
+ */
94
+ turnOnConformanceTests(plainFile?: string): string;
69
95
  /**
70
96
  * A notice answered: one of its actions (by index or label) - the chat it is in started on it - the person's own words
71
97
  * about it, dismissed, or - `ignorable: true` - ignored for good: left as the build has it, the sentinel told not to
@@ -1,12 +1,15 @@
1
1
  import { createAgentSession, DefaultResourceLoader, getAgentDir, SessionManager } from "@earendil-works/pi-coding-agent";
2
2
  import { lastRun } from "./chat-records.js";
3
3
  import { interruptChat } from "./chats.js";
4
- import { createTurriCore, sessionLoop } from "./extension.js";
4
+ import { evaluateTests } from "./advisor-chat.js";
5
+ import { reachable, ruleNames, settingCandidates } from "./config-command.js";
6
+ import { settingOf } from "./config-edit.js";
7
+ import { createTurriCore, sessionAdvisor, sessionLoop, turnOnConformance } from "./extension.js";
5
8
  import { stopDeployed } from "./regenerate/release.js";
6
9
  /** turri's session in a host, in the process's working folder - the project's. */
7
10
  export async function createTurri(options = {}) {
8
11
  const { dialogs, uiContext, extensions = [], agentDir = getAgentDir(), sessionDir, restore, ...host } = options;
9
- const core = createTurriCore(host, { persist: true, ...(dialogs !== undefined ? { dialogs } : {}) });
12
+ const core = createTurriCore(host, { persist: true, advisor: true, ...(dialogs !== undefined ? { dialogs } : {}) });
10
13
  const cwd = core.cwd;
11
14
  core.setDriver({
12
15
  async create(chat, sessionFile) {
@@ -68,6 +71,31 @@ export async function createTurri(options = {}) {
68
71
  },
69
72
  decline: async (id) => core.decline(id),
70
73
  notices: () => core.notices(),
74
+ evaluateTests: (funcIds, options) => evaluateTests(cwd, sessionAdvisor(core), funcIds, options),
75
+ turnOnConformanceTests: (plainFile) => turnOnConformance(core, plainFile),
76
+ settingChoices: (key, typed = "") => {
77
+ const setting = settingOf(key, ruleNames());
78
+ if (setting === undefined)
79
+ return [];
80
+ // The models from a chat's own context - the one shown, else any open: the registry the chats use, whether or not one
81
+ // has run /turri-config.
82
+ const context = () => {
83
+ const shown = core.shownChat();
84
+ const states = [shown, ...core.chats().map((chat) => core.session(chat.id)).map((session) => (session !== undefined ? core.chatOfSession(session) : undefined))];
85
+ for (const state of states) {
86
+ try {
87
+ const context = state?.instance?.context();
88
+ if (context !== undefined)
89
+ return context;
90
+ }
91
+ catch {
92
+ /* a context let go: the next one */
93
+ }
94
+ }
95
+ return undefined;
96
+ };
97
+ return settingCandidates(setting, typed, { cwd, models: () => reachable(context()) });
98
+ },
71
99
  answer: async (id, answer) => core.answer(id, answer),
72
100
  onChats: (listener) => core.onChats(() => listener(core.chats())),
73
101
  dispose,
package/dist/host.d.ts CHANGED
@@ -10,9 +10,10 @@ export { SENTINEL, SENTINEL_START, type Conclusion, type Notice, type NoticeActi
10
10
  export type { AskUserIO } from "./ask.ts";
11
11
  /** pi itself, as turri is built against it: a host's own extensions and UI are made with this one, not another copy. */
12
12
  export * as pi from "@earendil-works/pi-coding-agent";
13
- export { configForSpec, debugMode, hasProjectConfig, modelSpec, projectModels, readProjectConfig, stacksOf, type ProjectConfig } from "./config.ts";
13
+ export { configForSpec, debugMode, DEFAULT_TEST_DIR, hasProjectConfig, modelSpec, projectModels, readProjectConfig, stacksOf, testDirOf, type ProjectConfig } from "./config.ts";
14
14
  export { MODULES_DIR, projectDirOf, resolveTemplateDirs } from "./regenerate/paths.ts";
15
15
  export { configSettings, currentValue, EFFECT_TEXT, overriddenBy, readConfigText, type Effect, type Setting, type SettingKind } from "./config-edit.ts";
16
+ export { bedrockRegionFor, type BedrockRegion, type BedrockRegionSource } from "./bedrock-region.ts";
16
17
  export { BEDROCK_DEFAULT, BEDROCK_GEOGRAPHIES, BEDROCK_IN_REGION, BEDROCK_REGIONS, bedrockAwsRegion, bedrockDefaultModels, bedrockMakers, bedrockModels, bedrockModelsIn, bedrockNeedsAwsRegion, bedrockPlaceOf, initConfig, makerLabel, VENDORS, type BedrockChoice, type BedrockMaker, type BedrockOffer, type InitConfigOptions, type ModelChoice, type ModelSet, type Vendor, } from "./init-config.ts";
17
18
  export { piModelCatalogue, type CatalogueModel, type ModelCatalogue } from "./model-check.ts";
18
19
  export { doctor, type Check, type DoctorOptions, type Level } from "./setup.ts";
@@ -22,11 +23,13 @@ export { FUNCTIONAL_REQUIREMENTS, iterateFunctionalSpecs } from "./plain/spec.ts
22
23
  export { highlightLines, roleOf, type PlainRole } from "./plain/highlight.ts";
23
24
  export { conceptsOf, type ModuleConcept } from "./plain/module-concepts.ts";
24
25
  export { conceptBeingTyped, findConcepts } from "./plain/concepts.ts";
25
- export { plainGraph, type PlainConceptNode, type PlainGraph, type PlainItemNode, type PlainLink, type PlainModuleNode, type PlainSection } from "./plain/graph.ts";
26
+ export { plainGraph, type PlainConceptNode, type PlainGraph, type PlainItemNode, type PlainLink, type PlainModuleNode, type PlainScenario, type PlainSection, type PlainTestPlainFileNode } from "./plain/graph.ts";
26
27
  export { isStack, STDLIB_KINDS, stdlibTemplates, type StdlibKind, type StdlibTemplate } from "./stdlib.ts";
27
28
  export { createWorkLoop, describeModules, functionalityCompletion, promptOf, type DriftReport, type ModuleStatus, type WorkLoop } from "./regenerate/loop.ts";
28
29
  export { createJobRegistry, defaultParallelJobs, type Job, type JobRegistry, type PulseCases, type PulseSpec } from "./regenerate/jobs.ts";
29
30
  export { describeTurnIncident, onModelEvent, type TranscriptEntry } from "./regenerate/activity.ts";
30
31
  export { sweepOrphanedGroups } from "./regenerate/shell.ts";
32
+ export { readAdvisor, textHash, type AdvisorEntry, type AdvisorFile, type AdvisorReport, type AdvisorState } from "./regenerate/advisor-records.ts";
33
+ export type { EvaluateOutcome } from "./advisor-chat.ts";
31
34
  export { ledgerDir, onLedgerEvent, readLedger, type AgentRef, type LedgerEntry, type LedgerEvent, type LedgerKind, type LedgerQuery } from "./regenerate/ledger.ts";
32
35
  export { asJob, describePulseView, describeRuns, earlierPulses, ledgerProjects, pulseView, runViews, type PulseView, type RunView } from "./regenerate/ledger-view.ts";
package/dist/host.js CHANGED
@@ -12,11 +12,14 @@ export { SENTINEL, SENTINEL_START } from "./notice.js";
12
12
  /** pi itself, as turri is built against it: a host's own extensions and UI are made with this one, not another copy. */
13
13
  export * as pi from "@earendil-works/pi-coding-agent";
14
14
  // The project and its settings.
15
- export { configForSpec, debugMode, hasProjectConfig, modelSpec, projectModels, readProjectConfig, stacksOf } from "./config.js";
15
+ export { configForSpec, debugMode, DEFAULT_TEST_DIR, hasProjectConfig, modelSpec, projectModels, readProjectConfig, stacksOf, testDirOf } from "./config.js";
16
16
  export { MODULES_DIR, projectDirOf, resolveTemplateDirs } from "./regenerate/paths.js";
17
17
  // Every setting of config.turri.yaml as /turri-config knows it - what it takes, what it is unset, when a change takes
18
18
  // effect - and what the file has now: for a host that shows them all, changing each through /turri-config.
19
19
  export { configSettings, currentValue, EFFECT_TEXT, overriddenBy, readConfigText } from "./config-edit.js";
20
+ // The AWS region a project's Bedrock models are called in, and where it comes from - what turri sets itself, as a host's
21
+ // session starts, when the models need one and the person set none.
22
+ export { bedrockRegionFor } from "./bedrock-region.js";
20
23
  // A project's first config.turri.yaml, as `turri init-config` writes it - the vendor, or the two models themselves, chosen by the host.
21
24
  export { BEDROCK_DEFAULT, BEDROCK_GEOGRAPHIES, BEDROCK_IN_REGION, BEDROCK_REGIONS, bedrockAwsRegion, bedrockDefaultModels, bedrockMakers, bedrockModels, bedrockModelsIn, bedrockNeedsAwsRegion, bedrockPlaceOf, initConfig, makerLabel, VENDORS, } from "./init-config.js";
22
25
  // pi's model catalogue, read without a request: what initConfig checks the models given against, and what a host offers.
@@ -41,6 +44,9 @@ export { createWorkLoop, describeModules, functionalityCompletion, promptOf } fr
41
44
  export { createJobRegistry, defaultParallelJobs } from "./regenerate/jobs.js";
42
45
  export { describeTurnIncident, onModelEvent } from "./regenerate/activity.js";
43
46
  export { sweepOrphanedGroups } from "./regenerate/shell.js";
47
+ // The test advisor: what it made of each functional spec of a module - pending, yes, no or failed, with its report - by
48
+ // func-id, an entry current while its hash is textHash of the functional spec's text; and what an evaluation came to.
49
+ export { readAdvisor, textHash } from "./regenerate/advisor-records.js";
44
50
  // What turri did, as facts: the ledger, heard live and read back - this session's pulses and every earlier one's.
45
51
  export { ledgerDir, onLedgerEvent, readLedger } from "./regenerate/ledger.js";
46
52
  export { asJob, describePulseView, describeRuns, earlierPulses, ledgerProjects, pulseView, runViews } from "./regenerate/ledger-view.js";
@@ -108,12 +108,12 @@ export declare function withModels(text: string, lines: [string, string]): strin
108
108
  * must stay what the code falls back to - tests/init-config.test.ts holds them to the constants they mirror.
109
109
  */
110
110
  export declare const DEFAULTS: {
111
- readonly conformanceTests: false;
112
111
  readonly review: false;
113
112
  readonly mutationTesting: true;
114
113
  readonly allowToStopJobs: true;
115
114
  readonly releaseAfterPulse: true;
116
115
  readonly sentinel: "ask";
116
+ readonly testAdvisor: true;
117
117
  readonly testingScriptsRunOn: "in-place";
118
118
  readonly turnLimit: 120;
119
119
  readonly compactAt: 100000;
@@ -215,12 +215,12 @@ export function withModels(text, lines) {
215
215
  * must stay what the code falls back to - tests/init-config.test.ts holds them to the constants they mirror.
216
216
  */
217
217
  export const DEFAULTS = {
218
- conformanceTests: false,
219
218
  review: false,
220
219
  mutationTesting: true,
221
220
  allowToStopJobs: true,
222
221
  releaseAfterPulse: true,
223
222
  sentinel: "ask",
223
+ testAdvisor: true,
224
224
  testingScriptsRunOn: "in-place",
225
225
  turnLimit: 120,
226
226
  compactAt: 100000,
@@ -247,10 +247,10 @@ export function quickStartConfig(vendor = "other", bedrock, models) {
247
247
  "# built: change one to change it. The optional ones, which have no value of their own, are commented out - uncomment",
248
248
  "# one to use it. docs/config.md has them all.",
249
249
  "",
250
- "# Conformance tests: all (or true) - every functional spec of a module with test reqs; chosen - those with acceptance",
251
- "# tests and those the chooser picks as each pulse ends; module - the chooser picks, and plans them, once the whole",
252
- "# module is regenerated; false - none written or run. Those you ask for are written under any of them.",
253
- `conformance-tests: ${DEFAULTS.conformanceTests}`,
250
+ "# Whether functional specs are conformance tested: black-box tests of the built software against the scenarios of",
251
+ "# their test plain files, run with the whole suite as more lands. false: none - no scenarios written or tested, the",
252
+ "# test advisor off. Asking for them in the chat turns them on, or true here.",
253
+ "conformance-tests: false",
254
254
  "",
255
255
  "# Whether the code and its tests are reviewed once they pass - two reviews at most, the second weighed by the arbiter.",
256
256
  `review: ${DEFAULTS.review}`,
@@ -270,6 +270,11 @@ export function quickStartConfig(vendor = "other", bedrock, models) {
270
270
  "# before it first opens in a session; true: it opens without asking; false: none.",
271
271
  `sentinel: ${DEFAULTS.sentinel}`,
272
272
  "",
273
+ "# Whether turri checks, in the background, each new or changed functional spec with no scenarios - could it fail",
274
+ "# silently, be read two ways, cost a lot when wrong - and when it needs testing, writes its test reqs, conformance",
275
+ "# testing script and scenarios itself. false: only the scenarios you ask for are written.",
276
+ `test-advisor: ${DEFAULTS.testAdvisor}`,
277
+ "",
273
278
  "# What the testing scripts run on: in-place - the code itself; copy - a fresh copy of it.",
274
279
  `testing-scripts-run-on: ${DEFAULTS.testingScriptsRunOn}`,
275
280
  "",
@@ -323,9 +328,13 @@ export function quickStartConfig(vendor = "other", bedrock, models) {
323
328
  "# Optional: the .plain file turri regenerate builds when none is named.",
324
329
  "# module: app.plain",
325
330
  "",
326
- "# Optional, under conformance-tests: chosen or module: how many functional specs of a module may be chosen - a count or a share.",
327
- "# Without it, any number.",
328
- "# conformance-budget: 30%",
331
+ "# Optional: the folder of the test plain files - a functional spec's conformance and acceptance scenarios - a folder",
332
+ "# per module in it: tests/<module>/<func-id>.plain. Without it, tests.",
333
+ "# test-dir: tests",
334
+ "",
335
+ "# Optional: a folder the code starts from - an existing codebase to build on, or files to use, such as images. It is",
336
+ "# copied in once, before the first pulse, and never written to; the ***plain can refer to it, and link into it.",
337
+ "# base-folder: existing-app",
329
338
  "",
330
339
  "# Optional: the judge and test review of a conformance run, when not on coding-model. A model is provider/id,",
331
340
  "# provider/id:thinking, or { model, thinking } - thinking is off, minimal, low, medium, high, xhigh or max.",
@@ -337,7 +346,6 @@ export function quickStartConfig(vendor = "other", bedrock, models) {
337
346
  `# default: ${coding}`,
338
347
  `${column(`# chat: ${harness}`, 72)}# the conversation - as harness-model, which it wins over`,
339
348
  `${column(`# sentinel: ${harness}`, 72)}# the sentinel: checks every finished pulse and tests the release`,
340
- role("chooser", harness, "chooses what gets conformance tests under turri regenerate"),
341
349
  role("planner", harness, "cuts the work into pulses"),
342
350
  role("diagnosis", harness, "looks into a pulse that failed for good"),
343
351
  role("release", coding, "turri release's agent, and release-after-pulse's"),
@@ -349,6 +357,7 @@ export function quickStartConfig(vendor = "other", bedrock, models) {
349
357
  role("judge", strong, "judges a failing conformance run - as judge-model, which it wins over"),
350
358
  role("arbiter", strong, "weighs a second review"),
351
359
  role("debugger", strong, "a fresh look at unit tests that keep failing the same way"),
360
+ role("advisor", coding, "the test advisor: which functional specs need scenarios"),
352
361
  "",
353
362
  ].join("\n");
354
363
  }
@@ -411,12 +420,12 @@ function signInText(vendor, bedrock, models) {
411
420
  return [
412
421
  brand.bold("Sign in to AWS"),
413
422
  ` ${set("AWS_PROFILE")} ${brand.muted("your profile (aws configure, or aws sso login)")}`,
414
- ` ${set("AWS_REGION", region)}`,
415
423
  ` ${brand.muted("or, instead of a profile, a Bedrock API key:")} ${set("AWS_BEARER_TOKEN_BEDROCK")}`,
416
424
  "",
417
425
  brand.muted(keep),
426
+ // The region is turri's to set (bedrock-region.ts): said, not asked for.
418
427
  ...(bedrockNeedsAwsRegion(geography)
419
- ? [brand.muted(`AWS_REGION has to be in ${geography}'s geography, as ${region} is: pi calls Bedrock in us-east-1 unless it - or the profile - names another.`)]
428
+ ? [brand.muted(`turri calls ${geography}'s models in ${region}. AWS_REGION, set, overrides it - and so does your AWS profile's region, when it is in ${geography}.`)]
420
429
  : []),
421
430
  brand.muted("Your AWS account needs access to the models: Bedrock console, model access."),
422
431
  ];
@@ -25,10 +25,10 @@ export declare function runLoop(options: {
25
25
  export declare function moduleStates(reports: DriftReport[]): ModuleState[];
26
26
  /**
27
27
  * The options every command that regenerates takes for a headless run: `--report` and `--build-dir`, and the config's
28
- * own keys (configKeyOptions) - `--harness-model`, `--coding-model` (`--model`), `--judge-model`, `--review`, `--mutation-testing` and, where
29
- * `conformance` is true, `--conformance-tests` - read as if config.turri.yaml had them. Exits on a value it cannot read.
28
+ * own keys (configKeyOptions) - `--harness-model`, `--coding-model` (`--model`), `--judge-model`, `--review` and
29
+ * `--mutation-testing` - read as if config.turri.yaml had them. Exits on a value it cannot read.
30
30
  */
31
- export declare function headlessOptions(options: Record<string, string | undefined>, conformance: boolean): {
31
+ export declare function headlessOptions(options: Record<string, string | undefined>): {
32
32
  report?: string;
33
33
  buildDir?: string;
34
34
  };
package/dist/loop-cli.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { sweepOrphanedGroups } from "./regenerate/shell.js";
2
+ import { refuseTestPlainFile } from "./test-plain-files.js";
2
3
  import { createInterface } from "node:readline/promises";
3
4
  import { resolve, sep } from "node:path";
4
5
  import { configForSpec, inheritModel, modelOnly, modelSpec, projectModels, readProjectConfig, roleModel, ROLE_THINKING, splitModelSpec, } from "./config.js";
@@ -9,7 +10,6 @@ import { ledgerWanted, startLedger } from "./regenerate/ledger.js";
9
10
  import { tryItCommand } from "./regenerate/delivery.js";
10
11
  import { describeDiagnosis, diagnoseFailure } from "./regenerate/diagnose.js";
11
12
  import { createJobRegistry, pulseName } from "./regenerate/jobs.js";
12
- import { conformanceChooser } from "./regenerate/choose-conformance.js";
13
13
  import { createWorkLoop, describeModules, functionalityCompletion, promptOf } from "./regenerate/loop.js";
14
14
  import { MODULES_DIR, projectDirOf } from "./regenerate/paths.js";
15
15
  import { exportModule } from "./regenerate/release.js";
@@ -25,6 +25,7 @@ import { configKeyOptions, optionValue } from "./cli-options.js";
25
25
  */
26
26
  export async function runLoop(options) {
27
27
  const plainFile = resolve(options.plainFile);
28
+ refuseTestPlainFile(plainFile);
28
29
  // What a turri that was killed left running - commands' process groups - stopped before anything starts.
29
30
  const orphans = sweepOrphanedGroups();
30
31
  if (orphans > 0)
@@ -54,7 +55,6 @@ export async function runLoop(options) {
54
55
  specFile: plainFile,
55
56
  projectDir,
56
57
  ...(buildDir !== undefined ? { buildDir } : {}),
57
- conformanceTests: conformanceLabel(project.conformanceTests),
58
58
  review: project.review === true,
59
59
  mutationTesting: project.mutationTesting !== false,
60
60
  models: reportModels(project, model),
@@ -111,9 +111,6 @@ async function runLoopReporting(options) {
111
111
  ...(model !== undefined ? { modelFor: () => model } : {}),
112
112
  ...(planner !== undefined ? { plannerModel: planner } : {}),
113
113
  ...(options.dryRun === true ? { dryRun: true } : {}),
114
- // Under conformance-tests: chosen or module, the chooser decides as a pulse ends, as in the chat - on the harness
115
- // model, else the coding model: never pi's own default, which may be one this account cannot reach.
116
- onChoose: conformanceChooser({ model: () => planner ?? model, ...(options.dryRun === true ? { dryRun: true } : {}) }),
117
114
  });
118
115
  report?.attach({
119
116
  jobs: () => registry.list(),
@@ -130,7 +127,9 @@ async function runLoopReporting(options) {
130
127
  const toStartOver = planned.filter((status) => status.startOver !== undefined);
131
128
  if (toStartOver.length > 0 && options.dryRun !== true) {
132
129
  const why = toStartOver
133
- .map(({ startOver }) => `${startOver.codebases.join(", ")} was built without ${startOver.requires.join(", ")}, which it now requires: no change to its code can bring it in.`)
130
+ .map(({ startOver }) => startOver.old === true
131
+ ? `${startOver.codebases.join(", ")} was built when functional specs were numbered, before they had func-ids: nothing more can be built on it.`
132
+ : `${startOver.codebases.join(", ")} was built without ${startOver.requires.join(", ")}, which it now requires: no change to its code can bring it in.`)
134
133
  .join("\n");
135
134
  // No terminal and no --start-over: no one to ask, so the run stops here rather than build nothing and say it is done.
136
135
  if (options.startOver !== true && process.stdin.isTTY !== true) {
@@ -187,11 +186,13 @@ export function moduleStates(reports) {
187
186
  ? "start_over_needed"
188
187
  : report.why !== undefined
189
188
  ? "halted"
190
- : report.code === null && (report.conformance === null || report.conformance === undefined) && report.scheduled.length === 0
189
+ : report.code === null && (report.conformance === null || report.conformance === undefined)
191
190
  ? "complete"
192
191
  : "incomplete";
193
192
  const why = state === "start_over_needed"
194
- ? `built without ${report.startOver.requires.join(", ")}, which it now requires: run with --start-over`
193
+ ? report.startOver.old === true
194
+ ? "built when functional specs were numbered, before they had func-ids: run with --start-over to regenerate its chain from scratch"
195
+ : `built without ${report.startOver.requires.join(", ")}, which it now requires: run with --start-over`
195
196
  : state === "incomplete"
196
197
  ? report.code !== null && report.code !== undefined
197
198
  ? "its code is not regenerated from the spec as it is"
@@ -230,10 +231,6 @@ function exportCode(plainFile, dir) {
230
231
  return { exported: false, dir, why: error instanceof Error ? error.message : String(error) };
231
232
  }
232
233
  }
233
- /** `conformance-tests:` as the report says it - not set is false. */
234
- function conformanceLabel(setting) {
235
- return setting === true ? "all" : setting === "chosen" || setting === "module" ? setting : "false";
236
- }
237
234
  /** The models of the three roles as this run uses them: the coding model given, the others as they fall back - each with the level it thinks at. */
238
235
  function reportModels(project, model) {
239
236
  const coding = splitModelSpec(model);
@@ -252,11 +249,11 @@ function reportModels(project, model) {
252
249
  }
253
250
  /**
254
251
  * The options every command that regenerates takes for a headless run: `--report` and `--build-dir`, and the config's
255
- * own keys (configKeyOptions) - `--harness-model`, `--coding-model` (`--model`), `--judge-model`, `--review`, `--mutation-testing` and, where
256
- * `conformance` is true, `--conformance-tests` - read as if config.turri.yaml had them. Exits on a value it cannot read.
252
+ * own keys (configKeyOptions) - `--harness-model`, `--coding-model` (`--model`), `--judge-model`, `--review` and
253
+ * `--mutation-testing` - read as if config.turri.yaml had them. Exits on a value it cannot read.
257
254
  */
258
- export function headlessOptions(options, conformance) {
259
- configKeyOptions(options, { conformance, review: true });
255
+ export function headlessOptions(options) {
256
+ configKeyOptions(options, { review: true });
260
257
  const report = optionValue(options, "report");
261
258
  const buildDir = optionValue(options, "build-dir");
262
259
  return { ...(report !== undefined ? { report } : {}), ...(buildDir !== undefined ? { buildDir } : {}) };
@@ -1,9 +1,11 @@
1
1
  import { existsSync } from "node:fs";
2
+ import { refuseTestPlainFile } from "./test-plain-files.js";
2
3
  import { basename, relative, resolve } from "node:path";
3
4
  import { defineTool } from "@earendil-works/pi-coding-agent";
4
5
  import { Type } from "typebox";
6
+ import { advisingNote } from "./advisor-chat.js";
5
7
  import { CONFIG_FILE, readProjectConfig, stackConfigFile, stackForModule, stackNamed, stacksOf, withStack } from "./config.js";
6
- import { checkPlainFile } from "./plain/check.js";
8
+ import { checkPlainFile, formatCheckResults } from "./plain/check.js";
7
9
  import { describeDrift, describeModules } from "./regenerate/loop.js";
8
10
  import { pulseName } from "./regenerate/jobs.js";
9
11
  import { stopDeployed } from "./regenerate/release.js";
@@ -145,7 +147,9 @@ function plainFileOf(cwd, given, what) {
145
147
  const named = located.plain_file ?? readProjectConfig(located.base).module;
146
148
  if (named === undefined)
147
149
  throw new Error(`Name the .plain file to ${what}, or set module: in ${CONFIG_FILE}.`);
148
- return resolve(located.base, named);
150
+ const plainFile = resolve(located.base, named);
151
+ refuseTestPlainFile(plainFile);
152
+ return plainFile;
149
153
  }
150
154
  /** Whether a /regenerate's words ask for the module from scratch: its code deleted, and all of it regenerated again. */
151
155
  export function asksFromScratch(words) {
@@ -165,14 +169,14 @@ export function regenerateTool(options) {
165
169
  "requires chain first. Call it after any change to the spec, by any means: the file tools, a skill, the user in an " +
166
170
  "editor. It takes no diff: what is left to regenerate - from what each module's code was regenerated from to its spec now - is " +
167
171
  "worked out and planned into pulses, and a pulse running that regenerates something the spec no longer wants is stopped and " +
168
- "rolled back. Nothing to do where the code is regenerated from the spec as it is. With functionalities named, those are " +
172
+ "rolled back. Nothing to do where the code is regenerated from the spec as it is. With funcIds named - each as module:func-id - those are " +
169
173
  "regenerated though their spec did not change - their code taken out and built anew, better made, never left as it was - " +
170
174
  "planned into pulses as any change is, the ones next to each other together; their conformance tests stay as they are.",
171
175
  promptSnippet: "regenerate a module's code from its spec as it is now - after any change to the spec",
172
176
  promptGuidelines: [
173
177
  "Call regenerate after editing a .plain file with the file tools - or when the user says they changed one - instead of working out a diff: turri compares what is regenerated with the spec now.",
174
178
  "regenerate returns at once with what runs and what is planned. Report it, keep talking, and use pulses when the user asks; the pulses report when they finish.",
175
- "When the user wants a functionality to come out differently, change its spec - the spec is what persists - then regenerate. When they want ones already built regenerated as their spec stands - better made, on another model, after many patches - name them in functionalities, all in one call.",
179
+ "When the user wants a functionality to come out differently, change its spec - the spec is what persists - then regenerate. When they want ones already built regenerated as their spec stands - better made, on another model, after many patches - name them in funcIds, each as module:func-id, all in one call.",
176
180
  "A change to a file the spec links - an API surface, a mapping document - is a change to the spec: regenerate sees it by itself, and regenerates every functional spec that uses what links it.",
177
181
  "Never stop pulses and start them again yourself to follow a spec change: regenerate does it.",
178
182
  "Set from_scratch only when the user asks for the module to be regenerated from scratch - started over, its code deleted - in this conversation, or agrees to it when a module's status says it has to start over: never to fix a pulse that did not land.",
@@ -183,8 +187,8 @@ export function regenerateTool(options) {
183
187
  plain_file: Type.Optional(Type.String({ description: "Path to the .plain file. Omitted: the module the project's config.turri.yaml names" })),
184
188
  working_dir: WORKING_DIR_PARAM,
185
189
  guidance: GUIDANCE_PARAM,
186
- functionalities: Type.Optional(Type.Array(Type.String(), {
187
- description: "Functional specs already built, by id, to regenerate though their spec did not change. Omitted: what the spec now differs from the code in",
190
+ funcIds: Type.Optional(Type.Array(Type.String(), {
191
+ description: "Functional specs already built, each as module:func-id (todo:add-task), to regenerate though their spec did not change. Omitted: what the spec now differs from the code in",
188
192
  })),
189
193
  from_scratch: Type.Optional(Type.Union([Type.Literal("module"), Type.Literal("chain")], {
190
194
  description: "Only when the user asks for it: the module's code and conformance tests deleted - committed first, so its lineage keeps them - and the whole module regenerated from its spec. " +
@@ -198,11 +202,12 @@ export function regenerateTool(options) {
198
202
  const plainFile = plainFileOf(options.cwd, given, "regenerate");
199
203
  return withCallStack(plainFile, given.stack, async () => {
200
204
  const check = checkPlainFile(plainFile);
205
+ // The parser's message as it gives it, a test plain file out of place, and turri's next step for the old syntax.
201
206
  if (!check.ok)
202
- throw new Error(`${relative(options.cwd, plainFile) || plainFile} does not parse, so nothing can be regenerated from it yet: ${check.error}`);
203
- const again = given.functionalities ?? [];
207
+ throw new Error(`${relative(options.cwd, plainFile) || plainFile} does not parse, so nothing can be regenerated from it yet:\n${formatCheckResults([check])}`);
208
+ const again = given.funcIds ?? [];
204
209
  if (again.length > 0 && given.from_scratch !== undefined)
205
- throw new Error("Name functionalities or set from_scratch, not both: from scratch regenerates every one of them.");
210
+ throw new Error("Name funcIds or set from_scratch, not both: from scratch regenerates every one of them.");
206
211
  // Named functionalities are work on functionalities, as engine chooses a model for it; the rest, spec changes.
207
212
  const work = again.length > 0 ? "functionalities" : "spec_changes";
208
213
  const model = options.model !== undefined ? options.model(ctx, plainFile, given.model, work) : given.model;
@@ -213,7 +218,10 @@ export function regenerateTool(options) {
213
218
  : again.length > 0
214
219
  ? await loop.regenerateAgain(plainFile, again, request)
215
220
  : await loop.bringUp(plainFile, request);
216
- return loopReply(loop, statuses, options.wait !== undefined ? options.wait(ctx, given.wait) : given.wait === true);
221
+ const reply = await loopReply(loop, statuses, options.wait !== undefined ? options.wait(ctx, given.wait) : given.wait === true);
222
+ // The functional specs the test advisor took up, for the agent to say so rather than offer their scenarios.
223
+ const advising = advisingNote(loop, plainFile);
224
+ return advising === "" ? reply : { ...reply, content: [...reply.content, { type: "text", text: advising }] };
217
225
  });
218
226
  },
219
227
  });
@@ -59,7 +59,12 @@ export declare function piModelCatalogue(): Promise<ModelCatalogue>;
59
59
  export declare function closestModels(id: string, known: string[]): string[];
60
60
  /** pi's own model runtime, with pi's credentials - auth.json, models.json and the environment - as turri's agents have them. */
61
61
  export declare function piModelAccess(): Promise<ModelAccess>;
62
- /** What to do about a provider's error, from what it says. */
63
- export declare function fixFor(provider: string, problem: string): string;
62
+ /**
63
+ * What to do about a provider's error, from what it says. `region`: for Bedrock, the region it is called in and where
64
+ * that comes from, as a line (bedrock-region.ts `describeBedrockRegion`) - turri sets it, so a person is not told to.
65
+ */
66
+ export declare function fixFor(provider: string, problem: string, region?: string): string;
64
67
  /** Each model the config uses, tried: answered, or what is wrong and what to do. One request a model. */
65
- export declare function checkModels(models: UsedModel[], access: ModelAccess): Promise<ModelVerdict[]>;
68
+ export declare function checkModels(models: UsedModel[], access: ModelAccess, options?: {
69
+ bedrockRegion?: string;
70
+ }): Promise<ModelVerdict[]>;
@@ -84,23 +84,28 @@ function signIn(provider) {
84
84
  anthropic: "ANTHROPIC_API_KEY, or /login in turri",
85
85
  openai: "OPENAI_API_KEY, or /login in turri",
86
86
  google: "GEMINI_API_KEY",
87
- "amazon-bedrock": "AWS_PROFILE (aws configure, or aws sso login) or AWS_BEARER_TOKEN_BEDROCK, and AWS_REGION",
87
+ "amazon-bedrock": "AWS_PROFILE (aws configure, or aws sso login) or AWS_BEARER_TOKEN_BEDROCK",
88
88
  };
89
89
  return keys[provider] ?? `the key pi reads for ${provider}, or /login in turri - https://pi.dev lists them`;
90
90
  }
91
- /** What to do about a provider's error, from what it says. */
92
- export function fixFor(provider, problem) {
91
+ /**
92
+ * What to do about a provider's error, from what it says. `region`: for Bedrock, the region it is called in and where
93
+ * that comes from, as a line (bedrock-region.ts `describeBedrockRegion`) - turri sets it, so a person is not told to.
94
+ */
95
+ export function fixFor(provider, problem, region) {
93
96
  const text = problem.toLowerCase();
97
+ const where = region !== undefined ? ` - ${region}` : " - the region pi calls Bedrock in, which AWS_REGION, set, chooses";
94
98
  if (/access (to|denied).*model|don't have access|not authorized to (invoke|access)|model access|accessdenied/.test(text) && provider === "amazon-bedrock") {
95
- return "ask for access to this model in the Bedrock console, under model access - for the region in AWS_REGION";
99
+ return `ask for access to this model in the Bedrock console, under model access, for its region${where}`;
96
100
  }
97
101
  if (/provider is not configured|no credentials|not logged in/.test(text))
98
102
  return `sign in to ${provider}: ${signIn(provider)}`;
99
103
  if (/401|403|unauthori[sz]ed|forbidden|invalid.*(api key|x-api-key|token|credential)|credential|security token|could not load|unable to locate|expired|no api key|missing api key|authentication/.test(text)) {
100
104
  return `check the credentials pi reads for ${provider}: ${signIn(provider)}`;
101
105
  }
102
- if (/region|endpoint|enotfound|getaddrinfo/.test(text))
103
- return provider === "amazon-bedrock" ? "check AWS_REGION: the model has to be served there" : "check the network, and the provider's endpoint";
106
+ if (/region|endpoint|enotfound|getaddrinfo/.test(text)) {
107
+ return provider === "amazon-bedrock" ? `the model has to be served in the region it is called in${where}` : "check the network, and the provider's endpoint";
108
+ }
104
109
  if (/not found|404|does not exist|unknown model|invalid model/.test(text))
105
110
  return "the provider does not serve this model id: change it in config.turri.yaml";
106
111
  if (/429|rate|quota|overloaded|capacity/.test(text))
@@ -117,7 +122,7 @@ function commonPrefix(a, b) {
117
122
  return at;
118
123
  }
119
124
  /** Each model the config uses, tried: answered, or what is wrong and what to do. One request a model. */
120
- export async function checkModels(models, access) {
125
+ export async function checkModels(models, access, options = {}) {
121
126
  return Promise.all(models.map(async ({ spec, usedBy }) => {
122
127
  const slash = spec.indexOf("/");
123
128
  if (slash === -1)
@@ -136,6 +141,6 @@ export async function checkModels(models, access) {
136
141
  if (problem === undefined)
137
142
  return { ok: true, spec, usedBy, ms: Date.now() - started };
138
143
  const said = problem.split("\n").find((line) => line.trim() !== "")?.trim().slice(0, 300) ?? problem;
139
- return { ok: false, spec, usedBy, problem: said, fix: fixFor(provider, problem) };
144
+ return { ok: false, spec, usedBy, problem: said, fix: fixFor(provider, problem, provider === "amazon-bedrock" ? options.bedrockRegion : undefined) };
140
145
  }));
141
146
  }
@@ -11,16 +11,20 @@ export const NEW_PROJECT = [
11
11
  " install, and that turri doctor checks it. Read the stack's template, its language's and its kind's common first.",
12
12
  "2. One module - a .plain file in the project's root, named after what it is (server.plain) - importing the stack and",
13
13
  " writing only what the module needs beyond it. Two side by side, each on its stack, only when it needs two stacks.",
14
- "3. config.turri.yaml: module: the new file, and conformance-tests: false unless they asked for conformance tests.",
15
- " What a config there already settles - a stack, a script, conformance - is kept, not chosen again.",
16
- "4. The unit testing script: keep one turri already finds; otherwise run cultivate with implement-unit-testing-script",
17
- " for the stack's language. Conformance tests asked for: their ***test reqs*** - the stack's conformance template -",
18
- " and cultivate with implement-conformance-testing-script.",
14
+ "3. config.turri.yaml: module: the new file. What a config there already settles - a stack, a script - is kept, not",
15
+ " chosen again.",
16
+ "4. The unit testing script: keep one turri already finds; otherwise start cultivate with implement-unit-testing-script,",
17
+ " plainFile the new module. It runs in the background: go straight on - the module's pulses wait for the script.",
18
+ " Tests only if the person asked for them: turn conformance tests on first (turn_on_conformance_tests), then in this",
19
+ " order: the ***test reqs*** - the stack's conformance template - then cultivate with",
20
+ " implement-conformance-testing-script, in the background too, then the scenarios.",
19
21
  "5. The first functional spec: the smallest thing that runs and shows something - the server answers GET / with the",
20
- " app's name, the CLI prints its usage. validate, fix what it finds, and regenerate at once.",
22
+ " app's name, the CLI prints its usage - with no test plain file, unless they asked for tests. parse, fix",
23
+ " what it finds, and regenerate at once.",
21
24
  "6. While it regenerates: the main thing they described, on its happy path, and at most one more - each a functional",
22
- " spec, with its concepts - then validate and regenerate again. Only what they described: they named only the kind of",
23
- " software, write none, and invite them to say what it does. No validation or edge cases yet.",
25
+ " spec, with its concepts, and a test plain file only if they asked for tests - then parse and regenerate",
26
+ " again. Only what they described: they named only the kind of software, write none, and invite them to say what",
27
+ " it does. No validation or edge cases yet.",
24
28
  "Then hand over in a few lines: what you chose, what is regenerating, and the next slices you would write - they say",
25
29
  "go on, or what to change, and every change after is ***plain you write and regenerate.",
26
30
  "</new-project>",
@@ -1,9 +1,13 @@
1
1
  import { checkPlainTargets, formatCheckResults } from "./plain/check.js";
2
2
  import { c } from "./module-report.js";
3
- const USAGE = `usage: turri validate <file.plain | directory> [options]
3
+ const USAGE = `usage: turri parse <file.plain | directory> [options]
4
4
 
5
- Validates a ***plain specification statically: parses it, resolves its imports and
6
- required modules, and reports every error without rendering anything.
5
+ Parses ***plain - its syntax and structure, never its code or tests: each module with its test plain files,
6
+ its imports and required modules, every error reported without regenerating anything - and every test plain file
7
+ outside its module's folder. A functional spec with no test plain file is not tested. A module whose
8
+ functional specs have scenarios but which has no test reqs, or no conformance testing script for its
9
+ stack, is a warning: its conformance tests are not written until it has them. A test plain file or
10
+ test folder is refused: name its module.
7
11
 
8
12
  --template-dir <dir> extra template directory (repeatable)
9
13
  --json machine-readable output
@@ -38,10 +42,12 @@ if (json) {
38
42
  else {
39
43
  const output = formatCheckResults(results)
40
44
  .split("\n")
41
- .map((line) => line.startsWith("PASS") ? c.success(line) : line.startsWith("FAIL") ? c.error(line) : c.dim(line))
45
+ .map((line) => line.startsWith("PASS") ? c.success(line) : line.startsWith("FAIL") ? c.error(line) : line.startsWith("WARN") ? c.warning(line) : c.dim(line))
42
46
  .join("\n");
43
47
  console.log(output);
44
48
  const failed = results.filter((r) => !r.ok).length;
45
- console.log(`\n${failed === 0 ? c.success(c.bold("all specs valid")) : c.error(c.bold(`${failed} of ${results.length} failed`))}`);
49
+ const warned = results.reduce((count, r) => count + (r.warnings?.length ?? 0), 0);
50
+ const warnings = warned > 0 ? c.warning(` - ${warned} warning${warned === 1 ? "" : "s"}`) : "";
51
+ console.log(`\n${failed === 0 ? c.success(c.bold("all specs valid")) + warnings : c.error(c.bold(`${failed} of ${results.length} failed`))}`);
46
52
  }
47
53
  process.exit(results.every((r) => r.ok) ? 0 : 1);
@@ -0,0 +1,9 @@
1
+ import { Type } from "typebox";
2
+ /** parse, for the conversation and for the skills that write specs alike: it checks the ***plain, never the code. */
3
+ export declare function checkPlainTool(cwd: string): import("@earendil-works/pi-coding-agent").ToolDefinition<Type.TObject<{
4
+ target: Type.TString;
5
+ template_dir: Type.TOptional<Type.TArray<Type.TString>>;
6
+ }>, {
7
+ failed: number;
8
+ checked: number;
9
+ }, any> & import("@earendil-works/pi-coding-agent").ToolDefinition<any, any, any>;