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.
@@ -0,0 +1,293 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.SQLITE_SCHEMA_VERSION = void 0;
7
+ exports.isSqliteBackend = isSqliteBackend;
8
+ exports.resolveSqlitePath = resolveSqlitePath;
9
+ exports.writeSqliteIndex = writeSqliteIndex;
10
+ exports.reserveSqliteNumericId = reserveSqliteNumericId;
11
+ exports.sqliteHealth = sqliteHealth;
12
+ const crypto_1 = __importDefault(require("crypto"));
13
+ const fs_1 = __importDefault(require("fs"));
14
+ const path_1 = __importDefault(require("path"));
15
+ const staleness_1 = require("./staleness");
16
+ exports.SQLITE_SCHEMA_VERSION = 1;
17
+ function loadDatabaseCtor() {
18
+ try {
19
+ const loaded = require("node:sqlite");
20
+ if (!loaded.DatabaseSync) {
21
+ throw new Error("node:sqlite DatabaseSync is unavailable");
22
+ }
23
+ return loaded.DatabaseSync;
24
+ }
25
+ catch (err) {
26
+ const message = err instanceof Error ? err.message : String(err);
27
+ throw new Error(`node:sqlite is required for mdkg SQLite index support: ${message}`);
28
+ }
29
+ }
30
+ function toPosixPath(value) {
31
+ return value.split(path_1.default.sep).join("/");
32
+ }
33
+ function sha256File(filePath) {
34
+ return `sha256:${crypto_1.default.createHash("sha256").update(fs_1.default.readFileSync(filePath)).digest("hex")}`;
35
+ }
36
+ function stripVolatileCacheFields(value) {
37
+ if (Array.isArray(value)) {
38
+ return value.map((item) => stripVolatileCacheFields(item));
39
+ }
40
+ if (value && typeof value === "object") {
41
+ const input = value;
42
+ const output = {};
43
+ for (const key of Object.keys(input).sort()) {
44
+ if (key === "generated_at" || key === "indexed_at") {
45
+ continue;
46
+ }
47
+ output[key] = stripVolatileCacheFields(input[key]);
48
+ }
49
+ return output;
50
+ }
51
+ return value;
52
+ }
53
+ function stableCacheJson(value) {
54
+ return JSON.stringify(stripVolatileCacheFields(value));
55
+ }
56
+ function isSqliteBackend(config) {
57
+ return config.index.backend === "sqlite";
58
+ }
59
+ function resolveSqlitePath(root, config) {
60
+ return path_1.default.resolve(root, config.index.sqlite_path);
61
+ }
62
+ function sqliteTempPath(sqlitePath) {
63
+ return path_1.default.join(path_1.default.dirname(sqlitePath), `.${path_1.default.basename(sqlitePath)}.${process.pid}.${Date.now()}.tmp`);
64
+ }
65
+ function createSchema(db) {
66
+ db.exec(`
67
+ PRAGMA journal_mode = DELETE;
68
+ PRAGMA synchronous = FULL;
69
+ CREATE TABLE meta (key TEXT PRIMARY KEY, value TEXT NOT NULL);
70
+ CREATE TABLE nodes (
71
+ qid TEXT PRIMARY KEY,
72
+ id TEXT NOT NULL,
73
+ ws TEXT NOT NULL,
74
+ type TEXT NOT NULL,
75
+ title TEXT NOT NULL,
76
+ path TEXT NOT NULL,
77
+ status TEXT,
78
+ priority INTEGER,
79
+ updated TEXT,
80
+ source_hash TEXT,
81
+ json TEXT NOT NULL
82
+ );
83
+ CREATE TABLE edges (
84
+ source_qid TEXT NOT NULL,
85
+ kind TEXT NOT NULL,
86
+ target_qid TEXT NOT NULL,
87
+ PRIMARY KEY (source_qid, kind, target_qid)
88
+ );
89
+ CREATE TABLE skills (
90
+ qid TEXT PRIMARY KEY,
91
+ slug TEXT NOT NULL,
92
+ ws TEXT NOT NULL,
93
+ name TEXT NOT NULL,
94
+ path TEXT NOT NULL,
95
+ json TEXT NOT NULL
96
+ );
97
+ CREATE TABLE capabilities (
98
+ qid TEXT NOT NULL,
99
+ kind TEXT NOT NULL,
100
+ workspace TEXT NOT NULL,
101
+ visibility TEXT NOT NULL,
102
+ id TEXT NOT NULL,
103
+ path TEXT NOT NULL,
104
+ source_hash TEXT NOT NULL,
105
+ json TEXT NOT NULL,
106
+ PRIMARY KEY (qid, kind, path)
107
+ );
108
+ CREATE TABLE archives (
109
+ qid TEXT PRIMARY KEY,
110
+ visibility TEXT NOT NULL,
111
+ compressed_path TEXT,
112
+ compressed_sha256 TEXT,
113
+ json TEXT NOT NULL
114
+ );
115
+ CREATE TABLE bundle_imports (
116
+ alias TEXT PRIMARY KEY,
117
+ enabled INTEGER NOT NULL,
118
+ stale INTEGER NOT NULL,
119
+ error_count INTEGER NOT NULL,
120
+ warning_count INTEGER NOT NULL,
121
+ json TEXT NOT NULL
122
+ );
123
+ CREATE TABLE id_allocations (
124
+ ws TEXT NOT NULL,
125
+ prefix TEXT NOT NULL,
126
+ next_value INTEGER NOT NULL,
127
+ PRIMARY KEY (ws, prefix)
128
+ );
129
+ `);
130
+ }
131
+ function insertMeta(db, key, value) {
132
+ db.prepare("INSERT INTO meta (key, value) VALUES (?, ?)").run(key, value);
133
+ }
134
+ function nodeSourceHash(root, nodePath) {
135
+ const fullPath = path_1.default.resolve(root, nodePath);
136
+ if (!fs_1.default.existsSync(fullPath) || !fs_1.default.statSync(fullPath).isFile()) {
137
+ return undefined;
138
+ }
139
+ return sha256File(fullPath);
140
+ }
141
+ function buildSourceFingerprint(options) {
142
+ const payload = {
143
+ nodes: Object.values(options.nodeIndex.nodes)
144
+ .sort((a, b) => a.qid.localeCompare(b.qid))
145
+ .map((node) => ({
146
+ qid: node.qid,
147
+ path: node.path,
148
+ hash: options.nodeHashes.get(node.qid) ?? "",
149
+ })),
150
+ skills: Object.values(options.skillsIndex.skills).sort((a, b) => a.qid.localeCompare(b.qid)),
151
+ capabilities: options.capabilitiesIndex.records,
152
+ imports: options.importsIndex.imports,
153
+ };
154
+ return `sha256:${crypto_1.default.createHash("sha256").update(stableCacheJson(payload)).digest("hex")}`;
155
+ }
156
+ function writeSqliteIndex(options) {
157
+ const sqlitePath = resolveSqlitePath(options.root, options.config);
158
+ fs_1.default.mkdirSync(path_1.default.dirname(sqlitePath), { recursive: true });
159
+ const tempPath = sqliteTempPath(sqlitePath);
160
+ fs_1.default.rmSync(tempPath, { force: true });
161
+ const DatabaseSync = loadDatabaseCtor();
162
+ const db = new DatabaseSync(tempPath);
163
+ try {
164
+ const nodeHashes = new Map();
165
+ for (const node of Object.values(options.nodeIndex.nodes)) {
166
+ nodeHashes.set(node.qid, nodeSourceHash(options.root, node.path));
167
+ }
168
+ createSchema(db);
169
+ insertMeta(db, "tool", "mdkg");
170
+ insertMeta(db, "schema_version", String(exports.SQLITE_SCHEMA_VERSION));
171
+ insertMeta(db, "package_schema_version", String(options.config.schema_version));
172
+ insertMeta(db, "backend", options.config.index.backend);
173
+ insertMeta(db, "source_fingerprint", buildSourceFingerprint({ ...options, nodeHashes }));
174
+ insertMeta(db, "root", ".");
175
+ const insertNode = db.prepare("INSERT INTO nodes (qid, id, ws, type, title, path, status, priority, updated, source_hash, json) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
176
+ const insertEdge = db.prepare("INSERT OR IGNORE INTO edges (source_qid, kind, target_qid) VALUES (?, ?, ?)");
177
+ for (const node of Object.values(options.nodeIndex.nodes).sort((a, b) => a.qid.localeCompare(b.qid))) {
178
+ insertNode.run(node.qid, node.id, node.ws, node.type, node.title, toPosixPath(node.path), node.status ?? null, node.priority ?? null, node.updated ?? null, nodeHashes.get(node.qid) ?? null, stableCacheJson(node));
179
+ for (const [kind, values] of [
180
+ ["relates", node.edges.relates],
181
+ ["blocked_by", node.edges.blocked_by],
182
+ ["blocks", node.edges.blocks],
183
+ ]) {
184
+ for (const target of values) {
185
+ insertEdge.run(node.qid, kind, target);
186
+ }
187
+ }
188
+ for (const [kind, target] of [
189
+ ["epic", node.edges.epic],
190
+ ["parent", node.edges.parent],
191
+ ["prev", node.edges.prev],
192
+ ["next", node.edges.next],
193
+ ]) {
194
+ if (target) {
195
+ insertEdge.run(node.qid, kind, target);
196
+ }
197
+ }
198
+ }
199
+ const insertSkill = db.prepare("INSERT INTO skills (qid, slug, ws, name, path, json) VALUES (?, ?, ?, ?, ?, ?)");
200
+ for (const skill of Object.values(options.skillsIndex.skills).sort((a, b) => a.qid.localeCompare(b.qid))) {
201
+ insertSkill.run(skill.qid, skill.slug, skill.ws, skill.name, skill.path, stableCacheJson(skill));
202
+ }
203
+ const insertCapability = db.prepare("INSERT INTO capabilities (qid, kind, workspace, visibility, id, path, source_hash, json) VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
204
+ for (const record of options.capabilitiesIndex.records) {
205
+ insertCapability.run(record.qid, record.kind, record.workspace, record.visibility, record.id, record.path, record.source_hash, stableCacheJson(record));
206
+ }
207
+ const insertArchive = db.prepare("INSERT INTO archives (qid, visibility, compressed_path, compressed_sha256, json) VALUES (?, ?, ?, ?, ?)");
208
+ for (const node of Object.values(options.nodeIndex.nodes).filter((item) => item.type === "archive")) {
209
+ insertArchive.run(node.qid, String(node.attributes.visibility ?? "private"), String(node.attributes.compressed_path ?? ""), String(node.attributes.compressed_sha256 ?? ""), stableCacheJson(node));
210
+ }
211
+ const insertImport = db.prepare("INSERT INTO bundle_imports (alias, enabled, stale, error_count, warning_count, json) VALUES (?, ?, ?, ?, ?, ?)");
212
+ for (const item of options.importsIndex.imports) {
213
+ insertImport.run(item.alias, item.enabled ? 1 : 0, item.stale ? 1 : 0, item.error_count, item.warning_count, stableCacheJson(item));
214
+ }
215
+ }
216
+ finally {
217
+ db.close();
218
+ }
219
+ fs_1.default.renameSync(tempPath, sqlitePath);
220
+ return sqlitePath;
221
+ }
222
+ function reserveSqliteNumericId(options) {
223
+ if (!isSqliteBackend(options.config)) {
224
+ return undefined;
225
+ }
226
+ const sqlitePath = resolveSqlitePath(options.root, options.config);
227
+ fs_1.default.mkdirSync(path_1.default.dirname(sqlitePath), { recursive: true });
228
+ const DatabaseSync = loadDatabaseCtor();
229
+ const db = new DatabaseSync(sqlitePath);
230
+ try {
231
+ db.exec("CREATE TABLE IF NOT EXISTS id_allocations (ws TEXT NOT NULL, prefix TEXT NOT NULL, next_value INTEGER NOT NULL, PRIMARY KEY (ws, prefix));");
232
+ db.exec("BEGIN IMMEDIATE");
233
+ const row = db
234
+ .prepare("SELECT next_value FROM id_allocations WHERE ws = ? AND prefix = ?")
235
+ .get(options.ws, options.prefix);
236
+ const existing = typeof row?.next_value === "number" ? row.next_value : undefined;
237
+ const nextValue = Math.max(existing ?? 1, options.currentMax + 1);
238
+ db.prepare("INSERT INTO id_allocations (ws, prefix, next_value) VALUES (?, ?, ?) ON CONFLICT(ws, prefix) DO UPDATE SET next_value = excluded.next_value").run(options.ws, options.prefix, nextValue + 1);
239
+ db.exec("COMMIT");
240
+ return `${options.prefix}-${nextValue}`;
241
+ }
242
+ catch (err) {
243
+ try {
244
+ db.exec("ROLLBACK");
245
+ }
246
+ catch {
247
+ // ignore rollback failures when no transaction is active
248
+ }
249
+ throw err;
250
+ }
251
+ finally {
252
+ db.close();
253
+ }
254
+ }
255
+ function sqliteHealth(root, config) {
256
+ const sqlitePath = resolveSqlitePath(root, config);
257
+ const warnings = [];
258
+ const errors = [];
259
+ if (!fs_1.default.existsSync(sqlitePath)) {
260
+ warnings.push(`SQLite cache missing at ${toPosixPath(path_1.default.relative(root, sqlitePath))}; run mdkg index`);
261
+ return { path: sqlitePath, exists: false, size: 0, warnings, errors };
262
+ }
263
+ const size = fs_1.default.statSync(sqlitePath).size;
264
+ if (config.index.sqlite_commit_warning_bytes > 0 && size > config.index.sqlite_commit_warning_bytes) {
265
+ warnings.push(`SQLite cache exceeds ${config.index.sqlite_commit_warning_bytes} bytes: ${toPosixPath(path_1.default.relative(root, sqlitePath))} (${size} bytes)`);
266
+ }
267
+ for (const suffix of ["-wal", "-shm", "-journal"]) {
268
+ const transient = `${sqlitePath}${suffix}`;
269
+ if (fs_1.default.existsSync(transient)) {
270
+ warnings.push(`transient SQLite file present: ${toPosixPath(path_1.default.relative(root, transient))}`);
271
+ }
272
+ }
273
+ try {
274
+ const DatabaseSync = loadDatabaseCtor();
275
+ const db = new DatabaseSync(sqlitePath);
276
+ try {
277
+ const row = db.prepare("SELECT value FROM meta WHERE key = 'schema_version'").get();
278
+ if (String(row?.value ?? "") !== String(exports.SQLITE_SCHEMA_VERSION)) {
279
+ errors.push(`SQLite schema mismatch; run mdkg index`);
280
+ }
281
+ }
282
+ finally {
283
+ db.close();
284
+ }
285
+ }
286
+ catch (err) {
287
+ errors.push(`failed to read SQLite cache: ${err instanceof Error ? err.message : String(err)}`);
288
+ }
289
+ if ((0, staleness_1.isIndexStale)(root, config)) {
290
+ warnings.push("SQLite cache may be stale because source graph files or config changed; run mdkg index");
291
+ }
292
+ return { path: sqlitePath, exists: true, size, warnings, errors };
293
+ }
@@ -2,6 +2,8 @@
2
2
 
3
3
  This repository uses mdkg for deterministic project memory.
4
4
 
5
+ mdkg is pre-v1 public alpha software. Treat generated graph, cache, bundle, and DAL contracts as usable but still stabilizing before v1.
6
+
5
7
  Read these files in order:
6
8
  1. `.mdkg/core/SOUL.md` if it exists
7
9
  2. `.mdkg/core/HUMAN.md` if it exists
@@ -23,6 +25,7 @@ Agent operating prompt:
23
25
  - Use `mdkg search "..."` and `mdkg next` to discover current work.
24
26
  - Use `mdkg skill list`, `mdkg skill search`, and `mdkg skill show <slug>` for skill discovery.
25
27
  - Use `mdkg capability list/search/show` for deterministic skills, `SPEC.md`, `WORK.md`, core-doc, and design-doc capability discovery.
28
+ - Use `mdkg index` to refresh JSON compatibility caches and `.mdkg/index/mdkg.sqlite` when SQLite mode is enabled.
26
29
  - Use `mdkg archive add/list/show/verify/compress` for committed source and artifact sidecars under `.mdkg/archive`.
27
30
  - Use `mdkg work ...` helpers for semantic mirror contracts, work orders, receipts, and artifact registration.
28
31
  - Treat work contracts, orders, and receipts as committed semantic mirrors only; never store raw secrets, credentials, live payment state, ledger mutations, or canonical marketplace state in mdkg.
@@ -22,6 +22,11 @@ Primary commands:
22
22
  - `mdkg task`
23
23
  - `mdkg validate`
24
24
 
25
+ Index backend:
26
+ - fresh mdkg workspaces default to `index.backend: sqlite`
27
+ - `.mdkg/index/mdkg.sqlite` is rebuildable and commit-eligible when intentionally tracked
28
+ - JSON compatibility caches remain generated and ignored by default
29
+
25
30
  Validation commands:
26
31
  - `mdkg validate [--out <path>] [--quiet] [--json]`
27
32
 
@@ -2,6 +2,8 @@
2
2
 
3
3
  This repository is initialized for mdkg.
4
4
 
5
+ mdkg is pre-v1 public alpha software. Graph, cache, bundle, and DAL contracts may change quickly before v1.
6
+
5
7
  ## Layout
6
8
 
7
9
  - `core/`: rules, operating guide, and pinned docs
@@ -10,7 +12,7 @@ This repository is initialized for mdkg.
10
12
  - `templates/`: default node templates
11
13
  - `archive/`: sidecar metadata and deterministic compressed source/artifact caches
12
14
  - `bundles/`: optional committed full graph snapshot bundles
13
- - `index/`: generated index cache (do not commit)
15
+ - `index/`: generated JSON caches plus optional commit-eligible `mdkg.sqlite`
14
16
  - `pack/`: generated context packs (do not commit)
15
17
 
16
18
  ## Next Commands
@@ -51,10 +53,17 @@ Read `AGENT_START.md` first when this repo includes it.
51
53
 
52
54
  Ensure ignore files include:
53
55
 
54
- - `.mdkg/index/`
56
+ - `.mdkg/index/*.json`
57
+ - `.mdkg/index/*.tmp`
58
+ - `.mdkg/index/write.lock/`
59
+ - `.mdkg/index/*.sqlite-wal`
60
+ - `.mdkg/index/*.sqlite-shm`
61
+ - `.mdkg/index/*.sqlite-journal`
55
62
  - `.mdkg/pack/`
56
63
  - `.mdkg/archive/**/source/`
57
64
 
65
+ Fresh mdkg workspaces default to `index.backend: sqlite`; `.mdkg/index/mdkg.sqlite` is a rebuildable cache and may be committed when the repo intentionally tracks it and it stays reasonably small.
66
+
58
67
  Recommended:
59
68
 
60
69
  ```bash
@@ -8,7 +8,11 @@
8
8
  "index": {
9
9
  "auto_reindex": true,
10
10
  "tolerant": false,
11
- "global_index_path": ".mdkg/index/global.json"
11
+ "backend": "sqlite",
12
+ "global_index_path": ".mdkg/index/global.json",
13
+ "sqlite_path": ".mdkg/index/mdkg.sqlite",
14
+ "sqlite_commit_warning_bytes": 52428800,
15
+ "lock_timeout_ms": 10000
12
16
  },
13
17
  "capabilities": {
14
18
  "cache_path": ".mdkg/index/capabilities.json"
@@ -229,8 +229,9 @@ The cache is enabled by default.
229
229
 
230
230
  - Root global index lives at `.mdkg/index/global.json`
231
231
  - Root skills index lives at `.mdkg/index/skills.json`
232
+ - Root SQLite access cache lives at `.mdkg/index/mdkg.sqlite` when `index.backend` is `sqlite`.
232
233
  - Index is rebuilt automatically when stale unless disabled by flag/config.
233
- - `.mdkg/index/` is generated and MUST be gitignored.
234
+ - Generated JSON index/temp/lock files are ignored. `.mdkg/index/mdkg.sqlite` is rebuildable and may be committed only by explicit repo policy.
234
235
 
235
236
  ## Safety guidance (high level)
236
237
 
@@ -124,7 +124,7 @@ If a user provides an unqualified ID and it is ambiguous globally:
124
124
  - `.agents/skills/`
125
125
  - `.claude/skills/`
126
126
  - deterministic `core.md` pin insertion (`rule-soul`, then `rule-human`)
127
- - ignore policy for `.mdkg/index/`, `.mdkg/pack/`, and raw archive source copies under `.mdkg/archive/**/source/`
127
+ - ignore policy for generated JSON index/temp/lock files, `.mdkg/pack/`, and raw archive source copies under `.mdkg/archive/**/source/`
128
128
  - mirrored skills are append-focused outputs:
129
129
  - `.mdkg/skills/` remains canonical
130
130
  - unrelated existing folders under `.agents/skills/` and `.claude/skills/` are preserved
@@ -146,6 +146,7 @@ If a user provides an unqualified ID and it is ambiguous globally:
146
146
  - rebuild skills cache `.mdkg/index/skills.json` from `.mdkg/skills/<slug>/SKILL.md`
147
147
  - rebuild capability cache `.mdkg/index/capabilities.json` from skills, `SPEC.md`, `WORK.md`, core docs, and design docs
148
148
  - rebuild bundle import projection cache `.mdkg/index/imports.json` when bundle imports are configured
149
+ - rebuild SQLite access cache `.mdkg/index/mdkg.sqlite` when `index.backend` is `sqlite`
149
150
  - tolerate `.mdkg/skills/<slug>/SKILLS.md` on read with warning
150
151
  - fail validation if both `SKILL.md` and `SKILLS.md` exist in one skill directory
151
152
  - strict by default (fails on invalid frontmatter)
@@ -21,7 +21,8 @@ mdkg content may contain sensitive notes and internal project planning. This rul
21
21
 
22
22
  - `.mdkg/` must not be shipped to production deployments.
23
23
  - `.mdkg/` must not be published to npm.
24
- - `.mdkg/index/` must never be committed.
24
+ - Generated JSON index, temp, lock, WAL, SHM, and journal files under `.mdkg/index/` must not be committed.
25
+ - `.mdkg/index/mdkg.sqlite` is a rebuildable access cache and may be committed when the repo intentionally tracks it and it stays reasonably small.
25
26
  - `.mdkg/bundles/` may be committed only when the repo intentionally tracks private or public snapshot bundles.
26
27
 
27
28
  ## Git ignore requirements
@@ -30,13 +31,22 @@ The repo MUST ignore at minimum:
30
31
 
31
32
  - `node_modules/`
32
33
  - `dist/`
33
- - `.mdkg/index/`
34
+ - `.mdkg/index/*.json`
35
+ - `.mdkg/index/*.tmp`
36
+ - `.mdkg/index/write.lock/`
37
+ - `.mdkg/index/*.sqlite-wal`
38
+ - `.mdkg/index/*.sqlite-shm`
39
+ - `.mdkg/index/*.sqlite-journal`
34
40
  - `.mdkg/pack/`
35
41
  - `.mdkg/archive/**/source/`
36
42
 
37
43
  Recommended `.gitignore` entries:
38
- - `.mdkg/index/`
39
- - `.mdkg/index/**`
44
+ - `.mdkg/index/*.json`
45
+ - `.mdkg/index/*.tmp`
46
+ - `.mdkg/index/write.lock/`
47
+ - `.mdkg/index/*.sqlite-wal`
48
+ - `.mdkg/index/*.sqlite-shm`
49
+ - `.mdkg/index/*.sqlite-journal`
40
50
  - `.mdkg/pack/`
41
51
  - `.mdkg/archive/**/source/`
42
52
 
@@ -60,7 +70,12 @@ Additional belt-and-suspenders:
60
70
  If the repo is containerized:
61
71
  - `.dockerignore` SHOULD exclude:
62
72
  - `.mdkg/`
63
- - `.mdkg/index/`
73
+ - `.mdkg/index/*.json`
74
+ - `.mdkg/index/*.tmp`
75
+ - `.mdkg/index/write.lock/`
76
+ - `.mdkg/index/*.sqlite-wal`
77
+ - `.mdkg/index/*.sqlite-shm`
78
+ - `.mdkg/index/*.sqlite-journal`
64
79
  - `node_modules/`
65
80
  - `dist/` (if built in container)
66
81
  - any other local artifacts
@@ -72,8 +87,8 @@ For application builds:
72
87
 
73
88
  `mdkg init` updates ignore files by default for safety:
74
89
 
75
- - `.gitignore` appends `.mdkg/index/`, `.mdkg/pack/`
76
- - `.npmignore` appends `.mdkg/`, `.mdkg/index/`, `.mdkg/pack/`
90
+ - `.gitignore` appends generated index cache/temp/lock patterns, `.mdkg/pack/`, and raw archive source ignores.
91
+ - `.npmignore` appends `.mdkg/`, generated index cache/temp/lock patterns, and `.mdkg/pack/`.
77
92
  - `--no-update-ignores` disables these default writes
78
93
 
79
94
  Explicit flags remain available and take precedence:
@@ -84,9 +99,9 @@ Explicit flags remain available and take precedence:
84
99
 
85
100
  ## Index safety
86
101
 
87
- - `.mdkg/index/` is generated.
88
- - Index files may contain extracted metadata and could expose sensitive strings.
89
- - Index files MUST be ignored from git.
102
+ - `.mdkg/index/` contains generated caches.
103
+ - JSON index files may contain extracted metadata and could expose sensitive strings; they MUST be ignored from git.
104
+ - `.mdkg/index/mdkg.sqlite` contains the same rebuildable access data and may be committed only by explicit repo policy; `mdkg doctor` warns when it exceeds `index.sqlite_commit_warning_bytes`.
90
105
  - Index rebuild should be deterministic and safe to regenerate at any time.
91
106
 
92
107
  ## Bundle safety
@@ -115,7 +130,8 @@ Workspace-local `.mdkg/` directories (near code) should follow the same rules:
115
130
 
116
131
  ## Summary checklist
117
132
 
118
- - ✅ `.mdkg/index/` ignored
133
+ - ✅ generated JSON index/temp/lock files ignored
134
+ - ✅ `.mdkg/index/mdkg.sqlite` committed only by explicit repo policy
119
135
  - ✅ event logs are committed by default unless a repo chooses to ignore them manually
120
136
  - ✅ npm publishes only `dist/`, `README.md`, `LICENSE`
121
137
  - ✅ optional `.npmignore` excludes `.mdkg/`
@@ -37,14 +37,15 @@ The npm package MUST include:
37
37
 
38
38
  It MUST NOT include:
39
39
  - `.mdkg/` docs
40
- - `.mdkg/index/`
40
+ - generated `.mdkg/index/` caches
41
41
  - source code (optional; can be included later, but not required)
42
42
 
43
43
  ## Release checklist (v1)
44
44
 
45
45
  1) Ensure clean working tree
46
46
  - no uncommitted changes
47
- - all `.mdkg/index/` ignored
47
+ - generated JSON index/temp/lock files ignored
48
+ - `.mdkg/index/mdkg.sqlite` either intentionally tracked or absent
48
49
 
49
50
  2) Rebuild and validate
50
51
  - run `mdkg index`
@@ -79,4 +80,4 @@ Release notes should call out:
79
80
  - new commands / flags
80
81
  - changes to pack behavior
81
82
  - changes to config schema (and how migration behaves)
82
- - new node types or template changes
83
+ - new node types or template changes
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "schema_version": 1,
3
3
  "tool": "mdkg",
4
- "mdkg_version": "0.1.2",
4
+ "mdkg_version": "0.1.3",
5
5
  "files": [
6
6
  {
7
7
  "path": ".mdkg/config.json",
8
8
  "category": "config",
9
- "sha256": "8f0fd9ba020511e22d912f59c39bdd1c09706595564361694860ded3e9488e7e"
9
+ "sha256": "0319f2e92ae0030d67816c025802be50ec5eecc3274e75a4e0ded3e753d945b3"
10
10
  },
11
11
  {
12
12
  "path": ".mdkg/core/core.md",
@@ -26,7 +26,7 @@
26
26
  {
27
27
  "path": ".mdkg/core/rule-1-mdkg-conventions.md",
28
28
  "category": "core",
29
- "sha256": "20073cd21c148f1b93a382550f835ccb2e896cc676903328557004319235c112"
29
+ "sha256": "15f886e6661046de45ad798c292d74cc1ff3cc14c8cfcc82ed0b8c35635d0477"
30
30
  },
31
31
  {
32
32
  "path": ".mdkg/core/rule-2-context-pack-rules.md",
@@ -36,17 +36,17 @@
36
36
  {
37
37
  "path": ".mdkg/core/rule-3-cli-contract.md",
38
38
  "category": "core",
39
- "sha256": "ad151ac183dea1158884c4341154118d9623392871062b8faa6cecd4bde580ba"
39
+ "sha256": "4ff22c60e3b4093492f00c8ddd738f7241192c7b1edbfdcbdc4ed09d95192aa6"
40
40
  },
41
41
  {
42
42
  "path": ".mdkg/core/rule-4-repo-safety-and-ignores.md",
43
43
  "category": "core",
44
- "sha256": "b4638d8b5aeae74768fb971a8844aa473177df8814b929fedff6faf865a034cc"
44
+ "sha256": "4edf0efa29ec470e96cad3e0abc7b8f44e2e718d7a9959f9cefc5e1db7e02b6c"
45
45
  },
46
46
  {
47
47
  "path": ".mdkg/core/rule-5-release-and-versioning.md",
48
48
  "category": "core",
49
- "sha256": "4fc6648052f80b90b2efc173e3bcab16cb0bc6680bb8b04f76c66fff06ad19cd"
49
+ "sha256": "e97b00aa3ade29011f1f2b482042ec292f74aad8b1d72e2f8e691cdeca5d4f70"
50
50
  },
51
51
  {
52
52
  "path": ".mdkg/core/rule-6-templates-and-schemas.md",
@@ -61,7 +61,7 @@
61
61
  {
62
62
  "path": ".mdkg/README.md",
63
63
  "category": "mdkg_doc",
64
- "sha256": "fc85d07197da2fce189d295b6e6047de2a49b711adc74c991029faec6be385de"
64
+ "sha256": "6ff69157098b5cc780f063ffadabff36b70e8567cec7fc406d7550385c467d0e"
65
65
  },
66
66
  {
67
67
  "path": ".mdkg/skills/build-pack-and-execute-task/SKILL.md",
@@ -176,7 +176,7 @@
176
176
  {
177
177
  "path": "AGENT_START.md",
178
178
  "category": "startup_doc",
179
- "sha256": "6ff5915ea72d7453b2f7666e138808b6b0854123c7030bcced313a15f86c2b3a"
179
+ "sha256": "e5bd54a4321443216645b11fd5f27aff2e8f6c760f21dad12b6d0dbe634b0986"
180
180
  },
181
181
  {
182
182
  "path": "AGENTS.md",
@@ -191,7 +191,7 @@
191
191
  {
192
192
  "path": "CLI_COMMAND_MATRIX.md",
193
193
  "category": "startup_doc",
194
- "sha256": "f852a4f0a99c87aeb123a839c2902798ffdf5bf8012b4f367cb9c85905473ae7"
194
+ "sha256": "6e347620120c93eb9f330d137087f9e6b3279c629fcc24b65af561bd3cf385a0"
195
195
  },
196
196
  {
197
197
  "path": "llms.txt",
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.atomicWriteFile = atomicWriteFile;
7
+ exports.writeFileExclusive = writeFileExclusive;
8
+ const crypto_1 = __importDefault(require("crypto"));
9
+ const fs_1 = __importDefault(require("fs"));
10
+ const path_1 = __importDefault(require("path"));
11
+ function randomSuffix() {
12
+ return `${process.pid}-${Date.now()}-${crypto_1.default.randomBytes(6).toString("hex")}`;
13
+ }
14
+ function writeAndSync(filePath, data, flags) {
15
+ const handle = fs_1.default.openSync(filePath, flags);
16
+ try {
17
+ if (typeof data === "string") {
18
+ fs_1.default.writeFileSync(handle, data, "utf8");
19
+ }
20
+ else {
21
+ fs_1.default.writeFileSync(handle, data);
22
+ }
23
+ fs_1.default.fsyncSync(handle);
24
+ }
25
+ finally {
26
+ fs_1.default.closeSync(handle);
27
+ }
28
+ }
29
+ function atomicWriteFile(filePath, data) {
30
+ fs_1.default.mkdirSync(path_1.default.dirname(filePath), { recursive: true });
31
+ const tempPath = path_1.default.join(path_1.default.dirname(filePath), `.${path_1.default.basename(filePath)}.${randomSuffix()}.tmp`);
32
+ try {
33
+ writeAndSync(tempPath, data, "wx");
34
+ fs_1.default.renameSync(tempPath, filePath);
35
+ }
36
+ catch (err) {
37
+ fs_1.default.rmSync(tempPath, { force: true });
38
+ throw err;
39
+ }
40
+ }
41
+ function writeFileExclusive(filePath, data) {
42
+ fs_1.default.mkdirSync(path_1.default.dirname(filePath), { recursive: true });
43
+ writeAndSync(filePath, data, "wx");
44
+ }