@odla-ai/cli 0.23.0 → 0.24.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/README.md +8 -0
- package/dist/bin.cjs +92 -10
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-RZDRUVBT.js → chunk-7P7MI4WX.js} +93 -11
- package/dist/chunk-7P7MI4WX.js.map +1 -0
- package/dist/index.cjs +92 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-RZDRUVBT.js.map +0 -1
package/README.md
CHANGED
|
@@ -43,12 +43,20 @@ app is registered, start each session by reading its doing tasks, open bugs and
|
|
|
43
43
|
goals, and recent decisions:
|
|
44
44
|
|
|
45
45
|
```bash
|
|
46
|
+
npx odla-ai pm handoff --app <appId>
|
|
46
47
|
npx odla-ai pm task list --app <appId> --column doing
|
|
47
48
|
npx odla-ai pm bug list --app <appId> --status open
|
|
48
49
|
npx odla-ai pm goal list --app <appId> --status open
|
|
49
50
|
npx odla-ai pm decision list --app <appId> --limit 20
|
|
50
51
|
```
|
|
51
52
|
|
|
53
|
+
`pm handoff` is the authoritative unresolved-state view: unmet goals, every
|
|
54
|
+
non-done task, and every open or triaged bug. File bugs with actionable context
|
|
55
|
+
(`pm bug add ... --description "reproduction/evidence"`); a title-only bug is
|
|
56
|
+
rejected. When an accepted decision resolves or explicitly retains a bug, link
|
|
57
|
+
it with `pm bug done <id> --decision <decision-id>` or
|
|
58
|
+
`pm bug set <id> --status triaged --decision <decision-id>`.
|
|
59
|
+
|
|
52
60
|
Read the active task, linked goal, and comments before changing code. Without
|
|
53
61
|
`--app`, list commands span every project the account co-owns, which is the
|
|
54
62
|
right first view when coordinating several efforts; scope every write to the
|
package/dist/bin.cjs
CHANGED
|
@@ -6426,13 +6426,14 @@ Usage:
|
|
|
6426
6426
|
odla-ai app owners add <email> [--email <odla-account>] [--json]
|
|
6427
6427
|
odla-ai app owners remove <email> [--email <odla-account>] [--json]
|
|
6428
6428
|
odla-ai pm <goal|task|decision|bug> list [--app <id>] [--status <s>] [--severity <s>] [--column <c>] [--q <text>] [--json]
|
|
6429
|
-
odla-ai pm <goal|task|decision|bug> add --app <id> --title <t> [--severity high|--column doing|--goal <id>|--body <text>|--proof <text>]
|
|
6429
|
+
odla-ai pm <goal|task|decision|bug> add --app <id> --title <t> [--severity high|--column doing|--goal <id>|--description <text>|--body <text>|--proof <text>]
|
|
6430
6430
|
odla-ai pm <goal|task|decision|bug> get <id> [--json]
|
|
6431
|
-
odla-ai pm <goal|task|decision|bug> set <id> [--status <s>|--column <c>|--assignee <id>|--no-assignee|--goal <id>]
|
|
6432
|
-
odla-ai pm <goal|task|decision|bug> done <id>
|
|
6431
|
+
odla-ai pm <goal|task|decision|bug> set <id> [--status <s>|--column <c>|--assignee <id>|--no-assignee|--goal <id>|--decision <id>]
|
|
6432
|
+
odla-ai pm <goal|task|decision|bug> done <id> [--decision <accepted-decision-id>]
|
|
6433
6433
|
odla-ai pm <goal|task|decision|bug> comment <id> --body "..."
|
|
6434
6434
|
odla-ai pm <goal|task|decision|bug> comments <id> [--json]
|
|
6435
6435
|
odla-ai pm <goal|task|decision|bug> rm <id>
|
|
6436
|
+
odla-ai pm handoff --app <id> [--json]
|
|
6436
6437
|
odla-ai whoami [--json]
|
|
6437
6438
|
odla-ai runbook ask "<question>" [--app <id>] [--all] [--json]
|
|
6438
6439
|
odla-ai runbook search "<question>" [--app <id>] [--all] [--limit <n>] [--json]
|
|
@@ -7191,6 +7192,7 @@ var FIELD_MAP = {
|
|
|
7191
7192
|
column: { key: "column" },
|
|
7192
7193
|
rank: { key: "rank", num: true },
|
|
7193
7194
|
goal: { key: "goalId" },
|
|
7195
|
+
decision: { key: "decisionId" },
|
|
7194
7196
|
assignee: { key: "assigneeId" },
|
|
7195
7197
|
due: { key: "dueAt", num: true },
|
|
7196
7198
|
proof: { key: "proof" },
|
|
@@ -7229,6 +7231,14 @@ function collectFields(parsed, allowClear) {
|
|
|
7229
7231
|
}
|
|
7230
7232
|
return out;
|
|
7231
7233
|
}
|
|
7234
|
+
function collectEntityFields(entity, parsed, allowClear) {
|
|
7235
|
+
const fields = collectFields(parsed, allowClear);
|
|
7236
|
+
if ((entity === "task" || entity === "bug") && fields.body !== void 0) {
|
|
7237
|
+
if (fields.description === void 0) fields.description = fields.body;
|
|
7238
|
+
delete fields.body;
|
|
7239
|
+
}
|
|
7240
|
+
return fields;
|
|
7241
|
+
}
|
|
7232
7242
|
function statusCol(entity, r) {
|
|
7233
7243
|
if (entity === "bug") return `${r.status ?? ""}/${r.severity ?? ""}`;
|
|
7234
7244
|
if (entity === "task") return String(r.column ?? "");
|
|
@@ -7243,7 +7253,14 @@ function emit(ctx, value, human) {
|
|
|
7243
7253
|
}
|
|
7244
7254
|
async function pmList(ctx, entity, parsed) {
|
|
7245
7255
|
const q = new URLSearchParams();
|
|
7246
|
-
const filters = {
|
|
7256
|
+
const filters = {
|
|
7257
|
+
status: "status",
|
|
7258
|
+
severity: "severity",
|
|
7259
|
+
column: "column",
|
|
7260
|
+
goal: "goalId",
|
|
7261
|
+
assignee: "assigneeId",
|
|
7262
|
+
decision: "decisionId"
|
|
7263
|
+
};
|
|
7247
7264
|
const app = stringOpt(parsed.options.app);
|
|
7248
7265
|
if (app) q.set("app", app);
|
|
7249
7266
|
for (const [flag, param] of Object.entries(filters)) {
|
|
@@ -7265,8 +7282,10 @@ async function pmList(ctx, entity, parsed) {
|
|
|
7265
7282
|
async function pmAdd(ctx, entity, parsed) {
|
|
7266
7283
|
const appId = stringOpt(parsed.options.app);
|
|
7267
7284
|
if (!appId) throw new Error("pm add needs --app <appId>");
|
|
7268
|
-
const input =
|
|
7285
|
+
const input = collectEntityFields(entity, parsed, false);
|
|
7269
7286
|
if (!input.title) throw new Error("pm add needs --title <title>");
|
|
7287
|
+
if (entity === "bug" && !input.description)
|
|
7288
|
+
throw new Error("pm bug add needs --description <context> (or --body <context>)");
|
|
7270
7289
|
const res = await pmRequest(ctx, "POST", `/${entity}`, { appId, input });
|
|
7271
7290
|
emit(ctx, res, () => ctx.out.log(`created ${entity} ${res.id}`));
|
|
7272
7291
|
}
|
|
@@ -7275,16 +7294,71 @@ async function pmGet(ctx, entity, id) {
|
|
|
7275
7294
|
emit(ctx, record5, () => printRecord(ctx, entity, record5));
|
|
7276
7295
|
}
|
|
7277
7296
|
async function pmSet(ctx, entity, id, parsed) {
|
|
7278
|
-
const patch2 =
|
|
7297
|
+
const patch2 = collectEntityFields(entity, parsed, true);
|
|
7279
7298
|
if (Object.keys(patch2).length === 0)
|
|
7280
7299
|
throw new Error("pm set needs at least one field flag (e.g. --status doing, --assignee me, --no-assignee)");
|
|
7281
7300
|
const res = await pmRequest(ctx, "PATCH", `/${entity}/${encodeURIComponent(id)}`, { patch: patch2 });
|
|
7282
7301
|
emit(ctx, res, () => ctx.out.log(res.record ? `${res.record.id} [${statusCol(entity, res.record)}] ${res.record.appId} ${res.record.title ?? ""}` : `updated ${entity} ${id}`));
|
|
7283
7302
|
}
|
|
7284
|
-
async function pmDone(ctx, entity, id) {
|
|
7285
|
-
const
|
|
7303
|
+
async function pmDone(ctx, entity, id, parsed) {
|
|
7304
|
+
const decisionId = stringOpt(parsed.options.decision);
|
|
7305
|
+
if (decisionId && entity !== "bug") throw new Error("--decision is only valid when completing a bug");
|
|
7306
|
+
const patch2 = { ...DONE[entity], ...decisionId ? { decisionId } : {} };
|
|
7307
|
+
const res = await pmRequest(ctx, "PATCH", `/${entity}/${encodeURIComponent(id)}`, { patch: patch2 });
|
|
7286
7308
|
emit(ctx, res, () => ctx.out.log(`${entity} ${id} \u2192 done`));
|
|
7287
7309
|
}
|
|
7310
|
+
async function allRecords(ctx, entity, appId) {
|
|
7311
|
+
const records = [];
|
|
7312
|
+
for (; ; ) {
|
|
7313
|
+
const q = new URLSearchParams({
|
|
7314
|
+
app: appId,
|
|
7315
|
+
limit: "100",
|
|
7316
|
+
offset: String(records.length)
|
|
7317
|
+
});
|
|
7318
|
+
const page = await pmRequest(ctx, "GET", `/${entity}?${q}`);
|
|
7319
|
+
records.push(...page.records);
|
|
7320
|
+
if (records.length >= page.total || page.records.length === 0) return records;
|
|
7321
|
+
}
|
|
7322
|
+
}
|
|
7323
|
+
async function pmHandoff(ctx, parsed) {
|
|
7324
|
+
const appId = stringOpt(parsed.options.app);
|
|
7325
|
+
if (!appId) throw new Error("pm handoff needs --app <appId>");
|
|
7326
|
+
const [goals, tasks, bugs] = await Promise.all([
|
|
7327
|
+
allRecords(ctx, "goal", appId),
|
|
7328
|
+
allRecords(ctx, "task", appId),
|
|
7329
|
+
allRecords(ctx, "bug", appId)
|
|
7330
|
+
]);
|
|
7331
|
+
const handoff = {
|
|
7332
|
+
appId,
|
|
7333
|
+
unmetGoals: goals.filter((record5) => record5.status !== "met"),
|
|
7334
|
+
activeTasks: tasks.filter((record5) => record5.column !== "done"),
|
|
7335
|
+
openBugs: bugs.filter((record5) => record5.status !== "fixed" && record5.status !== "wontfix")
|
|
7336
|
+
};
|
|
7337
|
+
const result = {
|
|
7338
|
+
...handoff,
|
|
7339
|
+
clean: !handoff.unmetGoals.length && !handoff.activeTasks.length && !handoff.openBugs.length
|
|
7340
|
+
};
|
|
7341
|
+
emit(ctx, result, () => {
|
|
7342
|
+
if (result.clean) {
|
|
7343
|
+
ctx.out.log(`${appId}: no unresolved PM work`);
|
|
7344
|
+
return;
|
|
7345
|
+
}
|
|
7346
|
+
ctx.out.log(`${appId}: authoritative PM handoff`);
|
|
7347
|
+
for (const [label, records] of [
|
|
7348
|
+
["unmet goals", result.unmetGoals],
|
|
7349
|
+
["active tasks", result.activeTasks],
|
|
7350
|
+
["open bugs", result.openBugs]
|
|
7351
|
+
]) {
|
|
7352
|
+
ctx.out.log(`${label}:`);
|
|
7353
|
+
if (!records.length) ctx.out.log("- (none)");
|
|
7354
|
+
else for (const record5 of records) printRecord(
|
|
7355
|
+
ctx,
|
|
7356
|
+
label === "unmet goals" ? "goal" : label === "active tasks" ? "task" : "bug",
|
|
7357
|
+
record5
|
|
7358
|
+
);
|
|
7359
|
+
}
|
|
7360
|
+
});
|
|
7361
|
+
}
|
|
7288
7362
|
async function pmComment(ctx, entity, id, parsed) {
|
|
7289
7363
|
const body = stringOpt(parsed.options.body);
|
|
7290
7364
|
if (!body) throw new Error('pm comment needs --body "..."');
|
|
@@ -7335,6 +7409,7 @@ var ALLOWED = [
|
|
|
7335
7409
|
"target",
|
|
7336
7410
|
"description",
|
|
7337
7411
|
"desc",
|
|
7412
|
+
"decision",
|
|
7338
7413
|
"limit",
|
|
7339
7414
|
"offset",
|
|
7340
7415
|
"q"
|
|
@@ -7357,6 +7432,10 @@ async function buildContext(parsed, deps) {
|
|
|
7357
7432
|
}
|
|
7358
7433
|
async function pmCommand(parsed, deps = {}) {
|
|
7359
7434
|
const word = parsed.positionals[1] ?? "";
|
|
7435
|
+
if (word === "handoff") {
|
|
7436
|
+
assertArgs(parsed, ALLOWED, 2);
|
|
7437
|
+
return pmHandoff(await buildContext(parsed, deps), parsed);
|
|
7438
|
+
}
|
|
7360
7439
|
const entity = ALIASES[word];
|
|
7361
7440
|
if (!entity) throw new Error(`unknown pm entity "${word}". Try "odla-ai pm bug list" (goal|task|decision|bug).`);
|
|
7362
7441
|
const action = parsed.positionals[2] ?? "list";
|
|
@@ -7377,7 +7456,7 @@ async function pmCommand(parsed, deps = {}) {
|
|
|
7377
7456
|
case "move":
|
|
7378
7457
|
return pmSet(ctx, entity, requireId(id, action), parsed);
|
|
7379
7458
|
case "done":
|
|
7380
|
-
return pmDone(ctx, entity, requireId(id, action));
|
|
7459
|
+
return pmDone(ctx, entity, requireId(id, action), parsed);
|
|
7381
7460
|
case "comment":
|
|
7382
7461
|
return pmComment(ctx, entity, requireId(id, action), parsed);
|
|
7383
7462
|
case "comments":
|
|
@@ -7853,7 +7932,10 @@ var COMMAND_SURFACE = {
|
|
|
7853
7932
|
doctor: {},
|
|
7854
7933
|
help: {},
|
|
7855
7934
|
init: {},
|
|
7856
|
-
pm:
|
|
7935
|
+
pm: {
|
|
7936
|
+
...PM_ENTITIES,
|
|
7937
|
+
handoff: {}
|
|
7938
|
+
},
|
|
7857
7939
|
provision: {},
|
|
7858
7940
|
runbook: {
|
|
7859
7941
|
ask: {},
|