@lovrozagar/grunt 0.5.2 → 0.6.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.
Files changed (157) hide show
  1. package/.agents/agents/grunt.md +2 -24
  2. package/.agents/agents/orchestrator.md +5 -50
  3. package/.agents/skills/ask/SKILL.md +5 -12
  4. package/.agents/skills/auto/SKILL.md +7 -12
  5. package/.agents/skills/browser/SKILL.md +4 -8
  6. package/.agents/skills/clasp/SKILL.md +11 -0
  7. package/.agents/skills/commit/SKILL.md +3 -1
  8. package/.agents/skills/commit-push/SKILL.md +2 -0
  9. package/.agents/skills/commit-push-deploy/SKILL.md +2 -0
  10. package/.agents/skills/commit-push-release/SKILL.md +2 -0
  11. package/.agents/skills/explain/SKILL.md +7 -13
  12. package/.agents/skills/google-workspace/SKILL.md +13 -0
  13. package/.agents/skills/handoff/SKILL.md +7 -11
  14. package/.agents/skills/implement-plan/SKILL.md +5 -109
  15. package/.agents/skills/listen/SKILL.md +15 -0
  16. package/.agents/skills/pickup/SKILL.md +6 -12
  17. package/.agents/skills/speak/SKILL.md +13 -0
  18. package/.agents/skills/tmp/SKILL.md +6 -10
  19. package/.agents/skills/write-plan/SKILL.md +5 -77
  20. package/.claude/agents/grunt.md +2 -26
  21. package/.claude/agents/orchestrator.md +12 -50
  22. package/.claude/skills/ask/SKILL.md +5 -12
  23. package/.claude/skills/auto/SKILL.md +7 -12
  24. package/.claude/skills/browser/SKILL.md +4 -8
  25. package/.claude/skills/clasp/SKILL.md +11 -0
  26. package/.claude/skills/commit/SKILL.md +3 -1
  27. package/.claude/skills/commit-push/SKILL.md +2 -0
  28. package/.claude/skills/commit-push-deploy/SKILL.md +2 -0
  29. package/.claude/skills/commit-push-release/SKILL.md +2 -0
  30. package/.claude/skills/explain/SKILL.md +7 -13
  31. package/.claude/skills/google-workspace/SKILL.md +13 -0
  32. package/.claude/skills/handoff/SKILL.md +7 -11
  33. package/.claude/skills/implement-plan/SKILL.md +5 -109
  34. package/.claude/skills/listen/SKILL.md +15 -0
  35. package/.claude/skills/pickup/SKILL.md +6 -12
  36. package/.claude/skills/speak/SKILL.md +13 -0
  37. package/.claude/skills/tmp/SKILL.md +6 -10
  38. package/.claude/skills/write-plan/SKILL.md +5 -77
  39. package/.codex/agents/grunt.toml +2 -26
  40. package/.codex/agents/orchestrator.toml +4 -50
  41. package/.grok/agents/grunt.md +2 -24
  42. package/.grok/agents/orchestrator.md +4 -48
  43. package/.grok/global-settings.toml +1 -3
  44. package/.grok/hooks/orchestrate-parent.js +190 -358
  45. package/.grok/skills/ask/SKILL.md +5 -12
  46. package/.grok/skills/auto/SKILL.md +7 -12
  47. package/.grok/skills/browser/SKILL.md +4 -8
  48. package/.grok/skills/clasp/SKILL.md +11 -0
  49. package/.grok/skills/commit/SKILL.md +3 -1
  50. package/.grok/skills/commit-push/SKILL.md +2 -0
  51. package/.grok/skills/commit-push-deploy/SKILL.md +2 -0
  52. package/.grok/skills/commit-push-release/SKILL.md +2 -0
  53. package/.grok/skills/explain/SKILL.md +7 -13
  54. package/.grok/skills/google-workspace/SKILL.md +13 -0
  55. package/.grok/skills/handoff/SKILL.md +7 -11
  56. package/.grok/skills/implement-plan/SKILL.md +5 -109
  57. package/.grok/skills/listen/SKILL.md +15 -0
  58. package/.grok/skills/pickup/SKILL.md +6 -12
  59. package/.grok/skills/speak/SKILL.md +13 -0
  60. package/.grok/skills/tmp/SKILL.md +6 -10
  61. package/.grok/skills/write-plan/SKILL.md +5 -77
  62. package/.rulesync/hooks.jsonc +1 -1
  63. package/.rulesync/mcp-policy.jsonc +0 -2
  64. package/.rulesync/reference/INDEX.md +30 -20
  65. package/.rulesync/reference/browser.md +13 -6
  66. package/.rulesync/reference/cascade.md +6 -86
  67. package/.rulesync/reference/clasp.md +21 -0
  68. package/.rulesync/reference/google-workspace.md +79 -0
  69. package/.rulesync/reference/hooks.md +7 -11
  70. package/.rulesync/reference/implementation-format.md +113 -0
  71. package/.rulesync/reference/law.md +5 -3
  72. package/.rulesync/reference/listen.md +80 -0
  73. package/.rulesync/reference/map.md +40 -36
  74. package/.rulesync/reference/output.md +3 -51
  75. package/.rulesync/reference/plan-format.md +4 -2
  76. package/.rulesync/reference/refs-map.md +12 -5
  77. package/.rulesync/reference/rtk.md +3 -2
  78. package/.rulesync/reference/scope.md +37 -0
  79. package/.rulesync/reference/setup.md +27 -0
  80. package/.rulesync/reference/skills-map.md +13 -12
  81. package/.rulesync/reference/speak.md +59 -0
  82. package/.rulesync/rules/CLAUDE.md +3 -49
  83. package/.rulesync/rules/overview.md +9 -47
  84. package/.rulesync/skills/ask/SKILL.md +5 -12
  85. package/.rulesync/skills/auto/SKILL.md +5 -12
  86. package/.rulesync/skills/browser/SKILL.md +4 -8
  87. package/.rulesync/skills/clasp/SKILL.md +9 -0
  88. package/.rulesync/skills/commit/SKILL.md +3 -1
  89. package/.rulesync/skills/commit-push/SKILL.md +2 -0
  90. package/.rulesync/skills/commit-push-deploy/SKILL.md +2 -0
  91. package/.rulesync/skills/commit-push-release/SKILL.md +2 -0
  92. package/.rulesync/skills/explain/SKILL.md +7 -14
  93. package/.rulesync/skills/google-workspace/SKILL.md +11 -0
  94. package/.rulesync/skills/handoff/SKILL.md +7 -11
  95. package/.rulesync/skills/implement-plan/SKILL.md +5 -119
  96. package/.rulesync/skills/listen/SKILL.md +13 -0
  97. package/.rulesync/skills/pickup/SKILL.md +6 -12
  98. package/.rulesync/skills/speak/SKILL.md +13 -0
  99. package/.rulesync/skills/tmp/SKILL.md +6 -10
  100. package/.rulesync/skills/write-plan/SKILL.md +5 -84
  101. package/.rulesync/subagents/grunt.md +2 -24
  102. package/.rulesync/subagents/orchestrator.md +14 -59
  103. package/AGENTS.md +9 -47
  104. package/CHANGELOG.md +67 -0
  105. package/CLAUDE.md +1 -49
  106. package/README.md +89 -80
  107. package/cli/grunt.mjs +21 -3
  108. package/cli/grunt.test.ts +47 -3
  109. package/cli/init.mjs +134 -234
  110. package/cli/init.test.ts +241 -58
  111. package/cli/prompt.mjs +9 -36
  112. package/cli/prompt.test.ts +14 -0
  113. package/package.json +10 -2
  114. package/scripts/browser.mjs +128 -7
  115. package/scripts/doctor.mjs +104 -10
  116. package/scripts/emit-gemini.mjs +2 -2
  117. package/scripts/emit-mcp-policy.mjs +1 -1
  118. package/scripts/gate-fat-tools.mjs +223 -173
  119. package/scripts/google-workspace.mjs +713 -0
  120. package/scripts/grunt-job.mjs +453 -69
  121. package/scripts/guarded-md.mjs +230 -0
  122. package/scripts/guarded-roots.mjs +1 -1
  123. package/scripts/interactive.mjs +15 -0
  124. package/scripts/listen.mjs +829 -0
  125. package/scripts/parse-need.mjs +21 -2
  126. package/scripts/persist-implementation.mjs +278 -0
  127. package/scripts/prompt.mjs +32 -0
  128. package/scripts/scrub-spawn-prompt.mjs +3 -50
  129. package/scripts/setup.mjs +746 -0
  130. package/scripts/speak.mjs +291 -0
  131. package/.agents/agents/implementer.md +0 -32
  132. package/.agents/agents/thinker.md +0 -29
  133. package/.agents/skills/cascade/SKILL.md +0 -28
  134. package/.agents/skills/parent/SKILL.md +0 -6
  135. package/.agents/skills/solo/SKILL.md +0 -25
  136. package/.claude/agents/implementer.md +0 -37
  137. package/.claude/agents/thinker.md +0 -36
  138. package/.claude/skills/cascade/SKILL.md +0 -28
  139. package/.claude/skills/parent/SKILL.md +0 -6
  140. package/.claude/skills/solo/SKILL.md +0 -25
  141. package/.codex/agents/implementer.toml +0 -26
  142. package/.codex/agents/thinker.toml +0 -26
  143. package/.grok/agents/implementer.md +0 -27
  144. package/.grok/agents/thinker.md +0 -27
  145. package/.grok/parent.md +0 -1
  146. package/.grok/roles/implementer.toml +0 -5
  147. package/.grok/roles/thinker.toml +0 -5
  148. package/.grok/skills/cascade/SKILL.md +0 -28
  149. package/.grok/skills/parent/SKILL.md +0 -6
  150. package/.grok/skills/solo/SKILL.md +0 -25
  151. package/.rulesync/grunt.config.jsonc +0 -7
  152. package/.rulesync/skills/cascade/SKILL.md +0 -28
  153. package/.rulesync/skills/parent/SKILL.md +0 -6
  154. package/.rulesync/skills/solo/SKILL.md +0 -25
  155. package/.rulesync/subagents/implementer.md +0 -49
  156. package/.rulesync/subagents/thinker.md +0 -49
  157. package/scripts/grunt-config.mjs +0 -50
