@phren/cli 0.0.10 → 0.0.12
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/README.md +11 -17
- package/mcp/dist/capabilities/cli.js +1 -1
- package/mcp/dist/capabilities/mcp.js +1 -1
- package/mcp/dist/capabilities/vscode.js +1 -1
- package/mcp/dist/capabilities/web-ui.js +1 -1
- package/mcp/dist/cli-actions.js +58 -71
- package/mcp/dist/cli-config.js +337 -131
- package/mcp/dist/cli-extract.js +3 -2
- package/mcp/dist/cli-govern.js +35 -63
- package/mcp/dist/cli-graph.js +19 -4
- package/mcp/dist/cli-hooks-globs.js +2 -1
- package/mcp/dist/cli-hooks-output.js +4 -4
- package/mcp/dist/cli-hooks-session.js +1 -1
- package/mcp/dist/cli-hooks.js +44 -35
- package/mcp/dist/cli-namespaces.js +15 -5
- package/mcp/dist/cli-search.js +2 -2
- package/mcp/dist/cli.js +1 -1
- package/mcp/dist/content-archive.js +23 -14
- package/mcp/dist/content-citation.js +13 -2
- package/mcp/dist/content-dedup.js +9 -9
- package/mcp/dist/content-learning.js +6 -4
- package/mcp/dist/content-metadata.js +10 -0
- package/mcp/dist/core-finding.js +1 -1
- package/mcp/dist/data-access.js +10 -31
- package/mcp/dist/data-tasks.js +5 -26
- package/mcp/dist/embedding.js +7 -8
- package/mcp/dist/entrypoint.js +133 -102
- package/mcp/dist/finding-impact.js +1 -32
- package/mcp/dist/finding-journal.js +1 -1
- package/mcp/dist/finding-lifecycle.js +2 -7
- package/mcp/dist/governance-locks.js +12 -5
- package/mcp/dist/governance-policy.js +156 -9
- package/mcp/dist/governance-scores.js +4 -10
- package/mcp/dist/hooks.js +62 -18
- package/mcp/dist/index.js +4 -4
- package/mcp/dist/init-config.js +4 -25
- package/mcp/dist/init-preferences.js +1 -1
- package/mcp/dist/init-setup.js +6 -55
- package/mcp/dist/init-shared.js +53 -1
- package/mcp/dist/init.js +191 -29
- package/mcp/dist/link-checksums.js +3 -2
- package/mcp/dist/link-context.js +2 -2
- package/mcp/dist/link-doctor.js +14 -57
- package/mcp/dist/link-skills.js +98 -12
- package/mcp/dist/link.js +16 -75
- package/mcp/dist/machine-identity.js +1 -9
- package/mcp/dist/mcp-config.js +247 -42
- package/mcp/dist/mcp-data.js +9 -9
- package/mcp/dist/mcp-extract-facts.js +12 -7
- package/mcp/dist/mcp-extract.js +2 -2
- package/mcp/dist/mcp-finding.js +16 -20
- package/mcp/dist/mcp-graph.js +12 -12
- package/mcp/dist/mcp-hooks.js +1 -1
- package/mcp/dist/mcp-ops.js +18 -18
- package/mcp/dist/mcp-search.js +11 -16
- package/mcp/dist/mcp-session.js +12 -2
- package/mcp/dist/memory-ui-assets.js +1 -36
- package/mcp/dist/memory-ui-graph.js +152 -50
- package/mcp/dist/memory-ui-page.js +30 -5
- package/mcp/dist/memory-ui-scripts.js +252 -63
- package/mcp/dist/memory-ui-server.js +115 -3
- package/mcp/dist/phren-core.js +2 -0
- package/mcp/dist/phren-paths.js +8 -9
- package/mcp/dist/proactivity.js +5 -5
- package/mcp/dist/profile-store.js +2 -2
- package/mcp/dist/project-config.js +64 -17
- package/mcp/dist/provider-adapters.js +1 -1
- package/mcp/dist/query-correlation.js +22 -19
- package/mcp/dist/session-checkpoints.js +14 -14
- package/mcp/dist/session-utils.js +3 -2
- package/mcp/dist/shared-data-utils.js +28 -0
- package/mcp/dist/shared-fragment-graph.js +22 -21
- package/mcp/dist/shared-governance.js +1 -1
- package/mcp/dist/shared-index.js +144 -105
- package/mcp/dist/shared-retrieval.js +21 -23
- package/mcp/dist/shared-search-fallback.js +15 -25
- package/mcp/dist/shared-sqljs.js +3 -2
- package/mcp/dist/shared.js +5 -6
- package/mcp/dist/shell-entry.js +1 -1
- package/mcp/dist/shell-input.js +63 -53
- package/mcp/dist/shell-palette.js +6 -1
- package/mcp/dist/shell-render.js +9 -5
- package/mcp/dist/shell-state-store.js +2 -5
- package/mcp/dist/shell-view.js +7 -6
- package/mcp/dist/shell.js +5 -55
- package/mcp/dist/skill-files.js +4 -10
- package/mcp/dist/skill-registry.js +3 -0
- package/mcp/dist/status.js +43 -21
- package/mcp/dist/task-hygiene.js +1 -1
- package/mcp/dist/telemetry.js +5 -4
- package/mcp/dist/update.js +1 -1
- package/mcp/dist/utils.js +4 -4
- package/package.json +2 -3
- package/skills/docs.md +11 -11
- package/starter/README.md +1 -1
- package/starter/global/CLAUDE.md +2 -2
- package/starter/global/skills/audit.md +106 -0
- package/mcp/dist/cli-hooks-retrieval.js +0 -2
- package/mcp/dist/impact-scoring.js +0 -22
- package/mcp/dist/shared-paths.js +0 -1
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import * as fs from "fs";
|
|
2
|
-
import { statSync } from "fs";
|
|
3
2
|
import * as path from "path";
|
|
4
3
|
import { debugLog, EXEC_TIMEOUT_MS, EXEC_TIMEOUT_QUICK_MS } from "./shared.js";
|
|
5
4
|
import { errorMessage, runGitOrThrow } from "./utils.js";
|
|
@@ -170,8 +169,16 @@ function resolveCitationFile(citation) {
|
|
|
170
169
|
}
|
|
171
170
|
// Session-scoped caches for git I/O during citation validation.
|
|
172
171
|
// Keyed by "repo\0commit" and "repo\0file\0line" respectively.
|
|
172
|
+
const MAX_CACHE_ENTRIES = 500;
|
|
173
173
|
const commitExistsCache = new Map();
|
|
174
174
|
const blameCache = new Map();
|
|
175
|
+
function evictOldest(cache) {
|
|
176
|
+
if (cache.size <= MAX_CACHE_ENTRIES)
|
|
177
|
+
return;
|
|
178
|
+
const first = cache.keys().next().value;
|
|
179
|
+
if (first !== undefined)
|
|
180
|
+
cache.delete(first);
|
|
181
|
+
}
|
|
175
182
|
function commitExists(repoPath, commit) {
|
|
176
183
|
const key = `${repoPath}\0${commit}`;
|
|
177
184
|
const cached = commitExistsCache.get(key);
|
|
@@ -180,11 +187,13 @@ function commitExists(repoPath, commit) {
|
|
|
180
187
|
try {
|
|
181
188
|
runGitOrThrow(repoPath, ["cat-file", "-e", `${commit}^{commit}`], EXEC_TIMEOUT_QUICK_MS);
|
|
182
189
|
commitExistsCache.set(key, true);
|
|
190
|
+
evictOldest(commitExistsCache);
|
|
183
191
|
return true;
|
|
184
192
|
}
|
|
185
193
|
catch (err) {
|
|
186
194
|
debugLog(`commitExists: commit ${commit} not found in ${repoPath}: ${errorMessage(err)}`);
|
|
187
195
|
commitExistsCache.set(key, false);
|
|
196
|
+
evictOldest(commitExistsCache);
|
|
188
197
|
return false;
|
|
189
198
|
}
|
|
190
199
|
}
|
|
@@ -197,11 +206,13 @@ function cachedBlame(repoPath, relFile, line) {
|
|
|
197
206
|
const out = runGitOrThrow(repoPath, ["blame", "-L", `${line},${line}`, "--porcelain", relFile], 10_000).trim();
|
|
198
207
|
const first = out.split("\n")[0] || "";
|
|
199
208
|
blameCache.set(key, first);
|
|
209
|
+
evictOldest(blameCache);
|
|
200
210
|
return first;
|
|
201
211
|
}
|
|
202
212
|
catch (err) {
|
|
203
213
|
debugLog(`cachedBlame: git blame failed for ${relFile}:${line}: ${errorMessage(err)}`);
|
|
204
214
|
blameCache.set(key, false);
|
|
215
|
+
evictOldest(blameCache);
|
|
205
216
|
return false;
|
|
206
217
|
}
|
|
207
218
|
}
|
|
@@ -274,7 +285,7 @@ function confidenceForAge(ageDays, decay) {
|
|
|
274
285
|
}
|
|
275
286
|
function wasFileModifiedAfter(filePath, findingDate) {
|
|
276
287
|
try {
|
|
277
|
-
const stat = statSync(filePath);
|
|
288
|
+
const stat = fs.statSync(filePath);
|
|
278
289
|
const fileModified = stat.mtime.toISOString().slice(0, 10);
|
|
279
290
|
return fileModified > findingDate;
|
|
280
291
|
}
|
|
@@ -2,7 +2,7 @@ import * as fs from "fs";
|
|
|
2
2
|
import * as path from "path";
|
|
3
3
|
import * as crypto from "crypto";
|
|
4
4
|
import { debugLog, runtimeFile, KNOWN_OBSERVATION_TAGS } from "./shared.js";
|
|
5
|
-
import { isFeatureEnabled, safeProjectPath } from "./utils.js";
|
|
5
|
+
import { isFeatureEnabled, safeProjectPath, errorMessage } from "./utils.js";
|
|
6
6
|
import { UNIVERSAL_TECH_TERMS_RE, EXTRA_ENTITY_PATTERNS } from "./phren-core.js";
|
|
7
7
|
import { isInactiveFindingLine } from "./finding-lifecycle.js";
|
|
8
8
|
// ── LLM provider abstraction ────────────────────────────────────────────────
|
|
@@ -50,8 +50,8 @@ async function withCache(cachePath, key, ttlMs, compute) {
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
catch (err) {
|
|
53
|
-
if ((process.env.PHREN_DEBUG
|
|
54
|
-
process.stderr.write(`[phren] withCache load (${path.basename(cachePath)}): ${
|
|
53
|
+
if ((process.env.PHREN_DEBUG))
|
|
54
|
+
process.stderr.write(`[phren] withCache load (${path.basename(cachePath)}): ${errorMessage(err)}\n`);
|
|
55
55
|
}
|
|
56
56
|
const result = await compute();
|
|
57
57
|
// Persist result
|
|
@@ -61,8 +61,8 @@ async function withCache(cachePath, key, ttlMs, compute) {
|
|
|
61
61
|
persistCache(cachePath, cache);
|
|
62
62
|
}
|
|
63
63
|
catch (err) {
|
|
64
|
-
if ((process.env.PHREN_DEBUG
|
|
65
|
-
process.stderr.write(`[phren] withCache persist (${path.basename(cachePath)}): ${
|
|
64
|
+
if ((process.env.PHREN_DEBUG))
|
|
65
|
+
process.stderr.write(`[phren] withCache persist (${path.basename(cachePath)}): ${errorMessage(err)}\n`);
|
|
66
66
|
}
|
|
67
67
|
return result;
|
|
68
68
|
}
|
|
@@ -562,8 +562,8 @@ export async function checkSemanticConflicts(phrenPath, project, newFinding, sig
|
|
|
562
562
|
return { name: e.name, mtime: fs.statSync(fp).mtimeMs, fp };
|
|
563
563
|
}
|
|
564
564
|
catch (err) {
|
|
565
|
-
if ((process.env.PHREN_DEBUG
|
|
566
|
-
process.stderr.write(`[phren] crossProjectScan stat: ${
|
|
565
|
+
if ((process.env.PHREN_DEBUG))
|
|
566
|
+
process.stderr.write(`[phren] crossProjectScan stat: ${errorMessage(err)}\n`);
|
|
567
567
|
return null;
|
|
568
568
|
}
|
|
569
569
|
})
|
|
@@ -577,8 +577,8 @@ export async function checkSemanticConflicts(phrenPath, project, newFinding, sig
|
|
|
577
577
|
}
|
|
578
578
|
}
|
|
579
579
|
catch (err) {
|
|
580
|
-
if ((process.env.PHREN_DEBUG
|
|
581
|
-
process.stderr.write(`[phren] crossProjectScan: ${
|
|
580
|
+
if ((process.env.PHREN_DEBUG))
|
|
581
|
+
process.stderr.write(`[phren] crossProjectScan: ${errorMessage(err)}\n`);
|
|
582
582
|
}
|
|
583
583
|
const annotations = [];
|
|
584
584
|
const deadline = Date.now() + CONFLICT_CHECK_TOTAL_TIMEOUT_MS;
|
|
@@ -378,7 +378,7 @@ export function addFindingToFile(phrenPath, project, learning, citationInput, op
|
|
|
378
378
|
if (!result.ok)
|
|
379
379
|
return result;
|
|
380
380
|
if (typeof result.data === "string")
|
|
381
|
-
return phrenOk(result.data);
|
|
381
|
+
return phrenOk({ message: result.data, status: "skipped" });
|
|
382
382
|
appendAuditLog(phrenPath, "add_finding", `project=${project}${result.data.created ? " created=true" : ""} citation_commit=${result.data.citation.commit ?? "none"} citation_file=${result.data.citation.file ?? "none"}`);
|
|
383
383
|
const cap = Number.parseInt((process.env.PHREN_FINDINGS_CAP) || "", 10) || DEFAULT_FINDINGS_CAP;
|
|
384
384
|
const activeCount = countActiveFindings(result.data.content);
|
|
@@ -390,10 +390,12 @@ export function addFindingToFile(phrenPath, project, learning, citationInput, op
|
|
|
390
390
|
}
|
|
391
391
|
if (result.data.created) {
|
|
392
392
|
const createdMsg = `Created FINDINGS.md for "${project}" and added insight.`;
|
|
393
|
-
|
|
393
|
+
const message = result.data.tagWarning ? `${createdMsg} Warning: ${result.data.tagWarning}` : createdMsg;
|
|
394
|
+
return phrenOk({ message, status: "created" });
|
|
394
395
|
}
|
|
395
396
|
const addedMsg = `Added finding to ${project}: ${result.data.bullet} (with citation metadata)`;
|
|
396
|
-
|
|
397
|
+
const message = result.data.tagWarning ? `${addedMsg} Warning: ${result.data.tagWarning}` : addedMsg;
|
|
398
|
+
return phrenOk({ message, status: "added" });
|
|
397
399
|
}
|
|
398
400
|
export function addFindingsToFile(phrenPath, project, learnings, opts) {
|
|
399
401
|
if (!isValidProjectName(project))
|
|
@@ -402,8 +404,8 @@ export function addFindingsToFile(phrenPath, project, learnings, opts) {
|
|
|
402
404
|
if (!resolvedDir)
|
|
403
405
|
return phrenErr(`Invalid project name: "${project}".`, PhrenError.INVALID_PROJECT_NAME);
|
|
404
406
|
const learningsPath = path.join(resolvedDir, "FINDINGS.md");
|
|
405
|
-
const today = new Date().toISOString().slice(0, 10);
|
|
406
407
|
const nowIso = new Date().toISOString();
|
|
408
|
+
const today = nowIso.slice(0, 10);
|
|
407
409
|
const resolvedCitationInputResult = resolveFindingCitationInput(phrenPath, project);
|
|
408
410
|
if (!resolvedCitationInputResult.ok)
|
|
409
411
|
return resolvedCitationInputResult;
|
|
@@ -172,6 +172,16 @@ export function stripAllMetadata(line) {
|
|
|
172
172
|
export function stripComments(text) {
|
|
173
173
|
return text.replace(METADATA_REGEX.anyComment, "").trim();
|
|
174
174
|
}
|
|
175
|
+
/** Normalize finding text for comparison: strips bullet prefix, HTML comments, confidence tags, normalizes whitespace, lowercases. */
|
|
176
|
+
export function normalizeFindingText(raw) {
|
|
177
|
+
return raw
|
|
178
|
+
.replace(/^-\s+/, "")
|
|
179
|
+
.replace(/<!--.*?-->/g, " ")
|
|
180
|
+
.replace(/\[confidence\s+[01](?:\.\d+)?\]/gi, " ")
|
|
181
|
+
.replace(/\s+/g, " ")
|
|
182
|
+
.trim()
|
|
183
|
+
.toLowerCase();
|
|
184
|
+
}
|
|
175
185
|
// ---------------------------------------------------------------------------
|
|
176
186
|
// Add helpers — append metadata comments to a line
|
|
177
187
|
// ---------------------------------------------------------------------------
|
package/mcp/dist/core-finding.js
CHANGED
|
@@ -18,7 +18,7 @@ export function addFinding(phrenPath, project, finding, citation, findingType) {
|
|
|
18
18
|
if (!result.ok) {
|
|
19
19
|
return { ok: false, message: result.error };
|
|
20
20
|
}
|
|
21
|
-
return { ok: true, message: result.data, data: { project, finding: taggedFinding } };
|
|
21
|
+
return { ok: true, message: result.data.message, data: { project, finding: taggedFinding } };
|
|
22
22
|
}
|
|
23
23
|
/**
|
|
24
24
|
* Remove a finding by partial text match.
|
package/mcp/dist/data-access.js
CHANGED
|
@@ -2,38 +2,17 @@ import * as fs from "fs";
|
|
|
2
2
|
import * as path from "path";
|
|
3
3
|
import * as yaml from "js-yaml";
|
|
4
4
|
import { phrenErr, PhrenError, phrenOk, forwardErr, getProjectDirs, isRecord, } from "./shared.js";
|
|
5
|
-
import { normalizeQueueEntryText,
|
|
5
|
+
import { normalizeQueueEntryText, } from "./shared-governance.js";
|
|
6
6
|
import { addFindingToFile, } from "./shared-content.js";
|
|
7
|
-
import { isValidProjectName, queueFilePath, safeProjectPath
|
|
7
|
+
import { isValidProjectName, queueFilePath, safeProjectPath } from "./utils.js";
|
|
8
8
|
import { parseCitationComment, parseSourceComment, } from "./content-citation.js";
|
|
9
9
|
import { parseFindingLifecycle, } from "./finding-lifecycle.js";
|
|
10
|
-
import { METADATA_REGEX, isCitationLine, isArchiveStart, isArchiveEnd, parseFindingId, parseAllContradictions, stripComments, } from "./content-metadata.js";
|
|
10
|
+
import { METADATA_REGEX, isCitationLine, isArchiveStart, isArchiveEnd, parseFindingId, parseAllContradictions, stripComments, normalizeFindingText, } from "./content-metadata.js";
|
|
11
|
+
import { withSafeLock, ensureProject } from "./shared-data-utils.js";
|
|
11
12
|
export { readTasks, readTasksAcrossProjects, resolveTaskItem, addTask, addTasks, completeTasks, completeTask, removeTask, removeTasks, updateTask, linkTaskIssue, pinTask, unpinTask, workNextTask, tidyDoneTasks, taskMarkdown, appendChildFinding, promoteTask, TASKS_FILENAME, TASK_FILE_ALIASES, canonicalTaskFilePath, resolveTaskFilePath, isTaskFileName, } from "./data-tasks.js";
|
|
12
13
|
export { addProjectToProfile, listMachines, listProfiles, listProjectCards, removeProjectFromProfile, setMachineProfile, } from "./profile-store.js";
|
|
13
|
-
export { loadShellState,
|
|
14
|
-
|
|
15
|
-
try {
|
|
16
|
-
return withFileLockRaw(filePath, fn);
|
|
17
|
-
}
|
|
18
|
-
catch (err) {
|
|
19
|
-
const msg = errorMessage(err);
|
|
20
|
-
if (msg.includes("could not acquire lock")) {
|
|
21
|
-
return phrenErr(`Could not acquire write lock for "${path.basename(filePath)}". Another write may be in progress; please retry.`, PhrenError.LOCK_TIMEOUT);
|
|
22
|
-
}
|
|
23
|
-
throw err;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
function ensureProject(phrenPath, project) {
|
|
27
|
-
if (!isValidProjectName(project))
|
|
28
|
-
return phrenErr(`Project name "${project}" is not valid. Use lowercase letters, numbers, and hyphens (e.g. "my-project").`, PhrenError.INVALID_PROJECT_NAME);
|
|
29
|
-
const dir = safeProjectPath(phrenPath, project);
|
|
30
|
-
if (!dir)
|
|
31
|
-
return phrenErr(`Project name "${project}" is not valid. Use lowercase letters, numbers, and hyphens (e.g. "my-project").`, PhrenError.INVALID_PROJECT_NAME);
|
|
32
|
-
if (!fs.existsSync(dir)) {
|
|
33
|
-
return phrenErr(`No project "${project}" found. Add it with 'cd ~/your-project && phren add'.`, PhrenError.PROJECT_NOT_FOUND);
|
|
34
|
-
}
|
|
35
|
-
return phrenOk(dir);
|
|
36
|
-
}
|
|
14
|
+
export { loadShellState, resetShellState, saveShellState, } from "./shell-state-store.js";
|
|
15
|
+
export { getRuntimeHealth as readRuntimeHealth } from "./shared-governance.js";
|
|
37
16
|
function extractDateHeading(line) {
|
|
38
17
|
const heading = line.match(/^##\s+(.+)$/);
|
|
39
18
|
if (!heading)
|
|
@@ -79,8 +58,8 @@ function findMatchingFindingBullet(bulletLines, needle, match) {
|
|
|
79
58
|
const fidMatch = /^[a-z0-9]{8}$/.test(fidNeedle)
|
|
80
59
|
? bulletLines.filter(({ line }) => new RegExp(`<!--\\s*fid:${fidNeedle}\\s*-->`).test(line))
|
|
81
60
|
: [];
|
|
82
|
-
const exactMatches = bulletLines.filter(({ line }) => line
|
|
83
|
-
const partialMatches = bulletLines.filter(({ line }) => line
|
|
61
|
+
const exactMatches = bulletLines.filter(({ line }) => normalizeFindingText(line) === needle);
|
|
62
|
+
const partialMatches = bulletLines.filter(({ line }) => normalizeFindingText(line).includes(needle));
|
|
84
63
|
if (fidMatch.length === 1)
|
|
85
64
|
return { kind: "found", idx: fidMatch[0].i };
|
|
86
65
|
if (exactMatches.length === 1)
|
|
@@ -276,7 +255,7 @@ export function removeFinding(phrenPath, project, match) {
|
|
|
276
255
|
return phrenErr(`No FINDINGS.md file found for "${project}". Add a finding first with add_finding or :find add.`, PhrenError.FILE_NOT_FOUND);
|
|
277
256
|
return withSafeLock(filePath, () => {
|
|
278
257
|
const lines = fs.readFileSync(filePath, "utf8").split("\n");
|
|
279
|
-
const needle = match
|
|
258
|
+
const needle = normalizeFindingText(match);
|
|
280
259
|
const bulletLines = collectFindingBulletLines(lines);
|
|
281
260
|
const activeMatch = findMatchingFindingBullet(bulletLines.filter(({ archived }) => !archived), needle, match);
|
|
282
261
|
if (activeMatch.kind === "ambiguous") {
|
|
@@ -313,7 +292,7 @@ export function editFinding(phrenPath, project, oldText, newText) {
|
|
|
313
292
|
return phrenErr(`No FINDINGS.md file found for "${project}".`, PhrenError.FILE_NOT_FOUND);
|
|
314
293
|
return withSafeLock(findingsPath, () => {
|
|
315
294
|
const lines = fs.readFileSync(findingsPath, "utf8").split("\n");
|
|
316
|
-
const needle = oldText
|
|
295
|
+
const needle = normalizeFindingText(oldText);
|
|
317
296
|
const bulletLines = collectFindingBulletLines(lines);
|
|
318
297
|
const activeMatch = findMatchingFindingBullet(bulletLines.filter(({ archived }) => !archived), needle, oldText);
|
|
319
298
|
if (activeMatch.kind === "ambiguous") {
|
package/mcp/dist/data-tasks.js
CHANGED
|
@@ -2,21 +2,9 @@ import * as fs from "fs";
|
|
|
2
2
|
import * as path from "path";
|
|
3
3
|
import { randomBytes, randomUUID } from "crypto";
|
|
4
4
|
import { phrenErr, PhrenError, phrenOk, forwardErr, getProjectDirs, } from "./shared.js";
|
|
5
|
-
import { withFileLock as withFileLockRaw } from "./shared-governance.js";
|
|
6
5
|
import { validateTaskFormat } from "./shared-content.js";
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
try {
|
|
10
|
-
return withFileLockRaw(filePath, fn);
|
|
11
|
-
}
|
|
12
|
-
catch (err) {
|
|
13
|
-
const msg = errorMessage(err);
|
|
14
|
-
if (msg.includes("could not acquire lock")) {
|
|
15
|
-
return phrenErr(`Could not acquire write lock for "${path.basename(filePath)}". Another write may be in progress; please retry.`, PhrenError.LOCK_TIMEOUT);
|
|
16
|
-
}
|
|
17
|
-
throw err;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
6
|
+
import { safeProjectPath } from "./utils.js";
|
|
7
|
+
import { withSafeLock, ensureProject } from "./shared-data-utils.js";
|
|
20
8
|
const ACTIVE_HEADINGS = new Set(["active", "in progress", "in-progress", "current", "wip"]);
|
|
21
9
|
const QUEUE_HEADINGS = new Set(["queue", "queued", "task", "todo", "upcoming", "next"]);
|
|
22
10
|
const DONE_HEADINGS = new Set(["done", "completed", "finished", "archived"]);
|
|
@@ -108,17 +96,6 @@ function parseContinuation(lines, idx) {
|
|
|
108
96
|
}
|
|
109
97
|
return { context, githubIssue, githubUrl, linesToSkip };
|
|
110
98
|
}
|
|
111
|
-
function ensureProject(phrenPath, project) {
|
|
112
|
-
if (!isValidProjectName(project))
|
|
113
|
-
return phrenErr(`Project name "${project}" is not valid. Use lowercase letters, numbers, and hyphens (e.g. "my-project").`, PhrenError.INVALID_PROJECT_NAME);
|
|
114
|
-
const dir = safeProjectPath(phrenPath, project);
|
|
115
|
-
if (!dir)
|
|
116
|
-
return phrenErr(`Project name "${project}" is not valid. Use lowercase letters, numbers, and hyphens (e.g. "my-project").`, PhrenError.INVALID_PROJECT_NAME);
|
|
117
|
-
if (!fs.existsSync(dir)) {
|
|
118
|
-
return phrenErr(`No project "${project}" found. Add it with 'cd ~/your-project && phren add'.`, PhrenError.PROJECT_NOT_FOUND);
|
|
119
|
-
}
|
|
120
|
-
return phrenOk(dir);
|
|
121
|
-
}
|
|
122
99
|
/** Pattern that matches the task metadata comment embedded in task item lines.
|
|
123
100
|
* Format: <!-- bid:HASH [rank:N] [lastActivity:ISO] -->
|
|
124
101
|
*/
|
|
@@ -808,7 +785,9 @@ export function tidyDoneTasks(phrenPath, project, keep = 30, dryRun) {
|
|
|
808
785
|
const lines = archived.map((item) => `- [x] ${item.line}${item.context ? `\n Context: ${item.context}` : ""}`);
|
|
809
786
|
const block = `## ${stamp}\n\n${lines.join("\n")}\n\n`;
|
|
810
787
|
const prior = fs.existsSync(archiveFile) ? fs.readFileSync(archiveFile, "utf8") : `# ${project} tasks archive\n\n`;
|
|
811
|
-
|
|
788
|
+
const tmpPath = `${archiveFile}.tmp-${randomUUID()}`;
|
|
789
|
+
fs.writeFileSync(tmpPath, prior + block);
|
|
790
|
+
fs.renameSync(tmpPath, archiveFile);
|
|
812
791
|
writeTaskDoc(parsed.data);
|
|
813
792
|
return phrenOk(`Tidied ${project}: archived ${archived.length} done item(s), kept ${safeKeep}.`);
|
|
814
793
|
});
|
package/mcp/dist/embedding.js
CHANGED
|
@@ -79,7 +79,7 @@ async function openCacheDb(phrenPath) {
|
|
|
79
79
|
db?.close();
|
|
80
80
|
}
|
|
81
81
|
catch (e2) {
|
|
82
|
-
if ((process.env.PHREN_DEBUG
|
|
82
|
+
if ((process.env.PHREN_DEBUG))
|
|
83
83
|
process.stderr.write(`[phren] embedding openCacheDb dbClose: ${e2 instanceof Error ? e2.message : String(e2)}\n`);
|
|
84
84
|
}
|
|
85
85
|
throw err;
|
|
@@ -126,13 +126,13 @@ function persistDb(phrenPath, db) {
|
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
128
|
catch (err) {
|
|
129
|
-
if ((process.env.PHREN_DEBUG
|
|
130
|
-
process.stderr.write(`[phren] embedding persistDb onDiskLoad: ${
|
|
129
|
+
if ((process.env.PHREN_DEBUG))
|
|
130
|
+
process.stderr.write(`[phren] embedding persistDb onDiskLoad: ${errorMessage(err)}\n`);
|
|
131
131
|
try {
|
|
132
132
|
onDisk?.close();
|
|
133
133
|
}
|
|
134
134
|
catch (e2) {
|
|
135
|
-
if ((process.env.PHREN_DEBUG
|
|
135
|
+
if ((process.env.PHREN_DEBUG))
|
|
136
136
|
process.stderr.write(`[phren] embedding persistDb onDiskClose: ${e2 instanceof Error ? e2.message : String(e2)}\n`);
|
|
137
137
|
}
|
|
138
138
|
onDisk = null;
|
|
@@ -149,7 +149,7 @@ function persistDb(phrenPath, db) {
|
|
|
149
149
|
onDisk.close();
|
|
150
150
|
}
|
|
151
151
|
catch (e2) {
|
|
152
|
-
if ((process.env.PHREN_DEBUG
|
|
152
|
+
if ((process.env.PHREN_DEBUG))
|
|
153
153
|
process.stderr.write(`[phren] embedding persistDb onDiskCloseFinally: ${e2 instanceof Error ? e2.message : String(e2)}\n`);
|
|
154
154
|
}
|
|
155
155
|
}
|
|
@@ -269,7 +269,7 @@ export async function getCachedEmbedding(phrenPath, text, apiKey, model) {
|
|
|
269
269
|
db?.close();
|
|
270
270
|
}
|
|
271
271
|
catch (e2) {
|
|
272
|
-
if ((process.env.PHREN_DEBUG
|
|
272
|
+
if ((process.env.PHREN_DEBUG))
|
|
273
273
|
process.stderr.write(`[phren] embedding getCachedEmbedding dbClose: ${e2 instanceof Error ? e2.message : String(e2)}\n`);
|
|
274
274
|
}
|
|
275
275
|
}
|
|
@@ -320,7 +320,7 @@ export async function getCachedEmbeddings(phrenPath, texts, apiKey, model) {
|
|
|
320
320
|
db?.close();
|
|
321
321
|
}
|
|
322
322
|
catch (e2) {
|
|
323
|
-
if ((process.env.PHREN_DEBUG
|
|
323
|
+
if ((process.env.PHREN_DEBUG))
|
|
324
324
|
process.stderr.write(`[phren] embedding getCachedEmbeddings dbClose: ${e2 instanceof Error ? e2.message : String(e2)}\n`);
|
|
325
325
|
}
|
|
326
326
|
}
|
|
@@ -340,5 +340,4 @@ export function cosineSimilarity(a, b) {
|
|
|
340
340
|
const denom = Math.sqrt(normA) * Math.sqrt(normB);
|
|
341
341
|
return denom === 0 ? 0 : dot / denom;
|
|
342
342
|
}
|
|
343
|
-
// Export helpers for testing
|
|
344
343
|
export { encodeEmbedding, decodeEmbedding, openCacheDb };
|