programmatic subagents
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(cli): config-first authoring help, remove discovery command, and refresh docs

+184 -246
+19 -16
README.md
··· 45 45 mill run <program.ts> [--sync] [--json] [--driver <name>] 46 46 mill status <runId> show run state 47 47 mill wait <runId> --timeout block until complete/failed/cancelled 48 - mill watch <runId> stream tier-1 events (NDJSON with --json) 48 + mill watch [--run <runId>] stream tier-1 events (global if --run omitted) 49 49 mill inspect <id>[.<spawnId>] inspect run or spawn detail 50 50 mill inspect <id> --session resolve full agent session via driver 51 51 mill cancel <runId> mark cancelled + kill worker process tree 52 52 mill ls [--status <filter>] list runs 53 - mill init generate starter mill.config.ts 53 + mill init [--global] generate starter config (local or ~/.mill/config.ts) 54 54 ``` 55 55 56 56 All commands accept `--json` for machine-readable output on stdout (diagnostics go to stderr). 57 57 58 + ### Help & authoring guidance 59 + 60 + - `mill` or `mill --help`: prints root help with authoring guidance 61 + - `mill <command> --help`: prints command help with authoring guidance 62 + - If resolved config overrides `authoring.instructions`, help uses that text. 63 + - Otherwise help falls back to built-in static guidance (`systemPrompt` = WHO, `prompt` = WHAT). 64 + 58 65 ## Configuration 59 66 60 67 ```ts 61 - // mill.config.ts 62 - import { defineConfig, processDriver, piCodec } from "@mill/core"; 63 - 64 - export default defineConfig({ 65 - defaultDriver: "pi", 66 - defaultModel: "openai/gpt-5.3-codex", 67 - defaultExecutor: "direct", 68 - drivers: { 69 - pi: processDriver({ 70 - command: "pi", 71 - args: ["-p"], 72 - codec: piCodec(), 73 - }), 68 + // mill.config.ts (local) 69 + // ~/.mill/config.ts (global) 70 + export default { 71 + authoring: { 72 + instructions: 73 + "Use systemPrompt for WHO (role/method), prompt for WHAT (explicit task + scope + validation).", 74 74 }, 75 - }); 75 + }; 76 76 ``` 77 + 78 + `mill init` creates `./mill.config.ts`. 79 + `mill init --global` creates `~/.mill/config.ts`. 77 80 78 81 Resolved in order: `./mill.config.ts` → walk up to repo root → `~/.mill/config.ts` → built-in defaults. 79 82
+1 -1
docs/design-docs/mill-v0-architecture-and-boundaries.md
··· 189 189 src/ 190 190 public/ 191 191 mill.api.ts # Promise-based user API 192 - discovery.api.ts # Promise-based CLI/discovery payload builders 192 + discovery.api.ts # Promise-based core discovery payload builders 193 193 types.ts # user-facing interfaces allowed 194 194 domain/ 195 195 run.schema.ts # Schema-based domain models (no interfaces)
+1
docs/exec-plans/active/vertical-slices.json
··· 1 1 { 2 + "historicalNote": "This file captures implementation planning history. S1 discovery references (mill --help --json, discovery command) are historical and not part of current CLI behavior.", 2 3 "slices": [ 3 4 { 4 5 "id": "S1",
+2
docs/exec-plans/active/vertical-slices.md
··· 1 1 # Vertical Slices Plan (v0) 2 2 3 + > Historical note (2026-02-25): this execution plan captures the implementation path. Some S1 discovery references (`mill --help --json`, `discovery` command) are historical and no longer match the current CLI surface. 4 + 3 5 This plan sequences integrated, testable slices from highest-leverage foundation to full v0 behavior. 4 6 5 7 Each slice intentionally spans:
+1 -1
docs/product-specs/README.md
··· 1 1 # Product Specs Index 2 2 3 - - `mill-v0-product-spec.md` — Sections 1–7 from SPEC (product definition, constraints, CLI, run model, config, discovery). 3 + - `mill-v0-product-spec.md` — Sections 1–7 (product definition, constraints, CLI, run model, config, authoring help behavior).
+42 -72
docs/product-specs/mill-v0-product-spec.md
··· 1 1 # mill v0 Product Spec (Sections 1–7) 2 2 3 - _Source: `SPEC.md` (verbatim split for cedar-style docs tree)._ 3 + _Source: `SPEC.md`, updated to reflect current CLI behavior._ 4 4 5 5 ## 1) Product definition 6 6 ··· 62 62 ## 3) CLI surface (v0) 63 63 64 64 ```bash 65 - mill run <program.ts> [--json] [--sync] [--driver <name>] [--executor <name>] [--confirm=false] 66 - mill status <runId> [--json] 67 - mill wait <runId> --timeout <seconds> [--json] 68 - mill watch <runId> [--json] [--raw] 69 - mill ls [--json] [--status <status>] 70 - mill inspect <runId>[.<spawnId>] [--json] [--session] 71 - mill cancel <runId> [--json] 72 - mill init 65 + mill run <program.ts> [--json] [--sync] [--runs-dir <path>] [--driver <name>] [--executor <name>] [--meta-json <json>] 66 + mill status <runId> [--json] [--runs-dir <path>] [--driver <name>] 67 + mill wait <runId> --timeout <seconds> [--json] [--runs-dir <path>] [--driver <name>] 68 + mill watch [--run <runId>] [--since-time <iso>] [--json] [--raw] [--runs-dir <path>] [--driver <name>] 69 + mill ls [--json] [--status <status>] [--runs-dir <path>] [--driver <name>] 70 + mill inspect <runId>[.<spawnId>] [--json] [--session] [--runs-dir <path>] [--driver <name>] 71 + mill cancel <runId> [--json] [--runs-dir <path>] [--driver <name>] 72 + mill init [--global] 73 73 ``` 74 74 75 - Discovery (for humans and agents): 75 + Help + authoring guidance: 76 76 77 - - `mill` (no subcommand): concise discovery card 78 - - `mill --help`: help text + authoring guidance 79 - - `mill --help --json`: machine-readable discovery payload 77 + - `mill` / `mill --help`: root help text with authoring guidance 78 + - `mill <command> --help`: command help text + authoring guidance 79 + - If resolved config overrides `authoring.instructions`, help uses that text. 80 + - Otherwise help falls back to static guidance (`systemPrompt` = WHO, `prompt` = WHAT). 80 81 81 - No other commands in v0. 82 + No `discovery` subcommand in v0. 82 83 83 84 ### 3.1 Output mode contract 84 85 ··· 116 117 117 118 1. Resolve config 118 119 2. Validate program path 119 - 3. Optional interactive confirmation 120 - 4. Allocate `runId`, create run directory, write initial metadata 121 - 5. Start detached worker process 122 - 6. Return immediately (`runId`, `status=running`, paths) 120 + 3. Allocate `runId`, create run directory, write initial metadata 121 + 4. Start detached worker process 122 + 5. Return immediately (`runId`, `status=running`, paths) 123 123 124 124 `--sync` blocks until completion (implemented as submit + wait internally). 125 125 ··· 149 149 150 150 ## 6) Config contract (`mill.config.ts`) 151 151 152 - ```ts 153 - import { defineConfig } from "@mill/core"; 154 - 155 - export default defineConfig({ 156 - defaultDriver: "default", 157 - defaultModel: "openai/gpt-5.3-codex", 158 - defaultExecutor: "direct", 159 - 160 - drivers: { 161 - default: processDriver({ 162 - command: "pi", 163 - args: ["-p"], 164 - codec: piCodec(), 165 - env: {}, 166 - }), 167 - }, 168 - 169 - executors: { 170 - direct: directExecutor(), 171 - vm: vmExecutor({ runtime: "docker", image: "mill-sandbox:latest" }), 172 - }, 152 + Minimal import-free config (works for both local and global config paths): 173 153 154 + ```ts 155 + export default { 156 + // Optional overrides: 157 + // defaultDriver: "pi", 158 + // defaultExecutor: "direct", 159 + // defaultModel: "openai-codex/gpt-5.3-codex", 174 160 authoring: { 175 161 instructions: 176 - "Use systemPrompt for WHO and prompt for WHAT. Prefer cheaper models for search and stronger models for synthesis.", 162 + "Use systemPrompt for WHO (role/method), prompt for WHAT (explicit task + scope + validation).", 177 163 }, 164 + }; 165 + ``` 178 166 179 - extensions: [ 180 - // optional 181 - ], 182 - }); 183 - ``` 167 + `mill init` writes `./mill.config.ts`. 168 + `mill init --global` writes `~/.mill/config.ts`. 184 169 185 170 ### 6.1 Config resolution order 186 171 ··· 195 180 - Resolved env values are normalized into config/services and passed downward. 196 181 - Runtime/domain modules must not read `process.env` directly. 197 182 198 - ## 7) Discovery contract (`mill --help --json`) 183 + ## 7) Authoring help contract 184 + 185 + `mill` help output is the primary authoring guide for humans/agents. 186 + 187 + Behavior: 199 188 200 - `mill --help --json` MUST include enough info for an agent to author a program without extra docs: 189 + 1. `mill` and `mill --help` print root help + authoring guidance. 190 + 2. `mill <command> --help` prints command help + authoring guidance. 191 + 3. If resolved config provides a custom `authoring.instructions` override, that text replaces static guidance in help output. 192 + 4. If config does not override authoring instructions, help falls back to static guidance: 193 + - `systemPrompt` = WHO the agent is 194 + - `prompt` = WHAT to do now 201 195 202 - ```json 203 - { 204 - "discoveryVersion": 1, 205 - "programApi": { 206 - "spawnRequired": ["agent", "systemPrompt", "prompt"], 207 - "spawnOptional": ["model"], 208 - "resultFields": ["text", "sessionRef", "agent", "model", "driver", "exitCode", "stopReason"] 209 - }, 210 - "drivers": { 211 - "default": { 212 - "description": "Local process driver", 213 - "modelFormat": "provider/model-id", 214 - "models": ["openai/gpt-5.3-codex", "anthropic/claude-sonnet-4-6"] 215 - } 216 - }, 217 - "authoring": { 218 - "instructions": "...from config..." 219 - }, 220 - "async": { 221 - "submit": "mill run <program.ts> --json", 222 - "status": "mill status <runId> --json", 223 - "wait": "mill wait <runId> --timeout 30 --json" 224 - } 225 - } 226 - ``` 196 + There is no dedicated `discovery` subcommand in CLI v0.
+1 -1
packages/cli/package.json
··· 1 1 { 2 2 "name": "@mill/cli", 3 - "version": "0.1.3", 3 + "version": "0.1.4", 4 4 "bin": { 5 5 "mill": "./src/bin/mill.ts" 6 6 },
+66 -48
packages/cli/src/public/index.api.test.ts
··· 5 5 import * as Schema from "@effect/schema/Schema"; 6 6 import { runCli } from "./index.api"; 7 7 8 - const DiscoveryEnvelope = Schema.parseJson( 9 - Schema.Struct({ 10 - discoveryVersion: Schema.Number, 11 - programApi: Schema.Struct({ 12 - spawnRequired: Schema.Array(Schema.String), 13 - spawnOptional: Schema.Array(Schema.String), 14 - resultFields: Schema.Array(Schema.String), 15 - }), 16 - drivers: Schema.Record({ 17 - key: Schema.String, 18 - value: Schema.Struct({ 19 - description: Schema.String, 20 - modelFormat: Schema.String, 21 - models: Schema.Array(Schema.String), 22 - }), 23 - }), 24 - executors: Schema.Record({ 25 - key: Schema.String, 26 - value: Schema.Struct({ 27 - description: Schema.String, 28 - }), 29 - }), 30 - authoring: Schema.Struct({ 31 - instructions: Schema.String, 32 - }), 33 - async: Schema.Struct({ 34 - submit: Schema.String, 35 - status: Schema.String, 36 - wait: Schema.String, 37 - watch: Schema.String, 38 - }), 39 - }), 40 - ); 41 - 42 8 const RunSyncEnvelope = Schema.parseJson( 43 9 Schema.Struct({ 44 10 run: Schema.Struct({ ··· 174 140 ); 175 141 176 142 describe("runCli", () => { 177 - it("writes machine payload to stdout for discovery --json", async () => { 143 + it("returns non-zero for removed discovery subcommand", async () => { 178 144 const stdout: Array<string> = []; 179 145 const stderr: Array<string> = []; 180 146 ··· 192 158 }, 193 159 }); 194 160 195 - expect(code).toBe(0); 196 - expect(stdout).toHaveLength(1); 161 + expect(code).toBe(1); 162 + expect(stdout).toHaveLength(0); 197 163 expect(stderr).toHaveLength(0); 198 - 199 - const payload = Schema.decodeUnknownSync(DiscoveryEnvelope)(stdout[0]); 200 - expect(payload.discoveryVersion).toBe(1); 201 - expect(Array.isArray(payload.drivers.pi?.models)).toBe(true); 202 - expect(Array.isArray(payload.drivers.claude?.models)).toBe(true); 203 - expect(Array.isArray(payload.drivers.codex?.models)).toBe(true); 204 - expect(payload.programApi.spawnRequired).toEqual(["agent", "systemPrompt", "prompt"]); 205 - expect(payload.executors.direct?.description).toBe("Local direct executor"); 206 - expect(payload.executors.vm).toBeUndefined(); 207 164 }); 208 165 209 166 it("executes run --sync and resolves status for persisted runId", async () => { ··· 940 897 } 941 898 }); 942 899 943 - it("loads authoring instructions from resolved config into root help", async () => { 900 + it("uses config authoring instructions in root help when configured", async () => { 944 901 const stdout: Array<string> = []; 945 902 const stderr: Array<string> = []; 946 903 ··· 967 924 968 925 expect(code).toBe(0); 969 926 expect(stderr).toHaveLength(0); 970 - expect(stdout[0]).toContain("CUSTOM_AUTHORING_INSTRUCTIONS"); 927 + expect(stdout[0]).toContain("Authoring:\n CUSTOM_AUTHORING_INSTRUCTIONS"); 928 + expect(stdout[0]).not.toContain("systemPrompt = WHO the agent is"); 929 + }); 930 + 931 + it("falls back to static authoring guidance in root help when config omits authoring", async () => { 932 + const stdout: Array<string> = []; 933 + const stderr: Array<string> = []; 934 + 935 + const code = await runCli([], { 936 + cwd: "/workspace/repo", 937 + homeDirectory: "/Users/tester", 938 + pathExists: async (path) => path === "/Users/tester/.mill/config.ts", 939 + loadConfigModule: async () => ({ 940 + default: { 941 + defaultDriver: "pi", 942 + }, 943 + }), 944 + io: { 945 + stdout: (line) => { 946 + stdout.push(line); 947 + }, 948 + stderr: (line) => { 949 + stderr.push(line); 950 + }, 951 + }, 952 + }); 953 + 954 + expect(code).toBe(0); 955 + expect(stderr).toHaveLength(0); 956 + expect(stdout[0]).toContain("systemPrompt = WHO the agent is"); 957 + expect(stdout[0]).toContain("prompt = WHAT to do now"); 958 + expect(stdout[0]).not.toContain("From config:"); 971 959 }); 972 960 973 961 it("loads authoring instructions from resolved config into command help", async () => { ··· 1000 988 expect(stdout.join("\n")).toContain( 1001 989 "Authoring (from config): CUSTOM_AUTHORING_IN_COMMAND_HELP", 1002 990 ); 991 + expect(stdout.join("\n")).not.toContain("systemPrompt = WHO the agent is"); 992 + }); 993 + 994 + it("falls back to static authoring guidance in command help when config omits authoring", async () => { 995 + const stdout: Array<string> = []; 996 + const stderr: Array<string> = []; 997 + 998 + const code = await runCli(["run", "--help"], { 999 + cwd: "/workspace/repo", 1000 + homeDirectory: "/Users/tester", 1001 + pathExists: async (path) => path === "/Users/tester/.mill/config.ts", 1002 + loadConfigModule: async () => ({ 1003 + default: { 1004 + defaultDriver: "pi", 1005 + }, 1006 + }), 1007 + io: { 1008 + stdout: (line) => { 1009 + stdout.push(line); 1010 + }, 1011 + stderr: (line) => { 1012 + stderr.push(line); 1013 + }, 1014 + }, 1015 + }); 1016 + 1017 + expect(code).toBe(0); 1018 + expect(stderr).toHaveLength(0); 1019 + expect(stdout.join("\n")).toContain("Authoring:\n systemPrompt = WHO the agent is"); 1020 + expect(stdout.join("\n")).not.toContain("Authoring (from config):"); 1003 1021 }); 1004 1022 1005 1023 it("wait timeout is deterministic with typed JSON error contract", async () => {
+46 -60
packages/cli/src/public/index.api.ts
··· 6 6 import { Effect, Option, Runtime, Scope } from "effect"; 7 7 import { 8 8 cancelRun, 9 - createDiscoveryPayload, 10 9 defineConfig, 11 10 getRunStatus, 12 11 inspectRun, ··· 610 609 return 0; 611 610 }; 612 611 613 - interface DiscoveryCommandInput { 614 - readonly json: boolean; 615 - } 616 - 617 - const discoveryCommand = async ( 618 - command: DiscoveryCommandInput, 619 - options: RunCliOptions, 620 - io: CliIo, 621 - ): Promise<number> => { 622 - const payload = await createDiscoveryPayload({ 623 - defaults: defaultConfig, 624 - cwd: options.cwd, 625 - homeDirectory: options.homeDirectory, 626 - pathExists: options.pathExists, 627 - loadConfigModule: options.loadConfigModule, 628 - }); 629 - 630 - io.stdout(command.json ? JSON.stringify(payload) : JSON.stringify(payload, null, 2)); 631 - return 0; 632 - }; 633 - 634 612 const createCli = (options: RunCliOptions, io: CliIo) => { 635 613 const run = CliCommand.make( 636 614 "run", ··· 745 723 ), 746 724 ); 747 725 748 - const discovery = CliCommand.make( 749 - "discovery", 750 - { 751 - json: Options.boolean("json"), 752 - }, 753 - (command) => toCliEffect(discoveryCommand(command, options, io)), 754 - ).pipe(CliCommand.withDescription("Emit discovery metadata for tooling.")); 755 - 756 726 return CliCommand.make("mill").pipe( 757 727 CliCommand.withDescription("Mill orchestration runtime."), 758 - CliCommand.withSubcommands([ 759 - run, 760 - status, 761 - wait, 762 - watch, 763 - inspect, 764 - cancel, 765 - ls, 766 - init, 767 - discovery, 768 - worker, 769 - ]), 728 + CliCommand.withSubcommands([run, status, wait, watch, inspect, cancel, ls, init, worker]), 770 729 ); 771 730 }; 772 731 773 - const buildHelpText = ( 774 - authoringInstructions: string, 775 - ): string => `mill - orchestration runtime for AI agents 732 + const STATIC_AUTHORING_HELP_LINES = [ 733 + " systemPrompt = WHO the agent is (personality, methodology, output format)", 734 + " prompt = WHAT to do now (specific files, concrete task)", 735 + ] as const; 736 + 737 + type ResolvedAuthoringHelp = 738 + | { readonly source: "static" } 739 + | { readonly source: "config"; readonly instructions: string }; 740 + 741 + const renderAuthoringHelp = (authoringHelp: ResolvedAuthoringHelp): string => 742 + authoringHelp.source === "config" 743 + ? `Authoring:\n ${authoringHelp.instructions}` 744 + : `Authoring:\n${STATIC_AUTHORING_HELP_LINES.join("\n")}`; 745 + 746 + const buildHelpText = (authoringHelp: ResolvedAuthoringHelp): string => 747 + `mill - orchestration runtime for AI agents 776 748 777 749 Usage: mill <command> [options] 778 750 ··· 785 757 cancel <runId> Cancel a running execution 786 758 ls List runs 787 759 init [--global] Create starter config (local or ~/.mill/config.ts) 788 - discovery Emit discovery metadata 789 760 790 761 Global options: --json, --driver <name>, --runs-dir <path> 791 762 ··· 809 780 mill.spawn({ agent: "perf", systemPrompt: "...", prompt: "Profile src/api/" }), 810 781 ]); 811 782 812 - Authoring: 813 - systemPrompt = WHO the agent is (personality, methodology, output format) 814 - prompt = WHAT to do now (specific files, concrete task) 815 - From config: ${authoringInstructions} 783 + ${renderAuthoringHelp(authoringHelp)} 816 784 817 785 Run mill <command> --help for details.`; 818 786 ··· 827 795 "cancel", 828 796 "ls", 829 797 "init", 830 - "discovery", 831 798 "_worker", 832 799 ]); 833 800 ··· 847 814 return argv.slice(1).some((argument) => HELP_FLAGS.has(argument)); 848 815 }; 849 816 850 - const resolveAuthoringInstructionsForHelp = async (options: RunCliOptions): Promise<string> => { 817 + const resolveAuthoringHelpForHelp = async ( 818 + options: RunCliOptions, 819 + ): Promise<ResolvedAuthoringHelp> => { 851 820 try { 852 821 const resolvedConfig = await resolveConfig({ 853 822 defaults: defaultConfig, ··· 857 826 loadConfigModule: options.loadConfigModule, 858 827 }); 859 828 860 - return resolvedConfig.config.authoring.instructions; 829 + const instructions = resolvedConfig.config.authoring.instructions; 830 + const hasAuthoringOverride = 831 + resolvedConfig.source !== "defaults" && instructions !== defaultConfig.authoring.instructions; 832 + 833 + if (hasAuthoringOverride) { 834 + return { 835 + source: "config", 836 + instructions, 837 + }; 838 + } 861 839 } catch { 862 - return defaultConfig.authoring.instructions; 840 + // fall through to static authoring help 863 841 } 842 + 843 + return { 844 + source: "static", 845 + }; 864 846 }; 865 847 866 848 export const runCli = async ( ··· 871 853 const io = resolvedOptions.io ?? defaultIo; 872 854 873 855 if (isHelpRequest(argv)) { 874 - const authoringInstructions = await resolveAuthoringInstructionsForHelp(resolvedOptions); 875 - io.stdout(buildHelpText(authoringInstructions)); 856 + const authoringHelp = await resolveAuthoringHelpForHelp(resolvedOptions); 857 + io.stdout(buildHelpText(authoringHelp)); 876 858 return 0; 877 859 } 878 860 879 861 const commandHelpRequest = isCommandHelpRequest(argv); 880 - const authoringInstructions = commandHelpRequest 881 - ? await resolveAuthoringInstructionsForHelp(resolvedOptions) 862 + const authoringHelp = commandHelpRequest 863 + ? await resolveAuthoringHelpForHelp(resolvedOptions) 882 864 : undefined; 883 865 884 866 const command = createCli(resolvedOptions, io); ··· 903 885 const compactHelp = CliConfig.layer({ showBuiltIns: false, showTypes: false }); 904 886 const exitCode = await runWithBunContext(Effect.provide(codeEffect, compactHelp)); 905 887 906 - if (commandHelpRequest && exitCode === 0 && authoringInstructions !== undefined) { 907 - io.stdout(`Authoring (from config): ${authoringInstructions}`); 888 + if (commandHelpRequest && exitCode === 0 && authoringHelp !== undefined) { 889 + if (authoringHelp.source === "config") { 890 + io.stdout(`Authoring (from config): ${authoringHelp.instructions}`); 891 + } else { 892 + io.stdout(`Authoring:\n${STATIC_AUTHORING_HELP_LINES.join("\n")}`); 893 + } 908 894 } 909 895 910 896 return exitCode;
+5 -47
packages/cli/src/public/index.e2e.test.ts
··· 9 9 10 10 const runtime = Runtime.defaultRuntime; 11 11 12 - const DiscoveryEnvelope = Schema.parseJson( 13 - Schema.Struct({ 14 - discoveryVersion: Schema.Number, 15 - programApi: Schema.Struct({ 16 - spawnRequired: Schema.Array(Schema.String), 17 - spawnOptional: Schema.Array(Schema.String), 18 - resultFields: Schema.Array(Schema.String), 19 - }), 20 - drivers: Schema.Record({ 21 - key: Schema.String, 22 - value: Schema.Struct({ 23 - description: Schema.String, 24 - modelFormat: Schema.String, 25 - models: Schema.Array(Schema.String), 26 - }), 27 - }), 28 - executors: Schema.Record({ 29 - key: Schema.String, 30 - value: Schema.Struct({ 31 - description: Schema.String, 32 - }), 33 - }), 34 - authoring: Schema.Struct({ 35 - instructions: Schema.String, 36 - }), 37 - async: Schema.Struct({ 38 - submit: Schema.String, 39 - status: Schema.String, 40 - wait: Schema.String, 41 - watch: Schema.String, 42 - }), 43 - }), 44 - ); 45 - 46 12 const RunSyncEnvelope = Schema.parseJson( 47 13 Schema.Struct({ 48 14 run: Schema.Struct({ ··· 149 115 const commandExitCode = (command: Command.Command): Promise<number> => 150 116 Runtime.runPromise(runtime)(Effect.provide(Command.exitCode(command), BunContext.layer)); 151 117 152 - describe("mill discovery/help (e2e)", () => { 153 - it("returns discovery contract payload on stdout", async () => { 154 - const output = await commandOutput( 118 + describe("mill help (e2e)", () => { 119 + it("does not expose discovery subcommand", async () => { 120 + const exitCode = await commandExitCode( 155 121 Command.make("bun", "run", "packages/cli/src/bin/mill.ts", "discovery", "--json"), 156 122 ); 157 123 158 - const payload = Schema.decodeUnknownSync(DiscoveryEnvelope)(output); 159 - expect(payload.discoveryVersion).toBe(1); 160 - expect(payload.programApi.spawnRequired).toEqual(["agent", "systemPrompt", "prompt"]); 161 - expect(Array.isArray(payload.drivers.pi?.models)).toBe(true); 162 - expect(Array.isArray(payload.drivers.claude?.models)).toBe(true); 163 - expect(Array.isArray(payload.drivers.codex?.models)).toBe(true); 164 - expect(payload.executors.direct?.description).toBe("Local direct executor"); 165 - expect(payload.executors.vm).toBeUndefined(); 166 - expect(payload.authoring.instructions.length).toBeGreaterThan(0); 167 - expect(payload.async.submit).toBe("mill run <program.ts> --json"); 124 + expect(exitCode).toBe(1); 168 125 }); 169 126 170 127 it("prints top-level help via built-in --help", async () => { ··· 175 132 expect(output).toContain("Usage: mill <command>"); 176 133 expect(output).toContain("Commands:"); 177 134 expect(output).toContain("run <program.ts>"); 135 + expect(output).not.toContain("discovery"); 178 136 expect(output).not.toContain("Effect-first"); 179 137 }); 180 138