@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.
Files changed (199) hide show
  1. package/CHANGELOG.md +236 -1
  2. package/agents/apt-planner.md +15 -0
  3. package/agents/apt-verifier.md +15 -10
  4. package/bin/apt-proof-video.mjs +256 -76
  5. package/dist/cli/commands/apply-recipe.d.mts +6 -0
  6. package/dist/cli/commands/apply-recipe.d.mts.map +1 -0
  7. package/dist/cli/commands/apply-recipe.mjs +98 -0
  8. package/dist/cli/commands/apply-recipe.mjs.map +1 -0
  9. package/dist/cli/commands/catch-up.d.mts +9 -0
  10. package/dist/cli/commands/catch-up.d.mts.map +1 -0
  11. package/dist/cli/commands/catch-up.mjs +64 -0
  12. package/dist/cli/commands/catch-up.mjs.map +1 -0
  13. package/dist/cli/commands/mobile-prepare.d.mts +53 -0
  14. package/dist/cli/commands/mobile-prepare.d.mts.map +1 -0
  15. package/dist/cli/commands/mobile-prepare.mjs +139 -0
  16. package/dist/cli/commands/mobile-prepare.mjs.map +1 -0
  17. package/dist/cli/commands/pr-review.d.mts +16 -4
  18. package/dist/cli/commands/pr-review.d.mts.map +1 -1
  19. package/dist/cli/commands/pr-review.mjs +30 -11
  20. package/dist/cli/commands/pr-review.mjs.map +1 -1
  21. package/dist/cli/commands/recipe-diff.d.mts +6 -0
  22. package/dist/cli/commands/recipe-diff.d.mts.map +1 -0
  23. package/dist/cli/commands/recipe-diff.mjs +75 -0
  24. package/dist/cli/commands/recipe-diff.mjs.map +1 -0
  25. package/dist/cli/commands/roadmap.d.mts.map +1 -1
  26. package/dist/cli/commands/roadmap.mjs +53 -0
  27. package/dist/cli/commands/roadmap.mjs.map +1 -1
  28. package/dist/cli/commands/route.d.mts.map +1 -1
  29. package/dist/cli/commands/route.mjs +92 -2
  30. package/dist/cli/commands/route.mjs.map +1 -1
  31. package/dist/cli/commands/verify-sandbox.d.mts +6 -0
  32. package/dist/cli/commands/verify-sandbox.d.mts.map +1 -0
  33. package/dist/cli/commands/verify-sandbox.mjs +200 -0
  34. package/dist/cli/commands/verify-sandbox.mjs.map +1 -0
  35. package/dist/cli/config/share-policy.d.mts.map +1 -1
  36. package/dist/cli/config/share-policy.mjs +1 -0
  37. package/dist/cli/config/share-policy.mjs.map +1 -1
  38. package/dist/cli/config/upgrade-gitignore.d.mts.map +1 -1
  39. package/dist/cli/config/upgrade-gitignore.mjs +1 -0
  40. package/dist/cli/config/upgrade-gitignore.mjs.map +1 -1
  41. package/dist/cli/consistency/parse-qa.d.mts.map +1 -1
  42. package/dist/cli/consistency/parse-qa.mjs +18 -4
  43. package/dist/cli/consistency/parse-qa.mjs.map +1 -1
  44. package/dist/cli/consistency/rules/r5-verdict-consistency.d.mts.map +1 -1
  45. package/dist/cli/consistency/rules/r5-verdict-consistency.mjs +11 -10
  46. package/dist/cli/consistency/rules/r5-verdict-consistency.mjs.map +1 -1
  47. package/dist/cli/coordination/catch-up.d.mts +26 -0
  48. package/dist/cli/coordination/catch-up.d.mts.map +1 -0
  49. package/dist/cli/coordination/catch-up.mjs +239 -0
  50. package/dist/cli/coordination/catch-up.mjs.map +1 -0
  51. package/dist/cli/coordination/last-seen.d.mts +45 -0
  52. package/dist/cli/coordination/last-seen.d.mts.map +1 -0
  53. package/dist/cli/coordination/last-seen.mjs +128 -0
  54. package/dist/cli/coordination/last-seen.mjs.map +1 -0
  55. package/dist/cli/coordination/store.d.mts +15 -0
  56. package/dist/cli/coordination/store.d.mts.map +1 -1
  57. package/dist/cli/coordination/store.mjs +16 -0
  58. package/dist/cli/coordination/store.mjs.map +1 -1
  59. package/dist/cli/coverage-check/user-outcomes.d.mts +7 -2
  60. package/dist/cli/coverage-check/user-outcomes.d.mts.map +1 -1
  61. package/dist/cli/coverage-check/user-outcomes.mjs +50 -17
  62. package/dist/cli/coverage-check/user-outcomes.mjs.map +1 -1
  63. package/dist/cli/dispatch.d.mts.map +1 -1
  64. package/dist/cli/dispatch.mjs +30 -0
  65. package/dist/cli/dispatch.mjs.map +1 -1
  66. package/dist/cli/gate/gates/verify-approved.d.mts +0 -48
  67. package/dist/cli/gate/gates/verify-approved.d.mts.map +1 -1
  68. package/dist/cli/gate/gates/verify-approved.mjs +23 -80
  69. package/dist/cli/gate/gates/verify-approved.mjs.map +1 -1
  70. package/dist/cli/git/default-branch.d.mts +51 -0
  71. package/dist/cli/git/default-branch.d.mts.map +1 -0
  72. package/dist/cli/git/default-branch.mjs +234 -0
  73. package/dist/cli/git/default-branch.mjs.map +1 -0
  74. package/dist/cli/git/identity.d.mts +3 -5
  75. package/dist/cli/git/identity.d.mts.map +1 -1
  76. package/dist/cli/git/identity.mjs +10 -4
  77. package/dist/cli/git/identity.mjs.map +1 -1
  78. package/dist/cli/install/legacy-paths.d.mts.map +1 -1
  79. package/dist/cli/install/legacy-paths.mjs +2 -0
  80. package/dist/cli/install/legacy-paths.mjs.map +1 -1
  81. package/dist/cli/install/mcp-server-specs.d.mts +6 -5
  82. package/dist/cli/install/mcp-server-specs.d.mts.map +1 -1
  83. package/dist/cli/install/mcp-server-specs.mjs +22 -4
  84. package/dist/cli/install/mcp-server-specs.mjs.map +1 -1
  85. package/dist/cli/roadmap/backend-adapter.d.mts +43 -0
  86. package/dist/cli/roadmap/backend-adapter.d.mts.map +1 -0
  87. package/dist/cli/roadmap/backend-adapter.mjs +222 -0
  88. package/dist/cli/roadmap/backend-adapter.mjs.map +1 -0
  89. package/dist/cli/util/runtime-capabilities.d.mts +7 -0
  90. package/dist/cli/util/runtime-capabilities.d.mts.map +1 -1
  91. package/dist/cli/util/runtime-capabilities.mjs +65 -0
  92. package/dist/cli/util/runtime-capabilities.mjs.map +1 -1
  93. package/dist/cli/verify-proof/manifest-validator.mjs +13 -0
  94. package/dist/cli/verify-proof/manifest-validator.mjs.map +1 -1
  95. package/dist/cli/verify-proof/resolver.d.mts +10 -7
  96. package/dist/cli/verify-proof/resolver.d.mts.map +1 -1
  97. package/dist/cli/verify-proof/resolver.mjs +28 -7
  98. package/dist/cli/verify-proof/resolver.mjs.map +1 -1
  99. package/dist/cli/verify-proof/sandbox/apply.d.mts +64 -0
  100. package/dist/cli/verify-proof/sandbox/apply.d.mts.map +1 -0
  101. package/dist/cli/verify-proof/sandbox/apply.mjs +379 -0
  102. package/dist/cli/verify-proof/sandbox/apply.mjs.map +1 -0
  103. package/dist/cli/verify-proof/sandbox/gate-predicate.d.mts +63 -0
  104. package/dist/cli/verify-proof/sandbox/gate-predicate.d.mts.map +1 -0
  105. package/dist/cli/verify-proof/sandbox/gate-predicate.mjs +115 -0
  106. package/dist/cli/verify-proof/sandbox/gate-predicate.mjs.map +1 -0
  107. package/dist/cli/verify-proof/sandbox/pattern-matcher.d.mts +57 -0
  108. package/dist/cli/verify-proof/sandbox/pattern-matcher.d.mts.map +1 -0
  109. package/dist/cli/verify-proof/sandbox/pattern-matcher.mjs +124 -0
  110. package/dist/cli/verify-proof/sandbox/pattern-matcher.mjs.map +1 -0
  111. package/dist/cli/verify-proof/sandbox/recipe-diff.d.mts +28 -0
  112. package/dist/cli/verify-proof/sandbox/recipe-diff.d.mts.map +1 -0
  113. package/dist/cli/verify-proof/sandbox/recipe-diff.mjs +190 -0
  114. package/dist/cli/verify-proof/sandbox/recipe-diff.mjs.map +1 -0
  115. package/dist/cli/verify-proof/sandbox/sandbox-config.d.mts +25 -0
  116. package/dist/cli/verify-proof/sandbox/sandbox-config.d.mts.map +1 -0
  117. package/dist/cli/verify-proof/sandbox/sandbox-config.mjs +85 -0
  118. package/dist/cli/verify-proof/sandbox/sandbox-config.mjs.map +1 -0
  119. package/dist/cli/verify-proof/sandbox/self-test.d.mts +39 -0
  120. package/dist/cli/verify-proof/sandbox/self-test.d.mts.map +1 -0
  121. package/dist/cli/verify-proof/sandbox/self-test.mjs +248 -0
  122. package/dist/cli/verify-proof/sandbox/self-test.mjs.map +1 -0
  123. package/dist/plugin/.claude-plugin/plugin.json +2 -1
  124. package/dist/plugin/agents/apt-planner.md +15 -0
  125. package/dist/plugin/agents/apt-verifier.md +15 -10
  126. package/dist/plugin/skills/apt/SKILL.md +29 -0
  127. package/dist/plugin/skills/apt-catch-up/SKILL.md +79 -0
  128. package/dist/plugin/skills/apt-plan/SKILL.md +23 -0
  129. package/dist/plugin/skills/apt-pr-review/SKILL.md +5 -4
  130. package/dist/plugin/skills/apt-run/SKILL.md +1 -1
  131. package/dist/plugin/skills/apt-setup/SKILL.md +132 -0
  132. package/dist/plugin/skills/apt-verify/SKILL.md +20 -15
  133. package/dist/plugin/skills/apt-verify-proof/SKILL.md +146 -11
  134. package/dist/proof-report.d.ts.map +1 -1
  135. package/dist/proof-report.js +4 -1
  136. package/dist/proof-report.js.map +1 -1
  137. package/dist/types/config.d.ts +42 -4
  138. package/dist/types/config.d.ts.map +1 -1
  139. package/dist/types/qa-scoring.d.ts +45 -9
  140. package/dist/types/qa-scoring.d.ts.map +1 -1
  141. package/dist/types/qa-scoring.js +34 -13
  142. package/dist/types/qa-scoring.js.map +1 -1
  143. package/dist/types/state.d.ts +7 -2
  144. package/dist/types/state.d.ts.map +1 -1
  145. package/dist/types/task-record.d.ts +10 -2
  146. package/dist/types/task-record.d.ts.map +1 -1
  147. package/drivers/mobile/README.md +40 -0
  148. package/drivers/mobile/driver.mjs +106 -0
  149. package/drivers/mobile/manifest.json +49 -0
  150. package/package.json +1 -1
  151. package/prompts/inbox-clarification.md +11 -0
  152. package/prompts/inbox-triage.md +20 -0
  153. package/prompts/qa_orchestrator_agentic.md +18 -17
  154. package/prompts/qa_reviewer.md +17 -15
  155. package/skills/apt/SKILL.md +29 -0
  156. package/skills/apt-catch-up/SKILL.md +79 -0
  157. package/skills/apt-plan/SKILL.md +23 -0
  158. package/skills/apt-pr-review/SKILL.md +5 -4
  159. package/skills/apt-run/SKILL.md +1 -1
  160. package/skills/apt-setup/SKILL.md +132 -0
  161. package/skills/apt-verifier.md +7 -4
  162. package/skills/apt-verify/SKILL.md +20 -15
  163. package/skills/apt-verify-proof/SKILL.md +146 -11
  164. package/src/cli/commands/apply-recipe.mjs +105 -0
  165. package/src/cli/commands/catch-up.mjs +67 -0
  166. package/src/cli/commands/mobile-prepare.mjs +151 -0
  167. package/src/cli/commands/pr-review.mjs +32 -11
  168. package/src/cli/commands/recipe-diff.mjs +81 -0
  169. package/src/cli/commands/roadmap.mjs +54 -0
  170. package/src/cli/commands/route.mjs +92 -1
  171. package/src/cli/commands/verify-sandbox.mjs +231 -0
  172. package/src/cli/config/share-policy.mjs +1 -0
  173. package/src/cli/config/upgrade-gitignore.mjs +1 -0
  174. package/src/cli/consistency/parse-qa.mjs +20 -4
  175. package/src/cli/consistency/rules/r5-verdict-consistency.mjs +11 -13
  176. package/src/cli/coordination/catch-up.mjs +231 -0
  177. package/src/cli/coordination/last-seen.mjs +131 -0
  178. package/src/cli/coordination/store.mjs +18 -0
  179. package/src/cli/coverage-check/user-outcomes.mjs +52 -17
  180. package/src/cli/dispatch.mjs +29 -0
  181. package/src/cli/gate/gates/verify-approved.mjs +22 -81
  182. package/src/cli/git/default-branch.mjs +250 -0
  183. package/src/cli/git/identity.mjs +9 -3
  184. package/src/cli/install/legacy-paths.mjs +2 -0
  185. package/src/cli/install/mcp-server-specs.mjs +24 -4
  186. package/src/cli/roadmap/backend-adapter.mjs +231 -0
  187. package/src/cli/util/runtime-capabilities.mjs +67 -0
  188. package/src/cli/verify-proof/manifest-validator.mjs +15 -0
  189. package/src/cli/verify-proof/resolver.mjs +27 -7
  190. package/src/cli/verify-proof/sandbox/apply.mjs +401 -0
  191. package/src/cli/verify-proof/sandbox/gate-predicate.mjs +126 -0
  192. package/src/cli/verify-proof/sandbox/pattern-matcher.mjs +127 -0
  193. package/src/cli/verify-proof/sandbox/recipe-diff.mjs +208 -0
  194. package/src/cli/verify-proof/sandbox/sandbox-config.mjs +82 -0
  195. package/src/cli/verify-proof/sandbox/sandbox-schema.json +33 -0
  196. package/src/cli/verify-proof/sandbox/self-test.mjs +265 -0
  197. package/templates/config.json +21 -1
  198. package/templates/proof-verification.md +27 -8
  199. package/workflows/verify-proof.md +376 -302
