@turri-dev/cli 0.1.6 → 0.1.7-next.eb57c34

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 (140) hide show
  1. package/dist/ask.d.ts +83 -0
  2. package/dist/brand.d.ts +13 -0
  3. package/dist/btw.d.ts +67 -0
  4. package/dist/chat-models.d.ts +22 -0
  5. package/dist/chat-skills.d.ts +17 -0
  6. package/dist/cli-options.d.ts +25 -0
  7. package/dist/cli.d.ts +9 -0
  8. package/dist/config-command.d.ts +26 -0
  9. package/dist/config-edit.d.ts +69 -0
  10. package/dist/config.d.ts +321 -0
  11. package/dist/conform-cli.d.ts +1 -0
  12. package/dist/cultivate-cli.d.ts +1 -0
  13. package/dist/drift-cli.d.ts +1 -0
  14. package/dist/environment.d.ts +39 -0
  15. package/dist/extension.d.ts +147 -0
  16. package/dist/extension.js +36 -10
  17. package/dist/genome.d.ts +62 -0
  18. package/dist/graph.d.ts +52 -0
  19. package/dist/host.d.ts +21 -0
  20. package/dist/host.js +25 -0
  21. package/dist/identity.d.ts +2 -0
  22. package/dist/init-config.d.ts +106 -0
  23. package/dist/jellyfish.d.ts +25 -0
  24. package/dist/judge.d.ts +25 -0
  25. package/dist/lineage-cli.d.ts +1 -0
  26. package/dist/loop-cli.d.ts +34 -0
  27. package/dist/loop-tools.d.ts +80 -0
  28. package/dist/mature-tools.d.ts +53 -0
  29. package/dist/model-check.d.ts +42 -0
  30. package/dist/module-report.d.ts +9 -0
  31. package/dist/pi-launch.d.ts +1 -0
  32. package/dist/pi.d.ts +246 -0
  33. package/dist/picture-row.d.ts +10 -0
  34. package/dist/plain/check.d.ts +23 -0
  35. package/dist/plain/concepts.d.ts +11 -0
  36. package/dist/plain/errors.d.ts +22 -0
  37. package/dist/plain/files.d.ts +12 -0
  38. package/dist/plain/highlight.d.ts +42 -0
  39. package/dist/plain/index.d.ts +15 -0
  40. package/dist/plain/liquid.d.ts +22 -0
  41. package/dist/plain/markdown.d.ts +74 -0
  42. package/dist/plain/parser.d.ts +87 -0
  43. package/dist/plain/spec.d.ts +90 -0
  44. package/dist/plain/stdlib.d.ts +14 -0
  45. package/dist/question-dialog.d.ts +51 -0
  46. package/dist/questioning.d.ts +55 -0
  47. package/dist/quiet.d.ts +34 -0
  48. package/dist/regenerate/activity.d.ts +207 -0
  49. package/dist/regenerate/agent-env.d.ts +44 -0
  50. package/dist/regenerate/agent-shell.d.ts +49 -0
  51. package/dist/regenerate/arbiter.d.ts +68 -0
  52. package/dist/regenerate/audit.d.ts +69 -0
  53. package/dist/regenerate/cases.d.ts +9 -0
  54. package/dist/regenerate/change-view.d.ts +18 -0
  55. package/dist/regenerate/choose-conformance.d.ts +32 -0
  56. package/dist/regenerate/chunk-plan.d.ts +164 -0
  57. package/dist/regenerate/codebase.d.ts +21 -0
  58. package/dist/regenerate/command-check.d.ts +40 -0
  59. package/dist/regenerate/compaction.d.ts +13 -0
  60. package/dist/regenerate/confine.d.ts +62 -0
  61. package/dist/regenerate/conform.d.ts +63 -0
  62. package/dist/regenerate/conformance-agents.d.ts +405 -0
  63. package/dist/regenerate/conformance.d.ts +189 -0
  64. package/dist/regenerate/conversation-writes.d.ts +10 -0
  65. package/dist/regenerate/credentials.d.ts +18 -0
  66. package/dist/regenerate/debug-log.d.ts +54 -0
  67. package/dist/regenerate/debugger.d.ts +31 -0
  68. package/dist/regenerate/delivery.d.ts +6 -0
  69. package/dist/regenerate/diagnose-job.d.ts +2 -0
  70. package/dist/regenerate/diagnose.d.ts +46 -0
  71. package/dist/regenerate/diff-view.d.ts +61 -0
  72. package/dist/regenerate/drift.d.ts +84 -0
  73. package/dist/regenerate/engine.d.ts +52 -0
  74. package/dist/regenerate/environment-error.d.ts +136 -0
  75. package/dist/regenerate/job-log.d.ts +28 -0
  76. package/dist/regenerate/job-panel.d.ts +122 -0
  77. package/dist/regenerate/jobs-tree.d.ts +71 -0
  78. package/dist/regenerate/jobs.d.ts +186 -0
  79. package/dist/regenerate/lanes.d.ts +16 -0
  80. package/dist/regenerate/lineage.d.ts +95 -0
  81. package/dist/regenerate/list-arguments.d.ts +21 -0
  82. package/dist/regenerate/loop.d.ts +392 -0
  83. package/dist/regenerate/masking.d.ts +19 -0
  84. package/dist/regenerate/notes.d.ts +159 -0
  85. package/dist/regenerate/os-sandbox.d.ts +89 -0
  86. package/dist/regenerate/panel-dock.d.ts +23 -0
  87. package/dist/regenerate/paths.d.ts +92 -0
  88. package/dist/regenerate/plain-palette.d.ts +34 -0
  89. package/dist/regenerate/prompt.d.ts +192 -0
  90. package/dist/regenerate/records.d.ts +88 -0
  91. package/dist/regenerate/regenerate.d.ts +108 -0
  92. package/dist/regenerate/regression.d.ts +58 -0
  93. package/dist/regenerate/release.d.ts +123 -0
  94. package/dist/regenerate/requires.d.ts +96 -0
  95. package/dist/regenerate/resources.d.ts +53 -0
  96. package/dist/regenerate/self-check.d.ts +29 -0
  97. package/dist/regenerate/services.d.ts +28 -0
  98. package/dist/regenerate/shell.d.ts +91 -0
  99. package/dist/regenerate/side-panel.d.ts +57 -0
  100. package/dist/regenerate/spec-map-view.d.ts +139 -0
  101. package/dist/regenerate/spec-map.d.ts +138 -0
  102. package/dist/regenerate/spec-steps.d.ts +137 -0
  103. package/dist/regenerate/state.d.ts +20 -0
  104. package/dist/regenerate/steer.d.ts +34 -0
  105. package/dist/regenerate/steering.d.ts +56 -0
  106. package/dist/regenerate/stop-report.d.ts +43 -0
  107. package/dist/regenerate/submit-work.d.ts +42 -0
  108. package/dist/regenerate/test-kinds.d.ts +42 -0
  109. package/dist/regenerate/test-scripts.d.ts +221 -0
  110. package/dist/regenerate/testing.d.ts +141 -0
  111. package/dist/regenerate/transcript-view.d.ts +21 -0
  112. package/dist/regenerate/unit-gate.d.ts +27 -0
  113. package/dist/regenerate/unit-review.d.ts +152 -0
  114. package/dist/regenerate/web.d.ts +61 -0
  115. package/dist/regenerate/workspace.d.ts +12 -0
  116. package/dist/regenerate-cli.d.ts +1 -0
  117. package/dist/release-agent.d.ts +26 -0
  118. package/dist/release-cli.d.ts +1 -0
  119. package/dist/release-tools.d.ts +38 -0
  120. package/dist/resume-line.d.ts +2 -0
  121. package/dist/review/live.d.ts +72 -0
  122. package/dist/rules.d.ts +20 -0
  123. package/dist/run-report.d.ts +88 -0
  124. package/dist/screen-padding.d.ts +28 -0
  125. package/dist/self-invocation.d.ts +25 -0
  126. package/dist/setup.d.ts +43 -0
  127. package/dist/skill-progress.d.ts +46 -0
  128. package/dist/skill.d.ts +98 -0
  129. package/dist/skills.d.ts +30 -0
  130. package/dist/spec-diff-cli.d.ts +1 -0
  131. package/dist/spec-tools.d.ts +36 -0
  132. package/dist/stack.d.ts +58 -0
  133. package/dist/stacks.d.ts +12 -0
  134. package/dist/stdlib.d.ts +27 -0
  135. package/dist/terminal-images.d.ts +2 -0
  136. package/dist/tools.d.ts +101 -0
  137. package/dist/validate-cli.d.ts +1 -0
  138. package/docs/README.md +2 -0
  139. package/docs/hosting.md +52 -0
  140. package/package.json +8 -1
