task-delegate 0.1.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.
- package/AGENTS.md +29 -0
- package/GITHUB_PROFILE_SETUP.md +59 -0
- package/LICENSE +52 -0
- package/NOTICE +7 -0
- package/PUBLISHING.md +118 -0
- package/README.md +333 -0
- package/bin/task-delegate.mjs +4 -0
- package/examples/brief.sample.md +34 -0
- package/package.json +66 -0
- package/scripts/create-github-repo.sh +34 -0
- package/skills/task-delegate/SKILL.md +103 -0
- package/skills/task-delegate/references/backend-selection.md +42 -0
- package/skills/task-delegate/references/brief-template.md +87 -0
- package/skills/task-delegate/references/permission-policy.md +74 -0
- package/skills/task-delegate/references/result-schema.md +46 -0
- package/skills/task-delegate/references/review-checklist.md +43 -0
- package/skills/task-delegate/references/roadmap.md +76 -0
- package/skills/task-delegate/scripts/adapters/claude.mjs +33 -0
- package/skills/task-delegate/scripts/adapters/codex.mjs +27 -0
- package/skills/task-delegate/scripts/adapters/opencode.mjs +84 -0
- package/skills/task-delegate/scripts/lib/git.mjs +30 -0
- package/skills/task-delegate/scripts/lib/utils.mjs +179 -0
- package/skills/task-delegate/scripts/relay.mjs +473 -0
- package/skills.sh.json +12 -0
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { cp } from 'node:fs/promises';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
commandExists,
|
|
8
|
+
countLines,
|
|
9
|
+
ensureDir,
|
|
10
|
+
exists,
|
|
11
|
+
nowIso,
|
|
12
|
+
parseArgs,
|
|
13
|
+
printHelp,
|
|
14
|
+
readText,
|
|
15
|
+
relativePath,
|
|
16
|
+
runProcess,
|
|
17
|
+
safeTimestamp,
|
|
18
|
+
writeText
|
|
19
|
+
} from './lib/utils.mjs';
|
|
20
|
+
import { changedFiles, diffStat, dirty, isGitRepo, statusPorcelain } from './lib/git.mjs';
|
|
21
|
+
|
|
22
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
23
|
+
const __dirname = path.dirname(__filename);
|
|
24
|
+
|
|
25
|
+
const adapters = {
|
|
26
|
+
opencode: () => import('./adapters/opencode.mjs'),
|
|
27
|
+
codex: () => import('./adapters/codex.mjs'),
|
|
28
|
+
claude: () => import('./adapters/claude.mjs')
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
function buildGuardedPrompt({ brief, backend, mode }) {
|
|
32
|
+
return `# TaskDelegate delegated implementation brief
|
|
33
|
+
|
|
34
|
+
You are a backend CLI coding agent receiving a bounded task from an orchestrator.
|
|
35
|
+
|
|
36
|
+
## Backend
|
|
37
|
+
|
|
38
|
+
- Backend: ${backend}
|
|
39
|
+
- Mode: ${mode}
|
|
40
|
+
|
|
41
|
+
## Hard rules
|
|
42
|
+
|
|
43
|
+
- Do not commit.
|
|
44
|
+
- Do not push.
|
|
45
|
+
- Do not run git reset, git clean, or rewrite history.
|
|
46
|
+
- Do not read .env files, key files, certificates, private tokens, or credentials.
|
|
47
|
+
- Do not write outside the project directory.
|
|
48
|
+
- Do not install packages unless explicitly allowed in the brief.
|
|
49
|
+
- Do not make unrelated changes.
|
|
50
|
+
- Keep output concise.
|
|
51
|
+
|
|
52
|
+
## Required final response
|
|
53
|
+
|
|
54
|
+
Return a short final response with:
|
|
55
|
+
|
|
56
|
+
- Summary
|
|
57
|
+
- Files changed
|
|
58
|
+
- Commands run
|
|
59
|
+
- Gates passed/failed/not run
|
|
60
|
+
- Unresolved risks
|
|
61
|
+
|
|
62
|
+
## Brief
|
|
63
|
+
|
|
64
|
+
${brief.trim()}
|
|
65
|
+
`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const KNOWN_COMMANDS = new Set(['run', 'doctor', 'list-backends', 'skill-path', 'install-skill', 'init-brief', 'help']);
|
|
69
|
+
|
|
70
|
+
function splitCommand(argv) {
|
|
71
|
+
const copy = [...argv];
|
|
72
|
+
const first = copy[0];
|
|
73
|
+
if (!first || first.startsWith('-')) return { command: 'run', argv: copy };
|
|
74
|
+
if (!KNOWN_COMMANDS.has(first)) return { command: 'run', argv: copy };
|
|
75
|
+
return { command: first, argv: copy.slice(1) };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function parseSimpleOptions(argv) {
|
|
79
|
+
const opts = { dest: undefined, out: undefined, target: undefined, force: false, json: false, help: false };
|
|
80
|
+
|
|
81
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
82
|
+
const token = argv[i];
|
|
83
|
+
const next = () => {
|
|
84
|
+
if (i + 1 >= argv.length) throw new Error(`Missing value for ${token}`);
|
|
85
|
+
i += 1;
|
|
86
|
+
return argv[i];
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
switch (token) {
|
|
90
|
+
case '--dest': opts.dest = next(); break;
|
|
91
|
+
case '--out': opts.out = next(); break;
|
|
92
|
+
case '--target': opts.target = next(); break;
|
|
93
|
+
case '--force': opts.force = true; break;
|
|
94
|
+
case '--json': opts.json = true; break;
|
|
95
|
+
case '--help':
|
|
96
|
+
case '-h': opts.help = true; break;
|
|
97
|
+
default: throw new Error(`Unknown argument: ${token}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return opts;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async function loadBackendMetadata() {
|
|
105
|
+
const entries = [];
|
|
106
|
+
for (const [name, importer] of Object.entries(adapters)) {
|
|
107
|
+
const mod = await importer();
|
|
108
|
+
entries.push({
|
|
109
|
+
name,
|
|
110
|
+
status: mod.backend.status,
|
|
111
|
+
binary: mod.backend.binary,
|
|
112
|
+
defaultMode: mod.backend.defaultMode,
|
|
113
|
+
supportedModes: mod.backend.supportedModes
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
return entries;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function runDoctor({ json = false } = {}) {
|
|
120
|
+
const backendMeta = await loadBackendMetadata();
|
|
121
|
+
const gitAvailable = await commandExists('git');
|
|
122
|
+
const backends = [];
|
|
123
|
+
for (const item of backendMeta) {
|
|
124
|
+
backends.push({
|
|
125
|
+
...item,
|
|
126
|
+
available: await commandExists(item.binary)
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const report = {
|
|
131
|
+
package: 'task-delegate',
|
|
132
|
+
node: process.version,
|
|
133
|
+
gitAvailable,
|
|
134
|
+
adaptersBundled: true,
|
|
135
|
+
note: 'Adapters are bundled with TaskDelegate. Backend CLIs must still be installed and authenticated separately.',
|
|
136
|
+
backends
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
if (json) {
|
|
140
|
+
console.log(JSON.stringify(report, null, 2));
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
console.log('TaskDelegate doctor');
|
|
145
|
+
console.log(`Node: ${report.node}`);
|
|
146
|
+
console.log(`Git: ${gitAvailable ? 'available' : 'missing'}`);
|
|
147
|
+
console.log(`Adapters bundled: yes`);
|
|
148
|
+
console.log('Backend CLIs:');
|
|
149
|
+
for (const b of backends) {
|
|
150
|
+
console.log(`- ${b.name} (${b.status}) -> ${b.binary}: ${b.available ? 'available' : 'missing'}; default=${b.defaultMode}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async function runListBackends({ json = false } = {}) {
|
|
155
|
+
const backendMeta = await loadBackendMetadata();
|
|
156
|
+
if (json) {
|
|
157
|
+
console.log(JSON.stringify({ backends: backendMeta }, null, 2));
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
console.log('TaskDelegate backends');
|
|
161
|
+
for (const b of backendMeta) {
|
|
162
|
+
console.log(`- ${b.name}: ${b.status}; binary=${b.binary}; default=${b.defaultMode}; modes=${b.supportedModes.join(',')}`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async function runSkillPath() {
|
|
167
|
+
console.log(path.resolve(__dirname, '..'));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async function runInstallSkill(options) {
|
|
171
|
+
const destRoot = path.resolve(options.dest || '.claude/skills');
|
|
172
|
+
const source = path.resolve(__dirname, '..');
|
|
173
|
+
const dest = path.join(destRoot, 'task-delegate');
|
|
174
|
+
|
|
175
|
+
if ((await exists(dest)) && !options.force) {
|
|
176
|
+
throw new Error(`Skill already exists at ${dest}. Pass --force to overwrite.`);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
await ensureDir(destRoot);
|
|
180
|
+
await cp(source, dest, { recursive: true, force: true });
|
|
181
|
+
|
|
182
|
+
const result = {
|
|
183
|
+
installed: true,
|
|
184
|
+
skill: 'task-delegate',
|
|
185
|
+
source,
|
|
186
|
+
destination: dest,
|
|
187
|
+
adaptersBundled: true,
|
|
188
|
+
note: 'The skill scripts/adapters were copied. Backend CLIs such as opencode, codex, or claude must still be installed/authenticated separately.'
|
|
189
|
+
};
|
|
190
|
+
console.log(JSON.stringify(result, null, 2));
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async function runInitBrief(options) {
|
|
194
|
+
const outPath = path.resolve(options.out || 'task-delegate.brief.md');
|
|
195
|
+
if ((await exists(outPath)) && !options.force) {
|
|
196
|
+
throw new Error(`Brief already exists at ${outPath}. Pass --force to overwrite.`);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const templatePath = path.resolve(__dirname, '..', 'references', 'brief-template.md');
|
|
200
|
+
const template = await readText(templatePath);
|
|
201
|
+
await writeText(outPath, template);
|
|
202
|
+
console.log(JSON.stringify({ created: true, brief: outPath }, null, 2));
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function defaultModeForBackend(name, backendModule) {
|
|
206
|
+
return backendModule.backend.defaultMode || (name === 'opencode' ? 'safe-auto' : 'manual');
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function validateMode({ backendName, backendModule, mode, force }) {
|
|
210
|
+
const supported = backendModule.backend.supportedModes || [];
|
|
211
|
+
if (!supported.includes(mode)) {
|
|
212
|
+
throw new Error(`Backend ${backendName} does not support mode '${mode}'. Supported: ${supported.join(', ')}`);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (backendName !== 'opencode' && mode === 'safe-auto' && !force) {
|
|
216
|
+
throw new Error(`${backendName} safe-auto is not enabled by default. Use --force if you explicitly accept experimental auto-like behavior.`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
async function main() {
|
|
221
|
+
const { command, argv } = splitCommand(process.argv.slice(2));
|
|
222
|
+
|
|
223
|
+
if (command === 'help') {
|
|
224
|
+
printHelp();
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (command === 'doctor') {
|
|
229
|
+
const options = parseSimpleOptions(argv);
|
|
230
|
+
if (options.help) { printHelp(); return; }
|
|
231
|
+
await runDoctor({ json: options.json });
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (command === 'list-backends') {
|
|
236
|
+
const options = parseSimpleOptions(argv);
|
|
237
|
+
if (options.help) { printHelp(); return; }
|
|
238
|
+
await runListBackends({ json: options.json });
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (command === 'skill-path') {
|
|
243
|
+
await runSkillPath();
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (command === 'install-skill') {
|
|
248
|
+
const options = parseSimpleOptions(argv);
|
|
249
|
+
if (options.help) { printHelp(); return; }
|
|
250
|
+
await runInstallSkill(options);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (command === 'init-brief') {
|
|
255
|
+
const options = parseSimpleOptions(argv);
|
|
256
|
+
if (options.help) { printHelp(); return; }
|
|
257
|
+
await runInitBrief(options);
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const args = parseArgs(argv);
|
|
262
|
+
if (args.help) {
|
|
263
|
+
printHelp();
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (!args.brief) {
|
|
268
|
+
printHelp();
|
|
269
|
+
throw new Error('Missing required --brief path.');
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (!Number.isFinite(args.maxBriefLines) || args.maxBriefLines < 1) {
|
|
273
|
+
throw new Error('--max-brief-lines must be a positive number.');
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const backendName = args.backend;
|
|
277
|
+
if (!adapters[backendName]) {
|
|
278
|
+
throw new Error(`Unsupported backend '${backendName}'. Supported: ${Object.keys(adapters).join(', ')}`);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const projectDir = path.resolve(args.cd);
|
|
282
|
+
if (!(await exists(projectDir))) {
|
|
283
|
+
throw new Error(`Project directory does not exist: ${projectDir}`);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const briefPath = path.resolve(args.brief);
|
|
287
|
+
if (!(await exists(briefPath))) {
|
|
288
|
+
throw new Error(`Brief file does not exist: ${briefPath}`);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const backendModule = await adapters[backendName]();
|
|
292
|
+
const mode = args.mode || defaultModeForBackend(backendName, backendModule);
|
|
293
|
+
validateMode({ backendName, backendModule, mode, force: args.force });
|
|
294
|
+
|
|
295
|
+
const brief = await readText(briefPath);
|
|
296
|
+
const briefLines = countLines(brief);
|
|
297
|
+
if (briefLines > args.maxBriefLines && !args.force) {
|
|
298
|
+
throw new Error(`Brief is ${briefLines} lines, above max ${args.maxBriefLines}. Shorten it or pass --force.`);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const runDir = path.resolve(args.out || path.join(projectDir, '.task-delegate', 'runs', `${safeTimestamp()}-${backendName}`));
|
|
302
|
+
await ensureDir(runDir);
|
|
303
|
+
|
|
304
|
+
const startedAt = nowIso();
|
|
305
|
+
const startTime = Date.now();
|
|
306
|
+
const warnings = [];
|
|
307
|
+
|
|
308
|
+
const gitRepo = await isGitRepo(projectDir);
|
|
309
|
+
if (!gitRepo) {
|
|
310
|
+
warnings.push('Project directory is not a git repository. Diff metadata will be incomplete.');
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const statusBefore = gitRepo ? await statusPorcelain(projectDir) : '';
|
|
314
|
+
const dirtyBefore = statusBefore.trim().length > 0;
|
|
315
|
+
await writeText(path.join(runDir, 'git-before.txt'), statusBefore);
|
|
316
|
+
|
|
317
|
+
if (dirtyBefore && !args.allowDirty) {
|
|
318
|
+
const result = {
|
|
319
|
+
schemaVersion: '0.1',
|
|
320
|
+
skill: 'task-delegate',
|
|
321
|
+
backend: backendName,
|
|
322
|
+
backendStatus: backendModule.backend.status,
|
|
323
|
+
mode,
|
|
324
|
+
projectDir,
|
|
325
|
+
runDir,
|
|
326
|
+
startedAt,
|
|
327
|
+
endedAt: nowIso(),
|
|
328
|
+
durationMs: Date.now() - startTime,
|
|
329
|
+
exitCode: 2,
|
|
330
|
+
timedOut: false,
|
|
331
|
+
dryRun: args.dryRun,
|
|
332
|
+
brief: {
|
|
333
|
+
path: relativePath(projectDir, briefPath),
|
|
334
|
+
lines: briefLines,
|
|
335
|
+
maxLines: args.maxBriefLines
|
|
336
|
+
},
|
|
337
|
+
git: {
|
|
338
|
+
isGitRepo: gitRepo,
|
|
339
|
+
dirtyBefore,
|
|
340
|
+
dirtyAfter: dirtyBefore,
|
|
341
|
+
changedFiles: [],
|
|
342
|
+
diffStatPath: relativePath(runDir, path.join(runDir, 'diff-stat.txt')),
|
|
343
|
+
changedFilesPath: relativePath(runDir, path.join(runDir, 'changed-files.txt'))
|
|
344
|
+
},
|
|
345
|
+
artifacts: {
|
|
346
|
+
promptPath: relativePath(runDir, path.join(runDir, 'prompt.md')),
|
|
347
|
+
stdoutPath: relativePath(runDir, path.join(runDir, 'stdout.log')),
|
|
348
|
+
stderrPath: relativePath(runDir, path.join(runDir, 'stderr.log'))
|
|
349
|
+
},
|
|
350
|
+
warnings: [
|
|
351
|
+
...warnings,
|
|
352
|
+
'Refused to run because git working tree is dirty. Commit/stash changes or pass --allow-dirty.'
|
|
353
|
+
],
|
|
354
|
+
reviewRequired: true,
|
|
355
|
+
commitAllowed: false
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
await writeText(path.join(runDir, 'result.json'), `${JSON.stringify(result, null, 2)}\n`);
|
|
359
|
+
console.error(`TaskDelegate refused dirty worktree. Result: ${path.join(runDir, 'result.json')}`);
|
|
360
|
+
process.exitCode = 2;
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const prompt = buildGuardedPrompt({ brief, backend: backendName, mode });
|
|
365
|
+
await writeText(path.join(runDir, 'prompt.md'), prompt);
|
|
366
|
+
|
|
367
|
+
let invocation = null;
|
|
368
|
+
let procResult = {
|
|
369
|
+
command: '',
|
|
370
|
+
args: [],
|
|
371
|
+
cwd: projectDir,
|
|
372
|
+
exitCode: 0,
|
|
373
|
+
stdout: '',
|
|
374
|
+
stderr: '',
|
|
375
|
+
timedOut: false
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
if (args.dryRun) {
|
|
379
|
+
warnings.push('Dry run: backend was not launched.');
|
|
380
|
+
} else {
|
|
381
|
+
invocation = backendModule.buildInvocation({ prompt, mode, model: args.model });
|
|
382
|
+
warnings.push(...(invocation.warnings || []));
|
|
383
|
+
|
|
384
|
+
const existsBackend = await commandExists(invocation.command);
|
|
385
|
+
if (!existsBackend) {
|
|
386
|
+
throw new Error(`Backend CLI not found in PATH: ${invocation.command}`);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const env = {
|
|
390
|
+
...process.env,
|
|
391
|
+
...(invocation.env || {})
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
procResult = await runProcess(invocation.command, invocation.args, {
|
|
395
|
+
cwd: projectDir,
|
|
396
|
+
env,
|
|
397
|
+
timeoutMs: args.timeoutMs
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
await writeText(path.join(runDir, 'stdout.log'), procResult.stdout || '');
|
|
402
|
+
await writeText(path.join(runDir, 'stderr.log'), procResult.stderr || '');
|
|
403
|
+
|
|
404
|
+
const statusAfter = gitRepo ? await statusPorcelain(projectDir) : '';
|
|
405
|
+
const dirtyAfter = gitRepo ? await dirty(projectDir) : false;
|
|
406
|
+
const filesChanged = gitRepo ? await changedFiles(projectDir) : [];
|
|
407
|
+
const stat = gitRepo ? await diffStat(projectDir) : '';
|
|
408
|
+
|
|
409
|
+
await writeText(path.join(runDir, 'git-after.txt'), statusAfter);
|
|
410
|
+
await writeText(path.join(runDir, 'changed-files.txt'), `${filesChanged.join('\n')}${filesChanged.length ? '\n' : ''}`);
|
|
411
|
+
await writeText(path.join(runDir, 'diff-stat.txt'), stat);
|
|
412
|
+
|
|
413
|
+
const endedAt = nowIso();
|
|
414
|
+
const result = {
|
|
415
|
+
schemaVersion: '0.1',
|
|
416
|
+
skill: 'task-delegate',
|
|
417
|
+
backend: backendName,
|
|
418
|
+
backendStatus: backendModule.backend.status,
|
|
419
|
+
mode,
|
|
420
|
+
projectDir,
|
|
421
|
+
runDir,
|
|
422
|
+
startedAt,
|
|
423
|
+
endedAt,
|
|
424
|
+
durationMs: Date.now() - startTime,
|
|
425
|
+
exitCode: procResult.exitCode,
|
|
426
|
+
timedOut: procResult.timedOut,
|
|
427
|
+
dryRun: args.dryRun,
|
|
428
|
+
command: invocation ? invocation.command : null,
|
|
429
|
+
commandArgsPreview: invocation ? invocation.args.slice(0, -1) : [],
|
|
430
|
+
brief: {
|
|
431
|
+
path: relativePath(projectDir, briefPath),
|
|
432
|
+
lines: briefLines,
|
|
433
|
+
maxLines: args.maxBriefLines
|
|
434
|
+
},
|
|
435
|
+
git: {
|
|
436
|
+
isGitRepo: gitRepo,
|
|
437
|
+
dirtyBefore,
|
|
438
|
+
dirtyAfter,
|
|
439
|
+
changedFiles: filesChanged,
|
|
440
|
+
diffStatPath: relativePath(runDir, path.join(runDir, 'diff-stat.txt')),
|
|
441
|
+
changedFilesPath: relativePath(runDir, path.join(runDir, 'changed-files.txt'))
|
|
442
|
+
},
|
|
443
|
+
artifacts: {
|
|
444
|
+
promptPath: relativePath(runDir, path.join(runDir, 'prompt.md')),
|
|
445
|
+
stdoutPath: relativePath(runDir, path.join(runDir, 'stdout.log')),
|
|
446
|
+
stderrPath: relativePath(runDir, path.join(runDir, 'stderr.log')),
|
|
447
|
+
gitBeforePath: relativePath(runDir, path.join(runDir, 'git-before.txt')),
|
|
448
|
+
gitAfterPath: relativePath(runDir, path.join(runDir, 'git-after.txt'))
|
|
449
|
+
},
|
|
450
|
+
warnings,
|
|
451
|
+
reviewRequired: true,
|
|
452
|
+
commitAllowed: false
|
|
453
|
+
};
|
|
454
|
+
|
|
455
|
+
await writeText(path.join(runDir, 'result.json'), `${JSON.stringify(result, null, 2)}\n`);
|
|
456
|
+
|
|
457
|
+
console.log(JSON.stringify({
|
|
458
|
+
status: procResult.exitCode === 0 ? 'completed' : 'failed',
|
|
459
|
+
backend: backendName,
|
|
460
|
+
mode,
|
|
461
|
+
runDir,
|
|
462
|
+
resultPath: path.join(runDir, 'result.json'),
|
|
463
|
+
changedFiles: filesChanged.length,
|
|
464
|
+
reviewRequired: true
|
|
465
|
+
}, null, 2));
|
|
466
|
+
|
|
467
|
+
process.exitCode = procResult.exitCode === null ? 1 : procResult.exitCode;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
main().catch((error) => {
|
|
471
|
+
console.error(`TaskDelegate error: ${error.message}`);
|
|
472
|
+
process.exitCode = 1;
|
|
473
|
+
});
|
package/skills.sh.json
ADDED