@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
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Persona-voice posture loader
|
|
2
|
+
|
|
3
|
+
Aperant-authored posture loader consumed by `apt-release-notes/SKILL.md`.
|
|
4
|
+
Per the CLAUDE.md `appendices/` convention, this file augments the parent
|
|
5
|
+
skill's context — it is NOT independently router-invocable.
|
|
6
|
+
|
|
7
|
+
## Why this exists
|
|
8
|
+
|
|
9
|
+
The dev `CHANGELOG.md` (owned by `/apt:docs changelog`) describes WHAT
|
|
10
|
+
changed in engineering terms: file paths, function names, task IDs,
|
|
11
|
+
schema bumps. Release notes describe WHY the change matters to a
|
|
12
|
+
specific human. Same merge, different voice. The release-notes pipeline
|
|
13
|
+
hands you a structured fragment payload — your job is to tune the prose
|
|
14
|
+
so the line reads like product-marketing copy for the persona, not like
|
|
15
|
+
a commit message with the prefix stripped.
|
|
16
|
+
|
|
17
|
+
## Posture rules (apply when composing a `Release note: …` line)
|
|
18
|
+
|
|
19
|
+
1. **Verb first.** Start with the user's outcome — "See your shipped
|
|
20
|
+
builds in one tab" — not the implementation ("Added a `/builds` tab
|
|
21
|
+
to the dashboard"). The reader doesn't care that you added a tab;
|
|
22
|
+
they care that they can now see builds.
|
|
23
|
+
2. **Persona-specific framing.**
|
|
24
|
+
- **End-user persona (audience: `end-user`)** — plain language, no
|
|
25
|
+
jargon. Avoid framework names, file paths, API endpoint paths.
|
|
26
|
+
One sentence. Concrete value.
|
|
27
|
+
- **Developer / API persona (audience: `developer-api`)** — API + SDK
|
|
28
|
+
semantics are the value proposition. Naming a new method is good.
|
|
29
|
+
Linking the migration page is good. Don't hide the implementation
|
|
30
|
+
details; they ARE the user-visible surface for this persona.
|
|
31
|
+
- **Internal-team persona (audience: `internal-team`)** — lighter
|
|
32
|
+
polish, faster cadence. One sentence is still ideal but the
|
|
33
|
+
internal team is fine with a touch of process-jargon ("we now
|
|
34
|
+
auto-rotate session tokens") when it conveys real value.
|
|
35
|
+
3. **Triage bucket is your friend.** If you don't have strong signal
|
|
36
|
+
for the persona's framing — leave the line in the form the auto-draft
|
|
37
|
+
produced. The compile step will surface it under `## Needs Triage`
|
|
38
|
+
and the human curator can decide whether to ship it. Forced
|
|
39
|
+
confidence in the note voice is a regression class.
|
|
40
|
+
4. **Tone matches the persona's archetype.** "The Vibe Coder" persona
|
|
41
|
+
wants energy ("Now you can branch off any session in one click").
|
|
42
|
+
"The Reviewer" persona wants precision ("Branch-from-session now
|
|
43
|
+
preserves your review queue with exact ordering"). Read the
|
|
44
|
+
archetype field in personas.json before composing.
|
|
45
|
+
5. **No emoji unless the project's design taste explicitly calls for
|
|
46
|
+
it.** Aperant's house style is restrained; consumer-facing apps with
|
|
47
|
+
"playful" design tokens may differ — defer to the project's design
|
|
48
|
+
brief if one exists.
|
|
49
|
+
|
|
50
|
+
## What this loader does NOT cover
|
|
51
|
+
|
|
52
|
+
- Compile-time narrative beyond the per-PR sentence — that's the host
|
|
53
|
+
LLM's wider job at `/apt:release-notes --compile`, informed by
|
|
54
|
+
`PROJECT.md` Core Value, the personas roster, and the structured
|
|
55
|
+
per-tier sections the compiler writes.
|
|
56
|
+
- Translating between audience values — picking the right audience is
|
|
57
|
+
set at `/apt:setup` Batch 9 time. The audience field doesn't change
|
|
58
|
+
mid-release; voice changes only at the per-fragment level inside the
|
|
59
|
+
audience already chosen.
|
|
@@ -120,7 +120,20 @@ Also check for global defaults at `~/.aperant/defaults.json` — if they exist a
|
|
|
120
120
|
|
|
121
121
|
## 1b. Review Mode (existing config detected)
|
|
122
122
|
|
|
123
|
-
|
|
123
|
+
**Canonical signal.** The `onboarding.completed` marker inside `.aperant/config.json` is the canonical signal that a user has walked through the full /apt:setup batch sequence. Structural completeness of the JSON config does NOT imply onboarding completeness — `packages/framework/templates/config.json` ships with all keys populated to sensible defaults by design, so the JSON alone cannot disambiguate "fresh seed" from "fully tuned." Future template additions must not assume otherwise.
|
|
124
|
+
|
|
125
|
+
**Dispatch.** Read `.aperant/config.json` and inspect the `onboarding` block. Branch on the four marker states:
|
|
126
|
+
|
|
127
|
+
| Marker state | `/apt:setup` behaviour |
|
|
128
|
+
|-------------------------------------------------------------|-----------------------------------------------------------------------|
|
|
129
|
+
| No `onboarding` block (legacy pre-fix config) | enter **review mode** (preserve current behaviour; do NOT re-onboard) |
|
|
130
|
+
| `onboarding.seeded_from_template: true` AND `onboarding.completed: false` | enter **full Batch 1-8 flow** (treat as fresh setup) |
|
|
131
|
+
| `onboarding.completed: true` | enter **review mode** |
|
|
132
|
+
| `--reset` flag passed | enter **full Batch 1-8 flow** always (overrides marker) |
|
|
133
|
+
|
|
134
|
+
`init` writes the `seeded_from_template: true` / `completed: false` marker on fresh seed only (see `packages/framework/src/cli/commands/init.mjs::applyInterviewAnswers`). The upgrade path never writes the marker — that is why a legacy unmarked config routes to review mode.
|
|
135
|
+
|
|
136
|
+
If the dispatch lands you in review mode, continue below. If it lands you in the full flow, jump to **Step 2**.
|
|
124
137
|
|
|
125
138
|
First, run host detection to show resolved orchestration:
|
|
126
139
|
|
|
@@ -175,6 +188,8 @@ AskUserQuestion([
|
|
|
175
188
|
{ label: "Orchestration", description: "Phase delegation, review mode, debate mode, agent teams" },
|
|
176
189
|
{ label: "Team Collaboration", description: "Enable/disable team mode — shared task board, roadmap, members" },
|
|
177
190
|
{ label: "Visibility", description: "Artifact-sharing policy — what leaves your machine (roadmap, team.json, decisions, digests, tasks)" },
|
|
191
|
+
{ label: "Task Tracking", description: "Tracker backend (local-only / github-issues / app-inbox), publish to tracker, TDD default, prototype discipline" },
|
|
192
|
+
{ label: "Changelog & Release Notes", description: "Internal dev changelog + opt-in user-facing release notes (audience, persona filter)" },
|
|
178
193
|
{ label: "Everything", description: "Re-run full setup from scratch" },
|
|
179
194
|
{ label: "Done", description: "Keep current settings" }
|
|
180
195
|
]
|
|
@@ -189,7 +204,8 @@ AskUserQuestion([
|
|
|
189
204
|
- "Team Collaboration" → jump to **Step 3d** (Batch 5 only), then skip to **Step 4** and **Step 5**
|
|
190
205
|
- "Visibility" → jump to **Step 3e** (Batch 6 only), then skip to **Step 4** and **Step 5**
|
|
191
206
|
- "Task Tracking" → jump to **Step 3g** (Batch 8 only), then skip to **Step 4** and **Step 5**
|
|
192
|
-
- "
|
|
207
|
+
- "Changelog & Release Notes" → jump to **Step 3h** (Batch 9 only), then skip to **Step 4** and **Step 5**
|
|
208
|
+
- "Everything" → proceed to **Step 2** and run all batches (same as fresh setup). **Legacy-completes-Everything marker write:** if the starting config has no `onboarding` block (legacy unmarked config) and the user completes the full flow, write a full `onboarding` block at Step 5 as `{ completed: true, seeded_from_template: false, seeded_at: <ISO-8601 now> }` to record the explicit onboarding pass.
|
|
193
209
|
- "Done" → display "Settings unchanged." and exit
|
|
194
210
|
|
|
195
211
|
For selective edits, **pre-fill the existing values** as the default selection in each question. The user can confirm or change. Unchanged values are preserved — do not overwrite the entire config, only merge the changed section.
|
|
@@ -978,6 +994,121 @@ Add a row to the Step 8 table:
|
|
|
978
994
|
|
|
979
995
|
---
|
|
980
996
|
|
|
997
|
+
## 3h. Present Settings (Batch 9: Changelog & Release Notes)
|
|
998
|
+
|
|
999
|
+
This batch captures the **persona-aware changelog + release-notes** preferences introduced by `apt-release-notes/SKILL.md`. Two top-level questions: (1) keep an internal dev `CHANGELOG.md` and (2) publish user-facing release notes. Q2 defaults to **No** on internal/library projects (per spec ID-7: the presence of `personas.json` does NOT auto-enable release-notes — opt-in must be explicit).
|
|
1000
|
+
|
|
1001
|
+
If a host supports structured prompts (Claude Code), prefer `AskUserQuestion`. For non-structured hosts (Codex, Gemini-CLI plain mode), present each question as a numbered list and read the reply as a single number per question.
|
|
1002
|
+
|
|
1003
|
+
### 3h.1 Present the dev-changelog picker
|
|
1004
|
+
|
|
1005
|
+
```
|
|
1006
|
+
AskUserQuestion([
|
|
1007
|
+
{
|
|
1008
|
+
question: "Keep an internal dev changelog?",
|
|
1009
|
+
header: "Dev Changelog",
|
|
1010
|
+
options: [
|
|
1011
|
+
{ label: "Yes (Recommended)",
|
|
1012
|
+
description: "Auto-update CHANGELOG.md from commits on every /apt:ship. Dev-flavored, internal." },
|
|
1013
|
+
{ label: "No",
|
|
1014
|
+
description: "Skip the dev changelog entirely." }
|
|
1015
|
+
]
|
|
1016
|
+
}
|
|
1017
|
+
])
|
|
1018
|
+
```
|
|
1019
|
+
|
|
1020
|
+
### 3h.2 Present the release-notes picker
|
|
1021
|
+
|
|
1022
|
+
```
|
|
1023
|
+
AskUserQuestion([
|
|
1024
|
+
{
|
|
1025
|
+
question: "Publish user-facing release notes?",
|
|
1026
|
+
header: "Release Notes",
|
|
1027
|
+
options: [
|
|
1028
|
+
{ label: "No (Recommended for internal / library projects)",
|
|
1029
|
+
description: "Skip the user-facing surface entirely. Most projects pick this." },
|
|
1030
|
+
{ label: "Yes",
|
|
1031
|
+
description: "Enable /apt:release-notes — auto-draft fragments on /apt:ship, compile at tag time." }
|
|
1032
|
+
]
|
|
1033
|
+
}
|
|
1034
|
+
])
|
|
1035
|
+
```
|
|
1036
|
+
|
|
1037
|
+
### 3h.3 If Yes — present the audience + persona-filter sub-pickers
|
|
1038
|
+
|
|
1039
|
+
When Q2 = Yes, ask:
|
|
1040
|
+
|
|
1041
|
+
```
|
|
1042
|
+
AskUserQuestion([
|
|
1043
|
+
{
|
|
1044
|
+
question: "Who is the primary audience for these release notes?",
|
|
1045
|
+
header: "Audience",
|
|
1046
|
+
options: [
|
|
1047
|
+
{ label: "End user", description: "Plain-language, value-focused. Suits consumer apps." },
|
|
1048
|
+
{ label: "Developer / API", description: "Technical, references API + SDK semantics. Suits libraries." },
|
|
1049
|
+
{ label: "Internal team", description: "Lighter polish, faster cadence. Suits company-internal apps." }
|
|
1050
|
+
]
|
|
1051
|
+
},
|
|
1052
|
+
{
|
|
1053
|
+
question: "Which personas should drive the published notes?",
|
|
1054
|
+
header: "Persona Filter",
|
|
1055
|
+
options: [
|
|
1056
|
+
{ label: "Primary only",
|
|
1057
|
+
description: "Only primary-tier personas surface in published notes." },
|
|
1058
|
+
{ label: "Primary + Secondary (Recommended)",
|
|
1059
|
+
description: "Primary + secondary tiers. Default — balances signal with completeness." },
|
|
1060
|
+
{ label: "All personas",
|
|
1061
|
+
description: "Every tier surfaces. Useful for marketplaces / multi-sided products." }
|
|
1062
|
+
]
|
|
1063
|
+
}
|
|
1064
|
+
])
|
|
1065
|
+
```
|
|
1066
|
+
|
|
1067
|
+
If `.aperant/personas.json` is absent and Q2 = Yes, emit a non-blocking hint:
|
|
1068
|
+
|
|
1069
|
+
```
|
|
1070
|
+
[apt:setup] Note: .aperant/personas.json is missing. Release notes will work but every
|
|
1071
|
+
fragment will land in `## Needs Triage` until you run /apt:personas.
|
|
1072
|
+
```
|
|
1073
|
+
|
|
1074
|
+
This is a hint only — the user can still complete setup; the triage bucket is the intentional safety net (spec ID-6 never-silent-drop).
|
|
1075
|
+
|
|
1076
|
+
### 3h.4 Map answers to `.aperant/config.json`
|
|
1077
|
+
|
|
1078
|
+
Mapping rules (extend Section 4 below):
|
|
1079
|
+
|
|
1080
|
+
| User picked | Config write |
|
|
1081
|
+
|---|---|
|
|
1082
|
+
| Q1: Yes | `changelog.dev.enabled = true` |
|
|
1083
|
+
| Q1: No | `changelog.dev.enabled = false` |
|
|
1084
|
+
| Q2: No | `changelog.release_notes.enabled = false` (DEFAULT — leave audience + persona_filter at template defaults) |
|
|
1085
|
+
| Q2: Yes | `changelog.release_notes.enabled = true` and apply the audience + persona-filter sub-pickers |
|
|
1086
|
+
| Audience: End user | `changelog.release_notes.audience = "end-user"` |
|
|
1087
|
+
| Audience: Developer / API | `changelog.release_notes.audience = "developer-api"` |
|
|
1088
|
+
| Audience: Internal team | `changelog.release_notes.audience = "internal-team"` |
|
|
1089
|
+
| Persona filter: Primary only | `changelog.release_notes.persona_filter = "primary"` |
|
|
1090
|
+
| Persona filter: Primary + Secondary | `changelog.release_notes.persona_filter = "primary+secondary"` (DEFAULT) |
|
|
1091
|
+
| Persona filter: All personas | `changelog.release_notes.persona_filter = "all"` |
|
|
1092
|
+
|
|
1093
|
+
The remaining `release_notes` keys (`output_dir`, `require_pr_field`, `draft.*`, `compile.*`, `publish.*`) keep their template defaults from `packages/framework/templates/config.json` v5. Users wanting to override those edit `.aperant/config.json` directly — they are not surfaced through the Q-block to keep the picker short.
|
|
1094
|
+
|
|
1095
|
+
Migration rule: any existing config WITHOUT a `changelog` block gets defaults silently from the template migrator (`migrateConfigSchema` in `src/cli/config/schema-migrate.mjs`) — this is the same auto_added path Batch 8 relies on for the `task_tracking` / `coding` blocks.
|
|
1096
|
+
|
|
1097
|
+
### 3h.5 Review-Mode routing entry
|
|
1098
|
+
|
|
1099
|
+
Extend the "1b. Review Mode" routing table (§1b) to include "Changelog & Release Notes" as a selectable edit target — jumping back into this Batch 9 picker.
|
|
1100
|
+
|
|
1101
|
+
### 3h.6 Confirmation row
|
|
1102
|
+
|
|
1103
|
+
Add a row to the Step 8 table:
|
|
1104
|
+
```
|
|
1105
|
+
| Changelog & Release Notes | dev: {Yes/No}; release-notes: {Yes/No} (audience: {value}, persona-filter: {value}) |
|
|
1106
|
+
```
|
|
1107
|
+
|
|
1108
|
+
The selective-edit pass through Batch 9 MUST respect the §1b dispatch — it does NOT flip `onboarding.completed`. Marker writes are reserved for the full-flow / "Everything" branches.
|
|
1109
|
+
|
|
1110
|
+
---
|
|
1111
|
+
|
|
981
1112
|
## 4. Map Answers to Config
|
|
982
1113
|
|
|
983
1114
|
Map the user's selections to config values:
|
|
@@ -1034,7 +1165,7 @@ Note: task ids are slug-first since v0.6.6 — the date-format setting controls
|
|
|
1034
1165
|
- "File (default)" → `diagram.mode: "file"`
|
|
1035
1166
|
- "MCP (live canvas)" → `diagram.mode: "mcp"`
|
|
1036
1167
|
|
|
1037
|
-
|
|
1168
|
+
**Diagram MCP consent (per-user, FRAMEWORK-AUDIT-001).** The `diagram.mcpConsent` field is per-user and lives in `.aperant/config.local.json` (gitignored), NOT in the shared `config.json`. If `config.local.json` doesn't exist or has no `diagram.mcpConsent` field, seed `{diagram: {mcpConsent: null}}` in `config.local.json`. The `diagram.mode` field (file vs mcp) stays in `config.json` as team-shared policy and continues to seed there. Existing `mcpConsent` records in `config.local.json` are preserved across mode flips (changing the mode does not invalidate prior consent for the same host).
|
|
1038
1169
|
|
|
1039
1170
|
**Orchestration (Auto mode):** "Auto (Recommended)" → all three fields set to `"auto"`:
|
|
1040
1171
|
- `orchestration.phase_delegation: "auto"`
|
|
@@ -1149,6 +1280,7 @@ Display:
|
|
|
1149
1280
|
| Review Mode | {Auto / Agent Team / Sub-Agents / Sequential} |
|
|
1150
1281
|
| Debate Mode | {Auto / Agent Team / Sub-Agents / Sequential} |
|
|
1151
1282
|
| Visibility | {solo / team / oss} (overrides if any) |
|
|
1283
|
+
| Changelog & Release Notes | dev: {Yes/No}; release-notes: {Yes/No} (audience, persona-filter) |
|
|
1152
1284
|
| Saved as Defaults | {Yes/No} |
|
|
1153
1285
|
|
|
1154
1286
|
Config saved to: .aperant/config.json
|
|
@@ -1160,6 +1292,17 @@ Commands now use these settings automatically:
|
|
|
1160
1292
|
/apt:setup — change settings anytime
|
|
1161
1293
|
```
|
|
1162
1294
|
|
|
1295
|
+
### 8. (continued) Flip `onboarding.completed` on full-flow success
|
|
1296
|
+
|
|
1297
|
+
After a successful **full Batch 1-8 pass** (i.e. the user came in via `--reset`, via the fresh-seed marker-state `seeded_from_template:true / completed:false`, or via the review-mode "Everything" option), patch `.aperant/config.json` to set `onboarding.completed: true`. **Preserve** the existing `seeded_from_template` and `seeded_at` fields untouched — they carry forensic value (debug "did init upgrade overwrite my config?"). Concretely:
|
|
1298
|
+
|
|
1299
|
+
- Read the current `.aperant/config.json` you just wrote in **Step 5**.
|
|
1300
|
+
- Set `onboarding.completed = true`. If the `onboarding` block was already present, preserve the other fields verbatim.
|
|
1301
|
+
- If the starting config was a **legacy unmarked config** (no `onboarding` block — see §1b "Everything" routing), instead write the full block as `{ completed: true, seeded_from_template: false, seeded_at: <ISO-8601 now> }`.
|
|
1302
|
+
- Re-write `.aperant/config.json` with 2-space indentation.
|
|
1303
|
+
|
|
1304
|
+
Selective edits in review mode (Verification, Pipeline, Orchestration, Team Collaboration, Visibility, Task Tracking) DO NOT flip the marker — only a full Batch 1-8 pass does.
|
|
1305
|
+
|
|
1163
1306
|
### 8a. PreCompact Hook Status
|
|
1164
1307
|
|
|
1165
1308
|
After confirming settings, check whether the framework's PreCompact hook is
|
package/skills/apt-ship/SKILL.md
CHANGED
|
@@ -146,6 +146,53 @@ Format as a markdown table:
|
|
|
146
146
|
|
|
147
147
|
If no plan exists (ad-hoc task), list commits without subtask mapping.
|
|
148
148
|
|
|
149
|
+
## 2.6 Release-note auto-draft (persona-aware release-notes opt-in)
|
|
150
|
+
|
|
151
|
+
This section runs ONLY when `.aperant/config.json` has `changelog.release_notes.enabled === true`. When the toggle is `false` (the default for the 9-of-10 internal projects that never publish user-facing notes), skip the section entirely — do NOT compute the feature diff and do NOT touch the PR body draft.
|
|
152
|
+
|
|
153
|
+
Read the toggle:
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
rn_enabled=$(jq -r '.changelog.release_notes.enabled // false' .aperant/config.json)
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
If `rn_enabled !== "true"`, jump to Section 3.
|
|
160
|
+
|
|
161
|
+
### 2.6.1 Compute the feature-registry diff
|
|
162
|
+
|
|
163
|
+
The auto-draft needs to know which features the PR's commits touched. Use the same `features-audit` surface that `apt:execute` uses post-step:
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
base=$(node packages/framework/bin/apt-tools.mjs task get . --id {task-id} | jq -r '.base_branch // "main"')
|
|
167
|
+
feature_diff=$(node packages/framework/bin/apt-tools.mjs features-audit . --diff-since "$base" --json)
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
The envelope shape (see `packages/framework/src/cli/commands/features-audit.mjs`) returns the list of touched features with their declared `personas: []` field (when the registry's feature row carries one).
|
|
171
|
+
|
|
172
|
+
### 2.6.2 Decide draft vs block via the helper
|
|
173
|
+
|
|
174
|
+
Hand the diff + the project's personas envelope + the filter mode to `ship-autodraft.mjs`. The CLI returns one of three envelopes:
|
|
175
|
+
|
|
176
|
+
- `{status: 'draft', line, persona, matchedFeatures}` — append `line` to the PR body draft built in Section 3.
|
|
177
|
+
- `{status: 'block', reason, message}` — fire the block path below.
|
|
178
|
+
- `{status: 'warn', reason, message}` — log the warning and proceed without the line.
|
|
179
|
+
|
|
180
|
+
The block path triggers when `release_notes.require_pr_field === true` (default) AND auto-draft failed (no feature match OR no persona signal). The exact error string is locked by AC7:
|
|
181
|
+
|
|
182
|
+
```
|
|
183
|
+
[APT] No release-note signal found — author manual note in PR body or label PR no-notes
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
When the block fires, STOP — do NOT proceed to `gh pr create`. The user must either (a) author a manual `Release note: …` line in the PR body via `git commit --amend` (or by editing the PR body after open), or (b) label the PR `no-notes` via `gh pr edit --add-label no-notes` and re-run `/apt:ship`. The label-based escape hatch keeps the gate from blocking infra-only PRs that genuinely have no user-facing impact.
|
|
187
|
+
|
|
188
|
+
### 2.6.3 Inject the line into the PR body draft
|
|
189
|
+
|
|
190
|
+
When the helper returns `status: 'draft'`, append the `line` value verbatim to the PR body draft assembled in Section 3, immediately under the `## Summary` heading. The line is one sentence, persona-voice-tuned, suitable for a user-facing surface.
|
|
191
|
+
|
|
192
|
+
### 2.6.4 Helper contract (for testability)
|
|
193
|
+
|
|
194
|
+
The helper lives at `packages/framework/src/cli/release-notes/ship-autodraft.mjs`. It is a pure function — given the feature-diff envelope + personas envelope + filter mode + the `require_pr_field` toggle, it returns the decision. The `decideAutodraft` export is unit-tested via TD-4; the SKILL.md body wires it without re-deriving the decision logic.
|
|
195
|
+
|
|
149
196
|
## 3. Build PR Body
|
|
150
197
|
|
|
151
198
|
**Title:** Derived from task description. Keep it short (<70 characters).
|
|
@@ -365,19 +412,30 @@ ci_watch_armed=$(echo "$ship_update" | jq -r '.ci_watch_armed // false')
|
|
|
365
412
|
ci_watch_skip_reason=$(echo "$ship_update" | jq -r '.ci_watch_skip_reason // empty')
|
|
366
413
|
ci_watch_host=$(echo "$ship_update" | jq -r '.ci_watch.host // "unknown"')
|
|
367
414
|
ci_watch_deadline=$(echo "$ship_update" | jq -r '.ci_watch.deadline // empty')
|
|
415
|
+
ci_watch_scheduler_handle=$(echo "$ship_update" | jq -r '.scheduler_handle // empty')
|
|
368
416
|
host_cron=$(node packages/framework/bin/apt-tools.mjs host-detect | jq -r '.host.capabilities.cron')
|
|
369
417
|
```
|
|
370
418
|
|
|
371
419
|
**Autonomy interaction** (apply BEFORE branching, same rule as 7.1): when `autonomy === 3` (YOLO), degrade `ask` → `auto`.
|
|
372
420
|
|
|
373
|
-
**Branch on the task-update envelope
|
|
421
|
+
**Branch on the task-update envelope** (FRAMEWORK-BUG-018 — Track 1: distinguish state-recording from wake-registration):
|
|
374
422
|
|
|
375
|
-
- **`ci_watch_armed=true` AND `host_cron=true
|
|
423
|
+
- **`ci_watch_armed=true` AND `host_cron=true` AND `ci_watch_scheduler_handle` non-empty** — emit:
|
|
376
424
|
|
|
377
425
|
```
|
|
378
426
|
[APT:ship] CI watcher armed (host cron: {ci_watch_host}). Deadline: {ci_watch_deadline}. Stop: /apt:watch-ci --stop --pr {N}.
|
|
379
427
|
```
|
|
380
428
|
|
|
429
|
+
This is the only honest "armed" case — a scheduler handle proves the wake is registered.
|
|
430
|
+
|
|
431
|
+
- **`ci_watch_armed=true` AND `host_cron=true` AND `ci_watch_scheduler_handle` empty** — emit:
|
|
432
|
+
|
|
433
|
+
```
|
|
434
|
+
[APT:ship] CI watcher state created (host: {ci_watch_host}). Register a wake-up to drive ticks: CronCreate every ~3min running '/apt:watch-ci --tick --pr {N}'. Deadline: {ci_watch_deadline}. Stop: /apt:watch-ci --stop --pr {N}.
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
State is recorded but no scheduler is registered — the orchestrator MUST register a wake or ticks will never fire. Track 2 (auto-wake on arm) will populate `scheduler_handle` and route into the first branch instead.
|
|
438
|
+
|
|
381
439
|
- **`ci_watch_armed=true` AND `host_cron=false`** — emit:
|
|
382
440
|
|
|
383
441
|
```
|
|
@@ -395,10 +453,11 @@ host_cron=$(node packages/framework/bin/apt-tools.mjs host-detect | jq -r '.host
|
|
|
395
453
|
On `Y` (or empty enter), call:
|
|
396
454
|
|
|
397
455
|
```bash
|
|
398
|
-
node packages/framework/bin/apt-tools.mjs ci-watch start . --pr {pr_number} --task-id {task-id} --host {cli}
|
|
456
|
+
arm_envelope=$(node packages/framework/bin/apt-tools.mjs ci-watch start . --pr {pr_number} --task-id {task-id} --host {cli})
|
|
457
|
+
ci_watch_scheduler_handle=$(echo "$arm_envelope" | jq -r '.scheduler_handle // empty')
|
|
399
458
|
```
|
|
400
459
|
|
|
401
|
-
Then
|
|
460
|
+
Then route into the same four-way logic above — branch 1 (armed with handle), branch 2 (state-created, needs wake), or branch 3 (host has no cron). On `N` or any other response, proceed silently to Section 8.
|
|
402
461
|
|
|
403
462
|
- **`ci_watch_skip_reason=disabled`** — silent. Proceed directly to Section 8.
|
|
404
463
|
|
package/skills/apt-spar/SKILL.md
CHANGED
|
@@ -129,14 +129,26 @@ If `--with` resolves to the same CLI as the host (e.g. `--with claude` when `hos
|
|
|
129
129
|
|
|
130
130
|
Do NOT proceed; self-sparring is sycophancy by construction (the host cannot objectively challenge itself). This check runs after `host-detect` and before any partner availability check.
|
|
131
131
|
|
|
132
|
-
**Partner
|
|
132
|
+
**Partner transport ladder (FRAMEWORK-BUG-024).** Read `host.partner_transports.<partner>` from the host-detect envelope (`jq '.host.partner_transports.codex'`, etc.). Each partner has a 3-rung ladder evaluated in preference order — **MCP-first** for cleaner tool-call integration, with automatic fallthrough to plugin shim then raw CLI:
|
|
133
|
+
|
|
134
|
+
1. **Rung 1 — MCP tool** (`mcp__<partner>-mcp__*`, e.g. `mcp__codex-mcp__codex`). Detected if the host exposes its deferred-tool manifest via `CLAUDE_MCP_DEFERRED_TOOLS` and the tool is registered. When `detected: "runtime-host-only"` the framework process cannot probe the host's tool registry from outside — treat as candidate and attempt the tool call; if the tool is genuinely unavailable in the host's visible registry, fall through to rung 2.
|
|
135
|
+
2. **Rung 2 — Plugin shim** (`${CLAUDE_PLUGIN_ROOT}/scripts/<partner>-companion.mjs`). Codex only (claude / gemini have no plugin shim).
|
|
136
|
+
3. **Rung 3 — Raw CLI on `$PATH`** (`codex exec`, `claude -p`, `gemini -m`).
|
|
137
|
+
|
|
138
|
+
Walk the ladder in order; pick the first rung with `detected: true` (or `"runtime-host-only"` for MCP). **MCP rung has a hard timeout** — default 60000ms, configurable via `apt-spar.mcp_timeout_ms` in `.aperant/config.json`. Empirical: the Codex MCP `claude_code` tool has been observed to hang past 120s. On MCP timeout, **automatically fall through to rung 2 or rung 3** — do NOT fail the whole spar. Treat MCP timeout as "rung unavailable for this invocation."
|
|
139
|
+
|
|
140
|
+
If all three rungs are unavailable (every rung `detected: false`, or MCP `runtime-host-only` timed out AND rungs 2+3 are `detected: false`), print:
|
|
133
141
|
|
|
134
142
|
```
|
|
135
|
-
[apt:spar] partner `<id>` unavailable.
|
|
136
|
-
-
|
|
137
|
-
-
|
|
143
|
+
[apt:spar] partner `<id>` unavailable. Tried:
|
|
144
|
+
- MCP tool `mcp__<partner>-mcp__*`: <not loaded in this host | runtime-host-only + timed out at <ms>ms>
|
|
145
|
+
- Plugin shim `${CLAUDE_PLUGIN_ROOT}/scripts/<partner>-companion.mjs`: <missing reason>
|
|
146
|
+
- Raw CLI `<bin>` on $PATH: not found
|
|
147
|
+
Install any one of the three or re-run with `--with <other-id>`.
|
|
138
148
|
```
|
|
139
149
|
|
|
150
|
+
The failure message MUST name all three transports tried (`mcp`, `plugin shim`, `raw CLI`) so the user can pick the cheapest fix.
|
|
151
|
+
|
|
140
152
|
Do NOT silently degrade to the host (sparring with yourself is sycophancy by construction).
|
|
141
153
|
|
|
142
154
|
## 4. Frame the Round
|
|
@@ -159,13 +171,26 @@ The frame is one shot per round — no multi-turn conversation with the partner
|
|
|
159
171
|
|
|
160
172
|
**Wrap every partner Bash call with an explicit timeout** (default 90000ms, override via `--timeout-ms`). The empirical reason: the Codex MCP `claude_code` tool has been observed to hang past 120s while the `claude -p` CLI returns in seconds. A hung partner CLI must NOT stall the spar loop.
|
|
161
173
|
|
|
162
|
-
The table below shows the canonical command shape; the actual invocation MUST use the heredoc pattern in the next subsection — never raw double-quoted interpolation.
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
174
|
+
The table below shows the canonical command shape per (partner × rung); the actual invocation for shell-based rungs (plugin shim, raw CLI) MUST use the heredoc pattern in the next subsection — never raw double-quoted interpolation. The MCP rung is a structured tool call — no shell quoting concern applies.
|
|
175
|
+
|
|
176
|
+
<!--
|
|
177
|
+
Future contributor: the `codex exec` flag below is taken from the precedent
|
|
178
|
+
at `packages/framework/skills/apt-pr-review/SKILL.md:1084`. Codex CLI flag
|
|
179
|
+
surface evolves; verify with `codex --help` at execution time and update
|
|
180
|
+
this table if the non-interactive invocation shape changes.
|
|
181
|
+
-->
|
|
182
|
+
|
|
183
|
+
| Partner | Rung | Command | Notes |
|
|
184
|
+
|---|---|---|---|
|
|
185
|
+
| codex | mcp | tool call: `mcp__codex-mcp__codex({prompt})` | Preferred. 60s timeout (configurable `apt-spar.mcp_timeout_ms`); on timeout fall through to plugin then CLI. |
|
|
186
|
+
| codex | plugin | `node "${CLAUDE_PLUGIN_ROOT}/scripts/codex-companion.mjs" task "$(cat <<'APT_SPAR_PROMPT_EOF' ... APT_SPAR_PROMPT_EOF\n)"` | **NO `--write`. NO `--effort` override.** Per SPAR-04 — spar is conversation, not patching. |
|
|
187
|
+
| codex | cli | `codex exec "$(cat <<'APT_SPAR_PROMPT_EOF' ... APT_SPAR_PROMPT_EOF\n)"` | Precedent: `apt-pr-review/SKILL.md:1084`. Verify with `codex --help`. |
|
|
188
|
+
| claude | mcp | tool call: `mcp__claude-code-mcp__claude_code({prompt})` | Preferred. Same 60s timeout + fallthrough rule. |
|
|
189
|
+
| claude | plugin | n/a | No plugin shim exists for Claude. Skip this rung. |
|
|
190
|
+
| claude | cli | `claude -p "$(cat <<'APT_SPAR_PROMPT_EOF' ... APT_SPAR_PROMPT_EOF\n)" --output-format text` | Symmetric. Claude has no `--write` equivalent — asymmetric flags are a smell. |
|
|
191
|
+
| gemini | mcp | tool call: `mcp__gemini-mcp__*({prompt})` | Preferred. Same 60s timeout + fallthrough rule. Any `mcp__gemini-mcp__*` tool satisfies the rung. |
|
|
192
|
+
| gemini | plugin | n/a | No plugin shim exists for Gemini. Skip this rung. |
|
|
193
|
+
| gemini | cli | `gemini -m <model-from-router.llm.providers.gemini-cli> "$(cat <<'APT_SPAR_PROMPT_EOF' ... APT_SPAR_PROMPT_EOF\n)"` | Read the exact model name from `.aperant/config.json` at runtime — do not hardcode. |
|
|
169
194
|
|
|
170
195
|
Set the Bash tool's `timeout` parameter to `--timeout-ms` (default 90000).
|
|
171
196
|
|
|
@@ -15,7 +15,7 @@ execution_modes:
|
|
|
15
15
|
- auto
|
|
16
16
|
- step
|
|
17
17
|
allowed-tools: "Bash, Read, Write"
|
|
18
|
-
argument-hint: "apt:update"
|
|
18
|
+
argument-hint: "apt:update [--from-source]"
|
|
19
19
|
gates: []
|
|
20
20
|
---
|
|
21
21
|
|
|
@@ -42,35 +42,101 @@ The cache file is at `~/.cache/aperant/apt-update-check.json`. It contains `runt
|
|
|
42
42
|
|
|
43
43
|
## 1. Read current state
|
|
44
44
|
|
|
45
|
+
<!-- TODO(followup): support PATH-only / npx-only kernel installs. The
|
|
46
|
+
hardcoded `.aperant/deps/...` path below fails users who don't have a
|
|
47
|
+
project-local kernel directory. Tracked as a separate task. -->
|
|
45
48
|
```bash
|
|
46
49
|
node .aperant/deps/node_modules/@aperant/framework/bin/apt-tools.mjs check-version . --json
|
|
47
50
|
```
|
|
48
51
|
|
|
49
|
-
Parse the JSON. Capture:
|
|
52
|
+
Parse the JSON. From 0.6.7 the envelope merges the SessionStart worker's cache (when fresh — written within the last hour to `~/.cache/aperant/apt-update-check.json`). Capture:
|
|
53
|
+
|
|
50
54
|
- `framework.installed_version` — what's currently on disk
|
|
51
|
-
- `framework.latest_version` — what
|
|
55
|
+
- `framework.latest_version` — what npm reports as the latest published version (MAY BE `null` when the worker cache is stale / missing / wrong schema)
|
|
52
56
|
- `runtimes[]` — list of installed runtime ids + per-runtime `stale_files` count
|
|
53
57
|
- `stale_files_total` — drift tally across runtimes
|
|
58
|
+
- `update_available` — true when worker cache is fresh AND `installed_version < latest_version`
|
|
59
|
+
- `dev_install_ahead` — true when local kernel is ahead of published (typical local dev)
|
|
60
|
+
- `not_published` — true when npm returned 404 (package not yet on registry)
|
|
61
|
+
- `lookup_error` — non-null when npm view failed for any non-404 reason (timeout, ENETDOWN, etc.)
|
|
62
|
+
|
|
63
|
+
The envelope is **single-shape** — every field above is always present. When the worker cache is stale or missing, `latest_version` is `null`, the four booleans are `false`, `stale_files_total` falls back to a local-only count, and `lookup_error` is `null`. Your parser doesn't need to branch on cache freshness.
|
|
54
64
|
|
|
55
65
|
If `runtimes[]` is empty, tell the user they haven't installed Aperant into any runtime yet and suggest `apt-tools init . --claude` (or their preferred runtime). Stop.
|
|
56
66
|
|
|
67
|
+
## 1.5. Bump the local kernel from npm when newer is available
|
|
68
|
+
|
|
69
|
+
The Aperant kernel lives at `.aperant/deps/node_modules/@aperant/framework/`. `apt-tools init` re-installs runtime files at THE LOCAL KERNEL'S VERSION — so a stale kernel means `/apt:update` re-stamps manifests at the OLD version even when npm has a newer release. Bump the kernel BEFORE invoking init.
|
|
70
|
+
|
|
71
|
+
First, resolve `latest_version` when the envelope had `null` (stale cache).
|
|
72
|
+
|
|
73
|
+
**Timeout enforcement (ID-04):** The `npm view` call must complete within 10 s — never block the user longer than the SessionStart worker would. Use npm's `--fetch-timeout` flag as the primary mechanism (universally portable across all npm versions); it sets the HTTP fetch timeout in milliseconds:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
npm view @aperant/framework version --fetch-timeout=10000 --fetch-retries=0 --json --silent 2>/dev/null || true
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
On Linux/macOS with GNU coreutils installed, you MAY additionally wrap with a hard shell-level timer: `timeout 10 npm view ...` (Linux) or `gtimeout 10 npm view ...` (macOS via `brew install coreutils`). Shell-level wrapping is optional — `--fetch-timeout=10000` alone is sufficient because it is universal. Do NOT rely solely on the shell `timeout` command without the npm flag, as it is not available on all systems.
|
|
80
|
+
|
|
81
|
+
If npm exits non-zero or times out, surface a one-line warning (`⚠ npm view failed — using local kernel`) and proceed; the local kernel still works for the runtime refresh, just at the older version.
|
|
82
|
+
|
|
83
|
+
**Security (SEC-001):** Before interpolating `latest_version` into any shell command, verify it matches the semver pattern `^\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$`. If it does not match, treat it as missing and fall back to the explicit version pinning path (proceed with the local kernel version, surface `⚠ latest_version invalid — using local kernel`). This protects against a poisoned cache entry reaching a shell command.
|
|
84
|
+
|
|
85
|
+
When `installed_version < latest_version`, bump:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
npm install --prefix .aperant/deps @aperant/framework@<latest_version> --no-save --no-package-lock --no-audit --no-fund
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
On `npm 404` (`E404` in stderr) treat it as `not_published` and skip the kernel bump (proceed to step 3). On any other npm failure, surface the npm stderr verbatim and STOP — do NOT proceed to init with a half-installed kernel.
|
|
92
|
+
|
|
93
|
+
## 1.6. Self-host case — bump from local workspace (FRAMEWORK-BUG-017)
|
|
94
|
+
|
|
95
|
+
When the user invokes `/apt:update --from-source` OR the route envelope's `update_check.reason` mentions `workspace source ahead of installed kernel`, the source of truth is the local `packages/framework/` workspace — NOT npm. This is the Aperant-contributor self-host case: a same-PR framework fix bumps the workspace `packages/framework/package.json:version` but the kernel under `.aperant/deps/node_modules/@aperant/framework/` still reflects the previous version. Step 1.5's `npm view` would fetch the published version (still the OLD one), so we must skip it.
|
|
96
|
+
|
|
97
|
+
The reliable bump mechanism mirrors what `runKernelInstall` does today — `pnpm pack` produces a versioned tarball, then `npm install --prefix` consumes it. Symlinking is fragile across re-installs and `pnpm install --filter` doesn't relocate the published artifact into `.aperant/deps`.
|
|
98
|
+
|
|
99
|
+
Walk up from the project root looking for a sibling `packages/framework/package.json`. When found AND its `name === "@aperant/framework"`:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
# 1. Pack the workspace source into a versioned tarball.
|
|
103
|
+
cd <workspace-root>
|
|
104
|
+
pnpm --filter @aperant/framework pack --pack-destination /tmp
|
|
105
|
+
|
|
106
|
+
# 2. Install the tarball into the project's kernel root.
|
|
107
|
+
cd <project-root>
|
|
108
|
+
npm install --prefix .aperant/deps /tmp/aperant-framework-<version>.tgz --no-save --no-package-lock --no-audit --no-fund
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Replace `<version>` with the workspace `package.json:version` you just read. The tarball name follows npm's `pack` convention (`@aperant/framework@X.Y.Z` → `aperant-framework-X.Y.Z.tgz`).
|
|
112
|
+
|
|
113
|
+
If `--from-source` was passed but no `packages/framework/package.json` is found anywhere above the project root, surface a one-line error (`⚠ --from-source: no workspace package.json found — falling back to npm`) and proceed with the standard step 1.5 npm path.
|
|
114
|
+
|
|
115
|
+
Then proceed to step 3 to re-install the runtime files at the new kernel version. Skip step 4 (cache clear) — the cache is keyed on `latest_version` from npm, and a `--from-source` bump never wrote to it, so there's nothing to invalidate.
|
|
116
|
+
|
|
57
117
|
## 2. Classify the update scenario
|
|
58
118
|
|
|
59
|
-
Pick ONE of three scenarios based on what you
|
|
119
|
+
Pick ONE of three scenarios based on what you parsed in step 1:
|
|
60
120
|
|
|
61
|
-
- **A. Version bump available** — `installed_version !== latest_version` (regardless of drift). Users see this most often.
|
|
62
|
-
- **B. Drift only** — `installed_version === latest_version` but `stale_files_total > 0`. User hand-edited an installed file or something was partially overwritten.
|
|
63
|
-
- **C. Nothing to do** — `installed_version === latest_version` AND `stale_files_total === 0`. Still re-run init to refresh
|
|
121
|
+
- **A. Version bump available** — `installed_version !== latest_version` (regardless of drift). Users see this most often. Step 1.5 bumped the kernel; step 3 re-stamps manifests at the new version.
|
|
122
|
+
- **B. Drift only** — `installed_version === latest_version` but `stale_files_total > 0`. User hand-edited an installed file or something was partially overwritten. Step 1.5 was a no-op; step 3 still needs `--force-runtimes` to re-stamp.
|
|
123
|
+
- **C. Nothing to do** — `installed_version === latest_version` AND `stale_files_total === 0`. Still re-run init with `--force-runtimes` to refresh manifests + cache, but tell the user plainly that no version change happened.
|
|
64
124
|
|
|
65
125
|
## 3. Re-install the currently-installed runtimes
|
|
66
126
|
|
|
67
|
-
Build the flags list from the runtime ids (e.g. `--claude --codex`) and
|
|
127
|
+
Build the flags list from the runtime ids (e.g. `--claude --codex`) and pass `--force-runtimes` so init bypasses the `upgrade_noop` short-circuit (Bug 2 fix, 0.6.7) and re-stamps every manifest:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
node .aperant/deps/node_modules/@aperant/framework/bin/apt-tools.mjs init . --claude --force-runtimes
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Example with multiple runtimes installed:
|
|
68
134
|
|
|
69
135
|
```bash
|
|
70
|
-
node .aperant/deps/node_modules/@aperant/framework/bin/apt-tools.mjs init .
|
|
136
|
+
node .aperant/deps/node_modules/@aperant/framework/bin/apt-tools.mjs init . --claude --codex --force-runtimes
|
|
71
137
|
```
|
|
72
138
|
|
|
73
|
-
**Do NOT add `--yes`, `--solo`, `--team`, or any other Class A flag.** This command is meant to refresh runtime files only — adding `--yes` triggers a config schema migration that can silently flip `share.visibility` to `solo` and re-write `.gitignore`, which is exactly the 0.6.0/0.6.1 dogfood bug. If `init` returns a `requires_input` envelope (schema drift), surface it to the user verbatim and stop; let them decide team vs solo with the explicit flag. From 0.6.4 onward `--yes` infers visibility from existing git tracking, but the skill must still not add it — keep this command scoped to runtime refresh.
|
|
139
|
+
**Do NOT add `--yes`, `--solo`, `--team`, or any other Class A flag.** This command is meant to refresh runtime files only — adding `--yes` triggers a config schema migration that can silently flip `share.visibility` to `solo` and re-write `.gitignore`, which is exactly the 0.6.0/0.6.1 dogfood bug. `--force-runtimes` is the ONLY new flag this skill adds. If `init` returns a `requires_input` envelope (schema drift), surface it to the user verbatim and stop; let them decide team vs solo with the explicit flag. From 0.6.4 onward `--yes` infers visibility from existing git tracking, but the skill must still not add it — keep this command scoped to runtime refresh.
|
|
74
140
|
|
|
75
141
|
Parse the init JSON. Capture for each runtime:
|
|
76
142
|
- `install_root` (shortened to the basename, e.g. `.claude`)
|
|
@@ -164,5 +230,6 @@ Runtime files were still refreshed; `/apt` will still work from PATH if `apt-too
|
|
|
164
230
|
- **Worker hasn't run yet**: the cache file is missing. That's fine — run check-version directly (step 1 is robust to a missing cache). Proceed normally.
|
|
165
231
|
- **Re-install fails (non-zero exit from init)**: surface the JSON `error` field verbatim in a single line, then stop. Do NOT delete the cache file — the chip should remain visible so the user sees the failure persists.
|
|
166
232
|
- **Kernel install fails but runtime install succeeds**: see Scenario D above. Do not treat this as a total failure; the user's skills still work via PATH fallback.
|
|
233
|
+
- **CLAUDE.md / AGENTS.md foreign rows (exit 2, `instruction-file.foreign-rows`)** — 0.7.0+ — init now refuses to silently clobber custom rows in the `<!-- APT:framework-* -->` block. If `init` exits 2 with `code: instruction-file.foreign-rows`, the `foreignRowIds[]` field lists the rows the generator did NOT produce. Two paths: (a) remove the foreign rows from CLAUDE.md / AGENTS.md by hand and re-run init, or (b) re-run with `--force-overwrite-catalog` to regenerate anyway. Unattended upgrade flows should detect this code and surface it to the operator — silent bypass is a regression.
|
|
167
234
|
|
|
168
235
|
</edge_cases>
|
|
@@ -69,8 +69,8 @@ Watcher already running for PR {N} (held by sessionId={x}, pid={y}); exiting.
|
|
|
69
69
|
|
|
70
70
|
For the `--tick` mode, execute exactly the following sequence — once — inside this single skill invocation:
|
|
71
71
|
|
|
72
|
-
1. **Poll.** Call `gh pr checks <N> --json name,bucket,state,conclusion,detailsUrl` and `gh pr view <N> --json state,reviewDecision`.
|
|
73
|
-
2. **Classify + state update.** Call `apt-tools ci-watch tick {worktree} --pr {N}`. The cmd internally:
|
|
72
|
+
1. **Poll.** Call `gh pr checks <N> --json name,bucket,state,conclusion,detailsUrl` and `gh pr view <N> --json state,reviewDecision`. Combine both JSON outputs into a single file shaped `{checks: <pr-checks-json>, prMetadata: <pr-view-json>}` (for example with `jq -s '{checks: .[0], prMetadata: .[1]}'`) and save it as `{captured.json}` (any path under the worktree is fine). On any `gh` failure, omit the file and let the cmd's `tick` route to `infra-backoff` (it surfaces the same action when no payload is available).
|
|
73
|
+
2. **Classify + state update.** Call `apt-tools ci-watch tick {worktree} --pr {N} --payload-file {captured.json}`. The cmd reads `{captured.json}` from disk and runs the classifier — it does NOT shell out to `gh` itself (that boundary stays with the orchestrator skill; see commands/ci-watch.mjs:15). The cmd internally:
|
|
74
74
|
- acquires the per-PR lock (BUSY → exit 2 → step §2);
|
|
75
75
|
- reads the per-PR state file;
|
|
76
76
|
- runs the stop-matrix classifier (`packages/framework/src/cli/ci-watch/stop-matrix.mjs`);
|
|
@@ -87,7 +87,10 @@ For the `--tick` mode, execute exactly the following sequence — once — insid
|
|
|
87
87
|
|
|
88
88
|
When `action ∈ {green, merged, closed, attempts-cap, wall-cap, identical-log}`:
|
|
89
89
|
|
|
90
|
-
- **green** — print "CI green for PR {N}." If `ci_watch.auto_merge_on_green` is `true` in `.aperant/config.json`, run `gh pr merge {N} --auto --squash` then suggest `/apt:close-task`.
|
|
90
|
+
- **green** — print "CI green for PR {N}." If `ci_watch.auto_merge_on_green` is `true` in `.aperant/config.json`, run `gh pr merge {N} --auto --squash` then suggest `/apt:close-task`.
|
|
91
|
+
- **DO NOT pass `--delete-branch`** to `gh pr merge` from this auto-merge path — ever, unconditionally. The watcher's cwd is the task worktree, where `gh`'s `--delete-branch` post-merge cleanup tries to `git switch <base>` and fails because `<base>` is checked out in the primary worktree. The remote merge has already succeeded by then; local branch + worktree cleanup is `/apt:close-task`'s job via `apt-tools task close-merged` → `computeWorktreeCleanup` (see FRAMEWORK-BUG-019).
|
|
92
|
+
|
|
93
|
+
Else print the exact D-08 text:
|
|
91
94
|
|
|
92
95
|
```
|
|
93
96
|
✅ CI green — merge in browser or run /apt:close-task post-merge
|