@design-ai/cli 4.56.0 → 4.57.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.
Files changed (264) hide show
  1. package/.claude-plugin/plugin.json +109 -25
  2. package/CHANGELOG.md +36 -0
  3. package/README.ko.md +9 -26
  4. package/README.md +10 -23
  5. package/cli/bin/design-ai.mjs +1 -0
  6. package/cli/commands/help.mjs +7 -4
  7. package/cli/commands/index.mjs +292 -0
  8. package/cli/commands/learn-help.mjs +149 -0
  9. package/cli/commands/learn-print-profile.mjs +373 -0
  10. package/cli/commands/learn-print-restore.mjs +349 -0
  11. package/cli/commands/learn-print-signals.mjs +453 -0
  12. package/cli/commands/learn.mjs +60 -1281
  13. package/cli/commands/pack.mjs +7 -3
  14. package/cli/commands/prompt.mjs +7 -3
  15. package/cli/commands/search.mjs +94 -1
  16. package/cli/lib/dispatch.mjs +3 -0
  17. package/cli/lib/embedding-index.mjs +199 -0
  18. package/cli/lib/embedding-provider.mjs +121 -0
  19. package/cli/lib/embedding-rerank.mjs +52 -0
  20. package/cli/lib/learn-args.mjs +490 -0
  21. package/cli/lib/learn-backup.mjs +748 -0
  22. package/cli/lib/learn-curation.mjs +612 -0
  23. package/cli/lib/learn-eval.mjs +459 -0
  24. package/cli/lib/learn-profile.mjs +763 -0
  25. package/cli/lib/learn-select.mjs +223 -0
  26. package/cli/lib/learn-shared.mjs +158 -0
  27. package/cli/lib/learn-test-support.mjs +218 -0
  28. package/cli/lib/learn-usage.mjs +360 -0
  29. package/cli/lib/learn.mjs +83 -3694
  30. package/cli/lib/lexical.mjs +137 -0
  31. package/cli/lib/local-config.mjs +110 -0
  32. package/cli/lib/mcp-server.mjs +13 -3
  33. package/cli/lib/pack.mjs +31 -4
  34. package/cli/lib/prompt.mjs +42 -4
  35. package/cli/lib/recall.mjs +193 -0
  36. package/cli/lib/retrieval-index.mjs +0 -0
  37. package/cli/lib/search-ranked.mjs +170 -0
  38. package/cli/lib/search.mjs +11 -1
  39. package/cli/lib/signals-backlog-commands.mjs +673 -0
  40. package/cli/lib/signals-backlog.mjs +361 -0
  41. package/cli/lib/signals-eval.mjs +176 -0
  42. package/cli/lib/signals-readiness.mjs +247 -0
  43. package/cli/lib/signals-registry.mjs +377 -0
  44. package/cli/lib/signals-render.mjs +478 -0
  45. package/cli/lib/signals-shared.mjs +75 -0
  46. package/cli/lib/signals.mjs +16 -2337
  47. package/cli/lib/site-bundle-handoff-expected.mjs +173 -0
  48. package/cli/lib/site-bundle-handoff-runbook-action-summary.mjs +332 -0
  49. package/cli/lib/site-bundle-handoff-runbook-human-lines.mjs +167 -0
  50. package/cli/lib/site-bundle-handoff-runbook-maps.mjs +238 -0
  51. package/cli/lib/site-bundle-handoff-runbook-next-step.mjs +278 -0
  52. package/cli/lib/site-bundle-handoff-runbook.mjs +44 -931
  53. package/cli/lib/site-test-support.mjs +68 -0
  54. package/cli/lib/skill-proposals-apply-commands.mjs +135 -0
  55. package/cli/lib/skill-proposals-apply-contract.mjs +750 -0
  56. package/cli/lib/skill-proposals-apply-plan.mjs +118 -0
  57. package/cli/lib/skill-proposals-generate.mjs +298 -0
  58. package/cli/lib/skill-proposals-render.mjs +532 -0
  59. package/cli/lib/skill-proposals-review.mjs +262 -0
  60. package/cli/lib/skill-proposals.mjs +15 -2046
  61. package/cli/lib/workspace-args.mjs +83 -0
  62. package/cli/lib/workspace-git.mjs +169 -0
  63. package/cli/lib/workspace-learning.mjs +483 -0
  64. package/cli/lib/workspace-repo.mjs +139 -0
  65. package/cli/lib/workspace-report.mjs +283 -0
  66. package/cli/lib/workspace-test-support.mjs +99 -0
  67. package/cli/lib/workspace.mjs +30 -1056
  68. package/docs/AI-LEARNING-PHASE2.md +215 -0
  69. package/docs/DISTRIBUTION.ko.md +2 -2
  70. package/docs/DISTRIBUTION.md +2 -2
  71. package/docs/NEXT-SURFACE-DECISION.md +125 -0
  72. package/docs/PRODUCT-READINESS.md +3 -3
  73. package/docs/RELEASE-GATES.ko.md +35 -0
  74. package/docs/RELEASE-GATES.md +234 -0
  75. package/docs/ROADMAP.md +53 -8
  76. package/docs/external-status.md +6 -8
  77. package/docs/inspection-20260630.md +169 -0
  78. package/docs/integrations/design-ai-mcp-server.md +4 -4
  79. package/docs/reference/ant-design.md +413 -0
  80. package/docs/reference/awesome-design-md.md +439 -0
  81. package/docs/reference/mui.md +783 -0
  82. package/docs/reference/shadcn-ui.md +298 -0
  83. package/examples/component-accordion-actions.md +5 -5
  84. package/examples/component-accordion-details.md +5 -5
  85. package/examples/component-accordion-summary.md +5 -5
  86. package/examples/component-accordion.md +3 -3
  87. package/examples/component-affix.md +1 -1
  88. package/examples/component-alert-dialog.md +1 -1
  89. package/examples/component-alert-title.md +1 -1
  90. package/examples/component-alert.md +3 -3
  91. package/examples/component-anchor.md +1 -1
  92. package/examples/component-app-bar.md +1 -1
  93. package/examples/component-aspect-ratio.md +1 -1
  94. package/examples/component-auto-complete.md +2 -2
  95. package/examples/component-avatar-group.md +5 -5
  96. package/examples/component-avatar.md +3 -3
  97. package/examples/component-back-top.md +1 -1
  98. package/examples/component-backdrop.md +1 -1
  99. package/examples/component-badge.md +3 -3
  100. package/examples/component-border-beam.md +3 -3
  101. package/examples/component-bottom-navigation.md +1 -1
  102. package/examples/component-box.md +1 -1
  103. package/examples/component-breadcrumb.md +3 -3
  104. package/examples/component-button-base.md +3 -3
  105. package/examples/component-button-group.md +2 -2
  106. package/examples/component-button.md +3 -3
  107. package/examples/component-calendar.md +2 -2
  108. package/examples/component-card-actions.md +1 -1
  109. package/examples/component-card-content.md +1 -1
  110. package/examples/component-card-header.md +1 -1
  111. package/examples/component-card-media.md +1 -1
  112. package/examples/component-card.md +3 -3
  113. package/examples/component-carousel.md +2 -2
  114. package/examples/component-cascader.md +1 -1
  115. package/examples/component-chart.md +1 -1
  116. package/examples/component-checkbox.md +3 -3
  117. package/examples/component-click-away-listener.md +1 -1
  118. package/examples/component-code.md +1 -1
  119. package/examples/component-collapsible.md +1 -1
  120. package/examples/component-color-picker.md +1 -1
  121. package/examples/component-combobox.md +1 -1
  122. package/examples/component-command.md +1 -1
  123. package/examples/component-config-provider.md +3 -3
  124. package/examples/component-context-menu.md +1 -1
  125. package/examples/component-css-baseline.md +3 -3
  126. package/examples/component-date-picker.md +2 -2
  127. package/examples/component-descriptions.md +1 -1
  128. package/examples/component-dialog-actions.md +1 -1
  129. package/examples/component-dialog-content-text.md +1 -1
  130. package/examples/component-dialog-content.md +1 -1
  131. package/examples/component-dialog-title.md +1 -1
  132. package/examples/component-dialog.md +2 -2
  133. package/examples/component-divider.md +3 -3
  134. package/examples/component-drawer.md +3 -3
  135. package/examples/component-dropdown.md +3 -3
  136. package/examples/component-empty.md +2 -2
  137. package/examples/component-fade.md +1 -1
  138. package/examples/component-field.md +1 -1
  139. package/examples/component-filled-input.md +1 -1
  140. package/examples/component-flex.md +1 -1
  141. package/examples/component-float-button.md +2 -2
  142. package/examples/component-form-control-label.md +1 -1
  143. package/examples/component-form-control.md +1 -1
  144. package/examples/component-form-controls.md +1 -1
  145. package/examples/component-form-group.md +1 -1
  146. package/examples/component-form-helper-text.md +1 -1
  147. package/examples/component-form-label.md +1 -1
  148. package/examples/component-form.md +2 -2
  149. package/examples/component-grid.md +2 -2
  150. package/examples/component-grow.md +1 -1
  151. package/examples/component-hover-card.md +1 -1
  152. package/examples/component-icon-button.md +1 -1
  153. package/examples/component-icon.md +1 -1
  154. package/examples/component-image-list.md +1 -1
  155. package/examples/component-image.md +1 -1
  156. package/examples/component-input-adornment.md +1 -1
  157. package/examples/component-input-base.md +1 -1
  158. package/examples/component-input-number.md +1 -1
  159. package/examples/component-input-otp.md +2 -2
  160. package/examples/component-input.md +3 -3
  161. package/examples/component-item.md +1 -1
  162. package/examples/component-label.md +1 -1
  163. package/examples/component-layout.md +1 -1
  164. package/examples/component-link.md +1 -1
  165. package/examples/component-list-item-avatar.md +1 -1
  166. package/examples/component-list-item-button.md +1 -1
  167. package/examples/component-list-item-icon.md +1 -1
  168. package/examples/component-list-item-text.md +1 -1
  169. package/examples/component-list-item.md +1 -1
  170. package/examples/component-list-subheader.md +1 -1
  171. package/examples/component-list.md +2 -2
  172. package/examples/component-masonry.md +1 -1
  173. package/examples/component-mentions.md +1 -1
  174. package/examples/component-menu-item.md +1 -1
  175. package/examples/component-menu-list.md +1 -1
  176. package/examples/component-menu.md +1 -1
  177. package/examples/component-menubar.md +1 -1
  178. package/examples/component-message.md +1 -1
  179. package/examples/component-mobile-stepper.md +1 -1
  180. package/examples/component-modal.md +5 -5
  181. package/examples/component-navigation-menu.md +1 -1
  182. package/examples/component-notification.md +1 -1
  183. package/examples/component-outlined-input.md +1 -1
  184. package/examples/component-pagination.md +3 -3
  185. package/examples/component-paper.md +1 -1
  186. package/examples/component-popconfirm.md +1 -1
  187. package/examples/component-popover.md +3 -3
  188. package/examples/component-popper.md +1 -1
  189. package/examples/component-progress.md +2 -2
  190. package/examples/component-qr-code.md +1 -1
  191. package/examples/component-radio.md +3 -3
  192. package/examples/component-rate.md +2 -2
  193. package/examples/component-resizable.md +1 -1
  194. package/examples/component-result.md +1 -1
  195. package/examples/component-scroll-area.md +1 -1
  196. package/examples/component-segmented.md +3 -3
  197. package/examples/component-select.md +3 -3
  198. package/examples/component-separator.md +1 -1
  199. package/examples/component-sheet.md +1 -1
  200. package/examples/component-sidebar.md +1 -1
  201. package/examples/component-skeleton.md +3 -3
  202. package/examples/component-slide.md +1 -1
  203. package/examples/component-slider.md +3 -3
  204. package/examples/component-snackbar-content.md +1 -1
  205. package/examples/component-snackbar.md +1 -1
  206. package/examples/component-sonner.md +1 -1
  207. package/examples/component-space.md +1 -1
  208. package/examples/component-speed-dial-action.md +1 -1
  209. package/examples/component-speed-dial.md +1 -1
  210. package/examples/component-spin.md +2 -2
  211. package/examples/component-spinner.md +1 -1
  212. package/examples/component-splitter.md +2 -2
  213. package/examples/component-stack.md +1 -1
  214. package/examples/component-statistic.md +1 -1
  215. package/examples/component-step-button.md +4 -4
  216. package/examples/component-step-connector.md +4 -4
  217. package/examples/component-step-content.md +1 -1
  218. package/examples/component-step-icon.md +1 -1
  219. package/examples/component-step-label.md +1 -1
  220. package/examples/component-step.md +2 -2
  221. package/examples/component-steps.md +2 -2
  222. package/examples/component-swipeable-drawer.md +1 -1
  223. package/examples/component-switch.md +3 -3
  224. package/examples/component-tab-scroll-button.md +5 -5
  225. package/examples/component-tab.md +1 -1
  226. package/examples/component-table-body.md +1 -1
  227. package/examples/component-table-cell.md +1 -1
  228. package/examples/component-table-container.md +1 -1
  229. package/examples/component-table-footer.md +1 -1
  230. package/examples/component-table-head.md +1 -1
  231. package/examples/component-table-pagination.md +1 -1
  232. package/examples/component-table-row.md +1 -1
  233. package/examples/component-table-sort-label.md +1 -1
  234. package/examples/component-table.md +2 -2
  235. package/examples/component-tabs.md +3 -3
  236. package/examples/component-tag-badge.md +2 -2
  237. package/examples/component-tag.md +1 -1
  238. package/examples/component-textarea-autosize.md +1 -1
  239. package/examples/component-textarea.md +1 -1
  240. package/examples/component-time-picker.md +1 -1
  241. package/examples/component-timeline.md +1 -1
  242. package/examples/component-toast.md +4 -4
  243. package/examples/component-toggle-button.md +1 -1
  244. package/examples/component-toggle.md +1 -1
  245. package/examples/component-toolbar.md +1 -1
  246. package/examples/component-tooltip.md +3 -3
  247. package/examples/component-tour.md +1 -1
  248. package/examples/component-transfer.md +1 -1
  249. package/examples/component-tree-select.md +1 -1
  250. package/examples/component-tree.md +1 -1
  251. package/examples/component-typography.md +2 -2
  252. package/examples/component-upload.md +1 -1
  253. package/examples/component-watermark.md +1 -1
  254. package/examples/component-zoom.md +1 -1
  255. package/knowledge/COVERAGE.md +4 -3
  256. package/knowledge/components/INDEX.md +202 -202
  257. package/knowledge/patterns/brand-references.md +72 -72
  258. package/package.json +1 -1
  259. package/tools/audit/local-ci.py +16 -1
  260. package/tools/audit/package-smoke.py +611 -9
  261. package/tools/audit/registry-smoke.py +534 -0
  262. package/tools/audit/release-metadata.py +47 -1
  263. package/tools/audit/smoke_assertions.py +915 -13
  264. package/tools/migrations/refs-links-to-reference-pages.py +179 -0
