@aperant/framework 0.7.0 → 0.7.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 +205 -0
- package/agents/apt-planner.md +12 -0
- package/agents/apt-pr-review-fixer.md +13 -9
- package/bin/apt-tools.mjs +7 -0
- package/dist/cli/ci-watch/stop-matrix.d.mts.map +1 -1
- package/dist/cli/ci-watch/stop-matrix.mjs +16 -0
- package/dist/cli/ci-watch/stop-matrix.mjs.map +1 -1
- package/dist/cli/commands/ci-watch.d.mts +11 -0
- package/dist/cli/commands/ci-watch.d.mts.map +1 -1
- package/dist/cli/commands/ci-watch.mjs +108 -2
- package/dist/cli/commands/ci-watch.mjs.map +1 -1
- package/dist/cli/commands/features-audit.d.mts +24 -0
- package/dist/cli/commands/features-audit.d.mts.map +1 -1
- package/dist/cli/commands/features-audit.mjs +159 -5
- package/dist/cli/commands/features-audit.mjs.map +1 -1
- package/dist/cli/commands/health-check.d.mts +16 -0
- package/dist/cli/commands/health-check.d.mts.map +1 -1
- package/dist/cli/commands/health-check.mjs +118 -2
- package/dist/cli/commands/health-check.mjs.map +1 -1
- package/dist/cli/commands/init.d.mts +9 -0
- package/dist/cli/commands/init.d.mts.map +1 -1
- package/dist/cli/commands/init.mjs +49 -4
- package/dist/cli/commands/init.mjs.map +1 -1
- package/dist/cli/commands/modes.d.mts.map +1 -1
- package/dist/cli/commands/modes.mjs +11 -0
- package/dist/cli/commands/modes.mjs.map +1 -1
- package/dist/cli/commands/pr-review-audit-fixer.d.mts +13 -0
- package/dist/cli/commands/pr-review-audit-fixer.d.mts.map +1 -1
- package/dist/cli/commands/pr-review-audit-fixer.mjs +18 -5
- package/dist/cli/commands/pr-review-audit-fixer.mjs.map +1 -1
- package/dist/cli/commands/task.d.mts.map +1 -1
- package/dist/cli/commands/task.mjs +84 -5
- package/dist/cli/commands/task.mjs.map +1 -1
- package/dist/cli/commands/validate-evidence.d.mts +24 -2
- package/dist/cli/commands/validate-evidence.d.mts.map +1 -1
- package/dist/cli/commands/validate-evidence.mjs +154 -17
- package/dist/cli/commands/validate-evidence.mjs.map +1 -1
- package/dist/cli/commands/vitest-doctor.d.mts +2 -0
- package/dist/cli/commands/vitest-doctor.d.mts.map +1 -0
- package/dist/cli/commands/vitest-doctor.mjs +168 -0
- package/dist/cli/commands/vitest-doctor.mjs.map +1 -0
- package/dist/cli/config/gitignore-drift.d.mts +23 -1
- package/dist/cli/config/gitignore-drift.d.mts.map +1 -1
- package/dist/cli/config/gitignore-drift.mjs +81 -3
- package/dist/cli/config/gitignore-drift.mjs.map +1 -1
- package/dist/cli/config/load.d.mts +56 -2
- package/dist/cli/config/load.d.mts.map +1 -1
- package/dist/cli/config/load.mjs +192 -2
- package/dist/cli/config/load.mjs.map +1 -1
- package/dist/cli/consistency/parse-review.mjs +7 -0
- package/dist/cli/consistency/parse-review.mjs.map +1 -1
- package/dist/cli/dispatch.d.mts.map +1 -1
- package/dist/cli/dispatch.mjs +24 -2
- package/dist/cli/dispatch.mjs.map +1 -1
- package/dist/cli/gate/gates/gitignore-in-sync.d.mts +1 -1
- package/dist/cli/gate/gates/gitignore-in-sync.d.mts.map +1 -1
- package/dist/cli/gate/gates/gitignore-in-sync.mjs +5 -2
- package/dist/cli/gate/gates/gitignore-in-sync.mjs.map +1 -1
- package/dist/cli/gate/gates/review-clean.d.mts +5 -1
- package/dist/cli/gate/gates/review-clean.d.mts.map +1 -1
- package/dist/cli/gate/gates/review-clean.mjs +23 -18
- package/dist/cli/gate/gates/review-clean.mjs.map +1 -1
- package/dist/cli/gate/gates/verify-approved.d.mts +49 -1
- package/dist/cli/gate/gates/verify-approved.d.mts.map +1 -1
- package/dist/cli/gate/gates/verify-approved.mjs +93 -14
- package/dist/cli/gate/gates/verify-approved.mjs.map +1 -1
- package/dist/cli/help.d.mts.map +1 -1
- package/dist/cli/help.mjs +7 -2
- package/dist/cli/help.mjs.map +1 -1
- package/dist/cli/install/runtime-detect.d.mts +13 -0
- package/dist/cli/install/runtime-detect.d.mts.map +1 -1
- package/dist/cli/install/runtime-detect.mjs +9 -0
- package/dist/cli/install/runtime-detect.mjs.map +1 -1
- package/dist/cli/task/index-md.d.mts.map +1 -1
- package/dist/cli/task/index-md.mjs +14 -2
- package/dist/cli/task/index-md.mjs.map +1 -1
- package/dist/plugin/.claude-plugin/plugin.json +11 -2
- package/dist/plugin/agents/apt-improver.md +99 -0
- package/dist/plugin/agents/apt-planner.md +127 -10
- package/dist/plugin/agents/apt-pr-review-fixer.md +13 -9
- package/dist/plugin/skills/apt/SKILL.md +1 -0
- package/dist/plugin/skills/apt-close-task/SKILL.md +63 -1
- package/dist/plugin/skills/apt-debug/SKILL.md +39 -6
- package/dist/plugin/skills/apt-debug/appendices/diagnose-discipline.md +119 -0
- package/dist/plugin/skills/apt-diagram/SKILL.md +378 -0
- package/dist/plugin/skills/apt-diagram/appendices/design-discipline.md +97 -0
- package/dist/plugin/skills/apt-discuss/SKILL.md +72 -5
- package/dist/plugin/skills/apt-discuss/appendices/grill-discipline.md +104 -0
- package/dist/plugin/skills/apt-discuss/appendices/zoom-out-helper.md +79 -0
- package/dist/plugin/skills/apt-execute/SKILL.md +57 -5
- package/dist/plugin/skills/apt-execute/appendices/tdd-mode.md +107 -0
- package/dist/plugin/skills/apt-improve/DEEPENING.md +84 -0
- package/dist/plugin/skills/apt-improve/INTERFACE-DESIGN.md +97 -0
- package/dist/plugin/skills/apt-improve/LANGUAGE.md +104 -0
- package/dist/plugin/skills/apt-improve/SKILL.md +141 -0
- package/dist/plugin/skills/apt-plan/SKILL.md +171 -4
- package/dist/plugin/skills/apt-plan/adapters/conductor.md +98 -0
- package/dist/plugin/skills/apt-pr-review/SKILL.md +57 -18
- package/dist/plugin/skills/apt-prototype/LOGIC.md +109 -0
- package/dist/plugin/skills/apt-prototype/SKILL.md +143 -0
- package/dist/plugin/skills/apt-prototype/UI.md +90 -0
- package/dist/plugin/skills/apt-quick/SKILL.md +49 -8
- package/dist/plugin/skills/apt-release-notes/SKILL.md +193 -0
- package/dist/plugin/skills/apt-release-notes/appendices/persona-voice.md +59 -0
- package/dist/plugin/skills/apt-review/SKILL.md +2 -0
- package/dist/plugin/skills/apt-run/SKILL.md +32 -4
- package/dist/plugin/skills/apt-setup/SKILL.md +308 -6
- package/dist/plugin/skills/apt-ship/SKILL.md +122 -1
- package/dist/plugin/skills/apt-spar/SKILL.md +315 -0
- package/dist/plugin/skills/apt-triage/AGENT-BRIEF.md +84 -0
- package/dist/plugin/skills/apt-triage/OUT-OF-SCOPE.md +75 -0
- package/dist/plugin/skills/apt-triage/SKILL.md +169 -0
- package/dist/plugin/skills/apt-update/SKILL.md +77 -10
- package/dist/plugin/skills/apt-verify/SKILL.md +3 -0
- package/dist/plugin/skills/apt-verify-proof/SKILL.md +10 -5
- package/dist/plugin/skills/apt-watch-ci/SKILL.md +166 -0
- package/dist/plugin/skills/apt-zoom-out/SKILL.md +130 -0
- package/package.json +1 -1
- package/prompts/conductor-framework-context.md +63 -0
- package/prompts/conductor-system.md +11 -0
- package/skills/apt-close-task/SKILL.md +1 -0
- package/skills/apt-discuss/SKILL.md +47 -5
- package/skills/apt-execute/SKILL.md +9 -0
- package/skills/apt-plan/SKILL.md +12 -0
- package/skills/apt-pr-review/SKILL.md +19 -3
- package/skills/apt-quick/SKILL.md +19 -8
- package/skills/apt-researcher.md +1 -0
- package/skills/apt-setup/SKILL.md +33 -2
- package/skills/apt-verify-proof/SKILL.md +7 -5
- package/src/cli/ci-watch/stop-matrix.mjs +17 -0
- package/src/cli/commands/ci-watch.mjs +113 -2
- package/src/cli/commands/features-audit.mjs +164 -5
- package/src/cli/commands/health-check.mjs +115 -2
- package/src/cli/commands/init.mjs +52 -4
- package/src/cli/commands/modes.mjs +11 -0
- package/src/cli/commands/pr-review-audit-fixer.mjs +18 -5
- package/src/cli/commands/task.mjs +83 -5
- package/src/cli/commands/validate-evidence.mjs +158 -17
- package/src/cli/commands/vitest-doctor.mjs +173 -0
- package/src/cli/config/gitignore-drift.mjs +74 -3
- package/src/cli/config/load.mjs +188 -2
- package/src/cli/consistency/parse-review.mjs +6 -0
- package/src/cli/dispatch.mjs +23 -2
- package/src/cli/gate/gates/gitignore-in-sync.mjs +5 -2
- package/src/cli/gate/gates/review-clean.mjs +24 -19
- package/src/cli/gate/gates/verify-approved.mjs +97 -14
- package/src/cli/help.mjs +7 -2
- package/src/cli/install/runtime-detect.mjs +9 -0
- package/src/cli/task/index-md.mjs +15 -2
- package/templates/config.json +2 -7
- package/workflows/verify-proof.md +8 -3
package/src/cli/config/load.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* config/load.mjs — project config loader + task-isolation normalizer (C29).
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { existsSync, readFileSync } from 'node:fs'
|
|
5
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
6
6
|
import { dirname, isAbsolute, join, relative, resolve } from 'node:path'
|
|
7
7
|
import { fileURLToPath } from 'node:url'
|
|
8
8
|
import { cliToInstallRoot } from '../install/cli-to-install-root.mjs'
|
|
@@ -31,7 +31,7 @@ export function loadProjectConfig(targetDir) {
|
|
|
31
31
|
* fallback would mask the user's broken override. See FRAMEWORK-AUDIT-001.
|
|
32
32
|
*
|
|
33
33
|
* @param {string} targetDir
|
|
34
|
-
* @returns {
|
|
34
|
+
* @returns {Record<string, any> | null}
|
|
35
35
|
*/
|
|
36
36
|
export function loadMergedProjectConfig(targetDir) {
|
|
37
37
|
const shared = loadProjectConfig(targetDir)
|
|
@@ -57,6 +57,192 @@ function deepMerge(a, b) {
|
|
|
57
57
|
return out
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
/**
|
|
61
|
+
* AUDIT-001 — read+write helpers for `.aperant/config.local.json`.
|
|
62
|
+
* Used by the 5 deferred fields migrated out of `config.json`:
|
|
63
|
+
* - verification.open_report
|
|
64
|
+
* - multi_model.cross_verification.tools
|
|
65
|
+
* - preferences.date_format
|
|
66
|
+
* - preferences.time_format
|
|
67
|
+
* - preferences.quick_task_post_verify
|
|
68
|
+
*
|
|
69
|
+
* Each field has its own read/write site. The shared helpers below give
|
|
70
|
+
* those sites a single primitive instead of hand-rolled JSON I/O.
|
|
71
|
+
*/
|
|
72
|
+
function setDottedPath(obj, dotPath, value) {
|
|
73
|
+
const parts = dotPath.split('.')
|
|
74
|
+
let cursor = obj
|
|
75
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
76
|
+
const key = parts[i]
|
|
77
|
+
if (!cursor[key] || typeof cursor[key] !== 'object' || Array.isArray(cursor[key])) {
|
|
78
|
+
cursor[key] = {}
|
|
79
|
+
}
|
|
80
|
+
cursor = cursor[key]
|
|
81
|
+
}
|
|
82
|
+
cursor[parts[parts.length - 1]] = value
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function getDottedPath(obj, dotPath) {
|
|
86
|
+
const parts = dotPath.split('.')
|
|
87
|
+
let cursor = obj
|
|
88
|
+
for (const part of parts) {
|
|
89
|
+
if (cursor === null || cursor === undefined || typeof cursor !== 'object') return undefined
|
|
90
|
+
cursor = cursor[part]
|
|
91
|
+
}
|
|
92
|
+
return cursor
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function deleteDottedPath(obj, dotPath) {
|
|
96
|
+
const parts = dotPath.split('.')
|
|
97
|
+
let cursor = obj
|
|
98
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
99
|
+
const key = parts[i]
|
|
100
|
+
if (!cursor[key] || typeof cursor[key] !== 'object') return false
|
|
101
|
+
cursor = cursor[key]
|
|
102
|
+
}
|
|
103
|
+
const leaf = parts[parts.length - 1]
|
|
104
|
+
if (leaf in cursor) {
|
|
105
|
+
delete cursor[leaf]
|
|
106
|
+
return true
|
|
107
|
+
}
|
|
108
|
+
return false
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Read a single dotted-path field from the merged config (local overrides
|
|
113
|
+
* shared). Returns `undefined` when neither file carries the path.
|
|
114
|
+
*
|
|
115
|
+
* @internal
|
|
116
|
+
*/
|
|
117
|
+
export function readLocalOrSharedField(targetDir, dotPath) {
|
|
118
|
+
const merged = loadMergedProjectConfig(targetDir)
|
|
119
|
+
if (!merged) return undefined
|
|
120
|
+
return getDottedPath(merged, dotPath)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Write a single dotted-path field into `.aperant/config.local.json`,
|
|
125
|
+
* creating the file (and intermediate path) when it doesn't exist.
|
|
126
|
+
*
|
|
127
|
+
* The shared `config.json` is NOT touched by this helper — that's the
|
|
128
|
+
* load-bearing AUDIT-001 contract. Callers that need to remove a stale
|
|
129
|
+
* entry from `config.json` (e.g. the init.mjs migrator) must do so
|
|
130
|
+
* explicitly.
|
|
131
|
+
*
|
|
132
|
+
* @internal
|
|
133
|
+
*/
|
|
134
|
+
export function writeLocalConfigField(targetDir, dotPath, value) {
|
|
135
|
+
const aperantDir = join(targetDir, '.aperant')
|
|
136
|
+
mkdirSync(aperantDir, { recursive: true })
|
|
137
|
+
const localPath = join(aperantDir, 'config.local.json')
|
|
138
|
+
let local = {}
|
|
139
|
+
if (existsSync(localPath)) {
|
|
140
|
+
try {
|
|
141
|
+
local = JSON.parse(readFileSync(localPath, 'utf-8'))
|
|
142
|
+
if (local === null || typeof local !== 'object' || Array.isArray(local)) local = {}
|
|
143
|
+
} catch (e) {
|
|
144
|
+
throw new Error(`config.local.json parse error: ${e.message}`)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
setDottedPath(local, dotPath, value)
|
|
148
|
+
writeFileSync(localPath, `${JSON.stringify(local, null, 2)}\n`)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* AUDIT-001 — first-run migrator. For each dotted-path entry present in
|
|
153
|
+
* `.aperant/config.json`, copy the value into `.aperant/config.local.json`
|
|
154
|
+
* (idempotent — won't overwrite an existing local value) and delete the
|
|
155
|
+
* entry from `config.json`. Each field's migration is independent —
|
|
156
|
+
* one failure does NOT block the others (a per-field try/catch wraps
|
|
157
|
+
* each move).
|
|
158
|
+
*
|
|
159
|
+
* Returns an array `[{ field, migrated_value, status }]` for caller
|
|
160
|
+
* telemetry; `status` is `'migrated' | 'already-local' | 'absent' | 'error'`.
|
|
161
|
+
*
|
|
162
|
+
* @internal
|
|
163
|
+
*/
|
|
164
|
+
export function migrateDeferredFieldsToLocalConfig(targetDir, dotPaths) {
|
|
165
|
+
const results = []
|
|
166
|
+
const sharedPath = join(targetDir, '.aperant', 'config.json')
|
|
167
|
+
if (!existsSync(sharedPath)) {
|
|
168
|
+
for (const dotPath of dotPaths) results.push({ field: dotPath, status: 'absent' })
|
|
169
|
+
return results
|
|
170
|
+
}
|
|
171
|
+
let shared
|
|
172
|
+
try {
|
|
173
|
+
shared = JSON.parse(readFileSync(sharedPath, 'utf-8'))
|
|
174
|
+
} catch {
|
|
175
|
+
for (const dotPath of dotPaths) results.push({ field: dotPath, status: 'error' })
|
|
176
|
+
return results
|
|
177
|
+
}
|
|
178
|
+
// Pre-load local config ONCE so the already-local check reads from
|
|
179
|
+
// local-only state (NOT from the merged loader — that would conflate
|
|
180
|
+
// the shared entry we're about to delete with a real local override).
|
|
181
|
+
const localPath = join(targetDir, '.aperant', 'config.local.json')
|
|
182
|
+
let localAtStart = {}
|
|
183
|
+
if (existsSync(localPath)) {
|
|
184
|
+
try {
|
|
185
|
+
localAtStart = JSON.parse(readFileSync(localPath, 'utf-8')) ?? {}
|
|
186
|
+
} catch {
|
|
187
|
+
localAtStart = {}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
let sharedMutated = false
|
|
191
|
+
for (const dotPath of dotPaths) {
|
|
192
|
+
try {
|
|
193
|
+
const sharedValue = getDottedPath(shared, dotPath)
|
|
194
|
+
if (sharedValue === undefined) {
|
|
195
|
+
results.push({ field: dotPath, status: 'absent' })
|
|
196
|
+
continue
|
|
197
|
+
}
|
|
198
|
+
const existingLocal = getDottedPath(localAtStart, dotPath)
|
|
199
|
+
// Already-local: the local override already exists. Strip the
|
|
200
|
+
// stale shared entry but keep the local value as-is (don't
|
|
201
|
+
// overwrite a user-tuned local with the stale shared default).
|
|
202
|
+
if (existingLocal !== undefined) {
|
|
203
|
+
deleteDottedPath(shared, dotPath)
|
|
204
|
+
sharedMutated = true
|
|
205
|
+
results.push({ field: dotPath, status: 'already-local', migrated_value: sharedValue })
|
|
206
|
+
continue
|
|
207
|
+
}
|
|
208
|
+
writeLocalConfigField(targetDir, dotPath, sharedValue)
|
|
209
|
+
deleteDottedPath(shared, dotPath)
|
|
210
|
+
sharedMutated = true
|
|
211
|
+
results.push({ field: dotPath, status: 'migrated', migrated_value: sharedValue })
|
|
212
|
+
} catch (e) {
|
|
213
|
+
results.push({ field: dotPath, status: 'error', error: e?.message ?? String(e) })
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
if (sharedMutated) {
|
|
217
|
+
try {
|
|
218
|
+
writeFileSync(sharedPath, `${JSON.stringify(shared, null, 2)}\n`)
|
|
219
|
+
} catch (e) {
|
|
220
|
+
// Disk write failed after per-field local writes succeeded.
|
|
221
|
+
// Log a structured warning so callers can surface the partial state
|
|
222
|
+
// to the user (config.local.json updated, config.json NOT cleaned up).
|
|
223
|
+
results.push({
|
|
224
|
+
field: '__write_shared__',
|
|
225
|
+
status: 'error',
|
|
226
|
+
error: `migration_partial_rollback: config.local.json was updated but config.json could not be written (${e?.message ?? String(e)}). Re-run init to retry.`,
|
|
227
|
+
})
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return results
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* AUDIT-001 — the 5 deferred fields that move to config.local.json.
|
|
235
|
+
* Single source of truth used by both the init migrator and the
|
|
236
|
+
* per-field regression tests.
|
|
237
|
+
*/
|
|
238
|
+
export const AUDIT_001_LOCAL_FIELDS = Object.freeze([
|
|
239
|
+
'verification.open_report',
|
|
240
|
+
'multi_model.cross_verification.tools',
|
|
241
|
+
'preferences.date_format',
|
|
242
|
+
'preferences.time_format',
|
|
243
|
+
'preferences.quick_task_post_verify',
|
|
244
|
+
])
|
|
245
|
+
|
|
60
246
|
/**
|
|
61
247
|
* Return a normalized task_isolation config object with safe fallbacks.
|
|
62
248
|
* Always returns a concrete object even when the project has no config at all,
|
|
@@ -95,6 +95,12 @@ function normalizeStatus(f) {
|
|
|
95
95
|
if (typeof f.status === 'string') {
|
|
96
96
|
const s = f.status.toLowerCase().trim()
|
|
97
97
|
if (STATUS_MAP[s]) return STATUS_MAP[s]
|
|
98
|
+
// BUG-009: reviewer-skill writes `deferred-per-spec`, `deferred-followup`,
|
|
99
|
+
// etc. for findings that are intentionally not being fixed in the
|
|
100
|
+
// current scope. Collapse the variant prefix back to canonical
|
|
101
|
+
// `deferred` so downstream gates (review-clean, ship) treat them
|
|
102
|
+
// the same as a bare `status: "deferred"`.
|
|
103
|
+
if (s.startsWith('deferred-')) return 'deferred'
|
|
98
104
|
}
|
|
99
105
|
return 'open'
|
|
100
106
|
}
|
package/src/cli/dispatch.mjs
CHANGED
|
@@ -41,7 +41,7 @@ import { cmdAuthorSkill } from './commands/author-skill.mjs'
|
|
|
41
41
|
import { cmdBootstrap } from './commands/bootstrap.mjs'
|
|
42
42
|
import { cmdCheck } from './commands/check.mjs'
|
|
43
43
|
import { cmdCheckVersion } from './commands/check-version.mjs'
|
|
44
|
-
import { cmdCiWatch } from './commands/ci-watch.mjs'
|
|
44
|
+
import { cmdCiWatch, cmdCiWatchSweep } from './commands/ci-watch.mjs'
|
|
45
45
|
import { cmdCommit } from './commands/commit.mjs'
|
|
46
46
|
import { cmdConsistency } from './commands/consistency.mjs'
|
|
47
47
|
import { cmdConstitution } from './commands/constitution.mjs'
|
|
@@ -93,6 +93,7 @@ import { cmdValidateEvidence } from './commands/validate-evidence.mjs'
|
|
|
93
93
|
import { cmdVariantGallery } from './commands/variant-gallery.mjs'
|
|
94
94
|
import { cmdVariantPick } from './commands/variant-pick.mjs'
|
|
95
95
|
import { cmdVariantPool } from './commands/variant-pool.mjs'
|
|
96
|
+
import { cmdVitestDoctor } from './commands/vitest-doctor.mjs'
|
|
96
97
|
import { cmdWave } from './commands/wave.mjs'
|
|
97
98
|
import { cmdWfrun } from './commands/wfrun.mjs'
|
|
98
99
|
import { cmdWorkflow } from './commands/workflow.mjs'
|
|
@@ -241,6 +242,21 @@ export const DISPATCH = {
|
|
|
241
242
|
'precompact-hook': (args) => wrap(cmdPrecompactHook)(args[1]),
|
|
242
243
|
'migrate-pr-reviews': (args) => wrap(cmdMigratePrReviews)(args[1]),
|
|
243
244
|
'validate-evidence': (args) => wrap(cmdValidateEvidence)(args[1], args.slice(2)),
|
|
245
|
+
vitest: (args) => {
|
|
246
|
+
// `vitest doctor <project-dir>` — currently the only `vitest`
|
|
247
|
+
// subcommand. Reserved namespace for future vitest diagnostics.
|
|
248
|
+
const subcommand = args[1]
|
|
249
|
+
if (subcommand === 'doctor') {
|
|
250
|
+
return wrap(cmdVitestDoctor)(args[2], args.slice(3))
|
|
251
|
+
}
|
|
252
|
+
return wrap(() => ({
|
|
253
|
+
envelope: {
|
|
254
|
+
status: 'error',
|
|
255
|
+
error: `Usage: apt-tools vitest doctor <project-dir>. Unknown subcommand: ${subcommand ?? '(none)'}`,
|
|
256
|
+
},
|
|
257
|
+
exitCode: 1,
|
|
258
|
+
}))()
|
|
259
|
+
},
|
|
244
260
|
|
|
245
261
|
// ---- (positional dir, args[2] subcommand, slice(3)) shape ----
|
|
246
262
|
tokens: (args) => wrap(cmdTokens)(args[1], args[2], args.slice(3)),
|
|
@@ -261,7 +277,12 @@ export const DISPATCH = {
|
|
|
261
277
|
seed: (args) => wrap(cmdSeed)(args[1], args[2], args.slice(3)),
|
|
262
278
|
workflow: (args) => wrap(cmdWorkflow)(args[1], args[2], args.slice(3)),
|
|
263
279
|
'pr-review': (args) => wrap(cmdPrReview)(args[1], args[2], args.slice(3)),
|
|
264
|
-
'ci-watch': (args) =>
|
|
280
|
+
'ci-watch': (args) => {
|
|
281
|
+
// `sweep` is async; route it to the async sibling so the sync dispatcher
|
|
282
|
+
// path doesn't return a CommandResult | Promise<CommandResult> union.
|
|
283
|
+
if (args[1] === 'sweep') return wrap(cmdCiWatchSweep)(args[2], args.slice(3))
|
|
284
|
+
return wrap(cmdCiWatch)(args[1], args[2], args.slice(3))
|
|
285
|
+
},
|
|
265
286
|
'plan-approval': (args) => wrap(cmdPlanApproval)(args[1], args[2], args.slice(3)),
|
|
266
287
|
|
|
267
288
|
// ---- Pocock adoption (ST-5, ST-6, ST-10, ST-13) artifact writers + discoverability ----
|
|
@@ -22,10 +22,13 @@ const gate = {
|
|
|
22
22
|
]
|
|
23
23
|
const result = checkGitignoreDrift(ctx.sharedRoot)
|
|
24
24
|
if (result.drifted) {
|
|
25
|
+
// BUG-028: surface the unified-diff summary in the reason so
|
|
26
|
+
// users see exactly which template lines drifted without
|
|
27
|
+
// re-running `apt-tools config upgrade-gitignore --dry-run`.
|
|
25
28
|
return {
|
|
26
29
|
status: 'fail',
|
|
27
|
-
reason: '.aperant/.gitignore has drifted from policy-expected template',
|
|
28
|
-
evidence: {
|
|
30
|
+
reason: result.reason ?? '.aperant/.gitignore has drifted from policy-expected template',
|
|
31
|
+
evidence: { diff: result.diff ?? null },
|
|
29
32
|
inputs,
|
|
30
33
|
}
|
|
31
34
|
}
|
|
@@ -3,24 +3,27 @@
|
|
|
3
3
|
* G5 review-clean — blocking gate for `apt:ship`.
|
|
4
4
|
*
|
|
5
5
|
* Skipped when {task_dir}/review.json is absent (review is optional for some
|
|
6
|
-
* tracks). When it is present,
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* reviewer format — we accept either.
|
|
6
|
+
* tracks). When it is present, delegates to `parseReview` (DRY with the
|
|
7
|
+
* consistency rule) which normalizes severities + statuses across all the
|
|
8
|
+
* shapes the reviewer skill writes: `fixed: true`, `resolved: true`,
|
|
9
|
+
* `status: "fixed"` (string), and `status: "deferred-*"` variants
|
|
10
|
+
* (BUG-009). Fails if any post-normalization finding is severity
|
|
11
|
+
* critical|major AND status is neither `fixed` nor `deferred`.
|
|
13
12
|
*/
|
|
14
13
|
|
|
15
14
|
import { existsSync, readFileSync } from 'node:fs'
|
|
16
15
|
import { join } from 'node:path'
|
|
16
|
+
import { parseReview } from '../../consistency/parse-review.mjs'
|
|
17
17
|
import { registerGate } from '../registry.mjs'
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
// Post-normalization severities. `parseReview` maps `warning → major`,
|
|
20
|
+
// so the gate keys off `major` here even though the input may carry
|
|
21
|
+
// `warning`.
|
|
22
|
+
const POST_NORM_BLOCKING = new Set(['critical', 'major'])
|
|
20
23
|
|
|
21
24
|
const gate = {
|
|
22
25
|
id: 'review-clean',
|
|
23
|
-
description: 'review.json has no unresolved critical/
|
|
26
|
+
description: 'review.json has no unresolved critical/major findings (skipped if absent)',
|
|
24
27
|
severity: 'blocking',
|
|
25
28
|
skills: ['ship'],
|
|
26
29
|
async check(ctx) {
|
|
@@ -43,23 +46,25 @@ const gate = {
|
|
|
43
46
|
inputs,
|
|
44
47
|
}
|
|
45
48
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
49
|
+
// BUG-009: delegate to parseReview so this gate sees the SAME
|
|
50
|
+
// normalized findings the consistency rule does. The pre-fix
|
|
51
|
+
// inline filter only inspected boolean `fixed`/`resolved`, missing
|
|
52
|
+
// `status: "fixed"` strings + `summary.findings_remaining: 0`
|
|
53
|
+
// shapes the reviewer skill writes; this delegation closes that
|
|
54
|
+
// hole and picks up `deferred-*` variants for free.
|
|
55
|
+
const { findings: normalized } = parseReview(doc)
|
|
56
|
+
const unresolved = normalized.filter(
|
|
57
|
+
(f) => POST_NORM_BLOCKING.has(f.severity) && f.status !== 'fixed' && f.status !== 'deferred',
|
|
53
58
|
)
|
|
54
59
|
if (unresolved.length > 0) {
|
|
55
60
|
return {
|
|
56
61
|
status: 'fail',
|
|
57
|
-
reason: `${unresolved.length} unresolved critical/
|
|
62
|
+
reason: `${unresolved.length} unresolved critical/major finding(s) in review.json`,
|
|
58
63
|
evidence: {
|
|
59
64
|
unresolved: unresolved.map((f) => ({
|
|
60
65
|
severity: f.severity,
|
|
61
66
|
id: f.id ?? null,
|
|
62
|
-
title: f.title ??
|
|
67
|
+
title: f.title ?? null,
|
|
63
68
|
})),
|
|
64
69
|
},
|
|
65
70
|
inputs,
|
|
@@ -67,7 +72,7 @@ const gate = {
|
|
|
67
72
|
}
|
|
68
73
|
return {
|
|
69
74
|
status: 'pass',
|
|
70
|
-
reason: `review.json has no unresolved critical/
|
|
75
|
+
reason: `review.json has no unresolved critical/major findings (${normalized.length} total)`,
|
|
71
76
|
inputs,
|
|
72
77
|
}
|
|
73
78
|
},
|
|
@@ -3,22 +3,54 @@
|
|
|
3
3
|
* G4 verify-approved — blocking gate for `apt:ship`.
|
|
4
4
|
*
|
|
5
5
|
* Requires {task_dir}/qa_signoff.json to exist, parse, and carry
|
|
6
|
-
* verdict === "approved"
|
|
7
|
-
*
|
|
6
|
+
* verdict === "approved" (BUG-013: normalized via parseQaSignoff — case
|
|
7
|
+
* and underscore/hyphen variants collapse to canonical). BUG-012: also
|
|
8
|
+
* accepts `approved-with-notes` when config.qa.approved_with_notes_threshold
|
|
9
|
+
* is set AND every dimension score meets the threshold (best-effort when
|
|
10
|
+
* dimensions are missing or in an unknown shape).
|
|
8
11
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* but only the string "approved" passes — "approved-with-notes" fails
|
|
12
|
-
* because ship-time deserves the strongest signal.
|
|
12
|
+
* Accepts both `verdict` (newer) and `status` (older) field names — that
|
|
13
|
+
* normalization is delegated to parseQaSignoff.
|
|
13
14
|
*/
|
|
14
15
|
|
|
15
16
|
import { existsSync, readFileSync } from 'node:fs'
|
|
16
17
|
import { join } from 'node:path'
|
|
18
|
+
import { parseQaSignoff } from '../../consistency/parse-qa.mjs'
|
|
17
19
|
import { registerGate } from '../registry.mjs'
|
|
18
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Extract dimension scores from the qa_signoff.json shape. We've seen two
|
|
23
|
+
* shapes in the wild:
|
|
24
|
+
* - Array: `dimensions: [{name: 'security', score: 0.9}, ...]`
|
|
25
|
+
* - Object: `dimensions: {security: 0.9, performance: 0.85}` OR
|
|
26
|
+
* `dimensions: {security: {score: 0.9}, ...}`
|
|
27
|
+
* Returns an array of numeric scores, or null when no recognizable shape
|
|
28
|
+
* is present (best-effort accept per brief).
|
|
29
|
+
*/
|
|
30
|
+
function extractDimensionScores(doc) {
|
|
31
|
+
const dims = doc.dimensions ?? doc.qa?.dimensions ?? null
|
|
32
|
+
if (!dims) return null
|
|
33
|
+
if (Array.isArray(dims)) {
|
|
34
|
+
const scores = dims
|
|
35
|
+
.map((d) => (typeof d?.score === 'number' ? d.score : null))
|
|
36
|
+
.filter((s) => s !== null)
|
|
37
|
+
return scores.length > 0 ? scores : null
|
|
38
|
+
}
|
|
39
|
+
if (typeof dims === 'object') {
|
|
40
|
+
const scores = []
|
|
41
|
+
for (const v of Object.values(dims)) {
|
|
42
|
+
if (typeof v === 'number') scores.push(v)
|
|
43
|
+
else if (typeof v?.score === 'number') scores.push(v.score)
|
|
44
|
+
}
|
|
45
|
+
return scores.length > 0 ? scores : null
|
|
46
|
+
}
|
|
47
|
+
return null
|
|
48
|
+
}
|
|
49
|
+
|
|
19
50
|
const gate = {
|
|
20
51
|
id: 'verify-approved',
|
|
21
|
-
description:
|
|
52
|
+
description:
|
|
53
|
+
'qa_signoff.json exists with verdict === "approved" (or "approved-with-notes" when configured)',
|
|
22
54
|
severity: 'blocking',
|
|
23
55
|
skills: ['ship'],
|
|
24
56
|
async check(ctx) {
|
|
@@ -41,18 +73,69 @@ const gate = {
|
|
|
41
73
|
inputs,
|
|
42
74
|
}
|
|
43
75
|
}
|
|
44
|
-
|
|
45
|
-
|
|
76
|
+
|
|
77
|
+
// BUG-013: route through parseQaSignoff so the verdict is
|
|
78
|
+
// case-insensitive AND tolerates `_↔-` (so 'APPROVED', 'Approved',
|
|
79
|
+
// ' approved ', 'APPROVED_WITH_NOTES' all behave canonically). The
|
|
80
|
+
// pre-fix gate compared the raw string against `'approved'` which
|
|
81
|
+
// false-rejected every legitimate variant.
|
|
82
|
+
const { verdict } = parseQaSignoff(doc)
|
|
83
|
+
|
|
84
|
+
if (verdict === 'approved') {
|
|
46
85
|
return {
|
|
47
|
-
status: '
|
|
48
|
-
reason:
|
|
49
|
-
evidence: { verdict },
|
|
86
|
+
status: 'pass',
|
|
87
|
+
reason: 'qa_signoff.json verdict normalized to "approved"',
|
|
50
88
|
inputs,
|
|
51
89
|
}
|
|
52
90
|
}
|
|
91
|
+
|
|
92
|
+
// BUG-012: accept `approved-with-notes` when the project has opted
|
|
93
|
+
// in via config.qa.approved_with_notes_threshold. The threshold is
|
|
94
|
+
// the minimum per-dimension score required. Best-effort: when
|
|
95
|
+
// dimensions are missing / in an unknown shape we still accept and
|
|
96
|
+
// log, rather than hard-failing, so projects that haven't started
|
|
97
|
+
// scoring dimensions yet aren't blocked.
|
|
98
|
+
if (verdict === 'approved-with-notes') {
|
|
99
|
+
const threshold = ctx.config?.qa?.approved_with_notes_threshold
|
|
100
|
+
if (typeof threshold !== 'number') {
|
|
101
|
+
return {
|
|
102
|
+
status: 'fail',
|
|
103
|
+
reason:
|
|
104
|
+
'qa_signoff.json verdict is "approved-with-notes" but config.qa.approved_with_notes_threshold is not set',
|
|
105
|
+
evidence: { verdict, threshold: threshold ?? null },
|
|
106
|
+
inputs,
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const scores = extractDimensionScores(doc)
|
|
110
|
+
if (scores === null) {
|
|
111
|
+
return {
|
|
112
|
+
status: 'pass',
|
|
113
|
+
reason: `qa_signoff.json verdict is "approved-with-notes" and threshold ${threshold} is enabled; dimensions missing or unknown-shape — accepting best-effort`,
|
|
114
|
+
evidence: { verdict, threshold, dimensions_present: false },
|
|
115
|
+
inputs,
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const failing = scores.filter((s) => s < threshold)
|
|
119
|
+
if (failing.length > 0) {
|
|
120
|
+
return {
|
|
121
|
+
status: 'fail',
|
|
122
|
+
reason: `qa_signoff.json verdict is "approved-with-notes" but ${failing.length}/${scores.length} dimension score(s) below threshold ${threshold}`,
|
|
123
|
+
evidence: { verdict, threshold, scores, failing },
|
|
124
|
+
inputs,
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
status: 'pass',
|
|
129
|
+
reason: `qa_signoff.json verdict is "approved-with-notes" and all ${scores.length} dimension score(s) meet threshold ${threshold}`,
|
|
130
|
+
evidence: { verdict, threshold, scores },
|
|
131
|
+
inputs,
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
53
135
|
return {
|
|
54
|
-
status: '
|
|
55
|
-
reason:
|
|
136
|
+
status: 'fail',
|
|
137
|
+
reason: `qa_signoff.json verdict is "${verdict}" (expected "approved" or "approved-with-notes" with threshold)`,
|
|
138
|
+
evidence: { verdict },
|
|
56
139
|
inputs,
|
|
57
140
|
}
|
|
58
141
|
},
|
package/src/cli/help.mjs
CHANGED
|
@@ -34,7 +34,7 @@ Commands:
|
|
|
34
34
|
task close-merged <dir> [--task <id>] [--narrate-only] [--dry-run] Sweep shipped-pending-merge tasks for confirmed merges (--narrate-only returns pending_narration[] entries read-only without closing).
|
|
35
35
|
task narration-drain <dir> --task <task-id> Remove a row from state.pending_narration[] under withFileLock (called by /apt:close-task --narrate-only after a successful narrator spawn)
|
|
36
36
|
task complete-subtask <dir> --task-id <id> --subtask-id <sid|a+b+c> [--commit <sha>] [--notes <text>] Atomic plan ↔ build-progress sync
|
|
37
|
-
features-audit <dir> [--diff-files <csv>|--since <ref>] [--apply-stubs] [--format json|md] Audit features registry for unregistered feature-qualifying files
|
|
37
|
+
features-audit <dir> [--task-id <id>] [--no-worktree-redirect] [--diff-files <csv>|--since <ref>] [--apply-stubs] [--format json|md] Audit features registry for unregistered feature-qualifying files (BUG-025b: --task-id redirects writes to active task's worktree)
|
|
38
38
|
docs-audit <dir> [--diff-files <csv>|--since <ref>|--dry-run] [--policy <path>] [--format json|md] Audit documented surfaces for docs-drift
|
|
39
39
|
|
|
40
40
|
lock claim <dir> --subtask <id> Claim exclusive lock on a subtask
|
|
@@ -110,7 +110,9 @@ Commands:
|
|
|
110
110
|
|
|
111
111
|
migrate-pr-reviews <project-dir> Migrate legacy .aperant/reviews/ → .aperant/pr-reviews/pr-{N}/rounds/{R}/ (idempotent)
|
|
112
112
|
|
|
113
|
-
validate-evidence <findings-dir> --worktree <path> Enforce the Evidence Rule (literal substring + multi-file check); mutates findings markdown in place
|
|
113
|
+
validate-evidence <findings-dir> --worktree <path> [--allow-approx-evidence] [--approx-floor <n>] [--llm-validator-confirmed] Enforce the Evidence Rule (literal substring + multi-file check); mutates findings markdown in place. BUG-014: --allow-approx-evidence opts into Jaccard + line-anchor fallback (downgrade to dismissed_evidence_approx_confirmed unless --llm-validator-confirmed promotes to confirmed_valid_approx).
|
|
114
|
+
|
|
115
|
+
vitest doctor <project-dir> [--format json|md] BUG-026: walk vitest.config.{ts,js,mjs} files, report NODE_ENV/jsxAutomaticShim matrix, warn when JSX-rendering tests lack jsxAutomaticShim coverage (exits 1 on warnings)
|
|
114
116
|
|
|
115
117
|
wfrun validate <workflow.yaml> C17: parse + schema-check a declarative YAML workflow (exits 0 on success)
|
|
116
118
|
wfrun plan <workflow.yaml> [--input k=v …] C17: resolve DAG waves + inputs, emit run-plan JSON (no side effects)
|
|
@@ -126,6 +128,9 @@ Commands:
|
|
|
126
128
|
pr-review record-comment <dir> --pr <N> --round <R> --url <url> --body-hash <sha256> [--review-id <id>] [--posted-at <iso>] Append entry to comment_timeline (Phase 6b)
|
|
127
129
|
pr-review audit-fixer <dir> --review-dir <path> --iteration <N> --fixer <name> --worktree <path> --status-line <str> Audit fixer output against git state; exits 0 with JSON envelope (verdict: verified|hallucinated|skipped)
|
|
128
130
|
|
|
131
|
+
ci-watch start|stop|status|tick <dir> [--pr <N>] [...] /apt:watch-ci primitives — see SKILL.md for full flag matrix
|
|
132
|
+
ci-watch sweep <dir> --orphaned [--dry-run] BUG-027: enumerate ci-watches state files, query gh per PR, remove terminal-state (MERGED|CLOSED) entries (manual GC; rate-limited)
|
|
133
|
+
|
|
129
134
|
Flags:
|
|
130
135
|
--version Print version and exit
|
|
131
136
|
--help Print usage and exit
|
|
@@ -33,6 +33,11 @@ import { MANIFEST_FILENAME } from './manifest.mjs'
|
|
|
33
33
|
* target mapped to a host CLI (e.g. `pi`,
|
|
34
34
|
* which has a detectable host but no apt
|
|
35
35
|
* install root of its own).
|
|
36
|
+
* @property {string} [displayLabel] Optional user-facing label (e.g. "Kilo Code").
|
|
37
|
+
* When unset, callers fall back to the
|
|
38
|
+
* capitalized form of `id`. Use for runtimes
|
|
39
|
+
* whose product branding differs from the
|
|
40
|
+
* internal id (C58: `kilo` → "Kilo Code").
|
|
36
41
|
*/
|
|
37
42
|
|
|
38
43
|
/** @type {RuntimeDescriptor[]} */
|
|
@@ -78,6 +83,10 @@ export const RUNTIMES = Object.freeze([
|
|
|
78
83
|
installRoot: '.kilo',
|
|
79
84
|
markers: ['.kilo/config.json'],
|
|
80
85
|
cliId: 'kilo',
|
|
86
|
+
// C58 (0.7.2): user-visible label. Internal id stays `kilo` (zero
|
|
87
|
+
// migration per spec ID-01). `--kilocode` is also accepted as a
|
|
88
|
+
// deprecated alias of `--kilo` at the CLI flag parser layer.
|
|
89
|
+
displayLabel: 'Kilo Code',
|
|
81
90
|
},
|
|
82
91
|
{
|
|
83
92
|
id: 'copilot',
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
6
6
|
import { join } from 'node:path'
|
|
7
|
+
import { loadMergedProjectConfig } from '../config/load.mjs'
|
|
7
8
|
import { withFileLock } from '../util/fs-lock.mjs'
|
|
8
9
|
import { formatDateDisplay, formatTimeDisplay } from './format.mjs'
|
|
9
10
|
|
|
@@ -15,8 +16,20 @@ export function regenerateTaskIndex(targetDir) {
|
|
|
15
16
|
const statePath = join(targetDir, '.aperant', 'state.json')
|
|
16
17
|
if (!existsSync(statePath)) return
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
// AUDIT-001: read through the merged loader so per-device
|
|
20
|
+
// preferences.date_format / preferences.time_format overrides land
|
|
21
|
+
// even when the shared config.json no longer carries those fields.
|
|
22
|
+
// Guard: malformed config.local.json must not crash every task state
|
|
23
|
+
// transition — fall back to shared-config-only read and warn on stderr.
|
|
24
|
+
let config
|
|
25
|
+
try {
|
|
26
|
+
config = loadMergedProjectConfig(targetDir) ?? {}
|
|
27
|
+
} catch (e) {
|
|
28
|
+
process.stderr.write(
|
|
29
|
+
`[index-md] config.local.json parse error — using shared config only: ${e.message}\n`,
|
|
30
|
+
)
|
|
31
|
+
config = {}
|
|
32
|
+
}
|
|
20
33
|
if (config.preferences?.task_index === false) return
|
|
21
34
|
|
|
22
35
|
const state = JSON.parse(readFileSync(statePath, 'utf-8'))
|
package/templates/config.json
CHANGED
|
@@ -39,7 +39,6 @@
|
|
|
39
39
|
"safe_paths": ["**/*.css", "**/*.md", "**/*.test.*", "**/*.spec.*"]
|
|
40
40
|
},
|
|
41
41
|
"verification": {
|
|
42
|
-
"open_report": true,
|
|
43
42
|
"report_formats": ["html", "md"],
|
|
44
43
|
"evidence_capture": true,
|
|
45
44
|
"max_iterations": 3,
|
|
@@ -47,8 +46,7 @@
|
|
|
47
46
|
},
|
|
48
47
|
"multi_model": {
|
|
49
48
|
"cross_verification": {
|
|
50
|
-
"enabled": false
|
|
51
|
-
"tools": []
|
|
49
|
+
"enabled": false
|
|
52
50
|
},
|
|
53
51
|
"video_review": {
|
|
54
52
|
"enabled": false,
|
|
@@ -92,14 +90,11 @@
|
|
|
92
90
|
"cleanup_on_close": "ask"
|
|
93
91
|
},
|
|
94
92
|
"preferences": {
|
|
95
|
-
"date_format": "YY-MM-DD",
|
|
96
|
-
"time_format": "24h",
|
|
97
93
|
"task_index": true,
|
|
98
94
|
"health_check": {
|
|
99
95
|
"enabled": false,
|
|
100
96
|
"interval_minutes": 30
|
|
101
|
-
}
|
|
102
|
-
"quick_task_post_verify": "ask"
|
|
97
|
+
}
|
|
103
98
|
},
|
|
104
99
|
"orchestration": {
|
|
105
100
|
"phase_delegation": "auto",
|
|
@@ -21,10 +21,15 @@ OUTPUT FORMAT: verification.json (NOT VERIFICATION.md). JSON is the source of tr
|
|
|
21
21
|
<step name="load_config" priority="first">
|
|
22
22
|
## 1. Load Config and Context
|
|
23
23
|
|
|
24
|
-
Read `.aperant/config.json`
|
|
24
|
+
Read the merged Aperant config for verification preferences (AUDIT-001 — the per-device `verification.open_report` field lives in `.aperant/config.local.json`; the rest in shared `.aperant/config.json`). Use the framework's merged loader so local overrides win:
|
|
25
25
|
```bash
|
|
26
|
-
|
|
26
|
+
node .aperant/deps/node_modules/@aperant/framework/bin/apt-tools.mjs config read . 2>/dev/null || echo "{}"
|
|
27
27
|
```
|
|
28
|
+
If `apt-tools config read` is unavailable, fall back to overlaying the two files in JS:
|
|
29
|
+
```bash
|
|
30
|
+
node -e "const fs=require('fs'); const s=(p)=>{try{return JSON.parse(fs.readFileSync(p,'utf-8'))}catch{return {}}}; function merge(a,b){if(a===null||typeof a!=='object'||Array.isArray(a))return b;if(b===null||typeof b!=='object'||Array.isArray(b))return b;const out={...a};for(const k of Object.keys(b))out[k]=(k in a)?merge(a[k],b[k]):b[k];return out}; const merged=merge(s('.aperant/config.json'),s('.aperant/config.local.json')); console.log(JSON.stringify(merged))"
|
|
31
|
+
```
|
|
32
|
+
Do NOT read `.aperant/config.json` directly — that misses per-device overrides like `verification.open_report` and `multi_model.cross_verification.tools`.
|
|
28
33
|
|
|
29
34
|
Read project constitution:
|
|
30
35
|
```bash
|
|
@@ -396,7 +401,7 @@ ln -sf "${PROOF_DIR}/report.html" .aperant/proof-report.html
|
|
|
396
401
|
<step name="open_report">
|
|
397
402
|
## 11. Open Report
|
|
398
403
|
|
|
399
|
-
|
|
404
|
+
Read `verification.open_report` from the MERGED config (per-device override lives in `.aperant/config.local.json` per AUDIT-001; do not read raw `.aperant/config.json`). If true (default):
|
|
400
405
|
```bash
|
|
401
406
|
open "${PROOF_DIR}/report.html"
|
|
402
407
|
```
|