ftown 0.26.0 → 0.28.0

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.
Files changed (83) hide show
  1. package/dist/attention-cli.d.ts +32 -0
  2. package/dist/attention-cli.js +310 -0
  3. package/dist/attention-cli.js.map +1 -0
  4. package/dist/attention-events.d.ts +2 -1
  5. package/dist/attention-events.js.map +1 -1
  6. package/dist/attention-store.d.ts +6 -3
  7. package/dist/attention-store.js +60 -2
  8. package/dist/attention-store.js.map +1 -1
  9. package/dist/brief-cli.d.ts +10 -0
  10. package/dist/brief-cli.js +163 -0
  11. package/dist/brief-cli.js.map +1 -0
  12. package/dist/command-rpc.d.ts +11 -0
  13. package/dist/command-rpc.js +83 -16
  14. package/dist/command-rpc.js.map +1 -1
  15. package/dist/coordination/attention-routing.d.ts +37 -0
  16. package/dist/coordination/attention-routing.js +154 -0
  17. package/dist/coordination/attention-routing.js.map +1 -0
  18. package/dist/coordination/board.js +82 -3
  19. package/dist/coordination/board.js.map +1 -1
  20. package/dist/coordination/contract-b.d.ts +206 -0
  21. package/dist/coordination/contract-b.js +58 -0
  22. package/dist/coordination/contract-b.js.map +1 -0
  23. package/dist/coordination/contract-c.d.ts +117 -0
  24. package/dist/coordination/contract-c.js +47 -0
  25. package/dist/coordination/contract-c.js.map +1 -0
  26. package/dist/coordination/contract.d.ts +8 -1
  27. package/dist/coordination/contract.js +1 -1
  28. package/dist/coordination/contract.js.map +1 -1
  29. package/dist/coordination/format.js +51 -12
  30. package/dist/coordination/format.js.map +1 -1
  31. package/dist/coordination/plan.d.ts +74 -0
  32. package/dist/coordination/plan.js +534 -0
  33. package/dist/coordination/plan.js.map +1 -0
  34. package/dist/coordination/routes-brief.d.ts +10 -0
  35. package/dist/coordination/routes-brief.js +149 -0
  36. package/dist/coordination/routes-brief.js.map +1 -0
  37. package/dist/coordination/routes-plan.d.ts +13 -0
  38. package/dist/coordination/routes-plan.js +182 -0
  39. package/dist/coordination/routes-plan.js.map +1 -0
  40. package/dist/coordination/routes-worktrees.d.ts +29 -0
  41. package/dist/coordination/routes-worktrees.js +138 -0
  42. package/dist/coordination/routes-worktrees.js.map +1 -0
  43. package/dist/coordination/wiring.d.ts +17 -2
  44. package/dist/coordination/wiring.js +81 -11
  45. package/dist/coordination/wiring.js.map +1 -1
  46. package/dist/coordination-cli.d.ts +6 -1
  47. package/dist/coordination-cli.js +23 -7
  48. package/dist/coordination-cli.js.map +1 -1
  49. package/dist/create-ftown-session.d.ts +26 -1
  50. package/dist/create-ftown-session.js +208 -104
  51. package/dist/create-ftown-session.js.map +1 -1
  52. package/dist/ftown-cli.js +14 -2
  53. package/dist/ftown-cli.js.map +1 -1
  54. package/dist/index.js +29 -0
  55. package/dist/index.js.map +1 -1
  56. package/dist/local-api-server.d.ts +24 -0
  57. package/dist/local-api-server.js +473 -32
  58. package/dist/local-api-server.js.map +1 -1
  59. package/dist/mail-delivery.d.ts +18 -4
  60. package/dist/mail-delivery.js +50 -8
  61. package/dist/mail-delivery.js.map +1 -1
  62. package/dist/plan-cli.d.ts +52 -0
  63. package/dist/plan-cli.js +357 -0
  64. package/dist/plan-cli.js.map +1 -0
  65. package/dist/security/bridge/permissions.js +55 -5
  66. package/dist/security/bridge/permissions.js.map +1 -1
  67. package/dist/session-controller.d.ts +17 -1
  68. package/dist/session-controller.js +93 -2
  69. package/dist/session-controller.js.map +1 -1
  70. package/dist/sessions-commands.js +75 -5
  71. package/dist/sessions-commands.js.map +1 -1
  72. package/dist/types.d.ts +9 -1
  73. package/dist/worktrees-cli.d.ts +39 -0
  74. package/dist/worktrees-cli.js +168 -0
  75. package/dist/worktrees-cli.js.map +1 -0
  76. package/dist/worktrees.d.ts +16 -0
  77. package/dist/worktrees.js +292 -0
  78. package/dist/worktrees.js.map +1 -0
  79. package/hooks/ftown-antigravity-hook.sh +12 -6
  80. package/package.json +2 -2
  81. package/skills/ftown/SKILL.md +88 -5
  82. package/skills/ftown/references/orchestrator.md +32 -29
  83. package/skills/ftown/references/sessions.md +155 -12
