pi-anchor 0.2.4 → 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/README.md +22 -18
- package/README_zh.md +22 -18
- package/assets/hero-en.svg +39 -60
- package/assets/hero-zh.svg +39 -60
- package/bin/anchor.js +38 -51
- package/dist/{chunk-G72UHPBN.js → chunk-Q7IILJFA.js} +165 -43
- package/dist/extension.cjs +188 -54
- package/dist/extension.d.cts +1 -1
- package/dist/extension.d.ts +1 -1
- package/dist/extension.js +34 -16
- package/dist/index.cjs +169 -43
- package/dist/index.d.cts +63 -36
- package/dist/index.d.ts +63 -36
- package/dist/index.js +9 -1
- package/dist/{store-BNKjzhS_.d.cts → store-t2_TDhJn.d.cts} +2 -1
- package/dist/{store-BNKjzhS_.d.ts → store-t2_TDhJn.d.ts} +2 -1
- package/package.json +2 -2
- package/src/extension.ts +43 -19
- package/src/matcher.ts +51 -0
- package/src/settlement.ts +72 -6
- package/src/store.ts +25 -9
- package/src/tui.ts +77 -29
- package/src/types.ts +7 -7
package/src/tui.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ExtensionContext } from '@earendil-works/pi-coding-agent';
|
|
2
2
|
import { AnchorStore, getTodayDateString } from './store.ts';
|
|
3
|
+
import { isAnchorTargetingWorkspace } from './matcher.ts';
|
|
3
4
|
import type { Anchor } from './types.ts';
|
|
4
5
|
import path from 'node:path';
|
|
5
6
|
|
|
@@ -57,6 +58,40 @@ export function groupAnchorsByQuadrant(anchors: Anchor[], now: number = Date.now
|
|
|
57
58
|
return groups;
|
|
58
59
|
}
|
|
59
60
|
|
|
61
|
+
const PRIORITY_RANK: Record<string, number> = { p0: 0, p1: 1, p2: 2 };
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Sort unfinished anchors into a single compact, priority-driven engineering list:
|
|
65
|
+
* 1. Active before Sleeping (quiet)
|
|
66
|
+
* 2. Matching current workspace before other workspaces
|
|
67
|
+
* 3. Due Today / Overdue before open-ended
|
|
68
|
+
* 4. Priority (P0 -> P1 -> P2)
|
|
69
|
+
*/
|
|
70
|
+
export function sortAnchorsForDisplay(anchors: Anchor[], cwd?: string, now: number = Date.now()): Anchor[] {
|
|
71
|
+
const todayStr = getTodayDateString(now);
|
|
72
|
+
return [...anchors].sort((a, b) => {
|
|
73
|
+
// 1. Active before sleeping
|
|
74
|
+
if (a.status !== b.status) {
|
|
75
|
+
return a.status === 'active' ? -1 : 1;
|
|
76
|
+
}
|
|
77
|
+
// 2. Current workspace match first
|
|
78
|
+
if (cwd) {
|
|
79
|
+
const aMatch = isAnchorTargetingWorkspace(a, cwd) ? 0 : 1;
|
|
80
|
+
const bMatch = isAnchorTargetingWorkspace(b, cwd) ? 0 : 1;
|
|
81
|
+
if (aMatch !== bMatch) return aMatch - bMatch;
|
|
82
|
+
}
|
|
83
|
+
// 3. Due today / overdue first
|
|
84
|
+
const aDue = a.targetDate && a.targetDate <= todayStr ? 0 : 1;
|
|
85
|
+
const bDue = b.targetDate && b.targetDate <= todayStr ? 0 : 1;
|
|
86
|
+
if (aDue !== bDue) return aDue - bDue;
|
|
87
|
+
// 4. Priority P0 -> P1 -> P2
|
|
88
|
+
const pA = PRIORITY_RANK[a.priority] ?? 1;
|
|
89
|
+
const pB = PRIORITY_RANK[b.priority] ?? 1;
|
|
90
|
+
if (pA !== pB) return pA - pB;
|
|
91
|
+
return a.createdAt - b.createdAt;
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
60
95
|
const ANSI_REGEX = /\x1b\[[0-9;]*[a-zA-Z]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g;
|
|
61
96
|
|
|
62
97
|
/**
|
|
@@ -286,15 +321,23 @@ export function formatRemainingTtl(anchor: Anchor, now: number = Date.now()): st
|
|
|
286
321
|
}
|
|
287
322
|
|
|
288
323
|
/**
|
|
289
|
-
* Format origin
|
|
324
|
+
* Format origin workspace / project label concisely
|
|
290
325
|
*/
|
|
291
326
|
export function formatOrigin(anchor: Anchor | string): string {
|
|
292
327
|
if (typeof anchor === 'object') {
|
|
293
|
-
if (
|
|
294
|
-
|
|
328
|
+
if (anchor.project && anchor.project !== 'global') {
|
|
329
|
+
const normProj = anchor.project.replace(/\\/g, '/').replace(/\/+$/, '');
|
|
330
|
+
if (normProj.includes('/')) {
|
|
331
|
+
const parts = normProj.split('/').filter(Boolean);
|
|
332
|
+
return parts.slice(-2).join('/');
|
|
333
|
+
}
|
|
334
|
+
return normProj;
|
|
335
|
+
}
|
|
336
|
+
if (!anchor.cwd) return 'global';
|
|
337
|
+
return path.basename(anchor.cwd.replace(/\\/g, '/')) || 'global';
|
|
295
338
|
}
|
|
296
339
|
if (!anchor) return 'global';
|
|
297
|
-
return path.basename(anchor) || 'global';
|
|
340
|
+
return path.basename(anchor.replace(/\\/g, '/')) || 'global';
|
|
298
341
|
}
|
|
299
342
|
|
|
300
343
|
/**
|
|
@@ -372,7 +415,7 @@ export function updateStartupBanner(ctx: ExtensionContext, store: AnchorStore):
|
|
|
372
415
|
}
|
|
373
416
|
|
|
374
417
|
/**
|
|
375
|
-
* Clean,
|
|
418
|
+
* Clean, flat priority-sorted engineering checklist (includes both active and quiet/sleeping tasks so nothing is ever lost)
|
|
376
419
|
*/
|
|
377
420
|
export async function openAnchorDashboard(
|
|
378
421
|
ctx: ExtensionContext,
|
|
@@ -380,66 +423,67 @@ export async function openAnchorDashboard(
|
|
|
380
423
|
): Promise<void> {
|
|
381
424
|
if (!hasActiveUI(ctx)) return;
|
|
382
425
|
|
|
383
|
-
const
|
|
384
|
-
|
|
426
|
+
const allUnfinished = store
|
|
427
|
+
.list({ cwd: ctx.cwd, all: true })
|
|
428
|
+
.filter(a => a.status === 'active' || a.status === 'sleeping');
|
|
429
|
+
|
|
430
|
+
if (allUnfinished.length === 0) {
|
|
385
431
|
ctx.ui.notify('⌖ No active anchors. Use /pin <task> to record.', 'info');
|
|
386
432
|
return;
|
|
387
433
|
}
|
|
388
434
|
|
|
389
|
-
const
|
|
390
|
-
const sortedList = [
|
|
391
|
-
...groups.today,
|
|
392
|
-
...groups.upcoming,
|
|
393
|
-
...groups.habits,
|
|
394
|
-
...groups.backlog
|
|
395
|
-
];
|
|
435
|
+
const sortedList = sortAnchorsForDisplay(allUnfinished, ctx.cwd);
|
|
396
436
|
|
|
397
437
|
const optionMap = new Map<string, Anchor>();
|
|
398
438
|
const displayOptions: string[] = [];
|
|
399
439
|
const termCols = process.stdout?.columns || 96;
|
|
400
440
|
const isNarrow = termCols < 88;
|
|
401
|
-
const titleWidth = isNarrow ? Math.max(18, termCols - 36) :
|
|
441
|
+
const titleWidth = isNarrow ? Math.max(18, termCols - 36) : 36;
|
|
402
442
|
|
|
403
443
|
for (let i = 0; i < sortedList.length; i++) {
|
|
404
444
|
const a = sortedList[i];
|
|
405
445
|
const num = a.id.replace(/^anc-/, '').padStart(2, '0');
|
|
406
|
-
const
|
|
446
|
+
const badge = a.status === 'sleeping'
|
|
447
|
+
? padToWidth('[Quiet]', 10)
|
|
448
|
+
: padToWidth(`[${a.priority.toUpperCase()}]`, 10);
|
|
407
449
|
const origin = formatOrigin(a);
|
|
408
|
-
const target = formatTargetDate(a);
|
|
450
|
+
const target = a.status === 'sleeping' ? 'Quiet (0 tok)' : formatTargetDate(a);
|
|
409
451
|
const created = formatCreationTime(a.createdAt);
|
|
410
452
|
|
|
411
453
|
const titleWithFiles = (a.files && a.files.length > 0)
|
|
412
454
|
? `${a.title} [${a.files.slice(0, 1).join(', ')}]`
|
|
413
455
|
: a.title;
|
|
414
456
|
|
|
415
|
-
// Clean tabular column alignment: ID, Category, Title, Project, Target, Created
|
|
416
457
|
const colNum = `${num} `;
|
|
417
458
|
const colTitle = padToWidth(titleWithFiles, titleWidth);
|
|
418
|
-
const colOrigin = padToWidth(origin,
|
|
419
|
-
const colTarget = padToWidth(target,
|
|
459
|
+
const colOrigin = padToWidth(origin, 12);
|
|
460
|
+
const colTarget = padToWidth(target, 14);
|
|
420
461
|
const colCreated = created;
|
|
421
462
|
|
|
422
463
|
const label = isNarrow
|
|
423
|
-
? `${colNum}${
|
|
424
|
-
: `${colNum}${
|
|
464
|
+
? `${colNum}${badge}${colTitle} ${colTarget}`.trimEnd()
|
|
465
|
+
: `${colNum}${badge}${colTitle} ${colOrigin} ${colTarget} ${colCreated}`.trimEnd();
|
|
425
466
|
|
|
426
467
|
optionMap.set(label, a);
|
|
427
468
|
displayOptions.push(label);
|
|
428
469
|
}
|
|
429
470
|
|
|
430
|
-
const selected = await ctx.ui.select('⌖ Anchors (select to
|
|
471
|
+
const selected = await ctx.ui.select('⌖ Anchors (select to settle, wake, or inspect):', displayOptions);
|
|
431
472
|
if (!selected) return;
|
|
432
473
|
|
|
433
474
|
const anchor = optionMap.get(selected);
|
|
434
475
|
if (!anchor) return;
|
|
435
476
|
|
|
477
|
+
const actions = [
|
|
478
|
+
'✓ Settle (Mark completed and archive)',
|
|
479
|
+
...(anchor.status === 'sleeping' ? ['⚡ Wake up (Activate for current session)'] : []),
|
|
480
|
+
'ℹ View details & associated files',
|
|
481
|
+
'↩ Cancel'
|
|
482
|
+
];
|
|
483
|
+
|
|
436
484
|
const action = await ctx.ui.select(
|
|
437
485
|
`⌖ Anchor #${anchor.id}: "${anchor.title}"`,
|
|
438
|
-
|
|
439
|
-
'✓ Settle (Mark completed and archive)',
|
|
440
|
-
'ℹ View details & associated files',
|
|
441
|
-
'↩ Cancel'
|
|
442
|
-
]
|
|
486
|
+
actions
|
|
443
487
|
);
|
|
444
488
|
if (!action || action.startsWith('↩')) return;
|
|
445
489
|
|
|
@@ -450,9 +494,13 @@ export async function openAnchorDashboard(
|
|
|
450
494
|
: `⌖ Settled #${anchor.id}: "${anchor.title}" (/anchor undo to revert)`;
|
|
451
495
|
ctx.ui.notify(successMsg, 'info');
|
|
452
496
|
updateAnchorStatusBar(ctx, store);
|
|
497
|
+
} else if (action.startsWith('⚡')) {
|
|
498
|
+
store.touch(anchor.id);
|
|
499
|
+
ctx.ui.notify(`⌖ Woke up #${anchor.id}: "${anchor.title}"`, 'info');
|
|
500
|
+
updateAnchorStatusBar(ctx, store);
|
|
453
501
|
} else if (action.startsWith('ℹ')) {
|
|
454
502
|
const details = [
|
|
455
|
-
`#${anchor.id} [${anchor.priority.toUpperCase()}] ${anchor.title}`,
|
|
503
|
+
`#${anchor.id} [${anchor.priority.toUpperCase()} · ${anchor.status.toUpperCase()}] ${anchor.title}`,
|
|
456
504
|
anchor.description ? `Description: ${anchor.description}` : null,
|
|
457
505
|
anchor.targetDate ? `Target Date: ${anchor.targetDate} (${formatTargetDate(anchor)})` : null,
|
|
458
506
|
anchor.files && anchor.files.length > 0 ? `Files: ${anchor.files.join(', ')}` : null,
|
package/src/types.ts
CHANGED
|
@@ -87,19 +87,19 @@ export interface SettlementProposal {
|
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
export const EPHEMERAL_DECAY_POLICY: AnchorDecayPolicy = {
|
|
90
|
-
activeDays:
|
|
91
|
-
sleepDays:
|
|
92
|
-
graveyardDays:
|
|
90
|
+
activeDays: 7, // 7 days active before quiet sleep
|
|
91
|
+
sleepDays: 30,
|
|
92
|
+
graveyardDays: 9999 // Never silently auto-delete unfinished tasks
|
|
93
93
|
};
|
|
94
94
|
|
|
95
95
|
export const DURABLE_DECAY_POLICY: AnchorDecayPolicy = {
|
|
96
|
-
activeDays:
|
|
96
|
+
activeDays: 7, // 7 days active before quiet sleep
|
|
97
97
|
sleepDays: 30,
|
|
98
98
|
graveyardDays: 9999 // Never permanently auto-dropped; preserved indefinitely
|
|
99
99
|
};
|
|
100
100
|
|
|
101
101
|
export const DEFAULT_DECAY_POLICY: AnchorDecayPolicy = {
|
|
102
|
-
activeDays:
|
|
103
|
-
sleepDays:
|
|
104
|
-
graveyardDays:
|
|
102
|
+
activeDays: 7,
|
|
103
|
+
sleepDays: 30,
|
|
104
|
+
graveyardDays: 9999
|
|
105
105
|
};
|