@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/doctor.js ADDED
@@ -0,0 +1,595 @@
1
+ import { execFile } from "node:child_process";
2
+ import { constants as fsConstants } from "node:fs";
3
+ import { readFile, readdir, lstat, stat } from "node:fs/promises";
4
+ import { readFileSync } from "node:fs";
5
+ import { homedir } from "node:os";
6
+ import { delimiter, dirname, join, resolve } from "node:path";
7
+ import { promisify } from "node:util";
8
+ import { fileURLToPath } from "node:url";
9
+ import { CATALOG } from "./catalog.js";
10
+ import { listAgentProfiles } from "./agent-profiles.js";
11
+ import { archiveRoot, assertSafePath, deploymentRoot, lifecycleRoot, } from "./lifecycle.js";
12
+ import { inspectReconcile } from "./reconcile.js";
13
+ import { stateRoot } from "./runtime/state.js";
14
+ const run = promisify(execFile);
15
+ const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
16
+ const packageName = "@kisev/skills-opencode";
17
+ const lspCatalog = JSON.parse(readFileSync(resolve(packageRoot, "dist", "assets", "lsp-catalog.json"), "utf8"));
18
+ function check(id, status, summary, evidence, remediation) {
19
+ return { id, status, summary, evidence, ...(remediation?.length ? { remediation } : {}) };
20
+ }
21
+ function errorText(error) {
22
+ const code = error && typeof error === "object" && "code" in error
23
+ ? String(error.code)
24
+ : "error";
25
+ return code === "EACCES" || code === "EPERM" ? "permission denied" : code;
26
+ }
27
+ async function regular(path) {
28
+ try {
29
+ await assertSafePath(path);
30
+ const info = await lstat(path);
31
+ if (info.isSymbolicLink() || !info.isFile() || info.nlink !== 1)
32
+ return { status: "incomplete" };
33
+ return { raw: await readFile(path), status: "present" };
34
+ }
35
+ catch (error) {
36
+ if (error.code === "ENOENT")
37
+ return { status: "missing" };
38
+ return { status: "incomplete" };
39
+ }
40
+ }
41
+ async function directoryEntries(path) {
42
+ try {
43
+ await assertSafePath(path, { target: "directory" });
44
+ const info = await lstat(path);
45
+ if (info.isSymbolicLink() || !info.isDirectory())
46
+ return { files: [], incomplete: true };
47
+ const files = [];
48
+ let incomplete = false;
49
+ for (const entry of (await readdir(path, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name))) {
50
+ if (entry.isSymbolicLink()) {
51
+ incomplete = true;
52
+ continue;
53
+ }
54
+ if (entry.isFile())
55
+ files.push(entry.name);
56
+ else if (entry.isDirectory()) {
57
+ const nested = await directoryEntries(join(path, entry.name));
58
+ files.push(...nested.files.map((name) => `${entry.name}/${name}`));
59
+ incomplete ||= nested.incomplete;
60
+ }
61
+ }
62
+ return { files, incomplete };
63
+ }
64
+ catch (error) {
65
+ return { files: [], incomplete: errorText(error) !== "ENOENT" };
66
+ }
67
+ }
68
+ function json(raw) {
69
+ try {
70
+ const value = JSON.parse(raw.toString("utf8"));
71
+ return value && typeof value === "object" && !Array.isArray(value)
72
+ ? value
73
+ : undefined;
74
+ }
75
+ catch {
76
+ return undefined;
77
+ }
78
+ }
79
+ function packageVersion() {
80
+ try {
81
+ const value = JSON.parse(readFileSync(resolve(packageRoot, "package.json"), "utf8"));
82
+ return typeof value.version === "string" ? value.version : null;
83
+ }
84
+ catch {
85
+ return null;
86
+ }
87
+ }
88
+ function manifestVersion(raw) {
89
+ const value = raw && json(raw);
90
+ const files = value?.files;
91
+ const validFiles = files &&
92
+ typeof files === "object" &&
93
+ !Array.isArray(files) &&
94
+ Object.entries(files).every(([path, record]) => path.length > 0 &&
95
+ !path.startsWith("/") &&
96
+ !path.includes("\\") &&
97
+ !path.split("/").some((part) => part === "" || part === "." || part === "..") &&
98
+ record &&
99
+ typeof record === "object" &&
100
+ /^[a-f0-9]{64}$/.test(String(record.sha256 ?? "")));
101
+ return value &&
102
+ (value.schema_version === 1 || value.schema_version === 2) &&
103
+ value.package === packageName &&
104
+ typeof value.version === "string" &&
105
+ validFiles
106
+ ? value.version
107
+ : null;
108
+ }
109
+ function agentManifestValid(raw) {
110
+ const value = raw && json(raw);
111
+ return Boolean(value &&
112
+ value.schema_version === 1 &&
113
+ value.package === packageName &&
114
+ typeof value.package_version === "string" &&
115
+ (value.scope === "global" || value.scope === "project") &&
116
+ Array.isArray(value.critic_pool) &&
117
+ value.profiles &&
118
+ typeof value.profiles === "object" &&
119
+ !Array.isArray(value.profiles));
120
+ }
121
+ function redactedConfig(value) {
122
+ const object = value && typeof value === "object" && !Array.isArray(value) ? value : {};
123
+ const rawPlugins = Array.isArray(object.plugin)
124
+ ? object.plugin.flatMap((item) => {
125
+ if (typeof item === "string")
126
+ return [item];
127
+ if (Array.isArray(item) && typeof item[0] === "string")
128
+ return [item[0]];
129
+ return [];
130
+ })
131
+ : [];
132
+ const knownPlugins = new Set([packageName, ...CATALOG.plugins]);
133
+ const plugins = rawPlugins.filter((plugin) => knownPlugins.has(plugin));
134
+ const enabled = plugins.filter((plugin) => plugin === packageName ||
135
+ CATALOG.plugins.includes(plugin));
136
+ return {
137
+ plugins: plugins.sort(),
138
+ enabled_plugins: enabled.sort(),
139
+ disabled_plugins: CATALOG.plugins.filter((plugin) => !enabled.includes(plugin)),
140
+ unknown_plugins: rawPlugins.length - plugins.length,
141
+ keys: Object.keys(object)
142
+ .filter((key) => [
143
+ "$schema",
144
+ "theme",
145
+ "keybinds",
146
+ "logLevel",
147
+ "command",
148
+ "watcher",
149
+ "plugin",
150
+ "lsp",
151
+ "formatter",
152
+ "provider",
153
+ "agent",
154
+ "permission",
155
+ "compaction",
156
+ "model",
157
+ ].includes(key))
158
+ .sort(),
159
+ source: "allowlisted-projection",
160
+ };
161
+ }
162
+ async function localConfig(scope, cwd, home) {
163
+ const paths = scope === "project"
164
+ ? [join(cwd, "opencode.json"), join(cwd, ".opencode", "opencode.json")]
165
+ : [join(home, ".config", "opencode", "opencode.json")];
166
+ for (const path of paths) {
167
+ const value = await regular(path);
168
+ if (value.status === "present") {
169
+ const parsed = json(value.raw);
170
+ return parsed
171
+ ? { projection: redactedConfig(parsed), source: path }
172
+ : { incomplete: true, source: path };
173
+ }
174
+ if (value.status === "incomplete")
175
+ return { incomplete: true, source: path };
176
+ }
177
+ return {};
178
+ }
179
+ async function expectedAssets(group) {
180
+ try {
181
+ return (await readdir(join(packageRoot, "assets", group), { withFileTypes: true }))
182
+ .filter((entry) => entry.isFile())
183
+ .map((entry) => entry.name)
184
+ .sort();
185
+ }
186
+ catch {
187
+ return [];
188
+ }
189
+ }
190
+ async function filesInState(root) {
191
+ const result = await directoryEntries(root);
192
+ let malformed = 0;
193
+ let incomplete = result.incomplete;
194
+ for (const name of result.files.filter((value) => value.endsWith(".json") || value.endsWith(".jsonl"))) {
195
+ const value = await regular(join(root, name));
196
+ if (value.status === "incomplete") {
197
+ incomplete = true;
198
+ continue;
199
+ }
200
+ if (value.status !== "present")
201
+ continue;
202
+ const lines = name.endsWith(".jsonl")
203
+ ? value.raw.toString("utf8").split(/\r?\n/).filter(Boolean)
204
+ : [value.raw.toString("utf8")];
205
+ for (const line of lines) {
206
+ try {
207
+ JSON.parse(line);
208
+ }
209
+ catch {
210
+ malformed += 1;
211
+ }
212
+ }
213
+ }
214
+ return {
215
+ count: result.files.filter((name) => name.endsWith(".json") || name.endsWith(".jsonl")).length,
216
+ malformed,
217
+ incomplete,
218
+ };
219
+ }
220
+ function diagnosticStateRoot(name, home) {
221
+ return home === homedir()
222
+ ? stateRoot(name, home)
223
+ : join(home, ".local", "state", "opencode", "skills", name);
224
+ }
225
+ async function lifecycleArtifacts(root) {
226
+ const result = await filesInState(root);
227
+ const entries = await directoryEntries(root);
228
+ const files = entries.files.map((name) => name.toLowerCase());
229
+ return {
230
+ count: result.count,
231
+ locks: files.filter((name) => name.includes("lock")).length,
232
+ receipts: files.filter((name) => name.includes("receipt")).length,
233
+ journals: files.filter((name) => name.includes("journal")).length,
234
+ malformed: result.malformed,
235
+ incomplete: result.incomplete || entries.incomplete,
236
+ };
237
+ }
238
+ async function executableAvailable(name) {
239
+ const pathValue = process.env.PATH ?? "";
240
+ for (const directory of pathValue.split(delimiter)) {
241
+ if (!directory)
242
+ continue;
243
+ try {
244
+ const info = await stat(join(directory, name));
245
+ if (info.isFile() && (info.mode & fsConstants.X_OK) !== 0)
246
+ return true;
247
+ }
248
+ catch {
249
+ // A missing or inaccessible PATH entry is equivalent to an unavailable binary.
250
+ }
251
+ }
252
+ return false;
253
+ }
254
+ async function lspFacts(project, host, partial) {
255
+ const suffixes = new Set();
256
+ let scanIncomplete = false;
257
+ const visit = async (root) => {
258
+ try {
259
+ const info = await lstat(root);
260
+ if (info.isSymbolicLink()) {
261
+ scanIncomplete = true;
262
+ return;
263
+ }
264
+ if (info.isFile()) {
265
+ suffixes.add(root.slice(root.lastIndexOf(".")).toLowerCase());
266
+ return;
267
+ }
268
+ if (!info.isDirectory())
269
+ return;
270
+ for (const name of await readdir(root))
271
+ await visit(join(root, name));
272
+ }
273
+ catch (error) {
274
+ if (error.code !== "ENOENT")
275
+ scanIncomplete = true;
276
+ }
277
+ };
278
+ await visit(project);
279
+ if (scanIncomplete)
280
+ partial.push("lsp.project-files");
281
+ const disabled = process.env.OPENCODE_DISABLE_LSP_DOWNLOAD === "true";
282
+ const servers = await Promise.all(lspCatalog.servers.map(async (server) => {
283
+ let available = false;
284
+ available = await executableAvailable(server.executable);
285
+ const applicable = server.extensions.some((extension) => suffixes.has(extension));
286
+ return {
287
+ name: server.name,
288
+ applicable,
289
+ configured: applicable,
290
+ binary_available: available,
291
+ active: applicable && available && !disabled,
292
+ runtime_status: "unavailable",
293
+ requirement_class: server.requirement_class,
294
+ reason: !applicable
295
+ ? "not-selected"
296
+ : disabled
297
+ ? "download-disabled"
298
+ : !available
299
+ ? "missing-dependency"
300
+ : "available",
301
+ missing: available ? [] : [server.executable],
302
+ install: `Install ${server.executable} with your project toolchain.`,
303
+ };
304
+ }));
305
+ let runtimeStatus = "unavailable";
306
+ if (host?.lsp) {
307
+ try {
308
+ const value = await host.lsp();
309
+ runtimeStatus = Array.isArray(value)
310
+ ? value.map((item) => {
311
+ const entry = item && typeof item === "object" ? item : {};
312
+ return {
313
+ id: typeof entry.id === "string" ? entry.id : "unknown",
314
+ name: typeof entry.name === "string" ? entry.name : "unknown",
315
+ status: entry.status === "connected" || entry.status === "error" ? entry.status : "unknown",
316
+ };
317
+ })
318
+ : { status: "unavailable" };
319
+ }
320
+ catch {
321
+ partial.push("lsp.runtime-status");
322
+ }
323
+ }
324
+ else
325
+ partial.push("lsp.runtime-status");
326
+ return {
327
+ schema_version: 1,
328
+ catalog_version: lspCatalog.catalog_version,
329
+ download_disabled: disabled,
330
+ runtime_status: runtimeStatus,
331
+ servers,
332
+ };
333
+ }
334
+ function reconcileProjection(plan) {
335
+ const groups = [
336
+ "current",
337
+ "renamed",
338
+ "retired",
339
+ "archive-pending",
340
+ "modified_managed",
341
+ "user_owned",
342
+ "unknown",
343
+ "diagnostic_state_only",
344
+ ];
345
+ const retired = Object.fromEntries(groups.map((name) => [
346
+ name,
347
+ plan[name].map((item) => ({
348
+ path: item.path,
349
+ status: item.status,
350
+ replacement: item.replacement ?? null,
351
+ })),
352
+ ]));
353
+ const conflicts = plan.conflicts.map((item) => ({
354
+ path: item.path,
355
+ status: item.status,
356
+ reason: item.reason,
357
+ replacement: item.replacement ?? null,
358
+ }));
359
+ return { retired, conflicts };
360
+ }
361
+ async function archiveFacts(scope, cwd, home) {
362
+ const root = archiveRoot(scope, cwd, home);
363
+ const rootInfo = await lstat(root).catch((error) => {
364
+ if (error.code === "ENOENT")
365
+ return undefined;
366
+ return null;
367
+ });
368
+ if (rootInfo === null ||
369
+ (rootInfo &&
370
+ (!rootInfo.isDirectory() || rootInfo.isSymbolicLink() || (rootInfo.mode & 0o077) !== 0)))
371
+ return { path: root, entries: 0, valid: false };
372
+ const value = await regular(join(root, "index.json"));
373
+ if (value.status === "missing")
374
+ return { path: root, entries: 0, valid: true };
375
+ if (value.status !== "present")
376
+ return { path: root, entries: 0, valid: false };
377
+ const indexInfo = await lstat(join(root, "index.json")).catch(() => undefined);
378
+ if (!indexInfo || (indexInfo.mode & 0o777) !== 0o600)
379
+ return { path: root, entries: 0, valid: false };
380
+ try {
381
+ const parsed = JSON.parse(value.raw.toString("utf8"));
382
+ return {
383
+ path: root,
384
+ entries: parsed.schema_version === 1 && Array.isArray(parsed.entries) ? parsed.entries.length : 0,
385
+ valid: parsed.schema_version === 1 && Array.isArray(parsed.entries),
386
+ };
387
+ }
388
+ catch {
389
+ return { path: root, entries: 0, valid: false };
390
+ }
391
+ }
392
+ export async function collectDoctorFacts(scope, cwd = process.cwd(), home = homedir(), host) {
393
+ const project = resolve(cwd);
394
+ const homeRoot = resolve(home);
395
+ const deployment = deploymentRoot(scope, project, homeRoot);
396
+ const lifecycle = lifecycleRoot(scope, project, homeRoot);
397
+ const checks = [];
398
+ const partial = [];
399
+ const unavailable = [];
400
+ let installedRaw;
401
+ const packageVersionValue = packageVersion();
402
+ checks.push(check("package.version", packageVersionValue ? "pass" : "fail", "Package metadata is readable", { package: packageName, version: packageVersionValue ?? "unavailable" }, ["Reinstall the package if metadata is missing."]));
403
+ checks.push(check("catalog.version", CATALOG.version === packageVersionValue ? "pass" : "fail", "Capability catalog matches package metadata", { catalog_version: CATALOG.version, package_version: packageVersionValue ?? "unavailable" }, ["Regenerate the package catalog from package metadata."]));
404
+ const manifest = await regular(join(deployment, ".skills-opencode-manifest.json"));
405
+ if (manifest.status === "present")
406
+ installedRaw = manifest.raw;
407
+ if (manifest.status === "incomplete")
408
+ partial.push("installed-manifest");
409
+ const installedVersion = manifestVersion(installedRaw);
410
+ checks.push(check("manifest.version", manifest.status === "missing"
411
+ ? "incomplete"
412
+ : installedVersion === null
413
+ ? "warn"
414
+ : installedVersion === packageVersionValue
415
+ ? "pass"
416
+ : "warn", manifest.status === "present"
417
+ ? "Installed manifest format and version are classified"
418
+ : "Installed manifest is not available", {
419
+ path: ".skills-opencode-manifest.json",
420
+ present: manifest.status === "present",
421
+ valid: installedVersion !== null,
422
+ version: installedVersion ?? "unavailable",
423
+ }, ["Run the installer preview for this scope."]));
424
+ const assetGroups = await Promise.all(["commands", "plugins", "agents"].map(async (group) => [
425
+ group,
426
+ await directoryEntries(join(deployment, group)),
427
+ await expectedAssets(group),
428
+ ]));
429
+ for (const [group, entries, expected] of assetGroups) {
430
+ const missing = expected.filter((name) => !entries.files.includes(name));
431
+ const extra = entries.files.filter((name) => !expected.includes(name));
432
+ const status = entries.incomplete
433
+ ? "incomplete"
434
+ : missing.length || (group !== "agents" && extra.length)
435
+ ? "fail"
436
+ : "pass";
437
+ checks.push(check(`assets.${group}`, status, `${group} completeness and collisions are classified`, {
438
+ count: entries.files.length,
439
+ expected: expected.length,
440
+ missing,
441
+ collisions: group === "agents" ? [] : extra,
442
+ }, status === "fail"
443
+ ? ["Run the installer preview and review ownership conflicts before applying."]
444
+ : entries.incomplete
445
+ ? ["Remove symlinks or restore inaccessible asset paths."]
446
+ : undefined));
447
+ }
448
+ let inventory;
449
+ try {
450
+ inventory = await listAgentProfiles(scope, project, homeRoot);
451
+ }
452
+ catch {
453
+ partial.push("agent-profiles");
454
+ }
455
+ const agentStatus = inventory
456
+ ? inventory.collisions.length || inventory.drift.length
457
+ ? "fail"
458
+ : "pass"
459
+ : "incomplete";
460
+ checks.push(check("agents.completeness-ownership-drift", agentStatus, "Agent profile inventory is classified", {
461
+ profiles: inventory?.profiles.length ?? 0,
462
+ collisions: inventory?.collisions.length ?? 0,
463
+ drift: inventory?.drift.length ?? 0,
464
+ user_owned: inventory?.user_owned.length ?? 0,
465
+ }, ["Review collisions and run agent reconcile only after explicit confirmation."]));
466
+ const config = await localConfig(scope, project, homeRoot);
467
+ if (config.incomplete)
468
+ partial.push("config.local");
469
+ let hostConfig;
470
+ if (host?.config) {
471
+ try {
472
+ hostConfig = redactedConfig(await host.config());
473
+ }
474
+ catch {
475
+ partial.push("config.resolved");
476
+ }
477
+ }
478
+ else
479
+ unavailable.push("config.resolved");
480
+ checks.push(check("config.plugins", config.projection || hostConfig ? "pass" : "incomplete", "Config plugin projection is available", {
481
+ local: config.projection?.plugins ?? [],
482
+ local_enabled: config.projection?.enabled_plugins ?? [],
483
+ local_disabled: config.projection?.disabled_plugins ?? [...CATALOG.plugins],
484
+ local_unknown: config.projection?.unknown_plugins ?? 0,
485
+ resolved: hostConfig?.plugins ?? [],
486
+ resolved_enabled: hostConfig?.enabled_plugins ?? [],
487
+ resolved_disabled: hostConfig?.disabled_plugins ?? [...CATALOG.plugins],
488
+ resolved_unknown: hostConfig?.unknown_plugins ?? 0,
489
+ source: config.source ?? "host-or-file-unavailable",
490
+ }, ["Inspect OpenCode configuration without exposing secrets."]));
491
+ const profileManifest = await regular(join(deployment, ".skills-opencode", "agent-profiles.manifest.json"));
492
+ const profileManifestIsValid = agentManifestValid(profileManifest.raw);
493
+ checks.push(check("agents.manifest", profileManifest.status === "present"
494
+ ? profileManifestIsValid
495
+ ? "pass"
496
+ : "warn"
497
+ : profileManifest.status === "missing"
498
+ ? "incomplete"
499
+ : "warn", "Agent profile manifest is classified", {
500
+ present: profileManifest.status === "present",
501
+ valid: profileManifestIsValid,
502
+ path: ".skills-opencode/agent-profiles.manifest.json",
503
+ }, ["Run the installer preview for this scope."]));
504
+ let reconcile;
505
+ try {
506
+ reconcile = await inspectReconcile(scope, project, homeRoot);
507
+ }
508
+ catch {
509
+ partial.push("stage-8-reconcile");
510
+ }
511
+ const projected = reconcile ? reconcileProjection(reconcile) : { retired: {}, conflicts: [] };
512
+ const archive = await archiveFacts(scope, project, homeRoot);
513
+ checks.push(check("archive.index", archive.valid ? "pass" : "incomplete", "Private archive index is inspected without mutation", archive, archive.valid ? undefined : ["Repair or remove the invalid archive index manually."]));
514
+ checks.push(check("migration.stage-8", reconcile ? (projected.conflicts.length ? "fail" : "pass") : "incomplete", "Stage-8 inventory classifications are read-only", {
515
+ inventory_version: reconcile?.inventory_version ?? "unavailable",
516
+ retired: reconcile?.retired.length ?? 0,
517
+ conflicts: projected.conflicts.length,
518
+ }, ["Review retired or conflicting entries; do not use historical backup state."]));
519
+ const stateRoots = ["attempt", "schedule", "worktree", "goal", "multi-run"].map((name) => [name, diagnosticStateRoot(name, homeRoot)]);
520
+ for (const [name, root] of stateRoots) {
521
+ const value = await filesInState(root);
522
+ if (value.incomplete)
523
+ partial.push(`state.${name}`);
524
+ if (value.malformed)
525
+ partial.push(`state.${name}.malformed`);
526
+ checks.push(check(`state.${name}`, value.incomplete || value.malformed ? "incomplete" : "pass", `${name} state is inspected without recovery`, {
527
+ records: value.count,
528
+ malformed: value.malformed,
529
+ diagnostic_state_only: name === "goal" || name === "multi-run",
530
+ }, value.incomplete || value.malformed
531
+ ? ["Repair permissions or malformed state manually; doctor never recovers it."]
532
+ : undefined));
533
+ }
534
+ const lifecycleFiles = await lifecycleArtifacts(lifecycle);
535
+ checks.push(check("lifecycle.artifacts", lifecycleFiles.incomplete || lifecycleFiles.malformed ? "incomplete" : "pass", "Lifecycle locks, receipts, and journals are inspected without mutation", {
536
+ records: lifecycleFiles.count,
537
+ locks: lifecycleFiles.locks,
538
+ receipts: lifecycleFiles.receipts,
539
+ journals: lifecycleFiles.journals,
540
+ malformed: lifecycleFiles.malformed,
541
+ recovery: false,
542
+ }, lifecycleFiles.incomplete
543
+ ? ["Resolve inaccessible lifecycle state before applying a mutation."]
544
+ : undefined));
545
+ const tools = await Promise.all(["opencode", ...lspCatalog.servers.map((server) => server.executable)].map(async (name) => {
546
+ try {
547
+ const result = await run(name, ["--version"], { timeout: 1000 });
548
+ return {
549
+ name,
550
+ available: true,
551
+ version: result.stdout
552
+ .trim()
553
+ .split(/\s+/)
554
+ .find((value) => /\d+\.\d+/.test(value)) ?? null,
555
+ };
556
+ }
557
+ catch {
558
+ return { name, available: false, version: null };
559
+ }
560
+ }));
561
+ checks.push(check("host.tools", tools.some((item) => item.name === "opencode" && item.available) ? "pass" : "incomplete", "External tool availability is reported", { tools: tools.map((item) => `${item.name}:${item.available ? "available" : "missing"}`) }, ["Install OpenCode or missing external tools when needed."]));
562
+ const lsp = await lspFacts(project, host, partial);
563
+ const counts = { pass: 0, warn: 0, fail: 0, incomplete: 0 };
564
+ for (const item of checks)
565
+ counts[item.status] += 1;
566
+ const status = counts.fail
567
+ ? "problems"
568
+ : counts.incomplete || partial.length > 0 || unavailable.length > 0
569
+ ? "incomplete"
570
+ : "clean";
571
+ return {
572
+ schema_version: 1,
573
+ status,
574
+ mutations: false,
575
+ scope,
576
+ roots: { project, deployment, lifecycle },
577
+ versions: {
578
+ package: packageVersionValue,
579
+ catalog: CATALOG.version,
580
+ installed_manifest: installedVersion,
581
+ opencode: tools.find((item) => item.name === "opencode")?.version ?? null,
582
+ },
583
+ checks,
584
+ counts,
585
+ partial: [...new Set(partial)].sort(),
586
+ unavailable: [...new Set(unavailable)].sort(),
587
+ lsp,
588
+ retired: projected.retired,
589
+ conflicts: projected.conflicts,
590
+ archive,
591
+ };
592
+ }
593
+ export function doctorExitCode(report) {
594
+ return report.status === "clean" ? 0 : report.status === "problems" ? 1 : 2;
595
+ }
@@ -1,4 +1,4 @@
1
- import { mkdir, readFile, readdir, unlink, writeFile } from "node:fs/promises";
1
+ import { copyFile, cp, mkdir, readFile, readdir, unlink, writeFile } from "node:fs/promises";
2
2
  import { dirname, resolve } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import { COMMAND_REGISTRY, renderCommand } from "./registry.js";
