@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.
@@ -0,0 +1,100 @@
1
+ // SDK namespace: learn.*. See docs/AGENT-SDK.md (Phase B) and docs/SDK.md.
2
+ //
3
+ // The ONLY writing verbs in the SDK. Phase A (route, prompt, pack, search,
4
+ // recall, check, routes, version) stays read-only and unchanged — this
5
+ // namespace is the explicit, opt-in write boundary: every verb here writes
6
+ // only the local learning profile (`DESIGN_AI_LEARNING_FILE` /
7
+ // `defaultLearningFile()`), never the network. Consumers target a specific
8
+ // profile file via the `DESIGN_AI_LEARNING_FILE` env var, exactly like the
9
+ // CLI — there is no `filePath` option and no `now`/timestamp option; the
10
+ // underlying cli/lib functions use their own defaults (the env var and
11
+ // `new Date()`).
12
+
13
+ import { checkArtifactContent, buildCheckLearningCapture } from "../lib/check.mjs";
14
+ import { rememberLearning, recordLearningFeedback } from "../lib/learn-profile.mjs";
15
+ import { assertKnownRouteId } from "../lib/route.mjs";
16
+ import { optionalString, requireNonEmptyString, requireOptions } from "./validate.mjs";
17
+
18
+ /**
19
+ * Record a local learning-profile preference. Writes only the local learning
20
+ * profile (`DESIGN_AI_LEARNING_FILE` / `defaultLearningFile()`); never the
21
+ * network. Pure adapter over `rememberLearning` from cli/lib/learn-profile.mjs.
22
+ *
23
+ * @param {string} text - Preference text to remember.
24
+ * @param {{category?: string}} [opts]
25
+ * @returns {{file: string, entry: object, profile: object}} RememberResult.
26
+ */
27
+ function remember(text, opts = {}) {
28
+ requireNonEmptyString(text, "text");
29
+ const options = requireOptions(opts, "learn.remember");
30
+ const category = optionalString(options.category, "category", "preference");
31
+
32
+ return rememberLearning({
33
+ text,
34
+ category,
35
+ source: "sdk",
36
+ });
37
+ }
38
+
39
+ /**
40
+ * Record feedback (keep/avoid/improve) as a local learning-profile entry.
41
+ * Writes only the local learning profile; never the network. Pure adapter
42
+ * over `recordLearningFeedback` from cli/lib/learn-profile.mjs.
43
+ *
44
+ * @param {string} text - Feedback text.
45
+ * @param {{outcome?: string, category?: string}} [opts]
46
+ * @returns {{file: string, entry: object, profile: object}} RememberResult.
47
+ */
48
+ function feedback(text, opts = {}) {
49
+ requireNonEmptyString(text, "text");
50
+ const options = requireOptions(opts, "learn.feedback");
51
+ // The lib normalizes the outcome itself (normalizeFeedbackOutcome); the SDK
52
+ // only validates that, if given, it is a string.
53
+ const outcome = optionalString(options.outcome, "outcome", "improve");
54
+ const category = optionalString(options.category, "category", "workflow");
55
+
56
+ return recordLearningFeedback({
57
+ text,
58
+ outcome,
59
+ category,
60
+ });
61
+ }
62
+
63
+ /**
64
+ * Check a Markdown artifact, then capture its non-pass results as local
65
+ * learning-profile entries. Writes only the local learning profile; never the
66
+ * network. Pure adapter over `checkArtifactContent` + `buildCheckLearningCapture`
67
+ * from cli/lib/check.mjs (the same path as the CLI's `check --learn --yes`).
68
+ *
69
+ * @param {string} artifact - Markdown artifact content to check and capture.
70
+ * @param {{routeId?: string}} [opts]
71
+ * @returns {object} CaptureResult — the `captureLearningEntries` return shape:
72
+ * { file, dryRun, applied, source, candidateCount, addedCount, skippedCount,
73
+ * count, entries, skipped }.
74
+ */
75
+ function captureFromCheck(artifact, opts = {}) {
76
+ requireNonEmptyString(artifact, "artifact");
77
+ const options = requireOptions(opts, "learn.captureFromCheck");
78
+
79
+ const routeId = optionalString(options.routeId, "routeId");
80
+ if (routeId) assertKnownRouteId(routeId, { allowEmpty: false });
81
+
82
+ const report = checkArtifactContent({
83
+ content: artifact,
84
+ filePath: "sdk",
85
+ routeId,
86
+ });
87
+
88
+ return buildCheckLearningCapture(report, { dryRun: false });
89
+ }
90
+
91
+ /**
92
+ * The Phase B local-write namespace. `learn.remember`, `learn.feedback`, and
93
+ * `learn.captureFromCheck` are the only SDK verbs that write files; all three
94
+ * write exclusively to the local learning profile. See docs/SDK.md.
95
+ */
96
+ export const learn = Object.freeze({
97
+ remember,
98
+ feedback,
99
+ captureFromCheck,
100
+ });
@@ -0,0 +1,55 @@
1
+ // SDK adapter: pack(brief, opts). See docs/AGENT-SDK.md.
2
+ //
3
+ // Phase A is read-only: like the prompt adapter, this never records the
4
+ // learning-usage sidecar, even when withLearning is requested.
5
+
6
+ import { DESIGN_AI_HOME, SYMLINK_PREFIX } from "../lib/paths.mjs";
7
+ import { buildPromptPack } from "../lib/pack.mjs";
8
+ import {
9
+ optionalBoolean,
10
+ optionalInteger,
11
+ optionalString,
12
+ requireNonEmptyString,
13
+ requireOptions,
14
+ } from "./validate.mjs";
15
+
16
+ const DEFAULT_MAX_BYTES = 120_000;
17
+
18
+ /**
19
+ * Build a ready-to-use prompt plus bounded context-file bundle from a task
20
+ * brief. Pure, read-only adapter over `buildPromptPack` from cli/lib/pack.mjs.
21
+ * Never records learning-usage, even with withLearning: true.
22
+ *
23
+ * @param {string} brief - Task brief text.
24
+ * @param {{routeId?: string, maxBytes?: number, withLearning?: boolean, learningCategory?: string, learningLimit?: number, withRecall?: boolean, recallLimit?: number}} [opts]
25
+ * @returns {object} Pack — the same shape as `design-ai pack --json` (minus learningUsage).
26
+ */
27
+ export function pack(brief, opts = {}) {
28
+ requireNonEmptyString(brief, "brief");
29
+ const options = requireOptions(opts, "pack");
30
+
31
+ const routeId = optionalString(options.routeId, "routeId");
32
+ const maxBytes = optionalInteger(options.maxBytes, "maxBytes", {
33
+ fallback: DEFAULT_MAX_BYTES,
34
+ min: 1000,
35
+ max: 1_000_000,
36
+ });
37
+ const withLearning = optionalBoolean(options.withLearning, "withLearning", false);
38
+ const learningCategory = optionalString(options.learningCategory, "learningCategory");
39
+ const learningLimit = optionalInteger(options.learningLimit, "learningLimit", { fallback: 0, min: 1, max: 100, zeroMeansUnset: true });
40
+ const withRecall = optionalBoolean(options.withRecall, "withRecall", false);
41
+ const recallLimit = optionalInteger(options.recallLimit, "recallLimit", { fallback: 0, min: 1, max: 20, zeroMeansUnset: true });
42
+
43
+ return buildPromptPack({
44
+ brief,
45
+ sourceRoot: DESIGN_AI_HOME,
46
+ prefix: SYMLINK_PREFIX,
47
+ maxBytes,
48
+ routeId,
49
+ withLearning,
50
+ learningCategory,
51
+ learningLimit,
52
+ withRecall,
53
+ recallLimit,
54
+ });
55
+ }
@@ -0,0 +1,49 @@
1
+ // SDK adapter: prompt(brief, opts). See docs/AGENT-SDK.md.
2
+ //
3
+ // Phase A is read-only: unlike the CLI's `--with-learning` (which records a
4
+ // local usage sidecar entry via recordLearningUsage), the SDK adapter never
5
+ // writes the learning-usage sidecar, even when withLearning is requested. It
6
+ // only reads the local learning profile to build the learning context.
7
+
8
+ import { DESIGN_AI_HOME, SYMLINK_PREFIX } from "../lib/paths.mjs";
9
+ import { buildPromptPlan } from "../lib/prompt.mjs";
10
+ import {
11
+ optionalBoolean,
12
+ optionalInteger,
13
+ optionalString,
14
+ requireNonEmptyString,
15
+ requireOptions,
16
+ } from "./validate.mjs";
17
+
18
+ /**
19
+ * Build a ready-to-use agent prompt plan from a task brief. Pure, read-only
20
+ * adapter over `buildPromptPlan` from cli/lib/prompt.mjs. Never records
21
+ * learning-usage, even with withLearning: true (Phase A is read-only).
22
+ *
23
+ * @param {string} brief - Task brief text.
24
+ * @param {{routeId?: string, withLearning?: boolean, learningCategory?: string, learningLimit?: number, withRecall?: boolean, recallLimit?: number}} [opts]
25
+ * @returns {object|null} PromptPlan — the same shape as `design-ai prompt --json` (minus learningUsage).
26
+ */
27
+ export function prompt(brief, opts = {}) {
28
+ requireNonEmptyString(brief, "brief");
29
+ const options = requireOptions(opts, "prompt");
30
+
31
+ const routeId = optionalString(options.routeId, "routeId");
32
+ const withLearning = optionalBoolean(options.withLearning, "withLearning", false);
33
+ const learningCategory = optionalString(options.learningCategory, "learningCategory");
34
+ const learningLimit = optionalInteger(options.learningLimit, "learningLimit", { fallback: 0, min: 1, max: 100, zeroMeansUnset: true });
35
+ const withRecall = optionalBoolean(options.withRecall, "withRecall", false);
36
+ const recallLimit = optionalInteger(options.recallLimit, "recallLimit", { fallback: 0, min: 1, max: 20, zeroMeansUnset: true });
37
+
38
+ return buildPromptPlan({
39
+ brief,
40
+ sourceRoot: DESIGN_AI_HOME,
41
+ prefix: SYMLINK_PREFIX,
42
+ routeId,
43
+ withLearning,
44
+ learningCategory,
45
+ learningLimit,
46
+ withRecall,
47
+ recallLimit,
48
+ });
49
+ }
@@ -0,0 +1,39 @@
1
+ // SDK adapter: recall(query, opts). See docs/AGENT-SDK.md.
2
+ //
3
+ // Combined recall view: brief-relevant shipped corpus knowledge (`corpus`) plus
4
+ // brief-relevant local learning-profile entries (`learning`), both ranked by the
5
+ // same shipped deterministic lexical scorer used by `search --ranked`. Read-only:
6
+ // reads the local corpus and `DESIGN_AI_LEARNING_FILE` learning profile, exactly
7
+ // as `design-ai learn --recall` does, and never writes either.
8
+
9
+ import { DESIGN_AI_HOME } from "../lib/paths.mjs";
10
+ import { buildLearnRecall, DEFAULT_RECALL_LIMIT } from "../lib/recall.mjs";
11
+ import { optionalInteger, optionalString, requireNonEmptyString, requireOptions } from "./validate.mjs";
12
+
13
+ /**
14
+ * Recall brief-relevant corpus knowledge and local learning-profile entries for
15
+ * a query. Pure, read-only adapter over `buildLearnRecall` from cli/lib/recall.mjs.
16
+ *
17
+ * @param {string} query - Recall query text.
18
+ * @param {{limit?: number, category?: string}} [opts]
19
+ * @returns {{corpus: object, learning: object}} combined recall view.
20
+ */
21
+ export function recall(query, opts = {}) {
22
+ requireNonEmptyString(query, "query");
23
+ const options = requireOptions(opts, "recall");
24
+
25
+ const limit = optionalInteger(options.limit, "limit", { fallback: DEFAULT_RECALL_LIMIT, min: 1, max: 20 });
26
+ const category = optionalString(options.category, "category");
27
+
28
+ const result = buildLearnRecall({
29
+ query,
30
+ limit,
31
+ category,
32
+ designAiPath: DESIGN_AI_HOME,
33
+ });
34
+
35
+ return {
36
+ corpus: result.corpus,
37
+ learning: result.learning,
38
+ };
39
+ }
@@ -0,0 +1,45 @@
1
+ // SDK adapter: route(brief, opts) and routes(). See docs/AGENT-SDK.md.
2
+
3
+ import { DESIGN_AI_HOME } from "../lib/paths.mjs";
4
+ import { readRouteManifestVersion, routeBrief, routeCatalog } from "../lib/route.mjs";
5
+ import {
6
+ optionalBoolean,
7
+ optionalInteger,
8
+ requireNonEmptyString,
9
+ requireOptions,
10
+ } from "./validate.mjs";
11
+
12
+ /**
13
+ * Recommend the best design-ai route(s), commands, skills, and knowledge files
14
+ * for a task brief. Pure, read-only adapter over `routeBrief` from cli/lib/route.mjs.
15
+ *
16
+ * @param {string} brief - Task brief text.
17
+ * @param {{limit?: number, explain?: boolean}} [opts]
18
+ * @returns {Array<object>} RouteResult[] — the same shape as `design-ai route --json`'s `routes` array.
19
+ */
20
+ export function route(brief, opts = {}) {
21
+ requireNonEmptyString(brief, "brief");
22
+ const options = requireOptions(opts, "route");
23
+ const limit = optionalInteger(options.limit, "limit", { fallback: 3, min: 1, max: 10 });
24
+ const explain = optionalBoolean(options.explain, "explain", false);
25
+
26
+ return routeBrief({
27
+ brief,
28
+ sourceRoot: DESIGN_AI_HOME,
29
+ limit,
30
+ explain,
31
+ });
32
+ }
33
+
34
+ /**
35
+ * List the full route catalog (all route ids with their static metadata),
36
+ * independent of any brief. Pure, read-only adapter over `routeCatalog`.
37
+ *
38
+ * @returns {{version: string, routes: Array<object>}} RouteCatalog
39
+ */
40
+ export function routes() {
41
+ return {
42
+ version: readRouteManifestVersion(DESIGN_AI_HOME),
43
+ routes: routeCatalog({ sourceRoot: DESIGN_AI_HOME }),
44
+ };
45
+ }
@@ -0,0 +1,55 @@
1
+ // SDK adapter: search(query, opts). See docs/AGENT-SDK.md.
2
+
3
+ import { DESIGN_AI_HOME } from "../lib/paths.mjs";
4
+ import { rankedSearchCorpus } from "../lib/search-ranked.mjs";
5
+ import { DEFAULT_SEARCH_DIRS, searchCorpus } from "../lib/search.mjs";
6
+ import {
7
+ optionalBoolean,
8
+ optionalInteger,
9
+ optionalString,
10
+ requireNonEmptyString,
11
+ requireOptions,
12
+ } from "./validate.mjs";
13
+
14
+ function resolveDirs(dirName) {
15
+ if (!dirName) return DEFAULT_SEARCH_DIRS;
16
+ if (!DEFAULT_SEARCH_DIRS.includes(dirName)) {
17
+ throw new Error(`dir must be one of: ${DEFAULT_SEARCH_DIRS.join(", ")}`);
18
+ }
19
+ return [dirName];
20
+ }
21
+
22
+ /**
23
+ * Search the local design-ai markdown corpus. Pure, read-only adapter over
24
+ * `searchCorpus` / `rankedSearchCorpus` from cli/lib/search.mjs and
25
+ * cli/lib/search-ranked.mjs.
26
+ *
27
+ * @param {string} query - Search query text.
28
+ * @param {{dir?: string, limit?: number, ranked?: boolean}} [opts]
29
+ * @returns {Array<object>} SearchHit[] — line hits by default, ranked (score + matchedTokens) hits when `ranked: true`.
30
+ */
31
+ export function search(query, opts = {}) {
32
+ requireNonEmptyString(query, "query");
33
+ const options = requireOptions(opts, "search");
34
+
35
+ const dirName = optionalString(options.dir, "dir");
36
+ const dirs = resolveDirs(dirName);
37
+ const limit = optionalInteger(options.limit, "limit", { fallback: 20, min: 1, max: 500 });
38
+ const ranked = optionalBoolean(options.ranked, "ranked", false);
39
+
40
+ if (ranked) {
41
+ return rankedSearchCorpus({
42
+ query,
43
+ designAiPath: DESIGN_AI_HOME,
44
+ dirs,
45
+ limit,
46
+ }).hits;
47
+ }
48
+
49
+ return searchCorpus({
50
+ query,
51
+ designAiPath: DESIGN_AI_HOME,
52
+ dirs,
53
+ limit,
54
+ });
55
+ }
@@ -0,0 +1,48 @@
1
+ // Input validation helpers shared by the SDK adapters (cli/sdk/*.mjs).
2
+ // Fail fast with a clear Error on bad input, per docs/AGENT-SDK.md Phase A.
3
+
4
+ export function requireNonEmptyString(value, label) {
5
+ if (typeof value !== "string" || value.trim() === "") {
6
+ throw new Error(`${label} must be a non-empty string`);
7
+ }
8
+ return value;
9
+ }
10
+
11
+ export function optionalString(value, label, fallback = "") {
12
+ if (value === undefined || value === null) return fallback;
13
+ if (typeof value !== "string") {
14
+ throw new Error(`${label} must be a string`);
15
+ }
16
+ return value;
17
+ }
18
+
19
+ export function optionalBoolean(value, label, fallback = false) {
20
+ if (value === undefined || value === null) return fallback;
21
+ if (typeof value !== "boolean") {
22
+ throw new Error(`${label} must be a boolean`);
23
+ }
24
+ return value;
25
+ }
26
+
27
+ // `zeroMeansUnset: true` is for options where the underlying cli/lib function
28
+ // treats 0 itself as "not set, use the library default" (e.g. learningLimit,
29
+ // recallLimit in prompt.mjs/pack.mjs use `learningLimit || 12`). In that case
30
+ // an explicit 0 is accepted and passed through unchecked against min/max,
31
+ // exactly like omitting the option.
32
+ export function optionalInteger(value, label, { fallback = 0, min = -Infinity, max = Infinity, zeroMeansUnset = false } = {}) {
33
+ if (value === undefined || value === null) return fallback;
34
+ if (zeroMeansUnset && value === 0) return 0;
35
+ const num = Number(value);
36
+ if (!Number.isInteger(num) || num < min || num > max) {
37
+ throw new Error(`${label} must be an integer from ${min} to ${max}`);
38
+ }
39
+ return num;
40
+ }
41
+
42
+ export function requireOptions(opts, label) {
43
+ if (opts === undefined || opts === null) return {};
44
+ if (typeof opts !== "object" || Array.isArray(opts)) {
45
+ throw new Error(`${label} options must be a plain object`);
46
+ }
47
+ return opts;
48
+ }
@@ -0,0 +1,17 @@
1
+ // SDK adapter: version(). See docs/AGENT-SDK.md.
2
+
3
+ import { collectVersionReport } from "../commands/version.mjs";
4
+
5
+ /**
6
+ * Report the CLI package version and the plugin/corpus version. Pure,
7
+ * read-only adapter over `collectVersionReport` from cli/commands/version.mjs.
8
+ *
9
+ * @returns {{cli: string, corpus: string}} version summary.
10
+ */
11
+ export function version() {
12
+ const report = collectVersionReport();
13
+ return {
14
+ cli: report.versions.cli,
15
+ corpus: report.versions.plugin,
16
+ };
17
+ }
@@ -0,0 +1,98 @@
1
+ # Agent SDK design
2
+
3
+ > Status: draft / planning — 2026-07-04
4
+
5
+ This document opens the Agent SDK phase — the fast-follow chosen in [NEXT-SURFACE-DECISION.md](NEXT-SURFACE-DECISION.md), now unblocked because the CLI recall interface has been stable across two releases (`--with-recall` / `learn --recall` shipped in v4.57.0 and stayed stable through v4.58.0's `route --explain` enrichment).
6
+
7
+ Nothing here is shipped until a phase below is implemented and release-gated. Until then, the only supported programmatic entry to design-ai is the CLI and the MCP server.
8
+
9
+ ## Goal
10
+
11
+ Let 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. The SDK is a thin, documented, semver-stable adapter over the same `cli/lib` functions the CLI and MCP already call, so a capability that ships in the CLI is instantly available to an SDK consumer.
12
+
13
+ The decision record scored this surface highest on leverage and learning synergy precisely because it reuses the shared library verbatim: `route`, `prompt`, `pack`, `search --ranked`, `recall`, and `check` are already pure functions in `cli/lib`.
14
+
15
+ ## Non-goals
16
+
17
+ - No new capabilities. The SDK exposes existing CLI/MCP behavior; it does not add design logic.
18
+ - No runtime dependencies, no network calls, no telemetry — same posture as the CLI.
19
+ - No exposure of the entire `cli/lib` internal surface. The SDK is a curated, stable subset; `cli/lib/*` stays internal and free to refactor.
20
+ - No new process model — the SDK runs in the caller's process, deterministic and synchronous where the underlying functions are.
21
+ - No model inference or fine-tuning (unchanged product stance).
22
+
23
+ ## The stable surface
24
+
25
+ A single curated entry (`cli/sdk/index.mjs`) re-exports a small set of adapter functions with **their own stable signatures**, independent of the internal `cli/lib` shapes. Internal functions may be renamed or refactored; the SDK adapter absorbs that so the public API stays put.
26
+
27
+ Proposed surface (Phase A — read-only verbs):
28
+
29
+ ```js
30
+ import { route, prompt, pack, search, recall, check, routes, version } from "@design-ai/cli/sdk";
31
+
32
+ route(brief, { limit = 3, explain = false }) // → RouteResult[]
33
+ prompt(brief, { routeId, withLearning, learningCategory, learningLimit, withRecall, recallLimit }) // → PromptPlan
34
+ pack(brief, { routeId, maxBytes, withLearning, learningCategory, learningLimit, withRecall, recallLimit }) // → Pack
35
+ search(query, { dir, limit = 20, ranked = false }) // → SearchHit[] (ranked: BM25 hits with scores)
36
+ recall(query, { limit = 5, category = "" }) // → { corpus, learning } (combined recall view)
37
+ check(artifact, { routeId = "", strict = false }) // → CheckReport
38
+ routes() // → RouteCatalog
39
+ version() // → { cli, corpus }
40
+ ```
41
+
42
+ Every function is a pure adapter: it validates its inputs, calls the corresponding `cli/lib` function with the resolved package root, and returns a plain JSON-serializable object — the same shape the CLI's `--json` mode emits, which is already a contract covered by smoke tests. No function writes files or reads the network in Phase A. `recall` and the `withRecall`/`withLearning` options read the local corpus and the user's local `learning.json` (via `DESIGN_AI_LEARNING_FILE`) exactly as the CLI does; nothing else is read.
43
+
44
+ ## Packaging
45
+
46
+ Add an `exports` map to `package.json`:
47
+
48
+ ```json
49
+ "exports": {
50
+ "./sdk": "./cli/sdk/index.mjs",
51
+ "./package.json": "./package.json"
52
+ }
53
+ ```
54
+
55
+ - `@design-ai/cli/sdk` is the one public import path. The bare package (`.`) intentionally stays unexported so `import "@design-ai/cli"` does not accidentally couple callers to internals; the CLI is invoked as a bin, the SDK via the explicit subpath.
56
+ - `cli/` is already in `files`, so `cli/sdk/` ships with the package with no `files` change.
57
+ - The `package-contents` audit and `package:smoke` must cover the new entry: a packed-tarball smoke that `import`s `@design-ai/cli/sdk` and calls `route`/`search --ranked`/`recall` and asserts deterministic output.
58
+
59
+ ## Stability contract
60
+
61
+ - The `@design-ai/cli/sdk` surface is **semver-stable**: additive changes are minor, signature/return changes are major. This is stated in the SDK reference doc and enforced by SDK contract tests that pin the exported names and return-shape keys.
62
+ - `cli/lib/*` remains **internal and unstable** — importing `@design-ai/cli/cli/lib/...` is unsupported. The adapter layer is the seam that lets internals refactor (as they did repeatedly during the retrieval work) without breaking SDK consumers.
63
+ - Determinism: same inputs → same outputs, matching the CLI. The SDK adds no randomness or time-dependence.
64
+
65
+ ## Phased plan
66
+
67
+ ### Phase A — read-only SDK core
68
+
69
+ The verbs above (`route`, `prompt`, `pack`, `search`, `recall`, `check`, `routes`, `version`) as pure adapters over existing `cli/lib` functions. Read-only: no file writes, no network, no learning-usage sidecar writes (the SDK's `prompt`/`pack` default to not recording usage, unlike the CLI's opt-in `--with-learning` sidecar write — an SDK caller opts in explicitly if that is added later).
70
+
71
+ Verification gates:
72
+ - `node --test` unit coverage for each adapter: signature, option defaults, return-shape keys, determinism, and parity with the CLI `--json` output for a fixed brief.
73
+ - SDK contract test pinning the exported names and the return-shape key sets (the semver anchor).
74
+ - `npm run audit` 8/8 (the SDK reference doc's links resolve; frontmatter valid).
75
+ - `npm run release:check` additions: packed-tarball smoke that imports `@design-ai/cli/sdk` from the installed package and exercises `route`/`search`(ranked)/`recall`, plus a registry-smoke parity check after publish.
76
+ - `npm run release:metadata` unchanged (README stance preserved).
77
+
78
+ ### Phase B — explicit local writes (shipped)
79
+
80
+ Implemented as a single `learn` namespace export grouping three opt-in adapters that mirror the CLI's local-write commands: `learn.remember`, `learn.feedback`, and `learn.captureFromCheck` (capture from a `check()` report). Each writes only the local learning profile (`DESIGN_AI_LEARNING_FILE` / `defaultLearningFile()`), never the network — no `filePath` or `now`/timestamp option; consumers target a profile via the env var, exactly like the CLI. The 8 Phase A verbs are unchanged and stay read-only; `check()` in particular gets no capture option — capture is reached only through the separate, explicitly-named `learn.captureFromCheck`, keeping the write boundary visible at the call site. See [`SDK.md`](SDK.md#phase-b--local-writes) for the full reference.
81
+
82
+ ## Integration points
83
+
84
+ - **MCP server** already wraps the same `cli/lib` functions; the SDK and MCP stay in lockstep because both are thin layers over one core. No MCP change is required for Phase A.
85
+ - **Retrieval** (`search` ranked, `recall`, `withRecall`) flows into the SDK for free — the shared lexical scorer and the generated-index exclusion apply identically.
86
+ - **Docs**: a `docs/SDK.md` (or this file, promoted from draft) becomes the public SDK reference; the README install table gains an "SDK" row pointing to it.
87
+
88
+ ## Risks and open questions
89
+
90
+ Risks:
91
+ - **Semver commitment.** Once published, the SDK surface is a promise. Mitigation: keep Phase A small (8 verbs), pin names/shapes in a contract test, and treat the adapter as the only stable seam.
92
+ - **Return-shape coupling.** SDK returns mirror the CLI `--json` shapes; if those change, the SDK breaks. Mitigation: the same smoke/parity tests that already guard the CLI JSON guard the SDK, and shape changes are already major-version events.
93
+
94
+ Open questions (answer during Phase A implementation review):
95
+ 1. Export the bare package root (`.`) as an alias for `./sdk`, or keep `./sdk` the only path? (Leaning: `./sdk` only, to avoid implying the whole package is the SDK.)
96
+ 2. Ship TypeScript types (`.d.ts`) hand-written for the 8 verbs, or JSDoc-only for the zero-toolchain stance? (Leaning: JSDoc + a hand-written `sdk/index.d.ts` with no build step.)
97
+ 3. Does `prompt`/`pack` via the SDK ever write the learning-usage sidecar, or is that strictly a Phase B explicit opt-in? (Leaning: never in Phase A; read-only.)
98
+ 4. Is `check` in Phase A (read-only quality check) or deferred with the other write-adjacent verbs? (Leaning: Phase A — `check` is read-only when capture is off.)
package/docs/ROADMAP.md CHANGED
@@ -1,5 +1,103 @@
1
1
  # Roadmap
2
2
 
3
+ ## Phase 761 — Agent SDK types + Phase B Release (v4.60.0) ✓ ready
4
+
5
+ Ships the Agent SDK TypeScript declarations (Phase 759) and the `learn.*` local-write namespace (Phase 760) together as one npm version. Both are additive and backward-compatible: the eight Phase A verbs stay read-only and unchanged, and TypeScript types resolve automatically for `@design-ai/cli/sdk` with no build step on either side.
6
+
7
+ ### Verified
8
+ - All 8 audits passed.
9
+ - `npm run release:check` (unit tests incl. SDK type-sync guard and `learn.*` write tests, strict audits, whitespace, package contents, release metadata, release self-tests, packed-tarball smoke that imports `@design-ai/cli/sdk` and exercises `learn.remember`).
10
+ - `npm run release:metadata`.
11
+ - `git diff --check`.
12
+ - The declarations compile under `tsc --strict` against a real consumer (transient check; TypeScript is not a package dependency).
13
+ - Main-branch GitHub Actions (`Design-AI audit`, `Deploy doc site`) passed for the constituent commits.
14
+
15
+ ### Versions
16
+ - `package.json` + `.claude-plugin/plugin.json`: 4.59.0 → 4.60.0.
17
+ - `vscode-extension/package.json`: remains 0.4.1.
18
+
19
+ ### What this enables
20
+ - TypeScript/editor tooling resolves the full SDK surface (verbs, option interfaces, return types) with zero `@types` install and zero build step, and agents gain an explicit, opt-in `learn.*` write namespace for recording preferences, feedback, and check-derived learnings — while every read-only verb keeps its no-write guarantee.
21
+
22
+ ### What's still ahead
23
+ - SDK surface is feature-complete for the planned phases; further verbs are demand-driven. Remaining roadmap work returns to other surfaces (see later phases).
24
+
25
+ ## Phase 760 — Agent SDK Phase B (local writes, implemented, unreleased)
26
+
27
+ Adds the Phase B local-write surface described in [AGENT-SDK.md](AGENT-SDK.md#phase-b--explicit-local-writes-shipped): a single `learn` namespace export grouping three explicit, opt-in local-write verbs (`learn.remember`, `learn.feedback`, `learn.captureFromCheck`). This is the only place the SDK writes files; the 8 Phase A verbs are unchanged and stay read-only — `check()` in particular gets no capture option, so its read-only contract test still holds. Landed in `main` as `cli/sdk/learn-adapter.mjs`; code-complete and gated but not yet released as an npm version.
28
+
29
+ ### Delivered
30
+ - [x] `learn.remember(text, opts?)` — records a local learning-profile preference. Adapter over `rememberLearning` from `cli/lib/learn-profile.mjs`; always writes `source: "sdk"`.
31
+ - [x] `learn.feedback(text, opts?)` — records keep/avoid/improve feedback as a learning-profile entry. Adapter over `recordLearningFeedback`; the library normalizes `outcome` and folds it into the stored entry's `text` and `source`.
32
+ - [x] `learn.captureFromCheck(artifact, opts?)` — checks a Markdown artifact, then captures its non-pass results as learning-profile entries. Adapter over `checkArtifactContent` + `buildCheckLearningCapture` from `cli/lib/check.mjs` — the SDK equivalent of the CLI's `check --learn --yes`. Duplicate captures are skipped (`reason: "duplicate-entry-text"`).
33
+ - [x] All three write **only** the local learning profile (`DESIGN_AI_LEARNING_FILE` / `defaultLearningFile()`), never the network; no `filePath` or `now`/timestamp option on any of them — consumers target a profile via the env var, exactly like the CLI.
34
+ - [x] `learn` is a frozen object (`Object.freeze`); `cli/sdk/index.mjs` re-exports it alongside the 8 unchanged Phase A verbs.
35
+ - [x] Input validation reused from `cli/sdk/validate.mjs` (`requireNonEmptyString`, `requireOptions`, `optionalString`); `routeId` validated with the same `assertKnownRouteId` helper the read-only `check` adapter uses.
36
+ - [x] `cli/sdk/learn-adapter.test.mjs` — per-verb coverage: persistence (re-reading the written profile), `remember`'s `source: "sdk"`, `feedback`'s outcome-to-text/source mapping, `captureFromCheck`'s added/skipped results and duplicate-capture skip, and bad-input rejection. 16 new tests.
37
+ - [x] `cli/sdk/index.test.mjs` contract test updated: the 8 read-only function verbs are pinned unchanged; `learn` is pinned as a frozen object export with exactly `["captureFromCheck", "feedback", "remember"]`, plus return-shape keys for each verb (including the full `captureLearningEntries` result shape: `file, dryRun, applied, source, candidateCount, addedCount, skippedCount, count, entries, skipped`).
38
+ - [x] `cli/sdk/types.test.mjs` unchanged and passing (it filters to `typeof === "function"`, so the `learn` object export is naturally excluded); a new assertion added confirming `index.d.ts` declares `export declare const learn:`.
39
+ - [x] `cli/sdk/index.d.ts` — hand-written types for `LearnRememberOptions`, `LearnFeedbackOptions`, `LearnCaptureOptions`, `LearningProfileEntry`, `LearningProfile`, `RememberResult`, `CaptureSkippedEntry`, `CaptureResult`, and the `learn` const declaration. Verified to compile under `tsc --strict` against a transient consumer (zero-toolchain stance preserved — TypeScript is not added to `package.json`).
40
+ - [x] `docs/SDK.md` — "Phase B — local writes" section replaces the "not yet shipped" placeholder: full reference for all three verbs, options, return shapes, the `DESIGN_AI_LEARNING_FILE` target, and the write-boundary rationale.
41
+ - [x] `docs/AGENT-SDK.md` Phase B section updated from "only if demand" to shipped.
42
+ - [x] `npm test` (563/563), `npm run audit` (8/8), `npm run release:metadata` all pass.
43
+
44
+ ### Remaining
45
+ - [ ] Release decision: bundle Phase B into the next npm version alongside or after Phase 759's TypeScript declarations.
46
+ - [ ] Optional follow-up: extend the packed-tarball SDK smoke (`tools/audit/package-smoke.py`) to exercise `learn.remember` against a temp `DESIGN_AI_LEARNING_FILE`.
47
+
48
+ ## Phase 759 — Agent SDK TypeScript declarations (implemented, unreleased)
49
+
50
+ Ships hand-written type declarations for the eight SDK verbs so TypeScript and editor tooling resolve `@design-ai/cli/sdk` with full types — no `@types` package, no build step (the `.d.ts` is maintained by hand to preserve the zero-toolchain stance). Landed in `main` as `cli/sdk/index.d.ts`; additive (types only), pending bundling into the next npm version.
51
+
52
+ ### Delivered
53
+ - [x] `cli/sdk/index.d.ts` — precise signatures + return interfaces for all eight verbs (incl. `search` ranked/plain overloads, the `RouteConfidence` union, and optional `learningContext`/`recall`/`relatedKnowledge` fields), derived from the runtime shapes the contract test pins.
54
+ - [x] `exports` `types` condition (`"./sdk": { "types": …, "default": … }`) so the subpath resolves declarations under `moduleResolution: node16`/`nodenext`/`bundler`.
55
+ - [x] `cli/sdk/types.test.mjs` — zero-dependency guard asserting the `.d.ts` declares exactly the runtime-exported verbs (drift anchor). Declarations independently verified to compile under `tsc --strict` against a real consumer.
56
+ - [x] `docs/SDK.md` TypeScript section.
57
+
58
+ ### Remaining
59
+ - [ ] Release: bundle into the next npm version (with Phase B, or standalone).
60
+
61
+ ## Phase 758 — Agent SDK Phase A Release (v4.59.0) ✓ ready
62
+
63
+ Ships the Agent SDK Phase A work (Phase 757) as an npm version: the curated, semver-stable `@design-ai/cli/sdk` surface — eight read-only adapter verbs (`route`, `prompt`, `pack`, `search`, `recall`, `check`, `routes`, `version`) over the same `cli/lib` functions the CLI and MCP already call. No new capabilities, no runtime dependencies, no network, no telemetry; read-only, deterministic, backward-compatible.
64
+
65
+ ### Verified
66
+ - All 8 audits passed.
67
+ - `npm run release:check` (unit tests incl. 61 SDK tests, strict audits, whitespace, package contents, release metadata, release self-tests, packed-tarball smoke that imports `@design-ai/cli/sdk`).
68
+ - `npm run release:metadata`.
69
+ - `git diff --check`.
70
+ - Main-branch GitHub Actions (`Design-AI audit`, `Deploy doc site`) passed for the constituent commits.
71
+
72
+ ### Versions
73
+ - `package.json` + `.claude-plugin/plugin.json`: 4.58.0 → 4.59.0.
74
+ - `vscode-extension/package.json`: remains 0.4.1.
75
+
76
+ ### What this enables
77
+ - design-ai's routing, prompt/pack generation, ranked search, recall, and artifact checks become plain importable functions for agent runtimes, build scripts, and custom tools — deterministic, in-process, zero-dependency — behind a semver-stable contract that insulates callers from internal `cli/lib` refactors.
78
+
79
+ ### What's still ahead
80
+ - Phase B explicit local-write adapters (`learn.remember`/`feedback`, capture) — implemented and landed in `main` (see Phase 760); pending release.
81
+ - Hand-written `cli/sdk/index.d.ts` TypeScript declarations shipped after this release (see Phase 759).
82
+
83
+ ## Phase 757 — Agent SDK (Phase A implemented, unreleased)
84
+
85
+ Opens the Agent SDK phase — the fast-follow chosen in [NEXT-SURFACE-DECISION.md](NEXT-SURFACE-DECISION.md), unblocked now that the recall interface has been stable across two releases (v4.57.0/v4.58.0). Scope, the stable surface, packaging, and the stability contract are defined in [AGENT-SDK.md](AGENT-SDK.md). No new capabilities, no runtime dependencies, no network, no telemetry; the SDK is a thin, semver-stable adapter over the same `cli/lib` functions the CLI and MCP already call.
86
+
87
+ Phase A landed in `main` (commit `326ed0a`) as `cli/sdk/`; it is code-complete and gated but **not yet released as an npm version** — shipping it is the next release decision.
88
+
89
+ ### Delivered (Phase A)
90
+ - [x] Read-only SDK core at `@design-ai/cli/sdk` — `route`, `prompt`, `pack`, `search` (incl. ranked), `recall`, `check`, `routes`, `version` as pure adapters with stable signatures; `exports` map added; no file writes / no network. `check` operates on artifact content (not a path), so it reads and writes nothing.
91
+ - [x] Unit coverage per adapter (signature, defaults, return-shape keys, determinism, CLI `--json` parity) plus an SDK contract test (`cli/sdk/index.test.mjs`) pinning exported names and return-shape keys — the semver anchor. 61 new tests, 540/540 total.
92
+ - [x] Packed-tarball smoke (`package-smoke.py`) that imports `@design-ai/cli/sdk` from the installed package and exercises `route`/`search` ranked/`recall`/`version`/`routes`/`prompt`/`pack`/`check`, asserting determinism and the Phase A no-`learningUsage` guarantee.
93
+ - [x] `docs/SDK.md` public reference + README/README.ko "Node.js / Agent SDK" install-table row + mkdocs nav.
94
+ - [x] Open questions from [AGENT-SDK.md](AGENT-SDK.md) resolved: bare root `.` not exported (only `./sdk`); usage sidecar never written in Phase A; `check` is in Phase A (read-only). TS `.d.ts` deferred — adapters carry JSDoc; a hand-written `sdk/index.d.ts` remains an optional follow-up.
95
+
96
+ ### Remaining
97
+ - [ ] Release decision: bundle Phase A into the next npm version (e.g. v4.59.0) — version bump, CHANGELOG, registry-smoke parity after publish.
98
+ - [ ] Optional follow-up: hand-written `cli/sdk/index.d.ts` type declarations for the 8 verbs (zero-build stance preserved).
99
+ - [ ] Phase B (optional, deferred until an adopter needs it): explicit local-write adapters (`learn.remember`/`feedback`, capture).
100
+
3
101
  ## Phase 756 — Retrieval Across Surfaces and Corpus Depth Release (v4.58.0) ✓ ready
4
102
 
5
103
  Groups the post-v4.57.0 retrieval completion and corpus work into one release: `route --explain` related-knowledge enrichment (retrieval now spans every surface), recall exclusion of generated index/meta files, the async-control and Korean B2B density knowledge files, and the registry-smoke assertion fix that realigns the live post-publish smoke with the shared scorer. Defaults are unchanged; the release is additive and backward-compatible.