@dadado/agent-kit-cli 5.15.1 → 5.16.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/dashboard/dashboard-data.mjs +69 -65
- package/dashboard/lib/guards.d.mts +5 -0
- package/dashboard/lib/guards.mjs +31 -0
- package/dashboard/lib/identity-probe.mjs +56 -0
- package/dashboard/lib/pipeline-cache.mjs +58 -0
- package/dashboard/lib/processes.mjs +71 -0
- package/dashboard/serve.mjs +43 -1
- package/dashboard/start-broadcast.mjs +2 -12
- package/dashboard/start.mjs +2 -13
- package/dist/add-7UMSGVWC.js +150 -0
- package/dist/chunk-2IPXQFJE.js +253 -0
- package/dist/chunk-2YU2H7I6.js +42 -0
- package/dist/chunk-4IWG5CVE.js +214 -0
- package/dist/chunk-5OXPNVCF.js +106 -0
- package/dist/chunk-5VCOWTTP.js +6 -0
- package/dist/chunk-6QV2BFGA.js +341 -0
- package/dist/chunk-77V75A7O.js +39 -0
- package/dist/chunk-7HDQEK6T.js +26 -0
- package/dist/chunk-CBCW5R5J.js +484 -0
- package/dist/chunk-CGTE7DHN.js +410 -0
- package/dist/chunk-DE3LQFSJ.js +18 -0
- package/dist/chunk-F6C3GFKV.js +57 -0
- package/dist/chunk-HFUH3HVS.js +282 -0
- package/dist/chunk-HS7LVVLO.js +74 -0
- package/dist/chunk-IHROBTNM.js +135 -0
- package/dist/chunk-KD2MYBB6.js +213 -0
- package/dist/chunk-KI6ZHBPV.js +332 -0
- package/dist/chunk-MAPOQEA2.js +43 -0
- package/dist/chunk-NQ7IQ5XT.js +26 -0
- package/dist/chunk-OUQZC744.js +1315 -0
- package/dist/chunk-OZ4QV6LM.js +1078 -0
- package/dist/chunk-PCCQUWE5.js +817 -0
- package/dist/chunk-PNFF6OU3.js +273 -0
- package/dist/chunk-QTON2GQX.js +203 -0
- package/dist/chunk-TFZ2JUXH.js +1081 -0
- package/dist/chunk-VGZBARQC.js +318 -0
- package/dist/chunk-VS2JD2XC.js +1453 -0
- package/dist/chunk-VY7MTPJ2.js +205 -0
- package/dist/chunk-WW3YZKHW.js +165 -0
- package/dist/chunk-XWPO3VYQ.js +53 -0
- package/dist/chunk-XZZAVHH4.js +503 -0
- package/dist/chunk-YGBBHKBE.js +82 -0
- package/dist/chunk-ZB43YMHW.js +20 -0
- package/dist/chunk-ZCTSUQRR.js +874 -0
- package/dist/chunk-ZXKZBTXE.js +17 -0
- package/dist/contribute-YKK6YCSI.js +367 -0
- package/dist/cursor-awareness-KTL2RGKD.js +77 -0
- package/dist/dashboard-YSNUGBFY.js +21 -0
- package/dist/dashboard-broadcast-M4CMW7RX.js +119 -0
- package/dist/diff-YTR7VOAN.js +74 -0
- package/dist/doctor-TA2FWREU.js +497 -0
- package/dist/dogfood-KKFVRWT6.js +366 -0
- package/dist/guard-EHXF76ID.js +530 -0
- package/dist/handoff-3UI6VKVG.js +302 -0
- package/dist/hook-MUV6Y7UW.js +1074 -0
- package/dist/index.js +62 -14068
- package/dist/init-TTTKGC4C.js +127 -0
- package/dist/install-ZMFJBPOI.js +34 -0
- package/dist/mission-control-LJACEVVN.js +89 -0
- package/dist/monitors-QV2IS3MP.js +176 -0
- package/dist/plan-index-JMC7Z4DW.js +44 -0
- package/dist/public-inbound-radar-WP3XF7GS.js +68 -0
- package/dist/remote-issues-SZ4HDQL5.js +44 -0
- package/dist/run-I3BFGULT.js +243 -0
- package/dist/run-plan-FO7CL4M3.js +118 -0
- package/dist/scan-NI2SWJ2E.js +33 -0
- package/dist/setup-global-JUVMJXJ5.js +340 -0
- package/dist/status-MVCUNCWX.js +138 -0
- package/dist/update-Q4EBIRUB.js +260 -0
- package/dist/validate-V2CW7J4B.js +201 -0
- package/package.json +1 -1
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import {
|
|
2
|
+
logger
|
|
3
|
+
} from "./chunk-ZB43YMHW.js";
|
|
4
|
+
|
|
5
|
+
// src/commands/dashboard.ts
|
|
6
|
+
import { execFileSync, spawn as spawn2 } from "child_process";
|
|
7
|
+
import { existsSync, readFileSync as readFileSync2, realpathSync } from "fs";
|
|
8
|
+
import { access } from "fs/promises";
|
|
9
|
+
import path from "path";
|
|
10
|
+
import { fileURLToPath } from "url";
|
|
11
|
+
import { defineCommand } from "citty";
|
|
12
|
+
|
|
13
|
+
// ../../dashboard/lib/guards.mjs
|
|
14
|
+
import { randomBytes, timingSafeEqual } from "crypto";
|
|
15
|
+
import { networkInterfaces } from "os";
|
|
16
|
+
import { resolve } from "path";
|
|
17
|
+
var REPO_ROOT_ENV = "MISSION_CONTROL_REPO_ROOT";
|
|
18
|
+
var KIT_ROOT_ENV_KEYS = Object.freeze(["MISSION_CONTROL_KIT_ROOT", "AGENT_KIT_HOME"]);
|
|
19
|
+
var CONTEXT_CONFIG_REL = ".cursor/context/config.json";
|
|
20
|
+
var CONFIG_PERSONA_IDS = Object.freeze(["autopilot", "night-shift", "ghost-runner"]);
|
|
21
|
+
var CONFIG_PERSONA_MODES = Object.freeze(["continue-plan", "run-plan", "cli-run-plan"]);
|
|
22
|
+
var CONFIG_REVIEW_BACKENDS = Object.freeze(["auto", "claude", "cursor"]);
|
|
23
|
+
var CONFIG_REVIEW_MODES = Object.freeze(["paste", "autonomous"]);
|
|
24
|
+
var CONFIG_REVIEW_PREFLIGHT = Object.freeze(["off", "warn", "block"]);
|
|
25
|
+
function resolveContextConfigPath(repoRoot, fsHooks = {}) {
|
|
26
|
+
const exists = fsHooks.existsSync;
|
|
27
|
+
const realpath = fsHooks.realpathSync;
|
|
28
|
+
const mkdir = fsHooks.mkdirSync;
|
|
29
|
+
if (typeof repoRoot !== "string" || !repoRoot) {
|
|
30
|
+
return { ok: false, error: "invalid repo root" };
|
|
31
|
+
}
|
|
32
|
+
const abs = resolve(repoRoot, CONTEXT_CONFIG_REL);
|
|
33
|
+
const contextDir = resolve(repoRoot, ".cursor", "context");
|
|
34
|
+
const absNorm = abs.replace(/\\/g, "/");
|
|
35
|
+
if (!absNorm.endsWith("/.cursor/context/config.json")) {
|
|
36
|
+
return { ok: false, error: "path escape" };
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
if (typeof mkdir === "function" && typeof exists === "function" && !exists(contextDir)) {
|
|
40
|
+
mkdir(contextDir, { recursive: true });
|
|
41
|
+
}
|
|
42
|
+
if (typeof realpath === "function" && typeof exists === "function" && exists(abs)) {
|
|
43
|
+
const fileReal = String(realpath(abs)).replace(/\\/g, "/");
|
|
44
|
+
const rootReal = String(realpath(repoRoot)).replace(/\\/g, "/");
|
|
45
|
+
if (!fileReal.startsWith(`${rootReal}/`)) {
|
|
46
|
+
return { ok: false, error: "path escape" };
|
|
47
|
+
}
|
|
48
|
+
if (!fileReal.endsWith("/.cursor/context/config.json")) {
|
|
49
|
+
return { ok: false, error: "path escape" };
|
|
50
|
+
}
|
|
51
|
+
return { ok: true, path: realpath(abs) };
|
|
52
|
+
}
|
|
53
|
+
} catch {
|
|
54
|
+
return { ok: false, error: "path escape" };
|
|
55
|
+
}
|
|
56
|
+
return { ok: true, path: abs };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ../../dashboard/lib/open-browser.mjs
|
|
60
|
+
import { spawn, spawnSync } from "child_process";
|
|
61
|
+
import { readFileSync } from "fs";
|
|
62
|
+
import { platform as osPlatform } from "os";
|
|
63
|
+
var OS_DEFAULT_TOKENS = /* @__PURE__ */ new Set(["", "default", "os", "ask"]);
|
|
64
|
+
function isSafePreferredBrowser(value) {
|
|
65
|
+
if (typeof value !== "string") return false;
|
|
66
|
+
const s = value.trim();
|
|
67
|
+
if (!s) return false;
|
|
68
|
+
if (/[/\\]/.test(s)) return false;
|
|
69
|
+
if (/[\0-\x1f\x7f]/.test(s)) return false;
|
|
70
|
+
if (/[$`;&|<>(){}[\]!*?#~"'%^=,+]/.test(s)) return false;
|
|
71
|
+
if (s.includes(":")) return false;
|
|
72
|
+
if (/^-/.test(s)) return false;
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
function normalizePreferredBrowser(value) {
|
|
76
|
+
if (value == null) return null;
|
|
77
|
+
const s = String(value).trim();
|
|
78
|
+
if (!s || OS_DEFAULT_TOKENS.has(s.toLowerCase())) return null;
|
|
79
|
+
if (!isSafePreferredBrowser(s)) return null;
|
|
80
|
+
return s;
|
|
81
|
+
}
|
|
82
|
+
function readPreferredBrowserFromConfig(configPath, fsHooks = {}) {
|
|
83
|
+
const read = fsHooks.readFileSync ?? readFileSync;
|
|
84
|
+
try {
|
|
85
|
+
const raw = read(configPath, "utf8");
|
|
86
|
+
const data = JSON.parse(raw);
|
|
87
|
+
if (!data || typeof data !== "object" || Array.isArray(data)) return null;
|
|
88
|
+
const mc = data.missionControl;
|
|
89
|
+
if (!mc || typeof mc !== "object" || Array.isArray(mc)) return null;
|
|
90
|
+
return mc.preferredBrowser ?? null;
|
|
91
|
+
} catch {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// src/commands/dashboard.ts
|
|
97
|
+
function readPreferredBrowserFromWorkspace(cwd, readFile = readFileSync2) {
|
|
98
|
+
const resolved = resolveContextConfigPath(path.resolve(cwd), {
|
|
99
|
+
existsSync,
|
|
100
|
+
realpathSync
|
|
101
|
+
});
|
|
102
|
+
if (!resolved.ok) return null;
|
|
103
|
+
const value = readPreferredBrowserFromConfig(resolved.path, { readFileSync: readFile });
|
|
104
|
+
return normalizePreferredBrowser(value);
|
|
105
|
+
}
|
|
106
|
+
function applyDashboardOpenEnv(env, opts) {
|
|
107
|
+
const next = { ...env };
|
|
108
|
+
if (opts.noOpen) next.MISSION_CONTROL_NO_OPEN = "1";
|
|
109
|
+
const flag = opts.browser?.trim();
|
|
110
|
+
if (flag) {
|
|
111
|
+
const safe = normalizePreferredBrowser(flag);
|
|
112
|
+
if (safe) next.MISSION_CONTROL_PREFERRED_BROWSER = safe;
|
|
113
|
+
} else if (!next.MISSION_CONTROL_PREFERRED_BROWSER && opts.cwd) {
|
|
114
|
+
const fromConfig = readPreferredBrowserFromWorkspace(opts.cwd);
|
|
115
|
+
if (fromConfig) next.MISSION_CONTROL_PREFERRED_BROWSER = fromConfig;
|
|
116
|
+
}
|
|
117
|
+
return next;
|
|
118
|
+
}
|
|
119
|
+
function dashboardSpawnEnv(env, snapshotRoot, opts) {
|
|
120
|
+
const root = path.resolve(snapshotRoot);
|
|
121
|
+
return applyDashboardOpenEnv({ ...env, MISSION_CONTROL_REPO_ROOT: root }, { ...opts, cwd: root });
|
|
122
|
+
}
|
|
123
|
+
function dashboardProcessTitle(snapshotRoot) {
|
|
124
|
+
const base = path.basename(path.resolve(snapshotRoot)) || "workspace";
|
|
125
|
+
return `Mission Control \xB7 ${base}`;
|
|
126
|
+
}
|
|
127
|
+
function bundledDashboardCandidates(filename, moduleUrl = import.meta.url) {
|
|
128
|
+
const here = path.dirname(fileURLToPath(moduleUrl));
|
|
129
|
+
return [
|
|
130
|
+
// Published / built: <pkg>/dist/index.js -> <pkg>/dashboard/
|
|
131
|
+
path.join(here, "..", "dashboard", filename),
|
|
132
|
+
// Dev source: packages/cli/src/commands/*.ts -> packages/cli/dashboard/ (sync copy)
|
|
133
|
+
path.join(here, "..", "..", "dashboard", filename),
|
|
134
|
+
// From src/commands: packages/dashboard/ (unused layout); from dist: two levels above package
|
|
135
|
+
path.join(here, "..", "..", "..", "dashboard", filename)
|
|
136
|
+
];
|
|
137
|
+
}
|
|
138
|
+
async function firstExisting(candidates) {
|
|
139
|
+
for (const candidate of candidates) {
|
|
140
|
+
try {
|
|
141
|
+
await access(candidate);
|
|
142
|
+
return path.resolve(candidate);
|
|
143
|
+
} catch {
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
async function findDashboardStart(cwd, env = process.env, options = {}) {
|
|
149
|
+
let dir = path.resolve(cwd);
|
|
150
|
+
for (; ; ) {
|
|
151
|
+
const candidate = path.join(dir, "dashboard", "start.mjs");
|
|
152
|
+
try {
|
|
153
|
+
await access(candidate);
|
|
154
|
+
return candidate;
|
|
155
|
+
} catch {
|
|
156
|
+
}
|
|
157
|
+
const parent = path.dirname(dir);
|
|
158
|
+
if (parent === dir) break;
|
|
159
|
+
dir = parent;
|
|
160
|
+
}
|
|
161
|
+
for (const key of ["MISSION_CONTROL_KIT_ROOT", "AGENT_KIT_HOME"]) {
|
|
162
|
+
const base = env[key];
|
|
163
|
+
if (!base || typeof base !== "string" || !base.trim()) continue;
|
|
164
|
+
const candidate = path.join(path.resolve(base.trim()), "dashboard", "start.mjs");
|
|
165
|
+
try {
|
|
166
|
+
await access(candidate);
|
|
167
|
+
return candidate;
|
|
168
|
+
} catch {
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
const sibling = path.join(path.resolve(cwd), "..", "agent-kit", "dashboard", "start.mjs");
|
|
172
|
+
try {
|
|
173
|
+
await access(sibling);
|
|
174
|
+
return path.resolve(sibling);
|
|
175
|
+
} catch {
|
|
176
|
+
}
|
|
177
|
+
return firstExisting(
|
|
178
|
+
bundledDashboardCandidates("start.mjs", options.moduleUrl ?? import.meta.url)
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
function resolveDashboardSnapshotRoot(cwd) {
|
|
182
|
+
const abs = path.resolve(cwd);
|
|
183
|
+
let dir = abs;
|
|
184
|
+
for (; ; ) {
|
|
185
|
+
if (existsSync(path.join(dir, ".cursor", "agent-kit.json"))) {
|
|
186
|
+
return dir;
|
|
187
|
+
}
|
|
188
|
+
const parent = path.dirname(dir);
|
|
189
|
+
if (parent === dir) break;
|
|
190
|
+
dir = parent;
|
|
191
|
+
}
|
|
192
|
+
try {
|
|
193
|
+
const top = execFileSync("git", ["-C", abs, "rev-parse", "--show-toplevel"], {
|
|
194
|
+
encoding: "utf8",
|
|
195
|
+
timeout: 5e3,
|
|
196
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
197
|
+
}).trim();
|
|
198
|
+
if (top) return path.resolve(top);
|
|
199
|
+
} catch {
|
|
200
|
+
}
|
|
201
|
+
return abs;
|
|
202
|
+
}
|
|
203
|
+
function runStartScript(startPath, env) {
|
|
204
|
+
return new Promise((resolve2, reject) => {
|
|
205
|
+
const child = spawn2(process.execPath, [startPath], {
|
|
206
|
+
cwd: path.dirname(path.dirname(startPath)),
|
|
207
|
+
env,
|
|
208
|
+
stdio: "inherit"
|
|
209
|
+
});
|
|
210
|
+
child.on("error", reject);
|
|
211
|
+
child.on("close", (code) => resolve2(code ?? 1));
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
var dashboardCommand = defineCommand({
|
|
215
|
+
meta: {
|
|
216
|
+
name: "dashboard",
|
|
217
|
+
description: "Start Mission Control for this workspace (loopback; opens panel URL)."
|
|
218
|
+
},
|
|
219
|
+
args: {
|
|
220
|
+
cwd: {
|
|
221
|
+
type: "string",
|
|
222
|
+
default: process.cwd(),
|
|
223
|
+
description: "Workspace to snapshot (nearest .cursor/agent-kit.json, else git root); also searched upward for dashboard/start.mjs"
|
|
224
|
+
},
|
|
225
|
+
"no-open": {
|
|
226
|
+
type: "boolean",
|
|
227
|
+
default: false,
|
|
228
|
+
description: "Do not open a browser; only ensure the server is up and print the URL"
|
|
229
|
+
},
|
|
230
|
+
browser: {
|
|
231
|
+
type: "string",
|
|
232
|
+
description: "Preferred browser app/binary for this launch (overrides config missionControl.preferredBrowser)"
|
|
233
|
+
}
|
|
234
|
+
},
|
|
235
|
+
async run({ args }) {
|
|
236
|
+
const snapshotRoot = resolveDashboardSnapshotRoot(args.cwd);
|
|
237
|
+
const startPath = await findDashboardStart(args.cwd);
|
|
238
|
+
if (!startPath) {
|
|
239
|
+
logger.error("No dashboard/start.mjs found.");
|
|
240
|
+
console.error(
|
|
241
|
+
[
|
|
242
|
+
"",
|
|
243
|
+
"The dashboard runtime is not available in this workspace.",
|
|
244
|
+
"L0 install provides the /dashboard command text but not the panel itself.",
|
|
245
|
+
"",
|
|
246
|
+
"Recovery (pick one):",
|
|
247
|
+
" 1. Upgrade the CLI: npx @dadado/agent-kit-cli@latest dashboard",
|
|
248
|
+
" (Path C ships dashboard/ from 4.8.2 onward)",
|
|
249
|
+
" 2. Set an env var pointing to an agent-kit checkout:",
|
|
250
|
+
" export MISSION_CONTROL_KIT_ROOT=/path/to/agent-kit",
|
|
251
|
+
" agent-kit dashboard",
|
|
252
|
+
" 3. Place an agent-kit sibling: ../agent-kit/dashboard/start.mjs",
|
|
253
|
+
" 4. Run directly from a kit tree: node dashboard/start.mjs",
|
|
254
|
+
"",
|
|
255
|
+
"Works in Cursor, VS Code, and any Node.js terminal.",
|
|
256
|
+
""
|
|
257
|
+
].join("\n")
|
|
258
|
+
);
|
|
259
|
+
process.exitCode = 1;
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
const env = dashboardSpawnEnv({ ...process.env }, snapshotRoot, {
|
|
263
|
+
noOpen: Boolean(args["no-open"]),
|
|
264
|
+
browser: args.browser
|
|
265
|
+
});
|
|
266
|
+
process.title = dashboardProcessTitle(snapshotRoot);
|
|
267
|
+
const code = await runStartScript(startPath, env);
|
|
268
|
+
if (code !== 0) process.exitCode = code;
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
export {
|
|
273
|
+
REPO_ROOT_ENV,
|
|
274
|
+
readPreferredBrowserFromWorkspace,
|
|
275
|
+
applyDashboardOpenEnv,
|
|
276
|
+
dashboardSpawnEnv,
|
|
277
|
+
dashboardProcessTitle,
|
|
278
|
+
bundledDashboardCandidates,
|
|
279
|
+
findDashboardStart,
|
|
280
|
+
resolveDashboardSnapshotRoot,
|
|
281
|
+
dashboardCommand
|
|
282
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import {
|
|
2
|
+
logger
|
|
3
|
+
} from "./chunk-ZB43YMHW.js";
|
|
4
|
+
|
|
5
|
+
// src/lifecycle/report.ts
|
|
6
|
+
function logApplyStats(stats) {
|
|
7
|
+
if (stats.written.length > 0) {
|
|
8
|
+
logger.success(`Wrote ${stats.written.length} file(s)`);
|
|
9
|
+
for (const p of stats.written) logger.info(` + ${p}`);
|
|
10
|
+
}
|
|
11
|
+
if (stats.unchanged.length > 0) {
|
|
12
|
+
logger.info(`Unchanged: ${stats.unchanged.length}`);
|
|
13
|
+
}
|
|
14
|
+
if (stats.removed.length > 0) {
|
|
15
|
+
logger.info(`Removed managed legacy files: ${stats.removed.length}`);
|
|
16
|
+
for (const p of stats.removed) logger.info(` - ${p}`);
|
|
17
|
+
}
|
|
18
|
+
if (stats.collisions.length > 0) {
|
|
19
|
+
logger.warn("Slash collision preserved because the legacy command is customized:");
|
|
20
|
+
for (const p of stats.collisions) logger.info(` ! ${p}`);
|
|
21
|
+
}
|
|
22
|
+
if (stats.preservedCustomized.length > 0) {
|
|
23
|
+
logger.warn(
|
|
24
|
+
`Preserved customized overlay (agents/skills/commands/hooks/scripts); local body kept, kit body not applied: ${stats.preservedCustomized.length}`
|
|
25
|
+
);
|
|
26
|
+
for (const p of stats.preservedCustomized) logger.info(` ! ${p}`);
|
|
27
|
+
logger.info(
|
|
28
|
+
" Run `agent-kit diff` to see the kit body; send the change upstream (`agent-kit contribute` for agents/skills/commands/hooks, a factory PR for scripts); or add the path to `protected` in .cursor/agent-kit.json to keep it pinned on purpose."
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
if (stats.skippedProtected.length > 0) {
|
|
32
|
+
logger.warn(`Skipped protected (L3): ${stats.skippedProtected.length}`);
|
|
33
|
+
for (const p of stats.skippedProtected) logger.info(` ~ ${p}`);
|
|
34
|
+
}
|
|
35
|
+
if (stats.skippedSymlink.length > 0) {
|
|
36
|
+
logger.warn(
|
|
37
|
+
`Skipped symlinked path (resolves outside the registry/project root, or is a symlink): ${stats.skippedSymlink.length}`
|
|
38
|
+
);
|
|
39
|
+
for (const p of stats.skippedSymlink) logger.info(` ~ ${p}`);
|
|
40
|
+
}
|
|
41
|
+
if (stats.missing.length > 0) {
|
|
42
|
+
logger.warn(`Missing in registry: ${stats.missing.length}`);
|
|
43
|
+
for (const p of stats.missing) logger.info(` ? ${p}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function listSkippedPaths(paths) {
|
|
47
|
+
const shown = paths.slice(0, 5).join(", ");
|
|
48
|
+
const more = paths.length > 5 ? ` (+${paths.length - 5} more)` : "";
|
|
49
|
+
return `${shown}${more}`;
|
|
50
|
+
}
|
|
51
|
+
function formatSkippedSymlinks(paths) {
|
|
52
|
+
return `${paths.length} kit path(s) were not written because they resolve outside the project (symlink): ${listSkippedPaths(paths)}. Manifest version left unchanged; replace the symlinked directory with a real one inside the project and re-run.`;
|
|
53
|
+
}
|
|
54
|
+
function formatPersonalizationSkippedSymlinks(paths, notRecorded) {
|
|
55
|
+
const ids = notRecorded.length > 0 ? notRecorded.join(", ") : "none";
|
|
56
|
+
return `${paths.length} personalization path(s) were not written because they resolve outside the project (symlink): ${listSkippedPaths(paths)}. Manifest saved without: ${ids}; replace the symlinked directory with a real one inside the project and re-run.`;
|
|
57
|
+
}
|
|
58
|
+
var SkippedSymlinkError = class extends Error {
|
|
59
|
+
constructor(stats, message = formatSkippedSymlinks(stats.skippedSymlink), claudeSessionStartInstructions) {
|
|
60
|
+
super(message);
|
|
61
|
+
this.stats = stats;
|
|
62
|
+
this.claudeSessionStartInstructions = claudeSessionStartInstructions;
|
|
63
|
+
this.name = "SkippedSymlinkError";
|
|
64
|
+
}
|
|
65
|
+
stats;
|
|
66
|
+
claudeSessionStartInstructions;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export {
|
|
70
|
+
logApplyStats,
|
|
71
|
+
formatSkippedSymlinks,
|
|
72
|
+
formatPersonalizationSkippedSymlinks,
|
|
73
|
+
SkippedSymlinkError
|
|
74
|
+
};
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
// src/invariants/handoff-prune.ts
|
|
2
|
+
var HANDOFF_MACHINE_FIELD_LABELS = [
|
|
3
|
+
"Plan",
|
|
4
|
+
"Last updated",
|
|
5
|
+
"Mode",
|
|
6
|
+
"Phase completed",
|
|
7
|
+
"Next phase",
|
|
8
|
+
"Completed to-dos",
|
|
9
|
+
"Next to-dos",
|
|
10
|
+
"Parked plans",
|
|
11
|
+
"Backlog plans",
|
|
12
|
+
"Run queue",
|
|
13
|
+
"Queue cursor",
|
|
14
|
+
"Queue status",
|
|
15
|
+
"Queue outcomes",
|
|
16
|
+
"Gaps",
|
|
17
|
+
"Instruction for the next agent"
|
|
18
|
+
];
|
|
19
|
+
var HANDOFF_FIXED_SECTION_HEADING_PREFIXES = [
|
|
20
|
+
"## Work Status",
|
|
21
|
+
"## Session Context",
|
|
22
|
+
"## Files Touched",
|
|
23
|
+
"## Decisions Made",
|
|
24
|
+
"## Issues Found",
|
|
25
|
+
"## Run queue (multi-plan only)"
|
|
26
|
+
];
|
|
27
|
+
var HANDOFF_MAX_LINES = 200;
|
|
28
|
+
var HANDOFF_MAX_LINE_LENGTH = 2e3;
|
|
29
|
+
var MACHINE_FIELD_RE = new RegExp(
|
|
30
|
+
`^- \\*\\*(${HANDOFF_MACHINE_FIELD_LABELS.map((l) => l.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")}):\\*\\*`
|
|
31
|
+
);
|
|
32
|
+
function isChunkStart(line) {
|
|
33
|
+
return /^- \*\*/.test(line) || /^## /.test(line);
|
|
34
|
+
}
|
|
35
|
+
function isFixedSectionHeading(line) {
|
|
36
|
+
return HANDOFF_FIXED_SECTION_HEADING_PREFIXES.some((p) => line.startsWith(p));
|
|
37
|
+
}
|
|
38
|
+
function splitIntoChunks(lines) {
|
|
39
|
+
const firstStart = lines.findIndex(isChunkStart);
|
|
40
|
+
if (firstStart === -1) return { preamble: lines, chunks: [] };
|
|
41
|
+
const preamble = lines.slice(0, firstStart);
|
|
42
|
+
const chunks = [];
|
|
43
|
+
let current = [lines[firstStart]];
|
|
44
|
+
for (const line of lines.slice(firstStart + 1)) {
|
|
45
|
+
if (isChunkStart(line)) {
|
|
46
|
+
chunks.push({ lines: current, isMachineField: MACHINE_FIELD_RE.test(current[0] ?? "") });
|
|
47
|
+
current = [line];
|
|
48
|
+
} else {
|
|
49
|
+
current.push(line);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
chunks.push({ lines: current, isMachineField: MACHINE_FIELD_RE.test(current[0] ?? "") });
|
|
53
|
+
return { preamble, chunks };
|
|
54
|
+
}
|
|
55
|
+
function pruneHandoffText(text, keep) {
|
|
56
|
+
if (!text.trim()) {
|
|
57
|
+
return { ok: false, reason: "empty file" };
|
|
58
|
+
}
|
|
59
|
+
const lines = text.split("\n");
|
|
60
|
+
const titleIdx = lines.findIndex((l) => /^# /.test(l));
|
|
61
|
+
if (titleIdx === -1) {
|
|
62
|
+
return { ok: false, reason: "no H1 title line found" };
|
|
63
|
+
}
|
|
64
|
+
let fixedStartIdx = lines.findIndex((l) => isFixedSectionHeading(l));
|
|
65
|
+
if (fixedStartIdx === -1) fixedStartIdx = lines.length;
|
|
66
|
+
const head = lines.slice(0, titleIdx + 1);
|
|
67
|
+
const narrativeLines = lines.slice(titleIdx + 1, fixedStartIdx);
|
|
68
|
+
const trailingLines = lines.slice(fixedStartIdx);
|
|
69
|
+
const { preamble, chunks } = splitIntoChunks(narrativeLines);
|
|
70
|
+
const machineFieldCount = chunks.filter((c) => c.isMachineField).length;
|
|
71
|
+
if (machineFieldCount === 0) {
|
|
72
|
+
return { ok: false, reason: "no machine-field bullets found; refusing to prune" };
|
|
73
|
+
}
|
|
74
|
+
if (fixedStartIdx === lines.length) {
|
|
75
|
+
return { ok: false, reason: "no recognized fixed-section heading found; refusing to prune" };
|
|
76
|
+
}
|
|
77
|
+
const narrativeChunks = chunks.filter((c) => !c.isMachineField);
|
|
78
|
+
if (narrativeChunks.length <= keep) {
|
|
79
|
+
return { ok: true, text, archived: [], keptCount: narrativeChunks.length };
|
|
80
|
+
}
|
|
81
|
+
const keptNarrativeIdx = /* @__PURE__ */ new Set();
|
|
82
|
+
let seen = 0;
|
|
83
|
+
const chunkIsNarrative = chunks.map((c) => !c.isMachineField);
|
|
84
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
85
|
+
if (chunkIsNarrative[i]) {
|
|
86
|
+
if (seen < keep) keptNarrativeIdx.add(i);
|
|
87
|
+
seen++;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const keptChunks = [];
|
|
91
|
+
const archivedChunks = [];
|
|
92
|
+
chunks.forEach((c, i) => {
|
|
93
|
+
if (c.isMachineField || keptNarrativeIdx.has(i)) keptChunks.push(c);
|
|
94
|
+
else archivedChunks.push(c);
|
|
95
|
+
});
|
|
96
|
+
const newNarrativeLines = [...preamble, ...keptChunks.flatMap((c) => c.lines)];
|
|
97
|
+
const newLines = [...head, ...newNarrativeLines, ...trailingLines];
|
|
98
|
+
return {
|
|
99
|
+
ok: true,
|
|
100
|
+
text: newLines.join("\n"),
|
|
101
|
+
archived: archivedChunks.map((c) => c.lines.join("\n")),
|
|
102
|
+
keptCount: keptNarrativeIdx.size
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function checkHandoffGrowth(text) {
|
|
106
|
+
if (!text.trim()) return [];
|
|
107
|
+
const lines = text.split("\n");
|
|
108
|
+
const warnings = [];
|
|
109
|
+
if (lines.length > HANDOFF_MAX_LINES) {
|
|
110
|
+
warnings.push({
|
|
111
|
+
code: "file-over-limit",
|
|
112
|
+
message: `HANDOFF.md is ${lines.length} lines, over the ${HANDOFF_MAX_LINES}-line growth-contract limit. Run \`agent-kit handoff --prune\` to archive older narrative entries.`
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
let firstOffendingLine = -1;
|
|
116
|
+
let overCount = 0;
|
|
117
|
+
lines.forEach((l, i) => {
|
|
118
|
+
if (l.length > HANDOFF_MAX_LINE_LENGTH) {
|
|
119
|
+
overCount++;
|
|
120
|
+
if (firstOffendingLine === -1) firstOffendingLine = i + 1;
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
if (overCount > 0) {
|
|
124
|
+
warnings.push({
|
|
125
|
+
code: "line-over-limit",
|
|
126
|
+
message: `HANDOFF.md has ${overCount} line(s) over the ${HANDOFF_MAX_LINE_LENGTH}-char growth-contract limit (first at line ${firstOffendingLine}). Wrap long paragraphs; do not widen the limit.`
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
return warnings;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export {
|
|
133
|
+
pruneHandoffText,
|
|
134
|
+
checkHandoffGrowth
|
|
135
|
+
};
|