phewsh 0.15.52 → 0.15.54

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
@@ -11,8 +11,11 @@ npm install -g phewsh
11
11
  ## Quick Start
12
12
 
13
13
  ```bash
14
- phewsh # enter the interactive shell
15
- phewsh serve # start live execution bridge for phewsh.com/intent
14
+ phewsh status # Project · Next · Work · Record at a glance
15
+ phewsh next add "ship the feature"
16
+ phewsh next criteria 1 file dist/app.js
17
+ phewsh next start 1
18
+ phewsh codex # verified brief → native tool → automatic postflight
16
19
  ```
17
20
 
18
21
  ## No API key needed
@@ -49,13 +52,13 @@ Rule of thumb: `phewsh serve` = dispatch tasks *to* your agents.
49
52
  phewsh ambient on
50
53
  ```
51
54
 
52
- A consent screen shows exactly what changes (two hook entries in Claude
53
- Code's settings), then: every Claude Code session in a project with
54
- `.intent/` starts pre-briefed, and every session leaves a one-line
55
+ A consent screen shows exactly what changes. Phewsh then keeps one canonical
56
+ generated core aligned across `CLAUDE.md`, `AGENTS.md`, `GEMINI.md`, and
57
+ `.cursorrules`, while preserving human-authored content outside its managed
58
+ blocks. Claude Code also receives a session-start brief and leaves a one-line
55
59
  metadata breadcrumb (never transcript contents) in
56
- `~/.phewsh/ambient-sessions.jsonl`. `phewsh ambient status` shows the
57
- full ledger; `phewsh ambient off` removes everything. Mission control
58
- is optional. Continuity is not.
60
+ `~/.phewsh/ambient-sessions.jsonl`. `phewsh ambient status` shows the ledger;
61
+ `phewsh ambient off` removes Phewsh-managed blocks without deleting your notes.
59
62
 
60
63
  ## Live Execution
61
64
 
@@ -95,21 +98,31 @@ single-line pastes of 300+ characters collapse after submission:
95
98
  Press `Ctrl+O` to inspect the last submitted paste and `Esc` to clear input or
96
99
  cancel an in-flight provider turn.
97
100
 
98
- ## What it does
101
+ ## The four answers
99
102
 
100
- Creates three structured artifacts in `.intent/`:
103
+ Phewsh keeps the same four answers available to you and every harness:
101
104
 
102
- - **vision.md** — The north star. Why this exists and where it's going.
103
- - **plan.md** — The strategy. Phases, systems, sequence, constraints.
104
- - **next.md** — Right now. Executable checklist with next actions.
105
+ - **Project** — what you are building and why (`.intent/`)
106
+ - **Next** — the current task and accepted success criteria (`.intent/next.json`)
107
+ - **Work** — the active native session and evidence-backed verification view
108
+ - **Record** — what happened, what was kept, and what was learned
105
109
 
106
- These artifacts become persistent context for AI conversations, both in the shell and across tools.
110
+ The lifecycle is explicit: intent criteria verified brief native tool
111
+ observed evidence → verdict → approved reconciliation → every other tool
112
+ inherits the updated truth.
107
113
 
108
114
  ## All Commands
109
115
 
110
116
  ```bash
111
117
  phewsh # The front door — routes through your installed agents
112
118
  phewsh setup # Guided setup: pick your default route (60 seconds)
119
+ phewsh status # Git status for AI continuity
120
+ phewsh next # Queue work and define success criteria
121
+ phewsh work # Current work + verification + human review
122
+ phewsh truth # Read-only source-of-truth audit
123
+ phewsh brief # Verified state + accepted work contract
124
+ phewsh watch # Sync native harness files + optional cloud mirror
125
+ phewsh ambient on # Automatic cross-tool continuity, with consent
113
126
  phewsh outcomes # Decision record — what was kept, reverted, or failed
114
127
  phewsh serve # Live execution bridge for web app
115
128
  phewsh clarify # AI-assisted artifact generation
@@ -169,7 +182,8 @@ phewsh push # upload .intent/ to cloud
169
182
  phewsh pull # download from cloud
