@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
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* commands/context.mjs — `apt-tools context write
|
|
2
|
+
* commands/context.mjs — `apt-tools context write`.
|
|
3
3
|
*
|
|
4
4
|
* Upserts a term-block into the project's CONTEXT.md (the persistent
|
|
5
|
-
* project glossary). Format ported from
|
|
5
|
+
* project glossary). Format ported from the Aperant CONTEXT-FORMAT.md
|
|
6
6
|
* (see packages/framework/templates/context-format.md for the MIT
|
|
7
7
|
* attribution + field semantics).
|
|
8
8
|
*
|
|
@@ -192,7 +192,7 @@ export function cmdContext(subcommand, projectDir, extraArgs) {
|
|
|
192
192
|
} else {
|
|
193
193
|
source = `${HEADER}\n\n${HEADER_NOTE}\n\n`
|
|
194
194
|
}
|
|
195
|
-
// Ensure the file starts with the header even if it existed pre-
|
|
195
|
+
// Ensure the file starts with the header even if it existed pre-vertical-slice.
|
|
196
196
|
if (!source.startsWith(HEADER)) {
|
|
197
197
|
source = `${HEADER}\n\n${HEADER_NOTE}\n\n${source}`
|
|
198
198
|
}
|
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* commands/coverage-check.mjs — plan-to-spec verification coverage validator (C29).
|
|
3
|
+
*
|
|
4
|
+
* v0.8.6 adds a second gate: `## User Outcomes` parsing + gate (see
|
|
5
|
+
* coverage-check/user-outcomes.mjs). The new gate is ADDITIVE — it does
|
|
6
|
+
* NOT change the existing AC parsing branch and does NOT block when
|
|
7
|
+
* only Acceptance Criteria are present (outcomes and ACs are
|
|
8
|
+
* independent concerns per spec §ID-01). QUICK-track plans
|
|
9
|
+
* (`complexity: "simple"` OR no `vertical_slice_schema_version`) skip
|
|
10
|
+
* the User Outcomes branch entirely per Fast Path Guarantee (ID-08).
|
|
3
11
|
*/
|
|
4
12
|
|
|
5
13
|
import { existsSync, readFileSync } from 'node:fs'
|
|
6
|
-
import { join, resolve } from 'node:path'
|
|
14
|
+
import { dirname, join, resolve } from 'node:path'
|
|
15
|
+
import { fileURLToPath } from 'node:url'
|
|
7
16
|
import { parseAcceptanceCriteria, resolveSpecPath } from '../config/coverage-ac.mjs'
|
|
17
|
+
import { parseUserOutcomes, userOutcomesGateResult } from '../coverage-check/user-outcomes.mjs'
|
|
8
18
|
import { err, ok } from '../util/result.mjs'
|
|
9
19
|
|
|
10
20
|
export function cmdCoverageCheck(subcommand, projectDir, extraArgs) {
|
|
@@ -125,31 +135,64 @@ export function cmdCoverageCheck(subcommand, projectDir, extraArgs) {
|
|
|
125
135
|
}
|
|
126
136
|
}
|
|
127
137
|
|
|
138
|
+
// Build the User Outcomes payload — additive, ID-08 QUICK-exempt.
|
|
139
|
+
// Plans with `complexity: "simple"` OR no
|
|
140
|
+
// `vertical_slice_schema_version` are QUICK-track per the Fast
|
|
141
|
+
// Path Guarantee — skip the gate entirely (the payload's
|
|
142
|
+
// `user_outcomes` key is OMITTED).
|
|
143
|
+
const isQuickTrack = plan.complexity === 'simple' || !plan.vertical_slice_schema_version
|
|
144
|
+
let userOutcomesPayload = null
|
|
145
|
+
if (!isQuickTrack && resolvedSpec.path) {
|
|
146
|
+
const specContentForOutcomes = readFileSync(resolvedSpec.path, 'utf-8')
|
|
147
|
+
const parsedOutcomes = parseUserOutcomes(specContentForOutcomes)
|
|
148
|
+
const frameworkVersion = readFrameworkVersion()
|
|
149
|
+
const gateResult = userOutcomesGateResult(parsedOutcomes, frameworkVersion)
|
|
150
|
+
userOutcomesPayload = {
|
|
151
|
+
header_present: parsedOutcomes.header_present,
|
|
152
|
+
body_kind: parsedOutcomes.body_kind,
|
|
153
|
+
outcomes_count: parsedOutcomes.outcomes.length,
|
|
154
|
+
gate_result: gateResult,
|
|
155
|
+
}
|
|
156
|
+
if (parsedOutcomes.epic) userOutcomesPayload.epic = parsedOutcomes.epic
|
|
157
|
+
if (parsedOutcomes.validation_warnings && parsedOutcomes.validation_warnings.length > 0) {
|
|
158
|
+
userOutcomesPayload.validation_warnings = parsedOutcomes.validation_warnings
|
|
159
|
+
}
|
|
160
|
+
if (gateResult === 'warn') {
|
|
161
|
+
userOutcomesPayload.migration_deadline =
|
|
162
|
+
'## User Outcomes section becomes a hard block in v0.9.0 — add it now, or use the empty-with-note escape (`_No user-observable changes — pure refactor._`) for refactor-only specs.'
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
128
166
|
// Zero-criteria guard — do NOT silently report ok when there is
|
|
129
167
|
// nothing to verify. This is the motivating bug: zero criteria
|
|
130
168
|
// trivially pass unmapped.length === 0, which produced a false
|
|
131
169
|
// green signal. Treat it as incomplete with an explicit reason.
|
|
132
170
|
if (criteria.length === 0) {
|
|
133
|
-
return ok(
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
171
|
+
return ok(
|
|
172
|
+
withUserOutcomes(
|
|
173
|
+
{
|
|
174
|
+
status: 'incomplete',
|
|
175
|
+
command: 'coverage-validate',
|
|
176
|
+
criteria_count: 0,
|
|
177
|
+
mapped_count: 0,
|
|
178
|
+
unmapped_criteria: [],
|
|
179
|
+
coverage: [],
|
|
180
|
+
subtask_verifications: plan.subtasks.map((st) => ({
|
|
181
|
+
id: st.id,
|
|
182
|
+
title: st.title || st.id,
|
|
183
|
+
verification: st.verification || '',
|
|
184
|
+
acceptance_criteria: st.acceptance_criteria || [],
|
|
185
|
+
})),
|
|
186
|
+
reason: 'no-criteria-found',
|
|
187
|
+
suggestion:
|
|
188
|
+
'Add acceptance criteria to spec.md under ## Acceptance Criteria, or add plan.acceptance_criteria to implementation_plan.json',
|
|
189
|
+
sources_checked: sourcesChecked,
|
|
190
|
+
spec_path: resolvedSpec.path,
|
|
191
|
+
autonomy_action: autonomyLevelEarly <= 1 ? 'stop-and-ask' : 'generate-stubs',
|
|
192
|
+
},
|
|
193
|
+
userOutcomesPayload,
|
|
194
|
+
),
|
|
195
|
+
)
|
|
153
196
|
}
|
|
154
197
|
|
|
155
198
|
// Classify verification methods for each subtask (D-19)
|
|
@@ -221,32 +264,71 @@ export function cmdCoverageCheck(subcommand, projectDir, extraArgs) {
|
|
|
221
264
|
}
|
|
222
265
|
|
|
223
266
|
if (unmapped.length > 0) {
|
|
224
|
-
return ok(
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
267
|
+
return ok(
|
|
268
|
+
withUserOutcomes(
|
|
269
|
+
{
|
|
270
|
+
status: 'incomplete',
|
|
271
|
+
command: 'coverage-validate',
|
|
272
|
+
criteria_count: criteria.length,
|
|
273
|
+
mapped_count: mappedCount,
|
|
274
|
+
unmapped_criteria: unmapped.map((u) => u.criterion),
|
|
275
|
+
coverage,
|
|
276
|
+
subtask_verifications: subtaskVerifications,
|
|
277
|
+
suggestion: 'generate test stubs',
|
|
278
|
+
autonomy_action: autonomyLevel <= 1 ? 'stop-and-ask' : 'generate-stubs',
|
|
279
|
+
spec_path: resolvedSpec.path,
|
|
280
|
+
sources_checked: sourcesChecked,
|
|
281
|
+
},
|
|
282
|
+
userOutcomesPayload,
|
|
283
|
+
),
|
|
284
|
+
)
|
|
237
285
|
}
|
|
238
|
-
return ok(
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
286
|
+
return ok(
|
|
287
|
+
withUserOutcomes(
|
|
288
|
+
{
|
|
289
|
+
status: 'ok',
|
|
290
|
+
command: 'coverage-validate',
|
|
291
|
+
criteria_count: criteria.length,
|
|
292
|
+
mapped_count: mappedCount,
|
|
293
|
+
coverage,
|
|
294
|
+
subtask_verifications: subtaskVerifications,
|
|
295
|
+
spec_path: resolvedSpec.path,
|
|
296
|
+
sources_checked: sourcesChecked,
|
|
297
|
+
},
|
|
298
|
+
userOutcomesPayload,
|
|
299
|
+
),
|
|
300
|
+
)
|
|
248
301
|
}
|
|
249
302
|
default:
|
|
250
303
|
return err(`Unknown coverage-check subcommand: ${subcommand}. Available: validate`)
|
|
251
304
|
}
|
|
252
305
|
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Attach the `user_outcomes` payload to a coverage-check envelope when
|
|
309
|
+
* present (i.e. STANDARD/DEEP plan with a spec on disk). QUICK plans
|
|
310
|
+
* pass `null` and the key is OMITTED from the envelope entirely — Fast
|
|
311
|
+
* Path Guarantee (ID-08).
|
|
312
|
+
*/
|
|
313
|
+
function withUserOutcomes(envelope, userOutcomesPayload) {
|
|
314
|
+
if (userOutcomesPayload === null) return envelope
|
|
315
|
+
return { ...envelope, user_outcomes: userOutcomesPayload }
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Read the running kernel's framework version from its own package.json.
|
|
320
|
+
* Used to gate the `## User Outcomes` missing-header behavior — warn in
|
|
321
|
+
* v0.8.x, hard-block from v0.9.0 forward. Returns `'0.0.0'` on failure
|
|
322
|
+
* so the gate fails-soft to warn (callers treat unknown version as
|
|
323
|
+
* pre-0.9.0).
|
|
324
|
+
*/
|
|
325
|
+
function readFrameworkVersion() {
|
|
326
|
+
try {
|
|
327
|
+
const here = dirname(fileURLToPath(import.meta.url))
|
|
328
|
+
const pkgPath = resolve(here, '..', '..', '..', 'package.json')
|
|
329
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'))
|
|
330
|
+
return typeof pkg.version === 'string' ? pkg.version : '0.0.0'
|
|
331
|
+
} catch {
|
|
332
|
+
return '0.0.0'
|
|
333
|
+
}
|
|
334
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* commands/detect-runtime.mjs — `apt-tools detect-runtime <projectDir>`.
|
|
3
|
+
*
|
|
4
|
+
* Exposes runtime-detect.mjs at the CLI surface so adopters can preview
|
|
5
|
+
* which driver Aperant will pick for /apt:verify-proof.
|
|
6
|
+
*
|
|
7
|
+
* Returns:
|
|
8
|
+
* { status: 'ok', command: 'detect-runtime', projectDir,
|
|
9
|
+
* runtime: { is_electron, is_tauri, is_nextjs, is_web_frontend, ... },
|
|
10
|
+
* selected_driver_id: string | null,
|
|
11
|
+
* fallback_attempts: [{ driver_id, reason }] }
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs'
|
|
15
|
+
import { join, resolve } from 'node:path'
|
|
16
|
+
import { fileURLToPath } from 'node:url'
|
|
17
|
+
import { err, ok } from '../util/result.mjs'
|
|
18
|
+
import { resolveDriver } from '../verify-proof/resolver.mjs'
|
|
19
|
+
import { loadRuntimeCapabilities } from '../verify-proof/runtime-detect.mjs'
|
|
20
|
+
|
|
21
|
+
/** Default required capabilities — the smallest set that proves a driver is usable. */
|
|
22
|
+
const DEFAULT_REQUIRED_CAPABILITIES = ['screenshot']
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Discover every driver manifest bundled with @aperant/framework.
|
|
26
|
+
* @param {string} frameworkRoot — absolute path to packages/framework or .aperant/deps/.../@aperant/framework
|
|
27
|
+
* @returns {Array<object>}
|
|
28
|
+
*/
|
|
29
|
+
function discoverBundledDrivers(frameworkRoot) {
|
|
30
|
+
const dir = join(frameworkRoot, 'drivers')
|
|
31
|
+
if (!existsSync(dir)) return []
|
|
32
|
+
const manifests = []
|
|
33
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
34
|
+
if (!entry.isDirectory()) continue
|
|
35
|
+
const manifestPath = join(dir, entry.name, 'manifest.json')
|
|
36
|
+
if (!existsSync(manifestPath)) continue
|
|
37
|
+
try {
|
|
38
|
+
const parsed = JSON.parse(readFileSync(manifestPath, 'utf-8'))
|
|
39
|
+
manifests.push({ ...parsed, locality: 'bundled' })
|
|
40
|
+
} catch {
|
|
41
|
+
/* skip malformed manifests */
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return manifests
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Discover every user-installed driver manifest at .aperant/drivers/.
|
|
49
|
+
* @param {string} projectDir
|
|
50
|
+
* @returns {Array<object>}
|
|
51
|
+
*/
|
|
52
|
+
function discoverUserDrivers(projectDir) {
|
|
53
|
+
const dir = join(projectDir, '.aperant/drivers')
|
|
54
|
+
if (!existsSync(dir)) return []
|
|
55
|
+
const manifests = []
|
|
56
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
57
|
+
if (!entry.isDirectory()) continue
|
|
58
|
+
const manifestPath = join(dir, entry.name, 'manifest.json')
|
|
59
|
+
if (!existsSync(manifestPath)) continue
|
|
60
|
+
try {
|
|
61
|
+
const parsed = JSON.parse(readFileSync(manifestPath, 'utf-8'))
|
|
62
|
+
manifests.push({ ...parsed, locality: 'user' })
|
|
63
|
+
} catch {
|
|
64
|
+
/* skip malformed manifests */
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return manifests
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @param {string} projectDir
|
|
72
|
+
* @param {string[]} [_extra]
|
|
73
|
+
*/
|
|
74
|
+
export function cmdDetectRuntime(projectDir, _extra = []) {
|
|
75
|
+
if (!projectDir) return err('Usage: apt-tools detect-runtime <project-dir>')
|
|
76
|
+
const target = resolve(projectDir)
|
|
77
|
+
|
|
78
|
+
// Read user config (used both for the monorepo walk + override).
|
|
79
|
+
let monorepoGlobs
|
|
80
|
+
let override = []
|
|
81
|
+
const configPath = join(target, '.aperant/config.json')
|
|
82
|
+
if (existsSync(configPath)) {
|
|
83
|
+
try {
|
|
84
|
+
const cfg = JSON.parse(readFileSync(configPath, 'utf-8'))
|
|
85
|
+
if (Array.isArray(cfg?.verification?.monorepo_globs)) {
|
|
86
|
+
monorepoGlobs = cfg.verification.monorepo_globs.filter((x) => typeof x === 'string')
|
|
87
|
+
}
|
|
88
|
+
if (Array.isArray(cfg?.verification?.runtimes)) {
|
|
89
|
+
override = cfg.verification.runtimes.filter((x) => typeof x === 'string')
|
|
90
|
+
}
|
|
91
|
+
} catch {
|
|
92
|
+
/* malformed config — ignore */
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Detect runtime capabilities from project's package.json + (optionally)
|
|
97
|
+
// monorepo `apps/*` walk. Default behaviour matches the canonical
|
|
98
|
+
// Aperant layout — adopters opt out via verification.monorepo_globs: [].
|
|
99
|
+
const runtime = loadRuntimeCapabilities(
|
|
100
|
+
target,
|
|
101
|
+
monorepoGlobs !== undefined ? { monorepo_globs: monorepoGlobs } : undefined,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
// Discover bundled + user drivers.
|
|
105
|
+
// __dirname-equivalent under ESM: walk up from this file to the framework
|
|
106
|
+
// package root. fileURLToPath is portable across POSIX + Windows;
|
|
107
|
+
// new URL(...).pathname returns '/C:/...' on Windows which resolve()
|
|
108
|
+
// does not handle correctly.
|
|
109
|
+
const frameworkRoot = fileURLToPath(new URL('../../..', import.meta.url))
|
|
110
|
+
const bundled = discoverBundledDrivers(frameworkRoot)
|
|
111
|
+
const user = discoverUserDrivers(target)
|
|
112
|
+
const drivers = [...bundled, ...user]
|
|
113
|
+
|
|
114
|
+
// Try resolving with the default minimum required capability.
|
|
115
|
+
let selected = null
|
|
116
|
+
let fallback_attempts = []
|
|
117
|
+
try {
|
|
118
|
+
const result = resolveDriver({
|
|
119
|
+
drivers,
|
|
120
|
+
runtime,
|
|
121
|
+
required_capabilities: DEFAULT_REQUIRED_CAPABILITIES,
|
|
122
|
+
override,
|
|
123
|
+
})
|
|
124
|
+
selected = result.selected.driverId
|
|
125
|
+
fallback_attempts = result.fallback_attempts
|
|
126
|
+
} catch (resolveErr) {
|
|
127
|
+
fallback_attempts = resolveErr && Array.isArray(resolveErr.attempts) ? resolveErr.attempts : []
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return ok({
|
|
131
|
+
status: 'ok',
|
|
132
|
+
command: 'detect-runtime',
|
|
133
|
+
projectDir: target,
|
|
134
|
+
runtime,
|
|
135
|
+
discovered_drivers: drivers.map((d) => ({
|
|
136
|
+
driverId: d.driverId,
|
|
137
|
+
version: d.version,
|
|
138
|
+
locality: d.locality,
|
|
139
|
+
})),
|
|
140
|
+
selected_driver_id: selected,
|
|
141
|
+
fallback_attempts,
|
|
142
|
+
})
|
|
143
|
+
}
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* commands/driver-doctor.mjs — `apt-tools driver doctor <projectDir>`.
|
|
3
|
+
*
|
|
4
|
+
* Validates every discovered driver against:
|
|
5
|
+
* 1. The manifest JSON-Schema (manifest-validator.mjs).
|
|
6
|
+
* 2. IDL conformance — every declared capability has a function export.
|
|
7
|
+
* NOTE: this step necessarily imports driver.mjs, which RUNS the
|
|
8
|
+
* driver's top-level module code. For that reason we trust-gate the
|
|
9
|
+
* import: bundled drivers (first-party signed, sitting under
|
|
10
|
+
* packages/framework/drivers/) are always imported; user drivers
|
|
11
|
+
* under .aperant/drivers/ are imported only when policy + TOFU say
|
|
12
|
+
* it is safe. A user driver that fails the gate is reported with
|
|
13
|
+
* `idl_conformant: false` and `idl_skipped_reason: <decision>`
|
|
14
|
+
* rather than executed.
|
|
15
|
+
* 3. Preflight commands (declared as structured local_command per
|
|
16
|
+
* ID-03). For each preflight whose declared transport is the
|
|
17
|
+
* structured `{ binary, args[] }` shape, we run it via execFile
|
|
18
|
+
* (exec.mjs — no shell) and record pass/fail + missing_deps.
|
|
19
|
+
* Preflights without a structured `command` (declaration-only) are
|
|
20
|
+
* marked `pending` — there is nothing to execute.
|
|
21
|
+
*
|
|
22
|
+
* Emits a JSON envelope listing pass/fail per driver. Third-party driver
|
|
23
|
+
* authors invoke this before publishing.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs'
|
|
27
|
+
import { join, resolve } from 'node:path'
|
|
28
|
+
import { fileURLToPath } from 'node:url'
|
|
29
|
+
import { err, ok } from '../util/result.mjs'
|
|
30
|
+
import { execLocalCommand } from '../verify-proof/exec.mjs'
|
|
31
|
+
import { IDL_VERB_NAMES } from '../verify-proof/idl/index.mjs'
|
|
32
|
+
import { validateManifest } from '../verify-proof/manifest-validator.mjs'
|
|
33
|
+
import { decide, readPolicy, readTofuLock } from '../verify-proof/trust.mjs'
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @typedef {Object} DriverDoctorReport
|
|
37
|
+
* @property {string} driverId
|
|
38
|
+
* @property {string} version
|
|
39
|
+
* @property {string} locality
|
|
40
|
+
* @property {boolean} manifest_valid
|
|
41
|
+
* @property {Array<{path: string, message: string}>} manifest_errors
|
|
42
|
+
* @property {boolean} idl_conformant
|
|
43
|
+
* @property {string[]} missing_exports
|
|
44
|
+
* @property {string[]} unknown_capabilities
|
|
45
|
+
* @property {string} [idl_skipped_reason] — present when conformance was skipped because the trust gate blocked driver.mjs import
|
|
46
|
+
* @property {'pending' | 'pass' | 'fail' | 'skipped'} preflight_status
|
|
47
|
+
* @property {string[]} missing_deps
|
|
48
|
+
* @property {string} [publisher_warning] — surfaced when a non-first-party manifest claims first_party_signed (v1 has no signature verification)
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Scan a drivers directory for manifest.json files. Returns
|
|
53
|
+
* (manifest, dir, locality) tuples.
|
|
54
|
+
* @param {string} rootDir
|
|
55
|
+
* @param {'bundled' | 'user'} locality
|
|
56
|
+
*/
|
|
57
|
+
function scanDriversDir(rootDir, locality) {
|
|
58
|
+
const out = []
|
|
59
|
+
if (!existsSync(rootDir)) return out
|
|
60
|
+
for (const entry of readdirSync(rootDir, { withFileTypes: true })) {
|
|
61
|
+
if (!entry.isDirectory()) continue
|
|
62
|
+
const driverDir = join(rootDir, entry.name)
|
|
63
|
+
const manifestPath = join(driverDir, 'manifest.json')
|
|
64
|
+
if (!existsSync(manifestPath)) continue
|
|
65
|
+
let manifest
|
|
66
|
+
try {
|
|
67
|
+
manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'))
|
|
68
|
+
} catch (e) {
|
|
69
|
+
out.push({ malformed: true, dir: driverDir, locality, parse_error: String(e) })
|
|
70
|
+
continue
|
|
71
|
+
}
|
|
72
|
+
out.push({ manifest, dir: driverDir, locality })
|
|
73
|
+
}
|
|
74
|
+
return out
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* @param {object} manifest
|
|
79
|
+
* @param {string} driverDir
|
|
80
|
+
* @returns {Promise<{idl_conformant: boolean, missing_exports: string[], unknown_capabilities: string[]}>}
|
|
81
|
+
*/
|
|
82
|
+
async function checkIdlConformance(manifest, driverDir) {
|
|
83
|
+
const driverPath = join(driverDir, 'driver.mjs')
|
|
84
|
+
if (!existsSync(driverPath)) {
|
|
85
|
+
return {
|
|
86
|
+
idl_conformant: false,
|
|
87
|
+
missing_exports: ['driver.mjs not found'],
|
|
88
|
+
unknown_capabilities: [],
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
const missing_exports = []
|
|
92
|
+
const unknown_capabilities = []
|
|
93
|
+
const idlSet = new Set(IDL_VERB_NAMES)
|
|
94
|
+
try {
|
|
95
|
+
const mod = await import(driverPath)
|
|
96
|
+
for (const cap of manifest.capabilities ?? []) {
|
|
97
|
+
if (!idlSet.has(cap)) {
|
|
98
|
+
unknown_capabilities.push(cap)
|
|
99
|
+
continue
|
|
100
|
+
}
|
|
101
|
+
if (typeof mod[cap] !== 'function') {
|
|
102
|
+
missing_exports.push(cap)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
} catch (e) {
|
|
106
|
+
return {
|
|
107
|
+
idl_conformant: false,
|
|
108
|
+
missing_exports: [`driver.mjs import failed: ${String(e)}`],
|
|
109
|
+
unknown_capabilities: [],
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
idl_conformant: missing_exports.length === 0 && unknown_capabilities.length === 0,
|
|
114
|
+
missing_exports,
|
|
115
|
+
unknown_capabilities,
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Run a driver's preflight checks. Each declared preflight with a
|
|
121
|
+
* structured `command: { binary, args[] }` is executed via execFile
|
|
122
|
+
* (no shell). Preflights without a structured command are reported as
|
|
123
|
+
* `pending` — there is nothing to execute. Empty preflight list is
|
|
124
|
+
* `skipped`.
|
|
125
|
+
*
|
|
126
|
+
* @param {object} manifest
|
|
127
|
+
* @returns {Promise<{preflight_status: 'pending'|'pass'|'fail'|'skipped', missing_deps: string[]}>}
|
|
128
|
+
*/
|
|
129
|
+
async function runPreflight(manifest) {
|
|
130
|
+
const preflight = Array.isArray(manifest.preflight) ? manifest.preflight : []
|
|
131
|
+
if (preflight.length === 0) return { preflight_status: 'skipped', missing_deps: [] }
|
|
132
|
+
|
|
133
|
+
const missing_deps = []
|
|
134
|
+
let executed = 0
|
|
135
|
+
let failed = false
|
|
136
|
+
|
|
137
|
+
for (const check of preflight) {
|
|
138
|
+
// Only execute when the preflight declares a structured local-command
|
|
139
|
+
// shape. Anything else (no command, or non-structured) is left as
|
|
140
|
+
// declaration-only — caller can drive it explicitly.
|
|
141
|
+
const cmd = check?.command
|
|
142
|
+
if (
|
|
143
|
+
cmd === undefined ||
|
|
144
|
+
cmd === null ||
|
|
145
|
+
typeof cmd !== 'object' ||
|
|
146
|
+
typeof cmd.binary !== 'string' ||
|
|
147
|
+
!Array.isArray(cmd.args) ||
|
|
148
|
+
cmd.args.some((a) => typeof a !== 'string')
|
|
149
|
+
) {
|
|
150
|
+
continue
|
|
151
|
+
}
|
|
152
|
+
executed++
|
|
153
|
+
try {
|
|
154
|
+
await execLocalCommand({ transport: { local_command: cmd } }, [], { timeout: 5000 })
|
|
155
|
+
} catch {
|
|
156
|
+
failed = true
|
|
157
|
+
if (check.required !== false) {
|
|
158
|
+
missing_deps.push(check.name ?? cmd.binary)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (executed === 0) return { preflight_status: 'pending', missing_deps: [] }
|
|
164
|
+
return { preflight_status: failed ? 'fail' : 'pass', missing_deps }
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Trust-gate the driver.mjs import. Bundled drivers (locality='bundled')
|
|
169
|
+
* always pass — they live in packages/framework/drivers/ and are first-
|
|
170
|
+
* party. User drivers go through the policy + TOFU decision; only
|
|
171
|
+
* `allow` permits the dynamic import.
|
|
172
|
+
*
|
|
173
|
+
* @param {object} manifest
|
|
174
|
+
* @param {'bundled'|'user'} locality
|
|
175
|
+
* @param {string} projectDir
|
|
176
|
+
* @returns {{ok: true} | {ok: false, reason: string}}
|
|
177
|
+
*/
|
|
178
|
+
function trustGate(manifest, locality, projectDir) {
|
|
179
|
+
if (locality === 'bundled') return { ok: true }
|
|
180
|
+
const policy = readPolicy(projectDir)
|
|
181
|
+
const tofu = readTofuLock(projectDir)
|
|
182
|
+
const decision = decide(manifest, tofu, policy)
|
|
183
|
+
if (decision.decision === 'allow') return { ok: true }
|
|
184
|
+
return { ok: false, reason: decision.reason }
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* v1 has no signature verification (spec §Out of Scope). A non-Aperant
|
|
189
|
+
* publisher claiming `first_party_signed` is suspicious — surface a
|
|
190
|
+
* warning so the operator notices.
|
|
191
|
+
*
|
|
192
|
+
* @param {object} manifest
|
|
193
|
+
* @returns {string | undefined}
|
|
194
|
+
*/
|
|
195
|
+
function publisherWarning(manifest) {
|
|
196
|
+
const tier = manifest?.trust?.tier
|
|
197
|
+
if (tier !== 'first_party_signed') return undefined
|
|
198
|
+
const npmScope = manifest?.trust?.publisher?.npm_scope
|
|
199
|
+
const githubOrg = manifest?.trust?.publisher?.github_org
|
|
200
|
+
const looksLikeAperant = npmScope === '@aperant' || githubOrg === 'Mikalsen-AI'
|
|
201
|
+
if (looksLikeAperant) return undefined
|
|
202
|
+
return `trust.tier=first_party_signed but publisher is not @aperant — v1 does not verify signatures; treat tier as advisory`
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* @param {string} projectDir
|
|
207
|
+
* @param {string[]} [_extra]
|
|
208
|
+
*/
|
|
209
|
+
export async function cmdDriverDoctor(projectDir, _extra = []) {
|
|
210
|
+
if (!projectDir) return err('Usage: apt-tools driver doctor <project-dir>')
|
|
211
|
+
const target = resolve(projectDir)
|
|
212
|
+
|
|
213
|
+
// fileURLToPath is portable across POSIX + Windows; new URL(...).pathname
|
|
214
|
+
// returns '/C:/...' on Windows which resolve() does not handle correctly.
|
|
215
|
+
const frameworkRoot = fileURLToPath(new URL('../../..', import.meta.url))
|
|
216
|
+
const bundled = scanDriversDir(join(frameworkRoot, 'drivers'), 'bundled')
|
|
217
|
+
const user = scanDriversDir(join(target, '.aperant/drivers'), 'user')
|
|
218
|
+
const discovered = [...bundled, ...user]
|
|
219
|
+
|
|
220
|
+
const drivers = []
|
|
221
|
+
for (const d of discovered) {
|
|
222
|
+
if (d.malformed) {
|
|
223
|
+
drivers.push({
|
|
224
|
+
driverId: '(malformed)',
|
|
225
|
+
version: '?',
|
|
226
|
+
locality: d.locality,
|
|
227
|
+
manifest_valid: false,
|
|
228
|
+
manifest_errors: [{ path: '/', message: d.parse_error }],
|
|
229
|
+
idl_conformant: false,
|
|
230
|
+
missing_exports: [],
|
|
231
|
+
unknown_capabilities: [],
|
|
232
|
+
preflight_status: 'skipped',
|
|
233
|
+
missing_deps: [],
|
|
234
|
+
})
|
|
235
|
+
continue
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const manifest = d.manifest
|
|
239
|
+
const schemaResult = validateManifest(manifest)
|
|
240
|
+
const manifest_valid = schemaResult.valid === true
|
|
241
|
+
const manifest_errors = schemaResult.valid === false ? schemaResult.errors : []
|
|
242
|
+
|
|
243
|
+
let idlResult = { idl_conformant: false, missing_exports: [], unknown_capabilities: [] }
|
|
244
|
+
let idl_skipped_reason
|
|
245
|
+
if (manifest_valid) {
|
|
246
|
+
const gate = trustGate(manifest, d.locality, target)
|
|
247
|
+
if (gate.ok) {
|
|
248
|
+
idlResult = await checkIdlConformance(manifest, d.dir)
|
|
249
|
+
} else {
|
|
250
|
+
// Trust gate denied. Do NOT dynamic-import the driver — that
|
|
251
|
+
// would execute arbitrary third-party code under the audit
|
|
252
|
+
// command. Report idl_conformant=false with the gate reason
|
|
253
|
+
// so the operator sees exactly why conformance was not run.
|
|
254
|
+
idl_skipped_reason = `trust gate denied: ${gate.reason}`
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
let preflightResult = { preflight_status: 'skipped', missing_deps: [] }
|
|
259
|
+
if (manifest_valid) {
|
|
260
|
+
preflightResult = await runPreflight(manifest)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const entry = {
|
|
264
|
+
driverId: manifest.driverId ?? '(unknown)',
|
|
265
|
+
version: manifest.version ?? '?',
|
|
266
|
+
locality: d.locality,
|
|
267
|
+
manifest_valid,
|
|
268
|
+
manifest_errors,
|
|
269
|
+
idl_conformant: idlResult.idl_conformant,
|
|
270
|
+
missing_exports: idlResult.missing_exports,
|
|
271
|
+
unknown_capabilities: idlResult.unknown_capabilities,
|
|
272
|
+
preflight_status: preflightResult.preflight_status,
|
|
273
|
+
missing_deps: preflightResult.missing_deps,
|
|
274
|
+
}
|
|
275
|
+
if (idl_skipped_reason !== undefined) entry.idl_skipped_reason = idl_skipped_reason
|
|
276
|
+
const warning = publisherWarning(manifest)
|
|
277
|
+
if (warning !== undefined) entry.publisher_warning = warning
|
|
278
|
+
drivers.push(entry)
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const all_pass = drivers.every((d) => d.manifest_valid && d.idl_conformant)
|
|
282
|
+
return ok({
|
|
283
|
+
status: 'ok',
|
|
284
|
+
command: 'driver-doctor',
|
|
285
|
+
projectDir: target,
|
|
286
|
+
drivers,
|
|
287
|
+
summary: {
|
|
288
|
+
total: drivers.length,
|
|
289
|
+
pass: drivers.filter((d) => d.manifest_valid && d.idl_conformant).length,
|
|
290
|
+
fail: drivers.filter((d) => !d.manifest_valid || !d.idl_conformant).length,
|
|
291
|
+
overall: all_pass ? 'pass' : 'fail',
|
|
292
|
+
},
|
|
293
|
+
})
|
|
294
|
+
}
|
|
@@ -135,6 +135,20 @@ async function cmdGateCheck(projectDir, extraArgs) {
|
|
|
135
135
|
const cache = cacheMod ? cacheMod.readCache(ctx.taskDir) : {}
|
|
136
136
|
|
|
137
137
|
for (const gate of gates) {
|
|
138
|
+
// Applicability check fires BEFORE cache lookup or check() execution.
|
|
139
|
+
// Gates that don't apply to the active profile (lite vs full-pipeline)
|
|
140
|
+
// short-circuit with a skip verdict. Skip results are intentionally
|
|
141
|
+
// NOT cached (see status: 'skip' branch below), so a track change on
|
|
142
|
+
// the same task immediately reflects in the next gate-check.
|
|
143
|
+
if (typeof gate.applies_to_profile === 'function' && gate.applies_to_profile(ctx) === false) {
|
|
144
|
+
skipped.push({
|
|
145
|
+
id: gate.id,
|
|
146
|
+
severity: gate.severity,
|
|
147
|
+
reason: `profile-not-applicable: ${ctx.profile}`,
|
|
148
|
+
})
|
|
149
|
+
continue
|
|
150
|
+
}
|
|
151
|
+
|
|
138
152
|
let verdict
|
|
139
153
|
let cached = false
|
|
140
154
|
let inputHash = null
|
|
@@ -244,6 +258,7 @@ async function cmdGateCheck(projectDir, extraArgs) {
|
|
|
244
258
|
command: 'gate-check',
|
|
245
259
|
skill,
|
|
246
260
|
task_id: taskId,
|
|
261
|
+
profile: ctx.profile,
|
|
247
262
|
passed: passed.map((p) => p.id),
|
|
248
263
|
warned: warned.map((w) => ({ id: w.id, reason: w.reason })),
|
|
249
264
|
skipped: skipped.map((s) => ({ id: s.id, reason: s.reason })),
|