trekoon 0.4.6 → 0.4.8
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/.agents/skills/trekoon/SKILL.md +3 -0
- package/.agents/skills/trekoon/reference/harness-primitives.md +25 -0
- package/.agents/skills/trekoon/reference/planning.md +37 -2
- package/docs/commands.md +19 -0
- package/docs/quickstart.md +18 -2
- package/package.json +1 -1
- package/src/commands/arg-parser.ts +52 -0
- package/src/commands/epic.ts +11 -5
- package/src/commands/help.ts +2 -0
- package/src/commands/quickstart.ts +24 -1
- package/src/commands/subtask.ts +8 -2
- package/src/commands/task.ts +8 -2
|
@@ -106,6 +106,9 @@ continue broader epic execution only when it matches the user intent.
|
|
|
106
106
|
current state.
|
|
107
107
|
- Append progress, verification, and blocker notes with `--append`; do not
|
|
108
108
|
rewrite descriptions unless fixing the plan itself.
|
|
109
|
+
- Compact specs are pipe-split. Before any `--task`/`--subtask`/`--dep`,
|
|
110
|
+
rephrase or escape bare `|` (especially `||` and trailing `|`) as `\|`.
|
|
111
|
+
See `reference/planning.md` "CAUTION — bare-pipe footguns".
|
|
109
112
|
- Preview search/replace before `--apply`.
|
|
110
113
|
- Never edit `.trekoon/trekoon.db` directly. Keep `.trekoon` gitignored.
|
|
111
114
|
- Never run `trekoon wipe --yes --toon` unless the user explicitly asks.
|
|
@@ -115,3 +115,28 @@ trekoon --toon task done <task-id>
|
|
|
115
115
|
|
|
116
116
|
`task done` auto-walks `todo` or `blocked` through `in_progress`. For subtasks,
|
|
117
117
|
move through `in_progress` (claim or status) before `done`.
|
|
118
|
+
|
|
119
|
+
## Compact Spec Hazards
|
|
120
|
+
|
|
121
|
+
Any batch creation command (`epic create`, `epic expand`, `task create-many`,
|
|
122
|
+
`subtask create-many`, `dep add-many`) splits `--task`/`--subtask`/`--dep`
|
|
123
|
+
values on raw `|`. Three recurring footguns — applies in plan and execute
|
|
124
|
+
modes (e.g. mid-execution `epic expand`):
|
|
125
|
+
|
|
126
|
+
1. **Single mid-value `|`** with no explicit `|<status>` field: trailing
|
|
127
|
+
text silently lands in the status slot. Creation succeeds, fails on next
|
|
128
|
+
transition.
|
|
129
|
+
2. **`||`** (JS logical-OR, shell OR): adds two extra fields per occurrence,
|
|
130
|
+
overshoots the field-count gate. Rephrase as "or" or escape as `\|\|`.
|
|
131
|
+
3. **Trailing `|`** is not a terminator: creates an empty final field; on a
|
|
132
|
+
4-field subtask shape that becomes an empty description and the parser
|
|
133
|
+
rejects with "is missing a description".
|
|
134
|
+
|
|
135
|
+
Escape literal `|` as `\|`. Pre-flight specs:
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
grep -nE '(^|[^\\])\|\||\|$' specs.txt
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Full rules and the silent/loud failure matrix live in
|
|
142
|
+
`reference/planning.md` "CAUTION — bare-pipe footguns".
|
|
@@ -139,6 +139,30 @@ compact specs: `\?`, `\.`, `\{`, `\(`, `\[` etc. fail before records are
|
|
|
139
139
|
created. Prefer prose over exact regex in descriptions. Rephrase operators
|
|
140
140
|
like `!=` as words to avoid escaping confusion.
|
|
141
141
|
|
|
142
|
+
Bare `|` inside field values is a field separator and will silently corrupt
|
|
143
|
+
records when the spec omits an explicit `|<status>` field. A single shell
|
|
144
|
+
pipe (`cmd a | cmd b`) in a Verify line on a 3-field task spec or 4-field
|
|
145
|
+
subtask spec (epic create/expand) splits into one extra field, and the parser
|
|
146
|
+
treats that trailing fragment as the status — but does not validate it.
|
|
147
|
+
Creation succeeds and the record only breaks on the next status transition.
|
|
148
|
+
Concrete failure: `--task "task-x|API|Verify: bun test foo | tail -20"`
|
|
149
|
+
splits to `[task-x, API, "Verify: bun test foo ", " tail -20"]`; ` tail -20`
|
|
150
|
+
becomes the status, and the bad status only surfaces on the next update.
|
|
151
|
+
|
|
152
|
+
Escape literal `|` as `\|` or rephrase to avoid the character. `||` fallbacks
|
|
153
|
+
(`cmd a || cmd b`) and other multi-pipe constructs are caught loudly by the
|
|
154
|
+
parser: every unescaped `|` adds a field, so multi-pipe input overshoots the
|
|
155
|
+
field-count gate and fails before any record is written. The empty-field gate
|
|
156
|
+
fires on a different shape — a single bare middle pipe like
|
|
157
|
+
`key|title||desc`. Either way, the silent failure mode is specifically the
|
|
158
|
+
single-pipe / no-explicit-status case. Specs that already pass `|<status>`
|
|
159
|
+
fail loudly even on a single unescaped `|`.
|
|
160
|
+
|
|
161
|
+
A bare `|` at the very end of a spec (trailing pipe) is **not** a terminator.
|
|
162
|
+
It produces an empty final field. On a `<...>|<title>|<description>` shape
|
|
163
|
+
that empty field becomes the description and the parser rejects the spec with
|
|
164
|
+
"is missing a description". Drop trailing `|`; never use it as a "done" marker.
|
|
165
|
+
|
|
142
166
|
Spec shape (status optional, defaults to `todo`):
|
|
143
167
|
|
|
144
168
|
- `--task <temp-key>|<title>|<description>` or `<temp-key>|<title>|<description>|<status>`
|
|
@@ -148,10 +172,21 @@ Spec shape (status optional, defaults to `todo`):
|
|
|
148
172
|
Prefer the shorter form. Pass an explicit `|<status>` only when seeding a
|
|
149
173
|
non-`todo` status.
|
|
150
174
|
|
|
175
|
+
The three bare-pipe footguns (`||`, single mid-value `|`, trailing `|`) and
|
|
176
|
+
the pre-flight `grep -nE '(^|[^\\])\|\||\|$'` recipe live in
|
|
177
|
+
`reference/harness-primitives.md` "Compact Spec Hazards". The paragraphs
|
|
178
|
+
above add the silent-vs-loud detail that the quick-ref points at. When in
|
|
179
|
+
doubt, build descriptions as plain prose without operator characters.
|
|
180
|
+
|
|
151
181
|
One-shot rules:
|
|
152
182
|
|
|
153
|
-
- Declare tasks/subtasks with plain temp keys, e.g. `task-api`, `sub-tests`.
|
|
154
|
-
-
|
|
183
|
+
- Declare tasks/subtasks with plain temp keys, e.g. `task-api`, `sub-api-tests`.
|
|
184
|
+
- Temp keys form a flat namespace per command. Every `--task` and `--subtask`
|
|
185
|
+
key must be unique across the whole `epic create` / `epic expand` call, not
|
|
186
|
+
per parent task. Prefix subtask keys with the parent task key
|
|
187
|
+
(`sub-api-tests`, `sub-ui-states`) to stay safe across re-runs.
|
|
188
|
+
- Refer to records created in the same command as `@task-api` or
|
|
189
|
+
`@sub-api-tests`.
|
|
155
190
|
- Use `@task-key` as the subtask parent ref and in `--dep` specs.
|
|
156
191
|
- `--dep <source>|<depends-on>` points from blocked item to prerequisite.
|
|
157
192
|
- `epic create` returns `result.mappings` and counts. Use those real UUIDs in
|
package/docs/commands.md
CHANGED
|
@@ -179,6 +179,25 @@ Rules:
|
|
|
179
179
|
- Subtask cascade is accepted for consistency but just updates one subtask
|
|
180
180
|
- Success response includes `data.cascade` with changed/unchanged IDs and counts
|
|
181
181
|
|
|
182
|
+
## Epic create and expand
|
|
183
|
+
|
|
184
|
+
### Temp-key rules
|
|
185
|
+
|
|
186
|
+
Temp keys in `--task` and `--subtask` share one flat namespace per `epic create` / `epic expand` invocation — reusing a key across any two records in the same call fails the batch. Prefix subtask keys with the parent task key to keep them unique (e.g. `task-a-sub-1` under `task-a`).
|
|
187
|
+
|
|
188
|
+
`task create-many` and `subtask create-many` use their own scoped namespaces, independent of each other and of `epic create`.
|
|
189
|
+
|
|
190
|
+
Escape any literal `|` inside field values as `\|`. A single bare `|` in a spec without an explicit `|<status>` field silently pushes the trailing text into the status slot — creation succeeds and the record only breaks on the next status update. Multi-pipe constructs like `||` fail loudly on the field-count check. See the planning skill for the full pipe-escape rules.
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
trekoon epic create \
|
|
194
|
+
--title "Launch" \
|
|
195
|
+
--task "task-a|First task|Description|todo" \
|
|
196
|
+
--task "task-b|Second task|Description|todo" \
|
|
197
|
+
--subtask "@task-a|task-a-sub-1|First subtask|Description|todo" \
|
|
198
|
+
--subtask "@task-b|task-b-sub-1|Second subtask|Description|todo"
|
|
199
|
+
```
|
|
200
|
+
|
|
182
201
|
## Status machine
|
|
183
202
|
|
|
184
203
|
Statuses: `todo`, `in_progress`, `done`, `blocked`. The hyphenated `in-progress`
|
package/docs/quickstart.md
CHANGED
|
@@ -93,11 +93,27 @@ trekoon --toon epic create \
|
|
|
93
93
|
--description "Ship one-shot planning workflows" \
|
|
94
94
|
--task "task-a|First task|First description|todo" \
|
|
95
95
|
--task "task-b|Second task|Second description|todo" \
|
|
96
|
-
--subtask "@task-a|sub-a|First subtask|Subtask description|todo" \
|
|
96
|
+
--subtask "@task-a|sub-a-first|First subtask|Subtask description|todo" \
|
|
97
97
|
--dep "@task-b|@task-a" \
|
|
98
|
-
--dep "@sub-a|@task-a"
|
|
98
|
+
--dep "@sub-a-first|@task-a"
|
|
99
99
|
```
|
|
100
100
|
|
|
101
|
+
All temp keys (task and subtask) must be unique across the whole command — they share one flat namespace. Prefix subtask keys with the parent task key to stay unique.
|
|
102
|
+
|
|
103
|
+
Escape any literal `|` inside field values as `\|`. Three recurring footguns:
|
|
104
|
+
|
|
105
|
+
- **Single mid-value `|`** on a spec without explicit `|<status>` silently pushes trailing text into the status slot (e.g. `Verify: bun test foo | tail` lets `tail` become the status, and creation still succeeds). Specs that already pass `|<status>` fail loudly on the same input.
|
|
106
|
+
- **`||`** (JS logical-OR `a || b`, shell OR `cmd || cmd`) adds two extra fields per occurrence; the field-count gate rejects with `Task specs must use ...` / `Subtask specs must use ...`. Rephrase `||` as "or" or escape as `\|\|`.
|
|
107
|
+
- **Trailing `|`** is not a terminator. It creates an empty final field; on a 4-field subtask shape that empty field becomes the description and the parser rejects with "is missing a description". Drop trailing pipes.
|
|
108
|
+
|
|
109
|
+
Pre-flight any batch before invoking `epic create`/`epic expand`/`task create-many`/`subtask create-many`:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
grep -nE '(^|[^\\])\|\||\|$' specs.txt
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
See the planning skill for full rules.
|
|
116
|
+
|
|
101
117
|
This is better than sequential creates because later records can reference
|
|
102
118
|
earlier ones with `@temp-key`, and you get one atomic operation with mappings
|
|
103
119
|
and counts in the response.
|
package/package.json
CHANGED
|
@@ -285,6 +285,58 @@ export function parseCompactFields(rawValue: string): ParsedCompactFields {
|
|
|
285
285
|
};
|
|
286
286
|
}
|
|
287
287
|
|
|
288
|
+
export function endsWithBareCompactPipe(rawSpec: string): boolean {
|
|
289
|
+
if (!rawSpec.endsWith("|")) {
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
292
|
+
let backslashes = 0;
|
|
293
|
+
for (let i = rawSpec.length - 2; i >= 0 && rawSpec[i] === "\\"; i--) {
|
|
294
|
+
backslashes++;
|
|
295
|
+
}
|
|
296
|
+
return backslashes % 2 === 0;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export function containsBareDoubleCompactPipe(rawSpec: string): boolean {
|
|
300
|
+
let escaping = false;
|
|
301
|
+
let prevBarePipe = false;
|
|
302
|
+
for (const character of rawSpec) {
|
|
303
|
+
if (escaping) {
|
|
304
|
+
escaping = false;
|
|
305
|
+
prevBarePipe = false;
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
if (character === "\\") {
|
|
309
|
+
escaping = true;
|
|
310
|
+
prevBarePipe = false;
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
if (character === "|") {
|
|
314
|
+
if (prevBarePipe) {
|
|
315
|
+
return true;
|
|
316
|
+
}
|
|
317
|
+
prevBarePipe = true;
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
prevBarePipe = false;
|
|
321
|
+
}
|
|
322
|
+
return false;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
export function describeCompactPipeIssue(rawSpec: string): string {
|
|
326
|
+
const doublePipe = containsBareDoubleCompactPipe(rawSpec);
|
|
327
|
+
const trailingPipe = endsWithBareCompactPipe(rawSpec);
|
|
328
|
+
if (doublePipe && trailingPipe) {
|
|
329
|
+
return "Spec has bare `||` and ends with a trailing `|`. `||` (logical-OR or back-to-back pipes) adds two extra fields per occurrence; a trailing `|` creates an empty final field. Escape literal pipes as `\\|` or rephrase (e.g. `||` -> `or`), and drop the trailing `|`.";
|
|
330
|
+
}
|
|
331
|
+
if (doublePipe) {
|
|
332
|
+
return "Spec has bare `||` (two pipes back-to-back) — common with JS logical-OR (`a || b`) or shell OR (`cmd a || cmd b`). Every unescaped `|` adds a field, so `||` adds two extra fields. Escape literal pipes as `\\|` or rephrase the operator (e.g. `||` -> `or`).";
|
|
333
|
+
}
|
|
334
|
+
if (trailingPipe) {
|
|
335
|
+
return "Spec ends with a bare `|`. The trailing `|` is not a terminator — it creates an empty final field. Drop the trailing `|`.";
|
|
336
|
+
}
|
|
337
|
+
return "Bare `|` inside a field value is a field separator. Escape literal pipes as `\\|` or rephrase the value to avoid `|`.";
|
|
338
|
+
}
|
|
339
|
+
|
|
288
340
|
export function parseCompactEntityRef(rawValue: string): CompactEntityRef {
|
|
289
341
|
if (rawValue.startsWith(COMPACT_TEMP_KEY_PREFIX)) {
|
|
290
342
|
const tempKey = rawValue.slice(COMPACT_TEMP_KEY_PREFIX.length);
|
package/src/commands/epic.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
SEARCH_REPLACE_FIELDS,
|
|
3
|
+
describeCompactPipeIssue,
|
|
4
|
+
endsWithBareCompactPipe,
|
|
3
5
|
findUnknownOption,
|
|
4
6
|
hasFlag,
|
|
5
7
|
isValidCompactTempKey,
|
|
@@ -397,7 +399,11 @@ function failUnexpectedPositionals(command: string, unexpected: readonly string[
|
|
|
397
399
|
|
|
398
400
|
function failEmptyCompactField(command: string, option: string, index: number, rawSpec: string, field: string): CliResult {
|
|
399
401
|
const label = option === "task" ? "Task" : "Subtask";
|
|
400
|
-
|
|
402
|
+
let human = `${label} spec ${index + 1} is missing a ${field}.`;
|
|
403
|
+
if (field === "description" && endsWithBareCompactPipe(rawSpec)) {
|
|
404
|
+
human += " Spec ends with a bare `|` — the trailing `|` is not a terminator and creates an empty description field. Drop the trailing `|` and write an actual description.";
|
|
405
|
+
}
|
|
406
|
+
return failBatchSpec(command, human, {
|
|
401
407
|
option,
|
|
402
408
|
index,
|
|
403
409
|
rawSpec,
|
|
@@ -465,7 +471,7 @@ function parseExpandTaskSpecs(rawSpecs: readonly string[]): { specs: CompactTask
|
|
|
465
471
|
if (parsed.fields.length !== 3 && parsed.fields.length !== 4) {
|
|
466
472
|
return {
|
|
467
473
|
specs: [],
|
|
468
|
-
error: failBatchSpec("epic.expand", `Task specs must use <temp-key>|<title>|<description> or <temp-key>|<title>|<description>|<status> in --task spec ${index + 1}
|
|
474
|
+
error: failBatchSpec("epic.expand", `Task specs must use <temp-key>|<title>|<description> or <temp-key>|<title>|<description>|<status> in --task spec ${index + 1}. ${describeCompactPipeIssue(rawSpec)}`, {
|
|
469
475
|
option: "task",
|
|
470
476
|
index,
|
|
471
477
|
rawSpec,
|
|
@@ -561,7 +567,7 @@ function parseExpandSubtaskSpecs(rawSpecs: readonly string[]): { specs: CompactS
|
|
|
561
567
|
if (parsed.fields.length !== 4 && parsed.fields.length !== 5) {
|
|
562
568
|
return {
|
|
563
569
|
specs: [],
|
|
564
|
-
error: failBatchSpec("epic.expand", `Subtask specs must use <parent-ref>|<temp-key>|<title>|<description> or <parent-ref>|<temp-key>|<title>|<description>|<status> in --subtask spec ${index + 1}
|
|
570
|
+
error: failBatchSpec("epic.expand", `Subtask specs must use <parent-ref>|<temp-key>|<title>|<description> or <parent-ref>|<temp-key>|<title>|<description>|<status> in --subtask spec ${index + 1}. ${describeCompactPipeIssue(rawSpec)}`, {
|
|
565
571
|
option: "subtask",
|
|
566
572
|
index,
|
|
567
573
|
rawSpec,
|
|
@@ -776,7 +782,7 @@ export async function runEpic(context: CliContext): Promise<CliResult> {
|
|
|
776
782
|
|
|
777
783
|
const duplicateTempKey = findDuplicateExpandTempKey(parsedTasks.specs, parsedSubtasks.specs);
|
|
778
784
|
if (duplicateTempKey !== null) {
|
|
779
|
-
return failBatchSpec("epic.create", `Duplicate temp key '${duplicateTempKey}' across --task and --subtask specs.`, {
|
|
785
|
+
return failBatchSpec("epic.create", `Duplicate temp key '${duplicateTempKey}' across --task and --subtask specs. Temp keys share one flat namespace per epic create — prefix subtask temp keys with the parent task key (e.g. sub-<task-key>-tests) to disambiguate.`, {
|
|
780
786
|
tempKey: duplicateTempKey,
|
|
781
787
|
});
|
|
782
788
|
}
|
|
@@ -849,7 +855,7 @@ export async function runEpic(context: CliContext): Promise<CliResult> {
|
|
|
849
855
|
|
|
850
856
|
const duplicateTempKey = findDuplicateExpandTempKey(parsedTasks.specs, parsedSubtasks.specs);
|
|
851
857
|
if (duplicateTempKey !== null) {
|
|
852
|
-
return failBatchSpec("epic.expand", `Duplicate temp key '${duplicateTempKey}' across --task and --subtask specs.`, {
|
|
858
|
+
return failBatchSpec("epic.expand", `Duplicate temp key '${duplicateTempKey}' across --task and --subtask specs. Temp keys share one flat namespace per epic expand — prefix subtask temp keys with the parent task key (e.g. sub-<task-key>-tests) to disambiguate.`, {
|
|
853
859
|
tempKey: duplicateTempKey,
|
|
854
860
|
});
|
|
855
861
|
}
|
package/src/commands/help.ts
CHANGED
|
@@ -126,7 +126,9 @@ const EPIC_HELP = [
|
|
|
126
126
|
" --task <temp-key>|<title>|<description>|<status> (explicit status)",
|
|
127
127
|
` --subtask <parent-ref>|<temp-key>|<title>|<description> (status defaults to todo) (${"@"}<temp-key> for new parents)`,
|
|
128
128
|
` --subtask <parent-ref>|<temp-key>|<title>|<description>|<status> (explicit status)`,
|
|
129
|
+
" Temp keys in --task and --subtask share one namespace per command. Prefix subtask keys with parent task key.",
|
|
129
130
|
` --dep <source-ref>|<depends-on-ref> (refs can be IDs or ${"@"}<temp-key>)`,
|
|
131
|
+
" Escape literal | inside field values (\\|); bare | is a field separator and will silently corrupt records.",
|
|
130
132
|
" Escapes in compact specs: \\| for |, \\\\ for \\, \\n, \\r, \\t",
|
|
131
133
|
"",
|
|
132
134
|
"List:",
|
|
@@ -88,7 +88,30 @@ const QUICKSTART_TEXT = [
|
|
|
88
88
|
" field/value details; batch prompts use a count-only confirmation (30s",
|
|
89
89
|
" timeout, defaults to reject).",
|
|
90
90
|
"",
|
|
91
|
-
"8)
|
|
91
|
+
"8) Batch planning with compact specs",
|
|
92
|
+
" One-shot create: trekoon --toon epic create --title \"...\" --description \"...\" \\",
|
|
93
|
+
" --task \"<temp-key>|<title>|<description>\" \\",
|
|
94
|
+
" --subtask \"@<task-temp-key>|<temp-key>|<title>|<description>\" \\",
|
|
95
|
+
" --dep \"@<source>|@<depends-on>\"",
|
|
96
|
+
" Status is optional; append |<status> only when seeding non-todo.",
|
|
97
|
+
" Temp keys (--task and --subtask) share one flat namespace per command; prefix",
|
|
98
|
+
" subtask keys with the parent task key (e.g. sub-<task-key>-tests).",
|
|
99
|
+
"",
|
|
100
|
+
" Compact specs split on raw |. CAUTION — three bare-pipe footguns:",
|
|
101
|
+
" a) Single mid-value | with no explicit |<status>: trailing text silently",
|
|
102
|
+
" lands in the status slot. e.g. `Verify: bun test foo | tail` makes",
|
|
103
|
+
" \" tail\" the status; creation succeeds, fails on next transition.",
|
|
104
|
+
" b) || (JS logical-OR or shell OR) adds two extra fields per occurrence and",
|
|
105
|
+
" overshoots the field-count gate (Task/Subtask specs must use ...).",
|
|
106
|
+
" Rephrase as \"or\" or escape as \\|\\|.",
|
|
107
|
+
" c) Trailing | is NOT a terminator. It creates an empty final field; on a",
|
|
108
|
+
" 4-field subtask shape that becomes an empty description and the parser",
|
|
109
|
+
" rejects with \"is missing a description\". Drop trailing pipes.",
|
|
110
|
+
" Escape literal | as \\|. Pre-flight specs before submitting:",
|
|
111
|
+
" grep -nE '(^|[^\\\\])\\|\\||\\|$' specs.txt",
|
|
112
|
+
" Full rules: .agents/skills/trekoon/reference/planning.md (CAUTION block).",
|
|
113
|
+
"",
|
|
114
|
+
"9) Wipe (destructive recovery only)",
|
|
92
115
|
" trekoon wipe --yes",
|
|
93
116
|
" Removes the shared .trekoon directory for every worktree in the repo.",
|
|
94
117
|
" Don't use this for routine cleanup, sync fixes, or gitignore issues.",
|
package/src/commands/subtask.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
SEARCH_REPLACE_FIELDS,
|
|
3
|
+
describeCompactPipeIssue,
|
|
4
|
+
endsWithBareCompactPipe,
|
|
3
5
|
findUnknownOption,
|
|
4
6
|
hasFlag,
|
|
5
7
|
isValidCompactTempKey,
|
|
@@ -249,7 +251,11 @@ function failConflictingTaskIds(optionTaskId: string, positionalTaskId: string):
|
|
|
249
251
|
}
|
|
250
252
|
|
|
251
253
|
function failEmptyCompactField(command: string, option: string, index: number, rawSpec: string, field: string): CliResult {
|
|
252
|
-
|
|
254
|
+
let human = `${option === "subtask" ? "Subtask" : "Spec"} spec ${index + 1} is missing a ${field}.`;
|
|
255
|
+
if (field === "description" && endsWithBareCompactPipe(rawSpec)) {
|
|
256
|
+
human += " Spec ends with a bare `|` — the trailing `|` is not a terminator and creates an empty description field. Drop the trailing `|` and write an actual description.";
|
|
257
|
+
}
|
|
258
|
+
return failBatchSpec(command, human, {
|
|
253
259
|
option,
|
|
254
260
|
index,
|
|
255
261
|
rawSpec,
|
|
@@ -289,7 +295,7 @@ function parseSubtaskCreateManySpecs(parentTaskId: string, rawSpecs: readonly st
|
|
|
289
295
|
if (parsed.fields.length !== 3 && parsed.fields.length !== 4) {
|
|
290
296
|
return {
|
|
291
297
|
specs: [],
|
|
292
|
-
error: failBatchSpec("subtask.create-many", `Subtask specs must use <temp-key>|<title>|<description> or <temp-key>|<title>|<description>|<status> in --subtask spec ${index + 1}
|
|
298
|
+
error: failBatchSpec("subtask.create-many", `Subtask specs must use <temp-key>|<title>|<description> or <temp-key>|<title>|<description>|<status> in --subtask spec ${index + 1}. ${describeCompactPipeIssue(rawSpec)}`, {
|
|
293
299
|
option: "subtask",
|
|
294
300
|
index,
|
|
295
301
|
rawSpec,
|
package/src/commands/task.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
SEARCH_REPLACE_FIELDS,
|
|
3
|
+
describeCompactPipeIssue,
|
|
4
|
+
endsWithBareCompactPipe,
|
|
3
5
|
findUnknownOption,
|
|
4
6
|
hasFlag,
|
|
5
7
|
isValidCompactTempKey,
|
|
@@ -338,7 +340,11 @@ function failUnexpectedPositionals(command: string, unexpected: readonly string[
|
|
|
338
340
|
}
|
|
339
341
|
|
|
340
342
|
function failEmptyCompactField(command: string, option: string, index: number, rawSpec: string, field: string): CliResult {
|
|
341
|
-
|
|
343
|
+
let human = `${option === "task" ? "Task" : "Spec"} spec ${index + 1} is missing a ${field}.`;
|
|
344
|
+
if (field === "description" && endsWithBareCompactPipe(rawSpec)) {
|
|
345
|
+
human += " Spec ends with a bare `|` — the trailing `|` is not a terminator and creates an empty description field. Drop the trailing `|` and write an actual description.";
|
|
346
|
+
}
|
|
347
|
+
return failBatchSpec(command, human, {
|
|
342
348
|
option,
|
|
343
349
|
index,
|
|
344
350
|
rawSpec,
|
|
@@ -378,7 +384,7 @@ function parseTaskCreateManySpecs(rawSpecs: readonly string[]): { specs: Compact
|
|
|
378
384
|
if (parsed.fields.length !== 3 && parsed.fields.length !== 4) {
|
|
379
385
|
return {
|
|
380
386
|
specs: [],
|
|
381
|
-
error: failBatchSpec("task.create-many", `Task specs must use <temp-key>|<title>|<description> or <temp-key>|<title>|<description>|<status> in --task spec ${index + 1}
|
|
387
|
+
error: failBatchSpec("task.create-many", `Task specs must use <temp-key>|<title>|<description> or <temp-key>|<title>|<description>|<status> in --task spec ${index + 1}. ${describeCompactPipeIssue(rawSpec)}`, {
|
|
382
388
|
option: "task",
|
|
383
389
|
index,
|
|
384
390
|
rawSpec,
|