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.
Files changed (68) hide show
  1. package/CHANGELOG.md +93 -0
  2. package/CLI_COMMAND_MATRIX.md +132 -24
  3. package/README.md +80 -25
  4. package/dist/cli.js +193 -36
  5. package/dist/command-contract.json +882 -122
  6. package/dist/commands/bundle.js +2 -0
  7. package/dist/commands/capability.js +1 -0
  8. package/dist/commands/checkpoint.js +139 -1
  9. package/dist/commands/db.js +8 -0
  10. package/dist/commands/doctor.js +7 -7
  11. package/dist/commands/format.js +155 -0
  12. package/dist/commands/goal.js +60 -0
  13. package/dist/commands/graph.js +148 -0
  14. package/dist/commands/handoff.js +301 -0
  15. package/dist/commands/mcp.js +9 -0
  16. package/dist/commands/new.js +12 -3
  17. package/dist/commands/pack.js +3 -1
  18. package/dist/commands/query_output.js +2 -0
  19. package/dist/commands/show.js +8 -0
  20. package/dist/commands/spec.js +76 -17
  21. package/dist/commands/status.js +1 -0
  22. package/dist/commands/subgraph.js +7 -4
  23. package/dist/commands/task.js +2 -0
  24. package/dist/commands/upgrade.js +128 -3
  25. package/dist/commands/validate.js +268 -6
  26. package/dist/commands/work.js +176 -5
  27. package/dist/core/project_db_queue_contract.js +101 -0
  28. package/dist/graph/agent_file_types.js +59 -20
  29. package/dist/graph/capabilities_indexer.js +45 -3
  30. package/dist/graph/edges.js +15 -0
  31. package/dist/graph/frontmatter.js +4 -1
  32. package/dist/graph/indexer.js +13 -0
  33. package/dist/graph/node.js +12 -1
  34. package/dist/graph/sqlite_index.js +2 -0
  35. package/dist/graph/subgraphs.js +2 -0
  36. package/dist/graph/template_schema.js +37 -17
  37. package/dist/graph/validate_graph.js +16 -5
  38. package/dist/graph/visibility.js +6 -0
  39. package/dist/init/AGENT_START.md +9 -6
  40. package/dist/init/CLI_COMMAND_MATRIX.md +50 -16
  41. package/dist/init/README.md +26 -9
  42. package/dist/init/init-manifest.json +67 -12
  43. package/dist/init/templates/default/bug.md +2 -0
  44. package/dist/init/templates/default/chk.md +3 -0
  45. package/dist/init/templates/default/epic.md +2 -0
  46. package/dist/init/templates/default/feat.md +2 -0
  47. package/dist/init/templates/default/goal.md +3 -0
  48. package/dist/init/templates/default/manifest.md +45 -0
  49. package/dist/init/templates/default/spike.md +2 -0
  50. package/dist/init/templates/default/task.md +2 -0
  51. package/dist/init/templates/default/test.md +2 -0
  52. package/dist/init/templates/specs/agent.MANIFEST.md +80 -0
  53. package/dist/init/templates/specs/api.MANIFEST.md +33 -0
  54. package/dist/init/templates/specs/base.MANIFEST.md +120 -0
  55. package/dist/init/templates/specs/capability.MANIFEST.md +45 -0
  56. package/dist/init/templates/specs/integration.MANIFEST.md +25 -0
  57. package/dist/init/templates/specs/model.MANIFEST.md +21 -0
  58. package/dist/init/templates/specs/project.MANIFEST.md +39 -0
  59. package/dist/init/templates/specs/runtime-agent.MANIFEST.md +49 -0
  60. package/dist/init/templates/specs/runtime-image.MANIFEST.md +21 -0
  61. package/dist/init/templates/specs/tool.MANIFEST.md +25 -0
  62. package/dist/pack/export_json.js +20 -8
  63. package/dist/pack/export_md.js +15 -4
  64. package/dist/pack/export_xml.js +9 -4
  65. package/dist/pack/metrics.js +12 -4
  66. package/dist/pack/pack.js +9 -1
  67. package/dist/util/argparse.js +3 -0
  68. package/package.json +21 -3
