mdkg 0.1.2 → 0.1.4
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 +51 -0
- package/README.md +31 -15
- package/dist/cli.js +182 -85
- package/dist/commands/archive.js +20 -8
- package/dist/commands/bundle.js +7 -7
- package/dist/commands/capability.js +118 -4
- package/dist/commands/checkpoint.js +31 -5
- package/dist/commands/doctor.js +61 -24
- package/dist/commands/index.js +12 -23
- package/dist/commands/init.js +7 -1
- package/dist/commands/list.js +1 -1
- package/dist/commands/new.js +33 -7
- package/dist/commands/next.js +1 -1
- package/dist/commands/node_card.js +1 -1
- package/dist/commands/pack.js +1 -1
- package/dist/commands/search.js +1 -1
- package/dist/commands/show.js +1 -1
- package/dist/commands/subgraph.js +312 -0
- package/dist/commands/task.js +21 -7
- package/dist/commands/upgrade.js +51 -4
- package/dist/commands/validate.js +12 -6
- package/dist/commands/work.js +44 -12
- package/dist/core/config.js +110 -39
- package/dist/graph/capabilities_index_cache.js +2 -2
- package/dist/graph/index_cache.js +14 -14
- package/dist/graph/indexer.js +1 -1
- 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/graph/{bundle_imports.js → subgraphs.js} +216 -142
- package/dist/graph/visibility.js +3 -3
- package/dist/init/AGENT_START.md +5 -1
- package/dist/init/CLI_COMMAND_MATRIX.md +21 -7
- package/dist/init/README.md +20 -10
- package/dist/init/config.json +6 -2
- package/dist/init/core/rule-1-mdkg-conventions.md +2 -1
- package/dist/init/core/rule-3-cli-contract.md +32 -24
- package/dist/init/core/rule-4-repo-safety-and-ignores.md +28 -12
- package/dist/init/core/rule-5-release-and-versioning.md +4 -3
- package/dist/init/init-manifest.json +10 -10
- package/dist/init/skills/default/verify-close-and-checkpoint/SKILL.md +1 -1
- package/dist/util/argparse.js +2 -0
- package/dist/util/atomic.js +44 -0
- package/dist/util/lock.js +72 -0
- package/package.json +13 -9
- package/dist/commands/bundle_import.js +0 -243
|
@@ -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 = 2;
|
|
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 subgraphs (
|
|
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
|
+
subgraphs: options.subgraphsIndex.subgraphs,
|
|
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 insertSubgraph = db.prepare("INSERT INTO subgraphs (alias, enabled, stale, error_count, warning_count, json) VALUES (?, ?, ?, ?, ?, ?)");
|
|
212
|
+
for (const item of options.subgraphsIndex.subgraphs) {
|
|
213
|
+
insertSubgraph.run(item.alias, item.enabled ? 1 : 0, item.stale ? 1 : 0, item.error_count, item.warning_count, stableCacheJson(item));
|
|
214
|
+
}
|
|
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
|
+
}
|