phewsh 0.15.62 → 0.15.64

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 phewsh (hello@phewsh.com)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -5,9 +5,24 @@ Turn intent into action. Structure your thinking, execute your next step.
5
5
  ## Install
6
6
 
7
7
  ```bash
8
- npm install -g phewsh
8
+ curl -fsSL https://phewsh.com/install.sh | sh
9
9
  ```
10
10
 
11
+ No sudo, nvm-aware. (Prefer a package manager? `npm install -g phewsh` —
12
+ same artifact, built from this repo.)
13
+
14
+ Then let it improve the AI tools you already have — without launching anything:
15
+
16
+ ```bash
17
+ phewsh ambient on # your .intent/ + a verified brief reach Claude Code, Codex,
18
+ # Gemini, Cursor at the start of every session (reversible)
19
+ ```
20
+
21
+ phewsh isn't just a CLI — `ambient` makes every supported tool start aligned to
22
+ what you're building. What it reads, writes, and sends is documented, factually,
23
+ in [SECURITY.md](./SECURITY.md). It's local-first and MIT; this repo is an exact
24
+ mirror of the published npm package.
25
+
11
26
  ## Quick Start
12
27
 
13
28
  ```bash
package/SECURITY.md ADDED
@@ -0,0 +1,97 @@
1
+ # Security & Data Flow — phewsh CLI
2
+
3
+ phewsh is a **local-first** tool. It runs on your machine, reads your project's
4
+ `.intent/` and your installed AI tools' config, and helps you carry context
5
+ between those tools. This document states — factually, traceable to source —
6
+ what it reads, writes, and sends. No vague claims; if something here is wrong,
7
+ it's a bug: **hello@phewsh.com**.
8
+
9
+ > Status: this is an honest self-documented inventory, **not** an independent
10
+ > audit. The CLI source is **public and MIT-licensed** at
11
+ > **github.com/cleverIdeaz/phewsh-cli** — an exact mirror of the published npm
12
+ > package, so you can read exactly what you install (`bin/ commands/ lib/ mcp/`).
13
+ > An independent review is still planned (see "Roadmap").
14
+
15
+ ## What phewsh reads
16
+ - **`.intent/`** in your project (`vision.md`, `plan.md`, `status.md`, `next.md`,
17
+ `project.json`, `next.json`). This is the source of truth it projects.
18
+ - **Your tools' context files** when present: `CLAUDE.md`, `AGENTS.md`,
19
+ `GEMINI.md`, `.cursorrules`, `.github/copilot-instructions.md`, `README.md`.
20
+ - **Your Claude project memory** (`~/.claude/projects/<cwd>/memory/MEMORY.md` and
21
+ files it links) and **global per-user memory** (`~/.claude/CLAUDE.md`,
22
+ `~/.codex/AGENTS.md`, `~/.gemini/GEMINI.md`) — read-only, used to enrich the
23
+ `phewsh seq` summary. Global memory is **never** written into a project file
24
+ unless you pass `--include-global` (`lib/sequencer/index.js`).
25
+ - **Git status** of the current repo (read-only: `git status`, HEAD, diff stats).
26
+
27
+ ## What phewsh writes (and where)
28
+ Every generated block is wrapped in `<!-- PHEWSH:START -->` / `<!-- PHEWSH:END -->`
29
+ markers; **content outside the markers is preserved byte-for-byte** (tested).
30
+ - **Project context files** — the canonical `.intent/` projection into
31
+ `CLAUDE.md` / `AGENTS.md` / `GEMINI.md` / `.cursorrules` (only files that
32
+ already exist, unless you create them). One canonical projector
33
+ (`lib/selfheal.js`) is the single writer.
34
+ - **`~/.phewsh/`** — `config.json` (your settings/keys, mode **0600**, dir 0700),
35
+ `briefs/` (saved handoff briefs), `sessions/`, `receipts/`, ledger files.
36
+ - **Shell rc** — only if you run `phewsh shim on` (adds one PATH line; reversible
37
+ with `phewsh shim off`).
38
+ - **Global base files** — only if you run `phewsh ambient on`: a marker-wrapped
39
+ block in `~/.claude/CLAUDE.md` etc. for tools you already have. Reversible with
40
+ `phewsh ambient off`.
41
+ - **Claude Code hook** — `phewsh ambient on` registers `SessionStart`/`SessionEnd`
42
+ hooks (context injection + a metadata breadcrumb; **never your transcript**).
43
+
44
+ ## Network calls (exactly these)
45
+ phewsh makes **no network calls for its core local features** (sequencing,
46
+ projection, briefs, status). Calls happen only for these named actions:
47
+ - **`api.anthropic.com` / `openrouter.ai` / `api.together.xyz`** — only if you set
48
+ a BYOK key and use `/run` or the API route. Your key goes **directly** to the
49
+ provider; it is **not** proxied through phewsh servers.
50
+ - **`registry.npmjs.org`** — version check for `phewsh update` (notify-only by
51
+ default).
52
+ - **`<project>.supabase.co`** — only if you `/login`: auth + optional cloud sync
53
+ of your `.intent/` (you initiate `push`/`pull`).
54
+ - **`phewsh.com`** — only for `phewsh serve`/cloud-bridge actions you start.
55
+ - **Sustainability telemetry** (`trackSap`, `lib/supabase.js`) — sends **model
56
+ name, token counts, and a kWh estimate only**. No prompts, no responses, no
57
+ file contents. Tied to your account id only when logged in.
58
+
59
+ ## Credentials
60
+ - A bring-your-own key lives in `~/.phewsh/config.json` at mode **0600**
61
+ (`lib/config-file.js`) and is sent **directly to the provider**, never to us.
62
+ - phewsh routes through your installed tools on **their own login** (your Claude
63
+ subscription, your ChatGPT plan) — it does not see or store those credentials.
64
+
65
+ ## The local bridge
66
+ `phewsh serve` binds **`127.0.0.1` only** (loopback), with an origin allowlist,
67
+ and runs only while you run it.
68
+
69
+ ## Known issues / hardening backlog (honest)
70
+ - **Local shell-injection surface (medium, local-only):** a few call sites build
71
+ shell strings from REPL input via `execSync` (e.g. `commands/session.js` gate /
72
+ outcomes arg passthrough). Inputs are your own typed/pasted text on your own
73
+ machine, but crafted args could execute. Fix: convert to argument-safe
74
+ `execFileSync`/`spawnSync`. Tracked as a Next item.
75
+ - **No release provenance yet** — npm provenance, signed tags, and a public
76
+ source repo are planned (Roadmap) so the published artifact is verifiable.
77
+
78
+ ## Disable / uninstall
79
+ - `phewsh ambient off` — removes injected hooks and global base files; restores
80
+ your files.
81
+ - `phewsh shim off` — removes the PATH line.
82
+ - `npm uninstall -g phewsh` — removes the CLI. `rm -rf ~/.phewsh` removes local
83
+ state. Generated `PHEWSH:START/END` blocks can be deleted by hand; everything
84
+ outside them is yours and untouched.
85
+
86
+ ## Roadmap to verifiable trust
87
+ Done: this doc, exec hardening, telemetry stays counts-only and explicit, and a
88
+ **public MIT source repo** (`cleverIdeaz/phewsh-cli`) mirroring the npm package.
89
+ Before broad promotion: npm provenance, signed tags, and dependency/secret
90
+ scanning so the published artifact is cryptographically verifiable from the
91
+ public source. For enterprise: a documented threat model and an independent
92
+ security assessment. We will not call phewsh "audited" until an independent
93
+ audit has actually occurred.
94
+
95
+ ## Reporting
96
+ Security issues: **hello@phewsh.com**. Please allow reasonable time to remediate
97
+ before public disclosure.
package/commands/mbhd.js CHANGED
@@ -1,16 +1,17 @@
1
- const { execSync } = require('child_process');
1
+ const { execFileSync } = require('child_process');
2
2
 
3
3
  const WEB_URL = 'https://phewsh.com/mbhd';
4
4
 
5
5
  console.log(`\n 🎵 Opening MBHD Music Engine...\n`);
6
6
 
7
7
  try {
8
+ // execFileSync (no shell) — argument-safe even though the URL is constant.
8
9
  if (process.platform === 'darwin') {
9
- execSync(`open "${WEB_URL}"`);
10
+ execFileSync('open', [WEB_URL]);
10
11
  } else if (process.platform === 'win32') {
11
- execSync(`start "${WEB_URL}"`);
12
+ execFileSync('cmd', ['/c', 'start', '', WEB_URL]);
12
13
  } else {
13
- execSync(`xdg-open "${WEB_URL}"`);
14
+ execFileSync('xdg-open', [WEB_URL]);
14
15
  }
15
16
  } catch {
16
17
  console.log(` Could not open browser. Visit: ${WEB_URL}\n`);
package/commands/mcp.js CHANGED
@@ -48,7 +48,7 @@ function loadLocalProject() {
48
48
  decisionGate: null,
49
49
  };
50
50
 
51
- const files = ['vision.md', 'plan.md', 'next.md', 'status.md', 'narrative.md'];
51
+ const files = ['vision.md', 'plan.md', 'next.md', 'status.md'];
52
52
  for (const file of files) {
53
53
  const filePath = path.join(INTENT_DIR, file);
54
54
  if (fs.existsSync(filePath)) {
@@ -117,6 +117,20 @@ async function main() {
117
117
  // Default behavior: no target = stdout summary
118
118
  const target = flags.target || 'stdout';
119
119
 
120
+ // CANONICAL WRITE. Writing a native projection (e.g. `phewsh seq claude -w`)
121
+ // goes through the one canonical projector (self-heal), NOT the broad
122
+ // sequencer — identical block, identical source policy, no stale clobber.
123
+ if (flags.write && target === 'claude-md') {
124
+ const selfheal = require('../lib/selfheal');
125
+ const res = selfheal.syncContextFiles({ targets: ['CLAUDE.md'], createMissing: true });
126
+ if (res.synced && res.synced.length) {
127
+ console.log(`\n ${green('✓')} ${sage('CLAUDE.md — canonical .intent/ projection (same block self-heal & watch write)')}\n`);
128
+ } else {
129
+ console.log(`\n ${slate('CLAUDE.md already current' + (res.reason ? ' (' + res.reason + ')' : ''))}\n`);
130
+ }
131
+ return;
132
+ }
133
+
120
134
  try {
121
135
  const result = sequence({
122
136
  target,
@@ -554,6 +554,8 @@ async function main() {
554
554
  const parts = [cream('HEAD ' + v.shortHead)];
555
555
  parts.push(v.dirtyCount ? peach(`${v.dirtyCount} uncommitted`) : sage('clean'));
556
556
  if (v.driftCommits > 0) parts.push(ember(`⚠ ${v.driftCommits} commit${v.driftCommits !== 1 ? 's' : ''} since .intent updated`));
557
+ // Sharper than recency: the docs still name an older version than shipped.
558
+ if (v.versionDrift) parts.push(ember(`⚠ .intent says ${v.versionDrift.claimed}, shipped ${v.versionDrift.shipped} — /reconcile`));
557
559
  row('VERIFIED', parts.join(slate(' · ')) + slate(' — /truth · /brief'));
558
560
  }
559
561
  } catch { /* the verified row is a glance, never a blocker */ }
@@ -1712,7 +1714,6 @@ async function main() {
1712
1714
  console.log(` ${teal('/key')} ${sage('Set API key (optional — harnesses need none)')}`);
1713
1715
  console.log(` ${teal('/login')} ${sage('Identity + cloud sync')}`);
1714
1716
  console.log(` ${teal('/model')} ${slate('<name>')} ${sage('Switch model — passed through, the provider validates')}`);
1715
- console.log(` ${teal('/width')} ${slate('[n]')} ${sage('Fix mid-word wrapping if your terminal misreports its size')}`);
1716
1717
  console.log(` ${teal('/update')} ${sage('Update phewsh')}`);
1717
1718
  console.log(` ${teal('/tour')} ${sage('Quick walkthrough')}`);
1718
1719
  console.log('');
@@ -1925,9 +1926,11 @@ async function main() {
1925
1926
 
1926
1927
  if (cmd === 'gate') {
1927
1928
  try {
1928
- const { execSync } = require('child_process');
1929
- const gateArg = cmdArg || 'status';
1930
- execSync(`node ${path.join(__dirname, 'gate.js')} ${gateArg}`, { stdio: 'inherit' });
1929
+ // execFileSync with an argv array — no shell, so REPL input can never
1930
+ // be interpreted as shell metacharacters (hardening: was execSync).
1931
+ const { execFileSync } = require('child_process');
1932
+ const gateArgs = (cmdArg || 'status').split(/\s+/).filter(Boolean);
1933
+ execFileSync(process.execPath, [path.join(__dirname, 'gate.js'), ...gateArgs], { stdio: 'inherit' });
1931
1934
  intentFiles = loadIntentContext();
1932
1935
  systemPrompt = buildSystemPrompt(intentFiles);
1933
1936
  } catch (err) {
@@ -1940,17 +1943,43 @@ async function main() {
1940
1943
  if (cmd === 'seq' || cmd === 'sequence') {
1941
1944
  try {
1942
1945
  const { sequence } = require('../lib/sequencer');
1946
+ const { resolveProjectRoot, discover } = require('../lib/sequencer/discover');
1943
1947
  const target = cmdArg?.split(/\s+/)[0];
1944
1948
  const explain = cmdArg?.includes('explain') || cmdArg?.includes('-e');
1945
- const write = cmdArg?.includes('-w') || cmdArg?.includes('write');
1946
1949
 
1947
1950
  if (target === 'claude' || target === 'claude-md') {
1948
- const result = sequence({ target: 'claude-md', write: true });
1949
- if (result.writeResult) {
1950
- console.log(`\n ${teal('●')} ${sage('CLAUDE.md ' + result.writeResult)} ${slate('(' + result.chunks.length + ' chunks)')}\n`);
1951
+ // CANONICAL WRITE. /seq claude writes the SAME deterministic block as
1952
+ // self-heal and watch — one source policy (.intent canonical), one
1953
+ // renderer so a manual sequence can never produce a divergent or
1954
+ // stale CLAUDE.md. (Previously this called the broad sequencer, which
1955
+ // ingested narrative.md + memory logs and clobbered the canonical block.)
1956
+ const res = selfheal.syncContextFiles({ targets: ['CLAUDE.md'], createMissing: true });
1957
+ if (res.synced && res.synced.length) {
1958
+ console.log(`\n ${teal('●')} ${sage('CLAUDE.md updated — canonical .intent/ projection')} ${slate('(same block self-heal & watch write)')}\n`);
1959
+ } else {
1960
+ console.log(`\n ${slate('CLAUDE.md already current' + (res.reason ? ' (' + res.reason + ')' : ''))}\n`);
1961
+ }
1962
+ } else if (explain || target === 'sources' || target === 'root') {
1963
+ // Diagnostic: show what the sequencer sees — resolved root, selected
1964
+ // sources (with scope), and the canonical projection's restricted set.
1965
+ const root = resolveProjectRoot(process.cwd());
1966
+ const found = discover(process.cwd());
1967
+ console.log('');
1968
+ console.log(` ${b(cream('Sequence diagnostics'))}`);
1969
+ ui.divider('line');
1970
+ console.log(` ${sage('resolved root')} ${cream(root)}${root !== process.cwd() ? slate(' (walked up from ' + process.cwd() + ')') : ''}`);
1971
+ console.log(` ${sage('canonical write set')} ${slate('vision.md · project.json · status.md · next.md · next.json')}`);
1972
+ console.log(` ${sage('broad read (this preview)')}`);
1973
+ for (const s of found) {
1974
+ const tag = s.scope === 'global' ? slate('global ') : sage('project');
1975
+ const canon = ['vision.md','project.json','status.md','next.md','next.json'].includes(s.name) ? teal(' ✓ canonical') : slate(' · preview-only');
1976
+ console.log(` ${tag} ${cream(s.name.padEnd(20))}${canon}`);
1951
1977
  }
1978
+ ui.divider('line');
1979
+ console.log(` ${slate('write the canonical projection with')} ${cream('/seq claude')}`);
1980
+ console.log('');
1952
1981
  } else {
1953
- // Default: stdout summary
1982
+ // Default: broad, READ-ONLY synthesis to stdout — never writes a file.
1954
1983
  sequence({ target: 'stdout', explain });
1955
1984
  }
1956
1985
  } catch (err) {
@@ -2544,10 +2573,11 @@ async function main() {
2544
2573
 
2545
2574
  if (cmd === 'outcomes') {
2546
2575
  try {
2547
- // execSync so the labeling prompt owns stdin while it runs
2548
- const { execSync } = require('child_process');
2549
- const outcomesArgs = cmdArg ? ' ' + cmdArg : '';
2550
- execSync(`node ${path.join(__dirname, '..', 'bin', 'phewsh.js')} outcomes${outcomesArgs}`, { stdio: 'inherit' });
2576
+ // execFileSync (argv array, no shell) so the labeling prompt owns
2577
+ // stdin AND REPL input can't be shell-interpreted (hardening).
2578
+ const { execFileSync } = require('child_process');
2579
+ const outcomesArgs = cmdArg ? cmdArg.split(/\s+/).filter(Boolean) : [];
2580
+ execFileSync(process.execPath, [path.join(__dirname, '..', 'bin', 'phewsh.js'), 'outcomes', ...outcomesArgs], { stdio: 'inherit' });
2551
2581
  } catch { /* user quit mid-labeling — fine */ }
2552
2582
  rl.prompt();
2553
2583
  return;
@@ -2725,6 +2755,20 @@ async function main() {
2725
2755
  console.log(` ${teal('●')} ${sage('Back in phewsh.')} ${slate('No changes detected. 1 kept · 2 undid · 3 redid · 4 flopped · /reconcile · /switch <tool>')}`);
2726
2756
  }
2727
2757
  console.log(` ${slate('continuing elsewhere?')} ${cream('/handoff')} ${slate('shows the updated brief the next tool inherits')}`);
2758
+ // Auto-reconcile OFFER (human-gated, never auto-writes). Only when the
2759
+ // work left project UNDERSTANDING stale — the docs now disagree with the
2760
+ // code or a projection drifted. Ordinary implementation diffs and a merely
2761
+ // dirty tree do NOT trigger this; that would be noise.
2762
+ try {
2763
+ const truthDrift = (postflight.conflicts || []).filter(c =>
2764
+ /current-state intent disagrees|current-state claims may be stale|should be regenerated|before authoritative intent updated/i.test(c));
2765
+ if (truthDrift.length) {
2766
+ console.log('');
2767
+ console.log(` ${ember('⚠')} ${sage('Your project understanding may now be stale:')}`);
2768
+ console.log(` ${slate(truthDrift[0])}`);
2769
+ console.log(` ${sage('Bring the record up to date so the next tool inherits the truth →')} ${cream('/reconcile')} ${slate('(proposes an exact diff; writes only after you approve)')}`);
2770
+ }
2771
+ } catch { /* the offer is a nicety, never a blocker */ }
2728
2772
  console.log('');
2729
2773
  rl.prompt();
2730
2774
  return;
package/lib/selfheal.js CHANGED
@@ -205,6 +205,7 @@ function projectionStatus({ cwd = process.cwd(), targets = TARGET_FILES } = {})
205
205
  // `seq`), where the user is actually working in the project.
206
206
  function syncContextFiles({ cwd = process.cwd(), targets = TARGET_FILES, createMissing = true } = {}) {
207
207
  try {
208
+ cwd = require('./sequencer/discover').resolveProjectRoot(cwd);
208
209
  const intentDir = path.join(cwd, '.intent');
209
210
  if (!fs.existsSync(intentDir)) return { synced: [], reason: 'no-intent' };
210
211
  const core = buildContextCore(cwd);
@@ -15,10 +15,38 @@ const fs = require('fs');
15
15
  const path = require('path');
16
16
  const os = require('os');
17
17
 
18
- const INTENT_FILES = ['vision.md', 'plan.md', 'status.md', 'narrative.md', 'next.md'];
18
+ // narrative.md is intentionally NOT a source: it is a deprecated artifact that
19
+ // predates the four-word model (Project · Next · Work · Record) and is no longer
20
+ // authoritative project truth. It must never enter a native projection. See
21
+ // resolveProjectRoot + the canonical projection in selfheal.
22
+ const INTENT_FILES = ['vision.md', 'plan.md', 'status.md', 'next.md'];
19
23
  const INTENT_JSON = ['project.json', 'next.json', 'pps.json', 'gate.json'];
20
24
 
25
+ // Resolve the project root by walking UP from `start` to the nearest ancestor
26
+ // (including start) that contains a `.intent/` directory — bounded by the home
27
+ // dir and the filesystem root. This stops a nested dir (e.g. an app folder under
28
+ // a monorepo) from being treated as its own project, and makes every
29
+ // context-generating path agree on the same root. Returns `start` unchanged when
30
+ // no `.intent/` exists anywhere upward (so bare-directory behavior is untouched).
31
+ function resolveProjectRoot(start = process.cwd(), home = os.homedir()) {
32
+ let dir;
33
+ try { dir = path.resolve(start); } catch { return start; }
34
+ const fsRoot = path.parse(dir).root;
35
+ const homeReal = (() => { try { return fs.realpathSync(home); } catch { return home; } })();
36
+ while (true) {
37
+ if (fs.existsSync(path.join(dir, '.intent'))) return dir;
38
+ if (dir === fsRoot) break;
39
+ let real; try { real = fs.realpathSync(dir); } catch { real = dir; }
40
+ if (real === homeReal) break; // don't escape above the user's home
41
+ const parent = path.dirname(dir);
42
+ if (parent === dir) break;
43
+ dir = parent;
44
+ }
45
+ return path.resolve(start);
46
+ }
47
+
21
48
  function discover(cwd = process.cwd(), home = os.homedir()) {
49
+ cwd = resolveProjectRoot(cwd, home);
22
50
  const sources = [];
23
51
  const seenPaths = new Set();
24
52
 
@@ -119,4 +147,4 @@ function discover(cwd = process.cwd(), home = os.homedir()) {
119
147
  return sources;
120
148
  }
121
149
 
122
- module.exports = { discover };
150
+ module.exports = { discover, resolveProjectRoot };
@@ -3,7 +3,7 @@
3
3
  // The core pipeline: discover → parse → rank → compress → emit
4
4
  // One function. N inputs. 1 optimal output per target format.
5
5
 
6
- const { discover } = require('./discover');
6
+ const { discover, resolveProjectRoot } = require('./discover');
7
7
  const { rank } = require('./ranker');
8
8
  const { compress } = require('./compressor');
9
9
 
@@ -53,8 +53,12 @@ function sequence(options = {}) {
53
53
  cwd = process.cwd(),
54
54
  } = options;
55
55
 
56
+ // Resolve to the canonical project root once, so discovery, naming, and any
57
+ // file write all agree on the same project (a nested dir never self-resolves).
58
+ const root = resolveProjectRoot(cwd);
59
+
56
60
  // 1. Discover all source files (project + global)
57
- let sources = discover(cwd);
61
+ let sources = discover(root);
58
62
 
59
63
  // Privacy guard: global per-user memory enriches the summary (stdout), but
60
64
  // never bleeds into a project-file target (CLAUDE.md) unless asked for —
@@ -112,12 +116,12 @@ function sequence(options = {}) {
112
116
  }
113
117
 
114
118
  const output = emitter.emit(chunks, {
115
- projectName: getProjectName(chunks, cwd),
119
+ projectName: getProjectName(chunks, root),
116
120
  });
117
121
 
118
122
  // Write to file if requested
119
123
  if (write && emitter.writeToFile) {
120
- const result = emitter.writeToFile(output, cwd);
124
+ const result = emitter.writeToFile(output, root);
121
125
  return { chunks, output, sources, writeResult: result };
122
126
  }
123
127
 
package/lib/truth.js CHANGED
@@ -273,16 +273,50 @@ async function fetchNpmLatest(packageName, { fetchImpl = global.fetch, timeoutMs
273
273
  // Fast, offline, fail-soft snapshot for the front-door cockpit — no npm fetch,
274
274
  // no file hashing. Answers "what verified truth is loaded?" at a glance so the
275
275
  // product thesis (one verified truth) is visible the moment phewsh opens.
276
+ // Cheap, offline version-claim drift: does the shipped package version sit ahead
277
+ // of the newest version the .intent narrative still claims? This is the SHARP
278
+ // staleness signal the front door was missing — the recency-based "commits since
279
+ // .intent changed" check goes quiet the moment you edit status.md, even if its
280
+ // headline claim is stale. We compare the project's package version (repo root,
281
+ // or a cli/ monorepo child like phewsh itself) to the max version mentioned in
282
+ // status.md/next.md, and only flag when code has shipped PAST the docs.
283
+ function quickVersionDrift(cwd = process.cwd()) {
284
+ try {
285
+ let shipped = null;
286
+ for (const rel of ['package.json', 'cli/package.json']) {
287
+ const p = path.join(cwd, rel);
288
+ if (!fs.existsSync(p)) continue;
289
+ try { shipped = JSON.parse(fs.readFileSync(p, 'utf-8')).version; } catch { /* unreadable */ }
290
+ if (shipped) break;
291
+ }
292
+ if (!shipped) return null;
293
+ let claimed = null;
294
+ for (const rel of ['.intent/status.md', '.intent/next.md']) {
295
+ const p = path.join(cwd, rel);
296
+ if (!fs.existsSync(p)) continue;
297
+ for (const m of fs.readFileSync(p, 'utf-8').matchAll(/\bv?(\d+\.\d+\.\d+)\b/g)) {
298
+ if (!claimed || compareVersions(m[1], claimed) > 0) claimed = m[1];
299
+ }
300
+ }
301
+ if (!claimed) return null;
302
+ return compareVersions(shipped, claimed) > 0 ? { shipped, claimed } : null;
303
+ } catch {
304
+ return null;
305
+ }
306
+ }
307
+
276
308
  function quickVerifiedState(cwd = process.cwd()) {
277
309
  try {
278
310
  const git = gitSnapshot(cwd);
279
- if (!git.available) return { available: false, isRepo: false };
311
+ const versionDrift = quickVersionDrift(cwd);
312
+ if (!git.available) return { available: false, isRepo: false, versionDrift };
280
313
  return {
281
314
  available: true,
282
315
  isRepo: true,
283
316
  shortHead: git.shortHead,
284
317
  dirtyCount: git.tracked.length + git.untracked.length,
285
318
  driftCommits: git.drift?.tracked ? git.drift.commitsSince : 0,
319
+ versionDrift,
286
320
  };
287
321
  } catch {
288
322
  return { available: false, isRepo: false };
@@ -397,5 +431,6 @@ module.exports = {
397
431
  parsePorcelain,
398
432
  projectionInfo,
399
433
  quickVerifiedState,
434
+ quickVersionDrift,
400
435
  statusDrift,
401
436
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "phewsh",
3
- "version": "0.15.62",
3
+ "version": "0.15.64",
4
4
  "description": "Turn intent into action. Structure your thinking, execute your next step.",
5
5
  "bin": {
6
6
  "phewsh": "bin/phewsh.js"
@@ -10,7 +10,9 @@
10
10
  "commands/",
11
11
  "lib/",
12
12
  "mcp/",
13
- "README.md"
13
+ "README.md",
14
+ "SECURITY.md",
15
+ "LICENSE"
14
16
  ],
15
17
  "keywords": [
16
18
  "intent",
@@ -29,7 +31,10 @@
29
31
  "license": "MIT",
30
32
  "repository": {
31
33
  "type": "git",
32
- "url": "git+https://github.com/cleverIdeaz/phewsh.git"
34
+ "url": "git+https://github.com/cleverIdeaz/phewsh-cli.git"
35
+ },
36
+ "bugs": {
37
+ "url": "https://github.com/cleverIdeaz/phewsh-cli/issues"
33
38
  },
34
39
  "homepage": "https://phewsh.com",
35
40
  "engines": {