@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,34 +4,32 @@ tags: [scope]
4
4
 
5
5
  # Scope
6
6
 
7
- Default: do the work. Escalate only when it is not a straight shot. This file is the size-gate SSOT.
7
+ Plan by default. A straight shot is the exception. The plan caps the result, so its depth scales with the work. This file is the size-gate SSOT.
8
8
 
9
9
  ## Gate
10
10
 
11
- Straight shot: one obvious path, the user named the edit, or a follow-up on work already agreed. Do it. Do not run this file as a ritual.
11
+ Straight shot: one file, one obvious edit, nothing to decide. Do it. Also skip planning for questions, lookups, commits, a follow-up inside an approved plan, and an explicit "just do it".
12
12
 
13
- Not a straight shot: ambiguous goal, two or more valid approaches, cross-cutting, or hard to undo — or the user asked to plan.
13
+ Plan: two or more files, two or more items in the message, two or more valid approaches, cross-cutting or hard-to-undo work, or the user asked to plan. A named goal is not a named edit.
14
14
 
15
- Skip the loop: questions, lookups, one-file named edits, "just do it", commits.
16
-
17
- `/ask` is not this loop. `/ask` pauses after each execution step. This loop pauses until the approach is agreed. `/auto` already treats a real choice as a blocker; "which approach?" is that blocker.
15
+ `/ask` is not this loop. `/ask` pauses after each execution step. This loop pauses on questions and on the plan. `/auto` continues after approval; it does not skip approval.
18
16
 
19
17
  ## Research
20
18
 
21
- Read and search until you can pick an approach. Enough to choose, not a literature review. Fat notes go under `.tmp/grunt/` (`/tmp` if they must persist). Do not add `## Research` to a plan.
19
+ Read and search until every leaf is defensible and needs no re-planning. Depth scales with stakes and ambiguity: a clear task needs enough to write the leaves; an unclear, cross-cutting, or hard-to-undo one needs the alternatives checked against the code, docs, and data. Fat notes go under `.tmp/grunt/`.
22
20
 
23
- ## Suggest
21
+ ## Questions
24
22
 
25
- 5–10 lines: approach, why, rejected alternative, one open question. Not a fake plan. Do not `/write-plan` in the same turn.
23
+ Ask only what research cannot settle and what changes the plan's shape: intent, scope, a trade-off the user owns. Batch them into one round, each with your recommendation. Research again on each answer, and ask again only if a new plan-shaping gap appears. When research settles everything, skip this step and write the plan in one shot.
26
24
 
27
- ## Wait
25
+ ## Plan
28
26
 
29
- Stop. One real question. Feedback → research if needed → suggest again. Repeat until the approach is agreed.
27
+ `/write-plan`. Format: `.rulesync/reference/plan-format.md`. The plan is the proposal: the chosen approach and its rejected alternative sit in Context or Constraints. A minor open choice is written as the default plus "confirm"; a plan-shaping one goes back to Questions. Report the goal, phases, top risks, open assumptions, and path, then stop for approval.
30
28
 
31
- ## Plan
29
+ ## Approval
32
30
 
33
- After agreement only: `/write-plan`. Format: `.rulesync/reference/plan-format.md`. Distill facts into Context, Constraints, Watch-outs. Leaves are the work. Report serial. Stop unless the user said plan and do it.
31
+ Feedback revises the same plan file; research again if needed. Stop again until the user approves. Approval is the user saying go, or an explicit "plan and do it" in the request.
34
32
 
35
33
  ## Implement
36
34
 
37
- `/implement-plan` in a separate turn, or the same turn only if the user said plan and do it. Skip `[x]`. Write-allowlist = paths listed in the plan. Do not skip test leaves. Journal: `.rulesync/reference/implementation-format.md` under `.tmp/grunt/implementations/` (create on start; not for straight shots). Straight-shot behavior change: run the nearest existing test if the repo has one. Bugfix: repro first. Do not scaffold a test file on a straight shot unless it is a bug.
35
+ `/implement-plan` after approval, in the same turn only on an explicit "plan and do it". Skip `[x]`. Write-allowlist = paths listed in the plan. Do not skip test leaves. Journal: `.rulesync/reference/implementation-format.md` under `.tmp/grunt/implementations/` (create on start; not for straight shots). Straight-shot behavior change: run the nearest existing test if the repo has one. Bugfix: repro first. Do not scaffold a test file on a straight shot unless it is a bug.
@@ -8,15 +8,15 @@ Handheld machine setup for optional integrations. Any OS. Secrets stay in `~/.gr
8
8
 
9
9
  ```
10
10
  npm exec grunt setup
