@odla-ai/cli 0.42.2 → 0.43.0
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/dist/bin.cjs +214 -66
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-K6ATAKGR.js → chunk-WDWJ7HC7.js} +182 -64
- package/dist/chunk-WDWJ7HC7.js.map +1 -0
- package/dist/{cli-4FCRBWED.js → cli-OITEPXW4.js} +2 -2
- package/dist/index.cjs +181 -63
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-K6ATAKGR.js.map +0 -1
- /package/dist/{cli-4FCRBWED.js.map → cli-OITEPXW4.js.map} +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
runCli
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-WDWJ7HC7.js";
|
|
5
5
|
import {
|
|
6
6
|
exitCodeFor
|
|
7
7
|
} from "./chunk-UKLSRQ5J.js";
|
|
@@ -9,4 +9,4 @@ export {
|
|
|
9
9
|
exitCodeFor,
|
|
10
10
|
runCli
|
|
11
11
|
};
|
|
12
|
-
//# sourceMappingURL=cli-
|
|
12
|
+
//# sourceMappingURL=cli-OITEPXW4.js.map
|
package/dist/index.cjs
CHANGED
|
@@ -10824,7 +10824,8 @@ Usage:
|
|
|
10824
10824
|
odla-ai pm bug list [--app <id>] [--status <s>] [--severity <s>] [--goal <id>] [--assignee <id>] [--decision <id>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
|
|
10825
10825
|
odla-ai pm goal add --app <id> --title <t> [--status <s>] [--proof <text>] [--target <pct>] [--mutation-id <id>] [--json]
|
|
10826
10826
|
odla-ai pm task add --app <id> --title <t> [--column <backlog|ready|doing|review|done>] [--goal <id>|--alignment-decision <id>] [--assignee <id>] [--description <text>|--body <text>] [--acceptance <text>] [--execution <human|agent|either>] [--due <epoch-ms>] [--mutation-id <id>] [--json]
|
|
10827
|
-
odla-ai pm next --app <id> [--project <id>] [--json]
|
|
10827
|
+
odla-ai pm next --app <id> [--project <id>] [--verbose] [--json]
|
|
10828
|
+
odla-ai pm start --app <id> [<task-id>] [--goal <id>] [--verbose] [--mutation-id <id>] [--json] [claims the top Ready task in one call]
|
|
10828
10829
|
odla-ai pm watch --app <id> [--cursor <cursor>] [--entity goal|task|decision|bug] [--action created|updated|deleted|comment.created|comment.updated] [--state <state>] [--by <principalId>] [--self <principalId>] [--interval <s>] [--timeout <s>] [--json|--jsonl]
|
|
10829
10830
|
odla-ai pm task ready <id> --expected-revision <n> [--goal <id>|--alignment-decision <id>] [--description <text>|--body <text>] [--acceptance <text>] [--execution <human|agent|either>] [--mutation-id <id>] [--json]
|
|
10830
10831
|
odla-ai pm task claim <id> --expected-revision <n> [--mutation-id <id>] [--json]
|
|
@@ -10845,7 +10846,7 @@ Usage:
|
|
|
10845
10846
|
odla-ai pm <goal|task|decision|bug> comments <id> [--json]
|
|
10846
10847
|
odla-ai pm <goal|task|decision|bug> history <id> [--limit <n>] [--json]
|
|
10847
10848
|
odla-ai pm <goal|task|decision|bug> rm <id>
|
|
10848
|
-
odla-ai pm handoff --app <id> [--project <id>] [--json]
|
|
10849
|
+
odla-ai pm handoff --app <id> [--project <id>] [--verbose] [--json]
|
|
10849
10850
|
odla-ai discuss groups [--json]
|
|
10850
10851
|
odla-ai discuss list [--app <id>] [--q <text>] [--state open|resolved|all] [--mentions] [--json]
|
|
10851
10852
|
odla-ai discuss read <topic> [--limit <n> --offset <n>] [--json]
|
|
@@ -11010,6 +11011,16 @@ Commands:
|
|
|
11010
11011
|
execution contract and a reviewed revision. Any caller with PM
|
|
11011
11012
|
mutation access to that app/project may do it; claims coordinate
|
|
11012
11013
|
execution but never own or lock the PM record.
|
|
11014
|
+
To pick up work: "pm next" reads open goals, Ready candidates,
|
|
11015
|
+
and work in progress; "pm start" claims the top Ready task in
|
|
11016
|
+
one call, compare-and-swapped on the revision it just read, and
|
|
11017
|
+
returns the task, its goal, and the acceptance criteria to work
|
|
11018
|
+
against. Finish with "pm task done <id>", or hand it back with
|
|
11019
|
+
"pm task release <id> --expected-revision <n>". "pm start" never
|
|
11020
|
+
marks work Ready: that contract is reviewed separately so an
|
|
11021
|
+
agent cannot authorize its own work.
|
|
11022
|
+
Intake output is projected to the fields an executing caller
|
|
11023
|
+
acts on; --verbose returns whole records with their audit trail.
|
|
11013
11024
|
bug Intent-first alias for PM bugs. "bug report" writes to
|
|
11014
11025
|
odla PM; odla product defects do not belong in GitHub Issues.
|
|
11015
11026
|
discuss Group discussions (via @odla-ai/chat) for the apps you co-own:
|
|
@@ -11760,6 +11771,15 @@ var FIELD_MAP = {
|
|
|
11760
11771
|
execution: { key: "executionMode" },
|
|
11761
11772
|
"expected-revision": { key: "expectedRevision", num: true }
|
|
11762
11773
|
};
|
|
11774
|
+
var PmRequestError = class extends Error {
|
|
11775
|
+
constructor(status, message2) {
|
|
11776
|
+
super(message2);
|
|
11777
|
+
this.status = status;
|
|
11778
|
+
this.name = "PmRequestError";
|
|
11779
|
+
}
|
|
11780
|
+
status;
|
|
11781
|
+
};
|
|
11782
|
+
var isRevisionConflict = (error) => error instanceof PmRequestError && error.status === 409;
|
|
11763
11783
|
async function pmRequest(ctx, method, path, body) {
|
|
11764
11784
|
const response2 = await ctx.doFetch(`${ctx.platformUrl}/registry/pm${path}`, {
|
|
11765
11785
|
method,
|
|
@@ -11779,7 +11799,8 @@ async function pmRequest(ctx, method, path, body) {
|
|
|
11779
11799
|
const message2 = error.message;
|
|
11780
11800
|
if (typeof message2 === "string" && message2.length > 0) detail = message2;
|
|
11781
11801
|
}
|
|
11782
|
-
throw new
|
|
11802
|
+
throw new PmRequestError(
|
|
11803
|
+
response2.status,
|
|
11783
11804
|
`pm ${method} ${path} failed: ${detail ?? `registry returned ${response2.status}`} (${response2.status})`
|
|
11784
11805
|
);
|
|
11785
11806
|
}
|
|
@@ -11968,103 +11989,196 @@ async function pmTaskLifecycle(ctx, id2, action2, parsed) {
|
|
|
11968
11989
|
ctx.out.log(`task: ${label} \u2192 ${state2}`);
|
|
11969
11990
|
});
|
|
11970
11991
|
}
|
|
11971
|
-
async function
|
|
11992
|
+
async function pmRemove(ctx, entity, id2) {
|
|
11993
|
+
await pmRequest(ctx, "DELETE", `/${entity}/${encodeURIComponent(id2)}`);
|
|
11994
|
+
ctx.out.log(`deleted ${entity} ${id2}`);
|
|
11995
|
+
}
|
|
11996
|
+
|
|
11997
|
+
// src/pm-lean.ts
|
|
11998
|
+
var COMMON = ["id", "appId", "projectId", "title", "revision"];
|
|
11999
|
+
var PER_ENTITY = {
|
|
12000
|
+
goal: ["status", "proof", "targetPct", "currentPct"],
|
|
12001
|
+
task: [
|
|
12002
|
+
"column",
|
|
12003
|
+
"goalId",
|
|
12004
|
+
"alignmentDecisionId",
|
|
12005
|
+
"description",
|
|
12006
|
+
"acceptanceCriteria",
|
|
12007
|
+
"executionMode",
|
|
12008
|
+
"assigneeId",
|
|
12009
|
+
"claimedByPrincipalId",
|
|
12010
|
+
"dueAt"
|
|
12011
|
+
],
|
|
12012
|
+
decision: ["status", "body", "supersedesId"],
|
|
12013
|
+
bug: ["status", "severity", "description", "goalId", "assigneeId", "decisionId"]
|
|
12014
|
+
};
|
|
12015
|
+
function leanRecord(entity, record11) {
|
|
12016
|
+
const out = {};
|
|
12017
|
+
for (const key of [...COMMON, ...PER_ENTITY[entity]]) {
|
|
12018
|
+
const value2 = record11[key];
|
|
12019
|
+
if (value2 !== void 0 && value2 !== null) out[key] = value2;
|
|
12020
|
+
}
|
|
12021
|
+
return out;
|
|
12022
|
+
}
|
|
12023
|
+
function leanRecords(entity, records, verbose) {
|
|
12024
|
+
return verbose ? records : records.map((record11) => leanRecord(entity, record11));
|
|
12025
|
+
}
|
|
12026
|
+
|
|
12027
|
+
// src/pm-intake.ts
|
|
12028
|
+
var READY_COLUMN = "todo";
|
|
12029
|
+
var UNMET_GOAL_STATUS = "open";
|
|
12030
|
+
var ACTIVE_TASK_COLUMNS = "backlog,todo,doing,review";
|
|
12031
|
+
var OPEN_BUG_STATUSES = "open,triaged";
|
|
12032
|
+
var wantsVerbose = (parsed) => parsed.options.verbose === true;
|
|
12033
|
+
async function listFiltered(ctx, entity, appId, projectId, filters) {
|
|
11972
12034
|
const records = [];
|
|
11973
12035
|
for (; ; ) {
|
|
11974
|
-
const q = new URLSearchParams({
|
|
11975
|
-
app: appId,
|
|
11976
|
-
limit: "100",
|
|
11977
|
-
offset: String(records.length)
|
|
11978
|
-
});
|
|
12036
|
+
const q = new URLSearchParams({ app: appId, limit: "100", offset: String(records.length) });
|
|
11979
12037
|
if (projectId) q.set("project", projectId);
|
|
12038
|
+
for (const [key, value2] of Object.entries(filters)) q.set(key, value2);
|
|
11980
12039
|
const page2 = await pmRequest(ctx, "GET", `/${entity}?${q}`);
|
|
11981
12040
|
records.push(...page2.records);
|
|
11982
12041
|
if (records.length >= page2.total || page2.records.length === 0) return records;
|
|
11983
12042
|
}
|
|
11984
12043
|
}
|
|
11985
|
-
|
|
12044
|
+
function requireApp(ctx, parsed, command) {
|
|
11986
12045
|
const appId = stringOpt(parsed.options.app) ?? ctx.appId;
|
|
11987
|
-
|
|
11988
|
-
|
|
12046
|
+
if (!appId) throw new Error(`pm ${command} needs --app <appId>`);
|
|
12047
|
+
return { appId, projectId: stringOpt(parsed.options.project) ?? ctx.projectId };
|
|
12048
|
+
}
|
|
12049
|
+
async function pmNext(ctx, parsed) {
|
|
12050
|
+
const { appId, projectId } = requireApp(ctx, parsed, "next");
|
|
12051
|
+
const verbose = wantsVerbose(parsed);
|
|
11989
12052
|
const [goals, tasks] = await Promise.all([
|
|
11990
|
-
|
|
11991
|
-
|
|
12053
|
+
listFiltered(ctx, "goal", appId, projectId, { status: UNMET_GOAL_STATUS }),
|
|
12054
|
+
// One request for both live columns; the split below is over open work only.
|
|
12055
|
+
listFiltered(ctx, "task", appId, projectId, { column: `${READY_COLUMN},doing` })
|
|
11992
12056
|
]);
|
|
12057
|
+
const ready = tasks.filter((record11) => record11.column === READY_COLUMN);
|
|
12058
|
+
const doing = tasks.filter((record11) => record11.column === "doing");
|
|
12059
|
+
const guidance = !goals.length ? "discuss alignment with the user before creating or claiming project work" : !ready.length ? 'refine a linked Backlog task and mark it Ready with "pm task ready <id>"' : `claim the top Ready task in one call: odla-ai pm start --app ${appId}`;
|
|
11993
12060
|
const result = {
|
|
11994
12061
|
appId,
|
|
11995
12062
|
projectId,
|
|
11996
|
-
openGoals:
|
|
11997
|
-
doing:
|
|
11998
|
-
ready:
|
|
12063
|
+
openGoals: leanRecords("goal", goals, verbose),
|
|
12064
|
+
doing: leanRecords("task", doing, verbose),
|
|
12065
|
+
ready: leanRecords("task", ready, verbose),
|
|
12066
|
+
next: guidance
|
|
11999
12067
|
};
|
|
12000
12068
|
emit2(ctx, result, () => {
|
|
12001
12069
|
ctx.out.log(`${appId}: goal-aligned work intake (read only)`);
|
|
12002
|
-
for (const [label, records] of [
|
|
12003
|
-
["doing",
|
|
12004
|
-
["ready",
|
|
12005
|
-
["open goals",
|
|
12070
|
+
for (const [label, entity, records] of [
|
|
12071
|
+
["doing", "task", doing],
|
|
12072
|
+
["ready", "task", ready],
|
|
12073
|
+
["open goals", "goal", goals]
|
|
12006
12074
|
]) {
|
|
12007
12075
|
ctx.out.log(`${label}:`);
|
|
12008
12076
|
if (!records.length) ctx.out.log("- (none)");
|
|
12009
|
-
else for (const record11 of records) printRecord(
|
|
12010
|
-
ctx,
|
|
12011
|
-
label === "open goals" ? "goal" : "task",
|
|
12012
|
-
record11
|
|
12013
|
-
);
|
|
12014
|
-
}
|
|
12015
|
-
if (!result.openGoals.length) {
|
|
12016
|
-
ctx.out.log("next: discuss alignment with the user before creating or claiming project work");
|
|
12017
|
-
} else if (!result.ready.length) {
|
|
12018
|
-
ctx.out.log("next: refine a linked Backlog task and mark it Ready");
|
|
12019
|
-
} else {
|
|
12020
|
-
ctx.out.log("next: review a Ready task, then claim it with its revision");
|
|
12077
|
+
else for (const record11 of records) printRecord(ctx, entity, record11);
|
|
12021
12078
|
}
|
|
12079
|
+
ctx.out.log(`next: ${guidance}`);
|
|
12022
12080
|
});
|
|
12023
12081
|
}
|
|
12024
12082
|
async function pmHandoff(ctx, parsed) {
|
|
12025
|
-
const appId =
|
|
12026
|
-
const
|
|
12027
|
-
if (!appId) throw new Error("pm handoff needs --app <appId>");
|
|
12083
|
+
const { appId, projectId } = requireApp(ctx, parsed, "handoff");
|
|
12084
|
+
const verbose = wantsVerbose(parsed);
|
|
12028
12085
|
const [goals, tasks, bugs] = await Promise.all([
|
|
12029
|
-
|
|
12030
|
-
|
|
12031
|
-
|
|
12086
|
+
listFiltered(ctx, "goal", appId, projectId, { status: UNMET_GOAL_STATUS }),
|
|
12087
|
+
listFiltered(ctx, "task", appId, projectId, { column: ACTIVE_TASK_COLUMNS }),
|
|
12088
|
+
listFiltered(ctx, "bug", appId, projectId, { status: OPEN_BUG_STATUSES })
|
|
12032
12089
|
]);
|
|
12033
|
-
const
|
|
12090
|
+
const clean4 = !goals.length && !tasks.length && !bugs.length;
|
|
12091
|
+
const result = {
|
|
12034
12092
|
appId,
|
|
12035
12093
|
projectId,
|
|
12036
|
-
unmetGoals:
|
|
12037
|
-
activeTasks:
|
|
12038
|
-
openBugs:
|
|
12039
|
-
|
|
12040
|
-
const result = {
|
|
12041
|
-
...handoff,
|
|
12042
|
-
clean: !handoff.unmetGoals.length && !handoff.activeTasks.length && !handoff.openBugs.length
|
|
12094
|
+
unmetGoals: leanRecords("goal", goals, verbose),
|
|
12095
|
+
activeTasks: leanRecords("task", tasks, verbose),
|
|
12096
|
+
openBugs: leanRecords("bug", bugs, verbose),
|
|
12097
|
+
clean: clean4
|
|
12043
12098
|
};
|
|
12044
12099
|
emit2(ctx, result, () => {
|
|
12045
|
-
if (
|
|
12100
|
+
if (clean4) {
|
|
12046
12101
|
ctx.out.log(`${appId}: no unresolved PM work`);
|
|
12047
12102
|
return;
|
|
12048
12103
|
}
|
|
12049
12104
|
ctx.out.log(`${appId}: authoritative PM handoff`);
|
|
12050
|
-
for (const [label, records] of [
|
|
12051
|
-
["unmet goals",
|
|
12052
|
-
["active tasks",
|
|
12053
|
-
["open bugs",
|
|
12105
|
+
for (const [label, entity, records] of [
|
|
12106
|
+
["unmet goals", "goal", goals],
|
|
12107
|
+
["active tasks", "task", tasks],
|
|
12108
|
+
["open bugs", "bug", bugs]
|
|
12054
12109
|
]) {
|
|
12055
12110
|
ctx.out.log(`${label}:`);
|
|
12056
12111
|
if (!records.length) ctx.out.log("- (none)");
|
|
12057
|
-
else for (const record11 of records) printRecord(
|
|
12058
|
-
ctx,
|
|
12059
|
-
label === "unmet goals" ? "goal" : label === "active tasks" ? "task" : "bug",
|
|
12060
|
-
record11
|
|
12061
|
-
);
|
|
12112
|
+
else for (const record11 of records) printRecord(ctx, entity, record11);
|
|
12062
12113
|
}
|
|
12063
12114
|
});
|
|
12064
12115
|
}
|
|
12065
|
-
|
|
12066
|
-
|
|
12067
|
-
|
|
12116
|
+
|
|
12117
|
+
// src/pm-start.ts
|
|
12118
|
+
var getTask = async (ctx, id2) => (await pmRequest(ctx, "GET", `/task/${encodeURIComponent(id2)}`)).record;
|
|
12119
|
+
async function claimAt(ctx, task, parsed) {
|
|
12120
|
+
const res = await pmRequest(
|
|
12121
|
+
ctx,
|
|
12122
|
+
"POST",
|
|
12123
|
+
`/task/${encodeURIComponent(task.id)}/claim`,
|
|
12124
|
+
{ expectedRevision: task.revision, mutationId: writeMutationId2(parsed) }
|
|
12125
|
+
);
|
|
12126
|
+
return res.record;
|
|
12127
|
+
}
|
|
12128
|
+
async function selectTask(ctx, parsed, appId, projectId) {
|
|
12129
|
+
const named = parsed.positionals[2];
|
|
12130
|
+
if (named) return getTask(ctx, named);
|
|
12131
|
+
const goalId = stringOpt(parsed.options.goal);
|
|
12132
|
+
const ready = await listFiltered(ctx, "task", appId, projectId, {
|
|
12133
|
+
column: READY_COLUMN,
|
|
12134
|
+
...goalId ? { goalId } : {}
|
|
12135
|
+
});
|
|
12136
|
+
const candidate = ready[0];
|
|
12137
|
+
if (!candidate) {
|
|
12138
|
+
throw new Error(
|
|
12139
|
+
`no Ready task to claim in ${appId}${goalId ? ` for goal ${goalId}` : ""}. Run "odla-ai pm next --app ${appId}" to see open goals and Backlog work, then "odla-ai pm task ready <id>" once a task has a complete contract.`
|
|
12140
|
+
);
|
|
12141
|
+
}
|
|
12142
|
+
return candidate;
|
|
12143
|
+
}
|
|
12144
|
+
async function pmStart(ctx, parsed) {
|
|
12145
|
+
const appId = stringOpt(parsed.options.app) ?? ctx.appId;
|
|
12146
|
+
if (!appId) throw new Error("pm start needs --app <appId>");
|
|
12147
|
+
const projectId = stringOpt(parsed.options.project) ?? ctx.projectId;
|
|
12148
|
+
const verbose = wantsVerbose(parsed);
|
|
12149
|
+
const selected = await selectTask(ctx, parsed, appId, projectId);
|
|
12150
|
+
if (selected.column !== READY_COLUMN) {
|
|
12151
|
+
throw new Error(
|
|
12152
|
+
`task ${selected.id} is ${selected.column === "doing" ? "already claimed" : `in ${selected.column}`}, not Ready. Only Ready work can be started; run "odla-ai pm task ready ${selected.id} --expected-revision ${selected.revision}" once its goal, description, and acceptance criteria are complete.`
|
|
12153
|
+
);
|
|
12154
|
+
}
|
|
12155
|
+
let claimed;
|
|
12156
|
+
try {
|
|
12157
|
+
claimed = await claimAt(ctx, selected, parsed);
|
|
12158
|
+
} catch (error) {
|
|
12159
|
+
if (!isRevisionConflict(error)) throw error;
|
|
12160
|
+
const reloaded = await getTask(ctx, selected.id);
|
|
12161
|
+
if (reloaded.column !== READY_COLUMN) {
|
|
12162
|
+
throw new Error(
|
|
12163
|
+
`task ${selected.id} was taken while starting it (now ${reloaded.column}). Run "odla-ai pm start --app ${appId}" again to take the next Ready task.`
|
|
12164
|
+
);
|
|
12165
|
+
}
|
|
12166
|
+
claimed = await claimAt(ctx, reloaded, parsed);
|
|
12167
|
+
}
|
|
12168
|
+
const task = claimed ?? await getTask(ctx, selected.id);
|
|
12169
|
+
const goalId = typeof task.goalId === "string" ? task.goalId : void 0;
|
|
12170
|
+
const goal = goalId ? await pmRequest(ctx, "GET", `/goal/${encodeURIComponent(goalId)}`).then((res) => res.record).catch(() => null) : null;
|
|
12171
|
+
const result = {
|
|
12172
|
+
claimed: verbose ? task : leanRecord("task", task),
|
|
12173
|
+
goal: goal ? verbose ? goal : leanRecord("goal", goal) : null,
|
|
12174
|
+
next: 'work the acceptance criteria, then "odla-ai pm task done <id>" (or "odla-ai pm task release <id> --expected-revision <n>" to hand it back)'
|
|
12175
|
+
};
|
|
12176
|
+
emit2(ctx, result, () => {
|
|
12177
|
+
ctx.out.log(`claimed: ${studioRecordLink(ctx, "task", task)}`);
|
|
12178
|
+
if (goal) ctx.out.log(`goal: ${studioRecordLink(ctx, "goal", goal)}`);
|
|
12179
|
+
if (typeof task.acceptanceCriteria === "string") ctx.out.log(`acceptance: ${task.acceptanceCriteria}`);
|
|
12180
|
+
ctx.out.log(`next: ${result.next}`);
|
|
12181
|
+
});
|
|
12068
12182
|
}
|
|
12069
12183
|
|
|
12070
12184
|
// src/pm-links.ts
|
|
@@ -12538,9 +12652,13 @@ async function pmCommand(parsed, deps = {}) {
|
|
|
12538
12652
|
throw new Error(`unknown pm project action "${action3}". Try list|add|use.`);
|
|
12539
12653
|
}
|
|
12540
12654
|
if (word === "next") {
|
|
12541
|
-
assertArgs(parsed, [...COMMON_OPTIONS, "app", "project"], 2);
|
|
12655
|
+
assertArgs(parsed, [...COMMON_OPTIONS, "app", "project", "verbose"], 2);
|
|
12542
12656
|
return pmNext(await buildContext2(parsed, deps), parsed);
|
|
12543
12657
|
}
|
|
12658
|
+
if (word === "start") {
|
|
12659
|
+
assertArgs(parsed, [...COMMON_OPTIONS, "app", "project", "goal", "verbose", "mutation-id"], 3);
|
|
12660
|
+
return pmStart(await buildContext2(parsed, deps), parsed);
|
|
12661
|
+
}
|
|
12544
12662
|
if (word === "watch") {
|
|
12545
12663
|
assertArgs(parsed, [
|
|
12546
12664
|
...COMMON_OPTIONS,
|
|
@@ -12558,11 +12676,11 @@ async function pmCommand(parsed, deps = {}) {
|
|
|
12558
12676
|
return pmWatch(await buildContext2(parsed, deps), parsed).then(() => void 0);
|
|
12559
12677
|
}
|
|
12560
12678
|
if (word === "handoff") {
|
|
12561
|
-
assertArgs(parsed, [...COMMON_OPTIONS, "app", "project"], 2);
|
|
12679
|
+
assertArgs(parsed, [...COMMON_OPTIONS, "app", "project", "verbose"], 2);
|
|
12562
12680
|
return pmHandoff(await buildContext2(parsed, deps), parsed);
|
|
12563
12681
|
}
|
|
12564
12682
|
const entity = ALIASES[word];
|
|
12565
|
-
if (!entity) throw new Error(`unknown pm entity "${word}". Try "odla-ai pm bug list" (goal|task|decision|bug).`);
|
|
12683
|
+
if (!entity) throw new Error(`unknown pm entity "${word}". Try "odla-ai pm start" to claim Ready work, or "odla-ai pm bug list" (goal|task|decision|bug).`);
|
|
12566
12684
|
const requestedAction = parsed.positionals[2] ?? "list";
|
|
12567
12685
|
const action2 = canonicalAction(requestedAction);
|
|
12568
12686
|
if (!action2) throw new Error(`unknown pm action "${requestedAction}". Try list|add|get|set|done|link|ref|comment|comments|history|rm.`);
|