@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/dist/index.cjs CHANGED
@@ -6486,13 +6486,14 @@ Usage:
6486
6486
  odla-ai app owners add <email> [--email <odla-account>] [--json]
6487
6487
  odla-ai app owners remove <email> [--email <odla-account>] [--json]
6488
6488
  odla-ai pm <goal|task|decision|bug> list [--app <id>] [--status <s>] [--severity <s>] [--column <c>] [--q <text>] [--json]
6489
- odla-ai pm <goal|task|decision|bug> add --app <id> --title <t> [--severity high|--column doing|--goal <id>|--body <text>|--proof <text>]
6489
+ odla-ai pm <goal|task|decision|bug> add --app <id> --title <t> [--severity high|--column doing|--goal <id>|--description <text>|--body <text>|--proof <text>]
6490
6490
  odla-ai pm <goal|task|decision|bug> get <id> [--json]
6491
- odla-ai pm <goal|task|decision|bug> set <id> [--status <s>|--column <c>|--assignee <id>|--no-assignee|--goal <id>]
6492
- odla-ai pm <goal|task|decision|bug> done <id>
6491
+ odla-ai pm <goal|task|decision|bug> set <id> [--status <s>|--column <c>|--assignee <id>|--no-assignee|--goal <id>|--decision <id>]
6492
+ odla-ai pm <goal|task|decision|bug> done <id> [--decision <accepted-decision-id>]
6493
6493
  odla-ai pm <goal|task|decision|bug> comment <id> --body "..."
6494
6494
  odla-ai pm <goal|task|decision|bug> comments <id> [--json]
6495
6495
  odla-ai pm <goal|task|decision|bug> rm <id>
6496
+ odla-ai pm handoff --app <id> [--json]
6496
6497
  odla-ai whoami [--json]
6497
6498
  odla-ai runbook ask "<question>" [--app <id>] [--all] [--json]
6498
6499
  odla-ai runbook search "<question>" [--app <id>] [--all] [--limit <n>] [--json]
@@ -7251,6 +7252,7 @@ var FIELD_MAP = {
7251
7252
  column: { key: "column" },
7252
7253
  rank: { key: "rank", num: true },
7253
7254
  goal: { key: "goalId" },
7255
+ decision: { key: "decisionId" },
7254
7256
  assignee: { key: "assigneeId" },
7255
7257
  due: { key: "dueAt", num: true },
7256
7258
  proof: { key: "proof" },
@@ -7289,6 +7291,14 @@ function collectFields(parsed, allowClear) {
7289
7291
  }
7290
7292
  return out;
7291
7293
  }
