@harness-engineering/cli 1.16.0 → 1.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/agents/skills/claude-code/harness-roadmap-pilot/SKILL.md +204 -0
  2. package/dist/agents/skills/claude-code/harness-roadmap-pilot/skill.yaml +52 -0
  3. package/dist/agents/skills/codex/harness-roadmap-pilot/SKILL.md +204 -0
  4. package/dist/agents/skills/codex/harness-roadmap-pilot/skill.yaml +52 -0
  5. package/dist/agents/skills/cursor/harness-roadmap-pilot/SKILL.md +204 -0
  6. package/dist/agents/skills/cursor/harness-roadmap-pilot/skill.yaml +52 -0
  7. package/dist/agents/skills/gemini-cli/harness-roadmap-pilot/SKILL.md +204 -0
  8. package/dist/agents/skills/gemini-cli/harness-roadmap-pilot/skill.yaml +52 -0
  9. package/dist/agents/skills/package.json +5 -5
  10. package/dist/{agents-md-VYDFPIRW.js → agents-md-DUYNKHJZ.js} +1 -1
  11. package/dist/{architecture-K5HSRBGB.js → architecture-UBO5KKUV.js} +2 -2
  12. package/dist/bin/harness-mcp.js +12 -12
  13. package/dist/bin/harness.js +15 -15
  14. package/dist/{check-phase-gate-5AS6SXL6.js → check-phase-gate-OSHN2AEL.js} +3 -3
  15. package/dist/{chunk-JOP2NDNB.js → chunk-2DMIQ35P.js} +151 -79
  16. package/dist/{chunk-TF6ZLHJV.js → chunk-5FM64G6D.js} +2 -2
  17. package/dist/{chunk-5ZXHMCPL.js → chunk-ABQUCXRE.js} +2 -1
  18. package/dist/{chunk-AV6KMDO5.js → chunk-APNPXLB2.js} +4 -4
  19. package/dist/{chunk-FTMXDOR6.js → chunk-CZZXE6BL.js} +1 -1
  20. package/dist/{chunk-SFRGPAK6.js → chunk-GWXP3JVA.js} +3 -3
  21. package/dist/{chunk-SHYWICGA.js → chunk-OA3MOZGG.js} +22 -22
  22. package/dist/{chunk-RWZPHW4H.js → chunk-OHZVGIPE.js} +9 -9
  23. package/dist/{chunk-C7DTKLPW.js → chunk-QSRRBNLY.js} +8 -8
  24. package/dist/{chunk-QDF7COPQ.js → chunk-TG7IUJ3J.js} +1 -1
  25. package/dist/{chunk-DNDBFIZN.js → chunk-TZIHFNEG.js} +7 -7
  26. package/dist/{chunk-ZJMU7MEV.js → chunk-UX3JHYEA.js} +1 -1
  27. package/dist/{chunk-ALFKNAZW.js → chunk-VF23UTNB.js} +545 -36
  28. package/dist/{chunk-7MJAPE3Z.js → chunk-YLN34N65.js} +1 -0
  29. package/dist/{chunk-OCDDCGDE.js → chunk-ZA2I7S3E.js} +20 -1
  30. package/dist/{ci-workflow-CRWU723U.js → ci-workflow-FJZMNZPT.js} +1 -1
  31. package/dist/{dist-4LPXJYVZ.js → dist-MF5BK5AD.js} +19 -1
  32. package/dist/{dist-B26DFXMP.js → dist-U7EAO6T2.js} +110 -60
  33. package/dist/{docs-4JRHTLUZ.js → docs-WZHW4N4P.js} +3 -3
  34. package/dist/{engine-3G3VIM6L.js → engine-VS6ZJ2VZ.js} +2 -2
  35. package/dist/{entropy-G6CZ2A6P.js → entropy-FCIGJIIT.js} +2 -2
  36. package/dist/{feedback-QYKQ65HB.js → feedback-O3FYTZIE.js} +1 -1
  37. package/dist/{generate-agent-definitions-SAAOAPT4.js → generate-agent-definitions-EYG263XD.js} +1 -1
  38. package/dist/{graph-loader-2M2HXDQI.js → graph-loader-KMHDQYDT.js} +1 -1
  39. package/dist/index.d.ts +62 -3
  40. package/dist/index.js +15 -15
  41. package/dist/{loader-VCOK3PF7.js → loader-B4XWX4K6.js} +1 -1
  42. package/dist/{mcp-YENEPHBW.js → mcp-DVVUODN7.js} +12 -12
  43. package/dist/{performance-UBCFI2UP.js → performance-NMJDV6HF.js} +3 -3
  44. package/dist/{review-pipeline-IQAVCWAX.js → review-pipeline-MSEJWTKM.js} +1 -1
  45. package/dist/{runtime-PYFFIESU.js → runtime-YHVLJNPG.js} +1 -1
  46. package/dist/{security-ZDADTPYW.js → security-HTDKKGMX.js} +1 -1
  47. package/dist/{validate-VRTUHALQ.js → validate-SPSTH2YW.js} +2 -2
  48. package/dist/{validate-cross-check-4Y6NHNK3.js → validate-cross-check-YTDWIMFI.js} +1 -1
  49. package/package.json +20 -21
@@ -1862,22 +1862,23 @@ import * as path17 from "path";
1862
1862
  import * as path18 from "path";
1863
1863
  import * as fs19 from "fs";
1864
1864
  import * as path19 from "path";