@@ -0,0 +1,321 @@
1
+ /**
2
+ * The project's `config.turri.yaml`, at its root - the folder that holds `plain_modules` - or, before it had one, its `config.yaml`.
3
+ *
4
+ * One file for what a project says about how it is built:
5
+ *
6
+ * ```yaml
7
+ * module: prism_cli.plain # what `turri regenerate` builds by default
8
+ * harness-model: # the model you chat with in `turri`
9
+ * model: anthropic/claude-opus-5-5
10
+ * thinking: high
11
+ * coding-model: # the rendering agents (default: harness-model)
12
+ * model: google/gemini-3.1-flash
13
+ * thinking: medium
14
+ * judge-model: # the judge and the test review (default: coding-model)
15
+ * model: google/gemini-3.1-pro
16
+ * conformance-tests: chosen # all, chosen (those with acceptance tests, and those the chooser picks after each pulse), module (the chooser picks, and plans, once the module is regenerated) or false (default: false)
17
+ * conformance-budget: 30% # under chosen or module: how many functional specs a module may have chosen, beside those with acceptance tests - a count or a share (default: no limit)
18
+ * review: false # review the code and its tests once they pass (default: true)
19
+ * mutation-testing: false # give the coding agent and the reviewer can_it_fail (default: true)
20
+ * allow-to-stop-jobs: false # stop a running job the spec moved on from (default: true)
21
+ * release-after-pulse: true # release what each pulse lands, as its last stage, before the next pulse (default: false)
22
+ * rules-off: [integrations] # ***plain rules to leave out of the prompts, by name (default: none)
23
+ * unittests-script: test_scripts/run_unittests.sh # the testing scripts - see test-scripts.ts
24
+ * conformance-tests-script: test_scripts/run_conformance_tests.sh
25
+ * prepare-environment-script: test_scripts/prepare_environment.sh # optional: run before the conformance testing script - see test-scripts.ts
26
+ * testing-scripts-run-on: in-place # what the testing scripts run on: in-place (default), or copy - see test-scripts.ts
27
+ * turn-limit: 150 # seconds each turn of an agent a render starts has (default: 120; 0 for none)
28
+ * models: # each agent role's own model, thinking and turn limit - see AGENT_ROLES
29
+ * default: amazon-bedrock/global.openai.gpt-6-luna # every role not named
30
+ * coding: { model: amazon-bedrock/eu.anthropic.claude-sonnet-5, thinking: high, turn-limit: 150 }
31
+ * explorer: google/gemini-3.1-flash-lite
32
+ * ```
33
+ *
34
+ * `models:` names a model, a thinking level and a turn limit per agent role (AGENT_ROLES), each falling back as
35
+ * roleModel says: a role it does not name takes `default`, and what an entry leaves out it takes from there too.
36
+ * `harness-model`, `coding-model` and `judge-model` are the same as `models: chat`, `coding` and `judge`, which win
37
+ * over them. The turn limit is the role's own, else `default`'s, else `turn-limit:`, else two minutes - the
38
+ * config is the only place it is set.
39
+ *
40
+ * Each model is a model reference and a thinking level (off, minimal, low, medium, high, xhigh, max),
41
+ * either of which may be left out; a bare string is the model alone. What a model leaves out it takes
42
+ * from the one it inherits: judge-model from coding-model, coding-model from harness-model. They are
43
+ * defaults: `--harness-model` / `--coding-model` / `--judge-model` on the command line win over them,
44
+ * and so does a model chosen during the conversation (pi's /model, engine). `model:` is read
45
+ * as `coding-model`, which it was before there were two.
46
+ *
47
+ * Keys are written with dashes; underscores are read the same (`conformance_tests`). Keys it
48
+ * does not know - another tool's `config.yaml`, read while it is the old file, may carry several - are ignored, and so is a value of
49
+ * the wrong type. Everything is optional, and what is given on the command line wins.
50
+ *
51
+ * A project whose parts do not share a stack - a Python backend and a React frontend beside each other - keeps a
52
+ * stack's own settings in `config.turri.<stack>.yaml` beside `config.turri.yaml`: `config.turri.backend.yaml` for the
53
+ * stack `backend` - its testing scripts, its toolchains. A stack is chosen, never worked out: `/stack` in the chat,
54
+ * `--stack <name>` (or `TURRI_STACK`) on the command line. With one chosen, everything turri builds from then on is
55
+ * built with `config.turri.yaml` and the stack's file over it, key by key - a model it names takes what it leaves out
56
+ * from the project's - and a pulse already running keeps the stack it started with. With none, `config.turri.yaml`
57
+ * alone. `module:` and `harness-model:` are the project's alone - there is one conversation - and are not read from a
58
+ * stack's file.
59
+ */
60
+ /** turri's settings for a project, in a file of its own: a `config.yaml` beside it may be another tool's. */
61
+ export declare const CONFIG_FILE = "config.turri.yaml";
62
+ /**
63
+ * Where turri's settings were before they had a file of their own. A project with no `config.turri*.yaml` yet is read
64
+ * from it - and a `config.<name>.yaml` beside it as the stack `<name>` - and turri never writes it: the first setting
65
+ * turri writes copies them to the new names (`adoptLegacyConfig`), and from then on the old files are not read.
66
+ */
67
+ export declare const LEGACY_CONFIG_FILE = "config.yaml";
68
+ /** A stack's config file's name: `config.turri.backend.yaml` for the stack `backend`. */
69
+ export declare function stackConfigFile(stack: string): string;
70
+ /** Whether a folder's settings are still in the old files: a config.yaml or a config.<stack>.yaml, and no config.turri*.yaml. */
71
+ export declare function usesLegacyConfig(root: string): boolean;
72
+ /** The file the project's settings are read from, by name: config.turri.yaml, or config.yaml while the folder has only the old files. */
73
+ export declare function projectConfigFile(root: string): string;
74
+ /** The file a stack's settings are read from, by name - the old name while the folder has only the old files. */
75
+ export declare function stackConfigFileIn(root: string, stack: string): string;
76
+ /** Whether a folder holds a project's settings: a config.turri.yaml, a stack's file, or the old files. */
77
+ export declare function hasProjectConfig(root: string): boolean;
78
+ /** The stacks a project folder has files for, by name. */
79
+ export declare function stacksOf(root: string): string[];
80
+ /**
81
+ * Before turri first writes a setting in a folder whose settings are still in the old files: config.yaml copied to
82
+ * config.turri.yaml, and each config.<stack>.yaml to config.turri.<stack>.yaml, so nothing is lost when the old files
83
+ * stop being read. The old files are left as they are. Gives back the files written, by name.
84
+ */
85
+ export declare function adoptLegacyConfig(root: string, options?: {
86
+ projectFile?: boolean;
87
+ }): string[];
88
+ /** The stack chosen now - `/stack`, `--stack`, `TURRI_STACK` - or, inside a pulse, the one it started with. */
89
+ export declare function chosenStack(): string | undefined;
90
+ /** Choose the stack everything is built with from now on - or none, the project's config alone. Kept in TURRI_STACK. */
91
+ export declare function chooseStack(stack: string | undefined): void;
92
+ /** Run `work` with `stack` as the one chosen, whatever is chosen meanwhile: a pulse keeps the stack it started with. */
93
+ export declare function withStack<T>(stack: string | undefined, work: () => T): T;
94
+ /** Whether a stack is pinned here - named by the call that asked for the work, or kept by the pulse it runs in. */
95
+ export declare function stackPinned(): boolean;
96
+ /**
97
+ * The stack a module is built with: the one pinned - named by the call, or kept by the pulse - else the stack named
98
+ * after the module, config.turri.backend.yaml for backend.plain, else the one chosen for the session; none, when none
99
+ * of them is.
100
+ */
101
+ export declare function stackForModule(plainFile: string): string | undefined;
102
+ /** The words that choose no stack: the project's config alone. */
103
+ export declare const NO_STACK: string[];
104
+ /**
105
+ * The stack `name` names for the project at `root` - undefined for none - refused when the project has no file for
106
+ * it, with the stacks it has.
107
+ */
108
+ export declare function stackNamed(root: string, name: string): string | undefined;
109
+ /** The stack a project folder is built with now: the one chosen, when the folder has a file for it. */
110
+ export declare function stackOf(root: string): string | undefined;
111
+ export declare const THINKING_LEVELS: readonly ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
112
+ export type ThinkingLevel = (typeof THINKING_LEVELS)[number];
113
+ /** A model, and how hard it thinks - either may be left to the one it inherits from. */
114
+ export interface ModelChoice {
115
+ /** "provider/model-id". */
116
+ model?: string;
117
+ thinking?: ThinkingLevel;
118
+ }
119
+ /**
120
+ * The agents turri starts, by what they do - each can be given its own model, thinking and turn limit (`models:`).
121
+ * The code fixer of a conformance run and the repair agent are coding agents; the unit-test and conformance arbiters
122
+ * are both `arbiter`.
123
+ */
124
+ export declare const AGENT_ROLES: readonly ["chat", "chooser", "planner", "diagnosis", "release", "coding", "conformance", "explorer", "reviewer", "test-reviewer", "judge", "arbiter", "debugger"];
125
+ export type AgentRole = (typeof AGENT_ROLES)[number];
126
+ /**
127
+ * How hard each role thinks when nothing names a level - not the config's `models:`, nor a `:level` on the model it is
128
+ * given. The agents that do the work - write the code and the tests, explore, release - think little: they work from a
129
+ * spec that leaves little to guess, and every turn of theirs is many. The ones that judge - review, weigh, plan, find a
130
+ * cause - think more: a wrong call there costs pulses. The conversation keeps its own level, pi's.
131
+ */
132
+ export declare const ROLE_THINKING: Partial<Record<AgentRole, ThinkingLevel>>;
133
+ /** A role's model and thinking, and how long each of its turns may take, in seconds - each may be left out. */
134
+ export interface RoleChoice extends ModelChoice {
135
+ turnLimit?: number;
136
+ }
137
+ /**
138
+ * A model choice as the one string every agent is given, in pi's own form: "provider/id", with
139
+ * ":level" when it names how hard to think - ":level" alone for the default model thinking so.
140
+ */
141
+ export declare function modelSpec(choice: ModelChoice | undefined): string | undefined;
142
+ /** A model spec back into its model and thinking level - "provider/id:high", "provider/id", ":high". */
143
+ export declare function splitModelSpec(spec: string | undefined): ModelChoice;
144
+ /**
145
+ * A model spec's model alone, its thinking left behind: what a judging role falls back on when it runs on the coding
146
+ * model - the model, not how hard the coding agent was told to think. Its role's level (ROLE_THINKING) applies instead.
147
+ */
148
+ export declare function modelOnly(spec: string | undefined): ModelChoice;
149
+ /** A choice with what it leaves out taken from the one it inherits from - or that one, when it is not given at all. */
150
+ export declare function inheritModel(own: ModelChoice | undefined, from: ModelChoice | undefined): ModelChoice | undefined;
151
+ /**
152
+ * The three models as they hold: the judge inherits from the coding model, the coding model from the harness model -
153
+ * `models:` over them: `chat` over harness-model, `coding` over coding-model, `judge` over judge-model, and `default`
154
+ * between the harness model and the rest.
155
+ */
156
+ export declare function projectModels(config: Pick<ProjectConfig, "harnessModel" | "codingModel" | "judgeModel" | "models">): {
157
+ harness?: ModelChoice;
158
+ coding?: ModelChoice;
159
+ judge?: ModelChoice;
160
+ };
161
+ /**
162
+ * What the config itself says a role runs on - its own entry, the legacy key that names it (judge-model for the judge
163
+ * and the test review), then `default` (the chat's side: the chat's model instead) - or nothing when it says nothing
164
+ * of it, and the role runs on what it is given: the job's model, which the command line and the conversation choose.
165
+ * The coding role always runs on what it is given: its config is already in it (projectModels).
166
+ */
167
+ export declare function roleModel(config: Pick<ProjectConfig, "harnessModel" | "judgeModel" | "models">, role: AgentRole): ModelChoice | undefined;
168
+ /** How long each turn of a role's agents may take, in milliseconds: its own, `default`'s, `turn-limit:`'s - or undefined, the harness's default. */
169
+ export declare function roleTurnLimitMs(config: Pick<ProjectConfig, "models" | "turnLimit">, role: AgentRole | undefined): number | undefined;
170
+ export interface ProjectConfig {
171
+ /** The .plain file the module command builds when it is given none. */
172
+ module?: string;
173
+ /** The model `turri` starts with: the one the person talks to. */
174
+ harnessModel?: ModelChoice;
175
+ /** The model the rendering agents run on - every render, conformance, repair and spec change. Unset: the harness model. */
176
+ codingModel?: ModelChoice;
177
+ /**
178
+ * The model of the agents that give a verdict in a conformance run - the judge, which says whether
179
+ * the code, the tests or the spec is wrong, and the test review. Unset: the coding model.
180
+ */
181
+ judgeModel?: ModelChoice;
182
+ /**
183
+ * Whether conformance tests are written and run, and for what. true (`all`): every functional spec the code is built
184
+ * from. "chosen": the ones with acceptance tests, and those the chooser picks from what each code pulse regenerated, as
185
+ * the pulse ends. "module": the same, but the chooser is asked once, when the whole module is regenerated - what to test,
186
+ * and in which pulses. Under both, those the person asks for with conform too, and a choice is kept once its tests land.
187
+ * false: none, unless the person asks. Unset: none, as with false.
188
+ */
189
+ conformanceTests?: boolean | "chosen" | "module";
190
+ /**
191
+ * Under `conformance-tests: chosen`: how many of a module's functional specs may be chosen for conformance tests, beside
192
+ * those with acceptance tests - a count, or a share of the module's functional specs (rounded up, at least one).
193
+ * Every choice for the module counts, whenever it was made; the person's own conform is not limited. Unset: no limit.
194
+ */
195
+ conformanceBudget?: {
196
+ count: number;
197
+ } | {
198
+ share: number;
199
+ };
200
+ /**
201
+ * Whether the code and its unit tests - and the conformance tests - are reviewed once they pass: two reviews at
202
+ * most, the second weighed by the arbiter. false: the work goes on once its tests pass. Unset: false.
203
+ */
204
+ review?: boolean;
205
+ /**
206
+ * Whether the coding agent and the reviewer have can_it_fail: a bug put in the code for one run of the unit tests,
207
+ * to see that a test catches it - on a copy, or in place and put back after. Unset: true.
208
+ */
209
+ mutationTesting?: boolean;
210
+ /**
211
+ * Whether turri stops a running job the spec has moved on from - a spec change, or a file it links edited - and
212
+ * regenerates the spec as it now is instead. false: the job runs to its end and lands what it was regenerating, and
213
+ * what changed since is regenerated on top of it by the next job: something lands as soon as it can, the software
214
+ * evolving job by job. The person's own halt stops a job either way. Unset: true.
215
+ */
216
+ allowToStopJobs?: boolean;
217
+ /**
218
+ * Whether every pulse that lands code releases it as its last stage, before the module's next pulse starts - an agent
219
+ * looking for the project's release guidance as the chat does, and doing what it says. Unset: false.
220
+ */
221
+ releaseAfterPulse?: boolean;
222
+ /** Path from the root to the unit testing script. */
223
+ unittestsScript?: string;
224
+ /** Path from the root to the conformance testing script. */
225
+ conformanceTestsScript?: string;
226
+ /** Run on the build before every conformance testing script run: `<script> <build folder>`. */
227
+ prepareEnvironmentScript?: string;
228
+ /**
229
+ * What the testing scripts run on, on this machine: the code in place (the default) - as for an integration whose
230
+ * scripts merge it into a host codebase - or a fresh copy of the code.
231
+ */
232
+ testingScriptsRunOn?: "copy" | "in-place";
233
+ /** How many jobs run at once in the chat - modules that share nothing build side by side up to it. Unset: from the machine's memory. */
234
+ parallelJobs?: number;
235
+ /**
236
+ * The ***plain authoring rules (genome/rules/<name>.md) left out of the conversation's prompt and every spec-writing
237
+ * agent's, by name: a rule a project's own skills or AGENTS.md replace. The project's alone.
238
+ */
239
+ rulesOff?: string[];
240
+ /**
241
+ * Tokens of conversation - what an agent has done, past its instructions - after which an agent that keeps its
242
+ * session through a job is compacted between turns. The project's alone. Unset: 100k.
243
+ */
244
+ compactAt?: number;
245
+ /** The most functional specs the planner puts in one pulse. Unset: 3. A stack's file wins. */
246
+ maxSpecsPerPulse?: number;
247
+ /** The most lines of spec - its functional specs and the definitions they bring - in one pulse; past it, split. Unset: 120. */
248
+ maxLinesPerPulse?: number;
249
+ /**
250
+ * What passes through to the agents' commands beside the scrubbed environment: every variable of the dotenv `files`
251
+ * (paths from the project root), and the `vars` named, from the environment turri was started in. A module's own are
252
+ * added to the project's.
253
+ */
254
+ agentEnv?: {
255
+ files: string[];
256
+ vars: string[];
257
+ };
258
+ /**
259
+ * Folders of this machine an agent's commands may read beside the system's and the toolchains turri finds itself: a
260
+ * toolchain installed somewhere unusual. A module's own are added to the project's.
261
+ */
262
+ toolchains?: string[];
263
+ /** Each agent role's own model, thinking and turn limit; `default` for every role not named. */
264
+ models?: Partial<Record<AgentRole | "default", RoleChoice>>;
265
+ /** Seconds each turn of an agent a render starts has, for every role without its own. Unset: 120. 0: none. */
266
+ turnLimit?: number;
267
+ }
268
+ /** The config keys, as written in the file. */
269
+ export declare const CONFIG_KEYS: {
270
+ readonly module: "module";
271
+ readonly harnessModel: "harness-model";
272
+ readonly codingModel: "coding-model";
273
+ readonly judgeModel: "judge-model";
274
+ readonly conformanceTests: "conformance-tests";
275
+ readonly conformanceBudget: "conformance-budget";
276
+ readonly review: "review";
277
+ readonly mutationTesting: "mutation-testing";
278
+ readonly allowToStopJobs: "allow-to-stop-jobs";
279
+ readonly releaseAfterPulse: "release-after-pulse";
280
+ readonly unittestsScript: "unittests-script";
281
+ readonly conformanceTestsScript: "conformance-tests-script";
282
+ readonly prepareEnvironmentScript: "prepare-environment-script";
283
+ readonly testingScriptsRunOn: "testing-scripts-run-on";
284
+ readonly parallelJobs: "parallel-jobs";
285
+ readonly rulesOff: "rules-off";
286
+ readonly compactAt: "compact-at";
287
+ readonly maxSpecsPerPulse: "max-specs-per-pulse";
288
+ readonly maxLinesPerPulse: "max-lines-per-pulse";
289
+ readonly agentEnv: "agent-env";
290
+ readonly toolchains: "toolchains";
291
+ readonly models: "models";
292
+ readonly turnLimit: "turn-limit";
293
+ };
294
+ /** Whether `conformance-tests:` turns the conformance tests on: all (true), chosen or module. Not set, they are off, as with false. */
295
+ export declare function conformanceSwitchedOn(setting: ProjectConfig["conformanceTests"]): boolean;
296
+ /** When the chooser decides which functional specs get conformance tests: after each code pulse, once the module is regenerated, or never - every one, or none. */
297
+ export declare function choosingWhen(setting: ProjectConfig["conformanceTests"]): "pulse" | "module" | undefined;
298
+ /** How many functional specs a module of `count` may have chosen under a budget: the count, or the share rounded up - at least one. */
299
+ export declare function budgetFor(budget: NonNullable<ProjectConfig["conformanceBudget"]>, count: number): number;
300
+ /**
301
+ * The project's config - with a stack's over it, when one is named - or nothing of it when there is no file or it does
302
+ * not parse.
303
+ */
304
+ export declare function readProjectConfig(root: string, stack?: string): ProjectConfig;
305
+ /**
306
+ * What a command's own options say of the config's keys - `turri regenerate --harness-model`, `--coding-model`,
307
+ * `--judge-model`, `--conformance-tests`, `--review`, `--mutation-testing` - read as if the file had them: each takes its key's value, and
308
+ * every key keeps its place in the order, so a run is the same as one with them in config.turri.yaml - a `models:`
309
+ * entry still winning over `harness-model`, `coding-model` or `judge-model`. A headless run needs no config file
310
+ * edited. Never set in the conversation.
311
+ */
312
+ type RunOverrides = Partial<Pick<ProjectConfig, "harnessModel" | "codingModel" | "judgeModel" | "conformanceTests" | "review" | "mutationTesting">>;
313
+ /** Read a command's options as the config's keys for the rest of this process. */
314
+ export declare function overrideConfig(overrides: RunOverrides): void;
315
+ /** `--conformance-tests <value>`: the config's own values - all (or true), chosen, module, false. Undefined for anything else. */
316
+ export declare function conformanceOption(value: string): ProjectConfig["conformanceTests"] | undefined;
317
+ /** `--review <value>`, `--mutation-testing <value>`: the config's own, true or false. Undefined for anything else. */
318
+ export declare function booleanOption(value: string): boolean | undefined;
319
+ /** The config a spec is built with: its project's, with its module's stack over it (`stackForModule`). */
320
+ export declare function configForSpec(plainFile: string): ProjectConfig;
321
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,39 @@
1
+ /**
2
+ * What turri needs from the machine it runs on, found out without changing anything.
3
+ *
4
+ * Kept free of heavy imports so the CLI can ask these questions before it has loaded pi - and so `turri doctor` can report a missing piece instead of crashing on it.
5
+ */
6
+ export declare const PACKAGE_ROOT: string;
7
+ export declare const AGENT_DIR: string;
8
+ /** The oldest Node that runs .ts files by itself, which is how turri runs. */
9
+ export declare const MIN_NODE: readonly [22, 19];
10
+ export declare function nodeVersionOk(version?: string): boolean;
11
+ /** Whether a command runs at all. */
12
+ export declare function runs(command: string, args?: string[]): boolean;
13
+ /** The global pi extension file that loads turri into every `pi`, if there is one. */
14
+ export declare function globalExtensionFile(): string | undefined;
15
+ export declare function extensionInstalledGlobally(): boolean;
16
+ export declare function themeFiles(): string[];
17
+ /**
18
+ * Are this project's themes already installed globally?
19
+ *
20
+ * Passing --theme as well would make pi load each of them twice and report a collision
21
+ * on startup - the global copy wins and the project copy is skipped, which is noise
22
+ * about a duplicate we created ourselves.
23
+ */
24
+ export declare function themesInstalledGlobally(): boolean;
25
+ /** Where `turri` on the PATH points, if it is on the PATH at all. */
26
+ export declare function turriOnPath(): string | undefined;
27
+ /** The version of the pi turri runs on, as Node finds it - undefined when it is not installed. */
28
+ export declare function piVersion(): string | undefined;
29
+ /**
30
+ * What `turri --version` prints: turri's version - from a clone, its commit too, as the banner says it - and pi's
31
+ * beside it. pi's own `--version` says only pi's, which read as turri's.
32
+ */
33
+ export declare function versionLine(identity?: ReturnType<typeof turriIdentity>, pi?: string | null): string;
34
+ /** turri's version, and the commit when it runs from a clone. */
35
+ export declare function turriIdentity(): {
36
+ version: string | null;
37
+ git_commit: string | null;
38
+ git_dirty: boolean | null;
39
+ };
@@ -0,0 +1,147 @@
1
+ import "./terminal-images.ts";
2
+ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
3
+ import { stackOnMachine } from "./stack.ts";
4
+ import { createAskUserTool } from "./tools.ts";
5
+ import { fromWorkingDir } from "./loop-tools.ts";
6
+ import { type WorkLoop } from "./regenerate/loop.ts";
7
+ import { type AskUser, type AskUserIO } from "./ask.ts";
8
+ import { type Job, type JobRegistry } from "./regenerate/jobs.ts";
9
+ /**
10
+ * Puts a skill step's question to the person, when there is one to ask.
11
+ *
12
+ * A skill runs inside one tool call, in its own session, so its dialogs are not
13
+ * conversation turns and nothing it asks would otherwise reach the transcript. Every
14
+ * exchange is therefore reported back, so the interview is visible while it happens and
15
+ * survives in the tool's result afterwards.
16
+ */
17
+ export declare function askUserVia(ctx: ExtensionContext, onExchange: (question: string, answer: string, said?: string) => void,
18
+ /** Given, "/btw ..." typed as an answer is a side question: answered with this, and the question asked again. */
19
+ aside?: (question: string) => Promise<string>,
20
+ /** A host's own dialogs (TurriHost.dialogs), the question put in them instead of the terminal's. */
21
+ dialogs?: AskUserIO): AskUser;
22
+ /**
23
+ * ask_user, for the conversation's agent too - the same dialog a skill's interview uses: options to
24
+ * pick from, several at once, "Something else…" to type instead, "Chat about this" to talk it over in
25
+ * the chat first - so a question with a handful of answers is picked, not typed.
26
+ */
27
+ export declare function chatAskUserTool(dialogs?: AskUserIO): ReturnType<typeof createAskUserTool>;
28
+ /**
29
+ * What a host that runs turri's conversation itself - a desktop app, an editor - is told and asked, where the terminal
30
+ * would draw: the conversation's jobs and its work loop as they are made and as they change, and the dialogs its
31
+ * questions are put to the person in. A host gives the extension `createTurriExtension(host)`; `turri` gives it none.
32
+ */
33
+ export interface TurriHost {
34
+ /** The conversation's job registry, as the extension makes it: every pulse the conversation starts is in it. */
35
+ onJobs?(jobs: JobRegistry): void;
36
+ /** The conversation's work loop, once work is first asked for: what each codebase has planned, and why. */
37
+ onWorkLoop?(loop: WorkLoop): void;
38
+ /** A job started, moved to another stage, did something, or ended. */
39
+ onJobChange?(job: Job): void;
40
+ /** What is planned changed with no job - a module being planned, or planned. */
41
+ onPlanChange?(): void;
42
+ /** The dialogs ask_user puts its questions in - the conversation's and a skill's interview - instead of the terminal's. */
43
+ dialogs?: AskUserIO;
44
+ }
45
+ /** turri's conversation as an extension of pi's, for a host that runs it itself: see TurriHost. */
46
+ export declare function createTurriExtension(host?: TurriHost): (pi: ExtensionAPI) => void;
47
+ declare const _default: (pi: ExtensionAPI) => void;
48
+ export default _default;
49
+ /**
50
+ * What a slash command showed the person: kept in the chat, where they read it, and put in the conversation as well -
51
+ * unseen there - so that when they speak of it next ("fix it"), the agent knows what they saw. A command runs outside
52
+ * the agent's turns, and a notification is gone in seconds and reaches no model.
53
+ */
54
+ export declare function shownAfterCommand(pi: ExtensionAPI, ctx: ExtensionContext, command: string, text: string, level: NonNullable<CommandOutcome["level"]>,
55
+ /** The command is in the chat already, above it (echoCommand): the text alone. */
56
+ echoed?: boolean): void;
57
+ export declare const COMMAND_OUTCOME = "turri_command_outcome";
58
+ interface CommandOutcome {
59
+ command: string;
60
+ /** None: the command itself, as the person typed it. */
61
+ text?: string;
62
+ level?: "info" | "warning" | "error";
63
+ echoed?: boolean;
64
+ }
65
+ /**
66
+ * What the person typed, put in the chat the moment they send it - before the command does anything - and drawn before
67
+ * it goes on: a command's work can hold the event loop (a long requires chain parsed), and the chat stays as it was
68
+ * until it lets go. What the command says comes under it.
69
+ */
70
+ export declare function echoCommand(pi: ExtensionAPI, command: string): Promise<void>;
71
+ /** Until the TUI has drawn what was just put in the chat. */
72
+ export declare const nextFrame: () => Promise<void>;
73
+ /**
74
+ * The module a /regenerate's first words name, and the words after it - all of them when none is named. A name typed
75
+ * with spaces for its underscores - "d365_function executor" - is the module d365_function_executor: the most words
76
+ * that name one win.
77
+ */
78
+ export declare function moduleNamedFirst(cwd: string, words: string[]): {
79
+ named?: string;
80
+ rest: string[];
81
+ };
82
+ /**
83
+ * How many jobs the chat runs at once: `parallel-jobs:` in the session folder's config.turri.yaml - the only place it
84
+ * is set - else half this machine's cores, between one and four.
85
+ */
86
+ export declare function parallelJobs(cwd: string): number;
87
+ /** The project's own files and a digest of each - not plain_modules, dependencies, or turri's and git's folders. */
88
+ export declare function projectFiles(root: string): Map<string, string>;
89
+ /** The files added, changed or removed between two digests of the project. */
90
+ export declare function changedFiles(before: Map<string, string>, after: Map<string, string>): string[];
91
+ /**
92
+ * This machine, for the chat's agent, read afresh every turn: which wall is up under the agents' commands, whether the
93
+ * machine has the toolchains the project's modules name, and what in its testing scripts it lacks - for the person to
94
+ * install or fix; the agents install nothing. `modules`: what the modules need, when it is known already - working it
95
+ * out parses every spec of the project.
96
+ */
97
+ export declare function machineBlock(cwd: string, modules?: ReturnType<typeof stackOnMachine>): string;
98
+ /**
99
+ * What the project's modules need of this machine, kept between turns - working it out parses every spec, seconds on a
100
+ * project of long requires chains, and a message is never kept waiting for it. Worked out once the chat is up, and again
101
+ * when a turn has settled and a spec changed since: a turn is told what was known last, at most a turn behind.
102
+ */
103
+ export declare function machineNeeds(cwd: string, work?: (cwd: string) => ReturnType<typeof stackOnMachine>): {
104
+ /** What is known - worked out now only when nothing is yet. */
105
+ now(): ReturnType<typeof stackOnMachine>;
106
+ /** Worked out again, if a spec changed - once the chat has drawn what it is drawing. */
107
+ refreshSoon(): void;
108
+ };
109
+ /**
110
+ * The project's config.turri.yaml, for the chat's agent: what it says, and what that means for the
111
+ * tools - so a question about the project's settings is answered from the file, not from the
112
+ * tools' defaults.
113
+ */
114
+ export declare function projectConfigBlock(root: string): string;
115
+ export { fromWorkingDir };
116
+ /**
117
+ * What the agent is to do with a job that just ended, unasked: what the project's guidance says to do then, or else say
118
+ * in a line or two what ended - the person saw it end already, in the transcript.
119
+ */
120
+ export declare const JOB_FINISHED_REACTION: string;
121
+ /**
122
+ * A skill's question as the transcript keeps it: who asked, what it said before asking - a brief to confirm, the choices
123
+ * it weighed, shown in the dialog that is gone once answered - the question, and the answer.
124
+ */
125
+ export declare function exchangeLines(data: Exchange, fg: (color: "muted" | "text" | "dim" | "accent", text: string) => string): string[];
126
+ export interface Exchange {
127
+ skill: string;
128
+ /** What the skill's agent said before asking - a brief to confirm, the choices it weighed: shown in the dialog, kept here. */
129
+ said?: string;
130
+ question: string;
131
+ answer: string;
132
+ }
133
+ /**
134
+ * The name on the door.
135
+ *
136
+ * Pi's own startup header is a built-in component an extension cannot replace, so this
137
+ * adds a wordmark of its own at the top of the session. `turri` starts pi without
138
+ * its header (see pi-launch.ts), leaving only this one.
139
+ */
140
+ /**
141
+ * The ***plain rules as the conversation has them in its system prompt, every turn - the standard library's
142
+ * inventory among them, in its rule, so the conversation knows what a spec can import from the start.
143
+ */
144
+ export declare function chatRulesBlock(rules?: {
145
+ name: string;
146
+ content: string;
147
+ }[]): string;
package/dist/extension.js CHANGED
@@ -80,8 +80,17 @@ const PACKAGE_ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
80
80
  */
