@phren/cli 0.0.42 → 0.0.43
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 +1 -1
- package/mcp/dist/cli/hooks-session.js +8 -8
- package/mcp/dist/cli/namespaces.js +3 -4
- package/mcp/dist/cli-hooks-session-handlers.js +8 -8
- package/mcp/dist/content/dedup.js +5 -2
- package/mcp/dist/entrypoint.js +3 -3
- package/mcp/dist/finding/context.js +3 -2
- package/mcp/dist/init/config.js +1 -1
- package/mcp/dist/init/init-configure.js +1 -1
- package/mcp/dist/init/init-hooks-mode.js +1 -1
- package/mcp/dist/init/init-mcp-mode.js +2 -2
- package/mcp/dist/init/init-walkthrough.js +9 -9
- package/mcp/dist/init/init.js +8 -8
- package/mcp/dist/init/setup.js +6 -0
- package/mcp/dist/init-fresh.js +4 -4
- package/mcp/dist/init-hooks.js +1 -1
- package/mcp/dist/init-modes.js +3 -3
- package/mcp/dist/init-update.js +3 -3
- package/mcp/dist/init-walkthrough.js +9 -9
- package/mcp/dist/link/link.js +1 -1
- package/mcp/dist/phren-paths.js +2 -2
- package/mcp/dist/profile-store.js +2 -2
- package/mcp/dist/status.js +1 -1
- package/mcp/dist/tools/finding.js +2 -2
- package/mcp/dist/tools/session.js +10 -4
- package/mcp/dist/tools/tasks.js +60 -1
- package/package.json +1 -1
- package/skills/sync/SKILL.md +1 -1
- package/starter/README.md +6 -6
- package/starter/machines.yaml +1 -1
- package/starter/my-first-project/tasks.md +1 -1
- package/starter/templates/README.md +1 -1
package/mcp/dist/cli/actions.js
CHANGED
|
@@ -291,7 +291,7 @@ export async function handleUpdate(args) {
|
|
|
291
291
|
process.exitCode = 1;
|
|
292
292
|
}
|
|
293
293
|
else {
|
|
294
|
-
console.log("Run '
|
|
294
|
+
console.log("Run 'phren init' to refresh hooks and config.");
|
|
295
295
|
}
|
|
296
296
|
}
|
|
297
297
|
export async function handleReview(args) {
|
|
@@ -86,13 +86,13 @@ export function getUntrackedProjectNotice(phrenPath, cwd) {
|
|
|
86
86
|
return [
|
|
87
87
|
"<phren-notice>",
|
|
88
88
|
"This project directory is not tracked by phren yet.",
|
|
89
|
-
"Run `
|
|
90
|
-
`Suggested command: \`
|
|
89
|
+
"Run `phren add` to track it now.",
|
|
90
|
+
`Suggested command: \`phren add \"${projectDir}\"\``,
|
|
91
91
|
"Ask the user whether they want to add it to phren now.",
|
|
92
|
-
"If they say no, tell them they can always run `
|
|
92
|
+
"If they say no, tell them they can always run `phren add` later.",
|
|
93
93
|
"If they say yes, also ask whether phren should manage repo instruction files or leave their existing repo-owned CLAUDE/AGENTS files alone.",
|
|
94
|
-
`Then use the \`add_project\` MCP tool with path="${projectDir}" and ownership="phren-managed"|"detached"|"repo-managed", or run \`
|
|
95
|
-
"After onboarding, run `
|
|
94
|
+
`Then use the \`add_project\` MCP tool with path="${projectDir}" and ownership="phren-managed"|"detached"|"repo-managed", or run \`phren add\` from that directory.`,
|
|
95
|
+
"After onboarding, run `phren doctor` if hooks or MCP tools are not responding.",
|
|
96
96
|
"<phren-notice>",
|
|
97
97
|
"",
|
|
98
98
|
].join("\n");
|
|
@@ -127,8 +127,8 @@ export function getSessionStartOnboardingNotice(phrenPath, cwd, activeProject) {
|
|
|
127
127
|
return [
|
|
128
128
|
"<phren-notice>",
|
|
129
129
|
"Phren onboarding: no tracked projects are active for this workspace yet.",
|
|
130
|
-
"Start in a project repo and run `
|
|
131
|
-
"Run `
|
|
130
|
+
"Start in a project repo and run `phren add` so SessionStart can inject project context.",
|
|
131
|
+
"Run `phren doctor` to verify hooks and MCP wiring after setup.",
|
|
132
132
|
"<phren-notice>",
|
|
133
133
|
"",
|
|
134
134
|
].join("\n");
|
|
@@ -141,7 +141,7 @@ export function getSessionStartOnboardingNotice(phrenPath, cwd, activeProject) {
|
|
|
141
141
|
"<phren-notice>",
|
|
142
142
|
`Phren onboarding: project "${activeProject}" is tracked but memory is still empty.`,
|
|
143
143
|
"Capture one finding with `add_finding` and one task with `add_task` to seed future SessionStart context.",
|
|
144
|
-
"Run `
|
|
144
|
+
"Run `phren doctor` if setup seems incomplete.",
|
|
145
145
|
"<phren-notice>",
|
|
146
146
|
"",
|
|
147
147
|
].join("\n");
|
|
@@ -594,7 +594,7 @@ export async function handleProjectsNamespace(args, profile) {
|
|
|
594
594
|
}
|
|
595
595
|
if (subcommand === "add") {
|
|
596
596
|
console.error("`phren projects add` has been removed from the supported workflow.");
|
|
597
|
-
console.error("Use `cd ~/your-project &&
|
|
597
|
+
console.error("Use `cd ~/your-project && phren add` so enrollment stays path-based.");
|
|
598
598
|
process.exit(1);
|
|
599
599
|
}
|
|
600
600
|
if (subcommand === "remove") {
|
|
@@ -864,7 +864,7 @@ function handleProjectsList(profile) {
|
|
|
864
864
|
.filter((name) => name !== "global")
|
|
865
865
|
.sort();
|
|
866
866
|
if (!projects.length) {
|
|
867
|
-
console.log("No projects found. Run: cd ~/your-project &&
|
|
867
|
+
console.log("No projects found. Run: cd ~/your-project && phren add");
|
|
868
868
|
return;
|
|
869
869
|
}
|
|
870
870
|
console.log(`\nProjects in ${phrenPath}:\n`);
|
|
@@ -888,7 +888,7 @@ function handleProjectsList(profile) {
|
|
|
888
888
|
console.log(` ${name}${tagStr}`);
|
|
889
889
|
}
|
|
890
890
|
console.log(`\n${projects.length} project(s) total.`);
|
|
891
|
-
console.log("Add another project: cd ~/your-project &&
|
|
891
|
+
console.log("Add another project: cd ~/your-project && phren add");
|
|
892
892
|
}
|
|
893
893
|
async function handleProjectsRemove(name, profile) {
|
|
894
894
|
if (!isValidProjectName(name)) {
|
|
@@ -1763,7 +1763,6 @@ export async function handlePromoteNamespace(args) {
|
|
|
1763
1763
|
// Write to target store
|
|
1764
1764
|
const targetProjectDir = path.join(targetStore.path, project);
|
|
1765
1765
|
fs.mkdirSync(targetProjectDir, { recursive: true });
|
|
1766
|
-
const targetFindingsPath = path.join(targetProjectDir, "FINDINGS.md");
|
|
1767
1766
|
const { addFindingToFile } = await import("../shared/content.js");
|
|
1768
1767
|
const result = addFindingToFile(targetStore.path, project, match.text);
|
|
1769
1768
|
if (!result.ok) {
|
|
@@ -60,13 +60,13 @@ export function getUntrackedProjectNotice(phrenPath, cwd) {
|
|
|
60
60
|
return [
|
|
61
61
|
"<phren-notice>",
|
|
62
62
|
"This project directory is not tracked by phren yet.",
|
|
63
|
-
"Run `
|
|
64
|
-
`Suggested command: \`
|
|
63
|
+
"Run `phren add` to track it now.",
|
|
64
|
+
`Suggested command: \`phren add "${projectDir}"\``,
|
|
65
65
|
"Ask the user whether they want to add it to phren now.",
|
|
66
|
-
"If they say no, tell them they can always run `
|
|
66
|
+
"If they say no, tell them they can always run `phren add` later.",
|
|
67
67
|
"If they say yes, also ask whether phren should manage repo instruction files or leave their existing repo-owned CLAUDE/AGENTS files alone.",
|
|
68
|
-
`Then use the \`add_project\` MCP tool with path="${projectDir}" and ownership="phren-managed"|"detached"|"repo-managed", or run \`
|
|
69
|
-
"After onboarding, run `
|
|
68
|
+
`Then use the \`add_project\` MCP tool with path="${projectDir}" and ownership="phren-managed"|"detached"|"repo-managed", or run \`phren add\` from that directory.`,
|
|
69
|
+
"After onboarding, run `phren doctor` if hooks or MCP tools are not responding.",
|
|
70
70
|
"<phren-notice>",
|
|
71
71
|
"",
|
|
72
72
|
].join("\n");
|
|
@@ -83,8 +83,8 @@ export function getSessionStartOnboardingNotice(phrenPath, cwd, activeProject) {
|
|
|
83
83
|
return [
|
|
84
84
|
"<phren-notice>",
|
|
85
85
|
"Phren onboarding: no tracked projects are active for this workspace yet.",
|
|
86
|
-
"Start in a project repo and run `
|
|
87
|
-
"Run `
|
|
86
|
+
"Start in a project repo and run `phren add` so SessionStart can inject project context.",
|
|
87
|
+
"Run `phren doctor` to verify hooks and MCP wiring after setup.",
|
|
88
88
|
"<phren-notice>",
|
|
89
89
|
"",
|
|
90
90
|
].join("\n");
|
|
@@ -97,7 +97,7 @@ export function getSessionStartOnboardingNotice(phrenPath, cwd, activeProject) {
|
|
|
97
97
|
"<phren-notice>",
|
|
98
98
|
`Phren onboarding: project "${activeProject}" is tracked but memory is still empty.`,
|
|
99
99
|
"Capture one finding with `add_finding` and one task with `add_task` to seed future SessionStart context.",
|
|
100
|
-
"Run `
|
|
100
|
+
"Run `phren doctor` if setup seems incomplete.",
|
|
101
101
|
"<phren-notice>",
|
|
102
102
|
"",
|
|
103
103
|
].join("\n");
|
|
@@ -338,12 +338,15 @@ export function isDuplicateFinding(existingContent, newLearning, threshold = 0.6
|
|
|
338
338
|
return true;
|
|
339
339
|
}
|
|
340
340
|
// Second pass: Jaccard similarity (strip metadata before comparing)
|
|
341
|
+
// Threshold lowered from 0.55 to 0.40 to catch agent paraphrases —
|
|
342
|
+
// swarm agents often report the same insight with different wording,
|
|
343
|
+
// and 0.55 let too many through.
|
|
341
344
|
const newTokens = jaccardTokenize(stripMetadata(newLearning));
|
|
342
345
|
const existingTokens = jaccardTokenize(stripMetadata(bullet));
|
|
343
346
|
if (newTokens.size < 3 || existingTokens.size < 3)
|
|
344
347
|
continue; // too few tokens for reliable Jaccard
|
|
345
348
|
const jaccard = jaccardSimilarity(newTokens, existingTokens);
|
|
346
|
-
if (jaccard > 0.
|
|
349
|
+
if (jaccard > 0.40) {
|
|
347
350
|
debugLog(`duplicate-detection: Jaccard ${Math.round(jaccard * 100)}% with existing: "${bullet.slice(0, 80)}"`);
|
|
348
351
|
return true;
|
|
349
352
|
}
|
|
@@ -489,7 +492,7 @@ export async function checkSemanticDedup(phrenPath, project, newLearning, signal
|
|
|
489
492
|
if (tokA.size < 3 || tokB.size < 3)
|
|
490
493
|
continue;
|
|
491
494
|
const jaccard = jaccardSimilarity(tokA, tokB);
|
|
492
|
-
if (jaccard >= 0.
|
|
495
|
+
if (jaccard >= 0.40)
|
|
493
496
|
continue; // already caught by sync isDuplicateFinding
|
|
494
497
|
if (jaccard >= 0.3) {
|
|
495
498
|
const isDup = await semanticDedup(a, b, phrenPath, signal);
|
package/mcp/dist/entrypoint.js
CHANGED
|
@@ -265,7 +265,7 @@ export async function runTopLevelCommand(argv) {
|
|
|
265
265
|
const phrenPath = defaultPhrenPath();
|
|
266
266
|
const profile = (process.env.PHREN_PROFILE) || undefined;
|
|
267
267
|
if (!fs.existsSync(phrenPath) || !fs.existsSync(path.join(phrenPath, ".config"))) {
|
|
268
|
-
console.log("phren is not set up yet. Run:
|
|
268
|
+
console.log("phren is not set up yet. Run: phren init");
|
|
269
269
|
return finish(1);
|
|
270
270
|
}
|
|
271
271
|
const ownership = ownershipArg
|
|
@@ -381,7 +381,7 @@ export async function runTopLevelCommand(argv) {
|
|
|
381
381
|
const note = getVerifyOutcomeNote(phrenPath, result.checks);
|
|
382
382
|
if (note)
|
|
383
383
|
console.log(`\nNote: ${note}`);
|
|
384
|
-
console.log(`\nRun \`
|
|
384
|
+
console.log(`\nRun \`phren init\` to fix setup issues.`);
|
|
385
385
|
}
|
|
386
386
|
return finish(result.ok ? 0 : 1);
|
|
387
387
|
}
|
|
@@ -408,7 +408,7 @@ export async function runTopLevelCommand(argv) {
|
|
|
408
408
|
}
|
|
409
409
|
}
|
|
410
410
|
if (argvCommand === "link") {
|
|
411
|
-
console.error("`phren link` has been removed. Use `
|
|
411
|
+
console.error("`phren link` has been removed. Use `phren init` instead.");
|
|
412
412
|
return finish(1);
|
|
413
413
|
}
|
|
414
414
|
if (argvCommand === "--health") {
|
|
@@ -167,6 +167,7 @@ export function resolveFindingSessionId(phrenPath, project, explicitSessionId) {
|
|
|
167
167
|
.sort((a, b) => sessionSortValue(b) - sessionSortValue(a));
|
|
168
168
|
if (matchingProject.length > 0)
|
|
169
169
|
return matchingProject[0].sessionId;
|
|
170
|
-
|
|
171
|
-
|
|
170
|
+
// Do not attribute findings to an unrelated active session from another project.
|
|
171
|
+
// Missing session provenance is safer than cross-project contamination.
|
|
172
|
+
return undefined;
|
|
172
173
|
}
|
package/mcp/dist/init/config.js
CHANGED
|
@@ -207,7 +207,7 @@ export function configureClaude(phrenPath, opts = {}) {
|
|
|
207
207
|
eventHooks.push({ matcher: "", hooks: [hookBody] });
|
|
208
208
|
}
|
|
209
209
|
};
|
|
210
|
-
const toolHookEnabled = hooksEnabled && isFeatureEnabled("PHREN_FEATURE_TOOL_HOOK",
|
|
210
|
+
const toolHookEnabled = hooksEnabled && isFeatureEnabled("PHREN_FEATURE_TOOL_HOOK", true);
|
|
211
211
|
if (hooksEnabled) {
|
|
212
212
|
upsertPhrenHook("UserPromptSubmit", {
|
|
213
213
|
type: "command",
|
|
@@ -89,7 +89,7 @@ export function configureHooksIfEnabled(phrenPath, hooksEnabled, verb) {
|
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
91
|
else {
|
|
92
|
-
log(` Hooks are disabled by preference (run:
|
|
92
|
+
log(` Hooks are disabled by preference (run: phren hooks-mode on)`);
|
|
93
93
|
}
|
|
94
94
|
// Install phren CLI wrapper at ~/.local/bin/phren so the bare command works
|
|
95
95
|
const wrapperInstalled = installPhrenCliWrapper(phrenPath);
|
|
@@ -14,7 +14,7 @@ export async function runHooksMode(modeArg) {
|
|
|
14
14
|
if (!normalizedArg || normalizedArg === "status") {
|
|
15
15
|
const current = getHooksEnabledPreference(phrenPath);
|
|
16
16
|
log(`Hooks mode: ${current ? "on (active)" : "off (disabled)"}`);
|
|
17
|
-
log(`Change mode:
|
|
17
|
+
log(`Change mode: phren hooks-mode on|off`);
|
|
18
18
|
return;
|
|
19
19
|
}
|
|
20
20
|
const mode = parseMcpMode(normalizedArg);
|
|
@@ -15,8 +15,8 @@ export async function runMcpMode(modeArg) {
|
|
|
15
15
|
const hooks = getHooksEnabledPreference(phrenPath);
|
|
16
16
|
log(`MCP mode: ${current ? "on (recommended)" : "off (hooks-only fallback)"}`);
|
|
17
17
|
log(`Hooks mode: ${hooks ? "on (active)" : "off (disabled)"}`);
|
|
18
|
-
log(`Change mode:
|
|
19
|
-
log(`Hooks toggle:
|
|
18
|
+
log(`Change mode: phren mcp-mode on|off`);
|
|
19
|
+
log(`Hooks toggle: phren hooks-mode on|off`);
|
|
20
20
|
return;
|
|
21
21
|
}
|
|
22
22
|
const mode = parseMcpMode(normalizedArg);
|
|
@@ -280,7 +280,7 @@ export async function runWalkthrough(phrenPath, options) {
|
|
|
280
280
|
log(" phren-managed: Phren may mirror CLAUDE.md / AGENTS.md into the repo");
|
|
281
281
|
log(" detached: Phren keeps its own docs but does not write into the repo");
|
|
282
282
|
log(" repo-managed: keep the repo's existing CLAUDE/AGENTS files as canonical");
|
|
283
|
-
log(" Change later:
|
|
283
|
+
log(" Change later: phren config project-ownership <mode>");
|
|
284
284
|
const projectOwnershipDefault = await prompts.select("Default project ownership", [
|
|
285
285
|
{ value: "detached", name: "detached (default)" },
|
|
286
286
|
{ value: "phren-managed", name: "phren-managed" },
|
|
@@ -291,7 +291,7 @@ export async function runWalkthrough(phrenPath, options) {
|
|
|
291
291
|
log("directly: search memory, manage tasks, save findings, etc.");
|
|
292
292
|
log(" Recommended for: Claude Code, Cursor, Copilot CLI, Codex");
|
|
293
293
|
log(" Alternative: hooks-only mode (read-only context injection, any agent)");
|
|
294
|
-
log(" Change later:
|
|
294
|
+
log(" Change later: phren mcp-mode on|off");
|
|
295
295
|
const mcp = (await prompts.confirm("Enable MCP?", true)) ? "on" : "off";
|
|
296
296
|
printSection("Hooks");
|
|
297
297
|
log("Hooks run shell commands at session start, prompt submit, and session end.");
|
|
@@ -299,7 +299,7 @@ export async function runWalkthrough(phrenPath, options) {
|
|
|
299
299
|
log(" - UserPromptSubmit: searches phren and injects relevant context");
|
|
300
300
|
log(" - Stop: commits and pushes any new findings after each response");
|
|
301
301
|
log(" What they touch: ~/.claude/settings.json (hooks section only)");
|
|
302
|
-
log(" Change later:
|
|
302
|
+
log(" Change later: phren hooks-mode on|off");
|
|
303
303
|
const hooks = (await prompts.confirm("Enable hooks?", true)) ? "on" : "off";
|
|
304
304
|
printSection("Semantic Search (Optional)");
|
|
305
305
|
log("Phren can use a local embedding model for semantic (fuzzy) search via Ollama.");
|
|
@@ -347,7 +347,7 @@ export async function runWalkthrough(phrenPath, options) {
|
|
|
347
347
|
let findingsProactivity = "high";
|
|
348
348
|
if (autoCaptureEnabled) {
|
|
349
349
|
log(" Findings capture level controls how eager phren is to save lessons automatically.");
|
|
350
|
-
log(" Change later:
|
|
350
|
+
log(" Change later: phren config proactivity.findings <high|medium|low>");
|
|
351
351
|
findingsProactivity = await prompts.select("Findings capture level", [
|
|
352
352
|
{ value: "high", name: "high (recommended)" },
|
|
353
353
|
{ value: "medium", name: "medium" },
|
|
@@ -363,7 +363,7 @@ export async function runWalkthrough(phrenPath, options) {
|
|
|
363
363
|
log(" suggest: proposes tasks but waits for approval before writing");
|
|
364
364
|
log(" manual: tasks are fully manual — you add them yourself");
|
|
365
365
|
log(" off: never touch tasks automatically");
|
|
366
|
-
log(" Change later:
|
|
366
|
+
log(" Change later: phren config workflow set --taskMode=<mode>");
|
|
367
367
|
const taskMode = await prompts.select("Task mode", [
|
|
368
368
|
{ value: "auto", name: "auto (recommended)" },
|
|
369
369
|
{ value: "suggest", name: "suggest" },
|
|
@@ -376,7 +376,7 @@ export async function runWalkthrough(phrenPath, options) {
|
|
|
376
376
|
log(" high (recommended): captures tasks as they come up naturally");
|
|
377
377
|
log(" medium: only when you explicitly mention a task");
|
|
378
378
|
log(" low: minimal auto-capture");
|
|
379
|
-
log(" Change later:
|
|
379
|
+
log(" Change later: phren config proactivity.tasks <high|medium|low>");
|
|
380
380
|
taskProactivity = await prompts.select("Task proactivity", [
|
|
381
381
|
{ value: "high", name: "high (recommended)" },
|
|
382
382
|
{ value: "medium", name: "medium" },
|
|
@@ -387,7 +387,7 @@ export async function runWalkthrough(phrenPath, options) {
|
|
|
387
387
|
log("Choose how strict review gates should be for risky or low-confidence writes.");
|
|
388
388
|
log(" lowConfidenceThreshold: confidence cutoff used to mark writes as risky");
|
|
389
389
|
log(" riskySections: sections always treated as risky");
|
|
390
|
-
log(" Change later:
|
|
390
|
+
log(" Change later: phren config workflow set --lowConfidenceThreshold=0.7 --riskySections=Stale,Conflicts");
|
|
391
391
|
const thresholdAnswer = await prompts.input("Low-confidence threshold [0.0-1.0]", "0.7");
|
|
392
392
|
const lowConfidenceThreshold = parseLowConfidenceThreshold(thresholdAnswer, 0.7);
|
|
393
393
|
const riskySectionsAnswer = await prompts.input("Risky sections [Review,Stale,Conflicts]", "Stale,Conflicts");
|
|
@@ -437,7 +437,7 @@ export async function runWalkthrough(phrenPath, options) {
|
|
|
437
437
|
log(" conservative — decisions and pitfalls only");
|
|
438
438
|
log(" balanced — non-obvious patterns, decisions, pitfalls, bugs (recommended)");
|
|
439
439
|
log(" aggressive — everything worth remembering, err on the side of capturing");
|
|
440
|
-
log(" Change later:
|
|
440
|
+
log(" Change later: phren config finding-sensitivity <level>");
|
|
441
441
|
const findingSensitivity = await prompts.select("Finding sensitivity", [
|
|
442
442
|
{ value: "balanced", name: "balanced (recommended)" },
|
|
443
443
|
{ value: "conservative", name: "conservative" },
|
|
@@ -466,7 +466,7 @@ export async function runWalkthrough(phrenPath, options) {
|
|
|
466
466
|
bootstrapCurrentProject = await prompts.confirm("Add this project to phren now?", true);
|
|
467
467
|
if (!bootstrapCurrentProject) {
|
|
468
468
|
bootstrapCurrentProject = false;
|
|
469
|
-
log(style.warning(` Skipped. Later: cd ${detectedProject} &&
|
|
469
|
+
log(style.warning(` Skipped. Later: cd ${detectedProject} && phren add`));
|
|
470
470
|
}
|
|
471
471
|
else {
|
|
472
472
|
bootstrapOwnership = await prompts.select("Ownership for detected project", [
|
package/mcp/dist/init/init.js
CHANGED
|
@@ -260,7 +260,7 @@ export async function runInit(opts = {}) {
|
|
|
260
260
|
shouldBootstrapCurrentProject = await prompts.confirm("Add this project to phren now?", true);
|
|
261
261
|
if (!shouldBootstrapCurrentProject) {
|
|
262
262
|
shouldBootstrapCurrentProject = false;
|
|
263
|
-
log(style.warning(` Skipped. Later: cd ${pendingBootstrap.path} &&
|
|
263
|
+
log(style.warning(` Skipped. Later: cd ${pendingBootstrap.path} && phren add`));
|
|
264
264
|
}
|
|
265
265
|
else {
|
|
266
266
|
bootstrapOwnership = await prompts.select("Ownership for detected project", [
|
|
@@ -352,7 +352,7 @@ export async function runInit(opts = {}) {
|
|
|
352
352
|
const previousVersion = prefs.installedVersion;
|
|
353
353
|
if (isVersionNewer(VERSION, previousVersion)) {
|
|
354
354
|
log(`\n Starter template update available: v${previousVersion} -> v${VERSION}`);
|
|
355
|
-
log(` Run \`
|
|
355
|
+
log(` Run \`phren init --apply-starter-update\` to refresh global/CLAUDE.md and global skills.`);
|
|
356
356
|
}
|
|
357
357
|
if (opts.applyStarterUpdate) {
|
|
358
358
|
const updated = applyStarterTemplateUpdates(phrenPath);
|
|
@@ -395,8 +395,8 @@ export async function runInit(opts = {}) {
|
|
|
395
395
|
log(`\n\x1b[95m◆\x1b[0m phren updated successfully`);
|
|
396
396
|
log(`\nNext steps:`);
|
|
397
397
|
log(` 1. Start a new Claude session in your project directory — phren injects context automatically`);
|
|
398
|
-
log(` 2. Run \`
|
|
399
|
-
log(` 3. Change defaults anytime: \`
|
|
398
|
+
log(` 2. Run \`phren doctor\` to verify everything is wired correctly`);
|
|
399
|
+
log(` 3. Change defaults anytime: \`phren config project-ownership\`, \`phren config workflow\`, \`phren config proactivity.findings\`, \`phren config proactivity.tasks\``);
|
|
400
400
|
log(` 4. After your first week, run phren-discover to surface gaps in your project knowledge`);
|
|
401
401
|
log(` 5. After working across projects, run phren-consolidate to find cross-project patterns`);
|
|
402
402
|
log(``);
|
|
@@ -581,8 +581,8 @@ export async function runInit(opts = {}) {
|
|
|
581
581
|
log(`\nNext steps:`);
|
|
582
582
|
let step = 1;
|
|
583
583
|
log(` ${step++}. Start a new Claude session in your project directory — phren injects context automatically`);
|
|
584
|
-
log(` ${step++}. Run \`
|
|
585
|
-
log(` ${step++}. Change defaults anytime: \`
|
|
584
|
+
log(` ${step++}. Run \`phren doctor\` to verify everything is wired correctly`);
|
|
585
|
+
log(` ${step++}. Change defaults anytime: \`phren config project-ownership\`, \`phren config workflow\`, \`phren config proactivity.findings\`, \`phren config proactivity.tasks\``);
|
|
586
586
|
const gh = opts._walkthroughGithub;
|
|
587
587
|
if (gh) {
|
|
588
588
|
const remote = gh.username
|
|
@@ -607,9 +607,9 @@ export async function runInit(opts = {}) {
|
|
|
607
607
|
log(` git remote add origin git@github.com:YOUR_USERNAME/my-phren.git`);
|
|
608
608
|
log(` git push -u origin main`);
|
|
609
609
|
}
|
|
610
|
-
log(` ${step++}. Add more projects: cd ~/your-project &&
|
|
610
|
+
log(` ${step++}. Add more projects: cd ~/your-project && phren add`);
|
|
611
611
|
if (!mcpEnabled) {
|
|
612
|
-
log(` ${step++}. Turn MCP on:
|
|
612
|
+
log(` ${step++}. Turn MCP on: phren mcp-mode on`);
|
|
613
613
|
}
|
|
614
614
|
log(` ${step++}. After your first week, run phren-discover to surface gaps in your project knowledge`);
|
|
615
615
|
log(` ${step++}. After working across projects, run phren-consolidate to find cross-project patterns`);
|
package/mcp/dist/init/setup.js
CHANGED
|
@@ -1089,9 +1089,15 @@ export function updateMachinesYaml(phrenPath, machine, profile) {
|
|
|
1089
1089
|
*/
|
|
1090
1090
|
export function detectProjectDir(dir, phrenPath) {
|
|
1091
1091
|
const home = os.homedir();
|
|
1092
|
+
const tmpRoot = path.resolve(os.tmpdir());
|
|
1092
1093
|
const resolvedPhrenPath = path.resolve(phrenPath);
|
|
1093
1094
|
let current = path.resolve(dir);
|
|
1094
1095
|
while (true) {
|
|
1096
|
+
// Never treat the shared OS temp root itself as a project. Tools may drop
|
|
1097
|
+
// global instruction files there, which would otherwise hijack detection
|
|
1098
|
+
// for arbitrary temp subdirectories.
|
|
1099
|
+
if (current === tmpRoot)
|
|
1100
|
+
return null;
|
|
1095
1101
|
if (current === home || current === resolvedPhrenPath)
|
|
1096
1102
|
return null;
|
|
1097
1103
|
if (current.startsWith(resolvedPhrenPath + path.sep))
|
package/mcp/dist/init-fresh.js
CHANGED
|
@@ -197,8 +197,8 @@ export async function runFreshInstall(phrenPath, opts, params) {
|
|
|
197
197
|
log(`\nNext steps:`);
|
|
198
198
|
let step = 1;
|
|
199
199
|
log(` ${step++}. Start a new Claude session in your project directory — phren injects context automatically`);
|
|
200
|
-
log(` ${step++}. Run \`
|
|
201
|
-
log(` ${step++}. Change defaults anytime: \`
|
|
200
|
+
log(` ${step++}. Run \`phren doctor\` to verify everything is wired correctly`);
|
|
201
|
+
log(` ${step++}. Change defaults anytime: \`phren config project-ownership\`, \`phren config workflow\`, \`phren config proactivity.findings\`, \`phren config proactivity.tasks\``);
|
|
202
202
|
const gh = opts._walkthroughGithub;
|
|
203
203
|
if (gh) {
|
|
204
204
|
const remote = gh.username
|
|
@@ -223,9 +223,9 @@ export async function runFreshInstall(phrenPath, opts, params) {
|
|
|
223
223
|
log(` git remote add origin git@github.com:YOUR_USERNAME/my-phren.git`);
|
|
224
224
|
log(` git push -u origin main`);
|
|
225
225
|
}
|
|
226
|
-
log(` ${step++}. Add more projects: cd ~/your-project &&
|
|
226
|
+
log(` ${step++}. Add more projects: cd ~/your-project && phren add`);
|
|
227
227
|
if (!mcpEnabled) {
|
|
228
|
-
log(` ${step++}. Turn MCP on:
|
|
228
|
+
log(` ${step++}. Turn MCP on: phren mcp-mode on`);
|
|
229
229
|
}
|
|
230
230
|
log(` ${step++}. After your first week, run phren-discover to surface gaps in your project knowledge`);
|
|
231
231
|
log(` ${step++}. After working across projects, run phren-consolidate to find cross-project patterns`);
|
package/mcp/dist/init-hooks.js
CHANGED
package/mcp/dist/init-modes.js
CHANGED
|
@@ -24,8 +24,8 @@ export async function runMcpMode(modeArg) {
|
|
|
24
24
|
const hooks = getHooksEnabledPreference(phrenPath);
|
|
25
25
|
log(`MCP mode: ${current ? "on (recommended)" : "off (hooks-only fallback)"}`);
|
|
26
26
|
log(`Hooks mode: ${hooks ? "on (active)" : "off (disabled)"}`);
|
|
27
|
-
log(`Change mode:
|
|
28
|
-
log(`Hooks toggle:
|
|
27
|
+
log(`Change mode: phren mcp-mode on|off`);
|
|
28
|
+
log(`Hooks toggle: phren hooks-mode on|off`);
|
|
29
29
|
return;
|
|
30
30
|
}
|
|
31
31
|
const mode = parseMcpMode(normalizedArg);
|
|
@@ -93,7 +93,7 @@ export async function runHooksMode(modeArg) {
|
|
|
93
93
|
if (!normalizedArg || normalizedArg === "status") {
|
|
94
94
|
const current = getHooksEnabledPreference(phrenPath);
|
|
95
95
|
log(`Hooks mode: ${current ? "on (active)" : "off (disabled)"}`);
|
|
96
|
-
log(`Change mode:
|
|
96
|
+
log(`Change mode: phren hooks-mode on|off`);
|
|
97
97
|
return;
|
|
98
98
|
}
|
|
99
99
|
const mode = parseMcpMode(normalizedArg);
|
package/mcp/dist/init-update.js
CHANGED
|
@@ -53,7 +53,7 @@ export async function runExistingInstallUpdate(phrenPath, opts, params) {
|
|
|
53
53
|
const previousVersion = prefs.installedVersion;
|
|
54
54
|
if (isVersionNewer(VERSION, previousVersion)) {
|
|
55
55
|
log(`\n Starter template update available: v${previousVersion} -> v${VERSION}`);
|
|
56
|
-
log(` Run \`
|
|
56
|
+
log(` Run \`phren init --apply-starter-update\` to refresh global/CLAUDE.md and global skills.`);
|
|
57
57
|
}
|
|
58
58
|
if (opts.applyStarterUpdate) {
|
|
59
59
|
const updated = applyStarterTemplateUpdates(phrenPath);
|
|
@@ -88,8 +88,8 @@ export async function runExistingInstallUpdate(phrenPath, opts, params) {
|
|
|
88
88
|
log(`\n\x1b[95m◆\x1b[0m phren updated successfully`);
|
|
89
89
|
log(`\nNext steps:`);
|
|
90
90
|
log(` 1. Start a new Claude session in your project directory — phren injects context automatically`);
|
|
91
|
-
log(` 2. Run \`
|
|
92
|
-
log(` 3. Change defaults anytime: \`
|
|
91
|
+
log(` 2. Run \`phren doctor\` to verify everything is wired correctly`);
|
|
92
|
+
log(` 3. Change defaults anytime: \`phren config project-ownership\`, \`phren config workflow\`, \`phren config proactivity.findings\`, \`phren config proactivity.tasks\``);
|
|
93
93
|
log(` 4. After your first week, run phren-discover to surface gaps in your project knowledge`);
|
|
94
94
|
log(` 5. After working across projects, run phren-consolidate to find cross-project patterns`);
|
|
95
95
|
log(``);
|
|
@@ -269,7 +269,7 @@ export async function runWalkthrough(phrenPath) {
|
|
|
269
269
|
log(" phren-managed: Phren may mirror CLAUDE.md / AGENTS.md into the repo");
|
|
270
270
|
log(" detached: Phren keeps its own docs but does not write into the repo");
|
|
271
271
|
log(" repo-managed: keep the repo's existing CLAUDE/AGENTS files as canonical");
|
|
272
|
-
log(" Change later:
|
|
272
|
+
log(" Change later: phren config project-ownership <mode>");
|
|
273
273
|
const projectOwnershipDefault = await prompts.select("Default project ownership", [
|
|
274
274
|
{ value: "detached", name: "detached (default)" },
|
|
275
275
|
{ value: "phren-managed", name: "phren-managed" },
|
|
@@ -280,7 +280,7 @@ export async function runWalkthrough(phrenPath) {
|
|
|
280
280
|
log("directly: search memory, manage tasks, save findings, etc.");
|
|
281
281
|
log(" Recommended for: Claude Code, Cursor, Copilot CLI, Codex");
|
|
282
282
|
log(" Alternative: hooks-only mode (read-only context injection, any agent)");
|
|
283
|
-
log(" Change later:
|
|
283
|
+
log(" Change later: phren mcp-mode on|off");
|
|
284
284
|
const mcp = (await prompts.confirm("Enable MCP?", true)) ? "on" : "off";
|
|
285
285
|
printSection("Hooks");
|
|
286
286
|
log("Hooks run shell commands at session start, prompt submit, and session end.");
|
|
@@ -288,7 +288,7 @@ export async function runWalkthrough(phrenPath) {
|
|
|
288
288
|
log(" - UserPromptSubmit: searches phren and injects relevant context");
|
|
289
289
|
log(" - Stop: commits and pushes any new findings after each response");
|
|
290
290
|
log(" What they touch: ~/.claude/settings.json (hooks section only)");
|
|
291
|
-
log(" Change later:
|
|
291
|
+
log(" Change later: phren hooks-mode on|off");
|
|
292
292
|
const hooks = (await prompts.confirm("Enable hooks?", true)) ? "on" : "off";
|
|
293
293
|
printSection("Semantic Search (Optional)");
|
|
294
294
|
log("Phren can use a local embedding model for semantic (fuzzy) search via Ollama.");
|
|
@@ -336,7 +336,7 @@ export async function runWalkthrough(phrenPath) {
|
|
|
336
336
|
let findingsProactivity = "high";
|
|
337
337
|
if (autoCaptureEnabled) {
|
|
338
338
|
log(" Findings capture level controls how eager phren is to save lessons automatically.");
|
|
339
|
-
log(" Change later:
|
|
339
|
+
log(" Change later: phren config proactivity.findings <high|medium|low>");
|
|
340
340
|
findingsProactivity = await prompts.select("Findings capture level", [
|
|
341
341
|
{ value: "high", name: "high (recommended)" },
|
|
342
342
|
{ value: "medium", name: "medium" },
|
|
@@ -352,7 +352,7 @@ export async function runWalkthrough(phrenPath) {
|
|
|
352
352
|
log(" suggest: proposes tasks but waits for approval before writing");
|
|
353
353
|
log(" manual: tasks are fully manual — you add them yourself");
|
|
354
354
|
log(" off: never touch tasks automatically");
|
|
355
|
-
log(" Change later:
|
|
355
|
+
log(" Change later: phren config workflow set --taskMode=<mode>");
|
|
356
356
|
const taskMode = await prompts.select("Task mode", [
|
|
357
357
|
{ value: "auto", name: "auto (recommended)" },
|
|
358
358
|
{ value: "suggest", name: "suggest" },
|
|
@@ -365,7 +365,7 @@ export async function runWalkthrough(phrenPath) {
|
|
|
365
365
|
log(" high (recommended): captures tasks as they come up naturally");
|
|
366
366
|
log(" medium: only when you explicitly mention a task");
|
|
367
367
|
log(" low: minimal auto-capture");
|
|
368
|
-
log(" Change later:
|
|
368
|
+
log(" Change later: phren config proactivity.tasks <high|medium|low>");
|
|
369
369
|
taskProactivity = await prompts.select("Task proactivity", [
|
|
370
370
|
{ value: "high", name: "high (recommended)" },
|
|
371
371
|
{ value: "medium", name: "medium" },
|
|
@@ -376,7 +376,7 @@ export async function runWalkthrough(phrenPath) {
|
|
|
376
376
|
log("Choose how strict review gates should be for risky or low-confidence writes.");
|
|
377
377
|
log(" lowConfidenceThreshold: confidence cutoff used to mark writes as risky");
|
|
378
378
|
log(" riskySections: sections always treated as risky");
|
|
379
|
-
log(" Change later:
|
|
379
|
+
log(" Change later: phren config workflow set --lowConfidenceThreshold=0.7 --riskySections=Stale,Conflicts");
|
|
380
380
|
const thresholdAnswer = await prompts.input("Low-confidence threshold [0.0-1.0]", "0.7");
|
|
381
381
|
const lowConfidenceThreshold = parseLowConfidenceThreshold(thresholdAnswer, 0.7);
|
|
382
382
|
const riskySectionsAnswer = await prompts.input("Risky sections [Review,Stale,Conflicts]", "Stale,Conflicts");
|
|
@@ -426,7 +426,7 @@ export async function runWalkthrough(phrenPath) {
|
|
|
426
426
|
log(" conservative — decisions and pitfalls only");
|
|
427
427
|
log(" balanced — non-obvious patterns, decisions, pitfalls, bugs (recommended)");
|
|
428
428
|
log(" aggressive — everything worth remembering, err on the side of capturing");
|
|
429
|
-
log(" Change later:
|
|
429
|
+
log(" Change later: phren config finding-sensitivity <level>");
|
|
430
430
|
const findingSensitivity = await prompts.select("Finding sensitivity", [
|
|
431
431
|
{ value: "balanced", name: "balanced (recommended)" },
|
|
432
432
|
{ value: "conservative", name: "conservative" },
|
|
@@ -455,7 +455,7 @@ export async function runWalkthrough(phrenPath) {
|
|
|
455
455
|
bootstrapCurrentProject = await prompts.confirm("Add this project to phren now?", true);
|
|
456
456
|
if (!bootstrapCurrentProject) {
|
|
457
457
|
bootstrapCurrentProject = false;
|
|
458
|
-
log(style.warning(` Skipped. Later: cd ${detectedProject} &&
|
|
458
|
+
log(style.warning(` Skipped. Later: cd ${detectedProject} && phren add`));
|
|
459
459
|
}
|
|
460
460
|
else {
|
|
461
461
|
bootstrapOwnership = await prompts.select("Ownership for detected project", [
|
package/mcp/dist/link/link.js
CHANGED
|
@@ -76,7 +76,7 @@ function maybeOfferStarterTemplateUpdate(phrenPath) {
|
|
|
76
76
|
const prefs = JSON.parse(fs.readFileSync(prefsPath, "utf8"));
|
|
77
77
|
if (isVersionNewer(current, prefs.installedVersion)) {
|
|
78
78
|
log(` Starter template update available: v${prefs.installedVersion} -> v${current}`);
|
|
79
|
-
log(` Run \`
|
|
79
|
+
log(` Run \`phren init --apply-starter-update\` to refresh global/CLAUDE.md and global skills.`);
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
82
|
catch (err) {
|
package/mcp/dist/phren-paths.js
CHANGED
|
@@ -209,7 +209,7 @@ export function findPhrenPathWithArg(arg) {
|
|
|
209
209
|
const existing = findPhrenPath();
|
|
210
210
|
if (existing)
|
|
211
211
|
return existing;
|
|
212
|
-
throw new Error(`${PhrenError.NOT_FOUND}: phren root not found. Run '
|
|
212
|
+
throw new Error(`${PhrenError.NOT_FOUND}: phren root not found. Run 'phren init'.`);
|
|
213
213
|
}
|
|
214
214
|
export function isProjectLocalMode(phrenPath) {
|
|
215
215
|
try {
|
|
@@ -513,7 +513,7 @@ export function getPhrenPath() {
|
|
|
513
513
|
if (!lazyPhrenPath) {
|
|
514
514
|
const existing = findPhrenPath();
|
|
515
515
|
if (!existing)
|
|
516
|
-
throw new Error(`${PhrenError.NOT_FOUND}: phren root not found. Run '
|
|
516
|
+
throw new Error(`${PhrenError.NOT_FOUND}: phren root not found. Run 'phren init'.`);
|
|
517
517
|
lazyPhrenPath = existing;
|
|
518
518
|
}
|
|
519
519
|
return lazyPhrenPath;
|
|
@@ -45,7 +45,7 @@ export function resolveActiveProfile(phrenPath, requestedProfile) {
|
|
|
45
45
|
export function listMachines(phrenPath) {
|
|
46
46
|
const machinesPath = path.join(phrenPath, "machines.yaml");
|
|
47
47
|
if (!fs.existsSync(machinesPath))
|
|
48
|
-
return phrenErr(`machines.yaml not found. Run '
|
|
48
|
+
return phrenErr(`machines.yaml not found. Run 'phren init' to set up your phren.`, PhrenError.FILE_NOT_FOUND);
|
|
49
49
|
try {
|
|
50
50
|
const raw = fs.readFileSync(machinesPath, "utf8");
|
|
51
51
|
const parsed = yaml.load(raw, { schema: yaml.CORE_SCHEMA });
|
|
@@ -205,7 +205,7 @@ export function getActiveProfileDefaults(phrenPath, profile) {
|
|
|
205
205
|
export function listProfiles(phrenPath) {
|
|
206
206
|
const profilesDir = path.join(phrenPath, "profiles");
|
|
207
207
|
if (!fs.existsSync(profilesDir))
|
|
208
|
-
return phrenErr(`No profiles/ directory found. Run '
|
|
208
|
+
return phrenErr(`No profiles/ directory found. Run 'phren init' to set up your phren.`, PhrenError.FILE_NOT_FOUND);
|
|
209
209
|
const files = fs.readdirSync(profilesDir).filter((file) => file.endsWith(".yaml")).sort();
|
|
210
210
|
const profiles = [];
|
|
211
211
|
for (const file of files) {
|
package/mcp/dist/status.js
CHANGED
|
@@ -53,7 +53,7 @@ function hasCommandHook(value) {
|
|
|
53
53
|
export async function runStatus() {
|
|
54
54
|
const phrenPath = findPhrenPath();
|
|
55
55
|
if (!phrenPath) {
|
|
56
|
-
console.log(`${RED}phren not found${RESET}. Run ${CYAN}npx phren init${RESET} to set up.`);
|
|
56
|
+
console.log(`${RED}phren not found${RESET}. Run ${CYAN}npx @phren/cli init${RESET} to set up.`);
|
|
57
57
|
process.exit(1);
|
|
58
58
|
}
|
|
59
59
|
const cwd = process.cwd();
|
|
@@ -17,8 +17,8 @@ import { getActiveTaskForSession } from "../task/lifecycle.js";
|
|
|
17
17
|
import { FINDING_PROVENANCE_SOURCES } from "../content/citation.js";
|
|
18
18
|
import { isInactiveFindingLine, supersedeFinding, retractFinding as retractFindingLifecycle, resolveFindingContradiction, } from "../finding/lifecycle.js";
|
|
19
19
|
import { permissionDeniedError } from "../governance/rbac.js";
|
|
20
|
-
const JACCARD_MAYBE_LOW = 0.
|
|
21
|
-
const JACCARD_MAYBE_HIGH = 0.
|
|
20
|
+
const JACCARD_MAYBE_LOW = 0.25;
|
|
21
|
+
const JACCARD_MAYBE_HIGH = 0.40; // above this isDuplicateFinding already catches it
|
|
22
22
|
function findJaccardCandidates(phrenPath, project, finding) {
|
|
23
23
|
try {
|
|
24
24
|
const findingsPath = path.join(phrenPath, project, "FINDINGS.md");
|
|
@@ -18,6 +18,7 @@ import { listTaskCheckpoints, writeTaskCheckpoint } from "../session/checkpoints
|
|
|
18
18
|
import { markImpactEntriesCompletedForSession } from "../finding/impact.js";
|
|
19
19
|
import { atomicWriteJson, debugError, scanSessionFiles } from "../session/utils.js";
|
|
20
20
|
import { getRuntimeHealth } from "../governance/policy.js";
|
|
21
|
+
import { getProjectSourcePath, readProjectConfig } from "../project-config.js";
|
|
21
22
|
const STALE_SESSION_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
22
23
|
function collectGitStatusSnapshot(cwd) {
|
|
23
24
|
try {
|
|
@@ -221,9 +222,11 @@ function cleanupStaleSessions(phrenPath) {
|
|
|
221
222
|
// (no endedAt) should never be removed regardless of age.
|
|
222
223
|
if (state && !state.endedAt)
|
|
223
224
|
continue;
|
|
224
|
-
//
|
|
225
|
-
|
|
226
|
-
|
|
225
|
+
// For ended sessions, age out by end time rather than start time so
|
|
226
|
+
// long-running sessions do not disappear immediately after they finish.
|
|
227
|
+
const expirationAnchor = state?.endedAt || state?.startedAt;
|
|
228
|
+
const ageMs = expirationAnchor
|
|
229
|
+
? Date.now() - new Date(expirationAnchor).getTime()
|
|
227
230
|
: Date.now() - fs.statSync(fullPath).mtimeMs;
|
|
228
231
|
if (ageMs > STALE_SESSION_MS) {
|
|
229
232
|
fs.unlinkSync(fullPath);
|
|
@@ -607,7 +610,10 @@ export function register(server, ctx) {
|
|
|
607
610
|
})();
|
|
608
611
|
if (activeTask) {
|
|
609
612
|
const taskId = activeTask.stableId || activeTask.id;
|
|
610
|
-
const
|
|
613
|
+
const projectConfig = readProjectConfig(phrenPath, endedState.project);
|
|
614
|
+
const snapshotRoot = getProjectSourcePath(phrenPath, endedState.project, projectConfig) ||
|
|
615
|
+
path.join(phrenPath, endedState.project);
|
|
616
|
+
const { gitStatus, editedFiles } = collectGitStatusSnapshot(snapshotRoot);
|
|
611
617
|
const resumptionHint = extractResumptionHint(effectiveSummary, activeTask.line, activeTask.context || "No prior attempt captured");
|
|
612
618
|
writeTaskCheckpoint(phrenPath, {
|
|
613
619
|
project: endedState.project,
|
package/mcp/dist/tools/tasks.js
CHANGED
|
@@ -5,7 +5,7 @@ import * as path from "path";
|
|
|
5
5
|
import { isValidProjectName } from "../utils.js";
|
|
6
6
|
import { addTask as addTaskStore, addTasks as addTasksBatch, taskMarkdown, completeTask as completeTaskStore, completeTasks as completeTasksBatch, removeTask as removeTaskStore, removeTasks as removeTasksBatch, linkTaskIssue, pinTask, workNextTask, tidyDoneTasks, readTasks, readTasksAcrossProjects, resolveTaskItem, TASKS_FILENAME, updateTask as updateTaskStore, promoteTask, } from "../data/access.js";
|
|
7
7
|
import { applyGravity } from "../data/tasks.js";
|
|
8
|
-
import { parseGithubIssueUrl, } from "../task/github.js";
|
|
8
|
+
import { buildTaskIssueBody, createGithubIssueForTask, parseGithubIssueUrl, resolveProjectGithubRepo, } from "../task/github.js";
|
|
9
9
|
import { clearTaskCheckpoint } from "../session/checkpoints.js";
|
|
10
10
|
import { incrementSessionTasksCompleted } from "./session.js";
|
|
11
11
|
import { normalizeMemoryScope } from "../shared.js";
|
|
@@ -366,6 +366,7 @@ export function register(server, ctx) {
|
|
|
366
366
|
github_issue: z.union([z.number().int().positive(), z.string()]).optional().describe("GitHub issue number (for example 14 or '#14')."),
|
|
367
367
|
github_url: z.string().optional().describe("GitHub issue URL to associate with the task item."),
|
|
368
368
|
unlink_github: z.boolean().optional().describe("If true, remove any linked GitHub issue metadata from the item."),
|
|
369
|
+
create_issue: z.boolean().optional().describe("If true, create a GitHub issue for this task and link it."),
|
|
369
370
|
pin: z.boolean().optional().describe("If true, pin the task so it floats to the top of its section."),
|
|
370
371
|
promote: z.boolean().optional().describe("If true, clear the speculative flag on this task (confirm the user wants it)."),
|
|
371
372
|
move_to_active: z.boolean().optional().describe("Used with promote: also move the task to the Active section."),
|
|
@@ -382,6 +383,25 @@ export function register(server, ctx) {
|
|
|
382
383
|
if (!updates.work_next && !item) {
|
|
383
384
|
return mcpResponse({ ok: false, error: "item is required unless updates.work_next is true." });
|
|
384
385
|
}
|
|
386
|
+
if (updates.create_issue) {
|
|
387
|
+
const extraUpdates = [
|
|
388
|
+
updates.text,
|
|
389
|
+
updates.priority,
|
|
390
|
+
updates.context,
|
|
391
|
+
updates.section,
|
|
392
|
+
updates.github_issue,
|
|
393
|
+
updates.github_url,
|
|
394
|
+
updates.unlink_github,
|
|
395
|
+
updates.pin,
|
|
396
|
+
updates.promote,
|
|
397
|
+
updates.move_to_active,
|
|
398
|
+
updates.work_next,
|
|
399
|
+
updates.replace_context,
|
|
400
|
+
].some((value) => value !== undefined);
|
|
401
|
+
if (extraUpdates) {
|
|
402
|
+
return mcpResponse({ ok: false, error: "create_issue must be used by itself." });
|
|
403
|
+
}
|
|
404
|
+
}
|
|
385
405
|
// Cross-validate github_issue and github_url
|
|
386
406
|
if (updates.github_url) {
|
|
387
407
|
const parsed = parseGithubIssueUrl(updates.github_url);
|
|
@@ -423,6 +443,45 @@ export function register(server, ctx) {
|
|
|
423
443
|
data: { project, item: result.data },
|
|
424
444
|
});
|
|
425
445
|
}
|
|
446
|
+
if (updates.create_issue) {
|
|
447
|
+
const resolved = resolveTaskItem(phrenPath, project, item);
|
|
448
|
+
if (!resolved.ok)
|
|
449
|
+
return mcpResponse({ ok: false, error: resolved.error });
|
|
450
|
+
const repo = resolveProjectGithubRepo(phrenPath, project);
|
|
451
|
+
if (!repo) {
|
|
452
|
+
return mcpResponse({
|
|
453
|
+
ok: false,
|
|
454
|
+
error: "Could not infer a GitHub repo. Add a GitHub URL to CLAUDE.md or summary.md, or link an existing issue instead.",
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
const created = createGithubIssueForTask({
|
|
458
|
+
repo,
|
|
459
|
+
title: resolved.data.line.replace(/\s*\[(high|medium|low)\]\s*$/i, "").trim(),
|
|
460
|
+
body: buildTaskIssueBody(project, resolved.data),
|
|
461
|
+
});
|
|
462
|
+
if (!created.ok)
|
|
463
|
+
return mcpResponse({ ok: false, error: created.error, errorCode: created.code });
|
|
464
|
+
const linked = linkTaskIssue(phrenPath, project, item, {
|
|
465
|
+
github_issue: created.data.issueNumber,
|
|
466
|
+
github_url: created.data.url,
|
|
467
|
+
});
|
|
468
|
+
if (!linked.ok)
|
|
469
|
+
return mcpResponse({ ok: false, error: linked.error, errorCode: linked.code });
|
|
470
|
+
refreshTaskIndex(updateFileInIndex, phrenPath, project);
|
|
471
|
+
return mcpResponse({
|
|
472
|
+
ok: true,
|
|
473
|
+
message: `Created GitHub issue ${created.data.issueNumber ? `#${created.data.issueNumber}` : created.data.url} for ${project} task.`,
|
|
474
|
+
data: {
|
|
475
|
+
project,
|
|
476
|
+
item,
|
|
477
|
+
issue_number: created.data.issueNumber ?? null,
|
|
478
|
+
issue_url: created.data.url,
|
|
479
|
+
githubIssue: linked.data.githubIssue ?? null,
|
|
480
|
+
githubUrl: linked.data.githubUrl || null,
|
|
481
|
+
stableId: linked.data.stableId || null,
|
|
482
|
+
},
|
|
483
|
+
});
|
|
484
|
+
}
|
|
426
485
|
// Handle github issue linking via update_task when github_issue or github_url is set (and no other field updates)
|
|
427
486
|
if ((updates.github_issue !== undefined || updates.github_url || updates.unlink_github) && !updates.text && !updates.priority && !updates.context && !updates.section) {
|
|
428
487
|
if (updates.unlink_github && (updates.github_issue !== undefined || updates.github_url)) {
|
package/package.json
CHANGED
package/skills/sync/SKILL.md
CHANGED
|
@@ -102,7 +102,7 @@ If this fails (not a git repo, no remote), tell the user. Don't silently skip.
|
|
|
102
102
|
Run init against the pulled phren repo so hooks, MCP registration, and machine/profile wiring are refreshed:
|
|
103
103
|
|
|
104
104
|
```bash
|
|
105
|
-
PHREN_PATH="$PHREN_DIR"
|
|
105
|
+
PHREN_PATH="$PHREN_DIR" phren init -y
|
|
106
106
|
```
|
|
107
107
|
|
|
108
108
|
If the user is in an untracked repo afterward, tell them to open a session there and let the agent ask, or run `phren add` from that directory.
|
package/starter/README.md
CHANGED
|
@@ -32,7 +32,7 @@ New to phren? Here's what each file does and when it matters.
|
|
|
32
32
|
|
|
33
33
|
**FINDINGS.md** fills itself. As Claude discovers insights, patterns, and decisions during your sessions, it tells phren and entries land here grouped by date. Old entries fade from retrieval over time. Wrong entries can be removed with `remove_finding()`.
|
|
34
34
|
|
|
35
|
-
**tasks.md** is your task board file. It keeps Active (working now), Queue (up next), and Done (finished) in one place so the work history stays with the project. You can also manage it from `
|
|
35
|
+
**tasks.md** is your task board file. It keeps Active (working now), Queue (up next), and Done (finished) in one place so the work history stays with the project. You can also manage it from `phren shell`.
|
|
36
36
|
|
|
37
37
|
**global/CLAUDE.md** applies everywhere. Your style preferences, tool choices, things Claude should always know regardless of which project you're in.
|
|
38
38
|
|
|
@@ -40,14 +40,14 @@ New to phren? Here's what each file does and when it matters.
|
|
|
40
40
|
|
|
41
41
|
## Getting started
|
|
42
42
|
|
|
43
|
-
If you got here via `npx phren init`, you're already set up. Restart Claude Code and you're good.
|
|
43
|
+
If you got here via `npx @phren/cli init`, you're already set up. Restart Claude Code and you're good.
|
|
44
44
|
|
|
45
45
|
If you cloned manually:
|
|
46
46
|
|
|
47
|
-
1. Add the MCP server: `claude mcp add phren -- npx phren ~/.phren`
|
|
47
|
+
1. Add the MCP server: `claude mcp add phren -- npx @phren/cli ~/.phren`
|
|
48
48
|
2. Install skills: `/plugin marketplace add alaarab/phren` then `/plugin install phren@phren`
|
|
49
49
|
3. Restart Claude Code
|
|
50
|
-
4. Add a project: run `/phren-init my-project` or scaffold one with a template such as `
|
|
50
|
+
4. Add a project: run `/phren-init my-project` or scaffold one with a template such as `phren init --template python-project`
|
|
51
51
|
5. Push to a private GitHub repo to sync across machines
|
|
52
52
|
|
|
53
53
|
## Day-to-day workflow
|
|
@@ -56,7 +56,7 @@ If you cloned manually:
|
|
|
56
56
|
2. **Work normally**: Claude reads your project docs and builds on what phren remembers
|
|
57
57
|
3. **Fragments accumulate**: tell phren what you learned, or he picks up insights automatically
|
|
58
58
|
4. **Session ends**: phren commits and pushes what he collected
|
|
59
|
-
5. **Review occasionally**: run `
|
|
59
|
+
5. **Review occasionally**: run `phren shell` to triage what phren queued, manage tasks, and check health
|
|
60
60
|
|
|
61
61
|
## Syncing across machines
|
|
62
62
|
|
|
@@ -71,4 +71,4 @@ Each profile in `profiles/` lists which projects that machine should see. After
|
|
|
71
71
|
|
|
72
72
|
## Troubleshooting
|
|
73
73
|
|
|
74
|
-
Run `
|
|
74
|
+
Run `phren doctor --fix` to check and repair your setup.
|
package/starter/machines.yaml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# starter/templates/
|
|
2
2
|
|
|
3
|
-
Project templates bundled in the npm package, used by `
|
|
3
|
+
Project templates bundled in the npm package, used by `phren init --template <name>`.
|
|
4
4
|
|
|
5
5
|
Each subdirectory (frontend, library, monorepo, python-project) contains pre-filled project files with sensible defaults for that project type. When a user runs init with `--template`, these files are copied into their `~/.phren/<project>/` directory. For other project types, adaptive init infers topics and structure from repo content.
|
|
6
6
|
|