@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.
Files changed (100) hide show
  1. package/README.md +11 -17
  2. package/mcp/dist/capabilities/cli.js +1 -1
  3. package/mcp/dist/capabilities/mcp.js +1 -1
  4. package/mcp/dist/capabilities/vscode.js +1 -1
  5. package/mcp/dist/capabilities/web-ui.js +1 -1
  6. package/mcp/dist/cli-actions.js +58 -71
  7. package/mcp/dist/cli-config.js +337 -131
  8. package/mcp/dist/cli-extract.js +3 -2
  9. package/mcp/dist/cli-govern.js +35 -63
  10. package/mcp/dist/cli-graph.js +19 -4
  11. package/mcp/dist/cli-hooks-globs.js +2 -1
  12. package/mcp/dist/cli-hooks-output.js +4 -4
  13. package/mcp/dist/cli-hooks-session.js +1 -1
  14. package/mcp/dist/cli-hooks.js +44 -35
  15. package/mcp/dist/cli-namespaces.js +15 -5
  16. package/mcp/dist/cli-search.js +2 -2
  17. package/mcp/dist/cli.js +1 -1
  18. package/mcp/dist/content-archive.js +23 -14
  19. package/mcp/dist/content-citation.js +13 -2
  20. package/mcp/dist/content-dedup.js +9 -9
  21. package/mcp/dist/content-learning.js +6 -4
  22. package/mcp/dist/content-metadata.js +10 -0
  23. package/mcp/dist/core-finding.js +1 -1
  24. package/mcp/dist/data-access.js +10 -31
  25. package/mcp/dist/data-tasks.js +5 -26
  26. package/mcp/dist/embedding.js +7 -8
  27. package/mcp/dist/entrypoint.js +133 -102
  28. package/mcp/dist/finding-impact.js +1 -32
  29. package/mcp/dist/finding-journal.js +1 -1
  30. package/mcp/dist/finding-lifecycle.js +2 -7
  31. package/mcp/dist/governance-locks.js +12 -5
  32. package/mcp/dist/governance-policy.js +156 -9
  33. package/mcp/dist/governance-scores.js +4 -10
  34. package/mcp/dist/hooks.js +62 -18
  35. package/mcp/dist/index.js +4 -4
  36. package/mcp/dist/init-config.js +4 -25
  37. package/mcp/dist/init-preferences.js +1 -1
  38. package/mcp/dist/init-setup.js +6 -55
  39. package/mcp/dist/init-shared.js +53 -1
  40. package/mcp/dist/init.js +191 -29
  41. package/mcp/dist/link-checksums.js +3 -2
  42. package/mcp/dist/link-context.js +2 -2
  43. package/mcp/dist/link-doctor.js +14 -57
  44. package/mcp/dist/link-skills.js +98 -12
  45. package/mcp/dist/link.js +16 -75
  46. package/mcp/dist/machine-identity.js +1 -9
  47. package/mcp/dist/mcp-config.js +247 -42
  48. package/mcp/dist/mcp-data.js +9 -9
  49. package/mcp/dist/mcp-extract-facts.js +12 -7
  50. package/mcp/dist/mcp-extract.js +2 -2
  51. package/mcp/dist/mcp-finding.js +16 -20
  52. package/mcp/dist/mcp-graph.js +12 -12
  53. package/mcp/dist/mcp-hooks.js +1 -1
  54. package/mcp/dist/mcp-ops.js +18 -18
  55. package/mcp/dist/mcp-search.js +11 -16
  56. package/mcp/dist/mcp-session.js +12 -2
  57. package/mcp/dist/memory-ui-assets.js +1 -36
  58. package/mcp/dist/memory-ui-graph.js +152 -50
  59. package/mcp/dist/memory-ui-page.js +30 -5
  60. package/mcp/dist/memory-ui-scripts.js +252 -63
  61. package/mcp/dist/memory-ui-server.js +115 -3
  62. package/mcp/dist/phren-core.js +2 -0
  63. package/mcp/dist/phren-paths.js +8 -9
  64. package/mcp/dist/proactivity.js +5 -5
  65. package/mcp/dist/profile-store.js +2 -2
  66. package/mcp/dist/project-config.js +64 -17
  67. package/mcp/dist/provider-adapters.js +1 -1
  68. package/mcp/dist/query-correlation.js +22 -19
  69. package/mcp/dist/session-checkpoints.js +14 -14
  70. package/mcp/dist/session-utils.js +3 -2
  71. package/mcp/dist/shared-data-utils.js +28 -0
  72. package/mcp/dist/shared-fragment-graph.js +22 -21
  73. package/mcp/dist/shared-governance.js +1 -1
  74. package/mcp/dist/shared-index.js +144 -105
  75. package/mcp/dist/shared-retrieval.js +21 -23
  76. package/mcp/dist/shared-search-fallback.js +15 -25
  77. package/mcp/dist/shared-sqljs.js +3 -2
  78. package/mcp/dist/shared.js +5 -6
  79. package/mcp/dist/shell-entry.js +1 -1
  80. package/mcp/dist/shell-input.js +63 -53
  81. package/mcp/dist/shell-palette.js +6 -1
  82. package/mcp/dist/shell-render.js +9 -5
  83. package/mcp/dist/shell-state-store.js +2 -5
  84. package/mcp/dist/shell-view.js +7 -6
  85. package/mcp/dist/shell.js +5 -55
  86. package/mcp/dist/skill-files.js +4 -10
  87. package/mcp/dist/skill-registry.js +3 -0
  88. package/mcp/dist/status.js +43 -21
  89. package/mcp/dist/task-hygiene.js +1 -1
  90. package/mcp/dist/telemetry.js +5 -4
  91. package/mcp/dist/update.js +1 -1
  92. package/mcp/dist/utils.js +4 -4
  93. package/package.json +2 -3
  94. package/skills/docs.md +11 -11
  95. package/starter/README.md +1 -1
  96. package/starter/global/CLAUDE.md +2 -2
  97. package/starter/global/skills/audit.md +106 -0
  98. package/mcp/dist/cli-hooks-retrieval.js +0 -2
  99. package/mcp/dist/impact-scoring.js +0 -22
  100. package/mcp/dist/shared-paths.js +0 -1
