@aperant/framework 0.8.7 → 0.10.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 (111) hide show
  1. package/CHANGELOG.md +142 -0
  2. package/agents/apt-pr-review-fixer.md +9 -4
  3. package/agents/apt-verifier.md +6 -3
  4. package/dist/cli/commands/init.d.mts.map +1 -1
  5. package/dist/cli/commands/init.mjs +157 -23
  6. package/dist/cli/commands/init.mjs.map +1 -1
  7. package/dist/cli/commands/toolchain-detect.d.mts.map +1 -1
  8. package/dist/cli/commands/toolchain-detect.mjs +44 -0
  9. package/dist/cli/commands/toolchain-detect.mjs.map +1 -1
  10. package/dist/cli/commands/triage.d.mts.map +1 -1
  11. package/dist/cli/commands/triage.mjs +346 -13
  12. package/dist/cli/commands/triage.mjs.map +1 -1
  13. package/dist/cli/commands/uninstall.d.mts.map +1 -1
  14. package/dist/cli/commands/uninstall.mjs +11 -8
  15. package/dist/cli/commands/uninstall.mjs.map +1 -1
  16. package/dist/cli/consistency/parse-review.mjs +5 -3
  17. package/dist/cli/consistency/parse-review.mjs.map +1 -1
  18. package/dist/cli/consistency/rules/r5-verdict-consistency.d.mts.map +1 -1
  19. package/dist/cli/consistency/rules/r5-verdict-consistency.mjs +10 -8
  20. package/dist/cli/consistency/rules/r5-verdict-consistency.mjs.map +1 -1
  21. package/dist/cli/gate/gates/review-clean.d.mts.map +1 -1
  22. package/dist/cli/gate/gates/review-clean.mjs +4 -2
  23. package/dist/cli/gate/gates/review-clean.mjs.map +1 -1
  24. package/dist/cli/install/mcp-provision.d.mts +26 -0
  25. package/dist/cli/install/mcp-provision.d.mts.map +1 -0
  26. package/dist/cli/install/mcp-provision.mjs +281 -0
  27. package/dist/cli/install/mcp-provision.mjs.map +1 -0
  28. package/dist/cli/install/mcp-server-specs.d.mts +80 -0
  29. package/dist/cli/install/mcp-server-specs.d.mts.map +1 -0
  30. package/dist/cli/install/mcp-server-specs.mjs +102 -0
  31. package/dist/cli/install/mcp-server-specs.mjs.map +1 -0
  32. package/dist/cli/install/runtime-detect.d.mts +27 -0
  33. package/dist/cli/install/runtime-detect.d.mts.map +1 -1
  34. package/dist/cli/install/runtime-detect.mjs +32 -0
  35. package/dist/cli/install/runtime-detect.mjs.map +1 -1
  36. package/dist/cli/install/toml-merge.d.mts +40 -0
  37. package/dist/cli/install/toml-merge.d.mts.map +1 -0
  38. package/dist/cli/install/toml-merge.mjs +134 -0
  39. package/dist/cli/install/toml-merge.mjs.map +1 -0
  40. package/dist/cli/util/aperant-section.d.mts +12 -0
  41. package/dist/cli/util/aperant-section.d.mts.map +1 -1
  42. package/dist/cli/util/aperant-section.mjs +12 -0
  43. package/dist/cli/util/aperant-section.mjs.map +1 -1
  44. package/dist/cli/util/copy.d.mts +27 -0
  45. package/dist/cli/util/copy.d.mts.map +1 -1
  46. package/dist/cli/util/copy.mjs +49 -5
  47. package/dist/cli/util/copy.mjs.map +1 -1
  48. package/dist/cli/util/events-path.d.mts +8 -0
  49. package/dist/cli/util/events-path.d.mts.map +1 -0
  50. package/dist/cli/util/events-path.mjs +23 -0
  51. package/dist/cli/util/events-path.mjs.map +1 -0
  52. package/dist/cli/util/runtime-capabilities.d.mts +30 -0
  53. package/dist/cli/util/runtime-capabilities.d.mts.map +1 -0
  54. package/dist/cli/util/runtime-capabilities.mjs +214 -0
  55. package/dist/cli/util/runtime-capabilities.mjs.map +1 -0
  56. package/dist/cli/verify-proof/audit.d.mts +2 -7
  57. package/dist/cli/verify-proof/audit.d.mts.map +1 -1
  58. package/dist/cli/verify-proof/audit.mjs +11 -17
  59. package/dist/cli/verify-proof/audit.mjs.map +1 -1
  60. package/dist/cli/verify-proof/idl/index.d.mts +12 -1
  61. package/dist/cli/verify-proof/idl/index.d.mts.map +1 -1
  62. package/dist/cli/verify-proof/idl/index.mjs +50 -1
  63. package/dist/cli/verify-proof/idl/index.mjs.map +1 -1
  64. package/dist/cli/verify-proof/resolver.d.mts.map +1 -1
  65. package/dist/cli/verify-proof/resolver.mjs +51 -1
  66. package/dist/cli/verify-proof/resolver.mjs.map +1 -1
  67. package/dist/cli/verify-proof/runtime-detect.d.mts +2 -29
  68. package/dist/cli/verify-proof/runtime-detect.d.mts.map +1 -1
  69. package/dist/cli/verify-proof/runtime-detect.mjs +8 -203
  70. package/dist/cli/verify-proof/runtime-detect.mjs.map +1 -1
  71. package/dist/plugin/.claude-plugin/plugin.json +1 -1
  72. package/dist/plugin/agents/apt-pr-review-fixer.md +9 -4
  73. package/dist/plugin/agents/apt-verifier.md +6 -3
  74. package/dist/plugin/skills/apt-pr-review/SKILL.md +106 -15
  75. package/dist/plugin/skills/apt-setup/SKILL.md +7 -6
  76. package/dist/plugin/skills/apt-triage/SKILL.md +8 -6
  77. package/dist/plugin/skills/apt-update/SKILL.md +1 -1
  78. package/dist/plugin/skills/apt-verify/SKILL.md +1 -1
  79. package/dist/plugin/skills/apt-verify-proof/SKILL.md +25 -1
  80. package/drivers/browser/skill/agent-browser/SKILL.md +51 -0
  81. package/drivers/tauri/README.md +107 -0
  82. package/drivers/tauri/driver.mjs +88 -0
  83. package/drivers/tauri/manifest.json +37 -0
  84. package/drivers/tauri/scripts/tauri-mcp.sh +41 -0
  85. package/package.json +1 -1
  86. package/skills/apt-pr-review/SKILL.md +106 -15
  87. package/skills/apt-setup/SKILL.md +7 -6
  88. package/skills/apt-triage/SKILL.md +8 -6
  89. package/skills/apt-update/SKILL.md +1 -1
  90. package/skills/apt-verify/SKILL.md +1 -1
  91. package/skills/apt-verify-proof/SKILL.md +25 -1
  92. package/src/cli/commands/init.mjs +163 -19
  93. package/src/cli/commands/toolchain-detect.mjs +44 -0
  94. package/src/cli/commands/triage.mjs +352 -11
  95. package/src/cli/commands/uninstall.mjs +11 -8
  96. package/src/cli/consistency/parse-review.mjs +5 -3
  97. package/src/cli/consistency/rules/r5-verdict-consistency.mjs +14 -9
  98. package/src/cli/gate/gates/review-clean.mjs +4 -2
  99. package/src/cli/install/mcp-provision.mjs +293 -0
  100. package/src/cli/install/mcp-server-specs.mjs +101 -0
  101. package/src/cli/install/runtime-detect.mjs +36 -0
  102. package/src/cli/install/toml-merge.mjs +139 -0
  103. package/src/cli/util/aperant-section.mjs +14 -0
  104. package/src/cli/util/copy.mjs +53 -8
  105. package/src/cli/util/events-path.mjs +24 -0
  106. package/src/cli/util/runtime-capabilities.mjs +216 -0
  107. package/src/cli/verify-proof/audit.mjs +11 -17
  108. package/src/cli/verify-proof/idl/index.mjs +49 -11
  109. package/src/cli/verify-proof/resolver.mjs +49 -2
  110. package/src/cli/verify-proof/runtime-detect.mjs +11 -204
  111. package/templates/config.json +2 -1
