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/main.cjs CHANGED
@@ -12772,6 +12772,7 @@ var sortTicketRecordsForList = (tickets, field, dir) => [...tickets].sort((x, y)
12772
12772
  // src/cli/list-projection-summaries.ts
12773
12773
  var listActiveEpicSummaries = (projection) => [...projection.epics.values()].filter((e) => !e.deleted).map((e) => ({
12774
12774
  id: e.id,
12775
+ number: e.number,
12775
12776
  title: e.title,
12776
12777
  status: e.status,
12777
12778
  createdAt: e.createdAt,
@@ -12783,6 +12784,7 @@ var listActiveStorySummaries = (projection, options) => [...projection.stories.v
12783
12784
  (s) => !s.deleted && (options?.epicId === void 0 || s.epicId === options.epicId)
12784
12785
  ).map((s) => ({
12785
12786
  id: s.id,
12787
+ number: s.number,
12786
12788
  title: s.title,
12787
12789
  epicId: s.epicId,
12788
12790
  status: s.status,
@@ -12815,6 +12817,7 @@ var listActiveTicketSummaries = (projection, options) => {
12815
12817
  const last = recent !== void 0 && recent.length > 0 ? recent[recent.length - 1] : void 0;
12816
12818
  return {
12817
12819
  id: t.id,
12820
+ number: t.number,
12818
12821
  title: t.title,
12819
12822
  status: t.status,
12820
12823
  storyId: t.storyId,
@@ -13183,1137 +13186,1222 @@ var partitionGithubIssuesForImport = (params) => {
13183
13186
  }
13184
13187
  return { candidates, skipped };
13185
13188
  };
13186
- var mergeTicketImportCreatePayload = (ticketId, base, storyId) => {
13189
+ var mergeTicketImportCreatePayload = (ticketId, base, storyId, number) => {
13187
13190
  const storyTrimmed = storyId !== void 0 && storyId !== "" ? storyId.trim() : void 0;
13188
13191
  const storyPayload = storyTrimmed !== void 0 && storyTrimmed !== "" ? { storyId: storyTrimmed } : {};
13189
- return { id: ticketId, ...base, ...storyPayload };
13192
+ return { id: ticketId, number, ...base, ...storyPayload };
13190
13193
  };
13191
13194
 
13192
- // src/config/hyper-pm-config.ts
13193
- var hyperPmConfigSchema = external_exports.object({
13194
- schema: external_exports.literal(1),
13195
- dataBranch: external_exports.string().min(1),
13196
- remote: external_exports.string().min(1).default("origin"),
13197
- sync: external_exports.enum(["off", "outbound", "full"]).default("outbound"),
13198
- githubRepo: external_exports.string().optional(),
13199
- issueMapping: external_exports.enum(["ticket", "story", "epic"]).default("ticket")
13200
- });
13201
-
13202
- // src/config/load-config.ts
13203
- var import_promises = require("node:fs/promises");
13204
- var import_node_path = require("node:path");
13205
- var CONFIG_DIR = ".hyper-pm";
13206
- var CONFIG_FILE = "config.json";
13207
- var getHyperPmConfigPath = (repoRoot) => {
13208
- return (0, import_node_path.join)(repoRoot, CONFIG_DIR, CONFIG_FILE);
13209
- };
13210
- var loadHyperPmConfig = async (repoRoot, overrides = {}) => {
13211
- const raw = await (0, import_promises.readFile)(getHyperPmConfigPath(repoRoot), "utf8");
13212
- const parsed = JSON.parse(raw);
13213
- const base = hyperPmConfigSchema.parse(parsed);
13214
- const merged = { ...base, ...stripUndefined(overrides) };
13215
- return hyperPmConfigSchema.parse(merged);
13216
- };
13217
- var stripUndefined = (obj) => {
13218
- const out = {};
13219
- for (const [k, v] of Object.entries(obj)) {
13220
- if (v !== void 0) out[k] = v;
13195
+ // src/lib/github-pr-activity.ts
13196
+ var GITHUB_PR_ACTIVITY_RECENT_CAP = 20;
13197
+ var githubPrActivityKindSchema = external_exports.enum([
13198
+ "opened",
13199
+ "updated",
13200
+ "commented",
13201
+ "reviewed",
13202
+ "merged",
13203
+ "closed",
13204
+ "ready_for_review"
13205
+ ]);
13206
+ var githubPrReviewStateSchema = external_exports.enum([
13207
+ "approved",
13208
+ "changes_requested",
13209
+ "commented"
13210
+ ]);
13211
+ var buildPrOpenSourceId = (ticketId, prNumber) => `hyper-pm:pr-open:${ticketId}:${prNumber}`;
13212
+ var parseGithubPrActivityPayload = (payload) => {
13213
+ const ticketId = payload["ticketId"];
13214
+ const prRaw = payload["prNumber"];
13215
+ const kindRaw = payload["kind"];
13216
+ const occurredAt = payload["occurredAt"];
13217
+ const sourceId = payload["sourceId"];
13218
+ if (typeof ticketId !== "string" || typeof occurredAt !== "string" || typeof sourceId !== "string") {
13219
+ return void 0;
13220
+ }
13221
+ const prNumber = typeof prRaw === "number" ? prRaw : Number(prRaw);
13222
+ if (!Number.isFinite(prNumber)) return void 0;
13223
+ const kindParsed = githubPrActivityKindSchema.safeParse(kindRaw);
13224
+ if (!kindParsed.success) return void 0;
13225
+ const out = {
13226
+ prNumber,
13227
+ kind: kindParsed.data,
13228
+ occurredAt,
13229
+ sourceId
13230
+ };
13231
+ const url = payload["url"];
13232
+ if (typeof url === "string" && url.length > 0) {
13233
+ out.url = url;
13234
+ }
13235
+ const rs = githubPrReviewStateSchema.safeParse(payload["reviewState"]);
13236
+ if (rs.success) {
13237
+ out.reviewState = rs.data;
13221
13238
  }
13222
13239
  return out;
13223
13240
  };
13224
13241
 
13225
- // src/config/save-config.ts
13226
- var import_promises2 = require("node:fs/promises");
13227
- var import_node_path2 = require("node:path");
13228
- var saveHyperPmConfig = async (repoRoot, config) => {
13229
- const target = getHyperPmConfigPath(repoRoot);
13230
- await (0, import_promises2.mkdir)((0, import_node_path2.dirname)(target), { recursive: true });
13231
- await (0, import_promises2.writeFile)(target, `${JSON.stringify(config, null, 2)}
13232
- `, "utf8");
13242
+ // src/storage/projection.ts
13243
+ var readOptionalPositiveIntegerFromPayload = (payload, key) => {
13244
+ if (!Object.prototype.hasOwnProperty.call(payload, key)) return void 0;
13245
+ const raw = payload[key];
13246
+ if (typeof raw !== "number" || !Number.isFinite(raw)) return void 0;
13247
+ if (!Number.isInteger(raw)) return void 0;
13248
+ if (raw < 1 || raw > Number.MAX_SAFE_INTEGER) return void 0;
13249
+ return raw;
13250
+ };
13251
+ var maxNumberAmongRows = (rows) => {
13252
+ let m = 0;
13253
+ for (const r of rows) {
13254
+ if (r.number > m) m = r.number;
13255
+ }
13256
+ return m;
13257
+ };
13258
+ var maxEpicNumberInProjection = (projection) => maxNumberAmongRows(projection.epics.values());
13259
+ var maxStoryNumberInProjection = (projection) => maxNumberAmongRows(projection.stories.values());
13260
+ var maxTicketNumberInProjection = (projection) => maxNumberAmongRows(projection.tickets.values());
13261
+ var nextEpicNumberForCreate = (projection) => maxEpicNumberInProjection(projection) + 1;
13262
+ var nextStoryNumberForCreate = (projection) => maxStoryNumberInProjection(projection) + 1;
13263
+ var nextTicketNumberForCreate = (projection) => maxTicketNumberInProjection(projection) + 1;
13264
+ var resolveWorkItemCreateNumber = (projection, kind, payload) => {
13265
+ const explicit = readOptionalPositiveIntegerFromPayload(payload, "number");
13266
+ if (explicit !== void 0) {
13267
+ return explicit;
13268
+ }
13269
+ return (kind === "epic" ? maxEpicNumberInProjection(projection) : kind === "story" ? maxStoryNumberInProjection(projection) : maxTicketNumberInProjection(projection)) + 1;
13233
13270
  };
13234
-
13235
- // src/doctor/run-doctor.ts
13236
- var runDoctorOnLines = (lines) => {
13237
- const issues = [];
13238
- for (let i = 0; i < lines.length; i++) {
13239
- const line = lines[i]?.trim() ?? "";
13240
- if (!line) continue;
13241
- let json;
13242
- try {
13243
- json = JSON.parse(line);
13244
- } catch (e) {
13245
- issues.push({
13246
- kind: "invalid-json",
13247
- line: i + 1,
13248
- message: e instanceof Error ? e.message : "parse error"
13249
- });
13250
- return issues;
13251
- }
13252
- const parsed = eventLineSchema.safeParse(json);
13253
- if (!parsed.success) {
13254
- issues.push({
13255
- kind: "invalid-event",
13256
- line: i + 1,
13257
- message: parsed.error.message
13258
- });
13259
- return issues;
13260
- }
13271
+ var emptyProjection = () => ({
13272
+ epics: /* @__PURE__ */ new Map(),
13273
+ stories: /* @__PURE__ */ new Map(),
13274
+ tickets: /* @__PURE__ */ new Map()
13275
+ });
13276
+ var parsePrRefs = (body) => {
13277
+ const out = /* @__PURE__ */ new Set();
13278
+ const re = /\b(?:Closes|Refs|Fixes)\s+#(\d+)\b/gi;
13279
+ let m = re.exec(body);
13280
+ while (m !== null) {
13281
+ out.add(Number(m[1]));
13282
+ m = re.exec(body);
13261
13283
  }
13262
- return issues;
13263
- };
13264
-
13265
- // src/git/create-and-checkout-branch.ts
13266
- var createAndCheckoutBranch = async (opts) => {
13267
- await opts.runGit(opts.repoRoot, [
13268
- "switch",
13269
- "-c",
13270
- opts.branchName,
13271
- opts.startPoint
13272
- ]);
13273
- };
13274
-
13275
- // src/git/data-worktree-session.ts
13276
- var import_promises3 = require("node:fs/promises");
13277
- var import_node_path3 = require("node:path");
13278
- var ensureDir = async (path) => {
13279
- await (0, import_promises3.mkdir)(path, { recursive: true });
13284
+ return [...out];
13280
13285
  };
13281
- var defaultPathExists = async (path) => {
13282
- try {
13283
- await (0, import_promises3.access)(path);
13284
- return true;
13285
- } catch {
13286
- return false;
13286
+ var applyTicketAssigneeFromPayload = (row, payload) => {
13287
+ if (!Object.prototype.hasOwnProperty.call(payload, "assignee")) return;
13288
+ const v = payload["assignee"];
13289
+ if (v === null) {
13290
+ delete row.assignee;
13291
+ return;
13287
13292
  }
13293
+ if (typeof v !== "string") return;
13294
+ const n = normalizeGithubLogin(v);
13295
+ if (n === "") delete row.assignee;
13296
+ else row.assignee = n;
13288
13297
  };
13289
- var parseDataBranchWorktreeFromPorcelain = (stdout, dataBranch) => {
13290
- const wantRef = `refs/heads/${dataBranch}`;
13291
- const blocks = stdout.split(/\n\n+/).map((b) => b.trim()).filter(Boolean);
13292
- for (const block of blocks) {
13293
- const lines = block.split("\n");
13294
- let worktreePath;
13295
- let branch;
13296
- for (const line of lines) {
13297
- if (line.startsWith("worktree ")) {
13298
- worktreePath = line.slice("worktree ".length);
13299
- } else if (line.startsWith("branch ")) {
13300
- branch = line.slice("branch ".length);
13301
- }
13302
- }
13303
- if (worktreePath !== void 0 && branch === wantRef) {
13304
- return worktreePath;
13305
- }
13298
+ var storyIdFromTicketCreatedPayload = (payload) => {
13299
+ if (!Object.prototype.hasOwnProperty.call(payload, "storyId")) {
13300
+ return null;
13306
13301
  }
13307
- return void 0;
13302
+ const v = payload["storyId"];
13303
+ if (v === null) return null;
13304
+ if (typeof v !== "string") return null;
13305
+ const t = v.trim();
13306
+ return t === "" ? null : t;
13308
13307
  };
13309
- var openDataBranchWorktree = async (opts) => {
13310
- const pathExists = opts.pathExists ?? defaultPathExists;
13311
- const { stdout: listOut } = await opts.runGit(opts.repoRoot, [
13312
- "worktree",
13313
- "list",
13314
- "--porcelain"
13315
- ]);
13316
- const listedPath = parseDataBranchWorktreeFromPorcelain(
13317
- listOut,
13318
- opts.dataBranch
13319
- );
13320
- if (listedPath !== void 0 && await pathExists(listedPath)) {
13321
- const dispose2 = async () => {
13322
- return;
13323
- };
13324
- return { worktreePath: listedPath, dispose: dispose2 };
13308
+ var applyTicketStoryIdFromPayload = (row, payload) => {
13309
+ if (!Object.prototype.hasOwnProperty.call(payload, "storyId")) return;
13310
+ const v = payload["storyId"];
13311
+ if (v === null) {
13312
+ row.storyId = null;
13313
+ return;
13325
13314
  }
13326
- const worktreePath = (0, import_node_path3.join)(
13327
- opts.tmpBase,
13328
- `hyper-pm-worktree-${ulid().toLowerCase()}`
13329
- );
13330
- await ensureDir(opts.tmpBase);
13331
- try {
13332
- await opts.runGit(opts.repoRoot, [
13333
- "worktree",
13334
- "add",
13335
- worktreePath,
13336
- opts.dataBranch
13337
- ]);
13338
- } catch (err) {
13339
- await (0, import_promises3.rm)(worktreePath, { recursive: true, force: true }).catch(() => {
13340
- });
13341
- throw err;
13315
+ if (typeof v !== "string") return;
13316
+ const t = v.trim();
13317
+ row.storyId = t === "" ? null : t;
13318
+ };
13319
+ var linkedBranchesFromTicketCreatedPayload = (payload) => {
13320
+ if (!Object.prototype.hasOwnProperty.call(payload, "branches")) {
13321
+ return [];
13342
13322
  }
13343
- const dispose = async () => {
13344
- if (opts.keepWorktree) {
13345
- return;
13346
- }
13347
- await opts.runGit(opts.repoRoot, ["worktree", "remove", "--force", worktreePath]).catch(() => {
13348
- });
13349
- await (0, import_promises3.rm)(worktreePath, { recursive: true, force: true }).catch(() => {
13350
- });
13351
- };
13352
- return { worktreePath, dispose };
13323
+ return normalizeTicketBranchListFromPayloadValue(payload["branches"]);
13353
13324
  };
13354
-
13355
- // src/git/find-git-root.ts
13356
- var findGitRoot = async (cwd, deps) => {
13357
- const { stdout } = await deps.runGit(cwd, ["rev-parse", "--show-toplevel"]);
13358
- return stdout;
13325
+ var applyTicketBranchesFromUpdatePayload = (row, payload) => {
13326
+ if (!Object.prototype.hasOwnProperty.call(payload, "branches")) return;
13327
+ const v = payload["branches"];
13328
+ if (!Array.isArray(v)) return;
13329
+ row.linkedBranches = normalizeTicketBranchListFromPayloadValue(v);
13359
13330
  };
13360
-
13361
- // src/git/init-orphan-data-branch.ts
13362
- var import_promises4 = require("node:fs/promises");
13363
- var import_node_path4 = require("node:path");
13364
- var initOrphanDataBranchInWorktree = async (opts) => {
13365
- const { stdout: tip } = await opts.runGit(opts.repoRoot, [
13366
- "rev-parse",
13367
- "HEAD"
13368
- ]);
13369
- const tipCommit = tip.trim();
13370
- await opts.runGit(opts.repoRoot, [
13371
- "worktree",
13372
- "add",
13373
- opts.worktreePath,
13374
- tipCommit
13375
- ]);
13376
- await opts.runGit(opts.worktreePath, [
13377
- "checkout",
13378
- "--orphan",
13379
- opts.dataBranch
13380
- ]);
13381
- await opts.runGit(opts.worktreePath, ["rm", "-rf", "."]).catch(() => {
13382
- });
13383
- const marker = (0, import_node_path4.join)(opts.worktreePath, "README.hyper-pm.md");
13384
- await (0, import_promises4.writeFile)(
13385
- marker,
13386
- "# hyper-pm data branch\n\nAppend-only events live under `events/`.\n",
13387
- "utf8"
13388
- );
13389
- await opts.runGit(opts.worktreePath, ["add", "."]);
13390
- await opts.runGit(opts.worktreePath, [
13391
- "commit",
13392
- "-m",
13393
- "init hyper-pm data branch"
13394
- ]);
13395
- };
13396
-
13397
- // src/git/pick-unique-local-branch-name.ts
13398
- var DEFAULT_MAX_SUFFIX = 1e3;
13399
- var localBranchRefExists = async (repoRoot, name, git) => {
13400
- try {
13401
- await git(repoRoot, ["show-ref", "--verify", `refs/heads/${name}`]);
13402
- return true;
13403
- } catch {
13404
- return false;
13405
- }
13406
- };
13407
- var pickUniqueLocalBranchName = async (opts) => {
13408
- const max = opts.maxSuffix ?? DEFAULT_MAX_SUFFIX;
13409
- const { preferredBase, repoRoot, runGit: git } = opts;
13410
- for (let n = 1; n <= max; n += 1) {
13411
- const raw = n === 1 ? preferredBase : `${preferredBase}-${n}`;
13412
- const norm = normalizeTicketBranchName(raw);
13413
- if (norm === void 0) {
13414
- continue;
13415
- }
13416
- if (!await localBranchRefExists(repoRoot, norm, git)) {
13417
- return { branch: norm, preferred: preferredBase };
13331
+ var applyTicketPlanningFieldsFromCreatePayload = (row, payload) => {
13332
+ if (Object.prototype.hasOwnProperty.call(payload, "labels")) {
13333
+ const v = ticketLabelsFromPayloadValue(payload["labels"]);
13334
+ if (v !== void 0 && v.length > 0) {
13335
+ row.labels = v;
13418
13336
  }
13419
13337
  }
13420
- throw new Error(
13421
- `Could not allocate a free local branch name from ${JSON.stringify(preferredBase)} (tried suffixes up to -${String(max)})`
13422
- );
13423
- };
13424
-
13425
- // src/git/resolve-integration-start-point.ts
13426
- var assertGitRefResolvable = async (repoRoot, ref, git) => {
13427
- try {
13428
- await git(repoRoot, ["rev-parse", "-q", "--verify", ref]);
13429
- } catch {
13430
- throw new Error(`Invalid or ambiguous --from ref: ${ref}`);
13338
+ const pr = readTicketPriorityPatch(payload);
13339
+ if (typeof pr === "string") {
13340
+ row.priority = pr;
13431
13341
  }
13432
- };
13433
- var resolveIntegrationStartPoint = async (repoRoot, remote, git) => {
13434
- const symRef = `refs/remotes/${remote}/HEAD`;
13435
- try {
13436
- const { stdout } = await git(repoRoot, ["symbolic-ref", "-q", symRef]);
13437
- const target = stdout.trim();
13438
- if (target !== "") {
13439
- await git(repoRoot, ["rev-parse", "-q", "--verify", target]);
13440
- return target;
13441
- }
13442
- } catch {
13342
+ const sz = readTicketSizePatch(payload);
13343
+ if (typeof sz === "string") {
13344
+ row.size = sz;
13443
13345
  }
13444
- for (const head of ["refs/heads/main", "refs/heads/master"]) {
13445
- try {
13446
- await git(repoRoot, ["rev-parse", "-q", "--verify", head]);
13447
- return head;
13448
- } catch {
13449
- }
13346
+ const est = readTicketEstimatePatch(payload);
13347
+ if (typeof est === "number") {
13348
+ row.estimate = est;
13450
13349
  }
13451
- try {
13452
- await git(repoRoot, ["rev-parse", "-q", "--verify", "HEAD"]);
13453
- return "HEAD";
13454
- } catch {
13350
+ const sw = readTicketIsoInstantPatch(payload, "startWorkAt");
13351
+ if (typeof sw === "string") {
13352
+ row.startWorkAt = sw;
13455
13353
  }
13456
- throw new Error(
13457
- "Could not resolve a default branch to branch from; pass --from <ref> (e.g. main or origin/main)."
13458
- );
13459
- };
13460
-
13461
- // src/git/list-repo-commit-authors.ts
13462
- var listRepoCommitAuthors = async (repoRoot, git) => {
13463
- try {
13464
- const { stdout } = await git(repoRoot, [
13465
- "-c",
13466
- "log.showSignature=false",
13467
- "log",
13468
- "--all",
13469
- "--format=%an%x1f%ae"
13470
- ]);
13471
- if (stdout === "") return [];
13472
- const lines = stdout.split("\n").filter((l) => l.length > 0);
13473
- const seenEmail = /* @__PURE__ */ new Set();
13474
- const out = [];
13475
- for (const line of lines) {
13476
- const sep2 = line.indexOf("");
13477
- if (sep2 <= 0) continue;
13478
- const name = line.slice(0, sep2).trim();
13479
- const email = line.slice(sep2 + 1).trim();
13480
- if (email === "") continue;
13481
- const key = email.toLowerCase();
13482
- if (seenEmail.has(key)) continue;
13483
- seenEmail.add(key);
13484
- const loginGuess = guessGithubLoginFromContact(name, email);
13485
- out.push(
13486
- loginGuess !== void 0 ? { name, email, loginGuess } : { name, email }
13487
- );
13354
+ const tf = readTicketIsoInstantPatch(payload, "targetFinishAt");
13355
+ if (typeof tf === "string") {
13356
+ row.targetFinishAt = tf;
13357
+ }
13358
+ if (Object.prototype.hasOwnProperty.call(payload, "dependsOn")) {
13359
+ const v = parseTicketDependsOnFromPayloadValue(payload["dependsOn"]);
13360
+ if (v !== void 0 && v.length > 0) {
13361
+ row.dependsOn = v;
13488
13362
  }
13489
- return out;
13490
- } catch {
13491
- return [];
13492
13363
  }
13493
13364
  };
13494
-
13495
- // src/git/parse-github-owner-repo-from-remote-url.ts
13496
- var parseGithubOwnerRepoFromRemoteUrl = (rawUrl) => {
13497
- const trimmed = rawUrl.trim();
13498
- if (!trimmed) {
13499
- return void 0;
13500
- }
13501
- const scpMatch = /^git@github\.com:(?<path>.+)$/i.exec(trimmed);
13502
- if (scpMatch?.groups?.path) {
13503
- return slugFromGithubPath(scpMatch.groups.path);
13504
- }
13505
- try {
13506
- const withScheme = trimmed.includes("://") ? trimmed : `https://${trimmed}`;
13507
- const u = new URL(withScheme);
13508
- const host = u.hostname.toLowerCase();
13509
- if (host !== "github.com" && host !== "www.github.com") {
13510
- return void 0;
13365
+ var applyTicketPlanningFieldsFromUpdatePayload = (row, payload) => {
13366
+ if (Object.prototype.hasOwnProperty.call(payload, "labels")) {
13367
+ if (payload["labels"] === null) {
13368
+ delete row.labels;
13369
+ } else {
13370
+ const v = ticketLabelsFromPayloadValue(payload["labels"]);
13371
+ if (v !== void 0) {
13372
+ if (v.length === 0) {
13373
+ delete row.labels;
13374
+ } else {
13375
+ row.labels = v;
13376
+ }
13377
+ }
13511
13378
  }
13512
- return slugFromGithubPath(u.pathname);
13513
- } catch {
13514
- return void 0;
13515
13379
  }
13516
- };
13517
- var slugFromGithubPath = (path) => {
13518
- const segments = path.replace(/^\/+/, "").split("/").filter((s) => s.length > 0).map((s) => s.replace(/\.git$/i, ""));
13519
- if (segments.length < 2) {
13520
- return void 0;
13380
+ const pr = readTicketPriorityPatch(payload);
13381
+ if (pr === null) {
13382
+ delete row.priority;
13383
+ } else if (typeof pr === "string") {
13384
+ row.priority = pr;
13521
13385
  }
13522
- const owner = segments[0];
13523
- const repo = segments[1];
13524
- if (!owner || !repo) {
13525
- return void 0;
13386
+ const sz = readTicketSizePatch(payload);
13387
+ if (sz === null) {
13388
+ delete row.size;
13389
+ } else if (typeof sz === "string") {
13390
+ row.size = sz;
13526
13391
  }
13527
- return `${owner}/${repo}`;
13528
- };
13529
-
13530
- // src/git/try-read-github-owner-repo-slug-from-git.ts
13531
- var tryReadGithubOwnerRepoSlugFromGit = async (params) => {
13532
- try {
13533
- const { stdout } = await params.runGit(params.repoRoot, [
13534
- "remote",
13535
- "get-url",
13536
- params.remote
13537
- ]);
13538
- return parseGithubOwnerRepoFromRemoteUrl(stdout);
13539
- } catch {
13540
- return void 0;
13392
+ const est = readTicketEstimatePatch(payload);
13393
+ if (est === null) {
13394
+ delete row.estimate;
13395
+ } else if (typeof est === "number") {
13396
+ row.estimate = est;
13541
13397
  }
13542
- };
13543
-
13544
- // src/git/resolve-effective-git-author-for-data-commit.ts
13545
- var defaultDataCommitName = "hyper-pm";
13546
- var defaultDataCommitEmail = "hyper-pm@users.noreply.github.com";
13547
- var tryReadGitConfigUserName = async (cwd, runGit2) => {
13548
- try {
13549
- const { stdout } = await runGit2(cwd, ["config", "--get", "user.name"]);
13550
- return stdout.trim();
13551
- } catch {
13552
- return "";
13398
+ const sw = readTicketIsoInstantPatch(payload, "startWorkAt");
13399
+ if (sw === null) {
13400
+ delete row.startWorkAt;
13401
+ } else if (typeof sw === "string") {
13402
+ row.startWorkAt = sw;
13553
13403
  }
13554
- };
13555
- var tryReadGitConfigUserEmail = async (cwd, runGit2) => {
13556
- try {
13557
- const { stdout } = await runGit2(cwd, ["config", "--get", "user.email"]);
13558
- return stdout.trim();
13559
- } catch {
13560
- return "";
13404
+ const tf = readTicketIsoInstantPatch(payload, "targetFinishAt");
13405
+ if (tf === null) {
13406
+ delete row.targetFinishAt;
13407
+ } else if (typeof tf === "string") {
13408
+ row.targetFinishAt = tf;
13409
+ }
13410
+ if (Object.prototype.hasOwnProperty.call(payload, "dependsOn")) {
13411
+ if (payload["dependsOn"] === null) {
13412
+ delete row.dependsOn;
13413
+ } else {
13414
+ const v = parseTicketDependsOnFromPayloadValue(payload["dependsOn"]);
13415
+ if (v !== void 0) {
13416
+ if (v.length === 0) {
13417
+ delete row.dependsOn;
13418
+ } else {
13419
+ row.dependsOn = v;
13420
+ }
13421
+ }
13422
+ }
13561
13423
  }
13562
13424
  };
13563
- var resolveEffectiveGitAuthorForDataCommit = async (cwd, runGit2, authorEnv) => {
13564
- const fromGitName = await tryReadGitConfigUserName(cwd, runGit2);
13565
- const fromGitEmail = await tryReadGitConfigUserEmail(cwd, runGit2);
13566
- const name = fromGitName || authorEnv.HYPER_PM_GIT_USER_NAME?.trim() || authorEnv.GIT_AUTHOR_NAME?.trim() || defaultDataCommitName;
13567
- const email = fromGitEmail || authorEnv.HYPER_PM_GIT_USER_EMAIL?.trim() || authorEnv.GIT_AUTHOR_EMAIL?.trim() || defaultDataCommitEmail;
13568
- return { name, email };
13569
- };
13570
-
13571
- // src/run/commit-data.ts
13572
- var maxActorSuffixLen = 60;
13573
- var formatDataBranchCommitMessage = (base, actorSuffix) => {
13574
- const raw = actorSuffix?.trim();
13575
- if (!raw) {
13576
- return base;
13577
- }
13578
- const collapsed = raw.replace(/\s+/g, " ");
13579
- const suffix = collapsed.length > maxActorSuffixLen ? `${collapsed.slice(0, maxActorSuffixLen - 1)}\u2026` : collapsed;
13580
- return `${base} (${suffix})`;
13581
- };
13582
- var commitDataWorktreeIfNeeded = async (worktreePath, message, runGit2, opts) => {
13583
- const { stdout } = await runGit2(worktreePath, ["status", "--porcelain"]);
13584
- if (!stdout.trim()) return;
13585
- await runGit2(worktreePath, ["add", "."]);
13586
- const authorEnv = opts?.authorEnv ?? env;
13587
- const { name, email } = await resolveEffectiveGitAuthorForDataCommit(
13588
- worktreePath,
13589
- runGit2,
13590
- authorEnv
13591
- );
13592
- await runGit2(worktreePath, [
13593
- "-c",
13594
- `user.name=${name}`,
13595
- "-c",
13596
- `user.email=${email}`,
13597
- "commit",
13598
- "-m",
13599
- message
13600
- ]);
13601
- };
13602
-
13603
- // src/run/push-data-branch.ts
13604
- var firstLineFromUnknown = (err) => {
13605
- const raw = err instanceof Error ? err.message : String(err);
13606
- const line = raw.trim().split("\n")[0]?.trim() ?? raw.trim();
13607
- return line.length > 0 ? line : "git error";
13608
- };
13609
- var tryPushDataBranchToRemote = async (worktreePath, remote, branch, runGit2) => {
13610
- try {
13611
- await runGit2(worktreePath, ["remote", "get-url", remote]);
13612
- } catch (e) {
13613
- return {
13614
- status: "skipped_no_remote",
13615
- detail: firstLineFromUnknown(e)
13616
- };
13617
- }
13618
- try {
13619
- await runGit2(worktreePath, ["push", "-u", remote, branch]);
13620
- return { status: "pushed" };
13621
- } catch (e) {
13622
- return {
13623
- status: "failed",
13624
- detail: firstLineFromUnknown(e)
13625
- };
13626
- }
13627
- };
13628
-
13629
- // src/run/sync-remote-data-branch.ts
13630
- var SyncRemoteDataBranchMergeError = class extends Error {
13631
- /** @param message - Human-readable reason (stderr excerpt or generic text). */
13632
- constructor(message) {
13633
- super(message);
13634
- this.name = "SyncRemoteDataBranchMergeError";
13635
- }
13636
- };
13637
- var remoteTrackingRef = (remote, dataBranch) => `refs/remotes/${remote}/${dataBranch}`;
13638
- var mergeRefSpecifier = (remote, dataBranch) => `${remote}/${dataBranch}`;
13639
- var isLikelyNonFastForwardPushFailure = (detail) => {
13640
- if (detail === void 0) return false;
13641
- const d = detail.toLowerCase();
13642
- return d.includes("non-fast-forward") || d.includes("failed to push");
13425
+ var applyCreatedAudit = (row, evt) => {
13426
+ row.createdAt = evt.ts;
13427
+ row.createdBy = evt.actor;
13428
+ row.updatedAt = evt.ts;
13429
+ row.updatedBy = evt.actor;
13643
13430
  };
13644
- var classifyMergeOutput = (combined) => {
13645
- const c = combined.toLowerCase();
13646
- if (c.includes("already up to date")) {
13647
- return "up_to_date";
13648
- }
13649
- if (c.includes("fast-forward")) {
13650
- return "fast_forward";
13651
- }
13652
- return "merge_commit";
13431
+ var applyLastUpdate = (row, evt) => {
13432
+ row.updatedAt = evt.ts;
13433
+ row.updatedBy = evt.actor;
13653
13434
  };
13654
- var refExists = async (cwd, ref, runGit2) => {
13655
- try {
13656
- await runGit2(cwd, ["show-ref", "--verify", "--quiet", ref]);
13657
- return true;
13658
- } catch {
13659
- return false;
13660
- }
13435
+ var applyStatusIfChanged = (row, evt, nextStatus) => {
13436
+ if (row.status === nextStatus) return false;
13437
+ row.status = nextStatus;
13438
+ row.statusChangedAt = evt.ts;
13439
+ row.statusChangedBy = evt.actor;
13440
+ return true;
13661
13441
  };
13662
- var fetchRemoteDataBranch = async (worktreePath, remote, dataBranch, runGit2) => {
13663
- try {
13664
- await runGit2(worktreePath, ["remote", "get-url", remote]);
13665
- } catch {
13666
- return "skipped_no_remote";
13667
- }
13668
- try {
13669
- await runGit2(worktreePath, ["fetch", remote, dataBranch]);
13670
- return "ok";
13671
- } catch (e) {
13672
- const msg = e instanceof Error ? e.message : String(e);
13673
- if (/couldn't find remote ref|could not find remote ref/i.test(msg)) {
13674
- return "remote_branch_absent";
13675
- }
13676
- throw e;
13677
- }
13442
+ var appendTicketCommentFromEvent = (ticket, evt) => {
13443
+ const list = ticket.comments ?? (ticket.comments = []);
13444
+ list.push({
13445
+ id: evt.id,
13446
+ body: String(evt.payload["body"] ?? ""),
13447
+ createdAt: evt.ts,
13448
+ createdBy: evt.actor
13449
+ });
13450
+ applyLastUpdate(ticket, evt);
13678
13451
  };
13679
- var mergeRemoteTrackingIntoHead = async (worktreePath, remote, dataBranch, runGit2, authorEnv = env) => {
13680
- const spec = mergeRefSpecifier(remote, dataBranch);
13681
- const { name, email } = await resolveEffectiveGitAuthorForDataCommit(
13682
- worktreePath,
13683
- runGit2,
13684
- authorEnv
13685
- );
13686
- try {
13687
- const { stdout, stderr } = await runGit2(worktreePath, [
13688
- "-c",
13689
- `user.name=${name}`,
13690
- "-c",
13691
- `user.email=${email}`,
13692
- "merge",
13693
- "--no-edit",
13694
- spec
13695
- ]);
13696
- return classifyMergeOutput(`${stdout}
13697
- ${stderr}`);
13698
- } catch (e) {
13699
- await runGit2(worktreePath, ["merge", "--abort"]).catch(() => {
13452
+ var resolveInboundTicketStatusFromPayload = (ticket, payload) => {
13453
+ const explicit = parseWorkItemStatus(payload["status"]);
13454
+ if (explicit !== void 0) return explicit;
13455
+ const legacySt = payload["state"];
13456
+ if (legacySt === "open" || legacySt === "closed") {
13457
+ return resolveTicketInboundStatus({
13458
+ issueState: legacySt,
13459
+ currentStatus: ticket.status
13700
13460
  });
13701
- const msg = e instanceof Error ? e.message : String(e);
13702
- throw new SyncRemoteDataBranchMergeError(
13703
- 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}`
13704
- );
13705
13461
  }
13462
+ return void 0;
13706
13463
  };
13707
- var runRemoteDataBranchGitSync = async (worktreePath, remote, dataBranch, runGit2, skipPush, deps = {}) => {
13708
- const tryPushFn = deps.tryPush ?? tryPushDataBranchToRemote;
13709
- const maxPushAttempts = deps.maxPushAttempts ?? 3;
13710
- const authorEnv = deps.authorEnv ?? env;
13711
- let dataBranchFetch = await fetchRemoteDataBranch(worktreePath, remote, dataBranch, runGit2);
13712
- let dataBranchMerge = dataBranchFetch === "skipped_no_remote" ? "skipped_no_remote" : dataBranchFetch === "remote_branch_absent" ? "skipped_missing_remote_branch" : "up_to_date";
13713
- if (dataBranchFetch === "ok") {
13714
- const tracking = remoteTrackingRef(remote, dataBranch);
13715
- const exists = await refExists(worktreePath, tracking, runGit2);
13716
- if (!exists) {
13717
- dataBranchMerge = "skipped_missing_remote_branch";
13718
- } else {
13719
- dataBranchMerge = await mergeRemoteTrackingIntoHead(
13720
- worktreePath,
13721
- remote,
13722
- dataBranch,
13723
- runGit2,
13724
- authorEnv
13725
- );
13464
+ var applyEvent = (projection, evt) => {
13465
+ switch (evt.type) {
13466
+ case "EpicCreated": {
13467
+ const id = String(evt.payload["id"]);
13468
+ const status = resolveStatusForNewEpicStoryPayload(evt.payload);
13469
+ const row = {
13470
+ id,
13471
+ number: resolveWorkItemCreateNumber(projection, "epic", evt.payload),
13472
+ title: String(evt.payload["title"] ?? ""),
13473
+ body: String(evt.payload["body"] ?? ""),
13474
+ status,
13475
+ statusChangedAt: evt.ts,
13476
+ statusChangedBy: evt.actor,
13477
+ createdAt: "",
13478
+ createdBy: "",
13479
+ updatedAt: "",
13480
+ updatedBy: ""
13481
+ };
13482
+ applyCreatedAudit(row, evt);
13483
+ projection.epics.set(id, row);
13484
+ break;
13726
13485
  }
13727
- }
13728
- if (skipPush) {
13729
- return {
13730
- dataBranchFetch,
13731
- dataBranchMerge,
13732
- dataBranchPush: "skipped_cli",
13733
- dataBranchPushDetail: "skip-push",
13734
- pushAttempts: 0
13735
- };
13736
- }
13737
- let pushAttempts = 0;
13738
- let lastPush = { status: "skipped_no_remote" };
13739
- const refetchAndMerge = async () => {
13740
- dataBranchFetch = await fetchRemoteDataBranch(
13741
- worktreePath,
13742
- remote,
13743
- dataBranch,
13744
- runGit2
13745
- );
13746
- if (dataBranchFetch !== "ok") {
13747
- return;
13486
+ case "EpicUpdated": {
13487
+ const id = String(evt.payload["id"]);
13488
+ const cur = projection.epics.get(id);
13489
+ if (!cur) break;
13490
+ if (evt.payload["title"] !== void 0) {
13491
+ cur.title = String(evt.payload["title"]);
13492
+ }
13493
+ if (evt.payload["body"] !== void 0) {
13494
+ cur.body = String(evt.payload["body"]);
13495
+ }
13496
+ const nextStatus = parseWorkItemStatus(evt.payload["status"]);
13497
+ if (nextStatus !== void 0) {
13498
+ applyStatusIfChanged(cur, evt, nextStatus);
13499
+ }
13500
+ applyLastUpdate(cur, evt);
13501
+ break;
13748
13502
  }
13749
- const tracking = remoteTrackingRef(remote, dataBranch);
13750
- const exists = await refExists(worktreePath, tracking, runGit2);
13751
- if (!exists) {
13752
- return;
13503
+ case "EpicDeleted": {
13504
+ const id = String(evt.payload["id"]);
13505
+ const cur = projection.epics.get(id);
13506
+ if (cur) {
13507
+ cur.deleted = true;
13508
+ applyLastUpdate(cur, evt);
13509
+ }
13510
+ break;
13753
13511
  }
13754
- dataBranchMerge = await mergeRemoteTrackingIntoHead(
13755
- worktreePath,
13756
- remote,
13757
- dataBranch,
13758
- runGit2,
13759
- authorEnv
13760
- );
13761
- };
13762
- for (let attempt = 1; attempt <= maxPushAttempts; attempt += 1) {
13763
- pushAttempts = attempt;
13764
- lastPush = await tryPushFn(worktreePath, remote, dataBranch, runGit2);
13765
- if (lastPush.status === "pushed" || lastPush.status === "skipped_no_remote") {
13512
+ case "StoryCreated": {
13513
+ const id = String(evt.payload["id"]);
13514
+ const status = resolveStatusForNewEpicStoryPayload(evt.payload);
13515
+ const row = {
13516
+ id,
13517
+ number: resolveWorkItemCreateNumber(projection, "story", evt.payload),
13518
+ epicId: String(evt.payload["epicId"]),
13519
+ title: String(evt.payload["title"] ?? ""),
13520
+ body: String(evt.payload["body"] ?? ""),
13521
+ status,
13522
+ statusChangedAt: evt.ts,
13523
+ statusChangedBy: evt.actor,
13524
+ createdAt: "",
13525
+ createdBy: "",
13526
+ updatedAt: "",
13527
+ updatedBy: ""
13528
+ };
13529
+ applyCreatedAudit(row, evt);
13530
+ projection.stories.set(id, row);
13766
13531
  break;
13767
13532
  }
13768
- if (lastPush.status === "failed" && isLikelyNonFastForwardPushFailure(lastPush.detail) && attempt < maxPushAttempts) {
13769
- await refetchAndMerge();
13770
- continue;
13533
+ case "StoryUpdated": {
13534
+ const id = String(evt.payload["id"]);
13535
+ const cur = projection.stories.get(id);
13536
+ if (!cur) break;
13537
+ if (evt.payload["title"] !== void 0) {
13538
+ cur.title = String(evt.payload["title"]);
13539
+ }
13540
+ if (evt.payload["body"] !== void 0) {
13541
+ cur.body = String(evt.payload["body"]);
13542
+ }
13543
+ const nextStatus = parseWorkItemStatus(evt.payload["status"]);
13544
+ if (nextStatus !== void 0) {
13545
+ applyStatusIfChanged(cur, evt, nextStatus);
13546
+ }
13547
+ applyLastUpdate(cur, evt);
13548
+ break;
13771
13549
  }
13772
- break;
13550
+ case "StoryDeleted": {
13551
+ const id = String(evt.payload["id"]);
13552
+ const cur = projection.stories.get(id);
13553
+ if (cur) {
13554
+ cur.deleted = true;
13555
+ applyLastUpdate(cur, evt);
13556
+ }
13557
+ break;
13558
+ }
13559
+ case "TicketCreated": {
13560
+ const id = String(evt.payload["id"]);
13561
+ const body = String(evt.payload["body"] ?? "");
13562
+ const status = resolveStatusForNewTicketPayload(evt.payload);
13563
+ const row = {
13564
+ id,
13565
+ number: resolveWorkItemCreateNumber(projection, "ticket", evt.payload),
13566
+ storyId: storyIdFromTicketCreatedPayload(evt.payload),
13567
+ title: String(evt.payload["title"] ?? ""),
13568
+ body,
13569
+ status,
13570
+ statusChangedAt: evt.ts,
13571
+ statusChangedBy: evt.actor,
13572
+ linkedPrs: parsePrRefs(body),
13573
+ linkedBranches: linkedBranchesFromTicketCreatedPayload(evt.payload),
13574
+ prActivityRecent: [],
13575
+ createdAt: "",
13576
+ createdBy: "",
13577
+ updatedAt: "",
13578
+ updatedBy: ""
13579
+ };
13580
+ applyCreatedAudit(row, evt);
13581
+ applyTicketAssigneeFromPayload(row, evt.payload);
13582
+ applyTicketPlanningFieldsFromCreatePayload(row, evt.payload);
13583
+ projection.tickets.set(id, row);
13584
+ break;
13585
+ }
13586
+ case "TicketUpdated": {
13587
+ const id = String(evt.payload["id"]);
13588
+ const cur = projection.tickets.get(id);
13589
+ if (!cur) break;
13590
+ if (evt.payload["title"] !== void 0) {
13591
+ cur.title = String(evt.payload["title"]);
13592
+ }
13593
+ if (evt.payload["body"] !== void 0) {
13594
+ cur.body = String(evt.payload["body"]);
13595
+ cur.linkedPrs = parsePrRefs(cur.body);
13596
+ }
13597
+ const nextStatus = resolveTicketStatusFromUpdatePayload(evt.payload);
13598
+ if (nextStatus !== void 0) {
13599
+ applyStatusIfChanged(cur, evt, nextStatus);
13600
+ }
13601
+ applyTicketAssigneeFromPayload(cur, evt.payload);
13602
+ applyTicketStoryIdFromPayload(cur, evt.payload);
13603
+ applyTicketBranchesFromUpdatePayload(cur, evt.payload);
13604
+ applyTicketPlanningFieldsFromUpdatePayload(cur, evt.payload);
13605
+ applyLastUpdate(cur, evt);
13606
+ break;
13607
+ }
13608
+ case "TicketDeleted": {
13609
+ const id = String(evt.payload["id"]);
13610
+ const cur = projection.tickets.get(id);
13611
+ if (cur) {
13612
+ cur.deleted = true;
13613
+ applyLastUpdate(cur, evt);
13614
+ }
13615
+ break;
13616
+ }
13617
+ case "TicketCommentAdded": {
13618
+ const ticketId = String(evt.payload["ticketId"] ?? "");
13619
+ const ticket = projection.tickets.get(ticketId);
13620
+ if (!ticket || ticket.deleted) break;
13621
+ appendTicketCommentFromEvent(ticket, evt);
13622
+ break;
13623
+ }
13624
+ case "SyncCursor": {
13625
+ projection.syncCursor = String(evt.payload["cursor"] ?? "");
13626
+ break;
13627
+ }
13628
+ case "GithubIssueLinked": {
13629
+ const ticketId = String(evt.payload["ticketId"]);
13630
+ const num = Number(evt.payload["issueNumber"]);
13631
+ const ticket = projection.tickets.get(ticketId);
13632
+ if (ticket && Number.isFinite(num)) {
13633
+ ticket.githubIssueNumber = num;
13634
+ applyLastUpdate(ticket, evt);
13635
+ }
13636
+ break;
13637
+ }
13638
+ case "GithubInboundUpdate": {
13639
+ const entity = String(evt.payload["entity"]);
13640
+ const entityId = String(evt.payload["entityId"]);
13641
+ if (entity === "ticket") {
13642
+ const ticket = projection.tickets.get(entityId);
13643
+ if (!ticket) break;
13644
+ if (evt.payload["title"] !== void 0) {
13645
+ ticket.title = String(evt.payload["title"]);
13646
+ }
13647
+ if (evt.payload["body"] !== void 0) {
13648
+ ticket.body = String(evt.payload["body"]);
13649
+ ticket.linkedPrs = parsePrRefs(ticket.body);
13650
+ }
13651
+ const inboundStatus = resolveInboundTicketStatusFromPayload(
13652
+ ticket,
13653
+ evt.payload
13654
+ );
13655
+ if (inboundStatus !== void 0) {
13656
+ applyStatusIfChanged(ticket, evt, inboundStatus);
13657
+ }
13658
+ applyTicketAssigneeFromPayload(ticket, evt.payload);
13659
+ applyTicketPlanningFieldsFromUpdatePayload(ticket, evt.payload);
13660
+ applyLastUpdate(ticket, evt);
13661
+ }
13662
+ break;
13663
+ }
13664
+ case "GithubPrActivity": {
13665
+ const summary = parseGithubPrActivityPayload(evt.payload);
13666
+ if (!summary) break;
13667
+ const ticket = projection.tickets.get(
13668
+ String(evt.payload["ticketId"] ?? "")
13669
+ );
13670
+ if (!ticket || ticket.deleted) break;
13671
+ const list = ticket.prActivityRecent ?? (ticket.prActivityRecent = []);
13672
+ list.push(summary);
13673
+ if (list.length > GITHUB_PR_ACTIVITY_RECENT_CAP) {
13674
+ ticket.prActivityRecent = list.slice(-GITHUB_PR_ACTIVITY_RECENT_CAP);
13675
+ }
13676
+ break;
13677
+ }
13678
+ default:
13679
+ break;
13773
13680
  }
13774
- return {
13775
- dataBranchFetch,
13776
- dataBranchMerge,
13777
- dataBranchPush: lastPush.status,
13778
- ...lastPush.detail !== void 0 ? { dataBranchPushDetail: lastPush.detail } : {},
13779
- pushAttempts
13780
- };
13781
13681
  };
13782
-
13783
- // src/storage/append-event.ts
13784
- var import_promises5 = require("node:fs/promises");
13785
- var import_node_path5 = require("node:path");
13786
-
13787
- // src/storage/event-path.ts
13788
- var nextEventRelPath = (now, deps) => {
13789
- const y = String(now.getUTCFullYear());
13790
- const m = String(now.getUTCMonth() + 1).padStart(2, "0");
13791
- const nextId = deps?.nextId ?? (() => ulid().toLowerCase());
13792
- const part = `part-${nextId()}`;
13793
- return `events/${y}/${m}/${part}.jsonl`;
13682
+ var replayEvents = (lines) => {
13683
+ const proj = emptyProjection();
13684
+ const events = [];
13685
+ for (const line of lines) {
13686
+ const trimmed = line.trim();
13687
+ if (!trimmed) continue;
13688
+ const json = JSON.parse(trimmed);
13689
+ events.push(eventLineSchema.parse(json));
13690
+ }
13691
+ events.sort((a, b) => {
13692
+ const t = a.ts.localeCompare(b.ts);
13693
+ if (t !== 0) return t;
13694
+ return a.id.localeCompare(b.id);
13695
+ });
13696
+ for (const e of events) {
13697
+ applyEvent(proj, e);
13698
+ }
13699
+ return proj;
13794
13700
  };
13795
13701
 
13796
- // src/storage/append-event.ts
13797
- var appendEventLine = async (dataRoot, event, clock, opts) => {
13798
- const rel = nextEventRelPath(clock.now(), {
13799
- nextId: opts?.nextEventId
13702
+ // src/cli/resolve-projection-work-item-id.ts
13703
+ var isDigitOnlyWorkItemRef = (raw) => /^\d+$/.test(raw.trim());
13704
+ var resolveEpicId = (projection, raw) => {
13705
+ const t = raw.trim();
13706
+ if (t === "") return void 0;
13707
+ if (projection.epics.has(t)) return t;
13708
+ if (!isDigitOnlyWorkItemRef(t)) return void 0;
13709
+ const n = Number(t);
13710
+ const hits = [...projection.epics.values()].filter((e) => e.number === n);
13711
+ if (hits.length !== 1) return void 0;
13712
+ return hits[0].id;
13713
+ };
13714
+ var resolveStoryId = (projection, raw) => {
13715
+ const t = raw.trim();
13716
+ if (t === "") return void 0;
13717
+ if (projection.stories.has(t)) return t;
13718
+ if (!isDigitOnlyWorkItemRef(t)) return void 0;
13719
+ const n = Number(t);
13720
+ const hits = [...projection.stories.values()].filter((s) => s.number === n);
13721
+ if (hits.length !== 1) return void 0;
13722
+ return hits[0].id;
13723
+ };
13724
+ var resolveTicketId = (projection, raw) => {
13725
+ const t = raw.trim();
13726
+ if (t === "") return void 0;
13727
+ if (projection.tickets.has(t)) return t;
13728
+ if (!isDigitOnlyWorkItemRef(t)) return void 0;
13729
+ const n = Number(t);
13730
+ const hits = [...projection.tickets.values()].filter((x) => x.number === n);
13731
+ if (hits.length !== 1) return void 0;
13732
+ return hits[0].id;
13733
+ };
13734
+ var resolveTicketDependsOnTokensToIds = (projection, tokens) => {
13735
+ const mapped = tokens.map((raw) => {
13736
+ const t = raw.trim();
13737
+ if (t === "") return "";
13738
+ if (projection.tickets.has(t)) return t;
13739
+ if (!isDigitOnlyWorkItemRef(t)) return t;
13740
+ const hit = resolveTicketId(projection, t);
13741
+ return hit ?? t;
13800
13742
  });
13801
- const abs = `${dataRoot.replace(/\/$/, "")}/${rel}`;
13802
- await (0, import_promises5.mkdir)((0, import_node_path5.dirname)(abs), { recursive: true });
13803
- await (0, import_promises5.appendFile)(abs, `${JSON.stringify(event)}
13804
- `, "utf8");
13805
- return rel;
13743
+ return normalizeTicketDependsOnIds(mapped);
13806
13744
  };
13807
-
13808
- // src/storage/read-event-lines.ts
13809
- var import_promises6 = require("node:fs/promises");
13810
- var import_node_path6 = require("node:path");
13811
- var listJsonlFiles = async (dir, root) => {
13812
- const out = [];
13813
- const entries = await (0, import_promises6.readdir)(dir, { withFileTypes: true });
13814
- for (const e of entries) {
13815
- const abs = (0, import_node_path6.join)(dir, e.name);
13816
- if (e.isDirectory()) {
13817
- out.push(...await listJsonlFiles(abs, root));
13818
- } else if (e.isFile() && e.name.endsWith(".jsonl")) {
13819
- out.push((0, import_node_path6.relative)(root, abs).split("\\").join("/"));
13820
- }
13745
+ var assertCreatePayloadUsesExpectedHeadNumber = (projection, kind, payload) => {
13746
+ const n = readOptionalPositiveIntegerFromPayload(payload, "number");
13747
+ const expected = kind === "epic" ? nextEpicNumberForCreate(projection) : kind === "story" ? nextStoryNumberForCreate(projection) : nextTicketNumberForCreate(projection);
13748
+ if (n !== expected) {
13749
+ const got = n === void 0 ? "missing" : String(n);
13750
+ throw new Error(
13751
+ `Invalid ${kind} number for this data branch: expected ${String(expected)}, got ${got}.`
13752
+ );
13821
13753
  }
13822
- return out;
13823
- };
13824
- var readAllEventLines = async (dataRoot) => {
13825
- const eventsRoot = (0, import_node_path6.join)(dataRoot, "events");
13826
- let files = [];
13827
- try {
13828
- files = await listJsonlFiles(eventsRoot, dataRoot);
13829
- } catch {
13830
- return [];
13831
- }
13832
- files.sort((a, b) => a.localeCompare(b));
13833
- const lines = [];
13834
- for (const rel of files) {
13835
- const content = await (0, import_promises6.readFile)((0, import_node_path6.join)(dataRoot, rel), "utf8");
13836
- for (const line of content.split("\n")) {
13837
- if (line.trim()) lines.push(line);
13838
- }
13839
- }
13840
- return lines;
13841
13754
  };
13842
13755
 
13843
- // src/lib/github-pr-activity.ts
13844
- var GITHUB_PR_ACTIVITY_RECENT_CAP = 20;
13845
- var githubPrActivityKindSchema = external_exports.enum([
13846
- "opened",
13847
- "updated",
13848
- "commented",
13849
- "reviewed",
13850
- "merged",
13851
- "closed",
13852
- "ready_for_review"
13853
- ]);
13854
- var githubPrReviewStateSchema = external_exports.enum([
13855
- "approved",
13856
- "changes_requested",
13857
- "commented"
13858
- ]);
13859
- var buildPrOpenSourceId = (ticketId, prNumber) => `hyper-pm:pr-open:${ticketId}:${prNumber}`;
13860
- var parseGithubPrActivityPayload = (payload) => {
13861
- const ticketId = payload["ticketId"];
13862
- const prRaw = payload["prNumber"];
13863
- const kindRaw = payload["kind"];
13864
- const occurredAt = payload["occurredAt"];
13865
- const sourceId = payload["sourceId"];
13866
- if (typeof ticketId !== "string" || typeof occurredAt !== "string" || typeof sourceId !== "string") {
13867
- return void 0;
13868
- }
13869
- const prNumber = typeof prRaw === "number" ? prRaw : Number(prRaw);
13870
- if (!Number.isFinite(prNumber)) return void 0;
13871
- const kindParsed = githubPrActivityKindSchema.safeParse(kindRaw);
13872
- if (!kindParsed.success) return void 0;
13873
- const out = {
13874
- prNumber,
13875
- kind: kindParsed.data,
13876
- occurredAt,
13877
- sourceId
13878
- };
13879
- const url = payload["url"];
13880
- if (typeof url === "string" && url.length > 0) {
13881
- out.url = url;
13882
- }
13883
- const rs = githubPrReviewStateSchema.safeParse(payload["reviewState"]);
13884
- if (rs.success) {
13885
- out.reviewState = rs.data;
13756
+ // src/config/hyper-pm-config.ts
13757
+ var hyperPmConfigSchema = external_exports.object({
13758
+ schema: external_exports.literal(1),
13759
+ dataBranch: external_exports.string().min(1),
13760
+ remote: external_exports.string().min(1).default("origin"),
13761
+ sync: external_exports.enum(["off", "outbound", "full"]).default("outbound"),
13762
+ githubRepo: external_exports.string().optional(),
13763
+ issueMapping: external_exports.enum(["ticket", "story", "epic"]).default("ticket")
13764
+ });
13765
+
13766
+ // src/config/load-config.ts
13767
+ var import_promises = require("node:fs/promises");
13768
+ var import_node_path = require("node:path");
13769
+ var CONFIG_DIR = ".hyper-pm";
13770
+ var CONFIG_FILE = "config.json";
13771
+ var getHyperPmConfigPath = (repoRoot) => {
13772
+ return (0, import_node_path.join)(repoRoot, CONFIG_DIR, CONFIG_FILE);
13773
+ };
13774
+ var loadHyperPmConfig = async (repoRoot, overrides = {}) => {
13775
+ const raw = await (0, import_promises.readFile)(getHyperPmConfigPath(repoRoot), "utf8");
13776
+ const parsed = JSON.parse(raw);
13777
+ const base = hyperPmConfigSchema.parse(parsed);
13778
+ const merged = { ...base, ...stripUndefined(overrides) };
13779
+ return hyperPmConfigSchema.parse(merged);
13780
+ };
13781
+ var stripUndefined = (obj) => {
13782
+ const out = {};
13783
+ for (const [k, v] of Object.entries(obj)) {
13784
+ if (v !== void 0) out[k] = v;
13886
13785
  }
13887
13786
  return out;
13888
13787
  };
13889
13788
 
13890
- // src/storage/projection.ts
13891
- var emptyProjection = () => ({
13892
- epics: /* @__PURE__ */ new Map(),
13893
- stories: /* @__PURE__ */ new Map(),
13894
- tickets: /* @__PURE__ */ new Map()
13895
- });
13896
- var parsePrRefs = (body) => {
13897
- const out = /* @__PURE__ */ new Set();
13898
- const re = /\b(?:Closes|Refs|Fixes)\s+#(\d+)\b/gi;
13899
- let m = re.exec(body);
13900
- while (m !== null) {
13901
- out.add(Number(m[1]));
13902
- m = re.exec(body);
13903
- }
13904
- return [...out];
13789
+ // src/config/save-config.ts
13790
+ var import_promises2 = require("node:fs/promises");
13791
+ var import_node_path2 = require("node:path");
13792
+ var saveHyperPmConfig = async (repoRoot, config) => {
13793
+ const target = getHyperPmConfigPath(repoRoot);
13794
+ await (0, import_promises2.mkdir)((0, import_node_path2.dirname)(target), { recursive: true });
13795
+ await (0, import_promises2.writeFile)(target, `${JSON.stringify(config, null, 2)}
13796
+ `, "utf8");
13905
13797
  };
13906
- var applyTicketAssigneeFromPayload = (row, payload) => {
13907
- if (!Object.prototype.hasOwnProperty.call(payload, "assignee")) return;
13908
- const v = payload["assignee"];
13909
- if (v === null) {
13910
- delete row.assignee;
13911
- return;
13798
+
13799
+ // src/doctor/run-doctor.ts
13800
+ var runDoctorOnLines = (lines) => {
13801
+ const issues = [];
13802
+ for (let i = 0; i < lines.length; i++) {
13803
+ const line = lines[i]?.trim() ?? "";
13804
+ if (!line) continue;
13805
+ let json;
13806
+ try {
13807
+ json = JSON.parse(line);
13808
+ } catch (e) {
13809
+ issues.push({
13810
+ kind: "invalid-json",
13811
+ line: i + 1,
13812
+ message: e instanceof Error ? e.message : "parse error"
13813
+ });
13814
+ return issues;
13815
+ }
13816
+ const parsed = eventLineSchema.safeParse(json);
13817
+ if (!parsed.success) {
13818
+ issues.push({
13819
+ kind: "invalid-event",
13820
+ line: i + 1,
13821
+ message: parsed.error.message
13822
+ });
13823
+ return issues;
13824
+ }
13912
13825
  }
13913
- if (typeof v !== "string") return;
13914
- const n = normalizeGithubLogin(v);
13915
- if (n === "") delete row.assignee;
13916
- else row.assignee = n;
13826
+ return issues;
13917
13827
  };
13918
- var storyIdFromTicketCreatedPayload = (payload) => {
13919
- if (!Object.prototype.hasOwnProperty.call(payload, "storyId")) {
13920
- return null;
13921
- }
13922
- const v = payload["storyId"];
13923
- if (v === null) return null;
13924
- if (typeof v !== "string") return null;
13925
- const t = v.trim();
13926
- return t === "" ? null : t;
13828
+
13829
+ // src/git/create-and-checkout-branch.ts
13830
+ var createAndCheckoutBranch = async (opts) => {
13831
+ await opts.runGit(opts.repoRoot, [
13832
+ "switch",
13833
+ "-c",
13834
+ opts.branchName,
13835
+ opts.startPoint
13836
+ ]);
13927
13837
  };
13928
- var applyTicketStoryIdFromPayload = (row, payload) => {
13929
- if (!Object.prototype.hasOwnProperty.call(payload, "storyId")) return;
13930
- const v = payload["storyId"];
13931
- if (v === null) {
13932
- row.storyId = null;
13933
- return;
13934
- }
13935
- if (typeof v !== "string") return;
13936
- const t = v.trim();
13937
- row.storyId = t === "" ? null : t;
13838
+
13839
+ // src/git/data-worktree-session.ts
13840
+ var import_promises3 = require("node:fs/promises");
13841
+ var import_node_path3 = require("node:path");
13842
+ var ensureDir = async (path) => {
13843
+ await (0, import_promises3.mkdir)(path, { recursive: true });
13938
13844
  };
13939
- var linkedBranchesFromTicketCreatedPayload = (payload) => {
13940
- if (!Object.prototype.hasOwnProperty.call(payload, "branches")) {
13941
- return [];
13845
+ var defaultPathExists = async (path) => {
13846
+ try {
13847
+ await (0, import_promises3.access)(path);
13848
+ return true;
13849
+ } catch {
13850
+ return false;
13942
13851
  }
13943
- return normalizeTicketBranchListFromPayloadValue(payload["branches"]);
13944
- };
13945
- var applyTicketBranchesFromUpdatePayload = (row, payload) => {
13946
- if (!Object.prototype.hasOwnProperty.call(payload, "branches")) return;
13947
- const v = payload["branches"];
13948
- if (!Array.isArray(v)) return;
13949
- row.linkedBranches = normalizeTicketBranchListFromPayloadValue(v);
13950
13852
  };
13951
- var applyTicketPlanningFieldsFromCreatePayload = (row, payload) => {
13952
- if (Object.prototype.hasOwnProperty.call(payload, "labels")) {
13953
- const v = ticketLabelsFromPayloadValue(payload["labels"]);
13954
- if (v !== void 0 && v.length > 0) {
13955
- row.labels = v;
13853
+ var parseDataBranchWorktreeFromPorcelain = (stdout, dataBranch) => {
13854
+ const wantRef = `refs/heads/${dataBranch}`;
13855
+ const blocks = stdout.split(/\n\n+/).map((b) => b.trim()).filter(Boolean);
13856
+ for (const block of blocks) {
13857
+ const lines = block.split("\n");
13858
+ let worktreePath;
13859
+ let branch;
13860
+ for (const line of lines) {
13861
+ if (line.startsWith("worktree ")) {
13862
+ worktreePath = line.slice("worktree ".length);
13863
+ } else if (line.startsWith("branch ")) {
13864
+ branch = line.slice("branch ".length);
13865
+ }
13956
13866
  }
13957
- }
13958
- const pr = readTicketPriorityPatch(payload);
13959
- if (typeof pr === "string") {
13960
- row.priority = pr;
13961
- }
13962
- const sz = readTicketSizePatch(payload);
13963
- if (typeof sz === "string") {
13964
- row.size = sz;
13965
- }
13966
- const est = readTicketEstimatePatch(payload);
13967
- if (typeof est === "number") {
13968
- row.estimate = est;
13969
- }
13970
- const sw = readTicketIsoInstantPatch(payload, "startWorkAt");
13971
- if (typeof sw === "string") {
13972
- row.startWorkAt = sw;
13973
- }
13974
- const tf = readTicketIsoInstantPatch(payload, "targetFinishAt");
13975
- if (typeof tf === "string") {
13976
- row.targetFinishAt = tf;
13977
- }
13978
- if (Object.prototype.hasOwnProperty.call(payload, "dependsOn")) {
13979
- const v = parseTicketDependsOnFromPayloadValue(payload["dependsOn"]);
13980
- if (v !== void 0 && v.length > 0) {
13981
- row.dependsOn = v;
13867
+ if (worktreePath !== void 0 && branch === wantRef) {
13868
+ return worktreePath;
13982
13869
  }
13983
13870
  }
13871
+ return void 0;
13984
13872
  };
13985
- var applyTicketPlanningFieldsFromUpdatePayload = (row, payload) => {
13986
- if (Object.prototype.hasOwnProperty.call(payload, "labels")) {
13987
- if (payload["labels"] === null) {
13988
- delete row.labels;
13989
- } else {
13990
- const v = ticketLabelsFromPayloadValue(payload["labels"]);
13991
- if (v !== void 0) {
13992
- if (v.length === 0) {
13993
- delete row.labels;
13994
- } else {
13995
- row.labels = v;
13996
- }
13997
- }
13998
- }
13999
- }
14000
- const pr = readTicketPriorityPatch(payload);
14001
- if (pr === null) {
14002
- delete row.priority;
14003
- } else if (typeof pr === "string") {
14004
- row.priority = pr;
14005
- }
14006
- const sz = readTicketSizePatch(payload);
14007
- if (sz === null) {
14008
- delete row.size;
14009
- } else if (typeof sz === "string") {
14010
- row.size = sz;
14011
- }
14012
- const est = readTicketEstimatePatch(payload);
14013
- if (est === null) {
14014
- delete row.estimate;
14015
- } else if (typeof est === "number") {
14016
- row.estimate = est;
14017
- }
14018
- const sw = readTicketIsoInstantPatch(payload, "startWorkAt");
14019
- if (sw === null) {
14020
- delete row.startWorkAt;
14021
- } else if (typeof sw === "string") {
14022
- row.startWorkAt = sw;
13873
+ var openDataBranchWorktree = async (opts) => {
13874
+ const pathExists = opts.pathExists ?? defaultPathExists;
13875
+ const { stdout: listOut } = await opts.runGit(opts.repoRoot, [
13876
+ "worktree",
13877
+ "list",
13878
+ "--porcelain"
13879
+ ]);
13880
+ const listedPath = parseDataBranchWorktreeFromPorcelain(
13881
+ listOut,
13882
+ opts.dataBranch
13883
+ );
13884
+ if (listedPath !== void 0 && await pathExists(listedPath)) {
13885
+ const dispose2 = async () => {
13886
+ return;
13887
+ };
13888
+ return { worktreePath: listedPath, dispose: dispose2 };
14023
13889
  }
14024
- const tf = readTicketIsoInstantPatch(payload, "targetFinishAt");
14025
- if (tf === null) {
14026
- delete row.targetFinishAt;
14027
- } else if (typeof tf === "string") {
14028
- row.targetFinishAt = tf;
13890
+ const worktreePath = (0, import_node_path3.join)(
13891
+ opts.tmpBase,
13892
+ `hyper-pm-worktree-${ulid().toLowerCase()}`
13893
+ );
13894
+ await ensureDir(opts.tmpBase);
13895
+ try {
13896
+ await opts.runGit(opts.repoRoot, [
13897
+ "worktree",
13898
+ "add",
13899
+ worktreePath,
13900
+ opts.dataBranch
13901
+ ]);
13902
+ } catch (err) {
13903
+ await (0, import_promises3.rm)(worktreePath, { recursive: true, force: true }).catch(() => {
13904
+ });
13905
+ throw err;
14029
13906
  }
14030
- if (Object.prototype.hasOwnProperty.call(payload, "dependsOn")) {
14031
- if (payload["dependsOn"] === null) {
14032
- delete row.dependsOn;
14033
- } else {
14034
- const v = parseTicketDependsOnFromPayloadValue(payload["dependsOn"]);
14035
- if (v !== void 0) {
14036
- if (v.length === 0) {
14037
- delete row.dependsOn;
14038
- } else {
14039
- row.dependsOn = v;
14040
- }
14041
- }
13907
+ const dispose = async () => {
13908
+ if (opts.keepWorktree) {
13909
+ return;
14042
13910
  }
14043
- }
14044
- };
14045
- var applyCreatedAudit = (row, evt) => {
14046
- row.createdAt = evt.ts;
14047
- row.createdBy = evt.actor;
14048
- row.updatedAt = evt.ts;
14049
- row.updatedBy = evt.actor;
14050
- };
14051
- var applyLastUpdate = (row, evt) => {
14052
- row.updatedAt = evt.ts;
14053
- row.updatedBy = evt.actor;
13911
+ await opts.runGit(opts.repoRoot, ["worktree", "remove", "--force", worktreePath]).catch(() => {
13912
+ });
13913
+ await (0, import_promises3.rm)(worktreePath, { recursive: true, force: true }).catch(() => {
13914
+ });
13915
+ };
13916
+ return { worktreePath, dispose };
14054
13917
  };
14055
- var applyStatusIfChanged = (row, evt, nextStatus) => {
14056
- if (row.status === nextStatus) return false;
14057
- row.status = nextStatus;
14058
- row.statusChangedAt = evt.ts;
14059
- row.statusChangedBy = evt.actor;
14060
- return true;
13918
+
13919
+ // src/git/find-git-root.ts
13920
+ var findGitRoot = async (cwd, deps) => {
13921
+ const { stdout } = await deps.runGit(cwd, ["rev-parse", "--show-toplevel"]);
13922
+ return stdout;
14061
13923
  };
14062
- var appendTicketCommentFromEvent = (ticket, evt) => {
14063
- const list = ticket.comments ?? (ticket.comments = []);
14064
- list.push({
14065
- id: evt.id,
14066
- body: String(evt.payload["body"] ?? ""),
14067
- createdAt: evt.ts,
14068
- createdBy: evt.actor
13924
+
13925
+ // src/git/init-orphan-data-branch.ts
13926
+ var import_promises4 = require("node:fs/promises");
13927
+ var import_node_path4 = require("node:path");
13928
+ var initOrphanDataBranchInWorktree = async (opts) => {
13929
+ const { stdout: tip } = await opts.runGit(opts.repoRoot, [
13930
+ "rev-parse",
13931
+ "HEAD"
13932
+ ]);
13933
+ const tipCommit = tip.trim();
13934
+ await opts.runGit(opts.repoRoot, [
13935
+ "worktree",
13936
+ "add",
13937
+ opts.worktreePath,
13938
+ tipCommit
13939
+ ]);
13940
+ await opts.runGit(opts.worktreePath, [
13941
+ "checkout",
13942
+ "--orphan",
13943
+ opts.dataBranch
13944
+ ]);
13945
+ await opts.runGit(opts.worktreePath, ["rm", "-rf", "."]).catch(() => {
14069
13946
  });
14070
- applyLastUpdate(ticket, evt);
13947
+ const marker = (0, import_node_path4.join)(opts.worktreePath, "README.hyper-pm.md");
13948
+ await (0, import_promises4.writeFile)(
13949
+ marker,
13950
+ "# hyper-pm data branch\n\nAppend-only events live under `events/`.\n",
13951
+ "utf8"
13952
+ );
13953
+ await opts.runGit(opts.worktreePath, ["add", "."]);
13954
+ await opts.runGit(opts.worktreePath, [
13955
+ "commit",
13956
+ "-m",
13957
+ "init hyper-pm data branch"
13958
+ ]);
14071
13959
  };
14072
- var resolveInboundTicketStatusFromPayload = (ticket, payload) => {
14073
- const explicit = parseWorkItemStatus(payload["status"]);
14074
- if (explicit !== void 0) return explicit;
14075
- const legacySt = payload["state"];
14076
- if (legacySt === "open" || legacySt === "closed") {
14077
- return resolveTicketInboundStatus({
14078
- issueState: legacySt,
14079
- currentStatus: ticket.status
14080
- });
13960
+
13961
+ // src/git/pick-unique-local-branch-name.ts
13962
+ var DEFAULT_MAX_SUFFIX = 1e3;
13963
+ var localBranchRefExists = async (repoRoot, name, git) => {
13964
+ try {
13965
+ await git(repoRoot, ["show-ref", "--verify", `refs/heads/${name}`]);
13966
+ return true;
13967
+ } catch {
13968
+ return false;
14081
13969
  }
14082
- return void 0;
14083
13970
  };
14084
- var applyEvent = (projection, evt) => {
14085
- switch (evt.type) {
14086
- case "EpicCreated": {
14087
- const id = String(evt.payload["id"]);
14088
- const status = resolveStatusForNewEpicStoryPayload(evt.payload);
14089
- const row = {
14090
- id,
14091
- title: String(evt.payload["title"] ?? ""),
14092
- body: String(evt.payload["body"] ?? ""),
14093
- status,
14094
- statusChangedAt: evt.ts,
14095
- statusChangedBy: evt.actor,
14096
- createdAt: "",
14097
- createdBy: "",
14098
- updatedAt: "",
14099
- updatedBy: ""
14100
- };
14101
- applyCreatedAudit(row, evt);
14102
- projection.epics.set(id, row);
14103
- break;
13971
+ var pickUniqueLocalBranchName = async (opts) => {
13972
+ const max = opts.maxSuffix ?? DEFAULT_MAX_SUFFIX;
13973
+ const { preferredBase, repoRoot, runGit: git } = opts;
13974
+ for (let n = 1; n <= max; n += 1) {
13975
+ const raw = n === 1 ? preferredBase : `${preferredBase}-${n}`;
13976
+ const norm = normalizeTicketBranchName(raw);
13977
+ if (norm === void 0) {
13978
+ continue;
14104
13979
  }
14105
- case "EpicUpdated": {
14106
- const id = String(evt.payload["id"]);
14107
- const cur = projection.epics.get(id);
14108
- if (!cur) break;
14109
- if (evt.payload["title"] !== void 0) {
14110
- cur.title = String(evt.payload["title"]);
14111
- }
14112
- if (evt.payload["body"] !== void 0) {
14113
- cur.body = String(evt.payload["body"]);
14114
- }
14115
- const nextStatus = parseWorkItemStatus(evt.payload["status"]);
14116
- if (nextStatus !== void 0) {
14117
- applyStatusIfChanged(cur, evt, nextStatus);
14118
- }
14119
- applyLastUpdate(cur, evt);
14120
- break;
13980
+ if (!await localBranchRefExists(repoRoot, norm, git)) {
13981
+ return { branch: norm, preferred: preferredBase };
14121
13982
  }
14122
- case "EpicDeleted": {
14123
- const id = String(evt.payload["id"]);
14124
- const cur = projection.epics.get(id);
14125
- if (cur) {
14126
- cur.deleted = true;
14127
- applyLastUpdate(cur, evt);
14128
- }
14129
- break;
13983
+ }
13984
+ throw new Error(
13985
+ `Could not allocate a free local branch name from ${JSON.stringify(preferredBase)} (tried suffixes up to -${String(max)})`
13986
+ );
13987
+ };
13988
+
13989
+ // src/git/resolve-integration-start-point.ts
13990
+ var assertGitRefResolvable = async (repoRoot, ref, git) => {
13991
+ try {
13992
+ await git(repoRoot, ["rev-parse", "-q", "--verify", ref]);
13993
+ } catch {
13994
+ throw new Error(`Invalid or ambiguous --from ref: ${ref}`);
13995
+ }
13996
+ };
13997
+ var resolveIntegrationStartPoint = async (repoRoot, remote, git) => {
13998
+ const symRef = `refs/remotes/${remote}/HEAD`;
13999
+ try {
14000
+ const { stdout } = await git(repoRoot, ["symbolic-ref", "-q", symRef]);
14001
+ const target = stdout.trim();
14002
+ if (target !== "") {
14003
+ await git(repoRoot, ["rev-parse", "-q", "--verify", target]);
14004
+ return target;
14130
14005
  }
14131
- case "StoryCreated": {
14132
- const id = String(evt.payload["id"]);
14133
- const status = resolveStatusForNewEpicStoryPayload(evt.payload);
14134
- const row = {
14135
- id,
14136
- epicId: String(evt.payload["epicId"]),
14137
- title: String(evt.payload["title"] ?? ""),
14138
- body: String(evt.payload["body"] ?? ""),
14139
- status,
14140
- statusChangedAt: evt.ts,
14141
- statusChangedBy: evt.actor,
14142
- createdAt: "",
14143
- createdBy: "",
14144
- updatedAt: "",
14145
- updatedBy: ""
14146
- };
14147
- applyCreatedAudit(row, evt);
14148
- projection.stories.set(id, row);
14149
- break;
14150
- }
14151
- case "StoryUpdated": {
14152
- const id = String(evt.payload["id"]);
14153
- const cur = projection.stories.get(id);
14154
- if (!cur) break;
14155
- if (evt.payload["title"] !== void 0) {
14156
- cur.title = String(evt.payload["title"]);
14157
- }
14158
- if (evt.payload["body"] !== void 0) {
14159
- cur.body = String(evt.payload["body"]);
14160
- }
14161
- const nextStatus = parseWorkItemStatus(evt.payload["status"]);
14162
- if (nextStatus !== void 0) {
14163
- applyStatusIfChanged(cur, evt, nextStatus);
14164
- }
14165
- applyLastUpdate(cur, evt);
14166
- break;
14006
+ } catch {
14007
+ }
14008
+ for (const head of ["refs/heads/main", "refs/heads/master"]) {
14009
+ try {
14010
+ await git(repoRoot, ["rev-parse", "-q", "--verify", head]);
14011
+ return head;
14012
+ } catch {
14167
14013
  }
14168
- case "StoryDeleted": {
14169
- const id = String(evt.payload["id"]);
14170
- const cur = projection.stories.get(id);
14171
- if (cur) {
14172
- cur.deleted = true;
14173
- applyLastUpdate(cur, evt);
14174
- }
14175
- break;
14014
+ }
14015
+ try {
14016
+ await git(repoRoot, ["rev-parse", "-q", "--verify", "HEAD"]);
14017
+ return "HEAD";
14018
+ } catch {
14019
+ }
14020
+ throw new Error(
14021
+ "Could not resolve a default branch to branch from; pass --from <ref> (e.g. main or origin/main)."
14022
+ );
14023
+ };
14024
+
14025
+ // src/git/list-repo-commit-authors.ts
14026
+ var listRepoCommitAuthors = async (repoRoot, git) => {
14027
+ try {
14028
+ const { stdout } = await git(repoRoot, [
14029
+ "-c",
14030
+ "log.showSignature=false",
14031
+ "log",
14032
+ "--all",
14033
+ "--format=%an%x1f%ae"
14034
+ ]);
14035
+ if (stdout === "") return [];
14036
+ const lines = stdout.split("\n").filter((l) => l.length > 0);
14037
+ const seenEmail = /* @__PURE__ */ new Set();
14038
+ const out = [];
14039
+ for (const line of lines) {
14040
+ const sep2 = line.indexOf("");
14041
+ if (sep2 <= 0) continue;
14042
+ const name = line.slice(0, sep2).trim();
14043
+ const email = line.slice(sep2 + 1).trim();
14044
+ if (email === "") continue;
14045
+ const key = email.toLowerCase();
14046
+ if (seenEmail.has(key)) continue;
14047
+ seenEmail.add(key);
14048
+ const loginGuess = guessGithubLoginFromContact(name, email);
14049
+ out.push(
14050
+ loginGuess !== void 0 ? { name, email, loginGuess } : { name, email }
14051
+ );
14176
14052
  }
14177
- case "TicketCreated": {
14178
- const id = String(evt.payload["id"]);
14179
- const body = String(evt.payload["body"] ?? "");
14180
- const status = resolveStatusForNewTicketPayload(evt.payload);
14181
- const row = {
14182
- id,
14183
- storyId: storyIdFromTicketCreatedPayload(evt.payload),
14184
- title: String(evt.payload["title"] ?? ""),
14185
- body,
14186
- status,
14187
- statusChangedAt: evt.ts,
14188
- statusChangedBy: evt.actor,
14189
- linkedPrs: parsePrRefs(body),
14190
- linkedBranches: linkedBranchesFromTicketCreatedPayload(evt.payload),
14191
- prActivityRecent: [],
14192
- createdAt: "",
14193
- createdBy: "",
14194
- updatedAt: "",
14195
- updatedBy: ""
14196
- };
14197
- applyCreatedAudit(row, evt);
14198
- applyTicketAssigneeFromPayload(row, evt.payload);
14199
- applyTicketPlanningFieldsFromCreatePayload(row, evt.payload);
14200
- projection.tickets.set(id, row);
14201
- break;
14053
+ return out;
14054
+ } catch {
14055
+ return [];
14056
+ }
14057
+ };
14058
+
14059
+ // src/git/parse-github-owner-repo-from-remote-url.ts
14060
+ var parseGithubOwnerRepoFromRemoteUrl = (rawUrl) => {
14061
+ const trimmed = rawUrl.trim();
14062
+ if (!trimmed) {
14063
+ return void 0;
14064
+ }
14065
+ const scpMatch = /^git@github\.com:(?<path>.+)$/i.exec(trimmed);
14066
+ if (scpMatch?.groups?.path) {
14067
+ return slugFromGithubPath(scpMatch.groups.path);
14068
+ }
14069
+ try {
14070
+ const withScheme = trimmed.includes("://") ? trimmed : `https://${trimmed}`;
14071
+ const u = new URL(withScheme);
14072
+ const host = u.hostname.toLowerCase();
14073
+ if (host !== "github.com" && host !== "www.github.com") {
14074
+ return void 0;
14202
14075
  }
14203
- case "TicketUpdated": {
14204
- const id = String(evt.payload["id"]);
14205
- const cur = projection.tickets.get(id);
14206
- if (!cur) break;
14207
- if (evt.payload["title"] !== void 0) {
14208
- cur.title = String(evt.payload["title"]);
14209
- }
14210
- if (evt.payload["body"] !== void 0) {
14211
- cur.body = String(evt.payload["body"]);
14212
- cur.linkedPrs = parsePrRefs(cur.body);
14213
- }
14214
- const nextStatus = resolveTicketStatusFromUpdatePayload(evt.payload);
14215
- if (nextStatus !== void 0) {
14216
- applyStatusIfChanged(cur, evt, nextStatus);
14217
- }
14218
- applyTicketAssigneeFromPayload(cur, evt.payload);
14219
- applyTicketStoryIdFromPayload(cur, evt.payload);
14220
- applyTicketBranchesFromUpdatePayload(cur, evt.payload);
14221
- applyTicketPlanningFieldsFromUpdatePayload(cur, evt.payload);
14222
- applyLastUpdate(cur, evt);
14223
- break;
14076
+ return slugFromGithubPath(u.pathname);
14077
+ } catch {
14078
+ return void 0;
14079
+ }
14080
+ };
14081
+ var slugFromGithubPath = (path) => {
14082
+ const segments = path.replace(/^\/+/, "").split("/").filter((s) => s.length > 0).map((s) => s.replace(/\.git$/i, ""));
14083
+ if (segments.length < 2) {
14084
+ return void 0;
14085
+ }
14086
+ const owner = segments[0];
14087
+ const repo = segments[1];
14088
+ if (!owner || !repo) {
14089
+ return void 0;
14090
+ }
14091
+ return `${owner}/${repo}`;
14092
+ };
14093
+
14094
+ // src/git/try-read-github-owner-repo-slug-from-git.ts
14095
+ var tryReadGithubOwnerRepoSlugFromGit = async (params) => {
14096
+ try {
14097
+ const { stdout } = await params.runGit(params.repoRoot, [
14098
+ "remote",
14099
+ "get-url",
14100
+ params.remote
14101
+ ]);
14102
+ return parseGithubOwnerRepoFromRemoteUrl(stdout);
14103
+ } catch {
14104
+ return void 0;
14105
+ }
14106
+ };
14107
+
14108
+ // src/git/resolve-effective-git-author-for-data-commit.ts
14109
+ var defaultDataCommitName = "hyper-pm";
14110
+ var defaultDataCommitEmail = "hyper-pm@users.noreply.github.com";
14111
+ var tryReadGitConfigUserName = async (cwd, runGit2) => {
14112
+ try {
14113
+ const { stdout } = await runGit2(cwd, ["config", "--get", "user.name"]);
14114
+ return stdout.trim();
14115
+ } catch {
14116
+ return "";
14117
+ }
14118
+ };
14119
+ var tryReadGitConfigUserEmail = async (cwd, runGit2) => {
14120
+ try {
14121
+ const { stdout } = await runGit2(cwd, ["config", "--get", "user.email"]);
14122
+ return stdout.trim();
14123
+ } catch {
14124
+ return "";
14125
+ }
14126
+ };
14127
+ var resolveEffectiveGitAuthorForDataCommit = async (cwd, runGit2, authorEnv) => {
14128
+ const fromGitName = await tryReadGitConfigUserName(cwd, runGit2);
14129
+ const fromGitEmail = await tryReadGitConfigUserEmail(cwd, runGit2);
14130
+ const name = fromGitName || authorEnv.HYPER_PM_GIT_USER_NAME?.trim() || authorEnv.GIT_AUTHOR_NAME?.trim() || defaultDataCommitName;
14131
+ const email = fromGitEmail || authorEnv.HYPER_PM_GIT_USER_EMAIL?.trim() || authorEnv.GIT_AUTHOR_EMAIL?.trim() || defaultDataCommitEmail;
14132
+ return { name, email };
14133
+ };
14134
+
14135
+ // src/run/commit-data.ts
14136
+ var maxActorSuffixLen = 60;
14137
+ var formatDataBranchCommitMessage = (base, actorSuffix) => {
14138
+ const raw = actorSuffix?.trim();
14139
+ if (!raw) {
14140
+ return base;
14141
+ }
14142
+ const collapsed = raw.replace(/\s+/g, " ");
14143
+ const suffix = collapsed.length > maxActorSuffixLen ? `${collapsed.slice(0, maxActorSuffixLen - 1)}\u2026` : collapsed;
14144
+ return `${base} (${suffix})`;
14145
+ };
14146
+ var commitDataWorktreeIfNeeded = async (worktreePath, message, runGit2, opts) => {
14147
+ const { stdout } = await runGit2(worktreePath, ["status", "--porcelain"]);
14148
+ if (!stdout.trim()) return;
14149
+ await runGit2(worktreePath, ["add", "."]);
14150
+ const authorEnv = opts?.authorEnv ?? env;
14151
+ const { name, email } = await resolveEffectiveGitAuthorForDataCommit(
14152
+ worktreePath,
14153
+ runGit2,
14154
+ authorEnv
14155
+ );
14156
+ await runGit2(worktreePath, [
14157
+ "-c",
14158
+ `user.name=${name}`,
14159
+ "-c",
14160
+ `user.email=${email}`,
14161
+ "commit",
14162
+ "-m",
14163
+ message
14164
+ ]);
14165
+ };
14166
+
14167
+ // src/run/push-data-branch.ts
14168
+ var firstLineFromUnknown = (err) => {
14169
+ const raw = err instanceof Error ? err.message : String(err);
14170
+ const line = raw.trim().split("\n")[0]?.trim() ?? raw.trim();
14171
+ return line.length > 0 ? line : "git error";
14172
+ };
14173
+ var tryPushDataBranchToRemote = async (worktreePath, remote, branch, runGit2) => {
14174
+ try {
14175
+ await runGit2(worktreePath, ["remote", "get-url", remote]);
14176
+ } catch (e) {
14177
+ return {
14178
+ status: "skipped_no_remote",
14179
+ detail: firstLineFromUnknown(e)
14180
+ };
14181
+ }
14182
+ try {
14183
+ await runGit2(worktreePath, ["push", "-u", remote, branch]);
14184
+ return { status: "pushed" };
14185
+ } catch (e) {
14186
+ return {
14187
+ status: "failed",
14188
+ detail: firstLineFromUnknown(e)
14189
+ };
14190
+ }
14191
+ };
14192
+
14193
+ // src/run/sync-remote-data-branch.ts
14194
+ var SyncRemoteDataBranchMergeError = class extends Error {
14195
+ /** @param message - Human-readable reason (stderr excerpt or generic text). */
14196
+ constructor(message) {
14197
+ super(message);
14198
+ this.name = "SyncRemoteDataBranchMergeError";
14199
+ }
14200
+ };
14201
+ var remoteTrackingRef = (remote, dataBranch) => `refs/remotes/${remote}/${dataBranch}`;
14202
+ var mergeRefSpecifier = (remote, dataBranch) => `${remote}/${dataBranch}`;
14203
+ var isLikelyNonFastForwardPushFailure = (detail) => {
14204
+ if (detail === void 0) return false;
14205
+ const d = detail.toLowerCase();
14206
+ return d.includes("non-fast-forward") || d.includes("failed to push");
14207
+ };
14208
+ var classifyMergeOutput = (combined) => {
14209
+ const c = combined.toLowerCase();
14210
+ if (c.includes("already up to date")) {
14211
+ return "up_to_date";
14212
+ }
14213
+ if (c.includes("fast-forward")) {
14214
+ return "fast_forward";
14215
+ }
14216
+ return "merge_commit";
14217
+ };
14218
+ var refExists = async (cwd, ref, runGit2) => {
14219
+ try {
14220
+ await runGit2(cwd, ["show-ref", "--verify", "--quiet", ref]);
14221
+ return true;
14222
+ } catch {
14223
+ return false;
14224
+ }
14225
+ };
14226
+ var fetchRemoteDataBranch = async (worktreePath, remote, dataBranch, runGit2) => {
14227
+ try {
14228
+ await runGit2(worktreePath, ["remote", "get-url", remote]);
14229
+ } catch {
14230
+ return "skipped_no_remote";
14231
+ }
14232
+ try {
14233
+ await runGit2(worktreePath, ["fetch", remote, dataBranch]);
14234
+ return "ok";
14235
+ } catch (e) {
14236
+ const msg = e instanceof Error ? e.message : String(e);
14237
+ if (/couldn't find remote ref|could not find remote ref/i.test(msg)) {
14238
+ return "remote_branch_absent";
14224
14239
  }
14225
- case "TicketDeleted": {
14226
- const id = String(evt.payload["id"]);
14227
- const cur = projection.tickets.get(id);
14228
- if (cur) {
14229
- cur.deleted = true;
14230
- applyLastUpdate(cur, evt);
14231
- }
14232
- break;
14240
+ throw e;
14241
+ }
14242
+ };
14243
+ var mergeRemoteTrackingIntoHead = async (worktreePath, remote, dataBranch, runGit2, authorEnv = env) => {
14244
+ const spec = mergeRefSpecifier(remote, dataBranch);
14245
+ const { name, email } = await resolveEffectiveGitAuthorForDataCommit(
14246
+ worktreePath,
14247
+ runGit2,
14248
+ authorEnv
14249
+ );
14250
+ try {
14251
+ const { stdout, stderr } = await runGit2(worktreePath, [
14252
+ "-c",
14253
+ `user.name=${name}`,
14254
+ "-c",
14255
+ `user.email=${email}`,
14256
+ "merge",
14257
+ "--no-edit",
14258
+ spec
14259
+ ]);
14260
+ return classifyMergeOutput(`${stdout}
14261
+ ${stderr}`);
14262
+ } catch (e) {
14263
+ await runGit2(worktreePath, ["merge", "--abort"]).catch(() => {
14264
+ });
14265
+ const msg = e instanceof Error ? e.message : String(e);
14266
+ throw new SyncRemoteDataBranchMergeError(
14267
+ msg.toLowerCase().includes("conflict") ? "Merge conflict while syncing hyper-pm data branch; merge aborted. Resolve manually on the data branch if needed." : `git merge failed: ${msg.trim().split("\n")[0] ?? msg}`
14268
+ );
14269
+ }
14270
+ };
14271
+ var runRemoteDataBranchGitSync = async (worktreePath, remote, dataBranch, runGit2, skipPush, deps = {}) => {
14272
+ const tryPushFn = deps.tryPush ?? tryPushDataBranchToRemote;
14273
+ const maxPushAttempts = deps.maxPushAttempts ?? 3;
14274
+ const authorEnv = deps.authorEnv ?? env;
14275
+ let dataBranchFetch = await fetchRemoteDataBranch(worktreePath, remote, dataBranch, runGit2);
14276
+ let dataBranchMerge = dataBranchFetch === "skipped_no_remote" ? "skipped_no_remote" : dataBranchFetch === "remote_branch_absent" ? "skipped_missing_remote_branch" : "up_to_date";
14277
+ if (dataBranchFetch === "ok") {
14278
+ const tracking = remoteTrackingRef(remote, dataBranch);
14279
+ const exists = await refExists(worktreePath, tracking, runGit2);
14280
+ if (!exists) {
14281
+ dataBranchMerge = "skipped_missing_remote_branch";
14282
+ } else {
14283
+ dataBranchMerge = await mergeRemoteTrackingIntoHead(
14284
+ worktreePath,
14285
+ remote,
14286
+ dataBranch,
14287
+ runGit2,
14288
+ authorEnv
14289
+ );
14233
14290
  }
14234
- case "TicketCommentAdded": {
14235
- const ticketId = String(evt.payload["ticketId"] ?? "");
14236
- const ticket = projection.tickets.get(ticketId);
14237
- if (!ticket || ticket.deleted) break;
14238
- appendTicketCommentFromEvent(ticket, evt);
14239
- break;
14291
+ }
14292
+ if (skipPush) {
14293
+ return {
14294
+ dataBranchFetch,
14295
+ dataBranchMerge,
14296
+ dataBranchPush: "skipped_cli",
14297
+ dataBranchPushDetail: "skip-push",
14298
+ pushAttempts: 0
14299
+ };
14300
+ }
14301
+ let pushAttempts = 0;
14302
+ let lastPush = { status: "skipped_no_remote" };
14303
+ const refetchAndMerge = async () => {
14304
+ dataBranchFetch = await fetchRemoteDataBranch(
14305
+ worktreePath,
14306
+ remote,
14307
+ dataBranch,
14308
+ runGit2
14309
+ );
14310
+ if (dataBranchFetch !== "ok") {
14311
+ return;
14240
14312
  }
14241
- case "SyncCursor": {
14242
- projection.syncCursor = String(evt.payload["cursor"] ?? "");
14243
- break;
14313
+ const tracking = remoteTrackingRef(remote, dataBranch);
14314
+ const exists = await refExists(worktreePath, tracking, runGit2);
14315
+ if (!exists) {
14316
+ return;
14244
14317
  }
14245
- case "GithubIssueLinked": {
14246
- const ticketId = String(evt.payload["ticketId"]);
14247
- const num = Number(evt.payload["issueNumber"]);
14248
- const ticket = projection.tickets.get(ticketId);
14249
- if (ticket && Number.isFinite(num)) {
14250
- ticket.githubIssueNumber = num;
14251
- applyLastUpdate(ticket, evt);
14252
- }
14318
+ dataBranchMerge = await mergeRemoteTrackingIntoHead(
14319
+ worktreePath,
14320
+ remote,
14321
+ dataBranch,
14322
+ runGit2,
14323
+ authorEnv
14324
+ );
14325
+ };
14326
+ for (let attempt = 1; attempt <= maxPushAttempts; attempt += 1) {
14327
+ pushAttempts = attempt;
14328
+ lastPush = await tryPushFn(worktreePath, remote, dataBranch, runGit2);
14329
+ if (lastPush.status === "pushed" || lastPush.status === "skipped_no_remote") {
14253
14330
  break;
14254
14331
  }
14255
- case "GithubInboundUpdate": {
14256
- const entity = String(evt.payload["entity"]);
14257
- const entityId = String(evt.payload["entityId"]);
14258
- if (entity === "ticket") {
14259
- const ticket = projection.tickets.get(entityId);
14260
- if (!ticket) break;
14261
- if (evt.payload["title"] !== void 0) {
14262
- ticket.title = String(evt.payload["title"]);
14263
- }
14264
- if (evt.payload["body"] !== void 0) {
14265
- ticket.body = String(evt.payload["body"]);
14266
- ticket.linkedPrs = parsePrRefs(ticket.body);
14267
- }
14268
- const inboundStatus = resolveInboundTicketStatusFromPayload(
14269
- ticket,
14270
- evt.payload
14271
- );
14272
- if (inboundStatus !== void 0) {
14273
- applyStatusIfChanged(ticket, evt, inboundStatus);
14274
- }
14275
- applyTicketAssigneeFromPayload(ticket, evt.payload);
14276
- applyTicketPlanningFieldsFromUpdatePayload(ticket, evt.payload);
14277
- applyLastUpdate(ticket, evt);
14278
- }
14279
- break;
14332
+ if (lastPush.status === "failed" && isLikelyNonFastForwardPushFailure(lastPush.detail) && attempt < maxPushAttempts) {
14333
+ await refetchAndMerge();
14334
+ continue;
14280
14335
  }
14281
- case "GithubPrActivity": {
14282
- const summary = parseGithubPrActivityPayload(evt.payload);
14283
- if (!summary) break;
14284
- const ticket = projection.tickets.get(
14285
- String(evt.payload["ticketId"] ?? "")
14286
- );
14287
- if (!ticket || ticket.deleted) break;
14288
- const list = ticket.prActivityRecent ?? (ticket.prActivityRecent = []);
14289
- list.push(summary);
14290
- if (list.length > GITHUB_PR_ACTIVITY_RECENT_CAP) {
14291
- ticket.prActivityRecent = list.slice(-GITHUB_PR_ACTIVITY_RECENT_CAP);
14292
- }
14293
- break;
14336
+ break;
14337
+ }
14338
+ return {
14339
+ dataBranchFetch,
14340
+ dataBranchMerge,
14341
+ dataBranchPush: lastPush.status,
14342
+ ...lastPush.detail !== void 0 ? { dataBranchPushDetail: lastPush.detail } : {},
14343
+ pushAttempts
14344
+ };
14345
+ };
14346
+
14347
+ // src/storage/append-event.ts
14348
+ var import_promises5 = require("node:fs/promises");
14349
+ var import_node_path5 = require("node:path");
14350
+
14351
+ // src/storage/event-path.ts
14352
+ var nextEventRelPath = (now, deps) => {
14353
+ const y = String(now.getUTCFullYear());
14354
+ const m = String(now.getUTCMonth() + 1).padStart(2, "0");
14355
+ const nextId = deps?.nextId ?? (() => ulid().toLowerCase());
14356
+ const part = `part-${nextId()}`;
14357
+ return `events/${y}/${m}/${part}.jsonl`;
14358
+ };
14359
+
14360
+ // src/storage/append-event.ts
14361
+ var appendEventLine = async (dataRoot, event, clock, opts) => {
14362
+ const rel = nextEventRelPath(clock.now(), {
14363
+ nextId: opts?.nextEventId
14364
+ });
14365
+ const abs = `${dataRoot.replace(/\/$/, "")}/${rel}`;
14366
+ await (0, import_promises5.mkdir)((0, import_node_path5.dirname)(abs), { recursive: true });
14367
+ await (0, import_promises5.appendFile)(abs, `${JSON.stringify(event)}
14368
+ `, "utf8");
14369
+ return rel;
14370
+ };
14371
+
14372
+ // src/storage/read-event-lines.ts
14373
+ var import_promises6 = require("node:fs/promises");
14374
+ var import_node_path6 = require("node:path");
14375
+ var listJsonlFiles = async (dir, root) => {
14376
+ const out = [];
14377
+ const entries = await (0, import_promises6.readdir)(dir, { withFileTypes: true });
14378
+ for (const e of entries) {
14379
+ const abs = (0, import_node_path6.join)(dir, e.name);
14380
+ if (e.isDirectory()) {
14381
+ out.push(...await listJsonlFiles(abs, root));
14382
+ } else if (e.isFile() && e.name.endsWith(".jsonl")) {
14383
+ out.push((0, import_node_path6.relative)(root, abs).split("\\").join("/"));
14294
14384
  }
14295
- default:
14296
- break;
14297
14385
  }
14386
+ return out;
14298
14387
  };
14299
- var replayEvents = (lines) => {
14300
- const proj = emptyProjection();
14301
- const events = [];
14302
- for (const line of lines) {
14303
- const trimmed = line.trim();
14304
- if (!trimmed) continue;
14305
- const json = JSON.parse(trimmed);
14306
- events.push(eventLineSchema.parse(json));
14388
+ var readAllEventLines = async (dataRoot) => {
14389
+ const eventsRoot = (0, import_node_path6.join)(dataRoot, "events");
14390
+ let files = [];
14391
+ try {
14392
+ files = await listJsonlFiles(eventsRoot, dataRoot);
14393
+ } catch {
14394
+ return [];
14307
14395
  }
14308
- events.sort((a, b) => {
14309
- const t = a.ts.localeCompare(b.ts);
14310
- if (t !== 0) return t;
14311
- return a.id.localeCompare(b.id);
14312
- });
14313
- for (const e of events) {
14314
- applyEvent(proj, e);
14396
+ files.sort((a, b) => a.localeCompare(b));
14397
+ const lines = [];
14398
+ for (const rel of files) {
14399
+ const content = await (0, import_promises6.readFile)((0, import_node_path6.join)(dataRoot, rel), "utf8");
14400
+ for (const line of content.split("\n")) {
14401
+ if (line.trim()) lines.push(line);
14402
+ }
14315
14403
  }
14316
- return proj;
14404
+ return lines;
14317
14405
  };
14318
14406
 
14319
14407
  // src/sync/collect-github-pr-activity-source-ids.ts
@@ -14873,14 +14961,15 @@ var parseCliWorkItemStatusList = (raws, deps) => {
14873
14961
  }
14874
14962
  return out;
14875
14963
  };
14876
- var buildTicketListQueryFromReadListOpts = (o, deps) => {
14964
+ var buildTicketListQueryFromReadListOpts = (projection, o, deps) => {
14877
14965
  const query = {};
14878
14966
  const statusTokens = normalizeCliStringList(o.status);
14879
14967
  if (statusTokens.length > 0) {
14880
14968
  query.statuses = parseCliWorkItemStatusList(statusTokens, deps);
14881
14969
  }
14882
14970
  if (o.epic !== void 0 && o.epic !== "") {
14883
- query.epicId = o.epic;
14971
+ const trimmed = o.epic.trim();
14972
+ query.epicId = resolveEpicId(projection, trimmed) ?? trimmed;
14884
14973
  }
14885
14974
  const createdAfterMs = parseCliOptionalIsoMillis(
14886
14975
  o.createdAfter,
@@ -15018,7 +15107,8 @@ var buildTicketListQueryFromReadListOpts = (o, deps) => {
15018
15107
  query.targetFinishBeforeMs = targetFinishBeforeMs;
15019
15108
  }
15020
15109
  if (o.dependsOn !== void 0 && o.dependsOn.trim() !== "") {
15021
- query.dependsOnIncludesId = o.dependsOn.trim();
15110
+ const trimmed = o.dependsOn.trim();
15111
+ query.dependsOnIncludesId = resolveTicketId(projection, trimmed) ?? trimmed;
15022
15112
  }
15023
15113
  return Object.keys(query).length > 0 ? query : void 0;
15024
15114
  };
@@ -15095,19 +15185,19 @@ var runCli = async (argv, deps = {
15095
15185
  const g = readGlobals(this);
15096
15186
  const o = this.opts();
15097
15187
  await mutateDataBranch(g, deps, async (root, { actor }) => {
15188
+ const lines = await readAllEventLines(root);
15189
+ const proj = replayEvents(lines);
15098
15190
  const id = o.id ?? ulid();
15099
15191
  const status = parseCliWorkItemStatus(o.status, deps);
15100
- const evt = makeEvent(
15101
- "EpicCreated",
15102
- {
15103
- id,
15104
- title: o.title,
15105
- body: o.body,
15106
- ...status !== void 0 ? { status } : {}
15107
- },
15108
- deps.clock,
15109
- actor
15110
- );
15192
+ const payload = {
15193
+ id,
15194
+ number: nextEpicNumberForCreate(proj),
15195
+ title: o.title,
15196
+ body: o.body,
15197
+ ...status !== void 0 ? { status } : {}
15198
+ };
15199
+ assertCreatePayloadUsesExpectedHeadNumber(proj, "epic", payload);
15200
+ const evt = makeEvent("EpicCreated", payload, deps.clock, actor);
15111
15201
  await appendEventLine(root, evt, deps.clock);
15112
15202
  return evt.payload;
15113
15203
  });
@@ -15123,18 +15213,19 @@ var runCli = async (argv, deps = {
15123
15213
  await mutateDataBranch(g, deps, async (root, { actor }) => {
15124
15214
  const lines = await readAllEventLines(root);
15125
15215
  const proj = replayEvents(lines);
15126
- const cur = proj.epics.get(o.id);
15216
+ const epicId = resolveEpicId(proj, o.id) ?? o.id.trim();
15217
+ const cur = proj.epics.get(epicId);
15127
15218
  if (!cur || cur.deleted) {
15128
15219
  throw new Error(`Epic not found: ${o.id}`);
15129
15220
  }
15130
15221
  const status = parseCliWorkItemStatus(o.status, deps);
15131
- const draft = { id: o.id };
15222
+ const draft = { id: epicId };
15132
15223
  if (o.title !== void 0) draft["title"] = o.title;
15133
15224
  if (o.body !== void 0) draft["body"] = o.body;
15134
15225
  if (status !== void 0) draft["status"] = status;
15135
15226
  const payload = pruneEpicOrStoryUpdatePayloadAgainstRow(cur, draft);
15136
15227
  if (isNoOpUpdatePayload(payload)) {
15137
- return { id: o.id, noChanges: true };
15228
+ return { id: epicId, noChanges: true };
15138
15229
  }
15139
15230
  const evt = makeEvent("EpicUpdated", payload, deps.clock, actor);
15140
15231
  await appendEventLine(root, evt, deps.clock);
@@ -15145,9 +15236,12 @@ var runCli = async (argv, deps = {
15145
15236
  const g = readGlobals(this);
15146
15237
  const o = this.opts();
15147
15238
  await mutateDataBranch(g, deps, async (root, { actor }) => {
15148
- const evt = makeEvent("EpicDeleted", { id: o.id }, deps.clock, actor);
15239
+ const lines = await readAllEventLines(root);
15240
+ const proj = replayEvents(lines);
15241
+ const epicId = resolveEpicId(proj, o.id) ?? o.id.trim();
15242
+ const evt = makeEvent("EpicDeleted", { id: epicId }, deps.clock, actor);
15149
15243
  await appendEventLine(root, evt, deps.clock);
15150
- return { id: o.id, deleted: true };
15244
+ return { id: epicId, deleted: true };
15151
15245
  });
15152
15246
  });
15153
15247
  const story = program2.command("story");
@@ -15160,24 +15254,23 @@ var runCli = async (argv, deps = {
15160
15254
  await mutateDataBranch(g, deps, async (root, { actor }) => {
15161
15255
  const lines = await readAllEventLines(root);
15162
15256
  const proj = replayEvents(lines);
15163
- const epic2 = proj.epics.get(o.epic);
15257
+ const epicId = resolveEpicId(proj, o.epic) ?? o.epic.trim();
15258
+ const epic2 = proj.epics.get(epicId);
15164
15259
  if (!epic2 || epic2.deleted) {
15165
15260
  throw new Error(`Epic not found: ${o.epic}`);
15166
15261
  }
15167
15262
  const id = o.id ?? ulid();
15168
15263
  const status = parseCliWorkItemStatus(o.status, deps);
15169
- const evt = makeEvent(
15170
- "StoryCreated",
15171
- {
15172
- id,
15173
- epicId: o.epic,
15174
- title: o.title,
15175
- body: o.body,
15176
- ...status !== void 0 ? { status } : {}
15177
- },
15178
- deps.clock,
15179
- actor
15180
- );
15264
+ const payload = {
15265
+ id,
15266
+ number: nextStoryNumberForCreate(proj),
15267
+ epicId,
15268
+ title: o.title,
15269
+ body: o.body,
15270
+ ...status !== void 0 ? { status } : {}
15271
+ };
15272
+ assertCreatePayloadUsesExpectedHeadNumber(proj, "story", payload);
15273
+ const evt = makeEvent("StoryCreated", payload, deps.clock, actor);
15181
15274
  await appendEventLine(root, evt, deps.clock);
15182
15275
  return evt.payload;
15183
15276
  });
@@ -15196,18 +15289,19 @@ var runCli = async (argv, deps = {
15196
15289
  await mutateDataBranch(g, deps, async (root, { actor }) => {
15197
15290
  const lines = await readAllEventLines(root);
15198
15291
  const proj = replayEvents(lines);
15199
- const cur = proj.stories.get(o.id);
15292
+ const storyId = resolveStoryId(proj, o.id) ?? o.id.trim();
15293
+ const cur = proj.stories.get(storyId);
15200
15294
  if (!cur || cur.deleted) {
15201
15295
  throw new Error(`Story not found: ${o.id}`);
15202
15296
  }
15203
15297
  const status = parseCliWorkItemStatus(o.status, deps);
15204
- const draft = { id: o.id };
15298
+ const draft = { id: storyId };
15205
15299
  if (o.title !== void 0) draft["title"] = o.title;
15206
15300
  if (o.body !== void 0) draft["body"] = o.body;
15207
15301
  if (status !== void 0) draft["status"] = status;
15208
15302
  const payload = pruneEpicOrStoryUpdatePayloadAgainstRow(cur, draft);
15209
15303
  if (isNoOpUpdatePayload(payload)) {
15210
- return { id: o.id, noChanges: true };
15304
+ return { id: storyId, noChanges: true };
15211
15305
  }
15212
15306
  const evt = makeEvent("StoryUpdated", payload, deps.clock, actor);
15213
15307
  await appendEventLine(root, evt, deps.clock);
@@ -15218,9 +15312,17 @@ var runCli = async (argv, deps = {
15218
15312
  const g = readGlobals(this);
15219
15313
  const o = this.opts();
15220
15314
  await mutateDataBranch(g, deps, async (root, { actor }) => {
15221
- const evt = makeEvent("StoryDeleted", { id: o.id }, deps.clock, actor);
15315
+ const lines = await readAllEventLines(root);
15316
+ const proj = replayEvents(lines);
15317
+ const storyId = resolveStoryId(proj, o.id) ?? o.id.trim();
15318
+ const evt = makeEvent(
15319
+ "StoryDeleted",
15320
+ { id: storyId },
15321
+ deps.clock,
15322
+ actor
15323
+ );
15222
15324
  await appendEventLine(root, evt, deps.clock);
15223
- return { id: o.id, deleted: true };
15325
+ return { id: storyId, deleted: true };
15224
15326
  });
15225
15327
  });
15226
15328
  const ticket = program2.command("ticket");
@@ -15311,9 +15413,6 @@ var runCli = async (argv, deps = {
15311
15413
  deps
15312
15414
  );
15313
15415
  const dependsOnTokensCreate = normalizeCliStringList(o.dependsOn);
15314
- const dependsOnNormCreate = normalizeTicketDependsOnIds(
15315
- dependsOnTokensCreate
15316
- );
15317
15416
  const planningPayload = {
15318
15417
  ...labelsPayloadCreate,
15319
15418
  ...priorityParsed !== void 0 ? { priority: priorityParsed } : {},
@@ -15327,16 +15426,21 @@ var runCli = async (argv, deps = {
15327
15426
  const proj = replayEvents(lines);
15328
15427
  const storyRaw = o.story;
15329
15428
  const storyTrimmed = storyRaw !== void 0 && storyRaw !== "" ? storyRaw.trim() : void 0;
15429
+ const storyIdResolved = storyTrimmed !== void 0 ? resolveStoryId(proj, storyTrimmed) ?? storyTrimmed : void 0;
15330
15430
  if (storyTrimmed !== void 0) {
15331
- const storyRow = proj.stories.get(storyTrimmed);
15431
+ const storyRow = proj.stories.get(storyIdResolved ?? "");
15332
15432
  if (!storyRow || storyRow.deleted) {
15333
15433
  throw new Error(`Story not found: ${storyTrimmed}`);
15334
15434
  }
15335
15435
  }
15336
15436
  const id = o.id ?? ulid();
15437
+ const dependsOnNormCreate = resolveTicketDependsOnTokensToIds(
15438
+ proj,
15439
+ dependsOnTokensCreate
15440
+ );
15337
15441
  const status = parseCliWorkItemStatus(o.status, deps);
15338
15442
  const assigneeCreate = o.assignee !== void 0 ? { assignee: normalizeGithubLogin(o.assignee) } : {};
15339
- const storyPayload = storyTrimmed !== void 0 ? { storyId: storyTrimmed } : {};
15443
+ const storyPayload = storyIdResolved !== void 0 ? { storyId: storyIdResolved } : {};
15340
15444
  const branchTokens = normalizeCliStringList(o.branch);
15341
15445
  const branchesNorm = normalizeTicketBranchListFromStrings(branchTokens);
15342
15446
  const branchesPayload = branchesNorm.length > 0 ? { branches: branchesNorm } : {};
@@ -15349,19 +15453,26 @@ var runCli = async (argv, deps = {
15349
15453
  throw new Error(dependsOnErr);
15350
15454
  }
15351
15455
  const dependsOnPayloadCreate = dependsOnNormCreate.length > 0 ? { dependsOn: dependsOnNormCreate } : {};
15456
+ const createPayload = {
15457
+ id,
15458
+ number: nextTicketNumberForCreate(proj),
15459
+ ...storyPayload,
15460
+ title: o.title,
15461
+ body,
15462
+ ...status !== void 0 ? { status } : {},
15463
+ ...assigneeCreate,
15464
+ ...branchesPayload,
15465
+ ...dependsOnPayloadCreate,
15466
+ ...planningPayload
15467
+ };
15468
+ assertCreatePayloadUsesExpectedHeadNumber(
15469
+ proj,
15470
+ "ticket",
15471
+ createPayload
15472
+ );
15352
15473
  const evt = makeEvent(
15353
15474
  "TicketCreated",
15354
- {
15355
- id,
15356
- ...storyPayload,
15357
- title: o.title,
15358
- body,
15359
- ...status !== void 0 ? { status } : {},
15360
- ...assigneeCreate,
15361
- ...branchesPayload,
15362
- ...dependsOnPayloadCreate,
15363
- ...planningPayload
15364
- },
15475
+ createPayload,
15365
15476
  deps.clock,
15366
15477
  actor
15367
15478
  );
@@ -15633,25 +15744,27 @@ ${body}`
15633
15744
  await mutateDataBranch(g, deps, async (root, { actor }) => {
15634
15745
  const lines = await readAllEventLines(root);
15635
15746
  const proj = replayEvents(lines);
15747
+ const ticketId = resolveTicketId(proj, o.id) ?? o.id.trim();
15748
+ const storyIdResolved = storyTrimmed !== void 0 ? resolveStoryId(proj, storyTrimmed) ?? storyTrimmed : void 0;
15636
15749
  if (storyTrimmed !== void 0) {
15637
- const storyRow = proj.stories.get(storyTrimmed);
15750
+ const storyRow = proj.stories.get(storyIdResolved ?? "");
15638
15751
  if (!storyRow || storyRow.deleted) {
15639
15752
  throw new Error(`Story not found: ${storyTrimmed}`);
15640
15753
  }
15641
15754
  }
15642
- const curTicket = proj.tickets.get(o.id);
15755
+ const curTicket = proj.tickets.get(ticketId);
15643
15756
  if (curTicket === void 0 || curTicket.deleted) {
15644
15757
  throw new Error(`Ticket not found: ${o.id}`);
15645
15758
  }
15646
15759
  const status = parseCliWorkItemStatus(o.status, deps);
15647
- const payload = { id: o.id };
15760
+ const payload = { id: ticketId };
15648
15761
  if (o.title !== void 0) payload["title"] = o.title;
15649
15762
  if (body !== void 0) payload["body"] = body;
15650
15763
  if (status !== void 0) payload["status"] = status;
15651
15764
  if (o.unlinkStory) {
15652
15765
  payload["storyId"] = null;
15653
15766
  } else if (storyTrimmed !== void 0) {
15654
- payload["storyId"] = storyTrimmed;
15767
+ payload["storyId"] = storyIdResolved;
15655
15768
  }
15656
15769
  if (o.unassign) {
15657
15770
  payload["assignee"] = null;
@@ -15708,19 +15821,19 @@ ${body}`
15708
15821
  nextDepends = [];
15709
15822
  } else {
15710
15823
  const removeDepSet = new Set(
15711
- normalizeTicketDependsOnIds(removeDependsOnTokens)
15824
+ resolveTicketDependsOnTokensToIds(proj, removeDependsOnTokens)
15712
15825
  );
15713
15826
  nextDepends = normalizeTicketDependsOnIds(
15714
15827
  (curTicket.dependsOn ?? []).filter((d) => !removeDepSet.has(d))
15715
15828
  );
15716
15829
  nextDepends = normalizeTicketDependsOnIds([
15717
15830
  ...nextDepends,
15718
- ...addDependsOnTokens
15831
+ ...resolveTicketDependsOnTokensToIds(proj, addDependsOnTokens)
15719
15832
  ]);
15720
15833
  }
15721
15834
  const depErr = validateTicketDependsOnForWrite({
15722
15835
  projection: proj,
15723
- fromTicketId: o.id,
15836
+ fromTicketId: ticketId,
15724
15837
  nextDependsOn: nextDepends
15725
15838
  });
15726
15839
  if (depErr !== void 0) {
@@ -15750,7 +15863,7 @@ ${body}`
15750
15863
  payload
15751
15864
  );
15752
15865
  if (isNoOpUpdatePayload(prunedPayload)) {
15753
- return { id: o.id, noChanges: true };
15866
+ return { id: ticketId, noChanges: true };
15754
15867
  }
15755
15868
  const evt = makeEvent(
15756
15869
  "TicketUpdated",
@@ -15787,25 +15900,25 @@ ${body}`
15787
15900
  runGit
15788
15901
  );
15789
15902
  }
15790
- const preferred = normalizeTicketBranchName(
15791
- o.branch ?? `hyper-pm/${o.id}`
15792
- );
15793
- if (preferred === void 0) {
15794
- deps.error(
15795
- "Invalid --branch or default branch name for this ticket id"
15796
- );
15797
- deps.exit(ExitCode.UserError);
15798
- }
15799
15903
  await mutateDataBranch(g, deps, async (root, { actor }) => {
15800
15904
  const lines = await readAllEventLines(root);
15801
15905
  const proj = replayEvents(lines);
15802
- const curRow = proj.tickets.get(o.id);
15906
+ const ticketId = resolveTicketId(proj, o.id) ?? o.id.trim();
15907
+ const preferred = normalizeTicketBranchName(
15908
+ o.branch ?? `hyper-pm/${ticketId}`
15909
+ );
15910
+ if (preferred === void 0) {
15911
+ throw new Error(
15912
+ "Invalid --branch or default branch name for this ticket id"
15913
+ );
15914
+ }
15915
+ const curRow = proj.tickets.get(ticketId);
15803
15916
  if (curRow === void 0 || curRow.deleted) {
15804
15917
  throw new Error(`Ticket not found: ${o.id}`);
15805
15918
  }
15806
15919
  if (curRow.status === "done" || curRow.status === "cancelled") {
15807
15920
  throw new Error(
15808
- `Ticket ${o.id} is ${curRow.status}; change status before starting work`
15921
+ `Ticket ${ticketId} is ${curRow.status}; change status before starting work`
15809
15922
  );
15810
15923
  }
15811
15924
  const { branch: chosenBranch } = await pickUniqueLocalBranchName({
@@ -15826,7 +15939,7 @@ ${body}`
15826
15939
  }
15827
15940
  next = normalizeTicketBranchListFromStrings(next);
15828
15941
  const payload = {
15829
- id: o.id,
15942
+ id: ticketId,
15830
15943
  status: "in_progress"
15831
15944
  };
15832
15945
  if (!ticketBranchListsEqual(next, curRow.linkedBranches)) {
@@ -15835,7 +15948,7 @@ ${body}`
15835
15948
  const evt = makeEvent("TicketUpdated", payload, deps.clock, actor);
15836
15949
  await appendEventLine(root, evt, deps.clock);
15837
15950
  const result = {
15838
- id: o.id,
15951
+ id: ticketId,
15839
15952
  status: "in_progress",
15840
15953
  branch: chosenBranch,
15841
15954
  branches: next
@@ -15857,27 +15970,36 @@ ${body}`
15857
15970
  await mutateDataBranch(g, deps, async (root, { actor }) => {
15858
15971
  const lines = await readAllEventLines(root);
15859
15972
  const proj = replayEvents(lines);
15860
- const row = proj.tickets.get(o.id);
15973
+ const ticketId = resolveTicketId(proj, o.id) ?? o.id.trim();
15974
+ const row = proj.tickets.get(ticketId);
15861
15975
  if (!row || row.deleted) {
15862
15976
  throw new Error(`Ticket not found: ${o.id}`);
15863
15977
  }
15864
15978
  const evt = makeEvent(
15865
15979
  "TicketCommentAdded",
15866
- { ticketId: o.id, body: trimmed },
15980
+ { ticketId, body: trimmed },
15867
15981
  deps.clock,
15868
15982
  actor
15869
15983
  );
15870
15984
  await appendEventLine(root, evt, deps.clock);
15871
- return { commentId: evt.id, ticketId: o.id, body: trimmed };
15985
+ return { commentId: evt.id, ticketId, body: trimmed };
15872
15986
  });
15873
15987
  });
15874
15988
  ticket.command("delete").requiredOption("--id <id>").action(async function() {
15875
15989
  const g = readGlobals(this);
15876
15990
  const o = this.opts();
15877
15991
  await mutateDataBranch(g, deps, async (root, { actor }) => {
15878
- const evt = makeEvent("TicketDeleted", { id: o.id }, deps.clock, actor);
15992
+ const lines = await readAllEventLines(root);
15993
+ const proj = replayEvents(lines);
15994
+ const ticketId = resolveTicketId(proj, o.id) ?? o.id.trim();
15995
+ const evt = makeEvent(
15996
+ "TicketDeleted",
15997
+ { id: ticketId },
15998
+ deps.clock,
15999
+ actor
16000
+ );
15879
16001
  await appendEventLine(root, evt, deps.clock);
15880
- return { id: o.id, deleted: true };
16002
+ return { id: ticketId, deleted: true };
15881
16003
  });
15882
16004
  });
15883
16005
  ticket.command("import-github").description(
@@ -15948,7 +16070,8 @@ ${body}`
15948
16070
  const storyRaw = o.story;
15949
16071
  const storyTrimmed = storyRaw !== void 0 && storyRaw !== "" ? storyRaw.trim() : void 0;
15950
16072
  if (storyTrimmed !== void 0 && storyTrimmed !== "") {
15951
- const storyRow = proj.stories.get(storyTrimmed);
16073
+ const storyIdForImport = resolveStoryId(proj, storyTrimmed) ?? storyTrimmed;
16074
+ const storyRow = proj.stories.get(storyIdForImport);
15952
16075
  if (!storyRow || storyRow.deleted) {
15953
16076
  deps.error(`Story not found: ${storyTrimmed}`);
15954
16077
  deps.exit(ExitCode.UserError);
@@ -15991,12 +16114,22 @@ ${body}`
15991
16114
  );
15992
16115
  } else {
15993
16116
  const imported = [];
16117
+ let importProj = proj;
16118
+ let importLines = lines;
16119
+ const storyIdForPayload = storyTrimmed !== void 0 && storyTrimmed !== "" ? resolveStoryId(importProj, storyTrimmed) ?? storyTrimmed : void 0;
15994
16120
  for (const c of candidates) {
15995
16121
  const ticketId = ulid();
16122
+ const nextNum = nextTicketNumberForCreate(importProj);
15996
16123
  const createPayload = mergeTicketImportCreatePayload(
15997
16124
  ticketId,
15998
16125
  c.ticketCreatedPayloadBase,
15999
- storyTrimmed
16126
+ storyIdForPayload,
16127
+ nextNum
16128
+ );
16129
+ assertCreatePayloadUsesExpectedHeadNumber(
16130
+ importProj,
16131
+ "ticket",
16132
+ createPayload
16000
16133
  );
16001
16134
  const createdEvt = makeEvent(
16002
16135
  "TicketCreated",
@@ -16017,6 +16150,8 @@ ${body}`
16017
16150
  );
16018
16151
  await appendEventLine(session.worktreePath, linkEvt, deps.clock);
16019
16152
  imported.push({ ticketId, issueNumber: c.issueNumber });
16153
+ importLines = await readAllEventLines(session.worktreePath);
16154
+ importProj = replayEvents(importLines);
16020
16155
  }
16021
16156
  await commitDataWorktreeIfNeeded(
16022
16157
  session.worktreePath,
@@ -16484,7 +16619,8 @@ var readEpic = async (g, id, deps) => {
16484
16619
  formatOutput(g.format, { items: listActiveEpicSummaries(proj) })
16485
16620
  );
16486
16621
  } else {
16487
- const row = proj.epics.get(id);
16622
+ const epicId = resolveEpicId(proj, id) ?? id.trim();
16623
+ const row = proj.epics.get(epicId);
16488
16624
  if (!row || row.deleted) {
16489
16625
  deps.error("Epic not found");
16490
16626
  exitCode = ExitCode.UserError;
@@ -16519,7 +16655,8 @@ var readStory = async (g, opts, deps) => {
16519
16655
  const proj = replayEvents(lines);
16520
16656
  const { id, epicId } = opts;
16521
16657
  if (id === void 0 || id === "") {
16522
- const epicFilter = epicId !== void 0 && epicId !== "" ? epicId : void 0;
16658
+ const epicFilterRaw = epicId !== void 0 && epicId !== "" ? epicId : void 0;
16659
+ const epicFilter = epicFilterRaw !== void 0 ? resolveEpicId(proj, epicFilterRaw) ?? epicFilterRaw.trim() : void 0;
16523
16660
  if (epicFilter !== void 0) {
16524
16661
  const epicRow = proj.epics.get(epicFilter);
16525
16662
  if (!epicRow || epicRow.deleted) {
@@ -16540,7 +16677,8 @@ var readStory = async (g, opts, deps) => {
16540
16677
  );
16541
16678
  }
16542
16679
  } else {
16543
- const row = proj.stories.get(id);
16680
+ const storyId = resolveStoryId(proj, id) ?? id.trim();
16681
+ const row = proj.stories.get(storyId);
16544
16682
  if (!row || row.deleted) {
16545
16683
  deps.error("Story not found");
16546
16684
  exitCode = ExitCode.UserError;
@@ -16584,8 +16722,8 @@ var readTicket = async (g, opts, deps) => {
16584
16722
  } = opts;
16585
16723
  if (id === void 0 || id === "") {
16586
16724
  const listWithoutStory = withoutStoryRaw === true;
16587
- const storyFilter = storyIdRaw !== void 0 && storyIdRaw !== "" ? storyIdRaw : void 0;
16588
- const epicFilter = epicIdRaw !== void 0 && epicIdRaw !== "" ? epicIdRaw : void 0;
16725
+ const storyFilter = storyIdRaw !== void 0 && storyIdRaw !== "" ? resolveStoryId(proj, storyIdRaw) ?? storyIdRaw.trim() : void 0;
16726
+ const epicFilter = epicIdRaw !== void 0 && epicIdRaw !== "" ? resolveEpicId(proj, epicIdRaw) ?? epicIdRaw.trim() : void 0;
16589
16727
  const sortBy = tryParseTicketListSortField(sortByOpt);
16590
16728
  const sortDir = tryParseTicketListSortDir(sortDirOpt);
16591
16729
  if (sortBy === void 0) {
@@ -16624,6 +16762,7 @@ var readTicket = async (g, opts, deps) => {
16624
16762
  exitCode = ExitCode.UserError;
16625
16763
  } else {
16626
16764
  const listQuery = buildTicketListQueryFromReadListOpts(
16765
+ proj,
16627
16766
  { epic: epicFilter, ...listFlagRest },
16628
16767
  deps
16629
16768
  );
@@ -16644,6 +16783,7 @@ var readTicket = async (g, opts, deps) => {
16644
16783
  exitCode = ExitCode.UserError;
16645
16784
  } else {
16646
16785
  const listQuery = buildTicketListQueryFromReadListOpts(
16786
+ proj,
16647
16787
  listFlagRest,
16648
16788
  deps
16649
16789
  );
@@ -16660,6 +16800,7 @@ var readTicket = async (g, opts, deps) => {
16660
16800
  }
16661
16801
  } else if (listWithoutStory) {
16662
16802
  const listQuery = buildTicketListQueryFromReadListOpts(
16803
+ proj,
16663
16804
  { withoutStory: true, ...listFlagRest },
16664
16805
  deps
16665
16806
  );
@@ -16674,6 +16815,7 @@ var readTicket = async (g, opts, deps) => {
16674
16815
  );
16675
16816
  } else {
16676
16817
  const listQuery = buildTicketListQueryFromReadListOpts(
16818
+ proj,
16677
16819
  { epic: epicFilter, ...listFlagRest },
16678
16820
  deps
16679
16821
  );
@@ -16688,7 +16830,8 @@ var readTicket = async (g, opts, deps) => {
16688
16830
  );
16689
16831
  }
16690
16832
  } else {
16691
- const row = proj.tickets.get(id);
16833
+ const ticketId = resolveTicketId(proj, id) ?? id.trim();
16834
+ const row = proj.tickets.get(ticketId);
16692
16835
  if (!row || row.deleted) {
16693
16836
  deps.error("Ticket not found");
16694
16837
  exitCode = ExitCode.UserError;