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 +29 -15
- package/bin/phewsh.js +1 -1
- package/commands/ambient.js +5 -1
- package/commands/next.js +47 -1
- package/commands/session.js +72 -12
- package/commands/watch.js +18 -191
- package/commands/work.js +15 -1
- package/lib/brief.js +38 -2
- package/lib/lifecycle.js +25 -1
- package/lib/next.js +62 -1
- package/lib/selfheal.js +89 -66
- package/lib/sequencer/discover.js +1 -1
- package/lib/sequencer/emitters/claude-md.js +9 -6
- package/lib/sequencer/parsers/intent.js +33 -0
- package/lib/suggest.js +5 -5
- package/lib/truth.js +29 -14
- package/lib/ui.js +1 -1
- package/lib/verify.js +131 -0
- package/lib/work.js +8 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,8 +11,11 @@ npm install -g phewsh
|
|
|
11
11
|
## Quick Start
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
phewsh
|
|
15
|
-
phewsh
|
|
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
|
|
53
|
-
|
|
54
|
-
`.
|
|
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
|
-
|
|
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
|
-
##
|
|
101
|
+
## The four answers
|
|
99
102
|
|
|
100
|
-
|
|
103
|
+
Phewsh keeps the same four answers available to you and every harness:
|
|
101
104
|
|
|
102
|
-
- **
|
|
103
|
-
- **
|
|
104
|
-
- **
|
|
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
|
-
|
|
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
|
-
|
|
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/ →
|
|
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')}`);
|
package/commands/ambient.js
CHANGED
|
@@ -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; }
|
package/commands/session.js
CHANGED
|
@@ -617,14 +617,13 @@ async function main() {
|
|
|
617
617
|
};
|
|
618
618
|
}
|
|
619
619
|
|
|
620
|
-
// Self-healing continuity on entry: if
|
|
621
|
-
//
|
|
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
|
|
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
|
-
//
|
|
1345
|
-
//
|
|
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("
|
|
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/ →
|
|
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
|
-
|
|
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 +
|
|
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
|
|
6
|
+
// edit in terminal → native harness files + phewsh.com/intent update.
|
|
6
7
|
//
|
|
7
8
|
// Usage:
|
|
8
|
-
// phewsh watch Start watching (push +
|
|
9
|
-
// phewsh watch --no-claude Skip
|
|
10
|
-
// phewsh watch --no-push Skip cloud push (local
|
|
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
|
|
46
|
-
|
|
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
|
|
91
|
+
// Update every native projection from the same canonical core.
|
|
265
92
|
if (!flags.noClaude) {
|
|
266
|
-
const
|
|
267
|
-
if (
|
|
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
|
|
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
|
|
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/ →
|
|
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('●')}
|
|
360
|
-
else features.push(`${g('○')}
|
|
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
|
|
374
|
-
if (
|
|
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('');
|