argsbarg 5.0.1 → 5.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 (38) hide show
  1. package/CHANGELOG.md +8 -1
  2. package/docs/distribution-homebrew.md +2 -0
  3. package/examples/full-example/scripts/formula-shared.ts +1 -0
  4. package/package.json +1 -1
  5. package/src/builtins/builtins.test.ts +23 -0
  6. package/src/builtins/config.test.ts +8 -0
  7. package/src/cli-tool/cli-smoke.test.ts +8 -0
  8. package/src/cli-tool/create.test.ts +13 -0
  9. package/src/cli-tool/full-example-capabilities.test.ts +8 -0
  10. package/src/cli-tool/main.ts +0 -0
  11. package/src/config/context.test.ts +8 -0
  12. package/src/config/file.test.ts +12 -0
  13. package/src/config/resolve.test.ts +18 -0
  14. package/src/config/validate.test.ts +11 -0
  15. package/src/config.integration.test.ts +5 -0
  16. package/src/configure/configure.test.ts +22 -0
  17. package/src/configure/index.ts +14 -0
  18. package/src/configure/prompt.ts +7 -0
  19. package/src/docs/api-guide.test.ts +10 -0
  20. package/src/docs/docs.test.ts +27 -0
  21. package/src/docs/mcp-resources.test.ts +10 -0
  22. package/src/formats.test.ts +9 -0
  23. package/src/headless.test.ts +10 -0
  24. package/src/hidden-mcpb.test.ts +22 -0
  25. package/src/install/binary-placement.test.ts +14 -0
  26. package/src/install/gh-release-update.test.ts +9 -0
  27. package/src/install/install-validate.test.ts +10 -0
  28. package/src/install/mcp-codex.test.ts +7 -0
  29. package/src/install/mcp-openclaw.test.ts +6 -0
  30. package/src/install/mcp-opencode.test.ts +10 -0
  31. package/src/install/status.test.ts +9 -0
  32. package/src/install/targets.test.ts +19 -0
  33. package/src/invoke.test.ts +14 -0
  34. package/src/mcp/claude.test.ts +10 -0
  35. package/src/mcp/env.test.ts +12 -0
  36. package/src/mcp/zip.test.ts +5 -0
  37. package/src/mcp.integration.test.ts +39 -0
  38. package/src/parse.test.ts +71 -0
@@ -1,3 +1,7 @@
1
+ /*
2
+ Tests for configure/configure module behavior.
3
+ */
4
+
1
5
  import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
6
  import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
3
7
  import { tmpdir } from "node:os";
@@ -42,30 +46,37 @@ afterEach(() => {
42
46
  rmSync(home, { recursive: true, force: true });
43
47
  });
44
48
 
49
+ /** Tests for configure opts. */
45
50
  describe("configure opts", () => {
51
+ /** Validate rejects multiple modes. */
46
52
  test("validate rejects multiple modes", () => {
47
53
  const opts = parseConfigureOpts({ sync: "1", status: "1" });
48
54
  expect(validateConfigureOpts(opts)).toContain("only one");
49
55
  });
50
56
 
57
+ /** Sync requires --yes. */
51
58
  test("sync requires --yes", () => {
52
59
  const opts = parseConfigureOpts({ sync: "1" });
53
60
  expect(validateConfigureOpts(opts)).toContain("--yes");
54
61
  });
55
62
 
63
+ /** Remove-all requires --yes. */
56
64
  test("remove-all requires --yes", () => {
57
65
  const opts = parseConfigureOpts({ "remove-all": "1" });
58
66
  expect(validateConfigureOpts(opts)).toContain("--yes");
59
67
  });
60
68
  });
61
69
 
