@aperant/framework 0.10.0 → 0.12.0
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 +236 -1
- package/agents/apt-planner.md +15 -0
- package/agents/apt-verifier.md +15 -10
- package/bin/apt-proof-video.mjs +256 -76
- package/dist/cli/commands/apply-recipe.d.mts +6 -0
- package/dist/cli/commands/apply-recipe.d.mts.map +1 -0
- package/dist/cli/commands/apply-recipe.mjs +98 -0
- package/dist/cli/commands/apply-recipe.mjs.map +1 -0
- package/dist/cli/commands/catch-up.d.mts +9 -0
- package/dist/cli/commands/catch-up.d.mts.map +1 -0
- package/dist/cli/commands/catch-up.mjs +64 -0
- package/dist/cli/commands/catch-up.mjs.map +1 -0
- package/dist/cli/commands/mobile-prepare.d.mts +53 -0
- package/dist/cli/commands/mobile-prepare.d.mts.map +1 -0
- package/dist/cli/commands/mobile-prepare.mjs +139 -0
- package/dist/cli/commands/mobile-prepare.mjs.map +1 -0
- package/dist/cli/commands/pr-review.d.mts +16 -4
- package/dist/cli/commands/pr-review.d.mts.map +1 -1
- package/dist/cli/commands/pr-review.mjs +30 -11
- package/dist/cli/commands/pr-review.mjs.map +1 -1
- package/dist/cli/commands/recipe-diff.d.mts +6 -0
- package/dist/cli/commands/recipe-diff.d.mts.map +1 -0
- package/dist/cli/commands/recipe-diff.mjs +75 -0
- package/dist/cli/commands/recipe-diff.mjs.map +1 -0
- package/dist/cli/commands/roadmap.d.mts.map +1 -1
- package/dist/cli/commands/roadmap.mjs +53 -0
- package/dist/cli/commands/roadmap.mjs.map +1 -1
- package/dist/cli/commands/route.d.mts.map +1 -1
- package/dist/cli/commands/route.mjs +92 -2
- package/dist/cli/commands/route.mjs.map +1 -1
- package/dist/cli/commands/verify-sandbox.d.mts +6 -0
- package/dist/cli/commands/verify-sandbox.d.mts.map +1 -0
- package/dist/cli/commands/verify-sandbox.mjs +200 -0
- package/dist/cli/commands/verify-sandbox.mjs.map +1 -0
- package/dist/cli/config/share-policy.d.mts.map +1 -1
- package/dist/cli/config/share-policy.mjs +1 -0
- package/dist/cli/config/share-policy.mjs.map +1 -1
- package/dist/cli/config/upgrade-gitignore.d.mts.map +1 -1
- package/dist/cli/config/upgrade-gitignore.mjs +1 -0
- package/dist/cli/config/upgrade-gitignore.mjs.map +1 -1
- package/dist/cli/consistency/parse-qa.d.mts.map +1 -1
- package/dist/cli/consistency/parse-qa.mjs +18 -4
- package/dist/cli/consistency/parse-qa.mjs.map +1 -1
- package/dist/cli/consistency/rules/r5-verdict-consistency.d.mts.map +1 -1
- package/dist/cli/consistency/rules/r5-verdict-consistency.mjs +11 -10
- package/dist/cli/consistency/rules/r5-verdict-consistency.mjs.map +1 -1
- package/dist/cli/coordination/catch-up.d.mts +26 -0
- package/dist/cli/coordination/catch-up.d.mts.map +1 -0
- package/dist/cli/coordination/catch-up.mjs +239 -0
- package/dist/cli/coordination/catch-up.mjs.map +1 -0
- package/dist/cli/coordination/last-seen.d.mts +45 -0
- package/dist/cli/coordination/last-seen.d.mts.map +1 -0
- package/dist/cli/coordination/last-seen.mjs +128 -0
- package/dist/cli/coordination/last-seen.mjs.map +1 -0
- package/dist/cli/coordination/store.d.mts +15 -0
- package/dist/cli/coordination/store.d.mts.map +1 -1
- package/dist/cli/coordination/store.mjs +16 -0
- package/dist/cli/coordination/store.mjs.map +1 -1
- package/dist/cli/coverage-check/user-outcomes.d.mts +7 -2
- package/dist/cli/coverage-check/user-outcomes.d.mts.map +1 -1
- package/dist/cli/coverage-check/user-outcomes.mjs +50 -17
- package/dist/cli/coverage-check/user-outcomes.mjs.map +1 -1
- package/dist/cli/dispatch.d.mts.map +1 -1
- package/dist/cli/dispatch.mjs +30 -0
- package/dist/cli/dispatch.mjs.map +1 -1
- package/dist/cli/gate/gates/verify-approved.d.mts +0 -48
- package/dist/cli/gate/gates/verify-approved.d.mts.map +1 -1
- package/dist/cli/gate/gates/verify-approved.mjs +23 -80
- package/dist/cli/gate/gates/verify-approved.mjs.map +1 -1
- package/dist/cli/git/default-branch.d.mts +51 -0
- package/dist/cli/git/default-branch.d.mts.map +1 -0
- package/dist/cli/git/default-branch.mjs +234 -0
- package/dist/cli/git/default-branch.mjs.map +1 -0
- package/dist/cli/git/identity.d.mts +3 -5
- package/dist/cli/git/identity.d.mts.map +1 -1
- package/dist/cli/git/identity.mjs +10 -4
- package/dist/cli/git/identity.mjs.map +1 -1
- package/dist/cli/install/legacy-paths.d.mts.map +1 -1
- package/dist/cli/install/legacy-paths.mjs +2 -0
- package/dist/cli/install/legacy-paths.mjs.map +1 -1
- package/dist/cli/install/mcp-server-specs.d.mts +6 -5
- package/dist/cli/install/mcp-server-specs.d.mts.map +1 -1
- package/dist/cli/install/mcp-server-specs.mjs +22 -4
- package/dist/cli/install/mcp-server-specs.mjs.map +1 -1
- package/dist/cli/roadmap/backend-adapter.d.mts +43 -0
- package/dist/cli/roadmap/backend-adapter.d.mts.map +1 -0
- package/dist/cli/roadmap/backend-adapter.mjs +222 -0
- package/dist/cli/roadmap/backend-adapter.mjs.map +1 -0
- package/dist/cli/util/runtime-capabilities.d.mts +7 -0
- package/dist/cli/util/runtime-capabilities.d.mts.map +1 -1
- package/dist/cli/util/runtime-capabilities.mjs +65 -0
- package/dist/cli/util/runtime-capabilities.mjs.map +1 -1
- package/dist/cli/verify-proof/manifest-validator.mjs +13 -0
- package/dist/cli/verify-proof/manifest-validator.mjs.map +1 -1
- package/dist/cli/verify-proof/resolver.d.mts +10 -7
- package/dist/cli/verify-proof/resolver.d.mts.map +1 -1
- package/dist/cli/verify-proof/resolver.mjs +28 -7
- package/dist/cli/verify-proof/resolver.mjs.map +1 -1
- package/dist/cli/verify-proof/sandbox/apply.d.mts +64 -0
- package/dist/cli/verify-proof/sandbox/apply.d.mts.map +1 -0
- package/dist/cli/verify-proof/sandbox/apply.mjs +379 -0
- package/dist/cli/verify-proof/sandbox/apply.mjs.map +1 -0
- package/dist/cli/verify-proof/sandbox/gate-predicate.d.mts +63 -0
- package/dist/cli/verify-proof/sandbox/gate-predicate.d.mts.map +1 -0
- package/dist/cli/verify-proof/sandbox/gate-predicate.mjs +115 -0
- package/dist/cli/verify-proof/sandbox/gate-predicate.mjs.map +1 -0
- package/dist/cli/verify-proof/sandbox/pattern-matcher.d.mts +57 -0
- package/dist/cli/verify-proof/sandbox/pattern-matcher.d.mts.map +1 -0
- package/dist/cli/verify-proof/sandbox/pattern-matcher.mjs +124 -0
- package/dist/cli/verify-proof/sandbox/pattern-matcher.mjs.map +1 -0
- package/dist/cli/verify-proof/sandbox/recipe-diff.d.mts +28 -0
- package/dist/cli/verify-proof/sandbox/recipe-diff.d.mts.map +1 -0
- package/dist/cli/verify-proof/sandbox/recipe-diff.mjs +190 -0
- package/dist/cli/verify-proof/sandbox/recipe-diff.mjs.map +1 -0
- package/dist/cli/verify-proof/sandbox/sandbox-config.d.mts +25 -0
- package/dist/cli/verify-proof/sandbox/sandbox-config.d.mts.map +1 -0
- package/dist/cli/verify-proof/sandbox/sandbox-config.mjs +85 -0
- package/dist/cli/verify-proof/sandbox/sandbox-config.mjs.map +1 -0
- package/dist/cli/verify-proof/sandbox/self-test.d.mts +39 -0
- package/dist/cli/verify-proof/sandbox/self-test.d.mts.map +1 -0
- package/dist/cli/verify-proof/sandbox/self-test.mjs +248 -0
- package/dist/cli/verify-proof/sandbox/self-test.mjs.map +1 -0
- package/dist/plugin/.claude-plugin/plugin.json +2 -1
- package/dist/plugin/agents/apt-planner.md +15 -0
- package/dist/plugin/agents/apt-verifier.md +15 -10
- package/dist/plugin/skills/apt/SKILL.md +29 -0
- package/dist/plugin/skills/apt-catch-up/SKILL.md +79 -0
- package/dist/plugin/skills/apt-plan/SKILL.md +23 -0
- package/dist/plugin/skills/apt-pr-review/SKILL.md +5 -4
- package/dist/plugin/skills/apt-run/SKILL.md +1 -1
- package/dist/plugin/skills/apt-setup/SKILL.md +132 -0
- package/dist/plugin/skills/apt-verify/SKILL.md +20 -15
- package/dist/plugin/skills/apt-verify-proof/SKILL.md +146 -11
- package/dist/proof-report.d.ts.map +1 -1
- package/dist/proof-report.js +4 -1
- package/dist/proof-report.js.map +1 -1
- package/dist/types/config.d.ts +42 -4
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/qa-scoring.d.ts +45 -9
- package/dist/types/qa-scoring.d.ts.map +1 -1
- package/dist/types/qa-scoring.js +34 -13
- package/dist/types/qa-scoring.js.map +1 -1
- package/dist/types/state.d.ts +7 -2
- package/dist/types/state.d.ts.map +1 -1
- package/dist/types/task-record.d.ts +10 -2
- package/dist/types/task-record.d.ts.map +1 -1
- package/drivers/mobile/README.md +40 -0
- package/drivers/mobile/driver.mjs +106 -0
- package/drivers/mobile/manifest.json +49 -0
- package/package.json +1 -1
- package/prompts/inbox-clarification.md +11 -0
- package/prompts/inbox-triage.md +20 -0
- package/prompts/qa_orchestrator_agentic.md +18 -17
- package/prompts/qa_reviewer.md +17 -15
- package/skills/apt/SKILL.md +29 -0
- package/skills/apt-catch-up/SKILL.md +79 -0
- package/skills/apt-plan/SKILL.md +23 -0
- package/skills/apt-pr-review/SKILL.md +5 -4
- package/skills/apt-run/SKILL.md +1 -1
- package/skills/apt-setup/SKILL.md +132 -0
- package/skills/apt-verifier.md +7 -4
- package/skills/apt-verify/SKILL.md +20 -15
- package/skills/apt-verify-proof/SKILL.md +146 -11
- package/src/cli/commands/apply-recipe.mjs +105 -0
- package/src/cli/commands/catch-up.mjs +67 -0
- package/src/cli/commands/mobile-prepare.mjs +151 -0
- package/src/cli/commands/pr-review.mjs +32 -11
- package/src/cli/commands/recipe-diff.mjs +81 -0
- package/src/cli/commands/roadmap.mjs +54 -0
- package/src/cli/commands/route.mjs +92 -1
- package/src/cli/commands/verify-sandbox.mjs +231 -0
- package/src/cli/config/share-policy.mjs +1 -0
- package/src/cli/config/upgrade-gitignore.mjs +1 -0
- package/src/cli/consistency/parse-qa.mjs +20 -4
- package/src/cli/consistency/rules/r5-verdict-consistency.mjs +11 -13
- package/src/cli/coordination/catch-up.mjs +231 -0
- package/src/cli/coordination/last-seen.mjs +131 -0
- package/src/cli/coordination/store.mjs +18 -0
- package/src/cli/coverage-check/user-outcomes.mjs +52 -17
- package/src/cli/dispatch.mjs +29 -0
- package/src/cli/gate/gates/verify-approved.mjs +22 -81
- package/src/cli/git/default-branch.mjs +250 -0
- package/src/cli/git/identity.mjs +9 -3
- package/src/cli/install/legacy-paths.mjs +2 -0
- package/src/cli/install/mcp-server-specs.mjs +24 -4
- package/src/cli/roadmap/backend-adapter.mjs +231 -0
- package/src/cli/util/runtime-capabilities.mjs +67 -0
- package/src/cli/verify-proof/manifest-validator.mjs +15 -0
- package/src/cli/verify-proof/resolver.mjs +27 -7
- package/src/cli/verify-proof/sandbox/apply.mjs +401 -0
- package/src/cli/verify-proof/sandbox/gate-predicate.mjs +126 -0
- package/src/cli/verify-proof/sandbox/pattern-matcher.mjs +127 -0
- package/src/cli/verify-proof/sandbox/recipe-diff.mjs +208 -0
- package/src/cli/verify-proof/sandbox/sandbox-config.mjs +82 -0
- package/src/cli/verify-proof/sandbox/sandbox-schema.json +33 -0
- package/src/cli/verify-proof/sandbox/self-test.mjs +265 -0
- package/templates/config.json +21 -1
- package/templates/proof-verification.md +27 -8
- package/workflows/verify-proof.md +376 -302
|
@@ -32,7 +32,12 @@ import {
|
|
|
32
32
|
getTaskIsolationConfig,
|
|
33
33
|
getTrustedSkillSources,
|
|
34
34
|
loadMergedProjectConfig,
|
|
35
|
+
loadProjectConfig,
|
|
35
36
|
} from '../config/load.mjs'
|
|
37
|
+
import { resolveSharePolicy } from '../config/share-policy.mjs'
|
|
38
|
+
import { computeCatchUpPanel } from '../coordination/catch-up.mjs'
|
|
39
|
+
import { createCoordinationStore } from '../coordination/store.mjs'
|
|
40
|
+
import { resolveLocalDefaultBranch } from '../git/default-branch.mjs'
|
|
36
41
|
import '../gate/gates/index.mjs'
|
|
37
42
|
import * as gateRegistry from '../gate/registry.mjs'
|
|
38
43
|
import { detectHost, SUPPORTED_HOSTS } from '../host/detect.mjs'
|
|
@@ -96,6 +101,78 @@ function readUpdateCheckCache() {
|
|
|
96
101
|
return null
|
|
97
102
|
}
|
|
98
103
|
|
|
104
|
+
/**
|
|
105
|
+
* Is this an interactive (TTY) apt call? The catch-up panel + Last-Seen Marker
|
|
106
|
+
* are gated to interactive contexts (LD-02) — exactly like the statusline
|
|
107
|
+
* isTTY gate — so spawned subagents, fan-out workers, and non-TTY/piped calls
|
|
108
|
+
* are strict no-ops (they neither read nor write the marker). `APT_FORCE_TTY`
|
|
109
|
+
* forces the gate open for tests; `APT_CATCH_UP_DISABLE` forces it closed so a
|
|
110
|
+
* background spawner can opt every nested call out.
|
|
111
|
+
*/
|
|
112
|
+
function isInteractiveTty() {
|
|
113
|
+
if (process.env.APT_CATCH_UP_DISABLE === '1') return false
|
|
114
|
+
if (process.env.APT_FORCE_TTY === '1') return true
|
|
115
|
+
return Boolean(process.stdout?.isTTY || process.stderr?.isTTY)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Lazy-compute the catch-up panel for an interactive call and advance the
|
|
120
|
+
* Last-Seen Marker AFTER the panel is evaluated (R2/LD-02). Returns the panel
|
|
121
|
+
* (or null) to fold onto the route envelope as a top-level `catch_up` field.
|
|
122
|
+
*
|
|
123
|
+
* Marker-advance ordering (correctness-critical):
|
|
124
|
+
* - last_run_at = now — always (engagement, not greeting);
|
|
125
|
+
* - last_main_sha = defaultBranch.sha — only AFTER the digest was computed,
|
|
126
|
+
* i.e. only when we resolved a branch
|
|
127
|
+
* (even when the digest is empty);
|
|
128
|
+
* - last_active_task = welcome-back task id, when present.
|
|
129
|
+
*
|
|
130
|
+
* Non-TTY callers never reach this function (the caller gates on
|
|
131
|
+
* isInteractiveTty), so background/spawned calls never touch the marker.
|
|
132
|
+
*
|
|
133
|
+
* @param {string} targetDir
|
|
134
|
+
* @returns {object|null} the catch_up panel, or null.
|
|
135
|
+
*/
|
|
136
|
+
function computeCatchUpForRoute(targetDir) {
|
|
137
|
+
// Setup mirrors cmdCatchUp in commands/catch-up.mjs (recompute-only variant).
|
|
138
|
+
const config = loadProjectConfig(targetDir)
|
|
139
|
+
if (!config) return null
|
|
140
|
+
|
|
141
|
+
const store = createCoordinationStore(targetDir)
|
|
142
|
+
const marker = store.readLastSeen()
|
|
143
|
+
const td = config?.collaboration?.team_digest
|
|
144
|
+
const vis = resolveSharePolicy(config).visibility
|
|
145
|
+
const needsDigest = td?.enabled !== false && (vis === 'team' || vis === 'oss')
|
|
146
|
+
const defaultBranch = needsDigest ? resolveLocalDefaultBranch(targetDir) : null
|
|
147
|
+
const now = Date.now()
|
|
148
|
+
|
|
149
|
+
let panel = null
|
|
150
|
+
try {
|
|
151
|
+
panel = computeCatchUpPanel(targetDir, {
|
|
152
|
+
config,
|
|
153
|
+
now,
|
|
154
|
+
marker,
|
|
155
|
+
defaultBranch,
|
|
156
|
+
})
|
|
157
|
+
} catch (e) {
|
|
158
|
+
console.warn('[apt] catch-up:', e?.message ?? e)
|
|
159
|
+
panel = null
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Advance the marker AFTER panel eval. last_main_sha only when we actually
|
|
163
|
+
// resolved a default branch (the digest was computed, possibly empty).
|
|
164
|
+
const advance = { last_run_at: new Date(now).toISOString() }
|
|
165
|
+
if (defaultBranch?.sha) advance.last_main_sha = defaultBranch.sha
|
|
166
|
+
if (panel?.welcome_back?.task_id) advance.last_active_task = panel.welcome_back.task_id
|
|
167
|
+
try {
|
|
168
|
+
store.advanceLastSeen(advance)
|
|
169
|
+
} catch {
|
|
170
|
+
/* best-effort — never block routing on a marker write */
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return panel
|
|
174
|
+
}
|
|
175
|
+
|
|
99
176
|
/**
|
|
100
177
|
* Deterministic mapping from an explicit track flag (--quick/--deep/--debug)
|
|
101
178
|
* to its canonical {track, primary_skill} pair. Flag overrides are user
|
|
@@ -309,6 +386,13 @@ export function cmdRoute(projectDir, extraArgs) {
|
|
|
309
386
|
}
|
|
310
387
|
}
|
|
311
388
|
|
|
389
|
+
// Catch-up panel (LD-11): lazy-compute on interactive (TTY) calls only, and
|
|
390
|
+
// only when config exists (init has no config yet). The compute also
|
|
391
|
+
// advances the Last-Seen Marker after panel eval (LD-02). Non-TTY calls are
|
|
392
|
+
// strict no-ops — no read, no write, no field. Folded onto every returned
|
|
393
|
+
// envelope below as a top-level `catch_up` field, mirroring update_check.
|
|
394
|
+
const catch_up = hasConfig && isInteractiveTty() ? computeCatchUpForRoute(targetDir) : null
|
|
395
|
+
|
|
312
396
|
// ---------------- Deterministic branches (zero-behavior-change) ----------------
|
|
313
397
|
// These fire regardless of host detection status — missing host is not
|
|
314
398
|
// fatal for init/status/help/skill-passthrough.
|
|
@@ -322,6 +406,7 @@ export function cmdRoute(projectDir, extraArgs) {
|
|
|
322
406
|
host_capabilities,
|
|
323
407
|
task_isolation,
|
|
324
408
|
update_check,
|
|
409
|
+
catch_up: null,
|
|
325
410
|
reason: 'No .aperant/config.json found — first-time setup required',
|
|
326
411
|
})
|
|
327
412
|
}
|
|
@@ -335,6 +420,7 @@ export function cmdRoute(projectDir, extraArgs) {
|
|
|
335
420
|
host_capabilities,
|
|
336
421
|
task_isolation,
|
|
337
422
|
update_check,
|
|
423
|
+
catch_up,
|
|
338
424
|
reason: 'No input provided — showing project status',
|
|
339
425
|
})
|
|
340
426
|
}
|
|
@@ -348,6 +434,7 @@ export function cmdRoute(projectDir, extraArgs) {
|
|
|
348
434
|
host_capabilities,
|
|
349
435
|
task_isolation,
|
|
350
436
|
update_check,
|
|
437
|
+
catch_up,
|
|
351
438
|
reason: 'Help requested',
|
|
352
439
|
})
|
|
353
440
|
}
|
|
@@ -395,6 +482,7 @@ export function cmdRoute(projectDir, extraArgs) {
|
|
|
395
482
|
host_capabilities,
|
|
396
483
|
task_isolation,
|
|
397
484
|
update_check,
|
|
485
|
+
catch_up,
|
|
398
486
|
...(gate_preview ? { gate_preview } : {}),
|
|
399
487
|
reason: `Direct skill invocation: apt:${firstWord}`,
|
|
400
488
|
})
|
|
@@ -444,6 +532,7 @@ export function cmdRoute(projectDir, extraArgs) {
|
|
|
444
532
|
host_capabilities,
|
|
445
533
|
task_isolation,
|
|
446
534
|
update_check,
|
|
535
|
+
catch_up,
|
|
447
536
|
...(gate_preview ? { gate_preview } : {}),
|
|
448
537
|
...(spar_inject_gates ? { spar_inject_gates } : {}),
|
|
449
538
|
classification: {
|
|
@@ -523,7 +612,9 @@ export function cmdRoute(projectDir, extraArgs) {
|
|
|
523
612
|
)
|
|
524
613
|
}
|
|
525
614
|
|
|
526
|
-
|
|
615
|
+
// Fold the catch-up panel onto the classify envelope as a top-level field,
|
|
616
|
+
// mirroring update_check (LD-11). null when non-TTY / neither half fires.
|
|
617
|
+
return ok({ ...envelope, catch_up })
|
|
527
618
|
}
|
|
528
619
|
|
|
529
620
|
/**
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* commands/verify-sandbox.mjs — `apt-tools verify-sandbox <projectDir>`.
|
|
3
|
+
*
|
|
4
|
+
* Drives runSelfTest() then atomically writes
|
|
5
|
+
* .aperant/state/verify-proof-sandbox-verified.json with the v2-shaped
|
|
6
|
+
* verdict envelope. On win32, short-circuits to a platform_unsupported
|
|
7
|
+
* verdict WITHOUT spawning child processes (ID-06 v1 supported floor =
|
|
8
|
+
* darwin + linux).
|
|
9
|
+
*
|
|
10
|
+
* The state file is written for BOTH verified-true and verified-false
|
|
11
|
+
* outcomes (ID-02 — no silent state mutation). The sandbox gate at
|
|
12
|
+
* apt-verify-proof/SKILL.md Step 2c (subtask 5.1) reads this file.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { join, resolve } from 'node:path'
|
|
16
|
+
import { atomicWriteJson } from '../util/atomic-write.mjs'
|
|
17
|
+
import { withFileLock } from '../util/fs-lock.mjs'
|
|
18
|
+
import { err, exitWith, ok } from '../util/result.mjs'
|
|
19
|
+
import { loadRuntimeCapabilities } from '../util/runtime-capabilities.mjs'
|
|
20
|
+
import { matchPatternForProject } from '../verify-proof/sandbox/pattern-matcher.mjs'
|
|
21
|
+
import {
|
|
22
|
+
discoverBundledRecipes,
|
|
23
|
+
readSandboxConfig,
|
|
24
|
+
} from '../verify-proof/sandbox/sandbox-config.mjs'
|
|
25
|
+
import { runSelfTest } from '../verify-proof/sandbox/self-test.mjs'
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @param {string} projectDir
|
|
29
|
+
* @param {string[]} [extra]
|
|
30
|
+
*/
|
|
31
|
+
export async function cmdVerifySandbox(projectDir, _extra = []) {
|
|
32
|
+
if (!projectDir) return err('Usage: apt-tools verify-sandbox <project-dir>')
|
|
33
|
+
|
|
34
|
+
const target = resolve(projectDir)
|
|
35
|
+
const platform = process.platform
|
|
36
|
+
const statePath = join(target, '.aperant', 'state', 'verify-proof-sandbox-verified.json')
|
|
37
|
+
const at = new Date().toISOString()
|
|
38
|
+
|
|
39
|
+
const host = {
|
|
40
|
+
platform,
|
|
41
|
+
arch: process.arch,
|
|
42
|
+
nodeVersion: process.version,
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Resolve recipeId from config or runtime detection.
|
|
46
|
+
const sandboxCfg = readSandboxConfig(target)
|
|
47
|
+
const recipes = discoverBundledRecipes()
|
|
48
|
+
let recipeId = sandboxCfg?.recipeId ?? null
|
|
49
|
+
let recipeVersion = sandboxCfg?.recipeVersion ?? null
|
|
50
|
+
if (!recipeId) {
|
|
51
|
+
const runtime = loadRuntimeCapabilities(target)
|
|
52
|
+
const match = matchPatternForProject(target, runtime, recipes)
|
|
53
|
+
recipeId = match.recipeId
|
|
54
|
+
}
|
|
55
|
+
// Always populate recipeVersion from bundled recipes when absent — covers
|
|
56
|
+
// the case where config pins recipeId but omits recipeVersion (LOG-003).
|
|
57
|
+
if (recipeId && !recipeVersion) {
|
|
58
|
+
recipeVersion = recipes.find((r) => r.recipeId === recipeId)?.recipeVersion ?? null
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// win32 short-circuit (AC12, ID-06 v1 floor).
|
|
62
|
+
if (platform === 'win32') {
|
|
63
|
+
const envelope = {
|
|
64
|
+
verified: false,
|
|
65
|
+
verdict: 'needs_human',
|
|
66
|
+
reason: 'platform_unsupported',
|
|
67
|
+
recipeId,
|
|
68
|
+
recipeVersion,
|
|
69
|
+
verifiedAt: at,
|
|
70
|
+
host,
|
|
71
|
+
}
|
|
72
|
+
const writeErr = writeStateFile(statePath, envelope)
|
|
73
|
+
if (writeErr) {
|
|
74
|
+
return exitWith(
|
|
75
|
+
{
|
|
76
|
+
status: 'error',
|
|
77
|
+
command: 'verify-sandbox',
|
|
78
|
+
code: 'E_STATE_WRITE_FAILED',
|
|
79
|
+
verdict: 'needs_human',
|
|
80
|
+
reason: 'state_write_failed',
|
|
81
|
+
cause: writeErr.message,
|
|
82
|
+
project_dir: target,
|
|
83
|
+
state_path: statePath,
|
|
84
|
+
},
|
|
85
|
+
1,
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
return ok({
|
|
89
|
+
status: 'ok',
|
|
90
|
+
command: 'verify-sandbox',
|
|
91
|
+
project_dir: target,
|
|
92
|
+
state_path: statePath,
|
|
93
|
+
...envelope,
|
|
94
|
+
})
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (!recipeId) {
|
|
98
|
+
const envelope = {
|
|
99
|
+
verified: false,
|
|
100
|
+
verdict: 'needs_human',
|
|
101
|
+
reason: 'no_recipe_available',
|
|
102
|
+
recipeId: null,
|
|
103
|
+
recipeVersion: null,
|
|
104
|
+
verifiedAt: at,
|
|
105
|
+
host,
|
|
106
|
+
}
|
|
107
|
+
const writeErr = writeStateFile(statePath, envelope)
|
|
108
|
+
if (writeErr) {
|
|
109
|
+
return exitWith(
|
|
110
|
+
{
|
|
111
|
+
status: 'error',
|
|
112
|
+
command: 'verify-sandbox',
|
|
113
|
+
code: 'E_STATE_WRITE_FAILED',
|
|
114
|
+
verdict: 'needs_human',
|
|
115
|
+
reason: 'state_write_failed',
|
|
116
|
+
cause: writeErr.message,
|
|
117
|
+
project_dir: target,
|
|
118
|
+
state_path: statePath,
|
|
119
|
+
},
|
|
120
|
+
1,
|
|
121
|
+
)
|
|
122
|
+
}
|
|
123
|
+
return exitWith(
|
|
124
|
+
{
|
|
125
|
+
status: 'error',
|
|
126
|
+
command: 'verify-sandbox',
|
|
127
|
+
code: 'E_NO_RECIPE',
|
|
128
|
+
project_dir: target,
|
|
129
|
+
state_path: statePath,
|
|
130
|
+
...envelope,
|
|
131
|
+
},
|
|
132
|
+
1,
|
|
133
|
+
)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
let result
|
|
137
|
+
try {
|
|
138
|
+
result = await runSelfTest({ projectDir: target, recipeId, recipes })
|
|
139
|
+
} catch (e) {
|
|
140
|
+
const envelope = {
|
|
141
|
+
verified: false,
|
|
142
|
+
verdict: 'needs_human',
|
|
143
|
+
reason: 'self_test_failed_app_init_timeout',
|
|
144
|
+
recipeId,
|
|
145
|
+
recipeVersion,
|
|
146
|
+
verifiedAt: at,
|
|
147
|
+
host,
|
|
148
|
+
error: e?.message ?? String(e),
|
|
149
|
+
}
|
|
150
|
+
const writeErr = writeStateFile(statePath, envelope)
|
|
151
|
+
if (writeErr) {
|
|
152
|
+
return exitWith(
|
|
153
|
+
{
|
|
154
|
+
status: 'error',
|
|
155
|
+
command: 'verify-sandbox',
|
|
156
|
+
code: 'E_STATE_WRITE_FAILED',
|
|
157
|
+
verdict: 'needs_human',
|
|
158
|
+
reason: 'state_write_failed',
|
|
159
|
+
cause: writeErr.message,
|
|
160
|
+
project_dir: target,
|
|
161
|
+
state_path: statePath,
|
|
162
|
+
},
|
|
163
|
+
1,
|
|
164
|
+
)
|
|
165
|
+
}
|
|
166
|
+
return exitWith(
|
|
167
|
+
{
|
|
168
|
+
status: 'error',
|
|
169
|
+
command: 'verify-sandbox',
|
|
170
|
+
project_dir: target,
|
|
171
|
+
state_path: statePath,
|
|
172
|
+
...envelope,
|
|
173
|
+
},
|
|
174
|
+
1,
|
|
175
|
+
)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const envelope = {
|
|
179
|
+
verified: result.verified,
|
|
180
|
+
verdict: result.verdict,
|
|
181
|
+
reason: result.reason,
|
|
182
|
+
recipeId,
|
|
183
|
+
recipeVersion,
|
|
184
|
+
verifiedAt: at,
|
|
185
|
+
host,
|
|
186
|
+
durationMs: result.durationMs,
|
|
187
|
+
}
|
|
188
|
+
const writeErr = writeStateFile(statePath, envelope)
|
|
189
|
+
if (writeErr) {
|
|
190
|
+
return exitWith(
|
|
191
|
+
{
|
|
192
|
+
status: 'error',
|
|
193
|
+
command: 'verify-sandbox',
|
|
194
|
+
code: 'E_STATE_WRITE_FAILED',
|
|
195
|
+
verdict: 'needs_human',
|
|
196
|
+
reason: 'state_write_failed',
|
|
197
|
+
cause: writeErr.message,
|
|
198
|
+
project_dir: target,
|
|
199
|
+
state_path: statePath,
|
|
200
|
+
},
|
|
201
|
+
1,
|
|
202
|
+
)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const exitCode = result.verified ? 0 : 1
|
|
206
|
+
return exitWith(
|
|
207
|
+
{
|
|
208
|
+
status: result.verified ? 'ok' : 'error',
|
|
209
|
+
command: 'verify-sandbox',
|
|
210
|
+
project_dir: target,
|
|
211
|
+
state_path: statePath,
|
|
212
|
+
...envelope,
|
|
213
|
+
},
|
|
214
|
+
exitCode,
|
|
215
|
+
)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Write the verdict envelope to the state file under a file lock.
|
|
220
|
+
* Returns null on success, or an Error if the lock or write fails.
|
|
221
|
+
*/
|
|
222
|
+
function writeStateFile(statePath, envelope) {
|
|
223
|
+
try {
|
|
224
|
+
withFileLock(statePath, () => {
|
|
225
|
+
atomicWriteJson(statePath, envelope)
|
|
226
|
+
})
|
|
227
|
+
return null
|
|
228
|
+
} catch (e) {
|
|
229
|
+
return e instanceof Error ? e : new Error(String(e))
|
|
230
|
+
}
|
|
231
|
+
}
|
|
@@ -254,6 +254,7 @@ export function buildPolicyIgnoreMatcher(policy) {
|
|
|
254
254
|
rest.startsWith('events/') ||
|
|
255
255
|
rest.startsWith('locks/') ||
|
|
256
256
|
rest.startsWith('team-status/') ||
|
|
257
|
+
rest.startsWith('last-seen/') ||
|
|
257
258
|
rest.startsWith('paused/') ||
|
|
258
259
|
rest.startsWith('rationalizations/') ||
|
|
259
260
|
rest === 'digests/.last-run.json' ||
|
|
@@ -189,6 +189,7 @@ export function buildAperantGitignoreTemplate(policy = DEFAULT_SHARE_POLICY) {
|
|
|
189
189
|
// maintainer cannot accidentally un-ignore it with a broad `!config*.json`
|
|
190
190
|
// allow rule.
|
|
191
191
|
lines.push('config.local.json')
|
|
192
|
+
lines.push('last-seen/')
|
|
192
193
|
lines.push('')
|
|
193
194
|
lines.push('# ── Derived markdown indexes (regenerated from JSON) ──────────────')
|
|
194
195
|
lines.push('tasks/INDEX.md')
|
|
@@ -7,13 +7,17 @@
|
|
|
7
7
|
* - flat findings: findings: [{severity}]
|
|
8
8
|
* - legacy `issues: [{severity}]`
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
10
|
+
* v0.9.0 (verify-proof-v2-outcome-walker_27-05-26) — verdict closed set is
|
|
11
|
+
* `{approved, rejected, needs_human, needs-changes}`. `approved_with_notes`
|
|
12
|
+
* is DELETED (no shim) — reading a legacy `approved_with_notes` /
|
|
13
|
+
* `approved-with-notes` value throws a clear migration error pointing at
|
|
14
|
+
* the framework CHANGELOG entry. Anything else maps to `'unknown'`.
|
|
15
|
+
* Accepts legacy `status` as a fallback field name.
|
|
12
16
|
*
|
|
13
17
|
* Output: {verdict, findings: [{severity, title, status, file}]}
|
|
14
18
|
*/
|
|
15
19
|
|
|
16
|
-
const KNOWN_VERDICTS = new Set(['approved', '
|
|
20
|
+
const KNOWN_VERDICTS = new Set(['approved', 'rejected', 'needs-human', 'needs-changes'])
|
|
17
21
|
|
|
18
22
|
const SEVERITY_MAP = {
|
|
19
23
|
critical: 'critical',
|
|
@@ -56,7 +60,19 @@ function normalizeVerdict(v) {
|
|
|
56
60
|
const k = v.toLowerCase().trim()
|
|
57
61
|
// normalize common spellings
|
|
58
62
|
const canonical = k.replace(/_/g, '-')
|
|
59
|
-
|
|
63
|
+
// v0.9.0 migration: approved_with_notes / approved-with-notes is
|
|
64
|
+
// DELETED with no shim. Crash loudly so the consumer task is
|
|
65
|
+
// re-run, not silently re-mapped to `approved`.
|
|
66
|
+
if (canonical === 'approved-with-notes') {
|
|
67
|
+
throw new Error(
|
|
68
|
+
"qa_signoff.json carries the legacy verdict 'approved_with_notes' (or 'approved-with-notes'). " +
|
|
69
|
+
"This verdict is removed in framework v0.9.0+ (see packages/framework/CHANGELOG.md :: 'verify-proof v2 — outcome walker + two-gate verdict'). " +
|
|
70
|
+
'Re-run verify-proof on this task to mint a fresh qa_signoff.json with the new {approved, rejected, needs_human} enum.',
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
// Accept both snake_case and kebab-case for needs_human.
|
|
74
|
+
const needsHuman = canonical === 'needs-human' ? 'needs-human' : canonical
|
|
75
|
+
return KNOWN_VERDICTS.has(needsHuman) ? needsHuman : 'unknown'
|
|
60
76
|
}
|
|
61
77
|
|
|
62
78
|
function collectRaw(qa) {
|
|
@@ -3,13 +3,15 @@
|
|
|
3
3
|
* consistency/rules/r5-verdict-consistency.mjs — R5 (C15).
|
|
4
4
|
*
|
|
5
5
|
* Fails when:
|
|
6
|
-
* - qa.verdict === 'approved'
|
|
7
|
-
*
|
|
8
|
-
* - qa.verdict === 'rejected' AND qa has no findings at all AND review
|
|
9
|
-
* zero unresolved findings (rejected-without-evidence)
|
|
6
|
+
* - qa.verdict === 'approved' AND review has >=1 unresolved critical or
|
|
7
|
+
* major finding.
|
|
8
|
+
* - qa.verdict === 'rejected' AND qa has no findings at all AND review
|
|
9
|
+
* has zero unresolved findings (rejected-without-evidence).
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
11
|
+
* v0.9.0 (verify-proof-v2-outcome-walker_27-05-26) — the legacy
|
|
12
|
+
* `approved-with-notes` branch is REMOVED. The verdict enum is the closed
|
|
13
|
+
* set `{approved, rejected, needs_human}`. Reading a legacy verdict
|
|
14
|
+
* crashes in parseQaSignoff before this rule executes.
|
|
13
15
|
*
|
|
14
16
|
* Severity: error. Stages: verify, ship.
|
|
15
17
|
*/
|
|
@@ -30,10 +32,7 @@ const rule = {
|
|
|
30
32
|
const unresolvedBlocking = reviewFindings.filter(
|
|
31
33
|
(f) => (f.severity === 'critical' || f.severity === 'major') && f.status !== 'fixed',
|
|
32
34
|
)
|
|
33
|
-
if (
|
|
34
|
-
(verdict === 'approved' || verdict === 'approved-with-notes') &&
|
|
35
|
-
unresolvedBlocking.length > 0
|
|
36
|
-
) {
|
|
35
|
+
if (verdict === 'approved' && unresolvedBlocking.length > 0) {
|
|
37
36
|
return {
|
|
38
37
|
status: 'fail',
|
|
39
38
|
drift: [
|
|
@@ -43,11 +42,10 @@ const rule = {
|
|
|
43
42
|
id: f.id,
|
|
44
43
|
title: f.title,
|
|
45
44
|
})),
|
|
46
|
-
reason:
|
|
47
|
-
'approved/approved-with-notes verdict with unresolved critical or major finding(s) (G42 Phase 1)',
|
|
45
|
+
reason: 'approved verdict with unresolved critical or major finding(s) (G42 Phase 1)',
|
|
48
46
|
},
|
|
49
47
|
],
|
|
50
|
-
reason: `approved
|
|
48
|
+
reason: `approved verdict contradicts ${unresolvedBlocking.length} unresolved critical/major finding(s)`,
|
|
51
49
|
}
|
|
52
50
|
}
|
|
53
51
|
if (verdict === 'rejected') {
|