@crouton-kit/crouter 0.3.8 → 0.3.12

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 (184) hide show
  1. package/bin/crtrd +2 -0
  2. package/dist/builtin-personas/design/base.md +9 -0
  3. package/dist/builtin-personas/design/orchestrator.md +10 -0
  4. package/dist/builtin-personas/developer/base.md +9 -0
  5. package/dist/builtin-personas/developer/orchestrator.md +12 -0
  6. package/dist/builtin-personas/explore/base.md +9 -0
  7. package/dist/builtin-personas/explore/orchestrator.md +9 -0
  8. package/dist/builtin-personas/general/base.md +5 -0
  9. package/dist/builtin-personas/general/orchestrator.md +7 -0
  10. package/dist/builtin-personas/orchestration-kernel.md +71 -0
  11. package/dist/builtin-personas/plan/base.md +7 -0
  12. package/dist/builtin-personas/plan/orchestrator.md +12 -0
  13. package/dist/builtin-personas/review/base.md +7 -0
  14. package/dist/builtin-personas/review/orchestrator.md +9 -0
  15. package/dist/builtin-personas/runtime-base.md +39 -0
  16. package/dist/builtin-personas/spec/base.md +7 -0
  17. package/dist/builtin-personas/spec/orchestrator.md +10 -0
  18. package/dist/builtin-skills/skills/design/SKILL.md +51 -0
  19. package/dist/builtin-skills/skills/development/SKILL.md +109 -0
  20. package/dist/builtin-skills/skills/planning/SKILL.md +59 -0
  21. package/dist/builtin-skills/skills/spec/SKILL.md +83 -0
  22. package/dist/cli.js +25 -27
  23. package/dist/commands/{job.d.ts → attention.d.ts} +1 -1
  24. package/dist/commands/attention.js +152 -0
  25. package/dist/commands/canvas.d.ts +2 -0
  26. package/dist/commands/canvas.js +35 -0
  27. package/dist/commands/{agent.d.ts → daemon.d.ts} +1 -1
  28. package/dist/commands/daemon.js +111 -0
  29. package/dist/commands/dashboard.d.ts +2 -0
  30. package/dist/commands/dashboard.js +65 -0
  31. package/dist/commands/human/prompts.d.ts +5 -0
  32. package/dist/commands/human/prompts.js +269 -0
  33. package/dist/commands/human/queue.d.ts +3 -0
  34. package/dist/commands/human/queue.js +133 -0
  35. package/dist/commands/human/shared.d.ts +43 -0
  36. package/dist/commands/human/shared.js +107 -0
  37. package/dist/commands/human.js +15 -427
  38. package/dist/commands/node.d.ts +2 -0
  39. package/dist/commands/node.js +354 -0
  40. package/dist/commands/pkg/market-inspect.d.ts +1 -0
  41. package/dist/commands/pkg/market-inspect.js +157 -0
  42. package/dist/commands/pkg/market-manage.d.ts +1 -0
  43. package/dist/commands/pkg/market-manage.js +316 -0
  44. package/dist/commands/pkg/market.d.ts +1 -0
  45. package/dist/commands/pkg/market.js +16 -0
  46. package/dist/commands/pkg/plugin-inspect.d.ts +1 -0
  47. package/dist/commands/pkg/plugin-inspect.js +142 -0
  48. package/dist/commands/pkg/plugin-manage.d.ts +1 -0
  49. package/dist/commands/pkg/plugin-manage.js +294 -0
  50. package/dist/commands/pkg/plugin.d.ts +1 -0
  51. package/dist/commands/pkg/plugin.js +16 -0
  52. package/dist/commands/pkg/shared.d.ts +5 -0
  53. package/dist/commands/pkg/shared.js +61 -0
  54. package/dist/commands/pkg.js +8 -1004
  55. package/dist/commands/push.d.ts +3 -0
  56. package/dist/commands/push.js +159 -0
  57. package/dist/commands/revive.d.ts +2 -0
  58. package/dist/commands/revive.js +64 -0
  59. package/dist/commands/skill/author.d.ts +3 -0
  60. package/dist/commands/skill/author.js +147 -0
  61. package/dist/commands/skill/find.d.ts +4 -0
  62. package/dist/commands/skill/find.js +254 -0
  63. package/dist/commands/skill/read.d.ts +1 -0
  64. package/dist/commands/skill/read.js +89 -0
  65. package/dist/commands/skill/shared.d.ts +19 -0
  66. package/dist/commands/skill/shared.js +207 -0
  67. package/dist/commands/skill/state.d.ts +3 -0
  68. package/dist/commands/skill/state.js +69 -0
  69. package/dist/commands/skill.js +12 -681
  70. package/dist/commands/sys/config.d.ts +1 -0
  71. package/dist/commands/sys/config.js +186 -0
  72. package/dist/commands/sys/doctor.d.ts +1 -0
  73. package/dist/commands/sys/doctor.js +369 -0
  74. package/dist/commands/sys/shared.d.ts +3 -0
  75. package/dist/commands/sys/shared.js +24 -0
  76. package/dist/commands/sys/update.d.ts +2 -0
  77. package/dist/commands/sys/update.js +114 -0
  78. package/dist/commands/sys.js +9 -694
  79. package/dist/core/__tests__/argv-parser.test.js +19 -1
  80. package/dist/core/__tests__/canvas-inbox-watcher.test.js +100 -0
  81. package/dist/core/__tests__/canvas.test.js +154 -0
  82. package/dist/core/__tests__/reset.test.js +105 -0
  83. package/dist/core/__tests__/resolver.test.js +69 -1
  84. package/dist/core/__tests__/unknown-path.test.d.ts +1 -0
  85. package/dist/core/__tests__/unknown-path.test.js +52 -0
  86. package/dist/core/bootstrap.d.ts +2 -0
  87. package/dist/core/bootstrap.js +66 -0
  88. package/dist/core/canvas/attention.d.ts +24 -0
  89. package/dist/core/canvas/attention.js +94 -0
  90. package/dist/core/canvas/canvas.d.ts +40 -0
  91. package/dist/core/canvas/canvas.js +210 -0
  92. package/dist/core/canvas/db.d.ts +7 -0
  93. package/dist/core/canvas/db.js +61 -0
  94. package/dist/core/canvas/index.d.ts +4 -0
  95. package/dist/core/canvas/index.js +6 -0
  96. package/dist/core/canvas/paths.d.ts +16 -0
  97. package/dist/core/canvas/paths.js +62 -0
  98. package/dist/core/canvas/render.d.ts +30 -0
  99. package/dist/core/canvas/render.js +186 -0
  100. package/dist/core/canvas/types.d.ts +87 -0
  101. package/dist/core/canvas/types.js +8 -0
  102. package/dist/core/command.d.ts +63 -2
  103. package/dist/core/command.js +97 -24
  104. package/dist/core/feed/feed.d.ts +43 -0
  105. package/dist/core/feed/feed.js +116 -0
  106. package/dist/core/feed/inbox.d.ts +50 -0
  107. package/dist/core/feed/inbox.js +124 -0
  108. package/dist/core/frontmatter.d.ts +10 -0
  109. package/dist/core/frontmatter.js +24 -9
  110. package/dist/core/help.d.ts +39 -8
  111. package/dist/core/help.js +69 -35
  112. package/dist/core/io.d.ts +15 -1
  113. package/dist/core/io.js +56 -6
  114. package/dist/core/personas/index.d.ts +12 -0
  115. package/dist/core/personas/index.js +10 -0
  116. package/dist/core/personas/loader.d.ts +44 -0
  117. package/dist/core/personas/loader.js +157 -0
  118. package/dist/core/personas/resolve.d.ts +36 -0
  119. package/dist/core/personas/resolve.js +110 -0
  120. package/dist/core/render.d.ts +11 -0
  121. package/dist/core/render.js +126 -0
  122. package/dist/core/resolver.d.ts +10 -0
  123. package/dist/core/resolver.js +160 -2
  124. package/dist/core/runtime/front-door.d.ts +10 -0
  125. package/dist/core/runtime/front-door.js +97 -0
  126. package/dist/core/runtime/kickoff.d.ts +23 -0
  127. package/dist/core/runtime/kickoff.js +134 -0
  128. package/dist/core/runtime/launch.d.ts +34 -0
  129. package/dist/core/runtime/launch.js +85 -0
  130. package/dist/core/runtime/nodes.d.ts +38 -0
  131. package/dist/core/runtime/nodes.js +95 -0
  132. package/dist/core/runtime/presence.d.ts +38 -0
  133. package/dist/core/runtime/presence.js +152 -0
  134. package/dist/core/runtime/promote.d.ts +30 -0
  135. package/dist/core/runtime/promote.js +105 -0
  136. package/dist/core/runtime/reset.d.ts +13 -0
  137. package/dist/core/runtime/reset.js +97 -0
  138. package/dist/core/runtime/revive.d.ts +26 -0
  139. package/dist/core/runtime/revive.js +89 -0
  140. package/dist/core/runtime/roadmap.d.ts +12 -0
  141. package/dist/core/runtime/roadmap.js +52 -0
  142. package/dist/core/runtime/spawn.d.ts +33 -0
  143. package/dist/core/runtime/spawn.js +118 -0
  144. package/dist/core/runtime/stop-guard.d.ts +18 -0
  145. package/dist/core/runtime/stop-guard.js +33 -0
  146. package/dist/core/runtime/tmux.d.ts +88 -0
  147. package/dist/core/runtime/tmux.js +198 -0
  148. package/dist/core/spawn.d.ts +17 -80
  149. package/dist/core/spawn.js +15 -219
  150. package/dist/daemon/crtrd-cli.d.ts +1 -0
  151. package/dist/daemon/crtrd-cli.js +4 -0
  152. package/dist/daemon/crtrd.d.ts +20 -0
  153. package/dist/daemon/crtrd.js +200 -0
  154. package/dist/daemon/manage.d.ts +17 -0
  155. package/dist/daemon/manage.js +57 -0
  156. package/dist/pi-extensions/canvas-inbox-watcher.d.ts +16 -0
  157. package/dist/pi-extensions/canvas-inbox-watcher.js +229 -0
  158. package/dist/pi-extensions/canvas-nav.d.ts +32 -0
  159. package/dist/pi-extensions/canvas-nav.js +536 -0
  160. package/dist/pi-extensions/canvas-stophook.d.ts +17 -0
  161. package/dist/pi-extensions/canvas-stophook.js +373 -0
  162. package/dist/types.d.ts +21 -0
  163. package/dist/types.js +3 -0
  164. package/package.json +6 -5
  165. package/dist/commands/agent.js +0 -384
  166. package/dist/commands/debug.d.ts +0 -3
  167. package/dist/commands/debug.js +0 -179
  168. package/dist/commands/job.js +0 -344
  169. package/dist/commands/plan.d.ts +0 -4
  170. package/dist/commands/plan.js +0 -309
  171. package/dist/commands/spec.d.ts +0 -3
  172. package/dist/commands/spec.js +0 -286
  173. package/dist/core/__tests__/flow-leaves.test.js +0 -248
  174. package/dist/core/__tests__/job.test.js +0 -310
  175. package/dist/core/__tests__/jobs.test.js +0 -66
  176. package/dist/core/jobs.d.ts +0 -101
  177. package/dist/core/jobs.js +0 -462
  178. package/dist/prompts/agent.d.ts +0 -18
  179. package/dist/prompts/agent.js +0 -153
  180. package/dist/prompts/debug.d.ts +0 -8
  181. package/dist/prompts/debug.js +0 -44
  182. /package/dist/core/__tests__/{flow-leaves.test.d.ts → canvas-inbox-watcher.test.d.ts} +0 -0
  183. /package/dist/core/__tests__/{job.test.d.ts → canvas.test.d.ts} +0 -0
  184. /package/dist/core/__tests__/{jobs.test.d.ts → reset.test.d.ts} +0 -0
