@aperant/framework 0.8.4 → 0.8.6

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 (108) hide show
  1. package/CHANGELOG.md +156 -0
  2. package/agents/apt-planner.md +34 -3
  3. package/dist/cli/commands/coverage-check.d.mts.map +1 -1
  4. package/dist/cli/commands/coverage-check.mjs +74 -7
  5. package/dist/cli/commands/coverage-check.mjs.map +1 -1
  6. package/dist/cli/commands/detect-runtime.d.mts.map +1 -1
  7. package/dist/cli/commands/detect-runtime.mjs +18 -12
  8. package/dist/cli/commands/detect-runtime.mjs.map +1 -1
  9. package/dist/cli/coverage-check/user-outcomes.d.mts +84 -0
  10. package/dist/cli/coverage-check/user-outcomes.d.mts.map +1 -0
  11. package/dist/cli/coverage-check/user-outcomes.mjs +265 -0
  12. package/dist/cli/coverage-check/user-outcomes.mjs.map +1 -0
  13. package/dist/cli/host/detect.mjs +1 -1
  14. package/dist/cli/host/detect.mjs.map +1 -1
  15. package/dist/cli/install/install-from-source.mjs +3 -3
  16. package/dist/cli/install/legacy-paths.d.mts.map +1 -1
  17. package/dist/cli/install/legacy-paths.mjs +2 -0
  18. package/dist/cli/install/legacy-paths.mjs.map +1 -1
  19. package/dist/cli/verify-proof/idl/index.d.mts +1 -1
  20. package/dist/cli/verify-proof/idl/index.mjs +7 -8
  21. package/dist/cli/verify-proof/idl/index.mjs.map +1 -1
  22. package/dist/cli/verify-proof/idl/types.d.ts +5 -4
  23. package/dist/cli/verify-proof/idl/types.d.ts.map +1 -1
  24. package/dist/cli/verify-proof/idl/types.js +4 -3
  25. package/dist/cli/verify-proof/idl/types.js.map +1 -1
  26. package/dist/cli/verify-proof/resolver.d.mts +63 -1
  27. package/dist/cli/verify-proof/resolver.d.mts.map +1 -1
  28. package/dist/cli/verify-proof/resolver.mjs +164 -4
  29. package/dist/cli/verify-proof/resolver.mjs.map +1 -1
  30. package/dist/cli/verify-proof/runtime-detect.d.mts +6 -3
  31. package/dist/cli/verify-proof/runtime-detect.d.mts.map +1 -1
  32. package/dist/cli/verify-proof/runtime-detect.mjs +104 -11
  33. package/dist/cli/verify-proof/runtime-detect.mjs.map +1 -1
  34. package/dist/cli/verify-proof/trust.d.mts +1 -1
  35. package/dist/cli/verify-proof/trust.d.mts.map +1 -1
  36. package/dist/cli/verify-proof/trust.mjs +1 -1
  37. package/dist/driver-sdk/conformance.d.ts +41 -0
  38. package/dist/driver-sdk/conformance.d.ts.map +1 -0
  39. package/dist/driver-sdk/conformance.js +123 -0
  40. package/dist/driver-sdk/conformance.js.map +1 -0
  41. package/dist/driver-sdk/errors.d.ts +75 -0
  42. package/dist/driver-sdk/errors.d.ts.map +1 -0
  43. package/dist/driver-sdk/errors.js +80 -0
  44. package/dist/driver-sdk/errors.js.map +1 -0
  45. package/dist/driver-sdk/idl.d.ts +221 -0
  46. package/dist/driver-sdk/idl.d.ts.map +1 -0
  47. package/dist/driver-sdk/idl.js +140 -0
  48. package/dist/driver-sdk/idl.js.map +1 -0
  49. package/dist/driver-sdk/index.d.ts +28 -0
  50. package/dist/driver-sdk/index.d.ts.map +1 -0
  51. package/dist/driver-sdk/index.js +28 -0
  52. package/dist/driver-sdk/index.js.map +1 -0
  53. package/dist/driver-sdk/manifest.d.ts +93 -0
  54. package/dist/driver-sdk/manifest.d.ts.map +1 -0
  55. package/dist/driver-sdk/manifest.js +12 -0
  56. package/dist/driver-sdk/manifest.js.map +1 -0
  57. package/dist/driver-sdk/retry.d.ts +33 -0
  58. package/dist/driver-sdk/retry.d.ts.map +1 -0
  59. package/dist/driver-sdk/retry.js +50 -0
  60. package/dist/driver-sdk/retry.js.map +1 -0
  61. package/dist/plugin/.claude-plugin/plugin.json +2 -1
  62. package/dist/plugin/agents/apt-planner.md +34 -3
  63. package/dist/plugin/skills/apt-plan/SKILL.md +50 -0
  64. package/dist/plugin/skills/apt-pr-review/SKILL.md +1 -1
  65. package/dist/plugin/skills/apt-research/SKILL.md +526 -0
  66. package/dist/plugin/skills/apt-research/appendices/budget-loop.md +397 -0
  67. package/dist/plugin/skills/apt-research/appendices/claim-graph-schema.md +206 -0
  68. package/dist/plugin/skills/apt-research/appendices/domain-tools.md +236 -0
  69. package/dist/plugin/skills/apt-spar/SKILL.md +19 -7
  70. package/dist/plugin/skills/apt-verify-proof/SKILL.md +41 -2
  71. package/dist/schemas/quick-task.d.ts +17 -17
  72. package/drivers/.gitkeep +0 -0
  73. package/drivers/api/README.md +40 -0
  74. package/drivers/api/driver.mjs +59 -0
  75. package/drivers/api/manifest.json +26 -0
  76. package/drivers/browser/README.md +105 -0
  77. package/drivers/browser/driver.mjs +134 -0
  78. package/drivers/browser/manifest.json +35 -0
  79. package/drivers/cli/README.md +44 -0
  80. package/drivers/cli/driver.mjs +62 -0
  81. package/drivers/cli/manifest.json +28 -0
  82. package/drivers/electron/README.md +64 -0
  83. package/drivers/electron/driver.mjs +87 -0
  84. package/drivers/electron/manifest.json +37 -0
  85. package/package.json +7 -3
  86. package/skills/apt-plan/SKILL.md +50 -0
  87. package/skills/apt-planner.md +16 -0
  88. package/skills/apt-pr-review/SKILL.md +1 -1
  89. package/skills/apt-research/SKILL.md +526 -0
  90. package/skills/apt-research/appendices/budget-loop.md +397 -0
  91. package/skills/apt-research/appendices/claim-graph-schema.md +206 -0
  92. package/skills/apt-research/appendices/domain-tools.md +236 -0
  93. package/skills/apt-spar/SKILL.md +19 -7
  94. package/skills/apt-verify-proof/SKILL.md +41 -2
  95. package/src/cli/commands/coverage-check.mjs +126 -44
  96. package/src/cli/commands/detect-runtime.mjs +23 -14
  97. package/src/cli/coverage-check/user-outcomes.mjs +273 -0
  98. package/src/cli/host/detect.mjs +1 -1
  99. package/src/cli/install/install-from-source.mjs +3 -3
  100. package/src/cli/install/legacy-paths.mjs +2 -0
  101. package/src/cli/verify-proof/idl/index.mjs +7 -8
  102. package/src/cli/verify-proof/idl/types.ts +5 -4
  103. package/src/cli/verify-proof/manifest-schema.json +2 -1
  104. package/src/cli/verify-proof/resolver.mjs +171 -4
  105. package/src/cli/verify-proof/runtime-detect.mjs +99 -10
  106. package/src/cli/verify-proof/trust.mjs +1 -1
  107. package/templates/proof-verification.md +32 -3
  108. package/workflows/verify-proof.md +78 -9
