@aperant/framework 0.6.7 → 0.7.3

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.
Files changed (205) hide show
  1. package/CHANGELOG.md +240 -0
  2. package/agents/apt-planner.md +12 -0
  3. package/agents/apt-pr-review-fixer.md +13 -9
  4. package/bin/apt-tools.mjs +7 -0
  5. package/dist/cli/ci-watch/stop-matrix.d.mts.map +1 -1
  6. package/dist/cli/ci-watch/stop-matrix.mjs +16 -0
  7. package/dist/cli/ci-watch/stop-matrix.mjs.map +1 -1
  8. package/dist/cli/commands/ci-watch.d.mts +11 -0
  9. package/dist/cli/commands/ci-watch.d.mts.map +1 -1
  10. package/dist/cli/commands/ci-watch.mjs +147 -3
  11. package/dist/cli/commands/ci-watch.mjs.map +1 -1
  12. package/dist/cli/commands/features-audit.d.mts +24 -0
  13. package/dist/cli/commands/features-audit.d.mts.map +1 -1
  14. package/dist/cli/commands/features-audit.mjs +159 -5
  15. package/dist/cli/commands/features-audit.mjs.map +1 -1
  16. package/dist/cli/commands/health-check.d.mts +16 -0
  17. package/dist/cli/commands/health-check.d.mts.map +1 -1
  18. package/dist/cli/commands/health-check.mjs +119 -3
  19. package/dist/cli/commands/health-check.mjs.map +1 -1
  20. package/dist/cli/commands/init.d.mts +19 -1
  21. package/dist/cli/commands/init.d.mts.map +1 -1
  22. package/dist/cli/commands/init.mjs +143 -8
  23. package/dist/cli/commands/init.mjs.map +1 -1
  24. package/dist/cli/commands/modes.d.mts.map +1 -1
  25. package/dist/cli/commands/modes.mjs +11 -0
  26. package/dist/cli/commands/modes.mjs.map +1 -1
  27. package/dist/cli/commands/pr-review-audit-fixer.d.mts +13 -0
  28. package/dist/cli/commands/pr-review-audit-fixer.d.mts.map +1 -1
  29. package/dist/cli/commands/pr-review-audit-fixer.mjs +18 -5
  30. package/dist/cli/commands/pr-review-audit-fixer.mjs.map +1 -1
  31. package/dist/cli/commands/route.d.mts.map +1 -1
  32. package/dist/cli/commands/route.mjs +37 -2
  33. package/dist/cli/commands/route.mjs.map +1 -1
  34. package/dist/cli/commands/task.d.mts.map +1 -1
  35. package/dist/cli/commands/task.mjs +132 -5
  36. package/dist/cli/commands/task.mjs.map +1 -1
  37. package/dist/cli/commands/validate-evidence.d.mts +24 -2
  38. package/dist/cli/commands/validate-evidence.d.mts.map +1 -1
  39. package/dist/cli/commands/validate-evidence.mjs +154 -17
  40. package/dist/cli/commands/validate-evidence.mjs.map +1 -1
  41. package/dist/cli/commands/vitest-doctor.d.mts +2 -0
  42. package/dist/cli/commands/vitest-doctor.d.mts.map +1 -0
  43. package/dist/cli/commands/vitest-doctor.mjs +168 -0
  44. package/dist/cli/commands/vitest-doctor.mjs.map +1 -0
  45. package/dist/cli/config/gitignore-drift.d.mts +23 -1
  46. package/dist/cli/config/gitignore-drift.d.mts.map +1 -1
  47. package/dist/cli/config/gitignore-drift.mjs +81 -3
  48. package/dist/cli/config/gitignore-drift.mjs.map +1 -1
  49. package/dist/cli/config/load.d.mts +56 -2
  50. package/dist/cli/config/load.d.mts.map +1 -1
  51. package/dist/cli/config/load.mjs +192 -2
  52. package/dist/cli/config/load.mjs.map +1 -1
  53. package/dist/cli/consistency/parse-review.mjs +7 -0
  54. package/dist/cli/consistency/parse-review.mjs.map +1 -1
  55. package/dist/cli/dispatch.d.mts.map +1 -1
  56. package/dist/cli/dispatch.mjs +24 -2
  57. package/dist/cli/dispatch.mjs.map +1 -1
  58. package/dist/cli/gate/gates/gitignore-in-sync.d.mts +1 -1
  59. package/dist/cli/gate/gates/gitignore-in-sync.d.mts.map +1 -1
  60. package/dist/cli/gate/gates/gitignore-in-sync.mjs +5 -2
  61. package/dist/cli/gate/gates/gitignore-in-sync.mjs.map +1 -1
  62. package/dist/cli/gate/gates/review-clean.d.mts +5 -1
  63. package/dist/cli/gate/gates/review-clean.d.mts.map +1 -1
  64. package/dist/cli/gate/gates/review-clean.mjs +23 -18
  65. package/dist/cli/gate/gates/review-clean.mjs.map +1 -1
  66. package/dist/cli/gate/gates/verify-approved.d.mts +49 -1
  67. package/dist/cli/gate/gates/verify-approved.d.mts.map +1 -1
  68. package/dist/cli/gate/gates/verify-approved.mjs +93 -14
  69. package/dist/cli/gate/gates/verify-approved.mjs.map +1 -1
  70. package/dist/cli/help.d.mts.map +1 -1
  71. package/dist/cli/help.mjs +8 -2
  72. package/dist/cli/help.mjs.map +1 -1
  73. package/dist/cli/host/detect.d.mts +122 -9
  74. package/dist/cli/host/detect.d.mts.map +1 -1
  75. package/dist/cli/host/detect.mjs +132 -0
  76. package/dist/cli/host/detect.mjs.map +1 -1
  77. package/dist/cli/install/legacy-paths.d.mts +38 -0
  78. package/dist/cli/install/legacy-paths.d.mts.map +1 -0
  79. package/dist/cli/install/legacy-paths.mjs +69 -0
  80. package/dist/cli/install/legacy-paths.mjs.map +1 -0
  81. package/dist/cli/install/runtime-detect.d.mts +13 -0
  82. package/dist/cli/install/runtime-detect.d.mts.map +1 -1
  83. package/dist/cli/install/runtime-detect.mjs +9 -0
  84. package/dist/cli/install/runtime-detect.mjs.map +1 -1
  85. package/dist/cli/install/runtime-migrate.d.mts +84 -0
  86. package/dist/cli/install/runtime-migrate.d.mts.map +1 -0
  87. package/dist/cli/install/runtime-migrate.mjs +244 -0
  88. package/dist/cli/install/runtime-migrate.mjs.map +1 -0
  89. package/dist/cli/route/drift-detect.d.mts +20 -0
  90. package/dist/cli/route/drift-detect.d.mts.map +1 -0
  91. package/dist/cli/route/drift-detect.mjs +107 -0
  92. package/dist/cli/route/drift-detect.mjs.map +1 -0
  93. package/dist/cli/task/index-md.d.mts.map +1 -1
  94. package/dist/cli/task/index-md.mjs +14 -2
  95. package/dist/cli/task/index-md.mjs.map +1 -1
  96. package/dist/cli/util/aperant-section.d.mts +34 -0
  97. package/dist/cli/util/aperant-section.d.mts.map +1 -0
  98. package/dist/cli/util/aperant-section.mjs +127 -0
  99. package/dist/cli/util/aperant-section.mjs.map +1 -0
  100. package/dist/cli/util/copy.d.mts +28 -1
  101. package/dist/cli/util/copy.d.mts.map +1 -1
  102. package/dist/cli/util/copy.mjs +43 -55
  103. package/dist/cli/util/copy.mjs.map +1 -1
  104. package/dist/cli/util/semver.d.mts +17 -0
  105. package/dist/cli/util/semver.d.mts.map +1 -0
  106. package/dist/cli/util/semver.mjs +29 -0
  107. package/dist/cli/util/semver.mjs.map +1 -0
  108. package/dist/cli/util/skill-installs.d.mts +65 -9
  109. package/dist/cli/util/skill-installs.d.mts.map +1 -1
  110. package/dist/cli/util/skill-installs.mjs +130 -21
  111. package/dist/cli/util/skill-installs.mjs.map +1 -1
  112. package/dist/cli/util/version-preflight.d.mts +44 -0
  113. package/dist/cli/util/version-preflight.d.mts.map +1 -0
  114. package/dist/cli/util/version-preflight.mjs +66 -0
  115. package/dist/cli/util/version-preflight.mjs.map +1 -0
  116. package/dist/plugin/.claude-plugin/plugin.json +11 -2
  117. package/dist/plugin/agents/apt-improver.md +99 -0
  118. package/dist/plugin/agents/apt-planner.md +127 -10
  119. package/dist/plugin/agents/apt-pr-review-fixer.md +13 -9
  120. package/dist/plugin/skills/apt/SKILL.md +1 -0
  121. package/dist/plugin/skills/apt-close-task/SKILL.md +63 -1
  122. package/dist/plugin/skills/apt-debug/SKILL.md +39 -6
  123. package/dist/plugin/skills/apt-debug/appendices/diagnose-discipline.md +119 -0
  124. package/dist/plugin/skills/apt-diagram/SKILL.md +378 -0
  125. package/dist/plugin/skills/apt-diagram/appendices/design-discipline.md +97 -0
  126. package/dist/plugin/skills/apt-discuss/SKILL.md +72 -5
  127. package/dist/plugin/skills/apt-discuss/appendices/grill-discipline.md +104 -0
  128. package/dist/plugin/skills/apt-discuss/appendices/zoom-out-helper.md +79 -0
  129. package/dist/plugin/skills/apt-execute/SKILL.md +57 -5
  130. package/dist/plugin/skills/apt-execute/appendices/tdd-mode.md +107 -0
  131. package/dist/plugin/skills/apt-improve/DEEPENING.md +84 -0
  132. package/dist/plugin/skills/apt-improve/INTERFACE-DESIGN.md +97 -0
  133. package/dist/plugin/skills/apt-improve/LANGUAGE.md +104 -0
  134. package/dist/plugin/skills/apt-improve/SKILL.md +141 -0
  135. package/dist/plugin/skills/apt-plan/SKILL.md +171 -4
  136. package/dist/plugin/skills/apt-plan/adapters/conductor.md +98 -0
  137. package/dist/plugin/skills/apt-pr-review/SKILL.md +57 -18
  138. package/dist/plugin/skills/apt-prototype/LOGIC.md +109 -0
  139. package/dist/plugin/skills/apt-prototype/SKILL.md +143 -0
  140. package/dist/plugin/skills/apt-prototype/UI.md +90 -0
  141. package/dist/plugin/skills/apt-quick/SKILL.md +49 -8
  142. package/dist/plugin/skills/apt-release-notes/SKILL.md +193 -0
  143. package/dist/plugin/skills/apt-release-notes/appendices/persona-voice.md +59 -0
  144. package/dist/plugin/skills/apt-review/SKILL.md +2 -0
  145. package/dist/plugin/skills/apt-run/SKILL.md +32 -4
  146. package/dist/plugin/skills/apt-setup/SKILL.md +308 -6
  147. package/dist/plugin/skills/apt-ship/SKILL.md +122 -1
  148. package/dist/plugin/skills/apt-spar/SKILL.md +315 -0
  149. package/dist/plugin/skills/apt-triage/AGENT-BRIEF.md +84 -0
  150. package/dist/plugin/skills/apt-triage/OUT-OF-SCOPE.md +75 -0
  151. package/dist/plugin/skills/apt-triage/SKILL.md +169 -0
  152. package/dist/plugin/skills/apt-update/SKILL.md +77 -10
  153. package/dist/plugin/skills/apt-verify/SKILL.md +3 -0
  154. package/dist/plugin/skills/apt-verify-proof/SKILL.md +10 -5
  155. package/dist/plugin/skills/apt-watch-ci/SKILL.md +166 -0
  156. package/dist/plugin/skills/apt-zoom-out/SKILL.md +130 -0
  157. package/package.json +133 -133
  158. package/prompts/conductor-framework-context.md +63 -0
  159. package/prompts/conductor-system.md +11 -0
  160. package/skills/apt-close-task/SKILL.md +6 -0
  161. package/skills/apt-discuss/SKILL.md +47 -5
  162. package/skills/apt-execute/SKILL.md +9 -0
  163. package/skills/apt-plan/SKILL.md +12 -0
  164. package/skills/apt-pr-review/SKILL.md +11 -2
  165. package/skills/apt-quick/SKILL.md +19 -8
  166. package/skills/apt-researcher.md +1 -0
  167. package/skills/apt-setup/SKILL.md +33 -2
  168. package/skills/apt-ship/SKILL.md +16 -4
  169. package/skills/apt-spar/SKILL.md +36 -11
  170. package/skills/apt-update/SKILL.md +26 -1
  171. package/skills/apt-verify-proof/SKILL.md +7 -5
  172. package/skills/apt-watch-ci/SKILL.md +4 -1
  173. package/src/cli/ci-watch/stop-matrix.mjs +17 -0
  174. package/src/cli/commands/ci-watch.mjs +152 -3
  175. package/src/cli/commands/features-audit.mjs +164 -5
  176. package/src/cli/commands/health-check.mjs +116 -3
  177. package/src/cli/commands/init.mjs +154 -6
  178. package/src/cli/commands/modes.mjs +11 -0
  179. package/src/cli/commands/pr-review-audit-fixer.mjs +18 -5
  180. package/src/cli/commands/route.mjs +38 -2
  181. package/src/cli/commands/task.mjs +132 -5
  182. package/src/cli/commands/validate-evidence.mjs +158 -17
  183. package/src/cli/commands/vitest-doctor.mjs +173 -0
  184. package/src/cli/config/gitignore-drift.mjs +74 -3
  185. package/src/cli/config/load.mjs +188 -2
  186. package/src/cli/consistency/parse-review.mjs +6 -0
  187. package/src/cli/dispatch.mjs +23 -2
  188. package/src/cli/gate/gates/gitignore-in-sync.mjs +5 -2
  189. package/src/cli/gate/gates/review-clean.mjs +24 -19
  190. package/src/cli/gate/gates/verify-approved.mjs +97 -14
  191. package/src/cli/help.mjs +8 -2
  192. package/src/cli/host/detect.mjs +135 -0
  193. package/src/cli/install/legacy-paths.mjs +69 -0
  194. package/src/cli/install/runtime-detect.mjs +9 -0
  195. package/src/cli/install/runtime-migrate.mjs +252 -0
  196. package/src/cli/route/drift-detect.mjs +107 -0
  197. package/src/cli/task/index-md.mjs +15 -2
  198. package/src/cli/util/aperant-section.mjs +136 -0
  199. package/src/cli/util/copy.mjs +43 -56
  200. package/src/cli/util/semver.mjs +28 -0
  201. package/src/cli/util/skill-installs.mjs +134 -21
  202. package/src/cli/util/version-preflight.mjs +65 -0
  203. package/templates/aperant-claude-md-appendix.md +37 -0
  204. package/templates/config.json +2 -7
  205. package/workflows/verify-proof.md +8 -3
