@harness-engineering/cli 4.0.0 → 4.0.1

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 (64) hide show
  1. package/dist/agents/skills/claude-code/product-advisor/SKILL.md +202 -0
  2. package/dist/agents/skills/claude-code/product-advisor/skill.yaml +74 -0
  3. package/dist/agents/skills/codex/product-advisor/SKILL.md +202 -0
  4. package/dist/agents/skills/codex/product-advisor/skill.yaml +74 -0
  5. package/dist/agents/skills/cursor/product-advisor/SKILL.md +202 -0
  6. package/dist/agents/skills/cursor/product-advisor/skill.yaml +74 -0
  7. package/dist/agents/skills/gemini-cli/product-advisor/SKILL.md +202 -0
  8. package/dist/agents/skills/gemini-cli/product-advisor/skill.yaml +74 -0
  9. package/dist/{agents-md-7OSNWU52.js → agents-md-VJLYEH2S.js} +4 -4
  10. package/dist/{analyzer-V633GUHY-MBCQWKDU.js → analyzer-V633GUHY-OV335HGM.js} +2 -2
  11. package/dist/{architecture-2XYGJGT3.js → architecture-YQH2NYXY.js} +5 -5
  12. package/dist/{assess-project-3KPP2D6M.js → assess-project-KOTAPAHD.js} +1 -1
  13. package/dist/bin/harness-mcp.js +16 -16
  14. package/dist/bin/harness.js +27 -27
  15. package/dist/{check-phase-gate-46U5AAXE.js → check-phase-gate-CHU2E3OU.js} +5 -5
  16. package/dist/{chunk-AK67S3O4.js → chunk-36GQREOE.js} +3 -3
  17. package/dist/{chunk-CLI4K2CH.js → chunk-4U5LDFCY.js} +1 -1
  18. package/dist/{chunk-VTUINXWR.js → chunk-4VXFZSYF.js} +1 -1
  19. package/dist/{chunk-HXMAIVV2.js → chunk-5RSXUAMN.js} +1 -1
  20. package/dist/{chunk-TTWNGOBT.js → chunk-6SCYTIF4.js} +6 -6
  21. package/dist/{chunk-N55WOGN3.js → chunk-6YFHUIGT.js} +1 -1
  22. package/dist/{chunk-WL3O2PKJ.js → chunk-7PJCK7UE.js} +8 -8
  23. package/dist/{chunk-PHX45JYN.js → chunk-7TIJXWG3.js} +2 -2
  24. package/dist/{chunk-SPE7P4GL.js → chunk-AAUY53CC.js} +1 -1
  25. package/dist/{chunk-VTU3QOJT.js → chunk-AX5KGRM6.js} +1 -1
  26. package/dist/{chunk-MYVTYFQD.js → chunk-E5EW5HVJ.js} +1 -1
  27. package/dist/{chunk-HMW3C5DP.js → chunk-EYMOPFSD.js} +2 -2
  28. package/dist/{chunk-6YXCZV33.js → chunk-GHBFNLV2.js} +278 -211
  29. package/dist/{chunk-363V6GPR.js → chunk-I3CGCZJF.js} +9 -9
  30. package/dist/{chunk-MSBJ445U.js → chunk-JPVOI7GP.js} +5 -5
  31. package/dist/{chunk-6NCREWWM.js → chunk-M7F2PPN3.js} +3 -3
  32. package/dist/{chunk-KLPX55TI.js → chunk-MMLQPHZ5.js} +210 -135
  33. package/dist/{chunk-BV45354Q.js → chunk-OQ6KOQEV.js} +9 -9
  34. package/dist/{chunk-ERILXQBP.js → chunk-RG3TNIUB.js} +610 -428
  35. package/dist/{chunk-GXCPDAS5.js → chunk-SDMV4QHM.js} +5 -5
  36. package/dist/{chunk-ZEYAOYJL.js → chunk-SEA2PFVI.js} +1 -1
  37. package/dist/{chunk-IYWT3JJW.js → chunk-T746REAE.js} +1 -1
  38. package/dist/{chunk-FZ4Q73HA.js → chunk-UWVMJEUJ.js} +1 -1
  39. package/dist/{chunk-PP6ZRL5T.js → chunk-WZJR6SOI.js} +536 -422
  40. package/dist/{chunk-XFHZ7GCI.js → chunk-XAVMTAUT.js} +2 -2
  41. package/dist/{chunk-S2RQPPL6.js → chunk-YANR2RL7.js} +1 -1
  42. package/dist/{chunk-2N4OENGE.js → chunk-YYFSFSB3.js} +1 -1
  43. package/dist/{ci-workflow-XJN4UO3Q.js → ci-workflow-HO4WKGNG.js} +4 -4
  44. package/dist/{dist-IPVFYGY2.js → dist-6D2F2J57.js} +3 -3
  45. package/dist/{dist-OWHMNL4W.js → dist-DWBTZXR5.js} +1 -1
  46. package/dist/{docs-RSFZTNPG.js → docs-FP47JONV.js} +5 -5
  47. package/dist/{engine-DJIHY4JT.js → engine-YUA6IYOG.js} +4 -4
  48. package/dist/{entropy-EAEKJKRM.js → entropy-DBW2INZU.js} +4 -4
  49. package/dist/{feedback-JT5X3DBC.js → feedback-BPHADTRQ.js} +1 -1
  50. package/dist/{generate-agent-definitions-CC6KED3E.js → generate-agent-definitions-KPVGBGDY.js} +5 -5
  51. package/dist/{glob-helper-GLZAURJC.js → glob-helper-VR6UCZZB.js} +1 -1
  52. package/dist/{graph-loader-HHXN5FLP.js → graph-loader-STFGYKZH.js} +1 -1
  53. package/dist/index.js +27 -27
  54. package/dist/{loader-UWNVTYBY.js → loader-QOJQ6NZM.js} +4 -4
  55. package/dist/{mcp-TCLNK7HG.js → mcp-ZPX6O767.js} +16 -16
  56. package/dist/{performance-R6O3N3UB.js → performance-FXYTGJGQ.js} +5 -5
  57. package/dist/{review-pipeline-MOOWXWSQ.js → review-pipeline-2TFQ5OAE.js} +4 -4
  58. package/dist/{runtime-RIWQUKBQ.js → runtime-XK6ZCHKE.js} +4 -4
  59. package/dist/{scan-P7YFKB77.js → scan-IYSVRPD2.js} +1 -1
  60. package/dist/{security-U76C54W6.js → security-4W2P5ZGI.js} +1 -1
  61. package/dist/{state-events-F44CUUZG.js → state-events-ECQAIZLU.js} +4 -4
  62. package/dist/{validate-IUAV7MOU.js → validate-64KTEGIE.js} +5 -5
  63. package/dist/{validate-cross-check-M2ZM6RFY.js → validate-cross-check-J5PVGICB.js} +4 -4
  64. package/package.json +1 -1
