@aperant/framework 0.8.1 → 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 +443 -1
- package/agents/apt-planner.md +39 -7
- 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/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 +6 -0
- package/dist/cli/commands/detect-runtime.d.mts.map +1 -0
- package/dist/cli/commands/detect-runtime.mjs +142 -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 +9 -6
- 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/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/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/host/detect.mjs +1 -1
- package/dist/cli/host/detect.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 +163 -39
- package/dist/cli/install/legacy-paths.mjs.map +1 -1
- package/dist/cli/install/runtime-detect.mjs +1 -1
- 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/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 +13 -0
- package/dist/cli/verify-proof/idl/index.mjs.map +1 -0
- package/dist/cli/verify-proof/idl/types.d.ts +10 -0
- package/dist/cli/verify-proof/idl/types.d.ts.map +1 -0
- package/dist/cli/verify-proof/idl/types.js +10 -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 +105 -0
- package/dist/cli/verify-proof/resolver.d.mts.map +1 -0
- package/dist/cli/verify-proof/resolver.mjs +320 -0
- package/dist/cli/verify-proof/resolver.mjs.map +1 -0
- package/dist/cli/verify-proof/runtime-detect.d.mts +30 -0
- package/dist/cli/verify-proof/runtime-detect.d.mts.map +1 -0
- package/dist/cli/verify-proof/runtime-detect.mjs +209 -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/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 +4 -1
- package/dist/plugin/agents/apt-planner.md +39 -7
- package/dist/plugin/skills/apt/SKILL.md +75 -6
- 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 +27 -4
- package/dist/plugin/skills/apt-execute/appendices/tdd-mode.md +2 -10
- package/dist/plugin/skills/apt-fan-out/SKILL.md +36 -1
- 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 +73 -5
- package/dist/plugin/skills/apt-plan/adapters/conductor.md +1 -1
- package/dist/plugin/skills/apt-pr-review/SKILL.md +1 -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-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-resume/SKILL.md +54 -31
- package/dist/plugin/skills/apt-run/SKILL.md +114 -16
- 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 +84 -23
- 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-proof/SKILL.md +87 -1
- 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/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 +138 -133
- package/prompts/planner.md +1 -1
- package/prompts/spec_writer.md +1 -1
- package/skills/apt/SKILL.md +75 -6
- 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 +73 -5
- package/skills/apt-plan/adapters/conductor.md +1 -1
- package/skills/apt-planner.md +17 -1
- package/skills/apt-pr-review/SKILL.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-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-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 +84 -23
- 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 +87 -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/coverage-check.mjs +126 -44
- package/src/cli/commands/detect-runtime.mjs +143 -0
- package/src/cli/commands/driver-doctor.mjs +294 -0
- package/src/cli/commands/gate.mjs +15 -0
- package/src/cli/commands/init.mjs +9 -6
- 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/coverage-check/user-outcomes.mjs +273 -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/host/detect.mjs +1 -1
- 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 +164 -39
- package/src/cli/install/runtime-detect.mjs +1 -1
- package/src/cli/install/runtime-migrate.mjs +63 -30
- package/src/cli/install/transforms/codex.mjs +26 -17
- 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 +23 -0
- package/src/cli/verify-proof/idl/types.ts +42 -0
- package/src/cli/verify-proof/manifest-schema.json +212 -0
- package/src/cli/verify-proof/manifest-validator.mjs +184 -0
- package/src/cli/verify-proof/resolver.mjs +330 -0
- package/src/cli/verify-proof/runtime-detect.mjs +211 -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 +51 -8
- package/workflows/verify-proof.md +130 -19
|
@@ -0,0 +1,211 @@
|
|
|
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
|
+
* v0.8.6 — `loadRuntimeCapabilities` now ALSO walks `apps/*` by default
|
|
13
|
+
* (configurable via the second arg `{monorepo_globs}`) and OR-merges
|
|
14
|
+
* each match's `package.json` capabilities with the root. The default
|
|
15
|
+
* `['apps/*']` matches the canonical Aperant monorepo layout; adopters
|
|
16
|
+
* whose monorepo lives elsewhere (e.g. `packages/apps/*`) pass a
|
|
17
|
+
* custom `monorepo_globs`. Setting `monorepo_globs: []` disables the
|
|
18
|
+
* walk entirely (root-only escape hatch).
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs'
|
|
22
|
+
import { join, resolve } from 'node:path'
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @typedef {Object} RuntimeCapabilities
|
|
26
|
+
* @property {boolean} is_electron
|
|
27
|
+
* @property {boolean} is_tauri
|
|
28
|
+
* @property {boolean} is_nextjs
|
|
29
|
+
* @property {boolean} is_nuxt
|
|
30
|
+
* @property {boolean} is_web_frontend
|
|
31
|
+
* @property {boolean} is_expo
|
|
32
|
+
* @property {boolean} is_react_native
|
|
33
|
+
* @property {boolean} has_node
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/** A capability record with every flag defaulted to false. */
|
|
37
|
+
function emptyCapabilities() {
|
|
38
|
+
return {
|
|
39
|
+
is_electron: false,
|
|
40
|
+
is_tauri: false,
|
|
41
|
+
is_nextjs: false,
|
|
42
|
+
is_nuxt: false,
|
|
43
|
+
is_web_frontend: false,
|
|
44
|
+
is_expo: false,
|
|
45
|
+
is_react_native: false,
|
|
46
|
+
has_node: false,
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Detect runtime capabilities from a parsed package.json object.
|
|
52
|
+
*
|
|
53
|
+
* @param {Record<string, unknown> | null} packageJson
|
|
54
|
+
* @returns {RuntimeCapabilities}
|
|
55
|
+
*/
|
|
56
|
+
export function detectFromPackageJson(packageJson) {
|
|
57
|
+
const capabilities = emptyCapabilities()
|
|
58
|
+
if (packageJson === null || typeof packageJson !== 'object') return capabilities
|
|
59
|
+
|
|
60
|
+
const allDeps = {
|
|
61
|
+
...(packageJson.dependencies ?? {}) /** @type {Record<string,string>} */,
|
|
62
|
+
...(packageJson.devDependencies ?? {}) /** @type {Record<string,string>} */,
|
|
63
|
+
}
|
|
64
|
+
const deps = new Set(Object.keys(allDeps).map((k) => k.toLowerCase()))
|
|
65
|
+
|
|
66
|
+
if (deps.has('electron') || [...deps].some((d) => d.startsWith('@electron'))) {
|
|
67
|
+
capabilities.is_electron = true
|
|
68
|
+
}
|
|
69
|
+
if (deps.has('@tauri-apps/api') || deps.has('tauri')) {
|
|
70
|
+
capabilities.is_tauri = true
|
|
71
|
+
}
|
|
72
|
+
if (deps.has('expo')) capabilities.is_expo = true
|
|
73
|
+
if (deps.has('react-native')) capabilities.is_react_native = true
|
|
74
|
+
|
|
75
|
+
// Web frontends — react/vue/svelte/angular/solid → frontend. BUT: an
|
|
76
|
+
// Electron app commonly bundles React as its renderer; in that case the
|
|
77
|
+
// runtime is "electron", not "web_frontend". Electron + react-native +
|
|
78
|
+
// expo + tauri all win over the web flag.
|
|
79
|
+
const isNativeContainer =
|
|
80
|
+
capabilities.is_electron ||
|
|
81
|
+
capabilities.is_tauri ||
|
|
82
|
+
capabilities.is_react_native ||
|
|
83
|
+
capabilities.is_expo
|
|
84
|
+
const webFrameworks = ['react', 'vue', 'svelte', 'angular', 'solid']
|
|
85
|
+
if (!isNativeContainer && webFrameworks.some((f) => deps.has(f))) {
|
|
86
|
+
capabilities.is_web_frontend = true
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (deps.has('next')) {
|
|
90
|
+
capabilities.is_nextjs = true
|
|
91
|
+
capabilities.is_web_frontend = true
|
|
92
|
+
}
|
|
93
|
+
if (deps.has('nuxt')) {
|
|
94
|
+
capabilities.is_nuxt = true
|
|
95
|
+
capabilities.is_web_frontend = true
|
|
96
|
+
}
|
|
97
|
+
if (deps.has('vite') && !isNativeContainer) {
|
|
98
|
+
capabilities.is_web_frontend = true
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Node-ish? Anything with a package.json + an entry point counts.
|
|
102
|
+
if (typeof packageJson.main === 'string' || typeof packageJson.module === 'string') {
|
|
103
|
+
capabilities.has_node = true
|
|
104
|
+
}
|
|
105
|
+
if (!capabilities.has_node && packageJson.scripts && typeof packageJson.scripts === 'object') {
|
|
106
|
+
capabilities.has_node = true
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return capabilities
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Load and detect capabilities from a project directory's package.json.
|
|
114
|
+
* v0.8.6 — also walks `monorepo_globs` (default `['apps/*']`) and
|
|
115
|
+
* OR-merges each match's capabilities with the root.
|
|
116
|
+
*
|
|
117
|
+
* @param {string} projectDir
|
|
118
|
+
* @param {{monorepo_globs?: string[]}} [opts]
|
|
119
|
+
* @returns {RuntimeCapabilities}
|
|
120
|
+
*/
|
|
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
|
|
172
|
+
try {
|
|
173
|
+
entries = readdirSync(dir)
|
|
174
|
+
} catch {
|
|
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,
|
|
210
|
+
}
|
|
211
|
+
}
|
|
@@ -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/framework/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": {
|
|
@@ -43,7 +53,13 @@ JSON schema reference for `.aperant/proof/{app}/{date}/{run}/verification.json`
|
|
|
43
53
|
"tested_count": 0,
|
|
44
54
|
"skipped_count": 0,
|
|
45
55
|
"percent": 0,
|
|
46
|
-
"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
|
+
}
|
|
47
63
|
},
|
|
48
64
|
|
|
49
65
|
"tests": [
|
|
@@ -51,14 +67,33 @@ JSON schema reference for `.aperant/proof/{app}/{date}/{run}/verification.json`
|
|
|
51
67
|
"id": "TEST-01",
|
|
52
68
|
"registry_id": "terminals-grid-auto-tile",
|
|
53
69
|
"name": "Three Terminal Grid Auto-Tile",
|
|
70
|
+
"outcome_id": "O3",
|
|
71
|
+
"surface": "electron",
|
|
72
|
+
"transport": "mcp",
|
|
54
73
|
"precondition": "2 terminals active",
|
|
55
74
|
"action": "Press Cmd+T",
|
|
56
75
|
"expected": "Grid reflows to 2+1 layout",
|
|
57
76
|
"actual": "Grid correctly tiled to 2+1",
|
|
58
|
-
"evidence": "screenshots/03-grid.png",
|
|
77
|
+
"evidence": "electron/screenshots/03-grid.png",
|
|
59
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.",
|
|
60
79
|
"result": "pass | fail | blocked | skip",
|
|
61
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
|
|
62
97
|
}
|
|
63
98
|
],
|
|
64
99
|
|
|
@@ -66,8 +101,8 @@ JSON schema reference for `.aperant/proof/{app}/{date}/{run}/verification.json`
|
|
|
66
101
|
{
|
|
67
102
|
"registry_id": "terminals-settings-scrollback-slider",
|
|
68
103
|
"name": "Scrollback buffer slider",
|
|
69
|
-
"reason": "
|
|
70
|
-
"detail": "
|
|
104
|
+
"reason": "unsatisfied_optional_capability",
|
|
105
|
+
"detail": "Selected driver does not implement the IDL verb required for slider interaction"
|
|
71
106
|
}
|
|
72
107
|
],
|
|
73
108
|
|
|
@@ -149,7 +184,9 @@ JSON schema reference for `.aperant/proof/{app}/{date}/{run}/verification.json`
|
|
|
149
184
|
| `updated` | string | OVERWRITE | ISO timestamp, updated on every write |
|
|
150
185
|
| `spec` | string | IMMUTABLE | Path to spec or task description |
|
|
151
186
|
| `verdict` | string | OVERWRITE | Derived from dimension scores after all tests |
|
|
152
|
-
| `tools` | array | IMMUTABLE | MCP tools detected as available |
|
|
187
|
+
| `tools` | array | IMMUTABLE | MCP tools / auxiliary capture tools detected as available |
|
|
188
|
+
| `selected_driver` | object | IMMUTABLE | The verify-proof driver chosen by the resolver per ID-02. Fields: `driverId`, `version`, `manifest_sha256`, `trust_tier`, `sandbox_profile`. |
|
|
189
|
+
| `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
190
|
|
|
154
191
|
### automated_checks
|
|
155
192
|
Each check has `status` (pass/fail) and `output` (captured terminal output). These are populated in step 3 before any manual testing begins.
|
|
@@ -163,6 +200,7 @@ Each check has `status` (pass/fail) and `output` (captured terminal output). The
|
|
|
163
200
|
| `skipped_count` | number | Number of entries in the skipped array |
|
|
164
201
|
| `percent` | number | `tested_count / registered_count * 100` |
|
|
165
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. |
|
|
166
204
|
|
|
167
205
|
### tests
|
|
168
206
|
Each test entry tracks one sub-feature verification. Fields:
|
|
@@ -172,11 +210,14 @@ Each test entry tracks one sub-feature verification. Fields:
|
|
|
172
210
|
| `id` | string | IMMUTABLE | Sequential ID: TEST-01, TEST-02, etc. |
|
|
173
211
|
| `registry_id` | string | IMMUTABLE | Must match a sub-feature `id` from `.aperant/features/{area}.json` |
|
|
174
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"`. |
|
|
175
216
|
| `precondition` | string | IMMUTABLE | What must be true before testing |
|
|
176
217
|
| `action` | string | IMMUTABLE | Specific action to perform |
|
|
177
218
|
| `expected` | string | IMMUTABLE | Specific observable result expected |
|
|
178
219
|
| `actual` | string | OVERWRITE | What actually happened (filled after testing) |
|
|
179
|
-
| `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`. |
|
|
180
221
|
| `analysis` | string | OVERWRITE | Adversarial analysis of evidence — describe what you SEE, not what you expect |
|
|
181
222
|
| `result` | string | OVERWRITE | `pass`, `fail`, `blocked`, or `skip` |
|
|
182
223
|
| `severity` | string/null | OVERWRITE | `blocker`, `major`, `minor`, `cosmetic`, or `null` if pass |
|
|
@@ -188,9 +229,11 @@ Every registered sub-feature that does NOT have a test MUST appear here. Silence
|
|
|
188
229
|
|-------|------|-------------|
|
|
189
230
|
| `registry_id` | string | The sub-feature `id` from the feature registry |
|
|
190
231
|
| `name` | string | Human-readable name of the skipped feature |
|
|
191
|
-
| `reason` | string | One of: `time_constraint`, `
|
|
232
|
+
| `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
233
|
| `detail` | string | Brief explanation of why this feature was skipped |
|
|
193
234
|
|
|
235
|
+
> **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[]`.
|
|
236
|
+
|
|
194
237
|
### dimensions
|
|
195
238
|
Four QA dimensions, each with `score` (1-10) and `rationale` (string). Scores are evidence-based, never assumed.
|
|
196
239
|
|