@crouton-kit/crouter 0.2.6 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/dist/builtin-skills/skills/crouter-development/marketplaces/SKILL.md +9 -9
  2. package/dist/builtin-skills/skills/crouter-development/plugins/SKILL.md +19 -19
  3. package/dist/cli.js +42 -37
  4. package/dist/commands/__tests__/human.test.d.ts +1 -0
  5. package/dist/commands/__tests__/human.test.js +214 -0
  6. package/dist/commands/__tests__/skill.test.d.ts +1 -0
  7. package/dist/commands/__tests__/skill.test.js +287 -0
  8. package/dist/commands/debug.d.ts +3 -0
  9. package/dist/commands/debug.js +179 -0
  10. package/dist/commands/flow.d.ts +2 -0
  11. package/dist/commands/flow.js +24 -0
  12. package/dist/commands/human.d.ts +2 -0
  13. package/dist/commands/human.js +480 -0
  14. package/dist/commands/job.d.ts +2 -0
  15. package/dist/commands/job.js +669 -0
  16. package/dist/commands/pkg.d.ts +2 -0
  17. package/dist/commands/pkg.js +1021 -0
  18. package/dist/commands/plan.d.ts +4 -2
  19. package/dist/commands/plan.js +306 -22
  20. package/dist/commands/skill.d.ts +2 -2
  21. package/dist/commands/skill.js +607 -456
  22. package/dist/commands/spec.d.ts +3 -2
  23. package/dist/commands/spec.js +283 -10
  24. package/dist/commands/sys.d.ts +2 -0
  25. package/dist/commands/sys.js +712 -0
  26. package/dist/core/__tests__/argv-parser.test.d.ts +1 -0
  27. package/dist/core/__tests__/argv-parser.test.js +199 -0
  28. package/dist/core/__tests__/flow-leaves.test.d.ts +1 -0
  29. package/dist/core/__tests__/flow-leaves.test.js +248 -0
  30. package/dist/core/__tests__/job.test.d.ts +1 -0
  31. package/dist/core/__tests__/job.test.js +346 -0
  32. package/dist/core/__tests__/pkg.test.d.ts +1 -0
  33. package/dist/core/__tests__/pkg.test.js +218 -0
  34. package/dist/core/__tests__/sys.test.d.ts +1 -0
  35. package/dist/core/__tests__/sys.test.js +208 -0
  36. package/dist/core/artifact.d.ts +29 -18
  37. package/dist/core/artifact.js +78 -221
  38. package/dist/core/auto-update.js +11 -3
  39. package/dist/core/command.d.ts +36 -0
  40. package/dist/core/command.js +287 -0
  41. package/dist/core/errors.d.ts +3 -0
  42. package/dist/core/errors.js +5 -0
  43. package/dist/core/fs-utils.d.ts +1 -0
  44. package/dist/core/fs-utils.js +4 -0
  45. package/dist/core/help.d.ts +98 -0
  46. package/dist/core/help.js +163 -0
  47. package/dist/core/io.d.ts +29 -0
  48. package/dist/core/io.js +83 -0
  49. package/dist/core/jobs.d.ts +87 -0
  50. package/dist/core/jobs.js +353 -0
  51. package/dist/core/pagination.d.ts +33 -0
  52. package/dist/core/pagination.js +89 -0
  53. package/dist/core/self-update.d.ts +21 -0
  54. package/dist/{commands/update.js → core/self-update.js} +28 -63
  55. package/dist/core/spawn.d.ts +47 -65
  56. package/dist/core/spawn.js +78 -228
  57. package/dist/prompts/agent.d.ts +10 -5
  58. package/dist/prompts/agent.js +51 -74
  59. package/dist/prompts/debug.d.ts +8 -0
  60. package/dist/prompts/debug.js +37 -0
  61. package/dist/prompts/review.js +4 -11
  62. package/dist/prompts/skill.d.ts +0 -1
  63. package/dist/prompts/skill.js +95 -149
  64. package/package.json +4 -2
  65. package/dist/commands/agent.d.ts +0 -2
  66. package/dist/commands/agent.js +0 -265
  67. package/dist/commands/config.d.ts +0 -2
  68. package/dist/commands/config.js +0 -146
  69. package/dist/commands/doctor.d.ts +0 -2
  70. package/dist/commands/doctor.js +0 -268
  71. package/dist/commands/marketplace.d.ts +0 -2
  72. package/dist/commands/marketplace.js +0 -365
  73. package/dist/commands/plugin.d.ts +0 -2
  74. package/dist/commands/plugin.js +0 -367
  75. package/dist/commands/update.d.ts +0 -4
  76. package/dist/prompts/plan.d.ts +0 -1
  77. package/dist/prompts/plan.js +0 -175
  78. package/dist/prompts/spec.d.ts +0 -1
  79. package/dist/prompts/spec.js +0 -153
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,199 @@
1
+ // Framework tests for the argv parser and help renderer (inputModel: 'argv').
2
+ // Run with: node --import tsx/esm --test src/core/__tests__/argv-parser.test.ts
3
+ // These tests exercise parseArgv and renderLeafArgv directly — no process.argv
4
+ // mutation, no subprocess spawning.
5
+ import { test, describe } from 'node:test';
6
+ import assert from 'node:assert/strict';
7
+ import { parseArgv } from '../command.js';
8
+ import { renderLeafArgv } from '../help.js';
9
+ import { defineLeaf } from '../command.js';
10
+ // ---------------------------------------------------------------------------
11
+ // parseArgv — flags
12
+ // ---------------------------------------------------------------------------
13
+ describe('parseArgv: flags', () => {
14
+ const params = [
15
+ { kind: 'flag', name: 'plan-id', type: 'string', required: true, constraint: 'Plan id.' },
16
+ { kind: 'flag', name: 'limit', type: 'int', required: false, constraint: 'Max results.', default: 20 },
17
+ { kind: 'flag', name: 'follow', type: 'bool', required: false, constraint: 'Stream.' },
18
+ { kind: 'flag', name: 'status', type: 'enum', choices: ['draft', 'done'], required: false, constraint: 'Filter.' },
19
+ ];
20
+ test('parses --flag value form', async () => {
21
+ const result = await parseArgv(params, ['--plan-id', 'abc123']);
22
+ assert.equal(result['planId'], 'abc123');
23
+ });
24
+ test('parses --flag=value form', async () => {
25
+ const result = await parseArgv(params, ['--plan-id=abc123']);
26
+ assert.equal(result['planId'], 'abc123');
27
+ });
28
+ test('parses int flag', async () => {
29
+ const result = await parseArgv(params, ['--plan-id', 'x', '--limit', '42']);
30
+ assert.equal(result['limit'], 42);
31
+ });
32
+ test('rejects non-integer for int flag', async () => {
33
+ await assert.rejects(() => parseArgv(params, ['--plan-id', 'x', '--limit', 'notanint']), (err) => { assert.match(err.message, /must be an integer/); return true; });
34
+ });
35
+ test('bool flag: presence = true', async () => {
36
+ const result = await parseArgv(params, ['--plan-id', 'x', '--follow']);
37
+ assert.equal(result['follow'], true);
38
+ });
39
+ test('bool flag: absence = default false', async () => {
40
+ const result = await parseArgv(params, ['--plan-id', 'x']);
41
+ assert.equal(result['follow'], false);
42
+ });
43
+ test('bool flag rejects --flag=value', async () => {
44
+ await assert.rejects(() => parseArgv(params, ['--plan-id', 'x', '--follow=true']), (err) => { assert.match(err.message, /takes no value/); return true; });
45
+ });
46
+ test('enum flag: valid value passes', async () => {
47
+ const result = await parseArgv(params, ['--plan-id', 'x', '--status', 'draft']);
48
+ assert.equal(result['status'], 'draft');
49
+ });
50
+ test('enum flag: invalid value throws invalid_type', async () => {
51
+ await assert.rejects(() => parseArgv(params, ['--plan-id', 'x', '--status', 'unknown']), (err) => { assert.match(err.message, /must be one of/); return true; });
52
+ });
53
+ test('int flag default applied when absent', async () => {
54
+ const result = await parseArgv(params, ['--plan-id', 'x']);
55
+ assert.equal(result['limit'], 20);
56
+ });
57
+ test('unknown flag throws unknown_flag', async () => {
58
+ await assert.rejects(() => parseArgv(params, ['--plan-id', 'x', '--bogus', 'y']), (err) => { assert.match(err.message, /unknown flag/); return true; });
59
+ });
60
+ test('missing required flag throws missing_parameter', async () => {
61
+ await assert.rejects(() => parseArgv(params, []), (err) => { assert.match(err.message, /required parameter is missing/); return true; });
62
+ });
63
+ test('kebab-case flag name becomes camelCase key', async () => {
64
+ const result = await parseArgv(params, ['--plan-id', 'abc']);
65
+ assert.ok('planId' in result);
66
+ assert.ok(!('plan-id' in result));
67
+ });
68
+ });
69
+ // ---------------------------------------------------------------------------
70
+ // parseArgv — positional
71
+ // ---------------------------------------------------------------------------
72
+ describe('parseArgv: positional', () => {
73
+ const params = [
74
+ { kind: 'positional', name: 'job-id', required: true, constraint: 'Job id.' },
75
+ { kind: 'flag', name: 'follow', type: 'bool', required: false, constraint: '' },
76
+ ];
77
+ test('parses single positional before flags', async () => {
78
+ const result = await parseArgv(params, ['job-abc', '--follow']);
79
+ assert.equal(result['jobId'], 'job-abc');
80
+ assert.equal(result['follow'], true);
81
+ });
82
+ test('parses single positional after flags', async () => {
83
+ const result = await parseArgv(params, ['--follow', 'job-abc']);
84
+ assert.equal(result['jobId'], 'job-abc');
85
+ });
86
+ test('parses positional after bare --', async () => {
87
+ const result = await parseArgv(params, ['--', 'job-abc']);
88
+ assert.equal(result['jobId'], 'job-abc');
89
+ });
90
+ test('two positionals throws bad_invocation', async () => {
91
+ await assert.rejects(() => parseArgv(params, ['job-abc', 'extra']), (err) => { assert.match(err.message, /unexpected extra positional/); return true; });
92
+ });
93
+ test('missing required positional throws missing_parameter', async () => {
94
+ await assert.rejects(() => parseArgv(params, []), (err) => { assert.match(err.message, /required parameter is missing/); return true; });
95
+ });
96
+ test('positional when none declared throws bad_invocation', async () => {
97
+ const noPos = [
98
+ { kind: 'flag', name: 'name', type: 'string', required: false, constraint: '' },
99
+ ];
100
+ await assert.rejects(() => parseArgv(noPos, ['somevalue']), (err) => { assert.match(err.message, /takes no positional/); return true; });
101
+ });
102
+ });
103
+ // ---------------------------------------------------------------------------
104
+ // parseArgv — context-file
105
+ // ---------------------------------------------------------------------------
106
+ describe('parseArgv: context-file', () => {
107
+ test('unknown --context-file when not declared throws unknown_flag', async () => {
108
+ const params = [];
109
+ await assert.rejects(() => parseArgv(params, ['--context-file', '/some/path']), (err) => { assert.match(err.message, /unknown flag: --context-file/); return true; });
110
+ });
111
+ test('--context-file missing PATH throws missing_parameter', async () => {
112
+ const params = [
113
+ { kind: 'context-file', name: 'context', required: false, constraint: '' },
114
+ ];
115
+ await assert.rejects(() => parseArgv(params, ['--context-file']), (err) => { assert.match(err.message, /requires a PATH/); return true; });
116
+ });
117
+ test('--context-file with nonexistent file throws invalid_type', async () => {
118
+ const params = [
119
+ { kind: 'context-file', name: 'context', required: false, constraint: '' },
120
+ ];
121
+ await assert.rejects(() => parseArgv(params, ['--context-file', '/no/such/file/xyz.json']), (err) => { assert.match(err.message, /cannot read file/); return true; });
122
+ });
123
+ });
124
+ // ---------------------------------------------------------------------------
125
+ // renderLeafArgv — help format
126
+ // ---------------------------------------------------------------------------
127
+ describe('renderLeafArgv: help format', () => {
128
+ const help = {
129
+ name: 'task claim',
130
+ summary: 'claim a draft task and spawn a worker',
131
+ params: [
132
+ { kind: 'positional', name: 'task-id', required: true, constraint: 'Task in draft state.' },
133
+ { kind: 'flag', name: 'worker', type: 'string', required: false, constraint: 'Worker template.' },
134
+ { kind: 'flag', name: 'follow', type: 'bool', required: false, constraint: 'Stream output.' },
135
+ { kind: 'context-file', name: 'context', required: false, constraint: 'Extra facts for the worker.', shape: '{key: string}' },
136
+ ],
137
+ output: [
138
+ { name: 'job_id', type: 'string', required: true, constraint: 'Use with crtr job logs.' },
139
+ ],
140
+ outputKind: 'object',
141
+ effects: ['Marks task claimed.'],
142
+ };
143
+ test('starts with name: summary', () => {
144
+ const out = renderLeafArgv(help);
145
+ assert.ok(out.startsWith('task claim: claim a draft task and spawn a worker.'));
146
+ });
147
+ test('contains Input section', () => {
148
+ const out = renderLeafArgv(help);
149
+ assert.ok(out.includes('Input'));
150
+ });
151
+ test('positional shown as TASK_ID', () => {
152
+ const out = renderLeafArgv(help);
153
+ assert.ok(out.includes('TASK-ID'));
154
+ });
155
+ test('bool flag shown without VALUE placeholder', () => {
156
+ const out = renderLeafArgv(help);
157
+ assert.ok(out.includes('--follow'));
158
+ assert.ok(!out.includes('--follow FOLLOW'));
159
+ });
160
+ test('string flag shown with VALUE placeholder', () => {
161
+ const out = renderLeafArgv(help);
162
+ assert.ok(out.includes('--worker WORKER'));
163
+ });
164
+ test('context-file shown as --context-file PATH', () => {
165
+ const out = renderLeafArgv(help);
166
+ assert.ok(out.includes('--context-file PATH'));
167
+ });
168
+ test('contains Output section', () => {
169
+ const out = renderLeafArgv(help);
170
+ assert.ok(out.includes('Output (stdout, JSON)'));
171
+ });
172
+ test('contains Effects section', () => {
173
+ const out = renderLeafArgv(help);
174
+ assert.ok(out.includes('Effects'));
175
+ assert.ok(out.includes('Marks task claimed.'));
176
+ });
177
+ });
178
+ // ---------------------------------------------------------------------------
179
+ // defineLeaf
180
+ // ---------------------------------------------------------------------------
181
+ describe('defineLeaf', () => {
182
+ test('builds a leaf with the declared params', () => {
183
+ const leaf = defineLeaf({
184
+ name: 'test',
185
+ help: {
186
+ name: 'test',
187
+ summary: 'test leaf',
188
+ params: [],
189
+ output: [],
190
+ outputKind: 'object',
191
+ effects: [],
192
+ },
193
+ run: async () => { },
194
+ });
195
+ assert.equal(leaf.kind, 'leaf');
196
+ assert.equal(leaf.name, 'test');
197
+ assert.deepEqual(leaf.help.params, []);
198
+ });
199
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,248 @@
1
+ // Tests for flow subtree leaves (spec new/show/list, plan new/show/list, debug).
2
+ // Exercises the argv parsing path for each leaf via parseArgv and verifies
3
+ // help.params structure (not help.input).
4
+ //
5
+ // Run with: node --import tsx/esm --test src/core/__tests__/flow-leaves.test.ts
6
+ import { test, describe } from 'node:test';
7
+ import assert from 'node:assert/strict';
8
+ import { parseArgv } from '../command.js';
9
+ import { renderLeafArgv } from '../help.js';
10
+ import { registerSpec } from '../../commands/spec.js';
11
+ import { registerPlan } from '../../commands/plan.js';
12
+ import { registerDebug } from '../../commands/debug.js';
13
+ // ---------------------------------------------------------------------------
14
+ // Helpers
15
+ // ---------------------------------------------------------------------------
16
+ function getLeaf(branch, name) {
17
+ const leaf = branch.children.find((c) => c.name === name);
18
+ assert.ok(leaf !== undefined, `leaf '${name}' not found in branch`);
19
+ assert.equal(leaf.kind, 'leaf');
20
+ return leaf;
21
+ }
22
+ // ---------------------------------------------------------------------------
23
+ // spec new
24
+ // ---------------------------------------------------------------------------
25
+ describe('spec new: argv model', () => {
26
+ const specBranch = registerSpec();
27
+ const leaf = getLeaf(specBranch, 'new');
28
+ test('help.params defined', () => {
29
+ assert.ok(leaf.help.params !== undefined);
30
+ });
31
+ test('has positional name param', () => {
32
+ const pos = leaf.help.params.find((p) => p.kind === 'positional' && p.name === 'name');
33
+ assert.ok(pos !== undefined);
34
+ assert.equal(pos.required, true);
35
+ });
36
+ test('has stdin body param', () => {
37
+ const stdin = leaf.help.params.find((p) => p.kind === 'stdin' && p.name === 'body');
38
+ assert.ok(stdin !== undefined);
39
+ assert.equal(stdin.required, true);
40
+ });
41
+ test('parseArgv: positional name parsed correctly', async () => {
42
+ const params = leaf.help.params;
43
+ // Remove stdin param for parse-only test (stdin reads from process.stdin)
44
+ const noStdin = params.filter((p) => p.kind !== 'stdin');
45
+ const result = await parseArgv(noStdin, ['my-spec-name']);
46
+ assert.equal(result['name'], 'my-spec-name');
47
+ });
48
+ test('parseArgv: missing required positional throws', async () => {
49
+ const params = leaf.help.params.filter((p) => p.kind !== 'stdin');
50
+ await assert.rejects(() => parseArgv(params, []), (err) => { assert.match(err.message, /required parameter is missing/); return true; });
51
+ });
52
+ test('renderLeafArgv renders Input section with positional', () => {
53
+ const out = renderLeafArgv(leaf.help);
54
+ assert.ok(out.includes('Input'));
55
+ assert.ok(out.includes('NAME'));
56
+ assert.ok(out.includes('stdin'));
57
+ });
58
+ });
59
+ // ---------------------------------------------------------------------------
60
+ // spec show
61
+ // ---------------------------------------------------------------------------
62
+ describe('spec show: argv model', () => {
63
+ const specBranch = registerSpec();
64
+ const leaf = getLeaf(specBranch, 'show');
65
+ test('help.params defined', () => {
66
+ assert.ok(leaf.help.params !== undefined);
67
+ });
68
+ test('parseArgv: positional name', async () => {
69
+ const result = await parseArgv(leaf.help.params, ['my-spec']);
70
+ assert.equal(result['name'], 'my-spec');
71
+ });
72
+ test('parseArgv: rejects unknown flag', async () => {
73
+ await assert.rejects(() => parseArgv(leaf.help.params, ['my-spec', '--bogus']), (err) => { assert.match(err.message, /unknown flag/); return true; });
74
+ });
75
+ });
76
+ // ---------------------------------------------------------------------------
77
+ // spec list
78
+ // ---------------------------------------------------------------------------
79
+ describe('spec list: argv model', () => {
80
+ const specBranch = registerSpec();
81
+ const leaf = getLeaf(specBranch, 'list');
82
+ test('help.params defined', () => {
83
+ assert.ok(leaf.help.params !== undefined);
84
+ });
85
+ test('has --scope enum flag', () => {
86
+ const flag = leaf.help.params.find((p) => p.kind === 'flag' && p.name === 'scope');
87
+ assert.ok(flag !== undefined);
88
+ assert.equal(flag.kind, 'flag');
89
+ if (flag.kind === 'flag') {
90
+ assert.equal(flag.type, 'enum');
91
+ assert.deepEqual(flag.choices, ['user', 'project', 'all']);
92
+ }
93
+ });
94
+ test('has --limit int flag with default 20', () => {
95
+ const flag = leaf.help.params.find((p) => p.kind === 'flag' && p.name === 'limit');
96
+ assert.ok(flag !== undefined);
97
+ assert.equal(flag.kind, 'flag');
98
+ if (flag.kind === 'flag') {
99
+ assert.equal(flag.type, 'int');
100
+ assert.equal(flag.default, 20);
101
+ }
102
+ });
103
+ test('has --cursor string flag', () => {
104
+ const flag = leaf.help.params.find((p) => p.kind === 'flag' && p.name === 'cursor');
105
+ assert.ok(flag !== undefined);
106
+ });
107
+ test('parseArgv: --limit 50 parses as integer', async () => {
108
+ const result = await parseArgv(leaf.help.params, ['--limit', '50']);
109
+ assert.equal(result['limit'], 50);
110
+ });
111
+ test('parseArgv: --scope user parses', async () => {
112
+ const result = await parseArgv(leaf.help.params, ['--scope', 'user']);
113
+ assert.equal(result['scope'], 'user');
114
+ });
115
+ test('parseArgv: --scope invalid throws', async () => {
116
+ await assert.rejects(() => parseArgv(leaf.help.params, ['--scope', 'invalid']), (err) => { assert.match(err.message, /must be one of/); return true; });
117
+ });
118
+ test('parseArgv: default limit applied when absent', async () => {
119
+ const result = await parseArgv(leaf.help.params, []);
120
+ assert.equal(result['limit'], 20);
121
+ });
122
+ test('parseArgv: --cursor token', async () => {
123
+ const result = await parseArgv(leaf.help.params, ['--cursor', 'tok123']);
124
+ assert.equal(result['cursor'], 'tok123');
125
+ });
126
+ });
127
+ // ---------------------------------------------------------------------------
128
+ // plan new
129
+ // ---------------------------------------------------------------------------
130
+ describe('plan new: argv model', () => {
131
+ const planBranch = registerPlan();
132
+ const leaf = getLeaf(planBranch, 'new');
133
+ test('help.params defined', () => {
134
+ assert.ok(leaf.help.params !== undefined);
135
+ });
136
+ test('has positional name param', () => {
137
+ const pos = leaf.help.params.find((p) => p.kind === 'positional' && p.name === 'name');
138
+ assert.ok(pos !== undefined);
139
+ assert.equal(pos.required, true);
140
+ });
141
+ test('has stdin body param', () => {
142
+ const stdin = leaf.help.params.find((p) => p.kind === 'stdin' && p.name === 'body');
143
+ assert.ok(stdin !== undefined);
144
+ assert.equal(stdin.required, true);
145
+ });
146
+ test('has optional --spec flag', () => {
147
+ const flag = leaf.help.params.find((p) => p.kind === 'flag' && p.name === 'spec');
148
+ assert.ok(flag !== undefined);
149
+ assert.equal(flag.required, false);
150
+ });
151
+ test('parseArgv: positional name and --spec flag', async () => {
152
+ const params = leaf.help.params.filter((p) => p.kind !== 'stdin');
153
+ const result = await parseArgv(params, ['my-plan', '--spec', 'my-spec']);
154
+ assert.equal(result['name'], 'my-plan');
155
+ assert.equal(result['spec'], 'my-spec');
156
+ });
157
+ test('parseArgv: positional name without --spec', async () => {
158
+ const params = leaf.help.params.filter((p) => p.kind !== 'stdin');
159
+ const result = await parseArgv(params, ['my-plan']);
160
+ assert.equal(result['name'], 'my-plan');
161
+ assert.equal(result['spec'], undefined);
162
+ });
163
+ });
164
+ // ---------------------------------------------------------------------------
165
+ // plan show
166
+ // ---------------------------------------------------------------------------
167
+ describe('plan show: argv model', () => {
168
+ const planBranch = registerPlan();
169
+ const leaf = getLeaf(planBranch, 'show');
170
+ test('help.params defined', () => {
171
+ assert.ok(leaf.help.params !== undefined);
172
+ });
173
+ test('parseArgv: positional name', async () => {
174
+ const result = await parseArgv(leaf.help.params, ['my-plan']);
175
+ assert.equal(result['name'], 'my-plan');
176
+ });
177
+ });
178
+ // ---------------------------------------------------------------------------
179
+ // plan list
180
+ // ---------------------------------------------------------------------------
181
+ describe('plan list: argv model', () => {
182
+ const planBranch = registerPlan();
183
+ const leaf = getLeaf(planBranch, 'list');
184
+ test('help.params defined', () => {
185
+ assert.ok(leaf.help.params !== undefined);
186
+ });
187
+ test('has --scope, --limit, --cursor flags', () => {
188
+ const names = leaf.help.params.map((p) => p.name);
189
+ assert.ok(names.includes('scope'));
190
+ assert.ok(names.includes('limit'));
191
+ assert.ok(names.includes('cursor'));
192
+ });
193
+ test('parseArgv: --limit 10 --cursor abc', async () => {
194
+ const result = await parseArgv(leaf.help.params, ['--limit', '10', '--cursor', 'abc']);
195
+ assert.equal(result['limit'], 10);
196
+ assert.equal(result['cursor'], 'abc');
197
+ });
198
+ });
199
+ // ---------------------------------------------------------------------------
200
+ // debug
201
+ // ---------------------------------------------------------------------------
202
+ describe('debug: argv model', () => {
203
+ const leaf = registerDebug();
204
+ test('help.params defined', () => {
205
+ assert.ok(leaf.help.params !== undefined);
206
+ });
207
+ test('has stdin steps_to_reproduce param', () => {
208
+ const stdin = leaf.help.params.find((p) => p.kind === 'stdin' && p.name === 'steps_to_reproduce');
209
+ assert.ok(stdin !== undefined);
210
+ assert.equal(stdin.required, true);
211
+ });
212
+ test('has required --summary flag', () => {
213
+ const flag = leaf.help.params.find((p) => p.kind === 'flag' && p.name === 'summary');
214
+ assert.ok(flag !== undefined);
215
+ assert.equal(flag.required, true);
216
+ if (flag.kind === 'flag')
217
+ assert.equal(flag.type, 'string');
218
+ });
219
+ test('has optional --cwd flag of type path', () => {
220
+ const flag = leaf.help.params.find((p) => p.kind === 'flag' && p.name === 'cwd');
221
+ assert.ok(flag !== undefined);
222
+ assert.equal(flag.required, false);
223
+ if (flag.kind === 'flag')
224
+ assert.equal(flag.type, 'path');
225
+ });
226
+ test('parseArgv: --summary and --cwd flags (no stdin)', async () => {
227
+ const params = leaf.help.params.filter((p) => p.kind !== 'stdin');
228
+ const result = await parseArgv(params, ['--summary', 'test fails on startup', '--cwd', '/tmp/project']);
229
+ assert.equal(result['summary'], 'test fails on startup');
230
+ assert.equal(result['cwd'], '/tmp/project');
231
+ });
232
+ test('parseArgv: missing required --summary throws', async () => {
233
+ const params = leaf.help.params.filter((p) => p.kind !== 'stdin');
234
+ await assert.rejects(() => parseArgv(params, []), (err) => { assert.match(err.message, /required parameter is missing/); return true; });
235
+ });
236
+ test('renderLeafArgv renders --summary and stdin', () => {
237
+ const out = renderLeafArgv(leaf.help);
238
+ assert.ok(out.includes('--summary'));
239
+ assert.ok(out.includes('stdin'));
240
+ assert.ok(out.includes('--cwd'));
241
+ });
242
+ test('stdin name is steps_to_reproduce (underscores, no camelCase transform)', () => {
243
+ // flagNameToKey only converts hyphen segments; underscores are passed through.
244
+ // The handler reads input['steps_to_reproduce'], not input['stepsToReproduce'].
245
+ const stdin = leaf.help.params.find((p) => p.kind === 'stdin');
246
+ assert.equal(stdin?.name, 'steps_to_reproduce');
247
+ });
248
+ });
@@ -0,0 +1 @@
1
+ export {};