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
package/dist/commands/archive.js
CHANGED
|
@@ -11,6 +11,8 @@ exports.runArchiveCompressCommand = runArchiveCompressCommand;
|
|
|
11
11
|
const fs_1 = __importDefault(require("fs"));
|
|
12
12
|
const path_1 = __importDefault(require("path"));
|
|
13
13
|
const config_1 = require("../core/config");
|
|
14
|
+
const filesystem_authority_1 = require("../core/filesystem_authority");
|
|
15
|
+
const workspace_path_1 = require("../core/workspace_path");
|
|
14
16
|
const frontmatter_1 = require("../graph/frontmatter");
|
|
15
17
|
const archive_integrity_1 = require("../graph/archive_integrity");
|
|
16
18
|
const indexer_1 = require("../graph/indexer");
|
|
@@ -21,7 +23,6 @@ const date_1 = require("../util/date");
|
|
|
21
23
|
const errors_1 = require("../util/errors");
|
|
22
24
|
const id_1 = require("../util/id");
|
|
23
25
|
const refs_1 = require("../util/refs");
|
|
24
|
-
const atomic_1 = require("../util/atomic");
|
|
25
26
|
const lock_1 = require("../util/lock");
|
|
26
27
|
const zip_1 = require("../util/zip");
|
|
27
28
|
const event_support_1 = require("./event_support");
|
|
@@ -173,10 +174,10 @@ function walkArchiveSidecars(root) {
|
|
|
173
174
|
function stringAttribute(value) {
|
|
174
175
|
return typeof value === "string" && value.trim().length > 0 ? value : undefined;
|
|
175
176
|
}
|
|
176
|
-
function writeArchiveSidecar(sidecarPath, frontmatter, body) {
|
|
177
|
+
function writeArchiveSidecar(root, sidecarPath, frontmatter, body) {
|
|
177
178
|
const lines = (0, frontmatter_1.formatFrontmatter)(frontmatter);
|
|
178
179
|
const content = ["---", ...lines, "---", body.trimStart()].join("\n");
|
|
179
|
-
(0,
|
|
180
|
+
(0, filesystem_authority_1.atomicReplaceContainedFile)({ root, relativePath: toPosixPath(path_1.default.relative(root, sidecarPath)) }, content.endsWith("\n") ? content : `${content}\n`);
|
|
180
181
|
}
|
|
181
182
|
function verifyArchiveSidecar(root, ws, sidecarPath) {
|
|
182
183
|
const relativePath = toPosixPath(path_1.default.relative(root, sidecarPath));
|
|
@@ -302,18 +303,28 @@ function runArchiveAddCommandLocked(options) {
|
|
|
302
303
|
const visibility = (0, visibility_1.normalizeVisibility)(options.visibility);
|
|
303
304
|
const today = (0, date_1.formatDate)(options.now ?? new Date());
|
|
304
305
|
const archiveDir = path_1.default.resolve(options.root, workspace.path, workspace.mdkg_dir, "archive", id);
|
|
306
|
+
const archiveRelativeDir = (0, workspace_path_1.workspaceDocumentRelativePath)(workspace.path, workspace.mdkg_dir, "archive", id);
|
|
305
307
|
const rawDir = path_1.default.join(archiveDir, "source");
|
|
306
308
|
const rawPath = path_1.default.join(rawDir, basename);
|
|
307
309
|
const zipPath = path_1.default.join(archiveDir, `${basename}.zip`);
|
|
308
310
|
const sidecarPath = path_1.default.join(archiveDir, `${basename}.md`);
|
|
309
|
-
|
|
311
|
+
const sidecarRelativePath = `${archiveRelativeDir}/${basename}.md`;
|
|
312
|
+
const rawRelativePath = `${archiveRelativeDir}/source/${basename}`;
|
|
313
|
+
const zipRelativePath = `${archiveRelativeDir}/${basename}.zip`;
|
|
314
|
+
if ((0, filesystem_authority_1.containedPathExists)({ root: options.root, relativePath: sidecarRelativePath })) {
|
|
310
315
|
throw new errors_1.UsageError(`archive sidecar already exists: ${path_1.default.relative(options.root, sidecarPath)}`);
|
|
311
316
|
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
317
|
+
for (const [relativePath, operation] of [
|
|
318
|
+
[rawRelativePath, "create"],
|
|
319
|
+
[zipRelativePath, "replace"],
|
|
320
|
+
[sidecarRelativePath, "create"],
|
|
321
|
+
]) {
|
|
322
|
+
(0, filesystem_authority_1.withContainedPathSink)({ root: options.root, relativePath, operation, createParents: true }, () => undefined);
|
|
323
|
+
}
|
|
324
|
+
const rawData = fs_1.default.readFileSync(sourcePath);
|
|
325
|
+
(0, filesystem_authority_1.writeContainedFileExclusive)({ root: options.root, relativePath: rawRelativePath }, rawData);
|
|
315
326
|
const zipData = (0, zip_1.createDeterministicZip)(basename, rawData);
|
|
316
|
-
(0,
|
|
327
|
+
(0, filesystem_authority_1.atomicReplaceContainedFile)({ root: options.root, relativePath: zipRelativePath }, zipData);
|
|
317
328
|
const frontmatter = {
|
|
318
329
|
id,
|
|
319
330
|
type: "archive",
|
|
@@ -339,7 +350,7 @@ function runArchiveAddCommandLocked(options) {
|
|
|
339
350
|
created: today,
|
|
340
351
|
updated: today,
|
|
341
352
|
};
|
|
342
|
-
writeArchiveSidecar(sidecarPath, frontmatter, ["# Archive Entry", "", `Archived ${archiveKind}: ${basename}`, "", "# Provenance", "", "Copied from local workspace input."].join("\n"));
|
|
353
|
+
writeArchiveSidecar(options.root, sidecarPath, frontmatter, ["# Archive Entry", "", `Archived ${archiveKind}: ${basename}`, "", "# Provenance", "", "Copied from local workspace input."].join("\n"));
|
|
343
354
|
maybeReindex(options.root);
|
|
344
355
|
(0, event_support_1.appendAutomaticEvent)({
|
|
345
356
|
root: options.root,
|
|
@@ -438,13 +449,19 @@ function runArchiveCompressCommandLocked(options) {
|
|
|
438
449
|
const today = (0, date_1.formatDate)(options.now ?? new Date());
|
|
439
450
|
for (const node of nodes) {
|
|
440
451
|
const { sidecarPath, rawPath, zipPath } = archiveNodePaths(options.root, node);
|
|
441
|
-
|
|
452
|
+
const rawRelativePath = toPosixPath(path_1.default.relative(options.root, rawPath));
|
|
453
|
+
const zipRelativePath = toPosixPath(path_1.default.relative(options.root, zipPath));
|
|
454
|
+
const sidecarRelativePath = toPosixPath(path_1.default.relative(options.root, sidecarPath));
|
|
455
|
+
for (const relativePath of [rawRelativePath, zipRelativePath, sidecarRelativePath]) {
|
|
456
|
+
(0, filesystem_authority_1.withContainedPathSink)({ root: options.root, relativePath, operation: relativePath === rawRelativePath ? "read" : "replace" }, () => undefined);
|
|
457
|
+
}
|
|
458
|
+
if (!(0, filesystem_authority_1.containedPathExists)({ root: options.root, relativePath: rawRelativePath })) {
|
|
442
459
|
throw new errors_1.NotFoundError(`raw archive file missing for ${node.qid}: ${path_1.default.relative(options.root, rawPath)}`);
|
|
443
460
|
}
|
|
444
|
-
const rawData =
|
|
461
|
+
const rawData = (0, filesystem_authority_1.readContainedFile)({ root: options.root, relativePath: rawRelativePath }, null);
|
|
445
462
|
const zipData = (0, zip_1.createDeterministicZip)(path_1.default.basename(rawPath), rawData);
|
|
446
|
-
(0,
|
|
447
|
-
const parsed = (0, frontmatter_1.parseFrontmatter)(
|
|
463
|
+
(0, filesystem_authority_1.atomicReplaceContainedFile)({ root: options.root, relativePath: zipRelativePath }, zipData);
|
|
464
|
+
const parsed = (0, frontmatter_1.parseFrontmatter)((0, filesystem_authority_1.readContainedFile)({ root: options.root, relativePath: sidecarRelativePath }), sidecarPath);
|
|
448
465
|
const nextFrontmatter = {
|
|
449
466
|
...parsed.frontmatter,
|
|
450
467
|
byte_size: String(rawData.length),
|
|
@@ -453,7 +470,7 @@ function runArchiveCompressCommandLocked(options) {
|
|
|
453
470
|
ingest_status: "compressed",
|
|
454
471
|
updated: today,
|
|
455
472
|
};
|
|
456
|
-
writeArchiveSidecar(sidecarPath, nextFrontmatter, parsed.body);
|
|
473
|
+
writeArchiveSidecar(options.root, sidecarPath, nextFrontmatter, parsed.body);
|
|
457
474
|
updated.push({
|
|
458
475
|
workspace: node.ws,
|
|
459
476
|
id: node.id,
|
package/dist/commands/bundle.js
CHANGED
|
@@ -3,6 +3,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.validateBundleManifest = validateBundleManifest;
|
|
7
|
+
exports.bundlePayloadErrors = bundlePayloadErrors;
|
|
6
8
|
exports.sha256Buffer = sha256Buffer;
|
|
7
9
|
exports.buildBundle = buildBundle;
|
|
8
10
|
exports.parseBundle = parseBundle;
|
|
@@ -16,6 +18,8 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
16
18
|
const path_1 = __importDefault(require("path"));
|
|
17
19
|
const child_process_1 = require("child_process");
|
|
18
20
|
const config_1 = require("../core/config");
|
|
21
|
+
const filesystem_authority_1 = require("../core/filesystem_authority");
|
|
22
|
+
const atomic_1 = require("../util/atomic");
|
|
19
23
|
const capabilities_indexer_1 = require("../graph/capabilities_indexer");
|
|
20
24
|
const subgraphs_1 = require("../graph/subgraphs");
|
|
21
25
|
const indexer_1 = require("../graph/indexer");
|
|
@@ -31,6 +35,168 @@ const INDEX_ENTRY_PATHS = {
|
|
|
31
35
|
skills: ".mdkg/index/skills.json",
|
|
32
36
|
capabilities: ".mdkg/index/capabilities.json",
|
|
33
37
|
};
|
|
38
|
+
const REQUIRED_INDEX_PATHS = Object.values(INDEX_ENTRY_PATHS);
|
|
39
|
+
const SHA256_PATTERN = /^sha256:[a-f0-9]{64}$/;
|
|
40
|
+
function isRecord(value) {
|
|
41
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
42
|
+
}
|
|
43
|
+
function manifestError(message) {
|
|
44
|
+
throw new errors_1.ValidationError(`bundle manifest invalid: ${message}`);
|
|
45
|
+
}
|
|
46
|
+
function manifestString(value, field) {
|
|
47
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
48
|
+
manifestError(`${field} must be a non-empty string`);
|
|
49
|
+
}
|
|
50
|
+
return value;
|
|
51
|
+
}
|
|
52
|
+
function manifestHash(value, field) {
|
|
53
|
+
const hash = manifestString(value, field);
|
|
54
|
+
if (!SHA256_PATTERN.test(hash)) {
|
|
55
|
+
manifestError(`${field} must be a sha256 digest`);
|
|
56
|
+
}
|
|
57
|
+
return hash;
|
|
58
|
+
}
|
|
59
|
+
function safeManifestPath(value, field) {
|
|
60
|
+
const candidate = manifestString(value, field).replace(/\\/g, "/");
|
|
61
|
+
const parts = candidate.split("/");
|
|
62
|
+
if (path_1.default.posix.isAbsolute(candidate) || parts.some((part) => part === "" || part === "." || part === "..")) {
|
|
63
|
+
manifestError(`${field} must be a safe relative path`);
|
|
64
|
+
}
|
|
65
|
+
return candidate;
|
|
66
|
+
}
|
|
67
|
+
function validateBundleManifest(value) {
|
|
68
|
+
if (!isRecord(value))
|
|
69
|
+
manifestError("root must be an object");
|
|
70
|
+
if (value.manifest_version !== 1)
|
|
71
|
+
manifestError("manifest_version must be 1");
|
|
72
|
+
if (value.tool !== "mdkg")
|
|
73
|
+
manifestError("tool must be mdkg");
|
|
74
|
+
const mdkgVersion = manifestString(value.mdkg_version, "mdkg_version");
|
|
75
|
+
if (value.profile !== "private" && value.profile !== "public") {
|
|
76
|
+
manifestError("profile must be private or public");
|
|
77
|
+
}
|
|
78
|
+
if (!Array.isArray(value.selected_workspaces) || value.selected_workspaces.length === 0) {
|
|
79
|
+
manifestError("selected_workspaces must be a non-empty string array");
|
|
80
|
+
}
|
|
81
|
+
const selectedWorkspaces = value.selected_workspaces.map((item, index) => manifestString(item, `selected_workspaces[${index}]`));
|
|
82
|
+
if (new Set(selectedWorkspaces).size !== selectedWorkspaces.length) {
|
|
83
|
+
manifestError("selected_workspaces must not contain duplicates");
|
|
84
|
+
}
|
|
85
|
+
if (!isRecord(value.source))
|
|
86
|
+
manifestError("source must be an object");
|
|
87
|
+
const repo = manifestString(value.source.repo, "source.repo");
|
|
88
|
+
const gitHead = value.source.git_head;
|
|
89
|
+
if (gitHead !== null && (typeof gitHead !== "string" || !/^[a-f0-9]{40}$/.test(gitHead))) {
|
|
90
|
+
manifestError("source.git_head must be null or a 40-character lowercase Git SHA");
|
|
91
|
+
}
|
|
92
|
+
if (typeof value.source.dirty !== "boolean")
|
|
93
|
+
manifestError("source.dirty must be boolean");
|
|
94
|
+
const sourceTreeHash = manifestHash(value.source_tree_hash, "source_tree_hash");
|
|
95
|
+
const bundleHash = manifestHash(value.bundle_hash, "bundle_hash");
|
|
96
|
+
if (!Number.isSafeInteger(value.file_count) || Number(value.file_count) < 0) {
|
|
97
|
+
manifestError("file_count must be a non-negative safe integer");
|
|
98
|
+
}
|
|
99
|
+
if (!isRecord(value.index_hashes))
|
|
100
|
+
manifestError("index_hashes must be an object");
|
|
101
|
+
const indexHashes = {};
|
|
102
|
+
for (const [rawPath, rawHash] of Object.entries(value.index_hashes)) {
|
|
103
|
+
const indexPath = safeManifestPath(rawPath, "index_hashes key");
|
|
104
|
+
indexHashes[indexPath] = manifestHash(rawHash, `index_hashes.${indexPath}`);
|
|
105
|
+
}
|
|
106
|
+
for (const requiredPath of REQUIRED_INDEX_PATHS) {
|
|
107
|
+
if (!indexHashes[requiredPath])
|
|
108
|
+
manifestError(`index_hashes missing required index ${requiredPath}`);
|
|
109
|
+
}
|
|
110
|
+
if (!Array.isArray(value.files))
|
|
111
|
+
manifestError("files must be an array");
|
|
112
|
+
const seenPaths = new Set();
|
|
113
|
+
const files = value.files.map((rawFile, index) => {
|
|
114
|
+
if (!isRecord(rawFile))
|
|
115
|
+
manifestError(`files[${index}] must be an object`);
|
|
116
|
+
const filePath = safeManifestPath(rawFile.path, `files[${index}].path`);
|
|
117
|
+
if (seenPaths.has(filePath))
|
|
118
|
+
manifestError(`duplicate file path: ${filePath}`);
|
|
119
|
+
seenPaths.add(filePath);
|
|
120
|
+
if (rawFile.kind !== "authored" && rawFile.kind !== "archive_cache" && rawFile.kind !== "generated_index") {
|
|
121
|
+
manifestError(`files[${index}].kind is invalid`);
|
|
122
|
+
}
|
|
123
|
+
if (!Number.isSafeInteger(rawFile.size) || Number(rawFile.size) < 0) {
|
|
124
|
+
manifestError(`files[${index}].size must be a non-negative safe integer`);
|
|
125
|
+
}
|
|
126
|
+
const workspace = rawFile.workspace === undefined
|
|
127
|
+
? undefined
|
|
128
|
+
: manifestString(rawFile.workspace, `files[${index}].workspace`);
|
|
129
|
+
const visibility = rawFile.visibility;
|
|
130
|
+
if (visibility !== undefined && visibility !== "private" && visibility !== "internal" && visibility !== "public") {
|
|
131
|
+
manifestError(`files[${index}].visibility is invalid`);
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
path: filePath,
|
|
135
|
+
kind: rawFile.kind,
|
|
136
|
+
...(workspace ? { workspace } : {}),
|
|
137
|
+
...(visibility ? { visibility } : {}),
|
|
138
|
+
size: Number(rawFile.size),
|
|
139
|
+
sha256: manifestHash(rawFile.sha256, `files[${index}].sha256`),
|
|
140
|
+
};
|
|
141
|
+
});
|
|
142
|
+
if (Number(value.file_count) !== files.length)
|
|
143
|
+
manifestError("file_count does not match files length");
|
|
144
|
+
for (const requiredPath of REQUIRED_INDEX_PATHS) {
|
|
145
|
+
const row = files.find((file) => file.path === requiredPath);
|
|
146
|
+
if (!row || row.kind !== "generated_index") {
|
|
147
|
+
manifestError(`files missing required generated index ${requiredPath}`);
|
|
148
|
+
}
|
|
149
|
+
if (row.sha256 !== indexHashes[requiredPath]) {
|
|
150
|
+
manifestError(`index hash does not match file row for ${requiredPath}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return {
|
|
154
|
+
manifest_version: 1,
|
|
155
|
+
tool: "mdkg",
|
|
156
|
+
mdkg_version: mdkgVersion,
|
|
157
|
+
profile: value.profile,
|
|
158
|
+
selected_workspaces: selectedWorkspaces,
|
|
159
|
+
source: { repo, git_head: gitHead, dirty: value.source.dirty },
|
|
160
|
+
source_tree_hash: sourceTreeHash,
|
|
161
|
+
bundle_hash: bundleHash,
|
|
162
|
+
file_count: files.length,
|
|
163
|
+
index_hashes: indexHashes,
|
|
164
|
+
files,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
function bundlePayloadErrors(entries, manifest) {
|
|
168
|
+
const errors = [];
|
|
169
|
+
const manifestPaths = new Set(manifest.files.map((file) => file.path));
|
|
170
|
+
for (const entryPath of entries.keys()) {
|
|
171
|
+
if (entryPath !== MANIFEST_ENTRY && !manifestPaths.has(entryPath))
|
|
172
|
+
errors.push(`unexpected bundle entry: ${entryPath}`);
|
|
173
|
+
}
|
|
174
|
+
for (const file of manifest.files) {
|
|
175
|
+
const data = entries.get(file.path);
|
|
176
|
+
if (!data) {
|
|
177
|
+
errors.push(`missing bundle entry: ${file.path}`);
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
if (sha256Buffer(data) !== file.sha256)
|
|
181
|
+
errors.push(`hash mismatch for ${file.path}`);
|
|
182
|
+
if (data.length !== file.size)
|
|
183
|
+
errors.push(`size mismatch for ${file.path}`);
|
|
184
|
+
}
|
|
185
|
+
for (const requiredPath of REQUIRED_INDEX_PATHS) {
|
|
186
|
+
const data = entries.get(requiredPath);
|
|
187
|
+
if (!data)
|
|
188
|
+
continue;
|
|
189
|
+
if (sha256Buffer(data) !== manifest.index_hashes[requiredPath]) {
|
|
190
|
+
errors.push(`generated index hash mismatch: ${requiredPath}`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (hashManifestFiles(manifest.files.filter((file) => file.kind !== "generated_index")) !== manifest.source_tree_hash) {
|
|
194
|
+
errors.push("source_tree_hash mismatch");
|
|
195
|
+
}
|
|
196
|
+
if (hashManifestFiles(manifest.files) !== manifest.bundle_hash)
|
|
197
|
+
errors.push("bundle_hash mismatch");
|
|
198
|
+
return Array.from(new Set(errors));
|
|
199
|
+
}
|
|
34
200
|
function toPosixPath(value) {
|
|
35
201
|
return value.split(path_1.default.sep).join("/");
|
|
36
202
|
}
|
|
@@ -519,9 +685,8 @@ function buildBundle(options) {
|
|
|
519
685
|
};
|
|
520
686
|
}
|
|
521
687
|
function parseBundle(bundlePath) {
|
|
522
|
-
const zip = fs_1.default.readFileSync(bundlePath);
|
|
523
688
|
const entries = new Map();
|
|
524
|
-
for (const entry of (0, zip_1.
|
|
689
|
+
for (const entry of (0, zip_1.readZipFileEntries)(bundlePath)) {
|
|
525
690
|
entries.set(entry.name, entry.data);
|
|
526
691
|
}
|
|
527
692
|
const manifestData = entries.get(MANIFEST_ENTRY);
|
|
@@ -529,10 +694,7 @@ function parseBundle(bundlePath) {
|
|
|
529
694
|
throw new errors_1.ValidationError("bundle manifest missing");
|
|
530
695
|
}
|
|
531
696
|
try {
|
|
532
|
-
return {
|
|
533
|
-
entries,
|
|
534
|
-
manifest: JSON.parse(manifestData.toString("utf8")),
|
|
535
|
-
};
|
|
697
|
+
return { entries, manifest: validateBundleManifest(JSON.parse(manifestData.toString("utf8"))) };
|
|
536
698
|
}
|
|
537
699
|
catch (err) {
|
|
538
700
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -562,25 +724,8 @@ function verifyBundle(root, bundlePath) {
|
|
|
562
724
|
stale_paths: [],
|
|
563
725
|
};
|
|
564
726
|
}
|
|
565
|
-
|
|
566
|
-
for (const entryPath of entries.keys()) {
|
|
567
|
-
if (entryPath !== MANIFEST_ENTRY && !manifestPaths.has(entryPath)) {
|
|
568
|
-
errors.push(`unexpected bundle entry: ${entryPath}`);
|
|
569
|
-
}
|
|
570
|
-
}
|
|
727
|
+
errors.push(...bundlePayloadErrors(entries, manifest));
|
|
571
728
|
for (const file of manifest.files) {
|
|
572
|
-
const data = entries.get(file.path);
|
|
573
|
-
if (!data) {
|
|
574
|
-
errors.push(`missing bundle entry: ${file.path}`);
|
|
575
|
-
continue;
|
|
576
|
-
}
|
|
577
|
-
const hash = sha256Buffer(data);
|
|
578
|
-
if (hash !== file.sha256) {
|
|
579
|
-
errors.push(`hash mismatch for ${file.path}`);
|
|
580
|
-
}
|
|
581
|
-
if (data.length !== file.size) {
|
|
582
|
-
errors.push(`size mismatch for ${file.path}`);
|
|
583
|
-
}
|
|
584
729
|
if (file.kind !== "generated_index") {
|
|
585
730
|
const sourcePath = path_1.default.resolve(root, file.path);
|
|
586
731
|
if (!fs_1.default.existsSync(sourcePath)) {
|
|
@@ -592,24 +737,6 @@ function verifyBundle(root, bundlePath) {
|
|
|
592
737
|
}
|
|
593
738
|
}
|
|
594
739
|
}
|
|
595
|
-
for (const [indexPath, expectedHash] of Object.entries(manifest.index_hashes)) {
|
|
596
|
-
const data = entries.get(indexPath);
|
|
597
|
-
if (!data) {
|
|
598
|
-
errors.push(`missing generated index: ${indexPath}`);
|
|
599
|
-
continue;
|
|
600
|
-
}
|
|
601
|
-
if (sha256Buffer(data) !== expectedHash) {
|
|
602
|
-
errors.push(`generated index hash mismatch: ${indexPath}`);
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
const computedSourceHash = hashManifestFiles(manifest.files.filter((file) => file.kind !== "generated_index"));
|
|
606
|
-
if (computedSourceHash !== manifest.source_tree_hash) {
|
|
607
|
-
errors.push("source_tree_hash mismatch");
|
|
608
|
-
}
|
|
609
|
-
const computedBundleHash = hashManifestFiles(manifest.files);
|
|
610
|
-
if (computedBundleHash !== manifest.bundle_hash) {
|
|
611
|
-
errors.push("bundle_hash mismatch");
|
|
612
|
-
}
|
|
613
740
|
const currentHead = gitOutput(root, ["rev-parse", "HEAD"]);
|
|
614
741
|
if (manifest.source.git_head && currentHead && manifest.source.git_head !== currentHead) {
|
|
615
742
|
stalePaths.push("git:HEAD");
|
|
@@ -645,8 +772,17 @@ function bundleSummary(manifest, bundlePath, zipSha256) {
|
|
|
645
772
|
}
|
|
646
773
|
function runBundleCreateCommand(options) {
|
|
647
774
|
const result = buildBundle(options);
|
|
648
|
-
|
|
649
|
-
|
|
775
|
+
if (options.output && path_1.default.isAbsolute(options.output)) {
|
|
776
|
+
const external = (0, filesystem_authority_1.authorizeOperatorSelectedExternalPath)({
|
|
777
|
+
operation: "replace",
|
|
778
|
+
path: options.output,
|
|
779
|
+
operatorSelected: true,
|
|
780
|
+
});
|
|
781
|
+
(0, atomic_1.atomicWriteFile)(external.absolutePath, result.zip);
|
|
782
|
+
}
|
|
783
|
+
else {
|
|
784
|
+
(0, filesystem_authority_1.atomicReplaceContainedFile)({ root: options.root, relativePath: path_1.default.relative(options.root, result.outputPath).split(path_1.default.sep).join("/") }, result.zip);
|
|
785
|
+
}
|
|
650
786
|
const receipt = {
|
|
651
787
|
action: "created",
|
|
652
788
|
...bundleSummary(result.manifest, path_1.default.relative(options.root, result.outputPath), result.zipSha256),
|
|
@@ -39,6 +39,7 @@ function loadCapabilityRecords(options) {
|
|
|
39
39
|
config,
|
|
40
40
|
useCache: !options.noCache,
|
|
41
41
|
allowReindex: !options.noReindex,
|
|
42
|
+
persistReindex: false,
|
|
42
43
|
});
|
|
43
44
|
if (stale && !rebuilt && !options.noCache) {
|
|
44
45
|
console.error("warning: capabilities index is stale; run mdkg index to refresh");
|
|
@@ -6,15 +6,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.CHECKPOINT_KINDS = void 0;
|
|
7
7
|
exports.createCheckpoint = createCheckpoint;
|
|
8
8
|
exports.runCheckpointNewCommand = runCheckpointNewCommand;
|
|
9
|
-
const fs_1 = __importDefault(require("fs"));
|
|
10
9
|
const path_1 = __importDefault(require("path"));
|
|
11
10
|
const config_1 = require("../core/config");
|
|
11
|
+
const filesystem_authority_1 = require("../core/filesystem_authority");
|
|
12
|
+
const workspace_path_1 = require("../core/workspace_path");
|
|
12
13
|
const index_cache_1 = require("../graph/index_cache");
|
|
13
14
|
const loader_1 = require("../templates/loader");
|
|
14
15
|
const date_1 = require("../util/date");
|
|
15
16
|
const errors_1 = require("../util/errors");
|
|
16
17
|
const id_1 = require("../util/id");
|
|
17
|
-
const atomic_1 = require("../util/atomic");
|
|
18
18
|
const lock_1 = require("../util/lock");
|
|
19
19
|
const sqlite_index_1 = require("../graph/sqlite_index");
|
|
20
20
|
const event_support_1 = require("./event_support");
|
|
@@ -253,7 +253,8 @@ function createCheckpointLocked(options) {
|
|
|
253
253
|
const wsEntry = config.workspaces[ws];
|
|
254
254
|
const workDir = path_1.default.resolve(options.root, wsEntry.path, wsEntry.mdkg_dir, "work");
|
|
255
255
|
const filePath = path_1.default.join(workDir, fileName);
|
|
256
|
-
|
|
256
|
+
const relativeFilePath = (0, workspace_path_1.workspaceDocumentRelativePath)(wsEntry.path, wsEntry.mdkg_dir, "work", fileName);
|
|
257
|
+
if ((0, filesystem_authority_1.containedPathExists)({ root: options.root, relativePath: relativeFilePath })) {
|
|
257
258
|
throw new errors_1.UsageError(`checkpoint file already exists: ${path_1.default.relative(options.root, filePath)}`);
|
|
258
259
|
}
|
|
259
260
|
const relates = parseCsvList(options.relates).map((value) => normalizeIdRef(value, "--relates"));
|
|
@@ -279,9 +280,9 @@ function createCheckpointLocked(options) {
|
|
|
279
280
|
relates,
|
|
280
281
|
scope,
|
|
281
282
|
});
|
|
282
|
-
const rendered = replaceRenderedBody(content, checkpointBody(kind));
|
|
283
|
+
const rendered = replaceRenderedBody(content, options.body ?? checkpointBody(kind));
|
|
283
284
|
try {
|
|
284
|
-
(0,
|
|
285
|
+
(0, filesystem_authority_1.writeContainedFileExclusive)({ root: options.root, relativePath: relativeFilePath }, rendered);
|
|
285
286
|
}
|
|
286
287
|
catch (err) {
|
|
287
288
|
const code = typeof err === "object" && err !== null && "code" in err ? String(err.code) : "";
|
|
@@ -12,9 +12,10 @@ exports.normalizeEventRefList = normalizeEventRefList;
|
|
|
12
12
|
exports.normalizeEventStringList = normalizeEventStringList;
|
|
13
13
|
exports.appendEvent = appendEvent;
|
|
14
14
|
exports.appendAutomaticEvent = appendAutomaticEvent;
|
|
15
|
-
const fs_1 = __importDefault(require("fs"));
|
|
16
15
|
const path_1 = __importDefault(require("path"));
|
|
17
16
|
const config_1 = require("../core/config");
|
|
17
|
+
const filesystem_authority_1 = require("../core/filesystem_authority");
|
|
18
|
+
const workspace_path_1 = require("../core/workspace_path");
|
|
18
19
|
const errors_1 = require("../util/errors");
|
|
19
20
|
function normalizeWorkspaceForEvents(config, raw) {
|
|
20
21
|
const normalized = (raw ?? "root").toLowerCase();
|
|
@@ -33,6 +34,13 @@ function resolveEventsPath(root, config, ws = "root") {
|
|
|
33
34
|
}
|
|
34
35
|
return path_1.default.resolve(root, entry.path, entry.mdkg_dir, "work", "events", "events.jsonl");
|
|
35
36
|
}
|
|
37
|
+
function resolveEventsRelativePath(config, ws) {
|
|
38
|
+
const entry = config.workspaces[ws];
|
|
39
|
+
if (!entry) {
|
|
40
|
+
throw new errors_1.NotFoundError(`workspace not found: ${ws}`);
|
|
41
|
+
}
|
|
42
|
+
return (0, workspace_path_1.workspaceDocumentRelativePath)(entry.path, entry.mdkg_dir, "work", "events", "events.jsonl");
|
|
43
|
+
}
|
|
36
44
|
function formatRunIdTimestamp(now) {
|
|
37
45
|
const iso = now.toISOString().replace(/[-:]/g, "").replace(/\./g, "").replace("Z", "Z");
|
|
38
46
|
return iso;
|
|
@@ -43,16 +51,16 @@ function createLocalRunId(kind, now = new Date()) {
|
|
|
43
51
|
}
|
|
44
52
|
function isEventLoggingEnabled(root, config, ws) {
|
|
45
53
|
const normalizedWs = normalizeWorkspaceForEvents(config, ws);
|
|
46
|
-
return
|
|
54
|
+
return (0, filesystem_authority_1.containedPathExists)({ root, relativePath: resolveEventsRelativePath(config, normalizedWs) });
|
|
47
55
|
}
|
|
48
56
|
function ensureEventsEnabled(options) {
|
|
49
57
|
const config = (0, config_1.loadConfig)(options.root);
|
|
50
58
|
const ws = normalizeWorkspaceForEvents(config, options.ws);
|
|
51
59
|
const eventsPath = resolveEventsPath(options.root, config, ws);
|
|
60
|
+
const relativePath = resolveEventsRelativePath(config, ws);
|
|
52
61
|
let created = false;
|
|
53
|
-
if (!
|
|
54
|
-
|
|
55
|
-
fs_1.default.writeFileSync(eventsPath, "", "utf8");
|
|
62
|
+
if (!(0, filesystem_authority_1.containedPathExists)({ root: options.root, relativePath })) {
|
|
63
|
+
(0, filesystem_authority_1.writeContainedFileExclusive)({ root: options.root, relativePath }, "");
|
|
56
64
|
created = true;
|
|
57
65
|
}
|
|
58
66
|
return { ws, eventsPath, created };
|
|
@@ -102,10 +110,11 @@ function appendEvent(options) {
|
|
|
102
110
|
throw new errors_1.UsageError("--refs requires at least one id or qid");
|
|
103
111
|
}
|
|
104
112
|
const eventsPath = resolveEventsPath(options.root, config, record.workspace);
|
|
105
|
-
|
|
113
|
+
const relativePath = resolveEventsRelativePath(config, record.workspace);
|
|
114
|
+
if (!(0, filesystem_authority_1.containedPathExists)({ root: options.root, relativePath })) {
|
|
106
115
|
throw new errors_1.NotFoundError(`events.jsonl is missing for workspace ${record.workspace}; run \`mdkg event enable --ws ${record.workspace}\``);
|
|
107
116
|
}
|
|
108
|
-
|
|
117
|
+
(0, filesystem_authority_1.appendContainedFile)({ root: options.root, relativePath }, `${JSON.stringify(record)}\n`);
|
|
109
118
|
return record;
|
|
110
119
|
}
|
|
111
120
|
function appendAutomaticEvent(options) {
|
package/dist/commands/fix.js
CHANGED
|
@@ -13,6 +13,7 @@ const child_process_1 = require("child_process");
|
|
|
13
13
|
const fs_1 = __importDefault(require("fs"));
|
|
14
14
|
const path_1 = __importDefault(require("path"));
|
|
15
15
|
const config_1 = require("../core/config");
|
|
16
|
+
const filesystem_authority_1 = require("../core/filesystem_authority");
|
|
16
17
|
const capabilities_indexer_1 = require("../graph/capabilities_indexer");
|
|
17
18
|
const capabilities_index_cache_1 = require("../graph/capabilities_index_cache");
|
|
18
19
|
const staleness_1 = require("../graph/staleness");
|
|
@@ -26,7 +27,6 @@ const template_schema_1 = require("../graph/template_schema");
|
|
|
26
27
|
const workspace_files_1 = require("../graph/workspace_files");
|
|
27
28
|
const errors_1 = require("../util/errors");
|
|
28
29
|
const refs_1 = require("../util/refs");
|
|
29
|
-
const atomic_1 = require("../util/atomic");
|
|
30
30
|
const lock_1 = require("../util/lock");
|
|
31
31
|
const index_1 = require("./index");
|
|
32
32
|
const FAMILY_VALUES = new Set(["index", "refs", "ids", "all"]);
|
|
@@ -1211,12 +1211,12 @@ function applyDuplicateIdChange(root, change) {
|
|
|
1211
1211
|
if (!fromId || !toId) {
|
|
1212
1212
|
throw new errors_1.UsageError(`fix apply change ${change.id} is missing duplicate id rewrite details`);
|
|
1213
1213
|
}
|
|
1214
|
-
const current =
|
|
1214
|
+
const current = (0, filesystem_authority_1.readContainedFile)({ root, relativePath });
|
|
1215
1215
|
const rewritten = rewriteIdInNodeContent(current, fromId, toId);
|
|
1216
1216
|
if (rewritten === current) {
|
|
1217
1217
|
throw new errors_1.UsageError(`fix apply change ${change.id} produced no file changes`);
|
|
1218
1218
|
}
|
|
1219
|
-
(0,
|
|
1219
|
+
(0, filesystem_authority_1.atomicReplaceContainedFile)({ root, relativePath }, rewritten);
|
|
1220
1220
|
const afterDetails = change.after;
|
|
1221
1221
|
const safeReferenceRewrites = Array.isArray(afterDetails.safe_reference_rewrites)
|
|
1222
1222
|
? afterDetails.safe_reference_rewrites
|
|
@@ -1227,13 +1227,13 @@ function applyDuplicateIdChange(root, change) {
|
|
|
1227
1227
|
continue;
|
|
1228
1228
|
}
|
|
1229
1229
|
const rewriteAbs = path_1.default.resolve(root, rewrite.path);
|
|
1230
|
-
if (!isInsideRoot(root, rewriteAbs) || !
|
|
1230
|
+
if (!isInsideRoot(root, rewriteAbs) || !(0, filesystem_authority_1.containedPathExists)({ root, relativePath: rewrite.path })) {
|
|
1231
1231
|
continue;
|
|
1232
1232
|
}
|
|
1233
|
-
const rewriteCurrent =
|
|
1233
|
+
const rewriteCurrent = (0, filesystem_authority_1.readContainedFile)({ root, relativePath: rewrite.path });
|
|
1234
1234
|
const rewriteNext = rewriteCurrent.split(rewrite.from).join(rewrite.to);
|
|
1235
1235
|
if (rewriteNext !== rewriteCurrent) {
|
|
1236
|
-
(0,
|
|
1236
|
+
(0, filesystem_authority_1.atomicReplaceContainedFile)({ root, relativePath: rewrite.path }, rewriteNext);
|
|
1237
1237
|
touchedPaths.add(rewrite.path);
|
|
1238
1238
|
}
|
|
1239
1239
|
}
|
|
@@ -1273,8 +1273,11 @@ function applyGitStageDuplicateIdChange(root, change) {
|
|
|
1273
1273
|
throw new errors_1.UsageError(`fix apply refused path outside repo while resolving ${conflictPath}`);
|
|
1274
1274
|
}
|
|
1275
1275
|
const rewrittenDuplicate = rewriteIdInNodeContent(duplicateContent, fromId, candidateId);
|
|
1276
|
-
(
|
|
1277
|
-
|
|
1276
|
+
for (const relativePath of [conflictPath, candidatePath]) {
|
|
1277
|
+
(0, filesystem_authority_1.withContainedPathSink)({ root, relativePath, operation: "replace", createParents: true }, () => undefined);
|
|
1278
|
+
}
|
|
1279
|
+
(0, filesystem_authority_1.atomicReplaceContainedFile)({ root, relativePath: conflictPath }, canonicalContent);
|
|
1280
|
+
(0, filesystem_authority_1.atomicReplaceContainedFile)({ root, relativePath: candidatePath }, rewrittenDuplicate);
|
|
1278
1281
|
runGitStrict(root, ["add", "--", conflictPath, candidatePath]);
|
|
1279
1282
|
return {
|
|
1280
1283
|
id: change.id,
|