@@ -0,0 +1,292 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { existsSync } from 'node:fs';
3
+ import { mkdir, realpath } from 'node:fs/promises';
4
+ import { dirname, join, resolve } from 'node:path';
5
+ import { promisify } from 'node:util';
6
+ import { WorktreeError, } from './coordination/contract-b.js';
7
+ export { WorktreeError, };
8
+ const execFileAsync = promisify(execFile);
9
+ const GIT_TIMEOUT_MS = 30_000;
10
+ const GIT_MAX_BUFFER = 10 * 1024 * 1024;
11
+ const SLUG_MAX_LENGTH = 48;
12
+ export function slugifyWorktreeName(name) {
13
+ const cleaned = name
14
+ .toLowerCase()
15
+ .replace(/[^a-z0-9]+/g, '-')
16
+ .replace(/^-+|-+$/g, '')
17
+ .slice(0, SLUG_MAX_LENGTH)
18
+ .replace(/-+$/g, '');
19
+ return cleaned || 'session';
20
+ }
21
+ async function runGit(cwd, args) {
22
+ const { stdout, stderr } = await execFileAsync('git', [...args], {
23
+ cwd,
24
+ timeout: GIT_TIMEOUT_MS,
25
+ maxBuffer: GIT_MAX_BUFFER,
26
+ env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
27
+ });
28
+ return { stdout, stderr };
29
+ }
30
+ function errorMessage(err) {
31
+ if (err && typeof err === 'object') {
32
+ const stderr = err.stderr;
33
+ if (typeof stderr === 'string' && stderr.trim().length > 0) {
34
+ return stderr.trim();
35
+ }
36
+ const message = err.message;
37
+ if (typeof message === 'string' && message.trim().length > 0) {
38
+ return message.trim();
39
+ }
40
+ }
41
+ return String(err);
42
+ }
43
+ export class GitWorktrees {
44
+ constructor(_options = {}) { }
45
+ async create(req) {
46
+ const fromDir = req.fromDir;
47
+ if (!fromDir || !existsSync(fromDir)) {
48
+ throw new WorktreeError('not_a_repo', `Directory does not exist: ${fromDir}`);
49
+ }
50
+ let topLevelRaw;
51
+ let gitDirRaw;
52
+ let commonDirRaw;
53
+ try {
54
+ topLevelRaw = (await runGit(fromDir, ['rev-parse', '--show-toplevel'])).stdout.trim();
55
+ gitDirRaw = (await runGit(fromDir, ['rev-parse', '--git-dir'])).stdout.trim();
56
+ commonDirRaw = (await runGit(fromDir, ['rev-parse', '--git-common-dir'])).stdout.trim();
57
+ }
58
+ catch (err) {
59
+ throw new WorktreeError('not_a_repo', `Not a git repository (${fromDir}): ${errorMessage(err)}`);
60
+ }
61
+ const topLevel = await realpath(resolve(fromDir, topLevelRaw));
62
+ const gitDir = await realpath(resolve(fromDir, gitDirRaw));
63
+ const commonDir = await realpath(resolve(fromDir, commonDirRaw));
64
+ // When fromDir is itself a linked worktree, gitDir !== commonDir; use the main worktree's root (commonDir parent).
65
+ const repoRoot = gitDir !== commonDir ? await realpath(dirname(commonDir)) : topLevel;
66
+ let base;
67
+ if (req.base !== undefined && req.base.trim().length > 0) {
68
+ base = req.base.trim();
69
+ if (base.startsWith('-')) {
70
+ throw new WorktreeError('git_failed', `Invalid base ref: ${base}`);
71
+ }
72
+ try {
73
+ await runGit(fromDir, ['rev-parse', '--verify', `${base}^{commit}`]);
74
+ }
75
+ catch (err) {
76
+ throw new WorktreeError('git_failed', `Unknown base ref '${base}': ${errorMessage(err)}`);
77
+ }
78
+ }
79
+ else {
80
+ try {
81
+ base = (await runGit(fromDir, ['symbolic-ref', '--short', 'HEAD'])).stdout.trim();
82
+ }
83
+ catch {
84
+ throw new WorktreeError('detached_head', 'detached HEAD; pass --base');
85
+ }
86
+ if (!base) {
87
+ throw new WorktreeError('detached_head', 'detached HEAD; pass --base');
88
+ }
89
+ }
90
+ const slug = slugifyWorktreeName(req.name);
91
+ const rawBranch = req.branch !== undefined ? req.branch.trim() : `ftown/${slug}`;
92
+ if (!rawBranch || rawBranch.startsWith('-') || rawBranch.includes('@{')) {
93
+ throw new WorktreeError('bad_branch', `Invalid branch name: ${req.branch ?? ''}`);
94
+ }
95
+ try {
96
+ const checked = (await runGit(repoRoot, ['check-ref-format', '--branch', rawBranch])).stdout.trim();
97
+ if (checked !== rawBranch) {
98
+ throw new WorktreeError('bad_branch', `Invalid branch name '${rawBranch}'`);
99
+ }
100
+ }
101
+ catch (err) {
102
+ if (err instanceof WorktreeError)
103
+ throw err;
104
+ throw new WorktreeError('bad_branch', `Invalid branch name '${rawBranch}': ${errorMessage(err)}`);
105
+ }
106
+ const branch = rawBranch;
107
+ const worktreePath = join(repoRoot, '.claude', 'worktrees', slug);
108
+ if (existsSync(worktreePath)) {
109
+ throw new WorktreeError('exists', `Worktree path already exists: ${worktreePath}`);
110
+ }
111
+ try {
112
+ await runGit(repoRoot, ['show-ref', '--verify', '--quiet', `refs/heads/${branch}`]);
113
+ throw new WorktreeError('exists', `Branch already exists: ${branch}`);
114
+ }
115
+ catch (err) {
116
+ if (err instanceof WorktreeError)
117
+ throw err;
118
+ }
119
+ await mkdir(dirname(worktreePath), { recursive: true });
120
+ try {
121
+ await runGit(repoRoot, ['worktree', 'add', '-b', branch, worktreePath, base]);
122
+ }
123
+ catch (err) {
124
+ const msg = errorMessage(err);
125
+ if (/already exists|already checked out/i.test(msg)) {
126
+ throw new WorktreeError('exists', msg);
127
+ }
128
+ throw new WorktreeError('git_failed', msg);
129
+ }
130
+ const realWorktreePath = await realpath(worktreePath);
131
+ return {
132
+ repoRoot,
133
+ path: realWorktreePath,
134
+ branch,
135
+ base,
136
+ createdByBridge: true,
137
+ };
138
+ }
139
+ async status(wt) {
140
+ if (!existsSync(wt.path)) {
141
+ return {
142
+ path: wt.path,
143
+ branch: wt.branch,
144
+ base: wt.base,
145
+ exists: false,
146
+ dirty: false,
147
+ ahead: 0,
148
+ unpushed: null,
149
+ mergedIntoBase: false,
150
+ };
151
+ }
152
+ let realWtPath;
153
+ let topLevel;
154
+ try {
155
+ realWtPath = await realpath(wt.path);
156
+ const topOut = (await runGit(realWtPath, ['rev-parse', '--show-toplevel'])).stdout.trim();
157
+ topLevel = await realpath(resolve(realWtPath, topOut));
158
+ }
159
+ catch (err) {
160
+ throw new WorktreeError('git_failed', `Failed to resolve worktree root (${wt.path}): ${errorMessage(err)}`);
161
+ }
162
+ if (topLevel !== realWtPath) {
163
+ throw new WorktreeError('git_failed', `Worktree path '${wt.path}' is not a standalone worktree toplevel (resolved to '${topLevel}')`);
164
+ }
165
+ let dirty;
166
+ try {
167
+ const porcelain = (await runGit(realWtPath, ['status', '--porcelain'])).stdout;
168
+ dirty = porcelain.trim().length > 0;
169
+ }
170
+ catch (err) {
171
+ throw new WorktreeError('git_failed', `Failed to read worktree status: ${errorMessage(err)}`);
172
+ }
173
+ let ahead;
174
+ try {
175
+ const countRaw = (await runGit(realWtPath, ['rev-list', '--count', `${wt.base}..${wt.branch}`])).stdout.trim();
176
+ const parsed = Number.parseInt(countRaw, 10);
177
+ if (!Number.isFinite(parsed) || parsed < 0) {
178
+ throw new Error(`Unexpected rev-list count: ${countRaw}`);
179
+ }
180
+ ahead = parsed;
181
+ }
182
+ catch (err) {
183
+ throw new WorktreeError('git_failed', `Failed to count commits ahead of base: ${errorMessage(err)}`);
184
+ }
185
+ let unpushed = null;
186
+ let upstreamRef;
187
+ try {
188
+ upstreamRef = (await runGit(realWtPath, ['for-each-ref', '--format=%(upstream:short)', `refs/heads/${wt.branch}`])).stdout.trim();
189
+ }
190
+ catch (err) {
191
+ throw new WorktreeError('git_failed', `Failed to inspect branch upstream: ${errorMessage(err)}`);
192
+ }
193
+ if (upstreamRef.length > 0) {
194
+ try {
195
+ const countRaw = (await runGit(realWtPath, ['rev-list', '--count', `${wt.branch}@{u}..${wt.branch}`])).stdout.trim();
196
+ const parsed = Number.parseInt(countRaw, 10);
197
+ if (!Number.isFinite(parsed) || parsed < 0) {
198
+ throw new Error(`Unexpected unpushed count: ${countRaw}`);
199
+ }
200
+ unpushed = parsed;
201
+ }
202
+ catch (err) {
203
+ throw new WorktreeError('git_failed', `Failed to count unpushed commits: ${errorMessage(err)}`);
204
+ }
205
+ }
206
+ let mergedIntoBase = false;
207
+ try {
208
+ await runGit(realWtPath, ['merge-base', '--is-ancestor', wt.branch, wt.base]);
209
+ mergedIntoBase = true;
210
+ }
211
+ catch (err) {
212
+ const code = err?.code;
213
+ if (code === 1) {
214
+ mergedIntoBase = false;
215
+ }
216
+ else {
217
+ throw new WorktreeError('git_failed', `Failed to check merge status: ${errorMessage(err)}`);
218
+ }
219
+ }
220
+ return {
221
+ path: wt.path,
222
+ branch: wt.branch,
223
+ base: wt.base,
224
+ exists: true,
225
+ dirty,
226
+ ahead,
227
+ unpushed,
228
+ mergedIntoBase,
229
+ };
230
+ }
231
+ decide(status) {
232
+ if (!status.exists) {
233
+ return { action: 'retain', reason: 'worktree missing' };
234
+ }
235
+ if (status.dirty) {
236
+ return { action: 'retain', reason: 'uncommitted changes' };
237
+ }
238
+ if (status.mergedIntoBase) {
239
+ return { action: 'remove', deleteBranch: true };
240
+ }
241
+ if (status.unpushed === 0) {
242
+ return { action: 'remove', deleteBranch: false };
243
+ }
244
+ if (status.unpushed !== null && status.unpushed > 0) {
245
+ return {
246
+ action: 'retain',
247
+ reason: `${status.unpushed} unpushed commit${status.unpushed === 1 ? '' : 's'}`,
248
+ };
249
+ }
250
+ if (status.ahead > 0) {
251
+ return {
252
+ action: 'retain',
253
+ reason: `${status.ahead} unmerged commit${status.ahead === 1 ? '' : 's'}`,
254
+ };
255
+ }
256
+ return { action: 'retain', reason: 'unmerged branch' };
257
+ }
258
+ async remove(wt, opts) {
259
+ const removeArgs = ['worktree', 'remove', ...(opts.force ? ['--force'] : []), wt.path];
260
+ try {
261
+ await runGit(wt.repoRoot, removeArgs);
262
+ }
263
+ catch (err) {
264
+ throw new WorktreeError('git_failed', errorMessage(err));
265
+ }
266
+ if (opts.deleteBranch) {
267
+ let merged = false;
268
+ try {
269
+ await runGit(wt.repoRoot, ['merge-base', '--is-ancestor', wt.branch, wt.base]);
270
+ merged = true;
271
+ }
272
+ catch (err) {
273
+ const code = err?.code;
274
+ if (code !== 1) {
275
+ console.warn(`[GitWorktrees] Failed to verify merge status for branch '${wt.branch}': ${errorMessage(err)}`);
276
+ }
277
+ merged = false;
278
+ }
279
+ if (!merged) {
280
+ console.warn(`[GitWorktrees] Refusing to delete unmerged branch '${wt.branch}'`);
281
+ return;
282
+ }
283
+ try {
284
+ await runGit(wt.repoRoot, ['branch', '-d', wt.branch]);
285
+ }
286
+ catch (err) {
287
+ console.warn(`[GitWorktrees] Failed to delete branch '${wt.branch}': ${errorMessage(err)}`);
288
+ }
289
+ }
290
+ }
291
+ }
292
+ //# sourceMappingURL=worktrees.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worktrees.js","sourceRoot":"","sources":["../src/worktrees.ts"],"names":[],"mappings":"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"}
@@ -3,9 +3,11 @@
3
3
  # ~/.ftown/antigravity-hook.sh and registered in ~/.gemini/config/hooks.json.
