@adhd/backlog 0.1.1 → 0.1.2

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.
@@ -1,5 +1,12 @@
1
1
  export type SkillHost = 'claude' | 'codex' | 'opencode';
2
2
  export type SkillScope = 'user' | 'project';
3
+ export declare const ALL_HOSTS: readonly SkillHost[];
4
+ /** Exported so `install.ts`'s codex MCP-registration path (which needs the
5
+ * SAME `~/.codex` (or `$CODEX_HOME`) root as the skill installer, but the
6
+ * file `config.toml` rather than a `skills/` subdirectory) never
7
+ * re-derives — and risks desyncing from — this resolution rule. */
8
+ export declare function resolveCodexHomeDir(home: string, homeOverride?: string): string;
9
+ export declare function hostSkillsDir(host: SkillHost, scope: SkillScope, cwd: string, homeOverride?: string): string;
3
10
  export interface InstallSkillResult {
4
11
  host: SkillHost;
5
12
  scope: SkillScope;
@@ -20,6 +27,14 @@ export interface InstallSkillResult {
20
27
  * always resolves the genuine machine home directory for `--scope user`.
21
28
  */
22
29
  export declare function installSkill(argv: string[], cwd?: string, homeOverride?: string): InstallSkillResult[];
30
+ /**
31
+ * The actual filesystem work behind `installSkill` — pulled out so
32
+ * `install.ts`'s richer `backlog install` parser (which additionally
33
+ * accepts `--skill-only`/`--mcp-only`) can drive it directly with an
34
+ * already-parsed `{ hosts, scope }` instead of re-parsing (and risking a
35
+ * DIFFERENT `--host`/`--scope` grammar from) a raw argv a second time.
36
+ */
37
+ export declare function installSkillToHosts(hosts: SkillHost[], scope: SkillScope, cwd: string, homeOverride?: string): InstallSkillResult[];
23
38
  /** CLI entry — parses argv, runs the install, prints the same
24
39
  * `console.log(JSON.stringify(...))` shape every other CLI command uses
25
40
  * (BUG-APIGEN-015 parity), so scripting `backlog install-skill` composes
package/install.d.ts ADDED
@@ -0,0 +1,59 @@
1
+ import { InstallSkillResult, SkillHost, SkillScope } from './install-skill.js';
2
+
3
+ export type McpHost = SkillHost;
4
+ export type McpScope = SkillScope;
5
+ /** The one, portable invocation every host's registration entry points at —
6
+ * `npx -y @adhd/backlog@latest serve --transport mcp` — so a written config
7
+ * works on any machine with `npx` and network access, never tied to this
8
+ * monorepo's own local `dist/index.js` path (that local-path form is what
9
+ * THIS repo's own checked-in `.mcp.json` uses for its own dev loop, which is
10
+ * deliberately NOT what a published `install` command should write for a
11
+ * third-party consumer). */
12
+ export declare const BACKLOG_MCP_NPX_ARGS: readonly string[];
13
+ export type McpInstallStatus = 'written' | 'manual';
14
+ export interface McpInstallResult {
15
+ host: McpHost;
16
+ scope: McpScope;
17
+ configPath: string;
18
+ status: McpInstallStatus;
19
+ note?: string;
20
+ }
21
+ /**
22
+ * Replaces (or appends) exactly one `[tableHeader]` table in a TOML
23
+ * document's text, leaving every other line — including every OTHER
24
+ * table — byte-for-byte untouched. Scoped deliberately narrow (see this
25
+ * file's header doc): it finds the line matching `[tableHeader]` exactly,
26
+ * then treats everything up to (but not including) the next line that
27
+ * starts a new table (`[...`) — or end of file — as "this table's body",
28
+ * and swaps that whole span for `newBodyLines`. If the table doesn't exist
29
+ * yet, the new table is appended at the end (separated by a single blank
30
+ * line from any existing content).
31
+ *
32
+ * This is intentionally NOT a general TOML parser/editor — it only
33
+ * recognizes bracketed table headers on their own line, which is exactly
34
+ * (and only) the shape this file ever writes or looks for.
35
+ */
36
+ export declare function upsertTomlTable(text: string, tableHeader: string, newBodyLines: readonly string[]): string;
37
+ export declare const INSTALL_HELP_TEXT = "backlog install [--host claude|opencode|codex|all] [--scope user|project] [--skill-only | --mcp-only]\n\nInstalls the backlog skill AND registers the backlog MCP server into an agent\nhost's config, in one step.\n\n --host <name> claude | opencode | codex | all (default: all)\n --scope <name> user | project (default: user)\n --skill-only only install the skill \u2014 never touch any MCP config\n --mcp-only only register the MCP server \u2014 never touch any skill dir\n\nExamples:\n backlog install\n backlog install --host claude --scope project\n backlog install --mcp-only --host opencode\n\nBack-compat: `backlog install-skill` remains available and behaves exactly\nlike `backlog install --skill-only` (original --host/--scope grammar only).\n";
38
+ export interface InstallResult {
39
+ skill: InstallSkillResult[];
40
+ mcp: McpInstallResult[];
41
+ }
42
+ /**
43
+ * Installs the backlog skill and/or registers the `backlog` MCP server into
44
+ * the requested host config(s), per `--host`/`--scope`/`--skill-only`/
45
+ * `--mcp-only`. Both halves are idempotent and non-destructive: re-running
46
+ * overwrites only the `backlog` skill files / the `backlog` MCP entry,
47
+ * never touching anything else already present.
48
+ *
49
+ * `homeOverride` is a TEST-ISOLATION ESCAPE HATCH ONLY, mirroring
50
+ * `installSkill`'s own parameter — never passed by `runInstallCommand`/the
51
+ * real CLI.
52
+ */
53
+ export declare function install(argv: string[], cwd?: string, homeOverride?: string): InstallResult;
54
+ /** CLI entry for `backlog install` — same `console.log(JSON.stringify(...))`
55
+ * output convention as `install-skill`, plus a short human-readable summary
56
+ * of what was written where (this command's own explicit DoD requirement),
57
+ * printed to stderr so it never disturbs the machine-readable stdout JSON
58
+ * a script might parse. */
59
+ export declare function runInstallCommand(argv: string[]): Promise<void>;
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@adhd/backlog",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "bin": {
5
5
  "backlog": "index.js"
6
6
  },
7
7
  "dependencies": {
8
8
  "@adhd/sox-graph-store": "^0.3.0",
9
9
  "better-sqlite3": "^12.10.0",
10
- "@adhd/environment": "^0.1.1",
10
+ "@adhd/environment": "^0.1.2",
11
11
  "@adhd/environment-base-spec": "^0.1.0",
12
12
  "@adhd/apigen-core-client": "^0.2.1",
13
13
  "@adhd/apigen-plugin-api-fastify": "^0.2.1",
@@ -20,6 +20,9 @@
20
20
  "pino": "10.3.1",
21
21
  "pino-pretty": "13.1.3"
22
22
  },
23
+ "assets": [
24
+ "skill"
25
+ ],
23
26
  "main": "./index.js",
24
27
  "module": "./index.mjs",
25
28
  "typings": "./index.d.ts",
package/skill/SKILL.md ADDED
@@ -0,0 +1,148 @@
1
+ ---
2
+ name: backlog-usage
3
+ description: "Use whenever filing, claiming, transitioning, or resolving a backlog item (bug/debt/feature/investigation) in ANY repo on this machine — via the `backlog` CLI/MCP, never by hand-editing a BACKLOG.md file. Also use to check current migration status before assuming markdown vs. the tool is authoritative. Examples: \"log this bug\", \"file a debt item for the flaky test\", \"claim BUG-042\", \"what's still open in this repo\", \"is BACKLOG.md still the source of truth here\"."
4
+ ---
5
+
6
+ # `@adhd/backlog` usage
7
+
8
+ `@adhd/backlog` is a graph-backed, multi-agent, cross-repo backlog tool. It is
9
+ migrating every repo on this machine off hand-edited `BACKLOG.md` files onto
10
+ itself as the source of truth, with `BACKLOG.md` demoted to a generated,
11
+ git-visible *projection* of the graph. This skill is the ONLY place the
12
+ command surface, protocol, and migration-state mechanism are documented —
13
+ the global `CLAUDE.md`/`AGENTS.md` carry just one pointer line to this file.
14
+
15
+ ## 1. Check migration status FIRST, every time
16
+
17
+ Never trust a hardcoded phase number in this document — it goes stale the
18
+ moment a phase advances. Before deciding whether `BACKLOG.md` or the tool is
19
+ authoritative for the CURRENT repo, run:
20
+
21
+ ```
22
+ backlog migration-status
23
+ ```
24
+
25
+ It reports `{ phase, description, toolIsAuthoritative }`. While
26
+ `toolIsAuthoritative` is `false` (phases `not-started`/`phase-1`/`phase-2`),
27
+ `BACKLOG.md` (root, per-plan, per-package) is still the real source of truth
28
+ in this repo — read/file there by hand as usual, but still prefer `backlog
29
+ list-items`/`backlog create-item` for querying and filing so items are
30
+ visible cross-repo and get FTS/symbol dedup for free. Once `toolIsAuthoritative`
31
+ is `true` (`phase-3` and later), **never hand-edit a `BACKLOG.md` file** —
32
+ every one is a generated projection at that point, and a hand-edit will be
33
+ silently overwritten (or, once the parity gate is blocking, rejected in CI).
34
+
35
+ ## 2. Command surface — 36 ops, one CLI convention
36
+
37
+ Every op takes `ctx` as an implicit first argument (never pass it — the CLI/
38
+ MCP host supplies it). The CLI's own parameter-naming convention (verified
39
+ against real spawned-binary tests, `cli.spec.ts`):
40
+
41
+ - **Scalar parameters** get individual kebab-case flags, named after the
42
+ parameter itself: `getItem(ctx, repo, humanId)` → `backlog get-item --repo
43
+ <repo> --human-id <id>`.
44
+ - **Object-shaped parameters** get a single JSON-blob flag, named after the
45
+ parameter's own name, kebab-cased: `createItem(ctx, input)` → `backlog
46
+ create-item --input '<json>'`.
47
+ - The bin name is never part of `argv` — a bare `create-item …`/`get-item …`
48
+ resolves without any manual namespace prefix.
49
+ - Failures use real process exit codes (never `0` on error — unknown command
50
+ → exit `4`, bad flag → exit `2`); errors are JSON on the last stderr line.
51
+ - Every op is also available as an MCP tool, named `backlog_client_d_<snake_case_op>`
52
+ (e.g. `backlog_client_d_create_item`), once `.mcp.json` wires the server —
53
+ same parameters, same semantics.
54
+
55
+ | `client.ts` export | CLI form |
56
+ |---|---|
57
+ | `createItem(ctx, input)` | `backlog create-item --input '<CreateItemInput json>'` |
58
+ | `getItem(ctx, repo, humanId)` | `backlog get-item --repo <repo> --human-id <id>` |
59
+ | `updateItem(ctx, repo, humanId, patch)` | `backlog update-item --repo <repo> --human-id <id> --patch '<UpdateItemInput json>'` |
60
+ | `listItems(ctx, filter?)` | `backlog list-items --filter '<BacklogFilter json>'` |
61
+ | `softDeleteItem(ctx, repo, humanId, reason)` | `backlog soft-delete-item --repo <repo> --human-id <id> --reason <text>` |
62
+ | `stats(ctx, scope?)` | `backlog stats --scope '<StatsScope json>'` |
63
+ | `spotlight(ctx, scope?, limit?)` | `backlog spotlight --scope '<json>' --limit <n>` |
64
+ | `readyItems(ctx, scope?)` | `backlog ready-items --scope '<json>'` |
65
+ | `blockers(ctx, repo, humanId)` | `backlog blockers --repo <repo> --human-id <id>` |
66
+ | `dependencyGraph(ctx, scope?)` | `backlog dependency-graph --scope '<json>'` |
67
+ | `topoOrder(ctx, scope?)` | `backlog topo-order --scope '<json>'` |
68
+ | `staleClaims(ctx, maxAgeMin, scope?)` | `backlog stale-claims --max-age-min <n> --scope '<json>'` |
69
+ | `claimItem(ctx, repo, humanId, by, opts?)` | `backlog claim-item --repo <repo> --human-id <id> --by <identity> --opts '<ClaimOpts json>'` |
70
+ | `renewClaim(ctx, repo, humanId, by)` | `backlog renew-claim --repo <repo> --human-id <id> --by <identity>` |
71
+ | `releaseClaim(ctx, repo, humanId, by, opts?)` | `backlog release-claim --repo <repo> --human-id <id> --by <identity> --opts '<json>'` |
72
+ | `assignItem(ctx, repo, humanId, to, by)` | `backlog assign-item --repo <repo> --human-id <id> --to <identity> --by <identity>` |
73
+ | `startWork(ctx, repo, humanId, by)` | `backlog start-work --repo <repo> --human-id <id> --by <identity>` |
74
+ | `transitionStatus(ctx, repo, humanId, status, opts)` | `backlog transition-status --repo <repo> --human-id <id> --status <STATUS> --opts '<TransitionOpts json>'` |
75
+ | `addCitation(ctx, repo, humanId, citation)` | `backlog add-citation --repo <repo> --human-id <id> --citation '<Citation json>'` |
76
+ | `appendNote(ctx, repo, humanId, by, text)` | `backlog append-note --repo <repo> --human-id <id> --by <identity> --text <note>` |
77
+ | `resolveItem(ctx, repo, humanId, status, opts)` | `backlog resolve-item --repo <repo> --human-id <id> --status <STATUS> --opts '<json>'` |
78
+ | `archiveResolved(ctx, scope, opts?)` | `backlog archive-resolved --scope '<StatsScope json>' --opts '<ArchiveOpts json>'` |
79
+ | `addDependency(ctx, repo, humanId, dependsOnHumanId)` | `backlog add-dependency --repo <repo> --human-id <id> --depends-on-human-id <id2>` |
80
+ | `removeDependency(ctx, repo, humanId, dependsOnHumanId)` | `backlog remove-dependency --repo <repo> --human-id <id> --depends-on-human-id <id2>` |
81
+ | `linkRelated(ctx, repo, humanIdA, humanIdB)` | `backlog link-related --repo <repo> --human-id-a <id1> --human-id-b <id2>` |
82
+ | `supersedeItem(ctx, repo, oldHumanId, newInput, reason)` | `backlog supersede-item --repo <repo> --old-human-id <id> --new-input '<CreateItemInput json>' --reason <text>` |
83
+ | `splitItem(ctx, repo, parentHumanId, children)` | `backlog split-item --repo <repo> --parent-human-id <id> --children '<CreateItemInput[] json>'` |
84
+ | `mergeItems(ctx, repo, keepHumanId, dropHumanId, reason)` | `backlog merge-items --repo <repo> --keep-human-id <id1> --drop-human-id <id2> --reason <text>` |
85
+ | `setPriority(ctx, repo, humanId, priority)` | `backlog set-priority --repo <repo> --human-id <id> --priority <PRIORITY>` |
86
+ | `attachToPlan(ctx, repo, humanId, planSlug)` | `backlog attach-to-plan --repo <repo> --human-id <id> --plan-slug <slug>` |
87
+ | `importFromMarkdown(ctx, input)` | `backlog import-from-markdown --input '<ImportMarkdownInput json>'` |
88
+ | `renderToMarkdown(ctx, filter?)` | `backlog render-to-markdown --filter '<BacklogFilter json>'` |
89
+ | `exportJson(ctx, filter?)` | `backlog export-json --filter '<json>'` |
90
+ | `auditTrail(ctx, repo, humanId)` | `backlog audit-trail --repo <repo> --human-id <id>` |
91
+ | `migrationStatus(ctx)` | `backlog migration-status` |
92
+ | `setMigrationPhase(ctx, phase)` | `backlog set-migration-phase --phase <PHASE>` (admin-only — only whoever just verified a phase's DoD should call this) |
93
+
94
+ Every `<repo>` value is this machine's stable git-remote-derived slug (e.g.
95
+ `PseudoSky/adhd`) — never a bare directory name. A worktree agent
96
+ (`<repo>/.worktrees/*`) resolves to the SAME main-repo `repo` slug, not a
97
+ phantom per-worktree repo.
98
+
99
+ ## 3. Claim/renew/release protocol (multi-agent use)
100
+
101
+ - Identity is always `${agentName}:${instanceId}` — NEVER a bare role literal
102
+ like `"agent"`. Two concurrent agents both claiming as `"implementer"`
103
+ defeats the CAS protocol entirely.
104
+ - `claimItem` is idempotent for the SAME claimant — always `renewed`, no
105
+ contention check.
106
+ - A long-running task must call `renewClaim` periodically (default
107
+ staleness: 30 minutes).
108
+ - Every exit path (done/error/abandoned) calls `releaseClaim`
109
+ unconditionally — it is a no-op on an already-unclaimed item, never an
110
+ error. Never leave an item claimed after you stop working on it.
111
+
112
+ ## 4. Citations — via tool calls, never hand-typed markdown
113
+
114
+ The `Citation` type (`{ file, lines?, context? }`) is the structured form of
115
+ one bracketed citation entry in the old hand-edited convention. Instead of
116
+ typing a `Citations: [...]` line by hand:
117
+
118
+ - Call `transitionStatus`/`resolveItem` with `{ citations: [...] }` when
119
+ moving into any terminal-done/terminal-workaround status — this is
120
+ REQUIRED and enforced (a status transition without citations there is
121
+ rejected).
122
+ - Call `addCitation` to attach evidence without a status change.
123
+
124
+ ## 5. Dedupe before filing — via the tool, not eyeballing
125
+
126
+ `createItem` runs a dedupe scan (FTS over title+body, plus exact
127
+ symbol/path/errorText metadata match) BEFORE writing, and returns
128
+ `duplicateCandidates` alongside `created: false` when a likely match exists.
129
+ **Always inspect `duplicateCandidates` first.** Only pass `force: true` after
130
+ confirming the candidates are genuinely a distinct issue — never as a way to
131
+ skip reading them.
132
+
133
+ ## 6. Filing a new item — worked example
134
+
135
+ ```
136
+ backlog create-item --input '{
137
+ "family": "BUG-MYAREA",
138
+ "title": "Short, specific summary",
139
+ "body": "Full description, root cause if known, evidence.",
140
+ "repo": "PseudoSky/adhd",
141
+ "projectPath": "packages/domain/my-package",
142
+ "priority": "HIGH"
143
+ }'
144
+ ```
145
+
146
+ If the response has `created: false` with non-empty `duplicateCandidates`,
147
+ read them before deciding whether to `force: true` or update the existing
148
+ item instead (`updateItem`/`appendNote`).