@harness-engineering/core 0.20.0 → 0.21.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.
package/dist/index.d.mts CHANGED
@@ -6478,7 +6478,7 @@ declare class GitHubIssuesSyncAdapter implements TrackerSyncAdapter {
6478
6478
  * Mutates `roadmap` in-place (stores new externalIds).
6479
6479
  * Never throws -- errors collected per-feature.
6480
6480
  */
6481
- declare function syncToExternal(roadmap: Roadmap, adapter: TrackerSyncAdapter, _config: TrackerSyncConfig): Promise<SyncResult>;
6481
+ declare function syncToExternal(roadmap: Roadmap, adapter: TrackerSyncAdapter, config: TrackerSyncConfig, prefetchedTickets?: ExternalTicketState[]): Promise<SyncResult>;
6482
6482
  /**
6483
6483
  * Pull execution fields (assignee, status) from external service.
6484
6484
  * - External assignee wins over local assignee
@@ -6487,7 +6487,7 @@ declare function syncToExternal(roadmap: Roadmap, adapter: TrackerSyncAdapter, _
6487
6487
  * Mutates `roadmap` in-place.
6488
6488
  * Never throws -- errors collected per-feature.
6489
6489
  */
6490
- declare function syncFromExternal(roadmap: Roadmap, adapter: TrackerSyncAdapter, config: TrackerSyncConfig, options?: ExternalSyncOptions): Promise<SyncResult>;
6490
+ declare function syncFromExternal(roadmap: Roadmap, adapter: TrackerSyncAdapter, config: TrackerSyncConfig, options?: ExternalSyncOptions, prefetchedTickets?: ExternalTicketState[]): Promise<SyncResult>;
6491
6491
  /**
6492
6492
  * Full bidirectional sync: read roadmap, push, pull, write back.
6493
6493
  * Serialized by in-process mutex.
package/dist/index.d.ts CHANGED
@@ -6478,7 +6478,7 @@ declare class GitHubIssuesSyncAdapter implements TrackerSyncAdapter {
6478
6478
  * Mutates `roadmap` in-place (stores new externalIds).
6479
6479
  * Never throws -- errors collected per-feature.
6480
6480
  */
6481
- declare function syncToExternal(roadmap: Roadmap, adapter: TrackerSyncAdapter, _config: TrackerSyncConfig): Promise<SyncResult>;
6481
+ declare function syncToExternal(roadmap: Roadmap, adapter: TrackerSyncAdapter, config: TrackerSyncConfig, prefetchedTickets?: ExternalTicketState[]): Promise<SyncResult>;
6482
6482
  /**
6483
6483
  * Pull execution fields (assignee, status) from external service.
6484
6484
  * - External assignee wins over local assignee
@@ -6487,7 +6487,7 @@ declare function syncToExternal(roadmap: Roadmap, adapter: TrackerSyncAdapter, _
6487
6487
  * Mutates `roadmap` in-place.
6488
6488
  * Never throws -- errors collected per-feature.
6489
6489
  */
6490
- declare function syncFromExternal(roadmap: Roadmap, adapter: TrackerSyncAdapter, config: TrackerSyncConfig, options?: ExternalSyncOptions): Promise<SyncResult>;
6490
+ declare function syncFromExternal(roadmap: Roadmap, adapter: TrackerSyncAdapter, config: TrackerSyncConfig, options?: ExternalSyncOptions, prefetchedTickets?: ExternalTicketState[]): Promise<SyncResult>;
6491
6491
  /**
6492
6492
  * Full bidirectional sync: read roadmap, push, pull, write back.
6493
6493
  * Serialized by in-process mutex.
package/dist/index.js CHANGED
@@ -13767,6 +13767,7 @@ var GitHubIssuesSyncAdapter = class {
13767
13767
  const data = await response.json();
13768
13768
  return (0, import_types25.Ok)({
13769
13769
  externalId,
13770
+ title: data.title,
13770
13771
  status: data.state,
13771
13772
  labels: data.labels.map((l) => l.name),
13772
13773
  assignee: data.assignee ? `@${data.assignee.login}` : null
@@ -13801,6 +13802,7 @@ var GitHubIssuesSyncAdapter = class {
13801
13802
  for (const issue of issues) {
13802
13803
  tickets.push({
13803
13804
  externalId: buildExternalId(this.owner, this.repo, issue.number),
13805
+ title: issue.title,
13804
13806
  status: issue.state,
13805
13807
  labels: issue.labels.map((l) => l.name),
13806
13808
  assignee: issue.assignee ? `@${issue.assignee.login}` : null
@@ -13845,43 +13847,49 @@ var fs22 = __toESM(require("fs"));
13845
13847
  function emptySyncResult() {
13846
13848
  return { created: [], updated: [], assignmentChanges: [], errors: [] };
13847
13849
  }
13848
- async function syncToExternal(roadmap, adapter, _config) {
13850
+ async function syncToExternal(roadmap, adapter, config, prefetchedTickets) {
13849
13851
  const result = emptySyncResult();
13850
- let defaultAssignee = null;
13851
- const userResult = await adapter.getAuthenticatedUser();
13852
- if (userResult.ok) {
13853
- defaultAssignee = userResult.value;
13852
+ const existingByTitle = /* @__PURE__ */ new Map();
13853
+ const configLabels = new Set((config.labels ?? []).map((l) => l.toLowerCase()));
13854
+ if (prefetchedTickets) {
13855
+ for (const ticket of prefetchedTickets) {
13856
+ const hasConfigLabels = configLabels.size === 0 || ticket.labels.some((l) => configLabels.has(l.toLowerCase()));
13857
+ if (!hasConfigLabels) continue;
13858
+ const key = ticket.title.toLowerCase();
13859
+ const prev = existingByTitle.get(key);
13860
+ if (!prev || prev.status === "closed" && ticket.status === "open") {
13861
+ existingByTitle.set(key, ticket);
13862
+ }
13863
+ }
13854
13864
  }
13855
13865
  for (const milestone of roadmap.milestones) {
13856
13866
  for (const feature of milestone.features) {
13857
- if (!feature.assignee && defaultAssignee) {
13858
- feature.assignee = defaultAssignee;
13859
- }
13860
13867
  if (!feature.externalId) {
13861
- const createResult = await adapter.createTicket(feature, milestone.name);
13862
- if (createResult.ok) {
13863
- feature.externalId = createResult.value.externalId;
13864
- result.created.push(createResult.value);
13868
+ const existing = existingByTitle.get(feature.name.toLowerCase());
13869
+ if (existing) {
13870
+ feature.externalId = existing.externalId;
13865
13871
  } else {
13866
- result.errors.push({ featureOrId: feature.name, error: createResult.error });
13872
+ const createResult = await adapter.createTicket(feature, milestone.name);
13873
+ if (createResult.ok) {
13874
+ feature.externalId = createResult.value.externalId;
13875
+ result.created.push(createResult.value);
13876
+ } else {
13877
+ result.errors.push({ featureOrId: feature.name, error: createResult.error });
13878
+ }
13879
+ continue;
13867
13880
  }
13881
+ }
13882
+ const updateResult = await adapter.updateTicket(feature.externalId, feature, milestone.name);
13883
+ if (updateResult.ok) {
13884
+ result.updated.push(feature.externalId);
13868
13885
  } else {
13869
- const updateResult = await adapter.updateTicket(
13870
- feature.externalId,
13871
- feature,
13872
- milestone.name
13873
- );
13874
- if (updateResult.ok) {
13875
- result.updated.push(feature.externalId);
13876
- } else {
13877
- result.errors.push({ featureOrId: feature.externalId, error: updateResult.error });
13878
- }
13886
+ result.errors.push({ featureOrId: feature.externalId, error: updateResult.error });
13879
13887
  }
13880
13888
  }
13881
13889
  }
13882
13890
  return result;
13883
13891
  }
13884
- async function syncFromExternal(roadmap, adapter, config, options) {
13892
+ async function syncFromExternal(roadmap, adapter, config, options, prefetchedTickets) {
13885
13893
  const result = emptySyncResult();
13886
13894
  const forceSync = options?.forceSync ?? false;
13887
13895
  const featureByExternalId = /* @__PURE__ */ new Map();
@@ -13893,12 +13901,18 @@ async function syncFromExternal(roadmap, adapter, config, options) {
13893
13901
  }
13894
13902
  }
13895
13903
  if (featureByExternalId.size === 0) return result;
13896
- const fetchResult = await adapter.fetchAllTickets();
13897
- if (!fetchResult.ok) {
13898
- result.errors.push({ featureOrId: "*", error: fetchResult.error });
13899
- return result;
13904
+ let tickets;
13905
+ if (prefetchedTickets) {
13906
+ tickets = prefetchedTickets;
13907
+ } else {
13908
+ const fetchResult = await adapter.fetchAllTickets();
13909
+ if (!fetchResult.ok) {
13910
+ result.errors.push({ featureOrId: "*", error: fetchResult.error });
13911
+ return result;
13912
+ }
13913
+ tickets = fetchResult.value;
13900
13914
  }
13901
- for (const ticketState of fetchResult.value) {
13915
+ for (const ticketState of tickets) {
13902
13916
  const feature = featureByExternalId.get(ticketState.externalId);
13903
13917
  if (!feature) continue;
13904
13918
  if (ticketState.assignee !== feature.assignee) {
@@ -13938,8 +13952,10 @@ async function fullSync(roadmapPath, adapter, config, options) {
13938
13952
  };
13939
13953
  }
13940
13954
  const roadmap = parseResult.value;
13941
- const pushResult = await syncToExternal(roadmap, adapter, config);
13942
- const pullResult = await syncFromExternal(roadmap, adapter, config, options);
13955
+ const fetchResult = await adapter.fetchAllTickets();
13956
+ const tickets = fetchResult.ok ? fetchResult.value : void 0;
13957
+ const pushResult = await syncToExternal(roadmap, adapter, config, tickets);
13958
+ const pullResult = await syncFromExternal(roadmap, adapter, config, options, tickets);
13943
13959
  fs22.writeFileSync(roadmapPath, serializeRoadmap(roadmap), "utf-8");
13944
13960
  return {
13945
13961
  created: pushResult.created,
package/dist/index.mjs CHANGED
@@ -11632,6 +11632,7 @@ var GitHubIssuesSyncAdapter = class {
11632
11632
  const data = await response.json();
11633
11633
  return Ok4({
11634
11634
  externalId,
11635
+ title: data.title,
11635
11636
  status: data.state,
11636
11637
  labels: data.labels.map((l) => l.name),
11637
11638
  assignee: data.assignee ? `@${data.assignee.login}` : null
@@ -11666,6 +11667,7 @@ var GitHubIssuesSyncAdapter = class {
11666
11667
  for (const issue of issues) {
11667
11668
  tickets.push({
11668
11669
  externalId: buildExternalId(this.owner, this.repo, issue.number),
11670
+ title: issue.title,
11669
11671
  status: issue.state,
11670
11672
  labels: issue.labels.map((l) => l.name),
11671
11673
  assignee: issue.assignee ? `@${issue.assignee.login}` : null
@@ -11710,43 +11712,49 @@ import * as fs22 from "fs";
11710
11712
  function emptySyncResult() {
11711
11713
  return { created: [], updated: [], assignmentChanges: [], errors: [] };
11712
11714
  }
11713
- async function syncToExternal(roadmap, adapter, _config) {
11715
+ async function syncToExternal(roadmap, adapter, config, prefetchedTickets) {
11714
11716
  const result = emptySyncResult();
11715
- let defaultAssignee = null;
11716
- const userResult = await adapter.getAuthenticatedUser();
11717
- if (userResult.ok) {
11718
- defaultAssignee = userResult.value;
11717
+ const existingByTitle = /* @__PURE__ */ new Map();
11718
+ const configLabels = new Set((config.labels ?? []).map((l) => l.toLowerCase()));
11719
+ if (prefetchedTickets) {
11720
+ for (const ticket of prefetchedTickets) {
11721
+ const hasConfigLabels = configLabels.size === 0 || ticket.labels.some((l) => configLabels.has(l.toLowerCase()));
11722
+ if (!hasConfigLabels) continue;
11723
+ const key = ticket.title.toLowerCase();
11724
+ const prev = existingByTitle.get(key);
11725
+ if (!prev || prev.status === "closed" && ticket.status === "open") {
11726
+ existingByTitle.set(key, ticket);
11727
+ }
11728
+ }
11719
11729
  }
11720
11730
  for (const milestone of roadmap.milestones) {
11721
11731
  for (const feature of milestone.features) {
11722
- if (!feature.assignee && defaultAssignee) {
11723
- feature.assignee = defaultAssignee;
11724
- }
11725
11732
  if (!feature.externalId) {
11726
- const createResult = await adapter.createTicket(feature, milestone.name);
11727
- if (createResult.ok) {
11728
- feature.externalId = createResult.value.externalId;
11729
- result.created.push(createResult.value);
11733
+ const existing = existingByTitle.get(feature.name.toLowerCase());
11734
+ if (existing) {
11735
+ feature.externalId = existing.externalId;
11730
11736
  } else {
11731
- result.errors.push({ featureOrId: feature.name, error: createResult.error });
11737
+ const createResult = await adapter.createTicket(feature, milestone.name);
11738
+ if (createResult.ok) {
11739
+ feature.externalId = createResult.value.externalId;
11740
+ result.created.push(createResult.value);
11741
+ } else {
11742
+ result.errors.push({ featureOrId: feature.name, error: createResult.error });
11743
+ }
11744
+ continue;
11732
11745
  }
11746
+ }
11747
+ const updateResult = await adapter.updateTicket(feature.externalId, feature, milestone.name);
11748
+ if (updateResult.ok) {
11749
+ result.updated.push(feature.externalId);
11733
11750
  } else {
11734
- const updateResult = await adapter.updateTicket(
11735
- feature.externalId,
11736
- feature,
11737
- milestone.name
11738
- );
11739
- if (updateResult.ok) {
11740
- result.updated.push(feature.externalId);
11741
- } else {
11742
- result.errors.push({ featureOrId: feature.externalId, error: updateResult.error });
11743
- }
11751
+ result.errors.push({ featureOrId: feature.externalId, error: updateResult.error });
11744
11752
  }
11745
11753
  }
11746
11754
  }
11747
11755
  return result;
11748
11756
  }
11749
- async function syncFromExternal(roadmap, adapter, config, options) {
11757
+ async function syncFromExternal(roadmap, adapter, config, options, prefetchedTickets) {
11750
11758
  const result = emptySyncResult();
11751
11759
  const forceSync = options?.forceSync ?? false;
11752
11760
  const featureByExternalId = /* @__PURE__ */ new Map();
@@ -11758,12 +11766,18 @@ async function syncFromExternal(roadmap, adapter, config, options) {
11758
11766
  }
11759
11767
  }
11760
11768
  if (featureByExternalId.size === 0) return result;
11761
- const fetchResult = await adapter.fetchAllTickets();
11762
- if (!fetchResult.ok) {
11763
- result.errors.push({ featureOrId: "*", error: fetchResult.error });
11764
- return result;
11769
+ let tickets;
11770
+ if (prefetchedTickets) {
11771
+ tickets = prefetchedTickets;
11772
+ } else {
11773
+ const fetchResult = await adapter.fetchAllTickets();
11774
+ if (!fetchResult.ok) {
11775
+ result.errors.push({ featureOrId: "*", error: fetchResult.error });
11776
+ return result;
11777
+ }
11778
+ tickets = fetchResult.value;
11765
11779
  }
11766
- for (const ticketState of fetchResult.value) {
11780
+ for (const ticketState of tickets) {
11767
11781
  const feature = featureByExternalId.get(ticketState.externalId);
11768
11782
  if (!feature) continue;
11769
11783
  if (ticketState.assignee !== feature.assignee) {
@@ -11803,8 +11817,10 @@ async function fullSync(roadmapPath, adapter, config, options) {
11803
11817
  };
11804
11818
  }
11805
11819
  const roadmap = parseResult.value;
11806
- const pushResult = await syncToExternal(roadmap, adapter, config);
11807
- const pullResult = await syncFromExternal(roadmap, adapter, config, options);
11820
+ const fetchResult = await adapter.fetchAllTickets();
11821
+ const tickets = fetchResult.ok ? fetchResult.value : void 0;
11822
+ const pushResult = await syncToExternal(roadmap, adapter, config, tickets);
11823
+ const pullResult = await syncFromExternal(roadmap, adapter, config, options, tickets);
11808
11824
  fs22.writeFileSync(roadmapPath, serializeRoadmap(roadmap), "utf-8");
11809
11825
  return {
11810
11826
  created: pushResult.created,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@harness-engineering/core",
3
- "version": "0.20.0",
3
+ "version": "0.21.0",
4
4
  "description": "Core library for Harness Engineering toolkit",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -50,7 +50,7 @@
50
50
  "web-tree-sitter": "^0.24.7",
51
51
  "zod": "^3.25.76",
52
52
  "@harness-engineering/graph": "0.4.0",
53
- "@harness-engineering/types": "0.8.1"
53
+ "@harness-engineering/types": "0.9.0"
54
54
  },
55
55
  "devDependencies": {
56
56
  "@types/ejs": "^3.1.5",