@aperant/framework 0.6.5 → 0.6.7
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 +155 -0
- package/README.md +17 -251
- 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 +34 -4
- package/dist/cli/commands/ci-watch.mjs.map +1 -1
- package/dist/cli/commands/commit.d.mts.map +1 -1
- package/dist/cli/commands/commit.mjs +25 -8
- package/dist/cli/commands/commit.mjs.map +1 -1
- package/dist/cli/commands/event.d.mts.map +1 -1
- package/dist/cli/commands/event.mjs +66 -0
- package/dist/cli/commands/event.mjs.map +1 -1
- package/dist/cli/commands/init.d.mts +8 -1
- package/dist/cli/commands/init.d.mts.map +1 -1
- package/dist/cli/commands/init.mjs +91 -9
- 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 +2 -1
- package/dist/cli/commands/modes.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/task.d.mts.map +1 -1
- package/dist/cli/commands/task.mjs +285 -139
- package/dist/cli/commands/task.mjs.map +1 -1
- package/dist/cli/commands/triage.d.mts.map +1 -1
- package/dist/cli/commands/triage.mjs +9 -5
- package/dist/cli/commands/triage.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/coordination/auto-emit-artifact-ready.d.mts +16 -0
- package/dist/cli/coordination/auto-emit-artifact-ready.d.mts.map +1 -0
- package/dist/cli/coordination/auto-emit-artifact-ready.mjs +88 -0
- package/dist/cli/coordination/auto-emit-artifact-ready.mjs.map +1 -0
- package/dist/cli/coordination/event-schema.d.mts +16 -0
- package/dist/cli/coordination/event-schema.d.mts.map +1 -0
- package/dist/cli/coordination/event-schema.mjs +161 -0
- package/dist/cli/coordination/event-schema.mjs.map +1 -0
- package/dist/cli/coordination/store.d.mts +6 -0
- package/dist/cli/coordination/store.d.mts.map +1 -1
- package/dist/cli/coordination/store.mjs +14 -0
- package/dist/cli/coordination/store.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.mjs +2 -2
- package/dist/cli/help.mjs.map +1 -1
- 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/roadmap/conductor-view.d.mts +13 -0
- package/dist/cli/roadmap/conductor-view.d.mts.map +1 -0
- package/dist/cli/roadmap/conductor-view.mjs +31 -0
- package/dist/cli/roadmap/conductor-view.mjs.map +1 -0
- package/dist/cli/route/skill-discover.d.mts.map +1 -1
- package/dist/cli/route/skill-discover.mjs +2 -1
- package/dist/cli/route/skill-discover.mjs.map +1 -1
- package/dist/cli/task/ids.d.mts +7 -4
- package/dist/cli/task/ids.d.mts.map +1 -1
- package/dist/cli/task/ids.mjs +11 -10
- package/dist/cli/task/ids.mjs.map +1 -1
- package/dist/types/config.d.ts +11 -7
- package/dist/types/config.d.ts.map +1 -1
- package/package.json +9 -1
- package/prompts/conductor-status-check.md +23 -0
- package/prompts/conductor-sub-agent.md +57 -0
- package/prompts/conductor-system.md +172 -0
- package/skills/apt-close-task/SKILL.md +25 -0
- package/skills/apt-diagram/SKILL.md +45 -9
- package/skills/apt-plan/SKILL.md +12 -0
- package/skills/apt-plan/adapters/conductor.md +98 -0
- 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 +148 -3
- package/skills/apt-ship/SKILL.md +74 -12
- package/skills/apt-spar/SKILL.md +290 -0
- package/skills/apt-update/SKILL.md +51 -9
- package/skills/apt-watch-ci/SKILL.md +2 -2
- package/src/cli/commands/check-version.mjs +73 -1
- package/src/cli/commands/ci-watch.mjs +35 -4
- package/src/cli/commands/commit.mjs +27 -8
- package/src/cli/commands/event.mjs +68 -0
- package/src/cli/commands/init.mjs +101 -9
- package/src/cli/commands/modes.mjs +2 -1
- package/src/cli/commands/release-notes.mjs +187 -0
- package/src/cli/commands/task.mjs +305 -152
- package/src/cli/commands/triage.mjs +14 -5
- 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/coordination/auto-emit-artifact-ready.mjs +96 -0
- package/src/cli/coordination/event-schema.d.ts +13 -0
- package/src/cli/coordination/event-schema.mjs +174 -0
- package/src/cli/coordination/store.mjs +14 -0
- package/src/cli/dispatch.mjs +2 -0
- package/src/cli/gate/gates/review-clean.mjs +4 -2
- package/src/cli/help.mjs +2 -2
- 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/roadmap/conductor-view.d.ts +10 -0
- package/src/cli/roadmap/conductor-view.mjs +31 -0
- package/src/cli/route/skill-discover.mjs +2 -1
- package/src/cli/task/ids.mjs +15 -13
- package/templates/config.json +28 -3
- package/workflows/docs.md +12 -0
|
@@ -6,6 +6,7 @@ import { appendFileSync, mkdirSync, readdirSync, readFileSync, writeFileSync } f
|
|
|
6
6
|
import { hostname } from 'node:os'
|
|
7
7
|
import { join, resolve } from 'node:path'
|
|
8
8
|
import { readLifecycleEventsForDay } from '../coordination/event-log.mjs'
|
|
9
|
+
import { validateConductorEvent } from '../coordination/event-schema.mjs'
|
|
9
10
|
import { createCoordinationStore } from '../coordination/store.mjs'
|
|
10
11
|
import { detectWorktree } from '../git/worktree-detect.mjs'
|
|
11
12
|
import { parseFlags } from '../util/args.mjs'
|
|
@@ -27,6 +28,21 @@ const VALID_EVENT_TYPES = [
|
|
|
27
28
|
'ci-watch.tick',
|
|
28
29
|
'ci-watch.fix-pushed',
|
|
29
30
|
'ci-watch.stopped',
|
|
31
|
+
// Phase 2.5 — Conductor-readable ops emitted by APT skills so the
|
|
32
|
+
// Conductor's readEventLog tool can drive its observe loop. These
|
|
33
|
+
// events route to JSONL (like ci-watch.*) and carry terminal_id
|
|
34
|
+
// auto-stamped from APERANT_TERMINAL_ID when present.
|
|
35
|
+
'phase.changed',
|
|
36
|
+
'artifact.ready',
|
|
37
|
+
'awaiting-input',
|
|
38
|
+
'input-rescinded',
|
|
39
|
+
'heartbeat',
|
|
40
|
+
'budget.exceeded',
|
|
41
|
+
'terminal.enrolled',
|
|
42
|
+
'terminal.unenrolled',
|
|
43
|
+
'conductor.intent',
|
|
44
|
+
'conductor.cross_check',
|
|
45
|
+
'conductor.realignment',
|
|
30
46
|
]
|
|
31
47
|
|
|
32
48
|
const CI_WATCH_EVENT_TYPES = new Set([
|
|
@@ -36,6 +52,20 @@ const CI_WATCH_EVENT_TYPES = new Set([
|
|
|
36
52
|
'ci-watch.stopped',
|
|
37
53
|
])
|
|
38
54
|
|
|
55
|
+
const CONDUCTOR_EVENT_TYPES = new Set([
|
|
56
|
+
'phase.changed',
|
|
57
|
+
'artifact.ready',
|
|
58
|
+
'awaiting-input',
|
|
59
|
+
'input-rescinded',
|
|
60
|
+
'heartbeat',
|
|
61
|
+
'budget.exceeded',
|
|
62
|
+
'terminal.enrolled',
|
|
63
|
+
'terminal.unenrolled',
|
|
64
|
+
'conductor.intent',
|
|
65
|
+
'conductor.cross_check',
|
|
66
|
+
'conductor.realignment',
|
|
67
|
+
])
|
|
68
|
+
|
|
39
69
|
/**
|
|
40
70
|
* Append a ci-watch event to the per-day JSONL file (D-04).
|
|
41
71
|
* O(1) — no directory scan. Creates the events dir if missing.
|
|
@@ -110,6 +140,44 @@ export function cmdEvent(subcommand, projectDir, extraArgs) {
|
|
|
110
140
|
return ok({ status: 'ok', command: 'event-append', event_file: jsonlFile, event })
|
|
111
141
|
}
|
|
112
142
|
|
|
143
|
+
// Phase 2.5 — Conductor-readable events route to the per-day JSONL
|
|
144
|
+
// path with the canonical envelope the Conductor's readEventLog
|
|
145
|
+
// expects: { t, agent, pid, hostname, incarnation, op, task, data }.
|
|
146
|
+
// terminal_id is auto-stamped from APERANT_TERMINAL_ID env when set,
|
|
147
|
+
// so APT skills running in a Conductor-spawned PTY automatically
|
|
148
|
+
// tag their events with the originating terminal.
|
|
149
|
+
if (CONDUCTOR_EVENT_TYPES.has(type)) {
|
|
150
|
+
const terminalId = process.env.APERANT_TERMINAL_ID
|
|
151
|
+
if (terminalId && data && typeof data === 'object' && !data.terminal_id) {
|
|
152
|
+
data.terminal_id = terminalId
|
|
153
|
+
}
|
|
154
|
+
// Step 1 — schema gate: reject payloads that violate the per-op
|
|
155
|
+
// contract before the line lands on disk. The same validator
|
|
156
|
+
// runs at the framework store seam and the core conductor
|
|
157
|
+
// writer; gating here catches CLI emitters (APT skills).
|
|
158
|
+
const validation = validateConductorEvent(type, data)
|
|
159
|
+
if (!validation.ok) {
|
|
160
|
+
return err(`Schema violation for ${type}: ${validation.error}`)
|
|
161
|
+
}
|
|
162
|
+
const envelope = {
|
|
163
|
+
t: new Date().toISOString(),
|
|
164
|
+
agent: `${hostname()}-${process.pid}`,
|
|
165
|
+
pid: process.pid,
|
|
166
|
+
hostname: hostname(),
|
|
167
|
+
incarnation: 1,
|
|
168
|
+
op: type,
|
|
169
|
+
task: subtask || null,
|
|
170
|
+
data: data || {},
|
|
171
|
+
}
|
|
172
|
+
const { jsonlFile } = appendToJsonl(eventsDir, envelope)
|
|
173
|
+
return ok({
|
|
174
|
+
status: 'ok',
|
|
175
|
+
command: 'event-append',
|
|
176
|
+
event_file: jsonlFile,
|
|
177
|
+
event: envelope,
|
|
178
|
+
})
|
|
179
|
+
}
|
|
180
|
+
|
|
113
181
|
// Find next sequence number — scan existing files for highest number
|
|
114
182
|
const existingFiles = readdirSync(eventsDir).filter((f) => f.endsWith('.json'))
|
|
115
183
|
let maxId = 0
|
|
@@ -82,8 +82,14 @@ const TRANSFORM_LOADERS = new Map(
|
|
|
82
82
|
* a missing runtimes flag should block (no flag, no `--yes`) or not
|
|
83
83
|
* (flag passed, or `--yes`).
|
|
84
84
|
*
|
|
85
|
+
* 0.6.7 — `--force-runtimes` (Bug 2 fix) bypasses the `upgrade_noop`
|
|
86
|
+
* short-circuit so `/apt:update` can re-stamp runtime manifests even
|
|
87
|
+
* when `.aperant/config.json` is at the current schema. Strictly
|
|
88
|
+
* additive: callers without the flag still get the no-op short-circuit
|
|
89
|
+
* (preserves `init-upgrade-drift.test.ts` case (a)).
|
|
90
|
+
*
|
|
85
91
|
* @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 }}
|
|
92
|
+
* @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 }}
|
|
87
93
|
*/
|
|
88
94
|
export function parseInstallFlags(args) {
|
|
89
95
|
const flagSet = new Set(args.filter((a) => a.startsWith('--')))
|
|
@@ -96,12 +102,16 @@ export function parseInstallFlags(args) {
|
|
|
96
102
|
// C47 D-04 — opt out of the PROJECT.md auto-chain. Forwarded as
|
|
97
103
|
// `answers.skipBootstrap` into both interview paths.
|
|
98
104
|
const skipBootstrap = flagSet.has('--skip-bootstrap')
|
|
105
|
+
// 0.6.7 (Bug 2 fix) — opt-in bypass of the upgrade_noop short-circuit
|
|
106
|
+
// so /apt:update can re-stamp runtime files on a no-drift upgrade.
|
|
107
|
+
const forceRuntimes = flagSet.has('--force-runtimes')
|
|
99
108
|
|
|
100
109
|
const classFlags = parseClassFlags(args)
|
|
101
110
|
|
|
102
111
|
const base = {
|
|
103
112
|
target,
|
|
104
113
|
skipBootstrap,
|
|
114
|
+
forceRuntimes,
|
|
105
115
|
visibility: classFlags.visibility,
|
|
106
116
|
autonomy: classFlags.autonomy,
|
|
107
117
|
accept_new_defaults: classFlags.accept_new_defaults,
|
|
@@ -326,6 +336,33 @@ function applyCollapsedClassAToMigration(
|
|
|
326
336
|
}
|
|
327
337
|
}
|
|
328
338
|
|
|
339
|
+
/**
|
|
340
|
+
* Resolve the effective runtimes list from flags, with fallback to
|
|
341
|
+
* `suggestRuntimes` and a final fallback to `['claude']`.
|
|
342
|
+
*
|
|
343
|
+
* Shared by the `nonInteractiveAnswers` upgrade path and the
|
|
344
|
+
* `--force-runtimes` synthesis block in `cmdInit` to avoid duplication.
|
|
345
|
+
*
|
|
346
|
+
* Emits a one-line stderr warning when the final fallback fires (no
|
|
347
|
+
* explicit flags and `suggestRuntimes` returned empty) so the choice is
|
|
348
|
+
* visible without breaking automated callers.
|
|
349
|
+
*
|
|
350
|
+
* @param {string} targetDir
|
|
351
|
+
* @param {ReturnType<typeof parseInstallFlags>} parsedFlags
|
|
352
|
+
* @returns {string[]}
|
|
353
|
+
*/
|
|
354
|
+
function selectRuntimes(targetDir, parsedFlags) {
|
|
355
|
+
if (parsedFlags.runtimes_explicit) return parsedFlags.runtimes
|
|
356
|
+
const suggested = new Set(suggestRuntimes(targetDir))
|
|
357
|
+
if (suggested.size === 0) {
|
|
358
|
+
process.stderr.write(
|
|
359
|
+
'[apt-tools] No runtime specified and config has no suggestRuntimes; defaulting to claude.\n',
|
|
360
|
+
)
|
|
361
|
+
suggested.add('claude')
|
|
362
|
+
}
|
|
363
|
+
return [...suggested]
|
|
364
|
+
}
|
|
365
|
+
|
|
329
366
|
/**
|
|
330
367
|
* Non-TTY / scripted path — derive answers from flags + sensible defaults,
|
|
331
368
|
* gated by the Class A/B classifier (spec R2 + R3).
|
|
@@ -427,9 +464,7 @@ function nonInteractiveAnswers(targetDir, canonicalRoot, archetype, parsedFlags)
|
|
|
427
464
|
const visibility = migration.migrated?.share?.visibility === 'team' ? 'team' : 'solo'
|
|
428
465
|
const autonomyDefault = migration.migrated?.autonomy?.default ?? 1
|
|
429
466
|
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]
|
|
467
|
+
const runtimes = selectRuntimes(targetDir, parsedFlags)
|
|
433
468
|
return {
|
|
434
469
|
runtimes,
|
|
435
470
|
autonomyDefault,
|
|
@@ -599,6 +634,12 @@ async function interactiveInterview(targetDir, version, canonicalRoot, archetype
|
|
|
599
634
|
migration.applied_defaults.length === 0 &&
|
|
600
635
|
migration.auto_added.length === 0
|
|
601
636
|
if (noDrift) {
|
|
637
|
+
// 0.6.7 (Bug 2 fix) — `--force-runtimes` bypasses the TTY
|
|
638
|
+
// "Nothing to do." outro so the runtime walker re-stamps every
|
|
639
|
+
// manifest; cmdInit's force-runtimes synthesis path takes over.
|
|
640
|
+
if (parsedFlags.forceRuntimes) {
|
|
641
|
+
return { upgrade_noop: true, skipBootstrap, migration }
|
|
642
|
+
}
|
|
602
643
|
process.stdout.write(`\n${aperantBanner(version)}\n\n`)
|
|
603
644
|
clack.intro('install')
|
|
604
645
|
clack.note(`Already up to date — v${version}`, 'No drift')
|
|
@@ -907,6 +948,22 @@ function applyInterviewAnswers(config, answers) {
|
|
|
907
948
|
}
|
|
908
949
|
config.project_type =
|
|
909
950
|
answers.archetype.type === 'brownfield' ? 'unknown' : config.project_type || 'unknown'
|
|
951
|
+
|
|
952
|
+
// Onboarding marker (task 15-05-26_apt-setup-onboarding-marker-7q9c).
|
|
953
|
+
// `/apt:setup` dispatches on this marker to decide review-mode vs full
|
|
954
|
+
// Batch 1-8 flow — file existence alone cannot disambiguate "fresh seed
|
|
955
|
+
// from template" from "fully tuned by setup" because templates/config.json
|
|
956
|
+
// ships with every key populated. Only the fresh-seed branch reaches this
|
|
957
|
+
// function (finishInit:1331); the upgrade branch (finishInit:1338-1357)
|
|
958
|
+
// writes answers.migration.migrated directly and never calls
|
|
959
|
+
// applyInterviewAnswers, so legacy unmarked configs correctly stay
|
|
960
|
+
// unmarked on upgrade. --yes / --accept-new-defaults walk this same fresh
|
|
961
|
+
// path but do NOT walk Batch 1-8 questions, so completed stays false.
|
|
962
|
+
config.onboarding = {
|
|
963
|
+
completed: false,
|
|
964
|
+
seeded_from_template: true,
|
|
965
|
+
seeded_at: new Date().toISOString(),
|
|
966
|
+
}
|
|
910
967
|
}
|
|
911
968
|
|
|
912
969
|
/**
|
|
@@ -1061,7 +1118,13 @@ export async function cmdInit(projectDir, extraArgs = []) {
|
|
|
1061
1118
|
// idempotent — preserves PROJECT.md, refreshes sidecar from it).
|
|
1062
1119
|
// In TTY mode clack already rendered "Nothing to do." to stdout,
|
|
1063
1120
|
// so suppress the JSON envelope (matches the cancelled path).
|
|
1064
|
-
|
|
1121
|
+
//
|
|
1122
|
+
// 0.6.7 (Bug 2 fix) — `--force-runtimes` bypasses this short-circuit
|
|
1123
|
+
// so `/apt:update` can re-stamp every runtime manifest even when
|
|
1124
|
+
// `.aperant/config.json` is at the current schema. We synthesize a
|
|
1125
|
+
// real InstallAnswers shape from the existing config so
|
|
1126
|
+
// installRuntimes + finishInit run their normal upgrade path.
|
|
1127
|
+
if (answers?.upgrade_noop && !parsedFlags.forceRuntimes) {
|
|
1065
1128
|
if (interactiveTty) return rawText('', 0)
|
|
1066
1129
|
const bootstrap = answers.skipBootstrap
|
|
1067
1130
|
? { status: 'skipped', reason: '--skip-bootstrap flag' }
|
|
@@ -1077,7 +1140,36 @@ export async function cmdInit(projectDir, extraArgs = []) {
|
|
|
1077
1140
|
})
|
|
1078
1141
|
}
|
|
1079
1142
|
|
|
1080
|
-
|
|
1143
|
+
// 0.6.7 (Bug 2 fix) — when forceRuntimes is set on a no-drift run,
|
|
1144
|
+
// synthesize a real InstallAnswers shape from the existing config so
|
|
1145
|
+
// the runtime walker re-stamps every manifest. Visibility/autonomy/
|
|
1146
|
+
// cross-verification are read from answers.migration.migrated (already
|
|
1147
|
+
// in memory from nonInteractiveAnswers — avoids a second config read);
|
|
1148
|
+
// runtimes resolved via selectRuntimes helper shared with the upgrade path.
|
|
1149
|
+
let effectiveAnswers = answers
|
|
1150
|
+
if (answers?.upgrade_noop && parsedFlags.forceRuntimes) {
|
|
1151
|
+
const existing = answers.migration?.migrated || {}
|
|
1152
|
+
const runtimes = selectRuntimes(installTarget, parsedFlags)
|
|
1153
|
+
effectiveAnswers = {
|
|
1154
|
+
runtimes,
|
|
1155
|
+
autonomyDefault: existing?.autonomy?.default ?? 1,
|
|
1156
|
+
visibility: existing?.share?.visibility === 'team' ? 'team' : 'solo',
|
|
1157
|
+
crossVerificationTools: existing?.multi_model?.cross_verification?.tools ?? [],
|
|
1158
|
+
scanNow: false,
|
|
1159
|
+
skipBootstrap: answers.skipBootstrap,
|
|
1160
|
+
archetype: detectArchetype(installTarget),
|
|
1161
|
+
agent_runtime: detectAgentRuntime(),
|
|
1162
|
+
applied_defaults: [],
|
|
1163
|
+
explicit_inputs: collectExplicitInputs(parsedFlags),
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
const results = await installRuntimes(
|
|
1168
|
+
effectiveAnswers.runtimes,
|
|
1169
|
+
canonicalRoot,
|
|
1170
|
+
installTarget,
|
|
1171
|
+
version,
|
|
1172
|
+
)
|
|
1081
1173
|
|
|
1082
1174
|
// Provision the project-local kernel BEFORE finishInit runs its JSON
|
|
1083
1175
|
// output — that way the "structure" summary can reflect the kernel
|
|
@@ -1089,14 +1181,14 @@ export async function cmdInit(projectDir, extraArgs = []) {
|
|
|
1089
1181
|
installTarget,
|
|
1090
1182
|
canonicalRoot,
|
|
1091
1183
|
version,
|
|
1092
|
-
|
|
1184
|
+
effectiveAnswers.runtimes,
|
|
1093
1185
|
results,
|
|
1094
|
-
|
|
1186
|
+
effectiveAnswers,
|
|
1095
1187
|
interactiveTty,
|
|
1096
1188
|
kernelResult,
|
|
1097
1189
|
)
|
|
1098
1190
|
if (interactiveTty) {
|
|
1099
|
-
await renderOutro(
|
|
1191
|
+
await renderOutro(effectiveAnswers, results, version, kernelResult)
|
|
1100
1192
|
// TTY mode: envelope was persisted to disk + suppressed from stdout
|
|
1101
1193
|
// because the clack outro already rendered the user-facing summary.
|
|
1102
1194
|
// Return the rawText shape with empty payload so wrap() routes us
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs'
|
|
29
29
|
import { join, resolve } from 'node:path'
|
|
30
30
|
import { parse as parseYaml } from 'yaml'
|
|
31
|
+
import { sanitizeAptVersionForYamlParse } from '../install/version-header.mjs'
|
|
31
32
|
import { ok } from '../util/result.mjs'
|
|
32
33
|
|
|
33
34
|
const FRONTMATTER_RE = /^---\s*([\s\S]*?)\s*---/
|
|
@@ -122,7 +123,7 @@ function readSkillForModes(file) {
|
|
|
122
123
|
if (!m) return null
|
|
123
124
|
let parsed
|
|
124
125
|
try {
|
|
125
|
-
parsed = parseYaml(m[1])
|
|
126
|
+
parsed = parseYaml(sanitizeAptVersionForYamlParse(m[1]))
|
|
126
127
|
} catch {
|
|
127
128
|
return null
|
|
128
129
|
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* commands/release-notes.mjs — `apt-tools release-notes <sub>` (subtask 2-3
|
|
3
|
+
* of persona-aware-changelog-and-release-note_16-05-26).
|
|
4
|
+
*
|
|
5
|
+
* Deterministic I/O surface for the apt:release-notes skill. NEVER calls
|
|
6
|
+
* an LLM. The SKILL.md body invokes the host LLM to author the note text
|
|
7
|
+
* and hands the structured payload to this CLI for persistence + compile.
|
|
8
|
+
*
|
|
9
|
+
* Sub-verb menu:
|
|
10
|
+
*
|
|
11
|
+
* apt-tools release-notes draft <project-dir> --pr <n> --note "<text>"
|
|
12
|
+
* [--persona <id>] [--output-dir <path>] [--commits <sha,sha,...>]
|
|
13
|
+
* [--date <YYYY-MM-DD>]
|
|
14
|
+
* → write `<output_dir>/_unreleased/PR-<n>.md`. Idempotent.
|
|
15
|
+
*
|
|
16
|
+
* apt-tools release-notes compile <project-dir> --tag <tag>
|
|
17
|
+
* [--output-dir <path>]
|
|
18
|
+
* → read every `_unreleased/PR-*.md`, filter by persona_filter, write
|
|
19
|
+
* `<output_dir>/<tag>.md`, clear `_unreleased/` only on success.
|
|
20
|
+
*
|
|
21
|
+
* apt-tools release-notes publish <project-dir> --tag <tag>
|
|
22
|
+
* [--output-dir <path>] [--dry-run]
|
|
23
|
+
* → read `<output_dir>/<tag>.md` + shell `gh release create/edit`.
|
|
24
|
+
*
|
|
25
|
+
* Return shape: always `{ status, command, ... }`. The wrap() helper in
|
|
26
|
+
* dispatch.mjs translates `{ status: 'error' }` envelopes into exit-1.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import { resolve } from 'node:path'
|
|
30
|
+
|
|
31
|
+
import { compileTag } from '../release-notes/compile.mjs'
|
|
32
|
+
import { writeDraft } from '../release-notes/draft.mjs'
|
|
33
|
+
import { publishTag } from '../release-notes/publish.mjs'
|
|
34
|
+
import { getFlagValue } from '../util/common-flags.mjs'
|
|
35
|
+
import { err, ok } from '../util/result.mjs'
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Dispatch shape (mirrors `audit`): `release-notes: (args) =>
|
|
39
|
+
* wrap(cmdReleaseNotes)(args[1], args[2], args.slice(3))` — args[1] is the
|
|
40
|
+
* sub-verb, args[2] is the project dir.
|
|
41
|
+
*
|
|
42
|
+
* @param {string | undefined} subcommand
|
|
43
|
+
* @param {string | undefined} projectDirArg
|
|
44
|
+
* @param {string[] | undefined} extraArgs
|
|
45
|
+
*/
|
|
46
|
+
export async function cmdReleaseNotes(subcommand, projectDirArg, extraArgs) {
|
|
47
|
+
const args = extraArgs ?? []
|
|
48
|
+
if (!subcommand) {
|
|
49
|
+
return err(
|
|
50
|
+
'Usage: apt-tools release-notes <draft|compile|publish> <project-dir> [flags]\n' +
|
|
51
|
+
' draft write docs/releases/_unreleased/PR-<n>.md\n' +
|
|
52
|
+
' compile read fragments → docs/releases/<tag>.md, clear _unreleased/\n' +
|
|
53
|
+
' publish shell gh release create/edit',
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
const projectDir = resolve(projectDirArg && projectDirArg.length > 0 ? projectDirArg : '.')
|
|
57
|
+
|
|
58
|
+
switch (subcommand) {
|
|
59
|
+
case 'draft':
|
|
60
|
+
return runDraft(projectDir, args)
|
|
61
|
+
case 'compile':
|
|
62
|
+
return runCompile(projectDir, args)
|
|
63
|
+
case 'publish':
|
|
64
|
+
return runPublish(projectDir, args)
|
|
65
|
+
default:
|
|
66
|
+
return err(
|
|
67
|
+
`apt-tools release-notes: unknown subcommand "${subcommand}". Known: draft, compile, publish.`,
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* draft — write a single PR fragment.
|
|
74
|
+
*
|
|
75
|
+
* @param {string} projectDir
|
|
76
|
+
* @param {string[]} args
|
|
77
|
+
*/
|
|
78
|
+
function runDraft(projectDir, args) {
|
|
79
|
+
const prRaw = getFlagValue(args, '--pr')
|
|
80
|
+
const note = getFlagValue(args, '--note')
|
|
81
|
+
const persona = getFlagValue(args, '--persona')
|
|
82
|
+
const outputDirFlag = getFlagValue(args, '--output-dir')
|
|
83
|
+
const commitsFlag = getFlagValue(args, '--commits')
|
|
84
|
+
const dateFlag = getFlagValue(args, '--date')
|
|
85
|
+
|
|
86
|
+
if (!prRaw) {
|
|
87
|
+
return err('release-notes draft: --pr <number> is required')
|
|
88
|
+
}
|
|
89
|
+
const pr = Number.parseInt(prRaw, 10)
|
|
90
|
+
if (!Number.isFinite(pr) || pr <= 0) {
|
|
91
|
+
return err(`release-notes draft: --pr must be a positive integer (got "${prRaw}")`)
|
|
92
|
+
}
|
|
93
|
+
if (!note || note.trim().length === 0) {
|
|
94
|
+
return err('release-notes draft: --note "<text>" is required (and must be non-empty)')
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const commits = commitsFlag
|
|
98
|
+
? commitsFlag
|
|
99
|
+
.split(',')
|
|
100
|
+
.map((s) => s.trim())
|
|
101
|
+
.filter((s) => s.length > 0)
|
|
102
|
+
: null
|
|
103
|
+
|
|
104
|
+
const result = writeDraft({
|
|
105
|
+
projectDir,
|
|
106
|
+
pr,
|
|
107
|
+
note,
|
|
108
|
+
persona,
|
|
109
|
+
outputDir: outputDirFlag,
|
|
110
|
+
commits,
|
|
111
|
+
date: dateFlag,
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
return ok({
|
|
115
|
+
status: 'ok',
|
|
116
|
+
command: 'release-notes-draft',
|
|
117
|
+
projectDir,
|
|
118
|
+
pr,
|
|
119
|
+
written: result.written,
|
|
120
|
+
reason: result.reason,
|
|
121
|
+
path: result.path,
|
|
122
|
+
})
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* compile — produce the per-tag narrative file from accumulated fragments.
|
|
127
|
+
*
|
|
128
|
+
* @param {string} projectDir
|
|
129
|
+
* @param {string[]} args
|
|
130
|
+
*/
|
|
131
|
+
function runCompile(projectDir, args) {
|
|
132
|
+
const tag = getFlagValue(args, '--tag')
|
|
133
|
+
if (!tag) {
|
|
134
|
+
return err('release-notes compile: --tag <tag> is required')
|
|
135
|
+
}
|
|
136
|
+
const outputDirFlag = getFlagValue(args, '--output-dir')
|
|
137
|
+
const result = compileTag({ projectDir, tag, outputDir: outputDirFlag })
|
|
138
|
+
return ok({
|
|
139
|
+
status: 'ok',
|
|
140
|
+
command: 'release-notes-compile',
|
|
141
|
+
projectDir,
|
|
142
|
+
tag,
|
|
143
|
+
written: result.written,
|
|
144
|
+
path: result.path,
|
|
145
|
+
fragments_total: result.fragmentsTotal,
|
|
146
|
+
fragments_in_body: result.fragmentsInBody,
|
|
147
|
+
fragments_in_triage: result.fragmentsInTriage,
|
|
148
|
+
fragments_filtered: result.fragmentsFiltered,
|
|
149
|
+
cleared_unreleased: result.clearedUnreleased,
|
|
150
|
+
})
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* publish — shell `gh release create/edit`. Returns a graceful
|
|
155
|
+
* `{status: 'skipped', reason: 'gh-not-found'}` when gh is unavailable.
|
|
156
|
+
*
|
|
157
|
+
* @param {string} projectDir
|
|
158
|
+
* @param {string[]} args
|
|
159
|
+
*/
|
|
160
|
+
function runPublish(projectDir, args) {
|
|
161
|
+
const tag = getFlagValue(args, '--tag')
|
|
162
|
+
if (!tag) {
|
|
163
|
+
return err('release-notes publish: --tag <tag> is required')
|
|
164
|
+
}
|
|
165
|
+
const outputDirFlag = getFlagValue(args, '--output-dir')
|
|
166
|
+
const dryRun = args.includes('--dry-run')
|
|
167
|
+
const result = publishTag({ projectDir, tag, outputDir: outputDirFlag, dryRun })
|
|
168
|
+
if (result.status === 'skipped') {
|
|
169
|
+
return ok({
|
|
170
|
+
status: 'skipped',
|
|
171
|
+
command: 'release-notes-publish',
|
|
172
|
+
projectDir,
|
|
173
|
+
tag,
|
|
174
|
+
reason: result.reason,
|
|
175
|
+
notes_path: result.notesPath ?? null,
|
|
176
|
+
})
|
|
177
|
+
}
|
|
178
|
+
return ok({
|
|
179
|
+
status: 'ok',
|
|
180
|
+
command: 'release-notes-publish',
|
|
181
|
+
projectDir,
|
|
182
|
+
tag,
|
|
183
|
+
mode: result.mode,
|
|
184
|
+
dry_run: dryRun,
|
|
185
|
+
notes_path: result.notesPath,
|
|
186
|
+
})
|
|
187
|
+
}
|