@phren/cli 0.0.28 → 0.0.33

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 (153) hide show
  1. package/mcp/dist/capabilities/cli.js +2 -5
  2. package/mcp/dist/capabilities/mcp.js +5 -8
  3. package/mcp/dist/capabilities/types.js +2 -5
  4. package/mcp/dist/capabilities/vscode.js +2 -5
  5. package/mcp/dist/capabilities/web-ui.js +2 -5
  6. package/mcp/dist/{cli-actions.js → cli/actions.js} +25 -21
  7. package/mcp/dist/{cli.js → cli/cli.js} +13 -13
  8. package/mcp/dist/{cli-config.js → cli/config.js} +12 -12
  9. package/mcp/dist/{cli-extract.js → cli/extract.js} +8 -8
  10. package/mcp/dist/{cli-govern.js → cli/govern.js} +28 -17
  11. package/mcp/dist/{cli-graph.js → cli/graph.js} +10 -9
  12. package/mcp/dist/{cli-hooks-citations.js → cli/hooks-citations.js} +2 -2
  13. package/mcp/dist/{cli-hooks-context.js → cli/hooks-context.js} +23 -23
  14. package/mcp/dist/{cli-hooks-globs.js → cli/hooks-globs.js} +4 -4
  15. package/mcp/dist/{cli-hooks-output.js → cli/hooks-output.js} +9 -10
  16. package/mcp/dist/{cli-hooks-session.js → cli/hooks-session.js} +58 -117
  17. package/mcp/dist/{cli-hooks.js → cli/hooks.js} +27 -26
  18. package/mcp/dist/{cli-namespaces.js → cli/namespaces.js} +25 -24
  19. package/mcp/dist/{cli-ops.js → cli/ops.js} +9 -9
  20. package/mcp/dist/{cli-search.js → cli/search.js} +12 -11
  21. package/mcp/dist/cli-hooks-git.js +243 -0
  22. package/mcp/dist/cli-hooks-prompt.js +323 -0
  23. package/mcp/dist/cli-hooks-session-handlers.js +337 -0
  24. package/mcp/dist/cli-hooks-stop.js +519 -0
  25. package/mcp/dist/{content-archive.js → content/archive.js} +16 -29
  26. package/mcp/dist/{content-citation.js → content/citation.js} +5 -5
  27. package/mcp/dist/{content-dedup.js → content/dedup.js} +9 -12
  28. package/mcp/dist/{content-learning.js → content/learning.js} +41 -20
  29. package/mcp/dist/{content-validate.js → content/validate.js} +5 -5
  30. package/mcp/dist/{core-finding.js → core/finding.js} +4 -4
  31. package/mcp/dist/{core-project.js → core/project.js} +4 -4
  32. package/mcp/dist/{core-search.js → core/search.js} +2 -2
  33. package/mcp/dist/{data-access.js → data/access.js} +142 -15
  34. package/mcp/dist/{data-tasks.js → data/tasks.js} +7 -5
  35. package/mcp/dist/embedding.js +9 -14
  36. package/mcp/dist/entrypoint.js +11 -11
  37. package/mcp/dist/{finding-context.js → finding/context.js} +2 -2
  38. package/mcp/dist/{finding-impact.js → finding/impact.js} +3 -3
  39. package/mcp/dist/{finding-journal.js → finding/journal.js} +4 -4
  40. package/mcp/dist/{finding-lifecycle.js → finding/lifecycle.js} +13 -7
  41. package/mcp/dist/governance/audit.js +30 -0
  42. package/mcp/dist/{governance-locks.js → governance/locks.js} +14 -9
  43. package/mcp/dist/{governance-policy.js → governance/policy.js} +23 -12
  44. package/mcp/dist/{governance-rbac.js → governance/rbac.js} +4 -4
  45. package/mcp/dist/{governance-scores.js → governance/scores.js} +10 -11
  46. package/mcp/dist/hooks.js +53 -37
  47. package/mcp/dist/index-query.js +4 -1
  48. package/mcp/dist/index.js +54 -30
  49. package/mcp/dist/{init-config.js → init/config.js} +6 -6
  50. package/mcp/dist/{init.js → init/init.js} +80 -69
  51. package/mcp/dist/{init-preferences.js → init/preferences.js} +3 -3
  52. package/mcp/dist/{init-setup.js → init/setup.js} +17 -19
  53. package/mcp/dist/{init-shared.js → init/shared.js} +4 -4
  54. package/mcp/dist/init-bootstrap.js +21 -0
  55. package/mcp/dist/init-detect.js +38 -0
  56. package/mcp/dist/init-env.js +114 -0
  57. package/mcp/dist/init-fresh.js +234 -0
  58. package/mcp/dist/init-hooks.js +26 -0
  59. package/mcp/dist/init-mcp.js +65 -0
  60. package/mcp/dist/init-modes.js +135 -0
  61. package/mcp/dist/init-npm.js +37 -0
  62. package/mcp/dist/init-project-local.js +99 -0
  63. package/mcp/dist/init-semantic.js +48 -0
  64. package/mcp/dist/init-types.js +1 -0
  65. package/mcp/dist/init-uninstall.js +504 -0
  66. package/mcp/dist/init-update.js +96 -0
  67. package/mcp/dist/init-walkthrough.js +524 -0
  68. package/mcp/dist/{link-checksums.js → link/checksums.js} +5 -5
  69. package/mcp/dist/{link-context.js → link/context.js} +4 -4
  70. package/mcp/dist/{link-doctor.js → link/doctor.js} +20 -22
  71. package/mcp/dist/{link.js → link/link.js} +26 -31
  72. package/mcp/dist/{link-skills.js → link/skills.js} +10 -10
  73. package/mcp/dist/logger.js +11 -3
  74. package/mcp/dist/package-metadata.js +1 -1
  75. package/mcp/dist/phren-art.js +4 -126
  76. package/mcp/dist/phren-paths.js +30 -12
  77. package/mcp/dist/proactivity.js +3 -3
  78. package/mcp/dist/profile-store.js +5 -6
  79. package/mcp/dist/project-config.js +2 -2
  80. package/mcp/dist/project-topics.js +17 -47
  81. package/mcp/dist/provider-adapters.js +1 -1
  82. package/mcp/dist/query-correlation.js +1 -1
  83. package/mcp/dist/runtime-profile.js +1 -1
  84. package/mcp/dist/{session-checkpoints.js → session/checkpoints.js} +3 -3
  85. package/mcp/dist/{session-utils.js → session/utils.js} +1 -1
  86. package/mcp/dist/{shared-content.js → shared/content.js} +7 -7
  87. package/mcp/dist/{shared-data-utils.js → shared/data-utils.js} +28 -3
  88. package/mcp/dist/{shared-embedding-cache.js → shared/embedding-cache.js} +3 -3
  89. package/mcp/dist/{shared-fragment-graph.js → shared/fragment-graph.js} +19 -42
  90. package/mcp/dist/shared/governance.js +4 -0
  91. package/mcp/dist/{shared-index.js → shared/index.js} +105 -132
  92. package/mcp/dist/{shared-ollama.js → shared/ollama.js} +25 -7
  93. package/mcp/dist/shared/process.js +24 -0
  94. package/mcp/dist/{shared-retrieval.js → shared/retrieval.js} +22 -24
  95. package/mcp/dist/{shared-search-fallback.js → shared/search-fallback.js} +18 -20
  96. package/mcp/dist/{shared-sqljs.js → shared/sqljs.js} +3 -3
  97. package/mcp/dist/{shared-vector-index.js → shared/vector-index.js} +3 -3
  98. package/mcp/dist/shared.js +6 -60
  99. package/mcp/dist/{shell-entry.js → shell/entry.js} +6 -6
  100. package/mcp/dist/{shell-input.js → shell/input.js} +13 -13
  101. package/mcp/dist/{shell-palette.js → shell/palette.js} +3 -3
  102. package/mcp/dist/{shell-render.js → shell/render.js} +2 -2
  103. package/mcp/dist/{shell.js → shell/shell.js} +11 -11
  104. package/mcp/dist/{shell-state-store.js → shell/state-store.js} +5 -5
  105. package/mcp/dist/{shell-view-list.js → shell/view-list.js} +1 -1
  106. package/mcp/dist/{shell-view.js → shell/view.js} +13 -13
  107. package/mcp/dist/{skill-files.js → skill/files.js} +9 -9
  108. package/mcp/dist/{skill-registry.js → skill/registry.js} +5 -5
  109. package/mcp/dist/{skill-state.js → skill/state.js} +1 -4
  110. package/mcp/dist/startup-embedding.js +2 -2
  111. package/mcp/dist/status.js +15 -14
  112. package/mcp/dist/{tasks-github.js → task/github.js} +3 -2
  113. package/mcp/dist/{task-hygiene.js → task/hygiene.js} +4 -4
  114. package/mcp/dist/{task-lifecycle.js → task/lifecycle.js} +8 -13
  115. package/mcp/dist/telemetry.js +3 -4
  116. package/mcp/dist/tool-registry.js +29 -17
  117. package/mcp/dist/tools/config.js +530 -0
  118. package/mcp/dist/{mcp-data.js → tools/data.js} +8 -10
  119. package/mcp/dist/{mcp-extract-facts.js → tools/extract-facts.js} +6 -6
  120. package/mcp/dist/{mcp-extract.js → tools/extract.js} +6 -6
  121. package/mcp/dist/tools/finding.js +584 -0
  122. package/mcp/dist/{mcp-graph.js → tools/graph.js} +11 -14
  123. package/mcp/dist/{mcp-hooks.js → tools/hooks.js} +6 -6
  124. package/mcp/dist/{mcp-memory.js → tools/memory.js} +5 -5
  125. package/mcp/dist/tools/ops.js +468 -0
  126. package/mcp/dist/tools/search.js +672 -0
  127. package/mcp/dist/{mcp-session.js → tools/session.js} +51 -25
  128. package/mcp/dist/{mcp-skills.js → tools/skills.js} +42 -35
  129. package/mcp/dist/{mcp-tasks.js → tools/tasks.js} +155 -282
  130. package/mcp/dist/{memory-ui-data.js → ui/data.js} +31 -17
  131. package/mcp/dist/{memory-ui.js → ui/memory-ui.js} +3 -3
  132. package/mcp/dist/{memory-ui-page.js → ui/page.js} +5 -7
  133. package/mcp/dist/ui/server.js +1024 -0
  134. package/mcp/dist/update.js +2 -2
  135. package/mcp/dist/utils.js +63 -19
  136. package/package.json +2 -2
  137. package/scripts/preuninstall.mjs +31 -0
  138. package/starter/global/CLAUDE.md +3 -2
  139. package/mcp/dist/governance-audit.js +0 -22
  140. package/mcp/dist/mcp-config.js +0 -551
  141. package/mcp/dist/mcp-finding.js +0 -594
  142. package/mcp/dist/mcp-ops.js +0 -363
  143. package/mcp/dist/mcp-search.js +0 -668
  144. package/mcp/dist/memory-ui-server.js +0 -1411
  145. package/mcp/dist/shared-governance.js +0 -4
  146. /package/mcp/dist/{content-metadata.js → content/metadata.js} +0 -0
  147. /package/mcp/dist/{shared-stemmer.js → shared/stemmer.js} +0 -0
  148. /package/mcp/dist/{shell-types.js → shell/types.js} +0 -0
  149. /package/mcp/dist/{mcp-types.js → tools/types.js} +0 -0
  150. /package/mcp/dist/{memory-ui-assets.js → ui/assets.js} +0 -0
  151. /package/mcp/dist/{memory-ui-graph.js → ui/graph.js} +0 -0
  152. /package/mcp/dist/{memory-ui-scripts.js → ui/scripts.js} +0 -0
  153. /package/mcp/dist/{memory-ui-styles.js → ui/styles.js} +0 -0
