@titan-design/active-work 0.1.0 → 0.3.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@titan-design/active-work",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "description": "Durable workspace state for engineering work — CLI, MCP, and Claude skill for tracking initiatives across sessions",
6
6
  "license": "MIT",
@@ -21,7 +21,8 @@
21
21
  "dist",
22
22
  "skill",
23
23
  "claude-commands",
24
- "scripts"
24
+ "scripts",
25
+ "docs/cli-reference.md"
25
26
  ],
26
27
  "engines": {
27
28
  "node": ">=22"
@@ -33,6 +34,16 @@
33
34
  "build:cli": "tsup",
34
35
  "build:dashboard": "vite build --config src/dashboard/vite.config.ts",
35
36
  "gen:cli-reference": "pnpm build:cli && node scripts/gen-cli-reference.mjs",
37
+ "mine:session": "node tools/mine-session-signals.mjs",
38
+ "index:session": "node tools/build-session-index.mjs",
39
+ "mine:files": "node tools/mine-file-history.mjs",
40
+ "mine:drain": "node tools/mine-drain.mjs",
41
+ "mine:drain-eval": "node tools/eval-drain.mjs",
42
+ "export:aw-data": "node tools/export-aw-data.mjs",
43
+ "cost:rollup": "node tools/cost-rollup.mjs",
44
+ "eval:miner": "node tools/eval-miner.mjs",
45
+ "index:eval": "node tools/eval-session-index.mjs",
46
+ "check:tools": "for f in tools/*.mjs; do node --check \"$f\" || exit 1; done && node -e \"JSON.parse(require('fs').readFileSync('tools/pricing/models.json','utf8'))\"",
36
47
  "postinstall": "node scripts/postinstall.js",
37
48
  "preuninstall": "node scripts/preuninstall.js",
38
49
  "test": "vitest run",
@@ -46,14 +57,14 @@
46
57
  "format:check": "prettier --check \"src/**/*.ts\" \"__tests__/**/*.ts\""
47
58
  },
48
59
  "devDependencies": {
49
- "@changesets/cli": "^2.31.0",
50
60
  "@eslint/js": "^9.39.4",
61
+ "@types/better-sqlite3": "^7.6.13",
51
62
  "@types/node": "^22.10.5",
52
63
  "@types/proper-lockfile": "^4.1.4",
53
64
  "@types/react": "^19.2.14",
54
65
  "@types/react-dom": "^19.2.3",
55
66
  "@vitejs/plugin-react": "^6.0.1",
56
- "@vitest/coverage-v8": "^3.0.5",
67
+ "@vitest/coverage-v8": "^3.2.7",
57
68
  "eslint": "^9.20.0",
58
69
  "prettier": "^3.4.2",
59
70
  "react": "^19.2.6",
@@ -65,7 +76,7 @@
65
76
  "typescript-eslint": "^8.24.1",
66
77
  "vite": "^8.0.12",
67
78
  "vite-plugin-singlefile": "^2.3.3",
68
- "vitest": "^3.0.5"
79
+ "vitest": "^3.2.7"
69
80
  },
70
81
  "publishConfig": {
71
82
  "access": "public"
@@ -74,6 +85,13 @@
74
85
  "@clack/prompts": "^1.4.0",
75
86
  "@hono/node-server": "^2.0.2",
76
87
  "@modelcontextprotocol/sdk": "^1.29.0",
88
+ "@titan-design/cluster": "^0.1.0",
89
+ "@titan-design/daemon": "^0.1.2",
90
+ "@titan-design/locator": "^0.1.0",
91
+ "@titan-design/registry": "^0.1.0",
92
+ "@titan-design/session-graph": "^0.3.1",
93
+ "@titan-design/session-read": "^0.2.0",
94
+ "better-sqlite3": "^13.0.2",
77
95
  "commander": "^14.0.3",
78
96
  "env-paths": "^4.0.0",
79
97
  "gray-matter": "^4.0.3",
@@ -84,5 +102,10 @@
84
102
  "proper-lockfile": "^4.1.2",
85
103
  "yaml": "^2.9.0",
86
104
  "zod": "^4.4.3"
105
+ },
106
+ "pnpm": {
107
+ "onlyBuiltDependencies": [
108
+ "better-sqlite3"
109
+ ]
87
110
  }
