@design-ai/cli 4.60.0 → 4.62.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 +55 -0
- package/README.ko.md +6 -6
- package/README.md +6 -6
- package/cli/commands/help.mjs +1 -1
- package/cli/commands/search.mjs +81 -2
- package/cli/lib/check.mjs +29 -0
- package/cli/lib/examples.mjs +2 -0
- package/cli/lib/pack.mjs +29 -1
- package/cli/lib/recall.mjs +2 -2
- package/cli/lib/route.mjs +26 -3
- package/cli/lib/search-eval.mjs +241 -0
- package/cli/lib/search-ranked.mjs +31 -11
- package/cli/lib/search.mjs +56 -1
- package/docs/AI-LEARNING-PHASE2.md +13 -2
- package/docs/DOGFOOD-SDK-FINDINGS.md +56 -0
- package/docs/NEXT-SURFACE-DECISION.md +36 -2
- package/docs/ROADMAP.md +77 -0
- package/docs/SDK.md +6 -0
- package/docs/external-status.md +11 -11
- package/docs/integrations/agent-sdk-walkthrough.ko.md +219 -0
- package/docs/integrations/agent-sdk-walkthrough.md +219 -0
- package/examples/README.md +6 -0
- package/examples/flow-design-report-block.md +141 -0
- package/knowledge/COVERAGE.md +7 -5
- package/knowledge/patterns/trust-safety-moderation.md +107 -0
- package/package.json +1 -1
- package/tools/audit/integration-check.py +1 -0
- package/tools/audit/smoke_assertions.py +11 -5
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
// Ranked-search eval checkpoint generation and reports for `design-ai search`.
|
|
2
|
+
// Mirrors cli/lib/route.mjs's eval-checkpoint pattern (docs/AI-LEARNING-PHASE2.md
|
|
3
|
+
// FU-3): runs hand-authored / generated cases through the shipped deterministic
|
|
4
|
+
// ranked search path (rankedSearchCorpus, BM25-style lexical scorer) and reports
|
|
5
|
+
// pass/warn/fail. This is the standing evidence artifact for any future decision
|
|
6
|
+
// to promote `--ranked` to the default `search` behavior.
|
|
7
|
+
|
|
8
|
+
import { rankedSearchCorpus } from "./search-ranked.mjs";
|
|
9
|
+
import { DEFAULT_SEARCH_DIRS } from "./search.mjs";
|
|
10
|
+
|
|
11
|
+
export const SEARCH_EVAL_VERSION = 1;
|
|
12
|
+
// Default per-case result limit when a case does not specify its own `limit`.
|
|
13
|
+
// Kept small and eval-appropriate (route's default is 3; search corpus hits are
|
|
14
|
+
// noisier, so 10 gives enough headroom for `minHits` assertions without needing
|
|
15
|
+
// a large recall window).
|
|
16
|
+
export const SEARCH_EVAL_DEFAULT_LIMIT = 10;
|
|
17
|
+
|
|
18
|
+
function isoTimestamp(now = new Date()) {
|
|
19
|
+
return (now instanceof Date ? now : new Date(now)).toISOString();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function buildSearchEvalTemplate({ sourceRoot, generatedAt = new Date() } = {}) {
|
|
23
|
+
return {
|
|
24
|
+
version: SEARCH_EVAL_VERSION,
|
|
25
|
+
generatedAt: isoTimestamp(generatedAt),
|
|
26
|
+
description: "Deterministic ranked-search checkpoints for design-ai's BM25-style lexical retrieval.",
|
|
27
|
+
cases: [
|
|
28
|
+
{
|
|
29
|
+
id: "korean-button-stem",
|
|
30
|
+
query: "버튼",
|
|
31
|
+
minHits: 1,
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: "korean-button-particle",
|
|
35
|
+
query: "버튼을",
|
|
36
|
+
minHits: 1,
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: "korean-accessibility-particle",
|
|
40
|
+
query: "접근성이",
|
|
41
|
+
minHits: 1,
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
id: "accessibility-english",
|
|
45
|
+
query: "accessibility",
|
|
46
|
+
minHits: 5,
|
|
47
|
+
matchedTokenIncludes: ["accessibility"],
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: "button-component-spec-english",
|
|
51
|
+
query: "button component spec",
|
|
52
|
+
minHits: 5,
|
|
53
|
+
matchedTokenIncludes: ["button", "component", "spec"],
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
id: "color-palette-english",
|
|
57
|
+
query: "color palette",
|
|
58
|
+
expectRelPathIn: ["commands/palette-from-brand.md"],
|
|
59
|
+
minHits: 5,
|
|
60
|
+
matchedTokenIncludes: ["color", "palette"],
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function searchEvalStatus(counts) {
|
|
67
|
+
if (counts.fail > 0) return "fail";
|
|
68
|
+
if (counts.warn > 0) return "warn";
|
|
69
|
+
return "pass";
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function normalizeSearchEvalPayload(evalText, source = "search-eval.json") {
|
|
73
|
+
let payload;
|
|
74
|
+
try {
|
|
75
|
+
payload = JSON.parse(evalText);
|
|
76
|
+
} catch (err) {
|
|
77
|
+
throw new Error(`Could not parse search eval JSON from ${source}: ${err.message}`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
81
|
+
throw new Error("Search eval payload must be a JSON object");
|
|
82
|
+
}
|
|
83
|
+
if (payload.version !== SEARCH_EVAL_VERSION) {
|
|
84
|
+
throw new Error(`Search eval payload version must be ${SEARCH_EVAL_VERSION}`);
|
|
85
|
+
}
|
|
86
|
+
if (!Array.isArray(payload.cases)) {
|
|
87
|
+
throw new Error("Search eval payload must include a cases array");
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return payload;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function normalizeStringList(value, { field, id }) {
|
|
94
|
+
if (value === undefined || value === null) return [];
|
|
95
|
+
if (!Array.isArray(value)) {
|
|
96
|
+
throw new Error(`Search eval case ${id} field ${field} must be an array`);
|
|
97
|
+
}
|
|
98
|
+
return value.map((item) => String(item || "").trim()).filter(Boolean);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function normalizeSearchEvalCase(rawCase, index) {
|
|
102
|
+
if (!rawCase || typeof rawCase !== "object" || Array.isArray(rawCase)) {
|
|
103
|
+
throw new Error(`Search eval case ${index + 1} must be a JSON object`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const id = String(rawCase.id || `case-${index + 1}`).trim();
|
|
107
|
+
if (!id) throw new Error(`Search eval case ${index + 1} is missing id`);
|
|
108
|
+
|
|
109
|
+
const query = String(rawCase.query || "").trim();
|
|
110
|
+
if (!query) throw new Error(`Search eval case ${id} is missing query`);
|
|
111
|
+
|
|
112
|
+
const dirs = normalizeStringList(rawCase.dirs, { field: "dirs", id });
|
|
113
|
+
for (const dir of dirs) {
|
|
114
|
+
if (!DEFAULT_SEARCH_DIRS.includes(dir)) {
|
|
115
|
+
throw new Error(`Search eval case ${id} field dirs has invalid value: ${dir}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const limit = rawCase.limit === undefined || rawCase.limit === null
|
|
120
|
+
? SEARCH_EVAL_DEFAULT_LIMIT
|
|
121
|
+
: Number(rawCase.limit);
|
|
122
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 500) {
|
|
123
|
+
throw new Error(`Search eval case ${id} limit must be an integer from 1 to 500`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const expectRelPathIn = normalizeStringList(rawCase.expectRelPathIn, { field: "expectRelPathIn", id });
|
|
127
|
+
const matchedTokenIncludes = normalizeStringList(rawCase.matchedTokenIncludes, { field: "matchedTokenIncludes", id })
|
|
128
|
+
.map((token) => token.toLowerCase());
|
|
129
|
+
|
|
130
|
+
let minHits;
|
|
131
|
+
if (rawCase.minHits === undefined || rawCase.minHits === null) {
|
|
132
|
+
minHits = undefined;
|
|
133
|
+
} else {
|
|
134
|
+
minHits = Number(rawCase.minHits);
|
|
135
|
+
if (!Number.isInteger(minHits) || minHits < 0) {
|
|
136
|
+
throw new Error(`Search eval case ${id} field minHits must be a non-negative integer`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
id,
|
|
142
|
+
query,
|
|
143
|
+
dirs: dirs.length > 0 ? dirs : DEFAULT_SEARCH_DIRS,
|
|
144
|
+
limit,
|
|
145
|
+
expectRelPathIn,
|
|
146
|
+
minHits,
|
|
147
|
+
matchedTokenIncludes,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function evaluateSearchEvalCase(testCase, sourceRoot, defaultLimit) {
|
|
152
|
+
const limit = testCase.limit || defaultLimit || SEARCH_EVAL_DEFAULT_LIMIT;
|
|
153
|
+
const { hits } = rankedSearchCorpus({
|
|
154
|
+
query: testCase.query,
|
|
155
|
+
designAiPath: sourceRoot,
|
|
156
|
+
dirs: testCase.dirs,
|
|
157
|
+
limit,
|
|
158
|
+
});
|
|
159
|
+
const topHit = hits[0] || null;
|
|
160
|
+
const topRelPath = topHit?.relPath || "";
|
|
161
|
+
const topMatchedTokens = (topHit?.matchedTokens || []).map((token) => token.toLowerCase());
|
|
162
|
+
|
|
163
|
+
const hasAssertions = testCase.expectRelPathIn.length > 0
|
|
164
|
+
|| testCase.minHits !== undefined
|
|
165
|
+
|| testCase.matchedTokenIncludes.length > 0;
|
|
166
|
+
|
|
167
|
+
let status = "pass";
|
|
168
|
+
let message = "All checkpoint expectations were satisfied.";
|
|
169
|
+
|
|
170
|
+
if (!hasAssertions) {
|
|
171
|
+
status = "warn";
|
|
172
|
+
message = "Case has no expectRelPathIn, minHits, or matchedTokenIncludes assertion.";
|
|
173
|
+
} else if (testCase.expectRelPathIn.length > 0 && !testCase.expectRelPathIn.includes(topRelPath)) {
|
|
174
|
+
status = "fail";
|
|
175
|
+
message = topRelPath
|
|
176
|
+
? `Expected top hit in [${testCase.expectRelPathIn.join(", ")}], but top hit was ${topRelPath}.`
|
|
177
|
+
: `Expected top hit in [${testCase.expectRelPathIn.join(", ")}], but there were no hits.`;
|
|
178
|
+
} else if (testCase.minHits !== undefined && hits.length < testCase.minHits) {
|
|
179
|
+
status = "fail";
|
|
180
|
+
message = `Expected at least ${testCase.minHits} hit(s), got ${hits.length}.`;
|
|
181
|
+
} else if (
|
|
182
|
+
testCase.matchedTokenIncludes.length > 0
|
|
183
|
+
&& !testCase.matchedTokenIncludes.every((token) => topMatchedTokens.includes(token))
|
|
184
|
+
) {
|
|
185
|
+
status = "fail";
|
|
186
|
+
const missing = testCase.matchedTokenIncludes.filter((token) => !topMatchedTokens.includes(token));
|
|
187
|
+
message = topHit
|
|
188
|
+
? `Top hit ${topRelPath} did not match expected token(s): ${missing.join(", ")}.`
|
|
189
|
+
: `Expected matched tokens [${missing.join(", ")}], but there were no hits.`;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
id: testCase.id,
|
|
194
|
+
status,
|
|
195
|
+
message,
|
|
196
|
+
query: testCase.query,
|
|
197
|
+
dirs: testCase.dirs,
|
|
198
|
+
limit,
|
|
199
|
+
topRelPath,
|
|
200
|
+
hitCount: hits.length,
|
|
201
|
+
matchedTokens: topHit?.matchedTokens || [],
|
|
202
|
+
expectRelPathIn: testCase.expectRelPathIn,
|
|
203
|
+
minHits: testCase.minHits ?? null,
|
|
204
|
+
matchedTokenIncludes: testCase.matchedTokenIncludes,
|
|
205
|
+
hits,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export function searchEvalReport({
|
|
210
|
+
evalText,
|
|
211
|
+
source = "search-eval.json",
|
|
212
|
+
sourceRoot,
|
|
213
|
+
limit = SEARCH_EVAL_DEFAULT_LIMIT,
|
|
214
|
+
generatedAt = new Date(),
|
|
215
|
+
}) {
|
|
216
|
+
const payload = normalizeSearchEvalPayload(evalText, source);
|
|
217
|
+
const normalizedCases = payload.cases.map((testCase, index) => normalizeSearchEvalCase(testCase, index));
|
|
218
|
+
const results = normalizedCases.map((testCase) => evaluateSearchEvalCase(testCase, sourceRoot, limit));
|
|
219
|
+
const counts = results.reduce(
|
|
220
|
+
(acc, result) => ({
|
|
221
|
+
...acc,
|
|
222
|
+
[result.status]: acc[result.status] + 1,
|
|
223
|
+
}),
|
|
224
|
+
{ pass: 0, warn: 0, fail: 0 },
|
|
225
|
+
);
|
|
226
|
+
const summary = {
|
|
227
|
+
total: results.length,
|
|
228
|
+
pass: counts.pass,
|
|
229
|
+
warn: counts.warn,
|
|
230
|
+
fail: counts.fail,
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
evalVersion: payload.version,
|
|
235
|
+
source,
|
|
236
|
+
generatedAt: isoTimestamp(generatedAt),
|
|
237
|
+
status: searchEvalStatus(counts),
|
|
238
|
+
summary,
|
|
239
|
+
cases: results,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
@@ -40,6 +40,24 @@ export function isGeneratedIndexDoc(relPath) {
|
|
|
40
40
|
|| normalized.startsWith("docs/reference/");
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
// Predicate for the full RECALL-injection exclusion set (docs/DOGFOOD-SDK-FINDINGS.md,
|
|
44
|
+
// F-2): generated index/meta docs (isGeneratedIndexDoc) OR anything under docs/.
|
|
45
|
+
// Rationale: recall injects DESIGN KNOWLEDGE into an agent's context, and the
|
|
46
|
+
// design corpus lives in knowledge/, examples/, skills/, agents/, commands/ —
|
|
47
|
+
// docs/ is product documentation (guides, roadmap, dogfood/inspection records,
|
|
48
|
+
// portfolio material, integration walkthroughs). Enumerating individual meta
|
|
49
|
+
// files proved to be whack-a-mole: the Phase 764 dogfood first surfaced
|
|
50
|
+
// docs/case-study.md ranked #1, and the follow-up probe surfaced
|
|
51
|
+
// docs/DOGFOOD-SDK-FINDINGS.md itself at 3x the top knowledge score because it
|
|
52
|
+
// quotes the brief's vocabulary. Same recall-injection-surfaces-only boundary
|
|
53
|
+
// as isGeneratedIndexDoc: raw `search --ranked` never applies this — a user
|
|
54
|
+
// explicitly searching docs/ still gets docs/ hits.
|
|
55
|
+
export function isRecallExcludedDoc(relPath) {
|
|
56
|
+
const normalized = String(relPath || "").replace(/\\/g, "/");
|
|
57
|
+
return isGeneratedIndexDoc(normalized)
|
|
58
|
+
|| normalized.startsWith("docs/");
|
|
59
|
+
}
|
|
60
|
+
|
|
43
61
|
// N = max(limit*5, 25): the number of top lexical candidates handed to the embedding
|
|
44
62
|
// reranker. Documented constant (docs/AI-LEARNING-PHASE2.md, Phase B CLI wiring).
|
|
45
63
|
export function embeddingCandidateCount(limit) {
|
|
@@ -82,25 +100,27 @@ export function rankedSearchCorpus({
|
|
|
82
100
|
dirs = DEFAULT_SEARCH_DIRS,
|
|
83
101
|
limit = 20,
|
|
84
102
|
indexDir = defaultIndexDir(),
|
|
85
|
-
// Opt-in RECALL filter: when true, drop
|
|
86
|
-
//
|
|
87
|
-
//
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
|
|
103
|
+
// Opt-in RECALL filter: when true, drop non-knowledge docs (isRecallExcludedDoc —
|
|
104
|
+
// generated index/meta docs, package-excluded repo-meta docs, docs/integrations/
|
|
105
|
+
// walkthroughs) BEFORE applying `limit`, so the limit fills with real knowledge
|
|
106
|
+
// instead of those files. Default false keeps raw `search --ranked` byte-unchanged.
|
|
107
|
+
// Filtering before the limit preserves determinism (score desc, id asc) — the rank
|
|
108
|
+
// pass already orders fully, we only remove excluded ids.
|
|
109
|
+
excludeNonKnowledge = false,
|
|
91
110
|
} = {}) {
|
|
92
111
|
const documents = collectCorpusDocuments({ designAiPath, dirs });
|
|
93
112
|
const stats = buildLexicalStats(documents.map(({ id, text }) => ({ id, text })));
|
|
94
113
|
const textById = new Map(documents.map((doc) => [doc.id, doc.text]));
|
|
95
114
|
|
|
96
115
|
// When excluding, rank the FULL candidate pool first (limit = documents.length)
|
|
97
|
-
// so that filtering out
|
|
98
|
-
// pre-limit cutoff; then re-apply `limit`. Ordering is unchanged (score
|
|
116
|
+
// so that filtering out non-knowledge docs cannot let a real-knowledge hit fall
|
|
117
|
+
// off the pre-limit cutoff; then re-apply `limit`. Ordering is unchanged (score
|
|
118
|
+
// desc, id asc).
|
|
99
119
|
const ranked = rankLexical(query, stats, {
|
|
100
|
-
limit:
|
|
120
|
+
limit: excludeNonKnowledge ? documents.length : limit,
|
|
101
121
|
});
|
|
102
|
-
const filtered =
|
|
103
|
-
? ranked.filter((hit) => !
|
|
122
|
+
const filtered = excludeNonKnowledge
|
|
123
|
+
? ranked.filter((hit) => !isRecallExcludedDoc(hit.id)).slice(0, limit)
|
|
104
124
|
: ranked;
|
|
105
125
|
|
|
106
126
|
const hits = filtered.map((hit) => ({
|
package/cli/lib/search.mjs
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
} from "node:fs";
|
|
8
8
|
import path from "node:path";
|
|
9
9
|
|
|
10
|
+
import { parseBriefSourceFlag } from "./brief.mjs";
|
|
10
11
|
import { expectedValueMessage, unknownOptionMessage } from "./suggest.mjs";
|
|
11
12
|
|
|
12
13
|
export const DEFAULT_SEARCH_DIRS = [
|
|
@@ -20,7 +21,21 @@ export const DEFAULT_SEARCH_DIRS = [
|
|
|
20
21
|
|
|
21
22
|
const PREVIEW_LEN = 120;
|
|
22
23
|
const PREVIEW_BEFORE = 50;
|
|
23
|
-
const SEARCH_OPTIONS = [
|
|
24
|
+
const SEARCH_OPTIONS = [
|
|
25
|
+
"-h",
|
|
26
|
+
"--help",
|
|
27
|
+
"--json",
|
|
28
|
+
"--limit",
|
|
29
|
+
"--dir",
|
|
30
|
+
"--ranked",
|
|
31
|
+
"--embeddings",
|
|
32
|
+
"--provider",
|
|
33
|
+
"--eval-template",
|
|
34
|
+
"--eval",
|
|
35
|
+
"--strict",
|
|
36
|
+
"--from-file",
|
|
37
|
+
"--stdin",
|
|
38
|
+
];
|
|
24
39
|
|
|
25
40
|
function exists(p) {
|
|
26
41
|
try {
|
|
@@ -118,11 +133,17 @@ export function parseSearchArgs(args) {
|
|
|
118
133
|
ranked: false,
|
|
119
134
|
embeddings: false,
|
|
120
135
|
provider: "",
|
|
136
|
+
evalTemplate: false,
|
|
137
|
+
eval: false,
|
|
138
|
+
strict: false,
|
|
139
|
+
fromFile: "",
|
|
140
|
+
stdin: false,
|
|
121
141
|
help: false,
|
|
122
142
|
};
|
|
123
143
|
|
|
124
144
|
for (let i = 0; i < args.length; i += 1) {
|
|
125
145
|
const arg = args[i];
|
|
146
|
+
out.index = i;
|
|
126
147
|
if (arg === "-h" || arg === "--help") {
|
|
127
148
|
out.help = true;
|
|
128
149
|
} else if (arg === "--json") {
|
|
@@ -131,9 +152,17 @@ export function parseSearchArgs(args) {
|
|
|
131
152
|
out.ranked = true;
|
|
132
153
|
} else if (arg === "--embeddings") {
|
|
133
154
|
out.embeddings = true;
|
|
155
|
+
} else if (arg === "--eval-template") {
|
|
156
|
+
out.evalTemplate = true;
|
|
157
|
+
} else if (arg === "--eval") {
|
|
158
|
+
out.eval = true;
|
|
159
|
+
} else if (arg === "--strict") {
|
|
160
|
+
out.strict = true;
|
|
134
161
|
} else if (arg === "--provider") {
|
|
135
162
|
out.provider = args[i + 1] || "";
|
|
136
163
|
i += 1;
|
|
164
|
+
} else if (parseBriefSourceFlag(args, out)) {
|
|
165
|
+
i = out.index;
|
|
137
166
|
} else if (arg === "--limit") {
|
|
138
167
|
const next = args[i + 1];
|
|
139
168
|
const limit = Number(next);
|
|
@@ -156,8 +185,34 @@ export function parseSearchArgs(args) {
|
|
|
156
185
|
}
|
|
157
186
|
}
|
|
158
187
|
|
|
188
|
+
if (out.eval && out.evalTemplate) {
|
|
189
|
+
throw new Error("Choose either --eval-template or --eval, not both");
|
|
190
|
+
}
|
|
191
|
+
if (out.strict && !out.eval) {
|
|
192
|
+
throw new Error("--strict can only be used with --eval");
|
|
193
|
+
}
|
|
194
|
+
if (
|
|
195
|
+
out.evalTemplate
|
|
196
|
+
&& (out.fromFile || out.stdin || out.queryParts.length > 0 || out.ranked || out.embeddings || out.provider || out.dirs.length > 0)
|
|
197
|
+
) {
|
|
198
|
+
throw new Error("--eval-template cannot be combined with a query, --from-file, --stdin, --ranked, --embeddings, --provider, or --dir");
|
|
199
|
+
}
|
|
200
|
+
if (out.eval && (!out.fromFile && !out.stdin)) {
|
|
201
|
+
throw new Error("--eval requires --from-file or --stdin");
|
|
202
|
+
}
|
|
203
|
+
if ((out.fromFile || out.stdin) && !out.eval) {
|
|
204
|
+
throw new Error("--from-file and --stdin require --eval");
|
|
205
|
+
}
|
|
206
|
+
if (
|
|
207
|
+
out.eval
|
|
208
|
+
&& (out.queryParts.length > 0 || out.ranked || out.embeddings || out.provider || out.dirs.length > 0)
|
|
209
|
+
) {
|
|
210
|
+
throw new Error("--eval cannot be combined with an inline query, --ranked, --embeddings, --provider, or --dir");
|
|
211
|
+
}
|
|
212
|
+
|
|
159
213
|
return {
|
|
160
214
|
...out,
|
|
215
|
+
index: undefined,
|
|
161
216
|
query: out.queryParts.join(" ").trim(),
|
|
162
217
|
dirs: out.dirs.length > 0 ? out.dirs : DEFAULT_SEARCH_DIRS,
|
|
163
218
|
};
|
|
@@ -202,8 +202,19 @@ This review answers the five open questions against the shipped Phase A implemen
|
|
|
202
202
|
|
|
203
203
|
- **FU-1 (Q4, before Phase B):** Add Hangul-aware tokenization (CJK bigramming or particle stripping) in `cli/lib/lexical.mjs` behind Korean `learn --eval` checkpoints; regression-test that `버튼`, `버튼을`, `버튼이` converge on the same button docs. _Done (2026-07-03): Hangul runs >= 2 chars now emit overlapping character bigrams alongside the surface form; the review's zero-hit queries recover (`버튼` 0 → 3 ranked hits, `접근성이` 0 → 3), with unit regression coverage in `lexical.test.mjs`._
|
|
204
204
|
- **FU-2 (Q2, before Phase B index-as-source-of-truth):** Key the corpus index by corpus digest / record `designAiPath` in the payload so multiple checkouts do not overwrite each other once the index is read for content rather than staleness. _Done (2026-07-03): sidecar format bumped to version 2 (auto-invalidating v1 files); the corpus payload records resolved `designAiPath` and the learning payload the resolved `learningFile`, and `index --status` reports `sourceMatch` and treats identity mismatch as not fresh._
|
|
205
|
-
- **FU-3 (Q1, gates default promotion):** Land a ranked-search eval checkpoint so any future `--ranked` default promotion is evidence-backed and announced.
|
|
206
|
-
- **FU-4 (Q5, Phase B):** Specify and implement `~/.design-ai/config.json` as the Phase B provider config home with per-invocation `--embeddings` still required.
|
|
205
|
+
- **FU-3 (Q1, gates default promotion):** Land a ranked-search eval checkpoint so any future `--ranked` default promotion is evidence-backed and announced. _Done (2026-07-05): `design-ai search --eval-template`/`--eval [--strict]` landed in `cli/lib/search-eval.mjs` and `cli/commands/search.mjs`, mirroring the route/prompt/pack/learn eval-checkpoint pattern; cases run through `rankedSearchCorpus` (the shipped ranked path), and the default template includes the three Korean particle-form regression cases from FU-1 (버튼/버튼을/접근성이) plus English cases, doubling as a retrieval-level Hangul-bigram regression check._
|
|
206
|
+
- **FU-4 (Q5, Phase B):** Specify and implement `~/.design-ai/config.json` as the Phase B provider config home with per-invocation `--embeddings` still required. _Done (2026-07-03, Phase B): `cli/lib/local-config.mjs` reads `~/.design-ai/config.json` (or `DESIGN_AI_CONFIG_FILE`); the config only supplies the provider — every invocation still requires the explicit `--embeddings` flag to arm it._
|
|
207
|
+
|
|
208
|
+
### Ranked-default decision closure (2026-07-06)
|
|
209
|
+
|
|
210
|
+
With FU-3 landed and shipped (v4.61.0), the promotion question that Decision 1 deferred was formally revisited and **closed for the 4.x line: `search --ranked` remains opt-in across all three surfaces (CLI, MCP `design_ai_search`, SDK `search()`)**. The evidence and reasoning:
|
|
211
|
+
|
|
212
|
+
- **The two modes answer different questions.** Default `search` is grep-like — line-precise hits (`lineNumber`) that feed the `show file:line` workflow. Ranked is retrieval-like — file-level relevance (`score`, `matchedTokens`). Neither is a strict upgrade of the other; flipping the default would remove the line-precise contract rather than improve it.
|
|
213
|
+
- **The SDK semver contract now binds the default.** Since v4.59.0/v4.60.0, `search(query)`'s unranked return shape (including `lineNumber`) is pinned by the SDK contract test as a semver-stable surface. Changing any surface's default without the others desynchronizes CLI/MCP/SDK; changing all three is by definition a major-version event — exactly the "announced major-version default change, never a silent one" bar Decision 1 set.
|
|
214
|
+
- **The practical benefit is already absorbed.** Every surface where relevance ranking matters (recall injection in `prompt`/`pack`, `learn --recall`, `route --explain` related knowledge) already uses the ranked scorer internally. The remaining delta from flipping the interactive default is small, and agent consumers opt in explicitly today.
|
|
215
|
+
- **Quality stays evidence-backed either way.** The FU-3 checkpoint (`search --eval`, 6/6 passing incl. the Korean particle-form cases) runs in the release gates, so ranked quality is continuously measured without needing the default flip to justify the eval.
|
|
216
|
+
|
|
217
|
+
If a future major version (5.0) reopens this, the prerequisites are already written: flip CLI + MCP + SDK defaults together, provide a line-hit escape hatch (e.g. `--lines`), re-pin the smoke assertions and SDK contract in the same change, and cite the then-current FU-3 eval results in the CHANGELOG announcement.
|
|
207
218
|
|
|
208
219
|
### Phase B gate: cleared-with-conditions
|
|
209
220
|
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Dogfood findings — Agent SDK end-to-end flow (Phase 764)
|
|
2
|
+
|
|
3
|
+
A real design task driven entirely through the Agent SDK (`@design-ai/cli/sdk`, shipped v4.59.0–v4.61.0), per the accepted composed slice in [NEXT-SURFACE-DECISION.md](NEXT-SURFACE-DECISION.md): SDK adoption evidence and a dogfood pass in one run.
|
|
4
|
+
|
|
5
|
+
**Scope of this dogfood**: the full consumer flow `route(brief)` → `pack(brief, { withRecall })` → author the artifact → `check(artifact, { routeId })` → `learn.captureFromCheck(artifact, { routeId })`, on a genuine brief, with the learning write isolated to a temp profile via `DESIGN_AI_LEARNING_FILE`.
|
|
6
|
+
|
|
7
|
+
**The brief** (deliberately chosen from a suspected corpus gap): 커뮤니티 앱의 게시물 신고 및 사용자 차단 플로우 설계 — 신고 사유 선택, 처리 상태 안내, 차단 후 상호작용 차단 범위.
|
|
8
|
+
|
|
9
|
+
## What worked
|
|
10
|
+
|
|
11
|
+
### 1. The SDK flow itself: zero friction
|
|
12
|
+
|
|
13
|
+
Every step worked first-try in a plain Node ESM script — no CLI shell-out, no MCP server, no docs consultation beyond `docs/SDK.md`:
|
|
14
|
+
|
|
15
|
+
- `route(brief, { limit: 3, explain: true })` returned scored routes with `relatedKnowledge`.
|
|
16
|
+
- `pack(brief, { withRecall: true, recallLimit: 5, maxBytes: 60000 })` returned a bounded 15-file context bundle (60,000 bytes exactly at budget) with the plan, checklist, and recall block.
|
|
17
|
+
- `check(artifact, { routeId })` returned `warn 9/10` with a precise per-result breakdown.
|
|
18
|
+
- `learn.captureFromCheck(artifact, { routeId })` captured the single non-pass result into the temp profile (`added 1, skipped 0`) and wrote nothing anywhere else.
|
|
19
|
+
- `DESIGN_AI_LEARNING_FILE` isolation behaved exactly as documented — the real user profile was untouched.
|
|
20
|
+
|
|
21
|
+
This is the adoption story: the flow a hosted agent would run is four function calls. It is recorded as the basis for the Agent SDK walkthrough.
|
|
22
|
+
|
|
23
|
+
### 2. The eval/check loop caught the routing mismatch on its own
|
|
24
|
+
|
|
25
|
+
The one `warn` (`route-design-from-brief-design-system-foundation`) is not noise — it is the check system correctly reporting that a flow-design artifact lacks design-system-foundation evidence, which is exactly what finding 1 below predicts: the brief should never have landed on `design-from-brief` in the first place.
|
|
26
|
+
|
|
27
|
+
## Findings
|
|
28
|
+
|
|
29
|
+
### F-1. Route-table gap: flow/feature-design briefs fall through to the wrong route
|
|
30
|
+
|
|
31
|
+
`route()` scored the report/block brief `[low] score=1`, matching only the keyword `앱`, and fell back to `design-from-brief` — whose command, skills (`color-palette`, `design-system-builder`, `handoff-spec`), checklist ("Produce foundations, tokens, component baseline"), and check requirements all target a **from-scratch design-system brief**, not a **feature-flow spec**. There is no route for interaction/flow design (신고, 차단, 온보딩, 결제 플로우류) — a very common real-task class. The mismatch propagated end-to-end: wrong skills in the pack, wrong checklist, and a guaranteed check warn.
|
|
32
|
+
|
|
33
|
+
**Proposed fix**: a `flow-design` (or `feature-flow`) route with keywords for common flows (신고/차단/온보딩/가입/결제/설정/알림, report/block/onboarding/signup/checkout/settings), curated knowledge (`ui-reasoning`, `async-control`, forms/patterns), and route-specific check requirements suited to flow specs (states, edge cases, error paths) rather than token foundations.
|
|
34
|
+
|
|
35
|
+
_Done (2026-07-06): `flow-design` route added to `cli/lib/route.mjs` (`command: null`, mirroring `handoff-spec`/`design-system-qa`), with skills `ux-audit` + `design-critique`, agent `a11y-reviewer`, and curated knowledge `ui-reasoning`, `async-control`, `trust-safety-moderation` (F-3), `form-design`, `error-states`, `onboarding`. Keywords use compound Korean/English terms (`결제 플로우`, `설정 화면`, `알림 설정`, `report flow`, `signup flow`, `moderation flow`, …) chosen to avoid stealing bare terms already owned by other routes (`앱`/`서비스`/`프로덕트`/`app`/`product` → `design-from-brief`; `component`/`form` → `component-spec`). Re-ran the dogfood brief: `route()` now returns `[high] flow-design score=4` (matched `플로우`, `신고`, `차단`, `처리 상태`), with `design-from-brief` still present at `[low] score=1` (matched `앱`) as a secondary candidate — up from the prior single `[low] design-from-brief score=1` result. Added `ROUTE_REQUIREMENTS["flow-design"]` to `cli/lib/check.mjs` (states/steps, edge/error paths, entry/exit/completion — bilingual patterns mirroring house style); the Walkthrough 3 sample artifact now scores `warn 8/12` against `--route flow-design` (3 route-specific warns, as expected for a short sample), matching the walkthrough doc's existing "mostly-pass" framing. Added `examples/flow-design-report-block.md`, an expanded version of the dogfooded artifact that satisfies every example-qa bar (12/12) and is now the top-ranked example for the route. Updated the `EXPECTED_ROUTE_CATALOG_IDS` tuple in `tools/audit/smoke_assertions.py` (the single source both `package-smoke.py` and `registry-smoke.py` import from) and both README example-count badges (221 → 222, matching `check-coverage.py`'s top-level-only `examples/*.md` count — the new file lives at `examples/flow-design-report-block.md`, not under `examples/cases/`). `knowledge/COVERAGE.md` was left to its normal regeneration via `tools/audit/check-coverage.py` (not hand-edited), matching the F-3 precedent. Incidental fix: this route's 15-file pack combination exposed a pre-existing off-by-a-few-bytes bug in `cli/lib/pack.mjs`'s `takeUtf8` — truncating mid-UTF-8-character let `Buffer.toString("utf8")` insert a 3-byte U+FFFD replacement character that could be larger than the partial bytes it replaced, letting `usedBytes` exceed `maxBytes` by 1-2 bytes on multi-file packs with tight budgets; fixed by trimming to the last complete UTF-8 character boundary before decoding (`trimIncompleteUtf8Tail`), verified against ASCII, Korean (1/2/3-byte-cut), and 4-byte emoji boundary cases. Full suite green (585 tests), all 8 audits, release-metadata, package-smoke, registry-smoke self-test, and ci:local pass. Route eval: default `--eval-template` checkpoint (6 cases) still `pass 6/6` under `--eval --strict`; four other representative briefs (design-review, website-improvement, component-spec, palette-from-brand, design-system-qa, motion-design eval checkpoints) all still resolve to their prior top route unchanged._
|
|
36
|
+
|
|
37
|
+
### F-2. Recall pollution: repo-meta docs outrank design knowledge
|
|
38
|
+
|
|
39
|
+
For this brief, `relatedKnowledge` surfaced `pricing-page-design` (37.0), `korean-document-style` (22.8), `dashboard-composition` (10.8) — none relevant — and the pack's recall block selected `docs/case-study.md` (49.1, the **top** hit), `docs/project-card.md` (30.8), `docs/interview-story.md` (30.4), and `docs/integrations/codex-walkthrough.ko.md` (30.2). Those are repo-meta documents (portfolio/case-study material, integration walkthroughs), not design knowledge; three of the four are even excluded from the npm package by the `files` field. The v4.58 generated-index exclusion (`COVERAGE.md`, `INDEX.md`, `docs/reference/*`) does not cover them.
|
|
40
|
+
|
|
41
|
+
**Proposed fix**: extend the recall exclusion list from "generated index files" to a "non-knowledge docs" set — at minimum the package-excluded meta docs (`docs/case-study.md`, `docs/project-card.md`, `docs/interview-story.md`, `docs/resume-bullets.md`, …) and `docs/integrations/*` walkthroughs — for recall-injection surfaces only (raw `search` stays unfiltered, same boundary as v4.58).
|
|
42
|
+
|
|
43
|
+
_Done (2026-07-06): `isRecallExcludedDoc(relPath)` added in `cli/lib/search-ranked.mjs`, extending `isGeneratedIndexDoc` with the package-excluded repo-meta docs (the exact `!docs/*.md` set from `package.json` `files`: case-study, evidence-checklist, evidence-gallery, implementation-evidence, interview-story, project-card, project-roadmap, readme-improvement, resume-bullets) and the `docs/integrations/` prefix. `rankedSearchCorpus`'s opt-in filter option was renamed `excludeGeneratedIndex` → `excludeNonKnowledge` (only 3 internal call sites + tests referenced the old name) and now applies the broader predicate; `cli/lib/recall.mjs` (both call sites) and `cli/lib/route.mjs`'s `relatedKnowledgeFor` pass it unchanged. Re-ran the dogfood brief through the SDK: `recall()`'s `corpus.selected` and `route --explain`'s `relatedKnowledge` no longer surface `docs/case-study.md` / `docs/project-card.md` / `docs/interview-story.md` / `docs/integrations/codex-walkthrough.ko.md` — those slots are now filled by real corpus knowledge. Raw `search --ranked` is unaffected (verified unfiltered). Full suite green (585 tests), all 8 audits, release-metadata, package-smoke, registry-smoke self-test, and ci:local pass._
|
|
44
|
+
|
|
45
|
+
_Follow-up F-2b (2026-07-06): the enumerated meta-doc list proved to be whack-a-mole — the post-fix probe surfaced `docs/DOGFOOD-SDK-FINDINGS.md` itself as the new top recall hit (141.2, ~3× the best knowledge score) because this document quotes the brief's vocabulary, with `docs/inspection-*.md` and `docs/announcements/*` next in line. Resolved by the principled rule: `isRecallExcludedDoc` now excludes **everything under `docs/`** (recall injects design knowledge; the design corpus is `knowledge/`, `examples/`, `skills/`, `agents/`, `commands/` — `docs/` is product documentation). The per-file meta list was removed as dead code. Raw `search`/`search --ranked` still returns `docs/` hits unfiltered. Post-change probe: `recall()` top hit for the dogfood brief is `knowledge/patterns/trust-safety-moderation.md` (the F-3 file), with zero `docs/` entries._
|
|
46
|
+
|
|
47
|
+
### F-3. Corpus gap: no trust & safety / moderation UX knowledge
|
|
48
|
+
|
|
49
|
+
Nothing in `knowledge/` covers report/block/moderation patterns: 신고 사유 분류 설계, 처리 상태 커뮤니케이션(접수→검토→조치), 차단 의미론(양방향 범위, 비통지 원칙), 신고 남용 방지, and the Korean regulatory angle (정보통신망법 제44조의2 임시조치 30일, KCSC 처리 관행). For a product with Korean community/social apps in scope, this is a real gap — the artifact had to be authored from general principles alone.
|
|
50
|
+
|
|
51
|
+
**Proposed fix**: `knowledge/patterns/trust-safety-moderation.md` (hand-written), covering the above plus accessibility specifics for report sheets/status badges; pairs naturally with the F-1 flow route's curated knowledge.
|
|
52
|
+
|
|
53
|
+
## Verdict
|
|
54
|
+
|
|
55
|
+
- **SDK**: walkthrough-ready as-is; no API changes needed. The flow is the walkthrough.
|
|
56
|
+
- **Backlog generated (evidence-backed, in priority order)**: F-2 recall exclusion (small, mirrors the shipped v4.58 exclusion mechanism), F-3 trust-safety knowledge file (corpus), F-1 flow-design route (route table + check requirements + smoke surface updates).
|
|
@@ -118,8 +118,42 @@ Cross-check any surface work against the release gate: `npm run audit` (8/8, lin
|
|
|
118
118
|
|
|
119
119
|
## Decision record
|
|
120
120
|
|
|
121
|
-
- **Status:**
|
|
122
|
-
- **Date:** 2026-07-03
|
|
121
|
+
- **Status:** accepted and executed — both recommended surfaces shipped
|
|
122
|
+
- **Date:** 2026-07-03 (proposed) / 2026-07-05 (executed)
|
|
123
123
|
- **Deciders:** maintainer
|
|
124
124
|
- **Supersedes:** none
|
|
125
125
|
- **Related:** [`PRODUCT-READINESS.md`](PRODUCT-READINESS.md), [`external-status.md`](external-status.md), `AI-LEARNING-PHASE2.md`
|
|
126
|
+
|
|
127
|
+
### Outcome (2026-07-05)
|
|
128
|
+
|
|
129
|
+
The recommendation was executed in order and both surfaces are live:
|
|
130
|
+
|
|
131
|
+
- **CLI deepening** shipped as v4.57.0 (local retrieval memory: `index`, `search --ranked`, `--with-recall`, `learn --recall`, opt-in embedding rerank) and v4.58.0 (`route --explain` related knowledge, recall quality, corpus depth). The dogfood friction this document identified — missing corpus and missing flags — is retired; retrieval spans every CLI/MCP surface.
|
|
132
|
+
- **Agent SDK** followed exactly on the stated revisit trigger (recall interface stable across two releases): design doc [`AGENT-SDK.md`](AGENT-SDK.md), then v4.59.0 (Phase A — read-only `@design-ai/cli/sdk`, 8 verbs) and v4.60.0 (TypeScript declarations + the opt-in `learn.*` local-write namespace).
|
|
133
|
+
|
|
134
|
+
The deferred surfaces (VS Code deepening, Web UI, Figma plugin) remain deferred with the same revisit triggers. The next-surface question is open again; re-evaluate against the criteria above when the next planning window opens, factoring in that the SDK now also serves programmatic adopters.
|
|
135
|
+
|
|
136
|
+
## Re-evaluation (2026-07-06)
|
|
137
|
+
|
|
138
|
+
Scored with the same seven criteria, in the post-execution landscape: CLI deepening and the Agent SDK are shipped (v4.57–v4.61), the AI-learning Phase A follow-ups and Phase B are all closed, and the ranked-default question is decided for 4.x (see [`AI-LEARNING-PHASE2.md`](AI-LEARNING-PHASE2.md)). The SDK's existence opens a new leverage axis — programmatic consumers — that did not exist at the original scoring.
|
|
139
|
+
|
|
140
|
+
| Candidate | Leverage | Reach | Build cost | Maintenance | Distribution | Risk | Synergy | Total |
|
|
141
|
+
|---|---|---|---|---|---|---|---|---|
|
|
142
|
+
| Dogfood verification pass | 5 | 4 | 4 | 5 | 3 | 5 | 5 | **31** |
|
|
143
|
+
| SDK adoption enablement | 4 | 4 | 5 | 4 | 4 | 5 | 4 | **30** |
|
|
144
|
+
| Web UI expansion | 4 | 3 | 3 | 4 | 3 | 4 | 3 | 24 |
|
|
145
|
+
| VS Code deepening | 4 | 3 | 3 | 3 | 4 | 3 | 3 | 23 |
|
|
146
|
+
| Component spec extractor v2 | 3 | 3 | 3 | 4 | 3 | 4 | 2 | 22 |
|
|
147
|
+
| Skill-proposal apply path | 3 | 3 | 3 | 3 | 4 | 2 | 4 | 22 |
|
|
148
|
+
|
|
149
|
+
- **Dogfood verification pass** — use design-ai itself to produce a real design artifact end-to-end and capture the gaps. This is how the async-control and Korean-density corpus gaps were found; it generates the next backlog organically and exercises every shipped surface.
|
|
150
|
+
- **SDK adoption enablement** — a real, runnable SDK consumer example plus an agent-integration recipe and docs. Converts the just-shipped SDK investment into adoption; zero new product code, zero platform risk.
|
|
151
|
+
- VS Code / Web UI / Figma: unchanged constraints, unchanged revisit triggers, no new demand signal — still deferred.
|
|
152
|
+
- Spec extractor: coverage is already 90.5%, so its value has shrunk from coverage-push to maintenance automation.
|
|
153
|
+
- Skill-proposal apply path: still gated on a separate approval + archive/review design per the standing constraint.
|
|
154
|
+
|
|
155
|
+
### Recommendation (2026-07-06)
|
|
156
|
+
|
|
157
|
+
**Run the two leaders as one composed slice: a real SDK consumer example that drives a design task end-to-end (`route` → `pack` → `check` → `learn.captureFromCheck`), documented as the SDK walkthrough.** The example is simultaneously the SDK adoption artifact (D) and a dogfood pass (G): building it exercises the shipped surfaces against a genuine task, and any friction or corpus gaps it surfaces become the evidence-backed next backlog — the same loop that produced the v4.58 corpus additions.
|
|
158
|
+
|
|
159
|
+
- **Status:** accepted — maintainer signed off 2026-07-06; the composed slice is the next work item
|
package/docs/ROADMAP.md
CHANGED
|
@@ -1,5 +1,82 @@
|
|
|
1
1
|
# Roadmap
|
|
2
2
|
|
|
3
|
+
## Phase 765 — Dogfood Loop Release (v4.62.0) ✓ ready
|
|
4
|
+
|
|
5
|
+
Ships the whole Phase 764 dogfood cycle as one npm version: the `flow-design` route (F-1), the trust & safety corpus file and worked example (F-3), the principled recall exclusion of `docs/` (F-2/F-2b), the `pack` UTF-8 byte-budget fix the walkthrough guard caught, and the Agent SDK walkthrough itself (en/ko).
|
|
6
|
+
|
|
7
|
+
### Verified
|
|
8
|
+
- All 8 audits passed.
|
|
9
|
+
- `npm run release:check` (unit tests incl. flow-example guard and pack budget-sweep invariant, strict audits, whitespace, package contents, release metadata, release self-tests, packed-tarball smoke).
|
|
10
|
+
- `npm run release:metadata`.
|
|
11
|
+
- `git diff --check`.
|
|
12
|
+
- Route eval checkpoint 6/6 and representative-brief routing unchanged; search eval 6/6.
|
|
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.61.0 → 4.62.0.
|
|
17
|
+
- `vscode-extension/package.json`: remains 0.4.1.
|
|
18
|
+
|
|
19
|
+
### What this enables
|
|
20
|
+
- npm users get the closed dogfood loop: flow briefs route to a purpose-built route with flow-appropriate checks, recall injection is design-knowledge-only, Korean community products get the trust & safety regulatory floor, `pack`'s byte budget is a hard guarantee, and the SDK has a verified end-to-end walkthrough.
|
|
21
|
+
|
|
22
|
+
### What's still ahead
|
|
23
|
+
- Next dogfood pass on a different task class (the loop is now cheap to rerun), or the next planning window per [NEXT-SURFACE-DECISION.md](NEXT-SURFACE-DECISION.md).
|
|
24
|
+
|
|
25
|
+
## Phase 764 — SDK adoption + dogfood composed slice (implemented, unreleased)
|
|
26
|
+
|
|
27
|
+
Executes the composed slice accepted in [NEXT-SURFACE-DECISION.md](NEXT-SURFACE-DECISION.md) (re-evaluation of 2026-07-06): a real design task driven end-to-end through the Agent SDK, recorded as both the SDK adoption walkthrough and a dogfood pass.
|
|
28
|
+
|
|
29
|
+
### Delivered
|
|
30
|
+
- [x] Dogfood run on a genuine brief (커뮤니티 앱 신고·차단 플로우) through `route` → `pack --withRecall` → author → `check` → `learn.captureFromCheck` with temp-profile isolation; SDK verdict: zero friction. Findings recorded in [DOGFOOD-SDK-FINDINGS.md](DOGFOOD-SDK-FINDINGS.md): F-1 flow-design route gap, F-2 recall pollution by repo-meta docs, F-3 trust & safety corpus gap.
|
|
31
|
+
- [x] `docs/integrations/agent-sdk-walkthrough.md` + `.ko.md` — copy-pastable end-to-end consumer script with measured outputs (doc outputs verified against a live run of the embedded sample artifact), registered in the integration-completeness audit and mkdocs nav; README(+ko) SDK row links it; `docs/SDK.md` gains a walkthrough pointer.
|
|
32
|
+
- [x] `cli/sdk/flow-example.test.mjs` — the walkthrough flow as a `node --test` regression guard (shape-based assertions; 583/583 total).
|
|
33
|
+
|
|
34
|
+
### Backlog closed (2026-07-06)
|
|
35
|
+
- [x] F-2 recall meta-doc exclusion (`isRecallExcludedDoc`, option renamed `excludeNonKnowledge`), then F-2b: the enumerated list proved whack-a-mole (the findings doc itself became the top recall hit at 3× the best knowledge score), resolved by the principled rule — recall injection excludes everything under `docs/`; the design corpus is `knowledge/`, `examples/`, `skills/`, `agents/`, `commands/`. Raw `search --ranked` unchanged.
|
|
36
|
+
- [x] F-3 `knowledge/patterns/trust-safety-moderation.md` (corpus 95): report flow, status pipeline incl. 정보통신망법 제44조의2 임시조치, block semantics (non-notification), abuse prevention, accessibility floor. Post-landing probe: top `relatedKnowledge` hit for the dogfood brief.
|
|
37
|
+
- [x] F-1 `flow-design` route (`command: null`, compound keywords to avoid hijacking, flow-specific check requirements, `examples/flow-design-report-block.md` at example-qa 12/12). Dogfood brief now routes `[high] flow-design score=4` (was `[low] design-from-brief score=1`); route eval 6/6 and representative briefs unchanged.
|
|
38
|
+
- [x] Bonus: the walkthrough's regression guard (`cli/sdk/flow-example.test.mjs`) caught a real pre-existing `pack` bug — mid-UTF-8-character truncation could exceed `maxBytes` via U+FFFD replacement growth; fixed with a character-boundary trim (RED-verified: the guard fails on the unfixed code), plus a Korean-content budget-sweep invariant test.
|
|
39
|
+
|
|
40
|
+
### Remaining
|
|
41
|
+
- [ ] Release: bundle into the next npm version.
|
|
42
|
+
|
|
43
|
+
## Phase 763 — Ranked-search Eval Checkpoint Release (v4.61.0) ✓ ready
|
|
44
|
+
|
|
45
|
+
Ships the FU-3 ranked-search eval checkpoint (Phase 762) as an npm version: `search --eval-template` / `search --eval [--strict]` over the shipped BM25 path, with the Korean particle-form regression cases in the default template. Additive and backward-compatible; default `search` behavior is unchanged. With this release the AI-learning Phase A follow-ups (FU-1–FU-4) are all closed and published.
|
|
46
|
+
|
|
47
|
+
### Verified
|
|
48
|
+
- All 8 audits passed.
|
|
49
|
+
- `npm run release:check` (unit tests incl. search-eval and search-command suites, strict audits, whitespace, package contents, release metadata, release self-tests, packed-tarball smoke).
|
|
50
|
+
- `npm run release:metadata`.
|
|
51
|
+
- `git diff --check`.
|
|
52
|
+
- Main-branch GitHub Actions (`Design-AI audit`, `Deploy doc site`) passed for the constituent commits.
|
|
53
|
+
|
|
54
|
+
### Versions
|
|
55
|
+
- `package.json` + `.claude-plugin/plugin.json`: 4.60.0 → 4.61.0.
|
|
56
|
+
- `vscode-extension/package.json`: remains 0.4.1.
|
|
57
|
+
|
|
58
|
+
### What this enables
|
|
59
|
+
- The `--ranked` default-promotion decision becomes evidence-backed for npm users too: the published CLI carries the checkpoint tooling, so the eval can be re-run against any installed version — including Korean agglutinative-query regressions — before and after such a change.
|
|
60
|
+
|
|
61
|
+
### What's still ahead
|
|
62
|
+
- ~~The `--ranked` default-promotion decision itself~~ — decided 2026-07-06: **opt-in stays for the 4.x line** across CLI/MCP/SDK; any future flip is a coordinated 5.0 major event. Full rationale recorded in [AI-LEARNING-PHASE2.md](AI-LEARNING-PHASE2.md) ("Ranked-default decision closure").
|
|
63
|
+
- Next-surface re-evaluation per the updated decision record in [NEXT-SURFACE-DECISION.md](NEXT-SURFACE-DECISION.md).
|
|
64
|
+
|
|
65
|
+
## Phase 762 — Ranked-search eval checkpoint (FU-3, implemented, unreleased)
|
|
66
|
+
|
|
67
|
+
Closes the last open follow-up from the AI-learning Phase A review ([AI-LEARNING-PHASE2.md](AI-LEARNING-PHASE2.md), FU-3): `design-ai search` gains `--eval-template` / `--eval [--strict]` checkpoint modes mirroring the route/prompt/pack/learn eval pattern, run through the shipped ranked (BM25) search path. This is the standing evidence artifact for any future decision to promote `--ranked` to the default; with FU-1/FU-2/FU-4 already annotated done, all four Phase A follow-ups are now closed.
|
|
68
|
+
|
|
69
|
+
### Delivered
|
|
70
|
+
- [x] `cli/lib/search-eval.mjs` — `SEARCH_EVAL_VERSION = 1`, `buildSearchEvalTemplate()`, `searchEvalReport()` over `rankedSearchCorpus`; route-style payload normalization and error messages. Case schema: `id`, `query`, optional `dirs`/`limit`, assertions `expectRelPathIn` / `minHits` / `matchedTokenIncludes`.
|
|
71
|
+
- [x] Default template includes the FU-1 Korean particle-form regression cases (`버튼`, `버튼을`, `접근성이`) plus three English cases — so the eval doubles as the retrieval-level Hangul-bigram regression. All six verified passing against the shipped corpus (`--strict` exit 0).
|
|
72
|
+
- [x] CLI wiring in `cli/lib/search.mjs` / `cli/commands/search.mjs` (`--eval-template`, `--eval`, `--strict`, `--from-file`, `--stdin`, mutual-exclusivity validation), help topic usage line, and the shared `smoke_assertions.py` pins (4 spots — shared constants, so both package and registry smokes stay aligned).
|
|
73
|
+
- [x] Tests: `search-eval.test.mjs` (template, pass/warn/fail reports, malformed payloads) and `search-command.test.mjs` (CLI-level, strict exit semantics). 578/578 total.
|
|
74
|
+
- [x] `docs/AI-LEARNING-PHASE2.md` FU-3 and FU-4 done annotations.
|
|
75
|
+
|
|
76
|
+
### Remaining
|
|
77
|
+
- [ ] Release: bundle into the next npm version.
|
|
78
|
+
- [ ] The `--ranked` default-promotion decision itself — a future, separate call that this checkpoint now makes evidence-backed.
|
|
79
|
+
|
|
3
80
|
## Phase 761 — Agent SDK types + Phase B Release (v4.60.0) ✓ ready
|
|
4
81
|
|
|
5
82
|
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.
|