mdkg 0.0.2 → 0.0.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 (50) hide show
  1. package/README.md +171 -151
  2. package/dist/cli.js +920 -422
  3. package/dist/commands/checkpoint.js +17 -6
  4. package/dist/commands/doctor.js +156 -0
  5. package/dist/commands/event.js +46 -0
  6. package/dist/commands/event_support.js +146 -0
  7. package/dist/commands/format.js +6 -7
  8. package/dist/commands/index.js +10 -4
  9. package/dist/commands/init.js +202 -11
  10. package/dist/commands/list.js +18 -1
  11. package/dist/commands/new.js +30 -5
  12. package/dist/commands/pack.js +332 -10
  13. package/dist/commands/query_output.js +84 -0
  14. package/dist/commands/search.js +22 -5
  15. package/dist/commands/show.js +26 -11
  16. package/dist/commands/skill.js +359 -0
  17. package/dist/commands/skill_support.js +121 -0
  18. package/dist/commands/task.js +270 -0
  19. package/dist/commands/validate.js +104 -7
  20. package/dist/graph/edges.js +2 -2
  21. package/dist/graph/frontmatter.js +1 -0
  22. package/dist/graph/indexer.js +21 -0
  23. package/dist/graph/node.js +20 -4
  24. package/dist/graph/skills_index_cache.js +94 -0
  25. package/dist/graph/skills_indexer.js +160 -0
  26. package/dist/init/README.md +43 -0
  27. package/dist/init/core/rule-1-mdkg-conventions.md +9 -2
  28. package/dist/init/core/rule-3-cli-contract.md +73 -14
  29. package/dist/init/core/rule-4-repo-safety-and-ignores.md +9 -3
  30. package/dist/init/core/rule-6-templates-and-schemas.md +6 -2
  31. package/dist/init/skills/SKILL.md.example +41 -0
  32. package/dist/init/templates/default/bug.md +1 -0
  33. package/dist/init/templates/default/chk.md +1 -0
  34. package/dist/init/templates/default/epic.md +1 -0
  35. package/dist/init/templates/default/feat.md +1 -0
  36. package/dist/init/templates/default/task.md +1 -0
  37. package/dist/init/templates/default/test.md +1 -0
  38. package/dist/pack/budget.js +186 -0
  39. package/dist/pack/export_md.js +17 -1
  40. package/dist/pack/export_xml.js +15 -0
  41. package/dist/pack/metrics.js +66 -0
  42. package/dist/pack/pack.js +35 -0
  43. package/dist/pack/profile.js +222 -0
  44. package/dist/pack/stats.js +37 -0
  45. package/dist/templates/headings.js +34 -0
  46. package/dist/util/argparse.js +47 -1
  47. package/dist/util/filter.js +18 -0
  48. package/dist/util/id.js +23 -0
  49. package/dist/util/output.js +2 -2
  50. package/package.json +6 -2
@@ -7,7 +7,17 @@ exports.runInitCommand = runInitCommand;
7
7
  const fs_1 = __importDefault(require("fs"));
8
8
  const path_1 = __importDefault(require("path"));
9
9
  const errors_1 = require("../util/errors");
10
+ const date_1 = require("../util/date");
11
+ const skill_support_1 = require("./skill_support");
10
12
  const DEFAULT_SEED_SUBDIR = path_1.default.resolve(__dirname, "..", "init");
