@kisev/skills-opencode 1.1.1 → 2.0.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.
Files changed (157) hide show
  1. package/README.md +134 -108
  2. package/README.ru.md +242 -0
  3. package/dist/agent-profiles.d.ts +1 -1
  4. package/dist/agent-profiles.js +21 -10
  5. package/dist/assets/agents/architect.md +67 -0
  6. package/{assets → dist/assets}/agents/critic.md +17 -2
  7. package/dist/assets/agents/manager.md +36 -0
  8. package/{assets → dist/assets}/agents/mapper.md +2 -2
  9. package/dist/assets/agents/review.md +25 -0
  10. package/{assets → dist/assets}/agents/worker.md +7 -5
  11. package/dist/assets/commands/agent-profiles.md +9 -0
  12. package/dist/assets/commands/agents-md.md +10 -0
  13. package/dist/assets/commands/askme.md +10 -0
  14. package/dist/assets/commands/ast-grep.md +10 -0
  15. package/dist/assets/commands/capabilities.md +9 -0
  16. package/dist/assets/commands/code-explain.md +10 -0
  17. package/dist/assets/commands/code-review.md +10 -0
  18. package/dist/assets/commands/commit-msg.md +10 -0
  19. package/dist/assets/commands/docs-prepare.md +10 -0
  20. package/dist/assets/commands/docs-review.md +10 -0
  21. package/dist/assets/commands/doctor.md +9 -0
  22. package/dist/assets/commands/doit.md +10 -0
  23. package/dist/assets/commands/goal.md +10 -0
  24. package/dist/assets/commands/humanize.md +10 -0
  25. package/dist/assets/commands/lsp-report.md +10 -0
  26. package/dist/assets/commands/mattermost.md +10 -0
  27. package/dist/assets/commands/mr-prepare.md +10 -0
  28. package/dist/assets/commands/reconcile.md +9 -0
  29. package/dist/assets/commands/release-prepare.md +10 -0
  30. package/dist/assets/commands/release-review.md +10 -0
  31. package/dist/assets/commands/rtk.md +10 -0
  32. package/dist/assets/commands/skill-improve.md +10 -0
  33. package/dist/assets/commands/slides-prompts-prepare.md +10 -0
  34. package/dist/assets/commands/spec-manage.md +10 -0
  35. package/dist/assets/commands/stopit.md +10 -0
  36. package/dist/assets/commands/summary.md +10 -0
  37. package/dist/assets/commands/task-prepare.md +10 -0
  38. package/dist/assets/commands/task-review.md +10 -0
  39. package/dist/assets/commands/task-triage.md +10 -0
  40. package/dist/assets/commands/team-retro.md +10 -0
  41. package/dist/assets/commands/team-roadmap.md +10 -0
  42. package/dist/assets/commands/team-sprint-close.md +10 -0
  43. package/dist/assets/commands/team-sprint-start.md +10 -0
  44. package/dist/assets/lsp-catalog.json +30 -0
  45. package/dist/assets/migration-inventory.json +294 -0
  46. package/dist/catalog.d.ts +8 -0
  47. package/dist/catalog.js +38 -0
  48. package/dist/cli-output.d.ts +7 -0
  49. package/dist/cli-output.js +68 -1
  50. package/dist/cli.js +256 -59
  51. package/dist/contracts.d.ts +52 -0
  52. package/dist/contracts.js +98 -0
  53. package/dist/doctor.d.ts +44 -0
  54. package/dist/doctor.js +595 -0
  55. package/dist/generate-assets.js +17 -2
  56. package/dist/index.d.ts +84 -55
  57. package/dist/index.js +170 -37
  58. package/dist/installer.d.ts +33 -5
  59. package/dist/installer.js +252 -28
  60. package/dist/lifecycle.d.ts +3 -0
  61. package/dist/lifecycle.js +49 -17
  62. package/dist/plugins/rtk.js +4 -1
  63. package/dist/reconcile.d.ts +43 -0
  64. package/dist/reconcile.js +507 -0
  65. package/dist/registry.d.ts +7 -4
  66. package/dist/registry.js +28 -88
  67. package/dist/routing.d.ts +97 -3
  68. package/dist/routing.js +470 -18
  69. package/dist/runtime/state.d.ts +1 -0
  70. package/dist/runtime/state.js +12 -5
  71. package/dist/runtime/worktree.d.ts +33 -0
  72. package/dist/runtime/worktree.js +220 -0
  73. package/dist/terminal-wizard.d.ts +2 -0
  74. package/dist/terminal-wizard.js +125 -0
  75. package/package.json +10 -26
  76. package/assets/agents/architect.md +0 -33
  77. package/assets/agents/manager.md +0 -82
  78. package/assets/agents/review.md +0 -25
  79. package/assets/commands/agent-list.md +0 -9
  80. package/assets/commands/agent-model-set.md +0 -9
  81. package/assets/commands/askme.md +0 -10
  82. package/assets/commands/ast-grep-rewrite.md +0 -10
  83. package/assets/commands/ast-grep-search.md +0 -10
  84. package/assets/commands/attempt-cancel.md +0 -10
  85. package/assets/commands/attempt-list.md +0 -10
  86. package/assets/commands/attempt-result.md +0 -10
  87. package/assets/commands/attempt-show.md +0 -10
  88. package/assets/commands/capabilities.md +0 -9
  89. package/assets/commands/code-review.md +0 -10
  90. package/assets/commands/commit-msg.md +0 -10
  91. package/assets/commands/critic-add.md +0 -9
  92. package/assets/commands/critic-remove.md +0 -9
  93. package/assets/commands/docs-prepare.md +0 -10
  94. package/assets/commands/docs-review.md +0 -10
  95. package/assets/commands/doctor.md +0 -9
  96. package/assets/commands/doit.md +0 -10
  97. package/assets/commands/goal-list.md +0 -10
  98. package/assets/commands/goal-pause.md +0 -10
  99. package/assets/commands/goal-prepare.md +0 -10
  100. package/assets/commands/goal-remove.md +0 -10
  101. package/assets/commands/goal-show.md +0 -10
  102. package/assets/commands/goal-start.md +0 -10
  103. package/assets/commands/lsp-report.md +0 -10
  104. package/assets/commands/mattermost.md +0 -10
  105. package/assets/commands/mr-prepare.md +0 -10
  106. package/assets/commands/mr-review.md +0 -10
  107. package/assets/commands/multi-run-cancel.md +0 -10
  108. package/assets/commands/multi-run-compare.md +0 -10
  109. package/assets/commands/multi-run-fusion.md +0 -10
  110. package/assets/commands/multi-run-start.md +0 -10
  111. package/assets/commands/multi-run-status.md +0 -10
  112. package/assets/commands/overview.md +0 -10
  113. package/assets/commands/release-prepare.md +0 -10
  114. package/assets/commands/release-review.md +0 -10
  115. package/assets/commands/route.md +0 -9
  116. package/assets/commands/schedule-add.md +0 -10
  117. package/assets/commands/schedule-disable.md +0 -10
  118. package/assets/commands/schedule-enable.md +0 -10
  119. package/assets/commands/schedule-list.md +0 -10
  120. package/assets/commands/schedule-remove.md +0 -10
  121. package/assets/commands/schedule-status.md +0 -10
  122. package/assets/commands/skill-improver.md +0 -10
  123. package/assets/commands/spec-audit.md +0 -10
  124. package/assets/commands/spec-init.md +0 -10
  125. package/assets/commands/spec-onboard.md +0 -10
  126. package/assets/commands/spec-update.md +0 -10
  127. package/assets/commands/stopit.md +0 -10
  128. package/assets/commands/summary.md +0 -10
  129. package/assets/commands/task-prepare-batch.md +0 -10
  130. package/assets/commands/task-prepare.md +0 -10
  131. package/assets/commands/task-review.md +0 -10
  132. package/assets/commands/task-triage.md +0 -10
  133. package/assets/commands/team-planning.md +0 -10
  134. package/assets/commands/team-retro.md +0 -10
  135. package/assets/commands/team-roadmap.md +0 -10
  136. package/assets/commands/team-slides-prompts.md +0 -10
  137. package/assets/commands/team-sprint-close.md +0 -10
  138. package/assets/commands/team-sprint-status.md +0 -10
  139. package/assets/commands/walkthrough.md +0 -10
  140. package/assets/plugins/autonomy-policy.js +0 -3
  141. package/assets/plugins/background-attempts.js +0 -3
  142. package/assets/plugins/goal-loop.js +0 -3
  143. package/assets/plugins/schedule.js +0 -3
  144. package/assets/plugins/zed-clickable-paths.js +0 -3
  145. package/dist/plugins/autonomy-policy.d.ts +0 -21
  146. package/dist/plugins/autonomy-policy.js +0 -71
  147. package/dist/plugins/background-attempts.d.ts +0 -76
  148. package/dist/plugins/background-attempts.js +0 -113
  149. package/dist/plugins/goal-loop.d.ts +0 -51
  150. package/dist/plugins/goal-loop.js +0 -87
  151. package/dist/plugins/schedule.d.ts +0 -36
  152. package/dist/plugins/schedule.js +0 -100
  153. package/dist/plugins/zed-clickable-paths.d.ts +0 -11
  154. package/dist/plugins/zed-clickable-paths.js +0 -10
  155. /package/{assets → dist/assets}/plugins/rtk.js +0 -0
  156. /package/{assets → dist/assets}/plugins/rules-injector.js +0 -0
  157. /package/{assets → dist/assets}/plugins/zed-bell.js +0 -0
