@webpresso/agent-kit 3.3.4 → 3.3.6
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.
Potentially problematic release.
This version of @webpresso/agent-kit might be problematic. Click here for more details.
- package/dist/esm/audit/agents.js +126 -26
- package/dist/esm/blueprint/trust/dossier.js +41 -1
- package/dist/esm/blueprint/verification.d.ts +12 -1
- package/dist/esm/blueprint/verification.js +26 -3
- package/dist/esm/build/vendor-oxlint-plugins.d.ts +4 -1
- package/dist/esm/build/vendor-oxlint-plugins.js +9 -14
- package/dist/esm/cli/commands/init/index.js +3 -4
- package/dist/esm/cli/commands/init/scaffold-base-kit.js +5 -1
- package/dist/esm/cli/commands/mcp.js +2 -0
- package/dist/esm/cli/commands/review.d.ts +1 -0
- package/dist/esm/cli/commands/review.js +77 -1
- package/dist/esm/cli/commands/sync.d.ts +8 -6
- package/dist/esm/cli/commands/sync.js +19 -9
- package/dist/esm/config/oxlint/oxlintrc.d.ts +1 -1
- package/dist/esm/config/oxlint/oxlintrc.js +3 -12
- package/dist/esm/content/dispatch.js +4 -0
- package/dist/esm/content/loader.d.ts +1 -1
- package/dist/esm/content/loader.js +10 -6
- package/dist/esm/content/skill-policy.d.ts +11 -0
- package/dist/esm/content/skill-policy.js +33 -0
- package/dist/esm/hooks/doctor.js +114 -10
- package/dist/esm/hooks/pretool-guard/dev-routing.js +67 -4
- package/dist/esm/hooks/pretool-guard/validators/worktree-discipline.js +62 -3
- package/dist/esm/mcp/blueprint/_shared/lifecycle.d.ts +13 -0
- package/dist/esm/mcp/blueprint/_shared/lifecycle.js +40 -8
- package/dist/esm/mcp/blueprint/handlers/document-mutations.d.ts +97 -0
- package/dist/esm/mcp/blueprint/handlers/document-mutations.js +23 -3
- package/dist/esm/mcp/blueprint/handlers/finalize.js +18 -7
- package/dist/esm/mcp/blueprint/registration.js +31 -2
- package/dist/esm/review/authority.js +56 -1
- package/dist/esm/review/delivery-verifier.d.ts +4 -0
- package/dist/esm/review/delivery-verifier.js +9 -1
- package/dist/esm/symlinker/consumers.d.ts +20 -1
- package/dist/esm/symlinker/consumers.js +15 -1
- package/dist/esm/symlinker/ownership.d.ts +48 -0
- package/dist/esm/symlinker/ownership.js +83 -0
- package/dist/esm/symlinker/unified-sync.d.ts +35 -3
- package/dist/esm/symlinker/unified-sync.js +112 -14
- package/dist/esm/test/shard-durations.json +0 -1
- package/package.json +12 -12
- package/dist/esm/cli/commands/init/scaffolders/subagents/index.d.ts +0 -6
- package/dist/esm/cli/commands/init/scaffolders/subagents/index.js +0 -77
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Unified rule + skill sync.
|
|
2
|
+
* Unified rule + skill + agent sync.
|
|
3
3
|
*
|
|
4
|
-
* Source of truth: catalog (`<pkg>/dist/catalog/agent/{rules,skills}/`)
|
|
5
|
-
* consumer (`<repo>/agent-{rules,skills}/`). The loader returns a
|
|
4
|
+
* Source of truth: catalog (`<pkg>/dist/catalog/agent/{rules,skills,agents}/`)
|
|
5
|
+
* UNION consumer (`<repo>/agent-{rules,skills,agents}/`). The loader returns a
|
|
6
6
|
* source-tagged record list; this module projects that list into per-IDE
|
|
7
7
|
* surfaces according to `DEFAULT_UNIFIED_CONSUMERS`.
|
|
8
8
|
*
|
|
@@ -16,6 +16,22 @@
|
|
|
16
16
|
* filename pattern but is not in the expected set is removed. This propagates
|
|
17
17
|
* deletions in `agent-rules/` and `agent-skills/` to per-IDE cleanup.
|
|
18
18
|
*
|
|
19
|
+
* Ownership: a consumer declares `ownership` (see `UnifiedConsumerConfig`).
|
|
20
|
+
* On a `shared` surface agent-kit destroys only what it can prove it created,
|
|
21
|
+
* via the single predicate in `ownership.ts`. Both write paths consult that one
|
|
22
|
+
* predicate but take DIFFERENT actions, and the asymmetry is deliberate:
|
|
23
|
+
*
|
|
24
|
+
* | path | situation | action |
|
|
25
|
+
* |-------------|------------------------------------|-------------------------|
|
|
26
|
+
* | pruneStale | nothing in the catalog claims this | leave untouched |
|
|
27
|
+
* | | name | |
|
|
28
|
+
* | applyTarget | a catalog record DOES claim it | preserve + skip + warn |
|
|
29
|
+
*
|
|
30
|
+
* Quarantining or deleting an unclaimed file would be gratuitous — it is in
|
|
31
|
+
* nobody's way. Silently replacing a claimed one is the data-loss bug this
|
|
32
|
+
* guard exists to prevent, because `.claude/agents` is gitignored and the loss
|
|
33
|
+
* is unrecoverable.
|
|
34
|
+
*
|
|
19
35
|
* `--check` mode (dry-run): produce a list of (target, status) pairs and
|
|
20
36
|
* return the count of mismatches; perform no writes.
|
|
21
37
|
*/
|
|
@@ -24,6 +40,7 @@ import { dirname, join, relative, resolve } from "node:path";
|
|
|
24
40
|
import { loadContent } from "#content/loader";
|
|
25
41
|
import { DEFAULT_PACKAGED_SKILL_SLUG_SET } from "#content/skill-policy";
|
|
26
42
|
import { RETIRED_UNIFIED_CONSUMERS, selectUnifiedConsumers, unifiedRuleFilename, } from "./consumers.js";
|
|
43
|
+
import { isAgentKitOwned, resolveManagedSourceDirs } from "./ownership.js";
|
|
27
44
|
import { pathHasSymlinkComponent } from "./retired-surfaces.js";
|
|
28
45
|
/**
|
|
29
46
|
* Create a symlink with explicit Windows type hint. POSIX ignores the type;
|
|
@@ -86,7 +103,9 @@ function planTargets(records, consumers, consumerRoot) {
|
|
|
86
103
|
const consumerDirAbs = join(consumerRoot, consumer.dir);
|
|
87
104
|
let entryName;
|
|
88
105
|
let targetPath;
|
|
89
|
-
if (record.kind === "rule") {
|
|
106
|
+
if (record.kind === "rule" || record.kind === "agent") {
|
|
107
|
+
// agent records are file-shaped like rules (a single <slug>.md),
|
|
108
|
+
// not dir-shaped like skills.
|
|
90
109
|
entryName = unifiedRuleFilename(consumer, record.slug);
|
|
91
110
|
targetPath = join(consumerDirAbs, entryName);
|
|
92
111
|
}
|
|
@@ -168,12 +187,49 @@ function dirsEqual(srcDir, destDir) {
|
|
|
168
187
|
}
|
|
169
188
|
return true;
|
|
170
189
|
}
|
|
190
|
+
/**
|
|
191
|
+
* True when applying `plan` would destroy something agent-kit cannot prove it
|
|
192
|
+
* created, on a surface the user co-owns.
|
|
193
|
+
*
|
|
194
|
+
* This is the apply-side half of the ownership question. The prune side asks
|
|
195
|
+
* the same question via the same predicate but takes a different action, and
|
|
196
|
+
* the asymmetry is deliberate — see the module docstring.
|
|
197
|
+
*
|
|
198
|
+
* An `exclusive` consumer is unaffected: its directory is generated and
|
|
199
|
+
* gitignored, so replacing an unexpected entry is the intended behavior.
|
|
200
|
+
*/
|
|
201
|
+
function wouldDestroyForeignPath(plan, managedSourceDirs) {
|
|
202
|
+
if ((plan.consumer.ownership ?? "exclusive") !== "shared")
|
|
203
|
+
return false;
|
|
204
|
+
if (!existsSync(plan.targetPath) && !isSymlinkLike(plan.targetPath))
|
|
205
|
+
return false;
|
|
206
|
+
return !isAgentKitOwned(plan.targetPath, managedSourceDirs);
|
|
207
|
+
}
|
|
208
|
+
/** `existsSync` follows symlinks, so a dangling link needs its own probe. */
|
|
209
|
+
function isSymlinkLike(path) {
|
|
210
|
+
try {
|
|
211
|
+
return lstatSync(path).isSymbolicLink();
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
171
217
|
/**
|
|
172
218
|
* Apply a single planned target. Returns 1 on write, 0 if already correct.
|
|
219
|
+
*
|
|
220
|
+
* `preserved` is a third outcome, distinct from both: the projection was
|
|
221
|
+
* skipped because the target belongs to the operator. It is reported as a
|
|
222
|
+
* warning, never as drift — see `UnifiedSyncResult`.
|
|
173
223
|
*/
|
|
174
|
-
function applyTarget(plan, check) {
|
|
224
|
+
function applyTarget(plan, check, managedSourceDirs) {
|
|
175
225
|
const { consumer, record, targetPath } = plan;
|
|
176
226
|
const sourcePath = record.kind === "skill" ? dirname(record.filePath) : record.filePath;
|
|
227
|
+
// Gate EVERY strategy, not just the symlink branch: each one below uses its
|
|
228
|
+
// "is it already what we'd write?" check as the destruction gate, and none of
|
|
229
|
+
// those checks can distinguish "stale projection" from "operator's file".
|
|
230
|
+
if (wouldDestroyForeignPath(plan, managedSourceDirs)) {
|
|
231
|
+
return { wrote: 0, preserved: targetPath };
|
|
232
|
+
}
|
|
177
233
|
switch (consumer.strategy) {
|
|
178
234
|
case "symlink": {
|
|
179
235
|
// Use the resolved (real) source path so links survive pnpm version churn.
|
|
@@ -254,7 +310,7 @@ function applyTarget(plan, check) {
|
|
|
254
310
|
* managed pattern (rule files: `<slug><ext>`; skill dirs: subdirectories).
|
|
255
311
|
* Real files outside the managed pattern (README.md, .gitkeep) are left alone.
|
|
256
312
|
*/
|
|
257
|
-
function pruneStale(plans, consumers, consumerRoot, check, preserveSkillSlugs) {
|
|
313
|
+
function pruneStale(plans, consumers, consumerRoot, check, preserveSkillSlugs, configuredAgentSourceDirs) {
|
|
258
314
|
const expectedByConsumerDir = new Map();
|
|
259
315
|
for (const plan of plans) {
|
|
260
316
|
const key = plan.consumer.dir;
|
|
@@ -265,6 +321,16 @@ function pruneStale(plans, consumers, consumerRoot, check, preserveSkillSlugs) {
|
|
|
265
321
|
}
|
|
266
322
|
set.add(plan.entryName);
|
|
267
323
|
}
|
|
324
|
+
// Plan-derived source dirs are folded in alongside the configuration-derived
|
|
325
|
+
// set. The configured set already guarantees non-emptiness (see
|
|
326
|
+
// `resolveManagedSourceDirs`); the plan-derived entries additionally cover a
|
|
327
|
+
// consumer override dir the configuration cannot know about.
|
|
328
|
+
const agentSourceDirs = new Set([
|
|
329
|
+
...configuredAgentSourceDirs,
|
|
330
|
+
...plans
|
|
331
|
+
.filter((plan) => plan.record.kind === "agent")
|
|
332
|
+
.map((plan) => dirname(plan.record.filePath)),
|
|
333
|
+
]);
|
|
268
334
|
let removed = 0;
|
|
269
335
|
const mismatches = [];
|
|
270
336
|
// Each unique dir gets a single sweep — multiple consumers can share a dir
|
|
@@ -292,15 +358,30 @@ function pruneStale(plans, consumers, consumerRoot, check, preserveSkillSlugs) {
|
|
|
292
358
|
preserveSkillSlugs.has(name) &&
|
|
293
359
|
entry.isDirectory())
|
|
294
360
|
continue;
|
|
295
|
-
// Only prune entries that match a managed shape: rule extension
|
|
296
|
-
// directory (skill).
|
|
297
|
-
const matchesRulePattern = consumersForDir.some((c) => c.acceptsKind === "rule"
|
|
361
|
+
// Only prune entries that match a managed shape: rule/agent extension
|
|
362
|
+
// OR a directory (skill). Agent records are file-shaped like rules.
|
|
363
|
+
const matchesRulePattern = consumersForDir.some((c) => (c.acceptsKind === "rule" || c.acceptsKind === "agent") &&
|
|
364
|
+
name.endsWith(c.ruleExtension ?? ".md"));
|
|
298
365
|
const matchesSkillPattern = consumersForDir.some((c) => c.acceptsKind === "skill") && entry.isDirectory();
|
|
299
366
|
// Also handle stale symlinks of either shape.
|
|
300
367
|
const isLink = entry.isSymbolicLink();
|
|
301
368
|
if (!matchesRulePattern && !matchesSkillPattern && !isLink)
|
|
302
369
|
continue;
|
|
303
370
|
const fullPath = join(dirAbs, name);
|
|
371
|
+
// Conservative prune on a shared surface: skip real files, real dirs, and
|
|
372
|
+
// foreign symlinks (anything agent-kit cannot prove it created). The
|
|
373
|
+
// stance is read from the consumer's DECLARED ownership rather than
|
|
374
|
+
// inferred from its kind — inference by `.every(c => acceptsKind ===
|
|
375
|
+
// "agent")` would silently switch the guard off for the whole directory
|
|
376
|
+
// the moment any non-agent consumer was registered on it.
|
|
377
|
+
//
|
|
378
|
+
// Exclusive dirs (rules, skills) are generated and gitignored with no
|
|
379
|
+
// hand-authoring workflow, so their sweep-everything behavior is
|
|
380
|
+
// deliberately unchanged.
|
|
381
|
+
const isSharedDir = consumersForDir.some((c) => (c.ownership ?? "exclusive") === "shared");
|
|
382
|
+
if (isSharedDir && !isAgentKitOwned(fullPath, agentSourceDirs)) {
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
304
385
|
if (check) {
|
|
305
386
|
mismatches.push({
|
|
306
387
|
consumerId: consumersForDir[0]?.id ?? dir,
|
|
@@ -348,7 +429,7 @@ function pruneEmptyRetiredConsumerDirs(consumers, consumerRoot, check) {
|
|
|
348
429
|
* Main entrypoint. See module docstring.
|
|
349
430
|
*/
|
|
350
431
|
export function runUnifiedSync(options) {
|
|
351
|
-
const kinds = options.kinds ?? ["rule", "skill"];
|
|
432
|
+
const kinds = options.kinds ?? ["rule", "skill", "agent"];
|
|
352
433
|
const kindSet = new Set(kinds);
|
|
353
434
|
const consumers = (options.consumers ?? selectUnifiedConsumers(options.hosts)).filter((consumer) => kindSet.has(consumer.acceptsKind));
|
|
354
435
|
const activeDirs = new Set(consumers.map((consumer) => consumer.dir));
|
|
@@ -379,10 +460,27 @@ export function runUnifiedSync(options) {
|
|
|
379
460
|
throw new Error(`wp sync: slug collisions between catalog and consumer (rename consumer copy):\n${lines.join("\n")}`);
|
|
380
461
|
}
|
|
381
462
|
const plans = planTargets(filteredRecords, consumers, consumerRoot);
|
|
463
|
+
// The directories agent-kit projects agent records from. Configuration-
|
|
464
|
+
// derived and therefore never empty, so a broken install cannot collapse
|
|
465
|
+
// ownership into "everything is ours". Owned by ownership.ts, shared by the
|
|
466
|
+
// apply and prune paths so they cannot answer the question differently.
|
|
467
|
+
const configuredAgentSourceDirs = resolveManagedSourceDirs({
|
|
468
|
+
catalogDir: options.catalogDir,
|
|
469
|
+
consumerRoot,
|
|
470
|
+
});
|
|
382
471
|
let fixCount = 0;
|
|
383
472
|
const mismatches = [];
|
|
473
|
+
const preserved = [];
|
|
384
474
|
for (const plan of plans) {
|
|
385
|
-
const result = applyTarget(plan, options.check === true);
|
|
475
|
+
const result = applyTarget(plan, options.check === true, configuredAgentSourceDirs);
|
|
476
|
+
if (result.preserved !== undefined) {
|
|
477
|
+
preserved.push({
|
|
478
|
+
consumerId: plan.consumer.id,
|
|
479
|
+
targetPath: result.preserved,
|
|
480
|
+
slug: plan.record.slug,
|
|
481
|
+
});
|
|
482
|
+
continue;
|
|
483
|
+
}
|
|
386
484
|
if (result.wrote > 0) {
|
|
387
485
|
fixCount += result.wrote;
|
|
388
486
|
if (options.check === true && result.mismatch !== undefined) {
|
|
@@ -394,14 +492,14 @@ export function runUnifiedSync(options) {
|
|
|
394
492
|
}
|
|
395
493
|
}
|
|
396
494
|
}
|
|
397
|
-
const prune = pruneStale(plans, consumers, consumerRoot, options.check === true, options.preserveSkillSlugs);
|
|
495
|
+
const prune = pruneStale(plans, consumers, consumerRoot, options.check === true, options.preserveSkillSlugs, configuredAgentSourceDirs);
|
|
398
496
|
fixCount += prune.removed;
|
|
399
497
|
mismatches.push(...prune.mismatches);
|
|
400
|
-
const retiredPrune = pruneStale([], retiredConsumers, consumerRoot, options.check === true, undefined);
|
|
498
|
+
const retiredPrune = pruneStale([], retiredConsumers, consumerRoot, options.check === true, undefined, configuredAgentSourceDirs);
|
|
401
499
|
fixCount += retiredPrune.removed;
|
|
402
500
|
mismatches.push(...retiredPrune.mismatches);
|
|
403
501
|
const retiredDirectories = pruneEmptyRetiredConsumerDirs(retiredConsumers, consumerRoot, options.check === true);
|
|
404
502
|
fixCount += retiredDirectories.removed;
|
|
405
503
|
mismatches.push(...retiredDirectories.mismatches);
|
|
406
|
-
return { fixCount, mismatches };
|
|
504
|
+
return { fixCount, mismatches, preserved };
|
|
407
505
|
}
|
|
@@ -296,7 +296,6 @@
|
|
|
296
296
|
"src/hooks/dispatch/index.test.ts": 11.771874999999994,
|
|
297
297
|
"src/hooks/shared/hook-bootstrap.test.ts": 58.217375000000004,
|
|
298
298
|
"src/cli/commands/init/scaffolders/agent-hooks/emitters/grok.test.ts": 67.57654199999999,
|
|
299
|
-
"src/cli/commands/init/scaffolders/subagents/index.test.ts": 49.99270800000005,
|
|
300
299
|
"src/blueprint/core/validation/criteria.test.ts": 9.635125000000016,
|
|
301
300
|
"src/cli/commands/session.test.ts": 662.8378339999999,
|
|
302
301
|
"src/cli/commands/dash/run-registry.test.ts": 793.002083,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpresso/agent-kit",
|
|
3
|
-
"version": "3.3.
|
|
3
|
+
"version": "3.3.6",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "TypeScript-first agent harness for guarded develop/deploy workflows: wp CLI gates, MCP tools, hooks, memory, worktrees, secrets, audits, and evidence checks.",
|
|
6
6
|
"keywords": [
|
|
@@ -514,16 +514,16 @@
|
|
|
514
514
|
"setupWpActionRef": "c2c71a7a4be446fc6858e6b57bf55a11ccfa2d88"
|
|
515
515
|
},
|
|
516
516
|
"optionalDependencies": {
|
|
517
|
-
"@webpresso/agent-kit-runtime-darwin-arm64": "3.3.
|
|
518
|
-
"@webpresso/agent-kit-runtime-darwin-x64": "3.3.
|
|
519
|
-
"@webpresso/agent-kit-runtime-linux-x64": "3.3.
|
|
520
|
-
"@webpresso/agent-kit-runtime-linux-arm64": "3.3.
|
|
521
|
-
"@webpresso/agent-kit-runtime-windows-x64": "3.3.
|
|
522
|
-
"@webpresso/agent-kit-session-memory-darwin-x64": "3.3.
|
|
523
|
-
"@webpresso/agent-kit-session-memory-darwin-arm64": "3.3.
|
|
524
|
-
"@webpresso/agent-kit-session-memory-linux-x64": "3.3.
|
|
525
|
-
"@webpresso/agent-kit-session-memory-linux-arm64": "3.3.
|
|
526
|
-
"@webpresso/agent-kit-session-memory-win32-x64": "3.3.
|
|
527
|
-
"@webpresso/agent-kit-session-memory-win32-arm64": "3.3.
|
|
517
|
+
"@webpresso/agent-kit-runtime-darwin-arm64": "3.3.6",
|
|
518
|
+
"@webpresso/agent-kit-runtime-darwin-x64": "3.3.6",
|
|
519
|
+
"@webpresso/agent-kit-runtime-linux-x64": "3.3.6",
|
|
520
|
+
"@webpresso/agent-kit-runtime-linux-arm64": "3.3.6",
|
|
521
|
+
"@webpresso/agent-kit-runtime-windows-x64": "3.3.6",
|
|
522
|
+
"@webpresso/agent-kit-session-memory-darwin-x64": "3.3.6",
|
|
523
|
+
"@webpresso/agent-kit-session-memory-darwin-arm64": "3.3.6",
|
|
524
|
+
"@webpresso/agent-kit-session-memory-linux-x64": "3.3.6",
|
|
525
|
+
"@webpresso/agent-kit-session-memory-linux-arm64": "3.3.6",
|
|
526
|
+
"@webpresso/agent-kit-session-memory-win32-x64": "3.3.6",
|
|
527
|
+
"@webpresso/agent-kit-session-memory-win32-arm64": "3.3.6"
|
|
528
528
|
}
|
|
529
529
|
}
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import { existsSync, lstatSync, mkdirSync, readdirSync, readlinkSync, rmSync, symlinkSync, } from "node:fs";
|
|
2
|
-
import { dirname, join, relative } from "node:path";
|
|
3
|
-
import { readPackageJson } from "#cli/commands/init/detect-consumer";
|
|
4
|
-
import { resolveAgentKitPackageRootOrThrow } from "#cli/commands/init/package-root";
|
|
5
|
-
function detectMode(repoRoot) {
|
|
6
|
-
const pkg = readPackageJson(repoRoot).info;
|
|
7
|
-
if (pkg?.name === "@webpresso/agent-kit") {
|
|
8
|
-
return {
|
|
9
|
-
mode: "self",
|
|
10
|
-
sourceRoot: join(repoRoot, "catalog", "agent", "agents"),
|
|
11
|
-
};
|
|
12
|
-
}
|
|
13
|
-
const installedPackageJsonPath = join(repoRoot, "node_modules", "@webpresso", "agent-kit", "package.json");
|
|
14
|
-
const installedAgentsRoot = join(repoRoot, "node_modules", "@webpresso", "agent-kit", "catalog", "agent", "agents");
|
|
15
|
-
if (existsSync(installedPackageJsonPath) && existsSync(installedAgentsRoot)) {
|
|
16
|
-
return {
|
|
17
|
-
mode: "consumer",
|
|
18
|
-
sourceRoot: installedAgentsRoot,
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
return {
|
|
22
|
-
mode: "package-fallback",
|
|
23
|
-
sourceRoot: join(resolveCurrentPackageRoot(), "catalog", "agent", "agents"),
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
function resolveCurrentPackageRoot() {
|
|
27
|
-
return resolveAgentKitPackageRootOrThrow("wp init: could not locate the webpresso package root for subagents fallback.", { requireCatalog: true });
|
|
28
|
-
}
|
|
29
|
-
export function scaffoldSubagents(input) {
|
|
30
|
-
const { repoRoot, options } = input;
|
|
31
|
-
const mode = detectMode(repoRoot);
|
|
32
|
-
const sourceRoot = mode.sourceRoot;
|
|
33
|
-
const targetRoot = join(repoRoot, ".claude", "agents");
|
|
34
|
-
const results = [];
|
|
35
|
-
if (!existsSync(sourceRoot))
|
|
36
|
-
return results;
|
|
37
|
-
const entries = readdirSync(sourceRoot).filter((f) => f.endsWith(".md") && f !== "README.md");
|
|
38
|
-
if (entries.length === 0)
|
|
39
|
-
return results;
|
|
40
|
-
if (!options.dryRun) {
|
|
41
|
-
mkdirSync(targetRoot, { recursive: true });
|
|
42
|
-
}
|
|
43
|
-
for (const name of entries) {
|
|
44
|
-
const sourcePath = join(sourceRoot, name);
|
|
45
|
-
const targetPath = join(targetRoot, name);
|
|
46
|
-
const symTarget = relative(dirname(targetPath), sourcePath);
|
|
47
|
-
if (options.dryRun) {
|
|
48
|
-
results.push({ targetPath, action: "created" });
|
|
49
|
-
continue;
|
|
50
|
-
}
|
|
51
|
-
try {
|
|
52
|
-
const stat = lstatSync(targetPath);
|
|
53
|
-
if (stat.isSymbolicLink()) {
|
|
54
|
-
const currentTarget = readlinkSync(targetPath);
|
|
55
|
-
if (currentTarget === symTarget) {
|
|
56
|
-
results.push({ targetPath, action: "identical" });
|
|
57
|
-
}
|
|
58
|
-
else if (options.overwrite) {
|
|
59
|
-
rmSync(targetPath);
|
|
60
|
-
symlinkSync(symTarget, targetPath);
|
|
61
|
-
results.push({ targetPath, action: "overwritten" });
|
|
62
|
-
}
|
|
63
|
-
else {
|
|
64
|
-
results.push({ targetPath, action: "drifted" });
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
else {
|
|
68
|
-
results.push({ targetPath, action: "identical" });
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
catch {
|
|
72
|
-
symlinkSync(symTarget, targetPath);
|
|
73
|
-
results.push({ targetPath, action: "created" });
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
return results;
|
|
77
|
-
}
|