4
4
  # Translates agy's camelCase hook payload (stdin) to the bridge /hook wire format and POSTs it.
5
5
  # On a turn-end Stop it drains this session's ftown inbox FIRST: with mail it
6
- # prints {"decision":"continue","reason":"<mail>"} and posts nothing (the agent
7
- # keeps working); without mail it posts Stop. On terminationReason
8
- # MAX_FORCED_INVOCATIONS / USER_CANCELED / ERROR it never drains, only posts Stop.
6
+ # prints {"decision":"continue","reason":"<mail>"} (the agent keeps working) and
7
+ # still POSTs Stop so the bridge clears the busy marker (the drain marks the
8
+ # mail delivered, so that Stop finds an empty inbox); without mail it posts
9
+ # Stop. On terminationReason MAX_FORCED_INVOCATIONS / USER_CANCELED / ERROR it
10
+ # never drains, only posts Stop.
9
11
  #
10
12
  # Always exits 0, also on HUP/INT/TERM/PIPE (a non-zero or signalled PreToolUse
11
13
  # hook makes agy BLOCK the tool), and writes nothing to stdout except that one
@@ -15,7 +17,7 @@
15
17
  # Worst-case runtime: ~6s inside agy's 10s hook timeout. The POST tries at most
16
18
  # 2 endpoint candidates x curl --max-time 2 (4s); on Stop the inbox read through
