mdkg 0.4.2 → 0.5.0
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 +71 -15
- package/CLI_COMMAND_MATRIX.md +59 -2
- package/README.md +20 -2
- package/dist/cli.js +124 -2
- package/dist/command-contract.json +756 -2
- package/dist/commands/archive.js +31 -14
- package/dist/commands/bundle.js +180 -44
- package/dist/commands/capability.js +1 -0
- package/dist/commands/checkpoint.js +6 -5
- package/dist/commands/event_support.js +16 -7
- package/dist/commands/fix.js +11 -8
- package/dist/commands/git.js +41 -10
- package/dist/commands/goal.js +23 -23
- package/dist/commands/graph.js +64 -10
- package/dist/commands/handoff.js +4 -2
- package/dist/commands/init.js +28 -23
- package/dist/commands/loop.js +1668 -0
- package/dist/commands/loop_descriptors.js +219 -0
- package/dist/commands/mcp.js +101 -11
- package/dist/commands/new.js +49 -3
- package/dist/commands/pack.js +14 -7
- package/dist/commands/search.js +10 -1
- package/dist/commands/skill.js +12 -9
- package/dist/commands/skill_mirror.js +39 -31
- package/dist/commands/subgraph.js +59 -26
- package/dist/commands/task.js +77 -4
- package/dist/commands/upgrade.js +13 -15
- package/dist/commands/validate.js +20 -7
- package/dist/commands/work.js +44 -26
- package/dist/commands/workspace.js +10 -4
- package/dist/core/config.js +45 -17
- package/dist/core/filesystem_authority.js +281 -0
- package/dist/core/project_db_events.js +4 -0
- package/dist/core/project_db_materializer.js +2 -0
- package/dist/core/project_db_migrations.js +238 -181
- package/dist/core/project_db_snapshot.js +64 -14
- package/dist/core/workspace_path.js +7 -0
- package/dist/graph/archive_integrity.js +1 -1
- package/dist/graph/capabilities_index_cache.js +4 -1
- package/dist/graph/frontmatter.js +38 -0
- package/dist/graph/goal_scope.js +4 -1
- package/dist/graph/index_cache.js +37 -7
- package/dist/graph/loop_bindings.js +39 -0
- package/dist/graph/node.js +209 -1
- package/dist/graph/node_body.js +52 -6
- package/dist/graph/skills_index_cache.js +41 -6
- package/dist/graph/skills_indexer.js +5 -2
- package/dist/graph/sqlite_index.js +118 -105
- package/dist/graph/subgraphs.js +142 -5
- package/dist/graph/validate_graph.js +109 -13
- package/dist/graph/workspace_files.js +39 -9
- package/dist/init/AGENT_START.md +16 -0
- package/dist/init/CLI_COMMAND_MATRIX.md +14 -0
- package/dist/init/config.json +7 -1
- package/dist/init/init-manifest.json +49 -4
- package/dist/init/skills/default/pursue-mdkg-loop/SKILL.md +150 -0
- package/dist/init/templates/default/loop.md +160 -0
- package/dist/init/templates/loops/backend-api-cli-bloat-audit.loop.md +117 -0
- package/dist/init/templates/loops/design-frontend-ux-audit.loop.md +117 -0
- package/dist/init/templates/loops/duplicate-code-and-linting-audit.loop.md +114 -0
- package/dist/init/templates/loops/security-audit.loop.md +140 -0
- package/dist/init/templates/loops/tech-stack-best-practices-audit.loop.md +116 -0
- package/dist/init/templates/loops/test-ci-skill-infrastructure-audit.loop.md +116 -0
- package/dist/init/templates/loops/user-story-audit-and-recommendations.loop.md +116 -0
- package/dist/pack/order.js +3 -1
- package/dist/pack/pack.js +139 -6
- package/dist/templates/loader.js +9 -3
- package/dist/util/lock.js +10 -9
- package/dist/util/zip.js +163 -9
- package/package.json +8 -4
|
@@ -7,12 +7,12 @@ exports.resolveContainedProjectDbPath = resolveContainedProjectDbPath;
|
|
|
7
7
|
exports.sealProjectDbSnapshot = sealProjectDbSnapshot;
|
|
8
8
|
exports.verifyProjectDbSnapshot = verifyProjectDbSnapshot;
|
|
9
9
|
exports.projectDbSnapshotStatus = projectDbSnapshotStatus;
|
|
10
|
-
exports.canonicalDumpForSnapshot = canonicalDumpForSnapshot;
|
|
11
10
|
exports.dumpProjectDbSnapshot = dumpProjectDbSnapshot;
|
|
12
11
|
exports.diffProjectDbSnapshots = diffProjectDbSnapshots;
|
|
13
12
|
const crypto_1 = __importDefault(require("crypto"));
|
|
14
13
|
const fs_1 = __importDefault(require("fs"));
|
|
15
14
|
const path_1 = __importDefault(require("path"));
|
|
15
|
+
const filesystem_authority_1 = require("./filesystem_authority");
|
|
16
16
|
const project_db_1 = require("./project_db");
|
|
17
17
|
const version_1 = require("./version");
|
|
18
18
|
const atomic_1 = require("../util/atomic");
|
|
@@ -166,7 +166,11 @@ function readManifest(filePath) {
|
|
|
166
166
|
typeof manifest.snapshot_sha256 !== "string" ||
|
|
167
167
|
typeof manifest.byte_size !== "number" ||
|
|
168
168
|
!Array.isArray(manifest.table_counts) ||
|
|
169
|
-
!Array.isArray(manifest.migrations)
|
|
169
|
+
!Array.isArray(manifest.migrations) ||
|
|
170
|
+
(manifest.queue_policy !== "drain" && manifest.queue_policy !== "paused") ||
|
|
171
|
+
typeof manifest.queue_summary !== "object" ||
|
|
172
|
+
manifest.queue_summary === null ||
|
|
173
|
+
Array.isArray(manifest.queue_summary)) {
|
|
170
174
|
throw new errors_1.ValidationError("snapshot manifest has unsupported shape");
|
|
171
175
|
}
|
|
172
176
|
return manifest;
|
|
@@ -251,7 +255,9 @@ function sealProjectDbSnapshot(root, config, queuePolicy = "drain") {
|
|
|
251
255
|
}
|
|
252
256
|
try {
|
|
253
257
|
const runtimeHash = sha256File(layout.runtimeFile);
|
|
254
|
-
const
|
|
258
|
+
const sealedQueueSummary = (0, project_db_queue_1.readProjectQueueSnapshotSummary)(tempSnapshot);
|
|
259
|
+
assertQueueSnapshotPolicy(queuePolicy, sealedQueueSummary);
|
|
260
|
+
const manifest = buildManifest(root, config, tempSnapshot, runtimeHash, queuePolicy, sealedQueueSummary);
|
|
255
261
|
(0, atomic_1.atomicWriteFile)(tempManifest, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
256
262
|
fs_1.default.renameSync(tempSnapshot, layout.stateFile);
|
|
257
263
|
fs_1.default.renameSync(tempManifest, layout.stateManifest);
|
|
@@ -267,7 +273,7 @@ function sealProjectDbSnapshot(root, config, queuePolicy = "drain") {
|
|
|
267
273
|
table_counts: manifest.table_counts,
|
|
268
274
|
migrations: manifest.migrations,
|
|
269
275
|
queue_policy: queuePolicy,
|
|
270
|
-
queue_summary:
|
|
276
|
+
queue_summary: sealedQueueSummary,
|
|
271
277
|
warnings: warningListFromVerify(root, config),
|
|
272
278
|
};
|
|
273
279
|
}
|
|
@@ -384,7 +390,18 @@ function verifyProjectDbSnapshot(root, config) {
|
|
|
384
390
|
warnings: [],
|
|
385
391
|
});
|
|
386
392
|
}
|
|
387
|
-
if (
|
|
393
|
+
if (fs_1.default.existsSync(layout.runtimeFile) && !manifest.source_runtime_sha256) {
|
|
394
|
+
checks.push({
|
|
395
|
+
name: "runtime-freshness",
|
|
396
|
+
ok: false,
|
|
397
|
+
level: "fail",
|
|
398
|
+
path: rel(root, layout.runtimeFile),
|
|
399
|
+
detail: "snapshot source runtime hash is missing",
|
|
400
|
+
errors: ["snapshot manifest must include source_runtime_sha256 when the runtime database exists"],
|
|
401
|
+
warnings: [],
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
else if (manifest.source_runtime_sha256 && fs_1.default.existsSync(layout.runtimeFile)) {
|
|
388
405
|
const runtimeHash = sha256File(layout.runtimeFile);
|
|
389
406
|
checks.push({
|
|
390
407
|
name: "runtime-freshness",
|
|
@@ -396,6 +413,30 @@ function verifyProjectDbSnapshot(root, config) {
|
|
|
396
413
|
warnings: runtimeHash === manifest.source_runtime_sha256 ? [] : ["runtime database hash differs from sealed snapshot source hash"],
|
|
397
414
|
});
|
|
398
415
|
}
|
|
416
|
+
try {
|
|
417
|
+
const sealedQueueSummary = (0, project_db_queue_1.readProjectQueueSnapshotSummary)(layout.stateFile);
|
|
418
|
+
assertQueueSnapshotPolicy(manifest.queue_policy, sealedQueueSummary);
|
|
419
|
+
const matchesManifest = compareJson(sealedQueueSummary, manifest.queue_summary);
|
|
420
|
+
checks.push({
|
|
421
|
+
name: "queue-policy",
|
|
422
|
+
ok: matchesManifest,
|
|
423
|
+
level: matchesManifest ? "ok" : "fail",
|
|
424
|
+
detail: matchesManifest ? "sealed queue state satisfies policy and matches manifest" : "sealed queue summary differs from manifest",
|
|
425
|
+
errors: matchesManifest ? [] : ["sealed queue summary does not match snapshot manifest"],
|
|
426
|
+
warnings: [],
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
catch (err) {
|
|
430
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
431
|
+
checks.push({
|
|
432
|
+
name: "queue-policy",
|
|
433
|
+
ok: false,
|
|
434
|
+
level: "fail",
|
|
435
|
+
detail: "sealed queue state violates snapshot policy",
|
|
436
|
+
errors: [message],
|
|
437
|
+
warnings: [],
|
|
438
|
+
});
|
|
439
|
+
}
|
|
399
440
|
}
|
|
400
441
|
const errors = checks.flatMap((check) => check.errors.map((error) => `${check.name}: ${error}`));
|
|
401
442
|
const warnings = checks.flatMap((check) => check.warnings.map((warning) => `${check.name}: ${warning}`));
|
|
@@ -485,17 +526,24 @@ function canonicalDumpForSnapshot(root, snapshotPath) {
|
|
|
485
526
|
db.close();
|
|
486
527
|
}
|
|
487
528
|
}
|
|
529
|
+
function canonicalDumpForContainedSnapshot(root, rawPath, label) {
|
|
530
|
+
const resolved = resolveContainedProjectDbPath(root, rawPath, label);
|
|
531
|
+
const relativePath = rel(root, resolved);
|
|
532
|
+
return (0, filesystem_authority_1.withContainedPathSink)({ root, relativePath, operation: "read" }, ({ absolutePath }) => ({
|
|
533
|
+
absolutePath,
|
|
534
|
+
dump: canonicalDumpForSnapshot(root, absolutePath),
|
|
535
|
+
}));
|
|
536
|
+
}
|
|
488
537
|
function dumpProjectDbSnapshot(root, config, snapshotPath, outputPath) {
|
|
489
538
|
const layout = (0, project_db_1.resolveConfiguredProjectDbLayout)(root, config.db);
|
|
490
|
-
const
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
const dump = canonicalDumpForSnapshot(root, resolvedSnapshot);
|
|
539
|
+
const snapshot = canonicalDumpForContainedSnapshot(root, snapshotPath ?? layout.stateFile, "--snapshot");
|
|
540
|
+
const resolvedSnapshot = snapshot.absolutePath;
|
|
541
|
+
const dump = snapshot.dump;
|
|
494
542
|
const dumpHash = sha256Buffer(dump);
|
|
495
543
|
let output = null;
|
|
496
544
|
if (outputPath) {
|
|
497
545
|
const resolvedOutput = resolveContainedProjectDbPath(root, outputPath, "--output");
|
|
498
|
-
(0,
|
|
546
|
+
(0, filesystem_authority_1.atomicReplaceContainedFile)({ root, relativePath: rel(root, resolvedOutput) }, dump);
|
|
499
547
|
output = rel(root, resolvedOutput);
|
|
500
548
|
}
|
|
501
549
|
return {
|
|
@@ -509,10 +557,12 @@ function dumpProjectDbSnapshot(root, config, snapshotPath, outputPath) {
|
|
|
509
557
|
};
|
|
510
558
|
}
|
|
511
559
|
function diffProjectDbSnapshots(root, leftPath, rightPath) {
|
|
512
|
-
const
|
|
513
|
-
const
|
|
514
|
-
const
|
|
515
|
-
const
|
|
560
|
+
const leftSnapshot = canonicalDumpForContainedSnapshot(root, leftPath, "left snapshot");
|
|
561
|
+
const rightSnapshot = canonicalDumpForContainedSnapshot(root, rightPath, "right snapshot");
|
|
562
|
+
const left = leftSnapshot.absolutePath;
|
|
563
|
+
const right = rightSnapshot.absolutePath;
|
|
564
|
+
const leftDump = leftSnapshot.dump;
|
|
565
|
+
const rightDump = rightSnapshot.dump;
|
|
516
566
|
const leftLines = leftDump.trimEnd().split("\n");
|
|
517
567
|
const rightLines = rightDump.trimEnd().split("\n");
|
|
518
568
|
const leftSet = new Set(leftLines);
|
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.normalizeContainedWorkspacePath = normalizeContainedWorkspacePath;
|
|
7
7
|
exports.isRootWorkspacePath = isRootWorkspacePath;
|
|
8
8
|
exports.workspaceDocumentRootKey = workspaceDocumentRootKey;
|
|
9
|
+
exports.workspaceDocumentRelativePath = workspaceDocumentRelativePath;
|
|
9
10
|
const path_1 = __importDefault(require("path"));
|
|
10
11
|
function isAbsoluteOnSupportedPlatform(value) {
|
|
11
12
|
return path_1.default.isAbsolute(value) || path_1.default.posix.isAbsolute(value) || path_1.default.win32.isAbsolute(value);
|
|
@@ -39,3 +40,9 @@ function workspaceDocumentRootKey(workspacePath, mdkgDir) {
|
|
|
39
40
|
.filter((part) => part && part !== ".")
|
|
40
41
|
.join("/");
|
|
41
42
|
}
|
|
43
|
+
function workspaceDocumentRelativePath(workspacePath, mdkgDir, ...suffix) {
|
|
44
|
+
return [workspacePath, mdkgDir, ...suffix]
|
|
45
|
+
.flatMap((value) => value.trim().split(/[\\/]+/))
|
|
46
|
+
.filter((part) => part && part !== ".")
|
|
47
|
+
.join("/");
|
|
48
|
+
}
|
|
@@ -36,7 +36,7 @@ function checkArchiveIntegrity(options) {
|
|
|
36
36
|
result.errors.push(`compressed_sha256 mismatch: ${actualCompressedHash}`);
|
|
37
37
|
}
|
|
38
38
|
try {
|
|
39
|
-
const unzipped = (0, zip_1.
|
|
39
|
+
const unzipped = (0, zip_1.readSingleFileZipPath)(options.zipPath);
|
|
40
40
|
const unzippedHash = hashArchiveBuffer(unzipped.data);
|
|
41
41
|
if (unzippedHash !== options.expectedRawHash) {
|
|
42
42
|
result.errors.push(`zip payload sha256 mismatch: ${unzippedHash}`);
|
|
@@ -82,6 +82,7 @@ function writeCapabilitiesIndex(indexPath, index) {
|
|
|
82
82
|
function loadCapabilitiesIndex(options) {
|
|
83
83
|
const useCache = options.useCache ?? true;
|
|
84
84
|
const allowReindex = options.allowReindex ?? options.config.index.auto_reindex;
|
|
85
|
+
const persistReindex = options.persistReindex ?? true;
|
|
85
86
|
const indexPath = (0, capabilities_indexer_1.resolveCapabilitiesIndexPath)(options.root, options.config);
|
|
86
87
|
if (!useCache) {
|
|
87
88
|
const index = (0, capabilities_indexer_1.buildCapabilitiesIndex)(options.root, options.config);
|
|
@@ -93,7 +94,9 @@ function loadCapabilitiesIndex(options) {
|
|
|
93
94
|
}
|
|
94
95
|
if (allowReindex) {
|
|
95
96
|
const index = (0, capabilities_indexer_1.buildCapabilitiesIndex)(options.root, options.config);
|
|
96
|
-
|
|
97
|
+
if (persistReindex) {
|
|
98
|
+
writeCapabilitiesIndex(indexPath, index);
|
|
99
|
+
}
|
|
97
100
|
return { index, rebuilt: true, stale };
|
|
98
101
|
}
|
|
99
102
|
if (fs_1.default.existsSync(indexPath)) {
|
|
@@ -4,6 +4,9 @@ exports.DEFAULT_FRONTMATTER_KEY_ORDER = void 0;
|
|
|
4
4
|
exports.parseFrontmatter = parseFrontmatter;
|
|
5
5
|
exports.formatFrontmatter = formatFrontmatter;
|
|
6
6
|
const KEY_RE = /^[a-z][a-z0-9_]*$/;
|
|
7
|
+
const MAX_FRONTMATTER_LINES = 1000;
|
|
8
|
+
const MAX_FRONTMATTER_LINE_BYTES = 64 * 1024;
|
|
9
|
+
const MAX_FRONTMATTER_LIST_ITEMS = 10000;
|
|
7
10
|
exports.DEFAULT_FRONTMATTER_KEY_ORDER = [
|
|
8
11
|
"id",
|
|
9
12
|
"type",
|
|
@@ -20,6 +23,32 @@ exports.DEFAULT_FRONTMATTER_KEY_ORDER = [
|
|
|
20
23
|
"required_checks",
|
|
21
24
|
"max_iterations",
|
|
22
25
|
"blocked_after_attempts",
|
|
26
|
+
"loop_mode",
|
|
27
|
+
"loop_role",
|
|
28
|
+
"scope_description",
|
|
29
|
+
"template_refs",
|
|
30
|
+
"materialization_mode",
|
|
31
|
+
"child_refs",
|
|
32
|
+
"pre_run_questions",
|
|
33
|
+
"question_answer_refs",
|
|
34
|
+
"pre_approved_actions",
|
|
35
|
+
"approval_gated_actions",
|
|
36
|
+
"required_actions",
|
|
37
|
+
"requested_actions",
|
|
38
|
+
"prohibited_actions",
|
|
39
|
+
"action_approval_refs",
|
|
40
|
+
"evidence_lanes",
|
|
41
|
+
"evidence_lane_refs",
|
|
42
|
+
"lane_waiver_refs",
|
|
43
|
+
"lane_waiver_decision_refs",
|
|
44
|
+
"lane_waiver_approval_refs",
|
|
45
|
+
"run_refs",
|
|
46
|
+
"decision_refs",
|
|
47
|
+
"output_refs",
|
|
48
|
+
"approval_refs",
|
|
49
|
+
"evaluation_refs",
|
|
50
|
+
"definition_of_done",
|
|
51
|
+
"blocker_policy",
|
|
23
52
|
"epic",
|
|
24
53
|
"parent",
|
|
25
54
|
"prev",
|
|
@@ -116,6 +145,9 @@ function parseList(valueRaw, filePath, lineNumber) {
|
|
|
116
145
|
return [];
|
|
117
146
|
}
|
|
118
147
|
const parts = inner.split(",");
|
|
148
|
+
if (parts.length > MAX_FRONTMATTER_LIST_ITEMS) {
|
|
149
|
+
throw formatError(filePath, lineNumber, `list exceeds ${MAX_FRONTMATTER_LIST_ITEMS} items`);
|
|
150
|
+
}
|
|
119
151
|
const items = [];
|
|
120
152
|
for (let i = 0; i < parts.length; i += 1) {
|
|
121
153
|
const item = parts[i].trim();
|
|
@@ -156,10 +188,16 @@ function parseFrontmatter(content, filePath) {
|
|
|
156
188
|
if (endIndex === -1) {
|
|
157
189
|
throw formatError(filePath, 1, "frontmatter closing --- not found");
|
|
158
190
|
}
|
|
191
|
+
if (endIndex > MAX_FRONTMATTER_LINES) {
|
|
192
|
+
throw formatError(filePath, 1, `frontmatter exceeds ${MAX_FRONTMATTER_LINES} lines`);
|
|
193
|
+
}
|
|
159
194
|
const frontmatter = {};
|
|
160
195
|
for (let i = 1; i < endIndex; i += 1) {
|
|
161
196
|
const line = lines[i];
|
|
162
197
|
const lineNumber = i + 1;
|
|
198
|
+
if (Buffer.byteLength(line, "utf8") > MAX_FRONTMATTER_LINE_BYTES) {
|
|
199
|
+
throw formatError(filePath, lineNumber, `frontmatter line exceeds ${MAX_FRONTMATTER_LINE_BYTES} bytes`);
|
|
200
|
+
}
|
|
163
201
|
if (line.trim().length === 0) {
|
|
164
202
|
throw formatError(filePath, lineNumber, "frontmatter lines must not be blank");
|
|
165
203
|
}
|
package/dist/graph/goal_scope.js
CHANGED
|
@@ -4,7 +4,7 @@ exports.GOAL_SCOPE_ALLOWED_TYPES = exports.GOAL_SCOPE_ACTIONABLE_TYPES = exports
|
|
|
4
4
|
exports.goalScopeRefs = goalScopeRefs;
|
|
5
5
|
exports.collectGoalScope = collectGoalScope;
|
|
6
6
|
const qid_1 = require("../util/qid");
|
|
7
|
-
exports.GOAL_SCOPE_CONTAINER_TYPES = new Set(["epic", "feat"]);
|
|
7
|
+
exports.GOAL_SCOPE_CONTAINER_TYPES = new Set(["loop", "epic", "feat"]);
|
|
8
8
|
exports.GOAL_SCOPE_ACTIONABLE_TYPES = new Set(["feat", "task", "bug", "test", "spike"]);
|
|
9
9
|
exports.GOAL_SCOPE_ALLOWED_TYPES = new Set([
|
|
10
10
|
...exports.GOAL_SCOPE_CONTAINER_TYPES,
|
|
@@ -75,6 +75,9 @@ function collectGoalScope(index, goal, options = {}) {
|
|
|
75
75
|
if (queued.has(qid)) {
|
|
76
76
|
return;
|
|
77
77
|
}
|
|
78
|
+
if (options.maxNodes !== undefined && queued.size >= options.maxNodes) {
|
|
79
|
+
throw new Error(`goal scope traversal exceeds node limit: ${options.maxNodes}`);
|
|
80
|
+
}
|
|
78
81
|
queued.add(qid);
|
|
79
82
|
queue.push(qid);
|
|
80
83
|
}
|
|
@@ -7,21 +7,47 @@ exports.writeIndex = writeIndex;
|
|
|
7
7
|
exports.loadIndex = loadIndex;
|
|
8
8
|
const fs_1 = __importDefault(require("fs"));
|
|
9
9
|
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const filesystem_authority_1 = require("../core/filesystem_authority");
|
|
10
11
|
const sort_1 = require("../util/sort");
|
|
11
12
|
const atomic_1 = require("../util/atomic");
|
|
12
13
|
const indexer_1 = require("./indexer");
|
|
13
14
|
const staleness_1 = require("./staleness");
|
|
14
15
|
const subgraphs_1 = require("./subgraphs");
|
|
15
|
-
function
|
|
16
|
+
function isRecord(value) {
|
|
17
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
18
|
+
}
|
|
19
|
+
function readIndex(root, indexPath) {
|
|
16
20
|
try {
|
|
17
|
-
const
|
|
18
|
-
|
|
21
|
+
const relativePath = path_1.default.relative(root, indexPath).split(path_1.default.sep).join("/");
|
|
22
|
+
const parsed = JSON.parse((0, filesystem_authority_1.readContainedFile)({ root, relativePath }, "utf8"));
|
|
23
|
+
if (!isRecord(parsed) || !isRecord(parsed.meta) || !isRecord(parsed.workspaces) || !isRecord(parsed.nodes) || !isRecord(parsed.reverse_edges)) {
|
|
24
|
+
throw new Error("index cache has an invalid shape");
|
|
25
|
+
}
|
|
26
|
+
return parsed;
|
|
19
27
|
}
|
|
20
28
|
catch (err) {
|
|
21
29
|
const message = err instanceof Error ? err.message : "unknown error";
|
|
22
30
|
throw new Error(`failed to read index: ${message}`);
|
|
23
31
|
}
|
|
24
32
|
}
|
|
33
|
+
function validateCachedNodePaths(root, config, cached) {
|
|
34
|
+
for (const node of Object.values(cached.nodes)) {
|
|
35
|
+
if (node.qid !== `${node.ws}:${node.id}` || node.source?.imported)
|
|
36
|
+
throw new Error(`invalid cached node identity: ${node.qid}`);
|
|
37
|
+
const workspace = config.workspaces[node.ws];
|
|
38
|
+
if (!workspace?.enabled)
|
|
39
|
+
throw new Error(`invalid cached node workspace: ${node.ws}`);
|
|
40
|
+
const normalized = node.path.split(path_1.default.sep).join("/");
|
|
41
|
+
const workspaceRoot = path_1.default.resolve(root, workspace.path, workspace.mdkg_dir);
|
|
42
|
+
const absolutePath = path_1.default.resolve(root, normalized);
|
|
43
|
+
const relativeWorkspacePath = path_1.default.relative(workspaceRoot, absolutePath);
|
|
44
|
+
if (path_1.default.isAbsolute(normalized) || relativeWorkspacePath.startsWith("..") || path_1.default.isAbsolute(relativeWorkspacePath)) {
|
|
45
|
+
throw new Error(`cached node path escapes workspace root: ${node.qid}`);
|
|
46
|
+
}
|
|
47
|
+
(0, filesystem_authority_1.withContainedPathSink)({ root, relativePath: normalized, operation: "read" }, () => undefined);
|
|
48
|
+
}
|
|
49
|
+
return cached;
|
|
50
|
+
}
|
|
25
51
|
function writeIndex(indexPath, index) {
|
|
26
52
|
const sortedIndex = { ...index, nodes: (0, sort_1.sortIndexNodes)(index.nodes) };
|
|
27
53
|
(0, atomic_1.atomicWriteFile)(indexPath, JSON.stringify(sortedIndex, null, 2));
|
|
@@ -31,13 +57,14 @@ function loadIndex(options) {
|
|
|
31
57
|
const allowReindex = options.allowReindex ?? options.config.index.auto_reindex;
|
|
32
58
|
const tolerant = options.tolerant ?? options.config.index.tolerant;
|
|
33
59
|
const includeImports = options.includeImports ?? true;
|
|
60
|
+
const persistReindex = options.persistReindex ?? true;
|
|
34
61
|
const indexPath = path_1.default.resolve(options.root, options.config.index.global_index_path);
|
|
35
62
|
const withSubgraphs = (index, rebuilt, stale) => {
|
|
36
63
|
if (!includeImports || Object.keys(options.config.subgraphs).length === 0) {
|
|
37
64
|
return { index, rebuilt, stale, warnings: [] };
|
|
38
65
|
}
|
|
39
66
|
const subgraphs = (0, subgraphs_1.buildSubgraphsIndex)(options.root, options.config);
|
|
40
|
-
if (allowReindex) {
|
|
67
|
+
if (allowReindex && persistReindex) {
|
|
41
68
|
(0, subgraphs_1.writeSubgraphsIndex)((0, subgraphs_1.resolveSubgraphsIndexPath)(options.root), subgraphs.index);
|
|
42
69
|
}
|
|
43
70
|
return {
|
|
@@ -53,15 +80,18 @@ function loadIndex(options) {
|
|
|
53
80
|
}
|
|
54
81
|
const stale = (0, staleness_1.isIndexStale)(options.root, options.config);
|
|
55
82
|
if (fs_1.default.existsSync(indexPath) && !stale) {
|
|
56
|
-
return withSubgraphs(readIndex(indexPath), false, false);
|
|
83
|
+
return withSubgraphs(validateCachedNodePaths(options.root, options.config, readIndex(options.root, indexPath)), false, false);
|
|
57
84
|
}
|
|
58
85
|
if (allowReindex) {
|
|
59
86
|
const index = (0, indexer_1.buildIndex)(options.root, options.config, { tolerant });
|
|
60
|
-
|
|
87
|
+
if (persistReindex) {
|
|
88
|
+
writeIndex(indexPath, index);
|
|
89
|
+
}
|
|
61
90
|
return withSubgraphs(index, true, stale);
|
|
62
91
|
}
|
|
63
92
|
if (fs_1.default.existsSync(indexPath)) {
|
|
64
|
-
|
|
93
|
+
const cached = validateCachedNodePaths(options.root, options.config, readIndex(options.root, indexPath));
|
|
94
|
+
return withSubgraphs(cached, false, true);
|
|
65
95
|
}
|
|
66
96
|
throw new Error("index missing and auto-reindex is disabled");
|
|
67
97
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isLoopIdentity = isLoopIdentity;
|
|
4
|
+
exports.parseLoopRefBinding = parseLoopRefBinding;
|
|
5
|
+
exports.parseLoopRefBindings = parseLoopRefBindings;
|
|
6
|
+
exports.groupLoopRefBindings = groupLoopRefBindings;
|
|
7
|
+
const LOOP_IDENTITY_RE = /^[a-z0-9][a-z0-9._-]*$/;
|
|
8
|
+
function isLoopIdentity(value) {
|
|
9
|
+
return LOOP_IDENTITY_RE.test(value);
|
|
10
|
+
}
|
|
11
|
+
function parseLoopRefBinding(value) {
|
|
12
|
+
const separator = value.indexOf("=");
|
|
13
|
+
if (separator <= 0 || separator === value.length - 1) {
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
const identity = value.slice(0, separator).trim().toLowerCase();
|
|
17
|
+
const ref = value.slice(separator + 1).trim();
|
|
18
|
+
if (!isLoopIdentity(identity) || !ref) {
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
return { identity, ref };
|
|
22
|
+
}
|
|
23
|
+
function parseLoopRefBindings(values) {
|
|
24
|
+
return values.flatMap((value) => {
|
|
25
|
+
const binding = parseLoopRefBinding(value);
|
|
26
|
+
return binding ? [binding] : [];
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
function groupLoopRefBindings(bindings) {
|
|
30
|
+
const grouped = new Map();
|
|
31
|
+
for (const binding of bindings) {
|
|
32
|
+
const refs = grouped.get(binding.identity) ?? [];
|
|
33
|
+
if (!refs.includes(binding.ref)) {
|
|
34
|
+
refs.push(binding.ref);
|
|
35
|
+
}
|
|
36
|
+
grouped.set(binding.identity, refs);
|
|
37
|
+
}
|
|
38
|
+
return grouped;
|
|
39
|
+
}
|
package/dist/graph/node.js
CHANGED
|
@@ -8,9 +8,10 @@ const agent_file_types_1 = require("./agent_file_types");
|
|
|
8
8
|
const archive_file_1 = require("./archive_file");
|
|
9
9
|
const id_1 = require("../util/id");
|
|
10
10
|
const refs_1 = require("../util/refs");
|
|
11
|
+
const loop_bindings_1 = require("./loop_bindings");
|
|
11
12
|
const DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
12
13
|
const DEC_ID_RE = /^dec-[0-9]+$/;
|
|
13
|
-
exports.WORK_TYPES = new Set(["goal", "epic", "feat", "task", "bug", "spike", "checkpoint", "test"]);
|
|
14
|
+
exports.WORK_TYPES = new Set(["goal", "loop", "epic", "feat", "task", "bug", "spike", "checkpoint", "test"]);
|
|
14
15
|
exports.DEC_TYPES = new Set(["dec"]);
|
|
15
16
|
exports.ALLOWED_TYPES = new Set([
|
|
16
17
|
"rule",
|
|
@@ -19,6 +20,7 @@ exports.ALLOWED_TYPES = new Set([
|
|
|
19
20
|
"dec",
|
|
20
21
|
"prop",
|
|
21
22
|
"goal",
|
|
23
|
+
"loop",
|
|
22
24
|
"epic",
|
|
23
25
|
"feat",
|
|
24
26
|
"task",
|
|
@@ -43,6 +45,45 @@ const GOAL_ATTRIBUTE_KEYS = [
|
|
|
43
45
|
"max_iterations",
|
|
44
46
|
"blocked_after_attempts",
|
|
45
47
|
];
|
|
48
|
+
const LOOP_MODES = new Set([
|
|
49
|
+
"readonly",
|
|
50
|
+
"planning",
|
|
51
|
+
"patch_proposal",
|
|
52
|
+
"write_with_approval",
|
|
53
|
+
"autonomous_local",
|
|
54
|
+
]);
|
|
55
|
+
const LOOP_ROLES = new Set(["template", "scoped", "run_bearing"]);
|
|
56
|
+
const LOOP_MATERIALIZATION_MODES = new Set(["default_children", "planning_only", "manual"]);
|
|
57
|
+
const LOOP_BLOCKER_POLICIES = new Set(["spike_proposal_recommendation_continue"]);
|
|
58
|
+
const LOOP_ATTRIBUTE_KEYS = [
|
|
59
|
+
"loop_mode",
|
|
60
|
+
"loop_role",
|
|
61
|
+
"scope_refs",
|
|
62
|
+
"scope_description",
|
|
63
|
+
"template_refs",
|
|
64
|
+
"materialization_mode",
|
|
65
|
+
"child_refs",
|
|
66
|
+
"pre_run_questions",
|
|
67
|
+
"question_answer_refs",
|
|
68
|
+
"pre_approved_actions",
|
|
69
|
+
"approval_gated_actions",
|
|
70
|
+
"required_actions",
|
|
71
|
+
"requested_actions",
|
|
72
|
+
"prohibited_actions",
|
|
73
|
+
"action_approval_refs",
|
|
74
|
+
"evidence_lanes",
|
|
75
|
+
"evidence_lane_refs",
|
|
76
|
+
"lane_waiver_refs",
|
|
77
|
+
"lane_waiver_decision_refs",
|
|
78
|
+
"lane_waiver_approval_refs",
|
|
79
|
+
"run_refs",
|
|
80
|
+
"decision_refs",
|
|
81
|
+
"output_refs",
|
|
82
|
+
"approval_refs",
|
|
83
|
+
"evaluation_refs",
|
|
84
|
+
"definition_of_done",
|
|
85
|
+
"blocker_policy",
|
|
86
|
+
];
|
|
46
87
|
function formatError(filePath, message) {
|
|
47
88
|
return new Error(`${filePath}: ${message}`);
|
|
48
89
|
}
|
|
@@ -215,6 +256,158 @@ function validateGoalFrontmatter(type, frontmatter, filePath) {
|
|
|
215
256
|
parsePositiveIntegerString(blockedAfterAttempts, "blocked_after_attempts", filePath);
|
|
216
257
|
}
|
|
217
258
|
}
|
|
259
|
+
function validateLoopRefList(frontmatter, key, filePath) {
|
|
260
|
+
const values = optionalList(frontmatter, key, filePath);
|
|
261
|
+
for (const [index, value] of values.entries()) {
|
|
262
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
263
|
+
throw formatError(filePath, `${key}[${index}] must not be empty`);
|
|
264
|
+
}
|
|
265
|
+
const normalized = value.includes("://") ? value : requireLowercase(value, `${key}[${index}]`, filePath);
|
|
266
|
+
if (!(0, refs_1.validatePortableOrUriRef)(normalized)) {
|
|
267
|
+
throw formatError(filePath, `${key}[${index}] must be a portable id, qid, or URI ref`);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
function validateLoopStringList(frontmatter, key, filePath) {
|
|
272
|
+
const values = optionalList(frontmatter, key, filePath);
|
|
273
|
+
const seen = new Set();
|
|
274
|
+
for (const [index, value] of values.entries()) {
|
|
275
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
276
|
+
throw formatError(filePath, `${key}[${index}] must not be empty`);
|
|
277
|
+
}
|
|
278
|
+
const normalized = value.trim().toLowerCase();
|
|
279
|
+
if (!(0, loop_bindings_1.isLoopIdentity)(normalized)) {
|
|
280
|
+
throw formatError(filePath, `${key}[${index}] must be a stable lowercase identity`);
|
|
281
|
+
}
|
|
282
|
+
if (value.trim() !== normalized) {
|
|
283
|
+
throw formatError(filePath, `${key}[${index}] must be lowercase; use ${normalized}`);
|
|
284
|
+
}
|
|
285
|
+
if (seen.has(normalized)) {
|
|
286
|
+
throw formatError(filePath, `${key}[${index}] duplicates identity ${normalized}`);
|
|
287
|
+
}
|
|
288
|
+
seen.add(normalized);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
function validateLoopBindingList(frontmatter, key, identityKey, refKeys, filePath, requireEveryRefKey = false) {
|
|
292
|
+
const values = optionalList(frontmatter, key, filePath);
|
|
293
|
+
const identities = new Set(optionalList(frontmatter, identityKey, filePath).map((value) => value.toLowerCase()));
|
|
294
|
+
const refsByKey = refKeys.map((refKey) => new Set(optionalList(frontmatter, refKey, filePath)));
|
|
295
|
+
const seen = new Set();
|
|
296
|
+
for (const [index, value] of values.entries()) {
|
|
297
|
+
if (typeof value !== "string") {
|
|
298
|
+
throw formatError(filePath, `${key}[${index}] must use <identity>=<ref>`);
|
|
299
|
+
}
|
|
300
|
+
const binding = (0, loop_bindings_1.parseLoopRefBinding)(value);
|
|
301
|
+
if (!binding) {
|
|
302
|
+
throw formatError(filePath, `${key}[${index}] must use <identity>=<ref>`);
|
|
303
|
+
}
|
|
304
|
+
if (!identities.has(binding.identity)) {
|
|
305
|
+
throw formatError(filePath, `${key}[${index}] references undeclared ${identityKey} identity ${binding.identity}`);
|
|
306
|
+
}
|
|
307
|
+
const normalizedRef = binding.ref.includes("://")
|
|
308
|
+
? binding.ref
|
|
309
|
+
: requireLowercase(binding.ref, `${key}[${index}]`, filePath);
|
|
310
|
+
if (!(0, refs_1.validatePortableOrUriRef)(normalizedRef)) {
|
|
311
|
+
throw formatError(filePath, `${key}[${index}] must reference a portable id, qid, or URI ref`);
|
|
312
|
+
}
|
|
313
|
+
const included = requireEveryRefKey
|
|
314
|
+
? refsByKey.every((refs) => refs.has(binding.ref))
|
|
315
|
+
: refsByKey.some((refs) => refs.has(binding.ref));
|
|
316
|
+
if (!included) {
|
|
317
|
+
const operator = requireEveryRefKey ? "each of" : "one of";
|
|
318
|
+
throw formatError(filePath, `${key}[${index}] ref ${binding.ref} must also appear in ${operator} ${refKeys.join(", ")}`);
|
|
319
|
+
}
|
|
320
|
+
const pair = `${binding.identity}\u0000${binding.ref}`;
|
|
321
|
+
if (seen.has(pair)) {
|
|
322
|
+
throw formatError(filePath, `${key}[${index}] duplicates ${binding.identity}=${binding.ref}`);
|
|
323
|
+
}
|
|
324
|
+
seen.add(pair);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
function validateLoopActionSets(frontmatter, filePath) {
|
|
328
|
+
const normalized = (key) => optionalList(frontmatter, key, filePath).map((value) => value.trim().toLowerCase());
|
|
329
|
+
const preApproved = new Set(normalized("pre_approved_actions"));
|
|
330
|
+
const approvalGated = new Set(normalized("approval_gated_actions"));
|
|
331
|
+
const required = normalized("required_actions");
|
|
332
|
+
const requested = normalized("requested_actions");
|
|
333
|
+
const prohibited = new Set(normalized("prohibited_actions"));
|
|
334
|
+
for (const action of preApproved) {
|
|
335
|
+
if (approvalGated.has(action)) {
|
|
336
|
+
throw formatError(filePath, `action ${action} cannot be both pre_approved_actions and approval_gated_actions`);
|
|
337
|
+
}
|
|
338
|
+
if (prohibited.has(action)) {
|
|
339
|
+
throw formatError(filePath, `action ${action} cannot be both pre_approved_actions and prohibited_actions`);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
for (const action of approvalGated) {
|
|
343
|
+
if (prohibited.has(action)) {
|
|
344
|
+
throw formatError(filePath, `action ${action} cannot be both approval_gated_actions and prohibited_actions`);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
for (const [key, actions] of [["required_actions", required], ["requested_actions", requested]]) {
|
|
348
|
+
for (const action of actions) {
|
|
349
|
+
if (!preApproved.has(action) && !approvalGated.has(action)) {
|
|
350
|
+
throw formatError(filePath, `${key} references undeclared action ${action}`);
|
|
351
|
+
}
|
|
352
|
+
if (prohibited.has(action)) {
|
|
353
|
+
throw formatError(filePath, `${key} cannot include prohibited action ${action}`);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
function validateLoopFrontmatter(type, frontmatter, filePath) {
|
|
359
|
+
if (type !== "loop") {
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
const loopMode = requireLowercase(expectString(frontmatter, "loop_mode", filePath), "loop_mode", filePath);
|
|
363
|
+
if (!LOOP_MODES.has(loopMode)) {
|
|
364
|
+
throw formatError(filePath, `loop_mode must be one of ${Array.from(LOOP_MODES).join(", ")}`);
|
|
365
|
+
}
|
|
366
|
+
const loopRole = requireLowercase(expectString(frontmatter, "loop_role", filePath), "loop_role", filePath);
|
|
367
|
+
if (!LOOP_ROLES.has(loopRole)) {
|
|
368
|
+
throw formatError(filePath, `loop_role must be one of ${Array.from(LOOP_ROLES).join(", ")}`);
|
|
369
|
+
}
|
|
370
|
+
const materializationMode = requireLowercase(expectString(frontmatter, "materialization_mode", filePath), "materialization_mode", filePath);
|
|
371
|
+
if (!LOOP_MATERIALIZATION_MODES.has(materializationMode)) {
|
|
372
|
+
throw formatError(filePath, `materialization_mode must be one of ${Array.from(LOOP_MATERIALIZATION_MODES).join(", ")}`);
|
|
373
|
+
}
|
|
374
|
+
const blockerPolicy = requireLowercase(expectString(frontmatter, "blocker_policy", filePath), "blocker_policy", filePath);
|
|
375
|
+
if (!LOOP_BLOCKER_POLICIES.has(blockerPolicy)) {
|
|
376
|
+
throw formatError(filePath, `blocker_policy must be one of ${Array.from(LOOP_BLOCKER_POLICIES).join(", ")}`);
|
|
377
|
+
}
|
|
378
|
+
expectString(frontmatter, "definition_of_done", filePath);
|
|
379
|
+
optionalString(frontmatter, "scope_description", filePath);
|
|
380
|
+
for (const key of [
|
|
381
|
+
"pre_run_questions",
|
|
382
|
+
"pre_approved_actions",
|
|
383
|
+
"approval_gated_actions",
|
|
384
|
+
"required_actions",
|
|
385
|
+
"requested_actions",
|
|
386
|
+
"prohibited_actions",
|
|
387
|
+
"evidence_lanes",
|
|
388
|
+
]) {
|
|
389
|
+
validateLoopStringList(frontmatter, key, filePath);
|
|
390
|
+
}
|
|
391
|
+
validateLoopActionSets(frontmatter, filePath);
|
|
392
|
+
for (const key of [
|
|
393
|
+
"scope_refs",
|
|
394
|
+
"template_refs",
|
|
395
|
+
"child_refs",
|
|
396
|
+
"lane_waiver_refs",
|
|
397
|
+
"run_refs",
|
|
398
|
+
"decision_refs",
|
|
399
|
+
"output_refs",
|
|
400
|
+
"approval_refs",
|
|
401
|
+
"evaluation_refs",
|
|
402
|
+
]) {
|
|
403
|
+
validateLoopRefList(frontmatter, key, filePath);
|
|
404
|
+
}
|
|
405
|
+
validateLoopBindingList(frontmatter, "question_answer_refs", "pre_run_questions", ["decision_refs"], filePath);
|
|
406
|
+
validateLoopBindingList(frontmatter, "action_approval_refs", "approval_gated_actions", ["approval_refs"], filePath);
|
|
407
|
+
validateLoopBindingList(frontmatter, "evidence_lane_refs", "evidence_lanes", ["run_refs", "output_refs", "evaluation_refs", "evidence_refs"], filePath);
|
|
408
|
+
validateLoopBindingList(frontmatter, "lane_waiver_decision_refs", "evidence_lanes", ["lane_waiver_refs", "decision_refs"], filePath, true);
|
|
409
|
+
validateLoopBindingList(frontmatter, "lane_waiver_approval_refs", "evidence_lanes", ["approval_refs"], filePath);
|
|
410
|
+
}
|
|
218
411
|
function extractGoalAttributes(type, frontmatter) {
|
|
219
412
|
if (type !== "goal") {
|
|
220
413
|
return {};
|
|
@@ -228,6 +421,19 @@ function extractGoalAttributes(type, frontmatter) {
|
|
|
228
421
|
}
|
|
229
422
|
return attributes;
|
|
230
423
|
}
|
|
424
|
+
function extractLoopAttributes(type, frontmatter) {
|
|
425
|
+
if (type !== "loop") {
|
|
426
|
+
return {};
|
|
427
|
+
}
|
|
428
|
+
const attributes = {};
|
|
429
|
+
for (const key of LOOP_ATTRIBUTE_KEYS) {
|
|
430
|
+
const value = frontmatter[key];
|
|
431
|
+
if (value !== undefined) {
|
|
432
|
+
attributes[key] = value;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
return attributes;
|
|
436
|
+
}
|
|
231
437
|
function requireTemplateSchema(type, templateSchemas, filePath) {
|
|
232
438
|
const schema = templateSchemas[type];
|
|
233
439
|
if (!schema) {
|
|
@@ -306,6 +512,7 @@ function parseNode(content, filePath, options) {
|
|
|
306
512
|
(0, agent_file_types_1.validateAgentFrontmatter)(type, frontmatter, filePath);
|
|
307
513
|
(0, archive_file_1.validateArchiveFrontmatter)(type, frontmatter, filePath);
|
|
308
514
|
validateGoalFrontmatter(type, frontmatter, filePath);
|
|
515
|
+
validateLoopFrontmatter(type, frontmatter, filePath);
|
|
309
516
|
const idValue = requireLowercase(expectString(frontmatter, "id", filePath), "id", filePath);
|
|
310
517
|
const id = isPortableType
|
|
311
518
|
? requirePortableIdFormat(idValue, "id", filePath)
|
|
@@ -376,6 +583,7 @@ function parseNode(content, filePath, options) {
|
|
|
376
583
|
});
|
|
377
584
|
const attributes = {
|
|
378
585
|
...extractGoalAttributes(type, frontmatter),
|
|
586
|
+
...extractLoopAttributes(type, frontmatter),
|
|
379
587
|
...(0, agent_file_types_1.extractAgentAttributes)(type, frontmatter),
|
|
380
588
|
...(0, archive_file_1.extractArchiveAttributes)(type, frontmatter),
|
|
381
589
|
};
|