@@ -8,7 +8,7 @@ const rootIndex = arguments_.indexOf("--root");
8
8
  if (rootIndex !== -1 && !arguments_[rootIndex + 1])
9
9
  throw new Error("--root requires a path");
10
10
  const commandsRoot = rootIndex === -1
11
- ? resolve(packageRoot, "assets", "commands")
11
+ ? resolve(packageRoot, "dist", "assets", "commands")
12
12
  : resolve(arguments_[rootIndex + 1] ?? "");
13
13
  const check = arguments_.includes("--check");
14
14
  const expectedNames = new Set(COMMAND_REGISTRY.map((command) => `${command.name}.md`));
@@ -37,3 +37,18 @@ for (const command of COMMAND_REGISTRY) {
37
37
  await writeFile(destination, expected, "utf8");
38
38
  }
39
39
  }
40
+ if (rootIndex === -1) {
41
+ const catalog = resolve(packageRoot, "..", "..", "shared", "references", "lsp-catalog.json");
42
+ const destination = resolve(packageRoot, "dist", "assets", "lsp-catalog.json");
43
+ if (check) {
44
+ if ((await readFile(destination).catch(() => Buffer.alloc(0))).compare(await readFile(catalog)) !== 0)
45
+ throw new Error("generated asset drift: lsp-catalog.json");
46
+ }
47
+ else {
48
+ await mkdir(dirname(destination), { recursive: true });
49
+ await copyFile(catalog, destination);
50
+ await cp(resolve(packageRoot, "assets", "agents"), resolve(packageRoot, "dist", "assets", "agents"), { recursive: true });
51
+ await cp(resolve(packageRoot, "assets", "plugins"), resolve(packageRoot, "dist", "assets", "plugins"), { recursive: true });
52
+ await copyFile(resolve(packageRoot, "assets", "migration-inventory.json"), resolve(packageRoot, "dist", "assets", "migration-inventory.json"));
53
+ }
54
+ }