@lovrozagar/grunt 0.10.1 → 0.11.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.
Files changed (120) hide show
  1. package/.agents/agents/orchestrator.md +1 -1
  2. package/.agents/hooks.json +2 -2
  3. package/.agents/skills/browser/SKILL.md +3 -3
  4. package/.agents/skills/clasp/SKILL.md +1 -1
  5. package/.agents/skills/google-workspace/SKILL.md +3 -3
  6. package/.agents/skills/handoff/SKILL.md +2 -2
  7. package/.agents/skills/implement-plan/SKILL.md +2 -2
  8. package/.agents/skills/jev/SKILL.md +3 -3
  9. package/.agents/skills/listen/SKILL.md +3 -3
  10. package/.agents/skills/pickup/SKILL.md +1 -1
  11. package/.agents/skills/speak/SKILL.md +3 -3
  12. package/.agents/skills/tmp/SKILL.md +2 -2
  13. package/.agents/skills/write-plan/SKILL.md +4 -2
  14. package/.claude/agents/orchestrator.md +1 -1
  15. package/.claude/settings.json +3 -3
  16. package/.claude/skills/browser/SKILL.md +3 -3
  17. package/.claude/skills/clasp/SKILL.md +1 -1
  18. package/.claude/skills/google-workspace/SKILL.md +3 -3
  19. package/.claude/skills/handoff/SKILL.md +2 -2
  20. package/.claude/skills/implement-plan/SKILL.md +2 -2
  21. package/.claude/skills/jev/SKILL.md +3 -3
  22. package/.claude/skills/listen/SKILL.md +3 -3
  23. package/.claude/skills/pickup/SKILL.md +1 -1
  24. package/.claude/skills/speak/SKILL.md +3 -3
  25. package/.claude/skills/tmp/SKILL.md +2 -2
  26. package/.claude/skills/write-plan/SKILL.md +4 -2
  27. package/.codex/agents/orchestrator.toml +1 -1
  28. package/.codex/hooks.json +3 -3
  29. package/.grok/agents/orchestrator.md +1 -1
  30. package/.grok/hooks/orchestrate-parent.js +9 -9
  31. package/.grok/skills/browser/SKILL.md +3 -3
  32. package/.grok/skills/clasp/SKILL.md +1 -1
  33. package/.grok/skills/google-workspace/SKILL.md +3 -3
  34. package/.grok/skills/handoff/SKILL.md +2 -2
  35. package/.grok/skills/implement-plan/SKILL.md +2 -2
  36. package/.grok/skills/jev/SKILL.md +3 -3
  37. package/.grok/skills/listen/SKILL.md +3 -3
  38. package/.grok/skills/pickup/SKILL.md +1 -1
  39. package/.grok/skills/speak/SKILL.md +3 -3
  40. package/.grok/skills/tmp/SKILL.md +2 -2
  41. package/.grok/skills/write-plan/SKILL.md +4 -2
  42. package/.rulesync/hooks.jsonc +3 -3
  43. package/.rulesync/reference/INDEX.md +9 -9
  44. package/.rulesync/reference/browser.md +4 -4
  45. package/.rulesync/reference/cascade.md +2 -2
  46. package/.rulesync/reference/clasp.md +1 -1
  47. package/.rulesync/reference/google-workspace.md +29 -29
  48. package/.rulesync/reference/hooks.md +3 -3
  49. package/.rulesync/reference/implementation-format.md +2 -2
  50. package/.rulesync/reference/jev.md +3 -3
  51. package/.rulesync/reference/law.md +1 -1
  52. package/.rulesync/reference/listen.md +12 -12
  53. package/.rulesync/reference/map.md +25 -25
  54. package/.rulesync/reference/refs-map.md +3 -3
  55. package/.rulesync/reference/rtk.md +1 -1
  56. package/.rulesync/reference/scope.md +12 -14
  57. package/.rulesync/reference/setup.md +7 -7
  58. package/.rulesync/reference/skills-map.md +5 -5
  59. package/.rulesync/reference/speak.md +6 -6
  60. package/.rulesync/rules/overview.md +5 -5
  61. package/{scripts → .rulesync/scripts}/doctor.mjs +2 -2
  62. package/{scripts → .rulesync/scripts}/feature-packs.mjs +1 -1
  63. package/{scripts → .rulesync/scripts}/folder-map.mjs +7 -3
  64. package/{scripts → .rulesync/scripts}/gate-fat-tools.mjs +3 -3
  65. package/{scripts → .rulesync/scripts}/google-workspace.mjs +2 -2
  66. package/{scripts → .rulesync/scripts}/grunt-job.mjs +2 -0
  67. package/{scripts → .rulesync/scripts}/guarded-md.mjs +10 -4
  68. package/{scripts → .rulesync/scripts}/hooks-union.mjs +13 -8
  69. package/{scripts → .rulesync/scripts}/jev.mjs +2 -2
  70. package/{scripts → .rulesync/scripts}/listen.mjs +6 -6
  71. package/{scripts → .rulesync/scripts}/pipeline.mjs +14 -14
  72. package/{scripts → .rulesync/scripts}/session-map.mjs +1 -1
  73. package/{scripts → .rulesync/scripts}/setup.mjs +4 -4
  74. package/{scripts → .rulesync/scripts}/speak.mjs +4 -4
  75. package/.rulesync/skills/browser/SKILL.md +3 -3
  76. package/.rulesync/skills/clasp/SKILL.md +1 -1
  77. package/.rulesync/skills/google-workspace/SKILL.md +3 -3
  78. package/.rulesync/skills/handoff/SKILL.md +2 -2
  79. package/.rulesync/skills/implement-plan/SKILL.md +2 -2
  80. package/.rulesync/skills/jev/SKILL.md +3 -3
  81. package/.rulesync/skills/listen/SKILL.md +3 -3
  82. package/.rulesync/skills/pickup/SKILL.md +1 -1
  83. package/.rulesync/skills/speak/SKILL.md +3 -3
  84. package/.rulesync/skills/tmp/SKILL.md +2 -2
  85. package/.rulesync/skills/write-plan/SKILL.md +4 -2
  86. package/.rulesync/subagents/orchestrator.md +1 -1
  87. package/AGENTS.md +7 -5
  88. package/CHANGELOG.md +21 -3
  89. package/CLAUDE.md +2 -0
  90. package/README.md +23 -23
  91. package/cli/grunt.mjs +3 -3
  92. package/cli/grunt.test.ts +6 -6
  93. package/cli/init.mjs +64 -16
  94. package/cli/init.test.ts +310 -163
  95. package/cli/prompt.mjs +1 -1
  96. package/cli/self-update.mjs +1 -1
  97. package/package.json +15 -50
  98. /package/{scripts → .rulesync/scripts}/board.mjs +0 -0
  99. /package/{scripts → .rulesync/scripts}/browser.mjs +0 -0
  100. /package/{scripts → .rulesync/scripts}/check-globals.mjs +0 -0
  101. /package/{scripts → .rulesync/scripts}/emit-agent-shell-tools.mjs +0 -0
  102. /package/{scripts → .rulesync/scripts}/emit-gemini.mjs +0 -0
  103. /package/{scripts → .rulesync/scripts}/emit-maps.mjs +0 -0
  104. /package/{scripts → .rulesync/scripts}/emit-mcp-policy.mjs +0 -0
  105. /package/{scripts → .rulesync/scripts}/guarded-roots.mjs +0 -0
  106. /package/{scripts → .rulesync/scripts}/interactive.mjs +0 -0
  107. /package/{scripts → .rulesync/scripts}/jsonc.mjs +0 -0
  108. /package/{scripts → .rulesync/scripts}/package-manager.mjs +0 -0
  109. /package/{scripts → .rulesync/scripts}/parse-need.mjs +0 -0
  110. /package/{scripts → .rulesync/scripts}/persist-handoff.mjs +0 -0
  111. /package/{scripts → .rulesync/scripts}/persist-implementation.mjs +0 -0
  112. /package/{scripts → .rulesync/scripts}/persist-plan.mjs +0 -0
  113. /package/{scripts → .rulesync/scripts}/persist-tmp.mjs +0 -0
  114. /package/{scripts → .rulesync/scripts}/prompt.mjs +0 -0
  115. /package/{scripts → .rulesync/scripts}/purge-global-mcps.mjs +0 -0
  116. /package/{scripts → .rulesync/scripts}/scrub-spawn-prompt.mjs +0 -0
  117. /package/{scripts → .rulesync/scripts}/scrub-text +0 -0
  118. /package/{scripts → .rulesync/scripts}/scrub-text-lib.mjs +0 -0
  119. /package/{scripts → .rulesync/scripts}/skill-conflicts.mjs +0 -0
  120. /package/{scripts → .rulesync/scripts}/sync-global-settings.mjs +0 -0
