hyper-pm 0.1.4 → 0.1.6

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.
Files changed (4) hide show
  1. package/README.md +23 -11
  2. package/dist/index.cjs +1530 -886
  3. package/dist/main.cjs +1530 -886
  4. package/package.json +3 -3
package/dist/main.cjs CHANGED
@@ -11530,6 +11530,103 @@ var normalizeTicketBranchListFromPayloadValue = (value) => {
11530
11530
  return normalizeTicketBranchListFromStrings(strings);
11531
11531
  };
11532
11532
 
11533
+ // src/lib/ticket-depends-on.ts
11534
+ var normalizeTicketDependsOnIds = (ids) => {
11535
+ const out = [];
11536
+ const seen = /* @__PURE__ */ new Set();
11537
+ for (const raw of ids) {
11538
+ const t = raw.trim();
11539
+ if (t === "") continue;
11540
+ if (seen.has(t)) continue;
11541
+ seen.add(t);
11542
+ out.push(t);
11543
+ }
11544
+ return out;
11545
+ };
11546
+ var ticketDependsOnListsEqual = (a, b) => {
11547
+ const an = normalizeTicketDependsOnIds(a ?? []);
11548
+ const bn = normalizeTicketDependsOnIds(b ?? []);
11549
+ if (an.length !== bn.length) return false;
11550
+ return an.every((x, i) => x === bn[i]);
11551
+ };
11552
+ var parseTicketDependsOnFromPayloadValue = (value) => {
11553
+ if (!Array.isArray(value)) return void 0;
11554
+ const strings = [];
11555
+ for (const x of value) {
11556
+ if (typeof x !== "string") return void 0;
11557
+ strings.push(x);
11558
+ }
11559
+ return normalizeTicketDependsOnIds(strings);
11560
+ };
11561
+ var parseTicketDependsOnFromFenceValue = (value) => {
11562
+ if (!Array.isArray(value)) return void 0;
11563
+ const strings = [];
11564
+ for (const x of value) {
11565
+ if (typeof x === "string") strings.push(x);
11566
+ }
11567
+ return normalizeTicketDependsOnIds(strings);
11568
+ };
11569
+ var wouldTicketDependsOnCreateCycle = (params) => {
11570
+ const { fromTicketId, nextDependsOn, successorsFor } = params;
11571
+ const dfsFromPrerequisite = (start) => {
11572
+ const stack = [start];
11573
+ const visited = /* @__PURE__ */ new Set();
11574
+ while (stack.length > 0) {
11575
+ const node = stack.pop();
11576
+ if (node === fromTicketId) return true;
11577
+ if (visited.has(node)) continue;
11578
+ visited.add(node);
11579
+ const next = successorsFor(node) ?? [];
11580
+ for (let i = next.length - 1; i >= 0; i -= 1) {
11581
+ stack.push(next[i]);
11582
+ }
11583
+ }
11584
+ return false;
11585
+ };
11586
+ for (const p of nextDependsOn) {
11587
+ if (dfsFromPrerequisite(p)) return true;
11588
+ }
11589
+ return false;
11590
+ };
11591
+ var ticketDependsOnSuccessorsForProjection = (projection, fromTicketId, nextDependsOn) => {
11592
+ return (ticketId) => {
11593
+ if (ticketId === fromTicketId) {
11594
+ return nextDependsOn.length > 0 ? nextDependsOn : void 0;
11595
+ }
11596
+ const row = projection.tickets.get(ticketId);
11597
+ if (!row || row.deleted) return void 0;
11598
+ return row.dependsOn;
11599
+ };
11600
+ };
11601
+ var validateTicketDependsOnForWrite = (params) => {
11602
+ const { projection, fromTicketId, nextDependsOn } = params;
11603
+ for (const id of nextDependsOn) {
11604
+ if (id === fromTicketId) {
11605
+ return `Ticket cannot depend on itself (${id})`;
11606
+ }
11607
+ const row = projection.tickets.get(id);
11608
+ if (row === void 0) {
11609
+ return `Dependency ticket not found: ${id}`;
11610
+ }
11611
+ if (row.deleted) {
11612
+ return `Dependency ticket deleted: ${id}`;
11613
+ }
11614
+ }
11615
+ const successorsFor = ticketDependsOnSuccessorsForProjection(
11616
+ projection,
11617
+ fromTicketId,
11618
+ nextDependsOn
11619
+ );
11620
+ if (wouldTicketDependsOnCreateCycle({
11621
+ fromTicketId,
11622
+ nextDependsOn,
11623
+ successorsFor
11624
+ })) {
11625
+ return "Ticket dependencies would create a cycle";
11626
+ }
11627
+ return void 0;
11628
+ };
11629
+
11533
11630
  // src/lib/ticket-planning-fields.ts
11534
11631
  var PRIORITY_SET = /* @__PURE__ */ new Set([
11535
11632
  "low",
@@ -11698,6 +11795,9 @@ var formatOutput = (format, value) => {
11698
11795
  return JSON.stringify(value);
11699
11796
  };
11700
11797
 
11798
+ // src/cli/normalize-raw-cli-argv.ts
11799
+ var normalizeRawCliArgv = (argv) => argv.map((token) => token === "--no-github" ? "--skip-network" : token);
11800
+
11701
11801
  // src/cli/prune-unchanged-work-item-update-payload.ts
11702
11802
  var branchListsEqual = (a, b) => a.length === b.length && a.every((x, i) => x === b[i]);
11703
11803
  var pruneEpicOrStoryUpdatePayloadAgainstRow = (cur, draft) => {
@@ -11753,6 +11853,20 @@ var pruneTicketUpdatePayloadAgainstRow = (cur, draft) => {
11753
11853
  out["labels"] = draft["labels"];
11754
11854
  }
11755
11855
  }
11856
+ if (draft["dependsOn"] !== void 0) {
11857
+ if (draft["dependsOn"] === null) {
11858
+ if (!ticketDependsOnListsEqual(cur.dependsOn, [])) {
11859
+ out["dependsOn"] = null;
11860
+ }
11861
+ } else {
11862
+ const parsed = parseTicketDependsOnFromPayloadValue(draft["dependsOn"]);
11863
+ if (parsed !== void 0) {
11864
+ if (!ticketDependsOnListsEqual(cur.dependsOn, parsed)) {
11865
+ out["dependsOn"] = parsed;
11866
+ }
11867
+ }
11868
+ }
11869
+ }
11756
11870
  if (draft["priority"] !== void 0) {
11757
11871
  const curP = cur.priority ?? null;
11758
11872
  const nextP = draft["priority"] === null ? null : draft["priority"];
@@ -11919,6 +12033,20 @@ var workItemUpdateAspects = (kind, payload) => {
11919
12033
  }
11920
12034
  }
11921
12035
  }
12036
+ if (kind === "ticket" && payload["dependsOn"] !== void 0) {
12037
+ if (payload["dependsOn"] === null) {
12038
+ aspects.push("cleared ticket dependencies");
12039
+ } else {
12040
+ const d = normalizeBranchList(payload["dependsOn"]);
12041
+ if (d.length > 0 && d.length <= MAX_BRANCH_NAMES_TO_LIST) {
12042
+ aspects.push(`set ticket dependencies to (${d.join(", ")})`);
12043
+ } else if (d.length > MAX_BRANCH_NAMES_TO_LIST) {
12044
+ aspects.push("updated ticket dependencies");
12045
+ } else {
12046
+ aspects.push("cleared ticket dependencies");
12047
+ }
12048
+ }
12049
+ }
11922
12050
  if (kind === "ticket" && payload["priority"] !== void 0) {
11923
12051
  if (payload["priority"] === null) {
11924
12052
  aspects.push("cleared priority");
@@ -12025,6 +12153,7 @@ var buildAuditLinkMetadata = (evt, githubRepo) => {
12025
12153
  if (p["status"] !== void 0) meta["status"] = String(p["status"]);
12026
12154
  if (p["assignee"] !== void 0) meta["assignee"] = p["assignee"];
12027
12155
  if (p["labels"] !== void 0) meta["labels"] = p["labels"];
12156
+ if (p["dependsOn"] !== void 0) meta["dependsOn"] = p["dependsOn"];
12028
12157
  if (p["priority"] !== void 0) meta["priority"] = p["priority"];
12029
12158
  if (p["size"] !== void 0) meta["size"] = p["size"];
12030
12159
  if (p["estimate"] !== void 0) meta["estimate"] = p["estimate"];
@@ -12092,6 +12221,14 @@ var formatEpicStoryTicketCreated = (evt, entity) => {
12092
12221
  parts.push("with labels");
12093
12222
  }
12094
12223
  }
12224
+ if (p["dependsOn"] !== void 0) {
12225
+ const d = normalizeBranchList(p["dependsOn"]);
12226
+ if (d.length > 0 && d.length <= MAX_BRANCH_NAMES_TO_LIST) {
12227
+ parts.push(`depending on (${d.join(", ")})`);
12228
+ } else if (d.length > MAX_BRANCH_NAMES_TO_LIST) {
12229
+ parts.push("with ticket dependencies");
12230
+ }
12231
+ }
12095
12232
  if (p["priority"] !== void 0) {
12096
12233
  parts.push(`with priority ${quoteStatus(String(p["priority"]))}`);
12097
12234
  }
@@ -12185,6 +12322,18 @@ var formatAuditHumanSentence = (evt) => {
12185
12322
  bits.push("updated labels");
12186
12323
  }
12187
12324
  }
12325
+ if (p["dependsOn"] !== void 0) {
12326
+ if (p["dependsOn"] === null) {
12327
+ bits.push("cleared ticket dependencies");
12328
+ } else {
12329
+ const d = normalizeBranchList(p["dependsOn"]);
12330
+ if (d.length > 0 && d.length <= MAX_BRANCH_NAMES_TO_LIST) {
12331
+ bits.push(`dependencies: ${d.join(", ")}`);
12332
+ } else {
12333
+ bits.push("updated ticket dependencies");
12334
+ }
12335
+ }
12336
+ }
12188
12337
  if (p["priority"] !== void 0) {
12189
12338
  bits.push("updated priority");
12190
12339
  }
@@ -12464,6 +12613,13 @@ var ticketMatchesTicketListQuery = (ticket, projection, query) => {
12464
12613
  return false;
12465
12614
  }
12466
12615
  }
12616
+ const dependsOnIncludesId = query.dependsOnIncludesId;
12617
+ if (dependsOnIncludesId !== void 0) {
12618
+ const deps = ticket.dependsOn ?? [];
12619
+ if (!deps.includes(dependsOnIncludesId)) {
12620
+ return false;
12621
+ }
12622
+ }
12467
12623
  return true;
12468
12624
  };
12469
12625
 
