@dadado/agent-kit-cli 5.1.0 → 5.2.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.html +26 -14
- package/dashboard/lib/guards.d.mts +103 -0
- package/dashboard/lib/open-browser.mjs +0 -5
- package/dashboard/lib/semantic-model.mjs +7 -1
- package/dashboard/start-broadcast.mjs +2 -2
- package/dashboard/start.mjs +2 -2
- package/dist/index.js +104 -22
- package/package.json +3 -1
package/dashboard/dashboard.html
CHANGED
|
@@ -743,16 +743,19 @@ body.mc-fullscreen .top-tabs-row {
|
|
|
743
743
|
}
|
|
744
744
|
|
|
745
745
|
/* ===== Progress Bar ===== */
|
|
746
|
+
/* Track must stay visibly a track at 0% (contract item 5): --border-active
|
|
747
|
+
reads against --bg-card in both skins where --border blended in, and 6px
|
|
748
|
+
with rounded ends keeps mid fills from collapsing into a hairline. */
|
|
746
749
|
.progress-bar {
|
|
747
|
-
height:
|
|
748
|
-
background: var(--border);
|
|
749
|
-
border-radius:
|
|
750
|
+
height: 6px;
|
|
751
|
+
background: var(--border-active);
|
|
752
|
+
border-radius: 3px;
|
|
750
753
|
overflow: hidden;
|
|
751
754
|
margin-top: 8px;
|
|
752
755
|
}
|
|
753
756
|
.progress-fill {
|
|
754
757
|
height: 100%;
|
|
755
|
-
border-radius:
|
|
758
|
+
border-radius: 3px;
|
|
756
759
|
transition: width 0.5s ease;
|
|
757
760
|
}
|
|
758
761
|
.progress-fill.green { background: var(--green); }
|
|
@@ -4267,11 +4270,11 @@ function fmtDate(iso) {
|
|
|
4267
4270
|
return d.toLocaleString('en-US', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' });
|
|
4268
4271
|
}
|
|
4269
4272
|
|
|
4273
|
+
// Presentation contract (Phase 2): progress is never an error signal. Any
|
|
4274
|
+
// in-flight percentage (0-99) renders the neutral accent; 100% renders green
|
|
4275
|
+
// (lifecycle completed with total > 0 implies 100 per the Phase 0 contract).
|
|
4270
4276
|
function progressColor(pct) {
|
|
4271
|
-
|
|
4272
|
-
if (pct >= 50) return 'blue';
|
|
4273
|
-
if (pct >= 25) return 'yellow';
|
|
4274
|
-
return 'red';
|
|
4277
|
+
return pct >= 100 ? 'green' : 'blue';
|
|
4275
4278
|
}
|
|
4276
4279
|
|
|
4277
4280
|
// Dot semantics: only state signals survive. completed = good, cancelled =
|
|
@@ -5614,8 +5617,8 @@ function renderConfigSection(d) {
|
|
|
5614
5617
|
</div>
|
|
5615
5618
|
<div class="config-row">
|
|
5616
5619
|
<label for="config-epr-reviewerModel">Reviewer model</label>
|
|
5617
|
-
<input type="text" id="config-epr-reviewerModel" name="eprReviewerModel" value="${escapeAttr(epr.reviewerModel || '
|
|
5618
|
-
<span class="config-hint">Claude default
|
|
5620
|
+
<input type="text" id="config-epr-reviewerModel" name="eprReviewerModel" value="${escapeAttr(epr.reviewerModel || 'sonnet')}" maxlength="64" data-focus-key="config-epr-reviewerModel" />
|
|
5621
|
+
<span class="config-hint">Claude default sonnet (auto permission mode). Must differ from the implementer stamp (Auto/Auto refused).</span>
|
|
5619
5622
|
</div>
|
|
5620
5623
|
<div class="config-row">
|
|
5621
5624
|
<label for="config-epr-advisorModel">Advisor model</label>
|
|
@@ -5702,7 +5705,7 @@ function collectMissionConfigPayload() {
|
|
|
5702
5705
|
offerOnExhausted: !!document.getElementById('config-epr-offer')?.checked,
|
|
5703
5706
|
autoRemediate: !!document.getElementById('config-epr-auto')?.checked,
|
|
5704
5707
|
backend: document.getElementById('config-epr-backend')?.value || 'claude',
|
|
5705
|
-
reviewerModel: (document.getElementById('config-epr-reviewerModel')?.value || '
|
|
5708
|
+
reviewerModel: (document.getElementById('config-epr-reviewerModel')?.value || 'sonnet').trim(),
|
|
5706
5709
|
advisorModel: (document.getElementById('config-epr-advisorModel')?.value || 'opus').trim(),
|
|
5707
5710
|
waitSliceSeconds: (() => {
|
|
5708
5711
|
const raw = document.getElementById('config-epr-waitSlice')?.value;
|
|
@@ -6334,10 +6337,18 @@ function mergePlansForUi(d) {
|
|
|
6334
6337
|
const inProgress = fromItems
|
|
6335
6338
|
? items.filter((t) => t.status === 'in_progress').length
|
|
6336
6339
|
: (raw.todos?.inProgress ?? 0);
|
|
6340
|
+
// Cancelled counts toward the fill numerator (terminal work, mirrors
|
|
6341
|
+
// TERMINAL_TODO_STATUSES / todoStats in dashboard/lib/semantic-model.mjs)
|
|
6342
|
+
// so the bar reaches 100% whenever the lifecycle pill says COMPLETED.
|
|
6343
|
+
const cancelled = fromItems
|
|
6344
|
+
? items.filter((t) => t.status === 'cancelled').length
|
|
6345
|
+
: (raw.todos?.cancelled ?? enriched.progress?.cancelled ?? 0);
|
|
6337
6346
|
const nextActionTodo = planNextActionTodo(items);
|
|
6338
6347
|
let lifecycle = enriched.lifecycle;
|
|
6339
6348
|
if (!lifecycle) {
|
|
6340
|
-
|
|
6349
|
+
// Mirrors classifyPlan: terminal (completed + cancelled) exhausting the
|
|
6350
|
+
// list means todoStats.open === 0 → completed.
|
|
6351
|
+
if (total > 0 && completed + cancelled >= total && inProgress === 0) {
|
|
6341
6352
|
lifecycle = 'completed';
|
|
6342
6353
|
} else {
|
|
6343
6354
|
lifecycle = 'incomplete';
|
|
@@ -6351,12 +6362,13 @@ function mergePlansForUi(d) {
|
|
|
6351
6362
|
modifiedAt: raw.modifiedAt || enriched.modifiedAt || null,
|
|
6352
6363
|
progressPct:
|
|
6353
6364
|
total > 0
|
|
6354
|
-
? Math.round((completed / total) * 100)
|
|
6365
|
+
? Math.round(((completed + cancelled) / total) * 100)
|
|
6355
6366
|
: typeof raw.progress === 'number'
|
|
6356
6367
|
? raw.progress
|
|
6357
6368
|
: 0,
|
|
6358
|
-
progressLabel: `${completed} of ${total} complete
|
|
6369
|
+
progressLabel: `${completed} of ${total} complete` + (cancelled > 0 ? ` · ${cancelled} cancelled` : ''),
|
|
6359
6370
|
progressCompleted: completed,
|
|
6371
|
+
progressCancelled: cancelled,
|
|
6360
6372
|
progressTotal: total,
|
|
6361
6373
|
progressInProgress: inProgress,
|
|
6362
6374
|
nextActionTodo,
|
|
@@ -1,5 +1,84 @@
|
|
|
1
1
|
/** Ambient types for dashboard/lib/guards.mjs (consumed by CLI TypeScript). */
|
|
2
2
|
|
|
3
|
+
type ProcessEnvLike = NodeJS.ProcessEnv | Record<string, string | undefined>;
|
|
4
|
+
type HeaderMap = Record<string, string | string[] | undefined>;
|
|
5
|
+
type GuardRequest = { headers?: HeaderMap };
|
|
6
|
+
type PortOpts = { base?: number; range?: number };
|
|
7
|
+
type GitStatusFile = {
|
|
8
|
+
path: string;
|
|
9
|
+
status: string;
|
|
10
|
+
staged: boolean;
|
|
11
|
+
unstaged: boolean;
|
|
12
|
+
untracked: boolean;
|
|
13
|
+
oldPath?: string;
|
|
14
|
+
renamed?: boolean;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const DEFAULT_HOST: "127.0.0.1";
|
|
18
|
+
export const BROADCAST_TOKEN_ENV: "MISSION_CONTROL_TOKEN";
|
|
19
|
+
export const REPO_ROOT_ENV: "MISSION_CONTROL_REPO_ROOT";
|
|
20
|
+
export const KIT_ROOT_ENV_KEYS: readonly ["MISSION_CONTROL_KIT_ROOT", "AGENT_KIT_HOME"];
|
|
21
|
+
export const BROADCAST_TOKEN_MIN_LEN: 16;
|
|
22
|
+
export const BROADCAST_TOKEN_COOKIE: "mc_token";
|
|
23
|
+
export const DEFAULT_PORT_BASE: 3333;
|
|
24
|
+
export const DEFAULT_PORT_RANGE: 256;
|
|
25
|
+
export const MAX_STRING: {
|
|
26
|
+
branch: number;
|
|
27
|
+
lastCommit: number;
|
|
28
|
+
terminalCwd: number;
|
|
29
|
+
terminalCommand: number;
|
|
30
|
+
processCommand: number;
|
|
31
|
+
};
|
|
32
|
+
export const MAX_GIT_FILES: 50;
|
|
33
|
+
export const MAX_GIT_PATH: 240;
|
|
34
|
+
export const CONTEXT_CONFIG_REL: ".cursor/context/config.json";
|
|
35
|
+
export const CONFIG_PERSONA_IDS: readonly ["autopilot", "night-shift", "ghost-runner"];
|
|
36
|
+
export const CONFIG_PERSONA_MODES: readonly ["continue-plan", "run-plan", "cli-run-plan"];
|
|
37
|
+
export const CONFIG_REVIEW_BACKENDS: readonly ["auto", "claude", "cursor"];
|
|
38
|
+
export const CONFIG_REVIEW_MODES: readonly ["paste", "autonomous"];
|
|
39
|
+
export const CONFIG_REVIEW_PREFLIGHT: readonly ["off", "warn", "block"];
|
|
40
|
+
|
|
41
|
+
export function escapePerlDoubleQuoted(value: string): string;
|
|
42
|
+
export function resolveSnapshotRepoRoot(env: ProcessEnvLike | undefined, kitRoot: string): string;
|
|
43
|
+
export function normalizeRepoRootKey(repoRoot: string): string;
|
|
44
|
+
export function hashRepoRoot(repoRoot: string): number;
|
|
45
|
+
export function repoRootLogId(repoRoot: string): string;
|
|
46
|
+
export function preferredPortForRepoRoot(repoRoot: string, opts?: PortOpts): number;
|
|
47
|
+
export function portCandidatesForRepoRoot(repoRoot: string, opts?: PortOpts): number[];
|
|
48
|
+
export function sameRepoRoot(a: string | null | undefined, b: string | null | undefined): boolean;
|
|
49
|
+
export function resolveMissionControlPort(args: {
|
|
50
|
+
repoRoot: string;
|
|
51
|
+
envPort?: string | number | null;
|
|
52
|
+
probe: (port: number) => { listening: boolean; repoRoot: string | null };
|
|
53
|
+
opts?: PortOpts;
|
|
54
|
+
}): { port: number; reuse: boolean; explicit: boolean };
|
|
55
|
+
export function isSafeRepoRelativePath(relPath: unknown): boolean;
|
|
56
|
+
export function resolveBindHost(envHost?: string | null): string;
|
|
57
|
+
export function isLoopbackBindHost(host: string | undefined | null): boolean;
|
|
58
|
+
export function normalizeAuthToken(raw: unknown): string;
|
|
59
|
+
export function isValidBroadcastToken(token: unknown): boolean;
|
|
60
|
+
export function generateBroadcastToken(): string;
|
|
61
|
+
export function tokensMatch(a: unknown, b: unknown): boolean;
|
|
62
|
+
export function resolveBroadcastAuth(
|
|
63
|
+
env?: ProcessEnvLike,
|
|
64
|
+
):
|
|
65
|
+
| { ok: true; host: string; tokenRequired: boolean; token: string | null; broadcast: boolean }
|
|
66
|
+
| { ok: false; error: string };
|
|
67
|
+
export function extractRequestToken(req: GuardRequest, url: URL): string;
|
|
68
|
+
export function authorizeMissionControlRequest(
|
|
69
|
+
req: GuardRequest,
|
|
70
|
+
url: URL,
|
|
71
|
+
opts: { tokenRequired: boolean; expectedToken: string | null },
|
|
72
|
+
): { ok: true; viaQuery: boolean } | { ok: false; status: number; error: string };
|
|
73
|
+
export function broadcastAuthCookieHeader(token: string): string;
|
|
74
|
+
export function listLanIPv4Addresses(): string[];
|
|
75
|
+
export function truncateStr(value: unknown, maxLen: number): unknown;
|
|
76
|
+
export function parseGitStatusShort(output: unknown): {
|
|
77
|
+
files: GitStatusFile[];
|
|
78
|
+
total: number;
|
|
79
|
+
truncated: boolean;
|
|
80
|
+
};
|
|
81
|
+
export function isLoopbackAddress(addr: string | undefined | null): boolean;
|
|
3
82
|
export function resolveContextConfigPath(
|
|
4
83
|
repoRoot: string,
|
|
5
84
|
fsHooks?: {
|
|
@@ -8,3 +87,27 @@ export function resolveContextConfigPath(
|
|
|
8
87
|
mkdirSync?: (path: string, opts?: { recursive?: boolean }) => void;
|
|
9
88
|
},
|
|
10
89
|
): { ok: true; path: string } | { ok: false; error: string };
|
|
90
|
+
export function validateConfigWriteBody(
|
|
91
|
+
body: unknown,
|
|
92
|
+
): { ok: true; patch: Record<string, unknown> } | { ok: false; error: string };
|
|
93
|
+
export function mergeConfigAllowlist(
|
|
94
|
+
existing: Record<string, unknown> | object,
|
|
95
|
+
patch: Record<string, unknown> | object,
|
|
96
|
+
): Record<string, unknown>;
|
|
97
|
+
export function allowlistConfig(raw: unknown): Record<string, unknown>;
|
|
98
|
+
export function isAllowedOrigin(origin: unknown, port: unknown): boolean;
|
|
99
|
+
export function applyCorsHeaders(
|
|
100
|
+
req: GuardRequest,
|
|
101
|
+
res: { setHeader: (name: string, value: string) => unknown },
|
|
102
|
+
port: unknown,
|
|
103
|
+
): boolean;
|
|
104
|
+
export function isUnderDashboard(resolvedPath: string, dashboardReal: string): boolean;
|
|
105
|
+
export function resolveDashboardStatic(
|
|
106
|
+
pathname: string,
|
|
107
|
+
hooks: {
|
|
108
|
+
dashboardDir: string;
|
|
109
|
+
dashboardReal: string;
|
|
110
|
+
existsSync: (path: string) => boolean;
|
|
111
|
+
realpathSync: (path: string) => string;
|
|
112
|
+
},
|
|
113
|
+
): string | null;
|
|
@@ -201,16 +201,11 @@ export function openBrowser(url, options = {}) {
|
|
|
201
201
|
|
|
202
202
|
/**
|
|
203
203
|
* Preferred open: detect failure before claiming success, then caller may fall back.
|
|
204
|
-
* Hermetic tests that only inject spawnFn use the detached path (throw = fail).
|
|
205
204
|
*
|
|
206
205
|
* @param {{ command: string, args: string[] }} built
|
|
207
206
|
* @returns {{ opened: boolean, reason?: string, command: string, args: string[] }}
|
|
208
207
|
*/
|
|
209
208
|
function runPreferred(built) {
|
|
210
|
-
if (options.spawnFn && !spawnSyncFn) {
|
|
211
|
-
return runDetached(built);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
209
|
const sync = spawnSyncFn ?? spawnSync;
|
|
215
210
|
|
|
216
211
|
if (platform !== "darwin" && platform !== "win32") {
|
|
@@ -3064,10 +3064,16 @@ export function enrichPlans(plans, handoff) {
|
|
|
3064
3064
|
path: plan.path,
|
|
3065
3065
|
overview: truncateStr(plan.overview || "", MAX_SEMANTIC_LABEL),
|
|
3066
3066
|
modifiedAt: plan.modifiedAt || null,
|
|
3067
|
+
// Counter SoT for plan progress (todoStats / TERMINAL_TODO_STATUSES).
|
|
3068
|
+
// `terminal` (completed + cancelled) is the fill numerator so the bar can
|
|
3069
|
+
// reach 100% exactly when classifyPlan says completed (open === 0).
|
|
3070
|
+
// Label format is mirrored by mergePlansForUi in dashboard/dashboard.html.
|
|
3067
3071
|
progress: {
|
|
3068
3072
|
completed: stats.completed,
|
|
3073
|
+
cancelled: stats.cancelled,
|
|
3074
|
+
terminal: stats.completed + stats.cancelled,
|
|
3069
3075
|
total: stats.total,
|
|
3070
|
-
label: `${stats.completed} of ${stats.total}`,
|
|
3076
|
+
label: `${stats.completed} of ${stats.total} complete${stats.cancelled > 0 ? ` · ${stats.cancelled} cancelled` : ""}`,
|
|
3071
3077
|
},
|
|
3072
3078
|
lifecycle: classifyPlan(plan, handoff),
|
|
3073
3079
|
// Preserved when lifecycle is completed so UI/sort can still know provenance.
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { execFileSync, execSync, spawn } from "node:child_process";
|
|
11
|
-
import { existsSync, openSync } from "node:fs";
|
|
11
|
+
import { existsSync, openSync, realpathSync } from "node:fs";
|
|
12
12
|
import { basename, dirname, join } from "node:path";
|
|
13
13
|
import { fileURLToPath } from "node:url";
|
|
14
14
|
import {
|
|
@@ -241,7 +241,7 @@ async function main() {
|
|
|
241
241
|
return;
|
|
242
242
|
}
|
|
243
243
|
let configValue = null;
|
|
244
|
-
const cfg = resolveContextConfigPath(ROOT, { existsSync });
|
|
244
|
+
const cfg = resolveContextConfigPath(ROOT, { existsSync, realpathSync });
|
|
245
245
|
if (cfg.ok) {
|
|
246
246
|
configValue = readPreferredBrowserFromConfig(cfg.path);
|
|
247
247
|
}
|
package/dashboard/start.mjs
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
import { execFileSync, execSync, spawn } from "node:child_process";
|
|
20
|
-
import { existsSync, openSync } from "node:fs";
|
|
20
|
+
import { existsSync, openSync, realpathSync } from "node:fs";
|
|
21
21
|
import { basename, dirname, join, resolve } from "node:path";
|
|
22
22
|
import { fileURLToPath } from "node:url";
|
|
23
23
|
import {
|
|
@@ -257,7 +257,7 @@ async function main() {
|
|
|
257
257
|
return;
|
|
258
258
|
}
|
|
259
259
|
let configValue = null;
|
|
260
|
-
const cfg = resolveContextConfigPath(ROOT, { existsSync });
|
|
260
|
+
const cfg = resolveContextConfigPath(ROOT, { existsSync, realpathSync });
|
|
261
261
|
if (cfg.ok) {
|
|
262
262
|
configValue = readPreferredBrowserFromConfig(cfg.path);
|
|
263
263
|
}
|
package/dist/index.js
CHANGED
|
@@ -194,7 +194,9 @@ var KNOWN_SHIPPED_OVERLAY_HASHES = /* @__PURE__ */ new Set([
|
|
|
194
194
|
"f981764422d468567b5aff31148dc659aeee22cb13dc0ecd873d737deaf07372",
|
|
195
195
|
"fa306a0cdb0f40c817e32164cd03b946564a02b4b7f7c3f4f0b9513096584d28",
|
|
196
196
|
"fa5cf460eb314437081f7cea30dc8041c0bd6fc3f560a74bc2d7be1bf07384b0",
|
|
197
|
-
"fc39ec6d8a22498697f968ffc0fe5f717bed97afd68f922c76a80ed1f10d6579"
|
|
197
|
+
"fc39ec6d8a22498697f968ffc0fe5f717bed97afd68f922c76a80ed1f10d6579",
|
|
198
|
+
"a8756742197c3a2e1a6d64f4bde2a88db48abb66a0f449d625cd2e1c7b8d0cb4",
|
|
199
|
+
"6a5f8795a4a26b419f167e249576b798781c95308dc1ea50958351de713231d4"
|
|
198
200
|
]);
|
|
199
201
|
|
|
200
202
|
// src/lifecycle/paths.ts
|
|
@@ -588,18 +590,36 @@ async function readLockOwner(lockDir) {
|
|
|
588
590
|
}
|
|
589
591
|
async function writeLockOwner(lockDir, owner) {
|
|
590
592
|
const finalPath = lockOwnerPath(lockDir);
|
|
591
|
-
const tmpPath = path5.join(lockDir, `owner.${owner.uuid}.tmp`);
|
|
593
|
+
const tmpPath = path5.join(lockDir, `owner.${owner.uuid}.${randomUUID()}.tmp`);
|
|
592
594
|
await writeFile3(tmpPath, JSON.stringify(owner), "utf8");
|
|
593
595
|
await rename(tmpPath, finalPath);
|
|
594
596
|
}
|
|
595
597
|
async function refreshLockOwner(lockDir, uuid) {
|
|
596
598
|
const owner = await readLockOwner(lockDir);
|
|
597
|
-
if (
|
|
598
|
-
await writeLockOwner(lockDir, {
|
|
599
|
+
if (owner && owner.uuid !== uuid) return;
|
|
600
|
+
await writeLockOwner(lockDir, { pid: process.pid, uuid, updatedAt: Date.now() });
|
|
599
601
|
}
|
|
600
602
|
async function releaseCacheLock(lockDir, uuid) {
|
|
601
|
-
const
|
|
602
|
-
|
|
603
|
+
const claimPath = path5.join(lockDir, `releasing.${uuid}`);
|
|
604
|
+
try {
|
|
605
|
+
await rename(lockOwnerPath(lockDir), claimPath);
|
|
606
|
+
} catch {
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
let claimed = null;
|
|
610
|
+
try {
|
|
611
|
+
const raw = await readFile4(claimPath, "utf8");
|
|
612
|
+
const parsed = JSON.parse(raw);
|
|
613
|
+
if (typeof parsed.pid === "number" && typeof parsed.uuid === "string") {
|
|
614
|
+
claimed = parsed;
|
|
615
|
+
}
|
|
616
|
+
} catch {
|
|
617
|
+
}
|
|
618
|
+
if (!claimed || claimed.uuid !== uuid) {
|
|
619
|
+
try {
|
|
620
|
+
await rename(claimPath, lockOwnerPath(lockDir));
|
|
621
|
+
} catch {
|
|
622
|
+
}
|
|
603
623
|
return;
|
|
604
624
|
}
|
|
605
625
|
try {
|
|
@@ -638,12 +658,18 @@ async function acquireCacheLock(cacheDir) {
|
|
|
638
658
|
const uuid = randomUUID();
|
|
639
659
|
const owner = { pid: process.pid, uuid, updatedAt: Date.now() };
|
|
640
660
|
await mkdir4(path5.dirname(lockDir), { recursive: true });
|
|
661
|
+
let lastErrorCode;
|
|
641
662
|
while (Date.now() < deadline) {
|
|
642
663
|
try {
|
|
643
664
|
await mkdir4(lockDir, { recursive: false });
|
|
644
665
|
await writeLockOwner(lockDir, owner);
|
|
666
|
+
let refreshing = false;
|
|
645
667
|
const refreshInterval = setInterval(() => {
|
|
668
|
+
if (refreshing) return;
|
|
669
|
+
refreshing = true;
|
|
646
670
|
refreshLockOwner(lockDir, uuid).catch(() => {
|
|
671
|
+
}).finally(() => {
|
|
672
|
+
refreshing = false;
|
|
647
673
|
});
|
|
648
674
|
}, LOCK_REFRESH_MS);
|
|
649
675
|
return async () => {
|
|
@@ -652,6 +678,7 @@ async function acquireCacheLock(cacheDir) {
|
|
|
652
678
|
};
|
|
653
679
|
} catch (err) {
|
|
654
680
|
const code = err.code;
|
|
681
|
+
lastErrorCode = code;
|
|
655
682
|
if (code === "EEXIST") {
|
|
656
683
|
if (await tryReclaimStaleLock(lockDir)) {
|
|
657
684
|
continue;
|
|
@@ -660,12 +687,16 @@ async function acquireCacheLock(cacheDir) {
|
|
|
660
687
|
continue;
|
|
661
688
|
}
|
|
662
689
|
if (code === "ENOENT") {
|
|
690
|
+
await mkdir4(path5.dirname(lockDir), { recursive: true });
|
|
691
|
+
await new Promise((r) => setTimeout(r, LOCK_RETRY_MS + Math.random() * 100));
|
|
663
692
|
continue;
|
|
664
693
|
}
|
|
665
694
|
throw err;
|
|
666
695
|
}
|
|
667
696
|
}
|
|
668
|
-
throw new Error(
|
|
697
|
+
throw new Error(
|
|
698
|
+
lastErrorCode === "ENOENT" ? `Timed out acquiring cache lock on ${cacheDir}: the lock parent directory kept vanishing (concurrent cache clear?).` : `Timed out waiting for cache lock on ${cacheDir}. Another install may be stuck.`
|
|
699
|
+
);
|
|
669
700
|
}
|
|
670
701
|
var DEFAULT_REGISTRY_URL = "https://github.com/agent-kit-startup/agent-kit";
|
|
671
702
|
var DEFAULT_REGISTRY_REF = "main";
|
|
@@ -1851,6 +1882,7 @@ async function resolveInventoryRoot(cwd) {
|
|
|
1851
1882
|
return dir;
|
|
1852
1883
|
} catch {
|
|
1853
1884
|
}
|
|
1885
|
+
if (await fileExists(path10.join(dir, ".git"))) break;
|
|
1854
1886
|
const parent = path10.dirname(dir);
|
|
1855
1887
|
if (parent === dir) break;
|
|
1856
1888
|
dir = parent;
|
|
@@ -2027,12 +2059,16 @@ function baseResult(partial) {
|
|
|
2027
2059
|
};
|
|
2028
2060
|
}
|
|
2029
2061
|
async function checkCursorUpdateAwareness(cwd, options = {}) {
|
|
2030
|
-
const
|
|
2062
|
+
const inventoryRoot = await resolveInventoryRoot(cwd);
|
|
2063
|
+
const prefs = readCursorUpdateCheckPrefs(
|
|
2064
|
+
inventoryRoot ? await loadContextConfig(inventoryRoot) : null
|
|
2065
|
+
);
|
|
2031
2066
|
const changelogUrl = options.changelogUrl ?? prefs.changelogUrl;
|
|
2032
2067
|
if (options.respectPrefs) {
|
|
2033
2068
|
if (!prefs.enabled) {
|
|
2034
2069
|
return baseResult({
|
|
2035
2070
|
status: "skipped-disabled",
|
|
2071
|
+
inventoryRoot,
|
|
2036
2072
|
inventoryPath: INVENTORY_REL,
|
|
2037
2073
|
featuresPath: FEATURES_REL,
|
|
2038
2074
|
changelogUrl,
|
|
@@ -2047,6 +2083,7 @@ async function checkCursorUpdateAwareness(cwd, options = {}) {
|
|
|
2047
2083
|
if (!intervalElapsed(prefs.lastCheckedAt, prefs.intervalDays)) {
|
|
2048
2084
|
return baseResult({
|
|
2049
2085
|
status: "skipped-interval",
|
|
2086
|
+
inventoryRoot,
|
|
2050
2087
|
inventoryPath: INVENTORY_REL,
|
|
2051
2088
|
featuresPath: FEATURES_REL,
|
|
2052
2089
|
changelogUrl,
|
|
@@ -2059,10 +2096,10 @@ async function checkCursorUpdateAwareness(cwd, options = {}) {
|
|
|
2059
2096
|
});
|
|
2060
2097
|
}
|
|
2061
2098
|
}
|
|
2062
|
-
const inventoryRoot = await resolveInventoryRoot(cwd);
|
|
2063
2099
|
if (!inventoryRoot) {
|
|
2064
2100
|
return baseResult({
|
|
2065
2101
|
status: "error",
|
|
2102
|
+
inventoryRoot: null,
|
|
2066
2103
|
inventoryPath: INVENTORY_REL,
|
|
2067
2104
|
featuresPath: FEATURES_REL,
|
|
2068
2105
|
changelogUrl,
|
|
@@ -2082,6 +2119,7 @@ async function checkCursorUpdateAwareness(cwd, options = {}) {
|
|
|
2082
2119
|
} catch {
|
|
2083
2120
|
return baseResult({
|
|
2084
2121
|
status: "error",
|
|
2122
|
+
inventoryRoot,
|
|
2085
2123
|
inventoryPath: INVENTORY_REL,
|
|
2086
2124
|
featuresPath: FEATURES_REL,
|
|
2087
2125
|
changelogUrl,
|
|
@@ -2166,6 +2204,7 @@ async function checkCursorUpdateAwareness(cwd, options = {}) {
|
|
|
2166
2204
|
const msg = err instanceof Error ? err.message : String(err);
|
|
2167
2205
|
return baseResult({
|
|
2168
2206
|
status: "error",
|
|
2207
|
+
inventoryRoot,
|
|
2169
2208
|
inventoryPath: INVENTORY_REL,
|
|
2170
2209
|
featuresPath: FEATURES_REL,
|
|
2171
2210
|
changelogUrl,
|
|
@@ -2178,8 +2217,8 @@ async function checkCursorUpdateAwareness(cwd, options = {}) {
|
|
|
2178
2217
|
});
|
|
2179
2218
|
}
|
|
2180
2219
|
}
|
|
2181
|
-
if (options.stamp) {
|
|
2182
|
-
await stampCursorUpdateCheck(
|
|
2220
|
+
if (options.stamp && inventoryRoot) {
|
|
2221
|
+
await stampCursorUpdateCheck(inventoryRoot, {
|
|
2183
2222
|
lastSeenCursorVersion: latestCursorVersion ?? prefs.lastSeenCursorVersion
|
|
2184
2223
|
});
|
|
2185
2224
|
}
|
|
@@ -2187,6 +2226,7 @@ async function checkCursorUpdateAwareness(cwd, options = {}) {
|
|
|
2187
2226
|
const message = status === "current" ? "No advisory Cursor-update gaps vs inventory (check-only)." : `Found ${gaps.length} advisory gap(s). ${CONVEYOR_HINT}`;
|
|
2188
2227
|
return baseResult({
|
|
2189
2228
|
status,
|
|
2229
|
+
inventoryRoot,
|
|
2190
2230
|
inventoryPath: INVENTORY_REL,
|
|
2191
2231
|
featuresPath: FEATURES_REL,
|
|
2192
2232
|
changelogUrl: options.offline ? null : changelogUrl,
|
|
@@ -5008,11 +5048,19 @@ async function fileExists2(p) {
|
|
|
5008
5048
|
return false;
|
|
5009
5049
|
}
|
|
5010
5050
|
}
|
|
5051
|
+
function isMarkdownTableSeparator(line) {
|
|
5052
|
+
const stripped = line.trim();
|
|
5053
|
+
if (!stripped.startsWith("|")) return false;
|
|
5054
|
+
const parts = stripped.replace(/^\|/, "").replace(/\|$/, "").split("|").map((cell) => cell.trim());
|
|
5055
|
+
return parts.length > 0 && parts.every((cell) => /^:?-+:?$/.test(cell));
|
|
5056
|
+
}
|
|
5011
5057
|
function parseUnprocessedDogfoodItems(readmeText) {
|
|
5012
5058
|
const items = [];
|
|
5013
5059
|
let inSection = false;
|
|
5014
5060
|
let sectionLevel = 0;
|
|
5015
|
-
|
|
5061
|
+
const lines = readmeText.split(/\r?\n/);
|
|
5062
|
+
for (let i = 0; i < lines.length; i++) {
|
|
5063
|
+
const line = lines[i] ?? "";
|
|
5016
5064
|
const unprocessedMatch = /^(#{2,3})\s+Unprocessed Files\b/.exec(line);
|
|
5017
5065
|
if (unprocessedMatch) {
|
|
5018
5066
|
const hashes = unprocessedMatch[1];
|
|
@@ -5024,11 +5072,14 @@ function parseUnprocessedDogfoodItems(readmeText) {
|
|
|
5024
5072
|
if (!inSection) continue;
|
|
5025
5073
|
const headingMatch = /^(#{1,6})\s+/.exec(line);
|
|
5026
5074
|
if (headingMatch) {
|
|
5027
|
-
if (
|
|
5075
|
+
if (/^#{1,6}\s+Processed(?:\s+Files)?\b/.test(line)) break;
|
|
5028
5076
|
const hashes = headingMatch[1];
|
|
5029
5077
|
if (hashes && hashes.length <= sectionLevel) break;
|
|
5030
5078
|
continue;
|
|
5031
5079
|
}
|
|
5080
|
+
if (line.trim().startsWith("|") && isMarkdownTableSeparator(lines[i + 1] ?? "")) {
|
|
5081
|
+
continue;
|
|
5082
|
+
}
|
|
5032
5083
|
const body = extractUnprocessedDogfoodLine(line);
|
|
5033
5084
|
if (!body) continue;
|
|
5034
5085
|
items.push(body);
|
|
@@ -5046,13 +5097,10 @@ function extractUnprocessedDogfoodLine(line) {
|
|
|
5046
5097
|
if (numbered?.[2]) {
|
|
5047
5098
|
raw = numbered[2].trim();
|
|
5048
5099
|
} else if (stripped.startsWith("|")) {
|
|
5100
|
+
if (isMarkdownTableSeparator(stripped)) return null;
|
|
5049
5101
|
const parts = stripped.replace(/^\|/, "").replace(/\|$/, "").split("|").map((cell) => cell.trim());
|
|
5050
5102
|
if (parts.length === 0) return null;
|
|
5051
|
-
|
|
5052
|
-
const first = parts[0] ?? "";
|
|
5053
|
-
const headerish = first.toLowerCase().replace(/[*_`]/g, "").trim();
|
|
5054
|
-
if (/^(note|file|entrada|title|name|item|path)$/.test(headerish)) return null;
|
|
5055
|
-
raw = first.trim();
|
|
5103
|
+
raw = (parts[0] ?? "").trim();
|
|
5056
5104
|
}
|
|
5057
5105
|
}
|
|
5058
5106
|
if (!raw) return null;
|
|
@@ -5818,7 +5866,40 @@ function buildPersonalizationPlan(profile, report, registry) {
|
|
|
5818
5866
|
(item) => componentAvailable(registry, item) ? item : { ...item, status: "unavailable" }
|
|
5819
5867
|
).sort((left, right) => `${left.kind}:${left.id}`.localeCompare(`${right.kind}:${right.id}`));
|
|
5820
5868
|
}
|
|
5821
|
-
|
|
5869
|
+
var INSTALLED_SKILL_STATUSES = /* @__PURE__ */ new Set(["applied", "skipped-customized"]);
|
|
5870
|
+
function installedSkillItems(items, installedIds = []) {
|
|
5871
|
+
const rows = [];
|
|
5872
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5873
|
+
for (const item of items) {
|
|
5874
|
+
if (item.kind !== "skill" || !INSTALLED_SKILL_STATUSES.has(item.status)) continue;
|
|
5875
|
+
if (seen.has(item.id)) continue;
|
|
5876
|
+
seen.add(item.id);
|
|
5877
|
+
rows.push(item);
|
|
5878
|
+
}
|
|
5879
|
+
for (const id of installedIds) {
|
|
5880
|
+
if (seen.has(id)) continue;
|
|
5881
|
+
seen.add(id);
|
|
5882
|
+
rows.push({
|
|
5883
|
+
kind: "skill",
|
|
5884
|
+
id,
|
|
5885
|
+
status: "applied",
|
|
5886
|
+
evidence: [{ source: "configuration", value: `.cursor/agent-kit.json skills[]:${id}` }]
|
|
5887
|
+
});
|
|
5888
|
+
}
|
|
5889
|
+
return rows.sort((left, right) => left.id.localeCompare(right.id));
|
|
5890
|
+
}
|
|
5891
|
+
function relevantSkillTableRows(skills) {
|
|
5892
|
+
if (skills.length === 0) {
|
|
5893
|
+
return ["| (none yet) | No installed or project-owned skills detected | \u2014 |"];
|
|
5894
|
+
}
|
|
5895
|
+
return skills.map((skill) => {
|
|
5896
|
+
const label = skill.path ?? skill.id;
|
|
5897
|
+
const role = skill.status === "skipped-customized" ? "Already present (customized)" : "Installed by personalization";
|
|
5898
|
+
const evidence = skill.path ?? skill.evidence[0]?.value ?? "personalization";
|
|
5899
|
+
return `| ${label} | ${role} | ${evidence} |`;
|
|
5900
|
+
});
|
|
5901
|
+
}
|
|
5902
|
+
function renderProjectContext(profile, skillItems = []) {
|
|
5822
5903
|
const sections = ["# Project Context", "", "Verified repository facts:"];
|
|
5823
5904
|
const purpose = purposeEvidence(profile);
|
|
5824
5905
|
if (purpose.length > 0 && profile.purpose.value !== "unknown") {
|
|
@@ -5845,7 +5926,7 @@ function renderProjectContext(profile) {
|
|
|
5845
5926
|
"",
|
|
5846
5927
|
"| Skill / path | Role | Evidence |",
|
|
5847
5928
|
"|--------------|------|----------|",
|
|
5848
|
-
|
|
5929
|
+
...relevantSkillTableRows(installedSkillItems(skillItems))
|
|
5849
5930
|
);
|
|
5850
5931
|
if (profile.context.sources.length > 0) {
|
|
5851
5932
|
sections.push("", "## Sources", ...profile.context.sources.map((item) => `- ${item.value}`));
|
|
@@ -5946,7 +6027,7 @@ async function applyPersonalization(input) {
|
|
|
5946
6027
|
createOwnedFile(
|
|
5947
6028
|
input.rootDir,
|
|
5948
6029
|
CONTEXT_PATH,
|
|
5949
|
-
renderProjectContext(input.profile),
|
|
6030
|
+
renderProjectContext(input.profile, installedSkillItems(componentResults, skills)),
|
|
5950
6031
|
profileEvidence
|
|
5951
6032
|
),
|
|
5952
6033
|
createOwnedFile(
|
|
@@ -6269,7 +6350,8 @@ var installCommand = defineCommand11({
|
|
|
6269
6350
|
console.error(`
|
|
6270
6351
|
${hint.recovery}
|
|
6271
6352
|
`);
|
|
6272
|
-
process.
|
|
6353
|
+
process.exitCode = 1;
|
|
6354
|
+
return;
|
|
6273
6355
|
}
|
|
6274
6356
|
}
|
|
6275
6357
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dadado/agent-kit-cli",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.2.0",
|
|
4
4
|
"description": "Agent Kit CLI: HITL framework install and tooling for AI-assisted IDEs (rules, skills, plan/handoff, context).",
|
|
5
5
|
"license": "PolyForm-Noncommercial-1.0.0",
|
|
6
6
|
"type": "module",
|
|
@@ -32,6 +32,8 @@
|
|
|
32
32
|
"dev": "tsx src/index.ts",
|
|
33
33
|
"start": "tsx src/index.ts",
|
|
34
34
|
"lint": "biome check src",
|
|
35
|
+
"overlay:hashes": "tsx src/lifecycle/refresh-known-hashes.ts",
|
|
36
|
+
"overlay:hashes:check": "tsx src/lifecycle/refresh-known-hashes.ts --check",
|
|
35
37
|
"test": "vitest run",
|
|
36
38
|
"typecheck": "tsc --noEmit"
|
|
37
39
|
}
|