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.
Files changed (46) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/README.md +31 -15
  3. package/dist/cli.js +182 -85
  4. package/dist/commands/archive.js +20 -8
  5. package/dist/commands/bundle.js +7 -7
  6. package/dist/commands/capability.js +118 -4
  7. package/dist/commands/checkpoint.js +31 -5
  8. package/dist/commands/doctor.js +61 -24
  9. package/dist/commands/index.js +12 -23
  10. package/dist/commands/init.js +7 -1
  11. package/dist/commands/list.js +1 -1
  12. package/dist/commands/new.js +33 -7
  13. package/dist/commands/next.js +1 -1
  14. package/dist/commands/node_card.js +1 -1
  15. package/dist/commands/pack.js +1 -1
  16. package/dist/commands/search.js +1 -1
  17. package/dist/commands/show.js +1 -1
  18. package/dist/commands/subgraph.js +312 -0
  19. package/dist/commands/task.js +21 -7
  20. package/dist/commands/upgrade.js +51 -4
  21. package/dist/commands/validate.js +12 -6
  22. package/dist/commands/work.js +44 -12
  23. package/dist/core/config.js +110 -39
  24. package/dist/graph/capabilities_index_cache.js +2 -2
  25. package/dist/graph/index_cache.js +14 -14
  26. package/dist/graph/indexer.js +1 -1
  27. package/dist/graph/reindex.js +46 -0
  28. package/dist/graph/skills_index_cache.js +2 -2
  29. package/dist/graph/sqlite_index.js +293 -0
  30. package/dist/graph/{bundle_imports.js → subgraphs.js} +216 -142
  31. package/dist/graph/visibility.js +3 -3
  32. package/dist/init/AGENT_START.md +5 -1
  33. package/dist/init/CLI_COMMAND_MATRIX.md +21 -7
  34. package/dist/init/README.md +20 -10
  35. package/dist/init/config.json +6 -2
  36. package/dist/init/core/rule-1-mdkg-conventions.md +2 -1
  37. package/dist/init/core/rule-3-cli-contract.md +32 -24
  38. package/dist/init/core/rule-4-repo-safety-and-ignores.md +28 -12
  39. package/dist/init/core/rule-5-release-and-versioning.md +4 -3
  40. package/dist/init/init-manifest.json +10 -10
  41. package/dist/init/skills/default/verify-close-and-checkpoint/SKILL.md +1 -1
  42. package/dist/util/argparse.js +2 -0
  43. package/dist/util/atomic.js +44 -0
  44. package/dist/util/lock.js +72 -0
  45. package/package.json +13 -9
  46. package/dist/commands/bundle_import.js +0 -243
@@ -13,7 +13,7 @@ const path_1 = __importDefault(require("path"));
13
13
  const child_process_1 = require("child_process");
14
14
  const config_1 = require("../core/config");
15
15
  const capabilities_indexer_1 = require("../graph/capabilities_indexer");
16
- const bundle_imports_1 = require("../graph/bundle_imports");
16
+ const subgraphs_1 = require("../graph/subgraphs");
17
17
  const indexer_1 = require("../graph/indexer");
18
18
  const skills_indexer_1 = require("../graph/skills_indexer");
19
19
  const zip_1 = require("../util/zip");
@@ -386,11 +386,11 @@ function collectStringValues(value, out) {
386
386
  }
387
387
  }
388
388
  }
