phewsh 0.15.10 → 0.15.11

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.
@@ -1,5 +1,6 @@
1
1
  // phewsh sequence (phewsh seq)
2
- // Universal Memory Transform — reads all AI memory files, emits optimal context.
2
+ // Universal Memory Transform — reads this directory's memory files plus the
3
+ // user's global per-tool memory (read-only), emits optimal context per target.
3
4
 
4
5
  const fs = require('fs');
5
6
  const path = require('path');
@@ -17,6 +18,7 @@ const flags = {
17
18
  write: args.includes('--write') || args.includes('-w'),
18
19
  dryRun: args.includes('--dry-run'),
19
20
  all: args.includes('--all'),
21
+ includeGlobal: args.includes('--include-global'),
20
22
  sources: getFlag('--sources', '-s'),
21
23
  help: args.includes('--help') || args.includes('-h'),
22
24
  };
@@ -47,15 +49,22 @@ function getPositionalTarget() {
47
49
  function showHelp() {
48
50
  console.log('');
49
51
  console.log(` ${b(cream('phewsh sequence'))} ${slate('(phewsh seq)')}`);
50
- console.log(` ${sage('Universal Memory Transform — reads all AI memory files,')}`);
51
- console.log(` ${sage('emits optimal context for any target agent.')}`);
52
+ console.log(` ${sage('Universal Memory Transform — reads the memory files in this')}`);
53
+ console.log(` ${sage('directory plus your global per-user memory across tools,')}`);
54
+ console.log(` ${sage('then emits optimal context for any target agent.')}`);
55
+ console.log('');
56
+ console.log(` ${cream('reads')} ${slate('(read-only — phewsh never edits these)')}`);
57
+ console.log(` ${sage('project .intent/, CLAUDE.md, AGENTS.md, GEMINI.md, .cursorrules,')}`);
58
+ console.log(` ${sage(' copilot-instructions, README, + this project’s Claude memory')}`);
59
+ console.log(` ${sage('global ~/.claude/CLAUDE.md, ~/.codex/AGENTS.md, ~/.gemini/GEMINI.md')}`);
60
+ console.log(` ${slate('global memory is per-user and travels across every project.')}`);
52
61
  console.log('');
53
62
  console.log(` ${cream('usage')}`);
54
- console.log(` ${teal('phewsh seq')} ${sage('Sequence → stdout summary')}`);
55
- console.log(` ${teal('phewsh seq')} ${slate('claude')} ${sage('Sequence → CLAUDE.md section')}`);
63
+ console.log(` ${teal('phewsh seq')} ${sage('Sequence → stdout summary (project + global)')}`);
64
+ console.log(` ${teal('phewsh seq')} ${slate('claude')} ${sage('Sequence → CLAUDE.md section (project only)')}`);
56
65
  console.log(` ${teal('phewsh seq')} ${slate('-w')} ${sage('Write to target file')}`);
57
66
  console.log(` ${teal('phewsh seq')} ${slate('--explain')} ${sage('Show full ranking breakdown')}`);
58
- console.log(` ${teal('phewsh seq')} ${slate('--dry-run')} ${sage('Show sources found, no output')}`);
67
+ console.log(` ${teal('phewsh seq')} ${slate('--dry-run')} ${sage('Show sources found (with scope), no output')}`);
59
68
  console.log('');
60
69
  console.log(` ${cream('targets')}`);
61
70
  console.log(` ${teal('claude')} ${sage('CLAUDE.md section (between markers)')}`);
@@ -63,6 +72,8 @@ function showHelp() {
63
72
  console.log(` ${cream('options')}`);
64
73
  console.log(` ${teal('--budget')} ${slate('<level>')} ${sage('Token budget: minimal|standard|full|unlimited')}`);
65
74
  console.log(` ${teal('--sources')} ${slate('<list>')} ${sage('Limit sources: intent,claude-md,claude-memory')}`);
75
+ console.log(` ${teal('--include-global')} ${sage('Allow global memory into a written project file')}`);
76
+ console.log(` ${slate(' (off by default — keeps personal notes out of committed files)')}`);
66
77
  console.log(` ${teal('--write, -w')} ${sage('Write output to target file')}`);
67
78
  console.log(` ${teal('--explain, -e')} ${sage('Full ranking breakdown')}`);
68
79
  console.log(` ${teal('--dry-run')} ${sage('Discover sources only')}`);
@@ -82,6 +93,9 @@ async function main() {
82
93
  sources = sources.filter(s => sourceFilter.some(f => s.type === f || s.type.startsWith(f)));
83
94
  }
84
95
 
96
+ const projectCount = sources.filter(s => s.scope !== 'global').length;
97
+ const globalCount = sources.filter(s => s.scope === 'global').length;
98
+
85
99
  console.log('');
86
100
  console.log(` ${b(cream('Sources discovered'))} ${slate(`(${sources.length})`)}`);
87
101
  ui.divider('line');
@@ -89,10 +103,13 @@ async function main() {
89
103
  console.log(` ${sage('No recognized memory files found in')} ${slate(process.cwd())}`);
90
104
  } else {
91
105
  for (const source of sources) {
92
- console.log(` ${teal(source.type.padEnd(20))} ${sage(source.name)}`);
106
+ const tag = source.scope === 'global' ? slate('global ') : sage('project');
107
+ console.log(` ${tag} ${teal(source.type.padEnd(18))} ${sage(source.name)}`);
93
108
  }
94
109
  }
95
110
  ui.divider('line');
111
+ console.log(` ${slate(`${projectCount} project · ${globalCount} global`)}`);
112
+ console.log(` ${slate('global = per-user memory across all tools; summary-only unless --include-global on write')}`);
96
113
  console.log('');
97
114
  return;
98
115
  }
@@ -107,6 +124,7 @@ async function main() {
107
124
  sources: sourceFilter,
108
125
  explain: flags.explain,
109
126
  write: flags.write,
127
+ includeGlobal: flags.includeGlobal,
110
128
  });
111
129
 
112
130
  // If target is stdout, emit already printed
@@ -1,5 +1,15 @@
1
- // Discover all memory/context source files in the working directory.
2
- // Returns a list of { path, type } for each recognized source.
1
+ // Discover all memory/context source files for the working directory.
2
+ // Returns a list of { path, type, name, scope } for each recognized source.
3
+ //
4
+ // Two scopes:
5
+ // 'project' — files in the working directory (this repo/project)
6
+ // 'global' — per-user memory that travels across every project
7
+ // (your global CLAUDE.md, Codex AGENTS.md, Gemini GEMINI.md)
8
+ //
9
+ // Global sources enrich the summary so `phewsh seq` reflects cross-tool
10
+ // continuity even in a bare directory. They are deliberately kept OUT of
11
+ // project-file writes by default (see sequence() includeGlobal) so personal
12
+ // global notes never leak into a committed project CLAUDE.md.
3
13
 
4
14
  const fs = require('fs');
5
15
  const path = require('path');
@@ -8,8 +18,18 @@ const os = require('os');
8
18
  const INTENT_FILES = ['vision.md', 'plan.md', 'status.md', 'narrative.md', 'next.md'];
9
19
  const INTENT_JSON = ['project.json', 'pps.json', 'gate.json'];
10
20
 
11
- function discover(cwd = process.cwd()) {
21
+ function discover(cwd = process.cwd(), home = os.homedir()) {
12
22
  const sources = [];
23
+ const seenPaths = new Set();
24
+
25
+ const add = (s) => {
26
+ const real = path.resolve(s.path);
27
+ if (seenPaths.has(real)) return; // never list the same file twice
28
+ seenPaths.add(real);
29
+ sources.push(s);
30
+ };
31
+
32
+ // ── Project scope ──────────────────────────────────────────────
13
33
 
14
34
  // .intent/ artifacts
15
35
  const intentDir = path.join(cwd, '.intent');
@@ -17,7 +37,7 @@ function discover(cwd = process.cwd()) {
17
37
  for (const file of [...INTENT_FILES, ...INTENT_JSON]) {
18
38
  const p = path.join(intentDir, file);
19
39
  if (fs.existsSync(p)) {
20
- sources.push({ path: p, type: 'intent', name: file });
40
+ add({ path: p, type: 'intent', name: file, scope: 'project' });
21
41
  }
22
42
  }
23
43
  }
@@ -25,12 +45,12 @@ function discover(cwd = process.cwd()) {
25
45
  // CLAUDE.md — split into manual and generated sections later by parser
26
46
  const claudeMd = path.join(cwd, 'CLAUDE.md');
27
47
  if (fs.existsSync(claudeMd)) {
28
- sources.push({ path: claudeMd, type: 'claude-md', name: 'CLAUDE.md' });
48
+ add({ path: claudeMd, type: 'claude-md', name: 'CLAUDE.md', scope: 'project' });
29
49
  }
30
50
 
31
51
  // Claude auto-memory — project-scoped
32
- // .claude/projects/<encoded-cwd>/memory/MEMORY.md
33
- const claudeDir = path.join(os.homedir(), '.claude');
52
+ // ~/.claude/projects/<encoded-cwd>/memory/MEMORY.md
53
+ const claudeDir = path.join(home, '.claude');
34
54
  if (fs.existsSync(claudeDir)) {
35
55
  const projectsDir = path.join(claudeDir, 'projects');
36
56
  if (fs.existsSync(projectsDir)) {
@@ -40,7 +60,7 @@ function discover(cwd = process.cwd()) {
40
60
  if (fs.existsSync(memoryDir)) {
41
61
  const memoryIndex = path.join(memoryDir, 'MEMORY.md');
42
62
  if (fs.existsSync(memoryIndex)) {
43
- sources.push({ path: memoryIndex, type: 'claude-memory', name: 'MEMORY.md' });
63
+ add({ path: memoryIndex, type: 'claude-memory', name: 'MEMORY.md', scope: 'project' });
44
64
 
45
65
  // Also discover linked memory files from MEMORY.md
46
66
  try {
@@ -50,7 +70,7 @@ function discover(cwd = process.cwd()) {
50
70
  while ((match = linkRegex.exec(content)) !== null) {
51
71
  const linked = path.join(memoryDir, match[2]);
52
72
  if (fs.existsSync(linked)) {
53
- sources.push({ path: linked, type: 'claude-memory-file', name: match[2] });
73
+ add({ path: linked, type: 'claude-memory-file', name: match[2], scope: 'project' });
54
74
  }
55
75
  }
56
76
  } catch { /* skip */ }
@@ -62,33 +82,54 @@ function discover(cwd = process.cwd()) {
62
82
  // .cursorrules
63
83
  const cursorrules = path.join(cwd, '.cursorrules');
64
84
  if (fs.existsSync(cursorrules)) {
65
- sources.push({ path: cursorrules, type: 'cursor', name: '.cursorrules' });
85
+ add({ path: cursorrules, type: 'cursor', name: '.cursorrules', scope: 'project' });
66
86
  }
67
87
 
68
- // agent.md / AGENTS.md
88
+ // agent.md / AGENTS.md (Codex / generic agent instructions, project scope)
69
89
  for (const name of ['agent.md', 'AGENTS.md']) {
70
90
  const p = path.join(cwd, name);
71
91
  if (fs.existsSync(p)) {
72
- sources.push({ path: p, type: 'agent', name });
92
+ add({ path: p, type: 'agent', name, scope: 'project' });
73
93
  }
74
94
  }
75
95
 
96
+ // GEMINI.md (project scope)
97
+ const geminiMd = path.join(cwd, 'GEMINI.md');
98
+ if (fs.existsSync(geminiMd)) {
99
+ add({ path: geminiMd, type: 'agent', name: 'GEMINI.md', scope: 'project' });
100
+ }
101
+
76
102
  // soul.md
77
103
  const soulMd = path.join(cwd, 'soul.md');
78
104
  if (fs.existsSync(soulMd)) {
79
- sources.push({ path: soulMd, type: 'soul', name: 'soul.md' });
105
+ add({ path: soulMd, type: 'soul', name: 'soul.md', scope: 'project' });
80
106
  }
81
107
 
82
108
  // .github/copilot-instructions.md
83
109
  const copilot = path.join(cwd, '.github', 'copilot-instructions.md');
84
110
  if (fs.existsSync(copilot)) {
85
- sources.push({ path: copilot, type: 'copilot', name: 'copilot-instructions.md' });
111
+ add({ path: copilot, type: 'copilot', name: 'copilot-instructions.md', scope: 'project' });
86
112
  }
87
113
 
88
114
  // README.md (low priority but useful for identity)
89
115
  const readme = path.join(cwd, 'README.md');
90
116
  if (fs.existsSync(readme)) {
91
- sources.push({ path: readme, type: 'readme', name: 'README.md' });
117
+ add({ path: readme, type: 'readme', name: 'README.md', scope: 'project' });
118
+ }
119
+
120
+ // ── Global scope (per-user, travels across every project) ──────
121
+ // Canonical global memory files for each agent CLI. Read-only; never
122
+ // written to. These give value even with no .intent/ in the directory.
123
+ // De-dup via seenPaths handles the edge where cwd === home.
124
+ const globals = [
125
+ { path: path.join(home, '.claude', 'CLAUDE.md'), type: 'claude-md', name: '~/.claude/CLAUDE.md' },
126
+ { path: path.join(home, '.codex', 'AGENTS.md'), type: 'agent', name: '~/.codex/AGENTS.md' },
127
+ { path: path.join(home, '.gemini', 'GEMINI.md'), type: 'agent', name: '~/.gemini/GEMINI.md' },
128
+ ];
129
+ for (const g of globals) {
130
+ if (fs.existsSync(g.path)) {
131
+ add({ path: g.path, type: g.type, name: g.name, scope: 'global' });
132
+ }
92
133
  }
93
134
 
94
135
  return sources;
@@ -58,7 +58,8 @@ function emitExplain(chunks, sources, { b, w, g, sage, slate, teal, cream, green
58
58
  // Sources discovered
59
59
  console.log(` ${b(cream('Sources'))}`);
60
60
  for (const source of sources) {
61
- console.log(` ${teal(source.type.padEnd(16))} ${sage(source.name)}`);
61
+ const tag = source.scope === 'global' ? slate('global ') : sage('project');
62
+ console.log(` ${tag} ${teal(source.type.padEnd(14))} ${sage(source.name)}`);
62
63
  }
63
64
  console.log('');
64
65
 
@@ -45,12 +45,20 @@ function sequence(options = {}) {
45
45
  sources: sourceFilter = null,
46
46
  explain = false,
47
47
  write = false,
48
+ includeGlobal = false,
48
49
  cwd = process.cwd(),
49
50
  } = options;
50
51
 
51
- // 1. Discover all source files
52
+ // 1. Discover all source files (project + global)
52
53
  let sources = discover(cwd);
53
54
 
55
+ // Privacy guard: global per-user memory enriches the summary (stdout), but
56
+ // never bleeds into a project-file target (CLAUDE.md) unless asked for —
57
+ // so personal global notes don't land in a committed project file.
58
+ if (target !== 'stdout' && !includeGlobal) {
59
+ sources = sources.filter(s => s.scope !== 'global');
60
+ }
61
+
54
62
  // Filter sources if requested
55
63
  if (sourceFilter && sourceFilter.length > 0) {
56
64
  sources = sources.filter(s =>
@@ -39,8 +39,9 @@ function parse(source) {
39
39
 
40
40
  const kind = classifySection(section.title);
41
41
  chunks.push({
42
- source: `CLAUDE.md:${section.title || 'root'}`,
42
+ source: `${source.name}:${section.title || 'root'}`,
43
43
  sourceType: 'claude-md-manual',
44
+ scope: source.scope || 'project',
44
45
  kind,
45
46
  content: section.body.trim(),
46
47
  timestamp: mtime,
@@ -52,8 +53,9 @@ function parse(source) {
52
53
  // Generated section — lower authority, will get deduped against .intent/
53
54
  if (generatedContent) {
54
55
  chunks.push({
55
- source: 'CLAUDE.md:phewsh-generated',
56
+ source: `${source.name}:phewsh-generated`,
56
57
  sourceType: 'claude-md-generated',
58
+ scope: source.scope || 'project',
57
59
  kind: 'context',
58
60
  content: generatedContent,
59
61
  timestamp: mtime,
@@ -21,6 +21,7 @@ function parse(source) {
21
21
  return [{
22
22
  source: source.name,
23
23
  sourceType: source.type,
24
+ scope: source.scope || 'project',
24
25
  kind,
25
26
  content: content.trim(),
26
27
  timestamp: mtime,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "phewsh",
3
- "version": "0.15.10",
3
+ "version": "0.15.11",
4
4
  "description": "Turn intent into action. Structure your thinking, execute your next step.",
5
5
  "bin": {
6
6
  "phewsh": "bin/phewsh.js"