mdkg 0.3.5 → 0.3.7
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 +97 -2
- package/CLI_COMMAND_MATRIX.md +90 -10
- package/README.md +54 -7
- package/dist/cli.js +222 -13
- package/dist/command-contract.json +587 -19
- package/dist/commands/bundle.js +2 -0
- package/dist/commands/checkpoint.js +139 -1
- package/dist/commands/db.js +8 -0
- package/dist/commands/format.js +116 -0
- package/dist/commands/goal.js +60 -0
- package/dist/commands/graph.js +280 -10
- package/dist/commands/handoff.js +295 -0
- package/dist/commands/mcp.js +656 -0
- 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/status.js +1 -0
- package/dist/commands/task.js +2 -0
- package/dist/commands/upgrade.js +61 -0
- package/dist/commands/validate.js +162 -3
- package/dist/commands/work.js +164 -0
- package/dist/core/project_db_queue_contract.js +101 -0
- package/dist/graph/edges.js +15 -0
- package/dist/graph/frontmatter.js +4 -1
- package/dist/graph/indexer.js +8 -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/validate_graph.js +5 -0
- package/dist/graph/visibility.js +6 -0
- package/dist/init/AGENT_START.md +4 -1
- package/dist/init/CLI_COMMAND_MATRIX.md +36 -4
- package/dist/init/README.md +26 -3
- package/dist/init/init-manifest.json +12 -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/spike.md +2 -0
- package/dist/init/templates/default/task.md +2 -0
- package/dist/init/templates/default/test.md +2 -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 +1 -0
- package/package.json +7 -2
package/dist/commands/status.js
CHANGED
|
@@ -229,6 +229,7 @@ function collectStatus(root) {
|
|
|
229
229
|
selected_exists: selected.state === null ? null : !selectedMissing,
|
|
230
230
|
selected_achieved: selected.state === null ? null : selectedAchieved,
|
|
231
231
|
active_node: selectedNode?.attributes.active_node ?? null,
|
|
232
|
+
last_active_node: selectedNode?.attributes.last_active_node ?? null,
|
|
232
233
|
goal_state: selectedNode?.attributes.goal_state ?? null,
|
|
233
234
|
status: selectedNode?.status ?? null,
|
|
234
235
|
},
|
package/dist/commands/task.js
CHANGED
|
@@ -297,6 +297,7 @@ function runTaskDoneCommandLocked(options) {
|
|
|
297
297
|
ws: loaded.ws,
|
|
298
298
|
relates: loaded.id,
|
|
299
299
|
scope: loaded.id,
|
|
300
|
+
kind: options.checkpointKind,
|
|
300
301
|
runId: options.runId,
|
|
301
302
|
note: `checkpoint created from mdkg task done for ${loaded.id}`,
|
|
302
303
|
now,
|
|
@@ -309,6 +310,7 @@ function runTaskDoneCommandLocked(options) {
|
|
|
309
310
|
ws: loaded.ws,
|
|
310
311
|
relates: loaded.id,
|
|
311
312
|
scope: loaded.id,
|
|
313
|
+
kind: options.checkpointKind,
|
|
312
314
|
runId: options.runId,
|
|
313
315
|
note: `checkpoint created from mdkg task done for ${loaded.id}`,
|
|
314
316
|
now,
|
package/dist/commands/upgrade.js
CHANGED
|
@@ -13,6 +13,8 @@ const paths_1 = require("../core/paths");
|
|
|
13
13
|
const version_1 = require("../core/version");
|
|
14
14
|
const project_db_1 = require("../core/project_db");
|
|
15
15
|
const errors_1 = require("../util/errors");
|
|
16
|
+
const frontmatter_1 = require("../graph/frontmatter");
|
|
17
|
+
const workspace_files_1 = require("../graph/workspace_files");
|
|
16
18
|
const init_manifest_1 = require("./init_manifest");
|
|
17
19
|
const skill_support_1 = require("./skill_support");
|
|
18
20
|
const skill_mirror_1 = require("./skill_mirror");
|
|
@@ -273,6 +275,64 @@ function migrateConfigIfNeeded(root, dryRun, summary, changes) {
|
|
|
273
275
|
writeFile(cfgPath, `${JSON.stringify(nextConfig.config, null, 2)}\n`);
|
|
274
276
|
}
|
|
275
277
|
}
|
|
278
|
+
function migrateClosedGoalActiveNodes(root, dryRun, summary, changes) {
|
|
279
|
+
const config = (0, config_1.validateConfigSchema)((0, migrate_1.migrateConfig)(JSON.parse(fs_1.default.readFileSync((0, paths_1.configPath)(root), "utf8"))).config);
|
|
280
|
+
const filesByAlias = (0, workspace_files_1.listWorkspaceDocFilesByAlias)(root, config);
|
|
281
|
+
let planned = 0;
|
|
282
|
+
for (const files of Object.values(filesByAlias)) {
|
|
283
|
+
for (const filePath of files) {
|
|
284
|
+
const content = fs_1.default.readFileSync(filePath, "utf8");
|
|
285
|
+
let parsed;
|
|
286
|
+
try {
|
|
287
|
+
parsed = (0, frontmatter_1.parseFrontmatter)(content, filePath);
|
|
288
|
+
}
|
|
289
|
+
catch {
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
const frontmatter = { ...parsed.frontmatter };
|
|
293
|
+
if (frontmatter.type !== "goal") {
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
const status = typeof frontmatter.status === "string" ? frontmatter.status : "";
|
|
297
|
+
const goalState = typeof frontmatter.goal_state === "string" ? frontmatter.goal_state : "";
|
|
298
|
+
const activeNode = typeof frontmatter.active_node === "string" ? frontmatter.active_node : undefined;
|
|
299
|
+
if (!activeNode || (status !== "done" && goalState !== "achieved")) {
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
const relativePath = path_1.default.relative(root, filePath).split(path_1.default.sep).join("/");
|
|
303
|
+
const lastActiveNode = typeof frontmatter.last_active_node === "string" ? frontmatter.last_active_node : undefined;
|
|
304
|
+
if (lastActiveNode && lastActiveNode !== activeNode) {
|
|
305
|
+
planned += 1;
|
|
306
|
+
record(summary, changes, {
|
|
307
|
+
path: relativePath,
|
|
308
|
+
category: "goal_lifecycle",
|
|
309
|
+
action: "conflict",
|
|
310
|
+
reason: `closed goal has active_node ${activeNode} but different last_active_node ${lastActiveNode}; local content preserved`,
|
|
311
|
+
});
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
planned += 1;
|
|
315
|
+
record(summary, changes, {
|
|
316
|
+
path: relativePath,
|
|
317
|
+
category: "goal_lifecycle",
|
|
318
|
+
action: "migrate",
|
|
319
|
+
reason: "move closed goal active_node to last_active_node",
|
|
320
|
+
});
|
|
321
|
+
if (!dryRun) {
|
|
322
|
+
if (!lastActiveNode) {
|
|
323
|
+
frontmatter.last_active_node = activeNode;
|
|
324
|
+
}
|
|
325
|
+
delete frontmatter.active_node;
|
|
326
|
+
const frontmatterBlock = ["---", ...(0, frontmatter_1.formatFrontmatter)(frontmatter, frontmatter_1.DEFAULT_FRONTMATTER_KEY_ORDER), "---"].join("\n");
|
|
327
|
+
const body = parsed.body.length > 0 ? parsed.body : "";
|
|
328
|
+
writeFile(filePath, body.length > 0 ? `${frontmatterBlock}\n${body}` : frontmatterBlock);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
if (planned === 0) {
|
|
333
|
+
summary.unchanged += 1;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
276
336
|
function isIgnoredBySimpleGitignore(root, relativePath) {
|
|
277
337
|
const ignorePath = path_1.default.join(root, ".gitignore");
|
|
278
338
|
if (!fs_1.default.existsSync(ignorePath)) {
|
|
@@ -440,6 +500,7 @@ function runUpgradeCommand(options) {
|
|
|
440
500
|
const agentWorkspace = isAgentWorkspace(root);
|
|
441
501
|
const managedCurrentFiles = [];
|
|
442
502
|
migrateConfigIfNeeded(root, dryRun, summary, changes);
|
|
503
|
+
migrateClosedGoalActiveNodes(root, dryRun, summary, changes);
|
|
443
504
|
for (const file of currentManifest.files) {
|
|
444
505
|
if (!shouldIncludeFile(file, agentWorkspace)) {
|
|
445
506
|
continue;
|
|
@@ -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,143 @@ 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 collectChangedPaths(root) {
|
|
133
|
+
const result = (0, child_process_1.spawnSync)("git", ["-C", root, "status", "--porcelain", "--", ".mdkg"], {
|
|
134
|
+
encoding: "utf8",
|
|
135
|
+
});
|
|
136
|
+
if (result.status !== 0) {
|
|
137
|
+
return new Set();
|
|
138
|
+
}
|
|
139
|
+
const changed = new Set();
|
|
140
|
+
for (const line of result.stdout.split(/\r?\n/)) {
|
|
141
|
+
if (!line.trim()) {
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
const rawPath = line.slice(3).trim();
|
|
145
|
+
const filePath = rawPath.includes(" -> ") ? rawPath.split(" -> ").pop() ?? rawPath : rawPath;
|
|
146
|
+
changed.add(filePath.replace(/\\/g, "/"));
|
|
147
|
+
}
|
|
148
|
+
return changed;
|
|
149
|
+
}
|
|
150
|
+
function qidFromWarning(message) {
|
|
151
|
+
const match = /^([a-z0-9_-]+:[^\s:]+):/.exec(message);
|
|
152
|
+
return match?.[1];
|
|
153
|
+
}
|
|
154
|
+
function warningPath(message, nodes) {
|
|
155
|
+
const qid = qidFromWarning(message);
|
|
156
|
+
if (qid && nodes[qid]) {
|
|
157
|
+
return nodes[qid].path;
|
|
158
|
+
}
|
|
159
|
+
for (const node of Object.values(nodes)) {
|
|
160
|
+
if (message.includes(node.path)) {
|
|
161
|
+
return node.path;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
const match = /([.]mdkg\/[^\s:]+\.md|[.]mdkg\/[^\s:]+\/SKILLS?\.md)/.exec(message);
|
|
165
|
+
return match?.[1];
|
|
166
|
+
}
|
|
167
|
+
function warningDiagnostic(message, nodes) {
|
|
168
|
+
const qid = qidFromWarning(message);
|
|
169
|
+
const pathValue = warningPath(message, nodes);
|
|
170
|
+
const rawMatch = /raw-content\.([a-z_]+)/.exec(message);
|
|
171
|
+
if (rawMatch) {
|
|
172
|
+
return {
|
|
173
|
+
id: `raw-content.${rawMatch[1]}`,
|
|
174
|
+
category: "raw-content",
|
|
175
|
+
severity: "warning",
|
|
176
|
+
message,
|
|
177
|
+
qid,
|
|
178
|
+
path: pathValue,
|
|
179
|
+
ref: qid,
|
|
180
|
+
remediation: "Replace raw secrets, prompts, tokens, or payloads with refs, hashes, redacted summaries, or archive/artifact links.",
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
if (message.includes("missing recommended heading")) {
|
|
184
|
+
return {
|
|
185
|
+
id: "heading.missing",
|
|
186
|
+
category: "headings",
|
|
187
|
+
severity: "warning",
|
|
188
|
+
message,
|
|
189
|
+
qid,
|
|
190
|
+
path: pathValue,
|
|
191
|
+
ref: qid,
|
|
192
|
+
remediation: "Run mdkg format --headings --dry-run to review missing heading additions, then --apply if acceptable.",
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
if (message.includes("bundled template schema fallback")) {
|
|
196
|
+
return {
|
|
197
|
+
id: "template_schema.fallback",
|
|
198
|
+
category: "templates",
|
|
199
|
+
severity: "warning",
|
|
200
|
+
message,
|
|
201
|
+
path: pathValue,
|
|
202
|
+
remediation: "Run mdkg upgrade --apply to vendor missing built-in template schemas when the managed asset update is safe.",
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
if (message.includes("sqlite") || message.includes("index")) {
|
|
206
|
+
return {
|
|
207
|
+
id: "cache.index",
|
|
208
|
+
category: "cache",
|
|
209
|
+
severity: "warning",
|
|
210
|
+
message,
|
|
211
|
+
path: pathValue,
|
|
212
|
+
remediation: "Run mdkg index or mdkg db index rebuild when generated cache state should be refreshed.",
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
if (message.includes("skill") || message.includes("mirror")) {
|
|
216
|
+
return {
|
|
217
|
+
id: "skill_mirror.warning",
|
|
218
|
+
category: "skills",
|
|
219
|
+
severity: "warning",
|
|
220
|
+
message,
|
|
221
|
+
path: pathValue,
|
|
222
|
+
remediation: "Run mdkg skill sync after reviewing managed skill mirror drift.",
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
if (message.includes("subgraph")) {
|
|
226
|
+
return {
|
|
227
|
+
id: "subgraph.warning",
|
|
228
|
+
category: "subgraph",
|
|
229
|
+
severity: "warning",
|
|
230
|
+
message,
|
|
231
|
+
path: pathValue,
|
|
232
|
+
remediation: "Run mdkg subgraph verify or refresh the source bundle after reviewing child graph freshness.",
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
return {
|
|
236
|
+
id: "warning.generic",
|
|
237
|
+
category: "general",
|
|
238
|
+
severity: "warning",
|
|
239
|
+
message,
|
|
240
|
+
qid,
|
|
241
|
+
path: pathValue,
|
|
242
|
+
ref: qid,
|
|
243
|
+
remediation: "Review the warning and apply the focused mdkg command suggested by the message when appropriate.",
|
|
244
|
+
};
|
|
245
|
+
}
|
|
106
246
|
function isCoreListFile(filePath) {
|
|
107
247
|
return path_1.default.basename(filePath) === "core.md" && path_1.default.basename(path_1.default.dirname(filePath)) === "core";
|
|
108
248
|
}
|
|
@@ -121,6 +261,8 @@ function normalizeEdges(edges, ws) {
|
|
|
121
261
|
relates: edges.relates.map((value) => normalizeEdgeTarget(value, ws)),
|
|
122
262
|
blocked_by: edges.blocked_by.map((value) => normalizeEdgeTarget(value, ws)),
|
|
123
263
|
blocks: edges.blocks.map((value) => normalizeEdgeTarget(value, ws)),
|
|
264
|
+
context_refs: (edges.context_refs ?? []).map((value) => normalizeEdgeTarget(value, ws)),
|
|
265
|
+
evidence_refs: (edges.evidence_refs ?? []).map((value) => normalizeEdgeTarget(value, ws)),
|
|
124
266
|
};
|
|
125
267
|
}
|
|
126
268
|
function buildIndexNode(root, ws, filePath, node) {
|
|
@@ -178,6 +320,12 @@ function buildReverseEdges(nodes) {
|
|
|
178
320
|
for (const target of node.edges.blocks) {
|
|
179
321
|
addReverseEdge(reverse, "blocks", target, qid);
|
|
180
322
|
}
|
|
323
|
+
for (const target of node.edges.context_refs ?? []) {
|
|
324
|
+
addReverseEdge(reverse, "context_refs", target, qid);
|
|
325
|
+
}
|
|
326
|
+
for (const target of node.edges.evidence_refs ?? []) {
|
|
327
|
+
addReverseEdge(reverse, "evidence_refs", target, qid);
|
|
328
|
+
}
|
|
181
329
|
}
|
|
182
330
|
for (const targets of Object.values(reverse)) {
|
|
183
331
|
for (const sources of Object.values(targets)) {
|
|
@@ -292,7 +440,7 @@ function collectValidateReceipt(options) {
|
|
|
292
440
|
idsByWorkspace[alias].set(node.id, filePath);
|
|
293
441
|
const qid = `${alias}:${node.id}`;
|
|
294
442
|
nodes[qid] = buildIndexNode(options.root, alias, filePath, node);
|
|
295
|
-
const recommended = RECOMMENDED_HEADINGS[node.type];
|
|
443
|
+
const recommended = exports.RECOMMENDED_HEADINGS[node.type];
|
|
296
444
|
if (recommended) {
|
|
297
445
|
const headings = extractHeadings(node.body);
|
|
298
446
|
for (const heading of recommended) {
|
|
@@ -301,6 +449,7 @@ function collectValidateReceipt(options) {
|
|
|
301
449
|
}
|
|
302
450
|
}
|
|
303
451
|
}
|
|
452
|
+
warnings.push(...collectRawContentWarnings(qid, node));
|
|
304
453
|
}
|
|
305
454
|
catch (err) {
|
|
306
455
|
const message = err instanceof Error ? err.message : "unknown error";
|
|
@@ -372,7 +521,13 @@ function collectValidateReceipt(options) {
|
|
|
372
521
|
}
|
|
373
522
|
warnings.push(...(0, skill_mirror_1.auditSkillMirrors)(options.root, config));
|
|
374
523
|
validateEventsJsonl(options.root, config, errors);
|
|
375
|
-
const
|
|
524
|
+
const allUniqueWarnings = Array.from(new Set(warnings));
|
|
525
|
+
const allWarningDiagnostics = allUniqueWarnings.map((warning) => warningDiagnostic(warning, nodes));
|
|
526
|
+
const changedPaths = options.changedOnly ? collectChangedPaths(options.root) : new Set();
|
|
527
|
+
const filteredWarningDiagnostics = options.changedOnly
|
|
528
|
+
? allWarningDiagnostics.filter((warning) => warning.path !== undefined && changedPaths.has(warning.path))
|
|
529
|
+
: allWarningDiagnostics;
|
|
530
|
+
const uniqueWarnings = filteredWarningDiagnostics.map((warning) => warning.message);
|
|
376
531
|
const uniqueErrors = Array.from(new Set(errors));
|
|
377
532
|
const reportLines = [
|
|
378
533
|
...uniqueWarnings.map((warning) => `warning: ${warning}`),
|
|
@@ -390,8 +545,12 @@ function collectValidateReceipt(options) {
|
|
|
390
545
|
warning_count: uniqueWarnings.length,
|
|
391
546
|
error_count: uniqueErrors.length,
|
|
392
547
|
warnings: uniqueWarnings,
|
|
548
|
+
warning_diagnostics: filteredWarningDiagnostics,
|
|
393
549
|
errors: uniqueErrors,
|
|
394
550
|
...(outPath ? { report_path: outPath } : {}),
|
|
551
|
+
...(options.changedOnly
|
|
552
|
+
? { warning_filter: { mode: "changed-only", changed_paths: Array.from(changedPaths).sort() } }
|
|
553
|
+
: {}),
|
|
395
554
|
};
|
|
396
555
|
return receipt;
|
|
397
556
|
}
|
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,162 @@ 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
|
+
if (message.includes("references missing") || message.includes("references missing node")) {
|
|
251
|
+
return "reference.missing";
|
|
252
|
+
}
|
|
253
|
+
if (message.includes("must be named")) {
|
|
254
|
+
return "schema.basename";
|
|
255
|
+
}
|
|
256
|
+
if (message.includes("must be") || message.includes("is required")) {
|
|
257
|
+
return "schema.invalid";
|
|
258
|
+
}
|
|
259
|
+
if (message.includes("visibility:")) {
|
|
260
|
+
return "visibility.policy";
|
|
261
|
+
}
|
|
262
|
+
return "validation.message";
|
|
263
|
+
}
|
|
264
|
+
function workflowDiagnosticQid(message, nodes) {
|
|
265
|
+
for (const node of nodes) {
|
|
266
|
+
if (message.includes(node.qid) || message.includes(node.path)) {
|
|
267
|
+
return node.qid;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return undefined;
|
|
271
|
+
}
|
|
272
|
+
function workflowCandidatePaths(options) {
|
|
273
|
+
const values = new Set();
|
|
274
|
+
if (options.target) {
|
|
275
|
+
values.add(options.target.path);
|
|
276
|
+
values.add(path_1.default.resolve(options.root, options.target.path));
|
|
277
|
+
return Array.from(values);
|
|
278
|
+
}
|
|
279
|
+
const basenames = options.type
|
|
280
|
+
? new Set([agent_file_types_1.AGENT_FILE_BASENAMES[options.type]])
|
|
281
|
+
: new Set(Object.values(agent_file_types_1.AGENT_FILE_BASENAMES));
|
|
282
|
+
const filesByAlias = (0, workspace_files_1.listWorkspaceDocFilesByAlias)(options.root, options.config);
|
|
283
|
+
for (const [alias, files] of Object.entries(filesByAlias)) {
|
|
284
|
+
if (options.ws !== "root" && alias !== options.ws) {
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
for (const filePath of files) {
|
|
288
|
+
if (!basenames.has(path_1.default.basename(filePath))) {
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
values.add(filePath);
|
|
292
|
+
values.add(path_1.default.relative(options.root, filePath).split(path_1.default.sep).join("/"));
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
return Array.from(values);
|
|
296
|
+
}
|
|
297
|
+
function filterWorkflowMessages(messages, nodes, candidatePaths) {
|
|
298
|
+
if (nodes.length === 0 && candidatePaths.length === 0) {
|
|
299
|
+
return [];
|
|
300
|
+
}
|
|
301
|
+
return messages.filter((message) => nodes.some((node) => message.includes(node.qid) || message.includes(node.path)) ||
|
|
302
|
+
candidatePaths.some((filePath) => message.includes(filePath)));
|
|
303
|
+
}
|
|
304
|
+
function buildWorkValidateReceipt(options) {
|
|
305
|
+
const config = (0, config_1.loadConfig)(options.root);
|
|
306
|
+
const ws = normalizeWorkspace(options.ws);
|
|
307
|
+
const { index } = (0, index_cache_1.loadIndex)({ root: options.root, config, tolerant: true });
|
|
308
|
+
const type = normalizeWorkflowValidateType(options.type);
|
|
309
|
+
let targets;
|
|
310
|
+
let target;
|
|
311
|
+
if (options.id) {
|
|
312
|
+
target = resolveWorkflowTarget(index, options.id, ws);
|
|
313
|
+
if (type && target.type !== type) {
|
|
314
|
+
throw new errors_1.UsageError(`workflow record ${target.qid} is ${target.type}, not ${type}`);
|
|
315
|
+
}
|
|
316
|
+
targets = [target];
|
|
317
|
+
}
|
|
318
|
+
else {
|
|
319
|
+
targets = Object.values(index.nodes)
|
|
320
|
+
.filter((node) => isWorkflowNode(node) && (!type || node.type === type))
|
|
321
|
+
.sort((a, b) => a.qid.localeCompare(b.qid));
|
|
322
|
+
}
|
|
323
|
+
const candidatePaths = workflowCandidatePaths({ root: options.root, config, ws, type, target });
|
|
324
|
+
const validation = (0, validate_1.collectValidateReceipt)({ root: options.root });
|
|
325
|
+
const warnings = filterWorkflowMessages(validation.warnings, targets, candidatePaths);
|
|
326
|
+
const errors = filterWorkflowMessages(validation.errors, targets, candidatePaths);
|
|
327
|
+
const diagnostics = [
|
|
328
|
+
...warnings.map((message) => ({
|
|
329
|
+
severity: "warning",
|
|
330
|
+
code: workflowDiagnosticCode(message),
|
|
331
|
+
message,
|
|
332
|
+
qid: workflowDiagnosticQid(message, targets),
|
|
333
|
+
})),
|
|
334
|
+
...errors.map((message) => ({
|
|
335
|
+
severity: "error",
|
|
336
|
+
code: workflowDiagnosticCode(message),
|
|
337
|
+
message,
|
|
338
|
+
qid: workflowDiagnosticQid(message, targets),
|
|
339
|
+
})),
|
|
340
|
+
];
|
|
341
|
+
return {
|
|
342
|
+
action: "work.validate",
|
|
343
|
+
ok: errors.length === 0,
|
|
344
|
+
type: type ?? "all",
|
|
345
|
+
...(target ? { target: (0, query_output_1.toNodeSummaryJson)(target) } : {}),
|
|
346
|
+
checked_count: target ? 1 : candidatePaths.filter((value) => !path_1.default.isAbsolute(value)).length,
|
|
347
|
+
nodes: targets.map((node) => (0, query_output_1.toNodeSummaryJson)(node)),
|
|
348
|
+
warning_count: warnings.length,
|
|
349
|
+
error_count: errors.length,
|
|
350
|
+
warnings,
|
|
351
|
+
errors,
|
|
352
|
+
diagnostics,
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
function printWorkValidateReceipt(receipt, json) {
|
|
356
|
+
if (json) {
|
|
357
|
+
(0, query_output_1.writeJson)(receipt);
|
|
358
|
+
if (!receipt.ok) {
|
|
359
|
+
throw new errors_1.ValidationError(`workflow validation failed with ${receipt.error_count} error(s)`);
|
|
360
|
+
}
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
for (const warning of receipt.warnings) {
|
|
364
|
+
console.error(`warning: ${warning}`);
|
|
365
|
+
}
|
|
366
|
+
if (!receipt.ok) {
|
|
367
|
+
for (const error of receipt.errors) {
|
|
368
|
+
console.error(error);
|
|
369
|
+
}
|
|
370
|
+
throw new errors_1.ValidationError(`workflow validation failed with ${receipt.error_count} error(s)`);
|
|
371
|
+
}
|
|
372
|
+
console.log(`workflow validation ok: ${receipt.checked_count} file(s)`);
|
|
373
|
+
}
|
|
213
374
|
function resolveTriggerWorkNode(index, ws, refRaw) {
|
|
214
375
|
const ref = normalizePortableIdRef(refRaw, "<work-or-capability-ref>");
|
|
215
376
|
const resolved = (0, qid_1.resolveQid)(index, ref, ws);
|
|
@@ -943,6 +1104,9 @@ function runWorkReceiptUpdateCommand(options) {
|
|
|
943
1104
|
function runWorkReceiptVerifyCommand(options) {
|
|
944
1105
|
return runWorkReceiptVerifyCommandLocked(options);
|
|
945
1106
|
}
|
|
1107
|
+
function runWorkValidateCommand(options) {
|
|
1108
|
+
return printWorkValidateReceipt(buildWorkValidateReceipt(options), options.json);
|
|
1109
|
+
}
|
|
946
1110
|
function runWorkArtifactAddCommand(options) {
|
|
947
1111
|
return withWorkLock(options.root, () => runWorkArtifactAddCommandLocked(options));
|
|
948
1112
|
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.projectDbQueueAdapterContract = projectDbQueueAdapterContract;
|
|
4
|
+
function projectDbQueueAdapterContract() {
|
|
5
|
+
return {
|
|
6
|
+
schema_version: 1,
|
|
7
|
+
contract_id: "mdkg.project_db.queue.adapter.v1",
|
|
8
|
+
stability: "public",
|
|
9
|
+
boundary: {
|
|
10
|
+
role: "durable local delivery state for mdkg project DB integrations",
|
|
11
|
+
canonical_history: "queue rows are not canonical event history or durable runtime transcripts",
|
|
12
|
+
raw_payload_policy: "store compact refs and redacted payloads; do not store raw secrets, prompts, provider payloads, or bulky runtime artifacts",
|
|
13
|
+
internal_surfaces: ["event", "receipt", "reducer", "writer_lease", "materializer"],
|
|
14
|
+
},
|
|
15
|
+
runtime: {
|
|
16
|
+
database: "node:sqlite",
|
|
17
|
+
transactions: "short BEGIN IMMEDIATE transactions for writes and claims",
|
|
18
|
+
external_dependencies: [],
|
|
19
|
+
},
|
|
20
|
+
commands: [
|
|
21
|
+
"mdkg db queue create <queue>",
|
|
22
|
+
"mdkg db queue pause <queue>",
|
|
23
|
+
"mdkg db queue resume <queue>",
|
|
24
|
+
"mdkg db queue enqueue <queue> <message-id>",
|
|
25
|
+
"mdkg db queue claim <queue>",
|
|
26
|
+
"mdkg db queue ack <queue> <message-id>",
|
|
27
|
+
"mdkg db queue fail <queue> <message-id>",
|
|
28
|
+
"mdkg db queue dead-letter <queue> <message-id>",
|
|
29
|
+
"mdkg db queue release-expired [queue]",
|
|
30
|
+
"mdkg db queue stats [queue]",
|
|
31
|
+
"mdkg db queue list <queue>",
|
|
32
|
+
"mdkg db queue show <queue> <message-id>",
|
|
33
|
+
"mdkg db queue contract",
|
|
34
|
+
],
|
|
35
|
+
queue_control: {
|
|
36
|
+
table: "project_queue",
|
|
37
|
+
states: ["active", "paused"],
|
|
38
|
+
paused_behavior: {
|
|
39
|
+
rejects: ["enqueue", "claim"],
|
|
40
|
+
allows: ["ack", "fail", "dead-letter", "release-expired", "stats", "list", "show"],
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
message: {
|
|
44
|
+
table: "project_queue_message",
|
|
45
|
+
states: ["ready", "leased", "acked", "dead_letter"],
|
|
46
|
+
fields: [
|
|
47
|
+
"queue_name",
|
|
48
|
+
"message_id",
|
|
49
|
+
"dedupe_key",
|
|
50
|
+
"payload_json",
|
|
51
|
+
"payload_hash",
|
|
52
|
+
"status",
|
|
53
|
+
"available_at_ms",
|
|
54
|
+
"attempt_count",
|
|
55
|
+
"max_attempts",
|
|
56
|
+
"lease_owner",
|
|
57
|
+
"lease_deadline_ms",
|
|
58
|
+
"created_at_ms",
|
|
59
|
+
"updated_at_ms",
|
|
60
|
+
"last_error",
|
|
61
|
+
],
|
|
62
|
+
terminal_states: ["acked", "dead_letter"],
|
|
63
|
+
},
|
|
64
|
+
payload_hash: {
|
|
65
|
+
algorithm: "sha256",
|
|
66
|
+
encoding: "sha256:<64 lowercase hex chars>",
|
|
67
|
+
canonicalization: "payload JSON is serialized deterministically with object keys sorted before hashing and storage",
|
|
68
|
+
},
|
|
69
|
+
dedupe: {
|
|
70
|
+
key: "queue_name + dedupe_key",
|
|
71
|
+
scope: "only non-null dedupe keys participate in the partial unique index",
|
|
72
|
+
duplicate_behavior: "enqueue with an existing dedupe key returns the existing message without replacing payload_json or payload_hash",
|
|
73
|
+
},
|
|
74
|
+
claim: {
|
|
75
|
+
selection: "oldest ready or expired leased message ordered by available_at_ms, created_at_ms, then message_id",
|
|
76
|
+
lease: "claim sets status=leased, lease_owner, and lease_deadline_ms; ack/fail/dead-letter must use the same lease owner",
|
|
77
|
+
transactional: true,
|
|
78
|
+
},
|
|
79
|
+
settlement: {
|
|
80
|
+
ack: "leased message becomes acked and clears lease owner/deadline",
|
|
81
|
+
fail: "leased message increments attempt_count; if attempts remain it becomes ready at now + retry_after_ms, otherwise it becomes dead_letter",
|
|
82
|
+
dead_letter: "leased message becomes dead_letter immediately and records last_error",
|
|
83
|
+
release_expired: "expired leased messages become ready and clear lease owner/deadline without changing attempt_count",
|
|
84
|
+
},
|
|
85
|
+
stats: {
|
|
86
|
+
counters: ["total", "ready", "leased", "acked", "dead_letter", "ready_available", "leased_expired"],
|
|
87
|
+
snapshot_summary: ["total", "ready", "leased", "acked", "dead_letter", "paused_ready", "active_ready"],
|
|
88
|
+
},
|
|
89
|
+
snapshot_policy: {
|
|
90
|
+
drain: "default seal policy; requires no ready or leased delivery work",
|
|
91
|
+
paused: "allows ready messages only when their queues are paused; leased messages are never allowed",
|
|
92
|
+
},
|
|
93
|
+
adapter_guidance: [
|
|
94
|
+
"create queues explicitly before enqueueing integration work",
|
|
95
|
+
"use dedupe keys for idempotent delivery",
|
|
96
|
+
"treat message payloads as refs and redacted envelopes, not canonical runtime state",
|
|
97
|
+
"settle or pause queues before committing sealed project DB state",
|
|
98
|
+
"use stats/list/show for operator review and avoid direct SQL coupling",
|
|
99
|
+
],
|
|
100
|
+
};
|
|
101
|
+
}
|
package/dist/graph/edges.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.extractEdges = extractEdges;
|
|
4
4
|
const id_1 = require("../util/id");
|
|
5
|
+
const refs_1 = require("../util/refs");
|
|
5
6
|
function formatError(filePath, key, message) {
|
|
6
7
|
return new Error(`${filePath}: ${key} ${message}`);
|
|
7
8
|
}
|
|
@@ -18,6 +19,12 @@ function normalizeIdRef(value, filePath, key, options) {
|
|
|
18
19
|
}
|
|
19
20
|
return normalized;
|
|
20
21
|
}
|
|
22
|
+
function normalizeSemanticRef(value, filePath, key) {
|
|
23
|
+
if (!(0, refs_1.validatePortableOrUriRef)(value)) {
|
|
24
|
+
throw formatError(filePath, key, `invalid semantic reference: ${value}`);
|
|
25
|
+
}
|
|
26
|
+
return value.includes("://") ? value : value.toLowerCase();
|
|
27
|
+
}
|
|
21
28
|
function readString(fm, key) {
|
|
22
29
|
const value = fm[key];
|
|
23
30
|
if (value === undefined) {
|
|
@@ -46,10 +53,18 @@ function extractEdges(frontmatter, filePath, options = {}) {
|
|
|
46
53
|
const relates = readStringList(frontmatter, "relates") ?? [];
|
|
47
54
|
const blocked_by = readStringList(frontmatter, "blocked_by") ?? [];
|
|
48
55
|
const blocks = readStringList(frontmatter, "blocks") ?? [];
|
|
56
|
+
const context_refs = options.includeSemanticRefs
|
|
57
|
+
? readStringList(frontmatter, "context_refs") ?? []
|
|
58
|
+
: [];
|
|
59
|
+
const evidence_refs = options.includeSemanticRefs
|
|
60
|
+
? readStringList(frontmatter, "evidence_refs") ?? []
|
|
61
|
+
: [];
|
|
49
62
|
const edges = {
|
|
50
63
|
relates: relates.map((value) => normalizeIdRef(value, filePath, "relates", options)),
|
|
51
64
|
blocked_by: blocked_by.map((value) => normalizeIdRef(value, filePath, "blocked_by", options)),
|
|
52
65
|
blocks: blocks.map((value) => normalizeIdRef(value, filePath, "blocks", options)),
|
|
66
|
+
context_refs: context_refs.map((value) => normalizeSemanticRef(value, filePath, "context_refs")),
|
|
67
|
+
evidence_refs: evidence_refs.map((value) => normalizeSemanticRef(value, filePath, "evidence_refs")),
|
|
53
68
|
};
|
|
54
69
|
if (epic) {
|
|
55
70
|
edges.epic = normalizeIdRef(epic, filePath, "epic", options);
|