mdkg 0.1.1 → 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.
Files changed (73) hide show
  1. package/CHANGELOG.md +104 -0
  2. package/README.md +124 -18
  3. package/dist/cli.js +567 -15
  4. package/dist/commands/archive.js +486 -0
  5. package/dist/commands/bundle.js +743 -0
  6. package/dist/commands/bundle_import.js +255 -0
  7. package/dist/commands/capability.js +162 -0
  8. package/dist/commands/checkpoint.js +31 -5
  9. package/dist/commands/doctor.js +269 -9
  10. package/dist/commands/format.js +38 -9
  11. package/dist/commands/index.js +12 -12
  12. package/dist/commands/init.js +194 -63
  13. package/dist/commands/init_manifest.js +19 -6
  14. package/dist/commands/list.js +5 -2
  15. package/dist/commands/new.js +36 -7
  16. package/dist/commands/next.js +7 -0
  17. package/dist/commands/node_card.js +4 -1
  18. package/dist/commands/pack.js +62 -2
  19. package/dist/commands/query_output.js +1 -0
  20. package/dist/commands/search.js +5 -2
  21. package/dist/commands/show.js +7 -14
  22. package/dist/commands/skill_mirror.js +22 -0
  23. package/dist/commands/task.js +23 -6
  24. package/dist/commands/upgrade.js +24 -1
  25. package/dist/commands/validate.js +20 -1
  26. package/dist/commands/work.js +397 -0
  27. package/dist/commands/workspace.js +12 -2
  28. package/dist/core/config.js +115 -1
  29. package/dist/graph/agent_file_types.js +78 -5
  30. package/dist/graph/archive_file.js +125 -0
  31. package/dist/graph/archive_integrity.js +66 -0
  32. package/dist/graph/bundle_imports.js +418 -0
  33. package/dist/graph/capabilities_index_cache.js +103 -0
  34. package/dist/graph/capabilities_indexer.js +231 -0
  35. package/dist/graph/frontmatter.js +19 -0
  36. package/dist/graph/index_cache.js +23 -6
  37. package/dist/graph/indexer.js +4 -1
  38. package/dist/graph/node.js +23 -4
  39. package/dist/graph/node_body.js +37 -0
  40. package/dist/graph/reindex.js +46 -0
  41. package/dist/graph/skills_index_cache.js +2 -2
  42. package/dist/graph/skills_indexer.js +8 -3
  43. package/dist/graph/sqlite_index.js +293 -0
  44. package/dist/graph/validate_graph.js +83 -7
  45. package/dist/graph/visibility.js +214 -0
  46. package/dist/graph/workspace_files.js +22 -0
  47. package/dist/init/AGENT_START.md +24 -0
  48. package/dist/init/CLI_COMMAND_MATRIX.md +61 -3
  49. package/dist/init/README.md +70 -4
  50. package/dist/init/config.json +18 -2
  51. package/dist/init/core/guide.md +6 -2
  52. package/dist/init/core/rule-1-mdkg-conventions.md +2 -1
  53. package/dist/init/core/rule-3-cli-contract.md +72 -4
  54. package/dist/init/core/rule-4-repo-safety-and-ignores.md +47 -11
  55. package/dist/init/core/rule-5-release-and-versioning.md +4 -3
  56. package/dist/init/core/rule-6-templates-and-schemas.md +7 -0
  57. package/dist/init/init-manifest.json +21 -16
  58. package/dist/init/skills/default/build-pack-and-execute-task/SKILL.md +2 -1
  59. package/dist/init/skills/default/verify-close-and-checkpoint/SKILL.md +26 -0
  60. package/dist/init/templates/default/archive.md +33 -0
  61. package/dist/init/templates/default/receipt.md +15 -1
  62. package/dist/init/templates/default/work.md +6 -1
  63. package/dist/init/templates/default/work_order.md +15 -1
  64. package/dist/pack/export_md.js +3 -0
  65. package/dist/pack/export_xml.js +3 -0
  66. package/dist/pack/order.js +1 -0
  67. package/dist/pack/pack.js +3 -13
  68. package/dist/util/argparse.js +30 -0
  69. package/dist/util/atomic.js +44 -0
  70. package/dist/util/lock.js +72 -0
  71. package/dist/util/refs.js +40 -0
  72. package/dist/util/zip.js +153 -0
  73. package/package.json +14 -5
