pmem-ai 0.6.0 → 0.6.1
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/CHANGELOG.md +24 -0
- package/dist/commands/update.d.ts +1 -0
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +331 -131
- package/dist/commands/update.js.map +1 -1
- package/dist/commands/verify.d.ts.map +1 -1
- package/dist/commands/verify.js +17 -33
- package/dist/commands/verify.js.map +1 -1
- package/dist/core/consistency.d.ts +10 -0
- package/dist/core/consistency.d.ts.map +1 -0
- package/dist/core/consistency.js +97 -0
- package/dist/core/consistency.js.map +1 -0
- package/dist/core/db.d.ts +9 -0
- package/dist/core/db.d.ts.map +1 -1
- package/dist/core/db.js +4 -0
- package/dist/core/db.js.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +40 -0
- package/dist/types.d.ts.map +1 -1
- package/docs/handover-v0.6.md +149 -0
- package/docs/project-roadmap.md +41 -5
- package/docs/v0.6.1 pre-design.md +566 -0
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to pmem are documented here.
|
|
4
4
|
|
|
5
|
+
## 0.6.1 - Actionable Update Suggestions
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Aggregate duplicate `update --suggest` items by `target + reason + matched_file`, with counts and `sources` arrays.
|
|
10
|
+
- Split suggestion output into `blocking_for_verify`, `current_suggestions`, and `historical_dirty_flags` groups.
|
|
11
|
+
- Add machine-readable severity metadata per suggestion: `severity`, `blocks_verify`, `is_duplicate`, `is_historical`.
|
|
12
|
+
- Compact output summarizing affected cards, blocking issues, hidden duplicates, and hidden history.
|
|
13
|
+
- `--include-history` flag to inspect historical dirty flags that are hidden by default.
|
|
14
|
+
- Shared `checkStaleMemory()` in `src/core/consistency.ts`, aligning `update --suggest` with `pmem verify`.
|
|
15
|
+
- Structured JSON output with `summary`, `message`, `next_steps`, and `groups` for agent decision-making.
|
|
16
|
+
- v0.6.1 E2E test suite covering duplicate aggregation, historical hiding, include-history, missing DB, and blocking groups.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- `update --suggest --format json` output restructured from flat arrays to `summary` + `groups`.
|
|
21
|
+
- `pmem verify` stale-memory check now delegates to shared `checkStaleMemory()` from `consistency.ts`.
|
|
22
|
+
- `update --suggest` exit code: only hidden historical items return 0; missing/corrupt DB returns 2 (runtime error).
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- Long-term projects no longer see repeated dirty flags and historical suggestions flooding `update --suggest` output.
|
|
27
|
+
- `pmem verify` 100/100 and `update --suggest` now semantically aligned — verify-clean projects see "No blocking memory consistency issues."
|
|
28
|
+
|
|
5
29
|
## 0.6.0 - Agent-native Workflow Polish
|
|
6
30
|
|
|
7
31
|
### Added
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../../src/commands/update.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../../src/commands/update.ts"],"names":[],"mappings":"AAaA,wBAAgB,aAAa,CAAC,OAAO,EAAE;IACrC,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B,GAAG,IAAI,CAyCP;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,IAAI,CA8FvF"}
|
package/dist/commands/update.js
CHANGED
|
@@ -42,6 +42,7 @@ const manifest_1 = require("../core/manifest");
|
|
|
42
42
|
const rebuild_1 = require("./rebuild");
|
|
43
43
|
const db_1 = require("../core/db");
|
|
44
44
|
const git_1 = require("../core/git");
|
|
45
|
+
const consistency_1 = require("../core/consistency");
|
|
45
46
|
const PMEM_DIR = '.pmem';
|
|
46
47
|
function updateCommand(options) {
|
|
47
48
|
const cwd = process.cwd();
|
|
@@ -57,7 +58,7 @@ function updateCommand(options) {
|
|
|
57
58
|
}
|
|
58
59
|
// --suggest: show intelligent update suggestions
|
|
59
60
|
if (options.suggest) {
|
|
60
|
-
suggestActions(pmemPath, options.format);
|
|
61
|
+
suggestActions(pmemPath, options.format, options.includeHistory);
|
|
61
62
|
return;
|
|
62
63
|
}
|
|
63
64
|
// --apply-suggestion: apply a specific suggestion
|
|
@@ -358,132 +359,301 @@ function listSourceFiles(root) {
|
|
|
358
359
|
walk(root);
|
|
359
360
|
return results;
|
|
360
361
|
}
|
|
361
|
-
|
|
362
|
+
/**
|
|
363
|
+
* Extract the matched file path from a dirty flag reason string.
|
|
364
|
+
* Handles formats like "file_changed: path/to/file" or plain text.
|
|
365
|
+
*/
|
|
366
|
+
function extractMatchedFile(reason) {
|
|
367
|
+
const match = reason.match(/^file_changed:\s*(.+)/);
|
|
368
|
+
if (match) {
|
|
369
|
+
return match[1].trim();
|
|
370
|
+
}
|
|
371
|
+
return null;
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Build the aggregation key for a dirty flag: target + reason + matched_file.
|
|
375
|
+
*/
|
|
376
|
+
function aggregationKey(flag) {
|
|
377
|
+
const mf = extractMatchedFile(flag.reason);
|
|
378
|
+
return `${flag.target}||${flag.reason}||${mf ?? ''}`;
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Find the most recent session end time.
|
|
382
|
+
* Returns null if no ended session exists.
|
|
383
|
+
*/
|
|
384
|
+
function getLatestSessionEnd(pmemPath) {
|
|
385
|
+
try {
|
|
386
|
+
const db = (0, db_1.openDatabase)(pmemPath);
|
|
387
|
+
const row = db.prepare("SELECT ended_at FROM sessions WHERE ended_at IS NOT NULL ORDER BY ended_at DESC LIMIT 1").get();
|
|
388
|
+
return row?.ended_at ?? null;
|
|
389
|
+
}
|
|
390
|
+
catch {
|
|
391
|
+
return null;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Get the active (un-ended) session if one exists.
|
|
396
|
+
*/
|
|
397
|
+
function getActiveSessionStart(pmemPath) {
|
|
398
|
+
try {
|
|
399
|
+
const db = (0, db_1.openDatabase)(pmemPath);
|
|
400
|
+
const row = db.prepare("SELECT started_at FROM sessions WHERE ended_at IS NULL ORDER BY started_at DESC LIMIT 1").get();
|
|
401
|
+
return row?.started_at ?? null;
|
|
402
|
+
}
|
|
403
|
+
catch {
|
|
404
|
+
return null;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
function generateSuggestions(pmemPath, includeHistory = false) {
|
|
362
408
|
const dbPath = path.join(pmemPath, 'pmem.db');
|
|
363
409
|
if (!(0, fs_1.fileExists)(dbPath)) {
|
|
364
|
-
|
|
365
|
-
|
|
410
|
+
return {
|
|
411
|
+
summary: { affected_cards: 0, blocking: 0, warning: 0, info: 0, duplicates_hidden: 0, historical_hidden: 0, verify_blocking: false },
|
|
412
|
+
message: 'No SQLite database. Run pmem rebuild first.',
|
|
413
|
+
next_steps: ['Run `pmem rebuild` to create the database index.'],
|
|
414
|
+
groups: { blocking_for_verify: [], current_suggestions: [], historical_dirty_flags: [] },
|
|
415
|
+
error: true,
|
|
416
|
+
};
|
|
366
417
|
}
|
|
367
418
|
let db;
|
|
368
419
|
try {
|
|
369
420
|
db = (0, db_1.openDatabase)(pmemPath);
|
|
370
421
|
}
|
|
371
422
|
catch {
|
|
372
|
-
|
|
373
|
-
|
|
423
|
+
return {
|
|
424
|
+
summary: { affected_cards: 0, blocking: 0, warning: 0, info: 0, duplicates_hidden: 0, historical_hidden: 0, verify_blocking: false },
|
|
425
|
+
message: 'Cannot open database. Run pmem rebuild first.',
|
|
426
|
+
next_steps: ['Run `pmem rebuild` to recreate the database.'],
|
|
427
|
+
groups: { blocking_for_verify: [], current_suggestions: [], historical_dirty_flags: [] },
|
|
428
|
+
error: true,
|
|
429
|
+
};
|
|
374
430
|
}
|
|
375
|
-
|
|
376
|
-
const
|
|
377
|
-
//
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
// Project-level dirty → create_trace or confirm suggestions
|
|
390
|
-
for (const flag of unresolved) {
|
|
391
|
-
if (flag.scope === 'project') {
|
|
392
|
-
suggestions.push({
|
|
393
|
-
id: `suggest-${suggestions.length + 1}`,
|
|
394
|
-
action: 'create_trace',
|
|
395
|
-
target: flag.target,
|
|
396
|
-
reason: flag.reason,
|
|
397
|
-
priority: 'medium',
|
|
398
|
-
});
|
|
431
|
+
// 1. Get raw dirty flags with full details
|
|
432
|
+
const allFlags = (0, db_1.getUnresolvedDirtyFlagsDetailed)(db);
|
|
433
|
+
// 2. Run shared stale-memory consistency check
|
|
434
|
+
const staleIssues = (0, consistency_1.checkStaleMemory)(pmemPath);
|
|
435
|
+
// Build lookup: card_id → set of stale file paths
|
|
436
|
+
const staleByCard = new Map();
|
|
437
|
+
for (const issue of staleIssues) {
|
|
438
|
+
if (issue.card_id) {
|
|
439
|
+
if (!staleByCard.has(issue.card_id)) {
|
|
440
|
+
staleByCard.set(issue.card_id, new Set());
|
|
441
|
+
}
|
|
442
|
+
if (issue.file_path) {
|
|
443
|
+
staleByCard.get(issue.card_id).add(issue.file_path);
|
|
444
|
+
}
|
|
399
445
|
}
|
|
400
446
|
}
|
|
401
|
-
//
|
|
402
|
-
|
|
403
|
-
const
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
const hoursSinceUpdate = (Date.now() - stat.mtimeMs) / (1000 * 60 * 60);
|
|
408
|
-
if (hoursSinceUpdate > 24) {
|
|
409
|
-
stateFreshness = 'stale';
|
|
447
|
+
// 3. Aggregate dirty flags by target + reason + matched_file
|
|
448
|
+
const groups = new Map();
|
|
449
|
+
for (const flag of allFlags) {
|
|
450
|
+
const key = aggregationKey(flag);
|
|
451
|
+
if (!groups.has(key)) {
|
|
452
|
+
groups.set(key, []);
|
|
410
453
|
}
|
|
454
|
+
groups.get(key).push(flag);
|
|
411
455
|
}
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
456
|
+
// 4. Get session boundaries for historical classification
|
|
457
|
+
const latestSessionEnd = getLatestSessionEnd(pmemPath);
|
|
458
|
+
const activeSessionStart = getActiveSessionStart(pmemPath);
|
|
459
|
+
const sessionBoundary = latestSessionEnd || activeSessionStart;
|
|
460
|
+
// 5. Get total card count for affected_cards
|
|
461
|
+
const cardCount = getCardCount(pmemPath);
|
|
462
|
+
// 6. Classify each aggregated group
|
|
463
|
+
const blockingForVerify = [];
|
|
464
|
+
const currentSuggestions = [];
|
|
465
|
+
const historicalDirtyFlags = [];
|
|
466
|
+
for (const [key, flags] of groups) {
|
|
467
|
+
const representative = flags[0];
|
|
468
|
+
const matchedFile = extractMatchedFile(representative.reason);
|
|
469
|
+
// Determine if this group blocks verify
|
|
470
|
+
let blocksVerify = false;
|
|
471
|
+
if (representative.scope === 'card' && staleByCard.has(representative.target)) {
|
|
472
|
+
const staleFiles = staleByCard.get(representative.target);
|
|
473
|
+
if (matchedFile && staleFiles.has(matchedFile)) {
|
|
474
|
+
blocksVerify = true;
|
|
475
|
+
}
|
|
476
|
+
else if (!matchedFile) {
|
|
477
|
+
// Card is in stale list, even if we can't match the specific file
|
|
478
|
+
blocksVerify = true;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
// Determine severity
|
|
482
|
+
let severity;
|
|
483
|
+
if (blocksVerify) {
|
|
484
|
+
severity = 'blocking';
|
|
485
|
+
}
|
|
486
|
+
else if (representative.scope === 'card') {
|
|
487
|
+
severity = 'warning';
|
|
488
|
+
}
|
|
489
|
+
else {
|
|
490
|
+
severity = 'info';
|
|
491
|
+
}
|
|
492
|
+
// Historical classification
|
|
493
|
+
const allCreatedAts = flags.map(f => f.created_at).sort();
|
|
494
|
+
const latestCreated = allCreatedAts[allCreatedAts.length - 1];
|
|
495
|
+
const earliestCreated = allCreatedAts[0];
|
|
496
|
+
const isMulti = flags.length > 1;
|
|
497
|
+
let isHistorical = false;
|
|
498
|
+
let isDuplicate = false;
|
|
499
|
+
if (blocksVerify) {
|
|
500
|
+
// Blocking items are never historical
|
|
501
|
+
isHistorical = false;
|
|
502
|
+
isDuplicate = isMulti;
|
|
503
|
+
}
|
|
504
|
+
else if (sessionBoundary && latestCreated < sessionBoundary) {
|
|
505
|
+
// All flags are from before the session boundary → historical
|
|
506
|
+
isHistorical = true;
|
|
507
|
+
isDuplicate = isMulti;
|
|
508
|
+
}
|
|
509
|
+
else if (isMulti && sessionBoundary && latestCreated < sessionBoundary) {
|
|
510
|
+
// Multiple flags, all old → historical duplicate
|
|
511
|
+
isHistorical = true;
|
|
512
|
+
isDuplicate = true;
|
|
513
|
+
}
|
|
514
|
+
else {
|
|
515
|
+
// Default: keep in current
|
|
516
|
+
isHistorical = false;
|
|
517
|
+
isDuplicate = isMulti;
|
|
518
|
+
}
|
|
519
|
+
const aggregated = {
|
|
520
|
+
target: representative.target,
|
|
521
|
+
reason: representative.reason,
|
|
522
|
+
matched_file: matchedFile,
|
|
523
|
+
count: flags.length,
|
|
524
|
+
severity,
|
|
525
|
+
blocks_verify: blocksVerify,
|
|
526
|
+
is_duplicate: isDuplicate,
|
|
527
|
+
is_historical: isHistorical,
|
|
528
|
+
created_at_first: earliestCreated,
|
|
529
|
+
created_at_last: latestCreated,
|
|
530
|
+
sources: flags.map(f => ({
|
|
531
|
+
scope: f.scope,
|
|
532
|
+
target: f.target,
|
|
533
|
+
reason: f.reason,
|
|
534
|
+
created_at: f.created_at,
|
|
535
|
+
session_id: f.session_id,
|
|
536
|
+
})),
|
|
537
|
+
};
|
|
538
|
+
if (blocksVerify) {
|
|
539
|
+
blockingForVerify.push(aggregated);
|
|
540
|
+
}
|
|
541
|
+
else if (isHistorical) {
|
|
542
|
+
historicalDirtyFlags.push(aggregated);
|
|
543
|
+
}
|
|
544
|
+
else {
|
|
545
|
+
currentSuggestions.push(aggregated);
|
|
546
|
+
}
|
|
420
547
|
}
|
|
421
|
-
//
|
|
422
|
-
const
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
(
|
|
440
|
-
|
|
548
|
+
// 7. Compute summary
|
|
549
|
+
const uniqueAffectedCards = new Set();
|
|
550
|
+
for (const item of [...blockingForVerify, ...currentSuggestions]) {
|
|
551
|
+
uniqueAffectedCards.add(item.target);
|
|
552
|
+
}
|
|
553
|
+
const duplicatesHidden = [...blockingForVerify, ...currentSuggestions, ...historicalDirtyFlags]
|
|
554
|
+
.filter(g => g.count > 1)
|
|
555
|
+
.reduce((sum, g) => sum + (g.count - 1), 0);
|
|
556
|
+
const summary = {
|
|
557
|
+
affected_cards: uniqueAffectedCards.size,
|
|
558
|
+
blocking: blockingForVerify.length,
|
|
559
|
+
warning: currentSuggestions.filter(g => g.severity === 'warning').length,
|
|
560
|
+
info: currentSuggestions.filter(g => g.severity === 'info').length,
|
|
561
|
+
duplicates_hidden: duplicatesHidden,
|
|
562
|
+
historical_hidden: includeHistory ? 0 : historicalDirtyFlags.length,
|
|
563
|
+
verify_blocking: blockingForVerify.length > 0,
|
|
564
|
+
};
|
|
565
|
+
// 8. Build message and next steps
|
|
566
|
+
const message = buildSuggestMessage(summary, cardCount);
|
|
567
|
+
const nextSteps = buildSuggestNextSteps(summary, cardCount);
|
|
568
|
+
return {
|
|
569
|
+
summary,
|
|
570
|
+
message,
|
|
571
|
+
next_steps: nextSteps,
|
|
572
|
+
groups: {
|
|
573
|
+
blocking_for_verify: blockingForVerify,
|
|
574
|
+
current_suggestions: currentSuggestions,
|
|
575
|
+
historical_dirty_flags: includeHistory ? historicalDirtyFlags : [],
|
|
576
|
+
},
|
|
577
|
+
};
|
|
441
578
|
}
|
|
442
|
-
function suggestActions(pmemPath, format) {
|
|
443
|
-
const
|
|
444
|
-
// Build contextual message and next steps for empty results
|
|
445
|
-
const cardCount = getCardCount(pmemPath);
|
|
446
|
-
const message = buildSuggestMessage(suggestions, dirtyFlags, cardCount);
|
|
447
|
-
const nextSteps = buildSuggestNextSteps(suggestions, dirtyFlags, cardCount);
|
|
579
|
+
function suggestActions(pmemPath, format, includeHistory) {
|
|
580
|
+
const report = generateSuggestions(pmemPath, includeHistory);
|
|
448
581
|
if (format === 'json') {
|
|
449
582
|
console.log(JSON.stringify({
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
next_steps: nextSteps,
|
|
583
|
+
summary: report.summary,
|
|
584
|
+
message: report.message,
|
|
585
|
+
next_steps: report.next_steps,
|
|
586
|
+
groups: report.groups,
|
|
455
587
|
}, null, 2));
|
|
456
588
|
}
|
|
457
589
|
else {
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
590
|
+
// Compact output
|
|
591
|
+
console.log('Memory update suggestions');
|
|
592
|
+
console.log('');
|
|
593
|
+
console.log(`Affected cards: ${report.summary.affected_cards}`);
|
|
594
|
+
console.log(`Blocking for verify: ${report.summary.blocking}`);
|
|
595
|
+
console.log(`Current suggestions: ${report.summary.warning + report.summary.info}`);
|
|
596
|
+
console.log(`Historical hidden: ${report.summary.historical_hidden}`);
|
|
597
|
+
console.log(`Duplicate flags hidden: ${report.summary.duplicates_hidden}`);
|
|
598
|
+
// Blocking section
|
|
599
|
+
if (report.groups.blocking_for_verify.length > 0) {
|
|
600
|
+
console.log('');
|
|
601
|
+
console.log('Blocking:');
|
|
602
|
+
for (const item of report.groups.blocking_for_verify) {
|
|
603
|
+
const filePart = (item.matched_file && !item.reason.includes(item.matched_file)) ? `, ${item.matched_file}` : '';
|
|
604
|
+
const countPart = item.count > 1 ? `, count ${item.count}` : '';
|
|
605
|
+
console.log(` - ${item.target} (${item.reason}${filePart}${countPart})`);
|
|
462
606
|
}
|
|
463
607
|
}
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
console.log(` ${
|
|
472
|
-
console.log(` ${s.reason}`);
|
|
608
|
+
// Current section
|
|
609
|
+
if (report.groups.current_suggestions.length > 0) {
|
|
610
|
+
console.log('');
|
|
611
|
+
console.log('Current:');
|
|
612
|
+
for (const item of report.groups.current_suggestions) {
|
|
613
|
+
const filePart = (item.matched_file && !item.reason.includes(item.matched_file)) ? `, ${item.matched_file}` : '';
|
|
614
|
+
const countPart = item.count > 1 ? `, count ${item.count}` : '';
|
|
615
|
+
console.log(` - ${item.target} (${item.reason}${filePart}${countPart})`);
|
|
473
616
|
}
|
|
474
617
|
}
|
|
618
|
+
// Historical section (only when --include-history)
|
|
619
|
+
if (includeHistory && report.groups.historical_dirty_flags.length > 0) {
|
|
620
|
+
console.log('');
|
|
621
|
+
console.log('Historical:');
|
|
622
|
+
for (const item of report.groups.historical_dirty_flags) {
|
|
623
|
+
const filePart = (item.matched_file && !item.reason.includes(item.matched_file)) ? `, ${item.matched_file}` : '';
|
|
624
|
+
const countPart = item.count > 1 ? `, count ${item.count}` : '';
|
|
625
|
+
console.log(` - ${item.target} (${item.reason}${filePart}${countPart})`);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
// Message
|
|
629
|
+
console.log('');
|
|
630
|
+
if (report.summary.blocking > 0) {
|
|
631
|
+
console.log(report.message);
|
|
632
|
+
}
|
|
475
633
|
else {
|
|
476
|
-
console.log(
|
|
477
|
-
if (
|
|
478
|
-
console.log('
|
|
479
|
-
for (const step of nextSteps) {
|
|
480
|
-
console.log(` ${step}`);
|
|
481
|
-
}
|
|
634
|
+
console.log('No blocking memory consistency issues.');
|
|
635
|
+
if (report.summary.historical_hidden > 0) {
|
|
636
|
+
console.log('Historical suggestions available with --include-history.');
|
|
482
637
|
}
|
|
483
638
|
}
|
|
639
|
+
// Next steps
|
|
640
|
+
if (report.next_steps.length > 0) {
|
|
641
|
+
console.log('');
|
|
642
|
+
console.log('Next:');
|
|
643
|
+
for (const step of report.next_steps) {
|
|
644
|
+
console.log(` - ${step}`);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
// Exit code: 2 for runtime errors (missing DB, etc.)
|
|
649
|
+
if (report.error) {
|
|
650
|
+
process.exit(2);
|
|
484
651
|
}
|
|
485
|
-
|
|
486
|
-
|
|
652
|
+
// Exit code: 1 if there are blocking or current suggestions (actionable items)
|
|
653
|
+
// Exit 0 if only hidden history or nothing at all
|
|
654
|
+
const hasActionable = report.summary.blocking > 0 ||
|
|
655
|
+
(report.summary.warning + report.summary.info) > 0;
|
|
656
|
+
process.exit(hasActionable ? 1 : 0);
|
|
487
657
|
}
|
|
488
658
|
function getCardCount(pmemPath) {
|
|
489
659
|
try {
|
|
@@ -495,45 +665,70 @@ function getCardCount(pmemPath) {
|
|
|
495
665
|
return 0;
|
|
496
666
|
}
|
|
497
667
|
}
|
|
498
|
-
function buildSuggestMessage(
|
|
499
|
-
if (suggestions.length > 0) {
|
|
500
|
-
return `${suggestions.length} suggestion(s) found.`;
|
|
501
|
-
}
|
|
502
|
-
if (dirtyFlags.length > 0) {
|
|
503
|
-
return 'Dirty flags exist but no matching suggestions could be generated.';
|
|
504
|
-
}
|
|
668
|
+
function buildSuggestMessage(summary, cardCount) {
|
|
505
669
|
if (cardCount === 0) {
|
|
506
670
|
return 'No memory cards found. Create a first module, decision, or task card to start building project memory.';
|
|
507
671
|
}
|
|
672
|
+
if (summary.blocking > 0 || summary.warning > 0 || summary.info > 0) {
|
|
673
|
+
const parts = [];
|
|
674
|
+
if (summary.blocking > 0)
|
|
675
|
+
parts.push(`${summary.blocking} blocking memory consistency issue(s)`);
|
|
676
|
+
if (summary.warning > 0)
|
|
677
|
+
parts.push(`${summary.warning} current suggestion(s)`);
|
|
678
|
+
if (summary.info > 0)
|
|
679
|
+
parts.push(`${summary.info} informational item(s)`);
|
|
680
|
+
return parts.join(' and ') + '.';
|
|
681
|
+
}
|
|
508
682
|
return 'No suggestions. Memory is up to date.';
|
|
509
683
|
}
|
|
510
|
-
function buildSuggestNextSteps(
|
|
511
|
-
if (suggestions.length > 0)
|
|
512
|
-
return [];
|
|
684
|
+
function buildSuggestNextSteps(summary, cardCount) {
|
|
513
685
|
const steps = [];
|
|
514
686
|
if (cardCount === 0) {
|
|
515
687
|
steps.push('Create a module card with source_files pointing to your code');
|
|
516
688
|
steps.push('Run `pmem rebuild` after creating cards');
|
|
517
689
|
steps.push('Then try `pmem status` and `pmem mark-dirty --auto`');
|
|
518
690
|
}
|
|
519
|
-
else if (
|
|
520
|
-
steps.push('
|
|
521
|
-
steps.push('
|
|
691
|
+
else if (summary.blocking > 0 || summary.warning > 0) {
|
|
692
|
+
steps.push('Update or confirm affected cards with pmem update --confirm -s "<summary>" -n "<next step>"');
|
|
693
|
+
steps.push('Use --include-history to inspect older dirty flags.');
|
|
694
|
+
}
|
|
695
|
+
else if (summary.historical_hidden > 0) {
|
|
696
|
+
steps.push('Use --include-history to inspect older dirty flags.');
|
|
697
|
+
steps.push('Run `pmem verify` to check overall memory consistency.');
|
|
522
698
|
}
|
|
523
699
|
else {
|
|
524
|
-
steps.push('
|
|
525
|
-
steps.push('
|
|
700
|
+
steps.push('Edit some source files, then run `pmem status` and `pmem mark-dirty --auto`');
|
|
701
|
+
steps.push('Run `pmem verify` to check overall memory consistency');
|
|
526
702
|
}
|
|
527
703
|
return steps;
|
|
528
704
|
}
|
|
529
705
|
function applySuggestionAction(pmemPath, suggestionId) {
|
|
530
|
-
// Re-derive suggestions to find the matching one
|
|
531
|
-
const
|
|
532
|
-
|
|
706
|
+
// Re-derive suggestions to find the matching one (with history included for full search)
|
|
707
|
+
const report = generateSuggestions(pmemPath, true);
|
|
708
|
+
// Flatten all groups into a single searchable list with generated IDs
|
|
709
|
+
const flatList = [];
|
|
710
|
+
let idx = 1;
|
|
711
|
+
for (const item of report.groups.blocking_for_verify) {
|
|
712
|
+
const action = item.reason.startsWith('file_changed') ? 'update_card' : 'create_trace';
|
|
713
|
+
flatList.push({ id: `suggest-${idx}`, item, action });
|
|
714
|
+
idx++;
|
|
715
|
+
}
|
|
716
|
+
for (const item of report.groups.current_suggestions) {
|
|
717
|
+
const action = item.reason.startsWith('file_changed') ? 'update_card' : 'create_trace';
|
|
718
|
+
flatList.push({ id: `suggest-${idx}`, item, action });
|
|
719
|
+
idx++;
|
|
720
|
+
}
|
|
721
|
+
for (const item of report.groups.historical_dirty_flags) {
|
|
722
|
+
const action = item.reason.startsWith('file_changed') ? 'update_card' : 'create_trace';
|
|
723
|
+
flatList.push({ id: `suggest-${idx}`, item, action });
|
|
724
|
+
idx++;
|
|
725
|
+
}
|
|
726
|
+
const match = flatList.find(s => s.id === suggestionId);
|
|
533
727
|
if (!match) {
|
|
534
728
|
console.log(`Suggestion "${suggestionId}" not found. Available suggestions:`);
|
|
535
|
-
for (const s of
|
|
536
|
-
|
|
729
|
+
for (const s of flatList) {
|
|
730
|
+
const filePart = s.item.matched_file ? `, ${s.item.matched_file}` : '';
|
|
731
|
+
console.log(` ${s.id}: ${s.action} ${s.item.target} (${s.item.reason}${filePart})`);
|
|
537
732
|
}
|
|
538
733
|
process.exit(2);
|
|
539
734
|
}
|
|
@@ -542,14 +737,17 @@ function applySuggestionAction(pmemPath, suggestionId) {
|
|
|
542
737
|
console.log('No SQLite database. Run pmem rebuild first.');
|
|
543
738
|
process.exit(2);
|
|
544
739
|
}
|
|
545
|
-
|
|
740
|
+
const action = match.action;
|
|
741
|
+
const target = match.item.target;
|
|
742
|
+
const reason = match.item.reason;
|
|
743
|
+
switch (action) {
|
|
546
744
|
case 'update_card': {
|
|
547
745
|
const db = (0, db_1.openDatabase)(pmemPath);
|
|
548
746
|
// Mark the card's last_verified_at as expired
|
|
549
|
-
db.prepare("UPDATE cards SET last_verified_at = ? WHERE id = ?").run(new Date(0).toISOString(),
|
|
747
|
+
db.prepare("UPDATE cards SET last_verified_at = ? WHERE id = ?").run(new Date(0).toISOString(), target);
|
|
550
748
|
(0, db_1.closeDatabase)();
|
|
551
|
-
console.log(`Marked card "${
|
|
552
|
-
console.log(` Reason: ${
|
|
749
|
+
console.log(`Marked card "${target}" as needing verification.`);
|
|
750
|
+
console.log(` Reason: ${reason}`);
|
|
553
751
|
break;
|
|
554
752
|
}
|
|
555
753
|
case 'create_trace': {
|
|
@@ -568,35 +766,37 @@ type: trace
|
|
|
568
766
|
created: ${today}
|
|
569
767
|
---
|
|
570
768
|
|
|
571
|
-
# Trace: ${
|
|
769
|
+
# Trace: ${reason}
|
|
572
770
|
|
|
573
771
|
## What Changed
|
|
574
|
-
${
|
|
772
|
+
${reason}
|
|
575
773
|
|
|
576
774
|
## Next
|
|
577
775
|
Continue as planned.
|
|
578
776
|
`);
|
|
579
777
|
console.log(`Auto-created trace: traces/${today}-${traceNum}.md`);
|
|
580
|
-
console.log(` Reason: ${
|
|
581
|
-
//
|
|
778
|
+
console.log(` Reason: ${reason}`);
|
|
779
|
+
// Resolve the associated dirty flags
|
|
582
780
|
const db = (0, db_1.openDatabase)(pmemPath);
|
|
583
781
|
const activeSession = (0, db_1.getActiveSession)(db);
|
|
584
|
-
|
|
782
|
+
// Resolve all dirty flags matching this target+reason
|
|
783
|
+
(0, db_1.resolveDirtyFlags)(db, 'card', target);
|
|
784
|
+
(0, db_1.insertUpdateLog)(db, 'auto_trace', reason, activeSession?.id, [`trace.${today}-${traceNum}`], true);
|
|
585
785
|
(0, db_1.closeDatabase)();
|
|
586
786
|
break;
|
|
587
787
|
}
|
|
588
788
|
case 'update_state': {
|
|
589
|
-
console.log(`Action required: ${
|
|
789
|
+
console.log(`Action required: ${reason}`);
|
|
590
790
|
console.log(' Please run `pmem update --confirm` to update state.md.');
|
|
591
791
|
break;
|
|
592
792
|
}
|
|
593
793
|
case 'update_next': {
|
|
594
|
-
console.log(`Action required: ${
|
|
794
|
+
console.log(`Action required: ${reason}`);
|
|
595
795
|
console.log(' Please run `pmem update --confirm --next "<next step>"` to update next.md.');
|
|
596
796
|
break;
|
|
597
797
|
}
|
|
598
798
|
default: {
|
|
599
|
-
console.log(`Unknown action "${
|
|
799
|
+
console.log(`Unknown action "${action}" for suggestion ${suggestionId}.`);
|
|
600
800
|
process.exit(2);
|
|
601
801
|
}
|
|
602
802
|
}
|