mdkg 0.1.3 → 0.1.5

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 (49) hide show
  1. package/CHANGELOG.md +45 -1
  2. package/README.md +34 -10
  3. package/dist/cli.js +354 -87
  4. package/dist/commands/bundle.js +7 -7
  5. package/dist/commands/capability.js +118 -4
  6. package/dist/commands/doctor.js +15 -15
  7. package/dist/commands/goal.js +548 -0
  8. package/dist/commands/index.js +1 -1
  9. package/dist/commands/init.js +1 -0
  10. package/dist/commands/list.js +1 -1
  11. package/dist/commands/new.js +7 -1
  12. package/dist/commands/next.js +1 -1
  13. package/dist/commands/node_card.js +1 -1
  14. package/dist/commands/pack.js +1 -1
  15. package/dist/commands/search.js +1 -1
  16. package/dist/commands/show.js +1 -1
  17. package/dist/commands/subgraph.js +312 -0
  18. package/dist/commands/task.js +1 -1
  19. package/dist/commands/upgrade.js +54 -7
  20. package/dist/commands/validate.js +39 -7
  21. package/dist/commands/work.js +1 -1
  22. package/dist/core/config.js +95 -39
  23. package/dist/graph/frontmatter.js +8 -0
  24. package/dist/graph/goal_scope.js +127 -0
  25. package/dist/graph/index_cache.js +12 -12
  26. package/dist/graph/indexer.js +1 -1
  27. package/dist/graph/node.js +80 -1
  28. package/dist/graph/reindex.js +6 -6
  29. package/dist/graph/sqlite_index.js +6 -6
  30. package/dist/graph/{bundle_imports.js → subgraphs.js} +214 -140
  31. package/dist/graph/validate_graph.js +41 -0
  32. package/dist/graph/visibility.js +3 -3
  33. package/dist/init/AGENT_START.md +17 -1
  34. package/dist/init/CLI_COMMAND_MATRIX.md +43 -7
  35. package/dist/init/README.md +9 -8
  36. package/dist/init/config.json +1 -1
  37. package/dist/init/core/rule-3-cli-contract.md +56 -23
  38. package/dist/init/core/rule-4-repo-safety-and-ignores.md +7 -2
  39. package/dist/init/core/rule-6-templates-and-schemas.md +10 -1
  40. package/dist/init/init-manifest.json +20 -10
  41. package/dist/init/skills/default/pursue-mdkg-goal/SKILL.md +68 -0
  42. package/dist/init/skills/default/select-work-and-ground-context/SKILL.md +9 -7
  43. package/dist/init/skills/default/verify-close-and-checkpoint/SKILL.md +11 -10
  44. package/dist/init/templates/default/goal.md +91 -0
  45. package/dist/pack/order.js +2 -1
  46. package/dist/pack/pack.js +17 -0
  47. package/dist/util/argparse.js +2 -0
  48. package/package.json +8 -6
  49. package/dist/commands/bundle_import.js +0 -255
@@ -23,9 +23,11 @@ const NEXT_WORK_STRATEGIES = new Set(["chain_then_priority"]);
23
23
  const WORKSPACE_VISIBILITY_VALUES = new Set(["private", "internal", "public"]);
24
24
  const BUNDLE_PROFILE_VALUES = new Set(["private", "public"]);
25
25
  const INDEX_BACKEND_VALUES = new Set(["json", "sqlite"]);
26
+ const SUBGRAPH_PERMISSION_VALUES = new Set(["read", "request", "propose", "mutate"]);
26
27
  const DEFAULT_ARCHIVE_LARGE_CACHE_WARNING_BYTES = 26214400;
27
28
  const DEFAULT_SQLITE_COMMIT_WARNING_BYTES = 52428800;
28
29
  const DEFAULT_LOCK_TIMEOUT_MS = 10000;
30
+ const DEFAULT_SUBGRAPH_MAX_STALE_SECONDS = 3600;
29
31
  function isObject(value) {
30
32
  return typeof value === "object" && value !== null && !Array.isArray(value);
31
33
  }
@@ -172,16 +174,16 @@ function validateWorkspaceAlias(alias, errors) {
172
174
  errors.push(`workspaces.${alias} alias must be lowercase and use [a-z0-9_]`);
173
175
  }
174
176
  }
