@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/api/copilot-with-fastify/.devcontainer/devcontainer.json +1 -1
- package/api/copilot-with-fastify/biome.template.json +1 -1
- package/api/copilot-with-fastify/package-lock.json +173 -203
- package/api/copilot-with-fastify/package.json +4 -4
- package/cli/copilot-with-citty/.devcontainer/devcontainer.json +1 -1
- package/cli/copilot-with-citty/biome.template.json +1 -1
- package/cli/copilot-with-citty/package.json +2 -2
- package/dist/index.js +1810 -37
- package/package.json +2 -1
- package/ui/copilot-with-react/.devcontainer/devcontainer.json +1 -1
- package/ui/copilot-with-react/biome.template.json +1 -1
- package/ui/copilot-with-react/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -2990,7 +2990,7 @@ exports.basename = (path, { windows } = {}) => {
|
|
|
2990
2990
|
|
|
2991
2991
|
|
|
2992
2992
|
},
|
|
2993
|
-
|
|
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
|
|
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
|
|
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(
|
|
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()
|
|
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
|
|
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
|
|
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),
|
|
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:
|
|
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:
|
|
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:
|
|
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 =
|
|
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:
|
|
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:
|
|
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__(
|
|
61377
|
+
var __webpack_exports__ = __webpack_require__(149);
|