@@ -12616,6 +12772,7 @@ var sortTicketRecordsForList = (tickets, field, dir) => [...tickets].sort((x, y)
12616
12772
  // src/cli/list-projection-summaries.ts
12617
12773
  var listActiveEpicSummaries = (projection) => [...projection.epics.values()].filter((e) => !e.deleted).map((e) => ({
12618
12774
  id: e.id,
12775
+ number: e.number,
12619
12776
  title: e.title,
12620
12777
  status: e.status,
12621
12778
  createdAt: e.createdAt,
@@ -12627,6 +12784,7 @@ var listActiveStorySummaries = (projection, options) => [...projection.stories.v
12627
12784
  (s) => !s.deleted && (options?.epicId === void 0 || s.epicId === options.epicId)
12628
12785
  ).map((s) => ({
12629
12786
  id: s.id,
12787
+ number: s.number,
12630
12788
  title: s.title,
12631
12789
  epicId: s.epicId,
12632
12790
  status: s.status,
@@ -12659,6 +12817,7 @@ var listActiveTicketSummaries = (projection, options) => {
12659
12817
  const last = recent !== void 0 && recent.length > 0 ? recent[recent.length - 1] : void 0;
12660
12818
  return {
12661
12819
  id: t.id,
12820
+ number: t.number,
12662
12821
  title: t.title,
12663
12822
  status: t.status,
12664
12823
  storyId: t.storyId,
@@ -12669,6 +12828,7 @@ var listActiveTicketSummaries = (projection, options) => {
12669
12828
  ...t.estimate !== void 0 ? { estimate: t.estimate } : {},
12670
12829
  ...t.startWorkAt !== void 0 ? { startWorkAt: t.startWorkAt } : {},
12671
12830
  ...t.targetFinishAt !== void 0 ? { targetFinishAt: t.targetFinishAt } : {},
12831
+ ...t.dependsOn !== void 0 && t.dependsOn.length > 0 ? { dependsOn: t.dependsOn } : {},
12672
12832
  ...t.linkedBranches.length > 0 ? { linkedBranches: t.linkedBranches } : {},
12673
12833
  ...last !== void 0 ? {
12674
12834
  lastPrActivity: {
@@ -12765,6 +12925,17 @@ var inboundTicketPlanningPayloadFromFenceMeta = (meta) => {
12765
12925
  }
12766
12926
  }
12767
12927
  }
12928
+ if (Object.prototype.hasOwnProperty.call(meta, "depends_on")) {
12929
+ const v = meta["depends_on"];
12930
+ if (v === null) {
12931
+ out["dependsOn"] = null;
12932
+ } else {
12933
+ const parsed = parseTicketDependsOnFromFenceValue(v);
12934
+ if (parsed !== void 0) {
12935
+ out["dependsOn"] = parsed;
12936
+ }
12937
+ }
12938
+ }
12768
12939
  return out;
12769
12940
  };
12770
12941
  var buildGithubIssueBody = (params) => {
@@ -12790,6 +12961,9 @@ var buildGithubIssueBody = (params) => {
12790
12961
  if (p.targetFinishAt !== void 0) {
12791
12962
  meta.target_finish_at = p.targetFinishAt;
12792
12963
  }
12964
+ if (p.dependsOn !== void 0 && p.dependsOn.length > 0) {
12965
+ meta.depends_on = p.dependsOn;
12966
+ }
12793
12967
  }
12794
12968
  return `${params.description.trim()}
12795
12969
 
@@ -12815,6 +12989,9 @@ var ticketPlanningForGithubIssueBody = (ticket) => {
12815
12989
  if (ticket.targetFinishAt !== void 0) {
12816
12990
  out.targetFinishAt = ticket.targetFinishAt;
12817
12991
  }
12992
+ if (ticket.dependsOn !== void 0 && ticket.dependsOn.length > 0) {
12993
+ out.dependsOn = ticket.dependsOn;
12994
+ }
12818
12995
  return Object.keys(out).length > 0 ? out : void 0;
12819
12996
  };
12820
12997
 
@@ -13009,750 +13186,302 @@ var partitionGithubIssuesForImport = (params) => {
13009
13186
  }
13010
13187
  return { candidates, skipped };
13011
13188
  };
13012
- var mergeTicketImportCreatePayload = (ticketId, base, storyId) => {
13189
+ var mergeTicketImportCreatePayload = (ticketId, base, storyId, number) => {
13013
13190
  const storyTrimmed = storyId !== void 0 && storyId !== "" ? storyId.trim() : void 0;
13014
13191
  const storyPayload = storyTrimmed !== void 0 && storyTrimmed !== "" ? { storyId: storyTrimmed } : {};
13015
- return { id: ticketId, ...base, ...storyPayload };
13192
+ return { id: ticketId, number, ...base, ...storyPayload };
13016
13193
  };
13017
13194
 
13018
- // src/config/hyper-pm-config.ts
13019
- var hyperPmConfigSchema = external_exports.object({
13020
- schema: external_exports.literal(1),
13021
- dataBranch: external_exports.string().min(1),
13022
- remote: external_exports.string().min(1).default("origin"),
13023
- sync: external_exports.enum(["off", "outbound", "full"]).default("outbound"),
13024
- githubRepo: external_exports.string().optional(),
13025
- issueMapping: external_exports.enum(["ticket", "story", "epic"]).default("ticket")
13026
- });
13027
-
13028
- // src/config/load-config.ts
13029
- var import_promises = require("node:fs/promises");
13030
- var import_node_path = require("node:path");
13031
- var CONFIG_DIR = ".hyper-pm";
13032
- var CONFIG_FILE = "config.json";
13033
- var getHyperPmConfigPath = (repoRoot) => {
13034
- return (0, import_node_path.join)(repoRoot, CONFIG_DIR, CONFIG_FILE);
13035
- };
13036
- var loadHyperPmConfig = async (repoRoot, overrides = {}) => {
13037
- const raw = await (0, import_promises.readFile)(getHyperPmConfigPath(repoRoot), "utf8");
13038
- const parsed = JSON.parse(raw);
13039
- const base = hyperPmConfigSchema.parse(parsed);
13040
- const merged = { ...base, ...stripUndefined(overrides) };
13041
- return hyperPmConfigSchema.parse(merged);
13042
- };
13043
- var stripUndefined = (obj) => {
13044
- const out = {};
13045
- for (const [k, v] of Object.entries(obj)) {
13046
- if (v !== void 0) out[k] = v;
13195
+ // src/lib/github-pr-activity.ts
13196
+ var GITHUB_PR_ACTIVITY_RECENT_CAP = 20;
13197
+ var githubPrActivityKindSchema = external_exports.enum([
13198
+ "opened",
13199
+ "updated",
13200
+ "commented",
13201
+ "reviewed",
13202
+ "merged",
13203
+ "closed",
13204
+ "ready_for_review"
13205
+ ]);
13206
+ var githubPrReviewStateSchema = external_exports.enum([
13207
+ "approved",
13208
+ "changes_requested",
13209
+ "commented"
13210
+ ]);
13211
+ var buildPrOpenSourceId = (ticketId, prNumber) => `hyper-pm:pr-open:${ticketId}:${prNumber}`;
13212
+ var parseGithubPrActivityPayload = (payload) => {
13213
+ const ticketId = payload["ticketId"];
13214
+ const prRaw = payload["prNumber"];
13215
+ const kindRaw = payload["kind"];
13216
+ const occurredAt = payload["occurredAt"];
13217
+ const sourceId = payload["sourceId"];
13218
+ if (typeof ticketId !== "string" || typeof occurredAt !== "string" || typeof sourceId !== "string") {
13219
+ return void 0;
13220
+ }
13221
+ const prNumber = typeof prRaw === "number" ? prRaw : Number(prRaw);
13222
+ if (!Number.isFinite(prNumber)) return void 0;
13223
+ const kindParsed = githubPrActivityKindSchema.safeParse(kindRaw);
13224
+ if (!kindParsed.success) return void 0;
13225
+ const out = {
13226
+ prNumber,
13227
+ kind: kindParsed.data,
13228
+ occurredAt,
13229
+ sourceId
13230
+ };
13231
+ const url = payload["url"];
13232
+ if (typeof url === "string" && url.length > 0) {
13233
+ out.url = url;
13234
+ }
13235
+ const rs = githubPrReviewStateSchema.safeParse(payload["reviewState"]);
13236
+ if (rs.success) {
13237
+ out.reviewState = rs.data;
13047
13238
  }
13048
13239
  return out;
13049
13240
  };
13050
13241
 
13051
- // src/config/save-config.ts
13052
- var import_promises2 = require("node:fs/promises");
13053
- var import_node_path2 = require("node:path");
13054
- var saveHyperPmConfig = async (repoRoot, config) => {
13055
- const target = getHyperPmConfigPath(repoRoot);
13056
- await (0, import_promises2.mkdir)((0, import_node_path2.dirname)(target), { recursive: true });
13057
- await (0, import_promises2.writeFile)(target, `${JSON.stringify(config, null, 2)}
13058
- `, "utf8");
13242
+ // src/storage/projection.ts
13243
+ var readOptionalPositiveIntegerFromPayload = (payload, key) => {
13244
+ if (!Object.prototype.hasOwnProperty.call(payload, key)) return void 0;
13245
+ const raw = payload[key];
13246
+ if (typeof raw !== "number" || !Number.isFinite(raw)) return void 0;
13247
+ if (!Number.isInteger(raw)) return void 0;
13248
+ if (raw < 1 || raw > Number.MAX_SAFE_INTEGER) return void 0;
13249
+ return raw;
13059
13250
  };
13060
-
13061
- // src/doctor/run-doctor.ts
13062
- var runDoctorOnLines = (lines) => {
13063
- const issues = [];
13064
- for (let i = 0; i < lines.length; i++) {
13065
- const line = lines[i]?.trim() ?? "";
13066
- if (!line) continue;
13067
- let json;
13068
- try {
13069
- json = JSON.parse(line);
13070
- } catch (e) {
13071
- issues.push({
13072
- kind: "invalid-json",
13073
- line: i + 1,
13074
- message: e instanceof Error ? e.message : "parse error"
13075
- });
13076
- return issues;
13077
- }
13078
- const parsed = eventLineSchema.safeParse(json);
13079
- if (!parsed.success) {
13080
- issues.push({
13081
- kind: "invalid-event",
13082
- line: i + 1,
13083
- message: parsed.error.message
13084
- });
13085
- return issues;
13086
- }
13251
+ var maxNumberAmongRows = (rows) => {
13252
+ let m = 0;
13253
+ for (const r of rows) {
13254
+ if (r.number > m) m = r.number;
13087
13255
  }
13088
- return issues;
13256
+ return m;
13089
13257
  };
13090
-
13091
- // src/git/create-and-checkout-branch.ts
13092
- var createAndCheckoutBranch = async (opts) => {
13093
- await opts.runGit(opts.repoRoot, [
13094
- "switch",
13095
- "-c",
13096
- opts.branchName,
13097
- opts.startPoint
13098
- ]);
13258
+ var maxEpicNumberInProjection = (projection) => maxNumberAmongRows(projection.epics.values());
13259
+ var maxStoryNumberInProjection = (projection) => maxNumberAmongRows(projection.stories.values());
13260
+ var maxTicketNumberInProjection = (projection) => maxNumberAmongRows(projection.tickets.values());
13261
+ var nextEpicNumberForCreate = (projection) => maxEpicNumberInProjection(projection) + 1;
13262
+ var nextStoryNumberForCreate = (projection) => maxStoryNumberInProjection(projection) + 1;
13263
+ var nextTicketNumberForCreate = (projection) => maxTicketNumberInProjection(projection) + 1;
13264
+ var resolveWorkItemCreateNumber = (projection, kind, payload) => {
13265
+ const explicit = readOptionalPositiveIntegerFromPayload(payload, "number");
13266
+ if (explicit !== void 0) {
13267
+ return explicit;
13268
+ }
13269
+ return (kind === "epic" ? maxEpicNumberInProjection(projection) : kind === "story" ? maxStoryNumberInProjection(projection) : maxTicketNumberInProjection(projection)) + 1;
13099
13270
  };
13100
-
13101
- // src/git/data-worktree-session.ts
13102
- var import_promises3 = require("node:fs/promises");
13103
- var import_node_path3 = require("node:path");
13104
- var ensureDir = async (path) => {
13105
- await (0, import_promises3.mkdir)(path, { recursive: true });
13271
+ var emptyProjection = () => ({
13272
+ epics: /* @__PURE__ */ new Map(),
13273
+ stories: /* @__PURE__ */ new Map(),
13274
+ tickets: /* @__PURE__ */ new Map()
13275
+ });
13276
+ var parsePrRefs = (body) => {
13277
+ const out = /* @__PURE__ */ new Set();
13278
+ const re = /\b(?:Closes|Refs|Fixes)\s+#(\d+)\b/gi;
13279
+ let m = re.exec(body);
13280
+ while (m !== null) {
13281
+ out.add(Number(m[1]));
13282
+ m = re.exec(body);
13283
+ }
13284
+ return [...out];
13106
13285
  };
13107
- var defaultPathExists = async (path) => {
13108
- try {
13109
- await (0, import_promises3.access)(path);
13110
- return true;
13111
- } catch {
13112
- return false;
13286
+ var applyTicketAssigneeFromPayload = (row, payload) => {
13287
+ if (!Object.prototype.hasOwnProperty.call(payload, "assignee")) return;
13288
+ const v = payload["assignee"];
13289
+ if (v === null) {
13290
+ delete row.assignee;
13291
+ return;
13113
13292
  }
13293
+ if (typeof v !== "string") return;
13294
+ const n = normalizeGithubLogin(v);
13295
+ if (n === "") delete row.assignee;
13296
+ else row.assignee = n;
13114
13297
  };
13115
- var parseDataBranchWorktreeFromPorcelain = (stdout, dataBranch) => {
13116
- const wantRef = `refs/heads/${dataBranch}`;
13117
- const blocks = stdout.split(/\n\n+/).map((b) => b.trim()).filter(Boolean);
13118
- for (const block of blocks) {
13119
- const lines = block.split("\n");
13120
- let worktreePath;
13121
- let branch;
13122
- for (const line of lines) {
13123
- if (line.startsWith("worktree ")) {
13124
- worktreePath = line.slice("worktree ".length);
13125
- } else if (line.startsWith("branch ")) {
13126
- branch = line.slice("branch ".length);
13127
- }
13128
- }
13129
- if (worktreePath !== void 0 && branch === wantRef) {
13130
- return worktreePath;
13131
- }
13298
+ var storyIdFromTicketCreatedPayload = (payload) => {
13299
+ if (!Object.prototype.hasOwnProperty.call(payload, "storyId")) {
13300
+ return null;
13132
13301
  }
13133
- return void 0;
13302
+ const v = payload["storyId"];
13303
+ if (v === null) return null;
13304
+ if (typeof v !== "string") return null;
13305
+ const t = v.trim();
13306
+ return t === "" ? null : t;
13134
13307
  };
13135
- var openDataBranchWorktree = async (opts) => {
13136
- const pathExists = opts.pathExists ?? defaultPathExists;
13137
- const { stdout: listOut } = await opts.runGit(opts.repoRoot, [
13138
- "worktree",
13139
- "list",
13140
- "--porcelain"
13141
- ]);
13142
- const listedPath = parseDataBranchWorktreeFromPorcelain(
13143
- listOut,
13144
- opts.dataBranch
13145
- );
13146
- if (listedPath !== void 0 && await pathExists(listedPath)) {
13147
- const dispose2 = async () => {
13148
- return;
13149
- };
13150
- return { worktreePath: listedPath, dispose: dispose2 };
13151
- }
13152
- const worktreePath = (0, import_node_path3.join)(
13153
- opts.tmpBase,
13154
- `hyper-pm-worktree-${ulid().toLowerCase()}`
13155
- );
13156
- await ensureDir(opts.tmpBase);
13157
- try {
13158
- await opts.runGit(opts.repoRoot, [
13159
- "worktree",
13160
- "add",
13161
- worktreePath,
13162
- opts.dataBranch
13163
- ]);
13164
- } catch (err) {
13165
- await (0, import_promises3.rm)(worktreePath, { recursive: true, force: true }).catch(() => {
13166
- });
13167
- throw err;
13168
- }
13169
- const dispose = async () => {
13170
- if (opts.keepWorktree) {
13171
- return;
13172
- }
13173
- await opts.runGit(opts.repoRoot, ["worktree", "remove", "--force", worktreePath]).catch(() => {
13174
- });
13175
- await (0, import_promises3.rm)(worktreePath, { recursive: true, force: true }).catch(() => {
13176
- });
13177
- };
13178
- return { worktreePath, dispose };
13179
- };
13180
-
13181
- // src/git/find-git-root.ts
13182
- var findGitRoot = async (cwd, deps) => {
13183
- const { stdout } = await deps.runGit(cwd, ["rev-parse", "--show-toplevel"]);
13184
- return stdout;
13185
- };
13186
-
13187
- // src/git/init-orphan-data-branch.ts
13188
- var import_promises4 = require("node:fs/promises");
13189
- var import_node_path4 = require("node:path");
13190
- var initOrphanDataBranchInWorktree = async (opts) => {
13191
- const { stdout: tip } = await opts.runGit(opts.repoRoot, [
13192
- "rev-parse",
13193
- "HEAD"
13194
- ]);
13195
- const tipCommit = tip.trim();
13196
- await opts.runGit(opts.repoRoot, [
13197
- "worktree",
13198
- "add",
13199
- opts.worktreePath,
13200
- tipCommit
13201
- ]);
13202
- await opts.runGit(opts.worktreePath, [
13203
- "checkout",
13204
- "--orphan",
13205
- opts.dataBranch
13206
- ]);
13207
- await opts.runGit(opts.worktreePath, ["rm", "-rf", "."]).catch(() => {
13208
- });
13209
- const marker = (0, import_node_path4.join)(opts.worktreePath, "README.hyper-pm.md");
13210
- await (0, import_promises4.writeFile)(
13211
- marker,
13212
- "# hyper-pm data branch\n\nAppend-only events live under `events/`.\n",
13213
- "utf8"
13214
- );
13215
- await opts.runGit(opts.worktreePath, ["add", "."]);
13216
- await opts.runGit(opts.worktreePath, [
13217
- "commit",
13218
- "-m",
13219
- "init hyper-pm data branch"
13220
- ]);
13221
- };
13222
-
13223
- // src/git/pick-unique-local-branch-name.ts
13224
- var DEFAULT_MAX_SUFFIX = 1e3;
13225
- var localBranchRefExists = async (repoRoot, name, git) => {
13226
- try {
13227
- await git(repoRoot, ["show-ref", "--verify", `refs/heads/${name}`]);
13228
- return true;
13229
- } catch {
13230
- return false;
13308
+ var applyTicketStoryIdFromPayload = (row, payload) => {
13309
+ if (!Object.prototype.hasOwnProperty.call(payload, "storyId")) return;
13310
+ const v = payload["storyId"];
13311
+ if (v === null) {
13312
+ row.storyId = null;
13313
+ return;
13231
13314
  }
13315
+ if (typeof v !== "string") return;
13316
+ const t = v.trim();
13317
+ row.storyId = t === "" ? null : t;
13232
13318
  };
13233
- var pickUniqueLocalBranchName = async (opts) => {
13234
- const max = opts.maxSuffix ?? DEFAULT_MAX_SUFFIX;
13235
- const { preferredBase, repoRoot, runGit: git } = opts;
13236
- for (let n = 1; n <= max; n += 1) {
13237
- const raw = n === 1 ? preferredBase : `${preferredBase}-${n}`;
13238
- const norm = normalizeTicketBranchName(raw);
13239
- if (norm === void 0) {
13240
- continue;
13241
- }
13242
- if (!await localBranchRefExists(repoRoot, norm, git)) {
13243
- return { branch: norm, preferred: preferredBase };
13244
- }
13319
+ var linkedBranchesFromTicketCreatedPayload = (payload) => {
13320
+ if (!Object.prototype.hasOwnProperty.call(payload, "branches")) {
13321
+ return [];
13245
13322
  }
13246
- throw new Error(
13247
- `Could not allocate a free local branch name from ${JSON.stringify(preferredBase)} (tried suffixes up to -${String(max)})`
13248
- );
13323
+ return normalizeTicketBranchListFromPayloadValue(payload["branches"]);
13249
13324
  };
13250
-
13251
- // src/git/resolve-integration-start-point.ts
13252
- var assertGitRefResolvable = async (repoRoot, ref, git) => {
13253
- try {
13254
- await git(repoRoot, ["rev-parse", "-q", "--verify", ref]);
13255
- } catch {
13256
- throw new Error(`Invalid or ambiguous --from ref: ${ref}`);
13257
- }
13325
+ var applyTicketBranchesFromUpdatePayload = (row, payload) => {
13326
+ if (!Object.prototype.hasOwnProperty.call(payload, "branches")) return;
13327
+ const v = payload["branches"];
13328
+ if (!Array.isArray(v)) return;
13329
+ row.linkedBranches = normalizeTicketBranchListFromPayloadValue(v);
13258
13330
  };
13259
- var resolveIntegrationStartPoint = async (repoRoot, remote, git) => {
13260
- const symRef = `refs/remotes/${remote}/HEAD`;
13261
- try {
13262
- const { stdout } = await git(repoRoot, ["symbolic-ref", "-q", symRef]);
13263
- const target = stdout.trim();
13264
- if (target !== "") {
13265
- await git(repoRoot, ["rev-parse", "-q", "--verify", target]);
13266
- return target;
13331
+ var applyTicketPlanningFieldsFromCreatePayload = (row, payload) => {
13332
+ if (Object.prototype.hasOwnProperty.call(payload, "labels")) {
13333
+ const v = ticketLabelsFromPayloadValue(payload["labels"]);
13334
+ if (v !== void 0 && v.length > 0) {
13335
+ row.labels = v;
13267
13336
  }
13268
- } catch {
13269
13337
  }
13270
- for (const head of ["refs/heads/main", "refs/heads/master"]) {
13271
- try {
13272
- await git(repoRoot, ["rev-parse", "-q", "--verify", head]);
13273
- return head;
13274
- } catch {
13275
- }
13338
+ const pr = readTicketPriorityPatch(payload);
13339
+ if (typeof pr === "string") {
13340
+ row.priority = pr;
13276
13341
  }
13277
- try {
13278
- await git(repoRoot, ["rev-parse", "-q", "--verify", "HEAD"]);
13279
- return "HEAD";
13280
- } catch {
13342
+ const sz = readTicketSizePatch(payload);
13343
+ if (typeof sz === "string") {
13344
+ row.size = sz;
13281
13345
  }
13282
- throw new Error(
13283
- "Could not resolve a default branch to branch from; pass --from <ref> (e.g. main or origin/main)."
13284
- );
13285
- };
13286
-
13287
- // src/git/list-repo-commit-authors.ts
13288
- var listRepoCommitAuthors = async (repoRoot, git) => {
13289
- try {
13290
- const { stdout } = await git(repoRoot, [
13291
- "-c",
13292
- "log.showSignature=false",
13293
- "log",
13294
- "--all",
13295
- "--format=%an%x1f%ae"
13296
- ]);
13297
- if (stdout === "") return [];
13298
- const lines = stdout.split("\n").filter((l) => l.length > 0);
13299
- const seenEmail = /* @__PURE__ */ new Set();
13300
- const out = [];
13301
- for (const line of lines) {
13302
- const sep2 = line.indexOf("");
13303
- if (sep2 <= 0) continue;
13304
- const name = line.slice(0, sep2).trim();
13305
- const email = line.slice(sep2 + 1).trim();
13306
- if (email === "") continue;
13307
- const key = email.toLowerCase();
13308
- if (seenEmail.has(key)) continue;
13309
- seenEmail.add(key);
13310
- const loginGuess = guessGithubLoginFromContact(name, email);
13311
- out.push(
13312
- loginGuess !== void 0 ? { name, email, loginGuess } : { name, email }
13313
- );
13314
- }
13315
- return out;
13316
- } catch {
13317
- return [];
13346
+ const est = readTicketEstimatePatch(payload);
13347
+ if (typeof est === "number") {
13348
+ row.estimate = est;
13318
13349
  }
13319
- };
13320
-
13321
- // src/git/parse-github-owner-repo-from-remote-url.ts
13322
- var parseGithubOwnerRepoFromRemoteUrl = (rawUrl) => {
13323
- const trimmed = rawUrl.trim();
13324
- if (!trimmed) {
13325
- return void 0;
13350
+ const sw = readTicketIsoInstantPatch(payload, "startWorkAt");
13351
+ if (typeof sw === "string") {
13352
+ row.startWorkAt = sw;
13326
13353
  }
13327
- const scpMatch = /^git@github\.com:(?<path>.+)$/i.exec(trimmed);
13328
- if (scpMatch?.groups?.path) {
13329
- return slugFromGithubPath(scpMatch.groups.path);
13354
+ const tf = readTicketIsoInstantPatch(payload, "targetFinishAt");
13355
+ if (typeof tf === "string") {
13356
+ row.targetFinishAt = tf;
13330
13357
  }
13331
- try {
13332
- const withScheme = trimmed.includes("://") ? trimmed : `https://${trimmed}`;
13333
- const u = new URL(withScheme);
13334
- const host = u.hostname.toLowerCase();
13335
- if (host !== "github.com" && host !== "www.github.com") {
13336
- return void 0;
13358
+ if (Object.prototype.hasOwnProperty.call(payload, "dependsOn")) {
13359
+ const v = parseTicketDependsOnFromPayloadValue(payload["dependsOn"]);
13360
+ if (v !== void 0 && v.length > 0) {
13361
+ row.dependsOn = v;
13337
13362
  }
13338
- return slugFromGithubPath(u.pathname);
13339
- } catch {
13340
- return void 0;
13341
13363
  }
13342
13364
  };
13343
- var slugFromGithubPath = (path) => {
13344
- const segments = path.replace(/^\/+/, "").split("/").filter((s) => s.length > 0).map((s) => s.replace(/\.git$/i, ""));
13345
- if (segments.length < 2) {
13346
- return void 0;
13365
+ var applyTicketPlanningFieldsFromUpdatePayload = (row, payload) => {
13366
+ if (Object.prototype.hasOwnProperty.call(payload, "labels")) {
13367
+ if (payload["labels"] === null) {
13368
+ delete row.labels;
13369
+ } else {
13370
+ const v = ticketLabelsFromPayloadValue(payload["labels"]);
13371
+ if (v !== void 0) {
13372
+ if (v.length === 0) {
13373
+ delete row.labels;
13374
+ } else {
13375
+ row.labels = v;
13376
+ }
13377
+ }
13378
+ }
13347
13379
  }
13348
- const owner = segments[0];
13349
- const repo = segments[1];
13350
- if (!owner || !repo) {
13351
- return void 0;
13380
+ const pr = readTicketPriorityPatch(payload);
13381
+ if (pr === null) {
13382
+ delete row.priority;
13383
+ } else if (typeof pr === "string") {
13384
+ row.priority = pr;
13352
13385
  }
13353
- return `${owner}/${repo}`;
13354
- };
13355
-
13356
- // src/git/try-read-github-owner-repo-slug-from-git.ts
13357
- var tryReadGithubOwnerRepoSlugFromGit = async (params) => {
13358
- try {
13359
- const { stdout } = await params.runGit(params.repoRoot, [
13360
- "remote",
13361
- "get-url",
13362
- params.remote
13363
- ]);
13364
- return parseGithubOwnerRepoFromRemoteUrl(stdout);
13365
- } catch {
13366
- return void 0;
13386
+ const sz = readTicketSizePatch(payload);
13387
+ if (sz === null) {
13388
+ delete row.size;
13389
+ } else if (typeof sz === "string") {
13390
+ row.size = sz;
13367
13391
  }
13368
- };
13369
-
13370
- // src/git/resolve-effective-git-author-for-data-commit.ts
13371
- var defaultDataCommitName = "hyper-pm";
13372
- var defaultDataCommitEmail = "hyper-pm@users.noreply.github.com";
13373
- var tryReadGitConfigUserName = async (cwd, runGit2) => {
13374
- try {
13375
- const { stdout } = await runGit2(cwd, ["config", "--get", "user.name"]);
13376
- return stdout.trim();
13377
- } catch {
13378
- return "";
13392
+ const est = readTicketEstimatePatch(payload);
13393
+ if (est === null) {
13394
+ delete row.estimate;
13395
+ } else if (typeof est === "number") {
13396
+ row.estimate = est;
13379
13397
  }
13380
- };
13381
- var tryReadGitConfigUserEmail = async (cwd, runGit2) => {
13382
- try {
13383
- const { stdout } = await runGit2(cwd, ["config", "--get", "user.email"]);
13384
- return stdout.trim();
13385
- } catch {
13386
- return "";
13398
+ const sw = readTicketIsoInstantPatch(payload, "startWorkAt");
13399
+ if (sw === null) {
13400
+ delete row.startWorkAt;
13401
+ } else if (typeof sw === "string") {
13402
+ row.startWorkAt = sw;
13387
13403
  }
13388
- };
13389
- var resolveEffectiveGitAuthorForDataCommit = async (cwd, runGit2, authorEnv) => {
13390
- const fromGitName = await tryReadGitConfigUserName(cwd, runGit2);
13391
- const fromGitEmail = await tryReadGitConfigUserEmail(cwd, runGit2);
13392
- const name = fromGitName || authorEnv.HYPER_PM_GIT_USER_NAME?.trim() || authorEnv.GIT_AUTHOR_NAME?.trim() || defaultDataCommitName;
13393
- const email = fromGitEmail || authorEnv.HYPER_PM_GIT_USER_EMAIL?.trim() || authorEnv.GIT_AUTHOR_EMAIL?.trim() || defaultDataCommitEmail;
13394
- return { name, email };
13395
- };
13396
-
13397
- // src/run/commit-data.ts
13398
- var maxActorSuffixLen = 60;
13399
- var formatDataBranchCommitMessage = (base, actorSuffix) => {
13400
- const raw = actorSuffix?.trim();
13401
- if (!raw) {
13402
- return base;
13404
+ const tf = readTicketIsoInstantPatch(payload, "targetFinishAt");
13405
+ if (tf === null) {
13406
+ delete row.targetFinishAt;
13407
+ } else if (typeof tf === "string") {
13408
+ row.targetFinishAt = tf;
13409
+ }
13410
+ if (Object.prototype.hasOwnProperty.call(payload, "dependsOn")) {
13411
+ if (payload["dependsOn"] === null) {
13412
+ delete row.dependsOn;
13413
+ } else {
13414
+ const v = parseTicketDependsOnFromPayloadValue(payload["dependsOn"]);
13415
+ if (v !== void 0) {
13416
+ if (v.length === 0) {
13417
+ delete row.dependsOn;
13418
+ } else {
13419
+ row.dependsOn = v;
13420
+ }
13421
+ }
13422
+ }
13403
13423
  }
13404
- const collapsed = raw.replace(/\s+/g, " ");
13405
- const suffix = collapsed.length > maxActorSuffixLen ? `${collapsed.slice(0, maxActorSuffixLen - 1)}\u2026` : collapsed;
13406
- return `${base} (${suffix})`;
13407
- };
13408
- var commitDataWorktreeIfNeeded = async (worktreePath, message, runGit2, opts) => {
13409
- const { stdout } = await runGit2(worktreePath, ["status", "--porcelain"]);
13410
- if (!stdout.trim()) return;
13411
- await runGit2(worktreePath, ["add", "."]);
13412
- const authorEnv = opts?.authorEnv ?? env;
13413
- const { name, email } = await resolveEffectiveGitAuthorForDataCommit(
13414
- worktreePath,
13415
- runGit2,
13416
- authorEnv
13417
- );
13418
- await runGit2(worktreePath, [
13419
- "-c",
13420
- `user.name=${name}`,
13421
- "-c",
13422
- `user.email=${email}`,
13423
- "commit",
13424
- "-m",
13425
- message
13426
- ]);
13427
13424
  };
13428
-
13429
- // src/run/push-data-branch.ts
13430
- var firstLineFromUnknown = (err) => {
13431
- const raw = err instanceof Error ? err.message : String(err);
13432
- const line = raw.trim().split("\n")[0]?.trim() ?? raw.trim();
13433
- return line.length > 0 ? line : "git error";
13425
+ var applyCreatedAudit = (row, evt) => {
13426
+ row.createdAt = evt.ts;
13427
+ row.createdBy = evt.actor;
13428
+ row.updatedAt = evt.ts;
13429
+ row.updatedBy = evt.actor;
13434
13430
  };
13435
- var tryPushDataBranchToRemote = async (worktreePath, remote, branch, runGit2) => {
13436
- try {
13437
- await runGit2(worktreePath, ["remote", "get-url", remote]);
13438
- } catch (e) {
13439
- return {
13440
- status: "skipped_no_remote",
13441
- detail: firstLineFromUnknown(e)
13442
- };
13443
- }
13444
- try {
13445
- await runGit2(worktreePath, ["push", "-u", remote, branch]);
13446
- return { status: "pushed" };
13447
- } catch (e) {
13448
- return {
13449
- status: "failed",
13450
- detail: firstLineFromUnknown(e)
13451
- };
13452
- }
13431
+ var applyLastUpdate = (row, evt) => {
13432
+ row.updatedAt = evt.ts;
13433
+ row.updatedBy = evt.actor;
13453
13434
  };
13454
-
13455
- // src/storage/append-event.ts
13456
- var import_promises5 = require("node:fs/promises");
13457
- var import_node_path5 = require("node:path");
13458
-
13459
- // src/storage/event-path.ts
13460
- var nextEventRelPath = (now, deps) => {
13461
- const y = String(now.getUTCFullYear());
13462
- const m = String(now.getUTCMonth() + 1).padStart(2, "0");
13463
- const nextId = deps?.nextId ?? (() => ulid().toLowerCase());
13464
- const part = `part-${nextId()}`;
13465
- return `events/${y}/${m}/${part}.jsonl`;
13435
+ var applyStatusIfChanged = (row, evt, nextStatus) => {
13436
+ if (row.status === nextStatus) return false;
13437
+ row.status = nextStatus;
13438
+ row.statusChangedAt = evt.ts;
13439
+ row.statusChangedBy = evt.actor;
13440
+ return true;
13466
13441
  };
13467
-
13468
- // src/storage/append-event.ts
13469
- var appendEventLine = async (dataRoot, event, clock, opts) => {
13470
- const rel = nextEventRelPath(clock.now(), {
13471
- nextId: opts?.nextEventId
13442
+ var appendTicketCommentFromEvent = (ticket, evt) => {
13443
+ const list = ticket.comments ?? (ticket.comments = []);
13444
+ list.push({
13445
+ id: evt.id,
13446
+ body: String(evt.payload["body"] ?? ""),
13447
+ createdAt: evt.ts,
13448
+ createdBy: evt.actor
13472
13449
  });
13473
- const abs = `${dataRoot.replace(/\/$/, "")}/${rel}`;
13474
- await (0, import_promises5.mkdir)((0, import_node_path5.dirname)(abs), { recursive: true });
13475
- await (0, import_promises5.appendFile)(abs, `${JSON.stringify(event)}
13476
- `, "utf8");
13477
- return rel;
13450
+ applyLastUpdate(ticket, evt);
13478
13451
  };
13479
-
13480
- // src/storage/read-event-lines.ts
13481
- var import_promises6 = require("node:fs/promises");
13482
- var import_node_path6 = require("node:path");
13483
- var listJsonlFiles = async (dir, root) => {
13484
- const out = [];
13485
- const entries = await (0, import_promises6.readdir)(dir, { withFileTypes: true });
13486
- for (const e of entries) {
13487
- const abs = (0, import_node_path6.join)(dir, e.name);
13488
- if (e.isDirectory()) {
13489
- out.push(...await listJsonlFiles(abs, root));
13490
- } else if (e.isFile() && e.name.endsWith(".jsonl")) {
13491
- out.push((0, import_node_path6.relative)(root, abs).split("\\").join("/"));
13492
- }
13452
+ var resolveInboundTicketStatusFromPayload = (ticket, payload) => {
13453
+ const explicit = parseWorkItemStatus(payload["status"]);
13454
+ if (explicit !== void 0) return explicit;
13455
+ const legacySt = payload["state"];
13456
+ if (legacySt === "open" || legacySt === "closed") {
13457
+ return resolveTicketInboundStatus({
13458
+ issueState: legacySt,
13459
+ currentStatus: ticket.status
13460
+ });
13493
13461
  }
13494
- return out;
13462
+ return void 0;
13495
13463
  };
13496
- var readAllEventLines = async (dataRoot) => {
13497
- const eventsRoot = (0, import_node_path6.join)(dataRoot, "events");
13498
- let files = [];
13499
- try {
13500
- files = await listJsonlFiles(eventsRoot, dataRoot);
13501
- } catch {
13502
- return [];
13503
- }
13504
- files.sort((a, b) => a.localeCompare(b));
13505
- const lines = [];
13506
- for (const rel of files) {
13507
- const content = await (0, import_promises6.readFile)((0, import_node_path6.join)(dataRoot, rel), "utf8");
13508
- for (const line of content.split("\n")) {
13509
- if (line.trim()) lines.push(line);
13510
- }
13511
- }
13512
- return lines;
13513
- };
13514
-
13515
- // src/lib/github-pr-activity.ts
13516
- var GITHUB_PR_ACTIVITY_RECENT_CAP = 20;
13517
- var githubPrActivityKindSchema = external_exports.enum([
13518
- "opened",
13519
- "updated",
13520
- "commented",
13521
- "reviewed",
13522
- "merged",
13523
- "closed",
13524
- "ready_for_review"
13525
- ]);
13526
- var githubPrReviewStateSchema = external_exports.enum([
13527
- "approved",
13528
- "changes_requested",
13529
- "commented"
13530
- ]);
13531
- var buildPrOpenSourceId = (ticketId, prNumber) => `hyper-pm:pr-open:${ticketId}:${prNumber}`;
13532
- var parseGithubPrActivityPayload = (payload) => {
13533
- const ticketId = payload["ticketId"];
13534
- const prRaw = payload["prNumber"];
13535
- const kindRaw = payload["kind"];
13536
- const occurredAt = payload["occurredAt"];
13537
- const sourceId = payload["sourceId"];
13538
- if (typeof ticketId !== "string" || typeof occurredAt !== "string" || typeof sourceId !== "string") {
13539
- return void 0;
13540
- }
13541
- const prNumber = typeof prRaw === "number" ? prRaw : Number(prRaw);
13542
- if (!Number.isFinite(prNumber)) return void 0;
13543
- const kindParsed = githubPrActivityKindSchema.safeParse(kindRaw);
13544
- if (!kindParsed.success) return void 0;
13545
- const out = {
13546
- prNumber,
13547
- kind: kindParsed.data,
13548
- occurredAt,
13549
- sourceId
13550
- };
13551
- const url = payload["url"];
13552
- if (typeof url === "string" && url.length > 0) {
13553
- out.url = url;
13554
- }
13555
- const rs = githubPrReviewStateSchema.safeParse(payload["reviewState"]);
13556
- if (rs.success) {
13557
- out.reviewState = rs.data;
13558
- }
13559
- return out;
13560
- };
13561
-
13562
- // src/storage/projection.ts
13563
- var emptyProjection = () => ({
13564
- epics: /* @__PURE__ */ new Map(),
13565
- stories: /* @__PURE__ */ new Map(),
13566
- tickets: /* @__PURE__ */ new Map()
13567
- });
13568
- var parsePrRefs = (body) => {
13569
- const out = /* @__PURE__ */ new Set();
13570
- const re = /\b(?:Closes|Refs|Fixes)\s+#(\d+)\b/gi;
13571
- let m = re.exec(body);
13572
- while (m !== null) {
13573
- out.add(Number(m[1]));
13574
- m = re.exec(body);
13575
- }
13576
- return [...out];
13577
- };
13578
- var applyTicketAssigneeFromPayload = (row, payload) => {
13579
- if (!Object.prototype.hasOwnProperty.call(payload, "assignee")) return;
13580
- const v = payload["assignee"];
13581
- if (v === null) {
13582
- delete row.assignee;
13583
- return;
13584
- }
13585
- if (typeof v !== "string") return;
13586
- const n = normalizeGithubLogin(v);
13587
- if (n === "") delete row.assignee;
13588
- else row.assignee = n;
13589
- };
13590
- var storyIdFromTicketCreatedPayload = (payload) => {
13591
- if (!Object.prototype.hasOwnProperty.call(payload, "storyId")) {
13592
- return null;
13593
- }
13594
- const v = payload["storyId"];
13595
- if (v === null) return null;
13596
- if (typeof v !== "string") return null;
13597
- const t = v.trim();
13598
- return t === "" ? null : t;
13599
- };
13600
- var applyTicketStoryIdFromPayload = (row, payload) => {
13601
- if (!Object.prototype.hasOwnProperty.call(payload, "storyId")) return;
13602
- const v = payload["storyId"];
13603
- if (v === null) {
13604
- row.storyId = null;
13605
- return;
13606
- }
13607
- if (typeof v !== "string") return;
13608
- const t = v.trim();
13609
- row.storyId = t === "" ? null : t;
13610
- };
13611
- var linkedBranchesFromTicketCreatedPayload = (payload) => {
13612
- if (!Object.prototype.hasOwnProperty.call(payload, "branches")) {
13613
- return [];
13614
- }
13615
- return normalizeTicketBranchListFromPayloadValue(payload["branches"]);
13616
- };
13617
- var applyTicketBranchesFromUpdatePayload = (row, payload) => {
13618
- if (!Object.prototype.hasOwnProperty.call(payload, "branches")) return;
13619
- const v = payload["branches"];
13620
- if (!Array.isArray(v)) return;
13621
- row.linkedBranches = normalizeTicketBranchListFromPayloadValue(v);
13622
- };
13623
- var applyTicketPlanningFieldsFromCreatePayload = (row, payload) => {
13624
- if (Object.prototype.hasOwnProperty.call(payload, "labels")) {
13625
- const v = ticketLabelsFromPayloadValue(payload["labels"]);
13626
- if (v !== void 0 && v.length > 0) {
13627
- row.labels = v;
13628
- }
13629
- }
13630
- const pr = readTicketPriorityPatch(payload);
13631
- if (typeof pr === "string") {
13632
- row.priority = pr;
13633
- }
13634
- const sz = readTicketSizePatch(payload);
13635
- if (typeof sz === "string") {
13636
- row.size = sz;
13637
- }
13638
- const est = readTicketEstimatePatch(payload);
13639
- if (typeof est === "number") {
13640
- row.estimate = est;
13641
- }
13642
- const sw = readTicketIsoInstantPatch(payload, "startWorkAt");
13643
- if (typeof sw === "string") {
13644
- row.startWorkAt = sw;
13645
- }
13646
- const tf = readTicketIsoInstantPatch(payload, "targetFinishAt");
13647
- if (typeof tf === "string") {
13648
- row.targetFinishAt = tf;
13649
- }
13650
- };
13651
- var applyTicketPlanningFieldsFromUpdatePayload = (row, payload) => {
13652
- if (Object.prototype.hasOwnProperty.call(payload, "labels")) {
13653
- if (payload["labels"] === null) {
13654
- delete row.labels;
13655
- } else {
13656
- const v = ticketLabelsFromPayloadValue(payload["labels"]);
13657
- if (v !== void 0) {
13658
- if (v.length === 0) {
13659
- delete row.labels;
13660
- } else {
13661
- row.labels = v;
13662
- }
13663
- }
13664
- }
13665
- }
13666
- const pr = readTicketPriorityPatch(payload);
13667
- if (pr === null) {
13668
- delete row.priority;
13669
- } else if (typeof pr === "string") {
13670
- row.priority = pr;
13671
- }
13672
- const sz = readTicketSizePatch(payload);
13673
- if (sz === null) {
13674
- delete row.size;
13675
- } else if (typeof sz === "string") {
13676
- row.size = sz;
13677
- }
13678
- const est = readTicketEstimatePatch(payload);
13679
- if (est === null) {
13680
- delete row.estimate;
13681
- } else if (typeof est === "number") {
13682
- row.estimate = est;
13683
- }
13684
- const sw = readTicketIsoInstantPatch(payload, "startWorkAt");
13685
- if (sw === null) {
13686
- delete row.startWorkAt;
13687
- } else if (typeof sw === "string") {
13688
- row.startWorkAt = sw;
13689
- }
13690
- const tf = readTicketIsoInstantPatch(payload, "targetFinishAt");
13691
- if (tf === null) {
13692
- delete row.targetFinishAt;
13693
- } else if (typeof tf === "string") {
13694
- row.targetFinishAt = tf;
13695
- }
13696
- };
13697
- var applyCreatedAudit = (row, evt) => {
13698
- row.createdAt = evt.ts;
13699
- row.createdBy = evt.actor;
13700
- row.updatedAt = evt.ts;
13701
- row.updatedBy = evt.actor;
13702
- };
13703
- var applyLastUpdate = (row, evt) => {
13704
- row.updatedAt = evt.ts;
13705
- row.updatedBy = evt.actor;
13706
- };
13707
- var applyStatusIfChanged = (row, evt, nextStatus) => {
13708
- if (row.status === nextStatus) return false;
13709
- row.status = nextStatus;
13710
- row.statusChangedAt = evt.ts;
13711
- row.statusChangedBy = evt.actor;
13712
- return true;
13713
- };
13714
- var appendTicketCommentFromEvent = (ticket, evt) => {
13715
- const list = ticket.comments ?? (ticket.comments = []);
13716
- list.push({
13717
- id: evt.id,
13718
- body: String(evt.payload["body"] ?? ""),
13719
- createdAt: evt.ts,
13720
- createdBy: evt.actor
13721
- });
13722
- applyLastUpdate(ticket, evt);
13723
- };
13724
- var resolveInboundTicketStatusFromPayload = (ticket, payload) => {
13725
- const explicit = parseWorkItemStatus(payload["status"]);
13726
- if (explicit !== void 0) return explicit;
13727
- const legacySt = payload["state"];
13728
- if (legacySt === "open" || legacySt === "closed") {
13729
- return resolveTicketInboundStatus({
13730
- issueState: legacySt,
13731
- currentStatus: ticket.status
13732
- });
13733
- }
13734
- return void 0;
13735
- };
13736
- var applyEvent = (projection, evt) => {
13737
- switch (evt.type) {
13738
- case "EpicCreated": {
13739
- const id = String(evt.payload["id"]);
13740
- const status = resolveStatusForNewEpicStoryPayload(evt.payload);
13741
- const row = {
13742
- id,
13743
- title: String(evt.payload["title"] ?? ""),
13744
- body: String(evt.payload["body"] ?? ""),
13745
- status,
13746
- statusChangedAt: evt.ts,
13747
- statusChangedBy: evt.actor,
13748
- createdAt: "",
13749
- createdBy: "",
13750
- updatedAt: "",
13751
- updatedBy: ""
13752
- };
13753
- applyCreatedAudit(row, evt);
13754
- projection.epics.set(id, row);
13755
- break;
13464
+ var applyEvent = (projection, evt) => {
13465
+ switch (evt.type) {
13466
+ case "EpicCreated": {
13467
+ const id = String(evt.payload["id"]);
13468
+ const status = resolveStatusForNewEpicStoryPayload(evt.payload);
13469
+ const row = {
13470
+ id,
13471
+ number: resolveWorkItemCreateNumber(projection, "epic", evt.payload),
13472
+ title: String(evt.payload["title"] ?? ""),
13473
+ body: String(evt.payload["body"] ?? ""),
13474
+ status,
13475
+ statusChangedAt: evt.ts,
13476
+ statusChangedBy: evt.actor,
13477
+ createdAt: "",
13478
+ createdBy: "",
13479
+ updatedAt: "",
13480
+ updatedBy: ""
13481
+ };
13482
+ applyCreatedAudit(row, evt);
13483
+ projection.epics.set(id, row);
13484
+ break;
13756
13485
  }
13757
13486
  case "EpicUpdated": {
13758
13487
  const id = String(evt.payload["id"]);
@@ -13785,6 +13514,7 @@ var applyEvent = (projection, evt) => {
13785
13514
  const status = resolveStatusForNewEpicStoryPayload(evt.payload);
13786
13515
  const row = {
13787
13516
  id,
13517
+ number: resolveWorkItemCreateNumber(projection, "story", evt.payload),
13788
13518
  epicId: String(evt.payload["epicId"]),
13789
13519
  title: String(evt.payload["title"] ?? ""),
13790
13520
  body: String(evt.payload["body"] ?? ""),
@@ -13832,6 +13562,7 @@ var applyEvent = (projection, evt) => {
13832
13562
  const status = resolveStatusForNewTicketPayload(evt.payload);
13833
13563
  const row = {
13834
13564
  id,
13565
+ number: resolveWorkItemCreateNumber(projection, "ticket", evt.payload),
13835
13566
  storyId: storyIdFromTicketCreatedPayload(evt.payload),
13836
13567
  title: String(evt.payload["title"] ?? ""),
13837
13568
  body,
@@ -13944,28 +13675,733 @@ var applyEvent = (projection, evt) => {
13944
13675
  }
13945
13676
  break;
13946
13677
  }
13947
- default:
13948
- break;
13678
+ default:
13679
+ break;
13680
+ }
13681
+ };
13682
+ var replayEvents = (lines) => {
13683
+ const proj = emptyProjection();
13684
+ const events = [];
13685
+ for (const line of lines) {
13686
+ const trimmed = line.trim();
13687
+ if (!trimmed) continue;
13688
+ const json = JSON.parse(trimmed);
13689
+ events.push(eventLineSchema.parse(json));
13690
+ }
13691
+ events.sort((a, b) => {
13692
+ const t = a.ts.localeCompare(b.ts);
13693
+ if (t !== 0) return t;
13694
+ return a.id.localeCompare(b.id);
13695
+ });
13696
+ for (const e of events) {
13697
+ applyEvent(proj, e);
13698
+ }
13699
+ return proj;
13700
+ };
13701
+
13702
+ // src/cli/resolve-projection-work-item-id.ts
13703
+ var isDigitOnlyWorkItemRef = (raw) => /^\d+$/.test(raw.trim());
13704
+ var resolveEpicId = (projection, raw) => {
13705
+ const t = raw.trim();
13706
+ if (t === "") return void 0;
13707
+ if (projection.epics.has(t)) return t;
13708
+ if (!isDigitOnlyWorkItemRef(t)) return void 0;
13709
+ const n = Number(t);
13710
+ const hits = [...projection.epics.values()].filter((e) => e.number === n);
13711
+ if (hits.length !== 1) return void 0;
13712
+ return hits[0].id;
13713
+ };
13714
+ var resolveStoryId = (projection, raw) => {
13715
+ const t = raw.trim();
13716
+ if (t === "") return void 0;
13717
+ if (projection.stories.has(t)) return t;
13718
+ if (!isDigitOnlyWorkItemRef(t)) return void 0;
13719
+ const n = Number(t);
13720
+ const hits = [...projection.stories.values()].filter((s) => s.number === n);
13721
+ if (hits.length !== 1) return void 0;
13722
+ return hits[0].id;
13723
+ };
13724
+ var resolveTicketId = (projection, raw) => {
13725
+ const t = raw.trim();
13726
+ if (t === "") return void 0;
13727
+ if (projection.tickets.has(t)) return t;
13728
+ if (!isDigitOnlyWorkItemRef(t)) return void 0;
13729
+ const n = Number(t);
13730
+ const hits = [...projection.tickets.values()].filter((x) => x.number === n);
13731
+ if (hits.length !== 1) return void 0;
13732
+ return hits[0].id;
13733
+ };
13734
+ var resolveTicketDependsOnTokensToIds = (projection, tokens) => {
13735
+ const mapped = tokens.map((raw) => {
13736
+ const t = raw.trim();
13737
+ if (t === "") return "";
13738
+ if (projection.tickets.has(t)) return t;
13739
+ if (!isDigitOnlyWorkItemRef(t)) return t;
13740
+ const hit = resolveTicketId(projection, t);
13741
+ return hit ?? t;
13742
+ });
13743
+ return normalizeTicketDependsOnIds(mapped);
13744
+ };
13745
+ var assertCreatePayloadUsesExpectedHeadNumber = (projection, kind, payload) => {
13746
+ const n = readOptionalPositiveIntegerFromPayload(payload, "number");
13747
+ const expected = kind === "epic" ? nextEpicNumberForCreate(projection) : kind === "story" ? nextStoryNumberForCreate(projection) : nextTicketNumberForCreate(projection);
13748
+ if (n !== expected) {
13749
+ const got = n === void 0 ? "missing" : String(n);
13750
+ throw new Error(
13751
+ `Invalid ${kind} number for this data branch: expected ${String(expected)}, got ${got}.`
13752
+ );
13753
+ }
13754
+ };
13755
+
13756
+ // src/config/hyper-pm-config.ts
13757
+ var hyperPmConfigSchema = external_exports.object({
13758
+ schema: external_exports.literal(1),
13759
+ dataBranch: external_exports.string().min(1),
13760
+ remote: external_exports.string().min(1).default("origin"),
13761
+ sync: external_exports.enum(["off", "outbound", "full"]).default("outbound"),
13762
+ githubRepo: external_exports.string().optional(),
13763
+ issueMapping: external_exports.enum(["ticket", "story", "epic"]).default("ticket")
13764
+ });
13765
+
13766
+ // src/config/load-config.ts
13767
+ var import_promises = require("node:fs/promises");
13768
+ var import_node_path = require("node:path");
13769
+ var CONFIG_DIR = ".hyper-pm";
13770
+ var CONFIG_FILE = "config.json";
13771
+ var getHyperPmConfigPath = (repoRoot) => {
13772
+ return (0, import_node_path.join)(repoRoot, CONFIG_DIR, CONFIG_FILE);
13773
+ };
13774
+ var loadHyperPmConfig = async (repoRoot, overrides = {}) => {
13775
+ const raw = await (0, import_promises.readFile)(getHyperPmConfigPath(repoRoot), "utf8");
13776
+ const parsed = JSON.parse(raw);
13777
+ const base = hyperPmConfigSchema.parse(parsed);
13778
+ const merged = { ...base, ...stripUndefined(overrides) };
13779
+ return hyperPmConfigSchema.parse(merged);
13780
+ };
13781
+ var stripUndefined = (obj) => {
13782
+ const out = {};
13783
+ for (const [k, v] of Object.entries(obj)) {
13784
+ if (v !== void 0) out[k] = v;
13785
+ }
13786
+ return out;
13787
+ };
13788
+
13789
+ // src/config/save-config.ts
13790
+ var import_promises2 = require("node:fs/promises");
13791
+ var import_node_path2 = require("node:path");
13792
+ var saveHyperPmConfig = async (repoRoot, config) => {
13793
+ const target = getHyperPmConfigPath(repoRoot);
13794
+ await (0, import_promises2.mkdir)((0, import_node_path2.dirname)(target), { recursive: true });
13795
+ await (0, import_promises2.writeFile)(target, `${JSON.stringify(config, null, 2)}
13796
+ `, "utf8");
13797
+ };
13798
+
13799
+ // src/doctor/run-doctor.ts
13800
+ var runDoctorOnLines = (lines) => {
13801
+ const issues = [];
13802
+ for (let i = 0; i < lines.length; i++) {
13803
+ const line = lines[i]?.trim() ?? "";
13804
+ if (!line) continue;
13805
+ let json;
13806
+ try {
13807
+ json = JSON.parse(line);
13808
+ } catch (e) {
13809
+ issues.push({
13810
+ kind: "invalid-json",
13811
+ line: i + 1,
13812
+ message: e instanceof Error ? e.message : "parse error"
13813
+ });
13814
+ return issues;
13815
+ }
13816
+ const parsed = eventLineSchema.safeParse(json);
13817
+ if (!parsed.success) {
13818
+ issues.push({
13819
+ kind: "invalid-event",
13820
+ line: i + 1,
13821
+ message: parsed.error.message
13822
+ });
13823
+ return issues;
13824
+ }
13825
+ }
13826
+ return issues;
13827
+ };
13828
+
13829
+ // src/git/create-and-checkout-branch.ts
13830
+ var createAndCheckoutBranch = async (opts) => {
13831
+ await opts.runGit(opts.repoRoot, [
13832
+ "switch",
13833
+ "-c",
13834
+ opts.branchName,
13835
+ opts.startPoint
13836
+ ]);
13837
+ };
13838
+
13839
+ // src/git/data-worktree-session.ts
13840
+ var import_promises3 = require("node:fs/promises");
13841
+ var import_node_path3 = require("node:path");
13842
+ var ensureDir = async (path) => {
13843
+ await (0, import_promises3.mkdir)(path, { recursive: true });
13844
+ };
13845
+ var defaultPathExists = async (path) => {
13846
+ try {
13847
+ await (0, import_promises3.access)(path);
13848
+ return true;
13849
+ } catch {
13850
+ return false;
13851
+ }
13852
+ };
13853
+ var parseDataBranchWorktreeFromPorcelain = (stdout, dataBranch) => {
13854
+ const wantRef = `refs/heads/${dataBranch}`;
13855
+ const blocks = stdout.split(/\n\n+/).map((b) => b.trim()).filter(Boolean);
13856
+ for (const block of blocks) {
13857
+ const lines = block.split("\n");
13858
+ let worktreePath;
13859
+ let branch;
13860
+ for (const line of lines) {
13861
+ if (line.startsWith("worktree ")) {
13862
+ worktreePath = line.slice("worktree ".length);
13863
+ } else if (line.startsWith("branch ")) {
13864
+ branch = line.slice("branch ".length);
13865
+ }
13866
+ }
13867
+ if (worktreePath !== void 0 && branch === wantRef) {
13868
+ return worktreePath;
13869
+ }
13870
+ }
13871
+ return void 0;
13872
+ };
13873
+ var openDataBranchWorktree = async (opts) => {
13874
+ const pathExists = opts.pathExists ?? defaultPathExists;
13875
+ const { stdout: listOut } = await opts.runGit(opts.repoRoot, [
13876
+ "worktree",
13877
+ "list",
13878
+ "--porcelain"
13879
+ ]);
13880
+ const listedPath = parseDataBranchWorktreeFromPorcelain(
13881
+ listOut,
13882
+ opts.dataBranch
13883
+ );
13884
+ if (listedPath !== void 0 && await pathExists(listedPath)) {
13885
+ const dispose2 = async () => {
13886
+ return;
13887
+ };
13888
+ return { worktreePath: listedPath, dispose: dispose2 };
13889
+ }
13890
+ const worktreePath = (0, import_node_path3.join)(
13891
+ opts.tmpBase,
13892
+ `hyper-pm-worktree-${ulid().toLowerCase()}`
13893
+ );
13894
+ await ensureDir(opts.tmpBase);
13895
+ try {
13896
+ await opts.runGit(opts.repoRoot, [
13897
+ "worktree",
13898
+ "add",
13899
+ worktreePath,
13900
+ opts.dataBranch
13901
+ ]);
13902
+ } catch (err) {
13903
+ await (0, import_promises3.rm)(worktreePath, { recursive: true, force: true }).catch(() => {
13904
+ });
13905
+ throw err;
13906
+ }
13907
+ const dispose = async () => {
13908
+ if (opts.keepWorktree) {
13909
+ return;
13910
+ }
13911
+ await opts.runGit(opts.repoRoot, ["worktree", "remove", "--force", worktreePath]).catch(() => {
13912
+ });
13913
+ await (0, import_promises3.rm)(worktreePath, { recursive: true, force: true }).catch(() => {
13914
+ });
13915
+ };
13916
+ return { worktreePath, dispose };
13917
+ };
13918
+
13919
+ // src/git/find-git-root.ts
13920
+ var findGitRoot = async (cwd, deps) => {
13921
+ const { stdout } = await deps.runGit(cwd, ["rev-parse", "--show-toplevel"]);
13922
+ return stdout;
13923
+ };
13924
+
13925
+ // src/git/init-orphan-data-branch.ts
13926
+ var import_promises4 = require("node:fs/promises");
13927
+ var import_node_path4 = require("node:path");
13928
+ var initOrphanDataBranchInWorktree = async (opts) => {
13929
+ const { stdout: tip } = await opts.runGit(opts.repoRoot, [
13930
+ "rev-parse",
13931
+ "HEAD"
13932
+ ]);
13933
+ const tipCommit = tip.trim();
13934
+ await opts.runGit(opts.repoRoot, [
13935
+ "worktree",
13936
+ "add",
13937
+ opts.worktreePath,
13938
+ tipCommit
13939
+ ]);
13940
+ await opts.runGit(opts.worktreePath, [
13941
+ "checkout",
13942
+ "--orphan",
13943
+ opts.dataBranch
13944
+ ]);
13945
+ await opts.runGit(opts.worktreePath, ["rm", "-rf", "."]).catch(() => {
13946
+ });
13947
+ const marker = (0, import_node_path4.join)(opts.worktreePath, "README.hyper-pm.md");
13948
+ await (0, import_promises4.writeFile)(
13949
+ marker,
13950
+ "# hyper-pm data branch\n\nAppend-only events live under `events/`.\n",
13951
+ "utf8"
13952
+ );
13953
+ await opts.runGit(opts.worktreePath, ["add", "."]);
13954
+ await opts.runGit(opts.worktreePath, [
13955
+ "commit",
13956
+ "-m",
13957
+ "init hyper-pm data branch"
13958
+ ]);
13959
+ };
13960
+
13961
+ // src/git/pick-unique-local-branch-name.ts
13962
+ var DEFAULT_MAX_SUFFIX = 1e3;
13963
+ var localBranchRefExists = async (repoRoot, name, git) => {
13964
+ try {
13965
+ await git(repoRoot, ["show-ref", "--verify", `refs/heads/${name}`]);
13966
+ return true;
13967
+ } catch {
13968
+ return false;
13969
+ }
13970
+ };
13971
+ var pickUniqueLocalBranchName = async (opts) => {
13972
+ const max = opts.maxSuffix ?? DEFAULT_MAX_SUFFIX;
13973
+ const { preferredBase, repoRoot, runGit: git } = opts;
13974
+ for (let n = 1; n <= max; n += 1) {
13975
+ const raw = n === 1 ? preferredBase : `${preferredBase}-${n}`;
13976
+ const norm = normalizeTicketBranchName(raw);
13977
+ if (norm === void 0) {
13978
+ continue;
13979
+ }
13980
+ if (!await localBranchRefExists(repoRoot, norm, git)) {
13981
+ return { branch: norm, preferred: preferredBase };
13982
+ }
13983
+ }
13984
+ throw new Error(
13985
+ `Could not allocate a free local branch name from ${JSON.stringify(preferredBase)} (tried suffixes up to -${String(max)})`
13986
+ );
13987
+ };
13988
+
13989
+ // src/git/resolve-integration-start-point.ts
13990
+ var assertGitRefResolvable = async (repoRoot, ref, git) => {
13991
+ try {
13992
+ await git(repoRoot, ["rev-parse", "-q", "--verify", ref]);
13993
+ } catch {
13994
+ throw new Error(`Invalid or ambiguous --from ref: ${ref}`);
13995
+ }
13996
+ };
13997
+ var resolveIntegrationStartPoint = async (repoRoot, remote, git) => {
13998
+ const symRef = `refs/remotes/${remote}/HEAD`;
13999
+ try {
14000
+ const { stdout } = await git(repoRoot, ["symbolic-ref", "-q", symRef]);
14001
+ const target = stdout.trim();
14002
+ if (target !== "") {
14003
+ await git(repoRoot, ["rev-parse", "-q", "--verify", target]);
14004
+ return target;
14005
+ }
14006
+ } catch {
14007
+ }
14008
+ for (const head of ["refs/heads/main", "refs/heads/master"]) {
14009
+ try {
14010
+ await git(repoRoot, ["rev-parse", "-q", "--verify", head]);
14011
+ return head;
14012
+ } catch {
14013
+ }
14014
+ }
14015
+ try {
14016
+ await git(repoRoot, ["rev-parse", "-q", "--verify", "HEAD"]);
14017
+ return "HEAD";
14018
+ } catch {
14019
+ }
14020
+ throw new Error(
14021
+ "Could not resolve a default branch to branch from; pass --from <ref> (e.g. main or origin/main)."
14022
+ );
14023
+ };
14024
+
14025
+ // src/git/list-repo-commit-authors.ts
14026
+ var listRepoCommitAuthors = async (repoRoot, git) => {
14027
+ try {
14028
+ const { stdout } = await git(repoRoot, [
14029
+ "-c",
14030
+ "log.showSignature=false",
14031
+ "log",
14032
+ "--all",
14033
+ "--format=%an%x1f%ae"
14034
+ ]);
14035
+ if (stdout === "") return [];
14036
+ const lines = stdout.split("\n").filter((l) => l.length > 0);
14037
+ const seenEmail = /* @__PURE__ */ new Set();
14038
+ const out = [];
14039
+ for (const line of lines) {
14040
+ const sep2 = line.indexOf("");
14041
+ if (sep2 <= 0) continue;
14042
+ const name = line.slice(0, sep2).trim();
14043
+ const email = line.slice(sep2 + 1).trim();
14044
+ if (email === "") continue;
14045
+ const key = email.toLowerCase();
14046
+ if (seenEmail.has(key)) continue;
14047
+ seenEmail.add(key);
14048
+ const loginGuess = guessGithubLoginFromContact(name, email);
14049
+ out.push(
14050
+ loginGuess !== void 0 ? { name, email, loginGuess } : { name, email }
14051
+ );
14052
+ }
14053
+ return out;
14054
+ } catch {
14055
+ return [];
14056
+ }
14057
+ };
14058
+
14059
+ // src/git/parse-github-owner-repo-from-remote-url.ts
14060
+ var parseGithubOwnerRepoFromRemoteUrl = (rawUrl) => {
14061
+ const trimmed = rawUrl.trim();
14062
+ if (!trimmed) {
14063
+ return void 0;
14064
+ }
14065
+ const scpMatch = /^git@github\.com:(?<path>.+)$/i.exec(trimmed);
14066
+ if (scpMatch?.groups?.path) {
14067
+ return slugFromGithubPath(scpMatch.groups.path);
14068
+ }
14069
+ try {
14070
+ const withScheme = trimmed.includes("://") ? trimmed : `https://${trimmed}`;
14071
+ const u = new URL(withScheme);
14072
+ const host = u.hostname.toLowerCase();
14073
+ if (host !== "github.com" && host !== "www.github.com") {
14074
+ return void 0;
14075
+ }
14076
+ return slugFromGithubPath(u.pathname);
14077
+ } catch {
14078
+ return void 0;
14079
+ }
14080
+ };
14081
+ var slugFromGithubPath = (path) => {
14082
+ const segments = path.replace(/^\/+/, "").split("/").filter((s) => s.length > 0).map((s) => s.replace(/\.git$/i, ""));
14083
+ if (segments.length < 2) {
14084
+ return void 0;
14085
+ }
14086
+ const owner = segments[0];
14087
+ const repo = segments[1];
14088
+ if (!owner || !repo) {
14089
+ return void 0;
14090
+ }
14091
+ return `${owner}/${repo}`;
14092
+ };
14093
+
14094
+ // src/git/try-read-github-owner-repo-slug-from-git.ts
14095
+ var tryReadGithubOwnerRepoSlugFromGit = async (params) => {
14096
+ try {
14097
+ const { stdout } = await params.runGit(params.repoRoot, [
14098
+ "remote",
14099
+ "get-url",
14100
+ params.remote
14101
+ ]);
14102
+ return parseGithubOwnerRepoFromRemoteUrl(stdout);
14103
+ } catch {
14104
+ return void 0;
14105
+ }
14106
+ };
14107
+
14108
+ // src/git/resolve-effective-git-author-for-data-commit.ts
14109
+ var defaultDataCommitName = "hyper-pm";
14110
+ var defaultDataCommitEmail = "hyper-pm@users.noreply.github.com";
14111
+ var tryReadGitConfigUserName = async (cwd, runGit2) => {
14112
+ try {
14113
+ const { stdout } = await runGit2(cwd, ["config", "--get", "user.name"]);
14114
+ return stdout.trim();
14115
+ } catch {
14116
+ return "";
14117
+ }
14118
+ };
14119
+ var tryReadGitConfigUserEmail = async (cwd, runGit2) => {
14120
+ try {
14121
+ const { stdout } = await runGit2(cwd, ["config", "--get", "user.email"]);
14122
+ return stdout.trim();
14123
+ } catch {
14124
+ return "";
14125
+ }
14126
+ };
14127
+ var resolveEffectiveGitAuthorForDataCommit = async (cwd, runGit2, authorEnv) => {
14128
+ const fromGitName = await tryReadGitConfigUserName(cwd, runGit2);
14129
+ const fromGitEmail = await tryReadGitConfigUserEmail(cwd, runGit2);
14130
+ const name = fromGitName || authorEnv.HYPER_PM_GIT_USER_NAME?.trim() || authorEnv.GIT_AUTHOR_NAME?.trim() || defaultDataCommitName;
14131
+ const email = fromGitEmail || authorEnv.HYPER_PM_GIT_USER_EMAIL?.trim() || authorEnv.GIT_AUTHOR_EMAIL?.trim() || defaultDataCommitEmail;
14132
+ return { name, email };
14133
+ };
14134
+
14135
+ // src/run/commit-data.ts
14136
+ var maxActorSuffixLen = 60;
14137
+ var formatDataBranchCommitMessage = (base, actorSuffix) => {
14138
+ const raw = actorSuffix?.trim();
14139
+ if (!raw) {
14140
+ return base;
14141
+ }
14142
+ const collapsed = raw.replace(/\s+/g, " ");
14143
+ const suffix = collapsed.length > maxActorSuffixLen ? `${collapsed.slice(0, maxActorSuffixLen - 1)}\u2026` : collapsed;
14144
+ return `${base} (${suffix})`;
14145
+ };
14146
+ var commitDataWorktreeIfNeeded = async (worktreePath, message, runGit2, opts) => {
14147
+ const { stdout } = await runGit2(worktreePath, ["status", "--porcelain"]);
14148
+ if (!stdout.trim()) return;
14149
+ await runGit2(worktreePath, ["add", "."]);
14150
+ const authorEnv = opts?.authorEnv ?? env;
14151
+ const { name, email } = await resolveEffectiveGitAuthorForDataCommit(
14152
+ worktreePath,
14153
+ runGit2,
14154
+ authorEnv
14155
+ );
14156
+ await runGit2(worktreePath, [
14157
+ "-c",
14158
+ `user.name=${name}`,
14159
+ "-c",
14160
+ `user.email=${email}`,
14161
+ "commit",
14162
+ "-m",
14163
+ message
14164
+ ]);
14165
+ };
14166
+
14167
+ // src/run/push-data-branch.ts
14168
+ var firstLineFromUnknown = (err) => {
14169
+ const raw = err instanceof Error ? err.message : String(err);
14170
+ const line = raw.trim().split("\n")[0]?.trim() ?? raw.trim();
14171
+ return line.length > 0 ? line : "git error";
14172
+ };
14173
+ var tryPushDataBranchToRemote = async (worktreePath, remote, branch, runGit2) => {
14174
+ try {
14175
+ await runGit2(worktreePath, ["remote", "get-url", remote]);
14176
+ } catch (e) {
14177
+ return {
14178
+ status: "skipped_no_remote",
14179
+ detail: firstLineFromUnknown(e)
14180
+ };
14181
+ }
14182
+ try {
14183
+ await runGit2(worktreePath, ["push", "-u", remote, branch]);
14184
+ return { status: "pushed" };
14185
+ } catch (e) {
14186
+ return {
14187
+ status: "failed",
14188
+ detail: firstLineFromUnknown(e)
14189
+ };
14190
+ }
14191
+ };
14192
+
14193
+ // src/run/sync-remote-data-branch.ts
14194
+ var SyncRemoteDataBranchMergeError = class extends Error {
14195
+ /** @param message - Human-readable reason (stderr excerpt or generic text). */
14196
+ constructor(message) {
14197
+ super(message);
14198
+ this.name = "SyncRemoteDataBranchMergeError";
14199
+ }
14200
+ };
14201
+ var remoteTrackingRef = (remote, dataBranch) => `refs/remotes/${remote}/${dataBranch}`;
14202
+ var mergeRefSpecifier = (remote, dataBranch) => `${remote}/${dataBranch}`;
14203
+ var isLikelyNonFastForwardPushFailure = (detail) => {
14204
+ if (detail === void 0) return false;
14205
+ const d = detail.toLowerCase();
14206
+ return d.includes("non-fast-forward") || d.includes("failed to push");
14207
+ };
14208
+ var classifyMergeOutput = (combined) => {
14209
+ const c = combined.toLowerCase();
14210
+ if (c.includes("already up to date")) {
14211
+ return "up_to_date";
14212
+ }
14213
+ if (c.includes("fast-forward")) {
14214
+ return "fast_forward";
14215
+ }
14216
+ return "merge_commit";
14217
+ };
14218
+ var refExists = async (cwd, ref, runGit2) => {
14219
+ try {
14220
+ await runGit2(cwd, ["show-ref", "--verify", "--quiet", ref]);
14221
+ return true;
14222
+ } catch {
14223
+ return false;
14224
+ }
14225
+ };
14226
+ var fetchRemoteDataBranch = async (worktreePath, remote, dataBranch, runGit2) => {
14227
+ try {
14228
+ await runGit2(worktreePath, ["remote", "get-url", remote]);
14229
+ } catch {
14230
+ return "skipped_no_remote";
14231
+ }
14232
+ try {
14233
+ await runGit2(worktreePath, ["fetch", remote, dataBranch]);
14234
+ return "ok";
14235
+ } catch (e) {
14236
+ const msg = e instanceof Error ? e.message : String(e);
14237
+ if (/couldn't find remote ref|could not find remote ref/i.test(msg)) {
14238
+ return "remote_branch_absent";
14239
+ }
14240
+ throw e;
14241
+ }
14242
+ };
14243
+ var mergeRemoteTrackingIntoHead = async (worktreePath, remote, dataBranch, runGit2, authorEnv = env) => {
14244
+ const spec = mergeRefSpecifier(remote, dataBranch);
14245
+ const { name, email } = await resolveEffectiveGitAuthorForDataCommit(
14246
+ worktreePath,
14247
+ runGit2,
14248
+ authorEnv
14249
+ );
14250
+ try {
14251
+ const { stdout, stderr } = await runGit2(worktreePath, [
14252
+ "-c",
14253
+ `user.name=${name}`,
14254
+ "-c",
14255
+ `user.email=${email}`,
14256
+ "merge",
14257
+ "--no-edit",
14258
+ spec
14259
+ ]);
14260
+ return classifyMergeOutput(`${stdout}
14261
+ ${stderr}`);
14262
+ } catch (e) {
14263
+ await runGit2(worktreePath, ["merge", "--abort"]).catch(() => {
14264
+ });
14265
+ const msg = e instanceof Error ? e.message : String(e);
14266
+ throw new SyncRemoteDataBranchMergeError(
14267
+ msg.toLowerCase().includes("conflict") ? "Merge conflict while syncing hyper-pm data branch; merge aborted. Resolve manually on the data branch if needed." : `git merge failed: ${msg.trim().split("\n")[0] ?? msg}`
14268
+ );
14269
+ }
14270
+ };
14271
+ var runRemoteDataBranchGitSync = async (worktreePath, remote, dataBranch, runGit2, skipPush, deps = {}) => {
14272
+ const tryPushFn = deps.tryPush ?? tryPushDataBranchToRemote;
14273
+ const maxPushAttempts = deps.maxPushAttempts ?? 3;
14274
+ const authorEnv = deps.authorEnv ?? env;
14275
+ let dataBranchFetch = await fetchRemoteDataBranch(worktreePath, remote, dataBranch, runGit2);
14276
+ let dataBranchMerge = dataBranchFetch === "skipped_no_remote" ? "skipped_no_remote" : dataBranchFetch === "remote_branch_absent" ? "skipped_missing_remote_branch" : "up_to_date";
14277
+ if (dataBranchFetch === "ok") {
14278
+ const tracking = remoteTrackingRef(remote, dataBranch);
14279
+ const exists = await refExists(worktreePath, tracking, runGit2);
14280
+ if (!exists) {
14281
+ dataBranchMerge = "skipped_missing_remote_branch";
14282
+ } else {
14283
+ dataBranchMerge = await mergeRemoteTrackingIntoHead(
14284
+ worktreePath,
14285
+ remote,
14286
+ dataBranch,
14287
+ runGit2,
14288
+ authorEnv
14289
+ );
14290
+ }
14291
+ }
14292
+ if (skipPush) {
14293
+ return {
14294
+ dataBranchFetch,
14295
+ dataBranchMerge,
14296
+ dataBranchPush: "skipped_cli",
14297
+ dataBranchPushDetail: "skip-push",
14298
+ pushAttempts: 0
14299
+ };
14300
+ }
14301
+ let pushAttempts = 0;
14302
+ let lastPush = { status: "skipped_no_remote" };
14303
+ const refetchAndMerge = async () => {
14304
+ dataBranchFetch = await fetchRemoteDataBranch(
14305
+ worktreePath,
14306
+ remote,
14307
+ dataBranch,
14308
+ runGit2
14309
+ );
14310
+ if (dataBranchFetch !== "ok") {
14311
+ return;
14312
+ }
14313
+ const tracking = remoteTrackingRef(remote, dataBranch);
14314
+ const exists = await refExists(worktreePath, tracking, runGit2);
14315
+ if (!exists) {
14316
+ return;
14317
+ }
14318
+ dataBranchMerge = await mergeRemoteTrackingIntoHead(
14319
+ worktreePath,
14320
+ remote,
14321
+ dataBranch,
14322
+ runGit2,
14323
+ authorEnv
14324
+ );
14325
+ };
14326
+ for (let attempt = 1; attempt <= maxPushAttempts; attempt += 1) {
14327
+ pushAttempts = attempt;
14328
+ lastPush = await tryPushFn(worktreePath, remote, dataBranch, runGit2);
14329
+ if (lastPush.status === "pushed" || lastPush.status === "skipped_no_remote") {
14330
+ break;
14331
+ }
14332
+ if (lastPush.status === "failed" && isLikelyNonFastForwardPushFailure(lastPush.detail) && attempt < maxPushAttempts) {
14333
+ await refetchAndMerge();
14334
+ continue;
14335
+ }
14336
+ break;
14337
+ }
14338
+ return {
14339
+ dataBranchFetch,
14340
+ dataBranchMerge,
14341
+ dataBranchPush: lastPush.status,
14342
+ ...lastPush.detail !== void 0 ? { dataBranchPushDetail: lastPush.detail } : {},
14343
+ pushAttempts
14344
+ };
14345
+ };
14346
+
14347
+ // src/storage/append-event.ts
14348
+ var import_promises5 = require("node:fs/promises");
14349
+ var import_node_path5 = require("node:path");
14350
+
14351
+ // src/storage/event-path.ts
14352
+ var nextEventRelPath = (now, deps) => {
14353
+ const y = String(now.getUTCFullYear());
14354
+ const m = String(now.getUTCMonth() + 1).padStart(2, "0");
14355
+ const nextId = deps?.nextId ?? (() => ulid().toLowerCase());
14356
+ const part = `part-${nextId()}`;
14357
+ return `events/${y}/${m}/${part}.jsonl`;
14358
+ };
14359
+
14360
+ // src/storage/append-event.ts
14361
+ var appendEventLine = async (dataRoot, event, clock, opts) => {
14362
+ const rel = nextEventRelPath(clock.now(), {
14363
+ nextId: opts?.nextEventId
14364
+ });
14365
+ const abs = `${dataRoot.replace(/\/$/, "")}/${rel}`;
14366
+ await (0, import_promises5.mkdir)((0, import_node_path5.dirname)(abs), { recursive: true });
14367
+ await (0, import_promises5.appendFile)(abs, `${JSON.stringify(event)}
14368
+ `, "utf8");
14369
+ return rel;
14370
+ };
14371
+
14372
+ // src/storage/read-event-lines.ts
14373
+ var import_promises6 = require("node:fs/promises");
14374
+ var import_node_path6 = require("node:path");
14375
+ var listJsonlFiles = async (dir, root) => {
14376
+ const out = [];
14377
+ const entries = await (0, import_promises6.readdir)(dir, { withFileTypes: true });
14378
+ for (const e of entries) {
14379
+ const abs = (0, import_node_path6.join)(dir, e.name);
14380
+ if (e.isDirectory()) {
14381
+ out.push(...await listJsonlFiles(abs, root));
14382
+ } else if (e.isFile() && e.name.endsWith(".jsonl")) {
14383
+ out.push((0, import_node_path6.relative)(root, abs).split("\\").join("/"));
14384
+ }
13949
14385
  }
14386
+ return out;
13950
14387
  };
13951
- var replayEvents = (lines) => {
13952
- const proj = emptyProjection();
13953
- const events = [];
13954
- for (const line of lines) {
13955
- const trimmed = line.trim();
13956
- if (!trimmed) continue;
13957
- const json = JSON.parse(trimmed);
13958
- events.push(eventLineSchema.parse(json));
14388
+ var readAllEventLines = async (dataRoot) => {
14389
+ const eventsRoot = (0, import_node_path6.join)(dataRoot, "events");
14390
+ let files = [];
14391
+ try {
14392
+ files = await listJsonlFiles(eventsRoot, dataRoot);
14393
+ } catch {
14394
+ return [];
13959
14395
  }
13960
- events.sort((a, b) => {
13961
- const t = a.ts.localeCompare(b.ts);
13962
- if (t !== 0) return t;
13963
- return a.id.localeCompare(b.id);
13964
- });
13965
- for (const e of events) {
13966
- applyEvent(proj, e);
14396
+ files.sort((a, b) => a.localeCompare(b));
14397
+ const lines = [];
14398
+ for (const rel of files) {
14399
+ const content = await (0, import_promises6.readFile)((0, import_node_path6.join)(dataRoot, rel), "utf8");
14400
+ for (const line of content.split("\n")) {
14401
+ if (line.trim()) lines.push(line);
14402
+ }
13967
14403
  }
13968
- return proj;
14404
+ return lines;
13969
14405
  };
13970
14406
 
13971
14407
  // src/sync/collect-github-pr-activity-source-ids.ts
@@ -14319,6 +14755,19 @@ var runGithubInboundSync = async (params) => {
14319
14755
  const planningSource = inboundTicketPlanningPayloadFromFenceMeta(meta);
14320
14756
  const planningPayload = {};
14321
14757
  for (const [k, v] of Object.entries(planningSource)) {
14758
+ if (k === "dependsOn") {
14759
+ if (v === null) {
14760
+ if (!ticketDependsOnListsEqual(ticket.dependsOn, [])) {
14761
+ planningPayload["dependsOn"] = null;
14762
+ }
14763
+ } else if (Array.isArray(v)) {
14764
+ const parsed = parseTicketDependsOnFromPayloadValue(v);
14765
+ if (parsed !== void 0 && !ticketDependsOnListsEqual(ticket.dependsOn, parsed)) {
14766
+ planningPayload["dependsOn"] = parsed;
14767
+ }
14768
+ }
14769
+ continue;
14770
+ }
14322
14771
  const tk = k;
14323
14772
  const cur = ticket[tk];
14324
14773
  if (v === null) {
@@ -14512,14 +14961,15 @@ var parseCliWorkItemStatusList = (raws, deps) => {
14512
14961
  }
14513
14962
  return out;
14514
14963
  };
14515
- var buildTicketListQueryFromReadListOpts = (o, deps) => {
14964
+ var buildTicketListQueryFromReadListOpts = (projection, o, deps) => {
14516
14965
  const query = {};
14517
14966
  const statusTokens = normalizeCliStringList(o.status);
14518
14967
  if (statusTokens.length > 0) {
14519
14968
  query.statuses = parseCliWorkItemStatusList(statusTokens, deps);
14520
14969
  }
14521
14970
  if (o.epic !== void 0 && o.epic !== "") {
14522
- query.epicId = o.epic;
14971
+ const trimmed = o.epic.trim();
14972
+ query.epicId = resolveEpicId(projection, trimmed) ?? trimmed;
14523
14973
  }
14524
14974
  const createdAfterMs = parseCliOptionalIsoMillis(
14525
14975
  o.createdAfter,
@@ -14656,6 +15106,10 @@ var buildTicketListQueryFromReadListOpts = (o, deps) => {
14656
15106
  if (targetFinishBeforeMs !== void 0) {
14657
15107
  query.targetFinishBeforeMs = targetFinishBeforeMs;
14658
15108
  }
15109
+ if (o.dependsOn !== void 0 && o.dependsOn.trim() !== "") {
15110
+ const trimmed = o.dependsOn.trim();
15111
+ query.dependsOnIncludesId = resolveTicketId(projection, trimmed) ?? trimmed;
15112
+ }
14659
15113
  return Object.keys(query).length > 0 ? query : void 0;
14660
15114
  };
14661
15115
  var runCli = async (argv, deps = {
@@ -14731,19 +15185,19 @@ var runCli = async (argv, deps = {
14731
15185
  const g = readGlobals(this);
14732
15186
  const o = this.opts();
14733
15187
  await mutateDataBranch(g, deps, async (root, { actor }) => {
15188
+ const lines = await readAllEventLines(root);
15189
+ const proj = replayEvents(lines);
14734
15190
  const id = o.id ?? ulid();
14735
15191
  const status = parseCliWorkItemStatus(o.status, deps);
14736
- const evt = makeEvent(
14737
- "EpicCreated",
14738
- {
14739
- id,
14740
- title: o.title,
14741
- body: o.body,
14742
- ...status !== void 0 ? { status } : {}
14743
- },
14744
- deps.clock,
14745
- actor
14746
- );
15192
+ const payload = {
15193
+ id,
15194
+ number: nextEpicNumberForCreate(proj),
15195
+ title: o.title,
15196
+ body: o.body,
15197
+ ...status !== void 0 ? { status } : {}
15198
+ };
15199
+ assertCreatePayloadUsesExpectedHeadNumber(proj, "epic", payload);
15200
+ const evt = makeEvent("EpicCreated", payload, deps.clock, actor);
14747
15201
  await appendEventLine(root, evt, deps.clock);
14748
15202
  return evt.payload;
14749
15203
  });
@@ -14759,18 +15213,19 @@ var runCli = async (argv, deps = {
14759
15213
  await mutateDataBranch(g, deps, async (root, { actor }) => {
14760
15214
  const lines = await readAllEventLines(root);
14761
15215
  const proj = replayEvents(lines);
14762
- const cur = proj.epics.get(o.id);
15216
+ const epicId = resolveEpicId(proj, o.id) ?? o.id.trim();
15217
+ const cur = proj.epics.get(epicId);
14763
15218
  if (!cur || cur.deleted) {
14764
15219
  throw new Error(`Epic not found: ${o.id}`);
14765
15220
  }
14766
15221
  const status = parseCliWorkItemStatus(o.status, deps);
14767
- const draft = { id: o.id };
15222
+ const draft = { id: epicId };
14768
15223
  if (o.title !== void 0) draft["title"] = o.title;
14769
15224
  if (o.body !== void 0) draft["body"] = o.body;
14770
15225
  if (status !== void 0) draft["status"] = status;
14771
15226
  const payload = pruneEpicOrStoryUpdatePayloadAgainstRow(cur, draft);
14772
15227
  if (isNoOpUpdatePayload(payload)) {
14773
- return { id: o.id, noChanges: true };
15228
+ return { id: epicId, noChanges: true };
14774
15229
  }
14775
15230
  const evt = makeEvent("EpicUpdated", payload, deps.clock, actor);
14776
15231
  await appendEventLine(root, evt, deps.clock);
@@ -14781,9 +15236,12 @@ var runCli = async (argv, deps = {
14781
15236
  const g = readGlobals(this);
14782
15237
  const o = this.opts();
14783
15238
  await mutateDataBranch(g, deps, async (root, { actor }) => {
14784
- const evt = makeEvent("EpicDeleted", { id: o.id }, deps.clock, actor);
15239
+ const lines = await readAllEventLines(root);
15240
+ const proj = replayEvents(lines);
15241
+ const epicId = resolveEpicId(proj, o.id) ?? o.id.trim();
15242
+ const evt = makeEvent("EpicDeleted", { id: epicId }, deps.clock, actor);
14785
15243
  await appendEventLine(root, evt, deps.clock);
14786
- return { id: o.id, deleted: true };
15244
+ return { id: epicId, deleted: true };
14787
15245
  });
14788
15246
  });
14789
15247
  const story = program2.command("story");
@@ -14796,24 +15254,23 @@ var runCli = async (argv, deps = {
14796
15254
  await mutateDataBranch(g, deps, async (root, { actor }) => {
14797
15255
  const lines = await readAllEventLines(root);
14798
15256
  const proj = replayEvents(lines);
14799
- const epic2 = proj.epics.get(o.epic);
15257
+ const epicId = resolveEpicId(proj, o.epic) ?? o.epic.trim();
15258
+ const epic2 = proj.epics.get(epicId);
14800
15259
  if (!epic2 || epic2.deleted) {
14801
15260
  throw new Error(`Epic not found: ${o.epic}`);
14802
15261
  }
14803
15262
  const id = o.id ?? ulid();
14804
15263
  const status = parseCliWorkItemStatus(o.status, deps);
14805
- const evt = makeEvent(
14806
- "StoryCreated",
14807
- {
14808
- id,
14809
- epicId: o.epic,
14810
- title: o.title,
14811
- body: o.body,
14812
- ...status !== void 0 ? { status } : {}
14813
- },
14814
- deps.clock,
14815
- actor
14816
- );
15264
+ const payload = {
15265
+ id,
15266
+ number: nextStoryNumberForCreate(proj),
15267
+ epicId,
15268
+ title: o.title,
15269
+ body: o.body,
15270
+ ...status !== void 0 ? { status } : {}
15271
+ };
15272
+ assertCreatePayloadUsesExpectedHeadNumber(proj, "story", payload);
15273
+ const evt = makeEvent("StoryCreated", payload, deps.clock, actor);
14817
15274
  await appendEventLine(root, evt, deps.clock);
14818
15275
  return evt.payload;
14819
15276
  });
@@ -14832,18 +15289,19 @@ var runCli = async (argv, deps = {
14832
15289
  await mutateDataBranch(g, deps, async (root, { actor }) => {
14833
15290
  const lines = await readAllEventLines(root);
14834
15291
  const proj = replayEvents(lines);
14835
- const cur = proj.stories.get(o.id);
15292
+ const storyId = resolveStoryId(proj, o.id) ?? o.id.trim();
15293
+ const cur = proj.stories.get(storyId);
14836
15294
  if (!cur || cur.deleted) {
14837
15295
  throw new Error(`Story not found: ${o.id}`);
14838
15296
  }
14839
15297
  const status = parseCliWorkItemStatus(o.status, deps);
14840
- const draft = { id: o.id };
15298
+ const draft = { id: storyId };
14841
15299
  if (o.title !== void 0) draft["title"] = o.title;
14842
15300
  if (o.body !== void 0) draft["body"] = o.body;
14843
15301
  if (status !== void 0) draft["status"] = status;
14844
15302
  const payload = pruneEpicOrStoryUpdatePayloadAgainstRow(cur, draft);
14845
15303
  if (isNoOpUpdatePayload(payload)) {
14846
- return { id: o.id, noChanges: true };
15304
+ return { id: storyId, noChanges: true };
14847
15305
  }
14848
15306
  const evt = makeEvent("StoryUpdated", payload, deps.clock, actor);
14849
15307
  await appendEventLine(root, evt, deps.clock);
@@ -14854,9 +15312,17 @@ var runCli = async (argv, deps = {
14854
15312
  const g = readGlobals(this);
14855
15313
  const o = this.opts();
14856
15314
  await mutateDataBranch(g, deps, async (root, { actor }) => {
14857
- const evt = makeEvent("StoryDeleted", { id: o.id }, deps.clock, actor);
15315
+ const lines = await readAllEventLines(root);
15316
+ const proj = replayEvents(lines);
15317
+ const storyId = resolveStoryId(proj, o.id) ?? o.id.trim();
15318
+ const evt = makeEvent(
15319
+ "StoryDeleted",
15320
+ { id: storyId },
15321
+ deps.clock,
15322
+ actor
15323
+ );
14858
15324
  await appendEventLine(root, evt, deps.clock);
14859
- return { id: o.id, deleted: true };
15325
+ return { id: storyId, deleted: true };
14860
15326
  });
14861
15327
  });
14862
15328
  const ticket = program2.command("ticket");
@@ -14879,6 +15345,11 @@ var runCli = async (argv, deps = {
14879
15345
  "planning label (repeatable)",
14880
15346
  (value, previous) => [...previous, value],
14881
15347
  []
15348
+ ).option(
15349
+ "--depends-on <id>",
15350
+ "prerequisite ticket id (repeatable)",
15351
+ (value, previous) => [...previous, value],
15352
+ []
14882
15353
  ).option("--priority <p>", "low|medium|high|urgent").option("--size <s>", "xs|s|m|l|xl").option("--estimate <n>", "non-negative estimate (e.g. story points)").option("--start-at <iso>", "planned start work at (ISO-8601)").option("--target-finish-at <iso>", "planned target finish at (ISO-8601)").option("--ai-draft", "draft body via AI (explicit)", false).action(async function() {
14883
15354
  const g = readGlobals(this);
14884
15355
  const o = this.opts();
@@ -14941,6 +15412,7 @@ var runCli = async (argv, deps = {
14941
15412
  "--target-finish-at",
14942
15413
  deps
14943
15414
  );
15415
+ const dependsOnTokensCreate = normalizeCliStringList(o.dependsOn);
14944
15416
  const planningPayload = {
14945
15417
  ...labelsPayloadCreate,
14946
15418
  ...priorityParsed !== void 0 ? { priority: priorityParsed } : {},
@@ -14954,31 +15426,53 @@ var runCli = async (argv, deps = {
14954
15426
  const proj = replayEvents(lines);
14955
15427
  const storyRaw = o.story;
14956
15428
  const storyTrimmed = storyRaw !== void 0 && storyRaw !== "" ? storyRaw.trim() : void 0;
15429
+ const storyIdResolved = storyTrimmed !== void 0 ? resolveStoryId(proj, storyTrimmed) ?? storyTrimmed : void 0;
14957
15430
  if (storyTrimmed !== void 0) {
14958
- const storyRow = proj.stories.get(storyTrimmed);
15431
+ const storyRow = proj.stories.get(storyIdResolved ?? "");
14959
15432
  if (!storyRow || storyRow.deleted) {
14960
15433
  throw new Error(`Story not found: ${storyTrimmed}`);
14961
15434
  }
14962
15435
  }
14963
15436
  const id = o.id ?? ulid();
15437
+ const dependsOnNormCreate = resolveTicketDependsOnTokensToIds(
15438
+ proj,
15439
+ dependsOnTokensCreate
15440
+ );
14964
15441
  const status = parseCliWorkItemStatus(o.status, deps);
14965
15442
  const assigneeCreate = o.assignee !== void 0 ? { assignee: normalizeGithubLogin(o.assignee) } : {};
14966
- const storyPayload = storyTrimmed !== void 0 ? { storyId: storyTrimmed } : {};
15443
+ const storyPayload = storyIdResolved !== void 0 ? { storyId: storyIdResolved } : {};
14967
15444
  const branchTokens = normalizeCliStringList(o.branch);
14968
15445
  const branchesNorm = normalizeTicketBranchListFromStrings(branchTokens);
14969
15446
  const branchesPayload = branchesNorm.length > 0 ? { branches: branchesNorm } : {};
15447
+ const dependsOnErr = validateTicketDependsOnForWrite({
15448
+ projection: proj,
15449
+ fromTicketId: id,
15450
+ nextDependsOn: dependsOnNormCreate
15451
+ });
15452
+ if (dependsOnErr !== void 0) {
15453
+ throw new Error(dependsOnErr);
15454
+ }
15455
+ const dependsOnPayloadCreate = dependsOnNormCreate.length > 0 ? { dependsOn: dependsOnNormCreate } : {};
15456
+ const createPayload = {
15457
+ id,
15458
+ number: nextTicketNumberForCreate(proj),
15459
+ ...storyPayload,
15460
+ title: o.title,
15461
+ body,
15462
+ ...status !== void 0 ? { status } : {},
15463
+ ...assigneeCreate,
15464
+ ...branchesPayload,
15465
+ ...dependsOnPayloadCreate,
15466
+ ...planningPayload
15467
+ };
15468
+ assertCreatePayloadUsesExpectedHeadNumber(
15469
+ proj,
15470
+ "ticket",
15471
+ createPayload
15472
+ );
14970
15473
  const evt = makeEvent(
14971
15474
  "TicketCreated",
14972
- {
14973
- id,
14974
- ...storyPayload,
14975
- title: o.title,
14976
- body,
14977
- ...status !== void 0 ? { status } : {},
14978
- ...assigneeCreate,
14979
- ...branchesPayload,
14980
- ...planningPayload
14981
- },
15475
+ createPayload,
14982
15476
  deps.clock,
14983
15477
  actor
14984
15478
  );
@@ -15034,6 +15528,9 @@ var runCli = async (argv, deps = {
15034
15528
  ).option(
15035
15529
  "--branch <name>",
15036
15530
  "when listing (no --id): only tickets linked to this branch (normalized exact match)"
15531
+ ).option(
15532
+ "--depends-on <id>",
15533
+ "when listing (no --id): only tickets that list this ticket id in dependsOn"
15037
15534
  ).option(
15038
15535
  "--priority <p>",
15039
15536
  "when listing (no --id): OR-set of priorities (repeat flag); low|medium|high|urgent",
@@ -15113,7 +15610,17 @@ var runCli = async (argv, deps = {
15113
15610
  "--clear-labels",
15114
15611
  "remove all planning labels from the ticket",
15115
15612
  false
15116
- ).option("--priority <p>", "low|medium|high|urgent").option("--clear-priority", "remove priority", false).option("--size <s>", "xs|s|m|l|xl").option("--clear-size", "remove size", false).option("--estimate <n>", "non-negative estimate (e.g. story points)").option("--clear-estimate", "remove estimate", false).option("--start-at <iso>", "planned start work at (ISO-8601)").option("--clear-start-at", "remove start work date", false).option("--target-finish-at <iso>", "planned target finish at (ISO-8601)").option("--clear-target-finish-at", "remove target finish date", false).option("--ai-improve", "expand description via AI (explicit)", false).action(async function() {
15613
+ ).option(
15614
+ "--add-depends-on <id>",
15615
+ "add a prerequisite ticket id (repeatable)",
15616
+ (value, previous) => [...previous, value],
15617
+ []
15618
+ ).option(
15619
+ "--remove-depends-on <id>",
15620
+ "remove a prerequisite ticket id (repeatable)",
15621
+ (value, previous) => [...previous, value],
15622
+ []
15623
+ ).option("--clear-depends-on", "remove all ticket dependencies", false).option("--priority <p>", "low|medium|high|urgent").option("--clear-priority", "remove priority", false).option("--size <s>", "xs|s|m|l|xl").option("--clear-size", "remove size", false).option("--estimate <n>", "non-negative estimate (e.g. story points)").option("--clear-estimate", "remove estimate", false).option("--start-at <iso>", "planned start work at (ISO-8601)").option("--clear-start-at", "remove start work date", false).option("--target-finish-at <iso>", "planned target finish at (ISO-8601)").option("--clear-target-finish-at", "remove target finish date", false).option("--ai-improve", "expand description via AI (explicit)", false).action(async function() {
15117
15624
  const g = readGlobals(this);
15118
15625
  const o = this.opts();
15119
15626
  let body = o.body;
@@ -15165,6 +15672,14 @@ ${body}`
15165
15672
  );
15166
15673
  deps.exit(ExitCode.UserError);
15167
15674
  }
15675
+ const addDependsOnTokens = normalizeCliStringList(o.addDependsOn);
15676
+ const removeDependsOnTokens = normalizeCliStringList(o.removeDependsOn);
15677
+ if (o.clearDependsOn === true && (addDependsOnTokens.length > 0 || removeDependsOnTokens.length > 0)) {
15678
+ deps.error(
15679
+ "Cannot use --clear-depends-on with --add-depends-on or --remove-depends-on"
15680
+ );
15681
+ deps.exit(ExitCode.UserError);
15682
+ }
15168
15683
  const mutual = (clear, set, clearName, setName) => {
15169
15684
  if (clear === true && set !== void 0 && set !== "") {
15170
15685
  deps.error(`Cannot use ${clearName} and ${setName} together`);
@@ -15229,25 +15744,27 @@ ${body}`
15229
15744
  await mutateDataBranch(g, deps, async (root, { actor }) => {
15230
15745
  const lines = await readAllEventLines(root);
15231
15746
  const proj = replayEvents(lines);
15747
+ const ticketId = resolveTicketId(proj, o.id) ?? o.id.trim();
15748
+ const storyIdResolved = storyTrimmed !== void 0 ? resolveStoryId(proj, storyTrimmed) ?? storyTrimmed : void 0;
15232
15749
  if (storyTrimmed !== void 0) {
15233
- const storyRow = proj.stories.get(storyTrimmed);
15750
+ const storyRow = proj.stories.get(storyIdResolved ?? "");
15234
15751
  if (!storyRow || storyRow.deleted) {
15235
15752
  throw new Error(`Story not found: ${storyTrimmed}`);
15236
15753
  }
15237
15754
  }
15238
- const curTicket = proj.tickets.get(o.id);
15755
+ const curTicket = proj.tickets.get(ticketId);
15239
15756
  if (curTicket === void 0 || curTicket.deleted) {
15240
15757
  throw new Error(`Ticket not found: ${o.id}`);
15241
15758
  }
15242
15759
  const status = parseCliWorkItemStatus(o.status, deps);
15243
- const payload = { id: o.id };
15760
+ const payload = { id: ticketId };
15244
15761
  if (o.title !== void 0) payload["title"] = o.title;
15245
15762
  if (body !== void 0) payload["body"] = body;
15246
15763
  if (status !== void 0) payload["status"] = status;
15247
15764
  if (o.unlinkStory) {
15248
15765
  payload["storyId"] = null;
15249
15766
  } else if (storyTrimmed !== void 0) {
15250
- payload["storyId"] = storyTrimmed;
15767
+ payload["storyId"] = storyIdResolved;
15251
15768
  }
15252
15769
  if (o.unassign) {
15253
15770
  payload["assignee"] = null;
@@ -15297,6 +15814,35 @@ ${body}`
15297
15814
  payload["labels"] = nextLabels;
15298
15815
  }
15299
15816
  }
15817
+ const wantsDependsOnChange = o.clearDependsOn === true || addDependsOnTokens.length > 0 || removeDependsOnTokens.length > 0;
15818
+ if (wantsDependsOnChange) {
15819
+ let nextDepends;
15820
+ if (o.clearDependsOn === true) {
15821
+ nextDepends = [];
15822
+ } else {
15823
+ const removeDepSet = new Set(
15824
+ resolveTicketDependsOnTokensToIds(proj, removeDependsOnTokens)
15825
+ );
15826
+ nextDepends = normalizeTicketDependsOnIds(
15827
+ (curTicket.dependsOn ?? []).filter((d) => !removeDepSet.has(d))
15828
+ );
15829
+ nextDepends = normalizeTicketDependsOnIds([
15830
+ ...nextDepends,
15831
+ ...resolveTicketDependsOnTokensToIds(proj, addDependsOnTokens)
15832
+ ]);
15833
+ }
15834
+ const depErr = validateTicketDependsOnForWrite({
15835
+ projection: proj,
15836
+ fromTicketId: ticketId,
15837
+ nextDependsOn: nextDepends
15838
+ });
15839
+ if (depErr !== void 0) {
15840
+ throw new Error(depErr);
15841
+ }
15842
+ if (!ticketDependsOnListsEqual(curTicket.dependsOn, nextDepends)) {
15843
+ payload["dependsOn"] = nextDepends;
15844
+ }
15845
+ }
15300
15846
  if (priorityUpdate !== void 0) {
15301
15847
  payload["priority"] = priorityUpdate;
15302
15848
  }
@@ -15317,7 +15863,7 @@ ${body}`
15317
15863
  payload
15318
15864
  );
15319
15865
  if (isNoOpUpdatePayload(prunedPayload)) {
15320
- return { id: o.id, noChanges: true };
15866
+ return { id: ticketId, noChanges: true };
15321
15867
  }
15322
15868
  const evt = makeEvent(
15323
15869
  "TicketUpdated",
@@ -15354,25 +15900,25 @@ ${body}`
15354
15900
  runGit
15355
15901
  );
15356
15902
  }
15357
- const preferred = normalizeTicketBranchName(
15358
- o.branch ?? `hyper-pm/${o.id}`
15359
- );
15360
- if (preferred === void 0) {
15361
- deps.error(
15362
- "Invalid --branch or default branch name for this ticket id"
15363
- );
15364
- deps.exit(ExitCode.UserError);
15365
- }
15366
15903
  await mutateDataBranch(g, deps, async (root, { actor }) => {
15367
15904
  const lines = await readAllEventLines(root);
15368
15905
  const proj = replayEvents(lines);
15369
- const curRow = proj.tickets.get(o.id);
15906
+ const ticketId = resolveTicketId(proj, o.id) ?? o.id.trim();
15907
+ const preferred = normalizeTicketBranchName(
15908
+ o.branch ?? `hyper-pm/${ticketId}`
15909
+ );
15910
+ if (preferred === void 0) {
15911
+ throw new Error(
15912
+ "Invalid --branch or default branch name for this ticket id"
15913
+ );
15914
+ }
15915
+ const curRow = proj.tickets.get(ticketId);
15370
15916
  if (curRow === void 0 || curRow.deleted) {
15371
15917
  throw new Error(`Ticket not found: ${o.id}`);
15372
15918
  }
15373
15919
  if (curRow.status === "done" || curRow.status === "cancelled") {
15374
15920
  throw new Error(
15375
- `Ticket ${o.id} is ${curRow.status}; change status before starting work`
15921
+ `Ticket ${ticketId} is ${curRow.status}; change status before starting work`
15376
15922
  );
15377
15923
  }
15378
15924
  const { branch: chosenBranch } = await pickUniqueLocalBranchName({
@@ -15393,7 +15939,7 @@ ${body}`
15393
15939
  }
15394
15940
  next = normalizeTicketBranchListFromStrings(next);
15395
15941
  const payload = {
15396
- id: o.id,
15942
+ id: ticketId,
15397
15943
  status: "in_progress"
15398
15944
  };
15399
15945
  if (!ticketBranchListsEqual(next, curRow.linkedBranches)) {
@@ -15402,7 +15948,7 @@ ${body}`
15402
15948
  const evt = makeEvent("TicketUpdated", payload, deps.clock, actor);
15403
15949
  await appendEventLine(root, evt, deps.clock);
15404
15950
  const result = {
15405
- id: o.id,
15951
+ id: ticketId,
15406
15952
  status: "in_progress",
15407
15953
  branch: chosenBranch,
15408
15954
  branches: next
@@ -15424,27 +15970,36 @@ ${body}`
15424
15970
  await mutateDataBranch(g, deps, async (root, { actor }) => {
15425
15971
  const lines = await readAllEventLines(root);
15426
15972
  const proj = replayEvents(lines);
15427
- const row = proj.tickets.get(o.id);
15973
+ const ticketId = resolveTicketId(proj, o.id) ?? o.id.trim();
15974
+ const row = proj.tickets.get(ticketId);
15428
15975
  if (!row || row.deleted) {
15429
15976
  throw new Error(`Ticket not found: ${o.id}`);
15430
15977
  }
15431
15978
  const evt = makeEvent(
15432
15979
  "TicketCommentAdded",
15433
- { ticketId: o.id, body: trimmed },
15980
+ { ticketId, body: trimmed },
15434
15981
  deps.clock,
15435
15982
  actor
15436
15983
  );
15437
15984
  await appendEventLine(root, evt, deps.clock);
15438
- return { commentId: evt.id, ticketId: o.id, body: trimmed };
15985
+ return { commentId: evt.id, ticketId, body: trimmed };
15439
15986
  });
15440
15987
  });
15441
15988
  ticket.command("delete").requiredOption("--id <id>").action(async function() {
15442
15989
  const g = readGlobals(this);
15443
15990
  const o = this.opts();
15444
15991
  await mutateDataBranch(g, deps, async (root, { actor }) => {
15445
- const evt = makeEvent("TicketDeleted", { id: o.id }, deps.clock, actor);
15992
+ const lines = await readAllEventLines(root);
15993
+ const proj = replayEvents(lines);
15994
+ const ticketId = resolveTicketId(proj, o.id) ?? o.id.trim();
15995
+ const evt = makeEvent(
15996
+ "TicketDeleted",
15997
+ { id: ticketId },
15998
+ deps.clock,
15999
+ actor
16000
+ );
15446
16001
  await appendEventLine(root, evt, deps.clock);
15447
- return { id: o.id, deleted: true };
16002
+ return { id: ticketId, deleted: true };
15448
16003
  });
15449
16004
  });
15450
16005
  ticket.command("import-github").description(
@@ -15515,7 +16070,8 @@ ${body}`
15515
16070
  const storyRaw = o.story;
15516
16071
  const storyTrimmed = storyRaw !== void 0 && storyRaw !== "" ? storyRaw.trim() : void 0;
15517
16072
  if (storyTrimmed !== void 0 && storyTrimmed !== "") {
15518
- const storyRow = proj.stories.get(storyTrimmed);
16073
+ const storyIdForImport = resolveStoryId(proj, storyTrimmed) ?? storyTrimmed;
16074
+ const storyRow = proj.stories.get(storyIdForImport);
15519
16075
  if (!storyRow || storyRow.deleted) {
15520
16076
  deps.error(`Story not found: ${storyTrimmed}`);
15521
16077
  deps.exit(ExitCode.UserError);
@@ -15558,12 +16114,22 @@ ${body}`
15558
16114
  );
15559
16115
  } else {
15560
16116
  const imported = [];
16117
+ let importProj = proj;
16118
+ let importLines = lines;
16119
+ const storyIdForPayload = storyTrimmed !== void 0 && storyTrimmed !== "" ? resolveStoryId(importProj, storyTrimmed) ?? storyTrimmed : void 0;
15561
16120
  for (const c of candidates) {
15562
16121
  const ticketId = ulid();
16122
+ const nextNum = nextTicketNumberForCreate(importProj);
15563
16123
  const createPayload = mergeTicketImportCreatePayload(
15564
16124
  ticketId,
15565
16125
  c.ticketCreatedPayloadBase,
15566
- storyTrimmed
16126
+ storyIdForPayload,
16127
+ nextNum
16128
+ );
16129
+ assertCreatePayloadUsesExpectedHeadNumber(
16130
+ importProj,
16131
+ "ticket",
16132
+ createPayload
15567
16133
  );
15568
16134
  const createdEvt = makeEvent(
15569
16135
  "TicketCreated",
@@ -15584,6 +16150,8 @@ ${body}`
15584
16150
  );
15585
16151
  await appendEventLine(session.worktreePath, linkEvt, deps.clock);
15586
16152
  imported.push({ ticketId, issueNumber: c.issueNumber });
16153
+ importLines = await readAllEventLines(session.worktreePath);
16154
+ importProj = replayEvents(importLines);
15587
16155
  }
15588
16156
  await commitDataWorktreeIfNeeded(
15589
16157
  session.worktreePath,
@@ -15610,7 +16178,19 @@ ${body}`
15610
16178
  }
15611
16179
  deps.exit(ExitCode.Success);
15612
16180
  });
15613
- program2.command("sync").description("GitHub Issues sync").option("--no-github", "skip network sync", false).option(
16181
+ program2.command("sync").description("Sync data branch over git; optional GitHub Issues sync").option(
16182
+ "--skip-network",
16183
+ "skip all sync network operations (git fetch/merge/push and GitHub); legacy: --no-github",
16184
+ false
16185
+ ).option(
16186
+ "--with-github",
16187
+ "also run GitHub Issues sync (requires GITHUB_TOKEN or gh; needs sync not off in config)",
16188
+ false
16189
+ ).option(
16190
+ "--git-data",
16191
+ "legacy no-op: default sync already updates the data branch via git only",
16192
+ false
16193
+ ).option(
15614
16194
  "--skip-push",
15615
16195
  "after a successful sync, do not push the data branch to the remote",
15616
16196
  false
@@ -15619,115 +16199,171 @@ ${body}`
15619
16199
  const o = this.opts();
15620
16200
  const repoRoot = await resolveRepoRoot(g.repo);
15621
16201
  const cfg = await loadMergedConfig(repoRoot, g);
15622
- if (cfg.sync === "off" || o.noGithub) {
16202
+ if (o.withGithub && o.skipNetwork) {
16203
+ deps.error("Cannot use --with-github together with --skip-network");
16204
+ deps.exit(ExitCode.UserError);
16205
+ }
16206
+ if (o.skipNetwork) {
15623
16207
  deps.log(formatOutput(g.format, { ok: true, skipped: true }));
15624
16208
  deps.exit(ExitCode.Success);
15625
16209
  }
15626
- const githubToken = await resolveGithubTokenForSync({
15627
- envToken: env.GITHUB_TOKEN,
15628
- cwd: repoRoot
15629
- });
15630
- if (!githubToken) {
15631
- deps.error(
15632
- "GitHub auth required for sync: set GITHUB_TOKEN or run `gh auth login`"
15633
- );
15634
- deps.exit(ExitCode.EnvironmentAuth);
15635
- }
15636
- const tmpBase = g.tempDir ?? env.TMPDIR ?? (0, import_node_os2.tmpdir)();
15637
- const session = await openDataBranchWorktree({
15638
- repoRoot,
15639
- dataBranch: cfg.dataBranch,
15640
- tmpBase,
15641
- keepWorktree: g.keepWorktree,
15642
- runGit
15643
- });
15644
- try {
15645
- const projection = await loadProjectionFromDataRoot(
15646
- session.worktreePath
15647
- );
15648
- const gitDerivedSlug = await tryReadGithubOwnerRepoSlugFromGit({
16210
+ if (o.withGithub) {
16211
+ if (cfg.sync === "off") {
16212
+ deps.error(
16213
+ "GitHub Issues sync is off in config (sync: off). Omit --with-github to sync the data branch via git only, or set sync to outbound/full."
16214
+ );
16215
+ deps.exit(ExitCode.UserError);
16216
+ }
16217
+ const githubToken = await resolveGithubTokenForSync({
16218
+ envToken: env.GITHUB_TOKEN,
16219
+ cwd: repoRoot
16220
+ });
16221
+ if (!githubToken) {
16222
+ deps.error(
16223
+ "GitHub auth required for sync --with-github: set GITHUB_TOKEN or run `gh auth login`"
16224
+ );
16225
+ deps.exit(ExitCode.EnvironmentAuth);
16226
+ }
16227
+ const tmpBase = g.tempDir ?? env.TMPDIR ?? (0, import_node_os2.tmpdir)();
16228
+ const session = await openDataBranchWorktree({
15649
16229
  repoRoot,
15650
- remote: cfg.remote,
16230
+ dataBranch: cfg.dataBranch,
16231
+ tmpBase,
16232
+ keepWorktree: g.keepWorktree,
15651
16233
  runGit
15652
16234
  });
15653
- const { owner, repo: repo2 } = resolveGithubRepo(
15654
- cfg,
15655
- env.GITHUB_REPO,
15656
- gitDerivedSlug
15657
- );
15658
- const octokit = new Octokit2({ auth: githubToken });
15659
- const outboundActor = await resolveGithubTokenActor(octokit);
15660
- const depsGh = {
15661
- octokit,
15662
- owner,
15663
- repo: repo2,
15664
- clock: deps.clock,
15665
- outboundActor
15666
- };
15667
- await runGithubOutboundSync({
15668
- dataRoot: session.worktreePath,
15669
- projection,
15670
- config: cfg,
15671
- deps: depsGh
15672
- });
15673
- await runGithubInboundSync({
15674
- dataRoot: session.worktreePath,
15675
- projection,
15676
- config: cfg,
15677
- deps: depsGh
15678
- });
15679
- const projectionAfterInbound = await loadProjectionFromDataRoot(
15680
- session.worktreePath
15681
- );
15682
- await runGithubPrActivitySync({
15683
- projection: projectionAfterInbound,
15684
- config: cfg,
15685
- deps: defaultGithubPrActivitySyncDeps({
15686
- dataRoot: session.worktreePath,
15687
- clock: deps.clock,
16235
+ try {
16236
+ const projection = await loadProjectionFromDataRoot(
16237
+ session.worktreePath
16238
+ );
16239
+ const gitDerivedSlug = await tryReadGithubOwnerRepoSlugFromGit({
16240
+ repoRoot,
16241
+ remote: cfg.remote,
16242
+ runGit
16243
+ });
16244
+ const { owner, repo: repo2 } = resolveGithubRepo(
16245
+ cfg,
16246
+ env.GITHUB_REPO,
16247
+ gitDerivedSlug
16248
+ );
16249
+ const octokit = new Octokit2({ auth: githubToken });
16250
+ const outboundActor = await resolveGithubTokenActor(octokit);
16251
+ const depsGh = {
15688
16252
  octokit,
15689
16253
  owner,
15690
16254
  repo: repo2,
15691
- actor: outboundActor
15692
- })
15693
- });
15694
- await commitDataWorktreeIfNeeded(
15695
- session.worktreePath,
15696
- formatDataBranchCommitMessage("hyper-pm: sync", outboundActor),
15697
- runGit
15698
- );
15699
- let dataBranchPush;
15700
- let dataBranchPushDetail;
15701
- if (o.skipPush) {
15702
- dataBranchPush = "skipped_cli";
15703
- dataBranchPushDetail = "skip-push";
15704
- } else {
15705
- const pushResult = await tryPushDataBranchToRemote(
16255
+ clock: deps.clock,
16256
+ outboundActor
16257
+ };
16258
+ await runGithubOutboundSync({
16259
+ dataRoot: session.worktreePath,
16260
+ projection,
16261
+ config: cfg,
16262
+ deps: depsGh
16263
+ });
16264
+ await runGithubInboundSync({
16265
+ dataRoot: session.worktreePath,
16266
+ projection,
16267
+ config: cfg,
16268
+ deps: depsGh
16269
+ });
16270
+ const projectionAfterInbound = await loadProjectionFromDataRoot(
16271
+ session.worktreePath
16272
+ );
16273
+ await runGithubPrActivitySync({
16274
+ projection: projectionAfterInbound,
16275
+ config: cfg,
16276
+ deps: defaultGithubPrActivitySyncDeps({
16277
+ dataRoot: session.worktreePath,
16278
+ clock: deps.clock,
16279
+ octokit,
16280
+ owner,
16281
+ repo: repo2,
16282
+ actor: outboundActor
16283
+ })
16284
+ });
16285
+ await commitDataWorktreeIfNeeded(
15706
16286
  session.worktreePath,
15707
- cfg.remote,
15708
- cfg.dataBranch,
16287
+ formatDataBranchCommitMessage("hyper-pm: sync", outboundActor),
15709
16288
  runGit
15710
16289
  );
15711
- dataBranchPush = pushResult.status;
15712
- dataBranchPushDetail = pushResult.detail;
15713
- if (pushResult.status === "failed" && pushResult.detail) {
15714
- deps.error(
15715
- `hyper-pm: data branch not pushed (${cfg.remote}/${cfg.dataBranch}): ${pushResult.detail}`
16290
+ let dataBranchPush;
16291
+ let dataBranchPushDetail;
16292
+ if (o.skipPush) {
16293
+ dataBranchPush = "skipped_cli";
16294
+ dataBranchPushDetail = "skip-push";
16295
+ } else {
16296
+ const pushResult = await tryPushDataBranchToRemote(
16297
+ session.worktreePath,
16298
+ cfg.remote,
16299
+ cfg.dataBranch,
16300
+ runGit
15716
16301
  );
16302
+ dataBranchPush = pushResult.status;
16303
+ dataBranchPushDetail = pushResult.detail;
16304
+ if (pushResult.status === "failed" && pushResult.detail) {
16305
+ deps.error(
16306
+ `hyper-pm: data branch not pushed (${cfg.remote}/${cfg.dataBranch}): ${pushResult.detail}`
16307
+ );
16308
+ }
15717
16309
  }
16310
+ deps.log(
16311
+ formatOutput(g.format, {
16312
+ ok: true,
16313
+ githubSync: true,
16314
+ dataBranchPush,
16315
+ ...dataBranchPushDetail !== void 0 ? { dataBranchPushDetail } : {}
16316
+ })
16317
+ );
16318
+ } catch (e) {
16319
+ deps.error(e instanceof Error ? e.message : String(e));
16320
+ deps.exit(ExitCode.ExternalApi);
16321
+ } finally {
16322
+ await session.dispose();
16323
+ }
16324
+ deps.exit(ExitCode.Success);
16325
+ }
16326
+ const tmpBaseGit = g.tempDir ?? env.TMPDIR ?? (0, import_node_os2.tmpdir)();
16327
+ const sessionGit = await openDataBranchWorktree({
16328
+ repoRoot,
16329
+ dataBranch: cfg.dataBranch,
16330
+ tmpBase: tmpBaseGit,
16331
+ keepWorktree: g.keepWorktree,
16332
+ runGit
16333
+ });
16334
+ try {
16335
+ let syncResult;
16336
+ try {
16337
+ syncResult = await runRemoteDataBranchGitSync(
16338
+ sessionGit.worktreePath,
16339
+ cfg.remote,
16340
+ cfg.dataBranch,
16341
+ runGit,
16342
+ Boolean(o.skipPush)
16343
+ );
16344
+ } catch (e) {
16345
+ if (e instanceof SyncRemoteDataBranchMergeError) {
16346
+ deps.error(e.message);
16347
+ deps.exit(ExitCode.UserError);
16348
+ }
16349
+ deps.error(e instanceof Error ? e.message : String(e));
16350
+ deps.exit(ExitCode.UserError);
16351
+ }
16352
+ if (syncResult.dataBranchPush === "failed" && syncResult.dataBranchPushDetail !== void 0) {
16353
+ deps.error(
16354
+ `hyper-pm: data branch not pushed (${cfg.remote}/${cfg.dataBranch}): ${syncResult.dataBranchPushDetail}`
16355
+ );
15718
16356
  }
15719
16357
  deps.log(
15720
16358
  formatOutput(g.format, {
15721
16359
  ok: true,
15722
- dataBranchPush,
15723
- ...dataBranchPushDetail !== void 0 ? { dataBranchPushDetail } : {}
16360
+ gitDataOnly: true,
16361
+ ...o.gitData ? { legacyGitDataFlag: true } : {},
16362
+ ...syncResult
15724
16363
  })
15725
16364
  );
15726
- } catch (e) {
15727
- deps.error(e instanceof Error ? e.message : String(e));
15728
- deps.exit(ExitCode.ExternalApi);
15729
16365
  } finally {
15730
- await session.dispose();
16366
+ await sessionGit.dispose();
15731
16367
  }
15732
16368
  deps.exit(ExitCode.Success);
15733
16369
  });
@@ -15893,7 +16529,7 @@ ${body}`
15893
16529
  await session.dispose();
15894
16530
  }
15895
16531
  });
15896
- await program2.parseAsync(argv, { from: "node" });
16532
+ await program2.parseAsync(normalizeRawCliArgv(argv), { from: "node" });
15897
16533
  };
15898
16534
  var makeEvent = (type, payload, clock, actor) => ({
15899
16535
  schema: 1,
@@ -15983,7 +16619,8 @@ var readEpic = async (g, id, deps) => {
15983
16619
  formatOutput(g.format, { items: listActiveEpicSummaries(proj) })
15984
16620
  );
15985
16621
  } else {
15986
- const row = proj.epics.get(id);
16622
+ const epicId = resolveEpicId(proj, id) ?? id.trim();
16623
+ const row = proj.epics.get(epicId);
15987
16624
  if (!row || row.deleted) {
15988
16625
  deps.error("Epic not found");
15989
16626
  exitCode = ExitCode.UserError;
@@ -16018,7 +16655,8 @@ var readStory = async (g, opts, deps) => {
16018
16655
  const proj = replayEvents(lines);
16019
16656
  const { id, epicId } = opts;
16020
16657
  if (id === void 0 || id === "") {
16021
- const epicFilter = epicId !== void 0 && epicId !== "" ? epicId : void 0;
16658
+ const epicFilterRaw = epicId !== void 0 && epicId !== "" ? epicId : void 0;
16659
+ const epicFilter = epicFilterRaw !== void 0 ? resolveEpicId(proj, epicFilterRaw) ?? epicFilterRaw.trim() : void 0;
16022
16660
  if (epicFilter !== void 0) {
16023
16661
  const epicRow = proj.epics.get(epicFilter);
16024
16662
  if (!epicRow || epicRow.deleted) {
@@ -16039,7 +16677,8 @@ var readStory = async (g, opts, deps) => {
16039
16677
  );
16040
16678
  }
16041
16679
  } else {
16042
- const row = proj.stories.get(id);
16680
+ const storyId = resolveStoryId(proj, id) ?? id.trim();
16681
+ const row = proj.stories.get(storyId);
16043
16682
  if (!row || row.deleted) {
16044
16683
  deps.error("Story not found");
16045
16684
  exitCode = ExitCode.UserError;
@@ -16083,8 +16722,8 @@ var readTicket = async (g, opts, deps) => {
16083
16722
  } = opts;
16084
16723
  if (id === void 0 || id === "") {
16085
16724
  const listWithoutStory = withoutStoryRaw === true;
16086
- const storyFilter = storyIdRaw !== void 0 && storyIdRaw !== "" ? storyIdRaw : void 0;
16087
- const epicFilter = epicIdRaw !== void 0 && epicIdRaw !== "" ? epicIdRaw : void 0;
16725
+ const storyFilter = storyIdRaw !== void 0 && storyIdRaw !== "" ? resolveStoryId(proj, storyIdRaw) ?? storyIdRaw.trim() : void 0;
16726
+ const epicFilter = epicIdRaw !== void 0 && epicIdRaw !== "" ? resolveEpicId(proj, epicIdRaw) ?? epicIdRaw.trim() : void 0;
16088
16727
  const sortBy = tryParseTicketListSortField(sortByOpt);
16089
16728
  const sortDir = tryParseTicketListSortDir(sortDirOpt);
16090
16729
  if (sortBy === void 0) {
@@ -16123,6 +16762,7 @@ var readTicket = async (g, opts, deps) => {
16123
16762
  exitCode = ExitCode.UserError;
16124
16763
  } else {
16125
16764
  const listQuery = buildTicketListQueryFromReadListOpts(
16765
+ proj,
16126
16766
  { epic: epicFilter, ...listFlagRest },
16127
16767
  deps
16128
16768
  );
@@ -16143,6 +16783,7 @@ var readTicket = async (g, opts, deps) => {
16143
16783
  exitCode = ExitCode.UserError;
16144
16784
  } else {
16145
16785
  const listQuery = buildTicketListQueryFromReadListOpts(
16786
+ proj,
16146
16787
  listFlagRest,
16147
16788
  deps
16148
16789
  );
@@ -16159,6 +16800,7 @@ var readTicket = async (g, opts, deps) => {
16159
16800
  }
16160
16801
  } else if (listWithoutStory) {
16161
16802
  const listQuery = buildTicketListQueryFromReadListOpts(
16803
+ proj,
16162
16804
  { withoutStory: true, ...listFlagRest },
16163
16805
  deps
16164
16806
  );
@@ -16173,6 +16815,7 @@ var readTicket = async (g, opts, deps) => {
16173
16815
  );
16174
16816
  } else {
16175
16817
  const listQuery = buildTicketListQueryFromReadListOpts(
16818
+ proj,
16176
16819
  { epic: epicFilter, ...listFlagRest },
16177
16820
  deps
16178
16821
  );
@@ -16187,7 +16830,8 @@ var readTicket = async (g, opts, deps) => {
16187
16830
  );
16188
16831
  }
16189
16832
  } else {
16190
- const row = proj.tickets.get(id);
16833
+ const ticketId = resolveTicketId(proj, id) ?? id.trim();
16834
+ const row = proj.tickets.get(ticketId);
16191
16835
  if (!row || row.deleted) {
16192
16836
  deps.error("Ticket not found");
16193
16837
  exitCode = ExitCode.UserError;