@harness-engineering/cli 4.0.0 → 4.1.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 (79) hide show
  1. package/dist/agents/skills/claude-code/harness-autopilot/SKILL.md +9 -8
  2. package/dist/agents/skills/claude-code/pre-merge-brief/SKILL.md +86 -0
  3. package/dist/agents/skills/claude-code/pre-merge-brief/skill.yaml +67 -0
  4. package/dist/agents/skills/claude-code/product-advisor/SKILL.md +202 -0
  5. package/dist/agents/skills/claude-code/product-advisor/skill.yaml +74 -0
  6. package/dist/agents/skills/codex/harness-autopilot/SKILL.md +9 -8
  7. package/dist/agents/skills/codex/pre-merge-brief/SKILL.md +86 -0
  8. package/dist/agents/skills/codex/pre-merge-brief/skill.yaml +67 -0
  9. package/dist/agents/skills/codex/product-advisor/SKILL.md +202 -0
  10. package/dist/agents/skills/codex/product-advisor/skill.yaml +74 -0
  11. package/dist/agents/skills/cursor/harness-autopilot/SKILL.md +9 -8
  12. package/dist/agents/skills/cursor/pre-merge-brief/SKILL.md +86 -0
  13. package/dist/agents/skills/cursor/pre-merge-brief/skill.yaml +67 -0
  14. package/dist/agents/skills/cursor/product-advisor/SKILL.md +202 -0
  15. package/dist/agents/skills/cursor/product-advisor/skill.yaml +74 -0
  16. package/dist/agents/skills/gemini-cli/harness-autopilot/SKILL.md +9 -8
  17. package/dist/agents/skills/gemini-cli/pre-merge-brief/SKILL.md +86 -0
  18. package/dist/agents/skills/gemini-cli/pre-merge-brief/skill.yaml +67 -0
  19. package/dist/agents/skills/gemini-cli/product-advisor/SKILL.md +202 -0
  20. package/dist/agents/skills/gemini-cli/product-advisor/skill.yaml +74 -0
  21. package/dist/{agents-md-7OSNWU52.js → agents-md-7L4PKKOM.js} +4 -4
  22. package/dist/analyzer-XRCV63KC-HLTEIKAJ.js +26 -0
  23. package/dist/{architecture-2XYGJGT3.js → architecture-RCHFDI7E.js} +5 -5
  24. package/dist/{assess-project-3KPP2D6M.js → assess-project-YUDPNRS6.js} +5 -3
  25. package/dist/bin/harness-mcp.js +16 -16
  26. package/dist/bin/harness.js +27 -27
  27. package/dist/{check-phase-gate-46U5AAXE.js → check-phase-gate-56GS3A72.js} +5 -5
  28. package/dist/{chunk-MSBJ445U.js → chunk-4SKWGVYD.js} +5 -5
  29. package/dist/{chunk-CLI4K2CH.js → chunk-4U5LDFCY.js} +1 -1
  30. package/dist/{chunk-6YXCZV33.js → chunk-ABRYIEWJ.js} +1090 -714
  31. package/dist/{chunk-AK67S3O4.js → chunk-AU4YPEIE.js} +3 -3
  32. package/dist/{chunk-BV45354Q.js → chunk-DGVT4DBI.js} +9 -9
  33. package/dist/{chunk-363V6GPR.js → chunk-GLHLXTKC.js} +9 -9
  34. package/dist/{chunk-VTUINXWR.js → chunk-GSYBGOY3.js} +1 -1
  35. package/dist/{chunk-FZ4Q73HA.js → chunk-IGWJRAJT.js} +1 -1
  36. package/dist/{chunk-S2RQPPL6.js → chunk-N4AI2WFH.js} +1 -1
  37. package/dist/{chunk-HXMAIVV2.js → chunk-NCMJXBEO.js} +1 -1
  38. package/dist/{chunk-ZEYAOYJL.js → chunk-OZSKLJQW.js} +1 -1
  39. package/dist/{chunk-VTU3QOJT.js → chunk-P4NA5OQX.js} +1 -1
  40. package/dist/{chunk-WL3O2PKJ.js → chunk-RY2U77OY.js} +8 -8
  41. package/dist/{chunk-TTWNGOBT.js → chunk-RZQZXWEL.js} +32 -7
  42. package/dist/{chunk-KLPX55TI.js → chunk-UACYZCW4.js} +239 -146
  43. package/dist/{chunk-IYWT3JJW.js → chunk-VG4G3R2V.js} +1 -1
  44. package/dist/{chunk-PP6ZRL5T.js → chunk-WZJR6SOI.js} +536 -422
  45. package/dist/{chunk-PHX45JYN.js → chunk-XW5XVVMI.js} +2 -2
  46. package/dist/{chunk-MYVTYFQD.js → chunk-XZNCONFG.js} +1 -1
  47. package/dist/{chunk-GXCPDAS5.js → chunk-Y33P35U3.js} +26 -8
  48. package/dist/{chunk-XFHZ7GCI.js → chunk-Y5HPJMTM.js} +19 -3
  49. package/dist/{chunk-N55WOGN3.js → chunk-YJYUJSWK.js} +59 -21
  50. package/dist/{chunk-SPE7P4GL.js → chunk-YWRPQZL7.js} +1 -1
  51. package/dist/{chunk-6NCREWWM.js → chunk-YXOPQEFZ.js} +3 -3
  52. package/dist/{chunk-2N4OENGE.js → chunk-YYFSFSB3.js} +1 -1
  53. package/dist/{chunk-ERILXQBP.js → chunk-Z4XWWNYX.js} +615 -433
  54. package/dist/{chunk-HMW3C5DP.js → chunk-ZU6T7W3U.js} +2 -2
  55. package/dist/{ci-workflow-XJN4UO3Q.js → ci-workflow-TFAA5BK7.js} +4 -4
  56. package/dist/{dist-OWHMNL4W.js → dist-DWBTZXR5.js} +1 -1
  57. package/dist/{dist-IPVFYGY2.js → dist-MCTEX5QH.js} +5 -3
  58. package/dist/{docs-RSFZTNPG.js → docs-BBMAN3CY.js} +5 -5
  59. package/dist/{engine-DJIHY4JT.js → engine-R2YH74AL.js} +4 -4
  60. package/dist/{entropy-EAEKJKRM.js → entropy-5UP5N6AF.js} +7 -4
  61. package/dist/{feedback-JT5X3DBC.js → feedback-6MDCCFZL.js} +1 -1
  62. package/dist/{generate-agent-definitions-CC6KED3E.js → generate-agent-definitions-KKAN5DB5.js} +5 -5
  63. package/dist/{glob-helper-GLZAURJC.js → glob-helper-VR6UCZZB.js} +1 -1
  64. package/dist/{graph-loader-HHXN5FLP.js → graph-loader-STFGYKZH.js} +1 -1
  65. package/dist/hooks/sentinel-pre.js +15 -138
  66. package/dist/index.d.ts +61 -0
  67. package/dist/index.js +27 -27
  68. package/dist/{loader-UWNVTYBY.js → loader-2WT2ZE2T.js} +4 -4
  69. package/dist/{mcp-TCLNK7HG.js → mcp-5V4LPMB3.js} +16 -16
  70. package/dist/{performance-R6O3N3UB.js → performance-2N3CFXLR.js} +5 -5
  71. package/dist/{review-pipeline-MOOWXWSQ.js → review-pipeline-XZIGBU3Q.js} +4 -4
  72. package/dist/{runtime-RIWQUKBQ.js → runtime-POME3GTR.js} +4 -4
  73. package/dist/{scan-P7YFKB77.js → scan-IYSVRPD2.js} +1 -1
  74. package/dist/{security-U76C54W6.js → security-QLJLMSIW.js} +1 -1
  75. package/dist/{state-events-F44CUUZG.js → state-events-YY5KBZTC.js} +4 -4
  76. package/dist/{validate-IUAV7MOU.js → validate-VZORVK43.js} +5 -5
  77. package/dist/{validate-cross-check-M2ZM6RFY.js → validate-cross-check-OYKEDCST.js} +4 -4
  78. package/package.json +5 -4
  79. package/dist/analyzer-V633GUHY-MBCQWKDU.js +0 -26
