wayfind 2.0.76 → 2.0.78
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
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
|
@@ -2897,6 +2897,42 @@ function extractStandupSection(lines, headers) {
|
|
|
2897
2897
|
|
|
2898
2898
|
// ── Update command ─────────────────────────────────────────────────────────
|
|
2899
2899
|
|
|
2900
|
+
/**
|
|
2901
|
+
* Ensure ~/.claude/settings.json contains Write allowlist entries for Wayfind
|
|
2902
|
+
* state files (journals, team-state, personal-state). Without these, plan mode
|
|
2903
|
+
* prompts for every state write, which is disruptive and confusing.
|
|
2904
|
+
* Idempotent — skips entries already present.
|
|
2905
|
+
* @returns {string[]} entries that were added (empty if nothing changed)
|
|
2906
|
+
*/
|
|
2907
|
+
function ensureStateWritePermissions() {
|
|
2908
|
+
if (!HOME) return [];
|
|
2909
|
+
const settingsPath = path.join(HOME, '.claude', 'settings.json');
|
|
2910
|
+
|
|
2911
|
+
const required = [
|
|
2912
|
+
`Write(${HOME}/.claude/memory/**)`,
|
|
2913
|
+
`Write(${HOME}/.claude/global-state.md)`,
|
|
2914
|
+
`Write(${HOME}/.claude/state.md)`,
|
|
2915
|
+
`Write(${HOME}/**/.claude/team-state.md)`,
|
|
2916
|
+
`Write(${HOME}/**/.claude/personal-state.md)`,
|
|
2917
|
+
];
|
|
2918
|
+
|
|
2919
|
+
let settings = {};
|
|
2920
|
+
if (fs.existsSync(settingsPath)) {
|
|
2921
|
+
try { settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8')); } catch { return []; }
|
|
2922
|
+
}
|
|
2923
|
+
|
|
2924
|
+
if (!settings.permissions) settings.permissions = {};
|
|
2925
|
+
if (!Array.isArray(settings.permissions.allow)) settings.permissions.allow = [];
|
|
2926
|
+
|
|
2927
|
+
const existing = new Set(settings.permissions.allow);
|
|
2928
|
+
const added = required.filter(r => !existing.has(r));
|
|
2929
|
+
if (added.length === 0) return [];
|
|
2930
|
+
|
|
2931
|
+
settings.permissions.allow.push(...added);
|
|
2932
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
2933
|
+
return added;
|
|
2934
|
+
}
|
|
2935
|
+
|
|
2900
2936
|
/**
|
|
2901
2937
|
* Re-sync hooks and commands from the installed Wayfind package to ~/.claude/.
|
|
2902
2938
|
* Copies hook scripts and slash-command files, overwriting stale copies.
|
|
@@ -2972,6 +3008,15 @@ function runUpdate() {
|
|
|
2972
3008
|
} else {
|
|
2973
3009
|
console.log(' Everything up to date.');
|
|
2974
3010
|
}
|
|
3011
|
+
|
|
3012
|
+
// Ensure state-file Write permissions are allowlisted so plan mode doesn't
|
|
3013
|
+
// prompt on every journal/state write.
|
|
3014
|
+
const addedPerms = ensureStateWritePermissions();
|
|
3015
|
+
if (addedPerms.length > 0) {
|
|
3016
|
+
console.log('\n── Write permissions ──');
|
|
3017
|
+
for (const p of addedPerms) console.log(` ✓ Allowlisted: ${p}`);
|
|
3018
|
+
console.log(' (Prevents plan-mode prompts on journal and state-file writes)');
|
|
3019
|
+
}
|
|
2975
3020
|
}
|
|
2976
3021
|
|
|
2977
3022
|
// ── Migrate to plugin ───────────────────────────────────────────────────────
|
package/package.json
CHANGED
|
@@ -146,6 +146,26 @@ Read the repo's `CLAUDE.md`. If it does NOT already contain "Session State Proto
|
|
|
146
146
|
|
|
147
147
|
If `CLAUDE.md` doesn't exist, create a minimal one with the repo name as a heading and the block above.
|
|
148
148
|
|
|
149
|
+
## Step 4.5: Patch Write Permissions in ~/.claude/settings.json
|
|
150
|
+
|
|
151
|
+
Read `~/.claude/settings.json` (create it as `{}` if missing).
|
|
152
|
+
|
|
153
|
+
Ensure the following entries are present in `permissions.allow`. Add any that are missing — do NOT remove existing entries:
|
|
154
|
+
|
|
155
|
+
```
|
|
156
|
+
Write(<HOME>/.claude/memory/**)
|
|
157
|
+
Write(<HOME>/.claude/global-state.md)
|
|
158
|
+
Write(<HOME>/.claude/state.md)
|
|
159
|
+
Write(<HOME>/**/.claude/team-state.md)
|
|
160
|
+
Write(<HOME>/**/.claude/personal-state.md)
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Where `<HOME>` is the user's actual home directory (e.g. `/home/greg` or `/Users/greg`).
|
|
164
|
+
|
|
165
|
+
**Why:** Without these, Claude Code's plan mode prompts for approval on every journal and state-file write — even when the user is in bypass/dangerously-skip-permissions mode. These files are internal Wayfind state and are never dangerous to write.
|
|
166
|
+
|
|
167
|
+
Report: "Write permissions patched — N entries added" (or "already present" if nothing changed).
|
|
168
|
+
|
|
149
169
|
## Step 5: Register State Files in Global Index
|
|
150
170
|
|
|
151
171
|
Read `~/.claude/global-state.md`. The Active Projects table is auto-generated by `wayfind status --write` — do NOT add rows to it manually.
|