1865
+ import * as fs20 from "fs";
1865
1866
  import { z as z7 } from "zod";
1866
- import * as fs20 from "fs/promises";
1867
- import * as path20 from "path";
1868
1867
  import * as fs21 from "fs/promises";
1868
+ import * as path20 from "path";
1869
+ import * as fs22 from "fs/promises";
1869
1870
  import * as path21 from "path";
1870
1871
  import * as ejs from "ejs";
1871
- import * as fs22 from "fs";
1872
+ import * as fs23 from "fs";
1872
1873
  import * as path22 from "path";
1873
1874
  import * as os from "os";
1874
1875
  import { spawn } from "child_process";
1875
1876
  import Parser from "web-tree-sitter";
1876
- import * as fs23 from "fs/promises";
1877
+ import * as fs24 from "fs/promises";
1877
1878
  import * as path23 from "path";
1878
- import * as fs24 from "fs";
1879
- import * as path24 from "path";
1880
1879
  import * as fs25 from "fs";
1880
+ import * as path24 from "path";
1881
+ import * as fs26 from "fs";
1881
1882
  import * as path25 from "path";
1882
1883
  import * as os2 from "os";
1883
1884
  async function validateFileStructure(projectPath, conventions) {
@@ -11587,6 +11588,7 @@ var VALID_STATUSES = /* @__PURE__ */ new Set([
11587
11588
  "blocked"
11588
11589
  ]);
11589
11590
  var EM_DASH = "\u2014";
11591
+ var VALID_PRIORITIES = /* @__PURE__ */ new Set(["P0", "P1", "P2", "P3"]);
11590
11592
  function parseRoadmap(markdown) {
11591
11593
  const fmMatch = markdown.match(/^---\n([\s\S]*?)\n---/);
11592
11594
  if (!fmMatch) {
@@ -11597,9 +11599,12 @@ function parseRoadmap(markdown) {
11597
11599
  const body = markdown.slice(fmMatch[0].length);
11598
11600
  const milestonesResult = parseMilestones(body);
11599
11601
  if (!milestonesResult.ok) return milestonesResult;
11602
+ const historyResult = parseAssignmentHistory(body);
11603
+ if (!historyResult.ok) return historyResult;
11600
11604
  return Ok({
11601
11605
  frontmatter: fmResult.value,
11602
- milestones: milestonesResult.value
11606
+ milestones: milestonesResult.value,
11607
+ assignmentHistory: historyResult.value
11603
11608
  });
11604
11609
  }
11605
11610
  function parseFrontmatter2(raw) {
@@ -11639,12 +11644,17 @@ function parseMilestones(body) {
11639
11644
  const h2Pattern = /^## (.+)$/gm;
11640
11645
  const h2Matches = [];
11641
11646
  let match;
11647
+ let bodyEnd = body.length;
11642
11648
  while ((match = h2Pattern.exec(body)) !== null) {
11649
+ if (match[1] === "Assignment History") {
11650
+ bodyEnd = match.index;
11651
+ break;
11652
+ }
11643
11653
  h2Matches.push({ heading: match[1], startIndex: match.index, fullMatch: match[0] });
11644
11654
  }
11645
11655
  for (let i = 0; i < h2Matches.length; i++) {
11646
11656
  const h2 = h2Matches[i];
11647
- const nextStart = i + 1 < h2Matches.length ? h2Matches[i + 1].startIndex : body.length;
11657
+ const nextStart = i + 1 < h2Matches.length ? h2Matches[i + 1].startIndex : bodyEnd;
11648
11658
  const sectionBody = body.slice(h2.startIndex + h2.fullMatch.length, nextStart);
11649
11659
  const isBacklog = h2.heading === "Backlog";
11650
11660
  const milestoneName = isBacklog ? "Backlog" : h2.heading.replace(/^Milestone:\s*/, "");
@@ -11710,15 +11720,60 @@ function parseFeatureFields(name, body) {
11710
11720
  const specRaw = fieldMap.get("Spec") ?? EM_DASH;
11711
11721
  const plans = parseListField(fieldMap, "Plans", "Plan");
11712
11722
  const blockedBy = parseListField(fieldMap, "Blocked by", "Blockers");
11723
+ const assigneeRaw = fieldMap.get("Assignee") ?? EM_DASH;
11724
+ const priorityRaw = fieldMap.get("Priority") ?? EM_DASH;
11725
+ const externalIdRaw = fieldMap.get("External-ID") ?? EM_DASH;
11726
+ if (priorityRaw !== EM_DASH && !VALID_PRIORITIES.has(priorityRaw)) {
11727
+ return Err(
11728
+ new Error(
11729
+ `Feature "${name}" has invalid priority: "${priorityRaw}". Valid priorities: ${[...VALID_PRIORITIES].join(", ")}`
11730
+ )
11731
+ );
11732
+ }
11713
11733
  return Ok({
11714
11734
  name,
11715
11735
  status: statusRaw,
11716
11736
  spec: specRaw === EM_DASH ? null : specRaw,
11717
11737
  plans,
11718
11738
  blockedBy,
11719
- summary: fieldMap.get("Summary") ?? ""
11739
+ summary: fieldMap.get("Summary") ?? "",
11740
+ assignee: assigneeRaw === EM_DASH ? null : assigneeRaw,
11741
+ priority: priorityRaw === EM_DASH ? null : priorityRaw,
11742
+ externalId: externalIdRaw === EM_DASH ? null : externalIdRaw
11720
11743
  });
11721
11744
  }
11745
+ function parseAssignmentHistory(body) {
11746
+ const historyMatch = body.match(/^## Assignment History\s*\n/m);
11747
+ if (!historyMatch || historyMatch.index === void 0) return Ok([]);
11748
+ const historyStart = historyMatch.index + historyMatch[0].length;
11749
+ const rawHistoryBody = body.slice(historyStart);
11750
+ const nextH2 = rawHistoryBody.search(/^## /m);
11751
+ const historyBody = nextH2 === -1 ? rawHistoryBody : rawHistoryBody.slice(0, nextH2);
11752
+ const records = [];
11753
+ const lines = historyBody.split("\n");
11754
+ let pastHeader = false;
11755
+ for (const line of lines) {
11756
+ const trimmed = line.trim();
11757
+ if (!trimmed.startsWith("|")) continue;
11758
+ if (!pastHeader) {
11759
+ if (trimmed.match(/^\|[-\s|]+\|$/)) {
11760
+ pastHeader = true;
11761
+ }
11762
+ continue;
11763
+ }
11764
+ const cells = trimmed.split("|").map((c) => c.trim()).filter((c) => c.length > 0);
11765
+ if (cells.length < 4) continue;
11766
+ const action = cells[2];
11767
+ if (!["assigned", "completed", "unassigned"].includes(action)) continue;
11768
+ records.push({
11769
+ feature: cells[0],
11770
+ assignee: cells[1],
11771
+ action,
11772
+ date: cells[3]
11773
+ });
11774
+ }
11775
+ return Ok(records);
11776
+ }
11722
11777
  var EM_DASH2 = "\u2014";
11723
11778
  function serializeRoadmap(roadmap) {
11724
11779
  const lines = [];
@@ -11744,6 +11799,10 @@ function serializeRoadmap(roadmap) {
11744
11799
  lines.push(...serializeFeature(feature));
11745
11800
  }
11746
11801
  }
11802
+ if (roadmap.assignmentHistory && roadmap.assignmentHistory.length > 0) {
11803
+ lines.push("");
11804
+ lines.push(...serializeAssignmentHistory(roadmap.assignmentHistory));
11805
+ }
11747
11806
  lines.push("");
11748
11807
  return lines.join("\n");
11749
11808
  }
@@ -11754,7 +11813,7 @@ function serializeFeature(feature) {
11754
11813
  const spec = feature.spec ?? EM_DASH2;
11755
11814
  const plans = feature.plans.length > 0 ? feature.plans.join(", ") : EM_DASH2;
11756
11815
  const blockedBy = feature.blockedBy.length > 0 ? feature.blockedBy.join(", ") : EM_DASH2;
11757
- return [
11816
+ const lines = [
11758
11817
  `### ${feature.name}`,
11759
11818
  "",
11760
11819
  `- **Status:** ${feature.status}`,
@@ -11763,6 +11822,35 @@ function serializeFeature(feature) {
11763
11822
  `- **Blockers:** ${blockedBy}`,
11764
11823
  `- **Plan:** ${plans}`
11765
11824
  ];
11825
+ const hasExtended = feature.assignee !== null || feature.priority !== null || feature.externalId !== null;
11826
+ if (hasExtended) {
11827
+ lines.push(`- **Assignee:** ${feature.assignee ?? EM_DASH2}`);
11828
+ lines.push(`- **Priority:** ${feature.priority ?? EM_DASH2}`);
11829
+ lines.push(`- **External-ID:** ${feature.externalId ?? EM_DASH2}`);
11830
+ }
11831
+ return lines;
11832
+ }
11833
+ function serializeAssignmentHistory(records) {
11834
+ const lines = [
11835
+ "## Assignment History",
11836
+ "| Feature | Assignee | Action | Date |",
11837
+ "|---------|----------|--------|------|"
11838
+ ];
11839
+ for (const record of records) {
11840
+ lines.push(`| ${record.feature} | ${record.assignee} | ${record.action} | ${record.date} |`);
11841
+ }
11842
+ return lines;
11843
+ }
11844
+ var STATUS_RANK = {
11845
+ backlog: 0,
11846
+ planned: 1,
11847
+ blocked: 1,
11848
+ // lateral to planned — sync can move to/from blocked freely
11849
+ "in-progress": 2,
11850
+ done: 3
11851
+ };
11852
+ function isRegression(from, to) {
11853
+ return STATUS_RANK[to] < STATUS_RANK[from];
11766
11854
  }
11767
11855
  function inferStatus(feature, projectPath, allFeatures) {
11768
11856
  if (feature.blockedBy.length > 0) {
@@ -11830,17 +11918,6 @@ function inferStatus(feature, projectPath, allFeatures) {
11830
11918
  if (anyStarted) return "in-progress";
11831
11919
  return null;
11832
11920
  }
11833
- var STATUS_RANK = {
11834
- backlog: 0,
11835
- planned: 1,
11836
- blocked: 1,
11837
- // lateral to planned — sync can move to/from blocked freely
11838
- "in-progress": 2,
11839
- done: 3
11840
- };
11841
- function isRegression(from, to) {
11842
- return STATUS_RANK[to] < STATUS_RANK[from];
11843
- }
11844
11921
  function syncRoadmap(options) {
11845
11922
  const { projectPath, roadmap, forceSync } = options;
11846
11923
  const allFeatures = roadmap.milestones.flatMap((m) => m.features);
@@ -11870,6 +11947,428 @@ function applySyncChanges(roadmap, changes) {
11870
11947
  }
11871
11948
  roadmap.frontmatter.lastSynced = (/* @__PURE__ */ new Date()).toISOString();
11872
11949
  }
11950
+ function resolveReverseStatus(externalStatus, labels, config) {
11951
+ const reverseMap = config.reverseStatusMap;
11952
+ if (!reverseMap) return null;
11953
+ if (reverseMap[externalStatus]) {
11954
+ return reverseMap[externalStatus];
11955
+ }
11956
+ const statusLabels = ["in-progress", "blocked", "planned"];
11957
+ const matchingLabels = labels.filter((l) => statusLabels.includes(l));
11958
+ if (matchingLabels.length === 1) {
11959
+ const compoundKey = `${externalStatus}:${matchingLabels[0]}`;
11960
+ if (reverseMap[compoundKey]) {
11961
+ return reverseMap[compoundKey];
11962
+ }
11963
+ }
11964
+ return null;
11965
+ }
11966
+ function parseExternalId(externalId) {
11967
+ const match = externalId.match(/^github:([^/]+)\/([^#]+)#(\d+)$/);
11968
+ if (!match) return null;
11969
+ return { owner: match[1], repo: match[2], number: parseInt(match[3], 10) };
11970
+ }
11971
+ function buildExternalId(owner, repo, number) {
11972
+ return `github:${owner}/${repo}#${number}`;
11973
+ }
11974
+ function labelsForStatus(status, config) {
11975
+ const base = config.labels ?? [];
11976
+ const externalStatus = config.statusMap[status];
11977
+ if (externalStatus === "open" && status !== "backlog") {
11978
+ return [...base, status];
11979
+ }
11980
+ return [...base];
11981
+ }
11982
+ var GitHubIssuesSyncAdapter = class {
11983
+ token;
11984
+ config;
11985
+ fetchFn;
11986
+ apiBase;
11987
+ owner;
11988
+ repo;
11989
+ constructor(options) {
11990
+ this.token = options.token;
11991
+ this.config = options.config;
11992
+ this.fetchFn = options.fetchFn ?? globalThis.fetch;
11993
+ this.apiBase = options.apiBase ?? "https://api.github.com";
11994
+ const repoParts = (options.config.repo ?? "").split("/");
11995
+ if (repoParts.length !== 2 || !repoParts[0] || !repoParts[1]) {
11996
+ throw new Error(`Invalid repo format: "${options.config.repo}". Expected "owner/repo".`);
11997
+ }
11998
+ this.owner = repoParts[0];
11999
+ this.repo = repoParts[1];
12000
+ }
12001
+ headers() {
12002
+ return {
12003
+ Authorization: `Bearer ${this.token}`,
12004
+ Accept: "application/vnd.github+json",
12005
+ "Content-Type": "application/json",
12006
+ "X-GitHub-Api-Version": "2022-11-28"
12007
+ };
12008
+ }
12009
+ async createTicket(feature, milestone) {
12010
+ try {
12011
+ const labels = labelsForStatus(feature.status, this.config);
12012
+ const body = [
12013
+ feature.summary,
12014
+ "",
12015
+ `**Milestone:** ${milestone}`,
12016
+ feature.spec ? `**Spec:** ${feature.spec}` : ""
12017
+ ].filter(Boolean).join("\n");
12018
+ const response = await this.fetchFn(
12019
+ `${this.apiBase}/repos/${this.owner}/${this.repo}/issues`,
12020
+ {
12021
+ method: "POST",
12022
+ headers: this.headers(),
12023
+ body: JSON.stringify({
12024
+ title: feature.name,
12025
+ body,
12026
+ labels
12027
+ })
12028
+ }
12029
+ );
12030
+ if (!response.ok) {
12031
+ const text = await response.text();
12032
+ return Err(new Error(`GitHub API error ${response.status}: ${text}`));
12033
+ }
12034
+ const data = await response.json();
12035
+ const externalId = buildExternalId(this.owner, this.repo, data.number);
12036
+ return Ok({ externalId, url: data.html_url });
12037
+ } catch (error) {
12038
+ return Err(error instanceof Error ? error : new Error(String(error)));
12039
+ }
12040
+ }
12041
+ async updateTicket(externalId, changes) {
12042
+ try {
12043
+ const parsed = parseExternalId(externalId);
12044
+ if (!parsed) return Err(new Error(`Invalid externalId format: "${externalId}"`));
12045
+ const patch = {};
12046
+ if (changes.name !== void 0) patch.title = changes.name;
12047
+ if (changes.summary !== void 0) {
12048
+ const body = [changes.summary, "", changes.spec ? `**Spec:** ${changes.spec}` : ""].filter(Boolean).join("\n");
12049
+ patch.body = body;
12050
+ }
12051
+ if (changes.status !== void 0) {
12052
+ const externalStatus = this.config.statusMap[changes.status];
12053
+ patch.state = externalStatus;
12054
+ patch.labels = labelsForStatus(changes.status, this.config);
12055
+ }
12056
+ const response = await this.fetchFn(
12057
+ `${this.apiBase}/repos/${parsed.owner}/${parsed.repo}/issues/${parsed.number}`,
12058
+ {
12059
+ method: "PATCH",
12060
+ headers: this.headers(),
12061
+ body: JSON.stringify(patch)
12062
+ }
12063
+ );
12064
+ if (!response.ok) {
12065
+ const text = await response.text();
12066
+ return Err(new Error(`GitHub API error ${response.status}: ${text}`));
12067
+ }
12068
+ const data = await response.json();
12069
+ return Ok({ externalId, url: data.html_url });
12070
+ } catch (error) {
12071
+ return Err(error instanceof Error ? error : new Error(String(error)));
12072
+ }
12073
+ }
12074
+ async fetchTicketState(externalId) {
12075
+ try {
12076
+ const parsed = parseExternalId(externalId);
12077
+ if (!parsed) return Err(new Error(`Invalid externalId format: "${externalId}"`));
12078
+ const response = await this.fetchFn(
12079
+ `${this.apiBase}/repos/${parsed.owner}/${parsed.repo}/issues/${parsed.number}`,
12080
+ {
12081
+ method: "GET",
12082
+ headers: this.headers()
12083
+ }
12084
+ );
12085
+ if (!response.ok) {
12086
+ const text = await response.text();
12087
+ return Err(new Error(`GitHub API error ${response.status}: ${text}`));
12088
+ }
12089
+ const data = await response.json();
12090
+ return Ok({
12091
+ externalId,
12092
+ status: data.state,
12093
+ labels: data.labels.map((l) => l.name),
12094
+ assignee: data.assignee ? `@${data.assignee.login}` : null
12095
+ });
12096
+ } catch (error) {
12097
+ return Err(error instanceof Error ? error : new Error(String(error)));
12098
+ }
12099
+ }
12100
+ async fetchAllTickets() {
12101
+ try {
12102
+ const filterLabels = this.config.labels ?? [];
12103
+ const labelsParam = filterLabels.length > 0 ? `&labels=${filterLabels.join(",")}` : "";
12104
+ const tickets = [];
12105
+ let page = 1;
12106
+ const perPage = 100;
12107
+ while (true) {
12108
+ const response = await this.fetchFn(
12109
+ `${this.apiBase}/repos/${this.owner}/${this.repo}/issues?state=all&per_page=${perPage}&page=${page}${labelsParam}`,
12110
+ {
12111
+ method: "GET",
12112
+ headers: this.headers()
12113
+ }
12114
+ );
12115
+ if (!response.ok) {
12116
+ const text = await response.text();
12117
+ return Err(new Error(`GitHub API error ${response.status}: ${text}`));
12118
+ }
12119
+ const data = await response.json();
12120
+ const issues = data.filter((d) => !d.pull_request);
12121
+ for (const issue of issues) {
12122
+ tickets.push({
12123
+ externalId: buildExternalId(this.owner, this.repo, issue.number),
12124
+ status: issue.state,
12125
+ labels: issue.labels.map((l) => l.name),
12126
+ assignee: issue.assignee ? `@${issue.assignee.login}` : null
12127
+ });
12128
+ }
12129
+ if (data.length < perPage) break;
12130
+ page++;
12131
+ }
12132
+ return Ok(tickets);
12133
+ } catch (error) {
12134
+ return Err(error instanceof Error ? error : new Error(String(error)));
12135
+ }
12136
+ }
12137
+ async assignTicket(externalId, assignee) {
12138
+ try {
12139
+ const parsed = parseExternalId(externalId);
12140
+ if (!parsed) return Err(new Error(`Invalid externalId format: "${externalId}"`));
12141
+ const login = assignee.startsWith("@") ? assignee.slice(1) : assignee;
12142
+ const response = await this.fetchFn(
12143
+ `${this.apiBase}/repos/${parsed.owner}/${parsed.repo}/issues/${parsed.number}/assignees`,
12144
+ {
12145
+ method: "POST",
12146
+ headers: this.headers(),
12147
+ body: JSON.stringify({ assignees: [login] })
12148
+ }
12149
+ );
12150
+ if (!response.ok) {
12151
+ const text = await response.text();
12152
+ return Err(new Error(`GitHub API error ${response.status}: ${text}`));
12153
+ }
12154
+ return Ok(void 0);
12155
+ } catch (error) {
12156
+ return Err(error instanceof Error ? error : new Error(String(error)));
12157
+ }
12158
+ }
12159
+ };
12160
+ function emptySyncResult() {
12161
+ return { created: [], updated: [], assignmentChanges: [], errors: [] };
12162
+ }
12163
+ async function syncToExternal(roadmap, adapter, _config) {
12164
+ const result = emptySyncResult();
12165
+ for (const milestone of roadmap.milestones) {
12166
+ for (const feature of milestone.features) {
12167
+ if (!feature.externalId) {
12168
+ const createResult = await adapter.createTicket(feature, milestone.name);
12169
+ if (createResult.ok) {
12170
+ feature.externalId = createResult.value.externalId;
12171
+ result.created.push(createResult.value);
12172
+ } else {
12173
+ result.errors.push({ featureOrId: feature.name, error: createResult.error });
12174
+ }
12175
+ } else {
12176
+ const updateResult = await adapter.updateTicket(feature.externalId, feature);
12177
+ if (updateResult.ok) {
12178
+ result.updated.push(feature.externalId);
12179
+ } else {
12180
+ result.errors.push({ featureOrId: feature.externalId, error: updateResult.error });
12181
+ }
12182
+ }
12183
+ }
12184
+ }
12185
+ return result;
12186
+ }
12187
+ async function syncFromExternal(roadmap, adapter, config, options) {
12188
+ const result = emptySyncResult();
12189
+ const forceSync = options?.forceSync ?? false;
12190
+ const featureByExternalId = /* @__PURE__ */ new Map();
12191
+ for (const milestone of roadmap.milestones) {
12192
+ for (const feature of milestone.features) {
12193
+ if (feature.externalId) {
12194
+ featureByExternalId.set(feature.externalId, feature);
12195
+ }
12196
+ }
12197
+ }
12198
+ if (featureByExternalId.size === 0) return result;
12199
+ const fetchResult = await adapter.fetchAllTickets();
12200
+ if (!fetchResult.ok) {
12201
+ result.errors.push({ featureOrId: "*", error: fetchResult.error });
12202
+ return result;
12203
+ }
12204
+ for (const ticketState of fetchResult.value) {
12205
+ const feature = featureByExternalId.get(ticketState.externalId);
12206
+ if (!feature) continue;
12207
+ if (ticketState.assignee !== feature.assignee) {
12208
+ result.assignmentChanges.push({
12209
+ feature: feature.name,
12210
+ from: feature.assignee,
12211
+ to: ticketState.assignee
12212
+ });
12213
+ feature.assignee = ticketState.assignee;
12214
+ }
12215
+ const resolvedStatus = resolveReverseStatus(ticketState.status, ticketState.labels, config);
12216
+ if (resolvedStatus && resolvedStatus !== feature.status) {
12217
+ const newStatus = resolvedStatus;
12218
+ if (!forceSync && isRegression(feature.status, newStatus)) {
12219
+ continue;
12220
+ }
12221
+ feature.status = newStatus;
12222
+ }
12223
+ }
12224
+ return result;
12225
+ }
12226
+ var syncMutex = Promise.resolve();
12227
+ async function fullSync(roadmapPath, adapter, config, options) {
12228
+ const previousSync = syncMutex;
12229
+ let releaseMutex;
12230
+ syncMutex = new Promise((resolve5) => {
12231
+ releaseMutex = resolve5;
12232
+ });
12233
+ await previousSync;
12234
+ try {
12235
+ const raw = fs20.readFileSync(roadmapPath, "utf-8");
12236
+ const parseResult = parseRoadmap(raw);
12237
+ if (!parseResult.ok) {
12238
+ return {
12239
+ ...emptySyncResult(),
12240
+ errors: [{ featureOrId: "*", error: parseResult.error }]
12241
+ };
12242
+ }
12243
+ const roadmap = parseResult.value;
12244
+ const pushResult = await syncToExternal(roadmap, adapter, config);
12245
+ const pullResult = await syncFromExternal(roadmap, adapter, config, options);
12246
+ fs20.writeFileSync(roadmapPath, serializeRoadmap(roadmap), "utf-8");
12247
+ return {
12248
+ created: pushResult.created,
12249
+ updated: pushResult.updated,
12250
+ assignmentChanges: pullResult.assignmentChanges,
12251
+ errors: [...pushResult.errors, ...pullResult.errors]
12252
+ };
12253
+ } finally {
12254
+ releaseMutex();
12255
+ }
12256
+ }
12257
+ var PRIORITY_RANK = {
12258
+ P0: 0,
12259
+ P1: 1,
12260
+ P2: 2,
12261
+ P3: 3
12262
+ };
12263
+ var POSITION_WEIGHT = 0.5;
12264
+ var DEPENDENTS_WEIGHT = 0.3;
12265
+ var AFFINITY_WEIGHT = 0.2;
12266
+ function scoreRoadmapCandidates(roadmap, options) {
12267
+ const allFeatures = roadmap.milestones.flatMap((m) => m.features);
12268
+ const allFeatureNames = new Set(allFeatures.map((f) => f.name.toLowerCase()));
12269
+ const doneFeatures = new Set(
12270
+ allFeatures.filter((f) => f.status === "done").map((f) => f.name.toLowerCase())
12271
+ );
12272
+ const dependentsCount = /* @__PURE__ */ new Map();
12273
+ for (const feature of allFeatures) {
12274
+ for (const blocker of feature.blockedBy) {
12275
+ const key = blocker.toLowerCase();
12276
+ dependentsCount.set(key, (dependentsCount.get(key) ?? 0) + 1);
12277
+ }
12278
+ }
12279
+ const maxDependents = Math.max(1, ...dependentsCount.values());
12280
+ const milestoneMap = /* @__PURE__ */ new Map();
12281
+ for (const ms of roadmap.milestones) {
12282
+ milestoneMap.set(
12283
+ ms.name,
12284
+ ms.features.map((f) => f.name.toLowerCase())
12285
+ );
12286
+ }
12287
+ const userCompletedFeatures = /* @__PURE__ */ new Set();
12288
+ if (options?.currentUser) {
12289
+ const user = options.currentUser.toLowerCase();
12290
+ for (const record of roadmap.assignmentHistory) {
12291
+ if (record.action === "completed" && record.assignee.toLowerCase() === user) {
12292
+ userCompletedFeatures.add(record.feature.toLowerCase());
12293
+ }
12294
+ }
12295
+ }
12296
+ let totalPositions = 0;
12297
+ for (const ms of roadmap.milestones) {
12298
+ totalPositions += ms.features.length;
12299
+ }
12300
+ totalPositions = Math.max(1, totalPositions);
12301
+ const candidates = [];
12302
+ let globalPosition = 0;
12303
+ for (const ms of roadmap.milestones) {
12304
+ for (let featureIdx = 0; featureIdx < ms.features.length; featureIdx++) {
12305
+ const feature = ms.features[featureIdx];
12306
+ globalPosition++;
12307
+ if (feature.status !== "planned" && feature.status !== "backlog") continue;
12308
+ const isBlocked = feature.blockedBy.some((blocker) => {
12309
+ const key = blocker.toLowerCase();
12310
+ return allFeatureNames.has(key) && !doneFeatures.has(key);
12311
+ });
12312
+ if (isBlocked) continue;
12313
+ const positionScore = 1 - (globalPosition - 1) / totalPositions;
12314
+ const deps = dependentsCount.get(feature.name.toLowerCase()) ?? 0;
12315
+ const dependentsScore = deps / maxDependents;
12316
+ let affinityScore = 0;
12317
+ if (userCompletedFeatures.size > 0) {
12318
+ const completedBlockers = feature.blockedBy.filter(
12319
+ (b) => userCompletedFeatures.has(b.toLowerCase())
12320
+ );
12321
+ if (completedBlockers.length > 0) {
12322
+ affinityScore = 1;
12323
+ } else {
12324
+ const siblings = milestoneMap.get(ms.name) ?? [];
12325
+ const completedSiblings = siblings.filter((s) => userCompletedFeatures.has(s));
12326
+ if (completedSiblings.length > 0) {
12327
+ affinityScore = 0.5;
12328
+ }
12329
+ }
12330
+ }
12331
+ const weightedScore = POSITION_WEIGHT * positionScore + DEPENDENTS_WEIGHT * dependentsScore + AFFINITY_WEIGHT * affinityScore;
12332
+ const priorityTier = feature.priority ? PRIORITY_RANK[feature.priority] : null;
12333
+ candidates.push({
12334
+ feature,
12335
+ milestone: ms.name,
12336
+ positionScore,
12337
+ dependentsScore,
12338
+ affinityScore,
12339
+ weightedScore,
12340
+ priorityTier
12341
+ });
12342
+ }
12343
+ }
12344
+ candidates.sort((a, b) => {
12345
+ if (a.priorityTier !== null && b.priorityTier === null) return -1;
12346
+ if (a.priorityTier === null && b.priorityTier !== null) return 1;
12347
+ if (a.priorityTier !== null && b.priorityTier !== null) {
12348
+ if (a.priorityTier !== b.priorityTier) return a.priorityTier - b.priorityTier;
12349
+ }
12350
+ return b.weightedScore - a.weightedScore;
12351
+ });
12352
+ return candidates;
12353
+ }
12354
+ function assignFeature(roadmap, feature, assignee, date) {
12355
+ if (feature.assignee === assignee) return;
12356
+ if (feature.assignee !== null) {
12357
+ roadmap.assignmentHistory.push({
12358
+ feature: feature.name,
12359
+ assignee: feature.assignee,
12360
+ action: "unassigned",
12361
+ date
12362
+ });
12363
+ }
12364
+ feature.assignee = assignee;
12365
+ roadmap.assignmentHistory.push({
12366
+ feature: feature.name,
12367
+ assignee,
12368
+ action: "assigned",
12369
+ date
12370
+ });
12371
+ }
11873
12372
  var InteractionTypeSchema = z7.enum(["question", "confirmation", "transition"]);
11874
12373
  var QuestionSchema = z7.object({
11875
12374
  text: z7.string(),
@@ -11900,11 +12399,12 @@ var ProjectScanner = class {
11900
12399
  constructor(rootDir) {
11901
12400
  this.rootDir = rootDir;
11902
12401
  }
12402
+ rootDir;
11903
12403
  async scan() {
11904
12404
  let projectName = path20.basename(this.rootDir);
11905
12405
  try {
11906
12406
  const pkgPath = path20.join(this.rootDir, "package.json");
11907
- const pkgRaw = await fs20.readFile(pkgPath, "utf-8");
12407
+ const pkgRaw = await fs21.readFile(pkgPath, "utf-8");
11908
12408
  const pkg = JSON.parse(pkgRaw);
11909
12409
  if (pkg.name) projectName = pkg.name;
11910
12410
  } catch {
@@ -12017,8 +12517,8 @@ var BlueprintGenerator = class {
12017
12517
  styles: STYLES,
12018
12518
  scripts: SCRIPTS
12019
12519
  });
12020
- await fs21.mkdir(options.outputDir, { recursive: true });
12021
- await fs21.writeFile(path21.join(options.outputDir, "index.html"), html);
12520
+ await fs22.mkdir(options.outputDir, { recursive: true });
12521
+ await fs22.writeFile(path21.join(options.outputDir, "index.html"), html);
12022
12522
  }
12023
12523
  };
12024
12524
  function getStatePath() {
@@ -12036,7 +12536,7 @@ function shouldRunCheck(state, intervalMs) {
12036
12536
  }
12037
12537
  function readCheckState() {
12038
12538
  try {
12039
- const raw = fs22.readFileSync(getStatePath(), "utf-8");
12539
+ const raw = fs23.readFileSync(getStatePath(), "utf-8");
12040
12540
  const parsed = JSON.parse(raw);
12041
12541
  if (typeof parsed === "object" && parsed !== null && "lastCheckTime" in parsed && typeof parsed.lastCheckTime === "number" && "currentVersion" in parsed && typeof parsed.currentVersion === "string") {
12042
12542
  const state = parsed;
@@ -12588,7 +13088,7 @@ function getStalenessMarkerPath(projectRoot) {
12588
13088
  }
12589
13089
  async function readDiskCache(projectRoot) {
12590
13090
  try {
12591
- const raw = await fs23.readFile(getCachePath(projectRoot), "utf-8");
13091
+ const raw = await fs24.readFile(getCachePath(projectRoot), "utf-8");
12592
13092
  return JSON.parse(raw);
12593
13093
  } catch {
12594
13094
  return null;
@@ -12596,8 +13096,8 @@ async function readDiskCache(projectRoot) {
12596
13096
  }
12597
13097
  async function writeDiskCache(projectRoot, data) {
12598
13098
  const cachePath = getCachePath(projectRoot);
12599
- await fs23.mkdir(path23.dirname(cachePath), { recursive: true });
12600
- await fs23.writeFile(cachePath, JSON.stringify(data, null, 2));
13099
+ await fs24.mkdir(path23.dirname(cachePath), { recursive: true });
13100
+ await fs24.writeFile(cachePath, JSON.stringify(data, null, 2));
12601
13101
  }
12602
13102
  async function fetchFromNetwork() {
12603
13103
  try {
@@ -12624,7 +13124,7 @@ function loadFallbackDataset() {
12624
13124
  async function checkAndWarnStaleness(projectRoot) {
12625
13125
  const markerPath = getStalenessMarkerPath(projectRoot);
12626
13126
  try {
12627
- const raw = await fs23.readFile(markerPath, "utf-8");
13127
+ const raw = await fs24.readFile(markerPath, "utf-8");
12628
13128
  const marker = JSON.parse(raw);
12629
13129
  const firstUse = new Date(marker.firstFallbackUse).getTime();
12630
13130
  const now = Date.now();
@@ -12636,8 +13136,8 @@ async function checkAndWarnStaleness(projectRoot) {
12636
13136
  }
12637
13137
  } catch {
12638
13138
  try {
12639
- await fs23.mkdir(path23.dirname(markerPath), { recursive: true });
12640
- await fs23.writeFile(
13139
+ await fs24.mkdir(path23.dirname(markerPath), { recursive: true });
13140
+ await fs24.writeFile(
12641
13141
  markerPath,
12642
13142
  JSON.stringify({ firstFallbackUse: (/* @__PURE__ */ new Date()).toISOString() })
12643
13143
  );
@@ -12647,7 +13147,7 @@ async function checkAndWarnStaleness(projectRoot) {
12647
13147
  }
12648
13148
  async function clearStalenessMarker(projectRoot) {
12649
13149
  try {
12650
- await fs23.unlink(getStalenessMarkerPath(projectRoot));
13150
+ await fs24.unlink(getStalenessMarkerPath(projectRoot));
12651
13151
  } catch {
12652
13152
  }
12653
13153
  }
@@ -12852,7 +13352,7 @@ function readCostRecords(projectRoot) {
12852
13352
  const costsFile = path24.join(projectRoot, ".harness", "metrics", "costs.jsonl");
12853
13353
  let raw;
12854
13354
  try {
12855
- raw = fs24.readFileSync(costsFile, "utf-8");
13355
+ raw = fs25.readFileSync(costsFile, "utf-8");
12856
13356
  } catch {
12857
13357
  return [];
12858
13358
  }
@@ -12913,7 +13413,7 @@ function parseCCLine(line, filePath, lineNumber) {
12913
13413
  function readCCFile(filePath) {
12914
13414
  let raw;
12915
13415
  try {
12916
- raw = fs25.readFileSync(filePath, "utf-8");
13416
+ raw = fs26.readFileSync(filePath, "utf-8");
12917
13417
  } catch {
12918
13418
  return [];
12919
13419
  }
@@ -12938,7 +13438,7 @@ function parseCCRecords() {
12938
13438
  const projectsDir = path25.join(homeDir, ".claude", "projects");
12939
13439
  let projectDirs;
12940
13440
  try {
12941
- projectDirs = fs25.readdirSync(projectsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => path25.join(projectsDir, d.name));
13441
+ projectDirs = fs26.readdirSync(projectsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => path25.join(projectsDir, d.name));
12942
13442
  } catch {
12943
13443
  return [];
12944
13444
  }
@@ -12946,7 +13446,7 @@ function parseCCRecords() {
12946
13446
  for (const dir of projectDirs) {
12947
13447
  let files;
12948
13448
  try {
12949
- files = fs25.readdirSync(dir).filter((f) => f.endsWith(".jsonl")).map((f) => path25.join(dir, f));
13449
+ files = fs26.readdirSync(dir).filter((f) => f.endsWith(".jsonl")).map((f) => path25.join(dir, f));
12950
13450
  } catch {
12951
13451
  continue;
12952
13452
  }
@@ -13205,8 +13705,17 @@ export {
13205
13705
  runReviewPipeline,
13206
13706
  parseRoadmap,
13207
13707
  serializeRoadmap,
13708
+ STATUS_RANK,
13709
+ isRegression,
13208
13710
  syncRoadmap,
13209
13711
  applySyncChanges,
13712
+ resolveReverseStatus,
13713
+ GitHubIssuesSyncAdapter,
13714
+ syncToExternal,
13715
+ syncFromExternal,
13716
+ fullSync,
13717
+ scoreRoadmapCandidates,
13718
+ assignFeature,
13210
13719
  InteractionTypeSchema,
13211
13720
  QuestionSchema,
13212
13721
  ConfirmationSchema,