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.
- package/CHANGELOG.md +45 -1
- package/README.md +34 -10
- package/dist/cli.js +354 -87
- package/dist/commands/bundle.js +7 -7
- package/dist/commands/capability.js +118 -4
- package/dist/commands/doctor.js +15 -15
- package/dist/commands/goal.js +548 -0
- package/dist/commands/index.js +1 -1
- package/dist/commands/init.js +1 -0
- package/dist/commands/list.js +1 -1
- package/dist/commands/new.js +7 -1
- package/dist/commands/next.js +1 -1
- package/dist/commands/node_card.js +1 -1
- package/dist/commands/pack.js +1 -1
- package/dist/commands/search.js +1 -1
- package/dist/commands/show.js +1 -1
- package/dist/commands/subgraph.js +312 -0
- package/dist/commands/task.js +1 -1
- package/dist/commands/upgrade.js +54 -7
- package/dist/commands/validate.js +39 -7
- package/dist/commands/work.js +1 -1
- package/dist/core/config.js +95 -39
- package/dist/graph/frontmatter.js +8 -0
- package/dist/graph/goal_scope.js +127 -0
- package/dist/graph/index_cache.js +12 -12
- package/dist/graph/indexer.js +1 -1
- package/dist/graph/node.js +80 -1
- package/dist/graph/reindex.js +6 -6
- package/dist/graph/sqlite_index.js +6 -6
- package/dist/graph/{bundle_imports.js → subgraphs.js} +214 -140
- package/dist/graph/validate_graph.js +41 -0
- package/dist/graph/visibility.js +3 -3
- package/dist/init/AGENT_START.md +17 -1
- package/dist/init/CLI_COMMAND_MATRIX.md +43 -7
- package/dist/init/README.md +9 -8
- package/dist/init/config.json +1 -1
- package/dist/init/core/rule-3-cli-contract.md +56 -23
- package/dist/init/core/rule-4-repo-safety-and-ignores.md +7 -2
- package/dist/init/core/rule-6-templates-and-schemas.md +10 -1
- package/dist/init/init-manifest.json +20 -10
- package/dist/init/skills/default/pursue-mdkg-goal/SKILL.md +68 -0
- package/dist/init/skills/default/select-work-and-ground-context/SKILL.md +9 -7
- package/dist/init/skills/default/verify-close-and-checkpoint/SKILL.md +11 -10
- package/dist/init/templates/default/goal.md +91 -0
- package/dist/pack/order.js +2 -1
- package/dist/pack/pack.js +17 -0
- package/dist/util/argparse.js +2 -0
- package/package.json +8 -6
- package/dist/commands/bundle_import.js +0 -255
package/dist/core/config.js
CHANGED
|
@@ -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
|
|
177
|
+
function validateSubgraphAlias(alias, workspaces, errors) {
|
|
176
178
|
if (alias === "all") {
|
|
177
|
-
errors.push("
|
|
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(`
|
|
183
|
+
errors.push(`subgraphs.${alias} alias must be lowercase and use [a-z0-9_]`);
|
|
182
184
|
}
|
|
183
185
|
if (workspaces[alias]) {
|
|
184
|
-
errors.push(`
|
|
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
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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
|
|
393
|
-
if (
|
|
394
|
-
for (const [alias, entry] of Object.entries(
|
|
395
|
-
|
|
396
|
-
const
|
|
397
|
-
|
|
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
|
|
401
|
-
const enabled = rawImport.enabled === undefined
|
|
409
|
+
const enabled = rawSubgraph.enabled === undefined
|
|
402
410
|
? true
|
|
403
|
-
: requireBoolean(
|
|
404
|
-
const visibility =
|
|
411
|
+
: requireBoolean(rawSubgraph.enabled, `${basePath}.enabled`, errors);
|
|
412
|
+
const visibility = rawSubgraph.visibility === undefined
|
|
405
413
|
? "private"
|
|
406
|
-
: requireStringInSet(
|
|
407
|
-
const
|
|
408
|
-
? "
|
|
409
|
-
:
|
|
410
|
-
const
|
|
411
|
-
?
|
|
412
|
-
:
|
|
413
|
-
const
|
|
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
|
-
:
|
|
416
|
-
const
|
|
423
|
+
: requireContainedPath(rawSubgraph.source_path, `${basePath}.source_path`, errors);
|
|
424
|
+
const sourceRepo = rawSubgraph.source_repo === undefined
|
|
417
425
|
? undefined
|
|
418
|
-
:
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
errors.push(
|
|
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 (
|
|
426
|
-
|
|
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
|
-
|
|
485
|
+
permissions,
|
|
486
|
+
max_stale_seconds: maxStaleSeconds,
|
|
431
487
|
...(sourcePath ? { source_path: sourcePath } : {}),
|
|
432
488
|
...(sourceRepo ? { source_repo: sourceRepo } : {}),
|
|
433
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
36
|
-
if (!includeImports || Object.keys(options.config.
|
|
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
|
|
39
|
+
const subgraphs = (0, subgraphs_1.buildSubgraphsIndex)(options.root, options.config);
|
|
40
40
|
if (allowReindex) {
|
|
41
|
-
(0,
|
|
41
|
+
(0, subgraphs_1.writeSubgraphsIndex)((0, subgraphs_1.resolveSubgraphsIndexPath)(options.root), subgraphs.index);
|
|
42
42
|
}
|
|
43
43
|
return {
|
|
44
|
-
index: (0,
|
|
44
|
+
index: (0, subgraphs_1.mergeSubgraphsIntoIndex)(index, subgraphs),
|
|
45
45
|
rebuilt,
|
|
46
|
-
stale: stale || (0,
|
|
47
|
-
warnings: (0,
|
|
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
|
|
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
|
|
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
|
|
61
|
+
return withSubgraphs(index, true, stale);
|
|
62
62
|
}
|
|
63
63
|
if (fs_1.default.existsSync(indexPath)) {
|
|
64
|
-
return
|
|
64
|
+
return withSubgraphs(readIndex(indexPath), false, true);
|
|
65
65
|
}
|
|
66
66
|
throw new Error("index missing and auto-reindex is disabled");
|
|
67
67
|
}
|
package/dist/graph/indexer.js
CHANGED
|
@@ -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.
|
|
146
|
+
externalWorkspaces: new Set(Object.keys(config.subgraphs ?? {})),
|
|
147
147
|
});
|
|
148
148
|
const latestCheckpointByWorkspace = {};
|
|
149
149
|
for (const alias of workspaceAliases) {
|
package/dist/graph/node.js
CHANGED
|
@@ -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
|
};
|
package/dist/graph/reindex.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
|
212
|
-
for (const item of options.
|
|
213
|
-
|
|
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 {
|