phewsh 0.15.79 → 0.15.81
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 +3 -0
- package/commands/ion.js +4 -3
- package/commands/project.js +121 -0
- package/commands/serve.js +19 -0
- package/lib/packs.js +8 -0
- package/lib/projects-index.js +72 -2
- package/package.json +4 -3
- package/skills/boat-to-shore/SKILL.md +62 -0
package/bin/phewsh.js
CHANGED
|
@@ -103,6 +103,8 @@ const COMMANDS = {
|
|
|
103
103
|
setup: () => require('../commands/setup')(),
|
|
104
104
|
update: () => require('../commands/update')(),
|
|
105
105
|
serve: () => require('../commands/serve')(),
|
|
106
|
+
project: () => require('../commands/project')(),
|
|
107
|
+
projects: () => require('../commands/project')(),
|
|
106
108
|
sequence: () => require('../commands/sequence')(),
|
|
107
109
|
seq: () => require('../commands/sequence')(),
|
|
108
110
|
ambient: () => require('../commands/ambient')(),
|
|
@@ -161,6 +163,7 @@ function showHelp() {
|
|
|
161
163
|
console.log(` ${cyan('watch')} ${g('Auto-sync .intent/ → native harness files + cloud')}`);
|
|
162
164
|
console.log(` ${cyan('push/pull')} ${g('Manual sync to/from phewsh.com/intent')}`);
|
|
163
165
|
console.log(` ${cyan('serve')} ${g('Execution bridge — run from phewsh.com/ion or /intent')}`);
|
|
166
|
+
console.log(` ${cyan('project')} ${g('Choose which projects this machine\'s worker shows on /ion')}`);
|
|
164
167
|
console.log(` ${cyan('ion')} ${g('Shared visual room for humans + local agents')}`);
|
|
165
168
|
console.log(` ${cyan('mcp')} ${g('Connect AI agents via MCP protocol')}`);
|
|
166
169
|
console.log(` ${cyan('ambient')} ${g('Continuity without launching phewsh — enhance your other tools')}`);
|
package/commands/ion.js
CHANGED
|
@@ -35,8 +35,9 @@ function showHelp() {
|
|
|
35
35
|
${cyan('phewsh ion')} ${sage('open phewsh.com/ion')}
|
|
36
36
|
${cyan('phewsh ion open')} ${sage('same')}
|
|
37
37
|
|
|
38
|
-
${sage('Make this machine available')}
|
|
39
|
-
${cyan('phewsh
|
|
38
|
+
${sage('Make this machine available — the two-step')}
|
|
39
|
+
${cyan('phewsh project add')} ${sage('1. register this repo (once per repo)')}
|
|
40
|
+
${cyan('phewsh serve')} ${sage('2. one worker serves the whole machine')}
|
|
40
41
|
${cyan('phewsh ion status')} ${sage('show bridge/project/task status')}
|
|
41
42
|
|
|
42
43
|
${sage('Use the existing task loop')}
|
|
@@ -92,7 +93,7 @@ module.exports = async function run() {
|
|
|
92
93
|
if (sub === 'serve' || sub === 'worker') return runServe(rest);
|
|
93
94
|
if (sub === 'status') {
|
|
94
95
|
console.log(`\n ${cream('Ion room:')} ${cyan(WEB_URL)}`);
|
|
95
|
-
console.log(` ${sage('Local worker:')} ${cream('phewsh
|
|
96
|
+
console.log(` ${sage('Local worker:')} ${cream('phewsh project add')} ${sage('(once per repo), then')} ${cream('phewsh serve')}`);
|
|
96
97
|
return runTask(['list']);
|
|
97
98
|
}
|
|
98
99
|
if (sub === 'task' || sub === 'tasks') return runTask(rest.length ? rest : ['list']);
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// phewsh project — the serve registry: which projects may this machine's
|
|
2
|
+
// worker expose to phewsh.com/ion and /cockpit?
|
|
3
|
+
//
|
|
4
|
+
// Jul 8 2026 Option C ruling: one worker per machine, explicit registry,
|
|
5
|
+
// identity = normalized git remote (never the folder name). Adding a project
|
|
6
|
+
// here does NOT execute anything and does NOT let anyone run work remotely —
|
|
7
|
+
// it only makes the project visible as "online" when `phewsh serve` runs.
|
|
8
|
+
// Manual claim remains the execution boundary.
|
|
9
|
+
//
|
|
10
|
+
// Usage:
|
|
11
|
+
// phewsh project Show the registry + what to do next
|
|
12
|
+
// phewsh project add [path] Register a project (default: this directory)
|
|
13
|
+
// phewsh project list Same as bare `phewsh project`
|
|
14
|
+
// phewsh project remove <name|path> Stop exposing a project
|
|
15
|
+
|
|
16
|
+
const path = require('path');
|
|
17
|
+
const { addServeProject, removeServeProject, serveProjects } = require('../lib/projects-index');
|
|
18
|
+
|
|
19
|
+
const b = (s) => `\x1b[1m${s}\x1b[0m`;
|
|
20
|
+
const g = (s) => `\x1b[38;5;247m${s}\x1b[0m`;
|
|
21
|
+
const w = (s) => `\x1b[97m${s}\x1b[0m`;
|
|
22
|
+
const green = (s) => `\x1b[32m${s}\x1b[0m`;
|
|
23
|
+
const yellow = (s) => `\x1b[33m${s}\x1b[0m`;
|
|
24
|
+
const cyan = (s) => `\x1b[36m${s}\x1b[0m`;
|
|
25
|
+
|
|
26
|
+
function showList() {
|
|
27
|
+
const projects = serveProjects();
|
|
28
|
+
console.log('');
|
|
29
|
+
console.log(` ${b(w('Served projects'))} ${g('— what this machine\'s worker shows on phewsh.com/ion')}`);
|
|
30
|
+
console.log('');
|
|
31
|
+
if (projects.length === 0) {
|
|
32
|
+
console.log(` ${g('None yet. Registering a project is one command, run inside its repo:')}`);
|
|
33
|
+
console.log('');
|
|
34
|
+
console.log(` ${cyan('cd <your-project> && phewsh project add')}`);
|
|
35
|
+
console.log('');
|
|
36
|
+
console.log(` ${g('Then start the worker once for the whole machine:')} ${cyan('phewsh serve')}`);
|
|
37
|
+
} else {
|
|
38
|
+
for (const p of projects) {
|
|
39
|
+
console.log(` ${green('●')} ${w(p.name)}`);
|
|
40
|
+
console.log(` ${g('repo:')} ${g(p.remote || 'no remote')}`);
|
|
41
|
+
console.log(` ${g('path:')} ${g(p.path)}`);
|
|
42
|
+
}
|
|
43
|
+
console.log('');
|
|
44
|
+
console.log(` ${g('These appear as this machine\'s projects when')} ${cyan('phewsh serve')} ${g('is running.')}`);
|
|
45
|
+
console.log(` ${g('Add another:')} ${cyan('phewsh project add [path]')} ${g('Stop exposing one:')} ${cyan('phewsh project remove <name>')}`);
|
|
46
|
+
}
|
|
47
|
+
console.log('');
|
|
48
|
+
console.log(` ${g('Registering never executes anything — work still starts only when a')}`);
|
|
49
|
+
console.log(` ${g('human claims it. See phewsh.com/ion for the team room.')}`);
|
|
50
|
+
console.log('');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function main() {
|
|
54
|
+
const args = process.argv.slice(3);
|
|
55
|
+
const sub = args[0];
|
|
56
|
+
|
|
57
|
+
if (sub === 'add') {
|
|
58
|
+
const dir = args[1] ? path.resolve(args[1]) : process.cwd();
|
|
59
|
+
try {
|
|
60
|
+
const p = addServeProject(dir);
|
|
61
|
+
console.log('');
|
|
62
|
+
console.log(` ${green('✓')} ${w(p.name)} ${g('registered for this machine\'s worker.')}`);
|
|
63
|
+
console.log(` ${g('identity:')} ${g(p.remote)}`);
|
|
64
|
+
console.log('');
|
|
65
|
+
console.log(` ${g('Next:')} ${cyan('phewsh serve')} ${g('— one worker serves every registered project;')}`);
|
|
66
|
+
console.log(` ${g('phewsh.com/ion will show it as online on this machine.')}`);
|
|
67
|
+
console.log('');
|
|
68
|
+
} catch (err) {
|
|
69
|
+
console.log('');
|
|
70
|
+
console.log(` ${yellow('●')} ${err.message.split('\n').join(`\n ${g('')}`)}`);
|
|
71
|
+
console.log('');
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (sub === 'remove' || sub === 'rm') {
|
|
78
|
+
if (!args[1]) {
|
|
79
|
+
console.log('');
|
|
80
|
+
console.log(` ${g('Which one? ')} ${cyan('phewsh project remove <name|path>')}`);
|
|
81
|
+
console.log('');
|
|
82
|
+
showList();
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
const hit = removeServeProject(args[1]);
|
|
86
|
+
console.log('');
|
|
87
|
+
if (hit) {
|
|
88
|
+
console.log(` ${green('✓')} ${w(hit.name)} ${g('is no longer exposed by this machine\'s worker.')}`);
|
|
89
|
+
console.log(` ${g('(The project itself is untouched — this only changes what the worker shows.)')}`);
|
|
90
|
+
} else {
|
|
91
|
+
console.log(` ${yellow('●')} ${g('No served project matches')} ${w(args[1])}${g('. Here\'s what\'s registered:')}`);
|
|
92
|
+
showList();
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
console.log('');
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (sub && sub !== 'list' && sub !== 'ls' && sub !== 'help' && sub !== '--help' && sub !== '-h') {
|
|
100
|
+
console.log('');
|
|
101
|
+
console.log(` ${yellow('●')} ${g('Unknown subcommand:')} ${w(sub)}`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (sub === 'help' || sub === '--help' || sub === '-h') {
|
|
105
|
+
console.log('');
|
|
106
|
+
console.log(` ${b(w('phewsh project'))} ${g('— choose which projects this machine\'s worker exposes')}`);
|
|
107
|
+
console.log('');
|
|
108
|
+
console.log(` ${cyan('phewsh project')} ${g('show the registry + guidance')}`);
|
|
109
|
+
console.log(` ${cyan('phewsh project add [path]')} ${g('register a project (default: here)')}`);
|
|
110
|
+
console.log(` ${cyan('phewsh project remove <name|path>')} ${g('stop exposing a project')}`);
|
|
111
|
+
console.log('');
|
|
112
|
+
console.log(` ${g('Safety: registering only affects visibility. Execution always requires')}`);
|
|
113
|
+
console.log(` ${g('a human claim — nothing runs remotely because it was registered.')}`);
|
|
114
|
+
console.log('');
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
showList();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
module.exports = main;
|
package/commands/serve.js
CHANGED
|
@@ -71,6 +71,7 @@ function detectRuntimes() {
|
|
|
71
71
|
// ─── Job Queue ─────────────────────────────────────────────────────────────
|
|
72
72
|
|
|
73
73
|
const { gatherReceipts, recordSessionEvent, recordResultFile } = require('../lib/receipts-data');
|
|
74
|
+
const { serveProjects } = require('../lib/projects-index');
|
|
74
75
|
|
|
75
76
|
const jobs = new Map();
|
|
76
77
|
|
|
@@ -259,10 +260,13 @@ function main() {
|
|
|
259
260
|
|
|
260
261
|
// Health check — includes which project this worker is serving, so the
|
|
261
262
|
// web can say "worker online — <project>" instead of an anonymous dot.
|
|
263
|
+
// `projects` = the explicit serve registry (`phewsh project add`); only
|
|
264
|
+
// deliberately registered projects are exposed, never the session index.
|
|
262
265
|
if (url.pathname === '/health' && req.method === 'GET') {
|
|
263
266
|
return json(req, res, {
|
|
264
267
|
status: 'ok',
|
|
265
268
|
project: currentProject(),
|
|
269
|
+
projects: serveProjects().map(p => ({ name: p.name, remote: p.remote })),
|
|
266
270
|
runtimes: detectRuntimes(),
|
|
267
271
|
version: require('../package.json').version,
|
|
268
272
|
uptime: process.uptime(),
|
|
@@ -328,6 +332,7 @@ function main() {
|
|
|
328
332
|
pending: pendingDecisions().length,
|
|
329
333
|
bypasses: bypassStats(),
|
|
330
334
|
recentProjects: listProjects().slice(0, 5).map(p => ({ name: p.name, path: p.path, lastOpened: p.lastOpened })),
|
|
335
|
+
servedProjects: serveProjects().map(p => ({ name: p.name, remote: p.remote })),
|
|
331
336
|
version: require('../package.json').version,
|
|
332
337
|
});
|
|
333
338
|
} catch (err) {
|
|
@@ -408,6 +413,20 @@ function main() {
|
|
|
408
413
|
console.log(` ${g('https://docs.anthropic.com/en/docs/claude-code')}`);
|
|
409
414
|
}
|
|
410
415
|
console.log('');
|
|
416
|
+
const registered = serveProjects();
|
|
417
|
+
const here = currentProject();
|
|
418
|
+
console.log(` ${b('Projects this worker shows on phewsh.com/ion:')}`);
|
|
419
|
+
if (registered.length === 0) {
|
|
420
|
+
console.log(` ${g('only the current directory:')} ${w(here.name)}${here.remote ? g(` (${here.remote})`) : ''}`);
|
|
421
|
+
console.log(` ${g('Register projects by name so the web knows them:')} ${cyan('phewsh project add')} ${g('(run inside each repo)')}`);
|
|
422
|
+
} else {
|
|
423
|
+
for (const p of registered) {
|
|
424
|
+
const isHere = p.path === process.cwd();
|
|
425
|
+
console.log(` ${green('●')} ${w(p.name)}${g(` (${p.remote})`)}${isHere ? g(' ← current directory') : ''}`);
|
|
426
|
+
}
|
|
427
|
+
console.log(` ${g('Manage the list:')} ${cyan('phewsh project')}`);
|
|
428
|
+
}
|
|
429
|
+
console.log('');
|
|
411
430
|
console.log(` ${g('Open phewsh.com/ion to see the worker online, or phewsh.com/intent → Work')}`);
|
|
412
431
|
console.log(` ${g('Press Ctrl+C to stop')}`);
|
|
413
432
|
console.log('');
|
package/lib/packs.js
CHANGED
|
@@ -44,6 +44,14 @@ const PACKS = {
|
|
|
44
44
|
targets: ['CLAUDE.md', 'AGENTS.md'],
|
|
45
45
|
content: KARPATHY_GUIDELINES,
|
|
46
46
|
},
|
|
47
|
+
'boat-to-shore': {
|
|
48
|
+
kind: 'linked',
|
|
49
|
+
title: 'Boat to Shore — completion-loop skill',
|
|
50
|
+
desc: 'A tested Claude skill for recurring autonomous improvement sessions (run it via /loop): one verified slice per session, state-file continuity across resets, branch discipline, honest handoffs. Moves a repo from unfinished (Water) to shippable (Shore) to excellent (Mountain). Pairs with a plan made at phewsh.com/intent.',
|
|
51
|
+
source: 'github.com/cleverIdeaz/phewsh-cli (skills/boat-to-shore)',
|
|
52
|
+
license: 'MIT',
|
|
53
|
+
install: 'mkdir -p ~/.claude/skills/boat-to-shore && curl -fsSL https://raw.githubusercontent.com/cleverIdeaz/phewsh-cli/main/skills/boat-to-shore/SKILL.md -o ~/.claude/skills/boat-to-shore/SKILL.md',
|
|
54
|
+
},
|
|
47
55
|
gsd: {
|
|
48
56
|
kind: 'linked',
|
|
49
57
|
title: 'GSD — Get Shit Done',
|
package/lib/projects-index.js
CHANGED
|
@@ -13,7 +13,8 @@ const fs = require('fs');
|
|
|
13
13
|
const path = require('path');
|
|
14
14
|
const os = require('os');
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
// PHEWSH_PROJECT_INDEX override exists for tests only — never point real use away from home.
|
|
17
|
+
const INDEX_FILE = process.env.PHEWSH_PROJECT_INDEX || path.join(os.homedir(), '.phewsh', 'project-index.json');
|
|
17
18
|
|
|
18
19
|
// Shallow-scanned roots when the user asks to find projects. One level deep,
|
|
19
20
|
// opt-in only — deep-scanning someone's machine uninvited is invasive.
|
|
@@ -114,6 +115,75 @@ function scanForCandidates(roots = SCAN_ROOTS) {
|
|
|
114
115
|
return found;
|
|
115
116
|
}
|
|
116
117
|
|
|
118
|
+
// ─── Serve registry (Jul 8 2026 Option C ruling) ────────────────────────────
|
|
119
|
+
// Auto-recorded session entries are NOT auto-exposed to the worker. Only
|
|
120
|
+
// projects the human deliberately registers with `phewsh project add` carry
|
|
121
|
+
// serve:true, and only those appear on the bridge/web. Identity is the
|
|
122
|
+
// normalized git remote — the same convention the task-claim path enforces —
|
|
123
|
+
// never the folder name.
|
|
124
|
+
|
|
125
|
+
const { execFileSync } = require('child_process');
|
|
126
|
+
const { normalizeRemote } = require('./team-tasks');
|
|
127
|
+
|
|
128
|
+
function originRemote(dir) {
|
|
129
|
+
try {
|
|
130
|
+
return execFileSync('git', ['remote', 'get-url', 'origin'], {
|
|
131
|
+
cwd: dir, stdio: ['ignore', 'pipe', 'ignore'],
|
|
132
|
+
}).toString().trim() || null;
|
|
133
|
+
} catch { return null; }
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Register a project so the local worker may expose it. Validates before
|
|
138
|
+
* writing; throws with a plain-language message when something is missing.
|
|
139
|
+
*/
|
|
140
|
+
function addServeProject(dir) {
|
|
141
|
+
if (!fs.existsSync(path.resolve(dir))) throw new Error(`That folder doesn't exist: ${path.resolve(dir)}`);
|
|
142
|
+
const key = fs.realpathSync(path.resolve(dir)); // one entry per real directory, symlinks collapse
|
|
143
|
+
|
|
144
|
+
if (!fs.existsSync(path.join(key, '.git'))) {
|
|
145
|
+
throw new Error(`Not a git repository: ${key}\nA served project needs a repo so work stays traceable. Run this inside a git repo (or git init first).`);
|
|
146
|
+
}
|
|
147
|
+
const remote = originRemote(key);
|
|
148
|
+
if (!remote) {
|
|
149
|
+
throw new Error(`This repo has no 'origin' remote yet.\nThe remote is the project's identity — it's how phewsh guarantees work lands in the right repo.\nAdd one first: git remote add origin <url>`);
|
|
150
|
+
}
|
|
151
|
+
const index = load();
|
|
152
|
+
index.projects[key] = {
|
|
153
|
+
...(index.projects[key] || {}),
|
|
154
|
+
name: path.basename(key),
|
|
155
|
+
path: key,
|
|
156
|
+
remote: normalizeRemote(remote),
|
|
157
|
+
serve: true,
|
|
158
|
+
serveAddedAt: new Date().toISOString(),
|
|
159
|
+
lastOpened: index.projects[key]?.lastOpened || new Date().toISOString(),
|
|
160
|
+
};
|
|
161
|
+
save(index);
|
|
162
|
+
return index.projects[key];
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** Stop exposing a project (by name or path). The session-index entry stays. */
|
|
166
|
+
function removeServeProject(nameOrPath) {
|
|
167
|
+
const index = load();
|
|
168
|
+
let wantPath = path.resolve(nameOrPath);
|
|
169
|
+
try { wantPath = fs.realpathSync(wantPath); } catch { /* not a live path — name match still works */ }
|
|
170
|
+
const hit = Object.values(index.projects).find(p =>
|
|
171
|
+
p.serve && (p.path === wantPath || p.name === nameOrPath));
|
|
172
|
+
if (!hit) return null;
|
|
173
|
+
delete index.projects[hit.path].serve;
|
|
174
|
+
delete index.projects[hit.path].serveAddedAt;
|
|
175
|
+
save(index);
|
|
176
|
+
return hit;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** Projects the worker is allowed to expose, pruned to paths that still exist. */
|
|
180
|
+
function serveProjects() {
|
|
181
|
+
const index = load();
|
|
182
|
+
return Object.values(index.projects)
|
|
183
|
+
.filter(p => p.serve === true && fs.existsSync(p.path))
|
|
184
|
+
.sort((a, b) => String(a.name).localeCompare(String(b.name)));
|
|
185
|
+
}
|
|
186
|
+
|
|
117
187
|
function fmtAgo(ts) {
|
|
118
188
|
if (!ts) return '';
|
|
119
189
|
const mins = Math.floor((Date.now() - new Date(ts).getTime()) / 60000);
|
|
@@ -123,4 +193,4 @@ function fmtAgo(ts) {
|
|
|
123
193
|
return `${Math.floor(hrs / 24)}d ago`;
|
|
124
194
|
}
|
|
125
195
|
|
|
126
|
-
module.exports = { INDEX_FILE, SCAN_ROOTS, recordProject, listProjects, scanForProjects, scanForCandidates, fmtAgo };
|
|
196
|
+
module.exports = { INDEX_FILE, SCAN_ROOTS, recordProject, listProjects, scanForProjects, scanForCandidates, fmtAgo, addServeProject, removeServeProject, serveProjects };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "phewsh",
|
|
3
|
-
"version": "0.15.
|
|
4
|
-
"description": "One mission. Many AI tools. No lost context. PHEWSH keeps your project's intent, decisions, and working context aligned across Claude Code, Codex, Cursor, Gemini and your team
|
|
3
|
+
"version": "0.15.81",
|
|
4
|
+
"description": "One mission. Many AI tools. No lost context. PHEWSH keeps your project's intent, decisions, and working context aligned across Claude Code, Codex, Cursor, Gemini and your team \u2014 so the next AI knows what the last one learned.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"phewsh": "bin/phewsh.js"
|
|
7
7
|
},
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"commands/",
|
|
11
11
|
"lib/",
|
|
12
12
|
"mcp/",
|
|
13
|
+
"skills/",
|
|
13
14
|
"README.md",
|
|
14
15
|
"SECURITY.md",
|
|
15
16
|
"LICENSE"
|
|
@@ -54,4 +55,4 @@
|
|
|
54
55
|
"dependencies": {
|
|
55
56
|
"@modelcontextprotocol/sdk": "^1.0.0"
|
|
56
57
|
}
|
|
57
|
-
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: boat-to-shore
|
|
3
|
+
description: Use when running recurring autonomous improvement sessions on a repository (e.g. via /loop or a scheduled continuation), when resuming work after a session reset or context loss, or when moving a repo from unfinished/TODO-riddled toward shippable without a human available to answer questions.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Boat to Shore
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
Move the repo from **Water** (broken, unclear, unfinished) to **Shore** (runs, builds, core flow works, verified) to **Mountain** (polished, tested, documented, launch-ready). One verified slice per session. The state file is the anchor: every session starts by reading it and ends by updating it. The loop's output is not just code — it's a repo the next session can pick up cold.
|
|
11
|
+
|
|
12
|
+
**Violating the letter of these rules is violating the spirit of the loop.** A high-quality sprawl is still a sprawl.
|
|
13
|
+
|
|
14
|
+
## Session sequence
|
|
15
|
+
|
|
16
|
+
1. **Rehydrate.** Read `./.agent/boat-loop.md`, `git status`/recent log, and README. If the repo has a `.intent/` directory (phewsh), treat it as the project plan and read it first. If the state file is missing, create it: run a first-pass diagnostic (stack, how to run/build/test, TODO/FIXME/placeholder sweep, run the checks) plus a blind-spot pass (unknown unknowns, risky assumptions, map-vs-territory disagreements — the plan is the map, the repo is the territory; when they disagree, trust the territory and log the disagreement).
|
|
17
|
+
2. **Choose ONE slice.** If the state file already names a Current Session Goal or Next best loop, that is your slice — do not renegotiate it. Otherwise pick the single highest-leverage goal completable and verifiable this session. A slice is one *goal*, not everything that happens to live in the same files. Priority order: runs → builds → core flow works → user understands it → verified → survives real use → feels excellent → next agent moves faster.
|
|
18
|
+
3. **Branch.** `git checkout -b boat/<slug>-<date>` (or reuse the existing boat branch). Never work directly on main/master.
|
|
19
|
+
4. **Implement small.** Smallest coherent change that completes the slice. Check `git diff` as you go. No unrelated refactors, no rewrites without proving the current architecture can't support the goal.
|
|
20
|
+
5. **Verify.** Strongest feasible ladder: typecheck/lint → tests → build → local smoke → browser/Playwright when UI behavior matters. A claim without a run check is not a result.
|
|
21
|
+
6. **Push harder — inside the slice's blast radius.** After it works: is there an error/empty/loading state, a locking test, or a doc line attached to *this* slice? Do the best one. Adjacent backlog items are the next loop's slices, not this one's dessert.
|
|
22
|
+
7. **Update state.** Append to `.agent/boat-loop.md` (never overwrite the log): what changed, what was verified, current status (Water/Shore/Mountain), and **Next best loop**. Log non-obvious decisions and deviations from the plan in `.agent/implementation-notes.md` (discovery → decision → why → risk → verification).
|
|
23
|
+
8. **Report** using the template below.
|
|
24
|
+
|
|
25
|
+
## Hard rules
|
|
26
|
+
|
|
27
|
+
- **One slice per session.** Even if the backlog looks small enough to finish. Finishing the backlog in one sitting produces an unreviewable diff and an untestable session.
|
|
28
|
+
- **Never commit to main/master.** Branch, always, even in tiny repos.
|
|
29
|
+
- **"Done" is not a state.** There are only Water, Shore, Mountain — and Mountain still gets a "Next best loop" (a strategic push, or an explicit recommendation to the human to stop the loop). If you wrote "Done", replace it.
|
|
30
|
+
- **Verify before claiming.** If a check fails and is out of scope, document it precisely — don't hide it.
|
|
31
|
+
- **Blocked?** Make the safest reversible assumption, log it, keep moving. Ask a question only if it would change architecture, data model, auth, pricing, or ownership — never "should I continue?"
|
|
32
|
+
- **Bounded research.** Competitor/reference scan at most once per repo, only after Shore, output = implementation moves (not a report), stored in `.agent/competitors.md`.
|
|
33
|
+
|
|
34
|
+
| Rationalization | Reality |
|
|
35
|
+
|---|---|
|
|
36
|
+
| "The remaining items are small — I'll just do them all" | That's three sessions' worth of review risk in one diff. One slice. |
|
|
37
|
+
| "It's all in the same two tiny files — one coherent slice" | File adjacency doesn't merge goals. The state file's scope instruction wins. |
|
|
38
|
+
| "This repo is tiny, master is fine" | The loop runs unattended. Branches are the undo button. |
|
|
39
|
+
| "Everything on the list is fixed — mark it Done" | The list was the map. Re-inspect the territory: embarrassment test, then write the next loop. |
|
|
40
|
+
| "It seems finished" | Prove it: run the checks, load the UI, read it as a stranger. |
|
|
41
|
+
| "Pushing harder means doing more items" | Pushing harder means making *this* slice excellent. |
|
|
42
|
+
|
|
43
|
+
## Red flags — stop and re-read Hard rules
|
|
44
|
+
|
|
45
|
+
Working on main · second backlog item in one session · "Done" anywhere · claiming results with no check run · state file unchanged at session end · asking the human what to do next.
|
|
46
|
+
|
|
47
|
+
## Session report template
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
## Session Result — <repo> — <date>
|
|
51
|
+
Status: Water | Shore | Mountain
|
|
52
|
+
### Slice completed
|
|
53
|
+
### Verified (commands run + results)
|
|
54
|
+
### Deviations & assumptions (map vs territory)
|
|
55
|
+
### Remaining risks
|
|
56
|
+
### Next best loop
|
|
57
|
+
### Quiz for the human (3 Qs: main improvement? riskiest assumption? what should the next loop do?)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## The mountain question
|
|
61
|
+
|
|
62
|
+
Before ending any Shore/Mountain session: *if a real user, a buyer, and a future maintainer saw this tomorrow, what would embarrass us?* The highest-impact answer is the next loop's slice.
|