pmem-ai 0.6.2 → 0.7.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.
Files changed (85) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/README.md +68 -4
  3. package/dist/commands/ask.d.ts.map +1 -1
  4. package/dist/commands/ask.js +5 -1
  5. package/dist/commands/ask.js.map +1 -1
  6. package/dist/commands/distill.d.ts.map +1 -1
  7. package/dist/commands/distill.js +15 -6
  8. package/dist/commands/distill.js.map +1 -1
  9. package/dist/commands/doctor.d.ts.map +1 -1
  10. package/dist/commands/doctor.js +21 -8
  11. package/dist/commands/doctor.js.map +1 -1
  12. package/dist/commands/graph.d.ts +3 -0
  13. package/dist/commands/graph.d.ts.map +1 -1
  14. package/dist/commands/graph.js +73 -25
  15. package/dist/commands/graph.js.map +1 -1
  16. package/dist/commands/init.d.ts +13 -0
  17. package/dist/commands/init.d.ts.map +1 -1
  18. package/dist/commands/init.js +142 -32
  19. package/dist/commands/init.js.map +1 -1
  20. package/dist/commands/integration.d.ts.map +1 -1
  21. package/dist/commands/integration.js +170 -15
  22. package/dist/commands/integration.js.map +1 -1
  23. package/dist/commands/new.d.ts.map +1 -1
  24. package/dist/commands/new.js +15 -14
  25. package/dist/commands/new.js.map +1 -1
  26. package/dist/commands/rebuild.d.ts.map +1 -1
  27. package/dist/commands/rebuild.js +84 -15
  28. package/dist/commands/rebuild.js.map +1 -1
  29. package/dist/commands/recall.d.ts.map +1 -1
  30. package/dist/commands/recall.js +11 -5
  31. package/dist/commands/recall.js.map +1 -1
  32. package/dist/commands/rename.d.ts.map +1 -1
  33. package/dist/commands/rename.js +98 -19
  34. package/dist/commands/rename.js.map +1 -1
  35. package/dist/commands/status.d.ts.map +1 -1
  36. package/dist/commands/status.js +31 -6
  37. package/dist/commands/status.js.map +1 -1
  38. package/dist/commands/update.d.ts +2 -0
  39. package/dist/commands/update.d.ts.map +1 -1
  40. package/dist/commands/update.js +117 -1
  41. package/dist/commands/update.js.map +1 -1
  42. package/dist/commands/verify.d.ts.map +1 -1
  43. package/dist/commands/verify.js +5 -3
  44. package/dist/commands/verify.js.map +1 -1
  45. package/dist/core/db.d.ts +8 -0
  46. package/dist/core/db.d.ts.map +1 -1
  47. package/dist/core/db.js +47 -0
  48. package/dist/core/db.js.map +1 -1
  49. package/dist/core/discover/detect.d.ts +12 -0
  50. package/dist/core/discover/detect.d.ts.map +1 -0
  51. package/dist/core/discover/detect.js +68 -0
  52. package/dist/core/discover/detect.js.map +1 -0
  53. package/dist/core/discover/index.d.ts +12 -0
  54. package/dist/core/discover/index.d.ts.map +1 -0
  55. package/dist/core/discover/index.js +645 -0
  56. package/dist/core/discover/index.js.map +1 -0
  57. package/dist/core/discover/patterns.d.ts +29 -0
  58. package/dist/core/discover/patterns.d.ts.map +1 -0
  59. package/dist/core/discover/patterns.js +322 -0
  60. package/dist/core/discover/patterns.js.map +1 -0
  61. package/dist/core/fs.d.ts +7 -0
  62. package/dist/core/fs.d.ts.map +1 -1
  63. package/dist/core/fs.js +57 -1
  64. package/dist/core/fs.js.map +1 -1
  65. package/dist/core/manifest.d.ts +23 -1
  66. package/dist/core/manifest.d.ts.map +1 -1
  67. package/dist/core/manifest.js +72 -0
  68. package/dist/core/manifest.js.map +1 -1
  69. package/dist/index.js +31 -2
  70. package/dist/index.js.map +1 -1
  71. package/dist/types.d.ts +108 -1
  72. package/dist/types.d.ts.map +1 -1
  73. package/docs/handover-v0.6.4.md +285 -0
  74. package/docs/project-roadmap.md +129 -4
  75. package/docs/release-checklist-v0.7.0.md +60 -0
  76. package/docs/session-start-create-design-eval.md +203 -0
  77. package/docs/usage.md +38 -0
  78. package/docs/v0.6.4 pre-design.md +526 -0
  79. package/docs/v0.7.0 pre-design.md +405 -0
  80. package/package.json +7 -3
  81. package/skills/pmem/SKILL.md +86 -6
  82. package/skills/pmem/references/first-init.md +21 -0
  83. package/skills/pmem/references/memory-cards.md +44 -1
  84. package/skills/pmem/references/session-workflow.md +3 -3
  85. package/skills/pmem/references/universal-domains.md +66 -0
