mdkg 0.3.6 → 0.3.8
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 +93 -0
- package/CLI_COMMAND_MATRIX.md +132 -24
- package/README.md +80 -25
- package/dist/cli.js +193 -36
- package/dist/command-contract.json +882 -122
- package/dist/commands/bundle.js +2 -0
- package/dist/commands/capability.js +1 -0
- package/dist/commands/checkpoint.js +139 -1
- package/dist/commands/db.js +8 -0
- package/dist/commands/doctor.js +7 -7
- package/dist/commands/format.js +155 -0
- package/dist/commands/goal.js +60 -0
- package/dist/commands/graph.js +148 -0
- package/dist/commands/handoff.js +301 -0
- package/dist/commands/mcp.js +9 -0
- package/dist/commands/new.js +12 -3
- package/dist/commands/pack.js +3 -1
- package/dist/commands/query_output.js +2 -0
- package/dist/commands/show.js +8 -0
- package/dist/commands/spec.js +76 -17
- package/dist/commands/status.js +1 -0
- package/dist/commands/subgraph.js +7 -4
- package/dist/commands/task.js +2 -0
- package/dist/commands/upgrade.js +128 -3
- package/dist/commands/validate.js +268 -6
- package/dist/commands/work.js +176 -5
- package/dist/core/project_db_queue_contract.js +101 -0
- package/dist/graph/agent_file_types.js +59 -20
- package/dist/graph/capabilities_indexer.js +45 -3
- package/dist/graph/edges.js +15 -0
- package/dist/graph/frontmatter.js +4 -1
- package/dist/graph/indexer.js +13 -0
- package/dist/graph/node.js +12 -1
- package/dist/graph/sqlite_index.js +2 -0
- package/dist/graph/subgraphs.js +2 -0
- package/dist/graph/template_schema.js +37 -17
- package/dist/graph/validate_graph.js +16 -5
- package/dist/graph/visibility.js +6 -0
- package/dist/init/AGENT_START.md +9 -6
- package/dist/init/CLI_COMMAND_MATRIX.md +50 -16
- package/dist/init/README.md +26 -9
- package/dist/init/init-manifest.json +67 -12
- package/dist/init/templates/default/bug.md +2 -0
- package/dist/init/templates/default/chk.md +3 -0
- package/dist/init/templates/default/epic.md +2 -0
- package/dist/init/templates/default/feat.md +2 -0
- package/dist/init/templates/default/goal.md +3 -0
- package/dist/init/templates/default/manifest.md +45 -0
- package/dist/init/templates/default/spike.md +2 -0
- package/dist/init/templates/default/task.md +2 -0
- package/dist/init/templates/default/test.md +2 -0
- package/dist/init/templates/specs/agent.MANIFEST.md +80 -0
- package/dist/init/templates/specs/api.MANIFEST.md +33 -0
- package/dist/init/templates/specs/base.MANIFEST.md +120 -0
- package/dist/init/templates/specs/capability.MANIFEST.md +45 -0
- package/dist/init/templates/specs/integration.MANIFEST.md +25 -0
- package/dist/init/templates/specs/model.MANIFEST.md +21 -0
- package/dist/init/templates/specs/project.MANIFEST.md +39 -0
- package/dist/init/templates/specs/runtime-agent.MANIFEST.md +49 -0
- package/dist/init/templates/specs/runtime-image.MANIFEST.md +21 -0
- package/dist/init/templates/specs/tool.MANIFEST.md +25 -0
- package/dist/pack/export_json.js +20 -8
- package/dist/pack/export_md.js +15 -4
- package/dist/pack/export_xml.js +9 -4
- package/dist/pack/metrics.js +12 -4
- package/dist/pack/pack.js +9 -1
- package/dist/util/argparse.js +3 -0
- package/package.json +21 -3
|
@@ -3,13 +3,16 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.RECOMMENDED_HEADINGS = void 0;
|
|
6
7
|
exports.collectValidateReceipt = collectValidateReceipt;
|
|
7
8
|
exports.runValidateCommand = runValidateCommand;
|
|
8
9
|
const fs_1 = __importDefault(require("fs"));
|
|
9
10
|
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const child_process_1 = require("child_process");
|
|
10
12
|
const config_1 = require("../core/config");
|
|
11
13
|
const template_schema_1 = require("../graph/template_schema");
|
|
12
14
|
const node_1 = require("../graph/node");
|
|
15
|
+
const agent_file_types_1 = require("../graph/agent_file_types");
|
|
13
16
|
const skills_indexer_1 = require("../graph/skills_indexer");
|
|
14
17
|
const workspace_files_1 = require("../graph/workspace_files");
|
|
15
18
|
const validate_graph_1 = require("../graph/validate_graph");
|
|
@@ -18,7 +21,7 @@ const visibility_1 = require("../graph/visibility");
|
|
|
18
21
|
const sqlite_index_1 = require("../graph/sqlite_index");
|
|
19
22
|
const errors_1 = require("../util/errors");
|
|
20
23
|
const skill_mirror_1 = require("./skill_mirror");
|
|
21
|
-
|
|
24
|
+
exports.RECOMMENDED_HEADINGS = {
|
|
22
25
|
task: [
|
|
23
26
|
"Overview",
|
|
24
27
|
"Acceptance Criteria",
|
|
@@ -103,6 +106,234 @@ function extractHeadings(body) {
|
|
|
103
106
|
}
|
|
104
107
|
return headings;
|
|
105
108
|
}
|
|
109
|
+
const RAW_CONTENT_MARKERS = [
|
|
110
|
+
{ id: "raw_prompt", pattern: /\bRAW_PROMPT_MARKER\b/i, description: "raw prompt marker" },
|
|
111
|
+
{ id: "raw_payload", pattern: /\bRAW_PAYLOAD_MARKER\b/i, description: "raw payload marker" },
|
|
112
|
+
{ id: "raw_secret", pattern: /\bRAW_SECRET_MARKER\b|BEGIN [A-Z ]*PRIVATE KEY|secret\s*=/i, description: "raw secret marker" },
|
|
113
|
+
];
|
|
114
|
+
function shouldCheckRawContentWarnings(node) {
|
|
115
|
+
if ((0, agent_file_types_1.isAgentFileType)(node.type)) {
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
return node.type === "checkpoint" && typeof node.frontmatter.checkpoint_kind === "string";
|
|
119
|
+
}
|
|
120
|
+
function collectRawContentWarnings(qid, node) {
|
|
121
|
+
if (!shouldCheckRawContentWarnings(node)) {
|
|
122
|
+
return [];
|
|
123
|
+
}
|
|
124
|
+
const warnings = [];
|
|
125
|
+
for (const marker of RAW_CONTENT_MARKERS) {
|
|
126
|
+
if (marker.pattern.test(node.body)) {
|
|
127
|
+
warnings.push(`${qid}: raw-content.${marker.id} warning: ${marker.description} detected; use refs, hashes, summaries, or artifact links instead of raw secrets/prompts/payloads`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return warnings;
|
|
131
|
+
}
|
|
132
|
+
function collectManifestCompatibilityWarnings(qid, filePath, node) {
|
|
133
|
+
const basename = path_1.default.basename(filePath);
|
|
134
|
+
if (basename === agent_file_types_1.LEGACY_SPEC_BASENAME && node.type === "spec") {
|
|
135
|
+
return [
|
|
136
|
+
`${qid}: manifest.compat.spec_legacy warning: SPEC.md is legacy; MANIFEST.md is the canonical manifest filename. Rename this file before the compatibility release closes.`,
|
|
137
|
+
];
|
|
138
|
+
}
|
|
139
|
+
if (basename === agent_file_types_1.CANONICAL_MANIFEST_BASENAME && node.type === "spec") {
|
|
140
|
+
return [
|
|
141
|
+
`${qid}: manifest.compat.type_spec warning: MANIFEST.md uses legacy type: spec; use type: manifest before the compatibility release closes.`,
|
|
142
|
+
];
|
|
143
|
+
}
|
|
144
|
+
return [];
|
|
145
|
+
}
|
|
146
|
+
function collectChangedPaths(root) {
|
|
147
|
+
const result = (0, child_process_1.spawnSync)("git", ["-C", root, "status", "--porcelain", "--", ".mdkg"], {
|
|
148
|
+
encoding: "utf8",
|
|
149
|
+
});
|
|
150
|
+
if (result.status !== 0) {
|
|
151
|
+
return new Set();
|
|
152
|
+
}
|
|
153
|
+
const changed = new Set();
|
|
154
|
+
for (const line of result.stdout.split(/\r?\n/)) {
|
|
155
|
+
if (!line.trim()) {
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
const rawPath = line.slice(3).trim();
|
|
159
|
+
const filePath = rawPath.includes(" -> ") ? rawPath.split(" -> ").pop() ?? rawPath : rawPath;
|
|
160
|
+
changed.add(filePath.replace(/\\/g, "/"));
|
|
161
|
+
}
|
|
162
|
+
return changed;
|
|
163
|
+
}
|
|
164
|
+
function qidFromWarning(message) {
|
|
165
|
+
const match = /^([a-z0-9_-]+:[^\s:]+):/.exec(message);
|
|
166
|
+
return match?.[1];
|
|
167
|
+
}
|
|
168
|
+
function warningPath(message, nodes) {
|
|
169
|
+
const qid = qidFromWarning(message);
|
|
170
|
+
if (qid && nodes[qid]) {
|
|
171
|
+
return nodes[qid].path;
|
|
172
|
+
}
|
|
173
|
+
for (const node of Object.values(nodes)) {
|
|
174
|
+
if (message.includes(node.path)) {
|
|
175
|
+
return node.path;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
const match = /([.]mdkg\/[^\s:]+\.md|[.]mdkg\/[^\s:]+\/SKILLS?\.md)/.exec(message);
|
|
179
|
+
return match?.[1];
|
|
180
|
+
}
|
|
181
|
+
function warningDiagnostic(message, nodes) {
|
|
182
|
+
const qid = qidFromWarning(message);
|
|
183
|
+
const nodeType = qid && nodes[qid] ? nodes[qid].type : undefined;
|
|
184
|
+
const pathValue = warningPath(message, nodes);
|
|
185
|
+
const rawMatch = /raw-content\.([a-z_]+)/.exec(message);
|
|
186
|
+
if (rawMatch) {
|
|
187
|
+
return {
|
|
188
|
+
id: `raw-content.${rawMatch[1]}`,
|
|
189
|
+
category: "raw-content",
|
|
190
|
+
severity: "warning",
|
|
191
|
+
message,
|
|
192
|
+
qid,
|
|
193
|
+
node_type: nodeType,
|
|
194
|
+
path: pathValue,
|
|
195
|
+
ref: qid,
|
|
196
|
+
remediation: "Replace raw secrets, prompts, tokens, or payloads with refs, hashes, redacted summaries, or archive/artifact links.",
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
if (message.includes("missing recommended heading")) {
|
|
200
|
+
return {
|
|
201
|
+
id: "heading.missing",
|
|
202
|
+
category: "headings",
|
|
203
|
+
severity: "warning",
|
|
204
|
+
message,
|
|
205
|
+
qid,
|
|
206
|
+
node_type: nodeType,
|
|
207
|
+
path: pathValue,
|
|
208
|
+
ref: qid,
|
|
209
|
+
remediation: "Run mdkg format --headings --dry-run to review missing heading additions, then --apply if acceptable.",
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
if (message.includes("bundled template schema fallback")) {
|
|
213
|
+
return {
|
|
214
|
+
id: "template_schema.fallback",
|
|
215
|
+
category: "templates",
|
|
216
|
+
severity: "warning",
|
|
217
|
+
message,
|
|
218
|
+
node_type: nodeType,
|
|
219
|
+
path: pathValue,
|
|
220
|
+
remediation: "Run mdkg upgrade --apply to vendor missing built-in template schemas when the managed asset update is safe.",
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
const manifestCompatMatch = /manifest\.compat\.([a-z_]+)/.exec(message);
|
|
224
|
+
if (manifestCompatMatch) {
|
|
225
|
+
return {
|
|
226
|
+
id: `manifest.compat.${manifestCompatMatch[1]}`,
|
|
227
|
+
category: "manifest-compatibility",
|
|
228
|
+
severity: "warning",
|
|
229
|
+
message,
|
|
230
|
+
qid,
|
|
231
|
+
node_type: nodeType,
|
|
232
|
+
path: pathValue,
|
|
233
|
+
ref: qid,
|
|
234
|
+
remediation: "Rename legacy SPEC.md files to MANIFEST.md and update transitional type: spec frontmatter to type: manifest before the compatibility release closes.",
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
if (message.includes("sqlite") || message.includes("index")) {
|
|
238
|
+
return {
|
|
239
|
+
id: "cache.index",
|
|
240
|
+
category: "cache",
|
|
241
|
+
severity: "warning",
|
|
242
|
+
message,
|
|
243
|
+
node_type: nodeType,
|
|
244
|
+
path: pathValue,
|
|
245
|
+
remediation: "Run mdkg index or mdkg db index rebuild when generated cache state should be refreshed.",
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
if (message.includes("skill") || message.includes("mirror")) {
|
|
249
|
+
return {
|
|
250
|
+
id: "skill_mirror.warning",
|
|
251
|
+
category: "skills",
|
|
252
|
+
severity: "warning",
|
|
253
|
+
message,
|
|
254
|
+
node_type: nodeType,
|
|
255
|
+
path: pathValue,
|
|
256
|
+
remediation: "Run mdkg skill sync after reviewing managed skill mirror drift.",
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
if (message.includes("subgraph")) {
|
|
260
|
+
return {
|
|
261
|
+
id: "subgraph.warning",
|
|
262
|
+
category: "subgraph",
|
|
263
|
+
severity: "warning",
|
|
264
|
+
message,
|
|
265
|
+
node_type: nodeType,
|
|
266
|
+
path: pathValue,
|
|
267
|
+
remediation: "Run mdkg subgraph verify or refresh the source bundle after reviewing child graph freshness.",
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
return {
|
|
271
|
+
id: "warning.generic",
|
|
272
|
+
category: "general",
|
|
273
|
+
severity: "warning",
|
|
274
|
+
message,
|
|
275
|
+
qid,
|
|
276
|
+
node_type: nodeType,
|
|
277
|
+
path: pathValue,
|
|
278
|
+
ref: qid,
|
|
279
|
+
remediation: "Review the warning and apply the focused mdkg command suggested by the message when appropriate.",
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
function countBuckets(values) {
|
|
283
|
+
const counts = new Map();
|
|
284
|
+
for (const value of values) {
|
|
285
|
+
if (!value) {
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
counts.set(value, (counts.get(value) ?? 0) + 1);
|
|
289
|
+
}
|
|
290
|
+
return Array.from(counts.entries())
|
|
291
|
+
.map(([key, count]) => ({ key, count }))
|
|
292
|
+
.sort((a, b) => b.count - a.count || a.key.localeCompare(b.key));
|
|
293
|
+
}
|
|
294
|
+
function normalizeLimit(limit) {
|
|
295
|
+
if (limit === undefined) {
|
|
296
|
+
return 50;
|
|
297
|
+
}
|
|
298
|
+
if (!Number.isInteger(limit) || limit < 0) {
|
|
299
|
+
throw new errors_1.ValidationError("--limit must be a non-negative integer");
|
|
300
|
+
}
|
|
301
|
+
return limit;
|
|
302
|
+
}
|
|
303
|
+
function buildWarningSummary(diagnostics, limit) {
|
|
304
|
+
const effectiveLimit = limit === null || limit === undefined ? diagnostics.length : limit;
|
|
305
|
+
const emitted = Math.min(diagnostics.length, effectiveLimit);
|
|
306
|
+
const truncated = emitted < diagnostics.length;
|
|
307
|
+
return {
|
|
308
|
+
total: diagnostics.length,
|
|
309
|
+
emitted,
|
|
310
|
+
truncated,
|
|
311
|
+
omitted_count: diagnostics.length - emitted,
|
|
312
|
+
limit: limit === undefined ? null : limit,
|
|
313
|
+
affected_file_count: new Set(diagnostics.map((diagnostic) => diagnostic.path).filter(Boolean)).size,
|
|
314
|
+
by_id: countBuckets(diagnostics.map((diagnostic) => diagnostic.id)),
|
|
315
|
+
by_category: countBuckets(diagnostics.map((diagnostic) => diagnostic.category)),
|
|
316
|
+
by_node_type: countBuckets(diagnostics.map((diagnostic) => diagnostic.node_type)),
|
|
317
|
+
top_qids: countBuckets(diagnostics.map((diagnostic) => diagnostic.qid)).slice(0, 25),
|
|
318
|
+
top_paths: countBuckets(diagnostics.map((diagnostic) => diagnostic.path)).slice(0, 25),
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
function shapeValidateReceiptForSummary(receipt, limit) {
|
|
322
|
+
const effectiveLimit = normalizeLimit(limit);
|
|
323
|
+
const warningDiagnostics = receipt.warning_diagnostics.slice(0, effectiveLimit);
|
|
324
|
+
return {
|
|
325
|
+
...receipt,
|
|
326
|
+
warnings: warningDiagnostics.map((warning) => warning.message),
|
|
327
|
+
warning_diagnostics: warningDiagnostics,
|
|
328
|
+
warning_summary: buildWarningSummary(receipt.warning_diagnostics, effectiveLimit),
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
function writeJsonReceipt(root, jsonOut, receipt) {
|
|
332
|
+
const outPath = path_1.default.resolve(root, jsonOut);
|
|
333
|
+
fs_1.default.mkdirSync(path_1.default.dirname(outPath), { recursive: true });
|
|
334
|
+
fs_1.default.writeFileSync(outPath, `${JSON.stringify(receipt, null, 2)}\n`, "utf8");
|
|
335
|
+
return outPath;
|
|
336
|
+
}
|
|
106
337
|
function isCoreListFile(filePath) {
|
|
107
338
|
return path_1.default.basename(filePath) === "core.md" && path_1.default.basename(path_1.default.dirname(filePath)) === "core";
|
|
108
339
|
}
|
|
@@ -121,6 +352,8 @@ function normalizeEdges(edges, ws) {
|
|
|
121
352
|
relates: edges.relates.map((value) => normalizeEdgeTarget(value, ws)),
|
|
122
353
|
blocked_by: edges.blocked_by.map((value) => normalizeEdgeTarget(value, ws)),
|
|
123
354
|
blocks: edges.blocks.map((value) => normalizeEdgeTarget(value, ws)),
|
|
355
|
+
context_refs: (edges.context_refs ?? []).map((value) => normalizeEdgeTarget(value, ws)),
|
|
356
|
+
evidence_refs: (edges.evidence_refs ?? []).map((value) => normalizeEdgeTarget(value, ws)),
|
|
124
357
|
};
|
|
125
358
|
}
|
|
126
359
|
function buildIndexNode(root, ws, filePath, node) {
|
|
@@ -178,6 +411,12 @@ function buildReverseEdges(nodes) {
|
|
|
178
411
|
for (const target of node.edges.blocks) {
|
|
179
412
|
addReverseEdge(reverse, "blocks", target, qid);
|
|
180
413
|
}
|
|
414
|
+
for (const target of node.edges.context_refs ?? []) {
|
|
415
|
+
addReverseEdge(reverse, "context_refs", target, qid);
|
|
416
|
+
}
|
|
417
|
+
for (const target of node.edges.evidence_refs ?? []) {
|
|
418
|
+
addReverseEdge(reverse, "evidence_refs", target, qid);
|
|
419
|
+
}
|
|
181
420
|
}
|
|
182
421
|
for (const targets of Object.values(reverse)) {
|
|
183
422
|
for (const sources of Object.values(targets)) {
|
|
@@ -264,6 +503,7 @@ function collectValidateReceipt(options) {
|
|
|
264
503
|
const idsByWorkspace = {};
|
|
265
504
|
for (const [alias, files] of Object.entries(filesByAlias)) {
|
|
266
505
|
idsByWorkspace[alias] = new Map();
|
|
506
|
+
errors.push(...(0, agent_file_types_1.collectManifestSiblingConflicts)(files, (dirPath) => path_1.default.relative(options.root, dirPath).split(path_1.default.sep).join("/") || "."));
|
|
267
507
|
for (const filePath of files) {
|
|
268
508
|
if (isCoreListFile(filePath)) {
|
|
269
509
|
continue;
|
|
@@ -292,7 +532,7 @@ function collectValidateReceipt(options) {
|
|
|
292
532
|
idsByWorkspace[alias].set(node.id, filePath);
|
|
293
533
|
const qid = `${alias}:${node.id}`;
|
|
294
534
|
nodes[qid] = buildIndexNode(options.root, alias, filePath, node);
|
|
295
|
-
const recommended = RECOMMENDED_HEADINGS[node.type];
|
|
535
|
+
const recommended = exports.RECOMMENDED_HEADINGS[node.type];
|
|
296
536
|
if (recommended) {
|
|
297
537
|
const headings = extractHeadings(node.body);
|
|
298
538
|
for (const heading of recommended) {
|
|
@@ -301,6 +541,8 @@ function collectValidateReceipt(options) {
|
|
|
301
541
|
}
|
|
302
542
|
}
|
|
303
543
|
}
|
|
544
|
+
warnings.push(...collectRawContentWarnings(qid, node));
|
|
545
|
+
warnings.push(...collectManifestCompatibilityWarnings(qid, filePath, node));
|
|
304
546
|
}
|
|
305
547
|
catch (err) {
|
|
306
548
|
const message = err instanceof Error ? err.message : "unknown error";
|
|
@@ -372,7 +614,13 @@ function collectValidateReceipt(options) {
|
|
|
372
614
|
}
|
|
373
615
|
warnings.push(...(0, skill_mirror_1.auditSkillMirrors)(options.root, config));
|
|
374
616
|
validateEventsJsonl(options.root, config, errors);
|
|
375
|
-
const
|
|
617
|
+
const allUniqueWarnings = Array.from(new Set(warnings));
|
|
618
|
+
const allWarningDiagnostics = allUniqueWarnings.map((warning) => warningDiagnostic(warning, nodes));
|
|
619
|
+
const changedPaths = options.changedOnly ? collectChangedPaths(options.root) : new Set();
|
|
620
|
+
const filteredWarningDiagnostics = options.changedOnly
|
|
621
|
+
? allWarningDiagnostics.filter((warning) => warning.path !== undefined && changedPaths.has(warning.path))
|
|
622
|
+
: allWarningDiagnostics;
|
|
623
|
+
const uniqueWarnings = filteredWarningDiagnostics.map((warning) => warning.message);
|
|
376
624
|
const uniqueErrors = Array.from(new Set(errors));
|
|
377
625
|
const reportLines = [
|
|
378
626
|
...uniqueWarnings.map((warning) => `warning: ${warning}`),
|
|
@@ -390,24 +638,38 @@ function collectValidateReceipt(options) {
|
|
|
390
638
|
warning_count: uniqueWarnings.length,
|
|
391
639
|
error_count: uniqueErrors.length,
|
|
392
640
|
warnings: uniqueWarnings,
|
|
641
|
+
warning_diagnostics: filteredWarningDiagnostics,
|
|
642
|
+
warning_summary: buildWarningSummary(filteredWarningDiagnostics),
|
|
393
643
|
errors: uniqueErrors,
|
|
394
644
|
...(outPath ? { report_path: outPath } : {}),
|
|
645
|
+
...(options.changedOnly
|
|
646
|
+
? { warning_filter: { mode: "changed-only", changed_paths: Array.from(changedPaths).sort() } }
|
|
647
|
+
: {}),
|
|
395
648
|
};
|
|
396
649
|
return receipt;
|
|
397
650
|
}
|
|
398
651
|
function runValidateCommand(options) {
|
|
399
|
-
|
|
652
|
+
let receipt = collectValidateReceipt(options);
|
|
653
|
+
if (options.jsonOut) {
|
|
654
|
+
const jsonOutPath = path_1.default.resolve(options.root, options.jsonOut);
|
|
655
|
+
receipt = { ...receipt, json_receipt_path: jsonOutPath };
|
|
656
|
+
writeJsonReceipt(options.root, options.jsonOut, receipt);
|
|
657
|
+
}
|
|
658
|
+
const displayReceipt = options.summary ? shapeValidateReceiptForSummary(receipt, options.limit) : receipt;
|
|
400
659
|
if (options.json) {
|
|
401
|
-
console.log(JSON.stringify(
|
|
660
|
+
console.log(JSON.stringify(displayReceipt, null, 2));
|
|
402
661
|
if (receipt.error_count > 0) {
|
|
403
662
|
throw new errors_1.ValidationError(`validation failed with ${receipt.error_count} error(s)`);
|
|
404
663
|
}
|
|
405
664
|
return;
|
|
406
665
|
}
|
|
407
666
|
if (!options.quiet) {
|
|
408
|
-
for (const warning of
|
|
667
|
+
for (const warning of displayReceipt.warnings) {
|
|
409
668
|
console.error(`warning: ${warning}`);
|
|
410
669
|
}
|
|
670
|
+
if (displayReceipt.warning_summary.truncated) {
|
|
671
|
+
console.error(`warning summary: omitted ${displayReceipt.warning_summary.omitted_count} warning(s); rerun without --summary or use --json-out <path> for full diagnostics`);
|
|
672
|
+
}
|
|
411
673
|
}
|
|
412
674
|
if (receipt.error_count > 0) {
|
|
413
675
|
if (receipt.report_path) {
|
package/dist/commands/work.js
CHANGED
|
@@ -11,6 +11,7 @@ exports.runWorkTriggerCommand = runWorkTriggerCommand;
|
|
|
11
11
|
exports.runWorkReceiptNewCommand = runWorkReceiptNewCommand;
|
|
12
12
|
exports.runWorkReceiptUpdateCommand = runWorkReceiptUpdateCommand;
|
|
13
13
|
exports.runWorkReceiptVerifyCommand = runWorkReceiptVerifyCommand;
|
|
14
|
+
exports.runWorkValidateCommand = runWorkValidateCommand;
|
|
14
15
|
exports.runWorkArtifactAddCommand = runWorkArtifactAddCommand;
|
|
15
16
|
const fs_1 = __importDefault(require("fs"));
|
|
16
17
|
const path_1 = __importDefault(require("path"));
|
|
@@ -34,11 +35,15 @@ const archive_1 = require("./archive");
|
|
|
34
35
|
const project_db_1 = require("../core/project_db");
|
|
35
36
|
const project_db_migrations_1 = require("../core/project_db_migrations");
|
|
36
37
|
const project_db_queue_1 = require("../core/project_db_queue");
|
|
38
|
+
const validate_1 = require("./validate");
|
|
39
|
+
const query_output_1 = require("./query_output");
|
|
40
|
+
const workspace_files_1 = require("../graph/workspace_files");
|
|
37
41
|
const PRICING_MODELS = new Set(["free", "included", "quoted", "fixed", "metered", "subscription"]);
|
|
38
42
|
const ORDER_STATUSES = new Set(["submitted", "accepted", "running", "completed", "cancelled", "failed"]);
|
|
39
43
|
const RECEIPT_STATUSES = new Set(["recorded", "verified", "rejected", "superseded"]);
|
|
40
44
|
const OUTCOMES = new Set(["success", "partial", "failure"]);
|
|
41
45
|
const REDACTION_POLICIES = new Set(["refs_and_hashes_only", "redacted_summary", "external_private"]);
|
|
46
|
+
const WORKFLOW_VALIDATE_TYPES = new Set(agent_file_types_1.AGENT_FILE_TYPES);
|
|
42
47
|
function parseCsvList(raw) {
|
|
43
48
|
if (!raw) {
|
|
44
49
|
return [];
|
|
@@ -210,6 +215,166 @@ function resolveReadableWorkNode(index, idOrQid, ws, type, label) {
|
|
|
210
215
|
}
|
|
211
216
|
return node;
|
|
212
217
|
}
|
|
218
|
+
function normalizeWorkflowValidateType(value) {
|
|
219
|
+
if (value === undefined) {
|
|
220
|
+
return undefined;
|
|
221
|
+
}
|
|
222
|
+
const normalized = value.toLowerCase();
|
|
223
|
+
if (!WORKFLOW_VALIDATE_TYPES.has(normalized)) {
|
|
224
|
+
throw new errors_1.UsageError(`--type must be one of ${agent_file_types_1.AGENT_FILE_TYPES.join(", ")}`);
|
|
225
|
+
}
|
|
226
|
+
return normalized;
|
|
227
|
+
}
|
|
228
|
+
function isWorkflowNode(node) {
|
|
229
|
+
return WORKFLOW_VALIDATE_TYPES.has(node.type);
|
|
230
|
+
}
|
|
231
|
+
function resolveWorkflowTarget(index, idOrQid, ws) {
|
|
232
|
+
const resolved = (0, qid_1.resolveQid)(index, idOrQid, ws);
|
|
233
|
+
if (resolved.status !== "ok") {
|
|
234
|
+
throw new errors_1.NotFoundError((0, qid_1.formatResolveError)("workflow record", idOrQid, resolved, ws));
|
|
235
|
+
}
|
|
236
|
+
const node = index.nodes[resolved.qid];
|
|
237
|
+
if (!node || !isWorkflowNode(node)) {
|
|
238
|
+
throw new errors_1.NotFoundError(`workflow record not found: ${idOrQid}`);
|
|
239
|
+
}
|
|
240
|
+
return node;
|
|
241
|
+
}
|
|
242
|
+
function workflowDiagnosticCode(message) {
|
|
243
|
+
const rawMatch = /raw-content\.([a-z_]+)/.exec(message);
|
|
244
|
+
if (rawMatch) {
|
|
245
|
+
return `raw-content.${rawMatch[1]}`;
|
|
246
|
+
}
|
|
247
|
+
if (message.includes("missing recommended heading")) {
|
|
248
|
+
return "heading.missing";
|
|
249
|
+
}
|
|
250
|
+
const manifestCompatMatch = /manifest\.compat\.([a-z_]+)/.exec(message);
|
|
251
|
+
if (manifestCompatMatch) {
|
|
252
|
+
return `manifest.compat.${manifestCompatMatch[1]}`;
|
|
253
|
+
}
|
|
254
|
+
if (message.includes("references missing") || message.includes("references missing node")) {
|
|
255
|
+
return "reference.missing";
|
|
256
|
+
}
|
|
257
|
+
if (message.includes("must be named")) {
|
|
258
|
+
return "schema.basename";
|
|
259
|
+
}
|
|
260
|
+
if (message.includes("must be") || message.includes("is required")) {
|
|
261
|
+
return "schema.invalid";
|
|
262
|
+
}
|
|
263
|
+
if (message.includes("visibility:")) {
|
|
264
|
+
return "visibility.policy";
|
|
265
|
+
}
|
|
266
|
+
return "validation.message";
|
|
267
|
+
}
|
|
268
|
+
function workflowDiagnosticQid(message, nodes) {
|
|
269
|
+
for (const node of nodes) {
|
|
270
|
+
if (message.includes(node.qid) || message.includes(node.path)) {
|
|
271
|
+
return node.qid;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return undefined;
|
|
275
|
+
}
|
|
276
|
+
function workflowCandidatePaths(options) {
|
|
277
|
+
const values = new Set();
|
|
278
|
+
if (options.target) {
|
|
279
|
+
values.add(options.target.path);
|
|
280
|
+
values.add(path_1.default.resolve(options.root, options.target.path));
|
|
281
|
+
return Array.from(values);
|
|
282
|
+
}
|
|
283
|
+
const basenames = options.type
|
|
284
|
+
? new Set([agent_file_types_1.AGENT_FILE_BASENAMES[options.type]])
|
|
285
|
+
: new Set(Object.values(agent_file_types_1.AGENT_FILE_BASENAMES));
|
|
286
|
+
const filesByAlias = (0, workspace_files_1.listWorkspaceDocFilesByAlias)(options.root, options.config);
|
|
287
|
+
for (const [alias, files] of Object.entries(filesByAlias)) {
|
|
288
|
+
if (options.ws !== "root" && alias !== options.ws) {
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
for (const filePath of files) {
|
|
292
|
+
if (!basenames.has(path_1.default.basename(filePath))) {
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
values.add(filePath);
|
|
296
|
+
values.add(path_1.default.relative(options.root, filePath).split(path_1.default.sep).join("/"));
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return Array.from(values);
|
|
300
|
+
}
|
|
301
|
+
function filterWorkflowMessages(messages, nodes, candidatePaths) {
|
|
302
|
+
if (nodes.length === 0 && candidatePaths.length === 0) {
|
|
303
|
+
return [];
|
|
304
|
+
}
|
|
305
|
+
return messages.filter((message) => nodes.some((node) => message.includes(node.qid) || message.includes(node.path)) ||
|
|
306
|
+
candidatePaths.some((filePath) => message.includes(filePath)));
|
|
307
|
+
}
|
|
308
|
+
function buildWorkValidateReceipt(options) {
|
|
309
|
+
const config = (0, config_1.loadConfig)(options.root);
|
|
310
|
+
const ws = normalizeWorkspace(options.ws);
|
|
311
|
+
const { index } = (0, index_cache_1.loadIndex)({ root: options.root, config, tolerant: true });
|
|
312
|
+
const type = normalizeWorkflowValidateType(options.type);
|
|
313
|
+
let targets;
|
|
314
|
+
let target;
|
|
315
|
+
if (options.id) {
|
|
316
|
+
target = resolveWorkflowTarget(index, options.id, ws);
|
|
317
|
+
if (type && target.type !== type) {
|
|
318
|
+
throw new errors_1.UsageError(`workflow record ${target.qid} is ${target.type}, not ${type}`);
|
|
319
|
+
}
|
|
320
|
+
targets = [target];
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
targets = Object.values(index.nodes)
|
|
324
|
+
.filter((node) => isWorkflowNode(node) && (!type || node.type === type))
|
|
325
|
+
.sort((a, b) => a.qid.localeCompare(b.qid));
|
|
326
|
+
}
|
|
327
|
+
const candidatePaths = workflowCandidatePaths({ root: options.root, config, ws, type, target });
|
|
328
|
+
const validation = (0, validate_1.collectValidateReceipt)({ root: options.root });
|
|
329
|
+
const warnings = filterWorkflowMessages(validation.warnings, targets, candidatePaths);
|
|
330
|
+
const errors = filterWorkflowMessages(validation.errors, targets, candidatePaths);
|
|
331
|
+
const diagnostics = [
|
|
332
|
+
...warnings.map((message) => ({
|
|
333
|
+
severity: "warning",
|
|
334
|
+
code: workflowDiagnosticCode(message),
|
|
335
|
+
message,
|
|
336
|
+
qid: workflowDiagnosticQid(message, targets),
|
|
337
|
+
})),
|
|
338
|
+
...errors.map((message) => ({
|
|
339
|
+
severity: "error",
|
|
340
|
+
code: workflowDiagnosticCode(message),
|
|
341
|
+
message,
|
|
342
|
+
qid: workflowDiagnosticQid(message, targets),
|
|
343
|
+
})),
|
|
344
|
+
];
|
|
345
|
+
return {
|
|
346
|
+
action: "work.validate",
|
|
347
|
+
ok: errors.length === 0,
|
|
348
|
+
type: type ?? "all",
|
|
349
|
+
...(target ? { target: (0, query_output_1.toNodeSummaryJson)(target) } : {}),
|
|
350
|
+
checked_count: target ? 1 : candidatePaths.filter((value) => !path_1.default.isAbsolute(value)).length,
|
|
351
|
+
nodes: targets.map((node) => (0, query_output_1.toNodeSummaryJson)(node)),
|
|
352
|
+
warning_count: warnings.length,
|
|
353
|
+
error_count: errors.length,
|
|
354
|
+
warnings,
|
|
355
|
+
errors,
|
|
356
|
+
diagnostics,
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
function printWorkValidateReceipt(receipt, json) {
|
|
360
|
+
if (json) {
|
|
361
|
+
(0, query_output_1.writeJson)(receipt);
|
|
362
|
+
if (!receipt.ok) {
|
|
363
|
+
throw new errors_1.ValidationError(`workflow validation failed with ${receipt.error_count} error(s)`);
|
|
364
|
+
}
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
for (const warning of receipt.warnings) {
|
|
368
|
+
console.error(`warning: ${warning}`);
|
|
369
|
+
}
|
|
370
|
+
if (!receipt.ok) {
|
|
371
|
+
for (const error of receipt.errors) {
|
|
372
|
+
console.error(error);
|
|
373
|
+
}
|
|
374
|
+
throw new errors_1.ValidationError(`workflow validation failed with ${receipt.error_count} error(s)`);
|
|
375
|
+
}
|
|
376
|
+
console.log(`workflow validation ok: ${receipt.checked_count} file(s)`);
|
|
377
|
+
}
|
|
213
378
|
function resolveTriggerWorkNode(index, ws, refRaw) {
|
|
214
379
|
const ref = normalizePortableIdRef(refRaw, "<work-or-capability-ref>");
|
|
215
380
|
const resolved = (0, qid_1.resolveQid)(index, ref, ws);
|
|
@@ -223,8 +388,8 @@ function resolveTriggerWorkNode(index, ws, refRaw) {
|
|
|
223
388
|
if (node.type === "work") {
|
|
224
389
|
return { workNode: node };
|
|
225
390
|
}
|
|
226
|
-
if (node.type
|
|
227
|
-
throw new errors_1.UsageError(`work trigger requires a WORK.md or SPEC.md ref, got ${node.type}: ${node.qid}`);
|
|
391
|
+
if (!(0, agent_file_types_1.isManifestSemanticType)(node.type)) {
|
|
392
|
+
throw new errors_1.UsageError(`work trigger requires a WORK.md or MANIFEST.md/SPEC.md ref, got ${node.type}: ${node.qid}`);
|
|
228
393
|
}
|
|
229
394
|
const candidates = new Map();
|
|
230
395
|
const specDir = path_1.default.posix.dirname(node.path);
|
|
@@ -250,11 +415,12 @@ function resolveTriggerWorkNode(index, ws, refRaw) {
|
|
|
250
415
|
}
|
|
251
416
|
}
|
|
252
417
|
const workNodes = Array.from(candidates.values()).sort((a, b) => a.qid.localeCompare(b.qid));
|
|
418
|
+
const manifestLabel = node.type === "spec" ? "legacy SPEC.md" : "MANIFEST.md";
|
|
253
419
|
if (workNodes.length === 0) {
|
|
254
|
-
throw new errors_1.NotFoundError(
|
|
420
|
+
throw new errors_1.NotFoundError(`${manifestLabel} ${node.qid} has no resolvable WORK.md contract`);
|
|
255
421
|
}
|
|
256
422
|
if (workNodes.length > 1) {
|
|
257
|
-
throw new errors_1.UsageError(
|
|
423
|
+
throw new errors_1.UsageError(`${manifestLabel} ${node.qid} has multiple work contracts; trigger one explicitly: ${workNodes
|
|
258
424
|
.map((workNode) => workNode.qid)
|
|
259
425
|
.join(", ")}`);
|
|
260
426
|
}
|
|
@@ -596,7 +762,9 @@ function runWorkContractNewCommandLocked(options) {
|
|
|
596
762
|
const agentId = normalizePortableIdRef(options.agentId, "--agent-id");
|
|
597
763
|
const kind = options.kind.toLowerCase();
|
|
598
764
|
const resolvedAgent = (0, qid_1.resolveQid)(index, agentId, ws);
|
|
599
|
-
const relates = resolvedAgent.status === "ok" && index.nodes[resolvedAgent.qid]?.type
|
|
765
|
+
const relates = resolvedAgent.status === "ok" && (0, agent_file_types_1.isManifestSemanticType)(index.nodes[resolvedAgent.qid]?.type ?? "")
|
|
766
|
+
? [agentId]
|
|
767
|
+
: [];
|
|
600
768
|
const requiredCapabilities = parseCsvList(options.requiredCapabilities);
|
|
601
769
|
const receipt = createAgentWorkflowNode({
|
|
602
770
|
root: options.root,
|
|
@@ -943,6 +1111,9 @@ function runWorkReceiptUpdateCommand(options) {
|
|
|
943
1111
|
function runWorkReceiptVerifyCommand(options) {
|
|
944
1112
|
return runWorkReceiptVerifyCommandLocked(options);
|
|
945
1113
|
}
|
|
1114
|
+
function runWorkValidateCommand(options) {
|
|
1115
|
+
return printWorkValidateReceipt(buildWorkValidateReceipt(options), options.json);
|
|
1116
|
+
}
|
|
946
1117
|
function runWorkArtifactAddCommand(options) {
|
|
947
1118
|
return withWorkLock(options.root, () => runWorkArtifactAddCommandLocked(options));
|
|
948
1119
|
}
|