@lovrozagar/grunt 0.1.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/agents/grunt.md +36 -0
- package/.agents/agents/implementer.md +40 -0
- package/.agents/agents/orchestrator.md +50 -0
- package/.agents/agents/thinker.md +27 -0
- package/.agents/hooks.json +26 -0
- package/.agents/mcp_config.json +3 -0
- package/.claude/agents/grunt.md +44 -0
- package/.claude/agents/implementer.md +46 -0
- package/.claude/agents/orchestrator.md +55 -0
- package/.claude/agents/thinker.md +34 -0
- package/.claude/settings.json +36 -0
- package/.codex/agents/grunt.toml +30 -0
- package/.codex/agents/implementer.toml +34 -0
- package/.codex/agents/orchestrator.toml +45 -0
- package/.codex/agents/thinker.toml +23 -0
- package/.codex/config.toml +2 -0
- package/.codex/hooks.json +26 -0
- package/.grok/agents/grunt.md +31 -0
- package/.grok/agents/implementer.md +35 -0
- package/.grok/agents/orchestrator.md +47 -0
- package/.grok/agents/thinker.md +25 -0
- package/.grok/config.toml +6 -0
- package/.grok/global-settings.toml +35 -0
- package/.grok/hooks/orchestrate-parent.js +476 -0
- package/.grok/hooks/orchestrate-parent.json +60 -0
- package/.grok/hooks/rtk-grok.js +137 -0
- package/.grok/hooks/rtk.json +16 -0
- package/.grok/hooks/rulesync.json +3 -0
- package/.grok/parent.md +1 -0
- package/.grok/roles/grunt.toml +5 -0
- package/.grok/roles/implementer.toml +5 -0
- package/.grok/roles/thinker.toml +5 -0
- package/.grok/skills/explain/SKILL.md +15 -0
- package/.grok/skills/implement-plan/SKILL.md +91 -0
- package/.grok/skills/parent/SKILL.md +12 -0
- package/.grok/skills/shared/plan-format.md +158 -0
- package/.grok/skills/write-plan/SKILL.md +84 -0
- package/.mcp.json +3 -0
- package/.rulesync/global-settings/antigravity.json +1 -0
- package/.rulesync/global-settings/claude.json +1 -0
- package/.rulesync/global-settings/codex.toml +1 -0
- package/.rulesync/global-settings/manifest.json +37 -0
- package/.rulesync/hooks.jsonc +22 -0
- package/.rulesync/mcp-policy.jsonc +15 -0
- package/.rulesync/reference/cascade.md +85 -0
- package/.rulesync/reference/hooks.md +19 -0
- package/.rulesync/reference/map.md +57 -0
- package/.rulesync/reference/rtk.md +23 -0
- package/.rulesync/rules/CLAUDE.md +13 -0
- package/.rulesync/rules/overview.md +23 -0
- package/.rulesync/skills/explain/SKILL.md +15 -0
- package/.rulesync/skills/parent/SKILL.md +12 -0
- package/.rulesync/skills/terse/SKILL.md +8 -0
- package/.rulesync/subagents/grunt.md +51 -0
- package/.rulesync/subagents/implementer.md +54 -0
- package/.rulesync/subagents/orchestrator.md +69 -0
- package/.rulesync/subagents/thinker.md +44 -0
- package/AGENTS.md +12 -0
- package/CLAUDE.md +7 -0
- package/LICENSE +21 -0
- package/README.md +56 -0
- package/bin/grunt.js +4 -0
- package/cli/grunt.mjs +54 -0
- package/cli/grunt.test.ts +117 -0
- package/cli/init.mjs +113 -0
- package/cli/init.test.ts +268 -0
- package/package.json +77 -0
- package/scripts/check-globals.mjs +98 -0
- package/scripts/emit-mcp-policy.mjs +434 -0
- package/scripts/gate-fat-tools.mjs +485 -0
- package/scripts/grunt-job.mjs +482 -0
- package/scripts/parse-need.mjs +75 -0
- package/scripts/persist-plan.mjs +309 -0
- package/scripts/purge-global-mcps.mjs +291 -0
- package/scripts/scrub-spawn-prompt.mjs +219 -0
- package/scripts/scrub-text +58 -0
- package/scripts/scrub-text-lib.mjs +210 -0
- package/scripts/sync-global-settings.mjs +239 -0
- package/scripts/telemetry.mjs +38 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/** Grok PreToolUse: rewrite Bash/run_terminal_command through `rtk rewrite`.
|
|
3
|
+
|
|
4
|
+
Fail-open: parse error, missing rtk, timeout, no rewrite → empty stdout, exit 0.
|
|
5
|
+
Keep the full toolInput (Grok needs `description`); only replace `command`.
|
|
6
|
+
*/
|
|
7
|
+
"use strict";
|
|
8
|
+
|
|
9
|
+
const { spawnSync } = require("node:child_process");
|
|
10
|
+
|
|
11
|
+
const RTK_TIMEOUT_MS = 3000;
|
|
12
|
+
|
|
13
|
+
const ULTRA_SUBS = new Set([
|
|
14
|
+
"grep",
|
|
15
|
+
"rg",
|
|
16
|
+
"curl",
|
|
17
|
+
"wget",
|
|
18
|
+
"vitest",
|
|
19
|
+
"jest",
|
|
20
|
+
"pytest",
|
|
21
|
+
"test",
|
|
22
|
+
"lint",
|
|
23
|
+
"tsc",
|
|
24
|
+
"npm",
|
|
25
|
+
"npx",
|
|
26
|
+
"read",
|
|
27
|
+
"find",
|
|
28
|
+
"diff",
|
|
29
|
+
"json",
|
|
30
|
+
"log",
|
|
31
|
+
"playwright",
|
|
32
|
+
"cargo",
|
|
33
|
+
"ruff",
|
|
34
|
+
"prettier",
|
|
35
|
+
"format",
|
|
36
|
+
]);
|
|
37
|
+
|
|
38
|
+
function main() {
|
|
39
|
+
try {
|
|
40
|
+
const data = readJsonValue();
|
|
41
|
+
if (data === undefined) return 0;
|
|
42
|
+
if (!data || typeof data !== "object" || Array.isArray(data)) return 0;
|
|
43
|
+
let toolInput = data.toolInput;
|
|
44
|
+
if (toolInput == null) toolInput = data.tool_input;
|
|
45
|
+
if (!toolInput || typeof toolInput !== "object" || Array.isArray(toolInput)) return 0;
|
|
46
|
+
const cmdRaw = toolInput.command;
|
|
47
|
+
if (typeof cmdRaw !== "string") return 0;
|
|
48
|
+
const cmd = cmdRaw.trim();
|
|
49
|
+
if (!cmd) return 0;
|
|
50
|
+
|
|
51
|
+
const first = cmd.split(/\s+/, 1)[0] || "";
|
|
52
|
+
const already = first === "rtk" || first.endsWith("/rtk");
|
|
53
|
+
let rewritten;
|
|
54
|
+
if (already) {
|
|
55
|
+
rewritten = injectUltra(cmd);
|
|
56
|
+
if (rewritten === cmd) return 0;
|
|
57
|
+
} else {
|
|
58
|
+
rewritten = rewrite(cmd);
|
|
59
|
+
if (!rewritten) return 0;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const updated = Object.assign({}, toolInput, { command: rewritten });
|
|
63
|
+
process.stdout.write(
|
|
64
|
+
JSON.stringify({
|
|
65
|
+
hookSpecificOutput: {
|
|
66
|
+
hookEventName: "PreToolUse",
|
|
67
|
+
permissionDecisionReason: "RTK auto-rewrite",
|
|
68
|
+
updatedInput: updated,
|
|
69
|
+
},
|
|
70
|
+
}),
|
|
71
|
+
);
|
|
72
|
+
return 0;
|
|
73
|
+
} catch {
|
|
74
|
+
return 0;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function injectUltra(rewritten) {
|
|
79
|
+
if (rewritten.includes("--ultra-compact")) return rewritten;
|
|
80
|
+
const m = rewritten.match(/^(\S+)\s+(\S+)(?:\s+([\s\S]*))?$/);
|
|
81
|
+
if (!m || m[1] !== "rtk") return rewritten;
|
|
82
|
+
if (!ULTRA_SUBS.has(m[2])) return rewritten;
|
|
83
|
+
const rest = m[3] || "";
|
|
84
|
+
return "rtk " + m[2] + " --ultra-compact" + (rest ? " " + rest : "");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function whichRtk() {
|
|
88
|
+
const rtk = spawnSync("sh", ["-c", "command -v rtk"], { encoding: "utf8" });
|
|
89
|
+
const path = (rtk.stdout || "").trim();
|
|
90
|
+
return path || null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function rewrite(cmd) {
|
|
94
|
+
const rtk = whichRtk();
|
|
95
|
+
if (!rtk) return null;
|
|
96
|
+
try {
|
|
97
|
+
const proc = spawnSync(rtk, ["rewrite", "--ultra-compact", cmd], {
|
|
98
|
+
encoding: "utf8",
|
|
99
|
+
timeout: RTK_TIMEOUT_MS,
|
|
100
|
+
});
|
|
101
|
+
const out = (proc.stdout || "").trim();
|
|
102
|
+
if (!out || out === cmd) return null;
|
|
103
|
+
return injectUltra(out);
|
|
104
|
+
} catch {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Read one JSON value (Python json.load): stop when parseable, do not wait for EOF. */
|
|
110
|
+
function readJsonValue() {
|
|
111
|
+
const fs = require("node:fs");
|
|
112
|
+
let buf = Buffer.alloc(0);
|
|
113
|
+
const tmp = Buffer.alloc(8192);
|
|
114
|
+
for (;;) {
|
|
115
|
+
let n;
|
|
116
|
+
try {
|
|
117
|
+
n = fs.readSync(0, tmp, 0, tmp.length, null);
|
|
118
|
+
} catch {
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
if (n === 0) break;
|
|
122
|
+
buf = Buffer.concat([buf, tmp.subarray(0, n)]);
|
|
123
|
+
try {
|
|
124
|
+
return JSON.parse(buf.toString("utf8"));
|
|
125
|
+
} catch {
|
|
126
|
+
// incomplete
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (!buf.length) return undefined;
|
|
130
|
+
try {
|
|
131
|
+
return JSON.parse(buf.toString("utf8"));
|
|
132
|
+
} catch {
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
process.exit(main());
|
package/.grok/parent.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
SSOT: `.grok/agents/orchestrator.md` (`[agent] name = "orchestrator"`).
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: explain
|
|
3
|
+
description: >
|
|
4
|
+
One-off human explanation. Use for /explain, "explain this", or a request
|
|
5
|
+
to explain for a human. Not a session mode. Escape superterse for this
|
|
6
|
+
reply only, then revert.
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
This reply only: explain for a human. Not a mode switch or session flag. After this reply, revert to maximal superterse automatically.
|
|
10
|
+
|
|
11
|
+
Still concise. Complete sentences OK. Prioritize clarity over token compression. Do not dump, sprawl, or narrate process.
|
|
12
|
+
|
|
13
|
+
en-US unless asked. Keep need:/verdict:/plan grammar when those apply.
|
|
14
|
+
|
|
15
|
+
This skill is the only one-off escape from superterse.
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: implement-plan
|
|
3
|
+
description: >
|
|
4
|
+
Implementer executes a local .tmp/plans checklist by serial, marks [x],
|
|
5
|
+
skips completed leaves. Use to implement/continue a written local plan
|
|
6
|
+
or /implement-plan. Not /implement review-loop, not /execute-plan PR DAG.
|
|
7
|
+
when-to-use: >
|
|
8
|
+
Use when asked to "implement plan", "run plan N", "continue plan",
|
|
9
|
+
"execute the checklist", or "/implement-plan".
|
|
10
|
+
argument-hint: "<plan_id_serial>"
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# implement-plan
|
|
14
|
+
|
|
15
|
+
Resolve serial (cheap-in-parent) → spawn implementer → parent verifies boxes. Format: `../shared/plan-format.md` — read it; do not paste.
|
|
16
|
+
|
|
17
|
+
## Invocation
|
|
18
|
+
|
|
19
|
+
`/implement-plan <serial>`
|
|
20
|
+
|
|
21
|
+
Todos: `resolve` → `implement` → `verify`. One serial per run. Partial progress is ok; re-run continues.
|
|
22
|
+
|
|
23
|
+
## Resolve serial (no spawn)
|
|
24
|
+
|
|
25
|
+
Arg = `$ARGUMENTS` stripped.
|
|
26
|
+
|
|
27
|
+
| input | action |
|
|
28
|
+
|---|---|
|
|
29
|
+
| empty | list `.tmp/plans/*.md` as `serial slug status` (read frontmatter `status` if cheap; else filename). Stop: `need serial. have: ...` |
|
|
30
|
+
| int (`1`, `0001`) | files whose leading number == that int |
|
|
31
|
+
| `{serial}-slug` or `*.md` or path | that file if under `.tmp/plans/` |
|
|
32
|
+
| slug only (`add-auth`) | unique match `*-{slug}-*.md` or `*-{slug}.md` |
|
|
33
|
+
| `.tmp/plans` missing / no files | `no plans. /write-plan first` stop |
|
|
34
|
+
| 0 matches | `no plan serial={n}. have: {list}` stop |
|
|
35
|
+
| >1 matches | `ambiguous: {paths}` stop |
|
|
36
|
+
| all boxes already `[x]` / `status: done` | `[implementer]: already done path=...` **no spawn** |
|
|
37
|
+
|
|
38
|
+
Lookup: integer equality so `1` matches canonical `1-foo-20260826T143000Z.md` and legacy `1-foo.md`. `0001-foo.md` may still resolve if an old padded file exists. Normalize to abs path + int serial.
|
|
39
|
+
|
|
40
|
+
Gather listed plan-leaf absolute paths plus prefetch `verdict:` blobs before the up-spawn (search|exec via parent grunt-job). Do not `resume_from` for dumps already listed in the plan leaves. Do not add a repo-wide explore. Then spawn.
|
|
41
|
+
|
|
42
|
+
## Read format
|
|
43
|
+
|
|
44
|
+
`read_file` `{repo}/.grok/skills/shared/plan-format.md`.
|
|
45
|
+
|
|
46
|
+
## Spawn implementer
|
|
47
|
+
|
|
48
|
+
`subagent_type: implementer`, `background: true`, omit `model`, `isolation: none`, `description: "implement plan {serial}"`.
|
|
49
|
+
|
|
50
|
+
Prompt:
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
You are implementer subagent. Do not spawn.
|
|
54
|
+
|
|
55
|
+
Read {abs}/.grok/skills/shared/plan-format.md and the plan at {abs_plan_path}.
|
|
56
|
+
|
|
57
|
+
Implement remaining `[ ]` leaves in numeric order. Skip `[x]`.
|
|
58
|
+
After each leaf: flip that line to `[x]` in the plan file (only the box).
|
|
59
|
+
When a phase's children are all `[x]`, mark the phase `[x]`.
|
|
60
|
+
Frontmatter status: in-progress at start; done when all boxes `[x]`; blocked if you cannot finish.
|
|
61
|
+
Do not expand scope, add steps, or start a review loop.
|
|
62
|
+
If blocked: leave remaining `[ ]`, status: blocked, report reason + leftover ids.
|
|
63
|
+
Run Verify leaves. Report abs paths you changed.
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Wait. Spawn fail → `[implementer]: failed: {err}` stop.
|
|
67
|
+
|
|
68
|
+
## Parent verify (cheap 1-file)
|
|
69
|
+
|
|
70
|
+
Grep plan for `\[ \]` vs `\[x\]` (and `[X]`).
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
[implementer]: serial={int} path=.tmp/plans/{file} status={status} done={x}/{total}
|
|
74
|
+
files: {abs paths}
|
|
75
|
+
{if leftover:}
|
|
76
|
+
leftover:
|
|
77
|
+
{up to 10 open leaf lines}
|
|
78
|
+
{if blocked:} blocked: {reason}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Rules
|
|
82
|
+
|
|
83
|
+
- Skip `[x]`; only flip boxes; no renumber/rewrite leaf text.
|
|
84
|
+
- No review-fix loop. Not bundled `/implement`.
|
|
85
|
+
- No auto `/write-plan` chain. Escalation → tell user to `/write-plan` a follow-up.
|
|
86
|
+
- Omit `model`. `subagent_type: implementer` only for this skill.
|
|
87
|
+
- First prompt sentence: `You are implementer subagent.`
|
|
88
|
+
- Child prompt = task + abs paths + verdicts only (no pasted transcripts).
|
|
89
|
+
- Resume prompt = `You are implementer subagent.` + new verdicts only. Do not re-send the original task. Max 3 `resume_from` per child id; then report blocked/partial.
|
|
90
|
+
- Tool-call first for spawn claims.
|
|
91
|
+
- Do not re-plan. Partial = skill success (plan file is SoT).
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: parent
|
|
3
|
+
description: >
|
|
4
|
+
One-turn parent-orchestrator escape. Use for /parent when this turn
|
|
5
|
+
must complete in-parent. Not a session mode.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
This turn only: parent may finish in-session. Not a mode. After this reply, Stop enforcement resumes.
|
|
9
|
+
|
|
10
|
+
`/parent` writes `parent-escape-{sid}` (Stop consume+unlink once). Protocol: `.rulesync/reference/cascade.md`. Do not paste cascade here.
|
|
11
|
+
|
|
12
|
+
Do not stay in-parent after this reply. Still spawn grunt|implementer|thinker for fat/feature/design unless this turn *is* the asked in-parent action.
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# Plan format (SSOT)
|
|
2
|
+
|
|
3
|
+
Local implementer checklist. Not a design doc, PR stack, or DAG.
|
|
4
|
+
|
|
5
|
+
## Path
|
|
6
|
+
|
|
7
|
+
`.tmp/plans/{serial}-{slug}-{YYYYMMDDTHHMMSSZ}.md`
|
|
8
|
+
|
|
9
|
+
- **serial:** unpadded decimal int only (`1`, `12`, `10000`). **Never** `0001`, never quoted `"0001"` / `"1"`. Serial ≥ 1.
|
|
10
|
+
- **filename:** unpadded serial, slug, UTC datetime stamp (`6-sync-global-settings-20260826T143000Z.md`, not `0001-slug.md`). Stamp is `created` with `-` and `:` dropped (keep `T` and `Z`).
|
|
11
|
+
- **lookup:** integer equality on leading digits (`1` matches `1-foo-20260826T143000Z.md` and legacy `1-foo.md`; also `0001-foo.md` if an old padded file still exists).
|
|
12
|
+
- **valid filename (new):** `^[0-9]+-[a-z0-9]+(-[a-z0-9]+)*-\d{8}T\d{6}Z\.md$`
|
|
13
|
+
- **valid filename (legacy):** `^[0-9]+-[a-z0-9]+(-[a-z0-9]+)*\.md$` (no stamp; still accepted for existing files)
|
|
14
|
+
- Frontmatter line must match `^serial: [1-9][0-9]*$`. Leading zeros are invalid (YAML octal / VS Code warning).
|
|
15
|
+
- **New writes** always use the stamped filename. Collision: increment serial, rebuild filename, same stamp.
|
|
16
|
+
|
|
17
|
+
## Slugify (`PLAN_NAME`)
|
|
18
|
+
|
|
19
|
+
1. lower
|
|
20
|
+
2. non `[a-z0-9]` → `-`
|
|
21
|
+
3. collapse `-`, strip edges
|
|
22
|
+
4. trim to 50 chars; if cut on `-`, strip trailing `-`
|
|
23
|
+
5. empty → `unnamed`
|
|
24
|
+
6. strip trailing `.lock` if any
|
|
25
|
+
|
|
26
|
+
## Allocate next serial (grunt, bash)
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
mkdir -p .tmp/plans
|
|
30
|
+
next=1
|
|
31
|
+
for f in .tmp/plans/*; do
|
|
32
|
+
[ -f "$f" ] || continue
|
|
33
|
+
b=$(basename "$f")
|
|
34
|
+
case "$b" in
|
|
35
|
+
[0-9]*.md) n=${b%%-*}; n=$((10#$n)); [ "$n" -ge "$next" ] && next=$((n+1));;
|
|
36
|
+
esac
|
|
37
|
+
done
|
|
38
|
+
# filename prefix: unpadded decimal "$next" (1, 12, 10000)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Collision: if target path exists, increment and retry (max 5).
|
|
42
|
+
|
|
43
|
+
## Frontmatter (required)
|
|
44
|
+
|
|
45
|
+
```yaml
|
|
46
|
+
---
|
|
47
|
+
serial: 1
|
|
48
|
+
name: add-auth
|
|
49
|
+
status: ready # ready | in-progress | done | blocked
|
|
50
|
+
created: 2026-08-26T14:30:00Z # UTC ISO-8601 seconds + Z; no milliseconds
|
|
51
|
+
source: "<≤120 chars of user task>"
|
|
52
|
+
---
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
`name` is the slug only (no datetime). `source` ≤120 chars.
|
|
56
|
+
|
|
57
|
+
`created` for **new writes:** `new Date().toISOString()` with milliseconds stripped (`.\d{3}Z` → `Z`), e.g. `2026-08-26T14:30:00Z`. Existing files may still have `created: YYYY-MM-DD`.
|
|
58
|
+
|
|
59
|
+
### Status machine
|
|
60
|
+
|
|
61
|
+
`ready` → `in-progress` → `done` | `blocked`
|
|
62
|
+
|
|
63
|
+
- Fresh write: `ready`
|
|
64
|
+
- Implement start: `in-progress`
|
|
65
|
+
- All boxes `[x]`: `done`
|
|
66
|
+
- Cannot finish: `blocked` (leave remaining `[ ]`)
|
|
67
|
+
|
|
68
|
+
## Body headings (this order only)
|
|
69
|
+
|
|
70
|
+
`# {slug}`, `## Goal`, `## Context`, `## Constraints`, `## Watch-outs`, `## Steps`, `## Verify`
|
|
71
|
+
|
|
72
|
+
No other `##` headings. No PR Plan, Key Decisions, or DAG.
|
|
73
|
+
|
|
74
|
+
## Checkbox grammar
|
|
75
|
+
|
|
76
|
+
- Open: `[ ]` (space in box required)
|
|
77
|
+
- Done: `[x]` (accept `[X]`, write `[x]`)
|
|
78
|
+
- Line: `^{id} [ ] {text}$` with optional 0–2 space indent
|
|
79
|
+
- `{id}` = `N` / `N.M` / `N.M.K` (digits + dots). **Not** `1. [ ]`. **Not** GitHub `- [ ]`
|
|
80
|
+
- Fresh plans: all `[ ]`, never `[x]`
|
|
81
|
+
- Marking done: only flip the box. No renumber, delete, or rewrite leaf text
|
|
82
|
+
- Depth 2 default (`N` phase + `N.M` leaf). 3rd level only if a leaf would itself be a mini-plan
|
|
83
|
+
- Every phase `N` has ≥1 leaf `N.M`. Implementer **executes leaves**. Roll up: all children `[x]` ⇒ parent `[x]`
|
|
84
|
+
- Verify = last numbered phase, same grammar
|
|
85
|
+
- Done plan: every checkbox `[x]`, `status: done`
|
|
86
|
+
|
|
87
|
+
## Prose vs steps
|
|
88
|
+
|
|
89
|
+
| Section | Amount |
|
|
90
|
+
|---|---|
|
|
91
|
+
| Goal | 1–3 sentences. What “done” means. |
|
|
92
|
+
| Context / Constraints / Watch-outs | bullets only. Watch-outs: non-obvious footguns; empty allowed (`(none)`). |
|
|
93
|
+
| Steps | bulk of the file; each leaf = one coherent edit or one file-level action |
|
|
94
|
+
| Verify | 1–5 runnable leaves (cmd or observable) |
|
|
95
|
+
|
|
96
|
+
Thinker cites **absolute paths**. Leaves must be implementable without re-planning. Product fork → pick the narrowest default, state it under Constraints, still emit steps.
|
|
97
|
+
|
|
98
|
+
## Validation checklist
|
|
99
|
+
|
|
100
|
+
- filename matches new stamped regex or legacy unstamped; new writes unpadded + stamp (`1-foo-20260826T143000Z.md` not `0001-foo.md`); serial int equals leading digits; `name` equals slug after stripping optional trailing `-\d{8}T\d{6}Z`
|
|
101
|
+
- frontmatter `serial` line matches `^serial: [1-9][0-9]*$` (unpadded decimal ≥ 1; reject `0001`, `"0001"`, `0`, `01`)
|
|
102
|
+
- frontmatter keys: `serial`, `name`, `status`, `created`, `source`
|
|
103
|
+
- `status` ∈ `ready` | `in-progress` | `done` | `blocked`; `created` `YYYY-MM-DDTHH:MM:SSZ` (new writes) or legacy `YYYY-MM-DD`; `source` ≤120
|
|
104
|
+
- headings in order only; no extra `##`
|
|
105
|
+
- ≥1 `N.M [ ]` leaf; every phase has ≥1 leaf; Verify is last numbered phase
|
|
106
|
+
- checkbox grammar (`N [ ]` / `N.M [ ]` / `N.M.K [ ]`); no `- [ ]`; no `1. [ ]`
|
|
107
|
+
- fresh write: zero `[x]` / `[X]`; Goal 1–3 sentences; Context/Constraints/Watch-outs bullets; Verify 1–5 leaves
|
|
108
|
+
|
|
109
|
+
## Worked example
|
|
110
|
+
|
|
111
|
+
File: `.tmp/plans/1-add-tmp-ignore-20260826T143000Z.md`
|
|
112
|
+
|
|
113
|
+
```markdown
|
|
114
|
+
---
|
|
115
|
+
serial: 1
|
|
116
|
+
name: add-tmp-ignore
|
|
117
|
+
status: ready
|
|
118
|
+
created: 2026-08-26T14:30:00Z
|
|
119
|
+
source: "gitignore .tmp so local plans stay untracked"
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
# add-tmp-ignore
|
|
123
|
+
|
|
124
|
+
## Goal
|
|
125
|
+
Repo `.gitignore` ignores `.tmp/` so files under `.tmp/plans/` stay untracked. Existing ignore entries stay.
|
|
126
|
+
|
|
127
|
+
## Context
|
|
128
|
+
- `/home/ecomet/Development/example-app/.gitignore` may be missing or lack `.tmp/`
|
|
129
|
+
- Plans live at `/home/ecomet/Development/example-app/.tmp/plans/`
|
|
130
|
+
- No python helpers; bash only
|
|
131
|
+
|
|
132
|
+
## Constraints
|
|
133
|
+
- Do not clobber other `.gitignore` lines
|
|
134
|
+
- Do not create sample plans under `.tmp/plans` for git
|
|
135
|
+
- Do not edit `AGENTS.md`
|
|
136
|
+
|
|
137
|
+
## Watch-outs
|
|
138
|
+
- Duplicate `.tmp/` if append without grep
|
|
139
|
+
- Use directory form `.tmp/`, not `.tmp`
|
|
140
|
+
|
|
141
|
+
## Steps
|
|
142
|
+
1 [ ] gitignore
|
|
143
|
+
1.1 [ ] read `/home/ecomet/Development/example-app/.gitignore` if it exists
|
|
144
|
+
1.2 [ ] create `/home/ecomet/Development/example-app/.gitignore` when missing, body `.tmp/`
|
|
145
|
+
1.3 [ ] append `.tmp/` to `/home/ecomet/Development/example-app/.gitignore` if the line is absent
|
|
146
|
+
2 [ ] plans dir
|
|
147
|
+
2.1 [ ] mkdir -p `/home/ecomet/Development/example-app/.tmp/plans`
|
|
148
|
+
2.2 [ ] confirm the dir exists; do not add a sample `*.md` under it
|
|
149
|
+
3 [ ] sanity
|
|
150
|
+
3.1 [ ] grep `^\.tmp/` in `/home/ecomet/Development/example-app/.gitignore`
|
|
151
|
+
3.2 [ ] confirm no extra hunks in that file besides the ignore line
|
|
152
|
+
3.3 [ ] `git check-ignore -v /home/ecomet/Development/example-app/.tmp/plans` from repo root
|
|
153
|
+
|
|
154
|
+
## Verify
|
|
155
|
+
4 [ ] Verify
|
|
156
|
+
4.1 [ ] `git check-ignore -v .tmp/plans` reports ignored
|
|
157
|
+
4.2 [ ] `git status --short -- .gitignore` shows only the ignore edit
|
|
158
|
+
```
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: write-plan
|
|
3
|
+
description: >
|
|
4
|
+
Thinker drafts a local implementer-ready checklist plan; parent Write
|
|
5
|
+
persists .tmp/plans/{serial}-{slug}-{YYYYMMDDTHHMMSSZ}.md. Use for a numbered local plan, checklist
|
|
6
|
+
for implementer, "write a plan", or /write-plan. Not a design doc or PR stack.
|
|
7
|
+
when-to-use: >
|
|
8
|
+
Use when asked to "write a plan", "make a checklist plan",
|
|
9
|
+
"plan this for implementer", or "/write-plan".
|
|
10
|
+
argument-hint: "<task or context>"
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# write-plan
|
|
14
|
+
|
|
15
|
+
Parent orchestrates thinker then persists via Write under `.tmp/plans/`. Parent does not author the plan body. Format: `../shared/plan-format.md` — read it; do not paste.
|
|
16
|
+
|
|
17
|
+
## Invocation
|
|
18
|
+
|
|
19
|
+
`/write-plan <task>`
|
|
20
|
+
|
|
21
|
+
Optional todos: `think` → `write` → `report`. Always a new serial.
|
|
22
|
+
|
|
23
|
+
## Cheap-in-parent (no spawn)
|
|
24
|
+
|
|
25
|
+
Empty `$ARGUMENTS` **and** no task in conversation → `need task` and stop. Do not list the repo.
|
|
26
|
+
|
|
27
|
+
## Read format
|
|
28
|
+
|
|
29
|
+
`read_file` `{repo}/.grok/skills/shared/plan-format.md` (`dirname(SKILL.md)/../shared/plan-format.md`).
|
|
30
|
+
|
|
31
|
+
Gather the listed plan-format/SSOT absolute paths before the up-spawn. Prefetch search|exec via parent `node scripts/grunt-job.mjs` (not LLM grunt spawn). Do not `need:` for paths already listed.
|
|
32
|
+
|
|
33
|
+
## Step 1 — thinker
|
|
34
|
+
|
|
35
|
+
Spawn `subagent_type: thinker`, `background: true`, omit `model`, `description: "write plan"`.
|
|
36
|
+
|
|
37
|
+
Prompt:
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
You are thinker subagent. Read-only. Do not write files. Do not spawn.
|
|
41
|
+
|
|
42
|
+
Read {abs}/.grok/skills/shared/plan-format.md. Produce a plan that matches it.
|
|
43
|
+
|
|
44
|
+
Task:
|
|
45
|
+
{full $ARGUMENTS + relevant conversation context}
|
|
46
|
+
|
|
47
|
+
Cite absolute paths. Use only listed abs paths plus prefetch `verdict:` blobs. Do not explore the repo. Narrowest change that solves the task.
|
|
48
|
+
|
|
49
|
+
Output rules:
|
|
50
|
+
- Line 1: PLAN_NAME: <3-6 word name>
|
|
51
|
+
- Then the plan body only: # heading, Goal, Context, Constraints, Watch-outs, Steps, Verify
|
|
52
|
+
- All boxes [ ]
|
|
53
|
+
- No preamble, no summary, no extra sections
|
|
54
|
+
- Final message IS the plan (parent persists it verbatim)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Wait. Fail → `[thinker]: failed: {err}` stop.
|
|
58
|
+
|
|
59
|
+
**Truncation:** if output lacks `PLAN_NAME:` or has no `N.M [ ]` leaf, resume once (`resume_from`). Resume prompt = `You are thinker subagent.` plus the new instruction only (do not re-send the original task). Still bad → stop, no write. Max 3 `resume_from` per child id.
|
|
60
|
+
|
|
61
|
+
## Step 2 — persist (parent Write)
|
|
62
|
+
|
|
63
|
+
No grunt spawn. Parent `write`s the thinker final message under `{repo}/.tmp/plans/` (any filename in that dir). `orchestrate-parent.js` runs `scripts/persist-plan.mjs`: serial/slug from `PLAN_NAME:` / heading, inject YAML, rewrite path+content. Deny invalid plans and any write outside `.tmp/plans/`.
|
|
64
|
+
|
|
65
|
+
Report serial/path from the tool result (`updatedInput.file_path`). Fail/deny → `[write-plan]: failed: {err}` stop.
|
|
66
|
+
|
|
67
|
+
## Report
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
[thinker]: {one-line goal}; {N} leaves
|
|
71
|
+
[write-plan]: serial={int} path=.tmp/plans/{serial}-{slug}-{YYYYMMDDTHHMMSSZ}.md
|
|
72
|
+
next: /implement-plan {int}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Do not dump the plan.
|
|
76
|
+
|
|
77
|
+
## Rules
|
|
78
|
+
|
|
79
|
+
- Omit `model`. `subagent_type` ∈ {grunt, implementer, thinker} only (thinker for draft).
|
|
80
|
+
- First prompt sentence: `You are {agent} subagent.`
|
|
81
|
+
- Child prompt = task + abs paths + verdicts only (no pasted transcripts).
|
|
82
|
+
- Parent coordinates; parent Write under `.tmp/plans/` only; thinker never writes.
|
|
83
|
+
- Not `/design`, not `/execute-plan`, not a design doc.
|
|
84
|
+
- Tool-call first: emit `spawn_subagent` in the same turn as any spawn claim. Past tense after the tool result.
|
package/.mcp.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# reserved: noop / project-only
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"skipKeyPattern": "^(secrets?|api[_-]?key|access[_-]?token|tokens?|passwords?|authorization|mcp(servers)?|hooks)$",
|
|
4
|
+
"hosts": [
|
|
5
|
+
{
|
|
6
|
+
"id": "grok",
|
|
7
|
+
"mode": "merge",
|
|
8
|
+
"format": "toml",
|
|
9
|
+
"payload": ".grok/global-settings.toml",
|
|
10
|
+
"dest": ".grok/config.toml"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"id": "claude",
|
|
14
|
+
"mode": "noop",
|
|
15
|
+
"scope": "project-only",
|
|
16
|
+
"format": "json",
|
|
17
|
+
"payload": ".rulesync/global-settings/claude.json",
|
|
18
|
+
"dest": null
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"id": "codex",
|
|
22
|
+
"mode": "noop",
|
|
23
|
+
"scope": "project-only",
|
|
24
|
+
"format": "toml",
|
|
25
|
+
"payload": ".rulesync/global-settings/codex.toml",
|
|
26
|
+
"dest": null
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"id": "antigravity",
|
|
30
|
+
"mode": "noop",
|
|
31
|
+
"scope": "project-only",
|
|
32
|
+
"format": "json",
|
|
33
|
+
"payload": ".rulesync/global-settings/antigravity.json",
|
|
34
|
+
"dest": null
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"hooks": {
|
|
4
|
+
// Claude / Codex / Antigravity only (generate -t omits grokcli for hooks).
|
|
5
|
+
// Grok spawn/fat SSOT is hand-placed .grok/hooks/orchestrate-parent.js + orchestrate-parent.json.
|
|
6
|
+
"preToolUse": [
|
|
7
|
+
{
|
|
8
|
+
"matcher": "spawn_subagent|Task|Agent|spawn_agent",
|
|
9
|
+
"type": "command",
|
|
10
|
+
"command": "node \"${GROK_WORKSPACE_ROOT:-${CLAUDE_PROJECT_DIR:-.}}/scripts/scrub-spawn-prompt.mjs\"",
|
|
11
|
+
"timeout": 5
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
// Fat Read/Grep/Glob/Bash: inject limits, path denylist, implementer dump deny.
|
|
15
|
+
"matcher": "Read|read_file|Grep|grep|Glob|list_dir|Bash|run_terminal_command",
|
|
16
|
+
"type": "command",
|
|
17
|
+
"command": "node \"${GROK_WORKSPACE_ROOT:-${CLAUDE_PROJECT_DIR:-.}}/scripts/gate-fat-tools.mjs\"",
|
|
18
|
+
"timeout": 5
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// MCP deny-default SSOT. Named allow is empty this pass; do not add cloudflare/docker.
|
|
2
|
+
{
|
|
3
|
+
"default": "deny",
|
|
4
|
+
"allow": [],
|
|
5
|
+
"plugins": {
|
|
6
|
+
"denyDefault": true
|
|
7
|
+
},
|
|
8
|
+
"servers": {},
|
|
9
|
+
"agents": {
|
|
10
|
+
"orchestrator": { "inherit": false },
|
|
11
|
+
"implementer": { "inherit": false },
|
|
12
|
+
"thinker": { "inherit": false },
|
|
13
|
+
"grunt": { "inherit": false }
|
|
14
|
+
}
|
|
15
|
+
}
|