@@ -1,310 +0,0 @@
1
- // Tests for the job subtree (monitoring) and agent new * (spawning) argv
2
- // migration. Exercises parseArgv against each leaf's param schema directly —
3
- // no subprocess, no tmux, no filesystem side-effects from the handler.
4
- //
5
- // Run with: node --import tsx/esm --test src/core/__tests__/job.test.ts
6
- import { test, describe } from 'node:test';
7
- import assert from 'node:assert/strict';
8
- import { parseArgv } from '../command.js';
9
- // ---------------------------------------------------------------------------
10
- // Param schemas extracted from each leaf (mirrors job.ts exactly)
11
- // ---------------------------------------------------------------------------
12
- const startPromptParams = [
13
- { kind: 'stdin', name: 'prompt', required: true, constraint: '' },
14
- { kind: 'flag', name: 'cwd', type: 'path', required: false, constraint: '' },
15
- ];
16
- const startForkParams = [
17
- { kind: 'flag', name: 'cwd', type: 'path', required: false, constraint: '' },
18
- ];
19
- const startPlannerParams = [
20
- { kind: 'positional', name: 'spec_path', type: 'path', required: true, constraint: '' },
21
- { kind: 'flag', name: 'cwd', type: 'path', required: false, constraint: '' },
22
- ];
23
- const startImplementerParams = [
24
- { kind: 'positional', name: 'plan_path', type: 'path', required: true, constraint: '' },
25
- { kind: 'flag', name: 'cwd', type: 'path', required: false, constraint: '' },
26
- ];
27
- const startReviewerParams = [
28
- { kind: 'positional', name: 'artifact_path', type: 'path', required: true, constraint: '' },
29
- { kind: 'flag', name: 'kind', type: 'enum', choices: ['plan', 'spec'], required: true, constraint: '' },
30
- { kind: 'flag', name: 'spec-path', type: 'path', required: false, constraint: '' },
31
- { kind: 'flag', name: 'cwd', type: 'path', required: false, constraint: '' },
32
- ];
33
- const readListParams = [
34
- { kind: 'flag', name: 'limit', type: 'int', required: false, default: 20, constraint: '' },
35
- { kind: 'flag', name: 'cursor', type: 'string', required: false, constraint: '' },
36
- ];
37
- const readStatusParams = [
38
- { kind: 'positional', name: 'job_id', type: 'string', required: true, constraint: '' },
39
- ];
40
- const readResultParams = [
41
- { kind: 'positional', name: 'job_id', type: 'string', required: true, constraint: '' },
42
- { kind: 'flag', name: 'wait', type: 'bool', required: false, constraint: '' },
43
- ];
44
- const readLogsParams = [
45
- { kind: 'positional', name: 'job_id', type: 'string', required: true, constraint: '' },
46
- { kind: 'flag', name: 'since', type: 'string', required: false, constraint: '' },
47
- { kind: 'flag', name: 'until', type: 'string', required: false, constraint: '' },
48
- { kind: 'flag', name: 'level', type: 'enum', choices: ['debug', 'info', 'warn', 'error'], required: false, default: 'info', constraint: '' },
49
- { kind: 'flag', name: 'follow', type: 'bool', required: false, constraint: '' },
50
- ];
51
- // NOTE: the real leaf also declares a `stdin` param for `body`. We omit it
52
- // from the test schema because parseArgv reads stdin to EOF whenever a stdin
53
- // param is declared — and under `node --test`, stdin is piped with no EOF, so
54
- // the call hangs forever. The body-required-on-status=done check lives in the
55
- // leaf's `run` handler, not in parseArgv, so the schema tests below cover
56
- // everything parseArgv can see without needing stdin.
57
- const submitParams = [
58
- { kind: 'positional', name: 'job_id', type: 'string', required: true, constraint: '' },
59
- { kind: 'flag', name: 'status', type: 'enum', choices: ['done', 'failed'], required: false, default: 'done', constraint: '' },
60
- { kind: 'flag', name: 'reason', type: 'string', required: false, constraint: '' },
61
- { kind: 'flag', name: 'kill-pane', type: 'bool', required: false, constraint: '' },
62
- ];
63
- const cancelParams = [
64
- { kind: 'positional', name: 'job_id', type: 'string', required: true, constraint: '' },
65
- ];
66
- const failParams = [
67
- { kind: 'positional', name: 'job_id', type: 'string', required: true, constraint: '' },
68
- ];
69
- // ---------------------------------------------------------------------------
70
- // agent new prompt (formerly job start prompt)
71
- // ---------------------------------------------------------------------------
72
- describe('agent new prompt', () => {
73
- // stdin is handled by readStdinRaw() which requires actual stdin — we only
74
- // test the non-stdin flag parsing here.
75
- test('--cwd flag parsed as string', async () => {
76
- // stdin param will be read but we can't pipe here — skip stdin assertion,
77
- // test that cwd parses correctly alongside other tokens.
78
- // We test the flag-only shape: no stdin params in isolation.
79
- const flagOnlyParams = [
80
- { kind: 'flag', name: 'cwd', type: 'path', required: false, constraint: '' },
81
- ];
82
- const result = await parseArgv(flagOnlyParams, ['--cwd', '/tmp/mydir']);
83
- assert.equal(result['cwd'], '/tmp/mydir');
84
- });
85
- test('cwd absent → undefined', async () => {
86
- const flagOnlyParams = [
87
- { kind: 'flag', name: 'cwd', type: 'path', required: false, constraint: '' },
88
- ];
89
- const result = await parseArgv(flagOnlyParams, []);
90
- assert.equal(result['cwd'], undefined);
91
- });
92
- });
93
- // ---------------------------------------------------------------------------
94
- // agent new fork (formerly job start fork)
95
- // ---------------------------------------------------------------------------
96
- describe('agent new fork', () => {
97
- test('no args parses cleanly', async () => {
98
- const result = await parseArgv(startForkParams, []);
99
- assert.equal(result['cwd'], undefined);
100
- });
101
- test('--cwd parsed', async () => {
102
- const result = await parseArgv(startForkParams, ['--cwd', '/workspace']);
103
- assert.equal(result['cwd'], '/workspace');
104
- });
105
- test('unknown positional rejected', async () => {
106
- await assert.rejects(() => parseArgv(startForkParams, ['extra-pos']), (err) => { assert.match(err.message, /takes no positional/); return true; });
107
- });
108
- });
109
- // ---------------------------------------------------------------------------
110
- // agent new planner (formerly job start planner)
111
- // ---------------------------------------------------------------------------
112
- describe('agent new planner', () => {
113
- test('positional spec_path required', async () => {
114
- await assert.rejects(() => parseArgv(startPlannerParams, []), (err) => { assert.match(err.message, /required parameter is missing/); return true; });
115
- });
116
- test('positional parsed as spec_path (camelCase: specPath? no — underscore stays as-is)', async () => {
117
- const result = await parseArgv(startPlannerParams, ['/tmp/spec.md']);
118
- // flagNameToKey converts kebab to camel; underscores are unaffected
119
- assert.equal(result['spec_path'], '/tmp/spec.md');
120
- });
121
- test('--cwd optional', async () => {
122
- const result = await parseArgv(startPlannerParams, ['/tmp/spec.md', '--cwd', '/src']);
123
- assert.equal(result['cwd'], '/src');
124
- assert.equal(result['spec_path'], '/tmp/spec.md');
125
- });
126
- });
127
- // ---------------------------------------------------------------------------
128
- // agent new implementer (formerly job start implementer)
129
- // ---------------------------------------------------------------------------
130
- describe('agent new implementer', () => {
131
- test('positional plan_path required', async () => {
132
- await assert.rejects(() => parseArgv(startImplementerParams, []), (err) => { assert.match(err.message, /required parameter is missing/); return true; });
133
- });
134
- test('positional parsed', async () => {
135
- const result = await parseArgv(startImplementerParams, ['/tmp/plan.md']);
136
- assert.equal(result['plan_path'], '/tmp/plan.md');
137
- });
138
- });
139
- // ---------------------------------------------------------------------------
140
- // agent new reviewer (formerly job start reviewer)
141
- // ---------------------------------------------------------------------------
142
- describe('agent new reviewer', () => {
143
- test('positional + --kind required', async () => {
144
- await assert.rejects(() => parseArgv(startReviewerParams, ['/tmp/artifact.md']), (err) => { assert.match(err.message, /required parameter is missing/); return true; });
145
- });
146
- test('valid kind: plan', async () => {
147
- const result = await parseArgv(startReviewerParams, ['/tmp/artifact.md', '--kind', 'plan']);
148
- assert.equal(result['artifact_path'], '/tmp/artifact.md');
149
- assert.equal(result['kind'], 'plan');
150
- });
151
- test('valid kind: spec', async () => {
152
- const result = await parseArgv(startReviewerParams, ['/tmp/artifact.md', '--kind', 'spec']);
153
- assert.equal(result['kind'], 'spec');
154
- });
155
- test('invalid kind throws invalid_type', async () => {
156
- await assert.rejects(() => parseArgv(startReviewerParams, ['/tmp/artifact.md', '--kind', 'bad']), (err) => { assert.match(err.message, /must be one of/); return true; });
157
- });
158
- test('--spec-path optional, becomes specPath', async () => {
159
- const result = await parseArgv(startReviewerParams, [
160
- '/tmp/artifact.md', '--kind', 'plan', '--spec-path', '/tmp/spec.md',
161
- ]);
162
- assert.equal(result['specPath'], '/tmp/spec.md');
163
- });
164
- });
165
- // ---------------------------------------------------------------------------
166
- // job read list
167
- // ---------------------------------------------------------------------------
168
- describe('job read list', () => {
169
- test('defaults: limit=20, cursor=undefined', async () => {
170
- const result = await parseArgv(readListParams, []);
171
- assert.equal(result['limit'], 20);
172
- assert.equal(result['cursor'], undefined);
173
- });
174
- test('--limit N parsed as int', async () => {
175
- const result = await parseArgv(readListParams, ['--limit', '50']);
176
- assert.equal(result['limit'], 50);
177
- });
178
- test('--limit with non-integer throws', async () => {
179
- await assert.rejects(() => parseArgv(readListParams, ['--limit', 'abc']), (err) => { assert.match(err.message, /must be an integer/); return true; });
180
- });
181
- test('--cursor opaque token', async () => {
182
- const result = await parseArgv(readListParams, ['--cursor', 'tok_abc123']);
183
- assert.equal(result['cursor'], 'tok_abc123');
184
- });
185
- test('--limit and --cursor together', async () => {
186
- const result = await parseArgv(readListParams, ['--limit', '5', '--cursor', 'next_page']);
187
- assert.equal(result['limit'], 5);
188
- assert.equal(result['cursor'], 'next_page');
189
- });
190
- });
191
- // ---------------------------------------------------------------------------
192
- // job read status
193
- // ---------------------------------------------------------------------------
194
- describe('job read status', () => {
195
- test('positional job_id required', async () => {
196
- await assert.rejects(() => parseArgv(readStatusParams, []), (err) => { assert.match(err.message, /required parameter is missing/); return true; });
197
- });
198
- test('positional job_id parsed', async () => {
199
- const result = await parseArgv(readStatusParams, ['job-abc-123']);
200
- assert.equal(result['job_id'], 'job-abc-123');
201
- });
202
- });
203
- // ---------------------------------------------------------------------------
204
- // job read result
205
- // ---------------------------------------------------------------------------
206
- describe('job read result', () => {
207
- test('positional job_id required', async () => {
208
- await assert.rejects(() => parseArgv(readResultParams, []), (err) => { assert.match(err.message, /required parameter is missing/); return true; });
209
- });
210
- test('positional job_id without --wait', async () => {
211
- const result = await parseArgv(readResultParams, ['job-xyz']);
212
- assert.equal(result['job_id'], 'job-xyz');
213
- assert.equal(result['wait'], false);
214
- });
215
- test('--wait presence = true', async () => {
216
- const result = await parseArgv(readResultParams, ['job-xyz', '--wait']);
217
- assert.equal(result['wait'], true);
218
- });
219
- test('--wait=value rejected (bool takes no value)', async () => {
220
- await assert.rejects(() => parseArgv(readResultParams, ['job-xyz', '--wait=true']), (err) => { assert.match(err.message, /takes no value/); return true; });
221
- });
222
- });
223
- // ---------------------------------------------------------------------------
224
- // job read logs
225
- // ---------------------------------------------------------------------------
226
- describe('job read logs', () => {
227
- test('positional job_id required', async () => {
228
- await assert.rejects(() => parseArgv(readLogsParams, []), (err) => { assert.match(err.message, /required parameter is missing/); return true; });
229
- });
230
- test('defaults: level=info, follow=false', async () => {
231
- const result = await parseArgv(readLogsParams, ['job-abc']);
232
- assert.equal(result['job_id'], 'job-abc');
233
- assert.equal(result['level'], 'info');
234
- assert.equal(result['follow'], false);
235
- });
236
- test('--follow presence = true', async () => {
237
- const result = await parseArgv(readLogsParams, ['job-abc', '--follow']);
238
- assert.equal(result['follow'], true);
239
- });
240
- test('--follow=value rejected', async () => {
241
- await assert.rejects(() => parseArgv(readLogsParams, ['job-abc', '--follow=true']), (err) => { assert.match(err.message, /takes no value/); return true; });
242
- });
243
- test('--level valid enum', async () => {
244
- const result = await parseArgv(readLogsParams, ['job-abc', '--level', 'debug']);
245
- assert.equal(result['level'], 'debug');
246
- });
247
- test('--level invalid enum throws', async () => {
248
- await assert.rejects(() => parseArgv(readLogsParams, ['job-abc', '--level', 'trace']), (err) => { assert.match(err.message, /must be one of/); return true; });
249
- });
250
- test('--since and --until optional strings', async () => {
251
- const result = await parseArgv(readLogsParams, [
252
- 'job-abc', '--since', '2025-01-01T00:00:00Z', '--until', '2025-01-02T00:00:00Z',
253
- ]);
254
- assert.equal(result['since'], '2025-01-01T00:00:00Z');
255
- assert.equal(result['until'], '2025-01-02T00:00:00Z');
256
- });
257
- });
258
- // ---------------------------------------------------------------------------
259
- // job submit
260
- // ---------------------------------------------------------------------------
261
- describe('job submit', () => {
262
- test('positional job_id + defaults: status=done, killPane=false', async () => {
263
- const result = await parseArgv(submitParams, ['job-abc']);
264
- assert.equal(result['job_id'], 'job-abc');
265
- assert.equal(result['status'], 'done');
266
- assert.equal(result['killPane'], false);
267
- });
268
- test('--status failed parsed', async () => {
269
- const result = await parseArgv(submitParams, ['job-abc', '--status', 'failed', '--reason', 'broken']);
270
- assert.equal(result['status'], 'failed');
271
- assert.equal(result['reason'], 'broken');
272
- });
273
- test('--status invalid enum throws', async () => {
274
- await assert.rejects(() => parseArgv(submitParams, ['job-abc', '--status', 'bogus']), (err) => { assert.match(err.message, /must be one of/); return true; });
275
- });
276
- test('--kill-pane presence = true, killPane key', async () => {
277
- const result = await parseArgv(submitParams, ['job-abc', '--kill-pane']);
278
- assert.equal(result['killPane'], true);
279
- });
280
- test('missing positional job_id throws missing_parameter', async () => {
281
- await assert.rejects(() => parseArgv(submitParams, []), (err) => { assert.match(err.message, /required parameter is missing/); return true; });
282
- });
283
- test('--context-file no longer accepted', async () => {
284
- await assert.rejects(() => parseArgv(submitParams, ['job-abc', '--context-file', '/tmp/anything']), (err) => { assert.match(err.message, /unknown flag: --context-file/); return true; });
285
- });
286
- });
287
- // ---------------------------------------------------------------------------
288
- // job cancel
289
- // ---------------------------------------------------------------------------
290
- describe('job cancel', () => {
291
- test('positional job_id required', async () => {
292
- await assert.rejects(() => parseArgv(cancelParams, []), (err) => { assert.match(err.message, /required parameter is missing/); return true; });
293
- });
294
- test('positional job_id parsed', async () => {
295
- const result = await parseArgv(cancelParams, ['job-abc-123']);
296
- assert.equal(result['job_id'], 'job-abc-123');
297
- });
298
- });
299
- // ---------------------------------------------------------------------------
300
- // job _fail
301
- // ---------------------------------------------------------------------------
302
- describe('job _fail', () => {
303
- test('positional job_id required', async () => {
304
- await assert.rejects(() => parseArgv(failParams, []), (err) => { assert.match(err.message, /required parameter is missing/); return true; });
305
- });
306
- test('positional job_id parsed', async () => {
307
- const result = await parseArgv(failParams, ['job-fail-001']);
308
- assert.equal(result['job_id'], 'job-fail-001');
309
- });
310
- });
@@ -1,66 +0,0 @@
1
- // Tests for jobs.ts result-file storage (markdown vs json paths).
2
- //
3
- // Run with: node --import tsx/esm --test src/core/__tests__/jobs.test.ts
4
- import { test, describe, before, after } from 'node:test';
5
- import assert from 'node:assert/strict';
6
- import { mkdirSync, rmSync, existsSync, readFileSync } from 'node:fs';
7
- import { tmpdir } from 'node:os';
8
- import { join } from 'node:path';
9
- import { createJob, writeResult, writeMarkdownResult, readResult, } from '../jobs.js';
10
- let stateDir;
11
- let origXdg;
12
- before(() => {
13
- stateDir = join(tmpdir(), `crtr-jobs-test-${Date.now()}`);
14
- mkdirSync(stateDir, { recursive: true });
15
- origXdg = process.env['XDG_STATE_HOME'];
16
- process.env['XDG_STATE_HOME'] = stateDir;
17
- });
18
- after(() => {
19
- if (origXdg === undefined) {
20
- delete process.env['XDG_STATE_HOME'];
21
- }
22
- else {
23
- process.env['XDG_STATE_HOME'] = origXdg;
24
- }
25
- rmSync(stateDir, { recursive: true, force: true });
26
- });
27
- describe('writeMarkdownResult + readResult round-trip', () => {
28
- test('done with body writes result.md, parses frontmatter back', async () => {
29
- const { jobId, dir } = createJob('prompt', { cwd: '/tmp' });
30
- const body = '**Summary:** all good\n\nMore details on the next line.\n';
31
- writeMarkdownResult(jobId, body, 'done');
32
- assert.ok(existsSync(join(dir, 'result.md')), 'result.md should exist');
33
- assert.ok(!existsSync(join(dir, 'result.json')), 'result.json should NOT exist on md path');
34
- const raw = readFileSync(join(dir, 'result.md'), 'utf8');
35
- assert.match(raw, /^---\nstatus: done\nwritten_at: \d{4}-\d{2}-\d{2}T/);
36
- assert.ok(raw.endsWith(body), 'body preserved at end of file');
37
- const r = await readResult(jobId, { waitMs: 0 });
38
- assert.equal(r.status, 'done');
39
- assert.equal(r.result_md, body);
40
- assert.equal(r.reason, undefined);
41
- assert.equal(r.result, undefined);
42
- });
43
- test('failed with reason writes reason into frontmatter and reads it back', async () => {
44
- const { jobId } = createJob('prompt', { cwd: '/tmp' });
45
- writeMarkdownResult(jobId, '', 'failed', 'broke: had "quoted" parts and a\nnewline');
46
- const r = await readResult(jobId, { waitMs: 0 });
47
- assert.equal(r.status, 'failed');
48
- assert.equal(r.result_md, '');
49
- assert.equal(r.reason, 'broke: had "quoted" parts and a\nnewline');
50
- });
51
- test('writeResult (JSON) writes result.json and read still works', async () => {
52
- const { jobId, dir } = createJob('prompt', { cwd: '/tmp' });
53
- writeResult(jobId, { feedback: 'approved', n: 3 }, 'done');
54
- assert.ok(existsSync(join(dir, 'result.json')));
55
- assert.ok(!existsSync(join(dir, 'result.md')));
56
- const r = await readResult(jobId, { waitMs: 0 });
57
- assert.equal(r.status, 'done');
58
- assert.deepEqual(r.result, { feedback: 'approved', n: 3 });
59
- assert.equal(r.result_md, undefined);
60
- });
61
- test('readResult with no result file and waitMs=0 returns timeout', async () => {
62
- const { jobId } = createJob('prompt', { cwd: '/tmp' });
63
- const r = await readResult(jobId, { waitMs: 0 });
64
- assert.equal(r.status, 'timeout');
65
- });
66
- });
@@ -1,101 +0,0 @@
1
- type TerminalStatus = 'done' | 'failed' | 'canceled';
2
- type JobState = 'live' | TerminalStatus;
3
- type LogLevel = 'debug' | 'info' | 'warn' | 'error';
4
- /**
5
- * Allocate a new job directory and write meta.json atomically.
6
- * Returns the job_id and the absolute directory path.
7
- */
8
- export declare function createJob(kind: string, opts: {
9
- cwd: string;
10
- pid?: number;
11
- }): {
12
- jobId: string;
13
- dir: string;
14
- };
15
- /**
16
- * Record the tmux pane hosting a detached worker so `cancelJob` can kill it.
17
- */
18
- export declare function recordJobPane(jobId: string, paneId: string): void;
19
- /**
20
- * Append one event line to log.jsonl. Does NOT throw if jobId doesn't exist —
21
- * a crashed writer should not further corrupt state; use a guard at the call site.
22
- */
23
- export declare function appendEvent(jobId: string, event: {
24
- level: LogLevel;
25
- event: string;
26
- message: string;
27
- data?: object;
28
- }): void;
29
- /**
30
- * Atomically write result.json (structured object) and update meta.json status.
31
- * Used by programmatic callers (human, sys) that produce object results.
32
- * The result file's appearance is the completion signal — never inferred from log content.
33
- */
34
- export declare function writeResult(jobId: string, result: object, terminalStatus: TerminalStatus): void;
35
- /**
36
- * Atomically write result.md (YAML frontmatter + markdown body) and update meta.json status.
37
- * Used by `crtr job submit` for agent-driven markdown results.
38
- */
39
- export declare function writeMarkdownResult(jobId: string, body: string, terminalStatus: TerminalStatus, reason?: string): void;
40
- /**
41
- * Read whichever result file exists (result.md or result.json). If neither
42
- * exists and waitMs is given, block via fs.watch until one appears or the
43
- * timeout elapses.
44
- *
45
- * Race safety: registers the watcher THEN re-stats. If a result file appeared
46
- * between the first stat and the watch registration, the re-stat catches it
47
- * before the watcher has a chance to miss it.
48
- *
49
- * Returns shape:
50
- * - JSON path: { status, result: object }
51
- * - Markdown path: { status, result_md: string, reason?: string }
52
- * - Timeout: { status: 'timeout' }
53
- */
54
- export interface ReadResultResponse {
55
- status: 'done' | 'failed' | 'canceled' | 'timeout';
56
- result?: object;
57
- result_md?: string;
58
- reason?: string;
59
- }
60
- export declare function readResult(jobId: string, opts?: {
61
- waitMs?: number;
62
- }): Promise<ReadResultResponse>;
63
- /**
64
- * Derive job state from meta.json, the result file, and the tail of log.jsonl.
65
- * If a pid is recorded, is not alive, and no result file exists → 'failed'.
66
- */
67
- export declare function jobStatus(jobId: string): {
68
- state: JobState;
69
- age_s: number;
70
- last_event: {
71
- event: string;
72
- ts: string;
73
- } | null;
74
- };
75
- /**
76
- * List all jobs sorted by created_at ascending. Pagination is applied by the
77
- * caller, not here.
78
- */
79
- export declare function listJobs(): {
80
- job_id: string;
81
- kind: string;
82
- state: JobState;
83
- created_at: string;
84
- }[];
85
- /**
86
- * Read and filter log events. Ordering preserved. sinceTs/untilTs are ISO8601
87
- * strings; minLevel filters by severity rank (inclusive).
88
- */
89
- export declare function readLog(jobId: string, opts?: {
90
- sinceTs?: string;
91
- untilTs?: string;
92
- minLevel?: LogLevel;
93
- }): object[];
94
- /**
95
- * Best-effort cancel: send SIGTERM to the recorded pid (if any), mark meta
96
- * canceled. Success means the signal was delivered, not that execution stopped.
97
- */
98
- export declare function cancelJob(jobId: string): {
99
- canceled: boolean;
100
- };
101
- export {};