@@ -1,209 +1,14 @@
1
1
  /**
2
- * runtime-detect.mjs — pure project-shape capability detection.
2
+ * verify-proof/runtime-detect.mjs — pure project-shape capability detection.
3
3
  *
4
- * Extracted from packages/core/src/prompts/prompt-loader.ts:572-595 so the
5
- * framework CLI is self-contained (no @aperant/core dep). Same heuristics:
6
- * read package.json's dependencies + devDependencies, return a capability
7
- * record the resolver can score against.
8
- *
9
- * Pattern: pure data in → pure data out. No filesystem walking, no exec.
10
- * Use loadRuntimeCapabilities(projectDir) for the cwd-rooted variant.
11
- *
12
- * v0.8.6 — `loadRuntimeCapabilities` now ALSO walks `apps/*` by default
13
- * (configurable via the second arg `{monorepo_globs}`) and OR-merges
14
- * each match's `package.json` capabilities with the root. The default
15
- * `['apps/*']` matches the canonical Aperant monorepo layout; adopters
16
- * whose monorepo lives elsewhere (e.g. `packages/apps/*`) pass a
17
- * custom `monorepo_globs`. Setting `monorepo_globs: []` disables the
18
- * walk entirely (root-only escape hatch).
19
- */
20
- import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
21
- import { join, resolve } from 'node:path';
22
- /**
23
- * @typedef {Object} RuntimeCapabilities
24
- * @property {boolean} is_electron
25
- * @property {boolean} is_tauri
26
- * @property {boolean} is_nextjs
27
- * @property {boolean} is_nuxt
28
- * @property {boolean} is_web_frontend
29
- * @property {boolean} is_expo
30
- * @property {boolean} is_react_native
31
- * @property {boolean} has_node
32
- */
33
- /** A capability record with every flag defaulted to false. */
34
- function emptyCapabilities() {
35
- return {
36
- is_electron: false,
37
- is_tauri: false,
38
- is_nextjs: false,
39
- is_nuxt: false,
40
- is_web_frontend: false,
41
- is_expo: false,
42
- is_react_native: false,
43
- has_node: false,
44
- };
45
- }
46
- /**
47
- * Detect runtime capabilities from a parsed package.json object.
48
- *
49
- * @param {Record<string, unknown> | null} packageJson
50
- * @returns {RuntimeCapabilities}
4
+ * The implementation now lives in util/runtime-capabilities.mjs so the install
5
+ * layer can share it WITHOUT install/ importing from verify-proof/ (the
6
+ * layer-boundary rule). Re-exported here byte-for-byte so every existing
7
+ * `import { } from '../verify-proof/runtime-detect.mjs'` call site (and the
8
+ * `RuntimeCapabilities` typedef references) keep working unchanged.
51
9
  */
