@tea-agent/loop-agent 0.20.0 → 0.20.1-beta.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/CHANGELOG.md +7 -0
- package/dist/executors/shell-executor.js +1 -0
- package/dist/worker/observe/spec-evidence.js +33 -0
- package/dist/worker/observe/static/views/dag-inspector.js +67 -4
- package/dist/workflows/dag/frontend-prewrite-gate.js +97 -2
- package/dist/workflows/dag/frontend-project-capability.js +6 -2
- package/dist/workflows/dag/init-hybrid.js +1 -0
- package/dist/workflows/dag/types.js +3 -0
- package/package.json +1 -1
- package/skills/frontend-design-review/SKILL.md +5 -3
- package/skills/frontend-design-review/references/review-checklist.md +3 -2
- package/skills/frontend-implementation/references/design-spec.md +16 -8
- package/skills/frontend-review/SKILL.md +7 -1
- package/skills/frontend-review/references/review-findings.md +5 -1
- package/skills/frontend-verification/SKILL.md +5 -3
- package/skills/frontend-verification/references/verification-checklist.md +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
### 新增
|
|
6
|
+
|
|
7
|
+
- 前端规范读取将知识库与 `openspec/` 作为并列来源:无论知识库是否可用或命中,均继续递归枚举 `<repoRoot>/openspec/**` 并读取索引与任务相关规范正文。`discoverFrontendProjectCapability` 在没有 `package.json` 或 `package.json` 不可读时仍能发现 `openspec/**` 规范候选。
|
|
8
|
+
- 前端预写门禁会校验 plan/design-review 的真实读取事件;生成期存在 `openspec` 规范候选但没有成功读取匹配文件时,在 writer 执行前阻断 DAG 并列出候选路径、检查节点和原因。
|
|
9
|
+
- Dashboard 规范证据 API/UI 分别展示知识库查询、`openspec` 检索和 `openspec` 成功读取证据,三种类别独立展示。
|
|
10
|
+
- 前端规范证据 API 新增 `openspecReads` 与 `openspecSearches` 字段,与通用 `specReads`/`specSearches` 并行。
|
|
11
|
+
|
|
5
12
|
## [0.20.0] - 2026-07-23
|
|
6
13
|
|
|
7
14
|
### 新增
|
|
@@ -769,6 +769,7 @@ export async function executeDagShellNode(input, meta) {
|
|
|
769
769
|
runDir: meta.runDir,
|
|
770
770
|
config: shell.frontendPrewriteGate,
|
|
771
771
|
sourceBinding: meta.spec.sourceBinding,
|
|
772
|
+
repoRoot: input.cwd,
|
|
772
773
|
});
|
|
773
774
|
return { ok: true, stdout: formatFrontendPrewriteGateStdout(result), stderr: "", failureCategory: "success", durationMs: Date.now() - started };
|
|
774
775
|
}
|
|
@@ -47,6 +47,23 @@ function isSpecFilePath(filePath) {
|
|
|
47
47
|
function isKnowledgeBaseTool(toolName) {
|
|
48
48
|
return KB_CONNECTOR_TOOLS.has(toolName);
|
|
49
49
|
}
|
|
50
|
+
/** A repo-relative path references <repoRoot>/openspec/**. */
|
|
51
|
+
function isOpenspecPath(filePath) {
|
|
52
|
+
const normalized = filePath.replaceAll(path.sep, "/");
|
|
53
|
+
return normalized === "openspec" || normalized.startsWith("openspec/");
|
|
54
|
+
}
|
|
55
|
+
/** A search query targets the openspec/ directory. */
|
|
56
|
+
function isOpenspecSearch(query, searchPath) {
|
|
57
|
+
const lower = query.toLowerCase();
|
|
58
|
+
const normalizedPath = searchPath?.replaceAll(path.sep, "/").toLowerCase();
|
|
59
|
+
return (normalizedPath === "openspec" ||
|
|
60
|
+
normalizedPath?.startsWith("openspec/") === true ||
|
|
61
|
+
lower === "openspec" ||
|
|
62
|
+
lower === "openspec/" ||
|
|
63
|
+
lower.startsWith("openspec/") ||
|
|
64
|
+
lower.includes("openspec/**") ||
|
|
65
|
+
lower.includes("openspec/*"));
|
|
66
|
+
}
|
|
50
67
|
function resolveSessionEventsPath(repoRoot, dagRunId, nodeId) {
|
|
51
68
|
if (!isSafeObservabilityIdentifier(dagRunId) || !isSafeObservabilityIdentifier(nodeId)) {
|
|
52
69
|
return null;
|
|
@@ -278,6 +295,9 @@ export async function extractSpecEvidence(repoRoot, dagRunId, nodeId) {
|
|
|
278
295
|
}
|
|
279
296
|
// Search/scan tool calls (grep, find, ls, glob)
|
|
280
297
|
if (["grep", "find", "ls", "glob"].includes(toolName)) {
|
|
298
|
+
const searchPath = typeof pairedInput.path === "string"
|
|
299
|
+
? pairedInput.path
|
|
300
|
+
: undefined;
|
|
281
301
|
const query = typeof pairedInput.pattern === "string"
|
|
282
302
|
? pairedInput.pattern
|
|
283
303
|
: typeof pairedInput.query === "string"
|
|
@@ -288,6 +308,7 @@ export async function extractSpecEvidence(repoRoot, dagRunId, nodeId) {
|
|
|
288
308
|
searches.push({
|
|
289
309
|
tool: toolName,
|
|
290
310
|
query,
|
|
311
|
+
path: searchPath,
|
|
291
312
|
timestamp: ts,
|
|
292
313
|
});
|
|
293
314
|
if (toolCallId)
|
|
@@ -341,6 +362,16 @@ export async function extractSpecEvidence(repoRoot, dagRunId, nodeId) {
|
|
|
341
362
|
if (status === "no-evidence") {
|
|
342
363
|
summaryLines.push("未观察到任何规范证据:无 skill 注入、无文件读取、无检索操作。");
|
|
343
364
|
}
|
|
365
|
+
// Separate openspec/** reads and searches for Dashboard display.
|
|
366
|
+
// Knowledge base and openspec are parallel sources.
|
|
367
|
+
const openspecReads = specReads.filter((r) => isOpenspecPath(r.path));
|
|
368
|
+
const openspecSearches = searches.filter((s) => isOpenspecSearch(s.query, s.path));
|
|
369
|
+
if (openspecReads.length > 0) {
|
|
370
|
+
summaryLines.push(`openspec 已读取 ${openspecReads.length} 个文件:${openspecReads.map((r) => r.path).join("、")}`);
|
|
371
|
+
}
|
|
372
|
+
if (openspecSearches.length > 0) {
|
|
373
|
+
summaryLines.push(`openspec 检索 ${openspecSearches.length} 次。`);
|
|
374
|
+
}
|
|
344
375
|
return {
|
|
345
376
|
dagRunId,
|
|
346
377
|
nodeId,
|
|
@@ -352,6 +383,8 @@ export async function extractSpecEvidence(repoRoot, dagRunId, nodeId) {
|
|
|
352
383
|
specReads,
|
|
353
384
|
specSearches: searches,
|
|
354
385
|
knowledgeBaseQueries: kbQueries,
|
|
386
|
+
openspecReads,
|
|
387
|
+
openspecSearches,
|
|
355
388
|
summary: summaryLines.join(" "),
|
|
356
389
|
};
|
|
357
390
|
}
|
|
@@ -233,10 +233,15 @@ async function renderSpecEvidence(content, dagRunId, nodeId, cachedEvidence) {
|
|
|
233
233
|
}
|
|
234
234
|
content.appendChild(statusSection);
|
|
235
235
|
|
|
236
|
-
const appendListSection = (title, entries, renderEntry) => {
|
|
237
|
-
if (!entries?.length) return;
|
|
236
|
+
const appendListSection = (title, entries, renderEntry, emptyMessage = "") => {
|
|
237
|
+
if (!entries?.length && !emptyMessage) return;
|
|
238
238
|
const section = el("div", "spec-evidence-section");
|
|
239
239
|
section.appendChild(el("h4", null, title));
|
|
240
|
+
if (!entries?.length) {
|
|
241
|
+
section.appendChild(el("p", "spec-evidence-summary", emptyMessage));
|
|
242
|
+
content.appendChild(section);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
240
245
|
const list = el("ul", "spec-evidence-list");
|
|
241
246
|
for (const entry of entries) list.appendChild(renderEntry(entry));
|
|
242
247
|
section.appendChild(list);
|
|
@@ -273,9 +278,15 @@ async function renderSpecEvidence(content, dagRunId, nodeId, cachedEvidence) {
|
|
|
273
278
|
return item;
|
|
274
279
|
});
|
|
275
280
|
appendListSection("显式需求编号", evidence.sourceBinding?.requirementIds, (id) => el("li", null, id));
|
|
281
|
+
const openspecReadPaths = new Set(
|
|
282
|
+
(evidence.openspecReads ?? []).map((entry) => entry.path),
|
|
283
|
+
);
|
|
284
|
+
const otherSpecReads = (evidence.specReads ?? []).filter(
|
|
285
|
+
(entry) => !openspecReadPaths.has(entry.path),
|
|
286
|
+
);
|
|
276
287
|
appendListSection(
|
|
277
|
-
|
|
278
|
-
|
|
288
|
+
`其他已读取规范文件(${otherSpecReads.length})`,
|
|
289
|
+
otherSpecReads,
|
|
279
290
|
(read) => {
|
|
280
291
|
const item = el("li", null);
|
|
281
292
|
const button = document.createElement("button");
|
|
@@ -327,6 +338,58 @@ async function renderSpecEvidence(content, dagRunId, nodeId, cachedEvidence) {
|
|
|
327
338
|
item.append(icon, document.createTextNode(` ${query.connector}`));
|
|
328
339
|
return item;
|
|
329
340
|
},
|
|
341
|
+
"unavailable:未观察到知识库查询;connector 可能未配置或未调用。",
|
|
342
|
+
);
|
|
343
|
+
|
|
344
|
+
// openspec reads are displayed independently from skill/docs reads.
|
|
345
|
+
appendListSection(
|
|
346
|
+
`openspec 已读取(${evidence.openspecReads?.length ?? 0})`,
|
|
347
|
+
evidence.openspecReads,
|
|
348
|
+
(read) => {
|
|
349
|
+
const item = el("li", null);
|
|
350
|
+
const button = document.createElement("button");
|
|
351
|
+
button.type = "button";
|
|
352
|
+
button.className = "spec-evidence-file-btn";
|
|
353
|
+
button.setAttribute("data-source", "read");
|
|
354
|
+
button.setAttribute("data-path", read.path);
|
|
355
|
+
if (read.timestamp) {
|
|
356
|
+
button.setAttribute("data-read-at", read.timestamp);
|
|
357
|
+
}
|
|
358
|
+
button.id = `spec-evidence-btn-read-${read.path}`;
|
|
359
|
+
const icon = el("i", "ri-book-open-line");
|
|
360
|
+
icon.setAttribute("aria-hidden", "true");
|
|
361
|
+
button.append(icon, el("code", null, read.path));
|
|
362
|
+
if (read.timestamp) {
|
|
363
|
+
button.appendChild(
|
|
364
|
+
el(
|
|
365
|
+
"span",
|
|
366
|
+
"spec-evidence-time",
|
|
367
|
+
formatSessionEventTime({ timestamp: read.timestamp }),
|
|
368
|
+
),
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
button.addEventListener("click", () =>
|
|
372
|
+
onSpecEvidenceFileClick(content, dagRunId, nodeId, "read", read.path, button),
|
|
373
|
+
);
|
|
374
|
+
item.appendChild(button);
|
|
375
|
+
return item;
|
|
376
|
+
},
|
|
377
|
+
evidence.openspecSearches?.length
|
|
378
|
+
? "已检索但未观察到 openspec 成功读取;若生成期存在候选,预写门禁会阻断 writer。"
|
|
379
|
+
: "unavailable:未观察到 openspec 成功读取。",
|
|
380
|
+
);
|
|
381
|
+
|
|
382
|
+
appendListSection(
|
|
383
|
+
`openspec 检索(${evidence.openspecSearches?.length ?? 0})`,
|
|
384
|
+
evidence.openspecSearches,
|
|
385
|
+
(search) => {
|
|
386
|
+
const item = el("li", null);
|
|
387
|
+
const icon = el("i", "ri-search-line");
|
|
388
|
+
icon.setAttribute("aria-hidden", "true");
|
|
389
|
+
item.append(icon, document.createTextNode(` ${search.tool}: ${search.query}`));
|
|
390
|
+
return item;
|
|
391
|
+
},
|
|
392
|
+
"unavailable:未观察到 openspec 检索。",
|
|
330
393
|
);
|
|
331
394
|
|
|
332
395
|
if (evidence.status === "source-bound" || evidence.status === "spec-injected" || evidence.status === "no-evidence") {
|
|
@@ -30,6 +30,80 @@ function firstVerdictLine(text) {
|
|
|
30
30
|
}
|
|
31
31
|
return "";
|
|
32
32
|
}
|
|
33
|
+
function eventArgs(event) {
|
|
34
|
+
return event.args ?? event.toolInput ?? event.input ?? {};
|
|
35
|
+
}
|
|
36
|
+
function toRepoRelativePath(filePath, repoRoot) {
|
|
37
|
+
const resolved = path.resolve(repoRoot, filePath);
|
|
38
|
+
const relative = path.relative(repoRoot, resolved);
|
|
39
|
+
if (!relative || relative.startsWith("..") || path.isAbsolute(relative)) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
return relative.replaceAll(path.sep, "/");
|
|
43
|
+
}
|
|
44
|
+
function isFailedToolResult(event) {
|
|
45
|
+
return (event.isError === true ||
|
|
46
|
+
event.toolResult?.error != null ||
|
|
47
|
+
event.toolResult?.ok === false ||
|
|
48
|
+
event.result?.error != null ||
|
|
49
|
+
event.result?.ok === false);
|
|
50
|
+
}
|
|
51
|
+
async function checkOpenspecReadEvidence(input) {
|
|
52
|
+
const { runDir, candidatePaths, planNodeId, reviewNodeId, repoRoot } = input;
|
|
53
|
+
if (candidatePaths.length === 0)
|
|
54
|
+
return [];
|
|
55
|
+
const matched = new Set();
|
|
56
|
+
const normalizedCandidates = new Set(candidatePaths
|
|
57
|
+
.map((candidate) => toRepoRelativePath(candidate, repoRoot))
|
|
58
|
+
.filter((candidate) => Boolean(candidate?.startsWith("openspec/"))));
|
|
59
|
+
for (const nodeId of [planNodeId, reviewNodeId]) {
|
|
60
|
+
const eventsPath = path.join(runDir, nodeId, "session-events.jsonl");
|
|
61
|
+
try {
|
|
62
|
+
const raw = await readFile(eventsPath, "utf8");
|
|
63
|
+
const startedReads = new Map();
|
|
64
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
65
|
+
const trimmed = line.trim();
|
|
66
|
+
if (!trimmed)
|
|
67
|
+
continue;
|
|
68
|
+
let event;
|
|
69
|
+
try {
|
|
70
|
+
event = JSON.parse(trimmed);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (event.type === "tool_execution_start" &&
|
|
76
|
+
event.toolName === "read" &&
|
|
77
|
+
event.toolCallId) {
|
|
78
|
+
const readPath = eventArgs(event).path;
|
|
79
|
+
if (typeof readPath === "string") {
|
|
80
|
+
startedReads.set(event.toolCallId, readPath);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
else if (event.type === "tool_execution_end" &&
|
|
84
|
+
event.toolName === "read" &&
|
|
85
|
+
event.toolCallId !== undefined) {
|
|
86
|
+
const readPath = startedReads.get(event.toolCallId);
|
|
87
|
+
const relativePath = readPath
|
|
88
|
+
? toRepoRelativePath(readPath, repoRoot)
|
|
89
|
+
: null;
|
|
90
|
+
if (relativePath &&
|
|
91
|
+
!isFailedToolResult(event) &&
|
|
92
|
+
normalizedCandidates.has(relativePath)) {
|
|
93
|
+
matched.add(relativePath);
|
|
94
|
+
}
|
|
95
|
+
startedReads.delete(event.toolCallId);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
if (error.code === "ENOENT")
|
|
101
|
+
continue;
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return [...matched];
|
|
106
|
+
}
|
|
33
107
|
export async function runFrontendPrewriteGate(input) {
|
|
34
108
|
const planNodeId = await selectNode(input.runDir, input.config.planFromNodeId, input.config.planFallbackFromNodeIds);
|
|
35
109
|
const reviewNodeId = await selectNode(input.runDir, input.config.reviewFromNodeId, input.config.reviewFallbackFromNodeIds);
|
|
@@ -55,6 +129,18 @@ export async function runFrontendPrewriteGate(input) {
|
|
|
55
129
|
if (!input.config.allowedMockStrategies.includes(contract.mockApi.strategy)) {
|
|
56
130
|
throw new Error(`frontend prewrite gate blocked mock strategy ${contract.mockApi.strategy}; allowed=${input.config.allowedMockStrategies.join(",")}`);
|
|
57
131
|
}
|
|
132
|
+
const candidatePaths = input.config.openspecCandidatePaths ?? [];
|
|
133
|
+
const openspecReadPaths = await checkOpenspecReadEvidence({
|
|
134
|
+
runDir: input.runDir,
|
|
135
|
+
candidatePaths,
|
|
136
|
+
planNodeId,
|
|
137
|
+
reviewNodeId,
|
|
138
|
+
repoRoot: input.repoRoot ?? process.cwd(),
|
|
139
|
+
});
|
|
140
|
+
if (candidatePaths.length > 0 && openspecReadPaths.length === 0) {
|
|
141
|
+
const checkedNodes = [planNodeId, reviewNodeId].join(", ");
|
|
142
|
+
throw new Error(`openspec gate blocked: ${candidatePaths.length} candidate(s) [${candidatePaths.join(", ")}] not read by ${checkedNodes}; writer not authorized.`);
|
|
143
|
+
}
|
|
58
144
|
return {
|
|
59
145
|
ok: true,
|
|
60
146
|
planNodeId,
|
|
@@ -62,10 +148,12 @@ export async function runFrontendPrewriteGate(input) {
|
|
|
62
148
|
verdict,
|
|
63
149
|
mockStrategy: contract.mockApi.strategy,
|
|
64
150
|
artifact,
|
|
151
|
+
openspecReadPaths,
|
|
152
|
+
openspecCandidatePaths: candidatePaths,
|
|
65
153
|
};
|
|
66
154
|
}
|
|
67
155
|
export function formatFrontendPrewriteGateStdout(result) {
|
|
68
|
-
|
|
156
|
+
const lines = [
|
|
69
157
|
"Frontend prewrite gate: pass",
|
|
70
158
|
`Plan: ${result.planNodeId}`,
|
|
71
159
|
`Review: ${result.reviewNodeId}`,
|
|
@@ -73,5 +161,12 @@ export function formatFrontendPrewriteGateStdout(result) {
|
|
|
73
161
|
`Structured artifact: ${result.artifact.path}`,
|
|
74
162
|
`Schema: ${result.artifact.schemaId}`,
|
|
75
163
|
`SHA-256: ${result.artifact.sha256}`,
|
|
76
|
-
]
|
|
164
|
+
];
|
|
165
|
+
if (result.openspecReadPaths.length > 0) {
|
|
166
|
+
lines.push(`openspec read: ${result.openspecReadPaths.join(", ")}`);
|
|
167
|
+
}
|
|
168
|
+
else if (result.openspecCandidatePaths.length === 0) {
|
|
169
|
+
lines.push("openspec: unavailable");
|
|
170
|
+
}
|
|
171
|
+
return lines.join("\n");
|
|
77
172
|
}
|
|
@@ -105,6 +105,7 @@ export async function discoverFrontendProjectCapability(repoRoot) {
|
|
|
105
105
|
const pkgPath = path.join(repoRoot, "package.json");
|
|
106
106
|
const pkgRaw = await readJson(pkgPath);
|
|
107
107
|
if (!pkgRaw || typeof pkgRaw !== "object") {
|
|
108
|
+
const openspec = await listOpenspec(repoRoot);
|
|
108
109
|
const base = {
|
|
109
110
|
schemaVersion: 1,
|
|
110
111
|
framework: "unknown",
|
|
@@ -115,14 +116,17 @@ export async function discoverFrontendProjectCapability(repoRoot) {
|
|
|
115
116
|
testRunner: [],
|
|
116
117
|
mock: [],
|
|
117
118
|
a11y: { status: "unknown", tools: [], evidencePaths: [] },
|
|
118
|
-
evidencePaths:
|
|
119
|
+
evidencePaths: openspec.slice(0, 5),
|
|
119
120
|
reasons: ["package.json missing or unreadable"],
|
|
120
121
|
designEvidence: {
|
|
121
|
-
normativePaths:
|
|
122
|
+
normativePaths: openspec,
|
|
122
123
|
advisoryPaths: [],
|
|
123
124
|
conflicts: [],
|
|
124
125
|
},
|
|
125
126
|
};
|
|
127
|
+
if (openspec.length) {
|
|
128
|
+
base.reasons.push(`openspec/ files discovered: ${openspec.slice(0, 5).join(", ")}`);
|
|
129
|
+
}
|
|
126
130
|
return { ...base, adapterGuidance: buildAdapterGuidance(base) };
|
|
127
131
|
}
|
|
128
132
|
evidencePaths.push("package.json");
|
|
@@ -2041,6 +2041,7 @@ function buildFrontendHybridDagFromTask(sources) {
|
|
|
2041
2041
|
: ["native", "browser-intercept", "request-adapter", "not-needed"],
|
|
2042
2042
|
artifactName: "frontend-implementation-contract.json",
|
|
2043
2043
|
outputDir: "contracts",
|
|
2044
|
+
openspecCandidatePaths: sources.frontendProjectCapability?.designEvidence.normativePaths ?? [],
|
|
2044
2045
|
},
|
|
2045
2046
|
cwd: ".",
|
|
2046
2047
|
timeoutMs: 60000,
|
|
@@ -126,6 +126,9 @@ export const dagFrontendPrewriteGateSchema = z.object({
|
|
|
126
126
|
allowedMockStrategies: z.array(z.enum(["native", "browser-intercept", "request-adapter", "not-needed"])).min(1),
|
|
127
127
|
artifactName: z.string().regex(/^[a-z0-9][a-z0-9._-]*\.json$/),
|
|
128
128
|
outputDir: z.string().regex(/^[a-z0-9][a-z0-9._-]*$/),
|
|
129
|
+
openspecCandidatePaths: z
|
|
130
|
+
.array(z.string().regex(/^openspec\/.+/, "openspec candidate must be repo-relative"))
|
|
131
|
+
.default([]),
|
|
129
132
|
});
|
|
130
133
|
export const dagFrontendVerificationBundleSchema = z.object({
|
|
131
134
|
schemaVersion: z.literal(1),
|
package/package.json
CHANGED
|
@@ -10,9 +10,11 @@ references:
|
|
|
10
10
|
# Frontend Design Review
|
|
11
11
|
|
|
12
12
|
For frontend design review nodes. Read the checklist; audit contract, scout, Mock
|
|
13
|
-
strategy, effective plan, task bounds, and traceable design evidence.
|
|
14
|
-
base
|
|
15
|
-
|
|
13
|
+
strategy, effective plan, task bounds, and traceable design evidence.
|
|
14
|
+
Knowledge base and `openspec/` are parallel specification sources. Query the
|
|
15
|
+
knowledge-base connector when available; regardless of result, also search and
|
|
16
|
+
read `<repoRoot>/openspec/**` before accepting repository conventions. The
|
|
17
|
+
connector format is TODO: never invent results.
|
|
16
18
|
|
|
17
19
|
## Verdict Contract
|
|
18
20
|
|
|
@@ -9,8 +9,9 @@
|
|
|
9
9
|
## Project Fit
|
|
10
10
|
|
|
11
11
|
- Reuse components, hooks, API helpers, mocks, schemas, router patterns, tokens, and theme rules.
|
|
12
|
-
- Cite knowledge
|
|
13
|
-
|
|
12
|
+
- Cite knowledge base and `openspec/` as parallel sources; failed or empty
|
|
13
|
+
knowledge queries must still search `<repoRoot>/openspec/**`.
|
|
14
|
+
- Record source status, query terms, paths/headings, conflicts, authorized deps, and allowed paths for both.
|
|
14
15
|
|
|
15
16
|
## Interaction / Quality
|
|
16
17
|
|
|
@@ -2,14 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
## Required Source Sequence
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
5
|
+
Knowledge base and `openspec/` are parallel specification sources:
|
|
6
|
+
|
|
7
|
+
1. Attempt the configured component/design knowledge-base query first when a
|
|
8
|
+
connector is available in the execution environment.
|
|
9
|
+
2. Regardless of knowledge-base success, failure, timeout, no match, or no
|
|
10
|
+
configuration, also recursively search the project root's exact `openspec/`
|
|
11
|
+
directory for index files and task-relevant specification content.
|
|
12
|
+
3. Treat relevant matches from both sources as the current project's
|
|
13
|
+
specification for this run.
|
|
14
|
+
4. Only then use component source, tokens, stories, tests, and pages as
|
|
15
|
+
non-normative repository fallback.
|
|
16
|
+
|
|
17
|
+
Never skip `openspec/` directly to neighboring-code conventions, even when a
|
|
18
|
+
knowledge-base query returned results. Report source conflicts instead of
|
|
19
|
+
combining them. Explicit task requirements remain the contract; flag conflicts
|
|
20
|
+
with knowledge-base or `openspec/` rules.
|
|
13
21
|
|
|
14
22
|
## Knowledge Base Connection — TODO
|
|
15
23
|
|
|
@@ -34,7 +34,13 @@ required check, forbidden write, or unmet acceptance criterion forces revision.
|
|
|
34
34
|
and no false real-integration claim. `not-needed` needs applicable real/no-remote
|
|
35
35
|
evidence, or an explicit default-auto skipped-Mock rationale with the Real
|
|
36
36
|
Integration Gap preserved when no project Mock capability is confirmed.
|
|
37
|
-
- Component/design claims require traceable
|
|
37
|
+
- Component/design claims require traceable evidence from two parallel sources:
|
|
38
|
+
knowledge base and `<repoRoot>/openspec/**`. Query the knowledge-base connector
|
|
39
|
+
when available; regardless of result, also read `<repoRoot>/openspec/**`.
|
|
40
|
+
The connector format is TODO; never claim a query or fallback search without
|
|
41
|
+
evidence. Execute explicit `grep`/`find` to locate spec files and `read` to
|
|
42
|
+
load them before referencing their rules. Only successful `read` tool calls are
|
|
43
|
+
observable as "已读取规范文件" in the spec-evidence inspector.
|
|
38
44
|
- Treat shell exit status as authoritative. Do not edit files.
|
|
39
45
|
|
|
40
46
|
## Evidence And Output
|
|
@@ -11,7 +11,11 @@
|
|
|
11
11
|
- Cite tight file locations, exact commands/results, or named DAG artifacts.
|
|
12
12
|
- Never invent evidence; name the missing check. An implementation summary is not the actual diff.
|
|
13
13
|
- Failed required static/behavior verification is at least Important unless proven unrelated.
|
|
14
|
-
-
|
|
14
|
+
- Treat the knowledge base and `openspec/` as parallel sources. Record
|
|
15
|
+
connector/query, source ID/version, and retrieval time for knowledge-base
|
|
16
|
+
claims. Regardless of that result, evidence must show
|
|
17
|
+
`<repoRoot>/openspec/**` search terms and matched paths/headings; label
|
|
18
|
+
`openspec`, `repository fallback`, or `unavailable` accurately.
|
|
15
19
|
|
|
16
20
|
## Review Sequence
|
|
17
21
|
|
|
@@ -24,9 +24,11 @@ verdict/findings, and required browser, visual, manual, or knowledge evidence.
|
|
|
24
24
|
- Mock-backed behavior proves frontend rendering and state transitions only. It never
|
|
25
25
|
proves backend readiness, transport compatibility, or real API integration.
|
|
26
26
|
- Unavailable commands remain gaps.
|
|
27
|
-
- Resolve design evidence
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
- Resolve design evidence from two parallel sources: query the execution environment's
|
|
28
|
+
knowledge base connector when available; regardless of result, also read
|
|
29
|
+
`<repoRoot>/openspec/**` for index and task-relevant specification content.
|
|
30
|
+
The connector format remains TODO; never invent it. Applied `openspec` rules
|
|
31
|
+
from successful reads are available project evidence.
|
|
30
32
|
- Separate Mock service/handler checks from page consumption and record the
|
|
31
33
|
dev/test-only boundary; handler tests alone do not prove page use.
|
|
32
34
|
|
|
@@ -12,8 +12,9 @@
|
|
|
12
12
|
|
|
13
13
|
## Design And Component Evidence
|
|
14
14
|
|
|
15
|
-
- Claims cite knowledge-base retrieval
|
|
16
|
-
|
|
15
|
+
- Claims cite two parallel sources: knowledge-base retrieval and
|
|
16
|
+
`<repoRoot>/openspec/**`.
|
|
17
|
+
- Evidence records query/source/time for knowledge base plus openspec search terms, paths, headings, and applied rules for both.
|
|
17
18
|
- Relevant `openspec/` matches satisfy source availability; missing both sources blocks explicit compliance or required design decisions.
|
|
18
19
|
|
|
19
20
|
## Status
|