11
- node scripts/setup.mjs
12
- node scripts/setup.mjs speak
13
- node scripts/setup.mjs listen
14
- node scripts/setup.mjs google-workspace
15
- node scripts/setup.mjs browser
16
- node scripts/setup.mjs jev
11
+ node .rulesync/scripts/setup.mjs
12
+ node .rulesync/scripts/setup.mjs speak
13
+ node .rulesync/scripts/setup.mjs listen
14
+ node .rulesync/scripts/setup.mjs google-workspace
15
+ node .rulesync/scripts/setup.mjs browser
16
+ node .rulesync/scripts/setup.mjs jev
17
17
  ```
18
18
 
19
- TTY: menu with ok/missing. The menu lists enabled packs and always lists browser and jev. A pack is enabled when `.rulesync/grunt.features.jsonc` names it. `node scripts/setup.mjs <pack>` when that pack is off prints how to enable it (`grunt init` or `grunt upgrade`) and exits 1. Already ok asks redo (`--redo` skips the ask). Missing bins: print the OS command (never run it), then re-check PATH. Ends with one status line per listed target. Non-TTY needs flags (keys / `--creds`). Doctor never installs bins; setup never installs bins either.
19
+ TTY: menu with ok/missing. The menu lists enabled packs and always lists browser and jev. A pack is enabled when `.rulesync/grunt.features.jsonc` names it. `node .rulesync/scripts/setup.mjs <pack>` when that pack is off prints how to enable it (`grunt init` or `grunt upgrade`) and exits 1. Already ok asks redo (`--redo` skips the ask). Missing bins: print the OS command (never run it), then re-check PATH. Ends with one status line per listed target. Non-TTY needs flags (keys / `--creds`). Doctor never installs bins; setup never installs bins either.
20
20
 
21
21
  | target | what it writes | flags |
22
22
  | --- | --- | --- |
@@ -6,7 +6,7 @@
6
6
  | --- | --- | --- | --- | --- | --- |
7
7
  | ask | local | Session flag. Finish one step, recap, then ask before the next. | | | |
8
8
  | auto | local | Session flag. Default. Work the task through. Ask on blockers instead of monkey-patching. | | | |
9
- | browser | local | Browse via node scripts/browser.mjs (Lightpanda). App e2e uses Playwright. | | | |
9
+ | browser | local | Browse via node .rulesync/scripts/browser.mjs (Lightpanda). App e2e uses Playwright. | | | |
10
10
  | clasp | local | Low-level Apps Script CLI. Prefer /google-workspace for sheet, doc, meeting, mail. | | | |
11
11
  | commit | local | Inspect the diff and commit with a Conventional Commits subject line. Use for /commit, "commit this", "commit these cha… | | | |
12
12
  | commit-and-push | local | Alias of commit-push (1-release stub). Use for /commit-and-push, "commit and push", "commit this and push". Must push.… | | | |
@@ -14,13 +14,13 @@
14
14
  | commit-push-deploy | local | Commit, push, then deploy only if allowlisted infra already exists. Use for /commit-push-deploy, "commit push deploy",… | | | |
15
15
  | commit-push-release | local | Commit, push, bump package.json, tag vX.Y.Z, push the tag. CI publishes. Use for /commit-push-release, "commit push rel… | | | |
16
16
  | explain | local | Longer human recap this reply only. | | | |
17
- | google-workspace | local | Google Sheets, Docs, Slides, Calendar, Gmail via node scripts/google-workspace.mjs. | | | |
17
+ | google-workspace | local | Google Sheets, Docs, Slides, Calendar, Gmail via node .rulesync/scripts/google-workspace.mjs. | | | |
18
18
  | handoff | local | Write a session handoff under .tmp/grunt/handoffs/. Continue with /pickup. | | | |
19
19
  | implement-plan | local | Execute remaining leaves in a .tmp/grunt/plans checklist. Skip [x]. | | | |
20
- | jev | local | Yes/no or named-option decision on text already saved. Run node scripts/jev.mjs --available first. On yes, one decision… | | | |
21
- | listen | local | Speech-to-text via node scripts/listen.mjs (ffmpeg mic + local whisper.cpp, OpenAI fallback). Input only. | | | |
20
+ | jev | local | Yes/no or named-option decision on text already saved. Run node .rulesync/scripts/jev.mjs --available first. On yes, on… | | | |
21
+ | listen | local | Speech-to-text via node .rulesync/scripts/listen.mjs (ffmpeg mic + local whisper.cpp, OpenAI fallback). Input only. | | | |
22
22
  | pickup | local | Continue a handoff under .tmp/grunt/handoffs/. | | | |
23
- | speak | local | Text-to-speech via node scripts/speak.mjs (ElevenLabs or OpenAI). Output only. | | | |
23
+ | speak | local | Text-to-speech via node .rulesync/scripts/speak.mjs (ElevenLabs or OpenAI). Output only. | | | |
24
24
  | su | local | Short status update: done, next, and blockers or on-track ETA. | | | |
25
25
  | tmp | local | Dump a one-off convo artifact under .tmp/grunt/. | | | |
26
26
  | write-plan | local | Write a local checklist under .tmp/grunt/plans/. Then /implement-plan {n}. | | | |
@@ -4,21 +4,21 @@ tags: [speak]
4
4
 
5
5
  # Speak
6
6
 
7
- Text-to-speech from the session. Output only. Not STT. Not a mic. Not MCP. Speech-to-text is `/listen`: `.rulesync/reference/listen.md` and `node scripts/listen.mjs`.
7
+ Text-to-speech from the session. Output only. Not STT. Not a mic. Not MCP. Speech-to-text is `/listen`: `.rulesync/reference/listen.md` and `node .rulesync/scripts/listen.mjs`.
8
8
 
9
9
  ```
