@cardor/agent-harness-kit 1.11.0 → 2.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/README.md +106 -19
- package/dist/agent-templates/builder.md +20 -14
- package/dist/agent-templates/explorer.md +11 -8
- package/dist/agent-templates/lead.md +10 -7
- package/dist/agent-templates/reviewer.md +9 -5
- package/dist/{chunk-6PEIJ2D5.js → chunk-JTACLEGM.js} +45 -16
- package/dist/chunk-JTACLEGM.js.map +1 -0
- package/dist/chunk-URMVLD2S.js +147 -0
- package/dist/chunk-URMVLD2S.js.map +1 -0
- package/dist/cli.js +885 -411
- package/dist/cli.js.map +1 -1
- package/dist/{db-3OXHRFAR.js → db-L3AADJF5.js} +4 -2
- package/dist/index.d.ts +2 -2
- package/dist/{mysql-THKQOXIS.js → mysql-AUPKARWA.js} +11 -5
- package/dist/mysql-AUPKARWA.js.map +1 -0
- package/dist/{postgres-IOQE32DM.js → postgres-BB4GY4PN.js} +11 -5
- package/dist/postgres-BB4GY4PN.js.map +1 -0
- package/dist/{sqlite-TR4D324R.js → sqlite-5OWKTUUZ.js} +11 -5
- package/dist/sqlite-5OWKTUUZ.js.map +1 -0
- package/package.json +2 -1
- package/dist/chunk-6PEIJ2D5.js.map +0 -1
- package/dist/mysql-THKQOXIS.js.map +0 -1
- package/dist/postgres-IOQE32DM.js.map +0 -1
- package/dist/sqlite-TR4D324R.js.map +0 -1
- /package/dist/{db-3OXHRFAR.js.map → db-L3AADJF5.js.map} +0 -0
package/dist/cli.js
CHANGED
|
@@ -12,20 +12,20 @@ import {
|
|
|
12
12
|
resolveGlobalStorageDir,
|
|
13
13
|
resolveSqlitePath,
|
|
14
14
|
resolveSqlitePathForScope
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-JTACLEGM.js";
|
|
16
16
|
|
|
17
17
|
// src/cli.ts
|
|
18
|
-
import { Command } from "commander";
|
|
19
|
-
import
|
|
18
|
+
import { Command, InvalidArgumentError } from "commander";
|
|
19
|
+
import pc20 from "picocolors";
|
|
20
20
|
|
|
21
21
|
// src/commands/build.ts
|
|
22
22
|
import { watch } from "fs";
|
|
23
23
|
import * as p from "@clack/prompts";
|
|
24
|
-
import
|
|
24
|
+
import pc2 from "picocolors";
|
|
25
25
|
|
|
26
26
|
// src/core/materializer/claude-code.ts
|
|
27
|
-
import { existsSync as
|
|
28
|
-
import { join as
|
|
27
|
+
import { existsSync as existsSync6 } from "fs";
|
|
28
|
+
import { join as join7 } from "path";
|
|
29
29
|
|
|
30
30
|
// src/utils/file.ts
|
|
31
31
|
import { mkdirSync, writeFileSync } from "fs";
|
|
@@ -37,24 +37,76 @@ var write = (cwd2, relPath, content, mode) => {
|
|
|
37
37
|
};
|
|
38
38
|
|
|
39
39
|
// src/core/materializer/detect-package-manager.ts
|
|
40
|
-
import { existsSync, readFileSync } from "fs";
|
|
41
|
-
import { join as
|
|
40
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
41
|
+
import { join as join4 } from "path";
|
|
42
|
+
|
|
43
|
+
// src/core/local-install-guard.ts
|
|
44
|
+
import { existsSync as existsSync2, readFileSync } from "fs";
|
|
45
|
+
import { join as join3 } from "path";
|
|
46
|
+
import pc from "picocolors";
|
|
47
|
+
|
|
48
|
+
// src/core/package-data.ts
|
|
49
|
+
import { existsSync } from "fs";
|
|
50
|
+
import { createRequire } from "module";
|
|
51
|
+
import { dirname, join as join2 } from "path";
|
|
52
|
+
import { fileURLToPath } from "url";
|
|
53
|
+
var require2 = createRequire(import.meta.url);
|
|
54
|
+
var here = dirname(fileURLToPath(import.meta.url));
|
|
55
|
+
var candidates = [join2(here, "..", "..", "package.json"), join2(here, "..", "package.json")];
|
|
56
|
+
var pkgPath = candidates.find((p8) => existsSync(p8)) ?? candidates[0];
|
|
57
|
+
var pkg = require2(pkgPath);
|
|
58
|
+
|
|
59
|
+
// src/core/local-install-guard.ts
|
|
60
|
+
function isLocalInstallSatisfied(cwd2) {
|
|
61
|
+
const selfPkgPath = join3(cwd2, "package.json");
|
|
62
|
+
let projectPkg = null;
|
|
63
|
+
if (existsSync2(selfPkgPath)) {
|
|
64
|
+
try {
|
|
65
|
+
const selfPkg = JSON.parse(readFileSync(selfPkgPath, "utf8"));
|
|
66
|
+
if (selfPkg?.name === pkg.name) return true;
|
|
67
|
+
projectPkg = selfPkg;
|
|
68
|
+
} catch {
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const [scope, name] = pkg.name.split("/");
|
|
72
|
+
const localPath = pkg.name.startsWith("@") ? join3(cwd2, "node_modules", scope, name) : join3(cwd2, "node_modules", pkg.name);
|
|
73
|
+
if (existsSync2(localPath)) return true;
|
|
74
|
+
const isPnp = existsSync2(join3(cwd2, ".pnp.cjs")) || existsSync2(join3(cwd2, ".pnp.loader.mjs"));
|
|
75
|
+
if (isPnp && projectPkg) {
|
|
76
|
+
const deps = {
|
|
77
|
+
...projectPkg.dependencies ?? {},
|
|
78
|
+
...projectPkg.devDependencies ?? {}
|
|
79
|
+
};
|
|
80
|
+
if (Object.prototype.hasOwnProperty.call(deps, pkg.name)) return true;
|
|
81
|
+
}
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
function printLocalInstallWarning() {
|
|
85
|
+
console.error(pc.yellow(`\u26A0 ${pkg.name} is not installed locally in this project.`));
|
|
86
|
+
console.error(pc.dim(" This is only a recommendation for reproducibility: pinning a local"));
|
|
87
|
+
console.error(pc.dim(" version keeps behavior consistent across your team and CI, instead of"));
|
|
88
|
+
console.error(pc.dim(" drifting with whatever version is installed globally on each machine."));
|
|
89
|
+
console.error(pc.dim(` Run: npm install --save-dev ${pkg.name}`));
|
|
90
|
+
console.error(pc.dim(" (or the equivalent for your package manager: pnpm add -D, yarn add --dev, bun add -d)"));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// src/core/materializer/detect-package-manager.ts
|
|
42
94
|
function detectPackageManager(cwd2) {
|
|
43
95
|
const fromField = detectFromPackageManagerField(cwd2);
|
|
44
96
|
if (fromField) return fromField;
|
|
45
|
-
if (
|
|
46
|
-
if (
|
|
47
|
-
if (
|
|
48
|
-
return
|
|
97
|
+
if (existsSync3(join4(cwd2, "pnpm-lock.yaml"))) return "pnpm";
|
|
98
|
+
if (existsSync3(join4(cwd2, "bun.lockb")) || existsSync3(join4(cwd2, "bun.lock"))) return "bun";
|
|
99
|
+
if (existsSync3(join4(cwd2, "yarn.lock"))) {
|
|
100
|
+
return existsSync3(join4(cwd2, ".yarnrc.yml")) ? "yarn-berry" : "yarn-classic";
|
|
49
101
|
}
|
|
50
|
-
if (
|
|
102
|
+
if (existsSync3(join4(cwd2, "package-lock.json"))) return "npm";
|
|
51
103
|
return "npm";
|
|
52
104
|
}
|
|
53
105
|
function detectFromPackageManagerField(cwd2) {
|
|
54
|
-
const pkgPath2 =
|
|
55
|
-
if (!
|
|
106
|
+
const pkgPath2 = join4(cwd2, "package.json");
|
|
107
|
+
if (!existsSync3(pkgPath2)) return null;
|
|
56
108
|
try {
|
|
57
|
-
const pkg2 = JSON.parse(
|
|
109
|
+
const pkg2 = JSON.parse(readFileSync2(pkgPath2, "utf8"));
|
|
58
110
|
const field = pkg2?.packageManager;
|
|
59
111
|
if (typeof field !== "string" || !field.trim()) return null;
|
|
60
112
|
const match = field.match(/^([a-z]+)@(\d+)/i);
|
|
@@ -78,8 +130,11 @@ function detectFromPackageManagerField(cwd2) {
|
|
|
78
130
|
return null;
|
|
79
131
|
}
|
|
80
132
|
}
|
|
81
|
-
function getMcpCommandParts(pm, port) {
|
|
133
|
+
function getMcpCommandParts(pm, port, cwd2) {
|
|
82
134
|
const portStr = String(port);
|
|
135
|
+
if (!isLocalInstallSatisfied(cwd2)) {
|
|
136
|
+
return ["ahk", "serve", "--port", portStr];
|
|
137
|
+
}
|
|
83
138
|
switch (pm) {
|
|
84
139
|
case "pnpm":
|
|
85
140
|
return ["pnpm", "exec", "ahk", "serve", "--port", portStr];
|
|
@@ -95,21 +150,21 @@ function getMcpCommandParts(pm, port) {
|
|
|
95
150
|
}
|
|
96
151
|
|
|
97
152
|
// src/core/materializer/mcp-merge.ts
|
|
98
|
-
import { existsSync as
|
|
99
|
-
import { dirname } from "path";
|
|
100
|
-
function mergeClaudeMcpJson(filePath, port, pm = "npm") {
|
|
101
|
-
const folderPath =
|
|
102
|
-
if (!
|
|
153
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
154
|
+
import { dirname as dirname2 } from "path";
|
|
155
|
+
function mergeClaudeMcpJson(filePath, port, cwd2, pm = "npm") {
|
|
156
|
+
const folderPath = dirname2(filePath);
|
|
157
|
+
if (!existsSync4(folderPath)) {
|
|
103
158
|
mkdirSync2(folderPath, { recursive: true });
|
|
104
159
|
}
|
|
105
160
|
let existing = {};
|
|
106
|
-
if (
|
|
161
|
+
if (existsSync4(filePath)) {
|
|
107
162
|
try {
|
|
108
|
-
existing = JSON.parse(
|
|
163
|
+
existing = JSON.parse(readFileSync3(filePath, "utf8"));
|
|
109
164
|
} catch {
|
|
110
165
|
}
|
|
111
166
|
}
|
|
112
|
-
const [command, ...args] = getMcpCommandParts(pm, port);
|
|
167
|
+
const [command, ...args] = getMcpCommandParts(pm, port, cwd2);
|
|
113
168
|
const merged = {
|
|
114
169
|
...existing,
|
|
115
170
|
mcpServers: {
|
|
@@ -121,15 +176,15 @@ function mergeClaudeMcpJson(filePath, port, pm = "npm") {
|
|
|
121
176
|
}
|
|
122
177
|
}
|
|
123
178
|
};
|
|
124
|
-
mkdirSync2(
|
|
179
|
+
mkdirSync2(dirname2(filePath), { recursive: true });
|
|
125
180
|
writeFileSync2(filePath, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
126
181
|
}
|
|
127
182
|
function mergeClaudeSettingsJson(filePath) {
|
|
128
|
-
mkdirSync2(
|
|
183
|
+
mkdirSync2(dirname2(filePath), { recursive: true });
|
|
129
184
|
let existing = {};
|
|
130
|
-
if (
|
|
185
|
+
if (existsSync4(filePath)) {
|
|
131
186
|
try {
|
|
132
|
-
existing = JSON.parse(
|
|
187
|
+
existing = JSON.parse(readFileSync3(filePath, "utf8"));
|
|
133
188
|
} catch {
|
|
134
189
|
}
|
|
135
190
|
}
|
|
@@ -232,11 +287,11 @@ var MCP_CLAUDE_PERMISSIONS = [
|
|
|
232
287
|
])
|
|
233
288
|
];
|
|
234
289
|
function mergeClaudeSettingsLocalJson(filePath) {
|
|
235
|
-
mkdirSync2(
|
|
290
|
+
mkdirSync2(dirname2(filePath), { recursive: true });
|
|
236
291
|
let existing = {};
|
|
237
|
-
if (
|
|
292
|
+
if (existsSync4(filePath)) {
|
|
238
293
|
try {
|
|
239
|
-
existing = JSON.parse(
|
|
294
|
+
existing = JSON.parse(readFileSync3(filePath, "utf8"));
|
|
240
295
|
} catch {
|
|
241
296
|
}
|
|
242
297
|
}
|
|
@@ -255,15 +310,15 @@ function mergeClaudeSettingsLocalJson(filePath) {
|
|
|
255
310
|
};
|
|
256
311
|
writeFileSync2(filePath, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
257
312
|
}
|
|
258
|
-
function mergeOpencodeJson(filePath, port, pm = "npm") {
|
|
259
|
-
const folderPath =
|
|
260
|
-
if (!
|
|
313
|
+
function mergeOpencodeJson(filePath, port, cwd2, pm = "npm") {
|
|
314
|
+
const folderPath = dirname2(filePath);
|
|
315
|
+
if (!existsSync4(folderPath)) {
|
|
261
316
|
mkdirSync2(folderPath, { recursive: true });
|
|
262
317
|
}
|
|
263
318
|
let existing = {};
|
|
264
|
-
if (
|
|
319
|
+
if (existsSync4(filePath)) {
|
|
265
320
|
try {
|
|
266
|
-
existing = JSON.parse(
|
|
321
|
+
existing = JSON.parse(readFileSync3(filePath, "utf8"));
|
|
267
322
|
} catch {
|
|
268
323
|
}
|
|
269
324
|
}
|
|
@@ -280,7 +335,7 @@ function mergeOpencodeJson(filePath, port, pm = "npm") {
|
|
|
280
335
|
type: "local",
|
|
281
336
|
// OpenCode's mcp.<name>.command field is a single array (unlike
|
|
282
337
|
// Claude/Codex, which split command/args) — pass the full token list.
|
|
283
|
-
command: getMcpCommandParts(pm, port)
|
|
338
|
+
command: getMcpCommandParts(pm, port, cwd2)
|
|
284
339
|
}
|
|
285
340
|
}
|
|
286
341
|
};
|
|
@@ -310,13 +365,13 @@ function mergeTomlSection(content, sectionName, sectionBody) {
|
|
|
310
365
|
];
|
|
311
366
|
return newLines.join("\n");
|
|
312
367
|
}
|
|
313
|
-
function mergeCodexConfigToml(filePath, port, pm = "npm") {
|
|
314
|
-
mkdirSync2(
|
|
368
|
+
function mergeCodexConfigToml(filePath, port, cwd2, pm = "npm") {
|
|
369
|
+
mkdirSync2(dirname2(filePath), { recursive: true });
|
|
315
370
|
let content = "";
|
|
316
|
-
if (
|
|
317
|
-
content =
|
|
371
|
+
if (existsSync4(filePath)) {
|
|
372
|
+
content = readFileSync3(filePath, "utf8");
|
|
318
373
|
}
|
|
319
|
-
const [command, ...args] = getMcpCommandParts(pm, port);
|
|
374
|
+
const [command, ...args] = getMcpCommandParts(pm, port, cwd2);
|
|
320
375
|
const sectionBody = [
|
|
321
376
|
`command = ${JSON.stringify(command)}`,
|
|
322
377
|
`args = ${JSON.stringify(args)}`,
|
|
@@ -325,16 +380,28 @@ function mergeCodexConfigToml(filePath, port, pm = "npm") {
|
|
|
325
380
|
content = mergeTomlSection(content, "mcp_servers.agent-harness-kit", sectionBody);
|
|
326
381
|
writeFileSync2(filePath, content, "utf8");
|
|
327
382
|
}
|
|
383
|
+
function mergeGrokConfigToml(filePath, port, cwd2, pm = "npm") {
|
|
384
|
+
mkdirSync2(dirname2(filePath), { recursive: true });
|
|
385
|
+
let content = "";
|
|
386
|
+
if (existsSync4(filePath)) {
|
|
387
|
+
content = readFileSync3(filePath, "utf8");
|
|
388
|
+
}
|
|
389
|
+
const [command, ...args] = getMcpCommandParts(pm, port, cwd2);
|
|
390
|
+
const sectionBody = [`command = ${JSON.stringify(command)}`, `args = ${JSON.stringify(args)}`].join("\n");
|
|
391
|
+
content = mergeTomlSection(content, "mcp_servers.agent-harness-kit", sectionBody);
|
|
392
|
+
writeFileSync2(filePath, content, "utf8");
|
|
393
|
+
}
|
|
328
394
|
|
|
329
395
|
// src/core/materializer/scaffold-utils.ts
|
|
330
|
-
import {
|
|
331
|
-
import {
|
|
332
|
-
import {
|
|
396
|
+
import { createHash } from "crypto";
|
|
397
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
|
|
398
|
+
import { dirname as dirname4, join as join6, resolve as resolve2 } from "path";
|
|
399
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
333
400
|
|
|
334
401
|
// src/core/materializer/templates.ts
|
|
335
|
-
import { readFileSync as
|
|
336
|
-
import { dirname as
|
|
337
|
-
import { fileURLToPath } from "url";
|
|
402
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
403
|
+
import { dirname as dirname3, join as join5 } from "path";
|
|
404
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
338
405
|
|
|
339
406
|
// src/core/materializer/agent-restrictions.ts
|
|
340
407
|
var AGENT_RESTRICTIONS = {
|
|
@@ -364,12 +431,15 @@ These tools may still appear available to you. The sandbox will reject the call.
|
|
|
364
431
|
function codexRestrictionNotice(agentName) {
|
|
365
432
|
return restrictionFor(agentName) === "no-write" ? CODEX_READ_ONLY_NOTICE : "";
|
|
366
433
|
}
|
|
434
|
+
function grokToolsAllowlist(agentName) {
|
|
435
|
+
return restrictionFor(agentName) === "no-write" ? ["Bash", "Read", "NotebookRead", "Grep", "Glob", "WebFetch", "WebSearch", "search_tool", "use_tool"] : [];
|
|
436
|
+
}
|
|
367
437
|
|
|
368
438
|
// src/core/materializer/templates.ts
|
|
369
|
-
var __dirname =
|
|
370
|
-
var TEMPLATES_DIR =
|
|
439
|
+
var __dirname = dirname3(fileURLToPath2(import.meta.url));
|
|
440
|
+
var TEMPLATES_DIR = join5(__dirname, "agent-templates");
|
|
371
441
|
function loadAgentTemplate(name, vars = {}) {
|
|
372
|
-
const raw =
|
|
442
|
+
const raw = readFileSync4(join5(TEMPLATES_DIR, `${name}.md`), "utf8");
|
|
373
443
|
return raw.replace(/\{\{(\w+)\}\}/g, (_, key) => vars[key] ?? `{{${key}}}`);
|
|
374
444
|
}
|
|
375
445
|
var HEALTH_SH = `#!/usr/bin/env bash
|
|
@@ -424,10 +494,10 @@ If it exits non-zero, stop and report the issue. Do not proceed with codebase ch
|
|
|
424
494
|
The harness exposes tools via MCP server on port ${port}. Use these instead of reading files directly.
|
|
425
495
|
|
|
426
496
|
\`\`\`
|
|
427
|
-
actions.start taskId agent \u2192 start an action, returns actionId
|
|
497
|
+
actions.start taskId agent \u2192 start an action, returns a numeric actionId
|
|
428
498
|
actions.write actionId section text \u2192 record a section (result, blockers, ...)
|
|
429
|
-
actions.record_tool actionId
|
|
430
|
-
actions.record_file actionId
|
|
499
|
+
actions.record_tool actionId calls[] \u2192 batch-log tool calls to the Tools dashboard (array, min 1)
|
|
500
|
+
actions.record_file actionId files[] \u2192 batch-log file touches to the Files dashboard (array, min 1)
|
|
431
501
|
actions.complete actionId summary \u2192 close the action
|
|
432
502
|
actions.get taskId \u2192 full action history for a task
|
|
433
503
|
tasks.add title [slug] [description] [acceptance] \u2192 create a new task from natural language
|
|
@@ -447,9 +517,8 @@ docs.search query \u2192 search ${docs
|
|
|
447
517
|
- tasks.get('pending') \u2192 pick lowest id
|
|
448
518
|
|
|
449
519
|
2. WORK (lead \u2192 explorer \u2192 consultant \u2192 builder \u2192 reviewer)
|
|
450
|
-
- Each agent calls actions.start(taskId, agentName) \u2192 actionId
|
|
451
|
-
-
|
|
452
|
-
- After EVERY file change: actions.record_file(actionId, filePath, operation, notes)
|
|
520
|
+
- Each agent calls actions.start(taskId, agentName) \u2192 numeric actionId
|
|
521
|
+
- Accumulate tool calls / file touches as you work; flush periodically (every few calls or at a phase boundary) via actions.record_tool(actionId, calls: [...]) and actions.record_file(actionId, files: [...]) \u2014 both are batch-only, even a single entry goes through as a one-element array
|
|
453
522
|
- Closes with actions.complete(actionId, summary)
|
|
454
523
|
|
|
455
524
|
3. CLOSE
|
|
@@ -508,10 +577,10 @@ If it exits non-zero, stop and report the issue. Do not proceed with codebase ch
|
|
|
508
577
|
The harness exposes tools via MCP server on port ${port}. Use these instead of reading files directly.
|
|
509
578
|
|
|
510
579
|
\`\`\`
|
|
511
|
-
actions.start taskId agent \u2192 start an action, returns actionId
|
|
580
|
+
actions.start taskId agent \u2192 start an action, returns a numeric actionId
|
|
512
581
|
actions.write actionId section text \u2192 record a section (result, blockers, ...)
|
|
513
|
-
actions.record_tool actionId
|
|
514
|
-
actions.record_file actionId
|
|
582
|
+
actions.record_tool actionId calls[] \u2192 batch-log tool calls to the Tools dashboard (array, min 1)
|
|
583
|
+
actions.record_file actionId files[] \u2192 batch-log file touches to the Files dashboard (array, min 1)
|
|
515
584
|
actions.complete actionId summary \u2192 close the action
|
|
516
585
|
actions.get taskId \u2192 full action history for a task
|
|
517
586
|
tasks.add title [slug] [description] [acceptance] \u2192 create a new task from natural language
|
|
@@ -532,9 +601,8 @@ docs.search query \u2192 search ${docs
|
|
|
532
601
|
- No pending tasks? \u2192 ask user, infer fields, call tasks.add, then tasks.claim
|
|
533
602
|
|
|
534
603
|
2. WORK (lead \u2192 explorer \u2192 consultant \u2192 builder \u2192 reviewer)
|
|
535
|
-
- Each agent calls actions.start(taskId, agentName) \u2192 actionId
|
|
536
|
-
-
|
|
537
|
-
- After EVERY file change: actions.record_file(actionId, filePath, operation, notes)
|
|
604
|
+
- Each agent calls actions.start(taskId, agentName) \u2192 numeric actionId
|
|
605
|
+
- Accumulate tool calls / file touches as you work; flush periodically (every few calls or at a phase boundary) via actions.record_tool(actionId, calls: [...]) and actions.record_file(actionId, files: [...]) \u2014 both are batch-only, even a single entry goes through as a one-element array
|
|
538
606
|
- Closes with actions.complete(actionId, summary)
|
|
539
607
|
|
|
540
608
|
3. CLOSE
|
|
@@ -692,9 +760,6 @@ function agentConsultant(vars) {
|
|
|
692
760
|
function agentReviewer(vars) {
|
|
693
761
|
return loadAgentTemplate("reviewer", vars);
|
|
694
762
|
}
|
|
695
|
-
function featureListJson(tasks) {
|
|
696
|
-
return JSON.stringify(tasks, null, 2) + "\n";
|
|
697
|
-
}
|
|
698
763
|
function stripFrontmatter(md) {
|
|
699
764
|
const parts = md.split(/^---\s*$/m);
|
|
700
765
|
if (parts.length < 3) return { description: "", body: md };
|
|
@@ -768,6 +833,13 @@ ${values.map((v4) => ` - ${v4}`).join("\n")}
|
|
|
768
833
|
${body}${block}---
|
|
769
834
|
`);
|
|
770
835
|
}
|
|
836
|
+
function appendFrontmatterScalar(md, key, value) {
|
|
837
|
+
const block = `${key}: ${value}
|
|
838
|
+
`;
|
|
839
|
+
return md.replace(/^---\n([\s\S]*?)^---\n/m, (_m, body) => `---
|
|
840
|
+
${body}${block}---
|
|
841
|
+
`);
|
|
842
|
+
}
|
|
771
843
|
function appendFrontmatterMapping(md, key, entries) {
|
|
772
844
|
const keys = Object.keys(entries);
|
|
773
845
|
if (keys.length === 0) return md;
|
|
@@ -778,9 +850,12 @@ ${keys.map((k) => ` ${k}: ${entries[k]}`).join("\n")}
|
|
|
778
850
|
${body}${block}---
|
|
779
851
|
`);
|
|
780
852
|
}
|
|
781
|
-
function translateFrontmatterForClaudeCode(md, agentName) {
|
|
853
|
+
function translateFrontmatterForClaudeCode(md, agentName, opts) {
|
|
782
854
|
let result = stripFrontmatterBlockSequence(md, "tools");
|
|
783
855
|
result = stripFrontmatterBlockSequence(result, "disallowedTools");
|
|
856
|
+
if (opts?.model && opts.model !== "inherit") {
|
|
857
|
+
result = appendFrontmatterScalar(result, "model", opts.model);
|
|
858
|
+
}
|
|
784
859
|
return appendFrontmatterBlockSequence(result, "disallowedTools", claudeDisallowedTools(agentName));
|
|
785
860
|
}
|
|
786
861
|
function translateFrontmatterForOpenCode(md, agentName) {
|
|
@@ -788,6 +863,11 @@ function translateFrontmatterForOpenCode(md, agentName) {
|
|
|
788
863
|
result = stripFrontmatterBlockSequence(result, "disallowedTools");
|
|
789
864
|
return appendFrontmatterMapping(result, "permission", opencodePermissions(agentName));
|
|
790
865
|
}
|
|
866
|
+
function translateFrontmatterForGrok(md, agentName) {
|
|
867
|
+
let result = stripFrontmatterBlockSequence(md, "tools");
|
|
868
|
+
result = stripFrontmatterBlockSequence(result, "disallowedTools");
|
|
869
|
+
return appendFrontmatterBlockSequence(result, "tools", grokToolsAllowlist(agentName));
|
|
870
|
+
}
|
|
791
871
|
var GITIGNORE_ENTRIES = `
|
|
792
872
|
# agent-harness-kit
|
|
793
873
|
.harness/harness.db
|
|
@@ -797,10 +877,10 @@ var GITIGNORE_ENTRIES = `
|
|
|
797
877
|
`;
|
|
798
878
|
|
|
799
879
|
// src/core/materializer/scaffold-utils.ts
|
|
800
|
-
var __dirname2 =
|
|
880
|
+
var __dirname2 = dirname4(fileURLToPath3(import.meta.url));
|
|
801
881
|
function writeAgentFiles(cwd2, entries, opts = {}) {
|
|
802
882
|
const result = { created: [], overwritten: [], preserved: [] };
|
|
803
|
-
const existing = entries.filter((e) =>
|
|
883
|
+
const existing = entries.filter((e) => existsSync5(join6(cwd2, e.relPath)));
|
|
804
884
|
if (opts.force && existing.length > 0) {
|
|
805
885
|
if (!opts.backupRoot) {
|
|
806
886
|
throw new Error(
|
|
@@ -808,12 +888,12 @@ function writeAgentFiles(cwd2, entries, opts = {}) {
|
|
|
808
888
|
);
|
|
809
889
|
}
|
|
810
890
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
811
|
-
const backupDir =
|
|
891
|
+
const backupDir = join6(opts.backupRoot, `agents-${stamp}`);
|
|
812
892
|
try {
|
|
813
893
|
for (const entry of existing) {
|
|
814
|
-
const dest =
|
|
894
|
+
const dest = join6(backupDir, entry.relPath);
|
|
815
895
|
mkdirSync3(resolve2(dest, ".."), { recursive: true });
|
|
816
|
-
writeFileSync3(dest,
|
|
896
|
+
writeFileSync3(dest, readFileSync5(join6(cwd2, entry.relPath), "utf8"), "utf8");
|
|
817
897
|
}
|
|
818
898
|
} catch (err) {
|
|
819
899
|
throw new Error(
|
|
@@ -823,8 +903,8 @@ function writeAgentFiles(cwd2, entries, opts = {}) {
|
|
|
823
903
|
result.backupDir = backupDir;
|
|
824
904
|
}
|
|
825
905
|
for (const entry of entries) {
|
|
826
|
-
const abs =
|
|
827
|
-
const exists =
|
|
906
|
+
const abs = join6(cwd2, entry.relPath);
|
|
907
|
+
const exists = existsSync5(abs);
|
|
828
908
|
if (exists && !opts.force) {
|
|
829
909
|
result.preserved.push(entry.relPath);
|
|
830
910
|
continue;
|
|
@@ -836,9 +916,106 @@ function writeAgentFiles(cwd2, entries, opts = {}) {
|
|
|
836
916
|
}
|
|
837
917
|
return result;
|
|
838
918
|
}
|
|
919
|
+
var GENERATED_MARKER_RE = /^([\s\S]*)\n<!-- ahk:generated ([0-9a-f]{64}) -->\n?$/;
|
|
920
|
+
function bodyFingerprint(body) {
|
|
921
|
+
return createHash("sha256").update(body, "utf8").digest("hex");
|
|
922
|
+
}
|
|
923
|
+
function stampGenerated(body) {
|
|
924
|
+
return `${body}
|
|
925
|
+
<!-- ahk:generated ${bodyFingerprint(body)} -->
|
|
926
|
+
`;
|
|
927
|
+
}
|
|
928
|
+
function readStamp(fileContent) {
|
|
929
|
+
const m = GENERATED_MARKER_RE.exec(fileContent);
|
|
930
|
+
if (!m) return null;
|
|
931
|
+
return { body: m[1], hash: m[2] };
|
|
932
|
+
}
|
|
933
|
+
function reconcileGeneratedFiles(cwd2, entries, opts = {}) {
|
|
934
|
+
const result = {
|
|
935
|
+
created: [],
|
|
936
|
+
current: [],
|
|
937
|
+
propagated: [],
|
|
938
|
+
preserved: [],
|
|
939
|
+
overwritten: []
|
|
940
|
+
};
|
|
941
|
+
const plans = [];
|
|
942
|
+
const toBackup = [];
|
|
943
|
+
for (const entry of entries) {
|
|
944
|
+
const abs = join6(cwd2, entry.relPath);
|
|
945
|
+
if (!existsSync5(abs)) {
|
|
946
|
+
plans.push({ entry, action: "create" });
|
|
947
|
+
continue;
|
|
948
|
+
}
|
|
949
|
+
const onDisk = readFileSync5(abs, "utf8");
|
|
950
|
+
const stamp = readStamp(onDisk);
|
|
951
|
+
const onDiskBody = stamp ? stamp.body : onDisk;
|
|
952
|
+
if (onDiskBody === entry.content) {
|
|
953
|
+
plans.push({ entry, action: "current" });
|
|
954
|
+
continue;
|
|
955
|
+
}
|
|
956
|
+
if (stamp && stamp.hash === bodyFingerprint(stamp.body)) {
|
|
957
|
+
plans.push({ entry, action: "propagate" });
|
|
958
|
+
continue;
|
|
959
|
+
}
|
|
960
|
+
if (opts.force) {
|
|
961
|
+
plans.push({ entry, action: "overwrite" });
|
|
962
|
+
toBackup.push(entry);
|
|
963
|
+
} else {
|
|
964
|
+
plans.push({ entry, action: "preserve" });
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
if (toBackup.length > 0) {
|
|
968
|
+
if (!opts.backupRoot) {
|
|
969
|
+
throw new Error(
|
|
970
|
+
"reconcileGeneratedFiles: force is set and hand-edited generated files would be overwritten, but no backupRoot was provided. Refusing to overwrite without a backup."
|
|
971
|
+
);
|
|
972
|
+
}
|
|
973
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
974
|
+
const backupDir = join6(opts.backupRoot, `derived-${stamp}`);
|
|
975
|
+
try {
|
|
976
|
+
for (const entry of toBackup) {
|
|
977
|
+
const dest = join6(backupDir, entry.relPath);
|
|
978
|
+
mkdirSync3(resolve2(dest, ".."), { recursive: true });
|
|
979
|
+
writeFileSync3(dest, readFileSync5(join6(cwd2, entry.relPath), "utf8"), "utf8");
|
|
980
|
+
}
|
|
981
|
+
} catch (err) {
|
|
982
|
+
throw new Error(
|
|
983
|
+
`Could not back up existing generated files to ${backupDir} (${err instanceof Error ? err.message : String(err)}). Aborting WITHOUT overwriting anything \u2014 no file was modified.`
|
|
984
|
+
);
|
|
985
|
+
}
|
|
986
|
+
result.backupDir = backupDir;
|
|
987
|
+
}
|
|
988
|
+
for (const { entry, action } of plans) {
|
|
989
|
+
const abs = join6(cwd2, entry.relPath);
|
|
990
|
+
switch (action) {
|
|
991
|
+
case "current":
|
|
992
|
+
result.current.push(entry.relPath);
|
|
993
|
+
break;
|
|
994
|
+
case "preserve":
|
|
995
|
+
result.preserved.push(entry.relPath);
|
|
996
|
+
break;
|
|
997
|
+
case "create":
|
|
998
|
+
mkdirSync3(resolve2(abs, ".."), { recursive: true });
|
|
999
|
+
writeFileSync3(abs, stampGenerated(entry.content), "utf8");
|
|
1000
|
+
result.created.push(entry.relPath);
|
|
1001
|
+
break;
|
|
1002
|
+
case "propagate":
|
|
1003
|
+
mkdirSync3(resolve2(abs, ".."), { recursive: true });
|
|
1004
|
+
writeFileSync3(abs, stampGenerated(entry.content), "utf8");
|
|
1005
|
+
result.propagated.push(entry.relPath);
|
|
1006
|
+
break;
|
|
1007
|
+
case "overwrite":
|
|
1008
|
+
mkdirSync3(resolve2(abs, ".."), { recursive: true });
|
|
1009
|
+
writeFileSync3(abs, stampGenerated(entry.content), "utf8");
|
|
1010
|
+
result.overwritten.push(entry.relPath);
|
|
1011
|
+
break;
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
return result;
|
|
1015
|
+
}
|
|
839
1016
|
function appendGitignore(cwd2) {
|
|
840
|
-
const giPath =
|
|
841
|
-
const existing =
|
|
1017
|
+
const giPath = join6(cwd2, ".gitignore");
|
|
1018
|
+
const existing = existsSync5(giPath) ? readFileSync5(giPath, "utf8") : "";
|
|
842
1019
|
const toAdd = GITIGNORE_ENTRIES.split("\n").filter((line) => line && !existing.includes(line)).join("\n");
|
|
843
1020
|
if (toAdd.trim()) {
|
|
844
1021
|
writeFileSync3(giPath, existing + (existing.endsWith("\n") ? "" : "\n") + toAdd + "\n", "utf8");
|
|
@@ -850,36 +1027,34 @@ function slugify(title) {
|
|
|
850
1027
|
function writeSkills(cwd2, skillsDir) {
|
|
851
1028
|
const skillNames = ["ahk-ask", "ahk-consultant", "ahk-triage", "ahk-review"];
|
|
852
1029
|
for (const skillName of skillNames) {
|
|
853
|
-
const src =
|
|
854
|
-
const destDir =
|
|
855
|
-
const dest =
|
|
1030
|
+
const src = join6(__dirname2, "skills", skillName, "SKILL.md");
|
|
1031
|
+
const destDir = join6(cwd2, skillsDir, skillName);
|
|
1032
|
+
const dest = join6(destDir, "SKILL.md");
|
|
856
1033
|
mkdirSync3(destDir, { recursive: true });
|
|
857
|
-
writeFileSync3(dest,
|
|
1034
|
+
writeFileSync3(dest, readFileSync5(src, "utf8"), "utf8");
|
|
858
1035
|
}
|
|
859
1036
|
}
|
|
860
1037
|
|
|
861
1038
|
// src/core/materializer/claude-code.ts
|
|
862
|
-
function claudeAgentFiles(config) {
|
|
1039
|
+
function claudeAgentFiles(config, modelsByRole) {
|
|
863
1040
|
const projectName = config.project.name;
|
|
864
1041
|
return [
|
|
865
|
-
{ relPath: ".claude/agents/lead.md", content: translateFrontmatterForClaudeCode(agentLead({ projectName }), "lead") },
|
|
866
|
-
{ relPath: ".claude/agents/explorer.md", content: translateFrontmatterForClaudeCode(agentExplorer({ projectName }), "explorer") },
|
|
867
|
-
{ relPath: ".claude/agents/consultant.md", content: translateFrontmatterForClaudeCode(agentConsultant({ projectName }), "consultant") },
|
|
868
|
-
{ relPath: ".claude/agents/builder.md", content: translateFrontmatterForClaudeCode(agentBuilder({ projectName }), "builder") },
|
|
869
|
-
{ relPath: ".claude/agents/reviewer.md", content: translateFrontmatterForClaudeCode(agentReviewer({ projectName }), "reviewer") }
|
|
1042
|
+
{ relPath: ".claude/agents/lead.md", content: translateFrontmatterForClaudeCode(agentLead({ projectName }), "lead", { model: modelsByRole?.lead }) },
|
|
1043
|
+
{ relPath: ".claude/agents/explorer.md", content: translateFrontmatterForClaudeCode(agentExplorer({ projectName }), "explorer", { model: modelsByRole?.explorer }) },
|
|
1044
|
+
{ relPath: ".claude/agents/consultant.md", content: translateFrontmatterForClaudeCode(agentConsultant({ projectName }), "consultant", { model: modelsByRole?.consultant }) },
|
|
1045
|
+
{ relPath: ".claude/agents/builder.md", content: translateFrontmatterForClaudeCode(agentBuilder({ projectName }), "builder", { model: modelsByRole?.builder }) },
|
|
1046
|
+
{ relPath: ".claude/agents/reviewer.md", content: translateFrontmatterForClaudeCode(agentReviewer({ projectName }), "reviewer", { model: modelsByRole?.reviewer }) }
|
|
870
1047
|
];
|
|
871
1048
|
}
|
|
872
1049
|
var ClaudeCodeMaterializer = class {
|
|
873
1050
|
async scaffold(config, opts) {
|
|
874
|
-
const { cwd: cwd2 } = opts;
|
|
875
|
-
write(cwd2, "AGENTS.md", agentsMd(config));
|
|
876
|
-
write(cwd2, "CLAUDE.md", claudeMd(config));
|
|
877
|
-
if (!
|
|
1051
|
+
const { cwd: cwd2, claudeAgentModels } = opts;
|
|
1052
|
+
write(cwd2, "AGENTS.md", stampGenerated(agentsMd(config)));
|
|
1053
|
+
write(cwd2, "CLAUDE.md", stampGenerated(claudeMd(config)));
|
|
1054
|
+
if (!existsSync6(join7(cwd2, "health.sh"))) {
|
|
878
1055
|
write(cwd2, "health.sh", HEALTH_SH, 493);
|
|
879
1056
|
}
|
|
880
|
-
|
|
881
|
-
write(cwd2, "feature_list.json", featureListJson(tasks));
|
|
882
|
-
if (config.storage.scope === "local" && !existsSync4(join5(cwd2, config.storage.markdownFallback.path))) {
|
|
1057
|
+
if (config.storage.scope === "local" && !existsSync6(join7(cwd2, config.storage.markdownFallback.path))) {
|
|
883
1058
|
write(
|
|
884
1059
|
cwd2,
|
|
885
1060
|
config.storage.markdownFallback.path,
|
|
@@ -892,30 +1067,31 @@ No tasks in progress.
|
|
|
892
1067
|
`
|
|
893
1068
|
);
|
|
894
1069
|
}
|
|
895
|
-
writeAgentFiles(cwd2, claudeAgentFiles(config));
|
|
896
|
-
mergeClaudeMcpJson(
|
|
897
|
-
mergeClaudeSettingsJson(
|
|
898
|
-
mergeClaudeSettingsLocalJson(
|
|
1070
|
+
writeAgentFiles(cwd2, claudeAgentFiles(config, claudeAgentModels));
|
|
1071
|
+
mergeClaudeMcpJson(join7(cwd2, ".mcp.json"), config.tools.mcp.port, cwd2, detectPackageManager(cwd2));
|
|
1072
|
+
mergeClaudeSettingsJson(join7(cwd2, ".claude/settings.json"));
|
|
1073
|
+
mergeClaudeSettingsLocalJson(join7(cwd2, ".claude/settings.local.json"));
|
|
899
1074
|
appendGitignore(cwd2);
|
|
900
1075
|
writeSkills(cwd2, ".claude/skills");
|
|
901
1076
|
}
|
|
902
1077
|
async build(config, cwd2, opts = {}) {
|
|
903
|
-
const
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
1078
|
+
const derived = reconcileGeneratedFiles(
|
|
1079
|
+
cwd2,
|
|
1080
|
+
[
|
|
1081
|
+
{ relPath: "AGENTS.md", content: agentsMd(config) },
|
|
1082
|
+
{ relPath: "CLAUDE.md", content: claudeMd(config) }
|
|
1083
|
+
],
|
|
1084
|
+
{ force: opts.force, backupRoot: join7(cwd2, config.storage.dir, "backups") }
|
|
1085
|
+
);
|
|
910
1086
|
const agents = writeAgentFiles(cwd2, claudeAgentFiles(config), {
|
|
911
1087
|
force: opts.force,
|
|
912
|
-
backupRoot:
|
|
1088
|
+
backupRoot: join7(cwd2, config.storage.dir, "backups")
|
|
913
1089
|
});
|
|
914
|
-
mergeClaudeMcpJson(
|
|
915
|
-
mergeClaudeSettingsJson(
|
|
916
|
-
mergeClaudeSettingsLocalJson(
|
|
1090
|
+
mergeClaudeMcpJson(join7(cwd2, ".mcp.json"), config.tools.mcp.port, cwd2, detectPackageManager(cwd2));
|
|
1091
|
+
mergeClaudeSettingsJson(join7(cwd2, ".claude/settings.json"));
|
|
1092
|
+
mergeClaudeSettingsLocalJson(join7(cwd2, ".claude/settings.local.json"));
|
|
917
1093
|
writeSkills(cwd2, ".claude/skills");
|
|
918
|
-
return { agents };
|
|
1094
|
+
return { agents, derived };
|
|
919
1095
|
}
|
|
920
1096
|
async migrate(config, _to, _cwd) {
|
|
921
1097
|
void config;
|
|
@@ -942,8 +1118,8 @@ No tasks in progress.
|
|
|
942
1118
|
};
|
|
943
1119
|
|
|
944
1120
|
// src/core/materializer/codex-cli.ts
|
|
945
|
-
import { existsSync as
|
|
946
|
-
import { join as
|
|
1121
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
1122
|
+
import { join as join8, resolve as resolve3 } from "path";
|
|
947
1123
|
function codexAgentFiles(config) {
|
|
948
1124
|
const projectName = config.project.name;
|
|
949
1125
|
return [
|
|
@@ -959,17 +1135,15 @@ var CodexCliMaterializer = class {
|
|
|
959
1135
|
async scaffold(config, opts) {
|
|
960
1136
|
const { cwd: cwd2 } = opts;
|
|
961
1137
|
const write2 = (relPath, content, mode) => {
|
|
962
|
-
const abs =
|
|
963
|
-
|
|
964
|
-
|
|
1138
|
+
const abs = join8(cwd2, relPath);
|
|
1139
|
+
mkdirSync4(resolve3(abs, ".."), { recursive: true });
|
|
1140
|
+
writeFileSync4(abs, content, { encoding: "utf8", mode });
|
|
965
1141
|
};
|
|
966
|
-
write2("AGENTS.md", agentsMd(config));
|
|
967
|
-
if (!
|
|
1142
|
+
write2("AGENTS.md", stampGenerated(agentsMd(config)));
|
|
1143
|
+
if (!existsSync7(join8(cwd2, "health.sh"))) {
|
|
968
1144
|
write2("health.sh", HEALTH_SH, 493);
|
|
969
1145
|
}
|
|
970
|
-
|
|
971
|
-
write2(join6(config.storage.dir, "feature_list.json"), featureListJson(tasks));
|
|
972
|
-
if (config.storage.scope === "local" && !existsSync5(join6(cwd2, config.storage.markdownFallback.path))) {
|
|
1146
|
+
if (config.storage.scope === "local" && !existsSync7(join8(cwd2, config.storage.markdownFallback.path))) {
|
|
973
1147
|
write2(
|
|
974
1148
|
config.storage.markdownFallback.path,
|
|
975
1149
|
`<!-- AUTO-GENERATED by agent-harness-kit \u2014 DO NOT EDIT MANUALLY -->
|
|
@@ -982,24 +1156,23 @@ No tasks in progress.
|
|
|
982
1156
|
);
|
|
983
1157
|
}
|
|
984
1158
|
writeAgentFiles(cwd2, codexAgentFiles(config));
|
|
985
|
-
mergeCodexConfigToml(
|
|
1159
|
+
mergeCodexConfigToml(join8(cwd2, ".codex/config.toml"), config.tools.mcp.port, cwd2, detectPackageManager(cwd2));
|
|
986
1160
|
appendGitignore(cwd2);
|
|
987
1161
|
writeSkills(cwd2, ".agents/skills");
|
|
988
1162
|
}
|
|
989
1163
|
async build(config, cwd2, opts = {}) {
|
|
990
|
-
const
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
write2("AGENTS.md", agentsMd(config));
|
|
1164
|
+
const derived = reconcileGeneratedFiles(
|
|
1165
|
+
cwd2,
|
|
1166
|
+
[{ relPath: "AGENTS.md", content: agentsMd(config) }],
|
|
1167
|
+
{ force: opts.force, backupRoot: join8(cwd2, config.storage.dir, "backups") }
|
|
1168
|
+
);
|
|
996
1169
|
const agents = writeAgentFiles(cwd2, codexAgentFiles(config), {
|
|
997
1170
|
force: opts.force,
|
|
998
|
-
backupRoot:
|
|
1171
|
+
backupRoot: join8(cwd2, config.storage.dir, "backups")
|
|
999
1172
|
});
|
|
1000
|
-
mergeCodexConfigToml(
|
|
1173
|
+
mergeCodexConfigToml(join8(cwd2, ".codex/config.toml"), config.tools.mcp.port, cwd2, detectPackageManager(cwd2));
|
|
1001
1174
|
writeSkills(cwd2, ".agents/skills");
|
|
1002
|
-
return { agents };
|
|
1175
|
+
return { agents, derived };
|
|
1003
1176
|
}
|
|
1004
1177
|
async migrate(config, _to, _cwd) {
|
|
1005
1178
|
void config;
|
|
@@ -1009,9 +1182,73 @@ No tasks in progress.
|
|
|
1009
1182
|
}
|
|
1010
1183
|
};
|
|
1011
1184
|
|
|
1185
|
+
// src/core/materializer/grok.ts
|
|
1186
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync5, writeFileSync as writeFileSync5 } from "fs";
|
|
1187
|
+
import { join as join9, resolve as resolve4 } from "path";
|
|
1188
|
+
function grokAgentFiles(config) {
|
|
1189
|
+
const projectName = config.project.name;
|
|
1190
|
+
return [
|
|
1191
|
+
{ relPath: ".grok/agents/lead.md", content: translateFrontmatterForGrok(agentLead({ projectName }), "lead") },
|
|
1192
|
+
{ relPath: ".grok/agents/explorer.md", content: translateFrontmatterForGrok(agentExplorer({ projectName }), "explorer") },
|
|
1193
|
+
{ relPath: ".grok/agents/consultant.md", content: translateFrontmatterForGrok(agentConsultant({ projectName }), "consultant") },
|
|
1194
|
+
{ relPath: ".grok/agents/builder.md", content: translateFrontmatterForGrok(agentBuilder({ projectName }), "builder") },
|
|
1195
|
+
{ relPath: ".grok/agents/reviewer.md", content: translateFrontmatterForGrok(agentReviewer({ projectName }), "reviewer") }
|
|
1196
|
+
];
|
|
1197
|
+
}
|
|
1198
|
+
var GrokMaterializer = class {
|
|
1199
|
+
async scaffold(config, opts) {
|
|
1200
|
+
const { cwd: cwd2 } = opts;
|
|
1201
|
+
const write2 = (relPath, content, mode) => {
|
|
1202
|
+
const abs = join9(cwd2, relPath);
|
|
1203
|
+
mkdirSync5(resolve4(abs, ".."), { recursive: true });
|
|
1204
|
+
writeFileSync5(abs, content, { encoding: "utf8", mode });
|
|
1205
|
+
};
|
|
1206
|
+
write2("AGENTS.md", stampGenerated(agentsMd(config)));
|
|
1207
|
+
if (!existsSync8(join9(cwd2, "health.sh"))) {
|
|
1208
|
+
write2("health.sh", HEALTH_SH, 493);
|
|
1209
|
+
}
|
|
1210
|
+
if (config.storage.scope === "local" && !existsSync8(join9(cwd2, config.storage.markdownFallback.path))) {
|
|
1211
|
+
write2(
|
|
1212
|
+
config.storage.markdownFallback.path,
|
|
1213
|
+
`<!-- AUTO-GENERATED by agent-harness-kit \u2014 DO NOT EDIT MANUALLY -->
|
|
1214
|
+
<!-- Run ahk status to refresh -->
|
|
1215
|
+
|
|
1216
|
+
# Current Session
|
|
1217
|
+
|
|
1218
|
+
No tasks in progress.
|
|
1219
|
+
`
|
|
1220
|
+
);
|
|
1221
|
+
}
|
|
1222
|
+
writeAgentFiles(cwd2, grokAgentFiles(config));
|
|
1223
|
+
mergeGrokConfigToml(join9(cwd2, ".grok/config.toml"), config.tools.mcp.port, cwd2, detectPackageManager(cwd2));
|
|
1224
|
+
appendGitignore(cwd2);
|
|
1225
|
+
writeSkills(cwd2, ".grok/skills");
|
|
1226
|
+
}
|
|
1227
|
+
async build(config, cwd2, opts = {}) {
|
|
1228
|
+
const derived = reconcileGeneratedFiles(
|
|
1229
|
+
cwd2,
|
|
1230
|
+
[{ relPath: "AGENTS.md", content: agentsMd(config) }],
|
|
1231
|
+
{ force: opts.force, backupRoot: join9(cwd2, config.storage.dir, "backups") }
|
|
1232
|
+
);
|
|
1233
|
+
const agents = writeAgentFiles(cwd2, grokAgentFiles(config), {
|
|
1234
|
+
force: opts.force,
|
|
1235
|
+
backupRoot: join9(cwd2, config.storage.dir, "backups")
|
|
1236
|
+
});
|
|
1237
|
+
mergeGrokConfigToml(join9(cwd2, ".grok/config.toml"), config.tools.mcp.port, cwd2, detectPackageManager(cwd2));
|
|
1238
|
+
writeSkills(cwd2, ".grok/skills");
|
|
1239
|
+
return { agents, derived };
|
|
1240
|
+
}
|
|
1241
|
+
async migrate(config, _to, _cwd) {
|
|
1242
|
+
void config;
|
|
1243
|
+
}
|
|
1244
|
+
async syncPermissions(_cwd) {
|
|
1245
|
+
console.log(" Permissions sync not needed for grok-cli \u2014 skipping");
|
|
1246
|
+
}
|
|
1247
|
+
};
|
|
1248
|
+
|
|
1012
1249
|
// src/core/materializer/opencode.ts
|
|
1013
|
-
import { existsSync as
|
|
1014
|
-
import { join as
|
|
1250
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync6, writeFileSync as writeFileSync6 } from "fs";
|
|
1251
|
+
import { join as join10, resolve as resolve5 } from "path";
|
|
1015
1252
|
function opencodeAgentFiles(config) {
|
|
1016
1253
|
const projectName = config.project.name;
|
|
1017
1254
|
return [
|
|
@@ -1026,17 +1263,15 @@ var OpenCodeMaterializer = class {
|
|
|
1026
1263
|
async scaffold(config, opts) {
|
|
1027
1264
|
const { cwd: cwd2 } = opts;
|
|
1028
1265
|
const write2 = (relPath, content, mode) => {
|
|
1029
|
-
const abs =
|
|
1266
|
+
const abs = join10(cwd2, relPath);
|
|
1030
1267
|
mkdirSync6(resolve5(abs, ".."), { recursive: true });
|
|
1031
1268
|
writeFileSync6(abs, content, { encoding: "utf8", mode });
|
|
1032
1269
|
};
|
|
1033
|
-
write2("AGENTS.md", agentsMd(config));
|
|
1034
|
-
if (!
|
|
1270
|
+
write2("AGENTS.md", stampGenerated(agentsMd(config)));
|
|
1271
|
+
if (!existsSync9(join10(cwd2, "health.sh"))) {
|
|
1035
1272
|
write2("health.sh", HEALTH_SH, 493);
|
|
1036
1273
|
}
|
|
1037
|
-
|
|
1038
|
-
write2(join7(config.storage.dir, "feature_list.json"), featureListJson(tasks));
|
|
1039
|
-
if (config.storage.scope === "local" && !existsSync6(join7(cwd2, config.storage.markdownFallback.path))) {
|
|
1274
|
+
if (config.storage.scope === "local" && !existsSync9(join10(cwd2, config.storage.markdownFallback.path))) {
|
|
1040
1275
|
write2(
|
|
1041
1276
|
config.storage.markdownFallback.path,
|
|
1042
1277
|
`<!-- AUTO-GENERATED by agent-harness-kit \u2014 DO NOT EDIT MANUALLY -->
|
|
@@ -1049,24 +1284,23 @@ No tasks in progress.
|
|
|
1049
1284
|
);
|
|
1050
1285
|
}
|
|
1051
1286
|
writeAgentFiles(cwd2, opencodeAgentFiles(config));
|
|
1052
|
-
mergeOpencodeJson(
|
|
1287
|
+
mergeOpencodeJson(join10(cwd2, "opencode.json"), config.tools.mcp.port, cwd2, detectPackageManager(cwd2));
|
|
1053
1288
|
appendGitignore(cwd2);
|
|
1054
1289
|
writeSkills(cwd2, ".opencode/skills");
|
|
1055
1290
|
}
|
|
1056
1291
|
async build(config, cwd2, opts = {}) {
|
|
1057
|
-
const
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
write2("AGENTS.md", agentsMd(config));
|
|
1292
|
+
const derived = reconcileGeneratedFiles(
|
|
1293
|
+
cwd2,
|
|
1294
|
+
[{ relPath: "AGENTS.md", content: agentsMd(config) }],
|
|
1295
|
+
{ force: opts.force, backupRoot: join10(cwd2, config.storage.dir, "backups") }
|
|
1296
|
+
);
|
|
1063
1297
|
const agents = writeAgentFiles(cwd2, opencodeAgentFiles(config), {
|
|
1064
1298
|
force: opts.force,
|
|
1065
|
-
backupRoot:
|
|
1299
|
+
backupRoot: join10(cwd2, config.storage.dir, "backups")
|
|
1066
1300
|
});
|
|
1067
|
-
mergeOpencodeJson(
|
|
1301
|
+
mergeOpencodeJson(join10(cwd2, "opencode.json"), config.tools.mcp.port, cwd2, detectPackageManager(cwd2));
|
|
1068
1302
|
writeSkills(cwd2, ".opencode/skills");
|
|
1069
|
-
return { agents };
|
|
1303
|
+
return { agents, derived };
|
|
1070
1304
|
}
|
|
1071
1305
|
async migrate(config, _to, _cwd) {
|
|
1072
1306
|
void config;
|
|
@@ -1085,6 +1319,8 @@ function getMaterializer(provider) {
|
|
|
1085
1319
|
return new OpenCodeMaterializer();
|
|
1086
1320
|
case "codex-cli":
|
|
1087
1321
|
return new CodexCliMaterializer();
|
|
1322
|
+
case "grok-cli":
|
|
1323
|
+
return new GrokMaterializer();
|
|
1088
1324
|
default:
|
|
1089
1325
|
throw new Error(`Unknown provider: ${provider}`);
|
|
1090
1326
|
}
|
|
@@ -1119,8 +1355,37 @@ async function buildOnce(cwd2, force) {
|
|
|
1119
1355
|
spinner6.message("Rebuilding files...");
|
|
1120
1356
|
const materializer = getMaterializer(config.provider);
|
|
1121
1357
|
const report = await materializer.build(config, cwd2, { force });
|
|
1122
|
-
spinner6.stop(
|
|
1123
|
-
|
|
1358
|
+
spinner6.stop(pc2.green("Build complete"));
|
|
1359
|
+
const d = report.derived;
|
|
1360
|
+
const upToDate = [...d.created, ...d.current, ...d.propagated];
|
|
1361
|
+
if (upToDate.length > 0) {
|
|
1362
|
+
p.log.success(upToDate.join(", "));
|
|
1363
|
+
}
|
|
1364
|
+
if (d.propagated.length > 0) {
|
|
1365
|
+
p.log.info(`Propagated config changes to ${d.propagated.length} generated file(s):
|
|
1366
|
+
${d.propagated.join("\n ")}`);
|
|
1367
|
+
}
|
|
1368
|
+
if (d.overwritten.length > 0) {
|
|
1369
|
+
p.log.warn(
|
|
1370
|
+
pc2.yellow(
|
|
1371
|
+
`--force REGENERATED ${d.overwritten.length} hand-edited generated file(s), discarding your edits:
|
|
1372
|
+
` + d.overwritten.join("\n ")
|
|
1373
|
+
)
|
|
1374
|
+
);
|
|
1375
|
+
if (d.backupDir) {
|
|
1376
|
+
p.log.info(pc2.yellow(` Previous content backed up \u2192 ${d.backupDir}`));
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
if (d.preserved.length > 0) {
|
|
1380
|
+
p.log.warn(
|
|
1381
|
+
pc2.yellow(
|
|
1382
|
+
`Left ${d.preserved.length} hand-edited generated file(s) UNTOUCHED \u2014 your edits are safe:
|
|
1383
|
+
` + d.preserved.join("\n ") + `
|
|
1384
|
+
These no longer match the current config. Re-run with --force to regenerate them
|
|
1385
|
+
(this DESTROYS your edits; a backup is written first).`
|
|
1386
|
+
)
|
|
1387
|
+
);
|
|
1388
|
+
}
|
|
1124
1389
|
p.log.success(`Agent definitions (${config.provider})`);
|
|
1125
1390
|
p.log.success("MCP config");
|
|
1126
1391
|
const { created, overwritten, preserved, backupDir } = report.agents;
|
|
@@ -1130,13 +1395,13 @@ async function buildOnce(cwd2, force) {
|
|
|
1130
1395
|
}
|
|
1131
1396
|
if (overwritten.length > 0) {
|
|
1132
1397
|
p.log.warn(
|
|
1133
|
-
|
|
1398
|
+
pc2.yellow(
|
|
1134
1399
|
`--force REGENERATED ${overwritten.length} existing agent file(s), discarding any customizations:
|
|
1135
1400
|
` + overwritten.join("\n ")
|
|
1136
1401
|
)
|
|
1137
1402
|
);
|
|
1138
1403
|
if (backupDir) {
|
|
1139
|
-
p.log.info(
|
|
1404
|
+
p.log.info(pc2.yellow(` Previous content backed up \u2192 ${backupDir}`));
|
|
1140
1405
|
}
|
|
1141
1406
|
}
|
|
1142
1407
|
if (preserved.length > 0) {
|
|
@@ -1147,7 +1412,7 @@ async function buildOnce(cwd2, force) {
|
|
|
1147
1412
|
);
|
|
1148
1413
|
}
|
|
1149
1414
|
} catch (err) {
|
|
1150
|
-
spinner6.stop(
|
|
1415
|
+
spinner6.stop(pc2.red("Build failed"));
|
|
1151
1416
|
p.log.error(err instanceof Error ? err.message : String(err));
|
|
1152
1417
|
process.exit(1);
|
|
1153
1418
|
}
|
|
@@ -1155,34 +1420,41 @@ async function buildOnce(cwd2, force) {
|
|
|
1155
1420
|
|
|
1156
1421
|
// src/commands/dashboard.ts
|
|
1157
1422
|
import { homedir } from "os";
|
|
1158
|
-
import { dirname as
|
|
1159
|
-
import { fileURLToPath as
|
|
1160
|
-
import
|
|
1423
|
+
import { dirname as dirname5, join as join12 } from "path";
|
|
1424
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
1425
|
+
import pc3 from "picocolors";
|
|
1161
1426
|
|
|
1162
1427
|
// src/core/dashboard-server.ts
|
|
1163
1428
|
import { watch as watch2 } from "fs";
|
|
1164
|
-
import { existsSync as
|
|
1165
|
-
import { extname, join as
|
|
1429
|
+
import { existsSync as existsSync10, readFileSync as readFileSync6 } from "fs";
|
|
1430
|
+
import { extname, join as join11 } from "path";
|
|
1166
1431
|
import { serve } from "@hono/node-server";
|
|
1167
1432
|
import { Hono } from "hono";
|
|
1168
1433
|
import { WebSocketServer } from "ws";
|
|
1169
1434
|
|
|
1170
1435
|
// src/core/port-utils.ts
|
|
1171
1436
|
import { createServer } from "net";
|
|
1172
|
-
|
|
1437
|
+
var DASHBOARD_BIND_HOST = void 0;
|
|
1438
|
+
function isPortFree(port, host = DASHBOARD_BIND_HOST) {
|
|
1173
1439
|
return new Promise((resolve12) => {
|
|
1174
1440
|
const server = createServer();
|
|
1175
1441
|
server.once("error", () => resolve12(false));
|
|
1176
1442
|
server.once("listening", () => {
|
|
1177
1443
|
server.close(() => resolve12(true));
|
|
1178
1444
|
});
|
|
1179
|
-
server.listen(port,
|
|
1445
|
+
server.listen(port, host);
|
|
1180
1446
|
});
|
|
1181
1447
|
}
|
|
1182
|
-
async function findFreePort(start,
|
|
1448
|
+
async function findFreePort(start, options = {}) {
|
|
1449
|
+
const { maxAttempts = 10, host = DASHBOARD_BIND_HOST } = options;
|
|
1450
|
+
if (typeof start !== "number" || !Number.isInteger(start)) {
|
|
1451
|
+
throw new Error(
|
|
1452
|
+
`findFreePort requires an integer port number, received ${typeof start} ${JSON.stringify(start)}. The port must be coerced to a number before it reaches here (commander supplies --port as a string).`
|
|
1453
|
+
);
|
|
1454
|
+
}
|
|
1183
1455
|
for (let i = 0; i < maxAttempts; i++) {
|
|
1184
1456
|
const port = start + i;
|
|
1185
|
-
if (await isPortFree(port)) return port;
|
|
1457
|
+
if (await isPortFree(port, host)) return port;
|
|
1186
1458
|
}
|
|
1187
1459
|
throw new Error(
|
|
1188
1460
|
`Could not find a free port after ${maxAttempts} attempts (tried ${start}-${start + maxAttempts - 1}). Please free a port and try again.`
|
|
@@ -1204,12 +1476,42 @@ var MIME = {
|
|
|
1204
1476
|
".ttf": "font/ttf"
|
|
1205
1477
|
};
|
|
1206
1478
|
function fileResponse(filePath) {
|
|
1207
|
-
const content =
|
|
1479
|
+
const content = readFileSync6(filePath);
|
|
1208
1480
|
const mime = MIME[extname(filePath)] ?? "application/octet-stream";
|
|
1209
1481
|
return new Response(content, {
|
|
1210
1482
|
headers: { "Content-Type": mime, "Cache-Control": "no-cache" }
|
|
1211
1483
|
});
|
|
1212
1484
|
}
|
|
1485
|
+
function awaitServerListening(server, port) {
|
|
1486
|
+
return new Promise((resolve12, reject) => {
|
|
1487
|
+
const closeQuietly = () => {
|
|
1488
|
+
try {
|
|
1489
|
+
server.close(() => {
|
|
1490
|
+
});
|
|
1491
|
+
} catch {
|
|
1492
|
+
}
|
|
1493
|
+
};
|
|
1494
|
+
const onError = (err) => {
|
|
1495
|
+
server.off("listening", onListening);
|
|
1496
|
+
closeQuietly();
|
|
1497
|
+
if (err.code === "EADDRINUSE") {
|
|
1498
|
+
reject(
|
|
1499
|
+
new Error(
|
|
1500
|
+
`Port ${port} was taken by another process while starting the dashboard. Please retry, or pick a different port with --port.`
|
|
1501
|
+
)
|
|
1502
|
+
);
|
|
1503
|
+
return;
|
|
1504
|
+
}
|
|
1505
|
+
reject(new Error(`Failed to start the dashboard on port ${port}: ${err.message}`));
|
|
1506
|
+
};
|
|
1507
|
+
const onListening = () => {
|
|
1508
|
+
server.off("error", onError);
|
|
1509
|
+
resolve12();
|
|
1510
|
+
};
|
|
1511
|
+
server.once("error", onError);
|
|
1512
|
+
server.once("listening", onListening);
|
|
1513
|
+
});
|
|
1514
|
+
}
|
|
1213
1515
|
async function startDashboardServer(db, dbPath, staticPath, port) {
|
|
1214
1516
|
const app = new Hono();
|
|
1215
1517
|
const { tasks, actions, stats } = db;
|
|
@@ -1310,21 +1612,29 @@ async function startDashboardServer(db, dbPath, staticPath, port) {
|
|
|
1310
1612
|
app.get("/*", (c) => {
|
|
1311
1613
|
const urlPath = c.req.path;
|
|
1312
1614
|
if (urlPath !== "/") {
|
|
1313
|
-
const candidate =
|
|
1314
|
-
if (
|
|
1615
|
+
const candidate = join11(staticPath, urlPath);
|
|
1616
|
+
if (existsSync10(candidate)) {
|
|
1315
1617
|
try {
|
|
1316
1618
|
return fileResponse(candidate);
|
|
1317
1619
|
} catch {
|
|
1318
1620
|
}
|
|
1319
1621
|
}
|
|
1320
1622
|
}
|
|
1321
|
-
return fileResponse(
|
|
1623
|
+
return fileResponse(join11(staticPath, "index.html"));
|
|
1322
1624
|
});
|
|
1323
|
-
const resolvedPort = await findFreePort(port);
|
|
1625
|
+
const resolvedPort = await findFreePort(port, { host: DASHBOARD_BIND_HOST });
|
|
1324
1626
|
if (resolvedPort !== port) {
|
|
1325
1627
|
console.log(`Port ${port} in use, using ${resolvedPort}`);
|
|
1326
1628
|
}
|
|
1327
|
-
const httpServer = serve({
|
|
1629
|
+
const httpServer = serve({
|
|
1630
|
+
fetch: app.fetch,
|
|
1631
|
+
port: resolvedPort,
|
|
1632
|
+
hostname: DASHBOARD_BIND_HOST
|
|
1633
|
+
});
|
|
1634
|
+
await awaitServerListening(httpServer, resolvedPort);
|
|
1635
|
+
httpServer.on("error", (err) => {
|
|
1636
|
+
console.error(`Dashboard server error: ${err.message}`);
|
|
1637
|
+
});
|
|
1328
1638
|
const wss = new WebSocketServer({ noServer: true });
|
|
1329
1639
|
httpServer.on("upgrade", (req, socket, head) => {
|
|
1330
1640
|
if (req.url === "/ws") {
|
|
@@ -1349,7 +1659,7 @@ async function startDashboardServer(db, dbPath, staticPath, port) {
|
|
|
1349
1659
|
let watcher = null;
|
|
1350
1660
|
if (dbPath) {
|
|
1351
1661
|
const walPath = `${dbPath}-wal`;
|
|
1352
|
-
const watchTarget =
|
|
1662
|
+
const watchTarget = existsSync10(walPath) ? walPath : dbPath;
|
|
1353
1663
|
watcher = watch2(watchTarget, broadcast);
|
|
1354
1664
|
}
|
|
1355
1665
|
return {
|
|
@@ -1364,16 +1674,16 @@ async function startDashboardServer(db, dbPath, staticPath, port) {
|
|
|
1364
1674
|
}
|
|
1365
1675
|
|
|
1366
1676
|
// src/commands/dashboard.ts
|
|
1367
|
-
var __dirname3 =
|
|
1677
|
+
var __dirname3 = dirname5(fileURLToPath4(import.meta.url));
|
|
1368
1678
|
async function runDashboard(cwd2, opts) {
|
|
1369
1679
|
const config = await loadConfig(cwd2);
|
|
1370
1680
|
const db = await openDB(config, cwd2);
|
|
1371
1681
|
const dbPath = config.database.type === "sqlite" ? resolveSqlitePath(config, cwd2, homedir()) : null;
|
|
1372
|
-
const staticPath =
|
|
1682
|
+
const staticPath = join12(__dirname3, "dashboard-dist");
|
|
1373
1683
|
const { url } = await startDashboardServer(db, dbPath, staticPath, opts.port);
|
|
1374
|
-
console.log(
|
|
1375
|
-
console.log(
|
|
1376
|
-
console.log(
|
|
1684
|
+
console.log(pc3.green(`\u2713`) + ` Dashboard running at ${pc3.bold(pc3.cyan(url))}`);
|
|
1685
|
+
console.log(pc3.dim(` WebSocket live updates enabled`));
|
|
1686
|
+
console.log(pc3.dim(` Press Ctrl+C to stop`));
|
|
1377
1687
|
if (opts.open) {
|
|
1378
1688
|
const { default: open } = await import("open");
|
|
1379
1689
|
await open(url);
|
|
@@ -1386,25 +1696,12 @@ async function runDashboard(cwd2, opts) {
|
|
|
1386
1696
|
}
|
|
1387
1697
|
|
|
1388
1698
|
// src/commands/doctor.ts
|
|
1389
|
-
import
|
|
1699
|
+
import pc4 from "picocolors";
|
|
1390
1700
|
|
|
1391
1701
|
// src/core/doctor.ts
|
|
1392
|
-
import { existsSync as
|
|
1393
|
-
import { dirname as dirname6, join as
|
|
1702
|
+
import { existsSync as existsSync11, readFileSync as readFileSync7 } from "fs";
|
|
1703
|
+
import { dirname as dirname6, join as join13 } from "path";
|
|
1394
1704
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
1395
|
-
|
|
1396
|
-
// src/core/package-data.ts
|
|
1397
|
-
import { existsSync as existsSync8 } from "fs";
|
|
1398
|
-
import { createRequire } from "module";
|
|
1399
|
-
import { dirname as dirname5, join as join10 } from "path";
|
|
1400
|
-
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
1401
|
-
var require2 = createRequire(import.meta.url);
|
|
1402
|
-
var here = dirname5(fileURLToPath4(import.meta.url));
|
|
1403
|
-
var candidates = [join10(here, "..", "..", "package.json"), join10(here, "..", "package.json")];
|
|
1404
|
-
var pkgPath = candidates.find((p8) => existsSync8(p8)) ?? candidates[0];
|
|
1405
|
-
var pkg = require2(pkgPath);
|
|
1406
|
-
|
|
1407
|
-
// src/core/doctor.ts
|
|
1408
1705
|
var REGISTRY_URL = `https://registry.npmjs.org/${pkg.name}/latest`;
|
|
1409
1706
|
var TIMEOUT_MS = 2e3;
|
|
1410
1707
|
var LIB_VERSION_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
@@ -1452,19 +1749,21 @@ function getProviderAgentInfo(provider) {
|
|
|
1452
1749
|
return { agentsDir: ".opencode/agents", ext: ".md" };
|
|
1453
1750
|
case "codex-cli":
|
|
1454
1751
|
return { agentsDir: ".codex/agents", ext: ".toml" };
|
|
1752
|
+
case "grok-cli":
|
|
1753
|
+
return { agentsDir: ".grok/agents", ext: ".md" };
|
|
1455
1754
|
default:
|
|
1456
1755
|
return { agentsDir: ".claude/agents", ext: ".md" };
|
|
1457
1756
|
}
|
|
1458
1757
|
}
|
|
1459
1758
|
function checkAgentFilesAtRoot(agentsRoot, ext) {
|
|
1460
1759
|
return AGENT_NAMES.map((name) => {
|
|
1461
|
-
const filePath =
|
|
1462
|
-
return { name, status:
|
|
1760
|
+
const filePath = join13(agentsRoot, `${name}${ext}`);
|
|
1761
|
+
return { name, status: existsSync11(filePath) ? "ok" : "missing" };
|
|
1463
1762
|
});
|
|
1464
1763
|
}
|
|
1465
1764
|
function checkAgentFiles(cwd2, provider) {
|
|
1466
1765
|
const { agentsDir, ext } = getProviderAgentInfo(provider);
|
|
1467
|
-
return checkAgentFilesAtRoot(
|
|
1766
|
+
return checkAgentFilesAtRoot(join13(cwd2, agentsDir), ext);
|
|
1468
1767
|
}
|
|
1469
1768
|
function getProviderSkillsDir(provider) {
|
|
1470
1769
|
switch (provider) {
|
|
@@ -1474,21 +1773,23 @@ function getProviderSkillsDir(provider) {
|
|
|
1474
1773
|
return ".opencode/skills";
|
|
1475
1774
|
case "codex-cli":
|
|
1476
1775
|
return ".agents/skills";
|
|
1776
|
+
case "grok-cli":
|
|
1777
|
+
return ".grok/skills";
|
|
1477
1778
|
default:
|
|
1478
1779
|
return ".claude/skills";
|
|
1479
1780
|
}
|
|
1480
1781
|
}
|
|
1481
1782
|
function checkSkillsAtRoot(skillsRoot) {
|
|
1482
|
-
const skillSourceBase =
|
|
1783
|
+
const skillSourceBase = join13(__dirname4, "skills");
|
|
1483
1784
|
return SKILL_NAMES.map((name) => {
|
|
1484
|
-
const livePath =
|
|
1485
|
-
const sourcePath =
|
|
1486
|
-
if (!
|
|
1785
|
+
const livePath = join13(skillsRoot, name, "SKILL.md");
|
|
1786
|
+
const sourcePath = join13(skillSourceBase, name, "SKILL.md");
|
|
1787
|
+
if (!existsSync11(livePath)) {
|
|
1487
1788
|
return { name, status: "missing" };
|
|
1488
1789
|
}
|
|
1489
1790
|
try {
|
|
1490
|
-
const live =
|
|
1491
|
-
const source =
|
|
1791
|
+
const live = readFileSync7(livePath, "utf8");
|
|
1792
|
+
const source = readFileSync7(sourcePath, "utf8");
|
|
1492
1793
|
return { name, status: live === source ? "ok" : "outdated" };
|
|
1493
1794
|
} catch {
|
|
1494
1795
|
return { name, status: "outdated" };
|
|
@@ -1497,7 +1798,7 @@ function checkSkillsAtRoot(skillsRoot) {
|
|
|
1497
1798
|
}
|
|
1498
1799
|
function checkSkills(cwd2, provider) {
|
|
1499
1800
|
const skillsDir = getProviderSkillsDir(provider);
|
|
1500
|
-
return checkSkillsAtRoot(
|
|
1801
|
+
return checkSkillsAtRoot(join13(cwd2, skillsDir));
|
|
1501
1802
|
}
|
|
1502
1803
|
async function getDoctorStatus(cwd2) {
|
|
1503
1804
|
const lib = await checkLibVersion();
|
|
@@ -1519,16 +1820,16 @@ async function getDoctorStatus(cwd2) {
|
|
|
1519
1820
|
|
|
1520
1821
|
// src/commands/doctor.ts
|
|
1521
1822
|
function ok(label, detail) {
|
|
1522
|
-
console.log(` ${
|
|
1823
|
+
console.log(` ${pc4.cyan(label.padEnd(16))}${pc4.green("[\u2713]")} ${detail}`);
|
|
1523
1824
|
}
|
|
1524
1825
|
function warn(label, detail, hint) {
|
|
1525
|
-
console.log(` ${
|
|
1826
|
+
console.log(` ${pc4.cyan(label.padEnd(16))}${pc4.yellow("[!]")} ${detail}`);
|
|
1526
1827
|
if (hint) {
|
|
1527
|
-
console.log(` ${"".padEnd(16)} ${
|
|
1828
|
+
console.log(` ${"".padEnd(16)} ${pc4.dim(hint)}`);
|
|
1528
1829
|
}
|
|
1529
1830
|
}
|
|
1530
1831
|
function neutral(label, detail) {
|
|
1531
|
-
console.log(` ${
|
|
1832
|
+
console.log(` ${pc4.cyan(label.padEnd(16))}${pc4.dim("[~]")} ${detail}`);
|
|
1532
1833
|
}
|
|
1533
1834
|
function printLibSection(lib) {
|
|
1534
1835
|
if (lib.latest === null) {
|
|
@@ -1580,7 +1881,7 @@ async function runDoctor(cwd2) {
|
|
|
1580
1881
|
configFound = false;
|
|
1581
1882
|
}
|
|
1582
1883
|
console.log("");
|
|
1583
|
-
console.log(
|
|
1884
|
+
console.log(pc4.bold(`\u25CF ahk doctor ` + "\u2500".repeat(44)));
|
|
1584
1885
|
console.log("");
|
|
1585
1886
|
const status = await getDoctorStatus(cwd2);
|
|
1586
1887
|
printLibSection(status.lib);
|
|
@@ -1599,10 +1900,10 @@ async function runDoctor(cwd2) {
|
|
|
1599
1900
|
|
|
1600
1901
|
// src/commands/export.ts
|
|
1601
1902
|
import { writeFileSync as writeFileSync7 } from "fs";
|
|
1602
|
-
import
|
|
1903
|
+
import pc5 from "picocolors";
|
|
1603
1904
|
async function runExport(cwd2, opts) {
|
|
1604
1905
|
if (!opts.sql && !opts.json) {
|
|
1605
|
-
console.error(
|
|
1906
|
+
console.error(pc5.red("Specify --sql or --json"));
|
|
1606
1907
|
process.exit(1);
|
|
1607
1908
|
}
|
|
1608
1909
|
const config = await loadConfig(cwd2);
|
|
@@ -1613,13 +1914,13 @@ async function runExport(cwd2, opts) {
|
|
|
1613
1914
|
const out = JSON.stringify(data, null, 2) + "\n";
|
|
1614
1915
|
if (opts.output) {
|
|
1615
1916
|
writeFileSync7(opts.output, out, "utf8");
|
|
1616
|
-
console.log(
|
|
1917
|
+
console.log(pc5.green(`\u2713 Exported JSON \u2192 ${opts.output}`));
|
|
1617
1918
|
} else {
|
|
1618
1919
|
process.stdout.write(out);
|
|
1619
1920
|
}
|
|
1620
1921
|
}
|
|
1621
1922
|
if (opts.sql) {
|
|
1622
|
-
console.error(
|
|
1923
|
+
console.error(pc5.dim("SQL dump requires direct SQLite access \u2014 use: sqlite3 .harness/harness.db .dump"));
|
|
1623
1924
|
process.exit(1);
|
|
1624
1925
|
}
|
|
1625
1926
|
} finally {
|
|
@@ -1629,28 +1930,28 @@ async function runExport(cwd2, opts) {
|
|
|
1629
1930
|
|
|
1630
1931
|
// src/commands/health.ts
|
|
1631
1932
|
import { spawnSync } from "child_process";
|
|
1632
|
-
import { existsSync as
|
|
1933
|
+
import { existsSync as existsSync12 } from "fs";
|
|
1633
1934
|
import { homedir as homedir2 } from "os";
|
|
1634
|
-
import { join as
|
|
1635
|
-
import
|
|
1935
|
+
import { join as join14, resolve as resolve6 } from "path";
|
|
1936
|
+
import pc6 from "picocolors";
|
|
1636
1937
|
function checkLine(label, ok3, message, indent = 0) {
|
|
1637
|
-
const prefix = label ?
|
|
1638
|
-
const icon = ok3 ?
|
|
1639
|
-
console.log(prefix + icon + " " + (ok3 ?
|
|
1938
|
+
const prefix = label ? pc6.cyan(`[${label}] `) : " ".repeat(indent);
|
|
1939
|
+
const icon = ok3 ? pc6.green("\u2713") : pc6.red("\u2717");
|
|
1940
|
+
console.log(prefix + icon + " " + (ok3 ? pc6.green(message) : pc6.red(message)));
|
|
1640
1941
|
}
|
|
1641
1942
|
async function runHealth(cwd2) {
|
|
1642
1943
|
let config;
|
|
1643
1944
|
try {
|
|
1644
1945
|
config = await loadConfig(cwd2);
|
|
1645
1946
|
} catch {
|
|
1646
|
-
console.error(
|
|
1947
|
+
console.error(pc6.red("\u2717 No config found. Run: ahk init"));
|
|
1647
1948
|
process.exit(1);
|
|
1648
1949
|
}
|
|
1649
1950
|
let allOk = true;
|
|
1650
1951
|
let dbOk;
|
|
1651
1952
|
if (config.database.type === "sqlite") {
|
|
1652
1953
|
const dbPath = resolveSqlitePath(config, cwd2, homedir2());
|
|
1653
|
-
dbOk =
|
|
1954
|
+
dbOk = existsSync12(dbPath);
|
|
1654
1955
|
checkLine("checking DB", dbOk, `${dbPath} reachable`);
|
|
1655
1956
|
} else {
|
|
1656
1957
|
dbOk = true;
|
|
@@ -1663,8 +1964,8 @@ async function runHealth(cwd2) {
|
|
|
1663
1964
|
const agentsLabelWidth = "[checking agents] ".length;
|
|
1664
1965
|
for (let i = 0; i < agentNames.length; i++) {
|
|
1665
1966
|
const name = agentNames[i];
|
|
1666
|
-
const agentPath =
|
|
1667
|
-
const ok3 =
|
|
1967
|
+
const agentPath = join14(cwd2, agentsDir, `${name}${providerFiles.agentExtension}`);
|
|
1968
|
+
const ok3 = existsSync12(agentPath);
|
|
1668
1969
|
checkLine(
|
|
1669
1970
|
i === 0 ? "checking agents" : null,
|
|
1670
1971
|
ok3,
|
|
@@ -1676,18 +1977,18 @@ async function runHealth(cwd2) {
|
|
|
1676
1977
|
if (config.tools.mcp.enabled) {
|
|
1677
1978
|
const mcpFile = providerFiles.mcpFile;
|
|
1678
1979
|
const mcpPath = resolve6(cwd2, mcpFile);
|
|
1679
|
-
const mcpOk =
|
|
1980
|
+
const mcpOk = existsSync12(mcpPath);
|
|
1680
1981
|
checkLine("checking MCP", mcpOk, `${mcpFile} valid`);
|
|
1681
1982
|
if (!mcpOk) allOk = false;
|
|
1682
1983
|
}
|
|
1683
1984
|
if (!allOk) {
|
|
1684
1985
|
console.log("");
|
|
1685
|
-
console.error(
|
|
1986
|
+
console.error(pc6.red("\u2717 Harness checks failed \u2014 fix the above before running health.sh"));
|
|
1686
1987
|
process.exit(1);
|
|
1687
1988
|
}
|
|
1688
1989
|
const scriptPath = resolve6(cwd2, config.health.scriptPath);
|
|
1689
|
-
if (!
|
|
1690
|
-
console.error(
|
|
1990
|
+
if (!existsSync12(scriptPath)) {
|
|
1991
|
+
console.error(pc6.red(`\u2717 health.sh not found: ${scriptPath}`));
|
|
1691
1992
|
console.error(" Run ahk init first.");
|
|
1692
1993
|
process.exit(1);
|
|
1693
1994
|
}
|
|
@@ -1697,14 +1998,14 @@ async function runHealth(cwd2) {
|
|
|
1697
1998
|
encoding: "utf8"
|
|
1698
1999
|
});
|
|
1699
2000
|
if (result.error) {
|
|
1700
|
-
console.error(
|
|
2001
|
+
console.error(pc6.red(`\u2717 Failed to run health.sh: ${result.error.message}`));
|
|
1701
2002
|
process.exit(1);
|
|
1702
2003
|
}
|
|
1703
2004
|
if (result.status === 0) {
|
|
1704
|
-
console.log(
|
|
2005
|
+
console.log(pc6.green("\u2713 Health check passed"));
|
|
1705
2006
|
process.exit(0);
|
|
1706
2007
|
} else {
|
|
1707
|
-
console.error(
|
|
2008
|
+
console.error(pc6.red(`\u2717 Health check failed (exit ${result.status ?? "unknown"})`));
|
|
1708
2009
|
process.exit(result.status ?? 1);
|
|
1709
2010
|
}
|
|
1710
2011
|
}
|
|
@@ -1716,14 +2017,16 @@ function getProviderHealthFiles(provider) {
|
|
|
1716
2017
|
return { agentsDir: ".opencode/agents", agentExtension: ".md", mcpFile: "opencode.json" };
|
|
1717
2018
|
case "codex-cli":
|
|
1718
2019
|
return { agentsDir: ".codex/agents", agentExtension: ".toml", mcpFile: ".codex/config.toml" };
|
|
2020
|
+
case "grok-cli":
|
|
2021
|
+
return { agentsDir: ".grok/agents", agentExtension: ".md", mcpFile: ".grok/config.toml" };
|
|
1719
2022
|
default:
|
|
1720
2023
|
throw new Error(`Unknown provider: ${provider}`);
|
|
1721
2024
|
}
|
|
1722
2025
|
}
|
|
1723
2026
|
|
|
1724
2027
|
// src/commands/init.ts
|
|
1725
|
-
import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync8 } from "fs";
|
|
1726
|
-
import { join as
|
|
2028
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync7, readFileSync as readFileSync9, writeFileSync as writeFileSync8 } from "fs";
|
|
2029
|
+
import { join as join16 } from "path";
|
|
1727
2030
|
import * as p3 from "@clack/prompts";
|
|
1728
2031
|
import pc8 from "picocolors";
|
|
1729
2032
|
|
|
@@ -1778,52 +2081,13 @@ var cliFormWithRetry = async (formFn, schema) => {
|
|
|
1778
2081
|
|
|
1779
2082
|
// src/commands/init-helpers.ts
|
|
1780
2083
|
import { randomUUID } from "crypto";
|
|
1781
|
-
import { existsSync as
|
|
1782
|
-
import { join as
|
|
2084
|
+
import { existsSync as existsSync13, readFileSync as readFileSync8 } from "fs";
|
|
2085
|
+
import { join as join15 } from "path";
|
|
1783
2086
|
import pc7 from "picocolors";
|
|
1784
|
-
|
|
1785
|
-
// src/core/local-install-guard.ts
|
|
1786
|
-
import { existsSync as existsSync11, readFileSync as readFileSync7 } from "fs";
|
|
1787
|
-
import { join as join13 } from "path";
|
|
1788
|
-
import pc6 from "picocolors";
|
|
1789
|
-
function isLocalInstallSatisfied(cwd2) {
|
|
1790
|
-
const selfPkgPath = join13(cwd2, "package.json");
|
|
1791
|
-
let projectPkg = null;
|
|
1792
|
-
if (existsSync11(selfPkgPath)) {
|
|
1793
|
-
try {
|
|
1794
|
-
const selfPkg = JSON.parse(readFileSync7(selfPkgPath, "utf8"));
|
|
1795
|
-
if (selfPkg?.name === pkg.name) return true;
|
|
1796
|
-
projectPkg = selfPkg;
|
|
1797
|
-
} catch {
|
|
1798
|
-
}
|
|
1799
|
-
}
|
|
1800
|
-
const [scope, name] = pkg.name.split("/");
|
|
1801
|
-
const localPath = pkg.name.startsWith("@") ? join13(cwd2, "node_modules", scope, name) : join13(cwd2, "node_modules", pkg.name);
|
|
1802
|
-
if (existsSync11(localPath)) return true;
|
|
1803
|
-
const isPnp = existsSync11(join13(cwd2, ".pnp.cjs")) || existsSync11(join13(cwd2, ".pnp.loader.mjs"));
|
|
1804
|
-
if (isPnp && projectPkg) {
|
|
1805
|
-
const deps = {
|
|
1806
|
-
...projectPkg.dependencies ?? {},
|
|
1807
|
-
...projectPkg.devDependencies ?? {}
|
|
1808
|
-
};
|
|
1809
|
-
if (Object.prototype.hasOwnProperty.call(deps, pkg.name)) return true;
|
|
1810
|
-
}
|
|
1811
|
-
return false;
|
|
1812
|
-
}
|
|
1813
|
-
function printLocalInstallWarning() {
|
|
1814
|
-
console.error(pc6.yellow(`\u26A0 ${pkg.name} is not installed locally in this project.`));
|
|
1815
|
-
console.error(pc6.dim(" This is only a recommendation for reproducibility: pinning a local"));
|
|
1816
|
-
console.error(pc6.dim(" version keeps behavior consistent across your team and CI, instead of"));
|
|
1817
|
-
console.error(pc6.dim(" drifting with whatever version is installed globally on each machine."));
|
|
1818
|
-
console.error(pc6.dim(` Run: npm install --save-dev ${pkg.name}`));
|
|
1819
|
-
console.error(pc6.dim(" (or the equivalent for your package manager: pnpm add -D, yarn add --dev, bun add -d)"));
|
|
1820
|
-
}
|
|
1821
|
-
|
|
1822
|
-
// src/commands/init-helpers.ts
|
|
1823
2087
|
function readProjectNameFromPackageJson(cwd2) {
|
|
1824
2088
|
try {
|
|
1825
|
-
const pkgPath2 =
|
|
1826
|
-
if (!
|
|
2089
|
+
const pkgPath2 = join15(cwd2, "package.json");
|
|
2090
|
+
if (!existsSync13(pkgPath2)) return null;
|
|
1827
2091
|
const content = readFileSync8(pkgPath2, "utf8");
|
|
1828
2092
|
const pkg2 = JSON.parse(content);
|
|
1829
2093
|
const name = pkg2?.name;
|
|
@@ -1836,9 +2100,9 @@ function readProjectNameFromPackageJson(cwd2) {
|
|
|
1836
2100
|
function detectConfigExtension(cwd2) {
|
|
1837
2101
|
if (!isLocalInstallSatisfied(cwd2)) return "json";
|
|
1838
2102
|
try {
|
|
1839
|
-
if (
|
|
1840
|
-
const pkgPath2 =
|
|
1841
|
-
if (!
|
|
2103
|
+
if (existsSync13(join15(cwd2, "tsconfig.json"))) return "ts";
|
|
2104
|
+
const pkgPath2 = join15(cwd2, "package.json");
|
|
2105
|
+
if (!existsSync13(pkgPath2)) return "mjs";
|
|
1842
2106
|
const pkg2 = JSON.parse(readFileSync8(pkgPath2, "utf8"));
|
|
1843
2107
|
if (pkg2?.type === "module") return "mjs";
|
|
1844
2108
|
} catch {
|
|
@@ -1918,6 +2182,34 @@ function printWelcomeMessage(projectName) {
|
|
|
1918
2182
|
}
|
|
1919
2183
|
|
|
1920
2184
|
// src/commands/init.ts
|
|
2185
|
+
async function reconcileFeatureList(db, installDir, storageDir, firstTask) {
|
|
2186
|
+
const featureListPath = join16(installDir, storageDir, "feature_list.json");
|
|
2187
|
+
let existingSeeds = [];
|
|
2188
|
+
let parseFailed = false;
|
|
2189
|
+
if (existsSync14(featureListPath)) {
|
|
2190
|
+
try {
|
|
2191
|
+
const parsed = JSON.parse(readFileSync9(featureListPath, "utf8"));
|
|
2192
|
+
if (!Array.isArray(parsed)) throw new Error("feature_list.json is not a JSON array");
|
|
2193
|
+
existingSeeds = parsed;
|
|
2194
|
+
} catch {
|
|
2195
|
+
parseFailed = true;
|
|
2196
|
+
}
|
|
2197
|
+
}
|
|
2198
|
+
const firstTaskSeed = firstTask ? {
|
|
2199
|
+
slug: slugify(firstTask.title),
|
|
2200
|
+
title: firstTask.title,
|
|
2201
|
+
description: firstTask.description,
|
|
2202
|
+
acceptance: firstTask.acceptance
|
|
2203
|
+
} : void 0;
|
|
2204
|
+
if (parseFailed) {
|
|
2205
|
+
if (firstTaskSeed) await db.syncFromFeatureList([firstTaskSeed]);
|
|
2206
|
+
} else {
|
|
2207
|
+
const seeds = firstTaskSeed ? [...existingSeeds, firstTaskSeed] : existingSeeds;
|
|
2208
|
+
await db.syncFromFeatureList(seeds);
|
|
2209
|
+
await db.writeFeatureList(installDir);
|
|
2210
|
+
}
|
|
2211
|
+
return { parseFailed };
|
|
2212
|
+
}
|
|
1921
2213
|
async function runInit(cwd2, flags) {
|
|
1922
2214
|
const existingConfig = findConfigFile(cwd2);
|
|
1923
2215
|
if (existingConfig) {
|
|
@@ -1968,7 +2260,7 @@ async function runInit(cwd2, flags) {
|
|
|
1968
2260
|
return val;
|
|
1969
2261
|
}, initDescriptionSchema);
|
|
1970
2262
|
let provider;
|
|
1971
|
-
if (flags.provider && ["claude-code", "opencode"].includes(flags.provider)) {
|
|
2263
|
+
if (flags.provider && ["claude-code", "opencode", "codex-cli", "grok-cli"].includes(flags.provider)) {
|
|
1972
2264
|
provider = flags.provider;
|
|
1973
2265
|
} else {
|
|
1974
2266
|
const val = await p3.select({
|
|
@@ -1976,7 +2268,8 @@ async function runInit(cwd2, flags) {
|
|
|
1976
2268
|
options: [
|
|
1977
2269
|
{ value: "opencode", label: "OpenCode" },
|
|
1978
2270
|
{ value: "claude-code", label: "Claude Code" },
|
|
1979
|
-
{ value: "codex-cli", label: "Codex CLI" }
|
|
2271
|
+
{ value: "codex-cli", label: "Codex CLI" },
|
|
2272
|
+
{ value: "grok-cli", label: "Grok CLI" }
|
|
1980
2273
|
]
|
|
1981
2274
|
});
|
|
1982
2275
|
if (p3.isCancel(val)) {
|
|
@@ -1985,6 +2278,34 @@ async function runInit(cwd2, flags) {
|
|
|
1985
2278
|
}
|
|
1986
2279
|
provider = val;
|
|
1987
2280
|
}
|
|
2281
|
+
const AGENT_LABELS = [
|
|
2282
|
+
{ key: "lead", label: "Lead" },
|
|
2283
|
+
{ key: "explorer", label: "Explorer" },
|
|
2284
|
+
{ key: "consultant", label: "Consultant" },
|
|
2285
|
+
{ key: "builder", label: "Builder" },
|
|
2286
|
+
{ key: "reviewer", label: "Reviewer" }
|
|
2287
|
+
];
|
|
2288
|
+
const claudeAgentModels = {};
|
|
2289
|
+
if (provider === "claude-code") {
|
|
2290
|
+
for (const agent of AGENT_LABELS) {
|
|
2291
|
+
const val = await p3.select({
|
|
2292
|
+
message: `Model for ${agent.label}`,
|
|
2293
|
+
options: [
|
|
2294
|
+
{ value: "inherit", label: "inherit (default)" },
|
|
2295
|
+
{ value: "haiku", label: "haiku" },
|
|
2296
|
+
{ value: "sonnet", label: "sonnet" },
|
|
2297
|
+
{ value: "opus", label: "opus" },
|
|
2298
|
+
{ value: "fable", label: "fable" }
|
|
2299
|
+
],
|
|
2300
|
+
initialValue: "inherit"
|
|
2301
|
+
});
|
|
2302
|
+
if (p3.isCancel(val)) {
|
|
2303
|
+
p3.cancel("Cancelled.");
|
|
2304
|
+
process.exit(0);
|
|
2305
|
+
}
|
|
2306
|
+
claudeAgentModels[agent.key] = val;
|
|
2307
|
+
}
|
|
2308
|
+
}
|
|
1988
2309
|
let docsPath;
|
|
1989
2310
|
if (flags.docs) {
|
|
1990
2311
|
docsPath = flags.docs;
|
|
@@ -2076,6 +2397,7 @@ async function runInit(cwd2, flags) {
|
|
|
2076
2397
|
firstTask = { title: taskTitle, description: taskDesc, acceptance };
|
|
2077
2398
|
}
|
|
2078
2399
|
let configExt = "ts";
|
|
2400
|
+
let featureListParseFailedPath = null;
|
|
2079
2401
|
const spinner6 = p3.spinner();
|
|
2080
2402
|
spinner6.start("Scaffolding...");
|
|
2081
2403
|
try {
|
|
@@ -2102,19 +2424,14 @@ async function runInit(cwd2, flags) {
|
|
|
2102
2424
|
scope: config.storage.scope,
|
|
2103
2425
|
projectId: config.storage.projectId
|
|
2104
2426
|
});
|
|
2105
|
-
writeFileSync8(
|
|
2106
|
-
mkdirSync7(
|
|
2427
|
+
writeFileSync8(join16(installDir, configFileName), configContent, "utf8");
|
|
2428
|
+
mkdirSync7(join16(installDir, config.storage.dir), { recursive: true });
|
|
2107
2429
|
const db = await openDB(config, installDir);
|
|
2108
2430
|
await db.writeStorageState(installDir);
|
|
2109
|
-
await materializer.scaffold(config, { cwd: installDir, firstTask });
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
slug,
|
|
2114
|
-
title: firstTask.title,
|
|
2115
|
-
description: firstTask.description,
|
|
2116
|
-
acceptance: firstTask.acceptance
|
|
2117
|
-
});
|
|
2431
|
+
await materializer.scaffold(config, { cwd: installDir, firstTask, claudeAgentModels });
|
|
2432
|
+
const { parseFailed } = await reconcileFeatureList(db, installDir, config.storage.dir, firstTask);
|
|
2433
|
+
if (parseFailed) {
|
|
2434
|
+
featureListParseFailedPath = join16(config.storage.dir, "feature_list.json");
|
|
2118
2435
|
}
|
|
2119
2436
|
await db.close();
|
|
2120
2437
|
spinner6.stop("");
|
|
@@ -2123,9 +2440,19 @@ async function runInit(cwd2, flags) {
|
|
|
2123
2440
|
p3.log.error(err instanceof Error ? err.message : String(err));
|
|
2124
2441
|
throw err;
|
|
2125
2442
|
}
|
|
2443
|
+
if (featureListParseFailedPath) {
|
|
2444
|
+
console.log(
|
|
2445
|
+
pc8.yellow("\u26A0") + " Existing " + pc8.bold(featureListParseFailedPath) + " is not valid JSON \u2014 left untouched. Fix it and run `ahk sync`."
|
|
2446
|
+
);
|
|
2447
|
+
}
|
|
2126
2448
|
console.log(pc8.green("\u2713 Scaffolded harness in current directory"));
|
|
2127
|
-
const
|
|
2128
|
-
|
|
2449
|
+
const PROVIDER_SUMMARY_INFO = {
|
|
2450
|
+
"claude-code": { agentsDir: ".claude/agents/", mcpFile: ".mcp.json" },
|
|
2451
|
+
opencode: { agentsDir: ".opencode/agents/", mcpFile: "./opencode.json" },
|
|
2452
|
+
"codex-cli": { agentsDir: ".codex/agents/", mcpFile: ".codex/config.toml" },
|
|
2453
|
+
"grok-cli": { agentsDir: ".grok/agents/", mcpFile: ".grok/config.toml" }
|
|
2454
|
+
};
|
|
2455
|
+
const { agentsDir, mcpFile } = PROVIDER_SUMMARY_INFO[provider];
|
|
2129
2456
|
console.log("");
|
|
2130
2457
|
console.log(pc8.green(`\u2713 agent-harness-kit.config.${configExt}`));
|
|
2131
2458
|
console.log(pc8.green("\u2713 AGENTS.md"));
|
|
@@ -2167,7 +2494,7 @@ import pc9 from "picocolors";
|
|
|
2167
2494
|
async function runMigrate(cwd2, opts) {
|
|
2168
2495
|
const config = await loadConfig(cwd2);
|
|
2169
2496
|
let target;
|
|
2170
|
-
if (opts.to && ["claude-code", "opencode", "codex-cli"].includes(opts.to)) {
|
|
2497
|
+
if (opts.to && ["claude-code", "opencode", "codex-cli", "grok-cli"].includes(opts.to)) {
|
|
2171
2498
|
target = opts.to;
|
|
2172
2499
|
} else {
|
|
2173
2500
|
const val = await p4.select({
|
|
@@ -2175,7 +2502,8 @@ async function runMigrate(cwd2, opts) {
|
|
|
2175
2502
|
options: [
|
|
2176
2503
|
{ value: "claude-code", label: "Claude Code" },
|
|
2177
2504
|
{ value: "opencode", label: "OpenCode" },
|
|
2178
|
-
{ value: "codex-cli", label: "Codex CLI" }
|
|
2505
|
+
{ value: "codex-cli", label: "Codex CLI" },
|
|
2506
|
+
{ value: "grok-cli", label: "Grok CLI" }
|
|
2179
2507
|
]
|
|
2180
2508
|
});
|
|
2181
2509
|
if (p4.isCancel(val)) {
|
|
@@ -2204,9 +2532,9 @@ async function runMigrate(cwd2, opts) {
|
|
|
2204
2532
|
}
|
|
2205
2533
|
|
|
2206
2534
|
// src/commands/migrate-storage.ts
|
|
2207
|
-
import { copyFileSync, existsSync as
|
|
2535
|
+
import { copyFileSync, existsSync as existsSync15, mkdirSync as mkdirSync8, rmSync, writeFileSync as writeFileSync9 } from "fs";
|
|
2208
2536
|
import { homedir as homedir3 } from "os";
|
|
2209
|
-
import { dirname as dirname7, join as
|
|
2537
|
+
import { dirname as dirname7, join as join17, resolve as resolve7 } from "path";
|
|
2210
2538
|
import pc10 from "picocolors";
|
|
2211
2539
|
function log5(msg) {
|
|
2212
2540
|
console.log(msg);
|
|
@@ -2218,14 +2546,14 @@ function defaultMarkdownPathForConfig(config) {
|
|
|
2218
2546
|
return config.storage.scope === "local" ? config.storage.markdownFallback.path : DEFAULT_MARKDOWN_PATH;
|
|
2219
2547
|
}
|
|
2220
2548
|
function currentMdPathForScope(scope, config, cwd2, homeDir) {
|
|
2221
|
-
return scope === "global" ?
|
|
2549
|
+
return scope === "global" ? join17(resolveGlobalStorageDir(config, homeDir), "current.md") : resolve7(cwd2, defaultMarkdownPathForConfig(config));
|
|
2222
2550
|
}
|
|
2223
2551
|
function defaultSqlitePathForConfig(config) {
|
|
2224
2552
|
return config.storage.scope === "local" && config.database.type === "sqlite" ? config.storage.sqlitePath ?? DEFAULT_SQLITE_PATH : DEFAULT_SQLITE_PATH;
|
|
2225
2553
|
}
|
|
2226
2554
|
async function backupDestination(cwd2, storageDir, data) {
|
|
2227
2555
|
const backupsDir = resolve7(cwd2, storageDir, "backups");
|
|
2228
|
-
const path =
|
|
2556
|
+
const path = join17(backupsDir, `pre-migrate-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.json`);
|
|
2229
2557
|
try {
|
|
2230
2558
|
mkdirSync8(backupsDir, { recursive: true });
|
|
2231
2559
|
writeFileSync9(path, JSON.stringify(data, null, 2) + "\n", "utf8");
|
|
@@ -2240,11 +2568,11 @@ function copySqliteFile(srcPath, destPath) {
|
|
|
2240
2568
|
mkdirSync8(dirname7(destPath), { recursive: true });
|
|
2241
2569
|
copyFileSync(srcPath, destPath);
|
|
2242
2570
|
for (const suffix of ["-wal", "-shm"]) {
|
|
2243
|
-
if (
|
|
2571
|
+
if (existsSync15(`${srcPath}${suffix}`)) {
|
|
2244
2572
|
copyFileSync(`${srcPath}${suffix}`, `${destPath}${suffix}`);
|
|
2245
2573
|
}
|
|
2246
2574
|
}
|
|
2247
|
-
if (!
|
|
2575
|
+
if (!existsSync15(destPath)) {
|
|
2248
2576
|
throw new Error(`Copy verification failed: ${destPath} does not exist after copy.`);
|
|
2249
2577
|
}
|
|
2250
2578
|
}
|
|
@@ -2303,8 +2631,8 @@ async function runMigrateStorage(cwd2, opts, homeDir = homedir3()) {
|
|
|
2303
2631
|
return migrateAcrossDbType(cwd2, config, homeDir, realScope, opts);
|
|
2304
2632
|
}
|
|
2305
2633
|
async function probeTaskCount(dbPath) {
|
|
2306
|
-
if (!
|
|
2307
|
-
const { SQLiteDriver } = await import("./sqlite-
|
|
2634
|
+
if (!existsSync15(dbPath)) return 0;
|
|
2635
|
+
const { SQLiteDriver } = await import("./sqlite-5OWKTUUZ.js");
|
|
2308
2636
|
const driver = new SQLiteDriver(dbPath);
|
|
2309
2637
|
try {
|
|
2310
2638
|
await driver.ensureSchema();
|
|
@@ -2320,12 +2648,12 @@ async function migrateScopeOnly(cwd2, config, homeDir, fromScope, toScope, opts)
|
|
|
2320
2648
|
const destDb = resolveSqlitePathForScope(toScope, sqlitePath, cwd2, config, homeDir);
|
|
2321
2649
|
const srcMd = currentMdPathForScope(fromScope, config, cwd2, homeDir);
|
|
2322
2650
|
const destMd = currentMdPathForScope(toScope, config, cwd2, homeDir);
|
|
2323
|
-
if (!
|
|
2651
|
+
if (!existsSync15(srcDb)) {
|
|
2324
2652
|
fail(`Source database not found at ${srcDb} (expected ${fromScope} scope) \u2014 nothing to move.`);
|
|
2325
2653
|
}
|
|
2326
|
-
const destExists =
|
|
2654
|
+
const destExists = existsSync15(destDb);
|
|
2327
2655
|
if (destExists) {
|
|
2328
|
-
const { SQLiteDriver } = await import("./sqlite-
|
|
2656
|
+
const { SQLiteDriver } = await import("./sqlite-5OWKTUUZ.js");
|
|
2329
2657
|
const destDriver = new SQLiteDriver(destDb);
|
|
2330
2658
|
let destEmpty;
|
|
2331
2659
|
try {
|
|
@@ -2340,12 +2668,12 @@ async function migrateScopeOnly(cwd2, config, homeDir, fromScope, toScope, opts)
|
|
|
2340
2668
|
);
|
|
2341
2669
|
}
|
|
2342
2670
|
if (!destEmpty && opts.force) {
|
|
2343
|
-
const { SQLiteDriver: Driver } = await import("./sqlite-
|
|
2671
|
+
const { SQLiteDriver: Driver } = await import("./sqlite-5OWKTUUZ.js");
|
|
2344
2672
|
const backupDriver = new Driver(destDb);
|
|
2345
2673
|
let data;
|
|
2346
2674
|
try {
|
|
2347
2675
|
await backupDriver.ensureSchema();
|
|
2348
|
-
const { HarnessDB } = await import("./db-
|
|
2676
|
+
const { HarnessDB } = await import("./db-L3AADJF5.js");
|
|
2349
2677
|
const tmpDb = new HarnessDB(backupDriver, config, homeDir);
|
|
2350
2678
|
data = await tmpDb.exportJson();
|
|
2351
2679
|
} finally {
|
|
@@ -2361,7 +2689,7 @@ async function migrateScopeOnly(cwd2, config, homeDir, fromScope, toScope, opts)
|
|
|
2361
2689
|
}
|
|
2362
2690
|
copySqliteFile(srcDb, destDb);
|
|
2363
2691
|
log5(pc10.green(`\u2713 Copied database ${srcDb} \u2192 ${destDb}`));
|
|
2364
|
-
if (
|
|
2692
|
+
if (existsSync15(srcMd)) {
|
|
2365
2693
|
mkdirSync8(dirname7(destMd), { recursive: true });
|
|
2366
2694
|
copyFileSync(srcMd, destMd);
|
|
2367
2695
|
log5(pc10.green(`\u2713 Copied current.md ${srcMd} \u2192 ${destMd}`));
|
|
@@ -2369,7 +2697,7 @@ async function migrateScopeOnly(cwd2, config, homeDir, fromScope, toScope, opts)
|
|
|
2369
2697
|
rmSync(srcDb, { force: true });
|
|
2370
2698
|
rmSync(`${srcDb}-wal`, { force: true });
|
|
2371
2699
|
rmSync(`${srcDb}-shm`, { force: true });
|
|
2372
|
-
if (
|
|
2700
|
+
if (existsSync15(srcMd) && srcMd !== destMd) rmSync(srcMd, { force: true });
|
|
2373
2701
|
const db = await openDB(config, cwd2, homeDir);
|
|
2374
2702
|
try {
|
|
2375
2703
|
await db.writeStorageState(cwd2);
|
|
@@ -2381,17 +2709,17 @@ async function migrateScopeOnly(cwd2, config, homeDir, fromScope, toScope, opts)
|
|
|
2381
2709
|
async function migrateAcrossDbType(cwd2, config, homeDir, sourceScope, opts) {
|
|
2382
2710
|
const sqlitePath = defaultSqlitePathForConfig(config);
|
|
2383
2711
|
const srcPath = resolveSqlitePathForScope(sourceScope, sqlitePath, cwd2, config, homeDir);
|
|
2384
|
-
if (!
|
|
2712
|
+
if (!existsSync15(srcPath)) {
|
|
2385
2713
|
fail(`Source sqlite database not found at ${srcPath} (expected ${sourceScope} scope) \u2014 nothing to migrate.`);
|
|
2386
2714
|
}
|
|
2387
|
-
const { SQLiteDriver } = await import("./sqlite-
|
|
2715
|
+
const { SQLiteDriver } = await import("./sqlite-5OWKTUUZ.js");
|
|
2388
2716
|
const srcDriver = new SQLiteDriver(srcPath);
|
|
2389
2717
|
let sourceData;
|
|
2390
2718
|
let sourceCounts;
|
|
2391
2719
|
try {
|
|
2392
2720
|
await srcDriver.ensureSchema();
|
|
2393
2721
|
sourceCounts = await getRowCounts(srcDriver);
|
|
2394
|
-
const { HarnessDB } = await import("./db-
|
|
2722
|
+
const { HarnessDB } = await import("./db-L3AADJF5.js");
|
|
2395
2723
|
const srcDb = new HarnessDB(srcDriver, config, homeDir);
|
|
2396
2724
|
sourceData = await srcDb.exportJson();
|
|
2397
2725
|
} finally {
|
|
@@ -2444,16 +2772,29 @@ async function migrateAcrossDbType(cwd2, config, homeDir, sourceScope, opts) {
|
|
|
2444
2772
|
}
|
|
2445
2773
|
|
|
2446
2774
|
// src/commands/reset.ts
|
|
2447
|
-
import { existsSync as
|
|
2775
|
+
import { existsSync as existsSync16, readdirSync, rmSync as rmSync2 } from "fs";
|
|
2448
2776
|
import { homedir as homedir4 } from "os";
|
|
2449
|
-
import { join as
|
|
2777
|
+
import { join as join18, resolve as resolve8 } from "path";
|
|
2450
2778
|
import * as p5 from "@clack/prompts";
|
|
2451
2779
|
import pc11 from "picocolors";
|
|
2452
2780
|
var AGENT_MD_FILES = ["lead", "explorer", "consultant", "builder", "reviewer"];
|
|
2781
|
+
var PROVIDER_AGENT_DIRS = {
|
|
2782
|
+
"claude-code": ".claude/agents",
|
|
2783
|
+
opencode: ".opencode/agents",
|
|
2784
|
+
"codex-cli": ".codex/agents",
|
|
2785
|
+
"grok-cli": ".grok/agents"
|
|
2786
|
+
};
|
|
2787
|
+
var PROVIDER_AGENT_EXT = {
|
|
2788
|
+
"claude-code": ".md",
|
|
2789
|
+
opencode: ".md",
|
|
2790
|
+
"codex-cli": ".toml",
|
|
2791
|
+
"grok-cli": ".md"
|
|
2792
|
+
};
|
|
2453
2793
|
async function resetAgentMds(cwd2, provider) {
|
|
2454
|
-
const agentDir = provider
|
|
2794
|
+
const agentDir = PROVIDER_AGENT_DIRS[provider];
|
|
2455
2795
|
const agentDirPath = resolve8(cwd2, agentDir);
|
|
2456
|
-
|
|
2796
|
+
const agentExt = PROVIDER_AGENT_EXT[provider];
|
|
2797
|
+
if (!existsSync16(agentDirPath)) {
|
|
2457
2798
|
console.log(pc11.yellow(` Skipping agent files \u2014 directory not found: ${agentDirPath}`));
|
|
2458
2799
|
return;
|
|
2459
2800
|
}
|
|
@@ -2461,7 +2802,7 @@ async function resetAgentMds(cwd2, provider) {
|
|
|
2461
2802
|
try {
|
|
2462
2803
|
const files = readdirSync(agentDirPath);
|
|
2463
2804
|
for (const f of files) {
|
|
2464
|
-
if (f.endsWith(
|
|
2805
|
+
if (f.endsWith(agentExt) && AGENT_MD_FILES.includes(f.replace(agentExt, ""))) {
|
|
2465
2806
|
existingFiles.push(f);
|
|
2466
2807
|
}
|
|
2467
2808
|
}
|
|
@@ -2484,7 +2825,7 @@ async function resetAgentMds(cwd2, provider) {
|
|
|
2484
2825
|
}
|
|
2485
2826
|
if (confirm3) {
|
|
2486
2827
|
try {
|
|
2487
|
-
const filePath =
|
|
2828
|
+
const filePath = join18(agentDirPath, file);
|
|
2488
2829
|
rmSync2(filePath, { force: true });
|
|
2489
2830
|
console.log(pc11.green(` Removed ${file}`));
|
|
2490
2831
|
} catch {
|
|
@@ -2509,7 +2850,7 @@ async function runReset(cwd2, opts) {
|
|
|
2509
2850
|
let resetDb = false;
|
|
2510
2851
|
let resetFeatureList = false;
|
|
2511
2852
|
let resetAgentMdsFlag = false;
|
|
2512
|
-
if (dbPath &&
|
|
2853
|
+
if (dbPath && existsSync16(dbPath)) {
|
|
2513
2854
|
if (opts.force) {
|
|
2514
2855
|
resetDb = true;
|
|
2515
2856
|
} else {
|
|
@@ -2531,7 +2872,7 @@ async function runReset(cwd2, opts) {
|
|
|
2531
2872
|
} else if (!dbPath) {
|
|
2532
2873
|
console.log(pc11.dim(` Skipping DB reset \u2014 remote ${config.database.type} database is not managed by this command.`));
|
|
2533
2874
|
}
|
|
2534
|
-
if (
|
|
2875
|
+
if (existsSync16(featureListPath)) {
|
|
2535
2876
|
if (opts.force) {
|
|
2536
2877
|
resetFeatureList = true;
|
|
2537
2878
|
} else {
|
|
@@ -2580,8 +2921,8 @@ async function runReset(cwd2, opts) {
|
|
|
2580
2921
|
}
|
|
2581
2922
|
|
|
2582
2923
|
// src/core/mcp-server.ts
|
|
2583
|
-
import { existsSync as
|
|
2584
|
-
import { join as
|
|
2924
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync9, readdirSync as readdirSync2, readFileSync as readFileSync10, statSync, writeFileSync as writeFileSync10 } from "fs";
|
|
2925
|
+
import { join as join20, resolve as resolve9 } from "path";
|
|
2585
2926
|
import { Server } from "@modelcontextprotocol/sdk/server";
|
|
2586
2927
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
2587
2928
|
import {
|
|
@@ -2590,8 +2931,8 @@ import {
|
|
|
2590
2931
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
2591
2932
|
|
|
2592
2933
|
// src/core/permissions-check.ts
|
|
2593
|
-
import { existsSync as
|
|
2594
|
-
import { join as
|
|
2934
|
+
import { existsSync as existsSync17 } from "fs";
|
|
2935
|
+
import { join as join19 } from "path";
|
|
2595
2936
|
var AGENTS = ["lead", "explorer", "consultant", "builder", "reviewer"];
|
|
2596
2937
|
function checkPermissionsSync(cwd2, config) {
|
|
2597
2938
|
if (config.provider !== "claude-code") {
|
|
@@ -2600,8 +2941,8 @@ function checkPermissionsSync(cwd2, config) {
|
|
|
2600
2941
|
const agents = {};
|
|
2601
2942
|
let in_sync = true;
|
|
2602
2943
|
for (const agent of AGENTS) {
|
|
2603
|
-
const filePath =
|
|
2604
|
-
const exists =
|
|
2944
|
+
const filePath = join19(cwd2, ".claude", "agents", `${agent}.md`);
|
|
2945
|
+
const exists = existsSync17(filePath);
|
|
2605
2946
|
if (!exists) in_sync = false;
|
|
2606
2947
|
agents[agent] = exists ? { ok: true } : { ok: false, reason: "missing_file" };
|
|
2607
2948
|
}
|
|
@@ -2613,7 +2954,7 @@ var VERSION = "0.1.0";
|
|
|
2613
2954
|
var TOOLS = [
|
|
2614
2955
|
{
|
|
2615
2956
|
name: "actions.start",
|
|
2616
|
-
description: "Start a new action for a task. Returns an actionId
|
|
2957
|
+
description: "Start a new action for a task. Returns an actionId.",
|
|
2617
2958
|
inputSchema: {
|
|
2618
2959
|
type: "object",
|
|
2619
2960
|
properties: {
|
|
@@ -2628,18 +2969,18 @@ var TOOLS = [
|
|
|
2628
2969
|
},
|
|
2629
2970
|
{
|
|
2630
2971
|
name: "actions.write",
|
|
2631
|
-
description: "Record a section in an action. Standard sections: result, tools_used, blockers, next_steps.
|
|
2972
|
+
description: "Record a section in an action. Standard sections: result, tools_used, blockers, next_steps.",
|
|
2632
2973
|
inputSchema: {
|
|
2633
2974
|
type: "object",
|
|
2634
2975
|
properties: {
|
|
2635
|
-
actionId: { type: "
|
|
2976
|
+
actionId: { type: "number", description: "The actionId returned by actions.start" },
|
|
2636
2977
|
sectionType: {
|
|
2637
2978
|
type: "string",
|
|
2638
2979
|
description: "Section name: result | tools_used | blockers | next_steps | <custom>. Do NOT use files_modified to track files \u2014 it is stored as plain text only. Use actions.record_file instead."
|
|
2639
2980
|
},
|
|
2640
2981
|
content: {
|
|
2641
2982
|
type: "string",
|
|
2642
|
-
description: "Content for this section. No length limit \u2014
|
|
2983
|
+
description: "Content for this section. No length limit; avoid padding \u2014 it costs shared context for other agents."
|
|
2643
2984
|
}
|
|
2644
2985
|
},
|
|
2645
2986
|
required: ["actionId", "sectionType", "content"]
|
|
@@ -2651,7 +2992,7 @@ var TOOLS = [
|
|
|
2651
2992
|
inputSchema: {
|
|
2652
2993
|
type: "object",
|
|
2653
2994
|
properties: {
|
|
2654
|
-
actionId: { type: "
|
|
2995
|
+
actionId: { type: "number", description: "The actionId of the action to close" },
|
|
2655
2996
|
summary: { type: "string", description: "One-line summary of what was done" }
|
|
2656
2997
|
},
|
|
2657
2998
|
required: ["actionId", "summary"]
|
|
@@ -2726,20 +3067,31 @@ var TOOLS = [
|
|
|
2726
3067
|
},
|
|
2727
3068
|
{
|
|
2728
3069
|
name: "actions.record_file",
|
|
2729
|
-
description: "Record
|
|
3070
|
+
description: "Record one or more files touched during an action, atomically (all-or-nothing). This is the only way to populate the files-touched count shown in the dashboard. Batch every file from a step of work into a single call \u2014 a single-element array is correct when only one file was touched.",
|
|
2730
3071
|
inputSchema: {
|
|
2731
3072
|
type: "object",
|
|
2732
3073
|
properties: {
|
|
2733
|
-
actionId: { type: "
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
3074
|
+
actionId: { type: "number", description: "The actionId returned by actions.start" },
|
|
3075
|
+
files: {
|
|
3076
|
+
type: "array",
|
|
3077
|
+
minItems: 1,
|
|
3078
|
+
description: "Files touched, recorded atomically in one transaction.",
|
|
3079
|
+
items: {
|
|
3080
|
+
type: "object",
|
|
3081
|
+
properties: {
|
|
3082
|
+
filePath: { type: "string", description: "Absolute or repo-relative path of the file" },
|
|
3083
|
+
operation: {
|
|
3084
|
+
type: "string",
|
|
3085
|
+
enum: ["read", "created", "modified", "deleted"],
|
|
3086
|
+
description: "What was done to the file"
|
|
3087
|
+
},
|
|
3088
|
+
notes: { type: "string", description: "Optional short note about the change" }
|
|
3089
|
+
},
|
|
3090
|
+
required: ["filePath", "operation"]
|
|
3091
|
+
}
|
|
3092
|
+
}
|
|
2741
3093
|
},
|
|
2742
|
-
required: ["actionId", "
|
|
3094
|
+
required: ["actionId", "files"]
|
|
2743
3095
|
}
|
|
2744
3096
|
},
|
|
2745
3097
|
{
|
|
@@ -2780,12 +3132,12 @@ var TOOLS = [
|
|
|
2780
3132
|
},
|
|
2781
3133
|
description: {
|
|
2782
3134
|
type: "string",
|
|
2783
|
-
description: "Longer description of the task goal. No length limit \u2014
|
|
3135
|
+
description: "Longer description of the task goal. No length limit; avoid padding \u2014 it costs shared context for other agents."
|
|
2784
3136
|
},
|
|
2785
3137
|
acceptance: {
|
|
2786
3138
|
type: "array",
|
|
2787
3139
|
items: { type: "string" },
|
|
2788
|
-
description: "List of acceptance criteria (plain sentences). No length limit \u2014
|
|
3140
|
+
description: "List of acceptance criteria (plain sentences). No length limit; avoid padding \u2014 it costs shared context for other agents."
|
|
2789
3141
|
}
|
|
2790
3142
|
},
|
|
2791
3143
|
required: ["title"]
|
|
@@ -2793,22 +3145,36 @@ var TOOLS = [
|
|
|
2793
3145
|
},
|
|
2794
3146
|
{
|
|
2795
3147
|
name: "actions.record_tool",
|
|
2796
|
-
description: "Record
|
|
3148
|
+
description: "Record one or more tool calls made during an action, atomically (all-or-nothing). This is the only way to populate the Tools dashboard. Batch every tool call from a step of work into a single call \u2014 a single-element array is correct when only one call was made.",
|
|
2797
3149
|
inputSchema: {
|
|
2798
3150
|
type: "object",
|
|
2799
3151
|
properties: {
|
|
2800
|
-
actionId: { type: "
|
|
2801
|
-
|
|
2802
|
-
type: "
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
3152
|
+
actionId: { type: "number", description: "The actionId returned by actions.start" },
|
|
3153
|
+
calls: {
|
|
3154
|
+
type: "array",
|
|
3155
|
+
minItems: 1,
|
|
3156
|
+
description: "Tool calls made, recorded atomically in one transaction.",
|
|
3157
|
+
items: {
|
|
3158
|
+
type: "object",
|
|
3159
|
+
properties: {
|
|
3160
|
+
toolName: {
|
|
3161
|
+
type: "string",
|
|
3162
|
+
description: "Name of the tool that was called (e.g. Read, Bash, Edit)"
|
|
3163
|
+
},
|
|
3164
|
+
argsJson: {
|
|
3165
|
+
type: "string",
|
|
3166
|
+
description: "Optional JSON string of the arguments passed to the tool"
|
|
3167
|
+
},
|
|
3168
|
+
resultSummary: {
|
|
3169
|
+
type: "string",
|
|
3170
|
+
description: "Optional short summary of the tool result"
|
|
3171
|
+
}
|
|
3172
|
+
},
|
|
3173
|
+
required: ["toolName"]
|
|
3174
|
+
}
|
|
3175
|
+
}
|
|
2810
3176
|
},
|
|
2811
|
-
required: ["actionId", "
|
|
3177
|
+
required: ["actionId", "calls"]
|
|
2812
3178
|
}
|
|
2813
3179
|
},
|
|
2814
3180
|
{
|
|
@@ -2899,39 +3265,50 @@ async function dispatch(name, args, db, docsPath, cwd2, config) {
|
|
|
2899
3265
|
const taskId = num(args, "taskId");
|
|
2900
3266
|
const agent = str(args, "agent");
|
|
2901
3267
|
const action = await db.startAction(taskId, agent);
|
|
2902
|
-
return ok2(JSON.stringify({ actionId: action.id
|
|
3268
|
+
return ok2(JSON.stringify({ actionId: action.id }));
|
|
2903
3269
|
}
|
|
2904
3270
|
case "actions.write": {
|
|
2905
|
-
const actionId =
|
|
3271
|
+
const actionId = num(args, "actionId");
|
|
2906
3272
|
const sectionType = str(args, "sectionType");
|
|
2907
3273
|
const content = str(args, "content");
|
|
2908
3274
|
await db.writeSection(actionId, sectionType, content);
|
|
2909
|
-
return ok2(JSON.stringify({
|
|
3275
|
+
return ok2(JSON.stringify({ recorded: true }));
|
|
2910
3276
|
}
|
|
2911
3277
|
case "actions.complete": {
|
|
2912
|
-
const actionId =
|
|
3278
|
+
const actionId = num(args, "actionId");
|
|
2913
3279
|
const summary = str(args, "summary");
|
|
2914
3280
|
const action = await db.completeAction(actionId, summary);
|
|
2915
|
-
return ok2(
|
|
2916
|
-
JSON.stringify({ actionId, status: action.status, completedAt: action.completed_at })
|
|
2917
|
-
);
|
|
3281
|
+
return ok2(JSON.stringify({ status: action.status, completedAt: action.completed_at }));
|
|
2918
3282
|
}
|
|
2919
3283
|
case "actions.get": {
|
|
2920
3284
|
const taskId = num(args, "taskId");
|
|
2921
3285
|
const actions = await db.getActionsForTask(taskId);
|
|
2922
3286
|
const full = await Promise.all(
|
|
2923
|
-
actions.map(async (a) =>
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
3287
|
+
actions.map(async (a) => {
|
|
3288
|
+
const sections = await db.getActionSections(a.id);
|
|
3289
|
+
return {
|
|
3290
|
+
id: a.id,
|
|
3291
|
+
agent: a.agent,
|
|
3292
|
+
status: a.status,
|
|
3293
|
+
created_at: a.created_at,
|
|
3294
|
+
completed_at: a.completed_at,
|
|
3295
|
+
summary: a.summary,
|
|
3296
|
+
sections: sections.map((s) => ({
|
|
3297
|
+
id: s.id,
|
|
3298
|
+
section_type: s.section_type,
|
|
3299
|
+
content: s.content,
|
|
3300
|
+
created_at: s.created_at
|
|
3301
|
+
}))
|
|
3302
|
+
};
|
|
3303
|
+
})
|
|
2927
3304
|
);
|
|
2928
|
-
return ok2(JSON.stringify(full
|
|
3305
|
+
return ok2(JSON.stringify(full));
|
|
2929
3306
|
}
|
|
2930
3307
|
case "tasks.get": {
|
|
2931
3308
|
const status = args["status"];
|
|
2932
3309
|
const includeArchived = args["includeArchived"];
|
|
2933
3310
|
const tasks = status ? await db.getTasks(status, includeArchived ?? false) : await db.getTasks(void 0, includeArchived ?? false);
|
|
2934
|
-
return ok2(JSON.stringify(tasks
|
|
3311
|
+
return ok2(JSON.stringify(tasks));
|
|
2935
3312
|
}
|
|
2936
3313
|
case "tasks.claim": {
|
|
2937
3314
|
const id = num(args, "id");
|
|
@@ -2962,15 +3339,17 @@ async function dispatch(name, args, db, docsPath, cwd2, config) {
|
|
|
2962
3339
|
case "docs.search": {
|
|
2963
3340
|
const query = str(args, "query");
|
|
2964
3341
|
const results = searchDocs(docsPath, query);
|
|
2965
|
-
return ok2(JSON.stringify(results
|
|
3342
|
+
return ok2(JSON.stringify(results));
|
|
2966
3343
|
}
|
|
2967
3344
|
case "actions.record_file": {
|
|
2968
|
-
const actionId =
|
|
2969
|
-
const
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
3345
|
+
const actionId = num(args, "actionId");
|
|
3346
|
+
const files = nonEmptyArray(args, "files").map((f) => ({
|
|
3347
|
+
filePath: str(f, "filePath"),
|
|
3348
|
+
operation: str(f, "operation"),
|
|
3349
|
+
notes: f["notes"]
|
|
3350
|
+
}));
|
|
3351
|
+
const recorded = await db.recordFiles(actionId, files);
|
|
3352
|
+
return ok2(JSON.stringify({ recorded }));
|
|
2974
3353
|
}
|
|
2975
3354
|
case "tasks.acceptance.update": {
|
|
2976
3355
|
const criterionId = num(args, "criterionId");
|
|
@@ -2980,15 +3359,17 @@ async function dispatch(name, args, db, docsPath, cwd2, config) {
|
|
|
2980
3359
|
case "tasks.acceptance.get": {
|
|
2981
3360
|
const taskId = num(args, "taskId");
|
|
2982
3361
|
const criteria = await db.getTaskAcceptance(taskId);
|
|
2983
|
-
return ok2(JSON.stringify(criteria
|
|
3362
|
+
return ok2(JSON.stringify(criteria));
|
|
2984
3363
|
}
|
|
2985
3364
|
case "actions.record_tool": {
|
|
2986
|
-
const actionId =
|
|
2987
|
-
const
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
3365
|
+
const actionId = num(args, "actionId");
|
|
3366
|
+
const calls = nonEmptyArray(args, "calls").map((c) => ({
|
|
3367
|
+
toolName: str(c, "toolName"),
|
|
3368
|
+
argsJson: c["argsJson"],
|
|
3369
|
+
resultSummary: c["resultSummary"]
|
|
3370
|
+
}));
|
|
3371
|
+
const recorded = await db.recordTools(actionId, calls);
|
|
3372
|
+
return ok2(JSON.stringify({ recorded }));
|
|
2992
3373
|
}
|
|
2993
3374
|
case "tasks.edit": {
|
|
2994
3375
|
const id = num(args, "id");
|
|
@@ -3019,22 +3400,22 @@ async function dispatch(name, args, db, docsPath, cwd2, config) {
|
|
|
3019
3400
|
}
|
|
3020
3401
|
case "permissions.check": {
|
|
3021
3402
|
const result = checkPermissionsSync(cwd2, config);
|
|
3022
|
-
return ok2(JSON.stringify(result
|
|
3403
|
+
return ok2(JSON.stringify(result));
|
|
3023
3404
|
}
|
|
3024
3405
|
case "deps.snapshot": {
|
|
3025
|
-
const pkgPath2 =
|
|
3026
|
-
if (!
|
|
3406
|
+
const pkgPath2 = join20(cwd2, "package.json");
|
|
3407
|
+
if (!existsSync18(pkgPath2)) {
|
|
3027
3408
|
return ok2("package.json not found in project root", true);
|
|
3028
3409
|
}
|
|
3029
|
-
const pkg2 = JSON.parse(
|
|
3410
|
+
const pkg2 = JSON.parse(readFileSync10(pkgPath2, "utf8"));
|
|
3030
3411
|
const snapshot = {
|
|
3031
3412
|
capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3032
3413
|
dependencies: pkg2.dependencies ?? {},
|
|
3033
3414
|
devDependencies: pkg2.devDependencies ?? {}
|
|
3034
3415
|
};
|
|
3035
|
-
const harnessDir =
|
|
3416
|
+
const harnessDir = join20(cwd2, ".harness");
|
|
3036
3417
|
mkdirSync9(harnessDir, { recursive: true });
|
|
3037
|
-
writeFileSync10(
|
|
3418
|
+
writeFileSync10(join20(harnessDir, "deps-lock.json"), JSON.stringify(snapshot, null, 2), "utf8");
|
|
3038
3419
|
return ok2(
|
|
3039
3420
|
JSON.stringify({
|
|
3040
3421
|
message: "Snapshot saved to .harness/deps-lock.json",
|
|
@@ -3043,12 +3424,12 @@ async function dispatch(name, args, db, docsPath, cwd2, config) {
|
|
|
3043
3424
|
);
|
|
3044
3425
|
}
|
|
3045
3426
|
case "deps.check": {
|
|
3046
|
-
const pkgPath2 =
|
|
3047
|
-
const lockPath =
|
|
3048
|
-
if (!
|
|
3427
|
+
const pkgPath2 = join20(cwd2, "package.json");
|
|
3428
|
+
const lockPath = join20(cwd2, ".harness", "deps-lock.json");
|
|
3429
|
+
if (!existsSync18(pkgPath2)) {
|
|
3049
3430
|
return ok2("package.json not found in project root", true);
|
|
3050
3431
|
}
|
|
3051
|
-
if (!
|
|
3432
|
+
if (!existsSync18(lockPath)) {
|
|
3052
3433
|
return ok2(
|
|
3053
3434
|
JSON.stringify({
|
|
3054
3435
|
status: "no-snapshot",
|
|
@@ -3056,8 +3437,8 @@ async function dispatch(name, args, db, docsPath, cwd2, config) {
|
|
|
3056
3437
|
})
|
|
3057
3438
|
);
|
|
3058
3439
|
}
|
|
3059
|
-
const pkg2 = JSON.parse(
|
|
3060
|
-
const lock = JSON.parse(
|
|
3440
|
+
const pkg2 = JSON.parse(readFileSync10(pkgPath2, "utf8"));
|
|
3441
|
+
const lock = JSON.parse(readFileSync10(lockPath, "utf8"));
|
|
3061
3442
|
const current = { ...pkg2.dependencies ?? {}, ...pkg2.devDependencies ?? {} };
|
|
3062
3443
|
const previous = { ...lock.dependencies ?? {}, ...lock.devDependencies ?? {} };
|
|
3063
3444
|
const added = [];
|
|
@@ -3111,7 +3492,7 @@ async function dispatch(name, args, db, docsPath, cwd2, config) {
|
|
|
3111
3492
|
ok: status.skills.filter((s) => s.status === "ok").map((s) => s.name)
|
|
3112
3493
|
}
|
|
3113
3494
|
};
|
|
3114
|
-
return ok2(JSON.stringify(result
|
|
3495
|
+
return ok2(JSON.stringify(result));
|
|
3115
3496
|
}
|
|
3116
3497
|
default:
|
|
3117
3498
|
return ok2(`Unknown tool: ${name}`, true);
|
|
@@ -3125,7 +3506,7 @@ function searchDocs(docsPath, query, maxResults = 10) {
|
|
|
3125
3506
|
for (const file of files) {
|
|
3126
3507
|
if (results.length >= maxResults) break;
|
|
3127
3508
|
try {
|
|
3128
|
-
const content =
|
|
3509
|
+
const content = readFileSync10(file, "utf8");
|
|
3129
3510
|
const lines = content.split("\n");
|
|
3130
3511
|
for (let i = 0; i < lines.length; i++) {
|
|
3131
3512
|
const lower = lines[i].toLowerCase();
|
|
@@ -3150,7 +3531,7 @@ function collectMarkdownFiles(dir) {
|
|
|
3150
3531
|
const files = [];
|
|
3151
3532
|
try {
|
|
3152
3533
|
for (const entry of readdirSync2(dir)) {
|
|
3153
|
-
const full =
|
|
3534
|
+
const full = join20(dir, entry);
|
|
3154
3535
|
const stat = statSync(full);
|
|
3155
3536
|
if (stat.isDirectory()) {
|
|
3156
3537
|
files.push(...collectMarkdownFiles(full));
|
|
@@ -3175,6 +3556,18 @@ function num(args, key) {
|
|
|
3175
3556
|
if (typeof v4 !== "number") throw new Error(`${key} must be a number`);
|
|
3176
3557
|
return v4;
|
|
3177
3558
|
}
|
|
3559
|
+
function nonEmptyArray(args, key) {
|
|
3560
|
+
const v4 = args[key];
|
|
3561
|
+
if (!Array.isArray(v4) || v4.length === 0) {
|
|
3562
|
+
throw new Error(`${key} must be a non-empty array`);
|
|
3563
|
+
}
|
|
3564
|
+
for (const item of v4) {
|
|
3565
|
+
if (typeof item !== "object" || item === null) {
|
|
3566
|
+
throw new Error(`${key} entries must be objects`);
|
|
3567
|
+
}
|
|
3568
|
+
}
|
|
3569
|
+
return v4;
|
|
3570
|
+
}
|
|
3178
3571
|
|
|
3179
3572
|
// src/commands/serve.ts
|
|
3180
3573
|
async function runServe(cwd2, opts) {
|
|
@@ -3268,13 +3661,13 @@ async function runStatus(cwd2, opts) {
|
|
|
3268
3661
|
}
|
|
3269
3662
|
|
|
3270
3663
|
// src/commands/sync.ts
|
|
3271
|
-
import { existsSync as
|
|
3272
|
-
import { join as
|
|
3664
|
+
import { existsSync as existsSync19, readFileSync as readFileSync11 } from "fs";
|
|
3665
|
+
import { join as join21, resolve as resolve10 } from "path";
|
|
3273
3666
|
import pc13 from "picocolors";
|
|
3274
3667
|
async function runSync(cwd2, opts) {
|
|
3275
3668
|
const config = await loadConfig(cwd2);
|
|
3276
3669
|
const direction = opts.direction ?? "both";
|
|
3277
|
-
const featureListPath = resolve10(
|
|
3670
|
+
const featureListPath = resolve10(join21(cwd2, config.storage.dir, "feature_list.json"));
|
|
3278
3671
|
const db = await openDB(config, cwd2);
|
|
3279
3672
|
try {
|
|
3280
3673
|
if (direction === "in" || direction === "both") {
|
|
@@ -3288,13 +3681,13 @@ async function runSync(cwd2, opts) {
|
|
|
3288
3681
|
}
|
|
3289
3682
|
}
|
|
3290
3683
|
async function syncIn(featureListPath, db, dryRun) {
|
|
3291
|
-
if (!
|
|
3684
|
+
if (!existsSync19(featureListPath)) {
|
|
3292
3685
|
console.log(pc13.dim(`feature_list.json not found at ${featureListPath} \u2014 skipping in-sync`));
|
|
3293
3686
|
return;
|
|
3294
3687
|
}
|
|
3295
3688
|
let seeds;
|
|
3296
3689
|
try {
|
|
3297
|
-
seeds = JSON.parse(
|
|
3690
|
+
seeds = JSON.parse(readFileSync11(featureListPath, "utf8"));
|
|
3298
3691
|
} catch (err) {
|
|
3299
3692
|
console.error(pc13.red(`Failed to parse feature_list.json: ${err}`));
|
|
3300
3693
|
process.exit(1);
|
|
@@ -3379,14 +3772,14 @@ async function runTaskAdd(cwd2) {
|
|
|
3379
3772
|
|
|
3380
3773
|
// src/commands/task/done.ts
|
|
3381
3774
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
3382
|
-
import { existsSync as
|
|
3775
|
+
import { existsSync as existsSync20 } from "fs";
|
|
3383
3776
|
import { resolve as resolve11 } from "path";
|
|
3384
3777
|
import pc15 from "picocolors";
|
|
3385
3778
|
async function runTaskDone(cwd2, idOrSlug) {
|
|
3386
3779
|
const config = await loadConfig(cwd2);
|
|
3387
3780
|
if (config.health.required) {
|
|
3388
3781
|
const scriptPath = resolve11(cwd2, config.health.scriptPath);
|
|
3389
|
-
if (
|
|
3782
|
+
if (existsSync20(scriptPath)) {
|
|
3390
3783
|
const result = spawnSync2("bash", [scriptPath], { cwd: cwd2, stdio: "pipe", encoding: "utf8" });
|
|
3391
3784
|
if (result.status !== 0) {
|
|
3392
3785
|
console.error(pc15.red("\u2717 Health check failed \u2014 cannot mark task as done."));
|
|
@@ -3557,8 +3950,74 @@ async function runTaskList(cwd2, opts) {
|
|
|
3557
3950
|
}
|
|
3558
3951
|
}
|
|
3559
3952
|
|
|
3560
|
-
// src/core/
|
|
3953
|
+
// src/core/path-probe.ts
|
|
3954
|
+
import { accessSync, constants, readdirSync as readdirSync3 } from "fs";
|
|
3955
|
+
import { join as join22 } from "path";
|
|
3561
3956
|
import pc18 from "picocolors";
|
|
3957
|
+
var DEFAULT_PATHEXT = [".COM", ".EXE", ".BAT", ".CMD"];
|
|
3958
|
+
function defaultIsExecutable(filePath) {
|
|
3959
|
+
try {
|
|
3960
|
+
accessSync(filePath, constants.X_OK);
|
|
3961
|
+
return true;
|
|
3962
|
+
} catch {
|
|
3963
|
+
return false;
|
|
3964
|
+
}
|
|
3965
|
+
}
|
|
3966
|
+
function normalizeExts(pathext) {
|
|
3967
|
+
const raw = pathext && pathext.trim().length > 0 ? pathext : DEFAULT_PATHEXT.join(";");
|
|
3968
|
+
return raw.split(";").map((ext) => ext.trim().toLowerCase()).filter((ext) => ext.length > 0).map((ext) => ext.startsWith(".") ? ext : `.${ext}`);
|
|
3969
|
+
}
|
|
3970
|
+
function resolveOnPath(name, options = {}) {
|
|
3971
|
+
const {
|
|
3972
|
+
pathValue = process.env.PATH,
|
|
3973
|
+
pathext = process.env.PATHEXT,
|
|
3974
|
+
platform = process.platform,
|
|
3975
|
+
isExecutable = defaultIsExecutable
|
|
3976
|
+
} = options;
|
|
3977
|
+
if (!pathValue) return false;
|
|
3978
|
+
const isWindows = platform === "win32";
|
|
3979
|
+
const sep = isWindows ? ";" : ":";
|
|
3980
|
+
const dirs = pathValue.split(sep).filter((dir) => dir.length > 0);
|
|
3981
|
+
if (dirs.length === 0) return false;
|
|
3982
|
+
if (isWindows) {
|
|
3983
|
+
const lowerName = name.toLowerCase();
|
|
3984
|
+
const candidates2 = /* @__PURE__ */ new Set([lowerName, ...normalizeExts(pathext).map((ext) => `${lowerName}${ext}`)]);
|
|
3985
|
+
for (const dir of dirs) {
|
|
3986
|
+
let entries;
|
|
3987
|
+
try {
|
|
3988
|
+
entries = readdirSync3(dir);
|
|
3989
|
+
} catch {
|
|
3990
|
+
continue;
|
|
3991
|
+
}
|
|
3992
|
+
for (const entry of entries) {
|
|
3993
|
+
if (candidates2.has(entry.toLowerCase())) return true;
|
|
3994
|
+
}
|
|
3995
|
+
}
|
|
3996
|
+
return false;
|
|
3997
|
+
}
|
|
3998
|
+
for (const dir of dirs) {
|
|
3999
|
+
try {
|
|
4000
|
+
if (isExecutable(join22(dir, name))) return true;
|
|
4001
|
+
} catch {
|
|
4002
|
+
continue;
|
|
4003
|
+
}
|
|
4004
|
+
}
|
|
4005
|
+
return false;
|
|
4006
|
+
}
|
|
4007
|
+
function isExecutableOnPath(name) {
|
|
4008
|
+
return resolveOnPath(name);
|
|
4009
|
+
}
|
|
4010
|
+
function printMissingGlobalBinaryWarning() {
|
|
4011
|
+
console.error(pc18.yellow("\u26A0 `ahk` was not found on your PATH."));
|
|
4012
|
+
console.error(pc18.dim(" Your project has no local install, so the generated MCP config launches"));
|
|
4013
|
+
console.error(pc18.dim(" `ahk serve` directly. Without `ahk` on your PATH, starting the MCP server"));
|
|
4014
|
+
console.error(pc18.dim(" from that config will fail. This is only a warning \u2014 the command continues."));
|
|
4015
|
+
console.error(pc18.dim(` Run: npm i -g ${pkg.name} (install globally)`));
|
|
4016
|
+
console.error(pc18.dim(` or: npm install --save-dev ${pkg.name} (install locally in this project)`));
|
|
4017
|
+
}
|
|
4018
|
+
|
|
4019
|
+
// src/core/update-check.ts
|
|
4020
|
+
import pc19 from "picocolors";
|
|
3562
4021
|
var REGISTRY_URL2 = `https://registry.npmjs.org/${pkg.name}/latest`;
|
|
3563
4022
|
var TIMEOUT_MS2 = 2500;
|
|
3564
4023
|
function checkForUpdate(currentVersion) {
|
|
@@ -3576,8 +4035,8 @@ function checkForUpdate(currentVersion) {
|
|
|
3576
4035
|
}
|
|
3577
4036
|
function printUpdateMessage({ current, latest }) {
|
|
3578
4037
|
const lines = [
|
|
3579
|
-
` Update available ${
|
|
3580
|
-
` Run: ${
|
|
4038
|
+
` Update available ${pc19.dim(current)} \u2192 ${pc19.green(latest)} `,
|
|
4039
|
+
` Run: ${pc19.cyan(`pnpm i ${pkg.name}@${latest}`)} `
|
|
3581
4040
|
];
|
|
3582
4041
|
drawBox(lines);
|
|
3583
4042
|
}
|
|
@@ -3592,10 +4051,22 @@ function isNewer2(latest, current) {
|
|
|
3592
4051
|
|
|
3593
4052
|
// src/cli.ts
|
|
3594
4053
|
var cwd = process.cwd();
|
|
4054
|
+
function parsePort(raw) {
|
|
4055
|
+
const trimmed = raw.trim();
|
|
4056
|
+
const rangeHint = "must be an integer between 1 and 65535";
|
|
4057
|
+
if (!/^\d+$/.test(trimmed)) {
|
|
4058
|
+
throw new InvalidArgumentError(`--port ${rangeHint} (received "${raw}").`);
|
|
4059
|
+
}
|
|
4060
|
+
const port = Number(trimmed);
|
|
4061
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
4062
|
+
throw new InvalidArgumentError(`--port ${rangeHint} (received "${raw}").`);
|
|
4063
|
+
}
|
|
4064
|
+
return port;
|
|
4065
|
+
}
|
|
3595
4066
|
var updateCheck = checkForUpdate(pkg.version);
|
|
3596
4067
|
var program = new Command();
|
|
3597
4068
|
program.name("ahk").description("agent-harness-kit \u2014 CLI scaffolding for multi-agent harness systems").version(pkg.version, "-v, --version");
|
|
3598
|
-
program.command("init").description("Scaffold a harness interactively in the current directory").option("--name <name>", "Project name (skip prompt)").option("--provider <provider>", "AI provider: claude-code | opencode (skip prompt)").option("--docs <path>", "Docs folder path (skip prompt)").option("--tasks <adapter>", "Task adapter: local | jira | linear (skip prompt)").option("--storage-scope <scope>", "Storage scope: local | global (skip prompt)").action(async (opts) => {
|
|
4069
|
+
program.command("init").description("Scaffold a harness interactively in the current directory").option("--name <name>", "Project name (skip prompt)").option("--provider <provider>", "AI provider: claude-code | opencode | codex-cli | grok-cli (skip prompt)").option("--docs <path>", "Docs folder path (skip prompt)").option("--tasks <adapter>", "Task adapter: local | jira | linear (skip prompt)").option("--storage-scope <scope>", "Storage scope: local | global (skip prompt)").action(async (opts) => {
|
|
3599
4070
|
await runInit(cwd, opts);
|
|
3600
4071
|
});
|
|
3601
4072
|
program.command("build").description("Regenerate AGENTS.md and provider files from agent-harness-kit.config.ts").option("--watch", "Rebuild on config changes").option("--sync", "Sync tools: frontmatter in existing .claude/agents/*.md to match current permission constants").option(
|
|
@@ -3613,7 +4084,7 @@ program.command("status").description("Show task table and active actions").opti
|
|
|
3613
4084
|
program.command("sync").description("Sync feature_list.json \u2194 SQLite").option("--dry-run", "Show what would change without applying").option("--direction <direction>", "in | out | both (default: both)").action(async (opts) => {
|
|
3614
4085
|
await runSync(cwd, { dryRun: opts["dry-run"], direction: opts.direction });
|
|
3615
4086
|
});
|
|
3616
|
-
program.command("serve").description("Start the MCP server (stdio)").option("--port <port>", "Port hint stored in config (default: 3742)",
|
|
4087
|
+
program.command("serve").description("Start the MCP server (stdio)").option("--port <port>", "Port hint stored in config (default: 3742)", parsePort).action(async (opts) => {
|
|
3617
4088
|
await runServe(cwd, { port: opts.port });
|
|
3618
4089
|
});
|
|
3619
4090
|
var task = program.command("task").description("Manage tasks");
|
|
@@ -3629,11 +4100,11 @@ task.command("done <id|slug>").description("Mark a task as done").action(async (
|
|
|
3629
4100
|
task.command("edit").description("Edit a task interactively").action(async () => {
|
|
3630
4101
|
await runTaskEdit(cwd);
|
|
3631
4102
|
});
|
|
3632
|
-
program.command("dashboard").description("Open web dashboard to visualize harness data").option("-p, --port <port>", "Port to listen on",
|
|
3633
|
-
await runDashboard(cwd, { port:
|
|
4103
|
+
program.command("dashboard").description("Open web dashboard to visualize harness data").option("-p, --port <port>", "Port to listen on", parsePort, 4242).option("--no-open", "Do not open browser automatically").action(async (opts) => {
|
|
4104
|
+
await runDashboard(cwd, { port: opts.port, open: opts.open });
|
|
3634
4105
|
});
|
|
3635
4106
|
var migrate = program.command("migrate").description("Migrate provider files to a different provider, or migrate harness storage (see subcommands)");
|
|
3636
|
-
migrate.command("provider").description("Migrate provider-specific files to a different provider").option("--to <provider>", "Target provider: claude-code | opencode | codex-cli").action(async (opts) => {
|
|
4107
|
+
migrate.command("provider").description("Migrate provider-specific files to a different provider").option("--to <provider>", "Target provider: claude-code | opencode | codex-cli | grok-cli").action(async (opts) => {
|
|
3637
4108
|
await runMigrate(cwd, opts);
|
|
3638
4109
|
});
|
|
3639
4110
|
migrate.command("storage").description(
|
|
@@ -3642,14 +4113,14 @@ migrate.command("storage").description(
|
|
|
3642
4113
|
try {
|
|
3643
4114
|
await runMigrateStorage(cwd, { force: opts.force, dryRun: opts["dry-run"] });
|
|
3644
4115
|
} catch (err) {
|
|
3645
|
-
console.error(
|
|
4116
|
+
console.error(pc20.red(`\u2717 ${err instanceof Error ? err.message : String(err)}`));
|
|
3646
4117
|
process.exit(1);
|
|
3647
4118
|
}
|
|
3648
4119
|
});
|
|
3649
4120
|
program.command("export").description("Export the database").option("--sql", "SQL dump").option("--json", "JSON export of tasks and actions").option("--output <path>", "Output file path (default: stdout)").action(async (opts) => {
|
|
3650
4121
|
await runExport(cwd, opts);
|
|
3651
4122
|
});
|
|
3652
|
-
program.command("reset").description("Reset/clear harness data (DB, feature list, agent files)").option("--force", "Skip confirmation prompts").option("--provider <claude-code|opencode>", "Reset agent MD files for specified provider").action(async (opts) => {
|
|
4123
|
+
program.command("reset").description("Reset/clear harness data (DB, feature list, agent files)").option("--force", "Skip confirmation prompts").option("--provider <claude-code|opencode|codex-cli|grok-cli>", "Reset agent MD files for specified provider").action(async (opts) => {
|
|
3653
4124
|
await runReset(cwd, opts);
|
|
3654
4125
|
});
|
|
3655
4126
|
program.command("doctor").description("Check lib version, agent files, and harness skills sync status").action(async () => {
|
|
@@ -3658,6 +4129,9 @@ program.command("doctor").description("Check lib version, agent files, and harne
|
|
|
3658
4129
|
program.hook("preAction", () => {
|
|
3659
4130
|
if (!isLocalInstallSatisfied(cwd)) {
|
|
3660
4131
|
printLocalInstallWarning();
|
|
4132
|
+
if (!isExecutableOnPath("ahk")) {
|
|
4133
|
+
printMissingGlobalBinaryWarning();
|
|
4134
|
+
}
|
|
3661
4135
|
}
|
|
3662
4136
|
});
|
|
3663
4137
|
program.hook("postAction", async () => {
|