pmem-ai 0.6.0 → 0.6.2
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 +49 -0
- package/README.md +11 -9
- package/dist/commands/distill.js +1 -1
- package/dist/commands/distill.js.map +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +15 -1
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/integration.d.ts.map +1 -1
- package/dist/commands/integration.js +57 -3
- package/dist/commands/integration.js.map +1 -1
- package/dist/commands/new.d.ts +2 -0
- package/dist/commands/new.d.ts.map +1 -0
- package/dist/commands/new.js +117 -0
- package/dist/commands/new.js.map +1 -0
- package/dist/commands/rebuild.d.ts.map +1 -1
- package/dist/commands/rebuild.js +27 -1
- package/dist/commands/rebuild.js.map +1 -1
- package/dist/commands/recall.d.ts +1 -1
- package/dist/commands/recall.d.ts.map +1 -1
- package/dist/commands/recall.js +34 -3
- package/dist/commands/recall.js.map +1 -1
- package/dist/commands/rename.d.ts +6 -0
- package/dist/commands/rename.d.ts.map +1 -0
- package/dist/commands/rename.js +177 -0
- package/dist/commands/rename.js.map +1 -0
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +3 -4
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/update.d.ts +1 -0
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +335 -133
- package/dist/commands/update.js.map +1 -1
- package/dist/commands/verify.d.ts +2 -0
- package/dist/commands/verify.d.ts.map +1 -1
- package/dist/commands/verify.js +61 -39
- 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/core/fs.d.ts +6 -0
- package/dist/core/fs.d.ts.map +1 -1
- package/dist/core/fs.js +32 -2
- package/dist/core/fs.js.map +1 -1
- package/dist/core/manifest.js +1 -1
- package/dist/core/manifest.js.map +1 -1
- package/dist/index.js +24 -3
- 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.4.md +2 -0
- package/docs/handover-v0.6.md +152 -0
- package/docs/project-roadmap.md +42 -5
- package/docs/usage.md +13 -14
- package/docs/v0.4 pre-design.md +2 -0
- package/docs/v0.5 pre-design.md +2 -0
- package/docs/v0.6 pre-design.md +2 -0
- package/docs/v0.6.1 pre-design.md +566 -0
- package/docs/v0.6.2 pre-design.md +657 -0
- package/package.json +3 -2
- package/skills/pmem/SKILL.md +12 -10
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
|
|
@@ -246,8 +247,12 @@ function autoUpdate(pmemPath, manifest) {
|
|
|
246
247
|
function confirmUpdate(pmemPath, summary, next) {
|
|
247
248
|
const lockPath = path.join(pmemPath, '.lock');
|
|
248
249
|
if (!(0, fs_1.acquireLock)(lockPath)) {
|
|
249
|
-
console.log('Failed to acquire pmem lock
|
|
250
|
-
console.log('
|
|
250
|
+
console.log('Failed to acquire pmem lock after 3s.');
|
|
251
|
+
console.log(' The lock at .pmem/.lock may be held by another pmem process, or a stale lock from a previous crash.');
|
|
252
|
+
console.log(' → Run: pmem verify --fix-locks (to check and clean stale locks)');
|
|
253
|
+
console.log(' → Or: pmem doctor (to diagnose lock status)');
|
|
254
|
+
console.log(' If no other pmem process is running, delete .pmem/.lock manually.');
|
|
255
|
+
console.log('No memory was written. Try again after resolving the lock.');
|
|
251
256
|
return;
|
|
252
257
|
}
|
|
253
258
|
try {
|
|
@@ -358,132 +363,299 @@ function listSourceFiles(root) {
|
|
|
358
363
|
walk(root);
|
|
359
364
|
return results;
|
|
360
365
|
}
|
|
361
|
-
|
|
366
|
+
/**
|
|
367
|
+
* Extract the matched file path from a dirty flag reason string.
|
|
368
|
+
* Handles formats like "file_changed: path/to/file" or plain text.
|
|
369
|
+
*/
|
|
370
|
+
function extractMatchedFile(reason) {
|
|
371
|
+
const match = reason.match(/^file_changed:\s*(.+)/);
|
|
372
|
+
if (match) {
|
|
373
|
+
return match[1].trim();
|
|
374
|
+
}
|
|
375
|
+
return null;
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Build the aggregation key for a dirty flag: target + reason + matched_file.
|
|
379
|
+
*/
|
|
380
|
+
function aggregationKey(flag) {
|
|
381
|
+
const mf = extractMatchedFile(flag.reason);
|
|
382
|
+
return `${flag.target}||${flag.reason}||${mf ?? ''}`;
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Find the most recent session end time.
|
|
386
|
+
* Returns null if no ended session exists.
|
|
387
|
+
*/
|
|
388
|
+
function getLatestSessionEnd(pmemPath) {
|
|
389
|
+
try {
|
|
390
|
+
const db = (0, db_1.openDatabase)(pmemPath);
|
|
391
|
+
const row = db.prepare("SELECT ended_at FROM sessions WHERE ended_at IS NOT NULL ORDER BY ended_at DESC LIMIT 1").get();
|
|
392
|
+
return row?.ended_at ?? null;
|
|
393
|
+
}
|
|
394
|
+
catch {
|
|
395
|
+
return null;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Get the active (un-ended) session if one exists.
|
|
400
|
+
*/
|
|
401
|
+
function getActiveSessionStart(pmemPath) {
|
|
402
|
+
try {
|
|
403
|
+
const db = (0, db_1.openDatabase)(pmemPath);
|
|
404
|
+
const row = db.prepare("SELECT started_at FROM sessions WHERE ended_at IS NULL ORDER BY started_at DESC LIMIT 1").get();
|
|
405
|
+
return row?.started_at ?? null;
|
|
406
|
+
}
|
|
407
|
+
catch {
|
|
408
|
+
return null;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
function generateSuggestions(pmemPath, includeHistory = false) {
|
|
362
412
|
const dbPath = path.join(pmemPath, 'pmem.db');
|
|
363
413
|
if (!(0, fs_1.fileExists)(dbPath)) {
|
|
364
|
-
|
|
365
|
-
|
|
414
|
+
return {
|
|
415
|
+
summary: { affected_cards: 0, blocking: 0, warning: 0, info: 0, duplicates_hidden: 0, historical_hidden: 0, verify_blocking: false },
|
|
416
|
+
message: 'No SQLite database. Run pmem rebuild first.',
|
|
417
|
+
next_steps: ['Run `pmem rebuild` to create the database index.'],
|
|
418
|
+
groups: { blocking_for_verify: [], current_suggestions: [], historical_dirty_flags: [] },
|
|
419
|
+
error: true,
|
|
420
|
+
};
|
|
366
421
|
}
|
|
367
422
|
let db;
|
|
368
423
|
try {
|
|
369
424
|
db = (0, db_1.openDatabase)(pmemPath);
|
|
370
425
|
}
|
|
371
426
|
catch {
|
|
372
|
-
|
|
373
|
-
|
|
427
|
+
return {
|
|
428
|
+
summary: { affected_cards: 0, blocking: 0, warning: 0, info: 0, duplicates_hidden: 0, historical_hidden: 0, verify_blocking: false },
|
|
429
|
+
message: 'Cannot open database. Run pmem rebuild first.',
|
|
430
|
+
next_steps: ['Run `pmem rebuild` to recreate the database.'],
|
|
431
|
+
groups: { blocking_for_verify: [], current_suggestions: [], historical_dirty_flags: [] },
|
|
432
|
+
error: true,
|
|
433
|
+
};
|
|
374
434
|
}
|
|
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
|
-
});
|
|
435
|
+
// 1. Get raw dirty flags with full details
|
|
436
|
+
const allFlags = (0, db_1.getUnresolvedDirtyFlagsDetailed)(db);
|
|
437
|
+
// 2. Run shared stale-memory consistency check
|
|
438
|
+
const staleIssues = (0, consistency_1.checkStaleMemory)(pmemPath);
|
|
439
|
+
// Build lookup: card_id → set of stale file paths
|
|
440
|
+
const staleByCard = new Map();
|
|
441
|
+
for (const issue of staleIssues) {
|
|
442
|
+
if (issue.card_id) {
|
|
443
|
+
if (!staleByCard.has(issue.card_id)) {
|
|
444
|
+
staleByCard.set(issue.card_id, new Set());
|
|
445
|
+
}
|
|
446
|
+
if (issue.file_path) {
|
|
447
|
+
staleByCard.get(issue.card_id).add(issue.file_path);
|
|
448
|
+
}
|
|
399
449
|
}
|
|
400
450
|
}
|
|
401
|
-
//
|
|
402
|
-
|
|
403
|
-
const
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
const hoursSinceUpdate = (Date.now() - stat.mtimeMs) / (1000 * 60 * 60);
|
|
408
|
-
if (hoursSinceUpdate > 24) {
|
|
409
|
-
stateFreshness = 'stale';
|
|
451
|
+
// 3. Aggregate dirty flags by target + reason + matched_file
|
|
452
|
+
const groups = new Map();
|
|
453
|
+
for (const flag of allFlags) {
|
|
454
|
+
const key = aggregationKey(flag);
|
|
455
|
+
if (!groups.has(key)) {
|
|
456
|
+
groups.set(key, []);
|
|
410
457
|
}
|
|
458
|
+
groups.get(key).push(flag);
|
|
411
459
|
}
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
460
|
+
// 4. Get session boundaries for historical classification
|
|
461
|
+
const latestSessionEnd = getLatestSessionEnd(pmemPath);
|
|
462
|
+
const activeSessionStart = getActiveSessionStart(pmemPath);
|
|
463
|
+
const sessionBoundary = latestSessionEnd || activeSessionStart;
|
|
464
|
+
// 5. Get total card count for affected_cards
|
|
465
|
+
const cardCount = getCardCount(pmemPath);
|
|
466
|
+
// 6. Classify each aggregated group
|
|
467
|
+
const blockingForVerify = [];
|
|
468
|
+
const currentSuggestions = [];
|
|
469
|
+
const historicalDirtyFlags = [];
|
|
470
|
+
for (const [key, flags] of groups) {
|
|
471
|
+
const representative = flags[0];
|
|
472
|
+
const matchedFile = extractMatchedFile(representative.reason);
|
|
473
|
+
// Determine if this group blocks verify
|
|
474
|
+
let blocksVerify = false;
|
|
475
|
+
if (representative.scope === 'card' && staleByCard.has(representative.target)) {
|
|
476
|
+
const staleFiles = staleByCard.get(representative.target);
|
|
477
|
+
if (matchedFile && staleFiles.has(matchedFile)) {
|
|
478
|
+
blocksVerify = true;
|
|
479
|
+
}
|
|
480
|
+
else if (!matchedFile) {
|
|
481
|
+
// Card is in stale list, even if we can't match the specific file
|
|
482
|
+
blocksVerify = true;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
// Determine severity
|
|
486
|
+
let severity;
|
|
487
|
+
if (blocksVerify) {
|
|
488
|
+
severity = 'blocking';
|
|
489
|
+
}
|
|
490
|
+
else if (representative.scope === 'card') {
|
|
491
|
+
severity = 'warning';
|
|
492
|
+
}
|
|
493
|
+
else {
|
|
494
|
+
severity = 'info';
|
|
495
|
+
}
|
|
496
|
+
// Historical classification
|
|
497
|
+
const allCreatedAts = flags.map(f => f.created_at).sort();
|
|
498
|
+
const latestCreated = allCreatedAts[allCreatedAts.length - 1];
|
|
499
|
+
const earliestCreated = allCreatedAts[0];
|
|
500
|
+
const isMulti = flags.length > 1;
|
|
501
|
+
let isHistorical = false;
|
|
502
|
+
let isDuplicate = false;
|
|
503
|
+
if (blocksVerify) {
|
|
504
|
+
// Blocking items are never historical
|
|
505
|
+
isHistorical = false;
|
|
506
|
+
isDuplicate = isMulti;
|
|
507
|
+
}
|
|
508
|
+
else if (sessionBoundary && latestCreated < sessionBoundary) {
|
|
509
|
+
// All flags are from before the session boundary → historical
|
|
510
|
+
isHistorical = true;
|
|
511
|
+
isDuplicate = isMulti;
|
|
512
|
+
}
|
|
513
|
+
else if (isMulti && sessionBoundary && latestCreated < sessionBoundary) {
|
|
514
|
+
// Multiple flags, all old → historical duplicate
|
|
515
|
+
isHistorical = true;
|
|
516
|
+
isDuplicate = true;
|
|
517
|
+
}
|
|
518
|
+
else {
|
|
519
|
+
// Default: keep in current
|
|
520
|
+
isHistorical = false;
|
|
521
|
+
isDuplicate = isMulti;
|
|
522
|
+
}
|
|
523
|
+
const aggregated = {
|
|
524
|
+
target: representative.target,
|
|
525
|
+
reason: representative.reason,
|
|
526
|
+
matched_file: matchedFile,
|
|
527
|
+
count: flags.length,
|
|
528
|
+
severity,
|
|
529
|
+
blocks_verify: blocksVerify,
|
|
530
|
+
is_duplicate: isDuplicate,
|
|
531
|
+
is_historical: isHistorical,
|
|
532
|
+
created_at_first: earliestCreated,
|
|
533
|
+
created_at_last: latestCreated,
|
|
534
|
+
sources: flags.map(f => ({
|
|
535
|
+
scope: f.scope,
|
|
536
|
+
target: f.target,
|
|
537
|
+
reason: f.reason,
|
|
538
|
+
created_at: f.created_at,
|
|
539
|
+
session_id: f.session_id,
|
|
540
|
+
})),
|
|
541
|
+
};
|
|
542
|
+
if (blocksVerify) {
|
|
543
|
+
blockingForVerify.push(aggregated);
|
|
544
|
+
}
|
|
545
|
+
else if (isHistorical) {
|
|
546
|
+
historicalDirtyFlags.push(aggregated);
|
|
547
|
+
}
|
|
548
|
+
else {
|
|
549
|
+
currentSuggestions.push(aggregated);
|
|
550
|
+
}
|
|
420
551
|
}
|
|
421
|
-
//
|
|
422
|
-
const
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
(
|
|
440
|
-
|
|
552
|
+
// 7. Compute summary
|
|
553
|
+
const uniqueAffectedCards = new Set();
|
|
554
|
+
for (const item of [...blockingForVerify, ...currentSuggestions]) {
|
|
555
|
+
uniqueAffectedCards.add(item.target);
|
|
556
|
+
}
|
|
557
|
+
const duplicatesHidden = [...blockingForVerify, ...currentSuggestions, ...historicalDirtyFlags]
|
|
558
|
+
.filter(g => g.count > 1)
|
|
559
|
+
.reduce((sum, g) => sum + (g.count - 1), 0);
|
|
560
|
+
const summary = {
|
|
561
|
+
affected_cards: uniqueAffectedCards.size,
|
|
562
|
+
blocking: blockingForVerify.length,
|
|
563
|
+
warning: currentSuggestions.filter(g => g.severity === 'warning').length,
|
|
564
|
+
info: currentSuggestions.filter(g => g.severity === 'info').length,
|
|
565
|
+
duplicates_hidden: duplicatesHidden,
|
|
566
|
+
historical_hidden: includeHistory ? 0 : historicalDirtyFlags.length,
|
|
567
|
+
verify_blocking: blockingForVerify.length > 0,
|
|
568
|
+
};
|
|
569
|
+
// 8. Build message and next steps
|
|
570
|
+
const message = buildSuggestMessage(summary, cardCount);
|
|
571
|
+
const nextSteps = buildSuggestNextSteps(summary, cardCount);
|
|
572
|
+
return {
|
|
573
|
+
summary,
|
|
574
|
+
message,
|
|
575
|
+
next_steps: nextSteps,
|
|
576
|
+
groups: {
|
|
577
|
+
blocking_for_verify: blockingForVerify,
|
|
578
|
+
current_suggestions: currentSuggestions,
|
|
579
|
+
historical_dirty_flags: includeHistory ? historicalDirtyFlags : [],
|
|
580
|
+
},
|
|
581
|
+
};
|
|
441
582
|
}
|
|
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);
|
|
583
|
+
function suggestActions(pmemPath, format, includeHistory) {
|
|
584
|
+
const report = generateSuggestions(pmemPath, includeHistory);
|
|
448
585
|
if (format === 'json') {
|
|
449
586
|
console.log(JSON.stringify({
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
next_steps: nextSteps,
|
|
587
|
+
summary: report.summary,
|
|
588
|
+
message: report.message,
|
|
589
|
+
next_steps: report.next_steps,
|
|
590
|
+
groups: report.groups,
|
|
455
591
|
}, null, 2));
|
|
456
592
|
}
|
|
457
593
|
else {
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
594
|
+
// Compact output
|
|
595
|
+
console.log('Memory update suggestions');
|
|
596
|
+
console.log('');
|
|
597
|
+
console.log(`Affected cards: ${report.summary.affected_cards}`);
|
|
598
|
+
console.log(`Blocking for verify: ${report.summary.blocking}`);
|
|
599
|
+
console.log(`Current suggestions: ${report.summary.warning + report.summary.info}`);
|
|
600
|
+
console.log(`Historical hidden: ${report.summary.historical_hidden}`);
|
|
601
|
+
console.log(`Duplicate flags hidden: ${report.summary.duplicates_hidden}`);
|
|
602
|
+
// Blocking section
|
|
603
|
+
if (report.groups.blocking_for_verify.length > 0) {
|
|
604
|
+
console.log('');
|
|
605
|
+
console.log('Blocking:');
|
|
606
|
+
for (const item of report.groups.blocking_for_verify) {
|
|
607
|
+
const filePart = (item.matched_file && !item.reason.includes(item.matched_file)) ? `, ${item.matched_file}` : '';
|
|
608
|
+
const countPart = item.count > 1 ? `, count ${item.count}` : '';
|
|
609
|
+
console.log(` - ${item.target} (${item.reason}${filePart}${countPart})`);
|
|
462
610
|
}
|
|
463
611
|
}
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
console.log(` ${
|
|
472
|
-
console.log(` ${s.reason}`);
|
|
612
|
+
// Current section
|
|
613
|
+
if (report.groups.current_suggestions.length > 0) {
|
|
614
|
+
console.log('');
|
|
615
|
+
console.log('Current:');
|
|
616
|
+
for (const item of report.groups.current_suggestions) {
|
|
617
|
+
const filePart = (item.matched_file && !item.reason.includes(item.matched_file)) ? `, ${item.matched_file}` : '';
|
|
618
|
+
const countPart = item.count > 1 ? `, count ${item.count}` : '';
|
|
619
|
+
console.log(` - ${item.target} (${item.reason}${filePart}${countPart})`);
|
|
473
620
|
}
|
|
474
621
|
}
|
|
622
|
+
// Historical section (only when --include-history)
|
|
623
|
+
if (includeHistory && report.groups.historical_dirty_flags.length > 0) {
|
|
624
|
+
console.log('');
|
|
625
|
+
console.log('Historical:');
|
|
626
|
+
for (const item of report.groups.historical_dirty_flags) {
|
|
627
|
+
const filePart = (item.matched_file && !item.reason.includes(item.matched_file)) ? `, ${item.matched_file}` : '';
|
|
628
|
+
const countPart = item.count > 1 ? `, count ${item.count}` : '';
|
|
629
|
+
console.log(` - ${item.target} (${item.reason}${filePart}${countPart})`);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
// Message
|
|
633
|
+
console.log('');
|
|
634
|
+
if (report.summary.blocking > 0) {
|
|
635
|
+
console.log(report.message);
|
|
636
|
+
}
|
|
475
637
|
else {
|
|
476
|
-
console.log(
|
|
477
|
-
if (
|
|
478
|
-
console.log('
|
|
479
|
-
for (const step of nextSteps) {
|
|
480
|
-
console.log(` ${step}`);
|
|
481
|
-
}
|
|
638
|
+
console.log('No blocking memory consistency issues.');
|
|
639
|
+
if (report.summary.historical_hidden > 0) {
|
|
640
|
+
console.log('Historical suggestions available with --include-history.');
|
|
482
641
|
}
|
|
483
642
|
}
|
|
643
|
+
// Next steps
|
|
644
|
+
if (report.next_steps.length > 0) {
|
|
645
|
+
console.log('');
|
|
646
|
+
console.log('Next:');
|
|
647
|
+
for (const step of report.next_steps) {
|
|
648
|
+
console.log(` - ${step}`);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
// Exit code: 2 for runtime errors (missing DB, etc.)
|
|
653
|
+
if (report.error) {
|
|
654
|
+
process.exit(2);
|
|
484
655
|
}
|
|
485
|
-
|
|
486
|
-
|
|
656
|
+
// v0.6.2: Exit 0 regardless of whether suggestions were found.
|
|
657
|
+
// Exit 1 is no longer used as "actionable suggestions exist" workflow signal.
|
|
658
|
+
// Agents should check JSON output summary fields instead of exit code.
|
|
487
659
|
}
|
|
488
660
|
function getCardCount(pmemPath) {
|
|
489
661
|
try {
|
|
@@ -495,45 +667,70 @@ function getCardCount(pmemPath) {
|
|
|
495
667
|
return 0;
|
|
496
668
|
}
|
|
497
669
|
}
|
|
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
|
-
}
|
|
670
|
+
function buildSuggestMessage(summary, cardCount) {
|
|
505
671
|
if (cardCount === 0) {
|
|
506
672
|
return 'No memory cards found. Create a first module, decision, or task card to start building project memory.';
|
|
507
673
|
}
|
|
674
|
+
if (summary.blocking > 0 || summary.warning > 0 || summary.info > 0) {
|
|
675
|
+
const parts = [];
|
|
676
|
+
if (summary.blocking > 0)
|
|
677
|
+
parts.push(`${summary.blocking} blocking memory consistency issue(s)`);
|
|
678
|
+
if (summary.warning > 0)
|
|
679
|
+
parts.push(`${summary.warning} current suggestion(s)`);
|
|
680
|
+
if (summary.info > 0)
|
|
681
|
+
parts.push(`${summary.info} informational item(s)`);
|
|
682
|
+
return parts.join(' and ') + '.';
|
|
683
|
+
}
|
|
508
684
|
return 'No suggestions. Memory is up to date.';
|
|
509
685
|
}
|
|
510
|
-
function buildSuggestNextSteps(
|
|
511
|
-
if (suggestions.length > 0)
|
|
512
|
-
return [];
|
|
686
|
+
function buildSuggestNextSteps(summary, cardCount) {
|
|
513
687
|
const steps = [];
|
|
514
688
|
if (cardCount === 0) {
|
|
515
689
|
steps.push('Create a module card with source_files pointing to your code');
|
|
516
690
|
steps.push('Run `pmem rebuild` after creating cards');
|
|
517
691
|
steps.push('Then try `pmem status` and `pmem mark-dirty --auto`');
|
|
518
692
|
}
|
|
519
|
-
else if (
|
|
520
|
-
steps.push('
|
|
521
|
-
steps.push('
|
|
693
|
+
else if (summary.blocking > 0 || summary.warning > 0) {
|
|
694
|
+
steps.push('Update or confirm affected cards with pmem update --confirm -s "<summary>" -n "<next step>"');
|
|
695
|
+
steps.push('Use --include-history to inspect older dirty flags.');
|
|
696
|
+
}
|
|
697
|
+
else if (summary.historical_hidden > 0) {
|
|
698
|
+
steps.push('Use --include-history to inspect older dirty flags.');
|
|
699
|
+
steps.push('Run `pmem verify` to check overall memory consistency.');
|
|
522
700
|
}
|
|
523
701
|
else {
|
|
524
|
-
steps.push('
|
|
525
|
-
steps.push('
|
|
702
|
+
steps.push('Edit some source files, then run `pmem status` and `pmem mark-dirty --auto`');
|
|
703
|
+
steps.push('Run `pmem verify` to check overall memory consistency');
|
|
526
704
|
}
|
|
527
705
|
return steps;
|
|
528
706
|
}
|
|
529
707
|
function applySuggestionAction(pmemPath, suggestionId) {
|
|
530
|
-
// Re-derive suggestions to find the matching one
|
|
531
|
-
const
|
|
532
|
-
|
|
708
|
+
// Re-derive suggestions to find the matching one (with history included for full search)
|
|
709
|
+
const report = generateSuggestions(pmemPath, true);
|
|
710
|
+
// Flatten all groups into a single searchable list with generated IDs
|
|
711
|
+
const flatList = [];
|
|
712
|
+
let idx = 1;
|
|
713
|
+
for (const item of report.groups.blocking_for_verify) {
|
|
714
|
+
const action = item.reason.startsWith('file_changed') ? 'update_card' : 'create_trace';
|
|
715
|
+
flatList.push({ id: `suggest-${idx}`, item, action });
|
|
716
|
+
idx++;
|
|
717
|
+
}
|
|
718
|
+
for (const item of report.groups.current_suggestions) {
|
|
719
|
+
const action = item.reason.startsWith('file_changed') ? 'update_card' : 'create_trace';
|
|
720
|
+
flatList.push({ id: `suggest-${idx}`, item, action });
|
|
721
|
+
idx++;
|
|
722
|
+
}
|
|
723
|
+
for (const item of report.groups.historical_dirty_flags) {
|
|
724
|
+
const action = item.reason.startsWith('file_changed') ? 'update_card' : 'create_trace';
|
|
725
|
+
flatList.push({ id: `suggest-${idx}`, item, action });
|
|
726
|
+
idx++;
|
|
727
|
+
}
|
|
728
|
+
const match = flatList.find(s => s.id === suggestionId);
|
|
533
729
|
if (!match) {
|
|
534
730
|
console.log(`Suggestion "${suggestionId}" not found. Available suggestions:`);
|
|
535
|
-
for (const s of
|
|
536
|
-
|
|
731
|
+
for (const s of flatList) {
|
|
732
|
+
const filePart = s.item.matched_file ? `, ${s.item.matched_file}` : '';
|
|
733
|
+
console.log(` ${s.id}: ${s.action} ${s.item.target} (${s.item.reason}${filePart})`);
|
|
537
734
|
}
|
|
538
735
|
process.exit(2);
|
|
539
736
|
}
|
|
@@ -542,14 +739,17 @@ function applySuggestionAction(pmemPath, suggestionId) {
|
|
|
542
739
|
console.log('No SQLite database. Run pmem rebuild first.');
|
|
543
740
|
process.exit(2);
|
|
544
741
|
}
|
|
545
|
-
|
|
742
|
+
const action = match.action;
|
|
743
|
+
const target = match.item.target;
|
|
744
|
+
const reason = match.item.reason;
|
|
745
|
+
switch (action) {
|
|
546
746
|
case 'update_card': {
|
|
547
747
|
const db = (0, db_1.openDatabase)(pmemPath);
|
|
548
748
|
// 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(),
|
|
749
|
+
db.prepare("UPDATE cards SET last_verified_at = ? WHERE id = ?").run(new Date(0).toISOString(), target);
|
|
550
750
|
(0, db_1.closeDatabase)();
|
|
551
|
-
console.log(`Marked card "${
|
|
552
|
-
console.log(` Reason: ${
|
|
751
|
+
console.log(`Marked card "${target}" as needing verification.`);
|
|
752
|
+
console.log(` Reason: ${reason}`);
|
|
553
753
|
break;
|
|
554
754
|
}
|
|
555
755
|
case 'create_trace': {
|
|
@@ -568,35 +768,37 @@ type: trace
|
|
|
568
768
|
created: ${today}
|
|
569
769
|
---
|
|
570
770
|
|
|
571
|
-
# Trace: ${
|
|
771
|
+
# Trace: ${reason}
|
|
572
772
|
|
|
573
773
|
## What Changed
|
|
574
|
-
${
|
|
774
|
+
${reason}
|
|
575
775
|
|
|
576
776
|
## Next
|
|
577
777
|
Continue as planned.
|
|
578
778
|
`);
|
|
579
779
|
console.log(`Auto-created trace: traces/${today}-${traceNum}.md`);
|
|
580
|
-
console.log(` Reason: ${
|
|
581
|
-
//
|
|
780
|
+
console.log(` Reason: ${reason}`);
|
|
781
|
+
// Resolve the associated dirty flags
|
|
582
782
|
const db = (0, db_1.openDatabase)(pmemPath);
|
|
583
783
|
const activeSession = (0, db_1.getActiveSession)(db);
|
|
584
|
-
|
|
784
|
+
// Resolve all dirty flags matching this target+reason
|
|
785
|
+
(0, db_1.resolveDirtyFlags)(db, 'card', target);
|
|
786
|
+
(0, db_1.insertUpdateLog)(db, 'auto_trace', reason, activeSession?.id, [`trace.${today}-${traceNum}`], true);
|
|
585
787
|
(0, db_1.closeDatabase)();
|
|
586
788
|
break;
|
|
587
789
|
}
|
|
588
790
|
case 'update_state': {
|
|
589
|
-
console.log(`Action required: ${
|
|
791
|
+
console.log(`Action required: ${reason}`);
|
|
590
792
|
console.log(' Please run `pmem update --confirm` to update state.md.');
|
|
591
793
|
break;
|
|
592
794
|
}
|
|
593
795
|
case 'update_next': {
|
|
594
|
-
console.log(`Action required: ${
|
|
796
|
+
console.log(`Action required: ${reason}`);
|
|
595
797
|
console.log(' Please run `pmem update --confirm --next "<next step>"` to update next.md.');
|
|
596
798
|
break;
|
|
597
799
|
}
|
|
598
800
|
default: {
|
|
599
|
-
console.log(`Unknown action "${
|
|
801
|
+
console.log(`Unknown action "${action}" for suggestion ${suggestionId}.`);
|
|
600
802
|
process.exit(2);
|
|
601
803
|
}
|
|
602
804
|
}
|