@@ -0,0 +1,645 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.discoverCommand = discoverCommand;
37
+ const path = __importStar(require("path"));
38
+ const fs_1 = require("../fs");
39
+ const manifest_1 = require("../manifest");
40
+ const db_1 = require("../db");
41
+ const patterns_1 = require("./patterns");
42
+ const detect_1 = require("./detect");
43
+ const PMEM_DIR = '.pmem';
44
+ /**
45
+ * Main discover command: detect languages, scan files, resolve to cards, produce edges.
46
+ */
47
+ function discoverCommand(options) {
48
+ const cwd = process.cwd();
49
+ const pmemPath = path.join(cwd, PMEM_DIR);
50
+ const minConfidence = options.minConfidence ?? 0.5;
51
+ if (!(0, fs_1.fileExists)(pmemPath)) {
52
+ console.log('No .pmem directory found. Run `pmem init` first.');
53
+ process.exit(2);
54
+ }
55
+ const manifest = (0, manifest_1.loadManifest)(pmemPath);
56
+ const isDiscoverEnabled = manifest && manifest.discover?.enabled === false ? false : true;
57
+ if (!isDiscoverEnabled) {
58
+ if (options.format === 'json') {
59
+ console.log(JSON.stringify({
60
+ enabled: false,
61
+ reason: 'discover disabled in this project'
62
+ }, null, 2));
63
+ }
64
+ else {
65
+ console.log('discover disabled in this project');
66
+ }
67
+ return;
68
+ }
69
+ const dbPath = path.join(pmemPath, 'pmem.db');
70
+ if (!(0, fs_1.fileExists)(dbPath)) {
71
+ console.log('No SQLite database found. Run `pmem rebuild` first.');
72
+ process.exit(2);
73
+ }
74
+ // 1. Load patterns
75
+ let extraPatterns;
76
+ if (options.patternFile) {
77
+ try {
78
+ const raw = (0, fs_1.readFile)(path.resolve(cwd, options.patternFile));
79
+ if (raw) {
80
+ const parsed = JSON.parse(raw);
81
+ extraPatterns = parsed.languages || parsed;
82
+ }
83
+ }
84
+ catch {
85
+ console.error(`Failed to parse pattern file: ${options.patternFile}`);
86
+ process.exit(2);
87
+ }
88
+ }
89
+ const allPatterns = (0, patterns_1.loadPatternRegistry)(extraPatterns);
90
+ // 2. Detect or filter languages
91
+ let activePatterns;
92
+ if (options.lang && options.lang !== 'auto') {
93
+ const langs = options.lang.split(',').map(l => l.trim().toLowerCase());
94
+ activePatterns = (0, detect_1.filterPatterns)(allPatterns, langs);
95
+ }
96
+ else {
97
+ const detected = (0, detect_1.detectLanguages)(cwd, allPatterns);
98
+ activePatterns = allPatterns.filter(p => detected.includes(p.language));
99
+ }
100
+ if (activePatterns.length === 0) {
101
+ const msg = options.lang
102
+ ? `No matching language patterns found for: ${options.lang}`
103
+ : 'No supported languages detected in this project.';
104
+ if (options.format === 'json') {
105
+ console.log(JSON.stringify({
106
+ project_languages: [],
107
+ discovered_edges: [],
108
+ ambiguous: [],
109
+ summary: { total_discovered: 0, high_confidence: 0, low_confidence: 0, unmatched_refs: 0 },
110
+ }, null, 2));
111
+ }
112
+ else {
113
+ console.log(msg);
114
+ }
115
+ return;
116
+ }
117
+ const db = (0, db_1.openDatabase)(pmemPath);
118
+ (0, db_1.createSchema)(db);
119
+ // 3. Get all registered source files from the paths table
120
+ const sourceFileRows = db.prepare("SELECT DISTINCT path, card_id FROM paths WHERE relation = 'source_file'").all();
121
+ // Build: absolutePath → cardId map; also track scanned files to avoid duplicates
122
+ const fileCardMap = new Map();
123
+ const seenFiles = new Set();
124
+ for (const row of sourceFileRows) {
125
+ const absPath = path.resolve(cwd, row.path);
126
+ if (!seenFiles.has(absPath)) {
127
+ seenFiles.add(absPath);
128
+ fileCardMap.set(absPath, row.card_id);
129
+ }
130
+ // Also index relative path for lookup only
131
+ if (!fileCardMap.has(row.path)) {
132
+ fileCardMap.set(row.path, row.card_id);
133
+ }
134
+ }
135
+ // Also detect languages from source file extensions (not just indicator files)
136
+ if (!options.lang || options.lang === 'auto') {
137
+ const extLangMap = {
138
+ '.ts': 'nodejs', '.tsx': 'nodejs', '.js': 'nodejs', '.jsx': 'nodejs',
139
+ '.py': 'python', '.rs': 'rust', '.go': 'go',
140
+ '.c': 'cpp', '.cpp': 'cpp', '.h': 'cpp', '.hpp': 'cpp',
141
+ '.java': 'java', '.kt': 'java',
142
+ };
143
+ const extDetected = new Set(activePatterns.map(p => p.language));
144
+ for (const absPath of seenFiles) {
145
+ const ext = path.extname(absPath).toLowerCase();
146
+ const lang = extLangMap[ext];
147
+ if (lang)
148
+ extDetected.add(lang);
149
+ }
150
+ // Filter active patterns to include extension-detected languages
151
+ activePatterns = allPatterns.filter(p => extDetected.has(p.language));
152
+ }
153
+ // 4. Scan for references
154
+ const allRefs = [];
155
+ for (const langPattern of activePatterns) {
156
+ // 4a. Scan source files registered in DB (only those matching language extensions)
157
+ const extSet = new Set(langPattern.extensions);
158
+ const scannedFiles = new Set();
159
+ for (const [filePath, cardId] of fileCardMap) {
160
+ const absPath = path.isAbsolute(filePath) ? filePath : path.resolve(cwd, filePath);
161
+ const ext = path.extname(absPath).toLowerCase();
162
+ if (!extSet.has(ext))
163
+ continue;
164
+ if (!(0, fs_1.fileExists)(absPath))
165
+ continue;
166
+ if (scannedFiles.has(absPath))
167
+ continue;
168
+ scannedFiles.add(absPath);
169
+ const content = (0, fs_1.readFile)(absPath);
170
+ if (!content)
171
+ continue;
172
+ for (const pattern of langPattern.source_patterns) {
173
+ if (pattern.confidence < minConfidence)
174
+ continue;
175
+ try {
176
+ const re = new RegExp(pattern.regex, 'gm');
177
+ let match;
178
+ while ((match = re.exec(content)) !== null) {
179
+ const target = match[1] || match[2] || match[3];
180
+ if (!target)
181
+ continue;
182
+ const cleanTarget = target.trim();
183
+ // Skip language builtins / stdlib (e.g. `fs`, `path`, `os`) entirely.
184
+ // They are never project-internal references and would only create noise.
185
+ if ((0, patterns_1.isBuiltinModule)(cleanTarget, langPattern.language))
186
+ continue;
187
+ if (pattern.scope !== 'external' && (cleanTarget.startsWith('.') || cleanTarget.startsWith('/'))) {
188
+ // Local import: try to resolve as file path
189
+ const resolvedFile = resolveImportPath(absPath, cleanTarget, langPattern.extensions);
190
+ allRefs.push({
191
+ from_file: absPath,
192
+ from_card_id: cardId,
193
+ target_name: resolvedFile || cleanTarget,
194
+ language: langPattern.language,
195
+ strategy: 'source_import',
196
+ confidence: pattern.confidence,
197
+ matched_pattern: match[0].trim().substring(0, 80),
198
+ target_kind: 'local_file',
199
+ });
200
+ }
201
+ else if (pattern.scope !== 'local') {
202
+ allRefs.push({
203
+ from_file: absPath,
204
+ from_card_id: cardId,
205
+ target_name: cleanTarget,
206
+ language: langPattern.language,
207
+ strategy: 'source_import',
208
+ confidence: pattern.confidence,
209
+ matched_pattern: match[0].trim().substring(0, 80),
210
+ target_kind: 'external_bare',
211
+ });
212
+ }
213
+ }
214
+ }
215
+ catch {
216
+ // skip invalid regex
217
+ }
218
+ }
219
+ }
220
+ // 4b. Scan dependency files
221
+ for (const depFile of langPattern.dep_files) {
222
+ if (depFile.confidence < minConfidence)
223
+ continue;
224
+ const depPath = path.join(cwd, depFile.filename);
225
+ if (!(0, fs_1.fileExists)(depPath))
226
+ continue;
227
+ const content = (0, fs_1.readFile)(depPath);
228
+ if (!content)
229
+ continue;
230
+ const deps = extractDeps(content, depFile);
231
+ // Find which card owns this dep file
232
+ const ownerCardId = fileCardMap.get(depPath);
233
+ for (const dep of deps) {
234
+ // Skip stdlib / well-known framework packages (e.g. @types/*).
235
+ // Bare dep names won't match project cards, and treating them as
236
+ // "unmatched" would only pollute the ambiguous list.
237
+ if ((0, patterns_1.isBuiltinModule)(dep, langPattern.language))
238
+ continue;
239
+ allRefs.push({
240
+ from_file: depPath,
241
+ from_card_id: ownerCardId,
242
+ target_name: dep,
243
+ language: langPattern.language,
244
+ strategy: 'dependency_file',
245
+ confidence: depFile.confidence,
246
+ matched_pattern: `${depFile.filename}: ${dep}`,
247
+ target_kind: 'external_bare',
248
+ });
249
+ }
250
+ }
251
+ }
252
+ // 5. Resolve references to card IDs
253
+ const { edges, ambiguous } = resolveToCards(allRefs, fileCardMap, db, minConfidence);
254
+ // Deduplicate ambiguous entries
255
+ const dedupedAmbiguous = dedupAmbiguous(ambiguous);
256
+ // 6. Output
257
+ const highConf = edges.filter(e => e.confidence >= 0.7).length;
258
+ const lowConf = edges.filter(e => e.confidence < 0.7).length;
259
+ const externalRefs = dedupedAmbiguous.filter(a => a.kind === 'external_unmatched').length;
260
+ const actionable = dedupedAmbiguous.filter(a => a.severity === 'actionable').length;
261
+ const result = {
262
+ project_languages: activePatterns.map(p => p.language),
263
+ discovered_edges: edges,
264
+ ambiguous: dedupedAmbiguous,
265
+ summary: {
266
+ total_discovered: edges.length,
267
+ high_confidence: highConf,
268
+ low_confidence: lowConf,
269
+ unmatched_refs: dedupedAmbiguous.filter(a => a.kind === 'unmatched_target').length,
270
+ external_refs: externalRefs,
271
+ actionable,
272
+ },
273
+ };
274
+ if (options.format === 'json') {
275
+ console.log(JSON.stringify(result, null, 2));
276
+ }
277
+ else {
278
+ printCompact(result);
279
+ }
280
+ // 7. Write to DB if not dry-run
281
+ if (!options.dryRun) {
282
+ const now = new Date().toISOString();
283
+ const activeSession = (0, db_1.getActiveSession)(db);
284
+ // Remove old inferred edges
285
+ (0, db_1.deleteInferredEdges)(db);
286
+ // Insert new edges
287
+ for (const de of edges) {
288
+ (0, db_1.insertEdge)(db, {
289
+ from_id: de.from_id,
290
+ to_id: de.to_id,
291
+ type: de.type,
292
+ source: 'inferred',
293
+ confidence: de.confidence,
294
+ created_at: now,
295
+ updated_at: now,
296
+ });
297
+ }
298
+ // Mark cards with new inferred edges as dirty
299
+ const dirtyCardIds = new Set(edges.map(e => e.from_id));
300
+ for (const cardId of dirtyCardIds) {
301
+ (0, db_1.insertDirtyFlag)(db, 'card', cardId, 'discover:inferred_edges_added', activeSession?.id);
302
+ }
303
+ (0, db_1.closeDatabase)();
304
+ }
305
+ }
306
+ /**
307
+ * Resolve collected references to card IDs.
308
+ */
309
+ function resolveToCards(refs, fileCardMap, db, minConfidence) {
310
+ const edges = [];
311
+ const ambiguous = [];
312
+ const seenEdges = new Set();
313
+ const now = new Date().toISOString();
314
+ for (const ref of refs) {
315
+ // Try to find target card
316
+ const targetIds = findTargetCards(ref.target_name, fileCardMap, db);
317
+ if (targetIds.length === 0) {
318
+ // Classify: a 'local_file' that didn't resolve is an internal project
319
+ // file with no card — actionable. An 'external_bare' is an external
320
+ // package or full path dep — informational, no action needed.
321
+ const isExternal = ref.target_kind === 'external_bare';
322
+ ambiguous.push({
323
+ kind: isExternal ? 'external_unmatched' : 'unmatched_target',
324
+ severity: isExternal ? 'informational' : 'actionable',
325
+ from_file: ref.from_file,
326
+ from_card_id: ref.from_card_id,
327
+ reference: ref.target_name,
328
+ language: ref.language,
329
+ confidence: ref.confidence,
330
+ });
331
+ continue;
332
+ }
333
+ if (targetIds.length > 1) {
334
+ ambiguous.push({
335
+ kind: 'multiple_targets',
336
+ from_file: ref.from_file,
337
+ from_card_id: ref.from_card_id,
338
+ reference: ref.target_name,
339
+ suggested_targets: targetIds,
340
+ language: ref.language,
341
+ confidence: ref.confidence,
342
+ });
343
+ }
344
+ // Use the best match (first)
345
+ const toId = targetIds[0];
346
+ const fromId = ref.from_card_id;
347
+ if (!fromId)
348
+ continue;
349
+ if (fromId === toId)
350
+ continue; // self-reference
351
+ const edgeKey = `${fromId}→${toId}:${ref.strategy}`;
352
+ if (seenEdges.has(edgeKey))
353
+ continue;
354
+ seenEdges.add(edgeKey);
355
+ if (ref.confidence < minConfidence) {
356
+ ambiguous.push({
357
+ kind: 'low_confidence',
358
+ from_file: ref.from_file,
359
+ from_card_id: fromId,
360
+ reference: `${fromId} → ${toId}`,
361
+ language: ref.language,
362
+ confidence: ref.confidence,
363
+ });
364
+ continue;
365
+ }
366
+ edges.push({
367
+ from_id: fromId,
368
+ to_id: toId,
369
+ type: ref.strategy === 'dependency_file' ? 'depends_on' : 'depends_on',
370
+ source: 'inferred',
371
+ confidence: ref.confidence,
372
+ evidence: {
373
+ language: ref.language,
374
+ strategy: ref.strategy,
375
+ matched_file: ref.from_file,
376
+ matched_pattern: ref.matched_pattern,
377
+ },
378
+ });
379
+ }
380
+ return { edges, ambiguous };
381
+ }
382
+ /**
383
+ * Find card IDs that match a reference target.
384
+ * Tries: exact path match, filename match, alias match, tag match.
385
+ */
386
+ function findTargetCards(target, fileCardMap, db) {
387
+ const results = [];
388
+ // 1. Direct file path match
389
+ if (fileCardMap.has(target)) {
390
+ const cid = fileCardMap.get(target);
391
+ if (!results.includes(cid))
392
+ results.push(cid);
393
+ }
394
+ // 2. Filename-only match — only for file paths (contain / or \) or relative refs,
395
+ // NOT for bare package names like 'fs', 'path', 'express'
396
+ const looksLikePath = target.includes('/') || target.includes('\\') || target.startsWith('.');
397
+ if (looksLikePath) {
398
+ const targetBasename = path.basename(target).replace(/\.[^.]+$/, '');
399
+ for (const [fpath, cid] of fileCardMap) {
400
+ if (results.includes(cid))
401
+ continue;
402
+ const basename = path.basename(fpath).replace(/\.[^.]+$/, '');
403
+ if (basename === targetBasename) {
404
+ results.push(cid);
405
+ }
406
+ }
407
+ }
408
+ // 3. Card alias match
409
+ try {
410
+ const aliasRows = db.prepare('SELECT card_id FROM aliases WHERE normalized_alias = ?').all(target.toLowerCase().trim());
411
+ for (const row of aliasRows) {
412
+ if (!results.includes(row.card_id)) {
413
+ results.push(row.card_id);
414
+ }
415
+ }
416
+ }
417
+ catch {
418
+ // ignore
419
+ }
420
+ // 4. Card ID substring match
421
+ try {
422
+ const normalizedTarget = target.toLowerCase().replace(/[^a-z0-9]/g, '-');
423
+ const cardRows = db.prepare('SELECT id FROM cards WHERE id = ? AND is_deleted = 0').all(normalizedTarget);
424
+ for (const row of cardRows) {
425
+ if (!results.includes(row.id)) {
426
+ results.push(row.id);
427
+ }
428
+ }
429
+ }
430
+ catch {
431
+ // ignore
432
+ }
433
+ return results;
434
+ }
435
+ /**
436
+ * Resolve a local import path (./xxx or ../xxx) to an absolute file path.
437
+ */
438
+ function resolveImportPath(fromFile, importPath, extensions) {
439
+ const dir = path.dirname(fromFile);
440
+ const resolved = path.resolve(dir, importPath);
441
+ // Try exact match first
442
+ if ((0, fs_1.fileExists)(resolved))
443
+ return resolved;
444
+ // Try with extensions
445
+ for (const ext of extensions) {
446
+ const withExt = resolved + ext;
447
+ if ((0, fs_1.fileExists)(withExt))
448
+ return withExt;
449
+ }
450
+ // Try /index.ext
451
+ for (const ext of extensions) {
452
+ const indexFile = path.join(resolved, 'index' + ext);
453
+ if ((0, fs_1.fileExists)(indexFile))
454
+ return indexFile;
455
+ }
456
+ return null;
457
+ }
458
+ /**
459
+ * Extract dependencies from a dependency file based on parser type.
460
+ */
461
+ function extractDeps(content, depFilePattern) {
462
+ const deps = [];
463
+ switch (depFilePattern.parser) {
464
+ case 'json': {
465
+ try {
466
+ const obj = JSON.parse(content);
467
+ const keys = depFilePattern.extractDeps.split(',');
468
+ for (const key of keys) {
469
+ const section = obj[key.trim()];
470
+ if (section && typeof section === 'object') {
471
+ deps.push(...Object.keys(section));
472
+ }
473
+ }
474
+ }
475
+ catch {
476
+ // invalid JSON
477
+ }
478
+ break;
479
+ }
480
+ case 'text': {
481
+ const extractKey = depFilePattern.extractDeps;
482
+ if (extractKey === 'line') {
483
+ // requirements.txt style: each line is "pkg==version" or "pkg>=version"
484
+ for (const line of content.split('\n')) {
485
+ const trimmed = line.trim();
486
+ if (!trimmed || trimmed.startsWith('#') || trimmed.startsWith('-'))
487
+ continue;
488
+ const pkgName = trimmed.split(/[=<>~!]/)[0].trim();
489
+ if (pkgName)
490
+ deps.push(pkgName);
491
+ }
492
+ }
493
+ else if (extractKey === 'require') {
494
+ // go.mod style: "require (...)" block or "require pkg version"
495
+ const blockMatch = content.match(/require\s*\(([\s\S]*?)\)/);
496
+ if (blockMatch) {
497
+ for (const line of blockMatch[1].split('\n')) {
498
+ const parts = line.trim().split(/\s+/);
499
+ if (parts.length >= 1 && parts[0])
500
+ deps.push(parts[0]);
501
+ }
502
+ }
503
+ // Single-line require
504
+ const singleRe = /require\s+(\S+)\s+\S+/g;
505
+ let m;
506
+ while ((m = singleRe.exec(content)) !== null) {
507
+ deps.push(m[1]);
508
+ }
509
+ }
510
+ else if (extractKey === 'target_link_libraries,find_package' || extractKey === 'find_package,target_link_libraries') {
511
+ // CMakeLists.txt style
512
+ const findPkgRe = /find_package\s*\(\s*(\w+)/g;
513
+ let fm;
514
+ while ((fm = findPkgRe.exec(content)) !== null) {
515
+ deps.push(fm[1]);
516
+ }
517
+ const tllRe = /target_link_libraries\s*\(\s*\w+\s+(?:PUBLIC|PRIVATE|INTERFACE)\s+(\w+)/g;
518
+ let tm;
519
+ while ((tm = tllRe.exec(content)) !== null) {
520
+ deps.push(tm[1]);
521
+ }
522
+ }
523
+ break;
524
+ }
525
+ case 'toml': {
526
+ const extractKey = depFilePattern.extractDeps;
527
+ // Simple TOML section parser for [dependencies] style
528
+ const sectionNames = extractKey.split(',');
529
+ for (const sectionName of sectionNames) {
530
+ const section = sectionName.trim();
531
+ // Match [section] or [section.sub]
532
+ const sectionRe = new RegExp(`\\[${section.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\]\\s*\\n([\\s\\S]*?)(?:\\n\\[|$)`, 'm');
533
+ const m = content.match(sectionRe);
534
+ if (m) {
535
+ for (const line of m[1].split('\n')) {
536
+ const kvMatch = line.match(/^\s*(\w[\w.-]*)\s*=/);
537
+ if (kvMatch) {
538
+ deps.push(kvMatch[1]);
539
+ }
540
+ }
541
+ }
542
+ // Also try: section.sub.key style (e.g. project.dependencies)
543
+ if (section.includes('.')) {
544
+ // handled by the section match above with simple bracket matching
545
+ }
546
+ }
547
+ break;
548
+ }
549
+ case 'xml': {
550
+ // Extract <dependency> blocks from pom.xml
551
+ const depRe = /<dependency>\s*<groupId>([^<]+)<\/groupId>\s*<artifactId>([^<]+)<\/artifactId>/g;
552
+ let dm;
553
+ while ((dm = depRe.exec(content)) !== null) {
554
+ deps.push(`${dm[1]}:${dm[2]}`);
555
+ }
556
+ break;
557
+ }
558
+ case 'groovy': {
559
+ // Extract implementation/api lines from build.gradle
560
+ const extractKey = depFilePattern.extractDeps;
561
+ const keys = extractKey.split(',').map(k => k.trim()).join('|');
562
+ const gradleRe = new RegExp(`(?:${keys})\\s+['"]([^'"]+)['"]`, 'g');
563
+ let gm;
564
+ while ((gm = gradleRe.exec(content)) !== null) {
565
+ // Format: group:artifact:version
566
+ deps.push(gm[1]);
567
+ }
568
+ break;
569
+ }
570
+ }
571
+ return [...new Set(deps)];
572
+ }
573
+ /**
574
+ * Deduplicate ambiguous relations by (kind, from_file, reference).
575
+ */
576
+ function dedupAmbiguous(items) {
577
+ const seen = new Set();
578
+ const result = [];
579
+ for (const item of items) {
580
+ const key = `${item.kind}|${item.severity ?? ''}|${item.from_file}|${item.reference}`;
581
+ if (!seen.has(key)) {
582
+ seen.add(key);
583
+ result.push(item);
584
+ }
585
+ }
586
+ return result;
587
+ }
588
+ /**
589
+ * Print compact text output
590
+ */
591
+ function printCompact(result) {
592
+ console.log(`Project languages: ${result.project_languages.join(', ') || '(none detected)'}`);
593
+ console.log(`Discovered edges: ${result.summary.total_discovered}`);
594
+ console.log(` High confidence (>=0.7): ${result.summary.high_confidence}`);
595
+ console.log(` Low confidence (<0.7): ${result.summary.low_confidence}`);
596
+ console.log(`Ambiguous references: ${result.ambiguous.length}`);
597
+ console.log(` Actionable (project files with no card): ${result.summary.actionable}`);
598
+ console.log(` Informational (external packages): ${result.summary.external_refs}`);
599
+ if (result.discovered_edges.length > 0) {
600
+ console.log('\nInferred Edges:');
601
+ for (const edge of result.discovered_edges) {
602
+ const icon = edge.confidence >= 0.8 ? '●' : edge.confidence >= 0.7 ? '○' : '◌';
603
+ console.log(` ${icon} ${edge.from_id} → ${edge.to_id} (${edge.type}, conf ${edge.confidence.toFixed(1)}, ${edge.evidence.language})`);
604
+ }
605
+ }
606
+ if (result.ambiguous.length > 0) {
607
+ // Show actionable items first so the agent's review is signal-first
608
+ const actionable = result.ambiguous.filter(a => a.severity === 'actionable');
609
+ const informational = result.ambiguous.filter(a => a.severity === 'informational');
610
+ const other = result.ambiguous.filter(a => !a.severity);
611
+ if (actionable.length > 0) {
612
+ console.log('\n⚠ Actionable (consider creating a card):');
613
+ for (const amb of actionable) {
614
+ const tag = `[${amb.kind}]`;
615
+ if (amb.kind === 'unmatched_target') {
616
+ console.log(` ${tag} ${amb.from_file}: "${amb.reference}" → no matching card (${amb.language})`);
617
+ }
618
+ else if (amb.kind === 'multiple_targets') {
619
+ console.log(` ${tag} ${amb.from_file}: "${amb.reference}" → ${amb.suggested_targets?.join(', ')} (${amb.language})`);
620
+ }
621
+ else if (amb.kind === 'low_confidence') {
622
+ console.log(` ${tag} ${amb.reference} (conf ${amb.confidence?.toFixed(1)})`);
623
+ }
624
+ }
625
+ }
626
+ if (informational.length > 0) {
627
+ console.log(`\n· Informational (external/builtin, no action): ${informational.length} item(s)`);
628
+ // Show up to 5, then a count
629
+ for (const amb of informational.slice(0, 5)) {
630
+ console.log(` [external] "${amb.reference}" from ${path.basename(amb.from_file)}`);
631
+ }
632
+ if (informational.length > 5) {
633
+ console.log(` ... and ${informational.length - 5} more (use --format json to see all)`);
634
+ }
635
+ }
636
+ if (other.length > 0) {
637
+ console.log('\nOther:');
638
+ for (const amb of other) {
639
+ const tag = `[${amb.kind}]`;
640
+ console.log(` ${tag} ${amb.reference} (${amb.language})`);
641
+ }
642
+ }
643
+ }
644
+ }
645
+ //# sourceMappingURL=index.js.map