17
19
  # `ftown security proxy` uses a 2s request timeout and happens before the POST,
18
- # and when it delivers mail no POST follows.
20
+ # and the POST also follows when the drain delivers mail (busy-marker clear).
19
21
  #
20
22
  # bash 3.2 compatible (macOS /bin/bash). No set -e on purpose; no temp files.
21
23
  # The hook's working directory is agy's hooks.json folder, so pwd is never used.
@@ -160,9 +162,13 @@ drain_mail() {
160
162
 
161
163
  MAIL_OUT=""
162
164
  if drain_mail; then
163
- # Mail delivered: the agent continues, so the session stays busy (no POST).
164
- # stderr is silenced so a reader that closed the pipe cannot see "Broken pipe".
165
+ # Mail delivered: the agent continues on the decision line, but the Stop is
166
+ # still POSTed so the bridge clears the busy marker (this hook posts no other
167
+ # Stop while the mail turn runs). The decision line goes out first; the POST
168
+ # is fire-and-forget and silenced. stderr is silenced so a reader that closed
169
+ # the pipe cannot see "Broken pipe".
165
170
  printf '%s\n' "$MAIL_OUT" 2>/dev/null
171
+ post_hook "$PAYLOAD" >/dev/null 2>&1 || true
166
172
  exit 0
167
173
  fi
168
174
  post_hook "$PAYLOAD"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ftown",
3
- "version": "0.26.0",
3
+ "version": "0.28.0",
4
4
  "description": "CLI bridge for ftown — generic PTY-over-Centrifugo relay",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -73,5 +73,5 @@
73
73
  "overrides": {
74
74
  "qs": "6.16.1"
75
75
  },
76
- "gitHead": "033b06c892db214c1104779f68ef4f36e3c567f8"
76
+ "gitHead": "1fee3d0e20fd50368c0e0ac1774eb47c184149ce"
77
77
  }
