@phren/cli 0.0.28 → 0.0.32

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 (147) 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} +22 -21
  7. package/mcp/dist/{cli.js → cli/cli.js} +13 -13
  8. package/mcp/dist/{cli-config.js → cli/config.js} +9 -9
  9. package/mcp/dist/{cli-extract.js → cli/extract.js} +8 -8
  10. package/mcp/dist/{cli-govern.js → cli/govern.js} +10 -9
  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} +42 -57
  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} +8 -7
  21. package/mcp/dist/cli-hooks-git.js +243 -0
  22. package/mcp/dist/cli-hooks-prompt.js +319 -0
  23. package/mcp/dist/cli-hooks-session-handlers.js +349 -0
  24. package/mcp/dist/cli-hooks-stop.js +557 -0
  25. package/mcp/dist/{content-archive.js → content/archive.js} +8 -9
  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} +12 -12
  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} +131 -13
  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} +4 -4
  41. package/mcp/dist/{governance-audit.js → governance/audit.js} +2 -2
  42. package/mcp/dist/{governance-locks.js → governance/locks.js} +14 -9
  43. package/mcp/dist/{governance-policy.js → governance/policy.js} +10 -12
  44. package/mcp/dist/{governance-rbac.js → governance/rbac.js} +3 -3
  45. package/mcp/dist/{governance-scores.js → governance/scores.js} +8 -10
  46. package/mcp/dist/hooks.js +39 -31
  47. package/mcp/dist/index-query.js +4 -1
  48. package/mcp/dist/index.js +53 -29
  49. package/mcp/dist/{init-config.js → init/config.js} +6 -6
  50. package/mcp/dist/{init.js → init/init.js} +28 -29
  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} +3 -3
  54. package/mcp/dist/init-bootstrap.js +68 -0
  55. package/mcp/dist/init-detect.js +38 -0
  56. package/mcp/dist/init-dryrun.js +55 -0
  57. package/mcp/dist/init-env.js +114 -0
  58. package/mcp/dist/init-fresh.js +239 -0
  59. package/mcp/dist/init-hooks.js +26 -0
  60. package/mcp/dist/init-mcp.js +65 -0
  61. package/mcp/dist/init-migrate.js +51 -0
  62. package/mcp/dist/init-modes.js +135 -0
  63. package/mcp/dist/init-npm.js +37 -0
  64. package/mcp/dist/init-project-local.js +99 -0
  65. package/mcp/dist/init-semantic.js +48 -0
  66. package/mcp/dist/init-types.js +1 -0
  67. package/mcp/dist/init-uninstall.js +482 -0
  68. package/mcp/dist/init-update.js +96 -0
  69. package/mcp/dist/init-walkthrough-merge.js +90 -0
  70. package/mcp/dist/init-walkthrough.js +529 -0
  71. package/mcp/dist/{link-checksums.js → link/checksums.js} +5 -5
  72. package/mcp/dist/{link-context.js → link/context.js} +4 -4
  73. package/mcp/dist/{link-doctor.js → link/doctor.js} +20 -22
  74. package/mcp/dist/{link.js → link/link.js} +26 -31
  75. package/mcp/dist/{link-skills.js → link/skills.js} +10 -10
  76. package/mcp/dist/logger.js +11 -3
  77. package/mcp/dist/phren-art.js +0 -6
  78. package/mcp/dist/phren-paths.js +30 -12
  79. package/mcp/dist/proactivity.js +2 -2
  80. package/mcp/dist/profile-store.js +5 -6
  81. package/mcp/dist/project-config.js +2 -2
  82. package/mcp/dist/project-topics.js +1 -1
  83. package/mcp/dist/query-correlation.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} +3 -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} +15 -24
  90. package/mcp/dist/shared/governance.js +4 -0
  91. package/mcp/dist/{shared-index.js → shared/index.js} +92 -123
  92. package/mcp/dist/{shared-ollama.js → shared/ollama.js} +2 -2
  93. package/mcp/dist/{shared-retrieval.js → shared/retrieval.js} +16 -21
  94. package/mcp/dist/{shared-search-fallback.js → shared/search-fallback.js} +17 -20
  95. package/mcp/dist/{shared-sqljs.js → shared/sqljs.js} +3 -3
  96. package/mcp/dist/{shared-vector-index.js → shared/vector-index.js} +3 -3
  97. package/mcp/dist/shared.js +4 -59
  98. package/mcp/dist/{shell-entry.js → shell/entry.js} +6 -6
  99. package/mcp/dist/{shell-input.js → shell/input.js} +13 -13
  100. package/mcp/dist/{shell-palette.js → shell/palette.js} +3 -3
  101. package/mcp/dist/{shell-render.js → shell/render.js} +1 -1
  102. package/mcp/dist/{shell.js → shell/shell.js} +11 -11
  103. package/mcp/dist/{shell-state-store.js → shell/state-store.js} +5 -5
  104. package/mcp/dist/{shell-view-list.js → shell/view-list.js} +1 -1
  105. package/mcp/dist/{shell-view.js → shell/view.js} +13 -13
  106. package/mcp/dist/{skill-files.js → skill/files.js} +9 -9
  107. package/mcp/dist/{skill-registry.js → skill/registry.js} +4 -4
  108. package/mcp/dist/{skill-state.js → skill/state.js} +1 -1
  109. package/mcp/dist/startup-embedding.js +2 -2
  110. package/mcp/dist/status.js +15 -14
  111. package/mcp/dist/{tasks-github.js → task/github.js} +2 -2
  112. package/mcp/dist/{task-hygiene.js → task/hygiene.js} +4 -4
  113. package/mcp/dist/{task-lifecycle.js → task/lifecycle.js} +7 -7
  114. package/mcp/dist/telemetry.js +3 -4
  115. package/mcp/dist/tool-registry.js +29 -17
  116. package/mcp/dist/tools/config.js +515 -0
  117. package/mcp/dist/{mcp-data.js → tools/data.js} +8 -10
  118. package/mcp/dist/{mcp-extract-facts.js → tools/extract-facts.js} +6 -6
  119. package/mcp/dist/{mcp-extract.js → tools/extract.js} +6 -6
  120. package/mcp/dist/{mcp-finding.js → tools/finding.js} +97 -124
  121. package/mcp/dist/{mcp-graph.js → tools/graph.js} +11 -14
  122. package/mcp/dist/{mcp-hooks.js → tools/hooks.js} +6 -6
  123. package/mcp/dist/{mcp-memory.js → tools/memory.js} +5 -5
  124. package/mcp/dist/{mcp-ops.js → tools/ops.js} +169 -71
  125. package/mcp/dist/{mcp-search.js → tools/search.js} +19 -23
  126. package/mcp/dist/{mcp-session.js → tools/session.js} +48 -23
  127. package/mcp/dist/{mcp-skills.js → tools/skills.js} +33 -35
  128. package/mcp/dist/{mcp-tasks.js → tools/tasks.js} +155 -282
  129. package/mcp/dist/{memory-ui-data.js → ui/data.js} +31 -17
  130. package/mcp/dist/{memory-ui.js → ui/memory-ui.js} +3 -3
  131. package/mcp/dist/{memory-ui-page.js → ui/page.js} +4 -6
  132. package/mcp/dist/{memory-ui-server.js → ui/server.js} +30 -22
  133. package/mcp/dist/update.js +2 -2
  134. package/mcp/dist/utils.js +51 -11
  135. package/package.json +2 -2
  136. package/scripts/preuninstall.mjs +31 -0
  137. package/starter/global/CLAUDE.md +3 -2
  138. package/mcp/dist/mcp-config.js +0 -551
  139. package/mcp/dist/shared-governance.js +0 -4
  140. /package/mcp/dist/{content-metadata.js → content/metadata.js} +0 -0
  141. /package/mcp/dist/{shared-stemmer.js → shared/stemmer.js} +0 -0
  142. /package/mcp/dist/{shell-types.js → shell/types.js} +0 -0
  143. /package/mcp/dist/{mcp-types.js → tools/types.js} +0 -0
  144. /package/mcp/dist/{memory-ui-assets.js → ui/assets.js} +0 -0
  145. /package/mcp/dist/{memory-ui-graph.js → ui/graph.js} +0 -0
  146. /package/mcp/dist/{memory-ui-scripts.js → ui/scripts.js} +0 -0
  147. /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;