@@ -0,0 +1,236 @@
1
+ # Domain Tools, Gates, and Output Templates
2
+
3
+ > Per-domain tool surface, reasoning gates, and synthesis output template.
4
+ > Load when domain has been detected (Step 1d) or when synthesizing
5
+ > (Step 10).
6
+
7
+ The four domains are detected from the user's question in Step 1d and
8
+ recorded as immutable in `research-spec.md ## Domain`. The detected
9
+ domain determines which tools each `apt-researcher` role gets, which
10
+ reasoning steps are enabled, and what shape the final `RESEARCH.md`
11
+ output takes.
12
+
13
+ ## Domain detection signals
14
+
15
+ | Domain | Signals | Examples |
16
+ |---|---|---|
17
+ | **mechanistic** | physics, math, distributed systems, algorithms, deterministic engineering systems with provable behavior | "how does Raft handle leader election", "what does axle stiffness do to chassis dynamics", "is this algorithm O(log n)" |
18
+ | **empirical** | medicine, biology, nutrition, psychology, public-health, social science, anything where human variation dominates and replication matters | "does intermittent fasting cause muscle loss", "what's the effect of X on Y in older adults", "is supplement X effective" |
19
+ | **strategic** | tool/vendor comparison, market analysis, license/contract evaluation, anything where the answer depends on vendor disclosure + ecosystem health | "best embedded DB for offline-first 2026", "should we pick Vitest or Bun test", "is Turso safe to depend on" |
20
+ | **general** | catch-all when none of the above fit; usually broad knowledge questions or framing-questions | "what is consensus in distributed systems", "explain bitemporal databases" |
21
+
22
+ Ambiguous question → `general`. The user can override with explicit
23
+ flag (future: `--domain <name>`); for v1, signal-based detection is
24
+ authoritative.
25
+
26
+ ## Per-domain tool surface
27
+
28
+ Each `apt-researcher` role gets the base tools (Read, Grep, Glob,
29
+ WebSearch, WebFetch, Context7) PLUS the domain extras below.
30
+
31
+ ### mechanistic
32
+
33
+ | Tool | How invoked | Used by |
34
+ |---|---|---|
35
+ | arxiv search | `curl "http://export.arxiv.org/api/query?search_query=..."` | Retriever |
36
+ | PDF reader | `curl -o /tmp/x.pdf {url} && pdftotext /tmp/x.pdf -` | Builder, Skeptic, Retriever |
37
+ | Wolfram Alpha (if API key set) | `curl "https://api.wolframalpha.com/v2/query?..."` | Builder, Skeptic |
38
+
39
+ First-principles derivation: **ENABLED**. Builder and Skeptic may
40
+ produce derivations grounded in domain theory. Judge accepts them
41
+ into `claims.json` at `source_tier: derived-theory` (weight 0.6
42
+ in `uncertainty` formula).
43
+
44
+ ### empirical
45
+
46
+ | Tool | How invoked | Used by |
47
+ |---|---|---|
48
+ | Crossref DOI resolve | `curl "https://api.crossref.org/works/{doi}"` | Retriever, citation audit |
49
+ | PubMed E-utilities | `curl "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?db=pubmed&term=..."` | Retriever |
50
+ | PMC full-text fetch | `curl "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi?db=pmc&id={pmcid}"` | Builder, Skeptic |
51
+ | Europe PMC fallback | `curl "https://www.ebi.ac.uk/europepmc/webservices/rest/search?query=..."` | Retriever |
52
+
53
+ First-principles derivation: **DEMOTED**. Builder/Skeptic may invoke
54
+ theory (mechanism, leucine threshold, etc.) but Judge demotes the
55
+ output to `source_tier: hypothesis` (weight 0.1). Hypotheses cannot
56
+ reach `confidence: high` regardless of how compelling the reasoning
57
+ sounds — only empirical evidence at `tier ≥ independent-test` can
58
+ elevate a claim to `high`.
59
+
60
+ Citation audit is **MANDATORY** every iteration (not every N) in
61
+ empirical domains. Every new citation MUST be DOI/PMID-verified
62
+ via Crossref or NCBI before promotion to `sources.json`. Failures
63
+ go straight to `suspect-citations.md` and the claim depending on
64
+ them is downgraded.
65
+
66
+ ### strategic
67
+
68
+ | Tool | How invoked | Used by |
69
+ |---|---|---|
70
+ | GitHub releases | `gh release list -R {owner}/{repo} --limit 20` | Retriever |
71
+ | GitHub code search | `gh search code "..." --limit 20` | Retriever, Skeptic |
72
+ | License fetch | `gh api repos/{owner}/{repo}/license` or WebFetch raw LICENSE | Retriever |
73
+ | Changelog fetch | WebFetch `CHANGELOG.md` or `gh api repos/{owner}/{repo}/contents/CHANGELOG.md` | Retriever |
74
+ | Package metadata | `npm view {pkg} --json` / `pip show {pkg} --verbose` / `cargo search {pkg}` | Retriever |
75
+
76
+ First-principles derivation: **DISABLED**. "Derive whether libSQL is
77
+ stable from theory" is nonsense. Builder is instructed to synthesize
78
+ from sources only. If Builder produces a derivation-shaped output,
79
+ Judge rejects it with `## reason: derivation invalid in strategic domain`.
80
+
81
+ Vendor-only evidence is a spar trigger (see `overnight-loop.md`
82
+ §spar-triggers). Strategic domain treats vendor-doc as
83
+ `source_tier: vendor-doc` (weight 0.5) — NOT a high tier.
84
+
85
+ ### general
86
+
87
+ Default tool surface. First-principles is **SOFT-ENABLED with
88
+ hypothesis demotion** — Builder/Skeptic may derive but Judge
89
+ demotes to `hypothesis` until empirical or primary-doc evidence
90
+ elevates the claim.
91
+
92
+ ## Output gates (Step 10 — synthesis)
93
+
94
+ The synthesis template in `RESEARCH.md` has a common core (defined in
95
+ the main SKILL.md `## 10. Synthesize`) PLUS domain-specific sections.
96
+
97
+ ### mechanistic
98
+
99
+ Add:
100
+
101
+ ```markdown
102
+ ## First-principles derivations
103
+
104
+ For each derivation accepted into the claim graph at
105
+ `source_tier: derived-theory`:
106
+
107
+ - **{derivation name}** — claim {C-id}
108
+ - Premises: {what we assumed}
109
+ - Steps: {compact derivation}
110
+ - Cross-check vs sources: {which sources confirm/contradict the derivation}
111
+ - Failure mode: {when does this derivation break?}
112
+ ```
113
+
114
+ If a derivation contradicts an empirical source, the derivation is
115
+ flagged as bounded — Judge MUST note the boundary condition.
116
+
117
+ ### empirical
118
+
119
+ Add (mandatory, non-negotiable):
120
+
121
+ ```markdown
122
+ ## Not medical / financial / safety advice
123
+
124
+ This research synthesis is informational. It is not a substitute for
125
+ professional advice from a licensed clinician, financial advisor, or
126
+ safety expert. Individual circumstances vary; outcomes that apply to
127
+ populations may not apply to any specific person.
128
+
129
+ ## What this CAN'T tell you
130
+
131
+ - {Specific question the evidence does not address}
132
+ - {Specific population the evidence does not cover}
133
+ - {Specific interaction or confounder the evidence cannot resolve}
134
+
135
+ ## Quality of evidence
136
+
137
+ For each cited study supporting a critical claim:
138
+
139
+ | Source | Design | n | Peer-reviewed | Replicated | Notes |
140
+ |---|---|---|---|---|---|
141
+ | {S-id} | {RCT/cohort/meta} | {n} | {yes/no} | {yes/no/unknown} | {note} |
142
+ ```
143
+
144
+ Imperatives in the synthesis prose ("you should…", "do X", "avoid Y")
145
+ are STRIPPED by Judge. Empirical synthesis uses descriptive language
146
+ ("evidence suggests…", "in the populations studied…").
147
+
148
+ If the synthesis would recommend a course of action affecting health,
149
+ finance, or safety, Judge inserts: "**Discuss with a qualified
150
+ professional before acting on this synthesis.**"
151
+
152
+ ### strategic
153
+
154
+ Add (mandatory):
155
+
156
+ ```markdown
157
+ ## Conditional-tree recommendation
158
+
159
+ ```
160
+ if {decision-relevant condition A}:
161
+ → recommend {Option X}
162
+ → reason: {1-line}
163
+ elif {decision-relevant condition B}:
164
+ → recommend {Option Y}
165
+ → reason: {1-line}
166
+ else:
167
+ → no clear winner — {what's the unresolved question}
168
+ ```
169
+
170
+ ## Vendor-claim conflicts
171
+
172
+ For each option where evidence is vendor-only or vendors disagree:
173
+
174
+ - **{Option}** — {dimension}
175
+ - Vendor claim: {what the vendor says}
176
+ - Independent confirmation: {available | NOT AVAILABLE}
177
+ - Risk if vendor claim is wrong: {what breaks}
178
+
179
+ ## Decision blockers
180
+
181
+ Questions only the user can answer that change the recommendation:
182
+
183
+ - {Question} — affects: {which option(s)}
184
+ ```
185
+
186
+ Single-winner output is **REJECTED** by Judge when evidence is
187
+ vendor-only across the deciding dimension. The skill produces a
188
+ conditional tree or explicit "no clear winner" — never a confident
189
+ universal recommendation in those cases.
190
+
191
+ ### general
192
+
193
+ Add:
194
+
195
+ ```markdown
196
+ ## Outstanding gaps
197
+
198
+ - {Open question that the synthesis does not close}
199
+
200
+ ## Recommended next reads
201
+
202
+ - {Source} — covers {what aspect}
203
+ ```
204
+
205
+ ## Mode-specific additions (orthogonal to domain)
206
+
207
+ Applied AFTER the domain gates. From the main SKILL.md `## 10.
208
+ Synthesize`, repeated here for the per-domain reader:
209
+
210
+ - **UNDERSTAND** → mental model section with mechanism + analogy
211
+ - **VERIFY** → verdict line (`confirmed | partially confirmed | false |
212
+ contested | unknown`)
213
+ - **SOLVE** → candidate approaches table + `## Falsification status`.
214
+ For code-domain SOLVE: blocks `CONVERGED` if no falsification was
215
+ run (test / simulator / model-checker / formal-method sketch).
216
+ Force STALLED with `## Cannot certify without falsification` and
217
+ enumerate what falsification harness would be needed.
218
+ - **COMPARE** → dimensions × options matrix + per-cell evidence
219
+ pointers (conditional-tree comes from the strategic domain gate)
220
+
221
+ ## Tool-surface fallbacks
222
+
223
+ If a domain tool is unavailable in the current environment (e.g.
224
+ `gh` not installed, no network access for Crossref), the affected
225
+ role MUST report this in its output:
226
+
227
+ ```
228
+ ## Tool unavailable
229
+ - {tool name}: {why unavailable}
230
+ - Impact on this iteration: {what could not be done}
231
+ ```
232
+
233
+ Judge records the missing capability in `iteration-log.md` and adjusts
234
+ stop-state evaluation: a STALLED verdict resulting from missing tools
235
+ gets `## Cannot resolve without` annotated with "tool {X} unavailable
236
+ in this environment". The user can resolve and `--resume`.
@@ -47,7 +47,7 @@ Termination posture (v1.1, amending SPAR-06): the termination headline is in-con
47
47
 
48
48
  **Reads:**
49
49
  - `AGENTS.md` — project constitution (principles, conventions, tech stack)
50
- - `.aperant/config.json` — `router.llm.providers` for partner fallback (read-only)
50
+ - `.aperant/config.json` — `router.llm.providers` for partner fallback (read-only). **NOTE:** `router.llm.providers.<id>.model` is NOT consulted for spar partner model selection — partner CLIs inherit their own native config (codex: `~/.codex/config.toml`; gemini: `GEMINI_MODEL` env / `~/.gemini/settings.json`). The `router.llm.providers` keys serve the cheap LLM-router classifier, a separately scoped reserved surface (see `packages/framework/src/types/config.ts:222-238`); do not conflate.
51
51
  - `.aperant/state.json` — `active_task` framing, optional
52
52
  - The live conversation transcript (when `[topic]` is empty)
53
53
 
@@ -133,7 +133,7 @@ Do NOT proceed; self-sparring is sycophancy by construction (the host cannot obj
133
133
 
134
134
  1. **Rung 1 — MCP tool** (`mcp__<partner>-mcp__*`, e.g. `mcp__codex-mcp__codex`). Detected if the host exposes its deferred-tool manifest via `CLAUDE_MCP_DEFERRED_TOOLS` and the tool is registered. When `detected: "runtime-host-only"` the framework process cannot probe the host's tool registry from outside — treat as candidate and attempt the tool call; if the tool is genuinely unavailable in the host's visible registry, fall through to rung 2.
135
135
  2. **Rung 2 — Plugin shim** (`${CLAUDE_PLUGIN_ROOT}/scripts/<partner>-companion.mjs`). Codex only (claude / gemini have no plugin shim).
136
- 3. **Rung 3 — Raw CLI on `$PATH`** (`codex exec`, `claude -p`, `gemini -m`).
136
+ 3. **Rung 3 — Raw CLI on `$PATH`** (`codex exec`, `claude -p`, `gemini`).
137
137
 
138
138
  Walk the ladder in order; pick the first rung with `detected: true` (or `"runtime-host-only"` for MCP). **MCP rung has a hard timeout** — default 900000ms (15 min), configurable via `apt-spar.mcp_timeout_ms` in `.aperant/config.json`. The MCP rung is a structured tool call, NOT a Bash invocation, so the Bash tool's 600000ms ceiling does NOT apply — the MCP rung can carry heavy-research partner runs (e.g. Claude Code partner doing multiple minutes of Read/Grep/Bash) where the Bash rungs would hit the host ceiling. On MCP timeout, **automatically fall through to rung 2 or rung 3** — do NOT fail the whole spar. Treat MCP timeout as "rung unavailable for this invocation."
139
139
 
@@ -183,20 +183,32 @@ The table below shows the canonical command shape per (partner × rung); the act
183
183
 
184
184
  | Partner | Rung | Command | Notes |
185
185
  |---|---|---|---|
186
- | codex | mcp | tool call: `mcp__codex-mcp__codex({prompt})` | Preferred. 900000ms (15 min) timeout (configurable `apt-spar.mcp_timeout_ms`); on timeout fall through to plugin then CLI. |
187
- | codex | plugin | `node "${CLAUDE_PLUGIN_ROOT}/scripts/codex-companion.mjs" task "$(cat <<'APT_SPAR_PROMPT_EOF' ... APT_SPAR_PROMPT_EOF\n)"` | **NO `--write`. NO `--effort` override.** Per SPAR-04 — spar is conversation, not patching. |
188
- | codex | cli | `codex exec "$(cat <<'APT_SPAR_PROMPT_EOF' ... APT_SPAR_PROMPT_EOF\n)"` | Precedent: `apt-pr-review/SKILL.md:1084`. Verify with `codex --help`. |
186
+ | codex | mcp | tool call: `mcp__codex-mcp__codex({prompt, model})` where `model` is parsed from `~/.codex/config.toml`'s `model = "<value>"` line (see §5a below). | Preferred. 900000ms (15 min) timeout (configurable `apt-spar.mcp_timeout_ms`); on timeout fall through to plugin then CLI. If `~/.codex/config.toml` is missing OR the `model =` line is absent/unparseable, treat MCP rung as UNAVAILABLE and fall through to plugin/CLI — never invent a default model (the MCP server's built-in fallback is stale `gpt-5.3-codex`, which is exactly the bug we are fixing). |
187
+ | codex | plugin | `node "${CLAUDE_PLUGIN_ROOT}/scripts/codex-companion.mjs" task "$(cat <<'APT_SPAR_PROMPT_EOF' ... APT_SPAR_PROMPT_EOF\n)"` | No `--model` flag — `codex-companion.mjs` invokes `codex exec` which inherits `~/.codex/config.toml` automatically. **NO `--write`. NO `--effort` override.** Per SPAR-04 — spar is conversation, not patching. |
188
+ | codex | cli | `codex exec "$(cat <<'APT_SPAR_PROMPT_EOF' ... APT_SPAR_PROMPT_EOF\n)"` | Precedent: `apt-pr-review/SKILL.md:1084`. No `--model` flag — `codex exec` inherits `~/.codex/config.toml` automatically. Verify with `codex --help`. |
189
189
  | claude | mcp | tool call: `mcp__claude-code-mcp__claude_code({prompt})` | Preferred. Same 900000ms (15 min) timeout + fallthrough rule. |
190
190
  | claude | plugin | n/a | No plugin shim exists for Claude. Skip this rung. |
191
191
  | claude | cli | `claude -p "$(cat <<'APT_SPAR_PROMPT_EOF' ... APT_SPAR_PROMPT_EOF\n)" --output-format text` | Symmetric. Claude has no `--write` equivalent — asymmetric flags are a smell. |
192
192
  | gemini | mcp | tool call: `mcp__gemini-mcp__*({prompt})` | Preferred. Same 900000ms (15 min) timeout + fallthrough rule. Any `mcp__gemini-mcp__*` tool satisfies the rung. |
193
193
  | gemini | plugin | n/a | No plugin shim exists for Gemini. Skip this rung. |
194
- | gemini | cli | `gemini -m <model-from-router.llm.providers.gemini-cli> "$(cat <<'APT_SPAR_PROMPT_EOF' ... APT_SPAR_PROMPT_EOF\n)"` | Read the exact model name from `.aperant/config.json` at runtime do not hardcode. |
194
+ | gemini | cli | `gemini "$(cat <<'APT_SPAR_PROMPT_EOF' ... APT_SPAR_PROMPT_EOF\n)"` | No `-m` flag — gemini-cli resolves model via `--model` > `GEMINI_MODEL` env > `~/.gemini/settings.json` > built-in default per `docs/cli/model-routing.md:47-58`. Drop the flag, let the cascade work; users override per-shell via `GEMINI_MODEL=...` or per-project via `settings.json`. |
195
195
 
196
196
  Set the Bash tool's `timeout` parameter to `--timeout-ms` (default 300000).
197
197
 
198
198
  **Timeout calibration.** The 5-min Bash-rung default and 15-min MCP-rung default are sized for heavy-research partner runs — modern partner CLIs commonly spend multiple minutes doing real Read/Grep/Bash investigation before producing a round response. The two defaults are intentionally asymmetric: the Bash rungs (plugin shim, raw CLI) are capped at the Claude Code Bash tool's 600000ms (10 min) host ceiling, while the MCP rung — a structured tool call rather than a Bash invocation — has no host ceiling and can carry the worst-case 15-min Claude Code partner. For tighter budgets (e.g. CI), pass `--timeout-ms` explicitly. The MCP-rung override is downward-only: if you observe consistent partner-CLI wedges (auth handshakes, hosted-API stalls, CI lanes wanting fast failure), set `apt-spar.mcp_timeout_ms` BELOW the 15-min default in `.aperant/config.json` (e.g. `120000` for a 2-min fast-fail budget) — raising it above 15 min is unnecessary because the default already covers the documented worst case. Future versions may introduce per-partner defaults.
199
199
 
200
+ ## 5a. Codex MCP model resolution
201
+
202
+ The codex MCP rung (table row above) requires an explicit `model` arg sourced from the user's `~/.codex/config.toml`. The MCP server (`codex-mcp-server`) does NOT read `~/.codex/config.toml` itself — when no `model` is passed it falls back to a hardcoded stale `gpt-5.3-codex`, silently overriding the user's actual configured model. Resolve the model exactly once per spar invocation, before the rung-table walk:
203
+
204
+ ```bash
205
+ # Resolve the codex MCP model exactly once per spar invocation, before the rung table walk
206
+ CODEX_MCP_MODEL="$(grep -E '^model = "[^"]+"' "$HOME/.codex/config.toml" 2>/dev/null | head -1 | sed -E 's/^model = "([^"]+)".*/\1/')"
207
+ # If empty → codex MCP rung is UNAVAILABLE; fall through to plugin/CLI.
208
+ ```
209
+
210
+ If `$CODEX_MCP_MODEL` is empty (file missing, `model =` line absent, or value not double-quoted), treat the MCP rung as UNAVAILABLE and fall through to plugin → CLI. **Never invent a default** — the upstream MCP server's built-in `gpt-5.3-codex` fallback is exactly the bug this resolution path exists to fix.
211
+
200
212
  **Prompt-quoting safety.** The prompt is user-controlled (it comes from `[topic]` and/or conversation context) and may legitimately contain double quotes, dollar signs, backticks, or `$(...)` substitution that bash would otherwise expand or break on. NEVER interpolate `<prompt>` directly inside a double-quoted shell argument. Instead, pipe via a here-document so the shell treats the body as opaque text:
201
213
 
202
214
  ```bash
@@ -213,7 +225,7 @@ APT_SPAR_PROMPT_EOF
213
225
  )" --output-format text
214
226
 
215
227
  # Gemini direction
216
- gemini -m <model-from-router.llm.providers.gemini-cli> "$(cat <<'APT_SPAR_PROMPT_EOF'
228
+ gemini "$(cat <<'APT_SPAR_PROMPT_EOF'
217
229
  <prompt body — opaque, no shell expansion>
218
230
  APT_SPAR_PROMPT_EOF
219
231
  )"
@@ -58,6 +58,35 @@ apt-tools detect-runtime .
58
58
  # → { runtime: {is_electron, is_nextjs, ...}, selected_driver_id: "..." }
59
59
  ```
60
60
 
61
+ v0.8.6 — the runtime detector now walks `apps/*/package.json` by
62
+ default and OR-merges each child's capabilities into the root. The
63
+ canonical Aperant monorepo (Electron at `apps/desktop`, Next.js at
64
+ `apps/web`) now reports `is_electron: true` AND `is_nextjs: true`
65
+ without any config override.
66
+
67
+ ### Per-outcome dispatch (v0.8.6+)
68
+
69
+ When the spec ships `## User Outcomes` (the verify-proof input
70
+ contract — see CLAUDE.md), the workflow runner imports
71
+ `resolveDriversForOutcomes()` from the resolver module and picks a
72
+ driver PER OUTCOME SURFACE instead of a single per-run driver pick.
73
+
74
+ ```js
75
+ import { resolveDriversForOutcomes } from '.aperant/deps/node_modules/@aperant/framework/src/cli/verify-proof/resolver.mjs'
76
+
77
+ const entries = resolveDriversForOutcomes({
78
+ drivers, runtime,
79
+ outcomes: parsed.outcomes, // from parseUserOutcomes(spec)
80
+ })
81
+ // → [{outcome_id: "O1", surface: "electron", driver: {...}}, ...]
82
+ // For [electron+web] outcomes, two entries share the same outcome_id.
83
+ ```
84
+
85
+ The legacy `apt-tools detect-runtime` still answers the per-run
86
+ "which driver would be picked?" question for the override preview.
87
+ For the per-outcome dispatch (the new contract), import
88
+ `resolveDriversForOutcomes` directly.
89
+
61
90
  ### Override
62
91
 
63
92
  Force a specific driver chain in `.aperant/config.json`:
@@ -66,6 +95,15 @@ Force a specific driver chain in `.aperant/config.json`:
66
95
  { "verification": { "runtimes": ["electron", "cli"] } }
67
96
  ```
68
97
 
98
+ v0.8.6 — `verification.monorepo_globs` is the new opt-out for the
99
+ monorepo walk. The walk now runs by default — adopters whose
100
+ monorepo lives at a non-canonical prefix
101
+ (`packages/apps/*`, `services/*`) override via
102
+ `verification.monorepo_globs: ["packages/apps/*"]`; set to `[]` to
103
+ disable the walk entirely (root-only behaviour, backwards compat).
104
+ The `verification.runtimes[]` override stays the final escape hatch
105
+ and is now usually unnecessary because the walk runs by default.
106
+
69
107
  ### Bundled drivers (4)
70
108
 
71
109
  - **browser** (priority=100, ga) — Next.js / vanilla web frontends.
@@ -83,8 +121,9 @@ apt-tools driver doctor .
83
121
  # → { drivers: [{driverId, manifest_valid, idl_conformant, ...}], summary: {...} }
84
122
  ```
85
123
 
86
- See `@aperant/driver-sdk` for the IDL types, error taxonomy, and
87
- conformance kit.
124
+ IDL types, error taxonomy, and conformance kit live inside the framework
125
+ itself under `packages/framework/src/driver-sdk/` — see that directory's
126
+ public-api-lock test for the surface contract.
88
127
 
89
128
  ## Execution
90
129
 
@@ -1,10 +1,20 @@
1
1
  /**
2
2
  * commands/coverage-check.mjs — plan-to-spec verification coverage validator (C29).
3
+ *
4
+ * v0.8.6 adds a second gate: `## User Outcomes` parsing + gate (see
5
+ * coverage-check/user-outcomes.mjs). The new gate is ADDITIVE — it does
6
+ * NOT change the existing AC parsing branch and does NOT block when
7
+ * only Acceptance Criteria are present (outcomes and ACs are
8
+ * independent concerns per spec §ID-01). QUICK-track plans
9
+ * (`complexity: "simple"` OR no `vertical_slice_schema_version`) skip
10
+ * the User Outcomes branch entirely per Fast Path Guarantee (ID-08).
3
11
  */
4
12
 
5
13
  import { existsSync, readFileSync } from 'node:fs'
6
- import { join, resolve } from 'node:path'
14
+ import { dirname, join, resolve } from 'node:path'
15
+ import { fileURLToPath } from 'node:url'
7
16
  import { parseAcceptanceCriteria, resolveSpecPath } from '../config/coverage-ac.mjs'
17
+ import { parseUserOutcomes, userOutcomesGateResult } from '../coverage-check/user-outcomes.mjs'
8
18
  import { err, ok } from '../util/result.mjs'
9
19
 
10
20
  export function cmdCoverageCheck(subcommand, projectDir, extraArgs) {
@@ -125,31 +135,64 @@ export function cmdCoverageCheck(subcommand, projectDir, extraArgs) {
125
135
  }
126
136
  }
127
137
 
138
+ // Build the User Outcomes payload — additive, ID-08 QUICK-exempt.
139
+ // Plans with `complexity: "simple"` OR no
140
+ // `vertical_slice_schema_version` are QUICK-track per the Fast
141
+ // Path Guarantee — skip the gate entirely (the payload's
142
+ // `user_outcomes` key is OMITTED).
143
+ const isQuickTrack = plan.complexity === 'simple' || !plan.vertical_slice_schema_version
144
+ let userOutcomesPayload = null
145
+ if (!isQuickTrack && resolvedSpec.path) {
146
+ const specContentForOutcomes = readFileSync(resolvedSpec.path, 'utf-8')
147
+ const parsedOutcomes = parseUserOutcomes(specContentForOutcomes)
148
+ const frameworkVersion = readFrameworkVersion()
149
+ const gateResult = userOutcomesGateResult(parsedOutcomes, frameworkVersion)
150
+ userOutcomesPayload = {
151
+ header_present: parsedOutcomes.header_present,
152
+ body_kind: parsedOutcomes.body_kind,
153
+ outcomes_count: parsedOutcomes.outcomes.length,
154
+ gate_result: gateResult,
155
+ }
156
+ if (parsedOutcomes.epic) userOutcomesPayload.epic = parsedOutcomes.epic
157
+ if (parsedOutcomes.validation_warnings && parsedOutcomes.validation_warnings.length > 0) {
158
+ userOutcomesPayload.validation_warnings = parsedOutcomes.validation_warnings
159
+ }
160
+ if (gateResult === 'warn') {
161
+ userOutcomesPayload.migration_deadline =
162
+ '## User Outcomes section becomes a hard block in v0.9.0 — add it now, or use the empty-with-note escape (`_No user-observable changes — pure refactor._`) for refactor-only specs.'
163
+ }
164
+ }
165
+
128
166
  // Zero-criteria guard — do NOT silently report ok when there is
129
167
  // nothing to verify. This is the motivating bug: zero criteria
130
168
  // trivially pass unmapped.length === 0, which produced a false
131
169
  // green signal. Treat it as incomplete with an explicit reason.
132
170
  if (criteria.length === 0) {
133
- return ok({
134
- status: 'incomplete',
135
- command: 'coverage-validate',
136
- criteria_count: 0,
137
- mapped_count: 0,
138
- unmapped_criteria: [],
139
- coverage: [],
140
- subtask_verifications: plan.subtasks.map((st) => ({
141
- id: st.id,
142
- title: st.title || st.id,
143
- verification: st.verification || '',
144
- acceptance_criteria: st.acceptance_criteria || [],
145
- })),
146
- reason: 'no-criteria-found',
147
- suggestion:
148
- 'Add acceptance criteria to spec.md under ## Acceptance Criteria, or add plan.acceptance_criteria to implementation_plan.json',
149
- sources_checked: sourcesChecked,
150
- spec_path: resolvedSpec.path,
151
- autonomy_action: autonomyLevelEarly <= 1 ? 'stop-and-ask' : 'generate-stubs',
152
- })
171
+ return ok(
172
+ withUserOutcomes(
173
+ {
174
+ status: 'incomplete',
175
+ command: 'coverage-validate',
176
+ criteria_count: 0,
177
+ mapped_count: 0,
178
+ unmapped_criteria: [],
179
+ coverage: [],
180
+ subtask_verifications: plan.subtasks.map((st) => ({
181
+ id: st.id,
182
+ title: st.title || st.id,
183
+ verification: st.verification || '',
184
+ acceptance_criteria: st.acceptance_criteria || [],
185
+ })),
186
+ reason: 'no-criteria-found',
187
+ suggestion:
188
+ 'Add acceptance criteria to spec.md under ## Acceptance Criteria, or add plan.acceptance_criteria to implementation_plan.json',
189
+ sources_checked: sourcesChecked,
190
+ spec_path: resolvedSpec.path,
191
+ autonomy_action: autonomyLevelEarly <= 1 ? 'stop-and-ask' : 'generate-stubs',
192
+ },
193
+ userOutcomesPayload,
194
+ ),
195
+ )
153
196
  }
154
197
 
155
198
  // Classify verification methods for each subtask (D-19)
@@ -221,32 +264,71 @@ export function cmdCoverageCheck(subcommand, projectDir, extraArgs) {
221
264
  }
222
265
 
223
266
  if (unmapped.length > 0) {
224
- return ok({
225
- status: 'incomplete',
226
- command: 'coverage-validate',
227
- criteria_count: criteria.length,
228
- mapped_count: mappedCount,
229
- unmapped_criteria: unmapped.map((u) => u.criterion),
230
- coverage,
231
- subtask_verifications: subtaskVerifications,
232
- suggestion: 'generate test stubs',
233
- autonomy_action: autonomyLevel <= 1 ? 'stop-and-ask' : 'generate-stubs',
234
- spec_path: resolvedSpec.path,
235
- sources_checked: sourcesChecked,
236
- })
267
+ return ok(
268
+ withUserOutcomes(
269
+ {
270
+ status: 'incomplete',
271
+ command: 'coverage-validate',
272
+ criteria_count: criteria.length,
273
+ mapped_count: mappedCount,
274
+ unmapped_criteria: unmapped.map((u) => u.criterion),
275
+ coverage,
276
+ subtask_verifications: subtaskVerifications,
277
+ suggestion: 'generate test stubs',
278
+ autonomy_action: autonomyLevel <= 1 ? 'stop-and-ask' : 'generate-stubs',
279
+ spec_path: resolvedSpec.path,
280
+ sources_checked: sourcesChecked,
281
+ },
282
+ userOutcomesPayload,
283
+ ),
284
+ )
237
285
  }
238
- return ok({
239
- status: 'ok',
240
- command: 'coverage-validate',
241
- criteria_count: criteria.length,
242
- mapped_count: mappedCount,
243
- coverage,
244
- subtask_verifications: subtaskVerifications,
245
- spec_path: resolvedSpec.path,
246
- sources_checked: sourcesChecked,
247
- })
286
+ return ok(
287
+ withUserOutcomes(
288
+ {
289
+ status: 'ok',
290
+ command: 'coverage-validate',
291
+ criteria_count: criteria.length,
292
+ mapped_count: mappedCount,
293
+ coverage,
294
+ subtask_verifications: subtaskVerifications,
295
+ spec_path: resolvedSpec.path,
296
+ sources_checked: sourcesChecked,
297
+ },
298
+ userOutcomesPayload,
299
+ ),
300
+ )
248
301
  }
