mdkg 0.0.7 → 0.0.8
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 +4 -0
- package/dist/commands/new.js +17 -7
- package/dist/commands/task.js +8 -1
- package/dist/graph/node.js +11 -22
- package/dist/graph/template_schema.js +126 -13
- package/dist/init/AGENT_START.md +2 -0
- package/dist/init/core/rule-6-templates-and-schemas.md +3 -1
- package/dist/templates/loader.js +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -94,6 +94,8 @@ mdkg task update task-1 --add-artifacts tests://unit.txt --add-tags release
|
|
|
94
94
|
mdkg task done task-1 --checkpoint "release readiness milestone"
|
|
95
95
|
```
|
|
96
96
|
|
|
97
|
+
`mdkg task ...` remains limited to `task`, `bug`, and `test`, but `skills: [...]` is valid metadata on all work items (`epic`, `feat`, `task`, `bug`, `checkpoint`, `test`). mdkg-generated work items should validate and round-trip through the task mutators without manual frontmatter repair.
|
|
98
|
+
|
|
97
99
|
Ensure and append baseline event memory:
|
|
98
100
|
|
|
99
101
|
```bash
|
|
@@ -184,6 +186,7 @@ Current source behavior:
|
|
|
184
186
|
- `mdkg skill search "<query>" --xml|--toon|--md`
|
|
185
187
|
- `mdkg skill show <slug> --xml|--toon|--md`
|
|
186
188
|
- work items may reference `skills: [slug,...]`
|
|
189
|
+
- `skills` is valid on all work items (`epic`, `feat`, `task`, `bug`, `checkpoint`, `test`)
|
|
187
190
|
- packs may include skills with `--skills` and `--skills-depth`
|
|
188
191
|
- mdkg indexes and discovers skills but does not execute skill scripts
|
|
189
192
|
- `SKILL.md` is canonical
|
|
@@ -219,6 +222,7 @@ Current direction:
|
|
|
219
222
|
- use `init --agent` for deeper AI-agent bootstrap
|
|
220
223
|
- keep `pack <id>` at the center of the human/agent loop
|
|
221
224
|
- use `mdkg task ...` for structured state changes and markdown edits for narrative/body content
|
|
225
|
+
- keep validation strict and fix schema drift instead of papering over mdkg-generated inconsistencies
|
|
222
226
|
- make event logging guided instead of purely manual
|
|
223
227
|
- dogfood real skills inside the repo
|
|
224
228
|
- make skill authoring first-class through `mdkg skill`
|
package/dist/commands/new.js
CHANGED
|
@@ -10,6 +10,7 @@ const config_1 = require("../core/config");
|
|
|
10
10
|
const index_cache_1 = require("../graph/index_cache");
|
|
11
11
|
const node_1 = require("../graph/node");
|
|
12
12
|
const indexer_1 = require("../graph/indexer");
|
|
13
|
+
const template_schema_1 = require("../graph/template_schema");
|
|
13
14
|
const loader_1 = require("../templates/loader");
|
|
14
15
|
const date_1 = require("../util/date");
|
|
15
16
|
const errors_1 = require("../util/errors");
|
|
@@ -172,7 +173,7 @@ function runNewCommand(options) {
|
|
|
172
173
|
throw new errors_1.UsageError(`--status must be one of ${Array.from(allowed).join(", ")}`);
|
|
173
174
|
}
|
|
174
175
|
}
|
|
175
|
-
else if (type
|
|
176
|
+
else if (node_1.DEC_TYPES.has(type)) {
|
|
176
177
|
status = statusInput ?? "proposed";
|
|
177
178
|
if (!DEC_STATUS.has(status)) {
|
|
178
179
|
throw new errors_1.UsageError(`--status must be one of ${Array.from(DEC_STATUS).join(", ")}`);
|
|
@@ -196,12 +197,20 @@ function runNewCommand(options) {
|
|
|
196
197
|
else if (options.priority !== undefined) {
|
|
197
198
|
throw new errors_1.UsageError("--priority is only valid for work items");
|
|
198
199
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
200
|
+
const graphFields = [
|
|
201
|
+
["epic", options.epic],
|
|
202
|
+
["parent", options.parent],
|
|
203
|
+
["prev", options.prev],
|
|
204
|
+
["next", options.next],
|
|
205
|
+
["blocked_by", options.blockedBy],
|
|
206
|
+
["blocks", options.blocks],
|
|
207
|
+
];
|
|
208
|
+
for (const [key, raw] of graphFields) {
|
|
209
|
+
if (raw && !(0, template_schema_1.schemaAllowsKey)(type, key)) {
|
|
210
|
+
throw new errors_1.UsageError(`--${key.replace("_", "-")} is not valid for ${type} nodes`);
|
|
202
211
|
}
|
|
203
212
|
}
|
|
204
|
-
if (options.cases && type
|
|
213
|
+
if (options.cases && !(0, template_schema_1.schemaAllowsKey)(type, "cases")) {
|
|
205
214
|
throw new errors_1.UsageError("--cases is only valid for test nodes");
|
|
206
215
|
}
|
|
207
216
|
const epic = options.epic ? normalizeIdRef(options.epic, "--epic") : undefined;
|
|
@@ -219,7 +228,7 @@ function runNewCommand(options) {
|
|
|
219
228
|
const skills = normalizeSkillList(options.skills);
|
|
220
229
|
const links = normalizeList(options.links);
|
|
221
230
|
const artifacts = normalizeList(options.artifacts);
|
|
222
|
-
if (skills.length > 0 && !
|
|
231
|
+
if (skills.length > 0 && !(0, template_schema_1.schemaAllowsKey)(type, "skills")) {
|
|
223
232
|
throw new errors_1.UsageError("--skills is only valid for work items");
|
|
224
233
|
}
|
|
225
234
|
if (type === "dec" && options.supersedes) {
|
|
@@ -258,6 +267,7 @@ function runNewCommand(options) {
|
|
|
258
267
|
}
|
|
259
268
|
const today = (0, date_1.formatDate)(options.now ?? new Date());
|
|
260
269
|
const template = (0, loader_1.loadTemplate)(options.root, config, type, options.template);
|
|
270
|
+
const schema = (0, template_schema_1.getNodeSchema)(type);
|
|
261
271
|
const content = (0, loader_1.renderTemplate)(template, {
|
|
262
272
|
id,
|
|
263
273
|
type,
|
|
@@ -282,7 +292,7 @@ function runNewCommand(options) {
|
|
|
282
292
|
supersedes: options.supersedes ? options.supersedes.toLowerCase() : undefined,
|
|
283
293
|
created: today,
|
|
284
294
|
updated: today,
|
|
285
|
-
});
|
|
295
|
+
}, schema?.allowedKeys);
|
|
286
296
|
fs_1.default.mkdirSync(targetDir, { recursive: true });
|
|
287
297
|
fs_1.default.writeFileSync(filePath, content, "utf8");
|
|
288
298
|
if (config.index.auto_reindex && !noReindex) {
|
package/dist/commands/task.js
CHANGED
|
@@ -9,10 +9,12 @@ exports.runTaskDoneCommand = runTaskDoneCommand;
|
|
|
9
9
|
const fs_1 = __importDefault(require("fs"));
|
|
10
10
|
const path_1 = __importDefault(require("path"));
|
|
11
11
|
const config_1 = require("../core/config");
|
|
12
|
+
const node_1 = require("../graph/node");
|
|
12
13
|
const indexer_1 = require("../graph/indexer");
|
|
13
14
|
const index_cache_1 = require("../graph/index_cache");
|
|
14
15
|
const frontmatter_1 = require("../graph/frontmatter");
|
|
15
16
|
const skills_indexer_1 = require("../graph/skills_indexer");
|
|
17
|
+
const template_schema_1 = require("../graph/template_schema");
|
|
16
18
|
const date_1 = require("../util/date");
|
|
17
19
|
const errors_1 = require("../util/errors");
|
|
18
20
|
const qid_1 = require("../util/qid");
|
|
@@ -112,7 +114,12 @@ function loadMutableTaskNode(root, idOrQid, wsHint) {
|
|
|
112
114
|
}
|
|
113
115
|
const filePath = path_1.default.resolve(root, node.path);
|
|
114
116
|
const content = fs_1.default.readFileSync(filePath, "utf8");
|
|
115
|
-
const parsed = (0,
|
|
117
|
+
const parsed = (0, node_1.parseNode)(content, filePath, {
|
|
118
|
+
workStatusEnum: config.work.status_enum,
|
|
119
|
+
priorityMin: config.work.priority_min,
|
|
120
|
+
priorityMax: config.work.priority_max,
|
|
121
|
+
templateSchemas: (0, template_schema_1.loadTemplateSchemas)(root, config, node_1.ALLOWED_TYPES),
|
|
122
|
+
});
|
|
116
123
|
return {
|
|
117
124
|
config,
|
|
118
125
|
index,
|
package/dist/graph/node.js
CHANGED
|
@@ -4,24 +4,13 @@ exports.ALLOWED_TYPES = exports.DEC_TYPES = exports.WORK_TYPES = void 0;
|
|
|
4
4
|
exports.parseNode = parseNode;
|
|
5
5
|
const frontmatter_1 = require("./frontmatter");
|
|
6
6
|
const edges_1 = require("./edges");
|
|
7
|
+
const template_schema_1 = require("./template_schema");
|
|
8
|
+
Object.defineProperty(exports, "ALLOWED_TYPES", { enumerable: true, get: function () { return template_schema_1.ALLOWED_TYPES; } });
|
|
9
|
+
Object.defineProperty(exports, "DEC_TYPES", { enumerable: true, get: function () { return template_schema_1.DEC_TYPES; } });
|
|
10
|
+
Object.defineProperty(exports, "WORK_TYPES", { enumerable: true, get: function () { return template_schema_1.WORK_TYPES; } });
|
|
7
11
|
const id_1 = require("../util/id");
|
|
8
12
|
const DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
9
13
|
const DEC_ID_RE = /^dec-[0-9]+$/;
|
|
10
|
-
exports.WORK_TYPES = new Set(["epic", "feat", "task", "bug", "checkpoint", "test"]);
|
|
11
|
-
exports.DEC_TYPES = new Set(["dec"]);
|
|
12
|
-
exports.ALLOWED_TYPES = new Set([
|
|
13
|
-
"rule",
|
|
14
|
-
"prd",
|
|
15
|
-
"edd",
|
|
16
|
-
"dec",
|
|
17
|
-
"prop",
|
|
18
|
-
"epic",
|
|
19
|
-
"feat",
|
|
20
|
-
"task",
|
|
21
|
-
"bug",
|
|
22
|
-
"checkpoint",
|
|
23
|
-
"test",
|
|
24
|
-
]);
|
|
25
14
|
const DEC_STATUS = new Set(["proposed", "accepted", "rejected", "superseded"]);
|
|
26
15
|
const SKILL_SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
27
16
|
function formatError(filePath, message) {
|
|
@@ -153,8 +142,8 @@ function validateTemplateKeys(frontmatter, schema, filePath) {
|
|
|
153
142
|
function parseNode(content, filePath, options) {
|
|
154
143
|
const { frontmatter, body } = (0, frontmatter_1.parseFrontmatter)(content, filePath);
|
|
155
144
|
const type = requireLowercase(expectString(frontmatter, "type", filePath), "type", filePath);
|
|
156
|
-
if (!
|
|
157
|
-
throw formatError(filePath, `type must be one of ${Array.from(
|
|
145
|
+
if (!template_schema_1.ALLOWED_TYPES.has(type)) {
|
|
146
|
+
throw formatError(filePath, `type must be one of ${Array.from(template_schema_1.ALLOWED_TYPES).join(", ")}`);
|
|
158
147
|
}
|
|
159
148
|
const schema = requireTemplateSchema(type, options.templateSchemas, filePath);
|
|
160
149
|
validateTemplateKeys(frontmatter, schema, filePath);
|
|
@@ -165,7 +154,7 @@ function parseNode(content, filePath, options) {
|
|
|
165
154
|
const statusValue = optionalString(frontmatter, "status", filePath);
|
|
166
155
|
let status = undefined;
|
|
167
156
|
const workStatus = new Set(options.workStatusEnum.map((value) => value.toLowerCase()));
|
|
168
|
-
if (
|
|
157
|
+
if (template_schema_1.WORK_TYPES.has(type)) {
|
|
169
158
|
if (!statusValue) {
|
|
170
159
|
throw formatError(filePath, "status is required for work items");
|
|
171
160
|
}
|
|
@@ -175,7 +164,7 @@ function parseNode(content, filePath, options) {
|
|
|
175
164
|
}
|
|
176
165
|
status = normalized;
|
|
177
166
|
}
|
|
178
|
-
else if (
|
|
167
|
+
else if (template_schema_1.DEC_TYPES.has(type)) {
|
|
179
168
|
if (!statusValue) {
|
|
180
169
|
throw formatError(filePath, "status is required for decision records");
|
|
181
170
|
}
|
|
@@ -191,7 +180,7 @@ function parseNode(content, filePath, options) {
|
|
|
191
180
|
const priorityValue = optionalString(frontmatter, "priority", filePath);
|
|
192
181
|
let priority = undefined;
|
|
193
182
|
if (priorityValue !== undefined) {
|
|
194
|
-
if (!
|
|
183
|
+
if (!template_schema_1.WORK_TYPES.has(type)) {
|
|
195
184
|
throw formatError(filePath, "priority is only allowed for work items");
|
|
196
185
|
}
|
|
197
186
|
priority = parsePriority(priorityValue, filePath, options.priorityMin, options.priorityMax);
|
|
@@ -204,13 +193,13 @@ function parseNode(content, filePath, options) {
|
|
|
204
193
|
const aliases = requireLowercaseList(optionalList(frontmatter, "aliases", filePath), "aliases", filePath);
|
|
205
194
|
const skillsRaw = optionalList(frontmatter, "skills", filePath);
|
|
206
195
|
const skills = normalizeSkillList(skillsRaw, filePath);
|
|
207
|
-
if (skills.length > 0 && !
|
|
196
|
+
if (skills.length > 0 && !template_schema_1.WORK_TYPES.has(type)) {
|
|
208
197
|
throw formatError(filePath, "skills is only allowed for work items");
|
|
209
198
|
}
|
|
210
199
|
normalizeIdList(optionalList(frontmatter, "scope", filePath), "scope", filePath);
|
|
211
200
|
const supersedesValue = optionalString(frontmatter, "supersedes", filePath);
|
|
212
201
|
if (supersedesValue !== undefined) {
|
|
213
|
-
if (!
|
|
202
|
+
if (!template_schema_1.DEC_TYPES.has(type)) {
|
|
214
203
|
throw formatError(filePath, "supersedes is only allowed for decision records");
|
|
215
204
|
}
|
|
216
205
|
const normalized = requireLowercase(supersedesValue, "supersedes", filePath);
|
|
@@ -3,10 +3,77 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ALLOWED_TYPES = exports.DEC_TYPES = exports.WORK_TYPES = void 0;
|
|
7
|
+
exports.buildNodeSchemas = buildNodeSchemas;
|
|
8
|
+
exports.getNodeSchema = getNodeSchema;
|
|
9
|
+
exports.schemaAllowsKey = schemaAllowsKey;
|
|
6
10
|
exports.loadTemplateSchemas = loadTemplateSchemas;
|
|
7
11
|
const fs_1 = __importDefault(require("fs"));
|
|
8
12
|
const path_1 = __importDefault(require("path"));
|
|
9
13
|
const frontmatter_1 = require("./frontmatter");
|
|
14
|
+
exports.WORK_TYPES = new Set(["epic", "feat", "task", "bug", "checkpoint", "test"]);
|
|
15
|
+
exports.DEC_TYPES = new Set(["dec"]);
|
|
16
|
+
exports.ALLOWED_TYPES = new Set([
|
|
17
|
+
"rule",
|
|
18
|
+
"prd",
|
|
19
|
+
"edd",
|
|
20
|
+
"dec",
|
|
21
|
+
"prop",
|
|
22
|
+
"epic",
|
|
23
|
+
"feat",
|
|
24
|
+
"task",
|
|
25
|
+
"bug",
|
|
26
|
+
"checkpoint",
|
|
27
|
+
"test",
|
|
28
|
+
]);
|
|
29
|
+
const COMMON_SCALAR_KEYS = ["id", "type", "title", "created", "updated"];
|
|
30
|
+
const COMMON_LIST_KEYS = ["tags", "owners", "links", "artifacts", "relates", "refs", "aliases"];
|
|
31
|
+
const NODE_SCHEMA_DEFS = {
|
|
32
|
+
rule: {
|
|
33
|
+
scalars: [...COMMON_SCALAR_KEYS],
|
|
34
|
+
lists: [...COMMON_LIST_KEYS],
|
|
35
|
+
},
|
|
36
|
+
prd: {
|
|
37
|
+
scalars: [...COMMON_SCALAR_KEYS],
|
|
38
|
+
lists: [...COMMON_LIST_KEYS],
|
|
39
|
+
},
|
|
40
|
+
edd: {
|
|
41
|
+
scalars: [...COMMON_SCALAR_KEYS],
|
|
42
|
+
lists: [...COMMON_LIST_KEYS],
|
|
43
|
+
},
|
|
44
|
+
prop: {
|
|
45
|
+
scalars: [...COMMON_SCALAR_KEYS],
|
|
46
|
+
lists: [...COMMON_LIST_KEYS],
|
|
47
|
+
},
|
|
48
|
+
dec: {
|
|
49
|
+
scalars: [...COMMON_SCALAR_KEYS, "status", "supersedes"],
|
|
50
|
+
lists: [...COMMON_LIST_KEYS],
|
|
51
|
+
},
|
|
52
|
+
epic: {
|
|
53
|
+
scalars: [...COMMON_SCALAR_KEYS, "status", "priority"],
|
|
54
|
+
lists: [...COMMON_LIST_KEYS, "blocked_by", "blocks", "skills"],
|
|
55
|
+
},
|
|
56
|
+
feat: {
|
|
57
|
+
scalars: [...COMMON_SCALAR_KEYS, "status", "priority", "epic", "parent", "prev", "next"],
|
|
58
|
+
lists: [...COMMON_LIST_KEYS, "blocked_by", "blocks", "skills"],
|
|
59
|
+
},
|
|
60
|
+
task: {
|
|
61
|
+
scalars: [...COMMON_SCALAR_KEYS, "status", "priority", "epic", "parent", "prev", "next"],
|
|
62
|
+
lists: [...COMMON_LIST_KEYS, "blocked_by", "blocks", "skills"],
|
|
63
|
+
},
|
|
64
|
+
bug: {
|
|
65
|
+
scalars: [...COMMON_SCALAR_KEYS, "status", "priority", "epic", "parent", "prev", "next"],
|
|
66
|
+
lists: [...COMMON_LIST_KEYS, "blocked_by", "blocks", "skills"],
|
|
67
|
+
},
|
|
68
|
+
checkpoint: {
|
|
69
|
+
scalars: [...COMMON_SCALAR_KEYS, "status", "priority", "epic", "parent", "prev", "next"],
|
|
70
|
+
lists: [...COMMON_LIST_KEYS, "blocked_by", "blocks", "skills", "scope"],
|
|
71
|
+
},
|
|
72
|
+
test: {
|
|
73
|
+
scalars: [...COMMON_SCALAR_KEYS, "status", "priority", "epic", "parent", "prev", "next"],
|
|
74
|
+
lists: [...COMMON_LIST_KEYS, "blocked_by", "blocks", "skills", "cases"],
|
|
75
|
+
},
|
|
76
|
+
};
|
|
10
77
|
function listMarkdownFiles(dir) {
|
|
11
78
|
if (!fs_1.default.existsSync(dir)) {
|
|
12
79
|
return [];
|
|
@@ -44,13 +111,65 @@ function addKeyToSchema(schema, key, kind, filePath) {
|
|
|
44
111
|
schema.listKeys.add(key);
|
|
45
112
|
}
|
|
46
113
|
}
|
|
114
|
+
function buildSchema(type, definition) {
|
|
115
|
+
const schema = {
|
|
116
|
+
type,
|
|
117
|
+
allowedKeys: new Set(),
|
|
118
|
+
keyKinds: {},
|
|
119
|
+
listKeys: new Set(),
|
|
120
|
+
};
|
|
121
|
+
for (const key of definition.scalars) {
|
|
122
|
+
addKeyToSchema(schema, key, "scalar", type);
|
|
123
|
+
}
|
|
124
|
+
for (const key of definition.lists ?? []) {
|
|
125
|
+
addKeyToSchema(schema, key, "list", type);
|
|
126
|
+
}
|
|
127
|
+
for (const key of definition.booleans ?? []) {
|
|
128
|
+
addKeyToSchema(schema, key, "boolean", type);
|
|
129
|
+
}
|
|
130
|
+
return schema;
|
|
131
|
+
}
|
|
132
|
+
function buildNodeSchemas(requiredTypes) {
|
|
133
|
+
const schemas = {};
|
|
134
|
+
for (const [type, definition] of Object.entries(NODE_SCHEMA_DEFS)) {
|
|
135
|
+
schemas[type] = buildSchema(type, definition);
|
|
136
|
+
}
|
|
137
|
+
if (requiredTypes) {
|
|
138
|
+
const required = Array.from(requiredTypes, (value) => value.toLowerCase());
|
|
139
|
+
const missing = required.filter((value) => !schemas[value]);
|
|
140
|
+
if (missing.length > 0) {
|
|
141
|
+
throw new Error(`template schema missing for type(s): ${missing.join(", ")}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return schemas;
|
|
145
|
+
}
|
|
146
|
+
function getNodeSchema(type) {
|
|
147
|
+
return buildNodeSchemas()[type.toLowerCase()];
|
|
148
|
+
}
|
|
149
|
+
function schemaAllowsKey(type, key) {
|
|
150
|
+
const schema = getNodeSchema(type);
|
|
151
|
+
return Boolean(schema?.allowedKeys.has(key));
|
|
152
|
+
}
|
|
153
|
+
function validateTemplateAgainstSchema(frontmatter, schema, filePath) {
|
|
154
|
+
for (const [key, value] of Object.entries(frontmatter)) {
|
|
155
|
+
const expected = schema.keyKinds[key];
|
|
156
|
+
if (!expected) {
|
|
157
|
+
throw new Error(`template key not allowed for ${schema.type}: ${key} (${filePath})`);
|
|
158
|
+
}
|
|
159
|
+
const kind = getValueKind(value);
|
|
160
|
+
if (expected !== kind) {
|
|
161
|
+
throw new Error(`template schema mismatch for ${schema.type}.${key}: expected ${expected} but found ${kind} (${filePath})`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
47
165
|
function loadTemplateSchemas(root, config, requiredTypes) {
|
|
166
|
+
const schemas = buildNodeSchemas(requiredTypes);
|
|
48
167
|
const templateRoot = path_1.default.resolve(root, config.templates.root_path, config.templates.default_set);
|
|
49
168
|
const files = listMarkdownFiles(templateRoot);
|
|
50
169
|
if (files.length === 0) {
|
|
51
170
|
throw new Error(`no templates found at ${templateRoot}`);
|
|
52
171
|
}
|
|
53
|
-
const
|
|
172
|
+
const discoveredTypes = new Set();
|
|
54
173
|
for (const filePath of files) {
|
|
55
174
|
const content = fs_1.default.readFileSync(filePath, "utf8");
|
|
56
175
|
const { frontmatter } = (0, frontmatter_1.parseFrontmatter)(content, filePath);
|
|
@@ -62,22 +181,16 @@ function loadTemplateSchemas(root, config, requiredTypes) {
|
|
|
62
181
|
if (normalizedType !== typeValue) {
|
|
63
182
|
throw new Error(`template type must be lowercase in ${filePath}`);
|
|
64
183
|
}
|
|
65
|
-
const schema = schemas[normalizedType]
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
allowedKeys: new Set(),
|
|
69
|
-
keyKinds: {},
|
|
70
|
-
listKeys: new Set(),
|
|
71
|
-
};
|
|
72
|
-
schemas[normalizedType] = schema;
|
|
73
|
-
for (const [key, value] of Object.entries(frontmatter)) {
|
|
74
|
-
const kind = getValueKind(value);
|
|
75
|
-
addKeyToSchema(schema, key, kind, filePath);
|
|
184
|
+
const schema = schemas[normalizedType];
|
|
185
|
+
if (!schema) {
|
|
186
|
+
throw new Error(`template schema missing for type ${normalizedType}`);
|
|
76
187
|
}
|
|
188
|
+
validateTemplateAgainstSchema(frontmatter, schema, filePath);
|
|
189
|
+
discoveredTypes.add(normalizedType);
|
|
77
190
|
}
|
|
78
191
|
if (requiredTypes) {
|
|
79
192
|
const required = Array.from(requiredTypes, (value) => value.toLowerCase());
|
|
80
|
-
const missing = required.filter((value) => !
|
|
193
|
+
const missing = required.filter((value) => !discoveredTypes.has(value));
|
|
81
194
|
if (missing.length > 0) {
|
|
82
195
|
throw new Error(`template schema missing for type(s): ${missing.join(", ")}`);
|
|
83
196
|
}
|
package/dist/init/AGENT_START.md
CHANGED
|
@@ -41,6 +41,8 @@ Conventions:
|
|
|
41
41
|
- mdkg does not execute skill scripts; runtimes decide when and whether to do that.
|
|
42
42
|
- Prefer packs over ad-hoc file lists.
|
|
43
43
|
- Prefer task/event commands for structured work-state changes and use markdown edits for narrative/body updates.
|
|
44
|
+
- `skills` is valid metadata on all work items (`epic`, `feat`, `task`, `bug`, `checkpoint`, `test`), but `mdkg task ...` still mutates only `task`, `bug`, and `test`.
|
|
45
|
+
- Treat mdkg-generated work items as schema-consistent by default: `mdkg new`, `mdkg validate`, and `mdkg task ...` should not require manual frontmatter repair.
|
|
44
46
|
- Use `mdkg task done <id> --checkpoint "<title>"` for milestone compression, not every routine task completion.
|
|
45
47
|
- Prefer checkpoints for feat/epic closeout summaries; parent status edits remain manual.
|
|
46
48
|
- If `events.jsonl` is missing, `mdkg task start` and `mdkg task done` will remind you how to recreate it.
|
|
@@ -95,7 +95,9 @@ Work items (`epic/feat/task/bug/checkpoint/test`):
|
|
|
95
95
|
- `status` (enum)
|
|
96
96
|
- optional `priority` (0..9)
|
|
97
97
|
- optional `skills: [slug, ...]` (kebab-case skill slugs)
|
|
98
|
-
- optional graph
|
|
98
|
+
- optional graph fields are type-specific by the shared schema/templates
|
|
99
|
+
- `relates`, `blocked_by`, and `blocks` are common work-item list fields
|
|
100
|
+
- `epic`, `parent`, `prev`, and `next` are only valid on the work-item types whose templates/schema include them
|
|
99
101
|
|
|
100
102
|
Decision records (`dec-*`):
|
|
101
103
|
- `status` (enum: `proposed`, `accepted`, `rejected`, `superseded`)
|
package/dist/templates/loader.js
CHANGED
|
@@ -48,8 +48,8 @@ function renderTokenValue(value) {
|
|
|
48
48
|
}
|
|
49
49
|
return value;
|
|
50
50
|
}
|
|
51
|
-
function renderTemplate(template, context) {
|
|
52
|
-
const allowedKeys = new Set(Object.keys(template.frontmatter));
|
|
51
|
+
function renderTemplate(template, context, allowedKeysInput) {
|
|
52
|
+
const allowedKeys = new Set(allowedKeysInput ?? Object.keys(template.frontmatter));
|
|
53
53
|
const rendered = {};
|
|
54
54
|
for (const [key, value] of Object.entries(template.frontmatter)) {
|
|
55
55
|
if (isTokenPlaceholder(value)) {
|