10
- node scripts/speak.mjs whoami
11
- node scripts/speak.mjs voices [--provider elevenlabs|openai]
12
- node scripts/speak.mjs say --text "hello" [--provider elevenlabs|openai] [--voice ID] [--out path] [--play]
10
+ node .rulesync/scripts/speak.mjs whoami
11
+ node .rulesync/scripts/speak.mjs voices [--provider elevenlabs|openai]
12
+ node .rulesync/scripts/speak.mjs say --text "hello" [--provider elevenlabs|openai] [--voice ID] [--out path] [--play]
13
13
  ```
14
14
 
15
15
  `say` writes an mp3 under `.tmp/grunt/speak/` (or `--out`) and prints the path. `--play` is local `afplay` (mac) or `ffplay`.
16
16
 
17
17
  ## Auth
18
18
 
19
- Handheld (any OS): `node scripts/setup.mjs speak` or `npm exec grunt setup` → speak. Opens the key page, hides the paste, writes `~/.grunt/speak.json`, verifies. Non-TTY: `--elevenlabs-key` / `--openai-key`.
19
+ Handheld (any OS): `node .rulesync/scripts/setup.mjs speak` or `npm exec grunt setup` → speak. Opens the key page, hides the paste, writes `~/.grunt/speak.json`, verifies. Non-TTY: `--elevenlabs-key` / `--openai-key`.
20
20
 
21
- If a key is missing or rejected, the CLI prints `setup: node scripts/setup.mjs speak` and exits 1. Agents should show that line, not a stack or JSON. Do not invent a key.
21
+ If a key is missing or rejected, the CLI prints `setup: node .rulesync/scripts/setup.mjs speak` and exits 1. Agents should show that line, not a stack or JSON. Do not invent a key.
22
22
 
23
23
  One key is enough. Never commit it.
24
24
 
@@ -8,20 +8,20 @@ targets:
8
8
  globs:
9
9
  - "**/*"
10
10
  ---
11
- 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.
11
+ 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.
12
12
 
13
13
  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`.
14
14
 
15
15
  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.
16
16
 
