wayfind 2.0.75 → 2.0.77
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/bin/content-store.js +68 -60
- package/bin/team-context.js +12 -0
- package/package.json +1 -1
package/bin/content-store.js
CHANGED
|
@@ -1401,8 +1401,8 @@ async function indexConversations(options = {}) {
|
|
|
1401
1401
|
candidates.push({ filePath, fp, transcript, transcriptText });
|
|
1402
1402
|
}
|
|
1403
1403
|
|
|
1404
|
-
// Phase 2:
|
|
1405
|
-
//
|
|
1404
|
+
// Phase 2+3: Extract decisions and merge results in batches.
|
|
1405
|
+
// Saving convIndex after each batch ensures a hook timeout doesn't discard all progress.
|
|
1406
1406
|
const MAX_CONCURRENT = 5;
|
|
1407
1407
|
|
|
1408
1408
|
if (options.onProgress) {
|
|
@@ -1411,7 +1411,6 @@ async function indexConversations(options = {}) {
|
|
|
1411
1411
|
}
|
|
1412
1412
|
}
|
|
1413
1413
|
|
|
1414
|
-
const extractionResults = [];
|
|
1415
1414
|
for (let i = 0; i < candidates.length; i += MAX_CONCURRENT) {
|
|
1416
1415
|
const batch = candidates.slice(i, i + MAX_CONCURRENT);
|
|
1417
1416
|
const batchResults = await Promise.all(
|
|
@@ -1424,65 +1423,67 @@ async function indexConversations(options = {}) {
|
|
|
1424
1423
|
}
|
|
1425
1424
|
})
|
|
1426
1425
|
);
|
|
1427
|
-
extractionResults.push(...batchResults);
|
|
1428
|
-
}
|
|
1429
1426
|
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1427
|
+
// Merge batch results immediately so a timeout preserves partial progress
|
|
1428
|
+
for (const result of batchResults) {
|
|
1429
|
+
if (result.error) {
|
|
1430
|
+
console.error(`Extraction failed for ${result.filePath}: ${result.error}`);
|
|
1431
|
+
stats.errors++;
|
|
1432
|
+
continue;
|
|
1433
|
+
}
|
|
1437
1434
|
|
|
1438
|
-
|
|
1435
|
+
const { filePath, fp, transcript, decisions } = result;
|
|
1439
1436
|
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1437
|
+
// Store extracted decisions in the content store
|
|
1438
|
+
const entryIds = [];
|
|
1439
|
+
const date = transcript.timestamp
|
|
1440
|
+
? transcript.timestamp.slice(0, 10)
|
|
1441
|
+
: new Date().toISOString().slice(0, 10);
|
|
1445
1442
|
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1443
|
+
for (const decision of decisions) {
|
|
1444
|
+
const id = generateEntryId(date, transcript.repo, decision.title);
|
|
1445
|
+
const content = [
|
|
1446
|
+
`${transcript.repo} — ${decision.title}`,
|
|
1447
|
+
`Date: ${date}`,
|
|
1448
|
+
`Decision: ${decision.decision}`,
|
|
1449
|
+
decision.alternatives ? `Alternatives considered: ${decision.alternatives}` : '',
|
|
1450
|
+
].filter(Boolean).join('\n');
|
|
1454
1451
|
|
|
1455
|
-
|
|
1452
|
+
const hash = contentHash(content);
|
|
1456
1453
|
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1454
|
+
const convEntry = {
|
|
1455
|
+
date,
|
|
1456
|
+
repo: transcript.repo,
|
|
1457
|
+
title: decision.title,
|
|
1458
|
+
source: 'conversation',
|
|
1459
|
+
user: '',
|
|
1460
|
+
drifted: false,
|
|
1461
|
+
contentHash: hash,
|
|
1462
|
+
contentLength: content.length,
|
|
1463
|
+
tags: decision.tags || [],
|
|
1464
|
+
hasEmbedding: false,
|
|
1465
|
+
hasReasoning: !!decision.has_reasoning,
|
|
1466
|
+
hasAlternatives: !!decision.has_alternatives,
|
|
1467
|
+
_content: content,
|
|
1468
|
+
};
|
|
1469
|
+
convEntry.qualityScore = computeQualityScore(convEntry);
|
|
1470
|
+
existingIndex.entries[id] = convEntry;
|
|
1471
|
+
entryIds.push(id);
|
|
1472
|
+
stats.decisionsExtracted++;
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
// Notify caller of extracted decisions (used for journal export)
|
|
1476
|
+
if (options.onDecisions && decisions.length > 0) {
|
|
1477
|
+
options.onDecisions(date, transcript.repo, decisions, fp);
|
|
1478
|
+
}
|
|
1477
1479
|
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1480
|
+
// Update conversation index
|
|
1481
|
+
convIndex[filePath] = { fingerprint: fp, entryIds, extractedAt: Date.now() };
|
|
1482
|
+
stats.transcriptsProcessed++;
|
|
1481
1483
|
}
|
|
1482
1484
|
|
|
1483
|
-
//
|
|
1484
|
-
|
|
1485
|
-
stats.transcriptsProcessed++;
|
|
1485
|
+
// Save convIndex after each batch — partial progress survives a timeout kill
|
|
1486
|
+
backend.saveConversationIndex(convIndex);
|
|
1486
1487
|
}
|
|
1487
1488
|
|
|
1488
1489
|
// Batch embed all new conversation entries
|
|
@@ -1640,21 +1641,25 @@ async function generateOnboardingPack(repoQuery, options = {}) {
|
|
|
1640
1641
|
* @param {Array<{ title: string, decision: string, alternatives: string, tags: string[] }>} decisions
|
|
1641
1642
|
* @param {string} journalDir - Journal directory path
|
|
1642
1643
|
*/
|
|
1643
|
-
function exportDecisionsAsJournal(date, repo, decisions, journalDir, teamId, author) {
|
|
1644
|
+
function exportDecisionsAsJournal(date, repo, decisions, journalDir, teamId, author, srcFp) {
|
|
1644
1645
|
if (!decisions || decisions.length === 0) return;
|
|
1645
1646
|
|
|
1646
1647
|
const authorPart = author ? `-${author}` : '';
|
|
1647
1648
|
const teamPart = teamId ? `-${teamId}` : '';
|
|
1648
1649
|
const filePath = path.join(journalDir, `${date}${authorPart}${teamPart}.md`);
|
|
1649
1650
|
|
|
1650
|
-
// Dedup each decision individually against existing file content
|
|
1651
1651
|
const existing = fs.existsSync(filePath)
|
|
1652
1652
|
? fs.readFileSync(filePath, 'utf8')
|
|
1653
1653
|
: null;
|
|
1654
1654
|
|
|
1655
|
+
// If this exact transcript (by fingerprint) was already exported, skip the whole block.
|
|
1656
|
+
// This prevents re-export when a growing transcript's fingerprint changes and it gets
|
|
1657
|
+
// re-extracted, producing LLM-rephrased titles that bypass title-substring dedup.
|
|
1658
|
+
if (srcFp && existing && existing.includes(`<!-- src-fp:${srcFp} -->`)) return;
|
|
1659
|
+
|
|
1655
1660
|
const newLines = [];
|
|
1656
1661
|
for (const d of decisions) {
|
|
1657
|
-
//
|
|
1662
|
+
// Fallback per-title dedup for entries written before src-fp markers were added
|
|
1658
1663
|
if (existing && existing.includes(d.title)) continue;
|
|
1659
1664
|
|
|
1660
1665
|
const qualityTags = [];
|
|
@@ -1678,6 +1683,9 @@ function exportDecisionsAsJournal(date, repo, decisions, journalDir, teamId, aut
|
|
|
1678
1683
|
|
|
1679
1684
|
if (newLines.length === 0) return;
|
|
1680
1685
|
|
|
1686
|
+
// Append source fingerprint marker so future runs can skip this whole block
|
|
1687
|
+
if (srcFp) newLines.push(`<!-- src-fp:${srcFp} -->`);
|
|
1688
|
+
|
|
1681
1689
|
const content = '\n' + newLines.join('\n');
|
|
1682
1690
|
|
|
1683
1691
|
if (existing !== null) {
|
|
@@ -1709,16 +1717,16 @@ async function indexConversationsWithExport(options = {}) {
|
|
|
1709
1717
|
|
|
1710
1718
|
const stats = await indexConversations({
|
|
1711
1719
|
...options,
|
|
1712
|
-
onDecisions: exportDir ? (date, repo, decisions) => {
|
|
1713
|
-
pendingExports.push({ date, repo, decisions });
|
|
1720
|
+
onDecisions: exportDir ? (date, repo, decisions, fp) => {
|
|
1721
|
+
pendingExports.push({ date, repo, decisions, fp });
|
|
1714
1722
|
} : undefined,
|
|
1715
1723
|
});
|
|
1716
1724
|
|
|
1717
1725
|
// Write pending exports — route to per-team journal files
|
|
1718
|
-
for (const { date, repo, decisions } of pendingExports) {
|
|
1726
|
+
for (const { date, repo, decisions, fp } of pendingExports) {
|
|
1719
1727
|
const teamId = repoToTeam(repo);
|
|
1720
1728
|
if (!teamId) continue; // Unbound repo — skip export (opt-in via .claude/wayfind.json)
|
|
1721
|
-
exportDecisionsAsJournal(date, repo, decisions, exportDir, teamId, author);
|
|
1729
|
+
exportDecisionsAsJournal(date, repo, decisions, exportDir, teamId, author, fp);
|
|
1722
1730
|
exported += decisions.length;
|
|
1723
1731
|
for (const d of decisions) {
|
|
1724
1732
|
if (d.has_reasoning || d.has_alternatives) {
|
package/bin/team-context.js
CHANGED
|
@@ -317,6 +317,18 @@ async function teamCreate() {
|
|
|
317
317
|
};
|
|
318
318
|
|
|
319
319
|
writeJSONFile(TEAM_FILE, team);
|
|
320
|
+
|
|
321
|
+
// Register in local context.json so /init-memory and other commands can find this team
|
|
322
|
+
const config = readContextConfig();
|
|
323
|
+
if (!config.teams) config.teams = {};
|
|
324
|
+
config.teams[id] = {
|
|
325
|
+
path: WAYFIND_DIR,
|
|
326
|
+
name,
|
|
327
|
+
configured_at: new Date().toISOString(),
|
|
328
|
+
};
|
|
329
|
+
if (!config.default) config.default = id;
|
|
330
|
+
writeContextConfig(config);
|
|
331
|
+
|
|
320
332
|
telemetry.capture('team_created', { member_count: 1 }, CLI_USER);
|
|
321
333
|
console.log('');
|
|
322
334
|
console.log(`Team '${name}' created.`);
|
package/package.json
CHANGED