170
183
  ```
171
184
 
172
- Sync is manual. The CLI shows status on startup.
185
+ Cloud sync is manual. Local harness projections self-heal automatically from
186
+ the same canonical `.intent/` source.
173
187
 
174
188
  ## Web app
175
189
 
package/bin/phewsh.js CHANGED
@@ -134,7 +134,7 @@ function showHelp() {
134
134
  console.log('');
135
135
  console.log(` ${b(w('sync everywhere'))}`);
136
136
  console.log(` ${cyan('seq')} ${g('Sequence all memory → optimal context for any agent')}`);
137
- console.log(` ${cyan('watch')} ${g('Auto-sync .intent/ → CLAUDE.md + cloud')}`);
137
+ console.log(` ${cyan('watch')} ${g('Auto-sync .intent/ → native harness files + cloud')}`);
138
138
  console.log(` ${cyan('push/pull')} ${g('Manual sync to/from phewsh.com/intent')}`);
139
139
  console.log(` ${cyan('serve')} ${g('Execution bridge — run from phewsh.com/intent')}`);
140
140
  console.log(` ${cyan('mcp')} ${g('Connect AI agents via MCP protocol')}`);
@@ -170,6 +170,7 @@ async function turnOn(skipConfirm) {
170
170
 
171
171
  const changes = applyClaudeHooks();
172
172
  const { written } = selfheal.syncGlobalBaseFiles();
173
+ const projectWritten = selfheal.syncContextFiles().synced || [];
173
174
  const slashWritten = slash.installSlashCommands().written;
174
175
  const ledger = loadLedger();
175
176
  ledger.applied['claude-code'] = {
@@ -201,6 +202,7 @@ async function turnOn(skipConfirm) {
201
202
  console.log(` ${green('●')} ${b('Ambient is on.')}`);
202
203
  changes.forEach(c => console.log(` ${teal('+')} ${slate(c)}`));
203
204
  written.forEach(f => console.log(` ${teal('+')} ${slate('base file ' + f + ' (machine-wide phewsh awareness)')}`));
205
+ projectWritten.forEach(f => console.log(` ${teal('+')} ${slate('project block ' + f + ' (shared canonical core)')}`));
204
206
  if (slashWritten.length) console.log(` ${teal('+')} ${cream('/intent')} ${slate('command added to ' + slashWritten.join(', '))}`);
205
207
  console.log('');
206
208
  console.log(` ${sage('Next Claude Code session in a project with')} ${cream('.intent/')} ${sage('starts pre-briefed.')}`);
@@ -214,6 +216,7 @@ async function turnOn(skipConfirm) {
214
216
  function turnOff() {
215
217
  const removed = removeClaudeHooks();
216
218
  const { removed: removedGlobal } = selfheal.removeGlobalBaseFiles();
219
+ const { removed: removedProject } = selfheal.removeProjectContextFiles();
217
220
  const { removed: removedSlash } = slash.removeSlashCommands();
218
221
  const ledger = loadLedger();
219
222
  delete ledger.applied['claude-code'];
@@ -223,10 +226,11 @@ function turnOff() {
223
226
  ledger.disabled = true; // sticky opt-out — first-run auto-enable must respect this
224
227
  saveLedger(ledger);
225
228
  console.log('');
226
- if (removed.length > 0 || removedGlobal.length > 0 || removedSlash.length > 0) {
229
+ if (removed.length > 0 || removedGlobal.length > 0 || removedProject.length > 0 || removedSlash.length > 0) {
227
230
  console.log(` ${green('●')} ${b('Ambient is off.')}`);
228
231
  removed.forEach(c => console.log(` ${peach('-')} ${slate(c)}`));
229
232
  removedGlobal.forEach(f => console.log(` ${peach('-')} ${slate('base file ' + f + ' (phewsh block removed)')}`));
233
+ removedProject.forEach(f => console.log(` ${peach('-')} ${slate('project block ' + f + ' (human content preserved)')}`));
230
234
  if (removedSlash.length) console.log(` ${peach('-')} ${slate('/intent command removed from ' + removedSlash.join(', '))}`);
231
235
  console.log(` ${sage('Breadcrumb log kept at')} ${cream('~/.phewsh/ambient-sessions.jsonl')} ${sage('— delete it if you want; phewsh never will.')}`);
232
236
  } else {
package/commands/next.js CHANGED
@@ -18,9 +18,13 @@ const sage = (s) => `\x1b[38;5;151m${s}\x1b[0m`;
18
18
  const slate = (s) => `\x1b[38;5;247m${s}\x1b[0m`;
19
19
  const cream = (s) => `\x1b[38;5;230m${s}\x1b[0m`;
20
20
  const green = (s) => `\x1b[32m${s}\x1b[0m`;
21
+ const yellow = (s) => `\x1b[33m${s}\x1b[0m`;
22
+ const red = (s) => `\x1b[31m${s}\x1b[0m`;
23
+ const peach = (s) => `\x1b[38;5;216m${s}\x1b[0m`;
21
24
 
22
25
  const MARK = { now: green('◐'), next: slate('○'), done: slate('✓') };
23
26
  const LABEL = { now: cream('NOW '), next: teal('NEXT'), done: slate('DONE') };
27
+ const VSYM = { pass: green('✓'), partial: yellow('~'), fail: red('✗'), unknown: slate('?'), human: peach('◇'), proposed: slate('○') };
24
28
 
25
29
  function render(cwd = process.cwd()) {
26
30
  const data = next.load(cwd);
@@ -36,9 +40,15 @@ function render(cwd = process.cwd()) {
36
40
  const n = slate(String(i + 1).padStart(2));
37
41
  const title = it.state === 'done' ? slate(it.title) : cream(it.title);
38
42
  console.log(` ${n} ${MARK[it.state]} ${LABEL[it.state]} ${title}`);
43
+ if (Array.isArray(it.criteria) && it.criteria.length) {
44
+ try {
45
+ const { results } = require('../lib/verify').verifyAll(it.criteria, cwd);
46
+ results.forEach(r => console.log(` ${VSYM[r.status] || slate('·')} ${slate(r.expected)} ${slate('· ' + r.note)}`));
47
+ } catch { /* verdicts are best-effort */ }
48
+ }
39
49
  });
40
50
  console.log('');
41
- console.log(` ${slate('start # · done # · drop # · add "…" · clear')}`);
51
+ console.log(` ${slate('start # · done # · drop # · add "…" · criteria # … · clear')}`);
42
52
  }
43
53
 
44
54
  // Unify with phewsh's own recommendation: the user's list is what THEY want
@@ -122,6 +132,42 @@ function main() {
122
132
  return;
123
133
  }
124
134
 
135
+ // Verification criteria — define what "done" looks like up front.
136
+ // phewsh next criteria <#> show verdicts
137
+ // phewsh next criteria <#> human "<expected>" a human judgment
138
+ // phewsh next criteria <#> file <path> a file must exist
139
+ // phewsh next criteria <#> contains <path> <txt> a file must contain text
140
+ // phewsh next criteria <#> changed <path> a path must change vs HEAD
141
+ // phewsh next criteria <#> accept | clear
142
+ if (sub === 'criteria' || sub === 'criterion') {
143
+ const ref = process.argv[4];
144
+ const kind = (process.argv[5] || '').toLowerCase();
145
+ const args = process.argv.slice(6);
146
+ if (!ref) { console.log(`\n ${slate('Which item? e.g.')} ${cream('phewsh next criteria 1 file out.txt')}\n`); return; }
147
+ if (!kind) { render(); return; }
148
+ if (kind === 'accept') { if (!next.acceptCriteria(ref)) return notFound(ref); render(); return; }
149
+ if (kind === 'clear') { if (!next.clearCriteria(ref)) return notFound(ref); render(); return; }
150
+ let criterion = null;
151
+ if (kind === 'human') {
152
+ criterion = { expected: args.join(' ').replace(/^["']|["']$/g, '') || 'human judgment required', type: 'human' };
153
+ } else if (kind === 'file' || kind === 'changed') {
154
+ const p = args[0];
155
+ if (!p) { console.log(`\n ${slate('Need a path. e.g.')} ${cream('phewsh next criteria ' + ref + ' ' + kind + ' cli/lib/verify.js')}\n`); return; }
156
+ const exp = args.slice(1).join(' ').replace(/^["']|["']$/g, '') || `${p} ${kind === 'file' ? 'exists' : 'changed'}`;
157
+ criterion = { expected: exp, type: 'measurable', check: { kind, path: p } };
158
+ } else if (kind === 'contains') {
159
+ const p = args[0]; const text = args.slice(1).join(' ').replace(/^["']|["']$/g, '');
160
+ if (!p || !text) { console.log(`\n ${slate('Need a path and text. e.g.')} ${cream('phewsh next criteria ' + ref + ' contains README.md "Four words"')}\n`); return; }
161
+ criterion = { expected: `${p} contains "${text}"`, type: 'measurable', check: { kind: 'contains', path: p, text } };
162
+ } else {
163
+ console.log(`\n ${slate('Kinds:')} ${cream('human <expected> | file <path> | contains <path> <text> | changed <path> | accept | clear')}\n`);
164
+ return;
165
+ }
166
+ if (!next.addCriterion(ref, criterion)) return notFound(ref);
167
+ render();
168
+ return;
169
+ }
170
+
125
171
  // Unknown sub → treat the whole thing as a quick add ("phewsh next fix the bug")
126
172
  const item = next.add([sub, rest].filter(Boolean).join(' '));
127
173
  if (item) { render(); return; }
@@ -617,14 +617,13 @@ async function main() {
617
617
  };
618
618
  }
619
619
 
620
- // Self-healing continuity on entry: if .intent/ drifted ahead of CLAUDE.md
621
- // since last time, quietly bring it current so every tool reading CLAUDE.md
622
- // (this session included) sees today — the user never runs `seq -w` by hand.
620
+ // Self-healing continuity on entry: if any existing harness projection
621
+ // drifted, quietly refresh every managed block from the same canonical core.
623
622
  function maybeHealOnEntry() {
624
623
  try {
625
624
  if (!selfheal.isStale()) return;
626
625
  const h = selfheal.heal();
627
- if (h.healed) console.log(` ${teal('↻')} ${sage('Synced your .intent/ into CLAUDE.md — kept current automatically')}`);
626
+ if (h.healed) console.log(` ${teal('↻')} ${sage('Synced .intent/ across your harness files — kept current automatically')}`);
628
627
  } catch { /* self-heal is a nicety, never a blocker */ }
629
628
  }
630
629
 
@@ -1341,12 +1340,11 @@ async function main() {
1341
1340
  if (global._phewshChildren) {
1342
1341
  global._phewshChildren.forEach(c => { try { c.kill(); } catch {} });
1343
1342
  }
1344
- // Self-healing continuity: leave the next tool a current CLAUDE.md so
1345
- // opening Claude Code / Codex here next reads today's intent — without
1346
- // anyone remembering to run `seq -w`.
1343
+ // Leave every existing harness projection current for whichever tool
1344
+ // opens this project next.
1347
1345
  try {
1348
1346
  const h = selfheal.heal();
1349
- if (h.healed) console.log(` ${teal('↻')} ${sage("CLAUDE.md refreshed from .intent/ — you didn't have to")}`);
1347
+ if (h.healed) console.log(` ${teal('↻')} ${sage("Harness context refreshed from .intent/ — you didn't have to")}`);
1350
1348
  } catch { /* self-heal must never block exit */ }
1351
1349
  try { require('../lib/intro').farewell(); } catch { /* sign-off is a nicety */ }
1352
1350
  console.log(` ${sage('session ended · ' + turns + ' exchanges · ' + (totalPromptTokens + totalCompletionTokens) + ' tokens')}`);
@@ -1367,7 +1365,7 @@ async function main() {
1367
1365
  const nx = require('../lib/next');
1368
1366
  const nparts = cmdArg.split(/\s+/);
1369
1367
  const nsub = (nparts[0] || '').toLowerCase();
1370
- const TASK_SUBS = ['add', 'a', 'start', 'now', 'done', 'drop', 'rm', 'list', 'ls', 'clear'];
1368
+ const TASK_SUBS = ['add', 'a', 'start', 'now', 'done', 'drop', 'rm', 'list', 'ls', 'clear', 'criteria', 'criterion'];
1371
1369
  const renderTasks = () => {
1372
1370
  const items = nx.ordered(nx.load());
1373
1371
  console.log('');
@@ -1379,8 +1377,22 @@ async function main() {
1379
1377
  const mark = it.state === 'now' ? green('◐') : it.state === 'done' ? slate('✓') : slate('○');
1380
1378
  const title = it.state === 'done' ? slate(it.title) : cream(it.title);
1381
1379
  console.log(` ${slate(String(i + 1).padStart(2))} ${mark} ${title}`);
1380
+ if (Array.isArray(it.criteria) && it.criteria.length) {
1381
+ const { results } = require('../lib/verify').verifyAll(it.criteria);
1382
+ const symbols = {
1383
+ pass: green('✓'),
1384
+ partial: yellow('~'),
1385
+ fail: ember('✗'),
1386
+ unknown: slate('?'),
1387
+ human: peach('◇'),
1388
+ proposed: slate('○'),
1389
+ };
1390
+ results.forEach(result => {
1391
+ console.log(` ${symbols[result.status] || slate('·')} ${slate(result.expected + ' · ' + result.note)}`);
1392
+ });
1393
+ }
1382
1394
  });
1383
- console.log(` ${slate('/next start # · /next done # · /next drop # · /next add "…"')}`);
1395
+ console.log(` ${slate('/next start # · /next done # · /next criteria # … · /next drop # · /next add "…"')}`);
1384
1396
  }
1385
1397
  console.log('');
1386
1398
  };
@@ -1388,6 +1400,53 @@ async function main() {
1388
1400
  if (nsub === 'add' || nsub === 'a') nx.add(cmdArg.replace(/^\S+\s*/, '').replace(/^["']|["']$/g, ''));
1389
1401
  else if (nsub === 'clear') { const dd = nx.load(); dd.items = dd.items.filter(i => i.state !== 'done'); nx.save(dd); }
1390
1402
  else if (nsub === 'list' || nsub === 'ls') { /* render only */ }
1403
+ else if (nsub === 'criteria' || nsub === 'criterion') {
1404
+ const ref = nparts[1];
1405
+ const kind = (nparts[2] || '').toLowerCase();
1406
+ const args = nparts.slice(3);
1407
+ if (!ref) {
1408
+ console.log(`\n ${slate('Which item? e.g.')} ${cream('/next criteria 1 file out.txt')}\n`);
1409
+ rl.prompt();
1410
+ return;
1411
+ }
1412
+ if (kind === 'accept') nx.acceptCriteria(ref);
1413
+ else if (kind === 'clear') nx.clearCriteria(ref);
1414
+ else if (kind === 'human') {
1415
+ nx.addCriterion(ref, {
1416
+ expected: args.join(' ').replace(/^["']|["']$/g, '') || 'human judgment required',
1417
+ type: 'human',
1418
+ });
1419
+ } else if (kind === 'file' || kind === 'changed') {
1420
+ const p = args[0];
1421
+ if (!p) {
1422
+ console.log(`\n ${slate('Need a path. e.g.')} ${cream('/next criteria ' + ref + ' ' + kind + ' cli/lib/verify.js')}\n`);
1423
+ rl.prompt();
1424
+ return;
1425
+ }
1426
+ nx.addCriterion(ref, {
1427
+ expected: args.slice(1).join(' ').replace(/^["']|["']$/g, '') || `${p} ${kind === 'file' ? 'exists' : 'changed'}`,
1428
+ type: 'measurable',
1429
+ check: { kind, path: p },
1430
+ });
1431
+ } else if (kind === 'contains') {
1432
+ const p = args[0];
1433
+ const text = args.slice(1).join(' ').replace(/^["']|["']$/g, '');
1434
+ if (!p || !text) {
1435
+ console.log(`\n ${slate('Need a path and text. e.g.')} ${cream('/next criteria ' + ref + ' contains README.md "Four words"')}\n`);
1436
+ rl.prompt();
1437
+ return;
1438
+ }
1439
+ nx.addCriterion(ref, {
1440
+ expected: `${p} contains "${text}"`,
1441
+ type: 'measurable',
1442
+ check: { kind: 'contains', path: p, text },
1443
+ });
1444
+ } else if (kind) {
1445
+ console.log(`\n ${slate('Kinds:')} ${cream('human <expected> | file <path> | contains <path> <text> | changed <path> | accept | clear')}\n`);
1446
+ rl.prompt();
1447
+ return;
1448
+ }
1449
+ }
1391
1450
  else { const ref = nparts[1]; if (nsub === 'drop' || nsub === 'rm') nx.remove(ref); else nx.setState(ref, (nsub === 'start' || nsub === 'now') ? 'now' : 'done'); }
1392
1451
  renderTasks();
1393
1452
  rl.prompt();
@@ -1580,7 +1639,7 @@ async function main() {
1580
1639
  console.log(` ${cream('sync everywhere')}`);
1581
1640
  console.log(` ${teal('/seq')} ${sage('Sequence all memory → optimal context')}`);
1582
1641
  console.log(` ${teal('/seq claude')} ${sage('Sequence → write to CLAUDE.md')}`);
1583
- console.log(` ${teal('/watch')} ${sage('Sync .intent/ → CLAUDE.md + cloud (background)')}`);
1642
+ console.log(` ${teal('/watch')} ${sage('Sync .intent/ → native harness files + cloud (background)')}`);
1584
1643
  console.log(` ${teal('/export')} ${sage('Export .intent/ for any AI tool')}`);
1585
1644
  console.log(` ${teal('/push')} ${sage('Push to phewsh.com/intent')}`);
1586
1645
  console.log(` ${teal('/pull')} ${sage('Pull from cloud (reloads context)')}`);
@@ -1710,7 +1769,8 @@ async function main() {
1710
1769
  if (cmd === 'reload') {
1711
1770
  intentFiles = loadIntentContext();
1712
1771
  systemPrompt = buildSystemPrompt(intentFiles);
1713
- console.log(` ${teal('●')} ${sage('Reloaded ' + intentFiles.length + ' artifact' + (intentFiles.length !== 1 ? 's' : ''))}`);
1772
+ const synced = selfheal.syncContextFiles({ createMissing: false }).synced || [];
1773
+ console.log(` ${teal('●')} ${sage('Reloaded ' + intentFiles.length + ' artifact' + (intentFiles.length !== 1 ? 's' : ''))}${synced.length ? slate(' · synced ' + synced.join(', ')) : ''}`);
1714
1774
  rl.prompt();
1715
1775
  return;
1716
1776
  }
package/commands/watch.js CHANGED
@@ -1,13 +1,14 @@
1
1
  // phewsh watch — Live sync daemon
2
2
  //
3
- // Watches .intent/ for changes and auto-pushes to Supabase + regenerates CLAUDE.md.
3
+ // Watches .intent/ for changes and auto-pushes to Supabase + refreshes each
4
+ // harness's managed context block from one canonical generated core.
4
5
  // This is the backbone of cross-tool continuity:
5
- // edit in terminal → phewsh.com/intent updates → Claude Code knows.
6
+ // edit in terminal → native harness files + phewsh.com/intent update.
6
7
  //
7
8
  // Usage:
8
- // phewsh watch Start watching (push + CLAUDE.md)
9
- // phewsh watch --no-claude Skip CLAUDE.md regeneration
10
- // phewsh watch --no-push Skip cloud push (local CLAUDE.md only)
9
+ // phewsh watch Start watching (push + harness projections)
10
+ // phewsh watch --no-claude Skip local projection sync (legacy flag)
11
+ // phewsh watch --no-push Skip cloud push (local projections only)
11
12
  // phewsh watch --verbose Show detailed sync info
12
13
 
13
14
  const fs = require('fs');
@@ -17,7 +18,6 @@ const configFile = require('../lib/config-file');
17
18
 
18
19
  const INTENT_DIR = path.join(process.cwd(), '.intent');
19
20
  const CONFIG_PATH = path.join(os.homedir(), '.phewsh', 'config.json');
20
- const CLAUDE_MD_PATH = path.join(process.cwd(), 'CLAUDE.md');
21
21
 
22
22
  const args = process.argv.slice(3);
23
23
  const flags = {
@@ -42,181 +42,8 @@ function loadConfig() {
42
42
  return configFile.loadConfig(CONFIG_PATH);
43
43
  }
44
44
 
45
- function saveConfig(config) {
46
- configFile.saveConfig(CONFIG_PATH, config);
47
- }
48
-
49
- // ── Context Generation (mirrors context.js) ────────────────────────────────
50
-
51
- function loadArtifact(name) {
52
- const p = path.join(INTENT_DIR, name);
53
- if (!fs.existsSync(p)) return null;
54
- return fs.readFileSync(p, 'utf-8');
55
- }
56
-
57
- function loadProjectJson() {
58
- const p = path.join(INTENT_DIR, 'project.json');
59
- if (!fs.existsSync(p)) return null;
60
- try { return JSON.parse(fs.readFileSync(p, 'utf-8')); } catch { return null; }
61
- }
62
-
63
- function loadGate() {
64
- const p = path.join(INTENT_DIR, 'gate.json');
65
- if (!fs.existsSync(p)) return null;
66
- try { return JSON.parse(fs.readFileSync(p, 'utf-8')); } catch { return null; }
67
- }
68
-
69
- // ── CLAUDE.md Generation ────────────────────────────────────────────────────
70
-
71
- function generateClaudeSection() {
72
- const vision = loadArtifact('vision.md');
73
- const plan = loadArtifact('plan.md');
74
- const next = loadArtifact('next.md');
75
- const gate = loadGate();
76
- const projectJson = loadProjectJson();
77
- const projectName = projectJson?.name || path.basename(process.cwd());
78
-
79
- if (!vision && !plan && !next) return null;
80
-
81
- const sections = [];
82
- const constraints = gate?.constraints || projectJson?.decisionGate?.constraints;
83
- const decisionGate = gate || projectJson?.decisionGate;
84
-
85
- sections.push(`# PHEWSH Adaptive Context — ${projectName}`);
86
- sections.push(`> **Generated file. Do not edit.** Modify .intent/ instead — this section regenerates automatically.`);
87
- sections.push(`> Auto-synced by \`phewsh watch\` | ${new Date().toISOString().split('T')[0]}`);
88
- sections.push('');
89
-
90
- // Project identity
91
- sections.push('## Project');
92
- sections.push(`- **Name**: ${projectName}`);
93
- if (projectJson?.archetype) sections.push(`- **Type**: ${projectJson.archetype}`);
94
- if (projectJson?.tldr) sections.push(`- **TLDR**: ${projectJson.tldr}`);
95
- if (vision) {
96
- const firstMeaningful = vision.split('\n').find(l => l.trim().length > 20 && !l.startsWith('#'));
97
- if (firstMeaningful && !projectJson?.tldr) sections.push(`- **TLDR**: ${firstMeaningful.trim()}`);
98
- }
99
- sections.push('');
100
-
101
- // Operational constraints
102
- if (constraints && (constraints.budget > 0 || constraints.timeHoursPerWeek > 0 ||
103
- constraints.skillLevel !== 'intermediate' || constraints.urgency !== 'moderate' ||
104
- constraints.autonomy !== 'guided')) {
105
- sections.push('## Operational Reality');
106
- sections.push('These constraints MUST shape every suggestion and implementation decision:');
107
- sections.push('');
108
- if (constraints.budget > 0) {
109
- sections.push(`- Budget: $${constraints.budget}. ${
110
- constraints.budget < 100 ? 'Extremely tight — free/open-source only.'
111
- : constraints.budget < 500 ? 'Limited — justify any paid tool.'
112
- : constraints.budget < 2000 ? 'Moderate — strategic spending OK.'
113
- : 'Substantial — professional tools welcome.'
114
- }`);
115
- }
116
- if (constraints.timeHoursPerWeek > 0) {
117
- sections.push(`- Time: ${constraints.timeHoursPerWeek} hrs/week. ${
118
- constraints.timeHoursPerWeek <= 5 ? 'Micro-steps only.'
119
- : constraints.timeHoursPerWeek <= 15 ? 'Part-time. Clear stopping points.'
120
- : constraints.timeHoursPerWeek <= 30 ? 'Near full-time.'
121
- : 'Full-time+.'
122
- }`);
123
- }
124
- sections.push(`- Skill: ${constraints.skillLevel}`);
125
- sections.push(`- Urgency: ${constraints.urgency}`);
126
- sections.push(`- Autonomy: ${constraints.autonomy}`);
127
- sections.push('');
128
- }
129
-
130
- // Success criteria
131
- if (decisionGate?.successCriteria?.length > 0) {
132
- sections.push('## Success Criteria');
133
- decisionGate.successCriteria.forEach(c => sections.push(`- ${c}`));
134
- sections.push('');
135
- }
136
-
137
- // Execution state from next.md
138
- if (next) {
139
- const items = [];
140
- for (const line of next.split('\n')) {
141
- const match = line.match(/^[-*]\s*\[([ x!\-~])\]\s*\*?\*?(.+?)\*?\*?\s*$/);
142
- if (match) {
143
- const stateMap = { ' ': 'todo', '-': 'in_progress', 'x': 'done', '!': 'blocked', '~': 'skipped' };
144
- const char = match[1] === '-' ? '-' : match[1];
145
- items.push({ title: match[2].replace(/\*\*/g, '').trim(), state: stateMap[char] || 'todo' });
146
- }
147
- }
148
- if (items.length > 0) {
149
- const done = items.filter(i => i.state === 'done').length;
150
- const doing = items.filter(i => i.state === 'in_progress');
151
- const blocked = items.filter(i => i.state === 'blocked');
152
- const todo = items.filter(i => i.state === 'todo');
153
-
154
- sections.push('## Execution State');
155
- sections.push(`Progress: ${done}/${items.length} done${doing.length ? `, ${doing.length} in progress` : ''}${blocked.length ? `, ${blocked.length} blocked` : ''}`);
156
- sections.push('');
157
- if (doing.length > 0) {
158
- sections.push('**Currently working on:**');
159
- doing.forEach(i => sections.push(`- ${i.title}`));
160
- sections.push('');
161
- }
162
- if (blocked.length > 0) {
163
- sections.push('**Blocked:**');
164
- blocked.forEach(i => sections.push(`- ${i.title}`));
165
- sections.push('');
166
- }
167
- if (todo.length > 0) {
168
- sections.push('**Remaining:**');
169
- todo.forEach(i => sections.push(`- ${i.title}`));
170
- sections.push('');
171
- }
172
- }
173
- }
174
-
175
- // Responsibility split
176
- if (decisionGate?.responsibilitySplit) {
177
- sections.push('## Responsibility Split');
178
- if (decisionGate.responsibilitySplit.ai?.length > 0) {
179
- sections.push('**AI can handle:**');
180
- decisionGate.responsibilitySplit.ai.forEach(r => sections.push(`- ${r}`));
181
- }
182
- if (decisionGate.responsibilitySplit.human?.length > 0) {
183
- sections.push('**Requires human:**');
184
- decisionGate.responsibilitySplit.human.forEach(r => sections.push(`- ${r}`));
185
- }
186
- sections.push('');
187
- }
188
-
189
- sections.push('---');
190
- sections.push('*Auto-synced from .intent/ by [PHEWSH](https://phewsh.com/intent)*');
191
-
192
- return sections.join('\n');
193
- }
194
-
195
- function updateClaudeMd() {
196
- const section = generateClaudeSection();
197
- if (!section) return false;
198
-
199
- const START_MARKER = '<!-- PHEWSH:START -->';
200
- const END_MARKER = '<!-- PHEWSH:END -->';
201
- const wrapped = `${START_MARKER}\n${section}\n${END_MARKER}`;
202
-
203
- if (fs.existsSync(CLAUDE_MD_PATH)) {
204
- let existing = fs.readFileSync(CLAUDE_MD_PATH, 'utf-8');
205
- const startIdx = existing.indexOf(START_MARKER);
206
- const endIdx = existing.indexOf(END_MARKER);
207
-
208
- if (startIdx !== -1 && endIdx !== -1) {
209
- // Replace existing section
210
- existing = existing.slice(0, startIdx) + wrapped + existing.slice(endIdx + END_MARKER.length);
211
- } else {
212
- // Append
213
- existing = existing.trimEnd() + '\n\n' + wrapped + '\n';
214
- }
215
- fs.writeFileSync(CLAUDE_MD_PATH, existing);
216
- } else {
217
- fs.writeFileSync(CLAUDE_MD_PATH, wrapped + '\n');
218
- }
219
- return true;
45
+ function updateHarnessFiles() {
46
+ return require('../lib/selfheal').syncContextFiles().synced || [];
220
47
  }
221
48
 
222
49
  // ── Cloud Push ──────────────────────────────────────────────────────────────
@@ -261,10 +88,10 @@ function watchIntent() {
261
88
 
262
89
  let actions = [];
263
90
 
264
- // Update CLAUDE.md
91
+ // Update every native projection from the same canonical core.
265
92
  if (!flags.noClaude) {
266
- const updated = updateClaudeMd();
267
- if (updated) actions.push('CLAUDE.md');
93
+ const synced = updateHarnessFiles();
94
+ if (synced.length) actions.push(synced.join(', '));
268
95
  }
269
96
 
270
97
  // Push to cloud
@@ -320,17 +147,17 @@ function showHelp() {
320
147
 
321
148
  Usage:
322
149
  phewsh watch Start watching
323
- phewsh watch --no-claude Skip CLAUDE.md regeneration
150
+ phewsh watch --no-claude Skip local harness-file regeneration
324
151
  phewsh watch --no-push Skip cloud push (local only)
325
152
  phewsh watch --verbose Show detailed sync info
326
153
 
327
154
  What it does:
328
155
  Watches .intent/ for changes and automatically:
329
- 1. Regenerates CLAUDE.md so Claude Code stays current with .intent/
156
+ 1. Regenerates managed blocks in CLAUDE.md, AGENTS.md, GEMINI.md, and .cursorrules
330
157
  2. Pushes to phewsh.com/intent (optional — needs account)
331
158
 
332
159
  The loop:
333
- edit .intent/ → CLAUDE.md updates → cloud mirror updates → every tool knows
160
+ edit .intent/ → native harness files update → cloud mirror updates
334
161
 
335
162
  Requirements:
336
163
  - .intent/ directory must exist (run \`phewsh intent --init\`)
@@ -356,8 +183,8 @@ async function main() {
356
183
 
357
184
  // Show what's enabled
358
185
  const features = [];
359
- if (!flags.noClaude) features.push(`${green('●')} CLAUDE.md auto-sync`);
360
- else features.push(`${g('○')} CLAUDE.md ${g('(disabled)')}`);
186
+ if (!flags.noClaude) features.push(`${green('●')} Harness projections auto-sync`);
187
+ else features.push(`${g('○')} Harness projections ${g('(disabled)')}`);
361
188
 
362
189
  if (!flags.noPush && loggedIn) features.push(`${green('●')} Cloud push → phewsh.com/intent`);
363
190
  else if (!flags.noPush && !loggedIn) features.push(`${yellow('●')} Cloud push ${g('(not logged in — run phewsh login)')}`);
@@ -370,8 +197,8 @@ async function main() {
370
197
  console.log(` ${cyan('~')} Running initial sync...`);
371
198
 
372
199
  if (!flags.noClaude) {
373
- const updated = updateClaudeMd();
374
- if (updated) console.log(` ${green('✓')} CLAUDE.md updated`);
200
+ const synced = updateHarnessFiles();
201
+ if (synced.length) console.log(` ${green('✓')} Updated ${synced.join(', ')}`);
375
202
  }
376
203
 
377
204
  if (!flags.noPush && loggedIn) {
package/commands/work.js CHANGED
@@ -14,6 +14,8 @@ const slate = (s) => `\x1b[38;5;247m${s}\x1b[0m`;
14
14
  const cream = (s) => `\x1b[38;5;230m${s}\x1b[0m`;
15
15
  const green = (s) => `\x1b[32m${s}\x1b[0m`;
16
16
  const peach = (s) => `\x1b[38;5;216m${s}\x1b[0m`;
17
+ const yellow = (s) => `\x1b[33m${s}\x1b[0m`;
18
+ const red = (s) => `\x1b[31m${s}\x1b[0m`;
17
19
 
18
20
  function trunc(s, n = 88) {
19
21
  s = String(s).replace(/\s+/g, ' ').trim();
@@ -61,12 +63,24 @@ function main() {
61
63
  console.log(` ${slate('nothing recorded yet — ')}${cream('phewsh remember "…"')}`);
62
64
  }
63
65
 
66
+ // Verify — the started item's success criteria, checked against evidence
67
+ if (v.verification) {
68
+ const VSYM = { pass: green('✓'), partial: yellow('~'), fail: red('✗'), unknown: slate('?'), human: peach('◇'), proposed: slate('○') };
69
+ const s = v.verification.summary;
70
+ const bits = ['pass', 'partial', 'fail', 'unknown', 'human', 'proposed']
71
+ .filter(k => s[k]).map(k => `${s[k]} ${k}`);
72
+ console.log(` ${cream('Verify')} ${slate('— ' + bits.join(' · '))}`);
73
+ v.verification.results.forEach(r => console.log(` ${VSYM[r.status] || slate('·')} ${slate(trunc(r.expected, 64) + ' · ' + r.note)}`));
74
+ }
75
+
64
76
  // Review — does anything need a human?
65
77
  console.log(` ${cream('Review')}`);
66
- if (v.review.clear) {
78
+ if (v.review.clear && !(v.verification && (v.verification.summary.fail || v.verification.summary.human || v.verification.summary.proposed))) {
67
79
  console.log(` ${green('✓')} ${sage('Nothing needs human review right now')}`);
68
80
  } else {
69
81
  v.review.needs.forEach(n => console.log(` ${peach('⚠')} ${sage(n)}`));
82
+ if (v.verification && v.verification.summary.fail) console.log(` ${red('✗')} ${sage(v.verification.summary.fail + ' criterion(s) failed — see Verify above')}`);
83
+ if (v.verification && (v.verification.summary.human || v.verification.summary.proposed)) console.log(` ${peach('◇')} ${sage('criteria need your judgment / acceptance')}`);
70
84
  }
71
85
 
72
86
  console.log('');