@design-ai/cli 4.58.0 → 4.60.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +53 -0
- package/README.ko.md +3 -2
- package/README.md +3 -2
- package/cli/sdk/check-adapter.mjs +36 -0
- package/cli/sdk/index.d.ts +435 -0
- package/cli/sdk/index.mjs +30 -0
- package/cli/sdk/learn-adapter.mjs +100 -0
- package/cli/sdk/pack-adapter.mjs +55 -0
- package/cli/sdk/prompt-adapter.mjs +49 -0
- package/cli/sdk/recall-adapter.mjs +39 -0
- package/cli/sdk/route-adapter.mjs +45 -0
- package/cli/sdk/search-adapter.mjs +55 -0
- package/cli/sdk/validate.mjs +48 -0
- package/cli/sdk/version-adapter.mjs +17 -0
- package/docs/AGENT-SDK.md +98 -0
- package/docs/ROADMAP.md +98 -0
- package/docs/SDK.md +249 -0
- package/docs/external-status.md +6 -6
- package/package.json +9 -2
- package/tools/audit/package-smoke.py +84 -0
- package/tools/audit/registry-smoke.py +2 -2
package/docs/SDK.md
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
# Agent SDK reference
|
|
2
|
+
|
|
3
|
+
> Status: shipped (Phase A + Phase B) — 8 read-only verbs plus the opt-in `learn.*` local-write namespace, semver-stable surface
|
|
4
|
+
|
|
5
|
+
`@design-ai/cli/sdk` lets an external Node.js program — an agent runtime, a build script, a custom tool — use design-ai's deterministic design capabilities as importable functions, without shelling out to the CLI or spawning the MCP server. It is a thin, curated adapter over the same `cli/lib` functions the CLI and MCP server already call, so a capability that ships in the CLI is instantly available to an SDK consumer.
|
|
6
|
+
|
|
7
|
+
See [`AGENT-SDK.md`](AGENT-SDK.md) for the full design rationale, phased plan, and open questions. This page is the public reference for the shipped Phase A (read-only) and Phase B (local-write) surface.
|
|
8
|
+
|
|
9
|
+
## Install and import
|
|
10
|
+
|
|
11
|
+
The SDK ships inside the `@design-ai/cli` package (no separate install):
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install @design-ai/cli
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
```js
|
|
18
|
+
import { route, prompt, pack, search, recall, check, routes, version } from "@design-ai/cli/sdk";
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Only the `./sdk` subpath is exported — `import "@design-ai/cli"` (the bare package root) is intentionally not exported, so importing the SDK is always an explicit `@design-ai/cli/sdk` import. `cli/lib/*` is internal and unstable; do not import it directly.
|
|
22
|
+
|
|
23
|
+
### TypeScript
|
|
24
|
+
|
|
25
|
+
Hand-written type declarations ship with the package at `cli/sdk/index.d.ts` and are wired through the `exports` `types` condition, so TypeScript and editors resolve them automatically for `@design-ai/cli/sdk` — no `@types` install, and no build step on our side (the declarations are maintained by hand to preserve the zero-toolchain stance). Use `moduleResolution: node16`/`nodenext` (or `bundler`) so the subpath `types` condition is honored. All exported types (`RouteResult`, `PromptPlan`, `Pack`, `SearchHit`, `RankedSearchHit`, `RecallResult`, `CheckReport`, option interfaces, …) are importable:
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import { route, type RouteResult, type SearchOptions } from "@design-ai/cli/sdk";
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
A `node --test` guard (`cli/sdk/types.test.mjs`) asserts the declaration file stays in exact sync with the runtime exports.
|
|
32
|
+
|
|
33
|
+
## Phase A: read-only
|
|
34
|
+
|
|
35
|
+
Every verb below is a pure, read-only adapter:
|
|
36
|
+
|
|
37
|
+
- It validates its inputs and throws a plain `Error` with a clear message on bad input (e.g. a non-string brief).
|
|
38
|
+
- It resolves the package root the same way the CLI does.
|
|
39
|
+
- It calls the corresponding `cli/lib` function and returns a plain JSON-serializable object — the same shape the CLI's `--json` mode emits.
|
|
40
|
+
- It performs no file writes, no network calls, and no learning-usage sidecar writes. This is a deliberate difference from the CLI: `prompt`/`pack`'s `withLearning` option in the SDK only **reads** the local learning profile to build context — it never records a usage event, unlike the CLI's `--with-learning` flag. There is no SDK equivalent of `check --learn` (capture) in Phase A.
|
|
41
|
+
- Given the same inputs, it returns the same outputs (determinism), matching the CLI.
|
|
42
|
+
|
|
43
|
+
## Verbs
|
|
44
|
+
|
|
45
|
+
### `route(brief, opts)`
|
|
46
|
+
|
|
47
|
+
Recommend the best design-ai route(s), commands, skills, and knowledge files for a task brief.
|
|
48
|
+
|
|
49
|
+
```js
|
|
50
|
+
route(brief: string, opts?: { limit?: number, explain?: boolean }): RouteResult[]
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
- `limit` — maximum route recommendations, 1-10. Default: `3`.
|
|
54
|
+
- `explain` — include route scoring, reference coverage, and related-knowledge detail. Default: `false`.
|
|
55
|
+
|
|
56
|
+
Returns an array of `RouteResult` objects (`id`, `label`, `score`, `confidence`, `matchedKeywords`, `command`, `skills`, `agents`, `knowledge`, `keywords`, `explanation`, plus `relatedKnowledge` when `explain: true`) — the same shape as the `routes` array in `design-ai route --json`.
|
|
57
|
+
|
|
58
|
+
### `prompt(brief, opts)`
|
|
59
|
+
|
|
60
|
+
Build a ready-to-use agent prompt plan from a task brief.
|
|
61
|
+
|
|
62
|
+
```js
|
|
63
|
+
prompt(brief: string, opts?: {
|
|
64
|
+
routeId?: string,
|
|
65
|
+
withLearning?: boolean,
|
|
66
|
+
learningCategory?: string,
|
|
67
|
+
learningLimit?: number,
|
|
68
|
+
withRecall?: boolean,
|
|
69
|
+
recallLimit?: number,
|
|
70
|
+
}): PromptPlan
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
- `routeId` — force a route id instead of scoring the brief.
|
|
74
|
+
- `withLearning` — include brief-relevant local learning preferences (reads `DESIGN_AI_LEARNING_FILE`; **never** records usage, unlike the CLI's `--with-learning`).
|
|
75
|
+
- `learningCategory`, `learningLimit` (1-100) — scope/limit the learning context; require `withLearning`.
|
|
76
|
+
- `withRecall` — include brief-relevant shipped corpus knowledge.
|
|
77
|
+
- `recallLimit` (1-20) — limit recalled corpus files; requires `withRecall`.
|
|
78
|
+
|
|
79
|
+
Returns a `PromptPlan` object (`brief`, `version`, `route`, `slashCommand`, `referenceExamples`, `filesToRead`, `checklist`, `qualityCommand`, `prompt`, plus `learningContext`/`recall` when requested) — the same shape as `design-ai prompt --json`, minus `learningUsage` (Phase A never writes it).
|
|
80
|
+
|
|
81
|
+
### `pack(brief, opts)`
|
|
82
|
+
|
|
83
|
+
Build a ready-to-use prompt plus a bounded context-file bundle from a task brief.
|
|
84
|
+
|
|
85
|
+
```js
|
|
86
|
+
pack(brief: string, opts?: {
|
|
87
|
+
routeId?: string,
|
|
88
|
+
maxBytes?: number,
|
|
89
|
+
withLearning?: boolean,
|
|
90
|
+
learningCategory?: string,
|
|
91
|
+
learningLimit?: number,
|
|
92
|
+
withRecall?: boolean,
|
|
93
|
+
recallLimit?: number,
|
|
94
|
+
}): Pack
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
- `maxBytes` — maximum context bytes to include, 1000-1,000,000. Default: `120000`.
|
|
98
|
+
- The remaining options mirror `prompt`'s learning/recall options.
|
|
99
|
+
|
|
100
|
+
Returns a `Pack` object (`brief`, `version`, `maxBytes`, `usedBytes`, `summary`, `warnings`, `plan`, `files`, `markdown`) — the same shape as `design-ai pack --json`, minus `learningUsage`.
|
|
101
|
+
|
|
102
|
+
### `search(query, opts)`
|
|
103
|
+
|
|
104
|
+
Search the local design-ai markdown corpus.
|
|
105
|
+
|
|
106
|
+
```js
|
|
107
|
+
search(query: string, opts?: { dir?: string, limit?: number, ranked?: boolean }): SearchHit[]
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
- `dir` — restrict to one corpus directory: `knowledge`, `examples`, `skills`, `docs`, `agents`, or `commands`.
|
|
111
|
+
- `limit` — maximum hits, 1-500. Default: `20`.
|
|
112
|
+
- `ranked` — rank results with the deterministic lexical (BM25-style) scorer instead of returning raw line hits. Default: `false`.
|
|
113
|
+
|
|
114
|
+
Returns `SearchHit[]`. Unranked hits: `{ file, relPath, lineNumber, preview }`. Ranked hits: `{ file, relPath, score, matchedTokens, preview }` — the same shapes as `design-ai search --json` and `design-ai search --ranked --json`.
|
|
115
|
+
|
|
116
|
+
### `recall(query, opts)`
|
|
117
|
+
|
|
118
|
+
Recall brief-relevant shipped corpus knowledge and local learning-profile entries for a query — a combined read-only view.
|
|
119
|
+
|
|
120
|
+
```js
|
|
121
|
+
recall(query: string, opts?: { limit?: number, category?: string }): { corpus: object, learning: object }
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
- `limit` — applies to both the corpus and learning lists, 1-20. Default: `5`.
|
|
125
|
+
- `category` — scopes only the learning list.
|
|
126
|
+
|
|
127
|
+
Returns `{ corpus: { candidateCount, selectedCount, selected }, learning: { mode, candidateCount, selectedCount, selected } }` — the same shape as `design-ai learn --recall --json`.
|
|
128
|
+
|
|
129
|
+
### `check(artifact, opts)`
|
|
130
|
+
|
|
131
|
+
Check a generated design Markdown artifact for grounding, accessibility, responsive, unresolved-marker, and route-specific requirements.
|
|
132
|
+
|
|
133
|
+
```js
|
|
134
|
+
check(artifact: string, opts?: { routeId?: string, strict?: boolean }): CheckReport
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
- `routeId` — add route-specific checks (e.g. `component-spec`, `palette-from-brand`). Must be a known route id.
|
|
138
|
+
- `strict` — accepted for CLI-flag parity but has no effect on the returned report; the SDK never sets a process exit code. Inspect `report.status` yourself.
|
|
139
|
+
|
|
140
|
+
Returns a `CheckReport` object (`filePath`, `status`, `passes`, `warnings`, `failures`, `total`, `score`, `results`, plus `routeId` when passed) — the same shape as `design-ai check --json` (minus any `--learn` capture, which Phase A does not support).
|
|
141
|
+
|
|
142
|
+
### `routes()`
|
|
143
|
+
|
|
144
|
+
List the full route catalog (every route id with its static metadata), independent of any brief.
|
|
145
|
+
|
|
146
|
+
```js
|
|
147
|
+
routes(): { version: string, routes: RouteResult[] }
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Returns the same shape as `design-ai route --list --json`.
|
|
151
|
+
|
|
152
|
+
### `version()`
|
|
153
|
+
|
|
154
|
+
Report the CLI package version and the plugin/corpus version.
|
|
155
|
+
|
|
156
|
+
```js
|
|
157
|
+
version(): { cli: string, corpus: string }
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Stability contract
|
|
161
|
+
|
|
162
|
+
`@design-ai/cli/sdk` is **semver-stable**:
|
|
163
|
+
|
|
164
|
+
- Additive changes (new optional fields, new opt-in options) are minor version bumps.
|
|
165
|
+
- Signature or return-shape changes are major version bumps.
|
|
166
|
+
- The 8 read-only function exports (`route`, `prompt`, `pack`, `search`, `recall`, `check`, `routes`, `version`) plus the frozen `learn` namespace object (`learn.remember`, `learn.feedback`, `learn.captureFromCheck`) and their return-shape key sets are pinned by an SDK contract test (`cli/sdk/index.test.mjs`) — this is the semver anchor. If a name or a top-level key drifts unintentionally, that test fails.
|
|
167
|
+
- `cli/lib/*` remains internal and unstable. Only `@design-ai/cli/sdk` is a supported import path.
|
|
168
|
+
- Determinism: the same inputs always produce the same outputs, with no randomness or time-dependence added by the adapter layer.
|
|
169
|
+
|
|
170
|
+
## Phase B — local writes
|
|
171
|
+
|
|
172
|
+
Phase A (above) is read-only by design: the 8 verbs never write a file. Phase B adds a single namespace export, `learn`, grouping the three explicit, opt-in **LOCAL-WRITE** verbs. This is the only place the SDK writes files — every Phase A verb stays read-only and unchanged.
|
|
173
|
+
|
|
174
|
+
```js
|
|
175
|
+
import { learn } from "@design-ai/cli/sdk";
|
|
176
|
+
|
|
177
|
+
learn.remember(text, opts?) // opts: { category?: string }
|
|
178
|
+
learn.feedback(text, opts?) // opts: { outcome?: string, category?: string }
|
|
179
|
+
learn.captureFromCheck(artifact, opts?) // opts: { routeId?: string }
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
**Write boundary rationale:** an SDK consumer should never be surprised by a file write. Phase A's 8 verbs are safe to call from any context — a build script, a read path, a CI check — with zero side effects. `learn.*` is the deliberate, narrow exception: three verbs, one destination, no ambiguity about what gets written or where.
|
|
183
|
+
|
|
184
|
+
All three verbs write **only** the local learning profile — `DESIGN_AI_LEARNING_FILE` if set, otherwise the CLI's `defaultLearningFile()` default path — and never touch the network. There is no `filePath` option and no `now`/timestamp option on any of them: target a specific profile the same way the CLI does, by setting the `DESIGN_AI_LEARNING_FILE` environment variable before calling. The underlying library functions supply their own defaults (the env var and `new Date()`).
|
|
185
|
+
|
|
186
|
+
### `learn.remember(text, opts)`
|
|
187
|
+
|
|
188
|
+
Record a local learning-profile preference. Adapter over `rememberLearning` from `cli/lib/learn-profile.mjs`.
|
|
189
|
+
|
|
190
|
+
```js
|
|
191
|
+
learn.remember(text: string, opts?: { category?: string }): RememberResult
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
- `text` — required, non-empty string.
|
|
195
|
+
- `category` — one of the learning-profile categories (e.g. `preference`, `brand`, `workflow`, `constraint`, `accessibility`, `korean`, `other`); the library normalizes and validates it. Default: `"preference"`.
|
|
196
|
+
|
|
197
|
+
Returns a `RememberResult`: `{ file, entry, profile }`, where `entry` is `{ id, category, text, source, createdAt }` (`source` is always `"sdk"`) and `profile` is the full updated learning profile `{ version, updatedAt, entries }`.
|
|
198
|
+
|
|
199
|
+
### `learn.feedback(text, opts)`
|
|
200
|
+
|
|
201
|
+
Record feedback (keep/avoid/improve) as a local learning-profile entry. Adapter over `recordLearningFeedback` from `cli/lib/learn-profile.mjs`.
|
|
202
|
+
|
|
203
|
+
```js
|
|
204
|
+
learn.feedback(text: string, opts?: { outcome?: string, category?: string }): RememberResult
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
- `text` — required, non-empty string.
|
|
208
|
+
- `outcome` — any string; the library normalizes it (`normalizeFeedbackOutcome`) to one of `keep` / `avoid` / `improve` and folds it into both the stored entry's `text` (e.g. `"Avoid in future outputs: …"`) and its `source` (e.g. `"feedback:avoid"`). Default: `"improve"`.
|
|
209
|
+
- `category` — same category enum as `remember`. Default: `"workflow"`.
|
|
210
|
+
|
|
211
|
+
Returns the same `RememberResult` shape as `learn.remember`.
|
|
212
|
+
|
|
213
|
+
### `learn.captureFromCheck(artifact, opts)`
|
|
214
|
+
|
|
215
|
+
Check a Markdown artifact, then capture its non-pass results as local learning-profile entries — the SDK equivalent of the CLI's `check --learn --yes`. Adapter over `checkArtifactContent` + `buildCheckLearningCapture` from `cli/lib/check.mjs`.
|
|
216
|
+
|
|
217
|
+
```js
|
|
218
|
+
learn.captureFromCheck(artifact: string, opts?: { routeId?: string }): CaptureResult
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
- `artifact` — required, non-empty Markdown string.
|
|
222
|
+
- `routeId` — add route-specific check requirements before capturing; must be a known route id (validated with the same `assertKnownRouteId` helper the read-only `check` adapter uses).
|
|
223
|
+
|
|
224
|
+
Returns a `CaptureResult` — the `captureLearningEntries` return shape:
|
|
225
|
+
|
|
226
|
+
```
|
|
227
|
+
{
|
|
228
|
+
file: string, // the learning profile file written to
|
|
229
|
+
dryRun: false, // always false; captureFromCheck always applies
|
|
230
|
+
applied: true, // always true when dryRun is false
|
|
231
|
+
source: string, // "check:<routeId>" or "check:artifact"
|
|
232
|
+
candidateCount: number, // non-pass check results considered
|
|
233
|
+
addedCount: number, // entries newly written
|
|
234
|
+
skippedCount: number, // entries skipped as duplicates of existing entries
|
|
235
|
+
count: number, // total entries in the profile after this call
|
|
236
|
+
entries: LearningProfileEntry[], // the entries that were added
|
|
237
|
+
skipped: Array<{ category, text, source, reason }>, // reason is "duplicate-entry-text"
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Calling `captureFromCheck` twice with the same artifact adds nothing the second time — every candidate is skipped with `reason: "duplicate-entry-text"`.
|
|
242
|
+
|
|
243
|
+
### Validation
|
|
244
|
+
|
|
245
|
+
All three verbs use the same `cli/sdk/validate.mjs` helpers as Phase A: `requireNonEmptyString` for `text`/`artifact`, `requireOptions` for the options bag, and `optionalString` for string options. `routeId` is validated with `assertKnownRouteId`, exactly like the read-only `check` adapter. Bad input throws a plain `Error` with a clear message — no process exit code, no silent fallback.
|
|
246
|
+
|
|
247
|
+
### What stays unchanged
|
|
248
|
+
|
|
249
|
+
`check()` (the Phase A read-only verb) is untouched: it still never writes, and it has no capture option. Capture is exclusively reached through `learn.captureFromCheck`, which is a separate, explicitly-named write verb — this keeps the read/write boundary visible at the call site rather than hidden behind an option flag on a read-only function.
|
package/docs/external-status.md
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
# External Publication Status
|
|
2
2
|
|
|
3
|
-
> Checked: 2026-07-
|
|
3
|
+
> Checked: 2026-07-04
|
|
4
4
|
> Scope: npm registry, GitHub Pages, Homebrew tap, VS Code Marketplace, Claude/Codex MCP
|
|
5
5
|
|
|
6
6
|
## Summary
|
|
7
7
|
|
|
8
|
-
npm is publicly published at `@design-ai/cli@4.
|
|
8
|
+
npm is publicly published at `@design-ai/cli@4.59.0` (npm dist-tag `latest` = `4.59.0`); publish run from tag `v4.59.0` succeeded with provenance and all pre-publish gates green. This release adds the read-only Agent SDK surface (`@design-ai/cli/sdk`, eight adapter verbs) — additive only, so CLI output is unchanged. The live `npm run registry:smoke` passes cleanly against published `@design-ai/cli@4.59.0` ("Registry smoke passed"), covering the retrieval surfaces (index/ranked/embeddings/recall) and route enrichment. GitHub Release `v4.59.0` is published, and the Homebrew tap formula points at the `v4.59.0` release source tarball with a verified SHA-256. The VS Code extension `sungjin.design-ai-vscode` remains published at `0.4.1`. GitHub Pages docs are publicly reachable.
|
|
9
9
|
|
|
10
10
|
## Results
|
|
11
11
|
|
|
12
12
|
| Surface | Checked target | Result | Evidence |
|
|
13
13
|
|---|---|---|---|
|
|
14
|
-
| npm registry | `@design-ai/cli` | Published latest is `4.
|
|
14
|
+
| npm registry | `@design-ai/cli` | Published latest is `4.59.0` (tag `v4.59.0`, provenance). Pre-publish packed-tarball smoke (incl. SDK import) and live `npm run registry:smoke` both pass for `@design-ai/cli@4.59.0`. | `npm view @design-ai/cli version` → `4.59.0`; live registry smoke "Registry smoke passed" |
|
|
15
15
|
| GitHub Pages | `https://sungjin9288.github.io/design-ai/` | Published and reachable: HTTP `200`, design-ai MkDocs page rendered | `evidence/cli-logs/github-pages-status.log` |
|
|
16
|
-
| Homebrew tap | `Formula/design-ai.rb` | Formula pinned to `v4.
|
|
16
|
+
| Homebrew tap | `Formula/design-ai.rb` | Formula pinned to `v4.59.0` release source tarball with SHA-256 `e711203fb96fee5f6e7f25d809bc42108553bc8108ddf3e8cd181f0a76f6e0f5` (recomputed from the published tag tarball) | `Formula/design-ai.rb` |
|
|
17
17
|
| VS Code Marketplace | `sungjin.design-ai-vscode` | Published: run `28431571256` published `v0.4.1`, and the Marketplace Gallery API returned visible version `0.4.1` on 2026-07-02. | `evidence/cli-logs/vscode-marketplace-status.log`, `evidence/cli-logs/vscode-marketplace-secret-status.log`, `evidence/cli-logs/vscode-extension-vsce-package.log`, `evidence/cli-logs/vscode-publish-workflow-status.log` |
|
|
18
|
-
| GitHub Release | `v4.
|
|
19
|
-
| MCP server | `@design-ai/cli@4.
|
|
18
|
+
| GitHub Release | `v4.59.0` | Published for tag `v4.59.0` at commit `9f53345` | `gh release view v4.59.0` |
|
|
19
|
+
| MCP server | `@design-ai/cli@4.59.0` / local clone | Public npm `design-ai-mcp` responds to initialize and tools/list with 10 tools; local Codex and Claude Code both report `design-ai` MCP as configured and connected. | `evidence/cli-logs/npm-registry-smoke.log`, `evidence/cli-logs/design-ai-mcp-client-status.log` |
|
|
20
20
|
|
|
21
21
|
## Interpretation
|
|
22
22
|
|
package/package.json
CHANGED
|
@@ -1,17 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@design-ai/cli",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.60.0",
|
|
4
4
|
"description": "Senior product designer for any AI coding agent. Installs design-ai (20 skills, 17 commands, 4 agents) into Claude Code globally. Korean market depth plus website improvement control tower.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"design-ai": "cli/bin/design-ai.mjs",
|
|
7
7
|
"design-ai-mcp": "cli/bin/design-ai-mcp.mjs"
|
|
8
8
|
},
|
|
9
9
|
"type": "module",
|
|
10
|
+
"exports": {
|
|
11
|
+
"./sdk": {
|
|
12
|
+
"types": "./cli/sdk/index.d.ts",
|
|
13
|
+
"default": "./cli/sdk/index.mjs"
|
|
14
|
+
},
|
|
15
|
+
"./package.json": "./package.json"
|
|
16
|
+
},
|
|
10
17
|
"engines": {
|
|
11
18
|
"node": ">=18"
|
|
12
19
|
},
|
|
13
20
|
"scripts": {
|
|
14
|
-
"test": "node --test cli/lib/*.test.mjs",
|
|
21
|
+
"test": "node --test cli/lib/*.test.mjs cli/sdk/*.test.mjs",
|
|
15
22
|
"audit": "python3 -B tools/audit/run-all.py",
|
|
16
23
|
"audit:strict": "python3 -B tools/audit/run-all.py --strict",
|
|
17
24
|
"audit:runner:self-test": "python3 -B tools/audit/run-all.py --self-test",
|
|
@@ -763,6 +763,85 @@ def assert_version_json_smoke(cmd: list[str], *, env: dict[str, str], cwd: Path
|
|
|
763
763
|
assert_version_json(result.stdout, context=context, cmd=cmd)
|
|
764
764
|
|
|
765
765
|
|
|
766
|
+
# Agent SDK packed-tarball smoke (docs/AGENT-SDK.md, docs/SDK.md): imports
|
|
767
|
+
# `@design-ai/cli/sdk` from the installed package (proving the package.json
|
|
768
|
+
# "exports" map resolves) and exercises route/search(ranked)/recall, asserting
|
|
769
|
+
# each call is deterministic (same input -> same JSON-serialized output) and
|
|
770
|
+
# that Phase A's read-only contract holds (prompt/pack never include
|
|
771
|
+
# learningUsage). Also exercises Phase B's `learn.remember` against a temp
|
|
772
|
+
# DESIGN_AI_LEARNING_FILE and asserts it actually wrote the profile. This is a
|
|
773
|
+
# Node script, not a design-ai CLI invocation, so it is run directly rather
|
|
774
|
+
# than through npm_exec_cmd/design-ai bin helpers.
|
|
775
|
+
SDK_SMOKE_SCRIPT = """
|
|
776
|
+
import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
777
|
+
import { tmpdir } from "node:os";
|
|
778
|
+
import path from "node:path";
|
|
779
|
+
import { check, learn, pack, prompt, recall, route, routes, search, version } from "@design-ai/cli/sdk";
|
|
780
|
+
|
|
781
|
+
const brief = "Spec a Button component API with variants, props, and keyboard accessibility";
|
|
782
|
+
|
|
783
|
+
const r1 = route(brief, { limit: 1 });
|
|
784
|
+
const r2 = route(brief, { limit: 1 });
|
|
785
|
+
if (JSON.stringify(r1) !== JSON.stringify(r2)) throw new Error("route() not deterministic");
|
|
786
|
+
if (r1[0].id !== "component-spec") throw new Error(`unexpected route id: ${r1[0].id}`);
|
|
787
|
+
|
|
788
|
+
const s1 = search("accessibility", { ranked: true, limit: 3 });
|
|
789
|
+
const s2 = search("accessibility", { ranked: true, limit: 3 });
|
|
790
|
+
if (JSON.stringify(s1) !== JSON.stringify(s2)) throw new Error("search() not deterministic");
|
|
791
|
+
if (s1.length === 0) throw new Error("search() returned no hits");
|
|
792
|
+
|
|
793
|
+
const rec1 = recall("keyboard accessibility button", { limit: 2 });
|
|
794
|
+
const rec2 = recall("keyboard accessibility button", { limit: 2 });
|
|
795
|
+
if (JSON.stringify(rec1) !== JSON.stringify(rec2)) throw new Error("recall() not deterministic");
|
|
796
|
+
if (!rec1.corpus || !rec1.learning) throw new Error("recall() missing corpus/learning");
|
|
797
|
+
|
|
798
|
+
const v = version();
|
|
799
|
+
if (typeof v.cli !== "string" || typeof v.corpus !== "string") throw new Error("version() bad shape");
|
|
800
|
+
|
|
801
|
+
const rc = routes();
|
|
802
|
+
if (!Array.isArray(rc.routes) || rc.routes.length === 0) throw new Error("routes() bad shape");
|
|
803
|
+
|
|
804
|
+
const p = prompt(brief, { routeId: "component-spec" });
|
|
805
|
+
if (Object.hasOwn(p, "learningUsage")) throw new Error("prompt() must not include learningUsage in Phase A");
|
|
806
|
+
|
|
807
|
+
const pk = pack(brief, { routeId: "component-spec", maxBytes: 20000 });
|
|
808
|
+
if (Object.hasOwn(pk, "learningUsage")) throw new Error("pack() must not include learningUsage in Phase A");
|
|
809
|
+
|
|
810
|
+
const ck = check(
|
|
811
|
+
"# T\\n\\nAnatomy variants API contrast 4.5:1 keyboard focus screen reader aria- responsive mobile desktop.",
|
|
812
|
+
{ routeId: "component-spec" },
|
|
813
|
+
);
|
|
814
|
+
if (typeof ck.status !== "string") throw new Error("check() bad shape");
|
|
815
|
+
|
|
816
|
+
// Phase B: learn.remember must write only DESIGN_AI_LEARNING_FILE.
|
|
817
|
+
const smokeDir = mkdtempSync(path.join(tmpdir(), "design-ai-sdk-learn-smoke-"));
|
|
818
|
+
const learningFile = path.join(smokeDir, "learning.json");
|
|
819
|
+
process.env.DESIGN_AI_LEARNING_FILE = learningFile;
|
|
820
|
+
try {
|
|
821
|
+
const result = learn.remember("Package smoke: prefer 8px spacing grid.", { category: "preference" });
|
|
822
|
+
if (result.entry.source !== "sdk") throw new Error("learn.remember() entry.source must be 'sdk'");
|
|
823
|
+
if (!existsSync(learningFile)) throw new Error("learn.remember() did not write DESIGN_AI_LEARNING_FILE");
|
|
824
|
+
const onDisk = JSON.parse(readFileSync(learningFile, "utf8"));
|
|
825
|
+
if (!Array.isArray(onDisk.entries) || onDisk.entries.length !== 1) {
|
|
826
|
+
throw new Error("learn.remember() did not persist exactly one entry");
|
|
827
|
+
}
|
|
828
|
+
} finally {
|
|
829
|
+
rmSync(smokeDir, { recursive: true, force: true });
|
|
830
|
+
delete process.env.DESIGN_AI_LEARNING_FILE;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
console.log("design-ai sdk smoke passed");
|
|
834
|
+
""".strip()
|
|
835
|
+
|
|
836
|
+
|
|
837
|
+
def assert_sdk_smoke(*, cwd: Path, env: dict[str, str], context: str) -> None:
|
|
838
|
+
result = run_plain(["node", "--input-type=module", "-e", SDK_SMOKE_SCRIPT], cwd=cwd, env=env)
|
|
839
|
+
if result.returncode != 0:
|
|
840
|
+
raise SystemExit(f"{context} failed (exit {result.returncode})")
|
|
841
|
+
if "design-ai sdk smoke passed" not in result.stdout:
|
|
842
|
+
raise SystemExit(f"{context}: expected success marker missing from output")
|
|
843
|
+
|
|
844
|
+
|
|
766
845
|
def assert_workspace_json_smoke(cmd: list[str], *, env: dict[str, str], cwd: Path | None = None, context: str) -> None:
|
|
767
846
|
result = run_plain(cmd, cwd=cwd, env=env)
|
|
768
847
|
assert_workspace_json(result.stdout, context=context, cmd=cmd)
|
|
@@ -20239,6 +20318,11 @@ def smoke_tarball(tarball: Path) -> None:
|
|
|
20239
20318
|
env=smoke_env,
|
|
20240
20319
|
context="package smoke installed bin version JSON",
|
|
20241
20320
|
)
|
|
20321
|
+
assert_sdk_smoke(
|
|
20322
|
+
cwd=install_root,
|
|
20323
|
+
env=smoke_env,
|
|
20324
|
+
context="package smoke installed Agent SDK (@design-ai/cli/sdk)",
|
|
20325
|
+
)
|
|
20242
20326
|
assert_design_ai_mcp_protocol_smoke(
|
|
20243
20327
|
[str(mcp_bin_path)],
|
|
20244
20328
|
cwd=install_root,
|
|
@@ -4637,7 +4637,7 @@ def assert_learning_relevance_context(payload: dict[str, object], *, context: st
|
|
|
4637
4637
|
message="learning selection explanation should mark the relevant entry as a brief match",
|
|
4638
4638
|
)
|
|
4639
4639
|
require_registry_smoke(
|
|
4640
|
-
|
|
4640
|
+
type(selected_entry.get("score")) in (int, float) and selected_entry.get("score") > 0,
|
|
4641
4641
|
context=context,
|
|
4642
4642
|
cmd=cmd,
|
|
4643
4643
|
message="learning selection explanation should include a positive relevance score",
|
|
@@ -4759,7 +4759,7 @@ def assert_learning_query_json(
|
|
|
4759
4759
|
require_registry_smoke(
|
|
4760
4760
|
selected[0].get("id") == "learn-relevant"
|
|
4761
4761
|
and selected[0].get("reason") == "brief-match"
|
|
4762
|
-
and
|
|
4762
|
+
and type(selected[0].get("score")) in (int, float)
|
|
4763
4763
|
and selected[0].get("score") > 0,
|
|
4764
4764
|
context=context,
|
|
4765
4765
|
cmd=cmd,
|