@@ -4,11 +4,11 @@ import { queryDocRows, queryRows, cosineFallback, extractSnippet, getDocSourceKe
4
4
  import { filterTrustedFindingsDetailed, } from "./shared-content.js";
5
5
  import { parseCitationComment } from "./content-citation.js";
6
6
  import { getHighImpactFindings } from "./finding-impact.js";
7
- import { buildFtsQueryVariants, buildRelaxedFtsQuery, isFeatureEnabled, STOP_WORDS } from "./utils.js";
7
+ import { buildFtsQueryVariants, buildRelaxedFtsQuery, isFeatureEnabled, STOP_WORDS, errorMessage } from "./utils.js";
8
8
  import * as fs from "fs";
9
9
  import * as path from "path";
10
10
  import { getProjectGlobBoost } from "./cli-hooks-globs.js";
11
- import { vectorFallback } from "./shared-search-fallback.js";
11
+ import { vectorFallback, deterministicSeed } from "./shared-search-fallback.js";
12
12
  import { getOllamaUrl, getCloudEmbeddingUrl } from "./shared-ollama.js";
13
13
  import { keywordFallbackSearch } from "./core-search.js";
14
14
  import { debugLog } from "./shared.js";
@@ -34,17 +34,23 @@ const TASK_RESCUE_SCORE_MARGIN = 0.6;
34
34
  /** Fraction of bullets that must be low-value before applying the low-value penalty. */
35
35
  const LOW_VALUE_BULLET_FRACTION = 0.5;
36
36
  // ── Intent and scoring helpers ───────────────────────────────────────────────
37
+ const INTENT_SKILL_CMD_RE = /(?:^|\s)\/(?!(?:home|usr|var|tmp|etc|opt|api|mnt)\b)[a-z][\w-]*\b/;
38
+ const INTENT_SKILL_KW_RE = /\bskill\b/;
39
+ const INTENT_DEBUG_RE = /(bug|error|fix|broken|regression|fail|stack trace)/;
40
+ const INTENT_REVIEW_RE = /(review|audit|pr|pull request|nit|refactor)/;
41
+ const INTENT_BUILD_RE = /(build|deploy|release|ci|workflow|pipeline|test)/;
42
+ const INTENT_DOCS_RE = /\b(doc|docs|readme|explain|guide|instructions?)\b/;
37
43
  export function detectTaskIntent(prompt) {
38
44
  const p = prompt.toLowerCase();
39
- if (/(?:^|\s)\/(?!(?:home|usr|var|tmp|etc|opt|api|mnt)\b)[a-z][\w-]*\b/.test(p) || /\bskill\b/.test(p))
45
+ if (INTENT_SKILL_CMD_RE.test(p) || INTENT_SKILL_KW_RE.test(p))
40
46
  return "skill";
41
- if (/(bug|error|fix|broken|regression|fail|stack trace)/.test(p))
47
+ if (INTENT_DEBUG_RE.test(p))
42
48
  return "debug";
43
- if (/(review|audit|pr|pull request|nit|refactor)/.test(p))
49
+ if (INTENT_REVIEW_RE.test(p))
44
50
  return "review";
45
- if (/(build|deploy|release|ci|workflow|pipeline|test)/.test(p))
51
+ if (INTENT_BUILD_RE.test(p))
46
52
  return "build";
47
- if (/\b(doc|docs|readme|explain|guide|instructions?)\b/.test(p))
53
+ if (INTENT_DOCS_RE.test(p))
48
54
  return "docs";
49
55
  return "general";
50
56
  }
@@ -155,14 +161,6 @@ function docOverlapScore(queryTokens, doc) {
155
161
  const corpus = `${doc.project} ${doc.filename} ${doc.type} ${doc.path}\n${doc.content.slice(0, 5000)}`;
156
162
  return overlapScore(queryTokens, corpus);
157
163
  }
158
- function semanticFallbackSeed(text) {
159
- let hash = 2166136261;
160
- for (let i = 0; i < text.length; i++) {
161
- hash ^= text.charCodeAt(i);
162
- hash = Math.imul(hash, 16777619);
163
- }
164
- return hash >>> 0;
165
- }
166
164
  function loadSemanticFallbackWindow(db, startRowid, limit, project, wrapBefore) {
167
165
  const where = [
168
166
  project ? "project = ?" : "",
@@ -244,7 +242,7 @@ function semanticFallbackDocs(db, prompt, project) {
244
242
  const windowCount = Math.min(SEMANTIC_FALLBACK_WINDOW_COUNT, cappedLimit);
245
243
  const perWindow = Math.max(1, Math.ceil(cappedLimit / windowCount));
246
244
  const stride = Math.max(1, Math.floor(span / windowCount));
247
- const seed = semanticFallbackSeed(`${project ?? "*"}\n${terms.join(" ")}`);
245
+ const seed = deterministicSeed(`${project ?? "*"}\n${terms.join(" ")}`);
248
246
  for (let i = 0; i < windowCount && docs.length < cappedLimit; i++) {
249
247
  const offset = (seed + i * stride) % span;
250
248
  const startRowid = minRowid + offset;
@@ -385,8 +383,8 @@ export async function searchDocumentsAsync(db, safeQuery, prompt, keywords, dete
385
383
  }
386
384
  catch (err) {
387
385
  // Vector search failure is non-fatal — return sync result
388
- if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
389
- process.stderr.write(`[phren] hybridSearch vectorFallback: ${err instanceof Error ? err.message : String(err)}\n`);
386
+ if ((process.env.PHREN_DEBUG))
387
+ process.stderr.write(`[phren] hybridSearch vectorFallback: ${errorMessage(err)}\n`);
390
388
  return syncResult;
391
389
  }
392
390
  }
@@ -445,7 +443,7 @@ export async function searchKnowledgeRows(db, options) {
445
443
  }
446
444
  }
447
445
  catch (err) {
448
- debugLog(`rowid dedup query failed: ${err instanceof Error ? err.message : String(err)}`);
446
+ debugLog(`rowid dedup query failed: ${errorMessage(err)}`);
449
447
  }
450
448
  const cosineResults = cosineFallback(db, query, ftsRowids, maxResults - rows.length)
451
449
  .filter((doc) => (!filterProject || doc.project === filterProject) && (!filterType || doc.type === filterType));
@@ -485,8 +483,8 @@ export async function searchKnowledgeRows(db, options) {
485
483
  }
486
484
  }
487
485
  catch (err) {
488
- if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG)) {
489
- process.stderr.write(`[phren] vectorFallback: ${err instanceof Error ? err.message : String(err)}\n`);
486
+ if ((process.env.PHREN_DEBUG)) {
487
+ process.stderr.write(`[phren] vectorFallback: ${errorMessage(err)}\n`);
490
488
  }
491
489
  }
492
490
  }