@@ -4,10 +4,10 @@ description: "Execute remaining leaves in a .tmp/grunt/plans checklist. Skip [x]
4
4
  ---
5
5
  # implement-plan
6
6
 
7
- You execute the plan in this session. Skip `[x]`. Start at the first open leaf. Flip only the box after each leaf. When a phase's children are `[x]`, mark the phase `[x]`. Frontmatter `in-progress` then `done`.
7
+ You execute the plan in this session. Start only on a plan the user approved, or when the request said "plan and do it". Skip `[x]`. Start at the first open leaf. Flip only the box after each leaf. When a phase's children are `[x]`, mark the phase `[x]`. Frontmatter `in-progress` then `done`.
8
8
 
9
9
  Arg `{n|path}` picks that file. Empty arg: unique in-progress, else unique ready, else list serials.
10
10
 
11
- Format: `.rulesync/reference/plan-format.md`. Write-allowlist = paths listed in the plan. Run Verify leaves. Do not skip a test leaf. Do not flip a behavior leaf `[x]` until its test ran. Fat dumps go through `node scripts/grunt-job.mjs`.
11
+ Format: `.rulesync/reference/plan-format.md`. Write-allowlist = paths listed in the plan. Run Verify leaves. Do not skip a test leaf. Do not flip a behavior leaf `[x]` until its test ran. Fat dumps go through `node .rulesync/scripts/grunt-job.mjs`.
12
12
 
13
13
  On start: if this plan has no journal, Write one under `.tmp/grunt/implementations/` (`IMPL_NAME:`, `IMPL_PLAN: {n}`). Format: `.rulesync/reference/implementation-format.md`. After each leaf: append `N.M [x] …` to Log, update Files (repo-relative product paths) and Done. Blocked → `status: blocked` and Blockers. Verify done → `status: done`. Straight shots do not write a journal.
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  name: jev
3
- description: "Yes/no or named-option decision on text already saved. Run node scripts/jev.mjs --available first. On yes, one decision call. On no, decide yourself and do not mention Jev."
3
+ description: "Yes/no or named-option decision on text already saved. Run node .rulesync/scripts/jev.mjs --available first. On yes, one decision call. On no, decide yourself and do not mention Jev."
4
4
  ---
5
5
  # jev
6
6
 
7
7
  Use this when the source text is already saved and the leftover question is a yes/no or a choice among options the research already named. Spec: `.rulesync/reference/jev.md`.
8
8
 
9
- Run `node scripts/jev.mjs --available`. It prints `yes` or `no` and exits 0.
9
+ Run `node .rulesync/scripts/jev.mjs --available`. It prints `yes` or `no` and exits 0.
10
10
 
11
11
  `no`: answer the question yourself. Do not run a decision call. Do not mention Jev, a missing key, or setup.
12
12
 
13
- `yes`: read `.rulesync/reference/jev.md`. Write the passage and one questions file under `.tmp/grunt/jev/`. Run `node scripts/jev.mjs --state <state> --questions <questions>` once. Use the printed label and confidence. A low confidence is the result. Do not call again.
13
+ `yes`: read `.rulesync/reference/jev.md`. Write the passage and one questions file under `.tmp/grunt/jev/`. Run `node .rulesync/scripts/jev.mjs --state <state> --questions <questions>` once. Use the printed label and confidence. A low confidence is the result. Do not call again.
14
14
 
15
15
  Open research, code, and grunt-job stay with the session. If the decision call exits non-zero, answer from the sources. Do not invent a label.
@@ -1,13 +1,13 @@
1
1
  ---
2
2
  name: listen
3
- description: "Speech-to-text via node scripts/listen.mjs (ffmpeg mic + local whisper.cpp, OpenAI fallback). Input only."
3
+ description: "Speech-to-text via node .rulesync/scripts/listen.mjs (ffmpeg mic + local whisper.cpp, OpenAI fallback). Input only."
4
4
  ---
5
5
  # listen
6
6
 
7
- When the user wants to dictate, transcribe a recording, or toggle a mic, run `node scripts/listen.mjs`. Spec: `.rulesync/reference/listen.md`. Input only (audio → text). Do not speak. Do not use `/speak`.
7
+ When the user wants to dictate, transcribe a recording, or toggle a mic, run `node .rulesync/scripts/listen.mjs`. Spec: `.rulesync/reference/listen.md`. Input only (audio → text). Do not speak. Do not use `/speak`.
8
8
 
9
9
  `whoami` · `devices` · `rec [--seconds N]` · `start` · `stop` · `toggle` · `status` · `file --path P` · `latest`. `--clip` copies the transcript. `--cwd DIR` when the process is not already in the repo (hotkeys). `rec` prints the transcript and `transcript=.tmp/grunt/listen/latest.txt`.
10
10
 
11
11
  STT: local `whisper-cli` first (mac: `brew install whisper-cpp`); first listen downloads the ggml model to `~/.grunt/whisper/`. Else `OPENAI_API_KEY`. Mic verbs need ffmpeg. Do not tell them to pip-install openai-whisper or curl the model by hand.
12
12
 
13
- If the CLI exits non-zero, print its message. Lines with `setup:` are for the user — show that command. Tell them to run `node scripts/setup.mjs listen` (or `grunt setup`) in their terminal. Do not dump JSON. Do not invent a key. Spec: `.rulesync/reference/listen.md` `.rulesync/reference/setup.md`.
13
+ If the CLI exits non-zero, print its message. Lines with `setup:` are for the user — show that command. Tell them to run `node .rulesync/scripts/setup.mjs listen` (or `grunt setup`) in their terminal. Do not dump JSON. Do not invent a key. Spec: `.rulesync/reference/listen.md` `.rulesync/reference/setup.md`.
@@ -18,7 +18,7 @@ Load a handoff and continue in this session. Not a mode. Not `/handoff`. Inverse
18
18
 
19
19
  ## Resolve
20
20
 
21
- Read `.tmp/grunt/handoffs/`. Use `node scripts/grunt-job.mjs --job search|exec` when the listing is fat.
21
+ Read `.tmp/grunt/handoffs/`. Use `node .rulesync/scripts/grunt-job.mjs --job search|exec` when the listing is fat.
22
22
 
23
23
  Order:
24
24
 
@@ -1,13 +1,13 @@
1
1
  ---
2
2
  name: speak
3
- description: "Text-to-speech via node scripts/speak.mjs (ElevenLabs or OpenAI). Output only."
3
+ description: "Text-to-speech via node .rulesync/scripts/speak.mjs (ElevenLabs or OpenAI). Output only."
4
4
  ---
5
5
  # speak
6
6
 
7
- When the user wants spoken audio, run `node scripts/speak.mjs`. Spec: `.rulesync/reference/speak.md`. Output only (text → mp3). Do not record a mic. Do not transcribe.
7
+ When the user wants spoken audio, run `node .rulesync/scripts/speak.mjs`. Spec: `.rulesync/reference/speak.md`. Output only (text → mp3). Do not record a mic. Do not transcribe.
8
8
 
9
9
  `whoami` · `voices` · `say --text T [--provider elevenlabs|openai] [--voice ID] [--out path] [--play]`.
10
10
 
11
11
  Auth: `ELEVENLABS_API_KEY` or `OPENAI_API_KEY` or `~/.grunt/speak.json`. Do not commit the key. Default voices: ElevenLabs George (`JBFqnCBsd6RMkjVDRZzb`), OpenAI `coral`.
12
12
 