88
111
  }
@@ -10,20 +10,41 @@
10
10
  * Requires `pnpm build` to have run so `dist/cli.js` exists.
11
11
  */
12
12
  import { spawnSync } from 'node:child_process';
13
- import { writeFileSync, existsSync } from 'node:fs';
13
+ import { writeFileSync, existsSync, statSync, readdirSync } from 'node:fs';
14
14
  import { fileURLToPath } from 'node:url';
15
- import { dirname, resolve } from 'node:path';
15
+ import { dirname, resolve, join } from 'node:path';
16
16
 
17
17
  const __dirname = dirname(fileURLToPath(import.meta.url));
18
18
  const REPO_ROOT = resolve(__dirname, '..');
19
19
  const CLI = resolve(REPO_ROOT, 'dist/cli.js');
20
20
  const OUT = resolve(REPO_ROOT, 'docs/cli-reference.md');
21
+ const SRC = resolve(REPO_ROOT, 'src');
21
22
 
22
23
  if (!existsSync(CLI)) {
23
24
  console.error(`error: ${CLI} not found. Run \`pnpm build\` first.`);
24
25
  process.exit(1);
25
26
  }
26
27
 
28
+ /** Recursively find the most recent mtime among files under `dir`. */
29
+ function newestMtime(dir) {
30
+ let newest = 0;
31
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
32
+ const path = join(dir, entry.name);
33
+ const mtime = entry.isDirectory() ? newestMtime(path) : statSync(path).mtimeMs;
34
+ if (mtime > newest) newest = mtime;
35
+ }
36
+ return newest;
37
+ }
38
+
39
+ const distMtime = statSync(CLI).mtimeMs;
40
+ const srcMtime = newestMtime(SRC);
41
+ if (srcMtime > distMtime) {
42
+ console.error(
43
+ `error: ${CLI} is older than src/ (dist stale). Run \`pnpm build:cli\` before regenerating docs.`,
44
+ );
45
+ process.exit(1);
46
+ }
47
+
27
48
  /** Run the built CLI and return its stdout (help is written to stdout by commander). */