package/cli/init.mjs CHANGED
@@ -6,24 +6,51 @@ import {
6
6
  WORKSPACE_SKILLS_REL,
7
7
  findSkillContentConflicts,
8
8
  formatSkillConflictWarn,
9
+ listSkillDirNames,
9
10
  } from "../scripts/skill-conflicts.mjs"
11
+ import {
12
+ SENTINEL_BEGIN,
13
+ extractGruntBody,
14
+ writeMergedGuardedFile,
15
+ snapshotGuardedRoots,
16
+ remergeGuardedRoots,
17
+ withGuardedCheckInteriors,
18
+ } from "../scripts/guarded-md.mjs"
19
+
20
+ export {
21
+ GUARDED_ROOT_FILES,
22
+ SENTINEL_BEGIN,
23
+ SENTINEL_END,
24
+ MAX_GUARDED_MARKDOWN_BYTES,
25
+ extractUserMarkdown,
26
+ extractGruntBody,
27
+ composeGuardedMarkdown,
28
+ mergeGuardedContent,
29
+ writeMergedGuardedFile,
30
+ guardedMarkdownDrift,
31
+ snapshotGuardedRoots,
32
+ remergeGuardedRoots,
33
+ healGuardedRootFile,
34
+ withGuardedCheckInteriors,
35
+ } from "../scripts/guarded-md.mjs"
10
36
 