13
+ const SOUL_PIN_ID = "rule-soul";
14
+ const HUMAN_PIN_ID = "rule-human";
15
+ const DEFAULT_CORE_LIST_HEADER = [
16
+ "# mdkg verbose core list",
17
+ "",
18
+ "# One node ID per line. Lines starting with # are comments.",
19
+ "# This list is included by `mdkg pack --verbose`.",
20
+ ];
11
21
  function listFiles(dir) {
12
22
  if (!fs_1.default.existsSync(dir)) {
13
23
  return [];
@@ -59,16 +69,166 @@ function appendIgnoreEntries(filePath, entries) {
59
69
  fs_1.default.writeFileSync(filePath, updated, "utf8");
60
70
  return true;
61
71
  }
72
+ function writeFileIfMissing(filePath, content, force, stats) {
73
+ if (fs_1.default.existsSync(filePath) && !force) {
74
+ stats.skipped += 1;
75
+ return;
76
+ }
77
+ fs_1.default.mkdirSync(path_1.default.dirname(filePath), { recursive: true });
78
+ fs_1.default.writeFileSync(filePath, content, "utf8");
79
+ stats.created += 1;
80
+ }
81
+ function soulTemplate(created) {
82
+ return [
83
+ "---",
84
+ `id: ${SOUL_PIN_ID}`,
85
+ "type: rule",
86
+ "title: agent soul and execution contract",
87
+ "tags: [omni, agent, constraints]",
88
+ "owners: []",
89
+ "links: []",
90
+ "artifacts: []",
91
+ "relates: []",
92
+ "refs: []",
93
+ "aliases: [soul]",
94
+ `created: ${created}`,
95
+ `updated: ${created}`,
96
+ "---",
97
+ "",
98
+ "# Purpose",
99
+ "",
100
+ "Define the canonical agent execution boundaries and memory contract for this repository.",
101
+ "",
102
+ "# Scope",
103
+ "",
104
+ "Applies to all orchestrators and coding-agent executions using mdkg in this repo.",
105
+ "",
106
+ "# Requirements",
107
+ "",
108
+ "- Ask for approval before destructive operations or policy-sensitive actions.",
109
+ "- Prefer deterministic mdkg packs over ad-hoc context assembly.",
110
+ "- Follow single-writer commit discipline and event-driven batching.",
111
+ "- Never place secrets in mdkg docs or generated packs.",
112
+ "",
113
+ "# Notes",
114
+ "",
115
+ "Customize this file to encode repo-specific constraints, approval boundaries, and memory cadence.",
116
+ "",
117
+ ].join("\n");
118
+ }
119
+ function humanTemplate(created) {
120
+ return [
121
+ "---",
122
+ `id: ${HUMAN_PIN_ID}`,
123
+ "type: rule",
124
+ "title: human working profile and collaboration preferences",
125
+ "tags: [human, collaboration, preferences]",
126
+ "owners: []",
127
+ "links: []",
128
+ "artifacts: []",
129
+ "relates: []",
130
+ "refs: []",
131
+ "aliases: [human]",
132
+ `created: ${created}`,
133
+ `updated: ${created}`,
134
+ "---",
135
+ "",
136
+ "# Purpose",
137
+ "",
138
+ "Capture stable collaboration preferences and boundaries so agents can work with less ambiguity.",
139
+ "",
140
+ "# Scope",
141
+ "",
142
+ "Applies to planning, implementation, and review interactions in this repository.",
143
+ "",
144
+ "# Requirements",
145
+ "",
146
+ "- Keep top goals, boundaries, and style preferences current.",
147
+ "- Include ask-before-doing constraints for risky or high-impact actions.",
148
+ "- Record preferred environment assumptions and validation commands.",
149
+ "",
150
+ "# Notes",
151
+ "",
152
+ "Suggested prompts:",
153
+ "- What are your top 3 goals in this repo right now?",
154
+ "- What should never happen without confirmation?",
155
+ "- What coding/review style should the agent prefer?",
156
+ "- What OS/runtime/test commands should be assumed?",
157
+ "",
158
+ ].join("\n");
159
+ }
160
+ function seededInitEvent(nowIso) {
161
+ const event = {
162
+ ts: nowIso,
163
+ run_id: `init-${nowIso.replace(/[^0-9]/g, "").slice(0, 14)}`,
164
+ workspace: "root",
165
+ agent: "mdkg",
166
+ kind: "RUN_STARTED",
167
+ status: "ok",
168
+ refs: ["edd-4"],
169
+ artifacts: [],
170
+ notes: "init omni scaffold target initialized",
171
+ redacted: true,
172
+ };
173
+ return `${JSON.stringify(event)}\n`;
174
+ }
175
+ function parseCoreList(raw) {
176
+ const lines = raw.split(/\r?\n/);
177
+ const header = [];
178
+ const ids = [];
179
+ let seenFirstId = false;
180
+ for (const line of lines) {
181
+ const trimmed = line.trim();
182
+ const isComment = trimmed.startsWith("#");
183
+ if (!seenFirstId && (trimmed.length === 0 || isComment)) {
184
+ header.push(line);
185
+ continue;
186
+ }
187
+ if (trimmed.length === 0 || isComment) {
188
+ seenFirstId = true;
189
+ continue;
190
+ }
191
+ seenFirstId = true;
192
+ ids.push(trimmed.toLowerCase());
193
+ }
194
+ return { header, ids };
195
+ }
196
+ function ensureCorePins(coreListPath, requiredPins) {
197
+ const raw = fs_1.default.existsSync(coreListPath) ? fs_1.default.readFileSync(coreListPath, "utf8") : "";
198
+ const { header, ids } = parseCoreList(raw);
199
+ const seen = new Set();
200
+ const dedupedExisting = [];
201
+ for (const id of ids) {
202
+ if (seen.has(id)) {
203
+ continue;
204
+ }
205
+ seen.add(id);
206
+ dedupedExisting.push(id);
207
+ }
208
+ const required = requiredPins.map((value) => value.toLowerCase());
209
+ const filteredExisting = dedupedExisting.filter((value) => !required.includes(value));
210
+ const finalIds = [...required, ...filteredExisting];
211
+ const headerLines = header.length > 0 ? header : DEFAULT_CORE_LIST_HEADER;
212
+ const normalizedHeader = headerLines.slice();
213
+ while (normalizedHeader.length > 0 && normalizedHeader[normalizedHeader.length - 1].trim() === "") {
214
+ normalizedHeader.pop();
215
+ }
216
+ const output = [...normalizedHeader, "", ...finalIds, ""].join("\n");
217
+ fs_1.default.mkdirSync(path_1.default.dirname(coreListPath), { recursive: true });
218
+ fs_1.default.writeFileSync(coreListPath, output, "utf8");
219
+ }
62
220
  function runInitCommand(options) {
63
221
  const root = path_1.default.resolve(options.root);
64
222
  const seedRoot = options.seedRoot ? path_1.default.resolve(options.seedRoot) : DEFAULT_SEED_SUBDIR;
65
223
  const createAgents = Boolean(options.createAgents || options.createLlm);
66
224
  const createClaude = Boolean(options.createClaude || options.createLlm);
225
+ const force = Boolean(options.force);
67
226
  const seedConfig = path_1.default.join(seedRoot, "config.json");
68
227
  const seedCore = path_1.default.join(seedRoot, "core");
69
228
  const seedTemplates = path_1.default.join(seedRoot, "templates");
70
229
  const seedAgents = path_1.default.join(seedRoot, "AGENTS.md");
71
230
  const seedClaude = path_1.default.join(seedRoot, "CLAUDE.md");
231
+ const seedReadme = path_1.default.join(seedRoot, "README.md");
72
232
  if (!fs_1.default.existsSync(seedConfig) || !fs_1.default.existsSync(seedCore) || !fs_1.default.existsSync(seedTemplates)) {
73
233
  throw new errors_1.NotFoundError(`init assets not found at ${seedRoot} (try reinstalling mdkg)`);
74
234
  }
@@ -78,24 +238,54 @@ function runInitCommand(options) {
78
238
  if (createClaude && !fs_1.default.existsSync(seedClaude)) {
79
239
  throw new errors_1.NotFoundError(`init assets missing CLAUDE.md at ${seedRoot}`);
80
240
  }
241
+ if (!fs_1.default.existsSync(seedReadme)) {
242
+ throw new errors_1.NotFoundError(`init assets missing README.md at ${seedRoot}`);
243
+ }
81
244
  const mdkgDir = path_1.default.join(root, ".mdkg");
82
245
  fs_1.default.mkdirSync(mdkgDir, { recursive: true });
83
246
  fs_1.default.mkdirSync(path_1.default.join(mdkgDir, "work"), { recursive: true });
84
247
  fs_1.default.mkdirSync(path_1.default.join(mdkgDir, "design"), { recursive: true });
85
248
  const stats = { created: 0, skipped: 0 };
86
- copySeedFile(seedConfig, path_1.default.join(mdkgDir, "config.json"), Boolean(options.force), stats);
87
- copySeedDir(seedCore, path_1.default.join(mdkgDir, "core"), Boolean(options.force), stats);
88
- copySeedDir(seedTemplates, path_1.default.join(mdkgDir, "templates"), Boolean(options.force), stats);
249
+ copySeedFile(seedConfig, path_1.default.join(mdkgDir, "config.json"), force, stats);
250
+ copySeedFile(seedReadme, path_1.default.join(mdkgDir, "README.md"), force, stats);
251
+ copySeedDir(seedCore, path_1.default.join(mdkgDir, "core"), force, stats);
252
+ copySeedDir(seedTemplates, path_1.default.join(mdkgDir, "templates"), force, stats);
89
253
  if (createAgents) {
90
- copySeedFile(seedAgents, path_1.default.join(root, "AGENTS.md"), Boolean(options.force), stats);
254
+ copySeedFile(seedAgents, path_1.default.join(root, "AGENTS.md"), force, stats);
91
255
  }
92
256
  if (createClaude) {
93
- copySeedFile(seedClaude, path_1.default.join(root, "CLAUDE.md"), Boolean(options.force), stats);
257
+ copySeedFile(seedClaude, path_1.default.join(root, "CLAUDE.md"), force, stats);
258
+ }
259
+ if (options.omni) {
260
+ const today = (0, date_1.formatDate)(new Date());
261
+ const soulPath = path_1.default.join(mdkgDir, "core", "SOUL.md");
262
+ const humanPath = path_1.default.join(mdkgDir, "core", "HUMAN.md");
263
+ const skillsDir = path_1.default.join(mdkgDir, "skills");
264
+ const registryPath = path_1.default.join(skillsDir, "registry.md");
265
+ const eventsDir = path_1.default.join(mdkgDir, "work", "events");
266
+ const eventsPath = path_1.default.join(eventsDir, "events.jsonl");
267
+ fs_1.default.mkdirSync(skillsDir, { recursive: true });
268
+ fs_1.default.mkdirSync(eventsDir, { recursive: true });
269
+ writeFileIfMissing(soulPath, soulTemplate(today), force, stats);
270
+ writeFileIfMissing(humanPath, humanTemplate(today), force, stats);
271
+ writeFileIfMissing(registryPath, (0, skill_support_1.registryTemplate)(), force, stats);
272
+ if (!fs_1.default.existsSync(eventsPath) || force) {
273
+ writeFileIfMissing(eventsPath, seededInitEvent(new Date().toISOString()), force, stats);
274
+ }
275
+ const coreListPath = path_1.default.join(mdkgDir, "core", "core.md");
276
+ ensureCorePins(coreListPath, [SOUL_PIN_ID, HUMAN_PIN_ID]);
94
277
  }
95
- if (options.updateGitignore) {
96
- appendIgnoreEntries(path_1.default.join(root, ".gitignore"), [".mdkg/index/", ".mdkg/pack/"]);
278
+ const noUpdateIgnores = Boolean(options.noUpdateIgnores);
279
+ const shouldUpdateGitignore = Boolean(options.updateGitignore || !noUpdateIgnores);
280
+ const shouldUpdateNpmignore = Boolean(options.updateNpmignore || !noUpdateIgnores);
281
+ if (shouldUpdateGitignore) {
282
+ appendIgnoreEntries(path_1.default.join(root, ".gitignore"), [
283
+ ".mdkg/index/",
284
+ ".mdkg/pack/",
285
+ ".mdkg/work/events/*.jsonl",
286
+ ]);
97
287
  }
98
- if (options.updateNpmignore) {
288
+ if (shouldUpdateNpmignore) {
99
289
  appendIgnoreEntries(path_1.default.join(root, ".npmignore"), [".mdkg/", ".mdkg/index/", ".mdkg/pack/"]);
100
290
  }
101
291
  if (options.updateDockerignore) {
@@ -103,9 +293,10 @@ function runInitCommand(options) {
103
293
  }
104
294
  console.log(`mdkg init complete: ${stats.created} file(s) created, ${stats.skipped} skipped`);
105
295
  console.log("next:");
106
- console.log(" mdkg index");
107
296
  console.log(' mdkg new task "..." --status todo --priority 1');
108
- console.log(" mdkg list --status todo");
109
- console.log(" mdkg pack <id> --verbose");
297
+ console.log(' mdkg search "..."');
298
+ console.log(" mdkg show <id>");
299
+ console.log(" mdkg next");
300
+ console.log(" mdkg pack <id>");
110
301
  console.log(" mdkg validate");
111
302
  }
@@ -8,6 +8,7 @@ const errors_1 = require("../util/errors");
8
8
  const qid_1 = require("../util/qid");
9
9
  const sort_1 = require("../util/sort");
10
10
  const node_card_1 = require("./node_card");
11
+ const query_output_1 = require("./query_output");
11
12
  function normalizeWorkspace(value) {
12
13
  if (!value || value === "all") {
13
14
  return undefined;
@@ -20,6 +21,10 @@ function runListCommand(options) {
20
21
  if (ws && !config.workspaces[ws]) {
21
22
  throw new errors_1.NotFoundError(`workspace not found: ${ws}`);
22
23
  }
24
+ const normalizedType = options.type?.toLowerCase();
25
+ if (normalizedType === "skill") {
26
+ throw new errors_1.UsageError("--type skill is no longer supported here; use `mdkg skill list`");
27
+ }
23
28
  const { index, rebuilt, stale } = (0, index_cache_1.loadIndex)({
24
29
  root: options.root,
25
30
  config,
@@ -39,13 +44,25 @@ function runListCommand(options) {
39
44
  }
40
45
  const filtered = (0, filter_1.filterNodes)(Object.values(index.nodes), {
41
46
  ws,
42
- type: options.type,
47
+ type: normalizedType,
43
48
  status: options.status,
44
49
  epic: epicQid,
45
50
  priority: options.priority,
46
51
  blocked: options.blocked,
52
+ tags: options.tags,
53
+ tagsMode: options.tagsMode,
47
54
  });
48
55
  const sorted = (0, sort_1.sortNodesByQid)(filtered);
56
+ if (options.json) {
57
+ (0, query_output_1.writeJson)({
58
+ command: "list",
59
+ kind: "node",
60
+ count: sorted.length,
61
+ items: sorted.map(query_output_1.toNodeSummaryJson),
62
+ });
63
+ return;
64
+ }
65
+ (0, query_output_1.writeCount)(sorted.length, sorted.length === 0 ? "no nodes matched current filters" : undefined);
49
66
  for (const node of sorted) {
50
67
  console.log((0, node_card_1.formatNodeCard)(node));
51
68
  }
@@ -14,10 +14,11 @@ const loader_1 = require("../templates/loader");
14
14
  const date_1 = require("../util/date");
15
15
  const errors_1 = require("../util/errors");
16
16
  const qid_1 = require("../util/qid");
17
- const ID_RE = /^[a-z]+-[0-9]+$/;
18
- const ID_REF_RE = /^([a-z][a-z0-9_]*:)?[a-z]+-[0-9]+$/;
17
+ const id_1 = require("../util/id");
18
+ const event_support_1 = require("./event_support");
19
19
  const DEC_ID_RE = /^dec-[0-9]+$/;
20
20
  const DEC_STATUS = new Set(["proposed", "accepted", "rejected", "superseded"]);
21
+ const SKILL_SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
21
22
  const CORE_TYPES = new Set(["rule"]);
22
23
  const DESIGN_TYPES = new Set(["prd", "edd", "dec", "prop"]);
23
24
  function parseCsvList(raw) {
@@ -31,14 +32,14 @@ function parseCsvList(raw) {
31
32
  }
32
33
  function normalizeId(value, key) {
33
34
  const normalized = value.toLowerCase();
34
- if (!ID_RE.test(normalized) && normalized !== "rule-guide") {
35
- throw new errors_1.UsageError(`${key} entries must match <prefix>-<number>: ${value}`);
35
+ if (!(0, id_1.isCanonicalId)(normalized)) {
36
+ throw new errors_1.UsageError(`${key} entries must match <prefix>-<number> or reserved id: ${value}`);
36
37
  }
37
38
  return normalized;
38
39
  }
39
40
  function normalizeIdRef(value, key) {
40
41
  const normalized = value.toLowerCase();
41
- if (!ID_REF_RE.test(normalized)) {
42
+ if (!(0, id_1.isCanonicalIdRef)(normalized)) {
42
43
  throw new errors_1.UsageError(`${key} entries must match <id> or <ws>:<id>: ${value}`);
43
44
  }
44
45
  return normalized;
@@ -55,6 +56,15 @@ function normalizeIdList(raw, key) {
55
56
  function normalizeIdRefList(raw, key) {
56
57
  return parseCsvList(raw).map((value) => normalizeIdRef(value, key));
57
58
  }
59
+ function normalizeSkillList(raw) {
60
+ return parseCsvList(raw).map((value) => {
61
+ const normalized = value.toLowerCase();
62
+ if (!SKILL_SLUG_RE.test(normalized)) {
63
+ throw new errors_1.UsageError(`--skills entries must be kebab-case: ${value}`);
64
+ }
65
+ return normalized;
66
+ });
67
+ }
58
68
  function normalizeWorkspace(value) {
59
69
  if (!value) {
60
70
  return "root";
@@ -206,8 +216,12 @@ function runNewCommand(options) {
206
216
  const tags = normalizeLowercaseList(options.tags);
207
217
  const owners = normalizeLowercaseList(options.owners);
208
218
  const cases = normalizeLowercaseList(options.cases);
219
+ const skills = normalizeSkillList(options.skills);
209
220
  const links = normalizeList(options.links);
210
221
  const artifacts = normalizeList(options.artifacts);
222
+ if (skills.length > 0 && !node_1.WORK_TYPES.has(type)) {
223
+ throw new errors_1.UsageError("--skills is only valid for work items");
224
+ }
211
225
  if (type === "dec" && options.supersedes) {
212
226
  const supersedes = options.supersedes.toLowerCase();
213
227
  if (!DEC_ID_RE.test(supersedes)) {
@@ -263,6 +277,7 @@ function runNewCommand(options) {
263
277
  artifacts: artifacts.length > 0 ? artifacts : undefined,
264
278
  refs: refs.length > 0 ? refs : undefined,
265
279
  aliases: aliases.length > 0 ? aliases : undefined,
280
+ skills: skills.length > 0 ? skills : undefined,
266
281
  cases: cases.length > 0 ? cases : undefined,
267
282
  supersedes: options.supersedes ? options.supersedes.toLowerCase() : undefined,
268
283
  created: today,
@@ -275,5 +290,15 @@ function runNewCommand(options) {
275
290
  const outputPath = path_1.default.resolve(options.root, config.index.global_index_path);
276
291
  (0, index_cache_1.writeIndex)(outputPath, updatedIndex);
277
292
  }
293
+ (0, event_support_1.appendAutomaticEvent)({
294
+ root: options.root,
295
+ ws,
296
+ kind: "NODE_CREATED",
297
+ status: "ok",
298
+ refs: [id],
299
+ notes: `node created via mdkg new`,
300
+ runId: options.runId,
301
+ now: options.now,
302
+ });
278
303
  console.log(`node created: ${ws}:${id} (${path_1.default.relative(options.root, filePath)})`);
279
304
  }