17
- Size first. Straight shot: do it. Else read `.rulesync/reference/scope.md`.
17
+ 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`.
18
18
  Default `/auto`: keep going; ask on blockers. `/ask`: one step, then ask.
19
19
  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.
20
20
 
21
21
  Read `.rulesync/reference/INDEX.md` once. When a row matches the work, read that reference in full.
22
- 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`.
22
+ 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`.
23
23
 
24
24
  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.
25
- Fat dumps: `node scripts/grunt-job.mjs --job search|exec|slice|fetch|test` first; spawn grunt only when the dump needs judgment.
25
+ Fat dumps: `node .rulesync/scripts/grunt-job.mjs --job search|exec|slice|fetch|test` first; spawn grunt only when the dump needs judgment.
26
26
 
27
- 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`.
27
+ 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`.
@@ -469,9 +469,9 @@ export function runDoctor({
469
469
  return { code: missingRequired.length || missingMaps.length ? 1 : 0, stdout, stderr: "" };
470
470
  }
471
471
 
472
- /** Load dest `scripts/lib/env.mjs` `loadEnv` when that file exists next to this script. */
472
+ /** Load the consumer's root `scripts/lib/env.mjs` `loadEnv` when it exists (this script is `.rulesync/scripts/doctor.mjs`). */
473
473
  export async function loadOptionalConsumerEnv(baseHref = import.meta.url) {
474
- const file = fileURLToPath(new URL("./lib/env.mjs", baseHref));
474
+ const file = fileURLToPath(new URL("../../scripts/lib/env.mjs", baseHref));
475
475
  if (!fs.existsSync(file)) return false;
476
476
  const { loadEnv } = await import(pathToFileURL(file).href);
477
477
  if (typeof loadEnv === "function") loadEnv();
@@ -107,7 +107,7 @@ export function unselectedPackRels(selected) {
107
107
  if (on.has(pack.name)) continue;
108
108
  for (const dir of SKILL_DIRS) rels.push(`${dir}/${pack.skill}`);
109
109
  if (pack.ref) rels.push(`.rulesync/reference/${pack.ref}`);
110
- if (pack.script) rels.push(`scripts/${pack.script}`);
110
+ if (pack.script) rels.push(`.rulesync/scripts/${pack.script}`);
111
111
  }
112
112
  return rels;
113
113
  }
@@ -9,7 +9,7 @@
9
9
  * Depth is uniform per package: the deepest level that fits the size limit;
10
10
  * deeper folders end in `…`.
11
11
  *
12
- * node scripts/folder-map.mjs [dir]
12
+ * node .rulesync/scripts/folder-map.mjs [dir]
13
13
  */
14
14
  import { spawnSync } from "node:child_process";
15
15
  import fs from "node:fs";
@@ -32,6 +32,8 @@ export const DEFAULTS = Object.freeze({
32
32
  });
33
33
 
34
34
  export const CONFIG_REL = ".rulesync/grunt.map.jsonc";
35
+ /** Grunt's product scripts run in every repo; map them despite the dot segment. */
36
+ const DOT_CODE_DIRS = [".rulesync/scripts"];
35
37
  const MANIFESTS = ["package.json", "go.mod", "Cargo.toml", "pyproject.toml"];
36
38
 
37
39
  /** DEFAULTS merged with known keys from CONFIG_REL; bad file → defaults + warn. */
@@ -133,7 +135,9 @@ export function buildTree(root, files, opts = {}) {
133
135
  const segs = dir.split("/");
134
136
  if (!forced) {
135
137
  if (!code.test(f)) continue;
136
- if (segs.some((s) => s.startsWith(".") || skip.has(s))) continue;
138
+ const under = DOT_CODE_DIRS.find((d) => dir === d || dir.startsWith(`${d}/`));
139
+ const checked = under ? segs.slice(under.split("/").length) : segs;
140
+ if (checked.some((s) => s.startsWith(".") || skip.has(s))) continue;
137
141
  }
138
142
  let node = top;
139
143
  for (const s of segs) {
@@ -254,7 +258,7 @@ export function folderMap({ root, dir = "", ...opts } = {}) {
254
258
  return { text, rows: lines.length, tokens: tokens(text), depth };
255
259
  }
256
260
 
257
- /** Text for `grunt map [dir]` / `node scripts/folder-map.mjs [dir]` run from `cwd`. */
261
+ /** Text for `grunt map [dir]` / `node .rulesync/scripts/folder-map.mjs [dir]` run from `cwd`. */
258
262
  export function mapCommand(cwd, dir = "") {
259
263
  const out = folderMap({ root: cwd, dir });
260
264
  if (!out) return "grunt map: not a git repository\n";
@@ -348,7 +348,7 @@ export function quoteQuery(q) {
348
348
  }
349
349
 
350
350
  export function gruntJobCommand(workspaceRoot, args) {
351
- const script = path.join(workspaceRoot, "scripts", "grunt-job.mjs");
351
+ const script = path.join(workspaceRoot, ".rulesync", "scripts", "grunt-job.mjs");
352
352
  return `node ${script} ${args}`;
353
353
  }
354
354
 
@@ -498,12 +498,12 @@ function gruntJobRest(command, workspaceRoot) {
498
498
  const abs = path.isAbsolute(scriptArg)
499
499
  ? path.resolve(scriptArg)
500
500
  : path.resolve(workspaceRoot, scriptArg);
501
- const expected = path.resolve(workspaceRoot, "scripts", "grunt-job.mjs");
501
+ const expected = path.resolve(workspaceRoot, ".rulesync", "scripts", "grunt-job.mjs");
502
502
  if (abs !== expected) return null;
503
503
  return parts.slice(i + 1);
504
504
  }
505
505
 
506
- /** True when argv targets workspace scripts/grunt-job.mjs (flags may be messy). */
506
+ /** True when argv targets workspace .rulesync/scripts/grunt-job.mjs (flags may be messy). */
507
507
  export function isWorkspaceGruntJobScript(command, workspaceRoot) {
508
508
  return gruntJobRest(command, workspaceRoot) != null;
509
509
  }
@@ -146,7 +146,7 @@ function persistToken(t, access, expiry) {
146
146
 
147
147
  async function accessToken() {
148
148
  const t = loadToken();
149
- if (!t) throw new Error("not logged in; run node scripts/google-workspace.mjs login");
149
+ if (!t) throw new Error("not logged in; run node .rulesync/scripts/google-workspace.mjs login");
150
150
  if (t.access_token && Number(t.expiry_date) > Date.now() + 60_000) {
151
151
  return t.access_token;
152
152
  }
@@ -288,7 +288,7 @@ async function runLogin(flags) {
288
288
  return ok("logged in via gcloud ADC");
289
289
  }
290
290
  return fail(
291
- `missing Desktop OAuth JSON (${creds}). setup: node scripts/setup.mjs google-workspace`,
291
+ `missing Desktop OAuth JSON (${creds}). setup: node .rulesync/scripts/setup.mjs google-workspace`,
292
292
  );
293
293
  }
294
294
 
@@ -485,8 +485,10 @@ export function runSearch(query, { cwd, path: searchPath, glob } = {}) {
485
485
  }
486
486
  const globs = Array.isArray(glob) ? glob.filter(Boolean) : glob ? [String(glob)] : [];
487
487
  const relStart = path.relative(root, start) || ".";
488
+ // --hidden: grunt code (.rulesync/scripts) and rulesync SSOT live in dot dirs; .gitignore still applies.
488
489
  const rgArgs = [
489
490
  "-n",
491
+ "--hidden",
490
492
  "--max-count",
491
493
  String(MAX_HITS),
492
494
  "--max-filesize",
@@ -26,6 +26,10 @@ function trimTrailingNewlines(text) {
26
26
  return String(text).replace(/(?:\r?\n)+$/, "");
27
27
  }
28
28
 
29
+ function trimLeadingBlankLines(text) {
30
+ return String(text).replace(/^(?:[ \t]*\r?\n)+/, "");
31
+ }
32
+
29
33
  function normEq(a, b) {
30
34
  return (
31
35
  trimTrailingNewlines(String(a).replace(/\r\n/g, "\n")) ===
@@ -55,7 +59,7 @@ function unwrapStackedMarkers(text) {
55
59
  const lines = String(text).split(/\r?\n/);
56
60
  let start = 0;
57
61
  let end = lines.length;
58
- while (start < end && BEGIN_LINE_RE.test(lines[start])) start++;
62
+ while (start < end && (BEGIN_LINE_RE.test(lines[start]) || !lines[start].trim())) start++;
59
63
  while (end > start && lines[end - 1] === "") end--;
60
64
  while (end > start && END_LINE_RE.test(lines[end - 1])) {
61
65
  end--;
@@ -87,13 +91,15 @@ function userRemainderIsBlank(user) {
87
91
  return String(user).replace(/\r\n/g, "\n").replace(/\n/g, "") === "";
88
92
  }
89
93
 
94
+ /** Blank lines around each sentinel: the markdown formatter fixed point (prettier, remark, mdformat). */
90
95
  export function composeGuardedMarkdown(gruntBody, userRemainder, nl = "\n") {
91
96
  const raw = extractGruntBody(gruntBody) ?? gruntBody;
92
- const body = toNewline(trimTrailingNewlines(raw), nl);
93
- const block = `${SENTINEL_BEGIN}${nl}${body}${nl}${SENTINEL_END}${nl}`;
97
+ const body = toNewline(trimTrailingNewlines(trimLeadingBlankLines(raw)), nl);
98
+ const inner = body ? `${nl}${body}${nl}${nl}` : nl;
99
+ const block = `${SENTINEL_BEGIN}${nl}${inner}${SENTINEL_END}${nl}`;
94
100
  const user = extractUserMarkdown(userRemainder == null ? "" : String(userRemainder));
95
101
  if (!user || userRemainderIsBlank(user)) return block;
96
- return block + toNewline(user, nl);
102
+ return `${block}${nl}${toNewline(trimLeadingBlankLines(user), nl)}`;
97
103
  }
98
104
 
99
105
  export function mergeGuardedContent(existingText, gruntBody) {
@@ -13,14 +13,14 @@
13
13
  * validate+sim. Path deny from consumer `.rulesync/permissions.json`.
14
14
  * Gemini: SessionStart + AfterTool only; check-behind / path deny stay caveats.
15
15
  *
16
- * node scripts/hooks-union.mjs apply then check
17
- * node scripts/hooks-union.mjs --check keep-list only
16
+ * node .rulesync/scripts/hooks-union.mjs apply then check
17
+ * node .rulesync/scripts/hooks-union.mjs --check keep-list only
18
18
  */
19
19
  import fs from "node:fs";
20
20
  import path from "node:path";
21
21
  import { fileURLToPath, pathToFileURL } from "node:url";
22
22
 
23
- const ROOT = path.join(path.dirname(fileURLToPath(import.meta.url)), "..");
23
+ const ROOT = path.join(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
24
24
 
25
25
  export const PULL = "node scripts/daily-pull-check.mjs";
26
26
  export const RTK = "node scripts/rtk-check.mjs";
@@ -29,11 +29,11 @@ export const BEHIND = "node scripts/check-behind.mjs --hook";
29
29
  export const VALIDATE = "node scripts/validate.mjs --hook";
30
30
  export const SIM = "node scripts/sim.mjs --hook";
31
31
  export const SCRUB =
32
- 'node "${GROK_WORKSPACE_ROOT:-${CLAUDE_PROJECT_DIR:-.}}/scripts/scrub-spawn-prompt.mjs"';
32
+ 'node "${GROK_WORKSPACE_ROOT:-${CLAUDE_PROJECT_DIR:-.}}/.rulesync/scripts/scrub-spawn-prompt.mjs"';
33
33
  export const GATE_FAT =
34
- 'node "${GROK_WORKSPACE_ROOT:-${CLAUDE_PROJECT_DIR:-.}}/scripts/gate-fat-tools.mjs"';
34
+ 'node "${GROK_WORKSPACE_ROOT:-${CLAUDE_PROJECT_DIR:-.}}/.rulesync/scripts/gate-fat-tools.mjs"';
35
35
  export const MAP =
36
- 'node "${GROK_WORKSPACE_ROOT:-${CLAUDE_PROJECT_DIR:-.}}/scripts/session-map.mjs"';
36
+ 'node "${GROK_WORKSPACE_ROOT:-${CLAUDE_PROJECT_DIR:-.}}/.rulesync/scripts/session-map.mjs"';
37
37
  const MAP_NEEDLE = "session-map.mjs";
38
38
 
39
39
  export const AUTORUN_NEEDLES = [
@@ -83,10 +83,15 @@ function resolveRoot(opts) {
83
83
  return process.cwd();
84
84
  }
85
85
 
86
+ /** Consumer-owned extras live in the consumer's root `scripts/`. */
86
87
  function hasScript(root, name) {
87
88
  return fs.existsSync(path.join(root, "scripts", name));
88
89
  }
89
90
 
91
+ function hasGruntScript(root, name) {
92
+ return fs.existsSync(path.join(root, ".rulesync", "scripts", name));
93
+ }
94
+
90
95
  function withoutWritePath(arr) {
91
96
  return arr.filter((x) => typeof x === "string" && !x.startsWith("Write("));
92
97
  }
@@ -179,13 +184,13 @@ function desiredPre(root) {
179
184
  }
180
185
 
181
186
  function mapNeedles(root) {
182
- return hasScript(root, MAP_NEEDLE) ? [MAP_NEEDLE] : [];
187
+ return hasGruntScript(root, MAP_NEEDLE) ? [MAP_NEEDLE] : [];
183
188
  }
184
189
 
185
190
  /** `map: false` for hosts rulesync emits no sessionStart for (Antigravity). */
186
191
  function desiredSession(root, { map = true } = {}) {
187
192
  const hooks = [];
188
- if (map && hasScript(root, MAP_NEEDLE)) hooks.push(hookCmd(MAP, 5));
193
+ if (map && hasGruntScript(root, MAP_NEEDLE)) hooks.push(hookCmd(MAP, 5));
189
194
  if (hasScript(root, "daily-pull-check.mjs")) hooks.push(hookCmd(PULL));
190
195
  if (hasScript(root, "rtk-check.mjs")) hooks.push(hookCmd(RTK));
191
196
  if (hasScript(root, "daily-fleet-check.mjs")) hooks.push(hookCmd(FLEET));
@@ -15,9 +15,9 @@ export const TIMEOUT_MS = 10_000;
15
15
  export const MAX_RETRY_WAIT_MS = 2_000;
16
16
  export const LOCK_STALE_MS = 10_000;
17
17
 
18
- const USAGE = "usage: node scripts/jev.mjs --available | --state FILE --questions FILE\n";
18
+ const USAGE = "usage: node .rulesync/scripts/jev.mjs --available | --state FILE --questions FILE\n";
19
19
  const MISSING_KEY =
20
- "missing TYPESAFE_API_KEY or ~/.grunt/jev.json. setup: node scripts/setup.mjs jev\n";
20
+ "missing TYPESAFE_API_KEY or ~/.grunt/jev.json. setup: node .rulesync/scripts/setup.mjs jev\n";
21
21
 
22
22
  export function jevJsonPath(home) {
23
23
  return path.join(home, ".grunt", "jev.json");
@@ -14,15 +14,15 @@ export const DEFAULT_SECONDS = 8;
14
14
  export const MAX_SECONDS = 120;
15
15
  export const USAGE =
16
16
  "usage: listen whoami | devices | rec [--seconds N] [--device D] [--clip] | start [--device D] | stop [--clip] | toggle [--clip] | status | file --path P [--clip] | latest";
17
- export const SETUP = "node scripts/setup.mjs listen";
17
+ export const SETUP = "node .rulesync/scripts/setup.mjs listen";
18
18
  export const NEED_STT =
19
- "missing whisper-cli (mac: brew install whisper-cpp) or OPENAI_API_KEY / ~/.grunt/speak.json openai.apiKey. setup: node scripts/setup.mjs listen";
19
+ "missing whisper-cli (mac: brew install whisper-cpp) or OPENAI_API_KEY / ~/.grunt/speak.json openai.apiKey. setup: node .rulesync/scripts/setup.mjs listen";
20
20
  export const NEED_FFMPEG =
21
- "missing ffmpeg. mac: brew install ffmpeg · linux: sudo apt install ffmpeg · win: winget install Gyan.FFmpeg. setup: node scripts/setup.mjs listen";
21
+ "missing ffmpeg. mac: brew install ffmpeg · linux: sudo apt install ffmpeg · win: winget install Gyan.FFmpeg. setup: node .rulesync/scripts/setup.mjs listen";
22
22
  export const NEED_OPENAI =
23
- "missing OPENAI_API_KEY (or ~/.grunt/speak.json openai.apiKey). setup: node scripts/setup.mjs listen";
23
+ "missing OPENAI_API_KEY (or ~/.grunt/speak.json openai.apiKey). setup: node .rulesync/scripts/setup.mjs listen";
24
24
  export const NEED_GGML =
25
- "whisper-cli found but no ggml model (auto-download failed). set WHISPER_MODEL or retry. setup: node scripts/setup.mjs listen";
25
+ "whisper-cli found but no ggml model (auto-download failed). set WHISPER_MODEL or retry. setup: node .rulesync/scripts/setup.mjs listen";
26
26
 
27
27
  export function formatHttpError(status, body, provider = "openai") {
28
28
  const n = Number(status);
@@ -254,7 +254,7 @@ export function ffmpegInputArgs(platform, device, ffmpegFormat = "") {
254
254
  if (platform === "win32") {
255
255
  if (!d) {
256
256
  throw new Error(
257
- "missing --device or LISTEN_DEVICE. run: node scripts/listen.mjs devices. setup: node scripts/setup.mjs listen",
257
+ "missing --device or LISTEN_DEVICE. run: node .rulesync/scripts/listen.mjs devices. setup: node .rulesync/scripts/setup.mjs listen",
258
258
  );
259
259
  }
260
260
  const spec = d.startsWith("audio=") ? d : `audio=${d}`;
@@ -12,27 +12,27 @@ export const COMMANDS = {
12
12
  generate: [
13
13
  "rulesync generate -t claudecode,codexcli,antigravity-cli,grokcli -f rules,subagents,skills",
14
14
  "rulesync generate -t claudecode,codexcli,antigravity-cli -f hooks",
15
- "node scripts/emit-mcp-policy.mjs",
16
- "node scripts/emit-gemini.mjs",
17
- "node scripts/emit-agent-shell-tools.mjs",
18
- "node scripts/emit-maps.mjs",
19
- "node scripts/hooks-union.mjs",
15
+ "node .rulesync/scripts/emit-mcp-policy.mjs",
16
+ "node .rulesync/scripts/emit-gemini.mjs",
17
+ "node .rulesync/scripts/emit-agent-shell-tools.mjs",
18
+ "node .rulesync/scripts/emit-maps.mjs",
19
+ "node .rulesync/scripts/hooks-union.mjs",
20
20
  ],
21
21
  check: [
22
22
  "rulesync generate -t claudecode,codexcli,antigravity-cli,grokcli -f rules,skills --check",
23
23
  "rulesync generate -t codexcli,antigravity-cli,grokcli -f subagents --check",
24
24
  "rulesync generate -t claudecode,codexcli,antigravity-cli -f hooks --check",
25
- "node scripts/emit-mcp-policy.mjs --check",
26
- "node scripts/emit-gemini.mjs --check",
27
- "node scripts/emit-agent-shell-tools.mjs --check",
28
- "node scripts/emit-maps.mjs --check",
29
- "node scripts/check-globals.mjs",
30
- "node scripts/hooks-union.mjs --check",
25
+ "node .rulesync/scripts/emit-mcp-policy.mjs --check",
26
+ "node .rulesync/scripts/emit-gemini.mjs --check",
27
+ "node .rulesync/scripts/emit-agent-shell-tools.mjs --check",
28
+ "node .rulesync/scripts/emit-maps.mjs --check",
29
+ "node .rulesync/scripts/check-globals.mjs",
30
+ "node .rulesync/scripts/hooks-union.mjs --check",
31
31
  ],
32
32
  watch: [
33
- "node scripts/emit-mcp-policy.mjs",
34
- "node scripts/emit-gemini.mjs",
35
- "node scripts/emit-agent-shell-tools.mjs",
33
+ "node .rulesync/scripts/emit-mcp-policy.mjs",
34
+ "node .rulesync/scripts/emit-gemini.mjs",
35
+ "node .rulesync/scripts/emit-agent-shell-tools.mjs",
36
36
  "rulesync generate -t claudecode,codexcli,antigravity-cli,grokcli -f rules,subagents,skills --watch",
37
37
  ],
38
38
  };
@@ -13,7 +13,7 @@ import { folderMap } from "./folder-map.mjs";
13
13
  /** First line of the injected map; `depth` = levels per package, null when nothing was cut. */
14
14
  export function header(depth) {
15
15
  const scope = depth == null ? "complete" : `${depth} levels per package`;
16
- return `Folder map (code folders, ${scope}; \`…\` = deeper, run \`node scripts/folder-map.mjs <dir>\`):`;
16
+ return `Folder map (code folders, ${scope}; \`…\` = deeper, run \`node .rulesync/scripts/folder-map.mjs <dir>\`):`;
17
17
  }
