arcane-cli 1.16.1 → 1.17.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/dist/index.js CHANGED
@@ -6,7 +6,7 @@ const require = __createRequire(import.meta.url);
6
6
  import { Command } from "commander";
7
7
  import { createRequire as createRequire3 } from "module";
8
8
  import { fileURLToPath } from "url";
9
- import { dirname as dirname14, join as join23 } from "path";
9
+ import { dirname as dirname14, join as join29 } from "path";
10
10
 
11
11
  // src/commands/init.ts
12
12
  import { select as select3, confirm as confirm2, input as input3 } from "@inquirer/prompts";
@@ -62,6 +62,10 @@ var WRITE_SUBCOMMANDS = /* @__PURE__ */ new Set([
62
62
  "notes"
63
63
  ]);
64
64
  var AMBIGUOUS_AS_WRITE = /* @__PURE__ */ new Set(["branch", "config", "remote", "worktree"]);
65
+ var AMBIGUOUS_READ_FORMS = {
66
+ worktree: /* @__PURE__ */ new Set(["list"]),
67
+ remote: /* @__PURE__ */ new Set(["get-url"])
68
+ };
65
69
  var READ_SUBCOMMANDS = /* @__PURE__ */ new Set([
66
70
  "status",
67
71
  "rev-parse",
@@ -97,6 +101,7 @@ var DEFAULT_TIMEOUTS_MS = {
97
101
  };
98
102
  function classifyGitCommand(args) {
99
103
  let subcommand;
104
+ let form;
100
105
  for (let i = 0; i < args.length; i++) {
101
106
  const arg = args[i];
102
107
  if (GLOBAL_FLAGS_WITH_SEPARATE_VALUE.has(arg)) {
@@ -105,10 +110,12 @@ function classifyGitCommand(args) {
105
110
  }
106
111
  if (arg.startsWith("-")) continue;
107
112
  subcommand = arg;
113
+ form = args[i + 1];
108
114
  break;
109
115
  }
110
116
  if (!subcommand) return "read";
111
117
  if (NETWORK_SUBCOMMANDS.has(subcommand)) return "network";
118
+ if (form !== void 0 && AMBIGUOUS_READ_FORMS[subcommand]?.has(form)) return "read";
112
119
  if (WRITE_SUBCOMMANDS.has(subcommand) || AMBIGUOUS_AS_WRITE.has(subcommand)) return "write";
113
120
  if (READ_SUBCOMMANDS.has(subcommand)) return "read";
114
121
  return "write";
@@ -299,15 +306,15 @@ async function isUneditedTrackedFile(cwd, relativePath) {
299
306
  // src/modules/real-path.ts
300
307
  import { realpathSync } from "fs";
301
308
  import { basename, dirname, join, resolve } from "path";
302
- function resolveRealPath(path4) {
309
+ function resolveRealPath(path5) {
303
310
  const missing = [];
304
- let existing = path4;
311
+ let existing = path5;
305
312
  for (; ; ) {
306
313
  try {
307
314
  return join(realpathSync.native(existing), ...missing);
308
315
  } catch {
309
316
  const parent = dirname(existing);
310
- if (parent === existing) return resolve(path4);
317
+ if (parent === existing) return resolve(path5);
311
318
  missing.unshift(basename(existing));
312
319
  existing = parent;
313
320
  }
@@ -1431,8 +1438,8 @@ var CLIENT_SHIM_PATH_PATTERNS = [
1431
1438
  /^\.claude\/commands\/spell-[a-z0-9-]+\.md$/,
1432
1439
  /^\.agents\/skills\/spell-[a-z0-9-]+\/SKILL\.md$/
1433
1440
  ];
1434
- function isClientShimPath(path4) {
1435
- const normalized = path4.replace(/\\/g, "/").replace(/^\.\//, "");
1441
+ function isClientShimPath(path5) {
1442
+ const normalized = path5.replace(/\\/g, "/").replace(/^\.\//, "");
1436
1443
  return CLIENT_SHIM_PATH_PATTERNS.some((pattern) => pattern.test(normalized));
1437
1444
  }
1438
1445
  var FRONTMATTER_PATTERN = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
@@ -1515,11 +1522,11 @@ var STORE_SPELL_PATH_PATTERN = /^spells\/(spell-[a-z0-9-]+)\.md$/;
1515
1522
  function storeSpellPath(id) {
1516
1523
  return `${USER_TIER_SPELLS_DIR}/${id}.md`;
1517
1524
  }
1518
- function spellIdFromCanonicalPath(path4) {
1519
- return path4.replace(/\\/g, "/").match(CANONICAL_SPELL_PATH_PATTERN)?.[1];
1525
+ function spellIdFromCanonicalPath(path5) {
1526
+ return path5.replace(/\\/g, "/").match(CANONICAL_SPELL_PATH_PATTERN)?.[1];
1520
1527
  }
1521
- function spellIdFromStorePath(path4) {
1522
- return path4.replace(/\\/g, "/").match(STORE_SPELL_PATH_PATTERN)?.[1];
1528
+ function spellIdFromStorePath(path5) {
1529
+ return path5.replace(/\\/g, "/").match(STORE_SPELL_PATH_PATTERN)?.[1];
1523
1530
  }
1524
1531
  function absoluteStoreSpellPath(storeRoot, id) {
1525
1532
  return resolve2(storeRoot, storeSpellPath(id)).replace(/\\/g, "/");
@@ -1618,9 +1625,9 @@ async function renderUserFanout(storeRoot, spellIds, fallbackDir) {
1618
1625
  }
1619
1626
  return { files, missing };
1620
1627
  }
1621
- async function classifyTarget(path4) {
1628
+ async function classifyTarget(path5) {
1622
1629
  try {
1623
- const stats = await lstat2(path4);
1630
+ const stats = await lstat2(path5);
1624
1631
  return stats.isFile() ? "file" : "other";
1625
1632
  } catch (err) {
1626
1633
  if (err.code === "ENOENT") return "absent";
@@ -2231,29 +2238,29 @@ var AgentConfigValidationError = class extends Error {
2231
2238
  function isRecord2(value) {
2232
2239
  return typeof value === "object" && value !== null && !Array.isArray(value);
2233
2240
  }
2234
- function requireRecord(value, path4, issues) {
2241
+ function requireRecord(value, path5, issues) {
2235
2242
  if (!isRecord2(value)) {
2236
- issues.push(`${path4} must be an object`);
2243
+ issues.push(`${path5} must be an object`);
2237
2244
  return void 0;
2238
2245
  }
2239
2246
  return value;
2240
2247
  }
2241
- function requireString(value, path4, issues) {
2248
+ function requireString(value, path5, issues) {
2242
2249
  if (typeof value !== "string" || value.trim().length === 0) {
2243
- issues.push(`${path4} must be a non-empty string`);
2250
+ issues.push(`${path5} must be a non-empty string`);
2244
2251
  }
2245
2252
  }
2246
- function requireBoolean(value, path4, issues) {
2247
- if (typeof value !== "boolean") issues.push(`${path4} must be a boolean`);
2253
+ function requireBoolean(value, path5, issues) {
2254
+ if (typeof value !== "boolean") issues.push(`${path5} must be a boolean`);
2248
2255
  }
2249
- function requireStringArray(value, path4, issues) {
2256
+ function requireStringArray(value, path5, issues) {
2250
2257
  if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) {
2251
- issues.push(`${path4} must be an array of strings`);
2258
+ issues.push(`${path5} must be an array of strings`);
2252
2259
  }
2253
2260
  }
2254
- function requireEnum(value, allowed, path4, issues) {
2261
+ function requireEnum(value, allowed, path5, issues) {
2255
2262
  if (typeof value !== "string" || !allowed.has(value)) {
2256
- issues.push(`${path4} must be one of: ${[...allowed].join(", ")}`);
2263
+ issues.push(`${path5} must be one of: ${[...allowed].join(", ")}`);
2257
2264
  }
2258
2265
  }
2259
2266
  function validateOptionalClient(clients, key, property, expected, issues) {
@@ -5913,12 +5920,12 @@ ${verb} ${orphansToReport.length} orphaned file(s) (tracked before this update,
5913
5920
  if (conversions.length > 0) {
5914
5921
  console.log(`
5915
5922
  Converted ${conversions.length} symlinked managed file(s) to regular files (#357):`);
5916
- const describe = {
5923
+ const describe3 = {
5917
5924
  "index-symlink": "the index tracked it as a symlink (mode 120000); rewritten and staged as a regular file",
5918
5925
  symlink: "was a symlink in the working tree; now a regular file (stage the typechange with git add)",
5919
5926
  "path-body": "its body was only a link target, a symlink written out as text; rewritten as a real file"
5920
5927
  };
5921
- for (const c of conversions) console.log(` ${c.file} \u2014 ${describe[c.shape]}`);
5928
+ for (const c of conversions) console.log(` ${c.file} \u2014 ${describe3[c.shape]}`);
5922
5929
  if (conversions.some((c) => c.shape === "index-symlink")) {
5923
5930
  console.log(
5924
5931
  " Commit the staged typechange, or every other checkout keeps reading a link whose target is the file's own text."
@@ -6103,6 +6110,56 @@ async function checkForUpdate(currentVersion, feedUrl, cachePath = DEFAULT_CACHE
6103
6110
  }
6104
6111
  }
6105
6112
 
6113
+ // src/modules/state/versions.ts
6114
+ function compareVersions(a, b) {
6115
+ const parse3 = (v) => /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?/.exec(v);
6116
+ const pa = parse3(a);
6117
+ const pb = parse3(b);
6118
+ if (!pa || !pb) return a < b ? -1 : a > b ? 1 : 0;
6119
+ for (let i = 1; i <= 3; i++) {
6120
+ const diff = Number(pa[i]) - Number(pb[i]);
6121
+ if (diff !== 0) return diff;
6122
+ }
6123
+ const sa = pa[4];
6124
+ const sb = pb[4];
6125
+ if (sa === sb) return 0;
6126
+ if (sa === void 0) return 1;
6127
+ if (sb === void 0) return -1;
6128
+ return sa < sb ? -1 : 1;
6129
+ }
6130
+ async function collectVersions(manifest, packageVersion, options) {
6131
+ const warnings = [];
6132
+ let lookup = null;
6133
+ if (options.online) {
6134
+ let feedUrl = options.feedUrl ?? "";
6135
+ if (feedUrl === "") {
6136
+ try {
6137
+ feedUrl = getFeedUrl();
6138
+ } catch {
6139
+ feedUrl = "";
6140
+ }
6141
+ }
6142
+ lookup = feedUrl ? await checkForUpdate(packageVersion, feedUrl, options.cachePath) : { current: packageVersion, latest: null, updateAvailable: false, error: "Feed URL unavailable" };
6143
+ if (lookup.latest === null) {
6144
+ warnings.push(
6145
+ `versions.latest: the npm lookup gave no answer${lookup.error ? ` (${lookup.error})` : ""}; latest and drift.cli_behind_latest are null`
6146
+ );
6147
+ }
6148
+ }
6149
+ const latest = lookup?.latest ?? null;
6150
+ const versions = {
6151
+ files: manifest.version,
6152
+ cli: packageVersion,
6153
+ latest,
6154
+ drift: {
6155
+ files_behind_cli: compareVersions(manifest.version, packageVersion) < 0,
6156
+ cli_behind_latest: latest === null ? null : compareVersions(packageVersion, latest) < 0
6157
+ },
6158
+ components: manifest.components.map((component) => ({ name: component.name, installedVersion: component.installedVersion })).sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0)
6159
+ };
6160
+ return { versions, warnings, lookup };
6161
+ }
6162
+
6106
6163
  // src/modules/diagram-generator.ts
6107
6164
  function generateVersionDriftDiagram(repoFilesVersion, installedCliVersion, npmLatestVersion) {
6108
6165
  if (repoFilesVersion === installedCliVersion && installedCliVersion === npmLatestVersion) {
@@ -6156,13 +6213,8 @@ async function runStatus(targetDir, packageVersion, options = {}) {
6156
6213
  console.log("No components installed.");
6157
6214
  return;
6158
6215
  }
6159
- let feedUrl;
6160
- try {
6161
- feedUrl = getFeedUrl();
6162
- } catch {
6163
- feedUrl = "";
6164
- }
6165
- const versionResult = feedUrl ? await checkForUpdate(packageVersion, feedUrl) : { current: packageVersion, latest: null, updateAvailable: false, error: "Feed URL unavailable" };
6216
+ const { versions, lookup } = await collectVersions(manifest, packageVersion, { online: true });
6217
+ const versionResult = lookup ?? { current: packageVersion, latest: null, updateAvailable: false };
6166
6218
  const nameWidth = Math.max(
6167
6219
  "Component".length,
6168
6220
  ...manifest.components.map((c) => c.name.length)
@@ -6219,25 +6271,25 @@ async function runStatus(targetDir, packageVersion, options = {}) {
6219
6271
  }
6220
6272
  }
6221
6273
  console.log("");
6222
- const latestStr = versionResult.error ? "unable to check" : versionResult.latest ?? manifest.version;
6223
- let footer = ` Installed: ${packageVersion} Latest: ${latestStr}`;
6274
+ const latestStr = versionResult.error ? "unable to check" : versions.latest ?? versions.files;
6275
+ let footer = ` Installed: ${versions.cli} Latest: ${latestStr}`;
6224
6276
  if (versionResult.updateAvailable) {
6225
6277
  footer += " (spell update available)";
6226
6278
  }
6227
6279
  console.log(footer);
6228
- if (versionResult.latest !== null) {
6280
+ if (versions.latest !== null) {
6229
6281
  const diagram = generateVersionDriftDiagram(
6230
- manifest.version,
6231
- packageVersion,
6232
- versionResult.latest
6282
+ versions.files,
6283
+ versions.cli,
6284
+ versions.latest
6233
6285
  );
6234
6286
  if (diagram !== null) {
6235
6287
  console.log("");
6236
6288
  if (process.stdout.isTTY) {
6237
6289
  console.log(" Version drift:");
6238
- console.log(` repo-files: ${manifest.version}`);
6239
- console.log(` installed-cli: ${packageVersion}`);
6240
- console.log(` latest (npm): ${versionResult.latest}`);
6290
+ console.log(` repo-files: ${versions.files}`);
6291
+ console.log(` installed-cli: ${versions.cli}`);
6292
+ console.log(` latest (npm): ${versions.latest}`);
6241
6293
  } else {
6242
6294
  console.log("```mermaid");
6243
6295
  console.log(diagram);
@@ -6252,9 +6304,1059 @@ async function runStatus(targetDir, packageVersion, options = {}) {
6252
6304
  }
6253
6305
  }
6254
6306
 
6307
+ // src/modules/state/agents.ts
6308
+ import { createHash as createHash2 } from "crypto";
6309
+ import { readFile as readFile11 } from "fs/promises";
6310
+ import { join as join13 } from "path";
6311
+ async function fileRevision(path5) {
6312
+ try {
6313
+ return `sha256:${createHash2("sha256").update(await readFile11(path5)).digest("hex")}`;
6314
+ } catch {
6315
+ return null;
6316
+ }
6317
+ }
6318
+ async function collectAgents(targetDir, options = {}) {
6319
+ let resolved;
6320
+ try {
6321
+ resolved = await resolveRoster(targetDir, "repo", options.homeDir);
6322
+ } catch (error) {
6323
+ if (error instanceof AgentRosterNotFoundError) return { agents: null, warnings: [] };
6324
+ throw error;
6325
+ }
6326
+ const origin = resolved.fromUserTier ? "user" : "repo";
6327
+ const warnings = [];
6328
+ const entries = [];
6329
+ for (const entry of resolved.roster.roster) {
6330
+ const definitionPath = join13(resolved.definitionsDir, `${entry.definition}.yaml`);
6331
+ const definition_revision = await fileRevision(definitionPath);
6332
+ if (definition_revision === null) {
6333
+ warnings.push(`agents: the definition file for "${entry.definition}" (${entry.id}) is missing from its definitions directory; definition_revision is null`);
6334
+ }
6335
+ entries.push({
6336
+ definition: entry.definition,
6337
+ name: entry.name,
6338
+ id: entry.id,
6339
+ epithet: entry.epithet ?? null,
6340
+ origin,
6341
+ definition_revision
6342
+ });
6343
+ }
6344
+ return {
6345
+ agents: {
6346
+ schema: "roster-v2",
6347
+ naming_strategy: resolved.roster.naming_strategy,
6348
+ agent_profile: resolved.roster.agent_profile,
6349
+ entries
6350
+ },
6351
+ warnings
6352
+ };
6353
+ }
6354
+
6355
+ // src/modules/state/books.ts
6356
+ import { createHash as createHash3 } from "crypto";
6357
+ import { readFile as readFile13 } from "fs/promises";
6358
+ import { join as join15 } from "path";
6359
+
6360
+ // src/modules/state/decisions.ts
6361
+ import { readFile as readFile12 } from "fs/promises";
6362
+ import { join as join14 } from "path";
6363
+ var HEADING = /^## ([A-Z]{2,5})-(\d{3,})\b(.*)$/gm;
6364
+ function headingAnchor(heading) {
6365
+ return heading.trim().toLowerCase().replace(/[^\p{L}\p{N} _-]/gu, "").replace(/ /g, "-");
6366
+ }
6367
+ function titleAfterId(rest) {
6368
+ return rest.replace(/^\s*[—–:-]+\s*/u, "").trim();
6369
+ }
6370
+ function parseDecisions(content) {
6371
+ const text = content.replace(/\r\n?/g, "\n");
6372
+ const positions = [];
6373
+ let match;
6374
+ HEADING.lastIndex = 0;
6375
+ while ((match = HEADING.exec(text)) !== null) {
6376
+ positions.push({ id: `${match[1]}-${match[2]}`, heading: `${match[1]}-${match[2]}${match[3]}`, index: match.index });
6377
+ }
6378
+ const entries = [];
6379
+ const warnings = [];
6380
+ positions.forEach(({ id, heading, index }, i) => {
6381
+ const end = i + 1 < positions.length ? positions[i + 1].index : text.length;
6382
+ const section = text.slice(index, end);
6383
+ const dateMatch = /^\*\*Date:\*\*\s*(\d{4}-\d{2}-\d{2})/m.exec(section);
6384
+ const statusMatch = /^\*\*Status:\*\*\s*(\S+)/m.exec(section);
6385
+ if (!dateMatch && !statusMatch) {
6386
+ warnings.push(`indexes.decisions: ${id} has no **Date:** or **Status:** line and was skipped`);
6387
+ return;
6388
+ }
6389
+ entries.push({
6390
+ id,
6391
+ title: titleAfterId(heading.slice(id.length)),
6392
+ status: statusMatch ? statusMatch[1] : null,
6393
+ date: dateMatch ? dateMatch[1] : null,
6394
+ anchor: headingAnchor(heading)
6395
+ });
6396
+ });
6397
+ return { entries, warnings };
6398
+ }
6399
+ function parseAdrs(decisionsContent) {
6400
+ return parseDecisions(decisionsContent).entries.map(({ id, date, status }) => ({ id, date, status }));
6401
+ }
6402
+ function countAcceptedAdrsInWindow(adrs, fromDate, toDate) {
6403
+ return adrs.filter(
6404
+ (adr) => adr.status === "Accepted" && adr.date !== null && adr.date >= fromDate && adr.date <= toDate
6405
+ ).length;
6406
+ }
6407
+ async function collectDecisions(root) {
6408
+ let content;
6409
+ try {
6410
+ content = await readFile12(join14(root, "DECISIONS.md"), "utf8");
6411
+ } catch (error) {
6412
+ if (error.code === "ENOENT") return { decisions: null, warnings: [] };
6413
+ return {
6414
+ decisions: null,
6415
+ warnings: [`indexes.decisions: DECISIONS.md could not be read (${error instanceof Error ? error.message : String(error)})`]
6416
+ };
6417
+ }
6418
+ const { entries, warnings } = parseDecisions(content);
6419
+ return {
6420
+ decisions: entries.sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0),
6421
+ warnings
6422
+ };
6423
+ }
6424
+
6425
+ // src/modules/state/books.ts
6426
+ var ITEM = /^\s*- \[( |x|X)\] (.*)$/;
6427
+ var HEADING2 = /^#{1,6}\s+(.+?)\s*#*\s*$/;
6428
+ var TEXT_LIMIT = 200;
6429
+ function normalizeItemText(text) {
6430
+ return text.replace(/\s+/g, " ").trim();
6431
+ }
6432
+ function itemLocator(section, text) {
6433
+ const slug = section === null ? "" : headingAnchor(section);
6434
+ const hash = createHash3("sha256").update(normalizeItemText(text), "utf8").digest("hex").slice(0, 12);
6435
+ return `${slug === "" ? "root" : slug}/${hash}`;
6436
+ }
6437
+ function parseBook(content) {
6438
+ const items = [];
6439
+ let section = null;
6440
+ let open = 0;
6441
+ let done = 0;
6442
+ for (const line of content.split(/\r?\n/)) {
6443
+ const heading = HEADING2.exec(line);
6444
+ if (heading) {
6445
+ section = heading[1];
6446
+ continue;
6447
+ }
6448
+ const item = ITEM.exec(line);
6449
+ if (!item) continue;
6450
+ const checked = item[1] !== " ";
6451
+ const text = item[2].trim();
6452
+ if (checked) done++;
6453
+ else open++;
6454
+ items.push({
6455
+ text: text.length > TEXT_LIMIT ? text.slice(0, TEXT_LIMIT) : text,
6456
+ section,
6457
+ locator: itemLocator(section, text),
6458
+ checked
6459
+ });
6460
+ }
6461
+ return { open, done, items };
6462
+ }
6463
+ async function collectBook(root, fileName) {
6464
+ let content;
6465
+ try {
6466
+ content = await readFile13(join15(root, fileName), "utf8");
6467
+ } catch (error) {
6468
+ if (error.code === "ENOENT") return { book: null, warnings: [] };
6469
+ return {
6470
+ book: null,
6471
+ warnings: [`indexes: ${fileName} could not be read (${error instanceof Error ? error.message : String(error)})`]
6472
+ };
6473
+ }
6474
+ return { book: parseBook(content), warnings: [] };
6475
+ }
6476
+
6477
+ // src/modules/state/config.ts
6478
+ function collectConfig(manifest) {
6479
+ return {
6480
+ tracking_mode: manifest.tracking_mode ?? null,
6481
+ external_provider: manifest.external_provider ?? null,
6482
+ push_policy: manifest.push_policy ?? null,
6483
+ role: manifest.role ?? null,
6484
+ business_root: manifest.business_root ?? null,
6485
+ spell_scope: manifest.spell_scope ?? null,
6486
+ content_sensitivity: manifest.content_sensitivity ?? null
6487
+ };
6488
+ }
6489
+
6490
+ // src/modules/state/features.ts
6491
+ import { readdir as readdir7, readFile as readFile14 } from "fs/promises";
6492
+ import { join as join16 } from "path";
6493
+ import { parse as parse2 } from "yaml";
6494
+ var FEATURES_DIR = "features";
6495
+ var PRD_FILE = "PRD.md";
6496
+ var FRONTMATTER = /^(?:# [^\n]*\n+)?---\n([\s\S]*?)\n---(?:\n|$)/;
6497
+ var H1 = /^# (.+?)\s*$/m;
6498
+ function isRecord3(value) {
6499
+ return typeof value === "object" && value !== null && !Array.isArray(value);
6500
+ }
6501
+ function stringOrNull(value) {
6502
+ if (typeof value === "string") return value;
6503
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
6504
+ return null;
6505
+ }
6506
+ function idOrNull(value) {
6507
+ if (typeof value === "number" || typeof value === "string") return value;
6508
+ return null;
6509
+ }
6510
+ function featureTracking(frontmatter) {
6511
+ const nested = frontmatter["tracking"];
6512
+ const source = isRecord3(nested) ? nested : "tracking_mode" in frontmatter || "external_provider" in frontmatter ? frontmatter : null;
6513
+ if (source === null) return null;
6514
+ return {
6515
+ tracking_mode: stringOrNull(source["tracking_mode"]),
6516
+ external_provider: stringOrNull(source["external_provider"]),
6517
+ adoWorkItemId: idOrNull(source["adoWorkItemId"]),
6518
+ githubIssueId: idOrNull(source["githubIssueId"])
6519
+ };
6520
+ }
6521
+ function parsePrd(content, displayPath) {
6522
+ const text = content.replace(/\r\n?/g, "\n");
6523
+ const heading = H1.exec(text);
6524
+ const empty = { title: heading ? heading[1] : null, status: null, implementation_status: null, tracking: null };
6525
+ const match = FRONTMATTER.exec(text);
6526
+ if (!match) return { fields: empty, warning: `features: ${displayPath} has no frontmatter` };
6527
+ let parsed;
6528
+ try {
6529
+ parsed = parse2(match[1]);
6530
+ } catch (error) {
6531
+ const reason = error instanceof Error ? error.message.split("\n")[0] : String(error);
6532
+ return { fields: empty, warning: `features: ${displayPath} has malformed frontmatter (${reason})` };
6533
+ }
6534
+ if (!isRecord3(parsed)) return { fields: empty, warning: `features: ${displayPath} has frontmatter that is not a map` };
6535
+ return {
6536
+ fields: {
6537
+ title: stringOrNull(parsed["title"]) ?? empty.title,
6538
+ status: stringOrNull(parsed["status"]),
6539
+ implementation_status: stringOrNull(parsed["implementation_status"]),
6540
+ tracking: featureTracking(parsed)
6541
+ },
6542
+ warning: null
6543
+ };
6544
+ }
6545
+ async function collectFeatures(root) {
6546
+ const dir = join16(root, FEATURES_DIR);
6547
+ let slugs;
6548
+ try {
6549
+ const entries = await readdir7(dir, { withFileTypes: true });
6550
+ slugs = entries.filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => entry.name);
6551
+ } catch (error) {
6552
+ if (error.code === "ENOENT") return { features: null, warnings: [] };
6553
+ return {
6554
+ features: null,
6555
+ warnings: [`features: ${FEATURES_DIR}/ could not be read (${error instanceof Error ? error.message : String(error)})`]
6556
+ };
6557
+ }
6558
+ const warnings = [];
6559
+ const features = [];
6560
+ for (const slug of slugs) {
6561
+ const path5 = `${FEATURES_DIR}/${slug}/${PRD_FILE}`;
6562
+ let content;
6563
+ try {
6564
+ content = await readFile14(join16(dir, slug, PRD_FILE), "utf8");
6565
+ } catch (error) {
6566
+ if (error.code === "ENOENT") continue;
6567
+ warnings.push(`features: ${path5} could not be read (${error instanceof Error ? error.message : String(error)})`);
6568
+ continue;
6569
+ }
6570
+ const { fields, warning } = parsePrd(content, path5);
6571
+ if (warning !== null) warnings.push(warning);
6572
+ features.push({ slug, ...fields, path: path5 });
6573
+ }
6574
+ features.sort((a, b) => a.slug < b.slug ? -1 : a.slug > b.slug ? 1 : 0);
6575
+ return { features, warnings };
6576
+ }
6577
+
6578
+ // src/modules/state/serialize.ts
6579
+ import path4 from "path";
6580
+ var object = (keys) => ({ kind: "object", keys });
6581
+ var array = (items, sortBy) => ({
6582
+ kind: "array",
6583
+ items,
6584
+ ...sortBy === void 0 ? {} : { sortBy }
6585
+ });
6586
+ var book = object({
6587
+ open: void 0,
6588
+ done: void 0,
6589
+ items: array(object({ text: void 0, section: void 0, locator: void 0, checked: void 0 }))
6590
+ });
6591
+ var STATE_SHAPE = object({
6592
+ schema_version: void 0,
6593
+ generated_at: void 0,
6594
+ generator: object({ name: void 0, version: void 0 }),
6595
+ repository: object({ root: void 0, scope: void 0, role: void 0, profile: void 0 }),
6596
+ versions: object({
6597
+ files: void 0,
6598
+ cli: void 0,
6599
+ latest: void 0,
6600
+ drift: object({ files_behind_cli: void 0, cli_behind_latest: void 0 }),
6601
+ components: array(object({ name: void 0, installedVersion: void 0 }), ["name"])
6602
+ }),
6603
+ git: object({
6604
+ branch: void 0,
6605
+ detached: void 0,
6606
+ trunk: void 0,
6607
+ ahead: void 0,
6608
+ behind: void 0,
6609
+ uncommitted: void 0,
6610
+ remote: object({ provider: void 0, url: void 0 }),
6611
+ worktrees: array(object({ path: void 0, branch: void 0 }), ["path"]),
6612
+ pull_request: object({ id: void 0, url: void 0, state: void 0 })
6613
+ }),
6614
+ indexes: object({
6615
+ decisions: array(
6616
+ object({ id: void 0, title: void 0, status: void 0, date: void 0, anchor: void 0 }),
6617
+ ["id"]
6618
+ ),
6619
+ todos: book,
6620
+ ideas: book,
6621
+ done: book,
6622
+ journal: array(object({ date: void 0, slug: void 0, title: void 0, path: void 0 }), ["date", "slug"])
6623
+ }),
6624
+ config: object({
6625
+ tracking_mode: void 0,
6626
+ external_provider: void 0,
6627
+ push_policy: void 0,
6628
+ role: void 0,
6629
+ business_root: void 0,
6630
+ spell_scope: void 0,
6631
+ content_sensitivity: void 0
6632
+ }),
6633
+ registry: object({
6634
+ updated: void 0,
6635
+ ventures: array(
6636
+ object({
6637
+ slug: void 0,
6638
+ name: void 0,
6639
+ aliases: void 0,
6640
+ status: void 0,
6641
+ visibility: void 0,
6642
+ ownership: void 0,
6643
+ tracking: void 0,
6644
+ repos: array(object({ name: void 0, remote: void 0, default: void 0, local_path: void 0 }))
6645
+ }),
6646
+ ["slug"]
6647
+ )
6648
+ }),
6649
+ features: array(
6650
+ object({
6651
+ slug: void 0,
6652
+ title: void 0,
6653
+ status: void 0,
6654
+ implementation_status: void 0,
6655
+ tracking: object({
6656
+ tracking_mode: void 0,
6657
+ external_provider: void 0,
6658
+ adoWorkItemId: void 0,
6659
+ githubIssueId: void 0
6660
+ }),
6661
+ path: void 0
6662
+ }),
6663
+ ["slug"]
6664
+ ),
6665
+ agents: object({
6666
+ schema: void 0,
6667
+ naming_strategy: void 0,
6668
+ agent_profile: void 0,
6669
+ entries: array(
6670
+ object({
6671
+ definition: void 0,
6672
+ name: void 0,
6673
+ id: void 0,
6674
+ epithet: void 0,
6675
+ origin: void 0,
6676
+ definition_revision: void 0
6677
+ })
6678
+ )
6679
+ }),
6680
+ warnings: array(void 0, [])
6681
+ });
6682
+ function isRecord4(value) {
6683
+ return typeof value === "object" && value !== null && !Array.isArray(value);
6684
+ }
6685
+ function compareScalars(a, b) {
6686
+ if (typeof a === "number" && typeof b === "number") return a - b;
6687
+ const left = String(a);
6688
+ const right = String(b);
6689
+ return left < right ? -1 : left > right ? 1 : 0;
6690
+ }
6691
+ function sortedBy(values, keys) {
6692
+ const indexed = values.map((value, index) => ({ value, index }));
6693
+ indexed.sort((a, b) => {
6694
+ for (const key of keys) {
6695
+ const result = compareScalars(
6696
+ isRecord4(a.value) ? a.value[key] : void 0,
6697
+ isRecord4(b.value) ? b.value[key] : void 0
6698
+ );
6699
+ if (result !== 0) return result;
6700
+ }
6701
+ return a.index - b.index;
6702
+ });
6703
+ return indexed.map((entry) => entry.value);
6704
+ }
6705
+ function canonicalize(value, shape) {
6706
+ if (value === null || value === void 0 || shape === void 0) return value;
6707
+ if (shape.kind === "array") {
6708
+ if (!Array.isArray(value)) return value;
6709
+ const items = value.map((item) => canonicalize(item, shape.items));
6710
+ if (shape.sortBy === void 0) return items;
6711
+ if (shape.sortBy.length === 0) return [...items].sort(compareScalars);
6712
+ return sortedBy(items, shape.sortBy);
6713
+ }
6714
+ if (!isRecord4(value)) return value;
6715
+ const ordered = {};
6716
+ for (const [key, keyShape] of Object.entries(shape.keys)) {
6717
+ if (!(key in value) || value[key] === void 0) continue;
6718
+ ordered[key] = canonicalize(value[key], keyShape);
6719
+ }
6720
+ for (const [key, extra] of Object.entries(value)) {
6721
+ if (key in ordered || key in shape.keys || extra === void 0) continue;
6722
+ ordered[key] = extra;
6723
+ }
6724
+ return ordered;
6725
+ }
6726
+ function serializeState(doc) {
6727
+ return `${JSON.stringify(canonicalize(doc, STATE_SHAPE), null, 2)}
6728
+ `;
6729
+ }
6730
+ function toPosixRelative(root, target) {
6731
+ const relative4 = path4.relative(root, target);
6732
+ if (relative4 === "") return ".";
6733
+ if (path4.isAbsolute(relative4)) return relative4.replace(/\\/g, "/");
6734
+ return relative4.split(path4.sep).join("/");
6735
+ }
6736
+
6737
+ // src/modules/state/git.ts
6738
+ function remoteProvider(url) {
6739
+ const host = remoteHost(url).toLowerCase();
6740
+ if (host === "github.com" || host.endsWith(".github.com")) return "github";
6741
+ if (host === "dev.azure.com" || host.endsWith(".dev.azure.com") || host.endsWith(".visualstudio.com")) return "ado";
6742
+ return "other";
6743
+ }
6744
+ function remoteHost(url) {
6745
+ const scheme = /^[a-z][a-z0-9+.-]*:\/\/(?:[^@/]*@)?([^/:]+)/i.exec(url);
6746
+ if (scheme) return scheme[1];
6747
+ const scp = /^(?:[^@/]+@)?([^:/]+):/.exec(url);
6748
+ return scp ? scp[1] : "";
6749
+ }
6750
+ function stripUserinfo(url) {
6751
+ return url.replace(/^([a-z][a-z0-9+.-]*:\/\/)[^@/]*@/i, "$1");
6752
+ }
6753
+ function parseWorktreeList(porcelain, root) {
6754
+ const worktrees = [];
6755
+ for (const block of porcelain.replace(/\r\n/g, "\n").split(/\n\n+/)) {
6756
+ const lines = block.split("\n").filter((line) => line.length > 0);
6757
+ const pathLine = lines.find((line) => line.startsWith("worktree "));
6758
+ if (!pathLine) continue;
6759
+ const branchLine = lines.find((line) => line.startsWith("branch "));
6760
+ const branch = branchLine ? branchLine.slice("branch ".length).replace(/^refs\/heads\//, "") : null;
6761
+ const absolute = resolveRealPath(pathLine.slice("worktree ".length));
6762
+ worktrees.push({ path: toPosixRelative(root, absolute), branch });
6763
+ }
6764
+ return worktrees.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
6765
+ }
6766
+ async function probe(read) {
6767
+ try {
6768
+ return { ok: true, value: await read() };
6769
+ } catch (error) {
6770
+ return { ok: false, error };
6771
+ }
6772
+ }
6773
+ function describe(error) {
6774
+ return error instanceof Error ? error.message : String(error);
6775
+ }
6776
+ async function collectGit(cwd, root, options = {}) {
6777
+ const git = options.runGit ?? runGit;
6778
+ const warnings = [];
6779
+ const inside = await probe(() => git(cwd, ["rev-parse", "--is-inside-work-tree"]));
6780
+ if (!inside.ok || inside.value.stdout.trim() !== "true") {
6781
+ warnings.push(
6782
+ inside.ok ? "git: not a git repository; the git section is null" : `git: could not determine whether this is a git repository (${describe(inside.error)}); the git section is null`
6783
+ );
6784
+ return { git: null, warnings };
6785
+ }
6786
+ let branch = null;
6787
+ let detached = false;
6788
+ const symbolic = await probe(() => git(cwd, ["symbolic-ref", "--short", "-q", "HEAD"]));
6789
+ if (symbolic.ok && symbolic.value.stdout.trim() !== "") {
6790
+ branch = symbolic.value.stdout.trim();
6791
+ } else {
6792
+ const head = await probe(() => git(cwd, ["rev-parse", "--short", "HEAD"]));
6793
+ if (head.ok) {
6794
+ branch = head.value.stdout.trim();
6795
+ detached = true;
6796
+ } else {
6797
+ warnings.push(`git.branch: HEAD could not be read (${describe(head.error)})`);
6798
+ }
6799
+ }
6800
+ let trunk = null;
6801
+ const originHead = await probe(() => git(cwd, ["symbolic-ref", "--short", "-q", "refs/remotes/origin/HEAD"]));
6802
+ if (originHead.ok && originHead.value.stdout.trim() !== "") {
6803
+ trunk = originHead.value.stdout.trim().replace(/^origin\//, "");
6804
+ } else {
6805
+ for (const candidate of ["main", "master"]) {
6806
+ const exists = await probe(() => git(cwd, ["rev-parse", "--verify", "--quiet", `refs/heads/${candidate}`]));
6807
+ if (exists.ok) {
6808
+ trunk = candidate;
6809
+ break;
6810
+ }
6811
+ }
6812
+ }
6813
+ let ahead = null;
6814
+ let behind = null;
6815
+ const upstream = await probe(() => git(cwd, ["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{upstream}"]));
6816
+ if (upstream.ok) {
6817
+ const counts = await probe(() => git(cwd, ["rev-list", "--left-right", "--count", "@{upstream}...HEAD"]));
6818
+ if (counts.ok) {
6819
+ const [left, right] = counts.value.stdout.trim().split(/\s+/);
6820
+ behind = Number.parseInt(left, 10);
6821
+ ahead = Number.parseInt(right, 10);
6822
+ if (Number.isNaN(ahead) || Number.isNaN(behind)) {
6823
+ ahead = behind = null;
6824
+ warnings.push(`git.ahead/behind: unexpected rev-list output "${counts.value.stdout.trim()}"`);
6825
+ }
6826
+ } else {
6827
+ warnings.push(`git.ahead/behind: rev-list failed (${describe(counts.error)})`);
6828
+ }
6829
+ } else if (isTimeout(upstream.error)) {
6830
+ warnings.push(`git.ahead/behind: ${describe(upstream.error)}`);
6831
+ }
6832
+ let uncommitted = null;
6833
+ const status = await probe(() => git(cwd, ["status", "--porcelain", "-uall"]));
6834
+ if (status.ok) {
6835
+ const lines = status.value.stdout.trim();
6836
+ uncommitted = lines.length === 0 ? 0 : lines.split("\n").length;
6837
+ } else {
6838
+ warnings.push(`git.uncommitted: status failed (${describe(status.error)})`);
6839
+ }
6840
+ let remote = null;
6841
+ const remotes = await probe(() => git(cwd, ["remote"], { commandClass: "read" }));
6842
+ if (!remotes.ok) {
6843
+ warnings.push(`git.remote: remote list failed (${describe(remotes.error)})`);
6844
+ } else if (remotes.value.stdout.split(/\r?\n/).includes("origin")) {
6845
+ const url = await probe(() => git(cwd, ["remote", "get-url", "origin"]));
6846
+ if (url.ok) {
6847
+ const raw = url.value.stdout.trim();
6848
+ remote = { provider: remoteProvider(raw), url: stripUserinfo(raw) };
6849
+ } else {
6850
+ warnings.push(`git.remote: remote get-url failed (${describe(url.error)})`);
6851
+ }
6852
+ }
6853
+ let worktrees = null;
6854
+ const list = await probe(() => git(cwd, ["worktree", "list", "--porcelain"]));
6855
+ if (list.ok) {
6856
+ worktrees = parseWorktreeList(list.value.stdout, root);
6857
+ } else {
6858
+ warnings.push(`git.worktrees: worktree list failed (${describe(list.error)})`);
6859
+ }
6860
+ return {
6861
+ git: { branch, detached, trunk, ahead, behind, uncommitted, remote, worktrees, pull_request: null },
6862
+ warnings
6863
+ };
6864
+ }
6865
+ function isTimeout(error) {
6866
+ return error instanceof Error && error.name === "GitTimeoutError";
6867
+ }
6868
+
6869
+ // src/modules/state/journal.ts
6870
+ import { readdir as readdir8, readFile as readFile15 } from "fs/promises";
6871
+ import { join as join17 } from "path";
6872
+ var JOURNAL_DIR = "journal";
6873
+ var ENTRY_NAME = /^(\d{4}-\d{2}-\d{2})-(.+)\.md$/;
6874
+ function journalTitle(content) {
6875
+ const text = content.replace(/\r\n?/g, "\n");
6876
+ const frontmatter = /^---\n([\s\S]*?)\n---/.exec(text);
6877
+ if (frontmatter) {
6878
+ const title = /^title:\s*(.+)$/m.exec(frontmatter[1]);
6879
+ if (title) return title[1].trim().replace(/^(["'])(.*)\1$/, "$2");
6880
+ }
6881
+ const heading = /^# (.+?)\s*$/m.exec(text);
6882
+ return heading ? heading[1].trim() : null;
6883
+ }
6884
+ async function collectJournal(root) {
6885
+ const dir = join17(root, JOURNAL_DIR);
6886
+ let names;
6887
+ try {
6888
+ const entries = await readdir8(dir, { withFileTypes: true });
6889
+ names = entries.filter((entry) => entry.isFile() && !entry.name.startsWith(".")).map((entry) => entry.name);
6890
+ } catch (error) {
6891
+ if (error.code === "ENOENT") return { journal: null, warnings: [] };
6892
+ return {
6893
+ journal: null,
6894
+ warnings: [`indexes.journal: ${JOURNAL_DIR}/ could not be read (${error instanceof Error ? error.message : String(error)})`]
6895
+ };
6896
+ }
6897
+ const warnings = [];
6898
+ const journal = [];
6899
+ for (const name of names) {
6900
+ const match = ENTRY_NAME.exec(name);
6901
+ if (!match) {
6902
+ warnings.push(`indexes.journal: ${JOURNAL_DIR}/${name} does not match YYYY-MM-DD-<slug>.md and was skipped`);
6903
+ continue;
6904
+ }
6905
+ let title = null;
6906
+ try {
6907
+ title = journalTitle(await readFile15(join17(dir, name), "utf8"));
6908
+ } catch (error) {
6909
+ warnings.push(`indexes.journal: ${JOURNAL_DIR}/${name} could not be read (${error instanceof Error ? error.message : String(error)})`);
6910
+ }
6911
+ if (title === null) warnings.push(`indexes.journal: ${JOURNAL_DIR}/${name} has no title: frontmatter or # heading`);
6912
+ journal.push({ date: match[1], slug: match[2], title, path: `${JOURNAL_DIR}/${name}` });
6913
+ }
6914
+ journal.sort((a, b) => a.date < b.date ? -1 : a.date > b.date ? 1 : a.slug < b.slug ? -1 : a.slug > b.slug ? 1 : 0);
6915
+ return { journal, warnings };
6916
+ }
6917
+
6918
+ // src/modules/exec.ts
6919
+ import { execFile as execFile2 } from "child_process";
6920
+ import { existsSync, readFileSync } from "fs";
6921
+ import { win32 } from "path";
6922
+ var EXTERNAL_CLI_TIMEOUT_MS = 1e4;
6923
+ function execFileWithTimeout(file, args, timeoutMs, options = {}) {
6924
+ return new Promise((resolve10, reject) => {
6925
+ const child = execFile2(
6926
+ file,
6927
+ args,
6928
+ {
6929
+ timeout: timeoutMs,
6930
+ killSignal: "SIGTERM",
6931
+ windowsHide: true,
6932
+ encoding: "utf8",
6933
+ ...options.env ? { env: { ...process.env, ...options.env } } : {}
6934
+ },
6935
+ (error, stdout, stderr) => {
6936
+ if (error) {
6937
+ reject(error);
6938
+ return;
6939
+ }
6940
+ resolve10({ stdout, stderr });
6941
+ }
6942
+ );
6943
+ child.stdin?.end();
6944
+ });
6945
+ }
6946
+ function defaultProbes() {
6947
+ return {
6948
+ platform: process.platform,
6949
+ pathEnv: process.env["PATH"] ?? process.env["Path"],
6950
+ readText: (path5) => {
6951
+ try {
6952
+ return readFileSync(path5, "utf8");
6953
+ } catch {
6954
+ return void 0;
6955
+ }
6956
+ },
6957
+ exists: (path5) => existsSync(path5)
6958
+ };
6959
+ }
6960
+ var MSI_LAUNCHER_LINE = /^\s*"%~dp0\\\.\.\\python\.exe"\s+-IBm\s+azure\.cli\s+%\*\s*$/im;
6961
+ function resolveAzureCli(probes = defaultProbes()) {
6962
+ const plain = { file: "az", prefixArgs: [] };
6963
+ if (probes.platform !== "win32") return plain;
6964
+ for (const raw of (probes.pathEnv ?? "").split(";")) {
6965
+ const dir = raw.trim().replace(/^"(.*)"$/, "$1");
6966
+ if (!dir || !win32.isAbsolute(dir)) continue;
6967
+ const launcher = probes.readText(win32.join(dir, "az.cmd"));
6968
+ if (launcher === void 0) continue;
6969
+ if (!MSI_LAUNCHER_LINE.test(launcher)) return plain;
6970
+ const interpreter = win32.resolve(dir, "..", "python.exe");
6971
+ if (!probes.exists(interpreter)) return plain;
6972
+ return { file: interpreter, prefixArgs: ["-IBm", "azure.cli"], env: { AZ_INSTALLER: "MSI" } };
6973
+ }
6974
+ return plain;
6975
+ }
6976
+
6977
+ // src/modules/state/pull-request.ts
6978
+ function parseAdoRemote(url) {
6979
+ const https = /^https?:\/\/(?:[^@/]*@)?dev\.azure\.com\/([^/]+)\/([^/]+)\/_git\/([^/?#]+)/i.exec(url);
6980
+ if (https) return { org: https[1], project: decodeURIComponent(https[2]), repo: decodeURIComponent(https[3]) };
6981
+ const legacy = /^https?:\/\/(?:[^@/]*@)?([^./]+)\.visualstudio\.com\/(?:DefaultCollection\/)?([^/]+)\/_git\/([^/?#]+)/i.exec(url);
6982
+ if (legacy) return { org: legacy[1], project: decodeURIComponent(legacy[2]), repo: decodeURIComponent(legacy[3]) };
6983
+ const ssh = /^(?:ssh:\/\/)?git@(?:ssh\.|vs-ssh\.)?(?:dev\.azure\.com|visualstudio\.com)[:/]v3\/([^/]+)\/([^/]+)\/([^/?#]+)/i.exec(url);
6984
+ if (ssh) return { org: ssh[1], project: decodeURIComponent(ssh[2]), repo: decodeURIComponent(ssh[3]) };
6985
+ return null;
6986
+ }
6987
+ function describe2(error) {
6988
+ if (error instanceof Error) {
6989
+ const code = error.code;
6990
+ if (code === "ENOENT") return "the CLI is not installed";
6991
+ if (error.killed) return `timed out after ${EXTERNAL_CLI_TIMEOUT_MS}ms`;
6992
+ return error.message.split("\n")[0];
6993
+ }
6994
+ return String(error);
6995
+ }
6996
+ function isRecord5(value) {
6997
+ return typeof value === "object" && value !== null && !Array.isArray(value);
6998
+ }
6999
+ async function lookupGitHub(branch, exec) {
7000
+ let stdout;
7001
+ try {
7002
+ ({ stdout } = await exec("gh", ["pr", "list", "--head", branch, "--state", "open", "--json", "number,url,state"], EXTERNAL_CLI_TIMEOUT_MS));
7003
+ } catch (error) {
7004
+ return { pull_request: null, warnings: [`git.pull_request: gh pr list failed (${describe2(error)})`] };
7005
+ }
7006
+ let parsed;
7007
+ try {
7008
+ parsed = JSON.parse(stdout);
7009
+ } catch {
7010
+ return { pull_request: null, warnings: ["git.pull_request: gh pr list printed something other than JSON"] };
7011
+ }
7012
+ if (!Array.isArray(parsed)) return { pull_request: null, warnings: ["git.pull_request: gh pr list printed something other than a list"] };
7013
+ const first = parsed.find(isRecord5);
7014
+ if (!first) return { pull_request: null, warnings: [] };
7015
+ if (typeof first["number"] !== "number" || typeof first["url"] !== "string" || typeof first["state"] !== "string") {
7016
+ return { pull_request: null, warnings: ["git.pull_request: gh pr list printed an entry without number, url and state"] };
7017
+ }
7018
+ return { pull_request: { id: first["number"], url: first["url"], state: first["state"] }, warnings: [] };
7019
+ }
7020
+ async function lookupAdo(branch, remoteUrl, exec, launch) {
7021
+ const coordinates = parseAdoRemote(remoteUrl);
7022
+ if (!coordinates) {
7023
+ return { pull_request: null, warnings: ["git.pull_request: the Azure DevOps remote URL names no organization, project and repository"] };
7024
+ }
7025
+ const orgUrl = `https://dev.azure.com/${coordinates.org}`;
7026
+ let stdout;
7027
+ try {
7028
+ ({ stdout } = await exec(
7029
+ launch.file,
7030
+ [
7031
+ ...launch.prefixArgs,
7032
+ "repos",
7033
+ "pr",
7034
+ "list",
7035
+ "--source-branch",
7036
+ branch,
7037
+ "--status",
7038
+ "active",
7039
+ "--org",
7040
+ orgUrl,
7041
+ "--project",
7042
+ coordinates.project,
7043
+ "--repository",
7044
+ coordinates.repo,
7045
+ "--output",
7046
+ "json"
7047
+ ],
7048
+ EXTERNAL_CLI_TIMEOUT_MS,
7049
+ launch.env ? { env: launch.env } : {}
7050
+ ));
7051
+ } catch (error) {
7052
+ return { pull_request: null, warnings: [`git.pull_request: az repos pr list failed (${describe2(error)})`] };
7053
+ }
7054
+ let parsed;
7055
+ try {
7056
+ parsed = JSON.parse(stdout);
7057
+ } catch {
7058
+ return { pull_request: null, warnings: ["git.pull_request: az repos pr list printed something other than JSON"] };
7059
+ }
7060
+ if (!Array.isArray(parsed)) return { pull_request: null, warnings: ["git.pull_request: az repos pr list printed something other than a list"] };
7061
+ const first = parsed.find(isRecord5);
7062
+ if (!first) return { pull_request: null, warnings: [] };
7063
+ const id = first["pullRequestId"];
7064
+ const status = first["status"];
7065
+ if (typeof id !== "number" || typeof status !== "string") {
7066
+ return { pull_request: null, warnings: ["git.pull_request: az repos pr list printed an entry without pullRequestId and status"] };
7067
+ }
7068
+ return {
7069
+ pull_request: {
7070
+ id,
7071
+ url: `${orgUrl}/${encodeURIComponent(coordinates.project)}/_git/${encodeURIComponent(coordinates.repo)}/pullrequest/${id}`,
7072
+ state: status
7073
+ },
7074
+ warnings: []
7075
+ };
7076
+ }
7077
+ async function collectPullRequest(git, options = {}) {
7078
+ const exec = options.exec ?? execFileWithTimeout;
7079
+ if (git.remote === null) return { pull_request: null, warnings: [] };
7080
+ if (git.branch === null || git.detached) {
7081
+ return { pull_request: null, warnings: ["git.pull_request: no branch is checked out, so there is nothing to look up"] };
7082
+ }
7083
+ switch (git.remote.provider) {
7084
+ case "github":
7085
+ return lookupGitHub(git.branch, exec);
7086
+ case "ado":
7087
+ return lookupAdo(git.branch, git.remote.url, exec, options.azureLaunch ?? resolveAzureCli());
7088
+ default:
7089
+ return { pull_request: null, warnings: [`git.pull_request: no pull-request lookup for a ${git.remote.provider} remote`] };
7090
+ }
7091
+ }
7092
+
7093
+ // src/modules/state/registry.ts
7094
+ import { readFile as readFile16 } from "fs/promises";
7095
+ import { hostname } from "os";
7096
+ import { join as join18 } from "path";
7097
+ var DEFAULT_BUSINESS_ROOT = "ventures";
7098
+ function isRecord6(value) {
7099
+ return typeof value === "object" && value !== null && !Array.isArray(value);
7100
+ }
7101
+ function stringOrNull2(value) {
7102
+ return typeof value === "string" ? value : null;
7103
+ }
7104
+ function localPathFor(clones, machine) {
7105
+ if (!isRecord6(clones)) return null;
7106
+ const wanted = machine.toLowerCase();
7107
+ for (const [key, value] of Object.entries(clones)) {
7108
+ if (key.toLowerCase() === wanted) return typeof value === "string" ? value : null;
7109
+ }
7110
+ return null;
7111
+ }
7112
+ function repoEntry(raw, options, machine) {
7113
+ const repo = isRecord6(raw) ? raw : {};
7114
+ const entry = {
7115
+ name: stringOrNull2(repo["name"]),
7116
+ remote: stringOrNull2(repo["remote"]),
7117
+ default: repo["default"] === true
7118
+ };
7119
+ if (options.includeLocalPaths) entry.local_path = localPathFor(repo["clones"], machine);
7120
+ return entry;
7121
+ }
7122
+ function ventureEntry(slug, raw, options, machine) {
7123
+ const venture = isRecord6(raw) ? raw : {};
7124
+ const aliases = Array.isArray(venture["aliases"]) ? venture["aliases"].filter((a) => typeof a === "string") : [];
7125
+ const repos = Array.isArray(venture["repos"]) ? venture["repos"].map((repo) => repoEntry(repo, options, machine)) : [];
7126
+ return {
7127
+ slug,
7128
+ name: stringOrNull2(venture["name"]),
7129
+ aliases,
7130
+ status: stringOrNull2(venture["status"]),
7131
+ visibility: stringOrNull2(venture["visibility"]),
7132
+ ownership: stringOrNull2(venture["ownership"]),
7133
+ tracking: venture["tracking"] ?? null,
7134
+ repos
7135
+ };
7136
+ }
7137
+ function parseRegistry(raw, options) {
7138
+ const machine = options.hostname ?? hostname();
7139
+ const registry = isRecord6(raw) ? raw : {};
7140
+ const ventures = [];
7141
+ const source = registry["ventures"];
7142
+ if (Array.isArray(source)) {
7143
+ for (const entry of source) {
7144
+ if (isRecord6(entry) && typeof entry["slug"] === "string") ventures.push(ventureEntry(entry["slug"], entry, options, machine));
7145
+ }
7146
+ } else if (isRecord6(source)) {
7147
+ for (const [slug, entry] of Object.entries(source)) ventures.push(ventureEntry(slug, entry, options, machine));
7148
+ }
7149
+ ventures.sort((a, b) => a.slug < b.slug ? -1 : a.slug > b.slug ? 1 : 0);
7150
+ return { updated: stringOrNull2(registry["updated"]), ventures };
7151
+ }
7152
+ async function collectRegistry(root, manifest, options) {
7153
+ if (manifest.role !== "hub") return { registry: null, warnings: [] };
7154
+ const businessRoot = manifest.business_root ?? DEFAULT_BUSINESS_ROOT;
7155
+ const relativePath = `${businessRoot}/registry.json`;
7156
+ let content;
7157
+ try {
7158
+ content = await readFile16(join18(root, businessRoot, "registry.json"), "utf8");
7159
+ } catch (error) {
7160
+ const reason = error.code === "ENOENT" ? "is missing" : `could not be read (${error instanceof Error ? error.message : String(error)})`;
7161
+ return { registry: null, warnings: [`registry: this repository is a hub but ${relativePath} ${reason}`] };
7162
+ }
7163
+ let raw;
7164
+ try {
7165
+ raw = JSON.parse(content);
7166
+ } catch (error) {
7167
+ return {
7168
+ registry: null,
7169
+ warnings: [`registry: ${relativePath} is not valid JSON (${error instanceof Error ? error.message : String(error)})`]
7170
+ };
7171
+ }
7172
+ return { registry: parseRegistry(raw, options), warnings: [] };
7173
+ }
7174
+
7175
+ // src/modules/state/types.ts
7176
+ var STATE_SCHEMA_VERSION = "1.0.0";
7177
+
7178
+ // src/modules/state/collect.ts
7179
+ async function guarded(section, warnings, run, fallback) {
7180
+ try {
7181
+ const result = await run();
7182
+ warnings.push(...result.warnings);
7183
+ return result.value;
7184
+ } catch (error) {
7185
+ if (error instanceof AgentConfigValidationError) throw error;
7186
+ warnings.push(`${section}: collector failed (${error instanceof Error ? error.message : String(error)}); the section is null`);
7187
+ return fallback;
7188
+ }
7189
+ }
7190
+ async function collectState(cwd, packageVersion, options) {
7191
+ const manifest = await readManifest(cwd);
7192
+ const root = resolveRealPath(cwd);
7193
+ const warnings = [];
7194
+ const resolvedScope = resolveInstallScope(cwd, manifest.scope, false);
7195
+ if (resolvedScope.misplacedUserManifest) warnings.push(`repository.scope: ${misplacedUserManifestMessage(cwd)}`);
7196
+ const versions = await guarded(
7197
+ "versions",
7198
+ warnings,
7199
+ async () => {
7200
+ const result = await collectVersions(manifest, packageVersion, {
7201
+ online: options.online,
7202
+ ...options.feedUrl === void 0 ? {} : { feedUrl: options.feedUrl },
7203
+ ...options.cachePath === void 0 ? {} : { cachePath: options.cachePath }
7204
+ });
7205
+ return { value: result.versions, warnings: result.warnings };
7206
+ },
7207
+ {
7208
+ files: manifest.version,
7209
+ cli: packageVersion,
7210
+ latest: null,
7211
+ drift: { files_behind_cli: false, cli_behind_latest: null },
7212
+ components: []
7213
+ }
7214
+ );
7215
+ const git = await guarded(
7216
+ "git",
7217
+ warnings,
7218
+ async () => {
7219
+ const result = await collectGit(cwd, root, options.runGit === void 0 ? {} : { runGit: options.runGit });
7220
+ return { value: result.git, warnings: result.warnings };
7221
+ },
7222
+ null
7223
+ );
7224
+ if (options.online && git !== null) {
7225
+ git.pull_request = await guarded(
7226
+ "git.pull_request",
7227
+ warnings,
7228
+ async () => {
7229
+ const result = await collectPullRequest(git, options.exec === void 0 ? {} : { exec: options.exec });
7230
+ return { value: result.pull_request, warnings: result.warnings };
7231
+ },
7232
+ null
7233
+ );
7234
+ }
7235
+ const decisions = await guarded(
7236
+ "indexes.decisions",
7237
+ warnings,
7238
+ async () => {
7239
+ const result = await collectDecisions(root);
7240
+ return { value: result.decisions, warnings: result.warnings };
7241
+ },
7242
+ null
7243
+ );
7244
+ const book2 = (fileName) => guarded(
7245
+ `indexes.${fileName}`,
7246
+ warnings,
7247
+ async () => {
7248
+ const result = await collectBook(root, fileName);
7249
+ return { value: result.book, warnings: result.warnings };
7250
+ },
7251
+ null
7252
+ );
7253
+ const todos = await book2("TODO.md");
7254
+ const ideas = await book2("IDEAS.md");
7255
+ const done = await book2("DONE.md");
7256
+ const journal = await guarded(
7257
+ "indexes.journal",
7258
+ warnings,
7259
+ async () => {
7260
+ const result = await collectJournal(root);
7261
+ return { value: result.journal, warnings: result.warnings };
7262
+ },
7263
+ null
7264
+ );
7265
+ const registry = await guarded(
7266
+ "registry",
7267
+ warnings,
7268
+ async () => {
7269
+ const result = await collectRegistry(root, manifest, {
7270
+ includeLocalPaths: options.includeLocalPaths,
7271
+ ...options.hostname === void 0 ? {} : { hostname: options.hostname }
7272
+ });
7273
+ return { value: result.registry, warnings: result.warnings };
7274
+ },
7275
+ null
7276
+ );
7277
+ const features = await guarded(
7278
+ "features",
7279
+ warnings,
7280
+ async () => {
7281
+ const result = await collectFeatures(root);
7282
+ return { value: result.features, warnings: result.warnings };
7283
+ },
7284
+ null
7285
+ );
7286
+ const agents = await guarded(
7287
+ "agents",
7288
+ warnings,
7289
+ async () => {
7290
+ const result = await collectAgents(cwd, options.homeDir === void 0 ? {} : { homeDir: options.homeDir });
7291
+ return { value: result.agents, warnings: result.warnings };
7292
+ },
7293
+ null
7294
+ );
7295
+ const now = options.now ?? (() => /* @__PURE__ */ new Date());
7296
+ return {
7297
+ schema_version: STATE_SCHEMA_VERSION,
7298
+ generated_at: now().toISOString(),
7299
+ generator: { name: "arcane-cli", version: packageVersion },
7300
+ repository: {
7301
+ root,
7302
+ scope: resolvedScope.scope,
7303
+ role: manifest.role ?? "consumer",
7304
+ profile: manifest.profile
7305
+ },
7306
+ versions,
7307
+ git,
7308
+ indexes: { decisions, todos, ideas, done, journal },
7309
+ config: collectConfig(manifest),
7310
+ registry,
7311
+ features,
7312
+ agents,
7313
+ warnings
7314
+ };
7315
+ }
7316
+
7317
+ // src/commands/state.ts
7318
+ var USAGE_POINTER = "spell state prints one JSON document to stdout: run `spell state --json` (spell status is the human view).";
7319
+ function writeStdout(text) {
7320
+ return new Promise((resolve10, reject) => {
7321
+ process.stdout.write(text, (error) => error ? reject(error) : resolve10());
7322
+ });
7323
+ }
7324
+ async function runState(targetDir, packageVersion, options = {}, deps = {}) {
7325
+ if (!options.json) {
7326
+ console.error(USAGE_POINTER);
7327
+ process.exitCode = 2;
7328
+ return;
7329
+ }
7330
+ const collect = deps.collect ?? collectState;
7331
+ let text;
7332
+ try {
7333
+ const doc = await collect(targetDir, packageVersion, {
7334
+ online: options.online === true,
7335
+ includeLocalPaths: options.includeLocalPaths === true
7336
+ });
7337
+ text = serializeState(doc);
7338
+ } catch (error) {
7339
+ if (error instanceof ManifestNotFoundError) {
7340
+ console.error('Not initialized. Run "spell init" first.');
7341
+ } else if (error instanceof AgentConfigValidationError) {
7342
+ console.error(error.message);
7343
+ } else {
7344
+ console.error(`spell state: ${error instanceof Error ? error.message : String(error)}`);
7345
+ }
7346
+ process.exitCode = 1;
7347
+ return;
7348
+ }
7349
+ try {
7350
+ await (deps.write ?? writeStdout)(text);
7351
+ } catch (error) {
7352
+ console.error(`spell state: stdout is not writable (${error instanceof Error ? error.message : String(error)})`);
7353
+ process.exitCode = 1;
7354
+ }
7355
+ }
7356
+
6255
7357
  // src/commands/uninstall.ts
6256
7358
  import { rm as rm5 } from "fs/promises";
6257
- import { dirname as dirname10, join as join13, resolve as resolve6 } from "path";
7359
+ import { dirname as dirname10, join as join19, resolve as resolve6 } from "path";
6258
7360
  import { confirm as confirm3 } from "@inquirer/prompts";
6259
7361
  async function runUninstall(options, targetDir) {
6260
7362
  let manifest;
@@ -6326,9 +7428,9 @@ Run \`spell unblock-push\` first (interactive), then uninstall.`
6326
7428
  }
6327
7429
  let removed = 0;
6328
7430
  let leftBehindLinks = 0;
6329
- const skip = (path4, err) => {
7431
+ const skip = (path5, err) => {
6330
7432
  if (err instanceof LinkedDirectoryError) leftBehindLinks++;
6331
- console.log(` ! Skipped ${path4}: ${skipReason(err, targetDir)}`);
7433
+ console.log(` ! Skipped ${path5}: ${skipReason(err, targetDir)}`);
6332
7434
  };
6333
7435
  for (const component of manifest.components) {
6334
7436
  for (const file of component.files) {
@@ -6340,7 +7442,7 @@ Run \`spell unblock-push\` first (interactive), then uninstall.`
6340
7442
  }
6341
7443
  }
6342
7444
  }
6343
- await rm5(join13(targetDir, ".arcane.json"), { force: true });
7445
+ await rm5(join19(targetDir, ".arcane.json"), { force: true });
6344
7446
  const agentOutputs = [
6345
7447
  ".github/agents",
6346
7448
  ".arcane/agents",
@@ -6351,10 +7453,10 @@ Run \`spell unblock-push\` first (interactive), then uninstall.`
6351
7453
  try {
6352
7454
  validateRealTargetPath(targetDir, output);
6353
7455
  } catch (err) {
6354
- if (await fileExists(join13(targetDir, output))) skip(output, err);
7456
+ if (await fileExists(join19(targetDir, output))) skip(output, err);
6355
7457
  continue;
6356
7458
  }
6357
- await rm5(join13(targetDir, output), { recursive: true, force: true });
7459
+ await rm5(join19(targetDir, output), { recursive: true, force: true });
6358
7460
  }
6359
7461
  const markerFiles = [
6360
7462
  "CLAUDE.md",
@@ -6432,10 +7534,10 @@ async function uninstallUserTier(options, storeRoot, manifest) {
6432
7534
  console.log(` ! Skipped ${file}: ${err instanceof Error ? err.message : String(err)}`);
6433
7535
  }
6434
7536
  }
6435
- await rm5(join13(storeRoot, ".arcane.json"), { force: true });
6436
- await rm5(join13(storeRoot, "agents"), { recursive: true, force: true });
6437
- await rm5(join13(storeRoot, "agents.yaml"), { force: true });
6438
- await removeDirectoryIfEmpty(join13(storeRoot, USER_TIER_SPELLS_DIR));
7537
+ await rm5(join19(storeRoot, ".arcane.json"), { force: true });
7538
+ await rm5(join19(storeRoot, "agents"), { recursive: true, force: true });
7539
+ await rm5(join19(storeRoot, "agents.yaml"), { force: true });
7540
+ await removeDirectoryIfEmpty(join19(storeRoot, USER_TIER_SPELLS_DIR));
6439
7541
  await removeDirectoryIfEmpty(storeRoot);
6440
7542
  for (const line of describeFanoutOutcomes(fanout.outcomes)) console.log(` ${line}`);
6441
7543
  const pruned = fanout.outcomes.filter((o) => o.status === "pruned").length;
@@ -6447,8 +7549,8 @@ async function uninstallUserTier(options, storeRoot, manifest) {
6447
7549
  }
6448
7550
 
6449
7551
  // src/commands/doctor.ts
6450
- import { access as access4, readdir as readdir9, readFile as readFile13, mkdir as mkdir7, copyFile as fsCopyFile } from "fs/promises";
6451
- import { join as join16, dirname as dirname11, resolve as resolve7 } from "path";
7552
+ import { access as access4, readdir as readdir11, readFile as readFile19, mkdir as mkdir7, copyFile as fsCopyFile } from "fs/promises";
7553
+ import { join as join22, dirname as dirname11, resolve as resolve7 } from "path";
6452
7554
 
6453
7555
  // src/config/incidents.ts
6454
7556
  var INCIDENT_QUEUE = [
@@ -6516,8 +7618,8 @@ function evaluateIncidentGate(records) {
6516
7618
  }
6517
7619
 
6518
7620
  // src/modules/denylist-scan.ts
6519
- import { readFile as readFile11, readdir as readdir7 } from "fs/promises";
6520
- import { join as join14, relative } from "path";
7621
+ import { readFile as readFile17, readdir as readdir9 } from "fs/promises";
7622
+ import { join as join20, relative } from "path";
6521
7623
  function escapeRegExp(value) {
6522
7624
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
6523
7625
  }
@@ -6534,7 +7636,7 @@ async function scanFile(absolutePath, displayPath, rules) {
6534
7636
  const findings = [];
6535
7637
  let content;
6536
7638
  try {
6537
- content = await readFile11(absolutePath, "utf8");
7639
+ content = await readFile17(absolutePath, "utf8");
6538
7640
  } catch {
6539
7641
  return findings;
6540
7642
  }
@@ -6563,12 +7665,12 @@ async function collectScannableFiles(root, repoRoot = root, extensions = DEFAULT
6563
7665
  const collected = [];
6564
7666
  let entries;
6565
7667
  try {
6566
- entries = await readdir7(root, { withFileTypes: true });
7668
+ entries = await readdir9(root, { withFileTypes: true });
6567
7669
  } catch {
6568
7670
  return collected;
6569
7671
  }
6570
7672
  for (const entry of entries) {
6571
- const absolutePath = join14(root, entry.name);
7673
+ const absolutePath = join20(root, entry.name);
6572
7674
  if (entry.isDirectory()) {
6573
7675
  if (SKIP_DIRECTORIES.has(entry.name)) continue;
6574
7676
  collected.push(...await collectScannableFiles(absolutePath, repoRoot, extensions));
@@ -6589,65 +7691,6 @@ async function scanRepository(repoRoot, rules, extensions, excludePrefixes = [])
6589
7691
  return findings;
6590
7692
  }
6591
7693
 
6592
- // src/modules/exec.ts
6593
- import { execFile as execFile2 } from "child_process";
6594
- import { existsSync, readFileSync } from "fs";
6595
- import { win32 } from "path";
6596
- var EXTERNAL_CLI_TIMEOUT_MS = 1e4;
6597
- function execFileWithTimeout(file, args, timeoutMs, options = {}) {
6598
- return new Promise((resolve10, reject) => {
6599
- const child = execFile2(
6600
- file,
6601
- args,
6602
- {
6603
- timeout: timeoutMs,
6604
- killSignal: "SIGTERM",
6605
- windowsHide: true,
6606
- encoding: "utf8",
6607
- ...options.env ? { env: { ...process.env, ...options.env } } : {}
6608
- },
6609
- (error, stdout, stderr) => {
6610
- if (error) {
6611
- reject(error);
6612
- return;
6613
- }
6614
- resolve10({ stdout, stderr });
6615
- }
6616
- );
6617
- child.stdin?.end();
6618
- });
6619
- }
6620
- function defaultProbes() {
6621
- return {
6622
- platform: process.platform,
6623
- pathEnv: process.env["PATH"] ?? process.env["Path"],
6624
- readText: (path4) => {
6625
- try {
6626
- return readFileSync(path4, "utf8");
6627
- } catch {
6628
- return void 0;
6629
- }
6630
- },
6631
- exists: (path4) => existsSync(path4)
6632
- };
6633
- }
6634
- var MSI_LAUNCHER_LINE = /^\s*"%~dp0\\\.\.\\python\.exe"\s+-IBm\s+azure\.cli\s+%\*\s*$/im;
6635
- function resolveAzureCli(probes = defaultProbes()) {
6636
- const plain = { file: "az", prefixArgs: [] };
6637
- if (probes.platform !== "win32") return plain;
6638
- for (const raw of (probes.pathEnv ?? "").split(";")) {
6639
- const dir = raw.trim().replace(/^"(.*)"$/, "$1");
6640
- if (!dir || !win32.isAbsolute(dir)) continue;
6641
- const launcher = probes.readText(win32.join(dir, "az.cmd"));
6642
- if (launcher === void 0) continue;
6643
- if (!MSI_LAUNCHER_LINE.test(launcher)) return plain;
6644
- const interpreter = win32.resolve(dir, "..", "python.exe");
6645
- if (!probes.exists(interpreter)) return plain;
6646
- return { file: interpreter, prefixArgs: ["-IBm", "azure.cli"], env: { AZ_INSTALLER: "MSI" } };
6647
- }
6648
- return plain;
6649
- }
6650
-
6651
7694
  // src/modules/platform-policy.ts
6652
7695
  var SANCTIONED_MERGE_METHODS = ["merge", "rebase"];
6653
7696
  function compareToLadder(effective, declaredButBlocked, interactionNote) {
@@ -6815,7 +7858,7 @@ function decodeAdoSegment(raw) {
6815
7858
  if (!decoded || decoded.startsWith("-")) return null;
6816
7859
  return decoded;
6817
7860
  }
6818
- function parseAdoRemote(url) {
7861
+ function parseAdoRemote2(url) {
6819
7862
  const match = /dev\.azure\.com\/([^/]+)\/([^/]+)\/_git\/([^/?]+)/.exec(url) ?? /([^./]+)\.visualstudio\.com\/([^/]+)\/_git\/([^/?]+)/.exec(url);
6820
7863
  if (!match) return null;
6821
7864
  const [, rawOrg, rawProject, rawRepo] = match;
@@ -6828,8 +7871,8 @@ function parseAdoRemote(url) {
6828
7871
  }
6829
7872
 
6830
7873
  // src/modules/placeholders.ts
6831
- import { readdir as readdir8, readFile as readFile12 } from "fs/promises";
6832
- import { join as join15 } from "path";
7874
+ import { readdir as readdir10, readFile as readFile18 } from "fs/promises";
7875
+ import { join as join21 } from "path";
6833
7876
  var PLACEHOLDER_STANDARD_FILE = "spell-authoring-standards.md";
6834
7877
  var RUNTIME_PLACEHOLDERS_START = "<!-- runtime-placeholders:start -->";
6835
7878
  var RUNTIME_PLACEHOLDERS_END = "<!-- runtime-placeholders:end -->";
@@ -6924,7 +7967,7 @@ function parseRuntimePlaceholders(standardContent) {
6924
7967
  async function scanGovernancePlaceholders(governanceDir) {
6925
7968
  let standard;
6926
7969
  try {
6927
- standard = await readFile12(join15(governanceDir, PLACEHOLDER_STANDARD_FILE), "utf8");
7970
+ standard = await readFile18(join21(governanceDir, PLACEHOLDER_STANDARD_FILE), "utf8");
6928
7971
  } catch (err) {
6929
7972
  if (err.code === "ENOENT") {
6930
7973
  return { status: "skipped", reason: `${PLACEHOLDER_STANDARD_FILE} is not installed` };
@@ -6938,12 +7981,12 @@ async function scanGovernancePlaceholders(governanceDir) {
6938
7981
  reason: `${PLACEHOLDER_STANDARD_FILE} has no runtime-placeholders list (an older version?)`
6939
7982
  };
6940
7983
  }
6941
- const files = (await readdir8(governanceDir, { withFileTypes: true })).filter((entry) => entry.isFile() && entry.name.endsWith(".md")).map((entry) => entry.name).sort();
7984
+ const files = (await readdir10(governanceDir, { withFileTypes: true })).filter((entry) => entry.isFile() && entry.name.endsWith(".md")).map((entry) => entry.name).sort();
6942
7985
  const activeDocs = [];
6943
7986
  const templateDocs = [];
6944
7987
  const findings = [];
6945
7988
  for (const file of files) {
6946
- const content = await readFile12(join15(governanceDir, file), "utf8");
7989
+ const content = await readFile18(join21(governanceDir, file), "utf8");
6947
7990
  const status = parseFrontmatterStatus(content);
6948
7991
  if (status === "template") templateDocs.push(file);
6949
7992
  if (status !== "active") continue;
@@ -6955,7 +7998,7 @@ async function scanGovernancePlaceholders(governanceDir) {
6955
7998
  }
6956
7999
 
6957
8000
  // src/commands/doctor.ts
6958
- function isRecord3(value) {
8001
+ function isRecord7(value) {
6959
8002
  return typeof value === "object" && value !== null && !Array.isArray(value);
6960
8003
  }
6961
8004
  async function checkNodeVersion() {
@@ -6974,14 +8017,14 @@ async function checkNodeVersion() {
6974
8017
  async function checkVSCodeExtension(id, label2, homeDir = resolveHomeDir()) {
6975
8018
  const name = `VS Code extension: ${label2}`;
6976
8019
  const flavours = [
6977
- { dir: join16(homeDir, ".vscode", "extensions"), bin: "code" },
6978
- { dir: join16(homeDir, ".vscode-insiders", "extensions"), bin: "code-insiders" }
8020
+ { dir: join22(homeDir, ".vscode", "extensions"), bin: "code" },
8021
+ { dir: join22(homeDir, ".vscode-insiders", "extensions"), bin: "code-insiders" }
6979
8022
  ];
6980
8023
  let installBin = null;
6981
8024
  for (const flavour of flavours) {
6982
8025
  let entries;
6983
8026
  try {
6984
- entries = await readdir9(flavour.dir);
8027
+ entries = await readdir11(flavour.dir);
6985
8028
  } catch {
6986
8029
  continue;
6987
8030
  }
@@ -7003,15 +8046,15 @@ async function checkVSCodeExtension(id, label2, homeDir = resolveHomeDir()) {
7003
8046
  }
7004
8047
  async function checkArcaneManifest(targetDir) {
7005
8048
  const name = "Arcane manifest (.arcane.json)";
7006
- const manifestPath2 = join16(targetDir, ".arcane.json");
8049
+ const manifestPath2 = join22(targetDir, ".arcane.json");
7007
8050
  try {
7008
8051
  await access4(manifestPath2);
7009
8052
  } catch {
7010
- const selfHostedManifestPath = join16(targetDir, "src", "assets", ".arcane.json");
8053
+ const selfHostedManifestPath = join22(targetDir, "src", "assets", ".arcane.json");
7011
8054
  try {
7012
- const raw = await readFile13(selfHostedManifestPath, "utf8");
8055
+ const raw = await readFile19(selfHostedManifestPath, "utf8");
7013
8056
  const manifest = JSON.parse(raw);
7014
- if (isRecord3(manifest) && manifest["selfHosted"] === true && manifest["tracking_mode"] === "internal") {
8057
+ if (isRecord7(manifest) && manifest["selfHosted"] === true && manifest["tracking_mode"] === "internal") {
7015
8058
  return {
7016
8059
  name,
7017
8060
  passed: true,
@@ -7028,9 +8071,9 @@ async function checkArcaneManifest(targetDir) {
7028
8071
  };
7029
8072
  }
7030
8073
  try {
7031
- const raw = await readFile13(manifestPath2, "utf8");
8074
+ const raw = await readFile19(manifestPath2, "utf8");
7032
8075
  const manifest = JSON.parse(raw);
7033
- if (!isRecord3(manifest) || typeof manifest["version"] !== "string" || !manifest["version"] || !Array.isArray(manifest["components"])) {
8076
+ if (!isRecord7(manifest) || typeof manifest["version"] !== "string" || !manifest["version"] || !Array.isArray(manifest["components"])) {
7034
8077
  return {
7035
8078
  name,
7036
8079
  passed: false,
@@ -7113,7 +8156,7 @@ async function checkSessionContinuity(targetDir) {
7113
8156
  const results = [];
7114
8157
  for (const file of SESSION_CONTINUITY_FILES) {
7115
8158
  const name = `Session file: ${file}`;
7116
- const filePath = join16(targetDir, file);
8159
+ const filePath = join22(targetDir, file);
7117
8160
  try {
7118
8161
  await access4(filePath);
7119
8162
  results.push({ name, passed: true, message: "present" });
@@ -7131,7 +8174,7 @@ async function checkSessionContinuity(targetDir) {
7131
8174
  async function fixSessionContinuity(targetDir, assetsDir) {
7132
8175
  const created = [];
7133
8176
  for (const file of SESSION_CONTINUITY_FILES) {
7134
- const destPath = join16(targetDir, file);
8177
+ const destPath = join22(targetDir, file);
7135
8178
  try {
7136
8179
  await access4(destPath);
7137
8180
  } catch {
@@ -7142,7 +8185,7 @@ async function fixSessionContinuity(targetDir, assetsDir) {
7142
8185
  );
7143
8186
  continue;
7144
8187
  }
7145
- const srcPath = join16(assetsDir, file);
8188
+ const srcPath = join22(assetsDir, file);
7146
8189
  await mkdir7(dirname11(destPath), { recursive: true });
7147
8190
  await fsCopyFile(srcPath, destPath);
7148
8191
  created.push(file);
@@ -7270,7 +8313,7 @@ async function checkGovernancePlaceholders(targetDir) {
7270
8313
  const name = "Governance placeholders (ARC-051)";
7271
8314
  let scan;
7272
8315
  try {
7273
- scan = await scanGovernancePlaceholders(join16(targetDir, ".arcane", "governance"));
8316
+ scan = await scanGovernancePlaceholders(join22(targetDir, ".arcane", "governance"));
7274
8317
  } catch (err) {
7275
8318
  return {
7276
8319
  name,
@@ -7357,10 +8400,10 @@ async function checkNewlyAvailableComponents(targetDir) {
7357
8400
  }
7358
8401
  async function checkMcpConfig(targetDir) {
7359
8402
  const name = "MCP config (per-server timeout, I12)";
7360
- const filePath = join16(targetDir, ".mcp.json");
8403
+ const filePath = join22(targetDir, ".mcp.json");
7361
8404
  let raw;
7362
8405
  try {
7363
- raw = await readFile13(filePath, "utf8");
8406
+ raw = await readFile19(filePath, "utf8");
7364
8407
  } catch {
7365
8408
  return { name, passed: true, blocking: false, message: "no .mcp.json \u2014 nothing to check" };
7366
8409
  }
@@ -7375,7 +8418,7 @@ async function checkMcpConfig(targetDir) {
7375
8418
  message: ".mcp.json exists but contains invalid JSON"
7376
8419
  };
7377
8420
  }
7378
- if (!isRecord3(rawParsed)) {
8421
+ if (!isRecord7(rawParsed)) {
7379
8422
  return {
7380
8423
  name,
7381
8424
  passed: false,
@@ -7433,17 +8476,17 @@ async function checkMcpConfig(targetDir) {
7433
8476
  };
7434
8477
  }
7435
8478
  function isDelegation(value) {
7436
- return isRecord3(value) && typeof value["id"] === "string" && typeof value["status"] === "string" && typeof value["scope"] === "string" && Array.isArray(value["excludedActions"]);
8479
+ return isRecord7(value) && typeof value["id"] === "string" && typeof value["status"] === "string" && typeof value["scope"] === "string" && Array.isArray(value["excludedActions"]);
7437
8480
  }
7438
8481
  function isDelegationsFile(value) {
7439
- return isRecord3(value) && Array.isArray(value["delegations"]) && value["delegations"].every(isDelegation);
8482
+ return isRecord7(value) && Array.isArray(value["delegations"]) && value["delegations"].every(isDelegation);
7440
8483
  }
7441
8484
  async function checkDelegations(targetDir) {
7442
8485
  const name = "Standing delegations (T13)";
7443
- const filePath = join16(targetDir, ".arcane", "delegations.json");
8486
+ const filePath = join22(targetDir, ".arcane", "delegations.json");
7444
8487
  let raw;
7445
8488
  try {
7446
- raw = await readFile13(filePath, "utf8");
8489
+ raw = await readFile19(filePath, "utf8");
7447
8490
  } catch {
7448
8491
  return { name, passed: true, blocking: false, message: "no delegations recorded" };
7449
8492
  }
@@ -7501,7 +8544,7 @@ async function checkPlatformBranchPolicy(targetDir) {
7501
8544
  const result = evaluateGitHubMergePolicy(rulesets);
7502
8545
  return { name, passed: result.passed, blocking: false, message: result.message };
7503
8546
  }
7504
- const ado = parseAdoRemote(url);
8547
+ const ado = parseAdoRemote2(url);
7505
8548
  if (ado) {
7506
8549
  const policies = await fetchAdoMergeTypePolicies(ado.org, ado.project, ado.repo, "main");
7507
8550
  if (policies === null) {
@@ -7543,7 +8586,7 @@ async function checkManagedFileModes(targetDir) {
7543
8586
  if (!isClientShimPath(file) || asLinks.includes(file)) continue;
7544
8587
  let body;
7545
8588
  try {
7546
- body = await readFile13(join16(targetDir, file), "utf-8");
8589
+ body = await readFile19(join22(targetDir, file), "utf-8");
7547
8590
  } catch {
7548
8591
  continue;
7549
8592
  }
@@ -7594,19 +8637,19 @@ async function checkSpellScope(targetDir, homeDir = resolveHomeDir()) {
7594
8637
  }
7595
8638
  let spellCount = 0;
7596
8639
  let probed = false;
7597
- const probe = "spell-status.md";
8640
+ const probe2 = "spell-status.md";
7598
8641
  try {
7599
- const entries = await readdir9(join16(storeRoot, USER_TIER_SPELLS_DIR));
8642
+ const entries = await readdir11(join22(storeRoot, USER_TIER_SPELLS_DIR));
7600
8643
  const spells = entries.filter((e) => e.startsWith("spell-") && e.endsWith(".md"));
7601
8644
  spellCount = spells.length;
7602
- probed = spells.includes(probe);
8645
+ probed = spells.includes(probe2);
7603
8646
  } catch {
7604
8647
  }
7605
8648
  if (spellCount === 0 || !probed) {
7606
8649
  return {
7607
8650
  name,
7608
8651
  passed: false,
7609
- message: `the user tier at ${storeRoot} holds ${spellCount} spell(s)${spellCount > 0 && !probed ? ` and not ${probe}` : ""} \u2014 this repository expects it to supply every spell. Run \`spell update --user\`.`
8652
+ message: `the user tier at ${storeRoot} holds ${spellCount} spell(s)${spellCount > 0 && !probed ? ` and not ${probe2}` : ""} \u2014 this repository expects it to supply every spell. Run \`spell update --user\`.`
7610
8653
  };
7611
8654
  }
7612
8655
  const majorMinor = (v) => v.split(".").slice(0, 2).join(".");
@@ -7630,7 +8673,7 @@ async function checkSpellScope(targetDir, homeDir = resolveHomeDir()) {
7630
8673
  name,
7631
8674
  passed: true,
7632
8675
  blocking: false,
7633
- message: `user \u2014 ${spellCount} spell(s) from ${storeRoot} (v${store.version}), ${probe} present${repoExpectsAgents ? ", agent roster present in the tier" : ""}`
8676
+ message: `user \u2014 ${spellCount} spell(s) from ${storeRoot} (v${store.version}), ${probe2} present${repoExpectsAgents ? ", agent roster present in the tier" : ""}`
7634
8677
  };
7635
8678
  }
7636
8679
  async function runDoctor(targetDir, options = {}, assetsDir) {
@@ -7714,16 +8757,16 @@ async function runDoctor(targetDir, options = {}, assetsDir) {
7714
8757
  }
7715
8758
 
7716
8759
  // src/commands/ward.ts
7717
- import { readFile as readFile15 } from "fs/promises";
8760
+ import { readFile as readFile21 } from "fs/promises";
7718
8761
 
7719
8762
  // src/modules/ward.ts
7720
- import { readdir as readdir10, readFile as readFile14 } from "fs/promises";
7721
- import { join as join17, relative as relative2 } from "path";
8763
+ import { readdir as readdir12, readFile as readFile20 } from "fs/promises";
8764
+ import { join as join23, relative as relative2 } from "path";
7722
8765
  async function scanFileAllRules(absolutePath, displayPath, rules) {
7723
8766
  const findings = [];
7724
8767
  let content;
7725
8768
  try {
7726
- content = await readFile14(absolutePath, "utf8");
8769
+ content = await readFile20(absolutePath, "utf8");
7727
8770
  } catch {
7728
8771
  return findings;
7729
8772
  }
@@ -7776,12 +8819,12 @@ async function scanFilenames(root, rules, repoRoot = root) {
7776
8819
  const findings = [];
7777
8820
  let entries;
7778
8821
  try {
7779
- entries = await readdir10(root, { withFileTypes: true });
8822
+ entries = await readdir12(root, { withFileTypes: true });
7780
8823
  } catch {
7781
8824
  return findings;
7782
8825
  }
7783
8826
  for (const entry of entries) {
7784
- const absolutePath = join17(root, entry.name);
8827
+ const absolutePath = join23(root, entry.name);
7785
8828
  if (entry.isDirectory()) {
7786
8829
  if (SKIP_DIRECTORIES.has(entry.name)) continue;
7787
8830
  findings.push(...await scanFilenames(absolutePath, rules, repoRoot));
@@ -7818,7 +8861,7 @@ async function runWard(repoRoot, options = {}) {
7818
8861
  const findings2 = await scanFileAllRules(absolutePath, displayPath, allRules);
7819
8862
  if (findings2.length > 0) {
7820
8863
  try {
7821
- fileLines.set(displayPath, (await readFile14(absolutePath, "utf8")).split("\n"));
8864
+ fileLines.set(displayPath, (await readFile20(absolutePath, "utf8")).split("\n"));
7822
8865
  } catch {
7823
8866
  }
7824
8867
  }
@@ -7846,7 +8889,7 @@ function parseTermsFileContent(raw) {
7846
8889
  }
7847
8890
  async function readTermsFile(filePath) {
7848
8891
  try {
7849
- return parseTermsFileContent(await readFile15(filePath, "utf8"));
8892
+ return parseTermsFileContent(await readFile21(filePath, "utf8"));
7850
8893
  } catch (error) {
7851
8894
  console.warn(
7852
8895
  `\u26A0 arcane: could not read --terms-file "${filePath}": ${error.message}`
@@ -8057,7 +9100,7 @@ async function runBlockPush(targetDir) {
8057
9100
  }
8058
9101
 
8059
9102
  // src/modules/credential-store.ts
8060
- import { createHash as createHash2, randomBytes } from "crypto";
9103
+ import { createHash as createHash4, randomBytes } from "crypto";
8061
9104
  var CHUNK_SIZE = 1e3;
8062
9105
  var MAX_CHUNKS = 64;
8063
9106
  var INDEX_NAME = "session/index";
@@ -8071,7 +9114,7 @@ var KeychainUnavailableError = class extends Error {
8071
9114
  }
8072
9115
  };
8073
9116
  function sha256Hex(value) {
8074
- return createHash2("sha256").update(value, "utf8").digest("hex");
9117
+ return createHash4("sha256").update(value, "utf8").digest("hex");
8075
9118
  }
8076
9119
  function chunkName(gen, n) {
8077
9120
  return `${CHUNK_PREFIX}${gen}/${n}`;
@@ -8215,17 +9258,17 @@ import { execFile as execFile3 } from "child_process";
8215
9258
  import * as client from "openid-client";
8216
9259
 
8217
9260
  // src/modules/result-pages.ts
8218
- import { createHash as createHash3 } from "crypto";
8219
- import { readFile as readFile16 } from "fs/promises";
8220
- import { join as join18 } from "path";
9261
+ import { createHash as createHash5 } from "crypto";
9262
+ import { readFile as readFile22 } from "fs/promises";
9263
+ import { join as join24 } from "path";
8221
9264
  var RESULT_KINDS = ["success", "denied", "failure"];
8222
9265
  function resultPagePath(kind) {
8223
- return join18("sign-in", `sign-in-callback-${kind}.html`);
9266
+ return join24("sign-in", `sign-in-callback-${kind}.html`);
8224
9267
  }
8225
9268
  function styleHashes(html) {
8226
9269
  return [...html.matchAll(/<style\b[^>]*>([\s\S]*?)<\/style\s*>/gi)].map((match) => {
8227
9270
  const text = match[1].replace(/\r\n?/g, "\n");
8228
- return `'sha256-${createHash3("sha256").update(text, "utf8").digest("base64")}'`;
9271
+ return `'sha256-${createHash5("sha256").update(text, "utf8").digest("base64")}'`;
8229
9272
  });
8230
9273
  }
8231
9274
  function resultPageHeaders(html) {
@@ -8268,7 +9311,7 @@ var PLAIN_RESULT_PAGES = {
8268
9311
  async function loadResultPages(assetsDir) {
8269
9312
  try {
8270
9313
  const [success, denied, failure] = await Promise.all(
8271
- RESULT_KINDS.map(async (kind) => resultPage(await readFile16(join18(assetsDir, resultPagePath(kind)), "utf8")))
9314
+ RESULT_KINDS.map(async (kind) => resultPage(await readFile22(join24(assetsDir, resultPagePath(kind)), "utf8")))
8272
9315
  );
8273
9316
  return { success, denied, failure };
8274
9317
  } catch {
@@ -8480,7 +9523,7 @@ function openInDefaultBrowser(url) {
8480
9523
  import { execFileSync } from "child_process";
8481
9524
  import { mkdirSync, readFileSync as readFileSync2, renameSync, rmSync, writeFileSync, existsSync as existsSync2 } from "fs";
8482
9525
  import { userInfo } from "os";
8483
- import { dirname as dirname12, join as join19 } from "path";
9526
+ import { dirname as dirname12, join as join25 } from "path";
8484
9527
 
8485
9528
  // src/modules/keychain-backend.ts
8486
9529
  import { createRequire as createRequire2 } from "module";
@@ -8614,7 +9657,7 @@ function restrictToCurrentUser(filePath) {
8614
9657
  });
8615
9658
  }
8616
9659
  function insecureSessionFilePath(homeDir) {
8617
- return join19(userTierRoot(homeDir), INSECURE_SESSION_FILE_NAME);
9660
+ return join25(userTierRoot(homeDir), INSECURE_SESSION_FILE_NAME);
8618
9661
  }
8619
9662
  function createSessionStorage(options = {}) {
8620
9663
  const keychain = new ChunkedSecretStore(options.keychainBackend ?? createKeychainBackend());
@@ -8882,15 +9925,15 @@ async function runWhoami(options, deps = defaultWhoamiDeps()) {
8882
9925
 
8883
9926
  // src/commands/report.ts
8884
9927
  import { access as access5 } from "fs/promises";
8885
- import { join as join22 } from "path";
9928
+ import { join as join28 } from "path";
8886
9929
 
8887
9930
  // src/modules/show-report/generate.ts
8888
- import { mkdir as mkdir8, readdir as readdir11, readFile as readFile18, writeFile as writeFile11 } from "fs/promises";
8889
- import { basename as basename4, dirname as dirname13, isAbsolute, join as join21, relative as relative3, resolve as resolve9 } from "path";
9931
+ import { mkdir as mkdir8, readdir as readdir13, readFile as readFile24, writeFile as writeFile11 } from "fs/promises";
9932
+ import { basename as basename4, dirname as dirname13, isAbsolute, join as join27, relative as relative3, resolve as resolve9 } from "path";
8890
9933
 
8891
9934
  // src/modules/show-report/model.ts
8892
- import { readFile as readFile17 } from "fs/promises";
8893
- import { join as join20 } from "path";
9935
+ import { readFile as readFile23 } from "fs/promises";
9936
+ import { join as join26 } from "path";
8894
9937
 
8895
9938
  // src/modules/show-report/plan-parser.ts
8896
9939
  function parseFrontmatter(content) {
@@ -9076,28 +10119,6 @@ function stripMarkdown2(text) {
9076
10119
  return text.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/\*\*/g, "").replace(/`/g, "").trim();
9077
10120
  }
9078
10121
 
9079
- // src/modules/show-report/decisions-parser.ts
9080
- function parseAdrs(decisionsContent) {
9081
- const headingPattern = /^## ARC-(\d{3})\b/gm;
9082
- const positions = [];
9083
- let match;
9084
- while ((match = headingPattern.exec(decisionsContent)) !== null) {
9085
- positions.push({ id: `ARC-${match[1]}`, index: match.index });
9086
- }
9087
- return positions.map(({ id, index }, i) => {
9088
- const end = i + 1 < positions.length ? positions[i + 1].index : decisionsContent.length;
9089
- const section = decisionsContent.slice(index, end);
9090
- const dateMatch = /^\*\*Date:\*\*\s*(\d{4}-\d{2}-\d{2})/m.exec(section);
9091
- const statusMatch = /^\*\*Status:\*\*\s*(\S+)/m.exec(section);
9092
- return { id, date: dateMatch ? dateMatch[1] : null, status: statusMatch ? statusMatch[1] : null };
9093
- });
9094
- }
9095
- function countAcceptedAdrsInWindow(adrs, fromDate, toDate) {
9096
- return adrs.filter(
9097
- (adr) => adr.status === "Accepted" && adr.date !== null && adr.date >= fromDate && adr.date <= toDate
9098
- ).length;
9099
- }
9100
-
9101
10122
  // src/modules/show-report/sources.ts
9102
10123
  async function runGitTextOrNull(cwd, args) {
9103
10124
  try {
@@ -9161,9 +10182,9 @@ async function getCast(cwd, fromRef, toRef, generatedOutputs = []) {
9161
10182
  }
9162
10183
 
9163
10184
  // src/modules/show-report/model.ts
9164
- async function readOptional(path4) {
10185
+ async function readOptional(path5) {
9165
10186
  try {
9166
- return await readFile17(path4, "utf8");
10187
+ return await readFile23(path5, "utf8");
9167
10188
  } catch {
9168
10189
  return null;
9169
10190
  }
@@ -9259,13 +10280,13 @@ function buildCorrections(ledgerRows, programName) {
9259
10280
  };
9260
10281
  }
9261
10282
  async function buildShowReportModel(options) {
9262
- const planAbsPath = join20(options.rootDir, options.planRelPath);
9263
- const queueAbsPath = join20(options.rootDir, options.queueRelPath);
10283
+ const planAbsPath = join26(options.rootDir, options.planRelPath);
10284
+ const queueAbsPath = join26(options.rootDir, options.queueRelPath);
9264
10285
  const ledgerRelPath = "docs/verification-ledger.md";
9265
- const ledgerAbsPath = join20(options.rootDir, ledgerRelPath);
9266
- const decisionsAbsPath = join20(options.rootDir, "DECISIONS.md");
10286
+ const ledgerAbsPath = join26(options.rootDir, ledgerRelPath);
10287
+ const decisionsAbsPath = join26(options.rootDir, "DECISIONS.md");
9267
10288
  const [planContent, queueContent, ledgerContent, decisionsContent] = await Promise.all([
9268
- readFile17(planAbsPath, "utf8"),
10289
+ readFile23(planAbsPath, "utf8"),
9269
10290
  readOptional(queueAbsPath),
9270
10291
  readOptional(ledgerAbsPath),
9271
10292
  readOptional(decisionsAbsPath)
@@ -9455,13 +10476,13 @@ function programNameFromTitle(title) {
9455
10476
  function deterministicCompiledAt(frontmatter) {
9456
10477
  return frontmatter.completed ?? frontmatter.activated ?? frontmatter.created ?? "unknown";
9457
10478
  }
9458
- function toPosix(path4) {
9459
- return path4.replace(/\\/g, "/");
10479
+ function toPosix(path5) {
10480
+ return path5.replace(/\\/g, "/");
9460
10481
  }
9461
10482
  async function programFromPlan(rootDir, slug, planRelPath) {
9462
10483
  let planContent;
9463
10484
  try {
9464
- planContent = await readFile18(join21(rootDir, planRelPath), "utf8");
10485
+ planContent = await readFile24(join27(rootDir, planRelPath), "utf8");
9465
10486
  } catch {
9466
10487
  return { skipped: { slug, reason: `no PLAN.md at ${planRelPath}` } };
9467
10488
  }
@@ -9473,16 +10494,16 @@ async function programFromPlan(rootDir, slug, planRelPath) {
9473
10494
  program: {
9474
10495
  slug,
9475
10496
  planRelPath,
9476
- queueRelPath: toPosix(join21(dirname13(planRelPath), "OPERATOR-QUEUE.md")),
10497
+ queueRelPath: toPosix(join27(dirname13(planRelPath), "OPERATOR-QUEUE.md")),
9477
10498
  ledgerProgramName: programNameFromTitle(frontmatter.title ?? slug)
9478
10499
  }
9479
10500
  };
9480
10501
  }
9481
10502
  async function discoverPrograms(rootDir) {
9482
- const plansDir = join21(rootDir, "docs", "plans");
10503
+ const plansDir = join27(rootDir, "docs", "plans");
9483
10504
  let entries;
9484
10505
  try {
9485
- entries = (await readdir11(plansDir, { withFileTypes: true })).filter((e) => e.isDirectory());
10506
+ entries = (await readdir13(plansDir, { withFileTypes: true })).filter((e) => e.isDirectory());
9486
10507
  } catch (error) {
9487
10508
  if (error.code === "ENOENT") return { programs: [], skipped: [] };
9488
10509
  throw error;
@@ -9504,16 +10525,16 @@ async function programFromPlanPath(rootDir, planPath) {
9504
10525
  function normalizeLineEndings2(content) {
9505
10526
  return content.replace(/\r\n?/g, "\n");
9506
10527
  }
9507
- async function readOptionalFile(path4) {
10528
+ async function readOptionalFile(path5) {
9508
10529
  try {
9509
- return await readFile18(path4, "utf8");
10530
+ return await readFile24(path5, "utf8");
9510
10531
  } catch (error) {
9511
10532
  if (error.code === "ENOENT") return null;
9512
10533
  throw error;
9513
10534
  }
9514
10535
  }
9515
10536
  async function generateReport(rootDir, program2, templateHtml, theme = "auto") {
9516
- const planContent = await readFile18(join21(rootDir, program2.planRelPath), "utf8");
10537
+ const planContent = await readFile24(join27(rootDir, program2.planRelPath), "utf8");
9517
10538
  const frontmatter = parseFrontmatter(planContent);
9518
10539
  const model = await buildShowReportModel({
9519
10540
  rootDir,
@@ -9533,7 +10554,7 @@ async function generateReport(rootDir, program2, templateHtml, theme = "auto") {
9533
10554
  };
9534
10555
  }
9535
10556
  async function runReportGeneration(options) {
9536
- const templateHtml = await readFile18(options.templatePath, "utf8");
10557
+ const templateHtml = await readFile24(options.templatePath, "utf8");
9537
10558
  const theme = options.theme ?? "auto";
9538
10559
  const drifted = [];
9539
10560
  const repaired = [];
@@ -9551,12 +10572,12 @@ async function runReportGeneration(options) {
9551
10572
  [`${outRel}/show-report.html`, generated.html]
9552
10573
  ];
9553
10574
  for (const [relPath, expected] of outputs) {
9554
- const actual = await readOptionalFile(join21(options.rootDir, relPath));
10575
+ const actual = await readOptionalFile(join27(options.rootDir, relPath));
9555
10576
  if (actual === null || normalizeLineEndings2(actual) !== normalizeLineEndings2(expected)) {
9556
10577
  drifted.push(`${relPath} does not match a regeneration from its sources.`);
9557
10578
  if (options.mode === "write") {
9558
10579
  await mkdir8(outAbs, { recursive: true });
9559
- await writeFile11(join21(options.rootDir, relPath), expected, "utf8");
10580
+ await writeFile11(join27(options.rootDir, relPath), expected, "utf8");
9560
10581
  repaired.push(relPath);
9561
10582
  }
9562
10583
  }
@@ -9567,7 +10588,7 @@ async function runReportGeneration(options) {
9567
10588
 
9568
10589
  // src/commands/report.ts
9569
10590
  async function runReport(targetDir, options = {}, assetsDir) {
9570
- const templatePath = join22(assetsDir, "report", "show-report.template.html");
10591
+ const templatePath = join28(assetsDir, "report", "show-report.template.html");
9571
10592
  try {
9572
10593
  await access5(templatePath);
9573
10594
  } catch {
@@ -9637,14 +10658,14 @@ async function runReport(targetDir, options = {}, assetsDir) {
9637
10658
  return;
9638
10659
  }
9639
10660
  console.log(`Show report: wrote ${result.repaired.length} file(s).`);
9640
- for (const path4 of result.repaired) console.log(` ${path4}`);
10661
+ for (const path5 of result.repaired) console.log(` ${path5}`);
9641
10662
  }
9642
10663
 
9643
10664
  // src/index.ts
9644
10665
  import { realpathSync as realpathSync2 } from "fs";
9645
10666
  var require3 = createRequire3(import.meta.url);
9646
10667
  var pkg = require3("../package.json");
9647
- var ASSETS_DIR = process.env["ARCANE_ASSETS_DIR"] ?? join23(dirname14(fileURLToPath(import.meta.url)), "assets");
10668
+ var ASSETS_DIR = process.env["ARCANE_ASSETS_DIR"] ?? join29(dirname14(fileURLToPath(import.meta.url)), "assets");
9648
10669
  var program = new Command();
9649
10670
  var USER_FLAG_DESCRIPTION = "Operate on the per-user tier at ~/.arcane \u2014 spells shared by every repository on this machine, fanned out to ~/.agents/skills (Codex, Copilot) and ~/.claude/commands (Claude Code)";
9650
10671
  var USER_TIER_AGENTS_DESCRIPTION = "Operate on the per-user tier at ~/.arcane \u2014 one agent roster shared by every repository on this machine, rendered to ~/.copilot/agents (VS Code agent modes)";
@@ -9758,6 +10779,15 @@ withManifestFlags(
9758
10779
  program.command("status").description("Show installed components and check for updates").option("--user", USER_FLAG_DESCRIPTION).action(async (opts) => {
9759
10780
  await runStatus(targetDirFor(opts), pkg.version, { user: opts.user });
9760
10781
  });
10782
+ program.command("state").description("Export this repository's Arcane state as one JSON document (schema https://arcaneai.net/schemas/state/v1)").option("--json", "Print the document to stdout (required; `spell status` is the human view)").option(
10783
+ "--online",
10784
+ "Also look up the latest published arcane-cli version and this branch's open pull request (gh / az). Off by default: a plain run makes no network request"
10785
+ ).option(
10786
+ "--include-local-paths",
10787
+ "Hub only: add each registered repository's clone path on this machine. Other machines' paths are never exported"
10788
+ ).action(async (opts) => {
10789
+ await runState(process.cwd(), pkg.version, opts);
10790
+ });
9761
10791
  program.command("uninstall").description("Remove all installed Arcane framework files").option("--yes", "Skip confirmation prompt").option("--dry-run", "Preview what would be removed without deleting files").option("--user", USER_FLAG_DESCRIPTION).action(async (opts) => {
9762
10792
  await runUninstall({ yes: opts.yes, dryRun: opts.dryRun, user: opts.user }, targetDirFor(opts));
9763
10793
  });