@aperant/framework 0.8.4 → 0.8.7
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.
- package/CHANGELOG.md +185 -0
- package/agents/apt-planner.md +34 -3
- package/dist/cli/commands/coverage-check.d.mts.map +1 -1
- package/dist/cli/commands/coverage-check.mjs +74 -7
- package/dist/cli/commands/coverage-check.mjs.map +1 -1
- package/dist/cli/commands/detect-runtime.d.mts.map +1 -1
- package/dist/cli/commands/detect-runtime.mjs +18 -12
- package/dist/cli/commands/detect-runtime.mjs.map +1 -1
- package/dist/cli/coverage-check/user-outcomes.d.mts +84 -0
- package/dist/cli/coverage-check/user-outcomes.d.mts.map +1 -0
- package/dist/cli/coverage-check/user-outcomes.mjs +265 -0
- package/dist/cli/coverage-check/user-outcomes.mjs.map +1 -0
- package/dist/cli/host/detect.mjs +1 -1
- package/dist/cli/host/detect.mjs.map +1 -1
- package/dist/cli/install/install-from-source.mjs +3 -3
- package/dist/cli/install/legacy-paths.d.mts.map +1 -1
- package/dist/cli/install/legacy-paths.mjs +2 -0
- package/dist/cli/install/legacy-paths.mjs.map +1 -1
- package/dist/cli/install/manifest.d.mts +13 -0
- package/dist/cli/install/manifest.d.mts.map +1 -1
- package/dist/cli/install/manifest.mjs +5 -0
- package/dist/cli/install/manifest.mjs.map +1 -1
- package/dist/cli/install/pipeline.d.mts +15 -0
- package/dist/cli/install/pipeline.d.mts.map +1 -1
- package/dist/cli/install/pipeline.mjs +27 -0
- package/dist/cli/install/pipeline.mjs.map +1 -1
- package/dist/cli/install/transforms/codex.d.mts +1 -1
- package/dist/cli/install/transforms/codex.d.mts.map +1 -1
- package/dist/cli/install/transforms/codex.mjs +43 -2
- package/dist/cli/install/transforms/codex.mjs.map +1 -1
- package/dist/cli/install/version-header.d.mts.map +1 -1
- package/dist/cli/install/version-header.mjs +7 -1
- package/dist/cli/install/version-header.mjs.map +1 -1
- package/dist/cli/verify-proof/idl/index.d.mts +1 -1
- package/dist/cli/verify-proof/idl/index.mjs +7 -8
- package/dist/cli/verify-proof/idl/index.mjs.map +1 -1
- package/dist/cli/verify-proof/idl/types.d.ts +5 -4
- package/dist/cli/verify-proof/idl/types.d.ts.map +1 -1
- package/dist/cli/verify-proof/idl/types.js +4 -3
- package/dist/cli/verify-proof/idl/types.js.map +1 -1
- package/dist/cli/verify-proof/resolver.d.mts +63 -1
- package/dist/cli/verify-proof/resolver.d.mts.map +1 -1
- package/dist/cli/verify-proof/resolver.mjs +164 -4
- package/dist/cli/verify-proof/resolver.mjs.map +1 -1
- package/dist/cli/verify-proof/runtime-detect.d.mts +6 -3
- package/dist/cli/verify-proof/runtime-detect.d.mts.map +1 -1
- package/dist/cli/verify-proof/runtime-detect.mjs +104 -11
- package/dist/cli/verify-proof/runtime-detect.mjs.map +1 -1
- package/dist/cli/verify-proof/trust.d.mts +1 -1
- package/dist/cli/verify-proof/trust.d.mts.map +1 -1
- package/dist/cli/verify-proof/trust.mjs +1 -1
- package/dist/driver-sdk/conformance.d.ts +41 -0
- package/dist/driver-sdk/conformance.d.ts.map +1 -0
- package/dist/driver-sdk/conformance.js +123 -0
- package/dist/driver-sdk/conformance.js.map +1 -0
- package/dist/driver-sdk/errors.d.ts +75 -0
- package/dist/driver-sdk/errors.d.ts.map +1 -0
- package/dist/driver-sdk/errors.js +80 -0
- package/dist/driver-sdk/errors.js.map +1 -0
- package/dist/driver-sdk/idl.d.ts +221 -0
- package/dist/driver-sdk/idl.d.ts.map +1 -0
- package/dist/driver-sdk/idl.js +140 -0
- package/dist/driver-sdk/idl.js.map +1 -0
- package/dist/driver-sdk/index.d.ts +28 -0
- package/dist/driver-sdk/index.d.ts.map +1 -0
- package/dist/driver-sdk/index.js +28 -0
- package/dist/driver-sdk/index.js.map +1 -0
- package/dist/driver-sdk/manifest.d.ts +93 -0
- package/dist/driver-sdk/manifest.d.ts.map +1 -0
- package/dist/driver-sdk/manifest.js +12 -0
- package/dist/driver-sdk/manifest.js.map +1 -0
- package/dist/driver-sdk/retry.d.ts +33 -0
- package/dist/driver-sdk/retry.d.ts.map +1 -0
- package/dist/driver-sdk/retry.js +50 -0
- package/dist/driver-sdk/retry.js.map +1 -0
- package/dist/plugin/.claude-plugin/plugin.json +2 -1
- package/dist/plugin/agents/apt-planner.md +34 -3
- package/dist/plugin/skills/apt-plan/SKILL.md +50 -0
- package/dist/plugin/skills/apt-pr-review/SKILL.md +1 -1
- package/dist/plugin/skills/apt-research/SKILL.md +526 -0
- package/dist/plugin/skills/apt-research/appendices/budget-loop.md +397 -0
- package/dist/plugin/skills/apt-research/appendices/claim-graph-schema.md +206 -0
- package/dist/plugin/skills/apt-research/appendices/domain-tools.md +236 -0
- package/dist/plugin/skills/apt-spar/SKILL.md +19 -7
- package/dist/plugin/skills/apt-verify-proof/SKILL.md +41 -2
- package/dist/schemas/quick-task.d.ts +17 -17
- package/drivers/.gitkeep +0 -0
- package/drivers/api/README.md +40 -0
- package/drivers/api/driver.mjs +59 -0
- package/drivers/api/manifest.json +26 -0
- package/drivers/browser/README.md +105 -0
- package/drivers/browser/driver.mjs +134 -0
- package/drivers/browser/manifest.json +35 -0
- package/drivers/cli/README.md +44 -0
- package/drivers/cli/driver.mjs +62 -0
- package/drivers/cli/manifest.json +28 -0
- package/drivers/electron/README.md +64 -0
- package/drivers/electron/driver.mjs +87 -0
- package/drivers/electron/manifest.json +37 -0
- package/package.json +7 -3
- package/skills/apt-plan/SKILL.md +50 -0
- package/skills/apt-planner.md +16 -0
- package/skills/apt-pr-review/SKILL.md +1 -1
- package/skills/apt-research/SKILL.md +526 -0
- package/skills/apt-research/appendices/budget-loop.md +397 -0
- package/skills/apt-research/appendices/claim-graph-schema.md +206 -0
- package/skills/apt-research/appendices/domain-tools.md +236 -0
- package/skills/apt-spar/SKILL.md +19 -7
- package/skills/apt-verify-proof/SKILL.md +41 -2
- package/src/cli/commands/coverage-check.mjs +126 -44
- package/src/cli/commands/detect-runtime.mjs +23 -14
- package/src/cli/coverage-check/user-outcomes.mjs +273 -0
- package/src/cli/host/detect.mjs +1 -1
- package/src/cli/install/install-from-source.mjs +3 -3
- package/src/cli/install/legacy-paths.mjs +2 -0
- package/src/cli/install/manifest.mjs +5 -0
- package/src/cli/install/pipeline.mjs +28 -0
- package/src/cli/install/transforms/codex.mjs +51 -2
- package/src/cli/install/version-header.mjs +7 -1
- package/src/cli/verify-proof/idl/index.mjs +7 -8
- package/src/cli/verify-proof/idl/types.ts +5 -4
- package/src/cli/verify-proof/manifest-schema.json +2 -1
- package/src/cli/verify-proof/resolver.mjs +171 -4
- package/src/cli/verify-proof/runtime-detect.mjs +99 -10
- package/src/cli/verify-proof/trust.mjs +1 -1
- package/templates/proof-verification.md +32 -3
- 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`.
|
package/skills/apt-spar/SKILL.md
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
-
|
|
87
|
-
|
|
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
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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
|
-
//
|
|
79
|
-
|
|
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 = []
|