@@ -21,6 +21,19 @@ gates: []
21
21
  ---
22
22
  <objective>
23
23
  Debug an issue using the scientific method: observe symptoms, form hypotheses, test them systematically, and reach a conclusion. State persists across context resets via `.aperant/debug/{session-id}/DEBUG.md`.
24
+
25
+ **Diagnose-discipline posture loader (Pocock adoption AC10).** You MUST
26
+ load `appendices/diagnose-discipline.md` into reasoning context at
27
+ session start. It carries the 6-phase loop (Reproduce → Minimise →
28
+ Hypothesise → Instrument → Fix → Regression-test) and the **Phase-1
29
+ prerequisite** constraint: do NOT advance from Observe (Section 2) to
30
+ Hypothesize (Section 3) until DEBUG.md records `repro_loop_verified:
31
+ true`. The appendix carries the rationale, the anti-pattern list
32
+ (sprint-to-hypothesis, stack-trace cargo cult, test-the-fix-not-the-bug,
33
+ logging-without-minimising), and the distinction between Phase 1
34
+ "reproduce" and Phase 2 "minimise". Open question #1 was verified —
35
+ apt:debug's existing Section 2 conflates symptom-gathering with
36
+ reproduction, so loading this appendix is non-trivial.
24
37
  </objective>
