mdkg 0.3.2 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,12 +7,14 @@ exports.runGoalShowCommand = runGoalShowCommand;
7
7
  exports.runGoalEvaluateCommand = runGoalEvaluateCommand;
8
8
  exports.runGoalNextCommand = runGoalNextCommand;
9
9
  exports.runGoalSelectCommand = runGoalSelectCommand;
10
+ exports.runGoalActivateCommand = runGoalActivateCommand;
10
11
  exports.runGoalCurrentCommand = runGoalCurrentCommand;
11
12
  exports.runGoalClearCommand = runGoalClearCommand;
12
13
  exports.runGoalClaimCommand = runGoalClaimCommand;
13
14
  exports.runGoalPauseCommand = runGoalPauseCommand;
14
15
  exports.runGoalResumeCommand = runGoalResumeCommand;
15
16
  exports.runGoalDoneCommand = runGoalDoneCommand;
17
+ exports.runGoalArchiveCommand = runGoalArchiveCommand;
16
18
  const fs_1 = __importDefault(require("fs"));
17
19
  const path_1 = __importDefault(require("path"));
18
20
  const config_1 = require("../core/config");
@@ -35,11 +37,13 @@ const GOAL_STATE_BY_ACTION = {
35
37
  pause: "paused",
36
38
  resume: "active",
37
39
  done: "achieved",
40
+ archive: "archived",
38
41
  };
39
42
  const STATUS_BY_ACTION = {
40
43
  pause: "blocked",
41
44
  resume: "progress",
42
45
  done: "done",
46
+ archive: "archived",
43
47
  };