@@ -0,0 +1,248 @@
1
+ /**
2
+ * self-test.mjs — two-instance concurrent self-test for parallel
3
+ * /apt:verify-proof.
4
+ *
5
+ * Spawns TWO instances of the recipe's launch command concurrently
6
+ * (Promise.all with B jittered 50-150 ms) against per-instance sandbox
7
+ * dirs. Asserts BOTH instances reach the recipe's `readiness.logPattern`
8
+ * within `readiness.timeoutMs` AND neither emits `SQLITE_BUSY` or
9
+ * `EADDRINUSE`/`bind` errors in stderr.
10
+ *
11
+ * The verdict envelope shape mirrors verify-proof v2's
12
+ * `verification.json :: meta.verdict`:
13
+ * {verified: bool, verdict: 'approved'|'needs_human',
14
+ * reason: null|'self_test_failed_sqlite_busy'|
15
+ * 'self_test_failed_port_conflict'|
16
+ * 'self_test_failed_app_init_timeout',
17
+ * durationMs: number}
18
+ *
19
+ * Cross-platform via `node:child_process.spawn` (NO shell). win32 short-
20
+ * circuits at the verify-sandbox command layer (subtask 4.2) — this
21
+ * module assumes a POSIX-ish host.
22
+ *
23
+ * No state-file writing here (4.2's job). Pure functional contract so
24
+ * the integration tests can drive it via `vi.spyOn(child_process,
25
+ * 'spawn')`.
26
+ */
27
+ import child_process from 'node:child_process';
28
+ import { mkdirSync } from 'node:fs';
29
+ import { tmpdir } from 'node:os';
30
+ import { join, resolve } from 'node:path';
31
+ /**
32
+ * @typedef {Object} SelfTestVerdict
33
+ * @property {boolean} verified
34
+ * @property {'approved'|'needs_human'} verdict
35
+ * @property {null|'self_test_failed_sqlite_busy'|'self_test_failed_port_conflict'|'self_test_failed_app_init_timeout'} reason
36
+ * @property {number} durationMs
37
+ * @property {{A: ChildOutcome, B: ChildOutcome}} children
38
+ */
39
+ /**
40
+ * @typedef {Object} ChildOutcome
41
+ * @property {boolean} ready
42
+ * @property {string|null} failureReason
43
+ * @property {string} stderrTail
44
+ */
45
+ /**
46
+ * @param {{projectDir: string, recipeId: string, recipes: object[]}} args
47
+ * @returns {Promise<SelfTestVerdict>}
48
+ */
49
+ export async function runSelfTest({ projectDir, recipeId, recipes }) {
50
+ const recipe = (recipes ?? []).find((r) => r && r.recipeId === recipeId);
51
+ if (!recipe) {
52
+ throw new Error(`runSelfTest: recipe not found: ${recipeId}`);
53
+ }
54
+ const launch = recipe.launch;
55
+ if (!launch?.binary) {
56
+ throw new Error(`runSelfTest: recipe ${recipeId} missing launch.{binary, args}`);
57
+ }
58
+ const readiness = recipe.readiness;
59
+ if (!readiness || typeof readiness.logPattern !== 'string') {
60
+ throw new Error(`runSelfTest: recipe ${recipeId} missing readiness.logPattern`);
61
+ }
62
+ const timeoutMs = readiness.timeoutMs ?? 30000;
63
+ const pid = process.pid;
64
+ const sandboxRoot = join(tmpdir(), `aperant-sandbox-${pid}-${Date.now()}`);
65
+ const dirs = {
66
+ A: join(sandboxRoot, 'A'),
67
+ B: join(sandboxRoot, 'B'),
68
+ };
69
+ mkdirSync(dirs.A, { recursive: true });
70
+ mkdirSync(dirs.B, { recursive: true });
71
+ const startedAt = Date.now();
72
+ const jitter = 50 + Math.random() * 100;
73
+ // Concurrency by construction: A starts immediately, B's start is
74
+ // awaited via a microtask + jitter delay. Promise.all races them.
75
+ const [outcomeA, outcomeB] = await Promise.all([
76
+ spawnInstance({ index: 'A', launch, readiness, timeoutMs, sandboxDir: dirs.A, projectDir }),
77
+ delay(jitter).then(() => spawnInstance({ index: 'B', launch, readiness, timeoutMs, sandboxDir: dirs.B, projectDir })),
78
+ ]);
79
+ const durationMs = Date.now() - startedAt;
80
+ const verdict = decideVerdict(outcomeA, outcomeB);
81
+ return {
82
+ ...verdict,
83
+ durationMs,
84
+ children: { A: outcomeA, B: outcomeB },
85
+ };
86
+ }
87
+ /**
88
+ * Race a readiness signal against the timeout, scanning stderr for the
89
+ * three known-bad patterns. Resolves to a ChildOutcome regardless of
90
+ * which arm wins. Always kills the child before resolving.
91
+ */
92
+ function spawnInstance({ index: _index, launch, readiness, timeoutMs, sandboxDir, projectDir }) {
93
+ return new Promise((resolveOutcome) => {
94
+ const env = renderLaunchEnv(launch.envTemplate ?? {}, sandboxDir);
95
+ const child = child_process.spawn(launch.binary, launch.args ?? [], {
96
+ cwd: resolve(projectDir),
97
+ env: { ...process.env, ...env, SANDBOX_DIR: sandboxDir },
98
+ stdio: ['ignore', 'pipe', 'pipe'],
99
+ });
100
+ let resolved = false;
101
+ let stderrBuf = '';
102
+ const stdoutBuf = [];
103
+ const MAX_STDERR_BYTES = 4096;
104
+ const finish = (outcome) => {
105
+ if (resolved)
106
+ return;
107
+ resolved = true;
108
+ clearTimeout(timer);
109
+ try {
110
+ child.kill('SIGTERM');
111
+ }
112
+ catch {
113
+ /* already gone */
114
+ }
115
+ // SIGKILL grace
116
+ setTimeout(() => {
117
+ try {
118
+ child.kill('SIGKILL');
119
+ }
120
+ catch {
121
+ /* gone */
122
+ }
123
+ }, 5000).unref?.();
124
+ resolveOutcome(outcome);
125
+ };
126
+ // onStderrText: appends to stderrBuf and checks failure-reason patterns.
127
+ // Must NOT be called with stdout data — informational app logs that mention
128
+ // "SQLITE_BUSY" or "EADDRINUSE" must not trigger failure verdicts (LOG-001).
129
+ const onStderrText = (text) => {
130
+ stderrBuf = (stderrBuf + text).slice(-MAX_STDERR_BYTES);
131
+ if (/SQLITE_BUSY/i.test(text)) {
132
+ finish({
133
+ ready: false,
134
+ failureReason: 'self_test_failed_sqlite_busy',
135
+ stderrTail: stderrBuf.slice(-500),
136
+ });
137
+ return;
138
+ }
139
+ if (/EADDRINUSE|bind\s+failed|address already in use|EACCES|EPERM/i.test(text)) {
140
+ finish({
141
+ ready: false,
142
+ failureReason: 'self_test_failed_port_conflict',
143
+ stderrTail: stderrBuf.slice(-500),
144
+ });
145
+ return;
146
+ }
147
+ };
148
+ // onStdoutText: checks readiness signal only. Never touches stderrBuf and
149
+ // never triggers failure-reason heuristics — stdout is not an error stream.
150
+ const onStdoutText = (text) => {
151
+ if (text.includes(readiness.logPattern)) {
152
+ finish({ ready: true, failureReason: null, stderrTail: stderrBuf.slice(-500) });
153
+ }
154
+ };
155
+ child.stdout?.on?.('data', (chunk) => {
156
+ const text = String(chunk);
157
+ stdoutBuf.push(text);
158
+ onStdoutText(text);
159
+ });
160
+ child.stderr?.on?.('data', (chunk) => {
161
+ const text = String(chunk);
162
+ onStderrText(text);
163
+ // Readiness may also appear on stderr (some apps log to stderr only).
164
+ if (text.includes(readiness.logPattern)) {
165
+ finish({ ready: true, failureReason: null, stderrTail: stderrBuf.slice(-500) });
166
+ }
167
+ });
168
+ child.on?.('error', (err) => {
169
+ finish({
170
+ ready: false,
171
+ failureReason: 'self_test_failed_app_init_timeout',
172
+ stderrTail: String(err.message ?? err),
173
+ });
174
+ });
175
+ child.on?.('exit', () => {
176
+ if (!resolved) {
177
+ // Process exited before signalling readiness. Treat as init timeout
178
+ // unless the stderr already revealed a known bad pattern (handled
179
+ // above before exit).
180
+ finish({
181
+ ready: false,
182
+ failureReason: 'self_test_failed_app_init_timeout',
183
+ stderrTail: stderrBuf.slice(-500),
184
+ });
185
+ }
186
+ });
187
+ const timer = setTimeout(() => {
188
+ finish({
189
+ ready: false,
190
+ failureReason: 'self_test_failed_app_init_timeout',
191
+ stderrTail: stderrBuf.slice(-500),
192
+ });
193
+ }, timeoutMs);
194
+ // Don't keep the event loop alive on the timer.
195
+ timer.unref?.();
196
+ });
197
+ }
198
+ /**
199
+ * Combine two outcomes into the v2-shaped verdict envelope.
200
+ * Failure precedence: sqlite_busy > port_conflict > app_init_timeout.
201
+ *
202
+ * Every spawnInstance() resolution sets failureReason explicitly on the
203
+ * !ready path (the four finish() call sites cover SQLITE_BUSY, port
204
+ * conflict, app_init_timeout, and the child-error path which also maps
205
+ * to app_init_timeout). The priority loop therefore always finds a
206
+ * match; the unreachable fallthrough that used to live here was
207
+ * deleted per CLAUDE.md guideline 2 (no handling for impossible
208
+ * scenarios).
209
+ */
210
+ function decideVerdict(a, b) {
211
+ if (a.ready && b.ready) {
212
+ return { verified: true, verdict: /** @type {const} */ ('approved'), reason: null };
213
+ }
214
+ const reasons = [a.failureReason, b.failureReason].filter(Boolean);
215
+ const priority = [
216
+ 'self_test_failed_sqlite_busy',
217
+ 'self_test_failed_port_conflict',
218
+ 'self_test_failed_app_init_timeout',
219
+ ];
220
+ for (const cand of priority) {
221
+ if (reasons.includes(cand)) {
222
+ return {
223
+ verified: false,
224
+ verdict: /** @type {const} */ ('needs_human'),
225
+ reason: /** @type {any} */ (cand),
226
+ };
227
+ }
228
+ }
229
+ throw new Error(`self-test decideVerdict: unreachable — both outcomes failed without a known reason: ${JSON.stringify({ a, b })}`);
230
+ }
231
+ /**
232
+ * Substitute ${SANDBOX_DIR} placeholders in the recipe's envTemplate.
233
+ */
234
+ function renderLaunchEnv(envTemplate, sandboxDir) {
235
+ const out = {};
236
+ for (const [k, v] of Object.entries(envTemplate)) {
237
+ // biome-ignore lint/suspicious/noTemplateCurlyInString: config template marker, not a typo
238
+ out[k] = typeof v === 'string' ? v.replaceAll('${SANDBOX_DIR}', sandboxDir) : v;
239
+ }
240
+ return out;
241
+ }
242
+ function delay(ms) {
243
+ return new Promise((res) => {
244
+ const t = setTimeout(res, ms);
245
+ t.unref?.();
246
+ });
247
+ }
248
+ //# sourceMappingURL=self-test.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"self-test.mjs","sourceRoot":"","sources":["../../../../src/cli/verify-proof/sandbox/self-test.mjs"],"names":[],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aperant",
3
- "version": "0.10.0",
3
+ "version": "0.12.0",
4
4
  "description": "AI coding framework — composable skills for planning, executing, verifying, and reviewing code with any LLM provider. Works as Claude Code commands, Codex tasks, or programmatically.",
