@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
package/src/cli/dispatch.mjs
CHANGED
|
@@ -53,7 +53,9 @@ import { cmdDesignScan } from './commands/design-scan.mjs'
|
|
|
53
53
|
import { cmdDesignTemplate } from './commands/design-template.mjs'
|
|
54
54
|
import { cmdDesignValidate } from './commands/design-validate.mjs'
|
|
55
55
|
import { cmdDetectProject } from './commands/detect-project.mjs'
|
|
56
|
+
import { cmdDetectRuntime } from './commands/detect-runtime.mjs'
|
|
56
57
|
import { cmdDocsAudit } from './commands/docs-audit.mjs'
|
|
58
|
+
import { cmdDriverDoctor } from './commands/driver-doctor.mjs'
|
|
57
59
|
import { cmdEditCheck } from './commands/edit-check.mjs'
|
|
58
60
|
import { cmdEffects } from './commands/effects.mjs'
|
|
59
61
|
import { cmdEvent } from './commands/event.mjs'
|
|
@@ -64,6 +66,7 @@ import { cmdHealthCheck } from './commands/health-check.mjs'
|
|
|
64
66
|
import { cmdHostDetect, cmdReconcile } from './commands/host-detect.mjs'
|
|
65
67
|
import { cmdInit } from './commands/init.mjs'
|
|
66
68
|
import { cmdInitState } from './commands/init-state.mjs'
|
|
69
|
+
import { cmdInstallFromSource } from './commands/install-from-source.mjs'
|
|
67
70
|
import { cmdLock } from './commands/lock.mjs'
|
|
68
71
|
import { cmdMigratePrReviews } from './commands/migrate-pr-reviews.mjs'
|
|
69
72
|
import { cmdPersonas } from './commands/personas.mjs'
|
|
@@ -213,6 +216,18 @@ export const DISPATCH = {
|
|
|
213
216
|
'state-update': (args) => wrap(cmdStateUpdate)(args[1], args.slice(2)),
|
|
214
217
|
'state-artifacts': (args) => wrap(cmdStateArtifacts)(args[1], args.slice(2)),
|
|
215
218
|
'detect-project': (args) => wrap(cmdDetectProject)(args[1]),
|
|
219
|
+
'detect-runtime': (args) => wrap(cmdDetectRuntime)(args[1], args.slice(2)),
|
|
220
|
+
driver: (args) => {
|
|
221
|
+
// `apt-tools driver doctor <project-dir>` — only subcommand for now.
|
|
222
|
+
if (args[1] === 'doctor') return wrap(cmdDriverDoctor)(args[2], args.slice(3))
|
|
223
|
+
return wrap(() => ({
|
|
224
|
+
envelope: {
|
|
225
|
+
status: 'error',
|
|
226
|
+
error: `Usage: apt-tools driver doctor <project-dir>. Unknown subcommand: ${args[1] ?? '(none)'}`,
|
|
227
|
+
},
|
|
228
|
+
exitCode: 1,
|
|
229
|
+
}))()
|
|
230
|
+
},
|
|
216
231
|
'features-audit': (args) => wrap(cmdFeaturesAudit)(args[1], args.slice(2)),
|
|
217
232
|
'init-state': (args) => wrap(cmdInitState)(args[1], args.slice(2)),
|
|
218
233
|
'risk-check': (args) => wrap(cmdRiskCheck)(args[1], args.slice(2)),
|
|
@@ -232,6 +247,7 @@ export const DISPATCH = {
|
|
|
232
247
|
'detect-worktree': (args) => wrap(cmdDetectWorktree)(args[1]),
|
|
233
248
|
template: (args) => wrap(cmdTemplate)(args[1]),
|
|
234
249
|
'check-version': (args) => wrap(cmdCheckVersion)(args[1]),
|
|
250
|
+
'install-from-source': (args) => wrap(cmdInstallFromSource)(args[1]),
|
|
235
251
|
'health-check': (args) => wrap(cmdHealthCheck)(args[1]),
|
|
236
252
|
route: (args) => wrap(cmdRoute)(args[1], args.slice(2)),
|
|
237
253
|
statusline: (args) => wrap(cmdStatusline)(args[1], args.slice(2)),
|
|
@@ -287,7 +303,7 @@ export const DISPATCH = {
|
|
|
287
303
|
},
|
|
288
304
|
'plan-approval': (args) => wrap(cmdPlanApproval)(args[1], args[2], args.slice(3)),
|
|
289
305
|
|
|
290
|
-
// ----
|
|
306
|
+
// ---- Aperant adoption (ST-5, ST-6, ST-10, ST-13) artifact writers + discoverability ----
|
|
291
307
|
context: (args) => wrap(cmdContext)(args[1], args[2], args.slice(3)),
|
|
292
308
|
adr: (args) => wrap(cmdAdr)(args[1], args[2], args.slice(3)),
|
|
293
309
|
triage: (args) => wrap(cmdTriage)(args[1], args[2], args.slice(3)),
|
package/src/cli/gate/context.mjs
CHANGED
|
@@ -11,7 +11,13 @@
|
|
|
11
11
|
* taskId: string,
|
|
12
12
|
* taskDir: string, // <sharedRoot>/.aperant/tasks/<taskId>/
|
|
13
13
|
* taskRecord: object | null, // state.active_tasks[taskId] (or null)
|
|
14
|
-
* config: object
|
|
14
|
+
* config: object, // .aperant/config.json (empty {} if none)
|
|
15
|
+
* profile: 'lite' | 'full-pipeline'
|
|
16
|
+
* // derived from taskRecord.track:
|
|
17
|
+
* // QUICK | DEBUG → 'lite'
|
|
18
|
+
* // STANDARD | DEEP → 'full-pipeline'
|
|
19
|
+
* // null taskRecord → 'full-pipeline'
|
|
20
|
+
* // (fail-closed)
|
|
15
21
|
* }
|
|
16
22
|
*
|
|
17
23
|
* The builder is side-effect-free — it never writes state, never mutates the
|
|
@@ -24,6 +30,8 @@ import { createCoordinationStore } from '../coordination/store.mjs'
|
|
|
24
30
|
|
|
25
31
|
const SAFE_TASK_ID_PATTERN = /^[a-zA-Z0-9_-]+$/
|
|
26
32
|
|
|
33
|
+
const KNOWN_TRACKS = new Set(['QUICK', 'STANDARD', 'DEEP', 'DEBUG'])
|
|
34
|
+
|
|
27
35
|
function readJsonIfExists(path) {
|
|
28
36
|
if (!existsSync(path)) return null
|
|
29
37
|
try {
|
|
@@ -81,6 +89,16 @@ export function buildGateContext(projectRoot, taskId) {
|
|
|
81
89
|
config = readJsonIfExists(join(absRoot, '.aperant', 'config.json')) ?? {}
|
|
82
90
|
}
|
|
83
91
|
|
|
92
|
+
if (taskRecord?.track && !KNOWN_TRACKS.has(taskRecord.track)) {
|
|
93
|
+
process.stderr.write(
|
|
94
|
+
`[apt:gate] WARNING: unrecognized track "${taskRecord.track}" — defaulting to full-pipeline. ` +
|
|
95
|
+
`Expected one of: ${[...KNOWN_TRACKS].join(', ')}.\n`,
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const profile =
|
|
100
|
+
taskRecord?.track === 'QUICK' || taskRecord?.track === 'DEBUG' ? 'lite' : 'full-pipeline'
|
|
101
|
+
|
|
84
102
|
return {
|
|
85
103
|
projectRoot: absRoot,
|
|
86
104
|
sharedRoot,
|
|
@@ -88,5 +106,6 @@ export function buildGateContext(projectRoot, taskId) {
|
|
|
88
106
|
taskDir,
|
|
89
107
|
taskRecord,
|
|
90
108
|
config,
|
|
109
|
+
profile,
|
|
91
110
|
}
|
|
92
111
|
}
|
|
@@ -23,6 +23,7 @@ const gate = {
|
|
|
23
23
|
description: 'Cross-artifact consistency (spec/plan/review/qa/progress) has no error drift.',
|
|
24
24
|
severity: 'blocking',
|
|
25
25
|
skills: ['ship'],
|
|
26
|
+
applies_to_profile: (ctx) => ctx.profile === 'full-pipeline',
|
|
26
27
|
async check(ctx) {
|
|
27
28
|
const inputs = [
|
|
28
29
|
join(ctx.taskDir, 'spec.md'),
|
|
@@ -26,6 +26,7 @@ const gate = {
|
|
|
26
26
|
description: 'review.json has no unresolved critical/major findings (skipped if absent)',
|
|
27
27
|
severity: 'blocking',
|
|
28
28
|
skills: ['ship'],
|
|
29
|
+
applies_to_profile: (ctx) => ctx.profile === 'full-pipeline',
|
|
29
30
|
async check(ctx) {
|
|
30
31
|
const path = join(ctx.taskDir, 'review.json')
|
|
31
32
|
const inputs = [path]
|
|
@@ -53,6 +53,7 @@ const gate = {
|
|
|
53
53
|
'qa_signoff.json exists with verdict === "approved" (or "approved-with-notes" when configured)',
|
|
54
54
|
severity: 'blocking',
|
|
55
55
|
skills: ['ship'],
|
|
56
|
+
applies_to_profile: (ctx) => ctx.profile === 'full-pipeline',
|
|
56
57
|
async check(ctx) {
|
|
57
58
|
const path = join(ctx.taskDir, 'qa_signoff.json')
|
|
58
59
|
const inputs = [path]
|
|
@@ -10,7 +10,12 @@
|
|
|
10
10
|
* description: string, // human-readable one-liner
|
|
11
11
|
* severity: 'blocking' | 'warn', // blocking gates halt the skill
|
|
12
12
|
* skills: string[], // skills this gate fires for
|
|
13
|
-
* check: async (ctx) => GateVerdict
|
|
13
|
+
* check: async (ctx) => GateVerdict,
|
|
14
|
+
* applies_to_profile?: (ctx) => boolean // optional; when present and
|
|
15
|
+
* // returns false, the gate is
|
|
16
|
+
* // skipped before cache lookup or
|
|
17
|
+
* // check() invocation. Omit to
|
|
18
|
+
* // apply universally (default).
|
|
14
19
|
* }
|
|
15
20
|
*
|
|
16
21
|
* A GateVerdict is:
|
|
@@ -57,6 +62,11 @@ export function registerGate(gate) {
|
|
|
57
62
|
if (typeof gate.check !== 'function') {
|
|
58
63
|
throw new Error(`registerGate(${gate.id}): check must be a function`)
|
|
59
64
|
}
|
|
65
|
+
if (gate.applies_to_profile !== undefined && typeof gate.applies_to_profile !== 'function') {
|
|
66
|
+
throw new Error(
|
|
67
|
+
`registerGate(${gate.id}): applies_to_profile, when present, must be a function`,
|
|
68
|
+
)
|
|
69
|
+
}
|
|
60
70
|
registry.set(gate.id, gate)
|
|
61
71
|
return gate
|
|
62
72
|
}
|
|
@@ -11,8 +11,6 @@
|
|
|
11
11
|
* the lookups below work automatically.
|
|
12
12
|
*
|
|
13
13
|
* `null` is returned for:
|
|
14
|
-
* - CLIs with no install target (e.g. `pi` — host detected, but no apt
|
|
15
|
-
* install root exists for the Pi runtime).
|
|
16
14
|
* - `'unknown'` — deterministic branches (init/status/help) don't need a
|
|
17
15
|
* mapping; the router falls back to the framework-scoped dev default
|
|
18
16
|
* defined in getTrustedSkillSources().
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* install/find-workspace-framework-version.mjs — Bundle A.
|
|
3
|
+
*
|
|
4
|
+
* Locates the workspace `packages/framework/package.json` above `targetDir`
|
|
5
|
+
* so the SessionStart worker and `apt-tools check-version` can detect the
|
|
6
|
+
* "self-host contributor's workspace is ahead of the installed kernel" case
|
|
7
|
+
* WITHOUT requiring the user to remember the `--from-source` flag.
|
|
8
|
+
*
|
|
9
|
+
* Mirrors the bounded-walk + semver-validation contract of the route-side
|
|
10
|
+
* `detectSourceWorkspaceDrift` (route/drift-detect.mjs:32-107), but returns
|
|
11
|
+
* just the source workspace metadata — the caller decides whether to flip
|
|
12
|
+
* `update_available` (worker / check-version branch on the comparison).
|
|
13
|
+
*
|
|
14
|
+
* Safe for hot paths: synchronous, file-existence + JSON parse only,
|
|
15
|
+
* bounded walk (6 segments max), no network, no spawn.
|
|
16
|
+
*
|
|
17
|
+
* Security: workspace `package.json:version` is validated against the same
|
|
18
|
+
* `SEMVER_RE` as `update-chips.mjs` so a poisoned `package.json` cannot
|
|
19
|
+
* propagate a non-semver value into downstream envelopes.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
23
|
+
import { dirname, join } from 'node:path'
|
|
24
|
+
import { SEMVER_RE } from '../util/semver.mjs'
|
|
25
|
+
|
|
26
|
+
/** Max directory frames to walk before giving up. Mirrors route/drift-detect.mjs:32. */
|
|
27
|
+
const MAX_WALK_DEPTH = 6
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Walk up from `start`, returning every directory frame including `start`
|
|
31
|
+
* itself. Stops at the filesystem root or at depth `MAX_WALK_DEPTH`
|
|
32
|
+
* (whichever comes first).
|
|
33
|
+
*
|
|
34
|
+
* @param {string} start
|
|
35
|
+
* @returns {string[]}
|
|
36
|
+
*/
|
|
37
|
+
function walkUp(start) {
|
|
38
|
+
const frames = []
|
|
39
|
+
let cur = start
|
|
40
|
+
for (let i = 0; i < MAX_WALK_DEPTH; i++) {
|
|
41
|
+
frames.push(cur)
|
|
42
|
+
const parent = dirname(cur)
|
|
43
|
+
if (parent === cur) break
|
|
44
|
+
cur = parent
|
|
45
|
+
}
|
|
46
|
+
return frames
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Find the workspace `@aperant/framework` package above `targetDir`.
|
|
51
|
+
*
|
|
52
|
+
* @param {string} targetDir Absolute path to start the walk from.
|
|
53
|
+
* @returns {null | { source_path: string, source_version: string }}
|
|
54
|
+
* `source_path` is the absolute path to `packages/framework/package.json`.
|
|
55
|
+
* `source_version` is the semver-validated `version` field. Returns null
|
|
56
|
+
* when no workspace package is found, when the workspace package name is
|
|
57
|
+
* not `@aperant/framework`, or when the workspace version fails the
|
|
58
|
+
* semver-shape check.
|
|
59
|
+
*/
|
|
60
|
+
export function findWorkspaceFrameworkVersion(targetDir) {
|
|
61
|
+
if (!targetDir || typeof targetDir !== 'string') return null
|
|
62
|
+
|
|
63
|
+
for (const dir of walkUp(targetDir)) {
|
|
64
|
+
const candidate = join(dir, 'packages', 'framework', 'package.json')
|
|
65
|
+
if (!existsSync(candidate)) continue
|
|
66
|
+
|
|
67
|
+
let pkg
|
|
68
|
+
try {
|
|
69
|
+
pkg = JSON.parse(readFileSync(candidate, 'utf-8'))
|
|
70
|
+
} catch {
|
|
71
|
+
continue
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Guard against false matches on unrelated monorepos that happen to
|
|
75
|
+
// have a `packages/framework/` directory.
|
|
76
|
+
if (pkg?.name !== '@aperant/framework') continue
|
|
77
|
+
|
|
78
|
+
const sourceVersion = pkg?.version
|
|
79
|
+
if (typeof sourceVersion !== 'string' || !SEMVER_RE.test(sourceVersion)) continue
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
source_path: candidate,
|
|
83
|
+
source_version: sourceVersion,
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return null
|
|
88
|
+
}
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* install/install-from-source.mjs — Bundle D part 1.
|
|
3
|
+
*
|
|
4
|
+
* `apt-tools install-from-source <project-dir>` for the Aperant-contributor
|
|
5
|
+
* self-host case. Resolves the workspace `@aperant/framework` chain — packs
|
|
6
|
+
* the framework itself AND every `workspace:*` dependency (currently
|
|
7
|
+
* `@aperant/driver-sdk`, but the walker is general). Hands the full tarball
|
|
8
|
+
* list to a single `npm install --prefix` invocation so the same-PR
|
|
9
|
+
* unpublished-pair case stops blowing up with `EUNSUPPORTEDPROTOCOL`.
|
|
10
|
+
*
|
|
11
|
+
* Stable envelope:
|
|
12
|
+
* { status: 'ok', tarballs: string[], kernel_path: string }
|
|
13
|
+
* { status: 'error', error: string, ...optional context }
|
|
14
|
+
*
|
|
15
|
+
* Test seams: `APT_PNPM_BIN` + `APT_NPM_BIN` mirror the existing
|
|
16
|
+
* `install-kernel.mjs` + worker patterns.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { spawnSync } from 'node:child_process'
|
|
20
|
+
import { existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync } from 'node:fs'
|
|
21
|
+
import { tmpdir } from 'node:os'
|
|
22
|
+
import { dirname, isAbsolute, join, resolve } from 'node:path'
|
|
23
|
+
import { err, ok } from '../util/result.mjs'
|
|
24
|
+
import { findWorkspaceFrameworkVersion } from './find-workspace-framework-version.mjs'
|
|
25
|
+
import { KERNEL_BIN_REL_PATH } from './install-kernel.mjs'
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Locate the monorepo root given the workspace framework package.json path.
|
|
29
|
+
* `<root>/packages/framework/package.json` → `<root>`.
|
|
30
|
+
*
|
|
31
|
+
* @param {string} frameworkPkgPath
|
|
32
|
+
* @returns {string}
|
|
33
|
+
*/
|
|
34
|
+
function monorepoRootFrom(frameworkPkgPath) {
|
|
35
|
+
// `<root>/packages/framework/package.json` — go up THREE levels.
|
|
36
|
+
return dirname(dirname(dirname(frameworkPkgPath)))
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Scan `<monorepoRoot>/packages/` for a directory whose `package.json:name`
|
|
41
|
+
* matches `pkgName`. Returns the directory path (containing package.json),
|
|
42
|
+
* or null if no match.
|
|
43
|
+
*
|
|
44
|
+
* @param {string} monorepoRoot
|
|
45
|
+
* @param {string} pkgName
|
|
46
|
+
* @returns {string|null}
|
|
47
|
+
*/
|
|
48
|
+
function findWorkspacePackage(monorepoRoot, pkgName) {
|
|
49
|
+
const packagesDir = join(monorepoRoot, 'packages')
|
|
50
|
+
if (!existsSync(packagesDir)) return null
|
|
51
|
+
let entries
|
|
52
|
+
try {
|
|
53
|
+
entries = readdirSync(packagesDir, { withFileTypes: true })
|
|
54
|
+
} catch {
|
|
55
|
+
return null
|
|
56
|
+
}
|
|
57
|
+
for (const entry of entries) {
|
|
58
|
+
if (!entry.isDirectory()) continue
|
|
59
|
+
const candidate = join(packagesDir, entry.name, 'package.json')
|
|
60
|
+
if (!existsSync(candidate)) continue
|
|
61
|
+
try {
|
|
62
|
+
const pkg = JSON.parse(readFileSync(candidate, 'utf-8'))
|
|
63
|
+
if (pkg?.name === pkgName) return join(packagesDir, entry.name)
|
|
64
|
+
} catch {
|
|
65
|
+
// Skip unreadable package.json files.
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return null
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* pnpm-pack a workspace package into `tmpDir` and return the resulting
|
|
73
|
+
* tarball path. Uses `pnpm --filter <name> pack --pack-destination <tmp>`
|
|
74
|
+
* so workspace protocols resolve to concrete versions inside the tarball.
|
|
75
|
+
*
|
|
76
|
+
* `knownBefore` is the set of .tgz filenames already present in `tmpDir`
|
|
77
|
+
* before this call; the mtime fallback uses it to find only the NEW file,
|
|
78
|
+
* avoiding coarse-timestamp ambiguity when multiple packs land in the same
|
|
79
|
+
* directory.
|
|
80
|
+
*
|
|
81
|
+
* @param {string} monorepoRoot
|
|
82
|
+
* @param {string} pkgName
|
|
83
|
+
* @param {string} tmpDir
|
|
84
|
+
* @param {Set<string>} knownBefore Filenames (not paths) present before this pack.
|
|
85
|
+
* @returns {{ tarball: string } | { error: string }}
|
|
86
|
+
*/
|
|
87
|
+
function packWorkspace(monorepoRoot, pkgName, tmpDir, knownBefore) {
|
|
88
|
+
const pnpmBin = process.env.APT_PNPM_BIN || 'pnpm'
|
|
89
|
+
const result = spawnSync(pnpmBin, ['--filter', pkgName, 'pack', '--pack-destination', tmpDir], {
|
|
90
|
+
cwd: monorepoRoot,
|
|
91
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
92
|
+
encoding: 'utf-8',
|
|
93
|
+
timeout: 120_000,
|
|
94
|
+
})
|
|
95
|
+
if (result.error) {
|
|
96
|
+
return { error: `pnpm spawn failed for ${pkgName}: ${result.error.message}` }
|
|
97
|
+
}
|
|
98
|
+
if (result.status !== 0) {
|
|
99
|
+
const stderr = (result.stderr || '').trim().slice(-600)
|
|
100
|
+
return { error: `pnpm pack ${pkgName} exited with ${result.status}: ${stderr || 'no stderr'}` }
|
|
101
|
+
}
|
|
102
|
+
// pnpm pack prints the resulting tarball path on stdout. Take the last
|
|
103
|
+
// `.tgz` line (pnpm sometimes emits warnings above the path line).
|
|
104
|
+
const tarballLine = (result.stdout || '')
|
|
105
|
+
.split('\n')
|
|
106
|
+
.map((s) => s.trim())
|
|
107
|
+
.reverse()
|
|
108
|
+
.find((s) => s.endsWith('.tgz'))
|
|
109
|
+
if (tarballLine) {
|
|
110
|
+
// `tarballLine` may be a bare filename (older pnpm) or an absolute path.
|
|
111
|
+
// Use `isAbsolute` so Windows backslash + drive-letter paths are also
|
|
112
|
+
// recognized — `startsWith('/')` was POSIX-only.
|
|
113
|
+
const tarball = isAbsolute(tarballLine) ? tarballLine : join(tmpDir, tarballLine)
|
|
114
|
+
// SEC-001: containment check — tarball must resolve to a child of tmpDir.
|
|
115
|
+
const resolvedTmpDir = resolve(tmpDir)
|
|
116
|
+
const resolvedTarball = resolve(tarball)
|
|
117
|
+
if (!resolvedTarball.startsWith(`${resolvedTmpDir}/`) && resolvedTarball !== resolvedTmpDir) {
|
|
118
|
+
return { error: `pnpm pack ${pkgName}: tarball path escapes tmpDir: ${tarball}` }
|
|
119
|
+
}
|
|
120
|
+
return { tarball }
|
|
121
|
+
}
|
|
122
|
+
// Fall back to scanning tmpDir for the NEW tgz when stdout was
|
|
123
|
+
// uninformative (some pnpm builds print only progress to stderr).
|
|
124
|
+
// Use `knownBefore` to find only files added by this pack, avoiding
|
|
125
|
+
// mtime coarse-timestamp ambiguity on FAT32/network filesystems.
|
|
126
|
+
try {
|
|
127
|
+
for (const f of readdirSync(tmpDir)) {
|
|
128
|
+
if (!f.endsWith('.tgz')) continue
|
|
129
|
+
if (knownBefore.has(f)) continue
|
|
130
|
+
return { tarball: join(tmpDir, f) }
|
|
131
|
+
}
|
|
132
|
+
} catch {
|
|
133
|
+
// fall through
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
error: `pnpm pack ${pkgName}: no .tgz produced (stdout: ${result.stdout?.slice(-200)})`,
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* `apt-tools install-from-source <project-dir>` — pack the workspace
|
|
142
|
+
* framework + every `workspace:*` dep, then npm-install the full tarball
|
|
143
|
+
* list into `<project-dir>/.aperant/deps/`.
|
|
144
|
+
*
|
|
145
|
+
* @param {string} projectDir
|
|
146
|
+
* @returns {import('../util/result.mjs').CommandResult}
|
|
147
|
+
*/
|
|
148
|
+
export function cmdInstallFromSource(projectDir) {
|
|
149
|
+
if (!projectDir) {
|
|
150
|
+
return err('Usage: apt-tools install-from-source <project-dir>')
|
|
151
|
+
}
|
|
152
|
+
const targetDir = resolve(projectDir)
|
|
153
|
+
|
|
154
|
+
const workspace = findWorkspaceFrameworkVersion(targetDir)
|
|
155
|
+
if (!workspace) {
|
|
156
|
+
return err('install-from-source: no @aperant/framework workspace found above project-dir')
|
|
157
|
+
}
|
|
158
|
+
const monorepoRoot = monorepoRootFrom(workspace.source_path)
|
|
159
|
+
|
|
160
|
+
// QUA-001: re-read the workspace package.json with a try/catch — the file
|
|
161
|
+
// was validated by findWorkspaceFrameworkVersion but could change between
|
|
162
|
+
// the two reads (active monorepo, contributor machine).
|
|
163
|
+
let frameworkPkg
|
|
164
|
+
try {
|
|
165
|
+
frameworkPkg = JSON.parse(readFileSync(workspace.source_path, 'utf-8'))
|
|
166
|
+
} catch (e) {
|
|
167
|
+
return err(
|
|
168
|
+
`install-from-source: failed to read ${workspace.source_path}: ${e instanceof Error ? e.message : String(e)}`,
|
|
169
|
+
)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Enumerate workspace:* deps from dependencies + peerDependencies.
|
|
173
|
+
const depSources = [frameworkPkg.dependencies, frameworkPkg.peerDependencies]
|
|
174
|
+
const workspaceDeps = []
|
|
175
|
+
for (const block of depSources) {
|
|
176
|
+
if (!block || typeof block !== 'object') continue
|
|
177
|
+
for (const [name, spec] of Object.entries(block)) {
|
|
178
|
+
if (typeof spec === 'string' && spec.startsWith('workspace:')) {
|
|
179
|
+
if (!workspaceDeps.includes(name)) workspaceDeps.push(name)
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Resolve each workspace dep to its `packages/<dir>` path. Reject hard
|
|
185
|
+
// when any dep cannot be located — we cannot pack what we cannot find.
|
|
186
|
+
const missingDeps = []
|
|
187
|
+
const resolvedDeps = []
|
|
188
|
+
for (const depName of workspaceDeps) {
|
|
189
|
+
const pkgDir = findWorkspacePackage(monorepoRoot, depName)
|
|
190
|
+
if (!pkgDir) {
|
|
191
|
+
missingDeps.push(depName)
|
|
192
|
+
continue
|
|
193
|
+
}
|
|
194
|
+
resolvedDeps.push({ name: depName, dir: pkgDir })
|
|
195
|
+
}
|
|
196
|
+
if (missingDeps.length > 0) {
|
|
197
|
+
return err(
|
|
198
|
+
`install-from-source: workspace:* dep(s) not found in packages/: ${missingDeps.join(', ')}`,
|
|
199
|
+
)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// PRF-001: include framework in the same pack loop — eliminates structural
|
|
203
|
+
// duplication and ensures per-pack knownBefore snapshots work uniformly.
|
|
204
|
+
const packTargets = [...resolvedDeps.map((d) => d.name), '@aperant/framework']
|
|
205
|
+
|
|
206
|
+
// Pack everything into a fresh tmp dir so accumulated stale tarballs
|
|
207
|
+
// from prior runs don't sneak in.
|
|
208
|
+
const tmpDir = mkdtempSync(join(tmpdir(), 'apt-install-from-source-'))
|
|
209
|
+
const tarballs = []
|
|
210
|
+
|
|
211
|
+
// QUA-002: wrap pack+install in try/finally so tmpDir is always removed.
|
|
212
|
+
try {
|
|
213
|
+
for (const pkgName of packTargets) {
|
|
214
|
+
// LOG-003: snapshot existing tgz filenames before each pack so the
|
|
215
|
+
// fallback can identify exactly the new file, even on coarse-timestamp
|
|
216
|
+
// filesystems (FAT32, some network mounts).
|
|
217
|
+
const knownBefore = new Set()
|
|
218
|
+
try {
|
|
219
|
+
for (const f of readdirSync(tmpDir)) {
|
|
220
|
+
if (f.endsWith('.tgz')) knownBefore.add(f)
|
|
221
|
+
}
|
|
222
|
+
} catch {
|
|
223
|
+
// tmpDir unreadable — proceed with empty set; fallback will still work.
|
|
224
|
+
}
|
|
225
|
+
const packResult = packWorkspace(monorepoRoot, pkgName, tmpDir, knownBefore)
|
|
226
|
+
if ('error' in packResult) return err(packResult.error)
|
|
227
|
+
tarballs.push(packResult.tarball)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// npm install --prefix .aperant/deps <tarball1> <tarball2> ...
|
|
231
|
+
const depsDir = join(targetDir, '.aperant', 'deps')
|
|
232
|
+
mkdirSync(depsDir, { recursive: true })
|
|
233
|
+
|
|
234
|
+
const npmBin = process.env.APT_NPM_BIN || 'npm'
|
|
235
|
+
const args = [
|
|
236
|
+
'install',
|
|
237
|
+
'--prefix',
|
|
238
|
+
depsDir,
|
|
239
|
+
...tarballs,
|
|
240
|
+
'--no-save',
|
|
241
|
+
'--no-package-lock',
|
|
242
|
+
'--no-audit',
|
|
243
|
+
'--no-fund',
|
|
244
|
+
'--silent',
|
|
245
|
+
]
|
|
246
|
+
const npmResult = spawnSync(npmBin, args, {
|
|
247
|
+
cwd: targetDir,
|
|
248
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
249
|
+
encoding: 'utf-8',
|
|
250
|
+
timeout: 180_000,
|
|
251
|
+
})
|
|
252
|
+
if (npmResult.error) {
|
|
253
|
+
return err(`install-from-source: npm spawn failed: ${npmResult.error.message}`)
|
|
254
|
+
}
|
|
255
|
+
if (npmResult.status !== 0) {
|
|
256
|
+
const stderr = (npmResult.stderr || '').trim().slice(-600)
|
|
257
|
+
return err(
|
|
258
|
+
`install-from-source: npm install --prefix exited with ${npmResult.status}: ${stderr || 'no stderr'}`,
|
|
259
|
+
)
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const kernelPath = join(targetDir, KERNEL_BIN_REL_PATH)
|
|
263
|
+
if (!existsSync(kernelPath)) {
|
|
264
|
+
return err(`install-from-source: npm reported success but kernel not found at ${kernelPath}`)
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return ok({
|
|
268
|
+
status: 'ok',
|
|
269
|
+
command: 'install-from-source',
|
|
270
|
+
tarballs,
|
|
271
|
+
kernel_path: kernelPath,
|
|
272
|
+
})
|
|
273
|
+
} finally {
|
|
274
|
+
rmSync(tmpDir, { recursive: true, force: true })
|
|
275
|
+
}
|
|
276
|
+
}
|
|
@@ -120,6 +120,30 @@ export function installKernel(targetDir, version, opts = {}) {
|
|
|
120
120
|
const kernelPath = join(targetDir, KERNEL_BIN_REL_PATH)
|
|
121
121
|
const npmBin = resolveNpmBin()
|
|
122
122
|
|
|
123
|
+
// Bundle B — idempotency short-circuit. When the kernel package.json
|
|
124
|
+
// already reports the requested version AND the kernel binary exists,
|
|
125
|
+
// skip the npm spawn entirely. Returns the same `status: 'installed'`
|
|
126
|
+
// envelope shape that downstream consumers branch on (init.mjs:1441,
|
|
127
|
+
// init.mjs:1735, apt-update/SKILL.md Scenario D) — no new enum value
|
|
128
|
+
// per D-02.
|
|
129
|
+
const installedPkgPath = join(depsDir, 'node_modules', '@aperant', 'framework', 'package.json')
|
|
130
|
+
if (existsSync(installedPkgPath) && existsSync(kernelPath)) {
|
|
131
|
+
try {
|
|
132
|
+
const installedPkg = JSON.parse(readFileSync(installedPkgPath, 'utf-8'))
|
|
133
|
+
if (installedPkg?.version === version) {
|
|
134
|
+
return {
|
|
135
|
+
status: 'installed',
|
|
136
|
+
kernelPath,
|
|
137
|
+
reason: 'already at target version',
|
|
138
|
+
durationMs: 0,
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
} catch {
|
|
142
|
+
// Malformed package.json — fall through to the npm install path
|
|
143
|
+
// (npm will reconcile / repair).
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
123
147
|
const t0 = Date.now()
|
|
124
148
|
const args = [
|
|
125
149
|
'install',
|