11
37
  const PKG_ROOT = path.join(path.dirname(fileURLToPath(import.meta.url)), "..")
12
38
 
13
- const PRODUCT_SCRIPTS = [
39
+ export const PRODUCT_SCRIPTS = [
14
40
  "check-globals.mjs",
15
41
  "emit-agent-shell-tools.mjs",
16
42
  "emit-gemini.mjs",
17
43
  "emit-maps.mjs",
44
+ "guarded-md.mjs",
18
45
  "guarded-roots.mjs",
19
46
  "emit-mcp-policy.mjs",
20
47
  "gate-fat-tools.mjs",
21
48
  "hooks-union.mjs",
22
49
  "pipeline.mjs",
23
50
  "grunt-job.mjs",
24
- "grunt-config.mjs",
25
51
  "parse-need.mjs",
26
52
  "persist-handoff.mjs",
53
+ "persist-implementation.mjs",
27
54
  "persist-tmp.mjs",
28
55
  "persist-plan.mjs",
29
56
  "purge-global-mcps.mjs",
@@ -31,14 +58,64 @@ const PRODUCT_SCRIPTS = [
31
58
  "scrub-text-lib.mjs",
32
59
  "sync-global-settings.mjs",
33
60
  "browser.mjs",
61
+ "speak.mjs",
62
+ "listen.mjs",
63
+ "google-workspace.mjs",
64
+ "interactive.mjs",
65
+ "prompt.mjs",
66
+ "setup.mjs",
34
67
  "doctor.mjs",
35
68
  "skill-conflicts.mjs",
36
69
  "scrub-text",
37
70
  ]
38
71
 
39
72
  const COPY_DIRS = [".rulesync", ".grok", ".codex", ".claude", ".agents"]
73
+ export const RETIRED_SKILLS = ["parent", "solo", "cascade"]
74
+ export const RETIRED_AGENTS = ["implementer", "thinker"]
75
+ /** Cumulative. Dest `scripts/<name>` deleted on init/upgrade even if no longer shipped. */
76
+ export const RETIRED_SCRIPTS = ["telemetry.mjs", "grunt-config.mjs"]
77
+ /** Repo-relative paths grunt used to ship. Deleted on init/upgrade. Consumer extras elsewhere kept. */
78
+ export const RETIRED_PATHS = [
79
+ ".grok/parent.md",
80
+ ".grok/skills/shared",
81
+ ".rulesync/grunt.config.jsonc",
82
+ ".rulesync/grunt.config.local.jsonc",
83
+ ".rulesync/grunt.config.local.jsonc.example",
84
+ ]
85
+ export const RESERVED_SKILLS = [
86
+ "ask",
87
+ "auto",
88
+ "browser",
89
+ "clasp",
90
+ "commit",
91
+ "commit-and-push",
92
+ "commit-push",
93
+ "commit-push-deploy",
94
+ "commit-push-release",
95
+ "explain",
96
+ "google-workspace",
97
+ "handoff",
98
+ "implement-plan",
99
+ "listen",
100
+ "pickup",
101
+ "speak",
102
+ "tmp",
103
+ "write-plan",
104
+ ]
105
+ const SKILL_MIRROR_DIRS = [
106
+ [".rulesync", "skills"],
107
+ [".grok", "skills"],
108
+ [".claude", "skills"],
109
+ [".agents", "skills"],
110
+ ]
111
+ const AGENT_MIRROR_DIRS = [
112
+ [".rulesync", "subagents"],
113
+ [".claude", "agents"],
114
+ [".grok", "agents"],
115
+ [".agents", "agents"],
116
+ [".codex", "agents"],
117
+ ]
40
118
  const GUARDED_MD_FILES = ["AGENTS.md", "CLAUDE.md"]
41
- export const GUARDED_ROOT_FILES = ["AGENTS.md", "CLAUDE.md", "GEMINI.md"]
42
119
  // .mcp.json is intentionally not copied here: scripts/emit-mcp-policy.mjs
43
120
  // (run via consumer `grunt:rulesync:generate`, see mergeClaudeSettings ordering) owns and
44
121
  // merges it, so a plain-copy would just be clobbered/redundant.
@@ -54,16 +131,6 @@ export const LAUNCH_SCRIPTS = {
54
131
  grok: "grok --yolo",
55
132
  }
56
133
  const OWNED_HOOK_FILES = ["scrub-spawn-prompt.mjs", "gate-fat-tools.mjs", "orchestrate-parent.js"]
57
- export const SENTINEL_BEGIN = "<!-- grunt:begin -->"
58
- export const SENTINEL_END = "<!-- grunt:end -->"
59
- export const MAX_GUARDED_MARKDOWN_BYTES = 2 * 1024 * 1024
60
- const GRUNT_REGION_RE =
61
- /(?:^|(?<=\n)|(?<=\r\n))[ \t]*<!-- grunt:begin -->[ \t]*\r?\n?[\s\S]*?(?:^|(?<=\n)|(?<=\r\n))[ \t]*<!-- grunt:end -->[ \t]*\r?\n?/g
62
- const GRUNT_INTERIOR_RE =
63
- /(?:^|(?<=\n)|(?<=\r\n))[ \t]*<!-- grunt:begin -->[ \t]*\r?\n?([\s\S]*?)(?:^|(?<=\n)|(?<=\r\n))[ \t]*<!-- grunt:end -->/g
64
- const ORPHAN_END_RE = /(?:^|\r?\n)[ \t]*<!-- grunt:end -->[ \t]*(?=\r?\n|$)|<!-- grunt:end -->/g
65
- const BEGIN_LINE_RE = /^[ \t]*<!-- grunt:begin -->[ \t]*$/
66
- const END_LINE_RE = /^[ \t]*<!-- grunt:end -->[ \t]*$/
67
134
 
68
135
  function sortKeys(obj) {
69
136
  return Object.fromEntries(Object.keys(obj).sort().map((k) => [k, obj[k]]))
@@ -77,17 +144,58 @@ export function samePath(a, b) {
77
144
  }
78
145
  }
79
146
 
147
+ const GITIGNORE_ENTRIES = [{ re: /^\.tmp\/?$/, line: ".tmp/" }]
148
+
149
+ function rmQuiet(abs) {
150
+ fs.rmSync(abs, { recursive: true, force: true })
151
+ }
152
+
153
+ export function pruneRetired(dest, { pkgRoot } = {}) {
154
+ const root = path.resolve(dest)
155
+ for (const segs of SKILL_MIRROR_DIRS) {
156
+ for (const name of RETIRED_SKILLS) {
157
+ rmQuiet(path.join(root, ...segs, name))
158
+ }
159
+ }
160
+ if (pkgRoot) {
161
+ const packaged = new Set(
162
+ listSkillDirNames(path.join(path.resolve(pkgRoot), WORKSPACE_SKILLS_REL)),
163
+ )
164
+ for (const segs of SKILL_MIRROR_DIRS) {
165
+ for (const name of RESERVED_SKILLS) {
166
+ if (packaged.has(name)) continue
167
+ rmQuiet(path.join(root, ...segs, name))
168
+ }
169
+ }
170
+ }
171
+ for (const segs of AGENT_MIRROR_DIRS) {
172
+ for (const name of RETIRED_AGENTS) {
173
+ rmQuiet(path.join(root, ...segs, `${name}.md`))
174
+ rmQuiet(path.join(root, ...segs, `${name}.toml`))
175
+ }
176
+ }
177
+ for (const name of RETIRED_AGENTS) {
178
+ rmQuiet(path.join(root, ".gemini", "agents", name))
179
+ rmQuiet(path.join(root, ".grok", "roles", `${name}.toml`))
180
+ }
181
+ for (const name of RETIRED_SCRIPTS) {
182
+ rmQuiet(path.join(root, "scripts", name))
183
+ }
184
+ for (const rel of RETIRED_PATHS) {
185
+ rmQuiet(path.join(root, ...rel.split("/")))
186
+ }
187
+ }
188
+
80
189
  export function mergeGitignore(dest) {
81
190
  const gi = path.join(dest, ".gitignore")
82
- if (!fs.existsSync(gi)) {
83
- fs.writeFileSync(gi, ".tmp/\n")
84
- return
85
- }
86
- const text = fs.readFileSync(gi, "utf8")
87
- const has = text.split(/\r?\n/).some((line) => /^\.tmp\/?$/.test(line))
88
- if (has) return
89
- const prefix = text.endsWith("\n") || text.length === 0 ? "" : "\n"
90
- fs.appendFileSync(gi, `${prefix}.tmp/\n`)
191
+ const text = fs.existsSync(gi) ? fs.readFileSync(gi, "utf8") : ""
192
+ const present = text.split(/\r?\n/)
193
+ const missing = GITIGNORE_ENTRIES.filter(
194
+ ({ re }) => !present.some((line) => re.test(line)),
195
+ ).map(({ line }) => line)
196
+ if (missing.length === 0) return
197
+ const prefix = text.length === 0 || text.endsWith("\n") ? "" : "\n"
198
+ fs.writeFileSync(gi, `${text}${prefix}${missing.join("\n")}\n`)
91
199
  }
92
200
 
93
201
  function destHasGruntSentinel(dest) {
@@ -108,120 +216,6 @@ export function shouldAutoSkipGlobals(dest) {
108
216
  return destHasGruntSentinel(dest)
109
217
  }
110
218
 
111
- function detectNewline(text) {
112
- return String(text).includes("\r\n") ? "\r\n" : "\n"
113
- }
114
-
115
- function toNewline(text, nl) {
116
- return String(text).replace(/\r\n/g, "\n").replace(/\n/g, nl)
117
- }
118
-
119
- function trimTrailingNewlines(text) {
120
- return String(text).replace(/(?:\r?\n)+$/, "")
121
- }
122
-
123
- function normEq(a, b) {
124
- return trimTrailingNewlines(String(a).replace(/\r\n/g, "\n")) ===
125
- trimTrailingNewlines(String(b).replace(/\r\n/g, "\n"))
126
- }
127
-
128
- function cloneRe(re) {
129
- return new RegExp(re.source, re.flags)
130
- }
131
-
132
- function inspectGuardedBuffer(buf) {
133
- if (buf.length > MAX_GUARDED_MARKDOWN_BYTES) return "huge"
134
- if (buf.includes(0)) return "binary"
135
- return null
136
- }
137
-
138
- export function extractUserMarkdown(text) {
139
- let user = String(text).replace(cloneRe(GRUNT_REGION_RE), "")
140
- const i = user.indexOf(SENTINEL_BEGIN)
141
- if (i >= 0) user = user.slice(0, i)
142
- user = user.replace(cloneRe(ORPHAN_END_RE), "")
143
- return userRemainderIsBlank(user) ? "" : user
144
- }
145
-
146
- function unwrapStackedMarkers(text) {
147
- const lines = String(text).split(/\r?\n/)
148
- let start = 0
149
- let end = lines.length
150
- while (start < end && BEGIN_LINE_RE.test(lines[start])) start++
151
- while (end > start && lines[end - 1] === "") end--
152
- while (end > start && END_LINE_RE.test(lines[end - 1])) {
153
- end--
154
- while (end > start && lines[end - 1] === "") end--
155
- }
156
- return lines.slice(start, end).join("\n")
157
- }
158
-
159
- export function extractGruntBody(text) {
160
- const src = String(text)
161
- const parts = []
162
- const re = cloneRe(GRUNT_INTERIOR_RE)
163
- let m
164
- while ((m = re.exec(src))) {
165
- parts.push(trimTrailingNewlines(unwrapStackedMarkers(m[1])))
166
- }
167
- if (parts.length) return parts.join("\n")
168
- const i = src.indexOf(SENTINEL_BEGIN)
169
- if (i >= 0) {
170
- return trimTrailingNewlines(
171
- unwrapStackedMarkers(src.slice(i + SENTINEL_BEGIN.length).replace(/^[ \t]*\r?\n/, "")),
172
- )
173
- }
174
- if (!src.includes(SENTINEL_END)) return null
175
- return trimTrailingNewlines(unwrapStackedMarkers(src))
176
- }
177
-
178
- function userRemainderIsBlank(user) {
179
- return String(user).replace(/\r\n/g, "\n").replace(/\n/g, "") === ""
180
- }
181
-
182
- export function composeGuardedMarkdown(gruntBody, userRemainder, nl = "\n") {
183
- const raw = extractGruntBody(gruntBody) ?? gruntBody
184
- const body = toNewline(trimTrailingNewlines(raw), nl)
185
- const block = `${SENTINEL_BEGIN}${nl}${body}${nl}${SENTINEL_END}${nl}`
186
- const user = extractUserMarkdown(userRemainder == null ? "" : String(userRemainder))
187
- if (!user || userRemainderIsBlank(user)) return block
188
- return block + toNewline(user, nl)
189
- }
190
-
191
- export function mergeGuardedContent(existingText, gruntBody) {
192
- const existing = existingText == null ? "" : String(existingText)
193
- const nl = existing.includes("\r\n") || String(gruntBody).includes("\r\n")
194
- ? detectNewline(existing || gruntBody)
195
- : "\n"
196
- if (!existing) return composeGuardedMarkdown(gruntBody, "", nl)
197
- const hadSentinel = existing.includes(SENTINEL_BEGIN) && existing.includes(SENTINEL_END)
198
- const user = extractUserMarkdown(existing)
199
- if (!hadSentinel && normEq(existing, gruntBody)) {
200
- return composeGuardedMarkdown(gruntBody, "", nl)
201
- }
202
- return composeGuardedMarkdown(gruntBody, user, nl)
203
- }
204
-
205
- export function writeMergedGuardedFile(destPath, gruntBody) {
206
- if (!fs.existsSync(destPath)) {
207
- fs.writeFileSync(destPath, mergeGuardedContent("", gruntBody))
208
- return
209
- }
210
- const buf = fs.readFileSync(destPath)
211
- if (inspectGuardedBuffer(buf)) return "aborted-unsafe"
212
- fs.writeFileSync(destPath, mergeGuardedContent(buf.toString("utf8"), gruntBody))
213
- }
214
-
215
- export function guardedMarkdownDrift(destPath, gruntBody) {
216
- if (!fs.existsSync(destPath)) return true
217
- const buf = fs.readFileSync(destPath)
218
- if (inspectGuardedBuffer(buf)) return true
219
- const text = buf.toString("utf8")
220
- const interior = extractGruntBody(text)
221
- if (interior == null) return !normEq(text, gruntBody)
222
- return !normEq(interior, gruntBody)
223
- }
224
-
225
219
  export function mergeGuardedMarkdown(dest, pkgRoot, file, { alreadyInited = false } = {}) {
226
220
  void alreadyInited
227
221
  const src = path.join(pkgRoot, file)
@@ -231,104 +225,6 @@ export function mergeGuardedMarkdown(dest, pkgRoot, file, { alreadyInited = fals
231
225
  return writeMergedGuardedFile(d, extractGruntBody(srcText) ?? srcText)
232
226
  }
233
227
 
234
- export function snapshotGuardedRoots(root) {
235
- const snap = {}
236
- for (const file of GUARDED_ROOT_FILES) {
237
- const p = path.join(root, file)
238
- if (!fs.existsSync(p)) continue
239
- const buf = fs.readFileSync(p)
240
- const unsafe = inspectGuardedBuffer(buf)
241
- if (unsafe) {
242
- snap[file] = { unsafe, rawBuf: buf }
243
- continue
244
- }
245
- const raw = buf.toString("utf8")
246
- snap[file] = { raw, user: extractUserMarkdown(raw) }
247
- }
248
- return snap
249
- }
250
-
251
- export function remergeGuardedRoots(root, snap) {
252
- for (const file of GUARDED_ROOT_FILES) {
253
- const p = path.join(root, file)
254
- const rec = snap[file]
255
- if (rec?.unsafe) {
256
- fs.writeFileSync(p, rec.rawBuf)
257
- continue
258
- }
259
- if (!fs.existsSync(p)) {
260
- if (rec?.raw != null) fs.writeFileSync(p, rec.raw)
261
- continue
262
- }
263
- const buf = fs.readFileSync(p)
264
- if (inspectGuardedBuffer(buf)) {
265
- if (rec?.raw != null) fs.writeFileSync(p, rec.raw)
266
- continue
267
- }
268
- const current = buf.toString("utf8")
269
- if (rec && current === rec.raw) continue
270
- const gruntBody = extractGruntBody(current) ?? current
271
- const nl = detectNewline((rec && rec.raw) || current)
272
- let user = rec ? rec.user : extractUserMarkdown(current)
273
- if (rec && !rec.raw.includes(SENTINEL_BEGIN) && normEq(rec.raw, gruntBody)) user = ""
274
- if (!rec && extractGruntBody(current) == null && normEq(current, gruntBody)) user = ""
275
- fs.writeFileSync(p, composeGuardedMarkdown(gruntBody, user, nl))
276
- }
277
- }
278
-
279
- export function healGuardedRootFile(root, file, snap) {
280
- if (!GUARDED_ROOT_FILES.includes(file)) return
281
- const p = path.join(root, file)
282
- const rec = snap[file]
283
- if (!fs.existsSync(p)) return
284
- if (rec?.unsafe) {
285
- fs.writeFileSync(p, rec.rawBuf)
286
- return
287
- }
288
- const buf = fs.readFileSync(p)
289
- if (inspectGuardedBuffer(buf)) {
290
- if (rec?.raw != null) fs.writeFileSync(p, rec.raw)
291
- return
292
- }
293
- const current = buf.toString("utf8")
294
- const interior = extractGruntBody(current)
295
- if (interior != null) {
296
- snap[file] = { raw: current, user: extractUserMarkdown(current) }
297
- return
298
- }
299
- if (rec && current === rec.raw) return
300
- const gruntBody = current
301
- const nl = detectNewline((rec && rec.raw) || current)
302
- let user = rec ? rec.user : extractUserMarkdown(current)
303
- if (rec && !rec.raw.includes(SENTINEL_BEGIN) && normEq(rec.raw, gruntBody)) user = ""
304
- if (!rec && normEq(current, gruntBody)) user = ""
305
- const out = composeGuardedMarkdown(gruntBody, user, nl)
306
- fs.writeFileSync(p, out)
307
- snap[file] = { raw: out, user }
308
- }
309
-
310
- export function withGuardedCheckInteriors(root, fn) {
311
- const snap = snapshotGuardedRoots(root)
312
- try {
313
- for (const file of GUARDED_ROOT_FILES) {
314
- const rec = snap[file]
315
- if (!rec || rec.unsafe) continue
316
- const body = extractGruntBody(rec.raw)
317
- if (body == null) continue
318
- const nl = detectNewline(rec.raw)
319
- fs.writeFileSync(path.join(root, file), `${toNewline(trimTrailingNewlines(body), nl)}${nl}`)
320
- }
321
- return fn()
322
- } finally {
323
- for (const file of GUARDED_ROOT_FILES) {
324
- const rec = snap[file]
325
- if (!rec) continue
326
- if (rec.unsafe) fs.writeFileSync(path.join(root, file), rec.rawBuf)
327
- else fs.writeFileSync(path.join(root, file), rec.raw)
328
- }
329
- }
330
- }
331
-
332
228
  export function mergeClaudeSettings(destRoot, pkgRoot) {
333
229
  const srcPath = path.join(pkgRoot, ".claude", "settings.json")
334
230
  const destPath = path.join(destRoot, ".claude", "settings.json")
@@ -485,6 +381,8 @@ export function mergePackageJson(dest, pkgRoot) {
485
381
  destPkg.devDependencies = { ...(destPkg.devDependencies || {}) }
486
382
  destPkg.devDependencies["smol-toml"] = srcPkg.devDependencies["smol-toml"]
487
383
  destPkg.devDependencies["rulesync"] = srcPkg.devDependencies["rulesync"]
384
+ const clack = srcPkg.dependencies?.["@clack/prompts"]
385
+ if (clack) destPkg.devDependencies["@clack/prompts"] = clack
488
386
  destPkg.scripts = sortKeys(destPkg.scripts)
489
387
  destPkg.devDependencies = sortKeys(destPkg.devDependencies)
490
388
  fs.writeFileSync(destPath, `${JSON.stringify(destPkg, null, 2)}\n`)
@@ -536,6 +434,8 @@ export function init(dest, { pkgRoot: pkgRootOpt, execFileSync: exec = execFileS
536
434
  }
537
435
  }
538
436
 
437
+ pruneRetired(dest, { pkgRoot })
438
+
539
439
  for (const file of GUARDED_MD_FILES) {
540
440
  mergeGuardedMarkdown(dest, pkgRoot, file)
541
441
  }