25
38
 
26
39
  <your_environment>
@@ -59,18 +72,38 @@ mkdir -p .aperant/debug/{session-id}
59
72
  mkdir -p .aperant/debug/{session-id}/checkpoints
60
73
  ```
61
74
 
62
- ## 2. Observe — Gather Symptoms
75
+ ## 2. Observe — Gather Symptoms + Phase 1 (Reproduce — prerequisite)
76
+
77
+ Collect all available evidence about the bug, then establish a
78
+ **reliable repro loop as a hard prerequisite** before advancing to
79
+ Section 3 (Hypothesize). The Phase-1-reproduce constraint comes from
80
+ `appendices/diagnose-discipline.md` and is load-bearing — you do NOT
81
+ emit any hypothesis until DEBUG.md records `repro_loop_verified: true`.
63
82
 
64
- Collect all available evidence about the bug:
83
+ Sub-steps (the existing 4 + 1 prerequisite check):
65
84
 
66
85
  1. **User description:** Extract from `$ARGUMENTS` what the issue is
67
86
  2. **Error output:** If the user provided error messages, record them verbatim
68
- 3. **Reproduce:** Attempt to reproduce the issue:
69
- - Run relevant test commands
70
- - Check logs, console output
71
- - Identify the exact failure point
87
+ 3. **Phase 1 Reproduce (prerequisite, per appendices/diagnose-discipline.md):**
88
+ Establish a reliable repro loop. The loop MUST be:
89
+ - Deterministic (same input → same failure)
90
+ - Cheap (under 10 seconds preferred; certainly under 60)
91
+ - Minimal (no irrelevant setup; just enough to fail)
92
+ Record the repro command + verification in DEBUG.md as
93
+ `repro_command: "..."` and `repro_loop_verified: true`. If you
94
+ cannot reproduce reliably, do NOT proceed to Section 3 — instead,
95
+ loop here: gather more environment context, add logging to the
96
+ target environment, ask the user for repro details. Hypothesis-driven
97
+ debugging without a reliable repro is just hope, dressed up as
98
+ method.
72
99
  4. **Context:** Note environment details (branch, recent commits, dependencies)
73
100
 
101
+ After Phase 1 succeeds, run **Phase 2 — Minimise** (also from the
102
+ appendix): strip the repro to the smallest case that still fails.
103
+ Record the minimised repro in DEBUG.md as `minimised_repro: "..."`.
104
+ Phase 2 prevents Sections 3-5 from anchoring on the first plausible
105
+ cause inside a 200-line repro.
106
+
74
107
  ```bash