5
5
  "author": {
6
6
  "name": "Mikalsen AI",
@@ -25,6 +25,7 @@
25
25
  "skills/apt",
26
26
  "skills/apt-author-skill",
27
27
  "skills/apt-bootstrap",
28
+ "skills/apt-catch-up",
28
29
  "skills/apt-caveman",
29
30
  "skills/apt-classify",
30
31
  "skills/apt-close-task",
@@ -217,6 +217,21 @@ Fast Path Guarantee). QUICK-routed tasks keep the existing 4-section
217
217
  shape; STANDARD/DEEP/COMPLEX tasks get a PRD-shaped artifact that
218
218
  survives the task as a second-consumer document.
219
219
 
220
+ **Sandbox-gated parallel verify-proof — cite the v2 envelope verbatim.**
221
+ When the plan calls for parallel `/apt:verify-proof` (e.g. fan-out
222
+ worker spawned a verify-proof child, or `--parallel` is passed to
223
+ `/apt:verify-proof`), the spec's User Outcomes section MUST cite the
224
+ sandbox gate's v2 failure envelope verbatim:
225
+ `{verdict: "needs_human", reason: "sandbox_unverified"}` (plus the
226
+ structured error code `E_SANDBOX_UNVERIFIED` and the remediation
227
+ `/apt:verify-proof setup`). Downstream agents reading the spec
228
+ dispatch on these exact strings; paraphrasing them breaks the
229
+ contract. The gate runs BEFORE Step 2.5 (Deferred-tool-load) and
230
+ BEFORE the outcome walker — sandbox-unverified is a precondition
231
+ failure, not a per-outcome transport failure. See
232
+ `apt-verify-proof/SKILL.md` Step 2c for the gate definition and
233
+ `docs/verify-proof-sandbox-patterns.md` for the worked adopter flow.
234
+
220
235
  ### 4.A. QUICK track / SIMPLE complexity — legacy 4-section spec
