@crouton-kit/crouter 0.3.11 → 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.
- package/bin/crtrd +2 -0
- package/dist/builtin-personas/design/base.md +9 -0
- package/dist/builtin-personas/design/orchestrator.md +10 -0
- package/dist/builtin-personas/developer/base.md +9 -0
- package/dist/builtin-personas/developer/orchestrator.md +12 -0
- package/dist/builtin-personas/explore/base.md +9 -0
- package/dist/builtin-personas/explore/orchestrator.md +9 -0
- package/dist/builtin-personas/general/base.md +5 -0
- package/dist/builtin-personas/general/orchestrator.md +7 -0
- package/dist/builtin-personas/orchestration-kernel.md +71 -0
- package/dist/builtin-personas/plan/base.md +7 -0
- package/dist/builtin-personas/plan/orchestrator.md +12 -0
- package/dist/builtin-personas/review/base.md +7 -0
- package/dist/builtin-personas/review/orchestrator.md +9 -0
- package/dist/builtin-personas/runtime-base.md +39 -0
- package/dist/builtin-personas/spec/base.md +7 -0
- package/dist/builtin-personas/spec/orchestrator.md +10 -0
- package/dist/builtin-skills/skills/design/SKILL.md +51 -0
- package/dist/builtin-skills/skills/development/SKILL.md +109 -0
- package/dist/builtin-skills/skills/planning/SKILL.md +59 -0
- package/dist/builtin-skills/skills/spec/SKILL.md +83 -0
- package/dist/cli.js +14 -6
- package/dist/commands/{mode.d.ts → attention.d.ts} +1 -1
- package/dist/commands/attention.js +152 -0
- package/dist/commands/canvas.d.ts +2 -0
- package/dist/commands/canvas.js +35 -0
- package/dist/commands/daemon.d.ts +2 -0
- package/dist/commands/daemon.js +111 -0
- package/dist/commands/dashboard.d.ts +2 -0
- package/dist/commands/dashboard.js +65 -0
- package/dist/commands/human/prompts.d.ts +5 -0
- package/dist/commands/human/prompts.js +269 -0
- package/dist/commands/human/queue.d.ts +3 -0
- package/dist/commands/human/queue.js +133 -0
- package/dist/commands/human/shared.d.ts +43 -0
- package/dist/commands/human/shared.js +107 -0
- package/dist/commands/human.js +10 -454
- package/dist/commands/node.d.ts +2 -0
- package/dist/commands/node.js +354 -0
- package/dist/commands/pkg/market-inspect.d.ts +1 -0
- package/dist/commands/pkg/market-inspect.js +157 -0
- package/dist/commands/pkg/market-manage.d.ts +1 -0
- package/dist/commands/pkg/market-manage.js +316 -0
- package/dist/commands/pkg/market.d.ts +1 -0
- package/dist/commands/pkg/market.js +16 -0
- package/dist/commands/pkg/plugin-inspect.d.ts +1 -0
- package/dist/commands/pkg/plugin-inspect.js +142 -0
- package/dist/commands/pkg/plugin-manage.d.ts +1 -0
- package/dist/commands/pkg/plugin-manage.js +294 -0
- package/dist/commands/pkg/plugin.d.ts +1 -0
- package/dist/commands/pkg/plugin.js +16 -0
- package/dist/commands/pkg/shared.d.ts +5 -0
- package/dist/commands/pkg/shared.js +61 -0
- package/dist/commands/pkg.js +3 -1004
- package/dist/commands/push.d.ts +3 -0
- package/dist/commands/push.js +159 -0
- package/dist/commands/revive.d.ts +2 -0
- package/dist/commands/revive.js +64 -0
- package/dist/commands/skill/author.d.ts +3 -0
- package/dist/commands/skill/author.js +147 -0
- package/dist/commands/skill/find.d.ts +4 -0
- package/dist/commands/skill/find.js +254 -0
- package/dist/commands/skill/read.d.ts +1 -0
- package/dist/commands/skill/read.js +89 -0
- package/dist/commands/skill/shared.d.ts +19 -0
- package/dist/commands/skill/shared.js +207 -0
- package/dist/commands/skill/state.d.ts +3 -0
- package/dist/commands/skill/state.js +69 -0
- package/dist/commands/skill.js +6 -691
- package/dist/commands/sys/config.d.ts +1 -0
- package/dist/commands/sys/config.js +186 -0
- package/dist/commands/sys/doctor.d.ts +1 -0
- package/dist/commands/sys/doctor.js +369 -0
- package/dist/commands/sys/shared.d.ts +3 -0
- package/dist/commands/sys/shared.js +24 -0
- package/dist/commands/sys/update.d.ts +2 -0
- package/dist/commands/sys/update.js +114 -0
- package/dist/commands/sys.js +4 -694
- package/dist/core/__tests__/argv-parser.test.js +19 -1
- package/dist/core/__tests__/canvas-inbox-watcher.test.js +100 -0
- package/dist/core/__tests__/canvas.test.js +154 -0
- package/dist/core/__tests__/reset.test.js +105 -0
- package/dist/core/canvas/attention.d.ts +24 -0
- package/dist/core/canvas/attention.js +94 -0
- package/dist/core/canvas/canvas.d.ts +40 -0
- package/dist/core/canvas/canvas.js +210 -0
- package/dist/core/canvas/db.d.ts +7 -0
- package/dist/core/canvas/db.js +61 -0
- package/dist/core/canvas/index.d.ts +4 -0
- package/dist/core/canvas/index.js +6 -0
- package/dist/core/canvas/paths.d.ts +16 -0
- package/dist/core/canvas/paths.js +62 -0
- package/dist/core/canvas/render.d.ts +30 -0
- package/dist/core/canvas/render.js +186 -0
- package/dist/core/canvas/types.d.ts +87 -0
- package/dist/core/canvas/types.js +8 -0
- package/dist/core/command.d.ts +5 -0
- package/dist/core/command.js +35 -10
- package/dist/core/feed/feed.d.ts +43 -0
- package/dist/core/feed/feed.js +116 -0
- package/dist/core/feed/inbox.d.ts +50 -0
- package/dist/core/feed/inbox.js +124 -0
- package/dist/core/help.js +5 -3
- package/dist/core/io.d.ts +15 -1
- package/dist/core/io.js +56 -6
- package/dist/core/personas/index.d.ts +12 -0
- package/dist/core/personas/index.js +10 -0
- package/dist/core/personas/loader.d.ts +44 -0
- package/dist/core/personas/loader.js +157 -0
- package/dist/core/personas/resolve.d.ts +36 -0
- package/dist/core/personas/resolve.js +110 -0
- package/dist/core/render.d.ts +11 -0
- package/dist/core/render.js +126 -0
- package/dist/core/resolver.d.ts +10 -0
- package/dist/core/resolver.js +109 -1
- package/dist/core/runtime/front-door.d.ts +10 -0
- package/dist/core/runtime/front-door.js +97 -0
- package/dist/core/runtime/kickoff.d.ts +23 -0
- package/dist/core/runtime/kickoff.js +134 -0
- package/dist/core/runtime/launch.d.ts +34 -0
- package/dist/core/runtime/launch.js +85 -0
- package/dist/core/runtime/nodes.d.ts +38 -0
- package/dist/core/runtime/nodes.js +95 -0
- package/dist/core/runtime/presence.d.ts +38 -0
- package/dist/core/runtime/presence.js +152 -0
- package/dist/core/runtime/promote.d.ts +30 -0
- package/dist/core/runtime/promote.js +105 -0
- package/dist/core/runtime/reset.d.ts +13 -0
- package/dist/core/runtime/reset.js +97 -0
- package/dist/core/runtime/revive.d.ts +26 -0
- package/dist/core/runtime/revive.js +89 -0
- package/dist/core/runtime/roadmap.d.ts +12 -0
- package/dist/core/runtime/roadmap.js +52 -0
- package/dist/core/runtime/spawn.d.ts +33 -0
- package/dist/core/runtime/spawn.js +118 -0
- package/dist/core/runtime/stop-guard.d.ts +18 -0
- package/dist/core/runtime/stop-guard.js +33 -0
- package/dist/core/runtime/tmux.d.ts +88 -0
- package/dist/core/runtime/tmux.js +198 -0
- package/dist/core/spawn.d.ts +17 -197
- package/dist/core/spawn.js +16 -539
- package/dist/daemon/crtrd-cli.js +4 -0
- package/dist/daemon/crtrd.d.ts +20 -0
- package/dist/daemon/crtrd.js +200 -0
- package/dist/daemon/manage.d.ts +17 -0
- package/dist/daemon/manage.js +57 -0
- package/dist/pi-extensions/canvas-inbox-watcher.d.ts +16 -0
- package/dist/pi-extensions/canvas-inbox-watcher.js +229 -0
- package/dist/pi-extensions/canvas-nav.d.ts +32 -0
- package/dist/pi-extensions/canvas-nav.js +536 -0
- package/dist/pi-extensions/canvas-stophook.d.ts +17 -0
- package/dist/pi-extensions/canvas-stophook.js +373 -0
- package/package.json +6 -5
- package/dist/commands/agent.d.ts +0 -6
- package/dist/commands/agent.js +0 -585
- package/dist/commands/debug.d.ts +0 -3
- package/dist/commands/debug.js +0 -192
- package/dist/commands/job.d.ts +0 -11
- package/dist/commands/job.js +0 -384
- package/dist/commands/mode.js +0 -231
- package/dist/commands/plan.d.ts +0 -4
- package/dist/commands/plan.js +0 -322
- package/dist/commands/spec.d.ts +0 -3
- package/dist/commands/spec.js +0 -299
- package/dist/core/__tests__/flow-leaves.test.js +0 -248
- package/dist/core/__tests__/job.test.js +0 -310
- package/dist/core/__tests__/jobs.test.js +0 -98
- package/dist/core/__tests__/spawn.test.js +0 -138
- package/dist/core/__tests__/subagents.test.d.ts +0 -1
- package/dist/core/__tests__/subagents.test.js +0 -75
- package/dist/core/jobs.d.ts +0 -107
- package/dist/core/jobs.js +0 -565
- package/dist/core/subagents.d.ts +0 -18
- package/dist/core/subagents.js +0 -163
- package/dist/prompts/agent.d.ts +0 -27
- package/dist/prompts/agent.js +0 -184
- package/dist/prompts/debug.d.ts +0 -8
- package/dist/prompts/debug.js +0 -44
- /package/dist/core/__tests__/{flow-leaves.test.d.ts → canvas-inbox-watcher.test.d.ts} +0 -0
- /package/dist/core/__tests__/{job.test.d.ts → canvas.test.d.ts} +0 -0
- /package/dist/core/__tests__/{jobs.test.d.ts → reset.test.d.ts} +0 -0
- /package/dist/{core/__tests__/spawn.test.d.ts → daemon/crtrd-cli.d.ts} +0 -0
package/dist/commands/spec.js
DELETED
|
@@ -1,299 +0,0 @@
|
|
|
1
|
-
// `crtr mode spec` subtree — spec new / show / list handlers.
|
|
2
|
-
export const SPEC_NEW_GUIDE = `## Spec workflow
|
|
3
|
-
|
|
4
|
-
Build and save a design + requirements spec: a document describing what to
|
|
5
|
-
build, the shape of the solution, and the behaviors it must satisfy. A spec is
|
|
6
|
-
upstream of a plan — it captures decisions, not implementation steps.
|
|
7
|
-
|
|
8
|
-
Anti-pattern: do not fish for clarifications upfront. Draft a concrete spec
|
|
9
|
-
first based on your investigation, then iterate. A specific draft the user can
|
|
10
|
-
react to converges faster than a list of questions in a vacuum.
|
|
11
|
-
|
|
12
|
-
### Phase 1: Shape
|
|
13
|
-
|
|
14
|
-
Build a comprehensive picture of the problem and the relevant code. Surface
|
|
15
|
-
existing patterns, constraints, and prior decisions.
|
|
16
|
-
|
|
17
|
-
Launch up to 3 Explore subagents IN PARALLEL (single message, multiple tool
|
|
18
|
-
calls). Use 1 agent for narrow, well-scoped problems; use more when the spec
|
|
19
|
-
touches several subsystems or you need to compare existing implementations.
|
|
20
|
-
Quality over quantity — 3 agents maximum.
|
|
21
|
-
|
|
22
|
-
After exploration, draft a high-level design: the shape of the solution, new or
|
|
23
|
-
changed pieces, the boundaries.
|
|
24
|
-
|
|
25
|
-
### Phase 2: Requirements
|
|
26
|
-
|
|
27
|
-
Translate the shape into concrete behavioral requirements. Each requirement
|
|
28
|
-
must be:
|
|
29
|
-
|
|
30
|
-
- Testable — has a clear pass/fail condition.
|
|
31
|
-
- Behavior-focused — describes what the system does, not how.
|
|
32
|
-
- Scoped — covers one observable behavior.
|
|
33
|
-
|
|
34
|
-
Group requirements by capability. Plain English is fine. For conditional or
|
|
35
|
-
stateful behaviors, EARS templates sharpen phrasing:
|
|
36
|
-
\`When <trigger>, the system shall <behavior>\` or
|
|
37
|
-
\`If <condition>, then the system shall <response>\`.
|
|
38
|
-
|
|
39
|
-
For larger / multi-component designs, walk the design end-to-end: at each step
|
|
40
|
-
from trigger to final state, verify preconditions, state, failure handling, and
|
|
41
|
-
handoffs between components are specified. Skip this for small self-contained
|
|
42
|
-
specs.
|
|
43
|
-
|
|
44
|
-
### Phase 3: Deepen
|
|
45
|
-
|
|
46
|
-
Read the critical files identified during Phase 1. Reconcile requirements
|
|
47
|
-
against the shape — if a requirement reveals a gap in the design, refine the
|
|
48
|
-
design before saving.
|
|
49
|
-
|
|
50
|
-
Use AskUserQuestion ONLY to clarify requirements or choose between approaches.
|
|
51
|
-
Never use it to ask "is this spec okay?" or "should I save?".
|
|
52
|
-
|
|
53
|
-
### Phase 4: Compose the spec body
|
|
54
|
-
|
|
55
|
-
Required sections:
|
|
56
|
-
|
|
57
|
-
# Spec: <one-line title>
|
|
58
|
-
|
|
59
|
-
## Context
|
|
60
|
-
<the problem this spec addresses, what motivates it, and the intended outcome.
|
|
61
|
-
Include relevant constraints — user goals, stakeholders, deadlines.>
|
|
62
|
-
|
|
63
|
-
## Design
|
|
64
|
-
<the shape of the solution. Components, data flow, key decisions and why they
|
|
65
|
-
were chosen. Reference existing code with \`file_path:line_number\`.>
|
|
66
|
-
|
|
67
|
-
## Requirements
|
|
68
|
-
<grouped behavioral requirements. Each one testable. Plain English is fine.>
|
|
69
|
-
|
|
70
|
-
### <Capability A>
|
|
71
|
-
- <one observable behavior>
|
|
72
|
-
|
|
73
|
-
### <Capability B>
|
|
74
|
-
- ...
|
|
75
|
-
|
|
76
|
-
## Out of scope
|
|
77
|
-
<things explicitly NOT covered, so the next reader knows where the edges are.>
|
|
78
|
-
|
|
79
|
-
## Open questions
|
|
80
|
-
<anything you could not resolve. Empty if all decisions are pinned.>
|
|
81
|
-
|
|
82
|
-
### Phase 5: Save
|
|
83
|
-
|
|
84
|
-
Run \`crtr mode spec new\`:
|
|
85
|
-
|
|
86
|
-
echo '<spec markdown>' | crtr mode spec new <kebab-case-name>
|
|
87
|
-
|
|
88
|
-
- NAME: short kebab-case slug. Nested names become subdirectories
|
|
89
|
-
(e.g. \`auth/refresh-tokens\`).
|
|
90
|
-
- Pipe the full spec markdown composed in Phase 4 on stdin.
|
|
91
|
-
|
|
92
|
-
Output: \`{path, follow_up}\`. The \`follow_up\` field names the exact next call
|
|
93
|
-
— run it.
|
|
94
|
-
|
|
95
|
-
### Phase 6: Done
|
|
96
|
-
|
|
97
|
-
After the reviewer approves the spec, your turn ends. Do not summarize in chat.
|
|
98
|
-
For a human gate, optionally run \`crtr human review\` on the spec for anchored
|
|
99
|
-
comments and \`crtr human approve\` to gate the handoff — this complements, not
|
|
100
|
-
replaces, \`crtr mode reviewer\`.
|
|
101
|
-
If the user is ready to plan, ask once whether to hand off; if yes, follow the
|
|
102
|
-
\`follow_up\` instructions from the save output.`;
|
|
103
|
-
import { defineBranch, defineLeaf } from '../core/command.js';
|
|
104
|
-
import { saveArtifact, readArtifact, listArtifacts, OVERSIZE_WARN_LINES } from '../core/artifact.js';
|
|
105
|
-
import { paginate } from '../core/pagination.js';
|
|
106
|
-
export function registerSpec() {
|
|
107
|
-
const specNew = defineLeaf({
|
|
108
|
-
name: 'new',
|
|
109
|
-
help: {
|
|
110
|
-
name: 'mode spec new',
|
|
111
|
-
summary: 'draft a specification artifact from intent',
|
|
112
|
-
guide: SPEC_NEW_GUIDE,
|
|
113
|
-
params: [
|
|
114
|
-
{
|
|
115
|
-
kind: 'positional',
|
|
116
|
-
name: 'name',
|
|
117
|
-
type: 'string',
|
|
118
|
-
required: true,
|
|
119
|
-
constraint: 'Kebab-case slug used as the artifact filename. No spaces; use hyphens.',
|
|
120
|
-
},
|
|
121
|
-
{
|
|
122
|
-
kind: 'stdin',
|
|
123
|
-
name: 'body',
|
|
124
|
-
required: true,
|
|
125
|
-
constraint: 'Full specification prose. Treated as ground truth for downstream planning.',
|
|
126
|
-
},
|
|
127
|
-
],
|
|
128
|
-
output: [
|
|
129
|
-
{
|
|
130
|
-
name: 'path',
|
|
131
|
-
type: 'string',
|
|
132
|
-
required: true,
|
|
133
|
-
constraint: 'Absolute path to the written spec artifact.',
|
|
134
|
-
},
|
|
135
|
-
{
|
|
136
|
-
name: 'follow_up',
|
|
137
|
-
type: 'string',
|
|
138
|
-
required: true,
|
|
139
|
-
constraint: 'Recommended next call (planner spawn via `crtr mode planner`).',
|
|
140
|
-
},
|
|
141
|
-
],
|
|
142
|
-
outputKind: 'object',
|
|
143
|
-
effects: ['Writes a spec artifact to the specs artifact directory.'],
|
|
144
|
-
},
|
|
145
|
-
run: async (input) => {
|
|
146
|
-
const name = input['name'];
|
|
147
|
-
const body = input['body'];
|
|
148
|
-
const { path, oversize, lineCount } = saveArtifact('specs', name, body);
|
|
149
|
-
let follow_up = `Plan it: crtr mode planner ${path} (returns {job_id}), then crtr job read result <job_id> --wait.`;
|
|
150
|
-
follow_up +=
|
|
151
|
-
` Optional human gate before planning (complements, does not replace \`crtr mode reviewer\`): crtr human review --file ${path} for anchored comments, then gate with crtr human approve --title "Approve this spec?".`;
|
|
152
|
-
if (oversize) {
|
|
153
|
-
follow_up +=
|
|
154
|
-
` OVERSIZE ADVISORY: this spec is ${lineCount} lines (> ${OVERSIZE_WARN_LINES}). Split into focused sub-specs before planning.`;
|
|
155
|
-
}
|
|
156
|
-
return { path, follow_up };
|
|
157
|
-
},
|
|
158
|
-
});
|
|
159
|
-
const specShow = defineLeaf({
|
|
160
|
-
name: 'show',
|
|
161
|
-
help: {
|
|
162
|
-
name: 'mode spec show',
|
|
163
|
-
summary: 'read a spec artifact by name',
|
|
164
|
-
params: [
|
|
165
|
-
{
|
|
166
|
-
kind: 'positional',
|
|
167
|
-
name: 'name',
|
|
168
|
-
type: 'string',
|
|
169
|
-
required: true,
|
|
170
|
-
constraint: 'Exact artifact name (no path extension). Use `spec list` to enumerate.',
|
|
171
|
-
},
|
|
172
|
-
],
|
|
173
|
-
output: [
|
|
174
|
-
{
|
|
175
|
-
name: 'name',
|
|
176
|
-
type: 'string',
|
|
177
|
-
required: true,
|
|
178
|
-
constraint: 'Artifact name as stored.',
|
|
179
|
-
},
|
|
180
|
-
{
|
|
181
|
-
name: 'path',
|
|
182
|
-
type: 'string',
|
|
183
|
-
required: true,
|
|
184
|
-
constraint: 'Absolute path to the artifact file.',
|
|
185
|
-
},
|
|
186
|
-
{
|
|
187
|
-
name: 'body',
|
|
188
|
-
type: 'string',
|
|
189
|
-
required: true,
|
|
190
|
-
constraint: 'Full spec body text.',
|
|
191
|
-
},
|
|
192
|
-
],
|
|
193
|
-
outputKind: 'object',
|
|
194
|
-
effects: ['None. Read-only.'],
|
|
195
|
-
},
|
|
196
|
-
run: async (input) => {
|
|
197
|
-
const name = input['name'];
|
|
198
|
-
const record = readArtifact('specs', name);
|
|
199
|
-
return { name: record.name, path: record.path, body: record.body };
|
|
200
|
-
},
|
|
201
|
-
});
|
|
202
|
-
const specList = defineLeaf({
|
|
203
|
-
name: 'list',
|
|
204
|
-
help: {
|
|
205
|
-
name: 'mode spec list',
|
|
206
|
-
summary: 'paginated list of spec artifacts, sorted ascending by name',
|
|
207
|
-
params: [
|
|
208
|
-
{
|
|
209
|
-
kind: 'flag',
|
|
210
|
-
name: 'scope',
|
|
211
|
-
type: 'enum',
|
|
212
|
-
choices: ['user', 'project', 'all'],
|
|
213
|
-
required: false,
|
|
214
|
-
constraint: 'Filter by scope. Omit to list all.',
|
|
215
|
-
},
|
|
216
|
-
{
|
|
217
|
-
kind: 'flag',
|
|
218
|
-
name: 'limit',
|
|
219
|
-
type: 'int',
|
|
220
|
-
required: false,
|
|
221
|
-
default: 20,
|
|
222
|
-
constraint: 'Default 20, max 100.',
|
|
223
|
-
},
|
|
224
|
-
{
|
|
225
|
-
kind: 'flag',
|
|
226
|
-
name: 'cursor',
|
|
227
|
-
type: 'string',
|
|
228
|
-
required: false,
|
|
229
|
-
constraint: "Opaque token from a previous response's next_cursor. Omit on first call.",
|
|
230
|
-
},
|
|
231
|
-
],
|
|
232
|
-
output: [
|
|
233
|
-
{
|
|
234
|
-
name: 'items',
|
|
235
|
-
type: 'object[]',
|
|
236
|
-
required: true,
|
|
237
|
-
constraint: 'Each: {name, path, updated_at}. Sorted ascending by name.',
|
|
238
|
-
},
|
|
239
|
-
{
|
|
240
|
-
name: 'next_cursor',
|
|
241
|
-
type: 'string | null',
|
|
242
|
-
required: true,
|
|
243
|
-
constraint: 'Pass on the next call to continue. null means no more items.',
|
|
244
|
-
},
|
|
245
|
-
{
|
|
246
|
-
name: 'total',
|
|
247
|
-
type: 'integer | null',
|
|
248
|
-
required: true,
|
|
249
|
-
constraint: 'Total specs matching the query. Exact when cheap; null on large/filtered sets — do not retry to force it.',
|
|
250
|
-
},
|
|
251
|
-
],
|
|
252
|
-
outputKind: 'object',
|
|
253
|
-
effects: ['None. Read-only.'],
|
|
254
|
-
},
|
|
255
|
-
run: async (input) => {
|
|
256
|
-
const limit = input['limit'] ?? 20;
|
|
257
|
-
const cursor = input['cursor'];
|
|
258
|
-
const all = listArtifacts('specs');
|
|
259
|
-
const result = paginate(all, { limit, cursor }, {
|
|
260
|
-
defaultLimit: 20,
|
|
261
|
-
maxLimit: 100,
|
|
262
|
-
keyOf: (item) => item.name,
|
|
263
|
-
total: 'count',
|
|
264
|
-
});
|
|
265
|
-
return {
|
|
266
|
-
items: result.items,
|
|
267
|
-
next_cursor: result.next_cursor,
|
|
268
|
-
total: result.total,
|
|
269
|
-
};
|
|
270
|
-
},
|
|
271
|
-
});
|
|
272
|
-
return defineBranch({
|
|
273
|
-
name: 'spec',
|
|
274
|
-
help: {
|
|
275
|
-
name: 'mode spec',
|
|
276
|
-
summary: 'create and read specification artifacts',
|
|
277
|
-
model: 'Lifecycle: draft -> approved. Approved specs drive plan creation.',
|
|
278
|
-
children: [
|
|
279
|
-
{ name: 'new', desc: 'draft a spec from intent', useWhen: 'capturing requirements before planning' },
|
|
280
|
-
{ name: 'show', desc: 'read a spec by name', useWhen: 'reasoning about an existing spec' },
|
|
281
|
-
{ name: 'list', desc: 'enumerate specs', useWhen: 'discovering what specs exist' },
|
|
282
|
-
],
|
|
283
|
-
},
|
|
284
|
-
slash: {
|
|
285
|
-
name: 'spec',
|
|
286
|
-
description: 'Spec mode — turn intent into a specification artifact (spec-driven workflow).',
|
|
287
|
-
argumentHint: '[what to spec]',
|
|
288
|
-
body: `You are entering **spec mode**: turn intent into a written specification artifact.
|
|
289
|
-
|
|
290
|
-
1. Run \`crtr mode spec new -h\` to load the spec-authoring workflow and output schema.
|
|
291
|
-
2. Follow that workflow to draft the spec, then save it by piping the markdown to \`crtr mode spec new <name>\` on stdin.
|
|
292
|
-
|
|
293
|
-
The request: $ARGUMENTS
|
|
294
|
-
|
|
295
|
-
If no request was given, ask the user what to spec before starting.`,
|
|
296
|
-
},
|
|
297
|
-
children: [specNew, specShow, specList],
|
|
298
|
-
});
|
|
299
|
-
}
|
|
@@ -1,248 +0,0 @@
|
|
|
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
|
-
});
|