@@ -0,0 +1,255 @@
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.runBundleImportAddCommand = runBundleImportAddCommand;
7
+ exports.runBundleImportListCommand = runBundleImportListCommand;
8
+ exports.runBundleImportRemoveCommand = runBundleImportRemoveCommand;
9
+ exports.runBundleImportEnableCommand = runBundleImportEnableCommand;
10
+ exports.runBundleImportDisableCommand = runBundleImportDisableCommand;
11
+ exports.runBundleImportVerifyCommand = runBundleImportVerifyCommand;
12
+ const fs_1 = __importDefault(require("fs"));
13
+ const path_1 = __importDefault(require("path"));
14
+ const config_1 = require("../core/config");
15
+ const migrate_1 = require("../core/migrate");
16
+ const workspace_path_1 = require("../core/workspace_path");
17
+ const bundle_imports_1 = require("../graph/bundle_imports");
18
+ const errors_1 = require("../util/errors");
19
+ const atomic_1 = require("../util/atomic");
20
+ const lock_1 = require("../util/lock");
21
+ const ALIAS_RE = /^[a-z][a-z0-9_]*$/;
22
+ function writeJson(value) {
23
+ console.log(JSON.stringify(value, null, 2));
24
+ }
25
+ function normalizeAlias(alias) {
26
+ if (alias === "all") {
27
+ throw new errors_1.UsageError("bundle import alias cannot be 'all'");
28
+ }
29
+ if (alias !== alias.toLowerCase() || !ALIAS_RE.test(alias)) {
30
+ throw new errors_1.UsageError("bundle import alias must be lowercase and use [a-z0-9_]");
31
+ }
32
+ return alias;
33
+ }
34
+ function normalizeVisibility(value) {
35
+ const normalized = (value ?? "private").toLowerCase();
36
+ if (normalized === "private" || normalized === "internal" || normalized === "public") {
37
+ return normalized;
38
+ }
39
+ throw new errors_1.UsageError("--visibility must be private, internal, or public");
40
+ }
41
+ function normalizeProfile(value) {
42
+ const normalized = (value ?? "private").toLowerCase();
43
+ if (normalized === "private" || normalized === "public") {
44
+ return normalized;
45
+ }
46
+ throw new errors_1.UsageError("--profile must be private or public");
47
+ }
48
+ function normalizeContained(value, label) {
49
+ try {
50
+ return (0, workspace_path_1.normalizeContainedWorkspacePath)(value, label);
51
+ }
52
+ catch (err) {
53
+ throw new errors_1.UsageError(err instanceof Error ? err.message : String(err));
54
+ }
55
+ }
56
+ function readRawConfig(root) {
57
+ const configPath = path_1.default.join(root, ".mdkg", "config.json");
58
+ if (!fs_1.default.existsSync(configPath)) {
59
+ throw new errors_1.NotFoundError(`config not found at ${configPath}`);
60
+ }
61
+ let parsed;
62
+ try {
63
+ parsed = JSON.parse(fs_1.default.readFileSync(configPath, "utf8"));
64
+ }
65
+ catch (err) {
66
+ throw new errors_1.UsageError(`failed to read config: ${err instanceof Error ? err.message : String(err)}`);
67
+ }
68
+ const migrated = (0, migrate_1.migrateConfig)(parsed).config;
69
+ (0, config_1.validateConfigSchema)(migrated);
70
+ if (typeof migrated !== "object" || migrated === null || Array.isArray(migrated)) {
71
+ throw new errors_1.UsageError("config must be a JSON object");
72
+ }
73
+ return { configPath, raw: migrated };
74
+ }
75
+ function writeRawConfig(configPath, raw) {
76
+ (0, atomic_1.atomicWriteFile)(configPath, `${JSON.stringify(raw, null, 2)}\n`);
77
+ }
78
+ function getImports(raw) {
79
+ const imports = raw.bundle_imports;
80
+ if (imports === undefined) {
81
+ raw.bundle_imports = {};
82
+ return raw.bundle_imports;
83
+ }
84
+ if (typeof imports !== "object" || imports === null || Array.isArray(imports)) {
85
+ throw new errors_1.UsageError("config.bundle_imports must be an object");
86
+ }
87
+ return imports;
88
+ }
89
+ function receiptForHealth(action, health) {
90
+ return {
91
+ action,
92
+ import: health,
93
+ };
94
+ }
95
+ function healthByAlias(root, alias) {
96
+ const config = (0, config_1.loadConfig)(root);
97
+ const health = (0, bundle_imports_1.buildBundleImportsIndex)(root, config).index.imports.find((item) => item.alias === alias);
98
+ if (!health) {
99
+ throw new errors_1.NotFoundError(`bundle import not found: ${alias}`);
100
+ }
101
+ return health;
102
+ }
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) {
108
+ const alias = normalizeAlias(options.alias);
109
+ const bundlePath = normalizeContained(options.bundlePath, "bundle import path");
110
+ const visibility = normalizeVisibility(options.visibility);
111
+ const expected_profile = normalizeProfile(options.profile);
112
+ if (visibility !== "private" && expected_profile !== "public") {
113
+ throw new errors_1.UsageError("--profile public is required when --visibility is public or internal");
114
+ }
115
+ const source_path = options.sourcePath
116
+ ? normalizeContained(options.sourcePath, "bundle import source path")
117
+ : undefined;
118
+ if (options.maxStaleSeconds !== undefined && (!Number.isInteger(options.maxStaleSeconds) || options.maxStaleSeconds <= 0)) {
119
+ throw new errors_1.UsageError("--max-stale-seconds must be a positive integer");
120
+ }
121
+ const { configPath, raw } = readRawConfig(options.root);
122
+ const imports = getImports(raw);
123
+ if (imports[alias]) {
124
+ throw new errors_1.UsageError(`bundle import already exists: ${alias}`);
125
+ }
126
+ const workspaces = raw.workspaces;
127
+ if (workspaces && workspaces[alias]) {
128
+ throw new errors_1.UsageError(`bundle import alias collides with workspace: ${alias}`);
129
+ }
130
+ imports[alias] = {
131
+ path: bundlePath,
132
+ enabled: true,
133
+ visibility,
134
+ expected_profile,
135
+ ...(source_path ? { source_path } : {}),
136
+ ...(options.sourceRepo ? { source_repo: options.sourceRepo } : {}),
137
+ ...(options.maxStaleSeconds !== undefined ? { max_stale_seconds: options.maxStaleSeconds } : {}),
138
+ };
139
+ raw.bundle_imports = imports;
140
+ const validated = (0, config_1.validateConfigSchema)(raw);
141
+ const health = (0, bundle_imports_1.buildBundleImportsIndex)(options.root, validated).index.imports.find((item) => item.alias === alias);
142
+ if (!health) {
143
+ throw new errors_1.NotFoundError(`bundle import not found after validation: ${alias}`);
144
+ }
145
+ if (health.error_count > 0) {
146
+ throw new errors_1.ValidationError(`bundle import ${alias} is invalid:\n${health.errors.join("\n")}`);
147
+ }
148
+ writeRawConfig(configPath, raw);
149
+ const receipt = receiptForHealth("added", health);
150
+ if (options.json) {
151
+ writeJson(receipt);
152
+ return;
153
+ }
154
+ console.log(`bundle import added: ${alias} (${bundlePath})`);
155
+ if (health.warning_count > 0) {
156
+ console.log(`warnings: ${health.warning_count}`);
157
+ }
158
+ }
159
+ function runBundleImportAddCommand(options) {
160
+ return withBundleImportLock(options.root, () => runBundleImportAddCommandLocked(options));
161
+ }
162
+ function runBundleImportListCommand(options) {
163
+ const config = (0, config_1.loadConfig)(options.root);
164
+ const imports = (0, bundle_imports_1.buildBundleImportsIndex)(options.root, config).index.imports;
165
+ if (options.json) {
166
+ writeJson({ action: "list", count: imports.length, imports });
167
+ return;
168
+ }
169
+ if (imports.length === 0) {
170
+ console.log("no bundle imports configured");
171
+ return;
172
+ }
173
+ for (const item of imports) {
174
+ const status = item.enabled ? item.error_count > 0 ? "invalid" : item.stale ? "stale" : "ok" : "disabled";
175
+ console.log(`${item.alias} | ${status} | ${item.visibility} | ${item.path}`);
176
+ }
177
+ }
178
+ function runBundleImportRemoveCommandLocked(options) {
179
+ const alias = normalizeAlias(options.alias);
180
+ const { configPath, raw } = readRawConfig(options.root);
181
+ const imports = getImports(raw);
182
+ const existing = imports[alias];
183
+ if (!existing) {
184
+ throw new errors_1.NotFoundError(`bundle import not found: ${alias}`);
185
+ }
186
+ delete imports[alias];
187
+ raw.bundle_imports = imports;
188
+ (0, config_1.validateConfigSchema)(raw);
189
+ writeRawConfig(configPath, raw);
190
+ const receipt = { action: "removed", import: { alias } };
191
+ if (options.json) {
192
+ writeJson(receipt);
193
+ return;
194
+ }
195
+ console.log(`bundle import removed: ${alias}`);
196
+ }
197
+ function runBundleImportRemoveCommand(options) {
198
+ return withBundleImportLock(options.root, () => runBundleImportRemoveCommandLocked(options));
199
+ }
200
+ function setBundleImportEnabledLocked(options, enabled) {
201
+ const alias = normalizeAlias(options.alias);
202
+ const { configPath, raw } = readRawConfig(options.root);
203
+ const imports = getImports(raw);
204
+ const existing = imports[alias];
205
+ if (!existing || typeof existing !== "object" || Array.isArray(existing)) {
206
+ throw new errors_1.NotFoundError(`bundle import not found: ${alias}`);
207
+ }
208
+ imports[alias] = { ...existing, enabled };
209
+ raw.bundle_imports = imports;
210
+ (0, config_1.validateConfigSchema)(raw);
211
+ writeRawConfig(configPath, raw);
212
+ const health = healthByAlias(options.root, alias);
213
+ const receipt = receiptForHealth(enabled ? "enabled" : "disabled", health);
214
+ if (options.json) {
215
+ writeJson(receipt);
216
+ return;
217
+ }
218
+ console.log(`bundle import ${enabled ? "enabled" : "disabled"}: ${alias}`);
219
+ }
220
+ function runBundleImportEnableCommand(options) {
221
+ withBundleImportLock(options.root, () => setBundleImportEnabledLocked(options, true));
222
+ }
223
+ function runBundleImportDisableCommand(options) {
224
+ withBundleImportLock(options.root, () => setBundleImportEnabledLocked(options, false));
225
+ }
226
+ function runBundleImportVerifyCommand(options) {
227
+ const config = (0, config_1.loadConfig)(options.root);
228
+ const all = options.all || !options.alias;
229
+ const imports = (0, bundle_imports_1.buildBundleImportsIndex)(options.root, config).index.imports.filter((item) => all ? true : item.alias === options.alias);
230
+ if (!all && imports.length === 0) {
231
+ throw new errors_1.NotFoundError(`bundle import not found: ${options.alias}`);
232
+ }
233
+ const ok = imports.every((item) => item.error_count === 0 && !item.stale);
234
+ const receipt = { action: "verified", ok, count: imports.length, imports };
235
+ if (options.json) {
236
+ writeJson(receipt);
237
+ }
238
+ else if (ok) {
239
+ console.log(`bundle imports verified: ${imports.length}`);
240
+ }
241
+ else {
242
+ console.log(`bundle import verify failed: ${imports.length}`);
243
+ for (const item of imports) {
244
+ for (const warning of item.warnings) {
245
+ console.log(`warning: ${item.alias}: ${warning}`);
246
+ }
247
+ for (const error of item.errors) {
248
+ console.log(`error: ${item.alias}: ${error}`);
249
+ }
250
+ }
251
+ }
252
+ if (!ok) {
253
+ throw new errors_1.ValidationError("bundle import verify failed");
254
+ }
255
+ }
@@ -0,0 +1,162 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runCapabilityListCommand = runCapabilityListCommand;
4
+ exports.runCapabilitySearchCommand = runCapabilitySearchCommand;
5
+ exports.runCapabilityShowCommand = runCapabilityShowCommand;
6
+ const config_1 = require("../core/config");
7
+ const capabilities_indexer_1 = require("../graph/capabilities_indexer");
8
+ const capabilities_index_cache_1 = require("../graph/capabilities_index_cache");
9
+ const bundle_imports_1 = require("../graph/bundle_imports");
10
+ const errors_1 = require("../util/errors");
11
+ function normalizeKind(value) {
12
+ if (value === undefined) {
13
+ return undefined;
14
+ }
15
+ const normalized = value.toLowerCase();
16
+ if (capabilities_indexer_1.CAPABILITY_KINDS.includes(normalized)) {
17
+ return normalized;
18
+ }
19
+ throw new errors_1.UsageError(`--kind must be one of ${capabilities_indexer_1.CAPABILITY_KINDS.join(", ")}`);
20
+ }
21
+ function normalizeVisibility(value) {
22
+ if (value === undefined) {
23
+ return undefined;
24
+ }
25
+ const normalized = value.toLowerCase();
26
+ if (capabilities_indexer_1.CAPABILITY_VISIBILITIES.includes(normalized)) {
27
+ return normalized;
28
+ }
29
+ throw new errors_1.UsageError(`--visibility must be one of ${capabilities_indexer_1.CAPABILITY_VISIBILITIES.join(", ")}`);
30
+ }
31
+ function loadRecords(options) {
32
+ const config = (0, config_1.loadConfig)(options.root);
33
+ const { index, stale, rebuilt } = (0, capabilities_index_cache_1.loadCapabilitiesIndex)({
34
+ root: options.root,
35
+ config,
36
+ useCache: !options.noCache,
37
+ allowReindex: !options.noReindex,
38
+ });
39
+ if (stale && !rebuilt && !options.noCache) {
40
+ console.error("warning: capabilities index is stale; run mdkg index to refresh");
41
+ }
42
+ const imported = (0, bundle_imports_1.buildImportedCapabilityRecords)(options.root, config);
43
+ for (const warning of imported.warnings) {
44
+ console.error(`warning: ${warning}`);
45
+ }
46
+ return [...index.records, ...imported.records];
47
+ }
48
+ function applyFilters(records, options) {
49
+ const kind = normalizeKind(options.kind);
50
+ const visibility = normalizeVisibility(options.visibility);
51
+ return records.filter((record) => {
52
+ if (kind && record.kind !== kind) {
53
+ return false;
54
+ }
55
+ if (visibility && record.visibility !== visibility) {
56
+ return false;
57
+ }
58
+ return true;
59
+ });
60
+ }
61
+ function capabilitySearchText(record) {
62
+ return [
63
+ record.kind,
64
+ record.workspace,
65
+ record.visibility,
66
+ record.id,
67
+ record.qid,
68
+ record.slug,
69
+ record.name,
70
+ record.title,
71
+ record.description,
72
+ record.path,
73
+ ...record.tags,
74
+ ...record.refs,
75
+ ...record.aliases,
76
+ ...record.links,
77
+ ...record.headings.map((heading) => heading.text),
78
+ JSON.stringify(record.spec ?? {}),
79
+ JSON.stringify(record.work ?? {}),
80
+ JSON.stringify(record.skill ?? {}),
81
+ ]
82
+ .filter((value) => typeof value === "string" && value.length > 0)
83
+ .join(" ")
84
+ .toLowerCase();
85
+ }
86
+ function matchesQuery(record, query) {
87
+ const terms = query
88
+ .toLowerCase()
89
+ .split(/\s+/)
90
+ .map((term) => term.trim())
91
+ .filter(Boolean);
92
+ if (terms.length === 0) {
93
+ return true;
94
+ }
95
+ const text = capabilitySearchText(record);
96
+ return terms.every((term) => text.includes(term));
97
+ }
98
+ function printCapabilityList(records, json, query) {
99
+ if (json) {
100
+ console.log(JSON.stringify({
101
+ kind: "capability",
102
+ query,
103
+ count: records.length,
104
+ items: records,
105
+ }, null, 2));
106
+ return;
107
+ }
108
+ if (records.length === 0) {
109
+ console.log(query ? `no capabilities matched query "${query}"` : "no capabilities matched current filters");
110
+ return;
111
+ }
112
+ console.log(`capabilities: ${records.length}`);
113
+ for (const record of records) {
114
+ const label = record.slug ?? record.id;
115
+ console.log(`${record.qid} | ${record.kind} | ${record.visibility} | ${label} | ${record.title}`);
116
+ }
117
+ }
118
+ function resolveCapability(records, id) {
119
+ const normalized = id.toLowerCase();
120
+ const exact = records.find((record) => record.qid === id || record.id === id);
121
+ if (exact) {
122
+ return exact;
123
+ }
124
+ const matches = records.filter((record) => record.slug === normalized ||
125
+ record.id === normalized ||
126
+ record.qid.toLowerCase() === normalized ||
127
+ `skill:${record.slug}` === normalized);
128
+ if (matches.length === 1) {
129
+ return matches[0];
130
+ }
131
+ if (matches.length > 1) {
132
+ throw new errors_1.UsageError(`capability reference is ambiguous: ${id}`);
133
+ }
134
+ throw new errors_1.NotFoundError(`capability not found: ${id}`);
135
+ }
136
+ function printCapability(record, json) {
137
+ if (json) {
138
+ console.log(JSON.stringify({ kind: "capability", item: record }, null, 2));
139
+ return;
140
+ }
141
+ console.log(`${record.qid} | ${record.kind} | ${record.visibility}`);
142
+ console.log(`title: ${record.title}`);
143
+ if (record.description) {
144
+ console.log(`description: ${record.description}`);
145
+ }
146
+ if (record.tags.length > 0) {
147
+ console.log(`tags: ${record.tags.join(", ")}`);
148
+ }
149
+ console.log(`path: ${record.path}`);
150
+ }
151
+ function runCapabilityListCommand(options) {
152
+ const records = applyFilters(loadRecords(options), options);
153
+ printCapabilityList(records, options.json);
154
+ }
155
+ function runCapabilitySearchCommand(options) {
156
+ const records = applyFilters(loadRecords(options), options).filter((record) => matchesQuery(record, options.query));
157
+ printCapabilityList(records, options.json, options.query);
158
+ }
159
+ function runCapabilityShowCommand(options) {
160
+ const records = loadRecords(options);
161
+ printCapability(resolveCapability(records, options.id), options.json);
162
+ }
@@ -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) {