@@ -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) {
@@ -277,6 +279,55 @@ export function removeFinding(phrenPath, project, match) {
277
279
  return phrenOk(`Removed from ${project}: ${matched}`);
278
280
  });
279
281
  }
282
+ export function removeFindings(phrenPath, project, matches) {
283
+ const ensured = ensureProject(phrenPath, project);
284
+ if (!ensured.ok)
285
+ return forwardErr(ensured);
286
+ const findingsPath = path.resolve(path.join(ensured.data, 'FINDINGS.md'));
287
+ if (!findingsPath.startsWith(phrenPath + path.sep) && findingsPath !== phrenPath) {
288
+ return phrenErr(`FINDINGS.md path escapes phren store`, PhrenError.VALIDATION_ERROR);
289
+ }
290
+ if (!fs.existsSync(findingsPath))
291
+ return phrenErr(`No FINDINGS.md file found for "${project}". Add a finding first with add_finding or :find add.`, PhrenError.FILE_NOT_FOUND);
292
+ return withSafeLock(findingsPath, () => {
293
+ const lines = fs.readFileSync(findingsPath, "utf8").split("\n");
294
+ const removed = [];
295
+ const errors = [];
296
+ const bulletLines = collectFindingBulletLines(lines);
297
+ const activeBullets = bulletLines.filter(({ archived }) => !archived);
298
+ const archivedBullets = bulletLines.filter(({ archived }) => archived);
299
+ // Collect indices to remove (with citation lines) in one pass over matches
300
+ const indicesToRemove = new Set();
301
+ for (const match of matches) {
302
+ const needle = normalizeFindingText(match);
303
+ const activeMatch = findMatchingFindingBullet(activeBullets.filter(({ i }) => !indicesToRemove.has(i)), needle, match);
304
+ if (activeMatch.kind === "ambiguous") {
305
+ errors.push(match);
306
+ continue;
307
+ }
308
+ if (activeMatch.kind === "not_found") {
309
+ const archivedMatch = findMatchingFindingBullet(archivedBullets, needle, match);
310
+ if (archivedMatch.kind === "ambiguous" || archivedMatch.kind === "found") {
311
+ errors.push(match);
312
+ continue;
313
+ }
314
+ errors.push(match);
315
+ continue;
316
+ }
317
+ const idx = activeMatch.idx;
318
+ indicesToRemove.add(idx);
319
+ if (isCitationLine(lines[idx + 1] || ""))
320
+ indicesToRemove.add(idx + 1);
321
+ removed.push(lines[idx]);
322
+ }
323
+ if (removed.length > 0) {
324
+ const filtered = lines.filter((_, i) => !indicesToRemove.has(i));
325
+ const normalized = filtered.join("\n").replace(/\n{3,}/g, "\n\n").trimEnd() + "\n";
326
+ fs.writeFileSync(findingsPath, normalized);
327
+ }
328
+ return phrenOk({ removed, errors });
329
+ });
330
+ }
280
331
  export function editFinding(phrenPath, project, oldText, newText) {
281
332
  const ensured = ensureProject(phrenPath, project);
282
333
  if (!ensured.ok)
@@ -384,6 +435,73 @@ export function readReviewQueue(phrenPath, project) {
384
435
  }
385
436
  return phrenOk(items);
386
437
  }