52
- export function detectFromPackageJson(packageJson) {
53
- const capabilities = emptyCapabilities();
54
- if (packageJson === null || typeof packageJson !== 'object')
55
- return capabilities;
56
- const allDeps = {
57
- ...(packageJson.dependencies ?? {}) /** @type {Record<string,string>} */,
58
- ...(packageJson.devDependencies ?? {}) /** @type {Record<string,string>} */,
59
- };
60
- const deps = new Set(Object.keys(allDeps).map((k) => k.toLowerCase()));
61
- if (deps.has('electron') || [...deps].some((d) => d.startsWith('@electron'))) {
62
- capabilities.is_electron = true;
63
- }
64
- if (deps.has('@tauri-apps/api') || deps.has('tauri')) {
65
- capabilities.is_tauri = true;
66
- }
67
- if (deps.has('expo'))
68
- capabilities.is_expo = true;
69
- if (deps.has('react-native'))
70
- capabilities.is_react_native = true;
71
- // Web frontends — react/vue/svelte/angular/solid → frontend. BUT: an
72
- // Electron app commonly bundles React as its renderer; in that case the
73
- // runtime is "electron", not "web_frontend". Electron + react-native +
74
- // expo + tauri all win over the web flag.
75
- const isNativeContainer = capabilities.is_electron ||
76
- capabilities.is_tauri ||
77
- capabilities.is_react_native ||
78
- capabilities.is_expo;
79
- const webFrameworks = ['react', 'vue', 'svelte', 'angular', 'solid'];
80
- if (!isNativeContainer && webFrameworks.some((f) => deps.has(f))) {
81
- capabilities.is_web_frontend = true;
82
- }
83
- if (deps.has('next')) {
84
- capabilities.is_nextjs = true;
85
- capabilities.is_web_frontend = true;
86
- }
87
- if (deps.has('nuxt')) {
88
- capabilities.is_nuxt = true;
89
- capabilities.is_web_frontend = true;
90
- }
91
- if (deps.has('vite') && !isNativeContainer) {
92
- capabilities.is_web_frontend = true;
93
- }
94
- // Node-ish? Anything with a package.json + an entry point counts.
95
- if (typeof packageJson.main === 'string' || typeof packageJson.module === 'string') {
96
- capabilities.has_node = true;
97
- }
98
- if (!capabilities.has_node && packageJson.scripts && typeof packageJson.scripts === 'object') {
99
- capabilities.has_node = true;
100
- }
101
- return capabilities;
102
- }
10
+ export { detectFromPackageJson, loadRuntimeCapabilities, } from '../util/runtime-capabilities.mjs';
103
11
  /**
104
- * Load and detect capabilities from a project directory's package.json.
105
- * v0.8.6 — also walks `monorepo_globs` (default `['apps/*']`) and
106
- * OR-merges each match's capabilities with the root.
107
- *
108
- * @param {string} projectDir
109
- * @param {{monorepo_globs?: string[]}} [opts]
110
- * @returns {RuntimeCapabilities}
111
- */
112
- export function loadRuntimeCapabilities(projectDir, opts) {
113
- const root = resolve(projectDir);
114
- const rootPkgPath = join(root, 'package.json');
115
- let rootCaps = emptyCapabilities();
116
- if (existsSync(rootPkgPath)) {
117
- try {
118
- rootCaps = detectFromPackageJson(JSON.parse(readFileSync(rootPkgPath, 'utf-8')));
119
- }
120
- catch {
121
- /* fall through with empty caps */
122
- }
123
- }
124
- // Default to the canonical Aperant monorepo layout. Passing `[]`
125
- // disables the walk entirely (root-only escape hatch per spec ID-05).
126
- const globs = opts && Array.isArray(opts.monorepo_globs) ? opts.monorepo_globs : ['apps/*'];
127
- if (globs.length === 0)
128
- return rootCaps;
129
- let merged = rootCaps;
130
- for (const glob of globs) {
131
- const matches = expandSimpleGlob(root, glob);
132
- for (const dir of matches) {
133
- const childPkgPath = join(dir, 'package.json');
134
- if (!existsSync(childPkgPath))
135
- continue;
136
- try {
137
- const childCaps = detectFromPackageJson(JSON.parse(readFileSync(childPkgPath, 'utf-8')));
138
- merged = orMergeCapabilities(merged, childCaps);
139
- }
140
- catch {
141
- /* skip malformed package.json */
142
- }
143
- }
144
- }
145
- return merged;
146
- }
147
- /**
148
- * Expand a simple `prefix/*` glob against the root. Supports ONLY the
149
- * `<dir>/*` shape (matches each direct subdirectory of `<dir>`). Other
150
- * glob syntax is intentionally rejected — v1 keeps the dep surface
151
- * small. Fails closed on malformed patterns (returns []).
152
- *
153
- * @param {string} root
154
- * @param {string} glob
155
- * @returns {string[]}
156
- */
157
- function expandSimpleGlob(root, glob) {
158
- if (typeof glob !== 'string' || !glob.endsWith('/*'))
159
- return [];
160
- const prefix = glob.slice(0, -2);
161
- if (prefix.length === 0 || prefix.includes('*'))
162
- return [];
163
- const dir = join(root, prefix);
164
- if (!existsSync(dir))
165
- return [];
166
- let entries;
167
- try {
168
- entries = readdirSync(dir);
169
- }
170
- catch {
171
- return [];
172
- }
173
- const matches = [];
174
- for (const name of entries) {
175
- const sub = join(dir, name);
176
- try {
177
- if (statSync(sub).isDirectory())
178
- matches.push(sub);
179
- }
180
- catch {
181
- /* skip unreadable entries */
182
- }
183
- }
184
- return matches;
185
- }
186
- /**
187
- * Boolean OR-merge of two capability records. Any flag true in either
188
- * input is true in the output. The "Electron wins over web" rule from
189
- * `detectFromPackageJson` is preserved when this is called repeatedly —
190
- * each child's flags are honest about that child's own shape, and the
191
- * OR-merge just unions them.
192
- *
193
- * @param {RuntimeCapabilities} a
194
- * @param {RuntimeCapabilities} b
195
- * @returns {RuntimeCapabilities}
12
+ * @typedef {import('../util/runtime-capabilities.mjs').RuntimeCapabilities} RuntimeCapabilities
196
13
  */
197
- function orMergeCapabilities(a, b) {
198
- return {
199
- is_electron: a.is_electron || b.is_electron,
200
- is_tauri: a.is_tauri || b.is_tauri,
201
- is_nextjs: a.is_nextjs || b.is_nextjs,
202
- is_nuxt: a.is_nuxt || b.is_nuxt,
203
- is_web_frontend: a.is_web_frontend || b.is_web_frontend,
204
- is_expo: a.is_expo || b.is_expo,
205
- is_react_native: a.is_react_native || b.is_react_native,
206
- has_node: a.has_node || b.has_node,
207
- };
208
- }
209
14
  //# sourceMappingURL=runtime-detect.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"runtime-detect.mjs","sourceRoot":"","sources":["../../../src/cli/verify-proof/runtime-detect.mjs"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"runtime-detect.mjs","sourceRoot":"","sources":["../../../src/cli/verify-proof/runtime-detect.mjs"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EACN,qBAAqB,EACrB,uBAAuB,GACvB,MAAM,kCAAkC,CAAA;AAEzC;;GAEG"}
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aperant",
3
- "version": "0.8.7",
3
+ "version": "0.10.0",
4
4
  "description": "AI coding framework — composable skills for planning, executing, verifying, and reviewing code with any LLM provider. Works as Claude Code commands, Codex tasks, or programmatically.",
