@kisev/agentomatic 11.0.0-dev.46.gfbe1e4c6992e

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