@@ -732,8 +730,8 @@ export function markStaleCitations(snippet) {
732
730
  }
733
731
  }
734
732
  catch (err) {
735
- if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
736
- process.stderr.write(`[phren] applyCitationAnnotations fileRead: ${err instanceof Error ? err.message : String(err)}\n`);
733
+ if ((process.env.PHREN_DEBUG))
734
+ process.stderr.write(`[phren] applyCitationAnnotations fileRead: ${errorMessage(err)}\n`);
737
735
  stale = true;
738
736
  }
739
737
  }
@@ -1,6 +1,6 @@
1
1
  import { createHash } from "crypto";
2
2
  import { debugLog } from "./shared.js";
3
- import { STOP_WORDS } from "./utils.js";
3
+ import { STOP_WORDS, errorMessage } from "./utils.js";
4
4
  import { porterStem } from "./shared-stemmer.js";
5
5
  import { classifyFile, normalizeIndexedContent, rowToDocWithRowid } from "./shared-index.js";
6
6
  import { embedText, cosineSimilarity, getEmbeddingModel, getOllamaUrl, getCloudEmbeddingUrl } from "./shared-ollama.js";
@@ -72,7 +72,7 @@ function cachedTokenize(text) {
72
72
  tokenCache.set(key, tokens);
73
73
  return tokens;
74
74
  }
75
- function deterministicSeed(text) {
75
+ export function deterministicSeed(text) {
76
76
  let hash = 2166136261;
77
77
  for (let i = 0; i < text.length; i++) {
78
78
  hash ^= text.charCodeAt(i);
@@ -153,18 +153,8 @@ function tfidfCosine(docs, query, corpusN) {
153
153
  return termTf * idf;
154
154
  });
155
155
  }
156
- function cosine(a, b) {
157
- let dot = 0, normA = 0, normB = 0;
158
- for (let i = 0; i < a.length; i++) {
159
- dot += a[i] * b[i];
160
- normA += a[i] * a[i];
161
- normB += b[i] * b[i];
162
- }
163
- const denom = Math.sqrt(normA) * Math.sqrt(normB);
164
- return denom === 0 ? 0 : dot / denom;
165
- }
166
156
  const queryVec = buildVector(queryTokens);
167
- return docTokenLists.map(docTokens => cosine(queryVec, buildVector(docTokens)));
157
+ return docTokenLists.map(docTokens => cosineSimilarity(queryVec, buildVector(docTokens)));
168
158
  }
