@lovrozagar/grunt 0.4.0 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/.agents/agents/grunt.md +2 -2
  2. package/.agents/agents/implementer.md +3 -2
  3. package/.agents/agents/orchestrator.md +17 -11
  4. package/.agents/agents/thinker.md +7 -2
  5. package/.agents/skills/explain/SKILL.md +2 -0
  6. package/.agents/skills/handoff/SKILL.md +1 -1
  7. package/.agents/skills/solo/SKILL.md +1 -1
  8. package/.claude/agents/grunt.md +2 -2
  9. package/.claude/agents/implementer.md +5 -2
  10. package/.claude/agents/orchestrator.md +17 -11
  11. package/.claude/agents/thinker.md +9 -2
  12. package/.claude/skills/explain/SKILL.md +2 -0
  13. package/.claude/skills/handoff/SKILL.md +1 -1
  14. package/.claude/skills/solo/SKILL.md +1 -1
  15. package/.codex/agents/grunt.toml +2 -2
  16. package/.codex/agents/implementer.toml +3 -2
  17. package/.codex/agents/orchestrator.toml +17 -11
  18. package/.codex/agents/thinker.toml +7 -2
  19. package/.grok/agents/grunt.md +2 -2
  20. package/.grok/agents/implementer.md +3 -2
  21. package/.grok/agents/orchestrator.md +17 -11
  22. package/.grok/agents/thinker.md +7 -2
  23. package/.grok/hooks/orchestrate-parent.js +28 -110
  24. package/.grok/skills/explain/SKILL.md +2 -0
  25. package/.grok/skills/handoff/SKILL.md +1 -1
  26. package/.grok/skills/solo/SKILL.md +1 -1
  27. package/.rulesync/reference/cascade.md +12 -6
  28. package/.rulesync/reference/hooks.md +4 -4
  29. package/.rulesync/reference/map.md +2 -3
  30. package/.rulesync/reference/output.md +38 -1
  31. package/.rulesync/rules/CLAUDE.md +18 -12
  32. package/.rulesync/rules/overview.md +18 -12
  33. package/.rulesync/skills/explain/SKILL.md +2 -0
  34. package/.rulesync/skills/handoff/SKILL.md +1 -1
  35. package/.rulesync/skills/solo/SKILL.md +1 -1
  36. package/.rulesync/subagents/grunt.md +2 -2
  37. package/.rulesync/subagents/implementer.md +3 -2
  38. package/.rulesync/subagents/orchestrator.md +17 -11
  39. package/.rulesync/subagents/thinker.md +7 -2
  40. package/AGENTS.md +20 -12
  41. package/CHANGELOG.md +187 -0
  42. package/CLAUDE.md +20 -12
  43. package/README.md +32 -15
  44. package/cli/init.mjs +195 -29
  45. package/cli/init.test.ts +458 -43
  46. package/package.json +11 -7
  47. package/scripts/emit-gemini.mjs +7 -1
  48. package/scripts/grunt-job.mjs +0 -9
  49. package/scripts/guarded-roots.mjs +83 -0
  50. package/scripts/telemetry.mjs +0 -38
@@ -3,6 +3,7 @@
3
3
  import fs from "node:fs";
4
4
  import path from "node:path";
5
5
  import { fileURLToPath, pathToFileURL } from "node:url";
6
+ import { guardedMarkdownDrift, writeMergedGuardedFile } from "../cli/init.mjs";
6
7
 
7
8
  export const AGENT_IDS = ["orchestrator", "implementer", "thinker", "grunt"];
8
9
  const SSOT_REL = ".rulesync/subagents";
