@phren/cli 0.0.50 → 0.0.53

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -68,6 +68,17 @@ phren team join git@github.com:org/phren-team.git
68
68
 
69
69
  Each team store syncs independently. Run `phren team list` to see all registered stores.
70
70
 
71
+ ### Filtering Team Store Projects
72
+
73
+ Subscribe to only the projects you care about:
74
+
75
+ ```bash
76
+ phren store subscribe qualus-shared arc intranet ogrid
77
+ phren store unsubscribe qualus-shared dendron powergrid-api
78
+ ```
79
+
80
+ Unsubscribed projects still exist in the store but won't appear in search, UI, or context injection.
81
+
71
82
  ---
72
83
 
73
84
  MIT License. Made by [Ala Arab](https://github.com/alaarab).
@@ -15,6 +15,19 @@ import { runSearch, runFragmentSearch, parseFragmentSearchArgs, runRelatedDocs,
15
15
  import { resolveRuntimeProfile } from "../runtime-profile.js";
16
16
  import { getProjectConsolidationStatus, CONSOLIDATION_ENTRY_THRESHOLD } from "../content/validate.js";
17
17
  import { listAllSessions } from "../tools/session.js";
18
+ function resolveProjectStorePath(phrenPath, project) {
19
+ try {
20
+ const { getNonPrimaryStores } = require("../store-registry.js");
21
+ if (fs.existsSync(path.join(phrenPath, project)))
22
+ return phrenPath;
23
+ for (const store of getNonPrimaryStores(phrenPath)) {
24
+ if (fs.existsSync(path.join(store.path, project)))
25
+ return store.path;
26
+ }
27
+ }
28
+ catch { /* fall through */ }
29
+ return phrenPath;
30
+ }
18
31
  async function runAndPrint(fn) {
19
32
  const result = await fn();
20
33
  if (result.lines.length > 0)
@@ -69,7 +82,8 @@ export async function handleTruths(project) {
69
82
  process.exit(1);
70
83
  }
71
84
  const phrenPath = getPhrenPath();
72
- const truthsPath = path.join(phrenPath, project, "truths.md");
85
+ const storePath = resolveProjectStorePath(phrenPath, project);
86
+ const truthsPath = path.join(storePath, project, "truths.md");
73
87
  if (!fs.existsSync(truthsPath)) {
74
88
  console.log(`No truths pinned for "${project}" yet.`);
75
89
  console.log(`\nPin one: phren pin ${project} "your truth here"`);
@@ -375,7 +389,8 @@ export async function handleConsolidationStatus(args) {
375
389
  ? (() => {
376
390
  if (!isValidProjectName(project))
377
391
  return null;
378
- const dir = path.join(phrenPath, project);
392
+ const storePath = resolveProjectStorePath(phrenPath, project);
393
+ const dir = path.join(storePath, project);
379
394
  return fs.existsSync(dir) ? [dir] : [];
380
395
  })()
381
396
  : getProjectDirs(phrenPath, profile);
@@ -79,7 +79,7 @@ export async function runCliCommand(command, args) {
79
79
  case "tasks":
80
80
  return handleTaskView(getProfile());
81
81
  case "sessions":
82
- return handleSessionsView(args);
82
+ return await handleSessionsView(args);
83
83
  case "task":
84
84
  return handleTaskNamespace(args);
85
85
  case "finding":
@@ -4,6 +4,19 @@ import { execFileSync } from "child_process";
4
4
  import { expandHomePath, findArchivedProjectNameCaseInsensitive, findProjectNameCaseInsensitive, getPhrenPath, getProjectDirs, homePath, hookConfigPath, normalizeProjectNameForCreate, readRootManifest, } from "../shared.js";
5
5
  import { isValidProjectName, errorMessage } from "../utils.js";
6
6
  import { logger } from "../logger.js";
7
+ function resolveProjectStorePath(phrenPath, project) {
8
+ try {
9
+ const { getNonPrimaryStores } = require("../store-registry.js");
10
+ if (fs.existsSync(path.join(phrenPath, project)))
11
+ return phrenPath;
12
+ for (const store of getNonPrimaryStores(phrenPath)) {
13
+ if (fs.existsSync(path.join(store.path, project)))
14
+ return store.path;
15
+ }
16
+ }
17
+ catch { /* fall through */ }
18
+ return phrenPath;
19
+ }
7
20
  import { readInstallPreferences, writeInstallPreferences } from "../init/preferences.js";
8
21
  import { buildSkillManifest, findLocalSkill, findSkill, getAllSkills } from "../skill/registry.js";
9
22
  import { detectSkillCollisions } from "../link/skills.js";
@@ -252,10 +265,17 @@ export function handleHooksNamespace(args) {
252
265
  const hooksEnabled = prefs.hooksEnabled !== false;
253
266
  const toolPrefs = prefs.hookTools && typeof prefs.hookTools === "object" ? prefs.hookTools : {};
254
267
  const project = getOptionValue(args.slice(1), "--project");
255
- if (project && (!isValidProjectName(project) || !fs.existsSync(path.join(phrenPath, project)))) {
268
+ if (project && !isValidProjectName(project)) {
256
269
  console.error(`Project "${project}" not found.`);
257
270
  process.exit(1);
258
271
  }
272
+ if (project) {
273
+ const storePath = resolveProjectStorePath(phrenPath, project);
274
+ if (!fs.existsSync(path.join(storePath, project))) {
275
+ console.error(`Project "${project}" not found.`);
276
+ process.exit(1);
277
+ }
278
+ }
259
279
  const rows = HOOK_TOOLS.map((tool) => ({
260
280
  tool,
261
281
  hookType: "lifecycle",
@@ -663,7 +683,8 @@ export async function handleProjectsNamespace(args, profile) {
663
683
  process.exit(1);
664
684
  }
665
685
  const phrenPath = getPhrenPath();
666
- const projectDir = path.join(phrenPath, name);
686
+ const storePath = resolveProjectStorePath(phrenPath, name);
687
+ const projectDir = path.join(storePath, name);
667
688
  if (!fs.existsSync(projectDir)) {
668
689
  console.error(`Project "${name}" not found.`);
669
690
  process.exit(1);
@@ -673,16 +694,16 @@ export async function handleProjectsNamespace(args, profile) {
673
694
  const summaryPath = path.join(projectDir, "summary.md");
674
695
  if (fs.existsSync(summaryPath))
675
696
  exported.summary = fs.readFileSync(summaryPath, "utf8");
676
- const learningsResult = readFindings(phrenPath, name);
697
+ const learningsResult = readFindings(storePath, name);
677
698
  if (learningsResult.ok)
678
699
  exported.learnings = learningsResult.data;
679
700
  const findingsPath = path.join(projectDir, "FINDINGS.md");
680
701
  if (fs.existsSync(findingsPath))
681
702
  exported.findingsRaw = fs.readFileSync(findingsPath, "utf8");
682
- const taskResult = readTasks(phrenPath, name);
703
+ const taskResult = readTasks(storePath, name);
683
704
  if (taskResult.ok) {
684
705
  exported.task = taskResult.data.items;
685
- const taskRawPath = resolveTaskFilePath(phrenPath, name);
706
+ const taskRawPath = resolveTaskFilePath(storePath, name);
686
707
  if (taskRawPath && fs.existsSync(taskRawPath))
687
708
  exported.taskRaw = fs.readFileSync(taskRawPath, "utf8");
688
709
  }
@@ -799,8 +820,9 @@ export async function handleProjectsNamespace(args, profile) {
799
820
  const phrenPath = getPhrenPath();
800
821
  if (subcommand === "archive") {
801
822
  const activeProject = findProjectNameCaseInsensitive(phrenPath, name);
802
- const projectDir = activeProject ? path.join(phrenPath, activeProject) : path.join(phrenPath, name);
803
- const archiveDir = path.join(phrenPath, `${activeProject ?? name}.archived`);
823
+ const storePath = resolveProjectStorePath(phrenPath, activeProject ?? name);
824
+ const projectDir = activeProject ? path.join(storePath, activeProject) : path.join(storePath, name);
825
+ const archiveDir = path.join(storePath, `${activeProject ?? name}.archived`);
804
826
  if (!fs.existsSync(projectDir)) {
805
827
  console.error(`Project "${name}" not found.`);
806
828
  process.exit(1);
@@ -826,8 +848,9 @@ export async function handleProjectsNamespace(args, profile) {
826
848
  process.exit(1);
827
849
  }
828
850
  const archivedProject = findArchivedProjectNameCaseInsensitive(phrenPath, name);
829
- const projectDir = path.join(phrenPath, archivedProject ?? name);
830
- const archiveDir = path.join(phrenPath, `${archivedProject ?? name}.archived`);
851
+ const storePath = resolveProjectStorePath(phrenPath, archivedProject ?? name);
852
+ const projectDir = path.join(storePath, archivedProject ?? name);
853
+ const archiveDir = path.join(storePath, `${archivedProject ?? name}.archived`);
831
854
  if (!fs.existsSync(archiveDir)) {
832
855
  const available = fs.readdirSync(phrenPath)
833
856
  .filter((e) => e.endsWith(".archived"))
@@ -1323,8 +1346,10 @@ export async function handleFindingNamespace(args) {
1323
1346
  console.error("Usage: phren finding list <project>");
1324
1347
  process.exit(1);
1325
1348
  }
1349
+ const phrenPath = getPhrenPath();
1326
1350
  const { readFindings } = await import("../data/access.js");
1327
- const result = readFindings(getPhrenPath(), project);
1351
+ const storePath = resolveProjectStorePath(phrenPath, project);
1352
+ const result = readFindings(storePath, project);
1328
1353
  if (!result.ok) {
1329
1354
  console.error(result.error);
1330
1355
  process.exit(1);
@@ -1510,6 +1535,8 @@ function printStoreUsage() {
1510
1535
  console.log(" phren store remove <name> Remove a store (local only)");
1511
1536
  console.log(" phren store sync Pull all stores");
1512
1537
  console.log(" phren store activity [--limit N] Recent team findings");
1538
+ console.log(" phren store subscribe <name> <project...> Subscribe store to projects");
1539
+ console.log(" phren store unsubscribe <name> <project...> Unsubscribe store from projects");
1513
1540
  }
1514
1541
  export async function handleStoreNamespace(args) {
1515
1542
  const subcommand = args[0];
@@ -1644,7 +1671,8 @@ export async function handleStoreNamespace(args) {
1644
1671
  for (const store of teamStores) {
1645
1672
  if (!fs.existsSync(store.path))
1646
1673
  continue;
1647
- const projectDirs = getProjectDirs(store.path);
1674
+ const { getStoreProjectDirs } = await import("../store-registry.js");
1675
+ const projectDirs = getStoreProjectDirs(store);
1648
1676
  for (const dir of projectDirs) {
1649
1677
  const projectName = path.basename(dir);
1650
1678
  const journalEntries = readTeamJournalEntries(store.path, projectName);
@@ -1703,6 +1731,42 @@ export async function handleStoreNamespace(args) {
1703
1731
  }
1704
1732
  return;
1705
1733
  }
1734
+ if (subcommand === "subscribe") {
1735
+ const storeName = args[1];
1736
+ const projects = args.slice(2);
1737
+ if (!storeName || projects.length === 0) {
1738
+ console.error("Usage: phren store subscribe <store-name> <project1> [project2...]");
1739
+ process.exit(1);
1740
+ }
1741
+ try {
1742
+ const { subscribeStoreProjects } = await import("../store-registry.js");
1743
+ subscribeStoreProjects(phrenPath, storeName, projects);
1744
+ console.log(`Added ${projects.length} project(s) to "${storeName}"`);
1745
+ }
1746
+ catch (err) {
1747
+ console.error(`Failed to subscribe: ${errorMessage(err)}`);
1748
+ process.exit(1);
1749
+ }
1750
+ return;
1751
+ }
1752
+ if (subcommand === "unsubscribe") {
1753
+ const storeName = args[1];
1754
+ const projects = args.slice(2);
1755
+ if (!storeName || projects.length === 0) {
1756
+ console.error("Usage: phren store unsubscribe <store-name> <project1> [project2...]");
1757
+ process.exit(1);
1758
+ }
1759
+ try {
1760
+ const { unsubscribeStoreProjects } = await import("../store-registry.js");
1761
+ unsubscribeStoreProjects(phrenPath, storeName, projects);
1762
+ console.log(`Removed ${projects.length} project(s) from "${storeName}"`);
1763
+ }
1764
+ catch (err) {
1765
+ console.error(`Failed to unsubscribe: ${errorMessage(err)}`);
1766
+ process.exit(1);
1767
+ }
1768
+ return;
1769
+ }
1706
1770
  console.error(`Unknown store subcommand: ${subcommand}`);
1707
1771
  printStoreUsage();
1708
1772
  process.exit(1);
@@ -1776,7 +1840,8 @@ function countStoreProjects(store) {
1776
1840
  if (!fs.existsSync(store.path))
1777
1841
  return 0;
1778
1842
  try {
1779
- return getProjectDirs(store.path).length;
1843
+ const storeRegistry = require("../store-registry.js");
1844
+ return storeRegistry.getStoreProjectDirs(store).length;
1780
1845
  }
1781
1846
  catch {
1782
1847
  return 0;
@@ -49,7 +49,7 @@ export function handleTaskView(profile) {
49
49
  }
50
50
  console.log(`\n${totalActive} active, ${totalQueue} queued across ${docs.length} project(s).`);
51
51
  }
52
- export function handleSessionsView(args) {
52
+ export async function handleSessionsView(args) {
53
53
  const phrenPath = getPhrenPath();
54
54
  const sessionId = args[0];
55
55
  if (sessionId) {
@@ -60,7 +60,7 @@ export function handleSessionsView(args) {
60
60
  console.error(`Session "${sessionId}" not found.`);
61
61
  process.exit(1);
62
62
  }
63
- const artifacts = getSessionArtifacts(phrenPath, session.sessionId);
63
+ const artifacts = await getSessionArtifacts(phrenPath, session.sessionId);
64
64
  console.log(`Session: ${session.sessionId.slice(0, 8)}`);
65
65
  console.log(`Project: ${session.project ?? "—"}`);
66
66
  console.log(`Started: ${session.startedAt.slice(0, 16).replace("T", " ")}`);
@@ -7,6 +7,7 @@ import { errorMessage } from "../utils.js";
7
7
  import { countActiveFindings } from "./archive.js";
8
8
  import { isTaskFileName } from "../data/tasks.js";
9
9
  import { METADATA_REGEX } from "./metadata.js";
10
+ import { getNonPrimaryStores, getStoreProjectDirs } from "../store-registry.js";
10
11
  /** Maximum allowed length for a single finding entry (token budget protection). */
11
12
  export const MAX_FINDING_LENGTH = 2000;
12
13
  function safeParseDate(s) {
@@ -74,6 +75,22 @@ export function checkConsolidationNeeded(phrenPath, profile) {
74
75
  results.push(status);
75
76
  }
76
77
  }
78
+ // Include projects from team stores
79
+ try {
80
+ for (const store of getNonPrimaryStores(phrenPath)) {
81
+ if (!fs.existsSync(store.path))
82
+ continue;
83
+ for (const dir of getStoreProjectDirs(store)) {
84
+ const status = getProjectConsolidationStatus(dir);
85
+ if (status && status.recommended) {
86
+ results.push(status);
87
+ }
88
+ }
89
+ }
90
+ }
91
+ catch {
92
+ // store-registry not available or error loading, continue with primary only
93
+ }
77
94
  return results;
78
95
  }
79
96
  /**
@@ -12,6 +12,7 @@ import { parseCitationComment, parseSourceComment, } from "../content/citation.j
12
12
  import { parseFindingLifecycle, } from "../finding/lifecycle.js";
13
13
  import { METADATA_REGEX, isCitationLine, isArchiveStart, isArchiveEnd, parseFindingId, parseAllContradictions, stripComments, normalizeFindingText, } from "../content/metadata.js";
14
14
  import { withSafeLock, ensureProject } from "../shared/data-utils.js";
15
+ import { getNonPrimaryStores, getStoreProjectDirs } from "../store-registry.js";
15
16
  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
17
  export { addProjectToProfile, listMachines, listProfiles, listProjectCards, removeProjectFromProfile, setMachineProfile, } from "../profile-store.js";
17
18
  export { loadShellState, resetShellState, saveShellState, } from "../shell/state-store.js";
@@ -559,6 +560,7 @@ export function readReviewQueueAcrossProjects(phrenPath, profile) {
559
560
  Conflicts: 2,
560
561
  };
561
562
  const items = [];
563
+ const seen = new Set(projects);
562
564
  for (const project of projects) {
563
565
  const result = readReviewQueue(phrenPath, project);
564
566
  if (!result.ok)
@@ -567,6 +569,30 @@ export function readReviewQueueAcrossProjects(phrenPath, profile) {
567
569
  items.push({ project, ...item });
568
570
  }
569
571
  }
572
+ // Include projects from team stores
573
+ try {
574
+ for (const store of getNonPrimaryStores(phrenPath)) {
575
+ if (!fs.existsSync(store.path))
576
+ continue;
577
+ const storeDirs = getStoreProjectDirs(store)
578
+ .map((d) => path.basename(d))
579
+ .filter((p) => p !== "global");
580
+ for (const storeProject of storeDirs) {
581
+ if (seen.has(storeProject))
582
+ continue;
583
+ seen.add(storeProject);
584
+ const result = readReviewQueue(store.path, storeProject);
585
+ if (!result.ok)
586
+ continue;
587
+ for (const item of result.data) {
588
+ items.push({ project: storeProject, ...item });
589
+ }
590
+ }
591
+ }
592
+ }
593
+ catch {
594
+ // store-registry not available or error loading, continue with primary only
595
+ }
570
596
  items.sort((a, b) => {
571
597
  const aDate = a.date === "unknown" ? "" : a.date;
572
598
  const bDate = b.date === "unknown" ? "" : b.date;
@@ -5,6 +5,7 @@ import { phrenErr, PhrenError, phrenOk, forwardErr, getProjectDirs, } from "../s
5
5
  import { validateTaskFormat } from "../shared/content.js";
6
6
  import { safeProjectPath } from "../utils.js";
7
7
  import { withSafeLock, ensureProject } from "../shared/data-utils.js";
8
+ import { getNonPrimaryStores, getStoreProjectDirs } from "../store-registry.js";
8
9
  const ACTIVE_HEADINGS = new Set(["active", "in progress", "in-progress", "current", "wip"]);
9
10
  const QUEUE_HEADINGS = new Set(["queue", "queued", "task", "todo", "upcoming", "next"]);
10
11
  const DONE_HEADINGS = new Set(["done", "completed", "finished", "archived"]);
@@ -352,9 +353,11 @@ export function readTasks(phrenPath, project) {
352
353
  return phrenOk(parseTaskContent(project, taskPath, content));
353
354
  }
354
355
  export function readTasksAcrossProjects(phrenPath, profile) {
355
- const projects = getProjectDirs(phrenPath, profile).map((dir) => path.basename(dir)).sort();
356
356
  const result = [];
357
- for (const project of projects) {
357
+ const seen = new Set();
358
+ // Primary store projects (with profile filtering)
359
+ const primaryProjects = getProjectDirs(phrenPath, profile).map((dir) => path.basename(dir)).sort();
360
+ for (const project of primaryProjects) {
358
361
  const file = canonicalTaskFilePath(phrenPath, project);
359
362
  if (!file || !fs.existsSync(file))
360
363
  continue;
@@ -362,7 +365,29 @@ export function readTasksAcrossProjects(phrenPath, profile) {
362
365
  if (!parsed.ok)
363
366
  continue;
364
367
  result.push(parsed.data);
368
+ seen.add(project);
365
369
  }
370
+ // Non-primary store projects (no profile — team stores don't have profiles)
371
+ try {
372
+ for (const store of getNonPrimaryStores(phrenPath)) {
373
+ if (!fs.existsSync(store.path))
374
+ continue;
375
+ const storeProjects = getStoreProjectDirs(store).map((dir) => path.basename(dir));
376
+ for (const project of storeProjects) {
377
+ if (seen.has(project))
378
+ continue;
379
+ seen.add(project);
380
+ const file = canonicalTaskFilePath(store.path, project);
381
+ if (!file || !fs.existsSync(file))
382
+ continue;
383
+ const parsed = readTasks(store.path, project);
384
+ if (!parsed.ok)
385
+ continue;
386
+ result.push(parsed.data);
387
+ }
388
+ }
389
+ }
390
+ catch { /* store-registry not available — primary only */ }
366
391
  return result;
367
392
  }
368
393
  export function resolveTaskItem(phrenPath, project, match) {