@@ -7,10 +7,10 @@ import {
7
7
  import {
8
8
  findConfigFile,
9
9
  loadConfig
10
- } from "./chunk-XFHZ7GCI.js";
10
+ } from "./chunk-XAVMTAUT.js";
11
11
  import {
12
12
  init_dist
13
- } from "./chunk-KLPX55TI.js";
13
+ } from "./chunk-MMLQPHZ5.js";
14
14
  import {
15
15
  Ok
16
16
  } from "./chunk-BDGTZRZ6.js";
@@ -50,18 +50,18 @@ async function handleCheckDocs(input) {
50
50
  const projectPath = sanitizePath(input.path);
51
51
  const scope = input.scope ?? "coverage";
52
52
  if (scope === "integrity") {
53
- const { validateKnowledgeMap } = await import("./dist-IPVFYGY2.js");
53
+ const { validateKnowledgeMap } = await import("./dist-6D2F2J57.js");
54
54
  const result2 = await validateKnowledgeMap(projectPath);
55
55
  return resultToMcpResponse(result2);
56
56
  }
57
57
  if (scope === "all") {
58
- const { checkDocCoverage: checkDocCoverage2, validateKnowledgeMap } = await import("./dist-IPVFYGY2.js");
58
+ const { checkDocCoverage: checkDocCoverage2, validateKnowledgeMap } = await import("./dist-6D2F2J57.js");
59
59
  const domain2 = input.domain ?? "src";
60
- const { loadGraphStore: loadGraphStore2 } = await import("./graph-loader-HHXN5FLP.js");
60
+ const { loadGraphStore: loadGraphStore2 } = await import("./graph-loader-STFGYKZH.js");
61
61
  const store2 = await loadGraphStore2(projectPath);
62
62
  let graphCoverage2;
63
63
  if (store2) {
64
- const { Assembler } = await import("./dist-OWHMNL4W.js");
64
+ const { Assembler } = await import("./dist-DWBTZXR5.js");
65
65
  const assembler = new Assembler(store2);
66
66
  const report = assembler.checkCoverage();
67
67
  graphCoverage2 = {
@@ -94,13 +94,13 @@ async function handleCheckDocs(input) {
94
94
  }
95
95
  return resultToMcpResponse(Ok({ coverage, integrity }));
96
96
  }
97
- const { checkDocCoverage } = await import("./dist-IPVFYGY2.js");
97
+ const { checkDocCoverage } = await import("./dist-6D2F2J57.js");
98
98
  const domain = input.domain ?? "src";
99
- const { loadGraphStore } = await import("./graph-loader-HHXN5FLP.js");
99
+ const { loadGraphStore } = await import("./graph-loader-STFGYKZH.js");
100
100
  const store = await loadGraphStore(projectPath);
101
101
  let graphCoverage;
102
102
  if (store) {
103
- const { Assembler } = await import("./dist-OWHMNL4W.js");
103
+ const { Assembler } = await import("./dist-DWBTZXR5.js");
104
104
  const assembler = new Assembler(store);
105
105
  const report = assembler.checkCoverage();
106
106
  graphCoverage = {
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-RC3OI5NK.js";
4
4
  import {
5
5
  resolveProjectConfig
6
- } from "./chunk-ZEYAOYJL.js";
6
+ } from "./chunk-SEA2PFVI.js";
7
7
  import {
8
8
  sanitizePath
9
9
  } from "./chunk-W6Y7ZW3Y.js";
@@ -38,7 +38,7 @@ async function handleCheckDependencies(input) {
38
38
  const configResult = resolveProjectConfig(projectPath);
39
39
  if (!configResult.ok) return resultToMcpResponse(configResult);
40
40
  try {
41
- const { validateDependencies, TypeScriptParser } = await import("./dist-IPVFYGY2.js");
41
+ const { validateDependencies, TypeScriptParser } = await import("./dist-6D2F2J57.js");
42
42
  const config = configResult.value;
43
43
  const rawLayers = Array.isArray(config.layers) ? config.layers : [];
44
44
  const layers = rawLayers.map((l) => ({
@@ -47,11 +47,11 @@ async function handleCheckDependencies(input) {
47
47
  allowedDependencies: l.allowedDependencies
48
48
  }));
49
49
  const parser = new TypeScriptParser();
50
- const { loadGraphStore } = await import("./graph-loader-HHXN5FLP.js");
50
+ const { loadGraphStore } = await import("./graph-loader-STFGYKZH.js");
51
51
  const store = await loadGraphStore(projectPath);
52
52
  let graphDependencyData;
53
53
  if (store) {
54
- const { GraphConstraintAdapter } = await import("./dist-OWHMNL4W.js");
54
+ const { GraphConstraintAdapter } = await import("./dist-DWBTZXR5.js");
55
55
  const adapter = new GraphConstraintAdapter(store);
56
56
  const graphData = adapter.computeDependencyGraph();
57
57
  graphDependencyData = {
@@ -69,7 +69,7 @@ async function handleCheckDependencies(input) {
69
69
  if (store) {
70
70
  const designTokenNodes = store.findNodes({ type: "design_token" });
71
71
  if (designTokenNodes.length > 0) {
72
- const { DesignConstraintAdapter } = await import("./dist-OWHMNL4W.js");
72
+ const { DesignConstraintAdapter } = await import("./dist-DWBTZXR5.js");
73
73
  const designAdapter = new DesignConstraintAdapter(store);
74
74
  const fileNodes = store.findNodes({ type: "file" });
75
75
  for (const fileNode of fileNodes) {
@@ -27,7 +27,7 @@ var runSecurityScanDefinition = {
27
27
  };
28
28
  async function handleRunSecurityScan(input) {
29
29
  try {
30
- const core = await import("./dist-IPVFYGY2.js");
30
+ const core = await import("./dist-6D2F2J57.js");
31
31
  const projectRoot = sanitizePath(input.path);
32
32
  let configData = {};
33
33
  try {
@@ -50,7 +50,7 @@ async function handleRunSecurityScan(input) {
50
50
  if (input.files && input.files.length > 0) {
51
51
  filesToScan = input.files.map((f) => path.resolve(projectRoot, f));
52
52
  } else {
53
- const { globFiles } = await import("./glob-helper-GLZAURJC.js");
53
+ const { globFiles } = await import("./glob-helper-VR6UCZZB.js");
54
54
  filesToScan = await globFiles(projectRoot, securityConfig.exclude);
55
55
  }
56
56
  const result = await scanner.scanFiles(filesToScan);
@@ -114,7 +114,7 @@ var getSecurityTrendsDefinition = {
114
114
  };
115
115
  async function handleGetSecurityTrends(input) {
116
116
  try {
117
- const core = await import("./dist-IPVFYGY2.js");
117
+ const core = await import("./dist-6D2F2J57.js");
118
118
  const projectRoot = sanitizePath(input.path);
119
119
  const manager = new core.SecurityTimelineManager(projectRoot);
120
120
  const trendOptions = {};
@@ -31,7 +31,7 @@ import {
31
31
  resetParserCache,
32
32
  resolveFileToLayer,
33
33
  validateDependencies
34
- } from "./chunk-N55WOGN3.js";
34
+ } from "./chunk-6YFHUIGT.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,
@@ -5965,16 +5965,7 @@ function quoteYamlScalar(value) {
5965
5965
  const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
5966
5966
  return `"${escaped}"`;
5967
5967
  }
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
- }
5968
+ function parseShardFrontmatter(data) {
5978
5969
  const slug = data.slug;
5979
5970
  if (typeof slug !== "string" || slug.length === 0) {
5980
5971
  return Err(new Error("Shard frontmatter missing required field: slug"));
@@ -5990,6 +5981,21 @@ function parseShard(md) {
5990
5981
  new Error(`Shard "${slug}" has invalid order: "${String(rawOrder)}" (must be a number)`)
5991
5982
  );
5992
5983
  }
5984
+ return Ok({ slug, milestone, order });
5985
+ }
5986
+ function parseShard(md) {
5987
+ let data;
5988
+ let content;
5989
+ try {
5990
+ const parsed = matter3(md);
5991
+ data = parsed.data;
5992
+ content = parsed.content;
5993
+ } catch (err) {
5994
+ return Err(new Error(`Shard frontmatter is not valid YAML: ${err.message}`));
5995
+ }
5996
+ const frontmatterResult = parseShardFrontmatter(data);
5997
+ if (!frontmatterResult.ok) return frontmatterResult;
5998
+ const { slug, milestone, order } = frontmatterResult.value;
5993
5999
  const nameMatch = content.match(H3_NAME);
5994
6000
  if (!nameMatch) {
5995
6001
  return Err(new Error(`Shard "${slug}" is missing its "### <name>" heading`));
@@ -6011,6 +6017,17 @@ function serializeShard(shard) {
6011
6017
  return [...frontmatter, ...serializeFeature(shard.feature)].join("\n") + "\n";
6012
6018
  }
6013
6019
  function parseMeta(md) {
6020
+ const block = parseFrontmatterBlock(md);
6021
+ if (!block.ok) return Err(block.error);
6022
+ const { data, body } = block.value;
6023
+ const frontmatter = buildFrontmatter(data);
6024
+ if (!frontmatter.ok) return Err(frontmatter.error);
6025
+ const milestones = parseMilestones2(data);
6026
+ if (!milestones.ok) return Err(milestones.error);
6027
+ const meta = { frontmatter: frontmatter.value, milestones: milestones.value };
6028
+ return attachAssignmentHistory(meta, body);
6029
+ }
6030
+ function parseFrontmatterBlock(md) {
6014
6031
  const match = md.match(FRONTMATTER);
6015
6032
  if (!match) {
6016
6033
  return Err(new Error("_meta.md is missing or has malformed YAML frontmatter"));
@@ -6022,6 +6039,9 @@ function parseMeta(md) {
6022
6039
  } catch (err) {
6023
6040
  return Err(new Error(`_meta.md frontmatter is not valid YAML: ${err.message}`));
6024
6041
  }
6042
+ return Ok({ data, body: match[2] ?? "" });
6043
+ }
6044
+ function buildFrontmatter(data) {
6025
6045
  const project = data.project;
6026
6046
  const rawVersion = data.version;
6027
6047
  const lastSynced = data.last_synced;
@@ -6040,12 +6060,16 @@ function parseMeta(md) {
6040
6060
  const frontmatter = { project, version, lastSynced, lastManualEdit };
6041
6061
  if (typeof data.created === "string") frontmatter.created = data.created;
6042
6062
  if (typeof data.updated === "string") frontmatter.updated = data.updated;
6063
+ return Ok(frontmatter);
6064
+ }
6065
+ function parseMilestones2(data) {
6043
6066
  const rawMilestones = data.milestones;
6044
6067
  if (!Array.isArray(rawMilestones) || !rawMilestones.every((m) => typeof m === "string")) {
6045
6068
  return Err(new Error("_meta.md frontmatter `milestones` must be a list of strings"));
6046
6069
  }
6047
- const meta = { frontmatter, milestones: rawMilestones };
6048
- const body = match[2] ?? "";
6070
+ return Ok(rawMilestones);
6071
+ }
6072
+ function attachAssignmentHistory(meta, body) {
6049
6073
  if (body.includes("## Assignment History")) {
6050
6074
  const history = parseAssignmentHistory(body);
6051
6075
  if (!history.ok) return Err(history.error);
@@ -6078,43 +6102,49 @@ function isRegression(from, to) {
6078
6102
  return STATUS_RANK[to] < STATUS_RANK[from];
6079
6103
  }
6080
6104
  function assembleRoadmap(shards, meta) {
6105
+ const byMilestone = groupShardsByMilestone(shards);
6106
+ const ordered = orderMilestoneNames(meta, byMilestone);
6107
+ const milestones = ordered.map((name) => buildMilestone(name, byMilestone));
6108
+ return {
6109
+ frontmatter: meta.frontmatter,
6110
+ milestones,
6111
+ assignmentHistory: meta.assignmentHistory ?? []
6112
+ };
6113
+ }
6114
+ function groupShardsByMilestone(shards) {
6081
6115
  const byMilestone = /* @__PURE__ */ new Map();
6082
6116
  for (const shard of shards) {
6083
6117
  const bucket = byMilestone.get(shard.milestone);
6084
6118
  if (bucket) bucket.push(shard);
6085
6119
  else byMilestone.set(shard.milestone, [shard]);
6086
6120
  }
6121
+ return byMilestone;
6122
+ }
6123
+ function orderMilestoneNames(meta, byMilestone) {
6087
6124
  const ordered = [];
6088
6125
  const seen = /* @__PURE__ */ new Set();
6089
- for (const name of meta.milestones) {
6126
+ const append = (name) => {
6090
6127
  if (!seen.has(name)) {
6091
6128
  ordered.push(name);
6092
6129
  seen.add(name);
6093
6130
  }
6094
- }
6095
- for (const name of byMilestone.keys()) {
6096
- if (!seen.has(name)) {
6097
- ordered.push(name);
6098
- seen.add(name);
6099
- }
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
- });
6131
+ };
6132
+ for (const name of meta.milestones) append(name);
6133
+ for (const name of byMilestone.keys()) append(name);
6134
+ return ordered;
6135
+ }
6136
+ function compareShards(a, b) {
6137
+ return a.order - b.order || STATUS_RANK[b.feature.status] - STATUS_RANK[a.feature.status] || // Deterministic code-unit comparison (NOT localeCompare, whose ICU/locale
6138
+ // collation is environment-dependent and threatens byte-stable regen).
6139
+ (a.slug < b.slug ? -1 : a.slug > b.slug ? 1 : 0);
6140
+ }
6141
+ function buildMilestone(name, byMilestone) {
6142
+ const bucket = [...byMilestone.get(name) ?? []];
6143
+ bucket.sort(compareShards);
6114
6144
  return {
6115
- frontmatter: meta.frontmatter,
6116
- milestones,
6117
- assignmentHistory: meta.assignmentHistory ?? []
6145
+ name,
6146
+ isBacklog: name === "Backlog",
6147
+ features: bucket.map((s) => s.feature)
6118
6148
  };
6119
6149
  }
6120
6150
  function joinPath(dir, name) {
@@ -13270,6 +13300,21 @@ function linearFromPriority(p) {
13270
13300
  return 0;
13271
13301
  }
13272
13302
  }
13303
+ function metaDerivedFields(meta, issue) {
13304
+ return {
13305
+ spec: meta.spec ?? null,
13306
+ plans: meta.plan ? [meta.plan] : [],
13307
+ blockedBy: meta.blocked_by ?? [],
13308
+ assignee: assigneeDisplayName(issue.assignee),
13309
+ priority: meta.priority ?? priorityFromLinear(issue.priority),
13310
+ milestone: meta.milestone ?? null,
13311
+ createdAt: issue.createdAt ?? (/* @__PURE__ */ new Date(0)).toISOString(),
13312
+ updatedAt: issue.updatedAt ?? null
13313
+ };
13314
+ }
13315
+ function assigneeDisplayName(assignee) {
13316
+ return assignee?.displayName ?? assignee?.name ?? null;
13317
+ }
13273
13318
  function metaFrom(feature) {
13274
13319
  const meta = {};
13275
13320
  if (feature.spec != null) meta.spec = feature.spec;
@@ -13279,6 +13324,25 @@ function metaFrom(feature) {
13279
13324
  if (feature.milestone != null) meta.milestone = feature.milestone;
13280
13325
  return meta;
13281
13326
  }
13327
+ function collectHistoryEvents(nodes) {
13328
+ const events = [];
13329
+ for (const c of nodes ?? []) {
13330
+ const parsed = parseHistoryComment(c.body);
13331
+ if (parsed) events.push(parsed);
13332
+ }
13333
+ return events;
13334
+ }
13335
+ function parseHistoryComment(body) {
13336
+ if (!body || !body.includes(HISTORY_MARKER)) return null;
13337
+ const m = /```json\s*([\s\S]*?)\s*```/.exec(body);
13338
+ if (!m?.[1]) return null;
13339
+ try {
13340
+ const parsed = JSON.parse(m[1]);
13341
+ if (parsed && typeof parsed.type === "string") return parsed;
13342
+ } catch {
13343
+ }
13344
+ return null;
13345
+ }
13282
13346
  function createTrackerClient(config) {
13283
13347
  if (config.kind === "github-issues") {
13284
13348
  const token = config.token ?? process.env.GITHUB_TOKEN;
@@ -13725,6 +13789,50 @@ function checkRoadmapHealth(roadmap, options) {
13725
13789
  }
13726
13790
  return findings;
13727
13791
  }
13792
+ function demoteUnactionablePlanned(feature, milestoneName, changes) {
13793
+ if (!isUnactionablePlanned(feature)) return;
13794
+ feature.status = "backlog";
13795
+ changes.push({
13796
+ kind: "demoted",
13797
+ feature: feature.name,
13798
+ from: milestoneName,
13799
+ to: "backlog"
13800
+ });
13801
+ }
13802
+ function restoreAssigneeInvariant(feature, milestoneName, changes) {
13803
+ if (feature.status !== "in-progress" && feature.assignee !== null) {
13804
+ changes.push({
13805
+ kind: "unassigned",
13806
+ feature: feature.name,
13807
+ from: milestoneName,
13808
+ to: feature.assignee
13809
+ });
13810
+ feature.assignee = null;
13811
+ } else if (feature.status === "in-progress" && feature.assignee === null) {
13812
+ feature.status = "planned";
13813
+ changes.push({
13814
+ kind: "demoted",
13815
+ feature: feature.name,
13816
+ from: milestoneName,
13817
+ to: "planned"
13818
+ });
13819
+ }
13820
+ }
13821
+ function groomFeature(feature, milestoneName, context, archived, changes) {
13822
+ demoteUnactionablePlanned(feature, milestoneName, changes);
13823
+ restoreAssigneeInvariant(feature, milestoneName, changes);
13824
+ if (context.archiveDone && feature.status === "done" && !context.archive && !context.isIntake) {
13825
+ archived.push(feature);
13826
+ changes.push({
13827
+ kind: "archived",
13828
+ feature: feature.name,
13829
+ from: milestoneName,
13830
+ to: "Shipped"
13831
+ });
13832
+ return false;
13833
+ }
13834
+ return true;
13835
+ }
13728
13836
  function groomRoadmap(roadmap, options) {
13729
13837
  const opts = resolveOptions(options);
13730
13838
  const archiveDone = options?.archiveDone ?? true;
@@ -13732,49 +13840,14 @@ function groomRoadmap(roadmap, options) {
13732
13840
  const archived = [];
13733
13841
  const changes = [];
13734
13842
  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;
13843
+ const context = {
13844
+ archiveDone,
13845
+ archive: opts.isArchive(milestone.name),
13846
+ isIntake: milestone.name === opts.intakeMilestone
13847
+ };
13848
+ milestone.features = milestone.features.filter(
13849
+ (feature) => groomFeature(feature, milestone.name, context, archived, changes)
13850
+ );
13778
13851
  }
13779
13852
  next.milestones = next.milestones.filter(
13780
13853
  (m) => m.features.length > 0 || m.name === opts.intakeMilestone
@@ -15060,7 +15133,7 @@ function arrayLen(arr) {
15060
15133
  return Array.isArray(arr) ? arr.length : 0;
15061
15134
  }
15062
15135
  async function gatherEntropyBlock(projectPath) {
15063
- const { EntropyAnalyzer: EntropyAnalyzer2 } = await import("./analyzer-V633GUHY-MBCQWKDU.js");
15136
+ const { EntropyAnalyzer: EntropyAnalyzer2 } = await import("./analyzer-V633GUHY-OV335HGM.js");
15064
15137
  const analyzer = new EntropyAnalyzer2({
15065
15138
  rootDir: projectPath,
15066
15139
  include: ["src/**/*.ts", "src/**/*.tsx", "packages/*/src/**/*.ts"],
@@ -21823,14 +21896,7 @@ ${JSON.stringify(event)}`;
21823
21896
  name: issue.title,
21824
21897
  status: statusFromStateType(issue.state?.type),
21825
21898
  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
21899
+ ...metaDerivedFields(meta, issue)
21834
21900
  };
21835
21901
  }
21836
21902
  /** Resolve (and cache) the team's first state id of each workflow type. */
@@ -21858,6 +21924,12 @@ ${JSON.stringify(event)}`;
21858
21924
  if (!r.ok) return r;
21859
21925
  return Ok(r.value.users?.nodes?.[0]?.id ?? null);
21860
21926
  }
21927
+ /** Resolve an assignee to a Linear user id, or null when absent/unresolved. */
21928
+ async assigneeIdFor(assignee) {
21929
+ if (!assignee) return null;
21930
+ const userR = await this.resolveUserId(assignee);
21931
+ return userR.ok && userR.value ? userR.value : null;
21932
+ }
21861
21933
  // ── Reads ──────────────────────────────────────────────────────────────
21862
21934
  async fetchAll() {
21863
21935
  const r = await this.gql(
@@ -21888,18 +21960,9 @@ ${JSON.stringify(event)}`;
21888
21960
  async create(feature) {
21889
21961
  const stateR = await this.resolveStateId(feature.status ?? "backlog");
21890
21962
  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
- }
21963
+ const input = this.buildCreateInput(feature, stateR.value);
21964
+ const assigneeId = await this.assigneeIdFor(feature.assignee);
21965
+ if (assigneeId) input.assigneeId = assigneeId;
21903
21966
  const r = await this.gql(
21904
21967
  `mutation($input:IssueCreateInput!){ issueCreate(input:$input){ issue { ${ISSUE_FIELDS} } } }`,
21905
21968
  { input }
@@ -21909,6 +21972,16 @@ ${JSON.stringify(event)}`;
21909
21972
  if (!issue) return Err(new Error("Linear issueCreate returned no issue"));
21910
21973
  return Ok(this.featureFromIssue(issue));
21911
21974
  }
21975
+ /** Assemble the IssueCreateInput payload for a new feature (sans assignee, which is resolved async). */
21976
+ buildCreateInput(feature, stateId) {
21977
+ return {
21978
+ teamId: this.teamId,
21979
+ title: feature.name,
21980
+ description: serializeBodyBlock(feature.summary ?? "", metaFrom(feature)),
21981
+ stateId,
21982
+ priority: linearFromPriority(feature.priority)
21983
+ };
21984
+ }
21912
21985
  /** Shared issueUpdate with optional optimistic-concurrency check on updatedAt. */
21913
21986
  async issueUpdate(externalId, input, ifMatch) {
21914
21987
  const id = this.toIssueId(externalId);
@@ -21935,6 +22008,26 @@ ${JSON.stringify(event)}`;
21935
22008
  if (!issue) return Err(new Error("Linear issueUpdate returned no issue"));
21936
22009
  return Ok(this.featureFromIssue(issue));
21937
22010
  }
22011
+ /**
22012
+ * Merge body-backed patch fields into the current issue's harness-meta block,
22013
+ * returning the serialized description (so the meta block isn't clobbered).
22014
+ */
22015
+ async mergedDescription(externalId, patch) {
22016
+ const cur = await this.fetchById(externalId);
22017
+ if (!cur.ok) return cur;
22018
+ if (!cur.value) return Err(new Error(`Linear issue not found: ${externalId}`));
22019
+ const f = cur.value.feature;
22020
+ const merged = {
22021
+ name: patch.name ?? f.name,
22022
+ summary: patch.summary ?? f.summary,
22023
+ spec: patch.spec !== void 0 ? patch.spec : f.spec,
22024
+ plans: patch.plans !== void 0 ? patch.plans : f.plans,
22025
+ blockedBy: patch.blockedBy !== void 0 ? patch.blockedBy : f.blockedBy,
22026
+ priority: patch.priority !== void 0 ? patch.priority : f.priority,
22027
+ milestone: patch.milestone !== void 0 ? patch.milestone : f.milestone
22028
+ };
22029
+ return Ok(serializeBodyBlock(merged.summary ?? "", metaFrom(merged)));
22030
+ }
21938
22031
  async update(externalId, patch, ifMatch) {
21939
22032
  const input = {};
21940
22033
  if (patch.name !== void 0) input.title = patch.name;
@@ -21953,31 +22046,23 @@ ${JSON.stringify(event)}`;
21953
22046
  "milestone"
21954
22047
  ];
21955
22048
  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
- }
22049
+ const descR = await this.mergedDescription(externalId, patch);
22050
+ if (!descR.ok) return descR;
22051
+ input.description = descR.value;
21978
22052
  }
22053
+ await this.applyAssigneeUpdate(patch, input);
21979
22054
  return this.issueUpdate(externalId, input, ifMatch);
21980
22055
  }
22056
+ /** Resolve `patch.assignee` (name → user id, or explicit null to unassign) into the update input. */
22057
+ async applyAssigneeUpdate(patch, input) {
22058
+ if (patch.assignee === void 0) return;
22059
+ if (patch.assignee === null) {
22060
+ input.assigneeId = null;
22061
+ return;
22062
+ }
22063
+ const userR = await this.resolveUserId(patch.assignee);
22064
+ if (userR.ok && userR.value) input.assigneeId = userR.value;
22065
+ }
21981
22066
  async claim(externalId, assignee, ifMatch) {
21982
22067
  const userR = await this.resolveUserId(assignee);
21983
22068
  if (!userR.ok) return userR;
@@ -22017,17 +22102,7 @@ ${JSON.stringify(event)}
22017
22102
  { id: this.toIssueId(externalId) }
22018
22103
  );
22019
22104
  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
- }
22105
+ const events = collectHistoryEvents(r.value.issue?.comments?.nodes);
22031
22106
  return Ok(typeof limit === "number" ? events.slice(0, limit) : events);
22032
22107
  }
22033
22108
  };