@agentxm/workspace-configuration 0.28.12 → 0.28.13

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 (38) hide show
  1. package/dist/src/errors.d.ts +29 -0
  2. package/dist/src/errors.js +68 -0
  3. package/dist/src/index.d.ts +17 -12
  4. package/dist/src/index.js +28 -11
  5. package/dist/src/inline-mcp/add-inline-mcp-server.d.ts +63 -0
  6. package/dist/src/inline-mcp/add-inline-mcp-server.js +253 -0
  7. package/dist/src/{inline-mcp.d.ts → inline-mcp/definition.d.ts} +3 -3
  8. package/dist/src/{inline-mcp.js → inline-mcp/definition.js} +2 -2
  9. package/dist/src/instructions/manage-instructions.d.ts +125 -0
  10. package/dist/src/instructions/manage-instructions.js +361 -0
  11. package/dist/src/mcp-import/apply.d.ts +33 -0
  12. package/dist/src/{mcp-import.js → mcp-import/apply.js} +8 -50
  13. package/dist/src/mcp-import/import-mcp-servers.d.ts +49 -0
  14. package/dist/src/mcp-import/import-mcp-servers.js +117 -0
  15. package/dist/src/{mcp-import-preflight.d.ts → mcp-import/preflight.d.ts} +1 -1
  16. package/dist/src/{mcp-import-preflight.js → mcp-import/preflight.js} +2 -2
  17. package/dist/src/membership/agent-lifecycle.d.ts +20 -0
  18. package/dist/src/membership/agent-lifecycle.js +33 -0
  19. package/dist/src/membership/atomic-membership.d.ts +41 -0
  20. package/dist/src/membership/atomic-membership.js +125 -0
  21. package/dist/src/membership/configure-agents.d.ts +197 -0
  22. package/dist/src/membership/configure-agents.js +395 -0
  23. package/dist/src/membership/validate-agent-ids.d.ts +16 -0
  24. package/dist/src/membership/validate-agent-ids.js +85 -0
  25. package/dist/src/{initialization-interaction.d.ts → setup/initialization-interaction.d.ts} +1 -1
  26. package/dist/src/{initialization.d.ts → setup/initialization.d.ts} +11 -11
  27. package/dist/src/{initialization.js → setup/initialization.js} +3 -3
  28. package/dist/src/setup/setup-workspace.d.ts +264 -0
  29. package/dist/src/setup/setup-workspace.js +512 -0
  30. package/dist/src/testing.d.ts +95 -4
  31. package/dist/src/testing.js +215 -4
  32. package/package.json +11 -9
  33. package/dist/src/instruction-reconciliation.d.ts +0 -57
  34. package/dist/src/instruction-reconciliation.js +0 -93
  35. package/dist/src/mcp-import.d.ts +0 -40
  36. package/dist/src/membership.d.ts +0 -29
  37. package/dist/src/membership.js +0 -162
  38. /package/dist/src/{initialization-interaction.js → setup/initialization-interaction.js} +0 -0