389
- function publicImportReferenceErrors(config, index, includedQids) {
390
- const privateImportAliases = new Set(Object.entries(config.bundle_imports)
389
+ function publicSubgraphReferenceErrors(config, index, includedQids) {
390
+ const privateSubgraphAliases = new Set(Object.entries(config.subgraphs)
391
391
  .filter(([, entry]) => entry.enabled && entry.visibility !== "public")
392
392
  .map(([alias]) => alias));
393
- if (privateImportAliases.size === 0) {
393
+ if (privateSubgraphAliases.size === 0) {
394
394
  return [];
395
395
  }
396
396
  const errors = [];
@@ -404,8 +404,8 @@ function publicImportReferenceErrors(config, index, includedQids) {
404
404
  values.push(...node.links, ...node.artifacts, ...node.refs, ...node.aliases);
405
405
  for (const value of values) {
406
406
  const [alias] = value.split(":");
407
- if (alias && privateImportAliases.has(alias)) {
408
- errors.push(`${node.qid} references private bundle import ${value}`);
407
+ if (alias && privateSubgraphAliases.has(alias)) {
408
+ errors.push(`${node.qid} references private subgraph ${value}`);
409
409
  }
410
410
  }
411
411
  }
@@ -465,7 +465,7 @@ function buildBundle(options) {
465
465
  const filteredIndex = filterIndex(index, config, selectedSet, profile);
466
466
  if (profile === "public") {
467
467
  const includedQids = new Set(Object.keys(filteredIndex.nodes));
468
- const mergedIndex = (0, bundle_imports_1.mergeBundleImportsIntoIndex)(index, (0, bundle_imports_1.buildBundleImportsIndex)(options.root, config));
468
+ const mergedIndex = (0, subgraphs_1.mergeSubgraphsIntoIndex)(index, (0, subgraphs_1.buildSubgraphsIndex)(options.root, config));
469
469
  const errors = (0, visibility_1.visibilityViolationMessages)((0, visibility_1.collectVisibilityViolations)(mergedIndex, config, {
470
470
  includedQids,
471
471
  scope: "public",
@@ -3,10 +3,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.runCapabilityListCommand = runCapabilityListCommand;
4
4
  exports.runCapabilitySearchCommand = runCapabilitySearchCommand;
5
5
  exports.runCapabilityShowCommand = runCapabilityShowCommand;
6
+ exports.runCapabilityResolveCommand = runCapabilityResolveCommand;
6
7
  const config_1 = require("../core/config");
7
8
  const capabilities_indexer_1 = require("../graph/capabilities_indexer");
8
9
  const capabilities_index_cache_1 = require("../graph/capabilities_index_cache");
9
- const bundle_imports_1 = require("../graph/bundle_imports");
10
+ const subgraphs_1 = require("../graph/subgraphs");
10
11
  const errors_1 = require("../util/errors");
11
12
  function normalizeKind(value) {
12
13
  if (value === undefined) {
@@ -39,11 +40,11 @@ function loadRecords(options) {
39
40
  if (stale && !rebuilt && !options.noCache) {
40
41
  console.error("warning: capabilities index is stale; run mdkg index to refresh");
41
42
  }
42
- const imported = (0, bundle_imports_1.buildImportedCapabilityRecords)(options.root, config);
43
- for (const warning of imported.warnings) {
43
+ const subgraph = (0, subgraphs_1.buildSubgraphCapabilityRecords)(options.root, config);
44
+ for (const warning of subgraph.warnings) {
44
45
  console.error(`warning: ${warning}`);
45
46
  }
46
- return [...index.records, ...imported.records];
47
+ return [...index.records, ...subgraph.records];
47
48
  }
48
49
  function applyFilters(records, options) {
49
50
  const kind = normalizeKind(options.kind);
@@ -95,6 +96,89 @@ function matchesQuery(record, query) {
95
96
  const text = capabilitySearchText(record);
96
97
  return terms.every((term) => text.includes(term));
97
98
  }
99
+ function recordSource(record) {
100
+ const source = record.source;
101
+ return source ?? {};
102
+ }
103
+ function isStale(record) {
104
+ return recordSource(record).stale === true;
105
+ }
106
+ function hasReadPermission(record) {
107
+ const permissions = recordSource(record).permissions;
108
+ return !Array.isArray(permissions) || permissions.includes("read");
109
+ }
110
+ function requirementMatch(record, required) {
111
+ if (!required) {
112
+ return 0;
113
+ }
114
+ const normalized = required.toLowerCase();
115
+ const haystack = [
116
+ ...record.refs,
117
+ ...record.tags,
118
+ JSON.stringify(record.spec ?? {}),
119
+ JSON.stringify(record.work ?? {}),
120
+ JSON.stringify(record.skill ?? {}),
121
+ ]
122
+ .join(" ")
123
+ .toLowerCase();
124
+ return haystack.includes(normalized) ? 1 : 0;
125
+ }
126
+ function linkageScore(record) {
127
+ let score = 0;
128
+ if (record.kind === "work") {
129
+ score += 2;
130
+ }
131
+ if (record.kind === "spec") {
132
+ score += 1;
133
+ }
134
+ if (record.work || record.spec) {
135
+ score += 1;
136
+ }
137
+ return score;
138
+ }
139
+ function resolveScore(record, options) {
140
+ let score = 0;
141
+ if (record.workspace === "root") {
142
+ score += 1000;
143
+ }
144
+ if (!isStale(record)) {
145
+ score += 500;
146
+ }
147
+ if (hasReadPermission(record)) {
148
+ score += 100;
149
+ }
150
+ if (options.query && matchesQuery(record, options.query)) {
151
+ score += 50;
152
+ }
153
+ score += requirementMatch(record, options.requires) * 25;
154
+ score += linkageScore(record);
155
+ return score;
156
+ }
157
+ function resolveCapabilities(records, options) {
158
+ const filtered = records.filter((record) => {
159
+ if (options.freshOnly && isStale(record)) {
160
+ return false;
161
+ }
162
+ if (options.query && !matchesQuery(record, options.query)) {
163
+ return false;
164
+ }
165
+ if (options.requires && requirementMatch(record, options.requires) === 0) {
166
+ return false;
167
+ }
168
+ return true;
169
+ });
170
+ return filtered.sort((a, b) => {
171
+ const scoreDelta = resolveScore(b, options) - resolveScore(a, options);
172
+ if (scoreDelta !== 0) {
173
+ return scoreDelta;
174
+ }
175
+ const qidDelta = a.qid.localeCompare(b.qid);
176
+ if (qidDelta !== 0) {
177
+ return qidDelta;
178
+ }
179
+ return a.path.localeCompare(b.path);
180
+ });
181
+ }
98
182
  function printCapabilityList(records, json, query) {
99
183
  if (json) {
100
184
  console.log(JSON.stringify({
@@ -160,3 +244,33 @@ function runCapabilityShowCommand(options) {
160
244
  const records = loadRecords(options);
161
245
  printCapability(resolveCapability(records, options.id), options.json);
162
246
  }
247
+ function runCapabilityResolveCommand(options) {
248
+ const records = applyFilters(loadRecords(options), options);
249
+ const items = resolveCapabilities(records, options).map((record, rank) => ({
250
+ rank: rank + 1,
251
+ score: resolveScore(record, options),
252
+ stale: isStale(record),
253
+ item: record,
254
+ }));
255
+ if (options.json) {
256
+ console.log(JSON.stringify({
257
+ kind: "capability.resolve",
258
+ query: options.query,
259
+ requires: options.requires,
260
+ fresh_only: options.freshOnly === true,
261
+ count: items.length,
262
+ items,
263
+ }, null, 2));
264
+ return;
265
+ }
266
+ if (items.length === 0) {
267
+ console.log("no capabilities resolved");
268
+ return;
269
+ }
270
+ console.log(`resolved capabilities: ${items.length}`);
271
+ for (const result of items) {
272
+ const record = result.item;
273
+ const stale = result.stale ? " stale" : "";
274
+ console.log(`${result.rank}. ${record.qid} | score ${result.score}${stale} | ${record.kind} | ${record.title}`);
275
+ }
276
+ }
@@ -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 `chk-${max + 1}`;
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 createCheckpoint(options) {
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 = nextCheckpointId(index, ws);
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
- fs_1.default.mkdirSync(workDir, { recursive: true });
133
- fs_1.default.writeFileSync(filePath, content, "utf8");
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) {
@@ -9,35 +9,41 @@ const path_1 = __importDefault(require("path"));
9
9
  const config_1 = require("../core/config");
10
10
  const index_cache_1 = require("../graph/index_cache");
11
11
  const capabilities_index_cache_1 = require("../graph/capabilities_index_cache");
12
- const bundle_imports_1 = require("../graph/bundle_imports");
12
+ const subgraphs_1 = require("../graph/subgraphs");
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;
18
+ const REQUIRED_NODE_MAJOR = 24;
19
+ const REQUIRED_NODE_MINOR = 15;
18
20
  const ARCHIVE_RAW_ALLOWED_DIRS = new Set(["source"]);
19
- function parseNodeMajor(version) {
20
- const major = Number.parseInt(version.split(".")[0] ?? "", 10);
21
- if (!Number.isInteger(major)) {
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 major = parseNodeMajor(nodeVersion);
29
- if (major === null) {
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 [];
@@ -150,45 +186,45 @@ function runBundleStorageCheck(root, outputDir) {
150
186
  detail: `${bundles.length} bundle(s) found; run \`mdkg bundle verify <path>\` to check freshness before handoff`,
151
187
  };
152
188
  }
153
- function runBundleImportChecks(root, config) {
154
- const projection = (0, bundle_imports_1.buildBundleImportsIndex)(root, config);
155
- if (projection.index.imports.length === 0) {
189
+ function runSubgraphChecks(root, config) {
190
+ const projection = (0, subgraphs_1.buildSubgraphsIndex)(root, config);
191
+ if (projection.index.subgraphs.length === 0) {
156
192
  return [
157
193
  {
158
- name: "bundle-imports",
194
+ name: "subgraphs",
159
195
  ok: true,
160
- detail: "no bundle imports configured",
196
+ detail: "no subgraphs configured",
161
197
  },
162
198
  ];
163
199
  }
164
- return projection.index.imports.map((item) => {
200
+ return projection.index.subgraphs.map((item) => {
165
201
  if (!item.enabled) {
166
202
  return {
167
- name: `bundle-import:${item.alias}`,
203
+ name: `subgraph:${item.alias}`,
168
204
  ok: true,
169
205
  level: "warn",
170
- detail: `disabled import at ${item.path}`,
206
+ detail: "disabled subgraph",
171
207
  };
172
208
  }
173
209
  if (item.error_count > 0) {
174
210
  return {
175
- name: `bundle-import:${item.alias}`,
211
+ name: `subgraph:${item.alias}`,
176
212
  ok: false,
177
213
  detail: item.errors.join("; "),
178
214
  };
179
215
  }
180
216
  if (item.stale || item.warning_count > 0) {
181
217
  return {
182
- name: `bundle-import:${item.alias}`,
218
+ name: `subgraph:${item.alias}`,
183
219
  ok: true,
184
220
  level: "warn",
185
- detail: `import is stale or has warnings; run \`mdkg bundle import verify ${item.alias}\` (${item.warnings.join("; ")})`,
221
+ detail: `subgraph is stale or has warnings; run \`mdkg subgraph verify ${item.alias}\` (${item.warnings.join("; ")})`,
186
222
  };
187
223
  }
188
224
  return {
189
- name: `bundle-import:${item.alias}`,
225
+ name: `subgraph:${item.alias}`,
190
226
  ok: true,
191
- detail: `import loaded from ${item.path}`,
227
+ detail: `subgraph loaded from ${item.sources.map((source) => source.path).join(", ")}`,
192
228
  };
193
229
  });
194
230
  }
@@ -262,7 +298,8 @@ 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));
265
- results.push(...runBundleImportChecks(options.root, config));
301
+ results.push(runSqliteCheck(options.root, config));
302
+ results.push(...runSubgraphChecks(options.root, config));
266
303
  results.push(runVisibilityPolicyCheck(options.root, config, options));
267
304
  try {
268
305
  const templateSchemaInfo = (0, template_schema_1.loadTemplateSchemasWithInfo)(options.root, config, node_1.ALLOWED_TYPES);
@@ -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 indexer_1 = require("../graph/indexer");
10
- const index_cache_1 = require("../graph/index_cache");
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
- const nodeIndex = (0, indexer_1.buildIndex)(options.root, config, { tolerant: options.tolerant });
19
- const skillsIndex = (0, skills_indexer_1.buildSkillsIndex)(options.root, config);
20
- const capabilitiesIndex = (0, capabilities_indexer_1.buildCapabilitiesIndex)(options.root, config, nodeIndex);
21
- const importsIndex = (0, bundle_imports_1.buildBundleImportsIndex)(options.root, config);
22
- const nodesOutputPath = path_1.default.resolve(options.root, config.index.global_index_path);
23
- const skillsOutputPath = (0, skills_indexer_1.resolveSkillsIndexPath)(options.root);
24
- const capabilitiesOutputPath = (0, capabilities_indexer_1.resolveCapabilitiesIndexPath)(options.root, config);
25
- const importsOutputPath = (0, bundle_imports_1.resolveBundleImportsIndexPath)(options.root);
26
- (0, index_cache_1.writeIndex)(nodesOutputPath, nodeIndex);
27
- (0, skills_index_cache_1.writeSkillsIndex)(skillsOutputPath, skillsIndex);
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(`subgraphs index written: ${path_1.default.relative(options.root, result.paths.subgraphs)}`);
19
+ if (result.paths.sqlite) {
20
+ console.log(`sqlite index written: ${path_1.default.relative(options.root, result.paths.sqlite)}`);
21
+ }
22
+ });
34
23
  }
@@ -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
  ])) {
@@ -18,7 +18,7 @@ function normalizeWorkspace(value) {
18
18
  function runListCommand(options) {
19
19
  const config = (0, config_1.loadConfig)(options.root);
20
20
  const ws = normalizeWorkspace(options.ws);
21
- if (ws && !config.workspaces[ws] && !config.bundle_imports[ws]) {
21
+ if (ws && !config.workspaces[ws] && !config.subgraphs[ws]) {
22
22
  throw new errors_1.NotFoundError(`workspace not found: ${ws}`);
23
23
  }
24
24
  const normalizedType = options.type?.toLowerCase();
@@ -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 `${prefix}-${max + 1}`;
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 runNewCommand(options) {
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
- : nextIdForPrefix(index.nodes, ws, prefix);
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
- fs_1.default.mkdirSync(path_1.default.dirname(filePath), { recursive: true });
321
- fs_1.default.writeFileSync(filePath, content, "utf8");
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
- const outputPath = path_1.default.resolve(options.root, config.index.global_index_path);
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
+ }
@@ -63,7 +63,7 @@ function runNextCommand(options) {
63
63
  }
64
64
  const node = index.nodes[resolved.qid];
65
65
  if (node.source?.imported) {
66
- console.error("no local next item: imported bundle nodes are read-only planning context");
66
+ console.error("no local next item: subgraph nodes are read-only planning context");
67
67
  return;
68
68
  }
69
69
  const nextQid = node.edges.next;
@@ -8,7 +8,7 @@ function formatStatusPriority(node) {
8
8
  }
9
9
  function formatNodeCard(node) {
10
10
  const sourceLabel = node.source?.imported
11
- ? ` | import:${node.source.import_alias}${node.source.stale ? ":stale" : ""} | read-only`
11
+ ? ` | subgraph:${node.source.subgraph_alias}${node.source.stale ? ":stale" : ""} | read-only`
12
12
  : "";
13
13
  return [
14
14
  node.qid,
@@ -318,7 +318,7 @@ function printDryRunSummary(pack, stats, format) {
318
318
  function runPackCommand(options) {
319
319
  const config = (0, config_1.loadConfig)(options.root);
320
320
  const ws = normalizeWorkspace(options.ws);
321
- if (ws && !config.workspaces[ws] && !config.bundle_imports[ws]) {
321
+ if (ws && !config.workspaces[ws] && !config.subgraphs[ws]) {
322
322
  throw new errors_1.NotFoundError(`workspace not found: ${ws}`);
323
323
  }
324
324
  const { index, rebuilt, stale, warnings } = (0, index_cache_1.loadIndex)({
@@ -54,7 +54,7 @@ function runSearchCommand(options) {
54
54
  }
55
55
  const config = (0, config_1.loadConfig)(options.root);
56
56
  const ws = normalizeWorkspace(options.ws);
57
- if (ws && !config.workspaces[ws] && !config.bundle_imports[ws]) {
57
+ if (ws && !config.workspaces[ws] && !config.subgraphs[ws]) {
58
58
  throw new errors_1.NotFoundError(`workspace not found: ${ws}`);
59
59
  }
60
60
  const normalizedType = options.type?.toLowerCase();
@@ -29,7 +29,7 @@ function formatAttributeLine(key, value) {
29
29
  function runShowCommand(options) {
30
30
  const config = (0, config_1.loadConfig)(options.root);
31
31
  const ws = normalizeWorkspace(options.ws);
32
- if (ws && !config.workspaces[ws] && !config.bundle_imports[ws]) {
32
+ if (ws && !config.workspaces[ws] && !config.subgraphs[ws]) {
33
33
  throw new errors_1.NotFoundError(`workspace not found: ${ws}`);
34
34
  }
35
35
  const normalizedId = options.id.toLowerCase();