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.
- package/dist/index.js +1247 -191
- 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
|
-
...
|
|
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(
|
|
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
|
-
|
|
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/
|
|
2226
|
-
import {
|
|
2227
|
-
import
|
|
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
|
|
2526
|
-
import
|
|
3530
|
+
import chalk4 from "chalk";
|
|
3531
|
+
import Table3 from "cli-table3";
|
|
2527
3532
|
init_api_client();
|
|
2528
|
-
function
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
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
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
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
|
|
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(
|
|
2627
|
-
console.log(
|
|
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(
|
|
3715
|
+
console.log(chalk5.bold.cyan(`
|
|
2679
3716
|
=== ${matchedName} ===
|
|
2680
3717
|
`));
|
|
2681
3718
|
if (agentsMd) {
|
|
2682
|
-
console.log(
|
|
3719
|
+
console.log(chalk5.bold("AGENTS.md:"));
|
|
2683
3720
|
console.log(agentsMd.content);
|
|
2684
3721
|
console.log();
|
|
2685
3722
|
} else {
|
|
2686
|
-
console.log(
|
|
3723
|
+
console.log(chalk5.yellow("No AGENTS.md found for this project.\n"));
|
|
2687
3724
|
}
|
|
2688
3725
|
if (frontmind.length > 0) {
|
|
2689
|
-
console.log(
|
|
3726
|
+
console.log(chalk5.bold(`Frontmind (${frontmind.length} files):`));
|
|
2690
3727
|
for (const doc of frontmind) {
|
|
2691
|
-
console.log(
|
|
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(
|
|
3734
|
+
console.log(chalk5.gray("No frontmind files.\n"));
|
|
2698
3735
|
}
|
|
2699
3736
|
if (backmind.length > 0) {
|
|
2700
|
-
console.log(
|
|
3737
|
+
console.log(chalk5.bold(`Backmind (${backmind.length} files available):`));
|
|
2701
3738
|
for (const entry of backmind) {
|
|
2702
|
-
console.log(
|
|
3739
|
+
console.log(chalk5.gray(` ${entry.filepath} \u2014 ${entry.title}`));
|
|
2703
3740
|
}
|
|
2704
|
-
console.log(
|
|
3741
|
+
console.log(chalk5.gray("\n Use: bisque get <documentId> to load any backmind file."));
|
|
2705
3742
|
} else {
|
|
2706
|
-
console.log(
|
|
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
|
|
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 ` ${
|
|
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} ${
|
|
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(
|
|
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
|
|
3801
|
+
import chalk7 from "chalk";
|
|
2765
3802
|
function healthColor(health) {
|
|
2766
3803
|
switch (health) {
|
|
2767
3804
|
case "healthy":
|
|
2768
|
-
return
|
|
3805
|
+
return chalk7.green(health);
|
|
2769
3806
|
case "needs-attention":
|
|
2770
|
-
return
|
|
3807
|
+
return chalk7.yellow(health);
|
|
2771
3808
|
case "needs-maintenance":
|
|
2772
|
-
return
|
|
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(` ${
|
|
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(
|
|
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(
|
|
3847
|
+
console.log(chalk7.bold("Mechanical Fixes"));
|
|
2811
3848
|
for (const lock of mechanical.taskLocksReleased) {
|
|
2812
3849
|
console.log(
|
|
2813
|
-
` ${
|
|
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
|
-
` ${
|
|
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
|
-
` ${
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
3063
|
-
import
|
|
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(
|
|
4124
|
+
console.log(chalk8.yellow("No personas found."));
|
|
3088
4125
|
return;
|
|
3089
4126
|
}
|
|
3090
|
-
const table = new
|
|
4127
|
+
const table = new Table4({
|
|
3091
4128
|
head: [
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
3698
|
-
import
|
|
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(
|
|
4811
|
+
console.log(chalk9.yellow("No tasks found."));
|
|
3775
4812
|
return;
|
|
3776
4813
|
}
|
|
3777
|
-
const table = new
|
|
4814
|
+
const table = new Table5({
|
|
3778
4815
|
head: [
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
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(
|
|
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
|
|
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(
|
|
4976
|
+
console.log(chalk10.dim(" (empty)"));
|
|
3940
4977
|
return;
|
|
3941
4978
|
}
|
|
3942
4979
|
for (const doc of data.documents) {
|
|
3943
|
-
console.log(
|
|
3944
|
-
console.log(
|
|
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(
|
|
4985
|
+
console.log(chalk10.dim(`Returned ${data.totalReturned}/${data.maxItems} max items`));
|
|
3949
4986
|
if (data.cursor) {
|
|
3950
4987
|
console.log(
|
|
3951
|
-
|
|
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
|
|
4038
|
-
import
|
|
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(
|
|
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(
|
|
5143
|
+
console.log(chalk11.bold.cyan(`
|
|
4089
5144
|
${label}`));
|
|
4090
|
-
const table = new
|
|
5145
|
+
const table = new Table6({
|
|
4091
5146
|
head: [
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
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(
|
|
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(
|
|
5174
|
+
console.error(chalk11.red(`Error: workspace '${id}' not found among your memberships.`));
|
|
4120
5175
|
console.error(
|
|
4121
|
-
|
|
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(
|
|
5183
|
+
console.log(chalk11.green(`Active workspace set: ${match.name} (${match.role})`));
|
|
4129
5184
|
console.log(
|
|
4130
|
-
|
|
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(
|
|
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(
|
|
5209
|
+
console.log(chalk11.green(`Active workspace set: ${ws.name} (owner)`));
|
|
4155
5210
|
} else {
|
|
4156
5211
|
console.log(
|
|
4157
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
4217
|
-
console.log(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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(
|
|
5318
|
+
console.log(chalk11.yellow("No members found."));
|
|
4264
5319
|
return;
|
|
4265
5320
|
}
|
|
4266
|
-
const table = new
|
|
5321
|
+
const table = new Table6({
|
|
4267
5322
|
head: [
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
4623
|
-
console.log(`${
|
|
4624
|
-
console.log(`${
|
|
4625
|
-
console.log(`${
|
|
4626
|
-
console.log(`${
|
|
4627
|
-
console.log(`${
|
|
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(
|
|
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(
|
|
4643
|
-
console.log(`${
|
|
4644
|
-
console.log(`${
|
|
4645
|
-
console.log(`${
|
|
4646
|
-
console.log(`${
|
|
4647
|
-
console.log(`${
|
|
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
|