@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/bin.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  exitCodeFor,
4
4
  runCli
5
- } from "./chunk-RZDRUVBT.js";
5
+ } from "./chunk-7P7MI4WX.js";
6
6
 
7
7
  // src/bin.ts
8
8
  runCli().catch((err) => {
@@ -5931,13 +5931,14 @@ Usage:
5931
5931
  odla-ai app owners add <email> [--email <odla-account>] [--json]
5932
5932
  odla-ai app owners remove <email> [--email <odla-account>] [--json]
5933
5933
  odla-ai pm <goal|task|decision|bug> list [--app <id>] [--status <s>] [--severity <s>] [--column <c>] [--q <text>] [--json]
5934
- odla-ai pm <goal|task|decision|bug> add --app <id> --title <t> [--severity high|--column doing|--goal <id>|--body <text>|--proof <text>]
5934
+ odla-ai pm <goal|task|decision|bug> add --app <id> --title <t> [--severity high|--column doing|--goal <id>|--description <text>|--body <text>|--proof <text>]
5935
5935
  odla-ai pm <goal|task|decision|bug> get <id> [--json]
5936
- odla-ai pm <goal|task|decision|bug> set <id> [--status <s>|--column <c>|--assignee <id>|--no-assignee|--goal <id>]
5937
- odla-ai pm <goal|task|decision|bug> done <id>
5936
+ odla-ai pm <goal|task|decision|bug> set <id> [--status <s>|--column <c>|--assignee <id>|--no-assignee|--goal <id>|--decision <id>]
5937
+ odla-ai pm <goal|task|decision|bug> done <id> [--decision <accepted-decision-id>]
5938
5938
  odla-ai pm <goal|task|decision|bug> comment <id> --body "..."
5939
5939
  odla-ai pm <goal|task|decision|bug> comments <id> [--json]
5940
5940
  odla-ai pm <goal|task|decision|bug> rm <id>
5941
+ odla-ai pm handoff --app <id> [--json]
5941
5942
  odla-ai whoami [--json]
5942
5943
  odla-ai runbook ask "<question>" [--app <id>] [--all] [--json]
5943
5944
  odla-ai runbook search "<question>" [--app <id>] [--all] [--limit <n>] [--json]
@@ -6784,7 +6785,10 @@ var COMMAND_SURFACE = {
6784
6785
  doctor: {},
6785
6786
  help: {},
6786
6787
  init: {},
6787
- pm: PM_ENTITIES,
6788
+ pm: {
6789
+ ...PM_ENTITIES,
6790
+ handoff: {}
6791
+ },
6788
6792
  provision: {},
6789
6793
  runbook: {
6790
6794
  ask: {},
@@ -7982,6 +7986,7 @@ var FIELD_MAP = {
7982
7986
  column: { key: "column" },
7983
7987
  rank: { key: "rank", num: true },
7984
7988
  goal: { key: "goalId" },
7989
+ decision: { key: "decisionId" },
7985
7990
  assignee: { key: "assigneeId" },
7986
7991
  due: { key: "dueAt", num: true },
7987
7992
  proof: { key: "proof" },
@@ -8020,6 +8025,14 @@ function collectFields(parsed, allowClear) {
8020
8025
  }
8021
8026
  return out;
8022
8027
  }
8028
+ function collectEntityFields(entity, parsed, allowClear) {
8029
+ const fields = collectFields(parsed, allowClear);
8030
+ if ((entity === "task" || entity === "bug") && fields.body !== void 0) {
8031
+ if (fields.description === void 0) fields.description = fields.body;
8032
+ delete fields.body;
8033
+ }
8034
+ return fields;
8035
+ }
8023
8036
  function statusCol(entity, r) {
8024
8037
  if (entity === "bug") return `${r.status ?? ""}/${r.severity ?? ""}`;
8025
8038
  if (entity === "task") return String(r.column ?? "");
@@ -8034,7 +8047,14 @@ function emit(ctx, value, human) {
8034
8047
  }
8035
8048
  async function pmList(ctx, entity, parsed) {
8036
8049
  const q = new URLSearchParams();
8037
- const filters = { status: "status", severity: "severity", column: "column", goal: "goalId", assignee: "assigneeId" };
8050
+ const filters = {
8051
+ status: "status",
8052
+ severity: "severity",
8053
+ column: "column",
8054
+ goal: "goalId",
8055
+ assignee: "assigneeId",
8056
+ decision: "decisionId"
8057
+ };
8038
8058
  const app = stringOpt(parsed.options.app);
8039
8059
  if (app) q.set("app", app);
8040
8060
  for (const [flag, param] of Object.entries(filters)) {
@@ -8056,8 +8076,10 @@ async function pmList(ctx, entity, parsed) {
8056
8076
  async function pmAdd(ctx, entity, parsed) {
8057
8077
  const appId = stringOpt(parsed.options.app);
8058
8078
  if (!appId) throw new Error("pm add needs --app <appId>");
8059
- const input = collectFields(parsed, false);
8079
+ const input = collectEntityFields(entity, parsed, false);
8060
8080
  if (!input.title) throw new Error("pm add needs --title <title>");
8081
+ if (entity === "bug" && !input.description)
8082
+ throw new Error("pm bug add needs --description <context> (or --body <context>)");
8061
8083
  const res = await pmRequest(ctx, "POST", `/${entity}`, { appId, input });
8062
8084
  emit(ctx, res, () => ctx.out.log(`created ${entity} ${res.id}`));
8063
8085
  }
@@ -8066,16 +8088,71 @@ async function pmGet(ctx, entity, id) {
8066
8088
  emit(ctx, record5, () => printRecord(ctx, entity, record5));
8067
8089
  }
8068
8090
  async function pmSet(ctx, entity, id, parsed) {
8069
- const patch2 = collectFields(parsed, true);
8091
+ const patch2 = collectEntityFields(entity, parsed, true);
8070
8092
  if (Object.keys(patch2).length === 0)
8071
8093
  throw new Error("pm set needs at least one field flag (e.g. --status doing, --assignee me, --no-assignee)");
8072
8094
  const res = await pmRequest(ctx, "PATCH", `/${entity}/${encodeURIComponent(id)}`, { patch: patch2 });
8073
8095
  emit(ctx, res, () => ctx.out.log(res.record ? `${res.record.id} [${statusCol(entity, res.record)}] ${res.record.appId} ${res.record.title ?? ""}` : `updated ${entity} ${id}`));
8074
8096
  }
8075
- async function pmDone(ctx, entity, id) {
8076
- const res = await pmRequest(ctx, "PATCH", `/${entity}/${encodeURIComponent(id)}`, { patch: DONE[entity] });
8097
+ async function pmDone(ctx, entity, id, parsed) {
8098
+ const decisionId = stringOpt(parsed.options.decision);
8099
+ if (decisionId && entity !== "bug") throw new Error("--decision is only valid when completing a bug");
8100
+ const patch2 = { ...DONE[entity], ...decisionId ? { decisionId } : {} };
8101
+ const res = await pmRequest(ctx, "PATCH", `/${entity}/${encodeURIComponent(id)}`, { patch: patch2 });
8077
8102
  emit(ctx, res, () => ctx.out.log(`${entity} ${id} \u2192 done`));
8078
8103
  }
8104
+ async function allRecords(ctx, entity, appId) {
8105
+ const records = [];
8106
+ for (; ; ) {
8107
+ const q = new URLSearchParams({
8108
+ app: appId,
8109
+ limit: "100",
8110
+ offset: String(records.length)
8111
+ });
8112
+ const page = await pmRequest(ctx, "GET", `/${entity}?${q}`);
8113
+ records.push(...page.records);
8114
+ if (records.length >= page.total || page.records.length === 0) return records;
8115
+ }
8116
+ }
8117
+ async function pmHandoff(ctx, parsed) {
8118
+ const appId = stringOpt(parsed.options.app);
8119
+ if (!appId) throw new Error("pm handoff needs --app <appId>");
8120
+ const [goals, tasks, bugs] = await Promise.all([
8121
+ allRecords(ctx, "goal", appId),
8122
+ allRecords(ctx, "task", appId),
8123
+ allRecords(ctx, "bug", appId)
8124
+ ]);
8125
+ const handoff = {
8126
+ appId,
8127
+ unmetGoals: goals.filter((record5) => record5.status !== "met"),
8128
+ activeTasks: tasks.filter((record5) => record5.column !== "done"),
8129
+ openBugs: bugs.filter((record5) => record5.status !== "fixed" && record5.status !== "wontfix")
8130
+ };
8131
+ const result = {
8132
+ ...handoff,
8133
+ clean: !handoff.unmetGoals.length && !handoff.activeTasks.length && !handoff.openBugs.length
8134
+ };
8135
+ emit(ctx, result, () => {
8136
+ if (result.clean) {
8137
+ ctx.out.log(`${appId}: no unresolved PM work`);
8138
+ return;
8139
+ }
8140
+ ctx.out.log(`${appId}: authoritative PM handoff`);
8141
+ for (const [label, records] of [
8142
+ ["unmet goals", result.unmetGoals],
8143
+ ["active tasks", result.activeTasks],
8144
+ ["open bugs", result.openBugs]
8145
+ ]) {
8146
+ ctx.out.log(`${label}:`);
8147
+ if (!records.length) ctx.out.log("- (none)");
8148
+ else for (const record5 of records) printRecord(
8149
+ ctx,
8150
+ label === "unmet goals" ? "goal" : label === "active tasks" ? "task" : "bug",
8151
+ record5
8152
+ );
8153
+ }
8154
+ });
8155
+ }
8079
8156
  async function pmComment(ctx, entity, id, parsed) {
8080
8157
  const body = stringOpt(parsed.options.body);
8081
8158
  if (!body) throw new Error('pm comment needs --body "..."');
@@ -8126,6 +8203,7 @@ var ALLOWED = [
8126
8203
  "target",
8127
8204
  "description",
8128
8205
  "desc",
8206
+ "decision",
8129
8207
  "limit",
8130
8208
  "offset",
8131
8209
  "q"
@@ -8148,6 +8226,10 @@ async function buildContext(parsed, deps) {
8148
8226
  }
8149
8227
  async function pmCommand(parsed, deps = {}) {
8150
8228
  const word = parsed.positionals[1] ?? "";
8229
+ if (word === "handoff") {
8230
+ assertArgs(parsed, ALLOWED, 2);
8231
+ return pmHandoff(await buildContext(parsed, deps), parsed);
8232
+ }
8151
8233
  const entity = ALIASES[word];
8152
8234
  if (!entity) throw new Error(`unknown pm entity "${word}". Try "odla-ai pm bug list" (goal|task|decision|bug).`);
8153
8235
  const action = parsed.positionals[2] ?? "list";
@@ -8168,7 +8250,7 @@ async function pmCommand(parsed, deps = {}) {
8168
8250
  case "move":
8169
8251
  return pmSet(ctx, entity, requireId(id, action), parsed);
8170
8252
  case "done":
8171
- return pmDone(ctx, entity, requireId(id, action));
8253
+ return pmDone(ctx, entity, requireId(id, action), parsed);
8172
8254
  case "comment":
8173
8255
  return pmComment(ctx, entity, requireId(id, action), parsed);
8174
8256
  case "comments":
@@ -9749,4 +9831,4 @@ export {
9749
9831
  exitCodeFor,
9750
9832
  runCli
9751
9833
  };
9752
- //# sourceMappingURL=chunk-RZDRUVBT.js.map
9834
+ //# sourceMappingURL=chunk-7P7MI4WX.js.map