@lovrozagar/grunt 0.2.0 → 0.3.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/grunt.md +4 -2
- package/.agents/agents/orchestrator.md +2 -2
- package/.agents/hooks.json +12 -1
- package/.agents/skills/commit/SKILL.md +21 -0
- package/.agents/skills/commit-and-push/SKILL.md +15 -0
- package/.agents/skills/explain/SKILL.md +14 -0
- package/.agents/skills/handoff/SKILL.md +84 -0
- package/.agents/skills/parent/SKILL.md +11 -0
- package/.agents/skills/solo/SKILL.md +21 -0
- package/.claude/agents/grunt.md +2 -0
- package/.claude/agents/orchestrator.md +3 -3
- package/.claude/settings.json +34 -1
- package/.claude/skills/commit/SKILL.md +21 -0
- package/.claude/skills/commit-and-push/SKILL.md +15 -0
- package/.claude/skills/explain/SKILL.md +14 -0
- package/.claude/skills/handoff/SKILL.md +84 -0
- package/.claude/skills/parent/SKILL.md +11 -0
- package/.claude/skills/solo/SKILL.md +21 -0
- package/.codex/agents/grunt.toml +4 -2
- package/.codex/agents/orchestrator.toml +2 -2
- package/.codex/hooks.json +34 -1
- package/.grok/agents/grunt.md +4 -2
- package/.grok/agents/orchestrator.md +2 -2
- package/.grok/hooks/orchestrate-parent.js +263 -29
- package/.grok/skills/commit/SKILL.md +21 -0
- package/.grok/skills/commit-and-push/SKILL.md +15 -0
- package/.grok/skills/handoff/SKILL.md +84 -0
- package/.grok/skills/solo/SKILL.md +21 -0
- package/.rulesync/hooks.jsonc +11 -3
- package/.rulesync/reference/cascade.md +4 -3
- package/.rulesync/reference/hooks.md +5 -1
- package/.rulesync/reference/map.md +18 -3
- package/.rulesync/rules/CLAUDE.md +2 -1
- package/.rulesync/rules/overview.md +2 -1
- package/.rulesync/skills/commit/SKILL.md +22 -0
- package/.rulesync/skills/commit-and-push/SKILL.md +16 -0
- package/.rulesync/skills/handoff/SKILL.md +84 -0
- package/.rulesync/skills/solo/SKILL.md +21 -0
- package/.rulesync/subagents/grunt.md +6 -2
- package/.rulesync/subagents/implementer.md +2 -0
- package/.rulesync/subagents/orchestrator.md +4 -2
- package/.rulesync/subagents/thinker.md +2 -0
- package/AGENTS.md +2 -1
- package/CLAUDE.md +2 -1
- package/README.md +2 -2
- package/cli/init.mjs +105 -6
- package/cli/init.test.ts +303 -6
- package/package.json +12 -6
- package/scripts/emit-agent-shell-tools.mjs +118 -0
- package/scripts/emit-gemini.mjs +219 -0
- package/scripts/emit-mcp-policy.mjs +23 -0
- package/scripts/gate-fat-tools.mjs +64 -0
- package/scripts/hooks-union.mjs +391 -0
- package/scripts/persist-handoff.mjs +231 -0
- package/scripts/scrub-spawn-prompt.mjs +37 -17
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/** Emit Gemini CLI GEMINI.md + .gemini/agents from .rulesync/subagents. */
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
6
|
+
|
|
7
|
+
export const AGENT_IDS = ["orchestrator", "implementer", "thinker", "grunt"];
|
|
8
|
+
const SSOT_REL = ".rulesync/subagents";
|
|
9
|
+
const GEMINI_MD_REL = "GEMINI.md";
|
|
10
|
+
const AGENTS_REL = ".gemini/agents";
|
|
11
|
+
const GEMINI_MD_BODY = "@AGENTS.md\n";
|
|
12
|
+
|
|
13
|
+
function isPlainObject(v) {
|
|
14
|
+
return v != null && typeof v === "object" && !Array.isArray(v);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function parseArgv(argv) {
|
|
18
|
+
const args = Array.isArray(argv) ? argv : [];
|
|
19
|
+
let check = false;
|
|
20
|
+
for (const a of args) {
|
|
21
|
+
if (a === "--check") {
|
|
22
|
+
check = true;
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
return { ok: false, error: `unknown flag: ${a}` };
|
|
26
|
+
}
|
|
27
|
+
return { ok: true, check };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function unquoteYaml(raw) {
|
|
31
|
+
const s = String(raw).trim();
|
|
32
|
+
if (s.length >= 2 && s[0] === '"' && s[s.length - 1] === '"') {
|
|
33
|
+
try {
|
|
34
|
+
return JSON.parse(s);
|
|
35
|
+
} catch {
|
|
36
|
+
return s.slice(1, -1);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (s.length >= 2 && s[0] === "'" && s[s.length - 1] === "'") {
|
|
40
|
+
return s.slice(1, -1).replace(/''/g, "'");
|
|
41
|
+
}
|
|
42
|
+
return s;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function yamlScalar(v) {
|
|
46
|
+
const s = String(v);
|
|
47
|
+
if (s === "") return '""';
|
|
48
|
+
if (/^[A-Za-z0-9_./+-]+$/.test(s)) return s;
|
|
49
|
+
return JSON.stringify(s);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function splitFrontmatter(text) {
|
|
53
|
+
const src = String(text ?? "");
|
|
54
|
+
if (!src.startsWith("---")) return { ok: false, error: "missing frontmatter" };
|
|
55
|
+
const rest = src.slice(3).replace(/^\r?\n/, "");
|
|
56
|
+
const m = rest.match(/\r?\n---\s*(?:\r?\n|$)/);
|
|
57
|
+
if (!m || m.index == null) return { ok: false, error: "missing frontmatter close" };
|
|
58
|
+
return { ok: true, fm: rest.slice(0, m.index), body: rest.slice(m.index + m[0].length) };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function parseYamlBlock(fm) {
|
|
62
|
+
const top = {};
|
|
63
|
+
let cur = null;
|
|
64
|
+
for (const line of String(fm).split(/\r?\n/)) {
|
|
65
|
+
if (!line.trim() || /^\s*#/.test(line)) continue;
|
|
66
|
+
const nested = line.match(/^([ \t]+)([^:#\s][^:]*):\s*(.*?)\s*$/);
|
|
67
|
+
if (nested && cur) {
|
|
68
|
+
const key = nested[2].trim();
|
|
69
|
+
const val = nested[3];
|
|
70
|
+
if (!isPlainObject(top[cur])) top[cur] = {};
|
|
71
|
+
if (val !== "") top[cur][key] = unquoteYaml(val);
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
const topm = line.match(/^([^:#\s][^:]*):\s*(.*?)\s*$/);
|
|
75
|
+
if (topm && !/^[ \t]/.test(line)) {
|
|
76
|
+
const key = topm[1].trim();
|
|
77
|
+
const val = topm[2];
|
|
78
|
+
cur = key;
|
|
79
|
+
top[key] = val === "" ? {} : unquoteYaml(val);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return top;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function geminiModel(parsed) {
|
|
86
|
+
if (parsed["geminicli.model"] != null && parsed["geminicli.model"] !== "") {
|
|
87
|
+
return String(parsed["geminicli.model"]);
|
|
88
|
+
}
|
|
89
|
+
if (isPlainObject(parsed.geminicli) && parsed.geminicli.model != null && parsed.geminicli.model !== "") {
|
|
90
|
+
return String(parsed.geminicli.model);
|
|
91
|
+
}
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function readText(abs) {
|
|
96
|
+
try {
|
|
97
|
+
return fs.readFileSync(abs, "utf8");
|
|
98
|
+
} catch (err) {
|
|
99
|
+
if (err && err.code === "ENOENT") return null;
|
|
100
|
+
throw err;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function lstat(abs) {
|
|
105
|
+
try {
|
|
106
|
+
return fs.lstatSync(abs);
|
|
107
|
+
} catch (err) {
|
|
108
|
+
if (err && err.code === "ENOENT") return null;
|
|
109
|
+
throw err;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function agentMarkdown(name, description, model, body) {
|
|
114
|
+
const fm = ["---", `name: ${yamlScalar(name)}`, `description: ${yamlScalar(description)}`, `model: ${yamlScalar(model)}`, "---"].join(
|
|
115
|
+
"\n",
|
|
116
|
+
);
|
|
117
|
+
const b = body.endsWith("\n") || body === "" ? body : `${body}\n`;
|
|
118
|
+
return `${fm}\n${b}`;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function collisionRel(id) {
|
|
122
|
+
return path.join(AGENTS_REL, `${id}.md`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function ownedRel(id) {
|
|
126
|
+
return path.join(AGENTS_REL, id, "agent.md");
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function emitGemini({ workspaceRoot, check = false } = {}) {
|
|
130
|
+
const ws = workspaceRoot || process.cwd();
|
|
131
|
+
const collisions = [];
|
|
132
|
+
for (const id of AGENT_IDS) {
|
|
133
|
+
const flat = path.join(ws, collisionRel(id));
|
|
134
|
+
const dir = path.join(ws, AGENTS_REL, id);
|
|
135
|
+
const stFlat = lstat(flat);
|
|
136
|
+
if (stFlat && stFlat.isFile()) collisions.push(collisionRel(id));
|
|
137
|
+
const stDir = lstat(dir);
|
|
138
|
+
if (stDir && stDir.isFile()) collisions.push(path.join(AGENTS_REL, id));
|
|
139
|
+
}
|
|
140
|
+
if (collisions.length) {
|
|
141
|
+
return {
|
|
142
|
+
ok: false,
|
|
143
|
+
check,
|
|
144
|
+
error: `collision: ${collisions.join(", ")}`,
|
|
145
|
+
collisions,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const files = [{ rel: GEMINI_MD_REL, text: GEMINI_MD_BODY }];
|
|
150
|
+
for (const id of AGENT_IDS) {
|
|
151
|
+
const ssotAbs = path.join(ws, SSOT_REL, `${id}.md`);
|
|
152
|
+
const raw = readText(ssotAbs);
|
|
153
|
+
if (raw == null) {
|
|
154
|
+
return { ok: false, check, error: `missing ${path.join(SSOT_REL, `${id}.md`)}` };
|
|
155
|
+
}
|
|
156
|
+
const split = splitFrontmatter(raw);
|
|
157
|
+
if (!split.ok) {
|
|
158
|
+
return { ok: false, check, error: `${id}: ${split.error}` };
|
|
159
|
+
}
|
|
160
|
+
const parsed = parseYamlBlock(split.fm);
|
|
161
|
+
const model = geminiModel(parsed);
|
|
162
|
+
if (!model) {
|
|
163
|
+
return { ok: false, check, error: `${id}: missing geminicli.model` };
|
|
164
|
+
}
|
|
165
|
+
const name = parsed.name != null && parsed.name !== "" ? String(parsed.name) : id;
|
|
166
|
+
const description = parsed.description != null ? String(parsed.description) : "";
|
|
167
|
+
files.push({
|
|
168
|
+
rel: ownedRel(id),
|
|
169
|
+
text: agentMarkdown(name, description, model, split.body),
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (check) {
|
|
174
|
+
const drift = [];
|
|
175
|
+
for (const f of files) {
|
|
176
|
+
const disk = readText(path.join(ws, f.rel));
|
|
177
|
+
if (disk !== f.text) drift.push(f.rel);
|
|
178
|
+
}
|
|
179
|
+
if (drift.length) {
|
|
180
|
+
return { ok: false, check: true, error: `drift: ${drift.join(", ")}`, drift };
|
|
181
|
+
}
|
|
182
|
+
return { ok: true, check: true, drift: [] };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
for (const f of files) {
|
|
186
|
+
const abs = path.join(ws, f.rel);
|
|
187
|
+
fs.mkdirSync(path.dirname(abs), { recursive: true });
|
|
188
|
+
fs.writeFileSync(abs, f.text);
|
|
189
|
+
}
|
|
190
|
+
return { ok: true, check: false };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function main() {
|
|
194
|
+
try {
|
|
195
|
+
const parsed = parseArgv(process.argv.slice(2));
|
|
196
|
+
if (!parsed.ok) {
|
|
197
|
+
process.stderr.write((parsed.error || "invalid argv") + "\n");
|
|
198
|
+
return 1;
|
|
199
|
+
}
|
|
200
|
+
const result = emitGemini({
|
|
201
|
+
workspaceRoot: process.cwd(),
|
|
202
|
+
check: parsed.check,
|
|
203
|
+
});
|
|
204
|
+
if (!result.ok) {
|
|
205
|
+
process.stderr.write((result.error || "emit failed") + "\n");
|
|
206
|
+
return 1;
|
|
207
|
+
}
|
|
208
|
+
return 0;
|
|
209
|
+
} catch (err) {
|
|
210
|
+
process.stderr.write(String(err && err.message ? err.message : err) + "\n");
|
|
211
|
+
return 1;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const thisFile = fileURLToPath(import.meta.url);
|
|
216
|
+
const invoked = process.argv[1] ? path.resolve(process.argv[1]) : "";
|
|
217
|
+
if (invoked === thisFile || import.meta.url === pathToFileURL(invoked).href) {
|
|
218
|
+
process.exit(main());
|
|
219
|
+
}
|
|
@@ -11,6 +11,7 @@ const CLAUDE_SETTINGS_REL = ".claude/settings.json";
|
|
|
11
11
|
const MCP_JSON_REL = ".mcp.json";
|
|
12
12
|
const CODEX_REL = ".codex/config.toml";
|
|
13
13
|
const AGENTS_MCP_REL = ".agents/mcp_config.json";
|
|
14
|
+
const GEMINI_SETTINGS_REL = ".gemini/settings.json";
|
|
14
15
|
const AGENT_IDS = ["orchestrator", "implementer", "thinker", "grunt"];
|
|
15
16
|
const MCP_DENY = "mcp__*";
|
|
16
17
|
const AGENT_ORCHESTRATOR_DENY = "Agent(orchestrator)";
|
|
@@ -276,6 +277,17 @@ function desiredAgentsMcp(policy) {
|
|
|
276
277
|
return { mcpServers: pickServers(policy.servers, policy.allow) };
|
|
277
278
|
}
|
|
278
279
|
|
|
280
|
+
function desiredGeminiSettings(disk, policy) {
|
|
281
|
+
const obj = isPlainObject(disk) ? { ...disk } : {};
|
|
282
|
+
obj.mcpServers = pickServers(policy.servers, policy.allow);
|
|
283
|
+
return obj;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function geminiCheckOk(disk, desired) {
|
|
287
|
+
if (!isPlainObject(disk)) return false;
|
|
288
|
+
return deepEqual(disk.mcpServers, desired.mcpServers);
|
|
289
|
+
}
|
|
290
|
+
|
|
279
291
|
function grokCheckOk(disk, desired, policy) {
|
|
280
292
|
if (!isPlainObject(disk)) return false;
|
|
281
293
|
if (Object.prototype.hasOwnProperty.call(disk, "features")) return false;
|
|
@@ -337,24 +349,28 @@ export function emitMcpPolicy({ workspaceRoot, check = false } = {}) {
|
|
|
337
349
|
const mcpAbs = path.join(ws, MCP_JSON_REL);
|
|
338
350
|
const codexAbs = path.join(ws, CODEX_REL);
|
|
339
351
|
const agentsAbs = path.join(ws, AGENTS_MCP_REL);
|
|
352
|
+
const geminiAbs = path.join(ws, GEMINI_SETTINGS_REL);
|
|
340
353
|
|
|
341
354
|
const grokRaw = readText(grokAbs);
|
|
342
355
|
const claudeRaw = readText(claudeAbs);
|
|
343
356
|
const mcpRaw = readText(mcpAbs);
|
|
344
357
|
const codexRaw = readText(codexAbs);
|
|
345
358
|
const agentsRaw = readText(agentsAbs);
|
|
359
|
+
const geminiRaw = readText(geminiAbs);
|
|
346
360
|
|
|
347
361
|
const grokDisk = parseTomlFile(grokRaw, {});
|
|
348
362
|
const claudeDisk = parseJsonFile(claudeRaw, {});
|
|
349
363
|
const mcpDisk = mcpRaw == null ? null : parseJsonFile(mcpRaw, {});
|
|
350
364
|
const codexDisk = parseTomlFile(codexRaw, {});
|
|
351
365
|
const agentsDisk = agentsRaw == null ? null : parseJsonFile(agentsRaw, {});
|
|
366
|
+
const geminiDisk = geminiRaw == null ? null : parseJsonFile(geminiRaw, {});
|
|
352
367
|
|
|
353
368
|
const grokDesired = desiredGrok(grokDisk, policy);
|
|
354
369
|
const claudeDesired = desiredClaudeSettings(claudeDisk, policy);
|
|
355
370
|
const mcpDesired = desiredMcpJson(policy);
|
|
356
371
|
const codexDesired = desiredCodex(codexDisk, policy);
|
|
357
372
|
const agentsDesired = desiredAgentsMcp(policy);
|
|
373
|
+
const geminiDesired = desiredGeminiSettings(geminiDisk, policy);
|
|
358
374
|
|
|
359
375
|
if (check) {
|
|
360
376
|
const drift = [];
|
|
@@ -371,6 +387,7 @@ export function emitMcpPolicy({ workspaceRoot, check = false } = {}) {
|
|
|
371
387
|
: !Object.prototype.hasOwnProperty.call(codexDisk, "mcp_servers"));
|
|
372
388
|
if (!codexOk) drift.push(CODEX_REL);
|
|
373
389
|
if (agentsDisk == null || !deepEqual(agentsDisk, agentsDesired)) drift.push(AGENTS_MCP_REL);
|
|
390
|
+
if (geminiDisk == null || !geminiCheckOk(geminiDisk, geminiDesired)) drift.push(GEMINI_SETTINGS_REL);
|
|
374
391
|
if (drift.length) {
|
|
375
392
|
return { ok: false, check: true, error: `drift: ${drift.join(", ")}`, drift };
|
|
376
393
|
}
|
|
@@ -409,6 +426,12 @@ export function emitMcpPolicy({ workspaceRoot, check = false } = {}) {
|
|
|
409
426
|
nextText: jsonOut(agentsDesired),
|
|
410
427
|
diskRaw: agentsRaw,
|
|
411
428
|
});
|
|
429
|
+
writeOrCheck({
|
|
430
|
+
check: false,
|
|
431
|
+
abs: geminiAbs,
|
|
432
|
+
nextText: jsonOut(geminiDesired),
|
|
433
|
+
diskRaw: geminiRaw,
|
|
434
|
+
});
|
|
412
435
|
return { ok: true, check: false };
|
|
413
436
|
}
|
|
414
437
|
|
|
@@ -42,6 +42,14 @@ const READ_TOOLS = new Set(["read", "readfile"]);
|
|
|
42
42
|
const GREP_TOOLS = new Set(["grep", "grepsearch"]);
|
|
43
43
|
const GLOB_TOOLS = new Set(["glob", "listdir"]);
|
|
44
44
|
const BASH_TOOLS = new Set(["bash", "runterminalcommand"]);
|
|
45
|
+
const WRITE_TOOLS = new Set(["write", "edit", "searchreplace"]);
|
|
46
|
+
const WRITE_PATH_FIELDS = [
|
|
47
|
+
"path",
|
|
48
|
+
"file_path",
|
|
49
|
+
"filePath",
|
|
50
|
+
"target_file",
|
|
51
|
+
"targetFile",
|
|
52
|
+
];
|
|
45
53
|
const PATH_FIELDS = [
|
|
46
54
|
"path",
|
|
47
55
|
"file_path",
|
|
@@ -124,6 +132,47 @@ export function hookResponse(updated) {
|
|
|
124
132
|
};
|
|
125
133
|
}
|
|
126
134
|
|
|
135
|
+
export function rewriteGruntScratchPath(filePath, workspaceRoot) {
|
|
136
|
+
if (filePath == null) return null;
|
|
137
|
+
const raw = String(filePath);
|
|
138
|
+
if (!raw || !workspaceRoot) return null;
|
|
139
|
+
const posix = raw.replace(/\\/g, "/");
|
|
140
|
+
const marker = ".tmp/grunt/";
|
|
141
|
+
const idx = posix.indexOf(marker);
|
|
142
|
+
if (idx === -1) return null;
|
|
143
|
+
|
|
144
|
+
const wsRoot = path.resolve(workspaceRoot);
|
|
145
|
+
const absIn = path.isAbsolute(raw)
|
|
146
|
+
? path.resolve(raw)
|
|
147
|
+
: path.resolve(wsRoot, raw);
|
|
148
|
+
const relWs = path.relative(wsRoot, absIn);
|
|
149
|
+
if (relWs !== "" && !relWs.startsWith("..") && !path.isAbsolute(relWs)) {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const rel = posix.slice(idx + marker.length);
|
|
154
|
+
if (!rel) return null;
|
|
155
|
+
if (path.posix.isAbsolute(rel) || rel.startsWith("/")) return null;
|
|
156
|
+
const relNorm = path.posix.normalize(rel);
|
|
157
|
+
if (
|
|
158
|
+
!relNorm ||
|
|
159
|
+
relNorm === "." ||
|
|
160
|
+
relNorm === ".." ||
|
|
161
|
+
relNorm.startsWith("../") ||
|
|
162
|
+
rel.split("/").includes("..") ||
|
|
163
|
+
relNorm.split("/").includes("..")
|
|
164
|
+
) {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
if (path.posix.isAbsolute(relNorm)) return null;
|
|
168
|
+
|
|
169
|
+
const destRoot = path.resolve(wsRoot, ".tmp", "grunt");
|
|
170
|
+
const dest = path.resolve(destRoot, relNorm);
|
|
171
|
+
const check = path.relative(destRoot, dest);
|
|
172
|
+
if (!check || check.startsWith("..") || path.isAbsolute(check)) return null;
|
|
173
|
+
return dest;
|
|
174
|
+
}
|
|
175
|
+
|
|
127
176
|
export function workspaceRootOf(data) {
|
|
128
177
|
return (
|
|
129
178
|
process.env.GROK_WORKSPACE_ROOT ||
|
|
@@ -337,6 +386,21 @@ export function processFatTools(data) {
|
|
|
337
386
|
const toolKey = eventKey(data.toolName || data.tool_name || "");
|
|
338
387
|
const input = toolInputOf(data);
|
|
339
388
|
if (!input) return null;
|
|
389
|
+
const isWrite = WRITE_TOOLS.has(toolKey);
|
|
390
|
+
if (isWrite) {
|
|
391
|
+
const ws = workspaceRootOf(data);
|
|
392
|
+
const next = Object.assign({}, input);
|
|
393
|
+
let changed = false;
|
|
394
|
+
for (const key of WRITE_PATH_FIELDS) {
|
|
395
|
+
if (typeof next[key] !== "string" || !next[key]) continue;
|
|
396
|
+
const dest = rewriteGruntScratchPath(next[key], ws);
|
|
397
|
+
if (dest && dest !== next[key]) {
|
|
398
|
+
next[key] = dest;
|
|
399
|
+
changed = true;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
return changed ? { type: "rewrite", updatedInput: next } : null;
|
|
403
|
+
}
|
|
340
404
|
const sub = subagentTypeOf(data);
|
|
341
405
|
if (sub === "grunt") return null;
|
|
342
406
|
|