5
5
  "author": {
6
6
  "name": "Mikalsen AI",
@@ -71,9 +71,14 @@ The only valid reasons to skip a finding:
71
71
 
72
72
  ## Coordination Protocol
73
73
 
74
- When `[PARALLEL_FIXERS]` is true, multiple fix agents work in parallel on different file groups.
75
-
76
- 1. **Only edit files in your `[FIX_ASSIGNMENT]`** do NOT touch files assigned to other fixers
74
+ **Fixers run SEQUENTIALLY, never concurrently (FRAMEWORK-BUG-006).** At most one fixer touches the
75
+ shared worktree at any moment — the orchestrator spawns fixer-1, waits for it to return and audits it,
76
+ THEN spawns fixer-2, and so on. You are the ONLY fixer touching this worktree right now. `[PARALLEL_FIXERS]`
77
+ is always `false`; do NOT assume a sibling fixer is editing, staging, or committing at the same time.
78
+ Concurrent `git add`/`git commit` against a shared index either sweeps another fixer's staged files
79
+ into your commit or clobbers their working-tree edits — which is exactly the race this rule prevents.
80
+
81
+ 1. **Only edit files in your `[FIX_ASSIGNMENT]`** — do NOT touch files outside your assigned group (the grouping keeps fixes scoped and the history clean)
77
82
  2. **If a finding spans files across groups** — fix only the parts in your group, note the cross-group dependency in your report
78
83
  3. **If you discover NEW issues** while fixing — note them in your report but do NOT fix them (they aren't validated)
79
84
 
@@ -89,7 +94,7 @@ Findings with severity `critical`:
89
94
 
90
95
  ## Report Format
91
96
 
92
- Write to `[REVIEW_DIR]/iterations/[N]/fixes-applied-[FIXER_NAME].md` (BUG-030 canonical naming — the audit gate resolves `fixes-applied-${FIXER_NAME}.md` ahead of the legacy `fixes-applied.md`, so parallel fixers no longer collide on a shared report path):
97
+ Write to `[REVIEW_DIR]/iterations/[N]/fixes-applied-[FIXER_NAME].md` (BUG-030 canonical naming — the audit gate resolves `fixes-applied-${FIXER_NAME}.md` ahead of the legacy `fixes-applied.md`. Each fixer owns its own report file; this per-fixer path stays correct now that fixers run sequentially — FRAMEWORK-BUG-006):
93
98
 
94
99
  ```markdown
95
100
  # Fix Report — Iteration [N]
@@ -105,9 +105,12 @@ Either shape is accepted:
105
105
 
106
106
  Gate G8 (`consistency-check`) enforces this on ship via rules R4
107
107
  (finding-propagation) and R5 (verdict-consistency):
108
- - `verdict: "approved"` is incompatible with any unresolved `critical`
109
- review finding use `approved-with-notes` as the escape hatch when you
110
- intentionally ship over a flagged finding, and cite it in `findings.notes`.
108
+ - `verdict: "approved"` or `verdict: "approved-with-notes"` is incompatible
109
+ with any unresolved `critical` or `major` review finding (G42 Phase 1).
110
+ Neither verdict escapes R5 for blocking-severity findings. The sole bypass
111
+ is `--force --reason "<why>"` on the gate CLI (audit-logged). For
112
+ non-blocking findings (suggestion/note/info/nitpick), cite them in
113
+ `findings.notes` and use `approved-with-notes`.
111
114
  - `verdict: "rejected"` requires at least one cited finding (rejected
112
115
  without evidence is treated as drift).
113
116
 
@@ -65,11 +65,12 @@ PHASE 3 -- PARALLEL REVIEW (6 agents, independent, all spawned in ONE message):
65
65
  PHASE 4 -- VALIDATION (1 agent):
66
66
  validator -> Re-investigates every finding, confirms or dismisses
67
67
 
68
- PHASE 6 -- FIX LOOP (up to 3 agents per iteration, max 4 iterations):
69
- fixer-1, fixer-2, fixer-3
68
+ PHASE 6 -- FIX LOOP (up to 3 fixers per iteration, spawned ONE AT A TIME, max 4 iterations):
69
+ fixer-1, fixer-2, fixer-3 (groups still exist; they run SEQUENTIALLY, never concurrently)
70
70
  - Read validated finding files directly
71
- - Fix assigned issues, run verification, stage changes
71
+ - Fix assigned issues, run verification, commit atomically per finding
72
72
  - Report brief status to orchestrator
73
+ - NEVER run pr-review fixers in parallel in a shared worktree (FRAMEWORK-BUG-006)
73
74
 
74
75
  PHASE 7 -- SELF-REVIEW (1 fresh agent):
75
76
  self-reviewer
@@ -468,6 +469,55 @@ fi
468
469
 
469
470
  After this point, the per-round artifact root is `.aperant/pr-reviews/pr-${PR_NUMBER}/rounds/${ROUND}/` (the `${REVIEW_ID}` is still used for the git worktree branch and temporary files). Subsequent `git diff` invocations within the round use `${PINNED_HEAD_SHA}` as the head ref.
470
471
 
472
+ ### Step 4c: Mergeability Pre-Check (PR reviews only)
473
+
474
+ **Why this exists.** The review diff is `git diff {BASE}...{HEAD}` (three-dot — from the *merge-base*), so it shows the PR's own changes relative to where it branched. It is **blind to drift on `{BASE}` since then** and therefore says nothing about whether the PR still merges cleanly. A PR can pass a glowing review and be unmergeable. This step closes that gap: it is **informational, never blocking** (the review still runs in full), but a conflicting/stale state MUST be surfaced so the reader doesn't trust a review of a diff that no longer applies.
475
+
476
+ ```bash
477
+ # PR-mode only. Query GitHub's authoritative mergeability + measure drift.
478
+ if [ -n "$PR_NUMBER" ]; then
479
+ MERGE_JSON=$(gh pr view ${PR_NUMBER} --json mergeable,mergeStateStatus,baseRefName 2>/dev/null)
480
+ MERGEABLE=$(echo "$MERGE_JSON" | jq -r '.mergeable // "UNKNOWN"')
481
+ MERGE_STATE=$(echo "$MERGE_JSON" | jq -r '.mergeStateStatus // "UNKNOWN"')
482
+ BASE_REF=$(echo "$MERGE_JSON" | jq -r '.baseRefName // "main"')
483
+ git fetch origin "${BASE_REF}" 2>/dev/null
484
+ BEHIND=$(git rev-list --count ${PINNED_HEAD_SHA}..origin/${BASE_REF} 2>/dev/null || echo "?")
485
+ AHEAD=$(git rev-list --count origin/${BASE_REF}..${PINNED_HEAD_SHA} 2>/dev/null || echo "?")
486
+
487
+ # If GitHub reports a conflict (or hasn't computed yet), enumerate the conflicting
488
+ # files locally without touching any working tree (git >= 2.38):
489
+ CONFLICT_FILES=""
490
+ if [ "$MERGEABLE" = "CONFLICTING" ] || [ "$MERGE_STATE" = "DIRTY" ]; then
491
+ CONFLICT_FILES=$(git merge-tree --write-tree --name-only origin/${BASE_REF} ${PINNED_HEAD_SHA} 2>/dev/null \
492
+ | tail -n +2 | sed '/^$/d' | grep '/' )
493
+ fi
494
+ fi
495
+ ```
496
+
497
+ Record the result in `metadata.json` under a `mergeability` key:
498
+
499
+ ```json
500
+ {
501
+ "mergeability": {
502
+ "mergeable": "CONFLICTING | MERGEABLE | UNKNOWN",
503
+ "merge_state_status": "DIRTY | CLEAN | BLOCKED | BEHIND | ...",
504
+ "base_ref": "main",
505
+ "commits_behind_base": 101,
506
+ "commits_ahead_base": 91,
507
+ "conflicting_files": ["apps/.../App.tsx", "..."]
508
+ }
509
+ }
510
+ ```
511
+
512
+ **How to use the result:**
513
+
514
+ - `MERGEABLE` ∈ {`CONFLICTING`} OR `MERGE_STATE` = `DIRTY` → the PR has conflicts. Surface a **⚠️ Mergeability** block in the Phase 5 intermediate report AND the Phase 8 final report, listing `commits_behind_base` and the `conflicting_files`. In **external / comment-only** mode, include the same block in the Phase 6b comment. State plainly that the branch needs a rebase/merge from `{BASE}` and that **the reviewed diff may be stale** with respect to current `{BASE}`.
515
+ - `MERGE_STATE` = `BEHIND` (mergeable but behind) → note the drift (`commits_behind_base`) as a softer advisory; no conflict, but the diff predates recent `{BASE}` commits.
516
+ - `MERGEABLE` = `UNKNOWN` (GitHub hasn't finished computing, common right after a push) → record `UNKNOWN`; optionally re-poll once after a few seconds. Do not block.
517
+ - `MERGEABLE` = `MERGEABLE`, `MERGE_STATE` = `CLEAN` → record it and move on silently.
518
+
519
+ This step never changes the review's findings or assessment — a conflicting PR with zero code findings is still `approve` on its own merits. Mergeability is reported as a **separate** signal so the reader knows whether the approval can actually land.
520
+
471
521
  ### Step 5: Read All Changed Files
472
522
 
473
523
  For every file in the changed file list, read it in full using the Read tool **from the worktree**:
@@ -594,6 +644,16 @@ Construct a toolchain commands object:
594
644
 
595
645
  Save to `.aperant/pr-reviews/${REVIEW_ID}/toolchain.json`.
596
646
 
647
+ **Cache-bypass variant (FRAMEWORK-BUG-006 Part B).** `toolchain-detect` emits an additive
648
+ `no_cache_command` field on `typecheck` (and `test`) when the project's verify command is turbo-backed
649
+ (e.g. `typecheck: { tool, command, no_cache_command: "pnpm run typecheck --force" }`). A turbo-backed
650
+ verify can return a STALE CACHE HIT ("replaying logs") that reports PASS while a fixer-introduced broken
651
+ import sits on disk — sighted on PR #244, where only a `--force` (no-cache) typecheck exposed it.
652
+ **Whenever a `no_cache_command` exists for a check, every POST-FIX verification (auto-checks re-runs,
653
+ the Phase 6 fixer verification, and the Phase 7 self-reviewer) MUST use it instead of the plain
654
+ `command`** so a stale cache cannot mask a broken import. Non-turbo projects have no `no_cache_command`
655
+ (plain `tsc`/`vitest` have no cache to bust) and use the plain command unchanged.
656
+
597
657
  Update `metadata.json` with the toolchain summary and trigger count.
598
658
 
599
659
  ---
@@ -663,8 +723,8 @@ cd {WORKTREE_PATH}
663
723
  ```
664
724
 
665
725
  1. Lint: {LINT_COMMAND}
666
- 2. Typecheck: {TYPECHECK_COMMAND}
667
- 3. Tests: {TEST_COMMAND}
726
+ 2. Typecheck: {TYPECHECK_COMMAND} (use toolchain.json `typecheck.no_cache_command` if present — FRAMEWORK-BUG-006: a turbo-backed typecheck can replay a stale cached PASS and mask a broken import)
727
+ 3. Tests: {TEST_COMMAND} (use toolchain.json `test.no_cache_command` if present, same cache-bypass rationale)
668
728
 
669
729
  ## Output
670
730
 
@@ -1047,6 +1107,10 @@ Review Synthesis (Before Auto-Fix)
1047
1107
  Auto-Checks: {PASS/FAIL}
1048
1108
  lint: {P/F} | typecheck: {P/F} | tests: {P/F}
1049
1109
 
1110
+ Mergeability (PR reviews — from Step 4c): {MERGEABLE/CONFLICTING/UNKNOWN}
1111
+ {if CONFLICTING/DIRTY: "⚠️ conflicts with {BASE} — {behind} commits behind — needs rebase — reviewed diff may be stale"}
1112
+ {if conflicting_files: list them}
1113
+
1050
1114
  Validated Findings:
1051
1115
  CRITICAL: {N}
1052
1116
  HIGH: {N}
@@ -1171,8 +1235,9 @@ This is the owned-mode path. External-mode invocations run Phase 6b instead of t
1171
1235
  for ITERATION in 1 2 3 4:
1172
1236
  1. Create iteration directory
1173
1237
  2. Group findings into fix assignments (max 3 groups)
1174
- 3. Spawn fix agents
1175
- 4. Wait for completion
1238
+ 3. Spawn fix agents ONE AT A TIME (never in parallel — FRAMEWORK-BUG-006):
1239
+ for each group: spawn fixer → wait → audit (Step 4b) → next group
1240
+ 4. (folded into step 3's per-fixer wait)
1176
1241
  5. Generate new diff
1177
1242
  6. If fixes were applied: spawn fresh review on new diff
1178
1243
  7. If ITERATION <= 2: accept new findings into the backlog
@@ -1197,6 +1262,14 @@ Take all confirmed, fixable findings and group them into up to 3 independent gro
1197
1262
 
1198
1263
  #### Step 3: Spawn Fix Agents
1199
1264
 
1265
+ > **HARD RULE (FRAMEWORK-BUG-006): NEVER run pr-review fixers in parallel in a shared worktree.**
1266
+ > All fixers share ONE worktree, ONE git index, and ONE branch. git's index is not a
1267
+ > concurrency-safe surface — concurrent `git add` / `git commit` / self-audit `git log`/`git diff`
1268
+ > from sibling fixers either SWEEP one fixer's staged files into another's commit (PR #114
1269
+ > entanglement) or CLOBBER another fixer's working-tree edits entirely (PR #244 work-loss). Spawn
1270
+ > fixers **one at a time**: spawn fixer-1, wait for it to return, run the Step 4b audit-fixer gate on
1271
+ > it, THEN spawn fixer-2, and so on. At most one fixer touches the shared worktree at any moment.
1272
+
1200
1273
  **Capture the iteration-start SHA BEFORE spawning fixers** (used by both the Step 4b audit gate AND the per-fixer Pre-Return Self-Audit added in BUG-016 — fixers now commit atomically per finding, and the audit window is scoped by this SHA):
1201
1274
 
1202
1275
  ```bash
@@ -1211,9 +1284,9 @@ For each fix group, spawn a fix agent. Read the agent definition from `.claude/a
1211
1284
  | `[FIX_ASSIGNMENT]` | List of finding IDs and files for this group |
1212
1285
  | `[N]` | Current iteration number |
1213
1286
  | `[FIXER_NAME]` | `fixer-1`, `fixer-2`, or `fixer-3` |
1214
- | `[TOOLCHAIN_COMMANDS]` | Commands from `toolchain.json` (the `all` command, or individual commands) |
1287
+ | `[TOOLCHAIN_COMMANDS]` | Commands from `toolchain.json` (the `all` command, or individual commands). **For the post-fix typecheck/test, pass the `no_cache_command` variant when `toolchain.json` provides one** (FRAMEWORK-BUG-006: a turbo-backed verify can replay a stale cached PASS and mask the fixer's own broken import). |
1215
1288
  | `[CRITICAL_APPROVAL]` | `true` or `false` for each critical finding in this group |
1216
- | `[PARALLEL_FIXERS]` | `true` if more than 1 fixer is spawned this iteration |
1289
+ | `[PARALLEL_FIXERS]` | Always `false` (FRAMEWORK-BUG-006). Fixers are spawned one at a time in the shared worktree — never concurrently. The placeholder is retained only so the fixer-agent Coordination Protocol can assert "you are the only fixer touching this worktree right now." |
1217
1290
  | `[WORKTREE_PATH]` | The worktree path — fix agents edit files HERE, not in the user's working directory |
1218
1291
  | `[ITERATION_START_SHA]` | The SHA captured BEFORE spawning fixers (see Step 4b's `ITERATION_START_SHA=$(...)` capture). Required for BUG-016 commit-discipline Pre-Return Self-Audit. |
1219
1292
 
@@ -1226,18 +1299,31 @@ Agent(
1226
1299
  )
1227
1300
  ```
1228
1301
 
1229
- If there are 2-3 groups, spawn all fix agents in ONE message for parallel execution.
1302
+ If there are 2-3 groups, spawn the fixers **sequentially one fixer at a time, never in parallel**
1303
+ (FRAMEWORK-BUG-006). Concretely, loop over the groups:
1304
+
1305
+ ```
1306
+ for each fix group (in order):
1307
+ 1. Spawn the fixer for THIS group only (a single Agent call in its own message)
1308
+ 2. Wait for it to return its brief status line (Step 4)
1309
+ 3. Run the Step 4b audit-fixer gate on it
1310
+ 4. Only THEN spawn the fixer for the next group
1311
+ ```
1312
+
1313
+ Do NOT batch multiple `Agent(...)` fixer calls into one message — that is the parallel-shared-worktree
1314
+ race this bug fixes. The atomic-per-finding commit discipline (BUG-016) and the Step 4b audit gate are
1315
+ UNCHANGED; they now simply run without a concurrent sibling mutating the index.
1230
1316
 
1231
1317
  #### Step 4: Wait for Completion
1232
1318
 
1233
- Wait for all fix agents to return their brief status:
1319
+ Wait for the **current** fix agent to return its brief status (you spawned only one this turn):
1234
1320
  ```
1235
1321
  FIXED: X | FAILED: Y | SKIPPED: Z | VERIFICATION: PASS/FAIL
1236
1322
  ```
1237
1323
 
1238
1324
  #### Step 4b: Audit Fixer Output (Hallucination Gate)
1239
1325
 
1240
- On 2026-04-20 a live fix agent returned `FIXED: 2 | FAILED: 0 | SKIPPED: 0 | VERIFICATION: PASS` and wrote a convincing 140-line `fixes-applied.md` — but the worktree had zero edits matching the claimed files. The orchestrator trusted the self-reported status line and nearly shipped two fabricated fixes. This step is the deterministic gate that closes that hole: for EACH fixer that returned in Step 4, run the audit CLI and cross-check the fixer's claims against the actual git state.
1326
+ On 2026-04-20 a live fix agent returned `FIXED: 2 | FAILED: 0 | SKIPPED: 0 | VERIFICATION: PASS` and wrote a convincing 140-line `fixes-applied.md` — but the worktree had zero edits matching the claimed files. The orchestrator trusted the self-reported status line and nearly shipped two fabricated fixes. This step is the deterministic gate that closes that hole: for the fixer that just returned in Step 4 (fixers run one at a time — FRAMEWORK-BUG-006), run the audit CLI and cross-check the fixer's claims against the actual git state BEFORE spawning the next group's fixer.
1241
1327
 
1242
1328
  **Capture the iteration-start SHA BEFORE spawning fixers** so the audit can cover committed fixes (bug-5, 2026-04-23 — fixers that commit their edits before returning would otherwise present an empty working tree and trip a false-positive hallucination verdict):
1243
1329
 
@@ -1271,7 +1357,7 @@ Rule of thumb: pass `--since-sha "${ITERATION_START_SHA}"` on every audit invoca
1271
1357
 
1272
1358
  The CLI returns a JSON envelope with `verdict` ∈ `verified | hallucinated | skipped`, writes `iterations/${ITERATION}/audit.json` (append-only), and bumps `metadata.json.fixer_hallucinations_total` on every `hallucinated` outcome. The envelope's `observed.since_sha` + `observed.since_sha_source` fields record which SHA was used and where it came from (`flag` | `merge-base` | `null`). The same envelope is used for the fixer and the self-reviewer (Phase 7) — `--fixer self-reviewer` reads from `self-review.md`.
1273
1359
 
1274
- **Canonical fix-report filename per fixer (BUG-030):** each fixer agent writes to `fixes-applied-${FIXER_NAME}.md` (e.g. `fixes-applied-fixer-1.md`). The audit CLI resolves candidates in strict order: `fixes-applied-group{N}.md` (legacy group-named, pre-BUG-030 reviews) → `fixes-applied-fixer-{N}.md` (NEW fixer-named) → `fixes-applied-${FIXER_NAME}.md` (exact match, preferred for non-numeric fixer names) → `fixes-applied.md` (legacy unscoped, single-fixer reviews only). Writing the canonical name eliminates the cross-fixer false-positive `hallucinated` verdict that fired when two parallel fixers shared `fixes-applied.md`.
1360
+ **Canonical fix-report filename per fixer (BUG-030):** each fixer agent writes to `fixes-applied-${FIXER_NAME}.md` (e.g. `fixes-applied-fixer-1.md`). The audit CLI resolves candidates in strict order: `fixes-applied-group{N}.md` (legacy group-named, pre-BUG-030 reviews) → `fixes-applied-fixer-{N}.md` (NEW fixer-named) → `fixes-applied-${FIXER_NAME}.md` (exact match, preferred for non-numeric fixer names) → `fixes-applied.md` (legacy unscoped, single-fixer reviews only). Writing the canonical name eliminates the cross-fixer false-positive `hallucinated` verdict that fired when fixers shared `fixes-applied.md` (a per-fixer report path is still correct now that fixers run sequentially — each fixer owns its own report file).
1275
1361
 
1276
1362
  | Verdict | Orchestrator Action |
1277
1363
  |---------|---------------------|
@@ -1430,7 +1516,7 @@ Read the agent definition from `.claude/agents/apt-pr-review-self-reviewer.md` a
1430
1516
  |-------------|-------|
1431
1517
  | `[FIX_DIFF]` | The combined fix diff content or path to the concatenated file |
1432
1518
  | `[REVIEW_DIR]` | `.aperant/pr-reviews/{REVIEW_ID}` |
1433
- | `[TOOLCHAIN_COMMANDS]` | Commands from `toolchain.json` |
1519
+ | `[TOOLCHAIN_COMMANDS]` | Commands from `toolchain.json`. **Use the `no_cache_command` variant for the typecheck/test when one is present** (FRAMEWORK-BUG-006: a turbo-backed verify can replay a stale cached PASS and mask a broken import the fixes introduced). |
1434
1520
  | `[WORKTREE_PATH]` | The worktree path — self-reviewer works HERE |
1435
1521
 
1436
1522
  ```
@@ -1597,6 +1683,7 @@ Output the human-readable summary:
1597
1683
 
1598
1684
  Review ID: {REVIEW_ID}
1599
1685
  Assessment: {APPROVE / REQUEST CHANGES / COMMENT}
1686
+ Mergeability: {MERGEABLE / CONFLICTING (N behind {BASE}, needs rebase) / UNKNOWN} # PR reviews — from Step 4c / metadata.json.mergeability
1600
1687
 
1601
1688
  Scores:
1602
1689
  Security: {score}/10
@@ -1683,11 +1770,15 @@ Fix agents self-report their outcome via a status line (`FIXED: X | FAILED: Y |
1683
1770
  **Wrong:** Reading the full finding text from agent responses and processing it in orchestrator context.
1684
1771
  **Right:** Agent responses are brief (3-5 lines). All detail goes to files. Orchestrator reads files via Read tool only when needed.
1685
1772
 
1686
- ### Mistake 2: Spawning Agents Sequentially
1773
+ ### Mistake 2: Spawning the Phase 3 REVIEW Agents Sequentially
1687
1774
 
1688
1775
  **Wrong:** Spawn security agent, wait, spawn quality agent, wait, etc.
1689
1776
  **Right:** Spawn all 6 review agents in ONE message using 6 parallel Agent tool calls. They run concurrently.
1690
1777
 
1778
+ > Scope note: this applies ONLY to the Phase 3 review specialists, which are read-only (Read/Grep/Glob,
1779
+ > no Edit/Bash, no git writes) and therefore cannot race the index. The Phase 6 FIXERS are the
1780
+ > opposite — they MUST be spawned one at a time (FRAMEWORK-BUG-006). Never parallelize fixers.
1781
+
1691
1782
  ### Mistake 3: Skipping the Validator
1692
1783
 
1693
1784
  **Wrong:** Sending raw review findings directly to fix agents.
@@ -966,8 +966,9 @@ ST-3 apt:execute --tdd).
966
966
 
967
967
  Per ID-01 (Framework full-featured locally; backends are pluggable), the
968
968
  default is `local-only` — zero GitHub auth required on first run. Users
969
- can switch to `github-issues` (v1 stub) or `app-inbox` (v1 stub) later;
970
- the framework abstraction is open for v2 adapter implementations.
969
+ can switch to `github-issues` (implemented creates + syncs real issues
970
+ via the `gh` CLI) or `app-inbox` (still a stub) later; the framework
971
+ abstraction is open for further adapter implementations.
971
972
 
972
973
  ### 3g.1 Present the task-tracking picker
973
974
 
@@ -979,10 +980,10 @@ AskUserQuestion([
979
980
  options: [
980
981
  { label: "Local files only (recommended for solo / 2-founder)",
981
982
  description: "Writes to .aperant/tasks/{id}/triage.json. Zero net dep." },
982
- { label: "GitHub Issues (v2 stub returns not-implemented in v1)",
983
- description: "v1 ships the adapter shape only; full implementation in v2." },
984
- { label: "App inbox (post-app-launch — stub in v1)",
985
- description: "Convex-backed; same state machine, app-rendered." }
983
+ { label: "GitHub Issues (creates + syncs real issues via the gh CLI)",
984
+ description: "First mirror creates an issue; transitions update the same issue (label swap), wontfix closes it as not-planned. Requires gh installed + authed + a git remote." },
985
+ { label: "App inbox (post-app-launch — stub)",
986
+ description: "Convex-backed; same state machine, app-rendered. Still a stub." }
986
987
  ]
987
988
  },
988
989
  {
@@ -127,16 +127,18 @@ needed.
127
127
 
128
128
  `.aperant/config.json.task_tracking.backend` selects the mirror:
129
129
 
130
- | Backend | v1 behavior |
130
+ | Backend | behavior |
131
131
  |---|---|
132
132
  | `local-only` (default) | State mirrors to `.aperant/tasks/{task-id}/triage.json` only. Zero network dependency. |
133
- | `github-issues` | Returns `{status: 'not-implemented', backend: 'github-issues', v2_issue: '#TBD'}`. Implementation deferred to v2. |
134
- | `app-inbox` | Same as github-issues stub returning not-implemented. |
133
+ | `github-issues` | Mirrors triage state to a real GitHub issue via the `gh` CLI. The first mirror CREATES an issue (`triage: <task-id>`, labelled `<category>` + `<vocab>:<state>`); subsequent transitions UPDATE the same issue (label swap), and `wontfix` CLOSES it as "not planned". The issue pointer (`github.{issue_number, issue_url, repo}`) persists on `triage.json` so transitions stay idempotent — no duplicates. Honors `task_tracking.tracker_label_vocabulary` (default → `triage:<state>`). Requires `gh` installed + authenticated + a resolvable git remote; otherwise returns a structured error (`reason: gh-unavailable | gh-unauthenticated | no-remote | gh-command-failed`) and exits non-zero — never a crash/stacktrace. Label ops are best-effort: an unknown label degrades to a `warnings[]` entry while the open/closed state change still lands. |
134
+ | `app-inbox` | Still a stub: returns `{status: 'not-implemented', backend: 'app-inbox', v2_issue: '#TBD'}`. Belongs to a future Convex-backed task (post-app-launch). |
135
135
 
136
136
  Skill body reads `task_tracking.backend` at the start of every
137
- invocation. If the backend's adapter is not implemented, the skill
138
- fails fast with the `not-implemented` envelope — it does NOT silently
139
- fall back to `local-only`.
137
+ invocation. For `app-inbox` (still unimplemented), the skill fails fast
138
+ with the `not-implemented` envelope — it does NOT silently fall back to
139
+ `local-only`. The `github-issues` backend is fully implemented (see
140
+ above); its preflight/`gh` failures surface as structured errors, again
141
+ without falling back to `local-only`.
140
142
 
141
143
  ## Fast Path Guarantee — QUICK never enters triage (AC9, ID-05)
142
144
 
@@ -287,6 +287,6 @@ Runtime files were still refreshed; `/apt` will still work from PATH if `apt-too
287
287
  - **Worker hasn't run yet**: the cache file is missing. That's fine — run check-version directly (step 1 is robust to a missing cache). Proceed normally.
288
288
  - **Re-install fails (non-zero exit from init)**: surface the JSON `error` field verbatim in a single line, then stop. Do NOT delete the cache file — the chip should remain visible so the user sees the failure persists.
289
289
  - **Kernel install fails but runtime install succeeds**: see Scenario D above. Do not treat this as a total failure; the user's skills still work via PATH fallback.
290
- - **CLAUDE.md / AGENTS.md foreign rows (exit 2, `instruction-file.foreign-rows`)** — 0.7.0+ — init now refuses to silently clobber custom rows in the `<!-- APT:framework-* -->` block. If `init` exits 2 with `code: instruction-file.foreign-rows`, the `foreignRowIds[]` field lists the rows the generator did NOT produce. Two paths: (a) remove the foreign rows from CLAUDE.md / AGENTS.md by hand and re-run init, or (b) re-run with `--force-overwrite-catalog` to regenerate anyway. Unattended upgrade flows should detect this code and surface it to the operator — silent bypass is a regression.
290
+ - **Instruction-file foreign rows (exit 2, `instruction-file.foreign-rows`)** — 0.7.0+ — init now refuses to silently clobber custom rows in the `<!-- APT:framework-* -->` block. Since the multi-file fix, init stamps the block into EVERY instruction file the selected runtimes read (claude → CLAUDE.md, codex/opencode → AGENTS.md, gemini → GEMINI.md) and runs an all-target foreign-row preflight: if ANY target has foreign rows, init writes NOTHING and exits 2 (no partial install). If `init` exits 2 with `code: instruction-file.foreign-rows`, the `foreignRowIds[]` field lists the rows the generator did NOT produce and `instructionFiles[]` lists the offending files. Two paths: (a) remove the foreign rows from the listed file(s) by hand and re-run init, or (b) re-run with `--force-overwrite-catalog` to regenerate anyway. Unattended upgrade flows should detect this code and surface it to the operator — silent bypass is a regression.
291
291
 
292
292
  </edge_cases>
@@ -60,7 +60,7 @@ Before spawning any verifier subagent or running scoring against acceptance crit
60
60
  </locked_decisions>
61
61
  ```
62
62
 
63
- Verifiers apply this digest when interpreting "completeness" and "correctness" — if a decision explicitly deferred a behavior, its absence is NOT a verification failure.
63
+ Verifiers apply this digest when interpreting "completeness" and "correctness" — if a decision explicitly deferred a behavior AND the deferred finding has severity below critical or major, its absence is NOT a verification failure. A deferred critical or major finding IS a Completeness gap — score accordingly and note it in the issues list.
64
64
  </decision_ledger_inject>
65
65
 
66
66
  <process>
@@ -20,6 +20,7 @@ gates: []
20
20
  config_keys:
21
21
  - verification.open_report
22
22
  - verification.report_formats
23
+ - verification.provision_mcp
23
24
  ---
24
25
 
25
26
  # Proof Verification
@@ -104,13 +105,36 @@ disable the walk entirely (root-only behaviour, backwards compat).
104
105
  The `verification.runtimes[]` override stays the final escape hatch
105
106
  and is now usually unnecessary because the walk runs by default.
106
107
 
107
- ### Bundled drivers (4)
108
+ ### Bundled drivers (5)
108
109
 
109
110
  - **browser** (priority=100, ga) — Next.js / vanilla web frontends.
110
111
  - **electron** (priority=90, ga) — Electron desktop apps.
112
+ - **tauri** (priority=85, ga) — Tauri desktop apps.
111
113
  - **cli** (priority=60, ga) — headless CI, output-shape assertions.
112
114
  - **api** (priority=50, ga) — HTTP / JSON-API regression tests.
113
115
 
116
+ ### Install-time MCP provisioning (verification.provision_mcp)
117
+
118
+ `apt-tools init` (and `/apt:update`, which routes through the same path)
119
+ auto-provisions the MCP transport a driver needs into each SELECTED host
120
+ CLI's config, so verify-proof works at the prompt with no manual MCP
121
+ setup. It detects the project runtime and, per host:
122
+
123
+ - **claude** → JSON-merges the server into the project-root `.mcp.json`
124
+ (`mcpServers.<id>`).
125
+ - **codex** → upserts a `[mcp_servers.<id>]` table into the project-root
126
+ `.codex/config.toml`.
127
+
128
+ Electron and Tauri projects get the matching MCP server; web frontends
129
+ get the browser driver's declared `agent-browser` skill copied into the
130
+ host skill dir (`.claude/skills/` / `.agents/skills/`) — its `agent-browser`
131
+ CLI is a documented one-time prereq, the same class as the
132
+ `electron-mcp-server` / `tauri-plugin-mcp-server` npm packages (see each
133
+ driver's README). All writes are **TOFU/non-clobbering**: a pre-existing
134
+ server id, config table, or skill file is never overwritten. Opt out with
135
+ `verification.provision_mcp: false` (default `true`) — then init/update
136
+ writes no host MCP config and no skill.
137
+
114
138
  ### Driver authors
115
139
 
116
140
  Third-party driver authors should run `apt-tools driver doctor .` before