@turri-dev/cli 0.1.13-next.9fe9bb8 → 0.1.14-next.3e3ff53
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/dist/extension.d.ts +5 -1
- package/dist/extension.js +28 -12
- package/dist/new-project.d.ts +7 -0
- package/dist/new-project.js +37 -0
- package/dist/setup.d.ts +1 -0
- package/dist/setup.js +1 -1
- package/dist/skills.d.ts +10 -0
- package/dist/skills.js +20 -3
- package/dist/stack.d.ts +10 -2
- package/dist/stack.js +17 -11
- package/docs/authoring-plain.md +4 -4
- package/docs/installation.md +1 -1
- package/docs/skills-and-gates.md +1 -1
- package/docs/using.md +3 -1
- package/genome/rules/import-modules.md +1 -0
- package/genome/rules/integration-embedded.md +1 -1
- package/genome/skills/break-down-func-spec/SKILL.md +1 -1
- package/genome/skills/refactor-module/SKILL.md +2 -2
- package/package.json +1 -1
- package/genome/skills/check-plain-env/SKILL.md +0 -285
- package/genome/skills/create-import-module/SKILL.md +0 -104
- package/genome/skills/create-requires-module/SKILL.md +0 -109
package/dist/extension.d.ts
CHANGED
|
@@ -24,7 +24,11 @@ dialogs?: AskUserIO): AskUser;
|
|
|
24
24
|
* pick from, several at once, "Something else…" to type instead, "Chat about this" to talk it over in
|
|
25
25
|
* the chat first - so a question with a handful of answers is picked, not typed.
|
|
26
26
|
*/
|
|
27
|
-
|
|
27
|
+
/**
|
|
28
|
+
* ask_user, for the conversation. `afterAnswer` may add to what an answer tells the model - a skill the person picked,
|
|
29
|
+
* to start at once.
|
|
30
|
+
*/
|
|
31
|
+
export declare function chatAskUserTool(dialogs?: AskUserIO, afterAnswer?: (answer: string) => string | undefined): ReturnType<typeof createAskUserTool>;
|
|
28
32
|
/**
|
|
29
33
|
* What a host that runs turri's conversation itself - a desktop app, an editor - is told and asked, where the terminal
|
|
30
34
|
* would draw: the conversation's jobs and its work loop as they are made and as they change, and the dialogs its
|
package/dist/extension.js
CHANGED
|
@@ -25,6 +25,7 @@ import { createDeployState, createDeployTools } from "./release-tools.js";
|
|
|
25
25
|
import { runReleaseAgent } from "./release-agent.js";
|
|
26
26
|
import { createSteeringStore, EVERY_MODULE, moduleNameOf, queueSectionOf, steeringFor } from "./regenerate/steer.js";
|
|
27
27
|
import { askedForCommandLine, refusal, selfInvocation, TOOLS_NOT_CLI } from "./self-invocation.js";
|
|
28
|
+
import { NEW_PROJECT } from "./new-project.js";
|
|
28
29
|
import { describeChoices, describeResolution, modelFor, resolveModel } from "./regenerate/engine.js";
|
|
29
30
|
import { checkPlainFile, checkPlainTargets, formatCheckResults } from "./plain/check.js";
|
|
30
31
|
import { whyFailed } from "./regenerate/diagnose-job.js";
|
|
@@ -57,7 +58,7 @@ import { describeStdlib } from "./stdlib.js";
|
|
|
57
58
|
import { peekKeyListener, runAsk } from "./ask.js";
|
|
58
59
|
import { QuestionDialog } from "./question-dialog.js";
|
|
59
60
|
import { registerStacks } from "./stacks.js";
|
|
60
|
-
import { catalogueLine, createCommandGate, discoverSkills, leftOutNote, startedByCommandOnly } from "./skills.js";
|
|
61
|
+
import { catalogueLine, chosenCommand, createCommandGate, startPicked, discoverSkills, leftOutNote, startedByCommandOnly } from "./skills.js";
|
|
61
62
|
import { supersededByTurri } from "./chat-skills.js";
|
|
62
63
|
import { conversationIdentity } from "./identity.js";
|
|
63
64
|
import { setSurface, surfaceOf } from "./surface.js";
|
|
@@ -170,7 +171,11 @@ function hookPeekKey(ctx) {
|
|
|
170
171
|
* pick from, several at once, "Something else…" to type instead, "Chat about this" to talk it over in
|
|
171
172
|
* the chat first - so a question with a handful of answers is picked, not typed.
|
|
172
173
|
*/
|
|
173
|
-
|
|
174
|
+
/**
|
|
175
|
+
* ask_user, for the conversation. `afterAnswer` may add to what an answer tells the model - a skill the person picked,
|
|
176
|
+
* to start at once.
|
|
177
|
+
*/
|
|
178
|
+
export function chatAskUserTool(dialogs, afterAnswer) {
|
|
174
179
|
let conversation;
|
|
175
180
|
const tool = createAskUserTool((request) => (conversation !== undefined ? askUserVia(conversation, () => { }, undefined, dialogs)(request) : Promise.resolve("No user is available to answer.")), undefined, undefined, undefined, "reply");
|
|
176
181
|
return {
|
|
@@ -181,7 +186,10 @@ export function chatAskUserTool(dialogs) {
|
|
|
181
186
|
],
|
|
182
187
|
async execute(...args) {
|
|
183
188
|
conversation = args[4];
|
|
184
|
-
|
|
189
|
+
const result = await tool.execute(...args);
|
|
190
|
+
const answer = result.details?.answer;
|
|
191
|
+
const note = answer !== undefined && answer !== "" ? afterAnswer?.(answer) : undefined;
|
|
192
|
+
return note !== undefined ? { ...result, content: [...result.content, { type: "text", text: note }] } : result;
|
|
185
193
|
},
|
|
186
194
|
};
|
|
187
195
|
}
|
|
@@ -313,7 +321,7 @@ function turriExtension(pi, host) {
|
|
|
313
321
|
}
|
|
314
322
|
}
|
|
315
323
|
return {
|
|
316
|
-
systemPrompt: conversationSystemPrompt(event.systemPrompt, host, [TOOLS_NOT_CLI, projectConfigBlock(cwd), machineBlock(cwd, machine.now()), rulesBlock()]),
|
|
324
|
+
systemPrompt: conversationSystemPrompt(event.systemPrompt, host, [TOOLS_NOT_CLI, NEW_PROJECT, projectConfigBlock(cwd), machineBlock(cwd, machine.now()), rulesBlock()]),
|
|
317
325
|
};
|
|
318
326
|
});
|
|
319
327
|
// The conversation's agent does not run turri again through its shell, unless the person
|
|
@@ -433,7 +441,17 @@ function turriExtension(pi, host) {
|
|
|
433
441
|
registerBranding(pi, discoveredSkills);
|
|
434
442
|
registerInterviewLog(pi);
|
|
435
443
|
registerJobTools(pi, jobs, cwd);
|
|
436
|
-
|
|
444
|
+
// The skills the person starts themselves - brood, spawn, bud: each has its command, and cultivate starts it only
|
|
445
|
+
// once its command is typed, or picked among the options the conversation offers with ask_user.
|
|
446
|
+
const gate = createCommandGate();
|
|
447
|
+
// A skill picked is started at once, with what the person asked for: they never type its command after picking it.
|
|
448
|
+
registerCheckPlain(pi, cwd, host.dialogs, (answer) => {
|
|
449
|
+
const chosen = chosenCommand(answer, discoveredSkills.skills);
|
|
450
|
+
if (chosen === undefined)
|
|
451
|
+
return undefined;
|
|
452
|
+
gate.typed(chosen);
|
|
453
|
+
return startPicked(chosen, lastUserMessage);
|
|
454
|
+
});
|
|
437
455
|
registerRegenerate(pi, cwd, jobs);
|
|
438
456
|
registerDrift(pi, cwd, jobs);
|
|
439
457
|
registerGlimpse(pi, cwd, jobs);
|
|
@@ -449,10 +467,8 @@ function turriExtension(pi, host) {
|
|
|
449
467
|
return;
|
|
450
468
|
const catalogue = skills.map(catalogueLine).join("\n");
|
|
451
469
|
const pendingNote = leftOutNote(discoveredSkills);
|
|
452
|
-
// The
|
|
453
|
-
//
|
|
454
|
-
// run it with what follows as its context.
|
|
455
|
-
const gate = createCommandGate();
|
|
470
|
+
// The command puts what was typed in the chat, as the person's message, for the model to run it with what follows as
|
|
471
|
+
// its context.
|
|
456
472
|
for (const skill of skills.filter((s) => s.commandOnly)) {
|
|
457
473
|
pi.registerCommand(skill.name, {
|
|
458
474
|
description: `Start ${skill.name}${skill.steps.length > 1 ? ` - ${skill.steps.map((step) => step.name).join(" → ")}` : ""}: /${skill.name} [what you want]`,
|
|
@@ -472,7 +488,7 @@ function turriExtension(pi, host) {
|
|
|
472
488
|
promptSnippet: "cultivate: run a gated skill - its steps and review gates enforced by turri",
|
|
473
489
|
promptGuidelines: [
|
|
474
490
|
"When a request matches a skill listed in cultivate's description, call cultivate instead of doing the work yourself. Doing it by hand skips the review gates.",
|
|
475
|
-
"A skill marked as started only by the person runs only from its command: a message that is its command - /spawn a small server - is the person starting it, so
|
|
491
|
+
"A skill marked as started only by the person runs only from its command: a message that is its command - /spawn a small server - is the person starting it, and so is an option starting with it that they picked in your ask_user - /brood - an interview. Call cultivate for it at once, with what they said as context. Never call cultivate for one otherwise; for a new project, offer the choice as <new-project> says.",
|
|
476
492
|
// pi lists .agents/skills among its own skills too: read and followed by hand, a skill's gates would be skipped.
|
|
477
493
|
"A skill cultivate lists may also be among your own skills, from the project's .agents/skills: run it with cultivate all the same, never by reading its SKILL.md and following it.",
|
|
478
494
|
"cultivate returns the reviewed artifact and a trace of every gate decision. Report the artifact to the user; do not rewrite it.",
|
|
@@ -2063,9 +2079,9 @@ function jobLines(all) {
|
|
|
2063
2079
|
* so it needs no external tool, no API key and no credits - and it is the only honest way
|
|
2064
2080
|
* to answer "is this spec valid?" without generating anything.
|
|
2065
2081
|
*/
|
|
2066
|
-
function registerCheckPlain(pi, cwd, dialogs) {
|
|
2082
|
+
function registerCheckPlain(pi, cwd, dialogs, afterAnswer) {
|
|
2067
2083
|
pi.registerTool(checkPlainTool(cwd));
|
|
2068
|
-
pi.registerTool(chatAskUserTool(dialogs));
|
|
2084
|
+
pi.registerTool(chatAskUserTool(dialogs, afterAnswer));
|
|
2069
2085
|
// The web, for the conversation too - a library's documentation, an API, an error; curl is its
|
|
2070
2086
|
// bash. Run here, like the rendering agents' (see render/web.ts).
|
|
2071
2087
|
for (const tool of createWebTools())
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Starting a project from the conversation: the person says what they want built - "create a small server in Java
|
|
3
|
+
* Spring Boot" - and the chat offers the three ways to start it: brood's interview, spawn's quick start, or writing the
|
|
4
|
+
* ***plain itself, here, with no skill. An option picked that starts with a skill's command starts that skill, as if
|
|
5
|
+
* typed (`chosenCommand` in skills.ts).
|
|
6
|
+
*/
|
|
7
|
+
export declare const NEW_PROJECT: string;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Starting a project from the conversation: the person says what they want built - "create a small server in Java
|
|
3
|
+
* Spring Boot" - and the chat offers the three ways to start it: brood's interview, spawn's quick start, or writing the
|
|
4
|
+
* ***plain itself, here, with no skill. An option picked that starts with a skill's command starts that skill, as if
|
|
5
|
+
* typed (`chosenCommand` in skills.ts).
|
|
6
|
+
*/
|
|
7
|
+
export const NEW_PROJECT = [
|
|
8
|
+
"<new-project>",
|
|
9
|
+
"When the person asks for new software / project ",
|
|
10
|
+
"and there is no ***plain module for it yet, or they ask for a new project, ask them first how to start it, with",
|
|
11
|
+
"ask_user: one question, these three options, word for word, so they know the skills are there:",
|
|
12
|
+
`- "${"/brood - an interview: the stack, the testing and every feature settled with you, each answer regenerated"}"`,
|
|
13
|
+
`- "${"/spawn - something running in minutes: the quick start, asking only what your message leaves open"}"`,
|
|
14
|
+
`- "${"Just do it - no skill: I write it here and start regenerating"}"`,
|
|
15
|
+
"Picked /brood or /spawn: the answer starts it - call cultivate for it at once, with what they said as its context,",
|
|
16
|
+
"never asking them to type its command. Do not ask when their",
|
|
17
|
+
"message says already - they typed the command, or said to just build it, with no skill, without questions: then",
|
|
18
|
+
"start it yourself. Started yourself, how soon the first pulse starts, and something they can try, come first:",
|
|
19
|
+
"1. The stack: the one they named; otherwise one of the standard library's that this machine can build - common,",
|
|
20
|
+
" simple - said in a line. A stack named in a language the machine lacks is written all the same: tell them what to",
|
|
21
|
+
" install, and that turri doctor checks it. Read the stack's template, its language's and its kind's common first.",
|
|
22
|
+
"2. One module - a .plain file in the project's root, named after what it is (server.plain) - importing the stack and",
|
|
23
|
+
" writing only what the module needs beyond it. Two side by side, each on its stack, only when it needs two stacks.",
|
|
24
|
+
"3. config.turri.yaml: module: the new file, and conformance-tests: false unless they asked for conformance tests.",
|
|
25
|
+
" What a config there already settles - a stack, a script, conformance - is kept, not chosen again.",
|
|
26
|
+
"4. The unit testing script: keep one turri already finds; otherwise run cultivate with implement-unit-testing-script",
|
|
27
|
+
" for the stack's language. Conformance tests asked for: their ***test reqs*** - the stack's conformance template -",
|
|
28
|
+
" and cultivate with implement-conformance-testing-script.",
|
|
29
|
+
"5. The first functional spec: the smallest thing that runs and shows something - the server answers GET / with the",
|
|
30
|
+
" app's name, the CLI prints its usage. validate, fix what it finds, and regenerate at once.",
|
|
31
|
+
"6. While it regenerates: the main thing they described, on its happy path, and at most one more - each a functional",
|
|
32
|
+
" spec, with its concepts - then validate and regenerate again. Only what they described: they named only the kind of",
|
|
33
|
+
" software, write none, and invite them to say what it does. No validation or edge cases yet.",
|
|
34
|
+
"Then hand over in a few lines: what you chose, what is regenerating, and the next slices you would write - they say",
|
|
35
|
+
"go on, or what to change, and every change after is ***plain you write and regenerate.",
|
|
36
|
+
"</new-project>",
|
|
37
|
+
].join("\n");
|
package/dist/setup.d.ts
CHANGED
|
@@ -35,6 +35,7 @@ export declare function checkSandbox(wall?: Wall, platform?: NodeJS.Platform): C
|
|
|
35
35
|
export declare function checkProject(project: string, machine?: {
|
|
36
36
|
has?: (command: string) => boolean;
|
|
37
37
|
exists?: (path: string) => boolean;
|
|
38
|
+
jdk?: () => boolean;
|
|
38
39
|
}): Check[];
|
|
39
40
|
/**
|
|
40
41
|
* In a project: each model its config names - with the stack chosen, that stack's over it - tried with one short
|
package/dist/setup.js
CHANGED
|
@@ -66,7 +66,7 @@ export function checkSandbox(wall = sandboxAvailable(), platform = process.platf
|
|
|
66
66
|
*/
|
|
67
67
|
export function checkProject(project, machine = {}) {
|
|
68
68
|
const checks = [];
|
|
69
|
-
for (const { module, needs, missing } of stackOnMachine(project, machine.has)) {
|
|
69
|
+
for (const { module, needs, missing } of stackOnMachine(project, machine.has, machine.jdk)) {
|
|
70
70
|
if (needs.length === 0)
|
|
71
71
|
continue;
|
|
72
72
|
checks.push(missing.length === 0
|
package/dist/skills.d.ts
CHANGED
|
@@ -42,3 +42,13 @@ export declare function createCommandGate(): {
|
|
|
42
42
|
};
|
|
43
43
|
/** What cultivate says of a command-only skill the person did not start. */
|
|
44
44
|
export declare function startedByCommandOnly(name: string): string;
|
|
45
|
+
/**
|
|
46
|
+
* What the model is told when the person picked a skill: start it now, with what they asked for - word for word, as the
|
|
47
|
+
* message that set off the question - so they never type its command after picking it.
|
|
48
|
+
*/
|
|
49
|
+
export declare function startPicked(name: string, request: string): string;
|
|
50
|
+
/**
|
|
51
|
+
* The command-only skill an answer picks: an option offered to the person that starts with its command - `/brood - an
|
|
52
|
+
* interview` - is the person starting it, as if they typed it.
|
|
53
|
+
*/
|
|
54
|
+
export declare function chosenCommand(answer: string, skills: ReadonlyArray<Pick<FoundSkill, "name" | "commandOnly">>): string | undefined;
|
package/dist/skills.js
CHANGED
|
@@ -61,8 +61,8 @@ export function discoverSkills(packageRoot, projectDir) {
|
|
|
61
61
|
/** A skill's line in cultivate's list: a project's says so, and where it is. */
|
|
62
62
|
export function catalogueLine(skill) {
|
|
63
63
|
const whose = skill.origin === "turri" ? "" : ` (this project's, ${skill.origin.project})`;
|
|
64
|
-
// One the person starts themselves
|
|
65
|
-
const byCommand = skill.commandOnly ? ` Started only by the person, with /${skill.name}
|
|
64
|
+
// One the person starts themselves - typed, or picked among the options the model offers: never run unasked.
|
|
65
|
+
const byCommand = skill.commandOnly ? ` Started only by the person, with /${skill.name} or by picking it when you offer it with ask_user: never run it unasked.` : "";
|
|
66
66
|
return `- ${skill.name}${whose}: ${skill.description}${byCommand}`;
|
|
67
67
|
}
|
|
68
68
|
/** What is said under the list about the skills it leaves out. */
|
|
@@ -88,5 +88,22 @@ export function createCommandGate() {
|
|
|
88
88
|
}
|
|
89
89
|
/** What cultivate says of a command-only skill the person did not start. */
|
|
90
90
|
export function startedByCommandOnly(name) {
|
|
91
|
-
return `Not started: ${name} is started only by the person, with /${name}
|
|
91
|
+
return `Not started: ${name} is started only by the person, with /${name} or by picking an option starting /${name} that you offer them with ask_user. For a new project, ask them as <new-project> says; otherwise do what they asked yourself, in the conversation.`;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* What the model is told when the person picked a skill: start it now, with what they asked for - word for word, as the
|
|
95
|
+
* message that set off the question - so they never type its command after picking it.
|
|
96
|
+
*/
|
|
97
|
+
export function startPicked(name, request) {
|
|
98
|
+
const asked = request.trim();
|
|
99
|
+
return (`They picked ${name}, and it may start now: call cultivate with skill "${name}" at once, before anything else, and never ask them to type /${name}. ` +
|
|
100
|
+
`Pass as its context everything they have told you${asked !== "" ? `, starting with what they asked for, word for word: ${JSON.stringify(asked)}` : ""}.`);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* The command-only skill an answer picks: an option offered to the person that starts with its command - `/brood - an
|
|
104
|
+
* interview` - is the person starting it, as if they typed it.
|
|
105
|
+
*/
|
|
106
|
+
export function chosenCommand(answer, skills) {
|
|
107
|
+
const typed = /^\s*\/([\w-]+)/.exec(answer)?.[1];
|
|
108
|
+
return skills.find((skill) => skill.commandOnly && skill.name === typed)?.name;
|
|
92
109
|
}
|
package/dist/stack.d.ts
CHANGED
|
@@ -19,8 +19,11 @@ export declare function projectImplementationReqs(project: string): Array<{
|
|
|
19
19
|
module: string;
|
|
20
20
|
reqs: string[];
|
|
21
21
|
}>;
|
|
22
|
-
/**
|
|
23
|
-
|
|
22
|
+
/**
|
|
23
|
+
* What a project's modules need of this machine, and which of it the machine lacks - `has` finding a command, and on
|
|
24
|
+
* macOS a JDK behind `java` (`toolchainCheck`), asked at most once.
|
|
25
|
+
*/
|
|
26
|
+
export declare function stackOnMachine(project: string, has?: (command: string) => boolean, jdk?: () => boolean): Array<{
|
|
24
27
|
module: string;
|
|
25
28
|
needs: StackNeed[];
|
|
26
29
|
missing: StackNeed[];
|
|
@@ -34,6 +37,11 @@ export declare const STDLIB_LANGUAGES: Array<{
|
|
|
34
37
|
language: string;
|
|
35
38
|
commands: string[];
|
|
36
39
|
}>;
|
|
40
|
+
/**
|
|
41
|
+
* Whether a toolchain's command is really there: `has` finds it - and Java has a JDK behind it as well, for macOS keeps a
|
|
42
|
+
* `/usr/bin/java` that only asks to install one.
|
|
43
|
+
*/
|
|
44
|
+
export declare function toolchainCheck(has: (command: string) => boolean, jdk?: () => boolean): (command: string) => boolean;
|
|
37
45
|
/** Which of the standard library's languages this machine can build and test now, and which it lacks a command of. */
|
|
38
46
|
export declare function languagesOnMachine(has?: (command: string) => boolean): {
|
|
39
47
|
ready: typeof STDLIB_LANGUAGES;
|
package/dist/stack.js
CHANGED
|
@@ -57,11 +57,16 @@ export function projectImplementationReqs(project) {
|
|
|
57
57
|
}
|
|
58
58
|
/** Whether a command is on this machine's PATH. */
|
|
59
59
|
const onPath = (command) => onThisMachine(command) !== undefined;
|
|
60
|
-
/**
|
|
61
|
-
|
|
60
|
+
/**
|
|
61
|
+
* What a project's modules need of this machine, and which of it the machine lacks - `has` finding a command, and on
|
|
62
|
+
* macOS a JDK behind `java` (`toolchainCheck`), asked at most once.
|
|
63
|
+
*/
|
|
64
|
+
export function stackOnMachine(project, has = onPath, jdk = jdkHere) {
|
|
65
|
+
let found;
|
|
66
|
+
const here = toolchainCheck(has, () => (found ??= jdk()));
|
|
62
67
|
return projectImplementationReqs(project).map(({ module, reqs }) => {
|
|
63
68
|
const needs = stackOf(reqs.join("\n"));
|
|
64
|
-
return { module, needs, missing: needs.filter((need) => !need.commands.some(
|
|
69
|
+
return { module, needs, missing: needs.filter((need) => !need.commands.some(here)) };
|
|
65
70
|
});
|
|
66
71
|
}
|
|
67
72
|
/**
|
|
@@ -76,17 +81,18 @@ export const STDLIB_LANGUAGES = [
|
|
|
76
81
|
{ language: "Rust", commands: ["cargo"] },
|
|
77
82
|
{ language: "Java", commands: ["java"] },
|
|
78
83
|
];
|
|
84
|
+
/** Whether this machine has a JDK behind `java`: anywhere but macOS, java found says so; on macOS, `java_home` finding one. */
|
|
85
|
+
function jdkHere() {
|
|
86
|
+
return process.platform !== "darwin" || spawnSync("/usr/libexec/java_home", [], { stdio: "ignore" }).status === 0;
|
|
87
|
+
}
|
|
79
88
|
/**
|
|
80
|
-
* Whether a toolchain's command is really there:
|
|
81
|
-
* asks to install
|
|
89
|
+
* Whether a toolchain's command is really there: `has` finds it - and Java has a JDK behind it as well, for macOS keeps a
|
|
90
|
+
* `/usr/bin/java` that only asks to install one.
|
|
82
91
|
*/
|
|
83
|
-
function
|
|
84
|
-
|
|
85
|
-
return false;
|
|
86
|
-
if (command === "java" && process.platform === "darwin")
|
|
87
|
-
return spawnSync("/usr/libexec/java_home", [], { stdio: "ignore" }).status === 0;
|
|
88
|
-
return true;
|
|
92
|
+
export function toolchainCheck(has, jdk = jdkHere) {
|
|
93
|
+
return (command) => has(command) && (command !== "java" || jdk());
|
|
89
94
|
}
|
|
95
|
+
const toolchainHere = toolchainCheck(onPath);
|
|
90
96
|
/** Which of the standard library's languages this machine can build and test now, and which it lacks a command of. */
|
|
91
97
|
export function languagesOnMachine(has = toolchainHere) {
|
|
92
98
|
const ready = STDLIB_LANGUAGES.filter(({ commands }) => commands.every(has));
|
package/docs/authoring-plain.md
CHANGED
|
@@ -122,7 +122,7 @@ The ones that shape \*\*\*plain most:
|
|
|
122
122
|
|
|
123
123
|
## Writing \*\*\*plain with turri
|
|
124
124
|
|
|
125
|
-
In a conversation (`turri`), say what you want and the matching skill runs - but the three that start a project or a feature you start yourself, by their commands: `/brood`, `/spawn` or `/bud`, with what you want after it. Two build a project from scratch, and write each \*\*\*plain change as a git diff that is built in the background the moment it is written. `brood` interviews you one question at a time and writes \*\*\*plain as you answer; `spawn`, its quick twin, gets something running as soon as it can - the smallest thing that runs first, then the product a slice at a time - asking only what your message leaves open:
|
|
125
|
+
In a conversation (`turri`), say what you want and the matching skill runs - but the three that start a project or a feature you start yourself, by their commands: `/brood`, `/spawn` or `/bud`, with what you want after it. Asked for new software without one - `create a small server in Java Spring Boot` - turri offers `/brood`, `/spawn` or `Just do it`, with no skill; a skill picked there starts at once with what you asked for, and Just do it starts the project in the conversation as `spawn` would, writing the module, its config and its unit testing script, then the smallest thing that runs, regenerated at once, and the first slices of what you described (see [using turri](using.md)). Two build a project from scratch, and write each \*\*\*plain change as a git diff that is built in the background the moment it is written. `brood` interviews you one question at a time and writes \*\*\*plain as you answer; `spawn`, its quick twin, gets something running as soon as it can - the smallest thing that runs first, then the product a slice at a time - asking only what your message leaves open:
|
|
126
126
|
|
|
127
127
|
| skill | phases | how a \*\*\*plain change is written |
|
|
128
128
|
| --- | --- | --- |
|
|
@@ -145,13 +145,13 @@ Every skill in `genome/skills/` can be run from a conversation; most are also us
|
|
|
145
145
|
|
|
146
146
|
| to… | skills |
|
|
147
147
|
| --- | --- |
|
|
148
|
-
| start a project | `brood`, `spawn` |
|
|
148
|
+
| start a project | `brood`, `spawn` - or ask, and choose `Just do it` for no skill |
|
|
149
149
|
| make rough \*\*\*plain build | `mature` |
|
|
150
150
|
| add to \*\*\*plain | `bud`, `add-functional-spec`, `add-functional-specs`, `add-concept`, `add-implementation-requirement`, `add-test-requirement`, `add-acceptance-test`, `add-resource`, `add-template` |
|
|
151
151
|
| check what is written | `analyze-if-func-spec-too-complex`, `analyze-2-func-specs`, `analyze-func-specs`, `resolve-section-ownership` |
|
|
152
152
|
| fix what is written | `break-down-func-spec`, `resolve-spec-conflict`, `debug-specs` |
|
|
153
|
-
| restructure modules | `
|
|
154
|
-
| set up the project to build | `implement-unit-testing-script`, `implement-conformance-testing-script
|
|
153
|
+
| restructure modules | `refactor-module`, `consolidate-concepts` |
|
|
154
|
+
| set up the project to build | `implement-unit-testing-script`, `implement-conformance-testing-script` |
|
|
155
155
|
|
|
156
156
|
`debug-specs` is the one for a running application that misbehaves: it reads the generated code to find the cause, traces it back to \*\*\*plain, and fixes only the `.plain` files.
|
|
157
157
|
|
package/docs/installation.md
CHANGED
|
@@ -54,7 +54,7 @@ In a project - a folder with `config.turri.yaml` (or the old `config.yaml`) or `
|
|
|
54
54
|
|
|
55
55
|
| what | |
|
|
56
56
|
| --- | --- |
|
|
57
|
-
| each module's toolchains | what its implementation reqs name - Python, Node, Java, Go, Rust, C/C++, Zig, ... - against this machine's PATH: the agents use this machine's toolchains and install none, so a missing one is yours to install |
|
|
57
|
+
| each module's toolchains | what its implementation reqs name - Python, Node, Java, Go, Rust, C/C++, Zig, ... - against this machine's PATH - and Java against a JDK, for macOS keeps a `/usr/bin/java` that only asks to install one: the agents use this machine's toolchains and install none, so a missing one is yours to install |
|
|
58
58
|
| the testing scripts | what in them this machine does not have: a program named by its whole path that is not here (a macOS-only `/usr/libexec/java_home` on Linux), a toolchain they run that is not on the PATH |
|
|
59
59
|
| the models | each model `config.turri.yaml` names - `harness-model`, `coding-model`, `judge-model`, every role under `models:` - sent one short request through [pi](https://pi.dev), with the credentials turri's agents will use, for any provider [pi](https://pi.dev) supports. One that answers is ready; for one that does not, it says what the provider said and what to do: the credentials [pi](https://pi.dev) reads for that provider (`AWS_PROFILE` and `AWS_REGION` for Amazon Bedrock, `OPENAI_API_KEY`, `/login`, ...), model access in the Bedrock console, the closest model ids [pi](https://pi.dev) knows for one it does not. `--harness-model`, `--coding-model` (`--model`) and `--judge-model` name models as the config's keys, as `turri regenerate` reads them - checked in a project or not. `--no-models` skips it |
|
|
60
60
|
|
package/docs/skills-and-gates.md
CHANGED
|
@@ -37,7 +37,7 @@ metadata:
|
|
|
37
37
|
|
|
38
38
|
A skill declares its steps with a `## Steps` section whose `###` headings are the steps; without it the whole skill is one step, which is what most skills are. Steps are never inferred from `###` headings elsewhere in the body - prose skills are full of those. Each gate becomes a conditional edge after its step; a single-step skill is gated by naming the skill itself. None of the skills turri ships declares a gate today - `brood`'s phases end on the person's confirmation alone - but a project's own skill can.
|
|
39
39
|
|
|
40
|
-
`command-only: true` under `metadata:` keeps a skill from the model: it is started only by the person, with its slash command - `/brood`, `/spawn` and `/bud` are. turri registers the command for every such skill, the project's own too. Typed, the command puts what was typed in the chat as the person's message, and lets `cultivate` start the skill once; the run starting uses that up, so the model cannot start it again later on its own. Without
|
|
40
|
+
`command-only: true` under `metadata:` keeps a skill from the model: it is started only by the person, with its slash command - `/brood`, `/spawn` and `/bud` are. turri registers the command for every such skill, the project's own too. Typed, the command puts what was typed in the chat as the person's message, and lets `cultivate` start the skill once - and so does an option starting with the command, `/brood - an interview`, picked in the conversation's `ask_user`, which is how the chat offers `/brood`, `/spawn` or starting a project itself with no skill (`chosenCommand` in `src/skills.ts`, `src/new-project.ts`). The answer tells the model to call `cultivate` for it at once, with the person's request word for word as its context (`startPicked`), so they never type the command after picking it; the run starting uses that up, so the model cannot start it again later on its own. Without either, `cultivate` refuses it and the model is told how the person starts it - for a new project, by the choice it offers; the skill's line in `cultivate`'s list says so too.
|
|
41
41
|
|
|
42
42
|
`paced: false` under `metadata:` lets a skill write \*\*\*plain changes one after another on its own: `change_plain` otherwise refuses a new change until the user has answered a question since the last one - one answer, one change, as an interview writes them. `spawn` is unpaced: the tool tells it so, and after each change to write the next at once, not to carry on with an interview it does not have.
|
|
43
43
|
|
package/docs/using.md
CHANGED
|
@@ -9,7 +9,9 @@ turri
|
|
|
9
9
|
|
|
10
10
|
That is [pi](https://pi.dev), with turri's tools, skills and themes loaded - `turri` alone, or with [pi](https://pi.dev)'s options (`turri -c`, `turri --harness-model <m>`). It opens with turri's own banner - the jellyfish it is named after, regenerating until the conversation fills the screen: a pixel polyp on the sea floor grows into the jellyfish - a bell from pink down to indigo, an orange gut, neon tentacles - which swims a while, sinks back to the floor, and the polyp grows again, round and round: a picture in a terminal that shows images (Kitty, Ghostty, WezTerm, Warp, iTerm2), and drawn at a third of the size in half blocks in any other, tmux included (`src/jellyfish.ts` draws it, ported from the design system's pixel kit, `scripts/jellyfish-frames.mjs` writes the pictures) - turri's version (and, run from a clone, the commit it is on, and whether the clone has changes) and where its settings are changed - and only when something is wrong, skills that failed to load or agents' commands less fenced than they should be - rather than [pi](https://pi.dev)'s startup header and resource list; `turri --verbose` shows those too. [pi](https://pi.dev)'s "Update Available" notice is left out - turri's [pi](https://pi.dev) is a pinned dependency `pi update` never touches - and the terminal's tab reads `🪼 turri - <folder>`, not [pi](https://pi.dev)'s `π`. Talk to it: ask it to add a feature to \*\*\*plain, regenerate a module, fix a module's \*\*\*plain. It decides which skill or tool applies - but not *how* a skill runs: `cultivate` hands the work to the gated runner, which owns the steps, the gates and the judges (see [skills and gates](skills-and-gates.md)).
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
To start a project, just say what you want - `create a small server in Java Spring Boot` - and turri asks how to start it, with three choices: `/brood`, the interview; `/spawn`, the quick start; or `Just do it`, no skill. A skill picked there starts at once, with what you asked for as its context - nothing to type again; saying so in your message - `just build it`, or the command itself - skips the question. Just do it starts the project in the conversation: turri takes the stack you named, or chooses one this machine can build, and says it in a line; writes the module importing the stack from the standard library, `config.turri.yaml` (conformance tests off unless you ask for them) and the unit testing script (with `implement-unit-testing-script`, unless there is one); then the smallest thing that runs, regenerated at once, and while it regenerates the main thing you described and at most one more, on their happy path - nothing you did not describe. You say `go on`, or what to change, and every change after is \*\*\*plain it writes and regenerates (`src/new-project.ts`).
|
|
13
|
+
|
|
14
|
+
Three skills you start yourself, by their commands or by picking them when turri offers them - it never starts them on its own: `/brood`, `/spawn` and `/bud`, each with what you want after it - `/spawn a URL shortener in Go`. The skills that build from scratch:
|
|
13
15
|
|
|
14
16
|
| skill | how it works |
|
|
15
17
|
| --- | --- |
|
|
@@ -11,6 +11,7 @@ When creating or editing a `.plain` file that uses `import` or is intended to be
|
|
|
11
11
|
- It must **not** contain `***functional specs***`
|
|
12
12
|
- It must **not** use `requires` in its frontmatter
|
|
13
13
|
- It may optionally `import` other import modules for layered reuse
|
|
14
|
+
- A project's import modules live in `template/`, which imports resolve from: `import: [shared_api]` is `template/shared_api.plain`, the `template/` prefix left out
|
|
14
15
|
|
|
15
16
|
## What import does
|
|
16
17
|
- `import` pulls in `***definitions***`, `***implementation reqs***`, and `***test reqs***` from the target module
|
|
@@ -46,7 +46,7 @@ This keeps the integration self-contained in one directory the user can `cd` int
|
|
|
46
46
|
|
|
47
47
|
Run host discovery **before** the first interview question. Treat the results as ground truth for everything that follows.
|
|
48
48
|
|
|
49
|
-
1. **Locate the host's `.plain` setup (if any).** Look for an existing `.plain` file or directory, a `plain_modules/`, a `resources/`, and a `test_scripts/`. If the host already has a `.plain` setup, **adopt it verbatim**: the new integration module lands inside the existing `plain_source_dir` and `requires` the relevant base module (
|
|
49
|
+
1. **Locate the host's `.plain` setup (if any).** Look for an existing `.plain` file or directory, a `plain_modules/`, a `resources/`, and a `test_scripts/`. If the host already has a `.plain` setup, **adopt it verbatim**: the new integration module lands inside the existing `plain_source_dir` and `requires` the relevant base module (following `requires-modules.md`). Do not create a parallel layout.
|
|
50
50
|
2. **Read every existing integration in the host.** For each one, extract: intent and scope, host base class / interface / protocol the integration subclasses or implements, package path and naming convention, configuration pattern (env vars, settings module, secret manager), error / exception hierarchy, logging / metrics / tracing conventions, testing pattern (live vs recorded vs mocked, fixture location, conformance-test layout). The new integration must follow the same patterns unless the user explicitly opts out.
|
|
51
51
|
3. **Capture findings in the `host-codebase` concept.** Cite the existing integration by file path so the reasoning is auditable. The concept holds, as facts:
|
|
52
52
|
- Host codebase root (absolute or project-relative path)
|
|
@@ -180,7 +180,7 @@ Define the component concept in `***definitions***` (via `add-concept`) before t
|
|
|
180
180
|
- A :Report: is exported to PDF using :PdfRenderer:, embedding charts rendered from :Report: data.
|
|
181
181
|
```
|
|
182
182
|
|
|
183
|
-
The extracted component spec and the rewritten original must together still cover 100% of the original functionality (step 6): the original must **reference** the component, never re-describe it. Keep the component's technology and architecture choices in `***implementation reqs***`, not in the functional spec. If the component is a self-contained subsystem or will be reused across modules, promote it to its own module
|
|
183
|
+
The extracted component spec and the rewritten original must together still cover 100% of the original functionality (step 6): the original must **reference** the component, never re-describe it. Keep the component's technology and architecture choices in `***implementation reqs***`, not in the functional spec. If the component is a self-contained subsystem or will be reused across modules, promote it to its own module - one that `requires` it, following `requires-modules.md`, or with `refactor-module` - instead of a sibling spec.
|
|
184
184
|
|
|
185
185
|
## Preserving Chronological Order
|
|
186
186
|
|
|
@@ -75,7 +75,7 @@ For each concept, determine where it belongs:
|
|
|
75
75
|
| Used across multiple modules in the chain | Define in the first module that uses it, and add to that module's `exported_concepts` |
|
|
76
76
|
| Already defined in an import template | Leave in the import — no change needed |
|
|
77
77
|
|
|
78
|
-
If many concepts are shared across all modules, consider creating a new import module (template) to hold them. This avoids long `exported_concepts` lists and keeps definitions centralized.
|
|
78
|
+
If many concepts are shared across all modules, consider creating a new import module (template) to hold them. This avoids long `exported_concepts` lists and keeps definitions centralized. Write it following `import-modules.md`.
|
|
79
79
|
|
|
80
80
|
### 2d. Assign implementation reqs and test reqs
|
|
81
81
|
|
|
@@ -102,7 +102,7 @@ Create the new modules in order, from the base of the chain upward.
|
|
|
102
102
|
|
|
103
103
|
### 3a. Create or update the import template (if needed)
|
|
104
104
|
|
|
105
|
-
If shared definitions or reqs are being moved to a new import template, create it first
|
|
105
|
+
If shared definitions or reqs are being moved to a new import template, create it first, following `import-modules.md`.
|
|
106
106
|
|
|
107
107
|
### 3b. Create the base module
|
|
108
108
|
|
package/package.json
CHANGED
|
@@ -1,285 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: check-plain-env
|
|
3
|
-
description: >-
|
|
4
|
-
Read a ***plain project — its `.plain` files, `test_scripts/`,
|
|
5
|
-
and `resources/` — and determine every command-line tool, runtime, package
|
|
6
|
-
manager, and external service the project needs on the host machine. Probe
|
|
7
|
-
the host for each one, then emit a `PASS` / `FAIL` report listing what's
|
|
8
|
-
installed (with versions), what's missing, and concrete OS-specific install
|
|
9
|
-
commands for the gaps. Run this any time someone is about to render, test,
|
|
10
|
-
or onboard onto a ***plain project for the first time.
|
|
11
|
-
allowed-tools: [read, bash, ls, grep, find]
|
|
12
|
-
---
|
|
13
|
-
|
|
14
|
-
# Check Plain Env
|
|
15
|
-
|
|
16
|
-
This skill answers one question: **does the host machine have everything the ***plain project needs to render and test?** It reads the project, derives the requirement list, probes the machine, and returns a report. It never installs anything itself — that decision belongs to the user.
|
|
17
|
-
|
|
18
|
-
## When to run
|
|
19
|
-
|
|
20
|
-
- **First time you open a ***plain project on a new machine** — before the first `turri regenerate <module>.plain` or `./test_scripts/run_unittests*.sh` invocation. This is the most common case.
|
|
21
|
-
- **Once a new project's stack is settled** — at the end of `brood`'s stack phase, so the first build does not stop on a missing toolchain.
|
|
22
|
-
- **After `bud`** — when the new feature brought in a new dependency (a new framework, a new service, a new package manager) the project didn't need before.
|
|
23
|
-
- **Before a real render** — alongside `validate`. `validate` answers "do the specs parse?"; this skill answers "can this machine render and test them?". Both should pass.
|
|
24
|
-
- **Onboarding** — when a teammate (or CI runner, or fresh dev container) is about to start using the project.
|
|
25
|
-
- **Debugging a confusing failure** — when a test script exits with "command not found", "module not found", "could not connect", or similar before any test runs.
|
|
26
|
-
- **On demand** — whenever the user asks "what do I need to install for this project?".
|
|
27
|
-
|
|
28
|
-
This skill is **read-only and observational**. It does not edit `.plain` files or scripts, and it never invokes install commands on the user's behalf.
|
|
29
|
-
|
|
30
|
-
## What this skill does NOT do
|
|
31
|
-
|
|
32
|
-
- It does **not** install anything. It only suggests install commands and lets the user run them.
|
|
33
|
-
- It does **not** generate scripts or change project files. Use the `implement-*-testing-script` skills for that.
|
|
34
|
-
- It does **not** start services (databases, brokers, Docker daemons). It only checks whether the right binaries and (optionally) running endpoints are reachable.
|
|
35
|
-
- It does **not** validate the specs themselves (syntax, structure, complexity). Use the `validate` tool for that — the two are complementary.
|
|
36
|
-
- It does **not** print secrets. Check whether an env var is *set* (e.g. `printenv FOO >/dev/null && echo set || echo missing`), never echo its value.
|
|
37
|
-
|
|
38
|
-
## Workflow
|
|
39
|
-
|
|
40
|
-
The skill is a **derive → probe → report** pass. It does not stop at the first missing tool; it surfaces every gap so the user gets a single complete shopping list.
|
|
41
|
-
|
|
42
|
-
### Step 1 — Inventory the project
|
|
43
|
-
|
|
44
|
-
1. **Detect the host OS** first. Run `uname -s 2>/dev/null || echo "$OS"`. Use the result to (a) pick the right script extensions to inspect (`.sh` vs `.ps1`) and (b) pick the right install commands later (Homebrew, apt, dnf, pacman, choco, scoop, winget).
|
|
45
|
-
2. **List every `.plain` file** in the repo root and in any subdirectory that contains them. Note which are top modules (not `requires`-ed by anything) and which are import modules (under `template/`).
|
|
46
|
-
3. **List every script under `test_scripts/`** (both `.sh` and `.ps1` — projects can ship both).
|
|
47
|
-
4. **List every file under `resources/`** if the directory exists. Schemas and protocol files there often imply running services (e.g. a Postgres `.sql` schema implies a Postgres server).
|
|
48
|
-
5. Print a one-line inventory summary, e.g. `Detected: 2 top modules (backend/api.plain, frontend/web.plain), 4 test_scripts, 3 resources. Host: Darwin.`
|
|
49
|
-
|
|
50
|
-
### Step 2 — Derive the requirement list (at runtime, from what the project actually says)
|
|
51
|
-
|
|
52
|
-
The requirement list is **not pre-baked into this skill**. There is no catalog of "things to always check". The list is derived for each project from the project itself, by reading the specs and scripts in front of you and classifying every signal into one of the categories below.
|
|
53
|
-
|
|
54
|
-
#### What to check vs. what *not* to check
|
|
55
|
-
|
|
56
|
-
The single most important rule:
|
|
57
|
-
|
|
58
|
-
> **Check only the layers a package manager cannot install.** Anything `pip install -r requirements.txt`, `npm ci`, `mvn install`, `cargo fetch`, `go mod download`, `bundle install`, `composer install`, etc. would resolve is **out of scope** for this skill. It will be installed by the project's own scripts the moment they run. Checking it here is wasted work and creates false-negative reports the moment the manifest changes.
|
|
59
|
-
|
|
60
|
-
Concretely:
|
|
61
|
-
|
|
62
|
-
- ❌ **Do NOT check individual language packages** — not `torch`, not `requests`, not `numpy`, not `FastAPI`, not `express`, not `react`, not a specific JAR in `pom.xml`, not a specific gem in `Gemfile`, not a specific crate in `Cargo.toml`. The language's package manager handles all of it. The same rule applies in every language — Python, Node.js, Java, Go, Rust, Ruby, PHP, .NET, Dart, etc.
|
|
63
|
-
- ✅ **Do check the layers below the package manager**, which fall into four categories. Build the requirement list at runtime by walking through each category and adding only what the project actually needs.
|
|
64
|
-
|
|
65
|
-
#### Category 1 — Language toolchains and their package managers
|
|
66
|
-
|
|
67
|
-
For each language the project uses, the *toolchain* must be on the host — the package manager cannot install itself. Decide which toolchains apply by reading the specs and scripts:
|
|
68
|
-
|
|
69
|
-
- **Signals in `.plain` files**: phrases like `should be in Python`, `should be in Java`, `should be in Go` in `***implementation reqs***`. Find the language; ignore the framework (the framework is a package, not a toolchain).
|
|
70
|
-
- **Signals in `test_scripts/`** (more reliable, because scripts are the executable contract): which interpreter / build tool is invoked at the top level. `python3` / `pip` → Python toolchain. `node` / `npm` / `pnpm` / `yarn` → Node.js toolchain. `mvn` / `gradle` → JDK + Maven/Gradle. `go` → Go toolchain. `cargo` → Rust toolchain. `dotnet` → .NET SDK. `ruby` / `bundle` → Ruby + Bundler. `php` / `composer` → PHP + Composer. `dart` / `flutter` → Dart/Flutter SDK.
|
|
71
|
-
|
|
72
|
-
For each detected toolchain, probe both the interpreter/compiler **and** its package manager:
|
|
73
|
-
|
|
74
|
-
- Python → `python3 --version` + `pip --version`.
|
|
75
|
-
- Node.js → `node --version` + `npm --version` (or `pnpm`/`yarn` if the scripts use them).
|
|
76
|
-
- Java → `java -version` + `javac -version` + `mvn -version` (or `gradle --version`).
|
|
77
|
-
- Go → `go version`.
|
|
78
|
-
- Rust → `cargo --version` + `rustc --version`.
|
|
79
|
-
- .NET → `dotnet --version`.
|
|
80
|
-
- Ruby → `ruby --version` + `bundle --version`.
|
|
81
|
-
- PHP → `php --version` + `composer --version`.
|
|
82
|
-
- Flutter / Dart → `flutter --version` + `dart --version`.
|
|
83
|
-
|
|
84
|
-
**That's where the language-package check stops.** Once `python3` and `pip` are on the host, `pip install -r requirements.txt` will handle `torch`, `numpy`, `requests`, etc. when the test script runs. Don't probe for them here.
|
|
85
|
-
|
|
86
|
-
#### Category 2 — External services
|
|
87
|
-
|
|
88
|
-
Services that run as separate processes / daemons and cannot be installed by a language package manager. Identify them at runtime by reading the specs and config:
|
|
89
|
-
|
|
90
|
-
- **Signals**: a `.plain` file names a service the implementation talks to (database, cache, queue, broker, orchestrator). A `resources/*.sql` schema implies a database. A `docker-compose.yml` (if present) lists services explicitly.
|
|
91
|
-
- For each service found, probe **both** that its CLI is available **and** — when possible — that the service is reachable. "Binary present but service not running" should report as `WARN`, not `FAIL`; the user may want to start it on demand.
|
|
92
|
-
|
|
93
|
-
No catalog — if the spec says "uses PostgreSQL" you check Postgres, if it says "uses Redis" you check Redis, if it says "uses some-niche-service" you look up its CLI on the fly and probe that. Don't pretend the list is closed.
|
|
94
|
-
|
|
95
|
-
#### Category 3 — System binaries that language packages wrap
|
|
96
|
-
|
|
97
|
-
Some language packages are thin wrappers around a system binary that **`pip install` / `npm install` cannot install for you**. The package manager installs the wrapper; the wrapped binary still has to be on the host separately. This is the most commonly missed category.
|
|
98
|
-
|
|
99
|
-
Decide at runtime by reading the workflow described in the specs. If the workflow can't physically run without an external tool, that tool needs to be checked even though the language wrapper around it is in `requirements.txt`. Examples of the *pattern* (not an exhaustive list — derive yours from the actual project):
|
|
100
|
-
|
|
101
|
-
- The spec describes OCR → a wrapper like `pytesseract` is in `requirements.txt`, but the host still needs the `tesseract` binary and the language data files.
|
|
102
|
-
- The spec describes video/audio processing → `ffmpeg-python` is in the manifest, but the `ffmpeg` binary itself must be on the host.
|
|
103
|
-
- The spec describes PDF extraction → a Python wrapper is in the manifest, but the `pdftotext` / `pdftoppm` binaries from poppler-utils are still required.
|
|
104
|
-
- The spec describes headless-browser e2e testing → the npm package is in `package.json`, but the actual browser binaries (Chromium, Firefox, WebKit) must be downloaded onto the host separately.
|
|
105
|
-
- The spec describes shelling out to any external CLI from generated code → that CLI must be on the host.
|
|
106
|
-
|
|
107
|
-
**Heuristic for spotting Category 3:** if removing the package from `requirements.txt` would still leave a binary on the host that the project depends on, the binary belongs in Category 3. If the package *is* the only thing the project needs, it's package-manager territory and out of scope.
|
|
108
|
-
|
|
109
|
-
Whenever the test scripts themselves invoke a non-language binary (`ffmpeg`, `psql`, `docker`, `tesseract`, ...), that binary is automatically Category 3 — the script is the executable contract. **Always reconcile the spec-derived list against the scripts** before probing.
|
|
110
|
-
|
|
111
|
-
#### Category 4 — Hardware, drivers, accelerators
|
|
112
|
-
|
|
113
|
-
Things the OS — not any package manager — must provide. The only category where the chain runs deeper than "is this binary on PATH?".
|
|
114
|
-
|
|
115
|
-
- **Signals**: a spec mentions GPU acceleration, local model training/fine-tuning, real-time inference, CUDA, MPS / Apple Silicon, hardware-accelerated video encoding, etc.
|
|
116
|
-
- The check is **layered**, and each layer fails independently. Probe each one and surface each result separately, in order, so the user sees exactly where the chain breaks:
|
|
117
|
-
1. **Driver present?** (e.g. `nvidia-smi` succeeds.)
|
|
118
|
-
2. **Device visible?** (e.g. `nvidia-smi -L` lists ≥ 1 GPU.)
|
|
119
|
-
3. **Accelerator SDK installed?** (e.g. `nvcc --version` for the CUDA toolkit.)
|
|
120
|
-
4. **Acceleration libraries discoverable?** (e.g. cuDNN via `ldconfig -p \| grep libcudnn` on Linux.)
|
|
121
|
-
5. **The runtime can actually see the accelerator?** (e.g. a one-line Python probe through whatever framework the spec named: `python -c "import torch; print(torch.cuda.is_available(), torch.version.cuda)"`.) This particular layer **does** require the language package, so run it after the test scripts have had a chance to install dependencies — or surface it as "can be verified after `pip install`" if dependencies aren't installed yet.
|
|
122
|
-
6. **Version compatibility.** If the runtime reports a different CUDA major version than the toolkit, surface a `WARN`. Driver / toolkit / runtime version mismatches are the most common silent GPU bug.
|
|
123
|
-
|
|
124
|
-
The same shape applies to other accelerators: Apple Silicon / MPS (`uname -m`, `sw_vers`, framework probe), AMD ROCm (`rocminfo`), Intel oneAPI, etc. Look up the relevant probe at runtime from the spec's terminology.
|
|
125
|
-
|
|
126
|
-
Worked example — the GPU case:
|
|
127
|
-
|
|
128
|
-
> A `.plain` file declares: `:Implementation: should fine-tune :BaseModel: on the user's local hardware using PyTorch. Training should use available GPUs when present.`
|
|
129
|
-
>
|
|
130
|
-
> What the package manager handles (out of scope for this skill): `torch` itself. `pip install -r requirements.txt` will fetch the right wheel.
|
|
131
|
-
>
|
|
132
|
-
> What this skill must probe (in scope):
|
|
133
|
-
>
|
|
134
|
-
> 1. The **Python toolchain** (Category 1): `python3 --version`, `pip --version`.
|
|
135
|
-
> 2. The **NVIDIA driver** (Category 4, layer 1): `nvidia-smi`.
|
|
136
|
-
> 3. **At least one GPU visible** (Category 4, layer 2): `nvidia-smi -L` lists ≥ 1 device.
|
|
137
|
-
> 4. **CUDA toolkit** (Category 4, layer 3): `nvcc --version`.
|
|
138
|
-
> 5. **cuDNN discoverable** (Category 4, layer 4): `ldconfig -p \| grep libcudnn` on Linux; skip on macOS.
|
|
139
|
-
> 6. **PyTorch actually sees the GPU** (Category 4, layer 5): `python -c "import torch; print(torch.cuda.is_available(), torch.version.cuda)"` — noting this requires `torch` to be installed (i.e. requires the test scripts to have run their install step at least once).
|
|
140
|
-
> 7. **Version match** (Category 4, layer 6): cross-check `torch.version.cuda` against `nvcc --version`. Mismatch → `WARN`.
|
|
141
|
-
>
|
|
142
|
-
> The skill never asks "is `torch` installed?". That belongs to `pip`.
|
|
143
|
-
|
|
144
|
-
#### Category 5 — Always required
|
|
145
|
-
|
|
146
|
-
Add these to every requirement list regardless of project:
|
|
147
|
-
|
|
148
|
-
- turri's own needs — Node, bash, the OS sandbox, a model provider — are checked by `turri doctor`. Run it and report its result; do not probe those separately.
|
|
149
|
-
- A shell matching the testing scripts' extension (Bash for `.sh`, PowerShell for `.ps1`).
|
|
150
|
-
- `git` (the renderer uses it; almost every plain project tracks itself in git).
|
|
151
|
-
|
|
152
|
-
### Step 3 — Probe the host
|
|
153
|
-
|
|
154
|
-
For every requirement the project produced in Step 2, run a check using the `bash` tool. Capture stdout/stderr and exit code. Classify each result:
|
|
155
|
-
|
|
156
|
-
- **PASS** — present and (where a version was specified) at or above the required version.
|
|
157
|
-
- **WARN** — present but at a version that doesn't match what the project explicitly asks for (e.g. spec says Java 17, host has Java 21), or service binary present but the service itself isn't running, or a Category 4 sub-layer is mismatched against another (e.g. CUDA toolkit version doesn't match what the framework expects).
|
|
158
|
-
- **FAIL** — missing, unreachable, or below the minimum version.
|
|
159
|
-
|
|
160
|
-
Probe in the same category order Step 2 produced them, so the report reads top-down:
|
|
161
|
-
|
|
162
|
-
1. **Category 1 — Language toolchains.** Run the toolchain + package-manager probes listed in Step 2's Category 1 for the languages this project actually uses. Do not probe individual language packages.
|
|
163
|
-
2. **Category 5 — turri and `git`.** Always required, independent of the project. `turri doctor`, `git --version`. Verify the shell flavor matches the scripts' extension; a `.sh`-only project on native Windows is a `FAIL` (suggest WSL).
|
|
164
|
-
3. **Category 2 — External services.** For each service identified at runtime, probe the CLI's presence first, then — if the CLI exists — check whether the service itself is reachable. Service binary present but daemon down → `WARN`, not `FAIL`.
|
|
165
|
-
4. **Category 3 — System binaries that language packages wrap.** For each one identified in Step 2, check the system binary is on `PATH` (`which <bin>` / `<bin> --version`). The wrapper package itself isn't probed.
|
|
166
|
-
5. **Category 4 — Hardware, drivers, accelerators.** Walk the layered probe from Step 2 in order (driver → device visibility → SDK → acceleration libs → framework-sees-it → version match), reporting each layer's result separately. Never collapse a multi-layer failure into a single "GPU not available" message — each layer has a different fix.
|
|
167
|
-
6. **Credentials and config holes.** For every env var, dotfile, or cloud CLI login state the project implies, probe its presence without printing its value: `printenv VAR >/dev/null && echo set || echo missing`, `[ -f <path> ] && echo present || echo missing`, `aws sts get-caller-identity` / `gcloud auth list` / `az account show` for cloud SDKs.
|
|
168
|
-
|
|
169
|
-
### Step 4 — Report
|
|
170
|
-
|
|
171
|
-
Emit a single report with **the verdict on the first line** so callers can pattern-match without parsing the body:
|
|
172
|
-
|
|
173
|
-
- **`PASS`** — every required item is present at an acceptable version. Follow with a one-paragraph summary listing what was checked.
|
|
174
|
-
- **`WARN`** — everything required is present but at least one item triggered a soft warning (version mismatch, service not currently running). Follow with the numbered list of warnings.
|
|
175
|
-
- **`FAIL`** — at least one required item is missing or below minimum version. Follow with a numbered list of every gap.
|
|
176
|
-
|
|
177
|
-
For every **FAIL** and **WARN** item, the report must include four columns:
|
|
178
|
-
|
|
179
|
-
| Column | Content |
|
|
180
|
-
|---|---|
|
|
181
|
-
| **What** | The missing tool/service/credential. |
|
|
182
|
-
| **Why** | Which spec, script, or library introduced this requirement (e.g. "`backend/api.plain` says `using FastAPI`", "`test_scripts/run_unittests_python.sh` calls `pytest`"). |
|
|
183
|
-
| **Status** | "missing", "outdated (found X, need Y)", "binary present but service not running", or "env var not set". |
|
|
184
|
-
| **How to install** | OS-specific command(s). See [Install suggestions](#install-suggestions) below. |
|
|
185
|
-
|
|
186
|
-
Group the report into the same six probe groups used in Step 3 (toolchains, turri/git, services, system binaries, hardware, credentials), so the user can fix one whole layer at a time.
|
|
187
|
-
|
|
188
|
-
End the report with a one-liner reminder: `Re-run check-plain-env after installing missing items to confirm.`
|
|
189
|
-
|
|
190
|
-
## Install suggestions
|
|
191
|
-
|
|
192
|
-
When emitting an install command, **match the host OS** detected in Step 1. Prefer the OS's first-party package manager. Don't suggest `curl | sh` style installs unless there is no alternative.
|
|
193
|
-
|
|
194
|
-
The tables below are a **starter set** of well-known install commands for the things projects most often need. They are **not** a list of "things to check" — that list is derived per project in Step 2. When the requirement list contains something not in these tables (e.g. a niche service, a specialty CLI), look up the canonical install command for the detected OS at the time of report generation rather than skipping the row or inventing a command.
|
|
195
|
-
|
|
196
|
-
### macOS (Darwin)
|
|
197
|
-
|
|
198
|
-
| Requirement | Suggested install |
|
|
199
|
-
|---|---|
|
|
200
|
-
| Python 3 | `brew install python@3.12` |
|
|
201
|
-
| Node.js | `brew install node` (or `brew install nvm` for multi-version) |
|
|
202
|
-
| Go | `brew install go` |
|
|
203
|
-
| Rust | `curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \| sh` (no Homebrew equivalent that stays current) |
|
|
204
|
-
| Java | `brew install openjdk@21` then follow the post-install `sudo ln -sfn ...` step |
|
|
205
|
-
| Maven | `brew install maven` |
|
|
206
|
-
| Gradle | `brew install gradle` |
|
|
207
|
-
| PostgreSQL | `brew install postgresql@16` then `brew services start postgresql@16` |
|
|
208
|
-
| MySQL | `brew install mysql` then `brew services start mysql` |
|
|
209
|
-
| Redis | `brew install redis` then `brew services start redis` |
|
|
210
|
-
| Docker | Install Docker Desktop from https://docker.com/products/docker-desktop |
|
|
211
|
-
| `pg_config` (psycopg) | comes with `brew install postgresql@16` |
|
|
212
|
-
| Xcode CLT | `xcode-select --install` |
|
|
213
|
-
| `turri` | follow the install instructions at https://turri.ai |
|
|
214
|
-
|
|
215
|
-
### Linux (Debian/Ubuntu)
|
|
216
|
-
|
|
217
|
-
| Requirement | Suggested install |
|
|
218
|
-
|---|---|
|
|
219
|
-
| Python 3 | `sudo apt update && sudo apt install -y python3 python3-pip python3-venv` |
|
|
220
|
-
| Node.js | `curl -fsSL https://deb.nodesource.com/setup_20.x \| sudo -E bash - && sudo apt install -y nodejs` |
|
|
221
|
-
| Go | `sudo apt install -y golang-go` (or download from https://go.dev/dl/ for the latest) |
|
|
222
|
-
| Rust | `curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \| sh` |
|
|
223
|
-
| Java | `sudo apt install -y openjdk-21-jdk` |
|
|
224
|
-
| Maven | `sudo apt install -y maven` |
|
|
225
|
-
| PostgreSQL | `sudo apt install -y postgresql postgresql-contrib && sudo systemctl start postgresql` |
|
|
226
|
-
| Redis | `sudo apt install -y redis-server && sudo systemctl start redis-server` |
|
|
227
|
-
| Docker | follow https://docs.docker.com/engine/install/ubuntu/ |
|
|
228
|
-
| `pg_config` (psycopg) | `sudo apt install -y libpq-dev` |
|
|
229
|
-
| C/C++ build tools | `sudo apt install -y build-essential` |
|
|
230
|
-
|
|
231
|
-
### Linux (Fedora/RHEL)
|
|
232
|
-
|
|
233
|
-
Equivalent commands with `sudo dnf install -y ...` instead of `apt`. Surface them when the host is detected as Fedora/RHEL (e.g. presence of `/etc/redhat-release` or `dnf --version` succeeding).
|
|
234
|
-
|
|
235
|
-
### Windows (native PowerShell)
|
|
236
|
-
|
|
237
|
-
| Requirement | Suggested install |
|
|
238
|
-
|---|---|
|
|
239
|
-
| Python 3 | `winget install Python.Python.3.12` or `choco install python` |
|
|
240
|
-
| Node.js | `winget install OpenJS.NodeJS.LTS` or `choco install nodejs-lts` |
|
|
241
|
-
| Go | `winget install GoLang.Go` |
|
|
242
|
-
| Rust | `winget install Rustlang.Rustup` |
|
|
243
|
-
| Java | `winget install Microsoft.OpenJDK.21` |
|
|
244
|
-
| PostgreSQL | `winget install PostgreSQL.PostgreSQL` |
|
|
245
|
-
| Docker | `winget install Docker.DockerDesktop` |
|
|
246
|
-
| MSVC build tools | `winget install Microsoft.VisualStudio.2022.BuildTools` |
|
|
247
|
-
|
|
248
|
-
If the project's `test_scripts/` are `.sh` only and the host is native Windows, suggest **WSL** (`wsl --install`) rather than trying to make Bash scripts run under PowerShell.
|
|
249
|
-
|
|
250
|
-
### When you can't pick
|
|
251
|
-
|
|
252
|
-
When the host OS detection in Step 1 was inconclusive (e.g. an unrecognized Linux distro), include the macOS and Debian/Ubuntu commands and tell the user to adapt for their package manager. Never invent install commands for a distro you can't identify.
|
|
253
|
-
|
|
254
|
-
## Anti-patterns
|
|
255
|
-
|
|
256
|
-
- **(Hard mistake) Don't probe individual language packages.** Anything `pip install -r requirements.txt`, `npm ci`, `mvn install`, `cargo fetch`, `go mod download`, `bundle install`, `composer install`, etc. would resolve is out of scope. `torch`, `requests`, `numpy`, `FastAPI`, `express`, `react`, JARs, gems, crates — all package-manager territory. Check the toolchain (`python3`, `pip`, `node`, `npm`, `mvn`, `cargo`, ...) and stop there. Probing the package itself creates false negatives the moment `requirements.txt` changes and duplicates work the test scripts will do anyway.
|
|
257
|
-
- **Don't stop after the directly named technologies.** A spec that says "fine-tune on local hardware using PyTorch" never says "CUDA", but the workflow can't run without CUDA. Always work through Category 3 (system binaries that language packages wrap) and Category 4 (hardware/drivers) — not only the directly named runtimes. These categories are where the workflow's hidden requirements live.
|
|
258
|
-
- **Don't merge a chain of failures into one generic message.** "GPU not available" is not a useful report. The chain is `driver → device visibility → toolkit → acceleration libs → framework-sees-it → version match`; each layer has a different fix. Surface each layer separately.
|
|
259
|
-
- **Don't work only from `.plain` files.** Always cross-reference with `test_scripts/` — the scripts are the executable contract. If a script invokes a non-language binary the specs never mention, that binary is automatically a Category 3 requirement.
|
|
260
|
-
- **Don't pre-bake a catalog.** Derive the requirement list at runtime from the project in front of you. A hard-coded "things to always check" table becomes wrong the moment any project deviates from the assumed shape — and every project does.
|
|
261
|
-
- **Don't probe testing framework binaries as if they were independent.** `pytest`, `jest`, `vitest`, `phpunit`, `junit-console`, etc. are installed by the package manager via `requirements.txt` / `package.json` / `pom.xml`. The toolchain check (Category 1) is enough — the framework binary itself is out of scope.
|
|
262
|
-
- **Don't probe in silence.** Use the `bash` tool and capture the actual command output (version strings, exit codes). Telling the user "looks like Python is installed" without running `python3 --version` is guessing.
|
|
263
|
-
- **Don't print secret values.** Check whether `DATABASE_URL`, provider API keys, etc. are set, not what they contain. Use `printenv VAR >/dev/null` not `printenv VAR`.
|
|
264
|
-
- **Don't install anything on the user's behalf.** Even when the install command is obvious. The user needs to opt in.
|
|
265
|
-
- **Don't stop at the first FAIL.** Run the full sweep so the report is complete in one pass.
|
|
266
|
-
- **Don't suggest `curl | sh` installs when a package manager works.** Reserve the upstream installer fallback for cases where no package-manager option exists (e.g. `rustup`, sometimes Go).
|
|
267
|
-
- **Don't ignore version mismatches.** If a spec says Java 17 and the host has Java 21, surface that as a `WARN` so the user can decide — don't silently let it pass.
|
|
268
|
-
- **Don't duplicate this check inline elsewhere.** Other skills (`brood`, `bud`) that need an env check should **delegate to `check-plain-env`** rather than re-implementing the probe themselves. This skill is the single source of truth.
|
|
269
|
-
|
|
270
|
-
## Validation Checklist
|
|
271
|
-
|
|
272
|
-
- [ ] Host OS detected (`uname -s` / `$OS`) before any other action.
|
|
273
|
-
- [ ] Requirement list derived **at runtime from this project's** `.plain` files, `test_scripts/`, and `resources/` — no pre-baked catalog used.
|
|
274
|
-
- [ ] **No individual language packages probed.** Only the toolchain + package manager for each detected language.
|
|
275
|
-
- [ ] Every script under `test_scripts/` opened and read; every non-language binary it invokes added to the Category 3 list.
|
|
276
|
-
- [ ] Every external service named in the specs is in the Category 2 list, with both a binary probe and (when feasible) a reachability probe.
|
|
277
|
-
- [ ] Every hardware / accelerator signal ("GPU", "fine-tune", "local inference", "CUDA", "MPS", etc.) triggered the layered Category 4 probe.
|
|
278
|
-
- [ ] Every requirement probed via the `bash` tool with an actual version / availability command (no "looks installed" guesses).
|
|
279
|
-
- [ ] GPU / accelerator chains probed layer-by-layer (driver → device visibility → toolkit → acceleration libs → framework-sees-it → version match), with each layer reported separately.
|
|
280
|
-
- [ ] Each result classified as PASS / WARN / FAIL.
|
|
281
|
-
- [ ] Report has the verdict on the first line.
|
|
282
|
-
- [ ] Every FAIL and WARN row has all four columns: What / Why / Status / How to install.
|
|
283
|
-
- [ ] Install suggestions match the detected host OS (no Debian commands on macOS, no `brew` on Linux, etc.).
|
|
284
|
-
- [ ] No secret values were printed at any point.
|
|
285
|
-
- [ ] Nothing was installed; no project files were modified.
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: create-import-module
|
|
3
|
-
description: >-
|
|
4
|
-
Create a ***plain import module that provides shared definitions,
|
|
5
|
-
implementation reqs, and test reqs for other modules to import. Use when
|
|
6
|
-
the user wants to create a new .plain file that contains only definitions,
|
|
7
|
-
implementation reqs, and/or test reqs — no functional specs.
|
|
8
|
-
allowed-tools: [read, write, edit, bash, ls, grep, find]
|
|
9
|
-
---
|
|
10
|
-
|
|
11
|
-
# Create Import Module
|
|
12
|
-
|
|
13
|
-
## What an Import Module Is
|
|
14
|
-
|
|
15
|
-
An import module is a `.plain` file that lives in the **`template/`** directory and contains **only** `***definitions***`, `***implementation reqs***`, and/or `***test reqs***`. It must **not** contain `***functional specs***` and must **not** use `requires` in its frontmatter. Other modules pull in its content via the `import` field in their YAML frontmatter, gaining access to its definitions and reqs.
|
|
16
|
-
|
|
17
|
-
Use import modules for:
|
|
18
|
-
- Sharing concept definitions across multiple modules
|
|
19
|
-
- Providing common implementation requirements (e.g., technology stack, coding standards)
|
|
20
|
-
- Providing common test requirements
|
|
21
|
-
- Creating reusable foundational structure (templates)
|
|
22
|
-
|
|
23
|
-
If the module needs functional specs (i.e., it describes what software should do), it is not an import module — see the `create-requires-module` skill.
|
|
24
|
-
|
|
25
|
-
## Workflow
|
|
26
|
-
|
|
27
|
-
1. **Determine what shared content this module should provide** — which definitions, implementation reqs, or test reqs will other modules need?
|
|
28
|
-
2. **Create the `.plain` file in the `template/` directory** with YAML frontmatter. Use `required_concepts` to declare concepts that importing modules must define. It may optionally `import` other templates for layered reuse, but must **not** use `requires`.
|
|
29
|
-
3. **Add the shared content** — definitions, implementation reqs, and/or test reqs.
|
|
30
|
-
4. **Do not add `***functional specs***`** — import modules must not contain functional specs.
|
|
31
|
-
5. **Verify concept availability** — ensure all `:Concepts:` referenced are either defined in this module, provided by its own imports, declared as `required_concepts`, or are predefined concepts.
|
|
32
|
-
|
|
33
|
-
## Format
|
|
34
|
-
|
|
35
|
-
The `import` field is a list of module paths in the YAML frontmatter:
|
|
36
|
-
|
|
37
|
-
```plain
|
|
38
|
-
---
|
|
39
|
-
import:
|
|
40
|
-
- base_template
|
|
41
|
-
required_concepts: [":AppName:"]
|
|
42
|
-
description: Shared API definitions and reqs
|
|
43
|
-
---
|
|
44
|
-
|
|
45
|
-
***definitions***
|
|
46
|
-
|
|
47
|
-
- :ApiClient: is the HTTP client used to communicate with external services.
|
|
48
|
-
|
|
49
|
-
- :ApiResponse: is the response returned by :ApiClient:.
|
|
50
|
-
|
|
51
|
-
***implementation reqs***
|
|
52
|
-
|
|
53
|
-
- :Implementation: should handle HTTP errors by raising appropriate exceptions.
|
|
54
|
-
|
|
55
|
-
- :ApiClient: should support configurable timeouts.
|
|
56
|
-
|
|
57
|
-
***test reqs***
|
|
58
|
-
|
|
59
|
-
- :ConformanceTests: should mock all external HTTP calls made by :ApiClient:.
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
The default import directory is `template/` — the `template/` prefix is not needed in import paths. The `.plain` extension is omitted. Note the absence of `***functional specs***` — import modules must not have them.
|
|
63
|
-
|
|
64
|
-
## Satisfying Required Concepts
|
|
65
|
-
|
|
66
|
-
If an imported template declares `required_concepts`, the importing module **must** define those concepts in its own `***definitions***` section:
|
|
67
|
-
|
|
68
|
-
```plain
|
|
69
|
-
---
|
|
70
|
-
import:
|
|
71
|
-
- auth_template
|
|
72
|
-
---
|
|
73
|
-
|
|
74
|
-
> auth_template declares required_concepts: [":AuthSchema:", ":AuthApiSpec:"]
|
|
75
|
-
> So this module must define them:
|
|
76
|
-
|
|
77
|
-
***definitions***
|
|
78
|
-
|
|
79
|
-
- :AuthSchema: is the JSON schema describing the authentication data structure.
|
|
80
|
-
|
|
81
|
-
- :AuthApiSpec: is the OpenAPI specification for the external service's auth endpoint.
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
## Import vs Requires
|
|
85
|
-
|
|
86
|
-
| Aspect | `import` | `requires` |
|
|
87
|
-
|--------|----------|------------|
|
|
88
|
-
| Pulls in definitions | Yes | No (only `exported_concepts`) |
|
|
89
|
-
| Pulls in implementation reqs | Yes | No |
|
|
90
|
-
| Pulls in test reqs | Yes | No |
|
|
91
|
-
| Pulls in functional specs | No | Yes (as previous requirements, transitively) |
|
|
92
|
-
| Copies generated code | No | Yes (accumulated ancestor chain) |
|
|
93
|
-
| Typical use | Templates, shared definitions | Attaching a module to the build chain |
|
|
94
|
-
|
|
95
|
-
## Validation Checklist
|
|
96
|
-
|
|
97
|
-
- [ ] Module file is in the `template/` directory
|
|
98
|
-
- [ ] Module does **not** contain `***functional specs***`
|
|
99
|
-
- [ ] Module does **not** use `requires` in its frontmatter
|
|
100
|
-
- [ ] Contains at least one of: definitions, implementation reqs, or test reqs
|
|
101
|
-
- [ ] All `required_concepts` from any imports this module itself uses are satisfied
|
|
102
|
-
- [ ] `required_concepts` declared for any concepts referenced but not defined here
|
|
103
|
-
- [ ] No concept name collisions between this module's imports and local definitions
|
|
104
|
-
- [ ] YAML frontmatter is correctly formatted between `---` markers
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: create-requires-module
|
|
3
|
-
description: >-
|
|
4
|
-
Create a ***plain module that uses requires to depend on another module in
|
|
5
|
-
the build chain. Use when the user wants to create a new .plain file that
|
|
6
|
-
builds on top of a previously built module, inheriting its functional specs
|
|
7
|
-
and generated code as a starting point.
|
|
8
|
-
allowed-tools: [read, write, edit, bash, ls, grep, find]
|
|
9
|
-
---
|
|
10
|
-
|
|
11
|
-
# Create Requires Module
|
|
12
|
-
|
|
13
|
-
## What Requires Does
|
|
14
|
-
|
|
15
|
-
`requires` attaches the current module to the project's build chain. The required module is built **before** the current one, and its generated code — the accumulated output of that module and all its ancestors — is copied as the starting point. The current module does not need to functionally extend the required module, but their outputs are never independent: the current module's code physically contains the required module's.
|
|
16
|
-
|
|
17
|
-
When this module is rendered:
|
|
18
|
-
- The required module's generated code (`plain_modules/<required_module>/code`) is copied as the starting point.
|
|
19
|
-
- The `***functional specs***` of the required module **and all its ancestors** become visible as **previous functional specs**.
|
|
20
|
-
- Of the required module's definitions, only the concepts in its `exported_concepts` are visible, each with its full definition text. Export visibility is **not** transitive — to see an ancestor's exports, list that ancestor in `requires` too.
|
|
21
|
-
|
|
22
|
-
All modules connected by `requires` form a single tree with a common root. **Diamond joins are prohibited**: multiple `requires` entries must lie on one root-to-tip ancestor path. The deepest entry is the attachment point; the other entries exist only to make those ancestors' exports visible.
|
|
23
|
-
|
|
24
|
-
Use `requires` for:
|
|
25
|
-
- Building on top of an existing module's functionality
|
|
26
|
-
- Attaching a module at the right point in the build chain, even when it addresses a different concern
|
|
27
|
-
|
|
28
|
-
If you only need shared definitions and reqs (no functional specs, no generated code), use `import` instead — see the `create-import-module` skill.
|
|
29
|
-
|
|
30
|
-
## Workflow
|
|
31
|
-
|
|
32
|
-
1. **Identify the attachment point.** Determine which existing module this new module attaches after in the build chain. That module must already exist and be renderable. If listing additional modules, verify each is an ancestor of the attachment point — no diamond joins.
|
|
33
|
-
2. **Create the `.plain` file at the repository root** with YAML frontmatter containing the `requires` field. Modules with functional specs live at the root, not in `template/`.
|
|
34
|
-
3. **Review the functional specs of the whole ancestor chain** — they will be treated as previous requirements. Your new functional specs must not conflict with any of them.
|
|
35
|
-
4. **Review each listed module's `exported_concepts`** — only those concepts are available to reference; an ancestor's exports require listing that ancestor.
|
|
36
|
-
5. **Add module-specific content** — definitions, implementation reqs, test reqs, and functional specs unique to this module.
|
|
37
|
-
6. **Check for conflicts** between your new functional specs and any spec in the ancestor chain.
|
|
38
|
-
|
|
39
|
-
## Format
|
|
40
|
-
|
|
41
|
-
The `requires` field is a list of module paths in the YAML frontmatter:
|
|
42
|
-
|
|
43
|
-
```plain
|
|
44
|
-
---
|
|
45
|
-
requires:
|
|
46
|
-
- base_module
|
|
47
|
-
import:
|
|
48
|
-
- shared_template
|
|
49
|
-
description: Extended module that builds on base_module
|
|
50
|
-
---
|
|
51
|
-
|
|
52
|
-
***definitions***
|
|
53
|
-
|
|
54
|
-
- :NewFeature: is a feature added by this module.
|
|
55
|
-
|
|
56
|
-
***functional specs***
|
|
57
|
-
|
|
58
|
-
- :NewFeature: is available.
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
A module can use both `requires` and `import` together. `requires` points to other root-level modules; `import` resolves from the default `template/` directory (no prefix needed).
|
|
62
|
-
|
|
63
|
-
Multiple `requires` entries are legal only when they lie on one ancestor path (e.g., `requires: [auth, messaging]` where `messaging` itself requires `auth`). The deepest entry (`messaging`) is the attachment point; `auth` is listed only to make its exports visible.
|
|
64
|
-
|
|
65
|
-
## Exported Concepts
|
|
66
|
-
|
|
67
|
-
The required module controls what concepts are visible via `exported_concepts`:
|
|
68
|
-
|
|
69
|
-
```plain
|
|
70
|
-
> In the required module's frontmatter:
|
|
71
|
-
---
|
|
72
|
-
exported_concepts: [":StorageClient:", ":BackupResult:"]
|
|
73
|
-
---
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
Only `:StorageClient:` and `:BackupResult:` would be available to modules that `require` this one, each with its full definition text. All other concepts from the required module are internal.
|
|
77
|
-
|
|
78
|
-
## Chronological Ordering with Requires
|
|
79
|
-
|
|
80
|
-
Functional specs from the whole `requires` ancestor chain are considered **previous functional specs**. This means:
|
|
81
|
-
- They are already rendered and their code exists.
|
|
82
|
-
- Your new specs are rendered after them, with full awareness of what they defined.
|
|
83
|
-
- Your new specs must not conflict with any spec in the ancestor chain.
|
|
84
|
-
- The renderer sees the chain's functional specs as context when rendering yours.
|
|
85
|
-
|
|
86
|
-
The current module may or may not be functionally related to the required module — `requires` places it at the right point in the accumulating build chain either way.
|
|
87
|
-
|
|
88
|
-
## Import vs Requires
|
|
89
|
-
|
|
90
|
-
| Aspect | `import` | `requires` |
|
|
91
|
-
|--------|----------|------------|
|
|
92
|
-
| Pulls in definitions | Yes | No (only `exported_concepts`) |
|
|
93
|
-
| Pulls in implementation reqs | Yes | No |
|
|
94
|
-
| Pulls in test reqs | Yes | No |
|
|
95
|
-
| Pulls in functional specs | No | Yes (as previous requirements, transitively) |
|
|
96
|
-
| Copies generated code | No | Yes (accumulated ancestor chain) |
|
|
97
|
-
| Typical use | Templates, shared definitions | Attaching a module to the build chain |
|
|
98
|
-
|
|
99
|
-
## Validation Checklist
|
|
100
|
-
|
|
101
|
-
- [ ] Module file is at the repository root (not in `template/`)
|
|
102
|
-
- [ ] Required module exists and is renderable
|
|
103
|
-
- [ ] All `requires` entries lie on one ancestor path — no diamond joins
|
|
104
|
-
- [ ] The deepest `requires` entry is the intended attachment point
|
|
105
|
-
- [ ] Every listed module's `exported_concepts` provide the concepts you need
|
|
106
|
-
- [ ] New functional specs do not conflict with any spec in the ancestor chain
|
|
107
|
-
- [ ] Module has at least one functional spec and one implementation req
|
|
108
|
-
- [ ] Both `requires` and `import` are used correctly (not mixed up)
|
|
109
|
-
- [ ] YAML frontmatter is correctly formatted between `---` markers
|