@phren/cli 0.0.32 → 0.0.34
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/mcp/dist/cli/actions.js +3 -0
- package/mcp/dist/cli/config.js +3 -3
- package/mcp/dist/cli/govern.js +18 -8
- package/mcp/dist/cli/hooks-context.js +1 -1
- package/mcp/dist/cli/hooks-session.js +18 -62
- package/mcp/dist/cli/namespaces.js +1 -1
- package/mcp/dist/cli/search.js +5 -5
- package/mcp/dist/cli-hooks-prompt.js +7 -3
- package/mcp/dist/cli-hooks-session-handlers.js +3 -15
- package/mcp/dist/cli-hooks-stop.js +10 -48
- package/mcp/dist/content/archive.js +8 -20
- package/mcp/dist/content/learning.js +29 -8
- package/mcp/dist/data/access.js +13 -4
- package/mcp/dist/finding/lifecycle.js +9 -3
- package/mcp/dist/governance/audit.js +13 -5
- package/mcp/dist/governance/policy.js +13 -0
- package/mcp/dist/governance/rbac.js +1 -1
- package/mcp/dist/governance/scores.js +2 -1
- package/mcp/dist/hooks.js +52 -6
- package/mcp/dist/index.js +1 -1
- package/mcp/dist/init/init.js +66 -45
- package/mcp/dist/init/shared.js +1 -1
- package/mcp/dist/init-bootstrap.js +0 -47
- package/mcp/dist/init-fresh.js +13 -18
- package/mcp/dist/init-uninstall.js +22 -0
- package/mcp/dist/init-walkthrough.js +19 -24
- package/mcp/dist/link/doctor.js +9 -0
- package/mcp/dist/package-metadata.js +1 -1
- package/mcp/dist/phren-art.js +4 -120
- package/mcp/dist/proactivity.js +1 -1
- package/mcp/dist/project-topics.js +16 -46
- package/mcp/dist/provider-adapters.js +1 -1
- package/mcp/dist/runtime-profile.js +1 -1
- package/mcp/dist/shared/data-utils.js +25 -0
- package/mcp/dist/shared/fragment-graph.js +4 -18
- package/mcp/dist/shared/index.js +14 -10
- package/mcp/dist/shared/ollama.js +23 -5
- package/mcp/dist/shared/process.js +24 -0
- package/mcp/dist/shared/retrieval.js +7 -4
- package/mcp/dist/shared/search-fallback.js +1 -0
- package/mcp/dist/shared.js +2 -1
- package/mcp/dist/shell/render.js +1 -1
- package/mcp/dist/skill/registry.js +1 -1
- package/mcp/dist/skill/state.js +0 -3
- package/mcp/dist/task/github.js +1 -0
- package/mcp/dist/task/lifecycle.js +1 -6
- package/mcp/dist/tools/config.js +415 -400
- package/mcp/dist/tools/finding.js +390 -373
- package/mcp/dist/tools/ops.js +372 -365
- package/mcp/dist/tools/search.js +495 -487
- package/mcp/dist/tools/session.js +3 -2
- package/mcp/dist/tools/skills.js +9 -0
- package/mcp/dist/ui/page.js +1 -1
- package/mcp/dist/ui/server.js +645 -1040
- package/mcp/dist/utils.js +12 -8
- package/package.json +1 -1
- package/mcp/dist/init-dryrun.js +0 -55
- package/mcp/dist/init-migrate.js +0 -51
- package/mcp/dist/init-walkthrough-merge.js +0 -90
package/mcp/dist/utils.js
CHANGED
|
@@ -169,7 +169,7 @@ export const STOP_WORDS = new Set([
|
|
|
169
169
|
export function extractKeywordEntries(text) {
|
|
170
170
|
const words = text
|
|
171
171
|
.toLowerCase()
|
|
172
|
-
.replace(/[^\
|
|
172
|
+
.replace(/[^\p{L}\p{N}\s_-]/gu, " ")
|
|
173
173
|
.split(/\s+/)
|
|
174
174
|
.filter(w => w.length > 1 && !STOP_WORDS.has(w));
|
|
175
175
|
// Build bigrams from adjacent non-stop-words
|
|
@@ -254,23 +254,27 @@ export function queueFilePath(phrenPath, project) {
|
|
|
254
254
|
}
|
|
255
255
|
return result;
|
|
256
256
|
}
|
|
257
|
+
const MAX_FTS_QUERY_LENGTH = 500;
|
|
257
258
|
// Sanitize user input before passing it to an FTS5 MATCH expression.
|
|
258
259
|
// Strips FTS5-specific syntax that could cause injection or parse errors.
|
|
259
260
|
export function sanitizeFts5Query(raw) {
|
|
260
261
|
if (!raw)
|
|
261
262
|
return "";
|
|
262
|
-
if (raw.length >
|
|
263
|
-
raw = raw.slice(0,
|
|
264
|
-
// Whitelist approach: only allow alphanumeric, spaces, hyphens, apostrophes,
|
|
265
|
-
let q = raw.replace(/[
|
|
266
|
-
|
|
267
|
-
|
|
263
|
+
if (raw.length > MAX_FTS_QUERY_LENGTH)
|
|
264
|
+
raw = raw.slice(0, MAX_FTS_QUERY_LENGTH);
|
|
265
|
+
// Whitelist approach: only allow alphanumeric, spaces, hyphens, apostrophes, asterisks
|
|
266
|
+
let q = raw.replace(/[^\p{L}\p{N} \-"*]/gu, " ");
|
|
267
|
+
// Strip all double quotes — buildFtsClauses wraps terms in quotes itself,
|
|
268
|
+
// so user-supplied quotes only risk producing unbalanced FTS5 syntax.
|
|
269
|
+
q = q.replace(/"/g, "");
|
|
268
270
|
// Q83: see docs/decisions/Q83-fts5-asterisk-validation.md
|
|
269
271
|
q = q.replace(/(?<!\w)\*/g, "");
|
|
270
272
|
// Also strip a trailing asterisk that is preceded only by whitespace at word
|
|
271
273
|
// end of the whole query (handles "foo *" → "foo").
|
|
272
274
|
q = q.replace(/\s+\*$/g, "");
|
|
273
|
-
|
|
275
|
+
// Normalize spaces after all stripping to avoid double spaces from removed characters
|
|
276
|
+
q = q.replace(/\s+/g, " ").trim();
|
|
277
|
+
return q;
|
|
274
278
|
}
|
|
275
279
|
function parseSynonymsYaml(filePath) {
|
|
276
280
|
if (!fs.existsSync(filePath))
|
package/package.json
CHANGED
package/mcp/dist/init-dryrun.js
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Dry-run output for init.
|
|
3
|
-
*/
|
|
4
|
-
import * as path from "path";
|
|
5
|
-
import { getMachineName } from "./machine-identity.js";
|
|
6
|
-
import { getPendingBootstrapTarget } from "./init-detect.js";
|
|
7
|
-
import { log } from "./init/shared.js";
|
|
8
|
-
export function printDryRun(phrenPath, opts, params) {
|
|
9
|
-
const { hasExistingInstall, mcpLabel, hooksLabel, hooksEnabled, storageChoice, storageRepoRoot } = params;
|
|
10
|
-
const pendingBootstrap = getPendingBootstrapTarget(phrenPath, opts);
|
|
11
|
-
log("\nInit dry run. No files will be written.\n");
|
|
12
|
-
if (storageChoice) {
|
|
13
|
-
log(`Storage location: ${storageChoice} (${phrenPath})`);
|
|
14
|
-
if (storageChoice === "project" && storageRepoRoot) {
|
|
15
|
-
log(` Would update ${path.join(storageRepoRoot, ".gitignore")} with .phren/`);
|
|
16
|
-
log(` Would set PHREN_PATH in ${path.join(storageRepoRoot, ".env")}`);
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
if (hasExistingInstall) {
|
|
20
|
-
log(`phren install detected at ${phrenPath}`);
|
|
21
|
-
log(`Would update configuration for the existing install:\n`);
|
|
22
|
-
log(` MCP mode: ${mcpLabel}`);
|
|
23
|
-
log(` Hooks mode: ${hooksLabel}`);
|
|
24
|
-
log(` Reconfigure Claude Code MCP/hooks`);
|
|
25
|
-
log(` Reconfigure VS Code, Cursor, Copilot CLI, and Codex MCP targets`);
|
|
26
|
-
if (hooksEnabled) {
|
|
27
|
-
log(` Reconfigure lifecycle hooks for detected tools`);
|
|
28
|
-
}
|
|
29
|
-
if (pendingBootstrap?.mode === "detected") {
|
|
30
|
-
log(` Would offer to add current project directory (${pendingBootstrap.path})`);
|
|
31
|
-
}
|
|
32
|
-
if (opts.applyStarterUpdate) {
|
|
33
|
-
log(` Apply starter template updates to global/CLAUDE.md and global skills`);
|
|
34
|
-
}
|
|
35
|
-
log(` Run post-init verification checks`);
|
|
36
|
-
log(`\nDry run complete.\n`);
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
log(`No existing phren install found at ${phrenPath}`);
|
|
40
|
-
log(`Would create a new phren install:\n`);
|
|
41
|
-
log(` Copy starter files to ${phrenPath} (or create minimal structure)`);
|
|
42
|
-
log(` Update machines.yaml for machine "${opts.machine || getMachineName()}"`);
|
|
43
|
-
log(` Create/update config files`);
|
|
44
|
-
log(` MCP mode: ${mcpLabel}`);
|
|
45
|
-
log(` Hooks mode: ${hooksLabel}`);
|
|
46
|
-
log(` Configure Claude Code plus detected MCP targets (VS Code/Cursor/Copilot/Codex)`);
|
|
47
|
-
if (hooksEnabled) {
|
|
48
|
-
log(` Configure lifecycle hooks for detected tools`);
|
|
49
|
-
}
|
|
50
|
-
if (pendingBootstrap?.mode === "detected") {
|
|
51
|
-
log(` Would offer to add current project directory (${pendingBootstrap.path})`);
|
|
52
|
-
}
|
|
53
|
-
log(` Write install preferences and run post-init verification checks`);
|
|
54
|
-
log(`\nDry run complete.\n`);
|
|
55
|
-
}
|
package/mcp/dist/init-migrate.js
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Legacy migration helpers for init: directory rename, skill rename.
|
|
3
|
-
*/
|
|
4
|
-
import * as fs from "fs";
|
|
5
|
-
import * as path from "path";
|
|
6
|
-
import { homePath } from "./shared.js";
|
|
7
|
-
import { hasInstallMarkers } from "./init-detect.js";
|
|
8
|
-
/**
|
|
9
|
-
* Migrate the legacy hidden store directory into ~/.phren when upgrading
|
|
10
|
-
* from the previous product name.
|
|
11
|
-
* @returns true if migration occurred
|
|
12
|
-
*/
|
|
13
|
-
export function migrateLegacyStore(phrenPath, dryRun) {
|
|
14
|
-
const legacyPath = path.resolve(homePath(".cortex"));
|
|
15
|
-
if (legacyPath === phrenPath || !fs.existsSync(legacyPath) || !hasInstallMarkers(legacyPath)) {
|
|
16
|
-
return false;
|
|
17
|
-
}
|
|
18
|
-
if (!dryRun) {
|
|
19
|
-
fs.renameSync(legacyPath, phrenPath);
|
|
20
|
-
}
|
|
21
|
-
console.log(`Migrated legacy store → ~/.phren`);
|
|
22
|
-
return true;
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* Rename stale legacy skill names left over from the rebrand.
|
|
26
|
-
* Runs on every init so users who already migrated the directory still get the fix.
|
|
27
|
-
*/
|
|
28
|
-
export function migrateLegacySkills(phrenPath) {
|
|
29
|
-
const skillsMigrateDir = path.join(phrenPath, "global", "skills");
|
|
30
|
-
if (!fs.existsSync(skillsMigrateDir))
|
|
31
|
-
return;
|
|
32
|
-
const legacySkillName = "cortex.md";
|
|
33
|
-
const legacySkillPrefix = "cortex-";
|
|
34
|
-
for (const entry of fs.readdirSync(skillsMigrateDir)) {
|
|
35
|
-
if (!entry.endsWith(".md"))
|
|
36
|
-
continue;
|
|
37
|
-
if (entry === legacySkillName) {
|
|
38
|
-
const dest = path.join(skillsMigrateDir, "phren.md");
|
|
39
|
-
if (!fs.existsSync(dest)) {
|
|
40
|
-
fs.renameSync(path.join(skillsMigrateDir, entry), dest);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
else if (entry.startsWith(legacySkillPrefix)) {
|
|
44
|
-
const newName = `phren-${entry.slice(legacySkillPrefix.length)}`;
|
|
45
|
-
const dest = path.join(skillsMigrateDir, newName);
|
|
46
|
-
if (!fs.existsSync(dest)) {
|
|
47
|
-
fs.renameSync(path.join(skillsMigrateDir, entry), dest);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
}
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Merge walkthrough answers back into InitOptions.
|
|
3
|
-
*/
|
|
4
|
-
import { execFileSync } from "child_process";
|
|
5
|
-
import { runWalkthrough } from "./init-walkthrough.js";
|
|
6
|
-
import { resolveInitPhrenPath, hasInstallMarkers } from "./init-detect.js";
|
|
7
|
-
import { log } from "./init/shared.js";
|
|
8
|
-
/**
|
|
9
|
-
* Run the interactive walkthrough for first-time installs, merging answers into opts.
|
|
10
|
-
* Mutates opts in-place and returns the (possibly updated) phrenPath and hasExistingInstall.
|
|
11
|
-
*/
|
|
12
|
-
export async function mergeWalkthroughAnswers(phrenPath, hasExisting, opts) {
|
|
13
|
-
const answers = await runWalkthrough(phrenPath);
|
|
14
|
-
opts._walkthroughStorageChoice = answers.storageChoice;
|
|
15
|
-
opts._walkthroughStoragePath = answers.storagePath;
|
|
16
|
-
opts._walkthroughStorageRepoRoot = answers.storageRepoRoot;
|
|
17
|
-
const newPhrenPath = resolveInitPhrenPath(opts);
|
|
18
|
-
const newHasExisting = hasInstallMarkers(newPhrenPath);
|
|
19
|
-
opts.machine = opts.machine || answers.machine;
|
|
20
|
-
opts.profile = opts.profile || answers.profile;
|
|
21
|
-
opts.mcp = opts.mcp || answers.mcp;
|
|
22
|
-
opts.hooks = opts.hooks || answers.hooks;
|
|
23
|
-
opts.projectOwnershipDefault = opts.projectOwnershipDefault || answers.projectOwnershipDefault;
|
|
24
|
-
opts.findingsProactivity = opts.findingsProactivity || answers.findingsProactivity;
|
|
25
|
-
opts.taskProactivity = opts.taskProactivity || answers.taskProactivity;
|
|
26
|
-
if (typeof opts.lowConfidenceThreshold !== "number")
|
|
27
|
-
opts.lowConfidenceThreshold = answers.lowConfidenceThreshold;
|
|
28
|
-
if (!Array.isArray(opts.riskySections))
|
|
29
|
-
opts.riskySections = answers.riskySections;
|
|
30
|
-
opts.taskMode = opts.taskMode || answers.taskMode;
|
|
31
|
-
if (answers.cloneUrl) {
|
|
32
|
-
opts._walkthroughCloneUrl = answers.cloneUrl;
|
|
33
|
-
}
|
|
34
|
-
if (answers.githubRepo) {
|
|
35
|
-
opts._walkthroughGithub = { username: answers.githubUsername, repo: answers.githubRepo };
|
|
36
|
-
}
|
|
37
|
-
opts._walkthroughDomain = answers.domain;
|
|
38
|
-
if (answers.inferredScaffold) {
|
|
39
|
-
opts._walkthroughInferredScaffold = answers.inferredScaffold;
|
|
40
|
-
}
|
|
41
|
-
if (!answers.ollamaEnabled) {
|
|
42
|
-
process.env._PHREN_WALKTHROUGH_OLLAMA_SKIP = "1";
|
|
43
|
-
}
|
|
44
|
-
else {
|
|
45
|
-
opts._walkthroughSemanticSearch = true;
|
|
46
|
-
}
|
|
47
|
-
opts._walkthroughAutoCapture = answers.autoCaptureEnabled;
|
|
48
|
-
if (answers.semanticDedupEnabled) {
|
|
49
|
-
opts._walkthroughSemanticDedup = true;
|
|
50
|
-
}
|
|
51
|
-
if (answers.semanticConflictEnabled) {
|
|
52
|
-
opts._walkthroughSemanticConflict = true;
|
|
53
|
-
}
|
|
54
|
-
if (answers.findingSensitivity && answers.findingSensitivity !== "balanced") {
|
|
55
|
-
opts.findingSensitivity = answers.findingSensitivity;
|
|
56
|
-
}
|
|
57
|
-
opts._walkthroughBootstrapCurrentProject = answers.bootstrapCurrentProject;
|
|
58
|
-
opts._walkthroughBootstrapOwnership = answers.bootstrapOwnership;
|
|
59
|
-
return { phrenPath: newPhrenPath, hasExistingInstall: newHasExisting };
|
|
60
|
-
}
|
|
61
|
-
/**
|
|
62
|
-
* Clone an existing phren from a remote URL.
|
|
63
|
-
* @returns true if the clone succeeded (existing install), false otherwise
|
|
64
|
-
*/
|
|
65
|
-
export function cloneExistingPhren(phrenPath, cloneUrl) {
|
|
66
|
-
log(`\nCloning existing phren from ${cloneUrl}...`);
|
|
67
|
-
try {
|
|
68
|
-
execFileSync("git", ["clone", cloneUrl, phrenPath], {
|
|
69
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
70
|
-
timeout: 60_000,
|
|
71
|
-
});
|
|
72
|
-
log(` Cloned to ${phrenPath}`);
|
|
73
|
-
return true;
|
|
74
|
-
}
|
|
75
|
-
catch (e) {
|
|
76
|
-
log(` Clone failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
77
|
-
log("");
|
|
78
|
-
log(" ┌──────────────────────────────────────────────────────────────────┐");
|
|
79
|
-
log(" │ WARNING: Sync is NOT configured. Your phren data is local-only. │");
|
|
80
|
-
log(" │ │");
|
|
81
|
-
log(" │ To fix later: │");
|
|
82
|
-
log(` │ cd ${phrenPath}`);
|
|
83
|
-
log(" │ git remote add origin <YOUR_REPO_URL> │");
|
|
84
|
-
log(" │ git push -u origin main │");
|
|
85
|
-
log(" └──────────────────────────────────────────────────────────────────┘");
|
|
86
|
-
log("");
|
|
87
|
-
log(` Continuing with fresh local-only install.`);
|
|
88
|
-
return false;
|
|
89
|
-
}
|
|
90
|
-
}
|