neurastream 0.0.0-stage → 0.1.1

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 (101) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +36 -2
  3. package/bin/agent-ledger +6 -0
  4. package/bin/agent-score +6 -0
  5. package/bin/brief-lint +6 -0
  6. package/bin/glm-reset +6 -0
  7. package/bin/heavy-run +6 -0
  8. package/bin/machine-guard +6 -0
  9. package/bin/main-health +6 -0
  10. package/bin/main-start +6 -0
  11. package/bin/oc-run +6 -0
  12. package/bin/pm-fail-facts +6 -0
  13. package/bin/priority-watch +6 -0
  14. package/bin/queue-cancel +6 -0
  15. package/bin/queue-check +6 -0
  16. package/bin/queue-close +6 -0
  17. package/bin/resource-alerts +6 -0
  18. package/bin/tmp-snapshot +6 -0
  19. package/bin/wip-backup +6 -0
  20. package/bin/wip-check +6 -0
  21. package/bin/wip-snapshot +6 -0
  22. package/blockers/deny-command +4 -0
  23. package/blockers/deny-path +4 -0
  24. package/blockers/deny-text +4 -0
  25. package/blockers/no-push +4 -0
  26. package/package.json +55 -4
  27. package/src/adapters/omp/doctor.ts +186 -0
  28. package/src/adapters/omp/herdr-agent-state.ts +440 -0
  29. package/src/adapters/omp/index.ts +263 -0
  30. package/src/adapters/omp/install.ts +151 -0
  31. package/src/adapters/omp/sessions.ts +286 -0
  32. package/src/cli/commands.ts +88 -0
  33. package/src/cli/dispatch.ts +85 -0
  34. package/src/cli/guard.ts +110 -0
  35. package/src/cli/init.ts +191 -0
  36. package/src/cli/main.ts +9 -0
  37. package/src/cli/runtime.ts +13 -0
  38. package/src/core/adapter.ts +92 -0
  39. package/src/core/agent-ledger.ts +101 -0
  40. package/src/core/agent-score.ts +361 -0
  41. package/src/core/blockers.ts +208 -0
  42. package/src/core/brief-lint.ts +120 -0
  43. package/src/core/clock.ts +23 -0
  44. package/src/core/config.ts +291 -0
  45. package/src/core/glm-reset.ts +32 -0
  46. package/src/core/guards.ts +516 -0
  47. package/src/core/heavy-run.ts +50 -0
  48. package/src/core/help.ts +20 -0
  49. package/src/core/index.ts +2 -0
  50. package/src/core/machine-guard.ts +38 -0
  51. package/src/core/main-health.ts +31 -0
  52. package/src/core/main-start.ts +51 -0
  53. package/src/core/oc-run.ts +115 -0
  54. package/src/core/pm-fail-facts.ts +116 -0
  55. package/src/core/priority-watch.ts +23 -0
  56. package/src/core/proc.ts +35 -0
  57. package/src/core/pyfmt.ts +120 -0
  58. package/src/core/pyjson.ts +61 -0
  59. package/src/core/queue/env.ts +14 -0
  60. package/src/core/queue/lib.ts +563 -0
  61. package/src/core/queue-cancel.ts +90 -0
  62. package/src/core/queue-check.ts +45 -0
  63. package/src/core/queue-close.ts +250 -0
  64. package/src/core/resource-alerts.ts +23 -0
  65. package/src/core/tmp-snapshot.ts +23 -0
  66. package/src/core/wip-backup.ts +119 -0
  67. package/src/core/wip-check.ts +75 -0
  68. package/src/core/wip-snapshot.ts +142 -0
  69. package/templates/agents/apodex.md +6 -0
  70. package/templates/agents/bunny-user.md +13 -0
  71. package/templates/agents/bunny.md +213 -0
  72. package/templates/agents/composer.md +9 -0
  73. package/templates/agents/deepseek.md +214 -0
  74. package/templates/agents/gemini.md +213 -0
  75. package/templates/agents/glm.md +214 -0
  76. package/templates/agents/grok.md +213 -0
  77. package/templates/agents/haiku.md +213 -0
  78. package/templates/agents/hiko.md +213 -0
  79. package/templates/agents/ling.md +213 -0
  80. package/templates/agents/local-qwen.md +220 -0
  81. package/templates/agents/luna-medium.md +211 -0
  82. package/templates/agents/luna.md +224 -0
  83. package/templates/agents/muse.md +214 -0
  84. package/templates/agents/sol-xhigh.md +217 -0
  85. package/templates/agents/sol.md +217 -0
  86. package/templates/config.yml +155 -0
  87. package/templates/help/agent-ledger.txt +5 -0
  88. package/templates/help/agent-score.txt +16 -0
  89. package/templates/help/brief-lint.txt +5 -0
  90. package/templates/help/main-health.txt +3 -0
  91. package/templates/help/oc-run.txt +4 -0
  92. package/templates/help/pm-fail-facts.txt +6 -0
  93. package/templates/help/wip-backup.txt +12 -0
  94. package/templates/help/wip-check.txt +5 -0
  95. package/templates/help/wip-snapshot.txt +10 -0
  96. package/templates/machine-guard.sh +76 -0
  97. package/templates/notes/lessons.md +7 -0
  98. package/templates/notes/subagents.md +17 -0
  99. package/templates/queue/TEMPLATE.md +36 -0
  100. package/templates/resource-alerts.sh +31 -0
  101. package/templates/tmp-snapshot.sh +46 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 umiddey
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,37 @@
1
- # Temporary Holding Version
1
+ # neurastream
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Agent orchestration (queue, guards, blockers, scoring, ledger) as one installable package. The core does not name a harness. Adapters translate harness events into core calls. The first adapter is omp.
4
+
5
+ ## Install
6
+
7
+ Do not install into the real home while this package is still being built. Use a sandbox home.
8
+
9
+ ```sh
10
+ export HOME=/tmp/neurastream-home-dev
11
+ bun add -g neurastream
12
+ neurastream init
13
+ neurastream adapter install omp
14
+ ```
15
+
16
+ `init` writes `~/.config/neurastream/config.yml` from the defaults in `docs/config.md`, plus an empty index, notes, the queue template, blocker wrappers, and the state directories. A second run changes nothing. `adapter install omp` links the extension under that same home (`~/.omp/agent/extensions` and `omp plugin link`). Neither command touches `~/.omp` or `~/.local` of the real user.
17
+
18
+ Check the merged result:
19
+
20
+ ```sh
21
+ neurastream config --json
22
+ ```
23
+
24
+ Compatibility bins keep today's names (`agent-score`, `agent-ledger`, `brief-lint`, `oc-run`, `wip-check`, `wip-backup`, `wip-snapshot`, `pm-fail-facts`, `main-health`, `queue-check`, `queue-close`, `queue-cancel`, `main-start`, `machine-guard`, `glm-reset`, `priority-watch`, `resource-alerts`, `tmp-snapshot`, `heavy-run`). They are the same program, selected by the bin name.
25
+
26
+ Green is `bun run typecheck` and `bun test`, both exit 0.
27
+
28
+ ## Changing a script
29
+
30
+ Edit the script in this repo. Green is `bun run typecheck` and `bun test`, both exit 0. Main tags the release. The publish workflow puts that tag on npm. Then:
31
+
32
+ ```sh
33
+ bun add -g neurastream@latest
34
+ neurastream adapter install omp
35
+ ```
36
+
37
+ Live copies are never hand-edited. `neurastream doctor` names each hand-edited or foreign installed file, and a stale install (the installed version is not `npm view neurastream version`). If the registry cannot be reached, that check is a warning, not a failure.
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env bun
2
+ import { basename } from "node:path";
3
+ import { run } from "../src/cli/dispatch.ts";
4
+
5
+ const invoked = basename(process.argv[1] ?? "");
6
+ process.exit(await run(invoked, process.argv.slice(2)));
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env bun
2
+ import { basename } from "node:path";
3
+ import { run } from "../src/cli/dispatch.ts";
4
+
5
+ const invoked = basename(process.argv[1] ?? "");
6
+ process.exit(await run(invoked, process.argv.slice(2)));
package/bin/brief-lint ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env bun
2
+ import { basename } from "node:path";
3
+ import { run } from "../src/cli/dispatch.ts";
4
+
5
+ const invoked = basename(process.argv[1] ?? "");
6
+ process.exit(await run(invoked, process.argv.slice(2)));
package/bin/glm-reset ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env bun
2
+ import { basename } from "node:path";
3
+ import { run } from "../src/cli/dispatch.ts";
4
+
5
+ const invoked = basename(process.argv[1] ?? "");
6
+ process.exit(await run(invoked, process.argv.slice(2)));
package/bin/heavy-run ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env bun
2
+ import { basename } from "node:path";
3
+ import { run } from "../src/cli/dispatch.ts";
4
+
5
+ const invoked = basename(process.argv[1] ?? "");
6
+ process.exit(await run(invoked, process.argv.slice(2)));
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env bun
2
+ import { basename } from "node:path";
3
+ import { run } from "../src/cli/dispatch.ts";
4
+
5
+ const invoked = basename(process.argv[1] ?? "");
6
+ process.exit(await run(invoked, process.argv.slice(2)));
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env bun
2
+ import { basename } from "node:path";
3
+ import { run } from "../src/cli/dispatch.ts";
4
+
5
+ const invoked = basename(process.argv[1] ?? "");
6
+ process.exit(await run(invoked, process.argv.slice(2)));
package/bin/main-start ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env bun
2
+ import { basename } from "node:path";
3
+ import { run } from "../src/cli/dispatch.ts";
4
+
5
+ const invoked = basename(process.argv[1] ?? "");
6
+ process.exit(await run(invoked, process.argv.slice(2)));
package/bin/oc-run ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env bun
2
+ import { basename } from "node:path";
3
+ import { run } from "../src/cli/dispatch.ts";
4
+
5
+ const invoked = basename(process.argv[1] ?? "");
6
+ process.exit(await run(invoked, process.argv.slice(2)));
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env bun
2
+ import { basename } from "node:path";
3
+ import { run } from "../src/cli/dispatch.ts";
4
+
5
+ const invoked = basename(process.argv[1] ?? "");
6
+ process.exit(await run(invoked, process.argv.slice(2)));
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env bun
2
+ import { basename } from "node:path";
3
+ import { run } from "../src/cli/dispatch.ts";
4
+
5
+ const invoked = basename(process.argv[1] ?? "");
6
+ process.exit(await run(invoked, process.argv.slice(2)));
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env bun
2
+ import { basename } from "node:path";
3
+ import { run } from "../src/cli/dispatch.ts";
4
+
5
+ const invoked = basename(process.argv[1] ?? "");
6
+ process.exit(await run(invoked, process.argv.slice(2)));
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env bun
2
+ import { basename } from "node:path";
3
+ import { run } from "../src/cli/dispatch.ts";
4
+
5
+ const invoked = basename(process.argv[1] ?? "");
6
+ process.exit(await run(invoked, process.argv.slice(2)));
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env bun
2
+ import { basename } from "node:path";
3
+ import { run } from "../src/cli/dispatch.ts";
4
+
5
+ const invoked = basename(process.argv[1] ?? "");
6
+ process.exit(await run(invoked, process.argv.slice(2)));
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env bun
2
+ import { basename } from "node:path";
3
+ import { run } from "../src/cli/dispatch.ts";
4
+
5
+ const invoked = basename(process.argv[1] ?? "");
6
+ process.exit(await run(invoked, process.argv.slice(2)));
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env bun
2
+ import { basename } from "node:path";
3
+ import { run } from "../src/cli/dispatch.ts";
4
+
5
+ const invoked = basename(process.argv[1] ?? "");
6
+ process.exit(await run(invoked, process.argv.slice(2)));
package/bin/wip-backup ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env bun
2
+ import { basename } from "node:path";
3
+ import { run } from "../src/cli/dispatch.ts";
4
+
5
+ const invoked = basename(process.argv[1] ?? "");
6
+ process.exit(await run(invoked, process.argv.slice(2)));
package/bin/wip-check ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env bun
2
+ import { basename } from "node:path";
3
+ import { run } from "../src/cli/dispatch.ts";
4
+
5
+ const invoked = basename(process.argv[1] ?? "");
6
+ process.exit(await run(invoked, process.argv.slice(2)));
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env bun
2
+ import { basename } from "node:path";
3
+ import { run } from "../src/cli/dispatch.ts";
4
+
5
+ const invoked = basename(process.argv[1] ?? "");
6
+ process.exit(await run(invoked, process.argv.slice(2)));
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env bun
2
+ import { denyCommand, runBlockerScript } from "../src/core/blockers.ts";
3
+
4
+ runBlockerScript(denyCommand);
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env bun
2
+ import { denyPath, runBlockerScript } from "../src/core/blockers.ts";
3
+
4
+ runBlockerScript(denyPath);
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env bun
2
+ import { denyText, runBlockerScript } from "../src/core/blockers.ts";
3
+
4
+ runBlockerScript(denyText);
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env bun
2
+ import { noPush, runBlockerScript } from "../src/core/blockers.ts";
3
+
4
+ runBlockerScript(noPush);
package/package.json CHANGED
@@ -1,6 +1,57 @@
1
1
  {
2
2
  "name": "neurastream",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.1",
4
+ "description": "Harness-agnostic agent orchestration: queue, guards, blockers, scoring, ledger.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/umiddey/neurastream.git"
10
+ },
11
+ "publishConfig": {
12
+ "access": "public"
13
+ },
14
+ "files": [
15
+ "src",
16
+ "bin",
17
+ "templates",
18
+ "blockers",
19
+ "README.md",
20
+ "LICENSE"
21
+ ],
22
+ "bin": {
23
+ "neurastream": "src/cli/main.ts",
24
+ "agent-score": "bin/agent-score",
25
+ "agent-ledger": "bin/agent-ledger",
26
+ "brief-lint": "bin/brief-lint",
27
+ "oc-run": "bin/oc-run",
28
+ "wip-check": "bin/wip-check",
29
+ "wip-backup": "bin/wip-backup",
30
+ "wip-snapshot": "bin/wip-snapshot",
31
+ "pm-fail-facts": "bin/pm-fail-facts",
32
+ "main-health": "bin/main-health",
33
+ "queue-check": "bin/queue-check",
34
+ "queue-close": "bin/queue-close",
35
+ "queue-cancel": "bin/queue-cancel",
36
+ "main-start": "bin/main-start",
37
+ "machine-guard": "bin/machine-guard",
38
+ "glm-reset": "bin/glm-reset",
39
+ "priority-watch": "bin/priority-watch",
40
+ "resource-alerts": "bin/resource-alerts",
41
+ "tmp-snapshot": "bin/tmp-snapshot",
42
+ "heavy-run": "bin/heavy-run"
43
+ },
44
+ "scripts": {
45
+ "typecheck": "tsc --noEmit",
46
+ "test": "bun test"
47
+ },
48
+ "omp": {
49
+ "extensions": [
50
+ "./src/adapters/omp/index.ts"
51
+ ]
52
+ },
53
+ "devDependencies": {
54
+ "@types/bun": "^1.3.14",
55
+ "typescript": "^5.9.3"
56
+ }
57
+ }
@@ -0,0 +1,186 @@
1
+ // Reports hand-edited or foreign live files the omp adapter installs, and a stale
2
+ // registry version. Offline `npm view` is a warning, not a failure.
3
+ // Notes, INDEX, queue state and config are user state: not checked.
4
+
5
+ import { spawnSync } from "node:child_process";
6
+ import { existsSync, lstatSync, readdirSync, readFileSync, realpathSync } from "node:fs";
7
+ import { homedir } from "node:os";
8
+ import { dirname, join, relative, resolve } from "node:path";
9
+ import { fileURLToPath } from "node:url";
10
+
11
+ const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "../../..");
12
+
13
+ export interface LiveFile {
14
+ live: string;
15
+ source: string;
16
+ }
17
+
18
+ interface PackageJson {
19
+ name?: string;
20
+ version?: string;
21
+ bin?: Record<string, string>;
22
+ }
23
+
24
+ function readPackage(root: string): PackageJson {
25
+ return JSON.parse(readFileSync(join(root, "package.json"), "utf8")) as PackageJson;
26
+ }
27
+
28
+ export function packageBins(root: string): { name: string; source: string }[] {
29
+ const pkg = readPackage(root);
30
+ return Object.entries(pkg.bin ?? {}).map(([name, rel]) => ({ name, source: join(root, rel) }));
31
+ }
32
+
33
+ /** Files adapter install places under this home. Bun's global bin dir is extra, checked only when a name is present. */
34
+ export function adapterLiveFiles(home: string, root: string = PACKAGE_ROOT): LiveFile[] {
35
+ const files: LiveFile[] = [];
36
+ for (const bin of packageBins(root)) {
37
+ files.push({ live: join(home, ".local/bin", bin.name), source: bin.source });
38
+ }
39
+ files.push({
40
+ live: join(home, ".omp/agent/extensions/neurastream.ts"),
41
+ source: join(root, "src/adapters/omp/index.ts"),
42
+ });
43
+ files.push({
44
+ live: join(home, ".omp/agent/queue/lib.ts"),
45
+ source: join(root, "src/core/queue/lib.ts"),
46
+ });
47
+ files.push({
48
+ live: join(home, ".omp/agent/queue/TEMPLATE.md"),
49
+ source: join(root, "templates/queue/TEMPLATE.md"),
50
+ });
51
+ const agents = join(root, "templates/agents");
52
+ for (const name of readdirSync(agents).sort()) {
53
+ if (!name.endsWith(".md") || name.includes(".bak")) continue;
54
+ files.push({ live: join(home, ".omp/agent/agents", name), source: join(agents, name) });
55
+ }
56
+ return files;
57
+ }
58
+
59
+ function bunLiveFiles(home: string, root: string): LiveFile[] {
60
+ const dir = join(home, ".bun/bin");
61
+ if (!existsSync(dir)) return [];
62
+ const files: LiveFile[] = [];
63
+ for (const bin of packageBins(root)) {
64
+ const live = join(dir, bin.name);
65
+ if (existsSync(live)) files.push({ live, source: bin.source });
66
+ }
67
+ return files;
68
+ }
69
+
70
+ function packageRootOf(file: string): string | null {
71
+ let dir = dirname(file);
72
+ for (let i = 0; i < 8; i++) {
73
+ const manifest = join(dir, "package.json");
74
+ if (existsSync(manifest)) {
75
+ try {
76
+ const pkg = JSON.parse(readFileSync(manifest, "utf8")) as PackageJson;
77
+ if (pkg.name === readPackage(PACKAGE_ROOT).name) return dir;
78
+ } catch {
79
+ return null;
80
+ }
81
+ }
82
+ const parent = dirname(dir);
83
+ if (parent === dir) break;
84
+ dir = parent;
85
+ }
86
+ return null;
87
+ }
88
+
89
+ function sameBytes(a: string, b: string): boolean {
90
+ try {
91
+ return readFileSync(a).equals(readFileSync(b));
92
+ } catch {
93
+ return false;
94
+ }
95
+ }
96
+
97
+ /** A launcher is a symlink into an installed copy of this package, at the same relative path. A copy is byte-identical. */
98
+ export function isPackageFile(live: string, source: string): boolean {
99
+ let st;
100
+ try {
101
+ st = lstatSync(live);
102
+ } catch {
103
+ return false;
104
+ }
105
+ if (st.isSymbolicLink()) {
106
+ let target: string;
107
+ try {
108
+ target = realpathSync(live);
109
+ } catch {
110
+ return false;
111
+ }
112
+ const root = packageRootOf(target);
113
+ if (!root) return false;
114
+ const rel = relative(PACKAGE_ROOT, source);
115
+ return target === resolve(root, rel);
116
+ }
117
+ if (!st.isFile()) return false;
118
+ return sameBytes(live, source);
119
+ }
120
+
121
+ export interface DoctorReport {
122
+ ok: string[];
123
+ problems: string[];
124
+ warnings: string[];
125
+ installed: string;
126
+ registry: string | null;
127
+ }
128
+
129
+ function npmViewVersion(name: string, env: NodeJS.ProcessEnv): { version: string } | { warning: string } {
130
+ const r = spawnSync("npm", ["view", name, "version"], { encoding: "utf8", env, timeout: 8000 });
131
+ if (r.error || r.status !== 0) {
132
+ const why = r.error ? r.error.message : `exit ${r.status ?? "signal"}`;
133
+ return { warning: `offline: npm view ${name} version failed (${why})` };
134
+ }
135
+ const version = (r.stdout ?? "").trim().split("\n").filter(Boolean).at(-1) ?? "";
136
+ if (!version) return { warning: `offline: npm view ${name} version returned nothing` };
137
+ return { version };
138
+ }
139
+
140
+ export function doctorReport(home: string, env: NodeJS.ProcessEnv = process.env): DoctorReport {
141
+ const pkg = readPackage(PACKAGE_ROOT);
142
+ const name = pkg.name ?? "neurastream";
143
+ const installed = pkg.version ?? "";
144
+ const problems: string[] = [];
145
+ const ok: string[] = [];
146
+ const seen = new Set<string>();
147
+ for (const file of [...adapterLiveFiles(home), ...bunLiveFiles(home, PACKAGE_ROOT)]) {
148
+ if (seen.has(file.live)) continue;
149
+ seen.add(file.live);
150
+ if (!existsSync(file.live)) {
151
+ problems.push(`foreign: ${file.live}`);
152
+ continue;
153
+ }
154
+ if (isPackageFile(file.live, file.source)) {
155
+ ok.push(file.live);
156
+ continue;
157
+ }
158
+ problems.push(`hand-edited: ${file.live}`);
159
+ }
160
+ const warnings: string[] = [];
161
+ const viewed = npmViewVersion(name, env);
162
+ let registry: string | null = null;
163
+ if ("warning" in viewed) warnings.push(viewed.warning);
164
+ else {
165
+ registry = viewed.version;
166
+ if (registry !== installed) problems.push(`stale version: installed ${installed}, registry ${registry}`);
167
+ }
168
+ return { ok, problems, warnings, installed, registry };
169
+ }
170
+
171
+ export function formatDoctor(report: DoctorReport): string {
172
+ const lines: string[] = [];
173
+ if (report.problems.length === 0) lines.push("neurastream doctor: ok");
174
+ else lines.push(`neurastream doctor: ${report.problems.length} problem(s)`);
175
+ for (const p of report.problems) lines.push(p);
176
+ lines.push(`installed ${report.installed}`);
177
+ if (report.registry !== null) lines.push(`registry ${report.registry}`);
178
+ for (const w of report.warnings) lines.push(`warning: ${w}`);
179
+ return lines.join("\n");
180
+ }
181
+
182
+ export function runDoctor(): number {
183
+ const report = doctorReport(homedir());
184
+ console.log(formatDoctor(report));
185
+ return report.problems.length === 0 ? 0 : 1;
186
+ }