@@ -53,6 +53,12 @@ ftown sessions preview --clear
53
53
  ftown sessions preview-reply --message "The preview is ready."
54
54
  ftown status set --state working --headline "Reviewing the API client" --progress 0/1
55
55
  ftown board
56
+ ftown plan
57
+ ftown plan add "Review the API client" --after p1 --priority 1
58
+ ftown sessions create --parent --plan-item p2 --prompt "Review the API client"
59
+ ftown brief set --file brief.md
60
+ ftown attention list
61
+ ftown worktrees list
56
62
  ftown harness mail send <session-id> --type task "review the API client"
57
63
  ftown harness mail read
58
64
  ftown env --help
@@ -125,6 +131,60 @@ ftown board --wait --until any-done --timeout 300
125
131
  ftown events --wait
126
132
  ```
127
133
 
134
+ Plain `ftown board` appends a `planned` section with up to 5 ready plan
135
+ items (`→ <id> <title>`, then `+N more`). `ftown board --all` also shows
136
+ retired (archived) children after the live ones, suffixed `(retired)`.
137
+ Tree events include the `plan` kind for plan changes and `removed` when a
138
+ child is removed or archived while not already exited.
139
+
140
+ ## Plan list
141
+
142
+ Keep the plan on the manager: `ftown plan` defaults to your own session
143
+ (`$FTOWN_SESSION_ID`, the manager), and `--session-id <id>` targets another
144
+ session's plan. This is the manager's plan, not a ticket system — it is a
145
+ short ordered todo list the manager keeps for its own coordination. Add
146
+ items, order them with precedence, and link each child to one item:
147
+
148
+ ```bash
149
+ ftown plan [--json] [--session-id <id>]
150
+ ftown plan add <title...> [--note <t>] [--after <id,id>] [--priority 1|2|3]
151
+ ftown plan start <id> --session <child-id>
152
+ ftown plan done <id>
153
+ ftown plan drop <id>
154
+ ftown plan todo <id>
155
+ ftown plan edit <id> [--title <t>] [--note <t>|--no-note] [--after <ids>|--no-after] [--priority n]
156
+ ftown plan rm <id>
157
+ ftown sessions create --parent --plan-item <id> --prompt "<task>"
158
+ ```
159
+
160
+ Precedence comes from `--after <id,id>` and `--priority 1|2|3` (1 is
161
+ highest). An item is ready when it is `todo` and every item in its `after`
162
+ list is `done` or `dropped`; the list shows `→` ready, `·` waiting, `▸`
163
+ started, `✓` done and `✗` dropped. Creating a child with `--plan-item <id>`
164
+ requires a parent, fails before creating anything when the item is unknown
165
+ or not `todo`, and otherwise marks the item `started` with that child
166
+ linked. When the linked child's card becomes `done`, its item becomes
167
+ `done` too; a `failed` card leaves the item `started` for the manager to
168
+ decide. `ftown plan start <id> --session <child-id>` links an existing
169
+ child by hand, and `done`/`drop`/`todo` set the state directly. Every plan
170
+ change emits one `plan` tree event (`<id> <state>: <title>`, plus
171
+ ` → ready: …` when items become ready). Exit codes: 0 ok, 1 error, 2 usage.
172
+
173
+ **Delegating work.** Delegate implementation to ftown child sessions (`ftown sessions create --parent ...`) rather than in-process subagents — only sessions appear on the board and dashboard (cards, events, digests, parent-first questions).
174
+ A child session may use its own subagents for reviews and lookups. Pick the CLI and model with `ftown quota --json` (see `references/quota.md`).
175
+
176
+ ## Manager brief
177
+
178
+ If you coordinate children, set a brief at the start and add a note at each
179
+ milestone. The dashboard overview renders the brief and your children
180
+ automatically; you do not build a status page yourself.
181
+
182
+ ```bash
183
+ ftown brief set --file brief.md
184
+ ftown brief note "All workers spawned; waiting on the first results"
185
+ ftown brief show
186
+ ```
187
+
128
188
  ## Human attention and pull requests
129
189
 
130
190
  Use `ftown sessions attention --message "..."` when you need human input. It
@@ -134,6 +194,18 @@ remains opt-in and requires a supported hosted session. Repeating an unresolved
134
194
  request updates its occurrence count; after an uncertain result, inspect the
135
195
  existing request before retrying.
136
196
 
197
+ If you have a parent, your request goes to your parent first, not the human:
198
+ the parent receives one waking escalation mail with the question and the
199
+ commands to answer or escalate it, and the human sees it greyed as "with
200
+ parent" (and may still answer). The parent answers with
201
+ `ftown attention answer <child-id> <event-id> --option <n>` (or
202
+ `--text "<reply>"`) only from facts it holds — the plan, the agreed contract,
203
+ earlier human answers — and escalates preferences and irreversible choices
204
+ with `ftown attention escalate <child-id> <event-id> --note "<context>"`.
205
+ Only the parent raises a parent-routed question to the human. Use
206
+ `ftown sessions attention ... --to human` for things only the human can
207
+ provide, such as credentials or approvals.
208
+
137
209
  ### Questions with answers
138
210
 
139
211
  Add `--option <label>` (repeatable) to ask a question the human answers in the
@@ -145,7 +217,8 @@ not cancel the question; check later with
145
217
  event ID.
146
218
 
147
219
  Delivery: a live waiter receives the answer; otherwise it arrives as ftown mail
148
- from "You" starting with `[ftown attention answer id=...]`.
220
+ from "You" starting with `[ftown attention answer id=...]`. When the parent
221
+ answers, the same delivery applies but the mail sender is the parent session.
149
222
 
150
223
  Claude Code `AskUserQuestion` calls inside an FTown session are answered from
151
224
  the same inbox, and the terminal picker appears only after "Answer in terminal"
@@ -176,12 +249,14 @@ created or updated, before the completion notice: use `ftown sessions pr <URL>`.
176
249
  Omit this step for tasks without a PR; never invent a PR or guess a session
177
250
  identity.
178
251
 
179
- Once the task is complete, request user attention **once per completed task**,
180
- before the final handoff, using `ftown sessions attention --title 'Work complete'
252
+ Once the task is complete, a top-level session requests user attention **once
253
+ per completed task**, before the final handoff, using
254
+ `ftown sessions attention --title 'Work complete'
181
255
  --message "<truthful outcome, checks and PR summary>"`.