221
236
 
222
237
  ```markdown
@@ -80,10 +80,13 @@ If the planner claims a dimension is covered but the implementation does not act
80
80
  | **Code Quality** | Clean, idiomatic, lint-clean | Good, minor style issues | Acceptable, needs cleanup | Poor quality | Unacceptable |
81
81
  | **Test Coverage** | Comprehensive, edge cases | Good main-path coverage | Basic tests exist | Minimal tests | No coverage |
82
82
 
83
- ## 4. Derive Verdict
84
- - **approved:** ALL dimensions >= 7, no critical issues
85
- - **approved-with-notes:** ALL dimensions >= 5
86
- - **rejected:** ANY dimension < 5 or critical issues
83
+ ## 4. Derive Verdict (v0.9.0 two-gate envelope)
84
+
85
+ Verdict is the closed set `{approved, rejected, needs_human}`. Derived from the two-gate envelope (`automation_gate` AND `proof_gate`); dimensions are informational rationale only.
86
+
87
+ - **approved:** `automation_gate: pass` AND `proof_gate: pass`.
88
+ - **needs_human:** `automation_gate: pass` AND `proof_gate: fail` (typically: a UI-surface outcome lacked driver transport — see `/apt:verify-proof`).
89
+ - **rejected:** `automation_gate: fail` (any of lint/typecheck/tests red) OR critical findings unresolved.
87
90
 
88
91
  ## 5. Write QA Sign-off
89
92
  Write `{task_dir}/qa_signoff.json` with scores, checks, issues, and verdict.
@@ -105,14 +108,16 @@ Either shape is accepted:
105
108
 
106
109
  Gate G8 (`consistency-check`) enforces this on ship via rules R4
107
110
  (finding-propagation) and R5 (verdict-consistency):
108
- - `verdict: "approved"` or `verdict: "approved-with-notes"` is incompatible
109
- with any unresolved `critical` or `major` review finding (G42 Phase 1).
110
- Neither verdict escapes R5 for blocking-severity findings. The sole bypass
111
- is `--force --reason "<why>"` on the gate CLI (audit-logged). For
112
- non-blocking findings (suggestion/note/info/nitpick), cite them in
113
- `findings.notes` and use `approved-with-notes`.
111
+ - `verdict: "approved"` is incompatible with any unresolved `critical` or
112
+ `major` review finding (G42 Phase 1). The sole bypass is `--force --reason
113
+ "<why>"` on the gate CLI (audit-logged). For non-blocking findings
114
+ (suggestion/note/info/nitpick), cite them in `findings.notes` and keep
115
+ the verdict `approved`.
114
116
  - `verdict: "rejected"` requires at least one cited finding (rejected
115
117
  without evidence is treated as drift).
118
+ - `verdict: "needs_human"` signals a transport gap (e.g. UI-surface outcome
119
+ with no MCP driver). The R5 rule does not gate on it; re-run verify-proof
120
+ after installing the missing transport.
116
121
 
117
122
  ## 6. Report
118
123
  Output verdict with dimension scores and next step recommendation.
@@ -75,6 +75,34 @@ SessionStart hook — zero latency on /apt. If `update_check` is null,
75
75
  no chip. Skip the chip entirely once the user has run `/apt:update`
76
76
  in the current session (the cache is cleared there).
77
77
 
78
+ **Catch-up panel (runtime-agnostic):** if the route envelope includes a
79
+ non-null `catch_up` field, print the combined panel BEFORE the mode handler
80
+ runs, regardless of runtime (Claude Code, Codex, Gemini, Cursor, …). The
81
+ panel has two halves; render `catch_up.welcome_back` first, then
82
+ `catch_up.team_digest`. Omit a half whose field is null.
83
+
84
+ If `catch_up.welcome_back` is present (your own half-finished work):
85
+
86
+ ```
87
+ Where you left off: {description} — {lifecycle_phase}{current_subtask ? " · subtask " + current_subtask : ""} ({relative_time})
88
+ {next_step ? " Next: " + next_step : ""}
89
+ {others_count > 0 ? " (+" + others_count + " others · /apt:resume)" : ""}
90
+ ```
91
+
92
+ If `catch_up.team_digest` is present (what teammates merged while you were away):
93
+
94
+ ```
95
+ While you were away:
96
+ {for each entry in team_digest.entries: " #" + number + " " + title + " — " + author}
97
+ {team_digest.more_count > 0 ? " +" + team_digest.more_count + " more on main" : ""}
98
+ ```
99
+
100
+ This is display-only here — the Last-Seen Marker advance happens in route.mjs,
101
+ not in this skill. The panel is computed lazily on interactive (TTY) calls
102
+ only; non-TTY/background/spawned calls carry no `catch_up` field. Do not call
103
+ it an "activity feed", "changelog", or "notifications" — it is the
104
+ Welcome-Back Summary + Team-Change Digest.
105
+
78
106
  Then proceed to the matching mode handler below. Do NOT deviate from the routing decision.
79
107
  </mandatory_first_step>
80
108
 
@@ -129,6 +157,7 @@ Show available commands table:
129
157
  | `apt:scan` | Discover standards + refresh feature registry |
130
158
  | `apt:docs` | Generate/update documentation |
131
159
  | `apt:resume` | Restore context after session break |
160
+ | `apt:catch-up` | Re-view the catch-up panel (Welcome-Back + Team-Change Digest) — never advances the Last-Seen Marker |
132
161
  | `apt:pause` | Human-initiated handoff — save state when YOU need to stop |
133
162
  | `apt:ship` | Create PR with traceability |
134
163
  | `apt:close-task` | Post-merge closer — confirm PR merged, flip phase, drain narration ledger |
@@ -0,0 +1,79 @@
1
+ ---
2
+ name: apt:catch-up
3
+ description: "Re-view the catch-up panel (Welcome-Back + Team-Change Digest) on demand"
4
+ apt-skill-version: {{APT_VERSION}}
5
+ stage: resume
6
+ intent: capture
7
+ when_to_use: "The user dismissed the auto catch-up panel on their last /apt and wants to re-summon it — an idempotent re-view of where they left off plus what teammates merged. Distinct from /apt:resume (full context restore) and never advances the Last-Seen Marker."
8
+ user_invocable: true
9
+ internal: false
10
+ spawns_agent: false
11
+ agent_name: null
12
+ task_context: none
13
+ default_execution_mode: auto
14
+ execution_modes:
15
+ - auto
16
+ allowed-tools: "Bash, Read"
17
+ argument-hint: "apt:catch-up"
18
+ gates: []
19
+ ---
20
+ <objective>
21
+ Recompute and display the combined catch-up panel — the **Welcome-Back
22
+ Summary** (your most-recently-active task, phase, next step) and the
23
+ **Team-Change Digest** (the PRs teammates merged to main since you last
24
+ pulled) — exactly as the auto-on-apt display would show it, but **without
25
+ advancing the Last-Seen Marker**. This is a read-only re-view: a user who
26
+ dismissed the auto panel can re-summon it without disturbing the baseline and
27
+ without invoking the heavier `/apt:resume`.
28
+ </objective>
29
+
30
+ <your_environment>
31
+ - **Working directory:** The project root (where you were invoked)
32
+ - **apt-tools:** `node packages/framework/bin/apt-tools.mjs` or the installed `apt-tools` binary
33
+ - **Last-Seen Marker:** `.aperant/last-seen/{email-slug}.json` (gitignored, per-machine) — this skill READS it but NEVER writes it.
34
+ </your_environment>
35
+
36
+ <process>
37
+
38
+ ## 1. Recompute the panel (read-only)
39
+
40
+ ```bash
41
+ node packages/framework/bin/apt-tools.mjs catch-up .
42
+ ```
43
+
44
+ This computes the SAME panel the interactive route call produces, against the
45
+ **current** marker — and deliberately does **NOT** call `advanceLastSeen`
46
+ (recompute-only contract). Parse the JSON `catch_up` field from the envelope.
47
+
48
+ ## 2. Render the panel
49
+
50
+ Render exactly as the `apt/SKILL.md` "Catch-up panel" block does — Welcome-Back
51
+ half first, then the Team-Change Digest half. Omit a half whose field is null.
52
+
53
+ If `catch_up` is null, tell the user there is nothing new since their last
54
+ visit (no active stale task and no new merges on main).
55
+
56
+ ```
57
+ Where you left off: {description} — {lifecycle_phase} · subtask {current_subtask} ({relative_time})
58
+ Next: {next_step}
59
+ (+{others_count} others · /apt:resume)
60
+
61
+ While you were away:
62
+ #{number} {title} — {author}
63
+ +{more_count} more on main
64
+ ```
65
+
66
+ </process>
67
+
68
+ <notes>
69
+ - **Never advances the marker.** Unlike the auto-on-apt display (which advances
70
+ the Last-Seen Marker after the panel is evaluated), this command is purely a
71
+ re-view. Re-running it is idempotent.
72
+ - **Distinct from `/apt:resume`.** Resume restores full context and routes to
73
+ the next action; catch-up is a lightweight orientation re-view.
74
+ - **v1 is lazy compute.** The panel is computed on demand here, bounded by the
75
+ commits since your last pull. A background pre-warm worker (like the
76
+ update-check worker) is a tracked follow-up (LD-12), not part of v1.
77
+ - **Vocabulary.** This is the Welcome-Back Summary + Team-Change Digest — not an
78
+ "activity feed", "changelog", or "notifications".
79
+ </notes>
@@ -209,6 +209,8 @@ see the gate table below.}
209
209
  **Epic:** {one-sentence user-facing capability headline}
210
210
 
211
211
  - **ON** [surface]: {user does X; system shows/does Y}
212
+ - {indented inferred sub-check derived by the planning LLM}
213
+ - {another inferred sub-check}
212
214
  - **ON** [surface]: {user does X; system shows/does Y} *(requires ON 1)*
213
215
  - **ON** [electron+web]: {cross-surface outcome — runs twice, once per surface}
214
216
 
@@ -246,6 +248,27 @@ ID. Subtasks reference these IDs via their per-subtask
246
248
  plan-coverage-check input.}
247
249
  ```
