mdkg 0.1.2 → 0.1.3
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 +25 -0
- package/README.md +22 -7
- package/dist/cli.js +3 -0
- package/dist/commands/archive.js +20 -8
- package/dist/commands/bundle_import.js +18 -6
- package/dist/commands/checkpoint.js +31 -5
- package/dist/commands/doctor.js +46 -9
- package/dist/commands/index.js +12 -23
- package/dist/commands/init.js +7 -1
- package/dist/commands/new.js +33 -7
- package/dist/commands/task.js +20 -6
- package/dist/commands/validate.js +6 -0
- package/dist/commands/work.js +43 -11
- package/dist/core/config.js +15 -0
- package/dist/graph/bundle_imports.js +2 -2
- package/dist/graph/capabilities_index_cache.js +2 -2
- package/dist/graph/index_cache.js +2 -2
- package/dist/graph/reindex.js +46 -0
- package/dist/graph/skills_index_cache.js +2 -2
- package/dist/graph/sqlite_index.js +293 -0
- package/dist/init/AGENT_START.md +3 -0
- package/dist/init/CLI_COMMAND_MATRIX.md +5 -0
- package/dist/init/README.md +11 -2
- package/dist/init/config.json +5 -1
- package/dist/init/core/rule-1-mdkg-conventions.md +2 -1
- package/dist/init/core/rule-3-cli-contract.md +2 -1
- package/dist/init/core/rule-4-repo-safety-and-ignores.md +27 -11
- package/dist/init/core/rule-5-release-and-versioning.md +4 -3
- package/dist/init/init-manifest.json +9 -9
- package/dist/util/atomic.js +44 -0
- package/dist/util/lock.js +72 -0
- package/package.json +8 -5
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,31 @@ All notable changes to mdkg are documented here.
|
|
|
4
4
|
|
|
5
5
|
This project follows a pragmatic changelog style inspired by Keep a Changelog. Versions use npm package versions.
|
|
6
6
|
|
|
7
|
+
## 0.1.3 - Unreleased
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Added first-class SQLite access cache support using Node's built-in `node:sqlite`; no third-party SQLite package is introduced.
|
|
12
|
+
- Added `.mdkg/index/mdkg.sqlite` as a rebuildable derived cache for nodes, edges, skills, capabilities, archives, bundle imports, source hashes, and schema metadata when `index.backend` is `sqlite`.
|
|
13
|
+
- Added fresh init defaults for `index.backend: sqlite`, `index.sqlite_path`, `index.sqlite_commit_warning_bytes`, and `index.lock_timeout_ms`.
|
|
14
|
+
- Added a shared mutation lock plus atomic writes for mdkg mutations and index writes.
|
|
15
|
+
- Added SQLite transactional id reservation for numeric node/checkpoint ids in SQLite mode.
|
|
16
|
+
- Added `npm run smoke:sqlite` and `npm run smoke:parallel` packed/temp-repo coverage.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- Raised the required Node runtime to `>=24.15.0`.
|
|
21
|
+
- Existing workspaces that are migrated from older configs remain on `index.backend: json` until they explicitly opt in to SQLite.
|
|
22
|
+
- Init ignore policy now keeps JSON indexes, temp files, lock directories, WAL, SHM, and journal files ignored while allowing `.mdkg/index/mdkg.sqlite` to be committed by intentional repo policy.
|
|
23
|
+
- `mdkg index` continues to write JSON compatibility indexes and also rebuilds SQLite when enabled.
|
|
24
|
+
- `mdkg doctor` and `mdkg validate` now report SQLite cache health when SQLite mode is enabled.
|
|
25
|
+
- README and seeded docs now state that mdkg is pre-v1 public alpha software and cache/DAL contracts may churn before v1.
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
- Removed the accidental self-dependency on `mdkg` from package metadata.
|
|
30
|
+
- Hardened parallel `mdkg new`, checkpoint, task, work, archive, bundle import config, and index writes against naming conflicts and partial cache writes.
|
|
31
|
+
|
|
7
32
|
## 0.1.2 - 2026-05-19
|
|
8
33
|
|
|
9
34
|
### Added
|
package/README.md
CHANGED
|
@@ -9,11 +9,14 @@ It is built for:
|
|
|
9
9
|
|
|
10
10
|
mdkg stays deliberately boring:
|
|
11
11
|
- repo-native under `.mdkg/`
|
|
12
|
-
- TypeScript + Node.js
|
|
13
|
-
- zero runtime dependencies
|
|
14
|
-
-
|
|
12
|
+
- TypeScript + Node.js `>=24.15.0`
|
|
13
|
+
- zero third-party runtime dependencies
|
|
14
|
+
- first-class rebuildable SQLite cache through built-in `node:sqlite`
|
|
15
|
+
- no daemon, hosted index, or vector DB
|
|
15
16
|
|
|
16
|
-
Current package version in source: `0.1.
|
|
17
|
+
Current package version in source: `0.1.3`
|
|
18
|
+
|
|
19
|
+
mdkg is still pre-v1 public alpha software. The public package is usable, but graph, cache, bundle, and DAL contracts may continue to change quickly while the project converges on a stable v1 surface.
|
|
17
20
|
|
|
18
21
|
## The product shape
|
|
19
22
|
|
|
@@ -202,10 +205,11 @@ mdkg lives under a hidden root directory:
|
|
|
202
205
|
- `.mdkg/skills/` Agent Skills packages
|
|
203
206
|
- `.mdkg/archive/` sidecar metadata plus deterministic compressed source/artifact caches
|
|
204
207
|
- `.mdkg/bundles/` optional committed full graph snapshot bundles
|
|
208
|
+
- `.mdkg/index/mdkg.sqlite` optional committed, rebuildable SQLite access cache
|
|
205
209
|
- `.mdkg/index/imports.json` generated read-only bundle import cache
|
|
206
210
|
- `.agents/skills/` Codex/OpenAI-facing mirrored skills
|
|
207
211
|
- `.claude/skills/` Claude-facing mirrored skills
|
|
208
|
-
- `.mdkg/index
|
|
212
|
+
- `.mdkg/index/*.json` generated JSON compatibility cache files
|
|
209
213
|
|
|
210
214
|
## Primary commands
|
|
211
215
|
|
|
@@ -292,6 +296,14 @@ The capability cache is not the full graph and is not source of truth. Normal ta
|
|
|
292
296
|
|
|
293
297
|
Capability records aggregate enabled registered workspaces and include deterministic source metadata such as `workspace`, `visibility`, `kind`, `id`, `qid`, `path`, headings, refs, source hash, and `indexed_at`. Workspace `visibility` also feeds mdkg's export safety checks for public/internal packs and public bundles. This is a CLI safety layer, not secret scanning, body redaction, or a replacement for private git hosting.
|
|
294
298
|
|
|
299
|
+
## Index backends and parallel safety
|
|
300
|
+
|
|
301
|
+
Fresh `mdkg init` workspaces default to `index.backend: sqlite`, which writes `.mdkg/index/mdkg.sqlite` as a rebuildable access cache using Node's built-in `node:sqlite`. Existing workspaces that are migrated from older configs default to `index.backend: json` until they opt in. Markdown files, archive sidecars, bundle manifests, and config remain source of truth in both modes.
|
|
302
|
+
|
|
303
|
+
`mdkg index` still writes JSON compatibility caches (`global.json`, `skills.json`, `capabilities.json`, and import projections when configured). In SQLite mode it also rebuilds the SQLite cache with nodes, edges, skills, capabilities, archive metadata, bundle imports, source hashes, and schema metadata. Deleting the SQLite file is recoverable with `mdkg index`.
|
|
304
|
+
|
|
305
|
+
Mutating commands use a workspace mutation lock plus atomic writes. SQLite mode additionally reserves numeric ids in a SQLite transaction before writing Markdown so parallel `mdkg new` and checkpoint calls avoid naming conflicts. Skipped ids after failed writes are acceptable because Markdown remains canonical.
|
|
306
|
+
|
|
295
307
|
## Agent workflow files
|
|
296
308
|
|
|
297
309
|
mdkg recognizes a small set of canonical agent workflow documents:
|
|
@@ -323,10 +335,12 @@ By default, init/upgrade ignore generated raw archive source copies with `.mdkg/
|
|
|
323
335
|
|
|
324
336
|
This release includes:
|
|
325
337
|
- `init --agent`
|
|
326
|
-
- default ignore updates with `--no-update-ignores` for
|
|
338
|
+
- default ignore updates with `--no-update-ignores` for generated JSON index/temp/lock files, `.mdkg/pack/`, and raw archive source copies
|
|
327
339
|
- root-only published init seed config
|
|
328
340
|
- skills indexing and search/show/list support
|
|
329
341
|
- JSON capability cache for skills, `SPEC.md`, `WORK.md`, core docs, and design docs
|
|
342
|
+
- SQLite index backend for fresh workspaces using built-in `node:sqlite`
|
|
343
|
+
- mutation locking and atomic writes for parallel mdkg calls
|
|
330
344
|
- optional `skills: [...]` on work items
|
|
331
345
|
- pack-time skill inclusion
|
|
332
346
|
- latest-checkpoint resolver + index hint
|
|
@@ -360,7 +374,8 @@ Design and decision records live in the internal graph under `.mdkg/design/`.
|
|
|
360
374
|
mdkg is not a secret store.
|
|
361
375
|
|
|
362
376
|
Use these defaults:
|
|
363
|
-
- keep `.mdkg/index
|
|
377
|
+
- keep generated `.mdkg/index/*.json`, temp, lock, WAL, SHM, and journal files gitignored
|
|
378
|
+
- commit `.mdkg/index/mdkg.sqlite` only when the repo intentionally tracks a reasonably sized rebuildable access cache
|
|
364
379
|
- keep `.mdkg/pack/` gitignored
|
|
365
380
|
- keep `.mdkg/archive/**/source/` gitignored unless a repo intentionally commits raw local copies
|
|
366
381
|
- commit archive sidecar `.md` metadata and deterministic `.zip` caches when they are needed for reviewable evidence
|
package/dist/cli.js
CHANGED
|
@@ -171,6 +171,8 @@ function printIndexHelp(log) {
|
|
|
171
171
|
log(" - .mdkg/index/global.json");
|
|
172
172
|
log(" - .mdkg/index/skills.json");
|
|
173
173
|
log(" - .mdkg/index/capabilities.json");
|
|
174
|
+
log(" - .mdkg/index/imports.json when bundle imports are configured");
|
|
175
|
+
log(" - .mdkg/index/mdkg.sqlite when index.backend is sqlite");
|
|
174
176
|
printGlobalOptions(log);
|
|
175
177
|
}
|
|
176
178
|
function printShowHelp(log) {
|
|
@@ -544,6 +546,7 @@ function printDoctorHelp(log) {
|
|
|
544
546
|
log(" - Bundle import health and staleness");
|
|
545
547
|
log(" - Index load/rebuild health");
|
|
546
548
|
log(" - Capability cache load/rebuild health");
|
|
549
|
+
log(" - SQLite cache health when enabled");
|
|
547
550
|
log("\nOptions:");
|
|
548
551
|
log(" --json Emit machine-readable JSON output");
|
|
549
552
|
printGlobalOptions(log);
|
package/dist/commands/archive.js
CHANGED
|
@@ -3,10 +3,10 @@ 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.runArchiveAddCommand = runArchiveAddCommand;
|
|
7
6
|
exports.runArchiveListCommand = runArchiveListCommand;
|
|
8
7
|
exports.runArchiveShowCommand = runArchiveShowCommand;
|
|
9
8
|
exports.runArchiveVerifyCommand = runArchiveVerifyCommand;
|
|
9
|
+
exports.runArchiveAddCommand = runArchiveAddCommand;
|
|
10
10
|
exports.runArchiveCompressCommand = runArchiveCompressCommand;
|
|
11
11
|
const fs_1 = __importDefault(require("fs"));
|
|
12
12
|
const path_1 = __importDefault(require("path"));
|
|
@@ -15,11 +15,14 @@ const frontmatter_1 = require("../graph/frontmatter");
|
|
|
15
15
|
const archive_integrity_1 = require("../graph/archive_integrity");
|
|
16
16
|
const indexer_1 = require("../graph/indexer");
|
|
17
17
|
const index_cache_1 = require("../graph/index_cache");
|
|
18
|
+
const reindex_1 = require("../graph/reindex");
|
|
18
19
|
const visibility_1 = require("../graph/visibility");
|
|
19
20
|
const date_1 = require("../util/date");
|
|
20
21
|
const errors_1 = require("../util/errors");
|
|
21
22
|
const id_1 = require("../util/id");
|
|
22
23
|
const refs_1 = require("../util/refs");
|
|
24
|
+
const atomic_1 = require("../util/atomic");
|
|
25
|
+
const lock_1 = require("../util/lock");
|
|
23
26
|
const zip_1 = require("../util/zip");
|
|
24
27
|
const event_support_1 = require("./event_support");
|
|
25
28
|
const ARCHIVE_KINDS = new Set(["source", "artifact"]);
|
|
@@ -124,8 +127,7 @@ function maybeReindex(root) {
|
|
|
124
127
|
if (!config.index.auto_reindex) {
|
|
125
128
|
return;
|
|
126
129
|
}
|
|
127
|
-
|
|
128
|
-
(0, index_cache_1.writeIndex)(outputPath, (0, indexer_1.buildIndex)(root, config, { tolerant: config.index.tolerant }));
|
|
130
|
+
(0, reindex_1.writeDerivedIndexes)(root, config, (0, indexer_1.buildIndex)(root, config, { tolerant: config.index.tolerant }));
|
|
129
131
|
}
|
|
130
132
|
function resolveArchiveNode(root, id, ws) {
|
|
131
133
|
const config = (0, config_1.loadConfig)(root);
|
|
@@ -174,7 +176,7 @@ function stringAttribute(value) {
|
|
|
174
176
|
function writeArchiveSidecar(sidecarPath, frontmatter, body) {
|
|
175
177
|
const lines = (0, frontmatter_1.formatFrontmatter)(frontmatter);
|
|
176
178
|
const content = ["---", ...lines, "---", body.trimStart()].join("\n");
|
|
177
|
-
|
|
179
|
+
(0, atomic_1.atomicWriteFile)(sidecarPath, content.endsWith("\n") ? content : `${content}\n`);
|
|
178
180
|
}
|
|
179
181
|
function verifyArchiveSidecar(root, ws, sidecarPath) {
|
|
180
182
|
const relativePath = toPosixPath(path_1.default.relative(root, sidecarPath));
|
|
@@ -276,7 +278,7 @@ function loadArchiveVerifyResults(options) {
|
|
|
276
278
|
}
|
|
277
279
|
return results;
|
|
278
280
|
}
|
|
279
|
-
function
|
|
281
|
+
function runArchiveAddCommandLocked(options) {
|
|
280
282
|
const config = (0, config_1.loadConfig)(options.root);
|
|
281
283
|
const ws = normalizeWorkspace(options.ws);
|
|
282
284
|
const workspace = config.workspaces[ws];
|
|
@@ -311,7 +313,7 @@ function runArchiveAddCommand(options) {
|
|
|
311
313
|
fs_1.default.copyFileSync(sourcePath, rawPath);
|
|
312
314
|
const rawData = fs_1.default.readFileSync(rawPath);
|
|
313
315
|
const zipData = (0, zip_1.createDeterministicZip)(basename, rawData);
|
|
314
|
-
|
|
316
|
+
(0, atomic_1.atomicWriteFile)(zipPath, zipData);
|
|
315
317
|
const frontmatter = {
|
|
316
318
|
id,
|
|
317
319
|
type: "archive",
|
|
@@ -423,7 +425,7 @@ function runArchiveVerifyCommand(options) {
|
|
|
423
425
|
throw new errors_1.ValidationError("archive verification failed");
|
|
424
426
|
}
|
|
425
427
|
}
|
|
426
|
-
function
|
|
428
|
+
function runArchiveCompressCommandLocked(options) {
|
|
427
429
|
if (!options.all && !options.id) {
|
|
428
430
|
throw new errors_1.UsageError("archive compress requires <id-or-archive-uri> or --all");
|
|
429
431
|
}
|
|
@@ -441,7 +443,7 @@ function runArchiveCompressCommand(options) {
|
|
|
441
443
|
}
|
|
442
444
|
const rawData = fs_1.default.readFileSync(rawPath);
|
|
443
445
|
const zipData = (0, zip_1.createDeterministicZip)(path_1.default.basename(rawPath), rawData);
|
|
444
|
-
|
|
446
|
+
(0, atomic_1.atomicWriteFile)(zipPath, zipData);
|
|
445
447
|
const parsed = (0, frontmatter_1.parseFrontmatter)(fs_1.default.readFileSync(sidecarPath, "utf8"), sidecarPath);
|
|
446
448
|
const nextFrontmatter = {
|
|
447
449
|
...parsed.frontmatter,
|
|
@@ -472,3 +474,13 @@ function runArchiveCompressCommand(options) {
|
|
|
472
474
|
}
|
|
473
475
|
console.log(`archive compressed: ${updated.length}`);
|
|
474
476
|
}
|
|
477
|
+
function withArchiveLock(root, fn) {
|
|
478
|
+
const config = (0, config_1.loadConfig)(root);
|
|
479
|
+
return (0, lock_1.withMutationLock)(root, config.index.lock_timeout_ms, fn);
|
|
480
|
+
}
|
|
481
|
+
function runArchiveAddCommand(options) {
|
|
482
|
+
return withArchiveLock(options.root, () => runArchiveAddCommandLocked(options));
|
|
483
|
+
}
|
|
484
|
+
function runArchiveCompressCommand(options) {
|
|
485
|
+
return withArchiveLock(options.root, () => runArchiveCompressCommandLocked(options));
|
|
486
|
+
}
|
|
@@ -16,6 +16,8 @@ const migrate_1 = require("../core/migrate");
|
|
|
16
16
|
const workspace_path_1 = require("../core/workspace_path");
|
|
17
17
|
const bundle_imports_1 = require("../graph/bundle_imports");
|
|
18
18
|
const errors_1 = require("../util/errors");
|
|
19
|
+
const atomic_1 = require("../util/atomic");
|
|
20
|
+
const lock_1 = require("../util/lock");
|
|
19
21
|
const ALIAS_RE = /^[a-z][a-z0-9_]*$/;
|
|
20
22
|
function writeJson(value) {
|
|
21
23
|
console.log(JSON.stringify(value, null, 2));
|
|
@@ -71,7 +73,7 @@ function readRawConfig(root) {
|
|
|
71
73
|
return { configPath, raw: migrated };
|
|
72
74
|
}
|
|
73
75
|
function writeRawConfig(configPath, raw) {
|
|
74
|
-
|
|
76
|
+
(0, atomic_1.atomicWriteFile)(configPath, `${JSON.stringify(raw, null, 2)}\n`);
|
|
75
77
|
}
|
|
76
78
|
function getImports(raw) {
|
|
77
79
|
const imports = raw.bundle_imports;
|
|
@@ -98,7 +100,11 @@ function healthByAlias(root, alias) {
|
|
|
98
100
|
}
|
|
99
101
|
return health;
|
|
100
102
|
}
|
|
101
|
-
function
|
|
103
|
+
function withBundleImportLock(root, fn) {
|
|
104
|
+
const config = (0, config_1.loadConfig)(root);
|
|
105
|
+
return (0, lock_1.withMutationLock)(root, config.index.lock_timeout_ms, fn);
|
|
106
|
+
}
|
|
107
|
+
function runBundleImportAddCommandLocked(options) {
|
|
102
108
|
const alias = normalizeAlias(options.alias);
|
|
103
109
|
const bundlePath = normalizeContained(options.bundlePath, "bundle import path");
|
|
104
110
|
const visibility = normalizeVisibility(options.visibility);
|
|
@@ -150,6 +156,9 @@ function runBundleImportAddCommand(options) {
|
|
|
150
156
|
console.log(`warnings: ${health.warning_count}`);
|
|
151
157
|
}
|
|
152
158
|
}
|
|
159
|
+
function runBundleImportAddCommand(options) {
|
|
160
|
+
return withBundleImportLock(options.root, () => runBundleImportAddCommandLocked(options));
|
|
161
|
+
}
|
|
153
162
|
function runBundleImportListCommand(options) {
|
|
154
163
|
const config = (0, config_1.loadConfig)(options.root);
|
|
155
164
|
const imports = (0, bundle_imports_1.buildBundleImportsIndex)(options.root, config).index.imports;
|
|
@@ -166,7 +175,7 @@ function runBundleImportListCommand(options) {
|
|
|
166
175
|
console.log(`${item.alias} | ${status} | ${item.visibility} | ${item.path}`);
|
|
167
176
|
}
|
|
168
177
|
}
|
|
169
|
-
function
|
|
178
|
+
function runBundleImportRemoveCommandLocked(options) {
|
|
170
179
|
const alias = normalizeAlias(options.alias);
|
|
171
180
|
const { configPath, raw } = readRawConfig(options.root);
|
|
172
181
|
const imports = getImports(raw);
|
|
@@ -185,7 +194,10 @@ function runBundleImportRemoveCommand(options) {
|
|
|
185
194
|
}
|
|
186
195
|
console.log(`bundle import removed: ${alias}`);
|
|
187
196
|
}
|
|
188
|
-
function
|
|
197
|
+
function runBundleImportRemoveCommand(options) {
|
|
198
|
+
return withBundleImportLock(options.root, () => runBundleImportRemoveCommandLocked(options));
|
|
199
|
+
}
|
|
200
|
+
function setBundleImportEnabledLocked(options, enabled) {
|
|
189
201
|
const alias = normalizeAlias(options.alias);
|
|
190
202
|
const { configPath, raw } = readRawConfig(options.root);
|
|
191
203
|
const imports = getImports(raw);
|
|
@@ -206,10 +218,10 @@ function setBundleImportEnabled(options, enabled) {
|
|
|
206
218
|
console.log(`bundle import ${enabled ? "enabled" : "disabled"}: ${alias}`);
|
|
207
219
|
}
|
|
208
220
|
function runBundleImportEnableCommand(options) {
|
|
209
|
-
|
|
221
|
+
withBundleImportLock(options.root, () => setBundleImportEnabledLocked(options, true));
|
|
210
222
|
}
|
|
211
223
|
function runBundleImportDisableCommand(options) {
|
|
212
|
-
|
|
224
|
+
withBundleImportLock(options.root, () => setBundleImportEnabledLocked(options, false));
|
|
213
225
|
}
|
|
214
226
|
function runBundleImportVerifyCommand(options) {
|
|
215
227
|
const config = (0, config_1.loadConfig)(options.root);
|
|
@@ -13,6 +13,9 @@ const loader_1 = require("../templates/loader");
|
|
|
13
13
|
const date_1 = require("../util/date");
|
|
14
14
|
const errors_1 = require("../util/errors");
|
|
15
15
|
const id_1 = require("../util/id");
|
|
16
|
+
const atomic_1 = require("../util/atomic");
|
|
17
|
+
const lock_1 = require("../util/lock");
|
|
18
|
+
const sqlite_index_1 = require("../graph/sqlite_index");
|
|
16
19
|
const event_support_1 = require("./event_support");
|
|
17
20
|
function parseCsvList(raw) {
|
|
18
21
|
if (!raw) {
|
|
@@ -38,6 +41,9 @@ function normalizeIdRef(value, key) {
|
|
|
38
41
|
return normalized;
|
|
39
42
|
}
|
|
40
43
|
function nextCheckpointId(index, ws) {
|
|
44
|
+
return `chk-${maxCheckpointId(index, ws) + 1}`;
|
|
45
|
+
}
|
|
46
|
+
function maxCheckpointId(index, ws) {
|
|
41
47
|
let max = 0;
|
|
42
48
|
for (const node of Object.values(index.nodes)) {
|
|
43
49
|
if (node.ws !== ws) {
|
|
@@ -52,7 +58,7 @@ function nextCheckpointId(index, ws) {
|
|
|
52
58
|
max = parsed;
|
|
53
59
|
}
|
|
54
60
|
}
|
|
55
|
-
return
|
|
61
|
+
return max;
|
|
56
62
|
}
|
|
57
63
|
function slugifyTitle(title) {
|
|
58
64
|
const slug = title
|
|
@@ -77,7 +83,7 @@ function normalizeWorkspace(value) {
|
|
|
77
83
|
}
|
|
78
84
|
return normalized;
|
|
79
85
|
}
|
|
80
|
-
function
|
|
86
|
+
function createCheckpointLocked(options) {
|
|
81
87
|
const title = options.title.trim();
|
|
82
88
|
if (!title) {
|
|
83
89
|
throw new errors_1.UsageError("checkpoint title cannot be empty");
|
|
@@ -99,7 +105,15 @@ function createCheckpoint(options) {
|
|
|
99
105
|
throw new errors_1.UsageError(`--priority must be between ${priorityMin} and ${priorityMax}`);
|
|
100
106
|
}
|
|
101
107
|
const { index } = (0, index_cache_1.loadIndex)({ root: options.root, config });
|
|
102
|
-
const id =
|
|
108
|
+
const id = (0, sqlite_index_1.isSqliteBackend)(config)
|
|
109
|
+
? (0, sqlite_index_1.reserveSqliteNumericId)({
|
|
110
|
+
root: options.root,
|
|
111
|
+
config,
|
|
112
|
+
ws,
|
|
113
|
+
prefix: "chk",
|
|
114
|
+
currentMax: maxCheckpointId(index, ws),
|
|
115
|
+
}) ?? nextCheckpointId(index, ws)
|
|
116
|
+
: nextCheckpointId(index, ws);
|
|
103
117
|
const slug = slugifyTitle(title);
|
|
104
118
|
const fileName = `${id}-${slug}.md`;
|
|
105
119
|
const wsEntry = config.workspaces[ws];
|
|
@@ -129,8 +143,16 @@ function createCheckpoint(options) {
|
|
|
129
143
|
relates,
|
|
130
144
|
scope,
|
|
131
145
|
});
|
|
132
|
-
|
|
133
|
-
|
|
146
|
+
try {
|
|
147
|
+
(0, atomic_1.writeFileExclusive)(filePath, content);
|
|
148
|
+
}
|
|
149
|
+
catch (err) {
|
|
150
|
+
const code = typeof err === "object" && err !== null && "code" in err ? String(err.code) : "";
|
|
151
|
+
if (code === "EEXIST") {
|
|
152
|
+
throw new errors_1.UsageError(`checkpoint file already exists: ${path_1.default.relative(options.root, filePath)}`);
|
|
153
|
+
}
|
|
154
|
+
throw err;
|
|
155
|
+
}
|
|
134
156
|
(0, event_support_1.appendAutomaticEvent)({
|
|
135
157
|
root: options.root,
|
|
136
158
|
ws,
|
|
@@ -148,6 +170,10 @@ function createCheckpoint(options) {
|
|
|
148
170
|
path: path_1.default.relative(options.root, filePath),
|
|
149
171
|
};
|
|
150
172
|
}
|
|
173
|
+
function createCheckpoint(options) {
|
|
174
|
+
const config = (0, config_1.loadConfig)(options.root);
|
|
175
|
+
return (0, lock_1.withMutationLock)(options.root, config.index.lock_timeout_ms, () => createCheckpointLocked(options));
|
|
176
|
+
}
|
|
151
177
|
function runCheckpointNewCommand(options) {
|
|
152
178
|
const checkpoint = createCheckpoint(options);
|
|
153
179
|
if (options.json) {
|
package/dist/commands/doctor.js
CHANGED
|
@@ -13,31 +13,37 @@ const bundle_imports_1 = require("../graph/bundle_imports");
|
|
|
13
13
|
const node_1 = require("../graph/node");
|
|
14
14
|
const template_schema_1 = require("../graph/template_schema");
|
|
15
15
|
const visibility_1 = require("../graph/visibility");
|
|
16
|
+
const sqlite_index_1 = require("../graph/sqlite_index");
|
|
16
17
|
const errors_1 = require("../util/errors");
|
|
17
|
-
const REQUIRED_NODE_MAJOR =
|
|
18
|
+
const REQUIRED_NODE_MAJOR = 24;
|
|
19
|
+
const REQUIRED_NODE_MINOR = 15;
|
|
18
20
|
const ARCHIVE_RAW_ALLOWED_DIRS = new Set(["source"]);
|
|
19
|
-
function
|
|
20
|
-
const
|
|
21
|
-
|
|
21
|
+
function parseNodeVersion(version) {
|
|
22
|
+
const [majorRaw, minorRaw, patchRaw] = version.split(".");
|
|
23
|
+
const major = Number.parseInt(majorRaw ?? "", 10);
|
|
24
|
+
const minor = Number.parseInt(minorRaw ?? "", 10);
|
|
25
|
+
const patch = Number.parseInt(patchRaw ?? "", 10);
|
|
26
|
+
if (!Number.isInteger(major) || !Number.isInteger(minor) || !Number.isInteger(patch)) {
|
|
22
27
|
return null;
|
|
23
28
|
}
|
|
24
|
-
return major;
|
|
29
|
+
return { major, minor, patch };
|
|
25
30
|
}
|
|
26
31
|
function runNodeVersionCheck() {
|
|
27
32
|
const nodeVersion = process.versions.node;
|
|
28
|
-
const
|
|
29
|
-
if (
|
|
33
|
+
const parsed = parseNodeVersion(nodeVersion);
|
|
34
|
+
if (parsed === null) {
|
|
30
35
|
return {
|
|
31
36
|
name: "node-version",
|
|
32
37
|
ok: false,
|
|
33
38
|
detail: `unable to parse Node.js version: ${nodeVersion}`,
|
|
34
39
|
};
|
|
35
40
|
}
|
|
36
|
-
if (major < REQUIRED_NODE_MAJOR
|
|
41
|
+
if (parsed.major < REQUIRED_NODE_MAJOR ||
|
|
42
|
+
(parsed.major === REQUIRED_NODE_MAJOR && parsed.minor < REQUIRED_NODE_MINOR)) {
|
|
37
43
|
return {
|
|
38
44
|
name: "node-version",
|
|
39
45
|
ok: false,
|
|
40
|
-
detail: `Node.js ${nodeVersion} is unsupported (requires >=${REQUIRED_NODE_MAJOR})`,
|
|
46
|
+
detail: `Node.js ${nodeVersion} is unsupported (requires >=${REQUIRED_NODE_MAJOR}.${REQUIRED_NODE_MINOR}.0)`,
|
|
41
47
|
};
|
|
42
48
|
}
|
|
43
49
|
return {
|
|
@@ -46,6 +52,36 @@ function runNodeVersionCheck() {
|
|
|
46
52
|
detail: `Node.js ${nodeVersion} (ok)`,
|
|
47
53
|
};
|
|
48
54
|
}
|
|
55
|
+
function runSqliteCheck(root, config) {
|
|
56
|
+
if (!(0, sqlite_index_1.isSqliteBackend)(config)) {
|
|
57
|
+
return {
|
|
58
|
+
name: "sqlite-cache",
|
|
59
|
+
ok: true,
|
|
60
|
+
detail: "SQLite backend disabled; JSON cache backend active",
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
const health = (0, sqlite_index_1.sqliteHealth)(root, config);
|
|
64
|
+
if (health.errors.length > 0) {
|
|
65
|
+
return {
|
|
66
|
+
name: "sqlite-cache",
|
|
67
|
+
ok: false,
|
|
68
|
+
detail: health.errors.join("; "),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
if (health.warnings.length > 0) {
|
|
72
|
+
return {
|
|
73
|
+
name: "sqlite-cache",
|
|
74
|
+
ok: true,
|
|
75
|
+
level: "warn",
|
|
76
|
+
detail: health.warnings.join("; "),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
name: "sqlite-cache",
|
|
81
|
+
ok: true,
|
|
82
|
+
detail: `.mdkg SQLite cache ok (${health.size} bytes)`,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
49
85
|
function walkFiles(root) {
|
|
50
86
|
if (!fs_1.default.existsSync(root)) {
|
|
51
87
|
return [];
|
|
@@ -262,6 +298,7 @@ function runDoctorCommand(options) {
|
|
|
262
298
|
results.push(runArchiveStorageCheck(options.root));
|
|
263
299
|
results.push(runArchiveLargeCacheCheck(options.root, config.archive.large_cache_warning_bytes));
|
|
264
300
|
results.push(runBundleStorageCheck(options.root, config.bundles.output_dir));
|
|
301
|
+
results.push(runSqliteCheck(options.root, config));
|
|
265
302
|
results.push(...runBundleImportChecks(options.root, config));
|
|
266
303
|
results.push(runVisibilityPolicyCheck(options.root, config, options));
|
|
267
304
|
try {
|
package/dist/commands/index.js
CHANGED
|
@@ -6,29 +6,18 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.runIndexCommand = runIndexCommand;
|
|
7
7
|
const path_1 = __importDefault(require("path"));
|
|
8
8
|
const config_1 = require("../core/config");
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const skills_index_cache_1 = require("../graph/skills_index_cache");
|
|
12
|
-
const skills_indexer_1 = require("../graph/skills_indexer");
|
|
13
|
-
const capabilities_indexer_1 = require("../graph/capabilities_indexer");
|
|
14
|
-
const capabilities_index_cache_1 = require("../graph/capabilities_index_cache");
|
|
15
|
-
const bundle_imports_1 = require("../graph/bundle_imports");
|
|
9
|
+
const reindex_1 = require("../graph/reindex");
|
|
10
|
+
const lock_1 = require("../util/lock");
|
|
16
11
|
function runIndexCommand(options) {
|
|
17
12
|
const config = (0, config_1.loadConfig)(options.root);
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
(0, capabilities_index_cache_1.writeCapabilitiesIndex)(capabilitiesOutputPath, capabilitiesIndex);
|
|
29
|
-
(0, bundle_imports_1.writeBundleImportsIndex)(importsOutputPath, importsIndex.index);
|
|
30
|
-
console.log(`index written: ${path_1.default.relative(options.root, nodesOutputPath)}`);
|
|
31
|
-
console.log(`skills index written: ${path_1.default.relative(options.root, skillsOutputPath)}`);
|
|
32
|
-
console.log(`capabilities index written: ${path_1.default.relative(options.root, capabilitiesOutputPath)}`);
|
|
33
|
-
console.log(`bundle imports index written: ${path_1.default.relative(options.root, importsOutputPath)}`);
|
|
13
|
+
(0, lock_1.withMutationLock)(options.root, config.index.lock_timeout_ms, () => {
|
|
14
|
+
const result = (0, reindex_1.writeDerivedIndexes)(options.root, config, undefined, { tolerant: options.tolerant });
|
|
15
|
+
console.log(`index written: ${path_1.default.relative(options.root, result.paths.nodes)}`);
|
|
16
|
+
console.log(`skills index written: ${path_1.default.relative(options.root, result.paths.skills)}`);
|
|
17
|
+
console.log(`capabilities index written: ${path_1.default.relative(options.root, result.paths.capabilities)}`);
|
|
18
|
+
console.log(`bundle imports index written: ${path_1.default.relative(options.root, result.paths.imports)}`);
|
|
19
|
+
if (result.paths.sqlite) {
|
|
20
|
+
console.log(`sqlite index written: ${path_1.default.relative(options.root, result.paths.sqlite)}`);
|
|
21
|
+
}
|
|
22
|
+
});
|
|
34
23
|
}
|
package/dist/commands/init.js
CHANGED
|
@@ -409,7 +409,13 @@ function runInitCommand(options) {
|
|
|
409
409
|
try {
|
|
410
410
|
if (shouldUpdateGitignore) {
|
|
411
411
|
if (appendIgnoreEntries(path_1.default.join(root, ".gitignore"), [
|
|
412
|
-
".mdkg/index
|
|
412
|
+
".mdkg/index/*.json",
|
|
413
|
+
".mdkg/index/*.tmp",
|
|
414
|
+
".mdkg/index/*.lock",
|
|
415
|
+
".mdkg/index/write.lock/",
|
|
416
|
+
".mdkg/index/*.sqlite-wal",
|
|
417
|
+
".mdkg/index/*.sqlite-shm",
|
|
418
|
+
".mdkg/index/*.sqlite-journal",
|
|
413
419
|
".mdkg/pack/",
|
|
414
420
|
".mdkg/archive/**/source/",
|
|
415
421
|
])) {
|
package/dist/commands/new.js
CHANGED
|
@@ -16,6 +16,10 @@ const date_1 = require("../util/date");
|
|
|
16
16
|
const errors_1 = require("../util/errors");
|
|
17
17
|
const qid_1 = require("../util/qid");
|
|
18
18
|
const id_1 = require("../util/id");
|
|
19
|
+
const atomic_1 = require("../util/atomic");
|
|
20
|
+
const lock_1 = require("../util/lock");
|
|
21
|
+
const sqlite_index_1 = require("../graph/sqlite_index");
|
|
22
|
+
const reindex_1 = require("../graph/reindex");
|
|
19
23
|
const event_support_1 = require("./event_support");
|
|
20
24
|
const DEC_ID_RE = /^dec-[0-9]+$/;
|
|
21
25
|
const DEC_STATUS = new Set(["proposed", "accepted", "rejected", "superseded"]);
|
|
@@ -97,6 +101,9 @@ function slugifyTitle(title) {
|
|
|
97
101
|
return slug.length > maxLen ? slug.slice(0, maxLen).replace(/-+$/g, "") : slug;
|
|
98
102
|
}
|
|
99
103
|
function nextIdForPrefix(index, ws, prefix) {
|
|
104
|
+
return `${prefix}-${maxIdForPrefix(index, ws, prefix) + 1}`;
|
|
105
|
+
}
|
|
106
|
+
function maxIdForPrefix(index, ws, prefix) {
|
|
100
107
|
let max = 0;
|
|
101
108
|
const pattern = new RegExp(`^${prefix}-(\\d+)$`);
|
|
102
109
|
for (const node of Object.values(index)) {
|
|
@@ -112,7 +119,7 @@ function nextIdForPrefix(index, ws, prefix) {
|
|
|
112
119
|
max = parsed;
|
|
113
120
|
}
|
|
114
121
|
}
|
|
115
|
-
return
|
|
122
|
+
return max;
|
|
116
123
|
}
|
|
117
124
|
function idPrefixForType(type) {
|
|
118
125
|
if (type === "checkpoint") {
|
|
@@ -150,7 +157,7 @@ function ensureExists(index, value, ws, label) {
|
|
|
150
157
|
throw new errors_1.NotFoundError((0, qid_1.formatResolveError)(label, value, resolved, ws));
|
|
151
158
|
}
|
|
152
159
|
}
|
|
153
|
-
function
|
|
160
|
+
function runNewCommandLocked(options) {
|
|
154
161
|
const title = options.title.trim();
|
|
155
162
|
if (!title) {
|
|
156
163
|
throw new errors_1.UsageError("title cannot be empty");
|
|
@@ -181,7 +188,15 @@ function runNewCommand(options) {
|
|
|
181
188
|
const prefix = idPrefixForType(type);
|
|
182
189
|
const id = options.id !== undefined
|
|
183
190
|
? normalizeAgentFileId(options.id)
|
|
184
|
-
:
|
|
191
|
+
: (0, sqlite_index_1.isSqliteBackend)(config)
|
|
192
|
+
? (0, sqlite_index_1.reserveSqliteNumericId)({
|
|
193
|
+
root: options.root,
|
|
194
|
+
config,
|
|
195
|
+
ws,
|
|
196
|
+
prefix,
|
|
197
|
+
currentMax: maxIdForPrefix(index.nodes, ws, prefix),
|
|
198
|
+
}) ?? nextIdForPrefix(index.nodes, ws, prefix)
|
|
199
|
+
: nextIdForPrefix(index.nodes, ws, prefix);
|
|
185
200
|
if (index.nodes[`${ws}:${id}`]) {
|
|
186
201
|
throw new errors_1.UsageError(`node already exists: ${ws}:${id}`);
|
|
187
202
|
}
|
|
@@ -317,12 +332,19 @@ function runNewCommand(options) {
|
|
|
317
332
|
created: today,
|
|
318
333
|
updated: today,
|
|
319
334
|
});
|
|
320
|
-
|
|
321
|
-
|
|
335
|
+
try {
|
|
336
|
+
(0, atomic_1.writeFileExclusive)(filePath, content);
|
|
337
|
+
}
|
|
338
|
+
catch (err) {
|
|
339
|
+
const code = typeof err === "object" && err !== null && "code" in err ? String(err.code) : "";
|
|
340
|
+
if (code === "EEXIST") {
|
|
341
|
+
throw new errors_1.UsageError(`node already exists: ${path_1.default.relative(options.root, filePath)}`);
|
|
342
|
+
}
|
|
343
|
+
throw err;
|
|
344
|
+
}
|
|
322
345
|
if (config.index.auto_reindex && !noReindex) {
|
|
323
346
|
const updatedIndex = (0, indexer_1.buildIndex)(options.root, config, { tolerant: config.index.tolerant });
|
|
324
|
-
|
|
325
|
-
(0, index_cache_1.writeIndex)(outputPath, updatedIndex);
|
|
347
|
+
(0, reindex_1.writeDerivedIndexes)(options.root, config, updatedIndex);
|
|
326
348
|
}
|
|
327
349
|
(0, event_support_1.appendAutomaticEvent)({
|
|
328
350
|
root: options.root,
|
|
@@ -354,3 +376,7 @@ function runNewCommand(options) {
|
|
|
354
376
|
}
|
|
355
377
|
console.log(`node created: ${receipt.qid} (${receipt.path})`);
|
|
356
378
|
}
|
|
379
|
+
function runNewCommand(options) {
|
|
380
|
+
const config = (0, config_1.loadConfig)(options.root);
|
|
381
|
+
return (0, lock_1.withMutationLock)(options.root, config.index.lock_timeout_ms, () => runNewCommandLocked(options));
|
|
382
|
+
}
|