@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.
- package/assets/agents/recon-worker.md +105 -0
- package/assets/bundle-metadata.json +1 -1
- package/assets/docs/cli-utilities/tool-packs.md +62 -8
- package/assets/docs/contributing/skills.md +26 -0
- package/assets/docs/workflows/skills/index.md +3 -0
- package/assets/docs/workflows/skills/recon.md +194 -0
- package/assets/public-package-versions.json +4 -4
- package/assets/skills/oat-dispatch-subagents/SKILL.md +105 -4
- package/assets/skills/oat-dispatch-subagents/references/record-schema.md +226 -0
- package/assets/skills/oat-project-autonomous/references/gate-inventory.md +2 -2
- package/assets/skills/oat-project-document/references/docs/autonomy-contract.md +2 -2
- package/assets/skills/oat-project-implement/references/docs/autonomy-contract.md +2 -2
- package/assets/skills/oat-project-pr-final/references/docs/autonomy-contract.md +2 -2
- package/assets/skills/oat-project-quick-start/references/docs/autonomy-contract.md +2 -2
- package/assets/skills/recon/SKILL.md +260 -0
- package/assets/skills/recon/references/packet-contract.md +307 -0
- package/assets/skills/recon/references/profiles.md +58 -0
- package/assets/skills/recon/references/worker-contract.md +71 -0
- package/assets/skills/recon/scripts/create-review-brief.mjs +452 -0
- package/assets/skills/recon/scripts/lib/canonical-json.mjs +55 -0
- package/assets/skills/recon/scripts/lib/contracts.mjs +2462 -0
- package/assets/skills/recon/scripts/lib/safe-path.mjs +120 -0
- package/assets/skills/recon/scripts/lib/validated-run.mjs +86 -0
- package/assets/skills/recon/scripts/reconcile-ledger.mjs +360 -0
- package/assets/skills/recon/scripts/render-packet.mjs +392 -0
- package/assets/skills/recon/scripts/validate-artifact.mjs +96 -0
- package/assets/skills/recon/scripts/validate-packet.mjs +2359 -0
- package/dist/commands/doctor/index.js +5 -5
- package/dist/commands/init/index.d.ts.map +1 -1
- package/dist/commands/init/index.js +7 -8
- package/dist/commands/status/index.d.ts +2 -2
- package/dist/commands/status/index.d.ts.map +1 -1
- package/dist/commands/status/index.js +8 -8
- package/dist/commands/sync/index.d.ts.map +1 -1
- package/dist/commands/sync/index.js +25 -9
- package/dist/commands/sync/sync.types.d.ts +2 -2
- package/dist/commands/sync/sync.types.d.ts.map +1 -1
- package/dist/commands/tools/migrate/index.d.ts.map +1 -1
- package/dist/commands/tools/migrate/index.js +25 -1
- package/dist/commands/tools/migrate/migrate-pack.d.ts +5 -0
- package/dist/commands/tools/migrate/migrate-pack.d.ts.map +1 -1
- package/dist/commands/tools/migrate/migrate-pack.js +66 -7
- package/dist/commands/tools/remove/index.js +1 -1
- package/dist/commands/tools/remove/remove-tools.d.ts +5 -1
- package/dist/commands/tools/remove/remove-tools.d.ts.map +1 -1
- package/dist/commands/tools/remove/remove-tools.js +81 -1
- package/dist/commands/tools/shared/apply-pack-reconcile.d.ts +3 -0
- package/dist/commands/tools/shared/apply-pack-reconcile.d.ts.map +1 -1
- package/dist/commands/tools/shared/apply-pack-reconcile.js +40 -9
- package/dist/commands/tools/shared/format-pack-inventory.d.ts.map +1 -1
- package/dist/commands/tools/shared/format-pack-inventory.js +3 -0
- package/dist/commands/tools/shared/install-sync-context.d.ts +2 -1
- package/dist/commands/tools/shared/install-sync-context.d.ts.map +1 -1
- package/dist/commands/tools/shared/install-sync-context.js +14 -3
- package/dist/commands/tools/shared/pack-dependencies.d.ts +14 -0
- package/dist/commands/tools/shared/pack-dependencies.d.ts.map +1 -0
- package/dist/commands/tools/shared/pack-dependencies.js +54 -0
- package/dist/commands/tools/shared/pack-inventory.d.ts.map +1 -1
- package/dist/commands/tools/shared/pack-inventory.js +9 -9
- package/dist/commands/tools/shared/pack-lifecycle.d.ts +9 -1
- package/dist/commands/tools/shared/pack-lifecycle.d.ts.map +1 -1
- package/dist/commands/tools/shared/pack-lifecycle.js +102 -9
- package/dist/commands/tools/shared/pack-manifest.d.ts +2 -2
- package/dist/commands/tools/shared/pack-manifest.d.ts.map +1 -1
- package/dist/commands/tools/shared/pack-manifest.js +91 -2
- package/dist/commands/tools/shared/pack-reconcile.d.ts +15 -1
- package/dist/commands/tools/shared/pack-reconcile.d.ts.map +1 -1
- package/dist/commands/tools/shared/pack-reconcile.js +75 -13
- package/dist/commands/tools/shared/scoped-pack-intent.d.ts +8 -0
- package/dist/commands/tools/shared/scoped-pack-intent.d.ts.map +1 -1
- package/dist/commands/tools/shared/scoped-pack-intent.js +61 -15
- package/dist/commands/tools/shared/types.d.ts +7 -0
- package/dist/commands/tools/shared/types.d.ts.map +1 -1
- package/dist/commands/tools/update/update-tools.d.ts.map +1 -1
- package/dist/commands/tools/update/update-tools.js +7 -1
- package/dist/config/oat-config.d.ts +5 -1
- package/dist/config/oat-config.d.ts.map +1 -1
- package/dist/config/oat-config.js +19 -0
- package/dist/engine/index.d.ts +2 -2
- package/dist/engine/index.d.ts.map +1 -1
- package/dist/engine/index.js +1 -1
- package/dist/engine/scanner.d.ts +16 -1
- package/dist/engine/scanner.d.ts.map +1 -1
- package/dist/engine/scanner.js +98 -4
- package/dist/providers/codex/codec/sync-extension.d.ts +1 -1
- package/dist/providers/codex/codec/sync-extension.d.ts.map +1 -1
- package/dist/providers/codex/codec/sync-extension.js +7 -7
- package/dist/providers/cursor/codec/sync-extension.d.ts +1 -1
- package/dist/providers/cursor/codec/sync-extension.d.ts.map +1 -1
- package/dist/providers/cursor/codec/sync-extension.js +11 -13
- package/dist/shared/types.d.ts +1 -0
- package/dist/shared/types.d.ts.map +1 -1
- package/dist/shared/types.js +3 -0
- 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
|
|
33
|
-
|
|
34
|
-
|
|
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:
|
|
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
|
-
|
|
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;
|
|
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
|
-
...[
|
|
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;
|
|
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
|
|
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
|
-
? [
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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:
|
|
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;
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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';
|