bisque-cli 0.2.2 → 0.3.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.
Files changed (2) hide show
  1. package/dist/index.js +1247 -191
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -776,9 +776,279 @@ import { createRequire } from "node:module";
776
776
  import { Command, Option as Option3 } from "commander";
777
777
 
778
778
  // src/commands/add.ts
779
- init_api_client();
780
779
  import { readFile as readFile2 } from "node:fs/promises";
781
780
  import { basename } from "node:path";
781
+
782
+ // src/codebase-sync/provenance.ts
783
+ var MAX_CODE_PATHS = 50;
784
+ var MAX_PATH_LENGTH = 256;
785
+ var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
786
+ // JS / TS
787
+ "js",
788
+ "jsx",
789
+ "mjs",
790
+ "cjs",
791
+ "ts",
792
+ "tsx",
793
+ "mts",
794
+ "cts",
795
+ // Other languages
796
+ "py",
797
+ "rb",
798
+ "php",
799
+ "go",
800
+ "rs",
801
+ "java",
802
+ "kt",
803
+ "kts",
804
+ "scala",
805
+ "swift",
806
+ "dart",
807
+ "lua",
808
+ "r",
809
+ "ex",
810
+ "exs",
811
+ "erl",
812
+ "clj",
813
+ "cljs",
814
+ // C family
815
+ "c",
816
+ "h",
817
+ "cc",
818
+ "cpp",
819
+ "cxx",
820
+ "hpp",
821
+ "hh",
822
+ "m",
823
+ "mm",
824
+ "cs",
825
+ // Shell / query / schema
826
+ "sh",
827
+ "bash",
828
+ "zsh",
829
+ "sql",
830
+ "graphql",
831
+ "gql",
832
+ "proto",
833
+ // Config / data
834
+ "json",
835
+ "jsonc",
836
+ "json5",
837
+ "yaml",
838
+ "yml",
839
+ "toml",
840
+ "ini",
841
+ "xml",
842
+ "tf",
843
+ "gradle",
844
+ // Web
845
+ "css",
846
+ "scss",
847
+ "sass",
848
+ "less",
849
+ "html",
850
+ "vue",
851
+ "svelte",
852
+ "astro",
853
+ // Docs
854
+ "md",
855
+ "mdx"
856
+ ]);
857
+ function stripWrappers(s) {
858
+ return s.replace(/^['"`<]+/, "").replace(/['"`>]+$/, "");
859
+ }
860
+ function normalizePath(raw) {
861
+ let p = stripWrappers(raw.trim());
862
+ if (!p) return null;
863
+ p = p.replace(/\\/g, "/");
864
+ p = p.replace(/[#?].*$/, "");
865
+ p = p.replace(/^\.\//, "");
866
+ p = p.replace(/^\/+/, "");
867
+ p = p.replace(/\/{2,}/g, "/");
868
+ p = p.replace(/\/+$/, "");
869
+ p = p.trim();
870
+ if (!p || p.length > MAX_PATH_LENGTH) return null;
871
+ return p;
872
+ }
873
+ function hasSourceExtension(p) {
874
+ const base = p.slice(p.lastIndexOf("/") + 1);
875
+ const dot = base.lastIndexOf(".");
876
+ if (dot <= 0) return false;
877
+ return SOURCE_EXTENSIONS.has(base.slice(dot + 1).toLowerCase());
878
+ }
879
+ function isExternalUrl(s) {
880
+ return /^[a-z][a-z0-9+.-]*:\/\//i.test(s) || s.startsWith("mailto:") || s.startsWith("//");
881
+ }
882
+ function dedupCap(paths) {
883
+ const seen = /* @__PURE__ */ new Set();
884
+ const out = [];
885
+ for (const p of paths) {
886
+ if (seen.has(p)) continue;
887
+ seen.add(p);
888
+ out.push(p);
889
+ if (out.length >= MAX_CODE_PATHS) break;
890
+ }
891
+ return out;
892
+ }
893
+ function splitList(raw) {
894
+ let t = raw.trim();
895
+ if (t.startsWith("[") && t.endsWith("]")) t = t.slice(1, -1);
896
+ return t.split(",").map((x) => stripWrappers(x.trim())).filter(Boolean);
897
+ }
898
+ function frontmatterEndLine(lines) {
899
+ if (lines.length === 0 || lines[0]?.trim() !== "---") return -1;
900
+ for (let i = 1; i < lines.length; i++) {
901
+ if (lines[i]?.trim() === "---") return i;
902
+ }
903
+ return -1;
904
+ }
905
+ function collectBlockList(lines, start, end) {
906
+ const items = [];
907
+ for (let j = start; j < end; j++) {
908
+ const itm = (lines[j] ?? "").match(/^\s*-\s+(.*)$/);
909
+ if (!itm) break;
910
+ const v = stripWrappers((itm[1] ?? "").trim());
911
+ if (v) items.push(v);
912
+ }
913
+ return items;
914
+ }
915
+ function collectFrontmatter(lines) {
916
+ const end = frontmatterEndLine(lines);
917
+ if (end < 0) return [];
918
+ for (let i = 1; i < end; i++) {
919
+ const m = (lines[i] ?? "").match(/^\s*codePaths\s*:\s*(.*)$/);
920
+ if (!m) continue;
921
+ const inline = (m[1] ?? "").trim();
922
+ return inline ? splitList(inline) : collectBlockList(lines, i + 1, end);
923
+ }
924
+ return [];
925
+ }
926
+ function collectSentinels(content) {
927
+ const out = [];
928
+ const re = /<!--\s*bisque-code-paths\s*:\s*([\s\S]*?)\s*-->/g;
929
+ let m = re.exec(content);
930
+ while (m !== null) {
931
+ out.push(...splitList(m[1] ?? ""));
932
+ m = re.exec(content);
933
+ }
934
+ return out;
935
+ }
936
+ function fenceTokenPath(token) {
937
+ const candidates = [];
938
+ const eq = token.lastIndexOf("=");
939
+ if (eq >= 0) candidates.push(token.slice(eq + 1));
940
+ const colon = token.lastIndexOf(":");
941
+ if (colon >= 0) candidates.push(token.slice(colon + 1));
942
+ candidates.push(token);
943
+ for (const cand of candidates) {
944
+ const p = normalizePath(cand);
945
+ if (p && hasSourceExtension(p)) return p;
946
+ }
947
+ return null;
948
+ }
949
+ function fenceInfoPaths(info) {
950
+ const out = [];
951
+ for (const token of info.split(/\s+/)) {
952
+ const p = fenceTokenPath(token);
953
+ if (p) out.push(p);
954
+ }
955
+ return out;
956
+ }
957
+ function scanLinks(line) {
958
+ const out = [];
959
+ const re = /\]\(\s*<?([^)\s>]+)>?[^)]*\)/g;
960
+ let m = re.exec(line);
961
+ while (m !== null) {
962
+ const target = m[1] ?? "";
963
+ if (!isExternalUrl(target) && !target.startsWith("#")) {
964
+ const p = normalizePath(target);
965
+ if (p && hasSourceExtension(p)) out.push(p);
966
+ }
967
+ m = re.exec(line);
968
+ }
969
+ return out;
970
+ }
971
+ function scanInline(line) {
972
+ const out = [];
973
+ const re = /`([^`\n]+)`/g;
974
+ let m = re.exec(line);
975
+ while (m !== null) {
976
+ const raw = (m[1] ?? "").trim();
977
+ if (raw && !/\s/.test(raw) && !isExternalUrl(raw)) {
978
+ const p = normalizePath(raw);
979
+ if (p && hasSourceExtension(p)) out.push(p);
980
+ }
981
+ m = re.exec(line);
982
+ }
983
+ return out;
984
+ }
985
+ function parseFenceMarker(line) {
986
+ const open = line.match(/^\s*(`{3,}|~{3,})(.*)$/);
987
+ if (!open) return null;
988
+ const marker = open[1] ?? "";
989
+ return { ch: marker[0] ?? "", len: marker.length, info: (open[2] ?? "").trim() };
990
+ }
991
+ function applyFenceMarker(marker, state) {
992
+ if (!state.inFence) {
993
+ state.inFence = true;
994
+ state.char = marker.ch;
995
+ state.len = marker.len;
996
+ return marker.info ? fenceInfoPaths(marker.info) : [];
997
+ }
998
+ if (marker.ch === state.char && marker.len >= state.len && marker.info === "") {
999
+ state.inFence = false;
1000
+ state.char = "";
1001
+ state.len = 0;
1002
+ }
1003
+ return [];
1004
+ }
1005
+ function scanBody(lines, startLine) {
1006
+ const fence = [];
1007
+ const link = [];
1008
+ const inline = [];
1009
+ const state = { char: "", len: 0, inFence: false };
1010
+ for (let i = startLine; i < lines.length; i++) {
1011
+ const line = lines[i] ?? "";
1012
+ const marker = parseFenceMarker(line);
1013
+ if (marker) {
1014
+ fence.push(...applyFenceMarker(marker, state));
1015
+ continue;
1016
+ }
1017
+ if (state.inFence) continue;
1018
+ link.push(...scanLinks(line));
1019
+ inline.push(...scanInline(line));
1020
+ }
1021
+ return { fence, link, inline };
1022
+ }
1023
+ function normalizeCodePaths(paths) {
1024
+ const normalized = [];
1025
+ for (const raw of paths) {
1026
+ const p = normalizePath(raw);
1027
+ if (p) normalized.push(p);
1028
+ }
1029
+ return dedupCap(normalized);
1030
+ }
1031
+ function extractCodePaths(content) {
1032
+ if (!content) return [];
1033
+ const lines = content.split(/\r?\n/);
1034
+ const declared = [...collectSentinels(content), ...collectFrontmatter(lines)];
1035
+ if (declared.length > 0) return normalizeCodePaths(declared);
1036
+ const fmEnd = frontmatterEndLine(lines);
1037
+ const body = scanBody(lines, fmEnd >= 0 ? fmEnd + 1 : 0);
1038
+ return dedupCap([...body.fence, ...body.link, ...body.inline]);
1039
+ }
1040
+ function resolveCodePaths(explicit, metadata, content) {
1041
+ if (explicit !== void 0) {
1042
+ const norm = normalizeCodePaths(explicit);
1043
+ return norm.length > 0 ? norm : void 0;
1044
+ }
1045
+ if (metadata && metadata["codePaths"] !== void 0) return void 0;
1046
+ const auto = extractCodePaths(content);
1047
+ return auto.length > 0 ? auto : void 0;
1048
+ }
1049
+
1050
+ // src/commands/add.ts
1051
+ init_api_client();
782
1052
  function slugify(name) {
783
1053
  return name.trim().replace(/\s+/g, "-").replace(/[^a-zA-Z0-9-_]/g, "");
784
1054
  }
