@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
|
@@ -2,7 +2,7 @@ import * as crypto from "crypto";
|
|
|
2
2
|
import * as fs from "fs";
|
|
3
3
|
import * as path from "path";
|
|
4
4
|
import { appendAuditLog, debugLog, isRecord, memoryScoresFile, memoryUsageLogFile, runtimeFile } from "./shared.js";
|
|
5
|
-
import { withFileLock } from "./governance
|
|
5
|
+
import { withFileLock, isFiniteNumber, hasValidSchemaVersion } from "./shared-governance.js";
|
|
6
6
|
import { errorMessage } from "./utils.js";
|
|
7
7
|
const GOVERNANCE_SCHEMA_VERSION = 1;
|
|
8
8
|
const DEFAULT_MEMORY_SCORES_FILE = {
|
|
@@ -15,12 +15,6 @@ function usageLogFile(phrenPath) {
|
|
|
15
15
|
function scoresJournalFile(phrenPath) {
|
|
16
16
|
return runtimeFile(phrenPath, "scores.jsonl");
|
|
17
17
|
}
|
|
18
|
-
function hasValidSchemaVersion(data) {
|
|
19
|
-
return !("schemaVersion" in data) || typeof data.schemaVersion === "number";
|
|
20
|
-
}
|
|
21
|
-
function isFiniteNumber(value) {
|
|
22
|
-
return typeof value === "number" && Number.isFinite(value);
|
|
23
|
-
}
|
|
24
18
|
function isEntryScore(value) {
|
|
25
19
|
if (!isRecord(value))
|
|
26
20
|
return false;
|
|
@@ -120,7 +114,7 @@ function readScoreJournal(phrenPath) {
|
|
|
120
114
|
return JSON.parse(line);
|
|
121
115
|
}
|
|
122
116
|
catch (err) {
|
|
123
|
-
if ((process.env.PHREN_DEBUG
|
|
117
|
+
if ((process.env.PHREN_DEBUG))
|
|
124
118
|
process.stderr.write(`[phren] readScoreJournal parseLine: ${errorMessage(err)}\n`);
|
|
125
119
|
return null;
|
|
126
120
|
}
|
|
@@ -153,7 +147,7 @@ function claimScoreJournal(phrenPath) {
|
|
|
153
147
|
return JSON.parse(line);
|
|
154
148
|
}
|
|
155
149
|
catch (err) {
|
|
156
|
-
if ((process.env.PHREN_DEBUG
|
|
150
|
+
if ((process.env.PHREN_DEBUG))
|
|
157
151
|
process.stderr.write(`[phren] claimScoreJournal parseLine: ${errorMessage(err)}\n`);
|
|
158
152
|
return null;
|
|
159
153
|
}
|
|
@@ -169,7 +163,7 @@ function claimScoreJournal(phrenPath) {
|
|
|
169
163
|
fs.unlinkSync(claimedFile);
|
|
170
164
|
}
|
|
171
165
|
catch (err) {
|
|
172
|
-
if ((process.env.PHREN_DEBUG
|
|
166
|
+
if ((process.env.PHREN_DEBUG))
|
|
173
167
|
process.stderr.write(`[phren] claimScoreJournal unlinkClaim: ${errorMessage(err)}\n`);
|
|
174
168
|
}
|
|
175
169
|
}
|
package/mcp/dist/hooks.js
CHANGED
|
@@ -1,18 +1,12 @@
|
|
|
1
1
|
import * as fs from "fs";
|
|
2
2
|
import * as path from "path";
|
|
3
|
-
import { createHmac
|
|
3
|
+
import { createHmac } from "crypto";
|
|
4
4
|
import { execFileSync } from "child_process";
|
|
5
5
|
import { fileURLToPath } from "url";
|
|
6
|
-
import { EXEC_TIMEOUT_QUICK_MS, PhrenError, debugLog, runtimeFile, homePath, installPreferencesFile } from "./shared.js";
|
|
6
|
+
import { EXEC_TIMEOUT_QUICK_MS, PhrenError, debugLog, runtimeFile, homePath, installPreferencesFile, atomicWriteText } from "./shared.js";
|
|
7
7
|
import { errorMessage } from "./utils.js";
|
|
8
8
|
import { hookConfigPath } from "./provider-adapters.js";
|
|
9
9
|
import { PACKAGE_SPEC } from "./package-metadata.js";
|
|
10
|
-
function atomicWriteText(filePath, content) {
|
|
11
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
12
|
-
const tmpPath = `${filePath}.tmp-${randomUUID()}`;
|
|
13
|
-
fs.writeFileSync(tmpPath, content);
|
|
14
|
-
fs.renameSync(tmpPath, filePath);
|
|
15
|
-
}
|
|
16
10
|
export function commandExists(cmd) {
|
|
17
11
|
try {
|
|
18
12
|
const whichCmd = process.platform === "win32" ? "where.exe" : "which";
|
|
@@ -215,10 +209,34 @@ function validateCodexConfig(config) {
|
|
|
215
209
|
Array.isArray(config.hooks?.UserPromptSubmit) &&
|
|
216
210
|
Array.isArray(config.hooks?.Stop));
|
|
217
211
|
}
|
|
212
|
+
// ── mtime-based install-preferences cache (shared by readHookPreferences + readCustomHooks) ──
|
|
213
|
+
const _installPrefsJsonCache = new Map();
|
|
214
|
+
export function clearHookPrefsCache() {
|
|
215
|
+
_installPrefsJsonCache.clear();
|
|
216
|
+
}
|
|
217
|
+
function cachedReadInstallPrefsJson(phrenPath) {
|
|
218
|
+
const prefsPath = installPreferencesFile(phrenPath);
|
|
219
|
+
let mtimeMs;
|
|
220
|
+
try {
|
|
221
|
+
mtimeMs = fs.statSync(prefsPath).mtimeMs;
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
_installPrefsJsonCache.delete(prefsPath);
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
const cached = _installPrefsJsonCache.get(prefsPath);
|
|
228
|
+
if (cached && cached.mtimeMs === mtimeMs) {
|
|
229
|
+
return cached.parsed;
|
|
230
|
+
}
|
|
231
|
+
const parsed = JSON.parse(fs.readFileSync(prefsPath, "utf8"));
|
|
232
|
+
_installPrefsJsonCache.set(prefsPath, { mtimeMs, parsed });
|
|
233
|
+
return parsed;
|
|
234
|
+
}
|
|
218
235
|
function readHookPreferences(phrenPath) {
|
|
219
236
|
try {
|
|
220
|
-
const
|
|
221
|
-
|
|
237
|
+
const prefs = cachedReadInstallPrefsJson(phrenPath);
|
|
238
|
+
if (!prefs)
|
|
239
|
+
return { enabled: true, toolPrefs: {} };
|
|
222
240
|
const enabled = prefs.hooksEnabled !== false;
|
|
223
241
|
const toolPrefs = prefs.hookTools && typeof prefs.hookTools === "object"
|
|
224
242
|
? prefs.hookTools
|
|
@@ -250,20 +268,38 @@ const VALID_HOOK_EVENTS = new Set(HOOK_EVENT_VALUES);
|
|
|
250
268
|
export function getHookTarget(h) {
|
|
251
269
|
return "webhook" in h ? h.webhook : h.command;
|
|
252
270
|
}
|
|
271
|
+
/** Re-validate a command hook at execution time (mirrors mcp-hooks.ts validateHookCommand). */
|
|
272
|
+
function validateCommandAtExecution(command) {
|
|
273
|
+
const trimmed = command.trim();
|
|
274
|
+
if (!trimmed)
|
|
275
|
+
return "Command cannot be empty.";
|
|
276
|
+
if (trimmed.length > 1000)
|
|
277
|
+
return "Command too long (max 1000 characters).";
|
|
278
|
+
if (/[`$(){}&|;<>]/.test(trimmed))
|
|
279
|
+
return "Command contains disallowed shell characters.";
|
|
280
|
+
if (/\b(eval|source)\b/.test(trimmed))
|
|
281
|
+
return "eval and source are not permitted in hook commands.";
|
|
282
|
+
if (!/^[\w./~"'"]/.test(trimmed))
|
|
283
|
+
return "Command must begin with an executable name or path.";
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
253
286
|
const DEFAULT_CUSTOM_HOOK_TIMEOUT = 5000;
|
|
254
287
|
const HOOK_TIMEOUT_MS = parseInt(process.env.PHREN_HOOK_TIMEOUT_MS || '14000', 10);
|
|
255
288
|
const HOOK_ERROR_LOG_MAX_LINES = 1000;
|
|
256
289
|
export function readCustomHooks(phrenPath) {
|
|
257
290
|
try {
|
|
258
|
-
const
|
|
259
|
-
|
|
260
|
-
if (!Array.isArray(prefs.customHooks))
|
|
291
|
+
const prefs = cachedReadInstallPrefsJson(phrenPath);
|
|
292
|
+
if (!prefs || !Array.isArray(prefs.customHooks))
|
|
261
293
|
return [];
|
|
262
|
-
return prefs.customHooks.filter((h) =>
|
|
263
|
-
typeof h
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
294
|
+
return prefs.customHooks.filter((h) => {
|
|
295
|
+
if (!h || typeof h !== "object")
|
|
296
|
+
return false;
|
|
297
|
+
const rec = h;
|
|
298
|
+
return (typeof rec.event === "string" &&
|
|
299
|
+
VALID_HOOK_EVENTS.has(rec.event) &&
|
|
300
|
+
((typeof rec.command === "string" && rec.command.trim().length > 0) ||
|
|
301
|
+
(typeof rec.webhook === "string" && rec.webhook.trim().length > 0)));
|
|
302
|
+
});
|
|
267
303
|
}
|
|
268
304
|
catch (err) {
|
|
269
305
|
debugLog(`readCustomHooks: ${errorMessage(err)}`);
|
|
@@ -321,6 +357,14 @@ export function runCustomHooks(phrenPath, event, env = {}) {
|
|
|
321
357
|
});
|
|
322
358
|
continue;
|
|
323
359
|
}
|
|
360
|
+
const cmdErr = validateCommandAtExecution(hook.command);
|
|
361
|
+
if (cmdErr) {
|
|
362
|
+
const message = `${event}: skipped hook (re-validation failed): ${cmdErr}`;
|
|
363
|
+
debugLog(`runCustomHooks: ${message}`);
|
|
364
|
+
errors.push({ code: PhrenError.VALIDATION_ERROR, message });
|
|
365
|
+
appendHookErrorLog(phrenPath, event, message);
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
324
368
|
const shellArgs = isWindows ? ["/c", hook.command] : ["-c", hook.command];
|
|
325
369
|
try {
|
|
326
370
|
execFileSync(shellCmd, shellArgs, {
|
package/mcp/dist/index.js
CHANGED
|
@@ -48,13 +48,13 @@ function cleanStaleLocks(phrenPath) {
|
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
catch (err) {
|
|
51
|
-
if ((process.env.PHREN_DEBUG
|
|
51
|
+
if ((process.env.PHREN_DEBUG))
|
|
52
52
|
process.stderr.write(`[phren] cleanStaleLocks statFile: ${errorMessage(err)}\n`);
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
catch (err) {
|
|
57
|
-
if ((process.env.PHREN_DEBUG
|
|
57
|
+
if ((process.env.PHREN_DEBUG))
|
|
58
58
|
process.stderr.write(`[phren] cleanStaleLocks readdir: ${errorMessage(err)}\n`);
|
|
59
59
|
}
|
|
60
60
|
}
|
|
@@ -88,7 +88,7 @@ async function main() {
|
|
|
88
88
|
db?.close();
|
|
89
89
|
}
|
|
90
90
|
catch (err) {
|
|
91
|
-
if ((process.env.PHREN_DEBUG
|
|
91
|
+
if ((process.env.PHREN_DEBUG))
|
|
92
92
|
process.stderr.write(`[phren] rebuildIndex dbClose: ${errorMessage(err)}\n`);
|
|
93
93
|
}
|
|
94
94
|
db = await buildIndex(phrenPath, profile);
|
|
@@ -158,7 +158,7 @@ async function main() {
|
|
|
158
158
|
trackToolCall(phrenPath, registeredName);
|
|
159
159
|
}
|
|
160
160
|
catch (err) {
|
|
161
|
-
if ((process.env.PHREN_DEBUG
|
|
161
|
+
if ((process.env.PHREN_DEBUG))
|
|
162
162
|
process.stderr.write(`[phren] trackToolCall: ${errorMessage(err)}\n`);
|
|
163
163
|
}
|
|
164
164
|
return handler(...args);
|
package/mcp/dist/init-config.js
CHANGED
|
@@ -4,27 +4,16 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import * as fs from "fs";
|
|
6
6
|
import * as path from "path";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { buildLifecycleCommands } from "./hooks.js";
|
|
10
|
-
import { EXEC_TIMEOUT_QUICK_MS, isRecord, hookConfigPath, homePath, readRootManifest, } from "./shared.js";
|
|
7
|
+
import { buildLifecycleCommands, commandExists } from "./hooks.js";
|
|
8
|
+
import { isRecord, hookConfigPath, homePath, readRootManifest, atomicWriteText, } from "./shared.js";
|
|
11
9
|
import { isFeatureEnabled, errorMessage } from "./utils.js";
|
|
12
10
|
import { probeVsCodeConfig, resolveCodexMcpConfig, resolveCopilotMcpConfig, resolveCursorMcpConfig, } from "./provider-adapters.js";
|
|
13
11
|
import { getMcpEnabledPreference, getHooksEnabledPreference } from "./init-preferences.js";
|
|
14
|
-
import { resolveEntryScript, VERSION } from "./init-shared.js";
|
|
15
|
-
function log(msg) {
|
|
16
|
-
process.stdout.write(msg + "\n");
|
|
17
|
-
}
|
|
12
|
+
import { resolveEntryScript, log, VERSION } from "./init-shared.js";
|
|
18
13
|
function getObjectProp(value, key) {
|
|
19
14
|
const candidate = value[key];
|
|
20
15
|
return isRecord(candidate) ? candidate : undefined;
|
|
21
16
|
}
|
|
22
|
-
function atomicWriteText(filePath, content) {
|
|
23
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
24
|
-
const tmpPath = `${filePath}.tmp-${randomUUID()}`;
|
|
25
|
-
fs.writeFileSync(tmpPath, content);
|
|
26
|
-
fs.renameSync(tmpPath, filePath);
|
|
27
|
-
}
|
|
28
17
|
export function patchJsonFile(filePath, patch) {
|
|
29
18
|
let data = {};
|
|
30
19
|
if (fs.existsSync(filePath)) {
|
|
@@ -44,16 +33,6 @@ export function patchJsonFile(filePath, patch) {
|
|
|
44
33
|
patch(data);
|
|
45
34
|
atomicWriteText(filePath, JSON.stringify(data, null, 2) + "\n");
|
|
46
35
|
}
|
|
47
|
-
function commandExists(cmd) {
|
|
48
|
-
try {
|
|
49
|
-
const whichCmd = process.platform === "win32" ? "where.exe" : "which";
|
|
50
|
-
execFileSync(whichCmd, [cmd], { stdio: ["ignore", "ignore", "ignore"], timeout: EXEC_TIMEOUT_QUICK_MS });
|
|
51
|
-
return true;
|
|
52
|
-
}
|
|
53
|
-
catch {
|
|
54
|
-
return false;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
36
|
function buildMcpServerConfig(phrenPath) {
|
|
58
37
|
const entryScript = resolveEntryScript();
|
|
59
38
|
if (entryScript && fs.existsSync(entryScript)) {
|
|
@@ -228,7 +207,7 @@ export function configureClaude(phrenPath, opts = {}) {
|
|
|
228
207
|
eventHooks.push({ matcher: "", hooks: [hookBody] });
|
|
229
208
|
}
|
|
230
209
|
};
|
|
231
|
-
const toolHookEnabled = hooksEnabled &&
|
|
210
|
+
const toolHookEnabled = hooksEnabled && isFeatureEnabled("PHREN_FEATURE_TOOL_HOOK", false);
|
|
232
211
|
if (hooksEnabled) {
|
|
233
212
|
upsertPhrenHook("UserPromptSubmit", {
|
|
234
213
|
type: "command",
|
|
@@ -6,7 +6,7 @@ import * as path from "path";
|
|
|
6
6
|
import * as crypto from "crypto";
|
|
7
7
|
import { debugLog, installPreferencesFile } from "./phren-paths.js";
|
|
8
8
|
import { errorMessage } from "./utils.js";
|
|
9
|
-
import { withFileLock } from "./governance
|
|
9
|
+
import { withFileLock } from "./shared-governance.js";
|
|
10
10
|
function preferencesFile(phrenPath) {
|
|
11
11
|
return installPreferencesFile(phrenPath);
|
|
12
12
|
}
|
package/mcp/dist/init-setup.js
CHANGED
|
@@ -11,7 +11,7 @@ import { getMachineName } from "./machine-identity.js";
|
|
|
11
11
|
import { execFileSync } from "child_process";
|
|
12
12
|
import { GOVERNANCE_SCHEMA_VERSION, } from "./shared-governance.js";
|
|
13
13
|
import { STOP_WORDS, errorMessage } from "./utils.js";
|
|
14
|
-
import { ROOT, STARTER_DIR, VERSION, resolveEntryScript } from "./init-shared.js";
|
|
14
|
+
import { ROOT, STARTER_DIR, VERSION, resolveEntryScript, commandVersion, versionAtLeast, nearestWritableTarget } from "./init-shared.js";
|
|
15
15
|
import { readInstallPreferences } from "./init-preferences.js";
|
|
16
16
|
import { TASKS_FILENAME } from "./data-tasks.js";
|
|
17
17
|
import { getProjectOwnershipDefault, parseProjectOwnershipMode, readProjectConfig, writeProjectConfig, } from "./project-config.js";
|
|
@@ -311,21 +311,6 @@ export function getVerifyOutcomeNote(phrenPath, checks) {
|
|
|
311
311
|
}
|
|
312
312
|
return "Some reported issues are optional for your chosen install mode; review git-remote / MCP failures separately from hard failures.";
|
|
313
313
|
}
|
|
314
|
-
function commandVersion(cmd, args = ["--version"]) {
|
|
315
|
-
const effectiveCmd = process.platform === "win32" && (cmd === "npm" || cmd === "npx") ? `${cmd}.cmd` : cmd;
|
|
316
|
-
try {
|
|
317
|
-
return execFileSync(effectiveCmd, args, {
|
|
318
|
-
encoding: "utf8",
|
|
319
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
320
|
-
shell: process.platform === "win32" && effectiveCmd.endsWith(".cmd"),
|
|
321
|
-
timeout: EXEC_TIMEOUT_QUICK_MS,
|
|
322
|
-
}).trim();
|
|
323
|
-
}
|
|
324
|
-
catch (err) {
|
|
325
|
-
debugLog(`commandVersion ${effectiveCmd} failed: ${errorMessage(err)}`);
|
|
326
|
-
return null;
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
314
|
export function getHookEntrypointCheck(deps = {}) {
|
|
330
315
|
const pathExists = deps.pathExists ?? fs.existsSync;
|
|
331
316
|
const versionReader = deps.versionReader ?? commandVersion;
|
|
@@ -344,40 +329,6 @@ export function getHookEntrypointCheck(deps = {}) {
|
|
|
344
329
|
fix: hookEntrypointOk ? undefined : "Rebuild phren: `npm run build` or reinstall the package, and ensure npm/npx is available for hook fallbacks",
|
|
345
330
|
};
|
|
346
331
|
}
|
|
347
|
-
function parseSemverTriple(raw) {
|
|
348
|
-
const match = raw.match(/(\d+)\.(\d+)\.(\d+)/);
|
|
349
|
-
if (!match)
|
|
350
|
-
return null;
|
|
351
|
-
return [Number.parseInt(match[1], 10), Number.parseInt(match[2], 10), Number.parseInt(match[3], 10)];
|
|
352
|
-
}
|
|
353
|
-
function versionAtLeast(raw, major, minor = 0) {
|
|
354
|
-
if (!raw)
|
|
355
|
-
return false;
|
|
356
|
-
const parsed = parseSemverTriple(raw);
|
|
357
|
-
if (!parsed)
|
|
358
|
-
return false;
|
|
359
|
-
const [m, n] = parsed;
|
|
360
|
-
if (m !== major)
|
|
361
|
-
return m > major;
|
|
362
|
-
return n >= minor;
|
|
363
|
-
}
|
|
364
|
-
function nearestWritableTarget(filePath) {
|
|
365
|
-
let probe = fs.existsSync(filePath) ? filePath : path.dirname(filePath);
|
|
366
|
-
while (!fs.existsSync(probe)) {
|
|
367
|
-
const parent = path.dirname(probe);
|
|
368
|
-
if (parent === probe)
|
|
369
|
-
return false;
|
|
370
|
-
probe = parent;
|
|
371
|
-
}
|
|
372
|
-
try {
|
|
373
|
-
fs.accessSync(probe, fs.constants.W_OK);
|
|
374
|
-
return true;
|
|
375
|
-
}
|
|
376
|
-
catch (err) {
|
|
377
|
-
debugLog(`nearestWritableTarget failed for ${filePath}: ${errorMessage(err)}`);
|
|
378
|
-
return false;
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
332
|
function gitRemoteStatus(phrenPath) {
|
|
382
333
|
try {
|
|
383
334
|
execFileSync("git", ["-C", phrenPath, "rev-parse", "--is-inside-work-tree"], {
|
|
@@ -1087,14 +1038,14 @@ export function updateMachinesYaml(phrenPath, machine, profile) {
|
|
|
1087
1038
|
}
|
|
1088
1039
|
}
|
|
1089
1040
|
catch (err) {
|
|
1090
|
-
if ((process.env.PHREN_DEBUG
|
|
1091
|
-
process.stderr.write(`[phren] updateMachinesYaml parse: ${
|
|
1041
|
+
if ((process.env.PHREN_DEBUG))
|
|
1042
|
+
process.stderr.write(`[phren] updateMachinesYaml parse: ${errorMessage(err)}\n`);
|
|
1092
1043
|
}
|
|
1093
1044
|
// Passive init/link refreshes should keep an existing mapping; explicit overrides can remap.
|
|
1094
1045
|
if (hasExistingMapping && !machine && !profile)
|
|
1095
1046
|
return;
|
|
1096
1047
|
const mapping = setMachineProfile(phrenPath, machineName, profileName);
|
|
1097
|
-
if (!mapping.ok && (process.env.PHREN_DEBUG
|
|
1048
|
+
if (!mapping.ok && (process.env.PHREN_DEBUG)) {
|
|
1098
1049
|
process.stderr.write(`[phren] updateMachinesYaml setMachineProfile: ${mapping.error}\n`);
|
|
1099
1050
|
}
|
|
1100
1051
|
}
|
|
@@ -1289,8 +1240,8 @@ export function runPostInitVerify(phrenPath) {
|
|
|
1289
1240
|
ftsOk = entries.some(d => d.isDirectory() && !d.name.startsWith("."));
|
|
1290
1241
|
}
|
|
1291
1242
|
catch (err) {
|
|
1292
|
-
if ((process.env.PHREN_DEBUG
|
|
1293
|
-
process.stderr.write(`[phren] runPostInitVerify projectScan: ${
|
|
1243
|
+
if ((process.env.PHREN_DEBUG))
|
|
1244
|
+
process.stderr.write(`[phren] runPostInitVerify projectScan: ${errorMessage(err)}\n`);
|
|
1294
1245
|
ftsOk = false;
|
|
1295
1246
|
}
|
|
1296
1247
|
checks.push({
|
package/mcp/dist/init-shared.js
CHANGED
|
@@ -2,8 +2,11 @@
|
|
|
2
2
|
* Shared constants and utilities for init modules.
|
|
3
3
|
* Kept separate to avoid circular dependencies between init-config and init-setup.
|
|
4
4
|
*/
|
|
5
|
+
import * as fs from "fs";
|
|
5
6
|
import * as path from "path";
|
|
6
|
-
import {
|
|
7
|
+
import { execFileSync } from "child_process";
|
|
8
|
+
import { homePath, EXEC_TIMEOUT_QUICK_MS, debugLog } from "./shared.js";
|
|
9
|
+
import { errorMessage } from "./utils.js";
|
|
7
10
|
import { ROOT as PACKAGE_ROOT, VERSION } from "./package-metadata.js";
|
|
8
11
|
export const ROOT = PACKAGE_ROOT;
|
|
9
12
|
export { VERSION };
|
|
@@ -15,6 +18,55 @@ export function resolveEntryScript() {
|
|
|
15
18
|
export function log(msg) {
|
|
16
19
|
process.stdout.write(msg + "\n");
|
|
17
20
|
}
|
|
21
|
+
export function commandVersion(cmd, args = ["--version"]) {
|
|
22
|
+
const effectiveCmd = process.platform === "win32" && (cmd === "npm" || cmd === "npx") ? `${cmd}.cmd` : cmd;
|
|
23
|
+
try {
|
|
24
|
+
return execFileSync(effectiveCmd, args, {
|
|
25
|
+
encoding: "utf8",
|
|
26
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
27
|
+
shell: process.platform === "win32" && effectiveCmd.endsWith(".cmd"),
|
|
28
|
+
timeout: EXEC_TIMEOUT_QUICK_MS,
|
|
29
|
+
}).trim();
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
debugLog(`commandVersion ${effectiveCmd} failed: ${errorMessage(err)}`);
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export function parseSemverTriple(raw) {
|
|
37
|
+
const match = raw.match(/(\d+)\.(\d+)\.(\d+)/);
|
|
38
|
+
if (!match)
|
|
39
|
+
return null;
|
|
40
|
+
return [Number.parseInt(match[1], 10), Number.parseInt(match[2], 10), Number.parseInt(match[3], 10)];
|
|
41
|
+
}
|
|
42
|
+
export function versionAtLeast(raw, major, minor = 0) {
|
|
43
|
+
if (!raw)
|
|
44
|
+
return false;
|
|
45
|
+
const parsed = parseSemverTriple(raw);
|
|
46
|
+
if (!parsed)
|
|
47
|
+
return false;
|
|
48
|
+
const [m, n] = parsed;
|
|
49
|
+
if (m !== major)
|
|
50
|
+
return m > major;
|
|
51
|
+
return n >= minor;
|
|
52
|
+
}
|
|
53
|
+
export function nearestWritableTarget(filePath) {
|
|
54
|
+
let probe = fs.existsSync(filePath) ? filePath : path.dirname(filePath);
|
|
55
|
+
while (!fs.existsSync(probe)) {
|
|
56
|
+
const parent = path.dirname(probe);
|
|
57
|
+
if (parent === probe)
|
|
58
|
+
return false;
|
|
59
|
+
probe = parent;
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
fs.accessSync(probe, fs.constants.W_OK);
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
debugLog(`nearestWritableTarget failed for ${filePath}: ${errorMessage(err)}`);
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
18
70
|
export async function confirmPrompt(message) {
|
|
19
71
|
if (process.env.CI === "true" || !process.stdin.isTTY)
|
|
20
72
|
return true;
|