175
- function validateBundleImportAlias(alias, workspaces, errors) {
177
+ function validateSubgraphAlias(alias, workspaces, errors) {
176
178
  if (alias === "all") {
177
- errors.push("bundle_imports.all alias is reserved");
179
+ errors.push("subgraphs.all alias is reserved");
178
180
  return;
179
181
  }
180
182
  if (alias !== alias.toLowerCase() || !WORKSPACE_ALIAS_RE.test(alias)) {
181
- errors.push(`bundle_imports.${alias} alias must be lowercase and use [a-z0-9_]`);
183
+ errors.push(`subgraphs.${alias} alias must be lowercase and use [a-z0-9_]`);
182
184
  }
183
185
  if (workspaces[alias]) {
184
- errors.push(`bundle_imports.${alias} must not collide with workspaces.${alias}`);
186
+ errors.push(`subgraphs.${alias} must not collide with workspaces.${alias}`);
185
187
  }
186
188
  }
187
189
  function requireContainedPath(value, path, errors) {
@@ -216,9 +218,15 @@ function validateConfigSchema(raw) {
216
218
  const bundlesRaw = raw.bundles === undefined
217
219
  ? { output_dir: ".mdkg/bundles", default_profile: "private" }
218
220
  : requireObject(raw.bundles, "bundles", errors);
219
- const bundleImportsRaw = raw.bundle_imports === undefined
220
- ? {}
221
- : requireObject(raw.bundle_imports, "bundle_imports", errors);
221
+ if (raw.bundle_imports !== undefined && raw.subgraphs !== undefined) {
222
+ errors.push("config must not define both subgraphs and legacy bundle_imports");
223
+ }
224
+ const subgraphsFromLegacyBundleImports = raw.subgraphs === undefined && raw.bundle_imports !== undefined;
225
+ const subgraphsRaw = raw.subgraphs === undefined
226
+ ? raw.bundle_imports === undefined
227
+ ? {}
228
+ : requireObject(raw.bundle_imports, "bundle_imports", errors)
229
+ : requireObject(raw.subgraphs, "subgraphs", errors);
222
230
  const packRaw = requireObject(raw.pack, "pack", errors);
223
231
  const templatesRaw = requireObject(raw.templates, "templates", errors);
224
232
  const workRaw = requireObject(raw.work, "work", errors);
@@ -389,48 +397,96 @@ function validateConfigSchema(raw) {
389
397
  errors.push('workspaces.root.mdkg_dir must be ".mdkg"');
390
398
  }
391
399
  }
392
- const bundle_imports = {};
393
- if (bundleImportsRaw) {
394
- for (const [alias, entry] of Object.entries(bundleImportsRaw)) {
395
- validateBundleImportAlias(alias, workspaces, errors);
396
- const rawImport = requireObject(entry, `bundle_imports.${alias}`, errors);
397
- if (!rawImport) {
400
+ const subgraphs = {};
401
+ if (subgraphsRaw) {
402
+ for (const [alias, entry] of Object.entries(subgraphsRaw)) {
403
+ validateSubgraphAlias(alias, workspaces, errors);
404
+ const basePath = subgraphsFromLegacyBundleImports ? `bundle_imports.${alias}` : `subgraphs.${alias}`;
405
+ const rawSubgraph = requireObject(entry, basePath, errors);
406
+ if (!rawSubgraph) {
398
407
  continue;
399
408
  }
400
- const importPath = requireContainedPath(rawImport.path, `bundle_imports.${alias}.path`, errors);
401
- const enabled = rawImport.enabled === undefined
409
+ const enabled = rawSubgraph.enabled === undefined
402
410
  ? true
403
- : requireBoolean(rawImport.enabled, `bundle_imports.${alias}.enabled`, errors);
404
- const visibility = rawImport.visibility === undefined
411
+ : requireBoolean(rawSubgraph.enabled, `${basePath}.enabled`, errors);
412
+ const visibility = rawSubgraph.visibility === undefined
405
413
  ? "private"
406
- : requireStringInSet(rawImport.visibility, `bundle_imports.${alias}.visibility`, WORKSPACE_VISIBILITY_VALUES, errors);
407
- const expectedProfile = rawImport.expected_profile === undefined
408
- ? "private"
409
- : requireStringInSet(rawImport.expected_profile, `bundle_imports.${alias}.expected_profile`, BUNDLE_PROFILE_VALUES, errors);
410
- const sourcePath = rawImport.source_path === undefined
411
- ? undefined
412
- : requireContainedPath(rawImport.source_path, `bundle_imports.${alias}.source_path`, errors);
413
- const sourceRepo = rawImport.source_repo === undefined
414
+ : requireStringInSet(rawSubgraph.visibility, `${basePath}.visibility`, WORKSPACE_VISIBILITY_VALUES, errors);
415
+ const permissions = rawSubgraph.permissions === undefined || subgraphsFromLegacyBundleImports
416
+ ? ["read"]
417
+ : requireKnownLowercaseUniqueStringArray(rawSubgraph.permissions, `${basePath}.permissions`, SUBGRAPH_PERMISSION_VALUES, errors);
418
+ const maxStaleSeconds = rawSubgraph.max_stale_seconds === undefined
419
+ ? DEFAULT_SUBGRAPH_MAX_STALE_SECONDS
420
+ : requirePositiveInteger(rawSubgraph.max_stale_seconds, `${basePath}.max_stale_seconds`, errors);
421
+ const sourcePath = rawSubgraph.source_path === undefined
414
422
  ? undefined
415
- : requireString(rawImport.source_repo, `bundle_imports.${alias}.source_repo`, errors);
416
- const maxStaleSeconds = rawImport.max_stale_seconds === undefined
423
+ : requireContainedPath(rawSubgraph.source_path, `${basePath}.source_path`, errors);
424
+ const sourceRepo = rawSubgraph.source_repo === undefined
417
425
  ? undefined
418
- : requirePositiveInteger(rawImport.max_stale_seconds, `bundle_imports.${alias}.max_stale_seconds`, errors);
419
- if (visibility !== undefined &&
420
- expectedProfile !== undefined &&
421
- visibility !== "private" &&
422
- expectedProfile !== "public") {
423
- errors.push(`bundle_imports.${alias}.expected_profile must be public when visibility is ${visibility}`);
426
+ : requireString(rawSubgraph.source_repo, `${basePath}.source_repo`, errors);
427
+ const rawSources = subgraphsFromLegacyBundleImports
428
+ ? [{ path: rawSubgraph.path, enabled: true, expected_profile: rawSubgraph.expected_profile }]
429
+ : rawSubgraph.sources;
430
+ if (!Array.isArray(rawSources)) {
431
+ errors.push(`${basePath}.sources must be an array`);
432
+ continue;
433
+ }
434
+ if (rawSources.length === 0) {
435
+ errors.push(`${basePath}.sources must not be empty`);
436
+ continue;
437
+ }
438
+ const sources = [];
439
+ const labels = new Set();
440
+ for (let i = 0; i < rawSources.length; i += 1) {
441
+ const sourceBasePath = `${basePath}.sources[${i}]`;
442
+ const rawSource = requireObject(rawSources[i], sourceBasePath, errors);
443
+ if (!rawSource) {
444
+ continue;
445
+ }
446
+ const sourceBundlePath = requireContainedPath(rawSource.path, `${sourceBasePath}.path`, errors);
447
+ const sourceEnabled = rawSource.enabled === undefined
448
+ ? true
449
+ : requireBoolean(rawSource.enabled, `${sourceBasePath}.enabled`, errors);
450
+ const expectedProfile = rawSource.expected_profile === undefined
451
+ ? "private"
452
+ : requireStringInSet(rawSource.expected_profile, `${sourceBasePath}.expected_profile`, BUNDLE_PROFILE_VALUES, errors);
453
+ const label = rawSource.label === undefined
454
+ ? undefined
455
+ : requireString(rawSource.label, `${sourceBasePath}.label`, errors);
456
+ if (label !== undefined) {
457
+ if (label.trim().length === 0) {
458
+ errors.push(`${sourceBasePath}.label must not be empty`);
459
+ }
460
+ if (labels.has(label)) {
461
+ errors.push(`${basePath}.sources label must be unique: ${label}`);
462
+ }
463
+ labels.add(label);
464
+ }
465
+ if (visibility !== undefined &&
466
+ expectedProfile !== undefined &&
467
+ sourceEnabled !== false &&
468
+ visibility !== "private" &&
469
+ expectedProfile !== "public") {
470
+ errors.push(`${sourceBasePath}.expected_profile must be public when ${basePath}.visibility is ${visibility}`);
471
+ }
472
+ if (sourceBundlePath && sourceEnabled !== undefined && expectedProfile) {
473
+ sources.push({
474
+ path: sourceBundlePath,
475
+ enabled: sourceEnabled,
476
+ expected_profile: expectedProfile,
477
+ ...(label ? { label } : {}),
478
+ });
479
+ }
424
480
  }
425
- if (importPath && enabled !== undefined && visibility && expectedProfile) {
426
- bundle_imports[alias] = {
427
- path: importPath,
481
+ if (enabled !== undefined && visibility && permissions && maxStaleSeconds !== undefined && sources.length > 0) {
482
+ subgraphs[alias] = {
428
483
  enabled,
429
484
  visibility: visibility,
430
- expected_profile: expectedProfile,
485
+ permissions,
486
+ max_stale_seconds: maxStaleSeconds,
431
487
  ...(sourcePath ? { source_path: sourcePath } : {}),
432
488
  ...(sourceRepo ? { source_repo: sourceRepo } : {}),
433
- ...(maxStaleSeconds !== undefined ? { max_stale_seconds: maxStaleSeconds } : {}),
489
+ sources,
434
490
  };
435
491
  }
436
492
  }
@@ -446,7 +502,7 @@ function validateConfigSchema(raw) {
446
502
  index: index,
447
503
  capabilities: capabilities,
448
504
  bundles: bundles,
449
- bundle_imports,
505
+ subgraphs,
450
506
  pack: pack,
451
507
  templates: templates,
452
508
  work: work,
@@ -10,6 +10,14 @@ exports.DEFAULT_FRONTMATTER_KEY_ORDER = [
10
10
  "title",
11
11
  "status",
12
12
  "priority",
13
+ "goal_state",
14
+ "goal_condition",
15
+ "scope_refs",
16
+ "active_node",
17
+ "required_skills",
18
+ "required_checks",
19
+ "max_iterations",
20
+ "blocked_after_attempts",
13
21
  "epic",
14
22
  "parent",
15
23
  "prev",
@@ -0,0 +1,127 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GOAL_SCOPE_ALLOWED_TYPES = exports.GOAL_SCOPE_ACTIONABLE_TYPES = exports.GOAL_SCOPE_CONTAINER_TYPES = void 0;
4
+ exports.goalScopeRefs = goalScopeRefs;
5
+ exports.collectGoalScope = collectGoalScope;
6
+ const qid_1 = require("../util/qid");
7
+ exports.GOAL_SCOPE_CONTAINER_TYPES = new Set(["epic", "feat"]);
8
+ exports.GOAL_SCOPE_ACTIONABLE_TYPES = new Set(["feat", "task", "bug", "test"]);
9
+ exports.GOAL_SCOPE_ALLOWED_TYPES = new Set([
10
+ ...exports.GOAL_SCOPE_CONTAINER_TYPES,
11
+ ...exports.GOAL_SCOPE_ACTIONABLE_TYPES,
12
+ ]);
13
+ function toStringList(value) {
14
+ if (!Array.isArray(value)) {
15
+ return [];
16
+ }
17
+ return value.filter((item) => typeof item === "string");
18
+ }
19
+ function goalScopeRefs(goal) {
20
+ return toStringList(goal.attributes.scope_refs);
21
+ }
22
+ function resolveGoalScopeRef(index, value, ws) {
23
+ const resolved = (0, qid_1.resolveQid)(index, value, ws);
24
+ return resolved.status === "ok" ? resolved.qid : undefined;
25
+ }
26
+ function addIfPresent(values, value) {
27
+ if (value) {
28
+ values.add(value);
29
+ }
30
+ }
31
+ function collectDirectCompatibilityRefs(index, goal) {
32
+ const qids = new Set();
33
+ const edges = goal.edges;
34
+ addIfPresent(qids, edges.next);
35
+ addIfPresent(qids, edges.prev);
36
+ addIfPresent(qids, edges.parent);
37
+ addIfPresent(qids, edges.epic);
38
+ for (const value of edges.relates) {
39
+ qids.add(value);
40
+ }
41
+ for (const value of edges.blocks) {
42
+ qids.add(value);
43
+ }
44
+ for (const value of edges.blocked_by) {
45
+ qids.add(value);
46
+ }
47
+ for (const node of Object.values(index.nodes)) {
48
+ if (node.edges.parent === goal.qid ||
49
+ node.edges.epic === goal.qid ||
50
+ node.edges.prev === goal.qid ||
51
+ node.edges.next === goal.qid ||
52
+ node.edges.relates.includes(goal.qid) ||
53
+ node.edges.blocked_by.includes(goal.qid) ||
54
+ node.edges.blocks.includes(goal.qid)) {
55
+ qids.add(node.qid);
56
+ }
57
+ }
58
+ return qids;
59
+ }
60
+ function descendantQids(index, qid) {
61
+ const byEpic = index.reverse_edges.epic?.[qid] ?? [];
62
+ const byParent = index.reverse_edges.parent?.[qid] ?? [];
63
+ return [...new Set([...byEpic, ...byParent])].sort();
64
+ }
65
+ function collectGoalScope(index, goal, options = {}) {
66
+ const includeCompatibilityRefs = options.includeCompatibilityRefs ?? true;
67
+ const rootQids = [];
68
+ const qids = new Set();
69
+ const actionableQids = new Set();
70
+ const missingRefs = [];
71
+ const invalidRefs = [];
72
+ const queued = new Set();
73
+ const queue = [];
74
+ function enqueue(qid) {
75
+ if (queued.has(qid)) {
76
+ return;
77
+ }
78
+ queued.add(qid);
79
+ queue.push(qid);
80
+ }
81
+ for (const ref of goalScopeRefs(goal)) {
82
+ const resolved = resolveGoalScopeRef(index, ref, goal.ws);
83
+ if (!resolved) {
84
+ missingRefs.push(ref);
85
+ continue;
86
+ }
87
+ rootQids.push(resolved);
88
+ enqueue(resolved);
89
+ }
90
+ if (includeCompatibilityRefs) {
91
+ for (const qid of collectDirectCompatibilityRefs(index, goal)) {
92
+ rootQids.push(qid);
93
+ enqueue(qid);
94
+ }
95
+ }
96
+ while (queue.length > 0) {
97
+ const qid = queue.shift();
98
+ if (!qid) {
99
+ break;
100
+ }
101
+ const node = index.nodes[qid];
102
+ if (!node) {
103
+ continue;
104
+ }
105
+ if (!exports.GOAL_SCOPE_ALLOWED_TYPES.has(node.type)) {
106
+ invalidRefs.push(qid);
107
+ continue;
108
+ }
109
+ qids.add(qid);
110
+ if (exports.GOAL_SCOPE_ACTIONABLE_TYPES.has(node.type)) {
111
+ actionableQids.add(qid);
112
+ }
113
+ if (exports.GOAL_SCOPE_CONTAINER_TYPES.has(node.type)) {
114
+ for (const childQid of descendantQids(index, qid)) {
115
+ enqueue(childQid);
116
+ }
117
+ }
118
+ }
119
+ rootQids.sort();
120
+ return {
121
+ rootQids: [...new Set(rootQids)],
122
+ qids,
123
+ actionableQids,
124
+ missingRefs,
125
+ invalidRefs: [...new Set(invalidRefs)].sort(),
126
+ };
127
+ }
@@ -11,7 +11,7 @@ const sort_1 = require("../util/sort");
11
11
  const atomic_1 = require("../util/atomic");
12
12
  const indexer_1 = require("./indexer");
13
13
  const staleness_1 = require("./staleness");
14
- const bundle_imports_1 = require("./bundle_imports");
14
+ const subgraphs_1 = require("./subgraphs");
15
15
  function readIndex(indexPath) {
16
16
  try {
17
17
  const raw = fs_1.default.readFileSync(indexPath, "utf8");
@@ -32,36 +32,36 @@ function loadIndex(options) {
32
32
  const tolerant = options.tolerant ?? options.config.index.tolerant;
33
33
  const includeImports = options.includeImports ?? true;
34
34
  const indexPath = path_1.default.resolve(options.root, options.config.index.global_index_path);
35
- const withImports = (index, rebuilt, stale) => {
36
- if (!includeImports || Object.keys(options.config.bundle_imports).length === 0) {
35
+ const withSubgraphs = (index, rebuilt, stale) => {
36
+ if (!includeImports || Object.keys(options.config.subgraphs).length === 0) {
37
37
  return { index, rebuilt, stale, warnings: [] };
38
38
  }
39
- const imports = (0, bundle_imports_1.buildBundleImportsIndex)(options.root, options.config);
39
+ const subgraphs = (0, subgraphs_1.buildSubgraphsIndex)(options.root, options.config);
40
40
  if (allowReindex) {
41
- (0, bundle_imports_1.writeBundleImportsIndex)((0, bundle_imports_1.resolveBundleImportsIndexPath)(options.root), imports.index);
41
+ (0, subgraphs_1.writeSubgraphsIndex)((0, subgraphs_1.resolveSubgraphsIndexPath)(options.root), subgraphs.index);
42
42
  }
43
43
  return {
44
- index: (0, bundle_imports_1.mergeBundleImportsIntoIndex)(index, imports),
44
+ index: (0, subgraphs_1.mergeSubgraphsIntoIndex)(index, subgraphs),
45
45
  rebuilt,
46
- stale: stale || (0, bundle_imports_1.isBundleImportsIndexStale)(options.root, options.config),
47
- warnings: (0, bundle_imports_1.importWarnings)(imports),
46
+ stale: stale || (0, subgraphs_1.isSubgraphsIndexStale)(options.root, options.config),
47
+ warnings: (0, subgraphs_1.subgraphWarnings)(subgraphs),
48
48
  };
49
49
  };
50
50
  if (!useCache) {
51
51
  const index = (0, indexer_1.buildIndex)(options.root, options.config, { tolerant });
52
- return withImports(index, true, false);
52
+ return withSubgraphs(index, true, false);
53
53
  }
54
54
  const stale = (0, staleness_1.isIndexStale)(options.root, options.config);
55
55
  if (fs_1.default.existsSync(indexPath) && !stale) {
56
- return withImports(readIndex(indexPath), false, false);
56
+ return withSubgraphs(readIndex(indexPath), false, false);
57
57
  }
58
58
  if (allowReindex) {
59
59
  const index = (0, indexer_1.buildIndex)(options.root, options.config, { tolerant });
60
60
  writeIndex(indexPath, index);
61
- return withImports(index, true, stale);
61
+ return withSubgraphs(index, true, stale);
62
62
  }
63
63
  if (fs_1.default.existsSync(indexPath)) {
64
- return withImports(readIndex(indexPath), false, true);
64
+ return withSubgraphs(readIndex(indexPath), false, true);
65
65
  }
66
66
  throw new Error("index missing and auto-reindex is disabled");
67
67
  }
@@ -143,7 +143,7 @@ function buildIndex(root, config, options = {}) {
143
143
  };
144
144
  (0, validate_graph_1.validateGraph)(index, {
145
145
  allowMissing: tolerant,
146
- externalWorkspaces: new Set(Object.keys(config.bundle_imports ?? {})),
146
+ externalWorkspaces: new Set(Object.keys(config.subgraphs ?? {})),
147
147
  });
148
148
  const latestCheckpointByWorkspace = {};
149
149
  for (const alias of workspaceAliases) {
@@ -10,7 +10,7 @@ const id_1 = require("../util/id");
10
10
  const refs_1 = require("../util/refs");
11
11
  const DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
12
12
  const DEC_ID_RE = /^dec-[0-9]+$/;
13
- exports.WORK_TYPES = new Set(["epic", "feat", "task", "bug", "checkpoint", "test"]);
13
+ exports.WORK_TYPES = new Set(["goal", "epic", "feat", "task", "bug", "checkpoint", "test"]);
14
14
  exports.DEC_TYPES = new Set(["dec"]);
15
15
  exports.ALLOWED_TYPES = new Set([
16
16
  "rule",
@@ -18,6 +18,7 @@ exports.ALLOWED_TYPES = new Set([
18
18
  "edd",
19
19
  "dec",
20
20
  "prop",
21
+ "goal",
21
22
  "epic",
22
23
  "feat",
23
24
  "task",
@@ -28,7 +29,18 @@ exports.ALLOWED_TYPES = new Set([
28
29
  ...agent_file_types_1.AGENT_FILE_TYPES,
29
30
  ]);
30
31
  const DEC_STATUS = new Set(["proposed", "accepted", "rejected", "superseded"]);
32
+ const GOAL_STATE = new Set(["active", "paused", "achieved", "blocked", "budget_limited"]);
31
33
  const SKILL_SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
34
+ const GOAL_ATTRIBUTE_KEYS = [
35
+ "goal_state",
36
+ "goal_condition",
37
+ "scope_refs",
38
+ "active_node",
39
+ "required_skills",
40
+ "required_checks",
41
+ "max_iterations",
42
+ "blocked_after_attempts",
43
+ ];
32
44
  function formatError(filePath, message) {
33
45
  return new Error(`${filePath}: ${message}`);
34
46
  }
@@ -142,6 +154,71 @@ function normalizeSkillList(values, filePath) {
142
154
  return normalized;
143
155
  });
144
156
  }
157
+ function parsePositiveIntegerString(value, key, filePath) {
158
+ if (!/^[1-9][0-9]*$/.test(value)) {
159
+ throw formatError(filePath, `${key} must be a positive integer`);
160
+ }
161
+ return value;
162
+ }
163
+ function validateGoalFrontmatter(type, frontmatter, filePath) {
164
+ if (type !== "goal") {
165
+ return;
166
+ }
167
+ const goalState = requireLowercase(expectString(frontmatter, "goal_state", filePath), "goal_state", filePath);
168
+ if (!GOAL_STATE.has(goalState)) {
169
+ throw formatError(filePath, `goal_state must be one of ${Array.from(GOAL_STATE).join(", ")}`);
170
+ }
171
+ const goalCondition = expectString(frontmatter, "goal_condition", filePath);
172
+ if (goalCondition.length > 4000) {
173
+ throw formatError(filePath, "goal_condition must be 4000 characters or fewer");
174
+ }
175
+ const activeNode = optionalString(frontmatter, "active_node", filePath);
176
+ if (activeNode !== undefined) {
177
+ const normalized = requireLowercase(activeNode, "active_node", filePath);
178
+ if (!(0, id_1.isPortableIdRef)(normalized)) {
179
+ throw formatError(filePath, "active_node must be a local id or qid");
180
+ }
181
+ }
182
+ const scopeRefs = optionalList(frontmatter, "scope_refs", filePath);
183
+ for (const [index, value] of scopeRefs.entries()) {
184
+ if (typeof value !== "string" || value.trim().length === 0) {
185
+ throw formatError(filePath, `scope_refs[${index}] must not be empty`);
186
+ }
187
+ const normalized = requireLowercase(value, `scope_refs[${index}]`, filePath);
188
+ if (!(0, id_1.isPortableIdRef)(normalized)) {
189
+ throw formatError(filePath, `scope_refs[${index}] must be a local id or qid`);
190
+ }
191
+ }
192
+ const requiredSkills = optionalList(frontmatter, "required_skills", filePath);
193
+ normalizeSkillList(requiredSkills, filePath);
194
+ const requiredChecks = optionalList(frontmatter, "required_checks", filePath);
195
+ for (const [index, value] of requiredChecks.entries()) {
196
+ if (typeof value !== "string" || value.trim().length === 0) {
197
+ throw formatError(filePath, `required_checks[${index}] must not be empty`);
198
+ }
199
+ }
200
+ const maxIterations = optionalString(frontmatter, "max_iterations", filePath);
201
+ if (maxIterations !== undefined) {
202
+ parsePositiveIntegerString(maxIterations, "max_iterations", filePath);
203
+ }
204
+ const blockedAfterAttempts = optionalString(frontmatter, "blocked_after_attempts", filePath);
205
+ if (blockedAfterAttempts !== undefined) {
206
+ parsePositiveIntegerString(blockedAfterAttempts, "blocked_after_attempts", filePath);
207
+ }
208
+ }
209
+ function extractGoalAttributes(type, frontmatter) {
210
+ if (type !== "goal") {
211
+ return {};
212
+ }
213
+ const attributes = {};
214
+ for (const key of GOAL_ATTRIBUTE_KEYS) {
215
+ const value = frontmatter[key];
216
+ if (value !== undefined) {
217
+ attributes[key] = value;
218
+ }
219
+ }
220
+ return attributes;
221
+ }
145
222
  function requireTemplateSchema(type, templateSchemas, filePath) {
146
223
  const schema = templateSchemas[type];
147
224
  if (!schema) {
@@ -185,6 +262,7 @@ function parseNode(content, filePath, options) {
185
262
  validateTemplateKeys(frontmatter, schema, filePath);
186
263
  (0, agent_file_types_1.validateAgentFrontmatter)(type, frontmatter, filePath);
187
264
  (0, archive_file_1.validateArchiveFrontmatter)(type, frontmatter, filePath);
265
+ validateGoalFrontmatter(type, frontmatter, filePath);
188
266
  const idValue = requireLowercase(expectString(frontmatter, "id", filePath), "id", filePath);
189
267
  const id = isPortableType
190
268
  ? requirePortableIdFormat(idValue, "id", filePath)
@@ -250,6 +328,7 @@ function parseNode(content, filePath, options) {
250
328
  }
251
329
  const edges = (0, edges_1.extractEdges)(frontmatter, filePath, { allowPortableRefs: isPortableType });
252
330
  const attributes = {
331
+ ...extractGoalAttributes(type, frontmatter),
253
332
  ...(0, agent_file_types_1.extractAgentAttributes)(type, frontmatter),
254
333
  ...(0, archive_file_1.extractArchiveAttributes)(type, frontmatter),
255
334
  };
@@ -7,7 +7,7 @@ exports.writeDerivedIndexes = writeDerivedIndexes;
7
7
  const path_1 = __importDefault(require("path"));
8
8
  const capabilities_indexer_1 = require("./capabilities_indexer");
9
9
  const capabilities_index_cache_1 = require("./capabilities_index_cache");
10
- const bundle_imports_1 = require("./bundle_imports");
10
+ const subgraphs_1 = require("./subgraphs");
11
11
  const indexer_1 = require("./indexer");
12
12
  const index_cache_1 = require("./index_cache");
13
13
  const skills_index_cache_1 = require("./skills_index_cache");
@@ -17,20 +17,20 @@ function writeDerivedIndexes(root, config, nodeIndex, options) {
17
17
  const nextNodeIndex = nodeIndex ?? (0, indexer_1.buildIndex)(root, config, { tolerant: options?.tolerant ?? config.index.tolerant });
18
18
  const skillsIndex = (0, skills_indexer_1.buildSkillsIndex)(root, config);
19
19
  const capabilitiesIndex = (0, capabilities_indexer_1.buildCapabilitiesIndex)(root, config, nextNodeIndex);
20
- const importsIndex = (0, bundle_imports_1.buildBundleImportsIndex)(root, config);
20
+ const subgraphsIndex = (0, subgraphs_1.buildSubgraphsIndex)(root, config);
21
21
  const nodesOutputPath = path_1.default.resolve(root, config.index.global_index_path);
22
22
  const skillsOutputPath = (0, skills_indexer_1.resolveSkillsIndexPath)(root);
23
23
  const capabilitiesOutputPath = (0, capabilities_indexer_1.resolveCapabilitiesIndexPath)(root, config);
24
- const importsOutputPath = (0, bundle_imports_1.resolveBundleImportsIndexPath)(root);
24
+ const subgraphsOutputPath = (0, subgraphs_1.resolveSubgraphsIndexPath)(root);
25
25
  (0, index_cache_1.writeIndex)(nodesOutputPath, nextNodeIndex);
26
26
  (0, skills_index_cache_1.writeSkillsIndex)(skillsOutputPath, skillsIndex);
27
27
  (0, capabilities_index_cache_1.writeCapabilitiesIndex)(capabilitiesOutputPath, capabilitiesIndex);
28
- (0, bundle_imports_1.writeBundleImportsIndex)(importsOutputPath, importsIndex.index);
28
+ (0, subgraphs_1.writeSubgraphsIndex)(subgraphsOutputPath, subgraphsIndex.index);
29
29
  const paths = {
30
30
  nodes: nodesOutputPath,
31
31
  skills: skillsOutputPath,
32
32
  capabilities: capabilitiesOutputPath,
33
- imports: importsOutputPath,
33
+ subgraphs: subgraphsOutputPath,
34
34
  };
35
35
  if ((0, sqlite_index_1.isSqliteBackend)(config)) {
36
36
  paths.sqlite = (0, sqlite_index_1.writeSqliteIndex)({
@@ -39,7 +39,7 @@ function writeDerivedIndexes(root, config, nodeIndex, options) {
39
39
  nodeIndex: nextNodeIndex,
40
40
  skillsIndex,
41
41
  capabilitiesIndex,
42
- importsIndex: importsIndex.index,
42
+ subgraphsIndex: subgraphsIndex.index,
43
43
  });
44
44
  }
45
45
  return { nodeIndex: nextNodeIndex, paths };
@@ -13,7 +13,7 @@ const crypto_1 = __importDefault(require("crypto"));
13
13
  const fs_1 = __importDefault(require("fs"));
14
14
  const path_1 = __importDefault(require("path"));
15
15
  const staleness_1 = require("./staleness");
16
- exports.SQLITE_SCHEMA_VERSION = 1;
16
+ exports.SQLITE_SCHEMA_VERSION = 2;
17
17
  function loadDatabaseCtor() {
18
18
  try {
19
19
  const loaded = require("node:sqlite");
@@ -112,7 +112,7 @@ function createSchema(db) {
112
112
  compressed_sha256 TEXT,
113
113
  json TEXT NOT NULL
114
114
  );
115
- CREATE TABLE bundle_imports (
115
+ CREATE TABLE subgraphs (
116
116
  alias TEXT PRIMARY KEY,
117
117
  enabled INTEGER NOT NULL,
118
118
  stale INTEGER NOT NULL,
@@ -149,7 +149,7 @@ function buildSourceFingerprint(options) {
149
149
  })),
150
150
  skills: Object.values(options.skillsIndex.skills).sort((a, b) => a.qid.localeCompare(b.qid)),
151
151
  capabilities: options.capabilitiesIndex.records,
152
- imports: options.importsIndex.imports,
152
+ subgraphs: options.subgraphsIndex.subgraphs,
153
153
  };
154
154
  return `sha256:${crypto_1.default.createHash("sha256").update(stableCacheJson(payload)).digest("hex")}`;
155
155
  }
@@ -208,9 +208,9 @@ function writeSqliteIndex(options) {
208
208
  for (const node of Object.values(options.nodeIndex.nodes).filter((item) => item.type === "archive")) {
209
209
  insertArchive.run(node.qid, String(node.attributes.visibility ?? "private"), String(node.attributes.compressed_path ?? ""), String(node.attributes.compressed_sha256 ?? ""), stableCacheJson(node));
210
210
  }
211
- const insertImport = db.prepare("INSERT INTO bundle_imports (alias, enabled, stale, error_count, warning_count, json) VALUES (?, ?, ?, ?, ?, ?)");
212
- for (const item of options.importsIndex.imports) {
213
- insertImport.run(item.alias, item.enabled ? 1 : 0, item.stale ? 1 : 0, item.error_count, item.warning_count, stableCacheJson(item));
211
+ const insertSubgraph = db.prepare("INSERT INTO subgraphs (alias, enabled, stale, error_count, warning_count, json) VALUES (?, ?, ?, ?, ?, ?)");
212
+ for (const item of options.subgraphsIndex.subgraphs) {
213
+ insertSubgraph.run(item.alias, item.enabled ? 1 : 0, item.stale ? 1 : 0, item.error_count, item.warning_count, stableCacheJson(item));
214
214
  }
215
215
  }
216
216
  finally {