@llblab/pi-kit 0.3.2 → 0.4.0
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/AGENTS.md +3 -3
- package/BACKLOG.md +1 -1
- package/CHANGELOG.md +6 -0
- package/README.md +19 -10
- package/node_modules/@llblab/pi-actors/AGENTS.md +1 -0
- package/node_modules/@llblab/pi-actors/CHANGELOG.md +6 -0
- package/node_modules/@llblab/pi-actors/dist/skills/actors/SKILL.md +16 -6
- package/node_modules/@llblab/pi-actors/dist/skills/music-player/SKILL.md +3 -3
- package/node_modules/@llblab/pi-actors/dist/skills/music-player/genapps/music-player.mjs +6 -4
- package/node_modules/@llblab/pi-actors/dist/skills/music-player/scripts/playback.mjs +85 -18
- package/node_modules/@llblab/pi-actors/dist/skills/swarm/SKILL.md +2 -6
- package/node_modules/@llblab/pi-actors/dist/skills/swarm/references/development-swarm.md +2 -31
- package/node_modules/@llblab/pi-actors/docs/recipe-library.md +1 -1
- package/node_modules/@llblab/pi-actors/package.json +1 -1
- package/node_modules/@llblab/pi-actors/skills/actors/SKILL.md +16 -6
- package/node_modules/@llblab/pi-actors/skills/music-player/SKILL.md +3 -3
- package/node_modules/@llblab/pi-actors/skills/music-player/genapps/music-player.mjs +6 -4
- package/node_modules/@llblab/pi-actors/skills/music-player/scripts/playback.mjs +85 -18
- package/node_modules/@llblab/pi-actors/skills/swarm/SKILL.md +2 -6
- package/node_modules/@llblab/pi-actors/skills/swarm/references/development-swarm.md +2 -31
- package/node_modules/@llblab/pi-clean-room/AGENTS.md +5 -0
- package/node_modules/@llblab/pi-clean-room/BACKLOG.md +3 -0
- package/node_modules/@llblab/pi-clean-room/CHANGELOG.md +15 -0
- package/node_modules/@llblab/pi-clean-room/README.md +61 -0
- package/node_modules/@llblab/pi-clean-room/banner.jpg +0 -0
- package/node_modules/@llblab/pi-clean-room/index.ts +178 -0
- package/node_modules/@llblab/pi-clean-room/package.json +53 -0
- package/node_modules/@llblab/pi-grow-loop/AGENTS.md +1 -1
- package/node_modules/@llblab/pi-grow-loop/CHANGELOG.md +4 -0
- package/node_modules/@llblab/pi-grow-loop/package.json +1 -1
- package/node_modules/@llblab/pi-grow-loop/skills/grow-loop/SKILL.md +3 -17
- package/node_modules/@llblab/pi-state-flow/AGENTS.md +28 -0
- package/node_modules/@llblab/pi-state-flow/BACKLOG.md +3 -0
- package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +79 -0
- package/node_modules/@llblab/pi-state-flow/README.md +197 -0
- package/node_modules/@llblab/pi-state-flow/index.ts +4 -0
- package/node_modules/@llblab/pi-state-flow/lib/context.ts +88 -0
- package/node_modules/@llblab/pi-state-flow/lib/episode.ts +29 -0
- package/node_modules/@llblab/pi-state-flow/lib/extension.ts +238 -0
- package/node_modules/@llblab/pi-state-flow/lib/json.ts +78 -0
- package/node_modules/@llblab/pi-state-flow/lib/recovery.ts +38 -0
- package/node_modules/@llblab/pi-state-flow/lib/session.ts +49 -0
- package/node_modules/@llblab/pi-state-flow/lib/skills.ts +58 -0
- package/node_modules/@llblab/pi-state-flow/lib/snapshot.ts +103 -0
- package/node_modules/@llblab/pi-state-flow/lib/state.ts +20 -0
- package/node_modules/@llblab/pi-state-flow/lib/status.ts +16 -0
- package/node_modules/@llblab/pi-state-flow/lib/terminal.ts +148 -0
- package/node_modules/@llblab/pi-state-flow/lib/transition.ts +47 -0
- package/node_modules/@llblab/pi-state-flow/lib/validation.ts +27 -0
- package/node_modules/@llblab/pi-state-flow/package.json +55 -0
- package/node_modules/@llblab/pi-telegram/BACKLOG.md +2 -0
- package/node_modules/@llblab/pi-telegram/CHANGELOG.md +12 -0
- package/node_modules/@llblab/pi-telegram/docs/multi-instance-bus.md +17 -5
- package/node_modules/@llblab/pi-telegram/index.ts +9 -0
- package/node_modules/@llblab/pi-telegram/lib/bus-follower.ts +29 -18
- package/node_modules/@llblab/pi-telegram/lib/bus-leader.ts +15 -6
- package/node_modules/@llblab/pi-telegram/lib/bus.ts +11 -4
- package/node_modules/@llblab/pi-telegram/lib/routing.ts +89 -32
- package/node_modules/@llblab/pi-telegram/lib/sync.ts +74 -15
- package/node_modules/@llblab/pi-telegram/lib/telegram-api.ts +32 -1
- package/node_modules/@llblab/pi-telegram/lib/thread-reconciler.ts +17 -0
- package/node_modules/@llblab/pi-telegram/lib/threads.ts +123 -15
- package/node_modules/@llblab/pi-telegram/package.json +1 -1
- package/node_modules/@llblab/pi-telegram/skills/telegram-bridge/references/diagnosis.md +4 -2
- package/node_modules/@llblab/skills/abcd-context/AGENTS.md +40 -0
- package/node_modules/@llblab/skills/abcd-context/BACKLOG.md +3 -0
- package/node_modules/@llblab/skills/abcd-context/CHANGELOG.md +19 -0
- package/node_modules/@llblab/skills/abcd-context/README.md +30 -0
- package/node_modules/@llblab/skills/abcd-context/SKILL.md +122 -0
- package/node_modules/@llblab/skills/abcd-context/docs/README.md +9 -0
- package/node_modules/@llblab/skills/abcd-context/docs/protocols.md +199 -0
- package/node_modules/@llblab/skills/abcd-context/docs/templates.md +194 -0
- package/node_modules/@llblab/skills/abcd-context/docs/validation-design.md +117 -0
- package/node_modules/@llblab/skills/abcd-context/fixtures/abcd-project/AGENTS.md +10 -0
- package/node_modules/@llblab/skills/abcd-context/fixtures/abcd-project/BACKLOG.md +5 -0
- package/node_modules/@llblab/skills/abcd-context/fixtures/abcd-project/CHANGELOG.md +5 -0
- package/node_modules/@llblab/skills/abcd-context/fixtures/abcd-project/README.md +10 -0
- package/node_modules/@llblab/skills/abcd-context/fixtures/abcd-project/docs/README.md +7 -0
- package/node_modules/@llblab/skills/abcd-context/fixtures/abcd-project/docs/overview.md +3 -0
- package/node_modules/@llblab/skills/abcd-context/recipes/validate-context.json +7 -0
- package/node_modules/@llblab/skills/abcd-context/scripts/_self-test.mjs +244 -0
- package/node_modules/@llblab/skills/abcd-context/scripts/validate-context.mjs +497 -0
- package/node_modules/@llblab/skills/brain-storm/AGENTS.md +5 -0
- package/node_modules/@llblab/skills/brain-storm/SKILL.md +321 -0
- package/node_modules/@llblab/skills/coding-contract/SKILL.md +228 -0
- package/node_modules/@llblab/skills/domain-dag/AGENTS.md +6 -0
- package/node_modules/@llblab/skills/domain-dag/SKILL.md +257 -0
- package/node_modules/@llblab/skills/domain-dag/domain-dag.json +9 -0
- package/node_modules/@llblab/skills/domain-dag/recipes/validate-domain-dag.json +7 -0
- package/node_modules/@llblab/skills/domain-dag/scripts/validate-domain-dag.mjs +771 -0
- package/node_modules/@llblab/skills/domain-dag/scripts/validate-domain-dag.sh +4 -0
- package/node_modules/@llblab/skills/edge-tts/AGENTS.md +19 -0
- package/node_modules/@llblab/skills/edge-tts/SKILL.md +56 -0
- package/node_modules/@llblab/skills/edge-tts/recipes/say-edge.json +9 -0
- package/node_modules/@llblab/skills/edge-tts/scripts/say.mjs +1548 -0
- package/node_modules/@llblab/skills/edge-tts/scripts/say.sh +4 -0
- package/node_modules/@llblab/skills/extra-self/SKILL.md +229 -0
- package/node_modules/@llblab/skills/frontend-design/SKILL.md +78 -0
- package/node_modules/@llblab/skills/groq-stt/AGENTS.md +15 -0
- package/node_modules/@llblab/skills/groq-stt/SKILL.md +35 -0
- package/node_modules/@llblab/skills/groq-stt/recipes/transcribe-groq.json +10 -0
- package/node_modules/@llblab/skills/groq-stt/scripts/transcribe.mjs +190 -0
- package/node_modules/@llblab/skills/groq-stt/scripts/transcribe.sh +4 -0
- package/node_modules/@llblab/skills/mistral-stt/AGENTS.md +15 -0
- package/node_modules/@llblab/skills/mistral-stt/SKILL.md +35 -0
- package/node_modules/@llblab/skills/mistral-stt/recipes/transcribe-mistral.json +10 -0
- package/node_modules/@llblab/skills/mistral-stt/scripts/transcribe.mjs +191 -0
- package/node_modules/@llblab/skills/mistral-stt/scripts/transcribe.sh +4 -0
- package/node_modules/@llblab/skills/package.json +46 -0
- package/node_modules/@llblab/skills/re-review/SKILL.md +318 -0
- package/node_modules/@llblab/skills/release-flow/SKILL.md +456 -0
- package/node_modules/@llblab/skills/show-me/SKILL.md +144 -0
- package/package.json +20 -8
- package/node_modules/@llblab/pi-actors/dist/skills/music-player/scripts/playback-client.mjs +0 -143
- package/node_modules/@llblab/pi-actors/skills/music-player/scripts/playback-client.mjs +0 -143
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
watch,
|
|
28
28
|
writeFileSync,
|
|
29
29
|
} from "node:fs";
|
|
30
|
-
import { createServer } from "node:net";
|
|
30
|
+
import { createConnection, createServer } from "node:net";
|
|
31
31
|
import { homedir } from "node:os";
|
|
32
32
|
import {
|
|
33
33
|
basename,
|
|
@@ -66,7 +66,7 @@ let updateRunControlStatusInStateDir;
|
|
|
66
66
|
let isAlive;
|
|
67
67
|
let verifyRunProcessIdentity;
|
|
68
68
|
let appendRunTraceEvent = () => {};
|
|
69
|
-
|
|
69
|
+
async function loadActorAdapter() {
|
|
70
70
|
({
|
|
71
71
|
appendRunControlInStateDir,
|
|
72
72
|
claimRunControlByIdInStateDir,
|
|
@@ -110,8 +110,9 @@ function usage() {
|
|
|
110
110
|
playback.mjs control <state-dir> <play|pause|toggle|next|previous|seek|volume|stop|status> [percent]
|
|
111
111
|
|
|
112
112
|
Runs a foreground music player so pi-actors can own it as a controlled Run.
|
|
113
|
-
|
|
114
|
-
|
|
113
|
+
Actor-owned controls use canonical records in <state-dir>/controls.jsonl.
|
|
114
|
+
Standalone controls use the generation-fenced playback service endpoint.
|
|
115
|
+
Prefer message target=run:<run> action=<command> for Actors; external adapters use control <state-dir> <action>.
|
|
115
116
|
Supported players: auto, mpv, afplay, ffplay, cvlc, play, wmp.
|
|
116
117
|
`);
|
|
117
118
|
}
|
|
@@ -1134,6 +1135,7 @@ function readAndClearCommand(ctx) {
|
|
|
1134
1135
|
}
|
|
1135
1136
|
|
|
1136
1137
|
async function playMain(args) {
|
|
1138
|
+
if (actorAdapterEnabled) await loadActorAdapter();
|
|
1137
1139
|
const [
|
|
1138
1140
|
sourceArg,
|
|
1139
1141
|
loopArg = "true",
|
|
@@ -1333,30 +1335,74 @@ function projectCurrentProgress(status, nowMs = Date.now()) {
|
|
|
1333
1335
|
};
|
|
1334
1336
|
}
|
|
1335
1337
|
|
|
1336
|
-
function actorControlAvailability(stateDir) {
|
|
1338
|
+
async function actorControlAvailability(stateDir) {
|
|
1337
1339
|
const run = readJsonFile(join(stateDir, "run.json"), {});
|
|
1338
1340
|
const result = readJsonFile(join(stateDir, "result.json"), {});
|
|
1339
1341
|
const endpoint = readJsonFile(join(stateDir, "control-endpoint.json"), {});
|
|
1340
1342
|
const playerStatus = readJsonFile(join(stateDir, "player.json"), {});
|
|
1343
|
+
const runInstanceId = typeof run.run_instance_id === "string"
|
|
1344
|
+
? run.run_instance_id
|
|
1345
|
+
: undefined;
|
|
1346
|
+
// Inactive status must remain readable without an installed Actor runtime,
|
|
1347
|
+
// including standalone state beside metadata from a rejected Actor launch.
|
|
1348
|
+
if (!runInstanceId || typeof result.completedAt === "string" ||
|
|
1349
|
+
endpoint.run_instance_id !== runInstanceId ||
|
|
1350
|
+
!["playing", "paused"].includes(playerStatus.state)) {
|
|
1351
|
+
return { available: false, runInstanceId };
|
|
1352
|
+
}
|
|
1353
|
+
if (!verifyRunProcessIdentity) {
|
|
1354
|
+
await loadActorAdapter();
|
|
1355
|
+
// Re-read authority after the asynchronous import before admitting control.
|
|
1356
|
+
return actorControlAvailability(stateDir);
|
|
1357
|
+
}
|
|
1341
1358
|
const pid = Number(run.pid || 0);
|
|
1342
1359
|
const hasProcessIdentity = pid > 0 || run.process_identity !== undefined;
|
|
1343
1360
|
const processIdentity = pid > 0
|
|
1344
1361
|
? verifyRunProcessIdentity(pid, run.process_identity)
|
|
1345
1362
|
: { valid: false };
|
|
1346
|
-
const available =
|
|
1347
|
-
typeof run.run_instance_id === "string" &&
|
|
1348
|
-
typeof result.completedAt !== "string" &&
|
|
1349
|
-
(!hasProcessIdentity || (pid > 0 && isAlive(pid) && processIdentity.valid === true)) &&
|
|
1350
|
-
endpoint.run_instance_id === run.run_instance_id &&
|
|
1351
|
-
["playing", "paused"].includes(playerStatus.state);
|
|
1352
1363
|
return {
|
|
1353
|
-
available
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
: undefined,
|
|
1364
|
+
available: !hasProcessIdentity ||
|
|
1365
|
+
(pid > 0 && isAlive(pid) && processIdentity.valid === true),
|
|
1366
|
+
runInstanceId,
|
|
1357
1367
|
};
|
|
1358
1368
|
}
|
|
1359
1369
|
|
|
1370
|
+
async function sendPlaybackCommand(endpoint, action, input) {
|
|
1371
|
+
const payload = `${JSON.stringify({
|
|
1372
|
+
action,
|
|
1373
|
+
...(input !== undefined ? { input } : {}),
|
|
1374
|
+
service_instance_id: endpoint.service_instance_id,
|
|
1375
|
+
})}\n`;
|
|
1376
|
+
const response = await new Promise((resolveResponse, rejectResponse) => {
|
|
1377
|
+
const socket = createConnection(endpoint.path);
|
|
1378
|
+
let content = "";
|
|
1379
|
+
const timeout = setTimeout(() => {
|
|
1380
|
+
socket.destroy(new Error("playback service command timed out"));
|
|
1381
|
+
}, 5_000);
|
|
1382
|
+
timeout.unref?.();
|
|
1383
|
+
socket.setEncoding("utf8");
|
|
1384
|
+
socket.on("connect", () => socket.write(payload));
|
|
1385
|
+
socket.on("data", (chunk) => {
|
|
1386
|
+
content += chunk;
|
|
1387
|
+
if (Buffer.byteLength(content, "utf8") > 4096) {
|
|
1388
|
+
socket.destroy(new Error("playback service response is too large"));
|
|
1389
|
+
}
|
|
1390
|
+
});
|
|
1391
|
+
socket.on("close", () => clearTimeout(timeout));
|
|
1392
|
+
socket.on("error", rejectResponse);
|
|
1393
|
+
socket.on("end", () => {
|
|
1394
|
+
try {
|
|
1395
|
+
resolveResponse(JSON.parse(content.trim()));
|
|
1396
|
+
} catch {
|
|
1397
|
+
rejectResponse(new Error("playback service returned invalid JSON"));
|
|
1398
|
+
}
|
|
1399
|
+
});
|
|
1400
|
+
});
|
|
1401
|
+
if (response?.ok !== true) {
|
|
1402
|
+
throw new Error(response?.error || "playback service rejected the command");
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1360
1406
|
async function controlMain(args) {
|
|
1361
1407
|
const stateDir = expandPath(args[0] || "");
|
|
1362
1408
|
const command = args[1] || "status";
|
|
@@ -1365,13 +1411,20 @@ async function controlMain(args) {
|
|
|
1365
1411
|
usage();
|
|
1366
1412
|
process.exit(2);
|
|
1367
1413
|
}
|
|
1368
|
-
|
|
1414
|
+
if (!CONTROL_COMMANDS.has(command)) fail(`unsupported command: ${command}`, 2);
|
|
1415
|
+
const endpoint = readJsonFile(join(stateDir, "playback-endpoint.json"), {});
|
|
1416
|
+
// A standalone service retains authority even if an unsuccessful Actor launch
|
|
1417
|
+
// left Run metadata beside its endpoint. Clients never start or adopt it.
|
|
1418
|
+
const actorOwned = endpoint.owner_mode !== "standalone" &&
|
|
1419
|
+
existsSync(join(stateDir, "run.json"));
|
|
1369
1420
|
if (command === "status") {
|
|
1370
1421
|
const statusFile = join(stateDir, "player.json");
|
|
1371
1422
|
const status = exists(statusFile)
|
|
1372
1423
|
? readJsonFile(statusFile, { state: "unknown" })
|
|
1373
1424
|
: { state: "unknown" };
|
|
1374
|
-
const actor =
|
|
1425
|
+
const actor = actorOwned
|
|
1426
|
+
? await actorControlAvailability(stateDir)
|
|
1427
|
+
: { available: false };
|
|
1375
1428
|
process.stdout.write(`${JSON.stringify({
|
|
1376
1429
|
...projectCurrentProgress(status),
|
|
1377
1430
|
actor_available: actor.available,
|
|
@@ -1381,7 +1434,7 @@ async function controlMain(args) {
|
|
|
1381
1434
|
})}\n`);
|
|
1382
1435
|
return;
|
|
1383
1436
|
}
|
|
1384
|
-
if (!actorControlAvailability(stateDir).available) {
|
|
1437
|
+
if (actorOwned && !(await actorControlAvailability(stateDir)).available) {
|
|
1385
1438
|
fail(`Run playback is not active: ${stateDir}`, 3);
|
|
1386
1439
|
}
|
|
1387
1440
|
let input;
|
|
@@ -1396,6 +1449,20 @@ async function controlMain(args) {
|
|
|
1396
1449
|
fail(error instanceof Error ? error.message : String(error), 2);
|
|
1397
1450
|
}
|
|
1398
1451
|
}
|
|
1452
|
+
if (!actorOwned) {
|
|
1453
|
+
if (endpoint.owner_mode !== "standalone" ||
|
|
1454
|
+
typeof endpoint.path !== "string" || !endpoint.path ||
|
|
1455
|
+
typeof endpoint.service_instance_id !== "string" || !endpoint.service_instance_id) {
|
|
1456
|
+
fail(`standalone playback service is not active: ${stateDir}`, 3);
|
|
1457
|
+
}
|
|
1458
|
+
try {
|
|
1459
|
+
await sendPlaybackCommand(endpoint, command === "resume" ? "play" : command, input);
|
|
1460
|
+
console.log(`music-player: command=${command} handled state_dir=${stateDir}`);
|
|
1461
|
+
return;
|
|
1462
|
+
} catch (error) {
|
|
1463
|
+
fail(error instanceof Error ? error.message : String(error), 3);
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1399
1466
|
const queued = appendControl(
|
|
1400
1467
|
{ controlsFile: join(stateDir, "controls.jsonl"), stateDir },
|
|
1401
1468
|
command,
|
|
@@ -9,13 +9,9 @@ Use multi-actor execution only when at least two scopes or evidence lenses are m
|
|
|
9
9
|
|
|
10
10
|
Read `actors` first for generic Recipe, spawn, Run, Trace, Control, artifact, and lifecycle operation. This Skill owns only multi-actor methodology: decomposition, scope ownership, independence, synthesis, integration, and completion proof.
|
|
11
11
|
|
|
12
|
-
## Coordinator
|
|
12
|
+
## Coordinator and participants
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
This resembles gateway orchestration in dependency direction but not in ownership: the coordinator is itself an agent instance with inspectable Runs, not an infrastructure service that implicitly creates sessions. Preserve that distinction in prompts, docs, recovery, and target routing.
|
|
17
|
-
|
|
18
|
-
Once work is delegated, keep the coordinator available for decisions and integration instead of duplicating participant implementation. Wait for the settled completion batch by default; use meaningful attention or evidence-based timers for overdue work rather than a tight inspection loop.
|
|
14
|
+
The coordinator owns decomposition, shared contracts, integration order, and final validation. Participants own bounded tasks or evidence lenses. Keep the coordinator available for decisions instead of duplicating participant implementation; use `actors` for launch, observation, and lifecycle mechanics.
|
|
19
15
|
|
|
20
16
|
## Reasoning allocation
|
|
21
17
|
|
|
@@ -14,38 +14,9 @@ Use a development swarm only when all are true:
|
|
|
14
14
|
|
|
15
15
|
Do not parallelize implementation when tasks need the same central files, semantic ordering dominates wall-clock time, or the likely conflicts would invalidate the decomposition. Use planning or review first.
|
|
16
16
|
|
|
17
|
-
##
|
|
17
|
+
## Roles and reasoning
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
The coordinator should:
|
|
22
|
-
|
|
23
|
-
- Translate high-level intent into bounded task cards and dependency edges.
|
|
24
|
-
- Keep user authority, shared contracts, integration order, and final validation local.
|
|
25
|
-
- Remain available for checkpoints, permissions, conflicts, and changing evidence.
|
|
26
|
-
- Consume terminal handoffs, durable artifacts, and Trace attention instead of mirroring participant work.
|
|
27
|
-
- Check overdue work on an evidence-based timer; never replace event-driven completion with a rapid polling loop.
|
|
28
|
-
|
|
29
|
-
A participant should:
|
|
30
|
-
|
|
31
|
-
- Own one concrete execution or evidence boundary.
|
|
32
|
-
- Avoid global orchestration and undeclared participant creation.
|
|
33
|
-
- Return a bounded handoff that lets the coordinator decide without replaying the entire task.
|
|
34
|
-
|
|
35
|
-
Use one ordinary Run under `actors` when only one worker is delegated. Activate this development-swarm protocol when two or more participants, parallel ownership, or explicit integration edges exist. Keep trivial single-boundary work inline when delegation overhead has no compensating value.
|
|
36
|
-
|
|
37
|
-
## Reasoning profiles
|
|
38
|
-
|
|
39
|
-
| Role | Default | Raise or fan out when |
|
|
40
|
-
| --- | --- | --- |
|
|
41
|
-
| Bounded implementation author | Reasoning off | The card explicitly owns unresolved diagnosis or design judgement |
|
|
42
|
-
| Reviewer | Medium reasoning, clean context | Stakes require independent lenses or repeated judges |
|
|
43
|
-
| Synthesizer / integrator | Medium reasoning | Evidence conflicts, shared contracts move, or merge order is semantic |
|
|
44
|
-
| Coordinator | Sufficient for decomposition and decisions | Scope, authority, or architecture remains unresolved |
|
|
45
|
-
|
|
46
|
-
Prefer independent review after implementation over asking one author thread to implement, retain all local assumptions, and then certify itself. When risk justifies the cost, use multiple independent reviewers: different lenses increase breadth, while repeated judges increase confidence. Preserve minority high-impact findings and merge only evidence-backed conclusions.
|
|
47
|
-
|
|
48
|
-
More reviewers are not automatically better. Do not fan out when they would inspect unstable code, share contaminated context, repeat one unsupported claim, or exceed the value of the decision. Never change an already-running participant solely to enforce a newer profile; add a fresh review boundary if evidence remains open.
|
|
19
|
+
Apply [Swarm's coordinator and reasoning contract](../SKILL.md#reasoning-allocation). Task cards record those profiles and the owned execution boundary; participants return evidence without undeclared orchestration. Do not fan out review over unstable code or contaminated context. The sections below specify development-only task cards, ownership transfers, conflict reports, and integration.
|
|
49
20
|
|
|
50
21
|
## Decompose by ownership
|
|
51
22
|
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Agent Notes
|
|
2
|
+
|
|
3
|
+
- `Isolation boundary`: Keep clean-room isolation process-based. Do not simulate isolation by filtering an already-loaded parent runtime because sibling extension handlers cannot be reliably unregistered.
|
|
4
|
+
- `Terminal ownership`: Launch nested Pi only through `ctx.ui.custom()`, stop the parent TUI before inheriting stdio, and restart plus fully rerender it after the child exits.
|
|
5
|
+
- `Zero configuration`: Keep `/clean-room <extension...>` as the complete user contract. Resolve project-local names before global names and do not add profiles or persisted configuration without a new requirement.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.1: Isolation and terminal recovery fixes
|
|
4
|
+
|
|
5
|
+
- Keep unselected append-system context excluded when selected global prompt files are missing.
|
|
6
|
+
- Restore the parent TUI after launch exceptions and report process startup errors, signal termination, and unsuccessful exits after terminal ownership returns.
|
|
7
|
+
- Resolve every source-file suffix offered by extension-name completion while preserving project-local precedence.
|
|
8
|
+
- Publish version-tag releases through GitHub Actions and npm Trusted Publisher, with provenance and post-publication identity checks.
|
|
9
|
+
- Include the Clean Room banner in the package and project presentation.
|
|
10
|
+
|
|
11
|
+
## 0.1.0
|
|
12
|
+
|
|
13
|
+
- Added `/clean-room` to launch an isolated nested Pi TUI with optional extension allowlisting.
|
|
14
|
+
- Preserved the current working directory, model, and thinking level while disabling discovered extensions, skills, prompts, themes, context files, and file-backed system prompt overrides.
|
|
15
|
+
- Added installed-extension completion and deterministic project-local-over-global name resolution.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# pi-clean-room
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
Launch a fresh, isolated Pi TUI with only the extensions you explicitly select. The parent session pauses while the clean-room owns the terminal and returns unchanged when the nested Pi exits.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
Requirements: Pi `0.84.4–0.84.x` and Node.js `22.19.0` or newer.
|
|
10
|
+
|
|
11
|
+
Install from npm:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pi install npm:@llblab/pi-clean-room
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Or install from GitHub:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pi install git:github.com/llblab/pi-clean-room
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Pi packages execute with full user permissions; review the source before installing, as with any extension.
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
Install or load this extension, then run:
|
|
28
|
+
|
|
29
|
+
```text
|
|
30
|
+
/clean-room
|
|
31
|
+
/clean-room pi-extension-name
|
|
32
|
+
/clean-room skills agents system append-system
|
|
33
|
+
/clean-room skills pi-extension-name pi-actors
|
|
34
|
+
/clean-room skills/brain-storm
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Arguments may be extension names or resource switches:
|
|
38
|
+
|
|
39
|
+
- `skills` — enable normal Pi skill discovery, including global (`~/.pi/agent/skills`, `~/.agents/skills`), project, package, and configured skills
|
|
40
|
+
- `skills/<name>` — load only that skill from known global or project skill directories (for example, `skills/brain-storm`)
|
|
41
|
+
- `agents` — include `~/.pi/agent/AGENTS.md`
|
|
42
|
+
- `system` — use `~/.pi/agent/SYSTEM.md`
|
|
43
|
+
- `append-system` — include `~/.pi/agent/APPEND_SYSTEM.md`
|
|
44
|
+
|
|
45
|
+
Extension names are resolved from the global or project-local extensions directory. Absolute and working-directory-relative extension paths are also accepted. Project-local names take precedence over global names. Source-file names resolve consistently across `.ts`, `.js`, `.mts`, `.mjs`, `.cts`, and `.cjs`.
|
|
46
|
+
|
|
47
|
+
The nested Pi starts in the current working directory with the current model and thinking level. All optional resources are disabled unless named explicitly. Pi's built-in system prompt and built-in tools remain available. Missing selected global prompt files contribute no content; unselected prompt files remain excluded.
|
|
48
|
+
|
|
49
|
+
Exit the nested TUI normally with `Ctrl+D` to return to the parent session. Launch failures and signal termination are reported after restoring the parent TUI.
|
|
50
|
+
|
|
51
|
+
## Releases
|
|
52
|
+
|
|
53
|
+
Version tags (`v*.*.*`) trigger [the release workflow](./.github/workflows/release.yml). It validates the package, publishes to npm through Trusted Publisher with provenance, verifies the published version and package inventory, and creates the GitHub Release from `CHANGELOG.md`.
|
|
54
|
+
|
|
55
|
+
Configure npm Trusted Publisher for GitHub owner `llblab`, repository `pi-clean-room`, workflow filename `release.yml`, with no environment. Publication uses GitHub OIDC; no npm token secret is required. Keep the tag, manifest, root lockfile metadata, and changelog version aligned before pushing a release tag.
|
|
56
|
+
|
|
57
|
+
## Limitations
|
|
58
|
+
|
|
59
|
+
- Interactive terminal ownership requires Pi TUI mode. RPC, JSON, and print modes are rejected.
|
|
60
|
+
- The clean-room retains Pi's built-in base system prompt and built-in tools.
|
|
61
|
+
- Extension arguments cannot contain whitespace.
|
|
Binary file
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { isAbsolute, join, resolve } from "node:path";
|
|
5
|
+
import {
|
|
6
|
+
CONFIG_DIR_NAME,
|
|
7
|
+
getAgentDir,
|
|
8
|
+
type ExtensionAPI,
|
|
9
|
+
type ExtensionCommandContext,
|
|
10
|
+
} from "@earendil-works/pi-coding-agent";
|
|
11
|
+
|
|
12
|
+
const EMPTY_COMPONENT = { render: () => [], invalidate: () => {} };
|
|
13
|
+
const EXTENSION_SUFFIXES = [".ts", ".js", ".mts", ".mjs", ".cts", ".cjs"];
|
|
14
|
+
|
|
15
|
+
export function discoverExtensionNames(cwd: string, agentDir = getAgentDir()): string[] {
|
|
16
|
+
const roots = [join(agentDir, "extensions"), join(cwd, CONFIG_DIR_NAME, "extensions")];
|
|
17
|
+
const names = new Set<string>();
|
|
18
|
+
for (const root of roots) {
|
|
19
|
+
if (!existsSync(root)) continue;
|
|
20
|
+
for (const entry of readdirSync(root, { withFileTypes: true })) {
|
|
21
|
+
if (entry.isDirectory()) names.add(entry.name);
|
|
22
|
+
const suffix = EXTENSION_SUFFIXES.find((suffix) => entry.name.endsWith(suffix));
|
|
23
|
+
if (entry.isFile() && suffix) names.add(entry.name.slice(0, -suffix.length));
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return [...names].sort();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function resolveExtension(source: string, cwd: string, agentDir = getAgentDir()): string | undefined {
|
|
30
|
+
const direct = isAbsolute(source) ? source : resolve(cwd, source);
|
|
31
|
+
if (existsSync(direct)) return direct;
|
|
32
|
+
|
|
33
|
+
const roots = [join(cwd, CONFIG_DIR_NAME, "extensions"), join(agentDir, "extensions")];
|
|
34
|
+
for (const root of roots) {
|
|
35
|
+
for (const candidate of [join(root, source), ...EXTENSION_SUFFIXES.map((suffix) => join(root, `${source}${suffix}`))]) {
|
|
36
|
+
if (existsSync(candidate)) return candidate;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function resolveSkill(name: string, cwd: string, agentDir = getAgentDir()): string | undefined {
|
|
43
|
+
if (!name || name.includes("..") || isAbsolute(name)) return undefined;
|
|
44
|
+
const roots = [
|
|
45
|
+
join(cwd, CONFIG_DIR_NAME, "skills"),
|
|
46
|
+
join(cwd, ".agents", "skills"),
|
|
47
|
+
join(agentDir, "skills"),
|
|
48
|
+
join(homedir(), ".agents", "skills"),
|
|
49
|
+
];
|
|
50
|
+
for (const root of roots) {
|
|
51
|
+
for (const candidate of [join(root, name), join(root, `${name}.md`)]) {
|
|
52
|
+
if (existsSync(candidate)) return candidate;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export const RESOURCE_ARGUMENTS = ["skills", "agents", "system", "append-system"] as const;
|
|
59
|
+
type ResourceArgument = (typeof RESOURCE_ARGUMENTS)[number];
|
|
60
|
+
|
|
61
|
+
export function buildCleanRoomArgs(
|
|
62
|
+
extensions: string[],
|
|
63
|
+
model?: { provider: string; id: string },
|
|
64
|
+
thinkingLevel?: string,
|
|
65
|
+
resources: ReadonlySet<ResourceArgument> = new Set(),
|
|
66
|
+
agentDir = getAgentDir(),
|
|
67
|
+
skills: string[] = [],
|
|
68
|
+
): string[] {
|
|
69
|
+
const args = ["--no-extensions"];
|
|
70
|
+
if (!resources.has("skills")) args.push("--no-skills");
|
|
71
|
+
for (const skill of skills) args.push("--skill", skill);
|
|
72
|
+
args.push("--no-prompt-templates", "--no-themes", "--no-context-files");
|
|
73
|
+
|
|
74
|
+
const systemPath = join(agentDir, "SYSTEM.md");
|
|
75
|
+
args.push("--system-prompt", resources.has("system") && existsSync(systemPath) ? readFileSync(systemPath, "utf8") : "");
|
|
76
|
+
const appendPaths: string[] = [];
|
|
77
|
+
for (const [resource, file] of [["agents", "AGENTS.md"], ["append-system", "APPEND_SYSTEM.md"]] as const) {
|
|
78
|
+
const path = join(agentDir, file);
|
|
79
|
+
if (resources.has(resource) && existsSync(path)) appendPaths.push(path);
|
|
80
|
+
}
|
|
81
|
+
for (const path of appendPaths.length > 0 ? appendPaths : [""]) args.push("--append-system-prompt", path);
|
|
82
|
+
|
|
83
|
+
if (model) args.push("--model", `${model.provider}/${model.id}`);
|
|
84
|
+
if (thinkingLevel) args.push("--thinking", thinkingLevel);
|
|
85
|
+
for (const extension of extensions) args.push("--extension", extension);
|
|
86
|
+
return args;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function parseNames(args: string): string[] {
|
|
90
|
+
return args.trim().split(/\s+/).filter(Boolean);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function launchCleanRoom(
|
|
94
|
+
ctx: ExtensionCommandContext,
|
|
95
|
+
extensionPaths: string[],
|
|
96
|
+
resources: ReadonlySet<ResourceArgument>,
|
|
97
|
+
skills: string[],
|
|
98
|
+
) {
|
|
99
|
+
const args = buildCleanRoomArgs(extensionPaths, ctx.model, ctx.thinkingLevel, resources, getAgentDir(), skills);
|
|
100
|
+
const cliEntry = process.argv[1];
|
|
101
|
+
const command = cliEntry && existsSync(cliEntry) ? process.execPath : "pi";
|
|
102
|
+
const commandArgs = cliEntry && existsSync(cliEntry) ? [cliEntry, ...args] : args;
|
|
103
|
+
|
|
104
|
+
return spawnSync(command, commandArgs, {
|
|
105
|
+
cwd: ctx.cwd,
|
|
106
|
+
env: process.env,
|
|
107
|
+
stdio: "inherit",
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export default function cleanRoomExtension(pi: ExtensionAPI) {
|
|
112
|
+
pi.registerCommand("clean-room", {
|
|
113
|
+
description: "Launch a clean Pi session with only the named extensions",
|
|
114
|
+
getArgumentCompletions(prefix) {
|
|
115
|
+
const separator = prefix.lastIndexOf(" ");
|
|
116
|
+
const head = separator === -1 ? "" : prefix.slice(0, separator + 1);
|
|
117
|
+
const needle = prefix.slice(separator + 1).toLowerCase();
|
|
118
|
+
const selected = new Set(parseNames(head));
|
|
119
|
+
const items = [...RESOURCE_ARGUMENTS, ...discoverExtensionNames(process.cwd())]
|
|
120
|
+
.filter((name) => !selected.has(name) && name.toLowerCase().startsWith(needle))
|
|
121
|
+
.map((name) => ({ value: `${head}${name}`, label: name }));
|
|
122
|
+
return items.length > 0 ? items : null;
|
|
123
|
+
},
|
|
124
|
+
async handler(rawArgs, ctx) {
|
|
125
|
+
if (ctx.mode !== "tui") {
|
|
126
|
+
ctx.ui.notify("pi-clean-room requires interactive TUI mode", "error");
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const names = parseNames(rawArgs);
|
|
131
|
+
const resources = new Set(names.filter((name): name is ResourceArgument =>
|
|
132
|
+
(RESOURCE_ARGUMENTS as readonly string[]).includes(name),
|
|
133
|
+
));
|
|
134
|
+
const skillNames = names.filter((name) => name.startsWith("skills/")).map((name) => name.slice("skills/".length));
|
|
135
|
+
const extensionNames = names.filter((name) => !resources.has(name as ResourceArgument) && !name.startsWith("skills/"));
|
|
136
|
+
const missingSkills: string[] = [];
|
|
137
|
+
const skills = skillNames.map((name) => {
|
|
138
|
+
const path = resolveSkill(name, ctx.cwd);
|
|
139
|
+
if (!path) missingSkills.push(name);
|
|
140
|
+
return path;
|
|
141
|
+
}).filter((path): path is string => path !== undefined);
|
|
142
|
+
const missingExtensions: string[] = [];
|
|
143
|
+
const paths = extensionNames.map((name) => {
|
|
144
|
+
const path = resolveExtension(name, ctx.cwd);
|
|
145
|
+
if (!path) missingExtensions.push(name);
|
|
146
|
+
return path;
|
|
147
|
+
}).filter((path): path is string => path !== undefined);
|
|
148
|
+
if (missingSkills.length > 0) {
|
|
149
|
+
ctx.ui.notify(`Unknown skill${missingSkills.length > 1 ? "s" : ""}: ${missingSkills.join(", ")}`, "error");
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
if (missingExtensions.length > 0) {
|
|
153
|
+
ctx.ui.notify(`Unknown extension${missingExtensions.length > 1 ? "s" : ""}: ${missingExtensions.join(", ")}`, "error");
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
let failure: string | undefined;
|
|
158
|
+
await ctx.ui.custom<void>((tui, _theme, _keybindings, done) => {
|
|
159
|
+
tui.stop();
|
|
160
|
+
try {
|
|
161
|
+
process.stdout.write("\x1b[2J\x1b[H");
|
|
162
|
+
const result = launchCleanRoom(ctx, paths, resources, skills);
|
|
163
|
+
if (result.error) failure = `pi-clean-room failed to start: ${result.error.message}`;
|
|
164
|
+
else if (result.signal) failure = `pi-clean-room terminated by ${result.signal}`;
|
|
165
|
+
else if (result.status !== 0) failure = `pi-clean-room exited with code ${result.status ?? "unknown"}`;
|
|
166
|
+
} catch (error) {
|
|
167
|
+
failure = `pi-clean-room failed: ${error instanceof Error ? error.message : String(error)}`;
|
|
168
|
+
} finally {
|
|
169
|
+
tui.start();
|
|
170
|
+
tui.requestRender(true);
|
|
171
|
+
done();
|
|
172
|
+
}
|
|
173
|
+
return EMPTY_COMPONENT;
|
|
174
|
+
});
|
|
175
|
+
if (failure) ctx.ui.notify(failure, "error");
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@llblab/pi-clean-room",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "Launch an isolated Pi TUI with only explicitly selected extensions",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"pi-package",
|
|
8
|
+
"pi-extension",
|
|
9
|
+
"pi",
|
|
10
|
+
"clean-room",
|
|
11
|
+
"testing",
|
|
12
|
+
"isolation"
|
|
13
|
+
],
|
|
14
|
+
"type": "module",
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/llblab/pi-clean-room.git"
|
|
19
|
+
},
|
|
20
|
+
"homepage": "https://github.com/llblab/pi-clean-room",
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=22.19.0"
|
|
23
|
+
},
|
|
24
|
+
"scripts": {
|
|
25
|
+
"check": "node --experimental-strip-types -e \"await import('./index.ts'); console.log('pi-clean-room: extension import ok')\"",
|
|
26
|
+
"test": "node --experimental-strip-types --test tests/*.test.ts",
|
|
27
|
+
"typecheck": "tsc --noEmit",
|
|
28
|
+
"audit": "npm audit --omit=peer",
|
|
29
|
+
"pack:dry": "npm pack --dry-run",
|
|
30
|
+
"validate": "npm run typecheck && npm run test && npm run check && npm run audit && npm run pack:dry"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"index.ts",
|
|
34
|
+
"banner.jpg",
|
|
35
|
+
"README.md",
|
|
36
|
+
"AGENTS.md",
|
|
37
|
+
"BACKLOG.md",
|
|
38
|
+
"CHANGELOG.md"
|
|
39
|
+
],
|
|
40
|
+
"pi": {
|
|
41
|
+
"extensions": [
|
|
42
|
+
"./index.ts"
|
|
43
|
+
],
|
|
44
|
+
"image": "https://raw.githubusercontent.com/llblab/pi-clean-room/main/banner.jpg"
|
|
45
|
+
},
|
|
46
|
+
"peerDependencies": {
|
|
47
|
+
"@earendil-works/pi-coding-agent": "*"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@types/node": "latest",
|
|
51
|
+
"typescript": "latest"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
- `Release-Level Changelog`: `CHANGELOG.md` records final release outcomes, not raw iteration telemetry or superseded internal steps. Keep entries linear, domain-tagged, and coherent for a release reader.
|
|
10
10
|
- `Docs Directory Purpose`: Create `docs/` only when there are multiple project documentation pages that need an index. A lone extra README under `docs/` is unnecessary; the root `README.md` is the human entrypoint.
|
|
11
11
|
- `Boundary Clarity`: Keep meta-loop decision, worker iteration, and runtime trigger separate.
|
|
12
|
-
- `Scope-First Activation`: Grow Loop
|
|
12
|
+
- `Scope-First Activation`: Grow Loop locks the concrete user-focus scope. While True alone resolves and validates its canonical open-work surface under the worker's source-of-truth protocol; Grow Loop retains that result and decides continuation from the handoff rather than repeating discovery.
|
|
13
13
|
- `Lexical Intent Precedence`: An explicitly named protocol selects its exact behavior. Standalone `while-true` runs only the portable worker; standalone `grow-loop` selects the meta-protocol; `while true | grow loop` is reserved for runtime continuation.
|
|
14
14
|
- `Structural Routing`: When no protocol is named, route by delegated execution shape and checkpoint topology, never by verbs, work categories, prompt length, apparent size, or internal step count. One coherent change with one natural validation/reporting boundary remains ordinary execution; a concrete outcome may select Grow Loop when it benefits from multiple independently useful validated slices and visible continuation checkpoints. Bootstrapping a canonical backlog from an explicit requested outcome is decomposition, not speculative work invention.
|
|
15
15
|
- `Stop Honesty`: Exact proof-of-stop is better than simulated autonomous momentum.
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.7.4: Worker Handoff Ownership Hotfix
|
|
4
|
+
|
|
5
|
+
- `Worker Handoff`: Keeps canonical work-surface resolution and handoff fields with While True; Grow Loop locks user scope and decides whether to schedule another iteration from the returned evidence without duplicating discovery or worker analysis.
|
|
6
|
+
|
|
3
7
|
## 0.7.3: Turn-Scoped Iteration Hotfix
|
|
4
8
|
|
|
5
9
|
- [Runtime] Keep repeated `grow_loop` calls within one agent turn bound to the same next iteration, replacing only the deferred delay instead of incrementing the visible loop number.
|
|
@@ -30,14 +30,9 @@ Natural routing into Grow Loop requires a concrete outcome or scope, a truthful
|
|
|
30
30
|
|
|
31
31
|
## Scope Lock
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
Lock the project, directory, file, issue, task, or scoped outcome selected by the user. Conversation context may supply it only when it identifies the next safe slice precisely.
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
2. An explicit scoped outcome that can be decomposed into a truthful canonical backlog.
|
|
37
|
-
3. A canonical open-work surface explicitly declared by relevant project-local delivery instructions for that scope.
|
|
38
|
-
4. The actively maintained open-work surface nearest that scope: `BACKLOG.md`, `PLAN.md`, `ROADMAP.md`, `TODO.md`, task list, or release checklist.
|
|
39
|
-
5. Active docs, validation failure, or repository reality that defines concrete remaining work.
|
|
40
|
-
6. Conversation context only when it identifies a safe next slice precisely.
|
|
35
|
+
Pass that scope to `while-true`, which alone resolves and validates the canonical open-work surface, including any `Canonical open work:` declaration. Retain the surface returned in its handoff; do not run a second discovery algorithm here.
|
|
41
36
|
|
|
42
37
|
Ignore unrelated repositories, temporary or generated directories, dependencies, caches, archives, and stale plans.
|
|
43
38
|
|
|
@@ -62,16 +57,7 @@ If a queued `while true | grow loop` prompt arrives after continuation-break int
|
|
|
62
57
|
|
|
63
58
|
## Continuation Checkpoint
|
|
64
59
|
|
|
65
|
-
After one `while-true` invocation, consume its handoff without redoing worker implementation analysis
|
|
66
|
-
|
|
67
|
-
- Locked scope and canonical work surface.
|
|
68
|
-
- Artifact or evidence produced.
|
|
69
|
-
- Validation result and highest completed rung.
|
|
70
|
-
- Plan-state transition.
|
|
71
|
-
- Highest-value remaining item and actionability class.
|
|
72
|
-
- Gate, blocker, and exact unblocker.
|
|
73
|
-
- Checkpoint signature: selected cohort items, changed surfaces, per-task and shared validation results, blocker, and plan transitions.
|
|
74
|
-
- Latest user intent.
|
|
60
|
+
After one `while-true` invocation, consume its [Handoff](../while-true/SKILL.md#handoff) and checkpoint signature without redoing worker implementation analysis. Combine that evidence with the latest user intent; `while-true` owns the handoff fields, while this Skill owns the continuation decision.
|
|
75
61
|
|
|
76
62
|
A useful invocation must change an artifact, increase validation confidence, narrow a blocker, improve plan truth, or remove a risky assumption. The worker may batch independent low-coupling tasks into one validation cohort; Grow Loop evaluates the cohort handoff as one checkpoint and does not reinterpret its batching. Otherwise treat the invocation as a possible no-op.
|
|
77
63
|
|