@lovrozagar/grunt 0.5.2 → 0.6.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 (153) 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 +39 -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 +56 -0
  105. package/CLAUDE.md +1 -49
  106. package/README.md +88 -80
  107. package/cli/grunt.mjs +18 -2
  108. package/cli/grunt.test.ts +33 -0
  109. package/cli/init.mjs +103 -10
  110. package/cli/init.test.ts +147 -35
  111. package/cli/prompt.mjs +8 -0
  112. package/cli/prompt.test.ts +14 -0
  113. package/package.json +7 -2
  114. package/scripts/browser.mjs +98 -5
  115. package/scripts/doctor.mjs +82 -1
  116. package/scripts/emit-gemini.mjs +1 -1
  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/listen.mjs +829 -0
  122. package/scripts/parse-need.mjs +21 -2
  123. package/scripts/persist-implementation.mjs +278 -0
  124. package/scripts/scrub-spawn-prompt.mjs +3 -50
  125. package/scripts/setup.mjs +746 -0
  126. package/scripts/speak.mjs +291 -0
  127. package/.agents/agents/implementer.md +0 -32
  128. package/.agents/agents/thinker.md +0 -29
  129. package/.agents/skills/cascade/SKILL.md +0 -28
  130. package/.agents/skills/parent/SKILL.md +0 -6
  131. package/.agents/skills/solo/SKILL.md +0 -25
  132. package/.claude/agents/implementer.md +0 -37
  133. package/.claude/agents/thinker.md +0 -36
  134. package/.claude/skills/cascade/SKILL.md +0 -28
  135. package/.claude/skills/parent/SKILL.md +0 -6
  136. package/.claude/skills/solo/SKILL.md +0 -25
  137. package/.codex/agents/implementer.toml +0 -26
  138. package/.codex/agents/thinker.toml +0 -26
  139. package/.grok/agents/implementer.md +0 -27
  140. package/.grok/agents/thinker.md +0 -27
  141. package/.grok/parent.md +0 -1
  142. package/.grok/roles/implementer.toml +0 -5
  143. package/.grok/roles/thinker.toml +0 -5
  144. package/.grok/skills/cascade/SKILL.md +0 -28
  145. package/.grok/skills/parent/SKILL.md +0 -6
  146. package/.grok/skills/solo/SKILL.md +0 -25
  147. package/.rulesync/grunt.config.jsonc +0 -7
  148. package/.rulesync/skills/cascade/SKILL.md +0 -28
  149. package/.rulesync/skills/parent/SKILL.md +0 -6
  150. package/.rulesync/skills/solo/SKILL.md +0 -25
  151. package/.rulesync/subagents/implementer.md +0 -49
  152. package/.rulesync/subagents/thinker.md +0 -49
  153. package/scripts/grunt-config.mjs +0 -50
@@ -4,7 +4,7 @@ import fs from "node:fs";
4
4
  import path from "node:path";
5
5
  import { fileURLToPath, pathToFileURL } from "node:url";
6
6
 
7
- export const NEED_JOBS = new Set(["search", "exec", "web", "test"]);
7
+ export const NEED_JOBS = new Set(["search", "exec", "web", "test", "slice", "fetch"]);
8
8
 