28
49
  function runHelp(args) {
29
50
  const result = spawnSync(process.execPath, [CLI, ...args, '--help'], {
package/skill/SKILL.md CHANGED
@@ -1,12 +1,12 @@
1
1
  ---
2
2
  name: active-work
3
3
  description: |
4
- Maintain durable per-initiative workspace state (brief, handoff, tasks, sessions, artifacts) so engineering work picks up cleanly across Claude Code sessions. Use when the user mentions: "active work", "what am I working on", "bootstrap session", "new initiative", "record this session", "update handoff", "archive initiative", "check active", "audit my workstreams", "find untracked work", "set up active-work", "add a task", "mark X done", "what's blocking me", "wrap up", "I'm done", or types /active-work.
4
+ Maintain durable per-initiative workspace state (brief, tasks, sessions, notes, artifacts) so engineering work picks up cleanly across Claude Code sessions. Use when the user mentions: "active work", "what am I working on", "bootstrap session", "new initiative", "record this session", "update handoff", "archive initiative", "check active", "audit my workstreams", "find untracked work", "set up active-work", "add a task", "mark X done", "what's blocking me", "wrap up", "I'm done", or types /active-work.
5
5
  ---
6
6
 
7
7
  # active-work — durable workspace state across Claude sessions
8
8
 
9
- `active-work` keeps a small filesystem-backed record for every engineering initiative the user has in flight. Files live under `$XDG_DATA_HOME/active-work/<slug>/` and include `brief.md` (frontmatter + prose), `handoff.md` (free prose), `tasks/*.yml`, `sessions/*.md`, `artifacts.yml`, and `sources/`. The CLI is `active-work`; `aw <slug>` is a thin launcher that bootstraps a Claude session for an initiative. A long-running daemon, `active-work mcp serve`, exposes MCP tools to Claude Code over HTTP and serves a read-only dashboard at `http://127.0.0.1:7400/ui`.
9
+ `active-work` keeps a small filesystem-backed record for every engineering initiative the user has in flight. Files live under the active root (`~/Library/Application Support/active-work/<slug>/` on macOS — resolved via `env-paths`, which ignores `XDG_DATA_HOME`) and include `brief.md` (frontmatter + prose), `tasks/*.yml`, `sessions/*.md`, `artifacts.yml`, and `sources/` (including `sources/notes/` for durable notes). There is no `handoff.md`: current state is *derived* from the open-loop ledger in the session files, so it cannot go stale. The CLI is `active-work`; `aw <slug>` is a thin launcher that bootstraps a Claude session for an initiative. A long-running daemon, `active-work mcp serve`, exposes MCP tools to Claude Code over HTTP and serves a read-only dashboard at `http://127.0.0.1:7400/ui`.
10
10
 
11
11
  ## When to engage
12
12
 
@@ -16,7 +16,7 @@ Engage whenever the user signals they want to inspect, mutate, or hand off persi
16
16
  - "bootstrap session" / `/active-work` — load the bootstrap prompt for an initiative
17
17
  - "new initiative" — scaffold a slug with `active-work new`
18
18
  - "add a task" / "mark X done" / "what's blocking me" — task ops via `active-work task`
19
- - "record this session" / "update handoff" / "wrap up" / "I'm done" — session capture + handoff
19
+ - "record this session" / "update handoff" / "wrap up" / "I'm done" — session capture via `active-work wrap` (there is no handoff file; the phrase still means wrap)
20
20
  - "archive initiative" — move a slug to the archived state
21
21
  - "check active" / "audit my workstreams" — `active-work audit` health check
22
22
  - "find untracked work" — `active-work discover` across configured sources
@@ -24,26 +24,62 @@ Engage whenever the user signals they want to inspect, mutate, or hand off persi
24
24
 
25
25
  ## Core rules
26
26
 
27
- 1. **Edits route through the CLI.** Use `active-work new`, `active-work set`, `active-work task add`, `active-work task done`, `active-work artifact add`, etc. Direct `Edit`/`Write` on `tasks/*.yml`, `artifacts.yml`, or the frontmatter of `brief.md` bypasses validation and corrupts the schema. Prose bodies (`brief.md` body, `handoff.md`, session summaries) can be edited directly, but prefer `active-work edit` for `brief.md` because it re-validates frontmatter on save.
27
+ 1. **Edits route through the CLI.** Use `active-work new`, `active-work set`, `active-work task add`, `active-work task done`, `active-work artifact add`, etc. Direct `Edit`/`Write` on `tasks/*.yml`, `artifacts.yml`, or the frontmatter of `brief.md` bypasses validation and corrupts the schema. Prose bodies (`brief.md` body, session summaries, note bodies) can be edited directly, but prefer `active-work edit` for `brief.md` because it re-validates frontmatter on save.
28
28
  2. **LLM writes prose; CLI handles structure.** Task ordering, session filenames, frontmatter dates, slug normalization, and rank reflow are CLI primitives. Don't compute them yourself.
29
29
  3. **`active-work --help` is the canonical command reference.** This skill intentionally doesn't duplicate the surface; run `active-work --help` or `active-work <command> --help` when you need flags.
30
- 4. **Session capture at end.** When wrapping up, run `active-work session record <slug>` with a 3-5 bullet summary of what happened, what changed, and what's next. Auto-prompt this when you detect the user winding down ("I'm done", "let's stop", "wrap up", inactivity after a chunk of work).
30
+ 4. **Wrap is the end of the world.** See "Wrapping a session" below. This is the rule agents most often get wrong, and getting it wrong loses the user's work.
31
31
  5. **`active-work mcp status` first.** If MCP tools aren't responding, the daemon may not be running. Start it with `active-work mcp serve --detach` before retrying.
32
32
 
33
+ ## Wrapping a session
34
+
35
+ **Assume the process exits the instant `wrap` returns, and that everything not written to disk is lost.** Not summarized in chat — *written*. The user will very likely start a fresh session immediately; the only thing that survives is what is on disk. Chat scrollback does not carry over.
36
+
37
+ The failure this is designed to prevent: an agent posts its session summary, then follows it with two to four "oh, by the way" items — a task that should be filed, a gotcha worth remembering, an uncommitted worktree — each of which then has to be captured before the session can actually end. **Do that sweep before calling `wrap`, not after.** If you find yourself about to write "one more thing" after wrapping, you wrapped too early.
38
+
39
+ Run `active-work preflight <slug>` first. It is read-only and returns the live git state plus the checklist of categories you must answer.
40
+
41
+ Then sweep your own session for everything durable and file it:
42
+
43
+ | What you have | Where it goes |
44
+ |---|---|
45
+ | Unfinished work, open PRs, unanswered questions | `--next-steps` (the open-loop ledger) |
46
+ | Prior loops this session closed | `--resolves` with `outcome: done` |
47
+ | A thread you're deliberately dropping | `--resolves` with `outcome: abandoned` **and a note saying why** — the note is required, and it surfaces in the bootstrap for 14 days so nobody proposes the dropped thing again |
48
+ | Anything actionable | `active-work task add` before wrapping, then `--tasks-filed` |
49
+ | Process improvements, gotchas, "avoid this next time", non-actionable FYIs | `--notes` (lands in `sources/notes/`, indexed in every future bootstrap) |
50
+ | Uncommitted work, unpushed branches, stashes, worktree state | recorded automatically by `wrap`; add `holding` context if the bare fact isn't enough |
51
+
52
+ Write `--next-steps` text that does **not** restate the task or PR the loop targets — put the id in the step's `ref` field and let the text open with the action. The bootstrap prefixes the ref itself, so text of "AW-28 drain the miner backlog" filed against `ref: AW-28` renders as "AW-28 AW-28 drain the miner backlog" in every future session (AW-71).
53
+
54
+ Every category needs an **explicit** answer. Omitting a flag is an error, not a shortcut — the assert-nothing forms (`--no-loops`, `--no-notes`, `--no-tasks`) exist so that "there was nothing here" is a claim you make deliberately rather than by forgetting. Use them only when they are actually true.
55
+
56
+ **active-work is the source of truth — not `~/.claude` memory, not `CLAUDE.md`.** A process lesson written to memory is invisible to the next session on this initiative and to every other surface. File it as a note.
57
+
58
+ Then report back plainly: the counts of what was filed and updated, and that you are ready to end. `wrap` returns exactly that receipt — relay it rather than re-deriving it.
59
+
60
+ Check `ready_to_end` before you say you are done. It is `false` when a `--resolves` ref closed no loop; `resolves_rejected[]` names each bad ref and why. The session file and `brief.updated` are already written, so **do not re-run `wrap`** — that would duplicate the narrative. Report the rejected refs to the user; they get re-filed from a later session.
61
+
62
+ Auto-prompt the wrap when you detect the user winding down ("I'm done", "let's stop", "wrap up", inactivity after a chunk of work).
63
+
33
64
  ## Bootstrap flow (`aw <slug>` / `active-work open <slug>`)
34
65
 
35
- `aw <slug>` is the operator-facing launcher: it assembles the bootstrap prompt and execs `claude` with the initiative's worktree as cwd. Omit the slug and it resolves the initiative from the caller's cwd (matching against each brief's registered worktrees), falling back to the interactive picker when nothing matches uniquely; `aw --pick` forces the picker. (Register a worktree so this resolution works with `active-work worktree set <slug> <path>`, or at creation via `new --worktree` / `track --worktree`.) `active-work open <slug>` is the same assembly logic, but prints the prompt to stdout instead of spawning Claude — use it from MCP / scripts / any caller that wants to handle the spawn itself (pass `--cwd <dir>` when the caller's process cwd isn't the user's shell cwd, e.g. the daemon). The bootstrap prompt inlines:
66
+ `aw <slug>` is the operator-facing launcher: it assembles the bootstrap prompt and execs `claude` with the initiative's worktree as cwd. Omit the slug and it resolves the initiative from the caller's cwd (matching against each initiative's **registered** worktrees — the entries in `artifacts.yml` that carry a `name`, not the ones `wrap` merely swept), falling back to the interactive picker when nothing matches uniquely; `aw --pick` forces the picker. (Register a worktree so this resolution works with `active-work worktree set <slug> <path>`, or at creation via `new --worktree` / `track --worktree`.) `active-work open <slug>` is the same assembly logic, but prints the prompt to stdout instead of spawning Claude — use it from MCP / scripts / any caller that wants to handle the spawn itself (pass `--cwd <dir>` when the caller's process cwd isn't the user's shell cwd, e.g. the daemon). The bootstrap prompt inlines:
36
67
 
37
- - The full `handoff.md` text
38
- - A brief excerpt (frontmatter summary + first prose paragraph)
68
+ - A brief excerpt (the brief's prose body, truncated to 40 lines)
69
+ - **Open loops** unresolved `next_steps` from prior sessions, with the age of each hang
70
+ - **Abandoned loops** from the last 14 days, each with the reason it was dropped (`active-work loops <slug> --state abandoned` for the full history)
39
71
  - The most recent session summary
40
72
  - The top N open tasks (rank-sorted)
73
+ - Recently-done tasks from the last 14 days, if any
74
+ - **Durable notes** — newest first, capped by count and never expired by age
41
75
  - Open artifacts with status
42
- - Time since the last session
76
+ - A context block with today's date, bootstrap time, and time since the last session
43
77
 
44
78
  To re-seed context **mid-session** (a session that wasn't started via `aw`, or one that has drifted), run `active-work prompt` — it prints the same bootstrap prompt to stdout, cwd-resolved and side-effect-free (no auto-archive). The bundled `/aw-prompt` slash command wraps it and injects the output straight into the session.
45
79
 
46
- Because handoff and brief excerpt are already in your context, **do not re-read `brief.md` or `handoff.md`** at the top of the session. Jump straight to the highest-rank open task unless the user redirects you. If the user opens a slug without further instruction, ask "continue with `<top task title>`?" and proceed on confirmation.
80
+ The brief excerpt is already in your context, so **do not re-read `brief.md`** at the top of the session. Current state needs no separate file — it is the open-loop section, derived fresh at every bootstrap from the session ledger. Jump straight to the highest-rank open task unless the user redirects you. If the user opens a slug without further instruction, ask "continue with `<top task title>`?" and proceed on confirmation.
81
+
82
+ **Sibling sessions** (`# Another session may already be live on this initiative`): each bootstrap records a lease under `<active root>/.sessions/<slug>/` and warns when another session still holds one. A session launched via `aw` is reported as a live process — its pid is actually checked — while one bootstrapped through `open`/MCP is a 90-minute guess and is worded as one, because that path leaves no process to confirm. When the section appears, **ask the user which session owns the top task before starting it**. If yours is the second session, take distinct scope and record it with `active-work wrap --track adhoc`: a `canonical` wrap here buries the other session's mainline thread in the next bootstrap. When the initiative carries an agent-chat channel, also register under a name that distinguishes you and coordinate scope there. `--no-sibling-check` skips the probe on `open`/`prompt` (and, for `open`, the lease write); `--offline` skips it too. `active-work prompt` detects siblings but never records a lease of its own — it stays read-only. Dead leases are swept at the next bootstrap; `active-work doctor` reports the live/pruned counts.
47
83
 
48
84
  **Ad-hoc sessions** (`aw <slug> --adhoc`, also `open`/`prompt --adhoc`): the opening and closing directives change to say the session is scoped to ad-hoc work on the workstream — the context is background, *not* a directive. Do **not** offer to continue the top task; wait for the user to describe the specific ad-hoc task, then work it with the workstream context in mind. The bootstrap prompt itself carries this instruction, so follow whichever framing it renders.
49
85
 
@@ -46,7 +46,7 @@ active-work track gh:hjewkes/active-work#42 \
46
46
  --title "Dashboard load is sluggish on cold open"
47
47
  ```
48
48
 
49
- `active-work track` scaffolds the initiative (`brief.md` with frontmatter, empty `handoff.md`, empty `tasks/`, an `artifacts.yml` seeded with the source ref), then prints the new slug. Open it next:
49
+ `active-work track` scaffolds the initiative (`brief.md` with frontmatter, empty `tasks/`, an `artifacts.yml` seeded with the source ref), then prints the new slug. Open it next:
50
50
 
51
51
  ```bash
52
52
  active-work open dashboard-perf
@@ -82,15 +82,13 @@ After triage, run the audit to catch issues:
82
82
  active-work audit
83
83
  ```
84
84
 
85
- Audit checks:
85
+ `audit` is a cross-initiative summary, not a rule engine. It returns exactly three things:
86
86
 
87
- - Every active slug has a non-empty `handoff.md`
88
- - Every active slug has at least one open task (otherwise: should it be archived?)
89
- - `artifacts.yml` references resolve (PRs exist, branches exist locally, etc.)
90
- - Last session timestamp isn't stale beyond the configured threshold
91
- - No frontmatter validation errors
87
+ - `initiatives` every slug with its title, state, rank, `updated` date, and ship target, sorted by rank then state
88
+ - `parse_errors` slugs whose `brief.md` frontmatter failed validation, with the error
89
+ - `worktree_conflicts` worktree paths registered by more than one initiative
92
90
 
93
- Warnings are non-fatal. Fix them iteratively with `active-work set <slug> ...`, `active-work task add`, or `active-work archive`.
91
+ For per-initiative health checks — staleness, loops hanging past 30 days, dangling `next_steps` refs — use `active-work lint` and `active-work doctor` instead. Nothing here is fatal; fix findings with `active-work set <slug> ...`, `active-work task add`, or `active-work archive`.
94
92
 
95
93
  ## Worked example
96
94
 
@@ -118,8 +116,14 @@ $ active-work drop claude:session/0192abc --reason "one-off debugging, no follow
118
116
  dropped
119
117
 
120
118
  $ active-work audit
121
- brain-inbox-rewrite: handoff.md is empty — add a one-paragraph status
122
- ok: 1 warning across 2 initiatives
119
+ {
120
+ "initiatives": [
121
+ { "slug": "dashboard-perf", "title": "Dashboard cold-load perf", "state": "focused", "rank": 1, "updated": "2026-07-28" },
122
+ { "slug": "brain-inbox-rewrite", "title": "Brain inbox rewrite", "state": "focused", "rank": 2, "updated": "2026-07-28" }
123
+ ],
124
+ "parse_errors": [],
125
+ "worktree_conflicts": []
126
+ }
123
127
  ```
124
128
 
125
129
  The user is now caught up. Continue with `active-work open dashboard-perf` (or whichever slug they want to push on first).
@@ -19,6 +19,7 @@ If you are modifying the `active-work` skill, adding a CLI command, extending th
19
19
  | `src/templates/` | Mustache templates for scaffolding new initiatives. |
20
20
  | `src/bootstrap/` | Bootstrap prompt assembly used by `active-work open`. |
21
21
  | `src/discover/` | Discovery sources (gh, git, projects, Claude sessions). |
22
+ | `src/sources/` | Reads `<slug>/sources/*.md` at call time. There is no sidecar index and no `sources:` frontmatter field — a stored list drifts from the directory, so the listing is always derived. |
22
23
  | `skill/` | This skill. Copied into `~/.claude/skills/active-work/` by `scripts/postinstall.js`. |
23
24
  | `scripts/` | npm lifecycle hooks: `postinstall.js`, `preuninstall.js`. |
24
25
  | `__tests__/` | Vitest tests; fixtures under `__tests__/fixtures/`. |
@@ -26,7 +27,7 @@ If you are modifying the `active-work` skill, adding a CLI command, extending th
26
27
  ## Adding a new command
27
28
 
28
29
  1. Create `src/commands/<name>.ts` exporting a `defineCommand({...})` entry. Define:
29
- - `name` (e.g. `"task add"`)
30
+ - `name` (dot-separated, e.g. `"task.add"`)
30
31
  - `description`
31
32
  - `input` zod schema
32
33
  - `output` zod schema
@@ -46,7 +47,7 @@ If you are modifying the `active-work` skill, adding a CLI command, extending th
46
47
  `src/server/` runs hono on `127.0.0.1:7400` by default (override with `AW_PORT`). It serves:
47
48
 
48
49
  - `/rpc/<command>` — REST for every registry entry
49
- - `/ws` — WebSocket live feed (chokidar-backed filesystem events)
50
+ - `/events` — SSE live-reload feed (`hono/streaming`), backed by a hand-rolled recursive `fs.watch` in `src/server/file-watch.ts` (not WebSocket, not chokidar), debounced and broadcast via `EventHub`
50
51
  - `/mcp` — MCP-over-HTTP transport
51
52
  - `/ui` — bundled dashboard SPA
52
53
 
@@ -36,19 +36,15 @@ ls ~/.claude/skills/active-work/
36
36
 
37
37
  ## 4. Run `active-work setup`
38
38
 
39
- `active-work setup` (Wave 6) is the interactive wizard that initializes data roots, registers the MCP server with Claude Code, and offers to install the daemon launchd plist. **It is not yet implemented.** Until it lands, you can prepare the data root manually:
40
-
41
39
  ```bash
42
- mkdir -p "${XDG_DATA_HOME:-$HOME/Library/Application Support}/active-work"
40
+ active-work setup
43
41
  ```
44
42
 
45
- (On Linux this resolves to `~/.local/share/active-work/`; on macOS to `~/Library/Application Support/active-work/`.)
43
+ This is the interactive wizard: it checks your Node version, creates the active/state/config data roots, writes a config stub, installs the skill and the `/aw-prompt` command (idempotently safe to re-run), registers the MCP server with Claude Code (`claude mcp add --user @hjewkes/active-work -- active-work mcp serve --stdio`), offers to install daemon supervision (launchd on macOS, systemd with linger on Linux), offers to start the daemon, and offers to walk through discovering existing work.
46
44
 
47
- Register the MCP server with Claude Code manually for now:
45
+ Use `active-work setup --yes` for a non-interactive run (skips prompts, assumes no for daemon start), or `--update` to allow it to overwrite an existing config stub.
48
46
 
49
- ```bash
50
- claude mcp add --user @hjewkes/active-work -- active-work mcp serve --stdio
51
- ```
47
+ If the `claude` CLI isn't on your PATH, the MCP-registration step prints the `~/.claude.json` snippet to add by hand instead of failing.
52
48
 
53
49
  ## 5. Verify the install
54
50
 
@@ -61,7 +57,7 @@ active-work mcp status # reports daemon state
61
57
 
62
58
  ## 6. Optional — start the daemon
63
59
 
64
- The daemon hosts MCP-over-HTTP, the REST API, the WebSocket live feed, and the dashboard.
60
+ The daemon hosts MCP-over-HTTP, the REST API, an SSE live-reload feed (`/events`), and the dashboard.
65
61
 
66
62
  ```bash
67
63
  active-work mcp serve --detach