169
159
  /**
170
160
  * Cosine fallback search: when FTS5 returns fewer than COSINE_FALLBACK_THRESHOLD results,
@@ -191,8 +181,8 @@ export function cosineFallback(db, query, excludeRowids, limit) {
191
181
  }
192
182
  }
193
183
  catch (err) {
194
- if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
195
- process.stderr.write(`[phren] cosineFallback count: ${err instanceof Error ? err.message : String(err)}\n`);
184
+ if ((process.env.PHREN_DEBUG))
185
+ process.stderr.write(`[phren] cosineFallback count: ${errorMessage(err)}\n`);
196
186
  return [];
197
187
  }
198
188
  if (totalDocs > COSINE_MAX_CORPUS) {
@@ -220,8 +210,8 @@ export function cosineFallback(db, query, excludeRowids, limit) {
220
210
  ftsRows.push(...ftsRes[0].values);
221
211
  }
222
212
  catch (err) {
223
- if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
224
- process.stderr.write(`[phren] cosineFallback FTS pre-filter: ${err instanceof Error ? err.message : String(err)}\n`);
213
+ if ((process.env.PHREN_DEBUG))
214
+ process.stderr.write(`[phren] cosineFallback FTS pre-filter: ${errorMessage(err)}\n`);
225
215
  }
226
216
  }
227
217
  // If FTS gave fewer than cap, supplement with deterministic rowid windows.
@@ -258,8 +248,8 @@ export function cosineFallback(db, query, excludeRowids, limit) {
258
248
  }
259
249
  }
260
250
  catch (err) {
261
- if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
262
- process.stderr.write(`[phren] cosineFallback deterministicSample: ${err instanceof Error ? err.message : String(err)}\n`);
251
+ if ((process.env.PHREN_DEBUG))
252
+ process.stderr.write(`[phren] cosineFallback deterministicSample: ${errorMessage(err)}\n`);
263
253
  }
264
254
  }
265
255
  if (ftsRows.length === 0)
@@ -269,8 +259,8 @@ export function cosineFallback(db, query, excludeRowids, limit) {
269
259
  }
270
260
  }
271
261
  catch (err) {
272
- if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
273
- process.stderr.write(`[phren] cosineFallback loadDocs: ${err instanceof Error ? err.message : String(err)}\n`);
262
+ if ((process.env.PHREN_DEBUG))
263
+ process.stderr.write(`[phren] cosineFallback loadDocs: ${errorMessage(err)}\n`);
274
264
  return [];
275
265
  }
276
266
  // Separate rowids, DocRows, and content strings for scoring
@@ -315,8 +305,8 @@ export async function vectorFallback(phrenPath, query, excludePaths, limit, proj
315
305
  await cache.load();
316
306
  }
317
307
  catch (err) {
318
- if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
319
- process.stderr.write(`[phren] vectorFallback cacheLoad: ${err instanceof Error ? err.message : String(err)}\n`);
308
+ if ((process.env.PHREN_DEBUG))
309
+ process.stderr.write(`[phren] vectorFallback cacheLoad: ${errorMessage(err)}\n`);
320
310
  }
321
311
  }
322
312
  if (cache.size() === 0)
@@ -367,8 +357,8 @@ export async function vectorFallback(phrenPath, query, excludePaths, limit, proj
367
357
  }
368
358
  }
369
359
  catch (err) {
370
- if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
371
- process.stderr.write(`[phren] vectorFallback fileRead: ${err instanceof Error ? err.message : String(err)}\n`);
360
+ if ((process.env.PHREN_DEBUG))
361
+ process.stderr.write(`[phren] vectorFallback fileRead: ${errorMessage(err)}\n`);
372
362
  }
373
363
  return { project: entryProject, filename, type, content, path: e.path };
374
364
  });
@@ -2,6 +2,7 @@ import * as fs from "fs";
2
2
  import * as path from "path";
3
3
  import { fileURLToPath } from "url";
4
4
  import { createRequire } from "module";
5
+ import { errorMessage } from "./utils.js";
5
6
  const require = createRequire(import.meta.url);
6
7
  /**
7
8
  * Locate the sql.js-fts5 WASM binary by require.resolve with path-probe fallback.
@@ -14,8 +15,8 @@ function findWasmBinary() {
14
15
  return fs.readFileSync(resolved);
15
16
  }
16
17
  catch (err) {
17
- if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
18
- process.stderr.write(`[phren] findWasmBinary requireResolve: ${err instanceof Error ? err.message : String(err)}\n`);
18
+ if ((process.env.PHREN_DEBUG))
19
+ process.stderr.write(`[phren] findWasmBinary requireResolve: ${errorMessage(err)}\n`);
19
20
  // fall through to path probing
20
21
  }
21
22
  const __filename = fileURLToPath(import.meta.url);
@@ -3,10 +3,9 @@ import * as path from "path";
3
3
  import { debugLog, runtimeFile } from "./phren-paths.js";
4
4
  import { errorMessage } from "./utils.js";
5
5
  export { HOOK_TOOL_NAMES, hookConfigPath } from "./provider-adapters.js";
6
- export { EXEC_TIMEOUT_MS, EXEC_TIMEOUT_QUICK_MS, PhrenError, phrenOk, phrenErr, forwardErr, parsePhrenErrorCode, isRecord, withDefaults, FINDING_TYPES, FINDING_TAGS, KNOWN_OBSERVATION_TAGS, DOC_TYPES, capCache, } from "./phren-core.js";
6
+ export { EXEC_TIMEOUT_MS, EXEC_TIMEOUT_QUICK_MS, PhrenError, phrenOk, phrenErr, forwardErr, parsePhrenErrorCode, isRecord, withDefaults, FINDING_TYPES, FINDING_TAGS, KNOWN_OBSERVATION_TAGS, DOC_TYPES, capCache, RESERVED_PROJECT_DIR_NAMES, } from "./phren-core.js";
7
7
  export { ROOT_MANIFEST_FILENAME, homeDir, homePath, expandHomePath, defaultPhrenPath, rootManifestPath, readRootManifest, writeRootManifest, resolveInstallContext, findNearestPhrenPath, isProjectLocalMode, runtimeDir, tryUnlink, sessionsDir, runtimeFile, installPreferencesFile, runtimeHealthFile, shellStateFile, sessionMetricsFile, memoryScoresFile, memoryUsageLogFile, sessionMarker, debugLog, appendIndexEvent, resolveFindingsPath, findPhrenPath, ensurePhrenPath, findPhrenPathWithArg, normalizeProjectNameForCreate, findProjectNameCaseInsensitive, getProjectDirs, collectNativeMemoryFiles, computePhrenLiveStateToken, getPhrenPath, qualityMarkers, atomicWriteText, } from "./phren-paths.js";
8
8
  export { PROACTIVITY_LEVELS, getProactivityLevel, getProactivityLevelForFindings, getProactivityLevelForTask, hasExplicitFindingSignal, hasExplicitTaskSignal, hasExecutionIntent, hasDiscoveryIntent, shouldAutoCaptureFindingsForLevel, shouldAutoCaptureTaskForLevel, } from "./proactivity.js";
9
- const RESERVED_PROJECT_DIR_NAMES = new Set(["profiles", "templates", "global"]);
10
9
  const MEMORY_SCOPE_PATTERN = /^[a-z][a-z0-9_-]{0,63}$/;
11
10
  export function normalizeMemoryScope(scope) {
12
11
  if (typeof scope !== "string")
@@ -50,7 +49,7 @@ export function appendAuditLog(phrenPath, event, details) {
50
49
  break;
51
50
  }
52
51
  catch (err) {
53
- if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
52
+ if ((process.env.PHREN_DEBUG))
54
53
  process.stderr.write(`[phren] appendAuditLog lockWrite: ${errorMessage(err)}\n`);
55
54
  try {
56
55
  const stat = fs.statSync(lockPath);
@@ -68,7 +67,7 @@ export function appendAuditLog(phrenPath, event, details) {
68
67
  }
69
68
  }
70
69
  catch (statErr) {
71
- if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
70
+ if ((process.env.PHREN_DEBUG))
72
71
  process.stderr.write(`[phren] appendAuditLog staleStat: ${errorMessage(statErr)}\n`);
73
72
  }
74
73
  Atomics.wait(waiter, 0, 0, pollMs);
@@ -81,7 +80,7 @@ export function appendAuditLog(phrenPath, event, details) {
81
80
  if (stat.size > 1_000_000) {
82
81
  const content = fs.readFileSync(logPath, "utf8");
83
82
  const lines = content.split("\n");
84
- fs.writeFileSync(logPath, lines.slice(-500).join("\n"));
83
+ fs.writeFileSync(logPath, lines.slice(-500).join("\n") + "\n");
85
84
  }
86
85
  }
87
86
  else {
@@ -97,7 +96,7 @@ export function appendAuditLog(phrenPath, event, details) {
97
96
  fs.unlinkSync(lockPath);
98
97
  }
99
98
  catch (err) {
100
- if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
99
+ if ((process.env.PHREN_DEBUG))
101
100
  process.stderr.write(`[phren] appendAuditLog unlock: ${errorMessage(err)}\n`);
102
101
  }
103
102
  }
@@ -147,7 +147,7 @@ export async function startShell(phrenPath, profile) {
147
147
  const shell = new PhrenShell(phrenPath, profile);
148
148
  if (!process.stdin.isTTY) {
149
149
  const { createInterface } = await import("readline");
150
- const rl = createInterface({ input: process.stdin, output: process.stdout, terminal: true });
150
+ const rl = createInterface({ input: process.stdin, output: process.stdout, terminal: process.stdin.isTTY ?? false });
151
151
  const repaint = async () => { clearScreen(); process.stdout.write(await shell.render()); rl.setPrompt(`\n${style.boldCyan(":phren>")} `); rl.prompt(); };
152
152
  const stopPoll = startLiveStatePoller({ phrenPath, shell, repaint });
153
153
  await repaint();
@@ -379,7 +379,7 @@ export async function executePalette(host, input) {
379
379
  }
380
380
  }
381
381
  catch (err) {
382
- if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
382
+ if ((process.env.PHREN_DEBUG))
383
383
  process.stderr.write(`[phren] shell status gitStatus: ${errorMessage(err)}\n`);
384
384
  }
385
385
  const auditPathNew = runtimeFile(host.phrenPath, "audit.log");
@@ -740,6 +740,67 @@ function showCursorPosition(host) {
740
740
  const short = label.length > 50 ? label.slice(0, 48) + "…" : label;
741
741
  host.setMessage(` ${style.dim(`${cursor + 1} / ${count}`)}${short ? ` ${style.dimItalic(short)}` : ""}`);
742
742
  }
743
+ // ── View shortcut keys (shared between handleInput text mode and handleNavigateKey) ─
744
+ /**
745
+ * Handle p/b/l/m/s/k/h shortcut keys that switch the active view.
746
+ * Returns true if the key was handled.
747
+ */
748
+ export function applyViewShortcut(host, key) {
749
+ if (key === "p") {
750
+ host.setView("Projects");
751
+ host.setMessage(` ${TAB_ICONS.Projects} Projects`);
752
+ return true;
753
+ }
754
+ if (key === "b") {
755
+ if (!host.state.project) {
756
+ host.setMessage(style.dim(" Select a project first (↵)"));
757
+ return true;
758
+ }
759
+ host.setView("Tasks");
760
+ host.setMessage(` ${TAB_ICONS.Tasks} Tasks`);
761
+ return true;
762
+ }
763
+ if (key === "l") {
764
+ if (!host.state.project) {
765
+ host.setMessage(style.dim(" Select a project first (↵)"));
766
+ return true;
767
+ }
768
+ host.setView("Findings");
769
+ host.setMessage(` ${TAB_ICONS.Findings} Findings`);
770
+ return true;
771
+ }
772
+ if (key === "m") {
773
+ if (!host.state.project) {
774
+ host.setMessage(style.dim(" Select a project first (↵)"));
775
+ return true;
776
+ }
777
+ host.setView("Review Queue");
778
+ host.setMessage(` ${TAB_ICONS["Review Queue"]} Review Queue`);
779
+ return true;
780
+ }
781
+ if (key === "s") {
782
+ if (!host.state.project) {
783
+ host.setMessage(style.dim(" Select a project first (↵)"));
784
+ return true;
785
+ }
786
+ host.setView("Skills");
787
+ host.setMessage(` ${TAB_ICONS.Skills} Skills`);
788
+ return true;
789
+ }
790
+ if (key === "k") {
791
+ host.setView("Hooks");
792
+ host.setMessage(` ${TAB_ICONS.Hooks} Hooks`);
793
+ return true;
794
+ }
795
+ if (key === "h") {
796
+ host.prevHealthView = host.state.view === "Health" ? host.prevHealthView : host.state.view;
797
+ host.healthCache = undefined;
798
+ host.setView("Health");
799
+ host.setMessage(` ${TAB_ICONS.Health} Health ${style.dim("(esc to return)")}`);
800
+ return true;
801
+ }
802
+ return false;
803
+ }
743
804
  // ── Navigate-mode key handler ─────────────────────────────────────────────────