@@ -829,14 +1099,20 @@ async function resolveContent(file, options) {
829
1099
  function parseDocIdList(val) {
830
1100
  return val.split(",").map((s) => s.trim()).filter(Boolean);
831
1101
  }
1102
+ function mergeCodePaths(metadata, options, content) {
1103
+ const codePaths = resolveCodePaths(options.codePaths, metadata, content);
1104
+ if (!codePaths) return metadata;
1105
+ return { ...metadata ?? {}, codePaths };
1106
+ }
832
1107
  function buildCreateBody(resolved, filepath, category, options, metadata) {
1108
+ const finalMetadata = mergeCodePaths(metadata, options, resolved.content);
833
1109
  return {
834
1110
  title: resolved.title,
835
1111
  filepath,
836
1112
  category,
837
1113
  tags: options.tags ?? [],
838
1114
  content: resolved.content,
839
- ...metadata && { metadata },
1115
+ ...finalMetadata && { metadata: finalMetadata },
840
1116
  // Freshness & Staleness v1 (T3). Pass through verbatim; backend
841
1117
  // enum gates `status`, supersedes array is accepted on create.
842
1118
  ...options.status !== void 0 && { status: options.status },
@@ -848,7 +1124,11 @@ function registerAddCommand(program2) {
848
1124
  "-t, --tags <tags>",
849
1125
  "Comma-separated tags",
850
1126
  (val) => val.split(",").map((t) => t.trim())
851
- ).option("--filepath <path>", "Explicit PARA filepath (overrides --project/--area/--resource)").option("--content <text>", "Inline content (skip file reading)").option("--title <name>", "Document title (required with --content)").option("--metadata <json>", "JSON metadata object").option("--status <status>", "Lifecycle state: live | superseded | archived. Defaults to live.").option(
1127
+ ).option("--filepath <path>", "Explicit PARA filepath (overrides --project/--area/--resource)").option("--content <text>", "Inline content (skip file reading)").option("--title <name>", "Document title (required with --content)").option("--metadata <json>", "JSON metadata object").option(
1128
+ "--code-paths <paths>",
1129
+ "Comma-separated source paths this doc describes (overrides auto-extraction)",
1130
+ parseDocIdList
1131
+ ).option("--status <status>", "Lifecycle state: live | superseded | archived. Defaults to live.").option(
852
1132
  "--supersedes <docIds>",
853
1133
  "Comma-separated doc UUIDs this doc supersedes. Optional on create.",
854
1134
  parseDocIdList
@@ -890,6 +1170,7 @@ function registerAddCommand(program2) {
890
1170
 
891
1171
  // src/agents-md/sections.ts
892
1172
  var KEYWORDS_SENTINEL = "<!-- bisque-keywords:mechanical -->";
1173
+ var STATUS_SENTINEL = "<!-- bisque-status:mechanical -->";
893
1174
  var CONTENT_EXTRACT_TARGET = 280;
894
1175
  var CONTENT_EXTRACT_HARD_MAX = 400;
895
1176
  function normalizeDir(dirPath) {
@@ -1073,6 +1354,45 @@ _(no keywords)_`;
1073
1354
  ${KEYWORDS_SENTINEL}
1074
1355
  ${bullets}`;
1075
1356
  }
1357
+ function asNonEmptyString(v) {
1358
+ if (typeof v !== "string") return void 0;
1359
+ const t = v.trim();
1360
+ return t.length > 0 ? t : void 0;
1361
+ }
1362
+ function toAbsoluteDate(v) {
1363
+ const s = asNonEmptyString(v);
1364
+ if (!s) return void 0;
1365
+ const m = /^(\d{4}-\d{2}-\d{2})/.exec(s);
1366
+ return m ? m[1] : s;
1367
+ }
1368
+ function toShortSha(v) {
1369
+ const s = asNonEmptyString(v);
1370
+ if (!s) return void 0;
1371
+ return s.length > 7 ? s.slice(0, 7) : s;
1372
+ }
1373
+ function toDriftCount(v) {
1374
+ const n = typeof v === "number" ? v : typeof v === "string" ? Number(v) : Number.NaN;
1375
+ if (!Number.isFinite(n)) return void 0;
1376
+ const i = Math.floor(n);
1377
+ return i >= 1 ? i : void 0;
1378
+ }
1379
+ function generateStatusSection(metadata) {
1380
+ const date = toAbsoluteDate(metadata?.["lastDistilledAt"]);
1381
+ const sha = toShortSha(metadata?.["codeWatermark"]);
1382
+ const count = toDriftCount(metadata?.["driftFlaggedCount"]);
1383
+ let line;
1384
+ if (!date) {
1385
+ line = "Context not yet synced.";
1386
+ } else {
1387
+ line = sha ? `Context synced ${date} @ ${sha}.` : `Context synced ${date}.`;
1388
+ if (count !== void 0) {
1389
+ line += ` ${count} ${count === 1 ? "doc" : "docs"} flagged for review.`;
1390
+ }
1391
+ }
1392
+ return `## Status
1393
+ ${STATUS_SENTINEL}
1394
+ ${line}`;
1395
+ }
1076
1396
 
1077
1397
  // src/agents-md/merge.ts
1078
1398
  var IF_WORKING_HERE_TITLE = "If you're working here";
@@ -1453,11 +1773,17 @@ function composeAgentsContent(dir, items) {
1453
1773
  });
1454
1774
  const existingDoc = items.find((d) => d.filepath === `${dir}AGENTS.md`);
1455
1775
  const header = generateHeader(dir);
1456
- const mechanicals = `${generateWhatsHereSection(dir, docs)}
1776
+ let mechanicals = `${generateWhatsHereSection(dir, docs)}
1457
1777
 
1458
1778
  ${generateSubdirectoriesSection(dir, docs)}
1459
1779
 
1460
1780
  ${generateKeywordsSection(inDirDocs)}`;
1781
+ const statusDoc = items.find((d) => d.filepath === `${dir}frontmind/status.md`);
1782
+ if (statusDoc) {
1783
+ mechanicals += `
1784
+
1785
+ ${generateStatusSection(statusDoc.metadata)}`;
1786
+ }
1461
1787
  const nextContent = mergeAgentsMd(existingDoc?.content ?? null, header, mechanicals);
1462
1788
  return nextContent;
1463
1789
  }
@@ -2222,9 +2548,418 @@ function registerDeleteCommand(program2) {
2222
2548
  });
2223
2549
  }
2224
2550
 
2225
- // src/commands/get.ts
2226
- import { writeFile as writeFile2 } from "node:fs/promises";
2227
- import { Option } from "commander";
2551
+ // src/commands/drift.ts
2552
+ import { readFile as readFile4 } from "node:fs/promises";
2553
+ import chalk3 from "chalk";
2554
+ import Table2 from "cli-table3";
2555
+
2556
+ // src/codebase-sync/drift-digest.ts
2557
+ var DIGEST_TOP_K = 25;
2558
+ var DIGEST_EVIDENCE_CAP = 3;
2559
+ var DIGEST_TOKEN_BUDGET = 2e3;
2560
+ function estimateTokens(value) {
2561
+ return Math.ceil(JSON.stringify(value).length / 4);
2562
+ }
2563
+ function buildDigest(ranked, opts) {
2564
+ const eligible = ranked.filter((d) => d.score > 0);
2565
+ const top = eligible.slice(0, DIGEST_TOP_K);
2566
+ const counts = {
2567
+ scored: ranked.length,
2568
+ forced: ranked.filter((d) => d.linkRot || d.breaking).length,
2569
+ shown: top.length,
2570
+ noProvenance: ranked.filter((d) => !d.hasProvenance).length
2571
+ };
2572
+ const makeRows = (evidenceCap) => top.map((d) => ({
2573
+ filepath: d.filepath,
2574
+ signal: d.signal,
2575
+ evidence: d.evidence.slice(0, evidenceCap)
2576
+ }));
2577
+ const digest = {
2578
+ schema: "bisque-drift/v1",
2579
+ project: opts.project,
2580
+ generatedAt: opts.generatedAt,
2581
+ watermark: { since: opts.watermarkSince, head: opts.headShort },
2582
+ lastDistilledAt: opts.lastDistilledAt,
2583
+ rows: makeRows(DIGEST_EVIDENCE_CAP),
2584
+ uncovered: opts.uncovered,
2585
+ counts
2586
+ };
2587
+ for (let cap = DIGEST_EVIDENCE_CAP - 1; cap >= 0 && estimateTokens(digest) > DIGEST_TOKEN_BUDGET; cap -= 1) {
2588
+ digest.rows = makeRows(cap);
2589
+ }
2590
+ return digest;
2591
+ }
2592
+
2593
+ // src/codebase-sync/drift-scoring.ts
2594
+ var DAY_MS = 24 * 60 * 60 * 1e3;
2595
+ var HALF_LIFE_MS = {
2596
+ inbox: 14 * DAY_MS,
2597
+ project: 180 * DAY_MS,
2598
+ area: 365 * DAY_MS,
2599
+ resource: null,
2600
+ archive: null,
2601
+ other: null
2602
+ };
2603
+ var LINK_ROT_WEIGHT = 2;
2604
+ var BREAKING_WEIGHT = 4;
2605
+ var UNCOVERED_CAP = 10;
2606
+ function getCodePaths(doc) {
2607
+ const raw = doc.metadata?.["codePaths"];
2608
+ if (!Array.isArray(raw)) return [];
2609
+ return raw.filter((p) => typeof p === "string" && p.length > 0);
2610
+ }
2611
+ function paraBucket(filepath) {
2612
+ const lower = filepath.toLowerCase();
2613
+ if (lower.startsWith("0-inbox/") || lower.startsWith("inbox/")) return "inbox";
2614
+ if (lower.startsWith("1-projects/") || lower.startsWith("projects/")) return "project";
2615
+ if (lower.startsWith("2-areas/") || lower.startsWith("areas/")) return "area";
2616
+ if (lower.startsWith("3-resources/") || lower.startsWith("resources/")) return "resource";
2617
+ if (lower.startsWith("4-archives/") || lower.startsWith("4-archive/") || lower.startsWith("archive/")) {
2618
+ return "archive";
2619
+ }
2620
+ return "other";
2621
+ }
2622
+ function isDatedRecordPath(filepath) {
2623
+ const lower = filepath.toLowerCase();
2624
+ if (lower.includes("backmind/chain-runs/")) return true;
2625
+ if (lower.includes("0-inbox/autonomous-decisions/")) return true;
2626
+ if (lower.includes("/tasks/") || lower.startsWith("tasks/")) return true;
2627
+ if (lower === "agents.md" || lower.endsWith("/agents.md")) return true;
2628
+ if (lower.endsWith("frontmind/status.md")) return true;
2629
+ if (lower.includes("backmind/designs/")) return true;
2630
+ if (lower.includes("backmind/reviews/")) return true;
2631
+ if (lower.includes("backmind/decisions/")) return true;
2632
+ if (lower.includes("backmind/research/")) return true;
2633
+ return false;
2634
+ }
2635
+ function timeDecayStaleness(doc, nowMs) {
2636
+ const halfLife = HALF_LIFE_MS[paraBucket(doc.filepath)] ?? null;
2637
+ if (halfLife === null) return 0;
2638
+ const lastDistilled = doc.metadata?.["lastDistilledAt"];
2639
+ const anchor = (typeof lastDistilled === "string" ? lastDistilled : void 0) ?? doc.updatedAt ?? doc.createdAt;
2640
+ if (!anchor) return 0;
2641
+ const tsMs = Date.parse(anchor);
2642
+ if (Number.isNaN(tsMs)) return 0;
2643
+ const dt = Math.max(0, nowMs - tsMs);
2644
+ return 1 - 0.5 ** (dt / halfLife);
2645
+ }
2646
+ function isVirtualDocPath(p) {
2647
+ const lower = p.toLowerCase();
2648
+ if (lower.startsWith("frontmind/") || lower.startsWith("backmind/")) return true;
2649
+ return /^[0-4]-(inbox|projects|areas|resources|archives?)\//.test(lower);
2650
+ }
2651
+ function detectLinkRot(codePaths, headFiles) {
2652
+ return codePaths.filter((p) => !isVirtualDocPath(p) && !headFiles.has(p));
2653
+ }
2654
+ function detectBreaking(codePaths, breakingTouched) {
2655
+ return codePaths.filter((p) => breakingTouched.has(p));
2656
+ }
2657
+ function computeChurn(codePaths, numstatByPath) {
2658
+ let total = 0;
2659
+ for (const p of codePaths) {
2660
+ const ns = numstatByPath.get(p);
2661
+ if (ns) total += ns.added + ns.deleted;
2662
+ }
2663
+ return total;
2664
+ }
2665
+ function scoreDoc(doc, ctx) {
2666
+ const codePaths = getCodePaths(doc);
2667
+ const hasProvenance = codePaths.length > 0;
2668
+ const linkRotPaths = hasProvenance ? detectLinkRot(codePaths, ctx.headFiles) : [];
2669
+ const breakingPaths = hasProvenance ? detectBreaking(codePaths, ctx.breakingTouched) : [];
2670
+ const staleness = timeDecayStaleness(doc, ctx.nowMs);
2671
+ const linkRot = linkRotPaths.length > 0;
2672
+ const breaking = breakingPaths.length > 0;
2673
+ const score = staleness + (linkRot ? LINK_ROT_WEIGHT : 0) + (breaking ? BREAKING_WEIGHT : 0);
2674
+ const signal = breaking ? "breaking" : linkRot ? "link-rot" : "time-decay";
2675
+ const evidence = breaking ? breakingPaths : linkRot ? linkRotPaths : [];
2676
+ const churn = ctx.churnEnabled && ctx.numstatByPath && !isDatedRecordPath(doc.filepath) ? computeChurn(codePaths, ctx.numstatByPath) : -1;
2677
+ return {
2678
+ filepath: doc.filepath,
2679
+ score,
2680
+ signal,
2681
+ evidence,
2682
+ hasProvenance,
2683
+ linkRot,
2684
+ breaking,
2685
+ staleness,
2686
+ churn
2687
+ };
2688
+ }
2689
+ function scoreAndRank(docs, ctx) {
2690
+ return docs.map((d) => scoreDoc(d, ctx)).sort((a, b) => b.score - a.score || a.filepath.localeCompare(b.filepath));
2691
+ }
2692
+ var DEFAULT_EXCLUDES = [
2693
+ // build / vendored output dirs
2694
+ "**/dist/**",
2695
+ "**/build/**",
2696
+ "**/node_modules/**",
2697
+ "**/.next/**",
2698
+ "**/coverage/**",
2699
+ "**/out/**",
2700
+ // lockfiles
2701
+ "pnpm-lock.yaml",
2702
+ "package-lock.json",
2703
+ "yarn.lock",
2704
+ "bun.lockb",
2705
+ "Cargo.lock",
2706
+ "poetry.lock",
2707
+ "Gemfile.lock",
2708
+ "composer.lock",
2709
+ "go.sum",
2710
+ // generated
2711
+ "*.min.js",
2712
+ "*.map",
2713
+ "*.snap"
2714
+ ];
2715
+ var REGEX_SPECIALS = /* @__PURE__ */ new Set([".", "+", "^", "$", "{", "}", "(", ")", "|", "[", "]", "\\"]);
2716
+ function expandStar(pat, i) {
2717
+ if (pat[i + 1] === "*") {
2718
+ if (pat[i + 2] === "/") return { frag: "(?:.*/)?", next: i + 3 };
2719
+ return { frag: ".*", next: i + 2 };
2720
+ }
2721
+ return { frag: "[^/]*", next: i + 1 };
2722
+ }
2723
+ function globBodyToRegex(pat) {
2724
+ let out = "";
2725
+ let i = 0;
2726
+ while (i < pat.length) {
2727
+ const c = pat[i] ?? "";
2728
+ if (c === "*") {
2729
+ const { frag, next } = expandStar(pat, i);
2730
+ out += frag;
2731
+ i = next;
2732
+ } else if (c === "?") {
2733
+ out += "[^/]";
2734
+ i += 1;
2735
+ } else {
2736
+ out += REGEX_SPECIALS.has(c) ? `\\${c}` : c;
2737
+ i += 1;
2738
+ }
2739
+ }
2740
+ return out;
2741
+ }
2742
+ function patternToRegex(pattern) {
2743
+ let pat = pattern.trim();
2744
+ if (!pat || pat.startsWith("#")) return null;
2745
+ if (pat.endsWith("/")) pat = pat.slice(0, -1);
2746
+ const anchored = pat.startsWith("/");
2747
+ if (anchored) pat = pat.slice(1);
2748
+ const body = globBodyToRegex(pat);
2749
+ const startsAnywhere = !anchored && !pat.includes("/");
2750
+ const prefix = startsAnywhere ? "^(?:.*/)?" : "^";
2751
+ return new RegExp(`${prefix}${body}(?:/.*)?$`);
2752
+ }
2753
+ function createIgnoreMatcher(bisqueignoreContent) {
2754
+ const lines = bisqueignoreContent ? bisqueignoreContent.split(/\r?\n/) : [];
2755
+ const regexes = [...DEFAULT_EXCLUDES, ...lines].map(patternToRegex).filter((r) => r !== null);
2756
+ return (path2) => regexes.some((re) => re.test(path2));
2757
+ }
2758
+ function buildUncoveredHint(addedPaths, allCodePaths, isIgnored, cap = UNCOVERED_CAP) {
2759
+ const candidates = [...new Set(addedPaths)].filter((p) => hasSourceExtension(p) && !allCodePaths.has(p) && !isIgnored(p)).sort((a, b) => a.localeCompare(b));
2760
+ const files = candidates.slice(0, cap);
2761
+ return { files, more: Math.max(0, candidates.length - cap) };
2762
+ }
2763
+
2764
+ // src/codebase-sync/git-drift.ts
2765
+ import { execFile } from "node:child_process";
2766
+ import { promisify } from "node:util";
2767
+ var execFileAsync = promisify(execFile);
2768
+ var RS = "";
2769
+ var US = "";
2770
+ var EOM = "";
2771
+ var PRETTY_FORMAT = `format:${RS}%H${US}%cd${US}%B${EOM}`;
2772
+ var DEFAULT_FIRST_RUN_COMMITS = 200;
2773
+ var GIT_MAX_BUFFER = 64 * 1024 * 1024;
2774
+ function isBreakingMessage(message) {
2775
+ const subject = (message.split(/\r?\n/, 1)[0] ?? "").trim();
2776
+ if (/^[a-zA-Z][a-zA-Z0-9]*(\([^)]*\))?!:/.test(subject)) return true;
2777
+ return /(^|\n)BREAKING[ -]CHANGE:/.test(message);
2778
+ }
2779
+ function normalizeGitPath(raw) {
2780
+ let p = raw.trim();
2781
+ if (p.startsWith('"') && p.endsWith('"') && p.length >= 2) p = p.slice(1, -1);
2782
+ p = p.replace(/^\.\//, "").replace(/\/{2,}/g, "/");
2783
+ return p.trim();
2784
+ }
2785
+ function resolveNumstatPath(field) {
2786
+ let f = field.trim();
2787
+ if (f.startsWith('"') && f.endsWith('"') && f.length >= 2) f = f.slice(1, -1);
2788
+ if (f.includes(" => ")) {
2789
+ const brace = f.match(/^(.*)\{(.*) => (.*)\}(.*)$/);
2790
+ if (brace) {
2791
+ f = `${brace[1] ?? ""}${brace[3] ?? ""}${brace[4] ?? ""}`;
2792
+ } else {
2793
+ const parts = f.split(" => ");
2794
+ f = parts[parts.length - 1] ?? f;
2795
+ }
2796
+ }
2797
+ return normalizeGitPath(f);
2798
+ }
2799
+ function parseLsTree(raw) {
2800
+ const set = /* @__PURE__ */ new Set();
2801
+ for (const line of raw.split(/\r?\n/)) {
2802
+ const p = normalizeGitPath(line);
2803
+ if (p) set.add(p);
2804
+ }
2805
+ return set;
2806
+ }
2807
+ function parseDiffLine(line) {
2808
+ const ns = line.match(/^(-|\d+)\t(-|\d+)\t(.+)$/);
2809
+ if (ns) {
2810
+ const path2 = resolveNumstatPath(ns[3] ?? "");
2811
+ if (!path2) return null;
2812
+ const added = ns[1] === "-" ? 0 : Number.parseInt(ns[1] ?? "0", 10);
2813
+ const deleted = ns[2] === "-" ? 0 : Number.parseInt(ns[2] ?? "0", 10);
2814
+ return { kind: "numstat", path: path2, added, deleted };
2815
+ }
2816
+ const created = line.match(/^ create mode \d+ (.+)$/);
2817
+ if (created) {
2818
+ const path2 = normalizeGitPath(created[1] ?? "");
2819
+ return path2 ? { kind: "created", path: path2 } : null;
2820
+ }
2821
+ return null;
2822
+ }
2823
+ function parseRecord(rec) {
2824
+ const eom = rec.indexOf(EOM);
2825
+ if (eom < 0) return null;
2826
+ const head = rec.slice(0, eom);
2827
+ const us1 = head.indexOf(US);
2828
+ const us2 = head.indexOf(US, us1 + 1);
2829
+ if (us1 < 0 || us2 < 0) return null;
2830
+ const sha = head.slice(0, us1).trim();
2831
+ const committerDate = head.slice(us1 + 1, us2).trim();
2832
+ const body = head.slice(us2 + 1);
2833
+ const touchedPaths = [];
2834
+ const added = [];
2835
+ const numstat = [];
2836
+ for (const line of rec.slice(eom + 1).split(/\r?\n/)) {
2837
+ const parsed = parseDiffLine(line);
2838
+ if (!parsed) continue;
2839
+ if (parsed.kind === "numstat") {
2840
+ touchedPaths.push(parsed.path);
2841
+ numstat.push({ path: parsed.path, added: parsed.added, deleted: parsed.deleted });
2842
+ } else {
2843
+ added.push(parsed.path);
2844
+ }
2845
+ }
2846
+ return {
2847
+ commit: {
2848
+ sha,
2849
+ shortSha: sha.slice(0, 7),
2850
+ committerDate,
2851
+ subject: (body.split(/\r?\n/, 1)[0] ?? "").trim(),
2852
+ body,
2853
+ breaking: isBreakingMessage(body),
2854
+ touchedPaths
2855
+ },
2856
+ added,
2857
+ numstat
2858
+ };
2859
+ }
2860
+ function parseGitLog(raw) {
2861
+ const commits = [];
2862
+ const addedPaths = /* @__PURE__ */ new Set();
2863
+ const numstatByPath = /* @__PURE__ */ new Map();
2864
+ if (!raw) return { commits, addedPaths: [], numstatByPath };
2865
+ for (const rec of raw.split(RS)) {
2866
+ if (!rec) continue;
2867
+ const parsed = parseRecord(rec);
2868
+ if (!parsed) continue;
2869
+ commits.push(parsed.commit);
2870
+ for (const p of parsed.added) addedPaths.add(p);
2871
+ for (const ns of parsed.numstat) {
2872
+ const prev = numstatByPath.get(ns.path) ?? { path: ns.path, added: 0, deleted: 0 };
2873
+ prev.added += ns.added;
2874
+ prev.deleted += ns.deleted;
2875
+ numstatByPath.set(ns.path, prev);
2876
+ }
2877
+ }
2878
+ return { commits, addedPaths: [...addedPaths], numstatByPath };
2879
+ }
2880
+ async function runGit(cwd, args) {
2881
+ const { stdout } = await execFileAsync("git", args, {
2882
+ cwd,
2883
+ env: { ...process.env, TZ: "UTC" },
2884
+ maxBuffer: GIT_MAX_BUFFER
2885
+ });
2886
+ return stdout;
2887
+ }
2888
+ function buildLogArgs(range, pathspec) {
2889
+ const args = [
2890
+ "log",
2891
+ "--numstat",
2892
+ "--summary",
2893
+ "-M",
2894
+ "--date=iso-strict-local",
2895
+ `--pretty=${PRETTY_FORMAT}`,
2896
+ ...range
2897
+ ];
2898
+ if (pathspec.length > 0) args.push("--", ...pathspec);
2899
+ return args;
2900
+ }
2901
+ async function loadGitContext(opts) {
2902
+ const cwd = opts.cwd ?? process.cwd();
2903
+ const pathspec = opts.pathspec ?? [];
2904
+ const maxCommits = opts.maxCommits ?? DEFAULT_FIRST_RUN_COMMITS;
2905
+ let headSha;
2906
+ try {
2907
+ headSha = (await runGit(cwd, ["rev-parse", "HEAD"])).trim();
2908
+ } catch {
2909
+ return emptyContext(false, null);
2910
+ }
2911
+ const lsTreeRaw = await runGit(cwd, ["ls-tree", "-r", "--name-only", "HEAD"]);
2912
+ const headFiles = parseLsTree(lsTreeRaw);
2913
+ const firstRunRange = ["-n", String(maxCommits), "HEAD"];
2914
+ let isFirstRun = !opts.watermarkSha;
2915
+ let watermarkFellBack = false;
2916
+ let logRaw = "";
2917
+ if (opts.watermarkSha) {
2918
+ try {
2919
+ logRaw = await runGit(cwd, buildLogArgs([`${opts.watermarkSha}..HEAD`], pathspec));
2920
+ } catch {
2921
+ isFirstRun = true;
2922
+ watermarkFellBack = true;
2923
+ logRaw = await runGit(cwd, buildLogArgs(firstRunRange, pathspec));
2924
+ }
2925
+ } else {
2926
+ logRaw = await runGit(cwd, buildLogArgs(firstRunRange, pathspec));
2927
+ }
2928
+ const parsed = parseGitLog(logRaw);
2929
+ const breakingTouchedPaths = /* @__PURE__ */ new Set();
2930
+ for (const c of parsed.commits) {
2931
+ if (!c.breaking) continue;
2932
+ for (const p of c.touchedPaths) breakingTouchedPaths.add(p);
2933
+ }
2934
+ return {
2935
+ isRepo: true,
2936
+ isFirstRun,
2937
+ watermarkFellBack,
2938
+ headSha,
2939
+ headShaShort: headSha.slice(0, 7),
2940
+ watermarkSha: watermarkFellBack ? null : opts.watermarkSha,
2941
+ headFiles,
2942
+ commits: parsed.commits,
2943
+ breakingTouchedPaths,
2944
+ addedPaths: parsed.addedPaths,
2945
+ numstatByPath: parsed.numstatByPath
2946
+ };
2947
+ }
2948
+ function emptyContext(isRepo, watermarkSha) {
2949
+ return {
2950
+ isRepo,
2951
+ isFirstRun: !watermarkSha,
2952
+ watermarkFellBack: false,
2953
+ headSha: "",
2954
+ headShaShort: "",
2955
+ watermarkSha,
2956
+ headFiles: /* @__PURE__ */ new Set(),
2957
+ commits: [],
2958
+ breakingTouchedPaths: /* @__PURE__ */ new Set(),
2959
+ addedPaths: [],
2960
+ numstatByPath: /* @__PURE__ */ new Map()
2961
+ };
2962
+ }
2228
2963
 
2229
2964
  // src/measure.ts
2230
2965
  import { appendFileSync, mkdirSync } from "node:fs";
@@ -2327,7 +3062,277 @@ function recordMeasurement(command, args, charsReturned) {
2327
3062
  }
2328
3063
  }
2329
3064
 
3065
+ // src/commands/drift.ts
3066
+ init_api_client();
3067
+
3068
+ // src/utils/list-documents.ts
3069
+ async function fetchAllDocumentsMetadata(client, options) {
3070
+ const pageLimit = options?.pageLimit ?? 100;
3071
+ const maxPages = options?.maxPages ?? 1e3;
3072
+ const all = [];
3073
+ let cursor;
3074
+ let pages = 0;
3075
+ do {
3076
+ const params = new URLSearchParams();
3077
+ params.set("view", "metadata");
3078
+ params.set("limit", String(pageLimit));
3079
+ if (options?.pathPrefix) {
3080
+ params.set("pathPrefix", options.pathPrefix);
3081
+ }
3082
+ if (options?.workspace) {
3083
+ params.set("workspace", options.workspace);
3084
+ }
3085
+ if (cursor) {
3086
+ params.set("cursor", cursor);
3087
+ }
3088
+ const response = await client.get(
3089
+ `/v1/documents?${params.toString()}`
3090
+ );
3091
+ all.push(...response.items ?? []);
3092
+ cursor = response.cursor;
3093
+ pages += 1;
3094
+ } while (cursor && pages < maxPages);
3095
+ return all;
3096
+ }
3097
+
3098
+ // src/commands/drift.ts
3099
+ var RITUAL_HELP = `
3100
+ Ritual cadence:
3101
+ bisque drift is a MANUAL, ritual-cadence command \u2014 run it like /bisque:distill
3102
+ (e.g. at the start of a sync session), NEVER per-session or per-tool-call. It
3103
+ is deliberately NOT wired into any SessionStart / PostToolUse hook. It only
3104
+ TRIAGES which project context docs may have drifted from code; it never
3105
+ archives, rejects, or "fixes" anything \u2014 the agent reconcile ritual decides.
3106
+
3107
+ Signals (mechanical, no LLM):
3108
+ link-rot a referenced codePath no longer exists at HEAD
3109
+ breaking an in-window feat!/fix!/BREAKING CHANGE commit touched a ref
3110
+ time-decay PARA-bucketed cadence timer over the doc's last update
3111
+
3112
+ Examples:
3113
+ bisque drift --project BisqueLayer
3114
+ bisque drift --project BisqueLayer --json
3115
+ bisque drift --project BisqueLayer --experimental-churn
3116
+ `;
3117
+ function asString(v) {
3118
+ return typeof v === "string" && v.length > 0 ? v : void 0;
3119
+ }
3120
+ function resolveProjectPrefix(input) {
3121
+ if (!input) return "1-Projects/";
3122
+ let name = input.trim();
3123
+ if (name.startsWith("1-Projects/")) name = name.slice("1-Projects/".length);
3124
+ name = name.replace(/^\/+/, "").replace(/\/+$/, "");
3125
+ return name ? `1-Projects/${name}/` : "1-Projects/";
3126
+ }
3127
+ function resolveWatermark(docs) {
3128
+ const statusDocs = docs.filter((d) => d.filepath.toLowerCase().endsWith("frontmind/status.md"));
3129
+ const withWm = statusDocs.filter((d) => asString(d.metadata?.["codeWatermark"]));
3130
+ if (withWm.length === 1) {
3131
+ const d = withWm[0];
3132
+ return {
3133
+ watermarkSha: asString(d.metadata?.["codeWatermark"]) ?? null,
3134
+ lastDistilledAt: asString(d.metadata?.["lastDistilledAt"]) ?? null,
3135
+ ambiguous: false
3136
+ };
3137
+ }
3138
+ if (withWm.length > 1) return { watermarkSha: null, lastDistilledAt: null, ambiguous: true };
3139
+ const single = statusDocs.length === 1 ? statusDocs[0] : void 0;
3140
+ return {
3141
+ watermarkSha: null,
3142
+ lastDistilledAt: single ? asString(single.metadata?.["lastDistilledAt"]) ?? null : null,
3143
+ ambiguous: false
3144
+ };
3145
+ }
3146
+ function derivePathspec(allCodePaths) {
3147
+ const dirs = /* @__PURE__ */ new Set();
3148
+ for (const p of allCodePaths) {
3149
+ const slash = p.lastIndexOf("/");
3150
+ dirs.add(slash >= 0 ? p.slice(0, slash) : p);
3151
+ }
3152
+ return [...dirs];
3153
+ }
3154
+ async function readBisqueignore(cwd) {
3155
+ try {
3156
+ return await readFile4(`${cwd}/.bisqueignore`, "utf-8");
3157
+ } catch {
3158
+ return void 0;
3159
+ }
3160
+ }
3161
+ function signalColor(signal) {
3162
+ if (signal === "breaking") return chalk3.red(signal);
3163
+ if (signal === "link-rot") return chalk3.yellow(signal);
3164
+ return chalk3.gray(signal);
3165
+ }
3166
+ function renderBanner(prefix, git, watermark) {
3167
+ console.log(chalk3.bold.cyan(`
3168
+ Drift triage \u2014 ${prefix}`));
3169
+ const since = git.watermarkSha ? git.watermarkSha.slice(0, 7) : null;
3170
+ const head = git.headShaShort || "(unknown)";
3171
+ if (since) {
3172
+ console.log(chalk3.gray(` Window: ${since}..${head} (committer-date UTC)`));
3173
+ } else {
3174
+ console.log(chalk3.gray(` Window: first-run bounded (no codeWatermark) \u2192 up to HEAD ${head}`));
3175
+ }
3176
+ if (watermark.ambiguous) {
3177
+ console.log(
3178
+ chalk3.yellow(
3179
+ " Note: multiple projects with watermarks in scope \u2014 using first-run window. Pass --project to scope."
3180
+ )
3181
+ );
3182
+ }
3183
+ if (git.watermarkFellBack) {
3184
+ console.log(
3185
+ chalk3.yellow(
3186
+ " Note: codeWatermark no longer resolves in git \u2192 fell back to first-run window."
3187
+ )
3188
+ );
3189
+ }
3190
+ console.log(chalk3.gray(` Last distilled: ${watermark.lastDistilledAt ?? "(never)"}`));
3191
+ }
3192
+ function renderTable(scored, churnEnabled) {
3193
+ const flagged = scored.filter((d) => d.score > 0);
3194
+ if (flagged.length === 0) {
3195
+ console.log(chalk3.green("\nNo drift signals. Context looks in sync with code."));
3196
+ return;
3197
+ }
3198
+ const head = [
3199
+ chalk3.cyan("#"),
3200
+ chalk3.cyan("Signal"),
3201
+ chalk3.cyan("Score"),
3202
+ chalk3.cyan("Prov"),
3203
+ chalk3.cyan("Evidence"),
3204
+ chalk3.cyan("Filepath")
3205
+ ];
3206
+ if (churnEnabled) head.splice(5, 0, chalk3.cyan("Churn*"));
3207
+ const table = new Table2({ head, style: { head: [], border: ["gray"] } });
3208
+ flagged.forEach((d, i) => {
3209
+ const row = [
3210
+ String(i + 1),
3211
+ signalColor(d.signal),
3212
+ d.score.toFixed(2),
3213
+ d.hasProvenance ? "yes" : chalk3.gray("no"),
3214
+ d.evidence.slice(0, 3).join("\n") || chalk3.gray("\u2014"),
3215
+ d.filepath
3216
+ ];
3217
+ if (churnEnabled) row.splice(5, 0, d.churn >= 0 ? String(d.churn) : chalk3.gray("\u2014"));
3218
+ table.push(row);
3219
+ });
3220
+ console.log(`
3221
+ ${table.toString()}`);
3222
+ if (churnEnabled) {
3223
+ console.log(
3224
+ chalk3.gray(
3225
+ "\n * Churn is EXPERIMENTAL (doc-class-gated, dated records excluded) and is NOT a ranker."
3226
+ )
3227
+ );
3228
+ }
3229
+ }
3230
+ function renderUncovered(uncovered) {
3231
+ if (uncovered.files.length === 0 && uncovered.more === 0) return;
3232
+ console.log(chalk3.bold("\nNew code lacking context (added in-window, no doc references it):"));
3233
+ for (const f of uncovered.files) console.log(chalk3.gray(` + ${f}`));
3234
+ if (uncovered.more > 0) console.log(chalk3.gray(` + ${uncovered.more} more`));
3235
+ }
3236
+ async function runDrift(options) {
3237
+ const prefix = resolveProjectPrefix(options.project);
3238
+ const client = new ApiClient();
3239
+ const docs = await fetchAllDocumentsMetadata(client, {
3240
+ pathPrefix: prefix,
3241
+ ...options.workspace ? { workspace: options.workspace } : {}
3242
+ });
3243
+ if (docs.length === 0) {
3244
+ console.log(chalk3.yellow(`No documents found under ${prefix}`));
3245
+ return;
3246
+ }
3247
+ const watermark = resolveWatermark(docs);
3248
+ const scorable = docs;
3249
+ const allCodePaths = /* @__PURE__ */ new Set();
3250
+ for (const d of docs) for (const p of getCodePaths(d)) allCodePaths.add(p);
3251
+ const git = await loadGitContext({
3252
+ cwd: process.cwd(),
3253
+ watermarkSha: watermark.watermarkSha,
3254
+ pathspec: derivePathspec([...allCodePaths]),
3255
+ maxCommits: Number.parseInt(options.maxCommits, 10) || void 0
3256
+ });
3257
+ if (!git.isRepo) {
3258
+ console.error(
3259
+ "Error: not a git repository (run bisque drift from inside the project's repo \u2014 drift compares docs against the local code)."
3260
+ );
3261
+ process.exitCode = 1;
3262
+ return;
3263
+ }
3264
+ const ignoreContent = await readBisqueignore(process.cwd());
3265
+ const isIgnored = createIgnoreMatcher(ignoreContent);
3266
+ const nowMs = Date.now();
3267
+ const scored = scoreAndRank(scorable, {
3268
+ headFiles: git.headFiles,
3269
+ breakingTouched: git.breakingTouchedPaths,
3270
+ nowMs,
3271
+ numstatByPath: git.numstatByPath,
3272
+ churnEnabled: options.experimentalChurn === true
3273
+ });
3274
+ const uncovered = buildUncoveredHint(git.addedPaths, allCodePaths, isIgnored);
3275
+ if (options.json) {
3276
+ const digest = buildDigest(scored, {
3277
+ project: prefix,
3278
+ generatedAt: new Date(nowMs).toISOString(),
3279
+ watermarkSince: git.watermarkSha ? git.watermarkSha.slice(0, 7) : null,
3280
+ headShort: git.headShaShort,
3281
+ lastDistilledAt: watermark.lastDistilledAt,
3282
+ uncovered
3283
+ });
3284
+ const out = JSON.stringify(digest);
3285
+ console.log(out);
3286
+ recordMeasurement(
3287
+ "drift",
3288
+ { projectPath: prefix, workspaceId: options.workspace, json: true },
3289
+ out.length
3290
+ );
3291
+ return;
3292
+ }
3293
+ renderBanner(prefix, git, watermark);
3294
+ renderTable(scored, options.experimentalChurn === true);
3295
+ renderUncovered(uncovered);
3296
+ const forced = scored.filter((d) => d.linkRot || d.breaking).length;
3297
+ console.log(
3298
+ chalk3.gray(
3299
+ `
3300
+ ${scored.length} doc(s) scored \xB7 ${forced} forced by link-rot/breaking \xB7 triage only (the agent reconcile ritual decides).`
3301
+ )
3302
+ );
3303
+ recordMeasurement(
3304
+ "drift",
3305
+ { projectPath: prefix, workspaceId: options.workspace, json: false },
3306
+ scored.length
3307
+ );
3308
+ }
3309
+ function registerDriftCommand(program2) {
3310
+ program2.command("drift").description(
3311
+ "Triage which project context docs may have drifted from code (mechanical, no-LLM). Ritual cadence \u2014 run like /distill, never per-session."
3312
+ ).option(
3313
+ "--project <path>",
3314
+ 'Project to scope to: a name ("BisqueLayer") or PARA path ("1-Projects/BisqueLayer"). Defaults to all 1-Projects/.'
3315
+ ).option("--workspace <id>", "Workspace ID").option("--json", "Emit a compact \u22642K-token top-K(25) triage digest as JSON").option(
3316
+ "--experimental-churn",
3317
+ "Add an EXPERIMENTAL churn-magnitude column (doc-class-gated; NOT a ranker)"
3318
+ ).option(
3319
+ "--max-commits <n>",
3320
+ "First-run window bound (commits) when no codeWatermark exists",
3321
+ "200"
3322
+ ).addHelpText("after", RITUAL_HELP).action(async (options) => {
3323
+ try {
3324
+ await runDrift(options);
3325
+ } catch (error) {
3326
+ const message = error instanceof Error ? error.message : String(error);
3327
+ console.error(`Error: ${message}`);
3328
+ process.exitCode = 1;
3329
+ }
3330
+ });
3331
+ }
3332
+
2330
3333
  // src/commands/get.ts
3334
+ import { writeFile as writeFile2 } from "node:fs/promises";
3335
+ import { Option } from "commander";
2331
3336
  init_api_client();
2332
3337
  function registerGetCommand(program2) {
2333
3338
  program2.command("get <docId>").description("Retrieve a document by ID").option("-o, --output <file>", "Save content to a file instead of stdout").addOption(
@@ -2522,65 +3527,97 @@ function registerLinkCommand(program2) {
2522
3527
  }
2523
3528
 
2524
3529
  // src/commands/list.ts
2525
- import chalk3 from "chalk";
2526
- import Table2 from "cli-table3";
3530
+ import chalk4 from "chalk";
3531
+ import Table3 from "cli-table3";
2527
3532
  init_api_client();
2528
- function registerListCommand(program2) {
2529
- program2.command("list").description("List documents with optional filters").option("--path-prefix <prefix>", "Filter by path prefix").option("--filepath <path>", "Filter by exact filepath").option("--workspace <id>", "Workspace ID").option("--limit <n>", "Max results", "20").action(
2530
- async (options) => {
2531
- try {
2532
- const params = new URLSearchParams();
2533
- params.set("limit", options.limit);
2534
- if (options.pathPrefix) {
2535
- params.set("pathPrefix", options.pathPrefix);
2536
- }
2537
- if (options.filepath) {
2538
- params.set("filepath", options.filepath);
2539
- }
2540
- if (options.workspace) {
2541
- params.set("workspace", options.workspace);
2542
- }
2543
- const client = new ApiClient();
2544
- const response = await client.get(
2545
- `/v1/documents?${params.toString()}`
2546
- );
2547
- const docs = response.items ?? [];
2548
- recordMeasurement(
2549
- "list",
2550
- {
2551
- pathPrefix: options.pathPrefix,
2552
- filepath: options.filepath,
2553
- workspaceId: options.workspace,
2554
- limit: options.limit
2555
- },
2556
- JSON.stringify(docs).length
2557
- );
2558
- if (docs.length === 0) {
2559
- console.log(chalk3.yellow("No documents found."));
2560
- return;
2561
- }
2562
- const table = new Table2({
2563
- head: [chalk3.cyan("Title"), chalk3.cyan("Filepath"), chalk3.cyan("Document ID")],
2564
- style: { head: [], border: ["gray"] }
2565
- });
2566
- for (const doc of docs) {
2567
- table.push([doc.title, doc.filepath, doc.documentId]);
2568
- }
2569
- console.log(table.toString());
2570
- console.log(chalk3.gray(`
3533
+ function renderTable2(docs) {
3534
+ const table = new Table3({
3535
+ head: [chalk4.cyan("Title"), chalk4.cyan("Filepath"), chalk4.cyan("Document ID")],
3536
+ style: { head: [], border: ["gray"] }
3537
+ });
3538
+ for (const doc of docs) {
3539
+ table.push([doc.title, doc.filepath, doc.documentId]);
3540
+ }
3541
+ console.log(table.toString());
3542
+ console.log(chalk4.gray(`
2571
3543
  Total: ${docs.length} document(s)`));
2572
- if (response.cursor) {
2573
- console.log(chalk3.gray("More results available \u2014 re-run with a larger --limit."));
2574
- }
2575
- } catch (error) {
2576
- exitWithError(error);
3544
+ }
3545
+ async function runMetadataList(options) {
3546
+ const client = new ApiClient();
3547
+ const pageLimit = Number.parseInt(options.limit, 10);
3548
+ const docs = await fetchAllDocumentsMetadata(client, {
3549
+ ...options.pathPrefix ? { pathPrefix: options.pathPrefix } : {},
3550
+ ...options.workspace ? { workspace: options.workspace } : {},
3551
+ ...Number.isFinite(pageLimit) && pageLimit > 0 ? { pageLimit } : {}
3552
+ });
3553
+ recordMeasurement(
3554
+ "list",
3555
+ {
3556
+ pathPrefix: options.pathPrefix,
3557
+ workspaceId: options.workspace,
3558
+ limit: options.limit,
3559
+ view: "metadata"
3560
+ },
3561
+ JSON.stringify(docs).length
3562
+ );
3563
+ if (docs.length === 0) {
3564
+ console.log(chalk4.yellow("No documents found."));
3565
+ return;
3566
+ }
3567
+ renderTable2(docs);
3568
+ }
3569
+ function registerListCommand(program2) {
3570
+ program2.command("list").description("List documents with optional filters").option("--path-prefix <prefix>", "Filter by path prefix").option("--filepath <path>", "Filter by exact filepath").option("--workspace <id>", "Workspace ID").option("--limit <n>", "Max results", "20").option(
3571
+ "--view <mode>",
3572
+ "Projection mode: 'metadata' omits content and follows the cursor to enumerate ALL matching docs"
3573
+ ).action(async (options) => {
3574
+ try {
3575
+ if (options.view === "metadata") {
3576
+ await runMetadataList(options);
3577
+ return;
3578
+ }
3579
+ const params = new URLSearchParams();
3580
+ params.set("limit", options.limit);
3581
+ if (options.pathPrefix) {
3582
+ params.set("pathPrefix", options.pathPrefix);
3583
+ }
3584
+ if (options.filepath) {
3585
+ params.set("filepath", options.filepath);
3586
+ }
3587
+ if (options.workspace) {
3588
+ params.set("workspace", options.workspace);
3589
+ }
3590
+ const client = new ApiClient();
3591
+ const response = await client.get(
3592
+ `/v1/documents?${params.toString()}`
3593
+ );
3594
+ const docs = response.items ?? [];
3595
+ recordMeasurement(
3596
+ "list",
3597
+ {
3598
+ pathPrefix: options.pathPrefix,
3599
+ filepath: options.filepath,
3600
+ workspaceId: options.workspace,
3601
+ limit: options.limit
3602
+ },
3603
+ JSON.stringify(docs).length
3604
+ );
3605
+ if (docs.length === 0) {
3606
+ console.log(chalk4.yellow("No documents found."));
3607
+ return;
3608
+ }
3609
+ renderTable2(docs);
3610
+ if (response.cursor) {
3611
+ console.log(chalk4.gray("More results available \u2014 re-run with a larger --limit."));
2577
3612
  }
3613
+ } catch (error) {
3614
+ exitWithError(error);
2578
3615
  }
2579
- );
3616
+ });
2580
3617
  }
2581
3618
 
2582
3619
  // src/commands/load.ts
2583
- import chalk4 from "chalk";
3620
+ import chalk5 from "chalk";
2584
3621
  init_api_client();
2585
3622
  function normalize(s) {
2586
3623
  return s.toLowerCase().replace(/[-_\s]/g, "");
@@ -2623,8 +3660,8 @@ function registerLoadCommand(program2) {
2623
3660
  )
2624
3661
  );
2625
3662
  } else {
2626
- console.error(chalk4.red(`Project not found: "${project}"`));
2627
- console.log(chalk4.yellow(`Available projects: ${projectNames.join(", ") || "(none)"}`));
3663
+ console.error(chalk5.red(`Project not found: "${project}"`));
3664
+ console.log(chalk5.yellow(`Available projects: ${projectNames.join(", ") || "(none)"}`));
2628
3665
  }
2629
3666
  process.exitCode = 1;
2630
3667
  return;
@@ -2675,35 +3712,35 @@ function registerLoadCommand(program2) {
2675
3712
  );
2676
3713
  return;
2677
3714
  }
2678
- console.log(chalk4.bold.cyan(`
3715
+ console.log(chalk5.bold.cyan(`
2679
3716
  === ${matchedName} ===
2680
3717
  `));
2681
3718
  if (agentsMd) {
2682
- console.log(chalk4.bold("AGENTS.md:"));
3719
+ console.log(chalk5.bold("AGENTS.md:"));
2683
3720
  console.log(agentsMd.content);
2684
3721
  console.log();
2685
3722
  } else {
2686
- console.log(chalk4.yellow("No AGENTS.md found for this project.\n"));
3723
+ console.log(chalk5.yellow("No AGENTS.md found for this project.\n"));
2687
3724
  }
2688
3725
  if (frontmind.length > 0) {
2689
- console.log(chalk4.bold(`Frontmind (${frontmind.length} files):`));
3726
+ console.log(chalk5.bold(`Frontmind (${frontmind.length} files):`));
2690
3727
  for (const doc of frontmind) {
2691
- console.log(chalk4.cyan(`
3728
+ console.log(chalk5.cyan(`
2692
3729
  --- ${doc.filepath} ---`));
2693
3730
  console.log(doc.content);
2694
3731
  }
2695
3732
  console.log();
2696
3733
  } else {
2697
- console.log(chalk4.gray("No frontmind files.\n"));
3734
+ console.log(chalk5.gray("No frontmind files.\n"));
2698
3735
  }
2699
3736
  if (backmind.length > 0) {
2700
- console.log(chalk4.bold(`Backmind (${backmind.length} files available):`));
3737
+ console.log(chalk5.bold(`Backmind (${backmind.length} files available):`));
2701
3738
  for (const entry of backmind) {
2702
- console.log(chalk4.gray(` ${entry.filepath} \u2014 ${entry.title}`));
3739
+ console.log(chalk5.gray(` ${entry.filepath} \u2014 ${entry.title}`));
2703
3740
  }
2704
- console.log(chalk4.gray("\n Use: bisque get <documentId> to load any backmind file."));
3741
+ console.log(chalk5.gray("\n Use: bisque get <documentId> to load any backmind file."));
2705
3742
  } else {
2706
- console.log(chalk4.gray("No backmind files."));
3743
+ console.log(chalk5.gray("No backmind files."));
2707
3744
  }
2708
3745
  } catch (error) {
2709
3746
  const message = error instanceof Error ? error.message : String(error);
@@ -2715,14 +3752,14 @@ function registerLoadCommand(program2) {
2715
3752
 
2716
3753
  // src/commands/ls.ts
2717
3754
  init_api_client();
2718
- import chalk5 from "chalk";
3755
+ import chalk6 from "chalk";
2719
3756
  function formatFolder(entry) {
2720
3757
  const countInfo = entry.childCount != null ? `(${entry.childCount} files)` : entry.hasChildren ? "(has children)" : "";
2721
- return ` ${chalk5.blue(`${entry.name}/`)} ${chalk5.dim(countInfo)}`;
3758
+ return ` ${chalk6.blue(`${entry.name}/`)} ${chalk6.dim(countInfo)}`;
2722
3759
  }
2723
3760
  function formatFile(entry) {
2724
3761
  const date = entry.updatedAt ? new Date(entry.updatedAt).toISOString().split("T")[0] : "";
2725
- return ` ${entry.name} ${chalk5.dim(`"${entry.title}"`)} ${chalk5.dim(date)}`;
3762
+ return ` ${entry.name} ${chalk6.dim(`"${entry.title}"`)} ${chalk6.dim(date)}`;
2726
3763
  }
2727
3764
  function buildParams(path2, options) {
2728
3765
  const params = new URLSearchParams();
@@ -2749,7 +3786,7 @@ function registerLsCommand(program2) {
2749
3786
  }
2750
3787
  const data = result;
2751
3788
  if (!data.entries || data.entries.length === 0) {
2752
- console.log(chalk5.dim(" (empty)"));
3789
+ console.log(chalk6.dim(" (empty)"));
2753
3790
  return;
2754
3791
  }
2755
3792
  displayEntries(data.entries);
@@ -2761,15 +3798,15 @@ function registerLsCommand(program2) {
2761
3798
 
2762
3799
  // src/commands/maintenance.ts
2763
3800
  init_api_client();
2764
- import chalk6 from "chalk";
3801
+ import chalk7 from "chalk";
2765
3802
  function healthColor(health) {
2766
3803
  switch (health) {
2767
3804
  case "healthy":
2768
- return chalk6.green(health);
3805
+ return chalk7.green(health);
2769
3806
  case "needs-attention":
2770
- return chalk6.yellow(health);
3807
+ return chalk7.yellow(health);
2771
3808
  case "needs-maintenance":
2772
- return chalk6.red(health);
3809
+ return chalk7.red(health);
2773
3810
  default:
2774
3811
  return health;
2775
3812
  }
@@ -2782,7 +3819,7 @@ function scopeLabel(scope) {
2782
3819
  }
2783
3820
  function printRecommendationLine(label, count) {
2784
3821
  if (count > 0) {
2785
- console.log(` ${chalk6.yellow("*")} ${label}: ${count}`);
3822
+ console.log(` ${chalk7.yellow("*")} ${label}: ${count}`);
2786
3823
  }
2787
3824
  }
2788
3825
  function registerMaintenanceCommand(program2) {
@@ -2801,26 +3838,26 @@ function registerMaintenanceCommand(program2) {
2801
3838
  }
2802
3839
  const report = await client.post("/v1/maintenance", body);
2803
3840
  console.log();
2804
- console.log(chalk6.bold("Workspace Maintenance Report"));
3841
+ console.log(chalk7.bold("Workspace Maintenance Report"));
2805
3842
  console.log(`Scope: ${scopeLabel(report.scope)} | ${report.timestamp}`);
2806
3843
  console.log();
2807
3844
  const { mechanical } = report;
2808
3845
  const hasMechanical = mechanical.taskLocksReleased.length > 0 || mechanical.chainsCompleted.length > 0 || mechanical.errors.length > 0;
2809
3846
  if (hasMechanical) {
2810
- console.log(chalk6.bold("Mechanical Fixes"));
3847
+ console.log(chalk7.bold("Mechanical Fixes"));
2811
3848
  for (const lock of mechanical.taskLocksReleased) {
2812
3849
  console.log(
2813
- ` ${chalk6.green("\u2713")} Released expired lock: ${lock.title} (${lock.taskId.substring(0, 8)})`
3850
+ ` ${chalk7.green("\u2713")} Released expired lock: ${lock.title} (${lock.taskId.substring(0, 8)})`
2814
3851
  );
2815
3852
  }
2816
3853
  for (const chain of mechanical.chainsCompleted) {
2817
3854
  console.log(
2818
- ` ${chalk6.green("\u2713")} Completed chain: ${chain.name} (${chain.chainId.substring(0, 8)})`
3855
+ ` ${chalk7.green("\u2713")} Completed chain: ${chain.name} (${chain.chainId.substring(0, 8)})`
2819
3856
  );
2820
3857
  }
2821
3858
  for (const err of mechanical.errors) {
2822
3859
  console.log(
2823
- ` ${chalk6.red("\u2717")} ${err.operation} failed on ${err.entityId.substring(0, 8)}: ${err.error}`
3860
+ ` ${chalk7.red("\u2717")} ${err.operation} failed on ${err.entityId.substring(0, 8)}: ${err.error}`
2824
3861
  );
2825
3862
  }
2826
3863
  console.log();
@@ -2828,7 +3865,7 @@ function registerMaintenanceCommand(program2) {
2828
3865
  const { recommendations: rec } = report;
2829
3866
  const hasRecommendations = report.summary.recommendations > 0;
2830
3867
  if (hasRecommendations) {
2831
- console.log(chalk6.bold("Recommendations"));
3868
+ console.log(chalk7.bold("Recommendations"));
2832
3869
  printRecommendationLine("Inbox items pending triage", rec.inboxItems.length);
2833
3870
  printRecommendationLine("Stale documents", rec.staleDocs.length);
2834
3871
  printRecommendationLine("AGENTS.md refresh needed", rec.agentsMdRefreshNeeded.length);
@@ -2844,7 +3881,7 @@ function registerMaintenanceCommand(program2) {
2844
3881
  printRecommendationLine("Misplaced documents", rec.misplacedDocs.length);
2845
3882
  console.log();
2846
3883
  }
2847
- console.log(chalk6.bold("Summary"));
3884
+ console.log(chalk7.bold("Summary"));
2848
3885
  console.log(` Mechanical fixes: ${report.summary.mechanicalFixes}`);
2849
3886
  console.log(` Recommendations: ${report.summary.recommendations}`);
2850
3887
  console.log(` Health: ${healthColor(report.summary.workspaceHealth)}`);
@@ -2965,7 +4002,7 @@ function renderReferenceTable(stats) {
2965
4002
  );
2966
4003
  return [header, sep, ...body].join("\n");
2967
4004
  }
2968
- function renderTable(stats) {
4005
+ function renderTable3(stats) {
2969
4006
  const header = "| command | n calls | avg tokens | p50 | p95 | total tokens |";
2970
4007
  const sep = "|---|---|---|---|---|---|";
2971
4008
  const body = stats.map(
@@ -3007,7 +4044,7 @@ function runSummarize(opts) {
3007
4044
  console.log("No measurements match your filters.");
3008
4045
  return;
3009
4046
  }
3010
- console.log(renderTable(stats));
4047
+ console.log(renderTable3(stats));
3011
4048
  }
3012
4049
  function registerMeasureCommand(program2) {
3013
4050
  const measure = program2.command("measure").description("Inspect measurement data captured by BISQUE_MEASURE=1");
@@ -3059,8 +4096,8 @@ function registerMoveCommand(program2) {
3059
4096
  // src/commands/persona.ts
3060
4097
  init_api_client();
3061
4098
  import { createInterface as createInterface2 } from "node:readline";
3062
- import chalk7 from "chalk";
3063
- import Table3 from "cli-table3";
4099
+ import chalk8 from "chalk";
4100
+ import Table4 from "cli-table3";
3064
4101
  function confirm2(prompt) {
3065
4102
  const rl = createInterface2({
3066
4103
  input: process.stdin,
@@ -3084,16 +4121,16 @@ function registerPersonaCommand(program2) {
3084
4121
  return;
3085
4122
  }
3086
4123
  if (data.agentPersonas.length === 0) {
3087
- console.log(chalk7.yellow("No personas found."));
4124
+ console.log(chalk8.yellow("No personas found."));
3088
4125
  return;
3089
4126
  }
3090
- const table = new Table3({
4127
+ const table = new Table4({
3091
4128
  head: [
3092
- chalk7.cyan("ID"),
3093
- chalk7.cyan("Name"),
3094
- chalk7.cyan("Specialization"),
3095
- chalk7.cyan("Tasks"),
3096
- chalk7.cyan("Last Active")
4129
+ chalk8.cyan("ID"),
4130
+ chalk8.cyan("Name"),
4131
+ chalk8.cyan("Specialization"),
4132
+ chalk8.cyan("Tasks"),
4133
+ chalk8.cyan("Last Active")
3097
4134
  ],
3098
4135
  style: {
3099
4136
  head: [],
@@ -3111,7 +4148,7 @@ function registerPersonaCommand(program2) {
3111
4148
  ]);
3112
4149
  }
3113
4150
  console.log(table.toString());
3114
- console.log(chalk7.gray(`
4151
+ console.log(chalk8.gray(`
3115
4152
  Total: ${data.agentPersonas.length} persona(s)`));
3116
4153
  } catch (error) {
3117
4154
  exitWithError(error);
@@ -3132,13 +4169,13 @@ Total: ${data.agentPersonas.length} persona(s)`));
3132
4169
  console.log(`Created: ${p.createdAt}`);
3133
4170
  console.log(`Updated: ${p.updatedAt}`);
3134
4171
  if (p.learnings && p.learnings.length > 0) {
3135
- console.log(chalk7.cyan("\nLearnings:"));
4172
+ console.log(chalk8.cyan("\nLearnings:"));
3136
4173
  for (const l of p.learnings) {
3137
4174
  console.log(` - ${l}`);
3138
4175
  }
3139
4176
  }
3140
4177
  if (p.feedback && p.feedback.length > 0) {
3141
- console.log(chalk7.cyan("\nFeedback:"));
4178
+ console.log(chalk8.cyan("\nFeedback:"));
3142
4179
  for (const f of p.feedback) {
3143
4180
  console.log(` - ${f}`);
3144
4181
  }
@@ -3229,7 +4266,7 @@ Total: ${data.agentPersonas.length} persona(s)`));
3229
4266
 
3230
4267
  // src/commands/plugin.ts
3231
4268
  import { spawn } from "node:child_process";
3232
- var BISQUE_MARKETPLACE_SOURCE = "Kokonaut/bisque";
4269
+ var BISQUE_MARKETPLACE_SOURCE = "Kokonaut/bisque-marketplace";
3233
4270
  var BISQUE_MARKETPLACE_NAME = "bisque-marketplace";
3234
4271
  var BISQUE_PLUGIN_NAME = "bisque";
3235
4272
  var BISQUE_PLUGIN_QUALIFIED = `${BISQUE_PLUGIN_NAME}@${BISQUE_MARKETPLACE_NAME}`;
@@ -3694,8 +4731,8 @@ function registerTagCommand(program2) {
3694
4731
  // src/commands/task.ts
3695
4732
  init_api_client();
3696
4733
  import { createInterface as createInterface3 } from "node:readline";
3697
- import chalk8 from "chalk";
3698
- import Table4 from "cli-table3";
4734
+ import chalk9 from "chalk";
4735
+ import Table5 from "cli-table3";
3699
4736
  import { Option as Option2 } from "commander";
3700
4737
  var PICKUP_MODES = ["interactive", "autonomous"];
3701
4738
  function splitCsv(value) {
@@ -3771,16 +4808,16 @@ function registerTaskCommand(program2) {
3771
4808
  const path2 = query ? `/v1/tasks?${query}` : "/v1/tasks";
3772
4809
  const data = await client.get(path2);
3773
4810
  if (data.length === 0) {
3774
- console.log(chalk8.yellow("No tasks found."));
4811
+ console.log(chalk9.yellow("No tasks found."));
3775
4812
  return;
3776
4813
  }
3777
- const table = new Table4({
4814
+ const table = new Table5({
3778
4815
  head: [
3779
- chalk8.cyan("#"),
3780
- chalk8.cyan("Title"),
3781
- chalk8.cyan("Status"),
3782
- chalk8.cyan("ID"),
3783
- chalk8.cyan("Chain")
4816
+ chalk9.cyan("#"),
4817
+ chalk9.cyan("Title"),
4818
+ chalk9.cyan("Status"),
4819
+ chalk9.cyan("ID"),
4820
+ chalk9.cyan("Chain")
3784
4821
  ],
3785
4822
  style: {
3786
4823
  head: [],
@@ -3793,7 +4830,7 @@ function registerTaskCommand(program2) {
3793
4830
  table.push([num, title, item.status, item.documentId, item.chainId ?? "-"]);
3794
4831
  }
3795
4832
  console.log(table.toString());
3796
- console.log(chalk8.gray(`
4833
+ console.log(chalk9.gray(`
3797
4834
  Total: ${data.length} task(s)`));
3798
4835
  } catch (error) {
3799
4836
  exitWithError(error);
@@ -3916,7 +4953,7 @@ Total: ${data.length} task(s)`));
3916
4953
 
3917
4954
  // src/commands/tree.ts
3918
4955
  init_api_client();
3919
- import chalk9 from "chalk";
4956
+ import chalk10 from "chalk";
3920
4957
  function registerTreeCommand(program2) {
3921
4958
  program2.command("tree [path]").description(
3922
4959
  "Recursively fetch documents with content. WARNING: This is the most expensive navigation operation. Prefer 'bisque ls' for navigation and 'bisque batch' for targeted loading."
@@ -3936,19 +4973,19 @@ function registerTreeCommand(program2) {
3936
4973
  }
3937
4974
  const data = result;
3938
4975
  if (!data.documents || data.documents.length === 0) {
3939
- console.log(chalk9.dim(" (empty)"));
4976
+ console.log(chalk10.dim(" (empty)"));
3940
4977
  return;
3941
4978
  }
3942
4979
  for (const doc of data.documents) {
3943
- console.log(chalk9.blue(`--- ${doc.filepath} ---`));
3944
- console.log(chalk9.dim(`Title: ${doc.title}`));
4980
+ console.log(chalk10.blue(`--- ${doc.filepath} ---`));
4981
+ console.log(chalk10.dim(`Title: ${doc.title}`));
3945
4982
  console.log(doc.content);
3946
4983
  console.log();
3947
4984
  }
3948
- console.log(chalk9.dim(`Returned ${data.totalReturned}/${data.maxItems} max items`));
4985
+ console.log(chalk10.dim(`Returned ${data.totalReturned}/${data.maxItems} max items`));
3949
4986
  if (data.cursor) {
3950
4987
  console.log(
3951
- chalk9.yellow("More results available. Use --json to get cursor for pagination.")
4988
+ chalk10.yellow("More results available. Use --json to get cursor for pagination.")
3952
4989
  );
3953
4990
  }
3954
4991
  } catch (err) {
@@ -3960,6 +4997,9 @@ function registerTreeCommand(program2) {
3960
4997
 
3961
4998
  // src/commands/update.ts
3962
4999
  init_api_client();
5000
+ function parseCommaList(val) {
5001
+ return val.split(",").map((s) => s.trim()).filter(Boolean);
5002
+ }
3963
5003
  function parseMetadataJson2(raw) {
3964
5004
  try {
3965
5005
  const parsed = JSON.parse(raw);
@@ -3998,9 +5038,20 @@ function buildUpdateBody(options) {
3998
5038
  if (options.status !== void 0) {
3999
5039
  body["status"] = options.status;
4000
5040
  }
5041
+ const codePaths = resolveCodePaths(
5042
+ options.codePaths,
5043
+ body["metadata"],
5044
+ options.content ?? ""
5045
+ );
5046
+ if (codePaths) {
5047
+ body["metadata"] = {
5048
+ ...body["metadata"] ?? {},
5049
+ codePaths
5050
+ };
5051
+ }
4001
5052
  if (Object.keys(body).length === 0) {
4002
5053
  return {
4003
- error: "Error: at least one update flag is required (--title, --content, --summary, --tags, --keywords, --metadata, --status)"
5054
+ error: "Error: at least one update flag is required (--title, --content, --summary, --tags, --keywords, --metadata, --code-paths, --status)"
4004
5055
  };
4005
5056
  }
4006
5057
  return { body };
@@ -4010,6 +5061,10 @@ function registerUpdateCommand(program2) {
4010
5061
  "--summary <summary>",
4011
5062
  "Set the doc's summary field (short abstract, rendered in AGENTS.md ## What's here)"
4012
5063
  ).option("--tags <tags>", "Replace tags (comma-separated)").option("--keywords <keywords>", "Replace keywords (comma-separated)").option("--metadata <json>", "JSON metadata object").option(
5064
+ "--code-paths <paths>",
5065
+ "Comma-separated source paths this doc describes (overrides auto-extraction)",
5066
+ parseCommaList
5067
+ ).option(
4013
5068
  "--status <status>",
4014
5069
  "New lifecycle state: live | superseded | archived. Used for manual transitions (e.g. archiving a draft). Supersession-specific transitions go through the v1.5 supersession route."
4015
5070
  ).action(async (docId, options) => {
@@ -4034,8 +5089,8 @@ init_src();
4034
5089
  init_config();
4035
5090
  init_api_client();
4036
5091
  import { createInterface as createInterface4 } from "node:readline/promises";
4037
- import chalk10 from "chalk";
4038
- import Table5 from "cli-table3";
5092
+ import chalk11 from "chalk";
5093
+ import Table6 from "cli-table3";
4039
5094
  init_runtime_context();
4040
5095
  init_workspace_context();
4041
5096
  async function resolveSubcommandWorkspaceId(options, config) {
@@ -4078,21 +5133,21 @@ async function workspaceList(json) {
4078
5133
  return;
4079
5134
  }
4080
5135
  if (cache.workspaces.length === 0) {
4081
- console.log(chalk10.yellow("No workspaces found."));
5136
+ console.log(chalk11.yellow("No workspaces found."));
4082
5137
  return;
4083
5138
  }
4084
5139
  const personal = cache.workspaces.filter((w) => w.isPersonal);
4085
5140
  const shared = cache.workspaces.filter((w) => !w.isPersonal);
4086
5141
  const renderSection2 = (label, items) => {
4087
5142
  if (items.length === 0) return;
4088
- console.log(chalk10.bold.cyan(`
5143
+ console.log(chalk11.bold.cyan(`
4089
5144
  ${label}`));
4090
- const table = new Table5({
5145
+ const table = new Table6({
4091
5146
  head: [
4092
- chalk10.cyan("ID"),
4093
- chalk10.cyan("Name"),
4094
- chalk10.cyan("Role"),
4095
- chalk10.cyan("Last accessed")
5147
+ chalk11.cyan("ID"),
5148
+ chalk11.cyan("Name"),
5149
+ chalk11.cyan("Role"),
5150
+ chalk11.cyan("Last accessed")
4096
5151
  ],
4097
5152
  style: { head: [], border: ["gray"] }
4098
5153
  });
@@ -4104,7 +5159,7 @@ ${label}`));
4104
5159
  };
4105
5160
  renderSection2("Personal", personal);
4106
5161
  renderSection2("Shared", shared);
4107
- console.log(chalk10.gray(`
5162
+ console.log(chalk11.gray(`
4108
5163
  Total: ${cache.workspaces.length} workspace(s)`));
4109
5164
  } catch (error) {
4110
5165
  exitWithError(error);
@@ -4116,18 +5171,18 @@ async function workspaceSwitch(id) {
4116
5171
  const cache = await refreshWorkspacesCache(config);
4117
5172
  const match = cache.workspaces.find((w) => w.workspaceId === id);
4118
5173
  if (!match) {
4119
- console.error(chalk10.red(`Error: workspace '${id}' not found among your memberships.`));
5174
+ console.error(chalk11.red(`Error: workspace '${id}' not found among your memberships.`));
4120
5175
  console.error(
4121
- chalk10.gray(
5176
+ chalk11.gray(
4122
5177
  "Run `bisque workspace list` to see what you can access. To join a workspace, run `bisque workspace join <code>`."
4123
5178
  )
4124
5179
  );
4125
5180
  process.exit(3);
4126
5181
  }
4127
5182
  await config.set("activeWorkspaceId", id);
4128
- console.log(chalk10.green(`Active workspace set: ${match.name} (${match.role})`));
5183
+ console.log(chalk11.green(`Active workspace set: ${match.name} (${match.role})`));
4129
5184
  console.log(
4130
- chalk10.yellow(
5185
+ chalk11.yellow(
4131
5186
  "Note: Agents must still pass `--workspace <id>` explicitly on every mutating operation. This setting is a human convenience and is NOT read by agents."
4132
5187
  )
4133
5188
  );
@@ -4144,17 +5199,17 @@ async function workspaceCreate(name, options) {
4144
5199
  console.log(JSON.stringify(ws, null, 2));
4145
5200
  return;
4146
5201
  }
4147
- console.log(chalk10.green(`Workspace created: ${ws.name}`));
5202
+ console.log(chalk11.green(`Workspace created: ${ws.name}`));
4148
5203
  console.log(` ID: ${ws.workspaceId}`);
4149
5204
  console.log(` Owner: ${ws.ownerUserId}`);
4150
5205
  const yes = options.yes === true || await confirm4("Switch to this workspace now? [y/N]", false);
4151
5206
  if (yes) {
4152
5207
  await refreshWorkspacesCache(config);
4153
5208
  await config.set("activeWorkspaceId", ws.workspaceId);
4154
- console.log(chalk10.green(`Active workspace set: ${ws.name} (owner)`));
5209
+ console.log(chalk11.green(`Active workspace set: ${ws.name} (owner)`));
4155
5210
  } else {
4156
5211
  console.log(
4157
- chalk10.gray(
5212
+ chalk11.gray(
4158
5213
  `Workspace ready. Run \`bisque workspace switch ${ws.workspaceId}\` to make it active.`
4159
5214
  )
4160
5215
  );
@@ -4169,7 +5224,7 @@ async function workspaceInvite(options) {
4169
5224
  const targetWs = await resolveSubcommandWorkspaceId(options, config);
4170
5225
  if (!targetWs) {
4171
5226
  console.error(
4172
- chalk10.red(
5227
+ chalk11.red(
4173
5228
  "Error: no active workspace. Use --workspace <id> or run `bisque workspace switch`."
4174
5229
  )
4175
5230
  );
@@ -4177,20 +5232,20 @@ async function workspaceInvite(options) {
4177
5232
  }
4178
5233
  const maxRedemptions = options.maxUses ? Number(options.maxUses) : 1;
4179
5234
  if (!Number.isInteger(maxRedemptions) || maxRedemptions < 1 || maxRedemptions > 100) {
4180
- console.error(chalk10.red("Error: --max-uses must be an integer between 1 and 100."));
5235
+ console.error(chalk11.red("Error: --max-uses must be an integer between 1 and 100."));
4181
5236
  process.exit(1);
4182
5237
  }
4183
5238
  const expiresIn = options.expires ?? "7d";
4184
5239
  const expiresAt = parseExpiresAt(expiresIn);
4185
5240
  if (expiresAt === void 0) {
4186
5241
  console.error(
4187
- chalk10.red("Error: --expires must be a duration like '7d', '24h', '30m' (max 30d).")
5242
+ chalk11.red("Error: --expires must be a duration like '7d', '24h', '30m' (max 30d).")
4188
5243
  );
4189
5244
  process.exit(1);
4190
5245
  }
4191
5246
  const maxExpiresAt = Math.floor(Date.now() / 1e3) + 30 * 86400;
4192
5247
  if (expiresAt > maxExpiresAt) {
4193
- console.error(chalk10.red("Error: --expires must not exceed 30 days."));
5248
+ console.error(chalk11.red("Error: --expires must not exceed 30 days."));
4194
5249
  process.exit(1);
4195
5250
  }
4196
5251
  const body = {
@@ -4208,13 +5263,13 @@ async function workspaceInvite(options) {
4208
5263
  return;
4209
5264
  }
4210
5265
  const expiresHuman = new Date(created.expiresAt * 1e3).toLocaleString();
4211
- console.log(chalk10.green(`Invite code created: ${chalk10.bold(created.code)}`));
5266
+ console.log(chalk11.green(`Invite code created: ${chalk11.bold(created.code)}`));
4212
5267
  console.log(` Role: ${created.roleOnRedeem}`);
4213
5268
  console.log(` Max uses: ${created.maxRedemptions}`);
4214
5269
  console.log(` Expires: ${expiresHuman}`);
4215
5270
  console.log("");
4216
- console.log(chalk10.gray("Share with your teammate:"));
4217
- console.log(chalk10.bold(` bisque workspace join ${created.code}`));
5271
+ console.log(chalk11.gray("Share with your teammate:"));
5272
+ console.log(chalk11.bold(` bisque workspace join ${created.code}`));
4218
5273
  } catch (error) {
4219
5274
  exitWithError(error);
4220
5275
  }
@@ -4230,11 +5285,11 @@ async function workspaceJoin(code, options) {
4230
5285
  return;
4231
5286
  }
4232
5287
  const verb = result.idempotent ? "Already a member of" : "Joined";
4233
- console.log(chalk10.green(`${verb} workspace: ${result.workspaceId} (role: ${result.role})`));
5288
+ console.log(chalk11.green(`${verb} workspace: ${result.workspaceId} (role: ${result.role})`));
4234
5289
  const yes = options.yes === true || await confirm4("Switch to this workspace now? [y/N]", false);
4235
5290
  if (yes) {
4236
5291
  await config.set("activeWorkspaceId", result.workspaceId);
4237
- console.log(chalk10.green("Active workspace updated."));
5292
+ console.log(chalk11.green("Active workspace updated."));
4238
5293
  }
4239
5294
  } catch (error) {
4240
5295
  exitWithError(error);
@@ -4246,7 +5301,7 @@ async function workspaceMembers(options) {
4246
5301
  const targetWs = await resolveSubcommandWorkspaceId(options, config);
4247
5302
  if (!targetWs) {
4248
5303
  console.error(
4249
- chalk10.red(
5304
+ chalk11.red(
4250
5305
  "Error: no workspace selected. Use --workspace <id> or run `bisque workspace switch`."
4251
5306
  )
4252
5307
  );
@@ -4260,15 +5315,15 @@ async function workspaceMembers(options) {
4260
5315
  return;
4261
5316
  }
4262
5317
  if (members.length === 0) {
4263
- console.log(chalk10.yellow("No members found."));
5318
+ console.log(chalk11.yellow("No members found."));
4264
5319
  return;
4265
5320
  }
4266
- const table = new Table5({
5321
+ const table = new Table6({
4267
5322
  head: [
4268
- chalk10.cyan("User ID"),
4269
- chalk10.cyan("Role"),
4270
- chalk10.cyan("Joined"),
4271
- chalk10.cyan("Last accessed")
5323
+ chalk11.cyan("User ID"),
5324
+ chalk11.cyan("Role"),
5325
+ chalk11.cyan("Joined"),
5326
+ chalk11.cyan("Last accessed")
4272
5327
  ],
4273
5328
  style: { head: [], border: ["gray"] }
4274
5329
  });
@@ -4278,7 +5333,7 @@ async function workspaceMembers(options) {
4278
5333
  table.push([m.userId, m.role, joined, last]);
4279
5334
  }
4280
5335
  console.log(table.toString());
4281
- console.log(chalk10.gray(`
5336
+ console.log(chalk11.gray(`
4282
5337
  Total: ${members.length} member(s)`));
4283
5338
  } catch (error) {
4284
5339
  exitWithError(error);
@@ -4290,7 +5345,7 @@ async function workspaceTransfer(targetUserId, options) {
4290
5345
  const targetWs = await resolveSubcommandWorkspaceId(options, config);
4291
5346
  if (!targetWs) {
4292
5347
  console.error(
4293
- chalk10.red(
5348
+ chalk11.red(
4294
5349
  "Error: no workspace selected. Use --workspace <id> or run `bisque workspace switch`."
4295
5350
  )
4296
5351
  );
@@ -4306,7 +5361,7 @@ async function workspaceTransfer(targetUserId, options) {
4306
5361
  console.log(JSON.stringify(result2, null, 2));
4307
5362
  return;
4308
5363
  }
4309
- console.log(chalk10.green(`Successor cleared for workspace ${result2.name}.`));
5364
+ console.log(chalk11.green(`Successor cleared for workspace ${result2.name}.`));
4310
5365
  return;
4311
5366
  }
4312
5367
  const result = await client.post(`/v1/workspaces/${targetWs}/ownership-transfer`, {
@@ -4318,7 +5373,7 @@ async function workspaceTransfer(targetUserId, options) {
4318
5373
  return;
4319
5374
  }
4320
5375
  console.log(
4321
- chalk10.green(`Ownership transferred to ${targetUserId} for workspace ${result.name}.`)
5376
+ chalk11.green(`Ownership transferred to ${targetUserId} for workspace ${result.name}.`)
4322
5377
  );
4323
5378
  } catch (error) {
4324
5379
  exitWithError(error);
@@ -4330,7 +5385,7 @@ async function workspaceSetSuccessor(userIdOrNull, options) {
4330
5385
  const targetWs = await resolveSubcommandWorkspaceId(options, config);
4331
5386
  if (!targetWs) {
4332
5387
  console.error(
4333
- chalk10.red(
5388
+ chalk11.red(
4334
5389
  "Error: no workspace selected. Use --workspace <id> or run `bisque workspace switch`."
4335
5390
  )
4336
5391
  );
@@ -4347,7 +5402,7 @@ async function workspaceSetSuccessor(userIdOrNull, options) {
4347
5402
  return;
4348
5403
  }
4349
5404
  console.log(
4350
- chalk10.green(
5405
+ chalk11.green(
4351
5406
  successorUserId ? `Successor set to ${successorUserId} for workspace ${result.name}.` : `Successor cleared for workspace ${result.name}.`
4352
5407
  )
4353
5408
  );
@@ -4361,7 +5416,7 @@ async function workspaceLeave(options) {
4361
5416
  const targetWs = await resolveSubcommandWorkspaceId(options, config);
4362
5417
  if (!targetWs) {
4363
5418
  console.error(
4364
- chalk10.red(
5419
+ chalk11.red(
4365
5420
  "Error: no workspace selected. Use --workspace <id> or run `bisque workspace switch`."
4366
5421
  )
4367
5422
  );
@@ -4369,7 +5424,7 @@ async function workspaceLeave(options) {
4369
5424
  }
4370
5425
  const selfId = await getSelfUserId(config);
4371
5426
  if (!selfId) {
4372
- console.error(chalk10.red("Error: could not resolve your user id -- are you authenticated?"));
5427
+ console.error(chalk11.red("Error: could not resolve your user id -- are you authenticated?"));
4373
5428
  process.exit(2);
4374
5429
  }
4375
5430
  const ok = await confirm4(
@@ -4377,7 +5432,7 @@ async function workspaceLeave(options) {
4377
5432
  options.yes === true
4378
5433
  );
4379
5434
  if (!ok) {
4380
- console.log(chalk10.gray("Aborted."));
5435
+ console.log(chalk11.gray("Aborted."));
4381
5436
  return;
4382
5437
  }
4383
5438
  const client = new ApiClient(config, { workspaceId: targetWs });
@@ -4390,7 +5445,7 @@ async function workspaceLeave(options) {
4390
5445
  console.log(JSON.stringify({ workspaceId: targetWs, left: true }, null, 2));
4391
5446
  return;
4392
5447
  }
4393
- console.log(chalk10.green(`Left workspace ${targetWs}.`));
5448
+ console.log(chalk11.green(`Left workspace ${targetWs}.`));
4394
5449
  } catch (error) {
4395
5450
  exitWithError(error);
4396
5451
  }
@@ -4404,12 +5459,12 @@ function renderStructure(structure) {
4404
5459
  ];
4405
5460
  const totalFolders = sections.reduce((n, s) => n + s.folders.length, 0);
4406
5461
  if (totalFolders === 0) {
4407
- console.log(chalk10.yellow("Workspace is empty -- no folders found."));
5462
+ console.log(chalk11.yellow("Workspace is empty -- no folders found."));
4408
5463
  return;
4409
5464
  }
4410
5465
  for (const { label, folders } of sections) {
4411
5466
  if (folders.length === 0) continue;
4412
- console.log(chalk10.bold.cyan(`
5467
+ console.log(chalk11.bold.cyan(`
4413
5468
  ${label}`));
4414
5469
  for (const folder of folders) {
4415
5470
  console.log(` ${folder}`);
@@ -4423,12 +5478,12 @@ function renderDeleteResult(result, json) {
4423
5478
  return;
4424
5479
  }
4425
5480
  if (result.status === "deleted") {
4426
- console.log(chalk10.green("Workspace deleted (cascade complete)."));
5481
+ console.log(chalk11.green("Workspace deleted (cascade complete)."));
4427
5482
  return;
4428
5483
  }
4429
5484
  if (result.status === "pending-async") {
4430
5485
  console.log(
4431
- chalk10.green(
5486
+ chalk11.green(
4432
5487
  `Workspace soft-deleted; large-cascade scheduled (job: ${result.jobId}). Membership has been revoked; data drain happens asynchronously.`
4433
5488
  )
4434
5489
  );
@@ -4436,7 +5491,7 @@ function renderDeleteResult(result, json) {
4436
5491
  }
4437
5492
  if (result.status === "transferred") {
4438
5493
  console.log(
4439
- chalk10.green(
5494
+ chalk11.green(
4440
5495
  `Ownership transferred to ${result.workspace.ownerUserId}. New owner of '${result.workspace.name}'.`
4441
5496
  )
4442
5497
  );
@@ -4444,23 +5499,23 @@ function renderDeleteResult(result, json) {
4444
5499
  }
4445
5500
  }
4446
5501
  function renderDeleteConfirmMenu(resp) {
4447
- console.log(chalk10.bold("\nDelete workspace -- confirmation required"));
5502
+ console.log(chalk11.bold("\nDelete workspace -- confirmation required"));
4448
5503
  console.log(` Workspace: ${resp.workspace.name} (${resp.workspace.id})`);
4449
5504
  console.log(` Members: ${resp.workspace.memberCount}`);
4450
5505
  console.log(` Admins: ${resp.workspace.adminCount}`);
4451
- console.log(chalk10.bold("\nAvailable actions:"));
5506
+ console.log(chalk11.bold("\nAvailable actions:"));
4452
5507
  for (const opt of resp.options) {
4453
5508
  if (opt.action === "transfer-to") {
4454
5509
  console.log(` - transfer-to=<userId> ${opt.label}`);
4455
5510
  if (opt.validRecipients && opt.validRecipients.length > 0) {
4456
- console.log(chalk10.gray(` eligible: ${opt.validRecipients.join(", ")}`));
5511
+ console.log(chalk11.gray(` eligible: ${opt.validRecipients.join(", ")}`));
4457
5512
  }
4458
5513
  } else {
4459
5514
  console.log(` - cascade-delete ${opt.label}`);
4460
5515
  }
4461
5516
  }
4462
5517
  console.log(
4463
- chalk10.gray("\nRe-run with --confirm cascade-delete OR --confirm transfer-to=<userId>")
5518
+ chalk11.gray("\nRe-run with --confirm cascade-delete OR --confirm transfer-to=<userId>")
4464
5519
  );
4465
5520
  }
4466
5521
  async function buildDeleteQueryString(action, targetWs, assumeYes) {
@@ -4470,7 +5525,7 @@ async function buildDeleteQueryString(action, targetWs, assumeYes) {
4470
5525
  assumeYes
4471
5526
  );
4472
5527
  if (!ok) {
4473
- console.log(chalk10.gray("Aborted."));
5528
+ console.log(chalk11.gray("Aborted."));
4474
5529
  return void 0;
4475
5530
  }
4476
5531
  return "confirm=true&action=cascade-delete";
@@ -4478,13 +5533,13 @@ async function buildDeleteQueryString(action, targetWs, assumeYes) {
4478
5533
  if (action.startsWith("transfer-to=")) {
4479
5534
  const recipient = action.slice("transfer-to=".length);
4480
5535
  if (!recipient) {
4481
- console.error(chalk10.red("Error: --confirm transfer-to=<userId> requires a userId."));
5536
+ console.error(chalk11.red("Error: --confirm transfer-to=<userId> requires a userId."));
4482
5537
  process.exit(1);
4483
5538
  }
4484
5539
  return `confirm=true&action=transfer-to&recipient=${encodeURIComponent(recipient)}`;
4485
5540
  }
4486
5541
  console.error(
4487
- chalk10.red(
5542
+ chalk11.red(
4488
5543
  `Error: --confirm must be 'cascade-delete' or 'transfer-to=<userId>' (got: ${action})`
4489
5544
  )
4490
5545
  );
@@ -4497,7 +5552,7 @@ async function runDeletePhase1(client, targetWs, json) {
4497
5552
  console.log(JSON.stringify({ status: "deleted" }, null, 2));
4498
5553
  return;
4499
5554
  }
4500
- console.log(chalk10.green("Workspace deleted."));
5555
+ console.log(chalk11.green("Workspace deleted."));
4501
5556
  return;
4502
5557
  }
4503
5558
  if (json) {
@@ -4522,7 +5577,7 @@ async function workspaceDelete(options) {
4522
5577
  const targetWs = await resolveSubcommandWorkspaceId(options, config);
4523
5578
  if (!targetWs) {
4524
5579
  console.error(
4525
- chalk10.red(
5580
+ chalk11.red(
4526
5581
  "Error: no workspace selected. Use --workspace <id> or run `bisque workspace switch`."
4527
5582
  )
4528
5583
  );
@@ -4553,7 +5608,7 @@ async function workspaceDelete(options) {
4553
5608
  exitWithError(error);
4554
5609
  }
4555
5610
  const message = unwrapApiErrorMessage(error);
4556
- console.error(chalk10.red(`Error: ${message}`));
5611
+ console.error(chalk11.red(`Error: ${message}`));
4557
5612
  process.exit(1);
4558
5613
  }
4559
5614
  }
@@ -4607,7 +5662,7 @@ function registerWorkspaceCommand(program2) {
4607
5662
  renderStructure(result.structure);
4608
5663
  } catch (error) {
4609
5664
  const message = error instanceof Error ? error.message : String(error);
4610
- console.error(chalk10.red(`Error: ${message}`));
5665
+ console.error(chalk11.red(`Error: ${message}`));
4611
5666
  process.exitCode = 1;
4612
5667
  }
4613
5668
  });
@@ -4619,15 +5674,15 @@ function registerWorkspaceCommand(program2) {
4619
5674
  console.log(JSON.stringify(ws, null, 2));
4620
5675
  return;
4621
5676
  }
4622
- console.log(chalk10.bold("\nWorkspace Details\n"));
4623
- console.log(`${chalk10.cyan("ID:")} ${ws.workspaceId}`);
4624
- console.log(`${chalk10.cyan("Name:")} ${ws.name}`);
4625
- console.log(`${chalk10.cyan("User ID:")} ${ws.userId}`);
4626
- console.log(`${chalk10.cyan("Created:")} ${new Date(ws.createdAt).toLocaleString()}`);
4627
- console.log(`${chalk10.cyan("Updated:")} ${new Date(ws.updatedAt).toLocaleString()}`);
5677
+ console.log(chalk11.bold("\nWorkspace Details\n"));
5678
+ console.log(`${chalk11.cyan("ID:")} ${ws.workspaceId}`);
5679
+ console.log(`${chalk11.cyan("Name:")} ${ws.name}`);
5680
+ console.log(`${chalk11.cyan("User ID:")} ${ws.userId}`);
5681
+ console.log(`${chalk11.cyan("Created:")} ${new Date(ws.createdAt).toLocaleString()}`);
5682
+ console.log(`${chalk11.cyan("Updated:")} ${new Date(ws.updatedAt).toLocaleString()}`);
4628
5683
  } catch (error) {
4629
5684
  const message = error instanceof Error ? error.message : String(error);
4630
- console.error(chalk10.red(`Error: ${message}`));
5685
+ console.error(chalk11.red(`Error: ${message}`));
4631
5686
  process.exitCode = 1;
4632
5687
  }
4633
5688
  });
@@ -4639,12 +5694,12 @@ function registerWorkspaceCommand(program2) {
4639
5694
  console.log(JSON.stringify(ws, null, 2));
4640
5695
  return;
4641
5696
  }
4642
- console.log(chalk10.bold("\nDefault Workspace\n"));
4643
- console.log(`${chalk10.cyan("ID:")} ${ws.workspaceId}`);
4644
- console.log(`${chalk10.cyan("Name:")} ${ws.name}`);
4645
- console.log(`${chalk10.cyan("User ID:")} ${ws.userId}`);
4646
- console.log(`${chalk10.cyan("Created:")} ${new Date(ws.createdAt).toLocaleString()}`);
4647
- console.log(`${chalk10.cyan("Updated:")} ${new Date(ws.updatedAt).toLocaleString()}`);
5697
+ console.log(chalk11.bold("\nDefault Workspace\n"));
5698
+ console.log(`${chalk11.cyan("ID:")} ${ws.workspaceId}`);
5699
+ console.log(`${chalk11.cyan("Name:")} ${ws.name}`);
5700
+ console.log(`${chalk11.cyan("User ID:")} ${ws.userId}`);
5701
+ console.log(`${chalk11.cyan("Created:")} ${new Date(ws.createdAt).toLocaleString()}`);
5702
+ console.log(`${chalk11.cyan("Updated:")} ${new Date(ws.updatedAt).toLocaleString()}`);
4648
5703
  } catch (error) {
4649
5704
  exitWithError(error);
4650
5705
  }
@@ -4685,6 +5740,7 @@ function registerCommands(program2) {
4685
5740
  registerPluginCommand(program2);
4686
5741
  registerSkillCommand(program2);
4687
5742
  registerMeasureCommand(program2);
5743
+ registerDriftCommand(program2);
4688
5744
  }
4689
5745
 
4690
5746
  // src/index.ts