438
+ /** Locate a queue line and apply a mutation within a file lock. */
439
+ function withQueueLineOp(phrenPath, project, lineText, op) {
440
+ const ensured = ensureProject(phrenPath, project);
441
+ if (!ensured.ok)
442
+ return forwardErr(ensured);
443
+ const file = queuePath(phrenPath, project);
444
+ if (!fs.existsSync(file))
445
+ return phrenErr(`No review queue found for "${project}".`, PhrenError.FILE_NOT_FOUND);
446
+ return withSafeLock(file, () => {
447
+ const lines = fs.readFileSync(file, "utf8").split("\n");
448
+ const idx = lines.findIndex((l) => l.trim() === lineText.trim());
449
+ if (idx === -1)
450
+ return phrenErr(`Queue item not found in "${project}".`, PhrenError.NOT_FOUND);
451
+ return op(lines, idx, file);
452
+ });
453
+ }
454
+ function writeQueueLines(file, lines) {
455
+ fs.writeFileSync(file, lines.join("\n").replace(/\n{3,}/g, "\n\n").trimEnd() + "\n");
456
+ }
457
+ /** Remove a queue item's line from review.md (finding stays in FINDINGS.md). */
458
+ export function approveQueueItem(phrenPath, project, lineText) {
459
+ return withQueueLineOp(phrenPath, project, lineText, (lines, idx, file) => {
460
+ lines.splice(idx, 1);
461
+ writeQueueLines(file, lines);
462
+ return phrenOk(`Approved queue item in ${project}`);
463
+ });
464
+ }
465
+ /** Remove a queue item from review.md AND remove the corresponding finding from FINDINGS.md. */
466
+ export function rejectQueueItem(phrenPath, project, lineText) {
467
+ const lockResult = withQueueLineOp(phrenPath, project, lineText, (lines, idx, file) => {
468
+ lines.splice(idx, 1);
469
+ writeQueueLines(file, lines);
470
+ return phrenOk("ok");
471
+ });
472
+ if (!lockResult.ok)
473
+ return lockResult;
474
+ const parsed = parseQueueLine(lineText);
475
+ if (parsed.text) {
476
+ const removeResult = removeFinding(phrenPath, project, parsed.text);
477
+ if (!removeResult.ok) {
478
+ return phrenOk(`Rejected queue item from ${project} (note: finding not found in FINDINGS.md — may have already been removed)`);
479
+ }
480
+ }
481
+ return phrenOk(`Rejected and removed queue item from ${project}`);
482
+ }
483
+ /** Edit a queue item's text in review.md and the corresponding finding in FINDINGS.md. */
484
+ export function editQueueItem(phrenPath, project, lineText, newText) {
485
+ const trimmed = newText.replace(/[\r\n]+/g, " ").trim();
486
+ if (!trimmed)
487
+ return phrenErr("New text cannot be empty.", PhrenError.EMPTY_INPUT);
488
+ const parsed = parseQueueLine(lineText);
489
+ const lockResult = withQueueLineOp(phrenPath, project, lineText, (lines, idx, file) => {
490
+ const dateMatch = lines[idx].match(/^- \[(\d{4}-\d{2}-\d{2})\]\s*/);
491
+ lines[idx] = dateMatch ? `- [${dateMatch[1]}] ${trimmed}` : `- ${trimmed}`;
492
+ writeQueueLines(file, lines);
493
+ return phrenOk("ok");
494
+ });
495
+ if (!lockResult.ok)
496
+ return lockResult;
497
+ if (parsed.text) {
498
+ const editResult = editFinding(phrenPath, project, parsed.text, trimmed);
499
+ if (!editResult.ok) {
500
+ return phrenOk(`Updated queue item in ${project} (note: corresponding finding not found in FINDINGS.md)`);
501
+ }
502
+ }
503
+ return phrenOk(`Updated queue item in ${project}`);
504
+ }
387
505
  export function readReviewQueueAcrossProjects(phrenPath, profile) {
388
506
  const validation = validateAggregateQueueProfile(phrenPath, profile);
389
507
  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 });