248
250
 
251
+ ### Inferred sub-checks (v0.9.0 — verify-proof-v2-outcome-walker)
252
+
253
+ Under each `**ON**` parent bullet, write indented child bullets (2-space
254
+ prefix + `- `) representing inferred sub-checks the planning LLM derives
255
+ from the outcome prose + surface. These are NOT user-written invariants;
256
+ they are the planner's judgment of "what else must be true for this
257
+ outcome to be approved." The verify-proof runner walks each inferred
258
+ child the same way it walks the parent.
259
+
260
+ Worked example (copy this shape verbatim into spec.md):
261
+
262
+ - **ON** [electron]: Clicking 'Settings' opens the settings modal.
263
+ - The modal traps focus on the close button.
264
+ - Clicking outside the modal closes it.
265
+ - Pressing Esc closes it and returns focus to the trigger.
266
+
267
+ **NO `patterns/` library, NO `surfaces/<name>/manifest.json` tree, NO
268
+ `OUTCOMES.md` file** — inferred bullets live IN spec.md. The author
269
+ edits spec.md once if the inferred list is wrong; the runner does not
270
+ re-derive them at proof time.
271
+
249
272
  **`## User Outcomes` is the verify-proof input contract (STANDARD/DEEP
250
273
  only — QUICK is hard-exempt per ID-05 / Fast Path Guarantee).** The
