pi-anchor 0.2.1 → 0.2.3
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/README.md +101 -195
- package/README_zh.md +115 -205
- package/dist/{chunk-B652T7QR.js → chunk-G72UHPBN.js} +218 -121
- package/dist/extension.cjs +286 -168
- package/dist/extension.js +73 -48
- package/dist/index.cjs +220 -121
- package/dist/index.d.cts +7 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.js +5 -1
- package/package.json +3 -3
- package/src/decay.ts +54 -52
- package/src/extension.ts +74 -55
- package/src/matcher.ts +27 -6
- package/src/settlement.ts +5 -3
- package/src/store.ts +75 -18
- package/src/tui.ts +82 -59
- package/src/types.ts +1 -0
|
@@ -22,20 +22,34 @@ var DEFAULT_DECAY_POLICY = {
|
|
|
22
22
|
// src/matcher.ts
|
|
23
23
|
import path from "path";
|
|
24
24
|
function globToRegExp(pattern) {
|
|
25
|
-
const
|
|
25
|
+
const collapsed = pattern.slice(0, 512).replace(/\/+(\*\*\/+)+/g, "/**/").replace(/\*{3,}/g, "**");
|
|
26
|
+
const escaped = collapsed.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\/\*\*\//g, "___GLOB_DIR_SLASH___").replace(/\*\*/g, "___GLOB_STAR_STAR___").replace(/\*/g, "[^/]*").replace(/___GLOB_DIR_SLASH___/g, "(?:/|/(?:[^/]+/)+)").replace(/___GLOB_STAR_STAR___/g, ".*");
|
|
26
27
|
return new RegExp(`^${escaped}$`);
|
|
27
28
|
}
|
|
28
29
|
function normalizePath(filePath, baseCwd) {
|
|
29
30
|
if (typeof filePath !== "string") return "";
|
|
30
31
|
let norm = filePath.trim().replace(/\\/g, "/").replace(/^\.\//, "");
|
|
31
|
-
if (baseCwd && (path.isAbsolute(filePath) || norm.startsWith("/"))) {
|
|
32
|
-
const normBase = baseCwd.trim().replace(/\\/g, "/");
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
if (baseCwd && (path.isAbsolute(filePath) || norm.startsWith("/") || /^[a-zA-Z]:\//.test(norm))) {
|
|
33
|
+
const normBase = baseCwd.trim().replace(/\\/g, "/").replace(/\/+$/, "");
|
|
34
|
+
const lowerNorm = norm.toLowerCase();
|
|
35
|
+
const lowerBase = normBase.toLowerCase();
|
|
36
|
+
if (lowerNorm === lowerBase) {
|
|
37
|
+
norm = "";
|
|
38
|
+
} else if (lowerNorm.startsWith(lowerBase + "/")) {
|
|
39
|
+
norm = norm.slice(normBase.length + 1).replace(/^\/+/, "");
|
|
35
40
|
}
|
|
36
41
|
}
|
|
37
42
|
return norm;
|
|
38
43
|
}
|
|
44
|
+
function isSameOrSubPath(anchorCwd, targetCwd) {
|
|
45
|
+
if (!anchorCwd || !targetCwd) return false;
|
|
46
|
+
const normA = anchorCwd.trim().replace(/\\/g, "/").replace(/\/+$/, "");
|
|
47
|
+
const normT = targetCwd.trim().replace(/\\/g, "/").replace(/\/+$/, "");
|
|
48
|
+
const isWinStyle = process.platform === "win32" || /^[a-zA-Z]:\//.test(normA) || /^[a-zA-Z]:\//.test(normT);
|
|
49
|
+
const cmpA = isWinStyle ? normA.toLowerCase() : normA;
|
|
50
|
+
const cmpT = isWinStyle ? normT.toLowerCase() : normT;
|
|
51
|
+
return cmpA === cmpT || cmpT.startsWith(cmpA + "/") || cmpA.startsWith(cmpT + "/");
|
|
52
|
+
}
|
|
39
53
|
function matchAnchorAgainstTouchedFiles(anchor, touchedFiles) {
|
|
40
54
|
if (!anchor.files || anchor.files.length === 0) return null;
|
|
41
55
|
const normTouched = touchedFiles.map((f) => normalizePath(f, anchor.cwd)).filter(Boolean);
|
|
@@ -400,9 +414,19 @@ var AnchorStore = class {
|
|
|
400
414
|
}
|
|
401
415
|
try {
|
|
402
416
|
const data = JSON.parse(raw);
|
|
403
|
-
if (!Array.isArray(data.anchors)) {
|
|
417
|
+
if (!data || typeof data !== "object" || !Array.isArray(data.anchors)) {
|
|
404
418
|
throw new Error("Invalid state: anchors must be array");
|
|
405
419
|
}
|
|
420
|
+
data.anchors = data.anchors.filter((a) => a && typeof a === "object" && typeof a.id === "string" && typeof a.title === "string").map((a) => ({
|
|
421
|
+
...a,
|
|
422
|
+
files: Array.isArray(a.files) ? a.files.filter((f) => typeof f === "string") : [],
|
|
423
|
+
tags: Array.isArray(a.tags) ? a.tags.filter((t) => typeof t === "string") : [],
|
|
424
|
+
decay: {
|
|
425
|
+
activeDays: Number.isFinite(a.decay?.activeDays) && a.decay.activeDays > 0 ? a.decay.activeDays : DURABLE_DECAY_POLICY.activeDays,
|
|
426
|
+
sleepDays: Number.isFinite(a.decay?.sleepDays) && a.decay.sleepDays > 0 ? a.decay.sleepDays : DURABLE_DECAY_POLICY.sleepDays,
|
|
427
|
+
graveyardDays: Number.isFinite(a.decay?.graveyardDays) && a.decay.graveyardDays > 0 ? a.decay.graveyardDays : DURABLE_DECAY_POLICY.graveyardDays
|
|
428
|
+
}
|
|
429
|
+
}));
|
|
406
430
|
return data;
|
|
407
431
|
} catch {
|
|
408
432
|
const backupPath = path2.join(this.storageDir, `state.corrupt.${Date.now()}.json`);
|
|
@@ -422,16 +446,19 @@ var AnchorStore = class {
|
|
|
422
446
|
*/
|
|
423
447
|
saveState(state) {
|
|
424
448
|
this.ensureDirs();
|
|
425
|
-
const
|
|
449
|
+
const rand = Math.random().toString(36).slice(2, 7);
|
|
450
|
+
const tempPath = path2.join(this.storageDir, `state.tmp.${process.pid}.${Date.now()}.${rand}`);
|
|
426
451
|
fs.writeFileSync(tempPath, JSON.stringify(state, null, 2), "utf-8");
|
|
427
452
|
atomicRenameWithRetry(tempPath, this.statePath);
|
|
428
453
|
}
|
|
429
|
-
generateId(
|
|
430
|
-
const numbers =
|
|
454
|
+
generateId(state) {
|
|
455
|
+
const numbers = state.anchors.map((a) => {
|
|
431
456
|
const match = a.id.match(/^anc-(\d+)$/);
|
|
432
457
|
return match ? parseInt(match[1], 10) : 0;
|
|
433
458
|
}).filter((n) => n > 0);
|
|
434
|
-
const
|
|
459
|
+
const maxExisting = numbers.length > 0 ? Math.max(...numbers) : 0;
|
|
460
|
+
const nextNum = Math.max(maxExisting + 1, state.nextId || 1);
|
|
461
|
+
state.nextId = nextNum + 1;
|
|
435
462
|
return `anc-${nextNum}`;
|
|
436
463
|
}
|
|
437
464
|
/**
|
|
@@ -441,7 +468,7 @@ var AnchorStore = class {
|
|
|
441
468
|
return this.withLock(() => {
|
|
442
469
|
const state = this.loadState();
|
|
443
470
|
const now = Date.now();
|
|
444
|
-
const id = this.generateId(state
|
|
471
|
+
const id = this.generateId(state);
|
|
445
472
|
const cwd = input.cwd ? normalizePath(input.cwd) : "";
|
|
446
473
|
const projectName = input.project ? input.project.trim() : cwd ? path2.basename(cwd) : "global";
|
|
447
474
|
const recurrence = input.recurrence || detectRecurrence(input.title);
|
|
@@ -501,7 +528,7 @@ var AnchorStore = class {
|
|
|
501
528
|
if (filter?.status && a.status !== filter.status) return false;
|
|
502
529
|
if (filter?.priority && a.priority !== filter.priority) return false;
|
|
503
530
|
if (!filter?.all && targetCwd) {
|
|
504
|
-
if (a.files.length > 0 && a.cwd && a.cwd
|
|
531
|
+
if (a.files.length > 0 && a.cwd && !isSameOrSubPath(a.cwd, targetCwd)) {
|
|
505
532
|
return false;
|
|
506
533
|
}
|
|
507
534
|
}
|
|
@@ -602,18 +629,46 @@ var AnchorStore = class {
|
|
|
602
629
|
throw new Error("No archived anchors to undo");
|
|
603
630
|
}
|
|
604
631
|
const raw = fs.readFileSync(this.archivePath, "utf-8");
|
|
605
|
-
const lines = raw.split(
|
|
606
|
-
|
|
632
|
+
const lines = raw.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
|
|
633
|
+
let anchor = null;
|
|
634
|
+
while (lines.length > 0 && !anchor) {
|
|
635
|
+
const candidate = lines.pop();
|
|
636
|
+
try {
|
|
637
|
+
const parsed = JSON.parse(candidate);
|
|
638
|
+
if (parsed && typeof parsed === "object" && typeof parsed.id === "string" && typeof parsed.title === "string") {
|
|
639
|
+
anchor = parsed;
|
|
640
|
+
}
|
|
641
|
+
} catch {
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
if (!anchor) {
|
|
607
645
|
throw new Error("Archive is empty, nothing to undo");
|
|
608
646
|
}
|
|
609
|
-
const lastLine = lines.pop();
|
|
610
|
-
const anchor = JSON.parse(lastLine);
|
|
611
647
|
fs.writeFileSync(this.archivePath, lines.length > 0 ? lines.join("\n") + "\n" : "", "utf-8");
|
|
612
648
|
anchor.status = "active";
|
|
613
649
|
anchor.updatedAt = Date.now();
|
|
614
650
|
delete anchor.evidence;
|
|
651
|
+
delete anchor.settledForDate;
|
|
652
|
+
if (anchor.recurrence === "daily") {
|
|
653
|
+
delete anchor.lastCompletedDate;
|
|
654
|
+
}
|
|
615
655
|
const state = this.loadState();
|
|
616
|
-
state.anchors.
|
|
656
|
+
const existingIdx = state.anchors.findIndex((a) => a.id === anchor.id);
|
|
657
|
+
if (existingIdx !== -1) {
|
|
658
|
+
if (state.anchors[existingIdx].title === anchor.title) {
|
|
659
|
+
state.anchors[existingIdx] = anchor;
|
|
660
|
+
} else {
|
|
661
|
+
anchor.id = this.generateId(state);
|
|
662
|
+
state.anchors.push(anchor);
|
|
663
|
+
}
|
|
664
|
+
} else {
|
|
665
|
+
const match = anchor.id.match(/^anc-(\d+)$/);
|
|
666
|
+
if (match) {
|
|
667
|
+
const restoredNum = parseInt(match[1], 10);
|
|
668
|
+
state.nextId = Math.max(state.nextId || 1, restoredNum + 1);
|
|
669
|
+
}
|
|
670
|
+
state.anchors.push(anchor);
|
|
671
|
+
}
|
|
617
672
|
this.saveState(state);
|
|
618
673
|
return anchor;
|
|
619
674
|
});
|
|
@@ -645,10 +700,19 @@ var AnchorStore = class {
|
|
|
645
700
|
if (!fs.existsSync(this.archivePath)) return [];
|
|
646
701
|
try {
|
|
647
702
|
const raw = fs.readFileSync(this.archivePath, "utf-8");
|
|
648
|
-
const all =
|
|
703
|
+
const all = [];
|
|
704
|
+
for (const l of raw.split(/\r?\n/)) {
|
|
705
|
+
const trimmed = l.trim();
|
|
706
|
+
if (!trimmed) continue;
|
|
707
|
+
try {
|
|
708
|
+
const parsed = JSON.parse(trimmed);
|
|
709
|
+
if (parsed && typeof parsed === "object" && typeof parsed.id === "string") all.push(parsed);
|
|
710
|
+
} catch {
|
|
711
|
+
}
|
|
712
|
+
}
|
|
649
713
|
if (filter?.all || !filter?.cwd) return all;
|
|
650
714
|
const targetCwd = normalizePath(filter.cwd);
|
|
651
|
-
return all.filter((a) => !a.cwd || a.cwd
|
|
715
|
+
return all.filter((a) => !a.cwd || isSameOrSubPath(a.cwd, targetCwd));
|
|
652
716
|
} catch {
|
|
653
717
|
return [];
|
|
654
718
|
}
|
|
@@ -657,10 +721,19 @@ var AnchorStore = class {
|
|
|
657
721
|
if (!fs.existsSync(this.graveyardPath)) return [];
|
|
658
722
|
try {
|
|
659
723
|
const raw = fs.readFileSync(this.graveyardPath, "utf-8");
|
|
660
|
-
const all =
|
|
724
|
+
const all = [];
|
|
725
|
+
for (const l of raw.split(/\r?\n/)) {
|
|
726
|
+
const trimmed = l.trim();
|
|
727
|
+
if (!trimmed) continue;
|
|
728
|
+
try {
|
|
729
|
+
const parsed = JSON.parse(trimmed);
|
|
730
|
+
if (parsed && typeof parsed === "object" && typeof parsed.id === "string") all.push(parsed);
|
|
731
|
+
} catch {
|
|
732
|
+
}
|
|
733
|
+
}
|
|
661
734
|
if (filter?.all || !filter?.cwd) return all;
|
|
662
735
|
const targetCwd = normalizePath(filter.cwd);
|
|
663
|
-
return all.filter((a) => !a.cwd || a.cwd
|
|
736
|
+
return all.filter((a) => !a.cwd || isSameOrSubPath(a.cwd, targetCwd));
|
|
664
737
|
} catch {
|
|
665
738
|
return [];
|
|
666
739
|
}
|
|
@@ -816,56 +889,58 @@ function evaluateAnchorDecay(anchor, now = Date.now()) {
|
|
|
816
889
|
};
|
|
817
890
|
}
|
|
818
891
|
function sweepStore(store, now = Date.now()) {
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
const
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
if (evalResult.nextStatus
|
|
833
|
-
anchor.status
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
anchor.status
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
892
|
+
return store.withLock(() => {
|
|
893
|
+
const state = store.loadState();
|
|
894
|
+
const result = {
|
|
895
|
+
transitionedToSleeping: [],
|
|
896
|
+
wokenToActive: [],
|
|
897
|
+
evictedToGraveyard: []
|
|
898
|
+
};
|
|
899
|
+
const toEvict = [];
|
|
900
|
+
let stateModified = false;
|
|
901
|
+
for (const anchor of state.anchors) {
|
|
902
|
+
const evalResult = evaluateAnchorDecay(anchor, now);
|
|
903
|
+
if (evalResult.nextStatus === "graveyard") {
|
|
904
|
+
toEvict.push(anchor);
|
|
905
|
+
} else if (evalResult.nextStatus !== anchor.status) {
|
|
906
|
+
if (evalResult.nextStatus === "sleeping" && anchor.status === "active") {
|
|
907
|
+
anchor.status = "sleeping";
|
|
908
|
+
anchor.updatedAt = now;
|
|
909
|
+
result.transitionedToSleeping.push(anchor.id);
|
|
910
|
+
stateModified = true;
|
|
911
|
+
} else if (evalResult.nextStatus === "active" && anchor.status === "sleeping") {
|
|
912
|
+
anchor.status = "active";
|
|
913
|
+
anchor.updatedAt = now;
|
|
914
|
+
result.wokenToActive.push(anchor.id);
|
|
915
|
+
stateModified = true;
|
|
916
|
+
}
|
|
842
917
|
}
|
|
843
918
|
}
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
919
|
+
if (toEvict.length > 0) {
|
|
920
|
+
const evictIds = new Set(toEvict.map((e) => e.id));
|
|
921
|
+
state.anchors = state.anchors.filter((a) => !evictIds.has(a.id));
|
|
922
|
+
stateModified = true;
|
|
923
|
+
const graveyardLines = toEvict.map((exp) => {
|
|
924
|
+
const rec = {
|
|
925
|
+
...exp,
|
|
926
|
+
status: "graveyard",
|
|
927
|
+
updatedAt: now,
|
|
928
|
+
evictedAt: now,
|
|
929
|
+
evictionReason: `Exceeded decay threshold (${exp.decay.graveyardDays} days untouched)`
|
|
930
|
+
};
|
|
931
|
+
return JSON.stringify(rec);
|
|
932
|
+
}).join("\n") + "\n";
|
|
933
|
+
fs2.appendFileSync(store.graveyardPath, graveyardLines, "utf-8");
|
|
934
|
+
for (const exp of toEvict) {
|
|
935
|
+
result.evictedToGraveyard.push(exp.id);
|
|
936
|
+
}
|
|
862
937
|
}
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
938
|
+
state.lastSweepAt = now;
|
|
939
|
+
if (stateModified) {
|
|
940
|
+
store.saveState(state);
|
|
941
|
+
}
|
|
942
|
+
return result;
|
|
943
|
+
});
|
|
869
944
|
}
|
|
870
945
|
|
|
871
946
|
// src/context_injector.ts
|
|
@@ -990,14 +1065,16 @@ var renderActiveAnchorsContext = renderColdStartAnchorsContext;
|
|
|
990
1065
|
// src/settlement.ts
|
|
991
1066
|
import { execSync } from "child_process";
|
|
992
1067
|
function runPhysicalVerification(anchor, cwd) {
|
|
993
|
-
if (!anchor.verifyCommand) {
|
|
1068
|
+
if (!anchor.verifyCommand?.trim()) {
|
|
994
1069
|
return { success: false, output: "No verification command specified" };
|
|
995
1070
|
}
|
|
1071
|
+
const targetCwd = anchor.cwd || cwd;
|
|
996
1072
|
try {
|
|
997
1073
|
const stdout = execSync(anchor.verifyCommand, {
|
|
998
|
-
cwd,
|
|
1074
|
+
cwd: targetCwd,
|
|
999
1075
|
encoding: "utf-8",
|
|
1000
|
-
timeout:
|
|
1076
|
+
timeout: 8e3,
|
|
1077
|
+
maxBuffer: 4 * 1024 * 1024,
|
|
1001
1078
|
stdio: ["ignore", "pipe", "pipe"]
|
|
1002
1079
|
});
|
|
1003
1080
|
return { success: true, output: stdout.trim() };
|
|
@@ -1086,27 +1163,36 @@ function getDisplayWidth(str) {
|
|
|
1086
1163
|
}
|
|
1087
1164
|
return width;
|
|
1088
1165
|
}
|
|
1166
|
+
function hasActiveUI(ctx) {
|
|
1167
|
+
if (!ctx) return false;
|
|
1168
|
+
try {
|
|
1169
|
+
return Boolean(ctx.hasUI && ctx.ui);
|
|
1170
|
+
} catch {
|
|
1171
|
+
return false;
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1089
1174
|
function truncateToWidth(str, maxWidth, ellipsis = "\u2026") {
|
|
1090
1175
|
if (maxWidth <= 0) return "";
|
|
1091
|
-
const
|
|
1092
|
-
|
|
1176
|
+
const singleLine = str.replace(/[\r\n\t]+/g, " ");
|
|
1177
|
+
const totalWidth = getDisplayWidth(singleLine);
|
|
1178
|
+
if (totalWidth <= maxWidth) return singleLine;
|
|
1093
1179
|
const ellipsisWidth = getDisplayWidth(ellipsis);
|
|
1094
1180
|
const target = Math.max(0, maxWidth - ellipsisWidth);
|
|
1095
1181
|
if (target === 0) return ellipsis.slice(0, maxWidth);
|
|
1096
1182
|
let accumulated = "";
|
|
1097
1183
|
let accumWidth = 0;
|
|
1098
1184
|
let i = 0;
|
|
1099
|
-
while (i <
|
|
1185
|
+
while (i < singleLine.length) {
|
|
1100
1186
|
ANSI_REGEX.lastIndex = i;
|
|
1101
|
-
const match = ANSI_REGEX.exec(
|
|
1187
|
+
const match = ANSI_REGEX.exec(singleLine);
|
|
1102
1188
|
if (match && match.index === i) {
|
|
1103
1189
|
accumulated += match[0];
|
|
1104
1190
|
i += match[0].length;
|
|
1105
1191
|
continue;
|
|
1106
1192
|
}
|
|
1107
|
-
const codePoint =
|
|
1193
|
+
const codePoint = singleLine.codePointAt(i) || 0;
|
|
1108
1194
|
const charLen = codePoint > 65535 ? 2 : 1;
|
|
1109
|
-
const fullChar =
|
|
1195
|
+
const fullChar = singleLine.slice(i, i + charLen);
|
|
1110
1196
|
const charWidth = getDisplayWidth(fullChar);
|
|
1111
1197
|
if (accumWidth + charWidth > target) {
|
|
1112
1198
|
break;
|
|
@@ -1116,7 +1202,7 @@ function truncateToWidth(str, maxWidth, ellipsis = "\u2026") {
|
|
|
1116
1202
|
i += charLen;
|
|
1117
1203
|
}
|
|
1118
1204
|
accumulated = accumulated.replace(/\s*[\(\[\{(【]$/, "");
|
|
1119
|
-
return accumulated + ellipsis + (
|
|
1205
|
+
return accumulated + ellipsis + (singleLine.includes("\x1B") ? "\x1B[0m" : "");
|
|
1120
1206
|
}
|
|
1121
1207
|
function padToWidth(str, targetWidth) {
|
|
1122
1208
|
const truncated = truncateToWidth(str, targetWidth);
|
|
@@ -1192,52 +1278,58 @@ function formatOrigin(anchor) {
|
|
|
1192
1278
|
return path4.basename(anchor) || "global";
|
|
1193
1279
|
}
|
|
1194
1280
|
function updateAnchorStatusBar(ctx, store) {
|
|
1195
|
-
if (!ctx
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1281
|
+
if (!hasActiveUI(ctx)) return;
|
|
1282
|
+
try {
|
|
1283
|
+
const active = store.list({ status: "active", cwd: ctx.cwd });
|
|
1284
|
+
if (active.length === 0) {
|
|
1285
|
+
ctx.ui.setStatus("anchor", void 0);
|
|
1286
|
+
return;
|
|
1287
|
+
}
|
|
1288
|
+
ctx.ui.setStatus("anchor", `\u2316 ${active.length}`);
|
|
1289
|
+
} catch {
|
|
1200
1290
|
}
|
|
1201
|
-
ctx.ui.setStatus("anchor", `\u2316 ${active.length}`);
|
|
1202
1291
|
}
|
|
1203
1292
|
function updateStartupBanner(ctx, store) {
|
|
1204
|
-
if (!ctx
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
const groups = groupAnchorsByQuadrant(active);
|
|
1211
|
-
const sorted = [...groups.today, ...groups.upcoming, ...groups.habits, ...groups.backlog];
|
|
1212
|
-
const topItems = sorted.slice(0, 3);
|
|
1213
|
-
const factory = (_tui, theme) => ({
|
|
1214
|
-
render: (width) => {
|
|
1215
|
-
const maxTitleWidth = Math.max(16, width - 36);
|
|
1216
|
-
const fg = theme?.fg ? theme.fg.bind(theme) : (_c, text) => text;
|
|
1217
|
-
const lines = [
|
|
1218
|
-
fg("accent", `\u2316 Anchor \xB7 ${active.length} active commitments:`)
|
|
1219
|
-
];
|
|
1220
|
-
for (let i = 0; i < topItems.length; i++) {
|
|
1221
|
-
const a = topItems[i];
|
|
1222
|
-
const num = (i + 1).toString().padStart(2, "0");
|
|
1223
|
-
const q = classifyAnchor(a);
|
|
1224
|
-
const tgt = formatTargetDate(a);
|
|
1225
|
-
const titlePadded = truncateToWidth(a.title, maxTitleWidth);
|
|
1226
|
-
lines.push(` \u2022 ${fg("muted", `[${q}]`)} ${fg("dim", num)} ${titlePadded} ${fg("dim", `(${tgt})`)}`);
|
|
1227
|
-
}
|
|
1228
|
-
if (sorted.length > 3) {
|
|
1229
|
-
lines.push(fg("dim", ` (+${sorted.length - 3} more \xB7 run /anchor to inspect)`));
|
|
1230
|
-
}
|
|
1231
|
-
return lines;
|
|
1232
|
-
},
|
|
1233
|
-
invalidate: () => {
|
|
1293
|
+
if (!hasActiveUI(ctx)) return;
|
|
1294
|
+
try {
|
|
1295
|
+
const active = store.list({ status: "active", cwd: ctx.cwd });
|
|
1296
|
+
if (active.length === 0) {
|
|
1297
|
+
ctx.ui.setWidget("anchor-startup", void 0);
|
|
1298
|
+
return;
|
|
1234
1299
|
}
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1300
|
+
const groups = groupAnchorsByQuadrant(active);
|
|
1301
|
+
const sorted = [...groups.today, ...groups.upcoming, ...groups.habits, ...groups.backlog];
|
|
1302
|
+
const topItems = sorted.slice(0, 3);
|
|
1303
|
+
const factory = (_tui, theme) => ({
|
|
1304
|
+
render: (width) => {
|
|
1305
|
+
const maxTitleWidth = Math.max(12, width - 36);
|
|
1306
|
+
const fg = theme?.fg ? theme.fg.bind(theme) : (_c, text) => text;
|
|
1307
|
+
const lines = [
|
|
1308
|
+
truncateToWidth(fg("accent", `\u2316 Anchor \xB7 ${active.length} active commitments:`), width)
|
|
1309
|
+
];
|
|
1310
|
+
for (let i = 0; i < topItems.length; i++) {
|
|
1311
|
+
const a = topItems[i];
|
|
1312
|
+
const num = a.id.replace(/^anc-/, "").padStart(2, "0");
|
|
1313
|
+
const q = classifyAnchor(a);
|
|
1314
|
+
const tgt = formatTargetDate(a);
|
|
1315
|
+
const titlePadded = truncateToWidth(a.title, maxTitleWidth);
|
|
1316
|
+
lines.push(truncateToWidth(` \u2022 ${fg("muted", `[${q}]`)} ${fg("dim", num)} ${titlePadded} ${fg("dim", `(${tgt})`)}`, width));
|
|
1317
|
+
}
|
|
1318
|
+
if (sorted.length > 3) {
|
|
1319
|
+
lines.push(truncateToWidth(fg("dim", ` (+${sorted.length - 3} more \xB7 run /anchor to inspect)`), width));
|
|
1320
|
+
}
|
|
1321
|
+
return lines;
|
|
1322
|
+
},
|
|
1323
|
+
invalidate: () => {
|
|
1324
|
+
}
|
|
1325
|
+
});
|
|
1326
|
+
const content = ctx.ui?.theme ? factory : factory().render(80);
|
|
1327
|
+
ctx.ui.setWidget("anchor-startup", content, { placement: "aboveEditor" });
|
|
1328
|
+
} catch {
|
|
1329
|
+
}
|
|
1238
1330
|
}
|
|
1239
1331
|
async function openAnchorDashboard(ctx, store) {
|
|
1240
|
-
if (!ctx
|
|
1332
|
+
if (!hasActiveUI(ctx)) return;
|
|
1241
1333
|
const list = store.list({ status: "active", cwd: ctx.cwd });
|
|
1242
1334
|
if (list.length === 0) {
|
|
1243
1335
|
ctx.ui.notify("\u2316 No active anchors. Use /pin <task> to record.", "info");
|
|
@@ -1252,20 +1344,23 @@ async function openAnchorDashboard(ctx, store) {
|
|
|
1252
1344
|
];
|
|
1253
1345
|
const optionMap = /* @__PURE__ */ new Map();
|
|
1254
1346
|
const displayOptions = [];
|
|
1347
|
+
const termCols = process.stdout?.columns || 96;
|
|
1348
|
+
const isNarrow = termCols < 88;
|
|
1349
|
+
const titleWidth = isNarrow ? Math.max(18, termCols - 36) : 34;
|
|
1255
1350
|
for (let i = 0; i < sortedList.length; i++) {
|
|
1256
1351
|
const a = sortedList[i];
|
|
1257
|
-
const num = (
|
|
1352
|
+
const num = a.id.replace(/^anc-/, "").padStart(2, "0");
|
|
1258
1353
|
const category = padToWidth(`[${classifyAnchor(a)}]`, 12);
|
|
1259
1354
|
const origin = formatOrigin(a);
|
|
1260
1355
|
const target = formatTargetDate(a);
|
|
1261
1356
|
const created = formatCreationTime(a.createdAt);
|
|
1262
1357
|
const titleWithFiles = a.files && a.files.length > 0 ? `${a.title} [${a.files.slice(0, 1).join(", ")}]` : a.title;
|
|
1263
1358
|
const colNum = `${num} `;
|
|
1264
|
-
const colTitle = padToWidth(titleWithFiles,
|
|
1359
|
+
const colTitle = padToWidth(titleWithFiles, titleWidth);
|
|
1265
1360
|
const colOrigin = padToWidth(origin, 10);
|
|
1266
1361
|
const colTarget = padToWidth(target, 12);
|
|
1267
1362
|
const colCreated = created;
|
|
1268
|
-
const label = `${colNum}${category}${colTitle} ${colOrigin} ${colTarget} ${colCreated}`.trimEnd();
|
|
1363
|
+
const label = isNarrow ? `${colNum}${category}${colTitle} ${colTarget}`.trimEnd() : `${colNum}${category}${colTitle} ${colOrigin} ${colTarget} ${colCreated}`.trimEnd();
|
|
1269
1364
|
optionMap.set(label, a);
|
|
1270
1365
|
displayOptions.push(label);
|
|
1271
1366
|
}
|
|
@@ -1306,6 +1401,7 @@ export {
|
|
|
1306
1401
|
DEFAULT_DECAY_POLICY,
|
|
1307
1402
|
globToRegExp,
|
|
1308
1403
|
normalizePath,
|
|
1404
|
+
isSameOrSubPath,
|
|
1309
1405
|
matchAnchorAgainstTouchedFiles,
|
|
1310
1406
|
findMatchedAnchors,
|
|
1311
1407
|
detectDurability,
|
|
@@ -1331,6 +1427,7 @@ export {
|
|
|
1331
1427
|
groupAnchorsByQuadrant,
|
|
1332
1428
|
stripAnsi,
|
|
1333
1429
|
getDisplayWidth,
|
|
1430
|
+
hasActiveUI,
|
|
1334
1431
|
truncateToWidth,
|
|
1335
1432
|
padToWidth,
|
|
1336
1433
|
formatTargetDate,
|