@lousy-agents/cli 5.15.7 → 5.17.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.
package/dist/index.js CHANGED
@@ -2990,7 +2990,7 @@ exports.basename = (path, { windows } = {}) => {
2990
2990
 
2991
2991
 
2992
2992
  },
2993
- 455(__unused_rspack_module, __unused_rspack___webpack_exports__, __webpack_require__) {
2993
+ 889(__unused_rspack_module, __unused_rspack___webpack_exports__, __webpack_require__) {
2994
2994
  // NAMESPACE OBJECT: ../../node_modules/micromark/lib/constructs.js
2995
2995
  var constructs_namespaceObject = {};
2996
2996
  __webpack_require__.r(constructs_namespaceObject);
@@ -3063,16 +3063,24 @@ function createBoundedReadStream(opened, maxBytes) {
3063
3063
  }
3064
3064
 
3065
3065
  ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/file-identity.js
3066
+
3066
3067
  function isZero(value) {
3067
3068
  return value === 0 || value === 0n;
3068
3069
  }
3070
+ function sameStatValue(left, right) {
3071
+ return typeof left === typeof right ? left === right : BigInt(left) === BigInt(right);
3072
+ }
3073
+ function sha256Hex(data, encoding) {
3074
+ const buffer = typeof data === "string" ? Buffer.from(data, encoding ?? "utf8") : data;
3075
+ return (0,external_node_crypto_.createHash)("sha256").update(buffer).digest("hex");
3076
+ }
3069
3077
  function file_identity_sameFileIdentity(left, right, platform = process.platform) {
3070
- if (left.ino !== right.ino) {
3078
+ if (!sameStatValue(left.ino, right.ino)) {
3071
3079
  return false;
3072
3080
  }
3073
3081
  // On Windows, path-based stat calls can report dev=0 while fd-based stat
3074
3082
  // reports a real volume serial; treat either-side dev=0 as "unknown device".
3075
- if (left.dev === right.dev) {
3083
+ if (sameStatValue(left.dev, right.dev)) {
3076
3084
  return true;
3077
3085
  }
3078
3086
  return platform === "win32" && (isZero(left.dev) || isZero(right.dev));
@@ -4466,6 +4474,377 @@ async function runPinnedPathHelper(params) {
4466
4474
  }
4467
4475
  }
4468
4476
 
4477
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock.js
4478
+
4479
+
4480
+
4481
+
4482
+ const GLOBAL_STATE_KEY = Symbol.for("fsSafe.sidecarLockManagers");
4483
+ function getGlobalManagers() {
4484
+ const globalWithState = globalThis;
4485
+ if (!globalWithState[GLOBAL_STATE_KEY]) {
4486
+ globalWithState[GLOBAL_STATE_KEY] = new Map();
4487
+ }
4488
+ return globalWithState[GLOBAL_STATE_KEY];
4489
+ }
4490
+ function resolveManagerState(key) {
4491
+ const managers = getGlobalManagers();
4492
+ let state = managers.get(key);
4493
+ if (!state) {
4494
+ state = { cleanupRegistered: false, held: new Map() };
4495
+ managers.set(key, state);
4496
+ }
4497
+ return state;
4498
+ }
4499
+ async function readLockSnapshot(lockPath) {
4500
+ try {
4501
+ const stat = await promises_.lstat(lockPath);
4502
+ const raw = await promises_.readFile(lockPath, "utf8");
4503
+ try {
4504
+ const parsed = JSON.parse(raw);
4505
+ const payload = parsed && typeof parsed === "object" && !Array.isArray(parsed)
4506
+ ? parsed
4507
+ : null;
4508
+ return { raw, payload, stat };
4509
+ }
4510
+ catch {
4511
+ return { raw, payload: null, stat };
4512
+ }
4513
+ }
4514
+ catch (err) {
4515
+ if (err.code === "ENOENT") {
4516
+ return null;
4517
+ }
4518
+ throw err;
4519
+ }
4520
+ }
4521
+ function snapshotMatches(current, observed) {
4522
+ if (observed.stat && current.stat && !file_identity_sameFileIdentity(observed.stat, current.stat)) {
4523
+ return false;
4524
+ }
4525
+ if (observed.raw !== undefined) {
4526
+ return current.raw === observed.raw;
4527
+ }
4528
+ return observed.stat !== undefined && current.stat !== undefined;
4529
+ }
4530
+ async function removeLockIfUnchanged(lockPath, observed) {
4531
+ const current = await readLockSnapshot(lockPath);
4532
+ if (!current || !observed) {
4533
+ return false;
4534
+ }
4535
+ if (!snapshotMatches(current, observed)) {
4536
+ // The lock changed after we decided it was stale. Leave the fresh holder's
4537
+ // file alone; deleting by path here would break mutual exclusion.
4538
+ return false;
4539
+ }
4540
+ await promises_.rm(lockPath, { force: true }).catch(() => undefined);
4541
+ return true;
4542
+ }
4543
+ async function lockSnapshotStillPresent(lockPath, observed) {
4544
+ const current = await readLockSnapshot(lockPath);
4545
+ return !!current && !!observed && snapshotMatches(current, observed);
4546
+ }
4547
+ async function removeStaleLockIfAllowed(params) {
4548
+ if (!params.shouldRemoveStaleLock) {
4549
+ return "not-approved";
4550
+ }
4551
+ if (params.snapshot.raw === undefined) {
4552
+ return "not-approved";
4553
+ }
4554
+ if (!(await params.shouldRemoveStaleLock({
4555
+ lockPath: params.lockPath,
4556
+ normalizedTargetPath: params.normalizedTargetPath,
4557
+ raw: params.snapshot.raw,
4558
+ payload: params.snapshot.payload,
4559
+ }))) {
4560
+ return "not-approved";
4561
+ }
4562
+ const current = await readLockSnapshot(params.lockPath);
4563
+ if (!current || !snapshotMatches(current, params.snapshot)) {
4564
+ return "changed";
4565
+ }
4566
+ try {
4567
+ await promises_.rm(params.lockPath, { force: true });
4568
+ }
4569
+ catch (err) {
4570
+ if (err.code === "ENOENT") {
4571
+ return "changed";
4572
+ }
4573
+ return "not-approved";
4574
+ }
4575
+ return "removed";
4576
+ }
4577
+ function snapshotMatchesSync(lockPath, observed) {
4578
+ try {
4579
+ const stat = external_node_fs_.lstatSync(lockPath);
4580
+ if (observed.stat && !file_identity_sameFileIdentity(observed.stat, stat)) {
4581
+ return false;
4582
+ }
4583
+ return observed.raw === undefined || external_node_fs_.readFileSync(lockPath, "utf8") === observed.raw;
4584
+ }
4585
+ catch {
4586
+ return false;
4587
+ }
4588
+ }
4589
+ async function resolveNormalizedTargetPath(targetPath) {
4590
+ const resolved = external_node_path_.resolve(targetPath);
4591
+ const dir = external_node_path_.dirname(resolved);
4592
+ await promises_.mkdir(dir, { recursive: true });
4593
+ try {
4594
+ return external_node_path_.join(await promises_.realpath(dir), external_node_path_.basename(resolved));
4595
+ }
4596
+ catch {
4597
+ return resolved;
4598
+ }
4599
+ }
4600
+ function computeDelayMs(retry, attempt) {
4601
+ const minTimeout = retry.minTimeout ?? 50;
4602
+ const maxTimeout = retry.maxTimeout ?? 1000;
4603
+ const factor = retry.factor ?? 1;
4604
+ const base = Math.min(maxTimeout, Math.max(minTimeout, minTimeout * factor ** attempt));
4605
+ const jitter = retry.randomize ? 1 + Math.random() : 1;
4606
+ return Math.min(maxTimeout, Math.round(base * jitter));
4607
+ }
4608
+ async function defaultShouldReclaim(params) {
4609
+ const createdAt = typeof params.payload?.createdAt === "string" ? params.payload.createdAt : "";
4610
+ const createdAtMs = Date.parse(createdAt);
4611
+ if (Number.isFinite(createdAtMs) && params.nowMs - createdAtMs > params.staleMs) {
4612
+ return true;
4613
+ }
4614
+ try {
4615
+ const stat = await promises_.stat(params.lockPath);
4616
+ return params.nowMs - stat.mtimeMs > params.staleMs;
4617
+ }
4618
+ catch {
4619
+ return true;
4620
+ }
4621
+ }
4622
+ function releaseAllLocksSync(state) {
4623
+ for (const [normalizedTargetPath, held] of state.held) {
4624
+ void held.handle.close().catch(() => undefined);
4625
+ try {
4626
+ if (snapshotMatchesSync(held.lockPath, held.snapshot)) {
4627
+ external_node_fs_.rmSync(held.lockPath, { force: true });
4628
+ }
4629
+ }
4630
+ catch {
4631
+ // Best-effort process-exit cleanup.
4632
+ }
4633
+ state.held.delete(normalizedTargetPath);
4634
+ }
4635
+ }
4636
+ async function releaseHeldLock(state, normalizedTargetPath, held, opts = {}) {
4637
+ const current = state.held.get(normalizedTargetPath);
4638
+ if (current !== held) {
4639
+ return false;
4640
+ }
4641
+ if (opts.force) {
4642
+ held.count = 0;
4643
+ }
4644
+ else {
4645
+ held.count -= 1;
4646
+ if (held.count > 0) {
4647
+ return false;
4648
+ }
4649
+ }
4650
+ if (held.releasePromise) {
4651
+ await held.releasePromise.catch(() => undefined);
4652
+ return true;
4653
+ }
4654
+ state.held.delete(normalizedTargetPath);
4655
+ held.releasePromise = (async () => {
4656
+ await held.handle.close().catch(() => undefined);
4657
+ await removeLockIfUnchanged(held.lockPath, held.snapshot);
4658
+ })();
4659
+ try {
4660
+ await held.releasePromise;
4661
+ return true;
4662
+ }
4663
+ finally {
4664
+ held.releasePromise = undefined;
4665
+ }
4666
+ }
4667
+ function createSidecarLockManager(key) {
4668
+ const state = resolveManagerState(key);
4669
+ function ensureExitCleanupRegistered() {
4670
+ if (state.cleanupRegistered) {
4671
+ return;
4672
+ }
4673
+ state.cleanupRegistered = true;
4674
+ process.on("exit", () => releaseAllLocksSync(state));
4675
+ }
4676
+ async function acquire(options) {
4677
+ ensureExitCleanupRegistered();
4678
+ const normalizedTargetPath = await resolveNormalizedTargetPath(options.targetPath);
4679
+ const lockPath = options.lockPath ?? `${normalizedTargetPath}.lock`;
4680
+ const held = state.held.get(normalizedTargetPath);
4681
+ if (held && options.allowReentrant) {
4682
+ held.count += 1;
4683
+ const release = () => releaseHeldLock(state, normalizedTargetPath, held).then(() => undefined);
4684
+ return {
4685
+ lockPath,
4686
+ normalizedTargetPath,
4687
+ release,
4688
+ [Symbol.asyncDispose]: release,
4689
+ };
4690
+ }
4691
+ const startedAt = Date.now();
4692
+ const retry = options.retry ?? {};
4693
+ const maxRetries = options.timeoutMs === Number.POSITIVE_INFINITY ? undefined : retry.retries;
4694
+ let attempt = 0;
4695
+ while (true) {
4696
+ let handle = null;
4697
+ try {
4698
+ handle = await promises_.open(lockPath, "wx");
4699
+ const payload = await options.payload();
4700
+ const raw = `${JSON.stringify(payload, null, 2)}\n`;
4701
+ await handle.writeFile(raw, "utf8");
4702
+ const snapshot = { raw, payload, stat: await handle.stat() };
4703
+ const createdHeld = {
4704
+ count: 1,
4705
+ handle,
4706
+ lockPath,
4707
+ snapshot,
4708
+ acquiredAt: Date.now(),
4709
+ metadata: options.metadata ?? {},
4710
+ };
4711
+ state.held.set(normalizedTargetPath, createdHeld);
4712
+ const release = () => releaseHeldLock(state, normalizedTargetPath, createdHeld).then(() => undefined);
4713
+ return {
4714
+ lockPath,
4715
+ normalizedTargetPath,
4716
+ release,
4717
+ [Symbol.asyncDispose]: release,
4718
+ };
4719
+ }
4720
+ catch (err) {
4721
+ if (handle) {
4722
+ const failedSnapshot = { payload: null };
4723
+ try {
4724
+ failedSnapshot.stat = await handle.stat();
4725
+ }
4726
+ catch {
4727
+ // Best-effort cleanup of a failed exclusive create.
4728
+ }
4729
+ const current = state.held.get(normalizedTargetPath);
4730
+ if (current?.handle === handle) {
4731
+ state.held.delete(normalizedTargetPath);
4732
+ }
4733
+ // If payload serialization/write fails, the file may be empty or
4734
+ // partial JSON, so remove while our exclusive handle is still open.
4735
+ await promises_.rm(lockPath, { force: true }).catch(() => undefined);
4736
+ await handle.close().catch(() => undefined);
4737
+ // Windows can refuse removing an open file; retry after close but
4738
+ // only if the path still points at the file identity we created.
4739
+ await removeLockIfUnchanged(lockPath, failedSnapshot);
4740
+ }
4741
+ if (err.code !== "EEXIST") {
4742
+ throw err;
4743
+ }
4744
+ const nowMs = Date.now();
4745
+ const snapshot = await readLockSnapshot(lockPath);
4746
+ if (!snapshot) {
4747
+ continue;
4748
+ }
4749
+ const shouldReclaim = options.shouldReclaim ?? defaultShouldReclaim;
4750
+ if (await shouldReclaim({
4751
+ lockPath,
4752
+ normalizedTargetPath,
4753
+ payload: snapshot?.payload ?? null,
4754
+ staleMs: options.staleMs,
4755
+ nowMs,
4756
+ heldByThisProcess: state.held.has(normalizedTargetPath),
4757
+ })) {
4758
+ if (!(await lockSnapshotStillPresent(lockPath, snapshot))) {
4759
+ continue;
4760
+ }
4761
+ const staleRecovery = options.staleRecovery ?? "fail-closed";
4762
+ if (staleRecovery === "remove-if-unchanged") {
4763
+ const removal = await removeStaleLockIfAllowed({
4764
+ lockPath,
4765
+ normalizedTargetPath,
4766
+ snapshot,
4767
+ shouldRemoveStaleLock: options.shouldRemoveStaleLock,
4768
+ });
4769
+ if (removal === "removed" || removal === "changed") {
4770
+ continue;
4771
+ }
4772
+ }
4773
+ throw Object.assign(new Error(`file lock stale for ${normalizedTargetPath}`), {
4774
+ code: "file_lock_stale",
4775
+ lockPath,
4776
+ normalizedTargetPath,
4777
+ });
4778
+ }
4779
+ const elapsed = Date.now() - startedAt;
4780
+ if ((options.timeoutMs !== undefined &&
4781
+ options.timeoutMs !== Number.POSITIVE_INFINITY &&
4782
+ elapsed >= options.timeoutMs) ||
4783
+ (maxRetries !== undefined && attempt >= maxRetries)) {
4784
+ throw Object.assign(new Error(`file lock timeout for ${normalizedTargetPath}`), {
4785
+ code: "file_lock_timeout",
4786
+ lockPath,
4787
+ normalizedTargetPath,
4788
+ });
4789
+ }
4790
+ const remaining = options.timeoutMs === undefined || options.timeoutMs === Number.POSITIVE_INFINITY
4791
+ ? Number.POSITIVE_INFINITY
4792
+ : Math.max(0, options.timeoutMs - elapsed);
4793
+ const delay = Math.min(computeDelayMs(retry, attempt), remaining);
4794
+ attempt += 1;
4795
+ await new Promise((resolve) => setTimeout(resolve, delay));
4796
+ }
4797
+ }
4798
+ }
4799
+ async function withLock(options, fn) {
4800
+ const lock = await acquire(options);
4801
+ try {
4802
+ return await fn();
4803
+ }
4804
+ finally {
4805
+ await lock.release();
4806
+ }
4807
+ }
4808
+ async function drain() {
4809
+ for (const [normalizedTargetPath, held] of Array.from(state.held.entries())) {
4810
+ await releaseHeldLock(state, normalizedTargetPath, held, { force: true }).catch(() => undefined);
4811
+ }
4812
+ }
4813
+ function reset() {
4814
+ releaseAllLocksSync(state);
4815
+ }
4816
+ function heldEntries() {
4817
+ return Array.from(state.held.entries()).map(([normalizedTargetPath, held]) => ({
4818
+ normalizedTargetPath,
4819
+ lockPath: held.lockPath,
4820
+ acquiredAt: held.acquiredAt,
4821
+ metadata: held.metadata,
4822
+ forceRelease: () => releaseHeldLock(state, normalizedTargetPath, held, { force: true }),
4823
+ }));
4824
+ }
4825
+ return { acquire, withLock, drain, reset, heldEntries };
4826
+ }
4827
+ async function withSidecarLock(targetPath, options, fn) {
4828
+ const manager = createSidecarLockManager(options.managerKey ?? `fs-safe.sidecar-lock:${targetPath}`);
4829
+ const { managerKey: _managerKey, ...acquireOptions } = options;
4830
+ return await manager.withLock({ ...acquireOptions, targetPath }, fn);
4831
+ }
4832
+
4833
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/test-hooks.js
4834
+ let test_hooks_fsSafeTestHooks;
4835
+ function allowFsSafeTestHooks() {
4836
+ return false || process.env.VITEST === "true";
4837
+ }
4838
+ function getFsSafeTestHooks() {
4839
+ return test_hooks_fsSafeTestHooks;
4840
+ }
4841
+ function __setFsSafeTestHooksForTest(hooks) {
4842
+ if (hooks && !allowFsSafeTestHooks()) {
4843
+ throw new Error("__setFsSafeTestHooksForTest is only available in tests");
4844
+ }
4845
+ test_hooks_fsSafeTestHooks = hooks;
4846
+ }
4847
+
4469
4848
  ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/pinned-write.js
4470
4849
 
4471
4850
 
@@ -4479,6 +4858,8 @@ async function runPinnedPathHelper(params) {
4479
4858
 
4480
4859
 
4481
4860
 
4861
+
4862
+
4482
4863
  function pinned_write_byteLength(input, encoding) {
4483
4864
  return typeof input === "string"
4484
4865
  ? Buffer.byteLength(input, encoding ?? "utf8")
@@ -4536,6 +4917,12 @@ async function runPinnedWriteHelper(params) {
4536
4917
  validatePinnedOperationPayload({
4537
4918
  relativeParentPath: params.relativeParentPath,
4538
4919
  });
4920
+ // The Python helper deliberately enforces the strict post-rename inode
4921
+ // contract. The explicit compatibility policy therefore uses the guarded
4922
+ // Node fallback, where content verification can replace that one check.
4923
+ if (params.onRenameIdentityMismatch === "verify-content") {
4924
+ return await runPinnedWriteFallback(params);
4925
+ }
4539
4926
  if (getFsSafePythonConfig().mode === "off") {
4540
4927
  return await runPinnedWriteFallback(params);
4541
4928
  }
@@ -4550,8 +4937,11 @@ async function runPinnedWriteHelper(params) {
4550
4937
  throw error;
4551
4938
  }
4552
4939
  }
4940
+ const input = params.input.kind === "stream"
4941
+ ? { kind: "buffer", data: Buffer.from(await inputToBase64(params.input, params.maxBytes), "base64") }
4942
+ : params.input;
4553
4943
  const payload = {
4554
- base64: await inputToBase64(params.input, params.maxBytes),
4944
+ base64: await inputToBase64(input, params.maxBytes),
4555
4945
  basename: params.basename,
4556
4946
  maxBytes: params.maxBytes ?? -1,
4557
4947
  mkdir: params.mkdir,
@@ -4569,11 +4959,32 @@ async function runPinnedWriteHelper(params) {
4569
4959
  }
4570
4960
  catch (error) {
4571
4961
  if (canFallbackFromPythonError(error)) {
4572
- return await runPinnedWriteFallback(params);
4962
+ return await runPinnedWriteFallback({ ...params, input });
4573
4963
  }
4574
4964
  throw error;
4575
4965
  }
4576
4966
  }
4967
+ async function runPinnedWriteWithRenamePolicy(params) {
4968
+ const { targetPath, renameIdentity, ...writeParams } = params;
4969
+ if (renameIdentity !== "verify-content-with-lock") {
4970
+ return await runPinnedWriteHelper(writeParams);
4971
+ }
4972
+ const relativeTargetPath = writeParams.relativeParentPath
4973
+ ? `${writeParams.relativeParentPath}/${writeParams.basename}`
4974
+ : writeParams.basename;
4975
+ const lockPath = external_node_path_.join(writeParams.rootPath, `.fs-safe-write-${sha256Hex(relativeTargetPath)}.lock`);
4976
+ return await withSidecarLock(writeParams.rootPath, {
4977
+ managerKey: `fs-safe.write:${targetPath}`,
4978
+ lockPath,
4979
+ staleMs: 30_000,
4980
+ timeoutMs: 5_000,
4981
+ payload: () => ({ pid: process.pid, createdAt: new Date().toISOString() }),
4982
+ retry: { retries: 5, minTimeout: 100, maxTimeout: 2_000, factor: 2 },
4983
+ }, async () => await runPinnedWriteHelper({
4984
+ ...writeParams,
4985
+ onRenameIdentityMismatch: "verify-content",
4986
+ }));
4987
+ }
4577
4988
  async function runPinnedCopyHelper(params) {
4578
4989
  assertSafeBasename(params.basename);
4579
4990
  validatePinnedOperationPayload({
@@ -4629,7 +5040,10 @@ async function runPinnedWriteFallback(params) {
4629
5040
  else {
4630
5041
  await writeStreamToHandle(params.input.stream, handle, params.maxBytes);
4631
5042
  }
5043
+ await handle.sync();
4632
5044
  const stat = await handle.stat();
5045
+ await handle.close().catch(() => undefined);
5046
+ await syncDirectoryBestEffort(parentPath);
4633
5047
  created = false;
4634
5048
  return { dev: stat.dev, ino: stat.ino };
4635
5049
  }
@@ -4677,11 +5091,49 @@ async function runPinnedWriteFallback(params) {
4677
5091
  await withAsyncDirectoryGuards([parentGuard], async () => {
4678
5092
  await promises_.rename(tempPath, targetPath);
4679
5093
  renamed = true;
5094
+ await getFsSafeTestHooks()?.afterPinnedWriteFallbackRename?.(targetPath);
4680
5095
  await syncDirectoryBestEffort(parentPath);
4681
5096
  targetStat = await promises_.lstat(targetPath);
4682
- if (targetStat.isSymbolicLink() || !file_identity_sameFileIdentity(targetStat, expectedTempStat)) {
5097
+ if (targetStat.isSymbolicLink()) {
4683
5098
  throw new errors_FsSafeError("path-mismatch", "fallback target changed during write");
4684
5099
  }
5100
+ if (!file_identity_sameFileIdentity(targetStat, expectedTempStat)) {
5101
+ // On filesystems like rclone FUSE, rename(2) can give the destination a
5102
+ // different inode from the source temp fd even with zero concurrency. The
5103
+ // caller must ensure mutual exclusion before passing "verify-content";
5104
+ // fall back to a content hash for this rename-boundary check only.
5105
+ if (params.onRenameIdentityMismatch !== "verify-content") {
5106
+ throw new errors_FsSafeError("path-mismatch", "fallback target changed during write");
5107
+ }
5108
+ if (params.input.kind !== "buffer") {
5109
+ throw new errors_FsSafeError("path-mismatch", "fallback target changed during write");
5110
+ }
5111
+ const expectedHash = sha256Hex(params.input.data, params.input.encoding);
5112
+ const readFlags = external_node_fs_.constants.O_RDONLY |
5113
+ (process.platform !== "win32" && "O_NOFOLLOW" in external_node_fs_.constants
5114
+ ? external_node_fs_.constants.O_NOFOLLOW
5115
+ : 0);
5116
+ const readHandle = await promises_.open(targetPath, readFlags);
5117
+ let actualHash;
5118
+ let readHandleStat;
5119
+ try {
5120
+ // Capture fd-based identity before reading — this is stable across all
5121
+ // subsequent lookups (on FUSE and locally), unlike the lstat-based
5122
+ // targetStat that triggered this fallback.
5123
+ readHandleStat = await readHandle.stat();
5124
+ actualHash = sha256Hex(await readHandle.readFile());
5125
+ }
5126
+ finally {
5127
+ await readHandle.close().catch(() => undefined);
5128
+ }
5129
+ if (actualHash !== expectedHash) {
5130
+ throw new errors_FsSafeError("path-mismatch", "fallback target changed during write");
5131
+ }
5132
+ // Replace the unreliable lstat-based targetStat with the fd-based stat so
5133
+ // the returned identity is consistent with what subsequent verifications
5134
+ // (e.g. verifyAtomicWriteResult) will obtain by opening the same file.
5135
+ targetStat = readHandleStat;
5136
+ }
4685
5137
  });
4686
5138
  }
4687
5139
  catch (error) {
@@ -5777,21 +6229,6 @@ function normalizePinnedPathError(error) {
5777
6229
  });
5778
6230
  }
5779
6231
 
5780
- ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/test-hooks.js
5781
- let test_hooks_fsSafeTestHooks;
5782
- function allowFsSafeTestHooks() {
5783
- return false || process.env.VITEST === "true";
5784
- }
5785
- function getFsSafeTestHooks() {
5786
- return test_hooks_fsSafeTestHooks;
5787
- }
5788
- function __setFsSafeTestHooksForTest(hooks) {
5789
- if (hooks && !allowFsSafeTestHooks()) {
5790
- throw new Error("__setFsSafeTestHooksForTest is only available in tests");
5791
- }
5792
- test_hooks_fsSafeTestHooks = hooks;
5793
- }
5794
-
5795
6232
  ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/json-stringify.js
5796
6233
  function stringifyJsonDocument(value, replacer, space) {
5797
6234
  const text = JSON.stringify(value, replacer, space);
@@ -6127,6 +6564,7 @@ class RootHandle {
6127
6564
  data,
6128
6565
  mkdir: this.defaults.mkdir,
6129
6566
  mode: this.defaults.mode,
6567
+ renameIdentity: this.defaults.renameIdentity,
6130
6568
  ...options,
6131
6569
  denyMutations: mergeDenyMutationPolicies(this.defaults.denyMutations, options.denyMutations),
6132
6570
  });
@@ -6561,21 +6999,23 @@ async function writeFileInRoot(root, params) {
6561
6999
  async function commitPinnedWriteInRoot(root, pinned, params) {
6562
7000
  let identity;
6563
7001
  try {
6564
- identity = await runPinnedWriteHelper({
7002
+ identity = await runPinnedWriteWithRenamePolicy({
6565
7003
  rootPath: pinned.rootReal,
6566
7004
  relativeParentPath: pinned.relativeParentPath,
6567
7005
  basename: pinned.basename,
7006
+ targetPath: pinned.targetPath,
7007
+ renameIdentity: params.renameIdentity,
6568
7008
  mkdir: params.mkdir !== false,
6569
7009
  mode: params.mode ?? pinned.mode,
6570
7010
  overwrite: params.overwrite,
6571
- input: {
6572
- kind: "buffer",
6573
- data: params.data,
6574
- encoding: params.encoding,
6575
- },
7011
+ input: { kind: "buffer", data: params.data, encoding: params.encoding },
6576
7012
  });
6577
7013
  }
6578
7014
  catch (error) {
7015
+ const errorCode = error?.code;
7016
+ if (errorCode === "file_lock_stale" || errorCode === "file_lock_timeout") {
7017
+ throw error;
7018
+ }
6579
7019
  if (params.overwrite === false && isAlreadyExistsError(error)) {
6580
7020
  throw new errors_FsSafeError("already-exists", "file already exists", {
6581
7021
  cause: error instanceof Error ? error : undefined,
@@ -10720,7 +11160,7 @@ function handleIntersectionResults(result, left, right) {
10720
11160
  result.value = merged.data;
10721
11161
  return result;
10722
11162
  }
10723
- const $ZodTuple = /*@__PURE__*/ (/* unused pure expression or super */ null && (core.$constructor("$ZodTuple", (inst, def) => {
11163
+ const $ZodTuple = /*@__PURE__*/ $constructor("$ZodTuple", (inst, def) => {
10724
11164
  $ZodType.init(inst, def);
10725
11165
  const items = def.items;
10726
11166
  inst._zod.parse = (payload, ctx) => {
@@ -10796,7 +11236,7 @@ const $ZodTuple = /*@__PURE__*/ (/* unused pure expression or super */ null && (
10796
11236
  }
10797
11237
  return handleTupleResults(itemResults, payload, items, input, optoutStart);
10798
11238
  };
10799
- })));
11239
+ });
10800
11240
  function getTupleOptStart(items, key) {
10801
11241
  for (let i = items.length - 1; i >= 0; i--) {
10802
11242
  if (items[i]._zod[key] !== "optional")
@@ -10806,7 +11246,7 @@ function getTupleOptStart(items, key) {
10806
11246
  }
10807
11247
  function handleTupleResult(result, final, index) {
10808
11248
  if (result.issues.length) {
10809
- final.issues.push(...util.prefixIssues(index, result.issues));
11249
+ final.issues.push(...prefixIssues(index, result.issues));
10810
11250
  }
10811
11251
  final.value[index] = result.value;
10812
11252
  }
@@ -10822,7 +11262,7 @@ function handleTupleResults(itemResults, final, items, input, optoutStart) {
10822
11262
  final.value.length = i;
10823
11263
  break;
10824
11264
  }
10825
- final.issues.push(...util.prefixIssues(i, r.issues));
11265
+ final.issues.push(...prefixIssues(i, r.issues));
10826
11266
  }
10827
11267
  final.value[i] = r.value;
10828
11268
  }
@@ -13606,12 +14046,12 @@ const tupleProcessor = (schema, ctx, _json, params) => {
13606
14046
  json.type = "array";
13607
14047
  const prefixPath = ctx.target === "draft-2020-12" ? "prefixItems" : "items";
13608
14048
  const restPath = ctx.target === "draft-2020-12" ? "items" : ctx.target === "openapi-3.0" ? "items" : "additionalItems";
13609
- const prefixItems = def.items.map((x, i) => process(x, ctx, {
14049
+ const prefixItems = def.items.map((x, i) => to_json_schema_process(x, ctx, {
13610
14050
  ...params,
13611
14051
  path: [...params.path, prefixPath, i],
13612
14052
  }));
13613
14053
  const rest = def.rest
13614
- ? process(def.rest, ctx, {
14054
+ ? to_json_schema_process(def.rest, ctx, {
13615
14055
  ...params,
13616
14056
  path: [...params.path, restPath, ...(ctx.target === "openapi-3.0" ? [def.items.length] : [])],
13617
14057
  })
@@ -14817,24 +15257,24 @@ function intersection(left, right) {
14817
15257
  right: right,
14818
15258
  });
14819
15259
  }
14820
- const ZodTuple = /*@__PURE__*/ (/* unused pure expression or super */ null && (core.$constructor("ZodTuple", (inst, def) => {
14821
- core.$ZodTuple.init(inst, def);
15260
+ const ZodTuple = /*@__PURE__*/ $constructor("ZodTuple", (inst, def) => {
15261
+ $ZodTuple.init(inst, def);
14822
15262
  ZodType.init(inst, def);
14823
- inst._zod.processJSONSchema = (ctx, json, params) => processors.tupleProcessor(inst, ctx, json, params);
15263
+ inst._zod.processJSONSchema = (ctx, json, params) => tupleProcessor(inst, ctx, json, params);
14824
15264
  inst.rest = (rest) => inst.clone({
14825
15265
  ...inst._zod.def,
14826
15266
  rest: rest,
14827
15267
  });
14828
- })));
15268
+ });
14829
15269
  function tuple(items, _paramsOrRest, _params) {
14830
- const hasRest = _paramsOrRest instanceof core.$ZodType;
15270
+ const hasRest = _paramsOrRest instanceof $ZodType;
14831
15271
  const params = hasRest ? _params : _paramsOrRest;
14832
15272
  const rest = hasRest ? _paramsOrRest : null;
14833
15273
  return new ZodTuple({
14834
15274
  type: "tuple",
14835
15275
  items: items,
14836
15276
  rest,
14837
- ...util.normalizeParams(params),
15277
+ ...normalizeParams(params),
14838
15278
  });
14839
15279
  }
14840
15280
  const ZodRecord = /*@__PURE__*/ $constructor("ZodRecord", (inst, def) => {
@@ -14844,7 +15284,7 @@ const ZodRecord = /*@__PURE__*/ $constructor("ZodRecord", (inst, def) => {
14844
15284
  inst.keyType = def.keyType;
14845
15285
  inst.valueType = def.valueType;
14846
15286
  });
14847
- function record(keyType, valueType, params) {
15287
+ function schemas_record(keyType, valueType, params) {
14848
15288
  // v3-compat: z.record(valueType, params?) — defaults keyType to z.string()
14849
15289
  if (!valueType || !valueType._zod) {
14850
15290
  return new ZodRecord({
@@ -15335,7 +15775,7 @@ const stringbool = (...args) => core._stringbool({
15335
15775
  }, ...args);
15336
15776
  function schemas_json(params) {
15337
15777
  const jsonSchema = lazy(() => {
15338
- return union([schemas_string(params), schemas_number(), schemas_boolean(), schemas_null(), schemas_array(jsonSchema), record(schemas_string(), jsonSchema)]);
15778
+ return union([schemas_string(params), schemas_number(), schemas_boolean(), schemas_null(), schemas_array(jsonSchema), schemas_record(schemas_string(), jsonSchema)]);
15339
15779
  });
15340
15780
  return jsonSchema;
15341
15781
  }
@@ -25617,7 +26057,7 @@ const dist_src_Octokit = Octokit.plugin(requestLog, legacyRestEndpointMethods, p
25617
26057
  const execFileAsync = (0,external_node_util_.promisify)(external_node_child_process_.execFile);
25618
26058
  const RulesetRuleSchema = schemas_object({
25619
26059
  type: schemas_string(),
25620
- parameters: record(schemas_string(), unknown()).optional()
26060
+ parameters: schemas_record(schemas_string(), unknown()).optional()
25621
26061
  });
25622
26062
  const RulesetSchema = schemas_object({
25623
26063
  id: schemas_number(),
@@ -26053,7 +26493,7 @@ const MAX_NPMRC_BYTES = 64 * 1024;
26053
26493
  // 1 MB — covers even the largest real-world manifests
26054
26494
  const MAX_PACKAGE_JSON_BYTES = 1024 * 1024;
26055
26495
  const PackageJsonSchema = schemas_object({
26056
- scripts: record(schemas_string(), schemas_string()).optional()
26496
+ scripts: schemas_record(schemas_string(), schemas_string()).optional()
26057
26497
  });
26058
26498
  /**
26059
26499
  * File system implementation of script discovery gateway
@@ -26187,7 +26627,7 @@ const PackageJsonSchema = schemas_object({
26187
26627
  /** Maximum lock file size: 256 KB */ const MAX_LOCK_FILE_BYTES = 262_144;
26188
26628
  /** Relative path to the skills lock file. */ const SKILLS_LOCK_PATH = "skills-lock.json";
26189
26629
  /** Zod schema for skills-lock.json structure */ const SkillsLockSchema = schemas_object({
26190
- skills: record(schemas_string(), unknown()).optional()
26630
+ skills: schemas_record(schemas_string(), unknown()).optional()
26191
26631
  });
26192
26632
  /**
26193
26633
  * Reads the skills-lock.json file and returns the set of locked skill names.
@@ -27900,6 +28340,1584 @@ async function checkAndPromptAgentShell(npmrcGateway, targetDir, prompt, environ
27900
28340
  }
27901
28341
  }
27902
28342
 
28343
+ ;// CONCATENATED MODULE: ../doctor/src/formatters/human-renderer.ts
28344
+ const SEVERITY_ORDER = [
28345
+ "critical",
28346
+ "high",
28347
+ "medium",
28348
+ "low",
28349
+ "info"
28350
+ ];
28351
+ const CATEGORY_ORDER = [
28352
+ "missing-required",
28353
+ "malformed-reference",
28354
+ "wrong-direction",
28355
+ "drift",
28356
+ "governance",
28357
+ "composition-style"
28358
+ ];
28359
+ function sortFindings(findings) {
28360
+ return [
28361
+ ...findings
28362
+ ].sort((a, b)=>{
28363
+ const severityDiff = SEVERITY_ORDER.indexOf(a.severity) - SEVERITY_ORDER.indexOf(b.severity);
28364
+ if (severityDiff !== 0) return severityDiff;
28365
+ return CATEGORY_ORDER.indexOf(a.category) - CATEGORY_ORDER.indexOf(b.category);
28366
+ });
28367
+ }
28368
+ function renderHuman(summary, findings, logger, snapshotRef) {
28369
+ if (snapshotRef) {
28370
+ logger.info(`Evidence snapshot: ${snapshotRef}`);
28371
+ }
28372
+ logger.info(`Archetype: ${summary.archetype}`);
28373
+ logger.info(` ${summary.archetypeDescription}`);
28374
+ logger.info(` Dominance score: ${(summary.dominanceScore * 100).toFixed(0)}%`);
28375
+ logger.info(` Total records: ${summary.totalRecords}`);
28376
+ if (summary.harnessBreakdown.length > 0) {
28377
+ logger.info(" Harness breakdown:");
28378
+ for (const { harness, count } of summary.harnessBreakdown){
28379
+ logger.info(` ${harness}: ${count}`);
28380
+ }
28381
+ }
28382
+ if (summary.crossHarnessEdges > 0) {
28383
+ logger.info(` Cross-harness edges: ${summary.crossHarnessEdges}`);
28384
+ }
28385
+ if (findings.length === 0) {
28386
+ logger.success("No findings.");
28387
+ return;
28388
+ }
28389
+ logger.info(`\nFindings (${findings.length}):`);
28390
+ const sorted = sortFindings(findings);
28391
+ for (const finding of sorted){
28392
+ const severityTag = `[${finding.severity.toUpperCase()}]`;
28393
+ const classTag = `[${finding.classification}]`;
28394
+ const assumedTag = finding.assumedIntent ? " [assumed intent]" : "";
28395
+ logger.info(` ${severityTag}${classTag} ${finding.criterionId}: ${finding.description}${assumedTag}`);
28396
+ if (finding.evidenceCitation) {
28397
+ logger.info(` Evidence: node ${finding.evidenceCitation.nodeId} (${finding.evidenceCitation.sourceFile})`);
28398
+ }
28399
+ }
28400
+ }
28401
+ function hasBlockingFindings(findings) {
28402
+ return findings.some((f)=>(f.severity === "critical" || f.severity === "high") && f.classification === "defect");
28403
+ }
28404
+
28405
+ ;// CONCATENATED MODULE: ../doctor/src/lib/cross-harness-edges.ts
28406
+ function resolveEdgeTargets(edge) {
28407
+ return Array.isArray(edge.direction.to) ? edge.direction.to : [
28408
+ edge.direction.to
28409
+ ];
28410
+ }
28411
+ function buildHarnessByPath(records) {
28412
+ const harnessByPath = new Map();
28413
+ for (const record of records){
28414
+ harnessByPath.set(record.path, record.harness);
28415
+ }
28416
+ return harnessByPath;
28417
+ }
28418
+ function isCrossHarnessTarget(edge, targetPath, sourceHarness, harnessByPath) {
28419
+ if (edge.malformed || edge.type === "glob-binding") return false;
28420
+ const targetHarness = harnessByPath.get(targetPath);
28421
+ return targetHarness !== undefined && targetHarness !== sourceHarness;
28422
+ }
28423
+ function countCrossHarnessEdges(records) {
28424
+ const harnessByPath = buildHarnessByPath(records);
28425
+ let count = 0;
28426
+ for (const record of records){
28427
+ for (const edge of record.edges){
28428
+ for (const target of resolveEdgeTargets(edge)){
28429
+ if (isCrossHarnessTarget(edge, target, record.harness, harnessByPath)) {
28430
+ count++;
28431
+ }
28432
+ }
28433
+ }
28434
+ }
28435
+ return count;
28436
+ }
28437
+
28438
+ ;// CONCATENATED MODULE: ../doctor/src/formatters/json-formatter.ts
28439
+
28440
+
28441
+ const HarnessNameSchema = schemas_enum([
28442
+ "claude",
28443
+ "copilot",
28444
+ "codex",
28445
+ "antigravity",
28446
+ "hermes",
28447
+ "crush",
28448
+ "pi",
28449
+ "shared"
28450
+ ]);
28451
+ const ConstructTypeSchema = schemas_enum([
28452
+ "instruction",
28453
+ "skill",
28454
+ "agent",
28455
+ "subagent",
28456
+ "mcp-server",
28457
+ "plugin",
28458
+ "hook"
28459
+ ]);
28460
+ const EdgeTypeSchema = schemas_enum([
28461
+ "hard-import",
28462
+ "soft-reference",
28463
+ "glob-binding"
28464
+ ]);
28465
+ const ArchetypeSchema = schemas_enum([
28466
+ "pure",
28467
+ "intentional-hybrid",
28468
+ "canonical-contract",
28469
+ "accidental-sprawl",
28470
+ "none",
28471
+ "ambiguous"
28472
+ ]);
28473
+ const CitationHandleSchema = schemas_object({
28474
+ nodeId: schemas_string(),
28475
+ sourceFile: schemas_string(),
28476
+ lineRange: tuple([
28477
+ schemas_number(),
28478
+ schemas_number()
28479
+ ]).optional(),
28480
+ snapshotRef: schemas_string().optional()
28481
+ });
28482
+ const FindingSchema = schemas_object({
28483
+ id: schemas_string(),
28484
+ criterionId: schemas_string(),
28485
+ targetId: schemas_string(),
28486
+ severity: schemas_enum([
28487
+ "critical",
28488
+ "high",
28489
+ "medium",
28490
+ "low",
28491
+ "info"
28492
+ ]),
28493
+ category: schemas_enum([
28494
+ "missing-required",
28495
+ "malformed-reference",
28496
+ "wrong-direction",
28497
+ "drift",
28498
+ "governance",
28499
+ "composition-style"
28500
+ ]),
28501
+ classification: schemas_enum([
28502
+ "defect",
28503
+ "advisory",
28504
+ "info"
28505
+ ]),
28506
+ intentGated: schemas_boolean(),
28507
+ assumedIntent: schemas_boolean(),
28508
+ description: schemas_string(),
28509
+ evidenceCitation: CitationHandleSchema.optional(),
28510
+ snapshotRef: schemas_string().optional()
28511
+ });
28512
+ const ReportInventoryItemSchema = schemas_object({
28513
+ id: schemas_string(),
28514
+ path: schemas_string(),
28515
+ harness: HarnessNameSchema,
28516
+ constructType: ConstructTypeSchema,
28517
+ loadMechanism: schemas_enum([
28518
+ "referenced",
28519
+ "convention-loaded"
28520
+ ]),
28521
+ serverName: schemas_string().optional(),
28522
+ transport: schemas_string().optional()
28523
+ });
28524
+ const ReportEdgeSchema = schemas_object({
28525
+ from: schemas_string(),
28526
+ to: schemas_string(),
28527
+ type: EdgeTypeSchema,
28528
+ malformed: schemas_boolean(),
28529
+ reason: schemas_enum([
28530
+ "missing-target",
28531
+ "path-traversal"
28532
+ ]).optional(),
28533
+ crossHarness: schemas_boolean()
28534
+ });
28535
+ const ReportJsonSchema = schemas_object({
28536
+ archetype: ArchetypeSchema,
28537
+ dominanceScore: schemas_number(),
28538
+ totalRecords: schemas_number(),
28539
+ harnessBreakdown: schemas_array(schemas_object({
28540
+ harness: schemas_string(),
28541
+ count: schemas_number()
28542
+ })),
28543
+ crossHarnessEdges: schemas_number(),
28544
+ inventory: schemas_array(ReportInventoryItemSchema),
28545
+ edges: schemas_array(ReportEdgeSchema),
28546
+ findings: schemas_array(FindingSchema),
28547
+ snapshotRef: schemas_string().optional()
28548
+ });
28549
+ function toInventoryItems(records) {
28550
+ return records.map((record)=>({
28551
+ id: record.id,
28552
+ path: record.path,
28553
+ harness: record.harness,
28554
+ constructType: record.constructType,
28555
+ loadMechanism: record.loadMechanism,
28556
+ ...record.serverName !== undefined ? {
28557
+ serverName: record.serverName
28558
+ } : {},
28559
+ ...record.transport !== undefined ? {
28560
+ transport: record.transport
28561
+ } : {}
28562
+ })).sort((a, b)=>a.path.localeCompare(b.path) || a.id.localeCompare(b.id));
28563
+ }
28564
+ function toReportEdges(records) {
28565
+ const harnessByPath = buildHarnessByPath(records);
28566
+ const result = [];
28567
+ for (const record of records){
28568
+ for (const edge of record.edges){
28569
+ for (const target of resolveEdgeTargets(edge)){
28570
+ result.push({
28571
+ from: edge.direction.from,
28572
+ to: target,
28573
+ type: edge.type,
28574
+ malformed: edge.malformed,
28575
+ ...edge.reason !== undefined ? {
28576
+ reason: edge.reason
28577
+ } : {},
28578
+ crossHarness: isCrossHarnessTarget(edge, target, record.harness, harnessByPath)
28579
+ });
28580
+ }
28581
+ }
28582
+ }
28583
+ return result;
28584
+ }
28585
+ function toJson(summary, findings, records, snapshotRef) {
28586
+ const edges = toReportEdges(records);
28587
+ const report = {
28588
+ archetype: summary.archetype,
28589
+ dominanceScore: summary.dominanceScore,
28590
+ totalRecords: summary.totalRecords,
28591
+ harnessBreakdown: summary.harnessBreakdown,
28592
+ crossHarnessEdges: edges.filter((edge)=>edge.crossHarness).length,
28593
+ inventory: toInventoryItems(records),
28594
+ edges,
28595
+ findings,
28596
+ ...snapshotRef !== undefined ? {
28597
+ snapshotRef
28598
+ } : {}
28599
+ };
28600
+ return ReportJsonSchema.parse(report);
28601
+ }
28602
+
28603
+ ;// CONCATENATED MODULE: ../doctor/src/formatters/summary-formatter.ts
28604
+
28605
+ const ARCHETYPE_DESCRIPTIONS = {
28606
+ pure: "Single-harness configuration. One AI coding assistant dominates the repository.",
28607
+ "intentional-hybrid": "Multi-harness configuration with cross-harness references. Harnesses deliberately share context.",
28608
+ "canonical-contract": "Shared AGENTS.md contract. All harnesses read from a single canonical instruction file.",
28609
+ "accidental-sprawl": "Multiple harnesses configured without cross-referencing. May indicate legacy or uncoordinated setup.",
28610
+ none: "No agentic configuration detected.",
28611
+ ambiguous: "Configuration does not fit a single archetype cleanly."
28612
+ };
28613
+ function formatSummary(records, classification) {
28614
+ const counts = new Map();
28615
+ for (const r of records){
28616
+ counts.set(r.harness, (counts.get(r.harness) ?? 0) + 1);
28617
+ }
28618
+ const harnessBreakdown = Array.from(counts.entries()).map(([harness, count])=>({
28619
+ harness,
28620
+ count
28621
+ })).sort((a, b)=>a.harness.localeCompare(b.harness));
28622
+ const crossHarnessEdges = countCrossHarnessEdges(records);
28623
+ return {
28624
+ archetype: classification.archetype,
28625
+ archetypeDescription: ARCHETYPE_DESCRIPTIONS[classification.archetype],
28626
+ dominanceScore: classification.dominanceScore,
28627
+ totalRecords: records.length,
28628
+ harnessBreakdown,
28629
+ crossHarnessEdges
28630
+ };
28631
+ }
28632
+
28633
+ ;// CONCATENATED MODULE: ../doctor/src/gateways/intent-artifact.ts
28634
+
28635
+
28636
+
28637
+
28638
+ const MAX_INTENT_BYTES = 65_536;
28639
+ const ARTIFACT_FILENAME = "intent.json";
28640
+ const ARTIFACT_DIR = ".agentic-doctor";
28641
+ const DeclaredIntentSchema = schemas_object({
28642
+ targetHarnesses: schemas_array(schemas_enum([
28643
+ "claude",
28644
+ "copilot",
28645
+ "codex",
28646
+ "antigravity",
28647
+ "hermes",
28648
+ "crush",
28649
+ "pi",
28650
+ "shared"
28651
+ ])),
28652
+ desiredCapabilities: schemas_array(schemas_string()),
28653
+ confirmedAnswers: schemas_record(schemas_string(), unknown()),
28654
+ intentSource: schemas_enum([
28655
+ "interactive",
28656
+ "ci-assumed",
28657
+ "pre-committed"
28658
+ ]),
28659
+ snapshotRef: schemas_string().optional()
28660
+ });
28661
+ async function readIntentArtifact(repoRoot) {
28662
+ const artifactRelPath = `${ARTIFACT_DIR}/${ARTIFACT_FILENAME}`;
28663
+ const artifactPath = (0,external_node_path_.resolve)(repoRoot, ARTIFACT_DIR, ARTIFACT_FILENAME);
28664
+ let raw;
28665
+ try {
28666
+ raw = await file_system_utils_readTextWithinRoot(repoRoot, artifactRelPath, MAX_INTENT_BYTES);
28667
+ } catch {
28668
+ return {
28669
+ found: false
28670
+ };
28671
+ }
28672
+ let parsed;
28673
+ try {
28674
+ parsed = JSON.parse(raw);
28675
+ } catch {
28676
+ return {
28677
+ found: false
28678
+ };
28679
+ }
28680
+ const result = DeclaredIntentSchema.safeParse(parsed);
28681
+ if (!result.success) {
28682
+ return {
28683
+ found: false
28684
+ };
28685
+ }
28686
+ return {
28687
+ found: true,
28688
+ artifact: result.data,
28689
+ path: artifactPath
28690
+ };
28691
+ }
28692
+ async function writeIntentArtifact(repoRoot, artifact) {
28693
+ const artifactPath = await resolveSafePath(repoRoot, `${ARTIFACT_DIR}/${ARTIFACT_FILENAME}`);
28694
+ await mkdir(dirname(artifactPath), {
28695
+ recursive: true
28696
+ });
28697
+ await writeFile(artifactPath, JSON.stringify(artifact, null, 2), "utf-8");
28698
+ return artifactPath;
28699
+ }
28700
+ function buildDefaultIntent(harnesses) {
28701
+ return {
28702
+ targetHarnesses: harnesses.filter((h)=>h !== "shared"),
28703
+ desiredCapabilities: [],
28704
+ confirmedAnswers: {},
28705
+ intentSource: "ci-assumed"
28706
+ };
28707
+ }
28708
+
28709
+ ;// CONCATENATED MODULE: ../doctor/src/entities/harness-footprints.ts
28710
+ const HARNESS_NAMES = [
28711
+ "claude",
28712
+ "copilot",
28713
+ "codex",
28714
+ "antigravity",
28715
+ "hermes",
28716
+ "crush",
28717
+ "pi"
28718
+ ];
28719
+ const HARNESS_FOOTPRINTS = {
28720
+ claude: {
28721
+ name: "claude",
28722
+ status: "verified",
28723
+ readsAgentsMd: false,
28724
+ walkBoundary: "git-root",
28725
+ conventionFiles: [
28726
+ "CLAUDE.md"
28727
+ ],
28728
+ conventionDirs: [
28729
+ ".claude/"
28730
+ ],
28731
+ primaryIndicators: [
28732
+ "CLAUDE.md",
28733
+ ".claude/"
28734
+ ],
28735
+ crossRefMechanisms: [
28736
+ {
28737
+ edgeType: "hard-import",
28738
+ description: "Claude @path/to/file syntax at line start inlines the referenced file"
28739
+ },
28740
+ {
28741
+ edgeType: "soft-reference",
28742
+ description: "Frontmatter see:/references:/requires: keys or markdown links to sibling instruction files"
28743
+ }
28744
+ ]
28745
+ },
28746
+ copilot: {
28747
+ name: "copilot",
28748
+ status: "verified",
28749
+ readsAgentsMd: false,
28750
+ walkBoundary: "project",
28751
+ conventionFiles: [
28752
+ ".github/copilot-instructions.md"
28753
+ ],
28754
+ conventionDirs: [
28755
+ ".github/instructions/"
28756
+ ],
28757
+ primaryIndicators: [
28758
+ ".github/copilot-instructions.md",
28759
+ ".github/instructions/"
28760
+ ],
28761
+ crossRefMechanisms: [
28762
+ {
28763
+ edgeType: "glob-binding",
28764
+ description: "Copilot applyTo frontmatter field scopes an instruction to matching source files"
28765
+ },
28766
+ {
28767
+ edgeType: "soft-reference",
28768
+ description: "Frontmatter see:/references:/requires: keys or markdown links to sibling instruction files"
28769
+ }
28770
+ ]
28771
+ },
28772
+ codex: {
28773
+ name: "codex",
28774
+ status: "verified",
28775
+ readsAgentsMd: true,
28776
+ walkBoundary: "git-root",
28777
+ conventionFiles: [
28778
+ "AGENTS.md",
28779
+ "AGENTS.override.md"
28780
+ ],
28781
+ conventionDirs: [
28782
+ ".codex/",
28783
+ ".agents/skills/",
28784
+ ".codex-plugin/"
28785
+ ],
28786
+ primaryIndicators: [
28787
+ "AGENTS.override.md",
28788
+ ".codex/",
28789
+ ".agents/skills/",
28790
+ ".codex-plugin/"
28791
+ ],
28792
+ crossRefMechanisms: [
28793
+ {
28794
+ edgeType: "soft-reference",
28795
+ description: "Frontmatter see:/references:/requires: keys or markdown links to sibling instruction files"
28796
+ }
28797
+ ]
28798
+ },
28799
+ antigravity: {
28800
+ name: "antigravity",
28801
+ status: "needs-verification",
28802
+ readsAgentsMd: false,
28803
+ walkBoundary: null,
28804
+ conventionFiles: [
28805
+ "GEMINI.md"
28806
+ ],
28807
+ conventionDirs: [
28808
+ ".gemini/"
28809
+ ],
28810
+ primaryIndicators: [
28811
+ "GEMINI.md",
28812
+ ".gemini/"
28813
+ ],
28814
+ crossRefMechanisms: [
28815
+ {
28816
+ edgeType: "soft-reference",
28817
+ description: "Frontmatter see:/references:/requires: keys or markdown links to sibling instruction files"
28818
+ }
28819
+ ]
28820
+ },
28821
+ hermes: {
28822
+ name: "hermes",
28823
+ status: "verified",
28824
+ readsAgentsMd: true,
28825
+ walkBoundary: "git-root",
28826
+ conventionFiles: [
28827
+ ".hermes.md",
28828
+ "HERMES.md",
28829
+ "AGENTS.md",
28830
+ "agents.md",
28831
+ "CLAUDE.md",
28832
+ "claude.md",
28833
+ ".cursorrules",
28834
+ "SOUL.md"
28835
+ ],
28836
+ conventionDirs: [],
28837
+ primaryIndicators: [
28838
+ ".hermes.md",
28839
+ "HERMES.md",
28840
+ "SOUL.md"
28841
+ ],
28842
+ crossRefMechanisms: [
28843
+ {
28844
+ edgeType: "soft-reference",
28845
+ description: "Frontmatter see:/references:/requires: keys or markdown links to sibling instruction files"
28846
+ }
28847
+ ]
28848
+ },
28849
+ crush: {
28850
+ name: "crush",
28851
+ status: "verified",
28852
+ readsAgentsMd: true,
28853
+ walkBoundary: null,
28854
+ conventionFiles: [
28855
+ "AGENTS.md",
28856
+ "crush.json"
28857
+ ],
28858
+ conventionDirs: [
28859
+ ".crush/",
28860
+ ".agents/skills/"
28861
+ ],
28862
+ primaryIndicators: [
28863
+ "crush.json",
28864
+ ".crush/"
28865
+ ],
28866
+ crossRefMechanisms: [
28867
+ {
28868
+ edgeType: "soft-reference",
28869
+ description: "Frontmatter see:/references:/requires: keys or markdown links to sibling instruction files"
28870
+ }
28871
+ ]
28872
+ },
28873
+ pi: {
28874
+ name: "pi",
28875
+ status: "verified",
28876
+ readsAgentsMd: true,
28877
+ walkBoundary: "filesystem-root",
28878
+ conventionFiles: [
28879
+ "AGENTS.md",
28880
+ "AGENTS.MD",
28881
+ "CLAUDE.md",
28882
+ "CLAUDE.MD"
28883
+ ],
28884
+ conventionDirs: [
28885
+ ".pi/",
28886
+ ".pi/skills/",
28887
+ ".pi/prompts/"
28888
+ ],
28889
+ primaryIndicators: [
28890
+ ".pi/",
28891
+ ".pi/skills/",
28892
+ ".pi/prompts/"
28893
+ ],
28894
+ crossRefMechanisms: [
28895
+ {
28896
+ edgeType: "soft-reference",
28897
+ description: "Frontmatter see:/references:/requires: keys or markdown links to sibling instruction files"
28898
+ }
28899
+ ]
28900
+ },
28901
+ shared: {
28902
+ name: "shared",
28903
+ status: "verified",
28904
+ readsAgentsMd: true,
28905
+ walkBoundary: null,
28906
+ conventionFiles: [
28907
+ "AGENTS.md",
28908
+ "AGENTS.MD",
28909
+ "agents.md"
28910
+ ],
28911
+ conventionDirs: [],
28912
+ primaryIndicators: [
28913
+ "AGENTS.md",
28914
+ "AGENTS.MD",
28915
+ "agents.md"
28916
+ ],
28917
+ crossRefMechanisms: []
28918
+ }
28919
+ };
28920
+ function getFootprint(harness) {
28921
+ return HARNESS_FOOTPRINTS[harness];
28922
+ }
28923
+ function matchesPrimaryIndicator(repoRelativePath, indicators) {
28924
+ const normalized = repoRelativePath.replace(/\\/g, "/");
28925
+ return indicators.some((indicator)=>{
28926
+ if (indicator.endsWith("/")) {
28927
+ return normalized === indicator.slice(0, -1) || normalized.startsWith(indicator);
28928
+ }
28929
+ return normalized === indicator;
28930
+ });
28931
+ }
28932
+
28933
+ ;// CONCATENATED MODULE: ../doctor/src/lib/edge-parser.ts
28934
+
28935
+ const HARD_IMPORT_GLOBAL_RE = /^@([^\s@][^\s]*)/gm;
28936
+ const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---/;
28937
+ const MARKDOWN_LINK_RE = /\[(?:[^\]]*)\]\(([^)]+)\)/g;
28938
+ const SOFT_REF_FRONTMATTER_KEYS = [
28939
+ "see",
28940
+ "references",
28941
+ "requires"
28942
+ ];
28943
+ function edge_parser_parseFrontmatter(content) {
28944
+ const match = FRONTMATTER_RE.exec(content);
28945
+ if (!match) return null;
28946
+ try {
28947
+ const parsed = (0,dist/* .parse */.qg)(match[1]);
28948
+ return typeof parsed === "object" && parsed !== null ? parsed : null;
28949
+ } catch {
28950
+ return null;
28951
+ }
28952
+ }
28953
+ function contentWithoutFrontmatter(content) {
28954
+ return content.replace(FRONTMATTER_RE, "");
28955
+ }
28956
+ function toStringArray(value) {
28957
+ if (typeof value === "string") return [
28958
+ value
28959
+ ];
28960
+ if (Array.isArray(value)) return value.filter((v)=>typeof v === "string");
28961
+ return [];
28962
+ }
28963
+ function isInstructionFilePath(path) {
28964
+ return path.endsWith(".md") || path.endsWith(".instructions.md") || path.endsWith(".mdc");
28965
+ }
28966
+ function parseRawEdges(content) {
28967
+ const edges = [];
28968
+ const fm = edge_parser_parseFrontmatter(content);
28969
+ if (fm) {
28970
+ if (fm.applyTo) {
28971
+ for (const glob of toStringArray(fm.applyTo)){
28972
+ edges.push({
28973
+ type: "glob-binding",
28974
+ rawTarget: glob
28975
+ });
28976
+ }
28977
+ }
28978
+ for (const key of SOFT_REF_FRONTMATTER_KEYS){
28979
+ const value = fm[key];
28980
+ if (value) {
28981
+ for (const ref of toStringArray(value)){
28982
+ if (isInstructionFilePath(ref)) {
28983
+ edges.push({
28984
+ type: "soft-reference",
28985
+ rawTarget: ref
28986
+ });
28987
+ }
28988
+ }
28989
+ }
28990
+ }
28991
+ }
28992
+ const body = fm ? contentWithoutFrontmatter(content) : content;
28993
+ for (const match of body.matchAll(HARD_IMPORT_GLOBAL_RE)){
28994
+ const target = match[1];
28995
+ if (target.includes("/")) {
28996
+ edges.push({
28997
+ type: "hard-import",
28998
+ rawTarget: target
28999
+ });
29000
+ }
29001
+ }
29002
+ for (const match of body.matchAll(MARKDOWN_LINK_RE)){
29003
+ const href = match[1];
29004
+ if (!href.startsWith("http://") && !href.startsWith("https://") && isInstructionFilePath(href)) {
29005
+ edges.push({
29006
+ type: "soft-reference",
29007
+ rawTarget: href
29008
+ });
29009
+ }
29010
+ }
29011
+ return edges;
29012
+ }
29013
+
29014
+ ;// CONCATENATED MODULE: ../doctor/src/gateways/mcp-config.ts
29015
+
29016
+
29017
+ const MAX_MCP_CONFIG_BYTES = 1_048_576;
29018
+ const MCP_CONFIG_SOURCES = [
29019
+ {
29020
+ relPath: ".mcp.json",
29021
+ harness: "shared"
29022
+ },
29023
+ {
29024
+ relPath: ".vscode/mcp.json",
29025
+ harness: "copilot"
29026
+ }
29027
+ ];
29028
+ const McpServerEntrySchema = schemas_object({
29029
+ type: schemas_string().optional(),
29030
+ transport: schemas_string().optional()
29031
+ }).passthrough();
29032
+ const McpConfigSchema = schemas_object({
29033
+ mcpServers: schemas_record(schemas_string(), McpServerEntrySchema).optional()
29034
+ }).passthrough();
29035
+ async function readMcpServersFromSource(repoRoot, source) {
29036
+ let content;
29037
+ try {
29038
+ content = await file_system_utils_readTextWithinRoot(repoRoot, source.relPath, MAX_MCP_CONFIG_BYTES);
29039
+ } catch {
29040
+ return [];
29041
+ }
29042
+ let raw;
29043
+ try {
29044
+ raw = JSON.parse(content);
29045
+ } catch {
29046
+ return [];
29047
+ }
29048
+ const result = McpConfigSchema.safeParse(raw);
29049
+ if (!result.success || result.data.mcpServers === undefined) {
29050
+ return [];
29051
+ }
29052
+ return Object.entries(result.data.mcpServers).map(([serverName, entry])=>({
29053
+ serverName,
29054
+ harness: source.harness,
29055
+ path: source.relPath,
29056
+ ...entry.transport !== undefined ? {
29057
+ transport: entry.transport
29058
+ } : entry.type !== undefined ? {
29059
+ transport: entry.type
29060
+ } : {}
29061
+ }));
29062
+ }
29063
+ async function enumerateMcpServers(repoRoot) {
29064
+ const records = [];
29065
+ for (const source of MCP_CONFIG_SOURCES){
29066
+ records.push(...await readMcpServersFromSource(repoRoot, source));
29067
+ }
29068
+ return records;
29069
+ }
29070
+
29071
+ ;// CONCATENATED MODULE: ../doctor/src/gateways/scanner.ts
29072
+
29073
+
29074
+
29075
+
29076
+
29077
+ const MAX_FILE_BYTES = 1_048_576;
29078
+ const SCANNABLE_EXTENSIONS = new Set([
29079
+ ".md",
29080
+ ".mdc",
29081
+ ".json",
29082
+ ".yaml",
29083
+ ".yml",
29084
+ ".toml"
29085
+ ]);
29086
+ // Extensionless files that are known harness convention artifacts
29087
+ const SCANNABLE_EXTENSIONLESS = new Set([
29088
+ ".cursorrules"
29089
+ ]);
29090
+ const ALL_HARNESS_NAMES = [
29091
+ ...HARNESS_NAMES,
29092
+ "shared"
29093
+ ];
29094
+ function isScannableFile(name) {
29095
+ if (SCANNABLE_EXTENSIONLESS.has(name)) return true;
29096
+ const dot = name.lastIndexOf(".");
29097
+ if (dot === -1) return false;
29098
+ return SCANNABLE_EXTENSIONS.has(name.slice(dot));
29099
+ }
29100
+ function isMarkdownFile(name) {
29101
+ return name.endsWith(".md") || name.endsWith(".mdc");
29102
+ }
29103
+ function determineConstructType(relPath) {
29104
+ if (relPath.startsWith(".agents/skills/") || relPath.startsWith(".pi/skills/") || relPath.startsWith(".pi/prompts/")) {
29105
+ return "skill";
29106
+ }
29107
+ if (relPath.startsWith(".codex-plugin/")) {
29108
+ return "plugin";
29109
+ }
29110
+ if (relPath.startsWith(".claude/hooks/")) {
29111
+ return "hook";
29112
+ }
29113
+ if (relPath.startsWith(".claude/agents/")) {
29114
+ return "subagent";
29115
+ }
29116
+ if (relPath.startsWith(".claude/commands/")) {
29117
+ return "agent";
29118
+ }
29119
+ return "instruction";
29120
+ }
29121
+ function determineHarness(repoRelativePath) {
29122
+ const matching = ALL_HARNESS_NAMES.filter((harness)=>matchesPrimaryIndicator(repoRelativePath, HARNESS_FOOTPRINTS[harness].primaryIndicators));
29123
+ if (matching.length === 0) return null;
29124
+ if (matching.length >= 2) return "shared";
29125
+ return matching[0];
29126
+ }
29127
+ function resolveEdge(rawTarget, type, sourceAbsPath, repoRoot, existingAbsPaths) {
29128
+ const from = (0,external_node_path_.relative)(repoRoot, sourceAbsPath).replace(/\\/g, "/");
29129
+ if (type === "glob-binding") {
29130
+ return {
29131
+ type,
29132
+ direction: {
29133
+ from,
29134
+ to: rawTarget
29135
+ },
29136
+ target: rawTarget,
29137
+ malformed: false
29138
+ };
29139
+ }
29140
+ const sourceDir = (0,external_node_path_.dirname)(sourceAbsPath);
29141
+ const resolvedAbs = (0,external_node_path_.resolve)(sourceDir, rawTarget);
29142
+ const absoluteRoot = (0,external_node_path_.resolve)(repoRoot);
29143
+ const isWithinRoot = resolvedAbs === absoluteRoot || resolvedAbs.startsWith(`${absoluteRoot}${external_node_path_.sep}`);
29144
+ if (!isWithinRoot) {
29145
+ return {
29146
+ type,
29147
+ direction: {
29148
+ from,
29149
+ to: rawTarget
29150
+ },
29151
+ target: rawTarget,
29152
+ malformed: true,
29153
+ reason: "path-traversal"
29154
+ };
29155
+ }
29156
+ const relativeTarget = (0,external_node_path_.relative)(absoluteRoot, resolvedAbs).replace(/\\/g, "/");
29157
+ if (!existingAbsPaths.has(resolvedAbs)) {
29158
+ return {
29159
+ type,
29160
+ direction: {
29161
+ from,
29162
+ to: relativeTarget
29163
+ },
29164
+ target: relativeTarget,
29165
+ malformed: true,
29166
+ reason: "missing-target"
29167
+ };
29168
+ }
29169
+ return {
29170
+ type,
29171
+ direction: {
29172
+ from,
29173
+ to: relativeTarget
29174
+ },
29175
+ target: relativeTarget,
29176
+ malformed: false
29177
+ };
29178
+ }
29179
+ async function collectFiles(repoRoot, relDir, collected, depth = 0) {
29180
+ if (depth > 10) return;
29181
+ let entries;
29182
+ try {
29183
+ entries = await file_system_utils_listDirectoryWithinRoot(repoRoot, relDir);
29184
+ } catch {
29185
+ return;
29186
+ }
29187
+ entries.sort((a, b)=>a.name.localeCompare(b.name));
29188
+ for (const entry of entries){
29189
+ const entryRel = relDir ? `${relDir}/${entry.name}` : entry.name;
29190
+ const entryAbs = (0,external_node_path_.resolve)(repoRoot, entryRel);
29191
+ if (entry.isDirectory()) {
29192
+ if (entry.name === "node_modules" || entry.name === ".git" || entry.name === "dist") {
29193
+ continue;
29194
+ }
29195
+ await collectFiles(repoRoot, entryRel, collected, depth + 1);
29196
+ } else if (entry.isFile() && isScannableFile(entry.name)) {
29197
+ collected.push({
29198
+ absPath: entryAbs,
29199
+ relPath: entryRel
29200
+ });
29201
+ }
29202
+ }
29203
+ }
29204
+ async function scanRepository(repoRoot) {
29205
+ const absRoot = (0,external_node_path_.resolve)(repoRoot);
29206
+ const allFiles = [];
29207
+ await collectFiles(absRoot, "", allFiles);
29208
+ const existingAbsPaths = new Set(allFiles.map((f)=>f.absPath));
29209
+ const rawRecords = [];
29210
+ for (const { absPath, relPath } of allFiles){
29211
+ const harness = determineHarness(relPath);
29212
+ if (harness === null) continue;
29213
+ let content = null;
29214
+ if (isMarkdownFile(relPath)) {
29215
+ try {
29216
+ content = await file_system_utils_readTextWithinRoot(absRoot, relPath, MAX_FILE_BYTES);
29217
+ } catch {
29218
+ content = null;
29219
+ }
29220
+ }
29221
+ rawRecords.push({
29222
+ absPath,
29223
+ relPath,
29224
+ harness,
29225
+ content
29226
+ });
29227
+ }
29228
+ const records = rawRecords.map(({ absPath, relPath, harness, content })=>{
29229
+ const edges = [];
29230
+ if (content !== null) {
29231
+ const rawEdges = parseRawEdges(content);
29232
+ for (const raw of rawEdges){
29233
+ edges.push(resolveEdge(raw.rawTarget, raw.type, absPath, absRoot, existingAbsPaths));
29234
+ }
29235
+ }
29236
+ const id = harness === "shared" ? `shared:${relPath}` : `${harness}:${relPath}`;
29237
+ return {
29238
+ id,
29239
+ path: relPath,
29240
+ harness,
29241
+ constructType: determineConstructType(relPath),
29242
+ loadMechanism: "convention-loaded",
29243
+ edges
29244
+ };
29245
+ });
29246
+ const referencedPaths = new Set();
29247
+ for (const record of records){
29248
+ for (const edge of record.edges){
29249
+ if (!edge.malformed && edge.type !== "glob-binding" && typeof edge.direction.to === "string") {
29250
+ referencedPaths.add(edge.direction.to);
29251
+ }
29252
+ }
29253
+ }
29254
+ for (const record of records){
29255
+ if (referencedPaths.has(record.path)) {
29256
+ record.loadMechanism = "referenced";
29257
+ }
29258
+ }
29259
+ const mcpServers = await enumerateMcpServers(absRoot);
29260
+ for (const server of mcpServers){
29261
+ records.push({
29262
+ id: `mcp-server:${server.path}#${server.serverName}`,
29263
+ path: server.path,
29264
+ harness: server.harness,
29265
+ constructType: "mcp-server",
29266
+ loadMechanism: "convention-loaded",
29267
+ edges: [],
29268
+ serverName: server.serverName,
29269
+ ...server.transport !== undefined ? {
29270
+ transport: server.transport
29271
+ } : {}
29272
+ });
29273
+ }
29274
+ return records;
29275
+ }
29276
+
29277
+ ;// CONCATENATED MODULE: ../doctor/src/gateways/wisdom-client.ts
29278
+
29279
+
29280
+ const MAX_GRAPH_BYTES = 10_485_760;
29281
+ class WisdomUnavailableError extends Error {
29282
+ constructor(reason){
29283
+ super(`Wisdom graph unavailable: ${reason}`);
29284
+ this.name = "WisdomUnavailableError";
29285
+ }
29286
+ }
29287
+ const WisdomNodeSchema = schemas_object({
29288
+ id: schemas_string(),
29289
+ label: schemas_string().optional(),
29290
+ description: schemas_string().optional(),
29291
+ sourceFile: schemas_string().optional(),
29292
+ lineStart: schemas_number().int().min(1).optional().catch(undefined),
29293
+ lineEnd: schemas_number().int().min(1).optional().catch(undefined)
29294
+ }).passthrough();
29295
+ const WisdomGraphSchema = schemas_object({
29296
+ nodes: schemas_array(WisdomNodeSchema),
29297
+ edges: schemas_array(unknown()).default([]),
29298
+ snapshotRef: schemas_string().optional()
29299
+ });
29300
+ function createWisdomClient(wisdomDir) {
29301
+ async function getGraph() {
29302
+ let raw;
29303
+ try {
29304
+ raw = await file_system_utils_readTextWithinRoot(wisdomDir, "graphify-out/graph.json", MAX_GRAPH_BYTES);
29305
+ } catch {
29306
+ throw new WisdomUnavailableError("graph.json not found in wisdom directory — ensure the wisdom submodule is initialized");
29307
+ }
29308
+ let parsed;
29309
+ try {
29310
+ parsed = JSON.parse(raw);
29311
+ } catch {
29312
+ throw new WisdomUnavailableError("graph.json is not valid JSON");
29313
+ }
29314
+ try {
29315
+ return WisdomGraphSchema.parse(parsed);
29316
+ } catch {
29317
+ throw new WisdomUnavailableError("graph.json does not match expected schema (nodes must be an array with id strings)");
29318
+ }
29319
+ }
29320
+ async function findNodeById(nodeId) {
29321
+ const graph = await getGraph();
29322
+ return graph.nodes.find((n)=>n.id === nodeId) ?? null;
29323
+ }
29324
+ return {
29325
+ getGraph,
29326
+ findNodeById
29327
+ };
29328
+ }
29329
+
29330
+ ;// CONCATENATED MODULE: ../doctor/src/use-cases/harness-profile.ts
29331
+ const PURE_DOMINANCE_THRESHOLD = 0.8;
29332
+ function buildHarnessProfiles(records) {
29333
+ if (records.length === 0) return [];
29334
+ const data = new Map();
29335
+ for (const record of records){
29336
+ const existing = data.get(record.harness) ?? {
29337
+ records: 0,
29338
+ edges: 0
29339
+ };
29340
+ data.set(record.harness, {
29341
+ records: existing.records + 1,
29342
+ edges: existing.edges + record.edges.length
29343
+ });
29344
+ }
29345
+ const totalWeight = records.reduce((acc, r)=>acc + 1 + r.edges.length, 0);
29346
+ return Array.from(data.entries()).map(([harness, { records: recordCount, edges: edgeCount }])=>({
29347
+ harness,
29348
+ recordCount,
29349
+ edgeCount,
29350
+ share: totalWeight > 0 ? (recordCount + edgeCount) / totalWeight : 0
29351
+ }));
29352
+ }
29353
+
29354
+ ;// CONCATENATED MODULE: ../doctor/src/use-cases/classify-archetype.ts
29355
+
29356
+
29357
+ function hasCrossHarnessEdges(records) {
29358
+ const harnessByPath = new Map();
29359
+ for (const record of records){
29360
+ harnessByPath.set(record.path, record.harness);
29361
+ }
29362
+ for (const record of records){
29363
+ for (const edge of record.edges){
29364
+ if (edge.malformed || edge.type === "glob-binding" || typeof edge.direction.to !== "string") {
29365
+ continue;
29366
+ }
29367
+ const targetHarness = harnessByPath.get(edge.direction.to);
29368
+ if (targetHarness && targetHarness !== record.harness) {
29369
+ return true;
29370
+ }
29371
+ }
29372
+ }
29373
+ return false;
29374
+ }
29375
+ function classifyArchetype(records) {
29376
+ if (records.length === 0) {
29377
+ return {
29378
+ archetype: "none",
29379
+ dominanceScore: 0,
29380
+ ambiguities: []
29381
+ };
29382
+ }
29383
+ const profiles = buildHarnessProfiles(records);
29384
+ const distinctHarnesses = new Set(records.map((r)=>r.harness));
29385
+ if (distinctHarnesses.size === 1 && distinctHarnesses.has("shared")) {
29386
+ return {
29387
+ archetype: "canonical-contract",
29388
+ dominanceScore: 1,
29389
+ ambiguities: []
29390
+ };
29391
+ }
29392
+ const nonSharedHarnesses = [
29393
+ ...distinctHarnesses
29394
+ ].filter((h)=>h !== "shared");
29395
+ const needsVerificationHarnesses = nonSharedHarnesses.filter((h)=>HARNESS_FOOTPRINTS[h].status === "needs-verification");
29396
+ const ambiguities = needsVerificationHarnesses.map((h)=>`harness '${h}' detection patterns are not fully verified`);
29397
+ // If all non-shared harnesses need verification, the archetype is ambiguous
29398
+ if (nonSharedHarnesses.length > 0 && needsVerificationHarnesses.length === nonSharedHarnesses.length) {
29399
+ const topProfile = profiles.reduce((a, b)=>a.share > b.share ? a : b);
29400
+ return {
29401
+ archetype: "ambiguous",
29402
+ dominanceScore: topProfile.share,
29403
+ ambiguities
29404
+ };
29405
+ }
29406
+ // pure: single dominant harness with no cross-harness edges
29407
+ if (nonSharedHarnesses.length === 1) {
29408
+ const dominant = profiles.find((p)=>p.harness === nonSharedHarnesses[0]);
29409
+ const score = dominant?.share ?? 1;
29410
+ if (score >= (/* inlined export .PURE_DOMINANCE_THRESHOLD */0.8) && !hasCrossHarnessEdges(records)) {
29411
+ return {
29412
+ archetype: "pure",
29413
+ dominanceScore: score,
29414
+ ambiguities
29415
+ };
29416
+ }
29417
+ }
29418
+ const crossHarness = hasCrossHarnessEdges(records);
29419
+ const topProfile = profiles.reduce((a, b)=>a.share > b.share ? a : b);
29420
+ if (crossHarness) {
29421
+ return {
29422
+ archetype: "intentional-hybrid",
29423
+ dominanceScore: topProfile.share,
29424
+ ambiguities
29425
+ };
29426
+ }
29427
+ // ambiguous when needs-verification harnesses are present alongside verified ones
29428
+ if (ambiguities.length > 0) {
29429
+ return {
29430
+ archetype: "ambiguous",
29431
+ dominanceScore: topProfile.share,
29432
+ ambiguities
29433
+ };
29434
+ }
29435
+ return {
29436
+ archetype: "accidental-sprawl",
29437
+ dominanceScore: topProfile.share,
29438
+ ambiguities
29439
+ };
29440
+ }
29441
+
29442
+ ;// CONCATENATED MODULE: ../doctor/src/use-cases/criteria.ts
29443
+ const CRITERIA = [
29444
+ {
29445
+ id: "missing-copilot-instructions",
29446
+ appliesToHarness: "copilot",
29447
+ severity: "critical",
29448
+ classification: "defect",
29449
+ category: "missing-required",
29450
+ description: "GitHub Copilot requires .github/copilot-instructions.md to load workspace instructions. Without it, Copilot operates without any project-specific context.",
29451
+ capability: "copilot-workspace-instructions",
29452
+ checkMethod: "inventory.fileExists",
29453
+ checkArgs: {
29454
+ harness: "copilot",
29455
+ paths: [
29456
+ ".github/copilot-instructions.md"
29457
+ ]
29458
+ }
29459
+ },
29460
+ {
29461
+ id: "missing-intent-artifact",
29462
+ appliesToHarness: "all",
29463
+ severity: "medium",
29464
+ classification: "defect",
29465
+ category: "governance",
29466
+ description: "No declared intent artifact found at .agentic-doctor/intent.json. Without declared intent, the doctor cannot evaluate capability preconditions.",
29467
+ checkMethod: "inventory.constructPresent",
29468
+ checkArgs: {
29469
+ harness: "claude",
29470
+ constructType: "instruction"
29471
+ }
29472
+ },
29473
+ {
29474
+ id: "malformed-claude-import",
29475
+ appliesToHarness: "claude",
29476
+ severity: "high",
29477
+ classification: "defect",
29478
+ category: "malformed-reference",
29479
+ description: "A Claude @import reference points to a file that does not exist or escapes the repository root.",
29480
+ checkMethod: "inventory.edgePresent",
29481
+ checkArgs: {
29482
+ fromHarness: "claude",
29483
+ toHarness: "claude",
29484
+ edgeType: "hard-import"
29485
+ }
29486
+ },
29487
+ {
29488
+ id: "cross-harness-drift",
29489
+ appliesToHarness: "all",
29490
+ appliesToArchetype: "accidental-sprawl",
29491
+ severity: "high",
29492
+ classification: "advisory",
29493
+ category: "drift",
29494
+ description: "Multiple AI harnesses are configured but share no cross-harness references. This may indicate accidental configuration sprawl rather than intentional multi-harness setup.",
29495
+ checkMethod: "inventory.archetypeIs",
29496
+ checkArgs: {}
29497
+ },
29498
+ {
29499
+ id: "wrong-direction-copilot-imports-claude",
29500
+ appliesToHarness: "copilot",
29501
+ severity: "medium",
29502
+ classification: "advisory",
29503
+ category: "wrong-direction",
29504
+ description: "A Copilot instruction file references a Claude instruction file. Copilot does not process @import directives, so this reference has no effect and may indicate copy-paste drift.",
29505
+ checkMethod: "inventory.edgeDirectionExists",
29506
+ checkArgs: {
29507
+ fromHarness: "copilot",
29508
+ toHarness: "claude"
29509
+ }
29510
+ },
29511
+ {
29512
+ id: "missing-claude-md",
29513
+ appliesToHarness: "claude",
29514
+ severity: "high",
29515
+ classification: "defect",
29516
+ category: "missing-required",
29517
+ description: "Claude Code requires CLAUDE.md or .claude/ directory to load project instructions.",
29518
+ checkMethod: "inventory.fileExists",
29519
+ checkArgs: {
29520
+ harness: "claude",
29521
+ paths: [
29522
+ "CLAUDE.md",
29523
+ ".claude/"
29524
+ ]
29525
+ }
29526
+ },
29527
+ {
29528
+ id: "missing-agents-md",
29529
+ appliesToHarness: "codex",
29530
+ severity: "high",
29531
+ classification: "defect",
29532
+ category: "missing-required",
29533
+ description: "Codex/OpenAI Codex requires AGENTS.md to load project instructions.",
29534
+ checkMethod: "inventory.fileExists",
29535
+ checkArgs: {
29536
+ harness: "codex",
29537
+ paths: [
29538
+ "AGENTS.md",
29539
+ "AGENTS.override.md"
29540
+ ]
29541
+ }
29542
+ },
29543
+ {
29544
+ id: "missing-hermes-config",
29545
+ appliesToHarness: "hermes",
29546
+ severity: "medium",
29547
+ classification: "advisory",
29548
+ category: "missing-required",
29549
+ description: "Hermes does not have a primary configuration file (.hermes.md or HERMES.md).",
29550
+ checkMethod: "inventory.fileExists",
29551
+ checkArgs: {
29552
+ harness: "hermes",
29553
+ paths: [
29554
+ ".hermes.md",
29555
+ "HERMES.md"
29556
+ ]
29557
+ }
29558
+ },
29559
+ {
29560
+ id: "missing-crush-config",
29561
+ appliesToHarness: "crush",
29562
+ severity: "medium",
29563
+ classification: "advisory",
29564
+ category: "missing-required",
29565
+ description: "Crush does not have a crush.json configuration file.",
29566
+ checkMethod: "inventory.fileExists",
29567
+ checkArgs: {
29568
+ harness: "crush",
29569
+ paths: [
29570
+ "crush.json"
29571
+ ]
29572
+ }
29573
+ }
29574
+ ];
29575
+
29576
+ ;// CONCATENATED MODULE: ../doctor/src/use-cases/detect-ambiguities.ts
29577
+ function detectAmbiguities(records, classification) {
29578
+ const questions = [];
29579
+ if (classification.archetype === "accidental-sprawl") {
29580
+ const harnesses = [
29581
+ ...new Set(records.map((r)=>r.harness))
29582
+ ].filter((h)=>h !== "shared");
29583
+ if (harnesses.length > 1) {
29584
+ questions.push({
29585
+ id: "intended-multi-harness",
29586
+ question: `Multiple AI harnesses are configured (${harnesses.join(", ")}). Is this intentional?`,
29587
+ hint: "Answer 'yes' if you want multiple harnesses to work together. Answer 'no' if this is legacy configuration."
29588
+ });
29589
+ }
29590
+ }
29591
+ if (classification.archetype === "intentional-hybrid") {
29592
+ const pathToHarness = new Map(records.map((r)=>[
29593
+ r.path,
29594
+ r.harness
29595
+ ]));
29596
+ const hasCrossHarnessImports = records.some((r)=>r.edges.some((e)=>!e.malformed && e.type === "hard-import" && typeof e.direction.to === "string" && pathToHarness.has(e.direction.to) && pathToHarness.get(e.direction.to) !== r.harness));
29597
+ if (hasCrossHarnessImports) {
29598
+ questions.push({
29599
+ id: "hard-import-intentional",
29600
+ question: "Claude hard-imports (@path) are referencing instruction files from other harnesses. Is this intentional?",
29601
+ hint: "Hard imports cause Claude to inline the file contents at runtime."
29602
+ });
29603
+ }
29604
+ }
29605
+ return questions;
29606
+ }
29607
+
29608
+ ;// CONCATENATED MODULE: ../doctor/src/use-cases/evaluate-engine.ts
29609
+ function makeId(criterionId, targetId) {
29610
+ return `${criterionId}:${targetId}`;
29611
+ }
29612
+ function checkFileExists(criterion, args, ctx) {
29613
+ const { harness, paths } = args;
29614
+ const anyMatch = ctx.records.some((r)=>paths.some((p)=>r.path === p || r.path.startsWith(p.endsWith("/") ? p : `${p}/`)));
29615
+ if (!anyMatch) {
29616
+ return [
29617
+ {
29618
+ id: makeId(criterion.id, `${harness}:all`),
29619
+ criterionId: criterion.id,
29620
+ targetId: `${harness}:all`,
29621
+ severity: criterion.severity,
29622
+ category: criterion.category,
29623
+ classification: criterion.classification,
29624
+ intentGated: false,
29625
+ assumedIntent: false,
29626
+ description: criterion.description
29627
+ }
29628
+ ];
29629
+ }
29630
+ return [];
29631
+ }
29632
+ function checkConstructPresent(criterion, ctx) {
29633
+ const hasRecords = ctx.records.length > 0;
29634
+ const hasIntent = ctx.intent !== null;
29635
+ if (hasRecords && !hasIntent) {
29636
+ return [
29637
+ {
29638
+ id: makeId(criterion.id, "all:intent"),
29639
+ criterionId: criterion.id,
29640
+ targetId: "all:intent",
29641
+ severity: criterion.severity,
29642
+ category: criterion.category,
29643
+ classification: criterion.classification,
29644
+ intentGated: false,
29645
+ assumedIntent: true,
29646
+ description: criterion.description
29647
+ }
29648
+ ];
29649
+ }
29650
+ return [];
29651
+ }
29652
+ function checkEdgePresent(criterion, args, ctx) {
29653
+ const fromRecords = ctx.records.filter((r)=>r.harness === args.fromHarness);
29654
+ const hasMalformedEdges = fromRecords.some((r)=>r.edges.some((e)=>e.malformed && (args.edgeType === undefined || e.type === args.edgeType)));
29655
+ if (hasMalformedEdges) {
29656
+ return [
29657
+ {
29658
+ id: makeId(criterion.id, `${args.fromHarness}:malformed`),
29659
+ criterionId: criterion.id,
29660
+ targetId: `${args.fromHarness}:malformed`,
29661
+ severity: criterion.severity,
29662
+ category: criterion.category,
29663
+ classification: criterion.classification,
29664
+ intentGated: false,
29665
+ assumedIntent: false,
29666
+ description: criterion.description
29667
+ }
29668
+ ];
29669
+ }
29670
+ return [];
29671
+ }
29672
+ function checkEdgeDirection(criterion, args, ctx) {
29673
+ const fromRecords = ctx.records.filter((r)=>r.harness === args.fromHarness);
29674
+ const toRecords = ctx.records.filter((r)=>r.harness === args.toHarness);
29675
+ const toPaths = new Set(toRecords.map((r)=>r.path));
29676
+ const hasDirectedEdge = fromRecords.some((r)=>r.edges.some((e)=>!e.malformed && (args.edgeType === undefined || e.type === args.edgeType) && typeof e.direction.to === "string" && toPaths.has(e.direction.to)));
29677
+ if (!hasDirectedEdge) {
29678
+ return [
29679
+ {
29680
+ id: makeId(criterion.id, `${args.fromHarness}:${args.toHarness}`),
29681
+ criterionId: criterion.id,
29682
+ targetId: `${args.fromHarness}:${args.toHarness}`,
29683
+ severity: criterion.severity,
29684
+ category: criterion.category,
29685
+ classification: criterion.classification,
29686
+ intentGated: false,
29687
+ assumedIntent: false,
29688
+ description: criterion.description
29689
+ }
29690
+ ];
29691
+ }
29692
+ return [];
29693
+ }
29694
+ function checkArchetypeIs(criterion, ctx) {
29695
+ return [
29696
+ {
29697
+ id: makeId(criterion.id, `all:${ctx.classification.archetype}`),
29698
+ criterionId: criterion.id,
29699
+ targetId: `all:${ctx.classification.archetype}`,
29700
+ severity: criterion.severity,
29701
+ category: criterion.category,
29702
+ classification: criterion.classification,
29703
+ intentGated: false,
29704
+ assumedIntent: false,
29705
+ description: criterion.description
29706
+ }
29707
+ ];
29708
+ }
29709
+ function checkEdgeDirectionExists(criterion, args, ctx) {
29710
+ const fromRecords = ctx.records.filter((r)=>r.harness === args.fromHarness);
29711
+ const toRecords = ctx.records.filter((r)=>r.harness === args.toHarness);
29712
+ const toPaths = new Set(toRecords.map((r)=>r.path));
29713
+ const hasEdge = fromRecords.some((r)=>r.edges.some((e)=>!e.malformed && (args.edgeType === undefined || e.type === args.edgeType) && typeof e.direction.to === "string" && toPaths.has(e.direction.to)));
29714
+ if (hasEdge) {
29715
+ return [
29716
+ {
29717
+ id: makeId(criterion.id, `${args.fromHarness}:${args.toHarness}`),
29718
+ criterionId: criterion.id,
29719
+ targetId: `${args.fromHarness}:${args.toHarness}`,
29720
+ severity: criterion.severity,
29721
+ category: criterion.category,
29722
+ classification: criterion.classification,
29723
+ intentGated: false,
29724
+ assumedIntent: false,
29725
+ description: criterion.description
29726
+ }
29727
+ ];
29728
+ }
29729
+ return [];
29730
+ }
29731
+ function checkCapabilityDeclared(criterion, ctx) {
29732
+ if (!criterion.capability || ctx.intent === null) return [];
29733
+ const isDeclared = ctx.intent.desiredCapabilities.includes(criterion.capability);
29734
+ if (!isDeclared) {
29735
+ return [
29736
+ {
29737
+ id: makeId(criterion.id, `all:${criterion.capability}`),
29738
+ criterionId: criterion.id,
29739
+ targetId: `all:${criterion.capability}`,
29740
+ severity: criterion.severity,
29741
+ category: criterion.category,
29742
+ classification: criterion.classification,
29743
+ intentGated: true,
29744
+ assumedIntent: false,
29745
+ description: criterion.description
29746
+ }
29747
+ ];
29748
+ }
29749
+ return [];
29750
+ }
29751
+ function evaluate(criteria, ctx) {
29752
+ const findings = [];
29753
+ for (const criterion of criteria){
29754
+ if (criterion.appliesToArchetype && criterion.appliesToArchetype !== "all" && ctx.classification.archetype !== criterion.appliesToArchetype) {
29755
+ continue;
29756
+ }
29757
+ const harnesses = new Set(ctx.records.map((r)=>r.harness));
29758
+ if (criterion.appliesToHarness !== "all" && !harnesses.has(criterion.appliesToHarness)) {
29759
+ continue;
29760
+ }
29761
+ switch(criterion.checkMethod){
29762
+ case "inventory.fileExists":
29763
+ {
29764
+ findings.push(...checkFileExists(criterion, criterion.checkArgs, ctx));
29765
+ break;
29766
+ }
29767
+ case "inventory.constructPresent":
29768
+ {
29769
+ findings.push(...checkConstructPresent(criterion, ctx));
29770
+ break;
29771
+ }
29772
+ case "inventory.edgePresent":
29773
+ {
29774
+ findings.push(...checkEdgePresent(criterion, criterion.checkArgs, ctx));
29775
+ break;
29776
+ }
29777
+ case "inventory.edgeDirection":
29778
+ {
29779
+ findings.push(...checkEdgeDirection(criterion, criterion.checkArgs, ctx));
29780
+ break;
29781
+ }
29782
+ case "inventory.archetypeIs":
29783
+ {
29784
+ findings.push(...checkArchetypeIs(criterion, ctx));
29785
+ break;
29786
+ }
29787
+ case "inventory.edgeDirectionExists":
29788
+ {
29789
+ findings.push(...checkEdgeDirectionExists(criterion, criterion.checkArgs, ctx));
29790
+ break;
29791
+ }
29792
+ case "intent.capabilityDeclared":
29793
+ {
29794
+ findings.push(...checkCapabilityDeclared(criterion, ctx));
29795
+ break;
29796
+ }
29797
+ default:
29798
+ break;
29799
+ }
29800
+ }
29801
+ return findings;
29802
+ }
29803
+
29804
+ ;// CONCATENATED MODULE: ../doctor/src/index.ts
29805
+
29806
+
29807
+
29808
+
29809
+
29810
+
29811
+
29812
+
29813
+
29814
+
29815
+
29816
+ ;// CONCATENATED MODULE: ./src/commands/doctor.ts
29817
+
29818
+
29819
+
29820
+
29821
+ const doctorCommand = defineCommand({
29822
+ meta: {
29823
+ name: "doctor",
29824
+ description: "Diagnose agentic configuration across harnesses: inventory, classify, and evaluate preconditions"
29825
+ },
29826
+ args: {
29827
+ summary: {
29828
+ type: "boolean",
29829
+ description: "Show archetype classification only — skip evaluation",
29830
+ default: false
29831
+ },
29832
+ format: {
29833
+ type: "string",
29834
+ description: "Output format: human (default) or json",
29835
+ default: "human"
29836
+ },
29837
+ ci: {
29838
+ type: "boolean",
29839
+ description: "Force non-interactive mode",
29840
+ default: false
29841
+ }
29842
+ },
29843
+ async run ({ args }) {
29844
+ const logger = dist_createConsola();
29845
+ const repoPath = (0,external_node_path_.resolve)(process.cwd());
29846
+ const format = args.format === "json" ? "json" : "human";
29847
+ const records = await scanRepository(repoPath);
29848
+ const classification = classifyArchetype(records);
29849
+ const summary = formatSummary(records, classification);
29850
+ // Load wisdom graph for snapshotRef (gracefully degrade if unavailable)
29851
+ const wisdomDir = (0,external_node_path_.resolve)(repoPath, "wisdom");
29852
+ let snapshotRef;
29853
+ try {
29854
+ const wisdomClient = createWisdomClient(wisdomDir);
29855
+ const graph = await wisdomClient.getGraph();
29856
+ snapshotRef = graph.snapshotRef;
29857
+ } catch (err) {
29858
+ if (!(err instanceof WisdomUnavailableError)) throw err;
29859
+ logger.debug("Wisdom graph unavailable — snapshotRef will be omitted");
29860
+ }
29861
+ if (args.summary) {
29862
+ if (format === "json") {
29863
+ process.stdout.write(`${JSON.stringify({
29864
+ archetype: summary.archetype,
29865
+ dominanceScore: summary.dominanceScore,
29866
+ totalRecords: summary.totalRecords,
29867
+ harnessBreakdown: summary.harnessBreakdown,
29868
+ crossHarnessEdges: summary.crossHarnessEdges,
29869
+ inventory: toInventoryItems(records)
29870
+ }, null, 2)}\n`);
29871
+ } else {
29872
+ renderHuman(summary, [], logger, snapshotRef);
29873
+ }
29874
+ return;
29875
+ }
29876
+ if (classification.archetype === "none") {
29877
+ if (format === "json") {
29878
+ process.stdout.write(`${JSON.stringify(toJson(summary, [], records, snapshotRef), null, 2)}\n`);
29879
+ } else {
29880
+ logger.info("No agentic constructs found in the repository.");
29881
+ }
29882
+ return;
29883
+ }
29884
+ const intentResult = await readIntentArtifact(repoPath);
29885
+ let intent = intentResult.found ? intentResult.artifact : null;
29886
+ // Interactive elicitation when no intent artifact and running in a TTY
29887
+ const isInteractive = !args.ci && process.stdin.isTTY === true;
29888
+ if (intent === null && isInteractive) {
29889
+ const questions = detectAmbiguities(records, classification);
29890
+ for (const q of questions){
29891
+ logger.info(`\n${q.hint}`);
29892
+ const answer = await logger.prompt(q.question, {
29893
+ type: "confirm",
29894
+ initial: false
29895
+ });
29896
+ if (answer) {
29897
+ logger.info(" Noted.");
29898
+ }
29899
+ }
29900
+ // After interactive prompts, re-read in case user pre-created the artifact
29901
+ const retried = await readIntentArtifact(repoPath);
29902
+ if (retried.found) intent = retried.artifact;
29903
+ }
29904
+ const findings = evaluate(CRITERIA, {
29905
+ records,
29906
+ classification,
29907
+ intent
29908
+ });
29909
+ if (format === "json") {
29910
+ const report = toJson(summary, findings, records, snapshotRef);
29911
+ process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
29912
+ } else {
29913
+ renderHuman(summary, findings, logger, snapshotRef);
29914
+ }
29915
+ if (hasBlockingFindings(findings)) {
29916
+ process.exitCode = 1;
29917
+ }
29918
+ }
29919
+ });
29920
+
27903
29921
  ;// CONCATENATED MODULE: ../core/src/use-cases/init-copilot-setup-workflow.ts
27904
29922
  /**
27905
29923
  * Use case for initializing a Copilot Setup Steps workflow in a new project.
@@ -48660,7 +50678,7 @@ const remark = unified().use(remarkParse).use(remarkStringify).freeze()
48660
50678
  */
48661
50679
 
48662
50680
 
48663
- /** Zod schema for a rule config map: rule IDs validated with regex to prevent prototype pollution */ const RuleConfigMapSchema = record(schemas_string().regex(/^[a-z]+\/[a-z]+(?:-[a-z]+)*$/), schemas_enum([
50681
+ /** Zod schema for a rule config map: rule IDs validated with regex to prevent prototype pollution */ const RuleConfigMapSchema = schemas_record(schemas_string().regex(/^[a-z]+\/[a-z]+(?:-[a-z]+)*$/), schemas_enum([
48664
50682
  "error",
48665
50683
  "warn",
48666
50684
  "off"
@@ -49500,7 +51518,7 @@ const MAX_HOOKS_PER_EVENT = 100;
49500
51518
  powershell: schemas_string().min(1, "Hook PowerShell command must not be empty").optional(),
49501
51519
  cwd: schemas_string().optional(),
49502
51520
  timeoutSec: schemas_number().positive().optional(),
49503
- env: record(schemas_string().regex(ENV_KEY_PATTERN, "Hook env key must be a valid identifier (no prototype-polluting keys)"), schemas_string()).optional()
51521
+ env: schemas_record(schemas_string().regex(ENV_KEY_PATTERN, "Hook env key must be a valid identifier (no prototype-polluting keys)"), schemas_string()).optional()
49504
51522
  }).strict().refine((data)=>Boolean(data.bash) || Boolean(data.powershell), {
49505
51523
  message: "At least one of 'bash' or 'powershell' must be provided and non-empty"
49506
51524
  });
@@ -49688,7 +51706,7 @@ const AgentSkillFrontmatterSchema = schemas_object({
49688
51706
  }),
49689
51707
  license: schemas_string().optional(),
49690
51708
  compatibility: schemas_string().max(500, "Compatibility must be 500 characters or fewer").optional(),
49691
- metadata: record(schemas_string(), schemas_string()).optional(),
51709
+ metadata: schemas_record(schemas_string(), schemas_string()).optional(),
49692
51710
  "allowed-tools": schemas_string().optional(),
49693
51711
  "argument-hint": schemas_string().optional()
49694
51712
  });
@@ -50713,6 +52731,7 @@ const skillArgs = {
50713
52731
 
50714
52732
 
50715
52733
 
52734
+
50716
52735
  // Gateways
50717
52736
  const lessonFileGateway = createLessonFileGateway();
50718
52737
  const initHooksConfigGateway = createInitHooksConfigGateway();
@@ -50738,7 +52757,8 @@ const src_main = defineCommand({
50738
52757
  "copilot-setup": copilotSetupCommand,
50739
52758
  context: contextCmd,
50740
52759
  "init-hooks": initHooksCmd,
50741
- capture: captureCommand
52760
+ capture: captureCommand,
52761
+ doctor: doctorCommand
50742
52762
  }
50743
52763
  });
50744
52764
  runMain(src_main);
@@ -59601,4 +61621,4 @@ if (installedChunkData !== 0) { // 0 means "already installed".'
59601
61621
  // module factories are used so entry inlining is disabled
59602
61622
  // startup
59603
61623
  // Load entry module and return exports
59604
- var __webpack_exports__ = __webpack_require__(455);
61624
+ var __webpack_exports__ = __webpack_require__(889);