@lousy-agents/cli 5.15.7 → 5.16.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
+ 149(__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,
@@ -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,1337 @@ 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/formatters/json-formatter.ts
28406
+ function toJson(summary, findings, snapshotRef) {
28407
+ return {
28408
+ archetype: summary.archetype,
28409
+ dominanceScore: summary.dominanceScore,
28410
+ totalRecords: summary.totalRecords,
28411
+ harnessBreakdown: summary.harnessBreakdown,
28412
+ crossHarnessEdges: summary.crossHarnessEdges,
28413
+ findings,
28414
+ ...snapshotRef !== undefined ? {
28415
+ snapshotRef
28416
+ } : {}
28417
+ };
28418
+ }
28419
+
28420
+ ;// CONCATENATED MODULE: ../doctor/src/formatters/summary-formatter.ts
28421
+ const ARCHETYPE_DESCRIPTIONS = {
28422
+ pure: "Single-harness configuration. One AI coding assistant dominates the repository.",
28423
+ "intentional-hybrid": "Multi-harness configuration with cross-harness references. Harnesses deliberately share context.",
28424
+ "canonical-contract": "Shared AGENTS.md contract. All harnesses read from a single canonical instruction file.",
28425
+ "accidental-sprawl": "Multiple harnesses configured without cross-referencing. May indicate legacy or uncoordinated setup.",
28426
+ none: "No agentic configuration detected.",
28427
+ ambiguous: "Configuration does not fit a single archetype cleanly."
28428
+ };
28429
+ function formatSummary(records, classification) {
28430
+ const counts = new Map();
28431
+ for (const r of records){
28432
+ counts.set(r.harness, (counts.get(r.harness) ?? 0) + 1);
28433
+ }
28434
+ const harnessBreakdown = Array.from(counts.entries()).map(([harness, count])=>({
28435
+ harness,
28436
+ count
28437
+ })).sort((a, b)=>a.harness.localeCompare(b.harness));
28438
+ let crossHarnessEdges = 0;
28439
+ const harnessByPath = new Map();
28440
+ for (const r of records){
28441
+ harnessByPath.set(r.path, r.harness);
28442
+ }
28443
+ for (const r of records){
28444
+ for (const edge of r.edges){
28445
+ if (!edge.malformed && edge.type !== "glob-binding" && typeof edge.direction.to === "string") {
28446
+ const targetHarness = harnessByPath.get(edge.direction.to);
28447
+ if (targetHarness && targetHarness !== r.harness) {
28448
+ crossHarnessEdges++;
28449
+ }
28450
+ }
28451
+ }
28452
+ }
28453
+ return {
28454
+ archetype: classification.archetype,
28455
+ archetypeDescription: ARCHETYPE_DESCRIPTIONS[classification.archetype],
28456
+ dominanceScore: classification.dominanceScore,
28457
+ totalRecords: records.length,
28458
+ harnessBreakdown,
28459
+ crossHarnessEdges
28460
+ };
28461
+ }
28462
+
28463
+ ;// CONCATENATED MODULE: ../doctor/src/gateways/intent-artifact.ts
28464
+
28465
+
28466
+
28467
+
28468
+ const MAX_INTENT_BYTES = 65_536;
28469
+ const ARTIFACT_FILENAME = "intent.json";
28470
+ const ARTIFACT_DIR = ".agentic-doctor";
28471
+ const DeclaredIntentSchema = schemas_object({
28472
+ targetHarnesses: schemas_array(schemas_enum([
28473
+ "claude",
28474
+ "copilot",
28475
+ "codex",
28476
+ "antigravity",
28477
+ "hermes",
28478
+ "crush",
28479
+ "pi",
28480
+ "shared"
28481
+ ])),
28482
+ desiredCapabilities: schemas_array(schemas_string()),
28483
+ confirmedAnswers: schemas_record(schemas_string(), unknown()),
28484
+ intentSource: schemas_enum([
28485
+ "interactive",
28486
+ "ci-assumed",
28487
+ "pre-committed"
28488
+ ]),
28489
+ snapshotRef: schemas_string().optional()
28490
+ });
28491
+ async function readIntentArtifact(repoRoot) {
28492
+ const artifactRelPath = `${ARTIFACT_DIR}/${ARTIFACT_FILENAME}`;
28493
+ const artifactPath = (0,external_node_path_.resolve)(repoRoot, ARTIFACT_DIR, ARTIFACT_FILENAME);
28494
+ let raw;
28495
+ try {
28496
+ raw = await file_system_utils_readTextWithinRoot(repoRoot, artifactRelPath, MAX_INTENT_BYTES);
28497
+ } catch {
28498
+ return {
28499
+ found: false
28500
+ };
28501
+ }
28502
+ let parsed;
28503
+ try {
28504
+ parsed = JSON.parse(raw);
28505
+ } catch {
28506
+ return {
28507
+ found: false
28508
+ };
28509
+ }
28510
+ const result = DeclaredIntentSchema.safeParse(parsed);
28511
+ if (!result.success) {
28512
+ return {
28513
+ found: false
28514
+ };
28515
+ }
28516
+ return {
28517
+ found: true,
28518
+ artifact: result.data,
28519
+ path: artifactPath
28520
+ };
28521
+ }
28522
+ async function writeIntentArtifact(repoRoot, artifact) {
28523
+ const artifactPath = await resolveSafePath(repoRoot, `${ARTIFACT_DIR}/${ARTIFACT_FILENAME}`);
28524
+ await mkdir(dirname(artifactPath), {
28525
+ recursive: true
28526
+ });
28527
+ await writeFile(artifactPath, JSON.stringify(artifact, null, 2), "utf-8");
28528
+ return artifactPath;
28529
+ }
28530
+ function buildDefaultIntent(harnesses) {
28531
+ return {
28532
+ targetHarnesses: harnesses.filter((h)=>h !== "shared"),
28533
+ desiredCapabilities: [],
28534
+ confirmedAnswers: {},
28535
+ intentSource: "ci-assumed"
28536
+ };
28537
+ }
28538
+
28539
+ ;// CONCATENATED MODULE: ../doctor/src/entities/harness-footprints.ts
28540
+ const HARNESS_NAMES = [
28541
+ "claude",
28542
+ "copilot",
28543
+ "codex",
28544
+ "antigravity",
28545
+ "hermes",
28546
+ "crush",
28547
+ "pi"
28548
+ ];
28549
+ const HARNESS_FOOTPRINTS = {
28550
+ claude: {
28551
+ name: "claude",
28552
+ status: "verified",
28553
+ readsAgentsMd: false,
28554
+ walkBoundary: "git-root",
28555
+ conventionFiles: [
28556
+ "CLAUDE.md"
28557
+ ],
28558
+ conventionDirs: [
28559
+ ".claude/"
28560
+ ],
28561
+ primaryIndicators: [
28562
+ "CLAUDE.md",
28563
+ ".claude/"
28564
+ ],
28565
+ crossRefMechanisms: [
28566
+ {
28567
+ edgeType: "hard-import",
28568
+ description: "Claude @path/to/file syntax at line start inlines the referenced file"
28569
+ },
28570
+ {
28571
+ edgeType: "soft-reference",
28572
+ description: "Frontmatter see:/references:/requires: keys or markdown links to sibling instruction files"
28573
+ }
28574
+ ]
28575
+ },
28576
+ copilot: {
28577
+ name: "copilot",
28578
+ status: "verified",
28579
+ readsAgentsMd: false,
28580
+ walkBoundary: "project",
28581
+ conventionFiles: [
28582
+ ".github/copilot-instructions.md"
28583
+ ],
28584
+ conventionDirs: [
28585
+ ".github/instructions/"
28586
+ ],
28587
+ primaryIndicators: [
28588
+ ".github/copilot-instructions.md",
28589
+ ".github/instructions/"
28590
+ ],
28591
+ crossRefMechanisms: [
28592
+ {
28593
+ edgeType: "glob-binding",
28594
+ description: "Copilot applyTo frontmatter field scopes an instruction to matching source files"
28595
+ },
28596
+ {
28597
+ edgeType: "soft-reference",
28598
+ description: "Frontmatter see:/references:/requires: keys or markdown links to sibling instruction files"
28599
+ }
28600
+ ]
28601
+ },
28602
+ codex: {
28603
+ name: "codex",
28604
+ status: "verified",
28605
+ readsAgentsMd: true,
28606
+ walkBoundary: "git-root",
28607
+ conventionFiles: [
28608
+ "AGENTS.md",
28609
+ "AGENTS.override.md"
28610
+ ],
28611
+ conventionDirs: [
28612
+ ".codex/",
28613
+ ".agents/skills/",
28614
+ ".codex-plugin/"
28615
+ ],
28616
+ primaryIndicators: [
28617
+ "AGENTS.override.md",
28618
+ ".codex/",
28619
+ ".agents/skills/",
28620
+ ".codex-plugin/"
28621
+ ],
28622
+ crossRefMechanisms: [
28623
+ {
28624
+ edgeType: "soft-reference",
28625
+ description: "Frontmatter see:/references:/requires: keys or markdown links to sibling instruction files"
28626
+ }
28627
+ ]
28628
+ },
28629
+ antigravity: {
28630
+ name: "antigravity",
28631
+ status: "needs-verification",
28632
+ readsAgentsMd: false,
28633
+ walkBoundary: null,
28634
+ conventionFiles: [
28635
+ "GEMINI.md"
28636
+ ],
28637
+ conventionDirs: [
28638
+ ".gemini/"
28639
+ ],
28640
+ primaryIndicators: [
28641
+ "GEMINI.md",
28642
+ ".gemini/"
28643
+ ],
28644
+ crossRefMechanisms: [
28645
+ {
28646
+ edgeType: "soft-reference",
28647
+ description: "Frontmatter see:/references:/requires: keys or markdown links to sibling instruction files"
28648
+ }
28649
+ ]
28650
+ },
28651
+ hermes: {
28652
+ name: "hermes",
28653
+ status: "verified",
28654
+ readsAgentsMd: true,
28655
+ walkBoundary: "git-root",
28656
+ conventionFiles: [
28657
+ ".hermes.md",
28658
+ "HERMES.md",
28659
+ "AGENTS.md",
28660
+ "agents.md",
28661
+ "CLAUDE.md",
28662
+ "claude.md",
28663
+ ".cursorrules",
28664
+ "SOUL.md"
28665
+ ],
28666
+ conventionDirs: [],
28667
+ primaryIndicators: [
28668
+ ".hermes.md",
28669
+ "HERMES.md",
28670
+ "SOUL.md"
28671
+ ],
28672
+ crossRefMechanisms: [
28673
+ {
28674
+ edgeType: "soft-reference",
28675
+ description: "Frontmatter see:/references:/requires: keys or markdown links to sibling instruction files"
28676
+ }
28677
+ ]
28678
+ },
28679
+ crush: {
28680
+ name: "crush",
28681
+ status: "verified",
28682
+ readsAgentsMd: true,
28683
+ walkBoundary: null,
28684
+ conventionFiles: [
28685
+ "AGENTS.md",
28686
+ "crush.json"
28687
+ ],
28688
+ conventionDirs: [
28689
+ ".crush/",
28690
+ ".agents/skills/"
28691
+ ],
28692
+ primaryIndicators: [
28693
+ "crush.json",
28694
+ ".crush/"
28695
+ ],
28696
+ crossRefMechanisms: [
28697
+ {
28698
+ edgeType: "soft-reference",
28699
+ description: "Frontmatter see:/references:/requires: keys or markdown links to sibling instruction files"
28700
+ }
28701
+ ]
28702
+ },
28703
+ pi: {
28704
+ name: "pi",
28705
+ status: "verified",
28706
+ readsAgentsMd: true,
28707
+ walkBoundary: "filesystem-root",
28708
+ conventionFiles: [
28709
+ "AGENTS.md",
28710
+ "AGENTS.MD",
28711
+ "CLAUDE.md",
28712
+ "CLAUDE.MD"
28713
+ ],
28714
+ conventionDirs: [
28715
+ ".pi/",
28716
+ ".pi/skills/",
28717
+ ".pi/prompts/"
28718
+ ],
28719
+ primaryIndicators: [
28720
+ ".pi/",
28721
+ ".pi/skills/",
28722
+ ".pi/prompts/"
28723
+ ],
28724
+ crossRefMechanisms: [
28725
+ {
28726
+ edgeType: "soft-reference",
28727
+ description: "Frontmatter see:/references:/requires: keys or markdown links to sibling instruction files"
28728
+ }
28729
+ ]
28730
+ },
28731
+ shared: {
28732
+ name: "shared",
28733
+ status: "verified",
28734
+ readsAgentsMd: true,
28735
+ walkBoundary: null,
28736
+ conventionFiles: [
28737
+ "AGENTS.md",
28738
+ "AGENTS.MD",
28739
+ "agents.md"
28740
+ ],
28741
+ conventionDirs: [],
28742
+ primaryIndicators: [
28743
+ "AGENTS.md",
28744
+ "AGENTS.MD",
28745
+ "agents.md"
28746
+ ],
28747
+ crossRefMechanisms: []
28748
+ }
28749
+ };
28750
+ function getFootprint(harness) {
28751
+ return HARNESS_FOOTPRINTS[harness];
28752
+ }
28753
+ function matchesPrimaryIndicator(repoRelativePath, indicators) {
28754
+ const normalized = repoRelativePath.replace(/\\/g, "/");
28755
+ return indicators.some((indicator)=>{
28756
+ if (indicator.endsWith("/")) {
28757
+ return normalized === indicator.slice(0, -1) || normalized.startsWith(indicator);
28758
+ }
28759
+ return normalized === indicator;
28760
+ });
28761
+ }
28762
+
28763
+ ;// CONCATENATED MODULE: ../doctor/src/lib/edge-parser.ts
28764
+
28765
+ const HARD_IMPORT_GLOBAL_RE = /^@([^\s@][^\s]*)/gm;
28766
+ const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---/;
28767
+ const MARKDOWN_LINK_RE = /\[(?:[^\]]*)\]\(([^)]+)\)/g;
28768
+ const SOFT_REF_FRONTMATTER_KEYS = [
28769
+ "see",
28770
+ "references",
28771
+ "requires"
28772
+ ];
28773
+ function edge_parser_parseFrontmatter(content) {
28774
+ const match = FRONTMATTER_RE.exec(content);
28775
+ if (!match) return null;
28776
+ try {
28777
+ const parsed = (0,dist/* .parse */.qg)(match[1]);
28778
+ return typeof parsed === "object" && parsed !== null ? parsed : null;
28779
+ } catch {
28780
+ return null;
28781
+ }
28782
+ }
28783
+ function contentWithoutFrontmatter(content) {
28784
+ return content.replace(FRONTMATTER_RE, "");
28785
+ }
28786
+ function toStringArray(value) {
28787
+ if (typeof value === "string") return [
28788
+ value
28789
+ ];
28790
+ if (Array.isArray(value)) return value.filter((v)=>typeof v === "string");
28791
+ return [];
28792
+ }
28793
+ function isInstructionFilePath(path) {
28794
+ return path.endsWith(".md") || path.endsWith(".instructions.md") || path.endsWith(".mdc");
28795
+ }
28796
+ function parseRawEdges(content) {
28797
+ const edges = [];
28798
+ const fm = edge_parser_parseFrontmatter(content);
28799
+ if (fm) {
28800
+ if (fm.applyTo) {
28801
+ for (const glob of toStringArray(fm.applyTo)){
28802
+ edges.push({
28803
+ type: "glob-binding",
28804
+ rawTarget: glob
28805
+ });
28806
+ }
28807
+ }
28808
+ for (const key of SOFT_REF_FRONTMATTER_KEYS){
28809
+ const value = fm[key];
28810
+ if (value) {
28811
+ for (const ref of toStringArray(value)){
28812
+ if (isInstructionFilePath(ref)) {
28813
+ edges.push({
28814
+ type: "soft-reference",
28815
+ rawTarget: ref
28816
+ });
28817
+ }
28818
+ }
28819
+ }
28820
+ }
28821
+ }
28822
+ const body = fm ? contentWithoutFrontmatter(content) : content;
28823
+ for (const match of body.matchAll(HARD_IMPORT_GLOBAL_RE)){
28824
+ const target = match[1];
28825
+ if (target.includes("/")) {
28826
+ edges.push({
28827
+ type: "hard-import",
28828
+ rawTarget: target
28829
+ });
28830
+ }
28831
+ }
28832
+ for (const match of body.matchAll(MARKDOWN_LINK_RE)){
28833
+ const href = match[1];
28834
+ if (!href.startsWith("http://") && !href.startsWith("https://") && isInstructionFilePath(href)) {
28835
+ edges.push({
28836
+ type: "soft-reference",
28837
+ rawTarget: href
28838
+ });
28839
+ }
28840
+ }
28841
+ return edges;
28842
+ }
28843
+
28844
+ ;// CONCATENATED MODULE: ../doctor/src/gateways/scanner.ts
28845
+
28846
+
28847
+
28848
+
28849
+ const MAX_FILE_BYTES = 1_048_576;
28850
+ const SCANNABLE_EXTENSIONS = new Set([
28851
+ ".md",
28852
+ ".mdc",
28853
+ ".json",
28854
+ ".yaml",
28855
+ ".yml",
28856
+ ".toml"
28857
+ ]);
28858
+ // Extensionless files that are known harness convention artifacts
28859
+ const SCANNABLE_EXTENSIONLESS = new Set([
28860
+ ".cursorrules"
28861
+ ]);
28862
+ const ALL_HARNESS_NAMES = [
28863
+ ...HARNESS_NAMES,
28864
+ "shared"
28865
+ ];
28866
+ function isScannableFile(name) {
28867
+ if (SCANNABLE_EXTENSIONLESS.has(name)) return true;
28868
+ const dot = name.lastIndexOf(".");
28869
+ if (dot === -1) return false;
28870
+ return SCANNABLE_EXTENSIONS.has(name.slice(dot));
28871
+ }
28872
+ function isMarkdownFile(name) {
28873
+ return name.endsWith(".md") || name.endsWith(".mdc");
28874
+ }
28875
+ function determineConstructType(relPath) {
28876
+ if (relPath.startsWith(".agents/skills/") || relPath.startsWith(".pi/skills/") || relPath.startsWith(".pi/prompts/")) {
28877
+ return "skill";
28878
+ }
28879
+ if (relPath.startsWith(".codex-plugin/")) {
28880
+ return "plugin";
28881
+ }
28882
+ if (relPath.startsWith(".claude/hooks/")) {
28883
+ return "hook";
28884
+ }
28885
+ if (relPath.startsWith(".claude/commands/")) {
28886
+ return "agent";
28887
+ }
28888
+ return "instruction";
28889
+ }
28890
+ function determineHarness(repoRelativePath) {
28891
+ const matching = ALL_HARNESS_NAMES.filter((harness)=>matchesPrimaryIndicator(repoRelativePath, HARNESS_FOOTPRINTS[harness].primaryIndicators));
28892
+ if (matching.length === 0) return null;
28893
+ if (matching.length >= 2) return "shared";
28894
+ return matching[0];
28895
+ }
28896
+ function resolveEdge(rawTarget, type, sourceAbsPath, repoRoot, existingAbsPaths) {
28897
+ const from = (0,external_node_path_.relative)(repoRoot, sourceAbsPath).replace(/\\/g, "/");
28898
+ if (type === "glob-binding") {
28899
+ return {
28900
+ type,
28901
+ direction: {
28902
+ from,
28903
+ to: rawTarget
28904
+ },
28905
+ target: rawTarget,
28906
+ malformed: false
28907
+ };
28908
+ }
28909
+ const sourceDir = (0,external_node_path_.dirname)(sourceAbsPath);
28910
+ const resolvedAbs = (0,external_node_path_.resolve)(sourceDir, rawTarget);
28911
+ const absoluteRoot = (0,external_node_path_.resolve)(repoRoot);
28912
+ const isWithinRoot = resolvedAbs === absoluteRoot || resolvedAbs.startsWith(`${absoluteRoot}${external_node_path_.sep}`);
28913
+ if (!isWithinRoot) {
28914
+ return {
28915
+ type,
28916
+ direction: {
28917
+ from,
28918
+ to: rawTarget
28919
+ },
28920
+ target: rawTarget,
28921
+ malformed: true,
28922
+ reason: "path-traversal"
28923
+ };
28924
+ }
28925
+ const relativeTarget = (0,external_node_path_.relative)(absoluteRoot, resolvedAbs).replace(/\\/g, "/");
28926
+ if (!existingAbsPaths.has(resolvedAbs)) {
28927
+ return {
28928
+ type,
28929
+ direction: {
28930
+ from,
28931
+ to: relativeTarget
28932
+ },
28933
+ target: relativeTarget,
28934
+ malformed: true,
28935
+ reason: "missing-target"
28936
+ };
28937
+ }
28938
+ return {
28939
+ type,
28940
+ direction: {
28941
+ from,
28942
+ to: relativeTarget
28943
+ },
28944
+ target: relativeTarget,
28945
+ malformed: false
28946
+ };
28947
+ }
28948
+ async function collectFiles(repoRoot, relDir, collected, depth = 0) {
28949
+ if (depth > 10) return;
28950
+ let entries;
28951
+ try {
28952
+ entries = await file_system_utils_listDirectoryWithinRoot(repoRoot, relDir);
28953
+ } catch {
28954
+ return;
28955
+ }
28956
+ entries.sort((a, b)=>a.name.localeCompare(b.name));
28957
+ for (const entry of entries){
28958
+ const entryRel = relDir ? `${relDir}/${entry.name}` : entry.name;
28959
+ const entryAbs = (0,external_node_path_.resolve)(repoRoot, entryRel);
28960
+ if (entry.isDirectory()) {
28961
+ if (entry.name === "node_modules" || entry.name === ".git" || entry.name === "dist") {
28962
+ continue;
28963
+ }
28964
+ await collectFiles(repoRoot, entryRel, collected, depth + 1);
28965
+ } else if (entry.isFile() && isScannableFile(entry.name)) {
28966
+ collected.push({
28967
+ absPath: entryAbs,
28968
+ relPath: entryRel
28969
+ });
28970
+ }
28971
+ }
28972
+ }
28973
+ async function scanRepository(repoRoot) {
28974
+ const absRoot = (0,external_node_path_.resolve)(repoRoot);
28975
+ const allFiles = [];
28976
+ await collectFiles(absRoot, "", allFiles);
28977
+ const existingAbsPaths = new Set(allFiles.map((f)=>f.absPath));
28978
+ const rawRecords = [];
28979
+ for (const { absPath, relPath } of allFiles){
28980
+ const harness = determineHarness(relPath);
28981
+ if (harness === null) continue;
28982
+ let content = null;
28983
+ if (isMarkdownFile(relPath)) {
28984
+ try {
28985
+ content = await file_system_utils_readTextWithinRoot(absRoot, relPath, MAX_FILE_BYTES);
28986
+ } catch {
28987
+ content = null;
28988
+ }
28989
+ }
28990
+ rawRecords.push({
28991
+ absPath,
28992
+ relPath,
28993
+ harness,
28994
+ content
28995
+ });
28996
+ }
28997
+ const records = rawRecords.map(({ absPath, relPath, harness, content })=>{
28998
+ const edges = [];
28999
+ if (content !== null) {
29000
+ const rawEdges = parseRawEdges(content);
29001
+ for (const raw of rawEdges){
29002
+ edges.push(resolveEdge(raw.rawTarget, raw.type, absPath, absRoot, existingAbsPaths));
29003
+ }
29004
+ }
29005
+ const id = harness === "shared" ? `shared:${relPath}` : `${harness}:${relPath}`;
29006
+ return {
29007
+ id,
29008
+ path: relPath,
29009
+ harness,
29010
+ constructType: determineConstructType(relPath),
29011
+ loadMechanism: "convention-loaded",
29012
+ edges
29013
+ };
29014
+ });
29015
+ const referencedPaths = new Set();
29016
+ for (const record of records){
29017
+ for (const edge of record.edges){
29018
+ if (!edge.malformed && edge.type !== "glob-binding" && typeof edge.direction.to === "string") {
29019
+ referencedPaths.add(edge.direction.to);
29020
+ }
29021
+ }
29022
+ }
29023
+ for (const record of records){
29024
+ if (referencedPaths.has(record.path)) {
29025
+ record.loadMechanism = "referenced";
29026
+ }
29027
+ }
29028
+ return records;
29029
+ }
29030
+
29031
+ ;// CONCATENATED MODULE: ../doctor/src/gateways/wisdom-client.ts
29032
+
29033
+
29034
+ const MAX_GRAPH_BYTES = 10_485_760;
29035
+ class WisdomUnavailableError extends Error {
29036
+ constructor(reason){
29037
+ super(`Wisdom graph unavailable: ${reason}`);
29038
+ this.name = "WisdomUnavailableError";
29039
+ }
29040
+ }
29041
+ const WisdomNodeSchema = schemas_object({
29042
+ id: schemas_string(),
29043
+ label: schemas_string().optional(),
29044
+ description: schemas_string().optional(),
29045
+ sourceFile: schemas_string().optional(),
29046
+ lineStart: schemas_number().int().min(1).optional().catch(undefined),
29047
+ lineEnd: schemas_number().int().min(1).optional().catch(undefined)
29048
+ }).passthrough();
29049
+ const WisdomGraphSchema = schemas_object({
29050
+ nodes: schemas_array(WisdomNodeSchema),
29051
+ edges: schemas_array(unknown()).default([]),
29052
+ snapshotRef: schemas_string().optional()
29053
+ });
29054
+ function createWisdomClient(wisdomDir) {
29055
+ async function getGraph() {
29056
+ let raw;
29057
+ try {
29058
+ raw = await file_system_utils_readTextWithinRoot(wisdomDir, "graphify-out/graph.json", MAX_GRAPH_BYTES);
29059
+ } catch {
29060
+ throw new WisdomUnavailableError("graph.json not found in wisdom directory — ensure the wisdom submodule is initialized");
29061
+ }
29062
+ let parsed;
29063
+ try {
29064
+ parsed = JSON.parse(raw);
29065
+ } catch {
29066
+ throw new WisdomUnavailableError("graph.json is not valid JSON");
29067
+ }
29068
+ try {
29069
+ return WisdomGraphSchema.parse(parsed);
29070
+ } catch {
29071
+ throw new WisdomUnavailableError("graph.json does not match expected schema (nodes must be an array with id strings)");
29072
+ }
29073
+ }
29074
+ async function findNodeById(nodeId) {
29075
+ const graph = await getGraph();
29076
+ return graph.nodes.find((n)=>n.id === nodeId) ?? null;
29077
+ }
29078
+ return {
29079
+ getGraph,
29080
+ findNodeById
29081
+ };
29082
+ }
29083
+
29084
+ ;// CONCATENATED MODULE: ../doctor/src/use-cases/harness-profile.ts
29085
+ const PURE_DOMINANCE_THRESHOLD = 0.8;
29086
+ function buildHarnessProfiles(records) {
29087
+ if (records.length === 0) return [];
29088
+ const data = new Map();
29089
+ for (const record of records){
29090
+ const existing = data.get(record.harness) ?? {
29091
+ records: 0,
29092
+ edges: 0
29093
+ };
29094
+ data.set(record.harness, {
29095
+ records: existing.records + 1,
29096
+ edges: existing.edges + record.edges.length
29097
+ });
29098
+ }
29099
+ const totalWeight = records.reduce((acc, r)=>acc + 1 + r.edges.length, 0);
29100
+ return Array.from(data.entries()).map(([harness, { records: recordCount, edges: edgeCount }])=>({
29101
+ harness,
29102
+ recordCount,
29103
+ edgeCount,
29104
+ share: totalWeight > 0 ? (recordCount + edgeCount) / totalWeight : 0
29105
+ }));
29106
+ }
29107
+
29108
+ ;// CONCATENATED MODULE: ../doctor/src/use-cases/classify-archetype.ts
29109
+
29110
+
29111
+ function hasCrossHarnessEdges(records) {
29112
+ const harnessByPath = new Map();
29113
+ for (const record of records){
29114
+ harnessByPath.set(record.path, record.harness);
29115
+ }
29116
+ for (const record of records){
29117
+ for (const edge of record.edges){
29118
+ if (edge.malformed || edge.type === "glob-binding" || typeof edge.direction.to !== "string") {
29119
+ continue;
29120
+ }
29121
+ const targetHarness = harnessByPath.get(edge.direction.to);
29122
+ if (targetHarness && targetHarness !== record.harness) {
29123
+ return true;
29124
+ }
29125
+ }
29126
+ }
29127
+ return false;
29128
+ }
29129
+ function classifyArchetype(records) {
29130
+ if (records.length === 0) {
29131
+ return {
29132
+ archetype: "none",
29133
+ dominanceScore: 0,
29134
+ ambiguities: []
29135
+ };
29136
+ }
29137
+ const profiles = buildHarnessProfiles(records);
29138
+ const distinctHarnesses = new Set(records.map((r)=>r.harness));
29139
+ if (distinctHarnesses.size === 1 && distinctHarnesses.has("shared")) {
29140
+ return {
29141
+ archetype: "canonical-contract",
29142
+ dominanceScore: 1,
29143
+ ambiguities: []
29144
+ };
29145
+ }
29146
+ const nonSharedHarnesses = [
29147
+ ...distinctHarnesses
29148
+ ].filter((h)=>h !== "shared");
29149
+ const needsVerificationHarnesses = nonSharedHarnesses.filter((h)=>HARNESS_FOOTPRINTS[h].status === "needs-verification");
29150
+ const ambiguities = needsVerificationHarnesses.map((h)=>`harness '${h}' detection patterns are not fully verified`);
29151
+ // If all non-shared harnesses need verification, the archetype is ambiguous
29152
+ if (nonSharedHarnesses.length > 0 && needsVerificationHarnesses.length === nonSharedHarnesses.length) {
29153
+ const topProfile = profiles.reduce((a, b)=>a.share > b.share ? a : b);
29154
+ return {
29155
+ archetype: "ambiguous",
29156
+ dominanceScore: topProfile.share,
29157
+ ambiguities
29158
+ };
29159
+ }
29160
+ // pure: single dominant harness with no cross-harness edges
29161
+ if (nonSharedHarnesses.length === 1) {
29162
+ const dominant = profiles.find((p)=>p.harness === nonSharedHarnesses[0]);
29163
+ const score = dominant?.share ?? 1;
29164
+ if (score >= (/* inlined export .PURE_DOMINANCE_THRESHOLD */0.8) && !hasCrossHarnessEdges(records)) {
29165
+ return {
29166
+ archetype: "pure",
29167
+ dominanceScore: score,
29168
+ ambiguities
29169
+ };
29170
+ }
29171
+ }
29172
+ const crossHarness = hasCrossHarnessEdges(records);
29173
+ const topProfile = profiles.reduce((a, b)=>a.share > b.share ? a : b);
29174
+ if (crossHarness) {
29175
+ return {
29176
+ archetype: "intentional-hybrid",
29177
+ dominanceScore: topProfile.share,
29178
+ ambiguities
29179
+ };
29180
+ }
29181
+ // ambiguous when needs-verification harnesses are present alongside verified ones
29182
+ if (ambiguities.length > 0) {
29183
+ return {
29184
+ archetype: "ambiguous",
29185
+ dominanceScore: topProfile.share,
29186
+ ambiguities
29187
+ };
29188
+ }
29189
+ return {
29190
+ archetype: "accidental-sprawl",
29191
+ dominanceScore: topProfile.share,
29192
+ ambiguities
29193
+ };
29194
+ }
29195
+
29196
+ ;// CONCATENATED MODULE: ../doctor/src/use-cases/criteria.ts
29197
+ const CRITERIA = [
29198
+ {
29199
+ id: "missing-copilot-instructions",
29200
+ appliesToHarness: "copilot",
29201
+ severity: "critical",
29202
+ classification: "defect",
29203
+ category: "missing-required",
29204
+ description: "GitHub Copilot requires .github/copilot-instructions.md to load workspace instructions. Without it, Copilot operates without any project-specific context.",
29205
+ capability: "copilot-workspace-instructions",
29206
+ checkMethod: "inventory.fileExists",
29207
+ checkArgs: {
29208
+ harness: "copilot",
29209
+ paths: [
29210
+ ".github/copilot-instructions.md"
29211
+ ]
29212
+ }
29213
+ },
29214
+ {
29215
+ id: "missing-intent-artifact",
29216
+ appliesToHarness: "all",
29217
+ severity: "medium",
29218
+ classification: "defect",
29219
+ category: "governance",
29220
+ description: "No declared intent artifact found at .agentic-doctor/intent.json. Without declared intent, the doctor cannot evaluate capability preconditions.",
29221
+ checkMethod: "inventory.constructPresent",
29222
+ checkArgs: {
29223
+ harness: "claude",
29224
+ constructType: "instruction"
29225
+ }
29226
+ },
29227
+ {
29228
+ id: "malformed-claude-import",
29229
+ appliesToHarness: "claude",
29230
+ severity: "high",
29231
+ classification: "defect",
29232
+ category: "malformed-reference",
29233
+ description: "A Claude @import reference points to a file that does not exist or escapes the repository root.",
29234
+ checkMethod: "inventory.edgePresent",
29235
+ checkArgs: {
29236
+ fromHarness: "claude",
29237
+ toHarness: "claude",
29238
+ edgeType: "hard-import"
29239
+ }
29240
+ },
29241
+ {
29242
+ id: "cross-harness-drift",
29243
+ appliesToHarness: "all",
29244
+ appliesToArchetype: "accidental-sprawl",
29245
+ severity: "high",
29246
+ classification: "advisory",
29247
+ category: "drift",
29248
+ description: "Multiple AI harnesses are configured but share no cross-harness references. This may indicate accidental configuration sprawl rather than intentional multi-harness setup.",
29249
+ checkMethod: "inventory.archetypeIs",
29250
+ checkArgs: {}
29251
+ },
29252
+ {
29253
+ id: "wrong-direction-copilot-imports-claude",
29254
+ appliesToHarness: "copilot",
29255
+ severity: "medium",
29256
+ classification: "advisory",
29257
+ category: "wrong-direction",
29258
+ 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.",
29259
+ checkMethod: "inventory.edgeDirectionExists",
29260
+ checkArgs: {
29261
+ fromHarness: "copilot",
29262
+ toHarness: "claude"
29263
+ }
29264
+ },
29265
+ {
29266
+ id: "missing-claude-md",
29267
+ appliesToHarness: "claude",
29268
+ severity: "high",
29269
+ classification: "defect",
29270
+ category: "missing-required",
29271
+ description: "Claude Code requires CLAUDE.md or .claude/ directory to load project instructions.",
29272
+ checkMethod: "inventory.fileExists",
29273
+ checkArgs: {
29274
+ harness: "claude",
29275
+ paths: [
29276
+ "CLAUDE.md",
29277
+ ".claude/"
29278
+ ]
29279
+ }
29280
+ },
29281
+ {
29282
+ id: "missing-agents-md",
29283
+ appliesToHarness: "codex",
29284
+ severity: "high",
29285
+ classification: "defect",
29286
+ category: "missing-required",
29287
+ description: "Codex/OpenAI Codex requires AGENTS.md to load project instructions.",
29288
+ checkMethod: "inventory.fileExists",
29289
+ checkArgs: {
29290
+ harness: "codex",
29291
+ paths: [
29292
+ "AGENTS.md",
29293
+ "AGENTS.override.md"
29294
+ ]
29295
+ }
29296
+ },
29297
+ {
29298
+ id: "missing-hermes-config",
29299
+ appliesToHarness: "hermes",
29300
+ severity: "medium",
29301
+ classification: "advisory",
29302
+ category: "missing-required",
29303
+ description: "Hermes does not have a primary configuration file (.hermes.md or HERMES.md).",
29304
+ checkMethod: "inventory.fileExists",
29305
+ checkArgs: {
29306
+ harness: "hermes",
29307
+ paths: [
29308
+ ".hermes.md",
29309
+ "HERMES.md"
29310
+ ]
29311
+ }
29312
+ },
29313
+ {
29314
+ id: "missing-crush-config",
29315
+ appliesToHarness: "crush",
29316
+ severity: "medium",
29317
+ classification: "advisory",
29318
+ category: "missing-required",
29319
+ description: "Crush does not have a crush.json configuration file.",
29320
+ checkMethod: "inventory.fileExists",
29321
+ checkArgs: {
29322
+ harness: "crush",
29323
+ paths: [
29324
+ "crush.json"
29325
+ ]
29326
+ }
29327
+ }
29328
+ ];
29329
+
29330
+ ;// CONCATENATED MODULE: ../doctor/src/use-cases/detect-ambiguities.ts
29331
+ function detectAmbiguities(records, classification) {
29332
+ const questions = [];
29333
+ if (classification.archetype === "accidental-sprawl") {
29334
+ const harnesses = [
29335
+ ...new Set(records.map((r)=>r.harness))
29336
+ ].filter((h)=>h !== "shared");
29337
+ if (harnesses.length > 1) {
29338
+ questions.push({
29339
+ id: "intended-multi-harness",
29340
+ question: `Multiple AI harnesses are configured (${harnesses.join(", ")}). Is this intentional?`,
29341
+ hint: "Answer 'yes' if you want multiple harnesses to work together. Answer 'no' if this is legacy configuration."
29342
+ });
29343
+ }
29344
+ }
29345
+ if (classification.archetype === "intentional-hybrid") {
29346
+ const pathToHarness = new Map(records.map((r)=>[
29347
+ r.path,
29348
+ r.harness
29349
+ ]));
29350
+ 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));
29351
+ if (hasCrossHarnessImports) {
29352
+ questions.push({
29353
+ id: "hard-import-intentional",
29354
+ question: "Claude hard-imports (@path) are referencing instruction files from other harnesses. Is this intentional?",
29355
+ hint: "Hard imports cause Claude to inline the file contents at runtime."
29356
+ });
29357
+ }
29358
+ }
29359
+ return questions;
29360
+ }
29361
+
29362
+ ;// CONCATENATED MODULE: ../doctor/src/use-cases/evaluate-engine.ts
29363
+ function makeId(criterionId, targetId) {
29364
+ return `${criterionId}:${targetId}`;
29365
+ }
29366
+ function checkFileExists(criterion, args, ctx) {
29367
+ const { harness, paths } = args;
29368
+ const anyMatch = ctx.records.some((r)=>paths.some((p)=>r.path === p || r.path.startsWith(p.endsWith("/") ? p : `${p}/`)));
29369
+ if (!anyMatch) {
29370
+ return [
29371
+ {
29372
+ id: makeId(criterion.id, `${harness}:all`),
29373
+ criterionId: criterion.id,
29374
+ targetId: `${harness}:all`,
29375
+ severity: criterion.severity,
29376
+ category: criterion.category,
29377
+ classification: criterion.classification,
29378
+ intentGated: false,
29379
+ assumedIntent: false,
29380
+ description: criterion.description
29381
+ }
29382
+ ];
29383
+ }
29384
+ return [];
29385
+ }
29386
+ function checkConstructPresent(criterion, ctx) {
29387
+ const hasRecords = ctx.records.length > 0;
29388
+ const hasIntent = ctx.intent !== null;
29389
+ if (hasRecords && !hasIntent) {
29390
+ return [
29391
+ {
29392
+ id: makeId(criterion.id, "all:intent"),
29393
+ criterionId: criterion.id,
29394
+ targetId: "all:intent",
29395
+ severity: criterion.severity,
29396
+ category: criterion.category,
29397
+ classification: criterion.classification,
29398
+ intentGated: false,
29399
+ assumedIntent: true,
29400
+ description: criterion.description
29401
+ }
29402
+ ];
29403
+ }
29404
+ return [];
29405
+ }
29406
+ function checkEdgePresent(criterion, args, ctx) {
29407
+ const fromRecords = ctx.records.filter((r)=>r.harness === args.fromHarness);
29408
+ const hasMalformedEdges = fromRecords.some((r)=>r.edges.some((e)=>e.malformed && (args.edgeType === undefined || e.type === args.edgeType)));
29409
+ if (hasMalformedEdges) {
29410
+ return [
29411
+ {
29412
+ id: makeId(criterion.id, `${args.fromHarness}:malformed`),
29413
+ criterionId: criterion.id,
29414
+ targetId: `${args.fromHarness}:malformed`,
29415
+ severity: criterion.severity,
29416
+ category: criterion.category,
29417
+ classification: criterion.classification,
29418
+ intentGated: false,
29419
+ assumedIntent: false,
29420
+ description: criterion.description
29421
+ }
29422
+ ];
29423
+ }
29424
+ return [];
29425
+ }
29426
+ function checkEdgeDirection(criterion, args, ctx) {
29427
+ const fromRecords = ctx.records.filter((r)=>r.harness === args.fromHarness);
29428
+ const toRecords = ctx.records.filter((r)=>r.harness === args.toHarness);
29429
+ const toPaths = new Set(toRecords.map((r)=>r.path));
29430
+ 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)));
29431
+ if (!hasDirectedEdge) {
29432
+ return [
29433
+ {
29434
+ id: makeId(criterion.id, `${args.fromHarness}:${args.toHarness}`),
29435
+ criterionId: criterion.id,
29436
+ targetId: `${args.fromHarness}:${args.toHarness}`,
29437
+ severity: criterion.severity,
29438
+ category: criterion.category,
29439
+ classification: criterion.classification,
29440
+ intentGated: false,
29441
+ assumedIntent: false,
29442
+ description: criterion.description
29443
+ }
29444
+ ];
29445
+ }
29446
+ return [];
29447
+ }
29448
+ function checkArchetypeIs(criterion, ctx) {
29449
+ return [
29450
+ {
29451
+ id: makeId(criterion.id, `all:${ctx.classification.archetype}`),
29452
+ criterionId: criterion.id,
29453
+ targetId: `all:${ctx.classification.archetype}`,
29454
+ severity: criterion.severity,
29455
+ category: criterion.category,
29456
+ classification: criterion.classification,
29457
+ intentGated: false,
29458
+ assumedIntent: false,
29459
+ description: criterion.description
29460
+ }
29461
+ ];
29462
+ }
29463
+ function checkEdgeDirectionExists(criterion, args, ctx) {
29464
+ const fromRecords = ctx.records.filter((r)=>r.harness === args.fromHarness);
29465
+ const toRecords = ctx.records.filter((r)=>r.harness === args.toHarness);
29466
+ const toPaths = new Set(toRecords.map((r)=>r.path));
29467
+ 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)));
29468
+ if (hasEdge) {
29469
+ return [
29470
+ {
29471
+ id: makeId(criterion.id, `${args.fromHarness}:${args.toHarness}`),
29472
+ criterionId: criterion.id,
29473
+ targetId: `${args.fromHarness}:${args.toHarness}`,
29474
+ severity: criterion.severity,
29475
+ category: criterion.category,
29476
+ classification: criterion.classification,
29477
+ intentGated: false,
29478
+ assumedIntent: false,
29479
+ description: criterion.description
29480
+ }
29481
+ ];
29482
+ }
29483
+ return [];
29484
+ }
29485
+ function checkCapabilityDeclared(criterion, ctx) {
29486
+ if (!criterion.capability || ctx.intent === null) return [];
29487
+ const isDeclared = ctx.intent.desiredCapabilities.includes(criterion.capability);
29488
+ if (!isDeclared) {
29489
+ return [
29490
+ {
29491
+ id: makeId(criterion.id, `all:${criterion.capability}`),
29492
+ criterionId: criterion.id,
29493
+ targetId: `all:${criterion.capability}`,
29494
+ severity: criterion.severity,
29495
+ category: criterion.category,
29496
+ classification: criterion.classification,
29497
+ intentGated: true,
29498
+ assumedIntent: false,
29499
+ description: criterion.description
29500
+ }
29501
+ ];
29502
+ }
29503
+ return [];
29504
+ }
29505
+ function evaluate(criteria, ctx) {
29506
+ const findings = [];
29507
+ for (const criterion of criteria){
29508
+ if (criterion.appliesToArchetype && criterion.appliesToArchetype !== "all" && ctx.classification.archetype !== criterion.appliesToArchetype) {
29509
+ continue;
29510
+ }
29511
+ const harnesses = new Set(ctx.records.map((r)=>r.harness));
29512
+ if (criterion.appliesToHarness !== "all" && !harnesses.has(criterion.appliesToHarness)) {
29513
+ continue;
29514
+ }
29515
+ switch(criterion.checkMethod){
29516
+ case "inventory.fileExists":
29517
+ {
29518
+ findings.push(...checkFileExists(criterion, criterion.checkArgs, ctx));
29519
+ break;
29520
+ }
29521
+ case "inventory.constructPresent":
29522
+ {
29523
+ findings.push(...checkConstructPresent(criterion, ctx));
29524
+ break;
29525
+ }
29526
+ case "inventory.edgePresent":
29527
+ {
29528
+ findings.push(...checkEdgePresent(criterion, criterion.checkArgs, ctx));
29529
+ break;
29530
+ }
29531
+ case "inventory.edgeDirection":
29532
+ {
29533
+ findings.push(...checkEdgeDirection(criterion, criterion.checkArgs, ctx));
29534
+ break;
29535
+ }
29536
+ case "inventory.archetypeIs":
29537
+ {
29538
+ findings.push(...checkArchetypeIs(criterion, ctx));
29539
+ break;
29540
+ }
29541
+ case "inventory.edgeDirectionExists":
29542
+ {
29543
+ findings.push(...checkEdgeDirectionExists(criterion, criterion.checkArgs, ctx));
29544
+ break;
29545
+ }
29546
+ case "intent.capabilityDeclared":
29547
+ {
29548
+ findings.push(...checkCapabilityDeclared(criterion, ctx));
29549
+ break;
29550
+ }
29551
+ default:
29552
+ break;
29553
+ }
29554
+ }
29555
+ return findings;
29556
+ }
29557
+
29558
+ ;// CONCATENATED MODULE: ../doctor/src/index.ts
29559
+
29560
+
29561
+
29562
+
29563
+
29564
+
29565
+
29566
+
29567
+
29568
+
29569
+
29570
+ ;// CONCATENATED MODULE: ./src/commands/doctor.ts
29571
+
29572
+
29573
+
29574
+
29575
+ const doctorCommand = defineCommand({
29576
+ meta: {
29577
+ name: "doctor",
29578
+ description: "Diagnose agentic configuration across harnesses: inventory, classify, and evaluate preconditions"
29579
+ },
29580
+ args: {
29581
+ summary: {
29582
+ type: "boolean",
29583
+ description: "Show archetype classification only — skip evaluation",
29584
+ default: false
29585
+ },
29586
+ format: {
29587
+ type: "string",
29588
+ description: "Output format: human (default) or json",
29589
+ default: "human"
29590
+ },
29591
+ ci: {
29592
+ type: "boolean",
29593
+ description: "Force non-interactive mode",
29594
+ default: false
29595
+ }
29596
+ },
29597
+ async run ({ args }) {
29598
+ const logger = dist_createConsola();
29599
+ const repoPath = (0,external_node_path_.resolve)(process.cwd());
29600
+ const format = args.format === "json" ? "json" : "human";
29601
+ const records = await scanRepository(repoPath);
29602
+ const classification = classifyArchetype(records);
29603
+ const summary = formatSummary(records, classification);
29604
+ // Load wisdom graph for snapshotRef (gracefully degrade if unavailable)
29605
+ const wisdomDir = (0,external_node_path_.resolve)(repoPath, "wisdom");
29606
+ let snapshotRef;
29607
+ try {
29608
+ const wisdomClient = createWisdomClient(wisdomDir);
29609
+ const graph = await wisdomClient.getGraph();
29610
+ snapshotRef = graph.snapshotRef;
29611
+ } catch (err) {
29612
+ if (!(err instanceof WisdomUnavailableError)) throw err;
29613
+ logger.debug("Wisdom graph unavailable — snapshotRef will be omitted");
29614
+ }
29615
+ if (args.summary) {
29616
+ if (format === "json") {
29617
+ process.stdout.write(`${JSON.stringify({
29618
+ archetype: summary.archetype,
29619
+ dominanceScore: summary.dominanceScore,
29620
+ totalRecords: summary.totalRecords,
29621
+ harnessBreakdown: summary.harnessBreakdown,
29622
+ crossHarnessEdges: summary.crossHarnessEdges
29623
+ }, null, 2)}\n`);
29624
+ } else {
29625
+ renderHuman(summary, [], logger, snapshotRef);
29626
+ }
29627
+ return;
29628
+ }
29629
+ if (classification.archetype === "none") {
29630
+ if (format === "json") {
29631
+ process.stdout.write(`${JSON.stringify(toJson(summary, [], snapshotRef), null, 2)}\n`);
29632
+ } else {
29633
+ logger.info("No agentic constructs found in the repository.");
29634
+ }
29635
+ return;
29636
+ }
29637
+ const intentResult = await readIntentArtifact(repoPath);
29638
+ let intent = intentResult.found ? intentResult.artifact : null;
29639
+ // Interactive elicitation when no intent artifact and running in a TTY
29640
+ const isInteractive = !args.ci && process.stdin.isTTY === true;
29641
+ if (intent === null && isInteractive) {
29642
+ const questions = detectAmbiguities(records, classification);
29643
+ for (const q of questions){
29644
+ logger.info(`\n${q.hint}`);
29645
+ const answer = await logger.prompt(q.question, {
29646
+ type: "confirm",
29647
+ initial: false
29648
+ });
29649
+ if (answer) {
29650
+ logger.info(" Noted.");
29651
+ }
29652
+ }
29653
+ // After interactive prompts, re-read in case user pre-created the artifact
29654
+ const retried = await readIntentArtifact(repoPath);
29655
+ if (retried.found) intent = retried.artifact;
29656
+ }
29657
+ const findings = evaluate(CRITERIA, {
29658
+ records,
29659
+ classification,
29660
+ intent
29661
+ });
29662
+ if (format === "json") {
29663
+ const report = toJson(summary, findings, snapshotRef);
29664
+ process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
29665
+ } else {
29666
+ renderHuman(summary, findings, logger, snapshotRef);
29667
+ }
29668
+ if (hasBlockingFindings(findings)) {
29669
+ process.exitCode = 1;
29670
+ }
29671
+ }
29672
+ });
29673
+
27903
29674
  ;// CONCATENATED MODULE: ../core/src/use-cases/init-copilot-setup-workflow.ts
