@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
package/mcp/dist/phren-paths.js
CHANGED
|
@@ -4,7 +4,7 @@ import * as path from "path";
|
|
|
4
4
|
import * as crypto from "crypto";
|
|
5
5
|
import * as yaml from "js-yaml";
|
|
6
6
|
import { bootstrapPhrenDotEnv } from "./phren-dotenv.js";
|
|
7
|
-
import { PhrenError, isRecord } from "./phren-core.js";
|
|
7
|
+
import { PhrenError, isRecord, RESERVED_PROJECT_DIR_NAMES } from "./phren-core.js";
|
|
8
8
|
import { errorMessage, isValidProjectName, safeProjectPath } from "./utils.js";
|
|
9
9
|
bootstrapPhrenDotEnv();
|
|
10
10
|
export const ROOT_MANIFEST_FILENAME = "phren.root.yaml";
|
|
@@ -74,7 +74,7 @@ export function readRootManifest(phrenPath) {
|
|
|
74
74
|
return normalizeManifest(parsed);
|
|
75
75
|
}
|
|
76
76
|
catch (err) {
|
|
77
|
-
if ((process.env.PHREN_DEBUG
|
|
77
|
+
if ((process.env.PHREN_DEBUG))
|
|
78
78
|
process.stderr.write(`[phren] readRootManifest: ${errorMessage(err)}\n`);
|
|
79
79
|
return null;
|
|
80
80
|
}
|
|
@@ -250,7 +250,7 @@ export function sessionMarker(phrenPath, name) {
|
|
|
250
250
|
}
|
|
251
251
|
// Debug logging is best-effort and only writes when a phren root already exists.
|
|
252
252
|
export function debugLog(msg) {
|
|
253
|
-
if (!(process.env.PHREN_DEBUG
|
|
253
|
+
if (!(process.env.PHREN_DEBUG))
|
|
254
254
|
return;
|
|
255
255
|
const phrenPath = findPhrenPath();
|
|
256
256
|
if (!phrenPath)
|
|
@@ -269,7 +269,7 @@ export function appendIndexEvent(phrenPath, event) {
|
|
|
269
269
|
fs.appendFileSync(file, JSON.stringify({ at: new Date().toISOString(), ...event }) + "\n");
|
|
270
270
|
}
|
|
271
271
|
catch (err) {
|
|
272
|
-
if ((process.env.PHREN_DEBUG
|
|
272
|
+
if ((process.env.PHREN_DEBUG))
|
|
273
273
|
process.stderr.write(`[phren] appendIndexEvent: ${errorMessage(err)}\n`);
|
|
274
274
|
}
|
|
275
275
|
}
|
|
@@ -280,7 +280,6 @@ export function resolveFindingsPath(projectDir) {
|
|
|
280
280
|
return findingsPath;
|
|
281
281
|
return undefined;
|
|
282
282
|
}
|
|
283
|
-
const RESERVED_PROJECT_DIR_NAMES = new Set(["profiles", "templates", "global"]);
|
|
284
283
|
function isProjectDirEntry(entry) {
|
|
285
284
|
return entry.isDirectory()
|
|
286
285
|
&& !entry.name.startsWith(".")
|
|
@@ -301,7 +300,7 @@ export function findProjectNameCaseInsensitive(phrenPath, name) {
|
|
|
301
300
|
}
|
|
302
301
|
}
|
|
303
302
|
catch (err) {
|
|
304
|
-
if ((process.env.PHREN_DEBUG
|
|
303
|
+
if ((process.env.PHREN_DEBUG))
|
|
305
304
|
process.stderr.write(`[phren] findProjectNameCaseInsensitive: ${errorMessage(err)}\n`);
|
|
306
305
|
}
|
|
307
306
|
return null;
|
|
@@ -357,7 +356,7 @@ export function getProjectDirs(phrenPath, profile) {
|
|
|
357
356
|
return [...new Set([...listed, ...sharedDirs])];
|
|
358
357
|
}
|
|
359
358
|
catch (err) {
|
|
360
|
-
if ((process.env.PHREN_DEBUG
|
|
359
|
+
if ((process.env.PHREN_DEBUG))
|
|
361
360
|
process.stderr.write(`[phren] getProjectDirs yamlParse: ${errorMessage(err)}\n`);
|
|
362
361
|
console.error(`${PhrenError.MALFORMED_YAML}: Malformed profile YAML: ${profilePath}`);
|
|
363
362
|
return [];
|
|
@@ -369,7 +368,7 @@ export function getProjectDirs(phrenPath, profile) {
|
|
|
369
368
|
.map((entry) => path.join(phrenPath, entry.name));
|
|
370
369
|
}
|
|
371
370
|
catch (err) {
|
|
372
|
-
if ((process.env.PHREN_DEBUG
|
|
371
|
+
if ((process.env.PHREN_DEBUG))
|
|
373
372
|
process.stderr.write(`[phren] getProjectDirs: ${errorMessage(err)}\n`);
|
|
374
373
|
return [];
|
|
375
374
|
}
|
|
@@ -396,7 +395,7 @@ export function collectNativeMemoryFiles() {
|
|
|
396
395
|
}
|
|
397
396
|
}
|
|
398
397
|
catch (err) {
|
|
399
|
-
if ((process.env.PHREN_DEBUG
|
|
398
|
+
if ((process.env.PHREN_DEBUG))
|
|
400
399
|
process.stderr.write(`[phren] collectNativeMemoryFiles: ${errorMessage(err)}\n`);
|
|
401
400
|
}
|
|
402
401
|
return results;
|
package/mcp/dist/proactivity.js
CHANGED
|
@@ -93,21 +93,21 @@ function resolveProactivityLevel(raw, fallback) {
|
|
|
93
93
|
}
|
|
94
94
|
export function getProactivityLevel(explicitPhrenPath) {
|
|
95
95
|
bootstrapPhrenDotEnv();
|
|
96
|
-
return resolveProactivityLevel(process.env.PHREN_PROACTIVITY
|
|
96
|
+
return resolveProactivityLevel(process.env.PHREN_PROACTIVITY, getConfiguredProactivityDefault(explicitPhrenPath));
|
|
97
97
|
}
|
|
98
98
|
export function getProactivityLevelForFindings(explicitPhrenPath) {
|
|
99
99
|
bootstrapPhrenDotEnv();
|
|
100
|
-
const findingsPreference = parseProactivityLevel(process.env.PHREN_PROACTIVITY_FINDINGS
|
|
100
|
+
const findingsPreference = parseProactivityLevel(process.env.PHREN_PROACTIVITY_FINDINGS);
|
|
101
101
|
if (findingsPreference)
|
|
102
102
|
return findingsPreference;
|
|
103
|
-
return resolveProactivityLevel(process.env.PHREN_PROACTIVITY
|
|
103
|
+
return resolveProactivityLevel(process.env.PHREN_PROACTIVITY, getConfiguredProactivityLevelForFindingsDefault(explicitPhrenPath));
|
|
104
104
|
}
|
|
105
105
|
export function getProactivityLevelForTask(explicitPhrenPath) {
|
|
106
106
|
bootstrapPhrenDotEnv();
|
|
107
|
-
const taskPreference = parseProactivityLevel(process.env.PHREN_PROACTIVITY_TASKS
|
|
107
|
+
const taskPreference = parseProactivityLevel(process.env.PHREN_PROACTIVITY_TASKS);
|
|
108
108
|
if (taskPreference)
|
|
109
109
|
return taskPreference;
|
|
110
|
-
return resolveProactivityLevel(process.env.PHREN_PROACTIVITY
|
|
110
|
+
return resolveProactivityLevel(process.env.PHREN_PROACTIVITY, getConfiguredProactivityLevelForTaskDefault(explicitPhrenPath));
|
|
111
111
|
}
|
|
112
112
|
export function hasExplicitFindingSignal(...texts) {
|
|
113
113
|
return texts.some((text) => {
|
|
@@ -73,7 +73,7 @@ export function listMachines(phrenPath) {
|
|
|
73
73
|
return phrenOk(cleaned);
|
|
74
74
|
}
|
|
75
75
|
catch (err) {
|
|
76
|
-
if ((process.env.PHREN_DEBUG
|
|
76
|
+
if ((process.env.PHREN_DEBUG))
|
|
77
77
|
process.stderr.write(`[phren] listMachines yaml parse: ${errorMessage(err)}\n`);
|
|
78
78
|
return phrenErr(`Could not parse machines.yaml. Check the file for syntax errors or run 'phren doctor --fix'.`, PhrenError.MALFORMED_YAML);
|
|
79
79
|
}
|
|
@@ -139,7 +139,7 @@ export function listProfiles(phrenPath) {
|
|
|
139
139
|
profiles.push({ name, file: full, projects });
|
|
140
140
|
}
|
|
141
141
|
catch (err) {
|
|
142
|
-
if ((process.env.PHREN_DEBUG
|
|
142
|
+
if ((process.env.PHREN_DEBUG))
|
|
143
143
|
process.stderr.write(`[phren] listProfiles yamlParse: ${errorMessage(err)}\n`);
|
|
144
144
|
return phrenErr(`profiles/${file}`, PhrenError.MALFORMED_YAML);
|
|
145
145
|
}
|
|
@@ -4,8 +4,8 @@ import * as path from "path";
|
|
|
4
4
|
import * as yaml from "js-yaml";
|
|
5
5
|
import { readInstallPreferences } from "./init-preferences.js";
|
|
6
6
|
import { debugLog } from "./shared.js";
|
|
7
|
-
import { errorMessage } from "./utils.js";
|
|
8
|
-
import { withFileLock } from "./governance
|
|
7
|
+
import { errorMessage, safeProjectPath } from "./utils.js";
|
|
8
|
+
import { withFileLock } from "./shared-governance.js";
|
|
9
9
|
export const PROJECT_OWNERSHIP_MODES = ["phren-managed", "detached", "repo-managed"];
|
|
10
10
|
export const PROJECT_HOOK_EVENTS = ["UserPromptSubmit", "Stop", "SessionStart", "PostToolUse"];
|
|
11
11
|
export function parseProjectOwnershipMode(raw) {
|
|
@@ -24,22 +24,58 @@ export function parseProjectOwnershipMode(raw) {
|
|
|
24
24
|
export function projectConfigPath(phrenPath, project) {
|
|
25
25
|
return path.join(phrenPath, project, "phren.project.yaml");
|
|
26
26
|
}
|
|
27
|
+
function resolveProjectConfigPath(phrenPath, project) {
|
|
28
|
+
return safeProjectPath(phrenPath, project, "phren.project.yaml");
|
|
29
|
+
}
|
|
30
|
+
function writeProjectConfigFile(configPath, next) {
|
|
31
|
+
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
32
|
+
const tmpPath = `${configPath}.tmp-${crypto.randomUUID()}`;
|
|
33
|
+
fs.writeFileSync(tmpPath, yaml.dump(next, { lineWidth: 1000 }));
|
|
34
|
+
fs.renameSync(tmpPath, configPath);
|
|
35
|
+
_projectConfigCache.delete(configPath);
|
|
36
|
+
}
|
|
37
|
+
function normalizeProjectOverrides(raw) {
|
|
38
|
+
return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
|
|
39
|
+
}
|
|
40
|
+
// ── mtime-based config cache ─────────────────────────────────────────────────
|
|
41
|
+
const _projectConfigCache = new Map();
|
|
42
|
+
export function clearProjectConfigCache() {
|
|
43
|
+
_projectConfigCache.clear();
|
|
44
|
+
}
|
|
27
45
|
export function readProjectConfig(phrenPath, project) {
|
|
28
|
-
const configPath =
|
|
29
|
-
if (!
|
|
46
|
+
const configPath = resolveProjectConfigPath(phrenPath, project);
|
|
47
|
+
if (!configPath) {
|
|
48
|
+
debugLog(`readProjectConfig: rejected path for project "${project}"`);
|
|
49
|
+
return {};
|
|
50
|
+
}
|
|
51
|
+
let mtimeMs;
|
|
52
|
+
try {
|
|
53
|
+
mtimeMs = fs.statSync(configPath).mtimeMs;
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// File doesn't exist or can't be stat'd
|
|
57
|
+
_projectConfigCache.delete(configPath);
|
|
30
58
|
return {};
|
|
59
|
+
}
|
|
60
|
+
const cached = _projectConfigCache.get(configPath);
|
|
61
|
+
if (cached && cached.mtimeMs === mtimeMs) {
|
|
62
|
+
return cached.config;
|
|
63
|
+
}
|
|
31
64
|
try {
|
|
32
65
|
const parsed = yaml.load(fs.readFileSync(configPath, "utf8"), { schema: yaml.CORE_SCHEMA });
|
|
33
|
-
|
|
66
|
+
const config = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
67
|
+
_projectConfigCache.set(configPath, { mtimeMs, config });
|
|
68
|
+
return config;
|
|
34
69
|
}
|
|
35
70
|
catch (err) {
|
|
36
71
|
debugLog(`readProjectConfig: failed to parse ${configPath}: ${errorMessage(err)}`);
|
|
72
|
+
_projectConfigCache.delete(configPath);
|
|
37
73
|
return {};
|
|
38
74
|
}
|
|
39
75
|
}
|
|
40
76
|
export function writeProjectConfig(phrenPath, project, patch) {
|
|
41
|
-
const configPath =
|
|
42
|
-
if (!configPath
|
|
77
|
+
const configPath = resolveProjectConfigPath(phrenPath, project);
|
|
78
|
+
if (!configPath) {
|
|
43
79
|
throw new Error(`Project config path escapes phren store`);
|
|
44
80
|
}
|
|
45
81
|
return withFileLock(configPath, () => {
|
|
@@ -48,10 +84,24 @@ export function writeProjectConfig(phrenPath, project, patch) {
|
|
|
48
84
|
...current,
|
|
49
85
|
...patch,
|
|
50
86
|
};
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
87
|
+
writeProjectConfigFile(configPath, next);
|
|
88
|
+
return next;
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
export function updateProjectConfigOverrides(phrenPath, project, updater) {
|
|
92
|
+
const configPath = resolveProjectConfigPath(phrenPath, project);
|
|
93
|
+
if (!configPath) {
|
|
94
|
+
throw new Error(`Project config path escapes phren store`);
|
|
95
|
+
}
|
|
96
|
+
return withFileLock(configPath, () => {
|
|
97
|
+
const current = readProjectConfig(phrenPath, project);
|
|
98
|
+
const currentConfig = normalizeProjectOverrides(current.config);
|
|
99
|
+
const nextOverrides = normalizeProjectOverrides(updater(currentConfig));
|
|
100
|
+
const next = {
|
|
101
|
+
...current,
|
|
102
|
+
config: nextOverrides,
|
|
103
|
+
};
|
|
104
|
+
writeProjectConfigFile(configPath, next);
|
|
55
105
|
return next;
|
|
56
106
|
});
|
|
57
107
|
}
|
|
@@ -82,8 +132,8 @@ export function isProjectHookEnabled(phrenPath, project, event, config) {
|
|
|
82
132
|
}
|
|
83
133
|
export function writeProjectHookConfig(phrenPath, project, patch) {
|
|
84
134
|
// Move read+merge inside the lock so concurrent writers cannot clobber each other.
|
|
85
|
-
const configPath =
|
|
86
|
-
if (!configPath
|
|
135
|
+
const configPath = resolveProjectConfigPath(phrenPath, project);
|
|
136
|
+
if (!configPath) {
|
|
87
137
|
throw new Error(`Project config path escapes phren store`);
|
|
88
138
|
}
|
|
89
139
|
return withFileLock(configPath, () => {
|
|
@@ -95,10 +145,7 @@ export function writeProjectHookConfig(phrenPath, project, patch) {
|
|
|
95
145
|
...patch,
|
|
96
146
|
},
|
|
97
147
|
};
|
|
98
|
-
|
|
99
|
-
const tmpPath = `${configPath}.tmp-${crypto.randomUUID()}`;
|
|
100
|
-
fs.writeFileSync(tmpPath, yaml.dump(next, { lineWidth: 1000 }));
|
|
101
|
-
fs.renameSync(tmpPath, configPath);
|
|
148
|
+
writeProjectConfigFile(configPath, next);
|
|
102
149
|
return next;
|
|
103
150
|
});
|
|
104
151
|
}
|
|
@@ -18,7 +18,7 @@ function homePathForEnv(env, ...parts) {
|
|
|
18
18
|
return joinPortable(homeDir(env), ...parts);
|
|
19
19
|
}
|
|
20
20
|
function defaultPhrenPath(env = process.env) {
|
|
21
|
-
return env.PHREN_PATH ||
|
|
21
|
+
return env.PHREN_PATH || homePathForEnv(env, ".phren");
|
|
22
22
|
}
|
|
23
23
|
function normalizeWindowsPathToWsl(input) {
|
|
24
24
|
if (!input)
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import * as fs from "fs";
|
|
8
8
|
import { runtimeFile, debugLog } from "./shared.js";
|
|
9
9
|
import { isFeatureEnabled, errorMessage } from "./utils.js";
|
|
10
|
+
import { withFileLock } from "./shared-governance.js";
|
|
10
11
|
const CORRELATION_FILENAME = "query-correlations.jsonl";
|
|
11
12
|
const RECENT_WINDOW = 500;
|
|
12
13
|
const MIN_TOKEN_OVERLAP = 2;
|
|
@@ -56,28 +57,30 @@ export function markCorrelationsHelpful(phrenPath, sessionId, docKey) {
|
|
|
56
57
|
const correlationFile = runtimeFile(phrenPath, CORRELATION_FILENAME);
|
|
57
58
|
if (!fs.existsSync(correlationFile))
|
|
58
59
|
return;
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
60
|
+
withFileLock(correlationFile, () => {
|
|
61
|
+
const raw = fs.readFileSync(correlationFile, "utf8");
|
|
62
|
+
const lines = raw.split("\n").filter(Boolean);
|
|
63
|
+
let modified = false;
|
|
64
|
+
const updated = lines.map((line) => {
|
|
65
|
+
try {
|
|
66
|
+
const entry = JSON.parse(line);
|
|
67
|
+
if (entry.sessionId === sessionId &&
|
|
68
|
+
`${entry.project}/${entry.filename}` === docKey &&
|
|
69
|
+
!entry.helpful) {
|
|
70
|
+
entry.helpful = true;
|
|
71
|
+
modified = true;
|
|
72
|
+
return JSON.stringify(entry);
|
|
73
|
+
}
|
|
71
74
|
}
|
|
75
|
+
catch {
|
|
76
|
+
// keep original line
|
|
77
|
+
}
|
|
78
|
+
return line;
|
|
79
|
+
});
|
|
80
|
+
if (modified) {
|
|
81
|
+
fs.writeFileSync(correlationFile, updated.join("\n") + "\n");
|
|
72
82
|
}
|
|
73
|
-
catch {
|
|
74
|
-
// keep original line
|
|
75
|
-
}
|
|
76
|
-
return line;
|
|
77
83
|
});
|
|
78
|
-
if (modified) {
|
|
79
|
-
fs.writeFileSync(correlationFile, updated.join("\n") + "\n");
|
|
80
|
-
}
|
|
81
84
|
}
|
|
82
85
|
catch (err) {
|
|
83
86
|
debugLog(`query-correlation mark-helpful failed: ${errorMessage(err)}`);
|
|
@@ -110,21 +110,21 @@ export function clearTaskCheckpoint(phrenPath, args) {
|
|
|
110
110
|
debugLog(`checkpoint clear ${filePath}: ${errorMessage(err)}`);
|
|
111
111
|
}
|
|
112
112
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
113
|
+
if (args.taskLine) {
|
|
114
|
+
const allProjectCheckpoints = listTaskCheckpoints(phrenPath, args.project);
|
|
115
|
+
for (const checkpoint of allProjectCheckpoints) {
|
|
116
|
+
if (checkpoint.taskLine !== args.taskLine)
|
|
117
|
+
continue;
|
|
118
|
+
const filePath = checkpointPath(phrenPath, checkpoint.project, checkpoint.taskId);
|
|
119
|
+
try {
|
|
120
|
+
if (fs.existsSync(filePath)) {
|
|
121
|
+
fs.unlinkSync(filePath);
|
|
122
|
+
removed++;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
catch (err) {
|
|
126
|
+
debugLog(`checkpoint clear scan ${filePath}: ${errorMessage(err)}`);
|
|
124
127
|
}
|
|
125
|
-
}
|
|
126
|
-
catch (err) {
|
|
127
|
-
debugLog(`checkpoint clear scan ${filePath}: ${errorMessage(err)}`);
|
|
128
128
|
}
|
|
129
129
|
}
|
|
130
130
|
return removed;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as fs from "fs";
|
|
2
2
|
import * as path from "path";
|
|
3
|
+
import { errorMessage } from "./utils.js";
|
|
3
4
|
/**
|
|
4
5
|
* Write JSON to a file atomically using temp-file + rename.
|
|
5
6
|
* Ensures the parent directory exists before writing.
|
|
@@ -15,8 +16,8 @@ export function atomicWriteJson(filePath, data) {
|
|
|
15
16
|
* Centralises the repeated `if (PHREN_DEBUG) stderr.write(...)` pattern.
|
|
16
17
|
*/
|
|
17
18
|
export function debugError(scope, err) {
|
|
18
|
-
if ((process.env.PHREN_DEBUG
|
|
19
|
-
process.stderr.write(`[phren] ${scope}: ${
|
|
19
|
+
if ((process.env.PHREN_DEBUG)) {
|
|
20
|
+
process.stderr.write(`[phren] ${scope}: ${errorMessage(err)}\n`);
|
|
20
21
|
}
|
|
21
22
|
}
|
|
22
23
|
/**
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import { phrenErr, PhrenError, phrenOk, } from "./shared.js";
|
|
4
|
+
import { withFileLock as withFileLockRaw } from "./shared-governance.js";
|
|
5
|
+
import { isValidProjectName, safeProjectPath, errorMessage } from "./utils.js";
|
|
6
|
+
export function withSafeLock(filePath, fn) {
|
|
7
|
+
try {
|
|
8
|
+
return withFileLockRaw(filePath, fn);
|
|
9
|
+
}
|
|
10
|
+
catch (err) {
|
|
11
|
+
const msg = errorMessage(err);
|
|
12
|
+
if (msg.includes("could not acquire lock")) {
|
|
13
|
+
return phrenErr(`Could not acquire write lock for "${path.basename(filePath)}". Another write may be in progress; please retry.`, PhrenError.LOCK_TIMEOUT);
|
|
14
|
+
}
|
|
15
|
+
throw err;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export function ensureProject(phrenPath, project) {
|
|
19
|
+
if (!isValidProjectName(project))
|
|
20
|
+
return phrenErr(`Project name "${project}" is not valid. Use lowercase letters, numbers, and hyphens (e.g. "my-project").`, PhrenError.INVALID_PROJECT_NAME);
|
|
21
|
+
const dir = safeProjectPath(phrenPath, project);
|
|
22
|
+
if (!dir)
|
|
23
|
+
return phrenErr(`Project name "${project}" is not valid. Use lowercase letters, numbers, and hyphens (e.g. "my-project").`, PhrenError.INVALID_PROJECT_NAME);
|
|
24
|
+
if (!fs.existsSync(dir)) {
|
|
25
|
+
return phrenErr(`No project "${project}" found. Add it with 'cd ~/your-project && phren add'.`, PhrenError.PROJECT_NOT_FOUND);
|
|
26
|
+
}
|
|
27
|
+
return phrenOk(dir);
|
|
28
|
+
}
|
|
@@ -2,6 +2,7 @@ import { decodeStringRow } from "./shared-index.js";
|
|
|
2
2
|
import * as fs from "fs";
|
|
3
3
|
import { runtimeFile } from "./shared.js";
|
|
4
4
|
import { UNIVERSAL_TECH_TERMS_RE } from "./phren-core.js";
|
|
5
|
+
import { errorMessage } from "./utils.js";
|
|
5
6
|
export function escapeRegex(s) { return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); }
|
|
6
7
|
/** Escape SQL LIKE wildcard characters so user input is treated literally. */
|
|
7
8
|
export function escapeLike(s) { return s.replace(/[%_\\]/g, '\\$&'); }
|
|
@@ -21,7 +22,7 @@ export function escapeLike(s) { return s.replace(/[%_\\]/g, '\\$&'); }
|
|
|
21
22
|
* found" -> suggest adding it).
|
|
22
23
|
*/
|
|
23
24
|
export function logFragmentMiss(phrenPath, name, context, project) {
|
|
24
|
-
if (!process.env.PHREN_DEBUG
|
|
25
|
+
if (!process.env.PHREN_DEBUG)
|
|
25
26
|
return;
|
|
26
27
|
if (!name || name.length <= 2)
|
|
27
28
|
return;
|
|
@@ -115,8 +116,8 @@ function getOrCreateFragment(db, name, type) {
|
|
|
115
116
|
db.run("INSERT OR IGNORE INTO entities (name, type, first_seen_at) VALUES (?, ?, ?)", [name, type, new Date().toISOString().slice(0, 10)]);
|
|
116
117
|
}
|
|
117
118
|
catch (err) {
|
|
118
|
-
if (process.env.PHREN_DEBUG
|
|
119
|
-
process.stderr.write(`[phren] fragmentInsert: ${
|
|
119
|
+
if (process.env.PHREN_DEBUG)
|
|
120
|
+
process.stderr.write(`[phren] fragmentInsert: ${errorMessage(err)}\n`);
|
|
120
121
|
}
|
|
121
122
|
const result = db.exec("SELECT id FROM entities WHERE name = ? AND type = ?", [name, type]);
|
|
122
123
|
if (result?.length && result[0]?.values?.length) {
|
|
@@ -138,8 +139,8 @@ export function ensureGlobalEntitiesTable(db) {
|
|
|
138
139
|
)`);
|
|
139
140
|
}
|
|
140
141
|
catch (err) {
|
|
141
|
-
if (process.env.PHREN_DEBUG
|
|
142
|
-
process.stderr.write(`[phren] ensureGlobalEntitiesTable: ${
|
|
142
|
+
if (process.env.PHREN_DEBUG)
|
|
143
|
+
process.stderr.write(`[phren] ensureGlobalEntitiesTable: ${errorMessage(err)}\n`);
|
|
143
144
|
}
|
|
144
145
|
}
|
|
145
146
|
/**
|
|
@@ -185,8 +186,8 @@ export function beginUserFragmentBuildCache(phrenPath, projects) {
|
|
|
185
186
|
_buildUserFragmentCache.set(cacheKey, loaded.fragments);
|
|
186
187
|
}
|
|
187
188
|
catch (err) {
|
|
188
|
-
if (process.env.PHREN_DEBUG
|
|
189
|
-
process.stderr.write(`[phren] beginUserFragmentBuildCache: ${
|
|
189
|
+
if (process.env.PHREN_DEBUG)
|
|
190
|
+
process.stderr.write(`[phren] beginUserFragmentBuildCache: ${errorMessage(err)}\n`);
|
|
190
191
|
_buildUserFragmentCache.set(cacheKey, []);
|
|
191
192
|
}
|
|
192
193
|
}
|
|
@@ -224,8 +225,8 @@ function parseUserDefinedFragments(phrenPath, project) {
|
|
|
224
225
|
}
|
|
225
226
|
}
|
|
226
227
|
catch (err) {
|
|
227
|
-
if (process.env.PHREN_DEBUG
|
|
228
|
-
process.stderr.write(`[phren] parseUserDefinedFragments statCheck: ${
|
|
228
|
+
if (process.env.PHREN_DEBUG)
|
|
229
|
+
process.stderr.write(`[phren] parseUserDefinedFragments statCheck: ${errorMessage(err)}\n`);
|
|
229
230
|
}
|
|
230
231
|
}
|
|
231
232
|
const loaded = readUserDefinedFragmentsFromDisk(claudeMdPath);
|
|
@@ -235,8 +236,8 @@ function parseUserDefinedFragments(phrenPath, project) {
|
|
|
235
236
|
return loaded.fragments;
|
|
236
237
|
}
|
|
237
238
|
catch (err) {
|
|
238
|
-
if (process.env.PHREN_DEBUG
|
|
239
|
-
process.stderr.write(`[phren] parseUserDefinedFragments: ${
|
|
239
|
+
if (process.env.PHREN_DEBUG)
|
|
240
|
+
process.stderr.write(`[phren] parseUserDefinedFragments: ${errorMessage(err)}\n`);
|
|
240
241
|
return [];
|
|
241
242
|
}
|
|
242
243
|
}
|
|
@@ -352,8 +353,8 @@ export function extractAndLinkFragments(db, content, sourceDoc, phrenPath) {
|
|
|
352
353
|
db.run("INSERT OR IGNORE INTO entity_links (source_id, target_id, rel_type, source_doc) VALUES (?, ?, ?, ?)", [docFragmentId, fragmentId, "mentions", sourceDoc]);
|
|
353
354
|
}
|
|
354
355
|
catch (err) {
|
|
355
|
-
if (process.env.PHREN_DEBUG
|
|
356
|
-
process.stderr.write(`[phren] fragmentLinksInsert: ${
|
|
356
|
+
if (process.env.PHREN_DEBUG)
|
|
357
|
+
process.stderr.write(`[phren] fragmentLinksInsert: ${errorMessage(err)}\n`);
|
|
357
358
|
}
|
|
358
359
|
// Write to global_entities for cross-project queries
|
|
359
360
|
if (project) {
|
|
@@ -361,8 +362,8 @@ export function extractAndLinkFragments(db, content, sourceDoc, phrenPath) {
|
|
|
361
362
|
db.run("INSERT OR IGNORE INTO global_entities (entity, project, doc_key) VALUES (?, ?, ?)", [name, project, sourceDoc]);
|
|
362
363
|
}
|
|
363
364
|
catch (err) {
|
|
364
|
-
if (process.env.PHREN_DEBUG
|
|
365
|
-
process.stderr.write(`[phren] globalFragmentsInsert: ${
|
|
365
|
+
if (process.env.PHREN_DEBUG)
|
|
366
|
+
process.stderr.write(`[phren] globalFragmentsInsert: ${errorMessage(err)}\n`);
|
|
366
367
|
}
|
|
367
368
|
}
|
|
368
369
|
}
|
|
@@ -390,8 +391,8 @@ export function queryFragmentLinks(db, name) {
|
|
|
390
391
|
}
|
|
391
392
|
}
|
|
392
393
|
catch (err) {
|
|
393
|
-
if (process.env.PHREN_DEBUG
|
|
394
|
-
process.stderr.write(`[phren] queryFragmentLinks: ${
|
|
394
|
+
if (process.env.PHREN_DEBUG)
|
|
395
|
+
process.stderr.write(`[phren] queryFragmentLinks: ${errorMessage(err)}\n`);
|
|
395
396
|
}
|
|
396
397
|
return { related };
|
|
397
398
|
}
|
|
@@ -426,8 +427,8 @@ export function queryCrossProjectFragments(db, fragmentName, excludeProject) {
|
|
|
426
427
|
}
|
|
427
428
|
}
|
|
428
429
|
catch (err) {
|
|
429
|
-
if (process.env.PHREN_DEBUG
|
|
430
|
-
process.stderr.write(`[phren] queryCrossProjectFragments: ${
|
|
430
|
+
if (process.env.PHREN_DEBUG)
|
|
431
|
+
process.stderr.write(`[phren] queryCrossProjectFragments: ${errorMessage(err)}\n`);
|
|
431
432
|
}
|
|
432
433
|
return results;
|
|
433
434
|
}
|
|
@@ -447,8 +448,8 @@ export function getFragmentBoostDocs(db, query) {
|
|
|
447
448
|
return boostDocs;
|
|
448
449
|
}
|
|
449
450
|
catch (err) {
|
|
450
|
-
if (process.env.PHREN_DEBUG
|
|
451
|
-
process.stderr.write(`[phren] getFragmentBoostDocs: ${
|
|
451
|
+
if (process.env.PHREN_DEBUG)
|
|
452
|
+
process.stderr.write(`[phren] getFragmentBoostDocs: ${errorMessage(err)}\n`);
|
|
452
453
|
return new Set();
|
|
453
454
|
}
|
|
454
455
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export * from './governance-policy.js';
|
|
2
2
|
export * from './governance-scores.js';
|
|
3
3
|
export * from './governance-audit.js';
|
|
4
|
-
export { withFileLock } from './governance-locks.js';
|
|
4
|
+
export { withFileLock, isFiniteNumber, hasValidSchemaVersion } from './governance-locks.js';
|