13
- If the CLI exits non-zero, print its message. Lines with `setup:` are for the user — show that command. Tell them to run `node scripts/setup.mjs speak` (or `grunt setup`) in their terminal. Do not dump JSON. Do not invent a key. Spec: `.rulesync/reference/speak.md` `.rulesync/reference/setup.md`.
13
+ If the CLI exits non-zero, print its message. Lines with `setup:` are for the user — show that command. Tell them to run `node .rulesync/scripts/setup.mjs speak` (or `grunt setup`) in their terminal. Do not dump JSON. Do not invent a key. Spec: `.rulesync/reference/speak.md` `.rulesync/reference/setup.md`.
@@ -33,9 +33,9 @@ TMP_EXT: md
33
33
  <artifact only>
34
34
  ```
35
35
 
36
- `TMP_EXT` optional. Write any root filename under `.tmp/grunt/` (not `plans|handoffs|implementations|browser|orchestrator-logs`). Grok `orchestrate-parent.js` runs `scripts/persist-tmp.mjs`: serial/slug/ext from `TMP_*`, strips those lines, no YAML frontmatter, rewrites path + content. Write outside that root or missing `TMP_NAME:` is denied.
36
+ `TMP_EXT` optional. Write any root filename under `.tmp/grunt/` (not `plans|handoffs|implementations|browser|orchestrator-logs`). Grok `orchestrate-parent.js` runs `.rulesync/scripts/persist-tmp.mjs`: serial/slug/ext from `TMP_*`, strips those lines, no YAML frontmatter, rewrites path + content. Write outside that root or missing `TMP_NAME:` is denied.
37
37
 
38
- Host without that hook: name the file per **Path** yourself, or pipe the body to `node scripts/persist-tmp.mjs --workspace {repo}` and use its `path`.
38
+ Host without that hook: name the file per **Path** yourself, or pipe the body to `node .rulesync/scripts/persist-tmp.mjs --workspace {repo}` and use its `path`.
39
39
 
40
40
  Never rewrite an existing dump; new serial. Artifact file content after hook = artifact only (no TMP_* lines no YAML frontmatter).
41
41
 
@@ -4,10 +4,12 @@ description: "Write a local checklist under .tmp/grunt/plans/. Then /implement-p
4
4
  ---
5
5
  # write-plan
6
6
 
7
- You author the plan. Persist under `.tmp/grunt/plans/` via Write (hook runs `scripts/persist-plan.mjs`). Format: `.rulesync/reference/plan-format.md`.
7
+ You author the plan. Persist under `.tmp/grunt/plans/` via Write (hook runs `.rulesync/scripts/persist-plan.mjs`). Format: `.rulesync/reference/plan-format.md`.
8
8
 
9
9
  Empty task → ask for a task and stop.
10
10
 
11
11
  Body starts with `PLAN_NAME: <3-6 word name>`, then Goal, Context, Constraints, Watch-outs, Steps, Verify. All boxes `[ ]`. Tests: new behavior or bug → failing test leaf before impl; other behavior → test leaf right after impl; docs/rename/config/generated → skip. Verify is the end gate.
12
12
 
13
- Report: wrote serial N at `.tmp/grunt/plans/{file}`. Next `/implement-plan {n}`. Do not dump the plan.
13
+ The plan is the proposal and caps the result (`.rulesync/reference/scope.md`). Before writing, research until every leaf is defensible; ask any plan-shaping question the user alone can answer, then research again on the answer. Do not default a plan-shaping choice. A minor open choice goes under Constraints as the chosen default plus "confirm".
14
+
15
+ Report: serial N at `.tmp/grunt/plans/{file}`, the goal, the phase list, the top risks, and any assumption to confirm. Do not dump the plan. Stop and ask for approval; on feedback, revise this file and stop again. Run `/implement-plan {n}` in the same turn only when the request said "plan and do it".
@@ -32,4 +32,4 @@ antigravity-cli:
32
32
  geminicli:
33
33
  model: gemini-2.5-pro
34
34
  ---
35
- Follow AGENTS.md. Do the work. Concise complete sentences with natural grammar. Scratch in `.tmp/grunt/`. Browse with `node scripts/browser.mjs`; the rail swaps to Chromium when Lightpanda is blocked. App e2e uses Playwright.
35
+ Follow AGENTS.md. Do the work. Concise complete sentences with natural grammar. Scratch in `.tmp/grunt/`. Browse with `node .rulesync/scripts/browser.mjs`; the rail swaps to Chromium when Lightpanda is blocked. App e2e uses Playwright.
package/AGENTS.md CHANGED
@@ -1,19 +1,21 @@
1
1
  <!-- grunt:begin -->
2
- You are the session agent. Do the work. Use en-US unless asked. Write concise complete sentences with natural grammar. Skip filler and fluff. Ship optimal solutions only; rewrite if not. Flag blockers. Do not monkey-patch. When advising, recommend the optimal choice based on research, data, and reasoning, not the user's preference. Change it only for new evidence or a flaw found in it. Pushback or a leading question is not a reason to switch; keep the recommendation and explain why. Do and verify the work yourself with every tool and credential you have. Don't guess, say "likely" or similar, trust older memory, stop short, leave placeholders, or hand the user anything you could do. Ask only when you truly lack the means, and say what you tried. Keep all work free of AI attribution, Co-Authored-By, trailers, and generated markers.
2
+
3
+ You are the session agent. Do the work. Use en-US unless asked. Write concise complete sentences with natural grammar. Skip filler and fluff. Ship optimal solutions only; rewrite if not. Flag blockers. Do not monkey-patch. When advising, recommend the optimal choice based on research, data, and reasoning, not the user's preference. Change it only for new evidence or a flaw found in it. Pushback or a leading question is not a reason to switch; keep the recommendation and explain why. Do and verify the work yourself with every tool and credential you have. Don't guess, say "likely" or similar, trust older memory, stop short, leave placeholders, or hand the user anything you could do. Stopping for approval of a written plan is not stopping short. Ask only when you truly lack the means (the user's intent and preferences count), and say what you tried. Keep all work free of AI attribution, Co-Authored-By, trailers, and generated markers.
3
4
 
4
5
  Examples are a class. `...`, `etc` / etcetera, and a `;` between specimens are that same class. An example, an open list, or a negative example means infer the class and do what reasonably fits this case, including what they did not name. Do not copy the example's words, stop at the named items, or add work outside the class. In a plan or a spawn prompt, pass the class. A `;` in code or an ordinary clause is not that cue. Closed instructions stay literal. Detail: `.rulesync/reference/output.md`.
5
6
 
6
7
  A question or a work result opens with the verdict, the counts, or the outcome, failures included. The middle is the concise explanation. The last line lands those same facts as the decision or the one real next step. Do not invent a question or change the numbers. Skip this shape for a one-line answer, a raw artifact, or a reply a skill already shapes.
7
8
 
8
- Size first. Straight shot: do it. Else read `.rulesync/reference/scope.md`.
9
+ Plan first. A straight shot is one file, one obvious edit, and nothing to decide: do it. Anything else: the plan caps the result, so research until every leaf is defensible, ask what only the user can answer, and research again on each answer. A clear task gets one plan; an unclear one gets rounds. Then `/write-plan`, stop for approval, revise on feedback, and `/implement-plan`. Detail: `.rulesync/reference/scope.md`.
9
10
  Default `/auto`: keep going; ask on blockers. `/ask`: one step, then ask.
10
11
  Ask before destructive git (stash, reset, checkout, restore, clean, rebase, push -f); others share the tree. In a loop, skip it and edit code instead.
11
12
 
12
13
  Read `.rulesync/reference/INDEX.md` once. When a row matches the work, read that reference in full.
13
- To learn the repo layout, use the folder map if it is in your context. If it is missing, or you need more depth, run `node scripts/folder-map.mjs [dir]` instead of exploring with `ls` or `find`.
14
+ To learn the repo layout, use the folder map if it is in your context. If it is missing, or you need more depth, run `node .rulesync/scripts/folder-map.mjs [dir]` instead of exploring with `ls` or `find`.
14
15
 
15
16
  Large dumps are compressed (search, test logs, snaps). Scratch and dumps live in `.tmp/grunt/` (gitignored). After a write, recap the path; edit with an offset slice when you need lines.
16
- Fat dumps: `node scripts/grunt-job.mjs --job search|exec|slice|fetch|test` first; spawn grunt only when the dump needs judgment.
17
+ Fat dumps: `node .rulesync/scripts/grunt-job.mjs --job search|exec|slice|fetch|test` first; spawn grunt only when the dump needs judgment.
18
+
19
+ Browse with `node .rulesync/scripts/browser.mjs`. Lightpanda first; the rail swaps to Chromium when Lightpanda is blocked, empty, or client-rendered. Do not stop and say you cannot. App e2e is `playwright test`.
17
20
 
18
- Browse with `node scripts/browser.mjs`. Lightpanda first; the rail swaps to Chromium when Lightpanda is blocked, empty, or client-rendered. Do not stop and say you cannot. App e2e is `playwright test`.
19
21
  <!-- grunt:end -->
package/CHANGELOG.md CHANGED
@@ -7,6 +7,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.11.0] - 2026-10-12
11
+
12
+ ### Changed
13
+
14
+ - Breaking: grunt's product scripts moved from root `scripts/` to `.rulesync/scripts/`. Hooks, `grunt:*` package scripts, the pipeline, skills, and references use the new path. `grunt upgrade` moves them: it deletes grunt-owned names from a root `scripts/` that held a grunt install, removes that dir if it ends up empty, and rewrites `scripts/<grunt name>` refs in package scripts and `.rulesync/grunt.pipeline.jsonc`. Consumer scripts and `scripts/lib/env.mjs` stay where they are. No forwarding stubs
15
+ - Plan by default: a straight shot is one file, one obvious edit, and nothing to decide. Anything else gets research proportional to the work, plan-shaping questions the user alone can answer (with more research on each answer), then `/write-plan`, a stop for approval, revisions on feedback, and `/implement-plan`. A clear task gets one plan; an unclear one gets rounds
16
+ - The folder map lists `.rulesync/scripts`, and `grunt-job --job search` searches dot directories (`.gitignore` still applies)
17
+
18
+ ### Fixed
19
+
20
+ - `grunt:begin` / `grunt:end` blocks use blank lines around the body, the form prettier and other markdown formatters produce, and grunt reads either form. Formatters and `grunt:rulesync:generate` no longer rewrite each other's output, and `grunt:rulesync:check` stops reporting drift after a format
21
+
22
+ ## [0.10.1] - 2026-10-11
23
+
24
+ ### Changed
25
+
26
+ - Session voice: an example, an open list, a negative example, `...`, `etc` / etcetera, or a semicolon between specimens is a class to carry out or avoid. Questions and work results open with the verdict and close on those same facts. Detail in `.rulesync/reference/output.md`
27
+
28
+ ## [0.10.0] - 2026-10-09
29
+
10
30
  ### Added
11
31
 
12
32
  - `scripts/jev.mjs`: one TypeSafe System One call for an external research decision, with a per-process and UTC-day cap. Key via `node scripts/setup.mjs jev` (`~/.grunt/jev.json`) or `TYPESAFE_API_KEY`. Doctor reports present or absent. `--available` prints `yes` or `no` and exits 0
@@ -14,9 +34,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
14
34
  - Live agent board at `~/.grunt/board/`: one line per running agent (time, provider, model, effort, session, directory, work). A read drops a dead process. SessionStart `additionalContext` stays the folder map. UserPromptSubmit can append up to 8 other live lines.
15
35
  - `google-workspace`, `listen`, `speak`, and `clasp` are opt-in. Init and upgrade ask with a multiselect that starts clear. `--packs` selects them without a TTY (`none` selects nothing). A repo with no `.rulesync/grunt.features.jsonc` drops those copies on the next init or upgrade. The npm package still ships them. Browser stays installed. Setup lists enabled packs and always lists browser and jev. Doctor always reports jev, and reports the other four only when that pack is enabled.
16
36
 
17
- ### Changed
18
-
19
- - Session voice: an example, an open list, a negative example, `...`, `etc` / etcetera, or a semicolon between specimens is a class to carry out or avoid. Questions and work results open with the verdict and close on those same facts. Detail in `.rulesync/reference/output.md`
37
+ ## [0.9.5] - 2026-10-05
20
38
 
21
39
  ### Fixed
22
40
 
package/CLAUDE.md CHANGED
@@ -1,3 +1,5 @@
1
1
  <!-- grunt:begin -->
2
+
2
3
  @AGENTS.md
4
+
3
5
  <!-- grunt:end -->
package/README.md CHANGED
@@ -43,7 +43,7 @@ All OS. Print-only. Never auto-install.
43
43
 
44
44
  ```