182
256
  Do not send a completion notice every turn; retain the uncertain-result guidance
183
- above. Children must still report their result to the parent as assigned; user
184
- attention does not replace that reporting. See the completion example in
257
+ above. If you have a parent, do not raise "Work complete": set your card to
258
+ `done` and report your result to the parent as assigned; the parent raises one
259
+ completion notice for the whole job. See the completion example in
185
260
  `references/sessions.md`.
186
261
 
187
262
  ## Cleanup
@@ -199,6 +274,14 @@ ftown sessions revive <archived-session-id>
199
274
  Removal archives the session; revival creates a new session id and resumes its
200
275
  agent conversation when supported. Preserve useful results before cleanup.
201
276
 
277
+ For children that write code, create them with their own worktree:
278
+ `ftown sessions create --parent --worktree [--branch <b>] [--base <ref>]`.
279
+ `ftown sessions remove <id>` retires a bridge-created worktree when it is
280
+ clean and merged or pushed; otherwise it keeps the worktree and says why.
281
+ `--keep-worktree` always keeps it, `--force-worktree` forces removal.
282
+ Inspect or clean up archived worktrees with `ftown worktrees list` and
283
+ `ftown worktrees prune`.
284
+
202
285
  ## Connection and compatibility
203
286
 
204
287
  The commands read `~/.ftown/bridge.json`. Anyone who can read that file can