@@ -1,18 +1,18 @@
1
1
  import * as fs from "fs";
2
2
  import * as path from "path";
3
3
  import * as crypto from "crypto";
4
- import { debugLog, appendAuditLog, phrenOk, phrenErr, PhrenError } from "./shared.js";
5
- import { normalizeMemoryScope } from "./shared.js";
6
- import { withFileLock } from "./shared-governance.js";
7
- import { isValidProjectName, safeProjectPath } from "./utils.js";
8
- import { getMachineName } from "./machine-identity.js";
9
- import { buildCitationComment, buildSourceComment, getHeadCommit, getRepoRoot, inferCitationLocation, isFindingProvenanceSource, } from "./content-citation.js";
10
- import { isDuplicateFinding, scanForSecrets, normalizeObservationTags, resolveCoref, detectConflicts, extractDynamicEntities } from "./content-dedup.js";
11
- import { validateFindingsFormat, validateFinding } from "./content-validate.js";
12
- import { countActiveFindings, autoArchiveToReference } from "./content-archive.js";
13
- import { resolveAutoFindingTaskItem, resolveFindingTaskReference, resolveFindingSessionId, } from "./finding-context.js";
14
- import { buildLifecycleComments, extractFindingType, parseFindingLifecycle, stripLifecycleComments, } from "./finding-lifecycle.js";
15
- import { METADATA_REGEX, } from "./content-metadata.js";
4
+ import { debugLog, appendAuditLog, phrenOk, phrenErr, PhrenError } from "../shared.js";
5
+ import { normalizeMemoryScope } from "../shared.js";
6
+ import { withFileLock } from "../shared/governance.js";
7
+ import { isValidProjectName, safeProjectPath } from "../utils.js";
8
+ import { getMachineName } from "../machine-identity.js";
9
+ import { buildCitationComment, buildSourceComment, getHeadCommit, getRepoRoot, inferCitationLocation, isFindingProvenanceSource, } from "./citation.js";
10
+ import { isDuplicateFinding, scanForSecrets, normalizeObservationTags, resolveCoref, detectConflicts, extractDynamicEntities } from "./dedup.js";
11
+ import { validateFindingsFormat, validateFinding } from "./validate.js";
12
+ import { countActiveFindings, autoArchiveToReference } from "./archive.js";
13
+ import { resolveAutoFindingTaskItem, resolveFindingTaskReference, resolveFindingSessionId, } from "../finding/context.js";
14
+ import { buildLifecycleComments, extractFindingType, parseFindingLifecycle, stripLifecycleComments, } from "../finding/lifecycle.js";
15
+ import { METADATA_REGEX, } from "./metadata.js";
16
16
  /** Default cap for active findings before auto-archiving is triggered. */
