@turri-dev/cli 0.1.5 → 0.1.6-next.10e70aa
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.
- package/.pi/themes/turri-dark.json +1 -1
- package/dist/config.js +19 -3
- package/dist/extension.js +1 -1
- package/dist/loop-tools.js +29 -20
- package/dist/regenerate/loop.js +11 -9
- package/dist/spec-tools.js +22 -4
- package/dist/stacks.js +1 -1
- package/docs/config.md +7 -7
- package/docs/theming.md +2 -2
- package/docs/using.md +1 -1
- package/genome/skills/brood/SKILL.md +1 -1
- package/genome/skills/brood/references/testing.md +1 -1
- package/genome/skills/implement-conformance-testing-script/SKILL.md +1 -1
- package/genome/skills/implement-unit-testing-script/SKILL.md +1 -1
- package/genome/skills/init-plain-project/SKILL.md +1 -1
- package/genome/skills/spawn/SKILL.md +1 -1
- package/package.json +1 -1
package/dist/config.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
2
|
import { copyFileSync, existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
3
|
-
import { join, resolve } from "node:path";
|
|
3
|
+
import { basename, join, resolve } from "node:path";
|
|
4
4
|
import { load as loadYaml } from "js-yaml";
|
|
5
5
|
import { projectDirOf } from "./regenerate/paths.js";
|
|
6
6
|
/**
|
|
@@ -157,6 +157,22 @@ export function chooseStack(stack) {
|
|
|
157
157
|
export function withStack(stack, work) {
|
|
158
158
|
return pinnedStack.run({ stack }, work);
|
|
159
159
|
}
|
|
160
|
+
/** Whether a stack is pinned here - named by the call that asked for the work, or kept by the pulse it runs in. */
|
|
161
|
+
export function stackPinned() {
|
|
162
|
+
return pinnedStack.getStore() !== undefined;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* The stack a module is built with: the one pinned - named by the call, or kept by the pulse - else the stack named
|
|
166
|
+
* after the module, config.turri.backend.yaml for backend.plain, else the one chosen for the session; none, when none
|
|
167
|
+
* of them is.
|
|
168
|
+
*/
|
|
169
|
+
export function stackForModule(plainFile) {
|
|
170
|
+
const root = projectDirOf(resolve(plainFile));
|
|
171
|
+
if (stackPinned())
|
|
172
|
+
return stackOf(root);
|
|
173
|
+
const own = basename(plainFile).replace(/\.plain$/, "");
|
|
174
|
+
return stacksOf(root).includes(own) ? own : stackOf(root);
|
|
175
|
+
}
|
|
160
176
|
/** The words that choose no stack: the project's config alone. */
|
|
161
177
|
export const NO_STACK = ["none", "off", "base", "-"];
|
|
162
178
|
/**
|
|
@@ -478,10 +494,10 @@ function agentEnvSetting(value) {
|
|
|
478
494
|
function withoutUndefined(config) {
|
|
479
495
|
return Object.fromEntries(Object.entries(config).filter(([, value]) => value !== undefined));
|
|
480
496
|
}
|
|
481
|
-
/** The config a spec is built with: its project's, with
|
|
497
|
+
/** The config a spec is built with: its project's, with its module's stack over it (`stackForModule`). */
|
|
482
498
|
export function configForSpec(plainFile) {
|
|
483
499
|
const root = projectDirOf(resolve(plainFile));
|
|
484
|
-
return readProjectConfig(root,
|
|
500
|
+
return readProjectConfig(root, stackForModule(plainFile));
|
|
485
501
|
}
|
|
486
502
|
/** One config file, read, or nothing of it when it is not there or does not parse. */
|
|
487
503
|
function readConfigFile(path) {
|
package/dist/extension.js
CHANGED
|
@@ -1041,7 +1041,7 @@ export function projectConfigBlock(root) {
|
|
|
1041
1041
|
return [
|
|
1042
1042
|
...settings(readProjectConfig(dir), withStack(undefined, () => findTestScripts(dir)), dir === root ? `${projectConfigFile(dir)}, for the specs here:` : `${relative(root, dir)}/${projectConfigFile(dir)}, for the specs in ${relative(root, dir)}/ - built in ${relative(root, dir)}/plain_modules:`, where, stacks.length > 0 ? "project of stacks" : "project"),
|
|
1043
1043
|
...(stacks.length > 0
|
|
1044
|
-
? ["", `Stacks: ${stacks.join(", ")} - chosen now: ${chosen ?? "none, so the project's file alone"}. Each module is built with the stack of its part
|
|
1044
|
+
? ["", `Stacks: ${stacks.join(", ")} - chosen now: ${chosen ?? "none, so the project's file alone"}. Each module is built with the stack of its part, pinned to its pulses whatever is chosen after: a stack named after the module is its own - backend.plain is built with config.turri.backend.yaml - with nothing to choose. For a module whose stack has another name, pass it as stack to regenerate, conform, glimpse and write_spec_diff. Choose one for the whole session with the stack tool only when the user asks.`]
|
|
1045
1045
|
: []),
|
|
1046
1046
|
// A stack: its file over the project's, for everything built while it is chosen.
|
|
1047
1047
|
...stacks.flatMap((stack) => [
|
package/dist/loop-tools.js
CHANGED
|
@@ -2,7 +2,7 @@ import { existsSync } from "node:fs";
|
|
|
2
2
|
import { basename, relative, resolve } from "node:path";
|
|
3
3
|
import { defineTool } from "@earendil-works/pi-coding-agent";
|
|
4
4
|
import { Type } from "typebox";
|
|
5
|
-
import {
|
|
5
|
+
import { CONFIG_FILE, readProjectConfig, stackConfigFile, stackForModule, stackNamed, stacksOf, withStack } from "./config.js";
|
|
6
6
|
import { checkPlainFile } from "./plain/check.js";
|
|
7
7
|
import { describeDrift, describeModules } from "./regenerate/loop.js";
|
|
8
8
|
import { pulseName } from "./regenerate/jobs.js";
|
|
@@ -28,29 +28,38 @@ export const MODEL_PARAM = Type.Optional(Type.String({
|
|
|
28
28
|
export const STACK_PARAM = Type.Optional(Type.String({
|
|
29
29
|
description: "In a project of stacks: the stack that builds this module - its config.turri.<stack>.yaml over config.turri.yaml, " +
|
|
30
30
|
"its testing scripts, toolchains and models - for every pulse of it, whatever stack is chosen later. \"none\": " +
|
|
31
|
-
"config.turri.yaml alone. Omitted: the stack
|
|
31
|
+
"config.turri.yaml alone. Omitted: the stack named after the module - config.turri.backend.yaml for backend.plain - " +
|
|
32
|
+
"else the one chosen now.",
|
|
32
33
|
}));
|
|
33
34
|
/**
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
35
|
+
* Why a module asked for with no stack named cannot be built: a project of stacks where it has no stack of its own and
|
|
36
|
+
* none is chosen, and config.turri.yaml alone names no unit testing script - every pulse would stop for want of one.
|
|
37
|
+
* The stacks and the script each names, and what to do. Undefined when it can be built.
|
|
37
38
|
*/
|
|
38
|
-
export function
|
|
39
|
+
export function stacklessBuild(plainFile) {
|
|
39
40
|
const root = projectDirOf(plainFile);
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
return `${each}${unit !== undefined ? ` (${stackConfigFile(each)}: ${relative(root, unit)})` : ""}`;
|
|
47
|
-
});
|
|
48
|
-
throw new Error(`Nothing was started: ${basename(plainFile)} would be built with no stack - ${CONFIG_FILE} alone - and that names no unit testing ` +
|
|
49
|
-
`script, so every pulse would stop. The project's stacks: ${scripts.join(", ")}. Pass the stack that builds ` +
|
|
50
|
-
`${basename(plainFile)} as stack - or ask the user which, when the spec does not make it plain.`);
|
|
51
|
-
}
|
|
52
|
-
return work();
|
|
41
|
+
const stacks = stacksOf(root);
|
|
42
|
+
if (stacks.length === 0 || stackForModule(plainFile) !== undefined || findTestScripts(root).unit !== undefined)
|
|
43
|
+
return undefined;
|
|
44
|
+
const scripts = stacks.map((each) => {
|
|
45
|
+
const unit = withStack(each, () => findTestScripts(root).unit);
|
|
46
|
+
return `${each}${unit !== undefined ? ` (${stackConfigFile(each)}: ${relative(root, unit)})` : ""}`;
|
|
53
47
|
});
|
|
48
|
+
return (`${basename(plainFile)} would be built with no stack - ${CONFIG_FILE} alone - and that names no unit testing script, so every ` +
|
|
49
|
+
`pulse would stop. No stack is named after it, and none is chosen. The project's stacks: ${scripts.join(", ")}. Pass the ` +
|
|
50
|
+
`stack that builds ${basename(plainFile)} as stack - or ask the user which, when the spec does not make it plain.`);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* `work` with the stack a call names chosen for it - or, none named, as it is: each module is then built with its own
|
|
54
|
+
* (`stackForModule`). A module that could not be built that way is refused before anything is planned (`stacklessBuild`).
|
|
55
|
+
*/
|
|
56
|
+
export function withCallStack(plainFile, named, work) {
|
|
57
|
+
if (named !== undefined)
|
|
58
|
+
return withStack(stackNamed(projectDirOf(plainFile), named), work);
|
|
59
|
+
const stackless = stacklessBuild(plainFile);
|
|
60
|
+
if (stackless !== undefined)
|
|
61
|
+
throw new Error(`Nothing was started: ${stackless}`);
|
|
62
|
+
return work();
|
|
54
63
|
}
|
|
55
64
|
export const WAIT_PARAM = Type.Optional(Type.Boolean({
|
|
56
65
|
description: "Block until the work finishes instead of returning a pulse id. Defaults to false in the " +
|
|
@@ -164,7 +173,7 @@ export function regenerateTool(options) {
|
|
|
164
173
|
"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.",
|
|
165
174
|
"Never stop pulses and start them again yourself to follow a spec change: regenerate does it.",
|
|
166
175
|
"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.",
|
|
167
|
-
"In a project of stacks - a backend and a frontend, each with its config.turri.<stack>.yaml -
|
|
176
|
+
"In a project of stacks - a backend and a frontend, each with its config.turri.<stack>.yaml - a module is built with the stack named after it by itself: backend.plain with config.turri.backend.yaml. Pass stack only for a module whose stack has another name. It is pinned to the module's pulses: a backend and a frontend regenerate side by side, each with its own, without switching the stack chosen. What it says back names the stack each module is built with.",
|
|
168
177
|
'from_scratch "module" takes out the code where the module is built; the modules below keep theirs, and the module starts from a copy of the code below it. "chain" takes out every codebase of its requires chain, the base\'s too, and builds all of it again from the bottom: use it after modules were split, merged or moved (refactor-module), or when the user asks for the whole chain.',
|
|
169
178
|
],
|
|
170
179
|
parameters: Type.Object({
|
package/dist/regenerate/loop.js
CHANGED
|
@@ -13,7 +13,7 @@ import { adoptDigests, chainTree, codeTree, conformanceTarget, REGENERATE_AGAIN,
|
|
|
13
13
|
import { describeSpecDiffConformance, renderSpecDiffConformance } from "./conform.js";
|
|
14
14
|
import { conformanceDirFor } from "./conformance.js";
|
|
15
15
|
import { modulesRoot } from "./paths.js";
|
|
16
|
-
import { budgetFor,
|
|
16
|
+
import { budgetFor, choosingWhen, configForSpec, conformanceSwitchedOn, stackForModule, stackPinned, withStack } from "../config.js";
|
|
17
17
|
import { describeSpecChange } from "./prompt.js";
|
|
18
18
|
import { resolveTemplateDirs } from "./paths.js";
|
|
19
19
|
import { builtTips, copyRequiredCode, importingModules, isImportModule, newlyRequired, resolveRequiresChain, startFromScratch } from "./requires.js";
|
|
@@ -1032,8 +1032,8 @@ export function createWorkLoop(options) {
|
|
|
1032
1032
|
return { status, jobs: (state.running !== undefined ? listed.slice(1) : listed).map((item) => item.job), scheduled: (state.running !== undefined ? listed.slice(1) : listed).map((item) => (item.entry.waitsFor !== undefined ? { title: item.entry.title, specs: item.entry.cases.add.map((touched) => touched.text) } : undefined)) };
|
|
1033
1033
|
};
|
|
1034
1034
|
/**
|
|
1035
|
-
* Look after a codebase - its chain up to it - from now on; says its state. `pin`:
|
|
1036
|
-
*
|
|
1035
|
+
* Look after a codebase - its chain up to it - from now on; says its state. `pin`: its stack is worked out again - its
|
|
1036
|
+
* tip's (`stackForModule`) - unless `keep`, and it has one: a spec change keeps the stack it was asked for with.
|
|
1037
1037
|
*/
|
|
1038
1038
|
const track = (chain, askedFile, request, pin) => {
|
|
1039
1039
|
const tip = tipOf(chain);
|
|
@@ -1050,8 +1050,10 @@ export function createWorkLoop(options) {
|
|
|
1050
1050
|
}
|
|
1051
1051
|
state.chain = chain;
|
|
1052
1052
|
state.askedFile = askedFile;
|
|
1053
|
-
|
|
1054
|
-
|
|
1053
|
+
// Kept only when it is a stack: a module pinned to none - built before its stack's file was there - takes its own.
|
|
1054
|
+
const keep = pin?.keep === true && !stackPinned() && state.stack?.name !== undefined;
|
|
1055
|
+
if (pin !== undefined && !keep)
|
|
1056
|
+
state.stack = { name: stackForModule(tip.plainFile) };
|
|
1055
1057
|
// Its model as its stack has it: a stack's file can name a coding model of its own.
|
|
1056
1058
|
const model = request.model ?? inStack(state, () => options.modelFor?.(tip.plainFile));
|
|
1057
1059
|
if (model !== undefined)
|
|
@@ -1066,9 +1068,9 @@ export function createWorkLoop(options) {
|
|
|
1066
1068
|
const files = codebasesOf(resolve(options.cwd, plainFile));
|
|
1067
1069
|
for (const file of files)
|
|
1068
1070
|
asked.set(file, { ...asked.get(file), ...request });
|
|
1069
|
-
// Asked for, it is built with
|
|
1070
|
-
// it was asked for with.
|
|
1071
|
-
const pin = {
|
|
1071
|
+
// Asked for, it is built with its stack - the one the call names, its own, or the one chosen now - pinned, whatever
|
|
1072
|
+
// is chosen later; a spec change keeps the one it was asked for with.
|
|
1073
|
+
const pin = { keep: specChange === true };
|
|
1072
1074
|
const states = withParseCache(() => files.flatMap((file) => chainsFor(file).flatMap((chain) => toBuild(chain).map((entry) => track(chain.slice(0, chain.indexOf(entry) + 1), file, request, pin)))));
|
|
1073
1075
|
// Asked again, a halted codebase is tried again - one the person halted too, unless only its spec changed.
|
|
1074
1076
|
for (const state of states) {
|
|
@@ -1191,7 +1193,7 @@ export function createWorkLoop(options) {
|
|
|
1191
1193
|
}
|
|
1192
1194
|
}
|
|
1193
1195
|
}
|
|
1194
|
-
const tracked = withParseCache(() => (wentBack ? [own] : chainsFor(file)).flatMap((chain) => toBuild(chain).map((entry) => track(chain.slice(0, chain.indexOf(entry) + 1), file, request, {
|
|
1196
|
+
const tracked = withParseCache(() => (wentBack ? [own] : chainsFor(file)).flatMap((chain) => toBuild(chain).map((entry) => track(chain.slice(0, chain.indexOf(entry) + 1), file, request, {}))));
|
|
1195
1197
|
// The module asked for, in its own codebase - and those below it: never the codebases built on it.
|
|
1196
1198
|
const asking = tracked.find((state) => tipEntry(state).buildDir === self.buildDir);
|
|
1197
1199
|
const states = asking !== undefined ? tracked.filter((state) => own.some((entry) => entry.buildDir === tipEntry(state).buildDir)) : tracked;
|
package/dist/spec-tools.js
CHANGED
|
@@ -8,6 +8,9 @@ import { applySpecDiff, plainFilesOf, revertSpecDiff } from "./regenerate/regene
|
|
|
8
8
|
import { MissingFunctionalitiesError } from "./plain/errors.js";
|
|
9
9
|
import { describeModules } from "./regenerate/loop.js";
|
|
10
10
|
import { reviewNote } from "./tools.js";
|
|
11
|
+
import { STACK_PARAM, stacklessBuild, withCallStack } from "./loop-tools.js";
|
|
12
|
+
import { stackNamed } from "./config.js";
|
|
13
|
+
import { projectDirOf } from "./regenerate/paths.js";
|
|
11
14
|
/**
|
|
12
15
|
* Writing a spec change as a diff, and building it, as one step.
|
|
13
16
|
*
|
|
@@ -37,12 +40,14 @@ export function createSpecDiffTools(options) {
|
|
|
37
40
|
skillPaced
|
|
38
41
|
? "One change per answer, and a new one only after the user has answered a question about it. Set fixes_earlier to correct something already written."
|
|
39
42
|
: "Write the changes one after another, on your own: no answer is needed between them, and none is asked for. Set fixes_earlier to correct something already written.",
|
|
43
|
+
"In a project of stacks, the change is built with the stack named after the module - backend.plain with config.turri.backend.yaml - by itself. For a module whose stack has another name, pass it as stack. A change that cannot be built yet for want of a stack is written all the same: carry on, and pass the stack with the next change.",
|
|
40
44
|
"To take something out of what is already written and queued - the user drops a page or a feature - write the diff that removes it from every spec that names it, with fixes_earlier. What is left to build is worked out from the spec each time, and a build the change undoes is stopped by itself; never stop builds to drop a change - it stays in the files.",
|
|
41
45
|
],
|
|
42
46
|
parameters: Type.Object({
|
|
43
47
|
plain_file: Type.String({ description: "The .plain file of the module the change is built for" }),
|
|
44
48
|
diff: Type.String({ description: "The change, as a git diff (diff --git a/<file> b/<file> ...)" }),
|
|
45
49
|
fixes_earlier: Type.Optional(Type.Boolean({ description: "This corrects something already written - a contradiction, a review finding" })),
|
|
50
|
+
stack: STACK_PARAM,
|
|
46
51
|
guidance: Type.Optional(Type.String({
|
|
47
52
|
description: "How to plan the queue for this change, in a sentence or two - which functional specs go together, what first - as the " +
|
|
48
53
|
"user said it or as the interview settled it. A strong preference for the planner. Omit when there is none.",
|
|
@@ -87,12 +92,25 @@ export function createSpecDiffTools(options) {
|
|
|
87
92
|
answeredSinceLastDiff = false;
|
|
88
93
|
owed = false;
|
|
89
94
|
liveReview?.notice(`${params.plain_file}, after the change to ${touched.join(", ")}`);
|
|
90
|
-
// In the files, the change is work: the loop works out what is left to build and plans it
|
|
91
|
-
// A spec change, not a request: a module the person halted stays halted.
|
|
92
|
-
|
|
95
|
+
// In the files, the change is work: the loop works out what is left to build and plans it - with the stack the call
|
|
96
|
+
// names, else the module's own. A spec change, not a request: a module the person halted stays halted. The change
|
|
97
|
+
// is written whatever the stack: one that cannot be built yet is said, and built once it can - nothing is lost.
|
|
98
|
+
const building = resolve(cwd, params.plain_file);
|
|
99
|
+
let stackless;
|
|
100
|
+
try {
|
|
101
|
+
stackless = params.stack === undefined ? stacklessBuild(building) : (stackNamed(projectDirOf(building), params.stack), undefined);
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
stackless = error instanceof Error ? error.message : String(error);
|
|
105
|
+
}
|
|
106
|
+
const statuses = stackless === undefined
|
|
107
|
+
? await withCallStack(building, params.stack, () => loop.bringUp(params.plain_file, { specChange: true, ...(params.guidance !== undefined ? { guidance: params.guidance } : {}) }))
|
|
108
|
+
: [];
|
|
93
109
|
const lines = [
|
|
94
110
|
result === "applied" ? `Applied the change to ${touched.join(", ")}.` : `The change to ${touched.join(", ")} was already applied.`,
|
|
95
|
-
|
|
111
|
+
stackless === undefined
|
|
112
|
+
? `Building in the background:\n${describeModules(statuses)}\n`
|
|
113
|
+
: `Not built yet - ${stackless} The change is in the spec: pass stack with the next change, or call regenerate with it, and everything written so far is built.\n`,
|
|
96
114
|
// An interview goes back to its questions; a skill that writes on its own, straight to its next change - a stop to ask ends it.
|
|
97
115
|
paced
|
|
98
116
|
? "Carry on with the interview."
|
package/dist/stacks.js
CHANGED
|
@@ -77,7 +77,7 @@ export function registerStacks(pi, cwd) {
|
|
|
77
77
|
"For the session; a pulse already running keeps the stack it started with. Without a name, says the stacks and the one chosen.",
|
|
78
78
|
promptSnippet: "choose the stack - its testing scripts and settings - that everything is built with",
|
|
79
79
|
promptGuidelines: [
|
|
80
|
-
"Switch the stack only when the user asks for it, and say which you chose.
|
|
80
|
+
"Switch the stack only when the user asks for it, and say which you chose. A module is built with the stack named after it by itself; to build one whose stack has another name, pass that stack as stack to regenerate, conform, glimpse or write_spec_diff instead: it is pinned to that module's pulses, and the session's choice stays as it is.",
|
|
81
81
|
],
|
|
82
82
|
parameters: Type.Object({
|
|
83
83
|
name: Type.Optional(Type.String({ description: "The stack to choose, or none. Omitted: the stacks there are, and the one chosen" })),
|
package/docs/config.md
CHANGED
|
@@ -216,22 +216,22 @@ coding-model:
|
|
|
216
216
|
thinking: high
|
|
217
217
|
```
|
|
218
218
|
|
|
219
|
-
|
|
219
|
+
Which stack builds a module, first match wins:
|
|
220
220
|
|
|
221
|
-
- **
|
|
222
|
-
- **
|
|
223
|
-
- **On the command line
|
|
221
|
+
- **The one a call names.** `regenerate`, `conform`, `glimpse` and `write_spec_diff` take a `stack`; the chat's model passes it for a module whose stack has a name of its own, leaving the session's choice as it is.
|
|
222
|
+
- **The stack named after the module** - `config.turri.backend.yaml` builds `backend.plain` - with nothing to choose or pass. Name each stack after the module it builds, and a backend and a frontend are each built with their own, side by side - in the chat, in a brood as each change is written, and on the command line.
|
|
223
|
+
- **The one chosen for the session** - in the chat, with `/stack` - a list of the project's stacks and none - or `/stack backend`, `/stack none`. The chat's model has a `stack` tool too, and switches the stack when you ask it to. `/stack` alone says which is chosen now. On the command line, with `--stack <name>` on any command - `turri regenerate`, `conform`, `spec-diff`, `glimpse`, `release`, `cultivate` - and on `turri --stack backend` for the chat; or with `TURRI_STACK` in the environment.
|
|
224
224
|
|
|
225
225
|
The choice lasts for the session only: nothing remembers it, and turri started again has none chosen. A stack the project has no file for is refused, with the stacks it has. With none chosen, `config.turri.yaml` alone is used.
|
|
226
226
|
|
|
227
227
|
How a stack's config is made:
|
|
228
228
|
|
|
229
229
|
- **Laid over `config.turri.yaml`, key by key.** What the stack's file gives wins; what it leaves out is the project's. A model it names takes what it leaves out from the project's same model - above, with the stack frontend chosen, everything renders on the project's coding model, thinking `high`.
|
|
230
|
-
- **Pinned to a module when it is asked for.** A module is built with the stack
|
|
231
|
-
- **Refused with none, when that cannot build.** In a project of stacks, a module asked for with no stack - none named, none chosen - is refused before anything is planned when `config.turri.yaml` alone names no unit testing script: every pulse would stop for want of one. The refusal names the stacks and the script each names. `stack: none` builds with `config.turri.yaml` alone all the same.
|
|
230
|
+
- **Pinned to a module when it is asked for.** A module is built with the stack it was asked for with, from its first pulse to its last: the pulses planned after it, its test runs, the scripts found, its plans and its conformance tests, whatever stack is chosen meanwhile. A pulse waiting for a free slot keeps the stack it was queued with. A change to its spec keeps its stack - unless it was built with none and has a stack of its own now; asked for again, its stack is worked out again. What `regenerate` says back names each module's stack, and so does a pulse's report.
|
|
231
|
+
- **Refused with none, when that cannot build.** In a project of stacks, a module asked for with no stack - none named, none named after it, none chosen - is refused before anything is planned when `config.turri.yaml` alone names no unit testing script: every pulse would stop for want of one. The refusal names the stacks and the script each names. A spec change `write_spec_diff` writes is never refused: it is written, and said not to be built yet - passed the stack, everything written is built. `stack: none` builds with `config.turri.yaml` alone all the same.
|
|
232
232
|
- **The project's alone.** `module:`, `harness-model:`, `parallel-jobs:`, `rules-off:` and `compact-at:` are read only from `config.turri.yaml` - there is one conversation, one module built when none is named, one machine. `agent-env:` and `toolchains:` in a stack's file add to the project's.
|
|
233
233
|
|
|
234
|
-
The chat is told each stack's config and the one chosen. The skills that write testing scripts put each stack's in its own file once a project has two stacks,
|
|
234
|
+
The chat is told each stack's config and the one chosen. The skills that write testing scripts put each stack's in its own file once a project has two stacks, each stack named after the module it builds.
|
|
235
235
|
|
|
236
236
|
## The old config.yaml
|
|
237
237
|
|
package/docs/theming.md
CHANGED
|
@@ -26,10 +26,10 @@ Both themes, the command line's colours, the spec diff's, the banner's jellyfish
|
|
|
26
26
|
| path | `#3e5a6b` | the editor's border, what is dim: `dim`, line numbers |
|
|
27
27
|
| grauzone | `#85a3b2` | body text, and what can be skimmed: `text`, `muted` |
|
|
28
28
|
| white | `#f4f7f9` | headings, tool titles, what you typed, the jellyfish's shine |
|
|
29
|
-
| siesta tan | `#e9d8c8` |
|
|
29
|
+
| siesta tan | `#e9d8c8` | strings in code, \*\*\*plain's text in quotes or backticks |
|
|
30
30
|
| eggplant | `#732553` | a removed line's fill in a spec diff |
|
|
31
31
|
| neon | `#5ce1e6` | links, focus, the selection, section labels, \*\*\*plain's concepts in a spec diff: `accent`, `mdLink`, regen's `◆` |
|
|
32
|
-
| neon hover | `#8eebee` | types in code, an added line's fill in a light theme's spec diff |
|
|
32
|
+
| neon hover | `#8eebee` | inline code - siesta tan beside white text was too close to tell apart, and tiring to read - types in code, an added line's fill in a light theme's spec diff: `mdCode` |
|
|
33
33
|
| pink | `#ff5c8d` | the wordmark, the jellyfish's bell, and what is done - its `✓`, the spec map's `◇` and `◆`: `success` |
|
|
34
34
|
| pink hover | `#ff7fa6` | \*\*\*plain's section headers, a removed line's `-`, keywords in code |
|
|
35
35
|
| orange | `#ff7a3d` | the jellyfish's gut, the `$` of bash mode, and everything that asks for you - what failed (`✗`), what works with limits, what needs you (the spec map's `!`): `error`, `warning` |
|
package/docs/using.md
CHANGED
|
@@ -102,7 +102,7 @@ In `print`, `json` and `rpc` modes the process ends when the answer is written,
|
|
|
102
102
|
|
|
103
103
|
The chat reads the project's `config.turri.yaml` afresh every turn and follows it: its agent is told what it sets, and the render tools default to it - `module` when you ask to build without naming one, `conformance-tests: false` to render without conformance tests (none written, none of the existing ones run) unless you ask for them, `coding-model` for what renders run on, and the testing scripts. `harness-model` is what `turri` starts on. What you ask for in the conversation wins. Every key, what happens without it, and how the models inherit: [the project config](config.md).
|
|
104
104
|
|
|
105
|
-
A project whose parts are on different stacks gives each stack its own `config.turri.<stack>.yaml` beside `config.turri.yaml` - `config.turri.backend.yaml`, `config.turri.frontend.yaml` - with its testing scripts, models and conformance over the project's. A stack
|
|
105
|
+
A project whose parts are on different stacks gives each stack its own `config.turri.<stack>.yaml` beside `config.turri.yaml` - `config.turri.backend.yaml`, `config.turri.frontend.yaml` - with its testing scripts, models and conformance over the project's. A stack named after its module builds it - `config.turri.backend.yaml` builds `backend.plain` - with nothing to choose, so a backend and a frontend are each built with their own, side by side, a brood's changes too; for a stack named otherwise the chat's model passes it as `stack`, or choose one for the session with `/stack backend`. The chat is told each stack's and which is chosen, and the status bar shows it. See [stacks](config.md#stacks).
|
|
106
106
|
|
|
107
107
|
A project with only a `config.yaml` - turri's file before it had its own name, or another tool's - is read from it until turri first writes a setting, which copies it to `config.turri.yaml`; see [the old config.yaml](config.md#the-old-configyaml).
|
|
108
108
|
|
|
@@ -102,7 +102,7 @@ Create the module structure and frontmatter so there is a `.plain` file to build
|
|
|
102
102
|
|
|
103
103
|
Write no definitions, no functional specs, no `***test reqs***` and no `***acceptance tests***`.
|
|
104
104
|
|
|
105
|
-
End the phase with the unit tests: their framework, as a `:UnitTests:` requirement, and the unit testing script, which every build needs from the first one on. If turri already finds a script - named by `unittests-script:` in `config.turri.yaml` at the project root, or the one `run_unittests*.sh` in the root or in `test_scripts/` - keep it. Otherwise write `test_scripts/run_unittests_<language>.sh` with the `implement-unit-testing-script` skill, for the language and framework just settled. Do not link it from the specs. Without it, every build stops before it starts. When this module sits in a project beside another on a different stack - a frontend beside a backend, sharing only an imported module - its script is its stack's: name it in that stack's `config.turri.<stack>.yaml` beside `config.turri.yaml` (`unittests-script:`) - `config.turri.frontend.yaml` for the stack `frontend` - not in `config.turri.yaml`, which is the project's and holds what the stacks share.
|
|
105
|
+
End the phase with the unit tests: their framework, as a `:UnitTests:` requirement, and the unit testing script, which every build needs from the first one on. If turri already finds a script - named by `unittests-script:` in `config.turri.yaml` at the project root, or the one `run_unittests*.sh` in the root or in `test_scripts/` - keep it. Otherwise write `test_scripts/run_unittests_<language>.sh` with the `implement-unit-testing-script` skill, for the language and framework just settled. Do not link it from the specs. Without it, every build stops before it starts. When this module sits in a project beside another on a different stack - a frontend beside a backend, sharing only an imported module - its script is its stack's: name it in that stack's `config.turri.<stack>.yaml` beside `config.turri.yaml` (`unittests-script:`) - `config.turri.frontend.yaml` for the stack `frontend` - not in `config.turri.yaml`, which is the project's and holds what the stacks share. Name the stack after this module - `config.turri.frontend.yaml` for `frontend.plain` - and it is built with it by itself, with nothing to choose. A stack the project already has under another name is passed as `stack` to `write_spec_diff`.
|
|
106
106
|
|
|
107
107
|
The unit testing script runs the static checks too - the formatter, linter and type checker the stack names, which count as the unit tests do; `implement-unit-testing-script` says which for each language. There is no separate static checks script.
|
|
108
108
|
|
|
@@ -19,7 +19,7 @@ Write no `***acceptance tests***`, no functional specs and no `***implementation
|
|
|
19
19
|
3. **How the tests reach the software** - as a subprocess of the CLI, over HTTP against a server the tests start, through the public entry point it exports. Author it.
|
|
20
20
|
4. **Setup and outside services** - fixtures, test data, databases, third-party APIs: started for real, faked, or mocked at the boundary. Author each decision as its own requirement.
|
|
21
21
|
5. **How the whole suite is run** - the one command that discovers every test. Author it.
|
|
22
|
-
6. **The conformance testing script** - if turri already finds one (named by `conformance-tests-script:` in `config.turri.yaml`, or the one `run_conformance_tests*.sh` in the project root or in `test_scripts/`), check it runs the suite the way the test reqs now say, and keep it. Otherwise write `test_scripts/run_conformance_tests_<language>.sh` with `implement-conformance-testing-script`, to match the test reqs. Never link it from the specs. If `test_scripts/` would hold two of a kind, name the one to use in `config.turri.yaml`. In a project whose parts are on different stacks, name each stack's script in its own `config.turri.<stack>.yaml` (`conformance-tests-script:`),
|
|
22
|
+
6. **The conformance testing script** - if turri already finds one (named by `conformance-tests-script:` in `config.turri.yaml`, or the one `run_conformance_tests*.sh` in the project root or in `test_scripts/`), check it runs the suite the way the test reqs now say, and keep it. Otherwise write `test_scripts/run_conformance_tests_<language>.sh` with `implement-conformance-testing-script`, to match the test reqs. Never link it from the specs. If `test_scripts/` would hold two of a kind, name the one to use in `config.turri.yaml`. In a project whose parts are on different stacks, name each stack's script in its own `config.turri.<stack>.yaml` (`conformance-tests-script:`), each stack named after the module it builds - `config.turri.backend.yaml` for `backend.plain` - so each part is built with its own by itself.
|
|
23
23
|
7. **Anything else** - anything about testing the user wants to add or change.
|
|
24
24
|
|
|
25
25
|
Run `validate` on every module you changed. The product phase's first build needs the test reqs and the conformance testing script both there: without them it stops before it starts.
|
|
@@ -208,7 +208,7 @@ The harness never runs `.ps1` scripts. These notes apply only to an optional cop
|
|
|
208
208
|
4. Pick the right row from the [Dependency isolation](#dependency-isolation) table — use the same flag/env var in steps 7, 7b and 8 — and the checks from [Static checks of the tests](#static-checks-of-the-tests).
|
|
209
209
|
5. Add the language-appropriate "no tests discovered" guard from [No tests discovered detection](#no-tests-discovered-detection), and the guard against conformance tests in the build folder from [Tests in the build folder](#tests-in-the-build-folder).
|
|
210
210
|
6. Save the new script as `test_scripts/run_conformance_tests_<lang>.sh` and `chmod +x` it. Write a `.ps1` copy only if the user wants to run it by hand on Windows.
|
|
211
|
-
7. **Make sure turri finds the script.** Do not link it from the specs - turri finds the conformance testing script in the project root (the folder that holds `plain_modules/`) and puts it, whole, into the prompts of the agents that write and review the conformance tests itself, whenever conformance tests are written for a build; a link to it from a spec is ignored. It looks, in order, at `config.turri.yaml` in the root (`conformance-tests-script: <path relative to the root>`) - with the chosen stack's `config.turri.<stack>.yaml` over it, then for the one `run_conformance_tests*.sh` in the root, then for the one in `test_scripts/`. Saving the script in `test_scripts/` (or the root) is enough when it is the only one there. If the project keeps it elsewhere, name it in `config.turri.yaml` as `conformance-tests-script:` - and when the project has only a `config.yaml`, copy it to `config.turri.yaml` first (and each `config.<name>.yaml` to `config.turri.<name>.yaml`) and edit the copy; `config.yaml` may be another tool's, and is never written. If the project has modules on different stacks - e.g. a Python backend and a React frontend side by side, sharing only an imported module - each part is on a stack of its own, and each stack names its own script in its own file beside `config.turri.yaml`: `conformance-tests-script:` in `config.turri.backend.yaml` for the stack `backend`, in `config.turri.frontend.yaml` for the stack `frontend`.
|
|
211
|
+
7. **Make sure turri finds the script.** Do not link it from the specs - turri finds the conformance testing script in the project root (the folder that holds `plain_modules/`) and puts it, whole, into the prompts of the agents that write and review the conformance tests itself, whenever conformance tests are written for a build; a link to it from a spec is ignored. It looks, in order, at `config.turri.yaml` in the root (`conformance-tests-script: <path relative to the root>`) - with the chosen stack's `config.turri.<stack>.yaml` over it, then for the one `run_conformance_tests*.sh` in the root, then for the one in `test_scripts/`. Saving the script in `test_scripts/` (or the root) is enough when it is the only one there. If the project keeps it elsewhere, name it in `config.turri.yaml` as `conformance-tests-script:` - and when the project has only a `config.yaml`, copy it to `config.turri.yaml` first (and each `config.<name>.yaml` to `config.turri.<name>.yaml`) and edit the copy; `config.yaml` may be another tool's, and is never written. If the project has modules on different stacks - e.g. a Python backend and a React frontend side by side, sharing only an imported module - each part is on a stack of its own, and each stack names its own script in its own file beside `config.turri.yaml`: `conformance-tests-script:` in `config.turri.backend.yaml` for the stack `backend`, in `config.turri.frontend.yaml` for the stack `frontend`. Name each stack after the module it builds - `config.turri.backend.yaml` for `backend.plain` - and that module is built with it by itself, with nothing to choose; a stack named otherwise is passed as `stack` to `regenerate` in the chat, or `--stack <stack>` on the command line. turri never guesses between two. The script is required: with conformance testing on, a build without one stops before it starts. The `***test reqs***` still describe the conformance framework and command in words; do not paraphrase the script into them.
|
|
212
212
|
|
|
213
213
|
## Anti-Patterns
|
|
214
214
|
|
|
@@ -139,7 +139,7 @@ Notes:
|
|
|
139
139
|
3. Translate each of the eight steps above into the equivalent commands for the target language **and** shell. The toolchain check, dependency install, static checks and test invocation are the language-specific parts; the rest is mechanical translation between Bash and PowerShell syntax.
|
|
140
140
|
4. Pick the dependency-isolation mechanism from the [Dependency isolation](#dependency-isolation) table and use it consistently in steps 6, 7 and 8, and the checks from the [Static checks](#static-checks) table.
|
|
141
141
|
5. Save the new script as `test_scripts/run_unittests_<lang>.sh` (a `.ps1` copy next to it only if the user wants to run it by hand on Windows). For Bash, `chmod +x` it.
|
|
142
|
-
6. **Make sure turri finds the script.** Do not link it from the specs - turri finds the unit testing script in the project root (the folder that holds `plain_modules/`) and puts it, whole, into the prompt of the coding agent itself; a link to it from a spec is ignored. It looks, in order, at `config.turri.yaml` in the root (`unittests-script: <path relative to the root>`) - with the chosen stack's `config.turri.<stack>.yaml` over it, then for the one `run_unittests*.sh` in the root, then for the one in `test_scripts/`. Saving the script in `test_scripts/` (or the root) is enough when it is the only one there. If the project keeps it elsewhere, name it in `config.turri.yaml` as `unittests-script:` - and when the project has only a `config.yaml`, copy it to `config.turri.yaml` first (and each `config.<name>.yaml` to `config.turri.<name>.yaml`) and edit the copy; `config.yaml` may be another tool's, and is never written. If the project has modules on different stacks - e.g. a Python backend and a React frontend side by side, sharing only an imported module - each part is on a stack of its own, and each stack names its own script in its own file beside `config.turri.yaml`: `unittests-script:` in `config.turri.backend.yaml` for the stack `backend`, in `config.turri.frontend.yaml` for the stack `frontend`.
|
|
142
|
+
6. **Make sure turri finds the script.** Do not link it from the specs - turri finds the unit testing script in the project root (the folder that holds `plain_modules/`) and puts it, whole, into the prompt of the coding agent itself; a link to it from a spec is ignored. It looks, in order, at `config.turri.yaml` in the root (`unittests-script: <path relative to the root>`) - with the chosen stack's `config.turri.<stack>.yaml` over it, then for the one `run_unittests*.sh` in the root, then for the one in `test_scripts/`. Saving the script in `test_scripts/` (or the root) is enough when it is the only one there. If the project keeps it elsewhere, name it in `config.turri.yaml` as `unittests-script:` - and when the project has only a `config.yaml`, copy it to `config.turri.yaml` first (and each `config.<name>.yaml` to `config.turri.<name>.yaml`) and edit the copy; `config.yaml` may be another tool's, and is never written. If the project has modules on different stacks - e.g. a Python backend and a React frontend side by side, sharing only an imported module - each part is on a stack of its own, and each stack names its own script in its own file beside `config.turri.yaml`: `unittests-script:` in `config.turri.backend.yaml` for the stack `backend`, in `config.turri.frontend.yaml` for the stack `frontend`. Name each stack after the module it builds - `config.turri.backend.yaml` for `backend.plain` - and that module is built with it by itself, with nothing to choose; a stack named otherwise is passed as `stack` to `regenerate` in the chat, or `--stack <stack>` on the command line. turri never guesses between two. The script is required: a build without one stops before it starts.
|
|
143
143
|
|
|
144
144
|
## Anti-Patterns
|
|
145
145
|
|
|
@@ -96,7 +96,7 @@ Call the corresponding implementation skills in order. Each skill writes a Bash
|
|
|
96
96
|
|
|
97
97
|
### 6. Check turri finds the scripts
|
|
98
98
|
|
|
99
|
-
turri finds the testing scripts in the project root, not through the specs: first in `config.turri.yaml` (`unittests-script:`, `conformance-tests-script:`, paths relative to the root), then the one `run_unittests*.sh` / `run_conformance_tests*.sh` in the root, then the one of each kind in `test_scripts/`. It puts them into the prompts of the agents that write and run the tests itself, so do not link them from `template/base.plain` (a link to one is ignored). Confirm every generated script is in `test_scripts/` and is the only one of its kind there - or, if the project wants it, that a `config.turri.yaml` at the root names the one to use and the path resolves. A project with parts on different stacks (e.g. a Python backend and a React frontend sharing an imported `base`) keeps one `config.turri.yaml` for what they share and gives each stack its own `config.turri.<stack>.yaml` beside it - `config.turri.backend.yaml`, `config.turri.frontend.yaml` - naming that stack's `unittests-script:` and `conformance-tests-script:`.
|
|
99
|
+
turri finds the testing scripts in the project root, not through the specs: first in `config.turri.yaml` (`unittests-script:`, `conformance-tests-script:`, paths relative to the root), then the one `run_unittests*.sh` / `run_conformance_tests*.sh` in the root, then the one of each kind in `test_scripts/`. It puts them into the prompts of the agents that write and run the tests itself, so do not link them from `template/base.plain` (a link to one is ignored). Confirm every generated script is in `test_scripts/` and is the only one of its kind there - or, if the project wants it, that a `config.turri.yaml` at the root names the one to use and the path resolves. A project with parts on different stacks (e.g. a Python backend and a React frontend sharing an imported `base`) keeps one `config.turri.yaml` for what they share and gives each stack its own `config.turri.<stack>.yaml` beside it - `config.turri.backend.yaml`, `config.turri.frontend.yaml` - naming that stack's `unittests-script:` and `conformance-tests-script:`. Name each stack after the module it builds - `config.turri.backend.yaml` for `backend.plain` - and that module is built with it by itself, with nothing to choose; a stack named otherwise is passed as `stack` to `regenerate` in the chat, or `--stack <stack>` on the command line. When the project has only a `config.yaml`, copy it to `config.turri.yaml` first - and each `config.<name>.yaml` to `config.turri.<name>.yaml` - and write the copies; never write `config.yaml`.
|
|
100
100
|
|
|
101
101
|
Do **not** run `validate` here — keeping this skill light is the whole point. The user can run it when they want validation.
|
|
102
102
|
|
|
@@ -38,7 +38,7 @@ Read what is already there first: an existing `.plain` file, `config.turri.yaml`
|
|
|
38
38
|
|
|
39
39
|
- Templates go in `template/`; import paths omit the `template/` prefix. Resources go in `resources/`.
|
|
40
40
|
- Generated code lands in `plain_modules/<module>/code/`, conformance tests in `plain_modules/<module>/tests/` (both read-only).
|
|
41
|
-
- One module, unless the intent needs two stacks - a web frontend and an API backend. Then two modules side by side, neither requiring the other, sharing what they both mean through one imported module in `template/`. Each is on a stack of its own, whose testing scripts are named in `config.turri.<stack>.yaml` beside `config.turri.yaml` (`unittests-script:`, `conformance-tests-script:`) - `config.turri.backend.yaml`, `config.turri.frontend.yaml`.
|
|
41
|
+
- One module, unless the intent needs two stacks - a web frontend and an API backend. Then two modules side by side, neither requiring the other, sharing what they both mean through one imported module in `template/`. Each is on a stack of its own, whose testing scripts are named in `config.turri.<stack>.yaml` beside `config.turri.yaml` (`unittests-script:`, `conformance-tests-script:`) - `config.turri.backend.yaml`, `config.turri.frontend.yaml`. Name each stack after the module it builds - `config.turri.backend.yaml` for `backend.plain` - and that module is built with it by itself, with nothing to choose; a stack named otherwise is passed as `stack` to `regenerate` in the chat, or `--stack <stack>` on the command line.
|
|
42
42
|
|
|
43
43
|
## Steps
|
|
44
44
|
|