9
9
  export function parseNeed(text) {
10
10
  const s = String(text ?? "");
@@ -28,10 +28,20 @@ export function parseNeed(text) {
28
28
  }
29
29
  const job = String(item.job ?? "").trim();
30
30
  const query = String(item.query ?? "").trim();
31
- if (!NEED_JOBS.has(job) || !query) {
31
+ const stash = item.stash != null ? String(item.stash).trim() : "";
32
+ if (!NEED_JOBS.has(job)) {
33
+ return { ok: false, error: "invalid job or query" };
34
+ }
35
+ if (job === "slice") {
36
+ if (!stash && !query) {
37
+ return { ok: false, error: "invalid job or query" };
38
+ }
39
+ } else if (!query) {
32
40
  return { ok: false, error: "invalid job or query" };
33
41
  }
34
42
  const row = { job, query };
43
+ if (job === "slice" && stash) row.stash = stash;
44
+ else if (stash) row.stash = stash;
35
45
  if (item.path != null && String(item.path).trim()) {
36
46
  row.path = String(item.path).trim();
37
47
  }
@@ -43,6 +53,15 @@ export function parseNeed(text) {
43
53
  ? item.glob.map((g) => String(g))
44
54
  : [String(item.glob)];
45
55
  }
56
+ if (item.from != null && String(item.from).trim()) {
57
+ row.from = String(item.from).trim();
58
+ }
59
+ if (item.to != null && String(item.to).trim()) {
60
+ row.to = String(item.to).trim();
61
+ }
62
+ if (job === "slice" && !row.stash && query) {
63
+ row.stash = query;
64
+ }
46
65
  jobs.push(row);
47
66
  }
48
67
  return { ok: true, jobs };
@@ -0,0 +1,278 @@
1
+ #!/usr/bin/env node
2
+ /** Allocate/slugify/validate/write a session journal under .tmp/grunt/implementations/. */
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+ import { fileURLToPath, pathToFileURL } from "node:url";
6
+ import {
7
+ CREATED_RE,
8
+ DATETIME_RE,
9
+ SERIAL_LINE_RE,
10
+ compactCreatedStamp,
11
+ ensureGitignore,
12
+ nextSerial,
13
+ slugify,
14
+ utcDateTime,
15
+ } from "./persist-plan.mjs";
16
+
17
+ export const IMPLEMENTATION_DIR = ".tmp/grunt/implementations";
18
+ /** `{serial}-{slug}-{YYYYMMDDTHHMMSSZ}.md`; stamp required for new writes. */
19
+ export const FILENAME_RE = /^[0-9]+-[a-z0-9]+(-[a-z0-9]+)*-\d{8}T\d{6}Z\.md$/;
20
+ export const STATUS_RE = /^(in-progress|done|blocked)$/;
21
+ export const PLAN_VALUE_RE = /^(none|[1-9][0-9]*)$/;
22
+ export const FILENAME_STAMP_RE = /-\d{8}T\d{6}Z$/;
23
+ export const REQUIRED_H2 = ["Done", "Issue", "Files", "Log", "Blockers", "Notes"];
24
+ const META_LINE_RE = /^\s*IMPL_(NAME|PLAN):\s*.+\s*$/;
25
+ const NAME_RE = /^\s*IMPL_NAME:\s*(.+?)\s*$/m;
26
+ const PLAN_RE = /^\s*IMPL_PLAN:\s*(.+?)\s*$/m;
27
+
28
+ export function slugFromFilename(filename) {
29
+ return String(filename)
30
+ .replace(/\.md$/, "")
31
+ .replace(/^[0-9]+-/, "")
32
+ .replace(FILENAME_STAMP_RE, "");
33
+ }
34
+
35
+ export function extractImplName(content) {
36
+ const m = String(content).match(NAME_RE);
37
+ return m ? m[1].trim() : "";
38
+ }
39
+
40
+ export function extractImplPlan(content) {
41
+ const m = String(content).match(PLAN_RE);
42
+ if (!m) return "none";
43
+ const v = m[1].trim().toLowerCase();
44
+ if (v === "none" || v === "") return "none";
45
+ if (/^[1-9][0-9]*$/.test(v)) return v;
46
+ return "";
47
+ }
48
+
49
+ export function stripImplMeta(content) {
50
+ return String(content)
51
+ .replace(/^\uFEFF/, "")
52
+ .split(/\r?\n/)
53
+ .filter((line) => !META_LINE_RE.test(line))
54
+ .join("\n");
55
+ }
56
+
57
+ export function extractH1(content) {
58
+ const m = String(content).match(/^#\s+(.+?)\s*$/m);
59
+ return m ? m[1].trim() : "";
60
+ }
61
+
62
+ export function isFullImplementationBody(content) {
63
+ const s = String(content || "");
64
+ return /^---\n/.test(s) || /^# /m.test(s) || /^## Done\b/m.test(s);
65
+ }
66
+
67
+ function stripFrontmatter(text) {
68
+ const s = String(text).replace(/^\uFEFF/, "");
69
+ if (!s.startsWith("---\n")) return s;
70
+ const end = s.indexOf("\n---\n", 4);
71
+ if (end === -1) return s;
72
+ return s.slice(end + 5);
73
+ }
74
+
75
+ export function injectFrontmatter({ serial, slug, source, body, created, plan }) {
76
+ const src = String(source || "").slice(0, 120);
77
+ const planVal = PLAN_VALUE_RE.test(String(plan || "")) ? String(plan) : "none";
78
+ let rest = stripFrontmatter(body).replace(/^\s+/, "");
79
+ if (!rest.startsWith("# ")) {
80
+ rest = `# ${slug}\n\n` + rest;
81
+ } else {
82
+ rest = rest.replace(/^# .+/, `# ${slug}`);
83
+ }
84
+ return `---
85
+ serial: ${serial}
86
+ plan: ${planVal}
87
+ name: ${slug}
88
+ status: in-progress
89
+ created: ${created || utcDateTime()}
90
+ source: ${JSON.stringify(src)}
91
+ ---
92
+
93
+ ${rest}`;
94
+ }
95
+
96
+ function sectionBodies(text) {
97
+ const h2 = [...text.matchAll(/^## (.+)$/gm)].map((m) => ({
98
+ title: m[1].trim(),
99
+ index: m.index,
100
+ }));
101
+ const bodies = {};
102
+ for (let i = 0; i < h2.length; i++) {
103
+ const start = h2[i].index + h2[i].title.length + 4;
104
+ const end = i + 1 < h2.length ? h2[i + 1].index : text.length;
105
+ bodies[h2[i].title] = text.slice(start, end);
106
+ }
107
+ return { order: h2.map((h) => h.title), bodies };
108
+ }
109
+
110
+ function parseFrontmatter(text) {
111
+ if (!text.startsWith("---\n")) return { error: "missing frontmatter" };
112
+ const end = text.indexOf("\n---\n", 4);
113
+ if (end === -1) return { error: "unterminated frontmatter" };
114
+ const raw = text.slice(4, end);
115
+ const map = {};
116
+ for (const line of raw.split("\n")) {
117
+ const m = line.match(/^([a-z]+):(?:\s+(.*))?$/);
118
+ if (!m) continue;
119
+ let v = m[2] ?? "";
120
+ if (v.startsWith('"') && v.endsWith('"')) {
121
+ try {
122
+ v = JSON.parse(v);
123
+ } catch {
124
+ v = v.slice(1, -1);
125
+ }
126
+ }
127
+ map[m[1]] = v;
128
+ }
129
+ return { map, body: text.slice(end + 5) };
130
+ }
131
+
132
+ function bulletsOrNone(body, label) {
133
+ const b = String(body || "").trim();
134
+ if (!b) return `${label} empty`;
135
+ if (b === "(none)") return "";
136
+ const lines = b.split("\n").map((l) => l.trim()).filter(Boolean);
137
+ if (!lines.length) return `${label} empty`;
138
+ for (const line of lines) {
139
+ if (line === "(none)") continue;
140
+ if (label === "Log" && /^\d+\.\d+(?:\.\d+)? \[[ xX]\] /.test(line)) continue;
141
+ if (!/^[-*] /.test(line)) return `${label} bullets`;
142
+ }
143
+ return "";
144
+ }
145
+
146
+ function filesError(body) {
147
+ const b = String(body || "").trim();
148
+ if (!b) return "Files empty";
149
+ if (b === "(none)") return "";
150
+ for (const line of b.split("\n")) {
151
+ const t = line.trim();
152
+ if (!t || t === "(none)") continue;
153
+ if (!/^[-*] /.test(t)) return "Files bullets";
154
+ const p = t.replace(/^[-*] /, "").trim();
155
+ if (!p) return "Files bullets";
156
+ if (p.startsWith("/") || /^[A-Za-z]:/.test(p)) return "Files abs";
157
+ if (p.startsWith(".tmp/") || p.includes("/.tmp/")) return "Files tmp";
158
+ }
159
+ return "";
160
+ }
161
+
162
+ export function validateImplementation(filename, text) {
163
+ const errors = [];
164
+ if (!FILENAME_RE.test(filename)) errors.push("filename");
165
+ const fm = parseFrontmatter(text);
166
+ if (fm.error) {
167
+ errors.push(fm.error);
168
+ return errors;
169
+ }
170
+ const { map, body } = fm;
171
+ const serialLine = text.split("\n").find((l) => l.startsWith("serial:"));
172
+ if (!serialLine || !SERIAL_LINE_RE.test(serialLine)) errors.push("serial line");
173
+ for (const k of ["serial", "plan", "name", "status", "created", "source"]) {
174
+ if (map[k] == null || map[k] === "") errors.push(`missing ${k}`);
175
+ }
176
+ if (map.status && !STATUS_RE.test(map.status)) errors.push("status");
177
+ if (map.plan && !PLAN_VALUE_RE.test(map.plan)) errors.push("plan");
178
+ if (map.created && !CREATED_RE.test(map.created)) errors.push("created");
179
+ if (typeof map.source === "string" && map.source.length > 120) {
180
+ errors.push("source length");
181
+ }
182
+ const slug = map.name;
183
+ if (slug && slugFromFilename(filename) !== slug) errors.push("name/filename slug");
184
+ if (!body.trim().startsWith(`# ${slug}`)) errors.push("h1 slug");
185
+ const { order, bodies } = sectionBodies(body);
186
+ const first = order.slice(0, REQUIRED_H2.length);
187
+ if (REQUIRED_H2.some((h, i) => first[i] !== h)) errors.push("heading order");
188
+ const extra = order.filter((h) => !REQUIRED_H2.includes(h));
189
+ if (extra.length) errors.push("extra h2");
190
+ for (const h of ["Done", "Issue", "Log", "Blockers", "Notes"]) {
191
+ const err = bulletsOrNone(bodies[h], h);
192
+ if (err) errors.push(err);
193
+ }
194
+ const ferr = filesError(bodies.Files);
195
+ if (ferr) errors.push(ferr);
196
+ const blockers = (bodies.Blockers || "").trim();
197
+ if (map.status === "blocked" && blockers === "(none)") errors.push("blocked blockers");
198
+ return [...new Set(errors)];
199
+ }
200
+
201
+ export function persistImplementation({
202
+ workspaceRoot,
203
+ content,
204
+ source,
205
+ created,
206
+ } = {}) {
207
+ const ws = workspaceRoot || process.cwd();
208
+ const raw = String(content || "");
209
+ const name = extractImplName(raw) || extractH1(stripFrontmatter(stripImplMeta(raw)));
210
+ const slug = slugify(name);
211
+ const dir = path.join(ws, IMPLEMENTATION_DIR);
212
+ fs.mkdirSync(dir, { recursive: true });
213
+ const plan = extractImplPlan(raw);
214
+ if (raw.match(PLAN_RE) && !plan) return { ok: false, error: "plan", errors: ["plan"] };
215
+ const body = stripImplMeta(raw);
216
+ const createdAt = created && DATETIME_RE.test(created) ? created : utcDateTime();
217
+ const stamp = compactCreatedStamp(createdAt);
218
+ let serial = nextSerial(dir);
219
+ let filename = `${serial}-${slug}-${stamp}.md`;
220
+ let dest = path.join(dir, filename);
221
+ let tries = 0;
222
+ while (fs.existsSync(dest) && tries < 5) {
223
+ serial += 1;
224
+ filename = `${serial}-${slug}-${stamp}.md`;
225
+ dest = path.join(dir, filename);
226
+ tries += 1;
227
+ }
228
+ if (fs.existsSync(dest)) return { ok: false, error: "serial collision" };
229
+ const doneMatch = stripFrontmatter(body).match(/## Done\n([\s\S]*?)\n## /);
230
+ const sourceText = String(
231
+ source ||
232
+ name ||
233
+ (doneMatch ? doneMatch[1].trim().replace(/\s+/g, " ") : slug),
234
+ ).slice(0, 120);
235
+ const full = injectFrontmatter({
236
+ serial,
237
+ slug,
238
+ source: sourceText,
239
+ body,
240
+ created: createdAt,
241
+ plan,
242
+ });
243
+ const errors = validateImplementation(filename, full);
244
+ if (errors.length) return { ok: false, error: errors.join("; "), errors };
245
+ ensureGitignore(ws);
246
+ fs.writeFileSync(dest, full);
247
+ return { ok: true, path: dest, serial, slug, content: full, filename, plan };
248
+ }
249
+
250
+ function main() {
251
+ try {
252
+ const wsIdx = process.argv.indexOf("--workspace");
253
+ const workspaceRoot = wsIdx >= 0 ? process.argv[wsIdx + 1] : process.cwd();
254
+ const content = fs.readFileSync(0, "utf8");
255
+ const result = persistImplementation({ workspaceRoot, content });
256
+ if (!result.ok) {
257
+ process.stderr.write((result.error || "invalid implementation") + "\n");
258
+ return 1;
259
+ }
260
+ process.stdout.write(
261
+ JSON.stringify({
262
+ serial: result.serial,
263
+ path: result.path,
264
+ filename: result.filename,
265
+ }) + "\n",
266
+ );
267
+ return 0;
268
+ } catch (err) {
269
+ process.stderr.write(String(err && err.message ? err.message : err) + "\n");
270
+ return 1;
271
+ }
272
+ }
273
+
274
+ const thisFile = fileURLToPath(import.meta.url);
275
+ const invoked = process.argv[1] ? path.resolve(process.argv[1]) : "";
276
+ if (invoked === thisFile || import.meta.url === pathToFileURL(invoked).href) {
277
+ process.exit(main());
278
+ }
@@ -6,7 +6,7 @@ import { fileURLToPath, pathToFileURL } from "node:url";
6
6
  import { scrubText } from "./scrub-text-lib.mjs";
7
7
  import { denyResponse } from "./gate-fat-tools.mjs";
8
8
 
9
- const ALLOWED_TYPES = new Set(["grunt", "implementer", "thinker"]);
9
+ const ALLOWED_TYPES = new Set(["grunt"]);
10
10
  export const MAX_PROMPT_CHARS = 100000;
11
11
  export const TRUNCATE_SUFFIX = "…[truncated]";
12
12
  /** `router` is a legacy transcript prefix; keep matching it to strip old pastes. */
@@ -14,17 +14,12 @@ const TRANSCRIPT_PREFIX =
14
14
  /^\s*(?:\[(?:orchestrator|router|implementer|thinker|grunt)\]:|Human:|Assistant:)\s*/i;
15
15
  const YOU_ARE_SUBAGENT =
16
16
  /^\s*You are (?:grunt|implementer|thinker) subagent\b/;
17
- const VERDICT_LINE = /^\s*verdict:\s*/;
18
17
 
19
18
  export function stripTranscripts(text) {
20
19
  const lines = String(text).split(/\n/);
21
20
  const out = [];
22
21
  let keptYouAre = false;
23
22
  for (const line of lines) {
24
- if (VERDICT_LINE.test(line)) {
25
- out.push(line);
26
- continue;
27
- }
28
23
  if (TRANSCRIPT_PREFIX.test(line)) continue;
29
24
  if (YOU_ARE_SUBAGENT.test(line)) {
30
25
  if (keptYouAre) continue;
@@ -35,44 +30,6 @@ export function stripTranscripts(text) {
35
30
  return out.join("\n");
36
31
  }
37
32
 
38
- export function extractVerdictBlocks(text) {
39
- const rawLines = String(text).split(/\n/);
40
- const lines = [];
41
- for (const line of rawLines) {
42
- const idx = line.search(/(?:^|\s)verdict:\s/);
43
- if (idx > 0) {
44
- const before = line.slice(0, idx).trimEnd();
45
- const after = line.slice(idx).trimStart();
46
- if (before) lines.push(before);
47
- lines.push(after);
48
- } else {
49
- lines.push(line);
50
- }
51
- }
52
- const body = [];
53
- const blocks = [];
54
- let current = null;
55
- for (const line of lines) {
56
- if (VERDICT_LINE.test(line)) {
57
- if (current) blocks.push(current.join("\n"));
58
- current = [line];
59
- continue;
60
- }
61
- if (current) {
62
- if (current.length < 8) current.push(line);
63
- else {
64
- blocks.push(current.join("\n"));
65
- current = null;
66
- body.push(line);
67
- }
68
- continue;
69
- }
70
- body.push(line);
71
- }
72
- if (current) blocks.push(current.join("\n"));
73
- return { body: body.join("\n"), blocks };
74
- }
75
-
76
33
  export function truncatePrompt(text, _max = MAX_PROMPT_CHARS) {
77
34
  return String(text);
78
35
  }
@@ -113,14 +70,10 @@ export function stripInlineTranscripts(text) {
113
70
 
114
71
  export function capSpawnPrompt(prompt) {
115
72
  let s = stripTranscripts(String(prompt));
116
- const extracted = extractVerdictBlocks(s);
117
- s = scrubText(extracted.body, { intent: true });
73
+ s = scrubText(s, { intent: true });
118
74
  s = stripTranscripts(s);
119
75
  s = stripInlineTranscripts(s);
120
- const again = extractVerdictBlocks(s);
121
- const blocks = extracted.blocks.concat(again.blocks);
122
- const verdictPart = blocks.length ? "\n" + blocks.join("\n") : "";
123
- return (again.body + verdictPart).trim();
76
+ return s.trim();
124
77
  }
125
78
 
126
79
  const SPAWN_TYPE_KEYS = ["subagent_type", "subagentType", "name", "type", "agent"];