@dadado/agent-kit-cli 4.8.4 → 4.8.9
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 +59 -0
- package/dashboard/dashboard-data.mjs +235 -77
- package/dashboard/dashboard.html +1505 -468
- package/dashboard/lib/semantic-model.mjs +173 -22
- package/dashboard/lib/terminal-snapshot.mjs +127 -0
- package/dist/index.js +1190 -333
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# @dadado/agent-kit-cli
|
|
2
|
+
|
|
3
|
+
Agent Kit CLI: HITL operating-layer install and tooling for AI-assisted IDEs (rules, skills, plan/handoff, staging-to-prod, memory). It installs local workspace contracts; it is not a hosted control plane or graph workflow runtime.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
From your project root (Node.js 20+):
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx @dadado/agent-kit-cli install
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Unpinned `npx` resolves to the latest publish. Pin a version when you need a reproducible install:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx @dadado/agent-kit-cli@x.y.z install
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Optional L1 packs:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npx @dadado/agent-kit-cli install --pack clean-code,context-management
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Install writes L0 kit files under `.cursor/`, plus `autogit/` and `.cursor/agent-kit.json`. It does not copy the Agent Kit monorepo into your project.
|
|
26
|
+
|
|
27
|
+
After install, in Cursor run `/agent-kit-onboard`, then `/start-project` when you have a deliverable.
|
|
28
|
+
|
|
29
|
+
## Mission Control
|
|
30
|
+
|
|
31
|
+
From package version **4.8.2** onward, this npm package includes Mission Control panel assets under `dashboard/`. In a consumer workspace after install:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
agent-kit dashboard
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The panel binds to loopback by default, serves its own static files, and snapshots the current workspace. L0 install does **not** copy `dashboard/` into your app; `agent-kit dashboard` resolves the panel from the installed package.
|
|
38
|
+
|
|
39
|
+
Older tags before 4.8.2 do not include those assets. Prefer a current pin, or point `MISSION_CONTROL_KIT_ROOT` / `AGENT_KIT_HOME` at an agent-kit checkout that contains `dashboard/`.
|
|
40
|
+
|
|
41
|
+
## Common commands
|
|
42
|
+
|
|
43
|
+
| Command | Purpose |
|
|
44
|
+
|---------|---------|
|
|
45
|
+
| `agent-kit install` | Bootstrap L0 (+ optional packs) and write `agent-kit.json` |
|
|
46
|
+
| `agent-kit status` | Show installed kit version and profile |
|
|
47
|
+
| `agent-kit doctor` | Diagnose repository readiness |
|
|
48
|
+
| `agent-kit update` | Re-apply L0/packs/skills from the registry |
|
|
49
|
+
| `agent-kit dashboard` | Start Mission Control for this workspace |
|
|
50
|
+
| `agent-kit add <id>` | Install a skill or L1 pack |
|
|
51
|
+
| `agent-kit run-plan` | Headless continuous plan runner (never promotes to production) |
|
|
52
|
+
|
|
53
|
+
Run `agent-kit --help` or `agent-kit <command> --help` for the full surface.
|
|
54
|
+
|
|
55
|
+
## Docs
|
|
56
|
+
|
|
57
|
+
- Public repository and guides: https://github.com/agent-kit-startup/agent-kit
|
|
58
|
+
- Install contract (chat / no-CLI fallback): https://raw.githubusercontent.com/agent-kit-startup/agent-kit/main/install.md
|
|
59
|
+
- Getting started: https://github.com/agent-kit-startup/agent-kit/blob/main/docs/getting-started.md
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
buildMissionControlView,
|
|
25
25
|
collectDeferredCheckIds,
|
|
26
26
|
collectReadinessPendingFromReport,
|
|
27
|
+
describeProcess,
|
|
27
28
|
detectAwaitingPrompt,
|
|
28
29
|
dismissedAttentionIds,
|
|
29
30
|
extractChatSnippet,
|
|
@@ -37,15 +38,26 @@ import {
|
|
|
37
38
|
serializeFlightLogLedger,
|
|
38
39
|
serializeMissionTimingLedger,
|
|
39
40
|
} from "./lib/semantic-model.mjs";
|
|
41
|
+
import { MAX_TERMINAL_BYTES, buildTerminalSnapshotFields } from "./lib/terminal-snapshot.mjs";
|
|
40
42
|
|
|
41
43
|
const KIT_ROOT = resolve(import.meta.dirname, "..");
|
|
42
44
|
/** Snapshot root: consumer workspace when MISSION_CONTROL_REPO_ROOT is set, else kit tree. */
|
|
43
45
|
const ROOT = resolveSnapshotRepoRoot(process.env, KIT_ROOT);
|
|
44
46
|
const MAX_TERMINALS = 20;
|
|
45
47
|
const MAX_PROCESSES = 25;
|
|
46
|
-
const
|
|
47
|
-
const
|
|
48
|
-
|
|
48
|
+
const MAX_GIT_GRAPH_LINES = 25;
|
|
49
|
+
const MAX_GIT_GRAPH_LINE_CHARS = 160;
|
|
50
|
+
|
|
51
|
+
/** Soft wall-clock budget for optional collectors (transcripts, reports, ps). */
|
|
52
|
+
const SNAPSHOT_STARTED_MS = Date.now();
|
|
53
|
+
const SNAPSHOT_BUDGET_MS = (() => {
|
|
54
|
+
const raw = process.env.AGENT_KIT_DASHBOARD_DATA_BUDGET_MS;
|
|
55
|
+
const n = raw != null && raw !== "" ? Number(raw) : 12_000;
|
|
56
|
+
return Number.isFinite(n) && n > 0 ? n : 12_000;
|
|
57
|
+
})();
|
|
58
|
+
function withinSnapshotBudget(reserveMs = 400) {
|
|
59
|
+
return Date.now() - SNAPSHOT_STARTED_MS + reserveMs < SNAPSHOT_BUDGET_MS;
|
|
60
|
+
}
|
|
49
61
|
|
|
50
62
|
// Agent-prompt scan bounds (fs half of the detection contract in semantic-model.mjs).
|
|
51
63
|
const MAX_TRANSCRIPT_FILES = 60; // cap directory reads per snapshot
|
|
@@ -82,31 +94,6 @@ function redactTerminalOutput(text) {
|
|
|
82
94
|
return out;
|
|
83
95
|
}
|
|
84
96
|
|
|
85
|
-
/** Last N lines of terminal body after YAML header, char-capped and redacted. */
|
|
86
|
-
function extractLastOutput(rawContent) {
|
|
87
|
-
const lines = rawContent.split("\n");
|
|
88
|
-
let headerEnd = 0;
|
|
89
|
-
let dashCount = 0;
|
|
90
|
-
for (let i = 0; i < lines.length; i++) {
|
|
91
|
-
if (lines[i].trim() === "---") {
|
|
92
|
-
dashCount++;
|
|
93
|
-
if (dashCount === 2) {
|
|
94
|
-
headerEnd = i + 1;
|
|
95
|
-
break;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
if (headerEnd === 0) headerEnd = 10;
|
|
100
|
-
|
|
101
|
-
const bodyLines = lines.slice(headerEnd).filter((l) => l.trim() && !l.startsWith("---"));
|
|
102
|
-
if (bodyLines.length === 0) return null;
|
|
103
|
-
|
|
104
|
-
const tail = bodyLines.slice(-MAX_LAST_OUTPUT_LINES);
|
|
105
|
-
let text = redactTerminalOutput(tail.join("\n"));
|
|
106
|
-
text = truncateStr(text, MAX_LAST_OUTPUT_CHARS);
|
|
107
|
-
return text?.trim() ? text : null;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
97
|
const SNAPSHOT = {
|
|
111
98
|
_schema: {
|
|
112
99
|
version: "1.2.0",
|
|
@@ -119,18 +106,20 @@ const SNAPSHOT = {
|
|
|
119
106
|
"System metadata: repoRoot, listen port, handoff state, allowlisted config summary, package info, version, name, contextPacks",
|
|
120
107
|
agents: "Agent definitions from .cursor/agents/*.md",
|
|
121
108
|
commands: "Slash commands from .cursor/commands/*.md",
|
|
122
|
-
memory:
|
|
123
|
-
|
|
109
|
+
memory:
|
|
110
|
+
"Memory records: error count, decision count, recent decisions, recent parsed errors, error-o-meter stats",
|
|
111
|
+
git: "Git repository state: branch, dirty status, commit, ahead/behind, bounded files[], promotion flow vs staging/main, graph lines, staging hygiene",
|
|
124
112
|
terminals:
|
|
125
113
|
"Active Cursor terminal sessions with metadata, output line count, and capped lastOutput",
|
|
126
|
-
processes:
|
|
114
|
+
processes:
|
|
115
|
+
"Running process snapshots (node, serve.mjs, git operations) with elapsed time and a generated narration per process",
|
|
127
116
|
skills: "Available skills discovered in .cursor/skills/",
|
|
128
117
|
health: "Aggregated health status with per-check results",
|
|
129
118
|
missionControl: "Normalized now/activity/attention/plans view model (source-backed; bounded)",
|
|
130
119
|
},
|
|
131
120
|
},
|
|
132
121
|
generatedAt: new Date().toISOString(),
|
|
133
|
-
dashboardDataVersion: "1.
|
|
122
|
+
dashboardDataVersion: "1.3.0",
|
|
134
123
|
plans: [],
|
|
135
124
|
system: {
|
|
136
125
|
repoRoot: ROOT,
|
|
@@ -243,9 +232,123 @@ if (existsSync(commandsDir)) {
|
|
|
243
232
|
}
|
|
244
233
|
}
|
|
245
234
|
|
|
235
|
+
// 4b. Kit-managed marker: commands, skills, and agents listed in registry/registry.json are
|
|
236
|
+
// owned by kit updates (read-only from the dashboard). No registry: all project-local.
|
|
237
|
+
const kitCommandPaths = new Set();
|
|
238
|
+
const kitSkillDirs = new Set();
|
|
239
|
+
const kitAgentPaths = new Set();
|
|
240
|
+
const registryFile = join(ROOT, "registry", "registry.json");
|
|
241
|
+
if (existsSync(registryFile)) {
|
|
242
|
+
try {
|
|
243
|
+
const registry = JSON.parse(readFileSync(registryFile, "utf8"));
|
|
244
|
+
const entries = Array.isArray(registry?.artifacts) ? registry.artifacts : [];
|
|
245
|
+
for (const entry of entries) {
|
|
246
|
+
if (entry?.kind === "command" && typeof entry?.path === "string") {
|
|
247
|
+
kitCommandPaths.add(entry.path);
|
|
248
|
+
}
|
|
249
|
+
if (
|
|
250
|
+
entry?.kind === "skill" &&
|
|
251
|
+
typeof entry?.path === "string" &&
|
|
252
|
+
entry.path.startsWith("registry/skills/")
|
|
253
|
+
) {
|
|
254
|
+
kitSkillDirs.add(entry.path.replace(/^registry\/skills\//, ".cursor/skills/"));
|
|
255
|
+
}
|
|
256
|
+
if (entry?.kind === "agent" && typeof entry?.path === "string") {
|
|
257
|
+
kitAgentPaths.add(entry.path);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
} catch {
|
|
261
|
+
// Unreadable registry: degrade to all-editable rather than locking everything.
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
for (const c of SNAPSHOT.commands) {
|
|
265
|
+
c.kitManaged = kitCommandPaths.has(c.path);
|
|
266
|
+
}
|
|
267
|
+
for (const a of SNAPSHOT.agents) {
|
|
268
|
+
a.kitManaged = kitAgentPaths.has(a.path);
|
|
269
|
+
}
|
|
270
|
+
|
|
246
271
|
// 5. Memory
|
|
247
272
|
const memoryErrorsDir = join(ROOT, ".cursor", "memory", "errors");
|
|
248
273
|
const memoryDecisionsDir = join(ROOT, ".cursor", "memory", "decisions");
|
|
274
|
+
const MAX_MEMORY_RECENT_ERRORS = 12; // cap parsed entries shipped per snapshot
|
|
275
|
+
const MAX_MEMORY_ERROR_BYTES = 64 * 1024; // skip oversized entries, degrade quietly
|
|
276
|
+
|
|
277
|
+
/** Parse one `.cursor/memory/errors/*.md` entry into the KPI-friendly shape. */
|
|
278
|
+
function parseMemoryErrorFile(dir, file) {
|
|
279
|
+
const id = file.replace(/\.md$/, "");
|
|
280
|
+
const path = `.cursor/memory/errors/${file}`;
|
|
281
|
+
const full = join(dir, file);
|
|
282
|
+
let modifiedAt = null;
|
|
283
|
+
try {
|
|
284
|
+
modifiedAt = statSync(full).mtime.toISOString();
|
|
285
|
+
} catch {
|
|
286
|
+
modifiedAt = null;
|
|
287
|
+
}
|
|
288
|
+
let raw = "";
|
|
289
|
+
try {
|
|
290
|
+
raw = readFileSync(full, "utf-8").slice(0, MAX_MEMORY_ERROR_BYTES);
|
|
291
|
+
} catch {
|
|
292
|
+
return {
|
|
293
|
+
id,
|
|
294
|
+
path,
|
|
295
|
+
title: id,
|
|
296
|
+
date: "",
|
|
297
|
+
error: "",
|
|
298
|
+
cause: "",
|
|
299
|
+
solution: "",
|
|
300
|
+
files: "",
|
|
301
|
+
tags: [],
|
|
302
|
+
modifiedAt,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
const field = (...names) => {
|
|
306
|
+
for (const name of names) {
|
|
307
|
+
const m = raw.match(new RegExp(`^- \\*\\*${name}:\\*\\*\\s*(.+)$`, "im"));
|
|
308
|
+
if (m) return truncateStr(m[1].trim(), 600);
|
|
309
|
+
}
|
|
310
|
+
return "";
|
|
311
|
+
};
|
|
312
|
+
const titleMatch = raw.match(/^#\s+(.+)$/m);
|
|
313
|
+
const tags = field("Tags")
|
|
314
|
+
.split(",")
|
|
315
|
+
.map((t) => t.trim())
|
|
316
|
+
.filter(Boolean);
|
|
317
|
+
return {
|
|
318
|
+
id,
|
|
319
|
+
path,
|
|
320
|
+
title: titleMatch ? truncateStr(titleMatch[1].trim(), 200) : id,
|
|
321
|
+
date: field("Date", "Data"),
|
|
322
|
+
error: field("Error", "Erro"),
|
|
323
|
+
cause: field("Cause", "Causa"),
|
|
324
|
+
solution: field("Solution", "Solução", "Solucao"),
|
|
325
|
+
files: field("Files", "Arquivos"),
|
|
326
|
+
tags,
|
|
327
|
+
modifiedAt,
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/** Error-o-meter aggregates: counts, rates, and top tags across parsed entries. */
|
|
332
|
+
function computeMemoryErrorStats(entries) {
|
|
333
|
+
const total = entries.length;
|
|
334
|
+
const now = Date.now();
|
|
335
|
+
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
336
|
+
const last30d = entries.filter((e) => {
|
|
337
|
+
const t = Date.parse(e.date || e.modifiedAt || "");
|
|
338
|
+
return Number.isFinite(t) && now - t <= 30 * DAY_MS;
|
|
339
|
+
}).length;
|
|
340
|
+
const weeklyRate = Math.round((last30d / 30) * 7 * 10) / 10;
|
|
341
|
+
const tagCounts = new Map();
|
|
342
|
+
for (const e of entries) {
|
|
343
|
+
for (const t of e.tags || []) tagCounts.set(t, (tagCounts.get(t) || 0) + 1);
|
|
344
|
+
}
|
|
345
|
+
const topTags = [...tagCounts.entries()]
|
|
346
|
+
.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
|
|
347
|
+
.slice(0, 6)
|
|
348
|
+
.map(([tag, count]) => ({ tag, count }));
|
|
349
|
+
return { total, last30d, weeklyRate, topTags };
|
|
350
|
+
}
|
|
351
|
+
|
|
249
352
|
if (existsSync(memoryErrorsDir)) {
|
|
250
353
|
const errorFiles = readdirSync(memoryErrorsDir).filter((f) => f.endsWith(".md"));
|
|
251
354
|
SNAPSHOT.memory.errors = errorFiles.length;
|
|
@@ -259,6 +362,11 @@ if (existsSync(memoryErrorsDir)) {
|
|
|
259
362
|
}
|
|
260
363
|
return { id, modifiedAt };
|
|
261
364
|
});
|
|
365
|
+
const parsedErrors = errorFiles
|
|
366
|
+
.map((f) => parseMemoryErrorFile(memoryErrorsDir, f))
|
|
367
|
+
.sort((a, b) => String(b.date || b.id).localeCompare(String(a.date || a.id)));
|
|
368
|
+
SNAPSHOT.memory.recentErrors = parsedErrors.slice(0, MAX_MEMORY_RECENT_ERRORS);
|
|
369
|
+
SNAPSHOT.memory.errorStats = computeMemoryErrorStats(parsedErrors);
|
|
262
370
|
}
|
|
263
371
|
if (existsSync(memoryDecisionsDir)) {
|
|
264
372
|
const files = readdirSync(memoryDecisionsDir).filter((f) => f.endsWith(".md"));
|
|
@@ -314,6 +422,44 @@ try {
|
|
|
314
422
|
recentLog = [];
|
|
315
423
|
}
|
|
316
424
|
|
|
425
|
+
// Promotion flow state: ahead/behind of HEAD vs BOTH origin/staging and
|
|
426
|
+
// origin/main, plus staging vs main (pending promotion count).
|
|
427
|
+
const countDivergence = (range) => {
|
|
428
|
+
try {
|
|
429
|
+
const out = execSync(`git rev-list --left-right --count ${range}`, gitOpts).trim();
|
|
430
|
+
const [left, right] = out.split(/\s+/).map((n) => Number.parseInt(n, 10) || 0);
|
|
431
|
+
return { ahead: right, behind: left };
|
|
432
|
+
} catch {
|
|
433
|
+
return null;
|
|
434
|
+
}
|
|
435
|
+
};
|
|
436
|
+
const flow = {
|
|
437
|
+
vsStaging: countDivergence("origin/staging...HEAD"),
|
|
438
|
+
vsMain: countDivergence("origin/main...HEAD"),
|
|
439
|
+
stagingVsMain: countDivergence("origin/main...origin/staging"),
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
// Readable graph (branch lanes + merges) as pre-rendered text lines.
|
|
443
|
+
let graphLines = [];
|
|
444
|
+
try {
|
|
445
|
+
graphLines = execSync(
|
|
446
|
+
`git log --graph --oneline --decorate --date-order --all -n ${MAX_GIT_GRAPH_LINES}`,
|
|
447
|
+
gitOpts,
|
|
448
|
+
)
|
|
449
|
+
.trimEnd()
|
|
450
|
+
.split("\n")
|
|
451
|
+
.filter(Boolean)
|
|
452
|
+
.map((line) => truncateStr(line, MAX_GIT_GRAPH_LINE_CHARS));
|
|
453
|
+
} catch {
|
|
454
|
+
graphLines = [];
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Staging hygiene: untracked plan-monitor WIP (add-by-name only, never a
|
|
458
|
+
// broad git add of .cursor/memory/; ADR 2026-07-29 staging hygiene R14/R15).
|
|
459
|
+
const monitorWip = parsed.files
|
|
460
|
+
.filter((f) => f.untracked && /^\.cursor\/memory\/plan-monitor-.+\.md$/.test(f.path))
|
|
461
|
+
.map((f) => f.path);
|
|
462
|
+
|
|
317
463
|
SNAPSHOT.git = {
|
|
318
464
|
branch: truncateStr(branch, MAX_STRING.branch),
|
|
319
465
|
dirty: parsed.total > 0,
|
|
@@ -323,6 +469,9 @@ try {
|
|
|
323
469
|
lastCommit: truncateStr(lastCommit, MAX_STRING.lastCommit),
|
|
324
470
|
ahead,
|
|
325
471
|
behind,
|
|
472
|
+
flow,
|
|
473
|
+
graph: graphLines,
|
|
474
|
+
hygiene: { monitorWip },
|
|
326
475
|
};
|
|
327
476
|
SNAPSHOT._gitRecentLog = recentLog;
|
|
328
477
|
} catch {
|
|
@@ -344,26 +493,24 @@ if (existsSync(terminalProjectPath)) {
|
|
|
344
493
|
for (const file of files) {
|
|
345
494
|
const full = join(terminalProjectPath, file);
|
|
346
495
|
const raw = readFileSync(full, "utf-8");
|
|
347
|
-
//
|
|
348
|
-
|
|
349
|
-
const
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
if (line.startsWith("command:")) meta.lastCommand = line.slice(8).trim();
|
|
355
|
-
if (line.startsWith("last_command:")) meta.lastCommand = line.slice(13).trim();
|
|
356
|
-
if (line.startsWith("last_exit_code:")) meta.lastExitCode = line.slice(15).trim();
|
|
357
|
-
}
|
|
358
|
-
const outputLines = lines.slice(10).filter((l) => {
|
|
359
|
-
return l.trim() && !l.startsWith("---");
|
|
360
|
-
}).length;
|
|
361
|
-
const lastOutput = extractLastOutput(content);
|
|
496
|
+
// Meta from file head; body/output from tail-cap so over-cap terminals keep pid/cwd/exit
|
|
497
|
+
// (plain tail-slice previously dropped the header and blanked exit-code dots).
|
|
498
|
+
const { meta, outputLines, lastOutput } = buildTerminalSnapshotFields(raw, {
|
|
499
|
+
maxBytes: MAX_TERMINAL_BYTES,
|
|
500
|
+
redact: redactTerminalOutput,
|
|
501
|
+
truncate: truncateStr,
|
|
502
|
+
});
|
|
362
503
|
const entry = {
|
|
363
504
|
id: file,
|
|
364
505
|
...redactTerminalMeta(meta),
|
|
365
506
|
outputLines,
|
|
366
507
|
};
|
|
508
|
+
// File mtime feeds the busy-outside-plan freshness window (semantic model).
|
|
509
|
+
try {
|
|
510
|
+
entry.updatedAt = statSync(full).mtime.toISOString();
|
|
511
|
+
} catch {
|
|
512
|
+
// Missing mtime only disables the busy freshness signal for this terminal.
|
|
513
|
+
}
|
|
367
514
|
if (lastOutput) entry.lastOutput = lastOutput;
|
|
368
515
|
SNAPSHOT.terminals.push(entry);
|
|
369
516
|
}
|
|
@@ -431,6 +578,7 @@ if (existsSync(skillsDir)) {
|
|
|
431
578
|
title: titleMatch ? titleMatch[1].trim() : relativeDir.split("/").pop(),
|
|
432
579
|
description: descMatch ? descMatch[1].trim().slice(0, 150) : "",
|
|
433
580
|
file: fullPath.replace(`${ROOT}/`, ""),
|
|
581
|
+
kitManaged: kitSkillDirs.has(`.cursor/skills/${relativeDir}`),
|
|
434
582
|
});
|
|
435
583
|
}
|
|
436
584
|
}
|
|
@@ -443,36 +591,43 @@ if (existsSync(skillsDir)) {
|
|
|
443
591
|
|
|
444
592
|
// 13. Process scanning (capped list: the UI only needs a sample of relevant procs)
|
|
445
593
|
try {
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
594
|
+
if (!withinSnapshotBudget(500)) {
|
|
595
|
+
SNAPSHOT.processes = [];
|
|
596
|
+
} else {
|
|
597
|
+
const psOutput = execSync("ps -axo pid=,pcpu=,pmem=,etime=,command=", {
|
|
598
|
+
encoding: "utf-8",
|
|
599
|
+
timeout: 3000,
|
|
600
|
+
}).trim();
|
|
601
|
+
if (psOutput) {
|
|
602
|
+
const interesting = [];
|
|
603
|
+
for (const line of psOutput.split("\n")) {
|
|
604
|
+
const trimmed = line.trim();
|
|
605
|
+
if (!trimmed) continue;
|
|
606
|
+
if (!/node|git|serve\.mjs|dashboard/i.test(trimmed)) continue;
|
|
607
|
+
if (/grep|dashboard-data/.test(trimmed)) continue;
|
|
608
|
+
const parts = trimmed.split(/\s+/);
|
|
609
|
+
const pid = parts[0];
|
|
610
|
+
const cpu = parts[1];
|
|
611
|
+
const mem = parts[2];
|
|
612
|
+
const etime = parts[3];
|
|
613
|
+
const cmd = parts.slice(4).join(" ") || "unknown";
|
|
614
|
+
let label = "other";
|
|
615
|
+
if (cmd.includes("serve.mjs") || cmd.includes("node dashboard")) label = "dashboard-server";
|
|
616
|
+
else if (/\bgit\b/.test(cmd)) label = "git";
|
|
617
|
+
else if (cmd.includes("node")) label = "node";
|
|
618
|
+
interesting.push({
|
|
619
|
+
pid,
|
|
620
|
+
cpu,
|
|
621
|
+
mem,
|
|
622
|
+
etime,
|
|
623
|
+
command: truncateStr(cmd, MAX_STRING.processCommand),
|
|
624
|
+
label,
|
|
625
|
+
description: describeProcess({ label, command: cmd, cpu, etime }),
|
|
626
|
+
});
|
|
627
|
+
if (interesting.length >= MAX_PROCESSES) break;
|
|
628
|
+
}
|
|
629
|
+
SNAPSHOT.processes = interesting;
|
|
474
630
|
}
|
|
475
|
-
SNAPSHOT.processes = interesting;
|
|
476
631
|
}
|
|
477
632
|
} catch {
|
|
478
633
|
SNAPSHOT.processes = [];
|
|
@@ -483,7 +638,8 @@ const checks = [
|
|
|
483
638
|
{ id: "plans", label: "Plans directory", ok: existsSync(plansDir) && SNAPSHOT.plans.length > 0 },
|
|
484
639
|
// Present + parseable HANDOFF is healthy even when Plan is none/null (idle).
|
|
485
640
|
{ id: "handoff", label: "HANDOFF.md", ok: !!SNAPSHOT.system.handoff },
|
|
486
|
-
|
|
641
|
+
// L0-optional: empty .cursor/agents/ is healthy (packs/skills may add agents later).
|
|
642
|
+
{ id: "agents", label: "Agents", ok: true },
|
|
487
643
|
{ id: "commands", label: "Commands", ok: SNAPSHOT.commands.length > 0 },
|
|
488
644
|
{
|
|
489
645
|
id: "memory",
|
|
@@ -518,6 +674,7 @@ SNAPSHOT.health.status = checks.every((c) => c.ok)
|
|
|
518
674
|
* never an error state.
|
|
519
675
|
*/
|
|
520
676
|
function collectAgentPrompts() {
|
|
677
|
+
if (!withinSnapshotBudget(800)) return [];
|
|
521
678
|
const projectsDir = resolve(process.env.HOME || "~", ".cursor", "projects");
|
|
522
679
|
const slug = ROOT.replace(/\//g, "-").replace(/^-/, "");
|
|
523
680
|
const transcriptsDir = join(projectsDir, slug, "agent-transcripts");
|
|
@@ -592,6 +749,7 @@ function collectAgentPrompts() {
|
|
|
592
749
|
* directory yields an empty list, never an error state.
|
|
593
750
|
*/
|
|
594
751
|
function collectExternalReports() {
|
|
752
|
+
if (!withinSnapshotBudget(600)) return [];
|
|
595
753
|
const memoryDir = join(ROOT, ".cursor", "memory");
|
|
596
754
|
if (!existsSync(memoryDir)) return [];
|
|
597
755
|
|