@@ -1,2046 +1,15 @@
1
- // Preview-only skill evolution proposal builder for local learning/check signals.
2
-
3
- import { existsSync, readFileSync } from "node:fs";
4
- import { createHash } from "node:crypto";
5
- import path from "node:path";
6
-
7
- import {
8
- defaultLearningFile,
9
- defaultLearningUsageFile,
10
- loadLearningProfile,
11
- } from "./learn.mjs";
12
- import { PACKAGE_ROOT } from "./paths.mjs";
13
- import { routeById } from "./route.mjs";
14
- import { learningSignalRegistry } from "./signals.mjs";
15
-
16
- const DEFAULT_MIN_EVIDENCE_COUNT = 2;
17
- const REVIEW_STATUSES = ["accepted", "rejected", "applied", "deferred"];
18
- const REVIEW_CLEAR_STATUSES = new Set(["rejected", "applied"]);
19
- const APPLY_PLAN_FOLLOW_UP_COMMAND_SPECS = Object.freeze({
20
- reviewCheckJson: ["--review-check", "--json"],
21
- reviewCheckReport: ["--review-check", "--report", "--out", "skill-proposal-review-check.md"],
22
- proposalPatchPreview: ["--patch", "--out", "skill-proposals.patch"],
23
- strictGate: ["--strict", "--json"],
24
- });
25
- const APPLY_PLAN_FOLLOW_UP_COMMAND_POLICIES = Object.freeze({
26
- reviewCheckJson: "preview-only",
27
- reviewCheckReport: "output-artifact",
28
- proposalPatchPreview: "output-artifact",
29
- strictGate: "strict-readiness-gate",
30
- });
31
- const APPLY_PLAN_FOLLOW_UP_COMMAND_DISPLAY_LABELS = Object.freeze({
32
- reviewCheckJson: "Review check JSON",
33
- reviewCheckReport: "Review check Markdown report",
34
- proposalPatchPreview: "Skill proposal patch preview",
35
- strictGate: "Strict proposal readiness gate",
36
- });
37
- const APPLY_PLAN_FOLLOW_UP_COMMAND_DESCRIPTIONS = Object.freeze({
38
- reviewCheckJson: "Check proposal review readiness as machine-readable JSON without writing local files.",
39
- reviewCheckReport: "Generate a Markdown review-check artifact for accepted proposal readiness.",
40
- proposalPatchPreview: "Generate a unified diff preview for accepted skill proposal edits.",
41
- strictGate: "Run the strict proposal readiness gate before marking accepted proposals applied.",
42
- });
43
- const APPLY_PLAN_FOLLOW_UP_COMMAND_OUTPUT_ARTIFACTS = Object.freeze({
44
- reviewCheckReport: "skill-proposal-review-check.md",
45
- proposalPatchPreview: "skill-proposals.patch",
46
- });
47
- const APPLY_PLAN_FOLLOW_UP_COMMAND_OUTPUT_ARTIFACT_TYPES = Object.freeze({
48
- reviewCheckReport: "markdown-report",
49
- proposalPatchPreview: "unified-diff",
50
- });
51
- const APPLY_PLAN_FOLLOW_UP_COMMAND_OUTPUT_ARTIFACT_ACTIONS = Object.freeze({
52
- reviewCheckReport: "render-markdown-report",
53
- proposalPatchPreview: "render-unified-diff-preview",
54
- });
55
- const APPLY_PLAN_FOLLOW_UP_COMMAND_OUTPUT_ARTIFACT_MEDIA_TYPES = Object.freeze({
56
- reviewCheckReport: "text/markdown",
57
- proposalPatchPreview: "text/x-diff",
58
- });
59
- const APPLY_PLAN_FOLLOW_UP_COMMAND_OUTPUT_ARTIFACT_DISPOSITIONS = Object.freeze({
60
- reviewCheckReport: "review-only",
61
- proposalPatchPreview: "manual-apply-preview",
62
- });
63
- const APPLY_PLAN_FOLLOW_UP_COMMAND_OUTPUT_ARTIFACT_MANUAL_APPLY_CANDIDATES = Object.freeze({
64
- reviewCheckReport: false,
65
- proposalPatchPreview: true,
66
- });
67
- const APPLY_PLAN_FOLLOW_UP_COMMAND_OUTPUT_ARTIFACT_REQUIRES_MANUAL_REVIEW = Object.freeze({
68
- reviewCheckReport: false,
69
- proposalPatchPreview: true,
70
- });
71
- const APPLY_PLAN_FOLLOW_UP_COMMAND_OUTPUT_ARTIFACT_REVIEW_INSTRUCTIONS = Object.freeze({
72
- reviewCheckReport: "Review the Markdown readiness report before changing proposal review status.",
73
- proposalPatchPreview: "Review the unified diff manually before applying any skill-file edits.",
74
- });
75
- const APPLY_PLAN_FOLLOW_UP_COMMAND_OUTPUT_ARTIFACT_REQUIRES_CLEAN_WORKSPACE_BEFORE_APPLY = Object.freeze({
76
- reviewCheckReport: false,
77
- proposalPatchPreview: true,
78
- });
79
- const APPLY_PLAN_FOLLOW_UP_COMMAND_OUTPUT_ARTIFACT_APPLY_PRECONDITION_IDS = Object.freeze({
80
- reviewCheckReport: Object.freeze([]),
81
- proposalPatchPreview: Object.freeze(["manual-review", "clean-workspace"]),
82
- });
83
- const APPLY_PLAN_FOLLOW_UP_COMMAND_OUTPUT_ARTIFACT_APPLY_PRECONDITION_LABELS = Object.freeze({
84
- reviewCheckReport: Object.freeze([]),
85
- proposalPatchPreview: Object.freeze(["Manual review completed", "Clean workspace confirmed"]),
86
- });
87
- const APPLY_PLAN_BASE_COMMAND = Object.freeze(["design-ai", "learn", "--propose-skills"]);
88
- const APPLY_PLAN_FORBIDDEN_FLAGS = Object.freeze(["--yes"]);
89
- const CATEGORY_FALLBACK_SKILLS = {
90
- accessibility: "skills/ux-audit/SKILL.md",
91
- korean: "skills/design-system-builder/SKILL.md",
92
- workflow: "skills/handoff-spec/SKILL.md",
93
- brand: "skills/design-critique/SKILL.md",
94
- constraint: "skills/handoff-spec/SKILL.md",
95
- preference: "skills/design-critique/SKILL.md",
96
- other: "skills/design-critique/SKILL.md",
97
- };
98
-
99
- function stableHash(value, length = 10) {
100
- return createHash("sha256").update(String(value)).digest("hex").slice(0, length);
101
- }
102
-
103
- function slug(value) {
104
- return String(value || "skill")
105
- .toLowerCase()
106
- .replace(/[^a-z0-9]+/g, "-")
107
- .replace(/^-+|-+$/g, "")
108
- .slice(0, 44) || "skill";
109
- }
110
-
111
- function previewText(text, maxLength = 180) {
112
- const normalized = String(text || "").replace(/\s+/g, " ").trim();
113
- if (normalized.length <= maxLength) return normalized;
114
- return `${normalized.slice(0, maxLength - 1)}...`;
115
- }
116
-
117
- function applyPreconditionsForCommandKey(commandKey) {
118
- const ids = APPLY_PLAN_FOLLOW_UP_COMMAND_OUTPUT_ARTIFACT_APPLY_PRECONDITION_IDS[commandKey] || [];
119
- const labels = APPLY_PLAN_FOLLOW_UP_COMMAND_OUTPUT_ARTIFACT_APPLY_PRECONDITION_LABELS[commandKey] || [];
120
- return ids.map((id, index) => ({
121
- id,
122
- label: labels[index] || id,
123
- required: true,
124
- }));
125
- }
126
-
127
- function applyPreconditionIsSatisfied(precondition) {
128
- return precondition?.satisfied === true;
129
- }
130
-
131
- function manualApplyBlockedReason({ manualApplyCandidate, requiredPendingApplyPreconditionCount }) {
132
- if (!manualApplyCandidate) {
133
- return {
134
- code: "not-manual-apply-candidate",
135
- message: "This output artifact is review-only and cannot be applied.",
136
- };
137
- }
138
- if (requiredPendingApplyPreconditionCount > 0) {
139
- return {
140
- code: "required-preconditions-pending",
141
- message: "Complete required apply preconditions before applying this patch preview.",
142
- };
143
- }
144
- return { code: "", message: "" };
145
- }
146
-
147
- function manualApplyStatus({ manualApplyCandidate, manualApplyReady }) {
148
- if (manualApplyReady) return "ready";
149
- if (manualApplyCandidate) return "blocked";
150
- return "not-applicable";
151
- }
152
-
153
- function manualApplyStatusLabel(status) {
154
- if (status === "ready") return "Ready to apply";
155
- if (status === "blocked") return "Blocked";
156
- return "Review only";
157
- }
158
-
159
- function manualApplyStatusTone(status) {
160
- if (status === "ready") return "success";
161
- if (status === "blocked") return "warning";
162
- return "neutral";
163
- }
164
-
165
- function routeIdFromSource(source) {
166
- const text = String(source || "").trim();
167
- if (!text.startsWith("check:")) return "";
168
- const routeId = text.slice("check:".length).trim();
169
- if (!routeId || routeId === "artifact") return "";
170
- return routeId;
171
- }
172
-
173
- function issueTitleFromText(text) {
174
- const normalized = String(text || "").replace(/\s+/g, " ").trim();
175
- const match = normalized.match(/^Improve future outputs by addressing\s+([^:]+):/i);
176
- return match ? match[1].trim() : "";
177
- }
178
-
179
- function routeSkillPath(routeId, sourceRoot) {
180
- if (!routeId) return "";
181
- try {
182
- const route = routeById({ routeId, sourceRoot });
183
- const firstExistingSkill = (route.skills || []).find((skill) => skill.exists);
184
- return firstExistingSkill?.path || route.skills?.[0]?.path || "";
185
- } catch {
186
- return "";
187
- }
188
- }
189
-
190
- function candidateSkillForEntry(entry, sourceRoot) {
191
- const routeId = routeIdFromSource(entry.source);
192
- const routeSkill = routeSkillPath(routeId, sourceRoot);
193
- return {
194
- routeId,
195
- skillPath: routeSkill || CATEGORY_FALLBACK_SKILLS[entry.category] || CATEGORY_FALLBACK_SKILLS.other,
196
- };
197
- }
198
-
199
- function riskForGroup(group) {
200
- if (group.routeIds.size > 1) return "medium";
201
- if (group.entries.length >= 5) return "medium";
202
- if (group.skillPath === "skills/design-system-builder/SKILL.md") return "medium";
203
- return "low";
204
- }
205
-
206
- function instructionDeltaForGroup(group) {
207
- const titleText = [...group.titles].join(" ").toLowerCase();
208
- if (group.category === "accessibility") {
209
- return "Add a pre-handoff accessibility checkpoint: when the artifact includes UI behavior, explicitly cover keyboard reachability, visible focus state, screen-reader semantics, and WCAG 2.1 AA contrast.";
210
- }
211
- if (group.category === "korean") {
212
- return "Add a Korean-context checkpoint: when Korean users or copy are in scope, explicitly verify Hangul typography, line-height, honorific level, local density conventions, and Korean copy risks.";
213
- }
214
- if (titleText.includes("responsive") || titleText.includes("mobile") || titleText.includes("viewport")) {
215
- return "Add a responsive QA checkpoint: describe desktop, tablet, and mobile behavior, likely breakpoint risks, and the verification command before handoff.";
216
- }
217
- if (group.category === "workflow") {
218
- return "Add a workflow QA checkpoint: convert recurring artifact warnings into evidence, next action, verification command, and residual risk before final handoff.";
219
- }
220
- if (group.category === "brand") {
221
- return "Add a brand-fit checkpoint: state brand tone, visual density, copy posture, and any mismatch risk when the task includes product or marketing artifacts.";
222
- }
223
- if (group.category === "constraint") {
224
- return "Add a guardrail checkpoint: restate operator constraints, dependency limits, and external-system boundaries before proposing implementation changes.";
225
- }
226
- return "Add a learned-preference checkpoint: fold repeated local feedback into the skill's final review checklist with evidence and a verification step.";
227
- }
228
-
229
- function verificationCommandForGroup(group) {
230
- const routeId = [...group.routeIds].sort()[0] || "";
231
- if (routeId) {
232
- return `node cli/bin/design-ai.mjs check --examples --route ${routeId} --limit 1 --strict --json`;
233
- }
234
- return "node cli/bin/design-ai.mjs check --examples --route design-from-brief --limit 1 --strict --json";
235
- }
236
-
237
- function evidenceForEntry(entry) {
238
- return {
239
- kind: "check-capture",
240
- entryId: entry.id,
241
- category: entry.category,
242
- source: entry.source || "check:artifact",
243
- routeId: routeIdFromSource(entry.source) || "",
244
- title: issueTitleFromText(entry.text),
245
- createdAt: entry.createdAt || "",
246
- textPreview: previewText(entry.text),
247
- };
248
- }
249
-
250
- function groupCheckCaptureEntries(entries, sourceRoot) {
251
- const groups = new Map();
252
- for (const entry of entries) {
253
- const candidate = candidateSkillForEntry(entry, sourceRoot);
254
- const key = `${candidate.skillPath}\n${entry.category || "other"}`;
255
- if (!groups.has(key)) {
256
- groups.set(key, {
257
- key,
258
- skillPath: candidate.skillPath,
259
- category: entry.category || "other",
260
- entries: [],
261
- routeIds: new Set(),
262
- sources: new Set(),
263
- titles: new Set(),
264
- });
265
- }
266
- const group = groups.get(key);
267
- group.entries.push(entry);
268
- if (candidate.routeId) group.routeIds.add(candidate.routeId);
269
- if (entry.source) group.sources.add(entry.source);
270
- const title = issueTitleFromText(entry.text);
271
- if (title) group.titles.add(title);
272
- }
273
- return [...groups.values()];
274
- }
275
-
276
- function proposalFromGroup(group) {
277
- const evidence = group.entries
278
- .slice()
279
- .sort((a, b) => String(b.createdAt || "").localeCompare(String(a.createdAt || "")))
280
- .slice(0, 5)
281
- .map(evidenceForEntry);
282
- const routeLabel = [...group.routeIds].sort().join(", ") || "artifact";
283
- const title = `Update ${group.skillPath} for repeated ${group.category} check captures`;
284
- const hashInput = `${group.skillPath}|${group.category}|${[...group.sources].sort().join(",")}|${[...group.titles].sort().join(",")}`;
285
- return {
286
- id: `skill-proposal-${slug(path.basename(path.dirname(group.skillPath) || group.skillPath))}-${stableHash(hashInput)}`,
287
- candidateSkill: path.basename(path.dirname(group.skillPath) || group.skillPath),
288
- candidateSkillPath: group.skillPath,
289
- title,
290
- riskLevel: riskForGroup(group),
291
- category: group.category,
292
- routeIds: [...group.routeIds].sort(),
293
- evidenceSources: evidence,
294
- sourceIssueCount: group.entries.length,
295
- proposedInstructionDelta: instructionDeltaForGroup(group),
296
- verificationCommand: verificationCommandForGroup(group),
297
- rationale: `Repeated ${group.category} check captures were recorded for ${routeLabel}; preview this delta before editing the skill file.`,
298
- };
299
- }
300
-
301
- function skippedFromGroup(group, minEvidenceCount) {
302
- return {
303
- candidateSkillPath: group.skillPath,
304
- category: group.category,
305
- sourceIssueCount: group.entries.length,
306
- reason: `Needs at least ${minEvidenceCount} related check-capture entries before proposing a skill edit.`,
307
- evidenceSources: group.entries
308
- .slice()
309
- .sort((a, b) => String(b.createdAt || "").localeCompare(String(a.createdAt || "")))
310
- .slice(0, 3)
311
- .map(evidenceForEntry),
312
- };
313
- }
314
-
315
- function proposalStatus({ signalStatus, proposalCount }) {
316
- if (signalStatus === "fail") return "fail";
317
- if (signalStatus && signalStatus !== "pass") return "warn";
318
- if (proposalCount > 0) return "warn";
319
- return "pass";
320
- }
321
-
322
- function normalizeReviewStatus(rawStatus) {
323
- const status = String(rawStatus || "").trim().toLowerCase();
324
- return REVIEW_STATUSES.includes(status) ? status : "";
325
- }
326
-
327
- function emptyProposalReviewState(reviewFile = "") {
328
- return {
329
- file: reviewFile ? path.resolve(reviewFile) : "",
330
- exists: false,
331
- status: reviewFile ? "missing" : "not-configured",
332
- decisionCount: 0,
333
- matchedCount: 0,
334
- staleCount: 0,
335
- pendingCount: 0,
336
- acceptedCount: 0,
337
- rejectedCount: 0,
338
- appliedCount: 0,
339
- deferredCount: 0,
340
- clearedCount: 0,
341
- warnings: [],
342
- decisionsByProposalId: new Map(),
343
- };
344
- }
345
-
346
- export function loadSkillProposalReviewState(reviewFile = "") {
347
- if (!reviewFile) return emptyProposalReviewState();
348
- const resolvedFile = path.resolve(reviewFile);
349
- if (!existsSync(resolvedFile)) return emptyProposalReviewState(resolvedFile);
350
-
351
- const state = emptyProposalReviewState(resolvedFile);
352
- state.exists = true;
353
- state.status = "pass";
354
-
355
- let rawPayload = null;
356
- try {
357
- rawPayload = JSON.parse(readFileSync(resolvedFile, "utf8"));
358
- } catch {
359
- return {
360
- ...state,
361
- status: "fail",
362
- warnings: [`Review file is not valid JSON: ${resolvedFile}`],
363
- };
364
- }
365
-
366
- const rawDecisions = Array.isArray(rawPayload?.decisions)
367
- ? rawPayload.decisions
368
- : Array.isArray(rawPayload?.reviews)
369
- ? rawPayload.reviews
370
- : [];
371
-
372
- if (!Array.isArray(rawPayload?.decisions) && !Array.isArray(rawPayload?.reviews)) {
373
- state.status = "warn";
374
- state.warnings.push("Review file should contain a decisions array.");
375
- }
376
-
377
- rawDecisions.forEach((decision, index) => {
378
- const proposalId = String(decision?.proposalId || decision?.id || "").trim();
379
- const status = normalizeReviewStatus(decision?.status);
380
- if (!proposalId) {
381
- state.status = state.status === "fail" ? "fail" : "warn";
382
- state.warnings.push(`Decision ${index + 1} is missing proposalId.`);
383
- return;
384
- }
385
- if (!status) {
386
- state.status = state.status === "fail" ? "fail" : "warn";
387
- state.warnings.push(`Decision ${proposalId} has unsupported status; use ${REVIEW_STATUSES.join(", ")}.`);
388
- return;
389
- }
390
- state.decisionsByProposalId.set(proposalId, {
391
- proposalId,
392
- status,
393
- reviewedAt: String(decision?.reviewedAt || decision?.createdAt || "").trim(),
394
- reviewer: String(decision?.reviewer || "").trim(),
395
- note: String(decision?.note || decision?.reason || "").trim(),
396
- });
397
- });
398
-
399
- state.decisionCount = state.decisionsByProposalId.size;
400
- return state;
401
- }
402
-
403
- function applySkillProposalReviewState(proposals, reviewState) {
404
- const decisions = reviewState?.decisionsByProposalId instanceof Map
405
- ? reviewState.decisionsByProposalId
406
- : new Map();
407
- const currentProposalIds = new Set(proposals.map((proposal) => proposal.id));
408
- const reviewedProposals = proposals.map((proposal) => {
409
- const decision = decisions.get(proposal.id);
410
- const reviewStatus = decision?.status || "pending";
411
- const reviewClearsStrict = REVIEW_CLEAR_STATUSES.has(reviewStatus);
412
- return {
413
- ...proposal,
414
- reviewStatus,
415
- reviewClearsStrict,
416
- ...(decision ? { reviewDecision: decision } : {}),
417
- };
418
- });
419
-
420
- const counts = {
421
- matchedCount: reviewedProposals.filter((proposal) => proposal.reviewDecision).length,
422
- staleCount: [...decisions.keys()].filter((proposalId) => !currentProposalIds.has(proposalId)).length,
423
- pendingCount: reviewedProposals.filter((proposal) => !proposal.reviewClearsStrict).length,
424
- acceptedCount: reviewedProposals.filter((proposal) => proposal.reviewStatus === "accepted").length,
425
- rejectedCount: reviewedProposals.filter((proposal) => proposal.reviewStatus === "rejected").length,
426
- appliedCount: reviewedProposals.filter((proposal) => proposal.reviewStatus === "applied").length,
427
- deferredCount: reviewedProposals.filter((proposal) => proposal.reviewStatus === "deferred").length,
428
- clearedCount: reviewedProposals.filter((proposal) => proposal.reviewClearsStrict).length,
429
- };
430
-
431
- return {
432
- proposals: reviewedProposals,
433
- review: {
434
- file: reviewState?.file || "",
435
- exists: Boolean(reviewState?.exists),
436
- status: reviewState?.status || "not-configured",
437
- decisionCount: reviewState?.decisionCount || 0,
438
- warnings: reviewState?.warnings || [],
439
- ...counts,
440
- },
441
- };
442
- }
443
-
444
- function skillProposalStatus({ signalStatus, reviewStatus, pendingReviewCount, reviewWarnings }) {
445
- if (signalStatus === "fail" || reviewStatus === "fail") return "fail";
446
- if (signalStatus && signalStatus !== "pass") return "warn";
447
- if (reviewWarnings > 0) return "warn";
448
- if (pendingReviewCount > 0) return "warn";
449
- return "pass";
450
- }
451
-
452
- export function buildSkillEvolutionProposals({
453
- filePath = defaultLearningFile(),
454
- usageFile = "",
455
- signalSource = "",
456
- root = process.cwd(),
457
- sourceRoot = PACKAGE_ROOT,
458
- now = new Date(),
459
- minEvidenceCount = DEFAULT_MIN_EVIDENCE_COUNT,
460
- reviewFile = "",
461
- signalRegistryProvider = learningSignalRegistry,
462
- } = {}) {
463
- const resolvedFile = path.resolve(filePath);
464
- const resolvedUsageFile = path.resolve(usageFile || defaultLearningUsageFile(resolvedFile));
465
- const generatedAt = (now instanceof Date ? now : new Date(now)).toISOString();
466
- const profile = loadLearningProfile(resolvedFile);
467
- const checkEntries = (profile.entries || [])
468
- .filter((entry) => String(entry.source || "").startsWith("check:"));
469
- const groups = groupCheckCaptureEntries(checkEntries, sourceRoot);
470
- const rawProposals = groups
471
- .filter((group) => group.entries.length >= minEvidenceCount)
472
- .map(proposalFromGroup)
473
- .sort((a, b) => (
474
- b.sourceIssueCount - a.sourceIssueCount
475
- || a.candidateSkillPath.localeCompare(b.candidateSkillPath)
476
- || a.category.localeCompare(b.category)
477
- ));
478
- const skipped = groups
479
- .filter((group) => group.entries.length < minEvidenceCount)
480
- .map((group) => skippedFromGroup(group, minEvidenceCount))
481
- .sort((a, b) => a.candidateSkillPath.localeCompare(b.candidateSkillPath) || a.category.localeCompare(b.category));
482
- const registry = signalRegistryProvider({
483
- filePath: resolvedFile,
484
- usageFile: resolvedUsageFile,
485
- signalSource,
486
- root,
487
- now,
488
- });
489
- const signalStatus = registry.status || "unknown";
490
- const reviewState = loadSkillProposalReviewState(reviewFile);
491
- const reviewed = applySkillProposalReviewState(rawProposals, reviewState);
492
- const proposals = reviewed.proposals;
493
- const status = reviewFile
494
- ? skillProposalStatus({
495
- signalStatus,
496
- reviewStatus: reviewed.review.status,
497
- pendingReviewCount: reviewed.review.pendingCount,
498
- reviewWarnings: reviewed.review.warnings.length,
499
- })
500
- : proposalStatus({
501
- signalStatus,
502
- proposalCount: proposals.length,
503
- });
504
-
505
- return {
506
- version: 1,
507
- generatedAt,
508
- file: resolvedFile,
509
- usageFile: resolvedUsageFile,
510
- signalSource: registry.signalSource || (signalSource ? path.resolve(signalSource) : path.resolve(root)),
511
- dryRun: true,
512
- applied: false,
513
- minEvidenceCount,
514
- checkCaptureCount: checkEntries.length,
515
- candidateCount: groups.length,
516
- count: proposals.length,
517
- proposalCount: proposals.length,
518
- skippedCount: skipped.length,
519
- pendingReviewCount: reviewed.review.pendingCount,
520
- reviewedCount: reviewed.review.matchedCount,
521
- reviewFile: reviewed.review.file,
522
- review: reviewed.review,
523
- status,
524
- signalStatus,
525
- proposals,
526
- skipped,
527
- recommendations: proposals.length === 0
528
- ? [{
529
- level: "info",
530
- text: "No repeated check-capture groups crossed the proposal threshold yet. Keep capturing explicit check --learn --yes warnings/failures before editing skills.",
531
- }]
532
- : reviewed.review.pendingCount === 0 && reviewFile
533
- ? [{
534
- level: "info",
535
- text: "All current skill proposals are marked applied or rejected in the review file. Re-run strict checks after any manual skill edits.",
536
- }]
537
- : [{
538
- level: "info",
539
- text: "Review proposed instruction deltas manually. Mark applied or rejected decisions in the review file to clear the strict proposal gate.",
540
- }],
541
- privacy: {
542
- mutatesProfile: false,
543
- mutatesSkillFiles: false,
544
- callsExternalAiApis: false,
545
- storesRawBriefText: false,
546
- exposesEntryTextPreview: true,
547
- },
548
- };
549
- }
550
-
551
- function reviewCheck({ id, level = "pass", passed = true, message, evidence = {} }) {
552
- return {
553
- id,
554
- level,
555
- passed,
556
- message,
557
- evidence,
558
- };
559
- }
560
-
561
- function summarizeReviewChecks(checks) {
562
- const failures = checks.filter((check) => check.level === "fail").length;
563
- const warnings = checks.filter((check) => check.level === "warn").length;
564
- return {
565
- status: failures > 0 ? "fail" : warnings > 0 ? "warn" : "pass",
566
- failures,
567
- warnings,
568
- passes: checks.filter((check) => check.level === "pass").length,
569
- total: checks.length,
570
- };
571
- }
572
-
573
- export function buildSkillProposalReviewCheck(payload, {
574
- generatedAt = new Date(),
575
- } = {}) {
576
- const generatedAtText = generatedAt instanceof Date ? generatedAt.toISOString() : String(generatedAt || "");
577
- const review = payload.review || {};
578
- const reviewFile = payload.reviewFile || review.file || "";
579
- const warnings = Array.isArray(review.warnings) ? review.warnings : [];
580
- const checks = [];
581
-
582
- checks.push(reviewCheck({
583
- id: "review-file-configured",
584
- level: reviewFile ? "pass" : "fail",
585
- passed: Boolean(reviewFile),
586
- message: reviewFile ? "A skill proposal review file is configured." : "No review file was provided.",
587
- evidence: { reviewFile },
588
- }));
589
- checks.push(reviewCheck({
590
- id: "review-file-exists",
591
- level: review.exists ? "pass" : "fail",
592
- passed: Boolean(review.exists),
593
- message: review.exists ? "The review file exists." : "The review file does not exist.",
594
- evidence: { reviewFile, exists: Boolean(review.exists) },
595
- }));
596
- checks.push(reviewCheck({
597
- id: "review-file-valid",
598
- level: review.status === "fail" ? "fail" : warnings.length > 0 || review.status === "warn" ? "warn" : "pass",
599
- passed: review.status === "pass",
600
- message: review.status === "pass"
601
- ? "The review file is valid and has a decisions array."
602
- : `Review file status is ${review.status || "unknown"}.`,
603
- evidence: { reviewStatus: review.status || "unknown", warnings },
604
- }));
605
- checks.push(reviewCheck({
606
- id: "current-proposals-cleared",
607
- level: (payload.pendingReviewCount || 0) > 0 ? "warn" : "pass",
608
- passed: (payload.pendingReviewCount || 0) === 0,
609
- message: (payload.pendingReviewCount || 0) === 0
610
- ? "All current proposals are applied or rejected."
611
- : "Some current proposals are still pending, accepted, or deferred.",
612
- evidence: {
613
- proposalCount: payload.proposalCount || 0,
614
- pendingReviewCount: payload.pendingReviewCount || 0,
615
- clearedCount: review.clearedCount || 0,
616
- },
617
- }));
618
- checks.push(reviewCheck({
619
- id: "no-stale-review-decisions",
620
- level: (review.staleCount || 0) > 0 ? "warn" : "pass",
621
- passed: (review.staleCount || 0) === 0,
622
- message: (review.staleCount || 0) === 0
623
- ? "No stale review decisions were found."
624
- : "Review file contains decisions for proposals that are no longer current.",
625
- evidence: {
626
- staleCount: review.staleCount || 0,
627
- decisionCount: review.decisionCount || 0,
628
- matchedCount: review.matchedCount || 0,
629
- },
630
- }));
631
-
632
- const summary = summarizeReviewChecks(checks);
633
- return {
634
- version: 1,
635
- kind: "skill-proposal-review-check",
636
- generatedAt: generatedAtText,
637
- file: payload.file,
638
- usageFile: payload.usageFile,
639
- signalSource: payload.signalSource,
640
- reviewFile,
641
- status: summary.status,
642
- proposalStatus: payload.status,
643
- signalStatus: payload.signalStatus,
644
- proposalCount: payload.proposalCount || 0,
645
- pendingReviewCount: payload.pendingReviewCount || 0,
646
- reviewedCount: payload.reviewedCount || 0,
647
- review,
648
- summary,
649
- checks,
650
- recommendations: summary.status === "pass"
651
- ? [{
652
- level: "info",
653
- text: "Review decisions clear the current skill proposal gate. Re-run proposal verification after any manual skill edit.",
654
- }]
655
- : [{
656
- level: "warning",
657
- text: "Refresh the review file from `--review-template`, then mark current proposals as applied or rejected after manual review.",
658
- }],
659
- privacy: {
660
- mutatesProfile: false,
661
- mutatesSkillFiles: false,
662
- callsExternalAiApis: false,
663
- storesRawBriefText: false,
664
- exposesEntryTextPreview: false,
665
- },
666
- };
667
- }
668
-
669
- function shellQuote(value) {
670
- const text = String(value ?? "");
671
- if (/^[A-Za-z0-9_./:=@%+-]+$/.test(text)) return text;
672
- return `'${text.replace(/'/g, "'\\''")}'`;
673
- }
674
-
675
- function commandFromArgs(args) {
676
- return args.map(shellQuote).join(" ");
677
- }
678
-
679
- function proposalContextArgs(payload = {}) {
680
- const args = [];
681
- if (payload.file) args.push("--file", payload.file);
682
- if (payload.usageFile) args.push("--usage-file", payload.usageFile);
683
- if (payload.signalSource) args.push("--from-file", payload.signalSource);
684
- return args;
685
- }
686
-
687
- function commandForReviewFile(payload, extraArgs = []) {
688
- const reviewFile = payload.reviewFile || payload.review?.file || "skill-proposals.review.json";
689
- const args = [
690
- "design-ai",
691
- "learn",
692
- "--propose-skills",
693
- ...proposalContextArgs(payload),
694
- "--review-file",
695
- reviewFile,
696
- ...extraArgs,
697
- ];
698
- return {
699
- command: commandFromArgs(args),
700
- commandArgs: args.map((item) => String(item)),
701
- };
702
- }
703
-
704
- function applyPlanFollowUpCommands(payload, reviewFile) {
705
- const context = { ...payload, reviewFile };
706
- return Object.fromEntries(Object.entries(APPLY_PLAN_FOLLOW_UP_COMMAND_SPECS).map(([key, extraArgs]) => [
707
- key,
708
- commandForReviewFile(context, extraArgs),
709
- ]));
710
- }
711
-
712
- function argsEndWith(args, suffix) {
713
- if (!Array.isArray(args) || args.length < suffix.length) return false;
714
- return suffix.every((item, index) => args[args.length - suffix.length + index] === item);
715
- }
716
-
717
- function argsStartWith(args, prefix) {
718
- if (!Array.isArray(args) || args.length < prefix.length) return false;
719
- return prefix.every((item, index) => args[index] === item);
720
- }
721
-
722
- function commandArgCheck({ id, passed, message, evidence = {} }) {
723
- return {
724
- id,
725
- level: passed ? "pass" : "fail",
726
- passed,
727
- message,
728
- evidence,
729
- };
730
- }
731
-
732
- function buildApplyPlanCommandContract(followUpCommands, reviewFile) {
733
- const requiredKeys = Object.keys(APPLY_PLAN_FOLLOW_UP_COMMAND_SPECS);
734
- const commandArgs = Object.fromEntries(Object.entries(followUpCommands).map(([key, value]) => [
735
- key,
736
- Array.isArray(value?.commandArgs) ? value.commandArgs : [],
737
- ]));
738
- const missingCommandKeys = requiredKeys.filter((key) => !Array.isArray(commandArgs[key]) || commandArgs[key].length === 0);
739
- const unexpectedCommandKeys = Object.keys(commandArgs).filter((key) => !requiredKeys.includes(key));
740
- const checks = [
741
- commandArgCheck({
742
- id: "required-command-keys-present",
743
- passed: missingCommandKeys.length === 0,
744
- message: missingCommandKeys.length === 0
745
- ? "All required apply-plan follow-up commands are present."
746
- : "Some required apply-plan follow-up commands are missing.",
747
- evidence: { requiredKeys, missingCommandKeys },
748
- }),
749
- commandArgCheck({
750
- id: "no-unexpected-command-keys",
751
- passed: unexpectedCommandKeys.length === 0,
752
- message: unexpectedCommandKeys.length === 0
753
- ? "No unexpected apply-plan follow-up commands are present."
754
- : "Unexpected apply-plan follow-up command keys were found.",
755
- evidence: { unexpectedCommandKeys },
756
- }),
757
- ];
758
-
759
- for (const key of requiredKeys) {
760
- const args = commandArgs[key] || [];
761
- const reviewFileIndex = args.indexOf("--review-file");
762
- checks.push(commandArgCheck({
763
- id: `${key}-base-command`,
764
- passed: argsStartWith(args, APPLY_PLAN_BASE_COMMAND),
765
- message: `${key} starts with design-ai learn --propose-skills.`,
766
- evidence: { commandArgs: args.slice(0, APPLY_PLAN_BASE_COMMAND.length) },
767
- }));
768
- checks.push(commandArgCheck({
769
- id: `${key}-review-file-context`,
770
- passed: Boolean(reviewFile) && reviewFileIndex >= 0 && args[reviewFileIndex + 1] === reviewFile,
771
- message: `${key} preserves the configured review file.`,
772
- evidence: { reviewFile, commandReviewFile: reviewFileIndex >= 0 ? args[reviewFileIndex + 1] || "" : "" },
773
- }));
774
- checks.push(commandArgCheck({
775
- id: `${key}-expected-suffix`,
776
- passed: argsEndWith(args, APPLY_PLAN_FOLLOW_UP_COMMAND_SPECS[key]),
777
- message: `${key} ends with the expected action flags.`,
778
- evidence: { expectedSuffix: APPLY_PLAN_FOLLOW_UP_COMMAND_SPECS[key], actualSuffix: args.slice(-APPLY_PLAN_FOLLOW_UP_COMMAND_SPECS[key].length) },
779
- }));
780
- checks.push(commandArgCheck({
781
- id: `${key}-read-only-flags`,
782
- passed: APPLY_PLAN_FORBIDDEN_FLAGS.every((flag) => !args.includes(flag)),
783
- message: `${key} does not include write/apply confirmation flags.`,
784
- evidence: { forbiddenFlags: APPLY_PLAN_FORBIDDEN_FLAGS },
785
- }));
786
- }
787
-
788
- const failures = checks.filter((check) => check.level === "fail").length;
789
- const warnings = checks.filter((check) => check.level === "warn").length;
790
- const passes = checks.filter((check) => check.level === "pass").length;
791
- const checkCount = checks.length;
792
- const failedChecks = checks
793
- .filter((check) => check.level === "fail")
794
- .map((check) => ({
795
- id: check.id,
796
- message: check.message,
797
- evidence: check.evidence || {},
798
- }));
799
- const nextCommandKey = failures > 0 ? "" : "reviewCheckJson";
800
- const nextCommand = nextCommandKey ? followUpCommands[nextCommandKey]?.command || "" : "";
801
- const nextCommandArgs = nextCommandKey ? commandArgs[nextCommandKey] || [] : [];
802
- const nextCommandRunPolicy = nextCommandKey ? "preview-only" : "";
803
- const nextCommandSafety = nextCommandKey
804
- ? {
805
- level: "read-only",
806
- writesLocalFiles: false,
807
- mutatesLocalState: false,
808
- mutatesProfile: false,
809
- mutatesReviewFile: false,
810
- mutatesSkillFiles: false,
811
- callsExternalAiApis: false,
812
- requiresCleanWorkspace: false,
813
- reason: "The next apply-plan follow-up command only checks proposal review readiness and does not mutate local state.",
814
- }
815
- : {};
816
- const commandSequence = failures > 0
817
- ? []
818
- : requiredKeys.map((key, index) => {
819
- const writesOutputArtifact = key === "reviewCheckReport" || key === "proposalPatchPreview";
820
- return {
821
- step: index + 1,
822
- key,
823
- command: followUpCommands[key]?.command || "",
824
- commandArgs: commandArgs[key] || [],
825
- runPolicy: APPLY_PLAN_FOLLOW_UP_COMMAND_POLICIES[key] || "preview-only",
826
- safety: {
827
- level: writesOutputArtifact ? "local-output" : "read-only",
828
- writesLocalFiles: writesOutputArtifact,
829
- writesOutputArtifact,
830
- mutatesLocalState: writesOutputArtifact,
831
- mutatesProfile: false,
832
- mutatesReviewFile: false,
833
- mutatesSkillFiles: false,
834
- callsExternalAiApis: false,
835
- requiresCleanWorkspace: false,
836
- reason: writesOutputArtifact
837
- ? "This follow-up command writes a local preview artifact with --out but does not mutate learning, review, or skill files."
838
- : "This follow-up command validates readiness without writing local files or mutating local state.",
839
- },
840
- };
841
- });
842
- const commandSequenceSummary = {
843
- executable: failures === 0,
844
- blocked: failures > 0,
845
- stepCount: commandSequence.length,
846
- readOnlyStepCount: commandSequence.filter((item) => item.safety?.level === "read-only").length,
847
- localOutputStepCount: commandSequence.filter((item) => item.safety?.level === "local-output").length,
848
- writesLocalFiles: commandSequence.some((item) => Boolean(item.safety?.writesLocalFiles)),
849
- writesOutputArtifacts: commandSequence.some((item) => Boolean(item.safety?.writesOutputArtifact)),
850
- mutatesProfile: commandSequence.some((item) => Boolean(item.safety?.mutatesProfile)),
851
- mutatesReviewFile: commandSequence.some((item) => Boolean(item.safety?.mutatesReviewFile)),
852
- mutatesSkillFiles: commandSequence.some((item) => Boolean(item.safety?.mutatesSkillFiles)),
853
- callsExternalAiApis: commandSequence.some((item) => Boolean(item.safety?.callsExternalAiApis)),
854
- requiresCleanWorkspace: commandSequence.some((item) => Boolean(item.safety?.requiresCleanWorkspace)),
855
- runPolicy: failures > 0 ? "blocked" : "mixed-preview-local-output",
856
- reason: failures > 0
857
- ? "Command contract failures must be fixed before running follow-up commands."
858
- : "The sequence combines read-only readiness checks with local output artifact previews; it does not mutate learning, review, or skill files.",
859
- };
860
- const commandSequenceKeys = commandSequence.map((item) => item.key);
861
- const commandSequenceByKey = Object.fromEntries(commandSequence.map((item) => [item.key, item]));
862
- const operatorRunbookStages = failures > 0
863
- ? []
864
- : [
865
- {
866
- step: 1,
867
- key: "previewArtifacts",
868
- label: "Generate optional review artifacts",
869
- kind: "local-output-preview",
870
- required: false,
871
- commandKeys: ["reviewCheckReport", "proposalPatchPreview"],
872
- commands: ["reviewCheckReport", "proposalPatchPreview"].map((key) => commandSequenceByKey[key]),
873
- reason: "Optional Markdown review and patch preview artifacts can be generated before manual skill edits.",
874
- },
875
- {
876
- step: 2,
877
- key: "manualSkillEdit",
878
- label: "Apply accepted skill deltas manually",
879
- kind: "manual-review",
880
- required: true,
881
- commandKeys: [],
882
- commands: [],
883
- reason: "No apply-plan command mutates skill files; the operator must manually edit accepted skill deltas after review.",
884
- },
885
- {
886
- step: 3,
887
- key: "reviewReadiness",
888
- label: "Run review readiness check",
889
- kind: "read-only-check",
890
- required: true,
891
- commandKeys: ["reviewCheckJson"],
892
- commands: [commandSequenceByKey.reviewCheckJson],
893
- reason: "Run the read-only review check after manual skill edits to verify proposal review state.",
894
- },
895
- {
896
- step: 4,
897
- key: "strictGate",
898
- label: "Run strict readiness gate",
899
- kind: "read-only-gate",
900
- required: true,
901
- commandKeys: ["strictGate"],
902
- commands: [commandSequenceByKey.strictGate],
903
- reason: "Run the strict gate before marking accepted proposals applied.",
904
- },
905
- ];
906
- const operatorRunbookStageKeys = operatorRunbookStages.map((stage) => stage.key);
907
- const operatorRunbookStageByKey = Object.fromEntries(operatorRunbookStages.map((stage) => [stage.key, stage]));
908
- const summarizeOperatorRunbookStage = (stage) => {
909
- if (!stage) return {};
910
- const commandSafetyItems = stage.commands
911
- .map((command) => command?.safety)
912
- .filter((safety) => safety && typeof safety === "object");
913
- const writesLocalFiles = commandSafetyItems.some((safety) => Boolean(safety.writesLocalFiles));
914
- const writesOutputArtifacts = commandSafetyItems.some((safety) => Boolean(safety.writesOutputArtifact));
915
- const mutatesLocalState = commandSafetyItems.some((safety) => Boolean(safety.mutatesLocalState));
916
- const mutatesProfile = commandSafetyItems.some((safety) => Boolean(safety.mutatesProfile));
917
- const mutatesReviewFile = commandSafetyItems.some((safety) => Boolean(safety.mutatesReviewFile));
918
- const mutatesSkillFiles = commandSafetyItems.some((safety) => Boolean(safety.mutatesSkillFiles));
919
- const callsExternalAiApis = commandSafetyItems.some((safety) => Boolean(safety.callsExternalAiApis));
920
- const requiresCleanWorkspace = commandSafetyItems.some((safety) => Boolean(safety.requiresCleanWorkspace));
921
- return {
922
- key: stage.key,
923
- step: stage.step,
924
- label: stage.label,
925
- kind: stage.kind,
926
- required: stage.required,
927
- hasCommands: stage.commandKeys.length > 0,
928
- commandCount: stage.commandKeys.length,
929
- commandKeys: stage.commandKeys,
930
- writesLocalFiles,
931
- writesOutputArtifacts,
932
- mutatesLocalState,
933
- mutatesProfile,
934
- mutatesReviewFile,
935
- mutatesSkillFiles,
936
- callsExternalAiApis,
937
- requiresCleanWorkspace,
938
- reason: stage.reason,
939
- };
940
- };
941
- const nextStage = failures > 0
942
- ? null
943
- : operatorRunbookStageByKey.previewArtifacts || null;
944
- const nextRequiredStage = failures > 0
945
- ? null
946
- : operatorRunbookStages.find((stage) => stage.required) || null;
947
- const nextRequiredCommandStage = failures > 0
948
- ? null
949
- : operatorRunbookStages.find((stage) => stage.required && stage.commandKeys.length > 0) || null;
950
- const nextStageSummary = summarizeOperatorRunbookStage(nextStage);
951
- const nextRequiredStageSummary = summarizeOperatorRunbookStage(nextRequiredStage);
952
- const nextRequiredCommandStageSummary = summarizeOperatorRunbookStage(nextRequiredCommandStage);
953
- const summarizeDecisionCommand = (command) => command
954
- ? {
955
- step: command.step,
956
- key: command.key,
957
- command: command.command,
958
- commandArgs: command.commandArgs,
959
- runPolicy: command.runPolicy,
960
- safetyLevel: command.safety?.level || "",
961
- safety: {
962
- level: command.safety?.level || "",
963
- writesLocalFiles: Boolean(command.safety?.writesLocalFiles),
964
- writesOutputArtifact: Boolean(command.safety?.writesOutputArtifact),
965
- mutatesLocalState: Boolean(command.safety?.mutatesLocalState),
966
- mutatesProfile: Boolean(command.safety?.mutatesProfile),
967
- mutatesReviewFile: Boolean(command.safety?.mutatesReviewFile),
968
- mutatesSkillFiles: Boolean(command.safety?.mutatesSkillFiles),
969
- callsExternalAiApis: Boolean(command.safety?.callsExternalAiApis),
970
- requiresCleanWorkspace: Boolean(command.safety?.requiresCleanWorkspace),
971
- reason: command.safety?.reason || "",
972
- },
973
- writesLocalFiles: Boolean(command.safety?.writesLocalFiles),
974
- writesOutputArtifact: Boolean(command.safety?.writesOutputArtifact),
975
- mutatesLocalState: Boolean(command.safety?.mutatesLocalState),
976
- mutatesProfile: Boolean(command.safety?.mutatesProfile),
977
- mutatesReviewFile: Boolean(command.safety?.mutatesReviewFile),
978
- mutatesSkillFiles: Boolean(command.safety?.mutatesSkillFiles),
979
- callsExternalAiApis: Boolean(command.safety?.callsExternalAiApis),
980
- requiresCleanWorkspace: Boolean(command.safety?.requiresCleanWorkspace),
981
- }
982
- : {};
983
- const decisionCommands = failures > 0
984
- ? []
985
- : (nextStage?.commands || []).map((command) => summarizeDecisionCommand(command));
986
- const decisionCommandByKey = Object.fromEntries(decisionCommands.map((command) => [command.key, command]));
987
- const decisionCommandStepByKey = Object.fromEntries(decisionCommands.map((command) => [command.key, command.step]));
988
- const decisionCommandRunPolicyByKey = Object.fromEntries(
989
- decisionCommands.map((command) => [command.key, command.runPolicy]),
990
- );
991
- const decisionCommandSafetyLevelByKey = Object.fromEntries(
992
- decisionCommands.map((command) => [command.key, command.safetyLevel]),
993
- );
994
- const decisionCommandArgsByKey = Object.fromEntries(
995
- decisionCommands.map((command) => [command.key, command.commandArgs]),
996
- );
997
- const decisionCommandStringByKey = Object.fromEntries(
998
- decisionCommands.map((command) => [command.key, command.command]),
999
- );
1000
- const decisionCommandDisplayLabelByKey = Object.fromEntries(
1001
- decisionCommands.map((command) => [
1002
- command.key,
1003
- APPLY_PLAN_FOLLOW_UP_COMMAND_DISPLAY_LABELS[command.key] || command.key,
1004
- ]),
1005
- );
1006
- const decisionCommandDescriptionByKey = Object.fromEntries(
1007
- decisionCommands.map((command) => [
1008
- command.key,
1009
- APPLY_PLAN_FOLLOW_UP_COMMAND_DESCRIPTIONS[command.key] || "",
1010
- ]),
1011
- );
1012
- const decisionCommandOutputArtifactByKey = Object.fromEntries(
1013
- decisionCommands.map((command) => [
1014
- command.key,
1015
- APPLY_PLAN_FOLLOW_UP_COMMAND_OUTPUT_ARTIFACTS[command.key] || "",
1016
- ]),
1017
- );
1018
- const decisionCommandOutputArtifactTypeByKey = Object.fromEntries(
1019
- decisionCommands.map((command) => [
1020
- command.key,
1021
- APPLY_PLAN_FOLLOW_UP_COMMAND_OUTPUT_ARTIFACT_TYPES[command.key] || "",
1022
- ]),
1023
- );
1024
- const decisionCommandOutputArtifactActionByKey = Object.fromEntries(
1025
- decisionCommands.map((command) => [
1026
- command.key,
1027
- APPLY_PLAN_FOLLOW_UP_COMMAND_OUTPUT_ARTIFACT_ACTIONS[command.key] || "",
1028
- ]),
1029
- );
1030
- const decisionCommandOutputArtifactMediaTypeByKey = Object.fromEntries(
1031
- decisionCommands.map((command) => [
1032
- command.key,
1033
- APPLY_PLAN_FOLLOW_UP_COMMAND_OUTPUT_ARTIFACT_MEDIA_TYPES[command.key] || "",
1034
- ]),
1035
- );
1036
- const decisionCommandOutputArtifactDispositionByKey = Object.fromEntries(
1037
- decisionCommands.map((command) => [
1038
- command.key,
1039
- APPLY_PLAN_FOLLOW_UP_COMMAND_OUTPUT_ARTIFACT_DISPOSITIONS[command.key] || "",
1040
- ]),
1041
- );
1042
- const decisionCommandOutputArtifactManualApplyCandidateByKey = Object.fromEntries(
1043
- decisionCommands.map((command) => [
1044
- command.key,
1045
- APPLY_PLAN_FOLLOW_UP_COMMAND_OUTPUT_ARTIFACT_MANUAL_APPLY_CANDIDATES[command.key] || false,
1046
- ]),
1047
- );
1048
- const decisionCommandOutputArtifactRequiresManualReviewByKey = Object.fromEntries(
1049
- decisionCommands.map((command) => [
1050
- command.key,
1051
- APPLY_PLAN_FOLLOW_UP_COMMAND_OUTPUT_ARTIFACT_REQUIRES_MANUAL_REVIEW[command.key] || false,
1052
- ]),
1053
- );
1054
- const decisionCommandOutputArtifactReviewInstructionByKey = Object.fromEntries(
1055
- decisionCommands.map((command) => [
1056
- command.key,
1057
- APPLY_PLAN_FOLLOW_UP_COMMAND_OUTPUT_ARTIFACT_REVIEW_INSTRUCTIONS[command.key] || "",
1058
- ]),
1059
- );
1060
- const decisionCommandOutputArtifactRequiresCleanWorkspaceBeforeApplyByKey = Object.fromEntries(
1061
- decisionCommands.map((command) => [
1062
- command.key,
1063
- APPLY_PLAN_FOLLOW_UP_COMMAND_OUTPUT_ARTIFACT_REQUIRES_CLEAN_WORKSPACE_BEFORE_APPLY[command.key] || false,
1064
- ]),
1065
- );
1066
- const decisionCommandOutputArtifactApplyPreconditionIdsByKey = Object.fromEntries(
1067
- decisionCommands.map((command) => [
1068
- command.key,
1069
- [...(APPLY_PLAN_FOLLOW_UP_COMMAND_OUTPUT_ARTIFACT_APPLY_PRECONDITION_IDS[command.key] || [])],
1070
- ]),
1071
- );
1072
- const decisionCommandOutputArtifactApplyPreconditionLabelsByKey = Object.fromEntries(
1073
- decisionCommands.map((command) => [
1074
- command.key,
1075
- [...(APPLY_PLAN_FOLLOW_UP_COMMAND_OUTPUT_ARTIFACT_APPLY_PRECONDITION_LABELS[command.key] || [])],
1076
- ]),
1077
- );
1078
- const decisionCommandOutputArtifactApplyPreconditionsByKey = Object.fromEntries(
1079
- decisionCommands.map((command) => [
1080
- command.key,
1081
- applyPreconditionsForCommandKey(command.key),
1082
- ]),
1083
- );
1084
- const decisionCommandOutputArtifactApplyPreconditionCountByKey = Object.fromEntries(
1085
- decisionCommands.map((command) => [
1086
- command.key,
1087
- decisionCommandOutputArtifactApplyPreconditionsByKey[command.key]?.length || 0,
1088
- ]),
1089
- );
1090
- const decisionCommandOutputArtifactRequiredApplyPreconditionCountByKey = Object.fromEntries(
1091
- decisionCommands.map((command) => [
1092
- command.key,
1093
- (decisionCommandOutputArtifactApplyPreconditionsByKey[command.key] || [])
1094
- .filter((precondition) => precondition.required).length,
1095
- ]),
1096
- );
1097
- const decisionCommandOutputArtifactSatisfiedApplyPreconditionCountByKey = Object.fromEntries(
1098
- decisionCommands.map((command) => [
1099
- command.key,
1100
- (decisionCommandOutputArtifactApplyPreconditionsByKey[command.key] || [])
1101
- .filter((precondition) => applyPreconditionIsSatisfied(precondition)).length,
1102
- ]),
1103
- );
1104
- const decisionCommandOutputArtifactPendingApplyPreconditionCountByKey = Object.fromEntries(
1105
- decisionCommands.map((command) => [
1106
- command.key,
1107
- (decisionCommandOutputArtifactApplyPreconditionsByKey[command.key] || [])
1108
- .filter((precondition) => !applyPreconditionIsSatisfied(precondition)).length,
1109
- ]),
1110
- );
1111
- const decisionCommandOutputArtifactRequiredPendingApplyPreconditionCountByKey = Object.fromEntries(
1112
- decisionCommands.map((command) => [
1113
- command.key,
1114
- (decisionCommandOutputArtifactApplyPreconditionsByKey[command.key] || [])
1115
- .filter((precondition) => precondition.required && !applyPreconditionIsSatisfied(precondition)).length,
1116
- ]),
1117
- );
1118
- const decisionCommandOutputArtifactManualApplyReadyByKey = Object.fromEntries(
1119
- decisionCommands.map((command) => [
1120
- command.key,
1121
- Boolean(decisionCommandOutputArtifactManualApplyCandidateByKey[command.key])
1122
- && (decisionCommandOutputArtifactRequiredPendingApplyPreconditionCountByKey[command.key] || 0) === 0,
1123
- ]),
1124
- );
1125
- const decisionCommandOutputArtifactManualApplyStatusByKey = Object.fromEntries(
1126
- decisionCommands.map((command) => [
1127
- command.key,
1128
- manualApplyStatus({
1129
- manualApplyCandidate: Boolean(decisionCommandOutputArtifactManualApplyCandidateByKey[command.key]),
1130
- manualApplyReady: Boolean(decisionCommandOutputArtifactManualApplyReadyByKey[command.key]),
1131
- }),
1132
- ]),
1133
- );
1134
- const decisionCommandOutputArtifactManualApplyStatusLabelByKey = Object.fromEntries(
1135
- decisionCommands.map((command) => [
1136
- command.key,
1137
- manualApplyStatusLabel(decisionCommandOutputArtifactManualApplyStatusByKey[command.key]),
1138
- ]),
1139
- );
1140
- const decisionCommandOutputArtifactManualApplyStatusToneByKey = Object.fromEntries(
1141
- decisionCommands.map((command) => [
1142
- command.key,
1143
- manualApplyStatusTone(decisionCommandOutputArtifactManualApplyStatusByKey[command.key]),
1144
- ]),
1145
- );
1146
- const decisionCommandOutputArtifactManualApplyBlockedReasonByKey = Object.fromEntries(
1147
- decisionCommands.map((command) => [
1148
- command.key,
1149
- manualApplyBlockedReason({
1150
- manualApplyCandidate: Boolean(decisionCommandOutputArtifactManualApplyCandidateByKey[command.key]),
1151
- requiredPendingApplyPreconditionCount: decisionCommandOutputArtifactRequiredPendingApplyPreconditionCountByKey[command.key] || 0,
1152
- }).message,
1153
- ]),
1154
- );
1155
- const decisionCommandOutputArtifactManualApplyBlockedReasonCodeByKey = Object.fromEntries(
1156
- decisionCommands.map((command) => [
1157
- command.key,
1158
- manualApplyBlockedReason({
1159
- manualApplyCandidate: Boolean(decisionCommandOutputArtifactManualApplyCandidateByKey[command.key]),
1160
- requiredPendingApplyPreconditionCount: decisionCommandOutputArtifactRequiredPendingApplyPreconditionCountByKey[command.key] || 0,
1161
- }).code,
1162
- ]),
1163
- );
1164
- const decisionNextCommand = decisionCommands[0] || {};
1165
- const decisionNextCommandDisplayLabel = decisionNextCommand.key
1166
- ? decisionCommandDisplayLabelByKey[decisionNextCommand.key] || decisionNextCommand.key
1167
- : "";
1168
- const decisionNextCommandDescription = decisionNextCommand.key
1169
- ? decisionCommandDescriptionByKey[decisionNextCommand.key] || ""
1170
- : "";
1171
- const decisionNextCommandOutputArtifact = decisionNextCommand.key
1172
- ? decisionCommandOutputArtifactByKey[decisionNextCommand.key] || ""
1173
- : "";
1174
- const decisionNextCommandOutputArtifactType = decisionNextCommand.key
1175
- ? decisionCommandOutputArtifactTypeByKey[decisionNextCommand.key] || ""
1176
- : "";
1177
- const decisionNextCommandOutputArtifactAction = decisionNextCommand.key
1178
- ? decisionCommandOutputArtifactActionByKey[decisionNextCommand.key] || ""
1179
- : "";
1180
- const decisionNextCommandOutputArtifactMediaType = decisionNextCommand.key
1181
- ? decisionCommandOutputArtifactMediaTypeByKey[decisionNextCommand.key] || ""
1182
- : "";
1183
- const decisionNextCommandOutputArtifactDisposition = decisionNextCommand.key
1184
- ? decisionCommandOutputArtifactDispositionByKey[decisionNextCommand.key] || ""
1185
- : "";
1186
- const decisionNextCommandOutputArtifactManualApplyCandidate = decisionNextCommand.key
1187
- ? decisionCommandOutputArtifactManualApplyCandidateByKey[decisionNextCommand.key] || false
1188
- : false;
1189
- const decisionNextCommandOutputArtifactRequiresManualReview = decisionNextCommand.key
1190
- ? decisionCommandOutputArtifactRequiresManualReviewByKey[decisionNextCommand.key] || false
1191
- : false;
1192
- const decisionNextCommandOutputArtifactReviewInstruction = decisionNextCommand.key
1193
- ? decisionCommandOutputArtifactReviewInstructionByKey[decisionNextCommand.key] || ""
1194
- : "";
1195
- const decisionNextCommandOutputArtifactRequiresCleanWorkspaceBeforeApply = decisionNextCommand.key
1196
- ? decisionCommandOutputArtifactRequiresCleanWorkspaceBeforeApplyByKey[decisionNextCommand.key] || false
1197
- : false;
1198
- const decisionNextCommandOutputArtifactApplyPreconditionIds = decisionNextCommand.key
1199
- ? decisionCommandOutputArtifactApplyPreconditionIdsByKey[decisionNextCommand.key] || []
1200
- : [];
1201
- const decisionNextCommandOutputArtifactApplyPreconditionLabels = decisionNextCommand.key
1202
- ? decisionCommandOutputArtifactApplyPreconditionLabelsByKey[decisionNextCommand.key] || []
1203
- : [];
1204
- const decisionNextCommandOutputArtifactApplyPreconditions = decisionNextCommand.key
1205
- ? decisionCommandOutputArtifactApplyPreconditionsByKey[decisionNextCommand.key] || []
1206
- : [];
1207
- const decisionNextCommandOutputArtifactApplyPreconditionCount = decisionNextCommand.key
1208
- ? decisionCommandOutputArtifactApplyPreconditionCountByKey[decisionNextCommand.key] || 0
1209
- : 0;
1210
- const decisionNextCommandOutputArtifactRequiredApplyPreconditionCount = decisionNextCommand.key
1211
- ? decisionCommandOutputArtifactRequiredApplyPreconditionCountByKey[decisionNextCommand.key] || 0
1212
- : 0;
1213
- const decisionNextCommandOutputArtifactSatisfiedApplyPreconditionCount = decisionNextCommand.key
1214
- ? decisionCommandOutputArtifactSatisfiedApplyPreconditionCountByKey[decisionNextCommand.key] || 0
1215
- : 0;
1216
- const decisionNextCommandOutputArtifactPendingApplyPreconditionCount = decisionNextCommand.key
1217
- ? decisionCommandOutputArtifactPendingApplyPreconditionCountByKey[decisionNextCommand.key] || 0
1218
- : 0;
1219
- const decisionNextCommandOutputArtifactRequiredPendingApplyPreconditionCount = decisionNextCommand.key
1220
- ? decisionCommandOutputArtifactRequiredPendingApplyPreconditionCountByKey[decisionNextCommand.key] || 0
1221
- : 0;
1222
- const decisionNextCommandOutputArtifactManualApplyReady = decisionNextCommand.key
1223
- ? decisionCommandOutputArtifactManualApplyReadyByKey[decisionNextCommand.key] || false
1224
- : false;
1225
- const decisionNextCommandOutputArtifactManualApplyStatus = decisionNextCommand.key
1226
- ? decisionCommandOutputArtifactManualApplyStatusByKey[decisionNextCommand.key] || "not-applicable"
1227
- : "not-applicable";
1228
- const decisionNextCommandOutputArtifactManualApplyStatusLabel = decisionNextCommand.key
1229
- ? decisionCommandOutputArtifactManualApplyStatusLabelByKey[decisionNextCommand.key] || "Review only"
1230
- : "Review only";
1231
- const decisionNextCommandOutputArtifactManualApplyStatusTone = decisionNextCommand.key
1232
- ? decisionCommandOutputArtifactManualApplyStatusToneByKey[decisionNextCommand.key] || "neutral"
1233
- : "neutral";
1234
- const decisionNextCommandOutputArtifactManualApplyBlockedReason = decisionNextCommand.key
1235
- ? decisionCommandOutputArtifactManualApplyBlockedReasonByKey[decisionNextCommand.key] || ""
1236
- : "";
1237
- const decisionNextCommandOutputArtifactManualApplyBlockedReasonCode = decisionNextCommand.key
1238
- ? decisionCommandOutputArtifactManualApplyBlockedReasonCodeByKey[decisionNextCommand.key] || ""
1239
- : "";
1240
- const operatorRunbookStageSelection = failures > 0
1241
- ? {}
1242
- : {
1243
- strategy: "optional-preview-before-required-manual-edit",
1244
- decision: {
1245
- action: "offer-optional-preview",
1246
- stageKey: "previewArtifacts",
1247
- stageKind: "local-output-preview",
1248
- required: false,
1249
- hasCommands: true,
1250
- commandCount: decisionCommands.length,
1251
- commandKeys: ["reviewCheckReport", "proposalPatchPreview"],
1252
- commands: decisionCommands,
1253
- commandByKey: decisionCommandByKey,
1254
- commandStepByKey: decisionCommandStepByKey,
1255
- commandRunPolicyByKey: decisionCommandRunPolicyByKey,
1256
- commandSafetyLevelByKey: decisionCommandSafetyLevelByKey,
1257
- commandArgsByKey: decisionCommandArgsByKey,
1258
- commandStringByKey: decisionCommandStringByKey,
1259
- commandDisplayLabelByKey: decisionCommandDisplayLabelByKey,
1260
- commandDescriptionByKey: decisionCommandDescriptionByKey,
1261
- commandOutputArtifactByKey: decisionCommandOutputArtifactByKey,
1262
- commandOutputArtifactTypeByKey: decisionCommandOutputArtifactTypeByKey,
1263
- commandOutputArtifactActionByKey: decisionCommandOutputArtifactActionByKey,
1264
- commandOutputArtifactMediaTypeByKey: decisionCommandOutputArtifactMediaTypeByKey,
1265
- commandOutputArtifactDispositionByKey: decisionCommandOutputArtifactDispositionByKey,
1266
- commandOutputArtifactManualApplyCandidateByKey: decisionCommandOutputArtifactManualApplyCandidateByKey,
1267
- commandOutputArtifactRequiresManualReviewByKey: decisionCommandOutputArtifactRequiresManualReviewByKey,
1268
- commandOutputArtifactReviewInstructionByKey: decisionCommandOutputArtifactReviewInstructionByKey,
1269
- commandOutputArtifactRequiresCleanWorkspaceBeforeApplyByKey: decisionCommandOutputArtifactRequiresCleanWorkspaceBeforeApplyByKey,
1270
- commandOutputArtifactApplyPreconditionIdsByKey: decisionCommandOutputArtifactApplyPreconditionIdsByKey,
1271
- commandOutputArtifactApplyPreconditionLabelsByKey: decisionCommandOutputArtifactApplyPreconditionLabelsByKey,
1272
- commandOutputArtifactApplyPreconditionsByKey: decisionCommandOutputArtifactApplyPreconditionsByKey,
1273
- commandOutputArtifactApplyPreconditionCountByKey: decisionCommandOutputArtifactApplyPreconditionCountByKey,
1274
- commandOutputArtifactRequiredApplyPreconditionCountByKey: decisionCommandOutputArtifactRequiredApplyPreconditionCountByKey,
1275
- commandOutputArtifactSatisfiedApplyPreconditionCountByKey: decisionCommandOutputArtifactSatisfiedApplyPreconditionCountByKey,
1276
- commandOutputArtifactPendingApplyPreconditionCountByKey: decisionCommandOutputArtifactPendingApplyPreconditionCountByKey,
1277
- commandOutputArtifactRequiredPendingApplyPreconditionCountByKey: decisionCommandOutputArtifactRequiredPendingApplyPreconditionCountByKey,
1278
- commandOutputArtifactManualApplyReadyByKey: decisionCommandOutputArtifactManualApplyReadyByKey,
1279
- commandOutputArtifactManualApplyStatusByKey: decisionCommandOutputArtifactManualApplyStatusByKey,
1280
- commandOutputArtifactManualApplyStatusLabelByKey: decisionCommandOutputArtifactManualApplyStatusLabelByKey,
1281
- commandOutputArtifactManualApplyStatusToneByKey: decisionCommandOutputArtifactManualApplyStatusToneByKey,
1282
- commandOutputArtifactManualApplyBlockedReasonByKey: decisionCommandOutputArtifactManualApplyBlockedReasonByKey,
1283
- commandOutputArtifactManualApplyBlockedReasonCodeByKey: decisionCommandOutputArtifactManualApplyBlockedReasonCodeByKey,
1284
- nextCommandEntry: decisionNextCommand,
1285
- nextCommandKey: decisionNextCommand.key || "",
1286
- nextCommandDisplayLabel: decisionNextCommandDisplayLabel,
1287
- nextCommandDescription: decisionNextCommandDescription,
1288
- nextCommandOutputArtifact: decisionNextCommandOutputArtifact,
1289
- nextCommandOutputArtifactType: decisionNextCommandOutputArtifactType,
1290
- nextCommandOutputArtifactAction: decisionNextCommandOutputArtifactAction,
1291
- nextCommandOutputArtifactMediaType: decisionNextCommandOutputArtifactMediaType,
1292
- nextCommandOutputArtifactDisposition: decisionNextCommandOutputArtifactDisposition,
1293
- nextCommandOutputArtifactManualApplyCandidate: decisionNextCommandOutputArtifactManualApplyCandidate,
1294
- nextCommandOutputArtifactRequiresManualReview: decisionNextCommandOutputArtifactRequiresManualReview,
1295
- nextCommandOutputArtifactReviewInstruction: decisionNextCommandOutputArtifactReviewInstruction,
1296
- nextCommandOutputArtifactRequiresCleanWorkspaceBeforeApply: decisionNextCommandOutputArtifactRequiresCleanWorkspaceBeforeApply,
1297
- nextCommandOutputArtifactApplyPreconditionIds: decisionNextCommandOutputArtifactApplyPreconditionIds,
1298
- nextCommandOutputArtifactApplyPreconditionLabels: decisionNextCommandOutputArtifactApplyPreconditionLabels,
1299
- nextCommandOutputArtifactApplyPreconditions: decisionNextCommandOutputArtifactApplyPreconditions,
1300
- nextCommandOutputArtifactApplyPreconditionCount: decisionNextCommandOutputArtifactApplyPreconditionCount,
1301
- nextCommandOutputArtifactRequiredApplyPreconditionCount: decisionNextCommandOutputArtifactRequiredApplyPreconditionCount,
1302
- nextCommandOutputArtifactSatisfiedApplyPreconditionCount: decisionNextCommandOutputArtifactSatisfiedApplyPreconditionCount,
1303
- nextCommandOutputArtifactPendingApplyPreconditionCount: decisionNextCommandOutputArtifactPendingApplyPreconditionCount,
1304
- nextCommandOutputArtifactRequiredPendingApplyPreconditionCount: decisionNextCommandOutputArtifactRequiredPendingApplyPreconditionCount,
1305
- nextCommandOutputArtifactManualApplyReady: decisionNextCommandOutputArtifactManualApplyReady,
1306
- nextCommandOutputArtifactManualApplyStatus: decisionNextCommandOutputArtifactManualApplyStatus,
1307
- nextCommandOutputArtifactManualApplyStatusLabel: decisionNextCommandOutputArtifactManualApplyStatusLabel,
1308
- nextCommandOutputArtifactManualApplyStatusTone: decisionNextCommandOutputArtifactManualApplyStatusTone,
1309
- nextCommandOutputArtifactManualApplyBlockedReason: decisionNextCommandOutputArtifactManualApplyBlockedReason,
1310
- nextCommandOutputArtifactManualApplyBlockedReasonCode: decisionNextCommandOutputArtifactManualApplyBlockedReasonCode,
1311
- nextCommandStep: decisionNextCommand.step || 0,
1312
- nextCommand: decisionNextCommand.command || "",
1313
- nextCommandArgs: decisionNextCommand.commandArgs || [],
1314
- nextCommandRunPolicy: decisionNextCommand.runPolicy || "",
1315
- nextCommandSafetyLevel: decisionNextCommand.safetyLevel || "",
1316
- nextCommandSafety: decisionNextCommand.safety || {},
1317
- runPolicy: "optional-local-output-preview",
1318
- safety: {
1319
- level: nextStageSummary.writesLocalFiles ? "local-output" : "read-only",
1320
- writesLocalFiles: nextStageSummary.writesLocalFiles,
1321
- writesOutputArtifacts: nextStageSummary.writesOutputArtifacts,
1322
- mutatesLocalState: nextStageSummary.mutatesLocalState,
1323
- mutatesProfile: nextStageSummary.mutatesProfile,
1324
- mutatesReviewFile: nextStageSummary.mutatesReviewFile,
1325
- mutatesSkillFiles: nextStageSummary.mutatesSkillFiles,
1326
- callsExternalAiApis: nextStageSummary.callsExternalAiApis,
1327
- requiresCleanWorkspace: nextStageSummary.requiresCleanWorkspace,
1328
- reason: "The selected decision only writes optional local preview artifacts and does not mutate learning, review, or skill files.",
1329
- },
1330
- nextRequiredStageKey: nextRequiredStage?.key || "",
1331
- nextRequiredCommandStageKey: nextRequiredCommandStage?.key || "",
1332
- requiresOperatorActionBeforeRequiredCommands: true,
1333
- reason: "Offer optional local preview artifacts first; the required path still starts with manual skill edits before read-only command gates.",
1334
- },
1335
- stageOrder: operatorRunbookStageKeys,
1336
- nextStageKey: "previewArtifacts",
1337
- nextStageCommandKeys: ["reviewCheckReport", "proposalPatchPreview"],
1338
- nextStage: nextStageSummary,
1339
- nextRequiredStageKey: nextRequiredStage?.key || "",
1340
- nextRequiredStageCommandKeys: nextRequiredStage?.commandKeys || [],
1341
- nextRequiredStage: nextRequiredStageSummary,
1342
- nextRequiredCommandStageKey: nextRequiredCommandStage?.key || "",
1343
- nextRequiredCommandStageCommandKeys: nextRequiredCommandStage?.commandKeys || [],
1344
- nextRequiredCommandStage: nextRequiredCommandStageSummary,
1345
- reason: "Offer optional local preview artifacts first, then require the manual skill edit before read-only review and strict gates.",
1346
- };
1347
- const operatorRunbook = {
1348
- version: 1,
1349
- executable: failures === 0,
1350
- blocked: failures > 0,
1351
- stageCount: operatorRunbookStages.length,
1352
- requiredStageCount: operatorRunbookStages.filter((stage) => stage.required).length,
1353
- commandStageCount: operatorRunbookStages.filter((stage) => stage.commandKeys.length > 0).length,
1354
- nextStageKey: failures > 0 ? "" : "previewArtifacts",
1355
- nextStageCommandKeys: failures > 0 ? [] : ["reviewCheckReport", "proposalPatchPreview"],
1356
- nextRequiredStageKey: nextRequiredStage?.key || "",
1357
- nextRequiredStageCommandKeys: nextRequiredStage?.commandKeys || [],
1358
- nextRequiredCommandStageKey: nextRequiredCommandStage?.key || "",
1359
- nextRequiredCommandStageCommandKeys: nextRequiredCommandStage?.commandKeys || [],
1360
- stageSelection: operatorRunbookStageSelection,
1361
- stageKeys: operatorRunbookStageKeys,
1362
- stageByKey: operatorRunbookStageByKey,
1363
- stages: operatorRunbookStages,
1364
- reason: failures > 0
1365
- ? "Command contract failures must be fixed before running the operator runbook."
1366
- : "Generate optional local review artifacts, apply accepted skill deltas manually, then run read-only review and strict readiness gates.",
1367
- };
1368
- return {
1369
- version: 1,
1370
- valid: failures === 0,
1371
- status: failures > 0 ? "fail" : "pass",
1372
- commandCount: Object.keys(commandArgs).length,
1373
- requiredKeys,
1374
- missingCommandKeys,
1375
- unexpectedCommandKeys,
1376
- baseCommand: [...APPLY_PLAN_BASE_COMMAND],
1377
- reviewFileRequired: true,
1378
- reviewFile,
1379
- forbiddenFlags: [...APPLY_PLAN_FORBIDDEN_FLAGS],
1380
- checkCount,
1381
- passCount: passes,
1382
- warningCount: warnings,
1383
- failureCount: failures,
1384
- failedCheckIds: failedChecks.map((check) => check.id),
1385
- failedChecks,
1386
- nextCommandKey,
1387
- nextCommand,
1388
- nextCommandArgs,
1389
- nextCommandRunPolicy,
1390
- nextCommandSafety,
1391
- commandSequenceCount: commandSequence.length,
1392
- commandSequence,
1393
- commandSequenceSummary,
1394
- commandSequenceKeys,
1395
- commandSequenceByKey,
1396
- operatorRunbook,
1397
- nextAction: failures > 0
1398
- ? "Fix command contract failures before running follow-up commands."
1399
- : "Run reviewCheckJson after manual skill edits, then use strictGate before marking proposals applied.",
1400
- checks,
1401
- summary: {
1402
- failures,
1403
- warnings,
1404
- passes,
1405
- total: checkCount,
1406
- },
1407
- };
1408
- }
1409
-
1410
- function acceptedProposalTask(proposal, index) {
1411
- const routes = Array.isArray(proposal.routeIds) && proposal.routeIds.length > 0
1412
- ? proposal.routeIds
1413
- : ["artifact"];
1414
- const evidenceSources = Array.isArray(proposal.evidenceSources) ? proposal.evidenceSources : [];
1415
- return {
1416
- id: `apply-${index + 1}-${proposal.id}`,
1417
- proposalId: proposal.id,
1418
- title: proposal.title,
1419
- candidateSkill: proposal.candidateSkill || path.basename(path.dirname(proposal.candidateSkillPath || "")) || "unknown",
1420
- candidateSkillPath: proposal.candidateSkillPath || "skills/unknown/SKILL.md",
1421
- category: proposal.category || "workflow",
1422
- riskLevel: proposal.riskLevel || "medium",
1423
- routeIds: routes,
1424
- sourceIssueCount: proposal.sourceIssueCount || evidenceSources.length,
1425
- proposedInstructionDelta: proposal.proposedInstructionDelta || "",
1426
- rationale: proposal.rationale || "",
1427
- verificationCommand: proposal.verificationCommand || "node cli/bin/design-ai.mjs check --examples --strict --json",
1428
- evidenceSources,
1429
- reviewDecision: proposal.reviewDecision || {
1430
- proposalId: proposal.id,
1431
- status: proposal.reviewStatus || "accepted",
1432
- reviewedAt: "",
1433
- reviewer: "",
1434
- note: "",
1435
- },
1436
- manualSteps: [
1437
- `Open ${proposal.candidateSkillPath || "the candidate skill file"} and inspect the relevant checklist or playbook section.`,
1438
- "Merge the proposed instruction delta manually instead of pasting duplicate generated text.",
1439
- "Run the verification command and inspect any route-specific failures before marking the work complete.",
1440
- "After the skill edit and verification pass, update the review decision from `accepted` to `applied`.",
1441
- ],
1442
- safetyChecklist: [
1443
- "Do not edit learning.json as part of this apply plan.",
1444
- "Do not call external AI APIs, embeddings, or fine-tuning jobs.",
1445
- "Keep the skill delta scoped to the repeated check-capture evidence.",
1446
- "Run the proposal review-check after updating the review file.",
1447
- ],
1448
- };
1449
- }
1450
-
1451
- export function buildSkillProposalApplyPlan(payload, {
1452
- generatedAt = new Date(),
1453
- } = {}) {
1454
- const generatedAtText = generatedAt instanceof Date ? generatedAt.toISOString() : String(generatedAt || "");
1455
- const review = payload.review || {};
1456
- const reviewFile = payload.reviewFile || review.file || "";
1457
- const acceptedProposals = Array.isArray(payload.proposals)
1458
- ? payload.proposals.filter((proposal) => proposal.reviewStatus === "accepted")
1459
- : [];
1460
- const tasks = acceptedProposals.map(acceptedProposalTask);
1461
- const reviewStatus = review.status || "unknown";
1462
- const status = reviewStatus === "fail" || !reviewFile || review.exists === false
1463
- ? "fail"
1464
- : tasks.length > 0
1465
- ? "warn"
1466
- : "pass";
1467
- const followUpCommands = applyPlanFollowUpCommands(payload, reviewFile);
1468
- const commandContract = buildApplyPlanCommandContract(followUpCommands, reviewFile);
1469
-
1470
- return {
1471
- version: 1,
1472
- kind: "skill-proposal-apply-plan",
1473
- generatedAt: generatedAtText,
1474
- file: payload.file,
1475
- usageFile: payload.usageFile,
1476
- signalSource: payload.signalSource,
1477
- reviewFile,
1478
- status,
1479
- proposalStatus: payload.status,
1480
- signalStatus: payload.signalStatus,
1481
- candidateCount: payload.candidateCount || 0,
1482
- proposalCount: payload.proposalCount || 0,
1483
- acceptedCount: tasks.length,
1484
- count: tasks.length,
1485
- pendingReviewCount: payload.pendingReviewCount || 0,
1486
- reviewedCount: payload.reviewedCount || 0,
1487
- review,
1488
- tasks,
1489
- commands: {
1490
- reviewCheckJson: followUpCommands.reviewCheckJson.command,
1491
- reviewCheckReport: followUpCommands.reviewCheckReport.command,
1492
- proposalPatchPreview: followUpCommands.proposalPatchPreview.command,
1493
- strictGate: followUpCommands.strictGate.command,
1494
- },
1495
- commandArgs: {
1496
- reviewCheckJson: followUpCommands.reviewCheckJson.commandArgs,
1497
- reviewCheckReport: followUpCommands.reviewCheckReport.commandArgs,
1498
- proposalPatchPreview: followUpCommands.proposalPatchPreview.commandArgs,
1499
- strictGate: followUpCommands.strictGate.commandArgs,
1500
- },
1501
- commandContract,
1502
- recommendations: tasks.length > 0
1503
- ? [{
1504
- level: "warning",
1505
- text: "Apply accepted proposal deltas manually, then mark each reviewed decision as applied only after verification passes.",
1506
- }]
1507
- : [{
1508
- level: "info",
1509
- text: "No accepted skill proposals are ready to apply. Mark reviewed proposals as accepted before generating an apply plan.",
1510
- }],
1511
- privacy: {
1512
- mutatesProfile: false,
1513
- mutatesReviewFile: false,
1514
- mutatesSkillFiles: false,
1515
- callsExternalAiApis: false,
1516
- storesRawBriefText: false,
1517
- exposesEntryTextPreview: true,
1518
- },
1519
- };
1520
- }
1521
-
1522
- function listItem(label, value) {
1523
- return `- ${label}: ${value}`;
1524
- }
1525
-
1526
- function yesNo(value) {
1527
- return value ? "yes" : "no";
1528
- }
1529
-
1530
- function normalizePatchPath(filePath) {
1531
- return String(filePath || "").replace(/\\/g, "/");
1532
- }
1533
-
1534
- function readSkillLines(skillPath, sourceRoot) {
1535
- const absolutePath = path.resolve(sourceRoot, skillPath);
1536
- if (!existsSync(absolutePath)) {
1537
- return {
1538
- exists: false,
1539
- absolutePath,
1540
- lines: [],
1541
- };
1542
- }
1543
- const text = readFileSync(absolutePath, "utf8");
1544
- return {
1545
- exists: true,
1546
- absolutePath,
1547
- lines: text.split(/\r?\n/),
1548
- };
1549
- }
1550
-
1551
- function patchSectionForProposal(proposal) {
1552
- const routes = Array.isArray(proposal.routeIds) && proposal.routeIds.length > 0
1553
- ? proposal.routeIds.join(", ")
1554
- : "artifact";
1555
- return [
1556
- "",
1557
- `## Local Learning Proposal: ${proposal.id}`,
1558
- "",
1559
- "<!-- Generated by design-ai learn --propose-skills --patch. Review manually before applying. -->",
1560
- "",
1561
- `- Category: ${proposal.category}`,
1562
- `- Routes: ${routes}`,
1563
- `- Risk: ${proposal.riskLevel}`,
1564
- `- Review status: ${proposal.reviewStatus || "pending"}`,
1565
- `- Evidence count: ${proposal.sourceIssueCount}`,
1566
- `- Proposed instruction: ${proposal.proposedInstructionDelta}`,
1567
- `- Verification: \`${proposal.verificationCommand}\``,
1568
- ];
1569
- }
1570
-
1571
- function patchForSkillFile(skillPath, proposals, { sourceRoot }) {
1572
- const normalizedSkillPath = normalizePatchPath(skillPath);
1573
- const file = readSkillLines(normalizedSkillPath, sourceRoot);
1574
- const currentLines = file.lines.slice();
1575
- if (currentLines.length > 0 && currentLines[currentLines.length - 1] === "") {
1576
- currentLines.pop();
1577
- }
1578
- const addedLines = proposals.flatMap(patchSectionForProposal);
1579
- const fromPath = `a/${normalizedSkillPath}`;
1580
- const toPath = `b/${normalizedSkillPath}`;
1581
- const lines = [
1582
- `diff --git ${fromPath} ${toPath}`,
1583
- `--- ${file.exists ? fromPath : "/dev/null"}`,
1584
- `+++ ${toPath}`,
1585
- ];
1586
-
1587
- if (currentLines.length === 0) {
1588
- lines.push(`@@ -0,0 +1,${addedLines.length} @@`);
1589
- for (const addedLine of addedLines) {
1590
- lines.push(`+${addedLine}`);
1591
- }
1592
- return lines;
1593
- }
1594
-
1595
- const contextLine = currentLines[currentLines.length - 1];
1596
- lines.push(`@@ -${currentLines.length},1 +${currentLines.length},${addedLines.length + 1} @@`);
1597
- lines.push(` ${contextLine}`);
1598
- for (const addedLine of addedLines) {
1599
- lines.push(`+${addedLine}`);
1600
- }
1601
- return lines;
1602
- }
1603
-
1604
- export function renderSkillEvolutionProposalPatch(payload, {
1605
- sourceRoot = PACKAGE_ROOT,
1606
- } = {}) {
1607
- const proposals = Array.isArray(payload.proposals)
1608
- ? payload.proposals.filter((proposal) => !proposal.reviewClearsStrict)
1609
- : [];
1610
- const lines = [
1611
- "# design-ai skill proposal patch preview",
1612
- "# Preview-only output from `design-ai learn --propose-skills --patch`.",
1613
- "# Review manually before applying. This command does not edit skill files.",
1614
- ];
1615
-
1616
- if (proposals.length === 0) {
1617
- lines.push((payload.proposalCount || 0) > 0
1618
- ? "# No pending skill proposal deltas remain after review-file decisions."
1619
- : "# No repeated check-capture groups crossed the proposal threshold.");
1620
- return `${lines.join("\n")}\n`;
1621
- }
1622
-
1623
- const proposalsBySkill = new Map();
1624
- for (const proposal of proposals) {
1625
- const skillPath = proposal.candidateSkillPath || "skills/unknown/SKILL.md";
1626
- if (!proposalsBySkill.has(skillPath)) proposalsBySkill.set(skillPath, []);
1627
- proposalsBySkill.get(skillPath).push(proposal);
1628
- }
1629
-
1630
- for (const [skillPath, skillProposals] of [...proposalsBySkill.entries()].sort((a, b) => a[0].localeCompare(b[0]))) {
1631
- lines.push("");
1632
- lines.push(...patchForSkillFile(skillPath, skillProposals, { sourceRoot }));
1633
- }
1634
-
1635
- return `${lines.join("\n")}\n`;
1636
- }
1637
-
1638
- export function renderSkillProposalReviewTemplate(payload, {
1639
- generatedAt = new Date(),
1640
- } = {}) {
1641
- const generatedAtText = generatedAt instanceof Date ? generatedAt.toISOString() : String(generatedAt || "");
1642
- const proposals = Array.isArray(payload.proposals)
1643
- ? payload.proposals.filter((proposal) => !proposal.reviewClearsStrict)
1644
- : [];
1645
- const decisions = proposals.map((proposal) => ({
1646
- proposalId: proposal.id,
1647
- status: proposal.reviewStatus && proposal.reviewStatus !== "pending" ? proposal.reviewStatus : "deferred",
1648
- reviewedAt: "",
1649
- reviewer: "",
1650
- note: `Review ${proposal.candidateSkillPath}: ${proposal.title}`,
1651
- }));
1652
-
1653
- return `${JSON.stringify({
1654
- version: 1,
1655
- generatedAt: generatedAtText,
1656
- source: "design-ai learn --propose-skills --review-template",
1657
- proposalFile: payload.file || "",
1658
- usageFile: payload.usageFile || "",
1659
- signalSource: payload.signalSource || "",
1660
- reviewFile: payload.reviewFile || "",
1661
- reviewPolicy: {
1662
- clearsStrict: ["applied", "rejected"],
1663
- remainsPending: ["accepted", "deferred"],
1664
- },
1665
- summary: {
1666
- proposalCount: payload.proposalCount || 0,
1667
- pendingReviewCount: payload.pendingReviewCount ?? proposals.length,
1668
- reviewedCount: payload.reviewedCount || 0,
1669
- templateDecisionCount: decisions.length,
1670
- },
1671
- decisions,
1672
- }, null, 2)}\n`;
1673
- }
1674
-
1675
- export function renderSkillEvolutionProposalReport(payload, {
1676
- generatedAt = new Date(),
1677
- } = {}) {
1678
- const generatedAtText = generatedAt instanceof Date ? generatedAt.toISOString() : String(generatedAt || "");
1679
- const lines = [
1680
- "# Skill Evolution Proposal Report",
1681
- "",
1682
- listItem("Generated", generatedAtText),
1683
- listItem("File", payload.file),
1684
- listItem("Usage sidecar", payload.usageFile),
1685
- listItem("Signal source", payload.signalSource),
1686
- listItem("Status", payload.status),
1687
- listItem("Signal status", payload.signalStatus),
1688
- listItem("Minimum evidence", payload.minEvidenceCount),
1689
- listItem("Check capture entries", payload.checkCaptureCount),
1690
- listItem("Candidate groups", payload.candidateCount),
1691
- listItem("Proposal count", payload.proposalCount),
1692
- listItem("Skipped groups", payload.skippedCount),
1693
- listItem("Pending review", payload.pendingReviewCount ?? payload.proposalCount ?? 0),
1694
- listItem("Reviewed", payload.reviewedCount ?? 0),
1695
- listItem("Dry run", yesNo(Boolean(payload.dryRun))),
1696
- listItem("Applied", yesNo(Boolean(payload.applied))),
1697
- "",
1698
- "## Proposed Skill Deltas",
1699
- "",
1700
- ];
1701
-
1702
- if (!Array.isArray(payload.proposals) || payload.proposals.length === 0) {
1703
- lines.push("No repeated check-capture groups crossed the proposal threshold.");
1704
- } else {
1705
- for (const proposal of payload.proposals) {
1706
- const routes = Array.isArray(proposal.routeIds) && proposal.routeIds.length > 0
1707
- ? proposal.routeIds.join(", ")
1708
- : "artifact";
1709
- lines.push(`### ${proposal.title}`);
1710
- lines.push("");
1711
- lines.push(listItem("Proposal id", proposal.id));
1712
- lines.push(listItem("Candidate skill", proposal.candidateSkillPath));
1713
- lines.push(listItem("Category", proposal.category));
1714
- lines.push(listItem("Routes", routes));
1715
- lines.push(listItem("Risk", proposal.riskLevel));
1716
- lines.push(listItem("Review status", proposal.reviewStatus || "pending"));
1717
- if (proposal.reviewDecision?.reviewedAt) lines.push(listItem("Reviewed at", proposal.reviewDecision.reviewedAt));
1718
- if (proposal.reviewDecision?.reviewer) lines.push(listItem("Reviewer", proposal.reviewDecision.reviewer));
1719
- if (proposal.reviewDecision?.note) lines.push(listItem("Review note", proposal.reviewDecision.note));
1720
- lines.push(listItem("Source issues", proposal.sourceIssueCount));
1721
- lines.push(listItem("Rationale", proposal.rationale));
1722
- lines.push("");
1723
- lines.push("Proposed instruction delta:");
1724
- lines.push("");
1725
- lines.push(`> ${proposal.proposedInstructionDelta}`);
1726
- lines.push("");
1727
- lines.push("Verification:");
1728
- lines.push("");
1729
- lines.push(`\`\`\`bash`);
1730
- lines.push(proposal.verificationCommand);
1731
- lines.push(`\`\`\``);
1732
- lines.push("");
1733
- lines.push("Evidence:");
1734
- const evidenceSources = Array.isArray(proposal.evidenceSources) ? proposal.evidenceSources : [];
1735
- for (const evidence of evidenceSources) {
1736
- const title = evidence.title ? ` / ${evidence.title}` : "";
1737
- lines.push(`- \`${evidence.entryId}\` [${evidence.category}] ${evidence.source}${title}`);
1738
- if (evidence.createdAt) lines.push(` - Captured: ${evidence.createdAt}`);
1739
- if (evidence.textPreview) lines.push(` - Preview: ${evidence.textPreview}`);
1740
- }
1741
- lines.push("");
1742
- }
1743
- }
1744
-
1745
- const review = payload.review || {};
1746
- lines.push("## Review File", "");
1747
- if (review.file) {
1748
- lines.push(listItem("File", review.file));
1749
- lines.push(listItem("Exists", yesNo(Boolean(review.exists))));
1750
- lines.push(listItem("Status", review.status || "unknown"));
1751
- lines.push(listItem("Decisions", review.decisionCount || 0));
1752
- lines.push(listItem("Matched", review.matchedCount || 0));
1753
- lines.push(listItem("Stale", review.staleCount || 0));
1754
- lines.push(listItem("Pending", review.pendingCount ?? 0));
1755
- lines.push(listItem("Applied", review.appliedCount || 0));
1756
- lines.push(listItem("Rejected", review.rejectedCount || 0));
1757
- lines.push(listItem("Accepted", review.acceptedCount || 0));
1758
- lines.push(listItem("Deferred", review.deferredCount || 0));
1759
- if (Array.isArray(review.warnings) && review.warnings.length > 0) {
1760
- lines.push("");
1761
- lines.push("Warnings:");
1762
- for (const warning of review.warnings) lines.push(`- ${warning}`);
1763
- }
1764
- } else {
1765
- lines.push("No review file was provided.");
1766
- }
1767
- lines.push("");
1768
-
1769
- lines.push("## Skipped Groups", "");
1770
- if (!Array.isArray(payload.skipped) || payload.skipped.length === 0) {
1771
- lines.push("No candidate groups were skipped.");
1772
- } else {
1773
- for (const item of payload.skipped) {
1774
- lines.push(`- \`${item.candidateSkillPath}\` [${item.category}]: ${item.reason}`);
1775
- const evidenceSources = Array.isArray(item.evidenceSources) ? item.evidenceSources : [];
1776
- for (const evidence of evidenceSources) {
1777
- lines.push(` - Evidence: \`${evidence.entryId}\` ${evidence.source}`);
1778
- }
1779
- }
1780
- }
1781
-
1782
- lines.push("", "## Recommendations", "");
1783
- if (!Array.isArray(payload.recommendations) || payload.recommendations.length === 0) {
1784
- lines.push("No recommendations emitted.");
1785
- } else {
1786
- for (const recommendation of payload.recommendations) {
1787
- lines.push(`- ${recommendation.level}: ${recommendation.text}`);
1788
- }
1789
- }
1790
-
1791
- const privacy = payload.privacy || {};
1792
- lines.push("", "## Privacy And Boundaries", "");
1793
- lines.push(listItem("Mutates learning profile", yesNo(Boolean(privacy.mutatesProfile))));
1794
- lines.push(listItem("Mutates skill files", yesNo(Boolean(privacy.mutatesSkillFiles))));
1795
- lines.push(listItem("Calls external AI APIs", yesNo(Boolean(privacy.callsExternalAiApis))));
1796
- lines.push(listItem("Stores raw brief text", yesNo(Boolean(privacy.storesRawBriefText))));
1797
- lines.push(listItem("Includes entry text preview", yesNo(Boolean(privacy.exposesEntryTextPreview))));
1798
-
1799
- lines.push("", "## Next Steps", "");
1800
- if ((payload.proposalCount || 0) > 0) {
1801
- lines.push("- Review each proposed instruction delta manually before editing any skill file.");
1802
- lines.push("- Mark resolved proposals as `applied` or `rejected` in the review file to clear the strict proposal gate.");
1803
- lines.push("- Run the proposal verification command after applying an accepted skill edit.");
1804
- lines.push("- Re-run `design-ai learn --propose-skills --strict --json` to confirm no pending proposal gate remains.");
1805
- } else {
1806
- lines.push("- Keep capturing explicit `design-ai check --learn --yes` warning/failure results until repeated evidence exists.");
1807
- lines.push("- Re-run this report after new check-capture entries are added.");
1808
- }
1809
- lines.push("- This report is preview-only evidence; it does not apply changes.");
1810
-
1811
- return `${lines.join("\n")}\n`;
1812
- }
1813
-
1814
- export function renderSkillProposalApplyPlanReport(payload, {
1815
- generatedAt = new Date(),
1816
- } = {}) {
1817
- const generatedAtText = generatedAt instanceof Date ? generatedAt.toISOString() : String(generatedAt || "");
1818
- const lines = [
1819
- "# Skill Proposal Apply Plan",
1820
- "",
1821
- listItem("Generated", generatedAtText),
1822
- listItem("Status", payload.status),
1823
- listItem("Proposal status", payload.proposalStatus),
1824
- listItem("Signal status", payload.signalStatus),
1825
- listItem("File", payload.file),
1826
- listItem("Usage sidecar", payload.usageFile),
1827
- listItem("Signal source", payload.signalSource),
1828
- listItem("Review file", payload.reviewFile || "not configured"),
1829
- listItem("Accepted proposals", payload.acceptedCount || 0),
1830
- listItem("Pending review", payload.pendingReviewCount || 0),
1831
- listItem("Reviewed", payload.reviewedCount || 0),
1832
- "",
1833
- "## Manual Apply Tasks",
1834
- "",
1835
- ];
1836
-
1837
- if (!Array.isArray(payload.tasks) || payload.tasks.length === 0) {
1838
- lines.push("No accepted skill proposals are ready for manual apply.");
1839
- } else {
1840
- for (const task of payload.tasks) {
1841
- lines.push(`### ${task.title}`);
1842
- lines.push("");
1843
- lines.push(listItem("Proposal id", task.proposalId));
1844
- lines.push(listItem("Candidate skill", task.candidateSkillPath));
1845
- lines.push(listItem("Category", task.category));
1846
- lines.push(listItem("Routes", (task.routeIds || []).join(", ") || "artifact"));
1847
- lines.push(listItem("Risk", task.riskLevel));
1848
- lines.push(listItem("Source issues", task.sourceIssueCount));
1849
- if (task.reviewDecision?.reviewer) lines.push(listItem("Reviewer", task.reviewDecision.reviewer));
1850
- if (task.reviewDecision?.reviewedAt) lines.push(listItem("Reviewed at", task.reviewDecision.reviewedAt));
1851
- if (task.reviewDecision?.note) lines.push(listItem("Review note", task.reviewDecision.note));
1852
- if (task.rationale) lines.push(listItem("Rationale", task.rationale));
1853
- lines.push("");
1854
- lines.push("Proposed instruction delta:");
1855
- lines.push("");
1856
- lines.push(`> ${task.proposedInstructionDelta}`);
1857
- lines.push("");
1858
- lines.push("Manual steps:");
1859
- for (const step of task.manualSteps || []) lines.push(`- ${step}`);
1860
- lines.push("");
1861
- lines.push("Verification:");
1862
- lines.push("");
1863
- lines.push("```bash");
1864
- lines.push(task.verificationCommand);
1865
- lines.push("```");
1866
- lines.push("");
1867
- lines.push("Safety checklist:");
1868
- for (const item of task.safetyChecklist || []) lines.push(`- ${item}`);
1869
- lines.push("");
1870
- lines.push("Evidence:");
1871
- for (const evidence of task.evidenceSources || []) {
1872
- const title = evidence.title ? ` / ${evidence.title}` : "";
1873
- lines.push(`- \`${evidence.entryId}\` [${evidence.category}] ${evidence.source}${title}`);
1874
- if (evidence.textPreview) lines.push(` - Preview: ${evidence.textPreview}`);
1875
- }
1876
- lines.push("");
1877
- }
1878
- }
1879
-
1880
- lines.push("## Follow-up Commands", "");
1881
- const commands = payload.commands || {};
1882
- for (const [label, command] of Object.entries(commands)) {
1883
- lines.push(listItem(label, `\`${command}\``));
1884
- }
1885
-
1886
- const commandContract = payload.commandContract || {};
1887
- lines.push("", "## Command Contract", "");
1888
- lines.push(listItem("Valid", yesNo(Boolean(commandContract.valid))));
1889
- lines.push(listItem("Status", commandContract.status || "unknown"));
1890
- lines.push(listItem("Command count", commandContract.commandCount || 0));
1891
- lines.push(listItem("Check count", commandContract.checkCount || 0));
1892
- lines.push(listItem("Pass count", commandContract.passCount || 0));
1893
- lines.push(listItem("Warning count", commandContract.warningCount || 0));
1894
- lines.push(listItem("Required keys", (commandContract.requiredKeys || []).join(", ") || "none"));
1895
- lines.push(listItem("Review file required", yesNo(Boolean(commandContract.reviewFileRequired))));
1896
- lines.push(listItem("Forbidden flags", (commandContract.forbiddenFlags || []).join(", ") || "none"));
1897
- lines.push(listItem("Failure count", commandContract.failureCount || 0));
1898
- lines.push(listItem("Failed checks", (commandContract.failedCheckIds || []).join(", ") || "none"));
1899
- lines.push(listItem("Next command key", commandContract.nextCommandKey || "none"));
1900
- lines.push(listItem("Next command policy", commandContract.nextCommandRunPolicy || "none"));
1901
- if (commandContract.nextCommandSafety?.level) lines.push(listItem("Next command safety", commandContract.nextCommandSafety.level));
1902
- if (commandContract.nextCommand) lines.push(listItem("Next command", `\`${commandContract.nextCommand}\``));
1903
- lines.push(listItem("Command sequence count", commandContract.commandSequenceCount || 0));
1904
- lines.push(listItem("Command sequence keys", (commandContract.commandSequenceKeys || []).join(", ") || "none"));
1905
- const sequenceSummary = commandContract.commandSequenceSummary || {};
1906
- lines.push(listItem("Command sequence policy", sequenceSummary.runPolicy || "none"));
1907
- lines.push(listItem("Command sequence executable", yesNo(Boolean(sequenceSummary.executable))));
1908
- lines.push(listItem("Command sequence local outputs", sequenceSummary.localOutputStepCount || 0));
1909
- lines.push(listItem("Command sequence mutates profile", yesNo(Boolean(sequenceSummary.mutatesProfile))));
1910
- lines.push(listItem("Command sequence mutates review file", yesNo(Boolean(sequenceSummary.mutatesReviewFile))));
1911
- lines.push(listItem("Command sequence mutates skill files", yesNo(Boolean(sequenceSummary.mutatesSkillFiles))));
1912
- lines.push(listItem("Command sequence calls external AI APIs", yesNo(Boolean(sequenceSummary.callsExternalAiApis))));
1913
- const operatorRunbook = commandContract.operatorRunbook || {};
1914
- lines.push(listItem("Operator runbook stages", operatorRunbook.stageCount || 0));
1915
- lines.push(listItem("Operator runbook keys", (operatorRunbook.stageKeys || []).join(", ") || "none"));
1916
- lines.push(listItem("Operator runbook required stages", operatorRunbook.requiredStageCount || 0));
1917
- lines.push(listItem("Operator runbook next stage", operatorRunbook.nextStageKey || "none"));
1918
- lines.push(listItem("Operator runbook next required stage", operatorRunbook.nextRequiredStageKey || "none"));
1919
- lines.push(listItem("Operator runbook next required command stage", operatorRunbook.nextRequiredCommandStageKey || "none"));
1920
- if (operatorRunbook.stageSelection?.strategy) {
1921
- lines.push(listItem("Operator runbook stage selection", operatorRunbook.stageSelection.strategy));
1922
- if (operatorRunbook.stageSelection.decision?.action) {
1923
- lines.push(listItem("Operator runbook decision", operatorRunbook.stageSelection.decision.action));
1924
- if (operatorRunbook.stageSelection.decision.safety?.level) {
1925
- lines.push(listItem("Operator runbook decision safety", operatorRunbook.stageSelection.decision.safety.level));
1926
- }
1927
- if (Array.isArray(operatorRunbook.stageSelection.decision.commands)) {
1928
- lines.push(listItem("Operator runbook decision commands", operatorRunbook.stageSelection.decision.commands.map((command) => command.key).join(", ") || "none"));
1929
- }
1930
- if (operatorRunbook.stageSelection.decision.nextCommandKey) {
1931
- lines.push(listItem("Operator runbook decision next command", operatorRunbook.stageSelection.decision.nextCommandKey));
1932
- }
1933
- }
1934
- if (operatorRunbook.stageSelection.nextStage?.key) {
1935
- const nextStageLabel = operatorRunbook.stageSelection.nextStage.required ? "required" : "optional";
1936
- lines.push(listItem("Operator runbook selected stage", `${operatorRunbook.stageSelection.nextStage.key} (${nextStageLabel}, ${operatorRunbook.stageSelection.nextStage.kind})`));
1937
- }
1938
- }
1939
- const commandSequence = Array.isArray(commandContract.commandSequence) ? commandContract.commandSequence : [];
1940
- if (commandSequence.length > 0) {
1941
- lines.push("");
1942
- lines.push("Command sequence:");
1943
- for (const item of commandSequence) {
1944
- const safetyLevel = item.safety?.level || "unknown";
1945
- lines.push(`- ${item.step}. ${item.key} (${item.runPolicy || "unknown"} / ${safetyLevel}): \`${item.command}\``);
1946
- }
1947
- }
1948
- const runbookStages = Array.isArray(operatorRunbook.stages) ? operatorRunbook.stages : [];
1949
- if (runbookStages.length > 0) {
1950
- lines.push("");
1951
- lines.push("Operator runbook:");
1952
- for (const stage of runbookStages) {
1953
- const required = stage.required ? "required" : "optional";
1954
- const commandKeys = Array.isArray(stage.commandKeys) && stage.commandKeys.length > 0
1955
- ? stage.commandKeys.join(", ")
1956
- : "manual";
1957
- lines.push(`- ${stage.step}. ${stage.key} (${required} / ${stage.kind || "unknown"}): ${commandKeys}`);
1958
- }
1959
- }
1960
- if (commandContract.nextAction) lines.push(listItem("Next action", commandContract.nextAction));
1961
- const missingCommandKeys = Array.isArray(commandContract.missingCommandKeys) ? commandContract.missingCommandKeys : [];
1962
- const unexpectedCommandKeys = Array.isArray(commandContract.unexpectedCommandKeys) ? commandContract.unexpectedCommandKeys : [];
1963
- if (missingCommandKeys.length > 0) lines.push(listItem("Missing command keys", missingCommandKeys.join(", ")));
1964
- if (unexpectedCommandKeys.length > 0) lines.push(listItem("Unexpected command keys", unexpectedCommandKeys.join(", ")));
1965
- const failedChecks = Array.isArray(commandContract.failedChecks) ? commandContract.failedChecks : [];
1966
- if (failedChecks.length > 0) {
1967
- lines.push("");
1968
- lines.push("Failed command checks:");
1969
- for (const check of failedChecks) {
1970
- lines.push(`- ${check.id}: ${check.message}`);
1971
- }
1972
- }
1973
-
1974
- lines.push("", "## Recommendations", "");
1975
- for (const recommendation of payload.recommendations || []) {
1976
- lines.push(`- ${recommendation.level}: ${recommendation.text}`);
1977
- }
1978
-
1979
- const privacy = payload.privacy || {};
1980
- lines.push("", "## Privacy And Boundaries", "");
1981
- lines.push(listItem("Mutates learning profile", yesNo(Boolean(privacy.mutatesProfile))));
1982
- lines.push(listItem("Mutates review file", yesNo(Boolean(privacy.mutatesReviewFile))));
1983
- lines.push(listItem("Mutates skill files", yesNo(Boolean(privacy.mutatesSkillFiles))));
1984
- lines.push(listItem("Calls external AI APIs", yesNo(Boolean(privacy.callsExternalAiApis))));
1985
- lines.push(listItem("Stores raw brief text", yesNo(Boolean(privacy.storesRawBriefText))));
1986
- lines.push(listItem("Includes entry text preview", yesNo(Boolean(privacy.exposesEntryTextPreview))));
1987
-
1988
- return `${lines.join("\n")}\n`;
1989
- }
1990
-
1991
- export function renderSkillProposalReviewCheckReport(payload, {
1992
- generatedAt = new Date(),
1993
- } = {}) {
1994
- const generatedAtText = generatedAt instanceof Date ? generatedAt.toISOString() : String(generatedAt || "");
1995
- const lines = [
1996
- "# Skill Proposal Review Check",
1997
- "",
1998
- listItem("Generated", generatedAtText),
1999
- listItem("Status", payload.status),
2000
- listItem("Proposal status", payload.proposalStatus),
2001
- listItem("Signal status", payload.signalStatus),
2002
- listItem("File", payload.file),
2003
- listItem("Usage sidecar", payload.usageFile),
2004
- listItem("Signal source", payload.signalSource),
2005
- listItem("Review file", payload.reviewFile || "not configured"),
2006
- listItem("Proposals", payload.proposalCount),
2007
- listItem("Pending review", payload.pendingReviewCount),
2008
- listItem("Reviewed", payload.reviewedCount),
2009
- "",
2010
- "## Checks",
2011
- "",
2012
- ];
2013
-
2014
- for (const check of payload.checks || []) {
2015
- lines.push(`- ${check.level}: ${check.id} - ${check.message}`);
2016
- }
2017
-
2018
- const review = payload.review || {};
2019
- lines.push("", "## Review Summary", "");
2020
- lines.push(listItem("Exists", yesNo(Boolean(review.exists))));
2021
- lines.push(listItem("Status", review.status || "unknown"));
2022
- lines.push(listItem("Decisions", review.decisionCount || 0));
2023
- lines.push(listItem("Matched", review.matchedCount || 0));
2024
- lines.push(listItem("Stale", review.staleCount || 0));
2025
- lines.push(listItem("Applied", review.appliedCount || 0));
2026
- lines.push(listItem("Rejected", review.rejectedCount || 0));
2027
- lines.push(listItem("Accepted", review.acceptedCount || 0));
2028
- lines.push(listItem("Deferred", review.deferredCount || 0));
2029
- if (Array.isArray(review.warnings) && review.warnings.length > 0) {
2030
- lines.push("", "Warnings:");
2031
- for (const warning of review.warnings) lines.push(`- ${warning}`);
2032
- }
2033
-
2034
- lines.push("", "## Recommendations", "");
2035
- for (const recommendation of payload.recommendations || []) {
2036
- lines.push(`- ${recommendation.level}: ${recommendation.text}`);
2037
- }
2038
-
2039
- lines.push("", "## Privacy And Boundaries", "");
2040
- lines.push(listItem("Mutates learning profile", yesNo(Boolean(payload.privacy?.mutatesProfile))));
2041
- lines.push(listItem("Mutates skill files", yesNo(Boolean(payload.privacy?.mutatesSkillFiles))));
2042
- lines.push(listItem("Calls external AI APIs", yesNo(Boolean(payload.privacy?.callsExternalAiApis))));
2043
- lines.push(listItem("Stores raw brief text", yesNo(Boolean(payload.privacy?.storesRawBriefText))));
2044
-
2045
- return `${lines.join("\n")}\n`;
2046
- }
1
+ // Public skill proposal surface for `design-ai learn --propose-skills` — re-exports the skill-proposals-* modules.
2
+
3
+ export { buildSkillProposalApplyPlan } from "./skill-proposals-apply-plan.mjs";
4
+ export { buildSkillEvolutionProposals } from "./skill-proposals-generate.mjs";
5
+ export {
6
+ renderSkillEvolutionProposalPatch,
7
+ renderSkillEvolutionProposalReport,
8
+ renderSkillProposalApplyPlanReport,
9
+ renderSkillProposalReviewCheckReport,
10
+ renderSkillProposalReviewTemplate,
11
+ } from "./skill-proposals-render.mjs";
12
+ export {
13
+ buildSkillProposalReviewCheck,
14
+ loadSkillProposalReviewState,
15
+ } from "./skill-proposals-review.mjs";