@open-agent-toolkit/cli 0.2.52 → 0.2.54

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.
Files changed (94) hide show
  1. package/assets/agents/recon-worker.md +105 -0
  2. package/assets/bundle-metadata.json +1 -1
  3. package/assets/docs/cli-utilities/tool-packs.md +62 -8
  4. package/assets/docs/contributing/skills.md +26 -0
  5. package/assets/docs/workflows/skills/index.md +3 -0
  6. package/assets/docs/workflows/skills/recon.md +194 -0
  7. package/assets/public-package-versions.json +4 -4
  8. package/assets/skills/oat-dispatch-subagents/SKILL.md +105 -4
  9. package/assets/skills/oat-dispatch-subagents/references/record-schema.md +226 -0
  10. package/assets/skills/oat-project-autonomous/references/gate-inventory.md +2 -2
  11. package/assets/skills/oat-project-document/references/docs/autonomy-contract.md +2 -2
  12. package/assets/skills/oat-project-implement/references/docs/autonomy-contract.md +2 -2
  13. package/assets/skills/oat-project-pr-final/references/docs/autonomy-contract.md +2 -2
  14. package/assets/skills/oat-project-quick-start/references/docs/autonomy-contract.md +2 -2
  15. package/assets/skills/recon/SKILL.md +260 -0
  16. package/assets/skills/recon/references/packet-contract.md +307 -0
  17. package/assets/skills/recon/references/profiles.md +58 -0
  18. package/assets/skills/recon/references/worker-contract.md +71 -0
  19. package/assets/skills/recon/scripts/create-review-brief.mjs +452 -0
  20. package/assets/skills/recon/scripts/lib/canonical-json.mjs +55 -0
  21. package/assets/skills/recon/scripts/lib/contracts.mjs +2462 -0
  22. package/assets/skills/recon/scripts/lib/safe-path.mjs +120 -0
  23. package/assets/skills/recon/scripts/lib/validated-run.mjs +86 -0
  24. package/assets/skills/recon/scripts/reconcile-ledger.mjs +360 -0
  25. package/assets/skills/recon/scripts/render-packet.mjs +392 -0
  26. package/assets/skills/recon/scripts/validate-artifact.mjs +96 -0
  27. package/assets/skills/recon/scripts/validate-packet.mjs +2359 -0
  28. package/dist/commands/doctor/index.js +5 -5
  29. package/dist/commands/init/index.d.ts.map +1 -1
  30. package/dist/commands/init/index.js +7 -8
  31. package/dist/commands/status/index.d.ts +2 -2
  32. package/dist/commands/status/index.d.ts.map +1 -1
  33. package/dist/commands/status/index.js +8 -8
  34. package/dist/commands/sync/index.d.ts.map +1 -1
  35. package/dist/commands/sync/index.js +25 -9
  36. package/dist/commands/sync/sync.types.d.ts +2 -2
  37. package/dist/commands/sync/sync.types.d.ts.map +1 -1
  38. package/dist/commands/tools/migrate/index.d.ts.map +1 -1
  39. package/dist/commands/tools/migrate/index.js +25 -1
  40. package/dist/commands/tools/migrate/migrate-pack.d.ts +5 -0
  41. package/dist/commands/tools/migrate/migrate-pack.d.ts.map +1 -1
  42. package/dist/commands/tools/migrate/migrate-pack.js +66 -7
  43. package/dist/commands/tools/remove/index.js +1 -1
  44. package/dist/commands/tools/remove/remove-tools.d.ts +5 -1
  45. package/dist/commands/tools/remove/remove-tools.d.ts.map +1 -1
  46. package/dist/commands/tools/remove/remove-tools.js +81 -1
  47. package/dist/commands/tools/shared/apply-pack-reconcile.d.ts +3 -0
  48. package/dist/commands/tools/shared/apply-pack-reconcile.d.ts.map +1 -1
  49. package/dist/commands/tools/shared/apply-pack-reconcile.js +40 -9
  50. package/dist/commands/tools/shared/format-pack-inventory.d.ts.map +1 -1
  51. package/dist/commands/tools/shared/format-pack-inventory.js +3 -0
  52. package/dist/commands/tools/shared/install-sync-context.d.ts +2 -1
  53. package/dist/commands/tools/shared/install-sync-context.d.ts.map +1 -1
  54. package/dist/commands/tools/shared/install-sync-context.js +14 -3
  55. package/dist/commands/tools/shared/pack-dependencies.d.ts +14 -0
  56. package/dist/commands/tools/shared/pack-dependencies.d.ts.map +1 -0
  57. package/dist/commands/tools/shared/pack-dependencies.js +54 -0
  58. package/dist/commands/tools/shared/pack-inventory.d.ts.map +1 -1
  59. package/dist/commands/tools/shared/pack-inventory.js +9 -9
  60. package/dist/commands/tools/shared/pack-lifecycle.d.ts +9 -1
  61. package/dist/commands/tools/shared/pack-lifecycle.d.ts.map +1 -1
  62. package/dist/commands/tools/shared/pack-lifecycle.js +102 -9
  63. package/dist/commands/tools/shared/pack-manifest.d.ts +2 -2
  64. package/dist/commands/tools/shared/pack-manifest.d.ts.map +1 -1
  65. package/dist/commands/tools/shared/pack-manifest.js +91 -2
  66. package/dist/commands/tools/shared/pack-reconcile.d.ts +15 -1
  67. package/dist/commands/tools/shared/pack-reconcile.d.ts.map +1 -1
  68. package/dist/commands/tools/shared/pack-reconcile.js +75 -13
  69. package/dist/commands/tools/shared/scoped-pack-intent.d.ts +8 -0
  70. package/dist/commands/tools/shared/scoped-pack-intent.d.ts.map +1 -1
  71. package/dist/commands/tools/shared/scoped-pack-intent.js +61 -15
  72. package/dist/commands/tools/shared/types.d.ts +7 -0
  73. package/dist/commands/tools/shared/types.d.ts.map +1 -1
  74. package/dist/commands/tools/update/update-tools.d.ts.map +1 -1
  75. package/dist/commands/tools/update/update-tools.js +7 -1
  76. package/dist/config/oat-config.d.ts +5 -1
  77. package/dist/config/oat-config.d.ts.map +1 -1
  78. package/dist/config/oat-config.js +19 -0
  79. package/dist/engine/index.d.ts +2 -2
  80. package/dist/engine/index.d.ts.map +1 -1
  81. package/dist/engine/index.js +1 -1
  82. package/dist/engine/scanner.d.ts +16 -1
  83. package/dist/engine/scanner.d.ts.map +1 -1
  84. package/dist/engine/scanner.js +98 -4
  85. package/dist/providers/codex/codec/sync-extension.d.ts +1 -1
  86. package/dist/providers/codex/codec/sync-extension.d.ts.map +1 -1
  87. package/dist/providers/codex/codec/sync-extension.js +7 -7
  88. package/dist/providers/cursor/codec/sync-extension.d.ts +1 -1
  89. package/dist/providers/cursor/codec/sync-extension.d.ts.map +1 -1
  90. package/dist/providers/cursor/codec/sync-extension.js +11 -13
  91. package/dist/shared/types.d.ts +1 -0
  92. package/dist/shared/types.d.ts.map +1 -1
  93. package/dist/shared/types.js +3 -0
  94. package/package.json +2 -2
