@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.
- package/README.md +134 -108
- package/README.ru.md +242 -0
- package/dist/agent-profiles.d.ts +1 -1
- package/dist/agent-profiles.js +21 -10
- package/dist/assets/agents/architect.md +67 -0
- package/{assets → dist/assets}/agents/critic.md +17 -2
- package/dist/assets/agents/manager.md +36 -0
- package/{assets → dist/assets}/agents/mapper.md +2 -2
- package/dist/assets/agents/review.md +25 -0
- package/{assets → dist/assets}/agents/worker.md +7 -5
- package/dist/assets/commands/agent-profiles.md +9 -0
- package/dist/assets/commands/agents-md.md +10 -0
- package/dist/assets/commands/askme.md +10 -0
- package/dist/assets/commands/ast-grep.md +10 -0
- package/dist/assets/commands/capabilities.md +9 -0
- package/dist/assets/commands/code-explain.md +10 -0
- package/dist/assets/commands/code-review.md +10 -0
- package/dist/assets/commands/commit-msg.md +10 -0
- package/dist/assets/commands/docs-prepare.md +10 -0
- package/dist/assets/commands/docs-review.md +10 -0
- package/dist/assets/commands/doctor.md +9 -0
- package/dist/assets/commands/doit.md +10 -0
- package/dist/assets/commands/goal.md +10 -0
- package/dist/assets/commands/humanize.md +10 -0
- package/dist/assets/commands/lsp-report.md +10 -0
- package/dist/assets/commands/mattermost.md +10 -0
- package/dist/assets/commands/mr-prepare.md +10 -0
- package/dist/assets/commands/reconcile.md +9 -0
- package/dist/assets/commands/release-prepare.md +10 -0
- package/dist/assets/commands/release-review.md +10 -0
- package/dist/assets/commands/rtk.md +10 -0
- package/dist/assets/commands/skill-improve.md +10 -0
- package/dist/assets/commands/slides-prompts-prepare.md +10 -0
- package/dist/assets/commands/spec-manage.md +10 -0
- package/dist/assets/commands/stopit.md +10 -0
- package/dist/assets/commands/summary.md +10 -0
- package/dist/assets/commands/task-prepare.md +10 -0
- package/dist/assets/commands/task-review.md +10 -0
- package/dist/assets/commands/task-triage.md +10 -0
- package/dist/assets/commands/team-retro.md +10 -0
- package/dist/assets/commands/team-roadmap.md +10 -0
- package/dist/assets/commands/team-sprint-close.md +10 -0
- package/dist/assets/commands/team-sprint-start.md +10 -0
- package/dist/assets/lsp-catalog.json +30 -0
- package/dist/assets/migration-inventory.json +294 -0
- package/dist/catalog.d.ts +8 -0
- package/dist/catalog.js +38 -0
- package/dist/cli-output.d.ts +7 -0
- package/dist/cli-output.js +68 -1
- package/dist/cli.js +256 -59
- package/dist/contracts.d.ts +52 -0
- package/dist/contracts.js +98 -0
- package/dist/doctor.d.ts +44 -0
- package/dist/doctor.js +595 -0
- package/dist/generate-assets.js +17 -2
- package/dist/index.d.ts +84 -55
- package/dist/index.js +170 -37
- package/dist/installer.d.ts +33 -5
- package/dist/installer.js +252 -28
- package/dist/lifecycle.d.ts +3 -0
- package/dist/lifecycle.js +49 -17
- package/dist/plugins/rtk.js +4 -1
- package/dist/reconcile.d.ts +43 -0
- package/dist/reconcile.js +507 -0
- package/dist/registry.d.ts +7 -4
- package/dist/registry.js +28 -88
- package/dist/routing.d.ts +97 -3
- package/dist/routing.js +470 -18
- package/dist/runtime/state.d.ts +1 -0
- package/dist/runtime/state.js +12 -5
- package/dist/runtime/worktree.d.ts +33 -0
- package/dist/runtime/worktree.js +220 -0
- package/dist/terminal-wizard.d.ts +2 -0
- package/dist/terminal-wizard.js +125 -0
- package/package.json +10 -26
- package/assets/agents/architect.md +0 -33
- package/assets/agents/manager.md +0 -82
- package/assets/agents/review.md +0 -25
- package/assets/commands/agent-list.md +0 -9
- package/assets/commands/agent-model-set.md +0 -9
- package/assets/commands/askme.md +0 -10
- package/assets/commands/ast-grep-rewrite.md +0 -10
- package/assets/commands/ast-grep-search.md +0 -10
- package/assets/commands/attempt-cancel.md +0 -10
- package/assets/commands/attempt-list.md +0 -10
- package/assets/commands/attempt-result.md +0 -10
- package/assets/commands/attempt-show.md +0 -10
- package/assets/commands/capabilities.md +0 -9
- package/assets/commands/code-review.md +0 -10
- package/assets/commands/commit-msg.md +0 -10
- package/assets/commands/critic-add.md +0 -9
- package/assets/commands/critic-remove.md +0 -9
- package/assets/commands/docs-prepare.md +0 -10
- package/assets/commands/docs-review.md +0 -10
- package/assets/commands/doctor.md +0 -9
- package/assets/commands/doit.md +0 -10
- package/assets/commands/goal-list.md +0 -10
- package/assets/commands/goal-pause.md +0 -10
- package/assets/commands/goal-prepare.md +0 -10
- package/assets/commands/goal-remove.md +0 -10
- package/assets/commands/goal-show.md +0 -10
- package/assets/commands/goal-start.md +0 -10
- package/assets/commands/lsp-report.md +0 -10
- package/assets/commands/mattermost.md +0 -10
- package/assets/commands/mr-prepare.md +0 -10
- package/assets/commands/mr-review.md +0 -10
- package/assets/commands/multi-run-cancel.md +0 -10
- package/assets/commands/multi-run-compare.md +0 -10
- package/assets/commands/multi-run-fusion.md +0 -10
- package/assets/commands/multi-run-start.md +0 -10
- package/assets/commands/multi-run-status.md +0 -10
- package/assets/commands/overview.md +0 -10
- package/assets/commands/release-prepare.md +0 -10
- package/assets/commands/release-review.md +0 -10
- package/assets/commands/route.md +0 -9
- package/assets/commands/schedule-add.md +0 -10
- package/assets/commands/schedule-disable.md +0 -10
- package/assets/commands/schedule-enable.md +0 -10
- package/assets/commands/schedule-list.md +0 -10
- package/assets/commands/schedule-remove.md +0 -10
- package/assets/commands/schedule-status.md +0 -10
- package/assets/commands/skill-improver.md +0 -10
- package/assets/commands/spec-audit.md +0 -10
- package/assets/commands/spec-init.md +0 -10
- package/assets/commands/spec-onboard.md +0 -10
- package/assets/commands/spec-update.md +0 -10
- package/assets/commands/stopit.md +0 -10
- package/assets/commands/summary.md +0 -10
- package/assets/commands/task-prepare-batch.md +0 -10
- package/assets/commands/task-prepare.md +0 -10
- package/assets/commands/task-review.md +0 -10
- package/assets/commands/task-triage.md +0 -10
- package/assets/commands/team-planning.md +0 -10
- package/assets/commands/team-retro.md +0 -10
- package/assets/commands/team-roadmap.md +0 -10
- package/assets/commands/team-slides-prompts.md +0 -10
- package/assets/commands/team-sprint-close.md +0 -10
- package/assets/commands/team-sprint-status.md +0 -10
- package/assets/commands/walkthrough.md +0 -10
- package/assets/plugins/autonomy-policy.js +0 -3
- package/assets/plugins/background-attempts.js +0 -3
- package/assets/plugins/goal-loop.js +0 -3
- package/assets/plugins/schedule.js +0 -3
- package/assets/plugins/zed-clickable-paths.js +0 -3
- package/dist/plugins/autonomy-policy.d.ts +0 -21
- package/dist/plugins/autonomy-policy.js +0 -71
- package/dist/plugins/background-attempts.d.ts +0 -76
- package/dist/plugins/background-attempts.js +0 -113
- package/dist/plugins/goal-loop.d.ts +0 -51
- package/dist/plugins/goal-loop.js +0 -87
- package/dist/plugins/schedule.d.ts +0 -36
- package/dist/plugins/schedule.js +0 -100
- package/dist/plugins/zed-clickable-paths.d.ts +0 -11
- package/dist/plugins/zed-clickable-paths.js +0 -10
- /package/{assets → dist/assets}/plugins/rtk.js +0 -0
- /package/{assets → dist/assets}/plugins/rules-injector.js +0 -0
- /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 {
|
|
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
|
-
|
|
8
|
-
|
|
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
|
|
87
|
-
process.
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
109
|
-
|
|
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
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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:
|
|
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
|
-
|
|
242
|
-
|
|
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
|
+
}
|
package/dist/doctor.d.ts
ADDED
|
@@ -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;
|