@titan-design/active-work 0.1.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/LICENSE +21 -0
- package/README.md +207 -0
- package/claude-commands/aw-prompt.md +21 -0
- package/dist/aw.js +183 -0
- package/dist/aw.js.map +1 -0
- package/dist/chunk-OET6AFME.js +1276 -0
- package/dist/chunk-OET6AFME.js.map +1 -0
- package/dist/cli.js +6967 -0
- package/dist/cli.js.map +1 -0
- package/dist/dashboard/index.html +22 -0
- package/package.json +88 -0
- package/scripts/gen-cli-reference.mjs +139 -0
- package/scripts/postinstall.js +48 -0
- package/scripts/preuninstall.js +15 -0
- package/skill/SKILL.md +54 -0
- package/skill/references/auditing-existing-work.md +125 -0
- package/skill/references/cli-dev.md +63 -0
- package/skill/references/onboarding.md +72 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Generate `docs/cli-reference.md` by walking `active-work --help` and capturing
|
|
4
|
+
* `--help` output for every leaf sub-command.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* node scripts/gen-cli-reference.mjs # writes docs/cli-reference.md
|
|
8
|
+
* node scripts/gen-cli-reference.mjs --stdout # prints to stdout
|
|
9
|
+
*
|
|
10
|
+
* Requires `pnpm build` to have run so `dist/cli.js` exists.
|
|
11
|
+
*/
|
|
12
|
+
import { spawnSync } from 'node:child_process';
|
|
13
|
+
import { writeFileSync, existsSync } from 'node:fs';
|
|
14
|
+
import { fileURLToPath } from 'node:url';
|
|
15
|
+
import { dirname, resolve } from 'node:path';
|
|
16
|
+
|
|
17
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
18
|
+
const REPO_ROOT = resolve(__dirname, '..');
|
|
19
|
+
const CLI = resolve(REPO_ROOT, 'dist/cli.js');
|
|
20
|
+
const OUT = resolve(REPO_ROOT, 'docs/cli-reference.md');
|
|
21
|
+
|
|
22
|
+
if (!existsSync(CLI)) {
|
|
23
|
+
console.error(`error: ${CLI} not found. Run \`pnpm build\` first.`);
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Run the built CLI and return its stdout (help is written to stdout by commander). */
|
|
28
|
+
function runHelp(args) {
|
|
29
|
+
const result = spawnSync(process.execPath, [CLI, ...args, '--help'], {
|
|
30
|
+
encoding: 'utf8',
|
|
31
|
+
env: { ...process.env, NO_COLOR: '1', FORCE_COLOR: '0' },
|
|
32
|
+
});
|
|
33
|
+
if (result.status !== 0 && result.status !== null) {
|
|
34
|
+
// commander exits 0 for help; treat anything else as a soft warning.
|
|
35
|
+
process.stderr.write(
|
|
36
|
+
`warn: \`active-work ${args.join(' ')} --help\` exited ${result.status}\n${result.stderr}\n`,
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
return (result.stdout || '').trimEnd();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Parse a `Commands:` block out of a help blob. Each entry is one or more
|
|
44
|
+
* lines: the first starts with two-space indent, then the command name,
|
|
45
|
+
* then a description that may wrap to subsequent indented lines.
|
|
46
|
+
*
|
|
47
|
+
* Returns an array of `{ name, description }`. Sub-command groups (their
|
|
48
|
+
* description is exactly `<name> commands`) are flagged via `isGroup`.
|
|
49
|
+
*/
|
|
50
|
+
function parseCommands(helpText) {
|
|
51
|
+
const lines = helpText.split('\n');
|
|
52
|
+
const startIdx = lines.findIndex((l) => l.trim() === 'Commands:');
|
|
53
|
+
if (startIdx === -1) return [];
|
|
54
|
+
|
|
55
|
+
const entries = [];
|
|
56
|
+
let current = null;
|
|
57
|
+
for (let i = startIdx + 1; i < lines.length; i++) {
|
|
58
|
+
const line = lines[i];
|
|
59
|
+
if (line.trim() === '') {
|
|
60
|
+
if (current) {
|
|
61
|
+
entries.push(current);
|
|
62
|
+
current = null;
|
|
63
|
+
}
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
// Lines starting with two spaces and a letter begin a new command;
|
|
67
|
+
// subsequent indented continuation lines extend the description.
|
|
68
|
+
const match = line.match(/^ {2}([a-z][a-z0-9-]*)(?:\s+\[options])?(?:\s+[^ ].*?)?\s{2,}(.*)$/);
|
|
69
|
+
if (match) {
|
|
70
|
+
if (current) entries.push(current);
|
|
71
|
+
current = { name: match[1], description: match[2].trim() };
|
|
72
|
+
} else if (current && /^\s{4,}/.test(line)) {
|
|
73
|
+
current.description += ' ' + line.trim();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (current) entries.push(current);
|
|
77
|
+
|
|
78
|
+
return entries
|
|
79
|
+
.filter((e) => e.name !== 'help')
|
|
80
|
+
.map((e) => ({
|
|
81
|
+
...e,
|
|
82
|
+
isGroup: /^[a-z]+ commands$/.test(e.description),
|
|
83
|
+
}));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Walk help recursively to enumerate every leaf command path.
|
|
88
|
+
* A leaf is any command whose own help has no `Commands:` section
|
|
89
|
+
* (or only `help` as a sub-command).
|
|
90
|
+
*/
|
|
91
|
+
function enumerateLeaves(prefix = []) {
|
|
92
|
+
const help = runHelp(prefix);
|
|
93
|
+
const cmds = parseCommands(help);
|
|
94
|
+
if (cmds.length === 0) {
|
|
95
|
+
// Leaf — return the prefix itself (or empty for root with no children).
|
|
96
|
+
return prefix.length === 0 ? [] : [prefix];
|
|
97
|
+
}
|
|
98
|
+
const leaves = [];
|
|
99
|
+
for (const cmd of cmds) {
|
|
100
|
+
leaves.push(...enumerateLeaves([...prefix, cmd.name]));
|
|
101
|
+
}
|
|
102
|
+
return leaves;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function renderReference() {
|
|
106
|
+
const header = `# CLI reference
|
|
107
|
+
|
|
108
|
+
Generated from \`active-work --help\` and individual command \`--help\` outputs.
|
|
109
|
+
Re-run \`node scripts/gen-cli-reference.mjs\` to refresh after the CLI
|
|
110
|
+
surface changes.
|
|
111
|
+
|
|
112
|
+
`;
|
|
113
|
+
|
|
114
|
+
const rootHelp = runHelp([]);
|
|
115
|
+
const sections = [
|
|
116
|
+
`## active-work\n\nTop-level help. Run \`active-work <command> --help\` for command-specific options.\n\n\`\`\`\n${rootHelp}\n\`\`\`\n`,
|
|
117
|
+
];
|
|
118
|
+
|
|
119
|
+
const leaves = enumerateLeaves();
|
|
120
|
+
// Sort alphabetically by joined path.
|
|
121
|
+
leaves.sort((a, b) => a.join(' ').localeCompare(b.join(' ')));
|
|
122
|
+
|
|
123
|
+
for (const leaf of leaves) {
|
|
124
|
+
const path = leaf.join(' ');
|
|
125
|
+
const help = runHelp(leaf);
|
|
126
|
+
sections.push(`## active-work ${path}\n\n\`\`\`\n${help}\n\`\`\`\n`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return header + sections.join('\n');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const out = renderReference();
|
|
133
|
+
|
|
134
|
+
if (process.argv.includes('--stdout')) {
|
|
135
|
+
process.stdout.write(out);
|
|
136
|
+
} else {
|
|
137
|
+
writeFileSync(OUT, out, 'utf8');
|
|
138
|
+
process.stderr.write(`wrote ${OUT}\n`);
|
|
139
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Copy the bundled Claude Code skill into ~/.claude/skills/active-work/ and the
|
|
2
|
+
// /aw-prompt slash command into ~/.claude/commands/ when ~/.claude exists. Skip
|
|
3
|
+
// silently otherwise. Fail-soft: never abort an npm install if a copy fails.
|
|
4
|
+
import { existsSync, mkdirSync, cpSync, copyFileSync, rmSync } from 'node:fs';
|
|
5
|
+
import { join, dirname } from 'node:path';
|
|
6
|
+
import { homedir } from 'node:os';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = dirname(__filename);
|
|
11
|
+
const skillSource = join(__dirname, '..', 'skill');
|
|
12
|
+
const commandSource = join(__dirname, '..', 'claude-commands', 'aw-prompt.md');
|
|
13
|
+
const claudeDir = join(homedir(), '.claude');
|
|
14
|
+
const claudeSkillsDir = join(claudeDir, 'skills', 'active-work');
|
|
15
|
+
const claudeCommandsDir = join(claudeDir, 'commands');
|
|
16
|
+
|
|
17
|
+
if (!existsSync(claudeDir)) {
|
|
18
|
+
// User doesn't have Claude Code installed — skip silently.
|
|
19
|
+
process.exit(0);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Skill and command installs are independent — a missing source for one must
|
|
23
|
+
// not skip the other.
|
|
24
|
+
if (existsSync(skillSource)) {
|
|
25
|
+
try {
|
|
26
|
+
// Remove existing install to ensure a clean copy.
|
|
27
|
+
if (existsSync(claudeSkillsDir)) {
|
|
28
|
+
rmSync(claudeSkillsDir, { recursive: true, force: true });
|
|
29
|
+
}
|
|
30
|
+
mkdirSync(claudeSkillsDir, { recursive: true });
|
|
31
|
+
cpSync(skillSource, claudeSkillsDir, { recursive: true });
|
|
32
|
+
console.log(`active-work: installed Claude Code skill to ${claudeSkillsDir}`);
|
|
33
|
+
} catch (err) {
|
|
34
|
+
// Don't fail npm install if skill copy fails.
|
|
35
|
+
console.error(`active-work: skill install skipped (${err.message})`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
if (existsSync(commandSource)) {
|
|
41
|
+
mkdirSync(claudeCommandsDir, { recursive: true });
|
|
42
|
+
copyFileSync(commandSource, join(claudeCommandsDir, 'aw-prompt.md'));
|
|
43
|
+
console.log(`active-work: installed /aw-prompt command to ${claudeCommandsDir}`);
|
|
44
|
+
}
|
|
45
|
+
} catch (err) {
|
|
46
|
+
// Don't fail npm install if the command copy fails.
|
|
47
|
+
console.error(`active-work: command install skipped (${err.message})`);
|
|
48
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Remove ~/.claude/skills/active-work/ on package removal. Fail-soft.
|
|
2
|
+
import { existsSync, rmSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { homedir } from 'node:os';
|
|
5
|
+
|
|
6
|
+
const claudeSkillsDir = join(homedir(), '.claude', 'skills', 'active-work');
|
|
7
|
+
|
|
8
|
+
try {
|
|
9
|
+
if (existsSync(claudeSkillsDir)) {
|
|
10
|
+
rmSync(claudeSkillsDir, { recursive: true, force: true });
|
|
11
|
+
console.log(`active-work: removed Claude Code skill from ${claudeSkillsDir}`);
|
|
12
|
+
}
|
|
13
|
+
} catch (err) {
|
|
14
|
+
console.error(`active-work: skill removal skipped (${err.message})`);
|
|
15
|
+
}
|
package/skill/SKILL.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: active-work
|
|
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.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# active-work — durable workspace state across Claude sessions
|
|
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`.
|
|
10
|
+
|
|
11
|
+
## When to engage
|
|
12
|
+
|
|
13
|
+
Engage whenever the user signals they want to inspect, mutate, or hand off persistent workspace state. Trigger phrases:
|
|
14
|
+
|
|
15
|
+
- "active work" / "what am I working on" — list initiatives, surface current focus
|
|
16
|
+
- "bootstrap session" / `/active-work` — load the bootstrap prompt for an initiative
|
|
17
|
+
- "new initiative" — scaffold a slug with `active-work new`
|
|
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
|
|
20
|
+
- "archive initiative" — move a slug to the archived state
|
|
21
|
+
- "check active" / "audit my workstreams" — `active-work audit` health check
|
|
22
|
+
- "find untracked work" — `active-work discover` across configured sources
|
|
23
|
+
- "set up active-work" — first-time install / `active-work setup`
|
|
24
|
+
|
|
25
|
+
## Core rules
|
|
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.
|
|
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
|
+
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).
|
|
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
|
+
|
|
33
|
+
## Bootstrap flow (`aw <slug>` / `active-work open <slug>`)
|
|
34
|
+
|
|
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:
|
|
36
|
+
|
|
37
|
+
- The full `handoff.md` text
|
|
38
|
+
- A brief excerpt (frontmatter summary + first prose paragraph)
|
|
39
|
+
- The most recent session summary
|
|
40
|
+
- The top N open tasks (rank-sorted)
|
|
41
|
+
- Open artifacts with status
|
|
42
|
+
- Time since the last session
|
|
43
|
+
|
|
44
|
+
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
|
+
|
|
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.
|
|
47
|
+
|
|
48
|
+
**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
|
+
|
|
50
|
+
## Reference docs
|
|
51
|
+
|
|
52
|
+
- [onboarding.md](references/onboarding.md) — first-time setup walkthrough
|
|
53
|
+
- [auditing-existing-work.md](references/auditing-existing-work.md) — discover + triage flow for catching up on untracked work
|
|
54
|
+
- [cli-dev.md](references/cli-dev.md) — internal architecture for skill maintainers and CLI contributors
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# Auditing existing work — discover + triage
|
|
2
|
+
|
|
3
|
+
If the user has a pile of in-flight work but nothing tracked in `active-work` (or only partially tracked), this walkthrough drives them through discovery and triage in three categories: **Track**, **Fold**, **Drop**.
|
|
4
|
+
|
|
5
|
+
## When to use
|
|
6
|
+
|
|
7
|
+
- "I have a bunch of stuff in flight and I want to get it all into active-work"
|
|
8
|
+
- "audit my workstreams"
|
|
9
|
+
- "find untracked work"
|
|
10
|
+
- "what am I forgetting"
|
|
11
|
+
- After a long break — sync state with reality before continuing
|
|
12
|
+
|
|
13
|
+
## 1. Discover
|
|
14
|
+
|
|
15
|
+
`active-work discover` walks the configured discovery sources and returns candidate references (PRs, branches, local repos, recent Claude sessions, project directories). Sources are configured in the user's `$XDG_CONFIG_HOME/active-work/config.json` under `discovery`:
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"discovery": {
|
|
20
|
+
"githubRepos": ["hjewkes/active-work", "hjewkes/brain"],
|
|
21
|
+
"localRepos": ["~/Documents/projects/active-work", "~/Documents/projects/brain"],
|
|
22
|
+
"projectsRoot": "~/Documents/projects"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Run:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
active-work discover
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Output lists each hit with a stable `ref` (e.g. `gh:hjewkes/active-work#42`, `git:active-work@feat/dashboard`, `dir:/Users/h/projects/foo`, `claude:session/abc123`), a short title, and a guess at the freshest activity date.
|
|
34
|
+
|
|
35
|
+
## 2. Triage each hit
|
|
36
|
+
|
|
37
|
+
For every reference, decide one of three actions:
|
|
38
|
+
|
|
39
|
+
### Track — it's a real initiative
|
|
40
|
+
|
|
41
|
+
If the work is meaningful and ongoing, give it a slug and a title:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
active-work track gh:hjewkes/active-work#42 \
|
|
45
|
+
--slug dashboard-perf \
|
|
46
|
+
--title "Dashboard load is sluggish on cold open"
|
|
47
|
+
```
|
|
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:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
active-work open dashboard-perf
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Fold — it belongs under something you already track
|
|
56
|
+
|
|
57
|
+
If the ref is part of an initiative you already have (e.g. one of three PRs against the same effort), fold it in as an artifact:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
active-work fold gh:hjewkes/active-work#43 \
|
|
61
|
+
--into dashboard-perf \
|
|
62
|
+
--note "follow-up PR for the WS reconnection fix"
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
This appends to `artifacts.yml` and writes a row in `sources/discovery.yml` so the same ref won't reappear on the next `active-work discover`.
|
|
66
|
+
|
|
67
|
+
### Drop — not real / not yours / abandoned
|
|
68
|
+
|
|
69
|
+
Mark it dismissed so future `active-work discover` runs ignore it:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
active-work drop gh:upstream/repo#99 --reason "upstream issue, not actionable"
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
The ref is recorded in `sources/discovery.yml` with `dismissed: true` and the reason. You can resurrect it with `active-work track <ref>` later if you change your mind.
|
|
76
|
+
|
|
77
|
+
## 3. Verify state
|
|
78
|
+
|
|
79
|
+
After triage, run the audit to catch issues:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
active-work audit
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Audit checks:
|
|
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
|
|
92
|
+
|
|
93
|
+
Warnings are non-fatal. Fix them iteratively with `active-work set <slug> ...`, `active-work task add`, or `active-work archive`.
|
|
94
|
+
|
|
95
|
+
## Worked example
|
|
96
|
+
|
|
97
|
+
```text
|
|
98
|
+
$ active-work discover
|
|
99
|
+
gh:hjewkes/active-work#42 Dashboard cold-load perf 2d ago
|
|
100
|
+
gh:hjewkes/active-work#43 Fix WS reconnect after sleep 2d ago
|
|
101
|
+
git:brain@feat/inbox-rewrite feat/inbox-rewrite (4 commits) 5d ago
|
|
102
|
+
dir:~/projects/scratch-jq scratch-jq 11d ago
|
|
103
|
+
claude:session/0192abc "look at this gnarly stack trace" 23d ago
|
|
104
|
+
|
|
105
|
+
$ active-work track gh:hjewkes/active-work#42 --slug dashboard-perf --title "Dashboard cold-load perf"
|
|
106
|
+
created: dashboard-perf
|
|
107
|
+
|
|
108
|
+
$ active-work fold gh:hjewkes/active-work#43 --into dashboard-perf --note "WS reconnect follow-up"
|
|
109
|
+
folded into dashboard-perf
|
|
110
|
+
|
|
111
|
+
$ active-work track git:brain@feat/inbox-rewrite --slug brain-inbox-rewrite --title "Brain inbox rewrite"
|
|
112
|
+
created: brain-inbox-rewrite
|
|
113
|
+
|
|
114
|
+
$ active-work drop dir:~/projects/scratch-jq --reason "scratch repo, not real work"
|
|
115
|
+
dropped
|
|
116
|
+
|
|
117
|
+
$ active-work drop claude:session/0192abc --reason "one-off debugging, no follow-up"
|
|
118
|
+
dropped
|
|
119
|
+
|
|
120
|
+
$ active-work audit
|
|
121
|
+
brain-inbox-rewrite: handoff.md is empty — add a one-paragraph status
|
|
122
|
+
ok: 1 warning across 2 initiatives
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
The user is now caught up. Continue with `active-work open dashboard-perf` (or whichever slug they want to push on first).
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# CLI dev — internal architecture for skill maintainers
|
|
2
|
+
|
|
3
|
+
If you are modifying the `active-work` skill, adding a CLI command, extending the daemon, or wiring a new MCP tool, this is the orientation doc. The CLI source lives in `hjewkes/active-work`.
|
|
4
|
+
|
|
5
|
+
## Directory map
|
|
6
|
+
|
|
7
|
+
| Path | What's here |
|
|
8
|
+
|---|---|
|
|
9
|
+
| `src/cli.ts` | Entrypoint. Wires commander to the command registry. |
|
|
10
|
+
| `src/registry/` | Command registry types, JSON envelope, dispatcher contract. The single source of truth for both CLI and MCP. |
|
|
11
|
+
| `src/commands/` | One file per CLI command; each exports a `defineCommand({...})` entry. |
|
|
12
|
+
| `src/commands/index.ts` | The aggregate export — register a new command here. |
|
|
13
|
+
| `src/schemas/` | Zod schemas for `brief.md` frontmatter, tasks, sessions, artifacts, state. Every write goes through a validator. |
|
|
14
|
+
| `src/utils/` | `fs-atomic` (atomic writes + flock), `paths`, `slug`, `gray-matter-io`, `yaml-io`, `today`, `color`. |
|
|
15
|
+
| `src/server/` | hono HTTP + WS + MCP-over-HTTP daemon. |
|
|
16
|
+
| `src/dashboard/` | React (react-native-web) read-only dashboard; built via vite. |
|
|
17
|
+
| `src/migrations/` | Schema migrations keyed by `from` version. |
|
|
18
|
+
| `src/lint/` | Per-artifact lint rules (warn-only). |
|
|
19
|
+
| `src/templates/` | Mustache templates for scaffolding new initiatives. |
|
|
20
|
+
| `src/bootstrap/` | Bootstrap prompt assembly used by `active-work open`. |
|
|
21
|
+
| `src/discover/` | Discovery sources (gh, git, projects, Claude sessions). |
|
|
22
|
+
| `skill/` | This skill. Copied into `~/.claude/skills/active-work/` by `scripts/postinstall.js`. |
|
|
23
|
+
| `scripts/` | npm lifecycle hooks: `postinstall.js`, `preuninstall.js`. |
|
|
24
|
+
| `__tests__/` | Vitest tests; fixtures under `__tests__/fixtures/`. |
|
|
25
|
+
|
|
26
|
+
## Adding a new command
|
|
27
|
+
|
|
28
|
+
1. Create `src/commands/<name>.ts` exporting a `defineCommand({...})` entry. Define:
|
|
29
|
+
- `name` (e.g. `"task add"`)
|
|
30
|
+
- `description`
|
|
31
|
+
- `input` zod schema
|
|
32
|
+
- `output` zod schema
|
|
33
|
+
- `handler(input, ctx)` returning the validated output
|
|
34
|
+
2. Add the import + registration in `src/commands/index.ts`.
|
|
35
|
+
3. The CLI dispatcher and MCP server both pick it up automatically — do not hand-maintain MCP tool definitions.
|
|
36
|
+
4. Add a test under `__tests__/commands/<name>.test.ts`. Use `withTempActiveRoot` / `withEmptyActiveRoot` helpers from `__tests__/setup/test-helpers.ts` for filesystem isolation.
|
|
37
|
+
|
|
38
|
+
## Schema + write discipline
|
|
39
|
+
|
|
40
|
+
- Every write to `brief.md` frontmatter, `tasks/*.yml`, `sessions/*.md` (frontmatter), or `artifacts.yml` must go through a validator in `src/schemas/`.
|
|
41
|
+
- Read-modify-write must use `withFileLock` from `src/utils/fs-atomic.ts` to take a per-initiative POSIX flock.
|
|
42
|
+
- Paths are computed via `env-paths` through `src/utils/paths.ts`. Never hardcode `~/.local/share/...`.
|
|
43
|
+
|
|
44
|
+
## Daemon
|
|
45
|
+
|
|
46
|
+
`src/server/` runs hono on `127.0.0.1:7400` by default (override with `AW_PORT`). It serves:
|
|
47
|
+
|
|
48
|
+
- `/rpc/<command>` — REST for every registry entry
|
|
49
|
+
- `/ws` — WebSocket live feed (chokidar-backed filesystem events)
|
|
50
|
+
- `/mcp` — MCP-over-HTTP transport
|
|
51
|
+
- `/ui` — bundled dashboard SPA
|
|
52
|
+
|
|
53
|
+
The same daemon process can also speak MCP-over-stdio when invoked as `active-work mcp serve --stdio`.
|
|
54
|
+
|
|
55
|
+
## Skill content
|
|
56
|
+
|
|
57
|
+
This skill is just three files: `SKILL.md` and three references. Keep `SKILL.md` skim-able (target ~150 lines, hard cap ~200). Push depth into references and link from `SKILL.md`. The frontmatter `description` is what Claude Code uses to decide whether to load the skill, so keep trigger phrases there fresh and aligned with the CLI command surface.
|
|
58
|
+
|
|
59
|
+
When you add a trigger phrase to `SKILL.md`, ask: is there an existing CLI command that handles it? If not, add the command first, then the trigger.
|
|
60
|
+
|
|
61
|
+
## Release flow
|
|
62
|
+
|
|
63
|
+
Changes to skill content ship in the next `@titan-design/active-work` release. The `postinstall.js` hook reinstalls the skill on every update; users do not need to do anything beyond `npm install -g @titan-design/active-work@latest` (or the pnpm equivalent).
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Onboarding — first-time `active-work` setup
|
|
2
|
+
|
|
3
|
+
This walkthrough takes a brand-new machine to a working `active-work` install with the Claude Code skill available.
|
|
4
|
+
|
|
5
|
+
## 1. Verify prerequisites
|
|
6
|
+
|
|
7
|
+
`active-work` requires Node 22 or newer. `pnpm` is recommended for global installs because it manages multiple Node versions cleanly, but `npm` works too.
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
node --version # expect v22.x or newer
|
|
11
|
+
pnpm --version # any recent version, e.g. 9.x or 10.x
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
If Node is older than 22, install a current LTS via `nvm`, `fnm`, or `volta` before continuing.
|
|
15
|
+
|
|
16
|
+
## 2. Install the package globally
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install -g @titan-design/active-work
|
|
20
|
+
# or
|
|
21
|
+
pnpm add -g @titan-design/active-work
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The package ships the CLI binary (`active-work`), a thin Claude-session launcher (`aw <slug>`), the MCP server, the dashboard bundle, and the Claude skill content.
|
|
25
|
+
|
|
26
|
+
## 3. Postinstall hook copies the skill
|
|
27
|
+
|
|
28
|
+
On install, `scripts/postinstall.js` runs automatically. It looks for `~/.claude/` and, if present, copies the bundled `skill/` directory into `~/.claude/skills/active-work/`. If `~/.claude/` does not exist (Claude Code not installed), the hook exits silently so the npm install never fails.
|
|
29
|
+
|
|
30
|
+
You can verify the skill landed:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
ls ~/.claude/skills/active-work/
|
|
34
|
+
# expect: SKILL.md references/
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## 4. Run `active-work setup`
|
|
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
|
+
```bash
|
|
42
|
+
mkdir -p "${XDG_DATA_HOME:-$HOME/Library/Application Support}/active-work"
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
(On Linux this resolves to `~/.local/share/active-work/`; on macOS to `~/Library/Application Support/active-work/`.)
|
|
46
|
+
|
|
47
|
+
Register the MCP server with Claude Code manually for now:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
claude mcp add --user @hjewkes/active-work -- active-work mcp serve --stdio
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## 5. Verify the install
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
active-work --help # prints the command surface
|
|
57
|
+
active-work mcp status # reports daemon state
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
`active-work mcp status` will report "not running" until you start the daemon (next step).
|
|
61
|
+
|
|
62
|
+
## 6. Optional — start the daemon
|
|
63
|
+
|
|
64
|
+
The daemon hosts MCP-over-HTTP, the REST API, the WebSocket live feed, and the dashboard.
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
active-work mcp serve --detach
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Then visit `http://127.0.0.1:7400/ui` to see the dashboard. Stop it with `active-work mcp stop`.
|
|
71
|
+
|
|
72
|
+
You're done. Open Claude Code in any directory and ask "what am I working on?" — the `active-work` skill should engage and surface your initiatives.
|