@imix-js/taproot 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +30 -0
  2. package/dist/adapters/index.d.ts +11 -1
  3. package/dist/adapters/index.js +113 -1
  4. package/dist/adapters/index.js.map +1 -1
  5. package/dist/cli.js +2 -0
  6. package/dist/cli.js.map +1 -1
  7. package/dist/commands/commithook.js +39 -2
  8. package/dist/commands/commithook.js.map +1 -1
  9. package/dist/commands/dod.d.ts +1 -0
  10. package/dist/commands/dod.js +24 -2
  11. package/dist/commands/dod.js.map +1 -1
  12. package/dist/commands/init.js +46 -1
  13. package/dist/commands/init.js.map +1 -1
  14. package/dist/commands/truth-sign.d.ts +5 -0
  15. package/dist/commands/truth-sign.js +58 -0
  16. package/dist/commands/truth-sign.js.map +1 -0
  17. package/dist/commands/update.js +22 -5
  18. package/dist/commands/update.js.map +1 -1
  19. package/dist/core/configuration.js +25 -0
  20. package/dist/core/configuration.js.map +1 -1
  21. package/dist/core/dod-runner.d.ts +4 -2
  22. package/dist/core/dod-runner.js +30 -1
  23. package/dist/core/dod-runner.js.map +1 -1
  24. package/dist/core/fs-walker.js +1 -1
  25. package/dist/core/fs-walker.js.map +1 -1
  26. package/dist/core/test-cache.d.ts +39 -0
  27. package/dist/core/test-cache.js +187 -0
  28. package/dist/core/test-cache.js.map +1 -0
  29. package/dist/core/truth-checker.d.ts +45 -0
  30. package/dist/core/truth-checker.js +152 -0
  31. package/dist/core/truth-checker.js.map +1 -0
  32. package/dist/validators/types.d.ts +4 -0
  33. package/docs/agents.md +11 -0
  34. package/docs/cli.md +18 -2
  35. package/docs/configuration.md +45 -1
  36. package/docs/patterns.md +75 -0
  37. package/docs/workflows.md +14 -0
  38. package/package.json +1 -1
  39. package/skills/backlog.md +7 -3
  40. package/skills/behaviour.md +7 -1
  41. package/skills/bug.md +13 -0
  42. package/skills/commit.md +27 -2
  43. package/skills/define-truth.md +96 -0
  44. package/skills/discover-truths.md +129 -0
  45. package/skills/grill-me.md +1 -0
  46. package/skills/guide.md +3 -0
  47. package/skills/implement.md +24 -1
  48. package/skills/intent.md +5 -0
  49. package/skills/refine.md +5 -0
  50. package/skills/review-all.md +30 -2
  51. package/skills/review.md +1 -0
  52. package/skills/status.md +3 -2
