@unbrained/pm-cli 2026.5.2 → 2026.5.3-5
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/AGENTS.md +8 -1
- package/CHANGELOG.md +53 -0
- package/README.md +9 -1
- package/dist/cli/bootstrap-args.d.ts +18 -0
- package/dist/cli/bootstrap-args.js +242 -0
- package/dist/cli/bootstrap-args.js.map +1 -0
- package/dist/cli/commander-usage.d.ts +17 -0
- package/dist/cli/commander-usage.js +178 -0
- package/dist/cli/commander-usage.js.map +1 -0
- package/dist/cli/commands/activity.js +1 -9
- package/dist/cli/commands/activity.js.map +1 -1
- package/dist/cli/commands/calendar.js +3 -29
- package/dist/cli/commands/calendar.js.map +1 -1
- package/dist/cli/commands/comments.js +1 -9
- package/dist/cli/commands/comments.js.map +1 -1
- package/dist/cli/commands/config.d.ts +21 -3
- package/dist/cli/commands/config.js +118 -2
- package/dist/cli/commands/config.js.map +1 -1
- package/dist/cli/commands/context.d.ts +90 -1
- package/dist/cli/commands/context.js +485 -23
- package/dist/cli/commands/context.js.map +1 -1
- package/dist/cli/commands/dedupe-audit.js +2 -11
- package/dist/cli/commands/dedupe-audit.js.map +1 -1
- package/dist/cli/commands/history.js +1 -9
- package/dist/cli/commands/history.js.map +1 -1
- package/dist/cli/commands/learnings.js +1 -9
- package/dist/cli/commands/learnings.js.map +1 -1
- package/dist/cli/commands/list.js +3 -29
- package/dist/cli/commands/list.js.map +1 -1
- package/dist/cli/commands/normalize.js +9 -6
- package/dist/cli/commands/normalize.js.map +1 -1
- package/dist/cli/commands/notes.js +1 -9
- package/dist/cli/commands/notes.js.map +1 -1
- package/dist/cli/commands/reindex.js +2 -7
- package/dist/cli/commands/reindex.js.map +1 -1
- package/dist/cli/commands/search.js +4 -35
- package/dist/cli/commands/search.js.map +1 -1
- package/dist/cli/commands/test-runs.js +1 -11
- package/dist/cli/commands/test-runs.js.map +1 -1
- package/dist/cli/error-guidance.d.ts +13 -0
- package/dist/cli/error-guidance.js +43 -4
- package/dist/cli/error-guidance.js.map +1 -1
- package/dist/cli/extension-command-help.d.ts +48 -0
- package/dist/cli/extension-command-help.js +389 -0
- package/dist/cli/extension-command-help.js.map +1 -0
- package/dist/cli/help-content.js +9 -3
- package/dist/cli/help-content.js.map +1 -1
- package/dist/cli/help-json-payload.d.ts +25 -0
- package/dist/cli/help-json-payload.js +265 -0
- package/dist/cli/help-json-payload.js.map +1 -0
- package/dist/cli/main.js +996 -4468
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/migration-gates.d.ts +22 -0
- package/dist/cli/migration-gates.js +146 -0
- package/dist/cli/migration-gates.js.map +1 -0
- package/dist/cli/register-list-query.d.ts +2 -0
- package/dist/cli/register-list-query.js +317 -0
- package/dist/cli/register-list-query.js.map +1 -0
- package/dist/cli/register-mutation.d.ts +2 -0
- package/dist/cli/register-mutation.js +795 -0
- package/dist/cli/register-mutation.js.map +1 -0
- package/dist/cli/register-operations.d.ts +2 -0
- package/dist/cli/register-operations.js +610 -0
- package/dist/cli/register-operations.js.map +1 -0
- package/dist/cli/register-setup.d.ts +2 -0
- package/dist/cli/register-setup.js +334 -0
- package/dist/cli/register-setup.js.map +1 -0
- package/dist/cli/registration-helpers.d.ts +53 -0
- package/dist/cli/registration-helpers.js +669 -0
- package/dist/cli/registration-helpers.js.map +1 -0
- package/dist/cli/shared-parsers.d.ts +6 -0
- package/dist/cli/shared-parsers.js +40 -0
- package/dist/cli/shared-parsers.js.map +1 -0
- package/dist/core/search/http-client.d.ts +29 -0
- package/dist/core/search/http-client.js +64 -0
- package/dist/core/search/http-client.js.map +1 -0
- package/dist/core/search/providers.d.ts +3 -13
- package/dist/core/search/providers.js +19 -69
- package/dist/core/search/providers.js.map +1 -1
- package/dist/core/search/semantic-defaults.js +2 -7
- package/dist/core/search/semantic-defaults.js.map +1 -1
- package/dist/core/search/vector-stores.d.ts +3 -13
- package/dist/core/search/vector-stores.js +17 -66
- package/dist/core/search/vector-stores.js.map +1 -1
- package/dist/core/sentry/helpers.d.ts +23 -2
- package/dist/core/sentry/helpers.js +101 -3
- package/dist/core/sentry/helpers.js.map +1 -1
- package/dist/core/sentry/instrument.d.ts +21 -0
- package/dist/core/sentry/instrument.js +34 -3
- package/dist/core/sentry/instrument.js.map +1 -1
- package/dist/core/shared/constants.d.ts +3 -0
- package/dist/core/shared/constants.js +58 -1
- package/dist/core/shared/constants.js.map +1 -1
- package/dist/core/store/front-matter-cache.d.ts +6 -0
- package/dist/core/store/front-matter-cache.js +150 -0
- package/dist/core/store/front-matter-cache.js.map +1 -0
- package/dist/core/store/item-store.js +2 -1
- package/dist/core/store/item-store.js.map +1 -1
- package/dist/core/store/settings.js +36 -0
- package/dist/core/store/settings.js.map +1 -1
- package/dist/core/telemetry/observability.d.ts +24 -0
- package/dist/core/telemetry/observability.js +185 -0
- package/dist/core/telemetry/observability.js.map +1 -0
- package/dist/core/telemetry/runtime.d.ts +27 -3
- package/dist/core/telemetry/runtime.js +298 -13
- package/dist/core/telemetry/runtime.js.map +1 -1
- package/dist/sdk/cli-contracts.js +28 -0
- package/dist/sdk/cli-contracts.js.map +1 -1
- package/dist/types.d.ts +21 -0
- package/dist/types.js +11 -0
- package/dist/types.js.map +1 -1
- package/docs/ARCHITECTURE.md +7 -1
- package/docs/COMMANDS.md +11 -1
- package/docs/RELEASING.md +56 -29
- package/package.json +8 -3
|
@@ -1,15 +1,35 @@
|
|
|
1
|
-
import { EXIT_CODE } from "../../core/shared/constants.js";
|
|
1
|
+
import { SETTINGS_DEFAULTS, EXIT_CODE } from "../../core/shared/constants.js";
|
|
2
2
|
import { PmCliError } from "../../core/shared/errors.js";
|
|
3
3
|
import { compareTimestampStrings } from "../../core/shared/time.js";
|
|
4
4
|
import { normalizeStatusInput } from "../../core/item/status.js";
|
|
5
5
|
import { resolveRuntimeStatusRegistry } from "../../core/schema/runtime-schema.js";
|
|
6
6
|
import { resolvePmRoot } from "../../core/store/paths.js";
|
|
7
7
|
import { readSettings } from "../../core/store/settings.js";
|
|
8
|
+
import { CONTEXT_DEPTH_VALUES, CONTEXT_SECTION_VALUES } from "../../types/index.js";
|
|
9
|
+
import { parseIntegerLimit } from "../shared-parsers.js";
|
|
8
10
|
import { runCalendar } from "./calendar.js";
|
|
9
11
|
import { runList } from "./list.js";
|
|
12
|
+
import { runActivity } from "./activity.js";
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// Output format
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
10
16
|
export const CONTEXT_OUTPUT_VALUES = ["markdown", "toon", "json"];
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Constants
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
11
20
|
const HIGH_LEVEL_TYPES = new Set(["Epic", "Feature"]);
|
|
12
21
|
const DEFAULT_CONTEXT_LIMIT = 10;
|
|
22
|
+
const STANDARD_SECTIONS = ["hierarchy", "activity", "progress", "workload"];
|
|
23
|
+
const DEEP_SECTIONS = [
|
|
24
|
+
...STANDARD_SECTIONS,
|
|
25
|
+
"blockers",
|
|
26
|
+
"files",
|
|
27
|
+
"staleness",
|
|
28
|
+
"tests",
|
|
29
|
+
];
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
// Parsers
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
13
33
|
function parseOutputFormat(raw) {
|
|
14
34
|
if (!raw)
|
|
15
35
|
return undefined;
|
|
@@ -29,16 +49,59 @@ export function resolveContextOutputFormat(options, global) {
|
|
|
29
49
|
}
|
|
30
50
|
return commandFormat ?? "toon";
|
|
31
51
|
}
|
|
32
|
-
function
|
|
33
|
-
|
|
34
|
-
|
|
52
|
+
function parseContextLimit(raw) {
|
|
53
|
+
return parseIntegerLimit(raw, "--limit") ?? DEFAULT_CONTEXT_LIMIT;
|
|
54
|
+
}
|
|
55
|
+
export function parseContextDepth(raw, settings) {
|
|
56
|
+
if (!raw)
|
|
57
|
+
return settings.default_depth;
|
|
58
|
+
const normalized = raw.trim().toLowerCase();
|
|
59
|
+
if (!CONTEXT_DEPTH_VALUES.includes(normalized)) {
|
|
60
|
+
throw new PmCliError(`Context --depth must be one of ${CONTEXT_DEPTH_VALUES.join("|")}`, EXIT_CODE.USAGE);
|
|
61
|
+
}
|
|
62
|
+
return normalized;
|
|
63
|
+
}
|
|
64
|
+
export function parseContextSections(raw, depth, settings) {
|
|
65
|
+
if (raw && raw.length > 0) {
|
|
66
|
+
const sections = [];
|
|
67
|
+
for (const value of raw) {
|
|
68
|
+
const normalized = value.trim().toLowerCase();
|
|
69
|
+
if (!CONTEXT_SECTION_VALUES.includes(normalized)) {
|
|
70
|
+
throw new PmCliError(`Context --section must be one of ${CONTEXT_SECTION_VALUES.join("|")}`, EXIT_CODE.USAGE);
|
|
71
|
+
}
|
|
72
|
+
if (!sections.includes(normalized)) {
|
|
73
|
+
sections.push(normalized);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return sections;
|
|
77
|
+
}
|
|
78
|
+
if (depth === "brief")
|
|
79
|
+
return [];
|
|
80
|
+
const pool = depth === "deep" ? DEEP_SECTIONS : STANDARD_SECTIONS;
|
|
81
|
+
return pool.filter((section) => settings.sections[section]);
|
|
82
|
+
}
|
|
83
|
+
function parseActivityLimit(raw, settings) {
|
|
84
|
+
if (!raw)
|
|
85
|
+
return settings.activity_limit;
|
|
86
|
+
return parseIntegerLimit(raw, "--activity-limit") ?? settings.activity_limit;
|
|
87
|
+
}
|
|
88
|
+
function parseStaleThresholdDays(raw, settings) {
|
|
89
|
+
if (!raw)
|
|
90
|
+
return settings.stale_threshold_days;
|
|
91
|
+
const trimmed = raw.trim().toLowerCase();
|
|
92
|
+
const match = /^(\d+)d?$/.exec(trimmed);
|
|
93
|
+
if (!match) {
|
|
94
|
+
throw new PmCliError("--stale-threshold must be a number of days (e.g. 7 or 7d)", EXIT_CODE.USAGE);
|
|
35
95
|
}
|
|
36
|
-
const
|
|
37
|
-
if (
|
|
38
|
-
throw new PmCliError("
|
|
96
|
+
const days = parseInt(match[1], 10);
|
|
97
|
+
if (days <= 0) {
|
|
98
|
+
throw new PmCliError("--stale-threshold must be positive", EXIT_CODE.USAGE);
|
|
39
99
|
}
|
|
40
|
-
return
|
|
100
|
+
return days;
|
|
41
101
|
}
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
// Status helpers
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
42
105
|
function normalizeStatusForRegistry(status, statusRegistry) {
|
|
43
106
|
return normalizeStatusInput(status, statusRegistry) ?? status;
|
|
44
107
|
}
|
|
@@ -64,6 +127,27 @@ function statusRank(status, statusRegistry) {
|
|
|
64
127
|
return 7;
|
|
65
128
|
return 6;
|
|
66
129
|
}
|
|
130
|
+
function isTerminal(status, statusRegistry) {
|
|
131
|
+
return statusRegistry.terminal_statuses.has(normalizeStatusForRegistry(status, statusRegistry));
|
|
132
|
+
}
|
|
133
|
+
function isClosedStatus(status, statusRegistry) {
|
|
134
|
+
const closeStatus = normalizeStatusInput("closed", statusRegistry);
|
|
135
|
+
return closeStatus ? normalizeStatusForRegistry(status, statusRegistry) === closeStatus : false;
|
|
136
|
+
}
|
|
137
|
+
function isInProgressStatus(status, statusRegistry) {
|
|
138
|
+
const inProgressStatus = normalizeStatusInput("in_progress", statusRegistry);
|
|
139
|
+
return inProgressStatus ? normalizeStatusForRegistry(status, statusRegistry) === inProgressStatus : false;
|
|
140
|
+
}
|
|
141
|
+
function isOpenStatus(status, statusRegistry) {
|
|
142
|
+
const openStatus = normalizeStatusInput("open", statusRegistry) ?? statusRegistry.open_status;
|
|
143
|
+
return normalizeStatusForRegistry(status, statusRegistry) === openStatus;
|
|
144
|
+
}
|
|
145
|
+
function isBlockedStatus(status, statusRegistry) {
|
|
146
|
+
return statusRegistry.blocked_statuses.has(normalizeStatusForRegistry(status, statusRegistry));
|
|
147
|
+
}
|
|
148
|
+
// ---------------------------------------------------------------------------
|
|
149
|
+
// Sorting / mapping helpers (unchanged from original)
|
|
150
|
+
// ---------------------------------------------------------------------------
|
|
67
151
|
function compareOptionalOrder(left, right) {
|
|
68
152
|
const leftValue = left ?? null;
|
|
69
153
|
const rightValue = right ?? null;
|
|
@@ -145,6 +229,249 @@ function summarizeAgenda(events) {
|
|
|
145
229
|
function filterTerminalCalendarEvents(events, statusRegistry) {
|
|
146
230
|
return events.filter((event) => !statusRegistry.terminal_statuses.has(normalizeStatusForRegistry(event.item_status, statusRegistry)));
|
|
147
231
|
}
|
|
232
|
+
// ---------------------------------------------------------------------------
|
|
233
|
+
// Section builders
|
|
234
|
+
// ---------------------------------------------------------------------------
|
|
235
|
+
function buildHierarchy(allItems, activeItems, statusRegistry, limit) {
|
|
236
|
+
const itemMap = new Map();
|
|
237
|
+
for (const item of allItems) {
|
|
238
|
+
itemMap.set(item.id, item);
|
|
239
|
+
}
|
|
240
|
+
const childrenByParent = new Map();
|
|
241
|
+
for (const item of allItems) {
|
|
242
|
+
if (!item.parent)
|
|
243
|
+
continue;
|
|
244
|
+
const children = childrenByParent.get(item.parent) ?? [];
|
|
245
|
+
children.push(item);
|
|
246
|
+
childrenByParent.set(item.parent, children);
|
|
247
|
+
}
|
|
248
|
+
const activeHighLevelIds = new Set(activeItems.filter((item) => HIGH_LEVEL_TYPES.has(item.type)).map((item) => item.id));
|
|
249
|
+
const nodes = [];
|
|
250
|
+
for (const parentId of activeHighLevelIds) {
|
|
251
|
+
const parent = itemMap.get(parentId);
|
|
252
|
+
if (!parent)
|
|
253
|
+
continue;
|
|
254
|
+
const allDescendants = collectDescendants(parentId, childrenByParent);
|
|
255
|
+
const childItems = childrenByParent.get(parentId) ?? [];
|
|
256
|
+
let closedCount = 0;
|
|
257
|
+
let openCount = 0;
|
|
258
|
+
let inProgressCount = 0;
|
|
259
|
+
let blockedCount = 0;
|
|
260
|
+
for (const desc of allDescendants) {
|
|
261
|
+
if (isClosedStatus(desc.status, statusRegistry))
|
|
262
|
+
closedCount++;
|
|
263
|
+
else if (isInProgressStatus(desc.status, statusRegistry))
|
|
264
|
+
inProgressCount++;
|
|
265
|
+
else if (isBlockedStatus(desc.status, statusRegistry))
|
|
266
|
+
blockedCount++;
|
|
267
|
+
else if (isOpenStatus(desc.status, statusRegistry))
|
|
268
|
+
openCount++;
|
|
269
|
+
}
|
|
270
|
+
const children = childItems
|
|
271
|
+
.sort((a, b) => compareCriticalItems(a, b, statusRegistry))
|
|
272
|
+
.slice(0, limit)
|
|
273
|
+
.map((child) => {
|
|
274
|
+
const grandchildren = collectDescendants(child.id, childrenByParent);
|
|
275
|
+
const gcClosed = grandchildren.filter((gc) => isClosedStatus(gc.status, statusRegistry)).length;
|
|
276
|
+
return {
|
|
277
|
+
id: child.id,
|
|
278
|
+
title: child.title,
|
|
279
|
+
type: child.type,
|
|
280
|
+
status: child.status,
|
|
281
|
+
children_total: grandchildren.length,
|
|
282
|
+
children_closed: gcClosed,
|
|
283
|
+
};
|
|
284
|
+
});
|
|
285
|
+
nodes.push({
|
|
286
|
+
id: parent.id,
|
|
287
|
+
title: parent.title,
|
|
288
|
+
type: parent.type,
|
|
289
|
+
status: parent.status,
|
|
290
|
+
children_total: allDescendants.length,
|
|
291
|
+
children_closed: closedCount,
|
|
292
|
+
children_open: openCount,
|
|
293
|
+
children_in_progress: inProgressCount,
|
|
294
|
+
children_blocked: blockedCount,
|
|
295
|
+
children,
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
return nodes
|
|
299
|
+
.sort((a, b) => {
|
|
300
|
+
const aParent = itemMap.get(a.id);
|
|
301
|
+
const bParent = itemMap.get(b.id);
|
|
302
|
+
return compareCriticalItems(aParent, bParent, statusRegistry);
|
|
303
|
+
})
|
|
304
|
+
.slice(0, limit);
|
|
305
|
+
}
|
|
306
|
+
function collectDescendants(parentId, childrenByParent) {
|
|
307
|
+
const result = [];
|
|
308
|
+
const stack = [parentId];
|
|
309
|
+
const visited = new Set();
|
|
310
|
+
while (stack.length > 0) {
|
|
311
|
+
const current = stack.pop();
|
|
312
|
+
if (visited.has(current))
|
|
313
|
+
continue;
|
|
314
|
+
visited.add(current);
|
|
315
|
+
const children = childrenByParent.get(current) ?? [];
|
|
316
|
+
for (const child of children) {
|
|
317
|
+
result.push(child);
|
|
318
|
+
stack.push(child.id);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
return result;
|
|
322
|
+
}
|
|
323
|
+
async function buildActivity(activityLimit, global) {
|
|
324
|
+
const result = await runActivity({ compact: true, limit: String(activityLimit) }, global);
|
|
325
|
+
return result.compact_activity ?? [];
|
|
326
|
+
}
|
|
327
|
+
function buildProgress(allItems, activeItems, statusRegistry, limit) {
|
|
328
|
+
const childrenByParent = new Map();
|
|
329
|
+
for (const item of allItems) {
|
|
330
|
+
if (!item.parent)
|
|
331
|
+
continue;
|
|
332
|
+
const children = childrenByParent.get(item.parent) ?? [];
|
|
333
|
+
children.push(item);
|
|
334
|
+
childrenByParent.set(item.parent, children);
|
|
335
|
+
}
|
|
336
|
+
const activeHighLevel = activeItems.filter((item) => HIGH_LEVEL_TYPES.has(item.type));
|
|
337
|
+
const entries = [];
|
|
338
|
+
for (const parent of activeHighLevel) {
|
|
339
|
+
const descendants = collectDescendants(parent.id, childrenByParent);
|
|
340
|
+
const total = descendants.length;
|
|
341
|
+
if (total === 0)
|
|
342
|
+
continue;
|
|
343
|
+
let closed = 0;
|
|
344
|
+
let open = 0;
|
|
345
|
+
let inProgress = 0;
|
|
346
|
+
let blocked = 0;
|
|
347
|
+
for (const desc of descendants) {
|
|
348
|
+
if (isClosedStatus(desc.status, statusRegistry))
|
|
349
|
+
closed++;
|
|
350
|
+
else if (isInProgressStatus(desc.status, statusRegistry))
|
|
351
|
+
inProgress++;
|
|
352
|
+
else if (isBlockedStatus(desc.status, statusRegistry))
|
|
353
|
+
blocked++;
|
|
354
|
+
else if (isOpenStatus(desc.status, statusRegistry))
|
|
355
|
+
open++;
|
|
356
|
+
}
|
|
357
|
+
entries.push({
|
|
358
|
+
id: parent.id,
|
|
359
|
+
title: parent.title,
|
|
360
|
+
type: parent.type,
|
|
361
|
+
total,
|
|
362
|
+
closed,
|
|
363
|
+
open,
|
|
364
|
+
in_progress: inProgress,
|
|
365
|
+
blocked,
|
|
366
|
+
completion_pct: total > 0 ? Math.round((closed / total) * 100) : 0,
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
return entries
|
|
370
|
+
.sort((a, b) => a.completion_pct - b.completion_pct)
|
|
371
|
+
.slice(0, limit);
|
|
372
|
+
}
|
|
373
|
+
function buildBlockers(blockedItems, itemMap, limit) {
|
|
374
|
+
return blockedItems.slice(0, limit).map((item) => {
|
|
375
|
+
const blockerItem = item.blocked_by ? itemMap.get(item.blocked_by) : undefined;
|
|
376
|
+
return {
|
|
377
|
+
id: item.id,
|
|
378
|
+
title: item.title,
|
|
379
|
+
blocked_by: item.blocked_by ?? null,
|
|
380
|
+
blocked_by_title: blockerItem?.title ?? null,
|
|
381
|
+
blocked_by_status: blockerItem?.status ?? null,
|
|
382
|
+
blocked_reason: item.blocked_reason ?? null,
|
|
383
|
+
unblock_note: item.unblock_note ?? null,
|
|
384
|
+
};
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
function buildHotFiles(activeItems, limit) {
|
|
388
|
+
const fileMap = new Map();
|
|
389
|
+
for (const item of activeItems) {
|
|
390
|
+
for (const file of item.files ?? []) {
|
|
391
|
+
const existing = fileMap.get(file.path) ?? new Set();
|
|
392
|
+
existing.add(item.id);
|
|
393
|
+
fileMap.set(file.path, existing);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
return [...fileMap.entries()]
|
|
397
|
+
.map(([filePath, itemIds]) => ({
|
|
398
|
+
path: filePath,
|
|
399
|
+
references: itemIds.size,
|
|
400
|
+
items: [...itemIds].sort(),
|
|
401
|
+
}))
|
|
402
|
+
.sort((a, b) => b.references - a.references)
|
|
403
|
+
.slice(0, limit);
|
|
404
|
+
}
|
|
405
|
+
function buildWorkload(activeItems, statusRegistry, limit) {
|
|
406
|
+
const groups = new Map();
|
|
407
|
+
for (const item of activeItems) {
|
|
408
|
+
const key = item.assignee ?? null;
|
|
409
|
+
const existing = groups.get(key) ?? [];
|
|
410
|
+
existing.push(item);
|
|
411
|
+
groups.set(key, existing);
|
|
412
|
+
}
|
|
413
|
+
return [...groups.entries()]
|
|
414
|
+
.map(([assignee, items]) => ({
|
|
415
|
+
assignee,
|
|
416
|
+
active: items.length,
|
|
417
|
+
in_progress: items.filter((item) => isInProgressStatus(item.status, statusRegistry)).length,
|
|
418
|
+
items: items.map((item) => item.id).sort(),
|
|
419
|
+
}))
|
|
420
|
+
.sort((a, b) => b.active - a.active)
|
|
421
|
+
.slice(0, limit);
|
|
422
|
+
}
|
|
423
|
+
function buildStaleness(allNonTerminal, staleThresholdDays, now, limit) {
|
|
424
|
+
const cutoffMs = new Date(now).getTime() - staleThresholdDays * 24 * 60 * 60 * 1000;
|
|
425
|
+
return allNonTerminal
|
|
426
|
+
.filter((item) => new Date(item.updated_at).getTime() < cutoffMs)
|
|
427
|
+
.map((item) => ({
|
|
428
|
+
id: item.id,
|
|
429
|
+
title: item.title,
|
|
430
|
+
status: item.status,
|
|
431
|
+
updated_at: item.updated_at,
|
|
432
|
+
stale_days: Math.floor((new Date(now).getTime() - new Date(item.updated_at).getTime()) / (24 * 60 * 60 * 1000)),
|
|
433
|
+
}))
|
|
434
|
+
.sort((a, b) => b.stale_days - a.stale_days)
|
|
435
|
+
.slice(0, limit);
|
|
436
|
+
}
|
|
437
|
+
function buildTestHealth(activeItems) {
|
|
438
|
+
let itemsWithTests = 0;
|
|
439
|
+
let itemsWithRecentRuns = 0;
|
|
440
|
+
let passed = 0;
|
|
441
|
+
let failed = 0;
|
|
442
|
+
let skipped = 0;
|
|
443
|
+
const itemsFailing = [];
|
|
444
|
+
for (const item of activeItems) {
|
|
445
|
+
if ((item.tests ?? []).length > 0) {
|
|
446
|
+
itemsWithTests++;
|
|
447
|
+
}
|
|
448
|
+
const runs = item.test_runs ?? [];
|
|
449
|
+
if (runs.length > 0) {
|
|
450
|
+
itemsWithRecentRuns++;
|
|
451
|
+
let itemHasFailure = false;
|
|
452
|
+
for (const run of runs) {
|
|
453
|
+
passed += run.passed ?? 0;
|
|
454
|
+
failed += run.failed ?? 0;
|
|
455
|
+
skipped += run.skipped ?? 0;
|
|
456
|
+
if ((run.failed ?? 0) > 0) {
|
|
457
|
+
itemHasFailure = true;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
if (itemHasFailure) {
|
|
461
|
+
itemsFailing.push(item.id);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
return {
|
|
466
|
+
items_with_tests: itemsWithTests,
|
|
467
|
+
items_with_recent_runs: itemsWithRecentRuns,
|
|
468
|
+
recent_runs: { passed, failed, skipped },
|
|
469
|
+
items_failing: itemsFailing.sort(),
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
// ---------------------------------------------------------------------------
|
|
473
|
+
// Markdown formatting
|
|
474
|
+
// ---------------------------------------------------------------------------
|
|
148
475
|
function formatClock(timestamp) {
|
|
149
476
|
return `${new Date(timestamp).toISOString().slice(11, 16)}Z`;
|
|
150
477
|
}
|
|
@@ -170,9 +497,16 @@ export function renderContextMarkdown(result) {
|
|
|
170
497
|
lines.push("# pm context");
|
|
171
498
|
lines.push("");
|
|
172
499
|
lines.push(`- now: ${result.now}`);
|
|
500
|
+
lines.push(`- depth: ${result.depth}`);
|
|
173
501
|
lines.push(`- active_items: ${result.summary.active_items} (in_progress: ${result.summary.in_progress}, open: ${result.summary.open})`);
|
|
502
|
+
if (result.summary.total_items !== undefined) {
|
|
503
|
+
lines.push(`- total_items: ${result.summary.total_items} (closed: ${result.summary.closed ?? 0}, canceled: ${result.summary.canceled ?? 0})`);
|
|
504
|
+
}
|
|
174
505
|
lines.push(`- agenda_events: ${result.summary.agenda_events}`);
|
|
175
506
|
lines.push(`- blocked_fallback_used: ${result.summary.blocked_fallback_used}`);
|
|
507
|
+
if (result.sections_included.length > 0) {
|
|
508
|
+
lines.push(`- sections: ${result.sections_included.join(", ")}`);
|
|
509
|
+
}
|
|
176
510
|
lines.push("");
|
|
177
511
|
lines.push("## High-level focus");
|
|
178
512
|
if (result.high_level.length === 0) {
|
|
@@ -211,11 +545,80 @@ export function renderContextMarkdown(result) {
|
|
|
211
545
|
lines.push(`- ${formatAgendaLine(event)}`);
|
|
212
546
|
}
|
|
213
547
|
}
|
|
548
|
+
lines.push("");
|
|
549
|
+
if (result.hierarchy && result.hierarchy.length > 0) {
|
|
550
|
+
lines.push("## Hierarchy");
|
|
551
|
+
for (const node of result.hierarchy) {
|
|
552
|
+
const pct = node.children_total > 0 ? Math.round((node.children_closed / node.children_total) * 100) : 0;
|
|
553
|
+
lines.push(`- ${node.id} ${node.type} ${node.status} "${node.title}" [${node.children_closed}/${node.children_total} done ${pct}%]`);
|
|
554
|
+
for (const child of node.children) {
|
|
555
|
+
const cpct = child.children_total > 0 ? Math.round((child.children_closed / child.children_total) * 100) : 0;
|
|
556
|
+
lines.push(` - ${child.id} ${child.type} ${child.status} "${child.title}" [${child.children_closed}/${child.children_total} done ${cpct}%]`);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
lines.push("");
|
|
560
|
+
}
|
|
561
|
+
if (result.progress && result.progress.length > 0) {
|
|
562
|
+
lines.push("## Progress");
|
|
563
|
+
for (const entry of result.progress) {
|
|
564
|
+
lines.push(`- ${entry.id} "${entry.title}" ${entry.completion_pct}% (${entry.closed}/${entry.total} closed, ${entry.in_progress} wip, ${entry.open} open, ${entry.blocked} blocked)`);
|
|
565
|
+
}
|
|
566
|
+
lines.push("");
|
|
567
|
+
}
|
|
568
|
+
if (result.activity && result.activity.length > 0) {
|
|
569
|
+
lines.push("## Recent activity");
|
|
570
|
+
for (const entry of result.activity) {
|
|
571
|
+
const msg = entry.msg ? ` ${entry.msg}` : "";
|
|
572
|
+
lines.push(`- ${entry.ts.slice(0, 16)}Z ${entry.id} ${entry.op} by:${entry.author}${msg}`);
|
|
573
|
+
}
|
|
574
|
+
lines.push("");
|
|
575
|
+
}
|
|
576
|
+
if (result.blockers && result.blockers.length > 0) {
|
|
577
|
+
lines.push("## Blockers");
|
|
578
|
+
for (const entry of result.blockers) {
|
|
579
|
+
const by = entry.blocked_by ? `blocked_by:${entry.blocked_by}(${entry.blocked_by_status ?? "?"})` : "blocked_by:-";
|
|
580
|
+
const reason = entry.blocked_reason ? ` reason:"${entry.blocked_reason}"` : "";
|
|
581
|
+
const note = entry.unblock_note ? ` unblock:"${entry.unblock_note}"` : "";
|
|
582
|
+
lines.push(`- ${entry.id} "${entry.title}" ${by}${reason}${note}`);
|
|
583
|
+
}
|
|
584
|
+
lines.push("");
|
|
585
|
+
}
|
|
586
|
+
if (result.files && result.files.length > 0) {
|
|
587
|
+
lines.push("## Hot files");
|
|
588
|
+
for (const file of result.files) {
|
|
589
|
+
lines.push(`- ${file.path} refs:${file.references} items:[${file.items.join(",")}]`);
|
|
590
|
+
}
|
|
591
|
+
lines.push("");
|
|
592
|
+
}
|
|
593
|
+
if (result.workload && result.workload.length > 0) {
|
|
594
|
+
lines.push("## Workload");
|
|
595
|
+
for (const entry of result.workload) {
|
|
596
|
+
const who = entry.assignee ?? "(unassigned)";
|
|
597
|
+
lines.push(`- ${who} active:${entry.active} wip:${entry.in_progress} items:[${entry.items.join(",")}]`);
|
|
598
|
+
}
|
|
599
|
+
lines.push("");
|
|
600
|
+
}
|
|
601
|
+
if (result.staleness && result.staleness.length > 0) {
|
|
602
|
+
lines.push("## Stale items");
|
|
603
|
+
for (const entry of result.staleness) {
|
|
604
|
+
lines.push(`- ${entry.id} ${entry.status} stale:${entry.stale_days}d last:${entry.updated_at.slice(0, 10)} "${entry.title}"`);
|
|
605
|
+
}
|
|
606
|
+
lines.push("");
|
|
607
|
+
}
|
|
608
|
+
if (result.tests) {
|
|
609
|
+
lines.push("## Test health");
|
|
610
|
+
lines.push(`- items_with_tests: ${result.tests.items_with_tests}`);
|
|
611
|
+
lines.push(`- items_with_recent_runs: ${result.tests.items_with_recent_runs}`);
|
|
612
|
+
lines.push(`- passed: ${result.tests.recent_runs.passed}, failed: ${result.tests.recent_runs.failed}, skipped: ${result.tests.recent_runs.skipped}`);
|
|
613
|
+
if (result.tests.items_failing.length > 0) {
|
|
614
|
+
lines.push(`- items_failing: [${result.tests.items_failing.join(",")}]`);
|
|
615
|
+
}
|
|
616
|
+
lines.push("");
|
|
617
|
+
}
|
|
214
618
|
const isEmpty = result.summary.active_items === 0 &&
|
|
215
619
|
result.summary.blocked === 0 &&
|
|
216
620
|
result.agenda.summary.events === 0;
|
|
217
621
|
if (isEmpty) {
|
|
218
|
-
lines.push("");
|
|
219
622
|
lines.push("## Suggestions");
|
|
220
623
|
lines.push("No active work items or upcoming events. Consider:");
|
|
221
624
|
lines.push("- `pm create --type Task --title \"...\"` to add a new work item");
|
|
@@ -225,14 +628,29 @@ export function renderContextMarkdown(result) {
|
|
|
225
628
|
}
|
|
226
629
|
return lines.join("\n");
|
|
227
630
|
}
|
|
631
|
+
// ---------------------------------------------------------------------------
|
|
632
|
+
// Main runner
|
|
633
|
+
// ---------------------------------------------------------------------------
|
|
228
634
|
export async function runContext(options, global) {
|
|
229
635
|
const pmRoot = resolvePmRoot(process.cwd(), global.path);
|
|
230
636
|
const settings = await readSettings(pmRoot);
|
|
637
|
+
const contextSettings = settings.context ?? SETTINGS_DEFAULTS.context;
|
|
231
638
|
const statusRegistry = resolveRuntimeStatusRegistry(settings.schema);
|
|
232
|
-
const limit =
|
|
639
|
+
const limit = parseContextLimit(options.limit);
|
|
640
|
+
const depth = parseContextDepth(options.depth, contextSettings);
|
|
641
|
+
const sectionsIncluded = parseContextSections(options.section, depth, contextSettings);
|
|
642
|
+
const activityLimit = parseActivityLimit(options.activityLimit, contextSettings);
|
|
643
|
+
const staleThresholdDays = parseStaleThresholdDays(options.staleThreshold, contextSettings);
|
|
644
|
+
const needsAllItems = sectionsIncluded.some((s) => ["hierarchy", "progress", "blockers", "staleness"].includes(s));
|
|
233
645
|
const listOptions = { ...options, excludeTerminal: true };
|
|
234
646
|
const listed = await runList(undefined, listOptions, global);
|
|
235
647
|
const listedFrontMatter = listed.items;
|
|
648
|
+
let allItems = listedFrontMatter;
|
|
649
|
+
if (needsAllItems) {
|
|
650
|
+
const allListOptions = { ...options, excludeTerminal: false };
|
|
651
|
+
const allListed = await runList(undefined, allListOptions, global);
|
|
652
|
+
allItems = allListed.items;
|
|
653
|
+
}
|
|
236
654
|
const ranked = [...listedFrontMatter].sort((left, right) => compareCriticalItems(left, right, statusRegistry));
|
|
237
655
|
const activeStatuses = statusRegistry.active_statuses.size > 0
|
|
238
656
|
? statusRegistry.active_statuses
|
|
@@ -266,9 +684,36 @@ export async function runContext(options, global) {
|
|
|
266
684
|
? activeItems.filter((item) => normalizeStatusForRegistry(item.status, statusRegistry) === inProgressStatus).length
|
|
267
685
|
: 0;
|
|
268
686
|
const openCount = activeItems.filter((item) => normalizeStatusForRegistry(item.status, statusRegistry) === openStatus).length;
|
|
269
|
-
|
|
687
|
+
const now = agenda.now;
|
|
688
|
+
const itemMap = new Map();
|
|
689
|
+
for (const item of allItems) {
|
|
690
|
+
itemMap.set(item.id, item);
|
|
691
|
+
}
|
|
692
|
+
const allNonTerminal = allItems.filter((item) => !isTerminal(item.status, statusRegistry));
|
|
693
|
+
const has = (section) => sectionsIncluded.includes(section);
|
|
694
|
+
const hierarchy = has("hierarchy") ? buildHierarchy(allItems, activeItems, statusRegistry, limit) : undefined;
|
|
695
|
+
const activity = has("activity") ? await buildActivity(activityLimit, global) : undefined;
|
|
696
|
+
const progress = has("progress") ? buildProgress(allItems, activeItems, statusRegistry, limit) : undefined;
|
|
697
|
+
const blockersSection = has("blockers") ? buildBlockers(blockedItems, itemMap, limit) : undefined;
|
|
698
|
+
const filesSection = has("files") ? buildHotFiles(activeItems, limit) : undefined;
|
|
699
|
+
const workload = has("workload") ? buildWorkload(activeItems, statusRegistry, limit) : undefined;
|
|
700
|
+
const staleness = has("staleness") ? buildStaleness(allNonTerminal, staleThresholdDays, now, limit) : undefined;
|
|
701
|
+
const tests = has("tests") ? buildTestHealth(activeItems) : undefined;
|
|
702
|
+
const summaryExtras = needsAllItems
|
|
703
|
+
? {
|
|
704
|
+
total_items: allItems.length,
|
|
705
|
+
closed: allItems.filter((i) => isClosedStatus(i.status, statusRegistry)).length,
|
|
706
|
+
canceled: allItems.filter((i) => {
|
|
707
|
+
const canceledStatus = normalizeStatusInput("canceled", statusRegistry);
|
|
708
|
+
return canceledStatus ? normalizeStatusForRegistry(i.status, statusRegistry) === canceledStatus : false;
|
|
709
|
+
}).length,
|
|
710
|
+
}
|
|
711
|
+
: {};
|
|
712
|
+
const result = {
|
|
270
713
|
output_default: "toon",
|
|
271
|
-
now
|
|
714
|
+
now,
|
|
715
|
+
depth,
|
|
716
|
+
sections_included: sectionsIncluded,
|
|
272
717
|
window: {
|
|
273
718
|
anchor: agenda.anchor,
|
|
274
719
|
start: agenda.range.start,
|
|
@@ -297,6 +742,7 @@ export async function runContext(options, global) {
|
|
|
297
742
|
high_level: highLevel.length,
|
|
298
743
|
low_level: lowLevel.length,
|
|
299
744
|
agenda_events: agendaSummary.events,
|
|
745
|
+
...summaryExtras,
|
|
300
746
|
},
|
|
301
747
|
high_level: highLevel,
|
|
302
748
|
low_level: lowLevel,
|
|
@@ -305,17 +751,33 @@ export async function runContext(options, global) {
|
|
|
305
751
|
summary: agendaSummary,
|
|
306
752
|
events: agendaEvents,
|
|
307
753
|
},
|
|
308
|
-
...(warnings.length > 0 ? { warnings } : {}),
|
|
309
|
-
...(activeItems.length === 0 && blockedItems.length === 0 && agendaEvents.length === 0
|
|
310
|
-
? {
|
|
311
|
-
suggestions: [
|
|
312
|
-
'pm create --type Task --title "..." to add a new work item',
|
|
313
|
-
"pm list --status closed --limit 5 to review recent completions",
|
|
314
|
-
"pm search <keywords> to find related past work",
|
|
315
|
-
"pm aggregate for a full project status overview",
|
|
316
|
-
],
|
|
317
|
-
}
|
|
318
|
-
: {}),
|
|
319
754
|
};
|
|
755
|
+
if (hierarchy)
|
|
756
|
+
result.hierarchy = hierarchy;
|
|
757
|
+
if (activity)
|
|
758
|
+
result.activity = activity;
|
|
759
|
+
if (progress)
|
|
760
|
+
result.progress = progress;
|
|
761
|
+
if (blockersSection)
|
|
762
|
+
result.blockers = blockersSection;
|
|
763
|
+
if (filesSection)
|
|
764
|
+
result.files = filesSection;
|
|
765
|
+
if (workload)
|
|
766
|
+
result.workload = workload;
|
|
767
|
+
if (staleness)
|
|
768
|
+
result.staleness = staleness;
|
|
769
|
+
if (tests)
|
|
770
|
+
result.tests = tests;
|
|
771
|
+
if (warnings.length > 0)
|
|
772
|
+
result.warnings = warnings;
|
|
773
|
+
if (activeItems.length === 0 && blockedItems.length === 0 && agendaEvents.length === 0) {
|
|
774
|
+
result.suggestions = [
|
|
775
|
+
'pm create --type Task --title "..." to add a new work item',
|
|
776
|
+
"pm list --status closed --limit 5 to review recent completions",
|
|
777
|
+
"pm search <keywords> to find related past work",
|
|
778
|
+
"pm aggregate for a full project status overview",
|
|
779
|
+
];
|
|
780
|
+
}
|
|
781
|
+
return result;
|
|
320
782
|
}
|
|
321
783
|
//# sourceMappingURL=context.js.map
|