@aperant/framework 0.8.0 → 0.8.4
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 +311 -1
- package/README.md +12 -0
- package/agents/apt-planner.md +5 -4
- package/dist/cli/commands/adr.mjs +1 -1
- package/dist/cli/commands/audit-branch-current.d.mts +25 -0
- package/dist/cli/commands/audit-branch-current.d.mts.map +1 -0
- package/dist/cli/commands/audit-branch-current.mjs +252 -0
- package/dist/cli/commands/audit-branch-current.mjs.map +1 -0
- package/dist/cli/commands/audit.d.mts.map +1 -1
- package/dist/cli/commands/audit.mjs +13 -2
- package/dist/cli/commands/audit.mjs.map +1 -1
- package/dist/cli/commands/check-version.d.mts.map +1 -1
- package/dist/cli/commands/check-version.mjs +19 -1
- package/dist/cli/commands/check-version.mjs.map +1 -1
- package/dist/cli/commands/context.mjs +3 -3
- package/dist/cli/commands/context.mjs.map +1 -1
- package/dist/cli/commands/detect-runtime.d.mts +6 -0
- package/dist/cli/commands/detect-runtime.d.mts.map +1 -0
- package/dist/cli/commands/detect-runtime.mjs +136 -0
- package/dist/cli/commands/detect-runtime.mjs.map +1 -0
- package/dist/cli/commands/driver-doctor.d.mts +29 -0
- package/dist/cli/commands/driver-doctor.d.mts.map +1 -0
- package/dist/cli/commands/driver-doctor.mjs +291 -0
- package/dist/cli/commands/driver-doctor.mjs.map +1 -0
- package/dist/cli/commands/gate.mjs +14 -0
- package/dist/cli/commands/gate.mjs.map +1 -1
- package/dist/cli/commands/init.d.mts.map +1 -1
- package/dist/cli/commands/init.mjs +76 -33
- package/dist/cli/commands/init.mjs.map +1 -1
- package/dist/cli/commands/install-from-source.d.mts +2 -0
- package/dist/cli/commands/install-from-source.d.mts.map +1 -0
- package/dist/cli/commands/install-from-source.mjs +2 -0
- package/dist/cli/commands/install-from-source.mjs.map +1 -0
- package/dist/cli/commands/modes.mjs +1 -1
- package/dist/cli/commands/route.d.mts.map +1 -1
- package/dist/cli/commands/route.mjs +38 -1
- package/dist/cli/commands/route.mjs.map +1 -1
- package/dist/cli/commands/triage.mjs +1 -1
- package/dist/cli/config/load.d.mts.map +1 -1
- package/dist/cli/config/load.mjs +9 -3
- package/dist/cli/config/load.mjs.map +1 -1
- package/dist/cli/coordination/auto-emit-artifact-ready.mjs +1 -1
- package/dist/cli/coordination/event-schema.d.mts.map +1 -1
- package/dist/cli/coordination/event-schema.mjs +18 -0
- package/dist/cli/coordination/event-schema.mjs.map +1 -1
- package/dist/cli/design/frontmatter-schema.d.mts +4 -4
- package/dist/cli/dispatch.d.mts.map +1 -1
- package/dist/cli/dispatch.mjs +18 -1
- package/dist/cli/dispatch.mjs.map +1 -1
- package/dist/cli/gate/context.d.mts +1 -0
- package/dist/cli/gate/context.d.mts.map +1 -1
- package/dist/cli/gate/context.mjs +14 -1
- package/dist/cli/gate/context.mjs.map +1 -1
- package/dist/cli/gate/gates/consistency-check.d.mts +1 -0
- package/dist/cli/gate/gates/consistency-check.d.mts.map +1 -1
- package/dist/cli/gate/gates/consistency-check.mjs +1 -0
- package/dist/cli/gate/gates/consistency-check.mjs.map +1 -1
- package/dist/cli/gate/gates/review-clean.d.mts +1 -0
- package/dist/cli/gate/gates/review-clean.d.mts.map +1 -1
- package/dist/cli/gate/gates/review-clean.mjs +1 -0
- package/dist/cli/gate/gates/review-clean.mjs.map +1 -1
- package/dist/cli/gate/gates/verify-approved.d.mts +1 -0
- package/dist/cli/gate/gates/verify-approved.d.mts.map +1 -1
- package/dist/cli/gate/gates/verify-approved.mjs +1 -0
- package/dist/cli/gate/gates/verify-approved.mjs.map +1 -1
- package/dist/cli/gate/registry.d.mts.map +1 -1
- package/dist/cli/gate/registry.mjs +9 -1
- package/dist/cli/gate/registry.mjs.map +1 -1
- package/dist/cli/install/cli-to-install-root.d.mts.map +1 -1
- package/dist/cli/install/cli-to-install-root.mjs +0 -2
- package/dist/cli/install/cli-to-install-root.mjs.map +1 -1
- package/dist/cli/install/find-workspace-framework-version.d.mts +16 -0
- package/dist/cli/install/find-workspace-framework-version.d.mts.map +1 -0
- package/dist/cli/install/find-workspace-framework-version.mjs +85 -0
- package/dist/cli/install/find-workspace-framework-version.mjs.map +1 -0
- package/dist/cli/install/install-from-source.d.mts +10 -0
- package/dist/cli/install/install-from-source.d.mts.map +1 -0
- package/dist/cli/install/install-from-source.mjs +271 -0
- package/dist/cli/install/install-from-source.mjs.map +1 -0
- package/dist/cli/install/install-kernel.d.mts.map +1 -1
- package/dist/cli/install/install-kernel.mjs +24 -0
- package/dist/cli/install/install-kernel.mjs.map +1 -1
- package/dist/cli/install/legacy-paths.d.mts +33 -11
- package/dist/cli/install/legacy-paths.d.mts.map +1 -1
- package/dist/cli/install/legacy-paths.mjs +161 -39
- package/dist/cli/install/legacy-paths.mjs.map +1 -1
- package/dist/cli/install/runtime-detect.d.mts +2 -6
- package/dist/cli/install/runtime-detect.d.mts.map +1 -1
- package/dist/cli/install/runtime-detect.mjs +9 -4
- package/dist/cli/install/runtime-detect.mjs.map +1 -1
- package/dist/cli/install/runtime-migrate.d.mts +34 -14
- package/dist/cli/install/runtime-migrate.d.mts.map +1 -1
- package/dist/cli/install/runtime-migrate.mjs +63 -30
- package/dist/cli/install/runtime-migrate.mjs.map +1 -1
- package/dist/cli/install/transforms/codex.d.mts.map +1 -1
- package/dist/cli/install/transforms/codex.mjs +28 -17
- package/dist/cli/install/transforms/codex.mjs.map +1 -1
- package/dist/cli/install/transforms/pi.d.mts +6 -0
- package/dist/cli/install/transforms/pi.d.mts.map +1 -0
- package/dist/cli/install/transforms/pi.mjs +37 -0
- package/dist/cli/install/transforms/pi.mjs.map +1 -0
- package/dist/cli/personas/sidecar.d.mts +1 -1
- package/dist/cli/roadmap/lifecycle.d.mts +1 -1
- package/dist/cli/roadmap/rollup.d.mts +2 -2
- package/dist/cli/skill-author/contract.mjs +2 -2
- package/dist/cli/skill-author/contract.mjs.map +1 -1
- package/dist/cli/verify-proof/audit.d.mts +34 -0
- package/dist/cli/verify-proof/audit.d.mts.map +1 -0
- package/dist/cli/verify-proof/audit.mjs +53 -0
- package/dist/cli/verify-proof/audit.mjs.map +1 -0
- package/dist/cli/verify-proof/exec.d.mts +20 -0
- package/dist/cli/verify-proof/exec.d.mts.map +1 -0
- package/dist/cli/verify-proof/exec.mjs +74 -0
- package/dist/cli/verify-proof/exec.mjs.map +1 -0
- package/dist/cli/verify-proof/idl/index.d.mts +2 -0
- package/dist/cli/verify-proof/idl/index.d.mts.map +1 -0
- package/dist/cli/verify-proof/idl/index.mjs +14 -0
- package/dist/cli/verify-proof/idl/index.mjs.map +1 -0
- package/dist/cli/verify-proof/idl/types.d.ts +9 -0
- package/dist/cli/verify-proof/idl/types.d.ts.map +1 -0
- package/dist/cli/verify-proof/idl/types.js +9 -0
- package/dist/cli/verify-proof/idl/types.js.map +1 -0
- package/dist/cli/verify-proof/manifest-validator.d.mts +19 -0
- package/dist/cli/verify-proof/manifest-validator.d.mts.map +1 -0
- package/dist/cli/verify-proof/manifest-validator.mjs +182 -0
- package/dist/cli/verify-proof/manifest-validator.mjs.map +1 -0
- package/dist/cli/verify-proof/resolver.d.mts +43 -0
- package/dist/cli/verify-proof/resolver.d.mts.map +1 -0
- package/dist/cli/verify-proof/resolver.mjs +160 -0
- package/dist/cli/verify-proof/resolver.mjs.map +1 -0
- package/dist/cli/verify-proof/runtime-detect.d.mts +27 -0
- package/dist/cli/verify-proof/runtime-detect.d.mts.map +1 -0
- package/dist/cli/verify-proof/runtime-detect.mjs +116 -0
- package/dist/cli/verify-proof/runtime-detect.mjs.map +1 -0
- package/dist/cli/verify-proof/trust.d.mts +70 -0
- package/dist/cli/verify-proof/trust.d.mts.map +1 -0
- package/dist/cli/verify-proof/trust.mjs +174 -0
- package/dist/cli/verify-proof/trust.mjs.map +1 -0
- package/dist/plugin/.claude-plugin/plugin.json +4 -1
- package/dist/plugin/agents/apt-executor.md +12 -0
- package/dist/plugin/agents/apt-planner.md +5 -4
- package/dist/plugin/agents/apt-team-docs-narrator.md +11 -0
- package/dist/plugin/skills/apt/SKILL.md +78 -8
- package/dist/plugin/skills/apt-caveman/SKILL.md +64 -0
- package/dist/plugin/skills/apt-debug/SKILL.md +36 -4
- package/dist/plugin/skills/apt-debug/appendices/diagnose-discipline.md +2 -11
- package/dist/plugin/skills/apt-discuss/SKILL.md +4 -4
- package/dist/plugin/skills/apt-discuss/appendices/grill-discipline.md +2 -11
- package/dist/plugin/skills/apt-discuss/appendices/zoom-out-helper.md +0 -8
- package/dist/plugin/skills/apt-execute/SKILL.md +40 -4
- package/dist/plugin/skills/apt-execute/appendices/tdd-mode.md +2 -10
- package/dist/plugin/skills/apt-fan-out/SKILL.md +159 -0
- package/dist/plugin/skills/apt-handoff/SKILL.md +228 -0
- package/dist/plugin/skills/apt-improve/DEEPENING.md +0 -8
- package/dist/plugin/skills/apt-improve/INTERFACE-DESIGN.md +0 -8
- package/dist/plugin/skills/apt-improve/LANGUAGE.md +0 -8
- package/dist/plugin/skills/apt-improve/SKILL.md +4 -4
- package/dist/plugin/skills/apt-plan/SKILL.md +23 -5
- package/dist/plugin/skills/apt-plan/adapters/conductor.md +1 -1
- package/dist/plugin/skills/apt-pr-review/SKILL.md +8 -1
- package/dist/plugin/skills/apt-prototype/LOGIC.md +0 -8
- package/dist/plugin/skills/apt-prototype/SKILL.md +4 -4
- package/dist/plugin/skills/apt-prototype/UI.md +0 -8
- package/dist/plugin/skills/apt-quick/SKILL.md +31 -1
- package/dist/plugin/skills/apt-resume/SKILL.md +54 -31
- package/dist/plugin/skills/apt-review/SKILL.md +4 -0
- package/dist/plugin/skills/apt-run/SKILL.md +134 -21
- package/dist/plugin/skills/apt-setup/SKILL.md +97 -8
- package/dist/plugin/skills/apt-ship/SKILL.md +165 -9
- package/dist/plugin/skills/apt-spar/SKILL.md +77 -25
- package/dist/plugin/skills/apt-triage/AGENT-BRIEF.md +0 -8
- package/dist/plugin/skills/apt-triage/OUT-OF-SCOPE.md +0 -7
- package/dist/plugin/skills/apt-triage/SKILL.md +2 -2
- package/dist/plugin/skills/apt-update/SKILL.md +74 -17
- package/dist/plugin/skills/apt-verify/SKILL.md +11 -0
- package/dist/plugin/skills/apt-verify-proof/SKILL.md +48 -1
- package/dist/plugin/skills/apt-watch-ci/SKILL.md +2 -2
- package/dist/plugin/skills/apt-zoom-out/SKILL.md +3 -3
- package/dist/schemas/quick-task.d.ts +17 -17
- package/dist/schemas/quick-task.d.ts.map +1 -1
- package/dist/schemas/quick-task.js +2 -2
- package/dist/schemas/quick-task.js.map +1 -1
- package/dist/types/config.d.ts +33 -0
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/state.d.ts +1 -1
- package/dist/types/state.d.ts.map +1 -1
- package/dist/types/task-record.d.ts +1 -1
- package/dist/types/task-record.d.ts.map +1 -1
- package/package.json +134 -133
- package/prompts/planner.md +1 -1
- package/prompts/spec_writer.md +1 -1
- package/skills/apt/SKILL.md +78 -8
- package/skills/apt-caveman/SKILL.md +64 -0
- package/skills/apt-debug/SKILL.md +36 -4
- package/skills/apt-debug/appendices/diagnose-discipline.md +2 -11
- package/skills/apt-discuss/SKILL.md +4 -4
- package/skills/apt-discuss/appendices/grill-discipline.md +2 -11
- package/skills/apt-discuss/appendices/zoom-out-helper.md +0 -8
- package/skills/apt-execute/SKILL.md +27 -4
- package/skills/apt-execute/appendices/tdd-mode.md +2 -10
- package/skills/apt-fan-out/SKILL.md +36 -1
- package/skills/apt-handoff/SKILL.md +228 -0
- package/skills/apt-improve/DEEPENING.md +0 -8
- package/skills/apt-improve/INTERFACE-DESIGN.md +0 -8
- package/skills/apt-improve/LANGUAGE.md +0 -8
- package/skills/apt-improve/SKILL.md +4 -4
- package/skills/apt-plan/SKILL.md +23 -5
- package/skills/apt-plan/adapters/conductor.md +1 -1
- package/skills/apt-planner.md +1 -1
- package/skills/apt-prototype/LOGIC.md +0 -8
- package/skills/apt-prototype/SKILL.md +4 -4
- package/skills/apt-prototype/UI.md +0 -8
- package/skills/apt-quick/SKILL.md +31 -1
- package/skills/apt-resume/SKILL.md +54 -31
- package/skills/apt-run/SKILL.md +114 -16
- package/skills/apt-setup/SKILL.md +97 -8
- package/skills/apt-ship/SKILL.md +165 -9
- package/skills/apt-spar/SKILL.md +65 -16
- package/skills/apt-triage/AGENT-BRIEF.md +0 -8
- package/skills/apt-triage/OUT-OF-SCOPE.md +0 -7
- package/skills/apt-triage/SKILL.md +2 -2
- package/skills/apt-update/SKILL.md +74 -17
- package/skills/apt-verify-proof/SKILL.md +48 -1
- package/skills/apt-zoom-out/SKILL.md +3 -3
- package/src/cli/commands/adr.mjs +1 -1
- package/src/cli/commands/audit-branch-current.mjs +250 -0
- package/src/cli/commands/audit.mjs +13 -2
- package/src/cli/commands/check-version.mjs +20 -1
- package/src/cli/commands/context.mjs +3 -3
- package/src/cli/commands/detect-runtime.mjs +134 -0
- package/src/cli/commands/driver-doctor.mjs +294 -0
- package/src/cli/commands/gate.mjs +15 -0
- package/src/cli/commands/init.mjs +75 -33
- package/src/cli/commands/install-from-source.mjs +1 -0
- package/src/cli/commands/modes.mjs +1 -1
- package/src/cli/commands/route.mjs +42 -1
- package/src/cli/commands/triage.mjs +1 -1
- package/src/cli/config/load.mjs +9 -3
- package/src/cli/coordination/auto-emit-artifact-ready.mjs +1 -1
- package/src/cli/coordination/event-schema.mjs +19 -0
- package/src/cli/dispatch.mjs +17 -1
- package/src/cli/gate/context.mjs +20 -1
- package/src/cli/gate/gates/consistency-check.mjs +1 -0
- package/src/cli/gate/gates/review-clean.mjs +1 -0
- package/src/cli/gate/gates/verify-approved.mjs +1 -0
- package/src/cli/gate/registry.mjs +11 -1
- package/src/cli/install/cli-to-install-root.mjs +0 -2
- package/src/cli/install/find-workspace-framework-version.mjs +88 -0
- package/src/cli/install/install-from-source.mjs +276 -0
- package/src/cli/install/install-kernel.mjs +24 -0
- package/src/cli/install/legacy-paths.mjs +162 -39
- package/src/cli/install/runtime-detect.mjs +9 -4
- package/src/cli/install/runtime-migrate.mjs +63 -30
- package/src/cli/install/transforms/codex.mjs +26 -17
- package/src/cli/install/transforms/pi.mjs +37 -0
- package/src/cli/skill-author/contract.mjs +2 -2
- package/src/cli/verify-proof/.gitkeep +0 -0
- package/src/cli/verify-proof/audit.mjs +55 -0
- package/src/cli/verify-proof/exec.mjs +74 -0
- package/src/cli/verify-proof/idl/index.mjs +24 -0
- package/src/cli/verify-proof/idl/types.ts +41 -0
- package/src/cli/verify-proof/manifest-schema.json +211 -0
- package/src/cli/verify-proof/manifest-validator.mjs +184 -0
- package/src/cli/verify-proof/resolver.mjs +163 -0
- package/src/cli/verify-proof/runtime-detect.mjs +122 -0
- package/src/cli/verify-proof/trust.mjs +187 -0
- package/templates/adr-format.md +0 -8
- package/templates/aperant-claude-md-appendix.md +1 -1
- package/templates/context-format.md +1 -9
- package/templates/proof-verification.md +19 -5
- package/workflows/verify-proof.md +56 -14
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* runtime-detect.mjs — pure project-shape capability detection.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from packages/core/src/prompts/prompt-loader.ts:572-595 so the
|
|
5
|
+
* framework CLI is self-contained (no @aperant/core dep). Same heuristics:
|
|
6
|
+
* read package.json's dependencies + devDependencies, return a capability
|
|
7
|
+
* record the resolver can score against.
|
|
8
|
+
*
|
|
9
|
+
* Pattern: pure data in → pure data out. No filesystem walking, no exec.
|
|
10
|
+
* Use loadRuntimeCapabilities(projectDir) for the cwd-rooted variant.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
14
|
+
import { join, resolve } from 'node:path'
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @typedef {Object} RuntimeCapabilities
|
|
18
|
+
* @property {boolean} is_electron
|
|
19
|
+
* @property {boolean} is_tauri
|
|
20
|
+
* @property {boolean} is_nextjs
|
|
21
|
+
* @property {boolean} is_nuxt
|
|
22
|
+
* @property {boolean} is_web_frontend
|
|
23
|
+
* @property {boolean} is_expo
|
|
24
|
+
* @property {boolean} is_react_native
|
|
25
|
+
* @property {boolean} has_node
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/** A capability record with every flag defaulted to false. */
|
|
29
|
+
function emptyCapabilities() {
|
|
30
|
+
return {
|
|
31
|
+
is_electron: false,
|
|
32
|
+
is_tauri: false,
|
|
33
|
+
is_nextjs: false,
|
|
34
|
+
is_nuxt: false,
|
|
35
|
+
is_web_frontend: false,
|
|
36
|
+
is_expo: false,
|
|
37
|
+
is_react_native: false,
|
|
38
|
+
has_node: false,
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Detect runtime capabilities from a parsed package.json object.
|
|
44
|
+
*
|
|
45
|
+
* @param {Record<string, unknown> | null} packageJson
|
|
46
|
+
* @returns {RuntimeCapabilities}
|
|
47
|
+
*/
|
|
48
|
+
export function detectFromPackageJson(packageJson) {
|
|
49
|
+
const capabilities = emptyCapabilities()
|
|
50
|
+
if (packageJson === null || typeof packageJson !== 'object') return capabilities
|
|
51
|
+
|
|
52
|
+
const allDeps = {
|
|
53
|
+
...(packageJson.dependencies ?? {}) /** @type {Record<string,string>} */,
|
|
54
|
+
...(packageJson.devDependencies ?? {}) /** @type {Record<string,string>} */,
|
|
55
|
+
}
|
|
56
|
+
const deps = new Set(Object.keys(allDeps).map((k) => k.toLowerCase()))
|
|
57
|
+
|
|
58
|
+
if (deps.has('electron') || [...deps].some((d) => d.startsWith('@electron'))) {
|
|
59
|
+
capabilities.is_electron = true
|
|
60
|
+
}
|
|
61
|
+
if (deps.has('@tauri-apps/api') || deps.has('tauri')) {
|
|
62
|
+
capabilities.is_tauri = true
|
|
63
|
+
}
|
|
64
|
+
if (deps.has('expo')) capabilities.is_expo = true
|
|
65
|
+
if (deps.has('react-native')) capabilities.is_react_native = true
|
|
66
|
+
|
|
67
|
+
// Web frontends — react/vue/svelte/angular/solid → frontend. BUT: an
|
|
68
|
+
// Electron app commonly bundles React as its renderer; in that case the
|
|
69
|
+
// runtime is "electron", not "web_frontend". Electron + react-native +
|
|
70
|
+
// expo + tauri all win over the web flag.
|
|
71
|
+
const isNativeContainer =
|
|
72
|
+
capabilities.is_electron ||
|
|
73
|
+
capabilities.is_tauri ||
|
|
74
|
+
capabilities.is_react_native ||
|
|
75
|
+
capabilities.is_expo
|
|
76
|
+
const webFrameworks = ['react', 'vue', 'svelte', 'angular', 'solid']
|
|
77
|
+
if (!isNativeContainer && webFrameworks.some((f) => deps.has(f))) {
|
|
78
|
+
capabilities.is_web_frontend = true
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (deps.has('next')) {
|
|
82
|
+
capabilities.is_nextjs = true
|
|
83
|
+
capabilities.is_web_frontend = true
|
|
84
|
+
}
|
|
85
|
+
if (deps.has('nuxt')) {
|
|
86
|
+
capabilities.is_nuxt = true
|
|
87
|
+
capabilities.is_web_frontend = true
|
|
88
|
+
}
|
|
89
|
+
if (deps.has('vite') && !isNativeContainer) {
|
|
90
|
+
capabilities.is_web_frontend = true
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Node-ish? Anything with a package.json + an entry point counts.
|
|
94
|
+
if (typeof packageJson.main === 'string' || typeof packageJson.module === 'string') {
|
|
95
|
+
capabilities.has_node = true
|
|
96
|
+
}
|
|
97
|
+
if (!capabilities.has_node && packageJson.scripts && typeof packageJson.scripts === 'object') {
|
|
98
|
+
capabilities.has_node = true
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return capabilities
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Load and detect capabilities from a project directory's package.json.
|
|
106
|
+
* Returns empty capabilities if no package.json exists (malformed or
|
|
107
|
+
* missing — both cases fall through to "nothing detected").
|
|
108
|
+
*
|
|
109
|
+
* @param {string} projectDir
|
|
110
|
+
* @returns {RuntimeCapabilities}
|
|
111
|
+
*/
|
|
112
|
+
export function loadRuntimeCapabilities(projectDir) {
|
|
113
|
+
const pkgPath = join(resolve(projectDir), 'package.json')
|
|
114
|
+
if (!existsSync(pkgPath)) return emptyCapabilities()
|
|
115
|
+
let parsed
|
|
116
|
+
try {
|
|
117
|
+
parsed = JSON.parse(readFileSync(pkgPath, 'utf-8'))
|
|
118
|
+
} catch {
|
|
119
|
+
return emptyCapabilities()
|
|
120
|
+
}
|
|
121
|
+
return detectFromPackageJson(parsed)
|
|
122
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* trust.mjs — driver trust enforcement (TOFU lock + policy file + decision).
|
|
3
|
+
*
|
|
4
|
+
* Implements ID-03:
|
|
5
|
+
* - 4 trust tiers: first_party_signed (default-allow), community_verified,
|
|
6
|
+
* community_unverified, local_unverified.
|
|
7
|
+
* - Default policy: only first_party_signed auto-runs.
|
|
8
|
+
* - Non-first-party drivers require one-time user approval; approval is
|
|
9
|
+
* bound to a TOFU lock at .aperant/driver-tofu.json keyed by
|
|
10
|
+
* {driver_id, version, manifest_sha256, artifact_digest}. Any drift
|
|
11
|
+
* re-prompts.
|
|
12
|
+
*
|
|
13
|
+
* The audit log (audit.mjs) writes one line per decision (allow/deny).
|
|
14
|
+
* The execution layer (exec.mjs) enforces structured-only local_command.
|
|
15
|
+
*
|
|
16
|
+
* This module is decision-only: it does NOT prompt the user. The caller
|
|
17
|
+
* (workflow runner / apt-tools driver doctor) decides whether to invoke
|
|
18
|
+
* the prompt UX, then calls recordApproval() to persist the TOFU entry.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { createHash } from 'node:crypto'
|
|
22
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
23
|
+
import { dirname, join, resolve } from 'node:path'
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @typedef {import('@aperant/driver-sdk').DriverManifest} DriverManifest
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
const TOFU_PATH = '.aperant/driver-tofu.json'
|
|
30
|
+
const POLICY_PATH = '.aperant/policy.json'
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Compute a stable manifest hash for TOFU + audit lines.
|
|
34
|
+
* @param {DriverManifest} manifest
|
|
35
|
+
* @returns {string}
|
|
36
|
+
*/
|
|
37
|
+
export function manifestSha256(manifest) {
|
|
38
|
+
const stable = JSON.stringify(manifest, Object.keys(manifest).sort())
|
|
39
|
+
return createHash('sha256').update(stable).digest('hex')
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Read the TOFU lock file. Returns an empty object if missing or malformed.
|
|
44
|
+
* @param {string} projectDir
|
|
45
|
+
* @returns {Record<string, {manifest_sha256: string, artifact_digest?: string, approved_at: string, trust_tier: string}>}
|
|
46
|
+
*/
|
|
47
|
+
export function readTofuLock(projectDir) {
|
|
48
|
+
const path = join(resolve(projectDir), TOFU_PATH)
|
|
49
|
+
if (!existsSync(path)) return {}
|
|
50
|
+
try {
|
|
51
|
+
return JSON.parse(readFileSync(path, 'utf-8'))
|
|
52
|
+
} catch {
|
|
53
|
+
return {}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Persist a TOFU lock file (atomic-ish: write + rename not used since we
|
|
59
|
+
* append-only to a tiny JSON; conflicting writes from parallel agents
|
|
60
|
+
* would clobber an entry but `.aperant/driver-tofu.json` is per-project,
|
|
61
|
+
* not per-agent).
|
|
62
|
+
*
|
|
63
|
+
* @param {string} projectDir
|
|
64
|
+
* @param {Record<string, unknown>} tofu
|
|
65
|
+
*/
|
|
66
|
+
export function writeTofuLock(projectDir, tofu) {
|
|
67
|
+
const path = join(resolve(projectDir), TOFU_PATH)
|
|
68
|
+
mkdirSync(dirname(path), { recursive: true })
|
|
69
|
+
writeFileSync(path, JSON.stringify(tofu, null, 2) + '\n', 'utf-8')
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Build the canonical TOFU key for a (driver, version) pair. The hash is
|
|
74
|
+
* NOT part of the key — the value's manifest_sha256 + artifact_digest
|
|
75
|
+
* detect drift.
|
|
76
|
+
* @param {string} driverId
|
|
77
|
+
* @param {string} version
|
|
78
|
+
* @returns {string}
|
|
79
|
+
*/
|
|
80
|
+
export function tofuKey(driverId, version) {
|
|
81
|
+
return `${driverId}@${version}`
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Read the project's policy file. Returns the default policy if missing.
|
|
86
|
+
* @param {string} projectDir
|
|
87
|
+
* @returns {{mode: 'first_party_only' | 'allowlist', allowed: string[], denied: string[]}}
|
|
88
|
+
*/
|
|
89
|
+
export function readPolicy(projectDir) {
|
|
90
|
+
const path = join(resolve(projectDir), POLICY_PATH)
|
|
91
|
+
const defaults = { mode: 'first_party_only', allowed: [], denied: [] }
|
|
92
|
+
if (!existsSync(path)) return defaults
|
|
93
|
+
try {
|
|
94
|
+
const parsed = JSON.parse(readFileSync(path, 'utf-8'))
|
|
95
|
+
return {
|
|
96
|
+
mode: parsed.mode === 'allowlist' ? 'allowlist' : 'first_party_only',
|
|
97
|
+
allowed: Array.isArray(parsed.allowed) ? parsed.allowed : [],
|
|
98
|
+
denied: Array.isArray(parsed.denied) ? parsed.denied : [],
|
|
99
|
+
}
|
|
100
|
+
} catch {
|
|
101
|
+
return defaults
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Decide whether a driver invocation is allowed.
|
|
107
|
+
*
|
|
108
|
+
* @param {DriverManifest} manifest
|
|
109
|
+
* @param {ReturnType<typeof readTofuLock>} tofu
|
|
110
|
+
* @param {ReturnType<typeof readPolicy>} policy
|
|
111
|
+
* @returns {{decision: 'allow' | 'deny' | 'requires_approval', reason: string, expected_manifest_sha256?: string}}
|
|
112
|
+
*/
|
|
113
|
+
export function decide(manifest, tofu, policy) {
|
|
114
|
+
const tier = manifest.trust?.tier
|
|
115
|
+
const driverId = manifest.driverId
|
|
116
|
+
const version = manifest.version
|
|
117
|
+
|
|
118
|
+
// Policy denylist always wins.
|
|
119
|
+
if (policy.denied.includes(driverId)) {
|
|
120
|
+
return { decision: 'deny', reason: `policy denies driver ${driverId}` }
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// first_party_signed: default-allow under either policy mode (unless
|
|
124
|
+
// explicitly denied above).
|
|
125
|
+
if (tier === 'first_party_signed') {
|
|
126
|
+
return { decision: 'allow', reason: 'first_party_signed driver — default allow' }
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// first_party_only mode blocks every other tier.
|
|
130
|
+
if (policy.mode === 'first_party_only') {
|
|
131
|
+
return {
|
|
132
|
+
decision: 'deny',
|
|
133
|
+
reason: `policy.mode=first_party_only blocks ${tier} driver`,
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// allowlist mode: require explicit allow entry AND a valid TOFU lock.
|
|
138
|
+
if (!policy.allowed.includes(driverId)) {
|
|
139
|
+
return {
|
|
140
|
+
decision: 'deny',
|
|
141
|
+
reason: `policy.mode=allowlist but ${driverId} not in allowed[]`,
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Check TOFU lock.
|
|
146
|
+
const key = tofuKey(driverId, version)
|
|
147
|
+
const entry = tofu[key]
|
|
148
|
+
const currentHash = manifestSha256(manifest)
|
|
149
|
+
|
|
150
|
+
if (!entry) {
|
|
151
|
+
return {
|
|
152
|
+
decision: 'requires_approval',
|
|
153
|
+
reason: `no TOFU entry for ${key} — first run requires approval`,
|
|
154
|
+
expected_manifest_sha256: currentHash,
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (entry.manifest_sha256 !== currentHash) {
|
|
159
|
+
return {
|
|
160
|
+
decision: 'requires_approval',
|
|
161
|
+
reason: `TOFU manifest_sha256 drift for ${key} — driver changed since approval, re-prompt required`,
|
|
162
|
+
expected_manifest_sha256: currentHash,
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return { decision: 'allow', reason: `TOFU entry valid for ${key}` }
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Persist a one-time approval for a (driver, version) pair into the TOFU
|
|
171
|
+
* lock. The caller is responsible for actually prompting the user; this
|
|
172
|
+
* function just writes the entry.
|
|
173
|
+
*
|
|
174
|
+
* @param {string} projectDir
|
|
175
|
+
* @param {DriverManifest} manifest
|
|
176
|
+
*/
|
|
177
|
+
export function recordApproval(projectDir, manifest) {
|
|
178
|
+
const tofu = readTofuLock(projectDir)
|
|
179
|
+
const key = tofuKey(manifest.driverId, manifest.version)
|
|
180
|
+
tofu[key] = {
|
|
181
|
+
manifest_sha256: manifestSha256(manifest),
|
|
182
|
+
artifact_digest: manifest.trust?.integrity?.artifact_digest,
|
|
183
|
+
approved_at: new Date().toISOString(),
|
|
184
|
+
trust_tier: manifest.trust?.tier,
|
|
185
|
+
}
|
|
186
|
+
writeTofuLock(projectDir, tofu)
|
|
187
|
+
}
|
package/templates/adr-format.md
CHANGED
|
@@ -1,11 +1,3 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
Adapted from Matt Pocock's MIT-licensed skill suite:
|
|
3
|
-
https://github.com/mattpocock/skills/blob/main/skills/engineering/grill-with-docs/ADR-FORMAT.md
|
|
4
|
-
Licensed under MIT. Modifications: ported as the Aperant ADR template
|
|
5
|
-
emitted by `apt-tools adr write` with Nygard's 3-gate filter
|
|
6
|
-
(hard-to-reverse AND surprising-without-context AND real-trade-off).
|
|
7
|
-
-->
|
|
8
|
-
|
|
9
1
|
# ADR-NNNN: {Title}
|
|
10
2
|
|
|
11
3
|
**Status:** {Proposed | Accepted | Deprecated | Superseded}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
This project uses the [Aperant Framework](https://github.com/Mikalsen-AI/aperant-cloud-desktop/tree/main/packages/framework) for AI-assisted development.
|
|
2
2
|
|
|
3
|
-
### Fast Path Guarantee
|
|
3
|
+
### Fast Path Guarantee
|
|
4
4
|
|
|
5
5
|
QUICK track is **hard-exempt** from PRD-shaped spec.md, HITL/AFK metadata,
|
|
6
6
|
vertical-slice subtask schema, TDD iron law (`apt:execute --tdd`), triage
|
|
@@ -1,15 +1,7 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
Adapted from Matt Pocock's MIT-licensed skill suite:
|
|
3
|
-
https://github.com/mattpocock/skills/blob/main/skills/engineering/grill-with-docs/CONTEXT-FORMAT.md
|
|
4
|
-
Licensed under MIT. Modifications: ported as the Aperant CONTEXT.md
|
|
5
|
-
template emitted by `apt-tools context write`. Adds the "Project glossary"
|
|
6
|
-
header and example dialogue section.
|
|
7
|
-
-->
|
|
8
|
-
|
|
9
1
|
# Project glossary (CONTEXT.md)
|
|
10
2
|
|
|
11
3
|
This file is the persistent project glossary. Each term-block carries the
|
|
12
|
-
following fields per
|
|
4
|
+
following fields per the Aperant CONTEXT.md format:
|
|
13
5
|
|
|
14
6
|
- **{Term}** — definition (one line).
|
|
15
7
|
- **Aliases to avoid:** comma-separated synonyms or near-synonyms that
|
|
@@ -18,7 +18,17 @@ JSON schema reference for `.aperant/proof/{app}/{date}/{run}/verification.json`
|
|
|
18
18
|
"updated": "{ISO timestamp}",
|
|
19
19
|
"spec": "{spec.md path or task description}",
|
|
20
20
|
"verdict": "pending | approved | approved_with_notes | rejected",
|
|
21
|
-
"tools": ["electron", "puppeteer", "cdp_video"]
|
|
21
|
+
"tools": ["electron", "puppeteer", "cdp_video"],
|
|
22
|
+
"selected_driver": {
|
|
23
|
+
"driverId": "electron",
|
|
24
|
+
"version": "0.1.0",
|
|
25
|
+
"manifest_sha256": "abc123...",
|
|
26
|
+
"trust_tier": "first_party_signed",
|
|
27
|
+
"sandbox_profile": "workspace_write"
|
|
28
|
+
},
|
|
29
|
+
"fallback_attempts": [
|
|
30
|
+
{ "driver_id": "browser", "reason": "missing required capabilities: dump_state" }
|
|
31
|
+
]
|
|
22
32
|
},
|
|
23
33
|
|
|
24
34
|
"automated_checks": {
|
|
@@ -66,8 +76,8 @@ JSON schema reference for `.aperant/proof/{app}/{date}/{run}/verification.json`
|
|
|
66
76
|
{
|
|
67
77
|
"registry_id": "terminals-settings-scrollback-slider",
|
|
68
78
|
"name": "Scrollback buffer slider",
|
|
69
|
-
"reason": "
|
|
70
|
-
"detail": "
|
|
79
|
+
"reason": "unsatisfied_optional_capability",
|
|
80
|
+
"detail": "Selected driver does not implement the IDL verb required for slider interaction"
|
|
71
81
|
}
|
|
72
82
|
],
|
|
73
83
|
|
|
@@ -149,7 +159,9 @@ JSON schema reference for `.aperant/proof/{app}/{date}/{run}/verification.json`
|
|
|
149
159
|
| `updated` | string | OVERWRITE | ISO timestamp, updated on every write |
|
|
150
160
|
| `spec` | string | IMMUTABLE | Path to spec or task description |
|
|
151
161
|
| `verdict` | string | OVERWRITE | Derived from dimension scores after all tests |
|
|
152
|
-
| `tools` | array | IMMUTABLE | MCP tools detected as available |
|
|
162
|
+
| `tools` | array | IMMUTABLE | MCP tools / auxiliary capture tools detected as available |
|
|
163
|
+
| `selected_driver` | object | IMMUTABLE | The verify-proof driver chosen by the resolver per ID-02. Fields: `driverId`, `version`, `manifest_sha256`, `trust_tier`, `sandbox_profile`. |
|
|
164
|
+
| `fallback_attempts` | array | IMMUTABLE | Drivers the resolver REJECTED before selecting. Each entry has `driver_id` + `reason` (missing capabilities, stability filter, hash drift, policy denial). Audit trail for coverage-loss decisions. |
|
|
153
165
|
|
|
154
166
|
### automated_checks
|
|
155
167
|
Each check has `status` (pass/fail) and `output` (captured terminal output). These are populated in step 3 before any manual testing begins.
|
|
@@ -188,9 +200,11 @@ Every registered sub-feature that does NOT have a test MUST appear here. Silence
|
|
|
188
200
|
|-------|------|-------------|
|
|
189
201
|
| `registry_id` | string | The sub-feature `id` from the feature registry |
|
|
190
202
|
| `name` | string | Human-readable name of the skipped feature |
|
|
191
|
-
| `reason` | string | One of: `time_constraint`, `
|
|
203
|
+
| `reason` | string | One of: `time_constraint`, `unsatisfied_optional_capability` (preferred), `depends_on_other`, `not_applicable`. **Legacy alias:** `blocked_by_platform` is read by the schema reader as an alias for `unsatisfied_optional_capability` and emits a deprecation warning. New writes MUST use the new reason. |
|
|
192
204
|
| `detail` | string | Brief explanation of why this feature was skipped |
|
|
193
205
|
|
|
206
|
+
> **Fail-closed, not a skip.** If a REQUIRED IDL capability has no implementing driver in the resolver chain, the resolver throws `unsatisfied_required_capability` (per ID-02) — aborts the run. That entry does NOT land in `skipped[]`; coverage loss is loud, deliberate, and audited via `meta.fallback_attempts[]`.
|
|
207
|
+
|
|
194
208
|
### dimensions
|
|
195
209
|
Four QA dimensions, each with `score` (1-10) and `rationale` (string). Scores are evidence-based, never assumed.
|
|
196
210
|
|
|
@@ -53,17 +53,47 @@ mkdir -p "${PROOF_DIR}/screenshots"
|
|
|
53
53
|
</step>
|
|
54
54
|
|
|
55
55
|
<step name="pre_check">
|
|
56
|
-
## 2. Pre-Check
|
|
56
|
+
## 2. Pre-Check + Driver Selection
|
|
57
57
|
|
|
58
58
|
If `implementation_plan.json` exists, verify all subtasks are completed.
|
|
59
59
|
If any are pending: report and exit.
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
- Try `mcp__electron__get_electron_window_info` — if responds, Electron is available
|
|
63
|
-
- Try `mcp__puppeteer__puppeteer_navigate` — if responds, browser is available
|
|
64
|
-
- Check `apt-proof-video --version` — if available, CDP video capture is enabled
|
|
61
|
+
### 2a. Resolve the verify-proof driver
|
|
65
62
|
|
|
66
|
-
|
|
63
|
+
The workflow is **runtime-agnostic** — it refers to IDL verbs
|
|
64
|
+
(`driver.click(target)`, `driver.screenshot(path)`), never to concrete
|
|
65
|
+
MCP tool names. The driver layer (`packages/framework/drivers/*/`)
|
|
66
|
+
maps verbs to transports. Auto-detect the runtime and select a driver:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# Detect the project's runtime capabilities + select a driver
|
|
70
|
+
node .aperant/deps/node_modules/@aperant/framework/bin/apt-tools.mjs detect-runtime .
|
|
71
|
+
# → {status:"ok", runtime:{is_electron, is_tauri, is_nextjs, ...}, selected_driver_id:"..."}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
The resolver picks the highest-scoring driver per ID-02 (capability_coverage
|
|
75
|
+
> compatibility > stability > priority > locality). Adopters override
|
|
76
|
+
auto-detect via `.aperant/config.json:verification.runtimes[]`:
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
{ "verification": { "runtimes": ["electron", "cli"] } }
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
If no driver satisfies a REQUIRED capability, the resolver throws
|
|
83
|
+
`unsatisfied_required_capability` — fail-closed, NOT a silent
|
|
84
|
+
`blocked_by_platform` skip (per ID-02).
|
|
85
|
+
|
|
86
|
+
### 2b. Probe optional evidence-capture tools
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
# CDP video capture (optional)
|
|
90
|
+
apt-proof-video --version # if available, CDP video is on
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Record the selected driver + auxiliary tools in verification.json:
|
|
94
|
+
`meta.selected_driver: {driverId, version, manifest_sha256, trust_tier,
|
|
95
|
+
sandbox_profile}` and `meta.fallback_attempts: [{driver_id, reason}]`
|
|
96
|
+
(see ST11's schema extension in `templates/proof-verification.md`).
|
|
67
97
|
</step>
|
|
68
98
|
|
|
69
99
|
<step name="automated_checks">
|
|
@@ -165,10 +195,14 @@ coverage = tested_count / registered_count
|
|
|
165
195
|
**Every untested registered feature MUST go in the `skipped` array** with a reason.
|
|
166
196
|
Valid skip reasons:
|
|
167
197
|
- `time_constraint` — would take too long relative to value
|
|
168
|
-
- `
|
|
198
|
+
- `unsatisfied_optional_capability` — the selected driver lacks an OPTIONAL IDL verb needed by this test (e.g. `assert_network` not implemented). Loud, deliberate skip.
|
|
169
199
|
- `depends_on_other` — requires a feature not yet implemented
|
|
170
200
|
- `not_applicable` — feature is deprecated or not relevant to this verification
|
|
171
201
|
|
|
202
|
+
> **Legacy alias.** `blocked_by_platform` (pre-driver-architecture) is read by the schema reader as an alias for `unsatisfied_optional_capability` and emits a deprecation warning. New writes MUST use the new reason.
|
|
203
|
+
|
|
204
|
+
> **Fail-closed, not a skip:** if a REQUIRED IDL capability has no implementing driver in the resolver chain, the resolver throws `unsatisfied_required_capability` (per ID-02). This aborts the run — it does NOT silently land in the `skipped` array. Coverage loss is loud, deliberate, and audited.
|
|
205
|
+
|
|
172
206
|
Silence about untested features is NOT acceptable. Every registered sub-feature must appear
|
|
173
207
|
either as a test (with `registry_id`) or in the `skipped` array.
|
|
174
208
|
</step>
|
|
@@ -232,11 +266,17 @@ If CDP is NOT available, note it and proceed without video.
|
|
|
232
266
|
For each test, in order:
|
|
233
267
|
|
|
234
268
|
### 7a. Perform the Action
|
|
235
|
-
Actually do what the test says:
|
|
236
|
-
- If it says "type X" →
|
|
237
|
-
- If it says "click X" →
|
|
238
|
-
- If it says "navigate to X" → navigate
|
|
239
|
-
- If it says "
|
|
269
|
+
Actually do what the test says — invoke the IDL verb on the selected driver:
|
|
270
|
+
- If it says "type X" → `driver.type({ target, text: "X" })`
|
|
271
|
+
- If it says "click X" → `driver.click({ target })`
|
|
272
|
+
- If it says "navigate to X" → `driver.navigate({ url: "X" })`
|
|
273
|
+
- If it says "press key X" → `driver.key({ key: "X" })`
|
|
274
|
+
- If it says "run command X" → `driver.execute_js({ script: "X" })`
|
|
275
|
+
|
|
276
|
+
The driver layer translates IDL verbs to its transport (an MCP server,
|
|
277
|
+
a local binary, or a built-in HTTP request). Workflow prose names ONLY
|
|
278
|
+
IDL verbs per ID-01 — concrete tool names live in
|
|
279
|
+
`packages/framework/drivers/<driver>/driver.mjs`.
|
|
240
280
|
|
|
241
281
|
### 7b. Wait for Result
|
|
242
282
|
Wait for the action to complete. If it involves loading, wait for the load.
|
|
@@ -245,8 +285,10 @@ Add a short delay (1-2 seconds) so the CDP capture also captures this state.
|
|
|
245
285
|
### 7c. Capture Evidence
|
|
246
286
|
Take a screenshot AFTER the action completes:
|
|
247
287
|
```
|
|
248
|
-
|
|
249
|
-
|
|
288
|
+
driver.screenshot({
|
|
289
|
+
target: { kind: "css", value: "body" },
|
|
290
|
+
output_path: "${PROOF_DIR}/screenshots/{feature-id}-test-{n}.png"
|
|
291
|
+
})
|
|
250
292
|
```
|
|
251
293
|
|
|
252
294
|
### 7d. Analyze Evidence
|