mancode 0.3.17 → 0.4.0

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.
@@ -1,4 +1,5 @@
1
1
  // src/installers/v3-adapter.ts
2
+ import { createHash } from "crypto";
2
3
  import {
3
4
  lstat,
4
5
  mkdir,
@@ -9,6 +10,7 @@ import {
9
10
  writeFile
10
11
  } from "fs/promises";
11
12
  import path from "path";
13
+ import { TextDecoder } from "util";
12
14
 
13
15
  // src/installers/managed-block.ts
14
16
  var DEFAULT_MANCODE_START_MARKER = "<!-- mancode:start -->";
@@ -29,6 +31,20 @@ function hasManagedBlock(existing, startMarker = DEFAULT_MANCODE_START_MARKER, e
29
31
  const end = findMarkerLine(existing, endMarker);
30
32
  return start !== null && end !== null && end.start > start.start;
31
33
  }
34
+ function extractManagedBlock(existing, startMarker = DEFAULT_MANCODE_START_MARKER, endMarker = DEFAULT_MANCODE_END_MARKER) {
35
+ const starts = findMarkerLines(existing, startMarker);
36
+ const ends = findMarkerLines(existing, endMarker);
37
+ if (starts.length === 0 && ends.length === 0) return null;
38
+ if (starts.length !== 1 || ends.length !== 1) {
39
+ throw new Error("managed block is malformed: marker count is invalid");
40
+ }
41
+ const start = starts[0];
42
+ const end = ends[0];
43
+ if (!start || !end || end.start < start.start) {
44
+ throw new Error("managed block is malformed: marker order is invalid");
45
+ }
46
+ return existing.slice(start.start, end.end);
47
+ }
32
48
  function cleanUpOrphanedNewlines(content) {
33
49
  const trimmed = content.replace(/\n{3,}/gu, "\n\n").replace(/\n+$/u, "\n");
34
50
  return trimmed || "";
@@ -75,8 +91,12 @@ function trimTrailingNewlines(value) {
75
91
  return value.replace(/\n+$/u, "");
76
92
  }
77
93
  function findMarkerLine(content, marker) {
94
+ return findMarkerLines(content, marker)[0] ?? null;
95
+ }
96
+ function findMarkerLines(content, marker) {
78
97
  let offset = 0;
79
98
  let inFence = null;
99
+ const matches = [];
80
100
  for (const lineWithBreak of content.matchAll(/[^\n]*(?:\n|$)/gu)) {
81
101
  const rawLine = lineWithBreak[0];
82
102
  if (!rawLine) break;
@@ -90,20 +110,23 @@ function findMarkerLine(content, marker) {
90
110
  inFence = null;
91
111
  }
92
112
  } else if (!inFence && line === marker) {
93
- return {
113
+ matches.push({
94
114
  start: offset,
95
115
  end: offset + marker.length
96
- };
116
+ });
97
117
  }
98
118
  offset += rawLine.length;
99
119
  }
100
- return null;
120
+ return matches;
101
121
  }
102
122
 
103
123
  // src/installers/v3-adapter.ts
104
124
  var V3_ADAPTER_VERSION = "3";
105
- var V3_ADAPTER_MANAGED_MARKER = "<!-- Managed by mancode:v3-adapter. Do not edit this marker. -->";
106
- var V3_MODE_ENTRY_MANAGED_MARKER = "<!-- Managed by mancode:v3-mode-entry. Do not edit this marker. -->";
125
+ var V3_ADAPTER_MANAGED_MARKER = "<!-- Managed by mancode:continuity-adapter. Do not edit this marker. -->";
126
+ var V3_MODE_ENTRY_MANAGED_MARKER = "<!-- Managed by mancode:continuity-mode-entry. Do not edit this marker. -->";
127
+ var LEGACY_V3_ADAPTER_MANAGED_MARKER = "<!-- Managed by mancode:v3-adapter. Do not edit this marker. -->";
128
+ var LEGACY_V3_MODE_ENTRY_MANAGED_MARKER = "<!-- Managed by mancode:v3-mode-entry. Do not edit this marker. -->";
129
+ var V3_ADAPTER_DIGEST_DOMAIN = "mancode-adapter-digest-v1";
107
130
  var V3_MODE_NAMES = [
108
131
  "manba",
109
132
  "man",
@@ -112,6 +135,7 @@ var V3_MODE_NAMES = [
112
135
  "mansolo"
113
136
  ];
114
137
  var LEGACY_MODE_ENTRY_MANAGED_MARKERS = [
138
+ LEGACY_V3_MODE_ENTRY_MANAGED_MARKER,
115
139
  "<!-- Managed by mancode:claude-skill. Do not edit this marker. -->",
116
140
  "<!-- Managed by mancode:codex-skill. Do not edit this file manually. -->",
117
141
  "<!-- Managed by mancode:zcode-skill. Do not edit this file manually. -->",
@@ -134,12 +158,26 @@ var LEGACY_CLAUDE_SETTINGS_RAW_HINTS = [
134
158
  ".mancode/hooks/user-prompt-submit.",
135
159
  ...LEGACY_CLAUDE_SKILL_PATHS
136
160
  ];
137
- var V3_CODEX_START_MARKER = "<!-- mancode:v3:codex:start -->";
138
- var V3_CODEX_END_MARKER = "<!-- mancode:v3:codex:end -->";
139
- var V3_ZCODE_START_MARKER = "<!-- mancode:v3:zcode:start -->";
140
- var V3_ZCODE_END_MARKER = "<!-- mancode:v3:zcode:end -->";
141
- var V3_COPILOT_START_MARKER = "<!-- mancode:v3:copilot:start -->";
142
- var V3_COPILOT_END_MARKER = "<!-- mancode:v3:copilot:end -->";
161
+ var CONTINUITY_CLAUDE_START_MARKER = "<!-- mancode:continuity:claude:start -->";
162
+ var CONTINUITY_CLAUDE_END_MARKER = "<!-- mancode:continuity:claude:end -->";
163
+ var V3_CODEX_START_MARKER = "<!-- mancode:continuity:codex:start -->";
164
+ var V3_CODEX_END_MARKER = "<!-- mancode:continuity:codex:end -->";
165
+ var V3_ZCODE_START_MARKER = "<!-- mancode:continuity:zcode:start -->";
166
+ var V3_ZCODE_END_MARKER = "<!-- mancode:continuity:zcode:end -->";
167
+ var V3_COPILOT_START_MARKER = "<!-- mancode:continuity:copilot:start -->";
168
+ var V3_COPILOT_END_MARKER = "<!-- mancode:continuity:copilot:end -->";
169
+ var LEGACY_V3_CODEX_MARKERS = [
170
+ "<!-- mancode:v3:codex:start -->",
171
+ "<!-- mancode:v3:codex:end -->"
172
+ ];
173
+ var LEGACY_V3_ZCODE_MARKERS = [
174
+ "<!-- mancode:v3:zcode:start -->",
175
+ "<!-- mancode:v3:zcode:end -->"
176
+ ];
177
+ var LEGACY_V3_COPILOT_MARKERS = [
178
+ "<!-- mancode:v3:copilot:start -->",
179
+ "<!-- mancode:v3:copilot:end -->"
180
+ ];
143
181
  var LEGACY_CODEX_START_MARKER = "<!-- mancode:start -->";
144
182
  var LEGACY_CODEX_END_MARKER = "<!-- mancode:end -->";
145
183
  var LEGACY_ZCODE_START_MARKER = "<!-- mancode:zcode:start -->";
@@ -147,6 +185,13 @@ var LEGACY_ZCODE_END_MARKER = "<!-- mancode:zcode:end -->";
147
185
  var RETRIABLE_ADAPTER_READ_CODES = /* @__PURE__ */ new Set(["EACCES", "EBUSY", "EPERM"]);
148
186
  var ADAPTER_READ_MAX_ATTEMPTS = 4;
149
187
  var ADAPTER_READ_RETRY_DELAY_MS = 25;
188
+ var V3_ADAPTER_PLATFORMS = [
189
+ "claude-code",
190
+ "codex",
191
+ "cursor",
192
+ "copilot",
193
+ "zcode"
194
+ ];
150
195
  var V3_MODE_ENTRY_FILE_TARGETS = V3_MODE_NAMES.flatMap((mode) => [
151
196
  `claude-mode-${mode}`,
152
197
  `agents-mode-${mode}`,
@@ -180,13 +225,27 @@ var V3_ADAPTER_FILE_TARGETS = [
180
225
  ...V3_MODE_ENTRY_FILE_TARGETS,
181
226
  ...V3_LEGACY_ADAPTER_FILE_TARGETS
182
227
  ];
228
+ function adapterManagedContentDigest(targetIdentity, managedContent) {
229
+ if (!targetIdentity.trim() || targetIdentity.includes("\0")) {
230
+ throw new Error("MANCODE_V3_ADAPTER_TARGET_IDENTITY_INVALID");
231
+ }
232
+ const content = typeof managedContent === "string" ? Buffer.from(managedContent, "utf8") : Buffer.from(managedContent);
233
+ const digest = createHash("sha256").update(Buffer.from(V3_ADAPTER_DIGEST_DOMAIN, "utf8")).update(Buffer.from([0])).update(Buffer.from(targetIdentity, "utf8")).update(Buffer.from([0])).update(content).digest("hex");
234
+ return `sha256:${digest}`;
235
+ }
183
236
  async function planV3AdapterFiles(projectRoot) {
184
237
  const root = path.resolve(projectRoot);
185
238
  const existing = /* @__PURE__ */ new Map();
186
239
  for (const target of V3_ADAPTER_FILE_TARGETS) {
187
240
  existing.set(target, await readAdapterTarget(root, target));
188
241
  }
189
- const agents = removeLegacyAgentsBlocks(existing.get("agents") ?? "");
242
+ const agents = removeLegacyV3Block(
243
+ removeLegacyV3Block(
244
+ removeLegacyAgentsBlocks(existing.get("agents") ?? ""),
245
+ LEGACY_V3_CODEX_MARKERS
246
+ ),
247
+ LEGACY_V3_ZCODE_MARKERS
248
+ );
190
249
  const nextAgents = replaceManagedV3BlockText(
191
250
  replaceManagedV3BlockText(
192
251
  agents,
@@ -200,11 +259,16 @@ async function planV3AdapterFiles(projectRoot) {
200
259
  );
201
260
  const legacyAdapterPlans = planLegacyAdapterRetirement(existing);
202
261
  const plans = [
203
- managedFilePlan(
204
- "claude-skill",
205
- existing.get("claude-skill") ?? null,
206
- renderClaudeSkill(renderV3Bootstrap("claude-code"))
207
- ),
262
+ {
263
+ target: "claude-skill",
264
+ beforeContent: existing.get("claude-skill") ?? null,
265
+ targetContent: replaceManagedV3BlockText(
266
+ existing.get("claude-skill") ?? "",
267
+ CONTINUITY_CLAUDE_START_MARKER,
268
+ CONTINUITY_CLAUDE_END_MARKER,
269
+ renderV3Bootstrap("claude-code")
270
+ )
271
+ },
208
272
  managedFilePlan(
209
273
  "cursor-rule",
210
274
  existing.get("cursor-rule") ?? null,
@@ -219,7 +283,10 @@ async function planV3AdapterFiles(projectRoot) {
219
283
  target: "copilot-instructions",
220
284
  beforeContent: existing.get("copilot-instructions") ?? null,
221
285
  targetContent: replaceManagedV3BlockText(
222
- removeManagedBlock(existing.get("copilot-instructions") ?? ""),
286
+ removeLegacyV3Block(
287
+ removeManagedBlock(existing.get("copilot-instructions") ?? ""),
288
+ LEGACY_V3_COPILOT_MARKERS
289
+ ),
223
290
  V3_COPILOT_START_MARKER,
224
291
  V3_COPILOT_END_MARKER,
225
292
  renderV3Bootstrap("copilot")
@@ -236,6 +303,73 @@ async function planV3AdapterFiles(projectRoot) {
236
303
  ];
237
304
  return plans;
238
305
  }
306
+ async function planV3AdapterUpgradeFiles(projectRoot, platforms) {
307
+ const root = path.resolve(projectRoot);
308
+ const selected = normalizeUpgradePlatforms(platforms);
309
+ const targetSet = /* @__PURE__ */ new Set();
310
+ for (const platform of selected) {
311
+ targetSet.add(primaryFileTarget(platform));
312
+ for (const mode of V3_MODE_NAMES) {
313
+ targetSet.add(modeEntryFileTarget(platform, mode));
314
+ }
315
+ for (const target of legacyAdapterTargetsForPlatform(platform, true)) {
316
+ targetSet.add(target);
317
+ }
318
+ }
319
+ const existing = /* @__PURE__ */ new Map();
320
+ for (const target of targetSet) {
321
+ existing.set(target, await readAdapterTarget(root, target));
322
+ }
323
+ const desired = new Map(existing);
324
+ for (const platform of selected) {
325
+ planPlatformBootstrapUpgrade(desired, platform);
326
+ for (const mode of V3_MODE_NAMES) {
327
+ const target = modeEntryFileTarget(platform, mode);
328
+ const current = desired.get(target) ?? null;
329
+ desired.set(
330
+ target,
331
+ managedModeEntryPlan(target, current, renderV3ModeEntry(mode, platform)).targetContent
332
+ );
333
+ }
334
+ }
335
+ const plans = [...desired.entries()].filter(([target, targetContent]) => existing.get(target) !== targetContent).map(([target, targetContent]) => ({
336
+ target,
337
+ beforeContent: existing.get(target) ?? null,
338
+ targetContent: targetContent ?? ""
339
+ }));
340
+ const legacyPlans = planLegacyAdapterRetirement(existing).filter(
341
+ (legacyPlan) => !plans.some((candidate) => candidate.target === legacyPlan.target)
342
+ );
343
+ return [...plans, ...legacyPlans];
344
+ }
345
+ async function stageV3AdapterUpgradeFiles(projectRoot, operationId, plans) {
346
+ if (!/^[0-7][0-9A-HJKMNP-TV-Z]{25}$/.test(operationId)) {
347
+ throw new Error("MANCODE_ADAPTER_UPGRADE_OPERATION_ID_INVALID");
348
+ }
349
+ const root = path.resolve(projectRoot);
350
+ const staged = [];
351
+ for (const plan of plans) {
352
+ const liveTarget = v3AdapterTargetPath(root, plan.target);
353
+ const relative = relativeAdapterPath(root, liveTarget);
354
+ const stagingTarget = path.join(
355
+ ".mancode",
356
+ "staging",
357
+ "adapters",
358
+ "upgrade",
359
+ operationId,
360
+ relative
361
+ );
362
+ const destination = path.join(root, stagingTarget);
363
+ await assertAdapterPathSafe(root, destination);
364
+ await mkdir(path.dirname(destination), { recursive: true });
365
+ await atomicWrite(destination, plan.targetContent);
366
+ staged.push({
367
+ target: plan.target,
368
+ stagingTarget: relativeAdapterPath(root, destination)
369
+ });
370
+ }
371
+ return staged;
372
+ }
239
373
  async function applyV3AdapterFilePlan(projectRoot, plan) {
240
374
  const root = path.resolve(projectRoot);
241
375
  if (!V3_ADAPTER_FILE_TARGETS.includes(plan.target)) {
@@ -246,8 +380,30 @@ async function applyV3AdapterFilePlan(projectRoot, plan) {
246
380
  }
247
381
  const target = v3AdapterTargetPath(root, plan.target);
248
382
  await assertAdapterPathSafe(root, target);
383
+ const retiredBootstrapPlatform = retiredBootstrapPlatformFor(plan.target);
384
+ if (retiredBootstrapPlatform !== null) {
385
+ for (const retired of retiredBootstrapSpecs(
386
+ root,
387
+ retiredBootstrapPlatform
388
+ )) {
389
+ await assertAdapterPathSafe(root, retired.filePath);
390
+ }
391
+ }
392
+ const current = await readAdapterTarget(root, plan.target);
393
+ if (current === plan.targetContent) {
394
+ if (retiredBootstrapPlatform !== null) {
395
+ await removeRetiredBootstrapFiles(root, retiredBootstrapPlatform);
396
+ }
397
+ return;
398
+ }
399
+ if (current !== plan.beforeContent) {
400
+ throw new Error("MANCODE_V3_ADAPTER_TARGET_CONFLICT");
401
+ }
249
402
  await mkdir(path.dirname(target), { recursive: true });
250
403
  await atomicWrite(target, plan.targetContent);
404
+ if (retiredBootstrapPlatform !== null) {
405
+ await removeRetiredBootstrapFiles(root, retiredBootstrapPlatform);
406
+ }
251
407
  }
252
408
  async function stageV3Adapter(projectRoot, platform) {
253
409
  const root = path.resolve(projectRoot);
@@ -258,7 +414,7 @@ async function stageV3Adapter(projectRoot, platform) {
258
414
  ".mancode",
259
415
  "staging",
260
416
  "adapters",
261
- "v3",
417
+ "continuity",
262
418
  platform,
263
419
  target
264
420
  );
@@ -276,7 +432,7 @@ async function stageV3Adapter(projectRoot, platform) {
276
432
  ".mancode",
277
433
  "staging",
278
434
  "adapters",
279
- "v3",
435
+ "continuity",
280
436
  platform,
281
437
  modeTarget
282
438
  );
@@ -302,9 +458,9 @@ function v3AdapterTargetPath(projectRoot, target) {
302
458
  if (legacyTarget !== null) return path.join(root, legacyTarget);
303
459
  switch (target) {
304
460
  case "claude-skill":
305
- return path.join(root, ".claude", "skills", "mancode-v3", "SKILL.md");
461
+ return path.join(root, "CLAUDE.md");
306
462
  case "cursor-rule":
307
- return path.join(root, ".cursor", "rules", "mancode-v3.mdc");
463
+ return path.join(root, ".cursor", "rules", "mancode-continuity.mdc");
308
464
  case "agents":
309
465
  return path.join(root, "AGENTS.md");
310
466
  case "copilot-instructions":
@@ -336,16 +492,20 @@ async function installV3Adapter(projectRoot, platform) {
336
492
  const content = renderV3Bootstrap(platform);
337
493
  switch (platform) {
338
494
  case "claude-code":
339
- await writeManagedFile(
340
- path.join(root, ".claude", "skills", "mancode-v3", "SKILL.md"),
341
- renderClaudeSkill(content)
495
+ await replaceManagedV3Block(
496
+ path.join(root, "CLAUDE.md"),
497
+ CONTINUITY_CLAUDE_START_MARKER,
498
+ CONTINUITY_CLAUDE_END_MARKER,
499
+ content
342
500
  );
501
+ await removeRetiredBootstrapFiles(root, platform);
343
502
  break;
344
503
  case "cursor":
345
504
  await writeManagedFile(
346
- path.join(root, ".cursor", "rules", "mancode-v3.mdc"),
505
+ path.join(root, ".cursor", "rules", "mancode-continuity.mdc"),
347
506
  renderCursorRule(content)
348
507
  );
508
+ await removeRetiredBootstrapFiles(root, platform);
349
509
  break;
350
510
  case "codex":
351
511
  await replaceManagedV3Block(
@@ -354,6 +514,7 @@ async function installV3Adapter(projectRoot, platform) {
354
514
  V3_CODEX_END_MARKER,
355
515
  content,
356
516
  [
517
+ LEGACY_V3_CODEX_MARKERS,
357
518
  [LEGACY_CODEX_START_MARKER, LEGACY_CODEX_END_MARKER],
358
519
  [LEGACY_ZCODE_START_MARKER, LEGACY_ZCODE_END_MARKER]
359
520
  ]
@@ -365,7 +526,10 @@ async function installV3Adapter(projectRoot, platform) {
365
526
  V3_COPILOT_START_MARKER,
366
527
  V3_COPILOT_END_MARKER,
367
528
  content,
368
- [[LEGACY_CODEX_START_MARKER, LEGACY_CODEX_END_MARKER]]
529
+ [
530
+ LEGACY_V3_COPILOT_MARKERS,
531
+ [LEGACY_CODEX_START_MARKER, LEGACY_CODEX_END_MARKER]
532
+ ]
369
533
  );
370
534
  break;
371
535
  case "zcode":
@@ -375,6 +539,7 @@ async function installV3Adapter(projectRoot, platform) {
375
539
  V3_ZCODE_END_MARKER,
376
540
  content,
377
541
  [
542
+ LEGACY_V3_ZCODE_MARKERS,
378
543
  [LEGACY_CODEX_START_MARKER, LEGACY_CODEX_END_MARKER],
379
544
  [LEGACY_ZCODE_START_MARKER, LEGACY_ZCODE_END_MARKER]
380
545
  ]
@@ -403,25 +568,26 @@ async function assertV3AdapterInstallable(projectRoot, platform) {
403
568
  }
404
569
  async function inspectV3Adapter(projectRoot, platform) {
405
570
  const root = path.resolve(projectRoot);
406
- await assertPlatformAdapterPathsSafe(root, platform);
407
571
  const target = targetFor(platform);
408
- const bootstrapInstalled = await adapterTargetPresent(root, platform);
409
- const modeEntriesInstalled = (await Promise.all(
410
- V3_MODE_NAMES.map(
411
- (mode) => v3ModeEntryPresent(v3ModeEntryPath(root, platform, mode))
412
- )
413
- )).every(Boolean);
414
- const installed = bootstrapInstalled && modeEntriesInstalled;
572
+ const targets = [];
573
+ for (const spec of managedTargetSpecs(root, platform)) {
574
+ targets.push(await inspectManagedTarget(root, platform, spec));
575
+ }
576
+ const ready = targets.every((item) => item.status === "ready");
577
+ const installed = targets.every((item) => item.status !== "missing");
578
+ const status = aggregateAdapterStatus(targets);
415
579
  return {
416
580
  version: V3_ADAPTER_VERSION,
417
581
  installed,
418
- ready: installed,
582
+ ready,
583
+ status,
419
584
  target,
420
- detail: installed ? "mancode bootstrap and original mode entries are present; session identity is explicit-required." : "mancode bootstrap or one of its original mode entries is not installed.",
585
+ detail: status === "ready" ? "Managed adapter content matches the current renderer." : status === "missing" ? "One or more managed adapter targets are missing." : status === "stale" ? "Managed adapter content differs from the current renderer." : "One or more managed adapter targets cannot be read safely.",
586
+ targets,
421
587
  capabilities: capabilitiesFor(platform)
422
588
  };
423
589
  }
424
- async function inspectV3AdapterVersions(projectRoot) {
590
+ async function inspectV3AdapterVersions(projectRoot, requiredPlatforms = []) {
425
591
  const platforms = [
426
592
  "claude-code",
427
593
  "codex",
@@ -429,13 +595,25 @@ async function inspectV3AdapterVersions(projectRoot) {
429
595
  "copilot",
430
596
  "zcode"
431
597
  ];
598
+ const required = new Set(requiredPlatforms);
432
599
  const entries = await Promise.all(
433
- platforms.map(async (platform) => {
434
- const status = await inspectV3Adapter(projectRoot, platform);
435
- return [platform, status.ready ? status.version : "missing"];
436
- })
600
+ platforms.map(
601
+ async (platform) => [platform, await inspectV3Adapter(projectRoot, platform)]
602
+ )
603
+ );
604
+ return v3AdapterVersionsFromStatuses(entries, required);
605
+ }
606
+ function v3AdapterVersionsFromStatuses(entries, requiredPlatforms = []) {
607
+ const required = requiredPlatforms instanceof Set ? requiredPlatforms : new Set(requiredPlatforms);
608
+ const versions = entries.map(([platform, status]) => {
609
+ const primaryPresent = status.targets[0]?.status !== "missing";
610
+ return required.has(platform) || primaryPresent ? [platform, status.ready ? status.version : status.status] : null;
611
+ });
612
+ return Object.fromEntries(
613
+ versions.filter(
614
+ (entry) => entry !== null
615
+ )
437
616
  );
438
- return Object.fromEntries(entries);
439
617
  }
440
618
  async function removeV3Adapter(projectRoot, platform) {
441
619
  const root = path.resolve(projectRoot);
@@ -443,14 +621,18 @@ async function removeV3Adapter(projectRoot, platform) {
443
621
  let preserveSharedModeEntries = false;
444
622
  switch (platform) {
445
623
  case "claude-code":
446
- await removeManagedFile(
447
- path.join(root, ".claude", "skills", "mancode-v3", "SKILL.md")
624
+ await removeManagedV3Block(
625
+ path.join(root, "CLAUDE.md"),
626
+ CONTINUITY_CLAUDE_START_MARKER,
627
+ CONTINUITY_CLAUDE_END_MARKER
448
628
  );
629
+ await removeRetiredBootstrapFiles(root, platform);
449
630
  break;
450
631
  case "cursor":
451
632
  await removeManagedFile(
452
- path.join(root, ".cursor", "rules", "mancode-v3.mdc")
633
+ path.join(root, ".cursor", "rules", "mancode-continuity.mdc")
453
634
  );
635
+ await removeRetiredBootstrapFiles(root, platform);
454
636
  break;
455
637
  case "codex":
456
638
  await removeManagedV3Block(
@@ -458,10 +640,13 @@ async function removeV3Adapter(projectRoot, platform) {
458
640
  V3_CODEX_START_MARKER,
459
641
  V3_CODEX_END_MARKER
460
642
  );
461
- preserveSharedModeEntries = await managedBlockPresent(
643
+ await removeManagedV3Block(
644
+ path.join(root, "AGENTS.md"),
645
+ ...LEGACY_V3_CODEX_MARKERS
646
+ );
647
+ preserveSharedModeEntries = await anyManagedBlockPresent(
462
648
  path.join(root, "AGENTS.md"),
463
- V3_ZCODE_START_MARKER,
464
- V3_ZCODE_END_MARKER
649
+ [[V3_ZCODE_START_MARKER, V3_ZCODE_END_MARKER], LEGACY_V3_ZCODE_MARKERS]
465
650
  );
466
651
  break;
467
652
  case "copilot":
@@ -470,6 +655,10 @@ async function removeV3Adapter(projectRoot, platform) {
470
655
  V3_COPILOT_START_MARKER,
471
656
  V3_COPILOT_END_MARKER
472
657
  );
658
+ await removeManagedV3Block(
659
+ path.join(root, ".github", "copilot-instructions.md"),
660
+ ...LEGACY_V3_COPILOT_MARKERS
661
+ );
473
662
  break;
474
663
  case "zcode":
475
664
  await removeManagedV3Block(
@@ -477,10 +666,13 @@ async function removeV3Adapter(projectRoot, platform) {
477
666
  V3_ZCODE_START_MARKER,
478
667
  V3_ZCODE_END_MARKER
479
668
  );
480
- preserveSharedModeEntries = await managedBlockPresent(
669
+ await removeManagedV3Block(
481
670
  path.join(root, "AGENTS.md"),
482
- V3_CODEX_START_MARKER,
483
- V3_CODEX_END_MARKER
671
+ ...LEGACY_V3_ZCODE_MARKERS
672
+ );
673
+ preserveSharedModeEntries = await anyManagedBlockPresent(
674
+ path.join(root, "AGENTS.md"),
675
+ [[V3_CODEX_START_MARKER, V3_CODEX_END_MARKER], LEGACY_V3_CODEX_MARKERS]
484
676
  );
485
677
  break;
486
678
  }
@@ -504,16 +696,25 @@ function renderV3Bootstrap(platform) {
504
696
  "",
505
697
  `- Platform: ${platformLabel}. This file is a non-authoritative bootstrap.`,
506
698
  "- Locate the project root before running mancode commands.",
699
+ "- Before the first command, choose one CLI binary for the entire task: use `./node_modules/.bin/mancode` when it exists, otherwise use `mancode`. Run that selected binary with `--version` once and never mix binaries or versions.",
700
+ "- In every command below, `mancode` means that selected binary; when the local binary exists, invoke the command as `./node_modules/.bin/mancode ...` rather than falling back to a global executable.",
507
701
  "- Reuse a `mancode status --brief --json` snapshot already obtained in this conversation. Only when no such snapshot exists, run it once from the project root.",
702
+ "- Inspect a session read-only with `mancode context session show --session <id> --client <client> --json`; do not invent other session subcommands.",
508
703
  "- The compact status is the public mancode Continuity runtime view. In operator-facing narration, say `mancode` or `mancode Continuity`; never prefix a mode or action with a version label.",
509
704
  "- An explicitly invoked original `man`, `manba`, `manteam`, `manps`, or `mansolo` entry supplies its authorized action. Its mode-specific steps override conflicting generic no-task or mutation guidance below.",
510
705
  "- In particular, `manps` may run local health scans without an actor, session, or TaskRef. `mansolo` needs them only for an explicit governed handoff.",
511
- '- If status has no `identity.actorId`, ask for a display name and run `mancode team identity create --name "<display name>"` before creating a session.',
706
+ "- Outside an explicitly invoked mode entry, treat an ordinary requested coding task as default Solo work. Ordinary Solo work requires no actor identity, session, TaskRef, or workflow; do not ask for a display name or create Continuity authority for it.",
707
+ "- Before editing in default Solo, inspect only the relevant project facts, implementation, tests, and contracts. A supplied instruction is not automatically sound: verify its factual assumptions and proposed solution against the repository and the operator's goal.",
708
+ "- If the goal and decision-changing requirements are clear, consistent with project evidence, and low risk, proceed with the narrowest useful change without ceremonial questions. Resolve repository-answerable unknowns yourself.",
709
+ "- When the goal is clear but requirements are incomplete, classify each remaining unknown as blocking, recommendable, or defaultable. Ask and wait only for blocking decisions that can materially change behavior, scope, acceptance, architecture, data, security, compatibility, or semantic ownership. For recommendable decisions, give bounded options and a clear recommendation. Use a default only when it is low-impact, reversible, consistent with repository conventions, and stated explicitly.",
710
+ "- If an explicit request conflicts with repository evidence or introduces a hard-risk change involving authentication, payment, sensitive data, deletion, migration, public APIs, untrusted input, concurrency, infrastructure, or another irreversible effect, stop before editing. Show the concrete conflict or impact, recommend the safer path, ask a focused confirmation or choice, and wait. Clarity never overrides safety or the operator's actual goal.",
711
+ "- A natural-language request explicitly asking for research, a plan, architecture, migration design, or formal acceptance authorizes the `man` planning path without a separate mode-confirmation question. For an ordinary implementation request whose blocking decision crosses modules or requires architecture, migration, semantic owner/source-of-truth, team coordination, or formal acceptance, recommend `/man`, explain why, and wait; never switch authority silently.",
712
+ '- For governed task work only, if status has no `identity.actorId`, ask for a display name and run `mancode team identity create --name "<display name>"` before creating a session.',
512
713
  "- If status reports `session`, reuse it. `task: null` and `MANCODE_TASK_REQUIRED` do not make a session stale.",
513
714
  `- ${sessionCreationGuidance} Pass its returned \`sessionId\` and matching client as \`${sessionArguments}\` to every later session command; an \`export\` inside one command tool does not persist to later command tools.`,
514
- '- Outside an invoked original mode entry, if no current task and no task is explicitly supplied, report "no task bound" and stop. Do not probe workflow subcommands to work around `MANCODE_TASK_REQUIRED`.',
715
+ '- Outside an invoked original mode entry, if no coding, planning, diagnostic, or review task was requested and no TaskRef is explicitly supplied, report "no task bound" and stop. Do not probe workflow subcommands to work around `MANCODE_TASK_REQUIRED`.',
515
716
  "- Bootstrap discovery is read-only: before the operator explicitly requests task work, do not run `mancode init`, `mancode migrate`, `mancode workflow`, or inspect mancode installed package/source.",
516
- `- With an existing or supplied task, read its Context Pack with \`mancode context show --purpose orient ${sessionArguments}\`; for anonymous diagnosis, include an explicit \`--task <namespace:id>\`.`,
717
+ `- With an existing or explicitly supplied TaskRef, read its Context Pack with \`mancode context show --purpose orient ${sessionArguments}\`; for anonymous diagnosis, include an explicit \`--task <namespace:id>\`. A plain-language Solo request is not a TaskRef and needs no Context Pack.`,
517
718
  "- After an operator explicitly requests task work, perform mutations only through `mancode workflow`, `mancode team`, and `mancode context` commands with their required revision and session arguments.",
518
719
  "- For a mode entry, request the matching Context Pack purpose: `plan`, `implement`, `review`, `verify`, or `handoff`.",
519
720
  "- Do not persist task, mode, or session state in this adapter file or any legacy state file.",
@@ -526,6 +727,7 @@ function renderV3ModeEntry(mode, platform) {
526
727
  const sessionArguments = sessionArgumentsFor(platform);
527
728
  const sessionClientGuidance = sessionClientGuidanceFor(platform);
528
729
  const statusGuidance = "Reuse a `mancode status --brief --json` snapshot already obtained in this conversation. Only when none exists, run it once from the project root.";
730
+ const cliSelectionGuidance = "Before the first command, use `./node_modules/.bin/mancode` when it exists, otherwise `mancode`; check that selected binary with `--version` once and never mix binaries or versions. In every command below, replace the literal `mancode` with that selected binary path when the local binary exists.";
529
731
  const sessionCreationGuidance = platform === "codex" || platform === "zcode" ? "If status has no current session, reuse an explicit session ID already retained in this conversation. Only if neither exists, run `mancode context session new --client codex` in Codex or `mancode context session new --client zcode` in ZCode exactly once, then retain the returned session ID." : `If status has no current session, reuse an explicit session ID already retained in this conversation. Only if neither exists, run \`mancode context session new --client ${platform}\` exactly once and retain the returned session ID.`;
530
732
  let authoritySteps;
531
733
  if (mode === "manps") {
@@ -581,6 +783,7 @@ function renderV3ModeEntry(mode, platform) {
581
783
  "",
582
784
  "## Enter through mancode",
583
785
  "",
786
+ cliSelectionGuidance,
584
787
  ...authoritySteps,
585
788
  "",
586
789
  "## Mode action",
@@ -608,9 +811,22 @@ var V3_MODE_DEFINITIONS = {
608
811
  actions: [
609
812
  "- For a read-only project orientation, inspect and answer directly; do not create governance records.",
610
813
  '- For a new task, run `mancode workflow create man "<task>" --session <id>`.',
611
- "- Write requirements as semantic JSON with `version: 1`, `goal`, `confirmedScope`, `excludedScope`, `technicalDecisions`, `defaults`, `blockingUnknowns`, all seven `coverage` dimensions, and `acceptanceCriteria`; mancode generates canonical internal IDs and digests.",
814
+ "- Before writing requirements, inspect the relevant project facts and implementation, then run a decision-readiness gate covering both clarity and soundness. Treat the request as ready only when the goal, in-scope/out-of-scope behavior, acceptance boundary, semantic owner/source of truth, and decision-changing constraints are supplied and consistent with evidence, verifiable from the repository, or explicitly recorded as safe defaults. A supplied instruction is not automatically correct. Do not ask ceremonial questions or manufacture alternatives when the request is already clear and sound.",
815
+ "- Classify unresolved decisions as blocking, recommendable, or defaultable. Ask and wait for blocking decisions; for recommendable decisions, present 2\u20133 bounded options with tradeoffs and one clear recommendation; use a default only when it is low-impact, reversible, consistent with repository conventions, and recorded with its reason.",
816
+ "- If any unresolved ambiguity could change the goal, scope, user-visible behavior, acceptance, architecture, data, security, compatibility, owner, or source of truth, stop before requirements finalization, explain the missing decision, ask focused questions, and wait for the user answer. Ask in as many batches as needed, do not repeat answered questions, and never turn an unverified assumption into confirmed scope or confirmed coverage.",
817
+ "- Before waiting on a blocking answer, persist the known facts, partial decisions, and each open question with `mancode workflow requirements <namespace:ULID> draft --file <requirements.json> --expected-revision <n> --session <id>`. A draft may leave scope, coverage, technical decisions, or acceptance incomplete only while `blockingUnknowns` names the open decisions. After every answer, update the draft or finalize it so another session can resume the exact clarification state.",
818
+ "- If an explicit direction conflicts with repository evidence or creates a hard-risk authentication, payment, sensitive-data, deletion, migration, public-API, untrusted-input, concurrency, infrastructure, or irreversible change, stop before requirements finalization. Show the evidence and impact, recommend a safer path, ask for a focused confirmation or choice, and wait; clarity does not waive risk.",
819
+ "- After the user answers, summarize the resolved requirements and any remaining defaults. Continue only when no decision-changing blocking unknown remains; otherwise keep the task in clarification and ask again.",
820
+ "- Write requirements as semantic JSON with `version: 1`, a non-empty `goal`, non-empty `confirmedScope`, and the arrays `excludedScope`, `technicalDecisions`, `defaults`, and `blockingUnknowns`. Every array item must be a non-empty string; an array may be empty except `confirmedScope`, and `technicalDecisions` must be non-empty whenever `technical_stack` applies.",
821
+ '- `coverage` must contain exactly one item for each dimension: `platform`, `core_scope`, `technical_stack`, `data_and_persistence`, `performance`, `compatibility`, and `security`. Each item has the shape `{ "dimension": "platform", "status": "confirmed", "rationale": "..." }`; `status` is exactly `confirmed`, `defaulted`, or `not_applicable`, and `rationale` is non-empty.',
822
+ '- `acceptanceCriteria` must contain at least one required item shaped as `{ "id": "AC-1", "description": "...", "required": true, "method": "automated" }`; `method` is exactly `automated`, `manual`, or `hybrid`.',
612
823
  "- Finalize requirements with `mancode workflow requirements <namespace:ULID> finalize --file <requirements.json> --expected-revision <n> --session <id>`.",
613
- "- Revise or confirm the plan with `mancode workflow plan <namespace:ULID> revise|confirm --expected-revision <n> ... --session <id>`.",
824
+ "- Let mancode assign internal IDs and digests; do not invent canonical IDs or digests in the semantic input.",
825
+ "- Revise the plan with `mancode workflow plan <namespace:ULID> revise --expected-revision <n> --file <plan.md> --session <id>`.",
826
+ "- Confirm the current plan with `mancode workflow plan <namespace:ULID> confirm --expected-revision <n> --plan-decision <plan_only|governed_execution> --session <id>`.",
827
+ "- Confirming with `--plan-decision plan_only` keeps the plan as planned authority and clears this session's active workflow pointer. Resume the TaskRef explicitly before any later governed mutation.",
828
+ '- When new evidence materially invalidates confirmed requirements and the operator explicitly chooses to realign the same local task, resume its TaskRef if needed, generate a fresh canonical checkpoint ULID, and run `mancode workflow reframe <namespace:ULID> --expected-revision <n> --checkpoint-id <fresh-ULID> --summary "<reason>" --next-action "<step-2 action>" --session <id>`. Reframe archives the confirmed requirements and plan, clears the plan decision, and stops at Step 2 with draft requirements. Do not substitute plan revise, scope-change, or workflow update for reframe.',
829
+ "- Read reframe evidence without opening private authority files: `mancode workflow archive <namespace:ULID> show <archive-ULID> --json` and `mancode workflow checkpoint <namespace:ULID> show <checkpoint-ULID> --json`.",
614
830
  "- Apply verification and review ledgers with their mancode `apply --file` commands, then use `mancode workflow complete <namespace:ULID> --expected-revision <n> --session <id>`."
615
831
  ]
616
832
  },
@@ -620,6 +836,8 @@ var V3_MODE_DEFINITIONS = {
620
836
  contextPurpose: "implement",
621
837
  actions: [
622
838
  '- For a new diagnostic task, run `mancode workflow create manba "<task>" --session <id>`.',
839
+ "- Before changing code, establish the expected behavior from reproducible evidence, tests, documentation, history, or the current semantic owner. If the bug goal is clear but the correct behavior cannot be established, ask one focused question and wait instead of inventing product behavior.",
840
+ "- If the requested fix conflicts with repository evidence or crosses a hard-risk boundary, show the conflict and obtain a focused confirmation or route the decision through `/man`; do not treat an explicit but unsound fix instruction as sufficient evidence.",
623
841
  "- When this is a child investigation, add `--parent <namespace:ULID>`; report and merge the typed outcome through the mancode child commands.",
624
842
  "- Change lifecycle only with `mancode workflow update <namespace:ULID> --status <status> --expected-revision <n> --session <id>` and finish with `workflow complete` plus the typed `--outcome`."
625
843
  ]
@@ -631,6 +849,8 @@ var V3_MODE_DEFINITIONS = {
631
849
  actions: [
632
850
  "- Confirm team membership with `mancode team status`; join invited participants before assigning shared work.",
633
851
  '- For a new shared task, run `mancode workflow create manteam "<task>" --visibility shared --coordination team --confirm-shared --session <id>`.',
852
+ "- Apply the same decision-readiness gate as `man` before finalizing requirements: validate both clarity and soundness against project facts and team authority. If the goal, scope, acceptance, owner/source of truth, and constraints are clear and consistent, continue without ceremonial questions; if a decision-changing ambiguity, ownership conflict, or hard-risk direction remains, give evidence and a recommendation, ask focused questions, and wait before writing confirmed requirements.",
853
+ "- Persist unresolved team clarification through the same `workflow requirements <namespace:ULID> draft --file <requirements.json>` command as `man`; do not leave ownership questions or partial answers only in chat history.",
634
854
  "- Use claims, checkpoints, sync, and handoffs through `mancode team`; never infer ownership from an adapter prompt.",
635
855
  "- Use the same mancode requirements, plan, verification, review, and completion commands as `man`, adding `--sync` whenever the active transport requires it."
636
856
  ]
@@ -650,6 +870,9 @@ var V3_MODE_DEFINITIONS = {
650
870
  contextPurpose: "implement",
651
871
  actions: [
652
872
  "- Do not create or persist a legacy solo mode. Ordinary focused work needs no TaskRef; if the operator expects a governed task, use its bound TaskRef or report that none is bound.",
873
+ "- Before editing, assess both clarity and soundness using the project facts. If the request is clear, consistent, and low risk, proceed without ceremonial questions. Resolve repository-answerable unknowns yourself; classify the rest as blocking, recommendable, or defaultable. Ask and wait only when a blocking unknown could materially change behavior, scope, acceptance, data, security, compatibility, or ownership.",
874
+ "- A supplied implementation direction is not automatically safe. If it conflicts with repository evidence or involves authentication, payment, sensitive data, deletion, migration, public APIs, untrusted input, concurrency, infrastructure, or another irreversible effect, show the evidence and impact, recommend the safer path, ask for focused confirmation, and wait before editing.",
875
+ "- If resolving the ambiguity requires architecture, semantic owner/source-of-truth, cross-module scope, migration, team coordination, or formal acceptance decisions, recommend `/man`, explain the trigger, and wait for the operator to choose; advice alone never changes mode or authority.",
653
876
  "- For a governed-to-solo transition, use `mancode workflow handoff <namespace:ULID> --to solo --expected-revision <n> --session <id>`."
654
877
  ]
655
878
  }
@@ -658,11 +881,12 @@ async function renderV3AdapterCandidate(root, platform) {
658
881
  switch (platform) {
659
882
  case "claude-code": {
660
883
  const existing = await readAdapterTarget(root, "claude-skill");
661
- return managedFilePlan(
662
- "claude-skill",
663
- existing,
664
- renderClaudeSkill(renderV3Bootstrap(platform))
665
- ).targetContent;
884
+ return replaceManagedV3BlockText(
885
+ existing ?? "",
886
+ CONTINUITY_CLAUDE_START_MARKER,
887
+ CONTINUITY_CLAUDE_END_MARKER,
888
+ renderV3Bootstrap(platform)
889
+ );
666
890
  }
667
891
  case "cursor": {
668
892
  const existing = await readAdapterTarget(root, "cursor-rule");
@@ -675,7 +899,10 @@ async function renderV3AdapterCandidate(root, platform) {
675
899
  case "codex": {
676
900
  const existing = await readAdapterTarget(root, "agents") ?? "";
677
901
  return replaceManagedV3BlockText(
678
- removeLegacyAgentsBlocks(existing),
902
+ removeLegacyV3Block(
903
+ removeLegacyAgentsBlocks(existing),
904
+ LEGACY_V3_CODEX_MARKERS
905
+ ),
679
906
  V3_CODEX_START_MARKER,
680
907
  V3_CODEX_END_MARKER,
681
908
  renderV3Bootstrap(platform)
@@ -684,7 +911,10 @@ async function renderV3AdapterCandidate(root, platform) {
684
911
  case "copilot": {
685
912
  const existing = await readAdapterTarget(root, "copilot-instructions") ?? "";
686
913
  return replaceManagedV3BlockText(
687
- removeManagedBlock(existing),
914
+ removeLegacyV3Block(
915
+ removeManagedBlock(existing),
916
+ LEGACY_V3_COPILOT_MARKERS
917
+ ),
688
918
  V3_COPILOT_START_MARKER,
689
919
  V3_COPILOT_END_MARKER,
690
920
  renderV3Bootstrap(platform)
@@ -693,7 +923,10 @@ async function renderV3AdapterCandidate(root, platform) {
693
923
  case "zcode": {
694
924
  const existing = await readAdapterTarget(root, "agents") ?? "";
695
925
  return replaceManagedV3BlockText(
696
- removeLegacyAgentsBlocks(existing),
926
+ removeLegacyV3Block(
927
+ removeLegacyAgentsBlocks(existing),
928
+ LEGACY_V3_ZCODE_MARKERS
929
+ ),
697
930
  V3_ZCODE_START_MARKER,
698
931
  V3_ZCODE_END_MARKER,
699
932
  renderV3Bootstrap(platform)
@@ -701,18 +934,6 @@ async function renderV3AdapterCandidate(root, platform) {
701
934
  }
702
935
  }
703
936
  }
704
- function renderClaudeSkill(content) {
705
- return [
706
- "---",
707
- "name: mancode-v3",
708
- 'description: "Internal bootstrap for the original mancode mode entries."',
709
- "user-invocable: false",
710
- "---",
711
- "",
712
- content,
713
- ""
714
- ].join("\n");
715
- }
716
937
  function renderCursorRule(content) {
717
938
  return [
718
939
  "---",
@@ -725,39 +946,283 @@ function renderCursorRule(content) {
725
946
  ""
726
947
  ].join("\n");
727
948
  }
728
- async function adapterTargetPresent(root, platform) {
949
+ function managedTargetSpecs(root, platform) {
950
+ const primary = primaryManagedTargetSpec(platform);
951
+ const modes = V3_MODE_NAMES.map((mode) => {
952
+ const fileTarget = modeEntryFileTarget(platform, mode);
953
+ return {
954
+ identity: fileTarget,
955
+ target: relativeAdapterPath(root, v3ModeEntryPath(root, platform, mode)),
956
+ fileTarget,
957
+ expectedManagedContent: renderV3ModeEntry(mode, platform),
958
+ blockMarkers: null
959
+ };
960
+ });
961
+ return [primary, ...modes];
962
+ }
963
+ function normalizeUpgradePlatforms(platforms) {
964
+ if (!Array.isArray(platforms) || platforms.length === 0) {
965
+ throw new Error("MANCODE_ADAPTER_UPGRADE_PLATFORM_REQUIRED");
966
+ }
967
+ const selected = /* @__PURE__ */ new Set();
968
+ for (const platform of platforms) {
969
+ if (!V3_ADAPTER_PLATFORMS.includes(platform)) {
970
+ throw new Error("MANCODE_ADAPTER_UPGRADE_PLATFORM_INVALID");
971
+ }
972
+ selected.add(platform);
973
+ }
974
+ return V3_ADAPTER_PLATFORMS.filter((platform) => selected.has(platform));
975
+ }
976
+ function primaryFileTarget(platform) {
977
+ switch (platform) {
978
+ case "claude-code":
979
+ return "claude-skill";
980
+ case "cursor":
981
+ return "cursor-rule";
982
+ case "codex":
983
+ case "zcode":
984
+ return "agents";
985
+ case "copilot":
986
+ return "copilot-instructions";
987
+ }
988
+ }
989
+ function planPlatformBootstrapUpgrade(desired, platform) {
990
+ const target = primaryFileTarget(platform);
991
+ const current = desired.get(target) ?? null;
729
992
  switch (platform) {
730
993
  case "claude-code":
731
- return managedFilePresent(
732
- path.join(root, ".claude", "skills", "mancode-v3", "SKILL.md")
994
+ desired.set(
995
+ target,
996
+ replaceManagedV3BlockText(
997
+ current ?? "",
998
+ CONTINUITY_CLAUDE_START_MARKER,
999
+ CONTINUITY_CLAUDE_END_MARKER,
1000
+ renderV3Bootstrap(platform)
1001
+ )
733
1002
  );
1003
+ return;
734
1004
  case "cursor":
735
- return managedFilePresent(
736
- path.join(root, ".cursor", "rules", "mancode-v3.mdc")
1005
+ desired.set(
1006
+ target,
1007
+ managedFilePlan(
1008
+ target,
1009
+ current,
1010
+ renderCursorRule(renderV3Bootstrap(platform))
1011
+ ).targetContent
737
1012
  );
1013
+ return;
738
1014
  case "codex":
739
- return managedBlockPresent(
740
- path.join(root, "AGENTS.md"),
1015
+ desired.set(
1016
+ target,
1017
+ replaceManagedV3BlockText(
1018
+ removeLegacyV3Block(
1019
+ removeLegacyAgentsBlocks(current ?? ""),
1020
+ LEGACY_V3_CODEX_MARKERS
1021
+ ),
1022
+ V3_CODEX_START_MARKER,
1023
+ V3_CODEX_END_MARKER,
1024
+ renderV3Bootstrap(platform)
1025
+ )
1026
+ );
1027
+ return;
1028
+ case "zcode":
1029
+ desired.set(
1030
+ target,
1031
+ replaceManagedV3BlockText(
1032
+ removeLegacyV3Block(
1033
+ removeLegacyAgentsBlocks(current ?? ""),
1034
+ LEGACY_V3_ZCODE_MARKERS
1035
+ ),
1036
+ V3_ZCODE_START_MARKER,
1037
+ V3_ZCODE_END_MARKER,
1038
+ renderV3Bootstrap(platform)
1039
+ )
1040
+ );
1041
+ return;
1042
+ case "copilot":
1043
+ desired.set(
1044
+ target,
1045
+ replaceManagedV3BlockText(
1046
+ removeLegacyV3Block(
1047
+ removeManagedBlock(current ?? ""),
1048
+ LEGACY_V3_COPILOT_MARKERS
1049
+ ),
1050
+ V3_COPILOT_START_MARKER,
1051
+ V3_COPILOT_END_MARKER,
1052
+ renderV3Bootstrap(platform)
1053
+ )
1054
+ );
1055
+ }
1056
+ }
1057
+ function primaryManagedTargetSpec(platform) {
1058
+ const target = targetFor(platform);
1059
+ switch (platform) {
1060
+ case "claude-code":
1061
+ return embeddedManagedTargetSpec(
1062
+ target,
1063
+ "claude-instructions#continuity",
1064
+ "claude-skill",
1065
+ CONTINUITY_CLAUDE_START_MARKER,
1066
+ CONTINUITY_CLAUDE_END_MARKER,
1067
+ renderV3Bootstrap(platform)
1068
+ );
1069
+ case "cursor":
1070
+ return {
1071
+ identity: "cursor-rule",
1072
+ target,
1073
+ fileTarget: "cursor-rule",
1074
+ expectedManagedContent: renderCursorRule(renderV3Bootstrap(platform)),
1075
+ blockMarkers: null
1076
+ };
1077
+ case "codex":
1078
+ return embeddedManagedTargetSpec(
1079
+ target,
1080
+ "agents#codex",
1081
+ "agents",
741
1082
  V3_CODEX_START_MARKER,
742
- V3_CODEX_END_MARKER
1083
+ V3_CODEX_END_MARKER,
1084
+ renderV3Bootstrap(platform)
743
1085
  );
744
1086
  case "copilot":
745
- return managedBlockPresent(
746
- path.join(root, ".github", "copilot-instructions.md"),
1087
+ return embeddedManagedTargetSpec(
1088
+ target,
1089
+ "copilot-instructions#copilot",
1090
+ "copilot-instructions",
747
1091
  V3_COPILOT_START_MARKER,
748
- V3_COPILOT_END_MARKER
1092
+ V3_COPILOT_END_MARKER,
1093
+ renderV3Bootstrap(platform)
749
1094
  );
750
1095
  case "zcode":
751
- return managedBlockPresent(
752
- path.join(root, "AGENTS.md"),
1096
+ return embeddedManagedTargetSpec(
1097
+ target,
1098
+ "agents#zcode",
1099
+ "agents",
753
1100
  V3_ZCODE_START_MARKER,
754
- V3_ZCODE_END_MARKER
1101
+ V3_ZCODE_END_MARKER,
1102
+ renderV3Bootstrap(platform)
755
1103
  );
756
1104
  }
757
1105
  }
1106
+ function embeddedManagedTargetSpec(target, identity, fileTarget, startMarker, endMarker, content) {
1107
+ return {
1108
+ identity,
1109
+ target,
1110
+ fileTarget,
1111
+ expectedManagedContent: [startMarker, content, endMarker].join("\n"),
1112
+ blockMarkers: [startMarker, endMarker]
1113
+ };
1114
+ }
1115
+ function modeEntryFileTarget(platform, mode) {
1116
+ const family = platform === "claude-code" ? "claude" : platform === "codex" || platform === "zcode" ? "agents" : platform;
1117
+ return `${family}-mode-${mode}`;
1118
+ }
1119
+ async function inspectManagedTarget(root, platform, spec) {
1120
+ const expectedDigest = adapterManagedContentDigest(
1121
+ spec.identity,
1122
+ spec.expectedManagedContent
1123
+ );
1124
+ const repair = [
1125
+ `Preview with \`mancode adapter upgrade --platform ${platform} --dry-run\`.`,
1126
+ `Confirm that exact preview with \`mancode adapter upgrade --platform ${platform} --confirm --operation-id <operationId> --session <id>\`.`
1127
+ ].join(" ");
1128
+ try {
1129
+ const bytes = await readAdapterBytesIfExists(
1130
+ root,
1131
+ v3AdapterTargetPath(root, spec.fileTarget)
1132
+ );
1133
+ if (bytes === null) {
1134
+ return managedTargetStatus(
1135
+ spec,
1136
+ "missing",
1137
+ null,
1138
+ expectedDigest,
1139
+ repair,
1140
+ "Managed target does not exist."
1141
+ );
1142
+ }
1143
+ const content = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
1144
+ let managedContent;
1145
+ if (spec.blockMarkers === null) {
1146
+ managedContent = content;
1147
+ } else {
1148
+ try {
1149
+ const extracted = extractManagedBlock(
1150
+ content,
1151
+ spec.blockMarkers[0],
1152
+ spec.blockMarkers[1]
1153
+ );
1154
+ if (extracted === null) {
1155
+ return managedTargetStatus(
1156
+ spec,
1157
+ "missing",
1158
+ null,
1159
+ expectedDigest,
1160
+ repair,
1161
+ "Managed block is absent from the target file."
1162
+ );
1163
+ }
1164
+ managedContent = extracted;
1165
+ } catch (error) {
1166
+ return managedTargetStatus(
1167
+ spec,
1168
+ "stale",
1169
+ null,
1170
+ expectedDigest,
1171
+ repair,
1172
+ error instanceof Error ? error.message : "Managed block is malformed."
1173
+ );
1174
+ }
1175
+ }
1176
+ const actualDigest = adapterManagedContentDigest(
1177
+ spec.identity,
1178
+ managedContent
1179
+ );
1180
+ const status = actualDigest === expectedDigest ? "ready" : "stale";
1181
+ return managedTargetStatus(
1182
+ spec,
1183
+ status,
1184
+ actualDigest,
1185
+ expectedDigest,
1186
+ status === "ready" ? "none" : repair,
1187
+ status === "ready" ? "Managed content matches the current renderer." : "Managed content differs from the current renderer."
1188
+ );
1189
+ } catch (error) {
1190
+ return managedTargetStatus(
1191
+ spec,
1192
+ "unreadable",
1193
+ null,
1194
+ expectedDigest,
1195
+ `Resolve the filesystem or UTF-8 error, then ${repair}`,
1196
+ error instanceof Error ? error.message : "Managed target is unreadable."
1197
+ );
1198
+ }
1199
+ }
1200
+ function managedTargetStatus(spec, status, actualDigest, expectedDigest, repair, detail) {
1201
+ return {
1202
+ identity: spec.identity,
1203
+ target: spec.target,
1204
+ status,
1205
+ actualDigest,
1206
+ expectedDigest,
1207
+ rendererVersion: V3_ADAPTER_VERSION,
1208
+ repair,
1209
+ detail
1210
+ };
1211
+ }
1212
+ function aggregateAdapterStatus(targets) {
1213
+ if (targets.every((target) => target.status === "ready")) return "ready";
1214
+ if (targets.some((target) => target.status === "unreadable")) {
1215
+ return "unreadable";
1216
+ }
1217
+ if (targets.some((target) => target.status === "stale")) return "stale";
1218
+ return "missing";
1219
+ }
1220
+ function relativeAdapterPath(root, target) {
1221
+ return path.relative(root, target).split(path.sep).join("/");
1222
+ }
758
1223
  async function writeManagedFile(filePath, content) {
759
1224
  const existing = await readTextIfExists(filePath);
760
- if (existing !== null && !existing.includes(V3_ADAPTER_MANAGED_MARKER)) {
1225
+ if (existing !== null && !existing.includes(V3_ADAPTER_MANAGED_MARKER) && !existing.includes(LEGACY_V3_ADAPTER_MANAGED_MARKER)) {
761
1226
  throw new Error("MANCODE_V3_ADAPTER_TARGET_USER_AUTHORED");
762
1227
  }
763
1228
  await mkdir(path.dirname(filePath), { recursive: true });
@@ -788,15 +1253,13 @@ async function assertV3ModeEntriesWritable(root, platform) {
788
1253
  }
789
1254
  async function removeV3ModeEntry(filePath) {
790
1255
  const existing = await readTextIfExists(filePath);
791
- if (existing?.includes(V3_MODE_ENTRY_MANAGED_MARKER)) {
1256
+ if (existing?.includes(V3_MODE_ENTRY_MANAGED_MARKER) || LEGACY_MODE_ENTRY_MANAGED_MARKERS.some(
1257
+ (marker) => existing?.includes(marker)
1258
+ )) {
792
1259
  await rm(filePath, { force: true });
793
1260
  await removeDirectoryIfEmpty(path.dirname(filePath));
794
1261
  }
795
1262
  }
796
- async function v3ModeEntryPresent(filePath) {
797
- const existing = await readTextIfExists(filePath);
798
- return existing?.includes(V3_MODE_ENTRY_MANAGED_MARKER) ?? false;
799
- }
800
1263
  async function removeDirectoryIfEmpty(directory) {
801
1264
  try {
802
1265
  await rmdir(directory);
@@ -813,6 +1276,18 @@ async function removeManagedFile(filePath) {
813
1276
  await rm(filePath, { force: true });
814
1277
  }
815
1278
  }
1279
+ async function removeRetiredBootstrapFiles(root, platform) {
1280
+ if (platform !== "claude-code" && platform !== "cursor") return;
1281
+ for (const retired of retiredBootstrapSpecs(root, platform)) {
1282
+ await assertAdapterPathSafe(root, retired.filePath);
1283
+ const existing = await readTextIfExists(retired.filePath);
1284
+ if (!retired.managedMarkers.some((marker) => existing?.includes(marker))) {
1285
+ continue;
1286
+ }
1287
+ await rm(retired.filePath, { force: true });
1288
+ await removeDirectoryIfEmpty(path.dirname(retired.filePath));
1289
+ }
1290
+ }
816
1291
  async function replaceManagedV3Block(filePath, startMarker, endMarker, content, legacyMarkers) {
817
1292
  const current = await readTextIfExists(filePath) ?? "";
818
1293
  const existing = (legacyMarkers ?? []).reduce(
@@ -837,6 +1312,9 @@ function removeLegacyAgentsBlocks(existing) {
837
1312
  LEGACY_ZCODE_END_MARKER
838
1313
  );
839
1314
  }
1315
+ function removeLegacyV3Block(existing, markers) {
1316
+ return removeManagedBlock(existing, markers[0], markers[1]);
1317
+ }
840
1318
  function replaceManagedV3BlockText(existing, startMarker, endMarker, content) {
841
1319
  return replaceManagedBlock(
842
1320
  existing,
@@ -846,7 +1324,7 @@ function replaceManagedV3BlockText(existing, startMarker, endMarker, content) {
846
1324
  );
847
1325
  }
848
1326
  function managedFilePlan(target, beforeContent, targetContent) {
849
- if (beforeContent !== null && !beforeContent.includes(V3_ADAPTER_MANAGED_MARKER)) {
1327
+ if (beforeContent !== null && !beforeContent.includes(V3_ADAPTER_MANAGED_MARKER) && !beforeContent.includes(LEGACY_V3_ADAPTER_MANAGED_MARKER)) {
850
1328
  throw new Error("MANCODE_V3_ADAPTER_TARGET_USER_AUTHORED");
851
1329
  }
852
1330
  return { target, beforeContent, targetContent };
@@ -1175,6 +1653,11 @@ async function assertPlatformAdapterPathsSafe(root, platform) {
1175
1653
  (target) => v3AdapterTargetPath(root, target)
1176
1654
  )
1177
1655
  ]);
1656
+ if (platform === "claude-code" || platform === "cursor") {
1657
+ for (const retired of retiredBootstrapSpecs(root, platform)) {
1658
+ targets.add(retired.filePath);
1659
+ }
1660
+ }
1178
1661
  for (const target of targets) {
1179
1662
  await assertAdapterPathSafe(root, target);
1180
1663
  }
@@ -1216,13 +1699,35 @@ async function removeManagedV3Block(filePath, startMarker, endMarker) {
1216
1699
  await rm(filePath, { force: true });
1217
1700
  }
1218
1701
  }
1219
- async function managedFilePresent(filePath) {
1702
+ async function anyManagedBlockPresent(filePath, markerPairs) {
1220
1703
  const content = await readTextIfExists(filePath);
1221
- return content?.includes(V3_ADAPTER_MANAGED_MARKER) ?? false;
1704
+ return content !== null && markerPairs.some(
1705
+ ([startMarker, endMarker]) => hasManagedBlock(content, startMarker, endMarker)
1706
+ );
1222
1707
  }
1223
- async function managedBlockPresent(filePath, startMarker, endMarker) {
1224
- const content = await readTextIfExists(filePath);
1225
- return content !== null && hasManagedBlock(content, startMarker, endMarker);
1708
+ async function readAdapterBytesIfExists(root, filePath) {
1709
+ await assertAdapterPathSafe(root, filePath);
1710
+ try {
1711
+ const entry = await lstat(filePath);
1712
+ if (!entry.isFile() || entry.isSymbolicLink()) {
1713
+ throw new Error("MANCODE_ARTIFACT_PATH_UNSAFE");
1714
+ }
1715
+ } catch (error) {
1716
+ if (isNodeError(error) && error.code === "ENOENT") return null;
1717
+ throw error;
1718
+ }
1719
+ for (let attempt = 1; attempt <= ADAPTER_READ_MAX_ATTEMPTS; attempt += 1) {
1720
+ try {
1721
+ return await readFile(filePath);
1722
+ } catch (error) {
1723
+ if (isNodeError(error) && error.code === "ENOENT") return null;
1724
+ if (!isRetriableAdapterReadError(error) || attempt === ADAPTER_READ_MAX_ATTEMPTS) {
1725
+ throw error;
1726
+ }
1727
+ await delay(ADAPTER_READ_RETRY_DELAY_MS * attempt);
1728
+ }
1729
+ }
1730
+ throw new Error("MANCODE_V3_ADAPTER_READ_RETRY_EXHAUSTED");
1226
1731
  }
1227
1732
  async function readTextIfExists(filePath) {
1228
1733
  for (let attempt = 1; attempt <= ADAPTER_READ_MAX_ATTEMPTS; attempt += 1) {
@@ -1253,9 +1758,9 @@ async function atomicWrite(filePath, content) {
1253
1758
  function targetFor(platform) {
1254
1759
  switch (platform) {
1255
1760
  case "claude-code":
1256
- return ".claude/skills/mancode-v3/SKILL.md";
1761
+ return "CLAUDE.md";
1257
1762
  case "cursor":
1258
- return ".cursor/rules/mancode-v3.mdc";
1763
+ return ".cursor/rules/mancode-continuity.mdc";
1259
1764
  case "codex":
1260
1765
  case "zcode":
1261
1766
  return "AGENTS.md";
@@ -1263,6 +1768,47 @@ function targetFor(platform) {
1263
1768
  return ".github/copilot-instructions.md";
1264
1769
  }
1265
1770
  }
1771
+ function retiredBootstrapPlatformFor(target) {
1772
+ if (target === "claude-skill") return "claude-code";
1773
+ if (target === "cursor-rule") return "cursor";
1774
+ return null;
1775
+ }
1776
+ function retiredBootstrapSpecs(root, platform) {
1777
+ const managedMarkers = [
1778
+ LEGACY_V3_ADAPTER_MANAGED_MARKER,
1779
+ V3_ADAPTER_MANAGED_MARKER
1780
+ ];
1781
+ if (platform === "claude-code") {
1782
+ return [
1783
+ {
1784
+ filePath: path.join(
1785
+ root,
1786
+ ".claude",
1787
+ "skills",
1788
+ "mancode-v3",
1789
+ "SKILL.md"
1790
+ ),
1791
+ managedMarkers
1792
+ },
1793
+ {
1794
+ filePath: path.join(
1795
+ root,
1796
+ ".claude",
1797
+ "skills",
1798
+ "mancode-continuity",
1799
+ "SKILL.md"
1800
+ ),
1801
+ managedMarkers
1802
+ }
1803
+ ];
1804
+ }
1805
+ return [
1806
+ {
1807
+ filePath: path.join(root, ".cursor", "rules", "mancode-v3.mdc"),
1808
+ managedMarkers
1809
+ }
1810
+ ];
1811
+ }
1266
1812
  function platformLabelFor(platform) {
1267
1813
  switch (platform) {
1268
1814
  case "claude-code":
@@ -1306,9 +1852,14 @@ export {
1306
1852
  V3_ADAPTER_VERSION,
1307
1853
  V3_ADAPTER_MANAGED_MARKER,
1308
1854
  V3_MODE_ENTRY_MANAGED_MARKER,
1855
+ V3_ADAPTER_DIGEST_DOMAIN,
1309
1856
  V3_MODE_NAMES,
1857
+ V3_ADAPTER_PLATFORMS,
1310
1858
  V3_ADAPTER_FILE_TARGETS,
1859
+ adapterManagedContentDigest,
1311
1860
  planV3AdapterFiles,
1861
+ planV3AdapterUpgradeFiles,
1862
+ stageV3AdapterUpgradeFiles,
1312
1863
  applyV3AdapterFilePlan,
1313
1864
  stageV3Adapter,
1314
1865
  v3AdapterTargetPath,
@@ -1318,8 +1869,9 @@ export {
1318
1869
  assertV3AdapterInstallable,
1319
1870
  inspectV3Adapter,
1320
1871
  inspectV3AdapterVersions,
1872
+ v3AdapterVersionsFromStatuses,
1321
1873
  removeV3Adapter,
1322
1874
  renderV3Bootstrap,
1323
1875
  renderV3ModeEntry
1324
1876
  };
1325
- //# sourceMappingURL=chunk-QIOMFU35.js.map
1877
+ //# sourceMappingURL=chunk-K3MYY47M.js.map