hyper-pm 0.1.5 → 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 +8 -0
  2. package/dist/index.cjs +1261 -1118
  3. package/dist/main.cjs +1261 -1118
  4. package/package.json +3 -3
package/dist/index.cjs CHANGED
@@ -12782,6 +12782,7 @@ var sortTicketRecordsForList = (tickets, field, dir) => [...tickets].sort((x, y)
12782
12782
  // src/cli/list-projection-summaries.ts
12783
12783
  var listActiveEpicSummaries = (projection) => [...projection.epics.values()].filter((e) => !e.deleted).map((e) => ({
12784
12784
  id: e.id,
12785
+ number: e.number,
12785
12786
  title: e.title,
12786
12787
  status: e.status,
12787
12788
  createdAt: e.createdAt,
@@ -12793,6 +12794,7 @@ var listActiveStorySummaries = (projection, options) => [...projection.stories.v
12793
12794
  (s) => !s.deleted && (options?.epicId === void 0 || s.epicId === options.epicId)
12794
12795
  ).map((s) => ({
12795
12796
  id: s.id,
12797
+ number: s.number,
12796
12798
  title: s.title,
12797
12799
  epicId: s.epicId,
12798
12800
  status: s.status,
@@ -12825,6 +12827,7 @@ var listActiveTicketSummaries = (projection, options) => {
12825
12827
  const last = recent !== void 0 && recent.length > 0 ? recent[recent.length - 1] : void 0;
12826
12828
  return {
12827
12829
  id: t.id,
12830
+ number: t.number,
12828
12831
  title: t.title,
12829
12832
  status: t.status,
12830
12833
  storyId: t.storyId,
@@ -13193,1137 +13196,1222 @@ var partitionGithubIssuesForImport = (params) => {
13193
13196
  }
13194
13197
  return { candidates, skipped };
13195
13198
  };
13196
- var mergeTicketImportCreatePayload = (ticketId, base, storyId) => {
13199
+ var mergeTicketImportCreatePayload = (ticketId, base, storyId, number) => {
13197
13200
  const storyTrimmed = storyId !== void 0 && storyId !== "" ? storyId.trim() : void 0;
13198
13201
  const storyPayload = storyTrimmed !== void 0 && storyTrimmed !== "" ? { storyId: storyTrimmed } : {};
13199
- return { id: ticketId, ...base, ...storyPayload };
13202
+ return { id: ticketId, number, ...base, ...storyPayload };
13200
13203
  };
13201
13204
 
13202
- // src/config/hyper-pm-config.ts
13203
- var hyperPmConfigSchema = external_exports.object({
13204
- schema: external_exports.literal(1),
13205
- dataBranch: external_exports.string().min(1),
13206
- remote: external_exports.string().min(1).default("origin"),
13207
- sync: external_exports.enum(["off", "outbound", "full"]).default("outbound"),
13208
- githubRepo: external_exports.string().optional(),
13209
- issueMapping: external_exports.enum(["ticket", "story", "epic"]).default("ticket")
13210
- });
13211
-
13212
- // src/config/load-config.ts
13213
- var import_promises = require("node:fs/promises");
13214
- var import_node_path = require("node:path");
13215
- var CONFIG_DIR = ".hyper-pm";
13216
- var CONFIG_FILE = "config.json";
13217
- var getHyperPmConfigPath = (repoRoot) => {
13218
- return (0, import_node_path.join)(repoRoot, CONFIG_DIR, CONFIG_FILE);
13219
- };
13220
- var loadHyperPmConfig = async (repoRoot, overrides = {}) => {
13221
- const raw = await (0, import_promises.readFile)(getHyperPmConfigPath(repoRoot), "utf8");
13222
- const parsed = JSON.parse(raw);
13223
- const base = hyperPmConfigSchema.parse(parsed);
13224
- const merged = { ...base, ...stripUndefined(overrides) };
13225
- return hyperPmConfigSchema.parse(merged);
13226
- };
13227
- var stripUndefined = (obj) => {
13228
- const out = {};
13229
- for (const [k, v] of Object.entries(obj)) {
13230
- if (v !== void 0) out[k] = v;
13205
+ // src/lib/github-pr-activity.ts
13206
+ var GITHUB_PR_ACTIVITY_RECENT_CAP = 20;
13207
+ var githubPrActivityKindSchema = external_exports.enum([
13208
+ "opened",
13209
+ "updated",
13210
+ "commented",
13211
+ "reviewed",
13212
+ "merged",
13213
+ "closed",
13214
+ "ready_for_review"
13215
+ ]);
13216
+ var githubPrReviewStateSchema = external_exports.enum([
13217
+ "approved",
13218
+ "changes_requested",
13219
+ "commented"
13220
+ ]);
13221
+ var buildPrOpenSourceId = (ticketId, prNumber) => `hyper-pm:pr-open:${ticketId}:${prNumber}`;
13222
+ var parseGithubPrActivityPayload = (payload) => {
13223
+ const ticketId = payload["ticketId"];
13224
+ const prRaw = payload["prNumber"];
13225
+ const kindRaw = payload["kind"];
13226
+ const occurredAt = payload["occurredAt"];
13227
+ const sourceId = payload["sourceId"];
13228
+ if (typeof ticketId !== "string" || typeof occurredAt !== "string" || typeof sourceId !== "string") {
13229
+ return void 0;
13230
+ }
13231
+ const prNumber = typeof prRaw === "number" ? prRaw : Number(prRaw);
13232
+ if (!Number.isFinite(prNumber)) return void 0;
13233
+ const kindParsed = githubPrActivityKindSchema.safeParse(kindRaw);
13234
+ if (!kindParsed.success) return void 0;
13235
+ const out = {
13236
+ prNumber,
13237
+ kind: kindParsed.data,
13238
+ occurredAt,
13239
+ sourceId
13240
+ };
13241
+ const url = payload["url"];
13242
+ if (typeof url === "string" && url.length > 0) {
13243
+ out.url = url;
13244
+ }
13245
+ const rs = githubPrReviewStateSchema.safeParse(payload["reviewState"]);
13246
+ if (rs.success) {
13247
+ out.reviewState = rs.data;
13231
13248
  }
13232
13249
  return out;
13233
13250
  };
13234
13251
 
13235
- // src/config/save-config.ts
13236
- var import_promises2 = require("node:fs/promises");
13237
- var import_node_path2 = require("node:path");
13238
- var saveHyperPmConfig = async (repoRoot, config) => {
13239
- const target = getHyperPmConfigPath(repoRoot);
13240
- await (0, import_promises2.mkdir)((0, import_node_path2.dirname)(target), { recursive: true });
13241
- await (0, import_promises2.writeFile)(target, `${JSON.stringify(config, null, 2)}
13242
- `, "utf8");
13252
+ // src/storage/projection.ts
13253
+ var readOptionalPositiveIntegerFromPayload = (payload, key) => {
13254
+ if (!Object.prototype.hasOwnProperty.call(payload, key)) return void 0;
13255
+ const raw = payload[key];
13256
+ if (typeof raw !== "number" || !Number.isFinite(raw)) return void 0;
13257
+ if (!Number.isInteger(raw)) return void 0;
13258
+ if (raw < 1 || raw > Number.MAX_SAFE_INTEGER) return void 0;
13259
+ return raw;
13260
+ };
13261
+ var maxNumberAmongRows = (rows) => {
13262
+ let m = 0;
13263
+ for (const r of rows) {
13264
+ if (r.number > m) m = r.number;
13265
+ }
13266
+ return m;
13267
+ };
13268
+ var maxEpicNumberInProjection = (projection) => maxNumberAmongRows(projection.epics.values());
13269
+ var maxStoryNumberInProjection = (projection) => maxNumberAmongRows(projection.stories.values());
13270
+ var maxTicketNumberInProjection = (projection) => maxNumberAmongRows(projection.tickets.values());
13271
+ var nextEpicNumberForCreate = (projection) => maxEpicNumberInProjection(projection) + 1;
13272
+ var nextStoryNumberForCreate = (projection) => maxStoryNumberInProjection(projection) + 1;
13273
+ var nextTicketNumberForCreate = (projection) => maxTicketNumberInProjection(projection) + 1;
13274
+ var resolveWorkItemCreateNumber = (projection, kind, payload) => {
13275
+ const explicit = readOptionalPositiveIntegerFromPayload(payload, "number");
13276
+ if (explicit !== void 0) {
13277
+ return explicit;
13278
+ }
13279
+ return (kind === "epic" ? maxEpicNumberInProjection(projection) : kind === "story" ? maxStoryNumberInProjection(projection) : maxTicketNumberInProjection(projection)) + 1;
13243
13280
  };
13244
-
13245
- // src/doctor/run-doctor.ts
13246
- var runDoctorOnLines = (lines) => {
13247
- const issues = [];
13248
- for (let i = 0; i < lines.length; i++) {
13249
- const line = lines[i]?.trim() ?? "";
13250
- if (!line) continue;
13251
- let json;
13252
- try {
13253
- json = JSON.parse(line);
13254
- } catch (e) {
13255
- issues.push({
13256
- kind: "invalid-json",
13257
- line: i + 1,
13258
- message: e instanceof Error ? e.message : "parse error"
13259
- });
13260
- return issues;
13261
- }
13262
- const parsed = eventLineSchema.safeParse(json);
13263
- if (!parsed.success) {
13264
- issues.push({
13265
- kind: "invalid-event",
13266
- line: i + 1,
13267
- message: parsed.error.message
13268
- });
13269
- return issues;
13270
- }
13281
+ var emptyProjection = () => ({
13282
+ epics: /* @__PURE__ */ new Map(),
13283
+ stories: /* @__PURE__ */ new Map(),
13284
+ tickets: /* @__PURE__ */ new Map()
13285
+ });
13286
+ var parsePrRefs = (body) => {
13287
+ const out = /* @__PURE__ */ new Set();
13288
+ const re = /\b(?:Closes|Refs|Fixes)\s+#(\d+)\b/gi;
13289
+ let m = re.exec(body);
13290
+ while (m !== null) {
13291
+ out.add(Number(m[1]));
13292
+ m = re.exec(body);
13271
13293
  }
13272
- return issues;
13273
- };
13274
-
13275
- // src/git/create-and-checkout-branch.ts
13276
- var createAndCheckoutBranch = async (opts) => {
13277
- await opts.runGit(opts.repoRoot, [
13278
- "switch",
13279
- "-c",
13280
- opts.branchName,
13281
- opts.startPoint
13282
- ]);
13283
- };
13284
-
13285
- // src/git/data-worktree-session.ts
13286
- var import_promises3 = require("node:fs/promises");
13287
- var import_node_path3 = require("node:path");
13288
- var ensureDir = async (path) => {
13289
- await (0, import_promises3.mkdir)(path, { recursive: true });
13294
+ return [...out];
13290
13295
  };
13291
- var defaultPathExists = async (path) => {
13292
- try {
13293
- await (0, import_promises3.access)(path);
13294
- return true;
13295
- } catch {
13296
- return false;
13296
+ var applyTicketAssigneeFromPayload = (row, payload) => {
13297
+ if (!Object.prototype.hasOwnProperty.call(payload, "assignee")) return;
13298
+ const v = payload["assignee"];
13299
+ if (v === null) {
13300
+ delete row.assignee;
13301
+ return;
13297
13302
  }
13303
+ if (typeof v !== "string") return;
13304
+ const n = normalizeGithubLogin(v);
13305
+ if (n === "") delete row.assignee;
13306
+ else row.assignee = n;
13298
13307
  };
13299
- var parseDataBranchWorktreeFromPorcelain = (stdout, dataBranch) => {
13300
- const wantRef = `refs/heads/${dataBranch}`;
13301
- const blocks = stdout.split(/\n\n+/).map((b) => b.trim()).filter(Boolean);
13302
- for (const block of blocks) {
13303
- const lines = block.split("\n");
13304
- let worktreePath;
13305
- let branch;
13306
- for (const line of lines) {
13307
- if (line.startsWith("worktree ")) {
13308
- worktreePath = line.slice("worktree ".length);
13309
- } else if (line.startsWith("branch ")) {
13310
- branch = line.slice("branch ".length);
13311
- }
13312
- }
13313
- if (worktreePath !== void 0 && branch === wantRef) {
13314
- return worktreePath;
13315
- }
13308
+ var storyIdFromTicketCreatedPayload = (payload) => {
13309
+ if (!Object.prototype.hasOwnProperty.call(payload, "storyId")) {
13310
+ return null;
13316
13311
  }
13317
- return void 0;
13312
+ const v = payload["storyId"];
13313
+ if (v === null) return null;
13314
+ if (typeof v !== "string") return null;
13315
+ const t = v.trim();
13316
+ return t === "" ? null : t;
13318
13317
  };
13319
- var openDataBranchWorktree = async (opts) => {
13320
- const pathExists = opts.pathExists ?? defaultPathExists;
13321
- const { stdout: listOut } = await opts.runGit(opts.repoRoot, [
13322
- "worktree",
13323
- "list",
13324
- "--porcelain"
13325
- ]);
13326
- const listedPath = parseDataBranchWorktreeFromPorcelain(
13327
- listOut,
13328
- opts.dataBranch
13329
- );
13330
- if (listedPath !== void 0 && await pathExists(listedPath)) {
13331
- const dispose2 = async () => {
13332
- return;
13333
- };
13334
- return { worktreePath: listedPath, dispose: dispose2 };
13318
+ var applyTicketStoryIdFromPayload = (row, payload) => {
13319
+ if (!Object.prototype.hasOwnProperty.call(payload, "storyId")) return;
13320
+ const v = payload["storyId"];
13321
+ if (v === null) {
13322
+ row.storyId = null;
13323
+ return;
13335
13324
  }
13336
- const worktreePath = (0, import_node_path3.join)(
13337
- opts.tmpBase,
13338
- `hyper-pm-worktree-${ulid().toLowerCase()}`
13339
- );
13340
- await ensureDir(opts.tmpBase);
13341
- try {
13342
- await opts.runGit(opts.repoRoot, [
13343
- "worktree",
13344
- "add",
13345
- worktreePath,
13346
- opts.dataBranch
13347
- ]);
13348
- } catch (err) {
13349
- await (0, import_promises3.rm)(worktreePath, { recursive: true, force: true }).catch(() => {
13350
- });
13351
- throw err;
13325
+ if (typeof v !== "string") return;
13326
+ const t = v.trim();
13327
+ row.storyId = t === "" ? null : t;
13328
+ };
13329
+ var linkedBranchesFromTicketCreatedPayload = (payload) => {
13330
+ if (!Object.prototype.hasOwnProperty.call(payload, "branches")) {
13331
+ return [];
13352
13332
  }
13353
- const dispose = async () => {
13354
- if (opts.keepWorktree) {
13355
- return;
13356
- }
13357
- await opts.runGit(opts.repoRoot, ["worktree", "remove", "--force", worktreePath]).catch(() => {
13358
- });
13359
- await (0, import_promises3.rm)(worktreePath, { recursive: true, force: true }).catch(() => {
13360
- });
13361
- };
13362
- return { worktreePath, dispose };
13333
+ return normalizeTicketBranchListFromPayloadValue(payload["branches"]);
13363
13334
  };
13364
-
13365
- // src/git/find-git-root.ts
13366
- var findGitRoot = async (cwd, deps) => {
13367
- const { stdout } = await deps.runGit(cwd, ["rev-parse", "--show-toplevel"]);
13368
- return stdout;
13335
+ var applyTicketBranchesFromUpdatePayload = (row, payload) => {
13336
+ if (!Object.prototype.hasOwnProperty.call(payload, "branches")) return;
13337
+ const v = payload["branches"];
13338
+ if (!Array.isArray(v)) return;
13339
+ row.linkedBranches = normalizeTicketBranchListFromPayloadValue(v);
13369
13340
  };
13370
-
13371
- // src/git/init-orphan-data-branch.ts
13372
- var import_promises4 = require("node:fs/promises");
13373
- var import_node_path4 = require("node:path");
13374
- var initOrphanDataBranchInWorktree = async (opts) => {
13375
- const { stdout: tip } = await opts.runGit(opts.repoRoot, [
13376
- "rev-parse",
13377
- "HEAD"
13378
- ]);
13379
- const tipCommit = tip.trim();
13380
- await opts.runGit(opts.repoRoot, [
13381
- "worktree",
13382
- "add",
13383
- opts.worktreePath,
13384
- tipCommit
13385
- ]);
13386
- await opts.runGit(opts.worktreePath, [
13387
- "checkout",
13388
- "--orphan",
13389
- opts.dataBranch
13390
- ]);
13391
- await opts.runGit(opts.worktreePath, ["rm", "-rf", "."]).catch(() => {
13392
- });
13393
- const marker = (0, import_node_path4.join)(opts.worktreePath, "README.hyper-pm.md");
13394
- await (0, import_promises4.writeFile)(
13395
- marker,
13396
- "# hyper-pm data branch\n\nAppend-only events live under `events/`.\n",
13397
- "utf8"
13398
- );
13399
- await opts.runGit(opts.worktreePath, ["add", "."]);
13400
- await opts.runGit(opts.worktreePath, [
13401
- "commit",
13402
- "-m",
13403
- "init hyper-pm data branch"
13404
- ]);
13405
- };
13406
-
13407
- // src/git/pick-unique-local-branch-name.ts
13408
- var DEFAULT_MAX_SUFFIX = 1e3;
13409
- var localBranchRefExists = async (repoRoot, name, git) => {
13410
- try {
13411
- await git(repoRoot, ["show-ref", "--verify", `refs/heads/${name}`]);
13412
- return true;
13413
- } catch {
13414
- return false;
13415
- }
13416
- };
13417
- var pickUniqueLocalBranchName = async (opts) => {
13418
- const max = opts.maxSuffix ?? DEFAULT_MAX_SUFFIX;
13419
- const { preferredBase, repoRoot, runGit: git } = opts;
13420
- for (let n = 1; n <= max; n += 1) {
13421
- const raw = n === 1 ? preferredBase : `${preferredBase}-${n}`;
13422
- const norm = normalizeTicketBranchName(raw);
13423
- if (norm === void 0) {
13424
- continue;
13425
- }
13426
- if (!await localBranchRefExists(repoRoot, norm, git)) {
13427
- return { branch: norm, preferred: preferredBase };
13341
+ var applyTicketPlanningFieldsFromCreatePayload = (row, payload) => {
13342
+ if (Object.prototype.hasOwnProperty.call(payload, "labels")) {
13343
+ const v = ticketLabelsFromPayloadValue(payload["labels"]);
13344
+ if (v !== void 0 && v.length > 0) {
13345
+ row.labels = v;
13428
13346
  }
13429
13347
  }
13430
- throw new Error(
13431
- `Could not allocate a free local branch name from ${JSON.stringify(preferredBase)} (tried suffixes up to -${String(max)})`
13432
- );
13433
- };
13434
-
13435
- // src/git/resolve-integration-start-point.ts
13436
- var assertGitRefResolvable = async (repoRoot, ref, git) => {
13437
- try {
13438
- await git(repoRoot, ["rev-parse", "-q", "--verify", ref]);
13439
- } catch {
13440
- throw new Error(`Invalid or ambiguous --from ref: ${ref}`);
13348
+ const pr = readTicketPriorityPatch(payload);
13349
+ if (typeof pr === "string") {
13350
+ row.priority = pr;
13441
13351
  }
13442
- };
13443
- var resolveIntegrationStartPoint = async (repoRoot, remote, git) => {
13444
- const symRef = `refs/remotes/${remote}/HEAD`;
13445
- try {
13446
- const { stdout } = await git(repoRoot, ["symbolic-ref", "-q", symRef]);
13447
- const target = stdout.trim();
13448
- if (target !== "") {
13449
- await git(repoRoot, ["rev-parse", "-q", "--verify", target]);
13450
- return target;
13451
- }
13452
- } catch {
13352
+ const sz = readTicketSizePatch(payload);
13353
+ if (typeof sz === "string") {
13354
+ row.size = sz;
13453
13355
  }
13454
- for (const head of ["refs/heads/main", "refs/heads/master"]) {
13455
- try {
13456
- await git(repoRoot, ["rev-parse", "-q", "--verify", head]);
13457
- return head;
13458
- } catch {
13459
- }
13356
+ const est = readTicketEstimatePatch(payload);
13357
+ if (typeof est === "number") {
13358
+ row.estimate = est;
13460
13359
  }
13461
- try {
13462
- await git(repoRoot, ["rev-parse", "-q", "--verify", "HEAD"]);
13463
- return "HEAD";
13464
- } catch {
13360
+ const sw = readTicketIsoInstantPatch(payload, "startWorkAt");
13361
+ if (typeof sw === "string") {
13362
+ row.startWorkAt = sw;
13465
13363
  }
13466
- throw new Error(
13467
- "Could not resolve a default branch to branch from; pass --from <ref> (e.g. main or origin/main)."
13468
- );
13469
- };
13470
-
13471
- // src/git/list-repo-commit-authors.ts
13472
- var listRepoCommitAuthors = async (repoRoot, git) => {
13473
- try {
13474
- const { stdout } = await git(repoRoot, [
13475
- "-c",
13476
- "log.showSignature=false",
13477
- "log",
13478
- "--all",
13479
- "--format=%an%x1f%ae"
13480
- ]);
13481
- if (stdout === "") return [];
13482
- const lines = stdout.split("\n").filter((l) => l.length > 0);
13483
- const seenEmail = /* @__PURE__ */ new Set();
13484
- const out = [];
13485
- for (const line of lines) {
13486
- const sep2 = line.indexOf("");
13487
- if (sep2 <= 0) continue;
13488
- const name = line.slice(0, sep2).trim();
13489
- const email = line.slice(sep2 + 1).trim();
13490
- if (email === "") continue;
13491
- const key = email.toLowerCase();
13492
- if (seenEmail.has(key)) continue;
13493
- seenEmail.add(key);
13494
- const loginGuess = guessGithubLoginFromContact(name, email);
13495
- out.push(
13496
- loginGuess !== void 0 ? { name, email, loginGuess } : { name, email }
13497
- );
13364
+ const tf = readTicketIsoInstantPatch(payload, "targetFinishAt");
13365
+ if (typeof tf === "string") {
13366
+ row.targetFinishAt = tf;
13367
+ }
13368
+ if (Object.prototype.hasOwnProperty.call(payload, "dependsOn")) {
13369
+ const v = parseTicketDependsOnFromPayloadValue(payload["dependsOn"]);
13370
+ if (v !== void 0 && v.length > 0) {
13371
+ row.dependsOn = v;
13498
13372
  }
13499
- return out;
13500
- } catch {
13501
- return [];
13502
13373
  }
13503
13374
  };
13504
-
13505
- // src/git/parse-github-owner-repo-from-remote-url.ts
13506
- var parseGithubOwnerRepoFromRemoteUrl = (rawUrl) => {
13507
- const trimmed = rawUrl.trim();
13508
- if (!trimmed) {
13509
- return void 0;
13510
- }
13511
- const scpMatch = /^git@github\.com:(?<path>.+)$/i.exec(trimmed);
13512
- if (scpMatch?.groups?.path) {
13513
- return slugFromGithubPath(scpMatch.groups.path);
13514
- }
13515
- try {
13516
- const withScheme = trimmed.includes("://") ? trimmed : `https://${trimmed}`;
13517
- const u = new URL(withScheme);
13518
- const host = u.hostname.toLowerCase();
13519
- if (host !== "github.com" && host !== "www.github.com") {
13520
- return void 0;
13375
+ var applyTicketPlanningFieldsFromUpdatePayload = (row, payload) => {
13376
+ if (Object.prototype.hasOwnProperty.call(payload, "labels")) {
13377
+ if (payload["labels"] === null) {
13378
+ delete row.labels;
13379
+ } else {
13380
+ const v = ticketLabelsFromPayloadValue(payload["labels"]);
13381
+ if (v !== void 0) {
13382
+ if (v.length === 0) {
13383
+ delete row.labels;
13384
+ } else {
13385
+ row.labels = v;
13386
+ }
13387
+ }
13521
13388
  }
13522
- return slugFromGithubPath(u.pathname);
13523
- } catch {
13524
- return void 0;
13525
13389
  }
13526
- };
13527
- var slugFromGithubPath = (path) => {
13528
- const segments = path.replace(/^\/+/, "").split("/").filter((s) => s.length > 0).map((s) => s.replace(/\.git$/i, ""));
13529
- if (segments.length < 2) {
13530
- return void 0;
13390
+ const pr = readTicketPriorityPatch(payload);
13391
+ if (pr === null) {
13392
+ delete row.priority;
13393
+ } else if (typeof pr === "string") {
13394
+ row.priority = pr;
13531
13395
  }
13532
- const owner = segments[0];
13533
- const repo = segments[1];
13534
- if (!owner || !repo) {
13535
- return void 0;
13396
+ const sz = readTicketSizePatch(payload);
13397
+ if (sz === null) {
13398
+ delete row.size;
13399
+ } else if (typeof sz === "string") {
13400
+ row.size = sz;
13536
13401
  }
13537
- return `${owner}/${repo}`;
13538
- };
13539
-
13540
- // src/git/try-read-github-owner-repo-slug-from-git.ts
13541
- var tryReadGithubOwnerRepoSlugFromGit = async (params) => {
13542
- try {
13543
- const { stdout } = await params.runGit(params.repoRoot, [
13544
- "remote",
13545
- "get-url",
13546
- params.remote
13547
- ]);
13548
- return parseGithubOwnerRepoFromRemoteUrl(stdout);
13549
- } catch {
13550
- return void 0;
13402
+ const est = readTicketEstimatePatch(payload);
13403
+ if (est === null) {
13404
+ delete row.estimate;
13405
+ } else if (typeof est === "number") {
13406
+ row.estimate = est;
13551
13407
  }
13552
- };
13553
-
13554
- // src/git/resolve-effective-git-author-for-data-commit.ts
13555
- var defaultDataCommitName = "hyper-pm";
13556
- var defaultDataCommitEmail = "hyper-pm@users.noreply.github.com";
13557
- var tryReadGitConfigUserName = async (cwd, runGit2) => {
13558
- try {
13559
- const { stdout } = await runGit2(cwd, ["config", "--get", "user.name"]);
13560
- return stdout.trim();
13561
- } catch {
13562
- return "";
13408
+ const sw = readTicketIsoInstantPatch(payload, "startWorkAt");
13409
+ if (sw === null) {
13410
+ delete row.startWorkAt;
13411
+ } else if (typeof sw === "string") {
13412
+ row.startWorkAt = sw;
13563
13413
  }
13564
- };
13565
- var tryReadGitConfigUserEmail = async (cwd, runGit2) => {
13566
- try {
13567
- const { stdout } = await runGit2(cwd, ["config", "--get", "user.email"]);
13568
- return stdout.trim();
13569
- } catch {
13570
- return "";
13414
+ const tf = readTicketIsoInstantPatch(payload, "targetFinishAt");
13415
+ if (tf === null) {
13416
+ delete row.targetFinishAt;
13417
+ } else if (typeof tf === "string") {
13418
+ row.targetFinishAt = tf;
13419
+ }
13420
+ if (Object.prototype.hasOwnProperty.call(payload, "dependsOn")) {
13421
+ if (payload["dependsOn"] === null) {
13422
+ delete row.dependsOn;
13423
+ } else {
13424
+ const v = parseTicketDependsOnFromPayloadValue(payload["dependsOn"]);
13425
+ if (v !== void 0) {
13426
+ if (v.length === 0) {
13427
+ delete row.dependsOn;
13428
+ } else {
13429
+ row.dependsOn = v;
13430
+ }
13431
+ }
13432
+ }
13571
13433
  }
13572
13434
  };
13573
- var resolveEffectiveGitAuthorForDataCommit = async (cwd, runGit2, authorEnv) => {
13574
- const fromGitName = await tryReadGitConfigUserName(cwd, runGit2);
13575
- const fromGitEmail = await tryReadGitConfigUserEmail(cwd, runGit2);
13576
- const name = fromGitName || authorEnv.HYPER_PM_GIT_USER_NAME?.trim() || authorEnv.GIT_AUTHOR_NAME?.trim() || defaultDataCommitName;
13577
- const email = fromGitEmail || authorEnv.HYPER_PM_GIT_USER_EMAIL?.trim() || authorEnv.GIT_AUTHOR_EMAIL?.trim() || defaultDataCommitEmail;
13578
- return { name, email };
13579
- };
13580
-
13581
- // src/run/commit-data.ts
13582
- var maxActorSuffixLen = 60;
13583
- var formatDataBranchCommitMessage = (base, actorSuffix) => {
13584
- const raw = actorSuffix?.trim();
13585
- if (!raw) {
13586
- return base;
13587
- }
13588
- const collapsed = raw.replace(/\s+/g, " ");
13589
- const suffix = collapsed.length > maxActorSuffixLen ? `${collapsed.slice(0, maxActorSuffixLen - 1)}\u2026` : collapsed;
13590
- return `${base} (${suffix})`;
13591
- };
13592
- var commitDataWorktreeIfNeeded = async (worktreePath, message, runGit2, opts) => {
13593
- const { stdout } = await runGit2(worktreePath, ["status", "--porcelain"]);
13594
- if (!stdout.trim()) return;
13595
- await runGit2(worktreePath, ["add", "."]);
13596
- const authorEnv = opts?.authorEnv ?? env;
13597
- const { name, email } = await resolveEffectiveGitAuthorForDataCommit(
13598
- worktreePath,
13599
- runGit2,
13600
- authorEnv
13601
- );
13602
- await runGit2(worktreePath, [
13603
- "-c",
13604
- `user.name=${name}`,
13605
- "-c",
13606
- `user.email=${email}`,
13607
- "commit",
13608
- "-m",
13609
- message
13610
- ]);
13611
- };
13612
-
13613
- // src/run/push-data-branch.ts
13614
- var firstLineFromUnknown = (err) => {
13615
- const raw = err instanceof Error ? err.message : String(err);
13616
- const line = raw.trim().split("\n")[0]?.trim() ?? raw.trim();
13617
- return line.length > 0 ? line : "git error";
13618
- };
13619
- var tryPushDataBranchToRemote = async (worktreePath, remote, branch, runGit2) => {
13620
- try {
13621
- await runGit2(worktreePath, ["remote", "get-url", remote]);
13622
- } catch (e) {
13623
- return {
13624
- status: "skipped_no_remote",
13625
- detail: firstLineFromUnknown(e)
13626
- };
13627
- }
13628
- try {
13629
- await runGit2(worktreePath, ["push", "-u", remote, branch]);
13630
- return { status: "pushed" };
13631
- } catch (e) {
13632
- return {
13633
- status: "failed",
13634
- detail: firstLineFromUnknown(e)
13635
- };
13636
- }
13637
- };
13638
-
13639
- // src/run/sync-remote-data-branch.ts
13640
- var SyncRemoteDataBranchMergeError = class extends Error {
13641
- /** @param message - Human-readable reason (stderr excerpt or generic text). */
13642
- constructor(message) {
13643
- super(message);
13644
- this.name = "SyncRemoteDataBranchMergeError";
13645
- }
13646
- };
13647
- var remoteTrackingRef = (remote, dataBranch) => `refs/remotes/${remote}/${dataBranch}`;
13648
- var mergeRefSpecifier = (remote, dataBranch) => `${remote}/${dataBranch}`;
13649
- var isLikelyNonFastForwardPushFailure = (detail) => {
13650
- if (detail === void 0) return false;
13651
- const d = detail.toLowerCase();
13652
- return d.includes("non-fast-forward") || d.includes("failed to push");
13435
+ var applyCreatedAudit = (row, evt) => {
13436
+ row.createdAt = evt.ts;
13437
+ row.createdBy = evt.actor;
13438
+ row.updatedAt = evt.ts;
13439
+ row.updatedBy = evt.actor;
13653
13440
  };
13654
- var classifyMergeOutput = (combined) => {
13655
- const c = combined.toLowerCase();
13656
- if (c.includes("already up to date")) {
13657
- return "up_to_date";
13658
- }
13659
- if (c.includes("fast-forward")) {
13660
- return "fast_forward";
13661
- }
13662
- return "merge_commit";
13441
+ var applyLastUpdate = (row, evt) => {
13442
+ row.updatedAt = evt.ts;
13443
+ row.updatedBy = evt.actor;
13663
13444
  };
13664
- var refExists = async (cwd, ref, runGit2) => {
13665
- try {
13666
- await runGit2(cwd, ["show-ref", "--verify", "--quiet", ref]);
13667
- return true;
13668
- } catch {
13669
- return false;
13670
- }
13445
+ var applyStatusIfChanged = (row, evt, nextStatus) => {
13446
+ if (row.status === nextStatus) return false;
13447
+ row.status = nextStatus;
13448
+ row.statusChangedAt = evt.ts;
13449
+ row.statusChangedBy = evt.actor;
13450
+ return true;
13671
13451
  };
13672
- var fetchRemoteDataBranch = async (worktreePath, remote, dataBranch, runGit2) => {
13673
- try {
13674
- await runGit2(worktreePath, ["remote", "get-url", remote]);
13675
- } catch {
13676
- return "skipped_no_remote";
13677
- }
13678
- try {
13679
- await runGit2(worktreePath, ["fetch", remote, dataBranch]);
13680
- return "ok";
13681
- } catch (e) {
13682
- const msg = e instanceof Error ? e.message : String(e);
13683
- if (/couldn't find remote ref|could not find remote ref/i.test(msg)) {
13684
- return "remote_branch_absent";
13685
- }
13686
- throw e;
13687
- }
13452
+ var appendTicketCommentFromEvent = (ticket, evt) => {
13453
+ const list = ticket.comments ?? (ticket.comments = []);
13454
+ list.push({
13455
+ id: evt.id,
13456
+ body: String(evt.payload["body"] ?? ""),
13457
+ createdAt: evt.ts,
13458
+ createdBy: evt.actor
13459
+ });
13460
+ applyLastUpdate(ticket, evt);
13688
13461
  };
13689
- var mergeRemoteTrackingIntoHead = async (worktreePath, remote, dataBranch, runGit2, authorEnv = env) => {
13690
- const spec = mergeRefSpecifier(remote, dataBranch);
13691
- const { name, email } = await resolveEffectiveGitAuthorForDataCommit(
13692
- worktreePath,
13693
- runGit2,
13694
- authorEnv
13695
- );
13696
- try {
13697
- const { stdout, stderr } = await runGit2(worktreePath, [
13698
- "-c",
13699
- `user.name=${name}`,
13700
- "-c",
13701
- `user.email=${email}`,
13702
- "merge",
13703
- "--no-edit",
13704
- spec
13705
- ]);
13706
- return classifyMergeOutput(`${stdout}
13707
- ${stderr}`);
13708
- } catch (e) {
13709
- await runGit2(worktreePath, ["merge", "--abort"]).catch(() => {
13462
+ var resolveInboundTicketStatusFromPayload = (ticket, payload) => {
13463
+ const explicit = parseWorkItemStatus(payload["status"]);
13464
+ if (explicit !== void 0) return explicit;
13465
+ const legacySt = payload["state"];
13466
+ if (legacySt === "open" || legacySt === "closed") {
13467
+ return resolveTicketInboundStatus({
13468
+ issueState: legacySt,
13469
+ currentStatus: ticket.status
13710
13470
  });
13711
- const msg = e instanceof Error ? e.message : String(e);
13712
- throw new SyncRemoteDataBranchMergeError(
13713
- 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}`
13714
- );
13715
13471
  }
13472
+ return void 0;
13716
13473
  };
13717
- var runRemoteDataBranchGitSync = async (worktreePath, remote, dataBranch, runGit2, skipPush, deps = {}) => {
13718
- const tryPushFn = deps.tryPush ?? tryPushDataBranchToRemote;
13719
- const maxPushAttempts = deps.maxPushAttempts ?? 3;
13720
- const authorEnv = deps.authorEnv ?? env;
13721
- let dataBranchFetch = await fetchRemoteDataBranch(worktreePath, remote, dataBranch, runGit2);
13722
- let dataBranchMerge = dataBranchFetch === "skipped_no_remote" ? "skipped_no_remote" : dataBranchFetch === "remote_branch_absent" ? "skipped_missing_remote_branch" : "up_to_date";
13723
- if (dataBranchFetch === "ok") {
13724
- const tracking = remoteTrackingRef(remote, dataBranch);
13725
- const exists = await refExists(worktreePath, tracking, runGit2);
13726
- if (!exists) {
13727
- dataBranchMerge = "skipped_missing_remote_branch";
13728
- } else {
13729
- dataBranchMerge = await mergeRemoteTrackingIntoHead(
13730
- worktreePath,
13731
- remote,
13732
- dataBranch,
13733
- runGit2,
13734
- authorEnv
13735
- );
13474
+ var applyEvent = (projection, evt) => {
13475
+ switch (evt.type) {
13476
+ case "EpicCreated": {
13477
+ const id = String(evt.payload["id"]);
13478
+ const status = resolveStatusForNewEpicStoryPayload(evt.payload);
13479
+ const row = {
13480
+ id,
13481
+ number: resolveWorkItemCreateNumber(projection, "epic", evt.payload),
13482
+ title: String(evt.payload["title"] ?? ""),
13483
+ body: String(evt.payload["body"] ?? ""),
13484
+ status,
13485
+ statusChangedAt: evt.ts,
13486
+ statusChangedBy: evt.actor,
13487
+ createdAt: "",
13488
+ createdBy: "",
13489
+ updatedAt: "",
13490
+ updatedBy: ""
13491
+ };
13492
+ applyCreatedAudit(row, evt);
13493
+ projection.epics.set(id, row);
13494
+ break;
13736
13495
  }
13737
- }
13738
- if (skipPush) {
13739
- return {
13740
- dataBranchFetch,
13741
- dataBranchMerge,
13742
- dataBranchPush: "skipped_cli",
13743
- dataBranchPushDetail: "skip-push",
13744
- pushAttempts: 0
13745
- };
13746
- }
13747
- let pushAttempts = 0;
13748
- let lastPush = { status: "skipped_no_remote" };
13749
- const refetchAndMerge = async () => {
13750
- dataBranchFetch = await fetchRemoteDataBranch(
13751
- worktreePath,
13752
- remote,
13753
- dataBranch,
13754
- runGit2
13755
- );
13756
- if (dataBranchFetch !== "ok") {
13757
- return;
13496
+ case "EpicUpdated": {
13497
+ const id = String(evt.payload["id"]);
13498
+ const cur = projection.epics.get(id);
13499
+ if (!cur) break;
13500
+ if (evt.payload["title"] !== void 0) {
13501
+ cur.title = String(evt.payload["title"]);
13502
+ }
13503
+ if (evt.payload["body"] !== void 0) {
13504
+ cur.body = String(evt.payload["body"]);
13505
+ }
13506
+ const nextStatus = parseWorkItemStatus(evt.payload["status"]);
13507
+ if (nextStatus !== void 0) {
13508
+ applyStatusIfChanged(cur, evt, nextStatus);
13509
+ }
13510
+ applyLastUpdate(cur, evt);
13511
+ break;
13758
13512
  }
13759
- const tracking = remoteTrackingRef(remote, dataBranch);
13760
- const exists = await refExists(worktreePath, tracking, runGit2);
13761
- if (!exists) {
13762
- return;
13513
+ case "EpicDeleted": {
13514
+ const id = String(evt.payload["id"]);
13515
+ const cur = projection.epics.get(id);
13516
+ if (cur) {
13517
+ cur.deleted = true;
13518
+ applyLastUpdate(cur, evt);
13519
+ }
13520
+ break;
13763
13521
  }
13764
- dataBranchMerge = await mergeRemoteTrackingIntoHead(
13765
- worktreePath,
13766
- remote,
13767
- dataBranch,
13768
- runGit2,
13769
- authorEnv
13770
- );
13771
- };
13772
- for (let attempt = 1; attempt <= maxPushAttempts; attempt += 1) {
13773
- pushAttempts = attempt;
13774
- lastPush = await tryPushFn(worktreePath, remote, dataBranch, runGit2);
13775
- if (lastPush.status === "pushed" || lastPush.status === "skipped_no_remote") {
13522
+ case "StoryCreated": {
13523
+ const id = String(evt.payload["id"]);
13524
+ const status = resolveStatusForNewEpicStoryPayload(evt.payload);
13525
+ const row = {
13526
+ id,
13527
+ number: resolveWorkItemCreateNumber(projection, "story", evt.payload),
13528
+ epicId: String(evt.payload["epicId"]),
13529
+ title: String(evt.payload["title"] ?? ""),
13530
+ body: String(evt.payload["body"] ?? ""),
13531
+ status,
13532
+ statusChangedAt: evt.ts,
13533
+ statusChangedBy: evt.actor,
13534
+ createdAt: "",
13535
+ createdBy: "",
13536
+ updatedAt: "",
13537
+ updatedBy: ""
13538
+ };
13539
+ applyCreatedAudit(row, evt);
13540
+ projection.stories.set(id, row);
13776
13541
  break;
13777
13542
  }
13778
- if (lastPush.status === "failed" && isLikelyNonFastForwardPushFailure(lastPush.detail) && attempt < maxPushAttempts) {
13779
- await refetchAndMerge();
13780
- continue;
13543
+ case "StoryUpdated": {
13544
+ const id = String(evt.payload["id"]);
13545
+ const cur = projection.stories.get(id);
13546
+ if (!cur) break;
13547
+ if (evt.payload["title"] !== void 0) {
13548
+ cur.title = String(evt.payload["title"]);
13549
+ }
13550
+ if (evt.payload["body"] !== void 0) {
13551
+ cur.body = String(evt.payload["body"]);
13552
+ }
13553
+ const nextStatus = parseWorkItemStatus(evt.payload["status"]);
13554
+ if (nextStatus !== void 0) {
13555
+ applyStatusIfChanged(cur, evt, nextStatus);
13556
+ }
13557
+ applyLastUpdate(cur, evt);
13558
+ break;
13781
13559
  }
13782
- break;
13560
+ case "StoryDeleted": {
13561
+ const id = String(evt.payload["id"]);
13562
+ const cur = projection.stories.get(id);
13563
+ if (cur) {
13564
+ cur.deleted = true;
13565
+ applyLastUpdate(cur, evt);
13566
+ }
13567
+ break;
13568
+ }
13569
+ case "TicketCreated": {
13570
+ const id = String(evt.payload["id"]);
13571
+ const body = String(evt.payload["body"] ?? "");
13572
+ const status = resolveStatusForNewTicketPayload(evt.payload);
13573
+ const row = {
13574
+ id,
13575
+ number: resolveWorkItemCreateNumber(projection, "ticket", evt.payload),
13576
+ storyId: storyIdFromTicketCreatedPayload(evt.payload),
13577
+ title: String(evt.payload["title"] ?? ""),
13578
+ body,
13579
+ status,
13580
+ statusChangedAt: evt.ts,
13581
+ statusChangedBy: evt.actor,
13582
+ linkedPrs: parsePrRefs(body),
13583
+ linkedBranches: linkedBranchesFromTicketCreatedPayload(evt.payload),
13584
+ prActivityRecent: [],
13585
+ createdAt: "",
13586
+ createdBy: "",
13587
+ updatedAt: "",
13588
+ updatedBy: ""
13589
+ };
13590
+ applyCreatedAudit(row, evt);
13591
+ applyTicketAssigneeFromPayload(row, evt.payload);
13592
+ applyTicketPlanningFieldsFromCreatePayload(row, evt.payload);
13593
+ projection.tickets.set(id, row);
13594
+ break;
13595
+ }
13596
+ case "TicketUpdated": {
13597
+ const id = String(evt.payload["id"]);
13598
+ const cur = projection.tickets.get(id);
13599
+ if (!cur) break;
13600
+ if (evt.payload["title"] !== void 0) {
13601
+ cur.title = String(evt.payload["title"]);
13602
+ }
13603
+ if (evt.payload["body"] !== void 0) {
13604
+ cur.body = String(evt.payload["body"]);
13605
+ cur.linkedPrs = parsePrRefs(cur.body);
13606
+ }
13607
+ const nextStatus = resolveTicketStatusFromUpdatePayload(evt.payload);
13608
+ if (nextStatus !== void 0) {
13609
+ applyStatusIfChanged(cur, evt, nextStatus);
13610
+ }
13611
+ applyTicketAssigneeFromPayload(cur, evt.payload);
13612
+ applyTicketStoryIdFromPayload(cur, evt.payload);
13613
+ applyTicketBranchesFromUpdatePayload(cur, evt.payload);
13614
+ applyTicketPlanningFieldsFromUpdatePayload(cur, evt.payload);
13615
+ applyLastUpdate(cur, evt);
13616
+ break;
13617
+ }
13618
+ case "TicketDeleted": {
13619
+ const id = String(evt.payload["id"]);
13620
+ const cur = projection.tickets.get(id);
13621
+ if (cur) {
13622
+ cur.deleted = true;
13623
+ applyLastUpdate(cur, evt);
13624
+ }
13625
+ break;
13626
+ }
13627
+ case "TicketCommentAdded": {
13628
+ const ticketId = String(evt.payload["ticketId"] ?? "");
13629
+ const ticket = projection.tickets.get(ticketId);
13630
+ if (!ticket || ticket.deleted) break;
13631
+ appendTicketCommentFromEvent(ticket, evt);
13632
+ break;
13633
+ }
13634
+ case "SyncCursor": {
13635
+ projection.syncCursor = String(evt.payload["cursor"] ?? "");
13636
+ break;
13637
+ }
13638
+ case "GithubIssueLinked": {
13639
+ const ticketId = String(evt.payload["ticketId"]);
13640
+ const num = Number(evt.payload["issueNumber"]);
13641
+ const ticket = projection.tickets.get(ticketId);
13642
+ if (ticket && Number.isFinite(num)) {
13643
+ ticket.githubIssueNumber = num;
13644
+ applyLastUpdate(ticket, evt);
13645
+ }
13646
+ break;
13647
+ }
13648
+ case "GithubInboundUpdate": {
13649
+ const entity = String(evt.payload["entity"]);
13650
+ const entityId = String(evt.payload["entityId"]);
13651
+ if (entity === "ticket") {
13652
+ const ticket = projection.tickets.get(entityId);
13653
+ if (!ticket) break;
13654
+ if (evt.payload["title"] !== void 0) {
13655
+ ticket.title = String(evt.payload["title"]);
13656
+ }
13657
+ if (evt.payload["body"] !== void 0) {
13658
+ ticket.body = String(evt.payload["body"]);
13659
+ ticket.linkedPrs = parsePrRefs(ticket.body);
13660
+ }
13661
+ const inboundStatus = resolveInboundTicketStatusFromPayload(
13662
+ ticket,
13663
+ evt.payload
13664
+ );
13665
+ if (inboundStatus !== void 0) {
13666
+ applyStatusIfChanged(ticket, evt, inboundStatus);
13667
+ }
13668
+ applyTicketAssigneeFromPayload(ticket, evt.payload);
13669
+ applyTicketPlanningFieldsFromUpdatePayload(ticket, evt.payload);
13670
+ applyLastUpdate(ticket, evt);
13671
+ }
13672
+ break;
13673
+ }
13674
+ case "GithubPrActivity": {
13675
+ const summary = parseGithubPrActivityPayload(evt.payload);
13676
+ if (!summary) break;
13677
+ const ticket = projection.tickets.get(
13678
+ String(evt.payload["ticketId"] ?? "")
13679
+ );
13680
+ if (!ticket || ticket.deleted) break;
13681
+ const list = ticket.prActivityRecent ?? (ticket.prActivityRecent = []);
13682
+ list.push(summary);
13683
+ if (list.length > GITHUB_PR_ACTIVITY_RECENT_CAP) {
13684
+ ticket.prActivityRecent = list.slice(-GITHUB_PR_ACTIVITY_RECENT_CAP);
13685
+ }
13686
+ break;
13687
+ }
13688
+ default:
13689
+ break;
13783
13690
  }
13784
- return {
13785
- dataBranchFetch,
13786
- dataBranchMerge,
13787
- dataBranchPush: lastPush.status,
13788
- ...lastPush.detail !== void 0 ? { dataBranchPushDetail: lastPush.detail } : {},
13789
- pushAttempts
13790
- };
13791
13691
  };
13792
-
13793
- // src/storage/append-event.ts
13794
- var import_promises5 = require("node:fs/promises");
13795
- var import_node_path5 = require("node:path");
13796
-
13797
- // src/storage/event-path.ts
13798
- var nextEventRelPath = (now, deps) => {
13799
- const y = String(now.getUTCFullYear());
13800
- const m = String(now.getUTCMonth() + 1).padStart(2, "0");
13801
- const nextId = deps?.nextId ?? (() => ulid().toLowerCase());
13802
- const part = `part-${nextId()}`;
13803
- return `events/${y}/${m}/${part}.jsonl`;
13692
+ var replayEvents = (lines) => {
13693
+ const proj = emptyProjection();
13694
+ const events = [];
13695
+ for (const line of lines) {
13696
+ const trimmed = line.trim();
13697
+ if (!trimmed) continue;
13698
+ const json = JSON.parse(trimmed);
13699
+ events.push(eventLineSchema.parse(json));
13700
+ }
13701
+ events.sort((a, b) => {
13702
+ const t = a.ts.localeCompare(b.ts);
13703
+ if (t !== 0) return t;
13704
+ return a.id.localeCompare(b.id);
13705
+ });
13706
+ for (const e of events) {
13707
+ applyEvent(proj, e);
13708
+ }
13709
+ return proj;
13804
13710
  };
13805
13711
 
13806
- // src/storage/append-event.ts
13807
- var appendEventLine = async (dataRoot, event, clock, opts) => {
13808
- const rel = nextEventRelPath(clock.now(), {
13809
- nextId: opts?.nextEventId
13712
+ // src/cli/resolve-projection-work-item-id.ts
13713
+ var isDigitOnlyWorkItemRef = (raw) => /^\d+$/.test(raw.trim());
13714
+ var resolveEpicId = (projection, raw) => {
13715
+ const t = raw.trim();
13716
+ if (t === "") return void 0;
13717
+ if (projection.epics.has(t)) return t;
13718
+ if (!isDigitOnlyWorkItemRef(t)) return void 0;
13719
+ const n = Number(t);
13720
+ const hits = [...projection.epics.values()].filter((e) => e.number === n);
13721
+ if (hits.length !== 1) return void 0;
13722
+ return hits[0].id;
13723
+ };
13724
+ var resolveStoryId = (projection, raw) => {
13725
+ const t = raw.trim();
13726
+ if (t === "") return void 0;
13727
+ if (projection.stories.has(t)) return t;
13728
+ if (!isDigitOnlyWorkItemRef(t)) return void 0;
13729
+ const n = Number(t);
13730
+ const hits = [...projection.stories.values()].filter((s) => s.number === n);
13731
+ if (hits.length !== 1) return void 0;
13732
+ return hits[0].id;
13733
+ };
13734
+ var resolveTicketId = (projection, raw) => {
13735
+ const t = raw.trim();
13736
+ if (t === "") return void 0;
13737
+ if (projection.tickets.has(t)) return t;
13738
+ if (!isDigitOnlyWorkItemRef(t)) return void 0;
13739
+ const n = Number(t);
13740
+ const hits = [...projection.tickets.values()].filter((x) => x.number === n);
13741
+ if (hits.length !== 1) return void 0;
13742
+ return hits[0].id;
13743
+ };
13744
+ var resolveTicketDependsOnTokensToIds = (projection, tokens) => {
13745
+ const mapped = tokens.map((raw) => {
13746
+ const t = raw.trim();
13747
+ if (t === "") return "";
13748
+ if (projection.tickets.has(t)) return t;
13749
+ if (!isDigitOnlyWorkItemRef(t)) return t;
13750
+ const hit = resolveTicketId(projection, t);
13751
+ return hit ?? t;
13810
13752
  });
13811
- const abs = `${dataRoot.replace(/\/$/, "")}/${rel}`;
13812
- await (0, import_promises5.mkdir)((0, import_node_path5.dirname)(abs), { recursive: true });
13813
- await (0, import_promises5.appendFile)(abs, `${JSON.stringify(event)}
13814
- `, "utf8");
13815
- return rel;
13753
+ return normalizeTicketDependsOnIds(mapped);
13816
13754
  };
13817
-
13818
- // src/storage/read-event-lines.ts
13819
- var import_promises6 = require("node:fs/promises");
13820
- var import_node_path6 = require("node:path");
13821
- var listJsonlFiles = async (dir, root) => {
13822
- const out = [];
13823
- const entries = await (0, import_promises6.readdir)(dir, { withFileTypes: true });
13824
- for (const e of entries) {
13825
- const abs = (0, import_node_path6.join)(dir, e.name);
13826
- if (e.isDirectory()) {
13827
- out.push(...await listJsonlFiles(abs, root));
13828
- } else if (e.isFile() && e.name.endsWith(".jsonl")) {
13829
- out.push((0, import_node_path6.relative)(root, abs).split("\\").join("/"));
13830
- }
13755
+ var assertCreatePayloadUsesExpectedHeadNumber = (projection, kind, payload) => {
13756
+ const n = readOptionalPositiveIntegerFromPayload(payload, "number");
13757
+ const expected = kind === "epic" ? nextEpicNumberForCreate(projection) : kind === "story" ? nextStoryNumberForCreate(projection) : nextTicketNumberForCreate(projection);
13758
+ if (n !== expected) {
13759
+ const got = n === void 0 ? "missing" : String(n);
13760
+ throw new Error(
13761
+ `Invalid ${kind} number for this data branch: expected ${String(expected)}, got ${got}.`
13762
+ );
13831
13763
  }
13832
- return out;
13833
- };
13834
- var readAllEventLines = async (dataRoot) => {
13835
- const eventsRoot = (0, import_node_path6.join)(dataRoot, "events");
13836
- let files = [];
13837
- try {
13838
- files = await listJsonlFiles(eventsRoot, dataRoot);
13839
- } catch {
13840
- return [];
13841
- }
13842
- files.sort((a, b) => a.localeCompare(b));
13843
- const lines = [];
13844
- for (const rel of files) {
13845
- const content = await (0, import_promises6.readFile)((0, import_node_path6.join)(dataRoot, rel), "utf8");
13846
- for (const line of content.split("\n")) {
13847
- if (line.trim()) lines.push(line);
13848
- }
13849
- }
13850
- return lines;
13851
13764
  };
13852
13765
 
13853
- // src/lib/github-pr-activity.ts
13854
- var GITHUB_PR_ACTIVITY_RECENT_CAP = 20;
13855
- var githubPrActivityKindSchema = external_exports.enum([
13856
- "opened",
13857
- "updated",
13858
- "commented",
13859
- "reviewed",
13860
- "merged",
13861
- "closed",
13862
- "ready_for_review"
13863
- ]);
13864
- var githubPrReviewStateSchema = external_exports.enum([
13865
- "approved",
13866
- "changes_requested",
13867
- "commented"
13868
- ]);
13869
- var buildPrOpenSourceId = (ticketId, prNumber) => `hyper-pm:pr-open:${ticketId}:${prNumber}`;
13870
- var parseGithubPrActivityPayload = (payload) => {
13871
- const ticketId = payload["ticketId"];
13872
- const prRaw = payload["prNumber"];
13873
- const kindRaw = payload["kind"];
13874
- const occurredAt = payload["occurredAt"];
13875
- const sourceId = payload["sourceId"];
13876
- if (typeof ticketId !== "string" || typeof occurredAt !== "string" || typeof sourceId !== "string") {
13877
- return void 0;
13878
- }
13879
- const prNumber = typeof prRaw === "number" ? prRaw : Number(prRaw);
13880
- if (!Number.isFinite(prNumber)) return void 0;
13881
- const kindParsed = githubPrActivityKindSchema.safeParse(kindRaw);
13882
- if (!kindParsed.success) return void 0;
13883
- const out = {
13884
- prNumber,
13885
- kind: kindParsed.data,
13886
- occurredAt,
13887
- sourceId
13888
- };
13889
- const url = payload["url"];
13890
- if (typeof url === "string" && url.length > 0) {
13891
- out.url = url;
13892
- }
13893
- const rs = githubPrReviewStateSchema.safeParse(payload["reviewState"]);
13894
- if (rs.success) {
13895
- out.reviewState = rs.data;
13766
+ // src/config/hyper-pm-config.ts
13767
+ var hyperPmConfigSchema = external_exports.object({
13768
+ schema: external_exports.literal(1),
13769
+ dataBranch: external_exports.string().min(1),
13770
+ remote: external_exports.string().min(1).default("origin"),
13771
+ sync: external_exports.enum(["off", "outbound", "full"]).default("outbound"),
13772
+ githubRepo: external_exports.string().optional(),
13773
+ issueMapping: external_exports.enum(["ticket", "story", "epic"]).default("ticket")
13774
+ });
13775
+
13776
+ // src/config/load-config.ts
13777
+ var import_promises = require("node:fs/promises");
13778
+ var import_node_path = require("node:path");
13779
+ var CONFIG_DIR = ".hyper-pm";
13780
+ var CONFIG_FILE = "config.json";
13781
+ var getHyperPmConfigPath = (repoRoot) => {
13782
+ return (0, import_node_path.join)(repoRoot, CONFIG_DIR, CONFIG_FILE);
13783
+ };
13784
+ var loadHyperPmConfig = async (repoRoot, overrides = {}) => {
13785
+ const raw = await (0, import_promises.readFile)(getHyperPmConfigPath(repoRoot), "utf8");
13786
+ const parsed = JSON.parse(raw);
13787
+ const base = hyperPmConfigSchema.parse(parsed);
13788
+ const merged = { ...base, ...stripUndefined(overrides) };
13789
+ return hyperPmConfigSchema.parse(merged);
13790
+ };
13791
+ var stripUndefined = (obj) => {
13792
+ const out = {};
13793
+ for (const [k, v] of Object.entries(obj)) {
13794
+ if (v !== void 0) out[k] = v;
13896
13795
  }
13897
13796
  return out;
13898
13797
  };
13899
13798
 
13900
- // src/storage/projection.ts
13901
- var emptyProjection = () => ({
13902
- epics: /* @__PURE__ */ new Map(),
13903
- stories: /* @__PURE__ */ new Map(),
13904
- tickets: /* @__PURE__ */ new Map()
13905
- });
13906
- var parsePrRefs = (body) => {
13907
- const out = /* @__PURE__ */ new Set();
13908
- const re = /\b(?:Closes|Refs|Fixes)\s+#(\d+)\b/gi;
13909
- let m = re.exec(body);
13910
- while (m !== null) {
13911
- out.add(Number(m[1]));
13912
- m = re.exec(body);
13913
- }
13914
- return [...out];
13799
+ // src/config/save-config.ts
13800
+ var import_promises2 = require("node:fs/promises");
13801
+ var import_node_path2 = require("node:path");
13802
+ var saveHyperPmConfig = async (repoRoot, config) => {
13803
+ const target = getHyperPmConfigPath(repoRoot);
13804
+ await (0, import_promises2.mkdir)((0, import_node_path2.dirname)(target), { recursive: true });
13805
+ await (0, import_promises2.writeFile)(target, `${JSON.stringify(config, null, 2)}
13806
+ `, "utf8");
13915
13807
  };
13916
- var applyTicketAssigneeFromPayload = (row, payload) => {
13917
- if (!Object.prototype.hasOwnProperty.call(payload, "assignee")) return;
13918
- const v = payload["assignee"];
13919
- if (v === null) {
13920
- delete row.assignee;
13921
- return;
13808
+
13809
+ // src/doctor/run-doctor.ts
13810
+ var runDoctorOnLines = (lines) => {
13811
+ const issues = [];
13812
+ for (let i = 0; i < lines.length; i++) {
13813
+ const line = lines[i]?.trim() ?? "";
13814
+ if (!line) continue;
13815
+ let json;
13816
+ try {
13817
+ json = JSON.parse(line);
13818
+ } catch (e) {
13819
+ issues.push({
13820
+ kind: "invalid-json",
13821
+ line: i + 1,
13822
+ message: e instanceof Error ? e.message : "parse error"
13823
+ });
13824
+ return issues;
13825
+ }
13826
+ const parsed = eventLineSchema.safeParse(json);
13827
+ if (!parsed.success) {
13828
+ issues.push({
13829
+ kind: "invalid-event",
13830
+ line: i + 1,
13831
+ message: parsed.error.message
13832
+ });
13833
+ return issues;
13834
+ }
13922
13835
  }
13923
- if (typeof v !== "string") return;
13924
- const n = normalizeGithubLogin(v);
13925
- if (n === "") delete row.assignee;
13926
- else row.assignee = n;
13836
+ return issues;
13927
13837
  };
13928
- var storyIdFromTicketCreatedPayload = (payload) => {
13929
- if (!Object.prototype.hasOwnProperty.call(payload, "storyId")) {
13930
- return null;
13931
- }
13932
- const v = payload["storyId"];
13933
- if (v === null) return null;
13934
- if (typeof v !== "string") return null;
13935
- const t = v.trim();
13936
- return t === "" ? null : t;
13838
+
13839
+ // src/git/create-and-checkout-branch.ts
13840
+ var createAndCheckoutBranch = async (opts) => {
13841
+ await opts.runGit(opts.repoRoot, [
13842
+ "switch",
13843
+ "-c",
13844
+ opts.branchName,
13845
+ opts.startPoint
13846
+ ]);
13937
13847
  };
13938
- var applyTicketStoryIdFromPayload = (row, payload) => {
13939
- if (!Object.prototype.hasOwnProperty.call(payload, "storyId")) return;
13940
- const v = payload["storyId"];
13941
- if (v === null) {
13942
- row.storyId = null;
13943
- return;
13944
- }
13945
- if (typeof v !== "string") return;
13946
- const t = v.trim();
13947
- row.storyId = t === "" ? null : t;
13848
+
13849
+ // src/git/data-worktree-session.ts
13850
+ var import_promises3 = require("node:fs/promises");
13851
+ var import_node_path3 = require("node:path");
13852
+ var ensureDir = async (path) => {
13853
+ await (0, import_promises3.mkdir)(path, { recursive: true });
13948
13854
  };
13949
- var linkedBranchesFromTicketCreatedPayload = (payload) => {
13950
- if (!Object.prototype.hasOwnProperty.call(payload, "branches")) {
13951
- return [];
13855
+ var defaultPathExists = async (path) => {
13856
+ try {
13857
+ await (0, import_promises3.access)(path);
13858
+ return true;
13859
+ } catch {
13860
+ return false;
13952
13861
  }
13953
- return normalizeTicketBranchListFromPayloadValue(payload["branches"]);
13954
- };
13955
- var applyTicketBranchesFromUpdatePayload = (row, payload) => {
13956
- if (!Object.prototype.hasOwnProperty.call(payload, "branches")) return;
13957
- const v = payload["branches"];
13958
- if (!Array.isArray(v)) return;
13959
- row.linkedBranches = normalizeTicketBranchListFromPayloadValue(v);
13960
13862
  };
13961
- var applyTicketPlanningFieldsFromCreatePayload = (row, payload) => {
13962
- if (Object.prototype.hasOwnProperty.call(payload, "labels")) {
13963
- const v = ticketLabelsFromPayloadValue(payload["labels"]);
13964
- if (v !== void 0 && v.length > 0) {
13965
- row.labels = v;
13863
+ var parseDataBranchWorktreeFromPorcelain = (stdout, dataBranch) => {
13864
+ const wantRef = `refs/heads/${dataBranch}`;
13865
+ const blocks = stdout.split(/\n\n+/).map((b) => b.trim()).filter(Boolean);
13866
+ for (const block of blocks) {
13867
+ const lines = block.split("\n");
13868
+ let worktreePath;
13869
+ let branch;
13870
+ for (const line of lines) {
13871
+ if (line.startsWith("worktree ")) {
13872
+ worktreePath = line.slice("worktree ".length);
13873
+ } else if (line.startsWith("branch ")) {
13874
+ branch = line.slice("branch ".length);
13875
+ }
13966
13876
  }
13967
- }
13968
- const pr = readTicketPriorityPatch(payload);
13969
- if (typeof pr === "string") {
13970
- row.priority = pr;
13971
- }
13972
- const sz = readTicketSizePatch(payload);
13973
- if (typeof sz === "string") {
13974
- row.size = sz;
13975
- }
13976
- const est = readTicketEstimatePatch(payload);
13977
- if (typeof est === "number") {
13978
- row.estimate = est;
13979
- }
13980
- const sw = readTicketIsoInstantPatch(payload, "startWorkAt");
13981
- if (typeof sw === "string") {
13982
- row.startWorkAt = sw;
13983
- }
13984
- const tf = readTicketIsoInstantPatch(payload, "targetFinishAt");
13985
- if (typeof tf === "string") {
13986
- row.targetFinishAt = tf;
13987
- }
13988
- if (Object.prototype.hasOwnProperty.call(payload, "dependsOn")) {
13989
- const v = parseTicketDependsOnFromPayloadValue(payload["dependsOn"]);
13990
- if (v !== void 0 && v.length > 0) {
13991
- row.dependsOn = v;
13877
+ if (worktreePath !== void 0 && branch === wantRef) {
13878
+ return worktreePath;
13992
13879
  }
13993
13880
  }
13881
+ return void 0;
13994
13882
  };
13995
- var applyTicketPlanningFieldsFromUpdatePayload = (row, payload) => {
13996
- if (Object.prototype.hasOwnProperty.call(payload, "labels")) {
13997
- if (payload["labels"] === null) {
13998
- delete row.labels;
13999
- } else {
14000
- const v = ticketLabelsFromPayloadValue(payload["labels"]);
14001
- if (v !== void 0) {
14002
- if (v.length === 0) {
14003
- delete row.labels;
14004
- } else {
14005
- row.labels = v;
14006
- }
14007
- }
14008
- }
14009
- }
14010
- const pr = readTicketPriorityPatch(payload);
14011
- if (pr === null) {
14012
- delete row.priority;
14013
- } else if (typeof pr === "string") {
14014
- row.priority = pr;
14015
- }
14016
- const sz = readTicketSizePatch(payload);
14017
- if (sz === null) {
14018
- delete row.size;
14019
- } else if (typeof sz === "string") {
14020
- row.size = sz;
14021
- }
14022
- const est = readTicketEstimatePatch(payload);
14023
- if (est === null) {
14024
- delete row.estimate;
14025
- } else if (typeof est === "number") {
14026
- row.estimate = est;
14027
- }
14028
- const sw = readTicketIsoInstantPatch(payload, "startWorkAt");
14029
- if (sw === null) {
14030
- delete row.startWorkAt;
14031
- } else if (typeof sw === "string") {
14032
- row.startWorkAt = sw;
13883
+ var openDataBranchWorktree = async (opts) => {
13884
+ const pathExists = opts.pathExists ?? defaultPathExists;
13885
+ const { stdout: listOut } = await opts.runGit(opts.repoRoot, [
13886
+ "worktree",
13887
+ "list",
13888
+ "--porcelain"
13889
+ ]);
13890
+ const listedPath = parseDataBranchWorktreeFromPorcelain(
13891
+ listOut,
13892
+ opts.dataBranch
13893
+ );
13894
+ if (listedPath !== void 0 && await pathExists(listedPath)) {
13895
+ const dispose2 = async () => {
13896
+ return;
13897
+ };
13898
+ return { worktreePath: listedPath, dispose: dispose2 };
14033
13899
  }
14034
- const tf = readTicketIsoInstantPatch(payload, "targetFinishAt");
14035
- if (tf === null) {
14036
- delete row.targetFinishAt;
14037
- } else if (typeof tf === "string") {
14038
- row.targetFinishAt = tf;
13900
+ const worktreePath = (0, import_node_path3.join)(
13901
+ opts.tmpBase,
13902
+ `hyper-pm-worktree-${ulid().toLowerCase()}`
13903
+ );
13904
+ await ensureDir(opts.tmpBase);
13905
+ try {
13906
+ await opts.runGit(opts.repoRoot, [
13907
+ "worktree",
13908
+ "add",
13909
+ worktreePath,
13910
+ opts.dataBranch
13911
+ ]);
13912
+ } catch (err) {
13913
+ await (0, import_promises3.rm)(worktreePath, { recursive: true, force: true }).catch(() => {
13914
+ });
13915
+ throw err;
14039
13916
  }
14040
- if (Object.prototype.hasOwnProperty.call(payload, "dependsOn")) {
14041
- if (payload["dependsOn"] === null) {
14042
- delete row.dependsOn;
14043
- } else {
14044
- const v = parseTicketDependsOnFromPayloadValue(payload["dependsOn"]);
14045
- if (v !== void 0) {
14046
- if (v.length === 0) {
14047
- delete row.dependsOn;
14048
- } else {
14049
- row.dependsOn = v;
14050
- }
14051
- }
13917
+ const dispose = async () => {
13918
+ if (opts.keepWorktree) {
13919
+ return;
14052
13920
  }
14053
- }
14054
- };
14055
- var applyCreatedAudit = (row, evt) => {
14056
- row.createdAt = evt.ts;
14057
- row.createdBy = evt.actor;
14058
- row.updatedAt = evt.ts;
14059
- row.updatedBy = evt.actor;
14060
- };
14061
- var applyLastUpdate = (row, evt) => {
14062
- row.updatedAt = evt.ts;
14063
- row.updatedBy = evt.actor;
13921
+ await opts.runGit(opts.repoRoot, ["worktree", "remove", "--force", worktreePath]).catch(() => {
13922
+ });
13923
+ await (0, import_promises3.rm)(worktreePath, { recursive: true, force: true }).catch(() => {
13924
+ });
13925
+ };
13926
+ return { worktreePath, dispose };
14064
13927
  };
14065
- var applyStatusIfChanged = (row, evt, nextStatus) => {
14066
- if (row.status === nextStatus) return false;
14067
- row.status = nextStatus;
14068
- row.statusChangedAt = evt.ts;
14069
- row.statusChangedBy = evt.actor;
14070
- return true;
13928
+
13929
+ // src/git/find-git-root.ts
13930
+ var findGitRoot = async (cwd, deps) => {
13931
+ const { stdout } = await deps.runGit(cwd, ["rev-parse", "--show-toplevel"]);
13932
+ return stdout;
14071
13933
  };
14072
- var appendTicketCommentFromEvent = (ticket, evt) => {
14073
- const list = ticket.comments ?? (ticket.comments = []);
14074
- list.push({
14075
- id: evt.id,
14076
- body: String(evt.payload["body"] ?? ""),
14077
- createdAt: evt.ts,
14078
- createdBy: evt.actor
13934
+
13935
+ // src/git/init-orphan-data-branch.ts
13936
+ var import_promises4 = require("node:fs/promises");
13937
+ var import_node_path4 = require("node:path");
13938
+ var initOrphanDataBranchInWorktree = async (opts) => {
13939
+ const { stdout: tip } = await opts.runGit(opts.repoRoot, [
13940
+ "rev-parse",
13941
+ "HEAD"
13942
+ ]);
13943
+ const tipCommit = tip.trim();
13944
+ await opts.runGit(opts.repoRoot, [
13945
+ "worktree",
13946
+ "add",
13947
+ opts.worktreePath,
13948
+ tipCommit
13949
+ ]);
13950
+ await opts.runGit(opts.worktreePath, [
13951
+ "checkout",
13952
+ "--orphan",
13953
+ opts.dataBranch
13954
+ ]);
13955
+ await opts.runGit(opts.worktreePath, ["rm", "-rf", "."]).catch(() => {
14079
13956
  });
14080
- applyLastUpdate(ticket, evt);
13957
+ const marker = (0, import_node_path4.join)(opts.worktreePath, "README.hyper-pm.md");
13958
+ await (0, import_promises4.writeFile)(
13959
+ marker,
13960
+ "# hyper-pm data branch\n\nAppend-only events live under `events/`.\n",
13961
+ "utf8"
13962
+ );
13963
+ await opts.runGit(opts.worktreePath, ["add", "."]);
13964
+ await opts.runGit(opts.worktreePath, [
13965
+ "commit",
13966
+ "-m",
13967
+ "init hyper-pm data branch"
13968
+ ]);
14081
13969
  };
14082
- var resolveInboundTicketStatusFromPayload = (ticket, payload) => {
14083
- const explicit = parseWorkItemStatus(payload["status"]);
14084
- if (explicit !== void 0) return explicit;
14085
- const legacySt = payload["state"];
14086
- if (legacySt === "open" || legacySt === "closed") {
14087
- return resolveTicketInboundStatus({
14088
- issueState: legacySt,
14089
- currentStatus: ticket.status
14090
- });
13970
+
13971
+ // src/git/pick-unique-local-branch-name.ts
13972
+ var DEFAULT_MAX_SUFFIX = 1e3;
13973
+ var localBranchRefExists = async (repoRoot, name, git) => {
13974
+ try {
13975
+ await git(repoRoot, ["show-ref", "--verify", `refs/heads/${name}`]);
13976
+ return true;
13977
+ } catch {
13978
+ return false;
14091
13979
  }
14092
- return void 0;
14093
13980
  };
14094
- var applyEvent = (projection, evt) => {
14095
- switch (evt.type) {
14096
- case "EpicCreated": {
14097
- const id = String(evt.payload["id"]);
14098
- const status = resolveStatusForNewEpicStoryPayload(evt.payload);
14099
- const row = {
14100
- id,
14101
- title: String(evt.payload["title"] ?? ""),
14102
- body: String(evt.payload["body"] ?? ""),
14103
- status,
14104
- statusChangedAt: evt.ts,
14105
- statusChangedBy: evt.actor,
14106
- createdAt: "",
14107
- createdBy: "",
14108
- updatedAt: "",
14109
- updatedBy: ""
14110
- };
14111
- applyCreatedAudit(row, evt);
14112
- projection.epics.set(id, row);
14113
- break;
13981
+ var pickUniqueLocalBranchName = async (opts) => {
13982
+ const max = opts.maxSuffix ?? DEFAULT_MAX_SUFFIX;
13983
+ const { preferredBase, repoRoot, runGit: git } = opts;
13984
+ for (let n = 1; n <= max; n += 1) {
13985
+ const raw = n === 1 ? preferredBase : `${preferredBase}-${n}`;
13986
+ const norm = normalizeTicketBranchName(raw);
13987
+ if (norm === void 0) {
13988
+ continue;
14114
13989
  }
14115
- case "EpicUpdated": {
14116
- const id = String(evt.payload["id"]);
14117
- const cur = projection.epics.get(id);
14118
- if (!cur) break;
14119
- if (evt.payload["title"] !== void 0) {
14120
- cur.title = String(evt.payload["title"]);
14121
- }
14122
- if (evt.payload["body"] !== void 0) {
14123
- cur.body = String(evt.payload["body"]);
14124
- }
14125
- const nextStatus = parseWorkItemStatus(evt.payload["status"]);
14126
- if (nextStatus !== void 0) {
14127
- applyStatusIfChanged(cur, evt, nextStatus);
14128
- }
14129
- applyLastUpdate(cur, evt);
14130
- break;
13990
+ if (!await localBranchRefExists(repoRoot, norm, git)) {
13991
+ return { branch: norm, preferred: preferredBase };
14131
13992
  }
14132
- case "EpicDeleted": {
14133
- const id = String(evt.payload["id"]);
14134
- const cur = projection.epics.get(id);
14135
- if (cur) {
14136
- cur.deleted = true;
14137
- applyLastUpdate(cur, evt);
14138
- }
14139
- break;
13993
+ }
13994
+ throw new Error(
13995
+ `Could not allocate a free local branch name from ${JSON.stringify(preferredBase)} (tried suffixes up to -${String(max)})`
13996
+ );
13997
+ };
13998
+
13999
+ // src/git/resolve-integration-start-point.ts
14000
+ var assertGitRefResolvable = async (repoRoot, ref, git) => {
14001
+ try {
14002
+ await git(repoRoot, ["rev-parse", "-q", "--verify", ref]);
14003
+ } catch {
14004
+ throw new Error(`Invalid or ambiguous --from ref: ${ref}`);
14005
+ }
14006
+ };
14007
+ var resolveIntegrationStartPoint = async (repoRoot, remote, git) => {
14008
+ const symRef = `refs/remotes/${remote}/HEAD`;
14009
+ try {
14010
+ const { stdout } = await git(repoRoot, ["symbolic-ref", "-q", symRef]);
14011
+ const target = stdout.trim();
14012
+ if (target !== "") {
14013
+ await git(repoRoot, ["rev-parse", "-q", "--verify", target]);
14014
+ return target;
14140
14015
  }
14141
- case "StoryCreated": {
14142
- const id = String(evt.payload["id"]);
14143
- const status = resolveStatusForNewEpicStoryPayload(evt.payload);
14144
- const row = {
14145
- id,
14146
- epicId: String(evt.payload["epicId"]),
14147
- title: String(evt.payload["title"] ?? ""),
14148
- body: String(evt.payload["body"] ?? ""),
14149
- status,
14150
- statusChangedAt: evt.ts,
14151
- statusChangedBy: evt.actor,
14152
- createdAt: "",
14153
- createdBy: "",
14154
- updatedAt: "",
14155
- updatedBy: ""
14156
- };
14157
- applyCreatedAudit(row, evt);
14158
- projection.stories.set(id, row);
14159
- break;
14160
- }
14161
- case "StoryUpdated": {
14162
- const id = String(evt.payload["id"]);
14163
- const cur = projection.stories.get(id);
14164
- if (!cur) break;
14165
- if (evt.payload["title"] !== void 0) {
14166
- cur.title = String(evt.payload["title"]);
14167
- }
14168
- if (evt.payload["body"] !== void 0) {
14169
- cur.body = String(evt.payload["body"]);
14170
- }
14171
- const nextStatus = parseWorkItemStatus(evt.payload["status"]);
14172
- if (nextStatus !== void 0) {
14173
- applyStatusIfChanged(cur, evt, nextStatus);
14174
- }
14175
- applyLastUpdate(cur, evt);
14176
- break;
14016
+ } catch {
14017
+ }
14018
+ for (const head of ["refs/heads/main", "refs/heads/master"]) {
14019
+ try {
14020
+ await git(repoRoot, ["rev-parse", "-q", "--verify", head]);
14021
+ return head;
14022
+ } catch {
14177
14023
  }
14178
- case "StoryDeleted": {
14179
- const id = String(evt.payload["id"]);
14180
- const cur = projection.stories.get(id);
14181
- if (cur) {
14182
- cur.deleted = true;
14183
- applyLastUpdate(cur, evt);
14184
- }
14185
- break;
14024
+ }
14025
+ try {
14026
+ await git(repoRoot, ["rev-parse", "-q", "--verify", "HEAD"]);
14027
+ return "HEAD";
14028
+ } catch {
14029
+ }
14030
+ throw new Error(
14031
+ "Could not resolve a default branch to branch from; pass --from <ref> (e.g. main or origin/main)."
14032
+ );
14033
+ };
14034
+
14035
+ // src/git/list-repo-commit-authors.ts
14036
+ var listRepoCommitAuthors = async (repoRoot, git) => {
14037
+ try {
14038
+ const { stdout } = await git(repoRoot, [
14039
+ "-c",
14040
+ "log.showSignature=false",
14041
+ "log",
14042
+ "--all",
14043
+ "--format=%an%x1f%ae"
14044
+ ]);
14045
+ if (stdout === "") return [];
14046
+ const lines = stdout.split("\n").filter((l) => l.length > 0);
14047
+ const seenEmail = /* @__PURE__ */ new Set();
14048
+ const out = [];
14049
+ for (const line of lines) {
14050
+ const sep2 = line.indexOf("");
14051
+ if (sep2 <= 0) continue;
14052
+ const name = line.slice(0, sep2).trim();
14053
+ const email = line.slice(sep2 + 1).trim();
14054
+ if (email === "") continue;
14055
+ const key = email.toLowerCase();
14056
+ if (seenEmail.has(key)) continue;
14057
+ seenEmail.add(key);
14058
+ const loginGuess = guessGithubLoginFromContact(name, email);
14059
+ out.push(
14060
+ loginGuess !== void 0 ? { name, email, loginGuess } : { name, email }
14061
+ );
14186
14062
  }
14187
- case "TicketCreated": {
14188
- const id = String(evt.payload["id"]);
14189
- const body = String(evt.payload["body"] ?? "");
14190
- const status = resolveStatusForNewTicketPayload(evt.payload);
14191
- const row = {
14192
- id,
14193
- storyId: storyIdFromTicketCreatedPayload(evt.payload),
14194
- title: String(evt.payload["title"] ?? ""),
14195
- body,
14196
- status,
14197
- statusChangedAt: evt.ts,
14198
- statusChangedBy: evt.actor,
14199
- linkedPrs: parsePrRefs(body),
14200
- linkedBranches: linkedBranchesFromTicketCreatedPayload(evt.payload),
14201
- prActivityRecent: [],
14202
- createdAt: "",
14203
- createdBy: "",
14204
- updatedAt: "",
14205
- updatedBy: ""
14206
- };
14207
- applyCreatedAudit(row, evt);
14208
- applyTicketAssigneeFromPayload(row, evt.payload);
14209
- applyTicketPlanningFieldsFromCreatePayload(row, evt.payload);
14210
- projection.tickets.set(id, row);
14211
- break;
14063
+ return out;
14064
+ } catch {
14065
+ return [];
14066
+ }
14067
+ };
14068
+
14069
+ // src/git/parse-github-owner-repo-from-remote-url.ts
14070
+ var parseGithubOwnerRepoFromRemoteUrl = (rawUrl) => {
14071
+ const trimmed = rawUrl.trim();
14072
+ if (!trimmed) {
14073
+ return void 0;
14074
+ }
14075
+ const scpMatch = /^git@github\.com:(?<path>.+)$/i.exec(trimmed);
14076
+ if (scpMatch?.groups?.path) {
14077
+ return slugFromGithubPath(scpMatch.groups.path);
14078
+ }
14079
+ try {
14080
+ const withScheme = trimmed.includes("://") ? trimmed : `https://${trimmed}`;
14081
+ const u = new URL(withScheme);
14082
+ const host = u.hostname.toLowerCase();
14083
+ if (host !== "github.com" && host !== "www.github.com") {
14084
+ return void 0;
14212
14085
  }
14213
- case "TicketUpdated": {
14214
- const id = String(evt.payload["id"]);
14215
- const cur = projection.tickets.get(id);
14216
- if (!cur) break;
14217
- if (evt.payload["title"] !== void 0) {
14218
- cur.title = String(evt.payload["title"]);
14219
- }
14220
- if (evt.payload["body"] !== void 0) {
14221
- cur.body = String(evt.payload["body"]);
14222
- cur.linkedPrs = parsePrRefs(cur.body);
14223
- }
14224
- const nextStatus = resolveTicketStatusFromUpdatePayload(evt.payload);
14225
- if (nextStatus !== void 0) {
14226
- applyStatusIfChanged(cur, evt, nextStatus);
14227
- }
14228
- applyTicketAssigneeFromPayload(cur, evt.payload);
14229
- applyTicketStoryIdFromPayload(cur, evt.payload);
14230
- applyTicketBranchesFromUpdatePayload(cur, evt.payload);
14231
- applyTicketPlanningFieldsFromUpdatePayload(cur, evt.payload);
14232
- applyLastUpdate(cur, evt);
14233
- break;
14086
+ return slugFromGithubPath(u.pathname);
14087
+ } catch {
14088
+ return void 0;
14089
+ }
14090
+ };
14091
+ var slugFromGithubPath = (path) => {
14092
+ const segments = path.replace(/^\/+/, "").split("/").filter((s) => s.length > 0).map((s) => s.replace(/\.git$/i, ""));
14093
+ if (segments.length < 2) {
14094
+ return void 0;
14095
+ }
14096
+ const owner = segments[0];
14097
+ const repo = segments[1];
14098
+ if (!owner || !repo) {
14099
+ return void 0;
14100
+ }
14101
+ return `${owner}/${repo}`;
14102
+ };
14103
+
14104
+ // src/git/try-read-github-owner-repo-slug-from-git.ts
14105
+ var tryReadGithubOwnerRepoSlugFromGit = async (params) => {
14106
+ try {
14107
+ const { stdout } = await params.runGit(params.repoRoot, [
14108
+ "remote",
14109
+ "get-url",
14110
+ params.remote
14111
+ ]);
14112
+ return parseGithubOwnerRepoFromRemoteUrl(stdout);
14113
+ } catch {
14114
+ return void 0;
14115
+ }
14116
+ };
14117
+
14118
+ // src/git/resolve-effective-git-author-for-data-commit.ts
14119
+ var defaultDataCommitName = "hyper-pm";
14120
+ var defaultDataCommitEmail = "hyper-pm@users.noreply.github.com";
14121
+ var tryReadGitConfigUserName = async (cwd, runGit2) => {
14122
+ try {
14123
+ const { stdout } = await runGit2(cwd, ["config", "--get", "user.name"]);
14124
+ return stdout.trim();
14125
+ } catch {
14126
+ return "";
14127
+ }
14128
+ };
14129
+ var tryReadGitConfigUserEmail = async (cwd, runGit2) => {
14130
+ try {
14131
+ const { stdout } = await runGit2(cwd, ["config", "--get", "user.email"]);
14132
+ return stdout.trim();
14133
+ } catch {
14134
+ return "";
14135
+ }
14136
+ };
14137
+ var resolveEffectiveGitAuthorForDataCommit = async (cwd, runGit2, authorEnv) => {
14138
+ const fromGitName = await tryReadGitConfigUserName(cwd, runGit2);
14139
+ const fromGitEmail = await tryReadGitConfigUserEmail(cwd, runGit2);
14140
+ const name = fromGitName || authorEnv.HYPER_PM_GIT_USER_NAME?.trim() || authorEnv.GIT_AUTHOR_NAME?.trim() || defaultDataCommitName;
14141
+ const email = fromGitEmail || authorEnv.HYPER_PM_GIT_USER_EMAIL?.trim() || authorEnv.GIT_AUTHOR_EMAIL?.trim() || defaultDataCommitEmail;
14142
+ return { name, email };
14143
+ };
14144
+
14145
+ // src/run/commit-data.ts
14146
+ var maxActorSuffixLen = 60;
14147
+ var formatDataBranchCommitMessage = (base, actorSuffix) => {
14148
+ const raw = actorSuffix?.trim();
14149
+ if (!raw) {
14150
+ return base;
14151
+ }
14152
+ const collapsed = raw.replace(/\s+/g, " ");
14153
+ const suffix = collapsed.length > maxActorSuffixLen ? `${collapsed.slice(0, maxActorSuffixLen - 1)}\u2026` : collapsed;
14154
+ return `${base} (${suffix})`;
14155
+ };
14156
+ var commitDataWorktreeIfNeeded = async (worktreePath, message, runGit2, opts) => {
14157
+ const { stdout } = await runGit2(worktreePath, ["status", "--porcelain"]);
14158
+ if (!stdout.trim()) return;
14159
+ await runGit2(worktreePath, ["add", "."]);
14160
+ const authorEnv = opts?.authorEnv ?? env;
14161
+ const { name, email } = await resolveEffectiveGitAuthorForDataCommit(
14162
+ worktreePath,
14163
+ runGit2,
14164
+ authorEnv
14165
+ );
14166
+ await runGit2(worktreePath, [
14167
+ "-c",
14168
+ `user.name=${name}`,
14169
+ "-c",
14170
+ `user.email=${email}`,
14171
+ "commit",
14172
+ "-m",
14173
+ message
14174
+ ]);
14175
+ };
14176
+
14177
+ // src/run/push-data-branch.ts
14178
+ var firstLineFromUnknown = (err) => {
14179
+ const raw = err instanceof Error ? err.message : String(err);
14180
+ const line = raw.trim().split("\n")[0]?.trim() ?? raw.trim();
14181
+ return line.length > 0 ? line : "git error";
14182
+ };
14183
+ var tryPushDataBranchToRemote = async (worktreePath, remote, branch, runGit2) => {
14184
+ try {
14185
+ await runGit2(worktreePath, ["remote", "get-url", remote]);
14186
+ } catch (e) {
14187
+ return {
14188
+ status: "skipped_no_remote",
14189
+ detail: firstLineFromUnknown(e)
14190
+ };
14191
+ }
14192
+ try {
14193
+ await runGit2(worktreePath, ["push", "-u", remote, branch]);
14194
+ return { status: "pushed" };
14195
+ } catch (e) {
14196
+ return {
14197
+ status: "failed",
14198
+ detail: firstLineFromUnknown(e)
14199
+ };
14200
+ }
14201
+ };
14202
+
14203
+ // src/run/sync-remote-data-branch.ts
14204
+ var SyncRemoteDataBranchMergeError = class extends Error {
14205
+ /** @param message - Human-readable reason (stderr excerpt or generic text). */
14206
+ constructor(message) {
14207
+ super(message);
14208
+ this.name = "SyncRemoteDataBranchMergeError";
14209
+ }
14210
+ };
14211
+ var remoteTrackingRef = (remote, dataBranch) => `refs/remotes/${remote}/${dataBranch}`;
14212
+ var mergeRefSpecifier = (remote, dataBranch) => `${remote}/${dataBranch}`;
14213
+ var isLikelyNonFastForwardPushFailure = (detail) => {
14214
+ if (detail === void 0) return false;
14215
+ const d = detail.toLowerCase();
14216
+ return d.includes("non-fast-forward") || d.includes("failed to push");
14217
+ };
14218
+ var classifyMergeOutput = (combined) => {
14219
+ const c = combined.toLowerCase();
14220
+ if (c.includes("already up to date")) {
14221
+ return "up_to_date";
14222
+ }
14223
+ if (c.includes("fast-forward")) {
14224
+ return "fast_forward";
14225
+ }
14226
+ return "merge_commit";
14227
+ };
14228
+ var refExists = async (cwd, ref, runGit2) => {
14229
+ try {
14230
+ await runGit2(cwd, ["show-ref", "--verify", "--quiet", ref]);
14231
+ return true;
14232
+ } catch {
14233
+ return false;
14234
+ }
14235
+ };
14236
+ var fetchRemoteDataBranch = async (worktreePath, remote, dataBranch, runGit2) => {
14237
+ try {
14238
+ await runGit2(worktreePath, ["remote", "get-url", remote]);
14239
+ } catch {
14240
+ return "skipped_no_remote";
14241
+ }
14242
+ try {
14243
+ await runGit2(worktreePath, ["fetch", remote, dataBranch]);
14244
+ return "ok";
14245
+ } catch (e) {
14246
+ const msg = e instanceof Error ? e.message : String(e);
14247
+ if (/couldn't find remote ref|could not find remote ref/i.test(msg)) {
14248
+ return "remote_branch_absent";
14234
14249
  }
14235
- case "TicketDeleted": {
14236
- const id = String(evt.payload["id"]);
14237
- const cur = projection.tickets.get(id);
14238
- if (cur) {
14239
- cur.deleted = true;
14240
- applyLastUpdate(cur, evt);
14241
- }
14242
- break;
14250
+ throw e;
14251
+ }
14252
+ };
14253
+ var mergeRemoteTrackingIntoHead = async (worktreePath, remote, dataBranch, runGit2, authorEnv = env) => {
14254
+ const spec = mergeRefSpecifier(remote, dataBranch);
14255
+ const { name, email } = await resolveEffectiveGitAuthorForDataCommit(
14256
+ worktreePath,
14257
+ runGit2,
14258
+ authorEnv
14259
+ );
14260
+ try {
14261
+ const { stdout, stderr } = await runGit2(worktreePath, [
14262
+ "-c",
14263
+ `user.name=${name}`,
14264
+ "-c",
14265
+ `user.email=${email}`,
14266
+ "merge",
14267
+ "--no-edit",
14268
+ spec
14269
+ ]);
14270
+ return classifyMergeOutput(`${stdout}
14271
+ ${stderr}`);
14272
+ } catch (e) {
14273
+ await runGit2(worktreePath, ["merge", "--abort"]).catch(() => {
14274
+ });
14275
+ const msg = e instanceof Error ? e.message : String(e);
14276
+ throw new SyncRemoteDataBranchMergeError(
14277
+ 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}`
14278
+ );
14279
+ }
14280
+ };
14281
+ var runRemoteDataBranchGitSync = async (worktreePath, remote, dataBranch, runGit2, skipPush, deps = {}) => {
14282
+ const tryPushFn = deps.tryPush ?? tryPushDataBranchToRemote;
14283
+ const maxPushAttempts = deps.maxPushAttempts ?? 3;
14284
+ const authorEnv = deps.authorEnv ?? env;
14285
+ let dataBranchFetch = await fetchRemoteDataBranch(worktreePath, remote, dataBranch, runGit2);
14286
+ let dataBranchMerge = dataBranchFetch === "skipped_no_remote" ? "skipped_no_remote" : dataBranchFetch === "remote_branch_absent" ? "skipped_missing_remote_branch" : "up_to_date";
14287
+ if (dataBranchFetch === "ok") {
14288
+ const tracking = remoteTrackingRef(remote, dataBranch);
14289
+ const exists = await refExists(worktreePath, tracking, runGit2);
14290
+ if (!exists) {
14291
+ dataBranchMerge = "skipped_missing_remote_branch";
14292
+ } else {
14293
+ dataBranchMerge = await mergeRemoteTrackingIntoHead(
14294
+ worktreePath,
14295
+ remote,
14296
+ dataBranch,
14297
+ runGit2,
14298
+ authorEnv
14299
+ );
14243
14300
  }
14244
- case "TicketCommentAdded": {
14245
- const ticketId = String(evt.payload["ticketId"] ?? "");
14246
- const ticket = projection.tickets.get(ticketId);
14247
- if (!ticket || ticket.deleted) break;
14248
- appendTicketCommentFromEvent(ticket, evt);
14249
- break;
14301
+ }
14302
+ if (skipPush) {
14303
+ return {
14304
+ dataBranchFetch,
14305
+ dataBranchMerge,
14306
+ dataBranchPush: "skipped_cli",
14307
+ dataBranchPushDetail: "skip-push",
14308
+ pushAttempts: 0
14309
+ };
14310
+ }
14311
+ let pushAttempts = 0;
14312
+ let lastPush = { status: "skipped_no_remote" };
14313
+ const refetchAndMerge = async () => {
14314
+ dataBranchFetch = await fetchRemoteDataBranch(
14315
+ worktreePath,
14316
+ remote,
14317
+ dataBranch,
14318
+ runGit2
14319
+ );
14320
+ if (dataBranchFetch !== "ok") {
14321
+ return;
14250
14322
  }
14251
- case "SyncCursor": {
14252
- projection.syncCursor = String(evt.payload["cursor"] ?? "");
14253
- break;
14323
+ const tracking = remoteTrackingRef(remote, dataBranch);
14324
+ const exists = await refExists(worktreePath, tracking, runGit2);
14325
+ if (!exists) {
14326
+ return;
14254
14327
  }
14255
- case "GithubIssueLinked": {
14256
- const ticketId = String(evt.payload["ticketId"]);
14257
- const num = Number(evt.payload["issueNumber"]);
14258
- const ticket = projection.tickets.get(ticketId);
14259
- if (ticket && Number.isFinite(num)) {
14260
- ticket.githubIssueNumber = num;
14261
- applyLastUpdate(ticket, evt);
14262
- }
14328
+ dataBranchMerge = await mergeRemoteTrackingIntoHead(
14329
+ worktreePath,
14330
+ remote,
14331
+ dataBranch,
14332
+ runGit2,
14333
+ authorEnv
14334
+ );
14335
+ };
14336
+ for (let attempt = 1; attempt <= maxPushAttempts; attempt += 1) {
14337
+ pushAttempts = attempt;
14338
+ lastPush = await tryPushFn(worktreePath, remote, dataBranch, runGit2);
14339
+ if (lastPush.status === "pushed" || lastPush.status === "skipped_no_remote") {
14263
14340
  break;
14264
14341
  }
14265
- case "GithubInboundUpdate": {
14266
- const entity = String(evt.payload["entity"]);
14267
- const entityId = String(evt.payload["entityId"]);
14268
- if (entity === "ticket") {
14269
- const ticket = projection.tickets.get(entityId);
14270
- if (!ticket) break;
14271
- if (evt.payload["title"] !== void 0) {
14272
- ticket.title = String(evt.payload["title"]);
14273
- }
14274
- if (evt.payload["body"] !== void 0) {
14275
- ticket.body = String(evt.payload["body"]);
14276
- ticket.linkedPrs = parsePrRefs(ticket.body);
14277
- }
14278
- const inboundStatus = resolveInboundTicketStatusFromPayload(
14279
- ticket,
14280
- evt.payload
14281
- );
14282
- if (inboundStatus !== void 0) {
14283
- applyStatusIfChanged(ticket, evt, inboundStatus);
14284
- }
14285
- applyTicketAssigneeFromPayload(ticket, evt.payload);
14286
- applyTicketPlanningFieldsFromUpdatePayload(ticket, evt.payload);
14287
- applyLastUpdate(ticket, evt);
14288
- }
14289
- break;
14342
+ if (lastPush.status === "failed" && isLikelyNonFastForwardPushFailure(lastPush.detail) && attempt < maxPushAttempts) {
14343
+ await refetchAndMerge();
14344
+ continue;
14290
14345
  }
14291
- case "GithubPrActivity": {
14292
- const summary = parseGithubPrActivityPayload(evt.payload);
14293
- if (!summary) break;
14294
- const ticket = projection.tickets.get(
14295
- String(evt.payload["ticketId"] ?? "")
14296
- );
14297
- if (!ticket || ticket.deleted) break;
14298
- const list = ticket.prActivityRecent ?? (ticket.prActivityRecent = []);
14299
- list.push(summary);
14300
- if (list.length > GITHUB_PR_ACTIVITY_RECENT_CAP) {
14301
- ticket.prActivityRecent = list.slice(-GITHUB_PR_ACTIVITY_RECENT_CAP);
14302
- }
14303
- break;
14346
+ break;
14347
+ }
14348
+ return {
14349
+ dataBranchFetch,
14350
+ dataBranchMerge,
14351
+ dataBranchPush: lastPush.status,
14352
+ ...lastPush.detail !== void 0 ? { dataBranchPushDetail: lastPush.detail } : {},
14353
+ pushAttempts
14354
+ };
14355
+ };
14356
+
14357
+ // src/storage/append-event.ts
14358
+ var import_promises5 = require("node:fs/promises");
14359
+ var import_node_path5 = require("node:path");
14360
+
14361
+ // src/storage/event-path.ts
14362
+ var nextEventRelPath = (now, deps) => {
14363
+ const y = String(now.getUTCFullYear());
14364
+ const m = String(now.getUTCMonth() + 1).padStart(2, "0");
14365
+ const nextId = deps?.nextId ?? (() => ulid().toLowerCase());
14366
+ const part = `part-${nextId()}`;
14367
+ return `events/${y}/${m}/${part}.jsonl`;
14368
+ };
14369
+
14370
+ // src/storage/append-event.ts
14371
+ var appendEventLine = async (dataRoot, event, clock, opts) => {
14372
+ const rel = nextEventRelPath(clock.now(), {
14373
+ nextId: opts?.nextEventId
14374
+ });
14375
+ const abs = `${dataRoot.replace(/\/$/, "")}/${rel}`;
14376
+ await (0, import_promises5.mkdir)((0, import_node_path5.dirname)(abs), { recursive: true });
14377
+ await (0, import_promises5.appendFile)(abs, `${JSON.stringify(event)}
14378
+ `, "utf8");
14379
+ return rel;
14380
+ };
14381
+
14382
+ // src/storage/read-event-lines.ts
14383
+ var import_promises6 = require("node:fs/promises");
14384
+ var import_node_path6 = require("node:path");
14385
+ var listJsonlFiles = async (dir, root) => {
14386
+ const out = [];
14387
+ const entries = await (0, import_promises6.readdir)(dir, { withFileTypes: true });
14388
+ for (const e of entries) {
14389
+ const abs = (0, import_node_path6.join)(dir, e.name);
14390
+ if (e.isDirectory()) {
14391
+ out.push(...await listJsonlFiles(abs, root));
14392
+ } else if (e.isFile() && e.name.endsWith(".jsonl")) {
14393
+ out.push((0, import_node_path6.relative)(root, abs).split("\\").join("/"));
14304
14394
  }
14305
- default:
14306
- break;
14307
14395
  }
14396
+ return out;
14308
14397
  };
14309
- var replayEvents = (lines) => {
14310
- const proj = emptyProjection();
14311
- const events = [];
14312
- for (const line of lines) {
14313
- const trimmed = line.trim();
14314
- if (!trimmed) continue;
14315
- const json = JSON.parse(trimmed);
14316
- events.push(eventLineSchema.parse(json));
14398
+ var readAllEventLines = async (dataRoot) => {
14399
+ const eventsRoot = (0, import_node_path6.join)(dataRoot, "events");
14400
+ let files = [];
14401
+ try {
14402
+ files = await listJsonlFiles(eventsRoot, dataRoot);
14403
+ } catch {
14404
+ return [];
14317
14405
  }
14318
- events.sort((a, b) => {
14319
- const t = a.ts.localeCompare(b.ts);
14320
- if (t !== 0) return t;
14321
- return a.id.localeCompare(b.id);
14322
- });
14323
- for (const e of events) {
14324
- applyEvent(proj, e);
14406
+ files.sort((a, b) => a.localeCompare(b));
14407
+ const lines = [];
14408
+ for (const rel of files) {
14409
+ const content = await (0, import_promises6.readFile)((0, import_node_path6.join)(dataRoot, rel), "utf8");
14410
+ for (const line of content.split("\n")) {
14411
+ if (line.trim()) lines.push(line);
14412
+ }
14325
14413
  }
14326
- return proj;
14414
+ return lines;
14327
14415
  };
14328
14416
 
14329
14417
  // src/sync/collect-github-pr-activity-source-ids.ts
@@ -14883,14 +14971,15 @@ var parseCliWorkItemStatusList = (raws, deps) => {
14883
14971
  }
14884
14972
  return out;
14885
14973
  };
14886
- var buildTicketListQueryFromReadListOpts = (o, deps) => {
14974
+ var buildTicketListQueryFromReadListOpts = (projection, o, deps) => {
14887
14975
  const query = {};
14888
14976
  const statusTokens = normalizeCliStringList(o.status);
14889
14977
  if (statusTokens.length > 0) {
14890
14978
  query.statuses = parseCliWorkItemStatusList(statusTokens, deps);
14891
14979
  }
14892
14980
  if (o.epic !== void 0 && o.epic !== "") {
14893
- query.epicId = o.epic;
14981
+ const trimmed = o.epic.trim();
14982
+ query.epicId = resolveEpicId(projection, trimmed) ?? trimmed;
14894
14983
  }
14895
14984
  const createdAfterMs = parseCliOptionalIsoMillis(
14896
14985
  o.createdAfter,
@@ -15028,7 +15117,8 @@ var buildTicketListQueryFromReadListOpts = (o, deps) => {
15028
15117
  query.targetFinishBeforeMs = targetFinishBeforeMs;
15029
15118
  }
15030
15119
  if (o.dependsOn !== void 0 && o.dependsOn.trim() !== "") {
15031
- query.dependsOnIncludesId = o.dependsOn.trim();
15120
+ const trimmed = o.dependsOn.trim();
15121
+ query.dependsOnIncludesId = resolveTicketId(projection, trimmed) ?? trimmed;
15032
15122
  }
15033
15123
  return Object.keys(query).length > 0 ? query : void 0;
15034
15124
  };
@@ -15105,19 +15195,19 @@ var runCli = async (argv, deps = {
15105
15195
  const g = readGlobals(this);
15106
15196
  const o = this.opts();
15107
15197
  await mutateDataBranch(g, deps, async (root, { actor }) => {
15198
+ const lines = await readAllEventLines(root);
15199
+ const proj = replayEvents(lines);
15108
15200
  const id = o.id ?? ulid();
15109
15201
  const status = parseCliWorkItemStatus(o.status, deps);
15110
- const evt = makeEvent(
15111
- "EpicCreated",
15112
- {
15113
- id,
15114
- title: o.title,
15115
- body: o.body,
15116
- ...status !== void 0 ? { status } : {}
15117
- },
15118
- deps.clock,
15119
- actor
15120
- );
15202
+ const payload = {
15203
+ id,
15204
+ number: nextEpicNumberForCreate(proj),
15205
+ title: o.title,
15206
+ body: o.body,
15207
+ ...status !== void 0 ? { status } : {}
15208
+ };
15209
+ assertCreatePayloadUsesExpectedHeadNumber(proj, "epic", payload);
15210
+ const evt = makeEvent("EpicCreated", payload, deps.clock, actor);
15121
15211
  await appendEventLine(root, evt, deps.clock);
15122
15212
  return evt.payload;
15123
15213
  });
@@ -15133,18 +15223,19 @@ var runCli = async (argv, deps = {
15133
15223
  await mutateDataBranch(g, deps, async (root, { actor }) => {
15134
15224
  const lines = await readAllEventLines(root);
15135
15225
  const proj = replayEvents(lines);
15136
- const cur = proj.epics.get(o.id);
15226
+ const epicId = resolveEpicId(proj, o.id) ?? o.id.trim();
15227
+ const cur = proj.epics.get(epicId);
15137
15228
  if (!cur || cur.deleted) {
15138
15229
  throw new Error(`Epic not found: ${o.id}`);
15139
15230
  }
15140
15231
  const status = parseCliWorkItemStatus(o.status, deps);
15141
- const draft = { id: o.id };
15232
+ const draft = { id: epicId };
15142
15233
  if (o.title !== void 0) draft["title"] = o.title;
15143
15234
  if (o.body !== void 0) draft["body"] = o.body;
15144
15235
  if (status !== void 0) draft["status"] = status;
15145
15236
  const payload = pruneEpicOrStoryUpdatePayloadAgainstRow(cur, draft);
15146
15237
  if (isNoOpUpdatePayload(payload)) {
15147
- return { id: o.id, noChanges: true };
15238
+ return { id: epicId, noChanges: true };
15148
15239
  }
15149
15240
  const evt = makeEvent("EpicUpdated", payload, deps.clock, actor);
15150
15241
  await appendEventLine(root, evt, deps.clock);
@@ -15155,9 +15246,12 @@ var runCli = async (argv, deps = {
15155
15246
  const g = readGlobals(this);
15156
15247
  const o = this.opts();
15157
15248
  await mutateDataBranch(g, deps, async (root, { actor }) => {
15158
- const evt = makeEvent("EpicDeleted", { id: o.id }, deps.clock, actor);
15249
+ const lines = await readAllEventLines(root);
15250
+ const proj = replayEvents(lines);
15251
+ const epicId = resolveEpicId(proj, o.id) ?? o.id.trim();
15252
+ const evt = makeEvent("EpicDeleted", { id: epicId }, deps.clock, actor);
15159
15253
  await appendEventLine(root, evt, deps.clock);
15160
- return { id: o.id, deleted: true };
15254
+ return { id: epicId, deleted: true };
15161
15255
  });
15162
15256
  });
15163
15257
  const story = program2.command("story");
@@ -15170,24 +15264,23 @@ var runCli = async (argv, deps = {
15170
15264
  await mutateDataBranch(g, deps, async (root, { actor }) => {
15171
15265
  const lines = await readAllEventLines(root);
15172
15266
  const proj = replayEvents(lines);
15173
- const epic2 = proj.epics.get(o.epic);
15267
+ const epicId = resolveEpicId(proj, o.epic) ?? o.epic.trim();
15268
+ const epic2 = proj.epics.get(epicId);
15174
15269
  if (!epic2 || epic2.deleted) {
15175
15270
  throw new Error(`Epic not found: ${o.epic}`);
15176
15271
  }
15177
15272
  const id = o.id ?? ulid();
15178
15273
  const status = parseCliWorkItemStatus(o.status, deps);
15179
- const evt = makeEvent(
15180
- "StoryCreated",
15181
- {
15182
- id,
15183
- epicId: o.epic,
15184
- title: o.title,
15185
- body: o.body,
15186
- ...status !== void 0 ? { status } : {}
15187
- },
15188
- deps.clock,
15189
- actor
15190
- );
15274
+ const payload = {
15275
+ id,
15276
+ number: nextStoryNumberForCreate(proj),
15277
+ epicId,
15278
+ title: o.title,
15279
+ body: o.body,
15280
+ ...status !== void 0 ? { status } : {}
15281
+ };
15282
+ assertCreatePayloadUsesExpectedHeadNumber(proj, "story", payload);
15283
+ const evt = makeEvent("StoryCreated", payload, deps.clock, actor);
15191
15284
  await appendEventLine(root, evt, deps.clock);
15192
15285
  return evt.payload;
15193
15286
  });
@@ -15206,18 +15299,19 @@ var runCli = async (argv, deps = {
15206
15299
  await mutateDataBranch(g, deps, async (root, { actor }) => {
15207
15300
  const lines = await readAllEventLines(root);
15208
15301
  const proj = replayEvents(lines);
15209
- const cur = proj.stories.get(o.id);
15302
+ const storyId = resolveStoryId(proj, o.id) ?? o.id.trim();
15303
+ const cur = proj.stories.get(storyId);
15210
15304
  if (!cur || cur.deleted) {
15211
15305
  throw new Error(`Story not found: ${o.id}`);
15212
15306
  }
15213
15307
  const status = parseCliWorkItemStatus(o.status, deps);
15214
- const draft = { id: o.id };
15308
+ const draft = { id: storyId };
15215
15309
  if (o.title !== void 0) draft["title"] = o.title;
15216
15310
  if (o.body !== void 0) draft["body"] = o.body;
15217
15311
  if (status !== void 0) draft["status"] = status;
15218
15312
  const payload = pruneEpicOrStoryUpdatePayloadAgainstRow(cur, draft);
15219
15313
  if (isNoOpUpdatePayload(payload)) {
15220
- return { id: o.id, noChanges: true };
15314
+ return { id: storyId, noChanges: true };
15221
15315
  }
15222
15316
  const evt = makeEvent("StoryUpdated", payload, deps.clock, actor);
15223
15317
  await appendEventLine(root, evt, deps.clock);
@@ -15228,9 +15322,17 @@ var runCli = async (argv, deps = {
15228
15322
  const g = readGlobals(this);
15229
15323
  const o = this.opts();
15230
15324
  await mutateDataBranch(g, deps, async (root, { actor }) => {
15231
- const evt = makeEvent("StoryDeleted", { id: o.id }, deps.clock, actor);
15325
+ const lines = await readAllEventLines(root);
15326
+ const proj = replayEvents(lines);
15327
+ const storyId = resolveStoryId(proj, o.id) ?? o.id.trim();
15328
+ const evt = makeEvent(
15329
+ "StoryDeleted",
15330
+ { id: storyId },
15331
+ deps.clock,
15332
+ actor
15333
+ );
15232
15334
  await appendEventLine(root, evt, deps.clock);
15233
- return { id: o.id, deleted: true };
15335
+ return { id: storyId, deleted: true };
15234
15336
  });
15235
15337
  });
15236
15338
  const ticket = program2.command("ticket");
@@ -15321,9 +15423,6 @@ var runCli = async (argv, deps = {
15321
15423
  deps
15322
15424
  );
15323
15425
  const dependsOnTokensCreate = normalizeCliStringList(o.dependsOn);
15324
- const dependsOnNormCreate = normalizeTicketDependsOnIds(
15325
- dependsOnTokensCreate
15326
- );
15327
15426
  const planningPayload = {
15328
15427
  ...labelsPayloadCreate,
15329
15428
  ...priorityParsed !== void 0 ? { priority: priorityParsed } : {},
@@ -15337,16 +15436,21 @@ var runCli = async (argv, deps = {
15337
15436
  const proj = replayEvents(lines);
15338
15437
  const storyRaw = o.story;
15339
15438
  const storyTrimmed = storyRaw !== void 0 && storyRaw !== "" ? storyRaw.trim() : void 0;
15439
+ const storyIdResolved = storyTrimmed !== void 0 ? resolveStoryId(proj, storyTrimmed) ?? storyTrimmed : void 0;
15340
15440
  if (storyTrimmed !== void 0) {
15341
- const storyRow = proj.stories.get(storyTrimmed);
15441
+ const storyRow = proj.stories.get(storyIdResolved ?? "");
15342
15442
  if (!storyRow || storyRow.deleted) {
15343
15443
  throw new Error(`Story not found: ${storyTrimmed}`);
15344
15444
  }
15345
15445
  }
15346
15446
  const id = o.id ?? ulid();
15447
+ const dependsOnNormCreate = resolveTicketDependsOnTokensToIds(
15448
+ proj,
15449
+ dependsOnTokensCreate
15450
+ );
15347
15451
  const status = parseCliWorkItemStatus(o.status, deps);
15348
15452
  const assigneeCreate = o.assignee !== void 0 ? { assignee: normalizeGithubLogin(o.assignee) } : {};
15349
- const storyPayload = storyTrimmed !== void 0 ? { storyId: storyTrimmed } : {};
15453
+ const storyPayload = storyIdResolved !== void 0 ? { storyId: storyIdResolved } : {};
15350
15454
  const branchTokens = normalizeCliStringList(o.branch);
15351
15455
  const branchesNorm = normalizeTicketBranchListFromStrings(branchTokens);
15352
15456
  const branchesPayload = branchesNorm.length > 0 ? { branches: branchesNorm } : {};
@@ -15359,19 +15463,26 @@ var runCli = async (argv, deps = {
15359
15463
  throw new Error(dependsOnErr);
15360
15464
  }
15361
15465
  const dependsOnPayloadCreate = dependsOnNormCreate.length > 0 ? { dependsOn: dependsOnNormCreate } : {};
15466
+ const createPayload = {
15467
+ id,
15468
+ number: nextTicketNumberForCreate(proj),
15469
+ ...storyPayload,
15470
+ title: o.title,
15471
+ body,
15472
+ ...status !== void 0 ? { status } : {},
15473
+ ...assigneeCreate,
15474
+ ...branchesPayload,
15475
+ ...dependsOnPayloadCreate,
15476
+ ...planningPayload
15477
+ };
15478
+ assertCreatePayloadUsesExpectedHeadNumber(
15479
+ proj,
15480
+ "ticket",
15481
+ createPayload
15482
+ );
15362
15483
  const evt = makeEvent(
15363
15484
  "TicketCreated",
15364
- {
15365
- id,
15366
- ...storyPayload,
15367
- title: o.title,
15368
- body,
15369
- ...status !== void 0 ? { status } : {},
15370
- ...assigneeCreate,
15371
- ...branchesPayload,
15372
- ...dependsOnPayloadCreate,
15373
- ...planningPayload
15374
- },
15485
+ createPayload,
15375
15486
  deps.clock,
15376
15487
  actor
15377
15488
  );
@@ -15643,25 +15754,27 @@ ${body}`
15643
15754
  await mutateDataBranch(g, deps, async (root, { actor }) => {
15644
15755
  const lines = await readAllEventLines(root);
15645
15756
  const proj = replayEvents(lines);
15757
+ const ticketId = resolveTicketId(proj, o.id) ?? o.id.trim();
15758
+ const storyIdResolved = storyTrimmed !== void 0 ? resolveStoryId(proj, storyTrimmed) ?? storyTrimmed : void 0;
15646
15759
  if (storyTrimmed !== void 0) {
15647
- const storyRow = proj.stories.get(storyTrimmed);
15760
+ const storyRow = proj.stories.get(storyIdResolved ?? "");
15648
15761
  if (!storyRow || storyRow.deleted) {
15649
15762
  throw new Error(`Story not found: ${storyTrimmed}`);
15650
15763
  }
15651
15764
  }
15652
- const curTicket = proj.tickets.get(o.id);
15765
+ const curTicket = proj.tickets.get(ticketId);
15653
15766
  if (curTicket === void 0 || curTicket.deleted) {
15654
15767
  throw new Error(`Ticket not found: ${o.id}`);
15655
15768
  }
15656
15769
  const status = parseCliWorkItemStatus(o.status, deps);
15657
- const payload = { id: o.id };
15770
+ const payload = { id: ticketId };
15658
15771
  if (o.title !== void 0) payload["title"] = o.title;
15659
15772
  if (body !== void 0) payload["body"] = body;
15660
15773
  if (status !== void 0) payload["status"] = status;
15661
15774
  if (o.unlinkStory) {
15662
15775
  payload["storyId"] = null;
15663
15776
  } else if (storyTrimmed !== void 0) {
15664
- payload["storyId"] = storyTrimmed;
15777
+ payload["storyId"] = storyIdResolved;
15665
15778
  }
15666
15779
  if (o.unassign) {
15667
15780
  payload["assignee"] = null;
@@ -15718,19 +15831,19 @@ ${body}`
15718
15831
  nextDepends = [];
15719
15832
  } else {
15720
15833
  const removeDepSet = new Set(
15721
- normalizeTicketDependsOnIds(removeDependsOnTokens)
15834
+ resolveTicketDependsOnTokensToIds(proj, removeDependsOnTokens)
15722
15835
  );
15723
15836
  nextDepends = normalizeTicketDependsOnIds(
15724
15837
  (curTicket.dependsOn ?? []).filter((d) => !removeDepSet.has(d))
15725
15838
  );
15726
15839
  nextDepends = normalizeTicketDependsOnIds([
15727
15840
  ...nextDepends,
15728
- ...addDependsOnTokens
15841
+ ...resolveTicketDependsOnTokensToIds(proj, addDependsOnTokens)
15729
15842
  ]);
15730
15843
  }
15731
15844
  const depErr = validateTicketDependsOnForWrite({
15732
15845
  projection: proj,
15733
- fromTicketId: o.id,
15846
+ fromTicketId: ticketId,
15734
15847
  nextDependsOn: nextDepends
15735
15848
  });
15736
15849
  if (depErr !== void 0) {
@@ -15760,7 +15873,7 @@ ${body}`
15760
15873
  payload
15761
15874
  );
15762
15875
  if (isNoOpUpdatePayload(prunedPayload)) {
15763
- return { id: o.id, noChanges: true };
15876
+ return { id: ticketId, noChanges: true };
15764
15877
  }
15765
15878
  const evt = makeEvent(
15766
15879
  "TicketUpdated",
@@ -15797,25 +15910,25 @@ ${body}`
15797
15910
  runGit
15798
15911
  );
15799
15912
  }
15800
- const preferred = normalizeTicketBranchName(
15801
- o.branch ?? `hyper-pm/${o.id}`
15802
- );
15803
- if (preferred === void 0) {
15804
- deps.error(
15805
- "Invalid --branch or default branch name for this ticket id"
15806
- );
15807
- deps.exit(ExitCode.UserError);
15808
- }
15809
15913
  await mutateDataBranch(g, deps, async (root, { actor }) => {
15810
15914
  const lines = await readAllEventLines(root);
15811
15915
  const proj = replayEvents(lines);
15812
- const curRow = proj.tickets.get(o.id);
15916
+ const ticketId = resolveTicketId(proj, o.id) ?? o.id.trim();
15917
+ const preferred = normalizeTicketBranchName(
15918
+ o.branch ?? `hyper-pm/${ticketId}`
15919
+ );
15920
+ if (preferred === void 0) {
15921
+ throw new Error(
15922
+ "Invalid --branch or default branch name for this ticket id"
15923
+ );
15924
+ }
15925
+ const curRow = proj.tickets.get(ticketId);
15813
15926
  if (curRow === void 0 || curRow.deleted) {
15814
15927
  throw new Error(`Ticket not found: ${o.id}`);
15815
15928
  }
15816
15929
  if (curRow.status === "done" || curRow.status === "cancelled") {
15817
15930
  throw new Error(
15818
- `Ticket ${o.id} is ${curRow.status}; change status before starting work`
15931
+ `Ticket ${ticketId} is ${curRow.status}; change status before starting work`
15819
15932
  );
15820
15933
  }
15821
15934
  const { branch: chosenBranch } = await pickUniqueLocalBranchName({
@@ -15836,7 +15949,7 @@ ${body}`
15836
15949
  }
15837
15950
  next = normalizeTicketBranchListFromStrings(next);
15838
15951
  const payload = {
15839
- id: o.id,
15952
+ id: ticketId,
15840
15953
  status: "in_progress"
15841
15954
  };
15842
15955
  if (!ticketBranchListsEqual(next, curRow.linkedBranches)) {
@@ -15845,7 +15958,7 @@ ${body}`
15845
15958
  const evt = makeEvent("TicketUpdated", payload, deps.clock, actor);
15846
15959
  await appendEventLine(root, evt, deps.clock);
15847
15960
  const result = {
15848
- id: o.id,
15961
+ id: ticketId,
15849
15962
  status: "in_progress",
15850
15963
  branch: chosenBranch,
15851
15964
  branches: next
@@ -15867,27 +15980,36 @@ ${body}`
15867
15980
  await mutateDataBranch(g, deps, async (root, { actor }) => {
15868
15981
  const lines = await readAllEventLines(root);
15869
15982
  const proj = replayEvents(lines);
15870
- const row = proj.tickets.get(o.id);
15983
+ const ticketId = resolveTicketId(proj, o.id) ?? o.id.trim();
15984
+ const row = proj.tickets.get(ticketId);
15871
15985
  if (!row || row.deleted) {
15872
15986
  throw new Error(`Ticket not found: ${o.id}`);
15873
15987
  }
15874
15988
  const evt = makeEvent(
15875
15989
  "TicketCommentAdded",
15876
- { ticketId: o.id, body: trimmed },
15990
+ { ticketId, body: trimmed },
15877
15991
  deps.clock,
15878
15992
  actor
15879
15993
  );
15880
15994
  await appendEventLine(root, evt, deps.clock);
15881
- return { commentId: evt.id, ticketId: o.id, body: trimmed };
15995
+ return { commentId: evt.id, ticketId, body: trimmed };
15882
15996
  });
15883
15997
  });
15884
15998
  ticket.command("delete").requiredOption("--id <id>").action(async function() {
15885
15999
  const g = readGlobals(this);
15886
16000
  const o = this.opts();
15887
16001
  await mutateDataBranch(g, deps, async (root, { actor }) => {
15888
- const evt = makeEvent("TicketDeleted", { id: o.id }, deps.clock, actor);
16002
+ const lines = await readAllEventLines(root);
16003
+ const proj = replayEvents(lines);
16004
+ const ticketId = resolveTicketId(proj, o.id) ?? o.id.trim();
16005
+ const evt = makeEvent(
16006
+ "TicketDeleted",
16007
+ { id: ticketId },
16008
+ deps.clock,
16009
+ actor
16010
+ );
15889
16011
  await appendEventLine(root, evt, deps.clock);
15890
- return { id: o.id, deleted: true };
16012
+ return { id: ticketId, deleted: true };
15891
16013
  });
15892
16014
  });
15893
16015
  ticket.command("import-github").description(
@@ -15958,7 +16080,8 @@ ${body}`
15958
16080
  const storyRaw = o.story;
15959
16081
  const storyTrimmed = storyRaw !== void 0 && storyRaw !== "" ? storyRaw.trim() : void 0;
15960
16082
  if (storyTrimmed !== void 0 && storyTrimmed !== "") {
15961
- const storyRow = proj.stories.get(storyTrimmed);
16083
+ const storyIdForImport = resolveStoryId(proj, storyTrimmed) ?? storyTrimmed;
16084
+ const storyRow = proj.stories.get(storyIdForImport);
15962
16085
  if (!storyRow || storyRow.deleted) {
15963
16086
  deps.error(`Story not found: ${storyTrimmed}`);
15964
16087
  deps.exit(ExitCode.UserError);
@@ -16001,12 +16124,22 @@ ${body}`
16001
16124
  );
16002
16125
  } else {
16003
16126
  const imported = [];
16127
+ let importProj = proj;
16128
+ let importLines = lines;
16129
+ const storyIdForPayload = storyTrimmed !== void 0 && storyTrimmed !== "" ? resolveStoryId(importProj, storyTrimmed) ?? storyTrimmed : void 0;
16004
16130
  for (const c of candidates) {
16005
16131
  const ticketId = ulid();
16132
+ const nextNum = nextTicketNumberForCreate(importProj);
16006
16133
  const createPayload = mergeTicketImportCreatePayload(
16007
16134
  ticketId,
16008
16135
  c.ticketCreatedPayloadBase,
16009
- storyTrimmed
16136
+ storyIdForPayload,
16137
+ nextNum
16138
+ );
16139
+ assertCreatePayloadUsesExpectedHeadNumber(
16140
+ importProj,
16141
+ "ticket",
16142
+ createPayload
16010
16143
  );
16011
16144
  const createdEvt = makeEvent(
16012
16145
  "TicketCreated",
@@ -16027,6 +16160,8 @@ ${body}`
16027
16160
  );
16028
16161
  await appendEventLine(session.worktreePath, linkEvt, deps.clock);
16029
16162
  imported.push({ ticketId, issueNumber: c.issueNumber });
16163
+ importLines = await readAllEventLines(session.worktreePath);
16164
+ importProj = replayEvents(importLines);
16030
16165
  }
16031
16166
  await commitDataWorktreeIfNeeded(
16032
16167
  session.worktreePath,
@@ -16494,7 +16629,8 @@ var readEpic = async (g, id, deps) => {
16494
16629
  formatOutput(g.format, { items: listActiveEpicSummaries(proj) })
16495
16630
  );
16496
16631
  } else {
16497
- const row = proj.epics.get(id);
16632
+ const epicId = resolveEpicId(proj, id) ?? id.trim();
16633
+ const row = proj.epics.get(epicId);
16498
16634
  if (!row || row.deleted) {
16499
16635
  deps.error("Epic not found");
16500
16636
  exitCode = ExitCode.UserError;
@@ -16529,7 +16665,8 @@ var readStory = async (g, opts, deps) => {
16529
16665
  const proj = replayEvents(lines);
16530
16666
  const { id, epicId } = opts;
16531
16667
  if (id === void 0 || id === "") {
16532
- const epicFilter = epicId !== void 0 && epicId !== "" ? epicId : void 0;
16668
+ const epicFilterRaw = epicId !== void 0 && epicId !== "" ? epicId : void 0;
16669
+ const epicFilter = epicFilterRaw !== void 0 ? resolveEpicId(proj, epicFilterRaw) ?? epicFilterRaw.trim() : void 0;
16533
16670
  if (epicFilter !== void 0) {
16534
16671
  const epicRow = proj.epics.get(epicFilter);
16535
16672
  if (!epicRow || epicRow.deleted) {
@@ -16550,7 +16687,8 @@ var readStory = async (g, opts, deps) => {
16550
16687
  );
16551
16688
  }
16552
16689
  } else {
16553
- const row = proj.stories.get(id);
16690
+ const storyId = resolveStoryId(proj, id) ?? id.trim();
16691
+ const row = proj.stories.get(storyId);
16554
16692
  if (!row || row.deleted) {
16555
16693
  deps.error("Story not found");
16556
16694
  exitCode = ExitCode.UserError;
@@ -16594,8 +16732,8 @@ var readTicket = async (g, opts, deps) => {
16594
16732
  } = opts;
16595
16733
  if (id === void 0 || id === "") {
16596
16734
  const listWithoutStory = withoutStoryRaw === true;
16597
- const storyFilter = storyIdRaw !== void 0 && storyIdRaw !== "" ? storyIdRaw : void 0;
16598
- const epicFilter = epicIdRaw !== void 0 && epicIdRaw !== "" ? epicIdRaw : void 0;
16735
+ const storyFilter = storyIdRaw !== void 0 && storyIdRaw !== "" ? resolveStoryId(proj, storyIdRaw) ?? storyIdRaw.trim() : void 0;
16736
+ const epicFilter = epicIdRaw !== void 0 && epicIdRaw !== "" ? resolveEpicId(proj, epicIdRaw) ?? epicIdRaw.trim() : void 0;
16599
16737
  const sortBy = tryParseTicketListSortField(sortByOpt);
16600
16738
  const sortDir = tryParseTicketListSortDir(sortDirOpt);
16601
16739
  if (sortBy === void 0) {
@@ -16634,6 +16772,7 @@ var readTicket = async (g, opts, deps) => {
16634
16772
  exitCode = ExitCode.UserError;
16635
16773
  } else {
16636
16774
  const listQuery = buildTicketListQueryFromReadListOpts(
16775
+ proj,
16637
16776
  { epic: epicFilter, ...listFlagRest },
16638
16777
  deps
16639
16778
  );
@@ -16654,6 +16793,7 @@ var readTicket = async (g, opts, deps) => {
16654
16793
  exitCode = ExitCode.UserError;
16655
16794
  } else {
16656
16795
  const listQuery = buildTicketListQueryFromReadListOpts(
16796
+ proj,
16657
16797
  listFlagRest,
16658
16798
  deps
16659
16799
  );
@@ -16670,6 +16810,7 @@ var readTicket = async (g, opts, deps) => {
16670
16810
  }
16671
16811
  } else if (listWithoutStory) {
16672
16812
  const listQuery = buildTicketListQueryFromReadListOpts(
16813
+ proj,
16673
16814
  { withoutStory: true, ...listFlagRest },
16674
16815
  deps
16675
16816
  );
@@ -16684,6 +16825,7 @@ var readTicket = async (g, opts, deps) => {
16684
16825
  );
16685
16826
  } else {
16686
16827
  const listQuery = buildTicketListQueryFromReadListOpts(
16828
+ proj,
16687
16829
  { epic: epicFilter, ...listFlagRest },
16688
16830
  deps
16689
16831
  );
@@ -16698,7 +16840,8 @@ var readTicket = async (g, opts, deps) => {
16698
16840
  );
16699
16841
  }
16700
16842
  } else {
16701
- const row = proj.tickets.get(id);
16843
+ const ticketId = resolveTicketId(proj, id) ?? id.trim();
16844
+ const row = proj.tickets.get(ticketId);
16702
16845
  if (!row || row.deleted) {
16703
16846
  deps.error("Ticket not found");
16704
16847
  exitCode = ExitCode.UserError;