251
274
  section sits between `## User Stories` (product motivation prose) and
@@ -247,10 +247,11 @@ The command resolves in this precedence order:
247
247
 
248
248
  1. `config.json:pr_review.authorship_overrides['pr-${PR_NUMBER}']` (per-PR pin)
249
249
  2. `config.json:pr_review.authorship_override` (global: `owned` | `external` | `auto`)
250
- 3. `gh pr view` → `authorAssociation` + `headRepository.owner.login` vs. viewer login
251
- - `authorAssociation IN (OWNER, MEMBER, COLLABORATOR)` AND same-repo branch → **owned**
252
- - Fork OR `authorAssociation IN (NONE, CONTRIBUTOR, FIRST_TIME_CONTRIBUTOR)` → **external**
253
- 4. Offline / gh unavailable / ambiguous payload → **unknown**
250
+ 3. `gh pr view` → `gh.author.login` vs. viewer login (G44 — `owned` means a PR you **authored**, not a branch you can push to)
251
+ - Same-repo branch AND `gh.author.login === viewer login` → **owned**
252
+ - Any other same-repo PR (a teammate's, or a repo-`OWNER` viewing someone else's PR) → **external** (`authorAssociation` is no longer consulted for the `owned` decision — it is retained only in the human-readable signal)
253
+ - Fork (head-repo owner base-repo owner) → **external**
254
+ 4. Offline / gh unavailable / ambiguous payload → **unknown** (treated as `external` for safety, see below)
254
255
 
255
256
  When the result is `unknown`, treat it as `external` for safety and prompt the user:
256
257
 
@@ -281,7 +281,7 @@ Execute the apt:verify workflow inline:
281
281
  6. Score on 4 dimensions (completeness, correctness, quality, coverage)
282
282
  7. Fix all issues found — every severity, not just critical (up to 4 iterations: first 2 discovery, last 2 verification-only)
283
283
  8. Write qa_signoff.json
284
- 9. If `status` is `approved` or `approved-with-notes`, flip lifecycle to `reviewing` so Stage 4 (or `/apt:ship` when review is skipped) finds the task in the correct from-state. Skip this step entirely on `rejected`:
284
+ 9. If `status` is `approved`, flip lifecycle to `reviewing` so Stage 4 (or `/apt:ship` when review is skipped) finds the task in the correct from-state. Skip this step entirely on `rejected` (fix the code and re-run) or `needs_human` (install the missing driver transport and re-run /apt:verify-proof):
285
285
  ```bash
286
286
  node packages/framework/bin/apt-tools.mjs task update . --id {task-id} --lifecycle-phase reviewing
287
287
  ```