@lovrozagar/grunt 0.5.0 → 0.5.2
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/agents/grunt.md +2 -1
- package/.agents/agents/implementer.md +1 -1
- package/.agents/agents/orchestrator.md +17 -12
- package/.agents/agents/thinker.md +6 -5
- package/.agents/skills/ask/SKILL.md +18 -0
- package/.agents/skills/auto/SKILL.md +18 -0
- package/.agents/skills/browser/SKILL.md +4 -3
- package/.agents/skills/cascade/SKILL.md +5 -3
- package/.agents/skills/handoff/SKILL.md +1 -1
- package/.agents/skills/implement-plan/SKILL.md +12 -11
- package/.agents/skills/solo/SKILL.md +5 -3
- package/.agents/skills/tmp/SKILL.md +58 -0
- package/.agents/skills/write-plan/SKILL.md +11 -8
- package/.claude/agents/grunt.md +2 -1
- package/.claude/agents/implementer.md +1 -1
- package/.claude/agents/orchestrator.md +17 -12
- package/.claude/agents/thinker.md +6 -5
- package/.claude/skills/ask/SKILL.md +18 -0
- package/.claude/skills/auto/SKILL.md +18 -0
- package/.claude/skills/browser/SKILL.md +4 -3
- package/.claude/skills/cascade/SKILL.md +5 -3
- package/.claude/skills/handoff/SKILL.md +1 -1
- package/.claude/skills/implement-plan/SKILL.md +12 -11
- package/.claude/skills/solo/SKILL.md +5 -3
- package/.claude/skills/tmp/SKILL.md +58 -0
- package/.claude/skills/write-plan/SKILL.md +11 -8
- package/.codex/agents/grunt.toml +2 -1
- package/.codex/agents/implementer.toml +1 -1
- package/.codex/agents/orchestrator.toml +17 -12
- package/.codex/agents/thinker.toml +6 -5
- package/.grok/agents/grunt.md +2 -1
- package/.grok/agents/implementer.md +1 -1
- package/.grok/agents/orchestrator.md +17 -12
- package/.grok/agents/thinker.md +6 -5
- package/.grok/hooks/orchestrate-parent.js +280 -62
- package/.grok/skills/ask/SKILL.md +18 -0
- package/.grok/skills/auto/SKILL.md +18 -0
- package/.grok/skills/browser/SKILL.md +4 -3
- package/.grok/skills/cascade/SKILL.md +5 -3
- package/.grok/skills/handoff/SKILL.md +1 -1
- package/.grok/skills/implement-plan/SKILL.md +12 -11
- package/.grok/skills/solo/SKILL.md +5 -3
- package/.grok/skills/tmp/SKILL.md +58 -0
- package/.grok/skills/write-plan/SKILL.md +11 -8
- package/.rulesync/grunt.config.jsonc +7 -0
- package/.rulesync/reference/INDEX.md +7 -4
- package/.rulesync/reference/cascade.md +15 -13
- package/.rulesync/reference/hooks.md +6 -4
- package/.rulesync/reference/law.md +1 -1
- package/.rulesync/reference/map.md +14 -9
- package/.rulesync/reference/output.md +18 -8
- package/.rulesync/reference/plan-format.md +10 -10
- package/.rulesync/reference/skills-map.md +6 -3
- package/.rulesync/rules/CLAUDE.md +18 -13
- package/.rulesync/rules/overview.md +18 -13
- package/.rulesync/skills/ask/SKILL.md +18 -0
- package/.rulesync/skills/auto/SKILL.md +18 -0
- package/.rulesync/skills/browser/SKILL.md +2 -2
- package/.rulesync/skills/cascade/SKILL.md +5 -3
- package/.rulesync/skills/handoff/SKILL.md +1 -1
- package/.rulesync/skills/implement-plan/SKILL.md +14 -11
- package/.rulesync/skills/solo/SKILL.md +5 -3
- package/.rulesync/skills/tmp/SKILL.md +58 -0
- package/.rulesync/skills/write-plan/SKILL.md +12 -8
- package/.rulesync/subagents/grunt.md +2 -1
- package/.rulesync/subagents/implementer.md +1 -1
- package/.rulesync/subagents/orchestrator.md +17 -12
- package/.rulesync/subagents/thinker.md +6 -5
- package/AGENTS.md +18 -13
- package/CHANGELOG.md +33 -0
- package/CLAUDE.md +18 -13
- package/README.md +17 -16
- package/cli/grunt.mjs +12 -12
- package/cli/grunt.test.ts +29 -20
- package/cli/init.mjs +116 -22
- package/cli/init.test.ts +545 -34
- package/package.json +8 -1
- package/scripts/gate-fat-tools.mjs +2 -2
- package/scripts/grunt-config.mjs +50 -0
- package/scripts/persist-plan.mjs +17 -2
- package/scripts/persist-tmp.mjs +123 -0
- package/scripts/scrub-spawn-prompt.mjs +1 -1
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/** PreToolUse parent-deny on Claude+Grok+Antigravity; Stop = first-non-empty-line recap; stop-block survives host banners; SubagentStop is single-registration intercept.
|
|
3
3
|
Parent Read/Grep/Glob/Bash/Web denied unless parent-escape (fat-gate still).
|
|
4
4
|
SubagentStop intercepts need: search|exec with grunt-job verdicts.
|
|
5
|
-
Stop: first-non-empty-line [orchestrator]:/[grunt]:/[implementer]:/[thinker]:/[handoff]: recap
|
|
5
|
+
Stop: first-non-empty-line [orchestrator]:/[grunt]:/[implementer]:/[thinker]:/[handoff]:/[tmp]: recap
|
|
6
6
|
| parent-escape once; else block. MAX_STOP=3. No isCheap/trivia.
|
|
7
7
|
Empty lastAssistantMessage → transcript_path tail-scan. Fail-open: parse/crash → empty stdout, exit 0.
|
|
8
8
|
*/
|
|
@@ -25,16 +25,20 @@ import {
|
|
|
25
25
|
} from "../../scripts/gate-fat-tools.mjs";
|
|
26
26
|
import { persistPlan } from "../../scripts/persist-plan.mjs";
|
|
27
27
|
import { persistHandoff } from "../../scripts/persist-handoff.mjs";
|
|
28
|
+
import { persistTmp } from "../../scripts/persist-tmp.mjs";
|
|
28
29
|
import { parseNeed } from "../../scripts/parse-need.mjs";
|
|
29
30
|
import { resolveJobCwd, runJob } from "../../scripts/grunt-job.mjs";
|
|
31
|
+
import { loadLeftoverGate, loadSpawnMode } from "../../scripts/grunt-config.mjs";
|
|
30
32
|
|
|
31
|
-
export const ORCHESTRATOR_LOGS_DIR = ".tmp/orchestrator-logs";
|
|
33
|
+
export const ORCHESTRATOR_LOGS_DIR = ".tmp/grunt/orchestrator-logs";
|
|
34
|
+
/** One-release dual-read; drop next release. */
|
|
35
|
+
export const LEGACY_ORCHESTRATOR_LOGS_DIR = ".tmp/orchestrator-logs";
|
|
32
36
|
export const DENY_REASON =
|
|
33
37
|
"First action=spawn implementer|grunt|thinker. Deny expected. Only /solo this session escapes.";
|
|
34
38
|
export const STOP_REASONS = [
|
|
35
|
-
"Spawn: ⚠/validate/sim findings or writes remain after Implement pick/`/implement-plan`/explicit implement or parent just tried to Write → spawn implementer. Else facts → grunt. Else no spec/not small/simple → thinker then recap-stop. Else small/simple/defined → implementer. Thinker recap ≠ spec-ready. `ok`/`yes` ≠ implement. Else recap `[orchestrator]:` one recap line; advise leftover
|
|
36
|
-
"Still spawn: ⚠/validate/sim findings or writes remain after Implement pick/`/implement-plan`/explicit implement or parent just tried to Write → spawn implementer. Else facts → grunt. Else no spec/not small/simple → thinker then recap-stop. Else small/simple/defined → implementer. Thinker recap ≠ spec-ready. `ok`/`yes` ≠ implement. Else recap `[orchestrator]:` one recap line; advise leftover
|
|
37
|
-
"Last spawn: ⚠/validate/sim findings or writes remain after Implement pick/`/implement-plan`/explicit implement or parent just tried to Write → spawn implementer. Else facts → grunt. Else no spec/not small/simple → thinker then recap-stop. Else small/simple/defined → implementer. Thinker recap ≠ spec-ready. `ok`/`yes` ≠ implement. Else recap `[orchestrator]:` one recap line; advise leftover
|
|
39
|
+
"Spawn: ⚠/validate/sim findings or writes remain after Implement pick/`/implement-plan`/explicit implement or parent just tried to Write → spawn implementer. Else facts → grunt. Else no spec/not small/simple → thinker then recap-stop. Else small/simple/defined → implementer. Thinker recap ≠ spec-ready. `ok`/`yes` ≠ implement. Else recap `[orchestrator]:` one recap line; advise leftover numbered pick each on own line after (echo printed leftover; always-print typed triple).",
|
|
40
|
+
"Still spawn: ⚠/validate/sim findings or writes remain after Implement pick/`/implement-plan`/explicit implement or parent just tried to Write → spawn implementer. Else facts → grunt. Else no spec/not small/simple → thinker then recap-stop. Else small/simple/defined → implementer. Thinker recap ≠ spec-ready. `ok`/`yes` ≠ implement. Else recap `[orchestrator]:` one recap line; advise leftover numbered pick each on own line after (echo printed leftover; always-print typed triple).",
|
|
41
|
+
"Last spawn: ⚠/validate/sim findings or writes remain after Implement pick/`/implement-plan`/explicit implement or parent just tried to Write → spawn implementer. Else facts → grunt. Else no spec/not small/simple → thinker then recap-stop. Else small/simple/defined → implementer. Thinker recap ≠ spec-ready. `ok`/`yes` ≠ implement. Else recap `[orchestrator]:` one recap line; advise leftover numbered pick each on own line after (echo printed leftover; always-print typed triple).",
|
|
38
42
|
];
|
|
39
43
|
const TRANSCRIPT_TAIL_BYTES = 512 * 1024;
|
|
40
44
|
const PARENT_TOOLS = new Set([
|
|
@@ -58,12 +62,15 @@ const WRITE_TOOLS = new Set([
|
|
|
58
62
|
]);
|
|
59
63
|
const BASH_TOOLS = new Set(["bash", "runterminalcommand", "runcommand"]);
|
|
60
64
|
const SKILL_TOOLS = new Set(["skill"]);
|
|
61
|
-
const PARENT_SKILLS = new Set([
|
|
65
|
+
export const PARENT_SKILLS = new Set([
|
|
62
66
|
"parent",
|
|
63
67
|
"explain",
|
|
64
68
|
"solo",
|
|
65
69
|
"cascade",
|
|
70
|
+
"auto",
|
|
71
|
+
"ask",
|
|
66
72
|
"handoff",
|
|
73
|
+
"tmp",
|
|
67
74
|
"pickup",
|
|
68
75
|
"write-plan",
|
|
69
76
|
"implement-plan",
|
|
@@ -74,7 +81,11 @@ const MAX_INTERCEPT = 3;
|
|
|
74
81
|
/** `/solo` enters single-agent mode; `/cascade` restores the orchestrator. */
|
|
75
82
|
const SOLO_RE = /^\s*\/solo\s*$/;
|
|
76
83
|
const CASCADE_RE = /^\s*\/cascade\s*$/;
|
|
84
|
+
const AUTO_RE = /^\s*\/auto\s*$/;
|
|
85
|
+
const ASK_RE = /^\s*\/ask\s*$/;
|
|
77
86
|
const SOLO_STAMP = "grunt-off";
|
|
87
|
+
export const AUTO_ASK_STAMP = "auto-ask";
|
|
88
|
+
export const SPAWN_MODE_STAMP = "spawn-mode";
|
|
78
89
|
|
|
79
90
|
|
|
80
91
|
function main() {
|
|
@@ -154,19 +165,17 @@ function preToolUse(data) {
|
|
|
154
165
|
return 0;
|
|
155
166
|
}
|
|
156
167
|
|
|
157
|
-
/** Session flag, not one-turn. Fail-closed:
|
|
168
|
+
/** Session flag, not one-turn. Fail-closed: unreadable spawn-mode stamp is not solo. */
|
|
158
169
|
export function isSoloMode(data) {
|
|
159
170
|
try {
|
|
160
|
-
|
|
161
|
-
return Boolean(p && fs.existsSync(p));
|
|
171
|
+
return spawnModeOf(data) === "solo";
|
|
162
172
|
} catch {
|
|
163
173
|
return false;
|
|
164
174
|
}
|
|
165
175
|
}
|
|
166
176
|
|
|
167
177
|
function hasParentEscape(data) {
|
|
168
|
-
|
|
169
|
-
return Boolean(p && fs.existsSync(p));
|
|
178
|
+
return Boolean(resolveStamp(data, "parent-escape"));
|
|
170
179
|
}
|
|
171
180
|
|
|
172
181
|
function emitFat(data) {
|
|
@@ -198,13 +207,30 @@ function isUnderDir(filePath, workspaceRoot, segments) {
|
|
|
198
207
|
}
|
|
199
208
|
|
|
200
209
|
export function isUnderPlans(filePath, workspaceRoot) {
|
|
201
|
-
return isUnderDir(filePath, workspaceRoot, [".tmp", "plans"]);
|
|
210
|
+
return isUnderDir(filePath, workspaceRoot, [".tmp", "grunt", "plans"]);
|
|
202
211
|
}
|
|
203
212
|
|
|
204
213
|
export function isUnderHandoffs(filePath, workspaceRoot) {
|
|
205
214
|
return isUnderDir(filePath, workspaceRoot, [".tmp", "grunt", "handoffs"]);
|
|
206
215
|
}
|
|
207
216
|
|
|
217
|
+
export const TMP_RESERVED_DIRS = new Set([
|
|
218
|
+
"plans",
|
|
219
|
+
"handoffs",
|
|
220
|
+
"browser",
|
|
221
|
+
"orchestrator-logs",
|
|
222
|
+
]);
|
|
223
|
+
|
|
224
|
+
export function isUnderTmp(filePath, workspaceRoot) {
|
|
225
|
+
if (!filePath || !workspaceRoot) return false;
|
|
226
|
+
const abs = path.isAbsolute(filePath)
|
|
227
|
+
? path.resolve(filePath)
|
|
228
|
+
: path.resolve(workspaceRoot, filePath);
|
|
229
|
+
const root = path.resolve(workspaceRoot, ".tmp", "grunt");
|
|
230
|
+
if (path.dirname(abs) !== root) return false;
|
|
231
|
+
return !TMP_RESERVED_DIRS.has(path.basename(abs));
|
|
232
|
+
}
|
|
233
|
+
|
|
208
234
|
export function isAllowedParentGruntJob(command, workspaceRoot) {
|
|
209
235
|
return isWorkspaceGruntJob(command, workspaceRoot, ["search", "exec"]);
|
|
210
236
|
}
|
|
@@ -257,8 +283,18 @@ function parentWrite(data, toolInput) {
|
|
|
257
283
|
"";
|
|
258
284
|
const rewritten = rewriteGruntScratchPath(rawPath, ws);
|
|
259
285
|
if (rewritten) rawPath = rewritten;
|
|
260
|
-
|
|
261
|
-
|
|
286
|
+
let persist;
|
|
287
|
+
let invalid = "invalid plan";
|
|
288
|
+
if (isUnderPlans(rawPath, ws)) {
|
|
289
|
+
persist = persistPlan;
|
|
290
|
+
invalid = "invalid plan";
|
|
291
|
+
} else if (isUnderHandoffs(rawPath, ws)) {
|
|
292
|
+
persist = persistHandoff;
|
|
293
|
+
invalid = "invalid handoff";
|
|
294
|
+
} else if (isUnderTmp(rawPath, ws)) {
|
|
295
|
+
persist = persistTmp;
|
|
296
|
+
invalid = "invalid tmp";
|
|
297
|
+
} else {
|
|
262
298
|
if (hasParentEscape(data)) {
|
|
263
299
|
emit({ decision: "allow" });
|
|
264
300
|
return 0;
|
|
@@ -267,12 +303,11 @@ function parentWrite(data, toolInput) {
|
|
|
267
303
|
return 0;
|
|
268
304
|
}
|
|
269
305
|
const content = typeof toolInput.content === "string" ? toolInput.content : "";
|
|
270
|
-
const persist = handoff ? persistHandoff : persistPlan;
|
|
271
306
|
const result = persist({ workspaceRoot: ws, content });
|
|
272
307
|
if (!result.ok) {
|
|
273
308
|
emit({
|
|
274
309
|
decision: "deny",
|
|
275
|
-
reason: result.error ||
|
|
310
|
+
reason: result.error || invalid,
|
|
276
311
|
});
|
|
277
312
|
return 0;
|
|
278
313
|
}
|
|
@@ -307,10 +342,7 @@ function rewriteSpawn(toolInput, data) {
|
|
|
307
342
|
|
|
308
343
|
function postToolUse(data) {
|
|
309
344
|
if (data.subagentType || data.subagent_type) return 0;
|
|
310
|
-
|
|
311
|
-
if (!stamp) return 0;
|
|
312
|
-
fs.mkdirSync(path.dirname(stamp), { recursive: true });
|
|
313
|
-
fs.writeFileSync(stamp, "1");
|
|
345
|
+
writeStamp(data, "tools-used", "1");
|
|
314
346
|
return 0;
|
|
315
347
|
}
|
|
316
348
|
|
|
@@ -324,35 +356,41 @@ function isParentEscapePrompt(prompt) {
|
|
|
324
356
|
}
|
|
325
357
|
|
|
326
358
|
function userPromptSubmit(data) {
|
|
327
|
-
|
|
359
|
+
unlinkStamp(data, "tools-used");
|
|
328
360
|
const prompt = userPromptOf(data);
|
|
329
361
|
if (!isHostStopBanner(prompt)) {
|
|
330
|
-
|
|
331
|
-
}
|
|
332
|
-
// Sticky: only /solo and /cascade move
|
|
333
|
-
if (SOLO_RE.test(prompt)) {
|
|
334
|
-
const
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
362
|
+
unlinkStamp(data, "stop-block");
|
|
363
|
+
}
|
|
364
|
+
// Sticky: only exact /solo and /cascade move spawn-mode. Always unlink grunt-off.
|
|
365
|
+
if (SOLO_RE.test(prompt) || CASCADE_RE.test(prompt)) {
|
|
366
|
+
const token = SOLO_RE.test(prompt) ? "solo" : "cascade";
|
|
367
|
+
const cfg = loadSpawnMode(workspaceRootOf(data));
|
|
368
|
+
if (token === cfg) {
|
|
369
|
+
unlinkSpawnModeStamp(data);
|
|
370
|
+
} else {
|
|
371
|
+
writeSpawnModeStamp(data, token);
|
|
372
|
+
}
|
|
373
|
+
unlinkSoloStamp(data);
|
|
374
|
+
}
|
|
375
|
+
if (AUTO_RE.test(prompt) || ASK_RE.test(prompt)) {
|
|
376
|
+
const token = AUTO_RE.test(prompt) ? "auto" : "ask";
|
|
377
|
+
const cfg = loadLeftoverGate(workspaceRootOf(data));
|
|
378
|
+
if (token === cfg) {
|
|
379
|
+
unlinkAutoAskStamp(data);
|
|
380
|
+
} else {
|
|
381
|
+
writeAutoAskStamp(data, token);
|
|
338
382
|
}
|
|
339
|
-
} else if (CASCADE_RE.test(prompt)) {
|
|
340
|
-
unlinkQuiet(soloStampPath(data));
|
|
341
383
|
}
|
|
342
384
|
if (isParentEscapePrompt(prompt)) {
|
|
343
|
-
|
|
344
|
-
if (p) {
|
|
345
|
-
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
346
|
-
fs.writeFileSync(p, "1");
|
|
347
|
-
}
|
|
385
|
+
writeStamp(data, "parent-escape", "1");
|
|
348
386
|
} else {
|
|
349
|
-
|
|
387
|
+
unlinkStamp(data, "parent-escape");
|
|
350
388
|
}
|
|
351
389
|
return 0;
|
|
352
390
|
}
|
|
353
391
|
|
|
354
392
|
const RECAP_TAG_RE =
|
|
355
|
-
/^\[(?:orchestrator|grunt|implementer|thinker|handoff)\]:/;
|
|
393
|
+
/^\[(?:orchestrator|grunt|implementer|thinker|handoff|tmp)\]:/;
|
|
356
394
|
const WAIT_GRUNT = "[orchestrator]: wait grunt";
|
|
357
395
|
|
|
358
396
|
function firstNonEmptyStripped(msg) {
|
|
@@ -376,6 +414,38 @@ export function isWaitGruntExact(msg) {
|
|
|
376
414
|
return nonempty[0].replace(/^[\s`*_>]+/, "") === WAIT_GRUNT;
|
|
377
415
|
}
|
|
378
416
|
|
|
417
|
+
function nonemptyLines(msg) {
|
|
418
|
+
return String(msg || "")
|
|
419
|
+
.split("\n")
|
|
420
|
+
.filter((l) => l.trim());
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/** Format-only leftover triple. No leftover-number → skill map. */
|
|
424
|
+
export function hasLeftoverTriple(msg) {
|
|
425
|
+
const lines = String(msg || "")
|
|
426
|
+
.split("\n")
|
|
427
|
+
.map((l) => l.replace(/^[\s`*_>]+/, "").trim());
|
|
428
|
+
const hit = (re) => lines.some((l) => re.test(l));
|
|
429
|
+
const tweak = hit(/^3\.\s+Tweak$/);
|
|
430
|
+
const impl =
|
|
431
|
+
hit(/^1\.\s+Implement with verbal plan$/) &&
|
|
432
|
+
hit(/^2\.\s+Implement with file plan$/);
|
|
433
|
+
const write =
|
|
434
|
+
hit(/^1\.\s+Write with verbal plan$/) &&
|
|
435
|
+
hit(/^2\.\s+Write with file plan$/);
|
|
436
|
+
return Boolean(tweak && (impl || write));
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/** Ask leftover-required: any `[thinker]:`; `[orchestrator]:` iff >1 nonempty line (except exact wait-grunt); `[grunt]|[implementer]|[handoff]|[tmp]:` exempt. Auto waives in Stop. */
|
|
440
|
+
export function leftoverRequiredAsk(msg) {
|
|
441
|
+
if (isWaitGruntExact(msg)) return false;
|
|
442
|
+
const first = firstNonEmptyStripped(msg);
|
|
443
|
+
if (/^\[(?:grunt|implementer|handoff|tmp)\]:/.test(first)) return false;
|
|
444
|
+
if (/^\[thinker\]:/.test(first)) return true;
|
|
445
|
+
if (/^\[orchestrator\]:/.test(first)) return nonemptyLines(msg).length > 1;
|
|
446
|
+
return false;
|
|
447
|
+
}
|
|
448
|
+
|
|
379
449
|
function payloadAssistantMessage(data) {
|
|
380
450
|
if (!data || typeof data !== "object") return "";
|
|
381
451
|
const camel = data.lastAssistantMessage;
|
|
@@ -466,9 +536,8 @@ function stop(data) {
|
|
|
466
536
|
// Before the parent-escape consume: solo must never burn the one-turn stamp.
|
|
467
537
|
if (isSoloMode(data)) return 0;
|
|
468
538
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
unlinkQuiet(escapeStamp);
|
|
539
|
+
if (resolveStamp(data, "parent-escape")) {
|
|
540
|
+
unlinkStamp(data, "parent-escape");
|
|
472
541
|
return 0;
|
|
473
542
|
}
|
|
474
543
|
|
|
@@ -483,22 +552,22 @@ function stop(data) {
|
|
|
483
552
|
if (waitFirst && !isWaitGruntExact(msg)) {
|
|
484
553
|
// fall through to block — leftover lines not allowed on mid-turn wait
|
|
485
554
|
} else if (isRecap(msg)) {
|
|
486
|
-
|
|
555
|
+
if (
|
|
556
|
+
leftoverGateOf(data) !== "auto" &&
|
|
557
|
+
leftoverRequiredAsk(msg) &&
|
|
558
|
+
!hasLeftoverTriple(msg)
|
|
559
|
+
) {
|
|
560
|
+
// fall through to block — leftover-required under ask
|
|
561
|
+
} else {
|
|
562
|
+
return 0;
|
|
563
|
+
}
|
|
487
564
|
}
|
|
488
565
|
|
|
489
|
-
|
|
490
|
-
let n = 0;
|
|
491
|
-
if (stopStamp && fs.existsSync(stopStamp)) {
|
|
492
|
-
n = parseInt(fs.readFileSync(stopStamp, "utf8"), 10);
|
|
493
|
-
if (!Number.isFinite(n) || n < 0) n = 0;
|
|
494
|
-
}
|
|
566
|
+
let n = readStampInt(data, "stop-block");
|
|
495
567
|
if (n >= MAX_STOP) {
|
|
496
568
|
return 0;
|
|
497
569
|
}
|
|
498
|
-
|
|
499
|
-
fs.mkdirSync(path.dirname(stopStamp), { recursive: true });
|
|
500
|
-
fs.writeFileSync(stopStamp, String(n + 1));
|
|
501
|
-
}
|
|
570
|
+
writeStamp(data, "stop-block", String(n + 1));
|
|
502
571
|
const reasonText = STOP_REASONS[Math.min(n, STOP_REASONS.length - 1)];
|
|
503
572
|
emit({ decision: "block", reason: reasonText });
|
|
504
573
|
return 0;
|
|
@@ -522,12 +591,7 @@ function interceptNeed(data, hookEventName) {
|
|
|
522
591
|
return 0;
|
|
523
592
|
}
|
|
524
593
|
|
|
525
|
-
|
|
526
|
-
let n = 0;
|
|
527
|
-
if (interceptStamp && fs.existsSync(interceptStamp)) {
|
|
528
|
-
n = parseInt(fs.readFileSync(interceptStamp, "utf8"), 10);
|
|
529
|
-
if (!Number.isFinite(n) || n < 0) n = 0;
|
|
530
|
-
}
|
|
594
|
+
let n = readStampInt(data, "need-intercept");
|
|
531
595
|
if (n >= MAX_INTERCEPT) {
|
|
532
596
|
return 0;
|
|
533
597
|
}
|
|
@@ -557,10 +621,7 @@ function interceptNeed(data, hookEventName) {
|
|
|
557
621
|
}
|
|
558
622
|
parts.push(String(result.text || "").trimEnd());
|
|
559
623
|
}
|
|
560
|
-
|
|
561
|
-
fs.mkdirSync(path.dirname(interceptStamp), { recursive: true });
|
|
562
|
-
fs.writeFileSync(interceptStamp, String(n + 1));
|
|
563
|
-
}
|
|
624
|
+
writeStamp(data, "need-intercept", String(n + 1));
|
|
564
625
|
const reason = parts.join("\n");
|
|
565
626
|
emit({
|
|
566
627
|
decision: "block",
|
|
@@ -579,6 +640,42 @@ function stampPath(data, prefix) {
|
|
|
579
640
|
return path.join(root, ORCHESTRATOR_LOGS_DIR, prefix + "-" + sid);
|
|
580
641
|
}
|
|
581
642
|
|
|
643
|
+
function legacyStampPath(data, prefix) {
|
|
644
|
+
const root = workspaceRootOf(data);
|
|
645
|
+
const sid = sessionIdOf(data) || "default";
|
|
646
|
+
if (!root) return null;
|
|
647
|
+
return path.join(root, LEGACY_ORCHESTRATOR_LOGS_DIR, prefix + "-" + sid);
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
function resolveStamp(data, prefix) {
|
|
651
|
+
const neu = stampPath(data, prefix);
|
|
652
|
+
if (neu && fs.existsSync(neu)) return neu;
|
|
653
|
+
const old = legacyStampPath(data, prefix);
|
|
654
|
+
if (old && fs.existsSync(old)) return old;
|
|
655
|
+
return null;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
function writeStamp(data, prefix, body) {
|
|
659
|
+
const p = stampPath(data, prefix);
|
|
660
|
+
if (!p) return null;
|
|
661
|
+
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
662
|
+
fs.writeFileSync(p, body);
|
|
663
|
+
return p;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
function unlinkStamp(data, prefix) {
|
|
667
|
+
unlinkQuiet(stampPath(data, prefix));
|
|
668
|
+
unlinkQuiet(legacyStampPath(data, prefix));
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
function readStampInt(data, prefix) {
|
|
672
|
+
const p = resolveStamp(data, prefix);
|
|
673
|
+
if (!p) return 0;
|
|
674
|
+
const n = parseInt(fs.readFileSync(p, "utf8"), 10);
|
|
675
|
+
if (!Number.isFinite(n) || n < 0) return 0;
|
|
676
|
+
return n;
|
|
677
|
+
}
|
|
678
|
+
|
|
582
679
|
function sessionIdOf(data) {
|
|
583
680
|
return String(
|
|
584
681
|
process.env.GROK_SESSION_ID ||
|
|
@@ -595,6 +692,127 @@ function soloStampPath(data) {
|
|
|
595
692
|
return path.join(root, ORCHESTRATOR_LOGS_DIR, SOLO_STAMP + "-" + sid);
|
|
596
693
|
}
|
|
597
694
|
|
|
695
|
+
function legacySoloStampPath(data) {
|
|
696
|
+
const root = workspaceRootOf(data);
|
|
697
|
+
const sid = sessionIdOf(data);
|
|
698
|
+
if (!root || !sid) return null;
|
|
699
|
+
return path.join(root, LEGACY_ORCHESTRATOR_LOGS_DIR, SOLO_STAMP + "-" + sid);
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
function resolveSoloStamp(data) {
|
|
703
|
+
const neu = soloStampPath(data);
|
|
704
|
+
if (neu && fs.existsSync(neu)) return neu;
|
|
705
|
+
const old = legacySoloStampPath(data);
|
|
706
|
+
if (old && fs.existsSync(old)) return old;
|
|
707
|
+
return null;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
function unlinkSoloStamp(data) {
|
|
711
|
+
unlinkQuiet(soloStampPath(data));
|
|
712
|
+
unlinkQuiet(legacySoloStampPath(data));
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
/** Spawn-mode stamp: never share a `default` stamp across sid-less sessions. */
|
|
716
|
+
export function spawnModeStampPath(data) {
|
|
717
|
+
const root = workspaceRootOf(data);
|
|
718
|
+
const sid = sessionIdOf(data);
|
|
719
|
+
if (!root || !sid) return null;
|
|
720
|
+
return path.join(root, ORCHESTRATOR_LOGS_DIR, SPAWN_MODE_STAMP + "-" + sid);
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
function legacySpawnModeStampPath(data) {
|
|
724
|
+
const root = workspaceRootOf(data);
|
|
725
|
+
const sid = sessionIdOf(data);
|
|
726
|
+
if (!root || !sid) return null;
|
|
727
|
+
return path.join(root, LEGACY_ORCHESTRATOR_LOGS_DIR, SPAWN_MODE_STAMP + "-" + sid);
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
function resolveSpawnModeStamp(data) {
|
|
731
|
+
const neu = spawnModeStampPath(data);
|
|
732
|
+
if (neu && fs.existsSync(neu)) return neu;
|
|
733
|
+
const old = legacySpawnModeStampPath(data);
|
|
734
|
+
if (old && fs.existsSync(old)) return old;
|
|
735
|
+
return null;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
function writeSpawnModeStamp(data, body) {
|
|
739
|
+
const p = spawnModeStampPath(data);
|
|
740
|
+
if (!p) return null;
|
|
741
|
+
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
742
|
+
fs.writeFileSync(p, body);
|
|
743
|
+
return p;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
function unlinkSpawnModeStamp(data) {
|
|
747
|
+
unlinkQuiet(spawnModeStampPath(data));
|
|
748
|
+
unlinkQuiet(legacySpawnModeStampPath(data));
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
/** Valid stamp solo|cascade wins; else grunt-off presence as solo; else config. Unreadable/bad body ignored. */
|
|
752
|
+
export function spawnModeOf(data) {
|
|
753
|
+
try {
|
|
754
|
+
const p = resolveSpawnModeStamp(data);
|
|
755
|
+
if (p) {
|
|
756
|
+
const body = fs.readFileSync(p, "utf8").trim();
|
|
757
|
+
if (body === "solo" || body === "cascade") return body;
|
|
758
|
+
}
|
|
759
|
+
} catch {
|
|
760
|
+
// unreadable stamp → fall through
|
|
761
|
+
}
|
|
762
|
+
if (resolveSoloStamp(data)) return "solo";
|
|
763
|
+
return loadSpawnMode(workspaceRootOf(data));
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
/** Leftover-gate stamp: never share a `default` stamp across sid-less sessions. */
|
|
767
|
+
export function autoAskStampPath(data) {
|
|
768
|
+
const root = workspaceRootOf(data);
|
|
769
|
+
const sid = sessionIdOf(data);
|
|
770
|
+
if (!root || !sid) return null;
|
|
771
|
+
return path.join(root, ORCHESTRATOR_LOGS_DIR, AUTO_ASK_STAMP + "-" + sid);
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
function legacyAutoAskStampPath(data) {
|
|
775
|
+
const root = workspaceRootOf(data);
|
|
776
|
+
const sid = sessionIdOf(data);
|
|
777
|
+
if (!root || !sid) return null;
|
|
778
|
+
return path.join(root, LEGACY_ORCHESTRATOR_LOGS_DIR, AUTO_ASK_STAMP + "-" + sid);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
function resolveAutoAskStamp(data) {
|
|
782
|
+
const neu = autoAskStampPath(data);
|
|
783
|
+
if (neu && fs.existsSync(neu)) return neu;
|
|
784
|
+
const old = legacyAutoAskStampPath(data);
|
|
785
|
+
if (old && fs.existsSync(old)) return old;
|
|
786
|
+
return null;
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
function writeAutoAskStamp(data, body) {
|
|
790
|
+
const p = autoAskStampPath(data);
|
|
791
|
+
if (!p) return null;
|
|
792
|
+
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
793
|
+
fs.writeFileSync(p, body);
|
|
794
|
+
return p;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
function unlinkAutoAskStamp(data) {
|
|
798
|
+
unlinkQuiet(autoAskStampPath(data));
|
|
799
|
+
unlinkQuiet(legacyAutoAskStampPath(data));
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
/** Stamp body auto|ask wins; else config; else ask. Bad stamp body ignored. */
|
|
803
|
+
export function leftoverGateOf(data) {
|
|
804
|
+
try {
|
|
805
|
+
const p = resolveAutoAskStamp(data);
|
|
806
|
+
if (p) {
|
|
807
|
+
const body = fs.readFileSync(p, "utf8").trim();
|
|
808
|
+
if (body === "auto" || body === "ask") return body;
|
|
809
|
+
}
|
|
810
|
+
} catch {
|
|
811
|
+
// unreadable stamp → fall through
|
|
812
|
+
}
|
|
813
|
+
return loadLeftoverGate(workspaceRootOf(data));
|
|
814
|
+
}
|
|
815
|
+
|
|
598
816
|
function unlinkQuiet(p) {
|
|
599
817
|
if (!p) return;
|
|
600
818
|
try {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ask
|
|
3
|
+
description: >
|
|
4
|
+
Session leftover-gate. /ask sets leftover-gate ask for this session — leftover
|
|
5
|
+
triple wait on advise-class recaps. Not leftover y/n. Not pick 3 Tweak. Not
|
|
6
|
+
spawn-escape. /auto sets auto.
|
|
7
|
+
---
|
|
8
|
+
Session leftover-gate, not spawn-escape. Not leftover y/n. Not pick 3 Tweak.
|
|
9
|
+
|
|
10
|
+
Whole-prompt `/ask` only (`/ask foo` no-op). Persists until `/auto` or session end.
|
|
11
|
+
|
|
12
|
+
Effective leftover-gate: stamp body `auto`|`ask` > `.rulesync/grunt.config.jsonc` `leftoverGate` > `ask`. Fail-closed `ask`.
|
|
13
|
+
|
|
14
|
+
Claude/Codex/Grok: `/ask` via submit hook. If slash token equals committed config `leftoverGate`, unlink `.tmp/grunt/orchestrator-logs/auto-ask-{sid}`; else write stamp body `ask`. Requires real sid (never `default`). Agents/Antigravity: instruction-only (this skill body); cannot create stamp via `/ask`; Stop still honors a pre-existing stamp if present. Session-scoped, never global.
|
|
15
|
+
|
|
16
|
+
Ask: always-print typed leftover triple on advise-class final recap. Write-typed leftover never spawns implementer. Not leftover pick 3.
|
|
17
|
+
|
|
18
|
+
Not `/solo`. Spawn-first still. Recap tags still.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: auto
|
|
3
|
+
description: >
|
|
4
|
+
Session leftover-gate. /auto sets leftover-gate auto for this session —
|
|
5
|
+
Implement-typed pick2 chains write-plan persist then implement-plan {n} and
|
|
6
|
+
skips leftover wait. Not autofix. Not spawn-escape. /ask restores ask.
|
|
7
|
+
---
|
|
8
|
+
Session leftover-gate, not spawn-escape. Not autofix. Not leftover y/n.
|
|
9
|
+
|
|
10
|
+
Whole-prompt `/auto` only (`/auto foo` no-op). Persists until `/ask` or session end.
|
|
11
|
+
|
|
12
|
+
Effective leftover-gate: stamp body `auto`|`ask` > `.rulesync/grunt.config.jsonc` `leftoverGate` > `ask`. Fail-closed `ask`.
|
|
13
|
+
|
|
14
|
+
Claude/Codex/Grok: `/auto` via submit hook. If slash token equals committed config `leftoverGate`, unlink `.tmp/grunt/orchestrator-logs/auto-ask-{sid}`; else write stamp body `auto`. Requires real sid (never `default`). Agents/Antigravity: instruction-only (this skill body); cannot create stamp via `/auto`; Stop still honors a pre-existing stamp if present. Session-scoped, never global.
|
|
15
|
+
|
|
16
|
+
effective=auto + Implement-typed: write-plan persist then implement-plan `{n}`; skip leftover triple wait. Write-typed under auto still leftover wait; never spawn implementer.
|
|
17
|
+
|
|
18
|
+
Not `/solo`. Spawn-first still. Recap tags still.
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: browser
|
|
3
3
|
description: >
|
|
4
|
-
|
|
5
|
-
nav|snap|click|fill|shot|pdf|stop. Lightpanda default;
|
|
6
|
-
read. Windows → Chromium. Never MCP. Never env knobs.
|
|
4
|
+
Live URL/interact → this; not websearch. /browser or browse a URL. Call node
|
|
5
|
+
scripts/browser.mjs nav|snap|click|fill|shot|pdf|stop. Lightpanda default;
|
|
6
|
+
snap is the default read. Windows → Chromium. Never MCP. Never env knobs.
|
|
7
|
+
Never raw Playwright.
|
|
7
8
|
---
|
|
8
9
|
Zero-config. `node scripts/browser.mjs <verb>`.
|
|
9
10
|
|
|
@@ -13,14 +13,16 @@ Session mode, not one-turn. Persists until `/solo` or session end.
|
|
|
13
13
|
This session: cascade restored. You are the parent orchestrator.
|
|
14
14
|
|
|
15
15
|
- Spawn grunt|implementer|thinker.
|
|
16
|
-
- Recap tags. Recap `[orchestrator]:` `[grunt]:` `[implementer]:` `[thinker]:` `[handoff]:`.
|
|
16
|
+
- Recap tags. Recap `[orchestrator]:` `[grunt]:` `[implementer]:` `[thinker]:` `[handoff]:` `[tmp]:`.
|
|
17
17
|
- Spawn-first rule. `need:`/`verdict:` grammar.
|
|
18
18
|
- Voice unchanged: `.rulesync/reference/output.md`. `/explain` is still the voice escape.
|
|
19
19
|
|
|
20
20
|
Fat-tool caps stay on — token guards, not cascade.
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
Effective spawn: stamp body `solo`|`cascade` > one-release `grunt-off-{sid}` presence as solo > `.rulesync/grunt.config.jsonc` `spawnMode` > `cascade`. Fail-closed `cascade`. Unreadable/bad stamp body ignored (dual-read grunt-off else config). Never fail-closed solo.
|
|
23
|
+
|
|
24
|
+
Claude/Codex/Grok: `/cascade` via submit hook. If slash token equals committed config `spawnMode`, unlink `.tmp/grunt/orchestrator-logs/spawn-mode-{sid}`; else write stamp body `cascade`. Always unlink `grunt-off-{sid}` (new+legacy). Requires real sid (never `default`). Already matching config with no leftover grunt-off: no-op. Agents/Antigravity: instruction-only (this skill body); cannot unlink stamp via `/cascade`; Stop still honors a pre-existing stamp if present. Session-scoped, never global.
|
|
23
25
|
|
|
24
26
|
`/explain` is voice-only not spawn-escape. After unlink first token = spawn.
|
|
25
27
|
|
|
26
|
-
After `/solo`, or when spawn/recap/need|verdict must resume.
|
|
28
|
+
After `/solo`, or when spawn/recap/need|verdict must resume. Leftover-gate `/auto`/`/ask` is not spawn-escape.
|
|
@@ -80,6 +80,6 @@ Do not dump the handoff body.
|
|
|
80
80
|
|
|
81
81
|
- No spawn. One write. `/handoff` is not a mode.
|
|
82
82
|
- Never parent Read/Bash. Write handoffs dir only.
|
|
83
|
-
- Handoff ≠ plan: no Steps/Verify phases, no `.tmp/plans/` write.
|
|
83
|
+
- Handoff ≠ plan: no Steps/Verify phases, no `.tmp/grunt/plans/` write.
|
|
84
84
|
- Never rewrite or renumber an existing handoff; new session = new serial.
|
|
85
85
|
- Protocol: `.rulesync/reference/cascade.md`. Do not paste it here.
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: implement-plan
|
|
3
3
|
description: >
|
|
4
|
-
Implementer executes a local .tmp/plans checklist: continue, resume, or
|
|
5
|
-
among plans. Empty /implement-plan resumes a unique in-progress or starts
|
|
6
|
-
unique ready plan; else lists and needs serial. Marks [x], skips completed
|
|
4
|
+
Implementer executes a local .tmp/grunt/plans checklist: continue, resume, or
|
|
5
|
+
pick among plans. Empty /implement-plan resumes a unique in-progress or starts
|
|
6
|
+
a unique ready plan; else lists and needs serial. Marks [x], skips completed
|
|
7
7
|
leaves. Not /implement review-loop, not /execute-plan PR DAG, not /pickup, not
|
|
8
8
|
/run-plan.
|
|
9
9
|
---
|
|
10
10
|
# implement-plan
|
|
11
11
|
|
|
12
|
-
Parent: spawn grunt to list/validate `.tmp/plans/` when the target is unknown or empty-arg; then spawn implementer with abs plan path + `{repo}/.rulesync/reference/plan-format.md` + first open leaf. Recap from implementer output only. No parent Read/Grep/Bash. Agent-agnostic (grunt then implementer). Not `/pickup` (handoffs only). Not `/run-plan`. Not `/execute-plan` (PR DAG).
|
|
12
|
+
Parent: spawn grunt to list/validate `.tmp/grunt/plans/` when the target is unknown or empty-arg; then spawn implementer with abs plan path + `{repo}/.rulesync/reference/plan-format.md` + first open leaf. Recap from implementer output only. No parent Read/Grep/Bash. Agent-agnostic (grunt then implementer). Not `/pickup` (handoffs only). Not `/run-plan`. Not `/execute-plan` (PR DAG).
|
|
13
13
|
|
|
14
14
|
Format SSOT: `.rulesync/reference/plan-format.md`.
|
|
15
15
|
|
|
@@ -22,7 +22,7 @@ Todos: `resolve` → `implement` → `verify`. One serial per run. Partial progr
|
|
|
22
22
|
|
|
23
23
|
## Resolve
|
|
24
24
|
|
|
25
|
-
Arg = `$ARGUMENTS` stripped. Listing/validate: spawn grunt (`job:search|exec`) on `.tmp/plans/`. Do not parent Read/Grep/Bash.
|
|
25
|
+
Arg = `$ARGUMENTS` stripped. Listing/validate: spawn grunt (`job:search|exec`) on `.tmp/grunt/plans/`. Do not parent Read/Grep/Bash.
|
|
26
26
|
|
|
27
27
|
Eligible auto = valid plan-format checkbox grammar (`N [ ]` / `N.M [ ]`; not `- [ ]`; not `1. [ ]`). Invalid grammar: exclude from auto. Drop `done` (all `[x]` or FM `status: done`) from auto. Never silent latest. Never auto-pick among many.
|
|
28
28
|
|
|
@@ -34,7 +34,7 @@ Eligible auto = valid plan-format checkbox grammar (`N [ ]` / `N.M [ ]`; not `-
|
|
|
34
34
|
|
|
35
35
|
### Empty arg (`/implement-plan`)
|
|
36
36
|
|
|
37
|
-
Grunt lists/validates `.tmp/plans/*.md`. Then:
|
|
37
|
+
Grunt lists/validates `.tmp/grunt/plans/*.md`. Then:
|
|
38
38
|
|
|
39
39
|
| condition | action |
|
|
40
40
|
|---|---|
|
|
@@ -54,9 +54,9 @@ That file only; skip `[x]`; remaining leaves. Write-plan remainder stays `/imple
|
|
|
54
54
|
| input | action |
|
|
55
55
|
|---|---|
|
|
56
56
|
| int (`1`, `0001`) | files whose leading number == that int |
|
|
57
|
-
| `{serial}-slug` or `*.md` or path | that file if under `.tmp/plans/` |
|
|
57
|
+
| `{serial}-slug` or `*.md` or path | that file if under `.tmp/grunt/plans/` |
|
|
58
58
|
| slug only (`add-auth`) | unique match `*-{slug}-*.md` or `*-{slug}.md` |
|
|
59
|
-
| `.tmp/plans` missing / no files | `no plans. /write-plan first` stop |
|
|
59
|
+
| `.tmp/grunt/plans` missing / no files | `no plans. /write-plan first` stop |
|
|
60
60
|
| 0 matches | `no plan serial={n}. have: {list}` stop |
|
|
61
61
|
| >1 matches | `ambiguous: {paths}` stop |
|
|
62
62
|
| all boxes already `[x]` / `status: done` | `[implementer]: already done path=...` **no implementer spawn** |
|
|
@@ -93,7 +93,7 @@ Wait. Spawn fail → `[implementer]: failed: {err}` stop.
|
|
|
93
93
|
Recap from implementer output only. No parent Read/Grep/Bash.
|
|
94
94
|
|
|
95
95
|
```
|
|
96
|
-
[implementer]: serial={int} path=.tmp/plans/{file} status={status} done={x}/{total}
|
|
96
|
+
[implementer]: serial={int} path=.tmp/grunt/plans/{file} status={status} done={x}/{total}
|
|
97
97
|
files: {abs paths}
|
|
98
98
|
{if leftover:}
|
|
99
99
|
leftover:
|
|
@@ -104,8 +104,9 @@ leftover:
|
|
|
104
104
|
## Rules
|
|
105
105
|
|
|
106
106
|
- Skip `[x]`; only flip boxes; no renumber/rewrite leaf text.
|
|
107
|
-
- No review-fix loop. Not bundled `/implement`.
|
|
108
|
-
- Slash `/implement-plan {n}` = disk/file run. Empty `/implement-plan` = unique-resume / unique-start / else list.
|
|
107
|
+
- No review-fix loop. Not bundled `/implement`. Implement leftover pick 1 (`Implement with verbal plan`) ≠ this skill (no `.tmp/grunt/plans`; no write-plan; no implement-plan). Bare `implement`/`implementer` → pick1 iff Implement-typed, not this skill; else recap “no implementer this remainder”.
|
|
108
|
+
- Slash `/implement-plan {n}` = disk/file run. Empty `/implement-plan` = unique-resume / unique-start / else list. Implement leftover pick 2 may invoke this sequencing without user slash (after write-plan persist; `plan=/abs/...` only). Write leftover pick 2 does not spawn implementer this turn. Always-do leftover match; leftover `2` ≠ Skill-name. Verbal leftover ≠ implement-plan.
|
|
109
|
+
- effective=auto + Implement-typed may chain this sequencing without user slash after write-plan persist (`plan=/abs/...`). Write-typed under auto: no implementer this turn; leftover wait.
|
|
109
110
|
- No auto `/write-plan` chain. Escalation → tell user to `/write-plan` a follow-up.
|
|
110
111
|
- Omit `model`. `subagent_type: implementer` only for this skill.
|
|
111
112
|
- First prompt sentence: `You are implementer subagent.`
|