81
81
  export function askUserVia(ctx, onExchange,
82
82
  /** Given, "/btw ..." typed as an answer is a side question: answered with this, and the question asked again. */
83
- aside) {
83
+ aside,
84
+ /** A host's own dialogs (TurriHost.dialogs), the question put in them instead of the terminal's. */
85
+ dialogs) {
84
86
  return async (request) => {
87
+ if (dialogs !== undefined) {
88
+ const asking = (asked) => runAsk(asked, dialogs);
89
+ const bare = { ...request, said: undefined };
90
+ const answer = aside !== undefined ? await askWithAsides(bare, asking, aside) : await asking(bare);
91
+ onExchange(request.question, answer, [request.context, request.said].map((part) => part?.trim() ?? "").filter((part) => part !== "").join("\n\n") || undefined);
92
+ return answer;
93
+ }
85
94
  if (!ctx.hasUI) {
86
95
  return "No user is available to answer. Decide yourself, and say in your report what you assumed.";
87
96
  }
@@ -156,9 +165,9 @@ function hookPeekKey(ctx) {
156
165
  * pick from, several at once, "Something else…" to type instead, "Chat about this" to talk it over in
157
166
  * the chat first - so a question with a handful of answers is picked, not typed.
158
167
  */
159
- export function chatAskUserTool() {
168
+ export function chatAskUserTool(dialogs) {
160
169
  let conversation;
161
- const tool = createAskUserTool((request) => (conversation !== undefined ? askUserVia(conversation, () => { })(request) : Promise.resolve("No user is available to answer.")), undefined, undefined, undefined, "reply");
170
+ const tool = createAskUserTool((request) => (conversation !== undefined ? askUserVia(conversation, () => { }, undefined, dialogs)(request) : Promise.resolve("No user is available to answer.")), undefined, undefined, undefined, "reply");
162
171
  return {
163
172
  ...tool,
164
173
  promptGuidelines: [
@@ -171,7 +180,14 @@ export function chatAskUserTool() {
171
180
  },
172
181
  };
173
182
  }
174
- export default function (pi) {
183
+ /** The host each conversation's jobs report to, for the work loop made for them later. */
184
+ const hosts = new WeakMap();
185
+ /** turri's conversation as an extension of pi's, for a host that runs it itself: see TurriHost. */
186
+ export function createTurriExtension(host = {}) {
187
+ return (pi) => turriExtension(pi, host);
188
+ }
189
+ export default createTurriExtension();
190
+ function turriExtension(pi, host) {
175
191
  dropNoise();
176
192
  const cwd = process.cwd();
177
193
  // turri's own skills, and the project's - .turri/skills, .agents/skills (skills.ts).
@@ -189,7 +205,10 @@ export default function (pi) {
189
205
  const deployState = createDeployState();
190
206
  const jobs = createJobRegistry({
191
207
  // The loop's plan changed - a module being planned, or planned: the live panel shows it at once.
192
- onNotify: () => panel.refresh(),
208
+ onNotify: () => {
209
+ panel.refresh();
210
+ host.onPlanChange?.();
211
+ },
193
212
  // A job that fails is run once more; failing again, it and every job behind it fail, and the
194
213
  // harness - which can read the skills, the rules, the code, the tests and turri itself -
195
214
  // works out why, and what has to be done.
@@ -208,9 +227,13 @@ export default function (pi) {
208
227
  });
209
228
  return diagnosis !== undefined ? describeDiagnosis(diagnosis) : undefined;
210
229
  },
211
- onChange: () => panel.refresh(),
230
+ onChange: (job) => {
231
+ panel.refresh();
232
+ host.onJobChange?.(job);
233
+ },
212
234
  onFinish: (job) => {
213
235
  panel.refresh();
236
+ host.onJobChange?.(job);
214
237
  // Queued again as another job: that one says how it went.
215
238
  if (job.replacedBy !== undefined)
216
239
  return;
@@ -236,6 +259,8 @@ export default function (pi) {
236
259
  }, { triggerTurn: true, deliverAs: "followUp" });
237
260
  },
238
261
  });
262
+ hosts.set(jobs, host);
263
+ host.onJobs?.(jobs);
239
264
  // The ***plain rules go into the system prompt in full, every turn. The block is
240
265
  // identical each time, so it sits inside the cached prefix rather than invalidating it.
241
266
  // With them, the instruction to do turri's work with its tools rather than its command
@@ -375,7 +400,7 @@ export default function (pi) {
375
400
  registerBranding(pi, discoveredSkills);
376
401
  registerInterviewLog(pi);
377
402
  registerJobTools(pi, jobs, cwd);
378
- registerCheckPlain(pi, cwd);
403
+ registerCheckPlain(pi, cwd, host.dialogs);
379
404
  registerRegenerate(pi, cwd, jobs);
380
405
  registerDrift(pi, cwd, jobs);
381
406
  registerGlimpse(pi, cwd, jobs);
@@ -549,7 +574,7 @@ export default function (pi) {
549
574
  push(` Q: ${question}`);
550
575
  push(` A: ${answer}`);
551
576
  recordExchange(pi, { skill: skill.name, question, answer, ...(said !== undefined && said.trim() !== "" ? { said: said.trim() } : {}) });
552
- }, (question) => askOnTheSide(pi, ctx, cwd, jobs, question)),
577
+ }, (question) => askOnTheSide(pi, ctx, cwd, jobs, question), host.dialogs),
553
578
  model: conversationModel(ctx),
554
579
  // Renders queued by a step land in the same registry as everything else, so
555
580
  // pulses sees them without knowing a skill started them.
@@ -1326,6 +1351,7 @@ function workLoopFor(jobs, cwd, ctx) {
1326
1351
  });
1327
1352
  shared = { loop, from };
1328
1353
  workLoops.set(jobs, shared);
1354
+ hosts.get(jobs)?.onWorkLoop?.(loop);
1329
1355
  }
1330
1356
  shared.from.ctx = ctx;
1331
1357
  return shared.loop;
@@ -1932,9 +1958,9 @@ function jobLines(all) {
1932
1958
  * so it needs no external tool, no API key and no credits - and it is the only honest way
1933
1959
  * to answer "is this spec valid?" without generating anything.
1934
1960
  */
1935
- function registerCheckPlain(pi, cwd) {
1961
+ function registerCheckPlain(pi, cwd, dialogs) {
1936
1962
  pi.registerTool(checkPlainTool(cwd));
1937
- pi.registerTool(chatAskUserTool());
1963
+ pi.registerTool(chatAskUserTool(dialogs));
1938
1964
  // The web, for the conversation too - a library's documentation, an API, an error; curl is its
1939
1965
  // bash. Run here, like the rendering agents' (see render/web.ts).
1940
1966
  for (const tool of createWebTools())