@@ -2,9 +2,18 @@ import { mkdir, writeFile } from 'node:fs/promises';
2
2
  import { dirname } from 'node:path';
3
3
  import { readOatConfig, writeOatConfig } from '../../../config/oat-config.js';
4
4
  import { applyPackReconcilePlan, preflightPackReconcilePlans, } from './apply-pack-reconcile.js';
5
+ import { dependencyRetainedAssetIds, expandPackLifecycleRequests, } from './pack-dependencies.js';
5
6
  import { inventoryScopedPack, } from './pack-inventory.js';
6
- import { planPackReconcile, } from './pack-reconcile.js';
7
- import { writeScopedPackIntent } from './scoped-pack-intent.js';
7
+ import { planPackReconcile, resolveSharedOwnerRetentions, } from './pack-reconcile.js';
8
+ import { hasScopedPackOwnershipEvidence, writeScopedPackIntent, writeScopedPackLease, } from './scoped-pack-intent.js';
9
+ function lifecycleInventoryKey(request) {
10
+ return JSON.stringify({
11
+ pack: request.pack,
12
+ scope: request.scope,
13
+ scopeRoot: request.scopeRoot,
14
+ assetsRoot: request.assetsRoot,
15
+ });
16
+ }
8
17
  async function writeGenerated(scopeRoot, operation) {
9
18
  await mkdir(dirname(operation.destination), { recursive: true });
10
19
  switch (operation.generation) {
@@ -27,17 +36,83 @@ async function writeGenerated(scopeRoot, operation) {
27
36
  }
28
37
  export async function reconcilePackLifecycles(requests, options = {}) {
29
38
  const dependencies = options.dependencies ?? {};
39
+ const getDefinition = dependencies.getDefinition;
30
40
  const inventory = dependencies.inventory ?? inventoryScopedPack;
31
41
  const plan = dependencies.plan ?? planPackReconcile;
32
- const planned = await Promise.all(requests.map(async (request) => {
33
- const before = await inventory(request);
34
- return {
42
+ const expanded = expandPackLifecycleRequests(requests, getDefinition);
43
+ const initialInventories = new Map();
44
+ const finalLeaseConsumers = new Map();
45
+ for (const request of expanded) {
46
+ if (!request.dependency)
47
+ continue;
48
+ const key = lifecycleInventoryKey(request);
49
+ let consumers = finalLeaseConsumers.get(key);
50
+ if (!consumers) {
51
+ const inventoried = await inventory(request);
52
+ initialInventories.set(key, inventoried);
53
+ consumers = [...inventoried.intent.requiredBy];
54
+ }
55
+ const nextConsumers = new Set(consumers);
56
+ if (request.dependency.lease === 'acquire') {
57
+ nextConsumers.add(request.dependency.requiredBy);
58
+ }
59
+ else {
60
+ nextConsumers.delete(request.dependency.requiredBy);
61
+ }
62
+ finalLeaseConsumers.set(key, [...nextConsumers].sort());
63
+ }
64
+ const projectedLeaseConsumers = new Map();
65
+ const planned = [];
66
+ for (const request of expanded) {
67
+ const key = lifecycleInventoryKey(request);
68
+ const inventoried = initialInventories.get(key) ?? (await inventory(request));
69
+ const projectedConsumers = projectedLeaseConsumers.get(key);
70
+ const before = projectedConsumers
71
+ ? {
72
+ ...inventoried,
73
+ intent: {
74
+ ...inventoried.intent,
75
+ requiredBy: projectedConsumers,
76
+ },
77
+ }
78
+ : inventoried;
79
+ const retainedAssets = request.action === 'remove'
80
+ ? await resolveSharedOwnerRetentions({
81
+ packs: [request.pack],
82
+ scope: request.scope,
83
+ scopeRoot: request.scopeRoot,
84
+ hasOwnershipEvidence: async (pack, scope, scopeRoot) => (dependencies.hasOwnershipEvidence ??
85
+ hasScopedPackOwnershipEvidence)({
86
+ pack,
87
+ scope,
88
+ scopeRoot,
89
+ }),
90
+ })
91
+ : [];
92
+ const retainedConsumers = finalLeaseConsumers.get(key) ?? before.intent.requiredBy;
93
+ const reconcilePlan = plan({
94
+ ...request,
95
+ inventory: before,
96
+ retainedAssets,
97
+ retainedDependencyAssetIds: dependencyRetainedAssetIds(request.pack, retainedConsumers, getDefinition),
98
+ });
99
+ planned.push({
35
100
  request,
36
101
  before,
37
- plan: plan({ ...request, inventory: before }),
102
+ plan: reconcilePlan,
38
103
  apply: null,
39
- };
40
- }));
104
+ });
105
+ if (request.dependency) {
106
+ const nextConsumers = new Set(before.intent.requiredBy);
107
+ if (request.dependency.lease === 'acquire') {
108
+ nextConsumers.add(request.dependency.requiredBy);
109
+ }
110
+ else {
111
+ nextConsumers.delete(request.dependency.requiredBy);
112
+ }
113
+ projectedLeaseConsumers.set(key, [...nextConsumers].sort());
114
+ }
115
+ }
41
116
  if (options.dryRun)
42
117
  return planned;
43
118
  await (dependencies.preflight ?? preflightPackReconcilePlans)(planned.map(({ request, plan: value }) => ({
@@ -56,6 +131,13 @@ export async function reconcilePackLifecycles(requests, options = {}) {
56
131
  scopeRoot: request.scopeRoot,
57
132
  enabled: operation.enabled,
58
133
  }),
134
+ writeLease: async (operation) => writeScopedPackLease({
135
+ pack: operation.pack,
136
+ scope: operation.scope,
137
+ scopeRoot: request.scopeRoot,
138
+ requiredBy: operation.requiredBy,
139
+ enabled: operation.enabled,
140
+ }),
59
141
  inventory: async () => inventory(request),
60
142
  });
61
143
  results.push({ ...entry, apply: applied });
@@ -63,5 +145,16 @@ export async function reconcilePackLifecycles(requests, options = {}) {
63
145
  return results;
64
146
  }
65
147
  export async function reconcilePackLifecycle(request, options = {}) {
66
- return (await reconcilePackLifecycles([request], options))[0];
148
+ const results = await reconcilePackLifecycles([request], options);
149
+ return (results.find(({ request: candidate }) => candidate.pack === request.pack &&
150
+ candidate.scope === request.scope &&
151
+ candidate.scopeRoot === request.scopeRoot &&
152
+ candidate.action === request.action &&
153
+ !candidate.dependency) ?? results[0]);
154
+ }
155
+ export async function reconcilePackDependencyLifecycles(request, options = {}) {
156
+ const dependencyRequests = expandPackLifecycleRequests([request], options.dependencies?.getDefinition).filter(({ dependency }) => dependency !== undefined);
157
+ if (dependencyRequests.length === 0)
158
+ return [];
159
+ return reconcilePackLifecycles(dependencyRequests, options);
67
160
  }
@@ -1,5 +1,5 @@
1
- import type { PackAssetDefinition, PackAssetGeneration, PackAssetKind, PackAssetOwnership, PackDefinition, PackName } from './types.js';
2
- export type { PackAssetDefinition, PackAssetGeneration, PackAssetKind, PackAssetOwnership, PackDefinition, };
1
+ import type { PackAssetDefinition, PackAssetGeneration, PackAssetKind, PackAssetOwnership, PackDependencyDefinition, PackDefinition, PackName } from './types.js';
2
+ export type { PackAssetDefinition, PackAssetGeneration, PackAssetKind, PackAssetOwnership, PackDependencyDefinition, PackDefinition, };
3
3
  export declare const PACK_NAMES: readonly ["core", "ideas", "docs", "workflows", "utility", "project-management", "research", "brainstorm"];
4
4
  export declare function isPackName(value: string): value is PackName;
5
5
  export declare const PACK_MANIFEST: readonly PackDefinition[];
@@ -1 +1 @@
1
- {"version":3,"file":"pack-manifest.d.ts","sourceRoot":"","sources":["../../../../src/commands/tools/shared/pack-manifest.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,mBAAmB,EACnB,mBAAmB,EACnB,aAAa,EACb,kBAAkB,EAClB,cAAc,EACd,QAAQ,EACT,MAAM,SAAS,CAAC;AAEjB,YAAY,EACV,mBAAmB,EACnB,mBAAmB,EACnB,aAAa,EACb,kBAAkB,EAClB,cAAc,GACf,CAAC;AAEF,eAAO,MAAM,UAAU,4GASiB,CAAC;AAEzC,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,QAAQ,CAE3D;AA8HD,eAAO,MAAM,aAAa,EAAE,SAAS,cAAc,EAwKlD,CAAC;AAEF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,QAAQ,GAAG,cAAc,CAMhE;AAED,wBAAgB,aAAa,CAC3B,IAAI,EAAE,QAAQ,EACd,IAAI,CAAC,EAAE,aAAa,GACnB,SAAS,mBAAmB,EAAE,CAGhC;AAED,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,QAAQ,EACd,IAAI,EAAE,OAAO,GAAG,OAAO,GAAG,UAAU,GAAG,QAAQ,GAC9C,MAAM,EAAE,CAQV;AAED,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,EAAE,CAIlE;AA0DD,wBAAgB,oBAAoB,CAClC,QAAQ,GAAE,SAAS,cAAc,EAAkB,GAClD,IAAI,CA6DN"}
1
+ {"version":3,"file":"pack-manifest.d.ts","sourceRoot":"","sources":["../../../../src/commands/tools/shared/pack-manifest.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,mBAAmB,EACnB,mBAAmB,EACnB,aAAa,EACb,kBAAkB,EAClB,wBAAwB,EACxB,cAAc,EACd,QAAQ,EACT,MAAM,SAAS,CAAC;AAEjB,YAAY,EACV,mBAAmB,EACnB,mBAAmB,EACnB,aAAa,EACb,kBAAkB,EAClB,wBAAwB,EACxB,cAAc,GACf,CAAC;AAEF,eAAO,MAAM,UAAU,4GASiB,CAAC;AAEzC,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,QAAQ,CAE3D;AAkID,eAAO,MAAM,aAAa,EAAE,SAAS,cAAc,EAwLlD,CAAC;AAEF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,QAAQ,GAAG,cAAc,CAMhE;AAED,wBAAgB,aAAa,CAC3B,IAAI,EAAE,QAAQ,EACd,IAAI,CAAC,EAAE,aAAa,GACnB,SAAS,mBAAmB,EAAE,CAGhC;AAED,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,QAAQ,EACd,IAAI,EAAE,OAAO,GAAG,OAAO,GAAG,UAAU,GAAG,QAAQ,GAC9C,MAAM,EAAE,CAQV;AAED,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,EAAE,CAIlE;AAoJD,wBAAgB,oBAAoB,CAClC,QAAQ,GAAE,SAAS,cAAc,EAAkB,GAClD,IAAI,CAiEN"}
@@ -28,7 +28,7 @@ function skill(name, scopes = BOTH_SCOPES) {
28
28
  ownership: scopes.length === 1 ? { user: 'managed' } : BOTH_MANAGED,
29
29
  };
30
30
  }
31
- function agent(name) {
31
+ function agent(name, options = {}) {
32
32
  return {
33
33
  id: `agent:${name}`,
34
34
  kind: 'agent',
@@ -36,6 +36,7 @@ function agent(name) {
36
36
  destination: `.agents/agents/${name}`,
37
37
  scopes: BOTH_SCOPES,
38
38
  ownership: BOTH_MANAGED,
39
+ ...options,
39
40
  };
40
41
  }
41
42
  function template(name, kind = 'template') {
@@ -264,8 +265,26 @@ export const PACK_MANIFEST = [
264
265
  allowedScopes: BOTH_SCOPES,
265
266
  defaultScope: 'user',
266
267
  assets: [
267
- ...['analyze', 'compare', 'deep-research', 'skeptic', 'synthesize'].map((name) => skill(name)),
268
+ ...[
269
+ 'analyze',
270
+ 'compare',
271
+ 'deep-research',
272
+ 'recon',
273
+ 'skeptic',
274
+ 'synthesize',
275
+ ].map((name) => skill(name)),
268
276
  agent('skeptical-evaluator.md'),
277
+ agent('recon-worker.md', { userMaterializable: true }),
278
+ ],
279
+ dependencies: [
280
+ {
281
+ pack: 'utility',
282
+ scope: 'same',
283
+ assets: [
284
+ 'skill:oat-dispatch-subagents',
285
+ 'skill:subagent-orchestration',
286
+ ],
287
+ },
269
288
  ],
270
289
  },
271
290
  {
@@ -335,15 +354,84 @@ function validateAsset(pack, asset) {
335
354
  throw new Error(`Pack ${pack.name} asset ${asset.id} declares ownership outside scope ${scope}`);
336
355
  }
337
356
  }
357
+ if (asset.userMaterializable) {
358
+ if (asset.kind !== 'agent') {
359
+ throw new Error(`Pack ${pack.name} user materializable asset ${asset.id} must be an agent`);
360
+ }
361
+ if (!asset.scopes.includes('user') || asset.ownership.user !== 'managed') {
362
+ throw new Error(`Pack ${pack.name} user materializable agent ${asset.id} must support user scope with managed ownership`);
363
+ }
364
+ }
365
+ }
366
+ function validateDependencies(manifest, packs) {
367
+ const graph = new Map();
368
+ for (const pack of manifest) {
369
+ const edges = new Set();
370
+ for (const dependency of pack.dependencies ?? []) {
371
+ if (dependency.scope !== 'same') {
372
+ throw new Error(`Pack ${pack.name} dependency on ${dependency.pack} must use same scope`);
373
+ }
374
+ const owner = packs.get(dependency.pack);
375
+ if (!owner) {
376
+ throw new Error(`Pack ${pack.name} declares unknown dependency pack ${dependency.pack}`);
377
+ }
378
+ if (dependency.assets.length === 0) {
379
+ throw new Error(`Pack ${pack.name} dependency on ${dependency.pack} selects no assets`);
380
+ }
381
+ for (const scope of pack.allowedScopes) {
382
+ if (!owner.allowedScopes.includes(scope)) {
383
+ throw new Error(`Pack ${pack.name} dependency on ${dependency.pack} cannot resolve at same scope ${scope}`);
384
+ }
385
+ }
386
+ for (const assetId of dependency.assets) {
387
+ const edge = `${dependency.pack}:${assetId}`;
388
+ if (edges.has(edge)) {
389
+ throw new Error(`Pack ${pack.name} has duplicate dependency edge ${edge}`);
390
+ }
391
+ edges.add(edge);
392
+ const asset = owner.assets.find(({ id }) => id === assetId);
393
+ if (!asset) {
394
+ throw new Error(`Pack ${pack.name} declares unknown dependency asset ${dependency.pack}/${assetId}`);
395
+ }
396
+ for (const scope of pack.allowedScopes) {
397
+ if (!asset.scopes.includes(scope)) {
398
+ throw new Error(`Pack ${pack.name} dependency asset ${dependency.pack}/${assetId} does not support same scope ${scope}`);
399
+ }
400
+ }
401
+ }
402
+ const targets = graph.get(pack.name) ?? new Set();
403
+ targets.add(dependency.pack);
404
+ graph.set(pack.name, targets);
405
+ }
406
+ }
407
+ const visiting = new Set();
408
+ const visited = new Set();
409
+ const visit = (pack, path) => {
410
+ if (visiting.has(pack)) {
411
+ throw new Error(`Pack dependency cycle: ${[...path, pack].join(' -> ')}`);
412
+ }
413
+ if (visited.has(pack))
414
+ return;
415
+ visiting.add(pack);
416
+ for (const dependency of graph.get(pack) ?? []) {
417
+ visit(dependency, [...path, pack]);
418
+ }
419
+ visiting.delete(pack);
420
+ visited.add(pack);
421
+ };
422
+ for (const pack of packs.keys())
423
+ visit(pack, []);
338
424
  }
339
425
  export function validatePackManifest(manifest = PACK_MANIFEST) {
340
426
  const packNames = new Set();
427
+ const packs = new Map();
341
428
  const destinations = new Map();
342
429
  for (const pack of manifest) {
343
430
  if (packNames.has(pack.name)) {
344
431
  throw new Error(`Duplicate pack name: ${pack.name}`);
345
432
  }
346
433
  packNames.add(pack.name);
434
+ packs.set(pack.name, pack);
347
435
  if (!pack.allowedScopes.includes(pack.defaultScope)) {
348
436
  throw new Error(`Pack ${pack.name} default scope ${pack.defaultScope} is not allowed`);
349
437
  }
@@ -362,6 +450,7 @@ export function validatePackManifest(manifest = PACK_MANIFEST) {
362
450
  }
363
451
  }
364
452
  }
453
+ validateDependencies(manifest, packs);
365
454
  for (const [destination, entries] of destinations) {
366
455
  if (entries.length < 2)
367
456
  continue;
@@ -1,4 +1,5 @@
1
1
  import type { ConcreteScope } from '../../../shared/types.js';
2
+ import type { PackDependencyLease } from './pack-dependencies.js';
2
3
  import type { ScopedPackInventory } from './pack-inventory.js';
3
4
  import type { PackAssetGeneration, PackCompleteness, PackName } from './types.js';
4
5
  export type PackReconcileAction = 'install' | 'update' | 'remove' | 'migrate-destination';
@@ -27,15 +28,25 @@ export type PackReconcileOperation = {
27
28
  pack: PackName;
28
29
  scope: ConcreteScope;
29
30
  enabled: boolean;
31
+ } | {
32
+ kind: 'write-lease';
33
+ pack: PackName;
34
+ scope: ConcreteScope;
35
+ requiredBy: PackName;
36
+ enabled: boolean;
30
37
  };
31
38
  export interface PackReconcilePlan {
32
39
  pack: PackName;
33
40
  scope: ConcreteScope;
34
41
  action: PackReconcileAction;
35
42
  operations: readonly PackReconcileOperation[];
36
- expectedCompleteness: PackCompleteness;
43
+ expectedCompleteness: PackCompleteness | null;
44
+ selectedAssetIds: readonly string[];
45
+ expectedAssetStatus: 'current' | 'missing' | null;
46
+ expectedAssetStatuses?: Readonly<Record<string, 'current' | 'missing'>>;
37
47
  changedCanonicalPaths: readonly string[];
38
48
  retainedAssets: readonly PackSharedOwnerRetention[];
49
+ retainedDependencyAssetIds: readonly string[];
39
50
  }
40
51
  export interface PackSharedOwnerRetention {
41
52
  assetId: string;
@@ -51,6 +62,9 @@ export interface PlanPackReconcileInput {
51
62
  action: PackReconcileAction;
52
63
  inventory: ScopedPackInventory;
53
64
  retainedAssets?: readonly PackSharedOwnerRetention[];
65
+ retainedDependencyAssetIds?: readonly string[];
66
+ assetIds?: readonly string[];
67
+ dependency?: PackDependencyLease;
54
68
  }
55
69
  export interface ResolveSharedOwnerRetentionsInput {
56
70
  packs: readonly PackName[];
@@ -1 +1 @@
1
- {"version":3,"file":"pack-reconcile.d.ts","sourceRoot":"","sources":["../../../../src/commands/tools/shared/pack-reconcile.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEnD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAE5D,OAAO,KAAK,EAEV,mBAAmB,EACnB,gBAAgB,EAChB,QAAQ,EACT,MAAM,SAAS,CAAC;AAEjB,MAAM,MAAM,mBAAmB,GAC3B,SAAS,GACT,QAAQ,GACR,QAAQ,GACR,qBAAqB,CAAC;AAE1B,MAAM,MAAM,sBAAsB,GAC9B;IACE,IAAI,EAAE,UAAU,GAAG,WAAW,CAAC;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,OAAO,CAAC;CAChB,GACD;IACE,IAAI,EAAE,iBAAiB,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,mBAAmB,CAAC;IAChC,WAAW,EAAE,MAAM,CAAC;CACrB,GACD;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC9D;IAAE,IAAI,EAAE,YAAY,GAAG,aAAa,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACrE;IACE,IAAI,EAAE,cAAc,CAAC;IACrB,IAAI,EAAE,QAAQ,CAAC;IACf,KAAK,EAAE,aAAa,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC;AAEN,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,QAAQ,CAAC;IACf,KAAK,EAAE,aAAa,CAAC;IACrB,MAAM,EAAE,mBAAmB,CAAC;IAC5B,UAAU,EAAE,SAAS,sBAAsB,EAAE,CAAC;IAC9C,oBAAoB,EAAE,gBAAgB,CAAC;IACvC,qBAAqB,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC,cAAc,EAAE,SAAS,wBAAwB,EAAE,CAAC;CACrD;AAED,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,SAAS,QAAQ,EAAE,CAAC;IAChC,MAAM,EAAE,uBAAuB,CAAC;CACjC;AAED,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,QAAQ,CAAC;IACf,KAAK,EAAE,aAAa,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,mBAAmB,CAAC;IAC5B,SAAS,EAAE,mBAAmB,CAAC;IAC/B,cAAc,CAAC,EAAE,SAAS,wBAAwB,EAAE,CAAC;CACtD;AAED,MAAM,WAAW,iCAAiC;IAChD,KAAK,EAAE,SAAS,QAAQ,EAAE,CAAC;IAC3B,KAAK,EAAE,aAAa,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,oBAAoB,EAAE,CACpB,IAAI,EAAE,QAAQ,EACd,KAAK,EAAE,aAAa,EACpB,SAAS,EAAE,MAAM,KACd,OAAO,CAAC,OAAO,CAAC,CAAC;CACvB;AAED,wBAAsB,4BAA4B,CAChD,KAAK,EAAE,iCAAiC,GACvC,OAAO,CAAC,wBAAwB,EAAE,CAAC,CAmDrC;AAqFD,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,sBAAsB,GAC5B,iBAAiB,CA4EnB;AAED,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,iBAAiB,GAAG,MAAM,CAE1E"}
1
+ {"version":3,"file":"pack-reconcile.d.ts","sourceRoot":"","sources":["../../../../src/commands/tools/shared/pack-reconcile.ts"],"names":[],"mappings":"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"}
@@ -95,6 +95,8 @@ function removalOperation(asset, input) {
95
95
  if (input.retainedAssets?.some(({ assetId }) => assetId === asset.id)) {
96
96
  return null;
97
97
  }
98
+ if (input.retainedDependencyAssetIds?.includes(asset.id))
99
+ return null;
98
100
  const observed = input.inventory.assets.find(({ definition }) => definition.id === asset.id);
99
101
  if (!observed || observed.status === 'missing')
100
102
  return null;
@@ -113,13 +115,27 @@ export function planPackReconcile(input) {
113
115
  if (!packDefinition.allowedScopes.includes(input.scope)) {
114
116
  throw new Error(`Pack ${input.pack} does not allow ${input.scope} scope`);
115
117
  }
116
- const applicableAssets = packDefinition.assets.filter(({ scopes }) => scopes.includes(input.scope));
118
+ const allApplicableAssets = packDefinition.assets.filter(({ scopes }) => scopes.includes(input.scope));
119
+ const requestedAssetIds = new Set(input.assetIds ?? []);
120
+ if (requestedAssetIds.size > 0) {
121
+ for (const assetId of requestedAssetIds) {
122
+ if (!allApplicableAssets.some(({ id }) => id === assetId)) {
123
+ throw new Error(`Pack ${input.pack} dependency selected unknown or out-of-scope asset ${assetId}`);
124
+ }
125
+ }
126
+ }
127
+ const applicableAssets = requestedAssetIds.size > 0
128
+ ? allApplicableAssets.filter(({ id }) => requestedAssetIds.has(id))
129
+ : allApplicableAssets;
117
130
  const changedCanonicalPaths = [];
118
131
  const operations = [];
119
132
  const retainedAssets = input.retainedAssets ?? [];
133
+ const directDependencyRetention = input.dependency?.lease === 'release' && input.inventory.intent.direct;
120
134
  for (const asset of applicableAssets) {
121
135
  const assetOperations = input.action === 'remove'
122
- ? [removalOperation(asset, input)].filter((operation) => operation !== null)
136
+ ? [
137
+ directDependencyRetention ? null : removalOperation(asset, input),
138
+ ].filter((operation) => operation !== null)
123
139
  : materializationOperations(asset, input);
124
140
  operations.push(...assetOperations);
125
141
  if (assetOperations.length > 0) {
@@ -128,32 +144,78 @@ export function planPackReconcile(input) {
128
144
  changedCanonicalPaths.push(path);
129
145
  }
130
146
  }
131
- const desiredIntent = input.action !== 'remove';
132
- if (input.inventory.intent.enabled !== desiredIntent ||
133
- (desiredIntent && input.inventory.intent.source !== 'declared')) {
134
- operations.push({
135
- kind: 'write-intent',
136
- pack: input.pack,
137
- scope: input.scope,
138
- enabled: desiredIntent,
139
- });
147
+ if (input.dependency) {
148
+ const desiredLease = input.dependency.lease === 'acquire';
149
+ const hasLease = input.inventory.intent.requiredBy.includes(input.dependency.requiredBy);
150
+ if (hasLease !== desiredLease) {
151
+ operations.push({
152
+ kind: 'write-lease',
153
+ pack: input.pack,
154
+ scope: input.scope,
155
+ requiredBy: input.dependency.requiredBy,
156
+ enabled: desiredLease,
157
+ });
158
+ }
159
+ }
160
+ else {
161
+ const desiredIntent = input.action !== 'remove';
162
+ if (input.inventory.intent.direct !== desiredIntent ||
163
+ (desiredIntent && input.inventory.intent.source !== 'declared')) {
164
+ operations.push({
165
+ kind: 'write-intent',
166
+ pack: input.pack,
167
+ scope: input.scope,
168
+ enabled: desiredIntent,
169
+ });
170
+ }
140
171
  }
141
172
  const managedAssets = applicableAssets.filter((asset) => asset.ownership[input.scope] === 'managed');
142
- const retainedPresent = managedAssets.filter((asset) => retainedAssets.some(({ assetId }) => assetId === asset.id) &&
173
+ const retainedPresent = managedAssets.filter((asset) => (retainedAssets.some(({ assetId }) => assetId === asset.id) ||
174
+ input.retainedDependencyAssetIds?.includes(asset.id)) &&
143
175
  input.inventory.assets.some(({ definition, status }) => definition.id === asset.id && status !== 'missing')).length;
144
176
  const expectedRemovalCompleteness = retainedPresent === 0
145
177
  ? 'absent'
146
178
  : retainedPresent === managedAssets.length
147
179
  ? 'complete'
148
180
  : 'partial';
181
+ const retainedSelectedAssets = applicableAssets.filter((asset) => retainedAssets.some(({ assetId }) => assetId === asset.id) ||
182
+ input.retainedDependencyAssetIds?.includes(asset.id)).length;
183
+ const expectedSelectedRemovalStatus = retainedSelectedAssets === 0
184
+ ? 'missing'
185
+ : retainedSelectedAssets === applicableAssets.length
186
+ ? 'current'
187
+ : null;
188
+ const expectedAssetStatuses = requestedAssetIds.size === 0
189
+ ? undefined
190
+ : Object.fromEntries(applicableAssets.map((asset) => [
191
+ asset.id,
192
+ input.action !== 'remove' ||
193
+ directDependencyRetention ||
194
+ retainedAssets.some(({ assetId }) => assetId === asset.id) ||
195
+ input.retainedDependencyAssetIds?.includes(asset.id)
196
+ ? 'current'
197
+ : 'missing',
198
+ ]));
149
199
  return {
150
200
  pack: input.pack,
151
201
  scope: input.scope,
152
202
  action: input.action,
153
203
  operations,
154
- expectedCompleteness: input.action === 'remove' ? expectedRemovalCompleteness : 'complete',
204
+ expectedCompleteness: requestedAssetIds.size > 0
205
+ ? null
206
+ : input.action === 'remove'
207
+ ? expectedRemovalCompleteness
208
+ : 'complete',
209
+ selectedAssetIds: applicableAssets.map(({ id }) => id),
210
+ expectedAssetStatus: requestedAssetIds.size === 0
211
+ ? null
212
+ : input.action !== 'remove' || directDependencyRetention
213
+ ? 'current'
214
+ : expectedSelectedRemovalStatus,
215
+ expectedAssetStatuses,
155
216
  changedCanonicalPaths: [...new Set(changedCanonicalPaths)],
156
217
  retainedAssets,
218
+ retainedDependencyAssetIds: input.retainedDependencyAssetIds ?? [],
157
219
  };
158
220
  }
159
221
  export function serializePackReconcilePlan(plan) {
@@ -1,10 +1,14 @@
1
1
  import type { ConcreteScope } from '../../../shared/types.js';
2
2
  import type { PackName } from './types.js';
3
3
  export type PackIntentSource = 'declared' | 'inferred-legacy' | 'none';
4
+ export type PackIntentState = 'direct' | 'transitive' | 'legacy-inferred' | 'absent';
4
5
  export interface ScopedPackIntent {
5
6
  pack: PackName;
6
7
  scope: ConcreteScope;
7
8
  enabled: boolean;
9
+ direct: boolean;
10
+ requiredBy: PackName[];
11
+ state: PackIntentState;
8
12
  source: PackIntentSource;
9
13
  configPath: string;
10
14
  diagnostics: PackIntentDiagnostic[];
@@ -22,6 +26,9 @@ export interface IntentReadInput {
22
26
  export interface IntentWriteInput extends IntentReadInput {
23
27
  enabled: boolean;
24
28
  }
29
+ export interface LeaseWriteInput extends IntentWriteInput {
30
+ requiredBy: PackName;
31
+ }
25
32
  /**
26
33
  * Returns ownership evidence that is safe to use when deciding whether a
27
34
  * shared managed asset must be retained. A shared asset cannot prove that any
@@ -31,4 +38,5 @@ export interface IntentWriteInput extends IntentReadInput {
31
38
  export declare function hasScopedPackOwnershipEvidence(input: IntentReadInput): Promise<boolean>;
32
39
  export declare function readScopedPackIntent(input: IntentReadInput): Promise<ScopedPackIntent>;
33
40
  export declare function writeScopedPackIntent(input: IntentWriteInput): Promise<void>;
41
+ export declare function writeScopedPackLease(input: LeaseWriteInput): Promise<void>;
34
42
  //# sourceMappingURL=scoped-pack-intent.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"scoped-pack-intent.d.ts","sourceRoot":"","sources":["../../../../src/commands/tools/shared/scoped-pack-intent.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAGnD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAExC,MAAM,MAAM,gBAAgB,GAAG,UAAU,GAAG,iBAAiB,GAAG,MAAM,CAAC;AAEvE,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,QAAQ,CAAC;IACf,KAAK,EAAE,aAAa,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,gBAAgB,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,oBAAoB,EAAE,CAAC;CACrC;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,uBAAuB,CAAC;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,QAAQ,CAAC;IACf,KAAK,EAAE,aAAa,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,gBAAiB,SAAQ,eAAe;IACvD,OAAO,EAAE,OAAO,CAAC;CAClB;AAoDD;;;;;GAKG;AACH,wBAAsB,8BAA8B,CAClD,KAAK,EAAE,eAAe,GACrB,OAAO,CAAC,OAAO,CAAC,CAgBlB;AAED,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,eAAe,GACrB,OAAO,CAAC,gBAAgB,CAAC,CAsB3B;AAgBD,wBAAsB,qBAAqB,CACzC,KAAK,EAAE,gBAAgB,GACtB,OAAO,CAAC,IAAI,CAAC,CAwBf"}
1
+ {"version":3,"file":"scoped-pack-intent.d.ts","sourceRoot":"","sources":["../../../../src/commands/tools/shared/scoped-pack-intent.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAGnD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAExC,MAAM,MAAM,gBAAgB,GAAG,UAAU,GAAG,iBAAiB,GAAG,MAAM,CAAC;AACvE,MAAM,MAAM,eAAe,GACvB,QAAQ,GACR,YAAY,GACZ,iBAAiB,GACjB,QAAQ,CAAC;AAEb,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,QAAQ,CAAC;IACf,KAAK,EAAE,aAAa,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,UAAU,EAAE,QAAQ,EAAE,CAAC;IACvB,KAAK,EAAE,eAAe,CAAC;IACvB,MAAM,EAAE,gBAAgB,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,oBAAoB,EAAE,CAAC;CACrC;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,uBAAuB,CAAC;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,QAAQ,CAAC;IACf,KAAK,EAAE,aAAa,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,gBAAiB,SAAQ,eAAe;IACvD,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,eAAgB,SAAQ,gBAAgB;IACvD,UAAU,EAAE,QAAQ,CAAC;CACtB;AAoDD;;;;;GAKG;AACH,wBAAsB,8BAA8B,CAClD,KAAK,EAAE,eAAe,GACrB,OAAO,CAAC,OAAO,CAAC,CAmBlB;AAED,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,eAAe,GACrB,OAAO,CAAC,gBAAgB,CAAC,CAwC3B;AAmED,wBAAsB,qBAAqB,CACzC,KAAK,EAAE,gBAAgB,GACtB,OAAO,CAAC,IAAI,CAAC,CAIf;AAED,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,eAAe,GACrB,OAAO,CAAC,IAAI,CAAC,CAOf"}
@@ -45,7 +45,8 @@ async function findPhysicalManagedAssets(input) {
45
45
  */
46
46
  export async function hasScopedPackOwnershipEvidence(input) {
47
47
  const config = await readConcreteConfig(input);
48
- if (config.tools?.[input.pack] === true) {
48
+ if (config.tools?.[input.pack] === true ||
49
+ (config.tools?.requiredBy?.[input.pack]?.length ?? 0) > 0) {
49
50
  return true;
50
51
  }
51
52
  const paths = getPackDefinition(input.pack)
@@ -58,8 +59,11 @@ export async function hasScopedPackOwnershipEvidence(input) {
58
59
  export async function readScopedPackIntent(input) {
59
60
  const config = await readConcreteConfig(input);
60
61
  const declared = config.tools?.[input.pack] === true;
62
+ const requiredBy = [...(config.tools?.requiredBy?.[input.pack] ?? [])].sort();
61
63
  const legacyFalse = config.tools?.[input.pack] === false;
62
- const physicalAssets = declared ? [] : await findPhysicalManagedAssets(input);
64
+ const physicalAssets = declared || requiredBy.length > 0
65
+ ? []
66
+ : await findPhysicalManagedAssets(input);
63
67
  const inferred = physicalAssets.length > 0;
64
68
  const diagnostics = [];
65
69
  if (legacyFalse && inferred) {
@@ -72,8 +76,21 @@ export async function readScopedPackIntent(input) {
72
76
  return {
73
77
  pack: input.pack,
74
78
  scope: input.scope,
75
- enabled: declared || inferred,
76
- source: declared ? 'declared' : inferred ? 'inferred-legacy' : 'none',
79
+ enabled: declared || requiredBy.length > 0 || inferred,
80
+ direct: declared,
81
+ requiredBy,
82
+ state: declared
83
+ ? 'direct'
84
+ : requiredBy.length > 0
85
+ ? 'transitive'
86
+ : inferred
87
+ ? 'legacy-inferred'
88
+ : 'absent',
89
+ source: declared || requiredBy.length > 0
90
+ ? 'declared'
91
+ : inferred
92
+ ? 'inferred-legacy'
93
+ : 'none',
77
94
  configPath: configPathForScope(input.scopeRoot),
78
95
  diagnostics,
79
96
  };
@@ -88,29 +105,58 @@ function updateTools(tools, pack, enabled) {
88
105
  }
89
106
  return Object.keys(next).length > 0 ? next : undefined;
90
107
  }
91
- export async function writeScopedPackIntent(input) {
108
+ function updateRequiredBy(tools, pack, requiredBy, enabled) {
109
+ const next = { ...tools };
110
+ const leases = new Set(next.requiredBy?.[pack] ?? []);
111
+ if (enabled) {
112
+ leases.add(requiredBy);
113
+ }
114
+ else {
115
+ leases.delete(requiredBy);
116
+ }
117
+ const requiredByConfig = { ...next.requiredBy };
118
+ if (leases.size > 0) {
119
+ requiredByConfig[pack] = [...leases].sort();
120
+ }
121
+ else {
122
+ delete requiredByConfig[pack];
123
+ }
124
+ if (Object.keys(requiredByConfig).length > 0) {
125
+ next.requiredBy = requiredByConfig;
126
+ }
127
+ else {
128
+ delete next.requiredBy;
129
+ }
130
+ return Object.keys(next).length > 0 ? next : undefined;
131
+ }
132
+ async function writeToolsConfig(input, update) {
92
133
  if (input.scope === 'project') {
93
134
  const config = await readOatConfig(input.scopeRoot);
94
- const tools = updateTools(config.tools, input.pack, input.enabled);
135
+ const tools = update(config.tools);
95
136
  const next = { ...config };
96
- if (tools) {
137
+ if (tools)
97
138
  next.tools = tools;
98
- }
99
- else {
139
+ else
100
140
  delete next.tools;
101
- }
102
141
  await writeOatConfig(input.scopeRoot, next);
103
142
  return;
104
143
  }
105
144
  const configDir = userConfigDir(input.scopeRoot);
106
145
  const config = await readUserConfig(configDir);
107
- const tools = updateTools(config.tools, input.pack, input.enabled);
146
+ const tools = update(config.tools);
108
147
  const next = { ...config };
109
- if (tools) {
148
+ if (tools)
110
149
  next.tools = tools;
111
- }
112
- else {
150
+ else
113
151
  delete next.tools;
114
- }
115
152
  await writeUserConfig(configDir, next);
116
153
  }
154
+ export async function writeScopedPackIntent(input) {
155
+ await writeToolsConfig(input, (tools) => updateTools(tools, input.pack, input.enabled));
156
+ }
157
+ export async function writeScopedPackLease(input) {
158
+ if (input.pack === input.requiredBy) {
159
+ throw new Error(`Pack ${input.pack} cannot require itself`);
160
+ }
161
+ await writeToolsConfig(input, (tools) => updateRequiredBy(tools, input.pack, input.requiredBy, input.enabled));
162
+ }
@@ -13,12 +13,19 @@ export interface PackAssetDefinition {
13
13
  ownership: Partial<Record<ConcreteScope, PackAssetOwnership>>;
14
14
  executable?: boolean;
15
15
  sharedOwner?: string;
16
+ userMaterializable?: boolean;
17
+ }
18
+ export interface PackDependencyDefinition {
19
+ pack: PackName;
20
+ scope: 'same';
21
+ assets: readonly string[];
16
22
  }
17
23
  export interface PackDefinition {
18
24
  name: PackName;
19
25
  allowedScopes: readonly ConcreteScope[];
20
26
  defaultScope: ConcreteScope;
21
27
  assets: readonly PackAssetDefinition[];
28
+ dependencies?: readonly PackDependencyDefinition[];
22
29
  }
23
30
  export type PackAssetStatus = 'missing' | 'current' | 'outdated' | 'newer' | 'present';
24
31
  export type PackCompleteness = 'complete' | 'partial' | 'absent';