@namzu/cli 0.0.2

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 (58) hide show
  1. package/README.md +79 -0
  2. package/dist/bin.d.ts +3 -0
  3. package/dist/bin.d.ts.map +1 -0
  4. package/dist/bin.js +38 -0
  5. package/dist/bin.js.map +1 -0
  6. package/dist/commands/doctor.d.ts +2 -0
  7. package/dist/commands/doctor.d.ts.map +1 -0
  8. package/dist/commands/doctor.js +201 -0
  9. package/dist/commands/doctor.js.map +1 -0
  10. package/dist/commands/doctor.test.d.ts +2 -0
  11. package/dist/commands/doctor.test.d.ts.map +1 -0
  12. package/dist/commands/doctor.test.js +82 -0
  13. package/dist/commands/doctor.test.js.map +1 -0
  14. package/dist/doctor/checks/index.d.ts +9 -0
  15. package/dist/doctor/checks/index.d.ts.map +1 -0
  16. package/dist/doctor/checks/index.js +15 -0
  17. package/dist/doctor/checks/index.js.map +1 -0
  18. package/dist/doctor/checks/providers.d.ts +19 -0
  19. package/dist/doctor/checks/providers.d.ts.map +1 -0
  20. package/dist/doctor/checks/providers.js +25 -0
  21. package/dist/doctor/checks/providers.js.map +1 -0
  22. package/dist/doctor/checks/runtime.d.ts +4 -0
  23. package/dist/doctor/checks/runtime.d.ts.map +1 -0
  24. package/dist/doctor/checks/runtime.js +40 -0
  25. package/dist/doctor/checks/runtime.js.map +1 -0
  26. package/dist/doctor/checks/sandbox.d.ts +3 -0
  27. package/dist/doctor/checks/sandbox.d.ts.map +1 -0
  28. package/dist/doctor/checks/sandbox.js +42 -0
  29. package/dist/doctor/checks/sandbox.js.map +1 -0
  30. package/dist/doctor/checks/telemetry.d.ts +12 -0
  31. package/dist/doctor/checks/telemetry.d.ts.map +1 -0
  32. package/dist/doctor/checks/telemetry.js +27 -0
  33. package/dist/doctor/checks/telemetry.js.map +1 -0
  34. package/dist/doctor/checks/vault.d.ts +13 -0
  35. package/dist/doctor/checks/vault.d.ts.map +1 -0
  36. package/dist/doctor/checks/vault.js +19 -0
  37. package/dist/doctor/checks/vault.js.map +1 -0
  38. package/dist/doctor/index.d.ts +4 -0
  39. package/dist/doctor/index.d.ts.map +1 -0
  40. package/dist/doctor/index.js +3 -0
  41. package/dist/doctor/index.js.map +1 -0
  42. package/dist/doctor/registry.d.ts +55 -0
  43. package/dist/doctor/registry.d.ts.map +1 -0
  44. package/dist/doctor/registry.js +197 -0
  45. package/dist/doctor/registry.js.map +1 -0
  46. package/dist/doctor/registry.test.d.ts +7 -0
  47. package/dist/doctor/registry.test.d.ts.map +1 -0
  48. package/dist/doctor/registry.test.js +376 -0
  49. package/dist/doctor/registry.test.js.map +1 -0
  50. package/dist/exit-codes.d.ts +16 -0
  51. package/dist/exit-codes.d.ts.map +1 -0
  52. package/dist/exit-codes.js +15 -0
  53. package/dist/exit-codes.js.map +1 -0
  54. package/dist/index.d.ts +10 -0
  55. package/dist/index.d.ts.map +1 -0
  56. package/dist/index.js +9 -0
  57. package/dist/index.js.map +1 -0
  58. package/package.json +58 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sandbox.js","sourceRoot":"","sources":["../../../src/doctor/checks/sandbox.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AAIpD,MAAM,mBAAmB,GAAG,uBAAuB,CAAA;AAEnD,MAAM,CAAC,MAAM,oBAAoB,GAAgB;IAChD,EAAE,EAAE,kBAAkB;IACtB,QAAQ,EAAE,SAAS;IACnB,GAAG,EAAE,KAAK,IAAgC,EAAE;QAC3C,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAA;QACjC,QAAQ,QAAQ,EAAE,CAAC;YAClB,KAAK,QAAQ;gBACZ,IAAI,CAAC;oBACJ,MAAM,MAAM,CAAC,mBAAmB,EAAE,SAAS,CAAC,IAAI,CAAC,CAAA;oBACjD,OAAO;wBACN,MAAM,EAAE,MAAM;wBACd,OAAO,EAAE,YAAY,mBAAmB,yBAAyB;qBACjE,CAAA;gBACF,CAAC;gBAAC,MAAM,CAAC;oBACR,OAAO;wBACN,MAAM,EAAE,MAAM;wBACd,OAAO,EAAE,YAAY,mBAAmB,iBAAiB;wBACzD,WAAW,EAAE,iEAAiE;qBAC9E,CAAA;gBACF,CAAC;YACF,KAAK,OAAO;gBACX,OAAO;oBACN,MAAM,EAAE,cAAc;oBACtB,OAAO,EACN,6IAA6I;iBAC9I,CAAA;YACF,KAAK,OAAO;gBACX,OAAO;oBACN,MAAM,EAAE,MAAM;oBACd,OAAO,EAAE,sDAAsD;iBAC/D,CAAA;YACF;gBACC,OAAO;oBACN,MAAM,EAAE,cAAc;oBACtB,OAAO,EAAE,0BAA0B,QAAQ,GAAG;iBAC9C,CAAA;QACH,CAAC;IACF,CAAC;CACD,CAAA"}