package/dist/cli.js CHANGED
@@ -1,11 +1,14 @@
1
1
  #!/usr/bin/env node
2
- import { createInterface } from "node:readline/promises";
3
2
  import { AgentProfileError, applyAgentProfileChange, availableModels, availableModelVariants, FIXED_AGENT_ROLES, listAgentProfiles, previewAgentProfileChange, validateAgentName, validateModel, validateVariant, } from "./agent-profiles.js";
4
- import { renderInventory, renderPlan, shellCommand, terminalSafe } from "./cli-output.js";
5
- import { apply, InstallerError, preview } from "./installer.js";
3
+ import { renderDoctor, renderInventory, renderPlan, renderReconcile, shellCommand, terminalSafe } from "./cli-output.js";
4
+ import { collectDoctorFacts, doctorExitCode } from "./doctor.js";
5
+ import { CATALOG } from "./catalog.js";
6
+ import { apply, defaultSelection, InstallerError, normalizeSelection, PACKAGE_COMMANDS, preview, SELECTABLE_PLUGINS, SKILL_COMMANDS } from "./installer.js";
6
7
  import { LifecycleError } from "./lifecycle.js";
7
- function parseOptions(values) {
8
- const options = { dryRun: false, json: false };
8
+ import { applyReconcile, previewReconcile } from "./reconcile.js";
9
+ import { promptText, selectOption } from "./terminal-wizard.js";
10
+ function parseOptions(values, requireScope = true) {
11
+ const options = { dryRun: false, json: false, selectionFlag: false };
9
12
  for (let index = 0; index < values.length; index += 1) {
10
13
  const value = values[index];
11
14
  if (!value.startsWith("--")) {
@@ -58,14 +61,78 @@ function parseOptions(values) {
58
61
  throw new InstallerError("invalid_input", "--json may be supplied once");
59
62
  options.json = true;
60
63
  }
64
+ else if (["--commands", "--skill-commands", "--package-commands", "--agents", "--plugins"].includes(value)) {
65
+ const raw = values[++index];
66
+ if (!raw)
67
+ throw new InstallerError("invalid_input", `${value} requires a comma-separated value`);
68
+ const target = value === "--agents" ? "agents" : value === "--plugins" ? "plugins" : "commands";
69
+ const names = raw === "none" ? [] : raw.split(",").map((item) => item.trim()).filter(Boolean);
70
+ options.selectionFlag = true;
71
+ if (value === "--package-commands") {
72
+ options.commands = [...(options.commands ?? []), ...names];
73
+ }
74
+ else if (value === "--skill-commands") {
75
+ options.commands = [...(options.commands ?? []), ...names];
76
+ }
77
+ else {
78
+ options[target] = names;
79
+ }
80
+ }
61
81
  else {
62
82
  throw new InstallerError("invalid_input", `Unknown argument: ${value}`);
63
83
  }
64
84
  }
65
- if (!options.scope)
85
+ if (requireScope && !options.scope)
66
86
  throw new InstallerError("invalid_input", "--scope is required");
67
87
  return options;
68
88
  }
89
+ function help() {
90
+ return [
91
+ "Usage: skills-opencode <command> [options]",
92
+ "",
93
+ "Commands: install, uninstall, doctor, capabilities, reconcile, agent list|configure|model-set|reconcile, critic add|remove",
94
+ "Mutations: use --dry-run for preview, then --confirm <digest>.",
95
+ "Install selection: --commands, --package-commands, --agents, --plugins (comma-separated or none).",
96
+ "Read-only: --json is stable machine-readable output; --help and --version need no scope.",
97
+ "",
98
+ ].join("\n");
99
+ }
100
+ async function interactiveInstallerSelection() {
101
+ if (!process.stdin.isTTY || !process.stderr.isTTY)
102
+ throw new InstallerError("terminal_required", "install requires explicit selection flags outside a terminal");
103
+ const group = async (label, names, initial) => {
104
+ const choices = ["Select all", "Select none", ...names];
105
+ const selected = await selectOption(label, choices, process.stdin, process.stderr, initial);
106
+ if (selected === null)
107
+ throw new InstallerError("cancelled", "Wizard cancelled");
108
+ if (selected === 0)
109
+ return [...names];
110
+ if (selected === 1)
111
+ return [];
112
+ return [names[selected - 2]];
113
+ };
114
+ const commands = [
115
+ ...(await group("Skill commands", SKILL_COMMANDS, 0)),
116
+ ...(await group("Package commands", PACKAGE_COMMANDS, 0)),
117
+ ];
118
+ const agents = await group("Fixed agents", defaultSelection().agents, 0);
119
+ const plugins = await group("Selectable plugins (none selected by default)", SELECTABLE_PLUGINS, 1);
120
+ return normalizeSelection({ commands, agents: agents, plugins: plugins });
121
+ }
122
+ function installerSelection(options) {
123
+ return normalizeSelection({
124
+ commands: options.commands,
125
+ agents: options.agents,
126
+ plugins: options.plugins,
127
+ });
128
+ }
129
+ function selectionArguments(selection) {
130
+ return [
131
+ "--commands", selection.commands.join(",") || "none",
132
+ "--agents", selection.agents.join(",") || "none",
133
+ "--plugins", selection.plugins.join(",") || "none",
134
+ ];
135
+ }
69
136
  function requireConfirmationMode(options) {
70
137
  if (options.dryRun === Boolean(options.confirm))
71
138
  throw new InstallerError("invalid_input", "Use exactly one of --dry-run or --confirm <digest>");
@@ -83,56 +150,123 @@ function exactModel(options) {
83
150
  throw new InstallerError("invalid_input", "--provider is required when --model is not provider/model");
84
151
  return validateModel(`${options.provider}/${options.model}`);
85
152
  }
86
- async function choose(label, values, input) {
87
- process.stderr.write(`${label}:\n${values.map((value, index) => ` ${index + 1}. ${value}`).join("\n")}\n`);
88
- const answer = await input.question("> ");
89
- const index = Number(answer) - 1;
90
- if (!Number.isSafeInteger(index) || !values[index])
91
- throw new InstallerError("invalid_input", `Invalid ${label.toLowerCase()} selection`);
92
- return values[index];
93
- }
94
- async function interactiveSelection(options) {
95
- if (!process.stdin.isTTY || !process.stderr.isTTY)
153
+ async function interactiveSelection(options, action = "model-set") {
154
+ if (!process.stdin.isTTY || !process.stderr.isTTY) {
96
155
  throw new InstallerError("terminal_required", "agent configure requires a terminal or explicit --provider and --model");
97
- const input = createInterface({ input: process.stdin, output: process.stderr });
98
- try {
99
- const inventory = await listAgentProfiles(options.scope);
100
- const configurable = inventory.profiles.filter((item) => item.ownership !== "user-owned").map((item) => item.name);
101
- const name = options.name ? validateAgentName(options.name) : await choose("Agent", configurable.length ? configurable : FIXED_AGENT_ROLES, input);
102
- if (options.provider && options.model)
103
- return { name, model: exactModel(options), ...(validateVariant(options.variant) ? { variant: validateVariant(options.variant) } : {}) };
104
- let models;
105
- try {
106
- models = await availableModels();
156
+ }
157
+ const inventory = await listAgentProfiles(options.scope);
158
+ const configurable = inventory.profiles.filter((item) => item.ownership !== "user-owned");
159
+ let name;
160
+ if (options.name) {
161
+ name = action === "critic-add" && !options.name.startsWith("critic-")
162
+ ? validateAgentName(`critic-${options.name}`)
163
+ : validateAgentName(options.name);
164
+ }
165
+ else if (action === "critic-add") {
166
+ const raw = await promptText("Critic name (critic-<suffix>):");
167
+ if (raw === null)
168
+ throw new InstallerError("cancelled", "Wizard cancelled");
169
+ name = raw.startsWith("critic-") ? raw : `critic-${raw}`;
170
+ validateAgentName(name);
171
+ }
172
+ else {
173
+ const agentNames = configurable.map((item) => item.name);
174
+ if (agentNames.length === 0)
175
+ agentNames.push(...FIXED_AGENT_ROLES);
176
+ const index = await selectOption("Select agent:", agentNames);
177
+ if (index === null)
178
+ throw new InstallerError("cancelled", "Wizard cancelled");
179
+ name = agentNames[index];
180
+ }
181
+ const currentProfile = configurable.find((item) => item.name === name);
182
+ const currentModel = currentProfile?.model;
183
+ const currentVariant = currentProfile?.variant;
184
+ const showTarget = (model, variant) => {
185
+ process.stderr.write(`Target: ${model}${variant ? ` / ${variant}` : ""}\n`);
186
+ };
187
+ if (options.provider && options.model) {
188
+ const model = exactModel(options);
189
+ const variant = validateVariant(options.variant);
190
+ return { name, model, ...(variant ? { variant } : {}) };
191
+ }
192
+ while (true) {
193
+ const actions = [];
194
+ if (currentModel) {
195
+ actions.push(`Keep current (${currentModel}${currentVariant ? ` / ${currentVariant}` : ""})`);
196
+ }
197
+ actions.push("Change model");
198
+ if (currentVariant)
199
+ actions.push("Clear variant");
200
+ if (!options.name && action === "model-set")
201
+ actions.push("Back");
202
+ actions.push("Cancel");
203
+ const currentLabel = `Agent: ${name}${currentModel ? ` (current: ${currentModel}${currentVariant ? ` / ${currentVariant}` : ""})` : ""}`;
204
+ const actionIndex = await selectOption(currentLabel, actions);
205
+ if (actionIndex === null)
206
+ throw new InstallerError("cancelled", "Wizard cancelled");
207
+ const chosen = actions[actionIndex];
208
+ if (chosen.startsWith("Keep current")) {
209
+ if (!currentModel)
210
+ throw new InstallerError("invalid_state", "No current model to keep");
211
+ showTarget(currentModel, currentVariant);
212
+ return { name, model: currentModel, ...(currentVariant ? { variant: currentVariant } : {}) };
107
213
  }
108
- catch (error) {
109
- if (!(error instanceof AgentProfileError) || error.code !== "catalog_unavailable")
214
+ if (chosen === "Change model") {
215
+ let models;
216
+ try {
217
+ models = await availableModels();
218
+ }
219
+ catch (error) {
220
+ if (error instanceof AgentProfileError && error.code === "catalog_unavailable") {
221
+ process.stderr.write("\nModel catalog is unavailable.\nUse direct CLI with exact --model provider/model and optional --variant.\n\n");
222
+ throw new InstallerError("catalog_unavailable", "Use direct CLI with exact --model provider/model");
223
+ }
224
+ throw error;
225
+ }
226
+ const providers = [...new Set(models.map((m) => m.split("/", 1)[0]))].sort();
227
+ const providerIndex = await selectOption("Select provider:", providers);
228
+ if (providerIndex === null)
229
+ continue;
230
+ const provider = providers[providerIndex];
231
+ const filteredModels = models.filter((m) => m.startsWith(`${provider}/`));
232
+ const modelIndex = await selectOption("Select model:", filteredModels);
233
+ if (modelIndex === null)
234
+ continue;
235
+ const selectedModel = filteredModels[modelIndex];
236
+ let selectedVariant;
237
+ try {
238
+ const variants = await availableModelVariants(selectedModel);
239
+ if (variants.length) {
240
+ const variantOptions = ["(none)", ...variants];
241
+ const variantIndex = await selectOption("Select variant:", variantOptions);
242
+ if (variantIndex === null)
243
+ continue;
244
+ if (variantIndex > 0)
245
+ selectedVariant = variantOptions[variantIndex];
246
+ }
247
+ }
248
+ catch (error) {
249
+ if (error instanceof AgentProfileError && error.code === "catalog_unavailable") {
250
+ process.stderr.write("\nModel variant metadata is unavailable.\nUse direct CLI with exact --model provider/model and optional --variant.\n\n");
251
+ throw new InstallerError("catalog_unavailable", "Use direct CLI with exact --model provider/model");
252
+ }
110
253
  throw error;
111
- const provider = options.provider ?? (await input.question("Provider: ")).trim();
112
- const model = options.model ?? (await input.question("Model: ")).trim();
113
- const variant = options.variant === undefined ? (await input.question("Variant (optional): ")).trim() : options.variant;
114
- return { name, model: validateModel(model.includes("/") ? model : `${provider}/${model}`), ...(validateVariant(variant) ? { variant: validateVariant(variant) } : {}) };
254
+ }
255
+ showTarget(selectedModel, selectedVariant);
256
+ return { name, model: selectedModel, ...(selectedVariant ? { variant: selectedVariant } : {}) };
115
257
  }
116
- const providers = [...new Set(models.map((model) => model.split("/", 1)[0]))].sort();
117
- const provider = options.provider ?? (await choose("Provider", providers, input));
118
- const selectedModel = options.model
119
- ? exactModel({ ...options, provider })
120
- : await choose("Model", models.filter((model) => model.startsWith(`${provider}/`)), input);
121
- let variants = [];
122
- try {
123
- variants = await availableModelVariants(selectedModel);
258
+ if (chosen === "Clear variant") {
259
+ if (!currentModel)
260
+ throw new InstallerError("invalid_state", "No model to clear variant for");
261
+ showTarget(currentModel);
262
+ return { name, model: currentModel };
124
263
  }
125
- catch (error) {
126
- if (!(error instanceof AgentProfileError) || error.code !== "catalog_unavailable")
127
- throw error;
264
+ if (chosen === "Back") {
265
+ return interactiveSelection({ ...options, name: undefined }, action);
266
+ }
267
+ if (chosen === "Cancel") {
268
+ throw new InstallerError("cancelled", "Wizard cancelled");
128
269
  }
129
- const variant = options.variant === null
130
- ? undefined
131
- : options.variant ?? (variants.length ? await choose("Variant", ["none", ...variants.filter((value) => value !== "none")], input) : (await input.question("Variant (optional): ")).trim());
132
- return { name, model: selectedModel, ...(variant && variant !== "none" ? { variant: validateVariant(variant) } : {}) };
133
- }
134
- finally {
135
- input.close();
136
270
  }
137
271
  }
138
272
  async function runProfile(request, options) {
@@ -167,7 +301,61 @@ function profileConfirmationArguments(request, scope, digest) {
167
301
  return command;
168
302
  }
169
303
  async function run(arguments_) {
304
+ if (arguments_.includes("--help") || arguments_.length === 0) {
305
+ process.stdout.write(help());
306
+ return;
307
+ }
308
+ if (arguments_.includes("--version")) {
309
+ process.stdout.write(arguments_.includes("--json")
310
+ ? `${JSON.stringify({ status: "ok", version: CATALOG.version })}\n`
311
+ : `${CATALOG.version}\n`);
312
+ return;
313
+ }
170
314
  const [domain, operation, ...rest] = arguments_;
315
+ if (domain === "doctor") {
316
+ if (operation)
317
+ rest.unshift(operation);
318
+ const options = parseOptions(rest);
319
+ if (options.dryRun || options.confirm || options.name || options.provider || options.model || options.variant !== undefined)
320
+ throw new InstallerError("invalid_input", "doctor accepts only --scope and --json");
321
+ const report = await collectDoctorFacts(options.scope);
322
+ if (options.json)
323
+ process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
324
+ else
325
+ process.stdout.write(renderDoctor(report));
326
+ process.exitCode = doctorExitCode(report);
327
+ return;
328
+ }
329
+ if (domain === "capabilities") {
330
+ const options = parseOptions(rest, false);
331
+ if (options.dryRun || options.confirm || options.name || options.provider || options.model || options.variant !== undefined || options.selectionFlag)
332
+ throw new InstallerError("invalid_input", "capabilities accepts only --scope and --json");
333
+ process.stdout.write(`${JSON.stringify({ schema_version: 1, status: "ok", ...CATALOG }, null, 2)}\n`);
334
+ return;
335
+ }
336
+ if (domain === "reconcile") {
337
+ if (operation)
338
+ rest.unshift(operation);
339
+ const options = parseOptions(rest);
340
+ if (options.name || options.provider || options.model || options.variant !== undefined)
341
+ throw new InstallerError("invalid_input", "reconcile accepts only scope and confirmation options");
342
+ requireConfirmationMode(options);
343
+ if (options.dryRun) {
344
+ const plan = await previewReconcile(options.scope);
345
+ if (options.json)
346
+ process.stdout.write(`${JSON.stringify({ status: "ok", applied: false, plan }, null, 2)}\n`);
347
+ else
348
+ process.stdout.write(renderReconcile(plan, { applied: false, confirmationCommand: shellCommand(["reconcile", "--scope", options.scope, "--confirm", plan.digest]) }));
349
+ }
350
+ else {
351
+ const applied = await applyReconcile(options.scope, options.confirm);
352
+ if (options.json)
353
+ process.stdout.write(`${JSON.stringify(applied, null, 2)}\n`);
354
+ else
355
+ process.stdout.write(renderReconcile(applied.plan, { applied: true }));
356
+ }
357
+ return;
358
+ }
171
359
  if (domain === "install" || domain === "uninstall") {
172
360
  if (operation?.startsWith("--") || operation === undefined)
173
361
  rest.unshift(...(operation ? [operation] : []));
@@ -176,17 +364,22 @@ async function run(arguments_) {
176
364
  const options = parseOptions(rest);
177
365
  if (options.name || options.provider || options.model || options.variant !== undefined)
178
366
  throw new InstallerError("invalid_input", "Installer accepts only scope and confirmation options");
179
- requireConfirmationMode(options);
180
367
  const action = domain;
368
+ if (action === "install" && options.selectionFlag && (options.commands === undefined || options.agents === undefined || options.plugins === undefined))
369
+ throw new InstallerError("invalid_input", "Non-TTY install requires --commands, --agents, and --plugins");
370
+ const selection = action === "install"
371
+ ? options.selectionFlag ? installerSelection(options) : await interactiveInstallerSelection()
372
+ : undefined;
373
+ requireConfirmationMode(options);
181
374
  if (options.dryRun) {
182
- const plan = await preview(action, options.scope);
375
+ const plan = await preview(action, options.scope, process.cwd(), undefined, selection);
183
376
  if (options.json)
184
- process.stdout.write(`${JSON.stringify({ status: "ok", applied: false, requires_restart: false, plan }, null, 2)}\n`);
377
+ process.stdout.write(`${JSON.stringify({ status: "ok", applied: false, requires_restart: plan.requires_restart, plan }, null, 2)}\n`);
185
378
  else
186
- process.stdout.write(renderPlan(plan, { applied: false, confirmationCommand: shellCommand([action, "--scope", options.scope, "--confirm", plan.digest]) }));
379
+ process.stdout.write(renderPlan(plan, { applied: false, confirmationCommand: shellCommand([action, "--scope", options.scope, ...selectionArguments(plan.selection), "--confirm", plan.digest]) }));
187
380
  }
188
381
  else {
189
- const plan = await apply(action, options.scope, options.confirm);
382
+ const plan = await apply(action, options.scope, options.confirm, process.cwd(), undefined, {}, selection);
190
383
  if (options.json)
191
384
  process.stdout.write(`${JSON.stringify({ status: "ok", applied: true, requires_restart: plan.requires_restart, plan }, null, 2)}\n`);
192
385
  else
@@ -210,7 +403,7 @@ async function run(arguments_) {
210
403
  requireConfirmationMode(options);
211
404
  const selected = options.provider && options.model && options.name
212
405
  ? { name: validateAgentName(options.name), model: exactModel(options), ...(validateVariant(options.variant) ? { variant: validateVariant(options.variant) } : {}) }
213
- : await interactiveSelection(options);
406
+ : await interactiveSelection(options, "model-set");
214
407
  await runProfile({ action: "model-set", ...selected, variant: selected.variant ?? null }, options);
215
408
  return;
216
409
  }
@@ -237,9 +430,13 @@ async function run(arguments_) {
237
430
  : `critic-${options.name ?? ""}`;
238
431
  if (operation === "add") {
239
432
  const model = exactModel(options);
240
- if (!model)
241
- throw new InstallerError("invalid_input", "critic add requires --provider and --model, or exact --model provider/model");
242
- await runProfile({ action: "critic-add", name, model, variant: options.variant ?? null }, options);
433
+ if (!model) {
434
+ const selected = await interactiveSelection(options, "critic-add");
435
+ await runProfile({ action: "critic-add", name: selected.name, model: selected.model, variant: selected.variant ?? null }, options);
436
+ }
437
+ else {
438
+ await runProfile({ action: "critic-add", name, model, variant: options.variant ?? null }, options);
439
+ }
243
440
  }
244
441
  else {
245
442
  if (options.provider || options.model || options.variant !== undefined)
@@ -0,0 +1,52 @@
1
+ import { type ExecutionCard } from "./routing.js";
2
+ export declare const CONTRACT_SCHEMA_VERSION: 1;
3
+ export type AgentReportStatus = "COMPLETED" | "BLOCKED" | "FAILED" | "REJECTED_PLAN" | "APPROVED" | "CHANGES_REQUIRED";
4
+ export type MapperReport = {
5
+ mapper_report: {
6
+ schema_version: 1;
7
+ paths: Array<Record<string, string>>;
8
+ callers: Array<Record<string, string>>;
9
+ tests: Array<Record<string, string>>;
10
+ patterns: Array<Record<string, string>>;
11
+ evidence_gaps: string[];
12
+ };
13
+ };
14
+ export type WorkerReport = {
15
+ worker_report: {
16
+ schema_version: 1;
17
+ status: Extract<AgentReportStatus, "COMPLETED" | "BLOCKED" | "FAILED" | "REJECTED_PLAN">;
18
+ card_id: string | null;
19
+ revision: number | null;
20
+ changed_files: string[];
21
+ checks: Array<{
22
+ command: string;
23
+ status: string;
24
+ }>;
25
+ writes_performed: boolean;
26
+ risks: string[];
27
+ };
28
+ };
29
+ export type ReviewReport = {
30
+ review_report: {
31
+ schema_version: 1;
32
+ status: "APPROVED" | "CHANGES_REQUIRED";
33
+ target: string;
34
+ findings: Array<Record<string, unknown>>;
35
+ evidence: string[];
36
+ checks: string[];
37
+ risks: string[];
38
+ };
39
+ };
40
+ export type CriticReport = {
41
+ critic_report: {
42
+ schema_version: 1;
43
+ status: "APPROVED" | "CHANGES_REQUIRED";
44
+ card_id: string;
45
+ revision: number;
46
+ findings: Array<Record<string, unknown>>;
47
+ evidence: string[];
48
+ unrun_checks: string[];
49
+ risks: string[];
50
+ };
51
+ };
52
+ export declare function validateAgentReport(agent: string, report: unknown, card?: ExecutionCard): void;
@@ -0,0 +1,98 @@
1
+ import { validateExecutionCard } from "./routing.js";
2
+ export const CONTRACT_SCHEMA_VERSION = 1;
3
+ function object(value) {
4
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
5
+ }
6
+ function strings(value) {
7
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
8
+ }
9
+ export function validateAgentReport(agent, report, card) {
10
+ if (!object(report))
11
+ throw new Error("agent report must be an object");
12
+ const reportKey = agent === "architect" ? "execution_card" : `${agent}_report`;
13
+ if (Object.keys(report).length !== 1 || !(reportKey in report))
14
+ throw new Error(`${agent} report has an invalid envelope`);
15
+ const value = report[reportKey];
16
+ if (!object(value))
17
+ throw new Error(`missing ${agent}_report`);
18
+ if (value.schema_version !== CONTRACT_SCHEMA_VERSION)
19
+ throw new Error(`${agent} report schema version is unsupported`);
20
+ if (agent === "mapper") {
21
+ for (const key of ["paths", "callers", "tests", "patterns"])
22
+ if (!Array.isArray(value[key]))
23
+ throw new Error(`mapper report field is invalid: ${key}`);
24
+ if (Object.keys(value).some((key) => !["schema_version", "paths", "callers", "tests", "patterns", "evidence_gaps"].includes(key)))
25
+ throw new Error("mapper report contains unknown fields");
26
+ if (!strings(value.evidence_gaps))
27
+ throw new Error("mapper report evidence_gaps is invalid");
28
+ }
29
+ else if (agent === "architect") {
30
+ if (value.status === "NEEDS_EVIDENCE")
31
+ return;
32
+ const result = validateExecutionCard(value);
33
+ if (!result.valid)
34
+ throw new Error(`architect execution card is invalid: ${result.failedField}`);
35
+ }
36
+ else if (agent === "worker") {
37
+ if (!strings(value.changed_files) ||
38
+ !Array.isArray(value.checks) ||
39
+ typeof value.writes_performed !== "boolean" ||
40
+ !strings(value.risks))
41
+ throw new Error("worker report is invalid");
42
+ if (Object.keys(value).some((key) => ![
43
+ "schema_version",
44
+ "status",
45
+ "card_id",
46
+ "revision",
47
+ "changed_files",
48
+ "checks",
49
+ "writes_performed",
50
+ "risks",
51
+ ].includes(key)))
52
+ throw new Error("worker report contains unknown fields");
53
+ if (value.status === "COMPLETED" &&
54
+ (value.writes_performed !== true ||
55
+ value.checks.some((check) => check.status !== "passed")))
56
+ throw new Error("completed worker report has incomplete checks");
57
+ if (card && value.changed_files.some((path) => !card.write_set.includes(path)))
58
+ throw new Error("worker report writes outside execution card");
59
+ }
60
+ else if (agent === "review" || agent === "critic") {
61
+ if (!["APPROVED", "CHANGES_REQUIRED"].includes(String(value.status)) ||
62
+ !Array.isArray(value.findings) ||
63
+ (typeof value.target !== "string" && agent === "review"))
64
+ throw new Error(`${agent} report is invalid`);
65
+ if (agent === "review" &&
66
+ (!Array.isArray(value.checks) ||
67
+ !strings(value.checks) ||
68
+ !strings(value.risks) ||
69
+ !strings(value.evidence) ||
70
+ !value.target))
71
+ throw new Error("review report fields are invalid");
72
+ if (agent === "critic" &&
73
+ (typeof value.card_id !== "string" ||
74
+ !Number.isInteger(value.revision) ||
75
+ !strings(value.unrun_checks) ||
76
+ !strings(value.risks) ||
77
+ !strings(value.evidence)))
78
+ throw new Error("critic report fields are invalid");
79
+ const allowed = agent === "review"
80
+ ? ["schema_version", "status", "target", "findings", "evidence", "checks", "risks"]
81
+ : [
82
+ "schema_version",
83
+ "status",
84
+ "card_id",
85
+ "revision",
86
+ "findings",
87
+ "evidence",
88
+ "unrun_checks",
89
+ "risks",
90
+ ];
91
+ if (Object.keys(value).some((key) => !allowed.includes(key)))
92
+ throw new Error(`${agent} report contains unknown fields`);
93
+ }
94
+ else
95
+ throw new Error(`unknown report agent: ${agent}`);
96
+ if (card && (value.card_id !== card.card_id || value.revision !== card.revision))
97
+ throw new Error("agent report does not match execution card");
98
+ }
@@ -0,0 +1,44 @@
1
+ import { type Scope } from "./lifecycle.js";
2
+ export type DoctorCheckStatus = "pass" | "warn" | "fail" | "incomplete";
3
+ export type DoctorCheck = {
4
+ id: string;
5
+ status: DoctorCheckStatus;
6
+ summary: string;
7
+ evidence: Record<string, string | number | boolean | string[]>;
8
+ remediation?: string[];
9
+ };
10
+ export type DoctorHost = {
11
+ config?: () => Promise<unknown>;
12
+ lsp?: () => Promise<unknown>;
13
+ };
14
+ export type DoctorReport = {
15
+ schema_version: 1;
16
+ status: "clean" | "problems" | "incomplete" | "error";
17
+ mutations: false;
18
+ scope: Scope;
19
+ roots: {
20
+ project: string;
21
+ deployment: string;
22
+ lifecycle: string;
23
+ };
24
+ versions: {
25
+ package: string | null;
26
+ catalog: string;
27
+ installed_manifest: string | null;
28
+ opencode: string | null;
29
+ };
30
+ checks: DoctorCheck[];
31
+ counts: Record<DoctorCheckStatus, number>;
32
+ partial: string[];
33
+ unavailable: string[];
34
+ lsp: Record<string, unknown>;
35
+ retired: Record<string, unknown>;
36
+ conflicts: Array<Record<string, unknown>>;
37
+ archive: {
38
+ path: string;
39
+ entries: number;
40
+ valid: boolean;
41
+ };
42
+ };
43
+ export declare function collectDoctorFacts(scope: Scope, cwd?: string, home?: string, host?: DoctorHost): Promise<DoctorReport>;
44
+ export declare function doctorExitCode(report: DoctorReport): number;