@kisev/skills-opencode 1.2.0 → 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 (153) hide show
  1. package/README.md +134 -112
  2. package/README.ru.md +242 -0
  3. package/dist/agent-profiles.d.ts +1 -1
  4. package/dist/agent-profiles.js +18 -9
  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 +136 -10
  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 +80 -56
  57. package/dist/index.js +162 -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 +39 -2
  68. package/dist/routing.js +339 -27
  69. package/dist/runtime/worktree.d.ts +33 -0
  70. package/dist/runtime/worktree.js +220 -0
  71. package/package.json +10 -26
  72. package/assets/agents/architect.md +0 -33
  73. package/assets/agents/manager.md +0 -82
  74. package/assets/agents/review.md +0 -25
  75. package/assets/commands/agent-list.md +0 -9
  76. package/assets/commands/agent-model-set.md +0 -9
  77. package/assets/commands/askme.md +0 -10
  78. package/assets/commands/ast-grep-rewrite.md +0 -10
  79. package/assets/commands/ast-grep-search.md +0 -10
  80. package/assets/commands/attempt-cancel.md +0 -10
  81. package/assets/commands/attempt-list.md +0 -10
  82. package/assets/commands/attempt-result.md +0 -10
  83. package/assets/commands/attempt-show.md +0 -10
  84. package/assets/commands/capabilities.md +0 -9
  85. package/assets/commands/code-review.md +0 -10
  86. package/assets/commands/commit-msg.md +0 -10
  87. package/assets/commands/critic-add.md +0 -9
  88. package/assets/commands/critic-remove.md +0 -9
  89. package/assets/commands/docs-prepare.md +0 -10
  90. package/assets/commands/docs-review.md +0 -10
  91. package/assets/commands/doctor.md +0 -9
  92. package/assets/commands/doit.md +0 -10
  93. package/assets/commands/goal-list.md +0 -10
  94. package/assets/commands/goal-pause.md +0 -10
  95. package/assets/commands/goal-prepare.md +0 -10
  96. package/assets/commands/goal-remove.md +0 -10
  97. package/assets/commands/goal-show.md +0 -10
  98. package/assets/commands/goal-start.md +0 -10
  99. package/assets/commands/lsp-report.md +0 -10
  100. package/assets/commands/mattermost.md +0 -10
  101. package/assets/commands/mr-prepare.md +0 -10
  102. package/assets/commands/mr-review.md +0 -10
  103. package/assets/commands/multi-run-cancel.md +0 -10
  104. package/assets/commands/multi-run-compare.md +0 -10
  105. package/assets/commands/multi-run-fusion.md +0 -10
  106. package/assets/commands/multi-run-start.md +0 -10
  107. package/assets/commands/multi-run-status.md +0 -10
  108. package/assets/commands/overview.md +0 -10
  109. package/assets/commands/release-prepare.md +0 -10
  110. package/assets/commands/release-review.md +0 -10
  111. package/assets/commands/route.md +0 -9
  112. package/assets/commands/schedule-add.md +0 -10
  113. package/assets/commands/schedule-disable.md +0 -10
  114. package/assets/commands/schedule-enable.md +0 -10
  115. package/assets/commands/schedule-list.md +0 -10
  116. package/assets/commands/schedule-remove.md +0 -10
  117. package/assets/commands/schedule-status.md +0 -10
  118. package/assets/commands/skill-improver.md +0 -10
  119. package/assets/commands/spec-audit.md +0 -10
  120. package/assets/commands/spec-init.md +0 -10
  121. package/assets/commands/spec-onboard.md +0 -10
  122. package/assets/commands/spec-update.md +0 -10
  123. package/assets/commands/stopit.md +0 -10
  124. package/assets/commands/summary.md +0 -10
  125. package/assets/commands/task-prepare-batch.md +0 -10
  126. package/assets/commands/task-prepare.md +0 -10
  127. package/assets/commands/task-review.md +0 -10
  128. package/assets/commands/task-triage.md +0 -10
  129. package/assets/commands/team-planning.md +0 -10
  130. package/assets/commands/team-retro.md +0 -10
  131. package/assets/commands/team-roadmap.md +0 -10
  132. package/assets/commands/team-slides-prompts.md +0 -10
  133. package/assets/commands/team-sprint-close.md +0 -10
  134. package/assets/commands/team-sprint-status.md +0 -10
  135. package/assets/commands/walkthrough.md +0 -10
  136. package/assets/plugins/autonomy-policy.js +0 -3
  137. package/assets/plugins/background-attempts.js +0 -3
  138. package/assets/plugins/goal-loop.js +0 -3
  139. package/assets/plugins/schedule.js +0 -3
  140. package/assets/plugins/zed-clickable-paths.js +0 -3
  141. package/dist/plugins/autonomy-policy.d.ts +0 -21
  142. package/dist/plugins/autonomy-policy.js +0 -71
  143. package/dist/plugins/background-attempts.d.ts +0 -76
  144. package/dist/plugins/background-attempts.js +0 -124
  145. package/dist/plugins/goal-loop.d.ts +0 -62
  146. package/dist/plugins/goal-loop.js +0 -99
  147. package/dist/plugins/schedule.d.ts +0 -36
  148. package/dist/plugins/schedule.js +0 -100
  149. package/dist/plugins/zed-clickable-paths.d.ts +0 -11
  150. package/dist/plugins/zed-clickable-paths.js +0 -10
  151. /package/{assets → dist/assets}/plugins/rtk.js +0 -0
  152. /package/{assets → dist/assets}/plugins/rules-injector.js +0 -0
  153. /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
2
  import { AgentProfileError, applyAgentProfileChange, availableModels, availableModelVariants, FIXED_AGENT_ROLES, listAgentProfiles, previewAgentProfileChange, validateAgentName, validateModel, validateVariant, } from "./agent-profiles.js";
3
- import { renderInventory, renderPlan, shellCommand, terminalSafe } from "./cli-output.js";
4
- 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";
5
7
  import { LifecycleError } from "./lifecycle.js";
8
+ import { applyReconcile, previewReconcile } from "./reconcile.js";
6
9
  import { promptText, selectOption } from "./terminal-wizard.js";
7
- function parseOptions(values) {
8
- const options = { dryRun: false, json: false };
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>");
@@ -234,7 +301,61 @@ function profileConfirmationArguments(request, scope, digest) {
234
301
  return command;
235
302
  }
236
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
+ }
237
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
+ }
238
359
  if (domain === "install" || domain === "uninstall") {
239
360
  if (operation?.startsWith("--") || operation === undefined)
240
361
  rest.unshift(...(operation ? [operation] : []));
@@ -243,17 +364,22 @@ async function run(arguments_) {
243
364
  const options = parseOptions(rest);
244
365
  if (options.name || options.provider || options.model || options.variant !== undefined)
245
366
  throw new InstallerError("invalid_input", "Installer accepts only scope and confirmation options");
246
- requireConfirmationMode(options);
247
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);
248
374
  if (options.dryRun) {
249
- const plan = await preview(action, options.scope);
375
+ const plan = await preview(action, options.scope, process.cwd(), undefined, selection);
250
376
  if (options.json)
251
- 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`);
252
378
  else
253
- 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]) }));
254
380
  }
255
381
  else {
256
- const plan = await apply(action, options.scope, options.confirm);
382
+ const plan = await apply(action, options.scope, options.confirm, process.cwd(), undefined, {}, selection);
257
383
  if (options.json)
258
384
  process.stdout.write(`${JSON.stringify({ status: "ok", applied: true, requires_restart: plan.requires_restart, plan }, null, 2)}\n`);
259
385
  else
@@ -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;