@@ -0,0 +1,12 @@
1
+ import type { DoctorCheck } from '@namzu/sdk';
2
+ /**
3
+ * Telemetry presence probe.
4
+ *
5
+ * Per ses_004 D1=B, `@namzu/telemetry` is a separate optional package.
6
+ * This check reports whether it is installed; absence is informational
7
+ * (not a failure). Endpoint reachability + dry-run span export are
8
+ * deferred to a follow-up check that the consumer can register
9
+ * themselves once their telemetry config is known.
10
+ */
11
+ export declare const telemetryInstalledCheck: DoctorCheck;
12
+ //# sourceMappingURL=telemetry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telemetry.d.ts","sourceRoot":"","sources":["../../../src/doctor/checks/telemetry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAqB,MAAM,YAAY,CAAA;AAEhE;;;;;;;;GAQG;AACH,eAAO,MAAM,uBAAuB,EAAE,WAerC,CAAA"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Telemetry presence probe.
3
+ *
4
+ * Per ses_004 D1=B, `@namzu/telemetry` is a separate optional package.
5
+ * This check reports whether it is installed; absence is informational
6
+ * (not a failure). Endpoint reachability + dry-run span export are
7
+ * deferred to a follow-up check that the consumer can register
8
+ * themselves once their telemetry config is known.
9
+ */
10
+ export const telemetryInstalledCheck = {
11
+ id: 'telemetry.installed',
12
+ category: 'telemetry',
13
+ run: async () => {
14
+ const specifier = '@namzu/telemetry';
15
+ try {
16
+ await import(specifier);
17
+ return { status: 'pass', message: '@namzu/telemetry is installed' };
18
+ }
19
+ catch {
20
+ return {
21
+ status: 'inconclusive',
22
+ message: '@namzu/telemetry not installed (optional package)',
23
+ };
24
+ }
25
+ },
26
+ };
27
+ //# sourceMappingURL=telemetry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telemetry.js","sourceRoot":"","sources":["../../../src/doctor/checks/telemetry.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAgB;IACnD,EAAE,EAAE,qBAAqB;IACzB,QAAQ,EAAE,WAAW;IACrB,GAAG,EAAE,KAAK,IAAgC,EAAE;QAC3C,MAAM,SAAS,GAAG,kBAAkB,CAAA;QACpC,IAAI,CAAC;YACJ,MAAM,MAAM,CAAC,SAAS,CAAC,CAAA;YACvB,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,+BAA+B,EAAE,CAAA;QACpE,CAAC;QAAC,MAAM,CAAC;YACR,OAAO;gBACN,MAAM,EAAE,cAAc;gBACtB,OAAO,EAAE,mDAAmD;aAC5D,CAAA;QACF,CAAC;IACF,CAAC;CACD,CAAA"}
@@ -0,0 +1,13 @@
1
+ import type { DoctorCheck } from '@namzu/sdk';
2
+ /**
3
+ * Built-in vault probe is intentionally inconclusive.
4
+ *
5
+ * `CredentialVault` is an interface, not a globally-discoverable
6
+ * registry — consumers instantiate their own (`InMemoryCredentialVault`,
7
+ * file-backed, env-backed, KMS-backed, …). The doctor cannot enumerate
8
+ * vaults it doesn't know about. To get a real vault healthcheck, the
9
+ * consumer registers a custom check via `registerDoctorCheck` that
10
+ * exercises their specific vault wiring.
11
+ */
12
+ export declare const vaultRegisteredCheck: DoctorCheck;
13
+ //# sourceMappingURL=vault.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vault.d.ts","sourceRoot":"","sources":["../../../src/doctor/checks/vault.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAqB,MAAM,YAAY,CAAA;AAEhE;;;;;;;;;GASG;AACH,eAAO,MAAM,oBAAoB,EAAE,WAQlC,CAAA"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Built-in vault probe is intentionally inconclusive.
3
+ *
4
+ * `CredentialVault` is an interface, not a globally-discoverable
5
+ * registry — consumers instantiate their own (`InMemoryCredentialVault`,
6
+ * file-backed, env-backed, KMS-backed, …). The doctor cannot enumerate
7
+ * vaults it doesn't know about. To get a real vault healthcheck, the
8
+ * consumer registers a custom check via `registerDoctorCheck` that
9
+ * exercises their specific vault wiring.
10
+ */
11
+ export const vaultRegisteredCheck = {
12
+ id: 'vault.registered',
13
+ category: 'vault',
14
+ run: async () => ({
15
+ status: 'inconclusive',
16
+ message: 'no vault auto-discovery in v1; register a vault check via registerDoctorCheck for your specific vault setup',
17
+ }),
18
+ };
19
+ //# sourceMappingURL=vault.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vault.js","sourceRoot":"","sources":["../../../src/doctor/checks/vault.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAgB;IAChD,EAAE,EAAE,kBAAkB;IACtB,QAAQ,EAAE,OAAO;IACjB,GAAG,EAAE,KAAK,IAAgC,EAAE,CAAC,CAAC;QAC7C,MAAM,EAAE,cAAc;QACtB,OAAO,EACN,6GAA6G;KAC9G,CAAC;CACF,CAAA"}
@@ -0,0 +1,4 @@
1
+ export { createDoctorRegistry, doctor, DoctorRegistry, registerDoctorCheck, runDoctor, } from './registry.js';
2
+ export type { RunDoctorOptions } from './registry.js';
3
+ export { builtInDoctorChecks, cwdWritableCheck, providersRegisteredCheck, sandboxPlatformCheck, telemetryInstalledCheck, tmpdirWritableCheck, vaultRegisteredCheck, } from './checks/index.js';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/doctor/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,oBAAoB,EACpB,MAAM,EACN,cAAc,EACd,mBAAmB,EACnB,SAAS,GACT,MAAM,eAAe,CAAA;AACtB,YAAY,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAErD,OAAO,EACN,mBAAmB,EACnB,gBAAgB,EAChB,wBAAwB,EACxB,oBAAoB,EACpB,uBAAuB,EACvB,mBAAmB,EACnB,oBAAoB,GACpB,MAAM,mBAAmB,CAAA"}
@@ -0,0 +1,3 @@
1
+ export { createDoctorRegistry, doctor, DoctorRegistry, registerDoctorCheck, runDoctor, } from './registry.js';
2
+ export { builtInDoctorChecks, cwdWritableCheck, providersRegisteredCheck, sandboxPlatformCheck, telemetryInstalledCheck, tmpdirWritableCheck, vaultRegisteredCheck, } from './checks/index.js';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/doctor/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,oBAAoB,EACpB,MAAM,EACN,cAAc,EACd,mBAAmB,EACnB,SAAS,GACT,MAAM,eAAe,CAAA;AAGtB,OAAO,EACN,mBAAmB,EACnB,gBAAgB,EAChB,wBAAwB,EACxB,oBAAoB,EACpB,uBAAuB,EACvB,mBAAmB,EACnB,oBAAoB,GACpB,MAAM,mBAAmB,CAAA"}
@@ -0,0 +1,55 @@
1
+ import type { DoctorCheck, DoctorCheckRecord, DoctorReport, Logger } from '@namzu/sdk';
2
+ export interface RunDoctorOptions {
3
+ readonly categories?: readonly DoctorCheck['category'][];
4
+ readonly perCheckTimeoutMs?: number;
5
+ readonly wallClockTimeoutMs?: number;
6
+ readonly cwd?: string;
7
+ readonly env?: Readonly<Record<string, string | undefined>>;
8
+ readonly projectRoot?: string | null;
9
+ readonly version?: string;
10
+ readonly registry?: DoctorRegistry;
11
+ /**
12
+ * Fired immediately before a check's `run()` is invoked. The TUI uses
13
+ * this to mark the row as 'running'. Throws are caught + logged + do
14
+ * not affect the doctor run.
15
+ */
16
+ readonly onCheckStart?: (check: DoctorCheck) => void;
17
+ /**
18
+ * Fired exactly once per check, after its record is built (whether
19
+ * pass / fail / inconclusive / warn — including timeout-induced
20
+ * inconclusive). Throws are caught + logged + do not affect the
21
+ * doctor run. Defended against double-fire by the same `completed`
22
+ * map that pins the record (ses_013 C4).
23
+ */
24
+ readonly onCheckComplete?: (record: DoctorCheckRecord) => void;
25
+ /**
26
+ * Cooperative cancellation. When the signal aborts, in-flight checks
27
+ * stop being awaited; their records become `inconclusive` with an
28
+ * "aborted" message. Completed records are preserved. Caller
29
+ * (typically the CLI bin) decides the exit code (sysexits 130 for
30
+ * SIGINT). Library callers can synthesize their own AbortController.
31
+ */
32
+ readonly signal?: AbortSignal;
33
+ }
34
+ export declare class DoctorRegistry {
35
+ private readonly checks;
36
+ private log?;
37
+ setLogger(log: Logger): void;
38
+ register(check: DoctorCheck): void;
39
+ unregister(id: string): boolean;
40
+ clear(): void;
41
+ list(): readonly DoctorCheck[];
42
+ run(opts?: Omit<RunDoctorOptions, 'registry'>): Promise<DoctorReport>;
43
+ /**
44
+ * Invoke a consumer callback (onCheckStart / onCheckComplete) with
45
+ * try/catch isolation. Per ses_013 C3: a throwing callback must not
46
+ * affect the doctor run or the final DoctorReport. Logged but
47
+ * swallowed.
48
+ */
49
+ private invokeCallback;
50
+ }
51
+ export declare const doctor: DoctorRegistry;
52
+ export declare function createDoctorRegistry(): DoctorRegistry;
53
+ export declare function registerDoctorCheck(check: DoctorCheck): void;
54
+ export declare function runDoctor(opts?: RunDoctorOptions): Promise<DoctorReport>;
55
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/doctor/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,WAAW,EAEX,iBAAiB,EAEjB,YAAY,EAEZ,MAAM,EACN,MAAM,YAAY,CAAA;AAKnB,MAAM,WAAW,gBAAgB;IAChC,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,WAAW,CAAC,UAAU,CAAC,EAAE,CAAA;IACxD,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAA;IACnC,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAA;IACpC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,GAAG,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAA;IAC3D,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACpC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,QAAQ,CAAC,EAAE,cAAc,CAAA;IAClC;;;;OAIG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAA;IACpD;;;;;;OAMG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC,MAAM,EAAE,iBAAiB,KAAK,IAAI,CAAA;IAC9D;;;;;;OAMG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAC7B;AAED,qBAAa,cAAc;IAC1B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAsC;IAC7D,OAAO,CAAC,GAAG,CAAC,CAAQ;IAEpB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAI5B,QAAQ,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI;IASlC,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAI/B,KAAK,IAAI,IAAI;IAIb,IAAI,IAAI,SAAS,WAAW,EAAE;IAIxB,GAAG,CAAC,IAAI,GAAE,IAAI,CAAC,gBAAgB,EAAE,UAAU,CAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAuH/E;;;;;OAKG;IACH,OAAO,CAAC,cAAc;CAQtB;AAED,eAAO,MAAM,MAAM,EAAE,cAAqC,CAAA;AAE1D,wBAAgB,oBAAoB,IAAI,cAAc,CAErD;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI,CAE5D;AAED,wBAAgB,SAAS,CAAC,IAAI,GAAE,gBAAqB,GAAG,OAAO,CAAC,YAAY,CAAC,CAc5E"}
@@ -0,0 +1,197 @@
1
+ const DEFAULT_PER_CHECK_TIMEOUT_MS = 5_000;
2
+ const DEFAULT_WALL_CLOCK_TIMEOUT_MS = 10_000;
3
+ export class DoctorRegistry {
4
+ checks = new Map();
5
+ log;
6
+ setLogger(log) {
7
+ this.log = log.child({ component: 'DoctorRegistry' });
8
+ }
9
+ register(check) {
10
+ if (this.checks.has(check.id)) {
11
+ throw new Error(`Doctor check with id "${check.id}" is already registered. Pick a different id or call clear() first.`);
12
+ }
13
+ this.checks.set(check.id, check);
14
+ }
15
+ unregister(id) {
16
+ return this.checks.delete(id);
17
+ }
18
+ clear() {
19
+ this.checks.clear();
20
+ }
21
+ list() {
22
+ return Array.from(this.checks.values());
23
+ }
24
+ async run(opts = {}) {
25
+ const ctx = buildContext(opts);
26
+ const perCheck = opts.perCheckTimeoutMs ?? DEFAULT_PER_CHECK_TIMEOUT_MS;
27
+ const wall = opts.wallClockTimeoutMs ?? DEFAULT_WALL_CLOCK_TIMEOUT_MS;
28
+ const signal = opts.signal;
29
+ const filteredChecks = opts.categories
30
+ ? this.list().filter((c) => opts.categories?.includes(c.category))
31
+ : this.list();
32
+ const startedAt = Date.now();
33
+ const completed = new Map();
34
+ const wallTimer = sleep(wall).then(() => 'wall-timeout');
35
+ // Fast-fail if already aborted before any check runs.
36
+ const abortPromise = signal
37
+ ? signal.aborted
38
+ ? Promise.resolve('aborted')
39
+ : new Promise((resolve) => {
40
+ signal.addEventListener('abort', () => resolve('aborted'), { once: true });
41
+ })
42
+ : new Promise(() => { }); // never resolves
43
+ const recordCompletion = (record) => {
44
+ // Defend against double-fire: per-check timeout race vs the check
45
+ // resolving, or signal-abort racing the same id. First record wins.
46
+ if (completed.has(record.id))
47
+ return;
48
+ completed.set(record.id, record);
49
+ // Fire onCheckComplete exactly once per id, isolated from the run.
50
+ this.invokeCallback('onCheckComplete', () => opts.onCheckComplete?.(record));
51
+ };
52
+ const inFlight = filteredChecks.map(async (check) => {
53
+ // onCheckStart fires before the check's run() is invoked. Isolated.
54
+ this.invokeCallback('onCheckStart', () => opts.onCheckStart?.(check));
55
+ const t0 = Date.now();
56
+ const checkPromise = (async () => check.run(ctx))();
57
+ const timeoutPromise = sleep(perCheck).then(() => ({
58
+ status: 'inconclusive',
59
+ message: `check did not return within ${perCheck}ms`,
60
+ }));
61
+ let result;
62
+ try {
63
+ result = await Promise.race([checkPromise, timeoutPromise]);
64
+ }
65
+ catch (error) {
66
+ const message = error instanceof Error ? error.message : String(error);
67
+ this.log?.warn('doctor check threw', { id: check.id, message });
68
+ result = { status: 'fail', message: `check threw: ${message}` };
69
+ }
70
+ recordCompletion({
71
+ id: check.id,
72
+ category: check.category,
73
+ status: result.status,
74
+ message: result.message,
75
+ remediation: result.remediation,
76
+ durationMs: result.durationMs ?? Date.now() - t0,
77
+ });
78
+ });
79
+ const allChecks = Promise.all(inFlight);
80
+ const raceWinner = await Promise.race([allChecks, wallTimer, abortPromise]);
81
+ let records;
82
+ if (raceWinner === 'wall-timeout' || raceWinner === 'aborted') {
83
+ const reason = raceWinner === 'aborted'
84
+ ? 'aborted by signal before this check completed'
85
+ : `wall-clock timeout (${wall}ms) reached before this check completed`;
86
+ const unfinished = filteredChecks.filter((c) => !completed.has(c.id));
87
+ this.log?.warn(raceWinner === 'aborted' ? 'doctor aborted by signal' : 'doctor wall-clock timeout', {
88
+ wall,
89
+ total: filteredChecks.length,
90
+ unfinished: unfinished.length,
91
+ });
92
+ records = filteredChecks.map((check) => {
93
+ const existing = completed.get(check.id);
94
+ if (existing)
95
+ return existing;
96
+ const record = {
97
+ id: check.id,
98
+ category: check.category,
99
+ status: 'inconclusive',
100
+ message: reason,
101
+ durationMs: Date.now() - startedAt,
102
+ };
103
+ // Fire onCheckComplete for unfinished checks too — TUI rows
104
+ // flip from 'running' to 'inconclusive' instead of staying
105
+ // stuck. recordCompletion handles double-fire defense if the
106
+ // inFlight promise eventually resolves.
107
+ recordCompletion(record);
108
+ return record;
109
+ });
110
+ }
111
+ else {
112
+ // All checks finished within wall-clock budget. Read from `completed`
113
+ // in registration order so the report layout is deterministic.
114
+ records = filteredChecks.map((check) => {
115
+ const existing = completed.get(check.id);
116
+ if (existing)
117
+ return existing;
118
+ // Belt + suspenders: if an inFlight callback somehow finished
119
+ // without populating `completed` (shouldn't happen), surface as
120
+ // inconclusive rather than throwing.
121
+ return {
122
+ id: check.id,
123
+ category: check.category,
124
+ status: 'inconclusive',
125
+ message: 'check resolved without producing a record (internal)',
126
+ durationMs: Date.now() - startedAt,
127
+ };
128
+ });
129
+ }
130
+ return buildReport(records, opts.version ?? 'unknown');
131
+ }
132
+ /**
133
+ * Invoke a consumer callback (onCheckStart / onCheckComplete) with
134
+ * try/catch isolation. Per ses_013 C3: a throwing callback must not
135
+ * affect the doctor run or the final DoctorReport. Logged but
136
+ * swallowed.
137
+ */
138
+ invokeCallback(phase, fn) {
139
+ try {
140
+ fn();
141
+ }
142
+ catch (error) {
143
+ const message = error instanceof Error ? error.message : String(error);
144
+ this.log?.warn('doctor callback threw', { phase, message });
145
+ }
146
+ }
147
+ }
148
+ export const doctor = new DoctorRegistry();
149
+ export function createDoctorRegistry() {
150
+ return new DoctorRegistry();
151
+ }
152
+ export function registerDoctorCheck(check) {
153
+ doctor.register(check);
154
+ }
155
+ export function runDoctor(opts = {}) {
156
+ const registry = opts.registry ?? doctor;
157
+ return registry.run({
158
+ categories: opts.categories,
159
+ perCheckTimeoutMs: opts.perCheckTimeoutMs,
160
+ wallClockTimeoutMs: opts.wallClockTimeoutMs,
161
+ cwd: opts.cwd,
162
+ env: opts.env,
163
+ projectRoot: opts.projectRoot,
164
+ version: opts.version,
165
+ onCheckStart: opts.onCheckStart,
166
+ onCheckComplete: opts.onCheckComplete,
167
+ signal: opts.signal,
168
+ });
169
+ }
170
+ function buildContext(opts) {
171
+ return Object.freeze({
172
+ cwd: opts.cwd ?? process.cwd(),
173
+ env: opts.env ?? process.env,
174
+ projectRoot: opts.projectRoot ?? null,
175
+ });
176
+ }
177
+ function buildReport(records, version) {
178
+ const summary = {
179
+ pass: records.filter((r) => r.status === 'pass').length,
180
+ fail: records.filter((r) => r.status === 'fail').length,
181
+ inconclusive: records.filter((r) => r.status === 'inconclusive').length,
182
+ warn: records.filter((r) => r.status === 'warn').length,
183
+ total: records.length,
184
+ };
185
+ const exit = summary.fail > 0 ? 1 : summary.total === 0 ? 2 : 0;
186
+ return Object.freeze({
187
+ version,
188
+ timestamp: new Date().toISOString(),
189
+ checks: records,
190
+ summary,
191
+ exit,
192
+ });
193
+ }
194
+ function sleep(ms) {
195
+ return new Promise((resolve) => setTimeout(() => resolve('sleep-done'), ms));
196
+ }
197
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/doctor/registry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Ratified §9 of docs.local/sessions/ses_007-probe-and-doctor/design.md.
3
+ * D4 = registerDoctorCheck + plugin auto-discovery + standalone-CLI /
4
+ * embedded-runDoctor split. D5 = sysexits exit codes (0/1/2/70).
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=registry.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.test.d.ts","sourceRoot":"","sources":["../../src/doctor/registry.test.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}