@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.
- package/CHANGELOG.md +156 -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/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/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
|
@@ -8,9 +8,17 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Pattern: pure data in → pure data out. No filesystem walking, no exec.
|
|
10
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).
|
|
11
19
|
*/
|
|
12
20
|
|
|
13
|
-
import { existsSync, readFileSync } from 'node:fs'
|
|
21
|
+
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs'
|
|
14
22
|
import { join, resolve } from 'node:path'
|
|
15
23
|
|
|
16
24
|
/**
|
|
@@ -103,20 +111,101 @@ export function detectFromPackageJson(packageJson) {
|
|
|
103
111
|
|
|
104
112
|
/**
|
|
105
113
|
* Load and detect capabilities from a project directory's package.json.
|
|
106
|
-
*
|
|
107
|
-
*
|
|
114
|
+
* v0.8.6 — also walks `monorepo_globs` (default `['apps/*']`) and
|
|
115
|
+
* OR-merges each match's capabilities with the root.
|
|
108
116
|
*
|
|
109
117
|
* @param {string} projectDir
|
|
118
|
+
* @param {{monorepo_globs?: string[]}} [opts]
|
|
110
119
|
* @returns {RuntimeCapabilities}
|
|
111
120
|
*/
|
|
112
|
-
export function loadRuntimeCapabilities(projectDir) {
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
let
|
|
121
|
+
export function loadRuntimeCapabilities(projectDir, opts) {
|
|
122
|
+
const root = resolve(projectDir)
|
|
123
|
+
const rootPkgPath = join(root, 'package.json')
|
|
124
|
+
let rootCaps = emptyCapabilities()
|
|
125
|
+
if (existsSync(rootPkgPath)) {
|
|
126
|
+
try {
|
|
127
|
+
rootCaps = detectFromPackageJson(JSON.parse(readFileSync(rootPkgPath, 'utf-8')))
|
|
128
|
+
} catch {
|
|
129
|
+
/* fall through with empty caps */
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Default to the canonical Aperant monorepo layout. Passing `[]`
|
|
134
|
+
// disables the walk entirely (root-only escape hatch per spec ID-05).
|
|
135
|
+
const globs = opts && Array.isArray(opts.monorepo_globs) ? opts.monorepo_globs : ['apps/*']
|
|
136
|
+
if (globs.length === 0) return rootCaps
|
|
137
|
+
|
|
138
|
+
let merged = rootCaps
|
|
139
|
+
for (const glob of globs) {
|
|
140
|
+
const matches = expandSimpleGlob(root, glob)
|
|
141
|
+
for (const dir of matches) {
|
|
142
|
+
const childPkgPath = join(dir, 'package.json')
|
|
143
|
+
if (!existsSync(childPkgPath)) continue
|
|
144
|
+
try {
|
|
145
|
+
const childCaps = detectFromPackageJson(JSON.parse(readFileSync(childPkgPath, 'utf-8')))
|
|
146
|
+
merged = orMergeCapabilities(merged, childCaps)
|
|
147
|
+
} catch {
|
|
148
|
+
/* skip malformed package.json */
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return merged
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Expand a simple `prefix/*` glob against the root. Supports ONLY the
|
|
157
|
+
* `<dir>/*` shape (matches each direct subdirectory of `<dir>`). Other
|
|
158
|
+
* glob syntax is intentionally rejected — v1 keeps the dep surface
|
|
159
|
+
* small. Fails closed on malformed patterns (returns []).
|
|
160
|
+
*
|
|
161
|
+
* @param {string} root
|
|
162
|
+
* @param {string} glob
|
|
163
|
+
* @returns {string[]}
|
|
164
|
+
*/
|
|
165
|
+
function expandSimpleGlob(root, glob) {
|
|
166
|
+
if (typeof glob !== 'string' || !glob.endsWith('/*')) return []
|
|
167
|
+
const prefix = glob.slice(0, -2)
|
|
168
|
+
if (prefix.length === 0 || prefix.includes('*')) return []
|
|
169
|
+
const dir = join(root, prefix)
|
|
170
|
+
if (!existsSync(dir)) return []
|
|
171
|
+
let entries
|
|
116
172
|
try {
|
|
117
|
-
|
|
173
|
+
entries = readdirSync(dir)
|
|
118
174
|
} catch {
|
|
119
|
-
return
|
|
175
|
+
return []
|
|
176
|
+
}
|
|
177
|
+
const matches = []
|
|
178
|
+
for (const name of entries) {
|
|
179
|
+
const sub = join(dir, name)
|
|
180
|
+
try {
|
|
181
|
+
if (statSync(sub).isDirectory()) matches.push(sub)
|
|
182
|
+
} catch {
|
|
183
|
+
/* skip unreadable entries */
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return matches
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Boolean OR-merge of two capability records. Any flag true in either
|
|
191
|
+
* input is true in the output. The "Electron wins over web" rule from
|
|
192
|
+
* `detectFromPackageJson` is preserved when this is called repeatedly —
|
|
193
|
+
* each child's flags are honest about that child's own shape, and the
|
|
194
|
+
* OR-merge just unions them.
|
|
195
|
+
*
|
|
196
|
+
* @param {RuntimeCapabilities} a
|
|
197
|
+
* @param {RuntimeCapabilities} b
|
|
198
|
+
* @returns {RuntimeCapabilities}
|
|
199
|
+
*/
|
|
200
|
+
function orMergeCapabilities(a, b) {
|
|
201
|
+
return {
|
|
202
|
+
is_electron: a.is_electron || b.is_electron,
|
|
203
|
+
is_tauri: a.is_tauri || b.is_tauri,
|
|
204
|
+
is_nextjs: a.is_nextjs || b.is_nextjs,
|
|
205
|
+
is_nuxt: a.is_nuxt || b.is_nuxt,
|
|
206
|
+
is_web_frontend: a.is_web_frontend || b.is_web_frontend,
|
|
207
|
+
is_expo: a.is_expo || b.is_expo,
|
|
208
|
+
is_react_native: a.is_react_native || b.is_react_native,
|
|
209
|
+
has_node: a.has_node || b.has_node,
|
|
120
210
|
}
|
|
121
|
-
return detectFromPackageJson(parsed)
|
|
122
211
|
}
|
|
@@ -23,7 +23,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
|
23
23
|
import { dirname, join, resolve } from 'node:path'
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
|
-
* @typedef {import('@aperant/driver-sdk').DriverManifest} DriverManifest
|
|
26
|
+
* @typedef {import('@aperant/framework/driver-sdk').DriverManifest} DriverManifest
|
|
27
27
|
*/
|
|
28
28
|
|
|
29
29
|
const TOFU_PATH = '.aperant/driver-tofu.json'
|
|
@@ -53,7 +53,13 @@ JSON schema reference for `.aperant/proof/{app}/{date}/{run}/verification.json`
|
|
|
53
53
|
"tested_count": 0,
|
|
54
54
|
"skipped_count": 0,
|
|
55
55
|
"percent": 0,
|
|
56
|
-
"gate_result": "pass | warn | block"
|
|
56
|
+
"gate_result": "pass | warn | block",
|
|
57
|
+
"by_surface": {
|
|
58
|
+
"electron": { "tested": 0, "total": 0 },
|
|
59
|
+
"web": { "tested": 0, "total": 0 },
|
|
60
|
+
"cli": { "tested": 0, "total": 0 },
|
|
61
|
+
"api": { "tested": 0, "total": 0 }
|
|
62
|
+
}
|
|
57
63
|
},
|
|
58
64
|
|
|
59
65
|
"tests": [
|
|
@@ -61,14 +67,33 @@ JSON schema reference for `.aperant/proof/{app}/{date}/{run}/verification.json`
|
|
|
61
67
|
"id": "TEST-01",
|
|
62
68
|
"registry_id": "terminals-grid-auto-tile",
|
|
63
69
|
"name": "Three Terminal Grid Auto-Tile",
|
|
70
|
+
"outcome_id": "O3",
|
|
71
|
+
"surface": "electron",
|
|
72
|
+
"transport": "mcp",
|
|
64
73
|
"precondition": "2 terminals active",
|
|
65
74
|
"action": "Press Cmd+T",
|
|
66
75
|
"expected": "Grid reflows to 2+1 layout",
|
|
67
76
|
"actual": "Grid correctly tiled to 2+1",
|
|
68
|
-
"evidence": "screenshots/03-grid.png",
|
|
77
|
+
"evidence": "electron/screenshots/03-grid.png",
|
|
69
78
|
"analysis": "Screenshot shows three terminal panes. Top row has 2 equal-width panes, bottom row has 1 centered pane. Grid lines are clean, no overlap or gap artifacts. Each pane shows an active shell prompt with correct path.",
|
|
70
79
|
"result": "pass | fail | blocked | skip",
|
|
71
80
|
"severity": null
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"id": "TEST-02",
|
|
84
|
+
"registry_id": "settings-theme-picker",
|
|
85
|
+
"name": "Theme picker — cross-surface",
|
|
86
|
+
"outcome_id": "O5",
|
|
87
|
+
"surface": "web",
|
|
88
|
+
"transport": "skill",
|
|
89
|
+
"precondition": "App loaded in web shell",
|
|
90
|
+
"action": "Click 'Dark mode' in the theme picker",
|
|
91
|
+
"expected": "Background flips to dark; persists across reload",
|
|
92
|
+
"actual": "Dark mode applied, persisted across reload",
|
|
93
|
+
"evidence": "web/screenshots/05-dark-mode.png",
|
|
94
|
+
"analysis": "Screenshot shows app body with #111 background, all text legible on dark surface. After reload, dark mode is still active — localStorage entry confirmed.",
|
|
95
|
+
"result": "pass",
|
|
96
|
+
"severity": null
|
|
72
97
|
}
|
|
73
98
|
],
|
|
74
99
|
|
|
@@ -175,6 +200,7 @@ Each check has `status` (pass/fail) and `output` (captured terminal output). The
|
|
|
175
200
|
| `skipped_count` | number | Number of entries in the skipped array |
|
|
176
201
|
| `percent` | number | `tested_count / registered_count * 100` |
|
|
177
202
|
| `gate_result` | string | `block` if < 50%, `warn` if < 80%, `pass` if >= 80% |
|
|
203
|
+
| `by_surface` | object | v0.8.6 — per-surface rollup. Each surface key (`electron`, `web`, `cli`, `api`) maps to `{tested, total}` so reviewers can tell which surface fell short. |
|
|
178
204
|
|
|
179
205
|
### tests
|
|
180
206
|
Each test entry tracks one sub-feature verification. Fields:
|
|
@@ -184,11 +210,14 @@ Each test entry tracks one sub-feature verification. Fields:
|
|
|
184
210
|
| `id` | string | IMMUTABLE | Sequential ID: TEST-01, TEST-02, etc. |
|
|
185
211
|
| `registry_id` | string | IMMUTABLE | Must match a sub-feature `id` from `.aperant/features/{area}.json` |
|
|
186
212
|
| `name` | string | IMMUTABLE | Human-readable test name |
|
|
213
|
+
| `outcome_id` | string | IMMUTABLE | v0.8.6 — positional ID from `## User Outcomes` (e.g. `O1`, `O2`). Synthesized by `parseUserOutcomes`; passed through verbatim by `resolveDriversForOutcomes`. Optional for legacy specs without `## User Outcomes`. |
|
|
214
|
+
| `surface` | string | IMMUTABLE | v0.8.6 — one of `electron | web | cli | api`. Drives the per-surface screenshot path and the `coverage.by_surface` rollup. Cross-surface outcomes (`[electron+web]`) produce TWO test entries with the same `outcome_id` and different `surface` values. |
|
|
215
|
+
| `transport` | string | OVERWRITE | v0.8.6 — `skill | mcp | binary | http`. Populated by the workflow runner from the dispatcher's directive (per Step 7a of `workflows/verify-proof.md`). For browser-driver web outcomes on Skill-capable hosts the value is `"skill"`; on legacy MCP hosts it falls back to `"mcp"`. |
|
|
187
216
|
| `precondition` | string | IMMUTABLE | What must be true before testing |
|
|
188
217
|
| `action` | string | IMMUTABLE | Specific action to perform |
|
|
189
218
|
| `expected` | string | IMMUTABLE | Specific observable result expected |
|
|
190
219
|
| `actual` | string | OVERWRITE | What actually happened (filled after testing) |
|
|
191
|
-
| `evidence` | string | OVERWRITE | Screenshot filename or video timestamp |
|
|
220
|
+
| `evidence` | string | OVERWRITE | Screenshot filename or video timestamp. v0.8.6 — paths are PER-SURFACE: `{surface}/screenshots/{outcome-id}.png`. |
|
|
192
221
|
| `analysis` | string | OVERWRITE | Adversarial analysis of evidence — describe what you SEE, not what you expect |
|
|
193
222
|
| `result` | string | OVERWRITE | `pass`, `fail`, `blocked`, or `skip` |
|
|
194
223
|
| `severity` | string/null | OVERWRITE | `blocker`, `major`, `minor`, `cosmetic`, or `null` if pass |
|
|
@@ -43,13 +43,25 @@ cat implementation_plan.json 2>/dev/null
|
|
|
43
43
|
cat testable_features.json 2>/dev/null
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
-
Generate run ID and create proof directory
|
|
46
|
+
Generate run ID and create proof directory. v0.8.6 — when the spec
|
|
47
|
+
carries `## User Outcomes` with cross-surface tags (e.g.
|
|
48
|
+
`[electron+web]`), proof artifacts MUST land under PER-SURFACE
|
|
49
|
+
subdirectories so reviewers can tell at a glance which surface failed.
|
|
50
|
+
Set up the path template here:
|
|
51
|
+
|
|
47
52
|
```bash
|
|
48
53
|
RUN_ID="$(date +%H%M%S)"
|
|
49
54
|
APP_NAME=$(node -e "try{console.log(JSON.parse(require('fs').readFileSync('package.json','utf8')).name)}catch{console.log('app')}" 2>/dev/null)
|
|
50
55
|
PROOF_DIR=".aperant/proof/${APP_NAME}/$(date +%Y%m%d)/${RUN_ID}"
|
|
51
|
-
|
|
56
|
+
# Per-surface screenshot path template — Step 7c populates ${surface}
|
|
57
|
+
# from the per-outcome resolver dispatch:
|
|
58
|
+
# ${PROOF_DIR}/${surface}/screenshots/${outcome-id}.png
|
|
59
|
+
mkdir -p "${PROOF_DIR}"
|
|
52
60
|
```
|
|
61
|
+
|
|
62
|
+
Sub-directories under `${PROOF_DIR}/${surface}/screenshots/` are
|
|
63
|
+
created lazily in Step 7c when the workflow runner takes the first
|
|
64
|
+
screenshot for that surface.
|
|
53
65
|
</step>
|
|
54
66
|
|
|
55
67
|
<step name="pre_check">
|
|
@@ -83,6 +95,18 @@ If no driver satisfies a REQUIRED capability, the resolver throws
|
|
|
83
95
|
`unsatisfied_required_capability` — fail-closed, NOT a silent
|
|
84
96
|
`blocked_by_platform` skip (per ID-02).
|
|
85
97
|
|
|
98
|
+
**v0.8.6 — per-outcome dispatch.** When the spec carries
|
|
99
|
+
`## User Outcomes`, the workflow runner picks a driver PER OUTCOME
|
|
100
|
+
SURFACE via `resolveDriversForOutcomes()` instead of a single
|
|
101
|
+
per-run driver pick. Each outcome's `[surface]` tag maps to the
|
|
102
|
+
runtime-capability flag a driver must carry; `[electron+web]`
|
|
103
|
+
outcomes dispatch to TWO drivers (one per surface) sharing the same
|
|
104
|
+
`outcome_id`. The legacy `apt-tools detect-runtime .` call above
|
|
105
|
+
still works for the per-run "what driver would be picked?" preview
|
|
106
|
+
— it answers the OLD question. For the new per-outcome question,
|
|
107
|
+
the workflow runner imports `resolveDriversForOutcomes` directly
|
|
108
|
+
from the resolver module in Step 7c.
|
|
109
|
+
|
|
86
110
|
### 2b. Probe optional evidence-capture tools
|
|
87
111
|
|
|
88
112
|
```bash
|
|
@@ -142,6 +166,16 @@ If no feature registry exists:
|
|
|
142
166
|
- Suggest: "No feature registry found. Consider running `/apt:scan {area}` first for exhaustive coverage."
|
|
143
167
|
- Proceed with manual discovery below, but warn that coverage may be incomplete
|
|
144
168
|
|
|
169
|
+
**v0.8.6 — read `## User Outcomes` from spec.md FIRST when present.**
|
|
170
|
+
The PRD-shaped spec.md (STANDARD/DEEP tracks) ships a
|
|
171
|
+
`## User Outcomes` section that is the verify-proof input contract:
|
|
172
|
+
`**Epic:**` headline + N atomic outcomes tagged by `[surface]`. When
|
|
173
|
+
present, each outcome maps 1:1 to a test entry — the workflow runner
|
|
174
|
+
does NOT have to invent test units from the feature registry. Fall
|
|
175
|
+
back to the feature-registry-based discovery below ONLY when the spec
|
|
176
|
+
lacks `## User Outcomes` (the v0.8.x warn-not-block migration window
|
|
177
|
+
per spec ID-06; the section becomes a hard block in v0.9.0).
|
|
178
|
+
|
|
145
179
|
### 4b. Read the source code
|
|
146
180
|
|
|
147
181
|
You MUST read the actual source code for the features under test. Do not guess.
|
|
@@ -278,19 +312,49 @@ a local binary, or a built-in HTTP request). Workflow prose names ONLY
|
|
|
278
312
|
IDL verbs per ID-01 — concrete tool names live in
|
|
279
313
|
`packages/framework/drivers/<driver>/driver.mjs`.
|
|
280
314
|
|
|
315
|
+
**v0.8.6 — transport dispatch directive (MUST honor).** When
|
|
316
|
+
`driver.transport.skill_id` is set AND the host CLI exposes the
|
|
317
|
+
Skill tool AND the outcome surface is `web`, the workflow runner
|
|
318
|
+
MUST invoke `Skill({skill: driver.transport.skill_id, args: ...})`
|
|
319
|
+
(passing the IDL verb args) and MUST record `transport: "skill"` on
|
|
320
|
+
the resulting test entry in verification.json. When the host CLI
|
|
321
|
+
does NOT expose the Skill tool, fall back to the MCP transport via
|
|
322
|
+
the existing puppeteer MCP server and record `transport: "mcp"`.
|
|
323
|
+
The driver's `chooseTransport({has_skill_tool})` helper computes
|
|
324
|
+
the choice; the workflow runner is the site that knows
|
|
325
|
+
`has_skill_tool`. This directive is LLM-honored (the v1 verify-proof
|
|
326
|
+
runner is the host LLM following this workflow as prose); a
|
|
327
|
+
text-level grep guard (TD-08) pins the `MUST` language so future
|
|
328
|
+
edits cannot silently soften it to `SHOULD`.
|
|
329
|
+
|
|
281
330
|
### 7b. Wait for Result
|
|
282
331
|
Wait for the action to complete. If it involves loading, wait for the load.
|
|
283
332
|
Add a short delay (1-2 seconds) so the CDP capture also captures this state.
|
|
284
333
|
|
|
285
334
|
### 7c. Capture Evidence
|
|
286
|
-
Take a screenshot AFTER the action completes
|
|
335
|
+
Take a screenshot AFTER the action completes. v0.8.6 — screenshots
|
|
336
|
+
land under PER-SURFACE subdirectories, with the per-outcome driver
|
|
337
|
+
returned by `resolveDriversForOutcomes()`:
|
|
338
|
+
|
|
287
339
|
```
|
|
288
|
-
driver.
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
340
|
+
// Pick a driver per outcome surface (NOT per run). `entries[]` is
|
|
341
|
+
// the resolver's per-(outcome, surface) dispatch — for an
|
|
342
|
+
// [electron+web] outcome you get TWO entries sharing outcome_id
|
|
343
|
+
// and you loop both, capturing one screenshot per surface.
|
|
344
|
+
for (const entry of entries) {
|
|
345
|
+
await entry.driver.screenshot({
|
|
346
|
+
target: { kind: "css", value: "body" },
|
|
347
|
+
output_path: `${PROOF_DIR}/${entry.surface}/screenshots/${outcome.id}.png`
|
|
348
|
+
})
|
|
349
|
+
}
|
|
292
350
|
```
|
|
293
351
|
|
|
352
|
+
Single-surface outcomes (`[electron]`, `[web]`, `[cli]`, `[api]`)
|
|
353
|
+
produce one screenshot under `${PROOF_DIR}/${surface}/screenshots/`;
|
|
354
|
+
cross-surface (`[electron+web]`) produces two — one per surface tree
|
|
355
|
+
— with both test entries in `verification.json:tests[]` carrying the
|
|
356
|
+
same `id` but distinct `surface` and `evidence` fields.
|
|
357
|
+
|
|
294
358
|
### 7d. Analyze Evidence
|
|
295
359
|
**MANDATORY and CRITICAL.** Every screenshot must be analyzed in detail.
|
|
296
360
|
|
|
@@ -313,18 +377,23 @@ apt-proof-video analyze "${PROOF_DIR}/screenshots/{feature-id}-test-{n}.png" \
|
|
|
313
377
|
```
|
|
314
378
|
|
|
315
379
|
### 7e. Record Result to verification.json
|
|
316
|
-
After each test, update the test entry in verification.json `tests` array
|
|
380
|
+
After each test, update the test entry in verification.json `tests` array.
|
|
381
|
+
v0.8.6 adds `surface` and `transport` fields per test entry — the workflow
|
|
382
|
+
runner populates them from the per-outcome dispatch directive:
|
|
317
383
|
|
|
318
384
|
```json
|
|
319
385
|
{
|
|
320
386
|
"id": "TEST-01",
|
|
321
387
|
"registry_id": "terminals-grid-auto-tile",
|
|
322
388
|
"name": "Three Terminal Grid Auto-Tile",
|
|
389
|
+
"outcome_id": "O3",
|
|
390
|
+
"surface": "electron",
|
|
391
|
+
"transport": "mcp",
|
|
323
392
|
"precondition": "2 terminals active",
|
|
324
393
|
"action": "Press Cmd+T",
|
|
325
394
|
"expected": "Grid reflows to 2+1 layout",
|
|
326
395
|
"actual": "Grid correctly tiled",
|
|
327
|
-
"evidence": "screenshots/03-grid.png",
|
|
396
|
+
"evidence": "electron/screenshots/03-grid.png",
|
|
328
397
|
"analysis": "Detailed adversarial analysis of what is actually visible in the screenshot...",
|
|
329
398
|
"result": "pass",
|
|
330
399
|
"severity": null
|