@@ -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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"}
@@ -0,0 +1,45 @@
1
+ export type TruthScope = 'intent' | 'behaviour' | 'impl';
2
+ export type DocLevel = 'intent' | 'behaviour' | 'impl';
3
+ export interface TruthFile {
4
+ relPath: string;
5
+ scope: TruthScope;
6
+ ambiguous: boolean;
7
+ unreadable: boolean;
8
+ content: string;
9
+ }
10
+ /**
11
+ * Determine scope from a path relative to the global-truths directory.
12
+ * Sub-folder takes precedence over suffix when they conflict (most restrictive wins).
13
+ */
14
+ export declare function resolveTruthScope(relFromGT: string): {
15
+ scope: TruthScope;
16
+ ambiguous: boolean;
17
+ };
18
+ /** Does a truth at scope X apply to a document at level Y? */
19
+ export declare function scopeAppliesTo(truthScope: TruthScope, docLevel: DocLevel): boolean;
20
+ /** Returns the absolute path to global-truths/, or null if it doesn't exist. */
21
+ export declare function globalTruthsDir(cwd: string): string | null;
22
+ /** Get hierarchy level of a file based on its filename. */
23
+ export declare function docLevelFromFilename(filename: string): DocLevel | null;
24
+ /** Collect all truth files applicable to the given document level. Skips README.md. */
25
+ export declare function collectApplicableTruths(cwd: string, docLevel: DocLevel): TruthFile[];
26
+ /**
27
+ * Write a truth-check session marker after the agent has approved the truth check.
28
+ * Called by `taproot truth-sign`.
29
+ */
30
+ export declare function writeTruthSession(cwd: string, stagedDocs: Array<{
31
+ path: string;
32
+ content: string;
33
+ }>, truths: TruthFile[]): void;
34
+ export interface SessionValidation {
35
+ valid: boolean;
36
+ reason: string;
37
+ }
38
+ /**
39
+ * Validate that a truth-check session exists and matches the current staging state.
40
+ * Called by the pre-commit hook.
41
+ */
42
+ export declare function validateTruthSession(cwd: string, stagedDocs: Array<{
43
+ path: string;
44
+ content: string;
45
+ }>, truths: TruthFile[]): SessionValidation;
@@ -0,0 +1,152 @@
1
+ import { createHash } from 'crypto';
2
+ import { existsSync, readFileSync, readdirSync, writeFileSync, mkdirSync } from 'fs';
3
+ import { join, dirname, basename, relative } from 'path';
4
+ const SCOPES = ['intent', 'behaviour', 'impl'];
5
+ /**
6
+ * Determine scope from a path relative to the global-truths directory.
7
+ * Sub-folder takes precedence over suffix when they conflict (most restrictive wins).
8
+ */
9
+ export function resolveTruthScope(relFromGT) {
10
+ const normalised = relFromGT.replace(/\\/g, '/');
11
+ const parts = normalised.split('/');
12
+ const filename = parts[parts.length - 1];
13
+ const topFolder = parts.length > 1 ? parts[0] : null;
14
+ const suffixMatch = filename.match(/_(intent|behaviour|impl)\.md$/);
15
+ const suffixScope = suffixMatch?.[1];
16
+ const folderScope = topFolder && SCOPES.includes(topFolder)
17
+ ? topFolder
18
+ : null;
19
+ // Conflicting signals: sub-folder wins (most restrictive)
20
+ if (folderScope && suffixScope && folderScope !== suffixScope) {
21
+ return { scope: folderScope, ambiguous: false };
22
+ }
23
+ if (folderScope)
24
+ return { scope: folderScope, ambiguous: false };
25
+ if (suffixScope)
26
+ return { scope: suffixScope, ambiguous: false };
27
+ // No scope signal — default to intent (broadest)
28
+ return { scope: 'intent', ambiguous: true };
29
+ }
30
+ /** Does a truth at scope X apply to a document at level Y? */
31
+ export function scopeAppliesTo(truthScope, docLevel) {
32
+ if (truthScope === 'intent')
33
+ return true;
34
+ if (truthScope === 'behaviour')
35
+ return docLevel === 'behaviour' || docLevel === 'impl';
36
+ return docLevel === 'impl'; // impl scope — impl only
37
+ }
38
+ const GLOBAL_TRUTHS_SUBDIR = 'taproot/global-truths';
39
+ /** Returns the absolute path to global-truths/, or null if it doesn't exist. */
40
+ export function globalTruthsDir(cwd) {
41
+ const dir = join(cwd, GLOBAL_TRUTHS_SUBDIR);
42
+ return existsSync(dir) ? dir : null;
43
+ }
44
+ /** Get hierarchy level of a file based on its filename. */
45
+ export function docLevelFromFilename(filename) {
46
+ const name = basename(filename);
47
+ if (name === 'intent.md')
48
+ return 'intent';
49
+ if (name === 'usecase.md')
50
+ return 'behaviour';
51
+ if (name === 'impl.md')
52
+ return 'impl';
53
+ return null;
54
+ }
55
+ /** Collect all truth files applicable to the given document level. Skips README.md. */
56
+ export function collectApplicableTruths(cwd, docLevel) {
57
+ const dir = globalTruthsDir(cwd);
58
+ if (!dir)
59
+ return [];
60
+ const results = [];
61
+ function walk(currentDir, relFromGT) {
62
+ let entries;
63
+ try {
64
+ entries = readdirSync(currentDir, { withFileTypes: true });
65
+ }
66
+ catch {
67
+ return;
68
+ }
69
+ for (const entry of entries) {
70
+ const fullPath = join(currentDir, entry.name);
71
+ const childRel = relFromGT ? `${relFromGT}/${entry.name}` : entry.name;
72
+ if (entry.isDirectory()) {
73
+ walk(fullPath, childRel);
74
+ }
75
+ else if (entry.isFile() && entry.name.endsWith('.md') && entry.name !== 'README.md') {
76
+ const { scope, ambiguous } = resolveTruthScope(childRel);
77
+ if (!scopeAppliesTo(scope, docLevel))
78
+ continue;
79
+ let content = '';
80
+ let unreadable = false;
81
+ try {
82
+ content = readFileSync(fullPath, 'utf-8');
83
+ }
84
+ catch {
85
+ unreadable = true;
86
+ }
87
+ results.push({
88
+ relPath: relative(cwd, fullPath).replace(/\\/g, '/'),
89
+ scope,
90
+ ambiguous,
91
+ unreadable,
92
+ content,
93
+ });
94
+ }
95
+ }
96
+ }
97
+ walk(dir, '');
98
+ return results;
99
+ }
100
+ // ─── Session management ────────────────────────────────────────────────────────
101
+ const SESSION_PATH = '.taproot/.truth-check-session';
102
+ function computeHash(stagedDocs, truths) {
103
+ const h = createHash('sha256');
104
+ for (const d of [...stagedDocs].sort((a, b) => a.path.localeCompare(b.path))) {
105
+ h.update(`doc:${d.path}\x00${d.content}\x00`);
106
+ }
107
+ for (const t of [...truths].sort((a, b) => a.relPath.localeCompare(b.relPath))) {
108
+ h.update(`truth:${t.relPath}\x00${t.content}\x00`);
109
+ }
110
+ return h.digest('hex');
111
+ }
112
+ /**
113
+ * Write a truth-check session marker after the agent has approved the truth check.
114
+ * Called by `taproot truth-sign`.
115
+ */
116
+ export function writeTruthSession(cwd, stagedDocs, truths) {
117
+ const sessionFile = join(cwd, SESSION_PATH);
118
+ mkdirSync(dirname(sessionFile), { recursive: true });
119
+ const session = {
120
+ hash: computeHash(stagedDocs, truths),
121
+ timestamp: new Date().toISOString(),
122
+ };
123
+ writeFileSync(sessionFile, JSON.stringify(session, null, 2) + '\n');
124
+ }
125
+ /**
126
+ * Validate that a truth-check session exists and matches the current staging state.
127
+ * Called by the pre-commit hook.
128
+ */
129
+ export function validateTruthSession(cwd, stagedDocs, truths) {
130
+ const sessionFile = join(cwd, SESSION_PATH);
131
+ if (!existsSync(sessionFile)) {
132
+ return {
133
+ valid: false,
134
+ reason: 'no truth-check session found — run /tr-commit to check truths before committing',
135
+ };
136
+ }
137
+ let session;
138
+ try {
139
+ session = JSON.parse(readFileSync(sessionFile, 'utf-8'));
140
+ }
141
+ catch {
142
+ return { valid: false, reason: 'truth-check session file is malformed — re-run /tr-commit' };
143
+ }
144
+ if (session.hash !== computeHash(stagedDocs, truths)) {
145
+ return {
146
+ valid: false,
147
+ reason: 'staged files or truths have changed since the last truth check — re-run /tr-commit',
148
+ };
149
+ }
150
+ return { valid: true, reason: '' };
151
+ }
152
+ //# sourceMappingURL=truth-checker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"truth-checker.js","sourceRoot":"","sources":["../../src/core/truth-checker.ts"],"names":[],"mappings":"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"}
@@ -68,4 +68,8 @@ export interface TaprootConfig {
68
68
  definitionOfDone?: DodConditionEntry[];
69
69
  definitionOfReady?: DodConditionEntry[];
70
70
  cli?: string;
71
+ autonomous?: boolean;
72
+ testsCommand?: string;
73
+ testResultMaxAge?: number;
74
+ testTimeout?: number;
71
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
@@ -170,7 +170,7 @@ Surfaces unimplemented behaviours as work items, ordered by priority (intents wi
170
170
  ### `taproot dod`
171
171
 
172
172
  ```bash
173
- taproot dod [impl-path] [--dry-run]
173
+ taproot dod [impl-path] [--dry-run] [--rerun-tests]
174
174
  taproot dod <impl-path> --resolve <condition> --note "<text>" [--resolve <condition> --note "<text>" ...]
175
175
  ```
176
176
 
@@ -178,6 +178,8 @@ Runs all configured DoD conditions from `.taproot/settings.yaml` against the spe
178
178
 
179
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.
180
180
 
181
+ Use `--rerun-tests` to force re-execution of `testsCommand` regardless of any cached result. Requires `<impl-path>`.
182
+
181
183
  See [Configuration](configuration.md) for how to define DoD conditions.
182
184
 
183
185
  ---
@@ -196,7 +198,7 @@ The hook uses a three-tier classification, where the implementation tier is dete
196
198
 
197
199
  | Staged files | Gate applied |
198
200
  |---|---|
199
- | Only hierarchy files (`intent.md`, `usecase.md`) | `validate-structure` + `validate-format` hierarchy must be valid before the commit lands |
201
+ | Only hierarchy files (`intent.md`, `usecase.md`) | `validate-structure` + `validate-format` + truth consistency check (if `taproot/global-truths/` contains applicable truths) |
200
202
  | Only `impl.md` (no source files in map) | Definition of Ready — the parent `usecase.md` must be in `specified` state and have `## Flow` and `## Related` sections |
201
203
  | Source files found in map + `impl.md` staged | Verify only `## Status` (and `## DoD Resolutions`) changed in `impl.md`; then run DoD |
202
204
  | Source files found in map but `impl.md` NOT staged | **Blocked** — "Stage `impl.md` alongside your source files. No implementation commit should proceed without its traceability record." |
@@ -204,6 +206,20 @@ The hook uses a three-tier classification, where the implementation tier is dete
204
206
 
205
207
  The DoR gate prevents committing an implementation record before the behaviour is fully specified. The DoD gate prevents marking an implementation complete without passing the quality checks defined in `.taproot/settings.yaml`.
206
208
 
209
+ **Truth consistency check:** when hierarchy files are staged and `taproot/global-truths/` exists, the hook validates that a truth-check session marker (`.taproot/.truth-check-session`) is present and matches the current staged content. This marker is written by `taproot truth-sign`, which `/tr-commit` calls after the agent approves the truth check. Committing hierarchy files directly with `git commit` (bypassing `/tr-commit`) will be blocked if applicable truths exist.
210
+
211
+ ---
212
+
213
+ ### `taproot truth-sign`
214
+
215
+ ```bash
216
+ taproot truth-sign
217
+ ```
218
+
219
+ Records a truth-check session marker after the agent has verified that staged hierarchy documents are consistent with applicable truths in `taproot/global-truths/`. Called automatically by the `/tr-commit` skill — you do not need to invoke this directly unless scripting a custom commit workflow.
220
+
221
+ The session marker is a SHA-256 hash of the staged document contents combined with all applicable truth file contents. If the staged files or truths change after signing, the marker is invalidated and the pre-commit hook will require re-signing.
222
+
207
223
  ---
208
224
 
209
225
  ## Commit Convention
@@ -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.
package/docs/patterns.md CHANGED
@@ -116,3 +116,78 @@ 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 |
162
+
163
+ ---
164
+
165
+ ## Agent-verified pre-commit check (`taproot truth-sign`)
166
+
167
+ **Problem:** A pre-commit hook needs to enforce a quality rule that requires semantic reasoning — something a shell command cannot evaluate alone (e.g. "does this spec contradict any active global truths?"). Running an LLM inside a hook is too slow and requires credentials.
168
+
169
+ **Pattern:** Separate the reasoning from the enforcement:
170
+ 1. The skill performs the semantic check (via the agent) before `git commit` is called
171
+ 2. If the check passes, the skill runs `taproot truth-sign` to write a session marker (`.taproot/.truth-check-session`) containing a SHA-256 hash of the checked content
172
+ 3. The hook validates the marker exists and matches the current staged state — a fast, deterministic check
173
+
174
+ ```bash
175
+ # In the skill (after agent approves):
176
+ taproot truth-sign
177
+
178
+ # In the hook (synchronous, no LLM needed):
179
+ # validateTruthSession(cwd, stagedDocs, truths)
180
+ ```
181
+
182
+ **When to use it:**
183
+ - The quality rule requires reasoning the CLI cannot perform alone
184
+ - The agent is always in the loop (via a skill) before the commit reaches the hook
185
+ - Content-hash binding is sufficient — the hook does not need to know *why* the check passed, only that it did for this exact content
186
+
187
+ **Limitation:** If the developer bypasses the skill and runs `git commit` directly, the hook blocks. This is intentional — the pattern enforces use of the skill as the authoritative commit path.
188
+
189
+ **Taproot's built-in uses:**
190
+
191
+ | Hook check | Written by | Validates |
192
+ |---|---|---|
193
+ | Truth consistency | `taproot truth-sign` (called by `/tr-commit`) | Staged hierarchy docs are consistent with `taproot/global-truths/` |
package/docs/workflows.md CHANGED
@@ -136,6 +136,20 @@ Use `/tr-review` on a fresh spec before starting implementation. Use `/tr-review
136
136
 
137
137
  ---
138
138
 
139
+ ## Surfacing implicit project truths
140
+
141
+ After building up several specs, some domain terms and business rules will recur across specs without being formally captured. Use `/tr-discover-truths` to surface them:
142
+
143
+ ```
144
+ /tr-discover-truths
145
+ ```
146
+
147
+ The skill scans all `intent.md` and `usecase.md` files, identifies recurring terms, business rules, and conventions not yet defined in `taproot/global-truths/`, and presents them as candidates. For each candidate you choose: **promote** (routes to `/tr-ineed` → define-truth), **backlog** (saves to `.taproot/backlog.md` for later), **skip** (reappears next run), or **dismiss** (permanently suppressed).
148
+
149
+ Truth discovery also runs as a final pass inside `/tr-review-all`, appending a `## Truth Candidates` section to the review report.
150
+
151
+ ---
152
+
139
153
  ## When you need to think something through
140
154
 
141
155
  ```