@@ -0,0 +1,512 @@
1
+ /**
2
+ * Setting up an AXM workspace: deciding what a setup request would do,
3
+ * initializing the workspace under one transaction, and reporting exactly
4
+ * which files the workspace now occupies.
5
+ *
6
+ * Two rules decide the shape of this use case. An unattended apply happens
7
+ * only when the request said so completely — approval, scope, and at least
8
+ * one agent — because a workspace initialized from guessed defaults is a
9
+ * workspace nobody chose. And initialization is one transaction: a run that
10
+ * cannot finish leaves no half-initialized workspace behind.
11
+ *
12
+ * The report is the same document whether the request was previewed or
13
+ * applied, so a preview can be trusted as a description of the apply. It
14
+ * names the files the workspace occupies rather than the calls that wrote
15
+ * them, which is why the enumeration lives here beside the code that writes
16
+ * them rather than in whichever adapter happens to be rendering.
17
+ *
18
+ * @experimental This API is unstable and may change without notice.
19
+ */
20
+ import * as Effect from "effect/Effect";
21
+ import * as FileSystem from "effect/FileSystem";
22
+ import * as Option from "effect/Option";
23
+ import * as Path from "effect/Path";
24
+ import * as Schema from "effect/Schema";
25
+ import { AGENTS } from "@agentxm/extension-model/unstable/agents/registry";
26
+ import { ExtensionTypeSchema } from "@agentxm/extension-model/unstable/extensions";
27
+ import { isGitManaged } from "@agentxm/extension-sources";
28
+ import { CodingAgentRepository, resolveInstructionTarget, } from "@agentxm/workspace-projection";
29
+ import { AXM_DIR_NAME, ArtifactChangeSchema, LOCK_FILENAME, resolveUserWorkspaceRoot, scanAllSubagentFiles, setupScopeSupport, } from "@agentxm/workspace-state";
30
+ import { WorkspaceTransactionScope, runWorkspaceTransaction, } from "@agentxm/workspace-transactions";
31
+ import { WorkspaceConfigurationFailed } from "../errors.js";
32
+ import { WorkspaceInitializationInteraction } from "./initialization-interaction.js";
33
+ import { bootstrapWorkspace } from "./initialization.js";
34
+ // -----------------------------------------------------------------------------
35
+ // Typed outcome
36
+ // -----------------------------------------------------------------------------
37
+ const SubagentSummarySchema = Schema.Struct({
38
+ agentId: Schema.String,
39
+ agentName: Schema.String,
40
+ subagentDir: Schema.String,
41
+ files: Schema.Array(Schema.Struct({ path: Schema.String })),
42
+ });
43
+ const SetupArtifactTargetSchema = Schema.Struct({
44
+ path: Schema.String,
45
+ change: ArtifactChangeSchema,
46
+ agentIds: Schema.optional(Schema.Array(Schema.String)),
47
+ });
48
+ const SetupPlanStepArtifactSchema = Schema.Struct({
49
+ path: Schema.optional(Schema.String),
50
+ scope: Schema.Literals(["project", "user"]),
51
+ agents: Schema.optional(Schema.Array(Schema.String)),
52
+ version: Schema.optional(Schema.String),
53
+ change: ArtifactChangeSchema,
54
+ previousVersion: Schema.optional(Schema.String),
55
+ fileCount: Schema.optional(Schema.Number),
56
+ targets: Schema.optional(Schema.Array(SetupArtifactTargetSchema)),
57
+ });
58
+ const SetupPlanStepSchema = Schema.Struct({
59
+ label: Schema.String,
60
+ status: Schema.Literals([
61
+ "ready",
62
+ "warning",
63
+ "error",
64
+ "applied",
65
+ "unchanged",
66
+ "failed",
67
+ "blocked",
68
+ ]),
69
+ message: Schema.optional(Schema.String),
70
+ artifact: Schema.optional(SetupPlanStepArtifactSchema),
71
+ });
72
+ const SetupAgentCandidateSchema = Schema.Struct({
73
+ id: Schema.String,
74
+ name: Schema.String,
75
+ projectDetected: Schema.Boolean,
76
+ userDetected: Schema.Boolean,
77
+ state: Schema.Literals(["selected", "suggested", "available", "retired"]),
78
+ selectionReason: Schema.optional(Schema.Literals([
79
+ "explicit",
80
+ "project-detected",
81
+ "user-detected",
82
+ "catalog-suggestion",
83
+ ])),
84
+ });
85
+ const SetupScopeSupportOutcomeSchema = Schema.Struct({
86
+ target: Schema.Literals(["workspace", "container", "agent", "agent-set"]),
87
+ agentId: Schema.optional(Schema.String),
88
+ agentName: Schema.optional(Schema.String),
89
+ status: Schema.Literals(["supported", "project-only", "unsupported", "refused"]),
90
+ reasonCode: Schema.Literals([
91
+ "supported",
92
+ "no-configured-agents",
93
+ "unknown-agent",
94
+ "native-capability-unavailable",
95
+ "axm-capability-unavailable",
96
+ "project-only",
97
+ "scope-not-modeled",
98
+ ]),
99
+ reason: Schema.String,
100
+ });
101
+ const SetupScopeSupportCategorySchema = Schema.Struct({
102
+ type: ExtensionTypeSchema,
103
+ label: Schema.String,
104
+ placement: Schema.Literals(["per-agent", "workspace", "container"]),
105
+ outcomes: Schema.Array(SetupScopeSupportOutcomeSchema),
106
+ });
107
+ /**
108
+ * How a preview chose the answers an interactive setup would have asked for.
109
+ */
110
+ const SetupPreviewDefaultsSchema = Schema.Struct({
111
+ agents: Schema.Literals(["explicit", "detected", "suggested"]),
112
+ instructions: Schema.optional(Schema.Struct({ enabled: Schema.Boolean, fileName: Schema.String })),
113
+ });
114
+ export const SetupOutcomeSchema = Schema.Struct({
115
+ outcome: Schema.Literals(["previewed", "cancelled", "applied", "no-op", "failed"]),
116
+ planName: Schema.String,
117
+ planDescription: Schema.optional(Schema.String),
118
+ message: Schema.optional(Schema.String),
119
+ totalSteps: Schema.Number,
120
+ readyCount: Schema.Number,
121
+ warningCount: Schema.Number,
122
+ errorCount: Schema.Number,
123
+ appliedCount: Schema.Number,
124
+ failedCount: Schema.Number,
125
+ blockedCount: Schema.Number,
126
+ steps: Schema.Array(SetupPlanStepSchema),
127
+ reason: Schema.optional(Schema.Literal("approval-required")),
128
+ errorCode: Schema.optional(Schema.Literal("usage")),
129
+ status: Schema.Literals([
130
+ "initialized",
131
+ "already-initialized",
132
+ "preview",
133
+ "cancelled",
134
+ "approval-required",
135
+ ]),
136
+ changed: Schema.Boolean,
137
+ defaultSkillInstalled: Schema.Boolean,
138
+ scope: Schema.String,
139
+ agents: Schema.Array(Schema.Struct({ id: Schema.String, name: Schema.String })),
140
+ agentCandidates: Schema.optional(Schema.Array(SetupAgentCandidateSchema)),
141
+ scopeSupport: Schema.Array(SetupScopeSupportCategorySchema),
142
+ settingsPath: Schema.String,
143
+ instructions: Schema.optional(Schema.Struct({
144
+ enabled: Schema.Boolean,
145
+ fileName: Schema.optional(Schema.String),
146
+ gitignoreAliases: Schema.optional(Schema.Boolean),
147
+ })),
148
+ telemetryEnabled: Schema.Boolean,
149
+ subagentFiles: Schema.optional(Schema.Array(SubagentSummarySchema)),
150
+ previewDefaults: Schema.optional(SetupPreviewDefaultsSchema),
151
+ });
152
+ const APPROVAL_REQUIRED_DETAIL = "Explicit approval, scope, and agents are required for unattended setup";
153
+ /**
154
+ * Settle a setup request.
155
+ *
156
+ * An unattended apply into a workspace that has no settings yet must name
157
+ * every input it depends on. Anything less is refused before a file is
158
+ * touched, and the refusal names the preview that would show the candidate.
159
+ */
160
+ export const prepareSetupWorkspace = (request) => Effect.gen(function* () {
161
+ const path = yield* Path.Path;
162
+ const fileSystem = yield* FileSystem.FileSystem;
163
+ const userWorkspaceRoot = yield* resolveUserWorkspaceRoot();
164
+ const workspaceDir = request.scope === "user"
165
+ ? path.join(userWorkspaceRoot, AXM_DIR_NAME)
166
+ : path.join(request.projectRoot, AXM_DIR_NAME);
167
+ const settingsPath = request.scope === "user"
168
+ ? path.join(userWorkspaceRoot, "axm.json")
169
+ : path.join(request.projectRoot, "axm.json");
170
+ const settingsExist = yield* fileSystem.exists(settingsPath).pipe(Effect.mapError((cause) => new WorkspaceConfigurationFailed({
171
+ category: "internal",
172
+ detail: `Failed to inspect setup state: ${workspaceDir}`,
173
+ cause,
174
+ })));
175
+ const unattended = request.nonInteractive || request.yes === true;
176
+ const intentComplete = request.yes === true &&
177
+ (request.agents?.length ?? 0) > 0 &&
178
+ (request.scopeExplicit === undefined || request.scopeExplicit);
179
+ if (!settingsExist &&
180
+ unattended &&
181
+ request.scopeExplicit !== undefined &&
182
+ request.preview !== true &&
183
+ !intentComplete) {
184
+ return {
185
+ _tag: "ApprovalRequired",
186
+ outcome: {
187
+ outcome: "failed",
188
+ planName: "Set up AXM workspace",
189
+ planDescription: `Set up AXM (${request.scope})`,
190
+ message: APPROVAL_REQUIRED_DETAIL,
191
+ totalSteps: 0,
192
+ readyCount: 0,
193
+ warningCount: 0,
194
+ errorCount: 1,
195
+ appliedCount: 0,
196
+ failedCount: 0,
197
+ blockedCount: 0,
198
+ steps: [],
199
+ reason: "approval-required",
200
+ errorCode: "usage",
201
+ status: "approval-required",
202
+ changed: false,
203
+ defaultSkillInstalled: false,
204
+ scope: request.scope,
205
+ agents: [],
206
+ scopeSupport: setupScopeSupport([], request.scope),
207
+ settingsPath: request.scope === "user" ? settingsPath : "axm.json",
208
+ telemetryEnabled: false,
209
+ },
210
+ };
211
+ }
212
+ return {
213
+ _tag: "SetupWorkspace",
214
+ request,
215
+ settingsExist,
216
+ settingsPath,
217
+ workspaceDir,
218
+ };
219
+ });
220
+ /**
221
+ * Initialize the workspace, or resolve what initializing it would do.
222
+ *
223
+ * A first-time initialization runs inside a workspace transaction it opens
224
+ * itself: no workspace exists yet, so there is no located workspace to supply
225
+ * the transaction scope, and a run that fails halfway must leave the
226
+ * directory as it found it.
227
+ */
228
+ export const previewOrApplySetupWorkspace = (candidate, options) => Effect.gen(function* () {
229
+ const path = yield* Path.Path;
230
+ const workspaceOptions = {
231
+ scope: candidate.request.scope,
232
+ projectRoot: candidate.request.projectRoot,
233
+ nonInteractive: candidate.request.nonInteractive,
234
+ ...(candidate.request.agents !== undefined && candidate.request.agents.length > 0
235
+ ? { agents: candidate.request.agents }
236
+ : {}),
237
+ ...(candidate.request.yes !== undefined ? { yes: candidate.request.yes } : {}),
238
+ ...(candidate.request.preview !== undefined ? { preview: candidate.request.preview } : {}),
239
+ };
240
+ const initialize = Effect.gen(function* () {
241
+ const settled = yield* bootstrapWorkspace(workspaceOptions);
242
+ if (settled.initialized && options?.bundledSkill !== undefined) {
243
+ yield* options.bundledSkill;
244
+ }
245
+ return settled;
246
+ });
247
+ if (candidate.request.preview === true || candidate.settingsExist)
248
+ return yield* initialize;
249
+ return yield* runWorkspaceTransaction({
250
+ claimDefaultTargets: false,
251
+ transition: initialize,
252
+ validate: () => Effect.void,
253
+ }).pipe(Effect.provide(WorkspaceTransactionScope.layer({
254
+ workspaceDir: candidate.workspaceDir,
255
+ settingsPath: candidate.settingsPath,
256
+ lockPath: path.join(path.dirname(candidate.settingsPath), LOCK_FILENAME),
257
+ })));
258
+ });
259
+ // -----------------------------------------------------------------------------
260
+ // report
261
+ // -----------------------------------------------------------------------------
262
+ const isKnownAgentId = (id) => Object.hasOwn(AGENTS, id);
263
+ const stepStatus = (status, hasChange) => status === "preview" ? "ready" : hasChange ? "applied" : "unchanged";
264
+ const artifactChange = (status, hasChange) => status === "preview"
265
+ ? "created"
266
+ : hasChange
267
+ ? "created"
268
+ : "unchanged";
269
+ /**
270
+ * How a preview chose its membership candidate: the explicit request when one
271
+ * was given, otherwise detection where it produced a strong signal, otherwise
272
+ * the catalog suggestion set.
273
+ */
274
+ export const previewAgentDefault = (candidates) => {
275
+ const reasons = candidates.flatMap((entry) => entry.state === "selected" && entry.selectionReason !== undefined
276
+ ? [entry.selectionReason]
277
+ : []);
278
+ if (reasons.includes("explicit"))
279
+ return "explicit";
280
+ if (reasons.some((reason) => reason === "project-detected" || reason === "user-detected")) {
281
+ return "detected";
282
+ }
283
+ return "suggested";
284
+ };
285
+ const bundledSkillDisplayPath = (scope) => scope === "project"
286
+ ? "agent_extensions/agentxm/@agentxm/skills/axm"
287
+ : ".axm/workspace/agent_extensions/agentxm/@agentxm/skills/axm";
288
+ /**
289
+ * Describe the settled setup: which files the workspace now occupies, which
290
+ * agents it configures, and what the same request would do again.
291
+ */
292
+ export const reportSetupWorkspace = (request) => Effect.gen(function* () {
293
+ const path = yield* Path.Path;
294
+ const fileSystem = yield* FileSystem.FileSystem;
295
+ const agentRepository = yield* CodingAgentRepository;
296
+ const { settings, location, initialized, wouldInitialize, cancelled, agentCandidates } = request.transition;
297
+ const scope = location.scope;
298
+ const agentIds = settings.agents ?? [];
299
+ const scopeAgentIds = cancelled
300
+ ? agentCandidates.flatMap((entry) => (entry.state === "selected" ? [entry.id] : []))
301
+ : agentIds;
302
+ const scopeSupport = setupScopeSupport(scopeAgentIds, scope);
303
+ const agents = [
304
+ ...agentIds.flatMap((id) => isKnownAgentId(id) ? [{ id: AGENTS[id].id, name: AGENTS[id].name }] : []),
305
+ ...agentIds.filter((id) => !isKnownAgentId(id)).map((id) => ({ id, name: id })),
306
+ ];
307
+ const settingsPath = scope === "user" ? location.settingsPath : "axm.json";
308
+ const instructionsValue = settings.instructionFiles;
309
+ const instructions = instructionsValue === undefined
310
+ ? undefined
311
+ : instructionsValue === false
312
+ ? { enabled: false }
313
+ : {
314
+ enabled: true,
315
+ ...(instructionsValue.fileName !== undefined
316
+ ? { fileName: instructionsValue.fileName }
317
+ : {}),
318
+ ...(instructionsValue.gitignoreAliases !== undefined
319
+ ? { gitignoreAliases: instructionsValue.gitignoreAliases }
320
+ : {}),
321
+ };
322
+ const subagentFiles = agents.length > 0 ? yield* scanAllSubagentFiles(location.baseDir) : [];
323
+ const status = wouldInitialize
324
+ ? "preview"
325
+ : initialized
326
+ ? "initialized"
327
+ : "already-initialized";
328
+ const resolvedStatus = cancelled ? "cancelled" : status;
329
+ const gitManaged = scope === "project" && (yield* isGitManaged(location.baseDir));
330
+ const changeForPath = (filePath) => Effect.gen(function* () {
331
+ if (resolvedStatus === "already-initialized" || resolvedStatus === "cancelled") {
332
+ return "unchanged";
333
+ }
334
+ if (resolvedStatus === "initialized")
335
+ return "created";
336
+ const exists = yield* fileSystem
337
+ .exists(filePath)
338
+ .pipe(Effect.catch(() => Effect.succeed(false)));
339
+ return exists ? "updated" : "created";
340
+ });
341
+ const displayTargetPath = (filePath) => {
342
+ const relative = path.relative(location.baseDir, filePath);
343
+ return relative === "" || relative.startsWith("..") || path.isAbsolute(relative)
344
+ ? filePath
345
+ : relative;
346
+ };
347
+ const targetsFor = (paths) => Effect.forEach([...new Set(paths)], (filePath) => changeForPath(filePath).pipe(Effect.map((change) => ({
348
+ path: displayTargetPath(filePath),
349
+ change,
350
+ }))));
351
+ const workspaceTargets = yield* targetsFor([
352
+ request.candidate.settingsPath,
353
+ location.lockPath,
354
+ ...(gitManaged ? [path.join(location.baseDir, ".gitignore")] : []),
355
+ ]);
356
+ const instructionTargets = instructions?.enabled === true
357
+ ? yield* targetsFor([
358
+ path.join(location.baseDir, instructions.fileName ?? "AGENTS.md"),
359
+ ...agentIds.flatMap((agentId) => {
360
+ if (!isKnownAgentId(agentId))
361
+ return [];
362
+ const resolution = resolveInstructionTarget({
363
+ instructions: AGENTS[agentId].instructions,
364
+ sourceFileName: instructions.fileName ?? "AGENTS.md",
365
+ symlinkSupported: true,
366
+ });
367
+ return resolution.action === "write"
368
+ ? [path.join(location.baseDir, resolution.relativeTarget)]
369
+ : [];
370
+ }),
371
+ ])
372
+ : [];
373
+ const skillTargets = (yield* Effect.forEach(agentIds.flatMap((agentId) => (isKnownAgentId(agentId) ? [agentId] : [])), (agentId) => agentRepository.get(agentId).pipe(Effect.flatMap((agent) => agent.resolveEffectiveSkillsDir({ workspaceRoot: location.baseDir })), Effect.flatMap((resolved) => resolved._tag !== "supported"
374
+ ? Effect.succeed(Option.none())
375
+ : changeForPath(path.join(resolved.dir, "axm")).pipe(Effect.map((change) => Option.some({
376
+ path: displayTargetPath(path.join(resolved.dir, "axm")),
377
+ change,
378
+ agentIds: [agentId],
379
+ }))))))).flatMap(Option.toArray);
380
+ const steps = [
381
+ {
382
+ label: "Workspace configuration",
383
+ status: stepStatus(resolvedStatus, initialized),
384
+ message: resolvedStatus === "preview"
385
+ ? "Would initialize workspace configuration"
386
+ : initialized
387
+ ? "Initialized workspace configuration"
388
+ : "Workspace configuration already exists",
389
+ artifact: {
390
+ path: settingsPath,
391
+ scope,
392
+ change: artifactChange(resolvedStatus, initialized),
393
+ targets: workspaceTargets,
394
+ },
395
+ },
396
+ ];
397
+ if (instructions !== undefined) {
398
+ steps.push({
399
+ label: "Instruction files",
400
+ status: stepStatus(resolvedStatus, initialized),
401
+ message: resolvedStatus === "preview"
402
+ ? "Would configure instruction files"
403
+ : initialized
404
+ ? "Configured instruction files"
405
+ : "Instruction files already configured",
406
+ artifact: {
407
+ path: instructionTargets[0]?.path ?? "instructions",
408
+ scope,
409
+ change: artifactChange(resolvedStatus, initialized),
410
+ targets: instructionTargets,
411
+ },
412
+ });
413
+ }
414
+ if (request.bundledSkill.installed || resolvedStatus === "preview") {
415
+ const change = artifactChange(resolvedStatus, request.bundledSkill.installed);
416
+ steps.push({
417
+ label: "@agentxm/skills/axm",
418
+ status: stepStatus(resolvedStatus, request.bundledSkill.installed),
419
+ message: resolvedStatus === "preview"
420
+ ? "Would install the bundled AXM skill"
421
+ : "Installed the bundled AXM skill",
422
+ artifact: {
423
+ path: bundledSkillDisplayPath(scope),
424
+ scope,
425
+ agents: agentIds,
426
+ version: request.bundledSkill.version,
427
+ change,
428
+ targets: [{ path: bundledSkillDisplayPath(scope), change }, ...skillTargets],
429
+ },
430
+ });
431
+ }
432
+ if (agentIds.length === 0 && resolvedStatus !== "preview") {
433
+ steps.push({
434
+ label: "Agent materialization",
435
+ status: "warning",
436
+ message: `No coding-agent targets are configured. Run \`axm agents add --detected${scope === "user" ? " --scope user" : ""}\` to materialize installed extensions.`,
437
+ });
438
+ }
439
+ const readyCount = steps.filter((step) => step.status === "ready").length;
440
+ const warningCount = steps.filter((step) => step.status === "warning").length;
441
+ const appliedCount = steps.filter((step) => step.status === "applied").length;
442
+ const blockedCount = steps.filter((step) => step.status === "blocked").length;
443
+ const failedCount = steps.filter((step) => step.status === "failed").length;
444
+ const membershipRequested = (request.candidate.request.agents?.length ?? 0) > 0;
445
+ const agentNames = agents.map((agent) => agent.name).join(", ");
446
+ const message = cancelled
447
+ ? "Setup cancelled — no changes applied"
448
+ : wouldInitialize
449
+ ? "Setup plan ready"
450
+ : !initialized
451
+ ? membershipRequested
452
+ ? "Workspace already initialized; use `axm agents add` or `axm agents remove` to change coding agents"
453
+ : agents.length > 0
454
+ ? `Workspace already initialized with agents: ${agentNames}`
455
+ : "Workspace already initialized with no coding agents"
456
+ : agents.length > 0
457
+ ? `Initialized with agents: ${agentNames}`
458
+ : "Workspace initialized with no coding agents";
459
+ return {
460
+ outcome: resolvedStatus === "preview"
461
+ ? "previewed"
462
+ : resolvedStatus === "cancelled"
463
+ ? "cancelled"
464
+ : appliedCount > 0
465
+ ? "applied"
466
+ : "no-op",
467
+ planName: "Set up AXM workspace",
468
+ planDescription: `Set up AXM (${scope})`,
469
+ message,
470
+ totalSteps: steps.length,
471
+ readyCount,
472
+ warningCount,
473
+ errorCount: 0,
474
+ appliedCount,
475
+ failedCount,
476
+ blockedCount,
477
+ steps,
478
+ status: resolvedStatus,
479
+ changed: initialized && request.candidate.request.preview !== true,
480
+ defaultSkillInstalled: request.bundledSkill.installed,
481
+ scope,
482
+ agents,
483
+ ...(agentCandidates.length > 0 ? { agentCandidates: [...agentCandidates] } : {}),
484
+ scopeSupport,
485
+ settingsPath,
486
+ ...(instructions === undefined ? {} : { instructions }),
487
+ telemetryEnabled: request.candidate.request.telemetryEnabled,
488
+ ...(subagentFiles.length > 0 ? { subagentFiles: [...subagentFiles] } : {}),
489
+ ...(resolvedStatus === "preview"
490
+ ? {
491
+ previewDefaults: {
492
+ agents: previewAgentDefault(agentCandidates),
493
+ ...(instructions === undefined
494
+ ? {}
495
+ : {
496
+ instructions: {
497
+ enabled: instructions.enabled,
498
+ fileName: instructions.fileName ?? "AGENTS.md",
499
+ },
500
+ }),
501
+ },
502
+ }
503
+ : {}),
504
+ };
505
+ });
506
+ /** The workspace setup use case. */
507
+ export const SetupWorkspace = {
508
+ prepare: prepareSetupWorkspace,
509
+ previewOrApply: previewOrApplySetupWorkspace,
510
+ report: reportSetupWorkspace,
511
+ };
512
+ //# sourceMappingURL=setup-workspace.js.map
@@ -1,9 +1,100 @@
1
1
  /**
2
- * Deterministic in-memory Layer implementations of the workspace-configuration
3
- * services. Tests and specifications may import this module; production
4
- * source composes the application's interaction implementation instead.
2
+ * @agentxm/workspace-configuration deterministic fixtures and ports.
3
+ *
4
+ * A throwaway workspace with settings, lockfile, and agent-native files
5
+ * written as the product writes them, plus every service a configuration use
6
+ * case reads or writes through: the workspace state and its transaction
7
+ * scope, the projection capability, the rule manager, and the plan pipeline's
8
+ * per-invocation services with a recording interaction port. Production
9
+ * source never imports this module.
5
10
  *
6
11
  * @experimental This API is unstable and may change without notice.
7
12
  */
