@imix-js/taproot 0.2.1 → 0.4.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.
@@ -0,0 +1,187 @@
1
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, statSync } from 'fs';
2
+ import { join, resolve, relative, dirname } from 'path';
3
+ import { spawn } from 'child_process';
4
+ import { readResolutions } from './dod-runner.js';
5
+ const DEFAULT_TEST_TIMEOUT_MS = 300_000; // 300 seconds
6
+ const DEFAULT_MAX_AGE_MS = 60 * 60 * 1000; // 60 minutes
7
+ /** Derive the cache path from an impl.md path.
8
+ * Strips 'taproot/' prefix and '/impl.md' suffix, maps into .taproot/.test-results/.
9
+ * Handles both absolute and relative implPath inputs. */
10
+ export function deriveCachePath(implPath, cwd) {
11
+ const absImpl = resolve(cwd, implPath);
12
+ const relImpl = relative(cwd, absImpl).replace(/\\/g, '/');
13
+ // Strip leading 'taproot/' segment
14
+ const withoutRoot = relImpl.startsWith('taproot/')
15
+ ? relImpl.slice('taproot/'.length)
16
+ : relImpl;
17
+ // Strip trailing '/impl.md'
18
+ const key = withoutRoot.endsWith('/impl.md')
19
+ ? withoutRoot.slice(0, -'/impl.md'.length)
20
+ : withoutRoot;
21
+ return join(cwd, '.taproot', '.test-results', `${key}.json`);
22
+ }
23
+ /** Read a cached test result. Returns null if absent or malformed. */
24
+ export function readCache(cachePath) {
25
+ if (!existsSync(cachePath))
26
+ return null;
27
+ try {
28
+ const raw = readFileSync(cachePath, 'utf-8');
29
+ const parsed = JSON.parse(raw);
30
+ if (typeof parsed.exitCode !== 'number' || !parsed.timestamp)
31
+ return null;
32
+ return parsed;
33
+ }
34
+ catch {
35
+ return null;
36
+ }
37
+ }
38
+ /** Returns true if the cached result is malformed JSON (exists but unparseable). */
39
+ export function isCacheCorrupt(cachePath) {
40
+ if (!existsSync(cachePath))
41
+ return false;
42
+ try {
43
+ JSON.parse(readFileSync(cachePath, 'utf-8'));
44
+ return false;
45
+ }
46
+ catch {
47
+ return true;
48
+ }
49
+ }
50
+ /** Check whether a cached result is stale.
51
+ * Stale if: any tracked source file is newer than the cache timestamp,
52
+ * or (if no source files listed) the cache is older than testResultMaxAge. */
53
+ export function isCacheStale(cache, implPath, cwd, testResultMaxAgeMs = DEFAULT_MAX_AGE_MS) {
54
+ const absImpl = resolve(cwd, implPath);
55
+ const cacheTs = Date.parse(cache.timestamp);
56
+ if (isNaN(cacheTs))
57
+ return true;
58
+ // Read source files from impl.md
59
+ const sourceFiles = readSourceFiles(absImpl, cwd);
60
+ if (sourceFiles.length === 0) {
61
+ // Fall back to max-age check
62
+ return Date.now() - cacheTs > testResultMaxAgeMs;
63
+ }
64
+ // Stale if any source file is newer than the cache
65
+ for (const src of sourceFiles) {
66
+ const absSrc = resolve(cwd, src);
67
+ if (!existsSync(absSrc))
68
+ continue;
69
+ try {
70
+ const mtime = statSync(absSrc).mtimeMs;
71
+ if (mtime > cacheTs)
72
+ return true;
73
+ }
74
+ catch {
75
+ // ignore stat errors
76
+ }
77
+ }
78
+ return false;
79
+ }
80
+ function readSourceFiles(absImplPath, cwd) {
81
+ try {
82
+ const content = readFileSync(absImplPath, 'utf-8');
83
+ const match = content.match(/^## Source Files\s*\n([\s\S]*?)(?=\n## |\n$|$)/m);
84
+ if (!match)
85
+ return [];
86
+ const sources = [];
87
+ for (const m of match[1].matchAll(/`([^`]+)`/g)) {
88
+ sources.push(m[1]);
89
+ }
90
+ return sources;
91
+ }
92
+ catch {
93
+ return [];
94
+ }
95
+ }
96
+ /** Write a test result to the cache file, creating parent dirs as needed. */
97
+ export function writeCache(cachePath, result) {
98
+ mkdirSync(dirname(cachePath), { recursive: true });
99
+ writeFileSync(cachePath, JSON.stringify(result, null, 2), 'utf-8');
100
+ }
101
+ /** Execute testsCommand, streaming output to the terminal while also capturing it.
102
+ * Returns the TestResult with the last N lines as summary. */
103
+ export async function executeTestCommand(command, cwd, timeoutMs = DEFAULT_TEST_TIMEOUT_MS) {
104
+ return new Promise((resolvePromise) => {
105
+ const timestamp = new Date().toISOString();
106
+ const lines = [];
107
+ const child = spawn(command, { shell: true, cwd });
108
+ let timedOut = false;
109
+ const timer = setTimeout(() => {
110
+ timedOut = true;
111
+ child.kill();
112
+ }, timeoutMs);
113
+ function handleData(chunk, stream) {
114
+ const text = chunk.toString();
115
+ stream.write(text);
116
+ lines.push(...text.split('\n'));
117
+ }
118
+ child.stdout?.on('data', (chunk) => handleData(chunk, process.stdout));
119
+ child.stderr?.on('data', (chunk) => handleData(chunk, process.stderr));
120
+ child.on('close', (code) => {
121
+ clearTimeout(timer);
122
+ const exitCode = timedOut ? -1 : (code ?? 1);
123
+ const timeoutSecs = Math.round(timeoutMs / 1000);
124
+ const summaryLines = timedOut
125
+ ? [`timed out after ${timeoutSecs}s`]
126
+ : lines.filter(l => l.trim()).slice(-20);
127
+ resolvePromise({
128
+ timestamp,
129
+ command,
130
+ exitCode,
131
+ summary: summaryLines.join('\n'),
132
+ });
133
+ });
134
+ child.on('error', (err) => {
135
+ clearTimeout(timer);
136
+ resolvePromise({
137
+ timestamp,
138
+ command,
139
+ exitCode: 127,
140
+ summary: err.message,
141
+ });
142
+ });
143
+ });
144
+ }
145
+ /** Full evidence-backed tests-passing check.
146
+ * Returns { passed, output, correction, warnNoCache } */
147
+ export async function runTestsPassingWithCache(options) {
148
+ const { implPath, cwd, testsCommand, rerunTests } = options;
149
+ const testResultMaxAgeMs = options.testResultMaxAgeMs ?? DEFAULT_MAX_AGE_MS;
150
+ const testTimeoutMs = options.testTimeoutMs ?? DEFAULT_TEST_TIMEOUT_MS;
151
+ const cachePath = deriveCachePath(implPath, cwd);
152
+ // Warn and re-run if cache is corrupt
153
+ if (isCacheCorrupt(cachePath)) {
154
+ process.stderr.write(`Warning: Ignoring malformed cache at ${cachePath} — re-running tests.\n`);
155
+ }
156
+ let result = null;
157
+ if (!rerunTests) {
158
+ result = readCache(cachePath);
159
+ if (result && isCacheStale(result, implPath, cwd, testResultMaxAgeMs)) {
160
+ result = null; // stale — will re-execute
161
+ }
162
+ }
163
+ // Execute if no fresh cache
164
+ if (!result) {
165
+ result = await executeTestCommand(testsCommand, cwd, testTimeoutMs);
166
+ writeCache(cachePath, result);
167
+ }
168
+ if (result.exitCode === 0) {
169
+ return { passed: true, output: '', correction: '' };
170
+ }
171
+ if (result.exitCode === -1) {
172
+ const timeoutSecs = Math.round(testTimeoutMs / 1000);
173
+ return {
174
+ passed: false,
175
+ output: result.summary,
176
+ correction: `tests-passing blocked: test command timed out after ${timeoutSecs}s. Increase testTimeout in .taproot/settings.yaml if needed.`,
177
+ };
178
+ }
179
+ return {
180
+ passed: false,
181
+ output: result.summary,
182
+ correction: 'tests-passing blocked: Fix failing tests and re-run taproot dod.',
183
+ };
184
+ }
185
+ // Re-export for use in commithook staleness check
186
+ export { readResolutions };
187
+ //# sourceMappingURL=test-cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-cache.js","sourceRoot":"","sources":["../../src/core/test-cache.ts"],"names":[],"mappings":"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"}
@@ -40,6 +40,8 @@ export type DodConditionEntry = string | {
40
40
  'check-if-affected-by': string;
41
41
  } | {
42
42
  'check': string;
43
+ } | {
44
+ 'require-discussion-log': boolean;
43
45
  };
44
46
  export interface TaprootConfig {
45
47
  version: number;
@@ -65,4 +67,9 @@ export interface TaprootConfig {
65
67
  };
66
68
  definitionOfDone?: DodConditionEntry[];
67
69
  definitionOfReady?: DodConditionEntry[];
70
+ cli?: string;
71
+ autonomous?: boolean;
72
+ testsCommand?: string;
73
+ testResultMaxAge?: number;
74
+ testTimeout?: number;
68
75
  }
package/docs/agents.md CHANGED
@@ -8,6 +8,7 @@ Taproot works with any AI assistant that can read files. Run `taproot init --age
8
8
  |-------|-------------|---------|---------------------|
9
9
  | Claude Code | **Tier 1** — fully supported | `taproot init --agent claude` | `.claude/commands/tr-*.md` — one file per slash command |
10
10
  | Gemini CLI | **Tier 2** — implemented & tested | `taproot init --agent gemini` | `.gemini/commands/tr-*.toml` — one file per command |
11
+ | Aider | **Tier 2** — implemented & tested | `taproot init --agent aider` | `.aider.conf.yml` with `read:` entries + `CONVENTIONS.md` |
11
12
  | Cursor | **Tier 3** — community supported | `taproot init --agent cursor` | `.cursor/rules/taproot.md` — project-wide rules |
12
13
  | GitHub Copilot | **Tier 3** — community supported | `taproot init --agent copilot` | `.github/copilot-instructions.md` |
13
14
  | Windsurf | **Tier 3** — community supported | `taproot init --agent windsurf` | `.windsurfrules` |
@@ -69,6 +70,16 @@ This refreshes all adapter files and skill definitions to the current version. R
69
70
 
70
71
  ---
71
72
 
73
+ ## Aider
74
+
75
+ The Aider adapter installs `.aider.conf.yml` with `read:` entries so every Aider session automatically loads all taproot skill definitions and `CONVENTIONS.md` as context. No extra flags needed — run `aider` in the project directory and it picks up taproot context immediately.
76
+
77
+ Skills are invoked by natural language: *"implement the behaviour at taproot/my-intent/my-feature/"* rather than a slash command.
78
+
79
+ If `.aider.conf.yml` already exists, `taproot init` merges the `read:` entries without removing your existing settings (model, API key, etc.). If the file contains invalid YAML, the command stops with an error rather than overwriting it.
80
+
81
+ ---
82
+
72
83
  ## Non-Claude agents
73
84
 
74
85
  For Cursor, Copilot, and Windsurf, the adapter installs a rules or instructions file. These give the agent context on the taproot hierarchy, the document formats, and the commit convention — enough to read and navigate the hierarchy, write conformant documents, and use the CLI commands correctly.
package/docs/cli.md CHANGED
@@ -9,11 +9,13 @@ The Taproot CLI handles setup, validation, and reporting. It does not generate c
9
9
  ### `taproot init`
10
10
 
11
11
  ```bash
12
- taproot init [--with-hooks] [--with-ci github|gitlab] [--with-skills] [--agent claude|cursor|copilot|windsurf|generic|all]
12
+ taproot init [--with-hooks] [--with-ci github|gitlab] [--with-skills] [--agent claude|cursor|copilot|windsurf|generic|all] [--template webapp|book-authoring|cli-tool] [--force]
13
13
  ```
14
14
 
15
15
  Initializes Taproot in the current directory. Creates `taproot/` and `.taproot/settings.yaml` if they don't exist, then installs whichever integrations you request.
16
16
 
17
+ When run on an empty project (no `taproot/` directory yet), the command prompts: **"Start from a template? [y/N]"** — entering `y` presents a list of starter hierarchies to choose from.
18
+
17
19
  | Option | Effect |
18
20
  |--------|--------|
19
21
  | `--with-hooks` | Installs `.git/hooks/pre-commit` running `taproot commithook` |
@@ -21,6 +23,8 @@ Initializes Taproot in the current directory. Creates `taproot/` and `.taproot/s
21
23
  | `--with-ci gitlab` | Generates a `taproot-validate` job in `.gitlab-ci.yml` |
22
24
  | `--with-skills` | Copies skill definitions to `.taproot/skills/`. Implied by `--agent claude` — only needed if you want skills without a Claude adapter. |
23
25
  | `--agent <name>` | Generates agent adapter files (see [Agent Setup](agents.md)) |
26
+ | `--template <type>` | Skip the prompt and apply a starter template directly (`webapp`, `book-authoring`, or `cli-tool`). Copies the starter's `taproot/` hierarchy and `.taproot/settings.yaml` into the project. |
27
+ | `--force` | When used with `--template`, overwrites an existing `.taproot/settings.yaml` with the template's version. |
24
28
 
25
29
  Running `taproot init` again on an existing project is safe — it skips files that already exist and reports `exists` for each.
26
30
 
@@ -166,7 +170,7 @@ Surfaces unimplemented behaviours as work items, ordered by priority (intents wi
166
170
  ### `taproot dod`
167
171
 
168
172
  ```bash
169
- taproot dod [impl-path] [--dry-run]
173
+ taproot dod [impl-path] [--dry-run] [--rerun-tests]
170
174
  taproot dod <impl-path> --resolve <condition> --note "<text>" [--resolve <condition> --note "<text>" ...]
171
175
  ```
172
176
 
@@ -174,6 +178,8 @@ Runs all configured DoD conditions from `.taproot/settings.yaml` against the spe
174
178
 
175
179
  Use `--resolve`/`--note` to record agent resolutions for agent-driven conditions (e.g. `document-current`, `check-if-affected`, `check-if-affected-by`). Multiple pairs can be supplied in a single invocation — conditions are paired with notes by position.
176
180
 
181
+ Use `--rerun-tests` to force re-execution of `testsCommand` regardless of any cached result. Requires `<impl-path>`.
182
+
177
183
  See [Configuration](configuration.md) for how to define DoD conditions.
178
184
 
179
185
  ---
@@ -64,7 +64,7 @@ The `definitionOfDone` list controls what `taproot dod` checks and what the pre-
64
64
 
65
65
  | Form | What it does |
66
66
  |------|-------------|
67
- | `tests-passing` | Built-in: runs `npm test` (or `yarn test`). Passes if exit code is 0. |
67
+ | `tests-passing` | Built-in: runs `npm test`. When `testsCommand` is configured in `settings.yaml`, uses evidence-backed execution with a cache file — see [State Transition Guardrails](#state-transition-guardrails). |
68
68
  | `linter-clean` | Built-in: runs `npm run lint`. Passes if exit code is 0. |
69
69
  | `commit-conventions` | Built-in: runs `npm run check:commits`. Passes if exit code is 0. |
70
70
  | `document-current: <description>` | Agent-verified: the agent checks whether the described documentation is current and applies updates if needed. |
@@ -106,6 +106,50 @@ Taproot's own `.taproot/settings.yaml` ships with several `check-if-affected-by`
106
106
 
107
107
  ---
108
108
 
109
+ ## Autonomous Execution
110
+
111
+ Setting `autonomous: true` in `settings.yaml` (or `TAPROOT_AUTONOMOUS=1` / `--autonomous` per invocation) puts all agent skills into non-interactive mode.
112
+
113
+ ```yaml
114
+ autonomous: true # all sessions in this repo run without confirmation prompts
115
+ ```
116
+
117
+ **What autonomous mode changes:**
118
+ - `/tr-implement` proceeds from plan to code without pausing for plan approval
119
+ - `/tr-commit` stages and commits without asking for confirmation when nothing is pre-staged
120
+ - DoD conditions are self-evaluated: resolvable conditions are recorded directly; unresolvable `check:` questions are marked `<!-- autonomous: pending-review -->` in `impl.md`
121
+ - Test failures or hook rejections are recorded in `impl.md` (impl marked `needs-rework`) and the agent stops — the developer returns to a clear failure report
122
+
123
+ **Three activation mechanisms (in order of scope):**
124
+ 1. `autonomous: true` in `.taproot/settings.yaml` — repo-wide, all sessions
125
+ 2. `TAPROOT_AUTONOMOUS=1` environment variable — per process invocation
126
+ 3. `--autonomous` flag on a skill invocation (e.g. `/tr-implement path/ --autonomous`) — per skill
127
+
128
+ When none of these is set, confirmation prompts are shown as normal. Autonomous mode is never inferred from context.
129
+
130
+ ---
131
+
132
+ ## State Transition Guardrails
133
+
134
+ When `testsCommand` is set in `settings.yaml`, the `tests-passing` condition uses evidence-backed execution: it runs the command, caches the result in `.taproot/.test-results/`, and enforces freshness at commit time.
135
+
136
+ ```yaml
137
+ testsCommand: npm test # command to run for evidence-backed tests-passing
138
+ testResultMaxAge: 60 # minutes before a no-source-file cache is stale (default: 60)
139
+ testTimeout: 300 # seconds before testsCommand is killed (default: 300)
140
+ ```
141
+
142
+ **How it works:**
143
+ - `taproot dod <impl-path>` runs `testsCommand`, streams output live, and writes `.taproot/.test-results/<intent>/<behaviour>/<impl>.json`
144
+ - On subsequent runs, the cached result is used if it is not stale (no tracked source files changed since the last run)
145
+ - `taproot dod <impl-path> --rerun-tests` forces re-execution regardless of cache
146
+ - `--resolve "tests-passing"` is rejected when `testsCommand` is configured — evidence is required, not agent assertion
147
+ - The pre-commit hook verifies a fresh passing result exists before allowing an implementation commit with a `complete` impl
148
+
149
+ **Add `.taproot/.test-results/` to `.gitignore`** — the cache is a local execution artifact, not a committed record.
150
+
151
+ ---
152
+
109
153
  ## Definition of Ready
110
154
 
111
155
  The `definitionOfReady` list controls what the pre-commit hook checks when you make a declaration commit (committing `impl.md` without source files). All conditions must pass before the declaration commit is accepted.
@@ -139,6 +183,19 @@ When a `check:` condition is present in `definitionOfReady`, the agent reasons a
139
183
  - condition: check: is this spec complete enough? | note: yes, all flows are specified | resolved: 2026-03-20T10:00:00.000Z
140
184
  ```
141
185
 
186
+ ### `require-discussion-log`
187
+
188
+ Require a `discussion.md` alongside every `impl.md` at declaration commit time. Off by default — enable for teams that want to enforce the record-decision-rationale habit:
189
+
190
+ ```yaml
191
+ definitionOfReady:
192
+ - require-discussion-log: true
193
+ ```
194
+
195
+ When enabled, the pre-commit hook checks whether `discussion.md` exists in the impl folder before accepting the declaration commit. If missing, the commit is rejected with the expected file path. The check is existence-only — content quality is not verified by the hook.
196
+
197
+ See `taproot/requirements-compliance/record-decision-rationale/` for what `discussion.md` should contain and how the agent writes it.
198
+
142
199
  ### When DoR runs
143
200
 
144
201
  DoR runs once: when the declaration commit is made (committing `impl.md` alone, before any source code). It is enforced by the pre-commit hook's declaration tier.
@@ -240,6 +297,26 @@ Applied as a second substitution pass after the language pack (if any). Re-appli
240
297
 
241
298
  ---
242
299
 
300
+ ## CLI invocation
301
+
302
+ ### `cli`
303
+
304
+ Controls which command agents use when executing taproot CLI steps (e.g. `taproot dod`, `taproot link-commits`). The value is injected into agent adapter files as a machine-readable block — agents read it at session start and substitute it wherever a skill step says `taproot <subcommand>`.
305
+
306
+ ```yaml
307
+ cli: taproot
308
+ ```
309
+
310
+ **Default:** `npx @imix-js/taproot` — works in any environment, whether or not taproot is globally installed.
311
+
312
+ **Common overrides:**
313
+ - `cli: taproot` — globally installed (`npm install -g @imix-js/taproot`)
314
+ - `cli: ./node_modules/.bin/taproot` — local `devDependency`
315
+
316
+ **Requires `taproot update`:** yes — the invocation block in agent adapter files is regenerated.
317
+
318
+ ---
319
+
243
320
  ## Validation settings
244
321
 
245
322
  ### `require_dates`
package/docs/patterns.md CHANGED
@@ -116,3 +116,46 @@ The agent reads the question text, reasons whether the answer is yes, no, or not
116
116
  |---|---|
117
117
  | `does this story introduce a cross-cutting concern that warrants a new check-if-affected-by or check-if-affected entry in .taproot/settings.yaml?` | Agent adds the entry to `.taproot/settings.yaml` |
118
118
  | `does this story reveal a reusable pattern worth documenting in docs/patterns.md?` | Agent adds a pattern entry to `docs/patterns.md` |
119
+
120
+ ---
121
+
122
+ ## Autonomous mode preamble (`## Autonomous mode` section in skills)
123
+
124
+ **Problem:** A skill has one or more confirmation prompts (plan approval, staging confirmation, DoD resolution prompts). You want the same skill to work in both interactive and non-interactive (CI, headless agent) contexts without maintaining two versions.
125
+
126
+ **Pattern:** Add an `## Autonomous mode` section at the top of the skill file (before `## Steps`) that tells the agent to check for autonomous mode activation and apply autonomous notes throughout the steps.
127
+
128
+ ```markdown
129
+ ## Autonomous mode
130
+
131
+ Before following any steps, check whether autonomous mode is active:
132
+ - `TAPROOT_AUTONOMOUS=1` is set in the environment, **or**
133
+ - `--autonomous` was passed as an argument to this skill invocation, **or**
134
+ - `.taproot/settings.yaml` contains `autonomous: true`
135
+
136
+ If any of these is true, **autonomous mode is active** — apply the autonomous notes at each step where they appear. If none is true, autonomous mode is **inactive** — show confirmation prompts as normal.
137
+ ```
138
+
139
+ Then at each confirmation step, add a conditional note:
140
+
141
+ ```markdown
142
+ **Interactive mode:** ask "Should I proceed?" and wait for confirmation.
143
+
144
+ **Autonomous mode:** proceed directly without waiting for confirmation.
145
+ ```
146
+
147
+ **When to use it:**
148
+ - The skill has at least one confirmation prompt that would block unattended execution
149
+ - You want CI agents, headless runners, or `TAPROOT_AUTONOMOUS=1` users to be able to run the skill without interaction
150
+ - The skill already exists and works correctly in interactive mode — this is an additive change
151
+
152
+ **When NOT to use it:**
153
+ - The confirmation prompt exists to prevent destructive irreversible actions (e.g. deleting data, force-pushing) — in these cases, autonomous bypass is unsafe
154
+ - The skill is already fully automated (no prompts) — the preamble adds noise without value
155
+
156
+ **Taproot's built-in uses:**
157
+
158
+ | Skill | Prompt bypassed in autonomous mode |
159
+ |---|---|
160
+ | `skills/implement.md` | Plan approval in step 4 |
161
+ | `skills/commit.md` | Staging confirmation in step 3 |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@imix-js/taproot",
3
- "version": "0.2.1",
3
+ "version": "0.4.0",
4
4
  "description": "AI-driven specs, enforced at commit time. Code without traceability doesn't merge.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -39,6 +39,7 @@
39
39
  "dependencies": {
40
40
  "@inquirer/checkbox": "^5.1.2",
41
41
  "@inquirer/confirm": "^6.0.10",
42
+ "@inquirer/select": "^5.1.2",
42
43
  "chalk": "^5.6.2",
43
44
  "commander": "^14.0.3",
44
45
  "js-yaml": "^4.1.1"
@@ -0,0 +1,76 @@
1
+ # Skill: backlog
2
+
3
+ ## Description
4
+
5
+ Capture ideas, findings, and deferred work mid-session with a single command — without interrupting current flow. Called with an argument: captures instantly, no prompts. Called with no argument: opens triage — review each item and discard, keep, or promote to `/tr-ineed`.
6
+
7
+ ## Inputs
8
+
9
+ - `idea` (optional): One-liner text to capture. If omitted, triage mode opens.
10
+
11
+ ## Steps
12
+
13
+ ### Capture mode (argument present)
14
+
15
+ 1. Detect that an argument was provided.
16
+ 2. If the argument is blank or whitespace only: warn *"Nothing to capture — provide a description."* and stop.
17
+ 3. Create `.taproot/backlog.md` if absent. Append the item:
18
+ `- [YYYY-MM-DD] <idea>` using today's date.
19
+ 4. Confirm in one line: *"✓ Captured: <idea>"*
20
+ No follow-up response required from the developer — the session continues.
21
+
22
+ ### Triage mode (no argument)
23
+
24
+ 1. Read `.taproot/backlog.md`.
25
+ - If absent or contains no standard items: report *"Backlog is empty. Use `/tr-backlog <idea>` to capture something."* and stop.
26
+
27
+ 2. Present all standard items as a numbered list (FIFO order, oldest first):
28
+ ```
29
+ Backlog — N items
30
+ 1. [YYYY-MM-DD] item text
31
+ 2. [YYYY-MM-DD] item text
32
+ ...
33
+ ```
34
+ Non-standard lines (not matching `- [YYYY-MM-DD] <text>`) are silently skipped in the display but preserved in the file.
35
+
36
+ 3. Offer: `D <n>` discard · `P <n>` promote to /tr-ineed · `A <n>` analyze · `done` finish
37
+
38
+ 4. Accept commands one at a time:
39
+ - **`D <n>`** — remove item n from `.taproot/backlog.md`. Confirm: *"✓ Discarded #n"*. Redisplay the updated numbered list.
40
+ - **`P <n>` promote to /tr-ineed** — remove item n from `.taproot/backlog.md`. Invoke `/tr-ineed` with the item text. On return, redisplay the updated numbered list.
41
+ - **`A <n>` analyze** — produce a structured analysis of the item:
42
+ - A short description of what the item is or could be (2–4 sentences)
43
+ - A complexity signal: **simple** / **moderate** / **significant**
44
+ - An impact assessment: **minor addition** / **meaningful improvement** / **major capability**
45
+ Then ask: *"[P] Promote to /tr-ineed · [K] Keep · [D] Discard"*. After the choice, redisplay the updated numbered list.
46
+ - **`done`** — end triage. Items not acted on are kept implicitly.
47
+
48
+ 5. After `done`: *"Triage complete — X discarded, Y promoted, Z kept."*
49
+ If any non-standard lines were skipped: *"Skipped N non-standard line(s) — they remain in `.taproot/backlog.md`."*
50
+
51
+ If the developer exits without `done`: unprocessed items remain unchanged. If any actions were taken, show the summary; otherwise continue naturally.
52
+
53
+ 6. Present next steps (triage mode only — skip for capture mode):
54
+
55
+ > 💡 If this session is getting long, consider running `/compact` or starting a fresh context before the next task.
56
+
57
+ **What's next?**
58
+ [A] `/tr-ineed <idea>` — route a kept item into the hierarchy now
59
+ [B] `/tr-status` — see current project health
60
+
61
+ ## Output
62
+
63
+ **Capture:** item written to `.taproot/backlog.md`, one-line confirmation.
64
+ **Triage:** backlog updated in place; promoted items handed to `/tr-ineed`; completion summary shown.
65
+
66
+ ## CLI Dependencies
67
+
68
+ None.
69
+
70
+ ## Notes
71
+
72
+ - Capture is instant — no confirmation, no required fields, no prompts beyond the one-line acknowledgement.
73
+ - Item format: `- [YYYY-MM-DD] <text>`. Items are presented FIFO (oldest first) during triage.
74
+ - Items promoted via `[P]` are removed from the backlog before `/tr-ineed` is invoked. If the developer abandons the `/tr-ineed` discovery, re-capture the item with `/tr-backlog <idea>`.
75
+ - This is a scratchpad, not a project management tool — no priority, labels, or status tracking.
76
+ - Storage is `.taproot/backlog.md` — a committed markdown file inside the taproot config directory, versioned with the project.
@@ -82,6 +82,8 @@ Define a UseCase (observable system behaviour) under an intent or another behavi
82
82
 
83
83
  9. Create the directory `<parent>/<slug>/` and write `usecase.md`.
84
84
 
85
+ 9b. **Optionally write `discussion.md`** — if the session involved meaningful discovery dialogue (scope decisions, alternate flows surfaced, pivotal questions that changed the spec), draft a brief `discussion.md` in the behaviour folder alongside `usecase.md`. Use the same four-section template as `tr-implement` (Pivotal Questions, Alternatives Considered, Decision, Open Questions) but set `Skill: tr-behaviour`. Skip if the spec authoring was straightforward with no significant exploration.
86
+
85
87
  10. Update the parent document's `## Behaviours <!-- taproot-managed -->` section:
86
88
  - Read the parent `intent.md` (or parent `usecase.md` for sub-behaviours).
87
89
  - If the `## Behaviours` section does not exist, insert it immediately before `## Status` (or append at end of file if `## Status` is absent), with the heading `## Behaviours <!-- taproot-managed -->`.
@@ -0,0 +1,84 @@
1
+ # Skill: browse
2
+
3
+ ## Description
4
+
5
+ Read a taproot hierarchy document section by section in the terminal — without opening an external editor. Presents each section one at a time, offers inline editing via `[M] Modify`, and lists what's below at the end. Distinct from `/tr-review` (which is an agent-driven critique); browse is the developer reading the spec themselves.
6
+
7
+ ## Inputs
8
+
9
+ - `path` (required): Path to a hierarchy document (`intent.md`, `usecase.md`, or `impl.md`) or the folder containing one.
10
+
11
+ ## Steps
12
+
13
+ 1. **Resolve the target document.**
14
+ - If `path` points directly to a file (`intent.md`, `usecase.md`, or `impl.md`): use it as-is.
15
+ - If `path` points to a folder: look for `intent.md`, then `usecase.md`, then `impl.md` at that exact folder level (not in subfolders). Child impl folders are children to list later — they are not the target document.
16
+ - If no hierarchy document is found: report *"No intent.md, usecase.md, or impl.md found at `<path>`"* and stop.
17
+ - If the path does not exist: report *"No file found at `<path>` — check the path and try again"* and stop.
18
+
19
+ 2. **Check for discussion.md.** Look for `discussion.md` in the same folder as the resolved document.
20
+ - If present and has substantive content (not just template headings): announce *"📝 Discussion notes found — I'll include context from them where relevant"*
21
+ - If absent or skeleton-only: skip silently — no announcement, no placeholder.
22
+
23
+ 3. **Determine the document type** (`intent.md`, `usecase.md`, or `impl.md`) and identify the section order by reading the document.
24
+
25
+ 4. **Present sections one at a time.** For each section:
26
+
27
+ a. Display the section heading and body, formatted to fit a terminal screen.
28
+
29
+ b. **Discussion context** — if `discussion.md` is present and substantive, insert a `> How we got here:` block at the following anchor (only once per browse session):
30
+ - `usecase.md`: before the `## Main Flow` section
31
+ - `impl.md`: alongside `## Design Decisions`
32
+ - `intent.md`: alongside `## Goal`
33
+
34
+ c. **Long section** — if the section body exceeds approximately 20 lines:
35
+ - Present the first ~20 lines, then offer: `[C] See more | [D] Done with this section`
36
+ - `[C]`: present the next ~20 lines; repeat until exhausted
37
+ - `[D]`: treat the section as complete and continue to step 4d
38
+
39
+ d. After displaying the section (or completing pagination), offer:
40
+
41
+ > `[C] Continue [M] Modify [S] Skip to children`
42
+
43
+ 5. **Handle developer choice:**
44
+
45
+ **[C] Continue** — present the next section (repeat step 4). When all sections are shown, proceed to step 6.
46
+
47
+ **[M] Modify** — ask: *"What would you like to change in this section?"*
48
+ - If the section is `## Commits` or `## DoD Resolutions` in an `impl.md`: first warn *"⚠ This section is managed by `taproot link-commits` / `taproot dod` — manual edits may be overwritten on the next run. Continue?"* and wait for confirmation before proceeding.
49
+ - Developer states the change. Apply it to the file and show the updated section.
50
+ - If the developer says "never mind" or equivalent: confirm *"No changes — continuing"* and return to step 4d.
51
+ - After applying the change, return to step 4d with the updated section displayed.
52
+
53
+ **[S] Skip to children** — skip remaining sections and proceed directly to step 6.
54
+
55
+ 6. **Show what's below:**
56
+ - **intent.md**: list child behaviours — name and relative path to each `usecase.md`
57
+ - **usecase.md**: list implementations — name and relative path to each `impl.md`
58
+ - **impl.md**: *"No children — this is a leaf implementation."*
59
+
60
+ To go deeper into any child, the developer calls `/tr-browse <child-path>`.
61
+
62
+ 7. Present next steps:
63
+
64
+ > 💡 If this session is getting long, consider running `/compact` or starting a fresh context before the next task.
65
+
66
+ **What's next?**
67
+ [A] `/tr-browse <child-path>` — go deeper into one of the listed children
68
+ [B] `/tr-implement <path>/` — start building (if browsing a behaviour spec)
69
+ [C] `/tr-review <path>` — get an agent critique of the spec
70
+
71
+ ## Output
72
+
73
+ The developer has read the document section by section in the terminal. Any `[M]` edits are saved to the file. The developer knows what's below without opening any additional files.
74
+
75
+ ## CLI Dependencies
76
+
77
+ None
78
+
79
+ ## Notes
80
+
81
+ - Browse is read-and-light-edit, not a stress-test. Resist the urge to comment on spec quality — the developer is reading, not asking for a critique. Use `/tr-review` for that.
82
+ - The ~20-line pagination threshold is a guideline. Use judgment — a section with 22 short lines may be fine to show in full; a section with 15 very long lines may need pagination.
83
+ - The `> How we got here:` block is shown once per browse session at the designated anchor, not repeated for every section.
84
+ - After [M] edits, the file on disk is updated but no git staging is performed — the developer commits when ready.
package/skills/bug.md CHANGED
@@ -31,6 +31,19 @@ Diagnose a defect through structured root cause analysis (5-Why) and delegate to
31
31
  - **External cause** — dependency, environment, or configuration outside the hierarchy
32
32
  - If categories overlap, use this priority: **Spec gap > Implementation gap > Missing test**
33
33
 
34
+ 4a. **Recurrence check.** Ask: *"Could this class of bug happen again — is there a missing gate or outdated guideline that would prevent it?"*
35
+
36
+ - **No (clearly one-off** — typo, isolated misconfiguration, external incident): note this and continue to step 5.
37
+ - **Yes**: propose prevention across one or more of:
38
+ - A new DoR or DoD condition to add to `.taproot/settings.yaml`
39
+ - An update to `docs/architecture.md`, `docs/security.md`, or `docs/patterns.md`
40
+
41
+ If a satisfactory measure is found: present it — e.g. *"I'll add `check-if-affected-by: <gate>` to `.taproot/settings.yaml`"* or *"I'll add to `docs/security.md`: `<constraint>`"* — and wait for actor confirmation.
42
+ - On **confirm**: apply the change, then continue to step 5.
43
+ - On **reject**: record the recurrence concern in the implicated impl.md `## Notes` and continue to step 5.
44
+
45
+ If no satisfactory measure can be identified: invoke `/tr-grill-me` seeded with *"How do we prevent `<root-cause>` from recurring?"* — incorporate the answer and apply it before continuing to step 5.
46
+
34
47
  5. **Locate the implicated artifact.** Use reverse lookup:
35
48
  - Scan all `impl.md` files in `taproot/` for `## Source Files` entries matching the files involved in the root cause
36
49
  - If a match is found: that impl.md is implicated