17
17
  const DEFAULT_FINDINGS_CAP = 20;
18
18
  const LIFECYCLE_ANNOTATION_RE = METADATA_REGEX.lifecycleAnnotation;
@@ -123,7 +123,8 @@ export function autoDetectFindingType(text) {
123
123
  return 'context';
124
124
  return null;
125
125
  }
126
- function prepareFinding(learning, project, fullHistory, extraAnnotations, citationInput, source, nowIso, inferredRepo, headCommit, phrenPath) {
126
+ function prepareFinding(opts) {
127
+ const { finding: learning, project, fullHistory, extraAnnotations, citationInput, source, nowIso, inferredRepo, headCommit, phrenPath } = opts;
127
128
  const secretType = scanForSecrets(learning);
128
129
  if (secretType) {
129
130
  return { status: "rejected", reason: `Contains ${secretType}` };
@@ -208,7 +209,11 @@ function insertFindingIntoContent(content, today, bullet, citationComment) {
208
209
  // Use positional insertion (not String.replace) to avoid: (1) special $& replacement patterns
209
210
  // if bullet contains $ chars, and (2) inserting inside an archived <details> block when a
210
211
  // duplicate date header exists from a prior consolidation run.
211
- const idx = content.indexOf(todayHeader);
212
+ // Search for todayHeader only after the last </details> close tag so we never
213
+ // insert into an archived block whose date happens to match today.
214
+ const lastDetailsClose = content.lastIndexOf("</details>");
215
+ const searchFrom = lastDetailsClose >= 0 ? lastDetailsClose : 0;
216
+ const idx = content.indexOf(todayHeader, searchFrom);
212
217
  if (idx !== -1) {
213
218
  const insertAt = idx + todayHeader.length;
214
219
  return content.slice(0, insertAt) + `\n\n${bullet}\n${citationComment}` + content.slice(insertAt);
@@ -288,7 +293,10 @@ export function addFindingToFile(phrenPath, project, learning, citationInput, op
288
293
  if (!fs.existsSync(resolvedDir))
289
294
  return phrenErr(`Project "${project}" does not exist.`, PhrenError.INVALID_PROJECT_NAME);
290
295
  const result = withFileLock(learningsPath, () => {
291
- const preparedForNewFile = prepareFinding(learning, project, "", opts?.extraAnnotations, resolvedCitationInput, source, nowIso, inferredRepo, headCommit, phrenPath);
296
+ const preparedForNewFile = prepareFinding({
297
+ finding: learning, project, fullHistory: "", extraAnnotations: opts?.extraAnnotations,
298
+ citationInput: resolvedCitationInput, source, nowIso, inferredRepo, headCommit, phrenPath,
299
+ });
292
300
  if (!fs.existsSync(learningsPath)) {
293
301
  if (preparedForNewFile.status === "rejected") {
294
302
  return phrenErr(`Rejected: finding appears to contain a secret (${preparedForNewFile.reason.replace(/^Contains /, "")}). Strip credentials before saving.`, PhrenError.VALIDATION_ERROR);
@@ -297,7 +305,9 @@ export function addFindingToFile(phrenPath, project, learning, citationInput, op
297
305
  return phrenOk(`Skipped duplicate finding for "${project}": already exists with similar wording.`);
298
306
  }
299
307
  const newContent = `# ${project} Findings\n\n## ${today}\n\n${preparedForNewFile.finding.bullet}\n${preparedForNewFile.finding.citationComment}\n`;
300
- fs.writeFileSync(learningsPath, newContent);
308
+ const tmpPath = learningsPath + ".tmp." + process.pid;
309
+ fs.writeFileSync(tmpPath, newContent);
310
+ fs.renameSync(tmpPath, learningsPath);
301
311
  return phrenOk({
302
312
  content: newContent,
303
313
  citation: buildFindingCitation(resolvedCitationInput, nowIso, inferredRepo, headCommit),
@@ -316,7 +326,10 @@ export function addFindingToFile(phrenPath, project, learning, citationInput, op
316
326
  .filter(line => !line.startsWith("- ") || !line.toLowerCase().includes(supersedesText.slice(0, 40).toLowerCase()))
317
327
  .join("\n")
318
328
  : content;
319
- const prepared = prepareFinding(learning, project, historyForDedup, opts?.extraAnnotations, resolvedCitationInput, source, nowIso, inferredRepo, headCommit, phrenPath);
329
+ const prepared = prepareFinding({
330
+ finding: learning, project, fullHistory: historyForDedup, extraAnnotations: opts?.extraAnnotations,
331
+ citationInput: resolvedCitationInput, source, nowIso, inferredRepo, headCommit, phrenPath,
332
+ });
320
333
  if (prepared.status === "rejected") {
321
334
  return phrenErr(`Rejected: finding appears to contain a secret (${prepared.reason.replace(/^Contains /, "")}). Strip credentials before saving.`, PhrenError.VALIDATION_ERROR);
322
335
  }
@@ -430,7 +443,10 @@ export function addFindingsToFile(phrenPath, project, learnings, opts) {
430
443
  rejected.push({ text: learning, reason: lengthError });
431
444
  continue;
432
445
  }
433
- const prepared = prepareFinding(learning, project, content, extraAnnotations, resolvedCitationInput, source, nowIso, inferredRepo, headCommit, phrenPath);
446
+ const prepared = prepareFinding({
447
+ finding: learning, project, fullHistory: content, extraAnnotations,
448
+ citationInput: resolvedCitationInput, source, nowIso, inferredRepo, headCommit, phrenPath,
449
+ });
434
450
  if (prepared.status === "rejected") {
435
451
  rejected.push({ text: learning, reason: prepared.reason });
436
452
  continue;
@@ -445,7 +461,9 @@ export function addFindingsToFile(phrenPath, project, learnings, opts) {
445
461
  added.push(learning);
446
462
  }
447
463
  if (added.length > 0) {
448
- fs.writeFileSync(learningsPath, content.endsWith("\n") ? content : `${content}\n`);
464
+ const tmpPath = learningsPath + ".tmp." + process.pid;
465
+ fs.writeFileSync(tmpPath, content.endsWith("\n") ? content : `${content}\n`);
466
+ fs.renameSync(tmpPath, learningsPath);
449
467
  }
450
468
  return phrenOk({ content, wrote: added.length > 0 });
451
469
  }
@@ -460,7 +478,10 @@ export function addFindingsToFile(phrenPath, project, learnings, opts) {
460
478
  rejected.push({ text: learning, reason: lengthError });
461
479
  continue;
462
480
  }
463
- const prepared = prepareFinding(learning, project, content, extraAnnotations, resolvedCitationInput, source, nowIso, inferredRepo, headCommit, phrenPath);
481
+ const prepared = prepareFinding({
482
+ finding: learning, project, fullHistory: content, extraAnnotations,
483
+ citationInput: resolvedCitationInput, source, nowIso, inferredRepo, headCommit, phrenPath,
484
+ });
464
485
  if (prepared.status === "rejected") {
465
486
  rejected.push({ text: learning, reason: prepared.reason });
466
487
  continue;
@@ -2,11 +2,11 @@ import * as fs from "fs";
2
2
  import * as path from "path";
3
3
  import * as crypto from "crypto";
4
4
  import { execFileSync } from "child_process";
5
- import { debugLog, EXEC_TIMEOUT_MS, getProjectDirs } from "./shared.js";
6
- import { errorMessage } from "./utils.js";
7
- import { countActiveFindings } from "./content-archive.js";
8
- import { isTaskFileName } from "./data-tasks.js";
9
- import { METADATA_REGEX } from "./content-metadata.js";
5
+ import { debugLog, EXEC_TIMEOUT_MS, getProjectDirs } from "../shared.js";
6
+ import { errorMessage } from "../utils.js";
7
+ import { countActiveFindings } from "./archive.js";
8
+ import { isTaskFileName } from "../data/tasks.js";
9
+ import { METADATA_REGEX } from "./metadata.js";
10
10
  /** Maximum allowed length for a single finding entry (token budget protection). */
11
11
  export const MAX_FINDING_LENGTH = 2000;
12
12
  function safeParseDate(s) {
@@ -1,7 +1,7 @@
1
- import { isValidProjectName } from "./utils.js";
2
- import { addFindingToFile, } from "./shared-content.js";
3
- import { removeFinding as removeFindingStore, } from "./data-access.js";
4
- import { MAX_FINDING_LENGTH } from "./content-validate.js";
1
+ import { isValidProjectName } from "../utils.js";
2
+ import { addFindingToFile, } from "../shared/content.js";
3
+ import { removeFinding as removeFindingStore, } from "../data/access.js";
4
+ import { MAX_FINDING_LENGTH } from "../content/validate.js";
5
5
  /**
6
6
  * Validate and add a single finding. Shared validation logic used by
7
7
  * both CLI `phren add-finding` and MCP `add_finding` tool.
@@ -1,8 +1,8 @@
1
1
  import * as path from "path";
2
- import { phrenErr, phrenOk, readRootManifest } from "./shared.js";
3
- import { bootstrapFromExisting } from "./init-setup.js";
4
- import { resolveActiveProfile } from "./profile-store.js";
5
- import { TASKS_FILENAME } from "./data-tasks.js";
2
+ import { phrenErr, phrenOk, readRootManifest } from "../shared.js";
3
+ import { bootstrapFromExisting } from "../init/setup.js";
4
+ import { resolveActiveProfile } from "../profile-store.js";
5
+ import { TASKS_FILENAME } from "../data/tasks.js";
6
6
  export function addProjectFromPath(phrenPath, targetPath, requestedProfile, ownership) {
7
7
  if (!targetPath) {
8
8
  return phrenErr("Path is required. Pass the current project directory explicitly to avoid adding the wrong working directory.");
@@ -1,5 +1,5 @@
1
- import { STOP_WORDS } from "./utils.js";
2
- import { queryDocRows } from "./shared-index.js";
1
+ import { STOP_WORDS } from "../utils.js";
2
+ import { queryDocRows } from "../shared/index.js";
3
3
  /**
4
4
  * Keyword overlap fallback for when FTS5 returns no results.
5
5
  * Scans all docs (optionally filtered by project/type), scores each by
@@ -1,18 +1,21 @@
1
+ // Barrel module: re-exports task, profile, and shell-state APIs from their
2
+ // dedicated modules (data-tasks.ts, profile-store.ts, shell-state-store.ts)
3
+ // and owns finding/queue logic directly.
1
4
  import * as fs from "fs";
2
5
  import * as path from "path";
3
6
  import * as yaml from "js-yaml";
4
- import { phrenErr, PhrenError, phrenOk, forwardErr, getProjectDirs, isRecord, } from "./shared.js";
5
- import { normalizeQueueEntryText, } from "./shared-governance.js";
6
- import { addFindingToFile, } from "./shared-content.js";
7
- import { isValidProjectName, queueFilePath, safeProjectPath } from "./utils.js";
8
- import { parseCitationComment, parseSourceComment, } from "./content-citation.js";
9
- import { parseFindingLifecycle, } from "./finding-lifecycle.js";
10
- import { METADATA_REGEX, isCitationLine, isArchiveStart, isArchiveEnd, parseFindingId, parseAllContradictions, stripComments, normalizeFindingText, } from "./content-metadata.js";
11
- import { withSafeLock, ensureProject } from "./shared-data-utils.js";
12
- export { readTasks, readTasksAcrossProjects, resolveTaskItem, addTask, addTasks, completeTasks, completeTask, removeTask, removeTasks, updateTask, linkTaskIssue, pinTask, unpinTask, workNextTask, tidyDoneTasks, taskMarkdown, appendChildFinding, promoteTask, TASKS_FILENAME, TASK_FILE_ALIASES, canonicalTaskFilePath, resolveTaskFilePath, isTaskFileName, } from "./data-tasks.js";
13
- export { addProjectToProfile, listMachines, listProfiles, listProjectCards, removeProjectFromProfile, setMachineProfile, } from "./profile-store.js";
14
- export { loadShellState, resetShellState, saveShellState, } from "./shell-state-store.js";
15
- export { getRuntimeHealth as readRuntimeHealth } from "./shared-governance.js";
7
+ import { phrenErr, PhrenError, phrenOk, forwardErr, getProjectDirs, isRecord, } from "../shared.js";
8
+ import { normalizeQueueEntryText, } from "../shared/governance.js";
9
+ import { addFindingToFile, } from "../shared/content.js";
10
+ import { isValidProjectName, queueFilePath, safeProjectPath } from "../utils.js";
11
+ import { parseCitationComment, parseSourceComment, } from "../content/citation.js";
12
+ import { parseFindingLifecycle, } from "../finding/lifecycle.js";
13
+ import { METADATA_REGEX, isCitationLine, isArchiveStart, isArchiveEnd, parseFindingId, parseAllContradictions, stripComments, normalizeFindingText, } from "../content/metadata.js";
14
+ import { withSafeLock, ensureProject } from "../shared/data-utils.js";
15
+ export { readTasks, readTasksAcrossProjects, resolveTaskItem, addTask, addTasks, completeTasks, completeTask, removeTask, removeTasks, updateTask, linkTaskIssue, pinTask, unpinTask, workNextTask, tidyDoneTasks, taskMarkdown, appendChildFinding, promoteTask, TASKS_FILENAME, TASK_FILE_ALIASES, canonicalTaskFilePath, resolveTaskFilePath, isTaskFileName, } from "./tasks.js";
16
+ export { addProjectToProfile, listMachines, listProfiles, listProjectCards, removeProjectFromProfile, setMachineProfile, } from "../profile-store.js";
17
+ export { loadShellState, resetShellState, saveShellState, } from "../shell/state-store.js";
18
+ export { getRuntimeHealth as readRuntimeHealth } from "../shared/governance.js";
16
19
  function extractDateHeading(line) {
17
20
  const heading = line.match(/^##\s+(.+)$/);
18
21
  if (!heading)
@@ -113,7 +116,6 @@ export function readFindings(phrenPath, project, opts = {}) {
113
116
  const includeArchived = opts.includeArchived ?? false;
114
117
  for (let i = 0; i < lines.length; i++) {
115
118
  const line = lines[i];
116
- const trimmed = line.trim();
117
119
  const archiveStartMatch = isArchiveStart(line);
118
120
  const archiveEnd = isArchiveEnd(line);
119
121
  if (archiveStartMatch) {
@@ -273,10 +275,63 @@ export function removeFinding(phrenPath, project, match) {
273
275
  const matched = lines[idx];
274
276
  lines.splice(idx, removeCount);
275
277
  const normalized = lines.join("\n").replace(/\n{3,}/g, "\n\n").trimEnd() + "\n";
276
- fs.writeFileSync(filePath, normalized);
278
+ const tmp = filePath + ".tmp." + process.pid;
279
+ fs.writeFileSync(tmp, normalized);
280
+ fs.renameSync(tmp, filePath);
277
281
  return phrenOk(`Removed from ${project}: ${matched}`);
278
282
  });
279
283
  }
284
+ export function removeFindings(phrenPath, project, matches) {
285
+ const ensured = ensureProject(phrenPath, project);
286
+ if (!ensured.ok)
287
+ return forwardErr(ensured);
288
+ const findingsPath = path.resolve(path.join(ensured.data, 'FINDINGS.md'));
289
+ if (!findingsPath.startsWith(phrenPath + path.sep) && findingsPath !== phrenPath) {
290
+ return phrenErr(`FINDINGS.md path escapes phren store`, PhrenError.VALIDATION_ERROR);
291
+ }
292
+ if (!fs.existsSync(findingsPath))
293
+ return phrenErr(`No FINDINGS.md file found for "${project}". Add a finding first with add_finding or :find add.`, PhrenError.FILE_NOT_FOUND);
294
+ return withSafeLock(findingsPath, () => {
295
+ const lines = fs.readFileSync(findingsPath, "utf8").split("\n");
296
+ const removed = [];
297
+ const errors = [];
298
+ const bulletLines = collectFindingBulletLines(lines);
299
+ const activeBullets = bulletLines.filter(({ archived }) => !archived);
300
+ const archivedBullets = bulletLines.filter(({ archived }) => archived);
301
+ // Collect indices to remove (with citation lines) in one pass over matches
302
+ const indicesToRemove = new Set();
303
+ for (const match of matches) {
304
+ const needle = normalizeFindingText(match);
305
+ const activeMatch = findMatchingFindingBullet(activeBullets.filter(({ i }) => !indicesToRemove.has(i)), needle, match);
306
+ if (activeMatch.kind === "ambiguous") {
307
+ errors.push(match);
308
+ continue;
309
+ }
310
+ if (activeMatch.kind === "not_found") {
311
+ const archivedMatch = findMatchingFindingBullet(archivedBullets, needle, match);
312
+ if (archivedMatch.kind === "ambiguous" || archivedMatch.kind === "found") {
313
+ errors.push(match);
314
+ continue;
315
+ }
316
+ errors.push(match);
317
+ continue;
318
+ }
319
+ const idx = activeMatch.idx;
320
+ indicesToRemove.add(idx);
321
+ if (isCitationLine(lines[idx + 1] || ""))
322
+ indicesToRemove.add(idx + 1);
323
+ removed.push(lines[idx]);
324
+ }
325
+ if (removed.length > 0) {
326
+ const filtered = lines.filter((_, i) => !indicesToRemove.has(i));
327
+ const normalized = filtered.join("\n").replace(/\n{3,}/g, "\n\n").trimEnd() + "\n";
328
+ const tmp = findingsPath + ".tmp." + process.pid;
329
+ fs.writeFileSync(tmp, normalized);
330
+ fs.renameSync(tmp, findingsPath);
331
+ }
332
+ return phrenOk({ removed, errors });
333
+ });
334
+ }
280
335
  export function editFinding(phrenPath, project, oldText, newText) {
281
336
  const ensured = ensureProject(phrenPath, project);
282
337
  if (!ensured.ok)
@@ -312,7 +367,9 @@ export function editFinding(phrenPath, project, oldText, newText) {
312
367
  const metaSuffix = metaMatch ? " " + metaMatch.join(" ") : "";
313
368
  lines[idx] = `- ${newTextTrimmed}${metaSuffix}`;
314
369
  const normalized = lines.join("\n").replace(/\n{3,}/g, "\n\n").trimEnd() + "\n";
315
- fs.writeFileSync(findingsPath, normalized);
370
+ const tmp = findingsPath + ".tmp." + process.pid;
371
+ fs.writeFileSync(tmp, normalized);
372
+ fs.renameSync(tmp, findingsPath);
316
373
  return phrenOk(`Updated finding in ${project}`);
317
374
  });
318
375
  }
@@ -384,6 +441,76 @@ export function readReviewQueue(phrenPath, project) {
384
441
  }
385
442
  return phrenOk(items);
386
443
  }
444
+ /** Locate a queue line and apply a mutation within a file lock. */
445
+ function withQueueLineOp(phrenPath, project, lineText, op) {
446
+ const ensured = ensureProject(phrenPath, project);
447
+ if (!ensured.ok)
448
+ return forwardErr(ensured);
449
+ const file = queuePath(phrenPath, project);
450
+ if (!fs.existsSync(file))
451
+ return phrenErr(`No review queue found for "${project}".`, PhrenError.FILE_NOT_FOUND);
452
+ return withSafeLock(file, () => {
453
+ const lines = fs.readFileSync(file, "utf8").split("\n");
454
+ const idx = lines.findIndex((l) => l.trim() === lineText.trim());
455
+ if (idx === -1)
456
+ return phrenErr(`Queue item not found in "${project}".`, PhrenError.NOT_FOUND);
457
+ return op(lines, idx, file);
458
+ });
459
+ }
460
+ function writeQueueLines(file, lines) {
461
+ const content = lines.join("\n").replace(/\n{3,}/g, "\n\n").trimEnd() + "\n";
462
+ const tmp = file + ".tmp." + process.pid;
463
+ fs.writeFileSync(tmp, content);
464
+ fs.renameSync(tmp, file);
465
+ }
466
+ /** Remove a queue item's line from review.md (finding stays in FINDINGS.md). */
467
+ export function approveQueueItem(phrenPath, project, lineText) {
468
+ return withQueueLineOp(phrenPath, project, lineText, (lines, idx, file) => {
469
+ lines.splice(idx, 1);
470
+ writeQueueLines(file, lines);
471
+ return phrenOk(`Approved queue item in ${project}`);
472
+ });
473
+ }
474
+ /** Remove a queue item from review.md AND remove the corresponding finding from FINDINGS.md. */
475
+ export function rejectQueueItem(phrenPath, project, lineText) {
476
+ const lockResult = withQueueLineOp(phrenPath, project, lineText, (lines, idx, file) => {
477
+ lines.splice(idx, 1);
478
+ writeQueueLines(file, lines);
479
+ return phrenOk("ok");
480
+ });
481
+ if (!lockResult.ok)
482
+ return lockResult;
483
+ const parsed = parseQueueLine(lineText);
484
+ if (parsed.text) {
485
+ const removeResult = removeFinding(phrenPath, project, parsed.text);
486
+ if (!removeResult.ok) {
487
+ return phrenOk(`Rejected queue item from ${project} (note: finding not found in FINDINGS.md — may have already been removed)`);
488
+ }
489
+ }
490
+ return phrenOk(`Rejected and removed queue item from ${project}`);
491
+ }
492
+ /** Edit a queue item's text in review.md and the corresponding finding in FINDINGS.md. */
493
+ export function editQueueItem(phrenPath, project, lineText, newText) {
494
+ const trimmed = newText.replace(/[\r\n]+/g, " ").trim();
495
+ if (!trimmed)
496
+ return phrenErr("New text cannot be empty.", PhrenError.EMPTY_INPUT);
497
+ const parsed = parseQueueLine(lineText);
498
+ const lockResult = withQueueLineOp(phrenPath, project, lineText, (lines, idx, file) => {
499
+ const dateMatch = lines[idx].match(/^- \[(\d{4}-\d{2}-\d{2})\]\s*/);
500
+ lines[idx] = dateMatch ? `- [${dateMatch[1]}] ${trimmed}` : `- ${trimmed}`;
501
+ writeQueueLines(file, lines);
502
+ return phrenOk("ok");
503
+ });
504
+ if (!lockResult.ok)
505
+ return lockResult;
506
+ if (parsed.text) {
507
+ const editResult = editFinding(phrenPath, project, parsed.text, trimmed);
508
+ if (!editResult.ok) {
509
+ return phrenOk(`Updated queue item in ${project} (note: corresponding finding not found in FINDINGS.md)`);
510
+ }
511
+ }
512
+ return phrenOk(`Updated queue item in ${project}`);
513
+ }
387
514
  export function readReviewQueueAcrossProjects(phrenPath, profile) {
388
515
  const validation = validateAggregateQueueProfile(phrenPath, profile);
389
516
  if (!validation.ok)
@@ -1,10 +1,10 @@
1
1
  import * as fs from "fs";
2
2
  import * as path from "path";
3
3
  import { randomBytes, randomUUID } from "crypto";
4
- import { phrenErr, PhrenError, phrenOk, forwardErr, getProjectDirs, } from "./shared.js";
5
- import { validateTaskFormat } from "./shared-content.js";
6
- import { safeProjectPath } from "./utils.js";
7
- import { withSafeLock, ensureProject } from "./shared-data-utils.js";
4
+ import { phrenErr, PhrenError, phrenOk, forwardErr, getProjectDirs, } from "../shared.js";
5
+ import { validateTaskFormat } from "../shared/content.js";
6
+ import { safeProjectPath } from "../utils.js";
7
+ import { withSafeLock, ensureProject } from "../shared/data-utils.js";
8
8
  const ACTIVE_HEADINGS = new Set(["active", "in progress", "in-progress", "current", "wip"]);
9
9
  const QUEUE_HEADINGS = new Set(["queue", "queued", "task", "todo", "upcoming", "next"]);
10
10
  const DONE_HEADINGS = new Set(["done", "completed", "finished", "archived"]);
@@ -408,7 +408,7 @@ export function addTask(phrenPath, project, item, opts) {
408
408
  return phrenOk(newItem);
409
409
  });
410
410
  }
411
- export function addTasks(phrenPath, project, items) {
411
+ export function addTasks(phrenPath, project, items, opts) {
412
412
  const bPath = canonicalTaskFilePath(phrenPath, project);
413
413
  if (!bPath)
414
414
  return phrenErr(`Project name "${project}" is not valid.`, PhrenError.INVALID_PROJECT_NAME);
@@ -429,10 +429,12 @@ export function addTasks(phrenPath, project, items) {
429
429
  }
430
430
  parsed.data.items.Queue.push({
431
431
  id: `Q${parsed.data.items.Queue.length + 1}`,
432
+ stableId: newBid(),
432
433
  section: "Queue",
433
434
  line,
434
435
  checked: false,
435
436
  priority: normalizePriority(line),
437
+ scope: opts?.scope,
436
438
  });
437
439
  added.push(line);
438
440
  }
@@ -2,9 +2,10 @@ import * as fs from "fs";
2
2
  import * as path from "path";
3
3
  import * as crypto from "crypto";
4
4
  import { debugLog, runtimeDir, } from "./shared.js";
5
- import { withFileLock } from "./shared-governance.js";
5
+ import { withFileLock } from "./shared/governance.js";
6
6
  import { errorMessage } from "./utils.js";
7
- import { bootstrapSqlJs } from "./shared-sqljs.js";
7
+ import { bootstrapSqlJs } from "./shared/sqljs.js";
8
+ import { logger } from "./logger.js";
8
9
  let sqlJsLoader = bootstrapSqlJs;
9
10
  const EMBED_CACHE_DB = "embed-cache.db";
10
11
  function getCacheDbPath(phrenPath) {
@@ -79,8 +80,7 @@ async function openCacheDb(phrenPath) {
79
80
  db?.close();
80
81
  }
81
82
  catch (e2) {
82
- if ((process.env.PHREN_DEBUG))
83
- process.stderr.write(`[phren] embedding openCacheDb dbClose: ${e2 instanceof Error ? e2.message : String(e2)}\n`);
83
+ logger.debug("embedding", `embedding openCacheDb dbClose: ${e2 instanceof Error ? e2.message : String(e2)}`);
84
84
  }
85
85
  throw err;
86
86
  }
@@ -126,14 +126,12 @@ function persistDb(phrenPath, db) {
126
126
  }
127
127
  }
128
128
  catch (err) {
129
- if ((process.env.PHREN_DEBUG))
130
- process.stderr.write(`[phren] embedding persistDb onDiskLoad: ${errorMessage(err)}\n`);
129
+ logger.debug("embedding", `embedding persistDb onDiskLoad: ${errorMessage(err)}`);
131
130
  try {
132
131
  onDisk?.close();
133
132
  }
134
133
  catch (e2) {
135
- if ((process.env.PHREN_DEBUG))
136
- process.stderr.write(`[phren] embedding persistDb onDiskClose: ${e2 instanceof Error ? e2.message : String(e2)}\n`);
134
+ logger.debug("embedding", `embedding persistDb onDiskClose: ${e2 instanceof Error ? e2.message : String(e2)}`);
137
135
  }
138
136
  onDisk = null;
139
137
  }
@@ -149,8 +147,7 @@ function persistDb(phrenPath, db) {
149
147
  onDisk.close();
150
148
  }
151
149
  catch (e2) {
152
- if ((process.env.PHREN_DEBUG))
153
- process.stderr.write(`[phren] embedding persistDb onDiskCloseFinally: ${e2 instanceof Error ? e2.message : String(e2)}\n`);
150
+ logger.debug("embedding", `embedding persistDb onDiskCloseFinally: ${e2 instanceof Error ? e2.message : String(e2)}`);
154
151
  }
155
152
  }
156
153
  });
@@ -269,8 +266,7 @@ export async function getCachedEmbedding(phrenPath, text, apiKey, model) {
269
266
  db?.close();
270
267
  }
271
268
  catch (e2) {
272
- if ((process.env.PHREN_DEBUG))
273
- process.stderr.write(`[phren] embedding getCachedEmbedding dbClose: ${e2 instanceof Error ? e2.message : String(e2)}\n`);
269
+ logger.debug("embedding", `embedding getCachedEmbedding dbClose: ${e2 instanceof Error ? e2.message : String(e2)}`);
274
270
  }
275
271
  }
276
272
  }
@@ -320,8 +316,7 @@ export async function getCachedEmbeddings(phrenPath, texts, apiKey, model) {
320
316
  db?.close();
321
317
  }
322
318
  catch (e2) {
323
- if ((process.env.PHREN_DEBUG))
324
- process.stderr.write(`[phren] embedding getCachedEmbeddings dbClose: ${e2 instanceof Error ? e2.message : String(e2)}\n`);
319
+ logger.debug("embedding", `embedding getCachedEmbeddings dbClose: ${e2 instanceof Error ? e2.message : String(e2)}`);
325
320
  }
326
321
  }
327
322
  }
@@ -1,9 +1,10 @@
1
1
  import * as fs from "fs";
2
2
  import * as path from "path";
3
- import { parseMcpMode, runInit } from "./init.js";
3
+ import { parseMcpMode, runInit } from "./init/init.js";
4
4
  import { errorMessage } from "./utils.js";
5
+ import { logger } from "./logger.js";
5
6
  import { defaultPhrenPath, findPhrenPath } from "./shared.js";
6
- import { addProjectFromPath } from "./core-project.js";
7
+ import { addProjectFromPath } from "./core/project.js";
7
8
  import { PROJECT_OWNERSHIP_MODES, getProjectOwnershipDefault, parseProjectOwnershipMode, } from "./project-config.js";
8
9
  const HELP_TEXT = `phren - persistent knowledge for your agents
9
10
 
@@ -344,7 +345,7 @@ export async function runTopLevelCommand(argv) {
344
345
  return finish();
345
346
  }
346
347
  if (argvCommand === "uninstall") {
347
- const { runUninstall } = await import("./init.js");
348
+ const { runUninstall } = await import("./init/init.js");
348
349
  const skipConfirm = argv.includes("--yes") || argv.includes("-y");
349
350
  await runUninstall({ yes: skipConfirm });
350
351
  return finish();
@@ -355,8 +356,8 @@ export async function runTopLevelCommand(argv) {
355
356
  return finish();
356
357
  }
357
358
  if (argvCommand === "verify") {
358
- const { runPostInitVerify, getVerifyOutcomeNote } = await import("./init.js");
359
- const { getWorkflowPolicy } = await import("./shared-governance.js");
359
+ const { runPostInitVerify, getVerifyOutcomeNote } = await import("./init/init.js");
360
+ const { getWorkflowPolicy } = await import("./shared/governance.js");
360
361
  const phrenPath = findPhrenPath() || defaultPhrenPath();
361
362
  const result = runPostInitVerify(phrenPath);
362
363
  console.log(`phren verify: ${result.ok ? "ok" : "issues found"}`);
@@ -376,7 +377,7 @@ export async function runTopLevelCommand(argv) {
376
377
  return finish(result.ok ? 0 : 1);
377
378
  }
378
379
  if (argvCommand === "mcp-mode") {
379
- const { runMcpMode } = await import("./init.js");
380
+ const { runMcpMode } = await import("./init/init.js");
380
381
  try {
381
382
  await runMcpMode(argv[1]);
382
383
  return finish();
@@ -387,7 +388,7 @@ export async function runTopLevelCommand(argv) {
387
388
  }
388
389
  }
389
390
  if (argvCommand === "hooks-mode") {
390
- const { runHooksMode } = await import("./init.js");
391
+ const { runHooksMode } = await import("./init/init.js");
391
392
  try {
392
393
  await runHooksMode(argv[1]);
393
394
  return finish();
@@ -405,19 +406,18 @@ export async function runTopLevelCommand(argv) {
405
406
  return finish();
406
407
  }
407
408
  if (!argvCommand && process.stdin.isTTY && process.stdout.isTTY) {
408
- const { runCliCommand } = await import("./cli.js");
409
+ const { runCliCommand } = await import("./cli/cli.js");
409
410
  await runCliCommand("shell", []);
410
411
  return finish();
411
412
  }
412
413
  if (argvCommand && CLI_COMMANDS.includes(argvCommand)) {
413
- const { runCliCommand } = await import("./cli.js");
414
+ const { runCliCommand } = await import("./cli/cli.js");
414
415
  try {
415
416
  const { trackCliCommand } = await import("./telemetry.js");
416
417
  trackCliCommand(defaultPhrenPath(), argvCommand);
417
418
  }
418
419
  catch (err) {
419
- if ((process.env.PHREN_DEBUG))
420
- process.stderr.write(`[phren] cli trackCliCommand: ${errorMessage(err)}\n`);
420
+ logger.debug("cli", `trackCliCommand: ${errorMessage(err)}`);
421
421
  }
422
422
  await runCliCommand(argvCommand, argv.slice(1));
423
423
  return finish();
@@ -1,7 +1,7 @@
1
1
  import * as fs from "fs";
2
2
  import * as path from "path";
3
- import { safeProjectPath } from "./utils.js";
4
- import { resolveTaskFilePath } from "./data-tasks.js";
3
+ import { safeProjectPath } from "../utils.js";
4
+ import { resolveTaskFilePath } from "../data/tasks.js";
5
5
  const ACTIVE_HEADINGS = new Set(["active", "in progress", "in-progress", "current", "wip"]);
6
6
  const QUEUE_HEADINGS = new Set(["queue", "queued", "task", "todo", "upcoming", "next"]);
7
7
  const DONE_HEADINGS = new Set(["done", "completed", "finished", "archived"]);
@@ -1,8 +1,8 @@
1
1
  import * as crypto from "crypto";
2
2
  import * as fs from "fs";
3
- import { impactLogFile } from "./shared.js";
4
- import { withFileLock } from "./shared-governance.js";
5
- import { normalizeFindingText } from "./content-metadata.js";
3
+ import { impactLogFile } from "../shared.js";
4
+ import { withFileLock } from "../shared/governance.js";
5
+ import { normalizeFindingText } from "../content/metadata.js";
6
6
  let highImpactCache = null;
7
7
  function nowIso() {
8
8
  return new Date().toISOString();
@@ -1,10 +1,10 @@
1
1
  import * as fs from "fs";
2
2
  import * as path from "path";
3
3
  import * as crypto from "crypto";
4
- import { runtimeDir, phrenOk, phrenErr, PhrenError } from "./shared.js";
5
- import { withFileLock } from "./shared-governance.js";
6
- import { addFindingToFile } from "./shared-content.js";
7
- import { isValidProjectName, errorMessage } from "./utils.js";
4
+ import { runtimeDir, phrenOk, phrenErr, PhrenError } from "../shared.js";
5
+ import { withFileLock } from "../shared/governance.js";
6
+ import { addFindingToFile } from "../shared/content.js";
7
+ import { isValidProjectName, errorMessage } from "../utils.js";
8
8
  function journalRoot(phrenPath) {
9
9
  const dir = path.join(runtimeDir(phrenPath), "finding-journal");
10
10
  fs.mkdirSync(dir, { recursive: true });