feat(cli): --criterion on create epictask, and file the reconciliation follow-ups
Merges the Epic reconciliation (task 170) and closes the gap that made it inert from the terminal. `mcpctl create epictask` had no `--criterion` flag while `create epic` did, so a roadmap built from the CLI had no criteria on its nodes. The reconciliation attributes a criterion to a node by id, so every criterion would have reported as `unowned` and both mismatch lists would always have been empty — the feature would have looked like it worked and detected nothing. Files two follow-ups the reconciliation surfaced: - 175: criteria and roadmap nodes are linked only by a free-form id convention that nothing enforces. EpicTaskService never validates ids against the parent Epic, ids can repeat across nodes, and a node can carry an id the Epic never had. The reconciliation reports those as `unowned` rather than guessing an owner, which is right, but the convention should become an invariant before more is built on it. - 176: `get epic -o yaml` now emits computed `completeness`/`reconciliation`, which are not settable. apply tolerates them, but the round-trip rule says get output is apply input. Local: build clean, workspace 2499 passed, lint unchanged at 794 errors. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N4wNHWf7xSwnZCWpJcyv9p
This commit is contained in:
@@ -179,11 +179,24 @@ export function createCreateCommand(deps: CreateCommandDeps): Command {
|
||||
.requiredOption('--epic <name>', 'Parent Epic')
|
||||
.option('--project <name>', 'Disambiguate the Epic by project')
|
||||
.option('--description <text>', 'Description')
|
||||
.option('--criterion <text>', 'Done-criterion "id=text" (repeat); the id must match one on the parent Epic, and prefix with q: for a quality criterion', collect, [])
|
||||
.option('--depends-on <name>', 'Prerequisite epictask in the same Epic (repeat)', collect, [])
|
||||
.action(async (name: string, opts) => {
|
||||
const body: Record<string, unknown> = { name, epic: opts.epic };
|
||||
if (opts.project !== undefined) body.project = opts.project;
|
||||
if (opts.description !== undefined) body.description = opts.description;
|
||||
// Without this a CLI-built roadmap has no criteria on its nodes, so the
|
||||
// Epic reconciliation reports every criterion as `unowned` and both
|
||||
// mismatch lists are always empty — the feature is inert from the terminal.
|
||||
const doneCriteria = (opts.criterion as string[]).map((raw) => {
|
||||
const eq = raw.indexOf('=');
|
||||
if (eq === -1) throw new Error(`Invalid --criterion '${raw}'. Expected id=text`);
|
||||
let id = raw.slice(0, eq);
|
||||
const quality = id.startsWith('q:');
|
||||
if (quality) id = id.slice(2);
|
||||
return { id, text: raw.slice(eq + 1), kind: quality ? 'quality' : 'outcome', met: false };
|
||||
});
|
||||
if (doneCriteria.length > 0) body.doneCriteria = doneCriteria;
|
||||
const created = await client.post<{ id: string; name: string }>('/api/v1/epictasks', body);
|
||||
log(`Created epictask '${created.name}'`);
|
||||
for (const dep of opts.dependsOn as string[]) {
|
||||
|
||||
Reference in New Issue
Block a user