@lovrozagar/grunt 0.10.1 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/agents/orchestrator.md +1 -1
- package/.agents/hooks.json +2 -2
- package/.agents/skills/browser/SKILL.md +3 -3
- package/.agents/skills/clasp/SKILL.md +1 -1
- package/.agents/skills/google-workspace/SKILL.md +3 -3
- package/.agents/skills/handoff/SKILL.md +2 -2
- package/.agents/skills/implement-plan/SKILL.md +2 -2
- package/.agents/skills/jev/SKILL.md +3 -3
- package/.agents/skills/listen/SKILL.md +3 -3
- package/.agents/skills/pickup/SKILL.md +1 -1
- package/.agents/skills/speak/SKILL.md +3 -3
- package/.agents/skills/tmp/SKILL.md +2 -2
- package/.agents/skills/write-plan/SKILL.md +4 -2
- package/.claude/agents/orchestrator.md +1 -1
- package/.claude/settings.json +3 -3
- package/.claude/skills/browser/SKILL.md +3 -3
- package/.claude/skills/clasp/SKILL.md +1 -1
- package/.claude/skills/google-workspace/SKILL.md +3 -3
- package/.claude/skills/handoff/SKILL.md +2 -2
- package/.claude/skills/implement-plan/SKILL.md +2 -2
- package/.claude/skills/jev/SKILL.md +3 -3
- package/.claude/skills/listen/SKILL.md +3 -3
- package/.claude/skills/pickup/SKILL.md +1 -1
- package/.claude/skills/speak/SKILL.md +3 -3
- package/.claude/skills/tmp/SKILL.md +2 -2
- package/.claude/skills/write-plan/SKILL.md +4 -2
- package/.codex/agents/orchestrator.toml +1 -1
- package/.codex/hooks.json +3 -3
- package/.grok/agents/orchestrator.md +1 -1
- package/.grok/hooks/orchestrate-parent.js +9 -9
- package/.grok/skills/browser/SKILL.md +3 -3
- package/.grok/skills/clasp/SKILL.md +1 -1
- package/.grok/skills/google-workspace/SKILL.md +3 -3
- package/.grok/skills/handoff/SKILL.md +2 -2
- package/.grok/skills/implement-plan/SKILL.md +2 -2
- package/.grok/skills/jev/SKILL.md +3 -3
- package/.grok/skills/listen/SKILL.md +3 -3
- package/.grok/skills/pickup/SKILL.md +1 -1
- package/.grok/skills/speak/SKILL.md +3 -3
- package/.grok/skills/tmp/SKILL.md +2 -2
- package/.grok/skills/write-plan/SKILL.md +4 -2
- package/.rulesync/hooks.jsonc +3 -3
- package/.rulesync/reference/INDEX.md +9 -9
- package/.rulesync/reference/browser.md +4 -4
- package/.rulesync/reference/cascade.md +2 -2
- package/.rulesync/reference/clasp.md +1 -1
- package/.rulesync/reference/google-workspace.md +29 -29
- package/.rulesync/reference/hooks.md +3 -3
- package/.rulesync/reference/implementation-format.md +2 -2
- package/.rulesync/reference/jev.md +3 -3
- package/.rulesync/reference/law.md +1 -1
- package/.rulesync/reference/listen.md +12 -12
- package/.rulesync/reference/map.md +25 -25
- package/.rulesync/reference/refs-map.md +3 -3
- package/.rulesync/reference/rtk.md +1 -1
- package/.rulesync/reference/scope.md +12 -14
- package/.rulesync/reference/setup.md +7 -7
- package/.rulesync/reference/skills-map.md +5 -5
- package/.rulesync/reference/speak.md +6 -6
- package/.rulesync/rules/overview.md +5 -5
- package/{scripts → .rulesync/scripts}/doctor.mjs +2 -2
- package/{scripts → .rulesync/scripts}/feature-packs.mjs +1 -1
- package/{scripts → .rulesync/scripts}/folder-map.mjs +7 -3
- package/{scripts → .rulesync/scripts}/gate-fat-tools.mjs +3 -3
- package/{scripts → .rulesync/scripts}/google-workspace.mjs +2 -2
- package/{scripts → .rulesync/scripts}/grunt-job.mjs +2 -0
- package/{scripts → .rulesync/scripts}/guarded-md.mjs +10 -4
- package/{scripts → .rulesync/scripts}/hooks-union.mjs +13 -8
- package/{scripts → .rulesync/scripts}/jev.mjs +2 -2
- package/{scripts → .rulesync/scripts}/listen.mjs +6 -6
- package/{scripts → .rulesync/scripts}/pipeline.mjs +14 -14
- package/{scripts → .rulesync/scripts}/session-map.mjs +1 -1
- package/{scripts → .rulesync/scripts}/setup.mjs +4 -4
- package/{scripts → .rulesync/scripts}/speak.mjs +4 -4
- package/.rulesync/skills/browser/SKILL.md +3 -3
- package/.rulesync/skills/clasp/SKILL.md +1 -1
- package/.rulesync/skills/google-workspace/SKILL.md +3 -3
- package/.rulesync/skills/handoff/SKILL.md +2 -2
- package/.rulesync/skills/implement-plan/SKILL.md +2 -2
- package/.rulesync/skills/jev/SKILL.md +3 -3
- package/.rulesync/skills/listen/SKILL.md +3 -3
- package/.rulesync/skills/pickup/SKILL.md +1 -1
- package/.rulesync/skills/speak/SKILL.md +3 -3
- package/.rulesync/skills/tmp/SKILL.md +2 -2
- package/.rulesync/skills/write-plan/SKILL.md +4 -2
- package/.rulesync/subagents/orchestrator.md +1 -1
- package/AGENTS.md +7 -5
- package/CHANGELOG.md +21 -3
- package/CLAUDE.md +2 -0
- package/README.md +23 -23
- package/cli/grunt.mjs +3 -3
- package/cli/grunt.test.ts +6 -6
- package/cli/init.mjs +67 -17
- package/cli/init.test.ts +321 -163
- package/cli/prompt.mjs +1 -1
- package/cli/self-update.mjs +1 -1
- package/package.json +15 -50
- /package/{scripts → .rulesync/scripts}/board.mjs +0 -0
- /package/{scripts → .rulesync/scripts}/browser.mjs +0 -0
- /package/{scripts → .rulesync/scripts}/check-globals.mjs +0 -0
- /package/{scripts → .rulesync/scripts}/emit-agent-shell-tools.mjs +0 -0
- /package/{scripts → .rulesync/scripts}/emit-gemini.mjs +0 -0
- /package/{scripts → .rulesync/scripts}/emit-maps.mjs +0 -0
- /package/{scripts → .rulesync/scripts}/emit-mcp-policy.mjs +0 -0
- /package/{scripts → .rulesync/scripts}/guarded-roots.mjs +0 -0
- /package/{scripts → .rulesync/scripts}/interactive.mjs +0 -0
- /package/{scripts → .rulesync/scripts}/jsonc.mjs +0 -0
- /package/{scripts → .rulesync/scripts}/package-manager.mjs +0 -0
- /package/{scripts → .rulesync/scripts}/parse-need.mjs +0 -0
- /package/{scripts → .rulesync/scripts}/persist-handoff.mjs +0 -0
- /package/{scripts → .rulesync/scripts}/persist-implementation.mjs +0 -0
- /package/{scripts → .rulesync/scripts}/persist-plan.mjs +0 -0
- /package/{scripts → .rulesync/scripts}/persist-tmp.mjs +0 -0
- /package/{scripts → .rulesync/scripts}/prompt.mjs +0 -0
- /package/{scripts → .rulesync/scripts}/purge-global-mcps.mjs +0 -0
- /package/{scripts → .rulesync/scripts}/scrub-spawn-prompt.mjs +0 -0
- /package/{scripts → .rulesync/scripts}/scrub-text +0 -0
- /package/{scripts → .rulesync/scripts}/scrub-text-lib.mjs +0 -0
- /package/{scripts → .rulesync/scripts}/skill-conflicts.mjs +0 -0
- /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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
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 (
|
|
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
|
|
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 (
|
|
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
|
-
|
|
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 (
|
|
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`
|
|
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
|
|
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
|
-
-
|
|
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:
|
|
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 "
|
|
16
|
-
import { mapCommand } from "
|
|
17
|
-
import { PACK_OPTIONS, parsePacksFlag, selectedPacks } from "
|
|
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("
|
|
38
|
-
vi.mock("
|
|
39
|
-
const actual = await importOriginal<typeof import("
|
|
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("
|
|
57
|
-
const actual = await importOriginal<typeof import("
|
|
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 "
|
|
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 "
|
|
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 "
|
|
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 "
|
|
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 "
|
|
33
|
-
import { LAW_REL, emitMaps } from "
|
|
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 "
|
|
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 "
|
|
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
|
|
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, "
|
|
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") : ""
|
|
@@ -338,8 +371,10 @@ export function mergeClaudeSettings(destRoot, pkgRoot) {
|
|
|
338
371
|
fs.writeFileSync(destPath, `${JSON.stringify(destSettings, null, 2)}\n`)
|
|
339
372
|
}
|
|
340
373
|
|
|
374
|
+
// A command grunt wrote: one of its scripts, or a run of a grunt: script. Not any `bun run`, which a
|
|
375
|
+
// repo's own `bun run build && …` would match, and its script would be taken over as grunt's.
|
|
341
376
|
function looksGruntOwnedPrefix(prefix) {
|
|
342
|
-
return /rulesync|sync:globals|^(?:npm|yarn|pnpm|bun) run
|
|
377
|
+
return /rulesync|sync:globals|^(?:npm|yarn|pnpm|bun) run grunt:|guarded-roots\.mjs|hooks-union\.mjs|doctor\.mjs|sync-global-settings\.mjs|check-globals\.mjs|purge-global-mcps\.mjs/.test(
|
|
343
378
|
prefix,
|
|
344
379
|
)
|
|
345
380
|
}
|
|
@@ -478,7 +513,12 @@ export function mergePackageJson(dest, pkgRoot) {
|
|
|
478
513
|
if (destPkg.name === "@lovrozagar/grunt") return
|
|
479
514
|
|
|
480
515
|
const srcPkg = JSON.parse(fs.readFileSync(path.join(pkgRoot, "package.json"), "utf8"))
|
|
481
|
-
destPkg.scripts =
|
|
516
|
+
destPkg.scripts = Object.fromEntries(
|
|
517
|
+
Object.entries(destPkg.scripts || {}).map(([k, v]) => [
|
|
518
|
+
k,
|
|
519
|
+
typeof v === "string" ? rewriteLegacyScriptRefs(v) : v,
|
|
520
|
+
]),
|
|
521
|
+
)
|
|
482
522
|
const srcKeys = []
|
|
483
523
|
for (const [k, v] of Object.entries(srcPkg.scripts || {})) {
|
|
484
524
|
if (k === "test" || k.endsWith(":raw") || k in LAUNCH_SCRIPTS) continue
|
|
@@ -603,8 +643,10 @@ export function init(dest, { pkgRoot: pkgRootOpt, execFileSync: exec = execFileS
|
|
|
603
643
|
const refSnap = self ? {} : snapshotReferenceDocs(dest)
|
|
604
644
|
let destPipelineSrc = ""
|
|
605
645
|
if (!self) {
|
|
606
|
-
const destPipeline =
|
|
607
|
-
|
|
646
|
+
const destPipeline = [SCRIPTS_REL, LEGACY_SCRIPTS_REL]
|
|
647
|
+
.map((rel) => path.join(dest, ...rel.split("/"), "pipeline.mjs"))
|
|
648
|
+
.find((p) => fs.existsSync(p))
|
|
649
|
+
if (destPipeline) destPipelineSrc = rewriteLegacyScriptRefs(fs.readFileSync(destPipeline, "utf8"))
|
|
608
650
|
}
|
|
609
651
|
|
|
610
652
|
for (const dir of COPY_DIRS) {
|
|
@@ -616,7 +658,12 @@ export function init(dest, { pkgRoot: pkgRootOpt, execFileSync: exec = execFileS
|
|
|
616
658
|
copyTree(src, d, (s) => path.basename(s) !== "settings.json" && allowPack(s))
|
|
617
659
|
mergeClaudeSettings(dest, pkgRoot)
|
|
618
660
|
} else if (dir === ".rulesync") {
|
|
619
|
-
|
|
661
|
+
const scriptsSrc = path.join(pkgRoot, ...SCRIPTS_REL.split("/"))
|
|
662
|
+
copyTree(
|
|
663
|
+
src,
|
|
664
|
+
d,
|
|
665
|
+
(s) => s !== scriptsSrc && !GENERATED_MAP_FILES.has(path.basename(s)) && allowPack(s),
|
|
666
|
+
)
|
|
620
667
|
} else {
|
|
621
668
|
copyTree(src, d, allowPack)
|
|
622
669
|
}
|
|
@@ -628,21 +675,24 @@ export function init(dest, { pkgRoot: pkgRootOpt, execFileSync: exec = execFileS
|
|
|
628
675
|
mergeGuardedMarkdown(dest, pkgRoot, file)
|
|
629
676
|
}
|
|
630
677
|
|
|
631
|
-
|
|
678
|
+
const scriptsDest = path.join(dest, ...SCRIPTS_REL.split("/"))
|
|
679
|
+
fs.mkdirSync(scriptsDest, { recursive: true })
|
|
632
680
|
const skipScripts = unselectedScriptNames(selected)
|
|
633
681
|
for (const name of PRODUCT_SCRIPTS) {
|
|
634
682
|
if (skipScripts.has(name)) continue
|
|
635
|
-
const src = path.join(pkgRoot, "
|
|
636
|
-
const d = path.join(
|
|
683
|
+
const src = path.join(pkgRoot, ...SCRIPTS_REL.split("/"), name)
|
|
684
|
+
const d = path.join(scriptsDest, name)
|
|
637
685
|
if (samePath(src, d)) continue
|
|
638
686
|
copyTree(src, d)
|
|
639
687
|
}
|
|
688
|
+
if (!self) pruneLegacyScripts(dest)
|
|
640
689
|
|
|
641
690
|
fs.mkdirSync(path.join(dest, ".tmp"), { recursive: true })
|
|
642
691
|
mergeGitignore(dest)
|
|
643
692
|
|
|
644
693
|
if (!self) {
|
|
645
694
|
remergeReferenceDocs(dest, refSnap, pkgRoot, unselectedRefNames(selected))
|
|
695
|
+
rewriteLegacyPipelineConfig(dest)
|
|
646
696
|
seedPipelineConfig(dest, destPipelineSrc)
|
|
647
697
|
mergePackageJson(dest, pkgRoot)
|
|
648
698
|
copyRootIfMissing(dest, pkgRoot)
|