@dadado/agent-kit-cli 5.1.0 → 5.2.1
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/logo-marketplace.svg +13 -0
- package/dashboard/start-broadcast.mjs +2 -2
- package/dashboard/start.mjs +2 -2
- package/dist/index.js +106 -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.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512" fill="none">
|
|
2
|
+
<!-- Marketplace 1:1 logotype. Plate fill is Mission Control bg-primary #0b0e14. Artwork is the Cursor-skin stroke helmet from logo-cursor.svg, centered with ~10% padding. Chrome marks stay in logo.svg and logo-cursor.svg. -->
|
|
3
|
+
<rect width="512" height="512" rx="96" fill="#0b0e14"/>
|
|
4
|
+
<g transform="translate(51.839 51.2) scale(18.244989)">
|
|
5
|
+
<path d="M9.61,22.1c-4.45-1.22-7.48-4.71-8.17-9.63v-2.47c0-5.39,4.37-9.75,9.75-9.75h0c5.39,0,9.75,4.37,9.75,9.75,0,.62.04,1.85,0,2.47-.34,4.86-3.54,8.41-8.2,9.63-.49.13-2.63.13-3.13,0Z" stroke="#e4e4e4" stroke-width=".5" stroke-miterlimit="10" fill="none"/>
|
|
6
|
+
<path d="M18.42,9.54c-.38,3.74-1.81,8.07-7.22,8.07s-6.66-4.49-7.22-8.07v-.19c0-3.47,3.23-6.28,7.22-6.28s7.22,2.81,7.22,6.28v.19Z" stroke="#e4e4e4" stroke-width=".5" stroke-miterlimit="10" fill="none"/>
|
|
7
|
+
<path d="M13.59,6.08c1.18.76,1.99,1.85,2.2,3.16.07.43.07.86,0,1.28s-.19.84-.37,1.25" stroke="#e4e4e4" stroke-width=".5" stroke-linecap="round" fill="none"/>
|
|
8
|
+
<g>
|
|
9
|
+
<line x1=".25" y1="7.9" x2=".25" y2="13.68" stroke="#e4e4e4" stroke-width=".5" stroke-linecap="round"/>
|
|
10
|
+
<line x1="22.13" y1="7.9" x2="22.13" y2="13.68" stroke="#e4e4e4" stroke-width=".5" stroke-linecap="round"/>
|
|
11
|
+
</g>
|
|
12
|
+
</g>
|
|
13
|
+
</svg>
|
|
@@ -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,11 @@ 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",
|
|
200
|
+
"4009c5e2faf9775d5708cdff0b5f9da80fd3ce390cc42aed0bb94272f6b05b1b",
|
|
201
|
+
"a8e070fae187908ef7b2cf2a41605e3079329403b1802b5fc67e83294c96a070"
|
|
198
202
|
]);
|
|
199
203
|
|
|
200
204
|
// src/lifecycle/paths.ts
|
|
@@ -588,18 +592,36 @@ async function readLockOwner(lockDir) {
|
|
|
588
592
|
}
|
|
589
593
|
async function writeLockOwner(lockDir, owner) {
|
|
590
594
|
const finalPath = lockOwnerPath(lockDir);
|
|
591
|
-
const tmpPath = path5.join(lockDir, `owner.${owner.uuid}.tmp`);
|
|
595
|
+
const tmpPath = path5.join(lockDir, `owner.${owner.uuid}.${randomUUID()}.tmp`);
|
|
592
596
|
await writeFile3(tmpPath, JSON.stringify(owner), "utf8");
|
|
593
597
|
await rename(tmpPath, finalPath);
|
|
594
598
|
}
|
|
595
599
|
async function refreshLockOwner(lockDir, uuid) {
|
|
596
600
|
const owner = await readLockOwner(lockDir);
|
|
597
|
-
if (
|
|
598
|
-
await writeLockOwner(lockDir, {
|
|
601
|
+
if (owner && owner.uuid !== uuid) return;
|
|
602
|
+
await writeLockOwner(lockDir, { pid: process.pid, uuid, updatedAt: Date.now() });
|
|
599
603
|
}
|
|
600
604
|
async function releaseCacheLock(lockDir, uuid) {
|
|
601
|
-
const
|
|
602
|
-
|
|
605
|
+
const claimPath = path5.join(lockDir, `releasing.${uuid}`);
|
|
606
|
+
try {
|
|
607
|
+
await rename(lockOwnerPath(lockDir), claimPath);
|
|
608
|
+
} catch {
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
let claimed = null;
|
|
612
|
+
try {
|
|
613
|
+
const raw = await readFile4(claimPath, "utf8");
|
|
614
|
+
const parsed = JSON.parse(raw);
|
|
615
|
+
if (typeof parsed.pid === "number" && typeof parsed.uuid === "string") {
|
|
616
|
+
claimed = parsed;
|
|
617
|
+
}
|
|
618
|
+
} catch {
|
|
619
|
+
}
|
|
620
|
+
if (!claimed || claimed.uuid !== uuid) {
|
|
621
|
+
try {
|
|
622
|
+
await rename(claimPath, lockOwnerPath(lockDir));
|
|
623
|
+
} catch {
|
|
624
|
+
}
|
|
603
625
|
return;
|
|
604
626
|
}
|
|
605
627
|
try {
|
|
@@ -638,12 +660,18 @@ async function acquireCacheLock(cacheDir) {
|
|
|
638
660
|
const uuid = randomUUID();
|
|
639
661
|
const owner = { pid: process.pid, uuid, updatedAt: Date.now() };
|
|
640
662
|
await mkdir4(path5.dirname(lockDir), { recursive: true });
|
|
663
|
+
let lastErrorCode;
|
|
641
664
|
while (Date.now() < deadline) {
|
|
642
665
|
try {
|
|
643
666
|
await mkdir4(lockDir, { recursive: false });
|
|
644
667
|
await writeLockOwner(lockDir, owner);
|
|
668
|
+
let refreshing = false;
|
|
645
669
|
const refreshInterval = setInterval(() => {
|
|
670
|
+
if (refreshing) return;
|
|
671
|
+
refreshing = true;
|
|
646
672
|
refreshLockOwner(lockDir, uuid).catch(() => {
|
|
673
|
+
}).finally(() => {
|
|
674
|
+
refreshing = false;
|
|
647
675
|
});
|
|
648
676
|
}, LOCK_REFRESH_MS);
|
|
649
677
|
return async () => {
|
|
@@ -652,6 +680,7 @@ async function acquireCacheLock(cacheDir) {
|
|
|
652
680
|
};
|
|
653
681
|
} catch (err) {
|
|
654
682
|
const code = err.code;
|
|
683
|
+
lastErrorCode = code;
|
|
655
684
|
if (code === "EEXIST") {
|
|
656
685
|
if (await tryReclaimStaleLock(lockDir)) {
|
|
657
686
|
continue;
|
|
@@ -660,12 +689,16 @@ async function acquireCacheLock(cacheDir) {
|
|
|
660
689
|
continue;
|
|
661
690
|
}
|
|
662
691
|
if (code === "ENOENT") {
|
|
692
|
+
await mkdir4(path5.dirname(lockDir), { recursive: true });
|
|
693
|
+
await new Promise((r) => setTimeout(r, LOCK_RETRY_MS + Math.random() * 100));
|
|
663
694
|
continue;
|
|
664
695
|
}
|
|
665
696
|
throw err;
|
|
666
697
|
}
|
|
667
698
|
}
|
|
668
|
-
throw new Error(
|
|
699
|
+
throw new Error(
|
|
700
|
+
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.`
|
|
701
|
+
);
|
|
669
702
|
}
|
|
670
703
|
var DEFAULT_REGISTRY_URL = "https://github.com/agent-kit-startup/agent-kit";
|
|
671
704
|
var DEFAULT_REGISTRY_REF = "main";
|
|
@@ -1851,6 +1884,7 @@ async function resolveInventoryRoot(cwd) {
|
|
|
1851
1884
|
return dir;
|
|
1852
1885
|
} catch {
|
|
1853
1886
|
}
|
|
1887
|
+
if (await fileExists(path10.join(dir, ".git"))) break;
|
|
1854
1888
|
const parent = path10.dirname(dir);
|
|
1855
1889
|
if (parent === dir) break;
|
|
1856
1890
|
dir = parent;
|
|
@@ -2027,12 +2061,16 @@ function baseResult(partial) {
|
|
|
2027
2061
|
};
|
|
2028
2062
|
}
|
|
2029
2063
|
async function checkCursorUpdateAwareness(cwd, options = {}) {
|
|
2030
|
-
const
|
|
2064
|
+
const inventoryRoot = await resolveInventoryRoot(cwd);
|
|
2065
|
+
const prefs = readCursorUpdateCheckPrefs(
|
|
2066
|
+
inventoryRoot ? await loadContextConfig(inventoryRoot) : null
|
|
2067
|
+
);
|
|
2031
2068
|
const changelogUrl = options.changelogUrl ?? prefs.changelogUrl;
|
|
2032
2069
|
if (options.respectPrefs) {
|
|
2033
2070
|
if (!prefs.enabled) {
|
|
2034
2071
|
return baseResult({
|
|
2035
2072
|
status: "skipped-disabled",
|
|
2073
|
+
inventoryRoot,
|
|
2036
2074
|
inventoryPath: INVENTORY_REL,
|
|
2037
2075
|
featuresPath: FEATURES_REL,
|
|
2038
2076
|
changelogUrl,
|
|
@@ -2047,6 +2085,7 @@ async function checkCursorUpdateAwareness(cwd, options = {}) {
|
|
|
2047
2085
|
if (!intervalElapsed(prefs.lastCheckedAt, prefs.intervalDays)) {
|
|
2048
2086
|
return baseResult({
|
|
2049
2087
|
status: "skipped-interval",
|
|
2088
|
+
inventoryRoot,
|
|
2050
2089
|
inventoryPath: INVENTORY_REL,
|
|
2051
2090
|
featuresPath: FEATURES_REL,
|
|
2052
2091
|
changelogUrl,
|
|
@@ -2059,10 +2098,10 @@ async function checkCursorUpdateAwareness(cwd, options = {}) {
|
|
|
2059
2098
|
});
|
|
2060
2099
|
}
|
|
2061
2100
|
}
|
|
2062
|
-
const inventoryRoot = await resolveInventoryRoot(cwd);
|
|
2063
2101
|
if (!inventoryRoot) {
|
|
2064
2102
|
return baseResult({
|
|
2065
2103
|
status: "error",
|
|
2104
|
+
inventoryRoot: null,
|
|
2066
2105
|
inventoryPath: INVENTORY_REL,
|
|
2067
2106
|
featuresPath: FEATURES_REL,
|
|
2068
2107
|
changelogUrl,
|
|
@@ -2082,6 +2121,7 @@ async function checkCursorUpdateAwareness(cwd, options = {}) {
|
|
|
2082
2121
|
} catch {
|
|
2083
2122
|
return baseResult({
|
|
2084
2123
|
status: "error",
|
|
2124
|
+
inventoryRoot,
|
|
2085
2125
|
inventoryPath: INVENTORY_REL,
|
|
2086
2126
|
featuresPath: FEATURES_REL,
|
|
2087
2127
|
changelogUrl,
|
|
@@ -2166,6 +2206,7 @@ async function checkCursorUpdateAwareness(cwd, options = {}) {
|
|
|
2166
2206
|
const msg = err instanceof Error ? err.message : String(err);
|
|
2167
2207
|
return baseResult({
|
|
2168
2208
|
status: "error",
|
|
2209
|
+
inventoryRoot,
|
|
2169
2210
|
inventoryPath: INVENTORY_REL,
|
|
2170
2211
|
featuresPath: FEATURES_REL,
|
|
2171
2212
|
changelogUrl,
|
|
@@ -2178,8 +2219,8 @@ async function checkCursorUpdateAwareness(cwd, options = {}) {
|
|
|
2178
2219
|
});
|
|
2179
2220
|
}
|
|
2180
2221
|
}
|
|
2181
|
-
if (options.stamp) {
|
|
2182
|
-
await stampCursorUpdateCheck(
|
|
2222
|
+
if (options.stamp && inventoryRoot) {
|
|
2223
|
+
await stampCursorUpdateCheck(inventoryRoot, {
|
|
2183
2224
|
lastSeenCursorVersion: latestCursorVersion ?? prefs.lastSeenCursorVersion
|
|
2184
2225
|
});
|
|
2185
2226
|
}
|
|
@@ -2187,6 +2228,7 @@ async function checkCursorUpdateAwareness(cwd, options = {}) {
|
|
|
2187
2228
|
const message = status === "current" ? "No advisory Cursor-update gaps vs inventory (check-only)." : `Found ${gaps.length} advisory gap(s). ${CONVEYOR_HINT}`;
|
|
2188
2229
|
return baseResult({
|
|
2189
2230
|
status,
|
|
2231
|
+
inventoryRoot,
|
|
2190
2232
|
inventoryPath: INVENTORY_REL,
|
|
2191
2233
|
featuresPath: FEATURES_REL,
|
|
2192
2234
|
changelogUrl: options.offline ? null : changelogUrl,
|
|
@@ -5008,11 +5050,19 @@ async function fileExists2(p) {
|
|
|
5008
5050
|
return false;
|
|
5009
5051
|
}
|
|
5010
5052
|
}
|
|
5053
|
+
function isMarkdownTableSeparator(line) {
|
|
5054
|
+
const stripped = line.trim();
|
|
5055
|
+
if (!stripped.startsWith("|")) return false;
|
|
5056
|
+
const parts = stripped.replace(/^\|/, "").replace(/\|$/, "").split("|").map((cell) => cell.trim());
|
|
5057
|
+
return parts.length > 0 && parts.every((cell) => /^:?-+:?$/.test(cell));
|
|
5058
|
+
}
|
|
5011
5059
|
function parseUnprocessedDogfoodItems(readmeText) {
|
|
5012
5060
|
const items = [];
|
|
5013
5061
|
let inSection = false;
|
|
5014
5062
|
let sectionLevel = 0;
|
|
5015
|
-
|
|
5063
|
+
const lines = readmeText.split(/\r?\n/);
|
|
5064
|
+
for (let i = 0; i < lines.length; i++) {
|
|
5065
|
+
const line = lines[i] ?? "";
|
|
5016
5066
|
const unprocessedMatch = /^(#{2,3})\s+Unprocessed Files\b/.exec(line);
|
|
5017
5067
|
if (unprocessedMatch) {
|
|
5018
5068
|
const hashes = unprocessedMatch[1];
|
|
@@ -5024,11 +5074,14 @@ function parseUnprocessedDogfoodItems(readmeText) {
|
|
|
5024
5074
|
if (!inSection) continue;
|
|
5025
5075
|
const headingMatch = /^(#{1,6})\s+/.exec(line);
|
|
5026
5076
|
if (headingMatch) {
|
|
5027
|
-
if (
|
|
5077
|
+
if (/^#{1,6}\s+Processed(?:\s+Files)?\b/.test(line)) break;
|
|
5028
5078
|
const hashes = headingMatch[1];
|
|
5029
5079
|
if (hashes && hashes.length <= sectionLevel) break;
|
|
5030
5080
|
continue;
|
|
5031
5081
|
}
|
|
5082
|
+
if (line.trim().startsWith("|") && isMarkdownTableSeparator(lines[i + 1] ?? "")) {
|
|
5083
|
+
continue;
|
|
5084
|
+
}
|
|
5032
5085
|
const body = extractUnprocessedDogfoodLine(line);
|
|
5033
5086
|
if (!body) continue;
|
|
5034
5087
|
items.push(body);
|
|
@@ -5046,13 +5099,10 @@ function extractUnprocessedDogfoodLine(line) {
|
|
|
5046
5099
|
if (numbered?.[2]) {
|
|
5047
5100
|
raw = numbered[2].trim();
|
|
5048
5101
|
} else if (stripped.startsWith("|")) {
|
|
5102
|
+
if (isMarkdownTableSeparator(stripped)) return null;
|
|
5049
5103
|
const parts = stripped.replace(/^\|/, "").replace(/\|$/, "").split("|").map((cell) => cell.trim());
|
|
5050
5104
|
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();
|
|
5105
|
+
raw = (parts[0] ?? "").trim();
|
|
5056
5106
|
}
|
|
5057
5107
|
}
|
|
5058
5108
|
if (!raw) return null;
|
|
@@ -5818,7 +5868,40 @@ function buildPersonalizationPlan(profile, report, registry) {
|
|
|
5818
5868
|
(item) => componentAvailable(registry, item) ? item : { ...item, status: "unavailable" }
|
|
5819
5869
|
).sort((left, right) => `${left.kind}:${left.id}`.localeCompare(`${right.kind}:${right.id}`));
|
|
5820
5870
|
}
|
|
5821
|
-
|
|
5871
|
+
var INSTALLED_SKILL_STATUSES = /* @__PURE__ */ new Set(["applied", "skipped-customized"]);
|
|
5872
|
+
function installedSkillItems(items, installedIds = []) {
|
|
5873
|
+
const rows = [];
|
|
5874
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5875
|
+
for (const item of items) {
|
|
5876
|
+
if (item.kind !== "skill" || !INSTALLED_SKILL_STATUSES.has(item.status)) continue;
|
|
5877
|
+
if (seen.has(item.id)) continue;
|
|
5878
|
+
seen.add(item.id);
|
|
5879
|
+
rows.push(item);
|
|
5880
|
+
}
|
|
5881
|
+
for (const id of installedIds) {
|
|
5882
|
+
if (seen.has(id)) continue;
|
|
5883
|
+
seen.add(id);
|
|
5884
|
+
rows.push({
|
|
5885
|
+
kind: "skill",
|
|
5886
|
+
id,
|
|
5887
|
+
status: "applied",
|
|
5888
|
+
evidence: [{ source: "configuration", value: `.cursor/agent-kit.json skills[]:${id}` }]
|
|
5889
|
+
});
|
|
5890
|
+
}
|
|
5891
|
+
return rows.sort((left, right) => left.id.localeCompare(right.id));
|
|
5892
|
+
}
|
|
5893
|
+
function relevantSkillTableRows(skills) {
|
|
5894
|
+
if (skills.length === 0) {
|
|
5895
|
+
return ["| (none yet) | No installed or project-owned skills detected | \u2014 |"];
|
|
5896
|
+
}
|
|
5897
|
+
return skills.map((skill) => {
|
|
5898
|
+
const label = skill.path ?? skill.id;
|
|
5899
|
+
const role = skill.status === "skipped-customized" ? "Already present (customized)" : "Installed by personalization";
|
|
5900
|
+
const evidence = skill.path ?? skill.evidence[0]?.value ?? "personalization";
|
|
5901
|
+
return `| ${label} | ${role} | ${evidence} |`;
|
|
5902
|
+
});
|
|
5903
|
+
}
|
|
5904
|
+
function renderProjectContext(profile, skillItems = []) {
|
|
5822
5905
|
const sections = ["# Project Context", "", "Verified repository facts:"];
|
|
5823
5906
|
const purpose = purposeEvidence(profile);
|
|
5824
5907
|
if (purpose.length > 0 && profile.purpose.value !== "unknown") {
|
|
@@ -5845,7 +5928,7 @@ function renderProjectContext(profile) {
|
|
|
5845
5928
|
"",
|
|
5846
5929
|
"| Skill / path | Role | Evidence |",
|
|
5847
5930
|
"|--------------|------|----------|",
|
|
5848
|
-
|
|
5931
|
+
...relevantSkillTableRows(installedSkillItems(skillItems))
|
|
5849
5932
|
);
|
|
5850
5933
|
if (profile.context.sources.length > 0) {
|
|
5851
5934
|
sections.push("", "## Sources", ...profile.context.sources.map((item) => `- ${item.value}`));
|
|
@@ -5946,7 +6029,7 @@ async function applyPersonalization(input) {
|
|
|
5946
6029
|
createOwnedFile(
|
|
5947
6030
|
input.rootDir,
|
|
5948
6031
|
CONTEXT_PATH,
|
|
5949
|
-
renderProjectContext(input.profile),
|
|
6032
|
+
renderProjectContext(input.profile, installedSkillItems(componentResults, skills)),
|
|
5950
6033
|
profileEvidence
|
|
5951
6034
|
),
|
|
5952
6035
|
createOwnedFile(
|
|
@@ -6269,7 +6352,8 @@ var installCommand = defineCommand11({
|
|
|
6269
6352
|
console.error(`
|
|
6270
6353
|
${hint.recovery}
|
|
6271
6354
|
`);
|
|
6272
|
-
process.
|
|
6355
|
+
process.exitCode = 1;
|
|
6356
|
+
return;
|
|
6273
6357
|
}
|
|
6274
6358
|
}
|
|
6275
6359
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dadado/agent-kit-cli",
|
|
3
|
-
"version": "5.1
|
|
3
|
+
"version": "5.2.1",
|
|
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
|
}
|