phewsh 0.15.45 → 0.15.47
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/bin/phewsh.js +8 -5
- package/commands/status.js +118 -0
- package/lib/harnesses.js +23 -0
- package/lib/shims.js +6 -12
- package/package.json +1 -1
package/bin/phewsh.js
CHANGED
|
@@ -87,6 +87,7 @@ const COMMANDS = {
|
|
|
87
87
|
seq: () => require('../commands/sequence')(),
|
|
88
88
|
ambient: () => require('../commands/ambient')(),
|
|
89
89
|
shim: () => require('../commands/shim')(),
|
|
90
|
+
status: () => require('../commands/status')(),
|
|
90
91
|
hook: () => require('../commands/hook')(),
|
|
91
92
|
welcome: () => require('../commands/welcome')(),
|
|
92
93
|
intro: () => require('../commands/welcome')(),
|
|
@@ -113,6 +114,7 @@ function showHelp() {
|
|
|
113
114
|
console.log(` ${cyan('intent')} ${g('Create, view, evolve .intent/ artifacts')}`);
|
|
114
115
|
console.log(` ${cyan('gate')} ${g('Set constraints (budget, time, skill, urgency)')}`);
|
|
115
116
|
console.log(` ${cyan('context')} ${g('Export .intent/ for any AI tool')}`);
|
|
117
|
+
console.log(` ${cyan('status')} ${g('git status for AI continuity — truth, record, drift, what\'s wired')}`);
|
|
116
118
|
console.log(` ${cyan('truth')} ${g('Read-only audit: versions, Git, intent, projections, conflicts')}`);
|
|
117
119
|
console.log(` ${cyan('brief')} ${g('Provider-ready briefing built from verified project truth')}`);
|
|
118
120
|
console.log(` ${cyan('ai')} ${g('One-shot prompt with .intent/ context')}
|
|
@@ -226,11 +228,12 @@ if (!command) {
|
|
|
226
228
|
exitAfterUpdate(0);
|
|
227
229
|
} else if (COMMANDS[command]) {
|
|
228
230
|
COMMANDS[command]();
|
|
229
|
-
} else if (require('../lib/harnesses').
|
|
230
|
-
// `phewsh <harness>` — a doorway shortcut.
|
|
231
|
-
//
|
|
232
|
-
//
|
|
233
|
-
|
|
231
|
+
} else if (require('../lib/harnesses').resolveHarness(command)) {
|
|
232
|
+
// `phewsh <harness>` — a doorway shortcut. Accepts the id OR the tool's real
|
|
233
|
+
// binary (so `phewsh claude` works, since that's the Claude Code binary).
|
|
234
|
+
// Launch the session and auto-run /work for that harness (preflight → brief →
|
|
235
|
+
// native handoff → postflight). This is what the phewsh.com doorways copy.
|
|
236
|
+
process.env.PHEWSH_AUTOWORK = require('../lib/harnesses').resolveHarness(command);
|
|
234
237
|
ambientSelfHeal().finally(() => maybeFirstRunIntro().then(() => COMMANDS.session()));
|
|
235
238
|
} else {
|
|
236
239
|
console.error(`\n Unknown command: ${command}\n Run 'phewsh help' for available commands.\n`);
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// phewsh status — "git status for AI continuity."
|
|
2
|
+
//
|
|
3
|
+
// The product, stated as health. The user's real question isn't "did CLAUDE.md
|
|
4
|
+
// load" — it's "will the next AI know what the last one learned?" This answers
|
|
5
|
+
// it: project truth, the decision record, cross-tool continuity, drift, and how
|
|
6
|
+
// phewsh is wired into this machine. Everything else (hooks, shims, base files,
|
|
7
|
+
// /intent) is implementation that feeds this one view. Offline, deterministic.
|
|
8
|
+
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const os = require('os');
|
|
12
|
+
|
|
13
|
+
const b = (s) => `\x1b[1m${s}\x1b[0m`;
|
|
14
|
+
const teal = (s) => `\x1b[38;5;79m${s}\x1b[0m`;
|
|
15
|
+
const sage = (s) => `\x1b[38;5;151m${s}\x1b[0m`;
|
|
16
|
+
const slate = (s) => `\x1b[38;5;247m${s}\x1b[0m`;
|
|
17
|
+
const cream = (s) => `\x1b[38;5;230m${s}\x1b[0m`;
|
|
18
|
+
const peach = (s) => `\x1b[38;5;216m${s}\x1b[0m`;
|
|
19
|
+
const green = (s) => `\x1b[32m${s}\x1b[0m`;
|
|
20
|
+
const yellow = (s) => `\x1b[33m${s}\x1b[0m`;
|
|
21
|
+
|
|
22
|
+
const ok = green('✓');
|
|
23
|
+
const off = slate('○');
|
|
24
|
+
const warn = peach('⚠');
|
|
25
|
+
|
|
26
|
+
function row(label, value) {
|
|
27
|
+
return ` ${slate(label.padEnd(13))} ${value}`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function projectName(cwd) {
|
|
31
|
+
try {
|
|
32
|
+
const m = JSON.parse(fs.readFileSync(path.join(cwd, '.intent', 'project.json'), 'utf-8'));
|
|
33
|
+
if (m.name) return m.name;
|
|
34
|
+
} catch { /* fall through */ }
|
|
35
|
+
return path.basename(cwd);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function loadDecisions() {
|
|
39
|
+
try { return JSON.parse(fs.readFileSync(path.join(os.homedir(), '.phewsh', 'outcomes', 'decisions.json'), 'utf-8')); }
|
|
40
|
+
catch { return []; }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function loadLedger() {
|
|
44
|
+
try { return JSON.parse(fs.readFileSync(path.join(os.homedir(), '.phewsh', 'ambient.json'), 'utf-8')); }
|
|
45
|
+
catch { return { applied: {} }; }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function main() {
|
|
49
|
+
const cwd = process.cwd();
|
|
50
|
+
const intentDir = path.join(cwd, '.intent');
|
|
51
|
+
const hasIntent = fs.existsSync(intentDir);
|
|
52
|
+
const key = path.basename(cwd);
|
|
53
|
+
|
|
54
|
+
console.log('');
|
|
55
|
+
console.log(` ${b('😮💨 PHEWSH STATUS')} ${slate('· ' + projectName(cwd))}`);
|
|
56
|
+
console.log('');
|
|
57
|
+
|
|
58
|
+
// ── Continuity asset (the product) ─────────────────────────────────
|
|
59
|
+
if (!hasIntent) {
|
|
60
|
+
console.log(row('Truth', `${off} ${sage('no shared project truth here yet')}`));
|
|
61
|
+
console.log(row('', slate('create it so the next AI inherits this one\'s context:')));
|
|
62
|
+
console.log(row('', cream('phewsh intent --init')));
|
|
63
|
+
} else {
|
|
64
|
+
const files = fs.readdirSync(intentDir).filter(f => /\.(md|json)$/.test(f));
|
|
65
|
+
const has = (f) => files.includes(f);
|
|
66
|
+
console.log(row('Truth', `${ok} ${sage('.intent present')} ${slate('(' + files.length + ' files)')}`));
|
|
67
|
+
console.log(row('Vision', has('vision.md') ? `${ok} ${slate('loaded')}` : `${off} ${slate('missing')}`));
|
|
68
|
+
console.log(row('Next step', (has('next.md') || has('status.md')) ? `${ok} ${slate('loaded')}` : `${off} ${slate('missing')}`));
|
|
69
|
+
|
|
70
|
+
// Decision record
|
|
71
|
+
let stats = { total: 0, pending: 0 };
|
|
72
|
+
try { stats = require('../lib/outcomes').outcomeStats({ project: key }); } catch { /* none */ }
|
|
73
|
+
const pend = stats.pending ? ` ${warn} ${peach(stats.pending + ' pending')}` : ` ${ok} ${slate('all labeled')}`;
|
|
74
|
+
console.log(row('Record', `${cream(stats.total + ' decisions')}${stats.total ? pend : slate(' — none yet')}`));
|
|
75
|
+
|
|
76
|
+
// Cross-tool continuity
|
|
77
|
+
try {
|
|
78
|
+
const continuity = require('../lib/continuity');
|
|
79
|
+
const decisions = loadDecisions();
|
|
80
|
+
const tools = continuity.toolsInThread(decisions, { project: key });
|
|
81
|
+
const line = continuity.lastLeftOff(decisions, { project: key });
|
|
82
|
+
if (tools >= 2) console.log(row('Continuity', `${ok} ${sage(tools + ' tools, one thread')}${line && line.ts ? slate(' · last ' + continuity.agoText(line.ts)) : ''}`));
|
|
83
|
+
else if (tools === 1) console.log(row('Continuity', `${slate('1 tool so far — continuity builds as you switch tools')}`));
|
|
84
|
+
else console.log(row('Continuity', slate('no activity recorded yet')));
|
|
85
|
+
} catch { /* best-effort */ }
|
|
86
|
+
|
|
87
|
+
// Drift
|
|
88
|
+
try {
|
|
89
|
+
const drift = require('../lib/selfheal').commitsSinceIntent(cwd);
|
|
90
|
+
console.log(row('Drift', drift > 0
|
|
91
|
+
? `${warn} ${peach(drift + ' commit(s) since .intent updated')} ${slate('— reconcile to refresh')}`
|
|
92
|
+
: `${ok} ${slate('0 — record matches the code')}`));
|
|
93
|
+
} catch { /* best-effort */ }
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ── Delivery (implementation — how phewsh reaches your tools) ───────
|
|
97
|
+
console.log('');
|
|
98
|
+
console.log(` ${slate('Delivery')}`);
|
|
99
|
+
const ledger = loadLedger();
|
|
100
|
+
let ambientOn = false;
|
|
101
|
+
try { ambientOn = fs.readFileSync(path.join(os.homedir(), '.claude', 'settings.json'), 'utf-8').includes('phewsh hook session-start'); } catch { /* off */ }
|
|
102
|
+
const gb = (ledger.applied && ledger.applied.globalBase) ? 'base files' : null;
|
|
103
|
+
const sc = (ledger.applied && ledger.applied.slashCommands && ledger.applied.slashCommands.tools) || [];
|
|
104
|
+
const ambientBits = [ambientOn ? 'Claude hook' : null, gb, sc.length ? '/intent in ' + sc.join(', ') : null].filter(Boolean);
|
|
105
|
+
console.log(row('Ambient', (ambientOn || gb) ? `${ok} ${sage('on')} ${slate('(' + (ambientBits.join(' · ') || 'context sync') + ')')}` : `${off} ${slate('off — ')}${cream('phewsh ambient on')}`));
|
|
106
|
+
|
|
107
|
+
let shimInstalled = [];
|
|
108
|
+
try { shimInstalled = require('../lib/shims').shimStatus().installed; } catch { /* none */ }
|
|
109
|
+
console.log(row('Shim', shimInstalled.length
|
|
110
|
+
? `${ok} ${sage('on')} ${slate('(' + shimInstalled.length + ' tools — banner each launch)')}`
|
|
111
|
+
: `${off} ${slate('off — ')}${cream('phewsh shim on')} ${slate('for a visible launch banner')}`));
|
|
112
|
+
|
|
113
|
+
console.log('');
|
|
114
|
+
console.log(` ${slate('The question this answers:')} ${cream('will the next AI know what the last one learned?')}`);
|
|
115
|
+
console.log('');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
module.exports = main;
|
package/lib/harnesses.js
CHANGED
|
@@ -218,9 +218,32 @@ function runViaHarness(id, systemPrompt, userPrompt, opts = {}) {
|
|
|
218
218
|
});
|
|
219
219
|
}
|
|
220
220
|
|
|
221
|
+
// Resolve a user-typed token to a harness id. Accepts the canonical id, the
|
|
222
|
+
// tool's actual binary (so `phewsh claude` → claude-code, since `claude` is the
|
|
223
|
+
// Claude Code binary; `phewsh cursor-agent` → cursor), or a friendly alias.
|
|
224
|
+
// Returns the harness id, or null if nothing matches.
|
|
225
|
+
const HARNESS_ALIASES = {
|
|
226
|
+
claude: 'claude-code',
|
|
227
|
+
cc: 'claude-code',
|
|
228
|
+
'cursor-agent': 'cursor',
|
|
229
|
+
'kiro-cli': 'kiro',
|
|
230
|
+
copilot: 'copilot',
|
|
231
|
+
};
|
|
232
|
+
function resolveHarness(token) {
|
|
233
|
+
if (!token) return null;
|
|
234
|
+
const t = String(token).toLowerCase();
|
|
235
|
+
if (HARNESSES[t]) return t;
|
|
236
|
+
if (HARNESS_ALIASES[t]) return HARNESS_ALIASES[t];
|
|
237
|
+
for (const [id, h] of Object.entries(HARNESSES)) {
|
|
238
|
+
if (h.bin && h.bin.toLowerCase() === t) return id;
|
|
239
|
+
}
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
|
|
221
243
|
module.exports = {
|
|
222
244
|
HARNESSES,
|
|
223
245
|
INSTALL,
|
|
246
|
+
resolveHarness,
|
|
224
247
|
isInstalled,
|
|
225
248
|
detectInstalled,
|
|
226
249
|
interactiveLaunchArgs,
|
package/lib/shims.js
CHANGED
|
@@ -118,23 +118,17 @@ function preflightBanner(bin, cwd = process.cwd()) {
|
|
|
118
118
|
}
|
|
119
119
|
return `${C.sage('😮💨🤫 phewsh active')} ${C.slate('· → ' + bin)}`;
|
|
120
120
|
}
|
|
121
|
-
// Truth present —
|
|
121
|
+
// Truth present — compact health line (like `git status` in a prompt, not a
|
|
122
|
+
// dump). Details live in `phewsh status`. One launch = one glance.
|
|
122
123
|
const files = fs.readdirSync(intentDir).filter(f => /\.(md|json)$/.test(f)).length;
|
|
123
|
-
|
|
124
|
-
const next = firstLine(path.join(intentDir, 'next.md')) || firstLine(path.join(intentDir, 'status.md'));
|
|
125
|
-
const decisions = decisionCount(cwd);
|
|
126
|
-
let health = '✓ intent';
|
|
124
|
+
let record = 'current';
|
|
127
125
|
try {
|
|
128
126
|
const { statusDrift } = require('./truth');
|
|
129
127
|
const d = statusDrift(cwd);
|
|
130
|
-
if (d && d.tracked
|
|
128
|
+
if (d && d.tracked && d.commitsSince > 0) record = `${d.commitsSince} behind`;
|
|
131
129
|
} catch { /* nicety */ }
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
if (vision) lines.push(` ${C.slate('vision:')} ${C.cream(vision)}`);
|
|
135
|
-
if (next) lines.push(` ${C.slate('next: ')} ${C.cream(next)}${decisions ? C.slate(' · ' + decisions + ' decisions') : ''}`);
|
|
136
|
-
else if (decisions) lines.push(` ${C.slate(decisions + ' decisions · ' + files + ' intent files')}`);
|
|
137
|
-
return lines.join('\n');
|
|
130
|
+
const continuity = decisionCount(cwd) >= 1 ? 'healthy' : 'building';
|
|
131
|
+
return `${C.sage('😮💨 phewsh')} ${C.teal('✓')} ${C.slate('intent: ' + files + ' files · record: ' + record + ' · continuity: ' + continuity)} ${C.slate('· phewsh status → ' + bin)}`;
|
|
138
132
|
} catch {
|
|
139
133
|
return `😮💨🤫 phewsh active · → ${bin}`; // never break the launch
|
|
140
134
|
}
|