mdkg 0.3.6 → 0.3.7

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 (48) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/CLI_COMMAND_MATRIX.md +56 -7
  3. package/README.md +33 -3
  4. package/dist/cli.js +132 -11
  5. package/dist/command-contract.json +431 -16
  6. package/dist/commands/bundle.js +2 -0
  7. package/dist/commands/checkpoint.js +139 -1
  8. package/dist/commands/db.js +8 -0
  9. package/dist/commands/format.js +116 -0
  10. package/dist/commands/goal.js +60 -0
  11. package/dist/commands/graph.js +148 -0
  12. package/dist/commands/handoff.js +295 -0
  13. package/dist/commands/mcp.js +9 -0
  14. package/dist/commands/pack.js +3 -1
  15. package/dist/commands/query_output.js +2 -0
  16. package/dist/commands/show.js +8 -0
  17. package/dist/commands/status.js +1 -0
  18. package/dist/commands/task.js +2 -0
  19. package/dist/commands/upgrade.js +61 -0
  20. package/dist/commands/validate.js +162 -3
  21. package/dist/commands/work.js +164 -0
  22. package/dist/core/project_db_queue_contract.js +101 -0
  23. package/dist/graph/edges.js +15 -0
  24. package/dist/graph/frontmatter.js +4 -1
  25. package/dist/graph/indexer.js +8 -0
  26. package/dist/graph/node.js +12 -1
  27. package/dist/graph/sqlite_index.js +2 -0
  28. package/dist/graph/subgraphs.js +2 -0
  29. package/dist/graph/validate_graph.js +5 -0
  30. package/dist/graph/visibility.js +6 -0
  31. package/dist/init/AGENT_START.md +4 -1
  32. package/dist/init/CLI_COMMAND_MATRIX.md +24 -3
  33. package/dist/init/README.md +17 -2
  34. package/dist/init/init-manifest.json +12 -12
  35. package/dist/init/templates/default/bug.md +2 -0
  36. package/dist/init/templates/default/chk.md +3 -0
  37. package/dist/init/templates/default/epic.md +2 -0
  38. package/dist/init/templates/default/feat.md +2 -0
  39. package/dist/init/templates/default/goal.md +3 -0
  40. package/dist/init/templates/default/spike.md +2 -0
  41. package/dist/init/templates/default/task.md +2 -0
  42. package/dist/init/templates/default/test.md +2 -0
  43. package/dist/pack/export_json.js +20 -8
  44. package/dist/pack/export_md.js +15 -4
  45. package/dist/pack/export_xml.js +9 -4
  46. package/dist/pack/metrics.js +12 -4
  47. package/dist/pack/pack.js +9 -1
  48. package/package.json +6 -2