@@ -22,8 +22,8 @@ import {
22
22
  getDefaultRegistry,
23
23
  getOutline,
24
24
  getParser,
25
- init_chunk_MUWJHO2S,
26
- init_chunk_S4DTDJ6J,
25
+ init_chunk_IHRT6II2,
26
+ init_chunk_JZ7UO3OF,
27
27
  parseFile,
28
28
  parseSize,
29
29
  readFileContent,
@@ -31,7 +31,7 @@ import {
31
31
  resetParserCache,
32
32
  resolveFileToLayer,
33
33
  validateDependencies
34
- } from "./chunk-N55WOGN3.js";
34
+ } from "./chunk-YJYUJSWK.js";
35
35
  import {
36
36
  AuthAuditEntrySchema,
37
37
  AuthTokenPublicSchema,
@@ -87,7 +87,7 @@ import {
87
87
  init_dist as init_dist2,
88
88
  queryTraceability,
89
89
  skipDirGlobs
90
- } from "./chunk-PP6ZRL5T.js";
90
+ } from "./chunk-WZJR6SOI.js";
91
91
  import {
92
92
  ArchBaselineSchema,
93
93
  ArchConfigSchema,
@@ -123,7 +123,7 @@ import {
123
123
  __export
124
124
  } from "./chunk-KFQGP6VL.js";
125
125
 
126
- // ../core/dist/chunk-FYTJQ2ZY.mjs
126
+ // ../core/dist/chunk-OFA2AAZK.mjs
127
127
  import { createHash } from "crypto";
128
128
  import { readFileSync, writeFileSync, renameSync, mkdirSync, existsSync } from "fs";
129
129
  import { randomBytes } from "crypto";
@@ -759,10 +759,10 @@ async function toHaveMaxDepDepth(received, maxDepth) {
759
759
  };
760
760
  }
761
761
  var CircularDepsCollector, LayerViolationCollector, ArchBaselineManager, ComplexityCollector, CouplingCollector, ForbiddenImportCollector, ModuleSizeCollector, DepDepthCollector, defaultCollectors, archMatchers;
