@harness-engineering/orchestrator 0.8.4 → 0.9.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 +650 -5
- package/dist/index.d.ts +650 -5
- package/dist/index.js +787 -216
- package/dist/index.mjs +823 -267
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -34,13 +34,18 @@ __export(index_exports, {
|
|
|
34
34
|
BUILT_IN_TASKS: () => BUILT_IN_TASKS,
|
|
35
35
|
BackendDefSchema: () => BackendDefSchema,
|
|
36
36
|
BackendRouter: () => BackendRouter,
|
|
37
|
+
CheckScriptRunner: () => CheckScriptRunner,
|
|
37
38
|
ClaimManager: () => ClaimManager,
|
|
38
39
|
GateNotReadyError: () => GateNotReadyError,
|
|
39
40
|
GateRunError: () => GateRunError,
|
|
40
41
|
InteractionQueue: () => InteractionQueue,
|
|
42
|
+
LinearGraphQLClient: () => LinearGraphQLClient,
|
|
41
43
|
LinearGraphQLStub: () => LinearGraphQLStub,
|
|
42
44
|
LocalModelResolver: () => LocalModelResolver,
|
|
45
|
+
MAINTENANCE_CHECK_MAX_BUFFER: () => MAINTENANCE_CHECK_MAX_BUFFER,
|
|
46
|
+
MAINTENANCE_CHECK_TIMEOUT_MS: () => MAINTENANCE_CHECK_TIMEOUT_MS,
|
|
43
47
|
MAX_ATTEMPTS: () => MAX_ATTEMPTS,
|
|
48
|
+
MaintenanceReporter: () => MaintenanceReporter,
|
|
44
49
|
MockBackend: () => MockBackend,
|
|
45
50
|
ORCHESTRATOR_IDENTITY_FILE: () => ORCHESTRATOR_IDENTITY_FILE,
|
|
46
51
|
Orchestrator: () => Orchestrator,
|
|
@@ -58,6 +63,7 @@ __export(index_exports, {
|
|
|
58
63
|
SqliteSearchIndex: () => SqliteSearchIndex,
|
|
59
64
|
StreamRecorder: () => StreamRecorder,
|
|
60
65
|
TaskOutputStore: () => TaskOutputStore,
|
|
66
|
+
TaskRunner: () => TaskRunner,
|
|
61
67
|
TokenStore: () => TokenStore,
|
|
62
68
|
WebhookQueue: () => WebhookQueue,
|
|
63
69
|
WorkflowLoader: () => WorkflowLoader,
|
|
@@ -68,7 +74,9 @@ __export(index_exports, {
|
|
|
68
74
|
buildArchiveHooks: () => buildArchiveHooks,
|
|
69
75
|
calculateRetryDelay: () => calculateRetryDelay,
|
|
70
76
|
canDispatch: () => canDispatch,
|
|
77
|
+
classifyCheckExecutionFailure: () => classifyCheckExecutionFailure,
|
|
71
78
|
computeRateLimitDelay: () => computeRateLimitDelay,
|
|
79
|
+
createAgentDispatcher: () => createAgentDispatcher,
|
|
72
80
|
createBackend: () => createBackend,
|
|
73
81
|
createEmptyState: () => createEmptyState,
|
|
74
82
|
crossFieldRoutingIssues: () => crossFieldRoutingIssues,
|
|
@@ -80,22 +88,27 @@ __export(index_exports, {
|
|
|
80
88
|
emitProposalApproved: () => emitProposalApproved,
|
|
81
89
|
emitProposalCreated: () => emitProposalCreated,
|
|
82
90
|
emitProposalRejected: () => emitProposalRejected,
|
|
91
|
+
explicitFindingsCount: () => explicitFindingsCount,
|
|
83
92
|
extractHighlights: () => extractHighlights,
|
|
84
93
|
extractTitlePrefix: () => extractTitlePrefix,
|
|
85
94
|
getAvailableSlots: () => getAvailableSlots,
|
|
86
95
|
getDefaultConfig: () => getDefaultConfig,
|
|
87
96
|
getPerStateCount: () => getPerStateCount,
|
|
88
97
|
indexSessionDirectory: () => indexSessionDirectory,
|
|
98
|
+
isCheckTimeoutError: () => isCheckTimeoutError,
|
|
89
99
|
isEligible: () => isEligible,
|
|
90
100
|
isSummaryEnabled: () => isSummaryEnabled,
|
|
91
101
|
launchTUI: () => launchTUI,
|
|
92
102
|
loadPublishedIndex: () => loadPublishedIndex,
|
|
103
|
+
makeBackendResolver: () => makeBackendResolver,
|
|
93
104
|
migrateAgentConfig: () => migrateAgentConfig,
|
|
94
105
|
normalizeFts5Query: () => normalizeFts5Query,
|
|
106
|
+
normalizeHarnessCommand: () => normalizeHarnessCommand,
|
|
95
107
|
normalizeLocalModel: () => normalizeLocalModel,
|
|
96
108
|
openSearchIndex: () => openSearchIndex,
|
|
97
109
|
promote: () => promote,
|
|
98
110
|
reconcile: () => reconcile,
|
|
111
|
+
recoverFindingsCount: () => recoverFindingsCount,
|
|
99
112
|
reindexFromArchive: () => reindexFromArchive,
|
|
100
113
|
renderAnalysisComment: () => renderAnalysisComment,
|
|
101
114
|
renderLlmSummaryMarkdown: () => renderLlmSummaryMarkdown,
|
|
@@ -105,9 +118,11 @@ __export(index_exports, {
|
|
|
105
118
|
routeIssue: () => routeIssue,
|
|
106
119
|
routingWarnings: () => routingWarnings,
|
|
107
120
|
runGate: () => runGate,
|
|
121
|
+
runHarnessCheck: () => runHarnessCheck,
|
|
108
122
|
savePublishedIndex: () => savePublishedIndex,
|
|
109
123
|
searchIndexPath: () => searchIndexPath,
|
|
110
124
|
selectCandidates: () => selectCandidates,
|
|
125
|
+
selectTasks: () => selectTasks,
|
|
111
126
|
sortCandidates: () => sortCandidates,
|
|
112
127
|
summarizeArchivedSession: () => summarizeArchivedSession,
|
|
113
128
|
syncMain: () => syncMain,
|
|
@@ -2327,7 +2342,6 @@ var WorkflowLoader = class {
|
|
|
2327
2342
|
};
|
|
2328
2343
|
|
|
2329
2344
|
// src/tracker/adapters/roadmap.ts
|
|
2330
|
-
var fs8 = __toESM(require("fs/promises"));
|
|
2331
2345
|
var import_node_crypto2 = require("crypto");
|
|
2332
2346
|
var import_core = require("@harness-engineering/core");
|
|
2333
2347
|
var import_types4 = require("@harness-engineering/types");
|
|
@@ -2358,8 +2372,7 @@ var RoadmapTrackerAdapter = class {
|
|
|
2358
2372
|
async fetchIssuesByStates(stateNames) {
|
|
2359
2373
|
try {
|
|
2360
2374
|
if (!this.config.filePath) return (0, import_types4.Err)(new Error("Missing filePath"));
|
|
2361
|
-
const
|
|
2362
|
-
const roadmapResult = (0, import_core.parseRoadmap)(content);
|
|
2375
|
+
const roadmapResult = await this.loadRoadmap();
|
|
2363
2376
|
if (!roadmapResult.ok) return roadmapResult;
|
|
2364
2377
|
const issues = [];
|
|
2365
2378
|
for (const milestone of roadmapResult.value.milestones) {
|
|
@@ -2390,17 +2403,19 @@ var RoadmapTrackerAdapter = class {
|
|
|
2390
2403
|
if (!terminal) {
|
|
2391
2404
|
return (0, import_types4.Err)(new Error("Tracker config has no terminalStates; cannot mark complete"));
|
|
2392
2405
|
}
|
|
2393
|
-
const
|
|
2394
|
-
const roadmapResult =
|
|
2406
|
+
const store = this.resolveStore();
|
|
2407
|
+
const roadmapResult = await store.load();
|
|
2395
2408
|
if (!roadmapResult.ok) return roadmapResult;
|
|
2396
2409
|
const roadmap = roadmapResult.value;
|
|
2410
|
+
const before = structuredClone(roadmap);
|
|
2397
2411
|
const target = this.findFeatureById(roadmap.milestones, issueId);
|
|
2398
2412
|
if (!target) return (0, import_types4.Ok)(void 0);
|
|
2399
2413
|
const normalizedTerminal = this.config.terminalStates.map((s) => s.toLowerCase());
|
|
2400
2414
|
if (normalizedTerminal.includes(target.status.toLowerCase())) return (0, import_types4.Ok)(void 0);
|
|
2401
2415
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2402
2416
|
(0, import_core.setStatus)(roadmap, target, terminal, now.slice(0, 10));
|
|
2403
|
-
await
|
|
2417
|
+
const persisted = await (0, import_core.applyRoadmapDiff)(store, before, roadmap);
|
|
2418
|
+
if (!persisted.ok) return persisted;
|
|
2404
2419
|
return (0, import_types4.Ok)(void 0);
|
|
2405
2420
|
} catch (error) {
|
|
2406
2421
|
return (0, import_types4.Err)(error instanceof Error ? error : new Error(String(error)));
|
|
@@ -2414,10 +2429,11 @@ var RoadmapTrackerAdapter = class {
|
|
|
2414
2429
|
async claimIssue(issueId, orchestratorId) {
|
|
2415
2430
|
try {
|
|
2416
2431
|
if (!this.config.filePath) return (0, import_types4.Err)(new Error("Missing filePath"));
|
|
2417
|
-
const
|
|
2418
|
-
const roadmapResult =
|
|
2432
|
+
const store = this.resolveStore();
|
|
2433
|
+
const roadmapResult = await store.load();
|
|
2419
2434
|
if (!roadmapResult.ok) return roadmapResult;
|
|
2420
2435
|
const roadmap = roadmapResult.value;
|
|
2436
|
+
const before = structuredClone(roadmap);
|
|
2421
2437
|
const target = this.findFeatureById(roadmap.milestones, issueId);
|
|
2422
2438
|
if (!target) return (0, import_types4.Ok)(void 0);
|
|
2423
2439
|
if (!(0, import_core.isClaimableBy)(target, orchestratorId)) {
|
|
@@ -2429,7 +2445,8 @@ var RoadmapTrackerAdapter = class {
|
|
|
2429
2445
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2430
2446
|
(0, import_core.claim)(roadmap, target, orchestratorId, now.slice(0, 10));
|
|
2431
2447
|
target.updatedAt = now;
|
|
2432
|
-
await
|
|
2448
|
+
const persisted = await (0, import_core.applyRoadmapDiff)(store, before, roadmap);
|
|
2449
|
+
if (!persisted.ok) return persisted;
|
|
2433
2450
|
return (0, import_types4.Ok)(void 0);
|
|
2434
2451
|
} catch (error) {
|
|
2435
2452
|
return (0, import_types4.Err)(error instanceof Error ? error : new Error(String(error)));
|
|
@@ -2446,10 +2463,11 @@ var RoadmapTrackerAdapter = class {
|
|
|
2446
2463
|
if (!activeState) {
|
|
2447
2464
|
return (0, import_types4.Err)(new Error("Tracker config has no activeStates; cannot release"));
|
|
2448
2465
|
}
|
|
2449
|
-
const
|
|
2450
|
-
const roadmapResult =
|
|
2466
|
+
const store = this.resolveStore();
|
|
2467
|
+
const roadmapResult = await store.load();
|
|
2451
2468
|
if (!roadmapResult.ok) return roadmapResult;
|
|
2452
2469
|
const roadmap = roadmapResult.value;
|
|
2470
|
+
const before = structuredClone(roadmap);
|
|
2453
2471
|
const target = this.findFeatureById(roadmap.milestones, issueId);
|
|
2454
2472
|
if (!target) return (0, import_types4.Ok)(void 0);
|
|
2455
2473
|
if (this.config.activeStates.includes(target.status) && target.assignee === null) {
|
|
@@ -2458,12 +2476,26 @@ var RoadmapTrackerAdapter = class {
|
|
|
2458
2476
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2459
2477
|
(0, import_core.setStatus)(roadmap, target, activeState, now.slice(0, 10));
|
|
2460
2478
|
target.updatedAt = null;
|
|
2461
|
-
await
|
|
2479
|
+
const persisted = await (0, import_core.applyRoadmapDiff)(store, before, roadmap);
|
|
2480
|
+
if (!persisted.ok) return persisted;
|
|
2462
2481
|
return (0, import_types4.Ok)(void 0);
|
|
2463
2482
|
} catch (error) {
|
|
2464
2483
|
return (0, import_types4.Err)(error instanceof Error ? error : new Error(String(error)));
|
|
2465
2484
|
}
|
|
2466
2485
|
}
|
|
2486
|
+
/**
|
|
2487
|
+
* Resolve the roadmap store anchored on the configured aggregate file path.
|
|
2488
|
+
* The shard backend is chosen when a sibling `roadmap.d/` exists next to the
|
|
2489
|
+
* file; otherwise the monolith file is read/written whole. Callers guard
|
|
2490
|
+
* `this.config.filePath` before invoking.
|
|
2491
|
+
*/
|
|
2492
|
+
resolveStore() {
|
|
2493
|
+
return (0, import_core.resolveRoadmapStoreForFile)({ roadmapPath: this.config.filePath });
|
|
2494
|
+
}
|
|
2495
|
+
/** Load the roadmap via the store (sharded or monolith), returning a Result. */
|
|
2496
|
+
loadRoadmap() {
|
|
2497
|
+
return this.resolveStore().load();
|
|
2498
|
+
}
|
|
2467
2499
|
findFeatureById(milestones, issueId) {
|
|
2468
2500
|
for (const milestone of milestones) {
|
|
2469
2501
|
for (const feature of milestone.features) {
|
|
@@ -2480,8 +2512,7 @@ var RoadmapTrackerAdapter = class {
|
|
|
2480
2512
|
async fetchIssueStatesByIds(issueIds) {
|
|
2481
2513
|
try {
|
|
2482
2514
|
if (!this.config.filePath) return (0, import_types4.Err)(new Error("Missing filePath"));
|
|
2483
|
-
const
|
|
2484
|
-
const roadmapResult = (0, import_core.parseRoadmap)(content);
|
|
2515
|
+
const roadmapResult = await this.loadRoadmap();
|
|
2485
2516
|
if (!roadmapResult.ok) return roadmapResult;
|
|
2486
2517
|
const issueMap = /* @__PURE__ */ new Map();
|
|
2487
2518
|
for (const milestone of roadmapResult.value.milestones) {
|
|
@@ -2537,6 +2568,56 @@ var RoadmapTrackerAdapter = class {
|
|
|
2537
2568
|
|
|
2538
2569
|
// src/tracker/extensions/linear.ts
|
|
2539
2570
|
var import_types5 = require("@harness-engineering/types");
|
|
2571
|
+
var LINEAR_GRAPHQL_ENDPOINT = "https://api.linear.app/graphql";
|
|
2572
|
+
var LinearGraphQLClient = class {
|
|
2573
|
+
apiKey;
|
|
2574
|
+
endpoint;
|
|
2575
|
+
fetchFn;
|
|
2576
|
+
constructor(opts) {
|
|
2577
|
+
this.apiKey = opts.apiKey;
|
|
2578
|
+
this.endpoint = opts.endpoint ?? LINEAR_GRAPHQL_ENDPOINT;
|
|
2579
|
+
this.fetchFn = opts.fetchFn ?? globalThis.fetch;
|
|
2580
|
+
}
|
|
2581
|
+
async query(query, variables) {
|
|
2582
|
+
let res;
|
|
2583
|
+
try {
|
|
2584
|
+
res = await this.fetchFn(this.endpoint, {
|
|
2585
|
+
method: "POST",
|
|
2586
|
+
headers: {
|
|
2587
|
+
Authorization: this.apiKey,
|
|
2588
|
+
"Content-Type": "application/json"
|
|
2589
|
+
},
|
|
2590
|
+
body: JSON.stringify({ query, variables: variables ?? {} })
|
|
2591
|
+
});
|
|
2592
|
+
} catch (err) {
|
|
2593
|
+
return (0, import_types5.Err)(
|
|
2594
|
+
new Error(
|
|
2595
|
+
`Linear GraphQL request failed: ${err instanceof Error ? err.message : String(err)}`
|
|
2596
|
+
)
|
|
2597
|
+
);
|
|
2598
|
+
}
|
|
2599
|
+
if (!res.ok) {
|
|
2600
|
+
const body = await res.text().catch(() => "");
|
|
2601
|
+
const detail = body ? `: ${body.slice(0, 500)}` : "";
|
|
2602
|
+
return (0, import_types5.Err)(new Error(`Linear GraphQL HTTP ${res.status}${detail}`));
|
|
2603
|
+
}
|
|
2604
|
+
let envelope;
|
|
2605
|
+
try {
|
|
2606
|
+
envelope = await res.json();
|
|
2607
|
+
} catch (err) {
|
|
2608
|
+
return (0, import_types5.Err)(
|
|
2609
|
+
new Error(
|
|
2610
|
+
`Linear GraphQL response was not valid JSON: ${err instanceof Error ? err.message : String(err)}`
|
|
2611
|
+
)
|
|
2612
|
+
);
|
|
2613
|
+
}
|
|
2614
|
+
if (envelope.errors && envelope.errors.length > 0) {
|
|
2615
|
+
const message = envelope.errors.map((e) => e.message ?? "unknown error").join("; ");
|
|
2616
|
+
return (0, import_types5.Err)(new Error(`Linear GraphQL error: ${message}`));
|
|
2617
|
+
}
|
|
2618
|
+
return (0, import_types5.Ok)(envelope.data ?? {});
|
|
2619
|
+
}
|
|
2620
|
+
};
|
|
2540
2621
|
var LinearGraphQLStub = class {
|
|
2541
2622
|
async query(query, _variables) {
|
|
2542
2623
|
console.log("Linear GraphQL query (stub):", query);
|
|
@@ -2545,7 +2626,7 @@ var LinearGraphQLStub = class {
|
|
|
2545
2626
|
};
|
|
2546
2627
|
|
|
2547
2628
|
// src/workspace/manager.ts
|
|
2548
|
-
var
|
|
2629
|
+
var fs8 = __toESM(require("fs/promises"));
|
|
2549
2630
|
var path8 = __toESM(require("path"));
|
|
2550
2631
|
var import_node_child_process2 = require("child_process");
|
|
2551
2632
|
var import_node_util2 = require("util");
|
|
@@ -2585,7 +2666,7 @@ var WorkspaceManager = class _WorkspaceManager {
|
|
|
2585
2666
|
async getRepoRoot() {
|
|
2586
2667
|
if (this.repoRoot) return this.repoRoot;
|
|
2587
2668
|
const root = path8.resolve(this.config.root);
|
|
2588
|
-
await
|
|
2669
|
+
await fs8.mkdir(root, { recursive: true });
|
|
2589
2670
|
const stdout = await this.git(["rev-parse", "--show-toplevel"], root);
|
|
2590
2671
|
this.repoRoot = stdout.trim();
|
|
2591
2672
|
return this.repoRoot;
|
|
@@ -2598,21 +2679,21 @@ var WorkspaceManager = class _WorkspaceManager {
|
|
|
2598
2679
|
try {
|
|
2599
2680
|
const workspacePath = path8.resolve(this.resolvePath(identifier));
|
|
2600
2681
|
try {
|
|
2601
|
-
await
|
|
2682
|
+
await fs8.access(path8.join(workspacePath, ".git"));
|
|
2602
2683
|
const repoRoot2 = await this.getRepoRoot();
|
|
2603
2684
|
try {
|
|
2604
2685
|
await this.git(["worktree", "remove", "--force", workspacePath], repoRoot2);
|
|
2605
2686
|
} catch {
|
|
2606
|
-
await
|
|
2687
|
+
await fs8.rm(workspacePath, { recursive: true, force: true });
|
|
2607
2688
|
}
|
|
2608
2689
|
} catch {
|
|
2609
2690
|
try {
|
|
2610
|
-
await
|
|
2691
|
+
await fs8.access(workspacePath);
|
|
2611
2692
|
const repoRoot2 = await this.getRepoRoot();
|
|
2612
2693
|
try {
|
|
2613
2694
|
await this.git(["worktree", "remove", "--force", workspacePath], repoRoot2);
|
|
2614
2695
|
} catch {
|
|
2615
|
-
await
|
|
2696
|
+
await fs8.rm(workspacePath, { recursive: true, force: true });
|
|
2616
2697
|
}
|
|
2617
2698
|
} catch {
|
|
2618
2699
|
}
|
|
@@ -2665,13 +2746,13 @@ var WorkspaceManager = class _WorkspaceManager {
|
|
|
2665
2746
|
}
|
|
2666
2747
|
const src = path8.join(repoRoot, rel);
|
|
2667
2748
|
try {
|
|
2668
|
-
await
|
|
2749
|
+
await fs8.access(src);
|
|
2669
2750
|
} catch {
|
|
2670
2751
|
continue;
|
|
2671
2752
|
}
|
|
2672
2753
|
const dest = path8.join(workspacePath, rel);
|
|
2673
2754
|
try {
|
|
2674
|
-
await
|
|
2755
|
+
await fs8.cp(src, dest, { recursive: true, force: true });
|
|
2675
2756
|
} catch {
|
|
2676
2757
|
}
|
|
2677
2758
|
}
|
|
@@ -2747,7 +2828,7 @@ var WorkspaceManager = class _WorkspaceManager {
|
|
|
2747
2828
|
async exists(identifier) {
|
|
2748
2829
|
try {
|
|
2749
2830
|
const workspacePath = this.resolvePath(identifier);
|
|
2750
|
-
await
|
|
2831
|
+
await fs8.access(workspacePath);
|
|
2751
2832
|
return true;
|
|
2752
2833
|
} catch {
|
|
2753
2834
|
return false;
|
|
@@ -2762,7 +2843,7 @@ var WorkspaceManager = class _WorkspaceManager {
|
|
|
2762
2843
|
try {
|
|
2763
2844
|
const workspacePath = path8.resolve(this.resolvePath(identifier));
|
|
2764
2845
|
try {
|
|
2765
|
-
await
|
|
2846
|
+
await fs8.access(path8.join(workspacePath, ".git"));
|
|
2766
2847
|
} catch {
|
|
2767
2848
|
return null;
|
|
2768
2849
|
}
|
|
@@ -2873,7 +2954,7 @@ var WorkspaceManager = class _WorkspaceManager {
|
|
|
2873
2954
|
const repoRoot = await this.getRepoRoot();
|
|
2874
2955
|
await this.git(["worktree", "remove", "--force", workspacePath], repoRoot);
|
|
2875
2956
|
} catch {
|
|
2876
|
-
await
|
|
2957
|
+
await fs8.rm(workspacePath, { recursive: true, force: true });
|
|
2877
2958
|
}
|
|
2878
2959
|
return (0, import_types6.Ok)(void 0);
|
|
2879
2960
|
} catch (error) {
|
|
@@ -3014,8 +3095,8 @@ var PromptRenderer = class {
|
|
|
3014
3095
|
// src/orchestrator.ts
|
|
3015
3096
|
var import_node_events = require("events");
|
|
3016
3097
|
var path21 = __toESM(require("path"));
|
|
3017
|
-
var
|
|
3018
|
-
var
|
|
3098
|
+
var import_node_crypto15 = require("crypto");
|
|
3099
|
+
var import_core15 = require("@harness-engineering/core");
|
|
3019
3100
|
|
|
3020
3101
|
// src/core/stall-detector.ts
|
|
3021
3102
|
function detectStalledIssues(running, nowMs, stallTimeoutMs) {
|
|
@@ -3591,7 +3672,7 @@ var CompletionHandler = class {
|
|
|
3591
3672
|
};
|
|
3592
3673
|
|
|
3593
3674
|
// src/orchestrator.ts
|
|
3594
|
-
var
|
|
3675
|
+
var import_core16 = require("@harness-engineering/core");
|
|
3595
3676
|
|
|
3596
3677
|
// src/tracker/adapters/github-issues-issue-tracker.ts
|
|
3597
3678
|
var import_types9 = require("@harness-engineering/types");
|
|
@@ -6511,6 +6592,96 @@ var OrchestratorBackendFactory = class {
|
|
|
6511
6592
|
}
|
|
6512
6593
|
};
|
|
6513
6594
|
|
|
6595
|
+
// src/agent/backend-resolver.ts
|
|
6596
|
+
function makeBackendResolver(backends) {
|
|
6597
|
+
return (backendName) => {
|
|
6598
|
+
const def = backends?.[backendName];
|
|
6599
|
+
return def ? createBackend(def) : null;
|
|
6600
|
+
};
|
|
6601
|
+
}
|
|
6602
|
+
|
|
6603
|
+
// src/maintenance/agent-dispatcher.ts
|
|
6604
|
+
function syntheticIssue(branch, skill) {
|
|
6605
|
+
return {
|
|
6606
|
+
id: `maintenance:${branch}`,
|
|
6607
|
+
identifier: branch,
|
|
6608
|
+
title: `Maintenance: ${skill}`,
|
|
6609
|
+
description: null,
|
|
6610
|
+
priority: null,
|
|
6611
|
+
state: "in_progress",
|
|
6612
|
+
branchName: branch,
|
|
6613
|
+
url: null,
|
|
6614
|
+
labels: ["maintenance"],
|
|
6615
|
+
blockedBy: [],
|
|
6616
|
+
spec: null,
|
|
6617
|
+
plans: [],
|
|
6618
|
+
createdAt: null,
|
|
6619
|
+
updatedAt: null,
|
|
6620
|
+
externalId: null
|
|
6621
|
+
};
|
|
6622
|
+
}
|
|
6623
|
+
function buildPrompt(skill, promptContext) {
|
|
6624
|
+
const instruction = `Run the \`${skill}\` skill to detect and fix issues in this repository. Apply the fixes and commit each logical change. If there is nothing to fix, make no commits.`;
|
|
6625
|
+
return promptContext ? `${promptContext}
|
|
6626
|
+
|
|
6627
|
+
${instruction}` : instruction;
|
|
6628
|
+
}
|
|
6629
|
+
function headOf(git2, cwd) {
|
|
6630
|
+
try {
|
|
6631
|
+
return git2(["rev-parse", "HEAD"], cwd) || null;
|
|
6632
|
+
} catch {
|
|
6633
|
+
return null;
|
|
6634
|
+
}
|
|
6635
|
+
}
|
|
6636
|
+
function createAgentDispatcher(deps) {
|
|
6637
|
+
const maxTurns = deps.maxTurns ?? 10;
|
|
6638
|
+
const dispatch = async (skill, branch, backendName, cwd, options) => {
|
|
6639
|
+
const backend = deps.resolveBackend(backendName);
|
|
6640
|
+
if (!backend) {
|
|
6641
|
+
deps.logger.warn(`Maintenance agent dispatch skipped: unknown backend "${backendName}"`, {
|
|
6642
|
+
skill,
|
|
6643
|
+
branch
|
|
6644
|
+
});
|
|
6645
|
+
return { producedCommits: false, fixed: 0 };
|
|
6646
|
+
}
|
|
6647
|
+
const before = headOf(deps.git, cwd);
|
|
6648
|
+
const runner = new AgentRunner(backend, { maxTurns });
|
|
6649
|
+
const session = runner.runSession(
|
|
6650
|
+
syntheticIssue(branch, skill),
|
|
6651
|
+
cwd,
|
|
6652
|
+
buildPrompt(skill, options?.promptContext)
|
|
6653
|
+
);
|
|
6654
|
+
let step = await session.next();
|
|
6655
|
+
while (!step.done) step = await session.next();
|
|
6656
|
+
const after = headOf(deps.git, cwd);
|
|
6657
|
+
let fixed = 0;
|
|
6658
|
+
if (after && after !== before) {
|
|
6659
|
+
if (before) {
|
|
6660
|
+
try {
|
|
6661
|
+
fixed = parseInt(deps.git(["rev-list", "--count", `${before}..${after}`], cwd), 10) || 0;
|
|
6662
|
+
} catch {
|
|
6663
|
+
fixed = 1;
|
|
6664
|
+
}
|
|
6665
|
+
} else {
|
|
6666
|
+
fixed = 1;
|
|
6667
|
+
}
|
|
6668
|
+
}
|
|
6669
|
+
const producedCommits = fixed > 0;
|
|
6670
|
+
deps.logger.info("Maintenance agent dispatch complete", {
|
|
6671
|
+
skill,
|
|
6672
|
+
branch,
|
|
6673
|
+
backendName,
|
|
6674
|
+
producedCommits,
|
|
6675
|
+
fixed
|
|
6676
|
+
});
|
|
6677
|
+
return { producedCommits, fixed };
|
|
6678
|
+
};
|
|
6679
|
+
return { dispatch };
|
|
6680
|
+
}
|
|
6681
|
+
|
|
6682
|
+
// src/orchestrator.ts
|
|
6683
|
+
var import_node_child_process13 = require("child_process");
|
|
6684
|
+
|
|
6514
6685
|
// src/agent/intelligence-factory.ts
|
|
6515
6686
|
var import_intelligence3 = require("@harness-engineering/intelligence");
|
|
6516
6687
|
var import_graph = require("@harness-engineering/graph");
|
|
@@ -7012,7 +7183,7 @@ function handleV1InteractionsResolveRoute(req, res, queue) {
|
|
|
7012
7183
|
|
|
7013
7184
|
// src/server/routes/plans.ts
|
|
7014
7185
|
var import_zod5 = require("zod");
|
|
7015
|
-
var
|
|
7186
|
+
var fs9 = __toESM(require("fs/promises"));
|
|
7016
7187
|
var path11 = __toESM(require("path"));
|
|
7017
7188
|
var PlanWriteSchema = import_zod5.z.object({
|
|
7018
7189
|
filename: import_zod5.z.string().min(1),
|
|
@@ -7041,9 +7212,9 @@ function handlePlansRoute(req, res, plansDir) {
|
|
|
7041
7212
|
);
|
|
7042
7213
|
return;
|
|
7043
7214
|
}
|
|
7044
|
-
await
|
|
7215
|
+
await fs9.mkdir(plansDir, { recursive: true });
|
|
7045
7216
|
const filePath = path11.join(plansDir, basename3);
|
|
7046
|
-
await
|
|
7217
|
+
await fs9.writeFile(filePath, parsed.content, "utf-8");
|
|
7047
7218
|
res.writeHead(201, { "Content-Type": "application/json" });
|
|
7048
7219
|
res.end(JSON.stringify({ ok: true, filename: basename3 }));
|
|
7049
7220
|
} catch {
|
|
@@ -7418,7 +7589,6 @@ function handleAnalyzeRoute(req, res, pipeline) {
|
|
|
7418
7589
|
}
|
|
7419
7590
|
|
|
7420
7591
|
// src/server/routes/roadmap-actions.ts
|
|
7421
|
-
var fs11 = __toESM(require("fs/promises"));
|
|
7422
7592
|
var path12 = __toESM(require("path"));
|
|
7423
7593
|
var import_core7 = require("@harness-engineering/core");
|
|
7424
7594
|
var import_zod8 = require("zod");
|
|
@@ -7500,13 +7670,14 @@ function handleRoadmapActionsRoute(req, res, roadmapPath) {
|
|
|
7500
7670
|
sendJSON2(res, 400, { error: "Title must not contain newlines or markdown headings" });
|
|
7501
7671
|
return;
|
|
7502
7672
|
}
|
|
7503
|
-
const
|
|
7504
|
-
const
|
|
7505
|
-
if (!
|
|
7673
|
+
const store = (0, import_core7.resolveRoadmapStoreForFile)({ roadmapPath });
|
|
7674
|
+
const loaded = await store.load();
|
|
7675
|
+
if (!loaded.ok) {
|
|
7506
7676
|
sendJSON2(res, 500, { error: "Failed to parse roadmap file" });
|
|
7507
7677
|
return;
|
|
7508
7678
|
}
|
|
7509
|
-
const roadmap =
|
|
7679
|
+
const roadmap = loaded.value;
|
|
7680
|
+
const before = structuredClone(roadmap);
|
|
7510
7681
|
let backlog = roadmap.milestones.find((m) => m.isBacklog);
|
|
7511
7682
|
if (!backlog) {
|
|
7512
7683
|
backlog = { name: "Backlog", isBacklog: true, features: [] };
|
|
@@ -7529,10 +7700,11 @@ function handleRoadmapActionsRoute(req, res, roadmapPath) {
|
|
|
7529
7700
|
updatedAt: null
|
|
7530
7701
|
});
|
|
7531
7702
|
roadmap.frontmatter.lastManualEdit = (/* @__PURE__ */ new Date()).toISOString();
|
|
7532
|
-
const
|
|
7533
|
-
|
|
7534
|
-
|
|
7535
|
-
|
|
7703
|
+
const persisted = await (0, import_core7.applyRoadmapDiff)(store, before, roadmap);
|
|
7704
|
+
if (!persisted.ok) {
|
|
7705
|
+
sendJSON2(res, 500, { error: persisted.error.message });
|
|
7706
|
+
return;
|
|
7707
|
+
}
|
|
7536
7708
|
sendJSON2(res, 201, { ok: true, featureName: parsed.title });
|
|
7537
7709
|
} catch (err) {
|
|
7538
7710
|
const msg = err instanceof Error ? err.message : "Failed to append to roadmap";
|
|
@@ -7798,7 +7970,6 @@ function handleV1JobsMaintenanceRoute(req, res, deps) {
|
|
|
7798
7970
|
}
|
|
7799
7971
|
|
|
7800
7972
|
// src/server/routes/v1/events-sse.ts
|
|
7801
|
-
var import_node_crypto8 = require("crypto");
|
|
7802
7973
|
var SSE_TOPICS = [
|
|
7803
7974
|
"state_change",
|
|
7804
7975
|
"agent_event",
|
|
@@ -7814,8 +7985,55 @@ var SSE_TOPICS = [
|
|
|
7814
7985
|
"webhook.subscription.deleted"
|
|
7815
7986
|
];
|
|
7816
7987
|
var HEARTBEAT_MS = 15e3;
|
|
7817
|
-
|
|
7818
|
-
|
|
7988
|
+
var DEFAULT_REPLAY_BUFFER = 1024;
|
|
7989
|
+
var SseEventLog = class {
|
|
7990
|
+
seq = 0;
|
|
7991
|
+
buffer = [];
|
|
7992
|
+
subscribers = /* @__PURE__ */ new Set();
|
|
7993
|
+
constructor(bus, cap = DEFAULT_REPLAY_BUFFER) {
|
|
7994
|
+
this.cap = cap;
|
|
7995
|
+
for (const topic of SSE_TOPICS) {
|
|
7996
|
+
bus.on(topic, (data) => this.record(topic, data));
|
|
7997
|
+
}
|
|
7998
|
+
}
|
|
7999
|
+
cap;
|
|
8000
|
+
record(topic, data) {
|
|
8001
|
+
const event = { id: ++this.seq, topic, data };
|
|
8002
|
+
this.buffer.push(event);
|
|
8003
|
+
if (this.buffer.length > this.cap) this.buffer.shift();
|
|
8004
|
+
for (const fn of this.subscribers) fn(event);
|
|
8005
|
+
}
|
|
8006
|
+
/** Highest assigned id (0 when nothing has been recorded yet). */
|
|
8007
|
+
currentSeq() {
|
|
8008
|
+
return this.seq;
|
|
8009
|
+
}
|
|
8010
|
+
/** Buffered events strictly newer than `lastId`, oldest-first. */
|
|
8011
|
+
replayFrom(lastId) {
|
|
8012
|
+
return this.buffer.filter((e) => e.id > lastId);
|
|
8013
|
+
}
|
|
8014
|
+
/** Register a live listener; returns an unsubscribe function. */
|
|
8015
|
+
subscribe(fn) {
|
|
8016
|
+
this.subscribers.add(fn);
|
|
8017
|
+
return () => {
|
|
8018
|
+
this.subscribers.delete(fn);
|
|
8019
|
+
};
|
|
8020
|
+
}
|
|
8021
|
+
};
|
|
8022
|
+
var logsByBus = /* @__PURE__ */ new WeakMap();
|
|
8023
|
+
function getSseEventLog(bus) {
|
|
8024
|
+
let log = logsByBus.get(bus);
|
|
8025
|
+
if (!log) {
|
|
8026
|
+
log = new SseEventLog(bus);
|
|
8027
|
+
logsByBus.set(bus, log);
|
|
8028
|
+
}
|
|
8029
|
+
return log;
|
|
8030
|
+
}
|
|
8031
|
+
function parseLastEventId(req) {
|
|
8032
|
+
const raw = req.headers["last-event-id"];
|
|
8033
|
+
const value = Array.isArray(raw) ? raw[0] : raw;
|
|
8034
|
+
if (value === void 0) return null;
|
|
8035
|
+
const n = Number(value);
|
|
8036
|
+
return Number.isInteger(n) && n >= 0 ? n : null;
|
|
7819
8037
|
}
|
|
7820
8038
|
function handleV1EventsSseRoute(req, res, bus) {
|
|
7821
8039
|
if (req.method !== "GET" || req.url !== "/api/v1/events") return false;
|
|
@@ -7827,22 +8045,28 @@ function handleV1EventsSseRoute(req, res, bus) {
|
|
|
7827
8045
|
res.write(`: harness gateway SSE \u2014 connected at ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
7828
8046
|
|
|
7829
8047
|
`);
|
|
7830
|
-
const
|
|
7831
|
-
|
|
7832
|
-
|
|
7833
|
-
|
|
7834
|
-
|
|
7835
|
-
|
|
7836
|
-
id: ${newEventId()}
|
|
8048
|
+
const log = getSseEventLog(bus);
|
|
8049
|
+
const send = (e) => {
|
|
8050
|
+
try {
|
|
8051
|
+
res.write(`event: ${e.topic}
|
|
8052
|
+
data: ${JSON.stringify(e.data)}
|
|
8053
|
+
id: ${e.id}
|
|
7837
8054
|
|
|
7838
|
-
|
|
7839
|
-
|
|
7840
|
-
|
|
7841
|
-
|
|
7842
|
-
|
|
7843
|
-
|
|
7844
|
-
|
|
8055
|
+
`);
|
|
8056
|
+
} catch {
|
|
8057
|
+
}
|
|
8058
|
+
};
|
|
8059
|
+
const lastId = parseLastEventId(req);
|
|
8060
|
+
let replayedThrough = lastId ?? log.currentSeq();
|
|
8061
|
+
if (lastId !== null) {
|
|
8062
|
+
for (const e of log.replayFrom(lastId)) {
|
|
8063
|
+
send(e);
|
|
8064
|
+
replayedThrough = e.id;
|
|
8065
|
+
}
|
|
7845
8066
|
}
|
|
8067
|
+
const unsubscribe = log.subscribe((e) => {
|
|
8068
|
+
if (e.id > replayedThrough) send(e);
|
|
8069
|
+
});
|
|
7846
8070
|
const heartbeat = setInterval(() => {
|
|
7847
8071
|
try {
|
|
7848
8072
|
res.write(": heartbeat\n\n");
|
|
@@ -7852,7 +8076,7 @@ id: ${newEventId()}
|
|
|
7852
8076
|
heartbeat.unref();
|
|
7853
8077
|
const cleanup = () => {
|
|
7854
8078
|
clearInterval(heartbeat);
|
|
7855
|
-
|
|
8079
|
+
unsubscribe();
|
|
7856
8080
|
};
|
|
7857
8081
|
res.on("close", cleanup);
|
|
7858
8082
|
res.on("finish", cleanup);
|
|
@@ -8161,7 +8385,7 @@ async function runGate(projectPath, proposalId) {
|
|
|
8161
8385
|
}
|
|
8162
8386
|
|
|
8163
8387
|
// src/proposals/promote.ts
|
|
8164
|
-
var
|
|
8388
|
+
var fs10 = __toESM(require("fs"));
|
|
8165
8389
|
var path13 = __toESM(require("path"));
|
|
8166
8390
|
var import_yaml4 = require("yaml");
|
|
8167
8391
|
var import_core9 = require("@harness-engineering/core");
|
|
@@ -8183,7 +8407,7 @@ function skillDir(projectPath, name) {
|
|
|
8183
8407
|
}
|
|
8184
8408
|
function readIfExists(p) {
|
|
8185
8409
|
try {
|
|
8186
|
-
return
|
|
8410
|
+
return fs10.readFileSync(p, "utf-8");
|
|
8187
8411
|
} catch {
|
|
8188
8412
|
return null;
|
|
8189
8413
|
}
|
|
@@ -8229,15 +8453,15 @@ function assertGateReady(proposal) {
|
|
|
8229
8453
|
}
|
|
8230
8454
|
async function promoteNewSkill(projectPath, proposal) {
|
|
8231
8455
|
const target = skillDir(projectPath, proposal.content.name);
|
|
8232
|
-
if (
|
|
8456
|
+
if (fs10.existsSync(target)) {
|
|
8233
8457
|
throw new PromotionError(
|
|
8234
8458
|
`a catalog skill already exists at ${target}; use a refinement proposal to update it`
|
|
8235
8459
|
);
|
|
8236
8460
|
}
|
|
8237
|
-
|
|
8461
|
+
fs10.mkdirSync(target, { recursive: true });
|
|
8238
8462
|
const yamlOut = injectProvenanceIntoYaml(proposal.content.skillYaml ?? "", proposal.id);
|
|
8239
|
-
|
|
8240
|
-
|
|
8463
|
+
fs10.writeFileSync(path13.join(target, "skill.yaml"), yamlOut);
|
|
8464
|
+
fs10.writeFileSync(path13.join(target, "SKILL.md"), proposal.content.skillMd ?? "");
|
|
8241
8465
|
return { skillPath: target };
|
|
8242
8466
|
}
|
|
8243
8467
|
async function promoteRefinement(projectPath, proposal) {
|
|
@@ -8245,7 +8469,7 @@ async function promoteRefinement(projectPath, proposal) {
|
|
|
8245
8469
|
throw new PromotionError("refinement proposal is missing targetSkill");
|
|
8246
8470
|
}
|
|
8247
8471
|
const target = skillDir(projectPath, proposal.targetSkill);
|
|
8248
|
-
if (!
|
|
8472
|
+
if (!fs10.existsSync(target)) {
|
|
8249
8473
|
throw new PromotionError(
|
|
8250
8474
|
`target skill ${proposal.targetSkill} does not exist at ${target}; cannot refine`
|
|
8251
8475
|
);
|
|
@@ -8258,7 +8482,7 @@ async function promoteRefinement(projectPath, proposal) {
|
|
|
8258
8482
|
"no metadata changes detected; check that the reviewer applied the proposed diff before approving"
|
|
8259
8483
|
);
|
|
8260
8484
|
}
|
|
8261
|
-
|
|
8485
|
+
fs10.writeFileSync(yamlPath, after);
|
|
8262
8486
|
return { skillPath: target };
|
|
8263
8487
|
}
|
|
8264
8488
|
async function promote(projectPath, proposalId, decidedBy) {
|
|
@@ -8696,7 +8920,7 @@ function handleV1RoutingRoute(req, res, deps) {
|
|
|
8696
8920
|
}
|
|
8697
8921
|
|
|
8698
8922
|
// src/server/routes/sessions.ts
|
|
8699
|
-
var
|
|
8923
|
+
var fs11 = __toESM(require("fs/promises"));
|
|
8700
8924
|
var path14 = __toESM(require("path"));
|
|
8701
8925
|
var import_zod15 = require("zod");
|
|
8702
8926
|
var SessionCreateSchema = import_zod15.z.object({
|
|
@@ -8717,12 +8941,12 @@ function extractSessionId(url) {
|
|
|
8717
8941
|
}
|
|
8718
8942
|
async function handleList2(res, sessionsDir) {
|
|
8719
8943
|
try {
|
|
8720
|
-
const entries = await
|
|
8944
|
+
const entries = await fs11.readdir(sessionsDir, { withFileTypes: true });
|
|
8721
8945
|
const sessions = [];
|
|
8722
8946
|
for (const entry of entries) {
|
|
8723
8947
|
if (!entry.isDirectory()) continue;
|
|
8724
8948
|
try {
|
|
8725
|
-
const content = await
|
|
8949
|
+
const content = await fs11.readFile(
|
|
8726
8950
|
path14.join(sessionsDir, entry.name, "session.json"),
|
|
8727
8951
|
"utf-8"
|
|
8728
8952
|
);
|
|
@@ -8748,7 +8972,7 @@ async function handleGet2(res, id, sessionsDir) {
|
|
|
8748
8972
|
return;
|
|
8749
8973
|
}
|
|
8750
8974
|
try {
|
|
8751
|
-
const content = await
|
|
8975
|
+
const content = await fs11.readFile(path14.join(sessionsDir, id, "session.json"), "utf-8");
|
|
8752
8976
|
jsonResponse(res, 200, JSON.parse(content));
|
|
8753
8977
|
} catch (err) {
|
|
8754
8978
|
if (err.code === "ENOENT") {
|
|
@@ -8772,8 +8996,8 @@ async function handleCreate(req, res, sessionsDir) {
|
|
|
8772
8996
|
return;
|
|
8773
8997
|
}
|
|
8774
8998
|
const sessionDir = path14.join(sessionsDir, session.sessionId);
|
|
8775
|
-
await
|
|
8776
|
-
await
|
|
8999
|
+
await fs11.mkdir(sessionDir, { recursive: true });
|
|
9000
|
+
await fs11.writeFile(path14.join(sessionDir, "session.json"), JSON.stringify(session, null, 2));
|
|
8777
9001
|
jsonResponse(res, 200, { ok: true });
|
|
8778
9002
|
} catch {
|
|
8779
9003
|
jsonResponse(res, 500, { error: "Failed to save session" });
|
|
@@ -8789,8 +9013,8 @@ async function handleUpdate(req, res, url, sessionsDir) {
|
|
|
8789
9013
|
const body = await readBody(req);
|
|
8790
9014
|
const updates = import_zod15.z.record(import_zod15.z.unknown()).parse(JSON.parse(body));
|
|
8791
9015
|
const sessionFilePath = path14.join(sessionsDir, id, "session.json");
|
|
8792
|
-
const current = JSON.parse(await
|
|
8793
|
-
await
|
|
9016
|
+
const current = JSON.parse(await fs11.readFile(sessionFilePath, "utf-8"));
|
|
9017
|
+
await fs11.writeFile(sessionFilePath, JSON.stringify({ ...current, ...updates }, null, 2));
|
|
8794
9018
|
jsonResponse(res, 200, { ok: true });
|
|
8795
9019
|
} catch {
|
|
8796
9020
|
jsonResponse(res, 500, { error: "Failed to update session" });
|
|
@@ -8803,7 +9027,7 @@ async function handleDelete(res, url, sessionsDir) {
|
|
|
8803
9027
|
jsonResponse(res, 400, { error: "Missing or invalid sessionId" });
|
|
8804
9028
|
return;
|
|
8805
9029
|
}
|
|
8806
|
-
await
|
|
9030
|
+
await fs11.rm(path14.join(sessionsDir, id), { recursive: true, force: true });
|
|
8807
9031
|
jsonResponse(res, 200, { ok: true });
|
|
8808
9032
|
} catch {
|
|
8809
9033
|
jsonResponse(res, 500, { error: "Failed to delete session" });
|
|
@@ -9047,7 +9271,7 @@ function handleLocalModelsRoute(req, res, getStatuses) {
|
|
|
9047
9271
|
}
|
|
9048
9272
|
|
|
9049
9273
|
// src/server/static.ts
|
|
9050
|
-
var
|
|
9274
|
+
var fs12 = __toESM(require("fs"));
|
|
9051
9275
|
var path15 = __toESM(require("path"));
|
|
9052
9276
|
var MIME_TYPES = {
|
|
9053
9277
|
".html": "text/html; charset=utf-8",
|
|
@@ -9077,11 +9301,11 @@ function handleStaticFile(req, res, dashboardDir) {
|
|
|
9077
9301
|
if (!resolved.startsWith(path15.resolve(dashboardDir))) {
|
|
9078
9302
|
return serveFile(path15.join(dashboardDir, "index.html"), res);
|
|
9079
9303
|
}
|
|
9080
|
-
if (
|
|
9304
|
+
if (fs12.existsSync(resolved) && fs12.statSync(resolved).isFile()) {
|
|
9081
9305
|
return serveFile(resolved, res);
|
|
9082
9306
|
}
|
|
9083
9307
|
const indexPath = path15.join(dashboardDir, "index.html");
|
|
9084
|
-
if (
|
|
9308
|
+
if (fs12.existsSync(indexPath)) {
|
|
9085
9309
|
return serveFile(indexPath, res);
|
|
9086
9310
|
}
|
|
9087
9311
|
return false;
|
|
@@ -9090,7 +9314,7 @@ function serveFile(filePath, res) {
|
|
|
9090
9314
|
const ext = path15.extname(filePath).toLowerCase();
|
|
9091
9315
|
const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
|
|
9092
9316
|
try {
|
|
9093
|
-
const content =
|
|
9317
|
+
const content = fs12.readFileSync(filePath);
|
|
9094
9318
|
res.writeHead(200, { "Content-Type": contentType });
|
|
9095
9319
|
res.end(content);
|
|
9096
9320
|
return true;
|
|
@@ -9100,7 +9324,7 @@ function serveFile(filePath, res) {
|
|
|
9100
9324
|
}
|
|
9101
9325
|
|
|
9102
9326
|
// src/server/plan-watcher.ts
|
|
9103
|
-
var
|
|
9327
|
+
var fs13 = __toESM(require("fs"));
|
|
9104
9328
|
var path16 = __toESM(require("path"));
|
|
9105
9329
|
var PlanWatcher = class {
|
|
9106
9330
|
plansDir;
|
|
@@ -9115,11 +9339,11 @@ var PlanWatcher = class {
|
|
|
9115
9339
|
* Creates the directory if it does not exist.
|
|
9116
9340
|
*/
|
|
9117
9341
|
start() {
|
|
9118
|
-
|
|
9119
|
-
this.watcher =
|
|
9342
|
+
fs13.mkdirSync(this.plansDir, { recursive: true });
|
|
9343
|
+
this.watcher = fs13.watch(this.plansDir, (eventType, filename) => {
|
|
9120
9344
|
if (eventType === "rename" && filename && filename.endsWith(".md")) {
|
|
9121
9345
|
const filePath = path16.join(this.plansDir, filename);
|
|
9122
|
-
if (
|
|
9346
|
+
if (fs13.existsSync(filePath)) {
|
|
9123
9347
|
void this.handleNewPlan(filename);
|
|
9124
9348
|
}
|
|
9125
9349
|
}
|
|
@@ -9150,7 +9374,7 @@ var PlanWatcher = class {
|
|
|
9150
9374
|
};
|
|
9151
9375
|
|
|
9152
9376
|
// src/auth/tokens.ts
|
|
9153
|
-
var
|
|
9377
|
+
var import_node_crypto8 = require("crypto");
|
|
9154
9378
|
var import_promises = require("fs/promises");
|
|
9155
9379
|
var import_node_path = require("path");
|
|
9156
9380
|
var import_bcryptjs = __toESM(require("bcryptjs"));
|
|
@@ -9158,10 +9382,10 @@ var import_types26 = require("@harness-engineering/types");
|
|
|
9158
9382
|
var BCRYPT_ROUNDS = 12;
|
|
9159
9383
|
var LEGACY_ENV_ID = "tok_legacy_env";
|
|
9160
9384
|
function genId() {
|
|
9161
|
-
return `tok_${(0,
|
|
9385
|
+
return `tok_${(0, import_node_crypto8.randomBytes)(8).toString("hex")}`;
|
|
9162
9386
|
}
|
|
9163
9387
|
function genSecret() {
|
|
9164
|
-
return (0,
|
|
9388
|
+
return (0, import_node_crypto8.randomBytes)(24).toString("base64url");
|
|
9165
9389
|
}
|
|
9166
9390
|
function parseToken(raw) {
|
|
9167
9391
|
const dot = raw.indexOf(".");
|
|
@@ -9192,7 +9416,7 @@ var TokenStore = class {
|
|
|
9192
9416
|
}
|
|
9193
9417
|
async persist(records) {
|
|
9194
9418
|
await (0, import_promises.mkdir)((0, import_node_path.dirname)(this.path), { recursive: true });
|
|
9195
|
-
const tmp = `${this.path}.tmp-${process.pid}-${Date.now()}-${(0,
|
|
9419
|
+
const tmp = `${this.path}.tmp-${process.pid}-${Date.now()}-${(0, import_node_crypto8.randomBytes)(4).toString("hex")}`;
|
|
9196
9420
|
await (0, import_promises.writeFile)(tmp, JSON.stringify(records, null, 2), "utf8");
|
|
9197
9421
|
await (0, import_promises.rename)(tmp, this.path);
|
|
9198
9422
|
this.cache = records;
|
|
@@ -9261,7 +9485,7 @@ var TokenStore = class {
|
|
|
9261
9485
|
const a = Buffer.from(presented);
|
|
9262
9486
|
const b = Buffer.from(envValue);
|
|
9263
9487
|
if (a.length !== b.length) return null;
|
|
9264
|
-
if (!(0,
|
|
9488
|
+
if (!(0, import_node_crypto8.timingSafeEqual)(a, b)) return null;
|
|
9265
9489
|
return {
|
|
9266
9490
|
id: LEGACY_ENV_ID,
|
|
9267
9491
|
name: "legacy-env",
|
|
@@ -9878,15 +10102,15 @@ var OrchestratorServer = class {
|
|
|
9878
10102
|
};
|
|
9879
10103
|
|
|
9880
10104
|
// src/gateway/webhooks/store.ts
|
|
9881
|
-
var
|
|
10105
|
+
var import_node_crypto10 = require("crypto");
|
|
9882
10106
|
var import_promises3 = require("fs/promises");
|
|
9883
10107
|
var import_node_path3 = require("path");
|
|
9884
10108
|
var import_types28 = require("@harness-engineering/types");
|
|
9885
10109
|
|
|
9886
10110
|
// src/gateway/webhooks/signer.ts
|
|
9887
|
-
var
|
|
10111
|
+
var import_node_crypto9 = require("crypto");
|
|
9888
10112
|
function sign(secret, body) {
|
|
9889
|
-
const hex = (0,
|
|
10113
|
+
const hex = (0, import_node_crypto9.createHmac)("sha256", secret).update(body).digest("hex");
|
|
9890
10114
|
return `sha256=${hex}`;
|
|
9891
10115
|
}
|
|
9892
10116
|
function eventMatches(pattern, type) {
|
|
@@ -9905,10 +10129,10 @@ function eventMatches(pattern, type) {
|
|
|
9905
10129
|
|
|
9906
10130
|
// src/gateway/webhooks/store.ts
|
|
9907
10131
|
function genId2() {
|
|
9908
|
-
return `whk_${(0,
|
|
10132
|
+
return `whk_${(0, import_node_crypto10.randomBytes)(8).toString("hex")}`;
|
|
9909
10133
|
}
|
|
9910
10134
|
function genSecret2() {
|
|
9911
|
-
return (0,
|
|
10135
|
+
return (0, import_node_crypto10.randomBytes)(32).toString("base64url");
|
|
9912
10136
|
}
|
|
9913
10137
|
var WebhookStore = class {
|
|
9914
10138
|
constructor(path24) {
|
|
@@ -9933,7 +10157,7 @@ var WebhookStore = class {
|
|
|
9933
10157
|
return this.cache;
|
|
9934
10158
|
}
|
|
9935
10159
|
async persist(records) {
|
|
9936
|
-
const tmp = `${this.path}.tmp-${process.pid}-${Date.now()}-${(0,
|
|
10160
|
+
const tmp = `${this.path}.tmp-${process.pid}-${Date.now()}-${(0, import_node_crypto10.randomBytes)(4).toString("hex")}`;
|
|
9937
10161
|
try {
|
|
9938
10162
|
await (0, import_promises3.mkdir)((0, import_node_path3.dirname)(this.path), { recursive: true });
|
|
9939
10163
|
await (0, import_promises3.writeFile)(tmp, JSON.stringify(records, null, 2), { encoding: "utf8", mode: 384 });
|
|
@@ -9977,7 +10201,7 @@ var WebhookStore = class {
|
|
|
9977
10201
|
};
|
|
9978
10202
|
|
|
9979
10203
|
// src/gateway/webhooks/delivery.ts
|
|
9980
|
-
var
|
|
10204
|
+
var import_node_crypto11 = require("crypto");
|
|
9981
10205
|
|
|
9982
10206
|
// src/gateway/webhooks/queue.ts
|
|
9983
10207
|
var import_better_sqlite3 = __toESM(require("better-sqlite3"));
|
|
@@ -10187,7 +10411,7 @@ var WebhookDelivery = class {
|
|
|
10187
10411
|
enqueue(sub, event) {
|
|
10188
10412
|
const payload = JSON.stringify(event);
|
|
10189
10413
|
this.queue.insert({
|
|
10190
|
-
id: `dlv_${(0,
|
|
10414
|
+
id: `dlv_${(0, import_node_crypto11.randomBytes)(8).toString("hex")}`,
|
|
10191
10415
|
subscriptionId: sub.id,
|
|
10192
10416
|
eventType: event.type,
|
|
10193
10417
|
payload
|
|
@@ -10295,7 +10519,7 @@ var WebhookDelivery = class {
|
|
|
10295
10519
|
};
|
|
10296
10520
|
|
|
10297
10521
|
// src/gateway/webhooks/events.ts
|
|
10298
|
-
var
|
|
10522
|
+
var import_node_crypto12 = require("crypto");
|
|
10299
10523
|
var WEBHOOK_TOPICS = [
|
|
10300
10524
|
"interaction.created",
|
|
10301
10525
|
"interaction.resolved",
|
|
@@ -10310,8 +10534,8 @@ var WEBHOOK_TOPICS = [
|
|
|
10310
10534
|
"proposal.approved",
|
|
10311
10535
|
"proposal.rejected"
|
|
10312
10536
|
];
|
|
10313
|
-
function
|
|
10314
|
-
return `evt_${(0,
|
|
10537
|
+
function newEventId() {
|
|
10538
|
+
return `evt_${(0, import_node_crypto12.randomBytes)(8).toString("hex")}`;
|
|
10315
10539
|
}
|
|
10316
10540
|
function wireWebhookFanout({ bus, store, delivery }) {
|
|
10317
10541
|
const handlers = [];
|
|
@@ -10322,7 +10546,7 @@ function wireWebhookFanout({ bus, store, delivery }) {
|
|
|
10322
10546
|
const subs = await store.listForEvent(eventType);
|
|
10323
10547
|
if (subs.length === 0) return;
|
|
10324
10548
|
const event = {
|
|
10325
|
-
id:
|
|
10549
|
+
id: newEventId(),
|
|
10326
10550
|
type: eventType,
|
|
10327
10551
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10328
10552
|
data
|
|
@@ -10341,7 +10565,7 @@ function wireWebhookFanout({ bus, store, delivery }) {
|
|
|
10341
10565
|
}
|
|
10342
10566
|
|
|
10343
10567
|
// src/gateway/telemetry/fanout.ts
|
|
10344
|
-
var
|
|
10568
|
+
var import_node_crypto13 = require("crypto");
|
|
10345
10569
|
var import_core12 = require("@harness-engineering/core");
|
|
10346
10570
|
var TOPICS = {
|
|
10347
10571
|
MAINTENANCE_STARTED: "maintenance:started",
|
|
@@ -10364,14 +10588,14 @@ var SPAN_NAME = {
|
|
|
10364
10588
|
[TOPICS.DISPATCH_DECISION]: "dispatch_decision",
|
|
10365
10589
|
[TOPICS.SKILL_INVOCATION]: "skill_invocation"
|
|
10366
10590
|
};
|
|
10367
|
-
function
|
|
10368
|
-
return `evt_${(0,
|
|
10591
|
+
function newEventId2() {
|
|
10592
|
+
return `evt_${(0, import_node_crypto13.randomBytes)(8).toString("hex")}`;
|
|
10369
10593
|
}
|
|
10370
10594
|
function newTraceId() {
|
|
10371
|
-
return (0,
|
|
10595
|
+
return (0, import_node_crypto13.randomBytes)(16).toString("hex");
|
|
10372
10596
|
}
|
|
10373
10597
|
function newSpanId() {
|
|
10374
|
-
return (0,
|
|
10598
|
+
return (0, import_node_crypto13.randomBytes)(8).toString("hex");
|
|
10375
10599
|
}
|
|
10376
10600
|
function nowNs() {
|
|
10377
10601
|
return BigInt(Date.now()) * 1000000n;
|
|
@@ -10485,7 +10709,7 @@ function wireTelemetryFanout(params) {
|
|
|
10485
10709
|
};
|
|
10486
10710
|
exporter.push(span);
|
|
10487
10711
|
const gatewayEvent = {
|
|
10488
|
-
id:
|
|
10712
|
+
id: newEventId2(),
|
|
10489
10713
|
type: TELEMETRY_TYPE[topic],
|
|
10490
10714
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10491
10715
|
data: payload,
|
|
@@ -10679,7 +10903,7 @@ function buildSlackSink(config, options) {
|
|
|
10679
10903
|
}
|
|
10680
10904
|
|
|
10681
10905
|
// src/notifications/events.ts
|
|
10682
|
-
var
|
|
10906
|
+
var import_node_crypto14 = require("crypto");
|
|
10683
10907
|
|
|
10684
10908
|
// src/notifications/envelope.ts
|
|
10685
10909
|
function asObj(data) {
|
|
@@ -10810,8 +11034,8 @@ var NOTIFICATION_TOPICS = [
|
|
|
10810
11034
|
"proposal.approved",
|
|
10811
11035
|
"proposal.rejected"
|
|
10812
11036
|
];
|
|
10813
|
-
function
|
|
10814
|
-
return `evt_${(0,
|
|
11037
|
+
function newEventId3() {
|
|
11038
|
+
return `evt_${(0, import_node_crypto14.randomBytes)(8).toString("hex")}`;
|
|
10815
11039
|
}
|
|
10816
11040
|
function dispatchToEntry(bus, entry, event) {
|
|
10817
11041
|
const eventType = event.type;
|
|
@@ -10846,7 +11070,7 @@ function wireNotificationSinks({ bus, registry }) {
|
|
|
10846
11070
|
const entries = registry.list();
|
|
10847
11071
|
if (entries.length === 0) return;
|
|
10848
11072
|
const event = {
|
|
10849
|
-
id:
|
|
11073
|
+
id: newEventId3(),
|
|
10850
11074
|
type: eventType,
|
|
10851
11075
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10852
11076
|
data
|
|
@@ -10864,7 +11088,7 @@ function wireNotificationSinks({ bus, registry }) {
|
|
|
10864
11088
|
}
|
|
10865
11089
|
|
|
10866
11090
|
// src/orchestrator.ts
|
|
10867
|
-
var
|
|
11091
|
+
var import_core17 = require("@harness-engineering/core");
|
|
10868
11092
|
|
|
10869
11093
|
// src/logging/logger.ts
|
|
10870
11094
|
var StructuredLogger = class {
|
|
@@ -10949,6 +11173,37 @@ async function scanWorkspaceConfig(workspacePath) {
|
|
|
10949
11173
|
return { exitCode: (0, import_core13.computeScanExitCode)(results), results };
|
|
10950
11174
|
}
|
|
10951
11175
|
|
|
11176
|
+
// src/core/lane-persistence.ts
|
|
11177
|
+
var import_core14 = require("@harness-engineering/core");
|
|
11178
|
+
var import_types29 = require("@harness-engineering/types");
|
|
11179
|
+
var SIGNAL_TO_LANE = {
|
|
11180
|
+
claim: "claimed",
|
|
11181
|
+
dispatch: "in_progress",
|
|
11182
|
+
success: "in_review",
|
|
11183
|
+
failure: "blocked",
|
|
11184
|
+
abandon: "canceled"
|
|
11185
|
+
};
|
|
11186
|
+
function mapOrchestratorLane(signal) {
|
|
11187
|
+
return SIGNAL_TO_LANE[signal];
|
|
11188
|
+
}
|
|
11189
|
+
async function persistLane(projectPath, issueId, signal) {
|
|
11190
|
+
try {
|
|
11191
|
+
const reg = await import_core14.eventSourcing.registerTask(projectPath, issueId, []);
|
|
11192
|
+
if (!reg.ok) return reg;
|
|
11193
|
+
return await import_core14.eventSourcing.transitionLane(projectPath, issueId, mapOrchestratorLane(signal));
|
|
11194
|
+
} catch (err) {
|
|
11195
|
+
return (0, import_types29.Err)(err instanceof Error ? err : new Error(String(err)));
|
|
11196
|
+
}
|
|
11197
|
+
}
|
|
11198
|
+
async function readPersistedLanes(projectPath) {
|
|
11199
|
+
try {
|
|
11200
|
+
const snap = await import_core14.eventSourcing.readSnapshot(projectPath);
|
|
11201
|
+
return snap.ok ? snap.value.lanes : { tasks: {} };
|
|
11202
|
+
} catch {
|
|
11203
|
+
return { tasks: {} };
|
|
11204
|
+
}
|
|
11205
|
+
}
|
|
11206
|
+
|
|
10952
11207
|
// src/maintenance/task-registry.ts
|
|
10953
11208
|
var BUILT_IN_TASKS = [
|
|
10954
11209
|
// --- Mechanical-AI ---
|
|
@@ -11012,7 +11267,13 @@ var BUILT_IN_TASKS = [
|
|
|
11012
11267
|
description: "Detect and fix cross-check violations",
|
|
11013
11268
|
schedule: "0 6 * * 1",
|
|
11014
11269
|
branch: "harness-maint/cross-check-fixes",
|
|
11015
|
-
|
|
11270
|
+
// `harness cross-check` is a dedicated read-only CLI subcommand that surfaces
|
|
11271
|
+
// JUST cross-artifact consistency (plan→implementation coverage + staleness),
|
|
11272
|
+
// mirroring the `validate_cross_check` MCP tool's core (`runCrossCheck`)
|
|
11273
|
+
// WITHOUT running the full `harness validate` suite. It prints a parseable
|
|
11274
|
+
// `Cross-check: N issues` line and exits 0 (clean) / 1 (N issues), so the
|
|
11275
|
+
// maintenance runner reports real results instead of an honest `failure`.
|
|
11276
|
+
checkCommand: ["cross-check"],
|
|
11016
11277
|
fixSkill: "harness-cross-check-fix"
|
|
11017
11278
|
},
|
|
11018
11279
|
// --- Pure-AI ---
|
|
@@ -11071,7 +11332,10 @@ var BUILT_IN_TASKS = [
|
|
|
11071
11332
|
description: "Assess overall project health",
|
|
11072
11333
|
schedule: "0 6 * * *",
|
|
11073
11334
|
branch: null,
|
|
11074
|
-
|
|
11335
|
+
// `assess_project` is an MCP tool name, not a CLI subcommand. The CLI
|
|
11336
|
+
// composite project-health report is `harness insights` (health, entropy,
|
|
11337
|
+
// decay, attention, impact) — a read-only report that records metrics.
|
|
11338
|
+
checkCommand: ["insights"]
|
|
11075
11339
|
},
|
|
11076
11340
|
{
|
|
11077
11341
|
id: "stale-constraints",
|
|
@@ -11079,7 +11343,14 @@ var BUILT_IN_TASKS = [
|
|
|
11079
11343
|
description: "Detect stale architectural constraints",
|
|
11080
11344
|
schedule: "0 2 1 * *",
|
|
11081
11345
|
branch: null,
|
|
11082
|
-
|
|
11346
|
+
// `harness stale-constraints` is a dedicated read-only CLI subcommand that
|
|
11347
|
+
// surfaces the `detect_stale_constraints` MCP tool's core in-process. It is
|
|
11348
|
+
// precondition-gated on the knowledge graph: with no graph it emits the
|
|
11349
|
+
// "No knowledge graph found. Run `harness scan` first." signature and exits
|
|
11350
|
+
// non-zero, which the runner classifies as `skipped` (not failure). With a
|
|
11351
|
+
// graph it prints a parseable `Stale constraints: N findings` line and exits
|
|
11352
|
+
// 0 (clean) / 1 (N stale), recorded as report-only metrics.
|
|
11353
|
+
checkCommand: ["stale-constraints"]
|
|
11083
11354
|
},
|
|
11084
11355
|
{
|
|
11085
11356
|
id: "graph-refresh",
|
|
@@ -11112,7 +11383,8 @@ var BUILT_IN_TASKS = [
|
|
|
11112
11383
|
description: "Clean up stale orchestrator sessions",
|
|
11113
11384
|
schedule: "0 0 * * *",
|
|
11114
11385
|
branch: null,
|
|
11115
|
-
checkCommand: ["cleanup-sessions"]
|
|
11386
|
+
checkCommand: ["cleanup-sessions"],
|
|
11387
|
+
excludeFromHumanSweep: true
|
|
11116
11388
|
},
|
|
11117
11389
|
{
|
|
11118
11390
|
id: "perf-baselines",
|
|
@@ -11120,7 +11392,8 @@ var BUILT_IN_TASKS = [
|
|
|
11120
11392
|
description: "Update performance baselines",
|
|
11121
11393
|
schedule: "0 7 * * *",
|
|
11122
11394
|
branch: null,
|
|
11123
|
-
checkCommand: ["perf", "baselines", "update"]
|
|
11395
|
+
checkCommand: ["perf", "baselines", "update"],
|
|
11396
|
+
excludeFromHumanSweep: true
|
|
11124
11397
|
},
|
|
11125
11398
|
{
|
|
11126
11399
|
id: "main-sync",
|
|
@@ -11128,7 +11401,8 @@ var BUILT_IN_TASKS = [
|
|
|
11128
11401
|
description: "Fast-forward local default branch from origin",
|
|
11129
11402
|
schedule: "*/15 * * * *",
|
|
11130
11403
|
branch: null,
|
|
11131
|
-
checkCommand: ["harness", "sync-main", "--json"]
|
|
11404
|
+
checkCommand: ["harness", "sync-main", "--json"],
|
|
11405
|
+
excludeFromHumanSweep: true
|
|
11132
11406
|
},
|
|
11133
11407
|
// Hermes Phase 4 — one-shot backfill that stamps `provenance: user-authored`
|
|
11134
11408
|
// on every existing catalog skill. Schedule is Feb 31 (a date that never
|
|
@@ -11141,7 +11415,8 @@ var BUILT_IN_TASKS = [
|
|
|
11141
11415
|
description: "Backfill provenance: user-authored on every existing skill (one-shot, idempotent)",
|
|
11142
11416
|
schedule: "0 0 31 2 *",
|
|
11143
11417
|
branch: null,
|
|
11144
|
-
checkCommand: ["backfill-skill-provenance"]
|
|
11418
|
+
checkCommand: ["backfill-skill-provenance"],
|
|
11419
|
+
excludeFromHumanSweep: true
|
|
11145
11420
|
}
|
|
11146
11421
|
];
|
|
11147
11422
|
|
|
@@ -11208,6 +11483,17 @@ function cronMatchesNow(expression, now) {
|
|
|
11208
11483
|
const daysOfWeek = parseField(dowField, 0, 6);
|
|
11209
11484
|
return minutes.has(minute) && hours.has(hour) && daysOfMonth.has(dayOfMonth) && months.has(month) && daysOfWeek.has(dayOfWeek);
|
|
11210
11485
|
}
|
|
11486
|
+
function cronMatchesDate(expression, date) {
|
|
11487
|
+
const fields = expression.trim().split(/\s+/);
|
|
11488
|
+
if (fields.length !== 5) {
|
|
11489
|
+
throw new Error(`Invalid cron expression: expected 5 fields, got ${fields.length}`);
|
|
11490
|
+
}
|
|
11491
|
+
const [, , domField, monthField, dowField] = fields;
|
|
11492
|
+
const daysOfMonth = parseField(domField, 1, 31);
|
|
11493
|
+
const months = parseField(monthField, 1, 12);
|
|
11494
|
+
const daysOfWeek = parseField(dowField, 0, 6);
|
|
11495
|
+
return daysOfMonth.has(date.getDate()) && months.has(date.getMonth() + 1) && daysOfWeek.has(date.getDay());
|
|
11496
|
+
}
|
|
11211
11497
|
|
|
11212
11498
|
// src/maintenance/scheduler.ts
|
|
11213
11499
|
var MaintenanceScheduler = class {
|
|
@@ -11449,15 +11735,15 @@ var MaintenanceScheduler = class {
|
|
|
11449
11735
|
};
|
|
11450
11736
|
|
|
11451
11737
|
// src/maintenance/leader-elector.ts
|
|
11452
|
-
var
|
|
11738
|
+
var import_types30 = require("@harness-engineering/types");
|
|
11453
11739
|
var SingleProcessLeaderElector = class {
|
|
11454
11740
|
async electLeader() {
|
|
11455
|
-
return (0,
|
|
11741
|
+
return (0, import_types30.Ok)("claimed");
|
|
11456
11742
|
}
|
|
11457
11743
|
};
|
|
11458
11744
|
|
|
11459
11745
|
// src/maintenance/reporter.ts
|
|
11460
|
-
var
|
|
11746
|
+
var fs14 = __toESM(require("fs"));
|
|
11461
11747
|
var path18 = __toESM(require("path"));
|
|
11462
11748
|
var import_zod17 = require("zod");
|
|
11463
11749
|
var RunResultSchema = import_zod17.z.object({
|
|
@@ -11493,9 +11779,9 @@ var MaintenanceReporter = class {
|
|
|
11493
11779
|
*/
|
|
11494
11780
|
async load() {
|
|
11495
11781
|
try {
|
|
11496
|
-
await
|
|
11782
|
+
await fs14.promises.mkdir(this.persistDir, { recursive: true });
|
|
11497
11783
|
const filePath = path18.join(this.persistDir, "history.json");
|
|
11498
|
-
const data = await
|
|
11784
|
+
const data = await fs14.promises.readFile(filePath, "utf-8");
|
|
11499
11785
|
const parsed = import_zod17.z.array(RunResultSchema).safeParse(JSON.parse(data));
|
|
11500
11786
|
if (parsed.success) {
|
|
11501
11787
|
this.history = parsed.data.slice(0, MAX_HISTORY);
|
|
@@ -11529,9 +11815,9 @@ var MaintenanceReporter = class {
|
|
|
11529
11815
|
*/
|
|
11530
11816
|
async persist() {
|
|
11531
11817
|
try {
|
|
11532
|
-
await
|
|
11818
|
+
await fs14.promises.mkdir(this.persistDir, { recursive: true });
|
|
11533
11819
|
const filePath = path18.join(this.persistDir, "history.json");
|
|
11534
|
-
await
|
|
11820
|
+
await fs14.promises.writeFile(filePath, JSON.stringify(this.history, null, 2), "utf-8");
|
|
11535
11821
|
} catch (err) {
|
|
11536
11822
|
this.logger.error("MaintenanceReporter: failed to persist history", { error: String(err) });
|
|
11537
11823
|
}
|
|
@@ -11571,20 +11857,20 @@ var TaskRunner = class {
|
|
|
11571
11857
|
* @param origin - Hermes Phase 2 trigger-source tag; defaults to `'cron'`
|
|
11572
11858
|
* when called from the scheduler path.
|
|
11573
11859
|
*/
|
|
11574
|
-
async run(task, origin = "cron") {
|
|
11860
|
+
async run(task, origin = "cron", mode = "fix") {
|
|
11575
11861
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
11576
11862
|
let result;
|
|
11577
11863
|
let captured;
|
|
11578
11864
|
try {
|
|
11579
11865
|
switch (task.type) {
|
|
11580
11866
|
case "mechanical-ai": {
|
|
11581
|
-
const out = await this.runMechanicalAI(task, startedAt);
|
|
11867
|
+
const out = await this.runMechanicalAI(task, startedAt, mode);
|
|
11582
11868
|
result = out.result;
|
|
11583
11869
|
captured = out.captured;
|
|
11584
11870
|
break;
|
|
11585
11871
|
}
|
|
11586
11872
|
case "pure-ai":
|
|
11587
|
-
result = await this.runPureAI(task, startedAt);
|
|
11873
|
+
result = await this.runPureAI(task, startedAt, mode);
|
|
11588
11874
|
break;
|
|
11589
11875
|
case "report-only": {
|
|
11590
11876
|
const out = await this.runReportOnly(task, startedAt);
|
|
@@ -11593,7 +11879,7 @@ var TaskRunner = class {
|
|
|
11593
11879
|
break;
|
|
11594
11880
|
}
|
|
11595
11881
|
case "housekeeping": {
|
|
11596
|
-
const out = await this.runHousekeeping(task, startedAt);
|
|
11882
|
+
const out = await this.runHousekeeping(task, startedAt, mode);
|
|
11597
11883
|
result = out.result;
|
|
11598
11884
|
captured = out.captured;
|
|
11599
11885
|
break;
|
|
@@ -11657,7 +11943,8 @@ var TaskRunner = class {
|
|
|
11657
11943
|
findings: r2.findings,
|
|
11658
11944
|
stdout: r2.output,
|
|
11659
11945
|
stderr: r2.stderr,
|
|
11660
|
-
structured: r2.structured ? r2.structured : null
|
|
11946
|
+
structured: r2.structured ? r2.structured : null,
|
|
11947
|
+
executionFailed: false
|
|
11661
11948
|
};
|
|
11662
11949
|
}
|
|
11663
11950
|
if (!task.checkCommand || task.checkCommand.length === 0) {
|
|
@@ -11669,7 +11956,8 @@ var TaskRunner = class {
|
|
|
11669
11956
|
findings: r.findings,
|
|
11670
11957
|
stdout: r.output,
|
|
11671
11958
|
stderr: "",
|
|
11672
|
-
structured: null
|
|
11959
|
+
structured: null,
|
|
11960
|
+
executionFailed: r.executionFailed ?? false
|
|
11673
11961
|
};
|
|
11674
11962
|
}
|
|
11675
11963
|
/**
|
|
@@ -11694,7 +11982,7 @@ var TaskRunner = class {
|
|
|
11694
11982
|
* only if fixable findings exist; persist captured stdout/stderr/context
|
|
11695
11983
|
* via the output store on the way out.
|
|
11696
11984
|
*/
|
|
11697
|
-
async runMechanicalAI(task, startedAt) {
|
|
11985
|
+
async runMechanicalAI(task, startedAt, mode = "fix") {
|
|
11698
11986
|
if (!task.fixSkill) {
|
|
11699
11987
|
return wrap(this.failureResult(task.id, startedAt, "mechanical-ai task missing fixSkill"));
|
|
11700
11988
|
}
|
|
@@ -11716,6 +12004,27 @@ var TaskRunner = class {
|
|
|
11716
12004
|
} catch (err) {
|
|
11717
12005
|
return wrap(this.failureResult(task.id, startedAt, String(err)));
|
|
11718
12006
|
}
|
|
12007
|
+
if (check.executionFailed) {
|
|
12008
|
+
const cls = classifyCheckExecutionFailure(`${check.stdout}
|
|
12009
|
+
${check.stderr}`);
|
|
12010
|
+
if (cls.kind === "unrunnable") {
|
|
12011
|
+
return wrap(
|
|
12012
|
+
this.failureResult(task.id, startedAt, checkExecutionError(check)),
|
|
12013
|
+
captureFromCheck(check)
|
|
12014
|
+
);
|
|
12015
|
+
}
|
|
12016
|
+
if (cls.kind === "precondition") {
|
|
12017
|
+
return wrap(
|
|
12018
|
+
this.skippedResult(task.id, startedAt, cls.reason ?? "precondition not met"),
|
|
12019
|
+
captureFromCheck(check)
|
|
12020
|
+
);
|
|
12021
|
+
}
|
|
12022
|
+
check = {
|
|
12023
|
+
...check,
|
|
12024
|
+
executionFailed: false,
|
|
12025
|
+
findings: check.findings > 0 ? check.findings : recoverFindingsCount(check.stdout)
|
|
12026
|
+
};
|
|
12027
|
+
}
|
|
11719
12028
|
const promptContext = await this.composePromptContext(task);
|
|
11720
12029
|
const baseCaptured = {
|
|
11721
12030
|
stdout: check.stdout,
|
|
@@ -11724,13 +12033,14 @@ var TaskRunner = class {
|
|
|
11724
12033
|
...promptContext ? { context: promptContext } : {}
|
|
11725
12034
|
};
|
|
11726
12035
|
const wakeAgentExplicitlyFalse = check.structured !== null && typeof check.structured === "object" && check.structured.wakeAgent === false;
|
|
11727
|
-
if (check.findings === 0 || wakeAgentExplicitlyFalse) {
|
|
12036
|
+
if (check.findings === 0 || wakeAgentExplicitlyFalse || mode === "report") {
|
|
12037
|
+
const reportedWithFindings = mode === "report" && check.findings > 0;
|
|
11728
12038
|
return {
|
|
11729
12039
|
result: {
|
|
11730
12040
|
taskId: task.id,
|
|
11731
12041
|
startedAt,
|
|
11732
12042
|
completedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11733
|
-
status: "no-issues",
|
|
12043
|
+
status: reportedWithFindings ? "success" : "no-issues",
|
|
11734
12044
|
findings: check.findings,
|
|
11735
12045
|
fixed: 0,
|
|
11736
12046
|
prUrl: null,
|
|
@@ -11800,7 +12110,19 @@ var TaskRunner = class {
|
|
|
11800
12110
|
/**
|
|
11801
12111
|
* Pure-AI: always dispatch agent with configured skill.
|
|
11802
12112
|
*/
|
|
11803
|
-
async runPureAI(task, startedAt) {
|
|
12113
|
+
async runPureAI(task, startedAt, mode = "fix") {
|
|
12114
|
+
if (mode === "report") {
|
|
12115
|
+
return {
|
|
12116
|
+
taskId: task.id,
|
|
12117
|
+
startedAt,
|
|
12118
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12119
|
+
status: "no-issues",
|
|
12120
|
+
findings: 0,
|
|
12121
|
+
fixed: 0,
|
|
12122
|
+
prUrl: null,
|
|
12123
|
+
prUpdated: false
|
|
12124
|
+
};
|
|
12125
|
+
}
|
|
11804
12126
|
if (!task.fixSkill) {
|
|
11805
12127
|
return this.failureResult(task.id, startedAt, "pure-ai task missing fixSkill");
|
|
11806
12128
|
}
|
|
@@ -11874,6 +12196,27 @@ var TaskRunner = class {
|
|
|
11874
12196
|
return wrap(this.failureResult(task.id, startedAt, String(err)));
|
|
11875
12197
|
}
|
|
11876
12198
|
const parsed = parseStatusLine(check.stdout);
|
|
12199
|
+
if (parsed === null && check.executionFailed) {
|
|
12200
|
+
const cls = classifyCheckExecutionFailure(`${check.stdout}
|
|
12201
|
+
${check.stderr}`);
|
|
12202
|
+
if (cls.kind === "unrunnable") {
|
|
12203
|
+
return wrap(
|
|
12204
|
+
this.failureResult(task.id, startedAt, checkExecutionError(check)),
|
|
12205
|
+
captureFromCheck(check)
|
|
12206
|
+
);
|
|
12207
|
+
}
|
|
12208
|
+
if (cls.kind === "precondition") {
|
|
12209
|
+
return wrap(
|
|
12210
|
+
this.skippedResult(task.id, startedAt, cls.reason ?? "precondition not met"),
|
|
12211
|
+
captureFromCheck(check)
|
|
12212
|
+
);
|
|
12213
|
+
}
|
|
12214
|
+
check = {
|
|
12215
|
+
...check,
|
|
12216
|
+
executionFailed: false,
|
|
12217
|
+
findings: check.findings > 0 ? check.findings : recoverFindingsCount(check.stdout)
|
|
12218
|
+
};
|
|
12219
|
+
}
|
|
11877
12220
|
const status = parsed?.status ?? "success";
|
|
11878
12221
|
const findings = parsed === null ? check.findings : typeof parsed.candidatesFound === "number" ? parsed.candidatesFound : 0;
|
|
11879
12222
|
const result = {
|
|
@@ -11907,7 +12250,20 @@ var TaskRunner = class {
|
|
|
11907
12250
|
* Hermes Phase 2: a `checkScript` may replace `checkCommand` for housekeeping
|
|
11908
12251
|
* tasks; the runner falls through to the same JSON-status parsing path.
|
|
11909
12252
|
*/
|
|
11910
|
-
async runHousekeeping(task, startedAt) {
|
|
12253
|
+
async runHousekeeping(task, startedAt, mode = "fix") {
|
|
12254
|
+
if (mode === "report") {
|
|
12255
|
+
return wrap({
|
|
12256
|
+
taskId: task.id,
|
|
12257
|
+
startedAt,
|
|
12258
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12259
|
+
status: "skipped",
|
|
12260
|
+
findings: 0,
|
|
12261
|
+
fixed: 0,
|
|
12262
|
+
prUrl: null,
|
|
12263
|
+
prUpdated: false,
|
|
12264
|
+
error: "skipped in report mode: housekeeping may mutate and report runs are read-only"
|
|
12265
|
+
});
|
|
12266
|
+
}
|
|
11911
12267
|
if (!task.checkCommand && !task.checkScript) {
|
|
11912
12268
|
return wrap(
|
|
11913
12269
|
this.failureResult(
|
|
@@ -11974,10 +12330,86 @@ var TaskRunner = class {
|
|
|
11974
12330
|
error
|
|
11975
12331
|
};
|
|
11976
12332
|
}
|
|
12333
|
+
/**
|
|
12334
|
+
* A precondition-gated check (e.g. `predict` with <3 snapshots, or a
|
|
12335
|
+
* graph-backed check before `harness scan`). The command is correctly
|
|
12336
|
+
* configured; the repo just lacks the state it needs. Reported as `skipped`
|
|
12337
|
+
* — distinct from a hard `failure` — carrying the refusal line as the reason
|
|
12338
|
+
* (surfaced in the run-report summary column). ADR 0050.
|
|
12339
|
+
*/
|
|
12340
|
+
skippedResult(taskId, startedAt, reason) {
|
|
12341
|
+
return {
|
|
12342
|
+
taskId,
|
|
12343
|
+
startedAt,
|
|
12344
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12345
|
+
status: "skipped",
|
|
12346
|
+
findings: 0,
|
|
12347
|
+
fixed: 0,
|
|
12348
|
+
prUrl: null,
|
|
12349
|
+
prUpdated: false,
|
|
12350
|
+
error: reason
|
|
12351
|
+
};
|
|
12352
|
+
}
|
|
11977
12353
|
};
|
|
11978
12354
|
function wrap(result, captured) {
|
|
11979
12355
|
return captured ? { result, captured } : { result };
|
|
11980
12356
|
}
|
|
12357
|
+
function checkExecutionError(check) {
|
|
12358
|
+
const detail = (check.stderr || check.stdout || "").trim();
|
|
12359
|
+
return detail ? `check command failed to execute: ${detail}` : "check command failed to execute";
|
|
12360
|
+
}
|
|
12361
|
+
function captureFromCheck(check) {
|
|
12362
|
+
return { stdout: check.stdout, stderr: check.stderr, structured: check.structured };
|
|
12363
|
+
}
|
|
12364
|
+
var PRECONDITION_PATTERNS = [
|
|
12365
|
+
/requires at least \d+ snapshots?/i,
|
|
12366
|
+
/Run ["'`]?harness snapshot/i,
|
|
12367
|
+
/no knowledge graph found/i,
|
|
12368
|
+
/Run ["'`]?harness scan/i,
|
|
12369
|
+
/no graph (?:available|found)/i
|
|
12370
|
+
];
|
|
12371
|
+
var UNRUNNABLE_PATTERNS = [
|
|
12372
|
+
/unknown command/i,
|
|
12373
|
+
/unknown option/i,
|
|
12374
|
+
/\bENOENT\b/,
|
|
12375
|
+
/command not found/i,
|
|
12376
|
+
/cannot find module/i,
|
|
12377
|
+
// A timed-out check did not complete — it is a hard failure, never a
|
|
12378
|
+
// "ran-no-count" success. The runners synthesize this message on SIGTERM.
|
|
12379
|
+
/timed out/i,
|
|
12380
|
+
/\bETIMEDOUT\b/
|
|
12381
|
+
];
|
|
12382
|
+
var TIMEOUT_SIGNATURE = /check timed out after \d+\s*ms/i;
|
|
12383
|
+
function explicitFindingsCount(output) {
|
|
12384
|
+
const after = output.match(/(?:findings?|issues?|violations?|errors?)\s*[:=]\s*(\d+)/i);
|
|
12385
|
+
if (after) return parseInt(after[1], 10);
|
|
12386
|
+
const before = output.match(/(\d+)\s+(?:findings?|issues?|violations?|errors?)\b/i);
|
|
12387
|
+
if (before) return parseInt(before[1], 10);
|
|
12388
|
+
return null;
|
|
12389
|
+
}
|
|
12390
|
+
function recoverFindingsCount(output) {
|
|
12391
|
+
return explicitFindingsCount(output) ?? 1;
|
|
12392
|
+
}
|
|
12393
|
+
function firstMeaningfulLine(output) {
|
|
12394
|
+
const line = output.split("\n").map((l) => l.trim()).find(Boolean);
|
|
12395
|
+
if (!line) return "precondition not met";
|
|
12396
|
+
return line.replace(/^[x✗✓!-]\s*/u, "").trim();
|
|
12397
|
+
}
|
|
12398
|
+
var CLASSIFY_HEAD_LINES = 3;
|
|
12399
|
+
function classifyCheckExecutionFailure(output) {
|
|
12400
|
+
const text = (output ?? "").trim();
|
|
12401
|
+
if (text.length === 0) return { kind: "unrunnable" };
|
|
12402
|
+
if (TIMEOUT_SIGNATURE.test(text)) return { kind: "unrunnable" };
|
|
12403
|
+
if (explicitFindingsCount(text) !== null) return { kind: "ran-no-count" };
|
|
12404
|
+
const head = text.split("\n").filter((l) => l.trim()).slice(0, CLASSIFY_HEAD_LINES).join("\n");
|
|
12405
|
+
for (const re of PRECONDITION_PATTERNS) {
|
|
12406
|
+
if (re.test(head)) return { kind: "precondition", reason: firstMeaningfulLine(text) };
|
|
12407
|
+
}
|
|
12408
|
+
for (const re of UNRUNNABLE_PATTERNS) {
|
|
12409
|
+
if (re.test(head)) return { kind: "unrunnable" };
|
|
12410
|
+
}
|
|
12411
|
+
return { kind: "ran-no-count" };
|
|
12412
|
+
}
|
|
11981
12413
|
function parseStatusLine(output) {
|
|
11982
12414
|
const lines = output.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
11983
12415
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
@@ -12126,8 +12558,49 @@ function heuristicResult(stdout, stderr, exitedAbnormally) {
|
|
|
12126
12558
|
};
|
|
12127
12559
|
}
|
|
12128
12560
|
|
|
12561
|
+
// src/maintenance/check-runner.ts
|
|
12562
|
+
var import_node_child_process12 = require("child_process");
|
|
12563
|
+
var import_node_util4 = require("util");
|
|
12564
|
+
var MAINTENANCE_CHECK_MAX_BUFFER = 64 * 1024 * 1024;
|
|
12565
|
+
var MAINTENANCE_CHECK_TIMEOUT_MS = 3e5;
|
|
12566
|
+
var FINDINGS_RE = /(\d+)\s+(?:finding|issue|violation|error)/i;
|
|
12567
|
+
var nodeExecFileAsync = (0, import_node_util4.promisify)(import_node_child_process12.execFile);
|
|
12568
|
+
function isCheckTimeoutError(e) {
|
|
12569
|
+
return e.killed === true || e.signal === "SIGTERM" || e.code === "ETIMEDOUT";
|
|
12570
|
+
}
|
|
12571
|
+
async function runHarnessCheck(spawn7, cwd, opts = {}) {
|
|
12572
|
+
const execFileAsync2 = opts.execFileAsync ?? nodeExecFileAsync;
|
|
12573
|
+
const timeoutMs = opts.timeoutMs ?? MAINTENANCE_CHECK_TIMEOUT_MS;
|
|
12574
|
+
const maxBuffer = opts.maxBuffer ?? MAINTENANCE_CHECK_MAX_BUFFER;
|
|
12575
|
+
try {
|
|
12576
|
+
const { stdout } = await execFileAsync2(spawn7.file, spawn7.args, {
|
|
12577
|
+
cwd,
|
|
12578
|
+
timeout: timeoutMs,
|
|
12579
|
+
maxBuffer
|
|
12580
|
+
});
|
|
12581
|
+
const text = String(stdout);
|
|
12582
|
+
const m = text.match(FINDINGS_RE);
|
|
12583
|
+
const findings = m ? parseInt(m[1], 10) : 0;
|
|
12584
|
+
return { passed: findings === 0, findings, output: text, executionFailed: false };
|
|
12585
|
+
} catch (err) {
|
|
12586
|
+
const e = err;
|
|
12587
|
+
let output = [e.stdout, e.stderr].map((v) => v == null ? "" : String(v)).filter(Boolean).join("\n");
|
|
12588
|
+
if (isCheckTimeoutError(e)) {
|
|
12589
|
+
const note = `check timed out after ${timeoutMs}ms`;
|
|
12590
|
+
output = output.trim() ? `${output}
|
|
12591
|
+
${note}` : note;
|
|
12592
|
+
return { passed: false, findings: 0, output, executionFailed: true };
|
|
12593
|
+
}
|
|
12594
|
+
const m = output.match(FINDINGS_RE);
|
|
12595
|
+
if (m) {
|
|
12596
|
+
return { passed: false, findings: parseInt(m[1], 10), output, executionFailed: false };
|
|
12597
|
+
}
|
|
12598
|
+
return { passed: false, findings: 0, output, executionFailed: true };
|
|
12599
|
+
}
|
|
12600
|
+
}
|
|
12601
|
+
|
|
12129
12602
|
// src/maintenance/output-store.ts
|
|
12130
|
-
var
|
|
12603
|
+
var fs15 = __toESM(require("fs"));
|
|
12131
12604
|
var path20 = __toESM(require("path"));
|
|
12132
12605
|
var DEFAULT_RETENTION = {
|
|
12133
12606
|
runs: 50,
|
|
@@ -12168,13 +12641,13 @@ var TaskOutputStore = class {
|
|
|
12168
12641
|
async write(taskId, entry, retention) {
|
|
12169
12642
|
this.ensureSafeTaskId(taskId);
|
|
12170
12643
|
const dir = this.dirFor(taskId);
|
|
12171
|
-
await
|
|
12644
|
+
await fs15.promises.mkdir(dir, { recursive: true });
|
|
12172
12645
|
const fileName = `${sanitizeIso(entry.completedAt || (/* @__PURE__ */ new Date()).toISOString())}.json`;
|
|
12173
12646
|
const filePath = path20.join(dir, fileName);
|
|
12174
12647
|
const tmpPath = `${filePath}.tmp`;
|
|
12175
12648
|
const payload = JSON.stringify(entry, null, 2);
|
|
12176
|
-
await
|
|
12177
|
-
await
|
|
12649
|
+
await fs15.promises.writeFile(tmpPath, payload, "utf-8");
|
|
12650
|
+
await fs15.promises.rename(tmpPath, filePath);
|
|
12178
12651
|
try {
|
|
12179
12652
|
await this.applyRetention(taskId, retention);
|
|
12180
12653
|
} catch (err) {
|
|
@@ -12225,7 +12698,7 @@ var TaskOutputStore = class {
|
|
|
12225
12698
|
}
|
|
12226
12699
|
async readEntry(filePath) {
|
|
12227
12700
|
try {
|
|
12228
|
-
const buf = await
|
|
12701
|
+
const buf = await fs15.promises.readFile(filePath, "utf-8");
|
|
12229
12702
|
const parsed = JSON.parse(buf);
|
|
12230
12703
|
return parsed;
|
|
12231
12704
|
} catch {
|
|
@@ -12247,7 +12720,7 @@ var TaskOutputStore = class {
|
|
|
12247
12720
|
const toRemove = /* @__PURE__ */ new Set([...overflow, ...aged]);
|
|
12248
12721
|
for (const name of toRemove) {
|
|
12249
12722
|
try {
|
|
12250
|
-
await
|
|
12723
|
+
await fs15.promises.unlink(path20.join(dir, name));
|
|
12251
12724
|
} catch {
|
|
12252
12725
|
}
|
|
12253
12726
|
}
|
|
@@ -12256,7 +12729,7 @@ var TaskOutputStore = class {
|
|
|
12256
12729
|
async function listJsonFilesDescending(dir) {
|
|
12257
12730
|
let names;
|
|
12258
12731
|
try {
|
|
12259
|
-
names = await
|
|
12732
|
+
names = await fs15.promises.readdir(dir);
|
|
12260
12733
|
} catch {
|
|
12261
12734
|
return [];
|
|
12262
12735
|
}
|
|
@@ -12365,7 +12838,7 @@ var fallbackLogger3 = {
|
|
|
12365
12838
|
};
|
|
12366
12839
|
|
|
12367
12840
|
// src/maintenance/custom-task-validator.ts
|
|
12368
|
-
var
|
|
12841
|
+
var import_types31 = require("@harness-engineering/types");
|
|
12369
12842
|
var ID_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
|
|
12370
12843
|
var REQUIRED_FIELDS_BY_TYPE = {
|
|
12371
12844
|
"mechanical-ai": ["branch", "fixSkill"],
|
|
@@ -12375,7 +12848,7 @@ var REQUIRED_FIELDS_BY_TYPE = {
|
|
|
12375
12848
|
};
|
|
12376
12849
|
function validateCustomTasks(customTasks, builtIns, deps = {}) {
|
|
12377
12850
|
const errors = [];
|
|
12378
|
-
if (!customTasks) return (0,
|
|
12851
|
+
if (!customTasks) return (0, import_types31.Ok)(void 0);
|
|
12379
12852
|
const builtInIds = new Set(builtIns.map((t) => t.id));
|
|
12380
12853
|
const customIds = Object.keys(customTasks);
|
|
12381
12854
|
const allIds = /* @__PURE__ */ new Set([...builtInIds, ...customIds]);
|
|
@@ -12385,7 +12858,7 @@ function validateCustomTasks(customTasks, builtIns, deps = {}) {
|
|
|
12385
12858
|
validateOne(id, task, builtInIds, allIds, deps, errors);
|
|
12386
12859
|
}
|
|
12387
12860
|
detectCycles(customTasks, builtIns, errors);
|
|
12388
|
-
return errors.length === 0 ? (0,
|
|
12861
|
+
return errors.length === 0 ? (0, import_types31.Ok)(void 0) : (0, import_types31.Err)(errors);
|
|
12389
12862
|
}
|
|
12390
12863
|
function validateOne(id, task, builtInIds, allIds, deps, errors) {
|
|
12391
12864
|
const prefix = `customTasks.${id}`;
|
|
@@ -12574,6 +13047,11 @@ function deriveSeedPaths(config) {
|
|
|
12574
13047
|
const roadmapPath = config.tracker.kind === "roadmap" && config.tracker.filePath ? config.tracker.filePath : "docs/roadmap.md";
|
|
12575
13048
|
return [".harness/proposals", roadmapPath];
|
|
12576
13049
|
}
|
|
13050
|
+
function normalizeHarnessCommand(command) {
|
|
13051
|
+
if (command.length === 0) return [];
|
|
13052
|
+
if (command[0] === "harness") return command;
|
|
13053
|
+
return ["harness", ...command];
|
|
13054
|
+
}
|
|
12577
13055
|
var Orchestrator = class extends import_node_events.EventEmitter {
|
|
12578
13056
|
state;
|
|
12579
13057
|
config;
|
|
@@ -12700,6 +13178,11 @@ var Orchestrator = class extends import_node_events.EventEmitter {
|
|
|
12700
13178
|
abortControllers = /* @__PURE__ */ new Map();
|
|
12701
13179
|
/** Guards against overlapping ticks when a tick takes longer than the polling interval */
|
|
12702
13180
|
tickInProgress = false;
|
|
13181
|
+
/** Phase 4 (DLane-5): lanes read back from the durable log on first tick, for
|
|
13182
|
+
* observability only — NOT fed into reconciliation. Empty until the first tick. */
|
|
13183
|
+
persistedLanes = { tasks: {} };
|
|
13184
|
+
/** Ensures the lane read-back diagnostic runs at most once (first tick). */
|
|
13185
|
+
laneReadbackDone = false;
|
|
12703
13186
|
/** Timestamp of the last stale branch sweep (at most once per hour) */
|
|
12704
13187
|
lastBranchSweepMs = 0;
|
|
12705
13188
|
/** Current tick-phase activity visible to the dashboard */
|
|
@@ -12773,7 +13256,7 @@ var Orchestrator = class extends import_node_events.EventEmitter {
|
|
|
12773
13256
|
this.localResolvers.set(name, new LocalModelResolver(resolverOpts));
|
|
12774
13257
|
}
|
|
12775
13258
|
}
|
|
12776
|
-
this.cacheMetrics = new
|
|
13259
|
+
this.cacheMetrics = new import_core17.CacheMetricsRecorder();
|
|
12777
13260
|
if (this.config.agent.backends !== void 0 && Object.keys(this.config.agent.backends).length > 0) {
|
|
12778
13261
|
const sandboxPolicy = this.config.agent.sandboxPolicy === "docker" ? "docker" : "none";
|
|
12779
13262
|
const firstBackendName = Object.keys(this.config.agent.backends)[0];
|
|
@@ -12859,7 +13342,7 @@ var Orchestrator = class extends import_node_events.EventEmitter {
|
|
|
12859
13342
|
this.setupNotifications(config.notifications);
|
|
12860
13343
|
const otlpCfg = config.telemetry?.export?.otlp;
|
|
12861
13344
|
if (otlpCfg) {
|
|
12862
|
-
this.otlpExporter = new
|
|
13345
|
+
this.otlpExporter = new import_core17.OTLPExporter({
|
|
12863
13346
|
endpoint: otlpCfg.endpoint,
|
|
12864
13347
|
...otlpCfg.enabled !== void 0 ? { enabled: otlpCfg.enabled } : {},
|
|
12865
13348
|
...otlpCfg.headers !== void 0 ? { headers: otlpCfg.headers } : {},
|
|
@@ -12928,7 +13411,7 @@ var Orchestrator = class extends import_node_events.EventEmitter {
|
|
|
12928
13411
|
...this.config.tracker.apiKey ? { token: this.config.tracker.apiKey } : {},
|
|
12929
13412
|
...this.config.tracker.endpoint ? { apiBase: this.config.tracker.endpoint } : {}
|
|
12930
13413
|
};
|
|
12931
|
-
const clientResult = (0,
|
|
13414
|
+
const clientResult = (0, import_core16.createTrackerClient)(trackerCfg);
|
|
12932
13415
|
if (!clientResult.ok) throw clientResult.error;
|
|
12933
13416
|
return new GitHubIssuesIssueTrackerAdapter(clientResult.value, this.config.tracker);
|
|
12934
13417
|
}
|
|
@@ -12946,48 +13429,30 @@ var Orchestrator = class extends import_node_events.EventEmitter {
|
|
|
12946
13429
|
const logger = this.logger;
|
|
12947
13430
|
const checkRunner = {
|
|
12948
13431
|
run: async (command, cwd) => {
|
|
12949
|
-
const
|
|
12950
|
-
|
|
12951
|
-
|
|
12952
|
-
const [cmd, ...args] = command;
|
|
12953
|
-
if (!cmd) return { passed: true, findings: 0, output: "" };
|
|
12954
|
-
try {
|
|
12955
|
-
const { stdout } = await execFileAsync2(cmd, args, { cwd, timeout: 12e4 });
|
|
12956
|
-
const findingsMatch = stdout.match(/(\d+)\s+(?:finding|issue|violation|error)/i);
|
|
12957
|
-
const findings = findingsMatch ? parseInt(findingsMatch[1], 10) : 0;
|
|
12958
|
-
return { passed: findings === 0, findings, output: stdout };
|
|
12959
|
-
} catch (err) {
|
|
12960
|
-
const error = err;
|
|
12961
|
-
const output = [error.stdout, error.stderr].filter(Boolean).join("\n");
|
|
12962
|
-
const findingsMatch = output.match(/(\d+)\s+(?:finding|issue|violation|error)/i);
|
|
12963
|
-
const findings = findingsMatch ? parseInt(findingsMatch[1], 10) : 1;
|
|
12964
|
-
return { passed: false, findings, output };
|
|
12965
|
-
}
|
|
12966
|
-
}
|
|
12967
|
-
};
|
|
12968
|
-
const agentDispatcher = {
|
|
12969
|
-
dispatch: async (skill, branch, backendName, cwd) => {
|
|
12970
|
-
logger.info(
|
|
12971
|
-
"Maintenance agent dispatcher invoked (stub \u2014 skill dispatch integration pending)",
|
|
12972
|
-
{
|
|
12973
|
-
skill,
|
|
12974
|
-
branch,
|
|
12975
|
-
backendName,
|
|
12976
|
-
cwd
|
|
12977
|
-
}
|
|
12978
|
-
);
|
|
12979
|
-
return { producedCommits: false, fixed: 0 };
|
|
13432
|
+
const [cmd, ...args] = normalizeHarnessCommand(command);
|
|
13433
|
+
if (!cmd) return { passed: true, findings: 0, output: "", executionFailed: false };
|
|
13434
|
+
return runHarnessCheck({ file: cmd, args }, cwd);
|
|
12980
13435
|
}
|
|
12981
13436
|
};
|
|
13437
|
+
const resolveBackend2 = makeBackendResolver(this.getBackends());
|
|
13438
|
+
const agentDispatcher = createAgentDispatcher({
|
|
13439
|
+
resolveBackend: resolveBackend2,
|
|
13440
|
+
git: (args, cwd) => (0, import_node_child_process13.execFileSync)("git", args, { cwd, encoding: "utf-8" }).toString().trim(),
|
|
13441
|
+
logger
|
|
13442
|
+
});
|
|
12982
13443
|
const commandExecutor = {
|
|
12983
13444
|
exec: async (command, cwd) => {
|
|
12984
13445
|
const { execFile: execFile7 } = await import("child_process");
|
|
12985
|
-
const { promisify:
|
|
12986
|
-
const execFileAsync2 =
|
|
12987
|
-
const [cmd, ...args] = command;
|
|
13446
|
+
const { promisify: promisify6 } = await import("util");
|
|
13447
|
+
const execFileAsync2 = promisify6(execFile7);
|
|
13448
|
+
const [cmd, ...args] = normalizeHarnessCommand(command);
|
|
12988
13449
|
if (!cmd) return { stdout: "" };
|
|
12989
13450
|
try {
|
|
12990
|
-
const { stdout } = await execFileAsync2(cmd, args, {
|
|
13451
|
+
const { stdout } = await execFileAsync2(cmd, args, {
|
|
13452
|
+
cwd,
|
|
13453
|
+
timeout: MAINTENANCE_CHECK_TIMEOUT_MS,
|
|
13454
|
+
maxBuffer: MAINTENANCE_CHECK_MAX_BUFFER
|
|
13455
|
+
});
|
|
12991
13456
|
return { stdout: String(stdout) };
|
|
12992
13457
|
} catch (err) {
|
|
12993
13458
|
logger.warn("Maintenance command execution failed", {
|
|
@@ -13122,6 +13587,10 @@ ${messages}`);
|
|
|
13122
13587
|
async asyncTick() {
|
|
13123
13588
|
await this.ensureClaimManager();
|
|
13124
13589
|
await this.intelligenceRunner.loadPersistedData();
|
|
13590
|
+
if (!this.laneReadbackDone) {
|
|
13591
|
+
this.laneReadbackDone = true;
|
|
13592
|
+
await this.readBackPersistedLanes();
|
|
13593
|
+
}
|
|
13125
13594
|
const nowMs = Date.now();
|
|
13126
13595
|
this.setTickActivity("fetching", "Polling tracker for candidates");
|
|
13127
13596
|
const candidatesResult = await this.tracker.fetchCandidateIssues();
|
|
@@ -13274,12 +13743,48 @@ ${messages}`);
|
|
|
13274
13743
|
break;
|
|
13275
13744
|
case "escalate":
|
|
13276
13745
|
await this.handleEscalation(effect);
|
|
13746
|
+
await this.persistLaneSafe(effect.issueId, "abandon");
|
|
13277
13747
|
break;
|
|
13278
13748
|
case "claim":
|
|
13279
13749
|
await this.handleClaimEffect(effect);
|
|
13280
13750
|
break;
|
|
13281
13751
|
}
|
|
13282
13752
|
}
|
|
13753
|
+
/**
|
|
13754
|
+
* Phase 4 (DLane-5): persist an orchestrator lane transition to the durable
|
|
13755
|
+
* core event log at the effect boundary. NEVER throws — `persistLane` returns
|
|
13756
|
+
* an `Err` Result on failure, which is logged and swallowed here so a
|
|
13757
|
+
* lane-persistence failure can never break orchestrator dispatch.
|
|
13758
|
+
*/
|
|
13759
|
+
async persistLaneSafe(issueId, signal) {
|
|
13760
|
+
const r = await persistLane(this.projectRoot, issueId, signal);
|
|
13761
|
+
if (!r.ok) {
|
|
13762
|
+
this.logger.warn(`lane persist failed for ${issueId} (${signal}): ${r.error.message}`);
|
|
13763
|
+
}
|
|
13764
|
+
}
|
|
13765
|
+
/**
|
|
13766
|
+
* Phase 4 (DLane-5): read persisted task lanes back from the durable log and
|
|
13767
|
+
* log a one-line summary. Stores the projection on `this.persistedLanes` for
|
|
13768
|
+
* observability. Read-only — never feeds reconciliation, never throws.
|
|
13769
|
+
*/
|
|
13770
|
+
async readBackPersistedLanes() {
|
|
13771
|
+
const lanes = await readPersistedLanes(this.projectRoot);
|
|
13772
|
+
this.persistedLanes = lanes;
|
|
13773
|
+
const entries = Object.keys(lanes.tasks).map((id) => `${id}:${lanes.tasks[id]?.lane}`);
|
|
13774
|
+
const nonTerminal = entries.filter((e) => !e.endsWith(":done") && !e.endsWith(":canceled"));
|
|
13775
|
+
this.logger.info(
|
|
13776
|
+
`Lane read-back on startup: ${entries.length} persisted task(s), ${nonTerminal.length} non-terminal`,
|
|
13777
|
+
{ nonTerminal }
|
|
13778
|
+
);
|
|
13779
|
+
}
|
|
13780
|
+
/**
|
|
13781
|
+
* Phase 4 (DLane-5): the task lanes most recently read back from the durable
|
|
13782
|
+
* log on startup, exposed for external observability. Read-only — a fresh
|
|
13783
|
+
* `{ tasks: {} }` until the first tick's read-back has run.
|
|
13784
|
+
*/
|
|
13785
|
+
getPersistedLanes() {
|
|
13786
|
+
return this.persistedLanes;
|
|
13787
|
+
}
|
|
13283
13788
|
/**
|
|
13284
13789
|
* Guards workspace cleanup by checking whether the agent pushed a branch
|
|
13285
13790
|
* that does not yet have a pull request. If so, the worktree is preserved
|
|
@@ -13312,7 +13817,7 @@ ${messages}`);
|
|
|
13312
13817
|
{ issueId }
|
|
13313
13818
|
);
|
|
13314
13819
|
await this.interactionQueue.push({
|
|
13315
|
-
id: `interaction-${(0,
|
|
13820
|
+
id: `interaction-${(0, import_node_crypto15.randomUUID)()}`,
|
|
13316
13821
|
issueId,
|
|
13317
13822
|
type: "needs-human",
|
|
13318
13823
|
reasons: [`Agent pushed branch "${branch}" but did not create a PR. Worktree preserved.`],
|
|
@@ -13388,7 +13893,7 @@ ${messages}`);
|
|
|
13388
13893
|
{ issueId: effect.issueId }
|
|
13389
13894
|
);
|
|
13390
13895
|
await this.interactionQueue.push({
|
|
13391
|
-
id: `interaction-${(0,
|
|
13896
|
+
id: `interaction-${(0, import_node_crypto15.randomUUID)()}`,
|
|
13392
13897
|
issueId: effect.issueId,
|
|
13393
13898
|
type: "needs-human",
|
|
13394
13899
|
reasons: effect.reasons,
|
|
@@ -13466,6 +13971,7 @@ ${messages}`);
|
|
|
13466
13971
|
}
|
|
13467
13972
|
return;
|
|
13468
13973
|
}
|
|
13974
|
+
await this.persistLaneSafe(effect.issue.id, "claim");
|
|
13469
13975
|
await this.postClaimComment(effect.issue);
|
|
13470
13976
|
await this.dispatchIssue(effect.issue, effect.attempt, effect.backend);
|
|
13471
13977
|
}
|
|
@@ -13484,12 +13990,12 @@ ${messages}`);
|
|
|
13484
13990
|
async postLifecycleComment(identifier, externalId, event) {
|
|
13485
13991
|
try {
|
|
13486
13992
|
if (!externalId) return;
|
|
13487
|
-
const trackerConfig = (0,
|
|
13993
|
+
const trackerConfig = (0, import_core16.loadTrackerSyncConfig)(this.projectRoot);
|
|
13488
13994
|
if (!trackerConfig) return;
|
|
13489
13995
|
const token = process.env.GITHUB_TOKEN;
|
|
13490
13996
|
if (!token) return;
|
|
13491
13997
|
const orchestratorId = await this.orchestratorIdPromise;
|
|
13492
|
-
const adapter = new
|
|
13998
|
+
const adapter = new import_core16.GitHubIssuesSyncAdapter({ token, config: trackerConfig });
|
|
13493
13999
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19);
|
|
13494
14000
|
const actionMap = {
|
|
13495
14001
|
claimed: "Dispatching agent for autonomous execution",
|
|
@@ -13526,6 +14032,7 @@ ${messages}`);
|
|
|
13526
14032
|
this.logger.info(`Dispatching issue: ${issue.identifier} (attempt ${attempt})`, {
|
|
13527
14033
|
issueId: issue.id
|
|
13528
14034
|
});
|
|
14035
|
+
await this.persistLaneSafe(issue.id, "dispatch");
|
|
13529
14036
|
try {
|
|
13530
14037
|
const workspaceResult = await this.workspace.ensureWorkspace(issue.identifier);
|
|
13531
14038
|
if (!workspaceResult.ok) throw workspaceResult.error;
|
|
@@ -13562,7 +14069,7 @@ ${messages}`);
|
|
|
13562
14069
|
...f.line !== void 0 ? { line: f.line } : {}
|
|
13563
14070
|
}))
|
|
13564
14071
|
);
|
|
13565
|
-
(0,
|
|
14072
|
+
(0, import_core15.writeTaint)(
|
|
13566
14073
|
workspacePath,
|
|
13567
14074
|
issue.id,
|
|
13568
14075
|
"Medium-severity injection patterns found in workspace config files",
|
|
@@ -13734,6 +14241,7 @@ ${messages}`);
|
|
|
13734
14241
|
* Informs the state machine that an agent worker has exited.
|
|
13735
14242
|
*/
|
|
13736
14243
|
async emitWorkerExit(issueId, reason, attempt, error) {
|
|
14244
|
+
await this.persistLaneSafe(issueId, reason === "normal" ? "success" : "failure");
|
|
13737
14245
|
await this.completionHandler.handleWorkerExit(
|
|
13738
14246
|
issueId,
|
|
13739
14247
|
reason,
|
|
@@ -14269,11 +14777,11 @@ function launchTUI(orchestrator) {
|
|
|
14269
14777
|
}
|
|
14270
14778
|
|
|
14271
14779
|
// src/maintenance/sync-main.ts
|
|
14272
|
-
var
|
|
14273
|
-
var
|
|
14780
|
+
var import_node_child_process14 = require("child_process");
|
|
14781
|
+
var import_node_util5 = require("util");
|
|
14274
14782
|
var DEFAULT_TIMEOUT_MS3 = 6e4;
|
|
14275
14783
|
async function git(execFileFn, args, cwd, timeoutMs) {
|
|
14276
|
-
const exec = (0,
|
|
14784
|
+
const exec = (0, import_node_util5.promisify)(execFileFn);
|
|
14277
14785
|
const { stdout, stderr } = await exec("git", args, { cwd, timeout: timeoutMs });
|
|
14278
14786
|
return { stdout: String(stdout), stderr: String(stderr) };
|
|
14279
14787
|
}
|
|
@@ -14335,7 +14843,7 @@ async function isAncestor(execFileFn, a, b, cwd, timeoutMs) {
|
|
|
14335
14843
|
}
|
|
14336
14844
|
}
|
|
14337
14845
|
async function syncMain(repoRoot, opts = {}) {
|
|
14338
|
-
const execFileFn = opts.execFileFn ??
|
|
14846
|
+
const execFileFn = opts.execFileFn ?? import_node_child_process14.execFile;
|
|
14339
14847
|
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS3;
|
|
14340
14848
|
try {
|
|
14341
14849
|
const originRef = await resolveOriginDefault(execFileFn, repoRoot, timeoutMs);
|
|
@@ -14412,11 +14920,59 @@ async function syncMain(repoRoot, opts = {}) {
|
|
|
14412
14920
|
}
|
|
14413
14921
|
}
|
|
14414
14922
|
|
|
14923
|
+
// src/maintenance/overdue.ts
|
|
14924
|
+
var LOOKBACK_DAYS = 366;
|
|
14925
|
+
function previousFireTime(schedule, now) {
|
|
14926
|
+
let day = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
14927
|
+
for (let d = 0; d <= LOOKBACK_DAYS; d++) {
|
|
14928
|
+
if (cronMatchesDate(schedule, day)) {
|
|
14929
|
+
const dayStartMs = day.getTime();
|
|
14930
|
+
const scanStart = d === 0 ? new Date(
|
|
14931
|
+
now.getFullYear(),
|
|
14932
|
+
now.getMonth(),
|
|
14933
|
+
now.getDate(),
|
|
14934
|
+
now.getHours(),
|
|
14935
|
+
now.getMinutes()
|
|
14936
|
+
) : new Date(day.getFullYear(), day.getMonth(), day.getDate(), 23, 59);
|
|
14937
|
+
for (let ms = scanStart.getTime(); ms >= dayStartMs; ms -= 6e4) {
|
|
14938
|
+
const candidate = new Date(ms);
|
|
14939
|
+
if (cronMatchesNow(schedule, candidate)) return candidate;
|
|
14940
|
+
}
|
|
14941
|
+
}
|
|
14942
|
+
day = new Date(day.getFullYear(), day.getMonth(), day.getDate() - 1);
|
|
14943
|
+
}
|
|
14944
|
+
return null;
|
|
14945
|
+
}
|
|
14946
|
+
function isSatisfyingRun(r) {
|
|
14947
|
+
return r.status === "success" || r.status === "no-issues";
|
|
14948
|
+
}
|
|
14949
|
+
function isOverdue(task, history, now) {
|
|
14950
|
+
const satisfyingRuns = history.filter((r) => r.taskId === task.id && isSatisfyingRun(r));
|
|
14951
|
+
const fire = previousFireTime(task.schedule, now);
|
|
14952
|
+
if (fire === null) return satisfyingRuns.length === 0;
|
|
14953
|
+
const fireMs = fire.getTime();
|
|
14954
|
+
const satisfied = satisfyingRuns.some((r) => new Date(r.completedAt).getTime() >= fireMs);
|
|
14955
|
+
return !satisfied;
|
|
14956
|
+
}
|
|
14957
|
+
function selectTasks(tasks, history, filter) {
|
|
14958
|
+
const eligible = tasks.filter((t) => t.excludeFromHumanSweep !== true);
|
|
14959
|
+
switch (filter.mode) {
|
|
14960
|
+
case "all":
|
|
14961
|
+
return eligible;
|
|
14962
|
+
case "ids": {
|
|
14963
|
+
const wanted = new Set(filter.ids ?? []);
|
|
14964
|
+
return eligible.filter((t) => wanted.has(t.id));
|
|
14965
|
+
}
|
|
14966
|
+
case "overdue":
|
|
14967
|
+
return eligible.filter((t) => isOverdue(t, history, filter.now));
|
|
14968
|
+
}
|
|
14969
|
+
}
|
|
14970
|
+
|
|
14415
14971
|
// src/sessions/search-index.ts
|
|
14416
|
-
var
|
|
14972
|
+
var fs16 = __toESM(require("fs"));
|
|
14417
14973
|
var path22 = __toESM(require("path"));
|
|
14418
14974
|
var import_better_sqlite32 = __toESM(require("better-sqlite3"));
|
|
14419
|
-
var
|
|
14975
|
+
var import_types32 = require("@harness-engineering/types");
|
|
14420
14976
|
var SEARCH_INDEX_FILE = "search-index.sqlite";
|
|
14421
14977
|
var SCHEMA_SQL2 = `
|
|
14422
14978
|
CREATE TABLE IF NOT EXISTS session_docs (
|
|
@@ -14474,7 +15030,7 @@ var SqliteSearchIndex = class {
|
|
|
14474
15030
|
removeSessionStmt;
|
|
14475
15031
|
totalStmt;
|
|
14476
15032
|
constructor(dbPath) {
|
|
14477
|
-
|
|
15033
|
+
fs16.mkdirSync(path22.dirname(dbPath), { recursive: true });
|
|
14478
15034
|
this.db = new import_better_sqlite32.default(dbPath);
|
|
14479
15035
|
this.db.pragma("journal_mode = WAL");
|
|
14480
15036
|
this.db.pragma("synchronous = NORMAL");
|
|
@@ -14574,18 +15130,18 @@ function openSearchIndex(projectPath) {
|
|
|
14574
15130
|
return new SqliteSearchIndex(searchIndexPath(projectPath));
|
|
14575
15131
|
}
|
|
14576
15132
|
function indexSessionDirectory(idx, args) {
|
|
14577
|
-
const kinds = args.fileKinds ?? [...
|
|
15133
|
+
const kinds = args.fileKinds ?? [...import_types32.INDEXED_FILE_KINDS];
|
|
14578
15134
|
const cap = args.maxBytesPerBody ?? 256 * 1024;
|
|
14579
15135
|
let docsWritten = 0;
|
|
14580
15136
|
for (const kind of kinds) {
|
|
14581
15137
|
const fileName = FILE_KIND_TO_FILENAME[kind];
|
|
14582
15138
|
const filePath = path22.join(args.sessionDir, fileName);
|
|
14583
|
-
if (!
|
|
14584
|
-
let body =
|
|
15139
|
+
if (!fs16.existsSync(filePath)) continue;
|
|
15140
|
+
let body = fs16.readFileSync(filePath, "utf8");
|
|
14585
15141
|
if (Buffer.byteLength(body, "utf8") > cap) {
|
|
14586
15142
|
body = body.slice(0, cap) + "\n\n[TRUNCATED]";
|
|
14587
15143
|
}
|
|
14588
|
-
const stat =
|
|
15144
|
+
const stat = fs16.statSync(filePath);
|
|
14589
15145
|
const relPath = path22.relative(args.projectPath, filePath).replaceAll("\\", "/");
|
|
14590
15146
|
idx.upsertSessionDoc({
|
|
14591
15147
|
sessionId: args.sessionId,
|
|
@@ -14607,8 +15163,8 @@ function reindexFromArchive(projectPath, opts = {}) {
|
|
|
14607
15163
|
idx.resetArchived();
|
|
14608
15164
|
let sessionsIndexed = 0;
|
|
14609
15165
|
let docsWritten = 0;
|
|
14610
|
-
if (
|
|
14611
|
-
const entries =
|
|
15166
|
+
if (fs16.existsSync(archiveBase)) {
|
|
15167
|
+
const entries = fs16.readdirSync(archiveBase, { withFileTypes: true });
|
|
14612
15168
|
for (const entry of entries) {
|
|
14613
15169
|
if (!entry.isDirectory()) continue;
|
|
14614
15170
|
const sessionDir = path22.join(archiveBase, entry.name);
|
|
@@ -14631,10 +15187,10 @@ function reindexFromArchive(projectPath, opts = {}) {
|
|
|
14631
15187
|
}
|
|
14632
15188
|
|
|
14633
15189
|
// src/sessions/summarize.ts
|
|
14634
|
-
var
|
|
15190
|
+
var fs17 = __toESM(require("fs"));
|
|
14635
15191
|
var path23 = __toESM(require("path"));
|
|
14636
|
-
var import_types32 = require("@harness-engineering/types");
|
|
14637
15192
|
var import_types33 = require("@harness-engineering/types");
|
|
15193
|
+
var import_types34 = require("@harness-engineering/types");
|
|
14638
15194
|
var LLM_SUMMARY_FILE = "llm-summary.md";
|
|
14639
15195
|
var SUMMARY_INPUT_FILES = [
|
|
14640
15196
|
{ filename: "summary.md", kind: "summary" },
|
|
@@ -14661,9 +15217,9 @@ function readInputCorpus(archiveDir) {
|
|
|
14661
15217
|
const parts = [];
|
|
14662
15218
|
for (const { filename, kind } of SUMMARY_INPUT_FILES) {
|
|
14663
15219
|
const p = path23.join(archiveDir, filename);
|
|
14664
|
-
if (!
|
|
15220
|
+
if (!fs17.existsSync(p)) continue;
|
|
14665
15221
|
try {
|
|
14666
|
-
const content =
|
|
15222
|
+
const content = fs17.readFileSync(p, "utf8");
|
|
14667
15223
|
if (content.trim().length === 0) continue;
|
|
14668
15224
|
parts.push(`## FILE: ${kind}
|
|
14669
15225
|
|
|
@@ -14725,17 +15281,17 @@ status: failed
|
|
|
14725
15281
|
|
|
14726
15282
|
- reason: ${reason}
|
|
14727
15283
|
`;
|
|
14728
|
-
|
|
15284
|
+
fs17.writeFileSync(filePath, body, "utf8");
|
|
14729
15285
|
return filePath;
|
|
14730
15286
|
}
|
|
14731
15287
|
async function summarizeArchivedSession(ctx) {
|
|
14732
15288
|
const writeStubOnError = ctx.writeStubOnError ?? true;
|
|
14733
|
-
if (!
|
|
14734
|
-
return (0,
|
|
15289
|
+
if (!fs17.existsSync(ctx.archiveDir)) {
|
|
15290
|
+
return (0, import_types34.Err)(new Error(`archive directory not found: ${ctx.archiveDir}`));
|
|
14735
15291
|
}
|
|
14736
15292
|
const corpus = readInputCorpus(ctx.archiveDir);
|
|
14737
15293
|
if (corpus.trim().length === 0) {
|
|
14738
|
-
return (0,
|
|
15294
|
+
return (0, import_types34.Err)(new Error(`no summary input files found in ${ctx.archiveDir}`));
|
|
14739
15295
|
}
|
|
14740
15296
|
const inputBudgetTokens = ctx.config?.inputBudgetTokens ?? DEFAULT_INPUT_BUDGET_TOKENS;
|
|
14741
15297
|
const truncated = truncateForBudget(corpus, inputBudgetTokens);
|
|
@@ -14744,7 +15300,7 @@ async function summarizeArchivedSession(ctx) {
|
|
|
14744
15300
|
const analyzeOpts = {
|
|
14745
15301
|
prompt,
|
|
14746
15302
|
systemPrompt: SYSTEM_PROMPT,
|
|
14747
|
-
responseSchema:
|
|
15303
|
+
responseSchema: import_types33.SessionSummarySchema,
|
|
14748
15304
|
...ctx.config?.model && { model: ctx.config.model }
|
|
14749
15305
|
};
|
|
14750
15306
|
let response;
|
|
@@ -14768,11 +15324,11 @@ async function summarizeArchivedSession(ctx) {
|
|
|
14768
15324
|
} catch {
|
|
14769
15325
|
}
|
|
14770
15326
|
}
|
|
14771
|
-
return (0,
|
|
15327
|
+
return (0, import_types34.Err)(
|
|
14772
15328
|
new Error(`session summary failed: ${reason}` + (stubPath ? ` (stub: ${stubPath})` : ""))
|
|
14773
15329
|
);
|
|
14774
15330
|
}
|
|
14775
|
-
const parsed =
|
|
15331
|
+
const parsed = import_types33.SessionSummarySchema.safeParse(response.result);
|
|
14776
15332
|
if (!parsed.success) {
|
|
14777
15333
|
const reason = `schema validation failed: ${parsed.error.message}`;
|
|
14778
15334
|
ctx.logger?.warn?.("session summary: invalid provider payload", { reason });
|
|
@@ -14782,7 +15338,7 @@ async function summarizeArchivedSession(ctx) {
|
|
|
14782
15338
|
} catch {
|
|
14783
15339
|
}
|
|
14784
15340
|
}
|
|
14785
|
-
return (0,
|
|
15341
|
+
return (0, import_types34.Err)(new Error(reason));
|
|
14786
15342
|
}
|
|
14787
15343
|
const meta = {
|
|
14788
15344
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -14793,8 +15349,8 @@ async function summarizeArchivedSession(ctx) {
|
|
|
14793
15349
|
};
|
|
14794
15350
|
const filePath = path23.join(ctx.archiveDir, LLM_SUMMARY_FILE);
|
|
14795
15351
|
const body = renderLlmSummaryMarkdown(parsed.data, meta);
|
|
14796
|
-
|
|
14797
|
-
return (0,
|
|
15352
|
+
fs17.writeFileSync(filePath, body, "utf8");
|
|
15353
|
+
return (0, import_types34.Ok)({ summary: parsed.data, meta, filePath });
|
|
14798
15354
|
}
|
|
14799
15355
|
function isSummaryEnabled(config) {
|
|
14800
15356
|
if (!config) return false;
|
|
@@ -14874,13 +15430,18 @@ function buildArchiveHooks(opts) {
|
|
|
14874
15430
|
BUILT_IN_TASKS,
|
|
14875
15431
|
BackendDefSchema,
|
|
14876
15432
|
BackendRouter,
|
|
15433
|
+
CheckScriptRunner,
|
|
14877
15434
|
ClaimManager,
|
|
14878
15435
|
GateNotReadyError,
|
|
14879
15436
|
GateRunError,
|
|
14880
15437
|
InteractionQueue,
|
|
15438
|
+
LinearGraphQLClient,
|
|
14881
15439
|
LinearGraphQLStub,
|
|
14882
15440
|
LocalModelResolver,
|
|
15441
|
+
MAINTENANCE_CHECK_MAX_BUFFER,
|
|
15442
|
+
MAINTENANCE_CHECK_TIMEOUT_MS,
|
|
14883
15443
|
MAX_ATTEMPTS,
|
|
15444
|
+
MaintenanceReporter,
|
|
14884
15445
|
MockBackend,
|
|
14885
15446
|
ORCHESTRATOR_IDENTITY_FILE,
|
|
14886
15447
|
Orchestrator,
|
|
@@ -14898,6 +15459,7 @@ function buildArchiveHooks(opts) {
|
|
|
14898
15459
|
SqliteSearchIndex,
|
|
14899
15460
|
StreamRecorder,
|
|
14900
15461
|
TaskOutputStore,
|
|
15462
|
+
TaskRunner,
|
|
14901
15463
|
TokenStore,
|
|
14902
15464
|
WebhookQueue,
|
|
14903
15465
|
WorkflowLoader,
|
|
@@ -14908,7 +15470,9 @@ function buildArchiveHooks(opts) {
|
|
|
14908
15470
|
buildArchiveHooks,
|
|
14909
15471
|
calculateRetryDelay,
|
|
14910
15472
|
canDispatch,
|
|
15473
|
+
classifyCheckExecutionFailure,
|
|
14911
15474
|
computeRateLimitDelay,
|
|
15475
|
+
createAgentDispatcher,
|
|
14912
15476
|
createBackend,
|
|
14913
15477
|
createEmptyState,
|
|
14914
15478
|
crossFieldRoutingIssues,
|
|
@@ -14920,22 +15484,27 @@ function buildArchiveHooks(opts) {
|
|
|
14920
15484
|
emitProposalApproved,
|
|
14921
15485
|
emitProposalCreated,
|
|
14922
15486
|
emitProposalRejected,
|
|
15487
|
+
explicitFindingsCount,
|
|
14923
15488
|
extractHighlights,
|
|
14924
15489
|
extractTitlePrefix,
|
|
14925
15490
|
getAvailableSlots,
|
|
14926
15491
|
getDefaultConfig,
|
|
14927
15492
|
getPerStateCount,
|
|
14928
15493
|
indexSessionDirectory,
|
|
15494
|
+
isCheckTimeoutError,
|
|
14929
15495
|
isEligible,
|
|
14930
15496
|
isSummaryEnabled,
|
|
14931
15497
|
launchTUI,
|
|
14932
15498
|
loadPublishedIndex,
|
|
15499
|
+
makeBackendResolver,
|
|
14933
15500
|
migrateAgentConfig,
|
|
14934
15501
|
normalizeFts5Query,
|
|
15502
|
+
normalizeHarnessCommand,
|
|
14935
15503
|
normalizeLocalModel,
|
|
14936
15504
|
openSearchIndex,
|
|
14937
15505
|
promote,
|
|
14938
15506
|
reconcile,
|
|
15507
|
+
recoverFindingsCount,
|
|
14939
15508
|
reindexFromArchive,
|
|
14940
15509
|
renderAnalysisComment,
|
|
14941
15510
|
renderLlmSummaryMarkdown,
|
|
@@ -14945,9 +15514,11 @@ function buildArchiveHooks(opts) {
|
|
|
14945
15514
|
routeIssue,
|
|
14946
15515
|
routingWarnings,
|
|
14947
15516
|
runGate,
|
|
15517
|
+
runHarnessCheck,
|
|
14948
15518
|
savePublishedIndex,
|
|
14949
15519
|
searchIndexPath,
|
|
14950
15520
|
selectCandidates,
|
|
15521
|
+
selectTasks,
|
|
14951
15522
|
sortCandidates,
|
|
14952
15523
|
summarizeArchivedSession,
|
|
14953
15524
|
syncMain,
|