@@ -0,0 +1,295 @@
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.runHandoffCreateCommand = runHandoffCreateCommand;
7
+ const crypto_1 = __importDefault(require("crypto"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const config_1 = require("../core/config");
10
+ const index_cache_1 = require("../graph/index_cache");
11
+ const pack_1 = require("../pack/pack");
12
+ const metrics_1 = require("../pack/metrics");
13
+ const atomic_1 = require("../util/atomic");
14
+ const errors_1 = require("../util/errors");
15
+ const qid_1 = require("../util/qid");
16
+ const version_1 = require("../core/version");
17
+ const RAW_CONTENT_MARKERS = [
18
+ { id: "raw_prompt", pattern: /\bRAW_PROMPT_MARKER\b/i, description: "raw prompt marker" },
19
+ { id: "raw_payload", pattern: /\bRAW_PAYLOAD_MARKER\b/i, description: "raw payload marker" },
20
+ { id: "raw_secret", pattern: /\bRAW_SECRET_MARKER\b|BEGIN [A-Z ]*PRIVATE KEY|secret\s*=/i, description: "raw secret marker" },
21
+ ];
22
+ function normalizeWorkspace(value) {
23
+ if (!value || value === "all") {
24
+ return undefined;
25
+ }
26
+ return value;
27
+ }
28
+ function outputPath(root, out) {
29
+ if (!out) {
30
+ return undefined;
31
+ }
32
+ const resolved = path_1.default.resolve(root, out);
33
+ const rootWithSep = root.endsWith(path_1.default.sep) ? root : `${root}${path_1.default.sep}`;
34
+ if (resolved !== root && !resolved.startsWith(rootWithSep)) {
35
+ throw new errors_1.UsageError("--out must stay within the repo root");
36
+ }
37
+ return resolved;
38
+ }
39
+ function sha256(value) {
40
+ return `sha256:${crypto_1.default.createHash("sha256").update(value).digest("hex")}`;
41
+ }
42
+ function listAttribute(node, key) {
43
+ const value = "attributes" in node ? node.attributes[key] : undefined;
44
+ return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
45
+ }
46
+ function stringAttribute(node, key) {
47
+ const value = "attributes" in node ? node.attributes[key] : undefined;
48
+ return typeof value === "string" ? value : undefined;
49
+ }
50
+ function collectRawMarkerWarnings(pack) {
51
+ const warnings = [];
52
+ for (const node of pack.nodes) {
53
+ for (const marker of RAW_CONTENT_MARKERS) {
54
+ if (marker.pattern.test(node.body)) {
55
+ warnings.push({
56
+ qid: node.qid,
57
+ path: node.path,
58
+ marker_id: marker.id,
59
+ message: `${marker.description} detected; handoff omitted raw body content for this node`,
60
+ });
61
+ }
62
+ }
63
+ }
64
+ return warnings;
65
+ }
66
+ function nodeLine(node) {
67
+ const parts = [`${node.qid}`, node.type, node.title];
68
+ const state = [node.status, node.priority !== undefined ? `p${node.priority}` : undefined].filter(Boolean).join("/");
69
+ if (state) {
70
+ parts.push(state);
71
+ }
72
+ parts.push(node.path);
73
+ return `- ${parts.join(" | ")}`;
74
+ }
75
+ function renderList(values, empty = "- none") {
76
+ if (values.length === 0) {
77
+ return [empty];
78
+ }
79
+ return values.map((value) => `- ${value}`);
80
+ }
81
+ function renderNodeRefs(node) {
82
+ const lines = [];
83
+ if (node.refs.length > 0) {
84
+ lines.push(` refs: ${node.refs.join(", ")}`);
85
+ }
86
+ if (node.context_refs.length > 0) {
87
+ lines.push(` context_refs: ${node.context_refs.join(", ")}`);
88
+ }
89
+ if (node.evidence_refs.length > 0) {
90
+ lines.push(` evidence_refs: ${node.evidence_refs.join(", ")}`);
91
+ }
92
+ return lines;
93
+ }
94
+ function renderHandoff(params) {
95
+ const { rootNode, indexNode, pack, rawWarnings } = params;
96
+ const stats = (0, metrics_1.measurePack)(pack);
97
+ const lines = [];
98
+ const requiredChecks = listAttribute(indexNode, "required_checks");
99
+ const requiredSkills = listAttribute(indexNode, "required_skills");
100
+ const goalCondition = stringAttribute(indexNode, "goal_condition");
101
+ const activeNode = stringAttribute(indexNode, "active_node");
102
+ const lastActiveNode = stringAttribute(indexNode, "last_active_node");
103
+ const latestCheckpoint = pack.meta.latest_checkpoint_qid
104
+ ? pack.nodes.find((node) => node.qid === pack.meta.latest_checkpoint_qid)
105
+ : undefined;
106
+ lines.push("# mdkg Agent Handoff");
107
+ lines.push("");
108
+ lines.push("Use this handoff as a sanitized graph summary. Inspect source files before mutating durable state.");
109
+ lines.push("");
110
+ lines.push("## Target");
111
+ lines.push(`- qid: ${rootNode.qid}`);
112
+ lines.push(`- type: ${rootNode.type}`);
113
+ lines.push(`- title: ${rootNode.title}`);
114
+ if (rootNode.status) {
115
+ lines.push(`- status: ${rootNode.status}`);
116
+ }
117
+ if (rootNode.priority !== undefined) {
118
+ lines.push(`- priority: ${rootNode.priority}`);
119
+ }
120
+ lines.push(`- path: ${rootNode.path}`);
121
+ if (goalCondition) {
122
+ lines.push(`- goal_condition: ${goalCondition}`);
123
+ }
124
+ if (activeNode) {
125
+ lines.push(`- active_node: ${activeNode}`);
126
+ }
127
+ if (lastActiveNode) {
128
+ lines.push(`- last_active_node: ${lastActiveNode}`);
129
+ }
130
+ lines.push("");
131
+ lines.push("## Boundaries");
132
+ lines.push("- mdkg is durable semantic memory and graph state, not raw execution trace storage.");
133
+ lines.push("- Do not include raw secrets, credentials, model prompts, provider payloads, cookies, tokens, or bulky runtime artifacts in mdkg nodes or handoffs.");
134
+ lines.push("- Use refs, hashes, redacted summaries, archive links, artifact links, and checkpoints for evidence.");
135
+ lines.push("- Treat subgraph nodes as read-only planning context unless you are operating in the owning repo.");
136
+ lines.push("- Run validation before closing work or handing execution to another agent.");
137
+ lines.push("");
138
+ lines.push("## Recommended Next Steps");
139
+ if (rootNode.type === "goal") {
140
+ lines.push(`- Run \`mdkg goal current --json\` and \`mdkg goal next ${rootNode.id} --json\` to confirm routing.`);
141
+ lines.push("- Claim one actionable local node before implementation with `mdkg goal claim <goal-id> <work-id> --json`.");
142
+ }
143
+ else {
144
+ lines.push(`- Run \`mdkg show ${rootNode.id} --json\` and \`mdkg pack ${rootNode.id}\` to refresh local context.`);
145
+ lines.push("- Use `mdkg task start|update|done` for lifecycle updates when the node is task-like.");
146
+ }
147
+ lines.push("- Keep detailed implementation notes in Markdown body sections, not CLI flags.");
148
+ lines.push("");
149
+ lines.push("## Required Checks");
150
+ lines.push(...renderList(requiredChecks));
151
+ lines.push("");
152
+ lines.push("## Required Skills");
153
+ lines.push(...renderList(requiredSkills));
154
+ lines.push("");
155
+ lines.push("## Latest Checkpoint");
156
+ if (latestCheckpoint) {
157
+ lines.push(nodeLine(latestCheckpoint));
158
+ }
159
+ else {
160
+ lines.push("- none");
161
+ }
162
+ lines.push("");
163
+ lines.push("## Included Graph Context");
164
+ for (const node of pack.nodes) {
165
+ lines.push(nodeLine(node));
166
+ lines.push(...renderNodeRefs(node));
167
+ }
168
+ lines.push("");
169
+ lines.push("## Raw Content Warnings");
170
+ if (rawWarnings.length === 0) {
171
+ lines.push("- none");
172
+ }
173
+ else {
174
+ for (const warning of rawWarnings) {
175
+ lines.push(`- ${warning.qid} (${warning.path}): ${warning.marker_id} - ${warning.message}`);
176
+ }
177
+ }
178
+ lines.push("");
179
+ lines.push("## Pack Summary");
180
+ lines.push(`- generated_at: ${pack.meta.generated_at}`);
181
+ lines.push(`- node_count: ${pack.nodes.length}`);
182
+ lines.push(`- tokens_estimate: ${stats.totals.tokens_estimate}`);
183
+ lines.push(`- truncated: max_nodes=${pack.meta.truncated.max_nodes} max_bytes=${pack.meta.truncated.max_bytes}`);
184
+ if (pack.meta.truncated.dropped.length > 0) {
185
+ lines.push(`- dropped: ${pack.meta.truncated.dropped.join(", ")}`);
186
+ }
187
+ lines.push("");
188
+ lines.push("## Handoff Prompt");
189
+ lines.push("Continue from the target above. Preserve the boundaries, verify current repo state, use mdkg commands for structured lifecycle changes, and record validation evidence before closeout.");
190
+ lines.push("");
191
+ return lines.join("\n");
192
+ }
193
+ function runHandoffCreateCommand(options) {
194
+ if (!options.id) {
195
+ throw new errors_1.UsageError("mdkg handoff create requires <id-or-qid>");
196
+ }
197
+ const config = (0, config_1.loadConfig)(options.root);
198
+ const ws = normalizeWorkspace(options.ws);
199
+ if (ws && !config.workspaces[ws] && !config.subgraphs[ws]) {
200
+ throw new errors_1.NotFoundError(`workspace not found: ${ws}`);
201
+ }
202
+ const { index, stale, rebuilt, warnings } = (0, index_cache_1.loadIndex)({
203
+ root: options.root,
204
+ config,
205
+ useCache: true,
206
+ allowReindex: true,
207
+ });
208
+ if (stale && !rebuilt) {
209
+ console.error("warning: index is stale; run mdkg index to refresh");
210
+ }
211
+ for (const warning of warnings) {
212
+ console.error(`warning: ${warning}`);
213
+ }
214
+ const resolved = (0, qid_1.resolveQid)(index, options.id, ws);
215
+ if (resolved.status !== "ok") {
216
+ throw new errors_1.NotFoundError((0, qid_1.formatResolveError)("id", options.id, resolved, ws));
217
+ }
218
+ const indexNode = index.nodes[resolved.qid];
219
+ if (!indexNode) {
220
+ throw new errors_1.NotFoundError(`node not found: ${resolved.qid}`);
221
+ }
222
+ const depth = options.depth ?? config.pack.default_depth;
223
+ if (!Number.isInteger(depth) || depth < 0) {
224
+ throw new errors_1.UsageError("--depth must be a non-negative integer");
225
+ }
226
+ const buildResult = (0, pack_1.buildPack)({
227
+ root: options.root,
228
+ index,
229
+ rootQid: resolved.qid,
230
+ depth,
231
+ edges: ["parent", "epic", "relates", "blocked_by", "blocks", "context_refs", "evidence_refs"],
232
+ verbose: false,
233
+ maxNodes: config.pack.limits.max_nodes,
234
+ verboseCoreListPath: path_1.default.resolve(options.root, config.pack.verbose_core_list_path),
235
+ wsHint: ws,
236
+ includeLatestCheckpoint: true,
237
+ });
238
+ for (const warning of buildResult.warnings) {
239
+ console.error(`warning: ${warning}`);
240
+ }
241
+ const rootNode = buildResult.pack.nodes[0];
242
+ if (!rootNode) {
243
+ throw new errors_1.NotFoundError(`node not found: ${resolved.qid}`);
244
+ }
245
+ const rawWarnings = collectRawMarkerWarnings(buildResult.pack);
246
+ const content = renderHandoff({
247
+ rootNode,
248
+ indexNode,
249
+ pack: buildResult.pack,
250
+ rawWarnings,
251
+ });
252
+ const contentHash = sha256(content);
253
+ const outPath = outputPath(options.root, options.out);
254
+ if (outPath) {
255
+ (0, atomic_1.atomicWriteFile)(outPath, content);
256
+ }
257
+ const receipt = {
258
+ action: "handoff-created",
259
+ ok: true,
260
+ mdkg_version: (0, version_1.readPackageVersion)(),
261
+ target: {
262
+ qid: rootNode.qid,
263
+ id: rootNode.id,
264
+ type: rootNode.type,
265
+ title: rootNode.title,
266
+ path: rootNode.path,
267
+ status: rootNode.status,
268
+ },
269
+ output_path: outPath ? path_1.default.relative(options.root, outPath).split(path_1.default.sep).join("/") : null,
270
+ content_sha256: contentHash,
271
+ raw_marker_warning_count: rawWarnings.length,
272
+ raw_marker_warnings: rawWarnings,
273
+ latest_checkpoint_qid: buildResult.pack.meta.latest_checkpoint_qid ?? null,
274
+ included_qids: buildResult.pack.nodes.map((node) => node.qid),
275
+ pack: {
276
+ node_count: buildResult.pack.nodes.length,
277
+ generated_at: buildResult.pack.meta.generated_at,
278
+ truncated: buildResult.pack.meta.truncated,
279
+ },
280
+ content,
281
+ };
282
+ if (options.json) {
283
+ console.log(JSON.stringify(receipt, null, 2));
284
+ return;
285
+ }
286
+ if (outPath) {
287
+ console.log(`handoff written: ${outPath}`);
288
+ console.log(`sha256: ${contentHash}`);
289
+ if (rawWarnings.length > 0) {
290
+ console.log(`raw marker warnings: ${rawWarnings.length}`);
291
+ }
292
+ return;
293
+ }
294
+ console.log(content);
295
+ }
@@ -464,6 +464,15 @@ function goalNextTool(root, args) {
464
464
  warnings,
465
465
  };
466
466
  }
467
+ if (loaded.node.status === "done" || String(loaded.node.attributes.goal_state ?? "") === "achieved") {
468
+ return {
469
+ command: "mcp.goal_next",
470
+ goal_source: loaded.source,
471
+ goal: (0, query_output_1.toNodeSummaryJson)(loaded.node),
472
+ node: null,
473
+ warnings,
474
+ };
475
+ }
467
476
  const statusPreference = config.work.next.status_preference.map((status) => status.toLowerCase());
468
477
  const statusRanks = new Set(statusPreference);
469
478
  const scope = (0, goal_scope_1.collectGoalScope)(index, loaded.node);
@@ -25,7 +25,7 @@ const headings_1 = require("../templates/headings");
25
25
  const errors_1 = require("../util/errors");
26
26
  const output_1 = require("../util/output");
27
27
  const qid_1 = require("../util/qid");
28
- const EDGE_KEYS = new Set(["parent", "epic", "relates", "blocked_by", "blocks", "prev", "next"]);
28
+ const EDGE_KEYS = new Set(["parent", "epic", "relates", "blocked_by", "blocks", "prev", "next", "context_refs", "evidence_refs"]);
29
29
  const FORMAT_KEYS = new Set(["md", "json", "toon", "xml"]);
30
30
  const SKILL_SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
31
31
  function normalizeWorkspace(value) {
@@ -183,6 +183,8 @@ function appendSkillsToPack(pack, skillEntries, depth, root) {
183
183
  links: skill.links,
184
184
  artifacts: [],
185
185
  refs: [],
186
+ context_refs: [],
187
+ evidence_refs: [],
186
188
  aliases: [skill.slug, ...skill.tags],
187
189
  attributes: {},
188
190
  body: depth === "full" ? loadSkillFullBody(root, skill) : renderSkillMetaBody(skill),
@@ -35,6 +35,8 @@ function toNodeSummaryJson(node) {
35
35
  relates: [...node.edges.relates],
36
36
  blocked_by: [...node.edges.blocked_by],
37
37
  blocks: [...node.edges.blocks],
38
+ context_refs: [...(node.edges.context_refs ?? [])],
39
+ evidence_refs: [...(node.edges.evidence_refs ?? [])],
38
40
  },
39
41
  ...(node.source ? { source: node.source } : {}),
40
42
  };
@@ -103,6 +103,14 @@ function runShowCommand(options) {
103
103
  if (blocksLine) {
104
104
  lines.push(blocksLine);
105
105
  }
106
+ const contextRefsLine = maybeLine("context_refs", node.edges.context_refs ?? []);
107
+ if (contextRefsLine) {
108
+ lines.push(contextRefsLine);
109
+ }
110
+ const evidenceRefsLine = maybeLine("evidence_refs", node.edges.evidence_refs ?? []);
111
+ if (evidenceRefsLine) {
112
+ lines.push(evidenceRefsLine);
113
+ }
106
114
  if (!options.metaOnly && body.length > 0) {
107
115
  lines.push("");
108
116
  lines.push(body);
@@ -229,6 +229,7 @@ function collectStatus(root) {
229
229
  selected_exists: selected.state === null ? null : !selectedMissing,
230
230
  selected_achieved: selected.state === null ? null : selectedAchieved,
231
231
  active_node: selectedNode?.attributes.active_node ?? null,
232
+ last_active_node: selectedNode?.attributes.last_active_node ?? null,
232
233
  goal_state: selectedNode?.attributes.goal_state ?? null,
233
234
  status: selectedNode?.status ?? null,
234
235
  },
@@ -297,6 +297,7 @@ function runTaskDoneCommandLocked(options) {
297
297
  ws: loaded.ws,
298
298
  relates: loaded.id,
299
299
  scope: loaded.id,
300
+ kind: options.checkpointKind,
300
301
  runId: options.runId,
301
302
  note: `checkpoint created from mdkg task done for ${loaded.id}`,
302
303
  now,
@@ -309,6 +310,7 @@ function runTaskDoneCommandLocked(options) {
309
310
  ws: loaded.ws,
310
311
  relates: loaded.id,
311
312
  scope: loaded.id,
313
+ kind: options.checkpointKind,
312
314
  runId: options.runId,
313
315
  note: `checkpoint created from mdkg task done for ${loaded.id}`,
314
316
  now,
@@ -13,6 +13,8 @@ const paths_1 = require("../core/paths");
13
13
  const version_1 = require("../core/version");
14
14
  const project_db_1 = require("../core/project_db");
15
15
  const errors_1 = require("../util/errors");
16
+ const frontmatter_1 = require("../graph/frontmatter");
17
+ const workspace_files_1 = require("../graph/workspace_files");
16
18
  const init_manifest_1 = require("./init_manifest");
17
19
  const skill_support_1 = require("./skill_support");
18
20
  const skill_mirror_1 = require("./skill_mirror");
@@ -273,6 +275,64 @@ function migrateConfigIfNeeded(root, dryRun, summary, changes) {
273
275
  writeFile(cfgPath, `${JSON.stringify(nextConfig.config, null, 2)}\n`);
274
276
  }
275
277
  }
278
+ function migrateClosedGoalActiveNodes(root, dryRun, summary, changes) {
279
+ const config = (0, config_1.validateConfigSchema)((0, migrate_1.migrateConfig)(JSON.parse(fs_1.default.readFileSync((0, paths_1.configPath)(root), "utf8"))).config);
280
+ const filesByAlias = (0, workspace_files_1.listWorkspaceDocFilesByAlias)(root, config);
281
+ let planned = 0;
282
+ for (const files of Object.values(filesByAlias)) {
283
+ for (const filePath of files) {
284
+ const content = fs_1.default.readFileSync(filePath, "utf8");
285
+ let parsed;
286
+ try {
287
+ parsed = (0, frontmatter_1.parseFrontmatter)(content, filePath);
288
+ }
289
+ catch {
290
+ continue;
291
+ }
292
+ const frontmatter = { ...parsed.frontmatter };
293
+ if (frontmatter.type !== "goal") {
294
+ continue;
295
+ }
296
+ const status = typeof frontmatter.status === "string" ? frontmatter.status : "";
297
+ const goalState = typeof frontmatter.goal_state === "string" ? frontmatter.goal_state : "";
298
+ const activeNode = typeof frontmatter.active_node === "string" ? frontmatter.active_node : undefined;
299
+ if (!activeNode || (status !== "done" && goalState !== "achieved")) {
300
+ continue;
301
+ }
302
+ const relativePath = path_1.default.relative(root, filePath).split(path_1.default.sep).join("/");
303
+ const lastActiveNode = typeof frontmatter.last_active_node === "string" ? frontmatter.last_active_node : undefined;
304
+ if (lastActiveNode && lastActiveNode !== activeNode) {
305
+ planned += 1;
306
+ record(summary, changes, {
307
+ path: relativePath,
308
+ category: "goal_lifecycle",
309
+ action: "conflict",
310
+ reason: `closed goal has active_node ${activeNode} but different last_active_node ${lastActiveNode}; local content preserved`,
311
+ });
312
+ continue;
313
+ }
314
+ planned += 1;
315
+ record(summary, changes, {
316
+ path: relativePath,
317
+ category: "goal_lifecycle",
318
+ action: "migrate",
319
+ reason: "move closed goal active_node to last_active_node",
320
+ });
321
+ if (!dryRun) {
322
+ if (!lastActiveNode) {
323
+ frontmatter.last_active_node = activeNode;
324
+ }
325
+ delete frontmatter.active_node;
326
+ const frontmatterBlock = ["---", ...(0, frontmatter_1.formatFrontmatter)(frontmatter, frontmatter_1.DEFAULT_FRONTMATTER_KEY_ORDER), "---"].join("\n");
327
+ const body = parsed.body.length > 0 ? parsed.body : "";
328
+ writeFile(filePath, body.length > 0 ? `${frontmatterBlock}\n${body}` : frontmatterBlock);
329
+ }
330
+ }
331
+ }
332
+ if (planned === 0) {
333
+ summary.unchanged += 1;
334
+ }
335
+ }
276
336
  function isIgnoredBySimpleGitignore(root, relativePath) {
277
337
  const ignorePath = path_1.default.join(root, ".gitignore");
278
338
  if (!fs_1.default.existsSync(ignorePath)) {
@@ -440,6 +500,7 @@ function runUpgradeCommand(options) {
440
500
  const agentWorkspace = isAgentWorkspace(root);
441
501
  const managedCurrentFiles = [];
442
502
  migrateConfigIfNeeded(root, dryRun, summary, changes);
503
+ migrateClosedGoalActiveNodes(root, dryRun, summary, changes);
443
504
  for (const file of currentManifest.files) {
444
505
  if (!shouldIncludeFile(file, agentWorkspace)) {
445
506
  continue;