@turri-dev/cli 0.1.4-next.246fd84 → 0.1.4-next.eb3e006
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/ask.js +34 -15
- package/dist/extension.js +12 -7
- package/dist/loop-cli.js +4 -0
- package/dist/loop-tools.js +11 -5
- package/dist/question-dialog.js +1 -0
- package/dist/regenerate/loop.js +21 -8
- package/dist/regenerate/spec-map-view.js +2 -4
- package/dist/regenerate-cli.js +8 -5
- package/dist/run-report.js +13 -0
- package/docs/headless.md +17 -2
- package/docs/regenerating.md +4 -2
- package/docs/skills-and-gates.md +3 -1
- package/docs/using.md +12 -7
- package/genome/skills/refactor-module/SKILL.md +24 -2
- package/package.json +1 -1
package/dist/ask.js
CHANGED
|
@@ -9,6 +9,9 @@ import { PANEL_KEY } from "./regenerate/job-panel.js";
|
|
|
9
9
|
export const OTHER_OPTION = "Something else…";
|
|
10
10
|
export const CHAT_OPTION = "Chat about this";
|
|
11
11
|
export const DONE_OPTION = "Done — that's all of them";
|
|
12
|
+
/** An option's box in a question of several, ticked and not. */
|
|
13
|
+
export const TICKED = "☑";
|
|
14
|
+
export const UNTICKED = "☐";
|
|
12
15
|
export const DISMISSED = "The user dismissed the question without answering.";
|
|
13
16
|
/** How an answer that is no answer starts: the person chose to talk about the question first. */
|
|
14
17
|
export const CHAT_PREFIX = "The user has not answered this question yet: they chose to talk about it first.";
|
|
@@ -107,32 +110,48 @@ export async function runAsk(asked, io) {
|
|
|
107
110
|
return choice;
|
|
108
111
|
}
|
|
109
112
|
}
|
|
110
|
-
// Pick several:
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
113
|
+
// Pick several: every option a box, ticked and unticked as it is chosen, until they say they are done. What they type
|
|
114
|
+
// is one more box, ticked - and can be unticked too. Something else, Chat about this and Done are no boxes.
|
|
115
|
+
const listed = [...options];
|
|
116
|
+
const ticked = new Set();
|
|
117
|
+
const picked = () => listed.filter((option) => ticked.has(option));
|
|
118
|
+
let at = 0;
|
|
119
|
+
for (;;) {
|
|
120
|
+
const rows = listed.map((option) => `${ticked.has(option) ? TICKED : UNTICKED} ${option}`);
|
|
121
|
+
const choice = await io.select(request.question, [...rows, ...extras, DONE_OPTION], context, at);
|
|
122
|
+
if (choice === undefined || choice === DONE_OPTION)
|
|
119
123
|
break;
|
|
120
124
|
if (choice === OTHER_OPTION) {
|
|
125
|
+
at = listed.length;
|
|
121
126
|
const typed = await typeInstead();
|
|
122
|
-
if (
|
|
123
|
-
|
|
127
|
+
if (typedNothing(typed))
|
|
128
|
+
continue;
|
|
129
|
+
const option = typed.trim();
|
|
130
|
+
if (!listed.includes(option))
|
|
131
|
+
listed.push(option);
|
|
132
|
+
ticked.add(option);
|
|
133
|
+
at = listed.indexOf(option);
|
|
124
134
|
continue;
|
|
125
135
|
}
|
|
126
136
|
if (choice === CHAT_OPTION) {
|
|
137
|
+
at = listed.length + extras.indexOf(CHAT_OPTION);
|
|
127
138
|
const said = await chatAbout();
|
|
128
139
|
if (typedNothing(said))
|
|
129
140
|
continue;
|
|
130
|
-
return chatAnswer(said,
|
|
141
|
+
return chatAnswer(said, picked());
|
|
131
142
|
}
|
|
132
|
-
|
|
133
|
-
|
|
143
|
+
// A row, or - where the answer is typed, on the command line - an option by its text, or one more of their own.
|
|
144
|
+
const row = rows.indexOf(choice);
|
|
145
|
+
const option = row !== -1 ? listed[row] : choice.trim();
|
|
146
|
+
if (!listed.includes(option))
|
|
147
|
+
listed.push(option);
|
|
148
|
+
if (ticked.has(option))
|
|
149
|
+
ticked.delete(option);
|
|
150
|
+
else
|
|
151
|
+
ticked.add(option);
|
|
152
|
+
at = listed.indexOf(option);
|
|
134
153
|
}
|
|
135
|
-
return
|
|
154
|
+
return ticked.size > 0 ? picked().join(", ") : DISMISSED;
|
|
136
155
|
}
|
|
137
156
|
/**
|
|
138
157
|
* While a question is open, what catches ctrl+h on the terminal's input: pi gives an extension's shortcut only the keys
|
package/dist/extension.js
CHANGED
|
@@ -16,7 +16,7 @@ import { readScript, scriptAgainstMachine, stackOnMachine, toolchainsBlock } fro
|
|
|
16
16
|
import { sandboxAvailable, sandboxSummary } from "./regenerate/os-sandbox.js";
|
|
17
17
|
import { findTestScripts } from "./regenerate/test-scripts.js";
|
|
18
18
|
import { createAskUserTool } from "./tools.js";
|
|
19
|
-
import { asksFromScratch, driftTool, fromWorkingDir, haltTool, loopReply, MODEL_PARAM, regenerateTool, stopJob, WAIT_PARAM, WORKING_DIR_PARAM } from "./loop-tools.js";
|
|
19
|
+
import { asksFromScratch, asksWholeChain, driftTool, fromWorkingDir, haltTool, loopReply, MODEL_PARAM, regenerateTool, stopJob, WAIT_PARAM, WORKING_DIR_PARAM } from "./loop-tools.js";
|
|
20
20
|
import { createWebTools } from "./regenerate/web.js";
|
|
21
21
|
import { describeGates, interruptedRun, runSkill } from "./genome.js";
|
|
22
22
|
import { skillStatus } from "./skill.js";
|
|
@@ -90,11 +90,12 @@ aside) {
|
|
|
90
90
|
// The question, with what the asker gives to read for it - a brief to confirm - above it; what it said of its work
|
|
91
91
|
// before asking is kept in its record, never put in the dialog.
|
|
92
92
|
// A dialog of turri's own: a long brief scrolls above the question, and the options always show (question-dialog.ts).
|
|
93
|
-
const dialog = (question, context, options) => ctx.ui.custom((tui, theme, _keybindings, done) => {
|
|
93
|
+
const dialog = (question, context, options, selected) => ctx.ui.custom((tui, theme, _keybindings, done) => {
|
|
94
94
|
const shown = new QuestionDialog({
|
|
95
95
|
question,
|
|
96
96
|
...(context !== undefined ? { context } : {}),
|
|
97
97
|
...(options !== undefined ? { options } : {}),
|
|
98
|
+
...(selected !== undefined ? { selected } : {}),
|
|
98
99
|
rows: () => tui.terminal.rows,
|
|
99
100
|
style: { accent: (text) => theme.fg("accent", text), muted: (text) => theme.fg("muted", text), bold: (text) => theme.bold(text) },
|
|
100
101
|
done: (answer) => {
|
|
@@ -108,7 +109,7 @@ aside) {
|
|
|
108
109
|
return shown;
|
|
109
110
|
});
|
|
110
111
|
const asking = (asked) => runAsk(asked, {
|
|
111
|
-
select: (title, options, context) => dialog(title, context, options),
|
|
112
|
+
select: (title, options, context, at) => dialog(title, context, options, at),
|
|
112
113
|
input: (title, context) => dialog(title, context),
|
|
113
114
|
});
|
|
114
115
|
const bare = { ...request, said: undefined };
|
|
@@ -610,7 +611,7 @@ function registerRegenerate(pi, cwd, jobs) {
|
|
|
610
611
|
// by the planner: one functional spec at a time, a few together or the whole module in one, as it judges. What
|
|
611
612
|
// follows the module is guidance for that plan, in the person's words.
|
|
612
613
|
pi.registerCommand("regenerate", {
|
|
613
|
-
description: "Regenerate a module from its spec as it is now, in pulses the planner cuts - named functionalities though they did not change, or all of it from scratch: /regenerate [module | file.plain] [functionality ...] [from scratch] [how to cut the work]",
|
|
614
|
+
description: "Regenerate a module from its spec as it is now, in pulses the planner cuts - named functionalities though they did not change, or all of it from scratch, its whole chain too when asked: /regenerate [module | file.plain] [functionality ...] [from scratch [whole chain]] [how to cut the work]",
|
|
614
615
|
handler: async (args, ctx) => {
|
|
615
616
|
const command = `/regenerate ${args.trim()}`.trim();
|
|
616
617
|
await echoCommand(pi, command);
|
|
@@ -652,13 +653,17 @@ function registerRegenerate(pi, cwd, jobs) {
|
|
|
652
653
|
}
|
|
653
654
|
// From scratch - started over, its code deleted - asked in the person's words: confirmed first, as it deletes.
|
|
654
655
|
const scratch = asksFromScratch(guidance);
|
|
656
|
+
// The whole chain with it - "from scratch, the whole chain" - every module it requires built again from the bottom.
|
|
657
|
+
const chain = scratch && asksWholeChain(guidance);
|
|
655
658
|
const module = basename(plainFile).replace(/\.plain$/, "");
|
|
656
659
|
if (scratch && again.length > 0) {
|
|
657
660
|
say("Name functionalities or ask for it from scratch, not both: from scratch regenerates every one of them.", "warning");
|
|
658
661
|
return;
|
|
659
662
|
}
|
|
660
663
|
if (scratch && ctx.hasUI) {
|
|
661
|
-
const agreed = await ctx.ui.confirm(`Regenerate ${module}
|
|
664
|
+
const agreed = await ctx.ui.confirm(chain ? `Regenerate ${module} and its whole chain from scratch?` : `Regenerate ${module} from scratch?`, chain
|
|
665
|
+
? `The code and conformance tests of ${module} and of every module it requires are deleted - committed first, so /lineage keeps them - and the whole chain is regenerated from its spec, from the bottom.`
|
|
666
|
+
: `Its code and conformance tests are deleted - committed first, so /lineage ${module} keeps them - and the whole module is regenerated from its spec.`);
|
|
662
667
|
if (!agreed)
|
|
663
668
|
return;
|
|
664
669
|
}
|
|
@@ -668,14 +673,14 @@ function registerRegenerate(pi, cwd, jobs) {
|
|
|
668
673
|
// Said at once, and kept in the chat: what follows takes a while - the planner is an agent - and the person sees it
|
|
669
674
|
// began, not a chat that stays empty until the first pulse starts. The plan is said under it once it is made.
|
|
670
675
|
say(scratch
|
|
671
|
-
? `Deleting ${module}'s code, then planning all of it again…`
|
|
676
|
+
? `Deleting ${module}'s code${chain ? " and its whole chain's" : ""}, then planning all of it again…`
|
|
672
677
|
: again.length > 0
|
|
673
678
|
? `Planning ${module}'s functionalit${again.length === 1 ? "y" : "ies"} ${again.join(", ")} again…`
|
|
674
679
|
: `Planning ${module}'s pulses…`, "info");
|
|
675
680
|
await nextFrame();
|
|
676
681
|
try {
|
|
677
682
|
const statuses = scratch
|
|
678
|
-
? await loop.fromScratch(plainFile, request)
|
|
683
|
+
? await loop.fromScratch(plainFile, { ...request, chain })
|
|
679
684
|
: again.length > 0
|
|
680
685
|
? await loop.regenerateAgain(plainFile, again, request)
|
|
681
686
|
: await loop.bringUp(plainFile, request);
|
package/dist/loop-cli.js
CHANGED
|
@@ -85,6 +85,10 @@ async function runLoopReporting(options) {
|
|
|
85
85
|
console.log(`${mark} ${job.id} ${pulseName(job)} - ${job.status}`);
|
|
86
86
|
if (job.status !== "succeeded")
|
|
87
87
|
console.log(c.dim((job.announce ?? job.report ?? job.error ?? "").trim()));
|
|
88
|
+
// Its report is not printed when it succeeds, and nobody is asked here: the spec gaps its agents met are said.
|
|
89
|
+
else
|
|
90
|
+
for (const gap of job.details?.specGaps ?? [])
|
|
91
|
+
console.log(c.yellow(` spec gap: ${gap.question}`), c.dim(`Meanwhile: ${gap.assumption}`));
|
|
88
92
|
report?.write();
|
|
89
93
|
},
|
|
90
94
|
});
|
package/dist/loop-tools.js
CHANGED
|
@@ -110,6 +110,10 @@ function plainFileOf(cwd, given, what) {
|
|
|
110
110
|
export function asksFromScratch(words) {
|
|
111
111
|
return FROM_SCRATCH.test(words);
|
|
112
112
|
}
|
|
113
|
+
/** Whether a /regenerate asking for its module from scratch asks for its whole requires chain with it. */
|
|
114
|
+
export function asksWholeChain(words) {
|
|
115
|
+
return /\bchain\b|\bevery module\b|\ball (the )?modules\b/i.test(words);
|
|
116
|
+
}
|
|
113
117
|
const FROM_SCRATCH = /\b(from (scratch|nothing|zero|the (start|beginning|ground up))|start(ing)? (it )?(over|afresh)|afresh|anew|clean slate|(delete|wipe|remove|drop|throw away)\b.{0,30}\b(module|code|codebase|everything|all of it))/i;
|
|
114
118
|
/** regenerate: regenerate a module's code up to its spec as it is now. */
|
|
115
119
|
export function regenerateTool(options) {
|
|
@@ -131,6 +135,7 @@ export function regenerateTool(options) {
|
|
|
131
135
|
"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.",
|
|
132
136
|
"Never stop pulses and start them again yourself to follow a spec change: regenerate does it.",
|
|
133
137
|
"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.",
|
|
138
|
+
'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.',
|
|
134
139
|
],
|
|
135
140
|
parameters: Type.Object({
|
|
136
141
|
plain_file: Type.Optional(Type.String({ description: "Path to the .plain file. Omitted: the module the project's config.turri.yaml names" })),
|
|
@@ -139,8 +144,9 @@ export function regenerateTool(options) {
|
|
|
139
144
|
functionalities: Type.Optional(Type.Array(Type.String(), {
|
|
140
145
|
description: "Functional specs already built, by id, to regenerate though their spec did not change. Omitted: what the spec now differs from the code in",
|
|
141
146
|
})),
|
|
142
|
-
from_scratch: Type.Optional(Type.
|
|
143
|
-
description: "Only when the user asks for it: the module's code and conformance tests deleted - committed first, so its lineage keeps them - and the whole module regenerated from its spec"
|
|
147
|
+
from_scratch: Type.Optional(Type.Union([Type.Literal("module"), Type.Literal("chain")], {
|
|
148
|
+
description: "Only when the user asks for it: the module's code and conformance tests deleted - committed first, so its lineage keeps them - and the whole module regenerated from its spec. " +
|
|
149
|
+
'"chain": every module of its requires chain too, built again from the bottom',
|
|
144
150
|
})),
|
|
145
151
|
model: MODEL_PARAM,
|
|
146
152
|
wait: WAIT_PARAM,
|
|
@@ -151,15 +157,15 @@ export function regenerateTool(options) {
|
|
|
151
157
|
if (!check.ok)
|
|
152
158
|
throw new Error(`${relative(options.cwd, plainFile) || plainFile} does not parse, so nothing can be regenerated from it yet: ${check.error}`);
|
|
153
159
|
const again = given.functionalities ?? [];
|
|
154
|
-
if (again.length > 0 && given.from_scratch
|
|
160
|
+
if (again.length > 0 && given.from_scratch !== undefined)
|
|
155
161
|
throw new Error("Name functionalities or set from_scratch, not both: from scratch regenerates every one of them.");
|
|
156
162
|
// Named functionalities are work on functionalities, as engine chooses a model for it; the rest, spec changes.
|
|
157
163
|
const work = again.length > 0 ? "functionalities" : "spec_changes";
|
|
158
164
|
const model = options.model !== undefined ? options.model(ctx, plainFile, given.model, work) : given.model;
|
|
159
165
|
const loop = options.loop(ctx);
|
|
160
166
|
const request = { ...(model !== undefined ? { model } : {}), ...(given.guidance !== undefined ? { guidance: given.guidance } : {}) };
|
|
161
|
-
const statuses = given.from_scratch
|
|
162
|
-
? await loop.fromScratch(plainFile, request)
|
|
167
|
+
const statuses = given.from_scratch !== undefined
|
|
168
|
+
? await loop.fromScratch(plainFile, { ...request, chain: given.from_scratch === "chain" })
|
|
163
169
|
: again.length > 0
|
|
164
170
|
? await loop.regenerateAgain(plainFile, again, request)
|
|
165
171
|
: await loop.bringUp(plainFile, request);
|
package/dist/question-dialog.js
CHANGED
package/dist/regenerate/loop.js
CHANGED
|
@@ -597,7 +597,14 @@ export function createWorkLoop(options) {
|
|
|
597
597
|
}
|
|
598
598
|
return {
|
|
599
599
|
report: released !== undefined ? `${report}\n\n${released}` : report,
|
|
600
|
-
details: {
|
|
600
|
+
details: {
|
|
601
|
+
passed: result.landed,
|
|
602
|
+
landed: result.landed,
|
|
603
|
+
...(released !== undefined ? { released: true } : {}),
|
|
604
|
+
...(result.story !== undefined ? { story: result.story } : {}),
|
|
605
|
+
// The spec gaps its agents met, for a headless run to say - in the chat the report puts them to the person.
|
|
606
|
+
...(result.specGaps !== undefined && result.specGaps.length > 0 ? { specGaps: result.specGaps } : {}),
|
|
607
|
+
},
|
|
601
608
|
};
|
|
602
609
|
},
|
|
603
610
|
});
|
|
@@ -1240,17 +1247,21 @@ export function createWorkLoop(options) {
|
|
|
1240
1247
|
startAgain(entry, `it now requires ${requires.join(", ")}, which its code was not built on`);
|
|
1241
1248
|
return loop.bringUp(plainFile, request);
|
|
1242
1249
|
},
|
|
1243
|
-
async fromScratch(plainFile, request = {}) {
|
|
1250
|
+
async fromScratch(plainFile, { chain, ...request } = {}) {
|
|
1244
1251
|
const file = resolve(options.cwd, plainFile);
|
|
1245
|
-
// Where its code lives: the tips its module is built in, or - none built yet - its own codebase.
|
|
1246
|
-
|
|
1252
|
+
// Where its code lives: the tips its module is built in, or - none built yet - its own codebase. The whole chain:
|
|
1253
|
+
// every codebase in those chains, bottom first - a tip taken back to nothing is otherwise started from a copy of
|
|
1254
|
+
// the code below it.
|
|
1255
|
+
const chains = chainsFor(file);
|
|
1256
|
+
const codebases = [...new Map((chain === true ? chains.flat() : chains.map(tipOf)).map((entry) => [entry.buildDir, entry])).values()];
|
|
1247
1257
|
for (const entry of codebases) {
|
|
1248
1258
|
if (modules.get(entry.buildDir)?.running !== undefined)
|
|
1249
1259
|
throw new Error(`${entry.moduleName} has a pulse running: halt it, or ask again once it is done.`);
|
|
1250
1260
|
}
|
|
1261
|
+
const why = chain === true ? "asked to be regenerated from scratch, with its whole chain" : "asked to be regenerated from scratch";
|
|
1251
1262
|
for (const entry of codebases)
|
|
1252
1263
|
if (started(entry))
|
|
1253
|
-
startAgain(entry,
|
|
1264
|
+
startAgain(entry, why);
|
|
1254
1265
|
return loop.bringUp(plainFile, request);
|
|
1255
1266
|
},
|
|
1256
1267
|
halt(target, reason) {
|
|
@@ -1511,7 +1522,7 @@ export function describeModules(statuses, options = {}) {
|
|
|
1511
1522
|
` Ask the user whether to start ${codebases.join(", ")} over, and say why: ${codebases.join(", ")} was built without ` +
|
|
1512
1523
|
`${requires.join(", ")}, so no change to its code can bring ${requires.join(", ")} in - its code and conformance ` +
|
|
1513
1524
|
"tests are taken out, kept in its history, and built again from the bottom of the chain. Only if they agree, " +
|
|
1514
|
-
`call regenerate with ${basename(status.plainFile)} and from_scratch.`,
|
|
1525
|
+
`call regenerate with ${basename(status.plainFile)} and from_scratch: "module".`,
|
|
1515
1526
|
];
|
|
1516
1527
|
};
|
|
1517
1528
|
return statuses
|
|
@@ -1565,7 +1576,7 @@ const defaultConformanceRunner = async (job, onDelta) => {
|
|
|
1565
1576
|
writeTestsRecord(result.conformanceDir, job.planned.step.tree);
|
|
1566
1577
|
recordHistory(job.buildDir, `conformance tests: ${job.planned.title}`);
|
|
1567
1578
|
}
|
|
1568
|
-
return { landed: result.passed, report: describeSpecDiffConformance(result) };
|
|
1579
|
+
return { landed: result.passed, report: describeSpecDiffConformance(result), ...(result.specGaps !== undefined ? { specGaps: result.specGaps } : {}) };
|
|
1569
1580
|
};
|
|
1570
1581
|
/** A job as a spec diff render, landed on its unit tests: conformance is a track of its own. */
|
|
1571
1582
|
const defaultRunner = async (job, onDelta) => {
|
|
@@ -1581,7 +1592,9 @@ const defaultRunner = async (job, onDelta) => {
|
|
|
1581
1592
|
onDelta,
|
|
1582
1593
|
});
|
|
1583
1594
|
// Landed code can be tried.
|
|
1584
|
-
return { landed: result.passed, report: withTryItLine(describeSpecDiff(result), result.passed && result.restructured !== true), story: codeStory(result)
|
|
1595
|
+
return { landed: result.passed, report: withTryItLine(describeSpecDiff(result), result.passed && result.restructured !== true), story: codeStory(result),
|
|
1596
|
+
...(result.specGaps !== undefined ? { specGaps: result.specGaps } : {}),
|
|
1597
|
+
};
|
|
1585
1598
|
};
|
|
1586
1599
|
/** How a code pulse went, for its line in the notes: the rounds it took, its test runs, what the debugger found. */
|
|
1587
1600
|
export function codeStory(result) {
|
|
@@ -329,7 +329,7 @@ function detailLines(item, width, style) {
|
|
|
329
329
|
// The rest of its text - its first line is the row - against the record's, for a change.
|
|
330
330
|
const rest = item.state === "changed" ? lineDiff(item.was ?? "", item.text).slice(1) : item.text.split("\n").slice(1).map((line) => ({ was: line, now: line }));
|
|
331
331
|
for (const line of rest) {
|
|
332
|
-
const drawn = item.state === "removed" ? style.removed(strike(line.now ?? "")) : item.state === "changed" ? diffLine(line, style) :
|
|
332
|
+
const drawn = item.state === "removed" ? style.removed(strike(line.now ?? "")) : item.state === "changed" ? diffLine(line, style) : highlighted(line.now ?? "", style);
|
|
333
333
|
lines.push(...wrapped(drawn, width));
|
|
334
334
|
}
|
|
335
335
|
if (item.acceptance.length > 0) {
|
|
@@ -476,9 +476,7 @@ export function specMapLines(pages, state, width, style, _jobs = [], now = Date.
|
|
|
476
476
|
? style.removed(strike(first))
|
|
477
477
|
: item.state === "changed" && wasFirst !== first
|
|
478
478
|
? wordDiff(wasFirst, first, style)
|
|
479
|
-
:
|
|
480
|
-
? style.added(first)
|
|
481
|
-
: highlighted(first, style);
|
|
479
|
+
: highlighted(first, style);
|
|
482
480
|
// Wrapped, never cut short: what follows a `…` is what a person came to read. The lines after the first hang
|
|
483
481
|
// under the item's text, past its bullet.
|
|
484
482
|
const [head = "", ...rest] = hanging(drawn, first, room);
|
package/dist/regenerate-cli.js
CHANGED
|
@@ -19,6 +19,8 @@ its conformance tests too when they are on.
|
|
|
19
19
|
--start-over start over, without asking, a codebase regenerated without a module it now requires -
|
|
20
20
|
with no terminal to ask and without it, the run stops there
|
|
21
21
|
--from-scratch delete the module's code and conformance tests (its lineage keeps them) and regenerate all of it
|
|
22
|
+
--from-scratch-chain the same for every module of its requires chain, built again from the bottom - after
|
|
23
|
+
modules were split or moved
|
|
22
24
|
--harness-model <p/id> config.turri.yaml's harness-model for this run: the planner and the conformance chooser
|
|
23
25
|
--coding-model <p/id> its coding-model, --model too: the agents that build; p/id:level for how hard a model thinks
|
|
24
26
|
--judge-model <p/id> its judge-model: the judge and the test review
|
|
@@ -31,7 +33,7 @@ its conformance tests too when they are on.
|
|
|
31
33
|
as it starts, as each pulse ends and as it exits (docs/headless.md)
|
|
32
34
|
|
|
33
35
|
PLAIN_TEMPLATE_DIRS colon-separated template directories`;
|
|
34
|
-
const { positional, options } = parseArgs(process.argv.slice(2), ["--dry-run", "--start-over", "--from-scratch"], USAGE, ["--build-dir", "--report", "--conformance-tests", "--harness-model", "--judge-model", "--review"]);
|
|
36
|
+
const { positional, options } = parseArgs(process.argv.slice(2), ["--dry-run", "--start-over", "--from-scratch", "--from-scratch-chain"], USAGE, ["--build-dir", "--report", "--conformance-tests", "--harness-model", "--judge-model", "--review"]);
|
|
35
37
|
const { report, buildDir } = headlessOptions(options, true);
|
|
36
38
|
const named = positional[0] ?? readProjectConfig(process.cwd()).module;
|
|
37
39
|
// Functional specs named after the file: regenerated again though their spec did not change.
|
|
@@ -40,9 +42,10 @@ if (!named) {
|
|
|
40
42
|
console.error(`${USAGE}\n\nNo file given, and ${CONFIG_FILE} names no module.`);
|
|
41
43
|
process.exit(1);
|
|
42
44
|
}
|
|
45
|
+
const scratch = options["from-scratch-chain"] !== undefined ? "chain" : options["from-scratch"] !== undefined ? "module" : undefined;
|
|
43
46
|
// A dry run starts nothing, and from scratch deletes before it plans: the two do not go together.
|
|
44
|
-
if (
|
|
45
|
-
console.error(
|
|
47
|
+
if (scratch !== undefined && options["dry-run"] !== undefined) {
|
|
48
|
+
console.error(`--from-scratch${scratch === "chain" ? "-chain" : ""} deletes the code before anything is planned: it cannot be a dry run.`);
|
|
46
49
|
process.exit(1);
|
|
47
50
|
}
|
|
48
51
|
runLoop({
|
|
@@ -54,8 +57,8 @@ runLoop({
|
|
|
54
57
|
startOver: options["start-over"] !== undefined,
|
|
55
58
|
ask: (loop) => again.length > 0
|
|
56
59
|
? loop.regenerateAgain(named, again)
|
|
57
|
-
:
|
|
58
|
-
? loop.fromScratch(named)
|
|
60
|
+
: scratch !== undefined
|
|
61
|
+
? loop.fromScratch(named, { chain: scratch === "chain" })
|
|
59
62
|
: loop.bringUp(named),
|
|
60
63
|
}).catch((error) => {
|
|
61
64
|
console.error(error instanceof Error ? error.message : error);
|
package/dist/run-report.js
CHANGED
|
@@ -181,6 +181,18 @@ export function startRunReport(options) {
|
|
|
181
181
|
list,
|
|
182
182
|
};
|
|
183
183
|
};
|
|
184
|
+
/** The spec gaps the pulses' agents met, in the order the pulses started: questions a person would want to decide. */
|
|
185
|
+
const specGapsOf = (jobs) => jobs.flatMap((job) => {
|
|
186
|
+
const details = job.details;
|
|
187
|
+
return (details?.specGaps ?? []).map((gap) => ({
|
|
188
|
+
module: job.module ?? null,
|
|
189
|
+
pulse: job.pulse ?? null,
|
|
190
|
+
pulse_id: job.id,
|
|
191
|
+
landed: job.status === "succeeded" && details?.landed === true,
|
|
192
|
+
question: gap.question,
|
|
193
|
+
assumption: gap.assumption,
|
|
194
|
+
}));
|
|
195
|
+
});
|
|
184
196
|
const functionalitiesOf = (counts) => {
|
|
185
197
|
if (counts === undefined)
|
|
186
198
|
return null;
|
|
@@ -245,6 +257,7 @@ export function startRunReport(options) {
|
|
|
245
257
|
usage: usageOf(jobs),
|
|
246
258
|
pulses: pulsesOf(jobs, now),
|
|
247
259
|
functionalities: functionalitiesOf(attempt(sources.completion)),
|
|
260
|
+
spec_gaps: specGapsOf(jobs),
|
|
248
261
|
...(exitParts ?? {}),
|
|
249
262
|
logs: {
|
|
250
263
|
jobs_dir: fromProject(join(projectRoot, ".turri", "jobs")),
|
package/docs/headless.md
CHANGED
|
@@ -87,6 +87,8 @@ Useful options - `turri regenerate --help` lists all of them:
|
|
|
87
87
|
| `--conformance-tests <all\|chosen\|module\|false>` | `conformance-tests`, with its values (`true` is `all`) |
|
|
88
88
|
| `--review <true\|false>` | `review`: whether the code and its tests are reviewed once they pass |
|
|
89
89
|
| `--stack <name>` | build with the stack's `config.turri.<name>.yaml` over `config.turri.yaml`; default, `TURRI_STACK`, else none |
|
|
90
|
+
| `--from-scratch` | delete the module's code and conformance tests (its lineage keeps them) and regenerate all of it |
|
|
91
|
+
| `--from-scratch-chain` | the same for every module of its requires chain, the base's included, built again from the bottom |
|
|
90
92
|
| `--start-over` | start over, unasked, a codebase built without a module it now requires - without a terminal, a run that needs it and lacks it stops |
|
|
91
93
|
| `--build-dir <dir>` | as the run ends, export the code there - see below |
|
|
92
94
|
| `--report <file.json>` | write [the run report](#the-run-report) there |
|
|
@@ -104,7 +106,8 @@ Conformance tests follow `conformance-tests:` in `config.turri.yaml`, or `--conf
|
|
|
104
106
|
The command exits `0` only when every module is complete: its code regenerated from the spec as it is
|
|
105
107
|
now, and its conformance tests written for what is built when they are on - no pulse failed, nothing
|
|
106
108
|
halted, nothing waiting for a start over. Anything else exits `1`, and the last lines say which
|
|
107
|
-
module is not complete, and why.
|
|
109
|
+
module is not complete, and why. A behaviour the spec leaves open that an agent had to decide is printed
|
|
110
|
+
as a `spec gap` under its pulse, with what was decided meanwhile. The log says, per functionality, what was built and whether its
|
|
108
111
|
tests passed.
|
|
109
112
|
|
|
110
113
|
| | |
|
|
@@ -191,6 +194,11 @@ changes meaning or goes; new fields may appear in the same version. Paths in `pu
|
|
|
191
194
|
],
|
|
192
195
|
"codebases": ["plain_modules/app/code"]
|
|
193
196
|
},
|
|
197
|
+
"spec_gaps": [{ // [] when no agent met one
|
|
198
|
+
"module": "app", "pulse": 3, "pulse_id": "2a058729", "landed": true,
|
|
199
|
+
"question": "What happens to a contact archived in HubSpot after it was synced?",
|
|
200
|
+
"assumption": "An archived contact is left as synced - asserted in sync.test.ts, archived contacts"
|
|
201
|
+
}],
|
|
194
202
|
"build_dir_export": { "exported": true, "dir": "/work/out", "module": "app", "commit": "8036b3b", "files": 23 },
|
|
195
203
|
"logs": { "jobs_dir": ".turri/jobs", "debug_dir": ".turri/debug", "sessions_dir": ".turri/sessions" }
|
|
196
204
|
}
|
|
@@ -209,6 +217,13 @@ calls; a role or model turri could not tell is `unknown`.
|
|
|
209
217
|
stopped, or did not pass - and everything it wrote was undone. A pulse that failed is tried once more
|
|
210
218
|
before it counts as failed; both tries are the one pulse.
|
|
211
219
|
|
|
220
|
+
**Spec gaps.** A behaviour the spec leaves open that an agent met and decided on its own - reported with
|
|
221
|
+
`report_spec_gap` (see [regenerating](regenerating.md)). In the chat they are put to you as the pulse ends; a
|
|
222
|
+
headless run has no one to ask, so they are listed here, in the order the pulses started, and printed under the
|
|
223
|
+
pulse that met them. `assumption` is what the agent decided meanwhile, also in the codebase's notes'
|
|
224
|
+
assumptions while its pulse is `landed`; a pulse rolled back takes it out of the notes, but the question still
|
|
225
|
+
stands. Settle the ones you care about in the spec and run again.
|
|
226
|
+
|
|
212
227
|
**Completed functional specs** - the pass rate, the figure to compare runs by.
|
|
213
228
|
A functional spec is completed when the code record of the codebase the run delivers - the
|
|
214
229
|
furthest-built of the chain, the one `--build-dir` exports - as last committed, holds it as the spec has
|
|
@@ -227,7 +242,7 @@ and modules that are complete are skipped, so it picks up where it stopped - aft
|
|
|
227
242
|
provider outage, or a machine that went to sleep anyway. Whatever the stopped run left
|
|
228
243
|
half-written is undone first: each step starts from the module's last commit. A functionality whose spec has changed
|
|
229
244
|
since counts as not built, so editing the spec and re-running rebuilds what changed. `--from-scratch`
|
|
230
|
-
rebuilds
|
|
245
|
+
rebuilds the module; `--from-scratch-chain` rebuilds every module of its chain.
|
|
231
246
|
|
|
232
247
|
## Rebuilding one functionality
|
|
233
248
|
|
package/docs/regenerating.md
CHANGED
|
@@ -718,7 +718,7 @@ with no agent and no testing script. Moving specs into a module the code was not
|
|
|
718
718
|
change to the code brings that module's code in, so the codebase stops: it has to be started over - its code
|
|
719
719
|
and conformance tests taken out, committed so its history keeps them, and the chain built again from the
|
|
720
720
|
bottom. That is never done unasked. In the chat the model asks the person, saying why, and calls
|
|
721
|
-
`regenerate` with `from_scratch` once they agree; `turri regenerate` and `spec-diff` ask at the terminal, and
|
|
721
|
+
`regenerate` with `from_scratch: "module"` once they agree; `turri regenerate` and `spec-diff` ask at the terminal, and
|
|
722
722
|
`--start-over` agrees in advance - with no terminal and no flag, the run stops there, exit 1, saying so.
|
|
723
723
|
|
|
724
724
|
## Conformance tests
|
|
@@ -1111,7 +1111,9 @@ are the next agent's, and a gap is yours. An agent reports one with `report_spec
|
|
|
1111
1111
|
and the assumption it made meanwhile; turri writes that assumption into the notes' assumptions, so the code built on
|
|
1112
1112
|
it is known for what it is. The gaps a pulse's agents report are said in its report - "Spec gaps its agents met ... Put
|
|
1113
1113
|
each to the user", each with its assumption - so the conversation asks you, and writes the ones you decide into the
|
|
1114
|
-
spec; the pulse that builds the answer finds the assumption it replaces in the notes, and the tests it names.
|
|
1114
|
+
spec; the pulse that builds the answer finds the assumption it replaces in the notes, and the tests it names.
|
|
1115
|
+
A headless run has no one to ask: it prints each gap under its pulse, and lists them in its run report's
|
|
1116
|
+
`spec_gaps` ([running unattended](headless.md#the-run-report)). However many gaps a pulse's agents report, they
|
|
1115
1117
|
are put to you - never a reason to stop the pulse; the tool is guarded like any other. A notes
|
|
1116
1118
|
file from before keeps no spec gaps section: its lines are dropped as it is read.
|
|
1117
1119
|
|
package/docs/skills-and-gates.md
CHANGED
|
@@ -133,7 +133,9 @@ change the step makes and puts its findings on the executor's next tool result.
|
|
|
133
133
|
- **Interview the user.** `ask_user` puts one question to the person. In the TUI it is a dialog;
|
|
134
134
|
from the CLI the options are numbered. Offering choices is required, not optional - a call with
|
|
135
135
|
neither `options` nor `free_form: true` is refused - and every list carries "Something else…".
|
|
136
|
-
`multi_select: true`
|
|
136
|
+
`multi_select: true` gives every option a box, ticked and unticked as it is chosen - a pick made by mistake is
|
|
137
|
+
undone by choosing it again - until they choose Done; what they type is one more box, ticked. "Something
|
|
138
|
+
else…", "Chat about this" and Done are no boxes. The skill's agent talks in a
|
|
137
139
|
session of its own that the person never sees, so what the person must read to answer - the brief
|
|
138
140
|
to confirm, the plan to approve - it passes as `ask_user`'s `context`, shown above the question in
|
|
139
141
|
the dialog; `brood`, `spawn` and `bud` pass their briefs so. The agents are told to keep it to what can be taken in at
|
package/docs/using.md
CHANGED
|
@@ -50,7 +50,7 @@ where it stopped. A project can have skills of its own, in `.turri/skills/` or `
|
|
|
50
50
|
|
|
51
51
|
| tool | |
|
|
52
52
|
| --- | --- |
|
|
53
|
-
| `regenerate` | regenerate a module and its chain from the spec as it is now - after any change to it, by any means: what is left to regenerate is planned into pulses of one to three functional specs, each with the definitions and reqs it uses, and a pulse regenerating what the spec no longer wants is stopped. With functionalities named, those are regenerated though their spec did not change - rewritten or started over, planned into pulses as any change is; with `from_scratch`, the whole module, its code deleted first - only when you ask, or agree |
|
|
53
|
+
| `regenerate` | regenerate a module and its chain from the spec as it is now - after any change to it, by any means: what is left to regenerate is planned into pulses of one to three functional specs, each with the definitions and reqs it uses, and a pulse regenerating what the spec no longer wants is stopped. With functionalities named, those are regenerated though their spec did not change - rewritten or started over, planned into pulses as any change is; with `from_scratch`, the whole module, its code deleted first - `"chain"` for every module of its requires chain too, built again from the bottom - only when you ask, or agree |
|
|
54
54
|
| `drift` | what is left to regenerate: how far the code and its conformance tests have drifted from the spec, starting nothing |
|
|
55
55
|
| `glimpse` | the pulse a module's codebases would start next, and the whole prompt its coding agent would get - starting nothing |
|
|
56
56
|
| `steer` | say how the work is planned - which functional specs go together, what first - for the project or one module, for the session; the next plan follows it, beside what AGENTS.md says under a heading naming the queue |
|
|
@@ -63,7 +63,7 @@ where it stopped. A project can have skills of its own, in `.turri/skills/` or `
|
|
|
63
63
|
| `lineage` | a module's lineage: what each regeneration and conformance run changed in it, and what it was regenerated from |
|
|
64
64
|
| `cultivate` | run a gated skill, its steps and review gates enforced |
|
|
65
65
|
| `release`, `activate` | try what is regenerated: release it to `dist/<module>` and activate it on this machine, installing what it needs - see [deploy](deploy.md) |
|
|
66
|
-
| `ask_user` | put a question to you as a dialog to pick from - several at once, "Something else…" to type, or "Chat about this" to talk it over first; Esc in either goes back to the options |
|
|
66
|
+
| `ask_user` | put a question to you as a dialog to pick from - several at once, each a box you tick and untick, "Something else…" to type, or "Chat about this" to talk it over first; Esc in either goes back to the options |
|
|
67
67
|
| `web_search`, `web_fetch` | search the web and read a page as text - documentation, an API, an error; `curl` and the like run in its `bash` |
|
|
68
68
|
|
|
69
69
|
What each render does, in order, is in [regenerating the code](regenerating.md).
|
|
@@ -174,8 +174,7 @@ come waits for a free slot, and says so.
|
|
|
174
174
|
regenerates. Pulses are numbered as they start in a module, from 1 each session.
|
|
175
175
|
|
|
176
176
|
A changed item shows what changed in it: its first line word by word - what is gone struck through in red,
|
|
177
|
-
what is new in green, what is kept highlighted as it reads now - and, under it, only the sub-bullets that changed. A
|
|
178
|
-
item - not in the code yet - is green, all of it, so a new functional spec stands apart from one changed. A spec never built that is taken
|
|
177
|
+
what is new in green, what is kept highlighted as it reads now - and, under it, only the sub-bullets that changed. A spec never built that is taken
|
|
179
178
|
out is simply gone. The top line has a strip of the module's functional specs - built, regenerating, planned,
|
|
180
179
|
waiting - and how many are built, then what is happening: `pulse 3 of 8 regenerating`, or why nothing is -
|
|
181
180
|
`not asked to build - regenerate plans it` (turri was just opened, or nothing has asked for the module: the
|
|
@@ -239,7 +238,7 @@ come waits for a free slot, and says so.
|
|
|
239
238
|
is laid over `config.turri.yaml` - its testing scripts, toolchains, models - for the session; a pulse already
|
|
240
239
|
running keeps the stack it started with. The chat model switches it too when you ask. See
|
|
241
240
|
[stacks](config.md#stacks).
|
|
242
|
-
- **`/regenerate [module | file.plain] [functionality ...] [from scratch] [how to cut the work]`** regenerates a module from its spec as it is now, as
|
|
241
|
+
- **`/regenerate [module | file.plain] [functionality ...] [from scratch [whole chain]] [how to cut the work]`** regenerates a module from its spec as it is now, as
|
|
243
242
|
the `regenerate` tool does: what is left to regenerate is planned into pulses - one functional spec at a time, a few
|
|
244
243
|
together or the whole module in one, as the planner judges - and the first starts. Words after the module are
|
|
245
244
|
guidance for that plan (`/regenerate app models first`). A module's name typed with spaces for its underscores is
|
|
@@ -247,8 +246,13 @@ come waits for a free slot, and says so.
|
|
|
247
246
|
`config.turri.yaml` names, else the folder's top modules, to pick from. Asked for from scratch - `from scratch`,
|
|
248
247
|
`start over`, `delete the module` - it asks first, then deletes the module's code and conformance tests in every
|
|
249
248
|
codebase where they live, committed so `/lineage` keeps them, and regenerates the whole module from its spec,
|
|
250
|
-
built though nothing in it changed: `/regenerate shorty from scratch`. The
|
|
251
|
-
|
|
249
|
+
built though nothing in it changed: `/regenerate shorty from scratch`. The modules below it keep their code, and
|
|
250
|
+
the module starts again from a copy of the code below it. Asked for its chain too - `/regenerate shorty from scratch,
|
|
251
|
+
the whole chain` - the code of every module of its requires chain is deleted, the base's included, and the whole
|
|
252
|
+
chain is regenerated from the bottom: what to do after modules were split, merged or moved, when the code below was
|
|
253
|
+
laid out for modules that are no more. The chat's `regenerate` does the same with `from_scratch: "module"` or
|
|
254
|
+
`"chain"`, when you ask for it; `turri regenerate --from-scratch` or `--from-scratch-chain` on the command line, not
|
|
255
|
+
with `--dry-run`.
|
|
252
256
|
Functionalities named after the module - `/regenerate shorty 3 5` - are regenerated though their spec did not
|
|
253
257
|
change, on the model chosen for functionalities: as `turri regenerate shorty.plain 3 5` does, and the chat's
|
|
254
258
|
`regenerate` with `functionalities`.
|
|
@@ -454,6 +458,7 @@ turri regenerate <file.plain> # regenerate what the code is not bui
|
|
|
454
458
|
turri glimpse <file.plain> # plan it, and print the first job's prompt - starts nothing
|
|
455
459
|
turri regenerate <file.plain> 2 # regenerate functionality 2 again, changed or not
|
|
456
460
|
turri regenerate <file.plain> --from-scratch # delete the module's code and tests (its lineage keeps them), regenerate all of it
|
|
461
|
+
turri regenerate <file.plain> --from-scratch-chain # the same for every module of its requires chain, from the bottom
|
|
457
462
|
turri spec-diff <file.plain> <diff> # apply a spec diff, and regenerate it
|
|
458
463
|
turri conform <file.plain> 1 # black-box tests for functionality 1
|
|
459
464
|
turri lineage <module> # what each regeneration changed in a module, and when
|
|
@@ -21,6 +21,8 @@ allowed-tools: [read, write, edit, bash, ls, grep, find]
|
|
|
21
21
|
|
|
22
22
|
The refactoring is purely structural. The **combined behavior** of the resulting modules must be **identical** to the original module. No functional spec is added, removed, or weakened. The split is semantic — grouping related specs together — not behavioral.
|
|
23
23
|
|
|
24
|
+
The spec is structural; the code is not. Once modules are split and specs move between them, the code already built no longer matches the module layout: specs move into modules the code was never built on, and no change to that code brings them in. **After a refactor, the whole requires chain is regenerated from scratch** — the existing code and conformance tests are taken out (their lineage keeps them) and every module is built again from the base of the chain upward. Say so up front: it is part of what the user agrees to (Phase 2e), and it is the last step of the skill (Phase 5).
|
|
25
|
+
|
|
24
26
|
## Input
|
|
25
27
|
|
|
26
28
|
The `.plain` file to refactor, plus guidance from the user on the desired grouping (or ask the user to confirm your proposed grouping).
|
|
@@ -90,8 +92,9 @@ Summarize for the user:
|
|
|
90
92
|
- Any new import module being created
|
|
91
93
|
- Which concepts are exported from each module
|
|
92
94
|
- Confirmation that all original specs are accounted for (none lost)
|
|
95
|
+
- That the whole chain will be **regenerated from scratch** once the split is done: the code and conformance tests built so far are taken out (kept in the lineage) and everything is built again from the base module up — this takes as long and costs as much as building the original module did
|
|
93
96
|
|
|
94
|
-
Get explicit user confirmation before proceeding.
|
|
97
|
+
Get explicit user confirmation before proceeding — of the split and of regenerating from scratch.
|
|
95
98
|
|
|
96
99
|
## Phase 3 — Execute the Split
|
|
97
100
|
|
|
@@ -162,6 +165,19 @@ For each new module:
|
|
|
162
165
|
|
|
163
166
|
Read each new `.plain` file in full and present the final structure to the user for approval. If the user requests changes, apply them and re-verify.
|
|
164
167
|
|
|
168
|
+
Validate each new module with `turri validate <module>.plain` (or the `validate` tool) before going on: a module that does not parse cannot be regenerated.
|
|
169
|
+
|
|
170
|
+
## Phase 5 — Regenerate the Chain from Scratch
|
|
171
|
+
|
|
172
|
+
The split moves specs between modules, so the code built from the original module cannot be patched into the new layout: the chain is regenerated from scratch. Do not try to keep or reuse the existing code, and do not regenerate only what looks changed — modules have moved, and a codebase built without a module it now requires has to be started over anyway.
|
|
173
|
+
|
|
174
|
+
1. **Remind the user** what regenerating from scratch does — the code and conformance tests built so far are taken out, committed so the lineage keeps them, and the whole chain is built again from the base up — and that they already agreed to it in Phase 2e. Proceed only on that agreement.
|
|
175
|
+
2. **Regenerate the tip of the chain from scratch, with its whole chain** — the last module, the one everything else is required by. From scratch for the tip alone is not enough: the modules below keep their code, and the tip starts from a copy of it, laid out for the old modules. With the whole chain, every module's code is taken out, the base's included, and the chain is built again from the bottom:
|
|
176
|
+
- in the chat: the `regenerate` tool on the tip's `.plain` file with `from_scratch: "chain"`, or `/regenerate <tip> from scratch, the whole chain`;
|
|
177
|
+
- on the command line: `turri regenerate <tip>.plain --from-scratch-chain`.
|
|
178
|
+
3. **Modules that require the chain** (updated in 3d) are regenerated after it, from scratch too (`from_scratch: "module"`): the modules they require are not the ones their code was built on. When the tip's whole chain is regenerated, a module built on top of it is a chain of its own — regenerating it from scratch with its whole chain instead covers the tip's chain too, in one go.
|
|
179
|
+
4. **The original module's codebase** (`plain_modules/<original-module>/`), when the original module was deleted or renamed, is no longer built from anything. Tell the user it is there; it is theirs to delete. Do not delete it unasked.
|
|
180
|
+
|
|
165
181
|
## Common Pitfalls
|
|
166
182
|
|
|
167
183
|
### Splitting too aggressively
|
|
@@ -179,9 +195,12 @@ This is a refactoring, not a rewrite. Spec text must be copied verbatim. If a sp
|
|
|
179
195
|
### Losing acceptance tests
|
|
180
196
|
Acceptance tests are nested under their parent functional spec. When moving a spec to a new module, its acceptance tests must move with it.
|
|
181
197
|
|
|
198
|
+
### Regenerating only what changed
|
|
199
|
+
After a split the existing code was built for a module layout that no longer exists. Regenerating incrementally either stops at a codebase that has to be started over or builds on code laid out for the old modules. Regenerate the whole chain from scratch.
|
|
200
|
+
|
|
182
201
|
## Validation Checklist
|
|
183
202
|
|
|
184
|
-
- [ ] User confirmed the split plan before execution
|
|
203
|
+
- [ ] User confirmed the split plan before execution, including regenerating the whole chain from scratch
|
|
185
204
|
- [ ] All original functional specs are present in exactly one new module — none lost
|
|
186
205
|
- [ ] Spec text is identical to the original — no rewording
|
|
187
206
|
- [ ] Acceptance tests moved with their parent specs
|
|
@@ -194,3 +213,6 @@ Acceptance tests are nested under their parent functional spec. When moving a sp
|
|
|
194
213
|
- [ ] Original module removed or updated
|
|
195
214
|
- [ ] Downstream modules (if any) updated to require the chain tip, plus any earlier chain modules whose exported concepts they reference
|
|
196
215
|
- [ ] User approved the final result
|
|
216
|
+
- [ ] Every new module validates
|
|
217
|
+
- [ ] The tip of the chain regenerated from scratch with its whole chain, then any module that requires it
|
|
218
|
+
- [ ] User told about the original module's leftover codebase, if there is one
|