@@ -1,11 +1,11 @@
1
1
  import * as fs from "fs";
2
2
  import * as path from "path";
3
- import { PhrenError, phrenErr, phrenOk } from "./phren-core.js";
3
+ import { PhrenError, phrenErr, phrenOk } from "../phren-core.js";
4
4
  // Phren lifecycle comment prefix. No backward compat.
5
5
  const LIFECYCLE_PREFIX = "phren";
6
- import { withFileLock } from "./shared-governance.js";
7
- import { isValidProjectName, safeProjectPath } from "./utils.js";
8
- import { isArchiveEnd, isArchiveStart, parseCreatedDate as parseCreatedDateMeta, parseStatusField, parseStatus, parseSupersession, parseContradiction, parseFindingId as parseFindingIdMeta, stripLifecycleMetadata, stripRelationMetadata, normalizeFindingText, } from "./content-metadata.js";
6
+ import { withFileLock } from "../shared/governance.js";
7
+ import { isValidProjectName, safeProjectPath } from "../utils.js";
8
+ import { isArchiveEnd, isArchiveStart, parseCreatedDate as parseCreatedDateMeta, parseStatusField, parseStatus, parseSupersession, parseContradiction, parseFindingId as parseFindingIdMeta, stripLifecycleMetadata, stripRelationMetadata, normalizeFindingText, } from "../content/metadata.js";
9
9
  export const FINDING_TYPE_DECAY = {
10
10
  'pattern': { maxAgeDays: 365, decayMultiplier: 1.0 }, // Slow decay, long-lived
11
11
  'decision': { maxAgeDays: Infinity, decayMultiplier: 1.0 }, // Never decays
@@ -1,7 +1,7 @@
1
1
  import * as fs from "fs";
2
2
  import * as path from "path";
3
- import { debugLog } from "./shared.js";
4
- import { errorMessage } from "./utils.js";
3
+ import { debugLog } from "../shared.js";
4
+ import { errorMessage } from "../utils.js";
5
5
  export function recordRetrieval(phrenPath, file, section) {
6
6
  const dir = path.join(phrenPath, ".runtime");
7
7
  fs.mkdirSync(dir, { recursive: true });
@@ -1,7 +1,8 @@
1
1
  import * as fs from "fs";
2
2
  import * as path from "path";
3
- import { debugLog } from "./shared.js";
4
- import { errorMessage } from "./utils.js";
3
+ import { debugLog } from "../shared.js";
4
+ import { errorMessage } from "../utils.js";
5
+ import { logger } from "../logger.js";
5
6
  // Acquire the file lock, returning true on success or throwing on timeout.
6
7
  function acquireFileLock(lockPath) {
7
8
  const maxWait = Number.parseInt(process.env.PHREN_FILE_LOCK_MAX_WAIT_MS || "5000", 10) || 5000;
@@ -19,8 +20,7 @@ function acquireFileLock(lockPath) {
19
20
  break;
20
21
  }
21
22
  catch (err) {
22
- if ((process.env.PHREN_DEBUG))
23
- process.stderr.write(`[phren] acquireFileLock lockWrite: ${errorMessage(err)}\n`);
23
+ logger.debug("acquireFileLock", `lockWrite: ${errorMessage(err)}`);
24
24
  try {
25
25
  const stat = fs.statSync(lockPath);
26
26
  if (Date.now() - stat.mtimeMs > staleThreshold) {
@@ -40,7 +40,14 @@ function acquireFileLock(lockPath) {
40
40
  }
41
41
  }
42
42
  else {
43
- ownerDead = true;
43
+ try {
44
+ const result = require('child_process').spawnSync('tasklist', ['/FI', `PID eq ${lockPid}`, '/NH'], { encoding: 'utf8', timeout: 2000 });
45
+ if (result.stdout && result.stdout.includes(String(lockPid)))
46
+ ownerDead = false;
47
+ }
48
+ catch {
49
+ ownerDead = true;
50
+ }
44
51
  }
45
52
  }
46
53
  }
@@ -54,8 +61,7 @@ function acquireFileLock(lockPath) {
54
61
  }
55
62
  }
56
63
  catch (statErr) {
57
- if ((process.env.PHREN_DEBUG))
58
- process.stderr.write(`[phren] acquireFileLock staleStat: ${statErr instanceof Error ? statErr.message : String(statErr)}\n`);
64
+ logger.debug("acquireFileLock", `staleStat: ${statErr instanceof Error ? statErr.message : String(statErr)}`);
59
65
  sleep(pollInterval);
60
66
  waited += pollInterval;
61
67
  continue;
@@ -75,8 +81,7 @@ function releaseFileLock(lockPath) {
75
81
  fs.unlinkSync(lockPath);
76
82
  }
77
83
  catch (err) {
78
- if ((process.env.PHREN_DEBUG))
79
- process.stderr.write(`[phren] releaseFileLock: ${errorMessage(err)}\n`);
84
+ logger.debug("releaseFileLock", `${errorMessage(err)}`);
80
85
  }
81
86
  }
82
87
  // Q10: withFileLock now accepts both sync and async callbacks.
@@ -1,13 +1,13 @@
1
1
  import * as crypto from "crypto";
2
2
  import * as fs from "fs";
3
3
  import * as path from "path";
4
- import { appendAuditLog, debugLog, getProjectDirs, isRecord, runtimeHealthFile, withDefaults, phrenErr, PhrenError, phrenOk, resolveFindingsPath } from "./shared.js";
5
- import { withFileLock, isFiniteNumber, hasValidSchemaVersion } from "./shared-governance.js";
6
- import { errorMessage, isValidProjectName, safeProjectPath } from "./utils.js";
7
- import { readProjectConfig } from "./project-config.js";
8
- import { getActiveProfileDefaults } from "./profile-store.js";
9
- import { runCustomHooks } from "./hooks.js";
10
- import { METADATA_REGEX, isCitationLine, isArchiveStart as isArchiveStartMeta, isArchiveEnd as isArchiveEndMeta, stripLifecycleMetadata as stripLifecycleMetadataMeta, } from "./content-metadata.js";
4
+ import { appendAuditLog, debugLog, getProjectDirs, isRecord, runtimeHealthFile, withDefaults, phrenErr, PhrenError, phrenOk, resolveFindingsPath } from "../shared.js";
5
+ import { withFileLock, isFiniteNumber, hasValidSchemaVersion } from "../shared/governance.js";
6
+ import { errorMessage, isValidProjectName, safeProjectPath } from "../utils.js";
7
+ import { readProjectConfig } from "../project-config.js";
8
+ import { getActiveProfileDefaults } from "../profile-store.js";
9
+ import { runCustomHooks } from "../hooks.js";
10
+ import { METADATA_REGEX, isCitationLine, isArchiveStart as isArchiveStartMeta, isArchiveEnd as isArchiveEndMeta, stripLifecycleMetadata as stripLifecycleMetadataMeta, } from "../content/metadata.js";
11
11
  export const MAX_QUEUE_ENTRY_LENGTH = 500;
12
12
  export const GOVERNANCE_SCHEMA_VERSION = 1;
13
13
  const DEFAULT_POLICY = {
@@ -641,7 +641,7 @@ export function pruneDeadMemories(phrenPath, project, dryRun) {
641
641
  const file = resolveFindingsPath(dir);
642
642
  if (!file)
643
643
  continue;
644
- // Q23: wrap read-modify-write in per-file lock to prevent races with concurrent finding writers
644
+ // Q23: see docs/decisions/Q23-per-file-lock-concurrent-writers.md
645
645
  withFileLock(file, () => {
646
646
  const lines = fs.readFileSync(file, "utf8").split("\n");
647
647
  let currentDate = null;
@@ -723,13 +723,11 @@ export function consolidateProjectFindings(phrenPath, project, dryRun) {
723
723
  const file = resolveFindingsPath(path.join(phrenPath, project));
724
724
  if (!file)
725
725
  return phrenErr(`No FINDINGS.md found for "${project}".`, PhrenError.FILE_NOT_FOUND);
726
- // Q23: wrap entire read-modify-write in per-file lock to prevent races with concurrent finding writers
726
+ // Q23: see docs/decisions/Q23-per-file-lock-concurrent-writers.md
727
727
  const result = withFileLock(file, () => {
728
728
  const raw = fs.readFileSync(file, "utf8");
729
729
  const lines = raw.split("\n");
730
- // Q12: Separate the file into "active" lines and verbatim archive/details blocks.
731
- // Archive blocks (<!-- phren:archive:start/end --> and <details>...</details>) are
732
- // collected verbatim and appended unchanged after the consolidated active section.
730
+ // Q12: see docs/decisions/Q12-active-vs-archive-separation.md
733
731
  const archiveBlocks = [];
734
732
  const activeLines = [];
735
733
  let inArchive = false;
@@ -18,9 +18,9 @@
18
18
  */
19
19
  import * as fs from "fs";
20
20
  import * as path from "path";
21
- import { debugLog } from "./shared.js";
22
- import { errorMessage } from "./utils.js";
23
- import { readProjectConfig } from "./project-config.js";
21
+ import { debugLog } from "../shared.js";
22
+ import { errorMessage } from "../utils.js";
23
+ import { readProjectConfig } from "../project-config.js";
24
24
  function configDir(phrenPath) {
25
25
  return path.join(phrenPath, ".config");
26
26
  }