27904
29675
  /**
27905
29676
  * Use case for initializing a Copilot Setup Steps workflow in a new project.
@@ -48660,7 +50431,7 @@ const remark = unified().use(remarkParse).use(remarkStringify).freeze()
48660
50431
  */
48661
50432
 
48662
50433
 
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([
50434
+ /** 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
50435
  "error",
48665
50436
  "warn",
48666
50437
  "off"
@@ -49500,7 +51271,7 @@ const MAX_HOOKS_PER_EVENT = 100;
49500
51271
  powershell: schemas_string().min(1, "Hook PowerShell command must not be empty").optional(),
49501
51272
  cwd: schemas_string().optional(),
49502
51273
  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()
51274
+ 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
51275
  }).strict().refine((data)=>Boolean(data.bash) || Boolean(data.powershell), {
49505
51276
  message: "At least one of 'bash' or 'powershell' must be provided and non-empty"
49506
51277
  });
@@ -49688,7 +51459,7 @@ const AgentSkillFrontmatterSchema = schemas_object({
49688
51459
  }),
49689
51460
  license: schemas_string().optional(),
49690
51461
  compatibility: schemas_string().max(500, "Compatibility must be 500 characters or fewer").optional(),
49691
- metadata: record(schemas_string(), schemas_string()).optional(),
51462
+ metadata: schemas_record(schemas_string(), schemas_string()).optional(),
49692
51463
  "allowed-tools": schemas_string().optional(),
49693
51464
  "argument-hint": schemas_string().optional()
49694
51465
  });
@@ -50713,6 +52484,7 @@ const skillArgs = {
50713
52484
 
50714
52485
 
50715
52486
 
52487
+
50716
52488
  // Gateways
50717
52489
  const lessonFileGateway = createLessonFileGateway();
50718
52490
  const initHooksConfigGateway = createInitHooksConfigGateway();
@@ -50738,7 +52510,8 @@ const src_main = defineCommand({
50738
52510
  "copilot-setup": copilotSetupCommand,
50739
52511
  context: contextCmd,
50740
52512
  "init-hooks": initHooksCmd,
50741
- capture: captureCommand
52513
+ capture: captureCommand,
52514
+ doctor: doctorCommand
50742
52515
  }
50743
52516
  });
50744
52517
  runMain(src_main);
@@ -59601,4 +61374,4 @@ if (installedChunkData !== 0) { // 0 means "already installed".'
59601
61374
  // module factories are used so entry inlining is disabled
59602
61375
  // startup
59603
61376
  // Load entry module and return exports
59604
- var __webpack_exports__ = __webpack_require__(455);
61377
+ var __webpack_exports__ = __webpack_require__(149);