8
- export { WorkspaceInitializationInteractionTest, type WorkspaceInitializationInteractionTestState, } from "./initialization-interaction.js";
13
+ import * as Effect from "effect/Effect";
14
+ import * as HttpClient from "effect/unstable/http/HttpClient";
15
+ import { AgentExecutableResolver } from "@agentxm/agent-integration";
16
+ import { SourceHostProviders } from "@agentxm/extension-sources";
17
+ import type { WorkspaceScope } from "@agentxm/extension-model/unstable/workspace-scope";
18
+ import { type ResolvePlanInteractionTestState } from "@agentxm/workspace-operations/testing";
19
+ import { ProjectionParticipants } from "@agentxm/workspace-projection";
20
+ import { WorkspaceInitializationInteractionTest, type WorkspaceInitializationInteractionTestState } from "./setup/initialization-interaction.js";
21
+ export { WorkspaceInitializationInteractionTest, type WorkspaceInitializationInteractionTestState };
22
+ export interface ConfigurationFixtureOptions {
23
+ readonly scope?: WorkspaceScope;
24
+ /** Settings document for the selected scope; written as authored. */
25
+ readonly settings?: Readonly<Record<string, unknown>>;
26
+ /** Lockfile document for the selected scope; `lockfileVersion` is supplied. */
27
+ readonly lockfile?: Readonly<Record<string, unknown>>;
28
+ /** Extra files written into the project root, keyed by relative path. */
29
+ readonly files?: Readonly<Record<string, string>>;
30
+ /** Extra files written into the pinned user home, keyed by relative path. */
31
+ readonly homeFiles?: Readonly<Record<string, string>>;
32
+ /**
33
+ * Agent executables the machine reports as present. Detection consults this
34
+ * alongside the workspace's own marker directories, so a fixture that leaves
35
+ * it empty observes detection from workspace evidence alone.
36
+ */
37
+ readonly installedExecutables?: ReadonlyArray<string>;
38
+ }
39
+ /**
40
+ * A throwaway workspace with every service a configuration use case needs
41
+ * over it, and a pinned user home so a project-scope run can be shown to
42
+ * leave the user scope alone.
43
+ */
44
+ export declare const makeConfigurationFixture: (options?: ConfigurationFixtureOptions) => {
45
+ root: string;
46
+ home: string;
47
+ workspaceRoot: string;
48
+ writeFile: (relativePath: string, contents: string) => void;
49
+ writeHomeFile: (relativePath: string, contents: string) => void;
50
+ readFile: (relativePath: string) => string;
51
+ exists: (relativePath: string) => boolean;
52
+ /** Every file under the project root. */
53
+ snapshot: () => readonly (readonly [string, string])[];
54
+ /** Every file under the pinned user home. */
55
+ homeSnapshot: () => readonly (readonly [string, string])[];
56
+ /** What the plan interaction port was asked to present and confirm. */
57
+ interactionState: () => ResolvePlanInteractionTestState;
58
+ provide: <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E | import("@agentxm/workspace-state").WorkspaceMutationsError, import("effect/FileSystem").FileSystem | import("effect/Path").Path | Exclude<R, import("@agentxm/workspace-projection").CodingAgentRepository | import("@agentxm/workspace-transactions").WorkspaceTransactionScope | import("@agentxm/workspace-state").WorkspaceMutations | import("@agentxm/workspace-state").ConfiguredAgentOutcomesProvider | import("@agentxm/workspace-transactions").FootprintRecorder | import("@agentxm/workspace-operations").OperationJournal | import("@agentxm/workspace-operations").ResolvePlanInteraction | import("@agentxm/extension-materialization").RuleManager | HttpClient.HttpClient | import("@agentxm/agent-integration").NativeWriteAuthority | SourceHostProviders | ProjectionParticipants | AgentExecutableResolver | import("@agentxm/workspace-state/live").WorkspaceStateServices | import("@agentxm/workspace-projection").WorkspaceInvariantFacts | import("@agentxm/extension-sources").WorkspaceCatalog>>;
59
+ cleanup: () => void;
60
+ };
61
+ export type ConfigurationFixture = ReturnType<typeof makeConfigurationFixture>;
62
+ export interface SetupFixtureOptions {
63
+ /** Canned answers for the prompts an interactive setup raises. */
64
+ readonly selectAgents?: ReadonlyArray<string>;
65
+ readonly confirmSetupPlan?: boolean;
66
+ readonly confirmInstructionSync?: boolean;
67
+ /** Files written into the bare project directory before setup runs. */
68
+ readonly files?: Readonly<Record<string, string>>;
69
+ /** Files written into the pinned user home before setup runs. */
70
+ readonly homeFiles?: Readonly<Record<string, string>>;
71
+ /** Agent executables the machine reports as present. */
72
+ readonly installedExecutables?: ReadonlyArray<string>;
73
+ }
74
+ /**
75
+ * A bare directory and the services setup needs to turn it into a workspace.
76
+ *
77
+ * Setup builds its own workspace, so this fixture deliberately provides no
78
+ * workspace state: only the platform-adjacent ports it reads — the pinned
79
+ * user home, the coding-agent catalog and executable probe, and a recording
80
+ * initialization-interaction port whose calls are the evidence channel for
81
+ * every claim about what setup asked.
82
+ */
83
+ export declare const makeSetupFixture: (options?: SetupFixtureOptions) => {
84
+ root: string;
85
+ home: string;
86
+ writeFile: (relativePath: string, contents: string) => void;
87
+ writeHomeFile: (relativePath: string, contents: string) => void;
88
+ readFile: (relativePath: string) => string;
89
+ exists: (relativePath: string) => boolean;
90
+ /** Every file under the bare project directory. */
91
+ snapshot: () => readonly (readonly [string, string])[];
92
+ /** Every file under the pinned user home. */
93
+ homeSnapshot: () => readonly (readonly [string, string])[];
94
+ /** What the initialization interaction port was asked. */
95
+ promptState: () => WorkspaceInitializationInteractionTestState;
96
+ provide: <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, import("./setup/initialization-interaction.js").WorkspaceInitializationInteraction | import("@agentxm/workspace-projection").CodingAgentRepository | AgentExecutableResolver>>;
97
+ cleanup: () => void;
98
+ };
99
+ export type SetupFixture = ReturnType<typeof makeSetupFixture>;
9
100
  //# sourceMappingURL=testing.d.ts.map