7294
+ function collectEntityFields(entity, parsed, allowClear) {
7295
+ const fields = collectFields(parsed, allowClear);
7296
+ if ((entity === "task" || entity === "bug") && fields.body !== void 0) {
7297
+ if (fields.description === void 0) fields.description = fields.body;
7298
+ delete fields.body;
7299
+ }
7300
+ return fields;
7301
+ }
7292
7302
  function statusCol(entity, r) {
7293
7303
  if (entity === "bug") return `${r.status ?? ""}/${r.severity ?? ""}`;
7294
7304
  if (entity === "task") return String(r.column ?? "");
@@ -7303,7 +7313,14 @@ function emit(ctx, value, human) {
7303
7313
  }
7304
7314
  async function pmList(ctx, entity, parsed) {
7305
7315
  const q = new URLSearchParams();
7306
- const filters = { status: "status", severity: "severity", column: "column", goal: "goalId", assignee: "assigneeId" };
7316
+ const filters = {
7317
+ status: "status",
7318
+ severity: "severity",
7319
+ column: "column",
7320
+ goal: "goalId",
7321
+ assignee: "assigneeId",
7322
+ decision: "decisionId"
7323
+ };
7307
7324
  const app = stringOpt(parsed.options.app);
7308
7325
  if (app) q.set("app", app);
7309
7326
  for (const [flag, param] of Object.entries(filters)) {
@@ -7325,8 +7342,10 @@ async function pmList(ctx, entity, parsed) {
7325
7342
  async function pmAdd(ctx, entity, parsed) {
7326
7343
  const appId = stringOpt(parsed.options.app);
7327
7344
  if (!appId) throw new Error("pm add needs --app <appId>");
7328
- const input = collectFields(parsed, false);
7345
+ const input = collectEntityFields(entity, parsed, false);
7329
7346
  if (!input.title) throw new Error("pm add needs --title <title>");
7347
+ if (entity === "bug" && !input.description)
7348
+ throw new Error("pm bug add needs --description <context> (or --body <context>)");
7330
7349
  const res = await pmRequest(ctx, "POST", `/${entity}`, { appId, input });
7331
7350
  emit(ctx, res, () => ctx.out.log(`created ${entity} ${res.id}`));
7332
7351
  }
@@ -7335,16 +7354,71 @@ async function pmGet(ctx, entity, id) {
7335
7354
  emit(ctx, record5, () => printRecord(ctx, entity, record5));
7336
7355
  }
7337
7356
  async function pmSet(ctx, entity, id, parsed) {
7338
- const patch2 = collectFields(parsed, true);
7357
+ const patch2 = collectEntityFields(entity, parsed, true);
7339
7358
  if (Object.keys(patch2).length === 0)
7340
7359
  throw new Error("pm set needs at least one field flag (e.g. --status doing, --assignee me, --no-assignee)");
7341
7360
  const res = await pmRequest(ctx, "PATCH", `/${entity}/${encodeURIComponent(id)}`, { patch: patch2 });
7342
7361
  emit(ctx, res, () => ctx.out.log(res.record ? `${res.record.id} [${statusCol(entity, res.record)}] ${res.record.appId} ${res.record.title ?? ""}` : `updated ${entity} ${id}`));
7343
7362
  }
7344
- async function pmDone(ctx, entity, id) {
7345
- const res = await pmRequest(ctx, "PATCH", `/${entity}/${encodeURIComponent(id)}`, { patch: DONE[entity] });
7363
+ async function pmDone(ctx, entity, id, parsed) {
7364
+ const decisionId = stringOpt(parsed.options.decision);
7365
+ if (decisionId && entity !== "bug") throw new Error("--decision is only valid when completing a bug");
7366
+ const patch2 = { ...DONE[entity], ...decisionId ? { decisionId } : {} };
7367
+ const res = await pmRequest(ctx, "PATCH", `/${entity}/${encodeURIComponent(id)}`, { patch: patch2 });
7346
7368
  emit(ctx, res, () => ctx.out.log(`${entity} ${id} \u2192 done`));
7347
7369
  }
7370
+ async function allRecords(ctx, entity, appId) {
7371
+ const records = [];
7372
+ for (; ; ) {
7373
+ const q = new URLSearchParams({
7374
+ app: appId,
7375
+ limit: "100",
7376
+ offset: String(records.length)
7377
+ });
7378
+ const page = await pmRequest(ctx, "GET", `/${entity}?${q}`);
7379
+ records.push(...page.records);
7380
+ if (records.length >= page.total || page.records.length === 0) return records;
7381
+ }
7382
+ }
7383
+ async function pmHandoff(ctx, parsed) {
7384
+ const appId = stringOpt(parsed.options.app);
7385
+ if (!appId) throw new Error("pm handoff needs --app <appId>");
7386
+ const [goals, tasks, bugs] = await Promise.all([
7387
+ allRecords(ctx, "goal", appId),
7388
+ allRecords(ctx, "task", appId),
7389
+ allRecords(ctx, "bug", appId)
7390
+ ]);
7391
+ const handoff = {
7392
+ appId,
7393
+ unmetGoals: goals.filter((record5) => record5.status !== "met"),
7394
+ activeTasks: tasks.filter((record5) => record5.column !== "done"),
7395
+ openBugs: bugs.filter((record5) => record5.status !== "fixed" && record5.status !== "wontfix")
7396
+ };
7397
+ const result = {
7398
+ ...handoff,
7399
+ clean: !handoff.unmetGoals.length && !handoff.activeTasks.length && !handoff.openBugs.length
7400
+ };
7401
+ emit(ctx, result, () => {
7402
+ if (result.clean) {
7403
+ ctx.out.log(`${appId}: no unresolved PM work`);
7404
+ return;
7405
+ }
7406
+ ctx.out.log(`${appId}: authoritative PM handoff`);
7407
+ for (const [label, records] of [
7408
+ ["unmet goals", result.unmetGoals],
7409
+ ["active tasks", result.activeTasks],
7410
+ ["open bugs", result.openBugs]
7411
+ ]) {
7412
+ ctx.out.log(`${label}:`);
7413
+ if (!records.length) ctx.out.log("- (none)");
7414
+ else for (const record5 of records) printRecord(
7415
+ ctx,
7416
+ label === "unmet goals" ? "goal" : label === "active tasks" ? "task" : "bug",
7417
+ record5
7418
+ );
7419
+ }
7420
+ });
7421
+ }
7348
7422
  async function pmComment(ctx, entity, id, parsed) {
7349
7423
  const body = stringOpt(parsed.options.body);
7350
7424
  if (!body) throw new Error('pm comment needs --body "..."');
@@ -7395,6 +7469,7 @@ var ALLOWED = [
7395
7469
  "target",
7396
7470
  "description",
7397
7471
  "desc",
7472
+ "decision",
7398
7473
  "limit",
7399
7474
  "offset",
7400
7475
  "q"
@@ -7417,6 +7492,10 @@ async function buildContext(parsed, deps) {
7417
7492
  }
7418
7493
  async function pmCommand(parsed, deps = {}) {
7419
7494
  const word = parsed.positionals[1] ?? "";
7495
+ if (word === "handoff") {
7496
+ assertArgs(parsed, ALLOWED, 2);
7497
+ return pmHandoff(await buildContext(parsed, deps), parsed);
7498
+ }
7420
7499
  const entity = ALIASES[word];
7421
7500
  if (!entity) throw new Error(`unknown pm entity "${word}". Try "odla-ai pm bug list" (goal|task|decision|bug).`);
7422
7501
  const action = parsed.positionals[2] ?? "list";
@@ -7437,7 +7516,7 @@ async function pmCommand(parsed, deps = {}) {
7437
7516
  case "move":
7438
7517
  return pmSet(ctx, entity, requireId(id, action), parsed);
7439
7518
  case "done":
7440
- return pmDone(ctx, entity, requireId(id, action));
7519
+ return pmDone(ctx, entity, requireId(id, action), parsed);
7441
7520
  case "comment":
7442
7521
  return pmComment(ctx, entity, requireId(id, action), parsed);
7443
7522
  case "comments":
@@ -7913,7 +7992,10 @@ var COMMAND_SURFACE = {
7913
7992
  doctor: {},
7914
7993
  help: {},
7915
7994
  init: {},
7916
- pm: PM_ENTITIES,
7995
+ pm: {
7996
+ ...PM_ENTITIES,
7997
+ handoff: {}
7998
+ },
7917
7999
  provision: {},
7918
8000
  runbook: {
7919
8001
  ask: {},