agentweaver 0.1.17 → 0.1.19
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/README.md +112 -23
- package/dist/artifacts.js +41 -0
- package/dist/index.js +258 -29
- package/dist/interactive/controller.js +323 -13
- package/dist/interactive/ink/index.js +2 -2
- package/dist/interactive/state.js +10 -0
- package/dist/interactive/web/index.js +326 -0
- package/dist/interactive/web/protocol.js +160 -0
- package/dist/interactive/web/server.js +1011 -0
- package/dist/interactive/web/static/app.js +1580 -0
- package/dist/interactive/web/static/index.html +114 -0
- package/dist/interactive/web/static/styles.css +2 -0
- package/dist/interactive/web/static/styles.input.css +849 -0
- package/dist/pipeline/flow-catalog.js +4 -0
- package/dist/pipeline/flow-specs/auto-common-guided.json +313 -0
- package/dist/pipeline/flow-specs/auto-common.json +3 -1
- package/dist/pipeline/flow-specs/design-review/design-review-loop.json +2 -0
- package/dist/pipeline/flow-specs/design-review.json +2 -0
- package/dist/pipeline/flow-specs/implement.json +3 -1
- package/dist/pipeline/flow-specs/plan.json +4 -0
- package/dist/pipeline/flow-specs/playbook-init.json +199 -0
- package/dist/pipeline/flow-specs/review/review-fix.json +3 -1
- package/dist/pipeline/flow-specs/review/review-loop.json +4 -0
- package/dist/pipeline/flow-specs/review/review.json +2 -0
- package/dist/pipeline/node-registry.js +45 -0
- package/dist/pipeline/nodes/flow-run-node.js +13 -1
- package/dist/pipeline/nodes/playbook-ensure-node.js +115 -0
- package/dist/pipeline/nodes/playbook-inventory-node.js +51 -0
- package/dist/pipeline/nodes/playbook-questions-form-node.js +166 -0
- package/dist/pipeline/nodes/playbook-write-node.js +243 -0
- package/dist/pipeline/nodes/project-guidance-node.js +69 -0
- package/dist/pipeline/prompt-registry.js +4 -1
- package/dist/pipeline/prompt-runtime.js +6 -2
- package/dist/pipeline/spec-types.js +19 -0
- package/dist/pipeline/value-resolver.js +39 -1
- package/dist/playbook/practice-candidates.js +12 -0
- package/dist/playbook/repo-inventory.js +208 -0
- package/dist/prompts.js +31 -0
- package/dist/runtime/artifact-catalog.js +379 -0
- package/dist/runtime/playbook.js +485 -0
- package/dist/runtime/project-guidance.js +339 -0
- package/dist/structured-artifact-schema-registry.js +8 -0
- package/dist/structured-artifact-schemas.json +235 -0
- package/dist/structured-artifacts.js +7 -1
- package/docs/declarative-workflows.md +565 -0
- package/docs/features.md +77 -0
- package/docs/playbook.md +327 -0
- package/package.json +8 -3
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { TaskRunnerError } from "../errors.js";
|
|
4
|
+
import { loadProjectPlaybook, PLAYBOOK_DIR, PLAYBOOK_MANIFEST, } from "./playbook.js";
|
|
5
|
+
export const GUIDANCE_PHASES = ["plan", "design-review", "implement", "review", "repair/review-fix"];
|
|
6
|
+
export const DEFAULT_GUIDANCE_BUDGETS = {
|
|
7
|
+
plan: 1200,
|
|
8
|
+
"design-review": 1000,
|
|
9
|
+
implement: 1400,
|
|
10
|
+
review: 1000,
|
|
11
|
+
"repair/review-fix": 1000,
|
|
12
|
+
};
|
|
13
|
+
export const DEFAULT_INLINE_THRESHOLD = 300;
|
|
14
|
+
const LANGUAGE_HINTS = ["typescript", "javascript", "node", "go", "golang", "python", "react", "vue", "svelte"];
|
|
15
|
+
const FRAMEWORK_HINTS = ["express", "fastify", "nestjs", "react", "vite", "vitest", "node:test", "jest", "ink"];
|
|
16
|
+
export function normalizeGuidancePhase(value) {
|
|
17
|
+
const normalized = value.trim().toLowerCase().replace(/_/g, "-");
|
|
18
|
+
if (normalized === "design_review") {
|
|
19
|
+
return "design-review";
|
|
20
|
+
}
|
|
21
|
+
if (normalized === "repair" || normalized === "review-fix") {
|
|
22
|
+
return "repair/review-fix";
|
|
23
|
+
}
|
|
24
|
+
if (GUIDANCE_PHASES.includes(normalized)) {
|
|
25
|
+
return normalized;
|
|
26
|
+
}
|
|
27
|
+
throw new TaskRunnerError(`Unsupported project guidance phase '${value}'. Supported phases: ${GUIDANCE_PHASES.join(", ")}.`);
|
|
28
|
+
}
|
|
29
|
+
export function toPlaybookPhase(phase) {
|
|
30
|
+
switch (phase) {
|
|
31
|
+
case "design-review":
|
|
32
|
+
return "design_review";
|
|
33
|
+
case "repair/review-fix":
|
|
34
|
+
return "repair";
|
|
35
|
+
default:
|
|
36
|
+
return phase;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export function getDefaultGuidanceBudget(phase) {
|
|
40
|
+
return { limit: DEFAULT_GUIDANCE_BUDGETS[phase], inlineThreshold: DEFAULT_INLINE_THRESHOLD };
|
|
41
|
+
}
|
|
42
|
+
export function estimateApproxTokens(value) {
|
|
43
|
+
return Math.ceil(value.length / 4);
|
|
44
|
+
}
|
|
45
|
+
export function extractTaskSignals(taskContext) {
|
|
46
|
+
const values = [];
|
|
47
|
+
collectStrings(taskContext, values);
|
|
48
|
+
const joined = values.join("\n");
|
|
49
|
+
const keywords = Array.from(new Set(joined.toLowerCase().match(/[a-z][a-z0-9_-]{2,}/g) ?? []))
|
|
50
|
+
.filter((word) => !STOP_WORDS.has(word))
|
|
51
|
+
.sort();
|
|
52
|
+
const referencedPaths = Array.from(new Set(joined.match(/(?:^|\s)([./]?[A-Za-z0-9_.@-]+\/[A-Za-z0-9_./@-]+)/g)?.map((item) => item.trim()) ?? []))
|
|
53
|
+
.map((item) => item.replace(/^["'`]|["'`,.;:]$/g, ""))
|
|
54
|
+
.sort();
|
|
55
|
+
const languages = LANGUAGE_HINTS.filter((hint) => keywords.includes(hint));
|
|
56
|
+
const frameworks = FRAMEWORK_HINTS.filter((hint) => keywords.includes(hint));
|
|
57
|
+
const record = isRecord(taskContext) ? taskContext : {};
|
|
58
|
+
return {
|
|
59
|
+
title: firstString(record.title, record.summary, record.key) || "Untitled task",
|
|
60
|
+
source_type: firstString(record.source_type, record.sourceType) || "unknown",
|
|
61
|
+
keywords,
|
|
62
|
+
file_globs: referencedPaths,
|
|
63
|
+
referenced_paths: referencedPaths,
|
|
64
|
+
languages,
|
|
65
|
+
frameworks,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
export function buildProjectGuidance(options) {
|
|
69
|
+
const phase = normalizeGuidancePhase(options.phase);
|
|
70
|
+
const playbookRoot = path.join(path.resolve(options.projectRoot), PLAYBOOK_DIR);
|
|
71
|
+
const manifestPath = path.join(playbookRoot, PLAYBOOK_MANIFEST);
|
|
72
|
+
const defaults = getDefaultGuidanceBudget(phase);
|
|
73
|
+
const budget = makeBudget(options.budgetLimit ?? defaults.limit, options.inlineThreshold ?? defaults.inlineThreshold);
|
|
74
|
+
const taskSignals = extractTaskSignals(options.taskContext);
|
|
75
|
+
if (!existsSync(manifestPath)) {
|
|
76
|
+
return {
|
|
77
|
+
summary: `No project playbook manifest was found for ${phase} guidance.`,
|
|
78
|
+
status: "missing_playbook",
|
|
79
|
+
phase,
|
|
80
|
+
source_playbook: { root_dir: playbookRoot, manifest_path: manifestPath, exists: false, valid: false },
|
|
81
|
+
task_signals: taskSignals,
|
|
82
|
+
selected_practices: [],
|
|
83
|
+
selected_examples: [],
|
|
84
|
+
always_include: [],
|
|
85
|
+
phase_sections: [],
|
|
86
|
+
budget,
|
|
87
|
+
skipped_items: [],
|
|
88
|
+
warnings: [{ code: "missing_playbook", message: "Project guidance was not generated because manifest.yaml is absent.", severity: "warning" }],
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
let playbook;
|
|
92
|
+
try {
|
|
93
|
+
playbook = loadProjectPlaybook(options.projectRoot);
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
const message = error.message;
|
|
97
|
+
if ((options.invalidPlaybookPolicy ?? "fail_before_prompt") === "write_diagnostic_artifact") {
|
|
98
|
+
return {
|
|
99
|
+
summary: `The project playbook manifest is invalid for ${phase} guidance.`,
|
|
100
|
+
status: "invalid_playbook",
|
|
101
|
+
phase,
|
|
102
|
+
source_playbook: { root_dir: playbookRoot, manifest_path: manifestPath, exists: true, valid: false, error: message },
|
|
103
|
+
task_signals: taskSignals,
|
|
104
|
+
selected_practices: [],
|
|
105
|
+
selected_examples: [],
|
|
106
|
+
always_include: [],
|
|
107
|
+
phase_sections: [],
|
|
108
|
+
budget,
|
|
109
|
+
skipped_items: [],
|
|
110
|
+
warnings: [{ code: "invalid_playbook", message, severity: "error" }],
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
throw new TaskRunnerError(`Invalid project playbook ${manifestPath}: ${message}`);
|
|
114
|
+
}
|
|
115
|
+
const scored = [...playbook.practices, ...playbook.examples]
|
|
116
|
+
.map((entry) => scoreEntry(entry, playbook, phase, taskSignals))
|
|
117
|
+
.filter((entry) => entry !== null)
|
|
118
|
+
.sort(compareScoredEntries);
|
|
119
|
+
const skipped = [];
|
|
120
|
+
const selected = [];
|
|
121
|
+
for (const scoredEntry of scored) {
|
|
122
|
+
selected.push(materializeEntry(scoredEntry, playbook, budget, skipped));
|
|
123
|
+
}
|
|
124
|
+
const selectedPractices = selected.filter((entry) => entry.kind === "practice");
|
|
125
|
+
const selectedExamples = selected.filter((entry) => entry.kind === "example");
|
|
126
|
+
const status = selected.length > 0 || playbook.alwaysInclude.length > 0 ? "available" : "empty_selection";
|
|
127
|
+
return {
|
|
128
|
+
summary: status === "available"
|
|
129
|
+
? `Selected compact ${phase} project guidance from manifest.yaml.`
|
|
130
|
+
: `No matching project guidance was selected for ${phase}.`,
|
|
131
|
+
status,
|
|
132
|
+
phase,
|
|
133
|
+
source_playbook: { root_dir: playbook.playbookRoot, manifest_path: playbook.manifestPath, exists: true, valid: true },
|
|
134
|
+
task_signals: taskSignals,
|
|
135
|
+
selected_practices: selectedPractices,
|
|
136
|
+
selected_examples: selectedExamples,
|
|
137
|
+
always_include: playbook.alwaysInclude,
|
|
138
|
+
phase_sections: selectedPractices.map((entry) => entry.title),
|
|
139
|
+
budget,
|
|
140
|
+
skipped_items: skipped,
|
|
141
|
+
warnings: [],
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
export function renderProjectGuidanceMarkdown(guidance, language = "en") {
|
|
145
|
+
if (language === "ru") {
|
|
146
|
+
return renderRussianMarkdown(guidance);
|
|
147
|
+
}
|
|
148
|
+
return renderEnglishMarkdown(guidance);
|
|
149
|
+
}
|
|
150
|
+
function scoreEntry(entry, playbook, phase, signals) {
|
|
151
|
+
const playbookPhase = toPlaybookPhase(phase);
|
|
152
|
+
const phases = entry.metadata.phases;
|
|
153
|
+
const always = playbook.alwaysInclude.includes(entry.path);
|
|
154
|
+
const reasons = [];
|
|
155
|
+
let score = 0;
|
|
156
|
+
if (always) {
|
|
157
|
+
score += 1000;
|
|
158
|
+
reasons.push("always_include");
|
|
159
|
+
}
|
|
160
|
+
if (phases.length === 0 || phases.includes(playbookPhase)) {
|
|
161
|
+
score += 100;
|
|
162
|
+
reasons.push("phase_match");
|
|
163
|
+
}
|
|
164
|
+
else if (!always) {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
const appliesTo = entry.metadata.applies_to;
|
|
168
|
+
const text = `${entry.id} ${entry.title} ${entry.body}`.toLowerCase();
|
|
169
|
+
const keywordMatches = (appliesTo?.keywords ?? signals.keywords).filter((keyword) => signals.keywords.includes(keyword.toLowerCase()) || text.includes(keyword.toLowerCase()));
|
|
170
|
+
if (keywordMatches.length > 0) {
|
|
171
|
+
score += Math.min(keywordMatches.length, 10) * 8;
|
|
172
|
+
reasons.push("keyword_match");
|
|
173
|
+
}
|
|
174
|
+
if ((appliesTo?.globs ?? []).some((glob) => signals.referenced_paths.some((filePath) => globMatches(glob, filePath)))) {
|
|
175
|
+
score += 40;
|
|
176
|
+
reasons.push("glob_match");
|
|
177
|
+
}
|
|
178
|
+
if ((appliesTo?.languages ?? []).some((language) => signals.languages.includes(language.toLowerCase()))) {
|
|
179
|
+
score += 30;
|
|
180
|
+
reasons.push("language_match");
|
|
181
|
+
}
|
|
182
|
+
if ((appliesTo?.frameworks ?? []).some((framework) => signals.frameworks.includes(framework.toLowerCase()))) {
|
|
183
|
+
score += 30;
|
|
184
|
+
reasons.push("framework_match");
|
|
185
|
+
}
|
|
186
|
+
score += (entry.metadata.priority ?? 0) * 5;
|
|
187
|
+
if (entry.metadata.priority !== undefined) {
|
|
188
|
+
reasons.push("priority");
|
|
189
|
+
}
|
|
190
|
+
score += severityWeight(entry.metadata.severity);
|
|
191
|
+
if (entry.metadata.severity) {
|
|
192
|
+
reasons.push("severity");
|
|
193
|
+
}
|
|
194
|
+
if (score <= 0) {
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
return { entry, score, reasons: Array.from(new Set(reasons)), always };
|
|
198
|
+
}
|
|
199
|
+
function materializeEntry(scored, playbook, budget, skipped) {
|
|
200
|
+
const safePath = containedPlaybookPath(playbook, scored.entry.path);
|
|
201
|
+
const tokens = estimateApproxTokens(scored.entry.body);
|
|
202
|
+
const referenceReason = scored.entry.kind === "example" ? "example_reference_only" : "over_budget";
|
|
203
|
+
const item = {
|
|
204
|
+
id: scored.entry.id,
|
|
205
|
+
title: scored.entry.title,
|
|
206
|
+
kind: scored.entry.kind,
|
|
207
|
+
source_path: safePath,
|
|
208
|
+
selection_reasons: scored.reasons,
|
|
209
|
+
relevance_score: scored.score,
|
|
210
|
+
priority: scored.entry.metadata.priority ?? 0,
|
|
211
|
+
severity: scored.entry.metadata.severity ?? "info",
|
|
212
|
+
reference_only: true,
|
|
213
|
+
reference: { source_path: safePath, reason: referenceReason },
|
|
214
|
+
};
|
|
215
|
+
if (scored.entry.kind === "practice" && tokens <= budget.inline_threshold && tokens <= budget.remaining) {
|
|
216
|
+
item.inline_content = scored.entry.body.trim();
|
|
217
|
+
item.reference_only = false;
|
|
218
|
+
item.reference.reason = "inlined";
|
|
219
|
+
budget.used += tokens;
|
|
220
|
+
budget.remaining = Math.max(0, budget.limit - budget.used);
|
|
221
|
+
}
|
|
222
|
+
else if (tokens > budget.inline_threshold || tokens > budget.remaining) {
|
|
223
|
+
skipped.push({ id: scored.entry.id, kind: scored.entry.kind, source_path: safePath, reason: tokens > budget.inline_threshold ? "inline_threshold_exceeded" : "over_budget" });
|
|
224
|
+
}
|
|
225
|
+
return item;
|
|
226
|
+
}
|
|
227
|
+
function containedPlaybookPath(playbook, relativePath) {
|
|
228
|
+
if (path.isAbsolute(relativePath)) {
|
|
229
|
+
throw new TaskRunnerError(`Playbook reference must be relative: ${relativePath}`);
|
|
230
|
+
}
|
|
231
|
+
const resolved = path.resolve(playbook.playbookRoot, relativePath);
|
|
232
|
+
const relative = path.relative(playbook.playbookRoot, resolved);
|
|
233
|
+
if (relative.startsWith("..") || path.isAbsolute(relative)) {
|
|
234
|
+
throw new TaskRunnerError(`Playbook reference escapes ${PLAYBOOK_DIR}: ${relativePath}`);
|
|
235
|
+
}
|
|
236
|
+
return relative.split(path.sep).join("/");
|
|
237
|
+
}
|
|
238
|
+
function compareScoredEntries(left, right) {
|
|
239
|
+
return (Number(right.always) - Number(left.always) ||
|
|
240
|
+
right.score - left.score ||
|
|
241
|
+
(right.entry.metadata.priority ?? 0) - (left.entry.metadata.priority ?? 0) ||
|
|
242
|
+
severityWeight(right.entry.metadata.severity) - severityWeight(left.entry.metadata.severity) ||
|
|
243
|
+
left.entry.id.localeCompare(right.entry.id));
|
|
244
|
+
}
|
|
245
|
+
function severityWeight(severity) {
|
|
246
|
+
if (severity === "must") {
|
|
247
|
+
return 30;
|
|
248
|
+
}
|
|
249
|
+
if (severity === "should") {
|
|
250
|
+
return 10;
|
|
251
|
+
}
|
|
252
|
+
return 0;
|
|
253
|
+
}
|
|
254
|
+
function makeBudget(limit, inlineThreshold) {
|
|
255
|
+
return {
|
|
256
|
+
limit: Math.max(0, Math.floor(limit)),
|
|
257
|
+
used: 0,
|
|
258
|
+
remaining: Math.max(0, Math.floor(limit)),
|
|
259
|
+
inline_threshold: Math.max(0, Math.floor(inlineThreshold)),
|
|
260
|
+
unit: "approx_tokens",
|
|
261
|
+
estimator: "chars_div_4",
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
function renderRussianMarkdown(guidance) {
|
|
265
|
+
const lines = [`# Проектные рекомендации: ${guidance.phase}`, "", `Статус: ${guidance.status}`, "", "## Обязательные правила"];
|
|
266
|
+
if (guidance.status === "missing_playbook") {
|
|
267
|
+
lines.push("- Проектный playbook manifest.yaml не найден; дополнительных проектных рекомендаций нет.");
|
|
268
|
+
}
|
|
269
|
+
else if (guidance.selected_practices.length === 0) {
|
|
270
|
+
lines.push("- Для этой фазы не выбраны дополнительные правила.");
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
for (const item of guidance.selected_practices) {
|
|
274
|
+
lines.push(`- ${item.title} (${item.source_path}): ${item.selection_reasons.join(", ")}`);
|
|
275
|
+
if (item.inline_content) {
|
|
276
|
+
lines.push(` ${item.inline_content.replace(/\n/g, " ")}`);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
lines.push("", "## Релевантные примеры и ссылки");
|
|
281
|
+
const refs = [...guidance.selected_examples, ...guidance.selected_practices.filter((item) => item.reference_only)];
|
|
282
|
+
lines.push(...(refs.length === 0 ? ["- Нет релевантных ссылок."] : refs.map((item) => `- ${item.title}: ${item.source_path} (${item.reference.reason})`)));
|
|
283
|
+
lines.push("", "Открывайте полные примеры только когда они напрямую релевантны текущему изменению.");
|
|
284
|
+
lines.push("", "## Бюджет", `Использовано ${guidance.budget.used} из ${guidance.budget.limit} ${guidance.budget.unit}; осталось ${guidance.budget.remaining}.`);
|
|
285
|
+
return `${lines.join("\n")}\n`;
|
|
286
|
+
}
|
|
287
|
+
function renderEnglishMarkdown(guidance) {
|
|
288
|
+
const lines = [`# Project Guidance: ${guidance.phase}`, "", `Status: ${guidance.status}`, "", "## Must-Follow Rules"];
|
|
289
|
+
if (guidance.status === "missing_playbook") {
|
|
290
|
+
lines.push("- Project playbook manifest.yaml was not found; no additional project guidance is available.");
|
|
291
|
+
}
|
|
292
|
+
else if (guidance.selected_practices.length === 0) {
|
|
293
|
+
lines.push("- No additional rules were selected for this phase.");
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
for (const item of guidance.selected_practices) {
|
|
297
|
+
lines.push(`- ${item.title} (${item.source_path}): ${item.selection_reasons.join(", ")}`);
|
|
298
|
+
if (item.inline_content) {
|
|
299
|
+
lines.push(` ${item.inline_content.replace(/\n/g, " ")}`);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
lines.push("", "## Relevant Examples And References");
|
|
304
|
+
const refs = [...guidance.selected_examples, ...guidance.selected_practices.filter((item) => item.reference_only)];
|
|
305
|
+
lines.push(...(refs.length === 0 ? ["- No relevant references."] : refs.map((item) => `- ${item.title}: ${item.source_path} (${item.reference.reason})`)));
|
|
306
|
+
lines.push("", "Open full examples only when they are directly relevant to the current change.");
|
|
307
|
+
lines.push("", "## Budget", `Used ${guidance.budget.used} of ${guidance.budget.limit} ${guidance.budget.unit}; remaining ${guidance.budget.remaining}.`);
|
|
308
|
+
return `${lines.join("\n")}\n`;
|
|
309
|
+
}
|
|
310
|
+
function collectStrings(value, result) {
|
|
311
|
+
if (typeof value === "string") {
|
|
312
|
+
result.push(value);
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
if (Array.isArray(value)) {
|
|
316
|
+
value.forEach((item) => collectStrings(item, result));
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
if (isRecord(value)) {
|
|
320
|
+
Object.values(value).forEach((item) => collectStrings(item, result));
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
function firstString(...values) {
|
|
324
|
+
return values.find((value) => typeof value === "string" && value.trim().length > 0)?.trim() ?? "";
|
|
325
|
+
}
|
|
326
|
+
function isRecord(value) {
|
|
327
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
328
|
+
}
|
|
329
|
+
function globMatches(glob, value) {
|
|
330
|
+
const source = glob
|
|
331
|
+
.replace(/[.+^${}()|[\]\\]/g, "\\$&")
|
|
332
|
+
.replace(/\*\*/g, ".*")
|
|
333
|
+
.replace(/\*/g, "[^/]*");
|
|
334
|
+
return new RegExp(`^${source}$`).test(value.replace(/^\.\//, ""));
|
|
335
|
+
}
|
|
336
|
+
const STOP_WORDS = new Set([
|
|
337
|
+
"and", "the", "with", "for", "from", "into", "that", "this", "when", "then", "must", "should", "will", "are", "was",
|
|
338
|
+
"как", "для", "что", "это", "или", "при", "над", "под", "если",
|
|
339
|
+
]);
|
|
@@ -15,7 +15,15 @@ export const STRUCTURED_ARTIFACT_SCHEMA_IDS = [
|
|
|
15
15
|
"jira-description/v1",
|
|
16
16
|
"mr-description/v1",
|
|
17
17
|
"planning-questions/v1",
|
|
18
|
+
"playbook-answers/v1",
|
|
19
|
+
"playbook-draft/v1",
|
|
20
|
+
"playbook-final/v1",
|
|
21
|
+
"playbook-questions/v1",
|
|
22
|
+
"playbook-write-result/v1",
|
|
23
|
+
"practice-candidates/v1",
|
|
24
|
+
"project-guidance/v1",
|
|
18
25
|
"qa-plan/v1",
|
|
26
|
+
"repo-inventory/v1",
|
|
19
27
|
"review-assessment/v1",
|
|
20
28
|
"review-findings/v1",
|
|
21
29
|
"review-fix-report/v1",
|
|
@@ -1,4 +1,94 @@
|
|
|
1
1
|
{
|
|
2
|
+
"project-guidance/v1": {
|
|
3
|
+
"type": "object",
|
|
4
|
+
"properties": {
|
|
5
|
+
"summary": { "type": "string", "nonEmpty": true },
|
|
6
|
+
"status": { "type": "string", "enum": ["available", "missing_playbook", "empty_selection", "invalid_playbook"] },
|
|
7
|
+
"phase": { "type": "string", "enum": ["plan", "design-review", "implement", "review", "repair/review-fix"] },
|
|
8
|
+
"source_playbook": {
|
|
9
|
+
"type": "object",
|
|
10
|
+
"properties": {
|
|
11
|
+
"root_dir": { "type": "string", "nonEmpty": true },
|
|
12
|
+
"manifest_path": { "type": "string", "nonEmpty": true },
|
|
13
|
+
"exists": { "type": "boolean" },
|
|
14
|
+
"valid": { "type": "boolean" },
|
|
15
|
+
"error": { "type": "string", "nonEmpty": true }
|
|
16
|
+
},
|
|
17
|
+
"required": ["root_dir", "manifest_path", "exists", "valid"]
|
|
18
|
+
},
|
|
19
|
+
"task_signals": {
|
|
20
|
+
"type": "object",
|
|
21
|
+
"properties": {
|
|
22
|
+
"title": { "type": "string", "nonEmpty": true },
|
|
23
|
+
"source_type": { "type": "string", "nonEmpty": true },
|
|
24
|
+
"keywords": { "type": "array", "items": { "type": "string", "nonEmpty": true } },
|
|
25
|
+
"file_globs": { "type": "array", "items": { "type": "string", "nonEmpty": true } },
|
|
26
|
+
"referenced_paths": { "type": "array", "items": { "type": "string", "nonEmpty": true } },
|
|
27
|
+
"languages": { "type": "array", "items": { "type": "string", "nonEmpty": true } },
|
|
28
|
+
"frameworks": { "type": "array", "items": { "type": "string", "nonEmpty": true } }
|
|
29
|
+
},
|
|
30
|
+
"required": ["title", "source_type", "keywords", "file_globs", "referenced_paths", "languages", "frameworks"]
|
|
31
|
+
},
|
|
32
|
+
"selected_practices": { "type": "array", "items": { "type": "object", "properties": {
|
|
33
|
+
"id": { "type": "string", "nonEmpty": true },
|
|
34
|
+
"title": { "type": "string", "nonEmpty": true },
|
|
35
|
+
"kind": { "type": "string", "enum": ["practice"] },
|
|
36
|
+
"source_path": { "type": "string", "nonEmpty": true },
|
|
37
|
+
"selection_reasons": { "type": "array", "items": { "type": "string", "nonEmpty": true }, "minItems": 1 },
|
|
38
|
+
"relevance_score": { "type": "number" },
|
|
39
|
+
"priority": { "type": "number" },
|
|
40
|
+
"severity": { "type": "string", "enum": ["must", "should", "info"] },
|
|
41
|
+
"reference_only": { "type": "boolean" },
|
|
42
|
+
"reference": { "type": "object", "properties": {
|
|
43
|
+
"source_path": { "type": "string", "nonEmpty": true },
|
|
44
|
+
"reason": { "type": "string", "nonEmpty": true }
|
|
45
|
+
}, "required": ["source_path", "reason"] },
|
|
46
|
+
"inline_content": { "type": "string", "nonEmpty": true }
|
|
47
|
+
}, "required": ["id", "title", "kind", "source_path", "selection_reasons", "relevance_score", "priority", "severity", "reference_only", "reference"] } },
|
|
48
|
+
"selected_examples": { "type": "array", "items": { "type": "object", "properties": {
|
|
49
|
+
"id": { "type": "string", "nonEmpty": true },
|
|
50
|
+
"title": { "type": "string", "nonEmpty": true },
|
|
51
|
+
"kind": { "type": "string", "enum": ["example"] },
|
|
52
|
+
"source_path": { "type": "string", "nonEmpty": true },
|
|
53
|
+
"selection_reasons": { "type": "array", "items": { "type": "string", "nonEmpty": true }, "minItems": 1 },
|
|
54
|
+
"relevance_score": { "type": "number" },
|
|
55
|
+
"priority": { "type": "number" },
|
|
56
|
+
"severity": { "type": "string", "enum": ["must", "should", "info"] },
|
|
57
|
+
"reference_only": { "type": "boolean" },
|
|
58
|
+
"reference": { "type": "object", "properties": {
|
|
59
|
+
"source_path": { "type": "string", "nonEmpty": true },
|
|
60
|
+
"reason": { "type": "string", "nonEmpty": true }
|
|
61
|
+
}, "required": ["source_path", "reason"] },
|
|
62
|
+
"inline_content": { "type": "string", "nonEmpty": true }
|
|
63
|
+
}, "required": ["id", "title", "kind", "source_path", "selection_reasons", "relevance_score", "priority", "severity", "reference_only", "reference"] } },
|
|
64
|
+
"always_include": { "type": "array", "items": { "type": "string", "nonEmpty": true } },
|
|
65
|
+
"phase_sections": { "type": "array", "items": { "type": "string", "nonEmpty": true } },
|
|
66
|
+
"budget": {
|
|
67
|
+
"type": "object",
|
|
68
|
+
"properties": {
|
|
69
|
+
"limit": { "type": "number", "minimum": 0 },
|
|
70
|
+
"used": { "type": "number", "minimum": 0 },
|
|
71
|
+
"remaining": { "type": "number", "minimum": 0 },
|
|
72
|
+
"inline_threshold": { "type": "number", "minimum": 0 },
|
|
73
|
+
"unit": { "type": "string", "enum": ["approx_tokens"] },
|
|
74
|
+
"estimator": { "type": "string", "enum": ["chars_div_4"] }
|
|
75
|
+
},
|
|
76
|
+
"required": ["limit", "used", "remaining", "inline_threshold", "unit", "estimator"]
|
|
77
|
+
},
|
|
78
|
+
"skipped_items": { "type": "array", "items": { "type": "object", "properties": {
|
|
79
|
+
"id": { "type": "string", "nonEmpty": true },
|
|
80
|
+
"kind": { "type": "string", "enum": ["practice", "example", "always_include"] },
|
|
81
|
+
"source_path": { "type": "string", "nonEmpty": true },
|
|
82
|
+
"reason": { "type": "string", "nonEmpty": true }
|
|
83
|
+
}, "required": ["id", "kind", "source_path", "reason"] } },
|
|
84
|
+
"warnings": { "type": "array", "items": { "type": "object", "properties": {
|
|
85
|
+
"code": { "type": "string", "nonEmpty": true },
|
|
86
|
+
"message": { "type": "string", "nonEmpty": true },
|
|
87
|
+
"severity": { "type": "string", "enum": ["info", "warning", "error"] }
|
|
88
|
+
}, "required": ["code", "message", "severity"] } }
|
|
89
|
+
},
|
|
90
|
+
"required": ["summary", "status", "phase", "source_playbook", "task_signals", "selected_practices", "selected_examples", "always_include", "phase_sections", "budget", "skipped_items", "warnings"]
|
|
91
|
+
},
|
|
2
92
|
"bug-analysis/v1": {
|
|
3
93
|
"type": "object",
|
|
4
94
|
"properties": {
|
|
@@ -461,6 +551,151 @@
|
|
|
461
551
|
},
|
|
462
552
|
"required": ["summary", "questions"]
|
|
463
553
|
},
|
|
554
|
+
"playbook-answers/v1": {
|
|
555
|
+
"type": "object",
|
|
556
|
+
"properties": {
|
|
557
|
+
"summary": { "type": "string", "nonEmpty": true },
|
|
558
|
+
"answered_at": { "type": "string", "nonEmpty": true },
|
|
559
|
+
"answers": {
|
|
560
|
+
"type": "array",
|
|
561
|
+
"items": {
|
|
562
|
+
"type": "object",
|
|
563
|
+
"properties": {
|
|
564
|
+
"question_id": { "type": "string", "nonEmpty": true },
|
|
565
|
+
"answer": { "type": "string" }
|
|
566
|
+
},
|
|
567
|
+
"required": ["question_id", "answer"]
|
|
568
|
+
}
|
|
569
|
+
},
|
|
570
|
+
"final_write_accepted": { "type": "boolean" }
|
|
571
|
+
},
|
|
572
|
+
"required": ["summary", "answered_at", "answers", "final_write_accepted"]
|
|
573
|
+
},
|
|
574
|
+
"playbook-draft/v1": {
|
|
575
|
+
"type": "object",
|
|
576
|
+
"properties": {
|
|
577
|
+
"summary": { "type": "string", "nonEmpty": true },
|
|
578
|
+
"generated_at": { "type": "string", "nonEmpty": true },
|
|
579
|
+
"accepted_rules": {
|
|
580
|
+
"type": "array",
|
|
581
|
+
"items": {
|
|
582
|
+
"type": "object",
|
|
583
|
+
"properties": {
|
|
584
|
+
"id": { "type": "string", "nonEmpty": true },
|
|
585
|
+
"title": { "type": "string", "nonEmpty": true },
|
|
586
|
+
"rule": { "type": "string", "nonEmpty": true },
|
|
587
|
+
"evidence_paths": { "type": "array", "items": { "type": "string", "nonEmpty": true }, "minItems": 1 }
|
|
588
|
+
},
|
|
589
|
+
"required": ["id", "title", "rule", "evidence_paths"]
|
|
590
|
+
}
|
|
591
|
+
},
|
|
592
|
+
"candidate_rules": { "type": "array", "items": { "type": "object" } },
|
|
593
|
+
"unresolved_questions": { "type": "array", "items": { "type": "string", "nonEmpty": true } },
|
|
594
|
+
"evidence_paths": { "type": "array", "items": { "type": "string", "nonEmpty": true } },
|
|
595
|
+
"proposed_files": { "type": "array", "items": { "type": "string", "nonEmpty": true }, "minItems": 2 }
|
|
596
|
+
},
|
|
597
|
+
"required": ["summary", "generated_at", "accepted_rules", "candidate_rules", "unresolved_questions", "evidence_paths", "proposed_files"]
|
|
598
|
+
},
|
|
599
|
+
"playbook-final/v1": {
|
|
600
|
+
"type": "object",
|
|
601
|
+
"properties": {
|
|
602
|
+
"status": { "type": "string", "nonEmpty": true, "enum": ["accepted"] },
|
|
603
|
+
"accepted_at": { "type": "string", "nonEmpty": true },
|
|
604
|
+
"source_draft_artifact": { "type": "string", "nonEmpty": true },
|
|
605
|
+
"summary": { "type": "string", "nonEmpty": true },
|
|
606
|
+
"rules": {
|
|
607
|
+
"type": "array",
|
|
608
|
+
"items": {
|
|
609
|
+
"type": "object",
|
|
610
|
+
"properties": {
|
|
611
|
+
"id": { "type": "string", "nonEmpty": true },
|
|
612
|
+
"title": { "type": "string", "nonEmpty": true },
|
|
613
|
+
"rule": { "type": "string", "nonEmpty": true },
|
|
614
|
+
"evidence_paths": { "type": "array", "items": { "type": "string", "nonEmpty": true }, "minItems": 1 }
|
|
615
|
+
},
|
|
616
|
+
"required": ["id", "title", "rule", "evidence_paths"]
|
|
617
|
+
}
|
|
618
|
+
},
|
|
619
|
+
"evidence_paths": { "type": "array", "items": { "type": "string", "nonEmpty": true } }
|
|
620
|
+
},
|
|
621
|
+
"required": ["status", "accepted_at", "source_draft_artifact", "summary", "rules", "evidence_paths"]
|
|
622
|
+
},
|
|
623
|
+
"playbook-questions/v1": {
|
|
624
|
+
"type": "object",
|
|
625
|
+
"properties": {
|
|
626
|
+
"summary": { "type": "string", "nonEmpty": true },
|
|
627
|
+
"questions": {
|
|
628
|
+
"type": "array",
|
|
629
|
+
"items": {
|
|
630
|
+
"type": "object",
|
|
631
|
+
"properties": {
|
|
632
|
+
"id": { "type": "string", "nonEmpty": true },
|
|
633
|
+
"text": { "type": "string", "nonEmpty": true },
|
|
634
|
+
"rationale": { "type": "string", "nonEmpty": true },
|
|
635
|
+
"candidate_ids": { "type": "array", "items": { "type": "string", "nonEmpty": true } },
|
|
636
|
+
"evidence_paths": { "type": "array", "items": { "type": "string", "nonEmpty": true }, "minItems": 1 },
|
|
637
|
+
"answer_kind": { "type": "string", "nonEmpty": true, "enum": ["text", "boolean", "single_select"] }
|
|
638
|
+
},
|
|
639
|
+
"required": ["id", "text", "rationale", "candidate_ids", "evidence_paths", "answer_kind"]
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
},
|
|
643
|
+
"required": ["summary", "questions"]
|
|
644
|
+
},
|
|
645
|
+
"playbook-write-result/v1": {
|
|
646
|
+
"type": "object",
|
|
647
|
+
"properties": {
|
|
648
|
+
"status": { "type": "string", "nonEmpty": true, "enum": ["written", "skipped_valid_existing", "blocked", "not_accepted", "dry_run_written", "invalid_manifest", "partial_manifest", "missing_playbook", "failed"] },
|
|
649
|
+
"message": { "type": "string", "nonEmpty": true },
|
|
650
|
+
"written_files": { "type": "array", "items": { "type": "string", "nonEmpty": true } },
|
|
651
|
+
"skipped_files": { "type": "array", "items": { "type": "string", "nonEmpty": true } },
|
|
652
|
+
"existing_playbook_path": { "type": "string" },
|
|
653
|
+
"intended_files": { "type": "array", "items": { "type": "string", "nonEmpty": true } },
|
|
654
|
+
"blocked_paths": { "type": "array", "items": { "type": "string", "nonEmpty": true } }
|
|
655
|
+
},
|
|
656
|
+
"required": ["status", "message", "written_files", "skipped_files", "existing_playbook_path", "intended_files", "blocked_paths"]
|
|
657
|
+
},
|
|
658
|
+
"practice-candidates/v1": {
|
|
659
|
+
"type": "object",
|
|
660
|
+
"properties": {
|
|
661
|
+
"summary": { "type": "string", "nonEmpty": true },
|
|
662
|
+
"candidates": {
|
|
663
|
+
"type": "array",
|
|
664
|
+
"items": {
|
|
665
|
+
"type": "object",
|
|
666
|
+
"properties": {
|
|
667
|
+
"id": { "type": "string", "nonEmpty": true },
|
|
668
|
+
"title": { "type": "string", "nonEmpty": true },
|
|
669
|
+
"proposed_rule_text": { "type": "string", "nonEmpty": true },
|
|
670
|
+
"confidence": { "type": "string", "nonEmpty": true, "enum": ["low", "medium", "high"] },
|
|
671
|
+
"evidence_paths": { "type": "array", "items": { "type": "string", "nonEmpty": true }, "minItems": 1 },
|
|
672
|
+
"rationale": { "type": "string", "nonEmpty": true },
|
|
673
|
+
"questions_needed": { "type": "array", "items": { "type": "string", "nonEmpty": true } }
|
|
674
|
+
},
|
|
675
|
+
"required": ["id", "title", "proposed_rule_text", "confidence", "evidence_paths", "rationale", "questions_needed"]
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
},
|
|
679
|
+
"required": ["summary", "candidates"]
|
|
680
|
+
},
|
|
681
|
+
"repo-inventory/v1": {
|
|
682
|
+
"type": "object",
|
|
683
|
+
"properties": {
|
|
684
|
+
"summary": { "type": "string", "nonEmpty": true },
|
|
685
|
+
"repository_root": { "type": "string", "nonEmpty": true },
|
|
686
|
+
"generated_at": { "type": "string", "nonEmpty": true },
|
|
687
|
+
"ignored_directories": { "type": "array", "items": { "type": "string", "nonEmpty": true } },
|
|
688
|
+
"stack_indicators": { "type": "array", "items": { "type": "object" } },
|
|
689
|
+
"test_structure": { "type": "array", "items": { "type": "object" } },
|
|
690
|
+
"architecture_hints": { "type": "array", "items": { "type": "object" } },
|
|
691
|
+
"quality_tooling": { "type": "array", "items": { "type": "object" } },
|
|
692
|
+
"specification_files": { "type": "array", "items": { "type": "object" } },
|
|
693
|
+
"runtime_configs": { "type": "array", "items": { "type": "object" } },
|
|
694
|
+
"generated_code": { "type": "array", "items": { "type": "object" } },
|
|
695
|
+
"evidence": { "type": "array", "items": { "type": "string", "nonEmpty": true } }
|
|
696
|
+
},
|
|
697
|
+
"required": ["summary", "repository_root", "generated_at", "ignored_directories", "stack_indicators", "test_structure", "architecture_hints", "quality_tooling", "specification_files", "runtime_configs", "generated_code", "evidence"]
|
|
698
|
+
},
|
|
464
699
|
"qa-plan/v1": {
|
|
465
700
|
"type": "object",
|
|
466
701
|
"properties": {
|
|
@@ -75,7 +75,13 @@ function validateNode(value, schema, currentPath) {
|
|
|
75
75
|
case "json":
|
|
76
76
|
return validateJsonValue(value, currentPath);
|
|
77
77
|
case "number":
|
|
78
|
-
|
|
78
|
+
if (typeof value !== "number" || Number.isNaN(value)) {
|
|
79
|
+
return [`${currentPath} must be a number`];
|
|
80
|
+
}
|
|
81
|
+
if (schema.minimum !== undefined && value < schema.minimum) {
|
|
82
|
+
return [`${currentPath} must be at least ${schema.minimum}`];
|
|
83
|
+
}
|
|
84
|
+
return [];
|
|
79
85
|
case "null":
|
|
80
86
|
return value === null ? [] : [`${currentPath} must be null`];
|
|
81
87
|
case "array": {
|