18
18
  /** Claude Code caps each hook's additionalContext at 10,000 chars; above it, only a 2KB preview stays inline. */
19
19
  export const HOOK_CONTEXT_CHARS = 10000;
@@ -427,7 +427,7 @@ export async function setupSpeak({
427
427
  throw new Error(
428
428
  interactive
429
429
  ? "need an ElevenLabs or OpenAI key"
430
- : "need --elevenlabs-key or --openai-key (or TTY: node scripts/setup.mjs speak)",
430
+ : "need --elevenlabs-key or --openai-key (or TTY: node .rulesync/scripts/setup.mjs speak)",
431
431
  );
432
432
  }
433
433
 
@@ -502,7 +502,7 @@ export async function setupListen({
502
502
  openaiKey = str(await io.password({ message: "OpenAI API key (fallback STT)" }));
503
503
  } else {
504
504
  throw new Error(
505
- "need whisper-cli or --openai-key (or TTY: node scripts/setup.mjs listen)",
505
+ "need whisper-cli or --openai-key (or TTY: node .rulesync/scripts/setup.mjs listen)",
506
506
  );
507
507
  }
508
508
  }
@@ -611,7 +611,7 @@ export async function setupGoogleWorkspace({
611
611
  installOAuthJson({ src: credsFlag, dest });
612
612
  } else if (!fs.existsSync(dest)) {
613
613
  throw new Error(
614
- `need --creds PATH (or TTY: node scripts/setup.mjs google-workspace)`,
614
+ `need --creds PATH (or TTY: node .rulesync/scripts/setup.mjs google-workspace)`,
615
615
  );
616
616
  }
617
617
 
@@ -678,7 +678,7 @@ export async function setupJev({
678
678
  throw new Error(
679
679
  interactive
680
680
  ? "need a TypeSafe API key"
681
- : "need --api-key (or TTY: node scripts/setup.mjs jev)",
681
+ : "need --api-key (or TTY: node .rulesync/scripts/setup.mjs jev)",
682
682
  );
683
683
  }
684
684
  const dest = writeSecretJson(path.join(home, ".grunt", "jev.json"), { apiKey });
@@ -27,13 +27,13 @@ export const USAGE =
27
27
  "usage: speak whoami | voices | say --text T [--provider elevenlabs|openai] [--voice ID] [--out path] [--play]";
28
28
  const ELEVEN_API = "https://api.elevenlabs.io/v1";
29
29
  const OPENAI_API = "https://api.openai.com/v1";
30
- export const SETUP = "node scripts/setup.mjs speak";
30
+ export const SETUP = "node .rulesync/scripts/setup.mjs speak";
31
31
  export const NEED_KEY =
32
- "missing ELEVENLABS_API_KEY or OPENAI_API_KEY (or ~/.grunt/speak.json). setup: node scripts/setup.mjs speak";
32
+ "missing ELEVENLABS_API_KEY or OPENAI_API_KEY (or ~/.grunt/speak.json). setup: node .rulesync/scripts/setup.mjs speak";
33
33
  export const NEED_ELEVEN =
34
- "missing ELEVENLABS_API_KEY (or ~/.grunt/speak.json elevenlabs.apiKey). setup: node scripts/setup.mjs speak";
34
+ "missing ELEVENLABS_API_KEY (or ~/.grunt/speak.json elevenlabs.apiKey). setup: node .rulesync/scripts/setup.mjs speak";
35
35
  export const NEED_OPENAI =
36
- "missing OPENAI_API_KEY (or ~/.grunt/speak.json openai.apiKey). setup: node scripts/setup.mjs speak";
36
+ "missing OPENAI_API_KEY (or ~/.grunt/speak.json openai.apiKey). setup: node .rulesync/scripts/setup.mjs speak";
37
37
 
38
38
  export function formatHttpError(status, body, provider) {
39
39
  const n = Number(status);
@@ -1,9 +1,9 @@
1
1
  ---
2
2
  name: browser
3
- description: "Browse via node scripts/browser.mjs (Lightpanda). App e2e uses Playwright."
3
+ description: "Browse via node .rulesync/scripts/browser.mjs (Lightpanda). App e2e uses Playwright."
4
4
  ---
5
5
 
6
- Zero-config. `node scripts/browser.mjs <verb>`.
6
+ Zero-config. `node .rulesync/scripts/browser.mjs <verb>`.
7
7
 
8
8
  Verbs: `nav <url>` · `snap` · `click <ref>` · `fill <ref> <text>` · `scroll [ref|down|up|N]` · `wait [ms]` · `hover <ref>` · `select <ref> <value>` · `shot` · `pdf` · `stop` · `doctor` · `ensure`.
9
9
 
@@ -11,5 +11,5 @@ Verbs: `nav <url>` · `snap` · `click <ref>` · `fill <ref> <text>` · `scroll
11
11
  - Lightpanda default. The rail swaps to Chromium when `shot`/`pdf`/`trace`, win32, missing Lightpanda, probe-fail once, Chromium-first hosts (figma docs/sheets/slides mail.google earth amazon), or a blocked/empty/client-rendered snap. One swap. Do not stop and say you cannot.
12
12
  - Session/profile: `.tmp/grunt/browser/`. `stop` reaps; second `stop` ok.
13
13
  - Windows: Chromium even if Lightpanda exists.
14
- - `doctor`/`ensure` → `node scripts/doctor.mjs` or `grunt doctor`. Engine hints: `node scripts/setup.mjs browser`. Do not read a README unless doctor/setup is missing; then https://github.com/lovrozagar/grunt#prerequisites (not the consumer README).
14
+ - `doctor`/`ensure` → `node .rulesync/scripts/doctor.mjs` or `grunt doctor`. Engine hints: `node .rulesync/scripts/setup.mjs browser`. Do not read a README unless doctor/setup is missing; then https://github.com/lovrozagar/grunt#prerequisites (not the consumer README).
15
15
  - No env. No MCP. No raw Playwright. Spec: `.rulesync/reference/browser.md`.
@@ -6,4 +6,4 @@ description: "Low-level Apps Script CLI. Prefer /google-workspace for sheet, doc
6
6
 
7
7
  Custom Apps Script in-repo only. Spec: `.rulesync/reference/clasp.md`.
8
8
 
9
- Sheets, Docs, Slides, Calendar, Gmail: `node scripts/google-workspace.mjs` and `.rulesync/reference/google-workspace.md`.
9
+ Sheets, Docs, Slides, Calendar, Gmail: `node .rulesync/scripts/google-workspace.mjs` and `.rulesync/reference/google-workspace.md`.
@@ -1,11 +1,11 @@
1
1
  ---
2
2
  name: google-workspace
3
- description: "Google Sheets, Docs, Slides, Calendar, Gmail via node scripts/google-workspace.mjs."
3
+ description: "Google Sheets, Docs, Slides, Calendar, Gmail via node .rulesync/scripts/google-workspace.mjs."
4
4
  ---
5
5
  # google-workspace
6
6
 
7
- When the user wants a Google sheet, doc, slide, meeting, or mail, run `node scripts/google-workspace.mjs`. Spec: `.rulesync/reference/google-workspace.md`. If Calendar/Gmail/sheet set fail or login is missing, tell them to run `node scripts/setup.mjs google-workspace` (TTY) for the handheld walk. Do not share creds. Do not invent OAuth JSON. Do not ask them to write Apps Script.
7
+ When the user wants a Google sheet, doc, slide, meeting, or mail, run `node .rulesync/scripts/google-workspace.mjs`. Spec: `.rulesync/reference/google-workspace.md`. If Calendar/Gmail/sheet set fail or login is missing, tell them to run `node .rulesync/scripts/setup.mjs google-workspace` (TTY) for the handheld walk. Do not share creds. Do not invent OAuth JSON. Do not ask them to write Apps Script.
8
8
 
9
9
  `whoami` · `scopes` · `accounts` · `login` · `sheet create|set|get|append|clear` · `doc create|append` · `drive list` · `slide create` · `meeting create [--attendees a@b,c@d]` · `mail send|list`. `--account NAME` for a second Google user on this machine.
10
10
 
11
- Auth: `node scripts/setup.mjs google-workspace` then `login` as needed. Two Google users on one machine: `--account NAME`. Creds stay in `~/.grunt/` (or `~/.grunt/workspace/NAME/`). Do not share OAuth JSON or tokens. Doctor: `node scripts/doctor.mjs` reports google-workspace as optional (`oauth` `tokens` `adc` `clasprc`; no secrets). `clasprc` is Drive-only. Spec: `.rulesync/reference/google-workspace.md` `.rulesync/reference/setup.md`.
11
+ Auth: `node .rulesync/scripts/setup.mjs google-workspace` then `login` as needed. Two Google users on one machine: `--account NAME`. Creds stay in `~/.grunt/` (or `~/.grunt/workspace/NAME/`). Do not share OAuth JSON or tokens. Doctor: `node .rulesync/scripts/doctor.mjs` reports google-workspace as optional (`oauth` `tokens` `adc` `clasprc`; no secrets). `clasprc` is Drive-only. Spec: `.rulesync/reference/google-workspace.md` `.rulesync/reference/setup.md`.
@@ -56,9 +56,9 @@ Facts only. Absolute paths. No transcript paste, no dump, no secrets/tokens.
56
56
 
57
57
  Line 1 of the body: `HANDOFF_NAME: <3-6 word name>`, then the body only.
58
58
 
59
- Write under `.tmp/grunt/handoffs/` (any filename in that dir). Grok `orchestrate-parent.js` runs `scripts/persist-handoff.mjs`: serial/slug from `HANDOFF_NAME:` / heading, injects frontmatter, rewrites path + content. Invalid handoff or a write outside that dir is denied — fix the body, do not retry elsewhere.
59
+ Write under `.tmp/grunt/handoffs/` (any filename in that dir). Grok `orchestrate-parent.js` runs `.rulesync/scripts/persist-handoff.mjs`: serial/slug from `HANDOFF_NAME:` / heading, injects frontmatter, rewrites path + content. Invalid handoff or a write outside that dir is denied — fix the body, do not retry elsewhere.
60
60
 
61
- Host without that hook: name the file per **Path** yourself, or pipe the body to `node scripts/persist-handoff.mjs --workspace {repo}` and use its `path`.
61
+ Host without that hook: name the file per **Path** yourself, or pipe the body to `node .rulesync/scripts/persist-handoff.mjs --workspace {repo}` and use its `path`.
62
62
 
63
63
  ## Report
64
64