45
45
  grunt doctor
46
- node scripts/doctor.mjs
46
+ node .rulesync/scripts/doctor.mjs
47
47
  ```
48
48
 
49
49
  Exit 1 if any required missing; 0 if all required ok. Optional tools are reported; missing optional does not fail.
@@ -59,12 +59,12 @@ Exit 1 if any required missing; 0 if all required ok. Optional tools are reporte
59
59
  | chromium-family | yes | linux `sudo apt install chromium` · mac `brew install --cask chromium` or `google-chrome` · win `winget install Google.Chrome` / `Microsoft.Edge` + PATH |
60
60
  | gh | no | report only |
61
61
  | clasp | no | `npm i -g @google/clasp` · custom Apps Script only |
62
- | google-workspace | no | `node scripts/google-workspace.mjs` · Sheets Docs Slides Calendar Gmail · doctor reports `oauth`/`tokens`/`adc`/`clasprc` (no secrets) · `grunt setup` / `node scripts/setup.mjs google-workspace` |
63
- | speak | no | `node scripts/speak.mjs` · TTS output (ElevenLabs or OpenAI) · `grunt setup` / `node scripts/setup.mjs speak` · or `ELEVENLABS_API_KEY` / `OPENAI_API_KEY` / `~/.grunt/speak.json` |
62
+ | google-workspace | no | `node .rulesync/scripts/google-workspace.mjs` · Sheets Docs Slides Calendar Gmail · doctor reports `oauth`/`tokens`/`adc`/`clasprc` (no secrets) · `grunt setup` / `node .rulesync/scripts/setup.mjs google-workspace` |
63
+ | speak | no | `node .rulesync/scripts/speak.mjs` · TTS output (ElevenLabs or OpenAI) · `grunt setup` / `node .rulesync/scripts/setup.mjs speak` · or `ELEVENLABS_API_KEY` / `OPENAI_API_KEY` / `~/.grunt/speak.json` |
64
64
  | ffmpeg | no | mic capture for `/listen` · mac `brew install ffmpeg` · linux `sudo apt install ffmpeg` · win `winget install Gyan.FFmpeg` |
65
65
  | whisper-cli | no | local STT for `/listen` · mac `brew install whisper-cpp` · first listen downloads `ggml-base.en.bin` · setup in `.rulesync/reference/listen.md` |
66
- | listen | no | `node scripts/listen.mjs` · STT input · `grunt setup` / `node scripts/setup.mjs listen` · local whisper.cpp then OpenAI Whisper |
67
- | jev | no | `node scripts/jev.mjs --state FILE --questions FILE` · one closed external research decision · `grunt setup` / `node scripts/setup.mjs jev` · `TYPESAFE_API_KEY` or `~/.grunt/jev.json` · local cap, one request per process |
66
+ | listen | no | `node .rulesync/scripts/listen.mjs` · STT input · `grunt setup` / `node .rulesync/scripts/setup.mjs listen` · local whisper.cpp then OpenAI Whisper |
67
+ | jev | no | `node .rulesync/scripts/jev.mjs --state FILE --questions FILE` · one closed external research decision · `grunt setup` / `node .rulesync/scripts/setup.mjs jev` · `TYPESAFE_API_KEY` or `~/.grunt/jev.json` · local cap, one request per process |
68
68
 
69
69
  Rulesync schema doctor is separate: `grunt:rulesync:doctor` (or SoT `rulesync:doctor`).
70
70
 
@@ -83,10 +83,10 @@ Rulesync schema doctor is separate: `grunt:rulesync:doctor` (or SoT `rulesync:do
83
83
  - `check` → run `grunt:rulesync:check`
84
84
  - `sync-globals` → run `grunt:sync:globals` (dry-run); `--apply` → `grunt:sync:globals:apply`