@@ -1,40 +1,43 @@
1
1
  # Worker coordination examples
2
2
 
3
- Read `../SKILL.md` for identity, reporting and cleanup behavior, and
4
- `sessions.md` for the command reference. These examples add only coordination
5
- steps. Use an ordinary session with the requested task and parent link; no
6
- special orchestrator mode is needed.
3
+ Read `../SKILL.md` for identity, reporting and cleanup, and
4
+ `sessions.md` for the command reference. No special orchestrator mode is needed.
7
5
 
8
6
  List existing sessions before creating another worker. Give each worker a
9
- bounded task, clear writable files and acceptance checks; save the returned
10
- `session.id` for follow-up. Omit `--shell` to inherit your current harness, or
11
- set it when the task calls for a different harness. Provider flavors require a
12
- bridge-host token configured with `ftown env set <flavor>`.
7
+ bounded task, clear writable files and checks; save the returned
8
+ `session.id`. Omit `--shell` to inherit your harness. Before picking a CLI,
9
+ model and effort, run `ftown quota --json` and follow `quota.md` (owner
10
+ tiers first); never route to `exhausted`.
13
11
 
14
- Before picking a CLI, model and effort for delegated work, run `ftown quota --json`
15
- and follow the decision procedure in `quota.md` (owner tiers first). Pass the
16
- choice as `--shell <provider>`; add `--model <id>` only with an id from that
17
- harness's model list. Never route to `exhausted`.
12
+ Keep the plan on the manager — the manager's plan, not a ticket system.
13
+ Add steps with `ftown plan add` (`--after`/`--priority` set precedence;
14
+ ready = every `after` done or dropped) and create each child with
15
+ `--plan-item`, so its item starts and completes with the child:
18
16
 