44
48
  function normalizeWorkspace(value) {
45
49
  if (!value) {
@@ -95,6 +99,22 @@ function writeSelectedGoalState(root, node, now) {
95
99
  };
96
100
  (0, atomic_1.atomicWriteFile)(selectedGoalPath(root), `${JSON.stringify(state, null, 2)}\n`);
97
101
  }
102
+ function readNodeFrontmatter(root, node) {
103
+ const filePath = path_1.default.resolve(root, node.path);
104
+ const parsed = (0, frontmatter_1.parseFrontmatter)(fs_1.default.readFileSync(filePath, "utf8"), filePath);
105
+ return {
106
+ filePath,
107
+ frontmatter: { ...parsed.frontmatter },
108
+ body: parsed.body,
109
+ };
110
+ }
111
+ function writeNodeFrontmatterFile(filePath, frontmatter, body, now) {
112
+ frontmatter.updated = (0, date_1.formatDate)(now);
113
+ const lines = (0, frontmatter_1.formatFrontmatter)(frontmatter, frontmatter_1.DEFAULT_FRONTMATTER_KEY_ORDER);
114
+ const frontmatterBlock = ["---", ...lines, "---"].join("\n");
115
+ const content = body.length > 0 ? `${frontmatterBlock}\n${body}` : frontmatterBlock;
116
+ (0, atomic_1.atomicWriteFile)(filePath, content);
117
+ }
98
118
  function removeSelectedGoalState(root) {
99
119
  const filePath = selectedGoalPath(root);
100
120
  if (!fs_1.default.existsSync(filePath)) {
@@ -125,6 +145,14 @@ function activeGoalCandidates(index, wsHint) {
125
145
  .filter((node) => node.status === "progress" && node.attributes.goal_state === "active")
126
146
  .sort((a, b) => a.qid.localeCompare(b.qid));
127
147
  }
148
+ function activeGoalConflicts(index, target) {
149
+ return activeGoalCandidates(index, target.ws).filter((node) => node.qid !== target.qid);
150
+ }
151
+ function isArchivedGoal(node) {
152
+ return Boolean(node &&
153
+ node.type === "goal" &&
154
+ (node.status === "archived" || node.attributes.goal_state === "archived"));
155
+ }
128
156
  function resolveGoalSelection(root, index, idOrQid, wsHint) {
129
157
  const warnings = [];
130
158
  if (idOrQid) {
@@ -137,19 +165,24 @@ function resolveGoalSelection(root, index, idOrQid, wsHint) {
137
165
  const selected = readSelectedGoalState(root, warnings);
138
166
  if (selected) {
139
167
  const node = index.nodes[selected.qid];
140
- if (node && node.type === "goal" && !node.source?.imported) {
168
+ if (node && node.type === "goal" && !node.source?.imported && !isArchivedGoal(node)) {
141
169
  return { node, source: "selected", warnings };
142
170
  }
143
- warnings.push(`selected goal ${selected.qid} is not available; run \`mdkg goal select <goal-id>\``);
171
+ if (isArchivedGoal(node)) {
172
+ warnings.push(`selected goal ${selected.qid} is archived; run \`mdkg goal activate <goal-id>\` or \`mdkg goal clear\``);
173
+ }
174
+ else {
175
+ warnings.push(`selected goal ${selected.qid} is not available; run \`mdkg goal select <goal-id>\``);
176
+ }
144
177
  }
145
178
  const active = activeGoalCandidates(index, wsHint);
146
179
  if (active.length === 1) {
147
180
  return { node: active[0], source: "unique_active", warnings };
148
181
  }
149
182
  if (active.length > 1) {
150
- throw new errors_1.UsageError(`multiple active goals found: ${active.map((node) => node.qid).join(", ")}; run \`mdkg goal select <goal-id>\``);
183
+ throw new errors_1.UsageError(`multiple active goals found: ${active.map((node) => node.qid).join(", ")}; run \`mdkg goal activate <goal-id>\``);
151
184
  }
152
- throw new errors_1.NotFoundError("no selected goal or unique active goal found; run `mdkg goal select <goal-id>`");
185
+ throw new errors_1.NotFoundError("no selected goal or unique active goal found; run `mdkg goal activate <goal-id>`");
153
186
  }
154
187
  function loadGoal(root, idOrQid, wsHint) {
155
188
  const config = (0, config_1.loadConfig)(root);
@@ -201,11 +234,7 @@ function goalReceipt(root, loaded) {
201
234
  };
202
235
  }
203
236
  function writeGoalFile(loaded, now) {
204
- loaded.frontmatter.updated = (0, date_1.formatDate)(now);
205
- const lines = (0, frontmatter_1.formatFrontmatter)(loaded.frontmatter, frontmatter_1.DEFAULT_FRONTMATTER_KEY_ORDER);
206
- const frontmatterBlock = ["---", ...lines, "---"].join("\n");
207
- const content = loaded.body.length > 0 ? `${frontmatterBlock}\n${loaded.body}` : frontmatterBlock;
208
- (0, atomic_1.atomicWriteFile)(loaded.filePath, content);
237
+ writeNodeFrontmatterFile(loaded.filePath, loaded.frontmatter, loaded.body, now);
209
238
  }
210
239
  function maybeReindex(root, config) {
211
240
  if (!config.index.auto_reindex) {
@@ -215,6 +244,9 @@ function maybeReindex(root, config) {
215
244
  }
216
245
  function ensureStatusAllowed(config, status) {
217
246
  const normalized = status.toLowerCase();
247
+ if (normalized === "archived") {
248
+ return normalized;
249
+ }
218
250
  const allowed = new Set(config.work.status_enum.map((value) => value.toLowerCase()));
219
251
  if (!allowed.has(normalized)) {
220
252
  throw new errors_1.UsageError(`goal status ${normalized} is not allowed by work.status_enum`);
@@ -301,6 +333,24 @@ function runGoalEvaluateCommand(options) {
301
333
  }
302
334
  function runGoalNextCommand(options) {
303
335
  const loaded = loadGoal(options.root, options.id, options.ws);
336
+ if (isArchivedGoal(loaded.node)) {
337
+ const warnings = [...loaded.warnings, `${loaded.node.qid} is archived and has no actionable next work`];
338
+ if (options.json) {
339
+ console.log(JSON.stringify({
340
+ action: "selected",
341
+ goal: goalReceipt(options.root, loaded),
342
+ goal_source: loaded.resolutionSource,
343
+ node: null,
344
+ warnings,
345
+ }, null, 2));
346
+ return;
347
+ }
348
+ for (const warning of warnings) {
349
+ console.error(`warning: ${warning}`);
350
+ }
351
+ console.error("no actionable local work found for goal");
352
+ return;
353
+ }
304
354
  const statusPreference = loaded.config.work.next.status_preference.map((status) => status.toLowerCase());
305
355
  const statusRanks = new Set(statusPreference);
306
356
  const warnings = [...loaded.warnings];
@@ -362,6 +412,9 @@ function runGoalSelectCommand(options) {
362
412
  const config = (0, config_1.loadConfig)(options.root);
363
413
  return (0, lock_1.withMutationLock)(options.root, config.index.lock_timeout_ms, () => {
364
414
  const loaded = loadGoal(options.root, options.id, options.ws);
415
+ if (isArchivedGoal(loaded.node)) {
416
+ throw new errors_1.UsageError(`cannot select archived goal ${loaded.node.qid}`);
417
+ }
365
418
  const now = options.now ?? new Date();
366
419
  writeSelectedGoalState(options.root, loaded.node, now);
367
420
  const receipt = {
@@ -379,6 +432,74 @@ function runGoalSelectCommand(options) {
379
432
  console.log(`selected goal: ${loaded.node.qid}`);
380
433
  });
381
434
  }
435
+ function runGoalActivateCommand(options) {
436
+ const config = (0, config_1.loadConfig)(options.root);
437
+ return (0, lock_1.withMutationLock)(options.root, config.index.lock_timeout_ms, () => {
438
+ const loaded = loadGoal(options.root, options.id, options.ws);
439
+ const currentState = String(loaded.frontmatter.goal_state ?? "");
440
+ const currentStatus = String(loaded.frontmatter.status ?? "");
441
+ if (loaded.node.status === "done" || currentStatus === "done" || currentState === "achieved") {
442
+ throw new errors_1.UsageError(`cannot activate achieved goal ${loaded.node.qid}`);
443
+ }
444
+ if (loaded.node.status === "archived" || currentStatus === "archived" || currentState === "archived") {
445
+ throw new errors_1.UsageError(`cannot activate archived goal ${loaded.node.qid}`);
446
+ }
447
+ const now = options.now ?? new Date();
448
+ const pausedGoals = [];
449
+ const conflicts = activeGoalConflicts(loaded.index, loaded.node);
450
+ for (const conflict of conflicts) {
451
+ const conflictFile = readNodeFrontmatter(options.root, conflict);
452
+ conflictFile.frontmatter.goal_state = "paused";
453
+ conflictFile.frontmatter.status = ensureStatusAllowed(config, "blocked");
454
+ writeNodeFrontmatterFile(conflictFile.filePath, conflictFile.frontmatter, conflictFile.body, now);
455
+ pausedGoals.push({
456
+ workspace: conflict.ws,
457
+ id: conflict.id,
458
+ qid: conflict.qid,
459
+ path: conflict.path,
460
+ previous_status: conflict.status ?? "",
461
+ previous_goal_state: String(conflict.attributes.goal_state ?? ""),
462
+ status: "blocked",
463
+ goal_state: "paused",
464
+ });
465
+ }
466
+ loaded.frontmatter.goal_state = "active";
467
+ loaded.frontmatter.status = ensureStatusAllowed(config, "progress");
468
+ writeGoalFile(loaded, now);
469
+ writeSelectedGoalState(options.root, loaded.node, now);
470
+ maybeReindex(options.root, loaded.config);
471
+ (0, event_support_1.appendAutomaticEvent)({
472
+ root: options.root,
473
+ ws: loaded.node.ws,
474
+ kind: "GOAL_ACTIVATE",
475
+ status: "ok",
476
+ refs: [loaded.node.id, ...conflicts.map((node) => node.id)],
477
+ notes: `goal activate via mdkg goal activate`,
478
+ now,
479
+ });
480
+ const receipt = {
481
+ action: "activated",
482
+ goal: goalReceipt(options.root, loaded),
483
+ activated_goal: goalReceipt(options.root, loaded),
484
+ paused_goals: pausedGoals,
485
+ selection: {
486
+ path: SELECTED_GOAL_STATE_PATH,
487
+ selected_at: now.toISOString(),
488
+ },
489
+ warnings: conflicts.length > 0
490
+ ? [`paused ${conflicts.length} competing active goal(s) in workspace ${loaded.node.ws}`]
491
+ : [],
492
+ };
493
+ if (options.json) {
494
+ console.log(JSON.stringify(receipt, null, 2));
495
+ return;
496
+ }
497
+ for (const warning of receipt.warnings) {
498
+ console.error(`warning: ${warning}`);
499
+ }
500
+ console.log(`goal activate: ${loaded.node.qid}`);
501
+ });
502
+ }
382
503
  function runGoalCurrentCommand(options) {
383
504
  const config = (0, config_1.loadConfig)(options.root);
384
505
  const { index } = (0, index_cache_1.loadIndex)({ root: options.root, config });
@@ -389,12 +510,17 @@ function runGoalCurrentCommand(options) {
389
510
  const selected = readSelectedGoalState(options.root, warnings);
390
511
  if (selected) {
391
512
  const selectedNode = index.nodes[selected.qid];
392
- if (selectedNode && selectedNode.type === "goal" && !selectedNode.source?.imported) {
513
+ if (selectedNode && selectedNode.type === "goal" && !selectedNode.source?.imported && !isArchivedGoal(selectedNode)) {
393
514
  node = selectedNode;
394
515
  source = "selected";
395
516
  }
396
517
  else {
397
- warnings.push(`selected goal ${selected.qid} is not available; run \`mdkg goal select <goal-id>\``);
518
+ if (isArchivedGoal(selectedNode)) {
519
+ warnings.push(`selected goal ${selected.qid} is archived; run \`mdkg goal activate <goal-id>\` or \`mdkg goal clear\``);
520
+ }
521
+ else {
522
+ warnings.push(`selected goal ${selected.qid} is not available; run \`mdkg goal select <goal-id>\``);
523
+ }
398
524
  }
399
525
  }
400
526
  if (!node) {
@@ -405,7 +531,7 @@ function runGoalCurrentCommand(options) {
405
531
  }
406
532
  else if (active.length > 1) {
407
533
  source = "ambiguous";
408
- warnings.push(`multiple active goals found: ${active.map((goal) => goal.qid).join(", ")}`);
534
+ warnings.push(`multiple active goals found: ${active.map((goal) => goal.qid).join(", ")}; run \`mdkg goal activate <goal-id>\``);
409
535
  }
410
536
  }
411
537
  const receipt = {
@@ -464,6 +590,9 @@ function runGoalClaimCommand(options) {
464
590
  const config = (0, config_1.loadConfig)(options.root);
465
591
  return (0, lock_1.withMutationLock)(options.root, config.index.lock_timeout_ms, () => {
466
592
  const loaded = loadGoal(options.root, options.id, options.ws);
593
+ if (isArchivedGoal(loaded.node)) {
594
+ throw new errors_1.UsageError(`cannot claim work for archived goal ${loaded.node.qid}`);
595
+ }
467
596
  const resolved = (0, qid_1.resolveQid)(loaded.index, options.workId, loaded.node.ws);
468
597
  if (resolved.status !== "ok") {
469
598
  throw new errors_1.NotFoundError((0, qid_1.formatResolveError)("work", options.workId, resolved, loaded.node.ws));
@@ -510,6 +639,9 @@ function runGoalClaimCommand(options) {
510
639
  }
511
640
  function runGoalStateMutationLocked(action, options) {
512
641
  const loaded = loadGoal(options.root, options.id, options.ws);
642
+ if (action !== "archive" && isArchivedGoal(loaded.node)) {
643
+ throw new errors_1.UsageError(`cannot ${action} archived goal ${loaded.node.qid}`);
644
+ }
513
645
  const now = options.now ?? new Date();
514
646
  loaded.frontmatter.goal_state = GOAL_STATE_BY_ACTION[action];
515
647
  loaded.frontmatter.status = ensureStatusAllowed(loaded.config, STATUS_BY_ACTION[action]);
@@ -546,3 +678,7 @@ function runGoalDoneCommand(options) {
546
678
  const config = (0, config_1.loadConfig)(options.root);
547
679
  return (0, lock_1.withMutationLock)(options.root, config.index.lock_timeout_ms, () => runGoalStateMutationLocked("done", options));
548
680
  }
681
+ function runGoalArchiveCommand(options) {
682
+ const config = (0, config_1.loadConfig)(options.root);
683
+ return (0, lock_1.withMutationLock)(options.root, config.index.lock_timeout_ms, () => runGoalStateMutationLocked("archive", options));
684
+ }
@@ -228,11 +228,12 @@ function runNewCommandLocked(options) {
228
228
  let status;
229
229
  if (node_1.WORK_TYPES.has(type)) {
230
230
  const allowed = new Set(config.work.status_enum.map((value) => value.toLowerCase()));
231
+ const allowedForType = type === "goal" ? new Set([...allowed, "archived"]) : allowed;
231
232
  status = statusInput ?? (type === "goal" && allowed.has("progress")
232
233
  ? "progress"
233
234
  : config.work.status_enum[0]?.toLowerCase());
234
- if (!status || !allowed.has(status)) {
235
- throw new errors_1.UsageError(`--status must be one of ${Array.from(allowed).join(", ")}`);
235
+ if (!status || !allowedForType.has(status)) {
236
+ throw new errors_1.UsageError(`--status must be one of ${Array.from(allowedForType).join(", ")}`);
236
237
  }
237
238
  }
238
239
  else if (type === "dec") {
@@ -346,7 +347,7 @@ function runNewCommandLocked(options) {
346
347
  skills: skills.length > 0 ? skills : undefined,
347
348
  cases: cases.length > 0 ? cases : undefined,
348
349
  supersedes: options.supersedes ? options.supersedes.toLowerCase() : undefined,
349
- goal_state: type === "goal" ? (status === "done" ? "achieved" : status === "blocked" ? "blocked" : "active") : undefined,
350
+ goal_state: type === "goal" ? (status === "done" ? "achieved" : status === "blocked" ? "blocked" : status === "archived" ? "archived" : "active") : undefined,
350
351
  goal_condition: type === "goal" ? title : undefined,
351
352
  max_iterations: type === "goal" ? 25 : undefined,
352
353
  blocked_after_attempts: type === "goal" ? 3 : undefined,
@@ -30,7 +30,7 @@ exports.ALLOWED_TYPES = new Set([
30
30
  ...agent_file_types_1.AGENT_FILE_TYPES,
31
31
  ]);
32
32
  const DEC_STATUS = new Set(["proposed", "accepted", "rejected", "superseded"]);
33
- const GOAL_STATE = new Set(["active", "paused", "achieved", "blocked", "budget_limited"]);
33
+ const GOAL_STATE = new Set(["active", "paused", "achieved", "blocked", "budget_limited", "archived"]);
34
34
  const SKILL_SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
35
35
  const GOAL_ATTRIBUTE_KEYS = [
36
36
  "goal_state",
@@ -280,13 +280,14 @@ function parseNode(content, filePath, options) {
280
280
  const statusValue = optionalString(frontmatter, "status", filePath);
281
281
  let status = undefined;
282
282
  const workStatus = new Set(options.workStatusEnum.map((value) => value.toLowerCase()));
283
+ const allowedWorkStatus = type === "goal" ? new Set([...workStatus, "archived"]) : workStatus;
283
284
  if (exports.WORK_TYPES.has(type)) {
284
285
  if (!statusValue) {
285
286
  throw formatError(filePath, "status is required for work items");
286
287
  }
287
288
  const normalized = requireLowercase(statusValue, "status", filePath);
288
- if (!workStatus.has(normalized)) {
289
- throw formatError(filePath, `status must be one of ${Array.from(workStatus).join(", ")}`);
289
+ if (!allowedWorkStatus.has(normalized)) {
290
+ throw formatError(filePath, `status must be one of ${Array.from(allowedWorkStatus).join(", ")}`);
290
291
  }
291
292
  status = normalized;
292
293
  }
@@ -485,6 +485,26 @@ function validateGoalRefs(index, allowMissing, errors) {
485
485
  }
486
486
  }
487
487
  }
488
+ function validateSingleActiveRootGoals(index, errors) {
489
+ const activeByWorkspace = {};
490
+ for (const [qid, node] of Object.entries(index.nodes)) {
491
+ if (node.type !== "goal" || node.source?.imported) {
492
+ continue;
493
+ }
494
+ if (node.status === "progress" && node.attributes.goal_state === "active") {
495
+ if (!activeByWorkspace[node.ws]) {
496
+ activeByWorkspace[node.ws] = [];
497
+ }
498
+ activeByWorkspace[node.ws].push(qid);
499
+ }
500
+ }
501
+ for (const [workspace, qids] of Object.entries(activeByWorkspace)) {
502
+ if (qids.length <= 1) {
503
+ continue;
504
+ }
505
+ pushError(errors, `${workspace}: multiple active root goals found: ${qids.sort().join(", ")}; run mdkg goal activate <goal-id> to select exactly one`);
506
+ }
507
+ }
488
508
  function detectPrevNextCycles(index, errors) {
489
509
  const nodes = index.nodes;
490
510
  const seen = new Set();
@@ -533,6 +553,7 @@ function collectGraphErrors(index, options = {}) {
533
553
  validateAgentWorkflowFeedbackProposalRefs(index, allowMissing, knownSkillSlugs, externalWorkspaces, errors);
534
554
  validateArchiveUriRefs(index, allowMissing, errors);
535
555
  validateGoalRefs(index, allowMissing, errors);
556
+ validateSingleActiveRootGoals(index, errors);
536
557
  detectPrevNextCycles(index, errors);
537
558
  return errors;
538
559
  }
@@ -24,7 +24,7 @@ Agent operating prompt:
24
24
  - Use `mdkg show <id>` for direct inspection and `mdkg show <id> --meta` for card-only inspection.
25
25
  - Use `mdkg search "..."` and `mdkg next` to discover current work.
26
26
  - Use `mdkg new goal "..."` for long-running recursive objectives that need an explicit end condition, active node, required skills, required checks, and completion evidence.
27
- - Use `mdkg goal select <goal-id>` when a goal is active, then `mdkg goal next` to surface one scoped feature, task, bug, or test at a time; normal `mdkg next` remains for non-goal concrete work.
27
+ - Use `mdkg goal activate <goal-id>` to make one local root goal active, then `mdkg goal next` to surface one scoped feature, task, bug, test, or spike at a time; normal `mdkg next` remains for non-goal concrete work.
28
28
  - Use `mdkg goal claim [goal-id] <work-id>` only after accepting the surfaced work item; `mdkg goal next` is read-only.
29
29
  - Treat goal `required_checks` as report-only guidance from mdkg. Run commands yourself, then record evidence in the goal or active work item.
30
30
  - Record skill improvement candidates during normal goal execution; edit `SKILL.md` only when the active node is explicit skill-maintenance work.
@@ -78,7 +78,7 @@ If the active task is known:
78
78
  - `mdkg validate`
79
79
 
80
80
  If an active goal is known:
81
- - `mdkg goal select <goal-id>`
81
+ - `mdkg goal activate <goal-id>`
82
82
  - `mdkg goal current`
83
83
  - `mdkg goal next`
84
84
  - `mdkg goal claim <work-id>`
@@ -27,14 +27,20 @@ Primary commands:
27
27
  - `mdkg task`
28
28
  - `mdkg validate`
29
29
  - `mdkg status [--json]`
30
- - `mdkg fix plan [--family index|refs|ids|all] [--target <id-or-qid>] [--json]`
30
+ - `mdkg fix plan [--family index|refs|ids|all] [--target <id-or-qid>] [--base-ref <ref>] [--json]`
31
+ - `mdkg fix apply [--family ids] [--target <id-or-qid>] [--base-ref <ref>] [--json]`
32
+ - `mdkg fix ids [--target <id-or-qid>] [--base-ref <ref>] [--apply] [--json]`
31
33
 
32
34
  Operator health:
33
35
  - `mdkg status [--json]` is a read-only summary for scripts and agents
34
36
  - reports mdkg version/config, git state, graph/index freshness, selected-goal state, project DB verification summary, and generated cache status
35
37
  - does not rebuild indexes, run migrations, repair files, mutate graph nodes, or change selected-goal state
36
- - `mdkg fix plan ...` is dry-run repair planning only; it writes nothing and `fix apply` is not exposed
37
- - `fix plan --json` returns a receipt-shaped plan with selected families, risk counts, paths, reason codes, and `apply_supported: false`
38
+ - `mdkg fix plan ...` is dry-run repair planning only; it writes nothing
39
+ - duplicate-ID graph repairs can be applied with `mdkg fix apply --family ids` or `mdkg fix ids --apply`
40
+ - use `--base-ref main` when mainline IDs should win branch-merge repair
41
+ - unresolved Git add/add conflict stages are split by keeping stage 2 at the conflicted path and writing stage 3 to a new canonical ID/path
42
+ - graph-reference and index/cache findings remain review-only guidance
43
+ - `fix plan --json` returns a receipt-shaped plan with selected families, risk counts, paths, reason codes, and per-change `apply_supported` metadata
38
44
 
39
45
  Index backend:
40
46
  - fresh mdkg workspaces default to `index.backend: sqlite`
@@ -242,14 +248,16 @@ Work semantic mirrors:
242
248
  Goal nodes:
243
249
  - `mdkg goal show <goal-id-or-qid> [--json]`
244
250
  - `mdkg goal select <goal-id-or-qid> [--json]`
251
+ - `mdkg goal activate <goal-id-or-qid> [--json]`
245
252
  - `mdkg goal current [--json]`
246
253
  - `mdkg goal clear [--json]`
247
254
  - `mdkg goal next [goal-id-or-qid] [--json]`
248
255
  - `mdkg goal claim [goal-id-or-qid] <work-id-or-qid> [--json]`
249
256
  - `mdkg goal evaluate <goal-id-or-qid> [--json]`
250
- - `mdkg goal pause|resume|done <goal-id-or-qid> [--json]`
257
+ - `mdkg goal pause|resume|done|archive <goal-id-or-qid> [--json]`
251
258
  - `mdkg goal show <goal-id-or-qid> [--ws <alias>] [--json]`
252
259
  - `mdkg goal select <goal-id-or-qid> [--ws <alias>] [--json]`
260
+ - `mdkg goal activate <goal-id-or-qid> [--ws <alias>] [--json]`
253
261
  - `mdkg goal current [--ws <alias>] [--json]`
254
262
  - `mdkg goal next [goal-id-or-qid] [--ws <alias>] [--json]`
255
263
  - `mdkg goal claim <work-id-or-qid> [--ws <alias>] [--json]`
@@ -258,7 +266,10 @@ Goal nodes:
258
266
  - `mdkg goal pause <goal-id-or-qid> [--ws <alias>] [--json]`
259
267
  - `mdkg goal resume <goal-id-or-qid> [--ws <alias>] [--json]`
260
268
  - `mdkg goal done <goal-id-or-qid> [--ws <alias>] [--json]`
269
+ - `mdkg goal archive <goal-id-or-qid> [--ws <alias>] [--json]`
261
270
  - goals orchestrate recursive progress through explicit `scope_refs`; tasks, bugs, tests, spikes, and features remain concrete executable units
271
+ - `goal activate` makes one local root goal active, pauses competing local active goals in the same workspace, and writes selected-goal state
272
+ - `goal archive` marks a superseded historical goal archived so it remains readable but not actionable
262
273
  - `goal next` is read-only; use `goal claim` to set `active_node`
263
274
  - `mdkg goal evaluate` is report-only and never runs commands from `required_checks`
264
275
  - skill improvements discovered during normal goal execution should be recorded as candidates or proposals unless the active node is skill-maintenance
@@ -35,7 +35,7 @@ mdkg fix plan --json
35
35
  mdkg validate
36
36
  ```
37
37
 
38
- This repo is already initialized. Use `mdkg upgrade` to preview safe scaffold updates, `mdkg index` to create or refresh generated graph/skill/capability/subgraph and SQLite caches after init, `mdkg new` to create work, `mdkg new goal "..."` plus `mdkg goal select/current/next/claim/evaluate` for recursive long-running objectives, `mdkg search`/`mdkg show` to inspect graph state, `mdkg capability ...` to inspect cached skill/spec/work/core/design capabilities, `mdkg spec ...` for focused optional SPEC records, `mdkg capability resolve ...` to rank local and subgraph capabilities, `mdkg archive ...` to register source/artifact sidecars, `mdkg work ...` to create work contract/order/receipt semantic mirrors and deterministic trigger/verification records, `mdkg bundle ...` to create full graph snapshot bundles, `mdkg subgraph ...` to register read-only child graph planning views, `mdkg pack <id>` to build deterministic context, and `mdkg validate` before closeout.
38
+ This repo is already initialized. Use `mdkg upgrade` to preview safe scaffold updates, `mdkg index` to create or refresh generated graph/skill/capability/subgraph and SQLite caches after init, `mdkg new` to create work, `mdkg new goal "..."` plus `mdkg goal activate/current/next/claim/evaluate` for recursive long-running objectives, `mdkg search`/`mdkg show` to inspect graph state, `mdkg capability ...` to inspect cached skill/spec/work/core/design capabilities, `mdkg spec ...` for focused optional SPEC records, `mdkg capability resolve ...` to rank local and subgraph capabilities, `mdkg archive ...` to register source/artifact sidecars, `mdkg work ...` to create work contract/order/receipt semantic mirrors and deterministic trigger/verification records, `mdkg bundle ...` to create full graph snapshot bundles, `mdkg subgraph ...` to register read-only child graph planning views, `mdkg pack <id>` to build deterministic context, and `mdkg validate` before closeout.
39
39
 
40
40
  Use `mdkg status --json` for a read-only operator summary of Git, graph,
41
41
  selected-goal, project DB, and generated-cache health before mutating work. Use
@@ -45,8 +45,13 @@ fresh init, run `mdkg index` first so strict doctor can load generated caches.
45
45
 
46
46
  Use `mdkg fix plan --json` for dry-run repair guidance. It reports generated
47
47
  index/cache repair hints, missing graph references, and duplicate local ids as
48
- receipt-shaped planned changes with risk levels and `apply_supported: false`.
49
- `fix apply` is not exposed; repair application is intentionally deferred.
48
+ receipt-shaped planned changes with risk levels and per-change
49
+ `apply_supported` metadata. Duplicate-ID graph repairs can be applied with
50
+ `mdkg fix apply --family ids --json` or `mdkg fix ids --apply --json`; use
51
+ `--base-ref main` when mainline IDs should win. Index/cache and graph-reference
52
+ findings remain review-only. For unresolved Git add/add conflicts, `fix ids`
53
+ keeps stage 2 at the conflicted path, rewrites stage 3 to the next unused
54
+ canonical ID/path, and records a receipt.
50
55
 
51
56
  Use research spikes for investigation and planning work that should produce a
52
57
  reviewable recommendation before implementation:
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schema_version": 1,
3
3
  "tool": "mdkg",
4
- "mdkg_version": "0.3.2",
4
+ "mdkg_version": "0.3.4",
5
5
  "files": [
6
6
  {
7
7
  "path": ".mdkg/config.json",
@@ -61,7 +61,7 @@
61
61
  {
62
62
  "path": ".mdkg/README.md",
63
63
  "category": "mdkg_doc",
64
- "sha256": "c08de01602698af7eb46b70e9459df9c2fa6e6c32c06b2daab3f697baa450e0d"
64
+ "sha256": "5962993e8dffd53ccbceaaedd0b8f2a868421a3a1cc16d7002b661b39b7ebf79"
65
65
  },
66
66
  {
67
67
  "path": ".mdkg/skills/build-pack-and-execute-task/SKILL.md",
@@ -71,7 +71,7 @@
71
71
  {
72
72
  "path": ".mdkg/skills/pursue-mdkg-goal/SKILL.md",
73
73
  "category": "default_skill",
74
- "sha256": "fd64f666fb1329c392ff0979ed9999b71ad948cbca6a191502eb2cdfd76ec825"
74
+ "sha256": "2bc888092d95b362e402494af4dbe6909204bd5578613d6159753a84efaae16d"
75
75
  },
76
76
  {
77
77
  "path": ".mdkg/skills/select-work-and-ground-context/SKILL.md",
@@ -246,7 +246,7 @@
246
246
  {
247
247
  "path": "AGENT_START.md",
248
248
  "category": "startup_doc",
249
- "sha256": "cf58e37c72be2593f1d920520dbdc6e316182bfda5c49837443a8b18024504c7"
249
+ "sha256": "46e734b6eae92eb957c3e29fb4d206e4be3fd6d32dd00e888e01a0dc75979428"
250
250
  },
251
251
  {
252
252
  "path": "AGENTS.md",
@@ -261,7 +261,7 @@
261
261
  {
262
262
  "path": "CLI_COMMAND_MATRIX.md",
263
263
  "category": "startup_doc",
264
- "sha256": "888c5ce1372c0a7ba2c24bfe0aea97e221804cab1fa8940e06dc050330830a76"
264
+ "sha256": "7322e6a2c47261f87dacd15ba0d93eba0d1e32eec0900a1e9f447ad13aaae860"
265
265
  },
266
266
  {
267
267
  "path": "llms.txt",
@@ -27,7 +27,8 @@ Move one durable mdkg goal forward without losing scope, evidence, or user inten
27
27
  ## Steps
28
28
 
29
29
  1. Resolve the goal:
30
- - If a goal id is provided, run `mdkg goal select <goal-id>`.
30
+ - If a goal id is provided and you are beginning durable work on it, run `mdkg goal activate <goal-id>`.
31
+ - Use `mdkg goal select <goal-id>` only when you need a local pointer without changing lifecycle state.
31
32
  - Otherwise run `mdkg goal current`.
32
33
  - If the result is missing or ambiguous, ask the user to select a goal.
33
34
  2. Inspect the goal with `mdkg goal show <goal-id>` and build context with `mdkg pack <goal-id>`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mdkg",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "description": "Markdown Knowledge Graph",
5
5
  "license": "MIT",
6
6
  "bin": {
@@ -28,8 +28,10 @@
28
28
  "smoke:operator-health": "npm run build && node scripts/smoke-operator-health.js",
29
29
  "smoke:fix-plan": "npm run build && node scripts/smoke-fix-plan.js",
30
30
  "smoke:branch-conflicts": "npm run build && node scripts/smoke-branch-conflicts.js",
31
+ "smoke:id-repair": "npm run build && node scripts/smoke-id-repair.js",
31
32
  "smoke:command-docs": "npm run build && node scripts/smoke-command-docs.js",
32
33
  "smoke:spike": "npm run build && node scripts/smoke-spike.js",
34
+ "smoke:goal-lifecycle": "npm run build && node scripts/smoke-goal-lifecycle.js",
33
35
  "smoke:bundle": "npm run build && node scripts/smoke-bundle.js",
34
36
  "smoke:bundle-import": "npm run smoke:subgraph",
35
37
  "smoke:visibility": "npm run build && node scripts/smoke-visibility.js",
@@ -40,7 +42,7 @@
40
42
  "cli:check": "npm run build && node scripts/cli_help_snapshot.js --check",
41
43
  "cli:contract": "npm run build && node scripts/generate-command-contract.js --check",
42
44
  "prepack": "npm run build && node scripts/assert-publish-ready.js",
43
- "prepublishOnly": "npm run test && npm run cli:check && npm run cli:contract && node dist/cli.js validate && npm run smoke:consumer && npm run smoke:matrix && npm run smoke:upgrade && npm run smoke:init && npm run smoke:capabilities && npm run smoke:db && npm run smoke:db-queue && npm run smoke:db-queue-cli && npm run smoke:db-events && npm run smoke:db-materializer && npm run smoke:db-snapshot && npm run smoke:archive-work && npm run smoke:work-invocation && npm run smoke:cli-ux-polish && npm run smoke:operator-health && npm run smoke:fix-plan && npm run smoke:branch-conflicts && npm run smoke:command-docs && npm run smoke:spike && npm run smoke:bundle && npm run smoke:subgraph && npm run smoke:visibility && npm run smoke:sqlite && npm run smoke:parallel && npm run smoke:goal && node scripts/assert-publish-ready.js",
45
+ "prepublishOnly": "npm run test && npm run cli:check && npm run cli:contract && node dist/cli.js validate && npm run smoke:consumer && npm run smoke:matrix && npm run smoke:upgrade && npm run smoke:init && npm run smoke:capabilities && npm run smoke:db && npm run smoke:db-queue && npm run smoke:db-queue-cli && npm run smoke:db-events && npm run smoke:db-materializer && npm run smoke:db-snapshot && npm run smoke:archive-work && npm run smoke:work-invocation && npm run smoke:cli-ux-polish && npm run smoke:operator-health && npm run smoke:fix-plan && npm run smoke:branch-conflicts && npm run smoke:id-repair && npm run smoke:command-docs && npm run smoke:spike && npm run smoke:goal-lifecycle && npm run smoke:bundle && npm run smoke:subgraph && npm run smoke:visibility && npm run smoke:sqlite && npm run smoke:parallel && npm run smoke:goal && node scripts/assert-publish-ready.js",
44
46
  "postinstall": "node scripts/postinstall.js",
45
47
  "smoke:subgraph": "npm run build && node scripts/smoke-subgraph.js"
46
48
  },