744
805
  export async function handleNavigateKey(host, key) {
745
806
  if (key === "\x1b[A") {
@@ -836,59 +897,8 @@ export async function handleNavigateKey(host, key) {
836
897
  }
837
898
  return true;
838
899
  }
839
- if (key === "p") {
840
- host.setView("Projects");
841
- host.setMessage(` ${TAB_ICONS.Projects} Projects`);
842
- return true;
843
- }
844
- if (key === "b") {
845
- if (!host.state.project) {
846
- host.setMessage(style.dim(" Select a project first (↵)"));
847
- return true;
848
- }
849
- host.setView("Tasks");
850
- host.setMessage(` ${TAB_ICONS.Tasks} Tasks`);
900
+ if (applyViewShortcut(host, key))
851
901
  return true;
852
- }
853
- if (key === "l") {
854
- if (!host.state.project) {
855
- host.setMessage(style.dim(" Select a project first (↵)"));
856
- return true;
857
- }
858
- host.setView("Findings");
859
- host.setMessage(` ${TAB_ICONS.Findings} Fragments`);
860
- return true;
861
- }
862
- if (key === "m") {
863
- if (!host.state.project) {
864
- host.setMessage(style.dim(" Select a project first (↵)"));
865
- return true;
866
- }
867
- host.setView("Review Queue");
868
- host.setMessage(` ${TAB_ICONS["Review Queue"]} Review Queue`);
869
- return true;
870
- }
871
- if (key === "s") {
872
- if (!host.state.project) {
873
- host.setMessage(style.dim(" Select a project first (↵)"));
874
- return true;
875
- }
876
- host.setView("Skills");
877
- host.setMessage(` ${TAB_ICONS.Skills} Skills`);
878
- return true;
879
- }
880
- if (key === "k") {
881
- host.setView("Hooks");
882
- host.setMessage(` ${TAB_ICONS.Hooks} Hooks`);
883
- return true;
884
- }
885
- if (key === "h") {
886
- host.prevHealthView = host.state.view === "Health" ? host.prevHealthView : host.state.view;
887
- host.healthCache = undefined;
888
- host.setView("Health");
889
- host.setMessage(` ${TAB_ICONS.Health} Health ${style.dim("(esc to return)")}`);
890
- return true;
891
- }
892
902
  if (key === "i" && host.state.view === "Projects") {
893
903
  const next = host.state.introMode === "always" ? "once-per-version" : host.state.introMode === "off" ? "always" : "off";
894
904
  host.state.introMode = next;
@@ -7,7 +7,12 @@ import { EXEC_TIMEOUT_MS, } from "./shared.js";
7
7
  export function resultMsg(r) {
8
8
  if (!r.ok)
9
9
  return r.error;
10
- return typeof r.data === "string" ? r.data : JSON.stringify(r.data);
10
+ if (typeof r.data === "string")
11
+ return r.data;
12
+ if (r.data && typeof r.data === "object" && "message" in r.data && typeof r.data.message === "string") {
13
+ return r.data.message;
14
+ }
15
+ return JSON.stringify(r.data);
11
16
  }
12
17
  export function editDistance(a, b) {
13
18
  const m = a.length;
@@ -1,6 +1,12 @@
1
1
  // ── ANSI utilities ──────────────────────────────────────────────────────────
2
2
  const ESC = "\x1b[";
3
3
  export const RESET = `${ESC}0m`;
4
+ export const BOLD = `${ESC}1m`;
5
+ export const DIM = `${ESC}2m`;
6
+ export const GREEN = `${ESC}32m`;
7
+ export const YELLOW = `${ESC}33m`;
8
+ export const RED = `${ESC}31m`;
9
+ export const CYAN = `${ESC}36m`;
4
10
  export const style = {
5
11
  bold: (s) => `${ESC}1m${s}${RESET}`,
6
12
  dim: (s) => `${ESC}2m${s}${RESET}`,
@@ -144,9 +150,7 @@ const PHREN_LOGO = [
144
150
  "██║ ██║ ██║██║ ██║███████╗██║ ╚████║",
145
151
  "╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═══╝",
146
152
  ];
147
- // Compact phren character for startup (uses PHREN_ART from phren-art.ts via import)
148
153
  import { PHREN_ART as PHREN_STARTUP_ART } from "./phren-art.js";
149
- const PHREN_STARTUP = PHREN_STARTUP_ART;
150
154
  // ── Line-based viewport: edge-triggered scroll (stable, no jumpiness) ─────────
151
155
  export function lineViewport(allLines, cursorFirstLine, cursorLastLine, height, prevStart) {
152
156
  if (allLines.length === 0 || height <= 0)
@@ -181,7 +185,7 @@ export function shellHelpText() {
181
185
  hdr("View-specific keys"),
182
186
  ` ${style.bold("Projects")} ${k("↵")} ${d("open project tasks")} ${k("i")} ${d("cycle intro mode")}`,
183
187
  ` ${style.bold("Tasks")} ${k("a")} ${d("add task")} ${k("d")} ${d("toggle active/queue")} ${k("↵")} ${d("mark complete")}`,
184
- ` ${style.bold("Fragments")} ${k("a")} ${d("tell phren")} ${k("d")} ${d("delete selected")}`,
188
+ ` ${style.bold("Findings")} ${k("a")} ${d("tell phren")} ${k("d")} ${d("delete selected")}`,
185
189
  ` ${style.bold("Review Queue")} ${k("↵")} ${d("inspect selected item")} ${d("(read-only)")}`,
186
190
  ` ${style.bold("Skills")} ${k("t")} ${d("toggle enabled")} ${k("d")} ${d("remove")}`,
187
191
  "",
@@ -221,7 +225,7 @@ export function shellStartupFrames(version) {
221
225
  const versionBadge = badge(`v${version}`, style.boldBlue);
222
226
  if (cols >= 72) {
223
227
  // Side-by-side: phren character on left, logo text on right
224
- const phrenLines = PHREN_STARTUP;
228
+ const phrenLines = PHREN_STARTUP_ART;
225
229
  const logoLines = PHREN_LOGO.map(line => gradient(line));
226
230
  const infoLine = `${gradient("◆")} ${style.bold("phren")} ${versionBadge} ${tagline}`;
227
231
  // Logo is 6 lines, pad to align vertically with character center
@@ -252,7 +256,7 @@ export function shellStartupFrames(version) {
252
256
  ];
253
257
  }
254
258
  // Narrow terminal: progressive text reveal with gradient
255
- const stages = ["c", "cor", "phren"];
259
+ const stages = ["p", "phr", "phren"];
256
260
  const spinners = ["◜", "◠", "◝"];
257
261
  return stages.map((stage, i) => [
258
262
  "",
@@ -1,7 +1,7 @@
1
1
  import * as fs from "fs";
2
2
  import * as path from "path";
3
3
  import { phrenErr, PhrenError, phrenOk, shellStateFile } from "./shared.js";
4
- import { getRuntimeHealth, withFileLock as withFileLockRaw } from "./shared-governance.js";
4
+ import { withFileLock as withFileLockRaw } from "./shared-governance.js";
5
5
  import { errorMessage } from "./utils.js";
6
6
  function withSafeLock(filePath, fn) {
7
7
  try {
@@ -45,7 +45,7 @@ export function loadShellState(phrenPath) {
45
45
  };
46
46
  }
47
47
  catch (err) {
48
- if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
48
+ if ((process.env.PHREN_DEBUG))
49
49
  process.stderr.write(`[phren] loadShellState parse: ${errorMessage(err)}\n`);
50
50
  return fallback;
51
51
  }
@@ -76,6 +76,3 @@ export function resetShellState(phrenPath) {
76
76
  return phrenOk("Shell state reset.");
77
77
  });
78
78
  }
79
- export function readRuntimeHealth(phrenPath) {
80
- return getRuntimeHealth(phrenPath);
81
- }
@@ -13,6 +13,7 @@ import { listMachines, listProfiles, } from "./data-access.js";
13
13
  import { readInstallPreferences } from "./init-preferences.js";
14
14
  import { isProjectHookEnabled, readProjectConfig } from "./project-config.js";
15
15
  import { getScopedSkills } from "./skill-registry.js";
16
+ import { errorMessage } from "./utils.js";
16
17
  // ── Tab bar ────────────────────────────────────────────────────────────────
17
18
  function renderTabBar(state) {
18
19
  const cols = renderWidth();
@@ -66,7 +67,7 @@ function renderBottomBar(state, navMode, inputCtx, inputBuf) {
66
67
  Tasks: [`${k("a")} ${d("add")}`, `${k("↵")} ${d("mark done")}`, `${k("d")} ${d("toggle active")}`],
67
68
  Findings: [`${k("a")} ${d("add")}`, `${k("d")} ${d("remove")}`],
68
69
  "Review Queue": [`${k("↵")} ${d("inspect")}`],
69
- Skills: [`${k("t")} ${d("toggle")}`, `${k("d")} ${d("remove")}`],
70
+ Skills: [`${k("a")} ${d("add")}`, `${k("t")} ${d("toggle")}`, `${k("d")} ${d("remove")}`],
70
71
  Hooks: [`${k("a")} ${d("enable")}`, `${k("d")} ${d("disable")}`],
71
72
  Health: [`${k("↑↓")} ${d("scroll")}`, `${k("esc")} ${d("back")}`],
72
73
  };
@@ -135,9 +136,9 @@ function renderProjectsDashboard(ctx, entries, height) {
135
136
  const findingsPreview = scoped
136
137
  .filter((entry) => entry.findingCount > 0)
137
138
  .slice(0, 3)
138
- .map((entry) => `${style.bold(entry.name)} ${style.dim(`${entry.findingCount} fragments`)}`);
139
+ .map((entry) => `${style.bold(entry.name)} ${style.dim(`${entry.findingCount} findings`)}`);
139
140
  const lines = [
140
- ` ${badge(ctx.profile || "default", style.boldBlue)} ${style.bold(String(scoped.length))} projects ${style.dim("·")} ${style.boldGreen(String(totals.active))} active ${style.dim("·")} ${style.boldYellow(String(totals.queue))} queued ${style.dim("·")} ${style.boldCyan(String(totals.findings))} fragments ${style.dim("·")} ${style.boldMagenta(String(totals.review))} review`,
141
+ ` ${badge(ctx.profile || "default", style.boldBlue)} ${style.bold(String(scoped.length))} projects ${style.dim("·")} ${style.boldGreen(String(totals.active))} active ${style.dim("·")} ${style.boldYellow(String(totals.queue))} queued ${style.dim("·")} ${style.boldCyan(String(totals.findings))} findings ${style.dim("·")} ${style.boldMagenta(String(totals.review))} review`,
141
142
  ctx.state.project
142
143
  ? ` ${style.green("●")} active context ${style.boldCyan(ctx.state.project)} ${style.dim("· ↵ opens selected project tasks")}`
143
144
  : ` ${style.dim("No project selected yet")} ${style.dim("· ↵ sets context and opens tasks")}`,
@@ -248,8 +249,8 @@ function parseSubsections(taskPath, project, cache) {
248
249
  }
249
250
  }
250
251
  catch (err) {
251
- if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
252
- process.stderr.write(`[phren] buildSubsectionMap: ${err instanceof Error ? err.message : String(err)}\n`);
252
+ if ((process.env.PHREN_DEBUG))
253
+ process.stderr.write(`[phren] buildSubsectionMap: ${errorMessage(err)}\n`);
253
254
  }
254
255
  const newCache = { project, map };
255
256
  return { map, cache: newCache };
@@ -516,7 +517,7 @@ function renderSkillsView(ctx, cursor, height) {
516
517
  }
517
518
  const LIFECYCLE_HOOKS = [
518
519
  { event: "UserPromptSubmit", description: "inject context before each prompt" },
519
- { event: "Stop", description: "phren saves fragments after each response" },
520
+ { event: "Stop", description: "phren saves findings after each response" },
520
521
  { event: "SessionStart", description: "git pull at session start" },
521
522
  ];
522
523
  export function getHookEntries(phrenPath, project) {
package/mcp/dist/shell.js CHANGED
@@ -2,11 +2,11 @@ import * as fs from "fs";
2
2
  import * as path from "path";
3
3
  import { addTask, addFinding, loadShellState, saveShellState, } from "./data-access.js";
4
4
  import { style } from "./shell-render.js";
5
- import { MAX_UNDO_STACK, TAB_ICONS, } from "./shell-types.js";
5
+ import { MAX_UNDO_STACK, } from "./shell-types.js";
6
6
  import { resultMsg, defaultRunHooks, defaultRunUpdate, defaultRunRelink, } from "./shell-palette.js";
7
7
  import { runDoctor } from "./link.js";
8
8
  import { renderShell, } from "./shell-view.js";
9
- import { executePalette, completeInput as completeInputFn, getListItems, handleNavigateKey, } from "./shell-input.js";
9
+ import { executePalette, completeInput as completeInputFn, getListItems, handleNavigateKey, applyViewShortcut, } from "./shell-input.js";
10
10
  import { errorMessage } from "./utils.js";
11
11
  // ── Shell class ──────────────────────────────────────────────────────────────
12
12
  export class PhrenShell {
@@ -16,7 +16,7 @@ export class PhrenShell {
16
16
  state;
17
17
  message = ` ${style.boldCyan("←→")} ${style.dim("tabs")} ${style.boldCyan("↑↓")} ${style.dim("move")} ${style.boldCyan("↵")} ${style.dim("activate")} ${style.boldCyan("?")} ${style.dim("help")}`;
18
18
  healthCache;
19
- prevHealthView;
19
+ prevHealthView = undefined;
20
20
  showHelp = false;
21
21
  pendingConfirm;
22
22
  undoStack = [];
@@ -72,7 +72,7 @@ export class PhrenShell {
72
72
  }
73
73
  }
74
74
  catch (err) {
75
- if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
75
+ if ((process.env.PHREN_DEBUG))
76
76
  process.stderr.write(`[phren] shell pushUndo: ${errorMessage(err)}\n`);
77
77
  }
78
78
  }
@@ -277,58 +277,8 @@ export class PhrenShell {
277
277
  return true;
278
278
  if (["q", "quit", ":q", ":quit", ":exit"].includes(input.toLowerCase()))
279
279
  return false;
280
- if (input === "p") {
281
- this.setView("Projects");
282
- this.setMessage(` ${TAB_ICONS.Projects} Projects`);
280
+ if (applyViewShortcut(this.asNavigationHost(), input))
283
281
  return true;
284
- }
285
- if (input === "b") {
286
- if (!this.state.project) {
287
- this.setMessage(style.dim(" Select a project first (↵)"));
288
- return true;
289
- }
290
- this.setView("Tasks");
291
- this.setMessage(` ${TAB_ICONS.Tasks} Tasks`);
292
- return true;
293
- }
294
- if (input === "l") {
295
- if (!this.state.project) {
296
- this.setMessage(style.dim(" Select a project first (↵)"));
297
- return true;
298
- }
299
- this.setView("Findings");
300
- this.setMessage(` ${TAB_ICONS.Findings} Fragments`);
301
- return true;
302
- }
303
- if (input === "m") {
304
- if (!this.state.project) {
305
- this.setMessage(style.dim(" Select a project first (↵)"));
306
- return true;
307
- }
308
- this.setView("Review Queue");
309
- this.setMessage(` ${TAB_ICONS["Review Queue"]} Review Queue`);
310
- return true;
311
- }
312
- if (input === "s") {
313
- if (!this.state.project) {
314
- this.setMessage(style.dim(" Select a project first (↵)"));
315
- return true;
316
- }
317
- this.setView("Skills");
318
- this.setMessage(` ${TAB_ICONS.Skills} Skills`);
319
- return true;
320
- }
321
- if (input === "k") {
322
- this.setView("Hooks");
323
- this.setMessage(` ${TAB_ICONS.Hooks} Hooks`);
324
- return true;
325
- }
326
- if (input === "h") {
327
- this.healthCache = undefined;
328
- this.setView("Health");
329
- this.setMessage(` ${TAB_ICONS.Health} Health`);
330
- return true;
331
- }
332
282
  if (input.startsWith("/")) {
333
283
  this.setFilter(input.slice(1));
334
284
  return true;