19
17
  ```bash
20
- ftown sessions list
21
- ftown sessions create --parent --workdir /path/to/repo --name review-auth \
22
- --prompt "Review auth; read-only. Report concrete findings and validation."
18
+ ftown brief set --file brief.md
19
+ ftown plan add "Fix the auth bug" --priority 1
20
+ ftown sessions create --parent --plan-item p1 --worktree --workdir /path/to/repo --name fix-auth --prompt "Fix auth; run checks."
23
21
  ftown board --wait --until any-done --timeout 300
24
- ftown board
25
- ftown harness mail send <worker-id> --type task "Check the expired-token case too"
22
+ ftown attention list
23
+ ftown harness mail send <worker-id> --type task "Check the expired-token case"
26
24
  ```
27
25
 
28
- Coordinate in a loop: spawn children, wait with
29
- `ftown board --wait --until any-done --timeout 300` (or `any-blocked`,
30
- `any-ready`, `all-done`; `ftown events --wait` waits for the next event), act
31
- on the board and mail, then repeat until every child is done. Children keep
32
- their cards current; progress goes on the card, never as mail. Use
33
- `ftown sessions screen <worker-id> --limit 100` only to investigate terminal
34
- detail. Mail handles directed instructions, results and escalations; read it
35
- with `ftown harness mail read`. Each new batch nudges the recipient at most
36
- once, and a nudge may append a one-line digest of child status. Parallel tasks
37
- need disjoint write ownership and serialized shared resources.
26
+ Loop: keep the plan on the manager, create children with `--plan-item`,
27
+ `ftown board --wait --until any-done` (or `any-blocked`/`any-ready`/
28
+ `all-done`), act, repeat until all are done. Children keep cards current;
29
+ progress goes on the card, never as mail. A child's question comes to you
30
+ first: answer from facts you hold with `ftown attention answer`, or
31
+ escalate preferences and irreversible choices
32
+ with `ftown attention escalate --note`. Mail is for directed
33
+ instructions, results and escalations; read it with
34
+ `ftown harness mail read`. Each mail batch nudges at most once (a digest
35
+ may be appended). Use `ftown sessions screen <worker-id> --limit 100`
36
+ only to investigate. Parallel tasks need disjoint write
37
+ ownership and serialized shared resources. Children set their card to
38
+ done and report to the parent; you raise one "Work complete" notice
39
+ for the whole job.
38
40
 
39
- After accepting a worker's result, request any concrete follow-up before
40
- explicit cleanup with `ftown sessions remove <worker-id>`.
41
+ After accepting a result, clean up with
42
+ `ftown sessions remove <worker-id>`, which retires a clean, merged or pushed
43
+ worktree and otherwise keeps it and says why.