@@ -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
+ }
@@ -3,15 +3,20 @@ 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.AGENT_ATTRIBUTE_KEY_ORDER = exports.AGENT_FILE_BASENAMES = exports.AGENT_FILE_TYPES = void 0;
6
+ exports.AGENT_ATTRIBUTE_KEY_ORDER = exports.AGENT_FILE_BASENAMES = exports.AGENT_FILE_TYPES = exports.LEGACY_SPEC_BASENAME = exports.CANONICAL_MANIFEST_BASENAME = void 0;
7
7
  exports.isAgentFileType = isAgentFileType;
8
+ exports.isManifestSemanticType = isManifestSemanticType;
9
+ exports.collectManifestSiblingConflicts = collectManifestSiblingConflicts;
8
10
  exports.validateAgentFileName = validateAgentFileName;
9
11
  exports.validateAgentFrontmatter = validateAgentFrontmatter;
10
12
  exports.extractAgentAttributes = extractAgentAttributes;
11
13
  const path_1 = __importDefault(require("path"));
12
14
  const id_1 = require("../util/id");
13
15
  const refs_1 = require("../util/refs");
16
+ exports.CANONICAL_MANIFEST_BASENAME = "MANIFEST.md";
17
+ exports.LEGACY_SPEC_BASENAME = "SPEC.md";
14
18
  exports.AGENT_FILE_TYPES = [
19
+ "manifest",
15
20
  "spec",
16
21
  "work",
17
22
  "work_order",
@@ -21,7 +26,8 @@ exports.AGENT_FILE_TYPES = [
21
26
  "proposal",
22
27
  ];
23
28
  exports.AGENT_FILE_BASENAMES = {
24
- spec: "SPEC.md",
29
+ manifest: exports.CANONICAL_MANIFEST_BASENAME,
30
+ spec: exports.LEGACY_SPEC_BASENAME,
25
31
  work: "WORK.md",
26
32
  work_order: "WORK_ORDER.md",
27
33
  receipt: "RECEIPT.md",
@@ -29,23 +35,25 @@ exports.AGENT_FILE_BASENAMES = {
29
35
  dispute: "DISPUTE.md",
30
36
  proposal: "PROPOSAL.md",
31
37
  };
38
+ const MANIFEST_ATTRIBUTE_KEYS = [
39
+ "version",
40
+ "spec_kind",
41
+ "role",
42
+ "runtime_mode",
43
+ "work_contracts",
44
+ "requested_capabilities",
45
+ "skill_refs",
46
+ "tool_refs",
47
+ "model_refs",
48
+ "wasm_component_refs",
49
+ "runtime_image_refs",
50
+ "subagent_refs",
51
+ "resource_profile",
52
+ "update_policy",
53
+ ];
32
54
  exports.AGENT_ATTRIBUTE_KEY_ORDER = {
33
- spec: [
34
- "version",
35
- "spec_kind",
36
- "role",
37
- "runtime_mode",
38
- "work_contracts",
39
- "requested_capabilities",
40
- "skill_refs",
41
- "tool_refs",
42
- "model_refs",
43
- "wasm_component_refs",
44
- "runtime_image_refs",
45
- "subagent_refs",
46
- "resource_profile",
47
- "update_policy",
48
- ],
55
+ manifest: MANIFEST_ATTRIBUTE_KEYS,
56
+ spec: MANIFEST_ATTRIBUTE_KEYS,
49
57
  work: [
50
58
  "version",
51
59
  "agent_id",
@@ -207,9 +215,39 @@ function formatError(filePath, message) {
207
215
  function isAgentFileType(type) {
208
216
  return exports.AGENT_FILE_TYPES.includes(type);
209
217
  }
218
+ function isManifestSemanticType(type) {
219
+ return type === "manifest" || type === "spec";
220
+ }
221
+ function collectManifestSiblingConflicts(filePaths, formatDir) {
222
+ const basenamesByDir = new Map();
223
+ for (const filePath of filePaths) {
224
+ const basename = path_1.default.basename(filePath);
225
+ if (basename !== exports.CANONICAL_MANIFEST_BASENAME && basename !== exports.LEGACY_SPEC_BASENAME) {
226
+ continue;
227
+ }
228
+ const dirPath = path_1.default.dirname(filePath);
229
+ const basenames = basenamesByDir.get(dirPath) ?? new Set();
230
+ basenames.add(basename);
231
+ basenamesByDir.set(dirPath, basenames);
232
+ }
233
+ const conflicts = [];
234
+ for (const [dirPath, basenames] of Array.from(basenamesByDir.entries()).sort()) {
235
+ if (basenames.has(exports.CANONICAL_MANIFEST_BASENAME) && basenames.has(exports.LEGACY_SPEC_BASENAME)) {
236
+ conflicts.push(`${formatDir(dirPath)}: MANIFEST.md and SPEC.md cannot both exist in the same logical Omni unit; keep MANIFEST.md and remove the legacy SPEC.md alias`);
237
+ }
238
+ }
239
+ return conflicts;
240
+ }
210
241
  function validateAgentFileName(type, filePath) {
242
+ const basename = path_1.default.basename(filePath);
243
+ if (type === "spec" && basename === exports.CANONICAL_MANIFEST_BASENAME) {
244
+ return;
245
+ }
211
246
  const expected = exports.AGENT_FILE_BASENAMES[type];
212
- if (path_1.default.basename(filePath) !== expected) {
247
+ if (basename !== expected) {
248
+ if (isManifestSemanticType(type)) {
249
+ throw formatError(filePath, `manifest files must be named MANIFEST.md (canonical) or SPEC.md (legacy alias); ${type} files must be named ${expected}`);
250
+ }
213
251
  throw formatError(filePath, `${type} files must be named ${expected}`);
214
252
  }
215
253
  }
@@ -303,7 +341,7 @@ function validateSpecKind(value, filePath) {
303
341
  }
304
342
  const route = DOCUMENTATION_ONLY_SPEC_KIND_ROUTES[value];
305
343
  if (route) {
306
- throw formatError(filePath, `spec_kind ${value} is documentation-only; ${route}. SPEC.md must define a reusable invocable capability surface.`);
344
+ throw formatError(filePath, `spec_kind ${value} is documentation-only; ${route}. MANIFEST.md must define a reusable invocable capability surface; legacy SPEC.md follows the same contract.`);
307
345
  }
308
346
  throw formatError(filePath, `spec_kind must be one of ${Array.from(SPEC_KIND_VALUES).join(", ")}; documentation-only records belong in normal mdkg nodes such as task, test, epic, goal, checkpoint, EDD, PRD, DEC, bug, feedback, dispute, or proposal.`);
309
347
  }
@@ -398,6 +436,7 @@ function validateAgentFrontmatter(type, frontmatter, filePath) {
398
436
  const version = expectString(frontmatter, "version", filePath);
399
437
  requireSemver(version, "version", filePath);
400
438
  switch (type) {
439
+ case "manifest":
401
440
  case "spec": {
402
441
  const specKind = optionalString(frontmatter, "spec_kind", filePath);
403
442
  if (specKind) {
@@ -10,6 +10,7 @@ const crypto_1 = __importDefault(require("crypto"));
10
10
  const fs_1 = __importDefault(require("fs"));
11
11
  const path_1 = __importDefault(require("path"));
12
12
  const indexer_1 = require("./indexer");
13
+ const agent_file_types_1 = require("./agent_file_types");
13
14
  const skills_indexer_1 = require("./skills_indexer");
14
15
  exports.CAPABILITIES_INDEX_RELATIVE_PATH = ".mdkg/index/capabilities.json";
15
16
  exports.CAPABILITY_KINDS = ["skill", "spec", "work", "core", "design"];
@@ -41,7 +42,7 @@ function workspaceMdkgPrefix(entry) {
41
42
  return `${wsPath}${mdkgDir}/`;
42
43
  }
43
44
  function classifyNodeCapability(node, config) {
44
- if (node.type === "spec") {
45
+ if ((0, agent_file_types_1.isManifestSemanticType)(node.type)) {
45
46
  return "spec";
46
47
  }
47
48
  if (node.type === "work") {
@@ -115,7 +116,7 @@ function resolveWorkSpecs(index, workNode) {
115
116
  const candidates = new Map();
116
117
  const workRefs = nodeRefSet(workNode);
117
118
  for (const node of Object.values(index.nodes)) {
118
- if (node.type !== "spec" || node.ws !== workNode.ws) {
119
+ if (!(0, agent_file_types_1.isManifestSemanticType)(node.type) || node.ws !== workNode.ws) {
119
120
  continue;
120
121
  }
121
122
  const agentId = typeof workNode.attributes.agent_id === "string" ? workNode.attributes.agent_id : undefined;
@@ -159,9 +160,49 @@ function buildCapabilityLinkage(index, node, kind) {
159
160
  receipt_qids: receipts.map((receiptNode) => receiptNode.qid),
160
161
  };
161
162
  }
163
+ function manifestCapabilityMetadata(node) {
164
+ if (!(0, agent_file_types_1.isManifestSemanticType)(node.type)) {
165
+ return undefined;
166
+ }
167
+ const sourceBasename = path_1.default.posix.basename(node.path);
168
+ const compatibilityMode = sourceBasename === agent_file_types_1.LEGACY_SPEC_BASENAME
169
+ ? "legacy"
170
+ : node.type === "spec"
171
+ ? "transitional"
172
+ : "canonical";
173
+ return {
174
+ semantic_kind: "manifest",
175
+ source_basename: sourceBasename,
176
+ source_type: node.type,
177
+ canonical_basename: agent_file_types_1.CANONICAL_MANIFEST_BASENAME,
178
+ legacy_basename: agent_file_types_1.LEGACY_SPEC_BASENAME,
179
+ compatibility_mode: compatibilityMode,
180
+ legacy: compatibilityMode !== "canonical",
181
+ deprecated: compatibilityMode !== "canonical",
182
+ command_family: "manifest",
183
+ legacy_command_family: "spec",
184
+ };
185
+ }
186
+ function manifestSearchAliases(metadata) {
187
+ if (!metadata) {
188
+ return [];
189
+ }
190
+ return [
191
+ "manifest",
192
+ "manifest.md",
193
+ "MANIFEST.md",
194
+ "manifest capability",
195
+ "MANIFEST.md legacy SPEC.md",
196
+ "spec.md compatibility alias",
197
+ "legacy spec.md",
198
+ metadata.compatibility_mode,
199
+ metadata.source_basename,
200
+ ];
201
+ }
162
202
  function nodeCapabilityRecord(root, config, index, node, kind, indexedAt) {
163
203
  const absolutePath = path_1.default.resolve(root, node.path);
164
204
  const content = fs_1.default.readFileSync(absolutePath, "utf8");
205
+ const manifest = kind === "spec" ? manifestCapabilityMetadata(node) : undefined;
165
206
  const record = {
166
207
  kind,
167
208
  workspace: node.ws,
@@ -172,7 +213,7 @@ function nodeCapabilityRecord(root, config, index, node, kind, indexedAt) {
172
213
  title: node.title,
173
214
  tags: [...node.tags],
174
215
  refs: [...node.refs],
175
- aliases: [...node.aliases],
216
+ aliases: Array.from(new Set([...node.aliases, ...manifestSearchAliases(manifest)])),
176
217
  links: [...node.links],
177
218
  artifacts: [...node.artifacts],
178
219
  updated: node.updated,
@@ -182,6 +223,7 @@ function nodeCapabilityRecord(root, config, index, node, kind, indexedAt) {
182
223
  node_type: node.type,
183
224
  };
184
225
  if (kind === "spec") {
226
+ record.manifest = manifest;
185
227
  record.spec = pickAttributes(node.attributes, [
186
228
  "version",
187
229
  "spec_kind",
@@ -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);
@@ -8,12 +8,14 @@ exports.DEFAULT_FRONTMATTER_KEY_ORDER = [
8
8
  "id",
9
9
  "type",
10
10
  "title",
11
+ "checkpoint_kind",
11
12
  "status",
12
13
  "priority",
13
14
  "goal_state",
14
15
  "goal_condition",
15
16
  "scope_refs",
16
17
  "active_node",
18
+ "last_active_node",
17
19
  "required_skills",
18
20
  "required_checks",
19
21
  "max_iterations",
@@ -60,7 +62,6 @@ exports.DEFAULT_FRONTMATTER_KEY_ORDER = [
60
62
  "severity",
61
63
  "proposal_status",
62
64
  "proposal_kind",
63
- "evidence_refs",
64
65
  "archive_kind",
65
66
  "source_path",
66
67
  "stored_path",
@@ -89,6 +90,8 @@ exports.DEFAULT_FRONTMATTER_KEY_ORDER = [
89
90
  "blocked_by",
90
91
  "blocks",
91
92
  "refs",
93
+ "context_refs",
94
+ "evidence_refs",
92
95
  "aliases",
93
96
  "skills",
94
97
  "cases",
@@ -10,6 +10,7 @@ const node_1 = require("./node");
10
10
  const workspace_files_1 = require("./workspace_files");
11
11
  const validate_graph_1 = require("./validate_graph");
12
12
  const template_schema_1 = require("./template_schema");
13
+ const agent_file_types_1 = require("./agent_file_types");
13
14
  function normalizeEdgeTarget(value, ws) {
14
15
  if (value.includes(":")) {
15
16
  return value;
@@ -25,6 +26,8 @@ function normalizeEdges(edges, ws) {
25
26
  relates: edges.relates.map((value) => normalizeEdgeTarget(value, ws)),
26
27
  blocked_by: edges.blocked_by.map((value) => normalizeEdgeTarget(value, ws)),
27
28
  blocks: edges.blocks.map((value) => normalizeEdgeTarget(value, ws)),
29
+ context_refs: (edges.context_refs ?? []).map((value) => normalizeEdgeTarget(value, ws)),
30
+ evidence_refs: (edges.evidence_refs ?? []).map((value) => normalizeEdgeTarget(value, ws)),
28
31
  };
29
32
  }
30
33
  function addReverseEdge(reverse, edgeKey, target, source) {
@@ -46,6 +49,10 @@ function buildIndex(root, config, options = {}) {
46
49
  for (const alias of workspaceAliases) {
47
50
  idsByWorkspace[alias] = new Set();
48
51
  const files = docFilesByAlias[alias];
52
+ const manifestConflicts = (0, agent_file_types_1.collectManifestSiblingConflicts)(files, (dirPath) => path_1.default.relative(root, dirPath).split(path_1.default.sep).join("/") || ".");
53
+ if (manifestConflicts.length > 0 && !tolerant) {
54
+ throw new Error(manifestConflicts[0]);
55
+ }
49
56
  for (const filePath of files) {
50
57
  if (path_1.default.basename(filePath) === "core.md" && path_1.default.basename(path_1.default.dirname(filePath)) === "core") {
51
58
  continue;
@@ -118,6 +125,12 @@ function buildIndex(root, config, options = {}) {
118
125
  for (const target of edges.blocks) {
119
126
  addReverseEdge(reverse_edges, "blocks", target, qid);
120
127
  }
128
+ for (const target of edges.context_refs ?? []) {
129
+ addReverseEdge(reverse_edges, "context_refs", target, qid);
130
+ }
131
+ for (const target of edges.evidence_refs ?? []) {
132
+ addReverseEdge(reverse_edges, "evidence_refs", target, qid);
133
+ }
121
134
  }
122
135
  for (const edgeKey of Object.keys(reverse_edges)) {
123
136
  for (const target of Object.keys(reverse_edges[edgeKey])) {
@@ -37,6 +37,7 @@ const GOAL_ATTRIBUTE_KEYS = [
37
37
  "goal_condition",
38
38
  "scope_refs",
39
39
  "active_node",
40
+ "last_active_node",
40
41
  "required_skills",
41
42
  "required_checks",
42
43
  "max_iterations",
@@ -180,6 +181,13 @@ function validateGoalFrontmatter(type, frontmatter, filePath) {
180
181
  throw formatError(filePath, "active_node must be a local id or qid");
181
182
  }
182
183
  }
184
+ const lastActiveNode = optionalString(frontmatter, "last_active_node", filePath);
185
+ if (lastActiveNode !== undefined) {
186
+ const normalized = requireLowercase(lastActiveNode, "last_active_node", filePath);
187
+ if (!(0, id_1.isPortableIdRef)(normalized)) {
188
+ throw formatError(filePath, "last_active_node must be a local id or qid");
189
+ }
190
+ }
183
191
  const scopeRefs = optionalList(frontmatter, "scope_refs", filePath);
184
192
  for (const [index, value] of scopeRefs.entries()) {
185
193
  if (typeof value !== "string" || value.trim().length === 0) {
@@ -334,7 +342,10 @@ function parseNode(content, filePath, options) {
334
342
  throw formatError(filePath, "supersedes must be a dec-# id");
335
343
  }
336
344
  }
337
- const edges = (0, edges_1.extractEdges)(frontmatter, filePath, { allowPortableRefs: true });
345
+ const edges = (0, edges_1.extractEdges)(frontmatter, filePath, {
346
+ allowPortableRefs: true,
347
+ includeSemanticRefs: exports.WORK_TYPES.has(type),
348
+ });
338
349
  const attributes = {
339
350
  ...extractGoalAttributes(type, frontmatter),
340
351
  ...(0, agent_file_types_1.extractAgentAttributes)(type, frontmatter),
@@ -205,6 +205,8 @@ function writeSqliteIndex(options) {
205
205
  ["relates", node.edges.relates],
206
206
  ["blocked_by", node.edges.blocked_by],
207
207
  ["blocks", node.edges.blocks],
208
+ ["context_refs", node.edges.context_refs ?? []],
209
+ ["evidence_refs", node.edges.evidence_refs ?? []],
208
210
  ]) {
209
211
  for (const target of values) {
210
212
  insertEdge.run(node.qid, kind, target);
@@ -278,6 +278,8 @@ function projectOneSubgraph(root, alias, subgraph) {
278
278
  relates: remapTargets(node.edges.relates, qidMap),
279
279
  blocked_by: remapTargets(node.edges.blocked_by, qidMap),
280
280
  blocks: remapTargets(node.edges.blocks, qidMap),
281
+ context_refs: remapTargets(node.edges.context_refs ?? [], qidMap),
282
+ evidence_refs: remapTargets(node.edges.evidence_refs ?? [], qidMap),
281
283
  },
282
284
  source: {
283
285
  imported: true,
@@ -9,6 +9,9 @@ const fs_1 = __importDefault(require("fs"));
9
9
  const path_1 = __importDefault(require("path"));
10
10
  const frontmatter_1 = require("./frontmatter");
11
11
  const builtin_1 = require("../templates/builtin");
12
+ const TEMPLATE_SCHEMA_ALIASES = {
13
+ spec: "manifest",
14
+ };
12
15
  function listMarkdownFiles(dir) {
13
16
  if (!fs_1.default.existsSync(dir)) {
14
17
  return [];
@@ -46,6 +49,34 @@ function addKeyToSchema(schema, key, kind, filePath) {
46
49
  schema.listKeys.add(key);
47
50
  }
48
51
  }
52
+ function cloneSchema(schema, type) {
53
+ return {
54
+ type,
55
+ allowedKeys: new Set(schema.allowedKeys),
56
+ keyKinds: { ...schema.keyKinds },
57
+ listKeys: new Set(schema.listKeys),
58
+ };
59
+ }
60
+ function loadBundledSchema(type) {
61
+ const bundledPath = (0, builtin_1.requireBundledTemplatePath)(type);
62
+ const content = fs_1.default.readFileSync(bundledPath, "utf8");
63
+ const { frontmatter } = (0, frontmatter_1.parseFrontmatter)(content, bundledPath);
64
+ const typeValue = frontmatter.type;
65
+ if (typeValue !== type) {
66
+ throw new Error(`bundled template fallback type mismatch for ${type}: ${bundledPath}`);
67
+ }
68
+ const schema = {
69
+ type,
70
+ allowedKeys: new Set(),
71
+ keyKinds: {},
72
+ listKeys: new Set(),
73
+ };
74
+ for (const [key, value] of Object.entries(frontmatter)) {
75
+ const kind = getValueKind(value);
76
+ addKeyToSchema(schema, key, kind, bundledPath);
77
+ }
78
+ return schema;
79
+ }
49
80
  function loadTemplateSchemas(root, config, requiredTypes) {
50
81
  return loadTemplateSchemasWithInfo(root, config, requiredTypes).schemas;
51
82
  }
@@ -84,24 +115,13 @@ function loadTemplateSchemasWithInfo(root, config, requiredTypes) {
84
115
  if (requiredTypes) {
85
116
  const required = Array.from(requiredTypes, (value) => value.toLowerCase());
86
117
  for (const missingType of required.filter((value) => !schemas[value])) {
87
- const bundledPath = (0, builtin_1.requireBundledTemplatePath)(missingType);
88
- const content = fs_1.default.readFileSync(bundledPath, "utf8");
89
- const { frontmatter } = (0, frontmatter_1.parseFrontmatter)(content, bundledPath);
90
- const typeValue = frontmatter.type;
91
- if (typeValue !== missingType) {
92
- throw new Error(`bundled template fallback type mismatch for ${missingType}: ${bundledPath}`);
93
- }
94
- const schema = {
95
- type: missingType,
96
- allowedKeys: new Set(),
97
- keyKinds: {},
98
- listKeys: new Set(),
99
- };
100
- schemas[missingType] = schema;
101
- for (const [key, value] of Object.entries(frontmatter)) {
102
- const kind = getValueKind(value);
103
- addKeyToSchema(schema, key, kind, bundledPath);
118
+ const aliasType = TEMPLATE_SCHEMA_ALIASES[missingType];
119
+ if (aliasType) {
120
+ const aliasSchema = schemas[aliasType] ?? loadBundledSchema(aliasType);
121
+ schemas[missingType] = cloneSchema(aliasSchema, missingType);
122
+ continue;
104
123
  }
124
+ schemas[missingType] = loadBundledSchema(missingType);
105
125
  fallbackTypes.push(missingType);
106
126
  }
107
127
  }
@@ -2,7 +2,9 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.collectGraphErrors = collectGraphErrors;
4
4
  exports.validateGraph = validateGraph;
5
+ const agent_file_types_1 = require("./agent_file_types");
5
6
  const refs_1 = require("../util/refs");
7
+ const qid_1 = require("../util/qid");
6
8
  const goal_scope_1 = require("./goal_scope");
7
9
  function pushError(errors, message) {
8
10
  if (errors) {
@@ -19,6 +21,8 @@ function validateEdgeTargets(index, allowMissing, knownSkillSlugs, externalWorks
19
21
  ["relates", edges.relates],
20
22
  ["blocked_by", edges.blocked_by],
21
23
  ["blocks", edges.blocks],
24
+ ["context_refs", edges.context_refs ?? []],
25
+ ["evidence_refs", edges.evidence_refs ?? []],
22
26
  ];
23
27
  if (edges.epic) {
24
28
  edgeLists.push(["epic", [edges.epic]]);
@@ -34,6 +38,9 @@ function validateEdgeTargets(index, allowMissing, knownSkillSlugs, externalWorks
34
38
  }
35
39
  for (const [edgeKey, values] of edgeLists) {
36
40
  for (const value of values) {
41
+ if ((edgeKey === "context_refs" || edgeKey === "evidence_refs") && (0, refs_1.isUriRef)(value)) {
42
+ continue;
43
+ }
37
44
  const targetNode = nodes[value];
38
45
  if (targetNode &&
39
46
  ["epic", "parent", "prev", "next"].includes(edgeKey) &&
@@ -142,7 +149,7 @@ function validateAgentWorkflowSpecWorkContracts(index, allowMissing, errors) {
142
149
  });
143
150
  }
144
151
  for (const [qid, node] of Object.entries(index.nodes)) {
145
- if (node.type !== "spec") {
152
+ if (!(0, agent_file_types_1.isManifestSemanticType)(node.type)) {
146
153
  continue;
147
154
  }
148
155
  const workContracts = node.attributes.work_contracts;
@@ -228,7 +235,7 @@ function validateAgentWorkflowReceiptWorkOrderRefs(index, allowMissing, external
228
235
  function buildSpecRolesByWorkspace(index) {
229
236
  const specRolesByWorkspace = {};
230
237
  for (const node of Object.values(index.nodes)) {
231
- if (node.type !== "spec") {
238
+ if (!(0, agent_file_types_1.isManifestSemanticType)(node.type)) {
232
239
  continue;
233
240
  }
234
241
  if (!specRolesByWorkspace[node.ws]) {
@@ -242,8 +249,12 @@ function buildSpecRolesByWorkspace(index) {
242
249
  return specRolesByWorkspace;
243
250
  }
244
251
  function resolveSpecRole(index, ws, value) {
245
- const node = resolveTypedNodeRef(index, ws, value, "spec");
246
- if (!node) {
252
+ const resolved = (0, qid_1.resolveQid)(index, value, ws);
253
+ if (resolved.status !== "ok") {
254
+ return undefined;
255
+ }
256
+ const node = index.nodes[resolved.qid];
257
+ if (!node || !(0, agent_file_types_1.isManifestSemanticType)(node.type)) {
247
258
  return undefined;
248
259
  }
249
260
  return {
@@ -254,7 +265,7 @@ function resolveSpecRole(index, ws, value) {
254
265
  function validateAgentWorkflowSubagentRefs(index, allowMissing, externalWorkspaces, errors) {
255
266
  const specRolesByWorkspace = buildSpecRolesByWorkspace(index);
256
267
  for (const [qid, node] of Object.entries(index.nodes)) {
257
- if (node.type !== "spec" && node.type !== "work") {
268
+ if (!(0, agent_file_types_1.isManifestSemanticType)(node.type) && node.type !== "work") {
258
269
  continue;
259
270
  }
260
271
  const subagentRefs = node.attributes.subagent_refs;
@@ -88,6 +88,12 @@ function collectNodeStringReferences(node) {
88
88
  for (const [index, value] of node.edges.blocks.entries()) {
89
89
  values.push({ field: `blocks[${index}]`, value });
90
90
  }
91
+ for (const [index, value] of (node.edges.context_refs ?? []).entries()) {
92
+ values.push({ field: `context_refs[${index}]`, value });
93
+ }
94
+ for (const [index, value] of (node.edges.evidence_refs ?? []).entries()) {
95
+ values.push({ field: `evidence_refs[${index}]`, value });
96
+ }
91
97
  for (const [index, value] of node.links.entries()) {
92
98
  values.push({ field: `links[${index}]`, value });
93
99
  }