70
+ /** Tests for install paths. */
62
71
  describe("install paths", () => {
72
+ /** ResolveInstallPaths includes skill and mcp paths. */
63
73
  test("resolveInstallPaths includes skill and mcp paths", () => {
64
74
  const paths = resolveInstallPaths(fixture);
65
75
  expect(paths.cursorSkillDir).toContain(".cursor/skills");
66
76
  expect(paths.cursorMcpPath).toContain("mcp.json");
67
77
  });
68
78
 
79
+ /** Claude desktop mcp path on darwin. */
69
80
  test("claude desktop mcp path on darwin", () => {
70
81
  const prev = process.platform;
71
82
  Object.defineProperty(process, "platform", { value: "darwin" });
@@ -79,7 +90,9 @@ describe("install paths", () => {
79
90
  });
80
91
  });
81
92
 
93
+ /** Tests for detect installed. */
82
94
  describe("detect installed", () => {
95
+ /** Detects cursor mcp when configured. */
83
96
  test("detects cursor mcp when configured", () => {
84
97
  const paths = resolveInstallPaths(fixture);
85
98
  mkdirSync(join(home, ".cursor"), { recursive: true });
@@ -95,7 +108,9 @@ describe("detect installed", () => {
95
108
  });
96
109
  });
97
110
 
111
+ /** Tests for sync plan. */
98
112
  describe("sync plan", () => {
113
+ /** BuildUpdatePlan greenfield includes agent targets. */
99
114
  test("buildUpdatePlan greenfield includes agent targets", () => {
100
115
  const paths = resolveInstallPaths(fixture);
101
116
  const plan = buildUpdatePlan(fixture, paths, parseInstallOpts({ reinstall: "1", yes: "1" }));
@@ -103,6 +118,7 @@ describe("sync plan", () => {
103
118
  expect(plan.some((a) => a.kind === "app")).toBe(false);
104
119
  });
105
120
 
121
+ /** BuildInstallPlan --all omits app self-install. */
106
122
  test("buildInstallPlan --all omits app self-install", () => {
107
123
  const paths = resolveInstallPaths(fixture);
108
124
  const plan = buildInstallPlan(fixture, paths, parseInstallOpts({ all: "1" }));
@@ -110,6 +126,7 @@ describe("sync plan", () => {
110
126
  expect(plan.some((a) => a.kind.endsWith("-mcp"))).toBe(true);
111
127
  });
112
128
 
129
+ /** BuildInstallPlan respects configure.agentIntegration skill. */
113
130
  test("buildInstallPlan respects configure.agentIntegration skill", () => {
114
131
  const skillApp: CliProgram = {
115
132
  ...fixture,
@@ -123,7 +140,9 @@ describe("sync plan", () => {
123
140
  });
124
141
  });
125
142
 
143
+ /** Tests for remove plan. */
126
144
  describe("remove plan", () => {
145
+ /** BuildUninstallPlan --all with nothing installed is empty. */
127
146
  test("buildUninstallPlan --all with nothing installed is empty", () => {
128
147
  const paths = resolveInstallPaths(fixture);
129
148
  const plan = buildUninstallPlan(fixture, paths, parseInstallOpts({ uninstall: "1", all: "1" }));
@@ -131,7 +150,9 @@ describe("remove plan", () => {
131
150
  });
132
151
  });
133
152
 
153
+ /** Tests for app config wizard. */
134
154
  describe("app config wizard", () => {
155
+ /** Tests that appConfigHasEntries when entries exist. */
135
156
  test("appConfigHasEntries when entries exist", () => {
136
157
  expect(
137
158
  appConfigHasEntries({
@@ -141,6 +162,7 @@ describe("app config wizard", () => {
141
162
  ).toBe(true);
142
163
  });
143
164
 
165
+ /** Tests that appConfigHasEntries false for empty entries or missing appConfig. */
144
166
  test("appConfigHasEntries false for empty entries or missing appConfig", () => {
145
167
  expect(appConfigHasEntries({ ...fixture, appConfig: { entries: {} } })).toBe(false);
146
168
  expect(appConfigHasEntries(fixture)).toBe(false);
@@ -1,3 +1,7 @@
1
+ /*
2
+ Interactive and automated `configure` command orchestration (agent artifacts and app config).
3
+ */
4
+
1
5
  import { resolveCapabilities } from "../capabilities.ts";
2
6
  import { displayAppConfigPath, runConfigure } from "../config/bootstrap.ts";
3
7
  import { resolveInstallPaths } from "../install/paths.ts";
@@ -36,6 +40,7 @@ export function appConfigHasEntries(program: CliProgram): boolean {
36
40
  return !!entries && Object.keys(entries).length > 0;
37
41
  }
38
42
 
43
+ /** Parsed flags for the top-level `configure` built-in. */
39
44
  export interface ConfigureOpts {
40
45
  sync?: boolean;
41
46
  removeAll?: boolean;
@@ -46,6 +51,7 @@ export interface ConfigureOpts {
46
51
  json?: boolean;
47
52
  }
48
53
 
54
+ /** Maps raw argv flags into {@link ConfigureOpts}. */
49
55
  export function parseConfigureOpts(raw: Record<string, string>): ConfigureOpts {
50
56
  const flag = (name: string) => raw[name] === "1";
51
57
  return {
@@ -59,6 +65,7 @@ export function parseConfigureOpts(raw: Record<string, string>): ConfigureOpts {
59
65
  };
60
66
  }
61
67
 
68
+ /** Returns an error message when configure flags are inconsistent; otherwise null. */
62
69
  export function validateConfigureOpts(opts: ConfigureOpts): string | null {
63
70
  const flags = [opts.sync, opts.removeAll, opts.removeConfig, opts.status].filter(Boolean);
64
71
  if (flags.length > 1) {
@@ -73,6 +80,7 @@ export function validateConfigureOpts(opts: ConfigureOpts): string | null {
73
80
  return null;
74
81
  }
75
82
 
83
+ /** Adapts configure flags to internal install-plan option shape. */
76
84
  function configureToInstallOpts(opts: ConfigureOpts): InstallOpts {
77
85
  if (opts.status) {
78
86
  return { status: true, yes: opts.yes, dry: opts.dry, json: opts.json };
@@ -89,6 +97,7 @@ function configureToInstallOpts(opts: ConfigureOpts): InstallOpts {
89
97
  return { dry: opts.dry, json: opts.json };
90
98
  }
91
99
 
100
+ /** Installs a skill target and returns changed paths. */
92
101
  function runSkillAction(root: CliProgram, kind: InstallActionKind, opts: InstallOpts): string[] {
93
102
  const target = skillTargetFromActionKind(kind);
94
103
  if (!target) return [];
@@ -99,6 +108,7 @@ function runSkillAction(root: CliProgram, kind: InstallActionKind, opts: Install
99
108
  });
100
109
  }
101
110
 
111
+ /** Runs install or uninstall actions and collects changed paths. */
102
112
  function executePlan(
103
113
  root: CliProgram,
104
114
  actions: Array<InstallAction | UninstallAction>,
@@ -130,6 +140,7 @@ function executePlan(
130
140
  return changed;
131
141
  }
132
142
 
143
+ /** Builds plan context limited to a single artifact key. */
133
144
  function buildSingleTargetContext(
134
145
  root: CliProgram,
135
146
  paths: ReturnType<typeof resolveInstallPaths>,
@@ -147,6 +158,7 @@ function buildSingleTargetContext(
147
158
  };
148
159
  }
149
160
 
161
+ /** Resolves install or uninstall actions for one artifact target. */
150
162
  function actionsForTarget(
151
163
  root: CliProgram,
152
164
  paths: ReturnType<typeof resolveInstallPaths>,
@@ -164,6 +176,7 @@ function actionsForTarget(
164
176
  return target.planUninstall(ctx);
165
177
  }
166
178
 
179
+ /** Walks enabled targets with per-target prompts (TTY required). */
167
180
  async function runInteractiveConfigure(root: CliProgram, opts: ConfigureOpts): Promise<string[]> {
168
181
  if (!process.stdin.isTTY) {
169
182
  throw new Error("Interactive configure requires a TTY. Use flags such as --sync --yes.");
@@ -218,6 +231,7 @@ async function runInteractiveConfigure(root: CliProgram, opts: ConfigureOpts): P
218
231
  return changed;
219
232
  }
220
233
 
234
+ /** Runs sync, remove, or status modes without per-target prompts. */
221
235
  async function runAutomatedConfigure(root: CliProgram, opts: ConfigureOpts): Promise<string[]> {
222
236
  const installOpts = configureToInstallOpts(opts);
223
237
  const paths = resolveInstallPaths(root);
@@ -1,6 +1,11 @@
1
+ /*
2
+ TTY prompts for per-target install, skip, or uninstall during interactive `configure`.
3
+ */
4
+
1
5
  import { readSync } from "node:fs";
2
6
  import type { CliInstallArtifactKey } from "../install/target-types.ts";
3
7
 
8
+ /** Human-readable labels for each install artifact key in interactive prompts. */
4
9
  const LABELS: Record<CliInstallArtifactKey, string> = {
5
10
  app: "App binary",
6
11
  cursorSkill: "Cursor skill",
@@ -18,10 +23,12 @@ const LABELS: Record<CliInstallArtifactKey, string> = {
18
23
  configure: "App config",
19
24
  };
20
25
 
26
+ /** Returns the prompt label for an install artifact key. */
21
27
  export function artifactPromptLabel(key: CliInstallArtifactKey): string {
22
28
  return LABELS[key];
23
29
  }
24
30
 
31
+ /** User choice from a per-target Y/n or y/N prompt. */
25
32
  export type TargetPromptAction = "install" | "skip" | "uninstall";
26
33
 
27
34
  /** Prompt per target: Y/n when not installed, y/N when installed. */
@@ -1,3 +1,7 @@
1
+ /*
2
+ Tests for docs/api-guide module behavior.
3
+ */
4
+
1
5
  import { expect, test } from "bun:test";
2
6
  import { cliSchemaExport } from "../schema.ts";
3
7
  import type { CliProgram } from "../types.ts";
@@ -45,6 +49,7 @@ const nestedFixture: CliProgram = {
45
49
  ],
46
50
  };
47
51
 
52
+ /** Tests that generateApiGuideBody matches command section of full API guide. */
48
53
  test("generateApiGuideBody matches command section of full API guide", () => {
49
54
  const body = generateApiGuideBody(nestedFixture);
50
55
  const full = generateApiGuide(nestedFixture);
@@ -53,6 +58,7 @@ test("generateApiGuideBody matches command section of full API guide", () => {
53
58
  expect(body).not.toContain("CLI API reference");
54
59
  });
55
60
 
61
+ /** Tests that generateApiGuide covers the same command keys as cliSchemaExport. */
56
62
  test("generateApiGuide covers the same command keys as cliSchemaExport", () => {
57
63
  const md = generateApiGuide(nestedFixture);
58
64
  const schema = cliSchemaExport(nestedFixture);
@@ -62,6 +68,7 @@ test("generateApiGuide covers the same command keys as cliSchemaExport", () => {
62
68
  expect(schema.commands?.map((c) => c.key)).toEqual(["stat"]);
63
69
  });
64
70
 
71
+ /** Tests that generateApiGuide resolves program key in install notes. */
65
72
  test("generateApiGuide resolves program key in install notes", () => {
66
73
  const fixture: CliProgram = {
67
74
  key: "myapp",
@@ -75,6 +82,7 @@ test("generateApiGuide resolves program key in install notes", () => {
75
82
  expect(md).not.toContain("Upgrade to latest release");
76
83
  });
77
84
 
85
+ /** Tests that generateApiGuide mentions Homebrew upgrade. */
78
86
  test("generateApiGuide mentions Homebrew upgrade", () => {
79
87
  const fixture: CliProgram = {
80
88
  key: "myapp",
@@ -87,6 +95,7 @@ test("generateApiGuide mentions Homebrew upgrade", () => {
87
95
  expect(md).not.toContain("install --update");
88
96
  });
89
97
 
98
+ /** Tests that generateApiGuide resolves {argsbarg:program} in consumer notes. */
90
99
  test("generateApiGuide resolves {argsbarg:program} in consumer notes", () => {
91
100
  const fixture: CliProgram = {
92
101
  key: "myapp",
@@ -105,6 +114,7 @@ test("generateApiGuide resolves {argsbarg:program} in consumer notes", () => {
105
114
  expect(md).toContain("Invoke `myapp run`.");
106
115
  });
107
116
 
117
+ /** Tests that generateApiGuide and cliSchemaExport include leaf outputSchema. */
108
118
  test("generateApiGuide and cliSchemaExport include leaf outputSchema", () => {
109
119
  const fixture: CliProgram = {
110
120
  key: "myapp",
@@ -1,3 +1,7 @@
1
+ /*
2
+ Tests for docs/docs module behavior.
3
+ */
4
+
1
5
  import { afterEach, beforeEach, expect, test } from "bun:test";
2
6
  import { mkdtempSync, readFileSync, rmSync } from "node:fs";
3
7
  import { tmpdir } from "node:os";
@@ -49,6 +53,7 @@ function docsFixture(mcp = true): CliProgram {
49
53
  };
50
54
  }
51
55
 
56
+ /** Docs reserved when enabled. */
52
57
  test("docs reserved when enabled", () => {
53
58
  const root: CliProgram = {
54
59
  ...docsFixture(),
@@ -63,6 +68,7 @@ test("docs reserved when enabled", () => {
63
68
  expect(() => cliValidateProgram(root)).toThrow(/Reserved command name: docs/);
64
69
  });
65
70
 
71
+ /** Docs rejects reserved topic keys. */
66
72
  test("docs rejects reserved topic keys", () => {
67
73
  const root = docsFixture();
68
74
  const docs = root.docs;
@@ -77,22 +83,26 @@ test("docs rejects reserved topic keys", () => {
77
83
  expect(() => cliValidateProgram(root)).toThrow(/reserved/);
78
84
  });
79
85
 
86
+ /** DocsEffectiveDefaultTopic uses first topic key. */
80
87
  test("docsEffectiveDefaultTopic uses first topic key", () => {
81
88
  expect(docsEffectiveDefaultTopic(docsFixture().docs!)).toBe("readme");
82
89
  });
83
90
 
91
+ /** Bare docs prints first topic via Cli.invoke. */
84
92
  test("bare docs prints first topic via Cli.invoke", async () => {
85
93
  const result = await new Cli(docsFixture()).invoke(["docs"]);
86
94
  expect(result.exitCode).toBe(0);
87
95
  expect(result.stdout).toContain("Hello README");
88
96
  });
89
97
 
98
+ /** Docs readme prints bundled text. */
90
99
  test("docs readme prints bundled text", async () => {
91
100
  const result = await new Cli(docsFixture()).invoke(["docs", "readme"]);
92
101
  expect(result.exitCode).toBe(0);
93
102
  expect(result.stdout).toContain("Hello README");
94
103
  });
95
104
 
105
+ /** Docs defaultTopic override. */
96
106
  test("docs defaultTopic override", async () => {
97
107
  const root = docsFixture();
98
108
  root.docs!.defaultTopic = "arch";
@@ -100,6 +110,7 @@ test("docs defaultTopic override", async () => {
100
110
  expect(result.stdout).toContain("Architecture");
101
111
  });
102
112
 
113
+ /** Docs mcp when MCP enabled. */
103
114
  test("docs mcp when MCP enabled", async () => {
104
115
  const result = await new Cli(docsFixture(true)).invoke(["docs", "mcp"]);
105
116
  expect(result.exitCode).toBe(0);
@@ -109,11 +120,13 @@ test("docs mcp when MCP enabled", async () => {
109
120
  expect(result.stdout).toContain("configure --sync --yes");
110
121
  });
111
122
 
123
+ /** Docs rejects unknown subcommand. */
112
124
  test("docs rejects unknown subcommand", async () => {
113
125
  const result = await new Cli(docsFixture()).invoke(["docs", "all"]);
114
126
  expect(result.exitCode).not.toBe(0);
115
127
  });
116
128
 
129
+ /** Docs mcp absent from router when MCP disabled. */
117
130
  test("docs mcp absent from router when MCP disabled", async () => {
118
131
  const root = docsFixture(false);
119
132
  const presentation = cliPresentationRoot(root);
@@ -126,6 +139,7 @@ test("docs mcp absent from router when MCP disabled", async () => {
126
139
  expect(result.exitCode).not.toBe(0);
127
140
  });
128
141
 
142
+ /** Presentation includes docs subtree. */
129
143
  test("presentation includes docs subtree", () => {
130
144
  const presentation = cliPresentationRoot(docsFixture());
131
145
  const docsNode = presentation.commands.find((c) => c.key === "docs");
@@ -135,6 +149,7 @@ test("presentation includes docs subtree", () => {
135
149
  ).toBe(true);
136
150
  });
137
151
 
152
+ /** Docs schema prints JSON. */
138
153
  test("docs schema prints JSON", async () => {
139
154
  const result = await new Cli(docsFixture()).invoke(["docs", "schema"]);
140
155
  expect(result.exitCode).toBe(0);
@@ -143,6 +158,7 @@ test("docs schema prints JSON", async () => {
143
158
  expect(schema.commands.some((c: { key: string }) => c.key === "run")).toBe(true);
144
159
  });
145
160
 
161
+ /** Docs api prints markdown reference. */
146
162
  test("docs api prints markdown reference", async () => {
147
163
  const result = await new Cli(docsFixture()).invoke(["docs", "api"]);
148
164
  expect(result.exitCode).toBe(0);
@@ -152,6 +168,7 @@ test("docs api prints markdown reference", async () => {
152
168
  expect(result.stdout).toContain("myapp docs schema");
153
169
  });
154
170
 
171
+ /** Tests that skipsRequiredAppConfigExit includes docs and config builtins. */
155
172
  test("skipsRequiredAppConfigExit includes docs and config builtins", () => {
156
173
  const program = {
157
174
  ...docsFixture(),
@@ -165,6 +182,7 @@ test("skipsRequiredAppConfigExit includes docs and config builtins", () => {
165
182
  expect(skipsRequiredAppConfigExit(["run"], caps)).toBe(false);
166
183
  });
167
184
 
185
+ /** Docs skill prints Cursor SKILL.md. */
168
186
  test("docs skill prints Cursor SKILL.md", async () => {
169
187
  const result = await new Cli(docsFixture()).invoke(["docs", "skill"]);
170
188
  expect(result.exitCode).toBe(0);
@@ -176,6 +194,7 @@ test("docs skill prints Cursor SKILL.md", async () => {
176
194
  expect(result.stdout).not.toContain("mcp.json");
177
195
  });
178
196
 
197
+ /** Docs skill help recommends configure. */
179
198
  test("docs skill help recommends configure", async () => {
180
199
  const presentation = cliPresentationRoot(docsFixture());
181
200
  const docsNode = presentation.commands.find((c) => c.key === "docs");
@@ -190,6 +209,7 @@ test("docs skill help recommends configure", async () => {
190
209
  }
191
210
  });
192
211
 
212
+ /** Presentation includes docs schema and skill. */
193
213
  test("presentation includes docs schema and skill", () => {
194
214
  const presentation = cliPresentationRoot(docsFixture());
195
215
  const docsNode = presentation.commands.find((c) => c.key === "docs");
@@ -201,6 +221,7 @@ test("presentation includes docs schema and skill", () => {
201
221
  }
202
222
  });
203
223
 
224
+ /** Completions offer docs subcommands. */
204
225
  test("completions offer docs subcommands", () => {
205
226
  const bash = completionBashScript(cliPresentationRoot(docsFixture()));
206
227
  expect(bash).toContain("docs) echo");
@@ -210,6 +231,7 @@ test("completions offer docs subcommands", () => {
210
231
  expect(bash).toContain("skill) echo");
211
232
  });
212
233
 
234
+ /** Tests that generateMcpGuide includes schema URI and configure sync. */
213
235
  test("generateMcpGuide includes schema URI and configure sync", () => {
214
236
  const guide = generateMcpGuide(docsFixture(true));
215
237
  expect(guide).toContain("myapp://schema");
@@ -223,6 +245,7 @@ test("generateMcpGuide includes schema URI and configure sync", () => {
223
245
  expect(guide).toContain("ChatGPT");
224
246
  });
225
247
 
248
+ /** Docs --save writes topic file. */
226
249
  test("docs --save writes topic file", async () => {
227
250
  const result = await new Cli(docsFixture()).invoke(["docs", "readme", "--save"]);
228
251
  expect(result.exitCode).toBe(0);
@@ -232,6 +255,7 @@ test("docs --save writes topic file", async () => {
232
255
  expect(text).not.toContain("Generated by");
233
256
  });
234
257
 
258
+ /** Docs api --save prepends generated hint. */
235
259
  test("docs api --save prepends generated hint", async () => {
236
260
  const result = await new Cli(docsFixture()).invoke(["docs", "api", "--save"]);
237
261
  expect(result.exitCode).toBe(0);
@@ -242,6 +266,7 @@ test("docs api --save prepends generated hint", async () => {
242
266
  expect(text).toContain("CLI API reference");
243
267
  });
244
268
 
269
+ /** Docs skill --save keeps frontmatter first. */
245
270
  test("docs skill --save keeps frontmatter first", async () => {
246
271
  const result = await new Cli(docsFixture()).invoke(["docs", "skill", "--save"]);
247
272
  expect(result.exitCode).toBe(0);
@@ -252,6 +277,7 @@ test("docs skill --save keeps frontmatter first", async () => {
252
277
  expect(text.indexOf(hint)).toBeGreaterThan(text.indexOf("---\n", 4));
253
278
  });
254
279
 
280
+ /** Docs schema --save writes JSON file. */
255
281
  test("docs schema --save writes JSON file", async () => {
256
282
  const result = await new Cli(docsFixture()).invoke(["docs", "schema", "--save"]);
257
283
  expect(result.exitCode).toBe(0);
@@ -262,6 +288,7 @@ test("docs schema --save writes JSON file", async () => {
262
288
  expect(schema.key).toBe("myapp");
263
289
  });
264
290
 
291
+ /** Tests that saveDocsTopic returns relative path. */
265
292
  test("saveDocsTopic returns relative path", () => {
266
293
  const path = saveDocsTopic(docsFixture(), "api");
267
294
  expect(path).toBe("docs/api.md");
@@ -1,3 +1,7 @@
1
+ /*
2
+ Tests for docs/mcp-resources module behavior.
3
+ */
4
+
1
5
  import { expect, test } from "bun:test";
2
6
  import type { CliProgram } from "../types.ts";
3
7
  import {
@@ -30,14 +34,17 @@ function fixture(opts?: { docs?: boolean; mcp?: boolean }): CliProgram {
30
34
  };
31
35
  }
32
36
 
37
+ /** DefaultDocsTopicResourceUri. */
33
38
  test("defaultDocsTopicResourceUri", () => {
34
39
  expect(defaultDocsTopicResourceUri("my_app", "readme")).toBe("my_app://docs/readme");
35
40
  });
36
41
 
42
+ /** ResolveDocsTopicResourceUri sanitizes program key. */
37
43
  test("resolveDocsTopicResourceUri sanitizes program key", () => {
38
44
  expect(resolveDocsTopicResourceUri(fixture(), "readme")).toBe("my_app://docs/readme");
39
45
  });
40
46
 
47
+ /** DocsMcpResources when docs and MCP enabled. */
41
48
  test("docsMcpResources when docs and MCP enabled", () => {
42
49
  const resources = docsMcpResources(fixture());
43
50
  expect(resources.map((r) => r.uri)).toEqual(["my_app://docs/readme", "my_app://docs/arch"]);
@@ -47,14 +54,17 @@ test("docsMcpResources when docs and MCP enabled", () => {
47
54
  expect(resources[0]?.load()).toBe("# Readme\n");
48
55
  });
49
56
 
57
+ /** DocsMcpResources empty when docs disabled. */
50
58
  test("docsMcpResources empty when docs disabled", () => {
51
59
  expect(docsMcpResources(fixture({ docs: false }))).toEqual([]);
52
60
  });
53
61
 
62
+ /** DocsMcpResources empty when MCP disabled. */
54
63
  test("docsMcpResources empty when MCP disabled", () => {
55
64
  expect(docsMcpResources(fixture({ mcp: false }))).toEqual([]);
56
65
  });
57
66
 
67
+ /** Tests that reservedDocsTopicResourceUris matches docsMcpResources URIs. */
58
68
  test("reservedDocsTopicResourceUris matches docsMcpResources URIs", () => {
59
69
  const program = fixture();
60
70
  expect(reservedDocsTopicResourceUris(program)).toEqual(
@@ -1,3 +1,7 @@
1
+ /*
2
+ Tests for formats module behavior.
3
+ */
4
+
1
5
  import { expect, test } from "bun:test";
2
6
  import {
3
7
  parseCommaList,
@@ -8,6 +12,7 @@ import {
8
12
  } from "./formats.ts";
9
13
  import { CliValueFormat } from "./types.ts";
10
14
 
15
+ /** ParseDurationMs parses minutes and hours. */
11
16
  test("parseDurationMs parses minutes and hours", () => {
12
17
  expect(parseDurationMs("30s")).toBe(30_000);
13
18
  expect(parseDurationMs("5m")).toBe(5 * 60 * 1000);
@@ -15,21 +20,25 @@ test("parseDurationMs parses minutes and hours", () => {
15
20
  expect(parseDurationMs("1d")).toBe(24 * 60 * 60 * 1000);
16
21
  });
17
22
 
23
+ /** ParseCommaList splits and trims. */
18
24
  test("parseCommaList splits and trims", () => {
19
25
  expect(parseCommaList("a,b")).toEqual(["a", "b"]);
20
26
  expect(parseCommaList(" a , b , ")).toEqual(["a", "b"]);
21
27
  });
22
28
 
29
+ /** ParseDate validates calendar dates. */
23
30
  test("parseDate validates calendar dates", () => {
24
31
  expect(parseDate("2026-06-22")).toBe("2026-06-22");
25
32
  expect(() => parseDate("2026-02-30")).toThrow();
26
33
  });
27
34
 
35
+ /** ParseDateTime normalizes to UTC ISO. */
28
36
  test("parseDateTime normalizes to UTC ISO", () => {
29
37
  expect(parseDateTime("2026-06-22T15:00:00Z")).toBe("2026-06-22T15:00:00.000Z");
30
38
  expect(() => parseDateTime("2026-06-22")).toThrow();
31
39
  });
32
40
 
41
+ /** ValidateFormatValue rejects invalid duration. */
33
42
  test("validateFormatValue rejects invalid duration", () => {
34
43
  expect(() => validateFormatValue("nope", CliValueFormat.Duration)).toThrow();
35
44
  });
@@ -1,3 +1,7 @@
1
+ /*
2
+ Tests for headless module behavior.
3
+ */
4
+
1
5
  import { expect, test } from "bun:test";
2
6
  import {
3
7
  formatDryRunMessage,
@@ -8,12 +12,14 @@ import {
8
12
  wantsExplicitJson,
9
13
  } from "./headless.ts";
10
14
 
15
+ /** WantsExplicitJson includes MCP invocation. */
11
16
  test("wantsExplicitJson includes MCP invocation", () => {
12
17
  expect(wantsExplicitJson({ invocation: "cli" }, false)).toBe(false);
13
18
  expect(wantsExplicitJson({ invocation: "mcp" }, false)).toBe(true);
14
19
  expect(wantsExplicitJson({ invocation: "cli" }, true)).toBe(true);
15
20
  });
16
21
 
22
+ /** Tests that shouldRunHeadless is true for MCP and json. */
17
23
  test("shouldRunHeadless is true for MCP and json", () => {
18
24
  expect(shouldRunHeadless({ invocation: "mcp" }, false)).toBe(true);
19
25
  expect(shouldRunHeadless({ invocation: "cli" }, true)).toBe(true);
@@ -21,6 +27,7 @@ test("shouldRunHeadless is true for MCP and json", () => {
21
27
  expect(shouldRunHeadless({ invocation: "cli" }, false, false, false)).toBe(true);
22
28
  });
23
29
 
30
+ /** Tests that shouldRunHeadlessWithPositionals requires positionals in non-tty. */
24
31
  test("shouldRunHeadlessWithPositionals requires positionals in non-tty", () => {
25
32
  expect(shouldRunHeadlessWithPositionals({ invocation: "cli" }, false, [], false, false)).toBe(
26
33
  false,
@@ -30,6 +37,7 @@ test("shouldRunHeadlessWithPositionals requires positionals in non-tty", () => {
30
37
  );
31
38
  });
32
39
 
40
+ /** Tests that shouldRunHeadlessWithYes requires yes in non-tty. */
33
41
  test("shouldRunHeadlessWithYes requires yes in non-tty", () => {
34
42
  expect(
35
43
  shouldRunHeadlessWithYes({ invocation: "cli" }, { yes: true, hasRequiredArgs: true }, false),
@@ -46,11 +54,13 @@ test("shouldRunHeadlessWithYes requires yes in non-tty", () => {
46
54
  ).toBe(true);
47
55
  });
48
56
 
57
+ /** Tests that formatDryRunMessage prefixes dry-run output. */
49
58
  test("formatDryRunMessage prefixes dry-run output", () => {
50
59
  expect(formatDryRunMessage("hello", false)).toBe("hello");
51
60
  expect(formatDryRunMessage("hello", true)).toBe("[DRY RUN] hello");
52
61
  });
53
62
 
63
+ /** Tests that requireYesInNonTty exits without yes in non-tty. */
54
64
  test("requireYesInNonTty exits without yes in non-tty", () => {
55
65
  const originalExit = process.exit;
56
66
  let code: number | undefined;