@aperant/framework 0.6.6 → 0.7.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 +215 -0
- package/dist/cli/commands/check-version.d.mts.map +1 -1
- package/dist/cli/commands/check-version.mjs +76 -1
- package/dist/cli/commands/check-version.mjs.map +1 -1
- package/dist/cli/commands/ci-watch.d.mts.map +1 -1
- package/dist/cli/commands/ci-watch.mjs +73 -5
- package/dist/cli/commands/ci-watch.mjs.map +1 -1
- package/dist/cli/commands/health-check.mjs +1 -1
- package/dist/cli/commands/health-check.mjs.map +1 -1
- package/dist/cli/commands/init.d.mts +17 -1
- package/dist/cli/commands/init.d.mts.map +1 -1
- package/dist/cli/commands/init.mjs +183 -11
- package/dist/cli/commands/init.mjs.map +1 -1
- package/dist/cli/commands/release-notes.d.mts +11 -0
- package/dist/cli/commands/release-notes.d.mts.map +1 -0
- package/dist/cli/commands/release-notes.mjs +173 -0
- package/dist/cli/commands/release-notes.mjs.map +1 -0
- package/dist/cli/commands/route.d.mts.map +1 -1
- package/dist/cli/commands/route.mjs +37 -2
- package/dist/cli/commands/route.mjs.map +1 -1
- package/dist/cli/commands/task.d.mts.map +1 -1
- package/dist/cli/commands/task.mjs +48 -0
- package/dist/cli/commands/task.mjs.map +1 -1
- package/dist/cli/config/load.d.mts +14 -0
- package/dist/cli/config/load.d.mts.map +1 -1
- package/dist/cli/config/load.mjs +40 -0
- package/dist/cli/config/load.mjs.map +1 -1
- package/dist/cli/config/upgrade-gitignore.d.mts.map +1 -1
- package/dist/cli/config/upgrade-gitignore.mjs +6 -0
- package/dist/cli/config/upgrade-gitignore.mjs.map +1 -1
- package/dist/cli/consistency/parse-review.d.mts.map +1 -1
- package/dist/cli/consistency/parse-review.mjs +4 -1
- package/dist/cli/consistency/parse-review.mjs.map +1 -1
- package/dist/cli/dispatch.d.mts.map +1 -1
- package/dist/cli/dispatch.mjs +2 -0
- package/dist/cli/dispatch.mjs.map +1 -1
- package/dist/cli/gate/gates/review-clean.d.mts.map +1 -1
- package/dist/cli/gate/gates/review-clean.mjs +4 -2
- package/dist/cli/gate/gates/review-clean.mjs.map +1 -1
- package/dist/cli/help.d.mts.map +1 -1
- package/dist/cli/help.mjs +1 -0
- package/dist/cli/help.mjs.map +1 -1
- package/dist/cli/host/detect.d.mts +122 -9
- package/dist/cli/host/detect.d.mts.map +1 -1
- package/dist/cli/host/detect.mjs +132 -0
- package/dist/cli/host/detect.mjs.map +1 -1
- package/dist/cli/install/legacy-paths.d.mts +38 -0
- package/dist/cli/install/legacy-paths.d.mts.map +1 -0
- package/dist/cli/install/legacy-paths.mjs +69 -0
- package/dist/cli/install/legacy-paths.mjs.map +1 -0
- package/dist/cli/install/runtime-migrate.d.mts +84 -0
- package/dist/cli/install/runtime-migrate.d.mts.map +1 -0
- package/dist/cli/install/runtime-migrate.mjs +244 -0
- package/dist/cli/install/runtime-migrate.mjs.map +1 -0
- package/dist/cli/install/update-chips.d.mts +23 -0
- package/dist/cli/install/update-chips.d.mts.map +1 -1
- package/dist/cli/install/update-chips.mjs +60 -0
- package/dist/cli/install/update-chips.mjs.map +1 -1
- package/dist/cli/release-notes/compile.d.mts +38 -0
- package/dist/cli/release-notes/compile.d.mts.map +1 -0
- package/dist/cli/release-notes/compile.mjs +244 -0
- package/dist/cli/release-notes/compile.mjs.map +1 -0
- package/dist/cli/release-notes/draft.d.mts +73 -0
- package/dist/cli/release-notes/draft.d.mts.map +1 -0
- package/dist/cli/release-notes/draft.mjs +120 -0
- package/dist/cli/release-notes/draft.mjs.map +1 -0
- package/dist/cli/release-notes/output-dir.d.mts +20 -0
- package/dist/cli/release-notes/output-dir.d.mts.map +1 -0
- package/dist/cli/release-notes/output-dir.mjs +42 -0
- package/dist/cli/release-notes/output-dir.mjs.map +1 -0
- package/dist/cli/release-notes/persona-filter.d.mts +51 -0
- package/dist/cli/release-notes/persona-filter.d.mts.map +1 -0
- package/dist/cli/release-notes/persona-filter.mjs +127 -0
- package/dist/cli/release-notes/persona-filter.mjs.map +1 -0
- package/dist/cli/release-notes/publish.d.mts +23 -0
- package/dist/cli/release-notes/publish.d.mts.map +1 -0
- package/dist/cli/release-notes/publish.mjs +125 -0
- package/dist/cli/release-notes/publish.mjs.map +1 -0
- package/dist/cli/release-notes/ship-autodraft.d.mts +37 -0
- package/dist/cli/release-notes/ship-autodraft.d.mts.map +1 -0
- package/dist/cli/release-notes/ship-autodraft.mjs +97 -0
- package/dist/cli/release-notes/ship-autodraft.mjs.map +1 -0
- package/dist/cli/route/drift-detect.d.mts +20 -0
- package/dist/cli/route/drift-detect.d.mts.map +1 -0
- package/dist/cli/route/drift-detect.mjs +107 -0
- package/dist/cli/route/drift-detect.mjs.map +1 -0
- package/dist/cli/util/aperant-section.d.mts +34 -0
- package/dist/cli/util/aperant-section.d.mts.map +1 -0
- package/dist/cli/util/aperant-section.mjs +127 -0
- package/dist/cli/util/aperant-section.mjs.map +1 -0
- package/dist/cli/util/copy.d.mts +28 -1
- package/dist/cli/util/copy.d.mts.map +1 -1
- package/dist/cli/util/copy.mjs +43 -55
- package/dist/cli/util/copy.mjs.map +1 -1
- package/dist/cli/util/semver.d.mts +17 -0
- package/dist/cli/util/semver.d.mts.map +1 -0
- package/dist/cli/util/semver.mjs +29 -0
- package/dist/cli/util/semver.mjs.map +1 -0
- package/dist/cli/util/skill-installs.d.mts +65 -9
- package/dist/cli/util/skill-installs.d.mts.map +1 -1
- package/dist/cli/util/skill-installs.mjs +130 -21
- package/dist/cli/util/skill-installs.mjs.map +1 -1
- package/dist/cli/util/version-preflight.d.mts +44 -0
- package/dist/cli/util/version-preflight.d.mts.map +1 -0
- package/dist/cli/util/version-preflight.mjs +66 -0
- package/dist/cli/util/version-preflight.mjs.map +1 -0
- package/dist/types/config.d.ts +11 -7
- package/dist/types/config.d.ts.map +1 -1
- package/package.json +133 -133
- package/skills/apt-close-task/SKILL.md +30 -0
- package/skills/apt-diagram/SKILL.md +45 -9
- package/skills/apt-release-notes/SKILL.md +193 -0
- package/skills/apt-release-notes/appendices/persona-voice.md +59 -0
- package/skills/apt-setup/SKILL.md +146 -3
- package/skills/apt-ship/SKILL.md +63 -4
- package/skills/apt-spar/SKILL.md +36 -11
- package/skills/apt-update/SKILL.md +77 -10
- package/skills/apt-watch-ci/SKILL.md +6 -3
- package/src/cli/commands/check-version.mjs +73 -1
- package/src/cli/commands/ci-watch.mjs +74 -5
- package/src/cli/commands/health-check.mjs +1 -1
- package/src/cli/commands/init.mjs +202 -10
- package/src/cli/commands/release-notes.mjs +187 -0
- package/src/cli/commands/route.mjs +38 -2
- package/src/cli/commands/task.mjs +49 -0
- package/src/cli/config/load.mjs +37 -0
- package/src/cli/config/upgrade-gitignore.mjs +6 -0
- package/src/cli/consistency/parse-review.mjs +3 -1
- package/src/cli/dispatch.mjs +2 -0
- package/src/cli/gate/gates/review-clean.mjs +4 -2
- package/src/cli/help.mjs +1 -0
- package/src/cli/host/detect.mjs +135 -0
- package/src/cli/install/legacy-paths.mjs +69 -0
- package/src/cli/install/runtime-migrate.mjs +252 -0
- package/src/cli/install/update-chips.mjs +57 -0
- package/src/cli/release-notes/compile.mjs +261 -0
- package/src/cli/release-notes/draft.mjs +133 -0
- package/src/cli/release-notes/output-dir.mjs +52 -0
- package/src/cli/release-notes/persona-filter.mjs +126 -0
- package/src/cli/release-notes/publish.mjs +128 -0
- package/src/cli/release-notes/ship-autodraft.mjs +106 -0
- package/src/cli/route/drift-detect.mjs +107 -0
- package/src/cli/util/aperant-section.mjs +136 -0
- package/src/cli/util/copy.mjs +43 -56
- package/src/cli/util/semver.mjs +28 -0
- package/src/cli/util/skill-installs.mjs +134 -21
- package/src/cli/util/version-preflight.mjs +65 -0
- package/templates/aperant-claude-md-appendix.md +37 -0
- package/templates/config.json +28 -3
- package/workflows/docs.md +12 -0
|
@@ -16,7 +16,26 @@
|
|
|
16
16
|
* stale_files: [{ path, reason }],
|
|
17
17
|
* update_available: false
|
|
18
18
|
* }],
|
|
19
|
-
* legacy_install: false
|
|
19
|
+
* legacy_install: false, // true when only the old .apt-version exists
|
|
20
|
+
* // === 0.6.7 cache-merge fields (Bug 1 fix) ===
|
|
21
|
+
* // Merged from the SessionStart worker's cache at
|
|
22
|
+
* // ~/.cache/aperant/apt-update-check.json when fresh (< 1 h,
|
|
23
|
+
* // schema_version=1). When stale, missing, or malformed, the same
|
|
24
|
+
* // field names are present with null/false/0 sentinel values so
|
|
25
|
+
* // the skill's parser stays single-shape.
|
|
26
|
+
* //
|
|
27
|
+
* // Note: framework.installed_version is intentionally absent.
|
|
28
|
+
* // The live kernel version is always in the top-level
|
|
29
|
+
* // `framework_version` field (read from package.json, never from
|
|
30
|
+
* // the cache).
|
|
31
|
+
* framework: {
|
|
32
|
+
* latest_version: '0.6.7' | null
|
|
33
|
+
* },
|
|
34
|
+
* update_available: false,
|
|
35
|
+
* dev_install_ahead: false,
|
|
36
|
+
* not_published: false,
|
|
37
|
+
* stale_files_total: 0,
|
|
38
|
+
* lookup_error: null
|
|
20
39
|
* }
|
|
21
40
|
*
|
|
22
41
|
* Per-file `reason` values:
|
|
@@ -31,6 +50,7 @@ import { dirname, join, resolve } from 'node:path'
|
|
|
31
50
|
import { fileURLToPath } from 'node:url'
|
|
32
51
|
import { readManifest } from '../install/manifest.mjs'
|
|
33
52
|
import { detectInstalledRuntimes } from '../install/runtime-detect.mjs'
|
|
53
|
+
import { readFreshUpdateCache } from '../install/update-chips.mjs'
|
|
34
54
|
import { inspectManifestFiles } from '../install/version-header.mjs'
|
|
35
55
|
import { err, ok } from '../util/result.mjs'
|
|
36
56
|
|
|
@@ -56,6 +76,10 @@ function inspectRuntime(targetDir, descriptor) {
|
|
|
56
76
|
runtime: descriptor.id,
|
|
57
77
|
installed_version: null,
|
|
58
78
|
stale_files: [],
|
|
79
|
+
// 0.6.7 — per-runtime `update_available` is retained as `false` for
|
|
80
|
+
// backward shape compatibility with pre-0.6.7 callers. The real
|
|
81
|
+
// npm-bump signal now lives on the TOP-LEVEL `update_available`
|
|
82
|
+
// field (merged from the SessionStart worker's cache below).
|
|
59
83
|
update_available: false,
|
|
60
84
|
}
|
|
61
85
|
}
|
|
@@ -67,10 +91,14 @@ function inspectRuntime(targetDir, descriptor) {
|
|
|
67
91
|
runtime: descriptor.id,
|
|
68
92
|
installed_version: manifest.framework_version,
|
|
69
93
|
stale_files: stale,
|
|
94
|
+
// See note above — top-level `update_available` carries the real signal.
|
|
70
95
|
update_available: false,
|
|
71
96
|
}
|
|
72
97
|
}
|
|
73
98
|
|
|
99
|
+
/** Cache freshness window — 1 hour, matches the worker's CACHE_TTL_MS. */
|
|
100
|
+
const CACHE_TTL_MS = 60 * 60 * 1_000
|
|
101
|
+
|
|
74
102
|
/**
|
|
75
103
|
* @param {string} projectDir
|
|
76
104
|
*/
|
|
@@ -106,6 +134,39 @@ export function cmdCheckVersion(projectDir) {
|
|
|
106
134
|
messageParts.push(`Skills are up to date for ${runtimes.length} runtime(s).`)
|
|
107
135
|
}
|
|
108
136
|
|
|
137
|
+
// 0.6.7 (Bug 1 fix) — merge the SessionStart worker's cache fields when
|
|
138
|
+
// the cache file is fresh (< 1 h, schema_version=1). On a stale / missing
|
|
139
|
+
// / malformed cache the same field names are emitted with null/false/0
|
|
140
|
+
// sentinel values so the skill's parser stays single-shape (ID-03).
|
|
141
|
+
//
|
|
142
|
+
// LOG-001: stale_files_total is always derived from the live per-runtime
|
|
143
|
+
// walk (totalStale). The cache value is intentionally NOT merged — it is
|
|
144
|
+
// up to 1 h old and would hide drift that occurred since the last worker
|
|
145
|
+
// run.
|
|
146
|
+
//
|
|
147
|
+
// LOG-002: installed_version always comes from the kernel's own
|
|
148
|
+
// package.json (frameworkVersion). The cache's installed_version is
|
|
149
|
+
// stale by definition and is NOT merged into the envelope. Consumers
|
|
150
|
+
// that need the live kernel version use the top-level `framework_version`
|
|
151
|
+
// field, which is identical.
|
|
152
|
+
const cacheResult = readFreshUpdateCache(CACHE_TTL_MS)
|
|
153
|
+
let latestVersion = null
|
|
154
|
+
let updateAvailable = false
|
|
155
|
+
let devInstallAhead = false
|
|
156
|
+
let notPublished = false
|
|
157
|
+
let lookupError = null
|
|
158
|
+
if (cacheResult.fresh) {
|
|
159
|
+
const c = cacheResult.cache
|
|
160
|
+
const fw = c.framework && typeof c.framework === 'object' ? c.framework : {}
|
|
161
|
+
// latest_version is semver-validated inside readFreshUpdateCache (SEC-001).
|
|
162
|
+
if (typeof fw.latest_version === 'string') latestVersion = fw.latest_version
|
|
163
|
+
if (typeof c.update_available === 'boolean') updateAvailable = c.update_available
|
|
164
|
+
if (typeof c.dev_install_ahead === 'boolean') devInstallAhead = c.dev_install_ahead
|
|
165
|
+
if (typeof c.not_published === 'boolean') notPublished = c.not_published
|
|
166
|
+
// stale_files_total intentionally NOT taken from cache (LOG-001).
|
|
167
|
+
if (typeof c.lookup_error === 'string') lookupError = c.lookup_error
|
|
168
|
+
}
|
|
169
|
+
|
|
109
170
|
return ok({
|
|
110
171
|
status,
|
|
111
172
|
command: 'check-version',
|
|
@@ -113,5 +174,16 @@ export function cmdCheckVersion(projectDir) {
|
|
|
113
174
|
runtimes,
|
|
114
175
|
legacy_install: legacyInstall,
|
|
115
176
|
message: messageParts.join(' '),
|
|
177
|
+
// Cache-merge fields (additive, nullable). Documented in the
|
|
178
|
+
// file-header schema comment.
|
|
179
|
+
framework: {
|
|
180
|
+
latest_version: latestVersion,
|
|
181
|
+
},
|
|
182
|
+
update_available: updateAvailable,
|
|
183
|
+
dev_install_ahead: devInstallAhead,
|
|
184
|
+
not_published: notPublished,
|
|
185
|
+
// stale_files_total: always the live sum from per-runtime walks (LOG-001).
|
|
186
|
+
stale_files_total: totalStale,
|
|
187
|
+
lookup_error: lookupError,
|
|
116
188
|
})
|
|
117
189
|
}
|
|
@@ -16,16 +16,43 @@
|
|
|
16
16
|
* reads the gh payload from that file instead of shelling out. The stub
|
|
17
17
|
* shape is `{ checks: [...], prMetadata: {...} }` — matches the classifier
|
|
18
18
|
* input contract in stop-matrix.mjs.
|
|
19
|
+
*
|
|
20
|
+
* Production seam: when `--payload-file <path>` is passed, the tick path
|
|
21
|
+
* reads the gh payload from that file. Same shape as the stub. The
|
|
22
|
+
* orchestrator skill (apt-watch-ci §3) captures `gh pr checks ...` +
|
|
23
|
+
* `gh pr view ...` into the payload file BEFORE invoking the cmd — the cmd
|
|
24
|
+
* itself never shells out to gh.
|
|
19
25
|
*/
|
|
20
26
|
|
|
21
27
|
import { randomBytes } from 'node:crypto'
|
|
22
28
|
import { existsSync, readFileSync } from 'node:fs'
|
|
23
|
-
import { resolve } from 'node:path'
|
|
29
|
+
import { dirname, resolve } from 'node:path'
|
|
30
|
+
import { fileURLToPath } from 'node:url'
|
|
24
31
|
import { acquireLock, releaseLock } from '../ci-watch/lock.mjs'
|
|
25
32
|
import { deleteState, listWatches, readState, writeState } from '../ci-watch/state.mjs'
|
|
26
33
|
import { classify, isFailing } from '../ci-watch/stop-matrix.mjs'
|
|
27
34
|
import { parseFlags } from '../util/args.mjs'
|
|
28
35
|
import { err, exitWith, ok } from '../util/result.mjs'
|
|
36
|
+
import { requireMinVersion } from '../util/version-preflight.mjs'
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Installed kernel version — read once at module scope, same pattern as
|
|
40
|
+
* commands/check-version.mjs:108-109. Used by the version-preflight in
|
|
41
|
+
* `doTick` to fail loud (instead of infra-backoff-loop) when a future
|
|
42
|
+
* framework fix introduces a new tick flag and the user is on a stale
|
|
43
|
+
* install. See FRAMEWORK-BUG-017 rung 2 + the helper docstring for the
|
|
44
|
+
* "future-guard" framing.
|
|
45
|
+
*/
|
|
46
|
+
const INSTALLED_KERNEL_VERSION = (() => {
|
|
47
|
+
try {
|
|
48
|
+
const here = dirname(fileURLToPath(import.meta.url))
|
|
49
|
+
const pkgPath = resolve(here, '..', '..', '..', 'package.json')
|
|
50
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'))
|
|
51
|
+
return typeof pkg?.version === 'string' ? pkg.version : null
|
|
52
|
+
} catch {
|
|
53
|
+
return null
|
|
54
|
+
}
|
|
55
|
+
})()
|
|
29
56
|
|
|
30
57
|
const DEFAULT_TICK_INTERVAL_MS = 300_000 // 5 minutes
|
|
31
58
|
const MAX_TICK_INTERVAL_MS = 30 * 60 * 1000 // 30 min
|
|
@@ -113,12 +140,14 @@ function doStart(root, flags) {
|
|
|
113
140
|
command: 'ci-watch-start',
|
|
114
141
|
pr: Number(pr),
|
|
115
142
|
reason: 'already-armed',
|
|
143
|
+
scheduler_handle: existingState.scheduler_handle ?? null,
|
|
116
144
|
existing: {
|
|
117
145
|
task_id: existingState.task_id,
|
|
118
146
|
host: existingState.host,
|
|
119
147
|
attempts: existingState.attempts,
|
|
120
148
|
started_at: existingState.started_at,
|
|
121
149
|
deadline: existingState.deadline,
|
|
150
|
+
scheduler_handle: existingState.scheduler_handle ?? null,
|
|
122
151
|
},
|
|
123
152
|
},
|
|
124
153
|
2,
|
|
@@ -255,6 +284,21 @@ function summarize(s) {
|
|
|
255
284
|
// ---------------------------------------------------------------------------
|
|
256
285
|
|
|
257
286
|
function doTick(root, flags) {
|
|
287
|
+
// FRAMEWORK-BUG-017 rung 2 (future-guard): emit a structured
|
|
288
|
+
// version_drift envelope when a future framework fix requires a
|
|
289
|
+
// new tick flag and the installed kernel predates it. The current
|
|
290
|
+
// pin to 0.6.7 (the version that introduced --payload-file) is a
|
|
291
|
+
// no-op on every 0.6.7+ install — but the scaffolding is in place
|
|
292
|
+
// for the NEXT new-flag fix to bump `introducedIn` without
|
|
293
|
+
// re-discovering the silent-infra-backoff failure mode.
|
|
294
|
+
const versionGate = requireMinVersion({
|
|
295
|
+
feature: 'payload-file',
|
|
296
|
+
introducedIn: '0.6.7',
|
|
297
|
+
installedVersion: INSTALLED_KERNEL_VERSION,
|
|
298
|
+
missingFlag: '--payload-file',
|
|
299
|
+
})
|
|
300
|
+
if (versionGate) return exitWith(versionGate, 1)
|
|
301
|
+
|
|
258
302
|
const prCheck = validatePr(flags.get('pr'))
|
|
259
303
|
if (typeof prCheck !== 'string') return prCheck
|
|
260
304
|
const pr = prCheck
|
|
@@ -290,7 +334,8 @@ function doTick(root, flags) {
|
|
|
290
334
|
})
|
|
291
335
|
}
|
|
292
336
|
|
|
293
|
-
const
|
|
337
|
+
const payloadFile = flags.get('payload-file')
|
|
338
|
+
const payload = payloadFile ? loadPayloadFile(resolve(root, payloadFile)) : fetchGhPayload(pr)
|
|
294
339
|
if (payload.error) {
|
|
295
340
|
// gh CLI not available / auth failed → infra-backoff. Don't count it,
|
|
296
341
|
// double the next-tick interval (capped at MAX_TICK_INTERVAL_MS), and reschedule.
|
|
@@ -480,10 +525,34 @@ function fetchGhPayload(_pr) {
|
|
|
480
525
|
// Real gh shell-out lives in the skill body. The cmd's tick treats
|
|
481
526
|
// "no stub + no payload" as infra-backoff so unit tests can exercise the
|
|
482
527
|
// path without a live gh binary. The orchestrator skill is responsible
|
|
483
|
-
// for pre-fetching and re-invoking with APT_TEST_GH_STUB set
|
|
484
|
-
//
|
|
528
|
+
// for pre-fetching and re-invoking with APT_TEST_GH_STUB set (tests) OR
|
|
529
|
+
// `--payload-file <path>` (production — see loadPayloadFile below).
|
|
485
530
|
return {
|
|
486
|
-
error:
|
|
531
|
+
error:
|
|
532
|
+
'gh shell-out is the skill orchestrator boundary — set APT_TEST_GH_STUB for unit tests or pass --payload-file for production',
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* Read a gh payload from disk for the production path (FRAMEWORK-BUG-010).
|
|
538
|
+
* Returns the same shape as fetchGhPayload's stub branch on success, or an
|
|
539
|
+
* { error } envelope on missing path / parse failure — the existing
|
|
540
|
+
* infra-backoff branch surfaces the error cleanly.
|
|
541
|
+
*
|
|
542
|
+
* @param {string} path absolute or already-resolved path
|
|
543
|
+
*/
|
|
544
|
+
function loadPayloadFile(path) {
|
|
545
|
+
try {
|
|
546
|
+
if (!existsSync(path)) {
|
|
547
|
+
return { error: `payload-file path not found: ${path}` }
|
|
548
|
+
}
|
|
549
|
+
const data = JSON.parse(readFileSync(path, 'utf-8'))
|
|
550
|
+
return {
|
|
551
|
+
checks: data.checks || [],
|
|
552
|
+
prMetadata: data.prMetadata || { state: 'OPEN' },
|
|
553
|
+
}
|
|
554
|
+
} catch (e) {
|
|
555
|
+
return { error: `payload-file parse error: ${e.message}` }
|
|
487
556
|
}
|
|
488
557
|
}
|
|
489
558
|
|
|
@@ -140,7 +140,7 @@ export function cmdHealthCheck(projectDir) {
|
|
|
140
140
|
issues.push({
|
|
141
141
|
type: 'missing-skills',
|
|
142
142
|
severity: 'warning',
|
|
143
|
-
detail: 'No
|
|
143
|
+
detail: 'No apt install manifests or version stamps found for any supported runtime',
|
|
144
144
|
install_surfaces: installs,
|
|
145
145
|
})
|
|
146
146
|
}
|
|
@@ -41,6 +41,7 @@ import {
|
|
|
41
41
|
writeLastInstallSummary,
|
|
42
42
|
} from '../install/pipeline.mjs'
|
|
43
43
|
import { RUNTIMES, suggestRuntimes } from '../install/runtime-detect.mjs'
|
|
44
|
+
import { runLegacyCleanup } from '../install/runtime-migrate.mjs'
|
|
44
45
|
import {
|
|
45
46
|
CLASS_A_SETTINGS,
|
|
46
47
|
CLASS_B_SETTINGS,
|
|
@@ -48,6 +49,7 @@ import {
|
|
|
48
49
|
parseClassFlags,
|
|
49
50
|
} from '../install/setting-class.mjs'
|
|
50
51
|
import { detectAgentRuntime } from '../util/agent-runtime.mjs'
|
|
52
|
+
import { buildAptSection } from '../util/aperant-section.mjs'
|
|
51
53
|
import { atomicWriteJson } from '../util/atomic-write.mjs'
|
|
52
54
|
import { aperantBanner } from '../util/banner.mjs'
|
|
53
55
|
import { copyDirRecursive, injectInstructionFile } from '../util/copy.mjs'
|
|
@@ -82,8 +84,21 @@ const TRANSFORM_LOADERS = new Map(
|
|
|
82
84
|
* a missing runtimes flag should block (no flag, no `--yes`) or not
|
|
83
85
|
* (flag passed, or `--yes`).
|
|
84
86
|
*
|
|
87
|
+
* 0.6.7 — `--force-runtimes` (Bug 2 fix) bypasses the `upgrade_noop`
|
|
88
|
+
* short-circuit so `/apt:update` can re-stamp runtime manifests even
|
|
89
|
+
* when `.aperant/config.json` is at the current schema. Strictly
|
|
90
|
+
* additive: callers without the flag still get the no-op short-circuit
|
|
91
|
+
* (preserves `init-upgrade-drift.test.ts` case (a)).
|
|
92
|
+
*
|
|
93
|
+
* 0.7.0 — `--force-overwrite-catalog` (FRAMEWORK-BUG-023) bypasses the
|
|
94
|
+
* foreign-row defensive check in `injectInstructionFile`. Power-user
|
|
95
|
+
* flag only — no interview prompt. Without it, an instruction file
|
|
96
|
+
* carrying rows the generator didn't produce causes init to exit 2 with
|
|
97
|
+
* `code: 'instruction-file.foreign-rows'` so unattended upgrade flows
|
|
98
|
+
* surface the would-be data loss instead of silently clobbering it.
|
|
99
|
+
*
|
|
85
100
|
* @param {string[]} args
|
|
86
|
-
* @returns {{ runtimes: string[], runtimes_explicit: boolean, target: string|null, interactive: boolean, skipBootstrap: boolean, visibility?: 'solo'|'team', autonomy?: 0|1|2|3, accept_new_defaults: boolean, yes: boolean }}
|
|
101
|
+
* @returns {{ runtimes: string[], runtimes_explicit: boolean, target: string|null, interactive: boolean, skipBootstrap: boolean, visibility?: 'solo'|'team', autonomy?: 0|1|2|3, accept_new_defaults: boolean, yes: boolean, forceRuntimes: boolean, forceOverwriteCatalog: boolean, keepLegacy: boolean }}
|
|
87
102
|
*/
|
|
88
103
|
export function parseInstallFlags(args) {
|
|
89
104
|
const flagSet = new Set(args.filter((a) => a.startsWith('--')))
|
|
@@ -96,12 +111,26 @@ export function parseInstallFlags(args) {
|
|
|
96
111
|
// C47 D-04 — opt out of the PROJECT.md auto-chain. Forwarded as
|
|
97
112
|
// `answers.skipBootstrap` into both interview paths.
|
|
98
113
|
const skipBootstrap = flagSet.has('--skip-bootstrap')
|
|
114
|
+
// 0.6.7 (Bug 2 fix) — opt-in bypass of the upgrade_noop short-circuit
|
|
115
|
+
// so /apt:update can re-stamp runtime files on a no-drift upgrade.
|
|
116
|
+
const forceRuntimes = flagSet.has('--force-runtimes')
|
|
117
|
+
// 0.7.0 (FRAMEWORK-BUG-023) — opt-in bypass of the foreign-row check
|
|
118
|
+
// in injectInstructionFile. Power-user flag only.
|
|
119
|
+
const forceOverwriteCatalog = flagSet.has('--force-overwrite-catalog')
|
|
120
|
+
// 0.7.0 (FRAMEWORK-BUG-021) — opt out of the codex legacy-tree cleanup
|
|
121
|
+
// that fires when `codex` is in the runtime set. Defaults to false so
|
|
122
|
+
// the cleanup runs by default; pass `--keep-legacy` to preserve the
|
|
123
|
+
// orphaned `.agents/skills/` tree on upgrades.
|
|
124
|
+
const keepLegacy = flagSet.has('--keep-legacy')
|
|
99
125
|
|
|
100
126
|
const classFlags = parseClassFlags(args)
|
|
101
127
|
|
|
102
128
|
const base = {
|
|
103
129
|
target,
|
|
104
130
|
skipBootstrap,
|
|
131
|
+
forceRuntimes,
|
|
132
|
+
forceOverwriteCatalog,
|
|
133
|
+
keepLegacy,
|
|
105
134
|
visibility: classFlags.visibility,
|
|
106
135
|
autonomy: classFlags.autonomy,
|
|
107
136
|
accept_new_defaults: classFlags.accept_new_defaults,
|
|
@@ -326,6 +355,33 @@ function applyCollapsedClassAToMigration(
|
|
|
326
355
|
}
|
|
327
356
|
}
|
|
328
357
|
|
|
358
|
+
/**
|
|
359
|
+
* Resolve the effective runtimes list from flags, with fallback to
|
|
360
|
+
* `suggestRuntimes` and a final fallback to `['claude']`.
|
|
361
|
+
*
|
|
362
|
+
* Shared by the `nonInteractiveAnswers` upgrade path and the
|
|
363
|
+
* `--force-runtimes` synthesis block in `cmdInit` to avoid duplication.
|
|
364
|
+
*
|
|
365
|
+
* Emits a one-line stderr warning when the final fallback fires (no
|
|
366
|
+
* explicit flags and `suggestRuntimes` returned empty) so the choice is
|
|
367
|
+
* visible without breaking automated callers.
|
|
368
|
+
*
|
|
369
|
+
* @param {string} targetDir
|
|
370
|
+
* @param {ReturnType<typeof parseInstallFlags>} parsedFlags
|
|
371
|
+
* @returns {string[]}
|
|
372
|
+
*/
|
|
373
|
+
function selectRuntimes(targetDir, parsedFlags) {
|
|
374
|
+
if (parsedFlags.runtimes_explicit) return parsedFlags.runtimes
|
|
375
|
+
const suggested = new Set(suggestRuntimes(targetDir))
|
|
376
|
+
if (suggested.size === 0) {
|
|
377
|
+
process.stderr.write(
|
|
378
|
+
'[apt-tools] No runtime specified and config has no suggestRuntimes; defaulting to claude.\n',
|
|
379
|
+
)
|
|
380
|
+
suggested.add('claude')
|
|
381
|
+
}
|
|
382
|
+
return [...suggested]
|
|
383
|
+
}
|
|
384
|
+
|
|
329
385
|
/**
|
|
330
386
|
* Non-TTY / scripted path — derive answers from flags + sensible defaults,
|
|
331
387
|
* gated by the Class A/B classifier (spec R2 + R3).
|
|
@@ -427,9 +483,7 @@ function nonInteractiveAnswers(targetDir, canonicalRoot, archetype, parsedFlags)
|
|
|
427
483
|
const visibility = migration.migrated?.share?.visibility === 'team' ? 'team' : 'solo'
|
|
428
484
|
const autonomyDefault = migration.migrated?.autonomy?.default ?? 1
|
|
429
485
|
const xverify = migration.migrated?.multi_model?.cross_verification?.tools ?? []
|
|
430
|
-
const
|
|
431
|
-
if (suggested.size === 0) suggested.add('claude')
|
|
432
|
-
const runtimes = parsedFlags.runtimes_explicit ? parsedFlags.runtimes : [...suggested]
|
|
486
|
+
const runtimes = selectRuntimes(targetDir, parsedFlags)
|
|
433
487
|
return {
|
|
434
488
|
runtimes,
|
|
435
489
|
autonomyDefault,
|
|
@@ -599,6 +653,12 @@ async function interactiveInterview(targetDir, version, canonicalRoot, archetype
|
|
|
599
653
|
migration.applied_defaults.length === 0 &&
|
|
600
654
|
migration.auto_added.length === 0
|
|
601
655
|
if (noDrift) {
|
|
656
|
+
// 0.6.7 (Bug 2 fix) — `--force-runtimes` bypasses the TTY
|
|
657
|
+
// "Nothing to do." outro so the runtime walker re-stamps every
|
|
658
|
+
// manifest; cmdInit's force-runtimes synthesis path takes over.
|
|
659
|
+
if (parsedFlags.forceRuntimes) {
|
|
660
|
+
return { upgrade_noop: true, skipBootstrap, migration }
|
|
661
|
+
}
|
|
602
662
|
process.stdout.write(`\n${aperantBanner(version)}\n\n`)
|
|
603
663
|
clack.intro('install')
|
|
604
664
|
clack.note(`Already up to date — v${version}`, 'No drift')
|
|
@@ -907,6 +967,22 @@ function applyInterviewAnswers(config, answers) {
|
|
|
907
967
|
}
|
|
908
968
|
config.project_type =
|
|
909
969
|
answers.archetype.type === 'brownfield' ? 'unknown' : config.project_type || 'unknown'
|
|
970
|
+
|
|
971
|
+
// Onboarding marker (task 15-05-26_apt-setup-onboarding-marker-7q9c).
|
|
972
|
+
// `/apt:setup` dispatches on this marker to decide review-mode vs full
|
|
973
|
+
// Batch 1-8 flow — file existence alone cannot disambiguate "fresh seed
|
|
974
|
+
// from template" from "fully tuned by setup" because templates/config.json
|
|
975
|
+
// ships with every key populated. Only the fresh-seed branch reaches this
|
|
976
|
+
// function (finishInit:1331); the upgrade branch (finishInit:1338-1357)
|
|
977
|
+
// writes answers.migration.migrated directly and never calls
|
|
978
|
+
// applyInterviewAnswers, so legacy unmarked configs correctly stay
|
|
979
|
+
// unmarked on upgrade. --yes / --accept-new-defaults walk this same fresh
|
|
980
|
+
// path but do NOT walk Batch 1-8 questions, so completed stays false.
|
|
981
|
+
config.onboarding = {
|
|
982
|
+
completed: false,
|
|
983
|
+
seeded_from_template: true,
|
|
984
|
+
seeded_at: new Date().toISOString(),
|
|
985
|
+
}
|
|
910
986
|
}
|
|
911
987
|
|
|
912
988
|
/**
|
|
@@ -1061,7 +1137,13 @@ export async function cmdInit(projectDir, extraArgs = []) {
|
|
|
1061
1137
|
// idempotent — preserves PROJECT.md, refreshes sidecar from it).
|
|
1062
1138
|
// In TTY mode clack already rendered "Nothing to do." to stdout,
|
|
1063
1139
|
// so suppress the JSON envelope (matches the cancelled path).
|
|
1064
|
-
|
|
1140
|
+
//
|
|
1141
|
+
// 0.6.7 (Bug 2 fix) — `--force-runtimes` bypasses this short-circuit
|
|
1142
|
+
// so `/apt:update` can re-stamp every runtime manifest even when
|
|
1143
|
+
// `.aperant/config.json` is at the current schema. We synthesize a
|
|
1144
|
+
// real InstallAnswers shape from the existing config so
|
|
1145
|
+
// installRuntimes + finishInit run their normal upgrade path.
|
|
1146
|
+
if (answers?.upgrade_noop && !parsedFlags.forceRuntimes) {
|
|
1065
1147
|
if (interactiveTty) return rawText('', 0)
|
|
1066
1148
|
const bootstrap = answers.skipBootstrap
|
|
1067
1149
|
? { status: 'skipped', reason: '--skip-bootstrap flag' }
|
|
@@ -1077,7 +1159,57 @@ export async function cmdInit(projectDir, extraArgs = []) {
|
|
|
1077
1159
|
})
|
|
1078
1160
|
}
|
|
1079
1161
|
|
|
1080
|
-
|
|
1162
|
+
// 0.6.7 (Bug 2 fix) — when forceRuntimes is set on a no-drift run,
|
|
1163
|
+
// synthesize a real InstallAnswers shape from the existing config so
|
|
1164
|
+
// the runtime walker re-stamps every manifest. Visibility/autonomy/
|
|
1165
|
+
// cross-verification are read from answers.migration.migrated (already
|
|
1166
|
+
// in memory from nonInteractiveAnswers — avoids a second config read);
|
|
1167
|
+
// runtimes resolved via selectRuntimes helper shared with the upgrade path.
|
|
1168
|
+
let effectiveAnswers = answers
|
|
1169
|
+
if (answers?.upgrade_noop && parsedFlags.forceRuntimes) {
|
|
1170
|
+
const existing = answers.migration?.migrated || {}
|
|
1171
|
+
const runtimes = selectRuntimes(installTarget, parsedFlags)
|
|
1172
|
+
effectiveAnswers = {
|
|
1173
|
+
runtimes,
|
|
1174
|
+
autonomyDefault: existing?.autonomy?.default ?? 1,
|
|
1175
|
+
visibility: existing?.share?.visibility === 'team' ? 'team' : 'solo',
|
|
1176
|
+
crossVerificationTools: existing?.multi_model?.cross_verification?.tools ?? [],
|
|
1177
|
+
scanNow: false,
|
|
1178
|
+
skipBootstrap: answers.skipBootstrap,
|
|
1179
|
+
archetype: detectArchetype(installTarget),
|
|
1180
|
+
agent_runtime: detectAgentRuntime(),
|
|
1181
|
+
applied_defaults: [],
|
|
1182
|
+
explicit_inputs: collectExplicitInputs(parsedFlags),
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
// FRAMEWORK-BUG-021 — sweep the legacy `.agents/skills/` codex tree
|
|
1187
|
+
// BEFORE the install pipeline runs so the install can re-stamp
|
|
1188
|
+
// `.codex/` against a clean slate. Only fires when codex is in the
|
|
1189
|
+
// requested runtime set and `--keep-legacy` was not passed; the
|
|
1190
|
+
// helper itself is conservative (no-op on managed installs, no-op
|
|
1191
|
+
// when no legacy framework files are present).
|
|
1192
|
+
let legacyCleanup = null
|
|
1193
|
+
if (effectiveAnswers.runtimes.includes('codex')) {
|
|
1194
|
+
if (parsedFlags.keepLegacy) {
|
|
1195
|
+
legacyCleanup = { runtime: 'codex', ran: false, reason: '--keep-legacy' }
|
|
1196
|
+
} else {
|
|
1197
|
+
const cleanup = runLegacyCleanup({ targetDir: installTarget })
|
|
1198
|
+
legacyCleanup = { runtime: 'codex', ...cleanup }
|
|
1199
|
+
if (cleanup.ran && cleanup.retained.length > 0) {
|
|
1200
|
+
process.stderr.write(
|
|
1201
|
+
`[apt-tools] legacy_cleanup retained ${cleanup.retained.length} user-added path(s) under .agents/skills/: ${cleanup.retained.join(', ')}\n`,
|
|
1202
|
+
)
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
const results = await installRuntimes(
|
|
1208
|
+
effectiveAnswers.runtimes,
|
|
1209
|
+
canonicalRoot,
|
|
1210
|
+
installTarget,
|
|
1211
|
+
version,
|
|
1212
|
+
)
|
|
1081
1213
|
|
|
1082
1214
|
// Provision the project-local kernel BEFORE finishInit runs its JSON
|
|
1083
1215
|
// output — that way the "structure" summary can reflect the kernel
|
|
@@ -1089,14 +1221,21 @@ export async function cmdInit(projectDir, extraArgs = []) {
|
|
|
1089
1221
|
installTarget,
|
|
1090
1222
|
canonicalRoot,
|
|
1091
1223
|
version,
|
|
1092
|
-
|
|
1224
|
+
effectiveAnswers.runtimes,
|
|
1093
1225
|
results,
|
|
1094
|
-
|
|
1226
|
+
effectiveAnswers,
|
|
1095
1227
|
interactiveTty,
|
|
1096
1228
|
kernelResult,
|
|
1229
|
+
parsedFlags.forceOverwriteCatalog === true,
|
|
1230
|
+
legacyCleanup,
|
|
1097
1231
|
)
|
|
1232
|
+
// FRAMEWORK-BUG-023 — finishInit signals the foreign-row defensive
|
|
1233
|
+
// abort by returning an early-exit sentinel envelope.
|
|
1234
|
+
if (envelope?.__earlyExit) {
|
|
1235
|
+
return envelope.__earlyExit
|
|
1236
|
+
}
|
|
1098
1237
|
if (interactiveTty) {
|
|
1099
|
-
await renderOutro(
|
|
1238
|
+
await renderOutro(effectiveAnswers, results, version, kernelResult)
|
|
1100
1239
|
// TTY mode: envelope was persisted to disk + suppressed from stdout
|
|
1101
1240
|
// because the clack outro already rendered the user-facing summary.
|
|
1102
1241
|
// Return the rawText shape with empty payload so wrap() routes us
|
|
@@ -1273,6 +1412,19 @@ async function renderOutro(answers, results, version, kernelResult) {
|
|
|
1273
1412
|
* deps/` — surfaced in the envelope so
|
|
1274
1413
|
* downstream tooling can detect a
|
|
1275
1414
|
* degraded install.
|
|
1415
|
+
* @param {boolean} [forceOverwriteCatalog=false]
|
|
1416
|
+
* FRAMEWORK-BUG-023 — bypass the
|
|
1417
|
+
* foreign-row check in
|
|
1418
|
+
* injectInstructionFile when the user
|
|
1419
|
+
* explicitly opted in via
|
|
1420
|
+
* --force-overwrite-catalog.
|
|
1421
|
+
* @param {object|null} [legacyCleanup=null]
|
|
1422
|
+
* FRAMEWORK-BUG-021 — outcome of the
|
|
1423
|
+
* codex `.agents/skills/` cleanup
|
|
1424
|
+
* step. `null` when codex was not in
|
|
1425
|
+
* the runtime set; otherwise the
|
|
1426
|
+
* shape `{ runtime, ran, … }` from
|
|
1427
|
+
* `runLegacyCleanup`.
|
|
1276
1428
|
*/
|
|
1277
1429
|
function finishInit(
|
|
1278
1430
|
targetDir,
|
|
@@ -1284,6 +1436,8 @@ function finishInit(
|
|
|
1284
1436
|
answers,
|
|
1285
1437
|
suppressStdout = false,
|
|
1286
1438
|
kernelResult = null,
|
|
1439
|
+
forceOverwriteCatalog = false,
|
|
1440
|
+
legacyCleanup = null,
|
|
1287
1441
|
) {
|
|
1288
1442
|
writeLastInstallSummary(targetDir, runtimeResults, version)
|
|
1289
1443
|
|
|
@@ -1414,7 +1568,40 @@ function finishInit(
|
|
|
1414
1568
|
if (existsSync(claudeMdPath)) instructionFile = claudeMdPath
|
|
1415
1569
|
else if (existsSync(agentsMdPath)) instructionFile = agentsMdPath
|
|
1416
1570
|
else instructionFile = claudeMdPath
|
|
1417
|
-
|
|
1571
|
+
// FRAMEWORK-BUG-023 — generate the section from discoverSkills before
|
|
1572
|
+
// handing it to the writer. The writer also runs a foreign-row defensive
|
|
1573
|
+
// check; if rows present in the file aren't in the generated set we
|
|
1574
|
+
// signal exit-2 via the __earlyExit sentinel so cmdInit can route the
|
|
1575
|
+
// envelope through exitWith() without writing a malformed install.
|
|
1576
|
+
const aptSection = buildAptSection({
|
|
1577
|
+
canonicalRoot,
|
|
1578
|
+
targetDir,
|
|
1579
|
+
cli: detectAgentRuntime(),
|
|
1580
|
+
})
|
|
1581
|
+
const injectionOutcome = injectInstructionFile(instructionFile, {
|
|
1582
|
+
aptSection,
|
|
1583
|
+
force: forceOverwriteCatalog,
|
|
1584
|
+
})
|
|
1585
|
+
if (injectionOutcome.status === 'aborted-foreign-rows') {
|
|
1586
|
+
return {
|
|
1587
|
+
__earlyExit: exitWith(
|
|
1588
|
+
{
|
|
1589
|
+
ok: false,
|
|
1590
|
+
status: 'error',
|
|
1591
|
+
command: 'init',
|
|
1592
|
+
code: 'instruction-file.foreign-rows',
|
|
1593
|
+
message:
|
|
1594
|
+
`${basename(instructionFile)} contains skill rows not present in the installed framework: ` +
|
|
1595
|
+
`${injectionOutcome.foreignRowIds.join(', ')}. ` +
|
|
1596
|
+
'Re-run with --force-overwrite-catalog to regenerate anyway, or remove the foreign rows manually.',
|
|
1597
|
+
foreignRowIds: injectionOutcome.foreignRowIds,
|
|
1598
|
+
instructionFile: basename(instructionFile),
|
|
1599
|
+
},
|
|
1600
|
+
2,
|
|
1601
|
+
),
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
const injectionResult = injectionOutcome.status
|
|
1418
1605
|
|
|
1419
1606
|
const installedClaude = chosenIds.includes('claude') || existsSync(join(targetDir, '.claude'))
|
|
1420
1607
|
const precompactResult = installedClaude
|
|
@@ -1525,6 +1712,11 @@ function finishInit(
|
|
|
1525
1712
|
...(kernelResult.reason ? { reason: kernelResult.reason } : {}),
|
|
1526
1713
|
}
|
|
1527
1714
|
: null,
|
|
1715
|
+
// FRAMEWORK-BUG-021 — always-present envelope field. `null` when
|
|
1716
|
+
// codex was not in the runtime set; otherwise the cleanup outcome
|
|
1717
|
+
// (uses `{ ran: false, reason }` for every skip case so parsers
|
|
1718
|
+
// see a uniform shape).
|
|
1719
|
+
legacy_cleanup: legacyCleanup,
|
|
1528
1720
|
}
|
|
1529
1721
|
|
|
1530
1722
|
if (suppressStdout) {
|