75
108
  git log --oneline -10
76
109
  git diff --stat
@@ -0,0 +1,119 @@
1
+ <!--
2
+ Adapted from Matt Pocock's MIT-licensed skill suite:
3
+ https://github.com/mattpocock/skills/blob/main/skills/engineering/diagnose
4
+ Licensed under MIT. Modifications: ported as an apt:debug posture loader
5
+ so the 6-phase loop attaches to the existing 5-phase apt:debug body
6
+ without forking apt:debug's control flow. Aligned with apt:debug's
7
+ DEBUG.md state file and checkpoint conventions.
8
+ -->
9
+
10
+ # Diagnose Discipline — apt:debug posture loader (6-phase loop)
11
+
12
+ This appendix loads into `apt:debug`'s reasoning context as a posture
13
+ upgrade. The existing apt:debug body has 5 phases (Observe → Hypothesize
14
+ → Checkpoint → Test → Conclude) but its Section 2 "Observe" conflates
15
+ symptom-gathering with reproduction. Open question #1 was verified — the
16
+ gap is real, the appendix is non-trivial.
17
+
18
+ This appendix carries the 6-phase Pocock discipline. The skill body
19
+ loads it and uses Phase 1 (Reproduce) as a **hard prerequisite** before
20
+ advancing from Observe to Hypothesize.
21
+
22
+ ## The 6-phase loop
23
+
24
+ 1. **Reproduce.** Establish a reliable repro loop. Until you can
25
+ trigger the bug at will, no hypothesis is testable — you'd be
26
+ guessing about a moving target. The loop MUST be:
27
+ - Deterministic (same input → same failure)
28
+ - Cheap (under 10 seconds preferred; certainly under 60)
29
+ - Minimal (no irrelevant setup; just enough to fail)
30
+
31
+ 2. **Minimise.** Once you can repro, strip the repro to the smallest
32
+ case. Remove every component that isn't load-bearing for the
33
+ failure. A 200-line repro is a hypothesis space; a 10-line repro is
34
+ a near-confirmed cause.
35
+
36
+ 3. **Hypothesise.** ONLY after minimise: write 3-5 ranked falsifiable
37
+ hypotheses. Each hypothesis must:
38
+ - Predict an observable consequence
39
+ - Be testable by changing ONE variable
40
+ - Be ordered by prior probability + cost-to-test
41
+
42
+ 4. **Instrument.** Add the minimum logging / breakpoint / probe to test
43
+ the highest-ranked hypothesis. Change ONE variable. Re-run the
44
+ minimal repro. Observe.
45
+
46
+ 5. **Fix.** When a hypothesis confirms, write the fix. Run the minimal
47
+ repro again — it must now PASS (no more failure). Run the full test
48
+ suite — no new regressions.
49
+
50
+ 6. **Regression-test.** Write a test that pins the fix. The test must
51
+ fail on the pre-fix code and pass on the fixed code. This is what
52
+ stops the bug from coming back six months later when someone
53
+ refactors.
54
+
55
+ ## The Phase-1 prerequisite (load-bearing)
56
+
57
+ apt:debug's body enforces this constraint: **you do NOT advance from
58
+ Observe to Hypothesize until Phase 1 produces a reliable repro loop**.
59
+
60
+ If you cannot reproduce reliably:
61
+
62
+ - Spend the budget on getting the repro working before guessing causes
63
+ - Add logging in the production / target environment to gather more
64
+ evidence of the failure trigger
65
+ - Ask the user for more context (when does it happen? on what input?
66
+ intermittent or consistent?)
67
+
68
+ It is better to spend 80% of your debug budget on the repro and 20% on
69
+ the fix than to spend 100% guessing at causes. Hypothesis-driven
70
+ debugging without a reliable repro is just hope, dressed up as method.
71
+
72
+ ## Distinguishing Phase 1 from apt:debug's existing "Observe"
73
+
74
+ apt:debug's existing Section 2 collects symptom evidence (error
75
+ messages, user reports, environment). That's still valuable — it's the
76
+ *input* to Phase 1. But "I read the error and inferred the cause"
77
+ DOES NOT count as a repro. The repro is a callable artifact: a
78
+ command-line invocation, a test, a script — something the agent can
79
+ run on demand to trigger the failure.
80
+
81
+ ## Distinguishing Phase 2 (Minimise) from Phase 3 (Hypothesise)
82
+
83
+ These are different mental modes. Minimise asks "what's the smallest
84
+ thing that still fails?" — a reductive activity. Hypothesise asks
85
+ "WHY does it fail?" — an explanatory activity. Pocock's discipline
86
+ separates them because skipping Phase 2 and going straight from "I can
87
+ repro" to "here's why" tends to anchor on the first plausible cause
88
+ and miss alternatives.
89
+
90
+ ## Practical consequence for the apt:debug body
91
+
92
+ When this appendix is loaded, apt:debug's Section 2 (Observe) renames
93
+ its "Reproduce" sub-step to **Phase 1 (Reproduce — prerequisite)** and
94
+ adds a gate: the skill MUST NOT emit any hypothesis (Section 3) until
95
+ DEBUG.md records a `repro_loop_verified: true` field.
96
+
97
+ The appendix's Phase 2 (Minimise) becomes a new sub-section between
98
+ apt:debug's Section 2 (Observe) and Section 3 (Hypothesize). DEBUG.md
99
+ gains a `minimised_repro` field.
100
+
101
+ apt:debug's Sections 3-5 (Hypothesize / Checkpoint / Test / Conclude)
102
+ align with Phases 3-6 of this appendix; no structural change there.
103
+
104
+ ## Anti-patterns this discipline prevents
105
+
106
+ - **Sprint-to-hypothesis.** Agent reads the error, picks the first
107
+ plausible cause, writes a fix, and ships. The bug comes back two
108
+ days later under different inputs.
109
+ - **Stack-trace cargo cult.** Agent assumes the line at the top of the
110
+ stack trace IS the bug. Often it's an effect, not a cause.
111
+ - **Test the fix, not the bug.** Agent writes a test that proves the
112
+ fix works but doesn't exercise the original buggy condition. The
113
+ regression test is a no-op against the future regression.
114
+ - **Logging without minimising.** Agent adds 50 log lines in a 200-line
115
+ repro, then drowns in noise. Minimise first; THEN instrument the
116
+ minimal repro.
117
+
118
+ Loading this appendix and following its Phase 1 prerequisite cuts most
119
+ of these failure modes by structural constraint.
@@ -0,0 +1,378 @@
1
+ ---
2
+ name: apt:diagram
3
+ description: "Generate .excalidraw or MCP-canvas diagrams"
4
+ apt-skill-version: {{APT_VERSION}}
5
+ stage: design
6
+ intent: design
7
+ when_to_use: "The user wants to generate a diagram from a prompt — system architecture sketch, flowchart, data-flow picture. Produces either a local .excalidraw file (default) or a live Excalidraw MCP canvas."
8
+ user_invocable: true
9
+ internal: false
10
+ spawns_agent: false
11
+ agent_name: null
12
+ task_context: self-managed
13
+ # default_track omitted — self-managed skill, router does not consult this field
14
+ default_execution_mode: auto
15
+ execution_modes:
16
+ - auto
17
+ - step
18
+ allowed-tools: "Read, Write, Edit, Bash, Grep, Glob"
19
+ argument-hint: "apt:diagram [prompt] [--mode=file|--mode=mcp] [--depth=default|comprehensive]"
20
+ gates: []
21
+ ---
22
+
23
+ <objective>
24
+ Turn a natural-language prompt into a diagram. The skill splits the
25
+ work into two phases: (1) the agent itself produces a `DiagramScene`
26
+ JSON (a minimal graph of labelled `nodes` and directed `edges`) from
27
+ the user prompt, and (2) a deterministic renderer turns that scene
28
+ into Excalidraw geometry — either a local `.excalidraw` file under
29
+ `docs/diagrams/<slug>.excalidraw` or a live canvas via the Excalidraw
30
+ MCP server.
31
+
32
+ There are exactly two render modes — `file` (offline, default) and
33
+ `mcp` (live canvas). There is no `auto` mode and no automatic
34
+ degradation between them: if the chosen renderer fails, the skill
35
+ hard-errors with a message telling the user to re-run with the other
36
+ mode. This is intentional in v1 — diagnostics over magic.
37
+ </objective>
38
+
39
+ <process>
40
+
41
+ ## 1. Parse arguments
42
+
43
+ `$ARGUMENTS` may contain free-text prompt material plus at most one
44
+ `--mode=<value>` flag and at most one `--depth=<value>` flag. Extract the
45
+ prompt by removing any flags (if present) and trimming. Extract each
46
+ flag value if any.
47
+
48
+ - If a `--mode=...` flag is present and its value is anything other
49
+ than `"file"` or `"mcp"`, emit a hard error of the exact shape:
50
+ `Unknown --mode value: <value>. Expected file or mcp.` and exit.
51
+ - Empty prompt → emit `apt:diagram requires a prompt describing what to draw.` and exit.
52
+ - If a `--depth=...` flag is present and its value is anything other
53
+ than `"default"` or `"comprehensive"`, emit a hard error of the exact
54
+ shape: `Unknown --depth value: <value>. Expected default or comprehensive.`
55
+ and exit. The default value when absent is `default`. When the value
56
+ is `comprehensive`, before generating the scene in Step 3 load
57
+ `appendices/design-discipline.md` into reasoning context (mirrors
58
+ apt:discuss's `appendices/grill-discipline.md` load pattern). The flag
59
+ is per-call only — it is NOT persisted to `.aperant/config.json`.
60
+
61
+ ## 2. Resolve mode
62
+
63
+ Resolution order: --mode flag > .aperant/config.json#diagram.mode > "file" default
64
+
65
+ Read `.aperant/config.json` and look for `diagram.mode`. The lookup
66
+ runs through the same in-skill Node one-liner pattern apt:quick uses
67
+ for `preferences.quick_task_post_verify` (see apt-quick/SKILL.md
68
+ Step 8):
69
+
70
+ ```bash
71
+ node -e '
72
+ const fs = require("fs");
73
+ const path = require("path");
74
+ const cfgPath = path.join(process.cwd(), ".aperant/config.json");
75
+ let mode = "file"; // default
76
+ try {
77
+ const cfg = JSON.parse(fs.readFileSync(cfgPath, "utf-8"));
78
+ if (cfg && cfg.diagram && (cfg.diagram.mode === "file" || cfg.diagram.mode === "mcp")) {
79
+ mode = cfg.diagram.mode;
80
+ }
81
+ } catch {}
82
+ // The flag override (if present) is layered on top in the agent step,
83
+ // not in this snippet — this only reads the persisted default.
84
+ process.stdout.write(mode);
85
+ '
86
+ ```
87
+
88
+ If the user passed `--mode=file` or `--mode=mcp`, that wins
89
+ unconditionally over whatever the file says — the flag is a one-call
90
+ override and is not persisted to config.
91
+
92
+ ## 3. Generate a `DiagramScene`
93
+
94
+ The agent itself produces a JSON object of shape:
95
+
96
+ ```json
97
+ {
98
+ "nodes": [{ "id": "<stable-id>", "label": "<short-label>", "x": <number>, "y": <number> }, ...],
99
+ "edges": [{ "from": "<id>", "to": "<id>", "label": "<optional>" }, ...]
100
+ }
101
+ ```
102
+
103
+ Constraints the agent MUST honour while producing the scene:
104
+
105
+ - **Stable string ids.** Every node has a short, stable `id` (lowercase
106
+ kebab works well — `auth-service`, `db`, `cdn`). Edges reference
107
+ these ids in `from`/`to`. NEVER use positional ids like `n0`/`n1`.
108
+ - **Endpoint validity.** Every `edge.from` and `edge.to` MUST appear in
109
+ the `nodes` array. The renderer hard-errors on a missing endpoint
110
+ with `DiagramScene edge references unknown node id: <id>` — that is
111
+ a failure of scene generation, not a renderer bug, so the agent
112
+ MUST validate this before dispatching to the renderer.
113
+ - **Coordinates.** Use a rough grid to avoid overlap — e.g. 200px
114
+ horizontal spacing between sibling nodes, 150px vertical between
115
+ layers. Nodes default to ~180x70 px in the renderer; keep ≥200px
116
+ centre-to-centre.
117
+ - **Scope.** v1 has no groups, containers, or layout constraints.
118
+ Don't invent extra fields — the renderer ignores them at best,
119
+ rejects the scene at worst.
120
+
121
+ ## 4. File mode — dispatch to `renderToFile`
122
+
123
+ When the resolved mode is `file`:
124
+
125
+ 1. Derive a slug from the prompt by calling `deriveSlug(prompt)` from
126
+ `@aperant/core/workflows/diagram`. The slug becomes the filename
127
+ under `docs/diagrams/<slug>.excalidraw`.
128
+ 2. Invoke `renderToFile({ scene, slug, projectDir })` via a small Node
129
+ invocation — same precedent as apt:design Step 3 ("invoke the
130
+ library entry point via a small Node invocation"). The agent
131
+ serialises the scene into the command via a temp file or
132
+ `JSON.stringify`-ed env var:
133
+
134
+ ```bash
135
+ node -e '
136
+ import("@aperant/core/workflows/diagram").then(async (m) => {
137
+ const scene = JSON.parse(process.env.APT_DIAGRAM_SCENE);
138
+ const slug = m.deriveSlug(process.env.APT_DIAGRAM_PROMPT || "diagram");
139
+ const result = await m.renderToFile({
140
+ scene,
141
+ slug,
142
+ projectDir: process.cwd(),
143
+ });
144
+ process.stdout.write(result.absolutePath);
145
+ }).catch((err) => {
146
+ process.stderr.write("renderToFile failed: " + err.message + "\n");
147
+ process.exit(1);
148
+ });
149
+ '
150
+ ```
151
+
152
+ Pass the scene as `APT_DIAGRAM_SCENE` env (JSON-encoded) and the raw
153
+ prompt as `APT_DIAGRAM_PROMPT`. The wrapper prints the absolute path
154
+ to stdout on success.
155
+
156
+ 3. On success, print to the user:
157
+
158
+ ```
159
+ Diagram written to <absolutePath>. Open it in Excalidraw (drag-drop the file into excalidraw.com or open via Aperant's Excalidraw integration).
160
+ ```
161
+
162
+ Relative path under the project: `docs/diagrams/<slug>.excalidraw`.
163
+
164
+ 4. On failure (any `Error` thrown from `renderToFile`, including the
165
+ `unknown node id` validation case), surface the message to the user
166
+ verbatim with no retry and no fallback. Keep the generated scene
167
+ JSON visible in the chat so the user can inspect what was
168
+ produced — that is the diagnostic.
169
+
170
+ ## 5. MCP mode — consent gate + renderer dispatch
171
+
172
+ When the resolved mode is `mcp`, the skill runs three sub-steps in
173
+ order: (a) consent gate, (b) renderer dispatch, (c) failure handling.
174
+
175
+ ### 5a. Consent gate
176
+
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:
209
+
210
+ ```json
211
+ { "host": "<host string>", "decision": "granted" | "denied", "grantedAt": "<ISO 8601 timestamp>" }
212
+ ```
213
+
214
+ or `null` if no decision has ever been recorded.
215
+
216
+ Determine the "configured MCP host" from the connected Excalidraw MCP
217
+ server. The skill does NOT manage MCP server installation — it reads
218
+ the host name from whatever `mcp__excalidraw__*` namespace is already
219
+ present on the agent's tool surface. (For most setups the host is
220
+ something like `mcp.excalidraw.com` or a self-hosted URL — use
221
+ whatever the surface reports.)
222
+
223
+ Decide whether to prompt:
224
+
225
+ - If `diagram.mcpConsent` is `null` → prompt.
226
+ - If `diagram.mcpConsent.host !== <configured MCP host>` → prompt.
227
+ - If `diagram.mcpConsent.host === <configured MCP host>` AND `diagram.mcpConsent.decision === "denied"` → hard-error immediately with the exact denied text (no re-prompt):
228
+ ```
229
+ diagram MCP consent denied; re-run with `--mode=file` or change diagram.mcpConsent in .aperant/config.local.json
230
+ ```
231
+ - Otherwise (host matches AND decision is `"granted"`) → the existing consent applies; proceed to 5b.
232
+
233
+ When prompting, print a single line naming the host and ask `granted / denied`:
234
+
235
+ ```
236
+ apt:diagram is about to send diagram data to the Excalidraw MCP server at <host>. Type `granted` to allow, `denied` to refuse.
237
+ ```
238
+
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:
243
+
244
+ ```bash
245
+ node -e '
246
+ const fs = require("fs");
247
+ const path = require("path");
248
+ const cfgPath = path.join(process.cwd(), ".aperant/config.local.json");
249
+ const decision = process.env.APT_DIAGRAM_DECISION; // "granted" | "denied"
250
+ if (decision !== "granted" && decision !== "denied") {
251
+ process.stderr.write("invalid decision\n");
252
+ process.exit(1);
253
+ }
254
+ const host = process.env.APT_DIAGRAM_HOST;
255
+ let cfg = { diagram: { mcpConsent: null } };
256
+ try { cfg = JSON.parse(fs.readFileSync(cfgPath, "utf-8")); } catch {}
257
+ cfg.diagram = cfg.diagram || { mcpConsent: null };
258
+ cfg.diagram.mcpConsent = {
259
+ host,
260
+ decision,
261
+ grantedAt: new Date().toISOString(),
262
+ };
263
+ fs.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2) + "\n", "utf-8");
264
+ '
265
+ ```
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
+
271
+ If the decision is `"denied"`, exit with EXACTLY this text (verbatim,
272
+ no rewording, no extra punctuation):
273
+
274
+ ```
275
+ diagram MCP consent denied; re-run with `--mode=file` or change diagram.mcpConsent in .aperant/config.local.json
276
+ ```
277
+
278
+ If the decision is `"granted"`, proceed to 5b.
279
+
280
+ ### 5b. Renderer dispatch
281
+
282
+ The skill IS the MCP renderer — there is no shared TS module here. The
283
+ agent dispatches MCP tool calls directly:
284
+
285
+ 1. Iterate `scene.nodes` FIRST. For each node, call the Excalidraw MCP
286
+ `create_element` tool (expected name `mcp__excalidraw__create_element`;
287
+ if the connected server exposes a different equivalent, use that
288
+ discovered name) with rectangle parameters: position from
289
+ `node.x`/`node.y`, default size 180x70, and the `node.label` as
290
+ text content. Record the element id the MCP server returns.
291
+ 2. Then iterate `scene.edges`. For each edge, call the same tool with
292
+ arrow parameters, setting `startBinding.elementId` to the rectangle
293
+ id created for `edge.from` and `endBinding.elementId` to the
294
+ rectangle id created for `edge.to`. If `edge.label` is set, pass it
295
+ as the arrow's label.
296
+
297
+ Nodes MUST be created before edges so the edge bindings have valid
298
+ element ids on both ends.
299
+
300
+ On success, surface whatever stable identifier the MCP server returned
301
+ (session id, canvas URL, or scene id) so the user can open the live
302
+ canvas — e.g. `Live canvas: <url>`.
303
+
304
+ ### 5c. Failure handling
305
+
306
+ If ANY `mcp__excalidraw__*` tool call fails — tool error, transport
307
+ error, or the expected tool is missing from the surface — emit EXACTLY
308
+ this message (verbatim) and exit:
309
+
310
+ ```
311
+ Excalidraw MCP renderer failed: <reason>. Re-run with `--mode=file` to generate an offline .excalidraw file.
312
+ ```
313
+
314
+ No retries. No automatic switch to file mode. No partial cleanup of
315
+ already-created MCP elements (v1 leaves them as a debugging artifact;
316
+ v2 may add cleanup).
317
+
318
+ ## 7. Quality pass loop (file mode only)
319
+
320
+ (Step 6 is intentionally reserved for future MCP-side quality work; v2
321
+ ships Step 7 for file mode only.)
322
+
323
+ After Step 4 (file written), if Playwright is installed, run a visual
324
+ quality-pass loop. The loop is capped at **3 iterations**. If
325
+ `validateScene(scene).score >= 70` AND the vision review reports no
326
+ critical issues, exit early.
327
+
328
+ ### 7a. Detect Playwright
329
+
330
+ Probe with:
331
+
332
+ ```bash
333
+ node -e 'import("playwright").then(() => process.stdout.write("yes"), () => process.stdout.write("no"))'
334
+ ```
335
+
336
+ If the result is `no`, skip Step 7 silently and append the following
337
+ one-line tip to the success message printed in Step 4:
338
+
339
+ ```
340
+ Tip: install playwright to enable visual quality-pass on future runs (pnpm add -D playwright && npx playwright install chromium).
341
+ ```
342
+
343
+ ### 7b. Loop body (max 3 iterations)
344
+
345
+ For each iteration `i` in `0..2`:
346
+
347
+ 1. Call `renderToPng({ excalidrawPath })` from `@aperant/core/workflows/diagram`
348
+ via the same Node-invocation pattern Step 4 uses. Capture the
349
+ absolute PNG path.
350
+ 2. Call `validateScene(scene)` from the same module. Capture
351
+ `{warnings, score}`.
352
+ 3. Read the PNG via the `Read` tool (multimodal vision review).
353
+ 4. Decide:
354
+ - If `score >= 70` AND the vision review reports no critical issues
355
+ (overlaps, off-canvas elements, illegible text), exit the loop
356
+ and return success.
357
+ - Else: emit IR patches as a JSON delta. Mutable fields are
358
+ exactly `DiagramNode.x`, `DiagramNode.y`, and `DiagramNode.label`.
359
+ Apply the patches to `scene`, re-call `renderToFile` (re-using
360
+ the slug from Step 4), and continue to the next iteration.
361
+ 5. After 3 iterations: return final state. If unresolved warnings
362
+ remain, print exactly:
363
+
364
+ ```
365
+ Quality pass: <N> warning(s) remain after 3 iterations. See <png-path> to inspect.
366
+ ```
367
+
368
+ ### 7c. Hard rules
369
+
370
+ - Iteration cap is exactly **3** (hard-coded, not configurable in v2).
371
+ - Score threshold is exactly **70** (hard-coded, not configurable in v2).
372
+ - Patch grammar is `{x, y, label}` — no other `DiagramNode`/
373
+ `DiagramEdge` fields are mutable.
374
+ - If `renderToPng` throws `DiagramMissingDep`, treat the same as Step
375
+ 7a's `no` branch (silent skip + tip). Any OTHER `renderToPng` error
376
+ fails the loop and surfaces verbatim to the user.
377
+
378
+ </process>
@@ -0,0 +1,97 @@
1
+ # apt:diagram — design-discipline appendix
2
+
3
+ Loaded into `apt:diagram`'s reasoning context when the skill is invoked
4
+ with `--depth=comprehensive`. Aperant-authored — not a verbatim port. The
5
+ goal: emit a diagram that earns its space on the page, not a free-floating
6
+ box graph that "looks like a diagram."
7
+
8
+ This appendix is a posture loader. It does not change the IR. It changes
9
+ what the agent decides to put *in* the IR.
10
+
11
+ ## Visual pattern library
12
+
13
+ Excalidraw exposes rectangles, ellipses, diamonds, arrows, text, and
14
+ free-form shapes. When you have a choice, the shape should encode
15
+ information the label can't:
16
+
17
+ - **Rectangle** — a unit of work, a service, a process, a system
18
+ component. Default for "this is a thing that exists." The boring,
19
+ honest shape.
20
+ - **Ellipse** — a state, a phase, a "moment in time." Use when the noun
21
+ is more like a place a flow visits than a thing that owns state.
22
+ - **Diamond** — a decision point, a branching condition. If the
23
+ outgoing arrows have different semantics ("yes" / "no" / "retry"),
24
+ the source should be a diamond.
25
+
26
+ v2 only emits rectangles. The IR has no `shape` field on `DiagramNode`,
27
+ and `renderToFile` hard-codes rectangle elements. The choice above is
28
+ informational — it tells you *what richer shapes would mean* so you can
29
+ note the missing semantics in the diagram's accompanying prose if a
30
+ node really wants to be a diamond. Richer shapes are scheduled for v3.
31
+
32
+ ## Isomorphism test
33
+
34
+ The diagram should be *the same shape* as the thing it depicts.
35
+
36
+ If the system has three layers (edge / app / data), the diagram has
37
+ three visually-distinct horizontal zones. If the system has a single
38
+ hub that talks to four leaves, the diagram has a single central node
39
+ with four spokes — not five nodes in a line.
40
+
41
+ Concretely:
42
+
43
+ - Count the "structural axes" in the thing being depicted. Layers?
44
+ Phases? Owners? Each axis should map to a visual axis (vertical /
45
+ horizontal / radial) in the diagram.
46
+ - If you cannot draw the structural axis, the diagram is lying. Either
47
+ add the missing axis or admit you're depicting a different thing.
48
+ - Symmetry in the system should appear as symmetry in the layout.
49
+ Asymmetry is a flag — either it's load-bearing (good — show it) or
50
+ it's noise (bad — fix it).
51
+
52
+ The `validateStructure` validator codifies one slice of this: it checks
53
+ that nodes occupy the top / middle / bottom thirds of the canvas in
54
+ roughly the expected counts. That's a weak proxy — the real check is in
55
+ your eyes.
56
+
57
+ ## Depth assessment
58
+
59
+ Every diagram answers one of two questions: **what** or **what + how**.
60
+
61
+ - A **what** diagram shows the inventory of things and the connections
62
+ between them. "These are the services. They talk to each other."
63
+ v2 targets this depth.
64
+ - A **what + how** diagram additionally encodes the protocol on each
65
+ connection — request shape, retry policy, idempotency key, transport.
66
+ This often requires labels on every edge, sub-groups around clusters
67
+ of services, and sometimes a second pass at a different zoom level.
68
+ v2 does not target this. The IR has no edge-protocol field.
69
+
70
+ Before generating the scene, decide which question the diagram answers.
71
+ If the user prompt implies "what + how" and you can only render "what,"
72
+ say so in the chat so the user knows they're getting the shallower
73
+ picture.
74
+
75
+ ## Multi-zoom architecture
76
+
77
+ A real system has multiple zoom levels:
78
+
79
+ - **Far zoom** — one diagram showing the 4-7 top-level components and
80
+ their primary connections. Fits on a slide.
81
+ - **Mid zoom** — one diagram per top-level component, showing its
82
+ internal modules. 4-7 boxes each, again.
83
+ - **Near zoom** — one diagram per critical interface, showing the
84
+ protocol/sequence.
85
+
86
+ v2 generates one diagram per `apt:diagram` invocation. If a user prompt
87
+ implies a multi-zoom set ("system architecture for the auth flow,"
88
+ where "the auth flow" is itself worth a deeper drawing), the agent
89
+ should:
90
+
91
+ 1. Generate the far-zoom diagram for v2.
92
+ 2. State in the chat that a mid-zoom diagram for the named subsystem
93
+ would be a natural follow-up.
94
+ 3. NOT chain a second `renderToFile` call automatically.
95
+
96
+ The default is one diagram per prompt. Multi-zoom orchestration is a
97
+ v3 problem.