762
- var init_chunk_FYTJQ2ZY = __esm({
763
- "../core/dist/chunk-FYTJQ2ZY.mjs"() {
762
+ var init_chunk_OFA2AAZK = __esm({
763
+ "../core/dist/chunk-OFA2AAZK.mjs"() {
764
764
  "use strict";
765
- init_chunk_MUWJHO2S();
765
+ init_chunk_JZ7UO3OF();
766
766
  init_chunk_IIEDD47I();
767
767
  init_dist2();
768
768
  init_dist2();
@@ -1718,6 +1718,7 @@ __export(dist_exports, {
1718
1718
  parseManifest: () => parseManifest,
1719
1719
  parseMeta: () => parseMeta,
1720
1720
  parseProtectedRegions: () => parseProtectedRegions,
1721
+ parseReferencedIssues: () => parseReferencedIssues,
1721
1722
  parseRoadmap: () => parseRoadmap,
1722
1723
  parseSections: () => parseSections,
1723
1724
  parseSecurityConfig: () => parseSecurityConfig,
@@ -5965,16 +5966,7 @@ function quoteYamlScalar(value) {
5965
5966
  const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
5966
5967
  return `"${escaped}"`;
5967
5968
  }
5968
- function parseShard(md) {
5969
- let data;
5970
- let content;
5971
- try {
5972
- const parsed = matter3(md);
5973
- data = parsed.data;
5974
- content = parsed.content;
5975
- } catch (err) {
5976
- return Err(new Error(`Shard frontmatter is not valid YAML: ${err.message}`));
5977
- }
5969
+ function parseShardFrontmatter(data) {
5978
5970
  const slug = data.slug;
5979
5971
  if (typeof slug !== "string" || slug.length === 0) {
5980
5972
  return Err(new Error("Shard frontmatter missing required field: slug"));
@@ -5990,6 +5982,21 @@ function parseShard(md) {
5990
5982
  new Error(`Shard "${slug}" has invalid order: "${String(rawOrder)}" (must be a number)`)
5991
5983
  );
5992
5984
  }
5985
+ return Ok({ slug, milestone, order });
5986
+ }
5987
+ function parseShard(md) {
5988
+ let data;
5989
+ let content;
5990
+ try {
5991
+ const parsed = matter3(md);
5992
+ data = parsed.data;
5993
+ content = parsed.content;
5994
+ } catch (err) {
5995
+ return Err(new Error(`Shard frontmatter is not valid YAML: ${err.message}`));
5996
+ }
5997
+ const frontmatterResult = parseShardFrontmatter(data);
5998
+ if (!frontmatterResult.ok) return frontmatterResult;
5999
+ const { slug, milestone, order } = frontmatterResult.value;
5993
6000
  const nameMatch = content.match(H3_NAME);
5994
6001
  if (!nameMatch) {
5995
6002
  return Err(new Error(`Shard "${slug}" is missing its "### <name>" heading`));
@@ -6011,6 +6018,17 @@ function serializeShard(shard) {
6011
6018
  return [...frontmatter, ...serializeFeature(shard.feature)].join("\n") + "\n";
6012
6019
  }
6013
6020
  function parseMeta(md) {
6021
+ const block = parseFrontmatterBlock(md);
6022
+ if (!block.ok) return Err(block.error);
6023
+ const { data, body } = block.value;
6024
+ const frontmatter = buildFrontmatter(data);
6025
+ if (!frontmatter.ok) return Err(frontmatter.error);
6026
+ const milestones = parseMilestones2(data);
6027
+ if (!milestones.ok) return Err(milestones.error);
6028
+ const meta = { frontmatter: frontmatter.value, milestones: milestones.value };
6029
+ return attachAssignmentHistory(meta, body);
6030
+ }
6031
+ function parseFrontmatterBlock(md) {
6014
6032
  const match = md.match(FRONTMATTER);
6015
6033
  if (!match) {
6016
6034
  return Err(new Error("_meta.md is missing or has malformed YAML frontmatter"));
@@ -6022,6 +6040,9 @@ function parseMeta(md) {
6022
6040
  } catch (err) {
6023
6041
  return Err(new Error(`_meta.md frontmatter is not valid YAML: ${err.message}`));
6024
6042
  }
6043
+ return Ok({ data, body: match[2] ?? "" });
6044
+ }
6045
+ function buildFrontmatter(data) {
6025
6046
  const project = data.project;
6026
6047
  const rawVersion = data.version;
6027
6048
  const lastSynced = data.last_synced;
@@ -6040,12 +6061,16 @@ function parseMeta(md) {
6040
6061
  const frontmatter = { project, version, lastSynced, lastManualEdit };
6041
6062
  if (typeof data.created === "string") frontmatter.created = data.created;
6042
6063
  if (typeof data.updated === "string") frontmatter.updated = data.updated;
6064
+ return Ok(frontmatter);
6065
+ }
6066
+ function parseMilestones2(data) {
6043
6067
  const rawMilestones = data.milestones;
6044
6068
  if (!Array.isArray(rawMilestones) || !rawMilestones.every((m) => typeof m === "string")) {
6045
6069
  return Err(new Error("_meta.md frontmatter `milestones` must be a list of strings"));
6046
6070
  }
6047
- const meta = { frontmatter, milestones: rawMilestones };
6048
- const body = match[2] ?? "";
6071
+ return Ok(rawMilestones);
6072
+ }
6073
+ function attachAssignmentHistory(meta, body) {
6049
6074
  if (body.includes("## Assignment History")) {
6050
6075
  const history = parseAssignmentHistory(body);
6051
6076
  if (!history.ok) return Err(history.error);
@@ -6078,43 +6103,49 @@ function isRegression(from, to) {
6078
6103
  return STATUS_RANK[to] < STATUS_RANK[from];
6079
6104
  }
6080
6105
  function assembleRoadmap(shards, meta) {
6106
+ const byMilestone = groupShardsByMilestone(shards);
6107
+ const ordered = orderMilestoneNames(meta, byMilestone);
6108
+ const milestones = ordered.map((name) => buildMilestone(name, byMilestone));
6109
+ return {
6110
+ frontmatter: meta.frontmatter,
6111
+ milestones,
6112
+ assignmentHistory: meta.assignmentHistory ?? []
6113
+ };
6114
+ }
6115
+ function groupShardsByMilestone(shards) {
6081
6116
  const byMilestone = /* @__PURE__ */ new Map();
6082
6117
  for (const shard of shards) {
6083
6118
  const bucket = byMilestone.get(shard.milestone);
6084
6119
  if (bucket) bucket.push(shard);
6085
6120
  else byMilestone.set(shard.milestone, [shard]);
6086
6121
  }
6122
+ return byMilestone;
6123
+ }
6124
+ function orderMilestoneNames(meta, byMilestone) {
6087
6125
  const ordered = [];
6088
6126
  const seen = /* @__PURE__ */ new Set();
6089
- for (const name of meta.milestones) {
6090
- if (!seen.has(name)) {
6091
- ordered.push(name);
6092
- seen.add(name);
6093
- }
6094
- }
6095
- for (const name of byMilestone.keys()) {
6127
+ const append = (name) => {
6096
6128
  if (!seen.has(name)) {
6097
6129
  ordered.push(name);
6098
6130
  seen.add(name);
6099
6131
  }
6100
- }
6101
- const milestones = ordered.map((name) => {
6102
- const bucket = [...byMilestone.get(name) ?? []];
6103
- bucket.sort(
6104
- (a, b) => a.order - b.order || STATUS_RANK[b.feature.status] - STATUS_RANK[a.feature.status] || // Deterministic code-unit comparison (NOT localeCompare, whose ICU/locale
6105
- // collation is environment-dependent and threatens byte-stable regen).
6106
- (a.slug < b.slug ? -1 : a.slug > b.slug ? 1 : 0)
6107
- );
6108
- return {
6109
- name,
6110
- isBacklog: name === "Backlog",
6111
- features: bucket.map((s) => s.feature)
6112
- };
6113
- });
6132
+ };
6133
+ for (const name of meta.milestones) append(name);
6134
+ for (const name of byMilestone.keys()) append(name);
6135
+ return ordered;
6136
+ }
6137
+ function compareShards(a, b) {
6138
+ return a.order - b.order || STATUS_RANK[b.feature.status] - STATUS_RANK[a.feature.status] || // Deterministic code-unit comparison (NOT localeCompare, whose ICU/locale
6139
+ // collation is environment-dependent and threatens byte-stable regen).
6140
+ (a.slug < b.slug ? -1 : a.slug > b.slug ? 1 : 0);
6141
+ }
6142
+ function buildMilestone(name, byMilestone) {
6143
+ const bucket = [...byMilestone.get(name) ?? []];
6144
+ bucket.sort(compareShards);
6114
6145
  return {
6115
- frontmatter: meta.frontmatter,
6116
- milestones,
6117
- assignmentHistory: meta.assignmentHistory ?? []
6146
+ name,
6147
+ isBacklog: name === "Backlog",
6148
+ features: bucket.map((s) => s.feature)
6118
6149
  };
6119
6150
  }
6120
6151
  function joinPath(dir, name) {
@@ -8940,10 +8971,12 @@ async function runEntropyCheck(projectRoot, config) {
8940
8971
  const entropyConfig = config.entropy || {};
8941
8972
  const perfConfig = config.performance || {};
8942
8973
  const entryPoints = entropyConfig.entryPoints ?? perfConfig.entryPoints;
8974
+ const driftConfig = entropyConfig.drift;
8943
8975
  const analyzer = new EntropyAnalyzer({
8944
8976
  rootDir: projectRoot,
8945
8977
  ...entryPoints ? { entryPoints } : {},
8946
- analyze: { drift: true, deadCode: true, patterns: false }
8978
+ ...driftConfig?.docPaths ? { docPaths: driftConfig.docPaths } : {},
8979
+ analyze: { drift: driftConfig ?? true, deadCode: true, patterns: false }
8947
8980
  });
8948
8981
  const result = await analyzer.analyze();
8949
8982
  if (!result.ok) {
@@ -12692,6 +12725,20 @@ async function fetchWithRetry(fetchFn, input, init, opts = RETRY_DEFAULTS) {
12692
12725
  }
12693
12726
  return lastResponse;
12694
12727
  }
12728
+ function parseReferencedIssues(text) {
12729
+ if (!text) return [];
12730
+ const re = /(?:[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+)?#(\d+)/g;
12731
+ const seen = /* @__PURE__ */ new Set();
12732
+ const out = [];
12733
+ for (const m of text.matchAll(re)) {
12734
+ const n = Number.parseInt(m[1], 10);
12735
+ if (Number.isInteger(n) && n > 0 && !seen.has(n)) {
12736
+ seen.add(n);
12737
+ out.push(n);
12738
+ }
12739
+ }
12740
+ return out;
12741
+ }
12695
12742
  function isValidTrackerShape(tracker) {
12696
12743
  if (!tracker || typeof tracker !== "object") return false;
12697
12744
  const t = tracker;
@@ -13270,6 +13317,21 @@ function linearFromPriority(p) {
13270
13317
  return 0;
13271
13318
  }
13272
13319
  }
13320
+ function metaDerivedFields(meta, issue) {
13321
+ return {
13322
+ spec: meta.spec ?? null,
13323
+ plans: meta.plan ? [meta.plan] : [],
13324
+ blockedBy: meta.blocked_by ?? [],
13325
+ assignee: assigneeDisplayName(issue.assignee),
13326
+ priority: meta.priority ?? priorityFromLinear(issue.priority),
13327
+ milestone: meta.milestone ?? null,
13328
+ createdAt: issue.createdAt ?? (/* @__PURE__ */ new Date(0)).toISOString(),
13329
+ updatedAt: issue.updatedAt ?? null
13330
+ };
13331
+ }
13332
+ function assigneeDisplayName(assignee) {
13333
+ return assignee?.displayName ?? assignee?.name ?? null;
13334
+ }
13273
13335
  function metaFrom(feature) {
13274
13336
  const meta = {};
13275
13337
  if (feature.spec != null) meta.spec = feature.spec;
@@ -13279,6 +13341,25 @@ function metaFrom(feature) {
13279
13341
  if (feature.milestone != null) meta.milestone = feature.milestone;
13280
13342
  return meta;
13281
13343
  }
13344
+ function collectHistoryEvents(nodes) {
13345
+ const events = [];
13346
+ for (const c of nodes ?? []) {
13347
+ const parsed = parseHistoryComment(c.body);
13348
+ if (parsed) events.push(parsed);
13349
+ }
13350
+ return events;
13351
+ }
13352
+ function parseHistoryComment(body) {
13353
+ if (!body || !body.includes(HISTORY_MARKER)) return null;
13354
+ const m = /```json\s*([\s\S]*?)\s*```/.exec(body);
13355
+ if (!m?.[1]) return null;
13356
+ try {
13357
+ const parsed = JSON.parse(m[1]);
13358
+ if (parsed && typeof parsed.type === "string") return parsed;
13359
+ } catch {
13360
+ }
13361
+ return null;
13362
+ }
13282
13363
  function createTrackerClient(config) {
13283
13364
  if (config.kind === "github-issues") {
13284
13365
  const token = config.token ?? process.env.GITHUB_TOKEN;
@@ -13725,6 +13806,50 @@ function checkRoadmapHealth(roadmap, options) {
13725
13806
  }
13726
13807
  return findings;
13727
13808
  }
13809
+ function demoteUnactionablePlanned(feature, milestoneName, changes) {
13810
+ if (!isUnactionablePlanned(feature)) return;
13811
+ feature.status = "backlog";
13812
+ changes.push({
13813
+ kind: "demoted",
13814
+ feature: feature.name,
13815
+ from: milestoneName,
13816
+ to: "backlog"
13817
+ });
13818
+ }
13819
+ function restoreAssigneeInvariant(feature, milestoneName, changes) {
13820
+ if (feature.status !== "in-progress" && feature.assignee !== null) {
13821
+ changes.push({
13822
+ kind: "unassigned",
13823
+ feature: feature.name,
13824
+ from: milestoneName,
13825
+ to: feature.assignee
13826
+ });
13827
+ feature.assignee = null;
13828
+ } else if (feature.status === "in-progress" && feature.assignee === null) {
13829
+ feature.status = "planned";
13830
+ changes.push({
13831
+ kind: "demoted",
13832
+ feature: feature.name,
13833
+ from: milestoneName,
13834
+ to: "planned"
13835
+ });
13836
+ }
13837
+ }
13838
+ function groomFeature(feature, milestoneName, context, archived, changes) {
13839
+ demoteUnactionablePlanned(feature, milestoneName, changes);
13840
+ restoreAssigneeInvariant(feature, milestoneName, changes);
13841
+ if (context.archiveDone && feature.status === "done" && !context.archive && !context.isIntake) {
13842
+ archived.push(feature);
13843
+ changes.push({
13844
+ kind: "archived",
13845
+ feature: feature.name,
13846
+ from: milestoneName,
13847
+ to: "Shipped"
13848
+ });
13849
+ return false;
13850
+ }
13851
+ return true;
13852
+ }
13728
13853
  function groomRoadmap(roadmap, options) {
13729
13854
  const opts = resolveOptions(options);
13730
13855
  const archiveDone = options?.archiveDone ?? true;
@@ -13732,49 +13857,14 @@ function groomRoadmap(roadmap, options) {
13732
13857
  const archived = [];
13733
13858
  const changes = [];
13734
13859
  for (const milestone of next.milestones) {
13735
- const archive = opts.isArchive(milestone.name);
13736
- const isIntake = milestone.name === opts.intakeMilestone;
13737
- const kept = [];
13738
- for (const feature of milestone.features) {
13739
- if (isUnactionablePlanned(feature)) {
13740
- feature.status = "backlog";
13741
- changes.push({
13742
- kind: "demoted",
13743
- feature: feature.name,
13744
- from: milestone.name,
13745
- to: "backlog"
13746
- });
13747
- }
13748
- if (feature.status !== "in-progress" && feature.assignee !== null) {
13749
- changes.push({
13750
- kind: "unassigned",
13751
- feature: feature.name,
13752
- from: milestone.name,
13753
- to: feature.assignee
13754
- });
13755
- feature.assignee = null;
13756
- } else if (feature.status === "in-progress" && feature.assignee === null) {
13757
- feature.status = "planned";
13758
- changes.push({
13759
- kind: "demoted",
13760
- feature: feature.name,
13761
- from: milestone.name,
13762
- to: "planned"
13763
- });
13764
- }
13765
- if (archiveDone && feature.status === "done" && !archive && !isIntake) {
13766
- archived.push(feature);
13767
- changes.push({
13768
- kind: "archived",
13769
- feature: feature.name,
13770
- from: milestone.name,
13771
- to: "Shipped"
13772
- });
13773
- continue;
13774
- }
13775
- kept.push(feature);
13776
- }
13777
- milestone.features = kept;
13860
+ const context = {
13861
+ archiveDone,
13862
+ archive: opts.isArchive(milestone.name),
13863
+ isIntake: milestone.name === opts.intakeMilestone
13864
+ };
13865
+ milestone.features = milestone.features.filter(
13866
+ (feature) => groomFeature(feature, milestone.name, context, archived, changes)
13867
+ );
13778
13868
  }
13779
13869
  next.milestones = next.milestones.filter(
13780
13870
  (m) => m.features.length > 0 || m.name === opts.intakeMilestone
@@ -15060,7 +15150,7 @@ function arrayLen(arr) {
15060
15150
  return Array.isArray(arr) ? arr.length : 0;
15061
15151
  }
15062
15152
  async function gatherEntropyBlock(projectPath) {
15063
- const { EntropyAnalyzer: EntropyAnalyzer2 } = await import("./analyzer-V633GUHY-MBCQWKDU.js");
15153
+ const { EntropyAnalyzer: EntropyAnalyzer2 } = await import("./analyzer-XRCV63KC-HLTEIKAJ.js");
15064
15154
  const analyzer = new EntropyAnalyzer2({
15065
15155
  rootDir: projectPath,
15066
15156
  include: ["src/**/*.ts", "src/**/*.tsx", "packages/*/src/**/*.ts"],
@@ -15976,9 +16066,9 @@ function writeStrategyDoc(doc, opts) {
15976
16066
  var WHATWG_BAD_PORTS, BAD_PORT_SET, VALID_TYPES, CONVENTIONAL_PATTERN, LOOKBACK_PATTERN, PulseDbSourceSchema, PulseSourcesSchema, PulseConfigSchema, KNOWN_SECTION_NAMES, ISO_DATE, PLACEHOLDER_INLINE, REQUIRED, ALLOWED, StrategyFrontmatterSchema, StrategySectionSchema, StrategyDocSchema, BUG_TRACK_CATEGORIES, KNOWLEDGE_TRACK_CATEGORIES, ALL_SOLUTION_CATEGORIES, ISO_DATE2, BugTrackSchema, KnowledgeTrackSchema, BaseFrontmatter, SolutionDocFrontmatterSchema, TRACK_CATEGORIES, DEFAULT_AGNIX_TIMEOUT_MS, HARNESS_AGNIX_DISABLE, HARNESS_AGNIX_BIN, CLAUDE_MD_WARN_BYTES, CLAUDE_MD_ERROR_BYTES, CONTEXT_FILES, MIN_DESCRIPTION_LENGTH, SETTINGS_PATHS, KNOWN_HOOK_EVENTS, MCP_SETTINGS_FILES, REQUIRED_SECTIONS, SETTINGS_FILES, KNOWN_AGNIX_TOOLS, ROADMAP_READ_ALLOWLIST, ROADMAP_MD, DYNAMIC_ROADMAP_IO, KEBAB_CASE, TICKET_ID, DEFAULT_SECTIONS, DEFAULT_RATIOS, NODE_TYPE_TO_CATEGORY, SECTION_LEVEL_MAP, DEFAULT_LEVEL, DEFAULT_BUDGET, DEFAULT_LOADER_CONFIG, LEVEL_FRACTIONS, PHASE_PRIORITIES, ManifestSchema, BundleConstraintsSchema, BundleSchema, ContributionsSchema, LockfilePackageSchema, LockfileSchema, SharableLayerSchema, SharableForbiddenImportSchema, SharableBoundaryConfigSchema, SharableSecurityRulesSchema, VALID_SCOPES, LINE_PATTERN, START_PATTERN, END_PATTERN, readFile72, writeFile32, unlink22, mkdir22, copyFile2, DEFAULT_FIX_CONFIG, EXPORT_TYPE_KEYWORD, ALWAYS_UNSAFE_TYPES, idCounter, DEAD_CODE_FIX_ACTIONS, MustExportRuleSchema, MustExportDefaultRuleSchema, NoExportRuleSchema, MustImportRuleSchema, NoImportRuleSchema, NamingRuleSchema, MaxExportsRuleSchema, MaxLinesRuleSchema, RequireJSDocRuleSchema, RuleSchema, ConfigPatternSchema, PatternConfigSchema, DriftConfigSchema, DeadCodeConfigSchema, EntropyConfigSchema, BaselineManager, BenchmarkRunner, RegressionDetector, SOURCE_EXTENSIONS, FUNCTION_DECL_RE, CONST_DECL_RE, CriticalPathResolver, NoOpTelemetryAdapter, NoOpExecutor, ConsoleSink, feedbackConfig, ChecklistBuilder, AgentActionEmitter, globalEmitter, FileSink, NoOpSink, ConfidenceTierSchema, RegressionResultSchema, DirectionSchema, CategoryForecastSchema, SpecImpactSignalsSchema, SpecImpactEstimateSchema, ContributingFeatureSchema, AdjustedForecastSchema, PredictionWarningSchema, StabilityForecastSchema, PredictionResultSchema, PredictionOptionsSchema, VALID_STATUSES, EM_DASH, VALID_PRIORITIES, EM_DASH2, H3_NAME, FRONTMATTER, STATUS_RANK, META_FILE, ShardStore, MonolithStore, ALL_CATEGORIES, DIRECTION_THRESHOLD, PredictionEngine, DEFAULT_COEFFICIENTS, SpecImpactEstimator, EMPTY_HISTORY, ViolationHistoryManager, NORMALIZERS, MAX_SAMPLES, DEFAULT_WINDOW_WEEKS, DEFAULT_MIN_OCCURRENCES, FailureEntrySchema, HandoffSchema, GateCheckSchema, GateResultSchema, GateConfigSchema, HarnessStateSchema, DEFAULT_STATE, HARNESS_DIR, STATE_FILE, LEARNINGS_FILE, FAILURES_FILE, HANDOFF_FILE, GATE_CONFIG_FILE, INDEX_FILE, SESSIONS_DIR, SESSION_INDEX_FILE, SUMMARY_FILE, SESSION_STATE_FILE, ARCHIVE_DIR, CONTENT_HASHES_FILE, StreamInfoSchema, StreamIndexSchema, DEFAULT_STREAM_INDEX, EVENT_LOG_FILE, EVENT_BLOBS_DIR, MAX_LINE_BYTES, BLOB_REF_KEY, SNAPSHOT_FILE, STREAMS_DIR, STREAM_NAME_REGEX, branchCache, BRANCH_CACHE_TTL_MS, STATE_FILES, STATE_DIRS, MAX_CACHE_ENTRIES, learningsCacheMap, WEIGHTS, LOCK_RETRIES, LOCK_BACKOFFS, PROMOTABLE_OUTCOMES, failuresCacheMap, FAILURE_LINE_REGEX, SAFE_GATE_COMMAND, event_sourcing_exports, ScopeSchema, envelopeShape, StateImportedPayload, DecisionRecordedPayload, PositionSetPayload, BlockerOpenedPayload, BlockerResolvedPayload, ProgressSetPayload, SessionSummarizedPayload, LANES, LaneSchema, TaskRegisteredPayload, LaneTransitionedPayload, UserInputCapturedPayload, ApprovalRequestedPayload, ApprovalResolvedPayload, EventSchema, BlobRefSchema, StoredEventSchema, cached, localCounters, CORE_HANDLERS, KIND_LABEL, MATERIALIZE_DEBOUNCE_MS, pendingTimers, inflightMaterializes, ensured, TERMINAL_LANES, NON_TERMINAL, TABLE, RuleRegistry, DEFAULT_SECURITY_CONFIG, RuleOverrideSchema, SecurityConfigSchema, secretRules, injectionRules, xssRules, cryptoRules, pathTraversalRules, networkRules, deserializationRules, agentConfigRules, mcpRules, insecureDefaultsRules, sharpEdgesRules, nodeRules, expressRules, reactRules, goRules, SecurityScanner, OSV_ENDPOINT, DEFAULT_CACHE_DIR, DEFAULT_TTL_HOURS, hiddenUnicodePatterns, reRolingPatterns, permissionEscalationPatterns, encodedPayloadPatterns, indirectInjectionPatterns, contextManipulationPatterns, socialEngineeringPatterns, suspiciousPatterns, ALL_PATTERNS, DESTRUCTIVE_BASH, TAINT_DURATION_MS, DEFAULT_SESSION_ID, SecurityCategorySnapshotSchema, SupplyChainSnapshotSchema, SecurityTimelineSnapshotSchema, FindingLifecycleSchema, SecurityTimelineFileSchema, DirectionSchema2, SecurityTrendLineSchema, TrendAttributionSchema, SecurityTrendResultSchema, TimeToFixStatsSchema, TimeToFixResultSchema, EMPTY_SUPPLY_CHAIN, SecurityTimelineManager, ALL_CHECKS, STATUS_ICONS, CINotifier, ExclusionSet, PREFIX_PATTERNS, TEST_FILE_PATTERN, MD_FILE_PATTERN, ALL_DOMAINS, SECURITY_PATTERNS, JS_EXT_FALLBACKS, SEVERITY_RANK, SEVERITY_ORDER, SEVERITY_LABELS, VALIDATED_BY_RANK, VALIDATION_SCORES, DOMAIN_BASELINES, FACTOR_WEIGHTS, EVIDENCE_SATURATION, CORROBORATED_AGREEMENT, STANDALONE_AGREEMENT, AGREEMENT_LINE_GAP, COMPLIANCE_DESCRIPTOR, EXPORT_RE, BUG_DETECTION_DESCRIPTOR, SECURITY_DESCRIPTOR, EVAL_PATTERN, SECRET_PATTERNS, SQL_CONCAT_PATTERN, SHELL_EXEC_PATTERN, ARCHITECTURE_DESCRIPTOR, LARGE_FILE_THRESHOLD, VIOLATION_FILE_RE, LEARNINGS_DESCRIPTOR, RELEVANCE_THRESHOLD, ADVERSARIAL_DESCRIPTOR, UNVERIFIED_INPUT_PATTERN, PROMISE_NO_REJECT_PATTERN, UNGUARDED_OPTIONAL_CALL, COMPOSE_NO_AWAIT, FETCH_NO_TIMEOUT, TYPESCRIPT_STRICT_DESCRIPTOR, EXPLICIT_ANY_PATTERN, TS_IGNORE_PATTERN, UNSAFE_AS_UNKNOWN, NON_NULL_ASSERTION, VAGUE_HELPER_NAME, FRONTEND_RACES_DESCRIPTOR, AGENT_DESCRIPTORS, CONDITIONAL_SUBAGENT_DESCRIPTORS, AGENT_RUNNERS, SUBAGENT_ORDER, RISK_KEYWORDS, EXCLUDED_PATH_PATTERNS, ASYNC_UI_SIGNALS, WORD_BOUNDARY, DOWNGRADE_MAP, DEFAULT_PROVIDER_TIERS, SMALL_SUGGESTION_LINE_LIMIT, FILE_LINE_RANGE_PATTERN, FILE_LINE_PATTERN, FILE_ONLY_PATTERN, SECURITY_SENSITIVE_PATH, MIGRATION_PATH, TEST_PATH, CONFIG_PATH, STAGE_DOMAINS, CI_REVIEW_VERDICT_SCHEMA_VERSION, CI_RUNNERS, CI_ASSESSMENTS, CI_REVIEW_DOMAINS, ReviewFindingSchema, CiReviewVerdictSchema, VERDICT_MAP, RUNNER_PRESETS, VERDICT_MAP2, DEFAULT_EXEC_TIMEOUT_MS, DEFAULT_EXEC_MAX_STDOUT_BYTES, STDERR_CAP_BYTES, STDERR_TAIL_BYTES, defaultExecFile, EMPTY_TIER, EM_DASH3, DEFAULT_MILESTONE, MAX_CLOSEST_MATCHES, MACHINE_ASSIGNEE_PATTERNS, EXTERNAL_ID_RE, RETRY_DEFAULTS, GitHubIssuesSyncAdapter, syncMutex, PRIORITY_RANK, PRIORITY_RANK2, POSITION_WEIGHT, DEPENDENTS_WEIGHT, AFFINITY_WEIGHT, ConflictError, DEFAULTS, GitHubHttp, ETagStore, START_MARKER, END_MARKER, SERIALIZER_FIELDS, GitHubIssuesTrackerAdapter, LINEAR_ENDPOINT, EXTERNAL_PREFIX, HISTORY_MARKER, ISSUE_FIELDS, LinearTrackerAdapter, migrate_exports, HASH_RE, DEFAULT_CATCH_ALL, DEFAULT_INTAKE, DEFAULT_MAX_ACTIVE, InteractionTypeSchema, QuestionSchema, ConfirmationSchema, TransitionSchema, EmitInteractionInputSchema, ProjectScanner, SHELL_TEMPLATE, STYLES, SCRIPTS, MockLLMService, llmService, ContentPipeline, BlueprintGenerator, TOKENS_PER_MILLION, fallback_default, LITELLM_PRICING_URL, CACHE_TTL_MS, STALENESS_WARNING_DAYS, MICRODOLLARS_PER_DOLLAR, TOKENS_PER_MILLION2, StructuralStrategy, DEFAULT_TOKEN_BUDGET, CHARS_PER_TOKEN, TRUNCATION_MARKER, TruncationStrategy, CompactionPipeline, STABILITY_LOOKUP, TIER_ORDER, AnthropicCacheAdapter, TIER_ORDER2, OpenAICacheAdapter, TIER_ORDER3, CACHED_CONTENT_MARKER, GeminiCacheAdapter, VERSION, POSTHOG_BATCH_URL, MAX_ATTEMPTS, TIMEOUT_MS, DEFAULT_CAPACITY, CacheMetricsRecorder, RETRY_BACKOFFS_MS, DEFAULT_FLUSH_INTERVAL_MS, DEFAULT_BATCH_SIZE, defaultFetch, defaultWarn, OTLPExporter, SERVICE_NAME_ATTR, SpanKind, ModeSchema, SeveritySchema, TierSchema, StrengthFindingSchema, HarnessConfigSubsetSchema, HookFileSchema, ProjectContextSchema, AuditSummarySchema, AuditResultSchema, SEVERITY_WEIGHTS, clamp, HOOK_SCRIPT_TOKEN, NEVER_BLOCKS, ALWAYS_EXIT_ZERO, EXIT_ZERO_LINE, NONZERO_EXIT, GATE, ERREXIT, strength001NonblockingHooks, BASELINE_REWRITE, strength002Autobaseline, SKIP_RE, strength003SkipList, strength004EmptyThresholds, INIT_SKILL_PATH, DEFAULT_BASIC, strength005LowestTier, AUTO_PATTERN, COMMENT_LINE, NAME_VALUE_LINE, PAT_GATING, REVIEW_SIGNAL, strength006AutoapproveBaseline, CHECK_KEYS, SIGNAL_REGISTRY, HEALTH_SIGNAL_NAMES, CHECK_SIGNAL_MAP, SIGNAL_CATEGORY_MAP, strength007SnapshotSignalMismatch, ALL_RULES, HarnessStrengthAuditor, MAX_TOP_AFFECTED, CompoundLockHeldError, KNOWN_CATEGORIES, ProposalNotFoundError, ProposalConflictError, ALLOWED_FIELD_KEYS, PII_TOKENS, PII_FIELD_DENYLIST, PII_LINE_RE, ALLOWED_SET, PulseAdapterAlreadyRegisteredError, REGISTRY, MOCK_ADAPTER_NAME, ALLOWED_FIELD_SET, adapter, LOOKBACK_RE, MAX_LINES, INLINE_TEMPLATE, TRUNCATION_MARKER2, execFileAsync, FIX_RE, EMPTY_REPO_RE, execFileAsync2, TRACK_CATEGORIES2, STOPWORDS, KEYWORD_TO_CATEGORY, SECTION_ORDER, FILENAME;
15977
16067
  var init_dist3 = __esm({
15978
16068
  "../core/dist/index.mjs"() {
15979
- init_chunk_FYTJQ2ZY();
15980
- init_chunk_S4DTDJ6J();
15981
- init_chunk_MUWJHO2S();
16069
+ init_chunk_OFA2AAZK();
16070
+ init_chunk_IHRT6II2();
16071
+ init_chunk_JZ7UO3OF();
15982
16072
  init_chunk_XKLVJIPL();
15983
16073
  init_chunk_IIEDD47I();
15984
16074
  init_chunk_7P6ASYW6();
@@ -17106,7 +17196,7 @@ var init_dist3 = __esm({
17106
17196
  check: "Context validation",
17107
17197
  passed: graphData.graphExists && graphData.nodeCount > 0,
17108
17198
  severity: "info",
17109
- details: graphData.graphExists ? `Graph loaded: ${graphData.nodeCount} nodes, ${graphData.edgeCount} edges` : "No graph available \u2014 run harness scan to build the knowledge graph"
17199
+ details: graphData.graphExists ? `Graph loaded: ${graphData.nodeCount} nodes, ${graphData.edgeCount} edges` : "No graph available \u2014 run harness graph scan to build the knowledge graph"
17110
17200
  }) : void 0
17111
17201
  )
17112
17202
  );
@@ -21823,14 +21913,7 @@ ${JSON.stringify(event)}`;
21823
21913
  name: issue.title,
21824
21914
  status: statusFromStateType(issue.state?.type),
21825
21915
  summary,
21826
- spec: meta.spec ?? null,
21827
- plans: meta.plan ? [meta.plan] : [],
21828
- blockedBy: meta.blocked_by ?? [],
21829
- assignee: issue.assignee?.displayName ?? issue.assignee?.name ?? null,
21830
- priority: meta.priority ?? priorityFromLinear(issue.priority),
21831
- milestone: meta.milestone ?? null,
21832
- createdAt: issue.createdAt ?? (/* @__PURE__ */ new Date(0)).toISOString(),
21833
- updatedAt: issue.updatedAt ?? null
21916
+ ...metaDerivedFields(meta, issue)
21834
21917
  };
21835
21918
  }
21836
21919
  /** Resolve (and cache) the team's first state id of each workflow type. */
@@ -21858,6 +21941,12 @@ ${JSON.stringify(event)}`;
21858
21941
  if (!r.ok) return r;
21859
21942
  return Ok(r.value.users?.nodes?.[0]?.id ?? null);
21860
21943
  }
21944
+ /** Resolve an assignee to a Linear user id, or null when absent/unresolved. */
21945
+ async assigneeIdFor(assignee) {
21946
+ if (!assignee) return null;
21947
+ const userR = await this.resolveUserId(assignee);
21948
+ return userR.ok && userR.value ? userR.value : null;
21949
+ }
21861
21950
  // ── Reads ──────────────────────────────────────────────────────────────
21862
21951
  async fetchAll() {
21863
21952
  const r = await this.gql(
@@ -21888,18 +21977,9 @@ ${JSON.stringify(event)}`;
21888
21977
  async create(feature) {
21889
21978
  const stateR = await this.resolveStateId(feature.status ?? "backlog");
21890
21979
  if (!stateR.ok) return stateR;
21891
- const body = serializeBodyBlock(feature.summary ?? "", metaFrom(feature));
21892
- const input = {
21893
- teamId: this.teamId,
21894
- title: feature.name,
21895
- description: body,
21896
- stateId: stateR.value,
21897
- priority: linearFromPriority(feature.priority)
21898
- };
21899
- if (feature.assignee) {
21900
- const userR = await this.resolveUserId(feature.assignee);
21901
- if (userR.ok && userR.value) input.assigneeId = userR.value;
21902
- }
21980
+ const input = this.buildCreateInput(feature, stateR.value);
21981
+ const assigneeId = await this.assigneeIdFor(feature.assignee);
21982
+ if (assigneeId) input.assigneeId = assigneeId;
21903
21983
  const r = await this.gql(
21904
21984
  `mutation($input:IssueCreateInput!){ issueCreate(input:$input){ issue { ${ISSUE_FIELDS} } } }`,
21905
21985
  { input }
@@ -21909,6 +21989,16 @@ ${JSON.stringify(event)}`;
21909
21989
  if (!issue) return Err(new Error("Linear issueCreate returned no issue"));
21910
21990
  return Ok(this.featureFromIssue(issue));
21911
21991
  }
21992
+ /** Assemble the IssueCreateInput payload for a new feature (sans assignee, which is resolved async). */
21993
+ buildCreateInput(feature, stateId) {
21994
+ return {
21995
+ teamId: this.teamId,
21996
+ title: feature.name,
21997
+ description: serializeBodyBlock(feature.summary ?? "", metaFrom(feature)),
21998
+ stateId,
21999
+ priority: linearFromPriority(feature.priority)
22000
+ };
22001
+ }
21912
22002
  /** Shared issueUpdate with optional optimistic-concurrency check on updatedAt. */
21913
22003
  async issueUpdate(externalId, input, ifMatch) {
21914
22004
  const id = this.toIssueId(externalId);
@@ -21935,6 +22025,26 @@ ${JSON.stringify(event)}`;
21935
22025
  if (!issue) return Err(new Error("Linear issueUpdate returned no issue"));
21936
22026
  return Ok(this.featureFromIssue(issue));
21937
22027
  }
22028
+ /**
22029
+ * Merge body-backed patch fields into the current issue's harness-meta block,
22030
+ * returning the serialized description (so the meta block isn't clobbered).
22031
+ */
22032
+ async mergedDescription(externalId, patch) {
22033
+ const cur = await this.fetchById(externalId);
22034
+ if (!cur.ok) return cur;
22035
+ if (!cur.value) return Err(new Error(`Linear issue not found: ${externalId}`));
22036
+ const f = cur.value.feature;
22037
+ const merged = {
22038
+ name: patch.name ?? f.name,
22039
+ summary: patch.summary ?? f.summary,
22040
+ spec: patch.spec !== void 0 ? patch.spec : f.spec,
22041
+ plans: patch.plans !== void 0 ? patch.plans : f.plans,
22042
+ blockedBy: patch.blockedBy !== void 0 ? patch.blockedBy : f.blockedBy,
22043
+ priority: patch.priority !== void 0 ? patch.priority : f.priority,
22044
+ milestone: patch.milestone !== void 0 ? patch.milestone : f.milestone
22045
+ };
22046
+ return Ok(serializeBodyBlock(merged.summary ?? "", metaFrom(merged)));
22047
+ }
21938
22048
  async update(externalId, patch, ifMatch) {
21939
22049
  const input = {};
21940
22050
  if (patch.name !== void 0) input.title = patch.name;
@@ -21953,31 +22063,23 @@ ${JSON.stringify(event)}`;
21953
22063
  "milestone"
21954
22064
  ];
21955
22065
  if (bodyFields.some((k) => patch[k] !== void 0)) {
21956
- const cur = await this.fetchById(externalId);
21957
- if (!cur.ok) return cur;
21958
- if (!cur.value) return Err(new Error(`Linear issue not found: ${externalId}`));
21959
- const f = cur.value.feature;
21960
- const merged = {
21961
- name: patch.name ?? f.name,
21962
- summary: patch.summary ?? f.summary,
21963
- spec: patch.spec !== void 0 ? patch.spec : f.spec,
21964
- plans: patch.plans !== void 0 ? patch.plans : f.plans,
21965
- blockedBy: patch.blockedBy !== void 0 ? patch.blockedBy : f.blockedBy,
21966
- priority: patch.priority !== void 0 ? patch.priority : f.priority,
21967
- milestone: patch.milestone !== void 0 ? patch.milestone : f.milestone
21968
- };
21969
- input.description = serializeBodyBlock(merged.summary ?? "", metaFrom(merged));
21970
- }
21971
- if (patch.assignee !== void 0) {
21972
- if (patch.assignee === null) {
21973
- input.assigneeId = null;
21974
- } else {
21975
- const userR = await this.resolveUserId(patch.assignee);
21976
- if (userR.ok && userR.value) input.assigneeId = userR.value;
21977
- }
22066
+ const descR = await this.mergedDescription(externalId, patch);
22067
+ if (!descR.ok) return descR;
22068
+ input.description = descR.value;
21978
22069
  }
22070
+ await this.applyAssigneeUpdate(patch, input);
21979
22071
  return this.issueUpdate(externalId, input, ifMatch);
21980
22072
  }
22073
+ /** Resolve `patch.assignee` (name → user id, or explicit null to unassign) into the update input. */
22074
+ async applyAssigneeUpdate(patch, input) {
22075
+ if (patch.assignee === void 0) return;
22076
+ if (patch.assignee === null) {
22077
+ input.assigneeId = null;
22078
+ return;
22079
+ }
22080
+ const userR = await this.resolveUserId(patch.assignee);
22081
+ if (userR.ok && userR.value) input.assigneeId = userR.value;
22082
+ }
21981
22083
  async claim(externalId, assignee, ifMatch) {
21982
22084
  const userR = await this.resolveUserId(assignee);
21983
22085
  if (!userR.ok) return userR;
@@ -22017,17 +22119,7 @@ ${JSON.stringify(event)}
22017
22119
  { id: this.toIssueId(externalId) }
22018
22120
  );
22019
22121
  if (!r.ok) return r;
22020
- const events = [];
22021
- for (const c of r.value.issue?.comments?.nodes ?? []) {
22022
- if (!c.body || !c.body.includes(HISTORY_MARKER)) continue;
22023
- const m = /```json\s*([\s\S]*?)\s*```/.exec(c.body);
22024
- if (!m?.[1]) continue;
22025
- try {
22026
- const parsed = JSON.parse(m[1]);
22027
- if (parsed && typeof parsed.type === "string") events.push(parsed);
22028
- } catch {
22029
- }
22030
- }
22122
+ const events = collectHistoryEvents(r.value.issue?.comments?.nodes);
22031
22123
  return Ok(typeof limit === "number" ? events.slice(0, limit) : events);
22032
22124
  }
22033
22125
  };
@@ -23490,6 +23582,7 @@ export {
23490
23582
  parseExternalId,
23491
23583
  buildExternalId,
23492
23584
  GitHubIssuesSyncAdapter,
23585
+ parseReferencedIssues,
23493
23586
  loadTrackerSyncConfig,
23494
23587
  reconcileDoneFromClosedIssues,
23495
23588
  syncToExternal,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  init_dist
3
- } from "./chunk-KLPX55TI.js";
3
+ } from "./chunk-UACYZCW4.js";
4
4
  import {
5
5
  Err,
6
6
  Ok