@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,1056 +1,30 @@
1
- // Local dogfood workspace diagnostics for `design-ai workspace`.
2
-
3
- import { spawnSync } from "node:child_process";
4
- import { existsSync, readFileSync } from "node:fs";
5
- import path from "node:path";
6
-
7
- import {
8
- defaultLearningFile,
9
- defaultLearningUsageFile,
10
- learningEvalReport,
11
- listLearningRestoreBackups,
12
- learningStats,
13
- learningUsageStats,
14
- } from "./learn.mjs";
15
- import { DESIGN_AI_HOME } from "./paths.mjs";
16
- import { unknownOptionMessage } from "./suggest.mjs";
17
-
18
- export const WORKSPACE_OPTIONS = [
19
- "-h",
20
- "--help",
21
- "--json",
22
- "--strict",
23
- "--root",
24
- "--learning-file",
25
- "--learning-usage",
26
- "--learning-eval",
27
- ];
28
-
29
- const RELEASE_SCRIPT_NAMES = [
30
- "test",
31
- "audit:strict",
32
- "release:metadata",
33
- "release:self-test",
34
- "package:smoke",
35
- "release:check",
36
- "ci:local",
37
- ];
38
-
39
- const UNIQUE_RELEASE_SCRIPT_NAMES = [...new Set(RELEASE_SCRIPT_NAMES)];
40
- const CANONICAL_REPOSITORY_SLUG = "sungjin9288/design-ai";
41
- const CANONICAL_REPOSITORY_URL = `https://github.com/${CANONICAL_REPOSITORY_SLUG}`;
42
- const DEFAULT_LEARNING_EVAL_FILE = "learning-eval.json";
43
- const DEFAULT_LEARNING_CURATION_REPORT_FILE = "learning-curation-report.md";
44
- const DEFAULT_RESTORE_BACKUP_KEEP = 5;
45
- const IGNORED_LOCAL_ARTIFACT_EXACT_PATHS = new Set([
46
- "DEV_LOG.md",
47
- "docs/case-study.md",
48
- "docs/evidence-checklist.md",
49
- "docs/evidence-gallery.md",
50
- "docs/implementation-evidence.md",
51
- "docs/interview-story.md",
52
- "docs/project-card.md",
53
- "docs/project-roadmap.md",
54
- "docs/readme-improvement.md",
55
- "docs/resume-bullets.md",
56
- "links.md",
57
- "portfolio_manifest.md",
58
- ]);
59
- const IGNORED_LOCAL_ARTIFACT_PREFIXES = [
60
- "_portfolio_export/",
61
- "evidence/",
62
- ];
63
-
64
- export function parseWorkspaceArgs(args) {
65
- const flags = {
66
- help: false,
67
- json: false,
68
- strict: false,
69
- root: "",
70
- learningFilePath: "",
71
- learningUsagePath: "",
72
- learningEvalPath: "",
73
- };
74
-
75
- for (let i = 0; i < args.length; i += 1) {
76
- const arg = args[i];
77
- if (arg === "-h" || arg === "--help") {
78
- flags.help = true;
79
- continue;
80
- }
81
- if (arg === "--json") {
82
- flags.json = true;
83
- continue;
84
- }
85
- if (arg === "--strict") {
86
- flags.strict = true;
87
- continue;
88
- }
89
- if (arg === "--root") {
90
- const root = args[i + 1];
91
- if (!root || root.startsWith("--")) throw new Error("--root expects a path");
92
- flags.root = root;
93
- i += 1;
94
- continue;
95
- }
96
- if (arg === "--learning-file") {
97
- const filePath = args[i + 1];
98
- if (!filePath || filePath.startsWith("--")) {
99
- throw new Error("--learning-file expects a path");
100
- }
101
- flags.learningFilePath = filePath;
102
- i += 1;
103
- continue;
104
- }
105
- if (arg === "--learning-usage") {
106
- const usagePath = args[i + 1];
107
- if (!usagePath || usagePath.startsWith("--")) {
108
- throw new Error("--learning-usage expects a path");
109
- }
110
- flags.learningUsagePath = usagePath;
111
- i += 1;
112
- continue;
113
- }
114
- if (arg === "--learning-eval") {
115
- const evalPath = args[i + 1];
116
- if (!evalPath || evalPath.startsWith("--")) {
117
- throw new Error("--learning-eval expects a path");
118
- }
119
- flags.learningEvalPath = evalPath;
120
- i += 1;
121
- continue;
122
- }
123
-
124
- throw new Error(
125
- `${unknownOptionMessage("workspace", arg, WORKSPACE_OPTIONS)}\n` +
126
- "Usage: design-ai workspace [--root path] [--learning-file path] [--learning-usage path] [--learning-eval path] [--strict] [--json]",
127
- );
128
- }
129
-
130
- return flags;
131
- }
132
-
133
- function runGitCommand(args, { cwd }) {
134
- const result = spawnSync("git", args, {
135
- cwd,
136
- encoding: "utf8",
137
- timeout: 5000,
138
- });
139
- return {
140
- ok: result.status === 0,
141
- status: result.status,
142
- stdout: result.stdout || "",
143
- stderr: result.stderr || "",
144
- error: result.error?.message || "",
145
- };
146
- }
147
-
148
- function splitLines(text) {
149
- return String(text || "")
150
- .split(/\r?\n/)
151
- .map((line) => line.trimEnd())
152
- .filter(Boolean);
153
- }
154
-
155
- function trimOutput(result) {
156
- return String(result?.stdout || "").trim();
157
- }
158
-
159
- function parseAheadBehind(text) {
160
- const [behindRaw, aheadRaw] = String(text || "").trim().split(/\s+/);
161
- const behind = Number(behindRaw);
162
- const ahead = Number(aheadRaw);
163
- return {
164
- ahead: Number.isInteger(ahead) ? ahead : 0,
165
- behind: Number.isInteger(behind) ? behind : 0,
166
- };
167
- }
168
-
169
- function parseLastCommit(text) {
170
- const trimmed = String(text || "").trim();
171
- if (!trimmed) return null;
172
- const [hash, ...subjectParts] = trimmed.split("\t");
173
- return {
174
- hash: hash || "",
175
- subject: subjectParts.join("\t").trim(),
176
- };
177
- }
178
-
179
- function parseUntrackedStatusPath(line) {
180
- const text = String(line || "");
181
- if (!text.startsWith("?? ")) return "";
182
- return text.slice(3).trim();
183
- }
184
-
185
- function isIgnoredLocalArtifactStatus(line) {
186
- const filePath = parseUntrackedStatusPath(line);
187
- if (!filePath) return false;
188
- if (IGNORED_LOCAL_ARTIFACT_EXACT_PATHS.has(filePath)) return true;
189
- return IGNORED_LOCAL_ARTIFACT_PREFIXES.some((prefix) => filePath === prefix.slice(0, -1) || filePath.startsWith(prefix));
190
- }
191
-
192
- function splitGitStatusShort(statusShort) {
193
- const activeStatusShort = [];
194
- const ignoredStatusShort = [];
195
- for (const line of statusShort) {
196
- if (isIgnoredLocalArtifactStatus(line)) {
197
- ignoredStatusShort.push(line);
198
- } else {
199
- activeStatusShort.push(line);
200
- }
201
- }
202
- return { activeStatusShort, ignoredStatusShort };
203
- }
204
-
205
- export function collectGitReport({ root = process.cwd(), gitRunner = runGitCommand } = {}) {
206
- const resolvedRoot = path.resolve(root);
207
- const base = {
208
- isRepo: false,
209
- root: resolvedRoot,
210
- branch: "",
211
- clean: true,
212
- upstream: "",
213
- ahead: 0,
214
- behind: 0,
215
- remote: "",
216
- lastCommit: null,
217
- statusShort: [],
218
- allStatusShort: [],
219
- ignoredStatusShort: [],
220
- ignoredLocalArtifactCount: 0,
221
- hasIgnoredLocalArtifacts: false,
222
- reason: "",
223
- };
224
-
225
- const inside = gitRunner(["rev-parse", "--is-inside-work-tree"], { cwd: resolvedRoot });
226
- if (!inside.ok || trimOutput(inside) !== "true") {
227
- return {
228
- ...base,
229
- reason: inside.error || trimOutput(inside) || String(inside.stderr || "").trim() || "not a git repository",
230
- };
231
- }
232
-
233
- const repoRootResult = gitRunner(["rev-parse", "--show-toplevel"], { cwd: resolvedRoot });
234
- const repoRoot = repoRootResult.ok && trimOutput(repoRootResult)
235
- ? trimOutput(repoRootResult)
236
- : resolvedRoot;
237
- const branchResult = gitRunner(["branch", "--show-current"], { cwd: repoRoot });
238
- const statusResult = gitRunner(["status", "--short"], { cwd: repoRoot });
239
- const upstreamResult = gitRunner(["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"], {
240
- cwd: repoRoot,
241
- });
242
- const remoteResult = gitRunner(["config", "--get", "remote.origin.url"], { cwd: repoRoot });
243
- const lastCommitResult = gitRunner(["log", "-1", "--pretty=%h%x09%s"], { cwd: repoRoot });
244
-
245
- let ahead = 0;
246
- let behind = 0;
247
- if (upstreamResult.ok && trimOutput(upstreamResult)) {
248
- const countsResult = gitRunner(["rev-list", "--left-right", "--count", "@{u}...HEAD"], {
249
- cwd: repoRoot,
250
- });
251
- if (countsResult.ok) {
252
- ({ ahead, behind } = parseAheadBehind(trimOutput(countsResult)));
253
- }
254
- }
255
-
256
- const statusShort = statusResult.ok ? splitLines(statusResult.stdout) : [];
257
- const { activeStatusShort, ignoredStatusShort } = splitGitStatusShort(statusShort);
258
-
259
- return {
260
- ...base,
261
- isRepo: true,
262
- root: repoRoot,
263
- branch: trimOutput(branchResult),
264
- clean: activeStatusShort.length === 0,
265
- upstream: upstreamResult.ok ? trimOutput(upstreamResult) : "",
266
- ahead,
267
- behind,
268
- remote: remoteResult.ok ? trimOutput(remoteResult) : "",
269
- lastCommit: lastCommitResult.ok ? parseLastCommit(lastCommitResult.stdout) : null,
270
- statusShort: activeStatusShort,
271
- allStatusShort: statusShort,
272
- ignoredStatusShort,
273
- ignoredLocalArtifactCount: ignoredStatusShort.length,
274
- hasIgnoredLocalArtifacts: ignoredStatusShort.length > 0,
275
- reason: "",
276
- };
277
- }
278
-
279
- function safeReadJsonFile(filePath) {
280
- try {
281
- return JSON.parse(readFileSync(filePath, "utf8"));
282
- } catch {
283
- return null;
284
- }
285
- }
286
-
287
- function safeReadPackageJson(sourceRoot) {
288
- return safeReadJsonFile(path.join(sourceRoot, "package.json"));
289
- }
290
-
291
- function safeReadPluginJson(sourceRoot) {
292
- return safeReadJsonFile(path.join(sourceRoot, ".claude-plugin", "plugin.json"));
293
- }
294
-
295
- export function collectReleaseScriptReport({ sourceRoot = DESIGN_AI_HOME } = {}) {
296
- const packageJson = safeReadPackageJson(sourceRoot);
297
- const scripts = packageJson?.scripts && typeof packageJson.scripts === "object"
298
- ? packageJson.scripts
299
- : {};
300
- const available = UNIQUE_RELEASE_SCRIPT_NAMES.filter((name) => typeof scripts[name] === "string");
301
- const missing = UNIQUE_RELEASE_SCRIPT_NAMES.filter((name) => typeof scripts[name] !== "string");
302
-
303
- return {
304
- packageName: packageJson?.name || "",
305
- version: packageJson?.version || "",
306
- scripts: Object.fromEntries(available.map((name) => [name, scripts[name]])),
307
- available,
308
- missing,
309
- };
310
- }
311
-
312
- export function normalizeRepositoryUrl(value) {
313
- let text = String(value || "").trim();
314
- if (!text) return "";
315
- if (text.startsWith("git+")) text = text.slice(4);
316
- text = text.replace(/\.git$/u, "");
317
-
318
- const scpMatch = text.match(/^git@github\.com:(?<slug>[^/]+\/[^/]+)$/u);
319
- if (scpMatch?.groups?.slug) return `https://github.com/${scpMatch.groups.slug}`;
320
-
321
- const sshMatch = text.match(/^ssh:\/\/git@github\.com\/(?<slug>[^/]+\/[^/]+)$/u);
322
- if (sshMatch?.groups?.slug) return `https://github.com/${sshMatch.groups.slug}`;
323
-
324
- const httpsMatch = text.match(/^https?:\/\/github\.com\/(?<slug>[^/]+\/[^/]+)$/u);
325
- if (httpsMatch?.groups?.slug) return `https://github.com/${httpsMatch.groups.slug}`;
326
-
327
- return text;
328
- }
329
-
330
- export function repositorySlugFromUrl(value) {
331
- const normalized = normalizeRepositoryUrl(value);
332
- const match = normalized.match(/^https:\/\/github\.com\/(?<slug>[^/]+\/[^/]+)$/u);
333
- return match?.groups?.slug || "";
334
- }
335
-
336
- export function collectRepositoryReport({
337
- sourceRoot = DESIGN_AI_HOME,
338
- git = null,
339
- } = {}) {
340
- const packageJson = safeReadPackageJson(sourceRoot) || {};
341
- const pluginJson = safeReadPluginJson(sourceRoot) || {};
342
- const expectedPackageRepositoryUrl = `git+${CANONICAL_REPOSITORY_URL}.git`;
343
- const expectedPackageHomepage = `${CANONICAL_REPOSITORY_URL}#readme`;
344
- const expectedPackageBugsUrl = `${CANONICAL_REPOSITORY_URL}/issues`;
345
- const expectedPluginUrl = CANONICAL_REPOSITORY_URL;
346
-
347
- const packageRepositoryUrl = typeof packageJson.repository === "object" && packageJson.repository
348
- ? packageJson.repository.url || ""
349
- : "";
350
- const packageHomepage = packageJson.homepage || "";
351
- const packageBugsUrl = typeof packageJson.bugs === "object" && packageJson.bugs
352
- ? packageJson.bugs.url || ""
353
- : "";
354
- const pluginHomepage = pluginJson.homepage || "";
355
- const pluginRepository = pluginJson.repository || "";
356
-
357
- const metadataChecks = [
358
- ["package.repository.url", packageRepositoryUrl, expectedPackageRepositoryUrl],
359
- ["package.homepage", packageHomepage, expectedPackageHomepage],
360
- ["package.bugs.url", packageBugsUrl, expectedPackageBugsUrl],
361
- ["plugin.homepage", pluginHomepage, expectedPluginUrl],
362
- ["plugin.repository", pluginRepository, expectedPluginUrl],
363
- ].map(([label, actual, expected]) => ({
364
- label,
365
- actual,
366
- expected,
367
- aligned: actual === expected,
368
- }));
369
-
370
- const issues = metadataChecks
371
- .filter((check) => !check.aligned)
372
- .map((check) => `${check.label} mismatch: ${check.actual || "missing"} != ${check.expected}`);
373
-
374
- const remoteUrl = git?.remote || "";
375
- const remoteSlug = repositorySlugFromUrl(remoteUrl);
376
- const remoteAligned = git?.isRepo && remoteUrl ? remoteSlug === CANONICAL_REPOSITORY_SLUG : null;
377
- if (remoteAligned === false) {
378
- issues.push(`git remote origin points to ${remoteSlug || remoteUrl}, expected ${CANONICAL_REPOSITORY_SLUG}`);
379
- }
380
-
381
- return {
382
- slug: CANONICAL_REPOSITORY_SLUG,
383
- url: CANONICAL_REPOSITORY_URL,
384
- expectedRemoteUrl: `${CANONICAL_REPOSITORY_URL}.git`,
385
- packageRepositoryUrl,
386
- packageHomepage,
387
- packageBugsUrl,
388
- pluginHomepage,
389
- pluginRepository,
390
- metadataAligned: metadataChecks.every((check) => check.aligned),
391
- remoteUrl,
392
- remoteSlug,
393
- remoteAligned,
394
- issues,
395
- };
396
- }
397
-
398
- export function collectLearningReport({
399
- filePath = defaultLearningFile(),
400
- learningStatsProvider = learningStats,
401
- } = {}) {
402
- const resolvedFile = path.resolve(filePath);
403
- try {
404
- const stats = learningStatsProvider({ filePath: resolvedFile });
405
- return {
406
- file: stats.file || resolvedFile,
407
- exists: Boolean(stats.exists),
408
- updatedAt: stats.updatedAt || "",
409
- count: Number.isInteger(stats.count) ? stats.count : 0,
410
- categoryCounts: stats.categoryCounts || {},
411
- sourceCounts: stats.sourceCounts || {},
412
- latestEntry: stats.latestEntry || null,
413
- auditSummary: stats.auditSummary || { status: "pass", failures: 0, warnings: 0 },
414
- error: "",
415
- };
416
- } catch (error) {
417
- return {
418
- file: resolvedFile,
419
- exists: existsSync(resolvedFile),
420
- updatedAt: "",
421
- count: 0,
422
- categoryCounts: {},
423
- sourceCounts: {},
424
- latestEntry: null,
425
- auditSummary: { status: "fail", failures: 1, warnings: 0 },
426
- error: error?.message || String(error),
427
- };
428
- }
429
- }
430
-
431
- export function collectLearningEvalReport({
432
- learningFilePath = defaultLearningFile(),
433
- learningEvalPath = "",
434
- learningEvalReportProvider = learningEvalReport,
435
- } = {}) {
436
- if (!learningEvalPath) return null;
437
-
438
- const resolvedEvalPath = path.resolve(learningEvalPath);
439
- const resolvedLearningFile = path.resolve(learningFilePath);
440
- try {
441
- const evalText = readFileSync(resolvedEvalPath, "utf8");
442
- const report = learningEvalReportProvider({
443
- filePath: resolvedLearningFile,
444
- evalText,
445
- source: resolvedEvalPath,
446
- });
447
- return {
448
- source: resolvedEvalPath,
449
- file: report.file,
450
- status: report.status,
451
- caseCount: report.caseCount,
452
- passed: report.passed,
453
- warned: report.warned,
454
- failed: report.failed,
455
- generatedAt: report.generatedAt || "",
456
- sourceProfile: report.sourceProfile || null,
457
- profileExists: report.profileExists,
458
- profileEntryCount: report.profileEntryCount,
459
- auditSummary: report.auditSummary,
460
- privacy: report.privacy,
461
- error: "",
462
- };
463
- } catch (error) {
464
- return {
465
- source: resolvedEvalPath,
466
- file: resolvedLearningFile,
467
- status: "fail",
468
- caseCount: 0,
469
- passed: 0,
470
- warned: 0,
471
- failed: 0,
472
- generatedAt: "",
473
- sourceProfile: null,
474
- profileExists: existsSync(resolvedLearningFile),
475
- profileEntryCount: 0,
476
- auditSummary: { status: "fail", failures: 1, warnings: 0 },
477
- privacy: {
478
- storesRawBriefText: false,
479
- storesBriefHash: true,
480
- exposesMatchedTokens: false,
481
- },
482
- error: error?.message || String(error),
483
- };
484
- }
485
- }
486
-
487
- export function assessLearningRestoreBackupReadiness({
488
- totalCount = 0,
489
- keep = DEFAULT_RESTORE_BACKUP_KEEP,
490
- error = "",
491
- } = {}) {
492
- if (error) {
493
- return {
494
- status: "fail",
495
- reason: "restore-backup-inventory-error",
496
- keep,
497
- totalCount,
498
- pruneCandidateCount: 0,
499
- };
500
- }
501
-
502
- if (totalCount <= 0) {
503
- return {
504
- status: "unknown",
505
- reason: "no restore rollback backups found",
506
- keep,
507
- totalCount,
508
- pruneCandidateCount: 0,
509
- };
510
- }
511
-
512
- const pruneCandidateCount = Math.max(0, totalCount - keep);
513
- if (pruneCandidateCount > 0) {
514
- return {
515
- status: "warn",
516
- reason: `${pruneCandidateCount} older restore rollback backup(s) can be pruned`,
517
- keep,
518
- totalCount,
519
- pruneCandidateCount,
520
- };
521
- }
522
-
523
- return {
524
- status: "pass",
525
- reason: "",
526
- keep,
527
- totalCount,
528
- pruneCandidateCount,
529
- };
530
- }
531
-
532
- export function collectLearningRestoreBackupsReport({
533
- learningFilePath = defaultLearningFile(),
534
- limit = DEFAULT_RESTORE_BACKUP_KEEP,
535
- keep = DEFAULT_RESTORE_BACKUP_KEEP,
536
- learningRestoreBackupsProvider = listLearningRestoreBackups,
537
- } = {}) {
538
- const resolvedLearningFile = path.resolve(learningFilePath);
539
-
540
- try {
541
- const inventory = learningRestoreBackupsProvider({
542
- filePath: resolvedLearningFile,
543
- limit,
544
- });
545
- const totalCount = Number.isInteger(inventory.totalCount) ? inventory.totalCount : 0;
546
- if (totalCount <= 0) return null;
547
-
548
- const backups = Array.isArray(inventory.backups) ? inventory.backups : [];
549
- const readiness = assessLearningRestoreBackupReadiness({ totalCount, keep });
550
-
551
- return {
552
- file: inventory.file || resolvedLearningFile,
553
- directory: inventory.directory || path.dirname(resolvedLearningFile),
554
- pattern: inventory.pattern || "",
555
- generatedAt: inventory.generatedAt || "",
556
- limit: Number.isInteger(inventory.limit) ? inventory.limit : limit,
557
- totalCount,
558
- count: Number.isInteger(inventory.count) ? inventory.count : backups.length,
559
- latestBackup: backups[0] || null,
560
- backups,
561
- readiness,
562
- privacy: inventory.privacy || {
563
- storesRawBriefText: false,
564
- exposesEntryTextPreview: false,
565
- mutatesProfile: false,
566
- },
567
- error: "",
568
- };
569
- } catch (error) {
570
- const message = error?.message || String(error);
571
- return {
572
- file: resolvedLearningFile,
573
- directory: path.dirname(resolvedLearningFile),
574
- pattern: "",
575
- generatedAt: "",
576
- limit,
577
- totalCount: 0,
578
- count: 0,
579
- latestBackup: null,
580
- backups: [],
581
- readiness: assessLearningRestoreBackupReadiness({ totalCount: 0, keep, error: message }),
582
- privacy: {
583
- storesRawBriefText: false,
584
- exposesEntryTextPreview: false,
585
- mutatesProfile: false,
586
- },
587
- error: message,
588
- };
589
- }
590
- }
591
-
592
- export function defaultLearningEvalPath(learningFilePath = defaultLearningFile()) {
593
- return path.join(path.dirname(path.resolve(learningFilePath)), DEFAULT_LEARNING_EVAL_FILE);
594
- }
595
-
596
- export function defaultLearningUsagePath(learningFilePath = defaultLearningFile()) {
597
- return defaultLearningUsageFile(learningFilePath);
598
- }
599
-
600
- function action(level, text, command = "") {
601
- return command ? { level, text, command } : { level, text };
602
- }
603
-
604
- export function quoteShellArg(value) {
605
- const text = String(value ?? "");
606
- if (/^[A-Za-z0-9_@%+=:,./-]+$/u.test(text)) return text;
607
- return `'${text.replace(/'/gu, "'\\''")}'`;
608
- }
609
-
610
- function pushUniqueAction(actions, nextAction) {
611
- if (nextAction.command && actions.some((item) => item.command === nextAction.command)) return;
612
- actions.push(nextAction);
613
- }
614
-
615
- function learningCurationCommand(learning, learningUsage = null) {
616
- const usagePart = learningUsage?.usageFile
617
- ? ` --usage-file ${quoteShellArg(learningUsage.usageFile)}`
618
- : "";
619
- return `design-ai learn --curate --file ${quoteShellArg(learning.file)}${usagePart}`;
620
- }
621
-
622
- export function defaultLearningCurationReportPath(learningFilePath = defaultLearningFile()) {
623
- return path.join(path.dirname(path.resolve(learningFilePath)), DEFAULT_LEARNING_CURATION_REPORT_FILE);
624
- }
625
-
626
- function learningCurationReportCommand(learning, learningUsage = null) {
627
- return `${learningCurationCommand(learning, learningUsage)} --report --out ${quoteShellArg(defaultLearningCurationReportPath(learning.file))}`;
628
- }
629
-
630
- function learningRestoreBackupsCommand(learning) {
631
- return `design-ai learn --restore-backups --file ${quoteShellArg(learning.file)}`;
632
- }
633
-
634
- function learningRestoreBackupPruneCommand(learning, keep = DEFAULT_RESTORE_BACKUP_KEEP) {
635
- return `${learningRestoreBackupsCommand(learning)} --prune --keep ${keep}`;
636
- }
637
-
638
- function parsedTimestamp(value) {
639
- const time = Date.parse(String(value || ""));
640
- return Number.isNaN(time) ? null : time;
641
- }
642
-
643
- export function assessLearningEvalFreshness({ learning, learningEval } = {}) {
644
- const profileUpdatedAt = learning?.updatedAt || "";
645
- const checkpointGeneratedAt = learningEval?.generatedAt || "";
646
- const sourceProfile = learningEval?.sourceProfile || null;
647
- const issues = [];
648
-
649
- if (!learningEval) return null;
650
-
651
- if (sourceProfile?.file && path.resolve(sourceProfile.file) !== path.resolve(learning?.file || "")) {
652
- issues.push("source-profile-file-mismatch");
653
- }
654
-
655
- if (
656
- Number.isInteger(sourceProfile?.entryCount)
657
- && Number.isInteger(learning?.count)
658
- && sourceProfile.entryCount !== learning.count
659
- ) {
660
- issues.push("source-profile-entry-count-changed");
661
- }
662
-
663
- const profileUpdatedTime = parsedTimestamp(profileUpdatedAt);
664
- const checkpointGeneratedTime = parsedTimestamp(checkpointGeneratedAt);
665
- if (
666
- profileUpdatedTime !== null
667
- && checkpointGeneratedTime !== null
668
- && profileUpdatedTime > checkpointGeneratedTime
669
- ) {
670
- issues.push("profile-updated-after-checkpoint");
671
- }
672
-
673
- if (issues.length > 0) {
674
- return {
675
- status: "warn",
676
- stale: true,
677
- reason: issues.join(", "),
678
- profileUpdatedAt,
679
- checkpointGeneratedAt,
680
- sourceProfileFile: sourceProfile?.file || "",
681
- sourceProfileEntryCount: Number.isInteger(sourceProfile?.entryCount) ? sourceProfile.entryCount : null,
682
- };
683
- }
684
-
685
- if (!checkpointGeneratedAt && !sourceProfile) {
686
- return {
687
- status: "unknown",
688
- stale: false,
689
- reason: "checkpoint metadata unavailable",
690
- profileUpdatedAt,
691
- checkpointGeneratedAt,
692
- sourceProfileFile: "",
693
- sourceProfileEntryCount: null,
694
- };
695
- }
696
-
697
- return {
698
- status: "pass",
699
- stale: false,
700
- reason: "",
701
- profileUpdatedAt,
702
- checkpointGeneratedAt,
703
- sourceProfileFile: sourceProfile?.file || "",
704
- sourceProfileEntryCount: Number.isInteger(sourceProfile?.entryCount) ? sourceProfile.entryCount : null,
705
- };
706
- }
707
-
708
- export function collectLearningUsageReport({
709
- learningFilePath = defaultLearningFile(),
710
- learningUsagePath = "",
711
- learningUsageStatsProvider = learningUsageStats,
712
- } = {}) {
713
- if (!learningUsagePath) return null;
714
-
715
- const resolvedLearningFile = path.resolve(learningFilePath);
716
- const resolvedUsagePath = path.resolve(learningUsagePath);
717
- try {
718
- const stats = learningUsageStatsProvider({
719
- filePath: resolvedLearningFile,
720
- usageFile: resolvedUsagePath,
721
- });
722
- return {
723
- file: stats.file || resolvedLearningFile,
724
- usageFile: stats.usageFile || resolvedUsagePath,
725
- exists: Boolean(stats.exists),
726
- profileExists: Boolean(stats.profileExists),
727
- profileFile: stats.profileFile || resolvedLearningFile,
728
- version: Number.isInteger(stats.version) ? stats.version : 1,
729
- updatedAt: stats.updatedAt || "",
730
- eventCount: Number.isInteger(stats.eventCount) ? stats.eventCount : 0,
731
- profileEntryCount: Number.isInteger(stats.profileEntryCount) ? stats.profileEntryCount : 0,
732
- usedEntryCount: Number.isInteger(stats.usedEntryCount) ? stats.usedEntryCount : 0,
733
- unusedEntryCount: Number.isInteger(stats.unusedEntryCount) ? stats.unusedEntryCount : 0,
734
- staleSelectedEntryCount: Number.isInteger(stats.staleSelectedEntryCount) ? stats.staleSelectedEntryCount : 0,
735
- commandCounts: stats.commandCounts || {},
736
- routeCounts: stats.routeCounts || {},
737
- categoryCounts: stats.categoryCounts || {},
738
- auditStatusCounts: stats.auditStatusCounts || {},
739
- latestEvent: stats.latestEvent || null,
740
- privacy: stats.privacy || {
741
- storesRawBriefText: false,
742
- storesBriefHash: true,
743
- storesSelectedEntryIds: true,
744
- },
745
- recommendations: Array.isArray(stats.recommendations) ? stats.recommendations : [],
746
- readiness: assessLearningUsageReadiness({ learningFilePath: resolvedLearningFile, usage: stats }),
747
- error: "",
748
- };
749
- } catch (error) {
750
- return {
751
- file: resolvedLearningFile,
752
- usageFile: resolvedUsagePath,
753
- exists: existsSync(resolvedUsagePath),
754
- profileExists: existsSync(resolvedLearningFile),
755
- profileFile: resolvedLearningFile,
756
- version: 1,
757
- updatedAt: "",
758
- eventCount: 0,
759
- profileEntryCount: 0,
760
- usedEntryCount: 0,
761
- unusedEntryCount: 0,
762
- staleSelectedEntryCount: 0,
763
- commandCounts: {},
764
- routeCounts: {},
765
- categoryCounts: {},
766
- auditStatusCounts: {},
767
- latestEvent: null,
768
- privacy: {
769
- storesRawBriefText: false,
770
- storesBriefHash: true,
771
- storesSelectedEntryIds: true,
772
- },
773
- recommendations: [],
774
- readiness: {
775
- status: "fail",
776
- reason: "usage-sidecar-error",
777
- profileFile: resolvedLearningFile,
778
- profileFileMatches: true,
779
- staleSelectedEntryCount: 0,
780
- },
781
- error: error?.message || String(error),
782
- };
783
- }
784
- }
785
-
786
- export function assessLearningUsageReadiness({ learningFilePath = defaultLearningFile(), usage } = {}) {
787
- if (!usage) return null;
788
-
789
- const resolvedLearningFile = path.resolve(learningFilePath);
790
- const profileFile = usage.profileFile ? path.resolve(usage.profileFile) : "";
791
- const profileFileMatches = profileFile ? profileFile === resolvedLearningFile : true;
792
- const staleSelectedEntryCount = Number.isInteger(usage.staleSelectedEntryCount)
793
- ? usage.staleSelectedEntryCount
794
- : 0;
795
- const issues = [];
796
-
797
- if (!profileFileMatches) issues.push("usage-profile-file-mismatch");
798
- if (staleSelectedEntryCount > 0) issues.push("stale-selected-entry-ids");
799
-
800
- if (issues.length > 0) {
801
- return {
802
- status: "warn",
803
- reason: issues.join(", "),
804
- profileFile: usage.profileFile || "",
805
- profileFileMatches,
806
- staleSelectedEntryCount,
807
- };
808
- }
809
-
810
- if (!usage.exists || usage.eventCount === 0) {
811
- return {
812
- status: "unknown",
813
- reason: usage.exists ? "usage sidecar has no events" : "usage sidecar unavailable",
814
- profileFile: usage.profileFile || "",
815
- profileFileMatches,
816
- staleSelectedEntryCount,
817
- };
818
- }
819
-
820
- return {
821
- status: "pass",
822
- reason: "",
823
- profileFile: usage.profileFile || "",
824
- profileFileMatches,
825
- staleSelectedEntryCount,
826
- };
827
- }
828
-
829
- export function buildWorkspaceNextActions({
830
- git,
831
- repository,
832
- learning,
833
- learningUsage,
834
- learningEval,
835
- learningRestoreBackups,
836
- release,
837
- }) {
838
- const actions = [];
839
-
840
- if (!git.isRepo) {
841
- actions.push(action("warn", "Open design-ai from a git workspace before preparing shared changes."));
842
- } else {
843
- if (!git.clean) {
844
- actions.push(action("warn", "Review local changes before committing or pushing.", "git status --short"));
845
- } else if (git.hasIgnoredLocalArtifacts) {
846
- actions.push(action("info", "Ignored local portfolio/evidence artifacts are present and excluded from workspace readiness.", "git status --short"));
847
- } else if (!git.upstream && git.branch) {
848
- actions.push(action("warn", "Set an upstream branch before sharing dogfood work.", `git push -u origin ${git.branch}`));
849
- } else if (git.behind > 0) {
850
- actions.push(action("warn", "Rebase or merge remote changes before continuing release work.", "git pull --rebase"));
851
- } else if (git.ahead > 0) {
852
- actions.push(action("info", "Push committed local work when the current phase is ready.", "git push"));
853
- } else {
854
- actions.push(action("pass", "Git workspace is clean and synced."));
855
- }
856
- }
857
-
858
- if (repository && !repository.metadataAligned) {
859
- actions.push(action("fail", "Fix package/plugin repository metadata before preparing shared dogfood builds.", "npm run release:metadata"));
860
- } else if (repository?.remoteAligned === false) {
861
- actions.push(action("warn", "Verify git remote points at the canonical design-ai repository before pushing.", "git remote -v"));
862
- }
863
-
864
- if (release.missing.length > 0) {
865
- actions.push(action("fail", `Restore required release script(s): ${release.missing.join(", ")}`));
866
- }
867
-
868
- if (learning.error) {
869
- actions.push(action("fail", "Repair the local learning profile before relying on personalized prompt context.", "design-ai learn --audit"));
870
- } else if (learning.auditSummary.status !== "pass") {
871
- pushUniqueAction(actions, action(
872
- "warn",
873
- "Preview archive-first learning curation before dogfooding prompts.",
874
- learningCurationCommand(learning, learningUsage),
875
- ));
876
- pushUniqueAction(actions, action(
877
- "info",
878
- "Save a Markdown learning curation report before applying archive actions.",
879
- learningCurationReportCommand(learning, learningUsage),
880
- ));
881
- } else if (learning.count === 0) {
882
- actions.push(action("info", "Capture reviewed feedback after checks to make dogfood runs improve over time.", "design-ai check artifact.md --learn --yes"));
883
- } else if (!learningEval) {
884
- actions.push(action(
885
- "info",
886
- "Generate a local learning eval checkpoint before relying on personalized prompt context.",
887
- `design-ai learn --eval-template --file ${quoteShellArg(learning.file)} --out ${quoteShellArg(defaultLearningEvalPath(learning.file))}`,
888
- ));
889
- }
890
-
891
- if (learningUsage) {
892
- const usageCommand = `design-ai learn --usage --file ${quoteShellArg(learningUsage.file)} --usage-file ${quoteShellArg(learningUsage.usageFile)}`;
893
- if (learningUsage.error) {
894
- actions.push(action("fail", "Repair the local learning usage sidecar before trusting prompt/pack usage analytics.", usageCommand));
895
- } else if (learningUsage.readiness?.status === "warn") {
896
- pushUniqueAction(actions, action(
897
- "warn",
898
- "Preview usage-aware learning curation before curating learning entries.",
899
- learningCurationCommand(learning, learningUsage),
900
- ));
901
- pushUniqueAction(actions, action(
902
- "info",
903
- "Save a Markdown usage-aware learning curation report before applying archive actions.",
904
- learningCurationReportCommand(learning, learningUsage),
905
- ));
906
- } else if (learningUsage.readiness?.status === "pass") {
907
- actions.push(action("pass", "Learning usage sidecar is aligned with the active profile.", usageCommand));
908
- } else {
909
- actions.push(action("info", "Record prompt/pack --with-learning usage before judging which learning entries are useful.", usageCommand));
910
- }
911
- } else if (!learning.error && learning.auditSummary.status === "pass" && learning.count > 0) {
912
- actions.push(action(
913
- "info",
914
- "Run prompt or pack with --with-learning to create a local learning usage sidecar.",
915
- "design-ai prompt \"your brief\" --with-learning",
916
- ));
917
- }
918
-
919
- if (learningEval) {
920
- const evalCommand = `design-ai learn --eval --from-file ${quoteShellArg(learningEval.source)} --file ${quoteShellArg(learningEval.file)} --strict`;
921
- const regenerateCommand = `design-ai learn --eval-template --file ${quoteShellArg(learningEval.file)} --out ${quoteShellArg(learningEval.source)} --force`;
922
- if (learningEval.error) {
923
- actions.push(action("fail", "Fix the local learning eval checkpoint before using workspace readiness as a gate.", evalCommand));
924
- } else if (learningEval.status === "fail") {
925
- actions.push(action("fail", "Review failed local learning eval checkpoint cases before trusting prompt/pack selection.", evalCommand));
926
- } else if (learningEval.status === "warn") {
927
- actions.push(action("warn", "Review local learning eval checkpoint warnings before relying on personalized prompt context.", evalCommand));
928
- } else if (learningEval.freshness?.status === "warn") {
929
- actions.push(action("warn", "Regenerate the local learning eval checkpoint because the learning profile changed after it was created.", regenerateCommand));
930
- } else {
931
- actions.push(action("pass", "Learning eval checkpoints pass.", evalCommand));
932
- }
933
- }
934
-
935
- if (learningRestoreBackups) {
936
- const readiness = learningRestoreBackups.readiness || {};
937
- if (learningRestoreBackups.error) {
938
- actions.push(action("fail", "Repair the learning restore rollback backup inventory before relying on restore readiness.", learningRestoreBackupsCommand(learning)));
939
- } else if (readiness.pruneCandidateCount > 0) {
940
- actions.push(action(
941
- "info",
942
- "Preview pruning older learning restore rollback backups after keeping the newest recovery points.",
943
- learningRestoreBackupPruneCommand(learning, readiness.keep || DEFAULT_RESTORE_BACKUP_KEEP),
944
- ));
945
- } else if (learningRestoreBackups.totalCount > 0) {
946
- actions.push(action("pass", "Learning restore rollback backups are available for rollback review.", learningRestoreBackupsCommand(learning)));
947
- }
948
- }
949
-
950
- if (release.available.includes("test")) {
951
- actions.push(action("info", "Run CLI unit tests before handing this phase off.", "npm test"));
952
- }
953
- if (release.available.includes("audit:strict")) {
954
- actions.push(action("info", "Run the strict repository audit before repo cleanup or team distribution.", "npm run audit:strict"));
955
- }
956
- if (release.available.includes("package:smoke")) {
957
- actions.push(action("info", "Use package smoke before publishing or testing the packed install path.", "npm run package:smoke"));
958
- }
959
-
960
- return actions;
961
- }
962
-
963
- export function hasWorkspaceStrictIssues(report) {
964
- return (report?.nextActions || []).some((item) => item.level === "fail" || item.level === "warn");
965
- }
966
-
967
- export function collectWorkspaceReport({
968
- root = process.cwd(),
969
- sourceRoot = DESIGN_AI_HOME,
970
- learningFilePath = defaultLearningFile(),
971
- learningUsagePath = "",
972
- learningEvalPath = "",
973
- gitRunner = runGitCommand,
974
- learningStatsProvider = learningStats,
975
- learningUsageStatsProvider = learningUsageStats,
976
- learningEvalReportProvider = learningEvalReport,
977
- learningRestoreBackupsProvider = listLearningRestoreBackups,
978
- } = {}) {
979
- const resolvedRoot = path.resolve(root);
980
- const resolvedSourceRoot = path.resolve(sourceRoot);
981
- const resolvedLearningFile = path.resolve(learningFilePath);
982
- const resolvedLearningUsagePath = learningUsagePath || (
983
- existsSync(defaultLearningUsagePath(resolvedLearningFile)) ? defaultLearningUsagePath(resolvedLearningFile) : ""
984
- );
985
- const resolvedLearningEvalPath = learningEvalPath || (
986
- existsSync(defaultLearningEvalPath(resolvedLearningFile)) ? defaultLearningEvalPath(resolvedLearningFile) : ""
987
- );
988
- const git = collectGitReport({ root: resolvedRoot, gitRunner });
989
- const learning = collectLearningReport({
990
- filePath: resolvedLearningFile,
991
- learningStatsProvider,
992
- });
993
- const learningUsage = collectLearningUsageReport({
994
- learningFilePath: resolvedLearningFile,
995
- learningUsagePath: resolvedLearningUsagePath,
996
- learningUsageStatsProvider,
997
- });
998
- const learningEval = collectLearningEvalReport({
999
- learningFilePath: resolvedLearningFile,
1000
- learningEvalPath: resolvedLearningEvalPath,
1001
- learningEvalReportProvider,
1002
- });
1003
- const learningRestoreBackups = collectLearningRestoreBackupsReport({
1004
- learningFilePath: resolvedLearningFile,
1005
- learningRestoreBackupsProvider,
1006
- });
1007
- const learningEvalWithFreshness = learningEval
1008
- ? {
1009
- ...learningEval,
1010
- freshness: assessLearningEvalFreshness({ learning, learningEval }),
1011
- }
1012
- : null;
1013
- const release = collectReleaseScriptReport({ sourceRoot: resolvedSourceRoot });
1014
- const repository = collectRepositoryReport({ sourceRoot: resolvedSourceRoot, git });
1015
- const nextActions = buildWorkspaceNextActions({
1016
- git,
1017
- repository,
1018
- learning,
1019
- learningUsage,
1020
- learningEval: learningEvalWithFreshness,
1021
- learningRestoreBackups,
1022
- release,
1023
- });
1024
-
1025
- return {
1026
- context: {
1027
- cwd: process.cwd(),
1028
- root: resolvedRoot,
1029
- sourceRoot: resolvedSourceRoot,
1030
- packageName: release.packageName,
1031
- version: release.version,
1032
- },
1033
- git,
1034
- repository,
1035
- learning,
1036
- learningUsage,
1037
- learningEval: learningEvalWithFreshness,
1038
- learningRestoreBackups,
1039
- release,
1040
- nextActions,
1041
- };
1042
- }
1043
-
1044
- export function formatWorkspaceJson(report) {
1045
- return JSON.stringify({
1046
- context: report.context,
1047
- git: report.git,
1048
- repository: report.repository,
1049
- learning: report.learning,
1050
- learningUsage: report.learningUsage || null,
1051
- learningEval: report.learningEval || null,
1052
- learningRestoreBackups: report.learningRestoreBackups || null,
1053
- release: report.release,
1054
- nextActions: report.nextActions,
1055
- }, null, 2);
1056
- }
1
+ // Public workspace readiness surface for `design-ai workspace` — re-exports the workspace-* modules.
2
+
3
+ export { parseWorkspaceArgs, WORKSPACE_OPTIONS } from "./workspace-args.mjs";
4
+ export { collectGitReport } from "./workspace-git.mjs";
5
+ export {
6
+ assessLearningEvalFreshness,
7
+ assessLearningRestoreBackupReadiness,
8
+ assessLearningUsageReadiness,
9
+ collectLearningEvalReport,
10
+ collectLearningReport,
11
+ collectLearningRestoreBackupsReport,
12
+ collectLearningUsageReport,
13
+ collectRetrievalIndexReport,
14
+ defaultLearningCurationReportPath,
15
+ defaultLearningEvalPath,
16
+ defaultLearningUsagePath,
17
+ quoteShellArg,
18
+ } from "./workspace-learning.mjs";
19
+ export {
20
+ buildWorkspaceNextActions,
21
+ collectWorkspaceReport,
22
+ formatWorkspaceJson,
23
+ hasWorkspaceStrictIssues,
24
+ } from "./workspace-report.mjs";
25
+ export {
26
+ collectReleaseScriptReport,
27
+ collectRepositoryReport,
28
+ normalizeRepositoryUrl,
29
+ repositorySlugFromUrl,
30
+ } from "./workspace-repo.mjs";