@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,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 })),
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
* rewrite, 2026-04).
|
|
4
4
|
*
|
|
5
5
|
* Replaces the previous Claude-only `cp -r` installer with the per-runtime
|
|
6
|
-
* transform pipeline (src/cli/install/pipeline.mjs).
|
|
7
|
-
* supported via `--<runtime>` flags or `--all`. Default with no
|
|
6
|
+
* transform pipeline (src/cli/install/pipeline.mjs). Registered runtime
|
|
7
|
+
* targets are supported via `--<runtime>` flags or `--all`. Default with no
|
|
8
|
+
* flags in
|
|
8
9
|
* non-TTY mode → `--claude` (backward compat — existing skills invoke
|
|
9
10
|
* `apt-tools init <dir>` with no flags). Default in TTY → interactive prompt
|
|
10
11
|
* with auto-detection markers.
|
|
@@ -40,12 +41,13 @@ import { buildAperantGitignoreTemplate } from '../config/upgrade-gitignore.mjs'
|
|
|
40
41
|
import { detectPublicRemote } from '../git/remote.mjs'
|
|
41
42
|
import { detectArchetype } from '../install/archetype.mjs'
|
|
42
43
|
import { installKernel } from '../install/install-kernel.mjs'
|
|
44
|
+
import { MANIFEST_FILENAME } from '../install/manifest.mjs'
|
|
43
45
|
import {
|
|
44
46
|
discoverCanonicalFiles,
|
|
45
47
|
runInstall,
|
|
46
48
|
writeLastInstallSummary,
|
|
47
49
|
} from '../install/pipeline.mjs'
|
|
48
|
-
import { RUNTIMES, suggestRuntimes } from '../install/runtime-detect.mjs'
|
|
50
|
+
import { getRuntime, RUNTIMES, suggestRuntimes } from '../install/runtime-detect.mjs'
|
|
49
51
|
import { runLegacyCleanup } from '../install/runtime-migrate.mjs'
|
|
50
52
|
import {
|
|
51
53
|
CLASS_A_SETTINGS,
|
|
@@ -392,6 +394,42 @@ function selectRuntimes(targetDir, parsedFlags) {
|
|
|
392
394
|
return [...suggested]
|
|
393
395
|
}
|
|
394
396
|
|
|
397
|
+
/**
|
|
398
|
+
* Return the runtime ids that should be installed during a no-schema-drift
|
|
399
|
+
* init. A current config is not enough to prove runtime files exist on this
|
|
400
|
+
* machine: team repos often carry `.aperant/config.json` but not local
|
|
401
|
+
* runtime roots/manifests.
|
|
402
|
+
*
|
|
403
|
+
* @param {string} targetDir
|
|
404
|
+
* @param {string[]} runtimeIds
|
|
405
|
+
* @returns {string[]}
|
|
406
|
+
*/
|
|
407
|
+
function runtimesMissingManifest(targetDir, runtimeIds) {
|
|
408
|
+
const missing = []
|
|
409
|
+
for (const id of runtimeIds) {
|
|
410
|
+
const runtime = getRuntime(id)
|
|
411
|
+
if (!runtime) continue
|
|
412
|
+
const manifestPath = join(targetDir, runtime.installRoot, MANIFEST_FILENAME)
|
|
413
|
+
if (!existsSync(manifestPath)) missing.push(id)
|
|
414
|
+
}
|
|
415
|
+
return missing
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Current-schema init should still run the runtime installer when the user
|
|
420
|
+
* explicitly requested runtimes, when --force-runtimes is present, or when
|
|
421
|
+
* local runtime roots are suggested but have no apt manifest.
|
|
422
|
+
*
|
|
423
|
+
* @param {string} targetDir
|
|
424
|
+
* @param {ReturnType<typeof parseInstallFlags>} parsedFlags
|
|
425
|
+
* @returns {string[]}
|
|
426
|
+
*/
|
|
427
|
+
function selectNoDriftRuntimeInstallIds(targetDir, parsedFlags) {
|
|
428
|
+
if (parsedFlags.forceRuntimes) return selectRuntimes(targetDir, parsedFlags)
|
|
429
|
+
if (parsedFlags.runtimes_explicit) return parsedFlags.runtimes
|
|
430
|
+
return runtimesMissingManifest(targetDir, suggestRuntimes(targetDir))
|
|
431
|
+
}
|
|
432
|
+
|
|
395
433
|
/**
|
|
396
434
|
* Non-TTY / scripted path — derive answers from flags + sensible defaults,
|
|
397
435
|
* gated by the Class A/B classifier (spec R2 + R3).
|
|
@@ -663,11 +701,16 @@ async function interactiveInterview(targetDir, version, canonicalRoot, archetype
|
|
|
663
701
|
migration.applied_defaults.length === 0 &&
|
|
664
702
|
migration.auto_added.length === 0
|
|
665
703
|
if (noDrift) {
|
|
666
|
-
|
|
667
|
-
// "Nothing to do." outro so the
|
|
668
|
-
//
|
|
669
|
-
if (
|
|
670
|
-
return {
|
|
704
|
+
const runtimeInstallIds = selectNoDriftRuntimeInstallIds(targetDir, parsedFlags)
|
|
705
|
+
// Runtime repair bypasses the TTY "Nothing to do." outro so the
|
|
706
|
+
// runtime walker can re-stamp missing manifests first.
|
|
707
|
+
if (runtimeInstallIds.length > 0) {
|
|
708
|
+
return {
|
|
709
|
+
upgrade_noop: true,
|
|
710
|
+
skipBootstrap,
|
|
711
|
+
migration,
|
|
712
|
+
runtime_reinstall_runtimes: runtimeInstallIds,
|
|
713
|
+
}
|
|
671
714
|
}
|
|
672
715
|
process.stdout.write(`\n${aperantBanner(version)}\n\n`)
|
|
673
716
|
clack.intro('install')
|
|
@@ -1158,20 +1201,20 @@ export async function cmdInit(projectDir, extraArgs = []) {
|
|
|
1158
1201
|
)
|
|
1159
1202
|
}
|
|
1160
1203
|
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1204
|
+
const noDriftRuntimeInstallIds = answers?.upgrade_noop
|
|
1205
|
+
? (answers.runtime_reinstall_runtimes ??
|
|
1206
|
+
selectNoDriftRuntimeInstallIds(installTarget, parsedFlags))
|
|
1207
|
+
: []
|
|
1208
|
+
|
|
1209
|
+
// Upgrade no-drift with no runtime work — short-circuit before
|
|
1210
|
+
// installRuntimes. /apt:update remains the right surface for refreshing
|
|
1211
|
+
// already-manifested skills; init only repairs selected or suggested
|
|
1212
|
+
// runtime roots that lack manifests.
|
|
1164
1213
|
// The bootstrap auto-chain still runs (it's deterministic and
|
|
1165
1214
|
// idempotent — preserves PROJECT.md, refreshes sidecar from it).
|
|
1166
1215
|
// In TTY mode clack already rendered "Nothing to do." to stdout,
|
|
1167
1216
|
// so suppress the JSON envelope (matches the cancelled path).
|
|
1168
|
-
|
|
1169
|
-
// 0.6.7 (Bug 2 fix) — `--force-runtimes` bypasses this short-circuit
|
|
1170
|
-
// so `/apt:update` can re-stamp every runtime manifest even when
|
|
1171
|
-
// `.aperant/config.json` is at the current schema. We synthesize a
|
|
1172
|
-
// real InstallAnswers shape from the existing config so
|
|
1173
|
-
// installRuntimes + finishInit run their normal upgrade path.
|
|
1174
|
-
if (answers?.upgrade_noop && !parsedFlags.forceRuntimes) {
|
|
1217
|
+
if (answers?.upgrade_noop && noDriftRuntimeInstallIds.length === 0) {
|
|
1175
1218
|
if (interactiveTty) return rawText('', 0)
|
|
1176
1219
|
const bootstrap = answers.skipBootstrap
|
|
1177
1220
|
? { status: 'skipped', reason: '--skip-bootstrap flag' }
|
|
@@ -1187,18 +1230,14 @@ export async function cmdInit(projectDir, extraArgs = []) {
|
|
|
1187
1230
|
})
|
|
1188
1231
|
}
|
|
1189
1232
|
|
|
1190
|
-
//
|
|
1191
|
-
//
|
|
1192
|
-
//
|
|
1193
|
-
// cross-verification are read from answers.migration.migrated (already
|
|
1194
|
-
// in memory from nonInteractiveAnswers — avoids a second config read);
|
|
1195
|
-
// runtimes resolved via selectRuntimes helper shared with the upgrade path.
|
|
1233
|
+
// Current-schema runtime install — synthesize a real InstallAnswers
|
|
1234
|
+
// shape from the existing config so installRuntimes + finishInit run
|
|
1235
|
+
// their normal upgrade path.
|
|
1196
1236
|
let effectiveAnswers = answers
|
|
1197
|
-
if (answers?.upgrade_noop &&
|
|
1237
|
+
if (answers?.upgrade_noop && noDriftRuntimeInstallIds.length > 0) {
|
|
1198
1238
|
const existing = answers.migration?.migrated || {}
|
|
1199
|
-
const runtimes = selectRuntimes(installTarget, parsedFlags)
|
|
1200
1239
|
effectiveAnswers = {
|
|
1201
|
-
runtimes,
|
|
1240
|
+
runtimes: noDriftRuntimeInstallIds,
|
|
1202
1241
|
autonomyDefault: existing?.autonomy?.default ?? 1,
|
|
1203
1242
|
visibility: existing?.share?.visibility === 'team' ? 'team' : 'solo',
|
|
1204
1243
|
crossVerificationTools: existing?.multi_model?.cross_verification?.tools ?? [],
|
|
@@ -1211,12 +1250,15 @@ export async function cmdInit(projectDir, extraArgs = []) {
|
|
|
1211
1250
|
}
|
|
1212
1251
|
}
|
|
1213
1252
|
|
|
1214
|
-
// FRAMEWORK-BUG-021 — sweep the legacy `.
|
|
1253
|
+
// FRAMEWORK-BUG-021 — sweep the legacy `.codex/` codex tree
|
|
1215
1254
|
// BEFORE the install pipeline runs so the install can re-stamp
|
|
1216
|
-
// `.
|
|
1217
|
-
//
|
|
1218
|
-
//
|
|
1219
|
-
//
|
|
1255
|
+
// `.agents/skills/<folder>/SKILL.md` (Codex CLI's documented
|
|
1256
|
+
// surface, per https://developers.openai.com/codex/skills) against
|
|
1257
|
+
// a clean slate. Only fires when codex is in the requested runtime
|
|
1258
|
+
// set and `--keep-legacy` was not passed; the helper itself is
|
|
1259
|
+
// conservative (no-op on managed installs, no-op when no legacy
|
|
1260
|
+
// framework files are present). The v0.8.2 inversion is
|
|
1261
|
+
// documented in `runtime-migrate.mjs:7-12`.
|
|
1220
1262
|
let legacyCleanup = null
|
|
1221
1263
|
if (effectiveAnswers.runtimes.includes('codex')) {
|
|
1222
1264
|
if (parsedFlags.keepLegacy) {
|
|
@@ -1226,7 +1268,7 @@ export async function cmdInit(projectDir, extraArgs = []) {
|
|
|
1226
1268
|
legacyCleanup = { runtime: 'codex', ...cleanup }
|
|
1227
1269
|
if (cleanup.ran && cleanup.retained.length > 0) {
|
|
1228
1270
|
process.stderr.write(
|
|
1229
|
-
`[apt-tools] legacy_cleanup retained ${cleanup.retained.length} user-added path(s) under .
|
|
1271
|
+
`[apt-tools] legacy_cleanup retained ${cleanup.retained.length} user-added path(s) under .codex/: ${cleanup.retained.join(', ')}\n`,
|
|
1230
1272
|
)
|
|
1231
1273
|
}
|
|
1232
1274
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { cmdInstallFromSource } from '../install/install-from-source.mjs'
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* commands/modes.mjs — `apt-tools modes list
|
|
2
|
+
* commands/modes.mjs — `apt-tools modes list`.
|
|
3
3
|
*
|
|
4
4
|
* Power-user discoverability tool. Returns a JSON envelope with one
|
|
5
5
|
* record per discovered skill, exposing modes / flags / content-formats /
|
|
@@ -28,7 +28,11 @@ import { existsSync, readFileSync } from 'node:fs'
|
|
|
28
28
|
import { homedir } from 'node:os'
|
|
29
29
|
import { dirname, join, resolve } from 'node:path'
|
|
30
30
|
import { fileURLToPath } from 'node:url'
|
|
31
|
-
import {
|
|
31
|
+
import {
|
|
32
|
+
getTaskIsolationConfig,
|
|
33
|
+
getTrustedSkillSources,
|
|
34
|
+
loadMergedProjectConfig,
|
|
35
|
+
} from '../config/load.mjs'
|
|
32
36
|
import '../gate/gates/index.mjs'
|
|
33
37
|
import * as gateRegistry from '../gate/registry.mjs'
|
|
34
38
|
import { detectHost, SUPPORTED_HOSTS } from '../host/detect.mjs'
|
|
@@ -264,6 +268,19 @@ export function cmdRoute(projectDir, extraArgs) {
|
|
|
264
268
|
else if (f === 'debug') track_override = 'DEBUG'
|
|
265
269
|
}
|
|
266
270
|
|
|
271
|
+
// FRAMEWORK-RFC-001 — consent signal for spar pipeline auto-inject.
|
|
272
|
+
// Reads merged config (config.json ∪ config.local.json) via the same
|
|
273
|
+
// helper apt-setup writes through. AUDIT-001 — spar fields live in
|
|
274
|
+
// .aperant/config.local.json, not shared config. Resolved once and
|
|
275
|
+
// folded into the envelope by both classify branches below.
|
|
276
|
+
// `loadMergedProjectConfig` returns null when no shared config exists
|
|
277
|
+
// (first-time init path) — treat as consent-off + no partner.
|
|
278
|
+
const merged = loadMergedProjectConfig(targetDir) ?? {}
|
|
279
|
+
const sparEnabled = merged.spar?.enabled === true
|
|
280
|
+
const sparPartner = merged.spar?.partner ?? null
|
|
281
|
+
const userPassedExplicitSparGates =
|
|
282
|
+
rawInput.includes('--spar-gates') || flags.some((f) => f.startsWith('spar-gates'))
|
|
283
|
+
|
|
267
284
|
const taskDescription = normalizeAptInvocation(rawInput.replace(/--\w+/g, '').trim())
|
|
268
285
|
|
|
269
286
|
const configPath = join(targetDir, '.aperant', 'config.json')
|
|
@@ -399,6 +416,17 @@ export function cmdRoute(projectDir, extraArgs) {
|
|
|
399
416
|
const agentName = spawnAgent ? index.agentOf(primarySkill) || 'apt-planner' : null
|
|
400
417
|
const options = buildTrackOptions(track)
|
|
401
418
|
const gate_preview = previewGatesFor(primarySkill)
|
|
419
|
+
// FRAMEWORK-RFC-001 — flag-override path: track is known deterministically,
|
|
420
|
+
// so we can resolve the spar gate string inline and emit it directly.
|
|
421
|
+
// QUICK/DEBUG are Fast-Path-exempt — no inject. User-passed --spar-gates
|
|
422
|
+
// wins over consent (no double-inject). Field omitted entirely when null
|
|
423
|
+
// so the envelope stays byte-equivalent for users who never opted in.
|
|
424
|
+
let spar_inject_gates = null
|
|
425
|
+
if (sparEnabled && !userPassedExplicitSparGates) {
|
|
426
|
+
if (track === 'STANDARD') spar_inject_gates = 'plan'
|
|
427
|
+
else if (track === 'DEEP') spar_inject_gates = 'plan,execute'
|
|
428
|
+
// QUICK / DEBUG → stays null
|
|
429
|
+
}
|
|
402
430
|
return ok({
|
|
403
431
|
status: 'ok',
|
|
404
432
|
command: 'route',
|
|
@@ -417,6 +445,7 @@ export function cmdRoute(projectDir, extraArgs) {
|
|
|
417
445
|
task_isolation,
|
|
418
446
|
update_check,
|
|
419
447
|
...(gate_preview ? { gate_preview } : {}),
|
|
448
|
+
...(spar_inject_gates ? { spar_inject_gates } : {}),
|
|
420
449
|
classification: {
|
|
421
450
|
source: 'flag-override',
|
|
422
451
|
reason: `Explicit --${track_override.toLowerCase()} flag`,
|
|
@@ -467,6 +496,18 @@ export function cmdRoute(projectDir, extraArgs) {
|
|
|
467
496
|
reason:
|
|
468
497
|
'Track and skill classification deferred to the host LLM; see classification_meta.trust_notice.',
|
|
469
498
|
},
|
|
499
|
+
// FRAMEWORK-RFC-001 — host model composes --spar-gates from these
|
|
500
|
+
// signals + its track classification per apt/SKILL.md MODE: classify
|
|
501
|
+
// Step 4. Field is absent when consent is off (envelope stays slim).
|
|
502
|
+
...(sparEnabled
|
|
503
|
+
? {
|
|
504
|
+
spar_consent: {
|
|
505
|
+
enabled: true,
|
|
506
|
+
partner: sparPartner,
|
|
507
|
+
user_passed_explicit_gates: userPassedExplicitSparGates,
|
|
508
|
+
},
|
|
509
|
+
}
|
|
510
|
+
: {}),
|
|
470
511
|
},
|
|
471
512
|
})
|
|
472
513
|
} catch (e) {
|
package/src/cli/config/load.mjs
CHANGED
|
@@ -361,11 +361,17 @@ export function getTrustedSkillSources(projectDir, cli) {
|
|
|
361
361
|
)
|
|
362
362
|
if (existsSync(consumerSkills)) defaults.push(consumerSkills)
|
|
363
363
|
// 4. Host CLI's installed runtime skills — only when the router knows
|
|
364
|
-
// which runtime it's in. Some hosts (
|
|
365
|
-
// .
|
|
364
|
+
// which runtime it's in. Some hosts (claude-code, gemini, …) install into
|
|
365
|
+
// .claude/skills / .gemini/skills etc; the router picks skills from
|
|
366
366
|
// the host-local directory so per-runtime transforms are honored.
|
|
367
|
+
//
|
|
368
|
+
// Codex is excluded: its transform strips frontmatter to {name, description}
|
|
369
|
+
// for codex validator compliance, and that stripped form fails the
|
|
370
|
+
// router's SkillFrontmatterSchema (contract.mjs). The canonical + kernel
|
|
371
|
+
// roots above still serve routing on Codex hosts (they carry the fat
|
|
372
|
+
// frontmatter the schema requires).
|
|
367
373
|
const installRoot = cli ? cliToInstallRoot(cli) : null
|
|
368
|
-
if (installRoot) {
|
|
374
|
+
if (installRoot && cli !== 'codex') {
|
|
369
375
|
const runtimeSkills = join(targetDir, installRoot, 'skills')
|
|
370
376
|
if (existsSync(runtimeSkills)) defaults.push(runtimeSkills)
|
|
371
377
|
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* postcondition of `apt-tools commit`. This closes the Conductor v2 gap
|
|
8
8
|
* exposed during live verification 2026-05-15: the planner's QUICK /
|
|
9
9
|
* SIMPLE track is hard-exempt from "new interaction discipline"
|
|
10
|
-
* (
|
|
10
|
+
* (Aperant Fast Path Guarantee ID-05), so the markdown emit step was
|
|
11
11
|
* never executed for tasks router-classified as simple. The fleet
|
|
12
12
|
* never saw artifact.ready, the PlanReadyCard never auto-promoted.
|
|
13
13
|
*
|
|
@@ -42,6 +42,10 @@ const CONDUCTOR_OPS = new Set([
|
|
|
42
42
|
'conductor.action.executed',
|
|
43
43
|
'conductor.action.gate_blocked',
|
|
44
44
|
'conductor.boot_reconciled',
|
|
45
|
+
// AC21 — MCP-server consent decisions audited to .aperant/events/*.jsonl
|
|
46
|
+
'mcp-consent.approved',
|
|
47
|
+
'mcp-consent.denied',
|
|
48
|
+
'mcp-consent.tofu-reprompt',
|
|
45
49
|
])
|
|
46
50
|
|
|
47
51
|
/** Allowed values for `phase.changed.to` — PTY busy/idle only. */
|
|
@@ -133,6 +137,21 @@ export function validateConductorEvent(op, data) {
|
|
|
133
137
|
'blocked_reason',
|
|
134
138
|
])
|
|
135
139
|
|
|
140
|
+
// AC21 — MCP server consent audit. Three ops share the same
|
|
141
|
+
// payload shape: who approved/denied which server, with which
|
|
142
|
+
// configHash, at what time. session_id is a session-scoped string
|
|
143
|
+
// (chat-session row id).
|
|
144
|
+
case 'mcp-consent.approved':
|
|
145
|
+
case 'mcp-consent.denied':
|
|
146
|
+
case 'mcp-consent.tofu-reprompt':
|
|
147
|
+
return requireFields(op, d, [
|
|
148
|
+
'server_id',
|
|
149
|
+
'config_sha256',
|
|
150
|
+
'decided_at',
|
|
151
|
+
'decided_by',
|
|
152
|
+
'session_id',
|
|
153
|
+
])
|
|
154
|
+
|
|
136
155
|
case 'conductor.boot_reconciled': {
|
|
137
156
|
// Fleet-wide event — numeric fields only. All optional but if
|
|
138
157
|
// present must be finite numbers (zero allowed).
|