@agentxm/workspace-sync 0.28.4-bootstrap.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +110 -0
- package/dist/src/errors.d.ts +29 -0
- package/dist/src/errors.js +25 -0
- package/dist/src/failure-adapter.d.ts +18 -0
- package/dist/src/failure-adapter.js +11 -0
- package/dist/src/index.d.ts +14 -0
- package/dist/src/index.js +14 -0
- package/dist/src/materialize.d.ts +125 -0
- package/dist/src/materialize.js +547 -0
- package/dist/src/plan.d.ts +100 -0
- package/dist/src/plan.js +456 -0
- package/dist/src/rendered-file-cleanup.d.ts +45 -0
- package/dist/src/rendered-file-cleanup.js +381 -0
- package/package.json +55 -0
package/dist/src/plan.js
ADDED
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sync plan policy: recovery identities, aggregate-unit reconciliation steps
|
|
3
|
+
* (knowledge discovery, managed hook projections, instruction files, stale
|
|
4
|
+
* managed-projection cleanup, inline MCP servers and managed-entry pruning),
|
|
5
|
+
* and the plan-assembly ordering that realizes desired state. The CLI keeps
|
|
6
|
+
* argument parsing, confirmation, rendering, and plan execution.
|
|
7
|
+
*
|
|
8
|
+
* The application supplies a {@link SyncFailureAdapter}: its boundary mapping
|
|
9
|
+
* from typed failures to the kernel's `StepFailure`, so step categories and
|
|
10
|
+
* details stay byte-identical with the boundary's own rendering.
|
|
11
|
+
*
|
|
12
|
+
* @experimental This API is unstable and may change without notice.
|
|
13
|
+
*/
|
|
14
|
+
import * as Effect from "effect/Effect";
|
|
15
|
+
import * as FileSystem from "effect/FileSystem";
|
|
16
|
+
import * as Option from "effect/Option";
|
|
17
|
+
import * as Path from "effect/Path";
|
|
18
|
+
import * as Result from "effect/Result";
|
|
19
|
+
import { CodingAgentRepository, HookManager, KnowledgeManager, RuleManager, applyPlannedProjections, inspectMcpServerAcrossAgents, observeInstructionProjection, projectionFactRequiresReconciliation, pruneManagedMcpServersForAgent, resolveInstructionsConfig, syncInlineMcpServerToAgents, assertInstructionTargetsSafe, assertInstructionsGitignoreSafe, instructionProjectionEffects, instructionProjectionIsCurrent, } from "@agentxm/extension-workspace";
|
|
20
|
+
import { StepFailure, } from "@agentxm/workspace-operations";
|
|
21
|
+
import { WorkspaceMutations } from "@agentxm/workspace-state";
|
|
22
|
+
import { cleanupStaleManagedSkillDirectories, cleanupStaleManagedSubagentFiles, } from "./rendered-file-cleanup.js";
|
|
23
|
+
export const SYNC_RECOVERY_IDS = {
|
|
24
|
+
packManifestDivergence: "pack:manifest-divergence",
|
|
25
|
+
extensionConstraintMismatch: "extension:constraint-mismatch",
|
|
26
|
+
inlineMcpCollision: "mcp-server:inline",
|
|
27
|
+
hookProjections: "hook:projections",
|
|
28
|
+
instructionReconcile: "instruction:reconcile",
|
|
29
|
+
};
|
|
30
|
+
/** Executable sync recovery and blocker identities covered by recovery conformance. */
|
|
31
|
+
export const syncRecoveryIdentifiers = [
|
|
32
|
+
SYNC_RECOVERY_IDS.packManifestDivergence,
|
|
33
|
+
SYNC_RECOVERY_IDS.extensionConstraintMismatch,
|
|
34
|
+
SYNC_RECOVERY_IDS.inlineMcpCollision,
|
|
35
|
+
SYNC_RECOVERY_IDS.hookProjections,
|
|
36
|
+
SYNC_RECOVERY_IDS.instructionReconcile,
|
|
37
|
+
];
|
|
38
|
+
export const SYNC_PLAN_NAME = "Sync workspace";
|
|
39
|
+
export const SYNC_PLAN_DESCRIPTION = "Workspace-wide materialization from settings and on-disk extension content";
|
|
40
|
+
export const SYNC_PRESENTATION = {
|
|
41
|
+
verb: { imperative: "sync", past: "Synced", gerund: "Syncing" },
|
|
42
|
+
subject: { singular: "workspace item", plural: "workspace items" },
|
|
43
|
+
};
|
|
44
|
+
// Deliberately duplicated from the CLI-destined renderer helper: a feature
|
|
45
|
+
// package may not depend on application presentation utilities, and this
|
|
46
|
+
// pluralizer is within the sanctioned duplication budget for small pure
|
|
47
|
+
// functions.
|
|
48
|
+
const count = (n, singular, plural) => `${n} ${n === 1 ? singular : (plural ?? `${singular}s`)}`;
|
|
49
|
+
// -----------------------------------------------------------------------------
|
|
50
|
+
// Projection-fact queries
|
|
51
|
+
// -----------------------------------------------------------------------------
|
|
52
|
+
export const projectionFactsNeedReconciliation = (facts) => facts.some(projectionFactRequiresReconciliation);
|
|
53
|
+
export const projectionDivergenceLabel = (label, facts) => {
|
|
54
|
+
const violations = facts.filter(projectionFactRequiresReconciliation);
|
|
55
|
+
const statuses = Array.from(new Set(violations.map(({ observation }) => observation.status))).join(", ");
|
|
56
|
+
const contributors = Array.from(new Set(violations.flatMap(({ affectedContributors }) => affectedContributors)));
|
|
57
|
+
const details = contributors.length === 0 ? statuses : `${statuses}: ${contributors.join(", ")}`;
|
|
58
|
+
return details.length === 0 ? label : `${label} (${details})`;
|
|
59
|
+
};
|
|
60
|
+
const managedRegionsForFacts = (facts) => facts.flatMap(({ subject }) => subject.owner === undefined
|
|
61
|
+
? []
|
|
62
|
+
: [{ unitId: subject.unitId, path: subject.path, owner: subject.owner }]);
|
|
63
|
+
const projectionFileTargets = (facts) => facts
|
|
64
|
+
.filter(projectionFactRequiresReconciliation)
|
|
65
|
+
.map(({ subject }) => ({
|
|
66
|
+
path: subject.path.split("#", 1)[0] ?? subject.path,
|
|
67
|
+
change: "updated",
|
|
68
|
+
}))
|
|
69
|
+
.filter((target, index, targets) => targets.findIndex((candidate) => candidate.path === target.path) === index);
|
|
70
|
+
const mergeArtifactTargets = (targets) => {
|
|
71
|
+
const byPath = new Map();
|
|
72
|
+
for (const target of targets)
|
|
73
|
+
byPath.set(target.path, target);
|
|
74
|
+
return [...byPath.values()].sort((left, right) => left.path.localeCompare(right.path));
|
|
75
|
+
};
|
|
76
|
+
// -----------------------------------------------------------------------------
|
|
77
|
+
// MCP-server projection steps
|
|
78
|
+
// -----------------------------------------------------------------------------
|
|
79
|
+
export const isInlineMcpServerEntry = (entry) => entry.kind === "inline";
|
|
80
|
+
export const buildInlineMcpServerSyncOperation = ({ name, entry, agentIds, force, ws, adapter, }) => ({
|
|
81
|
+
key: `mcp-server:inline:${name}`,
|
|
82
|
+
label: `mcp-server ${name}`,
|
|
83
|
+
readiness: "ready",
|
|
84
|
+
run: Effect.gen(function* () {
|
|
85
|
+
const inspections = yield* inspectMcpServerAcrossAgents({
|
|
86
|
+
workspaceRoot: ws.baseDir,
|
|
87
|
+
scope: ws.scope,
|
|
88
|
+
agentIds,
|
|
89
|
+
serverName: name,
|
|
90
|
+
entry,
|
|
91
|
+
});
|
|
92
|
+
const inspectionWarnings = inspections.flatMap((inspection) => inspection.status === "drift" || inspection.status === "unmanaged"
|
|
93
|
+
? [
|
|
94
|
+
`${inspection.agentId}: ${inspection.status}${inspection.fields.length > 0 ? ` (${inspection.fields.join(", ")})` : ""}`,
|
|
95
|
+
]
|
|
96
|
+
: []);
|
|
97
|
+
const hasUnownedCollision = inspections.some((inspection) => inspection.status === "unmanaged");
|
|
98
|
+
if (hasUnownedCollision && !force) {
|
|
99
|
+
return {
|
|
100
|
+
result: "error",
|
|
101
|
+
message: `Inline MCP server ${name} collides with unowned native config; move, remove, or adopt the unowned entry before rerunning axm sync`,
|
|
102
|
+
error: new StepFailure({
|
|
103
|
+
category: "conflict",
|
|
104
|
+
detail: `Inline MCP server ${name} collides with unowned native config`,
|
|
105
|
+
}),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
const batchOutcomes = yield* syncInlineMcpServerToAgents(agentIds, {
|
|
109
|
+
workspaceRoot: ws.baseDir,
|
|
110
|
+
serverName: name,
|
|
111
|
+
entry,
|
|
112
|
+
scope: ws.scope,
|
|
113
|
+
});
|
|
114
|
+
const outcomes = agentIds.flatMap((agentId, index) => {
|
|
115
|
+
const outcome = batchOutcomes[index];
|
|
116
|
+
return outcome === undefined ? [] : [{ agentId, outcome }];
|
|
117
|
+
});
|
|
118
|
+
const warningDetails = outcomes.flatMap(({ agentId, outcome }) => {
|
|
119
|
+
if (outcome._tag === "success") {
|
|
120
|
+
return (outcome.warnings ?? []).map((warning) => `${agentId}: ${warning}`);
|
|
121
|
+
}
|
|
122
|
+
return [`${agentId}: ${outcome.reason}`];
|
|
123
|
+
});
|
|
124
|
+
const warnings = [...inspectionWarnings, ...warningDetails];
|
|
125
|
+
return {
|
|
126
|
+
result: "success",
|
|
127
|
+
message: warnings.length === 0
|
|
128
|
+
? `Synced inline MCP server ${name}`
|
|
129
|
+
: `Synced inline MCP server ${name} with ${count(warnings.length, "warning")}`,
|
|
130
|
+
...(warnings.length > 0 ? { warnings } : {}),
|
|
131
|
+
};
|
|
132
|
+
}).pipe(Effect.mapError(adapter.toStepFailure)),
|
|
133
|
+
});
|
|
134
|
+
export const buildMcpServerPruneOperation = ({ declaredServerNames, agentIds, ws, adapter, }) => ({
|
|
135
|
+
key: "mcp-server:prune",
|
|
136
|
+
label: "mcp-server stale managed entries",
|
|
137
|
+
readiness: "ready",
|
|
138
|
+
run: Effect.forEach(agentIds, (agentId) => pruneManagedMcpServersForAgent(agentId, {
|
|
139
|
+
workspaceRoot: ws.baseDir,
|
|
140
|
+
declaredServerNames,
|
|
141
|
+
scope: ws.scope,
|
|
142
|
+
}).pipe(Effect.map((outcome) => ({ agentId, outcome }))), { concurrency: "unbounded" }).pipe(Effect.map((outcomes) => {
|
|
143
|
+
const warnings = outcomes.filter(({ outcome }) => outcome._tag !== "success");
|
|
144
|
+
return {
|
|
145
|
+
result: "success",
|
|
146
|
+
message: warnings.length === 0
|
|
147
|
+
? "Pruned stale managed MCP server entries"
|
|
148
|
+
: `Pruned stale managed MCP server entries with ${count(warnings.length, "warning")}`,
|
|
149
|
+
};
|
|
150
|
+
}), Effect.mapError(adapter.toStepFailure)),
|
|
151
|
+
});
|
|
152
|
+
// -----------------------------------------------------------------------------
|
|
153
|
+
// Aggregate-unit reconciliation steps
|
|
154
|
+
// -----------------------------------------------------------------------------
|
|
155
|
+
export const collectKnowledgeStep = Effect.fn("Sync.collectKnowledgeStep")(function* (args) {
|
|
156
|
+
const manager = yield* KnowledgeManager;
|
|
157
|
+
const ws = yield* WorkspaceMutations;
|
|
158
|
+
const instructions = yield* ws.getInstructionsConfig();
|
|
159
|
+
const instructionFile = resolveInstructionsConfig(Option.isSome(instructions) && instructions.value !== false ? instructions.value : undefined).fileName;
|
|
160
|
+
const previewResult = args.deferPreview === true ? undefined : yield* Effect.result(manager.sync({ dryRun: true }));
|
|
161
|
+
if (previewResult !== undefined && Result.isFailure(previewResult)) {
|
|
162
|
+
return Option.some({
|
|
163
|
+
key: "knowledge:discovery",
|
|
164
|
+
label: "Knowledge discovery",
|
|
165
|
+
readiness: "error",
|
|
166
|
+
errorMessage: args.adapter.toStepFailure(previewResult.failure).detail,
|
|
167
|
+
artifact: {
|
|
168
|
+
path: instructionFile,
|
|
169
|
+
scope: ws.scope,
|
|
170
|
+
change: "unchanged",
|
|
171
|
+
managedRegions: managedRegionsForFacts(args.facts ?? []),
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
const preview = previewResult === undefined ? undefined : previewResult.success;
|
|
176
|
+
if (preview !== undefined && !preview.changed && preview.warnings.length === 0) {
|
|
177
|
+
return Option.none();
|
|
178
|
+
}
|
|
179
|
+
const details = preview?.artifacts
|
|
180
|
+
.filter((artifact) => artifact.change !== "unchanged")
|
|
181
|
+
.map((artifact) => `${artifact.change} ${artifact.path}${artifact.mechanism === undefined ? "" : ` (${artifact.mechanism})`}`) ?? [];
|
|
182
|
+
const message = [...details, ...(preview?.warnings ?? [])].join("; ");
|
|
183
|
+
const artifact = {
|
|
184
|
+
path: instructionFile,
|
|
185
|
+
scope: ws.scope,
|
|
186
|
+
change: preview?.changed === false ? "unchanged" : "updated",
|
|
187
|
+
managedRegions: managedRegionsForFacts(args.facts ?? []),
|
|
188
|
+
};
|
|
189
|
+
return Option.some({
|
|
190
|
+
key: "knowledge:discovery",
|
|
191
|
+
label: "Knowledge discovery",
|
|
192
|
+
readiness: "ready",
|
|
193
|
+
artifact,
|
|
194
|
+
...(message.length === 0 ? {} : { message }),
|
|
195
|
+
run: manager.sync({ dryRun: false }).pipe(Effect.mapError(args.adapter.toStepFailure), Effect.map((result) => {
|
|
196
|
+
const mechanism = result.artifacts.find((artifact) => artifact.mechanism !== undefined)?.mechanism;
|
|
197
|
+
return {
|
|
198
|
+
result: "success",
|
|
199
|
+
message: result.changed
|
|
200
|
+
? "Reconciled Knowledge discovery"
|
|
201
|
+
: "Knowledge discovery already current",
|
|
202
|
+
...(result.warnings.length === 0 ? {} : { warnings: result.warnings }),
|
|
203
|
+
artifact: {
|
|
204
|
+
...artifact,
|
|
205
|
+
change: result.changed ? "updated" : "unchanged",
|
|
206
|
+
...(mechanism === undefined ? {} : { mechanism }),
|
|
207
|
+
targets: result.artifacts.map((artifact) => ({
|
|
208
|
+
path: artifact.path,
|
|
209
|
+
change: artifact.change,
|
|
210
|
+
})),
|
|
211
|
+
},
|
|
212
|
+
};
|
|
213
|
+
})),
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
export const collectCleanupStep = Effect.fn("Sync.collectCleanupStep")(function* (args) {
|
|
217
|
+
const ws = yield* WorkspaceMutations;
|
|
218
|
+
const expectedSkillProjectionNames = new Set([
|
|
219
|
+
...args.expectedSkillNames,
|
|
220
|
+
...args.expectedSubagentNames,
|
|
221
|
+
]);
|
|
222
|
+
const preview = yield* Effect.all([
|
|
223
|
+
cleanupStaleManagedSkillDirectories({
|
|
224
|
+
expectedSkillNames: expectedSkillProjectionNames,
|
|
225
|
+
dryRun: true,
|
|
226
|
+
}),
|
|
227
|
+
cleanupStaleManagedSubagentFiles({
|
|
228
|
+
expectedSubagentNames: args.expectedSubagentNames,
|
|
229
|
+
dryRun: true,
|
|
230
|
+
}),
|
|
231
|
+
]);
|
|
232
|
+
const previewPaths = preview.flatMap(({ removedPaths }) => removedPaths);
|
|
233
|
+
if (previewPaths.length === 0)
|
|
234
|
+
return Option.none();
|
|
235
|
+
return Option.some({
|
|
236
|
+
key: "projection:cleanup",
|
|
237
|
+
label: "stale managed agent projections",
|
|
238
|
+
readiness: "ready",
|
|
239
|
+
artifact: {
|
|
240
|
+
path: previewPaths[0] ?? "stale managed agent projections",
|
|
241
|
+
scope: ws.scope,
|
|
242
|
+
change: "removed",
|
|
243
|
+
fileCount: previewPaths.length,
|
|
244
|
+
targets: previewPaths.map((filePath) => ({ path: filePath, change: "removed" })),
|
|
245
|
+
},
|
|
246
|
+
run: Effect.all([
|
|
247
|
+
cleanupStaleManagedSkillDirectories({
|
|
248
|
+
expectedSkillNames: expectedSkillProjectionNames,
|
|
249
|
+
}),
|
|
250
|
+
cleanupStaleManagedSubagentFiles({ expectedSubagentNames: args.expectedSubagentNames }),
|
|
251
|
+
]).pipe(Effect.mapError(args.adapter.toStepFailure), Effect.map((results) => {
|
|
252
|
+
const removedPaths = results.flatMap((result) => result.removedPaths);
|
|
253
|
+
return {
|
|
254
|
+
result: "success",
|
|
255
|
+
message: `Removed ${count(removedPaths.length, "stale managed agent projection")}`,
|
|
256
|
+
artifact: {
|
|
257
|
+
path: removedPaths[0] ?? previewPaths[0] ?? "stale managed agent projections",
|
|
258
|
+
scope: ws.scope,
|
|
259
|
+
change: "removed",
|
|
260
|
+
fileCount: removedPaths.length,
|
|
261
|
+
targets: removedPaths.map((filePath) => ({ path: filePath, change: "removed" })),
|
|
262
|
+
},
|
|
263
|
+
};
|
|
264
|
+
})),
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
export const collectHooksStep = Effect.fn("Sync.collectHooksStep")(function* (args) {
|
|
268
|
+
const facts = args.facts;
|
|
269
|
+
const manager = yield* HookManager;
|
|
270
|
+
const ws = yield* WorkspaceMutations;
|
|
271
|
+
if (!projectionFactsNeedReconciliation(facts))
|
|
272
|
+
return Option.none();
|
|
273
|
+
const unsupported = facts.find(({ observation }) => observation.reasonCode === "unsupported-version");
|
|
274
|
+
if (unsupported !== undefined) {
|
|
275
|
+
return Option.some({
|
|
276
|
+
key: SYNC_RECOVERY_IDS.hookProjections,
|
|
277
|
+
label: "managed hook projections",
|
|
278
|
+
readiness: "error",
|
|
279
|
+
errorMessage: unsupported.observation.message ??
|
|
280
|
+
"Managed hook projection uses an unsupported marker version; upgrade AXM.",
|
|
281
|
+
artifact: {
|
|
282
|
+
path: "managed hook projections",
|
|
283
|
+
scope: ws.scope,
|
|
284
|
+
change: "unchanged",
|
|
285
|
+
managedRegions: managedRegionsForFacts(facts),
|
|
286
|
+
},
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
const agentOutcomes = manager.configuredAgentOutcomes === undefined
|
|
290
|
+
? []
|
|
291
|
+
: yield* manager.configuredAgentOutcomes("projected");
|
|
292
|
+
const artifact = {
|
|
293
|
+
path: "managed hook projections",
|
|
294
|
+
scope: ws.scope,
|
|
295
|
+
change: "updated",
|
|
296
|
+
agentOutcomes,
|
|
297
|
+
managedRegions: managedRegionsForFacts(facts),
|
|
298
|
+
};
|
|
299
|
+
const blocked = agentOutcomes.filter(({ outcome }) => outcome === "blocked");
|
|
300
|
+
if (blocked.length > 0) {
|
|
301
|
+
return Option.some({
|
|
302
|
+
key: SYNC_RECOVERY_IDS.hookProjections,
|
|
303
|
+
label: projectionDivergenceLabel("managed hook projections", facts),
|
|
304
|
+
readiness: "error",
|
|
305
|
+
errorMessage: blocked
|
|
306
|
+
.map(({ name, agentId, reason }) => `${name} for ${agentId}: ${reason}`)
|
|
307
|
+
.join("; "),
|
|
308
|
+
artifact,
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
return Option.some({
|
|
312
|
+
key: SYNC_RECOVERY_IDS.hookProjections,
|
|
313
|
+
label: projectionDivergenceLabel("managed hook projections", facts),
|
|
314
|
+
readiness: "ready",
|
|
315
|
+
artifact,
|
|
316
|
+
run: Effect.gen(function* () {
|
|
317
|
+
yield* applyPlannedProjections(manager);
|
|
318
|
+
const currentOutcomes = manager.configuredAgentOutcomes === undefined
|
|
319
|
+
? []
|
|
320
|
+
: yield* manager.configuredAgentOutcomes("current");
|
|
321
|
+
return {
|
|
322
|
+
result: "success",
|
|
323
|
+
message: "Reconciled managed hook entries and the fallback region",
|
|
324
|
+
artifact: { ...artifact, agentOutcomes: currentOutcomes },
|
|
325
|
+
};
|
|
326
|
+
}).pipe(Effect.mapError(args.adapter.toStepFailure)),
|
|
327
|
+
});
|
|
328
|
+
});
|
|
329
|
+
export const collectInstructionStep = Effect.fn("Sync.collectInstructionStep")(function* (args) {
|
|
330
|
+
const projectionFacts = args.projectionFacts;
|
|
331
|
+
const ws = yield* WorkspaceMutations;
|
|
332
|
+
const config = yield* ws.getInstructionsConfig();
|
|
333
|
+
const manager = yield* RuleManager;
|
|
334
|
+
const unsupported = projectionFacts.find(({ observation }) => observation.reasonCode === "unsupported-version");
|
|
335
|
+
if (unsupported !== undefined) {
|
|
336
|
+
return Option.some({
|
|
337
|
+
key: SYNC_RECOVERY_IDS.instructionReconcile,
|
|
338
|
+
readiness: "error",
|
|
339
|
+
label: "instruction files",
|
|
340
|
+
errorMessage: unsupported.observation.message ??
|
|
341
|
+
"Instruction projection uses an unsupported marker version; upgrade AXM.",
|
|
342
|
+
artifact: {
|
|
343
|
+
path: unsupported.subject.path.split("#", 1)[0] ?? unsupported.subject.path,
|
|
344
|
+
scope: ws.scope,
|
|
345
|
+
change: "unchanged",
|
|
346
|
+
managedRegions: managedRegionsForFacts(projectionFacts),
|
|
347
|
+
},
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
if (Option.isNone(config) || config.value === false) {
|
|
351
|
+
if (!projectionFactsNeedReconciliation(projectionFacts))
|
|
352
|
+
return Option.none();
|
|
353
|
+
const targets = projectionFileTargets(projectionFacts);
|
|
354
|
+
const artifact = {
|
|
355
|
+
path: targets[0]?.path ?? "managed Rules region",
|
|
356
|
+
scope: ws.scope,
|
|
357
|
+
change: targets[0]?.change ?? "updated",
|
|
358
|
+
targets,
|
|
359
|
+
managedRegions: managedRegionsForFacts(projectionFacts),
|
|
360
|
+
};
|
|
361
|
+
return Option.some({
|
|
362
|
+
key: SYNC_RECOVERY_IDS.instructionReconcile,
|
|
363
|
+
readiness: "ready",
|
|
364
|
+
label: projectionDivergenceLabel("managed Rules region", projectionFacts),
|
|
365
|
+
artifact,
|
|
366
|
+
run: applyPlannedProjections(manager).pipe(Effect.mapError(args.adapter.toStepFailure), Effect.as({
|
|
367
|
+
result: "success",
|
|
368
|
+
message: "Reconciled the managed Rules region",
|
|
369
|
+
artifact,
|
|
370
|
+
})),
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
const configuredAgents = yield* ws.getConfiguredAgents();
|
|
374
|
+
const resolvedConfig = resolveInstructionsConfig(config.value);
|
|
375
|
+
const snapshot = yield* observeInstructionProjection({
|
|
376
|
+
workspaceRoot: ws.baseDir,
|
|
377
|
+
scope: ws.scope,
|
|
378
|
+
configuredAgents,
|
|
379
|
+
config: resolvedConfig,
|
|
380
|
+
});
|
|
381
|
+
const path = yield* Path.Path;
|
|
382
|
+
const regionCurrent = !projectionFactsNeedReconciliation(projectionFacts);
|
|
383
|
+
const current = snapshot.status.missingSources.length === 0 &&
|
|
384
|
+
regionCurrent &&
|
|
385
|
+
instructionProjectionIsCurrent(snapshot);
|
|
386
|
+
if (current)
|
|
387
|
+
return Option.none();
|
|
388
|
+
const readiness = yield* Effect.result(Effect.all([assertInstructionTargetsSafe(snapshot.status), assertInstructionsGitignoreSafe(ws.baseDir)], { concurrency: 1, discard: true }));
|
|
389
|
+
if (readiness._tag === "Failure") {
|
|
390
|
+
return Option.some({
|
|
391
|
+
key: SYNC_RECOVERY_IDS.instructionReconcile,
|
|
392
|
+
readiness: "error",
|
|
393
|
+
label: "instruction files",
|
|
394
|
+
errorMessage: args.adapter.toStepFailure(readiness.failure).detail,
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
const ruleTargets = projectionFileTargets(projectionFacts);
|
|
398
|
+
const instructionTargets = instructionProjectionEffects(snapshot).map((effect) => ({
|
|
399
|
+
...effect,
|
|
400
|
+
path: path.relative(ws.baseDir, effect.path),
|
|
401
|
+
}));
|
|
402
|
+
const targets = mergeArtifactTargets([...ruleTargets, ...instructionTargets]);
|
|
403
|
+
const artifact = {
|
|
404
|
+
path: targets[0]?.path ?? resolvedConfig.fileName,
|
|
405
|
+
scope: ws.scope,
|
|
406
|
+
change: targets[0]?.change ?? "updated",
|
|
407
|
+
managedRegions: managedRegionsForFacts(projectionFacts),
|
|
408
|
+
targets,
|
|
409
|
+
};
|
|
410
|
+
return Option.some({
|
|
411
|
+
key: SYNC_RECOVERY_IDS.instructionReconcile,
|
|
412
|
+
readiness: "ready",
|
|
413
|
+
label: projectionDivergenceLabel("instruction files", projectionFacts),
|
|
414
|
+
artifact,
|
|
415
|
+
run: applyPlannedProjections(manager).pipe(Effect.mapError(args.adapter.toStepFailure), Effect.map(() => ({
|
|
416
|
+
result: "success",
|
|
417
|
+
message: "Reconciled canonical instructions, aliases, and gitignore entries",
|
|
418
|
+
artifact,
|
|
419
|
+
}))),
|
|
420
|
+
});
|
|
421
|
+
});
|
|
422
|
+
// Rule materialization and instruction reconciliation are ordered explicitly in
|
|
423
|
+
// the plan so aliases are updated only after canonical content is current.
|
|
424
|
+
export const makeSyncPlan = ({ materializeSteps, knowledgeStep, hooksStep, cleanupStep, instructionStep, releaseAge, serialMaterialization = false, name = SYNC_PLAN_NAME, description = SYNC_PLAN_DESCRIPTION, }) => {
|
|
425
|
+
const ruleSteps = materializeSteps.filter((step) => step.key?.startsWith("rule:") === true);
|
|
426
|
+
const nonRuleSteps = materializeSteps.filter((step) => step.key?.startsWith("rule:") !== true);
|
|
427
|
+
const jobs = [];
|
|
428
|
+
if (nonRuleSteps.length > 0) {
|
|
429
|
+
jobs.push({ concurrency: serialMaterialization ? 1 : "unbounded", steps: nonRuleSteps });
|
|
430
|
+
}
|
|
431
|
+
if (Option.isSome(knowledgeStep)) {
|
|
432
|
+
jobs.push({ concurrency: 1, steps: [knowledgeStep.value] });
|
|
433
|
+
}
|
|
434
|
+
if (ruleSteps.length > 0) {
|
|
435
|
+
jobs.push({ concurrency: "unbounded", steps: ruleSteps });
|
|
436
|
+
}
|
|
437
|
+
// Aggregate hook units render after canonical hook materialization.
|
|
438
|
+
if (Option.isSome(hooksStep)) {
|
|
439
|
+
jobs.push({ concurrency: 1, steps: [hooksStep.value] });
|
|
440
|
+
}
|
|
441
|
+
if (Option.isSome(cleanupStep)) {
|
|
442
|
+
jobs.push({ concurrency: 1, steps: [cleanupStep.value] });
|
|
443
|
+
}
|
|
444
|
+
if (Option.isSome(instructionStep)) {
|
|
445
|
+
jobs.push({ concurrency: 1, steps: [instructionStep.value] });
|
|
446
|
+
}
|
|
447
|
+
return {
|
|
448
|
+
_tag: "Plan",
|
|
449
|
+
name,
|
|
450
|
+
description: Option.some(description),
|
|
451
|
+
jobs,
|
|
452
|
+
releaseAge,
|
|
453
|
+
presentation: SYNC_PRESENTATION,
|
|
454
|
+
};
|
|
455
|
+
};
|
|
456
|
+
//# sourceMappingURL=plan.js.map
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Destructive sweep of AXM-managed rendered files across agent surfaces:
|
|
3
|
+
* stale skill and subagent projections, managed MCP server entries, and
|
|
4
|
+
* managed hook groups. Read-only discovery primitives live in
|
|
5
|
+
* `extension-workspace/managed-file-discovery.ts`.
|
|
6
|
+
*
|
|
7
|
+
* @experimental This API is unstable and may change without notice.
|
|
8
|
+
* @packageDocumentation
|
|
9
|
+
*/
|
|
10
|
+
import * as Effect from "effect/Effect";
|
|
11
|
+
import * as FileSystem from "effect/FileSystem";
|
|
12
|
+
import * as Path from "effect/Path";
|
|
13
|
+
import { type WorkspaceSyncCleanupFailure } from "./errors.js";
|
|
14
|
+
import { CodingAgentRepository, type WorkspaceOwnershipIssue } from "@agentxm/extension-workspace";
|
|
15
|
+
import { WorkspaceMutations } from "@agentxm/workspace-state";
|
|
16
|
+
export interface RenderedFileCleanupResult {
|
|
17
|
+
readonly removedPaths: ReadonlyArray<string>;
|
|
18
|
+
}
|
|
19
|
+
export interface RemovedAgentArtifactCleanupResult {
|
|
20
|
+
readonly removedPaths: ReadonlyArray<string>;
|
|
21
|
+
readonly preservedPaths: ReadonlyArray<string>;
|
|
22
|
+
}
|
|
23
|
+
/** Remove AXM-owned skill projections whose package is no longer desired. */
|
|
24
|
+
export declare const cleanupStaleManagedSkillDirectories: (args: {
|
|
25
|
+
readonly expectedSkillNames: ReadonlySet<string>;
|
|
26
|
+
readonly dryRun?: boolean;
|
|
27
|
+
}) => Effect.Effect<RenderedFileCleanupResult, WorkspaceSyncCleanupFailure, CodingAgentRepository | FileSystem.FileSystem | Path.Path | WorkspaceMutations>;
|
|
28
|
+
/**
|
|
29
|
+
* Remove AXM-managed artifacts for agents that are no longer configured for a
|
|
30
|
+
* workspace: rendered skill and subagent files, plus the agent's
|
|
31
|
+
* managed MCP server entries and hook groups. Only content carrying an
|
|
32
|
+
* AXM-managed signal is removed; user-authored files and entries are left
|
|
33
|
+
* untouched.
|
|
34
|
+
*/
|
|
35
|
+
export declare const cleanupManagedArtifactsForRemovedAgents: (args: {
|
|
36
|
+
readonly removedAgentIds: ReadonlySet<string>;
|
|
37
|
+
readonly dryRun?: boolean;
|
|
38
|
+
}) => Effect.Effect<RemovedAgentArtifactCleanupResult, WorkspaceSyncCleanupFailure, CodingAgentRepository | FileSystem.FileSystem | Path.Path | WorkspaceMutations>;
|
|
39
|
+
/** Inspect ownership proofs without mutating any agent-native artifact. */
|
|
40
|
+
export declare const inspectWorkspaceOwnership: () => Effect.Effect<ReadonlyArray<WorkspaceOwnershipIssue>, WorkspaceSyncCleanupFailure, CodingAgentRepository | FileSystem.FileSystem | Path.Path | WorkspaceMutations>;
|
|
41
|
+
export declare const cleanupStaleManagedSubagentFiles: (args: {
|
|
42
|
+
readonly expectedSubagentNames: ReadonlySet<string>;
|
|
43
|
+
readonly dryRun?: boolean;
|
|
44
|
+
}) => Effect.Effect<RenderedFileCleanupResult, WorkspaceSyncCleanupFailure, CodingAgentRepository | FileSystem.FileSystem | Path.Path | WorkspaceMutations>;
|
|
45
|
+
//# sourceMappingURL=rendered-file-cleanup.d.ts.map
|