@@ -173,6 +174,10 @@ export function emitGemini({ workspaceRoot, check = false } = {}) {
173
174
  if (check) {
174
175
  const drift = [];
175
176
  for (const f of files) {
177
+ if (f.rel === GEMINI_MD_REL) {
178
+ if (guardedMarkdownDrift(path.join(ws, f.rel), f.text)) drift.push(f.rel);
179
+ continue;
180
+ }
176
181
  const disk = readText(path.join(ws, f.rel));
177
182
  if (disk !== f.text) drift.push(f.rel);
178
183
  }
@@ -185,7 +190,8 @@ export function emitGemini({ workspaceRoot, check = false } = {}) {
185
190
  for (const f of files) {
186
191
  const abs = path.join(ws, f.rel);
187
192
  fs.mkdirSync(path.dirname(abs), { recursive: true });
188
- fs.writeFileSync(abs, f.text);
193
+ if (f.rel === GEMINI_MD_REL) writeMergedGuardedFile(abs, f.text);
194
+ else fs.writeFileSync(abs, f.text);
189
195
  }
190
196
  return { ok: true, check: false };
191
197
  }
@@ -4,7 +4,6 @@ import { spawnSync } from "node:child_process";
4
4
  import fs from "node:fs";
5
5
  import path from "node:path";
6
6
  import { fileURLToPath, pathToFileURL } from "node:url";
7
- import { logTelemetry } from "./telemetry.mjs";
8
7
 
9
8
  export const FALLBACK = "FALLBACK";
10
9
  export const SHELL_META = /[|&;`$(){}<>\n\r]/;
@@ -443,14 +442,12 @@ function main() {
443
442
  try {
444
443
  const parsed = parseArgv(process.argv.slice(2));
445
444
  if (parsed.unknown) {
446
- logTelemetry("grunt-job", { fallback: true, query: parsed.query }, process.cwd());
447
445
  process.stdout.write(FALLBACK + "\n");
448
446
  return 2;
449
447
  }
450
448
  const ws = process.cwd();
451
449
  const cwd = parsed.cwd ? resolveJobCwd(parsed.cwd, ws) : ws;
452
450
  if (!cwd) {
453
- logTelemetry("grunt-job", { fallback: true, query: parsed.query }, ws);
454
451
  process.stdout.write(FALLBACK + "\n");
455
452
  return 2;
456
453
  }
@@ -461,15 +458,9 @@ function main() {
461
458
  path: parsed.path,
462
459
  glob: parsed.glob,
463
460
  });
464
- logTelemetry(
465
- "grunt-job",
466
- { job: parsed.job, fallback: Boolean(result.fallback), query: parsed.query },
467
- ws,
468
- );
469
461
  process.stdout.write(result.text);
470
462
  return result.code;
471
463
  } catch {
472
- logTelemetry("grunt-job", { fallback: true }, process.cwd());
473
464
  process.stdout.write(FALLBACK + "\n");
474
465
  return 2;
475
466
  }
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env node
2
+ /** Snapshot/remerge AGENTS.md CLAUDE.md GEMINI.md around generate/check/watch. */
3
+ import { execSync } from "node:child_process";
4
+ import fs from "node:fs";
5
+ import { fileURLToPath } from "node:url";
6
+ import path from "node:path";
7
+ import {
8
+ GUARDED_ROOT_FILES,
9
+ healGuardedRootFile,
10
+ remergeGuardedRoots,
11
+ snapshotGuardedRoots,
12
+ withGuardedCheckInteriors,
13
+ } from "../cli/init.mjs";
14
+
15
+ const GENERATE_RAW = "rulesync:generate:raw";
16
+ const CHECK_RAW = "rulesync:check:raw";
17
+ const WATCH_RAW = "rulesync:watch:raw";
18
+
19
+ export function attachGuardedRootWatchers(cwd, heal) {
20
+ const w = fs.watch(cwd, (_event, filename) => {
21
+ if (!filename) return;
22
+ const base = path.basename(String(filename));
23
+ if (!GUARDED_ROOT_FILES.includes(base)) return;
24
+ heal(base);
25
+ });
26
+ return () => {
27
+ try {
28
+ w.close();
29
+ } catch {
30
+ /* ignore */
31
+ }
32
+ };
33
+ }
34
+
35
+ export function runGuardedRoots(mode, { cwd = process.cwd(), exec = execSync, attachWatchers } = {}) {
36
+ if (mode === "generate") {
37
+ const snap = snapshotGuardedRoots(cwd);
38
+ try {
39
+ exec(`npm run ${GENERATE_RAW}`, { cwd, stdio: "inherit", shell: true });
40
+ } finally {
41
+ remergeGuardedRoots(cwd, snap);
42
+ }
43
+ return;
44
+ }
45
+ if (mode === "check") {
46
+ withGuardedCheckInteriors(cwd, () => {
47
+ exec(`npm run ${CHECK_RAW}`, { cwd, stdio: "inherit", shell: true });
48
+ });
49
+ return;
50
+ }
51
+ if (mode === "watch") {
52
+ const snap = snapshotGuardedRoots(cwd);
53
+ const heal = (file) => healGuardedRootFile(cwd, file, snap);
54
+ const attach = attachWatchers || ((opts) => attachGuardedRootWatchers(opts.cwd, opts.heal));
55
+ const stop = attach({ cwd, snap, heal });
56
+ try {
57
+ exec(`npm run ${WATCH_RAW}`, { cwd, stdio: "inherit", shell: true });
58
+ } finally {
59
+ try {
60
+ stop?.();
61
+ } catch {
62
+ /* ignore */
63
+ }
64
+ remergeGuardedRoots(cwd, snap);
65
+ }
66
+ return;
67
+ }
68
+ throw new Error("usage: guarded-roots.mjs generate|check|watch");
69
+ }
70
+
71
+ function main() {
72
+ try {
73
+ runGuardedRoots(process.argv[2] || "");
74
+ return 0;
75
+ } catch (err) {
76
+ process.stderr.write((err && err.message ? err.message : String(err)) + "\n");
77
+ return 1;
78
+ }
79
+ }
80
+
81
+ const thisFile = fileURLToPath(import.meta.url);
82
+ const invoked = process.argv[1] ? path.resolve(process.argv[1]) : "";
83
+ if (invoked === thisFile) process.exit(main());
@@ -1,38 +0,0 @@
1
- #!/usr/bin/env node
2
- /** Append-only hook sidecar. Never throws. Never writes stdout. */
3
- import fs from "node:fs";
4
- import path from "node:path";
5
-
6
- const QUERY_MAX = 120;
7
-
8
- export const ORCHESTRATOR_LOGS_DIR = ".tmp/orchestrator-logs";
9
-
10
- export function telemetryPath(workspaceRoot) {
11
- const root =
12
- workspaceRoot ||
13
- process.env.GROK_WORKSPACE_ROOT ||
14
- process.cwd();
15
- return path.join(root, ORCHESTRATOR_LOGS_DIR, "telemetry.ndjson");
16
- }
17
-
18
- function truncateQuery(q) {
19
- const s = String(q ?? "");
20
- if (s.length <= QUERY_MAX) return s;
21
- return s.slice(0, QUERY_MAX);
22
- }
23
-
24
- export function logTelemetry(event, fields, workspaceRoot) {
25
- try {
26
- const rec = { ts: Date.now(), event: String(event || "") };
27
- const src = fields && typeof fields === "object" ? fields : {};
28
- for (const [k, v] of Object.entries(src)) {
29
- if (k === "query" && v != null) rec.query = truncateQuery(v);
30
- else rec[k] = v;
31
- }
32
- const dest = telemetryPath(workspaceRoot);
33
- fs.mkdirSync(path.dirname(dest), { recursive: true });
34
- fs.appendFileSync(dest, JSON.stringify(rec) + "\n");
35
- } catch {
36
- // fail-open
37
- }
38
- }