249
302
  default:
250
303
  return err(`Unknown coverage-check subcommand: ${subcommand}. Available: validate`)
251
304
  }
252
305
  }
306
+
307
+ /**
308
+ * Attach the `user_outcomes` payload to a coverage-check envelope when
309
+ * present (i.e. STANDARD/DEEP plan with a spec on disk). QUICK plans
310
+ * pass `null` and the key is OMITTED from the envelope entirely — Fast
311
+ * Path Guarantee (ID-08).
312
+ */
313
+ function withUserOutcomes(envelope, userOutcomesPayload) {
314
+ if (userOutcomesPayload === null) return envelope
315
+ return { ...envelope, user_outcomes: userOutcomesPayload }
316
+ }
317
+
318
+ /**
319
+ * Read the running kernel's framework version from its own package.json.
320
+ * Used to gate the `## User Outcomes` missing-header behavior — warn in
321
+ * v0.8.x, hard-block from v0.9.0 forward. Returns `'0.0.0'` on failure
322
+ * so the gate fails-soft to warn (callers treat unknown version as
323
+ * pre-0.9.0).
324
+ */
325
+ function readFrameworkVersion() {
326
+ try {
327
+ const here = dirname(fileURLToPath(import.meta.url))
328
+ const pkgPath = resolve(here, '..', '..', '..', 'package.json')
329
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'))
330
+ return typeof pkg.version === 'string' ? pkg.version : '0.0.0'
331
+ } catch {
332
+ return '0.0.0'
333
+ }
334
+ }
@@ -75,25 +75,16 @@ export function cmdDetectRuntime(projectDir, _extra = []) {
75
75
  if (!projectDir) return err('Usage: apt-tools detect-runtime <project-dir>')
76
76
  const target = resolve(projectDir)
77
77
 
78
- // Detect runtime capabilities from project's package.json.
79
- const runtime = loadRuntimeCapabilities(target)
80
-
81
- // Discover bundled + user drivers.
82
- // __dirname-equivalent under ESM: walk up from this file to the framework
83
- // package root. fileURLToPath is portable across POSIX + Windows;
84
- // new URL(...).pathname returns '/C:/...' on Windows which resolve()
85
- // does not handle correctly.
86
- const frameworkRoot = fileURLToPath(new URL('../../..', import.meta.url))
87
- const bundled = discoverBundledDrivers(frameworkRoot)
88
- const user = discoverUserDrivers(target)
89
- const drivers = [...bundled, ...user]
90
-
91
- // Read user override from .aperant/config.json:verification.runtimes[].
78
+ // Read user config (used both for the monorepo walk + override).
79
+ let monorepoGlobs
92
80
  let override = []
93
81
  const configPath = join(target, '.aperant/config.json')
94
82
  if (existsSync(configPath)) {
95
83
  try {
96
84
  const cfg = JSON.parse(readFileSync(configPath, 'utf-8'))
85
+ if (Array.isArray(cfg?.verification?.monorepo_globs)) {
86
+ monorepoGlobs = cfg.verification.monorepo_globs.filter((x) => typeof x === 'string')
87
+ }
97
88
  if (Array.isArray(cfg?.verification?.runtimes)) {
98
89
  override = cfg.verification.runtimes.filter((x) => typeof x === 'string')
99
90
  }
@@ -102,6 +93,24 @@ export function cmdDetectRuntime(projectDir, _extra = []) {
102
93
  }
103
94
  }
104
95
 
96
+ // Detect runtime capabilities from project's package.json + (optionally)
97
+ // monorepo `apps/*` walk. Default behaviour matches the canonical
98
+ // Aperant layout — adopters opt out via verification.monorepo_globs: [].
99
+ const runtime = loadRuntimeCapabilities(
100
+ target,
101
+ monorepoGlobs !== undefined ? { monorepo_globs: monorepoGlobs } : undefined,
102
+ )
103
+
104
+ // Discover bundled + user drivers.
105
+ // __dirname-equivalent under ESM: walk up from this file to the framework
106
+ // package root. fileURLToPath is portable across POSIX + Windows;
107
+ // new URL(...).pathname returns '/C:/...' on Windows which resolve()
108
+ // does not handle correctly.
109
+ const frameworkRoot = fileURLToPath(new URL('../../..', import.meta.url))
110
+ const bundled = discoverBundledDrivers(frameworkRoot)
111
+ const user = discoverUserDrivers(target)
112
+ const drivers = [...bundled, ...user]
113
+
105
114
  // Try resolving with the default minimum required capability.
106
115
  let selected = null
107
116
  let fallback_attempts = []