85
85
  - `purge-mcps` → run `grunt:purge:global-mcps` (dry-run); `--apply` → `grunt:purge:global-mcps:apply`
86
- - `doctor` → run `grunt:doctor` (`grunt doctor` / `node scripts/doctor.mjs` stay). Rulesync schema: `grunt:rulesync:doctor`
87
- - `setup` → run `grunt:setup` (`node scripts/setup.mjs`). The menu lists enabled packs and always lists browser and jev. Flags for non-TTY keys/`--creds`
86
+ - `doctor` → run `grunt:doctor` (`grunt doctor` / `node .rulesync/scripts/doctor.mjs` stay). Rulesync schema: `grunt:rulesync:doctor`
87
+ - `setup` → run `grunt:setup` (`node .rulesync/scripts/setup.mjs`). The menu lists enabled packs and always lists browser and jev. Flags for non-TTY keys/`--creds`
88
88
  - `upgrade` → install the latest grunt as a devDependency and re-run with it, then the same merge as init for an already-inited repo: copy owned trees/scripts, prune retired grunt-owned names, print reserved skill names
89
- - `map [dir]` → code-only folder map (`scripts/folder-map.mjs`); no package manager needed. See [Folder map](#folder-map)
89
+ - `map [dir]` → code-only folder map (`.rulesync/scripts/folder-map.mjs`); no package manager needed. See [Folder map](#folder-map)
90
90
  - `help`
91
91
  - `version`
92
92
 
@@ -136,16 +136,16 @@ SoT: `.rulesync/subagents/{orchestrator grunt}.md`
136
136
 
137
137
  Emit: `.claude/` `.grok/` `.agents/` `.gemini/`
138
138
 
139
- - **orchestrator** — session agent. Tools on. Does the work. en-US unless asked. Concise complete sentences with natural grammar. Skip filler and fluff. Optimal solutions only; rewrite if not. Flag blockers. Do not monkey-patch. Runs `node scripts/grunt-job.mjs` for fat dumps; spawn grunt only when the dump needs judgment.
139
+ - **orchestrator** — session agent. Tools on. Does the work. en-US unless asked. Concise complete sentences with natural grammar. Skip filler and fluff. Optimal solutions only; rewrite if not. Flag blockers. Do not monkey-patch. Runs `node .rulesync/scripts/grunt-job.mjs` for fat dumps; spawn grunt only when the dump needs judgment.
140
140
  - **grunt** — isolation worker. Facts only (≤8 lines, stash receipt when needed).
141
141
 
142
- Fat dumps rewrite to `scripts/grunt-job.mjs` (squeez + stash). Scratch goes in `.tmp/grunt/`. Voice: `.rulesync/reference/output.md`. Examples are a class. Questions and work results open with the verdict and close on those same facts. `GEMINI.md` → `@AGENTS.md`.
142
+ Fat dumps rewrite to `.rulesync/scripts/grunt-job.mjs` (squeez + stash). Scratch goes in `.tmp/grunt/`. Voice: `.rulesync/reference/output.md`. Examples are a class. Questions and work results open with the verdict and close on those same facts. `GEMINI.md` → `@AGENTS.md`.
143
143
 
144
144
  ## Browser
145
145
 
146
- Lightpanda-first session CLI: `node scripts/browser.mjs`. Zero MCP. Zero env knobs. The rail swaps to Chromium for `shot`/`pdf`/`trace`, Windows, missing Lightpanda, probe-fail, Chromium-first hosts (figma, Google docs/sheets/slides, Gmail, earth, Amazon), or a blocked/empty/client-rendered snap. Session: `.tmp/grunt/browser/`. Spec: [`.rulesync/reference/browser.md`](.rulesync/reference/browser.md). App e2e uses Playwright. URL-in-a-cite is a cite.
146
+ Lightpanda-first session CLI: `node .rulesync/scripts/browser.mjs`. Zero MCP. Zero env knobs. The rail swaps to Chromium for `shot`/`pdf`/`trace`, Windows, missing Lightpanda, probe-fail, Chromium-first hosts (figma, Google docs/sheets/slides, Gmail, earth, Amazon), or a blocked/empty/client-rendered snap. Session: `.tmp/grunt/browser/`. Spec: [`.rulesync/reference/browser.md`](.rulesync/reference/browser.md). App e2e uses Playwright. URL-in-a-cite is a cite.
147
147
 
148
- `node scripts/browser.mjs doctor` (alias `ensure`) runs the unified doctor (`scripts/doctor.mjs`). Install engines: [Prerequisites](#prerequisites).
148
+ `node .rulesync/scripts/browser.mjs doctor` (alias `ensure`) runs the unified doctor (`.rulesync/scripts/doctor.mjs`). Install engines: [Prerequisites](#prerequisites).
149
149
 
150
150
  ## Skills
151
151
 
@@ -159,7 +159,7 @@ Present under `.claude` / `.rulesync` / `.agents` / `.grok` (`rulesync -f skills
159
159
 
160
160
  Reserved names: do not reuse those stems for consumer custom skills. Same name → one SSOT under `.rulesync/skills/<name>/`; re-init force-refresh overwrites grunt-owned names; extras kept; maps `origin` badge ≠ content picker. Retired 0.5 names (`parent` `solo` `cascade` plus subagents `implementer` `thinker`) are pruned on init/upgrade. See `.rulesync/reference/law.md` (flows into INDEX).
161
161
 
162
- Size first: `.rulesync/reference/scope.md`. Straight shot → do it. Else that file.
162
+ Plan first: `.rulesync/reference/scope.md`. Straight shot (one file, one obvious edit, nothing to decide) → do it. Else research → `/write-plan` → approval → `/implement-plan`.
163
163
 
164
164
  `/write-plan` and `/implement-plan` SSOT: `.rulesync/skills/{write-plan,implement-plan}/`; format SSOT `.rulesync/reference/plan-format.md`. `/write-plan` plan-only inspect-pause → `next: /implement-plan {n}`; empty `/implement-plan` resumes unique in-progress or starts unique ready, else lists (need serial); slash `/implement-plan {n}` disk/file. The session agent executes the plan. `/implement-plan` also writes `.tmp/grunt/implementations/` (format `.rulesync/reference/implementation-format.md`): Files / Log / Done for commits. Straight shots skip the journal.
165
165
 
@@ -187,7 +187,7 @@ Emit writes other-CLI trees from `.rulesync` for the **next** process of that CL
187
187
 
188
188
  ### Folder map
189
189
 
190
- Claude and Codex get a folder map at SessionStart (`scripts/session-map.mjs`), so agents skip `ls`/`find` loops. Agents run `node scripts/folder-map.mjs <dir>` (same as `grunt map <dir>`) for depth. Grok and Antigravity get the `AGENTS.md` line only. The injected map stays under Claude Code's 10,000-character per-hook `additionalContext` limit (about 2.4k tokens); past it, Claude keeps only a 2KB preview inline. `grunt map` itself uses the full `budget`. SessionStart `additionalContext` stays that map.
190
+ Claude and Codex get a folder map at SessionStart (`.rulesync/scripts/session-map.mjs`), so agents skip `ls`/`find` loops. Agents run `node .rulesync/scripts/folder-map.mjs <dir>` (same as `grunt map <dir>`) for depth. Grok and Antigravity get the `AGENTS.md` line only. The injected map stays under Claude Code's 10,000-character per-hook `additionalContext` limit (about 2.4k tokens); past it, Claude keeps only a 2KB preview inline. `grunt map` itself uses the full `budget`. SessionStart `additionalContext` stays that map.
191
191
 
192
192
  - Folders only, never files. Source: `git ls-files --cached --others --exclude-standard`, so gitignored paths never show
193
193
  - A folder shows when it or a descendant holds a code file (`codeExt`) and no path segment is a dot dir or in `skip`
@@ -212,13 +212,13 @@ Optional `.rulesync/grunt.map.jsonc` (missing or invalid → defaults):
212
212
 
213
213
  One live line per running agent, stored in `~/.grunt/board/` (one file per session, this machine). The line is time, provider, model, effort, session id, directory, and current work. A missing model or effort is `-`. A read deletes a line whose process is gone, so quit, crash, and OOM drop off at the next read.
214
214
 
215
- Claude and Codex write the row at SessionStart. `additionalContext` stays the folder map. Every UserPromptSubmit refreshes the row and may append up to 8 other live lines. Grok writes on the prompt. Antigravity runs `node scripts/board.mjs set --work` when the work changes and `node scripts/board.mjs` to read.
215
+ Claude and Codex write the row at SessionStart. `additionalContext` stays the folder map. Every UserPromptSubmit refreshes the row and may append up to 8 other live lines. Grok writes on the prompt. Antigravity runs `node .rulesync/scripts/board.mjs set --work` when the work changes and `node .rulesync/scripts/board.mjs` to read.
216
216
 
217
217
  ### Secrets (machine)
218
218
 
219
219
  Not git. Not `sync-globals`.
220
220
 
221
- First-hand: `grunt setup` (TTY). The menu lists enabled packs and always lists browser and jev. Per target: `node scripts/setup.mjs speak|listen|google-workspace|browser`. Spec: `.rulesync/reference/setup.md`.
221
+ First-hand: `grunt setup` (TTY). The menu lists enabled packs and always lists browser and jev. Per target: `node .rulesync/scripts/setup.mjs speak|listen|google-workspace|browser`. Spec: `.rulesync/reference/setup.md`.
222
222
 
223
223
  | | where |
224
224
  | --- | --- |
@@ -313,14 +313,14 @@ Same topology as the session recap; every legal edge labeled.
313
313
 
314
314
  ### `need:` / grunt-job
315
315
 
316
- The session agent runs `node scripts/grunt-job.mjs --job search|exec|slice|fetch|test` for fat dumps. A grunt child that still needs a fat dump **stops** on a `need:` JSON line. SubagentStop intercepts `search|exec|slice|fetch` (cap 4) in-hook. Spawn a grunt **model** only when the dump needs judgment. Grunt does not emit `need:` for its own tool use.
316
+ The session agent runs `node .rulesync/scripts/grunt-job.mjs --job search|exec|slice|fetch|test` for fat dumps. A grunt child that still needs a fat dump **stops** on a `need:` JSON line. SubagentStop intercepts `search|exec|slice|fetch` (cap 4) in-hook. Spawn a grunt **model** only when the dump needs judgment. Grunt does not emit `need:` for its own tool use.
317
317
 
318
318
  ```
319
319
  session agent
320
320
  │
321
321
  │ grunt-job first
322
322
  ▼
323
- node scripts/grunt-job.mjs --job search|exec|slice|fetch|test
323
+ node .rulesync/scripts/grunt-job.mjs --job search|exec|slice|fetch|test
324
324
  │
325
325
  │ spawn grunt model only if the dump needs judgment
326
326
  ▼
@@ -343,7 +343,7 @@ The session agent runs `node scripts/grunt-job.mjs --job search|exec|slice|fetch
343
343
  | `scrub-spawn-prompt` / `parse-need` / `grunt-job` | yes | **local** | Hooks and scripts. Isolation facts are grunt-job output, not a model hop. |
344
344
  | emit / generate / init | yes (this repo / install) | **local** | Writes other-CLI configs. Not a runtime line to those CLIs. |
345
345
  | WebSearch / web_fetch | tool from host | **remote-not-LLM** | Network search/fetch. Not a second Model API box. Snippet/cite/"what is X"/world fact: grunt `job: web`. Live URL/DOM: grunt browser rail, not this box. |
346
- | `jev` | yes | **remote-not-LLM** | `node scripts/jev.mjs --state FILE --questions FILE`. One System One decision about external source text. Not the host Model API. Not grunt-job. |
346
+ | `jev` | yes | **remote-not-LLM** | `node .rulesync/scripts/jev.mjs --state FILE --questions FILE`. One System One decision about external source text. Not the host Model API. Not grunt-job. |
347
347
  | MCP | policy deny | n/a | Denied by policy in-tree. Do not draw as a main path. |
348
348
  | Model API | **no** (one box outside) | AI-server | This host’s vendor SDK only. Parent and child **turns** complete here. Spawn omits model; frontmatter on `.rulesync/subagents/*.md` picks the model. |
349
349
  | Other CLIs | no (not this process) | emit/config only | Same protocol files emitted elsewhere. No hop, no shared peek. |
@@ -396,9 +396,9 @@ Repo-relative (repository root):
396
396
 
397
397
  ## Layout
398
398
 
399
- Published (`package.json` `files`): `bin/grunt.js` `cli` `scripts/check-globals.mjs` `scripts/emit-agent-shell-tools.mjs` `scripts/emit-gemini.mjs` `scripts/emit-maps.mjs` `scripts/guarded-md.mjs` `scripts/guarded-roots.mjs` `scripts/emit-mcp-policy.mjs` `scripts/gate-fat-tools.mjs` `scripts/hooks-union.mjs` `scripts/pipeline.mjs` `scripts/grunt-job.mjs` `scripts/parse-need.mjs` `scripts/persist-handoff.mjs` `scripts/persist-implementation.mjs` `scripts/persist-tmp.mjs` `scripts/persist-plan.mjs` `scripts/purge-global-mcps.mjs` `scripts/scrub-spawn-prompt.mjs` `scripts/scrub-text-lib.mjs` `scripts/sync-global-settings.mjs` `scripts/board.mjs` `scripts/browser.mjs` `scripts/feature-packs.mjs` `scripts/speak.mjs` `scripts/listen.mjs` `scripts/jev.mjs` `scripts/google-workspace.mjs` `scripts/interactive.mjs` `scripts/prompt.mjs` `scripts/setup.mjs` `scripts/doctor.mjs` `scripts/skill-conflicts.mjs` `scripts/scrub-text` `.rulesync` `.grok` `.codex` `.claude` `.agents` `AGENTS.md` `CLAUDE.md` `.mcp.json` `README.md` `LICENSE` `CHANGELOG.md`
399
+ Published (`package.json` `files`): `bin/grunt.js` `cli` `.rulesync/scripts/check-globals.mjs` `.rulesync/scripts/emit-agent-shell-tools.mjs` `.rulesync/scripts/emit-gemini.mjs` `.rulesync/scripts/emit-maps.mjs` `.rulesync/scripts/guarded-md.mjs` `.rulesync/scripts/guarded-roots.mjs` `.rulesync/scripts/emit-mcp-policy.mjs` `.rulesync/scripts/gate-fat-tools.mjs` `.rulesync/scripts/hooks-union.mjs` `.rulesync/scripts/pipeline.mjs` `.rulesync/scripts/grunt-job.mjs` `.rulesync/scripts/parse-need.mjs` `.rulesync/scripts/persist-handoff.mjs` `.rulesync/scripts/persist-implementation.mjs` `.rulesync/scripts/persist-tmp.mjs` `.rulesync/scripts/persist-plan.mjs` `.rulesync/scripts/purge-global-mcps.mjs` `.rulesync/scripts/scrub-spawn-prompt.mjs` `.rulesync/scripts/scrub-text-lib.mjs` `.rulesync/scripts/sync-global-settings.mjs` `.rulesync/scripts/board.mjs` `.rulesync/scripts/browser.mjs` `.rulesync/scripts/feature-packs.mjs` `.rulesync/scripts/speak.mjs` `.rulesync/scripts/listen.mjs` `.rulesync/scripts/jev.mjs` `.rulesync/scripts/google-workspace.mjs` `.rulesync/scripts/interactive.mjs` `.rulesync/scripts/prompt.mjs` `.rulesync/scripts/setup.mjs` `.rulesync/scripts/doctor.mjs` `.rulesync/scripts/skill-conflicts.mjs` `.rulesync/scripts/scrub-text` `.rulesync` `.grok` `.codex` `.claude` `.agents` `AGENTS.md` `CLAUDE.md` `.mcp.json` `README.md` `LICENSE` `CHANGELOG.md`
400
400
 
401
- No `scripts/*.test.ts` `scripts/fixtures/` `docs/` `coverage/` `vitest.config.ts` in `files`. `cli` dir ships whole (includes `cli/*.test.ts`).
401
+ No `.rulesync/scripts/*.test.ts` `.rulesync/scripts/fixtures/` `docs/` `coverage/` `vitest.config.ts` in `files`. `cli` dir ships whole (includes `cli/*.test.ts`).
402
402
 
403
403
  Not packed: `GEMINI.md` `.gemini/` — `emit-gemini.mjs` writes them on `generate` / `init` (`GEMINI.md` → `@AGENTS.md`). Not in `files`.
404
404
 
@@ -406,7 +406,7 @@ Repo root (also): `coverage/` `.gemini/` `GEMINI.md` — no `src/` no `CONTRIBUT
406
406
 
407
407
  - `bin/grunt.js` — CLI bin
408
408
  - `cli/grunt.mjs` — commands
409
- - `scripts/` — init copies product scripts (`grunt-job.mjs` `scrub-text` …). Re-init sentinels: `.grok/hooks/orchestrate-parent.js` `.rulesync` `<!-- grunt:begin -->`
409
+ - `.rulesync/scripts/` — init copies product scripts (`grunt-job.mjs` `scrub-text` …). Re-init sentinels: `.grok/hooks/orchestrate-parent.js` `.rulesync` `<!-- grunt:begin -->`
410
410
  - `.rulesync/` — SoT (subagents skills reference). Skills + reference maps on generate; host trees are mirrors
411
411
  - `.claude/` `.grok/` `.codex/` `.agents/` `.gemini/` — host emit; not SoT; no mirror scan
412
412
  - `coverage/` — vitest local not published
@@ -420,7 +420,7 @@ npm i && npm test
420
420
  ```
421
421
 
422
422
  - Test: `vitest run --coverage`
423
- - Test include: `scripts/**/*.test.ts` `cli/**/*.test.ts`
423
+ - Test include: `.rulesync/scripts/**/*.test.ts` `cli/**/*.test.ts`
424
424
  - Coverage include: `cli/**`
425
425
  - Coverage thresholds: 100% lines / functions / branches / statements
426
426
  - Dev deps include rulesync vitest typescript
package/cli/grunt.mjs CHANGED
@@ -12,9 +12,9 @@ import {
12
12
  detectPackageManager,
13
13
  isPackageManager,
14
14
  runScriptArgs,
15
- } from "../scripts/package-manager.mjs"
16
- import { mapCommand } from "../scripts/folder-map.mjs"
17
- import { PACK_OPTIONS, parsePacksFlag, selectedPacks } from "../scripts/feature-packs.mjs"
15
+ } from "../.rulesync/scripts/package-manager.mjs"
16
+ import { mapCommand } from "../.rulesync/scripts/folder-map.mjs"
17
+ import { PACK_OPTIONS, parsePacksFlag, selectedPacks } from "../.rulesync/scripts/feature-packs.mjs"
18
18
 
19
19
  const PKG_ROOT = path.join(path.dirname(fileURLToPath(import.meta.url)), "..")
20
20
 
package/cli/grunt.test.ts CHANGED
@@ -34,9 +34,9 @@ vi.mock("./init.mjs", () => ({
34
34
  }));
35
35
  vi.mock("./spawn.mjs", () => ({ execFileSync }));
36
36
  vi.mock("./self-update.mjs", () => ({ selfUpdate }));
37
- vi.mock("../scripts/folder-map.mjs", () => ({ mapCommand }));
38
- vi.mock("../scripts/package-manager.mjs", async (importOriginal) => {
39
- const actual = await importOriginal<typeof import("../scripts/package-manager.mjs")>();
37
+ vi.mock("../.rulesync/scripts/folder-map.mjs", () => ({ mapCommand }));
38
+ vi.mock("../.rulesync/scripts/package-manager.mjs", async (importOriginal) => {
39
+ const actual = await importOriginal<typeof import("../.rulesync/scripts/package-manager.mjs")>();
40
40
  actualDetect.fn = actual.detectPackageManager;
41
41
  detectPackageManager.mockImplementation((...args: unknown[]) =>
42
42
  actual.detectPackageManager(
@@ -53,15 +53,15 @@ vi.mock("./prompt.mjs", () => ({
53
53
  spinner,
54
54
  bailIfCancel,
55
55
  }));
56
- vi.mock("../scripts/feature-packs.mjs", async (importOriginal) => {
57
- const actual = await importOriginal<typeof import("../scripts/feature-packs.mjs")>();
56
+ vi.mock("../.rulesync/scripts/feature-packs.mjs", async (importOriginal) => {
57
+ const actual = await importOriginal<typeof import("../.rulesync/scripts/feature-packs.mjs")>();
58
58
  return { ...actual, selectedPacks };
59
59
  });
60
60
 
61
61
  import {
62
62
  PACKAGE_MANAGER_ASK,
63
63
  UNKNOWN_PACKAGE_MANAGER,
64
- } from "../scripts/package-manager.mjs";
64
+ } from "../.rulesync/scripts/package-manager.mjs";
65
65
  import { APPLY_GLOBALS_CONFIRM, parseArgv, start } from "./grunt.mjs";
66
66
 
67
67
  const USAGE = `Usage: grunt [command]
package/cli/init.mjs CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  findSkillContentConflicts,
8
8
  formatSkillConflictWarn,
9
9
  listSkillDirNames,
10
- } from "../scripts/skill-conflicts.mjs"
10
+ } from "../.rulesync/scripts/skill-conflicts.mjs"
11
11
  import {
12
12
  SENTINEL_BEGIN,
13
13
  extractGruntBody,
@@ -16,12 +16,12 @@ import {
16
16
  remergeGuardedRoots,
17
17
  withGuardedCheckInteriors,
18
18
  mergeReferenceMarkdown,
19
- } from "../scripts/guarded-md.mjs"
19
+ } from "../.rulesync/scripts/guarded-md.mjs"
20
20
  import {
21
21
  COMMANDS as PIPELINE_COMMANDS,
22
22
  PIPELINE_CONFIG_REL,
23
23
  inferPipelineSkip,
24
- } from "../scripts/pipeline.mjs"
24
+ } from "../.rulesync/scripts/pipeline.mjs"
25
25
  import {
26
26
  PACKAGE_MANAGERS,
27
27
  UNKNOWN_PACKAGE_MANAGER,
@@ -29,8 +29,8 @@ import {
29
29
  installArgs,
30
30
  isPackageManager,
31
31
  runScriptArgs,
32
- } from "../scripts/package-manager.mjs"
33
- import { LAW_REL, emitMaps } from "../scripts/emit-maps.mjs"
32
+ } from "../.rulesync/scripts/package-manager.mjs"
33
+ import { LAW_REL, emitMaps } from "../.rulesync/scripts/emit-maps.mjs"
34
34
  import {
35
35
  isUnselectedPackPath,
36
36
  resolvePackSelection,
@@ -38,7 +38,7 @@ import {
38
38
  unselectedRefNames,
39
39
  unselectedScriptNames,
40
40
  writePacks,
41
- } from "../scripts/feature-packs.mjs"
41
+ } from "../.rulesync/scripts/feature-packs.mjs"
42
42
 
43
43
  export {
44
44
  GUARDED_ROOT_FILES,
@@ -57,7 +57,7 @@ export {
57
57
  withGuardedCheckInteriors,
58
58
  extraH2Sections,
59
59
  mergeReferenceMarkdown,
60
- } from "../scripts/guarded-md.mjs"
60
+ } from "../.rulesync/scripts/guarded-md.mjs"
61
61
 
62
62
  const PKG_ROOT = path.join(path.dirname(fileURLToPath(import.meta.url)), "..")
63
63
 
@@ -102,12 +102,18 @@ export const PRODUCT_SCRIPTS = [
102
102
  ]
103
103
 
104
104
  const COPY_DIRS = [".rulesync", ".grok", ".codex", ".claude", ".agents"]
105
+ /** Product scripts home in the package and in consumers. */
106
+ export const SCRIPTS_REL = ".rulesync/scripts"
107
+ /** Pre-0.11 home. Grunt-owned names here move to SCRIPTS_REL on init/upgrade; consumer files stay. */
108
+ export const LEGACY_SCRIPTS_REL = "scripts"
109
+ /** Any of these in LEGACY_SCRIPTS_REL means grunt installed there before, so its product names are grunt's. */
110
+ const LEGACY_SCRIPTS_MARKERS = ["guarded-roots.mjs", "gate-fat-tools.mjs"]
105
111
  /** Generated on dest from the dest tree (grunt refs + consumer extras). Never stamp the package copies. */
106
112
  export const GENERATED_MAP_FILES = new Set(["INDEX.md", "skills-map.md", "refs-map.md"])
107
113
  const COPY_ROOT_IF_MISSING = [".mcp.json"]
108
114
  export const RETIRED_SKILLS = ["parent", "solo", "cascade"]
109
115
  export const RETIRED_AGENTS = ["implementer", "thinker"]
110
- /** Cumulative. Dest `scripts/<name>` deleted on init/upgrade even if no longer shipped. */
116
+ /** Cumulative. Dest `<SCRIPTS_REL|LEGACY_SCRIPTS_REL>/<name>` deleted on init/upgrade even if no longer shipped. */
111
117
  export const RETIRED_SCRIPTS = ["telemetry.mjs", "grunt-config.mjs"]
112
118
  /** Repo-relative paths grunt used to ship. Deleted on init/upgrade. Consumer extras elsewhere kept. */
113
119
  export const RETIRED_PATHS = [
@@ -238,13 +244,40 @@ export function pruneRetired(dest, { pkgRoot } = {}) {
238
244
  rmQuiet(path.join(root, ".grok", "roles", `${name}.toml`))
239
245
  }
240
246
  for (const name of RETIRED_SCRIPTS) {
241
- rmQuiet(path.join(root, "scripts", name))
247
+ rmQuiet(path.join(root, ...SCRIPTS_REL.split("/"), name))
248
+ rmQuiet(path.join(root, LEGACY_SCRIPTS_REL, name))
242
249
  }
243
250
  for (const rel of RETIRED_PATHS) {
244
251
  rmQuiet(path.join(root, ...rel.split("/")))
245
252
  }
246
253
  }
247
254
 
255
+ /** Drop grunt-owned names from the pre-0.11 root `scripts/`; remove the dir only when that leaves it empty. */
256
+ export function pruneLegacyScripts(dest) {
257
+ const dir = path.join(path.resolve(dest), LEGACY_SCRIPTS_REL)
258
+ if (!LEGACY_SCRIPTS_MARKERS.some((name) => fs.existsSync(path.join(dir, name)))) return
259
+ for (const name of PRODUCT_SCRIPTS) rmQuiet(path.join(dir, name))
260
+ if (fs.readdirSync(dir).length === 0) fs.rmdirSync(dir)
261
+ }
262
+
263
+ const LEGACY_SCRIPT_REF_RE = new RegExp(
264
+ `(^|[\\s"'\`=(]|\\./|\\}/)${LEGACY_SCRIPTS_REL}/(${PRODUCT_SCRIPTS.map(escapeRe).join("|")})(?![\\w.-])`,
265
+ "g",
266
+ )
267
+
268
+ /** Rewrite pre-0.11 `scripts/<grunt name>` command refs to SCRIPTS_REL. Consumer script refs stay. */
269
+ export function rewriteLegacyScriptRefs(text) {
270
+ return String(text).replace(LEGACY_SCRIPT_REF_RE, `$1${SCRIPTS_REL}/$2`)
271
+ }
272
+
273
+ function rewriteLegacyPipelineConfig(dest) {
274
+ const abs = path.join(dest, PIPELINE_CONFIG_REL)
275
+ if (!fs.existsSync(abs)) return
276
+ const text = fs.readFileSync(abs, "utf8")
277
+ const next = rewriteLegacyScriptRefs(text)
278
+ if (next !== text) fs.writeFileSync(abs, next)
279
+ }
280
+
248
281
  export function mergeGitignore(dest) {
249
282
  const gi = path.join(dest, ".gitignore")
250
283
  const text = fs.existsSync(gi) ? fs.readFileSync(gi, "utf8") : ""
@@ -478,7 +511,12 @@ export function mergePackageJson(dest, pkgRoot) {
478
511
  if (destPkg.name === "@lovrozagar/grunt") return
479
512
 
480
513
  const srcPkg = JSON.parse(fs.readFileSync(path.join(pkgRoot, "package.json"), "utf8"))
481
- destPkg.scripts = { ...(destPkg.scripts || {}) }
514
+ destPkg.scripts = Object.fromEntries(
515
+ Object.entries(destPkg.scripts || {}).map(([k, v]) => [
516
+ k,
517
+ typeof v === "string" ? rewriteLegacyScriptRefs(v) : v,
518
+ ]),
519
+ )
482
520
  const srcKeys = []
483
521
  for (const [k, v] of Object.entries(srcPkg.scripts || {})) {
484
522
  if (k === "test" || k.endsWith(":raw") || k in LAUNCH_SCRIPTS) continue
@@ -603,8 +641,10 @@ export function init(dest, { pkgRoot: pkgRootOpt, execFileSync: exec = execFileS
603
641
  const refSnap = self ? {} : snapshotReferenceDocs(dest)
604
642
  let destPipelineSrc = ""
605
643
  if (!self) {
606
- const destPipeline = path.join(dest, "scripts", "pipeline.mjs")
607
- if (fs.existsSync(destPipeline)) destPipelineSrc = fs.readFileSync(destPipeline, "utf8")
644
+ const destPipeline = [SCRIPTS_REL, LEGACY_SCRIPTS_REL]
645
+ .map((rel) => path.join(dest, ...rel.split("/"), "pipeline.mjs"))
646
+ .find((p) => fs.existsSync(p))
647
+ if (destPipeline) destPipelineSrc = rewriteLegacyScriptRefs(fs.readFileSync(destPipeline, "utf8"))
608
648
  }
609
649
 
610
650
  for (const dir of COPY_DIRS) {
@@ -616,7 +656,12 @@ export function init(dest, { pkgRoot: pkgRootOpt, execFileSync: exec = execFileS
616
656
  copyTree(src, d, (s) => path.basename(s) !== "settings.json" && allowPack(s))
617
657
  mergeClaudeSettings(dest, pkgRoot)
618
658
  } else if (dir === ".rulesync") {
619
- copyTree(src, d, (s) => !GENERATED_MAP_FILES.has(path.basename(s)) && allowPack(s))
659
+ const scriptsSrc = path.join(pkgRoot, ...SCRIPTS_REL.split("/"))
660
+ copyTree(
661
+ src,
662
+ d,
663
+ (s) => s !== scriptsSrc && !GENERATED_MAP_FILES.has(path.basename(s)) && allowPack(s),
664
+ )
620
665
  } else {
621
666
  copyTree(src, d, allowPack)
622
667
  }
@@ -628,21 +673,24 @@ export function init(dest, { pkgRoot: pkgRootOpt, execFileSync: exec = execFileS
628
673
  mergeGuardedMarkdown(dest, pkgRoot, file)
629
674
  }
630
675
 
631
- fs.mkdirSync(path.join(dest, "scripts"), { recursive: true })
676
+ const scriptsDest = path.join(dest, ...SCRIPTS_REL.split("/"))
677
+ fs.mkdirSync(scriptsDest, { recursive: true })
632
678
  const skipScripts = unselectedScriptNames(selected)
633
679
  for (const name of PRODUCT_SCRIPTS) {
634
680
  if (skipScripts.has(name)) continue
635
- const src = path.join(pkgRoot, "scripts", name)
636
- const d = path.join(dest, "scripts", name)
681
+ const src = path.join(pkgRoot, ...SCRIPTS_REL.split("/"), name)
682
+ const d = path.join(scriptsDest, name)
637
683
  if (samePath(src, d)) continue
638
684
  copyTree(src, d)
639
685
  }
686
+ if (!self) pruneLegacyScripts(dest)
640
687
 
641
688
  fs.mkdirSync(path.join(dest, ".tmp"), { recursive: true })
642
689
  mergeGitignore(dest)
643
690
 
644
691
  if (!self) {
645
692
  remergeReferenceDocs(dest, refSnap, pkgRoot, unselectedRefNames(selected))
693
+ rewriteLegacyPipelineConfig(dest)
646
694
  seedPipelineConfig(dest, destPipelineSrc)
647
695
  mergePackageJson(dest, pkgRoot)
648
696
  copyRootIfMissing(dest, pkgRoot)