mdkg 0.3.6 → 0.3.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/CHANGELOG.md +93 -0
- package/CLI_COMMAND_MATRIX.md +132 -24
- package/README.md +80 -25
- package/dist/cli.js +193 -36
- package/dist/command-contract.json +882 -122
- package/dist/commands/bundle.js +2 -0
- package/dist/commands/capability.js +1 -0
- package/dist/commands/checkpoint.js +139 -1
- package/dist/commands/db.js +8 -0
- package/dist/commands/doctor.js +7 -7
- package/dist/commands/format.js +155 -0
- package/dist/commands/goal.js +60 -0
- package/dist/commands/graph.js +148 -0
- package/dist/commands/handoff.js +301 -0
- package/dist/commands/mcp.js +9 -0
- package/dist/commands/new.js +12 -3
- package/dist/commands/pack.js +3 -1
- package/dist/commands/query_output.js +2 -0
- package/dist/commands/show.js +8 -0
- package/dist/commands/spec.js +76 -17
- package/dist/commands/status.js +1 -0
- package/dist/commands/subgraph.js +7 -4
- package/dist/commands/task.js +2 -0
- package/dist/commands/upgrade.js +128 -3
- package/dist/commands/validate.js +268 -6
- package/dist/commands/work.js +176 -5
- package/dist/core/project_db_queue_contract.js +101 -0
- package/dist/graph/agent_file_types.js +59 -20
- package/dist/graph/capabilities_indexer.js +45 -3
- package/dist/graph/edges.js +15 -0
- package/dist/graph/frontmatter.js +4 -1
- package/dist/graph/indexer.js +13 -0
- package/dist/graph/node.js +12 -1
- package/dist/graph/sqlite_index.js +2 -0
- package/dist/graph/subgraphs.js +2 -0
- package/dist/graph/template_schema.js +37 -17
- package/dist/graph/validate_graph.js +16 -5
- package/dist/graph/visibility.js +6 -0
- package/dist/init/AGENT_START.md +9 -6
- package/dist/init/CLI_COMMAND_MATRIX.md +50 -16
- package/dist/init/README.md +26 -9
- package/dist/init/init-manifest.json +67 -12
- package/dist/init/templates/default/bug.md +2 -0
- package/dist/init/templates/default/chk.md +3 -0
- package/dist/init/templates/default/epic.md +2 -0
- package/dist/init/templates/default/feat.md +2 -0
- package/dist/init/templates/default/goal.md +3 -0
- package/dist/init/templates/default/manifest.md +45 -0
- package/dist/init/templates/default/spike.md +2 -0
- package/dist/init/templates/default/task.md +2 -0
- package/dist/init/templates/default/test.md +2 -0
- package/dist/init/templates/specs/agent.MANIFEST.md +80 -0
- package/dist/init/templates/specs/api.MANIFEST.md +33 -0
- package/dist/init/templates/specs/base.MANIFEST.md +120 -0
- package/dist/init/templates/specs/capability.MANIFEST.md +45 -0
- package/dist/init/templates/specs/integration.MANIFEST.md +25 -0
- package/dist/init/templates/specs/model.MANIFEST.md +21 -0
- package/dist/init/templates/specs/project.MANIFEST.md +39 -0
- package/dist/init/templates/specs/runtime-agent.MANIFEST.md +49 -0
- package/dist/init/templates/specs/runtime-image.MANIFEST.md +21 -0
- package/dist/init/templates/specs/tool.MANIFEST.md +25 -0
- package/dist/pack/export_json.js +20 -8
- package/dist/pack/export_md.js +15 -4
- package/dist/pack/export_xml.js +9 -4
- package/dist/pack/metrics.js +12 -4
- package/dist/pack/pack.js +9 -1
- package/dist/util/argparse.js +3 -0
- package/package.json +21 -3
package/dist/commands/bundle.js
CHANGED
|
@@ -349,6 +349,8 @@ function publicFilteringErrors(index, includedQids) {
|
|
|
349
349
|
node.edges.relates,
|
|
350
350
|
node.edges.blocked_by,
|
|
351
351
|
node.edges.blocks,
|
|
352
|
+
node.edges.context_refs ?? [],
|
|
353
|
+
node.edges.evidence_refs ?? [],
|
|
352
354
|
]) {
|
|
353
355
|
for (const target of targets) {
|
|
354
356
|
if (index.nodes[target] && !includedQids.has(target)) {
|
|
@@ -79,6 +79,7 @@ function capabilitySearchText(record) {
|
|
|
79
79
|
...record.aliases,
|
|
80
80
|
...record.links,
|
|
81
81
|
...record.headings.map((heading) => heading.text),
|
|
82
|
+
JSON.stringify(record.manifest ?? {}),
|
|
82
83
|
JSON.stringify(record.spec ?? {}),
|
|
83
84
|
JSON.stringify(record.work ?? {}),
|
|
84
85
|
JSON.stringify(record.linkage ?? {}),
|
|
@@ -3,6 +3,7 @@ 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.CHECKPOINT_KINDS = void 0;
|
|
6
7
|
exports.createCheckpoint = createCheckpoint;
|
|
7
8
|
exports.runCheckpointNewCommand = runCheckpointNewCommand;
|
|
8
9
|
const fs_1 = __importDefault(require("fs"));
|
|
@@ -17,6 +18,20 @@ const atomic_1 = require("../util/atomic");
|
|
|
17
18
|
const lock_1 = require("../util/lock");
|
|
18
19
|
const sqlite_index_1 = require("../graph/sqlite_index");
|
|
19
20
|
const event_support_1 = require("./event_support");
|
|
21
|
+
exports.CHECKPOINT_KINDS = [
|
|
22
|
+
"implementation",
|
|
23
|
+
"test-proof",
|
|
24
|
+
"goal-closeout",
|
|
25
|
+
"audit",
|
|
26
|
+
"handoff",
|
|
27
|
+
];
|
|
28
|
+
function normalizeCheckpointKind(value) {
|
|
29
|
+
const normalized = (value ?? "implementation").toLowerCase();
|
|
30
|
+
if (exports.CHECKPOINT_KINDS.includes(normalized)) {
|
|
31
|
+
return normalized;
|
|
32
|
+
}
|
|
33
|
+
throw new errors_1.UsageError(`--kind must be one of ${exports.CHECKPOINT_KINDS.join(", ")}`);
|
|
34
|
+
}
|
|
20
35
|
function parseCsvList(raw) {
|
|
21
36
|
if (!raw) {
|
|
22
37
|
return [];
|
|
@@ -83,6 +98,125 @@ function normalizeWorkspace(value) {
|
|
|
83
98
|
}
|
|
84
99
|
return normalized;
|
|
85
100
|
}
|
|
101
|
+
function kindSpecificSection(kind) {
|
|
102
|
+
switch (kind) {
|
|
103
|
+
case "implementation":
|
|
104
|
+
return [
|
|
105
|
+
"# Implementation Details",
|
|
106
|
+
"",
|
|
107
|
+
"- Code or graph surfaces changed:",
|
|
108
|
+
"- Architecture or data-shape notes:",
|
|
109
|
+
"- Compatibility notes:",
|
|
110
|
+
];
|
|
111
|
+
case "test-proof":
|
|
112
|
+
return [
|
|
113
|
+
"# Test Proof",
|
|
114
|
+
"",
|
|
115
|
+
"- Test target:",
|
|
116
|
+
"- Fixtures or temp repos:",
|
|
117
|
+
"- Coverage gaps:",
|
|
118
|
+
];
|
|
119
|
+
case "goal-closeout":
|
|
120
|
+
return [
|
|
121
|
+
"# Goal Closeout",
|
|
122
|
+
"",
|
|
123
|
+
"- Goal condition result:",
|
|
124
|
+
"- Scoped nodes closed:",
|
|
125
|
+
"- Remaining deferred work:",
|
|
126
|
+
];
|
|
127
|
+
case "audit":
|
|
128
|
+
return [
|
|
129
|
+
"# Audit Findings",
|
|
130
|
+
"",
|
|
131
|
+
"- Reviewed surfaces:",
|
|
132
|
+
"- Findings:",
|
|
133
|
+
"- Residual risk:",
|
|
134
|
+
];
|
|
135
|
+
case "handoff":
|
|
136
|
+
return [
|
|
137
|
+
"# Handoff Summary",
|
|
138
|
+
"",
|
|
139
|
+
"- Recipient/context:",
|
|
140
|
+
"- Starting node or command:",
|
|
141
|
+
"- Explicit boundaries:",
|
|
142
|
+
];
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
function checkpointBody(kind) {
|
|
146
|
+
return [
|
|
147
|
+
"# Summary",
|
|
148
|
+
"",
|
|
149
|
+
"What was completed in this phase? What is now true?",
|
|
150
|
+
"",
|
|
151
|
+
"# Scope Covered",
|
|
152
|
+
"",
|
|
153
|
+
"Keep `scope` frontmatter updated when possible.",
|
|
154
|
+
"",
|
|
155
|
+
"## Changed Surfaces",
|
|
156
|
+
"",
|
|
157
|
+
"- files, commands, nodes, docs, or runtime surfaces changed",
|
|
158
|
+
"",
|
|
159
|
+
"## Boundaries",
|
|
160
|
+
"",
|
|
161
|
+
"- in scope:",
|
|
162
|
+
"- out of scope:",
|
|
163
|
+
"- raw secrets, raw prompts, raw payloads, and bulky execution traces excluded:",
|
|
164
|
+
"",
|
|
165
|
+
"# Decisions Captured",
|
|
166
|
+
"",
|
|
167
|
+
"Link the most important decision records.",
|
|
168
|
+
"",
|
|
169
|
+
"# Implementation Summary",
|
|
170
|
+
"",
|
|
171
|
+
"What changed? What patterns or architecture emerged?",
|
|
172
|
+
"",
|
|
173
|
+
...kindSpecificSection(kind),
|
|
174
|
+
"",
|
|
175
|
+
"# Verification / Testing",
|
|
176
|
+
"",
|
|
177
|
+
"## Command Evidence",
|
|
178
|
+
"",
|
|
179
|
+
"- command:",
|
|
180
|
+
"- result:",
|
|
181
|
+
"",
|
|
182
|
+
"## Pass / Fail Status",
|
|
183
|
+
"",
|
|
184
|
+
"- status:",
|
|
185
|
+
"",
|
|
186
|
+
"## Known Warnings",
|
|
187
|
+
"",
|
|
188
|
+
"- warning:",
|
|
189
|
+
"",
|
|
190
|
+
"# Known Issues / Follow-ups",
|
|
191
|
+
"",
|
|
192
|
+
"- issue 1",
|
|
193
|
+
"- issue 2",
|
|
194
|
+
"",
|
|
195
|
+
"## Follow-up Refs",
|
|
196
|
+
"",
|
|
197
|
+
"- task/test/goal refs:",
|
|
198
|
+
"",
|
|
199
|
+
"# Links / Artifacts",
|
|
200
|
+
"",
|
|
201
|
+
"- packs",
|
|
202
|
+
"- PRs/commits",
|
|
203
|
+
"- docs",
|
|
204
|
+
"- dashboards",
|
|
205
|
+
"",
|
|
206
|
+
"# Raw Content Safety",
|
|
207
|
+
"",
|
|
208
|
+
"- Summarize evidence and use refs, hashes, and artifact links instead of raw secrets, raw prompts, raw payloads, or bulky execution traces.",
|
|
209
|
+
"",
|
|
210
|
+
].join("\n");
|
|
211
|
+
}
|
|
212
|
+
function replaceRenderedBody(content, body) {
|
|
213
|
+
const marker = "\n---\n";
|
|
214
|
+
const start = content.indexOf(marker);
|
|
215
|
+
if (!content.startsWith("---\n") || start === -1) {
|
|
216
|
+
return content;
|
|
217
|
+
}
|
|
218
|
+
return `${content.slice(0, start + marker.length)}${body}`;
|
|
219
|
+
}
|
|
86
220
|
function createCheckpointLocked(options) {
|
|
87
221
|
const title = options.title.trim();
|
|
88
222
|
if (!title) {
|
|
@@ -130,12 +264,14 @@ function createCheckpointLocked(options) {
|
|
|
130
264
|
}
|
|
131
265
|
}
|
|
132
266
|
const scope = parseCsvList(options.scope).map((value) => normalizeId(value, "--scope"));
|
|
267
|
+
const kind = normalizeCheckpointKind(options.kind);
|
|
133
268
|
const now = options.now ?? new Date();
|
|
134
269
|
const today = (0, date_1.formatDate)(now);
|
|
135
270
|
const template = (0, loader_1.loadTemplate)(options.root, config, "checkpoint", options.template);
|
|
136
271
|
const content = (0, loader_1.renderTemplate)(template, {
|
|
137
272
|
id,
|
|
138
273
|
title,
|
|
274
|
+
checkpoint_kind: kind,
|
|
139
275
|
status,
|
|
140
276
|
priority,
|
|
141
277
|
created: today,
|
|
@@ -143,8 +279,9 @@ function createCheckpointLocked(options) {
|
|
|
143
279
|
relates,
|
|
144
280
|
scope,
|
|
145
281
|
});
|
|
282
|
+
const rendered = replaceRenderedBody(content, checkpointBody(kind));
|
|
146
283
|
try {
|
|
147
|
-
(0, atomic_1.writeFileExclusive)(filePath,
|
|
284
|
+
(0, atomic_1.writeFileExclusive)(filePath, rendered);
|
|
148
285
|
}
|
|
149
286
|
catch (err) {
|
|
150
287
|
const code = typeof err === "object" && err !== null && "code" in err ? String(err.code) : "";
|
|
@@ -168,6 +305,7 @@ function createCheckpointLocked(options) {
|
|
|
168
305
|
id,
|
|
169
306
|
qid: `${ws}:${id}`,
|
|
170
307
|
path: path_1.default.relative(options.root, filePath),
|
|
308
|
+
kind,
|
|
171
309
|
};
|
|
172
310
|
}
|
|
173
311
|
function createCheckpoint(options) {
|
package/dist/commands/db.js
CHANGED
|
@@ -9,6 +9,7 @@ exports.runDbMigrateCommand = runDbMigrateCommand;
|
|
|
9
9
|
exports.runDbVerifyCommand = runDbVerifyCommand;
|
|
10
10
|
exports.runDbStatsCommand = runDbStatsCommand;
|
|
11
11
|
exports.runDbQueueCreateCommand = runDbQueueCreateCommand;
|
|
12
|
+
exports.runDbQueueContractCommand = runDbQueueContractCommand;
|
|
12
13
|
exports.runDbQueuePauseCommand = runDbQueuePauseCommand;
|
|
13
14
|
exports.runDbQueueResumeCommand = runDbQueueResumeCommand;
|
|
14
15
|
exports.runDbQueueEnqueueCommand = runDbQueueEnqueueCommand;
|
|
@@ -36,6 +37,7 @@ const project_db_1 = require("../core/project_db");
|
|
|
36
37
|
const project_db_migrations_1 = require("../core/project_db_migrations");
|
|
37
38
|
const project_db_snapshot_1 = require("../core/project_db_snapshot");
|
|
38
39
|
const project_db_queue_1 = require("../core/project_db_queue");
|
|
40
|
+
const project_db_queue_contract_1 = require("../core/project_db_queue_contract");
|
|
39
41
|
const version_1 = require("../core/version");
|
|
40
42
|
const index_1 = require("./index");
|
|
41
43
|
const capabilities_indexer_1 = require("../graph/capabilities_indexer");
|
|
@@ -539,6 +541,12 @@ function runDbQueueCreateCommand(options) {
|
|
|
539
541
|
reason: options.reason,
|
|
540
542
|
}), "db-queue-create");
|
|
541
543
|
}
|
|
544
|
+
function runDbQueueContractCommand(options) {
|
|
545
|
+
writeQueueJsonOrText("db-queue-contract", {
|
|
546
|
+
mdkg_version: (0, version_1.readPackageVersion)(),
|
|
547
|
+
contract: (0, project_db_queue_contract_1.projectDbQueueAdapterContract)(),
|
|
548
|
+
}, options.json);
|
|
549
|
+
}
|
|
542
550
|
function runDbQueuePauseCommand(options) {
|
|
543
551
|
runQueueMutation(options, (databasePath) => ({ queue: (0, project_db_queue_1.pauseProjectQueue)(databasePath, { queue_name: requireQueueName(options), reason: options.reason }) }), "db-queue-pause");
|
|
544
552
|
}
|
package/dist/commands/doctor.js
CHANGED
|
@@ -164,8 +164,8 @@ function runArchiveStorageCheck(root) {
|
|
|
164
164
|
name: "archive-storage",
|
|
165
165
|
ok: true,
|
|
166
166
|
level: "warn",
|
|
167
|
-
detail: `stray uncompressed archive file(s) found without managed sidecars: ${strayRaw.join(", ")};
|
|
168
|
-
remediation: "
|
|
167
|
+
detail: `stray uncompressed archive file(s) found without managed sidecars: ${strayRaw.join(", ")}; these are storage hygiene warnings, not source defects`,
|
|
168
|
+
remediation: "Either run `mdkg archive add <file>` to create a managed sidecar, move raw files under an existing managed archive source directory, or remove unintended local files before committing.",
|
|
169
169
|
refs: strayRaw,
|
|
170
170
|
});
|
|
171
171
|
}
|
|
@@ -255,8 +255,8 @@ function runProjectDbRuntimePolicyCheck(root) {
|
|
|
255
255
|
name: "project-db-runtime",
|
|
256
256
|
ok: true,
|
|
257
257
|
level: "warn",
|
|
258
|
-
detail: `active project DB runtime/transient file(s) are local-only and should not be committed: ${files.join(", ")}`,
|
|
259
|
-
remediation: "Keep runtime DB and transient files ignored
|
|
258
|
+
detail: `active project DB runtime/transient file(s) are local-only and should not be committed: ${files.join(", ")}; this is expected local state when \`mdkg db verify\` passes`,
|
|
259
|
+
remediation: "Keep runtime DB and transient files ignored, run `mdkg db verify --json` if DB health is in question, and commit sealed state only by explicit repo policy.",
|
|
260
260
|
refs: files,
|
|
261
261
|
});
|
|
262
262
|
}
|
|
@@ -386,7 +386,7 @@ function runSelectedGoalChecks(root, config, options) {
|
|
|
386
386
|
ok: true,
|
|
387
387
|
level: "warn",
|
|
388
388
|
detail: selected.warning,
|
|
389
|
-
remediation: "Run `mdkg goal
|
|
389
|
+
remediation: "Run `mdkg goal clear --json` when no repo-local goal should be selected, or create/select a repo-local active goal only when work is continuing.",
|
|
390
390
|
strictFail: true,
|
|
391
391
|
}),
|
|
392
392
|
];
|
|
@@ -418,7 +418,7 @@ function runSelectedGoalChecks(root, config, options) {
|
|
|
418
418
|
ok: true,
|
|
419
419
|
level: "warn",
|
|
420
420
|
detail: `selected goal ${selected.state.qid} is missing from the graph`,
|
|
421
|
-
remediation: "Run `mdkg goal
|
|
421
|
+
remediation: "Run `mdkg goal clear --json` when no repo-local goal should be selected, or `mdkg goal activate <goal-id> --json` for the next active repo-local goal.",
|
|
422
422
|
refs: [selected.state.qid],
|
|
423
423
|
strictFail: true,
|
|
424
424
|
}),
|
|
@@ -433,7 +433,7 @@ function runSelectedGoalChecks(root, config, options) {
|
|
|
433
433
|
ok: true,
|
|
434
434
|
level: "warn",
|
|
435
435
|
detail: `selected goal ${selected.state.qid} is achieved but still current`,
|
|
436
|
-
remediation: "Run `mdkg goal
|
|
436
|
+
remediation: "Run `mdkg goal clear --json` for an achieved current goal, or `mdkg goal activate <goal-id> --json` only when a new repo-local goal should become active. Root orchestrators should not mutate dirty child repos without approval.",
|
|
437
437
|
refs: [selected.state.qid],
|
|
438
438
|
strictFail: true,
|
|
439
439
|
}),
|
package/dist/commands/format.js
CHANGED
|
@@ -19,6 +19,7 @@ const id_1 = require("../util/id");
|
|
|
19
19
|
const refs_1 = require("../util/refs");
|
|
20
20
|
const atomic_1 = require("../util/atomic");
|
|
21
21
|
const lock_1 = require("../util/lock");
|
|
22
|
+
const validate_1 = require("./validate");
|
|
22
23
|
const DEC_ID_RE = /^dec-[0-9]+$/;
|
|
23
24
|
const DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
24
25
|
const ID_LIST_KEYS = new Set(["refs", "scope"]);
|
|
@@ -46,6 +47,62 @@ function isValidId(value) {
|
|
|
46
47
|
function isCoreListFile(filePath) {
|
|
47
48
|
return path_1.default.basename(filePath) === "core.md" && path_1.default.basename(path_1.default.dirname(filePath)) === "core";
|
|
48
49
|
}
|
|
50
|
+
function normalizeHeading(value) {
|
|
51
|
+
return value.trim().toLowerCase();
|
|
52
|
+
}
|
|
53
|
+
function existingHeadings(body) {
|
|
54
|
+
const headings = new Set();
|
|
55
|
+
for (const line of body.split(/\r?\n/)) {
|
|
56
|
+
const match = /^#+\s+(.*)$/.exec(line);
|
|
57
|
+
if (match) {
|
|
58
|
+
headings.add(normalizeHeading(match[1] ?? ""));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return headings;
|
|
62
|
+
}
|
|
63
|
+
function appendMissingHeadings(body, headings) {
|
|
64
|
+
if (headings.length === 0) {
|
|
65
|
+
return body;
|
|
66
|
+
}
|
|
67
|
+
const trimmed = body.replace(/\s+$/g, "");
|
|
68
|
+
const prefix = trimmed.length > 0 ? `${trimmed}\n\n` : "";
|
|
69
|
+
return `${prefix}${headings.map((heading) => `# ${heading}\n`).join("\n")}`;
|
|
70
|
+
}
|
|
71
|
+
function countBuckets(values) {
|
|
72
|
+
const counts = new Map();
|
|
73
|
+
for (const value of values) {
|
|
74
|
+
if (!value) {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
counts.set(value, (counts.get(value) ?? 0) + 1);
|
|
78
|
+
}
|
|
79
|
+
return Array.from(counts.entries())
|
|
80
|
+
.map(([key, count]) => ({ key, count }))
|
|
81
|
+
.sort((a, b) => b.count - a.count || a.key.localeCompare(b.key));
|
|
82
|
+
}
|
|
83
|
+
function normalizeLimit(limit) {
|
|
84
|
+
if (limit === undefined) {
|
|
85
|
+
return 50;
|
|
86
|
+
}
|
|
87
|
+
if (!Number.isInteger(limit) || limit < 0) {
|
|
88
|
+
throw new errors_1.ValidationError("--limit must be a non-negative integer");
|
|
89
|
+
}
|
|
90
|
+
return limit;
|
|
91
|
+
}
|
|
92
|
+
function buildHeadingsSummary(changes, limit) {
|
|
93
|
+
const effectiveLimit = limit === null || limit === undefined ? changes.length : limit;
|
|
94
|
+
const emitted = Math.min(changes.length, effectiveLimit);
|
|
95
|
+
return {
|
|
96
|
+
total: changes.length,
|
|
97
|
+
emitted,
|
|
98
|
+
truncated: emitted < changes.length,
|
|
99
|
+
omitted_count: changes.length - emitted,
|
|
100
|
+
limit: limit === undefined ? null : limit,
|
|
101
|
+
affected_file_count: new Set(changes.map((change) => change.path)).size,
|
|
102
|
+
by_node_type: countBuckets(changes.map((change) => change.type)),
|
|
103
|
+
top_paths: countBuckets(changes.map((change) => change.path)).slice(0, 25),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
49
106
|
function normalizeScalar(value) {
|
|
50
107
|
return value.trim();
|
|
51
108
|
}
|
|
@@ -250,7 +307,105 @@ function normalizeFrontmatter(frontmatter, schema, type, workStatusEnum, priorit
|
|
|
250
307
|
}
|
|
251
308
|
return { normalized, errors };
|
|
252
309
|
}
|
|
310
|
+
function runHeadingFormatCommandLocked(options) {
|
|
311
|
+
if (options.dryRun && options.apply) {
|
|
312
|
+
throw new errors_1.ValidationError("format --headings cannot use --dry-run and --apply together");
|
|
313
|
+
}
|
|
314
|
+
const config = (0, config_1.loadConfig)(options.root);
|
|
315
|
+
const filesByAlias = (0, workspace_files_1.listWorkspaceDocFilesByAlias)(options.root, config);
|
|
316
|
+
const errors = [];
|
|
317
|
+
const changes = [];
|
|
318
|
+
for (const files of Object.values(filesByAlias)) {
|
|
319
|
+
for (const filePath of files) {
|
|
320
|
+
if (isCoreListFile(filePath)) {
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
let content = "";
|
|
324
|
+
try {
|
|
325
|
+
content = fs_1.default.readFileSync(filePath, "utf8");
|
|
326
|
+
}
|
|
327
|
+
catch (err) {
|
|
328
|
+
const message = err instanceof Error ? err.message : "unknown error";
|
|
329
|
+
errors.push(`${filePath}: failed to read file: ${message}`);
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
let parsed;
|
|
333
|
+
try {
|
|
334
|
+
parsed = (0, frontmatter_1.parseFrontmatter)(content, filePath);
|
|
335
|
+
}
|
|
336
|
+
catch (err) {
|
|
337
|
+
const message = err instanceof Error ? err.message : "unknown error";
|
|
338
|
+
errors.push(message);
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
341
|
+
const typeValue = parsed.frontmatter.type;
|
|
342
|
+
if (typeof typeValue !== "string") {
|
|
343
|
+
errors.push(`${filePath}: type is required`);
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
const type = typeValue.toLowerCase();
|
|
347
|
+
const recommended = validate_1.RECOMMENDED_HEADINGS[type];
|
|
348
|
+
if (!recommended) {
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
351
|
+
if (!node_1.ALLOWED_TYPES.has(type)) {
|
|
352
|
+
errors.push(`${filePath}: type must be one of ${Array.from(node_1.ALLOWED_TYPES).join(", ")}`);
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
const present = existingHeadings(parsed.body);
|
|
356
|
+
const addedHeadings = recommended.filter((heading) => !present.has(normalizeHeading(heading)));
|
|
357
|
+
if (addedHeadings.length === 0) {
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
const frontmatterEnd = content.indexOf("---", 3);
|
|
361
|
+
const frontmatterBlock = frontmatterEnd >= 0 ? content.slice(0, frontmatterEnd + 3) : ["---", ...(0, frontmatter_1.formatFrontmatter)(parsed.frontmatter, frontmatter_1.DEFAULT_FRONTMATTER_KEY_ORDER), "---"].join("\n");
|
|
362
|
+
const nextBody = appendMissingHeadings(parsed.body, addedHeadings);
|
|
363
|
+
const nextContent = `${frontmatterBlock}\n${nextBody.endsWith("\n") ? nextBody : `${nextBody}\n`}`;
|
|
364
|
+
changes.push({
|
|
365
|
+
filePath,
|
|
366
|
+
path: path_1.default.relative(options.root, filePath).split(path_1.default.sep).join("/"),
|
|
367
|
+
id: typeof parsed.frontmatter.id === "string" ? parsed.frontmatter.id : undefined,
|
|
368
|
+
type,
|
|
369
|
+
added_headings: addedHeadings,
|
|
370
|
+
content: nextContent,
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
if (errors.length > 0) {
|
|
375
|
+
for (const error of errors) {
|
|
376
|
+
console.error(error);
|
|
377
|
+
}
|
|
378
|
+
throw new errors_1.ValidationError(`format --headings failed with ${errors.length} error(s)`);
|
|
379
|
+
}
|
|
380
|
+
const apply = options.apply === true;
|
|
381
|
+
if (apply) {
|
|
382
|
+
for (const change of changes) {
|
|
383
|
+
(0, atomic_1.atomicWriteFile)(change.filePath, change.content);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
const publicChanges = changes.map(({ filePath: _filePath, content: _content, ...change }) => change);
|
|
387
|
+
const summaryLimit = options.summary ? normalizeLimit(options.limit) : undefined;
|
|
388
|
+
const emittedChanges = options.summary ? publicChanges.slice(0, summaryLimit) : publicChanges;
|
|
389
|
+
const receipt = {
|
|
390
|
+
action: "format.headings",
|
|
391
|
+
ok: true,
|
|
392
|
+
dry_run: !apply,
|
|
393
|
+
applied: apply,
|
|
394
|
+
changed_count: changes.length,
|
|
395
|
+
summary: buildHeadingsSummary(publicChanges, summaryLimit),
|
|
396
|
+
changes: emittedChanges,
|
|
397
|
+
};
|
|
398
|
+
if (options.json) {
|
|
399
|
+
console.log(JSON.stringify(receipt, null, 2));
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
console.log(`${apply ? "format headings updated" : "format headings dry-run"} ${changes.length} file(s)`);
|
|
403
|
+
}
|
|
253
404
|
function runFormatCommandLocked(options) {
|
|
405
|
+
if (options.headings) {
|
|
406
|
+
runHeadingFormatCommandLocked(options);
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
254
409
|
const config = (0, config_1.loadConfig)(options.root);
|
|
255
410
|
const templateSchemas = (0, template_schema_1.loadTemplateSchemas)(options.root, config, node_1.ALLOWED_TYPES);
|
|
256
411
|
const filesByAlias = (0, workspace_files_1.listWorkspaceDocFilesByAlias)(options.root, config);
|
package/dist/commands/goal.js
CHANGED
|
@@ -227,6 +227,7 @@ function goalReceipt(root, loaded) {
|
|
|
227
227
|
goal_condition: String(fm.goal_condition ?? ""),
|
|
228
228
|
scope_refs: toStringList(fm.scope_refs),
|
|
229
229
|
active_node: optionalString(fm.active_node),
|
|
230
|
+
last_active_node: optionalString(fm.last_active_node),
|
|
230
231
|
required_skills: toStringList(fm.required_skills),
|
|
231
232
|
required_checks: toStringList(fm.required_checks),
|
|
232
233
|
max_iterations: optionalString(fm.max_iterations),
|
|
@@ -276,6 +277,11 @@ function isConcreteCandidate(node, statusRanks) {
|
|
|
276
277
|
}
|
|
277
278
|
return node.status !== "done";
|
|
278
279
|
}
|
|
280
|
+
function isAchievedGoal(node, frontmatter) {
|
|
281
|
+
const status = frontmatter ? String(frontmatter.status ?? "") : node.status;
|
|
282
|
+
const goalState = frontmatter ? String(frontmatter.goal_state ?? "") : String(node.attributes.goal_state ?? "");
|
|
283
|
+
return status === "done" || goalState === "achieved";
|
|
284
|
+
}
|
|
279
285
|
function resolveCandidate(index, idOrQid, ws) {
|
|
280
286
|
const resolved = (0, qid_1.resolveQid)(index, idOrQid, ws);
|
|
281
287
|
if (resolved.status !== "ok") {
|
|
@@ -283,6 +289,29 @@ function resolveCandidate(index, idOrQid, ws) {
|
|
|
283
289
|
}
|
|
284
290
|
return index.nodes[resolved.qid];
|
|
285
291
|
}
|
|
292
|
+
function readOnlySubgraphBlockerWarnings(index, qids) {
|
|
293
|
+
const warnings = [];
|
|
294
|
+
const seen = new Set();
|
|
295
|
+
for (const qid of [...qids].sort()) {
|
|
296
|
+
const node = index.nodes[qid];
|
|
297
|
+
if (!node || node.source?.imported || node.status === "done") {
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
for (const blockerQid of node.edges.blocked_by) {
|
|
301
|
+
const blocker = index.nodes[blockerQid];
|
|
302
|
+
if (!blocker?.source?.imported) {
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
const subgraph = blocker.source.subgraph_alias;
|
|
306
|
+
const warning = `${node.qid} is blocked by read-only subgraph node ${blocker.qid}; update the source workspace for subgraph ${subgraph} or refresh the subgraph bundle before claiming local work`;
|
|
307
|
+
if (!seen.has(warning)) {
|
|
308
|
+
seen.add(warning);
|
|
309
|
+
warnings.push(warning);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
return warnings;
|
|
314
|
+
}
|
|
286
315
|
function runGoalShowCommand(options) {
|
|
287
316
|
const loaded = loadGoal(options.root, options.id, options.ws);
|
|
288
317
|
const receipt = { action: "showed", goal: goalReceipt(options.root, loaded) };
|
|
@@ -296,6 +325,9 @@ function runGoalShowCommand(options) {
|
|
|
296
325
|
if (loaded.frontmatter.active_node) {
|
|
297
326
|
console.log(`active_node: ${loaded.frontmatter.active_node}`);
|
|
298
327
|
}
|
|
328
|
+
if (loaded.frontmatter.last_active_node) {
|
|
329
|
+
console.log(`last_active_node: ${loaded.frontmatter.last_active_node}`);
|
|
330
|
+
}
|
|
299
331
|
const checks = toStringList(loaded.frontmatter.required_checks);
|
|
300
332
|
console.log(`required_checks: ${checks.length === 0 ? "none" : checks.join(", ")}`);
|
|
301
333
|
}
|
|
@@ -351,6 +383,23 @@ function runGoalNextCommand(options) {
|
|
|
351
383
|
console.error("no actionable local work found for goal");
|
|
352
384
|
return;
|
|
353
385
|
}
|
|
386
|
+
if (isAchievedGoal(loaded.node, loaded.frontmatter)) {
|
|
387
|
+
if (options.json) {
|
|
388
|
+
console.log(JSON.stringify({
|
|
389
|
+
action: "selected",
|
|
390
|
+
goal: goalReceipt(options.root, loaded),
|
|
391
|
+
goal_source: loaded.resolutionSource,
|
|
392
|
+
node: null,
|
|
393
|
+
warnings: loaded.warnings,
|
|
394
|
+
}, null, 2));
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
for (const warning of loaded.warnings) {
|
|
398
|
+
console.error(`warning: ${warning}`);
|
|
399
|
+
}
|
|
400
|
+
console.error("no actionable local work found for achieved goal");
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
354
403
|
const statusPreference = loaded.config.work.next.status_preference.map((status) => status.toLowerCase());
|
|
355
404
|
const statusRanks = new Set(statusPreference);
|
|
356
405
|
const warnings = [...loaded.warnings];
|
|
@@ -362,6 +411,7 @@ function runGoalNextCommand(options) {
|
|
|
362
411
|
for (const invalid of scope.invalidRefs) {
|
|
363
412
|
warnings.push(`scope contains non-actionable or unsupported node: ${invalid}`);
|
|
364
413
|
}
|
|
414
|
+
warnings.push(...readOnlySubgraphBlockerWarnings(loaded.index, scope.actionableQids));
|
|
365
415
|
if (activeNode) {
|
|
366
416
|
const node = resolveCandidate(loaded.index, activeNode, loaded.node.ws);
|
|
367
417
|
if (node && scope.actionableQids.has(node.qid) && isConcreteCandidate(node, statusRanks)) {
|
|
@@ -549,6 +599,7 @@ function runGoalCurrentCommand(options) {
|
|
|
549
599
|
goal_condition: String(node.attributes.goal_condition ?? ""),
|
|
550
600
|
scope_refs: toStringList(node.attributes.scope_refs),
|
|
551
601
|
active_node: optionalString(node.attributes.active_node),
|
|
602
|
+
last_active_node: optionalString(node.attributes.last_active_node),
|
|
552
603
|
required_skills: toStringList(node.attributes.required_skills),
|
|
553
604
|
required_checks: toStringList(node.attributes.required_checks),
|
|
554
605
|
max_iterations: optionalString(node.attributes.max_iterations),
|
|
@@ -643,6 +694,15 @@ function runGoalStateMutationLocked(action, options) {
|
|
|
643
694
|
throw new errors_1.UsageError(`cannot ${action} archived goal ${loaded.node.qid}`);
|
|
644
695
|
}
|
|
645
696
|
const now = options.now ?? new Date();
|
|
697
|
+
if (action === "done" || action === "archive") {
|
|
698
|
+
const activeNode = optionalString(loaded.frontmatter.active_node);
|
|
699
|
+
if (activeNode) {
|
|
700
|
+
if (!loaded.frontmatter.last_active_node) {
|
|
701
|
+
loaded.frontmatter.last_active_node = activeNode;
|
|
702
|
+
}
|
|
703
|
+
delete loaded.frontmatter.active_node;
|
|
704
|
+
}
|
|
705
|
+
}
|
|
646
706
|
loaded.frontmatter.goal_state = GOAL_STATE_BY_ACTION[action];
|
|
647
707
|
loaded.frontmatter.status = ensureStatusAllowed(loaded.config, STATUS_BY_ACTION[action]);
|
|
648
708
|
writeGoalFile(loaded, now);
|