crewhaus 0.1.4 → 0.1.6

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.
@@ -1,7 +1,5 @@
1
- import { join } from "node:path";
2
1
  import type { JustificationJudge } from "@crewhaus/permission-engine";
3
2
  import type { JustificationAuditSink } from "@crewhaus/runtime-core";
4
-
5
3
  /**
6
4
  * FR-004 — Pillar 3 intent-gate wiring for `crewhaus run`, factored out of
7
5
  * the entry file `index.ts` (which runs a top-level argv switch and so cannot
@@ -26,40 +24,25 @@ import type { JustificationAuditSink } from "@crewhaus/runtime-core";
26
24
  * `--justification-judge` flag > spec `security.justification.judge` >
27
25
  * `"rule-based"`.
28
26
  */
29
-
30
27
  export type JudgeChoice = "rule-based" | "claude";
31
-
32
- const VALID_JUDGE_CHOICES: ReadonlyArray<JudgeChoice> = ["rule-based", "claude"];
33
-
34
28
  /** Thrown by `resolveJudgeChoice` on an unrecognised choice. The CLI entry
35
29
  * file catches it and routes the message through `die()`; tests assert on
36
30
  * `.choice` / `.message` without the process exiting. */
37
- export class InvalidJudgeChoiceError extends Error {
38
- override readonly name = "InvalidJudgeChoiceError";
39
- constructor(readonly choice: string) {
40
- super(`invalid --justification-judge "${choice}" — allowed: ${VALID_JUDGE_CHOICES.join(", ")}`);
41
- }
31
+ export declare class InvalidJudgeChoiceError extends Error {
32
+ readonly choice: string;
33
+ readonly name = "InvalidJudgeChoiceError";
34
+ constructor(choice: string);
42
35
  }
43
-
44
- function isJudgeChoice(s: string): s is JudgeChoice {
45
- return (VALID_JUDGE_CHOICES as ReadonlyArray<string>).includes(s);
46
- }
47
-
48
36
  /**
49
37
  * Pure resolution of the judge choice from the flag value + spec block.
50
38
  * `flagValue` is the raw `--justification-judge` value (or undefined when the
51
39
  * flag is absent). Throws `InvalidJudgeChoiceError` for any value outside the
52
40
  * allowed set so the caller can `die()` with a friendly message.
53
41
  */
54
- export function resolveJudgeChoice(
55
- flagValue: string | undefined,
56
- securityJustification: { judge?: JudgeChoice; model?: string } | undefined,
57
- ): JudgeChoice {
58
- const raw = flagValue ?? securityJustification?.judge ?? "rule-based";
59
- if (!isJudgeChoice(raw)) throw new InvalidJudgeChoiceError(raw);
60
- return raw;
61
- }
62
-
42
+ export declare function resolveJudgeChoice(flagValue: string | undefined, securityJustification: {
43
+ judge?: JudgeChoice;
44
+ model?: string;
45
+ } | undefined): JudgeChoice;
63
46
  /**
64
47
  * Build the `JustificationJudge` for a resolved choice. Returns `undefined`
65
48
  * for `"rule-based"` so the caller omits `justificationJudge` from
@@ -76,38 +59,21 @@ export function resolveJudgeChoice(
76
59
  * `createAnthropicAdapter()` + verbatim model string broke every
77
60
  * non-Anthropic judge with model-not-found.
78
61
  */
79
- export async function createJustificationJudge(
80
- choice: JudgeChoice,
81
- model: string | undefined,
82
- ): Promise<JustificationJudge | undefined> {
83
- if (choice === "rule-based") return undefined;
84
- const { resolveModel } = await import("@crewhaus/model-router");
85
- const { createClaudeJustificationJudge } = await import("@crewhaus/justification-judge-claude");
86
- const resolution = await resolveModel(model ?? "claude-haiku-4-5");
87
- return createClaudeJustificationJudge({
88
- adapter: resolution.adapter,
89
- model: resolution.modelId,
90
- });
91
- }
92
-
62
+ export declare function createJustificationJudge(choice: JudgeChoice, model: string | undefined): Promise<JustificationJudge | undefined>;
93
63
  /** Directory the run path roots the durable justification audit log at. */
94
- export function justificationAuditDir(cwd: string): string {
95
- return join(cwd, ".crewhaus", "audit");
96
- }
97
-
64
+ export declare function justificationAuditDir(cwd: string): string;
98
65
  export type OpenJustificationAuditSinkOptions = {
99
- /** Working directory; the sink is rooted at `<cwd>/.crewhaus/audit`. */
100
- readonly cwd: string;
101
- /**
102
- * When false, no durable sink is opened (the gate keeps only its ephemeral
103
- * trace-bus event). Defaults to true: `crewhaus run` writes the durable,
104
- * hash-chained `permission_justification_evaluated` record by default so
105
- * the intent gate's decisions are tamper-evident. The `--no-justification-audit`
106
- * flag flips this off for ephemeral/offline runs.
107
- */
108
- readonly enabled?: boolean;
66
+ /** Working directory; the sink is rooted at `<cwd>/.crewhaus/audit`. */
67
+ readonly cwd: string;
68
+ /**
69
+ * When false, no durable sink is opened (the gate keeps only its ephemeral
70
+ * trace-bus event). Defaults to true: `crewhaus run` writes the durable,
71
+ * hash-chained `permission_justification_evaluated` record by default so
72
+ * the intent gate's decisions are tamper-evident. The `--no-justification-audit`
73
+ * flag flips this off for ephemeral/offline runs.
74
+ */
75
+ readonly enabled?: boolean;
109
76
  };
110
-
111
77
  /**
112
78
  * Open the durable `JustificationAuditSink` the gate appends to. Returns
113
79
  * `undefined` when disabled (so the caller spreads nothing into
@@ -115,10 +81,4 @@ export type OpenJustificationAuditSinkOptions = {
115
81
  * real `@crewhaus/audit-log` `AuditLog`, which structurally satisfies the
116
82
  * `JustificationAuditSink` seam (its `append({ kind, payload })`).
117
83
  */
118
- export async function openJustificationAuditSink(
119
- opts: OpenJustificationAuditSinkOptions,
120
- ): Promise<JustificationAuditSink | undefined> {
121
- if (opts.enabled === false) return undefined;
122
- const { openAuditLog } = await import("@crewhaus/audit-log");
123
- return await openAuditLog({ rootDir: justificationAuditDir(opts.cwd) });
124
- }
84
+ export declare function openJustificationAuditSink(opts: OpenJustificationAuditSinkOptions): Promise<JustificationAuditSink | undefined>;
@@ -0,0 +1,72 @@
1
+ import { join } from "node:path";
2
+ const VALID_JUDGE_CHOICES = ["rule-based", "claude"];
3
+ /** Thrown by `resolveJudgeChoice` on an unrecognised choice. The CLI entry
4
+ * file catches it and routes the message through `die()`; tests assert on
5
+ * `.choice` / `.message` without the process exiting. */
6
+ export class InvalidJudgeChoiceError extends Error {
7
+ choice;
8
+ name = "InvalidJudgeChoiceError";
9
+ constructor(choice) {
10
+ super(`invalid --justification-judge "${choice}" — allowed: ${VALID_JUDGE_CHOICES.join(", ")}`);
11
+ this.choice = choice;
12
+ }
13
+ }
14
+ function isJudgeChoice(s) {
15
+ return VALID_JUDGE_CHOICES.includes(s);
16
+ }
17
+ /**
18
+ * Pure resolution of the judge choice from the flag value + spec block.
19
+ * `flagValue` is the raw `--justification-judge` value (or undefined when the
20
+ * flag is absent). Throws `InvalidJudgeChoiceError` for any value outside the
21
+ * allowed set so the caller can `die()` with a friendly message.
22
+ */
23
+ export function resolveJudgeChoice(flagValue, securityJustification) {
24
+ const raw = flagValue ?? securityJustification?.judge ?? "rule-based";
25
+ if (!isJudgeChoice(raw))
26
+ throw new InvalidJudgeChoiceError(raw);
27
+ return raw;
28
+ }
29
+ /**
30
+ * Build the `JustificationJudge` for a resolved choice. Returns `undefined`
31
+ * for `"rule-based"` so the caller omits `justificationJudge` from
32
+ * `runChatLoop` (runtime-core then uses `ruleBasedJustificationJudge`, the
33
+ * documented default for tests/offline runs). For `"claude"` it lazily
34
+ * imports the model-router + `@crewhaus/justification-judge-claude` so
35
+ * that model-backed code only loads when actually selected.
36
+ *
37
+ * The judge model is resolved through the model-router, so the spec's
38
+ * `security.justification.model` accepts the full router grammar
39
+ * (claude-*, openai/*, gemini/*, bedrock/*, local/<m>@<url>) — the judge
40
+ * package only needs a `ProviderAdapter`. The wire model is the
41
+ * resolution's *stripped* modelId; the previous hardcoded
42
+ * `createAnthropicAdapter()` + verbatim model string broke every
43
+ * non-Anthropic judge with model-not-found.
44
+ */
45
+ export async function createJustificationJudge(choice, model) {
46
+ if (choice === "rule-based")
47
+ return undefined;
48
+ const { resolveModel } = await import("@crewhaus/model-router");
49
+ const { createClaudeJustificationJudge } = await import("@crewhaus/justification-judge-claude");
50
+ const resolution = await resolveModel(model ?? "claude-haiku-4-5");
51
+ return createClaudeJustificationJudge({
52
+ adapter: resolution.adapter,
53
+ model: resolution.modelId,
54
+ });
55
+ }
56
+ /** Directory the run path roots the durable justification audit log at. */
57
+ export function justificationAuditDir(cwd) {
58
+ return join(cwd, ".crewhaus", "audit");
59
+ }
60
+ /**
61
+ * Open the durable `JustificationAuditSink` the gate appends to. Returns
62
+ * `undefined` when disabled (so the caller spreads nothing into
63
+ * `runChatLoop` and the durable path is a no-op). The returned object is a
64
+ * real `@crewhaus/audit-log` `AuditLog`, which structurally satisfies the
65
+ * `JustificationAuditSink` seam (its `append({ kind, payload })`).
66
+ */
67
+ export async function openJustificationAuditSink(opts) {
68
+ if (opts.enabled === false)
69
+ return undefined;
70
+ const { openAuditLog } = await import("@crewhaus/audit-log");
71
+ return await openAuditLog({ rootDir: justificationAuditDir(opts.cwd) });
72
+ }
@@ -1,6 +1,5 @@
1
- import { type ScopeFinding, auditToolScopes, isOutwardName } from "@crewhaus/tool-builder";
1
+ import { type ScopeFinding, auditToolScopes } from "@crewhaus/tool-builder";
2
2
  import type { RegisteredTool } from "@crewhaus/tool-catalog";
3
-
4
3
  /**
5
4
  * FR-002 — Pillar 3 sink-side build-time gate. The canonical per-tool
6
5
  * `auditToolScopes` (and its `ScopeFinding` type) now live in
@@ -12,7 +11,6 @@ import type { RegisteredTool } from "@crewhaus/tool-catalog";
12
11
  * unchanged.
13
12
  */
14
13
  export { type ScopeFinding, auditToolScopes };
15
-
16
14
  /**
17
15
  * FR-002 — the `compile --strict` audit over the *spec-level* tool names a
18
16
  * lowered IR references (the IR only carries names, never RegisteredTools).
@@ -40,26 +38,7 @@ export { type ScopeFinding, auditToolScopes };
40
38
  * `resolve` is injected so this is a pure function unit-testable without the
41
39
  * CLI importing the (heavy, side-effectful) built-in tool packages.
42
40
  */
43
- export function auditSpecToolNames(
44
- names: readonly string[],
45
- resolve: (name: string) => RegisteredTool | undefined,
46
- ): ScopeFinding[] {
47
- const findings: ScopeFinding[] = [];
48
- for (const name of names) {
49
- const tool = resolve(name);
50
- if (tool) {
51
- findings.push(...auditToolScopes([tool]));
52
- } else if (isOutwardName(name)) {
53
- findings.push({
54
- toolName: name,
55
- reason:
56
- 'is an outward-reaching sink by name but could not be resolved to a scope:"external" tool at compile time (dynamic/MCP sinks must be vetted, not assumed) — its egress scope is unverifiable offline',
57
- });
58
- }
59
- }
60
- return findings;
61
- }
62
-
41
+ export declare function auditSpecToolNames(names: readonly string[], resolve: (name: string) => RegisteredTool | undefined): ScopeFinding[];
63
42
  /**
64
43
  * FR-002 — collect every tool NAME referenced anywhere in a lowered IR,
65
44
  * variant-agnostically. The IR is a JSON-serializable discriminated union
@@ -68,22 +47,4 @@ export function auditSpecToolNames(
68
47
  * each variant's shape, walk the serialized object and gather every string
69
48
  * under a `tools` key. Deterministic and dedup'd.
70
49
  */
71
- export function collectToolNames(ir: unknown): string[] {
72
- const names = new Set<string>();
73
- const visit = (node: unknown): void => {
74
- if (Array.isArray(node)) {
75
- for (const item of node) visit(item);
76
- return;
77
- }
78
- if (node !== null && typeof node === "object") {
79
- for (const [key, value] of Object.entries(node as Record<string, unknown>)) {
80
- if (key === "tools" && Array.isArray(value)) {
81
- for (const v of value) if (typeof v === "string") names.add(v);
82
- }
83
- visit(value);
84
- }
85
- }
86
- };
87
- visit(ir);
88
- return [...names];
89
- }
50
+ export declare function collectToolNames(ir: unknown): string[];
@@ -0,0 +1,85 @@
1
+ import { auditToolScopes, isOutwardName } from "@crewhaus/tool-builder";
2
+ /**
3
+ * FR-002 — Pillar 3 sink-side build-time gate. The canonical per-tool
4
+ * `auditToolScopes` (and its `ScopeFinding` type) now live in
5
+ * `@crewhaus/tool-builder`, next to `isOutwardName` and `buildTool` — the two
6
+ * facts the gate keys on — so every consumer (this CLI's `compile --strict`
7
+ * and `doctor --philosophy-alignment`, plus the `compile()` library and the
8
+ * compiler-worker) audits identically rather than re-deriving the rule. They
9
+ * are re-exported here so the CLI's existing import sites and tests are
10
+ * unchanged.
11
+ */
12
+ export { auditToolScopes };
13
+ /**
14
+ * FR-002 — the `compile --strict` audit over the *spec-level* tool names a
15
+ * lowered IR references (the IR only carries names, never RegisteredTools).
16
+ *
17
+ * For each name we ask `resolve` (the offline built-in tool map) for the
18
+ * concrete RegisteredTool:
19
+ *
20
+ * - Resolved → run the same per-tool `auditToolScopes` check (capability or
21
+ * outward-name vs scope). This is how the six built-ins are verified.
22
+ * - Unresolved AND outward-by-name (`mcp__*` or a known outward built-in
23
+ * name not in the offline map) → FINDING. The name is statically known to
24
+ * be an external sink (the FR lists MCP calls among the definitionally-
25
+ * outward kinds, and `buildTool` infers `external` for `mcp__*`), but the
26
+ * compiler cannot prove offline that its scope is `"external"`. `--strict`
27
+ * refuses to emit a bundle that reaches an outward sink whose external
28
+ * scope it cannot verify — this is the criterion's "I/O-capable tool left
29
+ * at an unspecified scope" from the compiler's offline vantage. Without
30
+ * this, a spec referencing `mcp__evil__exfiltrate` slipped through.
31
+ * - Unresolved and NOT outward-by-name → skipped. A name the offline map
32
+ * doesn't know and whose name carries no outward signal is either a
33
+ * pure-compute custom tool registered in code or a typo; the offline gate
34
+ * has no capability fact to assert (the live `doctor --philosophy-
35
+ * alignment` audit, which sees real RegisteredTools, covers the registry).
36
+ *
37
+ * `resolve` is injected so this is a pure function unit-testable without the
38
+ * CLI importing the (heavy, side-effectful) built-in tool packages.
39
+ */
40
+ export function auditSpecToolNames(names, resolve) {
41
+ const findings = [];
42
+ for (const name of names) {
43
+ const tool = resolve(name);
44
+ if (tool) {
45
+ findings.push(...auditToolScopes([tool]));
46
+ }
47
+ else if (isOutwardName(name)) {
48
+ findings.push({
49
+ toolName: name,
50
+ reason: 'is an outward-reaching sink by name but could not be resolved to a scope:"external" tool at compile time (dynamic/MCP sinks must be vetted, not assumed) — its egress scope is unverifiable offline',
51
+ });
52
+ }
53
+ }
54
+ return findings;
55
+ }
56
+ /**
57
+ * FR-002 — collect every tool NAME referenced anywhere in a lowered IR,
58
+ * variant-agnostically. The IR is a JSON-serializable discriminated union
59
+ * (14 variants); some carry tools at the top level (`IrV0.tools`), others
60
+ * nest them under steps / nodes / stages / sub-agents. Rather than couple to
61
+ * each variant's shape, walk the serialized object and gather every string
62
+ * under a `tools` key. Deterministic and dedup'd.
63
+ */
64
+ export function collectToolNames(ir) {
65
+ const names = new Set();
66
+ const visit = (node) => {
67
+ if (Array.isArray(node)) {
68
+ for (const item of node)
69
+ visit(item);
70
+ return;
71
+ }
72
+ if (node !== null && typeof node === "object") {
73
+ for (const [key, value] of Object.entries(node)) {
74
+ if (key === "tools" && Array.isArray(value)) {
75
+ for (const v of value)
76
+ if (typeof v === "string")
77
+ names.add(v);
78
+ }
79
+ visit(value);
80
+ }
81
+ }
82
+ };
83
+ visit(ir);
84
+ return [...names];
85
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "crewhaus",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "type": "module",
5
5
  "description": "CrewHaus — the meta-harness compiler for AI agents. Compile one crewhaus.yaml spec into a CLI agent, channel bot, RAG pipeline, multi-agent crew, eval harness, voice or browser agent, and more.",
6
6
  "keywords": [
@@ -17,80 +17,83 @@
17
17
  "rag",
18
18
  "eval"
19
19
  ],
20
- "main": "src/index.ts",
20
+ "main": "dist/index.js",
21
21
  "bin": {
22
- "crewhaus": "src/index.ts"
22
+ "crewhaus": "dist/index.js"
23
23
  },
24
24
  "exports": {
25
- ".": "./src/index.ts"
25
+ ".": {
26
+ "types": "./dist/index.d.ts",
27
+ "import": "./dist/index.js"
28
+ }
26
29
  },
27
30
  "scripts": {
28
31
  "test": "bun test src"
29
32
  },
30
33
  "dependencies": {
31
- "@crewhaus/adapter-anthropic": "0.1.4",
32
- "@crewhaus/agent-context-isolation": "0.1.4",
33
- "@crewhaus/audit-log": "0.1.4",
34
- "@crewhaus/eval-optimizer-orchestrator": "0.1.4",
35
- "@crewhaus/prompt-optimizer": "0.1.4",
36
- "@crewhaus/prompt-optimizer-claude": "0.1.4",
37
- "@crewhaus/justification-judge-claude": "0.1.4",
38
- "@crewhaus/spec-patch": "0.1.4",
39
- "@crewhaus/canary-controller": "0.1.4",
40
- "@crewhaus/compiler": "0.1.4",
41
- "@crewhaus/computer-use-driver": "0.1.4",
42
- "@crewhaus/context-bundle": "0.1.4",
43
- "@crewhaus/deployment-controller": "0.1.4",
44
- "@crewhaus/egress-classifier": "0.1.4",
45
- "@crewhaus/egress-matcher-semantic": "0.1.4",
46
- "@crewhaus/embedder": "0.1.4",
47
- "@crewhaus/errors": "0.1.4",
48
- "@crewhaus/eval-dataset": "0.1.4",
49
- "@crewhaus/eval-grader": "0.1.4",
50
- "@crewhaus/eval-report": "0.1.4",
51
- "@crewhaus/eval-runner": "0.1.4",
52
- "@crewhaus/hooks-engine": "0.1.4",
53
- "@crewhaus/infra-utils": "0.1.4",
54
- "@crewhaus/ir": "0.1.4",
55
- "@crewhaus/logging": "0.1.4",
56
- "@crewhaus/mcp-host": "0.1.4",
57
- "@crewhaus/model-router": "0.1.4",
58
- "@crewhaus/migration-engine": "0.1.4",
59
- "@crewhaus/migration-runner": "0.1.4",
60
- "@crewhaus/permission-engine": "0.1.4",
61
- "@crewhaus/run-context": "0.1.4",
62
- "@crewhaus/runtime-core": "0.1.4",
63
- "@crewhaus/secrets-manager": "0.1.4",
64
- "@crewhaus/session-store": "0.1.4",
65
- "@crewhaus/spec-registry": "0.1.4",
66
- "@crewhaus/skills-registry": "0.1.4",
67
- "@crewhaus/slash-commands": "0.1.4",
68
- "@crewhaus/spec": "0.1.4",
69
- "@crewhaus/sub-agent-spawner": "0.1.4",
70
- "@crewhaus/trace-event-bus": "0.1.4",
71
- "@crewhaus/tool-bash": "0.1.4",
72
- "@crewhaus/tool-builder": "0.1.4",
73
- "@crewhaus/tool-catalog": "0.1.4",
74
- "@crewhaus/tool-codegraph": "0.1.4",
75
- "@crewhaus/tool-fetch": "0.1.4",
76
- "@crewhaus/tool-fs": "0.1.4",
77
- "@crewhaus/tool-image": "0.1.4",
78
- "@crewhaus/tool-image-generation": "0.1.4",
79
- "@crewhaus/tool-document-ingest": "0.1.4",
80
- "@crewhaus/tool-mcp": "0.1.4",
81
- "@crewhaus/tool-mouse-keyboard": "0.1.4",
82
- "@crewhaus/tool-navigate": "0.1.4",
83
- "@crewhaus/tool-screen-capture": "0.1.4",
84
- "@crewhaus/tool-task": "0.1.4",
85
- "@crewhaus/tool-todo": "0.1.4",
86
- "@crewhaus/tool-vision-grounding": "0.1.4",
87
- "@crewhaus/tool-web": "0.1.4",
88
- "@crewhaus/docker-images": "0.1.4",
89
- "@crewhaus/crewhaus-cloud": "0.1.4",
90
- "@crewhaus/federation-discovery": "0.1.4",
91
- "@crewhaus/sandbox": "0.1.4",
92
- "@crewhaus/sandbox-image-registry": "0.1.4",
93
- "@crewhaus/compliance-controls": "0.1.4"
34
+ "@crewhaus/adapter-anthropic": "0.1.6",
35
+ "@crewhaus/agent-context-isolation": "0.1.6",
36
+ "@crewhaus/audit-log": "0.1.6",
37
+ "@crewhaus/eval-optimizer-orchestrator": "0.1.6",
38
+ "@crewhaus/prompt-optimizer": "0.1.6",
39
+ "@crewhaus/prompt-optimizer-claude": "0.1.6",
40
+ "@crewhaus/justification-judge-claude": "0.1.6",
41
+ "@crewhaus/spec-patch": "0.1.6",
42
+ "@crewhaus/canary-controller": "0.1.6",
43
+ "@crewhaus/compiler": "0.1.6",
44
+ "@crewhaus/computer-use-driver": "0.1.6",
45
+ "@crewhaus/context-bundle": "0.1.6",
46
+ "@crewhaus/deployment-controller": "0.1.6",
47
+ "@crewhaus/egress-classifier": "0.1.6",
48
+ "@crewhaus/egress-matcher-semantic": "0.1.6",
49
+ "@crewhaus/embedder": "0.1.6",
50
+ "@crewhaus/errors": "0.1.6",
51
+ "@crewhaus/eval-dataset": "0.1.6",
52
+ "@crewhaus/eval-grader": "0.1.6",
53
+ "@crewhaus/eval-report": "0.1.6",
54
+ "@crewhaus/eval-runner": "0.1.6",
55
+ "@crewhaus/hooks-engine": "0.1.6",
56
+ "@crewhaus/infra-utils": "0.1.6",
57
+ "@crewhaus/ir": "0.1.6",
58
+ "@crewhaus/logging": "0.1.6",
59
+ "@crewhaus/mcp-host": "0.1.6",
60
+ "@crewhaus/model-router": "0.1.6",
61
+ "@crewhaus/migration-engine": "0.1.6",
62
+ "@crewhaus/migration-runner": "0.1.6",
63
+ "@crewhaus/permission-engine": "0.1.6",
64
+ "@crewhaus/run-context": "0.1.6",
65
+ "@crewhaus/runtime-core": "0.1.6",
66
+ "@crewhaus/secrets-manager": "0.1.6",
67
+ "@crewhaus/session-store": "0.1.6",
68
+ "@crewhaus/spec-registry": "0.1.6",
69
+ "@crewhaus/skills-registry": "0.1.6",
70
+ "@crewhaus/slash-commands": "0.1.6",
71
+ "@crewhaus/spec": "0.1.6",
72
+ "@crewhaus/sub-agent-spawner": "0.1.6",
73
+ "@crewhaus/trace-event-bus": "0.1.6",
74
+ "@crewhaus/tool-bash": "0.1.6",
75
+ "@crewhaus/tool-builder": "0.1.6",
76
+ "@crewhaus/tool-catalog": "0.1.6",
77
+ "@crewhaus/tool-codegraph": "0.1.6",
78
+ "@crewhaus/tool-fetch": "0.1.6",
79
+ "@crewhaus/tool-fs": "0.1.6",
80
+ "@crewhaus/tool-image": "0.1.6",
81
+ "@crewhaus/tool-image-generation": "0.1.6",
82
+ "@crewhaus/tool-document-ingest": "0.1.6",
83
+ "@crewhaus/tool-mcp": "0.1.6",
84
+ "@crewhaus/tool-mouse-keyboard": "0.1.6",
85
+ "@crewhaus/tool-navigate": "0.1.6",
86
+ "@crewhaus/tool-screen-capture": "0.1.6",
87
+ "@crewhaus/tool-task": "0.1.6",
88
+ "@crewhaus/tool-todo": "0.1.6",
89
+ "@crewhaus/tool-vision-grounding": "0.1.6",
90
+ "@crewhaus/tool-web": "0.1.6",
91
+ "@crewhaus/docker-images": "0.1.6",
92
+ "@crewhaus/crewhaus-cloud": "0.1.6",
93
+ "@crewhaus/federation-discovery": "0.1.6",
94
+ "@crewhaus/sandbox": "0.1.6",
95
+ "@crewhaus/sandbox-image-registry": "0.1.6",
96
+ "@crewhaus/compliance-controls": "0.1.6"
94
97
  },
95
98
  "devDependencies": {
96
99
  "zod": "^3.23.8"
@@ -113,5 +116,5 @@
113
116
  "publishConfig": {
114
117
  "access": "public"
115
118
  },
116
- "files": ["src", "README.md", "LICENSE", "NOTICE"]
119
+ "files": ["dist", "README.md", "LICENSE", "NOTICE"]
117
120
  }
@@ -1,122 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
- import { buildCredentialChecks, extractSpecModel, selectedProvider } from "./doctor-checks";
3
-
4
- const yamlFor = (model: string): string =>
5
- `name: t\ntarget: cli\nagent:\n model: ${model}\n instructions: hi\n`;
6
-
7
- describe("extractSpecModel", () => {
8
- test("extracts agent.model from a valid spec", () => {
9
- expect(extractSpecModel(yamlFor("openai/gpt-4o-mini"))).toBe("openai/gpt-4o-mini");
10
- });
11
-
12
- test("returns undefined for an unparseable spec (doctor must not crash)", () => {
13
- expect(extractSpecModel("name: t\ntarget: cli\n")).toBeUndefined();
14
- expect(extractSpecModel(":::not yaml at all")).toBeUndefined();
15
- });
16
- });
17
-
18
- describe("selectedProvider", () => {
19
- test("maps the full router grammar to doctor providers", () => {
20
- expect(selectedProvider("claude-sonnet-4-5")).toBe("anthropic");
21
- expect(selectedProvider("openai/gpt-4o-mini")).toBe("openai");
22
- expect(selectedProvider("gemini/gemini-2.5-flash")).toBe("gemini");
23
- expect(selectedProvider("bedrock/us.anthropic.claude-sonnet-4-5-20250929-v1:0")).toBe(
24
- "bedrock",
25
- );
26
- // local/<m>@<url> parses to openai WITH a baseUrl → credential-free.
27
- expect(selectedProvider("local/llama3.2@http://localhost:11434/v1")).toBe("local");
28
- });
29
-
30
- test("returns undefined for unrecognised model strings", () => {
31
- expect(selectedProvider("gpt-4o-mini")).toBeUndefined();
32
- });
33
- });
34
-
35
- describe("buildCredentialChecks — model-aware", () => {
36
- test("openai spec + OPENAI_API_KEY passes; missing Anthropic creds do NOT fail", () => {
37
- const checks = buildCredentialChecks("openai/gpt-4o-mini", { OPENAI_API_KEY: "sk-x" });
38
- const selected = checks[0];
39
- expect(selected?.label).toContain("OpenAI credentials");
40
- expect(selected?.pass).toBe(true);
41
- expect(checks.every((c) => c.pass)).toBe(true);
42
- expect(checks.some((c) => c.label.includes("Anthropic") && !c.pass)).toBe(false);
43
- });
44
-
45
- test("openai spec accepts OPENAI_BASE_URL as the credential surface", () => {
46
- const checks = buildCredentialChecks("openai/gpt-4o-mini", {
47
- OPENAI_BASE_URL: "http://vllm:8000/v1",
48
- });
49
- expect(checks[0]?.pass).toBe(true);
50
- });
51
-
52
- test("openai spec without provider env FAILS the selected check", () => {
53
- const checks = buildCredentialChecks("openai/gpt-4o-mini", {});
54
- expect(checks[0]?.pass).toBe(false);
55
- expect(checks[0]?.reason).toContain("OPENAI_API_KEY");
56
- });
57
-
58
- test("gemini spec: GEMINI_API_KEY, GOOGLE_API_KEY, or Vertex env all satisfy", () => {
59
- expect(buildCredentialChecks("gemini/gemini-2.5-flash", { GEMINI_API_KEY: "g" })[0]?.pass).toBe(
60
- true,
61
- );
62
- expect(buildCredentialChecks("gemini/gemini-2.5-flash", { GOOGLE_API_KEY: "g" })[0]?.pass).toBe(
63
- true,
64
- );
65
- expect(
66
- buildCredentialChecks("gemini/gemini-2.5-flash", {
67
- GOOGLE_GENAI_USE_VERTEXAI: "1",
68
- GOOGLE_CLOUD_PROJECT: "p",
69
- })[0]?.pass,
70
- ).toBe(true);
71
- const missing = buildCredentialChecks("gemini/gemini-2.5-flash", {});
72
- expect(missing[0]?.pass).toBe(false);
73
- });
74
-
75
- test("bedrock spec is INFORMATIONAL — never fails even with no AWS env (SDK chain is authoritative)", () => {
76
- const none = buildCredentialChecks("bedrock/us.anthropic.claude-sonnet-4-5-20250929-v1:0", {});
77
- expect(none[0]?.pass).toBe(true);
78
- expect(none[0]?.warn).toBe(true);
79
- expect(none[0]?.reason).toContain("SDK default credential chain");
80
- const some = buildCredentialChecks("bedrock/anthropic.claude-sonnet-4-5-20250929-v1:0", {
81
- AWS_PROFILE: "dev",
82
- });
83
- expect(some[0]?.pass).toBe(true);
84
- });
85
-
86
- test("local spec needs no credentials", () => {
87
- const checks = buildCredentialChecks("local/llama3.2@http://localhost:11434/v1", {});
88
- expect(checks[0]?.pass).toBe(true);
89
- expect(checks[0]?.label).toContain("Local endpoint");
90
- });
91
-
92
- test("non-selected providers with env set surface as informational warn lines, not failures", () => {
93
- const checks = buildCredentialChecks("openai/gpt-4o-mini", {
94
- OPENAI_API_KEY: "sk-x",
95
- ANTHROPIC_API_KEY: "sk-ant",
96
- });
97
- const anthropicLine = checks.find((c) => c.label.includes("Anthropic"));
98
- expect(anthropicLine?.pass).toBe(true);
99
- expect(anthropicLine?.warn).toBe(true);
100
- expect(anthropicLine?.label).toContain("not required");
101
- });
102
- });
103
-
104
- describe("buildCredentialChecks — no spec model (legacy fallback)", () => {
105
- test("Anthropic creds present → plain pass (unchanged behaviour)", () => {
106
- const checks = buildCredentialChecks(undefined, { ANTHROPIC_API_KEY: "sk-ant" });
107
- expect(checks).toEqual([{ label: "Anthropic credentials", pass: true }]);
108
- });
109
-
110
- test("no creds at all → hard fail (unchanged behaviour)", () => {
111
- const checks = buildCredentialChecks(undefined, {});
112
- expect(checks[0]?.pass).toBe(false);
113
- expect(checks[0]?.reason).toContain("ANTHROPIC_AUTH_TOKEN");
114
- });
115
-
116
- test("missing Anthropic creds downgrade to informational when another provider env is clearly set", () => {
117
- const checks = buildCredentialChecks(undefined, { OPENAI_API_KEY: "sk-x" });
118
- expect(checks[0]?.pass).toBe(true);
119
- expect(checks[0]?.warn).toBe(true);
120
- expect(checks[0]?.reason).toContain("OpenAI");
121
- });
122
- });