mdkg 0.0.3 → 0.0.5
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/README.md +181 -169
- package/dist/cli.js +1002 -624
- package/dist/commands/checkpoint.js +17 -6
- package/dist/commands/event.js +46 -0
- package/dist/commands/event_support.js +146 -0
- package/dist/commands/format.js +6 -7
- package/dist/commands/index.js +10 -4
- package/dist/commands/init.js +224 -16
- package/dist/commands/list.js +18 -1
- package/dist/commands/new.js +30 -5
- package/dist/commands/pack.js +194 -2
- package/dist/commands/query_output.js +84 -0
- package/dist/commands/search.js +22 -5
- package/dist/commands/show.js +26 -11
- package/dist/commands/skill.js +374 -0
- package/dist/commands/skill_mirror.js +290 -0
- package/dist/commands/skill_support.js +122 -0
- package/dist/commands/task.js +278 -0
- package/dist/commands/validate.js +106 -7
- package/dist/graph/edges.js +2 -2
- package/dist/graph/frontmatter.js +1 -0
- package/dist/graph/indexer.js +21 -0
- package/dist/graph/node.js +20 -4
- package/dist/graph/skills_index_cache.js +94 -0
- package/dist/graph/skills_indexer.js +160 -0
- package/dist/init/AGENTS.md +6 -41
- package/dist/init/AGENT_START.md +46 -0
- package/dist/init/CLAUDE.md +6 -35
- package/dist/init/CLI_COMMAND_MATRIX.md +29 -0
- package/dist/init/README.md +6 -4
- package/dist/init/core/HUMAN.md +36 -0
- package/dist/init/core/SOUL.md +257 -0
- package/dist/init/core/core.md +3 -1
- package/dist/init/core/rule-1-mdkg-conventions.md +9 -2
- package/dist/init/core/rule-3-cli-contract.md +81 -14
- package/dist/init/core/rule-4-repo-safety-and-ignores.md +9 -3
- package/dist/init/core/rule-6-templates-and-schemas.md +6 -2
- package/dist/init/llms.txt +17 -0
- package/dist/init/skills/SKILL.md.example +41 -0
- package/dist/init/templates/default/bug.md +1 -0
- package/dist/init/templates/default/chk.md +1 -0
- package/dist/init/templates/default/epic.md +1 -0
- package/dist/init/templates/default/feat.md +1 -0
- package/dist/init/templates/default/task.md +1 -0
- package/dist/init/templates/default/test.md +1 -0
- package/dist/pack/export_md.js +6 -0
- package/dist/pack/export_xml.js +6 -0
- package/dist/pack/pack.js +35 -0
- package/dist/util/argparse.js +36 -5
- package/dist/util/filter.js +18 -0
- package/dist/util/id.js +23 -0
- package/package.json +5 -2
|
@@ -0,0 +1,122 @@
|
|
|
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.SKILL_REGISTRY_END = exports.SKILL_REGISTRY_START = void 0;
|
|
7
|
+
exports.resolveSkillTemplatePath = resolveSkillTemplatePath;
|
|
8
|
+
exports.loadSkillTemplate = loadSkillTemplate;
|
|
9
|
+
exports.renderSkillTemplate = renderSkillTemplate;
|
|
10
|
+
exports.registryTemplate = registryTemplate;
|
|
11
|
+
exports.ensureSkillsRegistry = ensureSkillsRegistry;
|
|
12
|
+
exports.refreshSkillsRegistry = refreshSkillsRegistry;
|
|
13
|
+
exports.formatSkillCard = formatSkillCard;
|
|
14
|
+
const fs_1 = __importDefault(require("fs"));
|
|
15
|
+
const path_1 = __importDefault(require("path"));
|
|
16
|
+
const skills_indexer_1 = require("../graph/skills_indexer");
|
|
17
|
+
exports.SKILL_REGISTRY_START = "<!-- mdkg:skill-registry:start -->";
|
|
18
|
+
exports.SKILL_REGISTRY_END = "<!-- mdkg:skill-registry:end -->";
|
|
19
|
+
function renderYamlList(values) {
|
|
20
|
+
return `[${values.join(", ")}]`;
|
|
21
|
+
}
|
|
22
|
+
function renderOptionalLine(key, values) {
|
|
23
|
+
if (values.length === 0) {
|
|
24
|
+
return "";
|
|
25
|
+
}
|
|
26
|
+
return `${key}: ${renderYamlList(values)}\n`;
|
|
27
|
+
}
|
|
28
|
+
function resolveSkillTemplatePath() {
|
|
29
|
+
return path_1.default.resolve(__dirname, "..", "init", "skills", "SKILL.md.example");
|
|
30
|
+
}
|
|
31
|
+
function loadSkillTemplate() {
|
|
32
|
+
const templatePath = resolveSkillTemplatePath();
|
|
33
|
+
if (!fs_1.default.existsSync(templatePath)) {
|
|
34
|
+
throw new Error(`missing skill template artifact: ${templatePath}`);
|
|
35
|
+
}
|
|
36
|
+
return fs_1.default.readFileSync(templatePath, "utf8");
|
|
37
|
+
}
|
|
38
|
+
function renderSkillTemplate(data) {
|
|
39
|
+
const template = loadSkillTemplate();
|
|
40
|
+
return template
|
|
41
|
+
.replace("{{name}}", data.name)
|
|
42
|
+
.replace("{{description}}", data.description)
|
|
43
|
+
.replace("{{tags_block}}", renderOptionalLine("tags", data.tags))
|
|
44
|
+
.replace("{{authors_block}}", renderOptionalLine("authors", data.authors))
|
|
45
|
+
.replace("{{links_block}}", renderOptionalLine("links", data.links));
|
|
46
|
+
}
|
|
47
|
+
function registryTemplate() {
|
|
48
|
+
return [
|
|
49
|
+
"# Skills Registry",
|
|
50
|
+
"",
|
|
51
|
+
"This directory stores Agent Skills packages used by mdkg tooling and orchestrators.",
|
|
52
|
+
"",
|
|
53
|
+
"Use `mdkg skill new <slug> \"<name>\" --description \"...\"` to scaffold a new skill from the built-in Anthropic-aligned template.",
|
|
54
|
+
"Use `mdkg skill sync` to mirror canonical skills into `.agents/skills/` and `.claude/skills/` when agent bootstrap is enabled.",
|
|
55
|
+
"Use `CLI_COMMAND_MATRIX.md` as the canonical command and flag reference when updating skill procedures.",
|
|
56
|
+
"",
|
|
57
|
+
"## Conventions",
|
|
58
|
+
"",
|
|
59
|
+
"- One folder per skill slug.",
|
|
60
|
+
"- Use `SKILL.md` as the canonical skill entrypoint.",
|
|
61
|
+
"- Keep procedures deterministic and avoid embedding secrets.",
|
|
62
|
+
"- Create `scripts/` only when deterministic execution cannot be expressed safely as instructions.",
|
|
63
|
+
"",
|
|
64
|
+
"## Registered Skills",
|
|
65
|
+
"",
|
|
66
|
+
`${exports.SKILL_REGISTRY_START}`,
|
|
67
|
+
"_No skills registered yet. Run `mdkg skill new` to add one._",
|
|
68
|
+
`${exports.SKILL_REGISTRY_END}`,
|
|
69
|
+
"",
|
|
70
|
+
].join("\n");
|
|
71
|
+
}
|
|
72
|
+
function renderRegistryLines(skillsIndex) {
|
|
73
|
+
const skills = Object.values(skillsIndex.skills).sort((a, b) => a.slug.localeCompare(b.slug));
|
|
74
|
+
if (skills.length === 0) {
|
|
75
|
+
return ["_No skills registered yet. Run `mdkg skill new` to add one._"];
|
|
76
|
+
}
|
|
77
|
+
const lines = [];
|
|
78
|
+
for (const skill of skills) {
|
|
79
|
+
lines.push(`- \`${skill.slug}\``);
|
|
80
|
+
lines.push(` - name: \`${skill.name}\``);
|
|
81
|
+
const stageTag = skill.tags.find((tag) => tag.startsWith("stage:"));
|
|
82
|
+
if (stageTag) {
|
|
83
|
+
lines.push(` - stage: \`${stageTag}\``);
|
|
84
|
+
}
|
|
85
|
+
const writerTag = skill.tags.find((tag) => tag.startsWith("writer:"));
|
|
86
|
+
if (writerTag) {
|
|
87
|
+
lines.push(` - writer role: \`${writerTag}\``);
|
|
88
|
+
}
|
|
89
|
+
lines.push(` - description: ${skill.description}`);
|
|
90
|
+
}
|
|
91
|
+
return lines;
|
|
92
|
+
}
|
|
93
|
+
function replaceManagedSection(raw, lines) {
|
|
94
|
+
const managed = `${exports.SKILL_REGISTRY_START}\n${lines.join("\n")}\n${exports.SKILL_REGISTRY_END}`;
|
|
95
|
+
if (raw.includes(exports.SKILL_REGISTRY_START) && raw.includes(exports.SKILL_REGISTRY_END)) {
|
|
96
|
+
const pattern = new RegExp(`${exports.SKILL_REGISTRY_START}[\\s\\S]*?${exports.SKILL_REGISTRY_END}`, "m");
|
|
97
|
+
const replaced = raw.replace(pattern, managed);
|
|
98
|
+
return replaced.endsWith("\n") ? replaced : `${replaced}\n`;
|
|
99
|
+
}
|
|
100
|
+
const trimmed = raw.trimEnd();
|
|
101
|
+
const prefix = trimmed.length > 0 ? `${trimmed}\n\n## Registered Skills\n\n` : "## Registered Skills\n\n";
|
|
102
|
+
return `${prefix}${managed}\n`;
|
|
103
|
+
}
|
|
104
|
+
function ensureSkillsRegistry(root, config) {
|
|
105
|
+
const skillsRoot = (0, skills_indexer_1.resolveSkillsRoot)(root, config);
|
|
106
|
+
const registryPath = path_1.default.join(skillsRoot, "registry.md");
|
|
107
|
+
if (!fs_1.default.existsSync(registryPath)) {
|
|
108
|
+
fs_1.default.mkdirSync(path_1.default.dirname(registryPath), { recursive: true });
|
|
109
|
+
fs_1.default.writeFileSync(registryPath, registryTemplate(), "utf8");
|
|
110
|
+
}
|
|
111
|
+
return registryPath;
|
|
112
|
+
}
|
|
113
|
+
function refreshSkillsRegistry(root, config) {
|
|
114
|
+
const registryPath = ensureSkillsRegistry(root, config);
|
|
115
|
+
const raw = fs_1.default.readFileSync(registryPath, "utf8");
|
|
116
|
+
const index = (0, skills_indexer_1.buildSkillsIndex)(root, config);
|
|
117
|
+
const updated = replaceManagedSection(raw, renderRegistryLines(index));
|
|
118
|
+
fs_1.default.writeFileSync(registryPath, updated, "utf8");
|
|
119
|
+
}
|
|
120
|
+
function formatSkillCard(skill) {
|
|
121
|
+
return [skill.qid, "skill", "-/-", skill.name, skill.path].join(" | ");
|
|
122
|
+
}
|
|
@@ -0,0 +1,278 @@
|
|
|
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.runTaskStartCommand = runTaskStartCommand;
|
|
7
|
+
exports.runTaskUpdateCommand = runTaskUpdateCommand;
|
|
8
|
+
exports.runTaskDoneCommand = runTaskDoneCommand;
|
|
9
|
+
const fs_1 = __importDefault(require("fs"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const config_1 = require("../core/config");
|
|
12
|
+
const indexer_1 = require("../graph/indexer");
|
|
13
|
+
const index_cache_1 = require("../graph/index_cache");
|
|
14
|
+
const frontmatter_1 = require("../graph/frontmatter");
|
|
15
|
+
const skills_indexer_1 = require("../graph/skills_indexer");
|
|
16
|
+
const date_1 = require("../util/date");
|
|
17
|
+
const errors_1 = require("../util/errors");
|
|
18
|
+
const qid_1 = require("../util/qid");
|
|
19
|
+
const id_1 = require("../util/id");
|
|
20
|
+
const event_support_1 = require("./event_support");
|
|
21
|
+
const checkpoint_1 = require("./checkpoint");
|
|
22
|
+
const MUTABLE_TASK_TYPES = new Set(["task", "bug", "test"]);
|
|
23
|
+
const SKILL_SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
24
|
+
function parseCsvList(raw) {
|
|
25
|
+
if (!raw) {
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
return raw
|
|
29
|
+
.split(",")
|
|
30
|
+
.map((value) => value.trim())
|
|
31
|
+
.filter(Boolean);
|
|
32
|
+
}
|
|
33
|
+
function normalizeWorkspace(value) {
|
|
34
|
+
if (!value) {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
const normalized = value.toLowerCase();
|
|
38
|
+
if (normalized === "all") {
|
|
39
|
+
throw new errors_1.UsageError("--ws all is not valid here");
|
|
40
|
+
}
|
|
41
|
+
return normalized;
|
|
42
|
+
}
|
|
43
|
+
function normalizeId(value, flag) {
|
|
44
|
+
const normalized = value.trim().toLowerCase();
|
|
45
|
+
if (!(0, id_1.isCanonicalId)(normalized)) {
|
|
46
|
+
throw new errors_1.UsageError(`${flag} entries must match <prefix>-<number> or reserved id: ${value}`);
|
|
47
|
+
}
|
|
48
|
+
return normalized;
|
|
49
|
+
}
|
|
50
|
+
function normalizeIdRef(value, flag) {
|
|
51
|
+
const normalized = value.trim().toLowerCase();
|
|
52
|
+
if (!(0, id_1.isCanonicalIdRef)(normalized)) {
|
|
53
|
+
throw new errors_1.UsageError(`${flag} entries must match <id> or <ws>:<id>: ${value}`);
|
|
54
|
+
}
|
|
55
|
+
return normalized;
|
|
56
|
+
}
|
|
57
|
+
function normalizeLowercaseList(raw) {
|
|
58
|
+
return parseCsvList(raw).map((value) => value.toLowerCase());
|
|
59
|
+
}
|
|
60
|
+
function normalizeIdList(raw, flag) {
|
|
61
|
+
return parseCsvList(raw).map((value) => normalizeId(value, flag));
|
|
62
|
+
}
|
|
63
|
+
function normalizeIdRefList(raw, flag) {
|
|
64
|
+
return parseCsvList(raw).map((value) => normalizeIdRef(value, flag));
|
|
65
|
+
}
|
|
66
|
+
function normalizeSkillList(raw) {
|
|
67
|
+
return parseCsvList(raw).map((value) => {
|
|
68
|
+
const normalized = value.toLowerCase();
|
|
69
|
+
if (!SKILL_SLUG_RE.test(normalized)) {
|
|
70
|
+
throw new errors_1.UsageError(`--add-skills entries must be kebab-case: ${value}`);
|
|
71
|
+
}
|
|
72
|
+
return normalized;
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
function appendUnique(existing, additions) {
|
|
76
|
+
const next = [...existing];
|
|
77
|
+
const seen = new Set(existing);
|
|
78
|
+
for (const value of additions) {
|
|
79
|
+
if (!seen.has(value)) {
|
|
80
|
+
next.push(value);
|
|
81
|
+
seen.add(value);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return next;
|
|
85
|
+
}
|
|
86
|
+
function toStringList(value) {
|
|
87
|
+
if (Array.isArray(value)) {
|
|
88
|
+
return value.map((item) => String(item));
|
|
89
|
+
}
|
|
90
|
+
return [];
|
|
91
|
+
}
|
|
92
|
+
function maybeReindex(root, config) {
|
|
93
|
+
if (!config.index.auto_reindex) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const outputPath = path_1.default.resolve(root, config.index.global_index_path);
|
|
97
|
+
(0, index_cache_1.writeIndex)(outputPath, (0, indexer_1.buildIndex)(root, config, { tolerant: config.index.tolerant }));
|
|
98
|
+
}
|
|
99
|
+
function loadMutableTaskNode(root, idOrQid, wsHint) {
|
|
100
|
+
const config = (0, config_1.loadConfig)(root);
|
|
101
|
+
const { index } = (0, index_cache_1.loadIndex)({ root, config });
|
|
102
|
+
const resolved = (0, qid_1.resolveQid)(index, idOrQid, normalizeWorkspace(wsHint));
|
|
103
|
+
if (resolved.status !== "ok") {
|
|
104
|
+
throw new errors_1.NotFoundError((0, qid_1.formatResolveError)("task", idOrQid, resolved, normalizeWorkspace(wsHint)));
|
|
105
|
+
}
|
|
106
|
+
const node = index.nodes[resolved.qid];
|
|
107
|
+
if (!node) {
|
|
108
|
+
throw new errors_1.NotFoundError(`task not found: ${idOrQid}`);
|
|
109
|
+
}
|
|
110
|
+
if (!MUTABLE_TASK_TYPES.has(node.type)) {
|
|
111
|
+
throw new errors_1.UsageError(`mdkg task only supports task, bug, and test nodes; use markdown editing for ${node.type}:${node.id}`);
|
|
112
|
+
}
|
|
113
|
+
const filePath = path_1.default.resolve(root, node.path);
|
|
114
|
+
const content = fs_1.default.readFileSync(filePath, "utf8");
|
|
115
|
+
const parsed = (0, frontmatter_1.parseFrontmatter)(content, filePath);
|
|
116
|
+
return {
|
|
117
|
+
config,
|
|
118
|
+
index,
|
|
119
|
+
qid: node.qid,
|
|
120
|
+
ws: node.ws,
|
|
121
|
+
id: node.id,
|
|
122
|
+
type: node.type,
|
|
123
|
+
filePath,
|
|
124
|
+
frontmatter: { ...parsed.frontmatter },
|
|
125
|
+
body: parsed.body,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
function writeNodeFile(root, filePath, frontmatter, body) {
|
|
129
|
+
const lines = (0, frontmatter_1.formatFrontmatter)(frontmatter, frontmatter_1.DEFAULT_FRONTMATTER_KEY_ORDER);
|
|
130
|
+
const frontmatterBlock = ["---", ...lines, "---"].join("\n");
|
|
131
|
+
const content = body.length > 0 ? `${frontmatterBlock}\n${body}` : frontmatterBlock;
|
|
132
|
+
fs_1.default.writeFileSync(filePath, content, "utf8");
|
|
133
|
+
}
|
|
134
|
+
function ensureStatusAllowed(config, statusRaw, flag = "--status") {
|
|
135
|
+
const normalized = statusRaw.trim().toLowerCase();
|
|
136
|
+
const allowed = new Set(config.work.status_enum.map((value) => value.toLowerCase()));
|
|
137
|
+
if (!allowed.has(normalized)) {
|
|
138
|
+
throw new errors_1.UsageError(`${flag} must be one of ${Array.from(allowed).join(", ")}`);
|
|
139
|
+
}
|
|
140
|
+
return normalized;
|
|
141
|
+
}
|
|
142
|
+
function ensurePriorityAllowed(config, priority) {
|
|
143
|
+
if (!Number.isInteger(priority) ||
|
|
144
|
+
priority < config.work.priority_min ||
|
|
145
|
+
priority > config.work.priority_max) {
|
|
146
|
+
throw new errors_1.UsageError(`--priority must be between ${config.work.priority_min} and ${config.work.priority_max}`);
|
|
147
|
+
}
|
|
148
|
+
return priority;
|
|
149
|
+
}
|
|
150
|
+
function ensureNodeRefsExist(index, values, ws, label) {
|
|
151
|
+
for (const value of values) {
|
|
152
|
+
const resolved = (0, qid_1.resolveQid)(index, value, ws);
|
|
153
|
+
if (resolved.status !== "ok") {
|
|
154
|
+
throw new errors_1.NotFoundError((0, qid_1.formatResolveError)(label, value, resolved, ws));
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
function ensureSkillsExist(root, node, slugs) {
|
|
159
|
+
if (slugs.length === 0) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const skillsIndex = (0, skills_indexer_1.buildSkillsIndex)(root, node.config);
|
|
163
|
+
for (const slug of slugs) {
|
|
164
|
+
if (!skillsIndex.skills[slug]) {
|
|
165
|
+
throw new errors_1.NotFoundError(`skill not found: ${slug}`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
function updateUpdatedDate(frontmatter, now) {
|
|
170
|
+
frontmatter.updated = (0, date_1.formatDate)(now);
|
|
171
|
+
}
|
|
172
|
+
function maybeWarnEventsDisabled(root, config, ws) {
|
|
173
|
+
if ((0, event_support_1.isEventLoggingEnabled)(root, config, ws)) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
console.error(`note: event logging not enabled for workspace ${ws}; run mdkg event enable --ws ${ws} if you want JSONL provenance`);
|
|
177
|
+
}
|
|
178
|
+
function runTaskStartCommand(options) {
|
|
179
|
+
const loaded = loadMutableTaskNode(options.root, options.id, options.ws);
|
|
180
|
+
const now = options.now ?? new Date();
|
|
181
|
+
loaded.frontmatter.status = ensureStatusAllowed(loaded.config, "progress");
|
|
182
|
+
updateUpdatedDate(loaded.frontmatter, now);
|
|
183
|
+
writeNodeFile(options.root, loaded.filePath, loaded.frontmatter, loaded.body);
|
|
184
|
+
maybeReindex(options.root, loaded.config);
|
|
185
|
+
(0, event_support_1.appendAutomaticEvent)({
|
|
186
|
+
root: options.root,
|
|
187
|
+
ws: loaded.ws,
|
|
188
|
+
kind: "TASK_STARTED",
|
|
189
|
+
status: "ok",
|
|
190
|
+
refs: [loaded.id],
|
|
191
|
+
notes: options.note ?? `status set to progress via mdkg task start`,
|
|
192
|
+
runId: options.runId,
|
|
193
|
+
now,
|
|
194
|
+
});
|
|
195
|
+
maybeWarnEventsDisabled(options.root, loaded.config, loaded.ws);
|
|
196
|
+
console.log(`task started: ${loaded.qid}`);
|
|
197
|
+
}
|
|
198
|
+
function runTaskUpdateCommand(options) {
|
|
199
|
+
const loaded = loadMutableTaskNode(options.root, options.id, options.ws);
|
|
200
|
+
const now = options.now ?? new Date();
|
|
201
|
+
if (options.status) {
|
|
202
|
+
loaded.frontmatter.status = ensureStatusAllowed(loaded.config, options.status);
|
|
203
|
+
}
|
|
204
|
+
if (options.priority !== undefined) {
|
|
205
|
+
loaded.frontmatter.priority = String(ensurePriorityAllowed(loaded.config, options.priority));
|
|
206
|
+
}
|
|
207
|
+
const nextLinks = appendUnique(toStringList(loaded.frontmatter.links), parseCsvList(options.addLinks));
|
|
208
|
+
const nextArtifacts = appendUnique(toStringList(loaded.frontmatter.artifacts), parseCsvList(options.addArtifacts));
|
|
209
|
+
const nextRefs = appendUnique(toStringList(loaded.frontmatter.refs), normalizeIdList(options.addRefs, "--add-refs"));
|
|
210
|
+
const nextTags = appendUnique(toStringList(loaded.frontmatter.tags), normalizeLowercaseList(options.addTags));
|
|
211
|
+
const nextSkills = appendUnique(toStringList(loaded.frontmatter.skills), normalizeSkillList(options.addSkills));
|
|
212
|
+
const blockedByAdditions = normalizeIdRefList(options.addBlockedBy, "--add-blocked-by");
|
|
213
|
+
ensureNodeRefsExist(loaded.index, blockedByAdditions, loaded.ws, "blocked_by");
|
|
214
|
+
const nextBlockedBy = options.clearBlockedBy
|
|
215
|
+
? blockedByAdditions
|
|
216
|
+
: appendUnique(toStringList(loaded.frontmatter.blocked_by), blockedByAdditions);
|
|
217
|
+
ensureSkillsExist(options.root, loaded, nextSkills);
|
|
218
|
+
loaded.frontmatter.links = nextLinks;
|
|
219
|
+
loaded.frontmatter.artifacts = nextArtifacts;
|
|
220
|
+
loaded.frontmatter.refs = nextRefs;
|
|
221
|
+
loaded.frontmatter.tags = nextTags;
|
|
222
|
+
loaded.frontmatter.skills = nextSkills;
|
|
223
|
+
loaded.frontmatter.blocked_by = nextBlockedBy;
|
|
224
|
+
updateUpdatedDate(loaded.frontmatter, now);
|
|
225
|
+
writeNodeFile(options.root, loaded.filePath, loaded.frontmatter, loaded.body);
|
|
226
|
+
maybeReindex(options.root, loaded.config);
|
|
227
|
+
(0, event_support_1.appendAutomaticEvent)({
|
|
228
|
+
root: options.root,
|
|
229
|
+
ws: loaded.ws,
|
|
230
|
+
kind: "TASK_UPDATED",
|
|
231
|
+
status: "ok",
|
|
232
|
+
refs: [loaded.id],
|
|
233
|
+
artifacts: nextArtifacts,
|
|
234
|
+
notes: options.note ?? `task metadata updated via mdkg task update`,
|
|
235
|
+
runId: options.runId,
|
|
236
|
+
now,
|
|
237
|
+
});
|
|
238
|
+
console.log(`task updated: ${loaded.qid}`);
|
|
239
|
+
}
|
|
240
|
+
function runTaskDoneCommand(options) {
|
|
241
|
+
const loaded = loadMutableTaskNode(options.root, options.id, options.ws);
|
|
242
|
+
const now = options.now ?? new Date();
|
|
243
|
+
const nextLinks = appendUnique(toStringList(loaded.frontmatter.links), parseCsvList(options.addLinks));
|
|
244
|
+
const nextArtifacts = appendUnique(toStringList(loaded.frontmatter.artifacts), parseCsvList(options.addArtifacts));
|
|
245
|
+
const nextRefs = appendUnique(toStringList(loaded.frontmatter.refs), normalizeIdList(options.addRefs, "--add-refs"));
|
|
246
|
+
loaded.frontmatter.status = ensureStatusAllowed(loaded.config, "done");
|
|
247
|
+
loaded.frontmatter.links = nextLinks;
|
|
248
|
+
loaded.frontmatter.artifacts = nextArtifacts;
|
|
249
|
+
loaded.frontmatter.refs = nextRefs;
|
|
250
|
+
updateUpdatedDate(loaded.frontmatter, now);
|
|
251
|
+
writeNodeFile(options.root, loaded.filePath, loaded.frontmatter, loaded.body);
|
|
252
|
+
(0, event_support_1.appendAutomaticEvent)({
|
|
253
|
+
root: options.root,
|
|
254
|
+
ws: loaded.ws,
|
|
255
|
+
kind: "TASK_DONE",
|
|
256
|
+
status: "ok",
|
|
257
|
+
refs: [loaded.id],
|
|
258
|
+
artifacts: nextArtifacts,
|
|
259
|
+
notes: options.note ?? `status set to done via mdkg task done`,
|
|
260
|
+
runId: options.runId,
|
|
261
|
+
now,
|
|
262
|
+
});
|
|
263
|
+
if (options.checkpoint) {
|
|
264
|
+
(0, checkpoint_1.runCheckpointNewCommand)({
|
|
265
|
+
root: options.root,
|
|
266
|
+
title: options.checkpoint,
|
|
267
|
+
ws: loaded.ws,
|
|
268
|
+
relates: loaded.id,
|
|
269
|
+
scope: loaded.id,
|
|
270
|
+
runId: options.runId,
|
|
271
|
+
note: `checkpoint created from mdkg task done for ${loaded.id}`,
|
|
272
|
+
now,
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
maybeReindex(options.root, loaded.config);
|
|
276
|
+
maybeWarnEventsDisabled(options.root, loaded.config, loaded.ws);
|
|
277
|
+
console.log(`task done: ${loaded.qid}`);
|
|
278
|
+
}
|
|
@@ -9,9 +9,11 @@ const path_1 = __importDefault(require("path"));
|
|
|
9
9
|
const config_1 = require("../core/config");
|
|
10
10
|
const template_schema_1 = require("../graph/template_schema");
|
|
11
11
|
const node_1 = require("../graph/node");
|
|
12
|
+
const skills_indexer_1 = require("../graph/skills_indexer");
|
|
12
13
|
const workspace_files_1 = require("../graph/workspace_files");
|
|
13
14
|
const validate_graph_1 = require("../graph/validate_graph");
|
|
14
15
|
const errors_1 = require("../util/errors");
|
|
16
|
+
const skill_mirror_1 = require("./skill_mirror");
|
|
15
17
|
const RECOMMENDED_HEADINGS = {
|
|
16
18
|
task: [
|
|
17
19
|
"Overview",
|
|
@@ -116,6 +118,7 @@ function buildIndexNode(root, ws, filePath, node) {
|
|
|
116
118
|
artifacts: node.artifacts,
|
|
117
119
|
refs: node.refs,
|
|
118
120
|
aliases: node.aliases,
|
|
121
|
+
skills: node.skills,
|
|
119
122
|
path: path_1.default.relative(root, filePath),
|
|
120
123
|
edges: normalizeEdges(node.edges, ws),
|
|
121
124
|
};
|
|
@@ -128,6 +131,65 @@ function buildWorkspaceMap(config) {
|
|
|
128
131
|
}
|
|
129
132
|
return workspaces;
|
|
130
133
|
}
|
|
134
|
+
function listDirectories(dirPath) {
|
|
135
|
+
if (!fs_1.default.existsSync(dirPath)) {
|
|
136
|
+
return [];
|
|
137
|
+
}
|
|
138
|
+
return fs_1.default
|
|
139
|
+
.readdirSync(dirPath, { withFileTypes: true })
|
|
140
|
+
.filter((entry) => entry.isDirectory())
|
|
141
|
+
.map((entry) => path_1.default.join(dirPath, entry.name))
|
|
142
|
+
.sort();
|
|
143
|
+
}
|
|
144
|
+
function validateEventsJsonl(root, config, errors) {
|
|
145
|
+
for (const [alias, workspace] of Object.entries(config.workspaces)) {
|
|
146
|
+
if (!workspace.enabled) {
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
const eventsPath = path_1.default.resolve(root, workspace.path, workspace.mdkg_dir, "work", "events", "events.jsonl");
|
|
150
|
+
if (!fs_1.default.existsSync(eventsPath)) {
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
const lines = fs_1.default.readFileSync(eventsPath, "utf8").split(/\r?\n/);
|
|
154
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
155
|
+
const raw = lines[i].trim();
|
|
156
|
+
if (!raw) {
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
let parsed;
|
|
160
|
+
try {
|
|
161
|
+
parsed = JSON.parse(raw);
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
errors.push(`${eventsPath}:${i + 1}: invalid JSON`);
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
168
|
+
errors.push(`${eventsPath}:${i + 1}: event must be a JSON object`);
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
const event = parsed;
|
|
172
|
+
for (const key of ["ts", "run_id", "workspace", "agent", "kind", "status"]) {
|
|
173
|
+
const value = event[key];
|
|
174
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
175
|
+
errors.push(`${eventsPath}:${i + 1}: ${key} is required and must be a non-empty string`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if (!Array.isArray(event.refs)) {
|
|
179
|
+
errors.push(`${eventsPath}:${i + 1}: refs is required and must be a list`);
|
|
180
|
+
}
|
|
181
|
+
if (!Array.isArray(event.artifacts)) {
|
|
182
|
+
errors.push(`${eventsPath}:${i + 1}: artifacts is required and must be a list`);
|
|
183
|
+
}
|
|
184
|
+
if (typeof event.notes !== "string") {
|
|
185
|
+
errors.push(`${eventsPath}:${i + 1}: notes is required and must be a string`);
|
|
186
|
+
}
|
|
187
|
+
if (typeof event.workspace === "string" && event.workspace !== alias) {
|
|
188
|
+
errors.push(`${eventsPath}:${i + 1}: workspace must match ${alias}`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
131
193
|
function runValidateCommand(options) {
|
|
132
194
|
const config = (0, config_1.loadConfig)(options.root);
|
|
133
195
|
const templateSchemas = (0, template_schema_1.loadTemplateSchemas)(options.root, config, node_1.ALLOWED_TYPES);
|
|
@@ -196,9 +258,46 @@ function runValidateCommand(options) {
|
|
|
196
258
|
};
|
|
197
259
|
const graphErrors = (0, validate_graph_1.collectGraphErrors)(index, { allowMissing: false });
|
|
198
260
|
errors.push(...graphErrors);
|
|
261
|
+
try {
|
|
262
|
+
const skillsIndex = (0, skills_indexer_1.buildSkillsIndex)(options.root, config);
|
|
263
|
+
const knownSkills = new Set(Object.keys(skillsIndex.skills));
|
|
264
|
+
for (const node of Object.values(nodes)) {
|
|
265
|
+
for (const slug of node.skills) {
|
|
266
|
+
if (!knownSkills.has(slug)) {
|
|
267
|
+
errors.push(`${node.qid}: skills reference missing slug: ${slug}`);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
catch (err) {
|
|
273
|
+
const message = err instanceof Error ? err.message : "unknown skill validation error";
|
|
274
|
+
errors.push(message);
|
|
275
|
+
}
|
|
276
|
+
const skillsRoot = (0, skills_indexer_1.resolveSkillsRoot)(options.root, config);
|
|
277
|
+
for (const dirPath of listDirectories(skillsRoot)) {
|
|
278
|
+
const canonicalPath = path_1.default.join(dirPath, "SKILL.md");
|
|
279
|
+
const compatPath = path_1.default.join(dirPath, "SKILLS.md");
|
|
280
|
+
const hasCanonical = fs_1.default.existsSync(canonicalPath);
|
|
281
|
+
const hasCompat = fs_1.default.existsSync(compatPath);
|
|
282
|
+
if (hasCanonical && hasCompat) {
|
|
283
|
+
errors.push(`${dirPath}: both SKILL.md and SKILLS.md exist`);
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
if (!hasCanonical && !hasCompat) {
|
|
287
|
+
errors.push(`${dirPath}: missing SKILL.md or SKILLS.md`);
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
if (hasCompat) {
|
|
291
|
+
warnings.push(`${path_1.default.relative(options.root, compatPath)}: using legacy SKILLS.md compatibility file`);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
warnings.push(...(0, skill_mirror_1.auditSkillMirrors)(options.root, config));
|
|
295
|
+
validateEventsJsonl(options.root, config, errors);
|
|
296
|
+
const uniqueWarnings = Array.from(new Set(warnings));
|
|
297
|
+
const uniqueErrors = Array.from(new Set(errors));
|
|
199
298
|
const reportLines = [
|
|
200
|
-
...
|
|
201
|
-
...
|
|
299
|
+
...uniqueWarnings.map((warning) => `warning: ${warning}`),
|
|
300
|
+
...uniqueErrors,
|
|
202
301
|
];
|
|
203
302
|
let outPath = undefined;
|
|
204
303
|
if (options.out) {
|
|
@@ -207,20 +306,20 @@ function runValidateCommand(options) {
|
|
|
207
306
|
fs_1.default.writeFileSync(outPath, reportLines.join("\n"), "utf8");
|
|
208
307
|
}
|
|
209
308
|
if (!options.quiet) {
|
|
210
|
-
for (const warning of
|
|
309
|
+
for (const warning of uniqueWarnings) {
|
|
211
310
|
console.error(`warning: ${warning}`);
|
|
212
311
|
}
|
|
213
312
|
}
|
|
214
|
-
if (
|
|
313
|
+
if (uniqueErrors.length > 0) {
|
|
215
314
|
if (outPath) {
|
|
216
|
-
console.error(`validation failed: ${
|
|
315
|
+
console.error(`validation failed: ${uniqueErrors.length} error(s). details written to ${outPath}`);
|
|
217
316
|
}
|
|
218
317
|
else {
|
|
219
|
-
for (const error of
|
|
318
|
+
for (const error of uniqueErrors) {
|
|
220
319
|
console.error(error);
|
|
221
320
|
}
|
|
222
321
|
}
|
|
223
|
-
throw new errors_1.ValidationError(`validation failed with ${
|
|
322
|
+
throw new errors_1.ValidationError(`validation failed with ${uniqueErrors.length} error(s)`);
|
|
224
323
|
}
|
|
225
324
|
if (outPath) {
|
|
226
325
|
console.log(`validation report written: ${outPath}`);
|
package/dist/graph/edges.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.extractEdges = extractEdges;
|
|
4
|
-
const
|
|
4
|
+
const id_1 = require("../util/id");
|
|
5
5
|
function formatError(filePath, key, message) {
|
|
6
6
|
return new Error(`${filePath}: ${key} ${message}`);
|
|
7
7
|
}
|
|
@@ -10,7 +10,7 @@ function normalizeIdRef(value, filePath, key) {
|
|
|
10
10
|
if (normalized !== value) {
|
|
11
11
|
throw formatError(filePath, key, "must be lowercase");
|
|
12
12
|
}
|
|
13
|
-
if (!
|
|
13
|
+
if (!(0, id_1.isCanonicalIdRef)(normalized)) {
|
|
14
14
|
throw formatError(filePath, key, `invalid id reference: ${value}`);
|
|
15
15
|
}
|
|
16
16
|
return normalized;
|
package/dist/graph/indexer.js
CHANGED
|
@@ -81,6 +81,7 @@ function buildIndex(root, config, options = {}) {
|
|
|
81
81
|
artifacts: node.artifacts,
|
|
82
82
|
refs: node.refs,
|
|
83
83
|
aliases: node.aliases,
|
|
84
|
+
skills: node.skills,
|
|
84
85
|
path: relPath,
|
|
85
86
|
edges: normalizedEdges,
|
|
86
87
|
};
|
|
@@ -140,5 +141,25 @@ function buildIndex(root, config, options = {}) {
|
|
|
140
141
|
reverse_edges,
|
|
141
142
|
};
|
|
142
143
|
(0, validate_graph_1.validateGraph)(index, { allowMissing: tolerant });
|
|
144
|
+
const latestCheckpointByWorkspace = {};
|
|
145
|
+
for (const alias of workspaceAliases) {
|
|
146
|
+
const candidates = Object.values(nodes)
|
|
147
|
+
.filter((node) => node.ws === alias && node.type === "checkpoint")
|
|
148
|
+
.sort((a, b) => {
|
|
149
|
+
if (a.updated !== b.updated) {
|
|
150
|
+
return b.updated.localeCompare(a.updated);
|
|
151
|
+
}
|
|
152
|
+
if (a.created !== b.created) {
|
|
153
|
+
return b.created.localeCompare(a.created);
|
|
154
|
+
}
|
|
155
|
+
return b.qid.localeCompare(a.qid);
|
|
156
|
+
});
|
|
157
|
+
if (candidates.length > 0) {
|
|
158
|
+
latestCheckpointByWorkspace[alias] = candidates[0].qid;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (Object.keys(latestCheckpointByWorkspace).length > 0) {
|
|
162
|
+
index.meta.latest_checkpoint_qid = latestCheckpointByWorkspace;
|
|
163
|
+
}
|
|
143
164
|
return index;
|
|
144
165
|
}
|