@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
package/package.json
CHANGED
|
@@ -1,134 +1,134 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}
|
|
2
|
+
"name": "@aperant/framework",
|
|
3
|
+
"version": "0.7.0",
|
|
4
|
+
"description": "AI coding framework — composable skills for planning, executing, verifying, and reviewing code with any LLM provider. Works as Claude Code commands, Codex tasks, or programmatically.",
|
|
5
|
+
"author": "Mikalsen AI <hello@mikalsen.ai>",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"bin": {
|
|
10
|
+
"framework": "./bin/apt-tools.mjs",
|
|
11
|
+
"apt-tools": "./bin/apt-tools.mjs",
|
|
12
|
+
"apt-proof-video": "./bin/apt-proof-video.mjs"
|
|
13
|
+
},
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"import": "./dist/index.js"
|
|
18
|
+
},
|
|
19
|
+
"./types": {
|
|
20
|
+
"types": "./dist/types/index.d.ts",
|
|
21
|
+
"import": "./dist/types/index.js"
|
|
22
|
+
},
|
|
23
|
+
"./cost": {
|
|
24
|
+
"types": "./dist/cost/index.d.ts",
|
|
25
|
+
"import": "./dist/cost/index.js"
|
|
26
|
+
},
|
|
27
|
+
"./mappers": {
|
|
28
|
+
"types": "./dist/mappers/index.d.ts",
|
|
29
|
+
"import": "./dist/mappers/index.js"
|
|
30
|
+
},
|
|
31
|
+
"./schemas": {
|
|
32
|
+
"types": "./dist/schemas/index.d.ts",
|
|
33
|
+
"import": "./dist/schemas/index.js"
|
|
34
|
+
},
|
|
35
|
+
"./standalone": {
|
|
36
|
+
"types": "./dist/standalone/index.d.ts",
|
|
37
|
+
"import": "./dist/standalone/index.js"
|
|
38
|
+
},
|
|
39
|
+
"./coordination/event-schema": {
|
|
40
|
+
"types": "./src/cli/coordination/event-schema.d.ts",
|
|
41
|
+
"import": "./src/cli/coordination/event-schema.mjs"
|
|
42
|
+
},
|
|
43
|
+
"./roadmap/conductor-view": {
|
|
44
|
+
"types": "./src/cli/roadmap/conductor-view.d.ts",
|
|
45
|
+
"import": "./src/cli/roadmap/conductor-view.mjs"
|
|
46
|
+
},
|
|
47
|
+
"./design/scan": {
|
|
48
|
+
"import": "./src/cli/design/scan.mjs"
|
|
49
|
+
},
|
|
50
|
+
"./design/extract-repo": {
|
|
51
|
+
"import": "./src/cli/design/extract-repo.mjs"
|
|
52
|
+
},
|
|
53
|
+
"./design/synthesize": {
|
|
54
|
+
"import": "./src/cli/design/synthesize.mjs"
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
"files": [
|
|
58
|
+
"dist/",
|
|
59
|
+
"bin/",
|
|
60
|
+
"src/cli/",
|
|
61
|
+
"src/cost/",
|
|
62
|
+
"commands/",
|
|
63
|
+
"skills/",
|
|
64
|
+
"agents/",
|
|
65
|
+
"prompts/",
|
|
66
|
+
"templates/",
|
|
67
|
+
"context/",
|
|
68
|
+
"workflows/",
|
|
69
|
+
"examples/",
|
|
70
|
+
"LICENSE",
|
|
71
|
+
"README.md",
|
|
72
|
+
"CHANGELOG.md",
|
|
73
|
+
"!**/__tests__/**",
|
|
74
|
+
"!**/*.test.*",
|
|
75
|
+
"!**/*.spec.*",
|
|
76
|
+
"!**/*.test.d.ts.map",
|
|
77
|
+
"!**/*.test.js.map"
|
|
78
|
+
],
|
|
79
|
+
"keywords": [
|
|
80
|
+
"ai",
|
|
81
|
+
"framework",
|
|
82
|
+
"coding-assistant",
|
|
83
|
+
"claude-code",
|
|
84
|
+
"codex",
|
|
85
|
+
"aperant"
|
|
86
|
+
],
|
|
87
|
+
"license": "AGPL-3.0-only",
|
|
88
|
+
"homepage": "https://github.com/Mikalsen-AI/aperant-cloud-desktop/tree/main/packages/framework#readme",
|
|
89
|
+
"bugs": {
|
|
90
|
+
"url": "https://github.com/Mikalsen-AI/aperant-cloud-desktop/issues"
|
|
91
|
+
},
|
|
92
|
+
"repository": {
|
|
93
|
+
"type": "git",
|
|
94
|
+
"url": "https://github.com/Mikalsen-AI/aperant-cloud-desktop.git",
|
|
95
|
+
"directory": "packages/framework"
|
|
96
|
+
},
|
|
97
|
+
"engines": {
|
|
98
|
+
"node": ">=18.0.0"
|
|
99
|
+
},
|
|
100
|
+
"publishConfig": {
|
|
101
|
+
"access": "public"
|
|
102
|
+
},
|
|
103
|
+
"dependencies": {
|
|
104
|
+
"@clack/prompts": "^1.2.0",
|
|
105
|
+
"proper-lockfile": "^4.1.2",
|
|
106
|
+
"yaml": "^2.8.3",
|
|
107
|
+
"zod": "^4.3.6"
|
|
108
|
+
},
|
|
109
|
+
"optionalDependencies": {
|
|
110
|
+
"@babel/parser": "^7.29.2",
|
|
111
|
+
"@babel/traverse": "^7.29.0",
|
|
112
|
+
"@google/genai": "^1.50.1",
|
|
113
|
+
"css-tree": "^3.2.1",
|
|
114
|
+
"playwright": "^1.59.1"
|
|
115
|
+
},
|
|
116
|
+
"devDependencies": {
|
|
117
|
+
"@vitest/coverage-v8": "^4.1.5"
|
|
118
|
+
},
|
|
119
|
+
"scripts": {
|
|
120
|
+
"build": "tsc && node scripts/extract-personas-schema.mjs",
|
|
121
|
+
"dev": "tsc --watch --preserveWatchOutput",
|
|
122
|
+
"typecheck": "tsc --noEmit",
|
|
123
|
+
"lint": "biome check . --diagnostic-level=error",
|
|
124
|
+
"lint:fix": "biome check --write . --diagnostic-level=error",
|
|
125
|
+
"clean": "rm -rf dist",
|
|
126
|
+
"test": "vitest run",
|
|
127
|
+
"c28:cutover": "node scripts/c28-cutover.mjs",
|
|
128
|
+
"build-plugin": "node scripts/build-plugin.mjs",
|
|
129
|
+
"publish-plugin": "node scripts/publish-plugin.mjs",
|
|
130
|
+
"publish:verdaccio": "node scripts/publish-verdaccio.mjs",
|
|
131
|
+
"release:verdaccio": "pnpm build && pnpm publish:verdaccio",
|
|
132
|
+
"ship:verdaccio": "npm version patch --no-git-tag-version && pnpm build && pnpm publish:verdaccio"
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -88,6 +88,31 @@ Do NOT wait on the narrator. Its status lands in `.aperant/digests/.last-run.jso
|
|
|
88
88
|
|
|
89
89
|
After step 2.5 spawns narrators for the just-closed candidates, the skill ALSO drains `state.pending_narration[]` (entries the passive sweep parked there) — see §4.
|
|
90
90
|
|
|
91
|
+
## 2.6 Drop user-facing release-note fragment (persona-aware release-notes opt-in)
|
|
92
|
+
|
|
93
|
+
This step is additive bookkeeping — it must NEVER block the close path or surface a destructive failure. Runs ONLY when `.aperant/config.json` has `changelog.release_notes.enabled === true`. When the toggle is `false` (the default for projects that don't publish user-facing notes), skip the section entirely.
|
|
94
|
+
|
|
95
|
+
For each entry in the close-merged envelope's `closed[]`:
|
|
96
|
+
|
|
97
|
+
1. Read the merged PR body via `gh pr view <pr_number> --json body`. Look for a single `Release note:` line (case-insensitive). The line is what `/apt:ship` Section 2.6 auto-drafted (or a manual edit by the author).
|
|
98
|
+
2. Look for a persona tag in the PR body or in commits. Reasonable sources: the `Release note:` line itself if the author included one (e.g. `Release note: [Maya] …`), a `release-note-persona:` trailer in the most recent commit, or the persona name embedded in the auto-drafted line by §2.6.2 of /apt:ship.
|
|
99
|
+
3. Invoke the deterministic drafter:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
node packages/framework/bin/apt-tools.mjs release-notes draft . \
|
|
103
|
+
--pr <pr_number> \
|
|
104
|
+
--note "<release-note-line-text>" \
|
|
105
|
+
[--persona <persona-name>]
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
This call is synchronous, sub-100ms, and idempotent per subtask 2 — re-running close-task does not duplicate the fragment.
|
|
109
|
+
|
|
110
|
+
4. On non-zero exit from `release-notes draft`, append a `pending_release_note[]` row to `.aperant/state.json` so a manual re-run can recover. Do NOT block close-merged on this — it is additive bookkeeping, not core lifecycle. Use `apt-tools state-update . --append-pending-release-note '{"task_id": "<id>", "pr_number": <n>, "reason": "<error>"}'` if such a helper exists, otherwise call `apt-tools state-artifacts . --note '<msg>'` or write a one-line warning to stderr.
|
|
111
|
+
|
|
112
|
+
Skip the section quietly when:
|
|
113
|
+
- The PR body has no `Release note:` line AND no auto-draft signal (intentional infra-only PR, or the `no-notes` label was applied at ship time).
|
|
114
|
+
- The CLI returns `{status: 'ok', written: false, reason: 'no-change'}` — already idempotent-handled by subtask 2's contract; treat as success.
|
|
115
|
+
|
|
91
116
|
## 3. Summary
|
|
92
117
|
|
|
93
118
|
close-merged emits an envelope with:
|
|
@@ -98,6 +123,8 @@ close-merged emits an envelope with:
|
|
|
98
123
|
|
|
99
124
|
Report the summary verbatim. If anything landed in `skipped[]` with a recoverable reason (`offline`, `rate_limited`), suggest the user re-run once the network clears.
|
|
100
125
|
|
|
126
|
+
**Local cleanup recap (best-effort).** On confirmed merge, `task close-merged` runs `computeWorktreeCleanup` against the primary repo (`packages/framework/src/cli/task/worktree-cleanup.mjs:140-230`): `git fetch origin <base>` (failure → recorded as `fetch_failed` warning, cleanup proceeds), `git checkout <base>` (failure → returns `action: 'checkout_failed'` and preserves BOTH the worktree and the task branch — nothing is removed), `git merge --ff-only origin/<base>` (failure → `ff_only_failed` warning, GC still runs), `git worktree remove <wtPath>`, and `git branch -d <taskBranch>` (failure on squash/rebase merges → `branch_delete_failed` warning, `branch_deleted: false` in the envelope; user can `git branch -D` manually). This is why `/apt:watch-ci` auto-merge MUST NOT pass `gh pr merge --delete-branch` — that flag would try to do the same work from inside the worktree where `git switch <base>` cannot succeed (see FRAMEWORK-BUG-019).
|
|
127
|
+
|
|
101
128
|
## 4. Narrate-only mode
|
|
102
129
|
|
|
103
130
|
When invoked with `--narrate-only`, skip §1 (no `gh pr view` polling) and §2 (no destructive close path). The deterministic close already ran via the passive post-merge sweep — your job is to drain the `state.pending_narration[]` ledger the sweep parked.
|
|
@@ -108,10 +135,13 @@ Flags:
|
|
|
108
135
|
|
|
109
136
|
Per ledger entry `{ task_id, scope, phase_id, closed_at, pr_number }`:
|
|
110
137
|
|
|
138
|
+
0. **Enumerate the ledger first.** Run `apt-tools task close-merged . --narrate-only [--task <id>]`. The envelope's `closed[]` is the read-only list of pending narration rows (each carries `narration_pending: true`); state.json is not mutated. When `closed[]` is empty, exit early with `{status: 'ok', narrated: [], skipped: [], remaining: 0}` — there is nothing to drain.
|
|
111
139
|
1. Run `apt-tools features-audit . --apply-stubs --task <task_id>` so the feature registry catches whatever the closed task introduced. If the subprocess exits non-zero, skip the entry with `reason: 'features-audit-failed'` — never block narration on registry update.
|
|
112
140
|
2. When `phase_id` is non-null, spawn `apt-team-docs-narrator` (background, fire-and-forget) using the same shape as §2.5: `subagent_type: apt-team-docs-narrator`, `run_in_background: true`, `description: Narrate shipped phase {phase_id}`, `prompt: Run in phase-ship mode with --phase {phase_id} --scope {scope}.`. When `phase_id` is null, skip the spawn with `reason: 'no-phase-id'` (quick tasks have no phase to narrate).
|
|
113
141
|
3. On successful spawn (or successful no-phase skip), drain the row via `apt-tools task narration-drain . --task <task_id>` which acquires `withFileLock(state.json)` and removes the matching `pending_narration[]` entry. On spawn failure, leave the row in place with `reason: 'narrator-spawn-failed'` so the user can re-invoke.
|
|
114
142
|
|
|
143
|
+
**At-least-once retry semantics (FRAMEWORK-BUG-020).** Step 0 is read-only — it does NOT remove rows. Steps 1–3 are the existing per-row drain contract: a row stays in `pending_narration[]` until step 3 calls `narration-drain` successfully. If the orchestrator crashes between step 2 (narrator spawn) and step 3 (drain), the row remains parked and a retry will spawn narration again. Re-spawning is safe because the narrator is **anchor-replace idempotent** by design (see `agents/apt-team-docs-narrator.md` §6 / §2 and the "Idempotent operations only" rule in §notes) — it uses HTML-comment anchors to bound the replaced region in ROADMAP.md, so a second run replaces the same span rather than appending.
|
|
144
|
+
|
|
115
145
|
Emit a summary envelope:
|
|
116
146
|
|
|
117
147
|
```json
|
|
@@ -174,8 +174,38 @@ order: (a) consent gate, (b) renderer dispatch, (c) failure handling.
|
|
|
174
174
|
|
|
175
175
|
### 5a. Consent gate
|
|
176
176
|
|
|
177
|
-
|
|
178
|
-
|
|
177
|
+
Per FRAMEWORK-AUDIT-001, `mcpConsent` is per-user state and lives in
|
|
178
|
+
`.aperant/config.local.json` (gitignored), NOT the shared
|
|
179
|
+
`.aperant/config.json`. The `diagram.mode` field stays in `config.json` as
|
|
180
|
+
team policy. Read the MERGED config (config.json deep-merged with
|
|
181
|
+
config.local.json — local wins on collision) and look at
|
|
182
|
+
`diagram.mcpConsent` via the same in-skill `node -e` pattern Step 2 uses:
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
node -e '
|
|
186
|
+
const fs = require("fs");
|
|
187
|
+
const path = require("path");
|
|
188
|
+
const aperantDir = path.join(process.cwd(), ".aperant");
|
|
189
|
+
function readJson(p) {
|
|
190
|
+
try { return JSON.parse(fs.readFileSync(p, "utf-8")); } catch { return null; }
|
|
191
|
+
}
|
|
192
|
+
const shared = readJson(path.join(aperantDir, "config.json")) || {};
|
|
193
|
+
const local = readJson(path.join(aperantDir, "config.local.json")) || {};
|
|
194
|
+
// Deep-merge — local wins on collision; matches loadMergedProjectConfig semantics.
|
|
195
|
+
function merge(a, b) {
|
|
196
|
+
if (a === null || typeof a !== "object" || Array.isArray(a)) return b;
|
|
197
|
+
if (b === null || typeof b !== "object" || Array.isArray(b)) return b;
|
|
198
|
+
const out = { ...a };
|
|
199
|
+
for (const k of Object.keys(b)) out[k] = (k in a) ? merge(a[k], b[k]) : b[k];
|
|
200
|
+
return out;
|
|
201
|
+
}
|
|
202
|
+
const cfg = merge(shared, local);
|
|
203
|
+
const consent = cfg && cfg.diagram ? cfg.diagram.mcpConsent : null;
|
|
204
|
+
process.stdout.write(JSON.stringify(consent || null));
|
|
205
|
+
'
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
The shape is:
|
|
179
209
|
|
|
180
210
|
```json
|
|
181
211
|
{ "host": "<host string>", "decision": "granted" | "denied", "grantedAt": "<ISO 8601 timestamp>" }
|
|
@@ -196,7 +226,7 @@ Decide whether to prompt:
|
|
|
196
226
|
- If `diagram.mcpConsent.host !== <configured MCP host>` → prompt.
|
|
197
227
|
- If `diagram.mcpConsent.host === <configured MCP host>` AND `diagram.mcpConsent.decision === "denied"` → hard-error immediately with the exact denied text (no re-prompt):
|
|
198
228
|
```
|
|
199
|
-
diagram MCP consent denied; re-run with `--mode=file` or change diagram.mcpConsent in .aperant/config.json
|
|
229
|
+
diagram MCP consent denied; re-run with `--mode=file` or change diagram.mcpConsent in .aperant/config.local.json
|
|
200
230
|
```
|
|
201
231
|
- Otherwise (host matches AND decision is `"granted"`) → the existing consent applies; proceed to 5b.
|
|
202
232
|
|
|
@@ -206,23 +236,25 @@ When prompting, print a single line naming the host and ask `granted / denied`:
|
|
|
206
236
|
apt:diagram is about to send diagram data to the Excalidraw MCP server at <host>. Type `granted` to allow, `denied` to refuse.
|
|
207
237
|
```
|
|
208
238
|
|
|
209
|
-
Read one line of input. Persist the decision
|
|
210
|
-
`.aperant/config.json`
|
|
239
|
+
Read one line of input. Persist the decision to
|
|
240
|
+
`.aperant/config.local.json` (NOT `config.json` — `mcpConsent` is per-user
|
|
241
|
+
state per FRAMEWORK-AUDIT-001; the local file is gitignored). The
|
|
242
|
+
read-mutate-write JSON pattern targets the local file only:
|
|
211
243
|
|
|
212
244
|
```bash
|
|
213
245
|
node -e '
|
|
214
246
|
const fs = require("fs");
|
|
215
247
|
const path = require("path");
|
|
216
|
-
const cfgPath = path.join(process.cwd(), ".aperant/config.json");
|
|
248
|
+
const cfgPath = path.join(process.cwd(), ".aperant/config.local.json");
|
|
217
249
|
const decision = process.env.APT_DIAGRAM_DECISION; // "granted" | "denied"
|
|
218
250
|
if (decision !== "granted" && decision !== "denied") {
|
|
219
251
|
process.stderr.write("invalid decision\n");
|
|
220
252
|
process.exit(1);
|
|
221
253
|
}
|
|
222
254
|
const host = process.env.APT_DIAGRAM_HOST;
|
|
223
|
-
let cfg = { diagram: {
|
|
255
|
+
let cfg = { diagram: { mcpConsent: null } };
|
|
224
256
|
try { cfg = JSON.parse(fs.readFileSync(cfgPath, "utf-8")); } catch {}
|
|
225
|
-
cfg.diagram = cfg.diagram || {
|
|
257
|
+
cfg.diagram = cfg.diagram || { mcpConsent: null };
|
|
226
258
|
cfg.diagram.mcpConsent = {
|
|
227
259
|
host,
|
|
228
260
|
decision,
|
|
@@ -232,11 +264,15 @@ node -e '
|
|
|
232
264
|
'
|
|
233
265
|
```
|
|
234
266
|
|
|
267
|
+
Note: the write target above is `config.local.json` (gitignored, per-user).
|
|
268
|
+
The shared `config.json` is not touched — `diagram.mode` lives there and
|
|
269
|
+
stays team-shared policy.
|
|
270
|
+
|
|
235
271
|
If the decision is `"denied"`, exit with EXACTLY this text (verbatim,
|
|
236
272
|
no rewording, no extra punctuation):
|
|
237
273
|
|
|
238
274
|
```
|
|
239
|
-
diagram MCP consent denied; re-run with `--mode=file` or change diagram.mcpConsent in .aperant/config.json
|
|
275
|
+
diagram MCP consent denied; re-run with `--mode=file` or change diagram.mcpConsent in .aperant/config.local.json
|
|
240
276
|
```
|
|
241
277
|
|
|
242
278
|
If the decision is `"granted"`, proceed to 5b.
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: apt:release-notes
|
|
3
|
+
description: "Persona-aware user-facing release notes — draft, compile, publish"
|
|
4
|
+
apt-skill-version: {{APT_VERSION}}
|
|
5
|
+
stage: ops
|
|
6
|
+
intent: capture
|
|
7
|
+
when_to_use: "The user wants to draft, compile, or publish persona-aware user-facing release notes for a project that opted in via /apt:setup."
|
|
8
|
+
user_invocable: true
|
|
9
|
+
internal: false
|
|
10
|
+
spawns_agent: false
|
|
11
|
+
agent_name: null
|
|
12
|
+
task_context: none
|
|
13
|
+
default_track: DEEP
|
|
14
|
+
default_execution_mode: auto
|
|
15
|
+
execution_modes:
|
|
16
|
+
- auto
|
|
17
|
+
- step
|
|
18
|
+
allowed-tools: "Bash, Read, Write, Edit, Grep, Glob"
|
|
19
|
+
argument-hint: "apt:release-notes [--draft --pr <n> | --compile <tag> | --publish <tag>]"
|
|
20
|
+
gates: []
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
<objective>
|
|
24
|
+
Produce user-facing release notes for a project that opted in via
|
|
25
|
+
`/apt:setup` → Batch 9 → "Publish user-facing release notes? Yes". This
|
|
26
|
+
skill is separate from `/apt:docs changelog` — the dev-flavored
|
|
27
|
+
`CHANGELOG.md` surface stays its responsibility; this skill owns the
|
|
28
|
+
persona-voice-tuned narrative that ships to end users.
|
|
29
|
+
|
|
30
|
+
Three modes:
|
|
31
|
+
|
|
32
|
+
- `--draft --pr <n>` — write a per-PR fragment file (towncrier-style).
|
|
33
|
+
Usually called automatically by `/apt:close-task` on merge; rarely
|
|
34
|
+
invoked by hand.
|
|
35
|
+
- `--compile <tag>` — at release time, read every accumulated fragment,
|
|
36
|
+
filter by the configured persona tier, write a single per-tag
|
|
37
|
+
narrative file, and clear the unreleased dir.
|
|
38
|
+
- `--publish <tag>` — shell `gh release create/edit` to push the
|
|
39
|
+
compiled body to the GitHub Release.
|
|
40
|
+
|
|
41
|
+
Rules of the road (locked):
|
|
42
|
+
|
|
43
|
+
- **Per-project opt-in (spec ID-7).** Presence of `.aperant/personas.json`
|
|
44
|
+
does NOT auto-enable this skill. The toggle is
|
|
45
|
+
`config.changelog.release_notes.enabled` — opt-in is explicit via
|
|
46
|
+
`/apt:setup` Batch 9.
|
|
47
|
+
- **No LLM calls in the CLI (spec ID-3).** The deterministic verbs
|
|
48
|
+
(`apt-tools release-notes draft|compile|publish`) handle file I/O only.
|
|
49
|
+
Persona-voice tuning is authored by you — the host LLM — using the
|
|
50
|
+
`appendices/persona-voice.md` loader.
|
|
51
|
+
- **Triage bucket is non-negotiable (spec ID-6).** Fragments without a
|
|
52
|
+
recognized persona tag MUST surface in a `## Needs Triage` section at
|
|
53
|
+
compile time. Never silent-drop. This is the human curator's safety
|
|
54
|
+
net against trust collapse over time.
|
|
55
|
+
- **Fast Path Guarantee (CLAUDE.md).** This skill is DEEP-track-only
|
|
56
|
+
surface area. `/apt:quick` never enters this skill; the
|
|
57
|
+
`apt-release-notes-quick-exempt.test.ts` regression test pins the
|
|
58
|
+
exemption.
|
|
59
|
+
</objective>
|
|
60
|
+
|
|
61
|
+
<your_environment>
|
|
62
|
+
- **Working directory:** The project root
|
|
63
|
+
- **apt-tools path:** `node packages/framework/bin/apt-tools.mjs` or the locally installed `apt-tools` binary.
|
|
64
|
+
- **gh CLI dependency:** required only for `--publish`. The `publish` verb returns a graceful `{status: 'skipped', reason: 'gh-not-found'}` envelope when `gh` is unavailable — the skill never crashes the host on a missing binary.
|
|
65
|
+
- **Output:** `docs/releases/_unreleased/PR-<n>.md` (fragments) + `docs/releases/<tag>.md` (compiled) + GitHub Release body (when published).
|
|
66
|
+
</your_environment>
|
|
67
|
+
|
|
68
|
+
<state_files>
|
|
69
|
+
## State Files
|
|
70
|
+
|
|
71
|
+
**Reads:**
|
|
72
|
+
- `.aperant/config.json` — `changelog.release_notes.{enabled, output_dir, persona_filter, require_pr_field}` and the stage blocks.
|
|
73
|
+
- `.aperant/personas.json` — persona roster + tier; used by `compile` to filter fragments.
|
|
74
|
+
- `docs/releases/_unreleased/PR-*.md` — fragments accumulated since the last tag.
|
|
75
|
+
- `PROJECT.md` (when present) — Core Value section feeds the hero narrative the host LLM authors at compile time.
|
|
76
|
+
|
|
77
|
+
**Writes:**
|
|
78
|
+
- `docs/releases/_unreleased/PR-<n>.md` — towncrier-style fragments (draft mode).
|
|
79
|
+
- `docs/releases/<tag>.md` — per-tag compiled narrative (compile mode).
|
|
80
|
+
- GitHub Release body via `gh release create/edit` (publish mode).
|
|
81
|
+
</state_files>
|
|
82
|
+
|
|
83
|
+
<process>
|
|
84
|
+
|
|
85
|
+
## 0. Hard-Gate + Opt-in Check
|
|
86
|
+
|
|
87
|
+
No hard gates (`gates: []`). Before running ANY mode, confirm the project opted in:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
rn_enabled=$(jq -r '.changelog.release_notes.enabled // false' .aperant/config.json)
|
|
91
|
+
if [ "$rn_enabled" != "true" ]; then
|
|
92
|
+
echo "[apt:release-notes] This project has not opted into user-facing release notes."
|
|
93
|
+
echo " Run /apt:setup → 'Changelog & Release Notes' to enable."
|
|
94
|
+
exit 0
|
|
95
|
+
fi
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
When opted out, exit cleanly — never crash, never write any file.
|
|
99
|
+
|
|
100
|
+
## 1. Parse Flags
|
|
101
|
+
|
|
102
|
+
The skill's three modes are mutually exclusive. Parse `$ARGUMENTS`:
|
|
103
|
+
|
|
104
|
+
- `--draft --pr <n> --note "<text>" [--persona <name>] [--commits <sha,sha,...>] [--date <YYYY-MM-DD>]` → Draft mode (§2).
|
|
105
|
+
- `--compile <tag>` → Compile mode (§3).
|
|
106
|
+
- `--publish <tag> [--dry-run]` → Publish mode (§4).
|
|
107
|
+
|
|
108
|
+
If no mode flag is passed, display a short usage message and exit.
|
|
109
|
+
|
|
110
|
+
## 2. Draft Mode
|
|
111
|
+
|
|
112
|
+
Used to write a per-PR fragment. Usually this is called automatically by `/apt:close-task` on merge (see `apt-close-task/SKILL.md` §2.6), but it can be invoked by hand for backfills or for manual fragments.
|
|
113
|
+
|
|
114
|
+
Hand the inputs to the deterministic CLI:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
node packages/framework/bin/apt-tools.mjs release-notes draft . \
|
|
118
|
+
--pr {pr_number} \
|
|
119
|
+
--note "{one-sentence user-impact note}" \
|
|
120
|
+
[--persona {persona-name}] \
|
|
121
|
+
[--date {ISO-date}]
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
The CLI is idempotent — re-running with identical inputs returns `{status: 'ok', written: false, reason: 'no-change'}`. Trust the envelope; do NOT manually inspect the file before running.
|
|
125
|
+
|
|
126
|
+
When you (the host LLM) compose the note, consult `appendices/persona-voice.md` for the persona-voice posture loader. The fragment goes into the user-facing pipeline; voice-tune accordingly.
|
|
127
|
+
|
|
128
|
+
## 3. Compile Mode
|
|
129
|
+
|
|
130
|
+
Used at release time to fold every accumulated fragment into a single per-tag narrative. The CLI does the deterministic work — your job is the narrative pass on the compiled body if the user wants polish beyond the structured per-PR list.
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
node packages/framework/bin/apt-tools.mjs release-notes compile . --tag {tag}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
The envelope reports:
|
|
137
|
+
|
|
138
|
+
```json
|
|
139
|
+
{
|
|
140
|
+
"status": "ok",
|
|
141
|
+
"command": "release-notes-compile",
|
|
142
|
+
"tag": "{tag}",
|
|
143
|
+
"path": "docs/releases/{tag}.md",
|
|
144
|
+
"fragments_total": N,
|
|
145
|
+
"fragments_in_body": M,
|
|
146
|
+
"fragments_in_triage": K,
|
|
147
|
+
"fragments_filtered": F,
|
|
148
|
+
"cleared_unreleased": true
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
If `fragments_in_triage > 0`, ALERT the user before proceeding to publish — those fragments need persona curation. Offer two options:
|
|
153
|
+
|
|
154
|
+
1. Edit the compiled file directly to move triage entries into the right tier section (and re-tag the persona in the original PR if appropriate).
|
|
155
|
+
2. Proceed to publish with the triage section intact — sometimes the right call when the unmapped change is genuinely cross-cutting.
|
|
156
|
+
|
|
157
|
+
If `fragments_total === 0`, gently note that there's nothing to compile yet (likely the user hasn't merged any PRs since the last release).
|
|
158
|
+
|
|
159
|
+
## 4. Publish Mode
|
|
160
|
+
|
|
161
|
+
Push the compiled body to the GitHub Release:
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
node packages/framework/bin/apt-tools.mjs release-notes publish . --tag {tag}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
The CLI handles both `gh release create` (new release) and `gh release edit` (existing release). When `gh` is not on PATH, the envelope returns `{status: 'skipped', reason: 'gh-not-found'}` — surface the message to the user and suggest installing the GitHub CLI.
|
|
168
|
+
|
|
169
|
+
For local-only verification, pass `--dry-run` to skip the network call and just confirm the body file exists.
|
|
170
|
+
|
|
171
|
+
## 5. Re-invocation
|
|
172
|
+
|
|
173
|
+
Each mode is safe to re-run:
|
|
174
|
+
|
|
175
|
+
- `draft` — idempotent per spec AC2.
|
|
176
|
+
- `compile` — overwrites `<tag>.md` and re-clears `_unreleased/`. Running compile twice in a row with no new fragments produces an empty body — careful.
|
|
177
|
+
- `publish` — `gh release edit` is the second call's branch, so the GitHub Release body is replaced cleanly.
|
|
178
|
+
|
|
179
|
+
</process>
|
|
180
|
+
|
|
181
|
+
<appendices>
|
|
182
|
+
- `appendices/persona-voice.md` — Aperant-authored posture loader for voice-tuning the note text per persona archetype.
|
|
183
|
+
</appendices>
|
|
184
|
+
|
|
185
|
+
<integration>
|
|
186
|
+
| Skill | How it interacts |
|
|
187
|
+
|-------|------------------|
|
|
188
|
+
| `/apt:setup` Batch 9 | Writes the opt-in toggle + audience + persona-filter that this skill reads. |
|
|
189
|
+
| `/apt:ship` §2.6 | Auto-drafts the `Release note: …` line in the PR body BEFORE this skill runs; ship blocks if `require_pr_field=true` AND no signal. |
|
|
190
|
+
| `/apt:close-task` §2.6 | Invokes `apt-tools release-notes draft` on merge — drops the per-PR fragment automatically. |
|
|
191
|
+
| `/apt:docs changelog` | The dev-flavored sibling. Stays its own surface; this skill does NOT touch `CHANGELOG.md`. |
|
|
192
|
+
| `/apt:personas` | Produces the `.aperant/personas.json` roster that compile mode filters against. |
|
|
193
|
+
</integration>
|