@harness-engineering/orchestrator 0.14.0 → 0.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +252 -22
- package/dist/index.d.ts +252 -22
- package/dist/index.js +699 -105
- package/dist/index.mjs +664 -70
- package/package.json +6 -6
package/dist/index.js
CHANGED
|
@@ -32,6 +32,7 @@ var index_exports = {};
|
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
AdaptiveRouter: () => AdaptiveRouter,
|
|
34
34
|
AnalysisArchive: () => AnalysisArchive,
|
|
35
|
+
BRAINSTORM_RUBRIC: () => BRAINSTORM_RUBRIC,
|
|
35
36
|
BUILT_IN_TASKS: () => BUILT_IN_TASKS,
|
|
36
37
|
BackendDefSchema: () => BackendDefSchema,
|
|
37
38
|
BackendRouter: () => BackendRouter,
|
|
@@ -74,8 +75,10 @@ __export(index_exports, {
|
|
|
74
75
|
WorkspaceManager: () => WorkspaceManager,
|
|
75
76
|
applyEvent: () => applyEvent,
|
|
76
77
|
artifactPresenceFromIssue: () => artifactPresenceFromIssue,
|
|
78
|
+
brainstormInputFromIssue: () => brainstormInputFromIssue,
|
|
77
79
|
buildArchiveHooks: () => buildArchiveHooks,
|
|
78
80
|
buildCapabilityRegistry: () => buildCapabilityRegistry,
|
|
81
|
+
buildProbeInput: () => buildProbeInput,
|
|
79
82
|
buildWorkflowContext: () => buildWorkflowContext,
|
|
80
83
|
calculateRetryDelay: () => calculateRetryDelay,
|
|
81
84
|
canDispatch: () => canDispatch,
|
|
@@ -95,6 +98,7 @@ __export(index_exports, {
|
|
|
95
98
|
emitProposalApproved: () => emitProposalApproved,
|
|
96
99
|
emitProposalCreated: () => emitProposalCreated,
|
|
97
100
|
emitProposalRejected: () => emitProposalRejected,
|
|
101
|
+
enrichIssueWithSpec: () => enrichIssueWithSpec,
|
|
98
102
|
estimateCost: () => estimateCost,
|
|
99
103
|
explicitFindingsCount: () => explicitFindingsCount,
|
|
100
104
|
extractHighlights: () => extractHighlights,
|
|
@@ -109,22 +113,30 @@ __export(index_exports, {
|
|
|
109
113
|
launchTUI: () => launchTUI,
|
|
110
114
|
loadPublishedIndex: () => loadPublishedIndex,
|
|
111
115
|
makeBackendResolver: () => makeBackendResolver,
|
|
116
|
+
makeGraphScope: () => makeGraphScope,
|
|
117
|
+
makeSelForkGenerator: () => makeSelForkGenerator,
|
|
118
|
+
markApprovedForDispatch: () => markApprovedForDispatch,
|
|
112
119
|
migrateAgentConfig: () => migrateAgentConfig,
|
|
113
120
|
normalizeFts5Query: () => normalizeFts5Query,
|
|
114
121
|
normalizeHarnessCommand: () => normalizeHarnessCommand,
|
|
115
122
|
normalizeLocalModel: () => normalizeLocalModel,
|
|
116
123
|
openSearchIndex: () => openSearchIndex,
|
|
124
|
+
pilotScore: () => import_intelligence13.pilotScore,
|
|
125
|
+
precedentLookupFromStored: () => precedentLookupFromStored,
|
|
117
126
|
promote: () => promote,
|
|
127
|
+
rankTriageCandidates: () => import_intelligence13.rankTriageCandidates,
|
|
118
128
|
reconcile: () => reconcile,
|
|
119
129
|
recoverFindingsCount: () => recoverFindingsCount,
|
|
120
130
|
reindexFromArchive: () => reindexFromArchive,
|
|
121
131
|
renderAnalysisComment: () => renderAnalysisComment,
|
|
122
132
|
renderLlmSummaryMarkdown: () => renderLlmSummaryMarkdown,
|
|
123
133
|
renderPRComment: () => renderPRComment,
|
|
134
|
+
renderSpecMarkdown: () => renderSpecMarkdown,
|
|
124
135
|
resolveEscalationConfig: () => resolveEscalationConfig,
|
|
125
136
|
resolveOrchestratorId: () => resolveOrchestratorId,
|
|
126
137
|
routeIssue: () => routeIssue,
|
|
127
138
|
routingWarnings: () => routingWarnings,
|
|
139
|
+
runBrainstormForIssue: () => runBrainstormForIssue,
|
|
128
140
|
runGate: () => runGate,
|
|
129
141
|
runHarnessCheck: () => runHarnessCheck,
|
|
130
142
|
savePublishedIndex: () => savePublishedIndex,
|
|
@@ -132,10 +144,11 @@ __export(index_exports, {
|
|
|
132
144
|
selectCandidates: () => selectCandidates,
|
|
133
145
|
selectCheapestQualifying: () => selectCheapestQualifying,
|
|
134
146
|
selectTasks: () => selectTasks,
|
|
147
|
+
slugFor: () => slugFor,
|
|
135
148
|
sortCandidates: () => sortCandidates,
|
|
136
149
|
summarizeArchivedSession: () => summarizeArchivedSession,
|
|
137
150
|
syncMain: () => syncMain,
|
|
138
|
-
triageIssue: () =>
|
|
151
|
+
triageIssue: () => triageIssue2,
|
|
139
152
|
truncateForBudget: () => truncateForBudget,
|
|
140
153
|
validateCustomTasks: () => validateCustomTasks,
|
|
141
154
|
validateWorkflowConfig: () => validateWorkflowConfig,
|
|
@@ -2156,6 +2169,31 @@ var RoutingConfigSchema = import_zod.z.object({
|
|
|
2156
2169
|
// when absent). Previously accepted by the runtime PUT endpoint only. ---
|
|
2157
2170
|
policy: RoutingPolicySchema.optional()
|
|
2158
2171
|
}).strict();
|
|
2172
|
+
var CONFIDENCE = import_zod.z.enum(["low", "medium", "high"]);
|
|
2173
|
+
var V1_MAX_RATCHET_STAGE = 2;
|
|
2174
|
+
var RATCHET_STAGE = import_zod.z.union([import_zod.z.literal(1), import_zod.z.literal(2), import_zod.z.literal(3), import_zod.z.literal(4)]).refine((s) => s <= V1_MAX_RATCHET_STAGE, {
|
|
2175
|
+
message: `ratchetStage ${">"} ${V1_MAX_RATCHET_STAGE} is deferred post-v1 (stages 3\u20134 not yet supported); use 1 or 2`
|
|
2176
|
+
});
|
|
2177
|
+
var RoadmapAutoTriageSchema = import_zod.z.object({
|
|
2178
|
+
enabled: import_zod.z.boolean(),
|
|
2179
|
+
schedule: import_zod.z.string().min(1).optional(),
|
|
2180
|
+
ratchetStage: RATCHET_STAGE,
|
|
2181
|
+
thresholds: import_zod.z.object({
|
|
2182
|
+
dispatchConfidence: CONFIDENCE,
|
|
2183
|
+
boundedScopeMax: import_zod.z.number(),
|
|
2184
|
+
brainstormConfidence: import_zod.z.number(),
|
|
2185
|
+
exceededByBands: import_zod.z.number(),
|
|
2186
|
+
ratchetAdvanceRate: import_zod.z.number(),
|
|
2187
|
+
ratchetMinSample: import_zod.z.number()
|
|
2188
|
+
}).strict(),
|
|
2189
|
+
depthBudget: import_zod.z.object({
|
|
2190
|
+
trivial: import_zod.z.number(),
|
|
2191
|
+
simple: import_zod.z.number()
|
|
2192
|
+
}).strict()
|
|
2193
|
+
}).strict();
|
|
2194
|
+
var RoadmapConfigSchema = import_zod.z.object({
|
|
2195
|
+
autoTriage: RoadmapAutoTriageSchema.optional()
|
|
2196
|
+
}).strict();
|
|
2159
2197
|
var WorkflowStepSchema = import_zod.z.object({
|
|
2160
2198
|
skill: import_zod.z.string().min(1),
|
|
2161
2199
|
produces: import_zod.z.string().min(1),
|
|
@@ -2193,12 +2231,12 @@ var BackendsMapSchema = import_zod2.z.record(import_zod2.z.string(), BackendDefS
|
|
|
2193
2231
|
function crossFieldRoutingIssues(backends, routing) {
|
|
2194
2232
|
const issues = [];
|
|
2195
2233
|
const names = new Set(Object.keys(backends));
|
|
2196
|
-
const checkRef = (
|
|
2234
|
+
const checkRef = (path25, value) => {
|
|
2197
2235
|
if (value === void 0) return;
|
|
2198
2236
|
const entries = Array.isArray(value) ? value : [value];
|
|
2199
2237
|
entries.forEach((name, idx) => {
|
|
2200
2238
|
if (names.has(name)) return;
|
|
2201
|
-
const pathWithIdx = Array.isArray(value) ? [...
|
|
2239
|
+
const pathWithIdx = Array.isArray(value) ? [...path25, String(idx)] : path25;
|
|
2202
2240
|
issues.push({
|
|
2203
2241
|
path: pathWithIdx,
|
|
2204
2242
|
message: `routing.${pathWithIdx.join(".")} references unknown backend '${name}'. Defined: [${[...names].join(", ")}].`
|
|
@@ -2295,6 +2333,10 @@ function validateWorkflowConfig(config, options = {}) {
|
|
|
2295
2333
|
const parsed = import_zod2.z.array(StagedWorkflowDeclSchema).safeParse(c.workflows);
|
|
2296
2334
|
if (!parsed.success) return (0, import_types2.Err)(new Error(`workflows: ${parsed.error.message}`));
|
|
2297
2335
|
}
|
|
2336
|
+
if (c.roadmap !== void 0) {
|
|
2337
|
+
const parsed = RoadmapConfigSchema.safeParse(c.roadmap);
|
|
2338
|
+
if (!parsed.success) return (0, import_types2.Err)(new Error(`roadmap: ${parsed.error.message}`));
|
|
2339
|
+
}
|
|
2298
2340
|
return (0, import_types2.Ok)({ config, warnings });
|
|
2299
2341
|
}
|
|
2300
2342
|
function getDefaultConfig() {
|
|
@@ -3537,7 +3579,65 @@ var path21 = __toESM(require("path"));
|
|
|
3537
3579
|
var import_node_crypto15 = require("crypto");
|
|
3538
3580
|
var import_types34 = require("@harness-engineering/types");
|
|
3539
3581
|
var import_core16 = require("@harness-engineering/core");
|
|
3540
|
-
var
|
|
3582
|
+
var import_intelligence12 = require("@harness-engineering/intelligence");
|
|
3583
|
+
|
|
3584
|
+
// src/agent/triage-outcome.ts
|
|
3585
|
+
var import_intelligence = require("@harness-engineering/intelligence");
|
|
3586
|
+
function signalsFromDiff(hunks, textOnly) {
|
|
3587
|
+
const files = /* @__PURE__ */ new Set();
|
|
3588
|
+
const layers = /* @__PURE__ */ new Set();
|
|
3589
|
+
let addedLines = 0;
|
|
3590
|
+
for (const h of hunks) {
|
|
3591
|
+
files.add(h.file);
|
|
3592
|
+
layers.add(layerOfPath(h.file));
|
|
3593
|
+
addedLines += h.addedContent === "" ? 0 : h.addedContent.split("\n").length;
|
|
3594
|
+
}
|
|
3595
|
+
return {
|
|
3596
|
+
...textOnly,
|
|
3597
|
+
filesTouched: files.size,
|
|
3598
|
+
layersTouched: layers.size,
|
|
3599
|
+
blastRadius: addedLines
|
|
3600
|
+
};
|
|
3601
|
+
}
|
|
3602
|
+
function layerOfPath(file) {
|
|
3603
|
+
const parts = file.split("/").filter((p) => p.length > 0);
|
|
3604
|
+
if (parts.length === 0) return "<root>";
|
|
3605
|
+
if (parts[0] === "packages" && parts.length >= 4 && parts[2] === "src") {
|
|
3606
|
+
return `${parts[1] ?? ""}/${parts[3] ?? ""}`;
|
|
3607
|
+
}
|
|
3608
|
+
return parts.length === 1 ? "<root>" : parts[0] ?? "<root>";
|
|
3609
|
+
}
|
|
3610
|
+
async function runRetrospective(deps, provider) {
|
|
3611
|
+
const signals = signalsFromDiff(deps.hunks, deps.textOnly);
|
|
3612
|
+
const actual = await (0, import_intelligence.classify)(
|
|
3613
|
+
{ signals, phase: "post-diff", riskHigh: deps.riskHigh, prompt: deps.prompt },
|
|
3614
|
+
provider
|
|
3615
|
+
);
|
|
3616
|
+
const comparison = deps.comparatorConfig ? (0, import_intelligence.compareToPrediction)(deps.prediction, actual, deps.comparatorConfig) : (0, import_intelligence.compareToPrediction)(deps.prediction, actual);
|
|
3617
|
+
return { actual, comparison };
|
|
3618
|
+
}
|
|
3619
|
+
function buildTriageOutcomeInput(externalId, shapeKey, result) {
|
|
3620
|
+
return {
|
|
3621
|
+
externalId,
|
|
3622
|
+
shapeKey,
|
|
3623
|
+
actual: result.actual,
|
|
3624
|
+
exceededBy: result.comparison.exceededBy,
|
|
3625
|
+
matched: result.comparison.matched
|
|
3626
|
+
};
|
|
3627
|
+
}
|
|
3628
|
+
function precedentLookupFromStored(records) {
|
|
3629
|
+
const bridged = records.map((r) => ({
|
|
3630
|
+
externalId: "",
|
|
3631
|
+
shapeKey: r.shapeKey,
|
|
3632
|
+
ts: "",
|
|
3633
|
+
...r.outcome ? { outcome: { actual: void 0, exceededBy: 0, matched: r.outcome.matched } } : {}
|
|
3634
|
+
}));
|
|
3635
|
+
return {
|
|
3636
|
+
rateForShape: (shapeKey) => (0, import_intelligence.aggregatePrecedent)(bridged, shapeKey)
|
|
3637
|
+
};
|
|
3638
|
+
}
|
|
3639
|
+
|
|
3640
|
+
// src/orchestrator.ts
|
|
3541
3641
|
var import_graph2 = require("@harness-engineering/graph");
|
|
3542
3642
|
|
|
3543
3643
|
// src/core/stall-detector.ts
|
|
@@ -3558,7 +3658,7 @@ function detectStalledIssues(running, nowMs, stallTimeoutMs) {
|
|
|
3558
3658
|
|
|
3559
3659
|
// src/intelligence/pipeline-runner.ts
|
|
3560
3660
|
var path9 = __toESM(require("path"));
|
|
3561
|
-
var
|
|
3661
|
+
var import_intelligence2 = require("@harness-engineering/intelligence");
|
|
3562
3662
|
var import_core2 = require("@harness-engineering/core");
|
|
3563
3663
|
var CONNECTION_ERROR_PATTERNS = [
|
|
3564
3664
|
"Connection error",
|
|
@@ -3663,7 +3763,7 @@ var IntelligencePipelineRunner = class {
|
|
|
3663
3763
|
refreshSpecializationProfiles() {
|
|
3664
3764
|
if (!this.ctx.graphStore) return;
|
|
3665
3765
|
try {
|
|
3666
|
-
const store = (0,
|
|
3766
|
+
const store = (0, import_intelligence2.refreshProfiles)(this.ctx.projectRoot, this.ctx.graphStore);
|
|
3667
3767
|
const personaCount = Object.keys(store.profiles).length;
|
|
3668
3768
|
if (personaCount > 0) {
|
|
3669
3769
|
this.ctx.logger.info(`Refreshed specialization profiles for ${personaCount} persona(s)`);
|
|
@@ -3906,7 +4006,7 @@ var IntelligencePipelineRunner = class {
|
|
|
3906
4006
|
for (const issue of candidates) {
|
|
3907
4007
|
const systemNodeIds = issue.labels.filter((l) => l.startsWith("system:") || l.startsWith("module:")).map((l) => l.split(":")[1]).filter((id) => id.length > 0);
|
|
3908
4008
|
if (systemNodeIds.length === 0) continue;
|
|
3909
|
-
const recs = (0,
|
|
4009
|
+
const recs = (0, import_intelligence2.weightedRecommendPersona)(this.ctx.graphStore, { systemNodeIds });
|
|
3910
4010
|
if (recs.length > 0) {
|
|
3911
4011
|
results.set(issue.id, recs);
|
|
3912
4012
|
}
|
|
@@ -4815,11 +4915,11 @@ function detectLegacyFields(agent) {
|
|
|
4815
4915
|
}
|
|
4816
4916
|
function buildCase1Warnings(presentLegacy, suppressLocalGroup) {
|
|
4817
4917
|
const warnings = [];
|
|
4818
|
-
for (const
|
|
4819
|
-
if (CASE1_ALWAYS_SUPPRESS.has(
|
|
4820
|
-
if (suppressLocalGroup && CASE1_LOCAL_GROUP.has(
|
|
4918
|
+
for (const path25 of presentLegacy) {
|
|
4919
|
+
if (CASE1_ALWAYS_SUPPRESS.has(path25)) continue;
|
|
4920
|
+
if (suppressLocalGroup && CASE1_LOCAL_GROUP.has(path25)) continue;
|
|
4821
4921
|
warnings.push(
|
|
4822
|
-
`Ignoring legacy field '${
|
|
4922
|
+
`Ignoring legacy field '${path25}': 'agent.backends' is set and takes precedence. See ${MIGRATION_GUIDE}.`
|
|
4823
4923
|
);
|
|
4824
4924
|
}
|
|
4825
4925
|
return warnings;
|
|
@@ -4847,7 +4947,7 @@ function migrateAgentConfig(agent) {
|
|
|
4847
4947
|
}
|
|
4848
4948
|
const { backends, routing } = synthesizeBackendsAndRouting(agent);
|
|
4849
4949
|
const warnings = presentLegacy.map(
|
|
4850
|
-
(
|
|
4950
|
+
(path25) => `Deprecated config field '${path25}' is in use. Migrate to 'agent.backends' / 'agent.routing'. See ${MIGRATION_GUIDE}.`
|
|
4851
4951
|
);
|
|
4852
4952
|
return {
|
|
4853
4953
|
config: { ...agent, backends, routing },
|
|
@@ -4932,12 +5032,12 @@ var BackendRouter = class {
|
|
|
4932
5032
|
*/
|
|
4933
5033
|
resolve(useCase, opts) {
|
|
4934
5034
|
const startedAt = performance.now();
|
|
4935
|
-
const
|
|
5035
|
+
const path25 = [];
|
|
4936
5036
|
const tryChain = (source, value) => {
|
|
4937
5037
|
if (value === void 0) return void 0;
|
|
4938
5038
|
for (const name of toArray(value)) {
|
|
4939
5039
|
const step = { source, candidate: name, outcome: "considered" };
|
|
4940
|
-
|
|
5040
|
+
path25.push(step);
|
|
4941
5041
|
if (this.backends[name]) {
|
|
4942
5042
|
step.outcome = "chosen";
|
|
4943
5043
|
return name;
|
|
@@ -4956,7 +5056,7 @@ var BackendRouter = class {
|
|
|
4956
5056
|
return {
|
|
4957
5057
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4958
5058
|
useCase,
|
|
4959
|
-
resolutionPath:
|
|
5059
|
+
resolutionPath: path25,
|
|
4960
5060
|
backendName,
|
|
4961
5061
|
backendType: def.type,
|
|
4962
5062
|
durationMs: performance.now() - startedAt
|
|
@@ -4989,7 +5089,7 @@ var BackendRouter = class {
|
|
|
4989
5089
|
if (fromDefault) return emitAndReturn(decide(fromDefault));
|
|
4990
5090
|
const knownList = Object.keys(this.backends).join(", ") || "(none)";
|
|
4991
5091
|
throw new Error(
|
|
4992
|
-
`BackendRouter.resolve: routing.default produced no available backend for useCase=${JSON.stringify(useCase)}. Resolution path: ${JSON.stringify(
|
|
5092
|
+
`BackendRouter.resolve: routing.default produced no available backend for useCase=${JSON.stringify(useCase)}. Resolution path: ${JSON.stringify(path25)}. Known backends: [${knownList}].`
|
|
4993
5093
|
);
|
|
4994
5094
|
}
|
|
4995
5095
|
/**
|
|
@@ -5082,7 +5182,7 @@ var BackendRouter = class {
|
|
|
5082
5182
|
check(`modes.${mode}`, value);
|
|
5083
5183
|
}
|
|
5084
5184
|
if (missing.length > 0) {
|
|
5085
|
-
const detail = missing.map(({ path:
|
|
5185
|
+
const detail = missing.map(({ path: path25, name }) => `routing.${path25} -> '${name}'`).join("; ");
|
|
5086
5186
|
const known_ = [...known].join(", ") || "(none)";
|
|
5087
5187
|
throw new Error(
|
|
5088
5188
|
`BackendRouter: routing references unknown backend(s): ${detail}. Defined backends: [${known_}].`
|
|
@@ -7475,11 +7575,11 @@ function createAgentDispatcher(deps) {
|
|
|
7475
7575
|
var import_node_child_process13 = require("child_process");
|
|
7476
7576
|
|
|
7477
7577
|
// src/agent/intelligence-factory.ts
|
|
7478
|
-
var
|
|
7578
|
+
var import_intelligence4 = require("@harness-engineering/intelligence");
|
|
7479
7579
|
var import_graph = require("@harness-engineering/graph");
|
|
7480
7580
|
|
|
7481
7581
|
// src/agent/analysis-provider-factory.ts
|
|
7482
|
-
var
|
|
7582
|
+
var import_intelligence3 = require("@harness-engineering/intelligence");
|
|
7483
7583
|
function buildAnalysisProvider(args) {
|
|
7484
7584
|
const { def, backendName, layer, intelligence, logger } = args;
|
|
7485
7585
|
const layerModel = layer === "sel" ? intelligence?.models?.sel : intelligence?.models?.pesl;
|
|
@@ -7519,7 +7619,7 @@ function buildLocalLikeProvider(def, args, layerModel) {
|
|
|
7519
7619
|
logger.info(
|
|
7520
7620
|
`Intelligence pipeline using backend '${backendName}' (${def.type}) at ${def.endpoint} (model: ${model ?? "(default)"})`
|
|
7521
7621
|
);
|
|
7522
|
-
return new
|
|
7622
|
+
return new import_intelligence3.OpenAICompatibleAnalysisProvider({
|
|
7523
7623
|
apiKey,
|
|
7524
7624
|
baseUrl: def.endpoint,
|
|
7525
7625
|
...model !== void 0 && { defaultModel: model },
|
|
@@ -7537,7 +7637,7 @@ function buildAnthropicProvider(def, args, layerModel) {
|
|
|
7537
7637
|
const apiKey = def.apiKey ?? process.env.ANTHROPIC_API_KEY;
|
|
7538
7638
|
const model = layerModel ?? def.model;
|
|
7539
7639
|
if (apiKey) {
|
|
7540
|
-
return new
|
|
7640
|
+
return new import_intelligence3.AnthropicAnalysisProvider({
|
|
7541
7641
|
apiKey,
|
|
7542
7642
|
...model !== void 0 && { defaultModel: model }
|
|
7543
7643
|
});
|
|
@@ -7545,7 +7645,7 @@ function buildAnthropicProvider(def, args, layerModel) {
|
|
|
7545
7645
|
args.logger.info(
|
|
7546
7646
|
`Intelligence pipeline routed to '${args.backendName}' (anthropic) without API key \u2014 using Claude CLI fallback.`
|
|
7547
7647
|
);
|
|
7548
|
-
return new
|
|
7648
|
+
return new import_intelligence3.ClaudeCliAnalysisProvider({
|
|
7549
7649
|
...model !== void 0 && { defaultModel: model },
|
|
7550
7650
|
...args.intelligence?.requestTimeoutMs !== void 0 && {
|
|
7551
7651
|
timeoutMs: args.intelligence.requestTimeoutMs
|
|
@@ -7561,7 +7661,7 @@ function buildOpenAIProvider(def, args, layerModel) {
|
|
|
7561
7661
|
return null;
|
|
7562
7662
|
}
|
|
7563
7663
|
const model = layerModel ?? def.model;
|
|
7564
|
-
return new
|
|
7664
|
+
return new import_intelligence3.OpenAICompatibleAnalysisProvider({
|
|
7565
7665
|
apiKey,
|
|
7566
7666
|
baseUrl: "https://api.openai.com/v1",
|
|
7567
7667
|
...model !== void 0 && { defaultModel: model },
|
|
@@ -7571,7 +7671,7 @@ function buildOpenAIProvider(def, args, layerModel) {
|
|
|
7571
7671
|
});
|
|
7572
7672
|
}
|
|
7573
7673
|
function buildClaudeCliProvider(def, args, layerModel) {
|
|
7574
|
-
return new
|
|
7674
|
+
return new import_intelligence3.ClaudeCliAnalysisProvider({
|
|
7575
7675
|
...def.command !== void 0 && { command: def.command },
|
|
7576
7676
|
...layerModel !== void 0 && { defaultModel: layerModel },
|
|
7577
7677
|
...args.intelligence?.requestTimeoutMs !== void 0 && {
|
|
@@ -7592,7 +7692,7 @@ function buildIntelligencePipeline(deps) {
|
|
|
7592
7692
|
const peslProvider = peslName !== selName ? buildAnalysisProviderForLayer("pesl", deps) : null;
|
|
7593
7693
|
const peslModel = intel.models?.pesl ?? config.agent.model;
|
|
7594
7694
|
const graphStore = new import_graph.GraphStore();
|
|
7595
|
-
const pipeline = new
|
|
7695
|
+
const pipeline = new import_intelligence4.IntelligencePipeline(selProvider, graphStore, {
|
|
7596
7696
|
...peslModel !== void 0 && { peslModel },
|
|
7597
7697
|
...peslProvider !== null && peslProvider !== void 0 && { peslProvider }
|
|
7598
7698
|
});
|
|
@@ -7658,13 +7758,13 @@ function buildExplicitProvider(provider, selModel, config) {
|
|
|
7658
7758
|
if (!apiKey2) {
|
|
7659
7759
|
throw new Error("Intelligence pipeline: no Anthropic API key found.");
|
|
7660
7760
|
}
|
|
7661
|
-
return new
|
|
7761
|
+
return new import_intelligence4.AnthropicAnalysisProvider({
|
|
7662
7762
|
apiKey: apiKey2,
|
|
7663
7763
|
...selModel !== void 0 && { defaultModel: selModel }
|
|
7664
7764
|
});
|
|
7665
7765
|
}
|
|
7666
7766
|
if (provider.kind === "claude-cli") {
|
|
7667
|
-
return new
|
|
7767
|
+
return new import_intelligence4.ClaudeCliAnalysisProvider({
|
|
7668
7768
|
command: config.agent.command,
|
|
7669
7769
|
...selModel !== void 0 && { defaultModel: selModel },
|
|
7670
7770
|
...config.intelligence?.requestTimeoutMs !== void 0 && {
|
|
@@ -7675,7 +7775,7 @@ function buildExplicitProvider(provider, selModel, config) {
|
|
|
7675
7775
|
const apiKey = provider.apiKey ?? config.agent.apiKey ?? "ollama";
|
|
7676
7776
|
const baseUrl = provider.baseUrl ?? "http://localhost:11434/v1";
|
|
7677
7777
|
const intel = config.intelligence;
|
|
7678
|
-
return new
|
|
7778
|
+
return new import_intelligence4.OpenAICompatibleAnalysisProvider({
|
|
7679
7779
|
apiKey,
|
|
7680
7780
|
baseUrl,
|
|
7681
7781
|
...selModel !== void 0 && { defaultModel: selModel },
|
|
@@ -7686,13 +7786,13 @@ function buildExplicitProvider(provider, selModel, config) {
|
|
|
7686
7786
|
}
|
|
7687
7787
|
|
|
7688
7788
|
// src/agent/adaptive-router.ts
|
|
7689
|
-
var
|
|
7789
|
+
var import_intelligence7 = require("@harness-engineering/intelligence");
|
|
7690
7790
|
var import_types24 = require("@harness-engineering/types");
|
|
7691
7791
|
|
|
7692
7792
|
// src/agent/capability-registry.ts
|
|
7693
7793
|
var import_types23 = require("@harness-engineering/types");
|
|
7694
7794
|
var import_local_models2 = require("@harness-engineering/local-models");
|
|
7695
|
-
var
|
|
7795
|
+
var import_intelligence5 = require("@harness-engineering/intelligence");
|
|
7696
7796
|
var PrivacyNoMatch = class extends import_types23.RoutingError {
|
|
7697
7797
|
code = "privacy-no-match";
|
|
7698
7798
|
constructor(message) {
|
|
@@ -7707,7 +7807,7 @@ var PRIVACY_RANK = {
|
|
|
7707
7807
|
"shared-cloud": 3
|
|
7708
7808
|
};
|
|
7709
7809
|
function selectCheapestQualifying(registry, requiredTier, constraints, providerOf) {
|
|
7710
|
-
const requiredRank =
|
|
7810
|
+
const requiredRank = import_intelligence5.TIER_RANK[requiredTier];
|
|
7711
7811
|
const entries = [...registry.entries()].map(([name, capabilities]) => ({
|
|
7712
7812
|
name,
|
|
7713
7813
|
capabilities
|
|
@@ -7729,7 +7829,7 @@ function selectCheapestQualifying(registry, requiredTier, constraints, providerO
|
|
|
7729
7829
|
}
|
|
7730
7830
|
const qualifying = passesPrivacyAllow.filter((e) => {
|
|
7731
7831
|
const c = e.capabilities;
|
|
7732
|
-
if (
|
|
7832
|
+
if (import_intelligence5.TIER_RANK[c.tier] < requiredRank) return false;
|
|
7733
7833
|
if (constraints.needsVision && !c.vision) return false;
|
|
7734
7834
|
if (constraints.needsToolUse && !c.toolUse) return false;
|
|
7735
7835
|
if (constraints.minContextTokens !== void 0 && c.contextWindow < constraints.minContextTokens)
|
|
@@ -7768,7 +7868,7 @@ function estimateCost(def, _req) {
|
|
|
7768
7868
|
}
|
|
7769
7869
|
|
|
7770
7870
|
// src/agent/escalation-state.ts
|
|
7771
|
-
var
|
|
7871
|
+
var import_intelligence6 = require("@harness-engineering/intelligence");
|
|
7772
7872
|
var EscalationState = class {
|
|
7773
7873
|
constructor(threshold = 2) {
|
|
7774
7874
|
this.threshold = threshold;
|
|
@@ -7797,7 +7897,7 @@ var EscalationState = class {
|
|
|
7797
7897
|
climbedUnits() {
|
|
7798
7898
|
const out = [];
|
|
7799
7899
|
for (const [coherenceUnit, state] of this.units) {
|
|
7800
|
-
if (
|
|
7900
|
+
if (import_intelligence6.TIER_RANK[state.floorTier] > import_intelligence6.TIER_RANK.fast) {
|
|
7801
7901
|
out.push({ coherenceUnit, floor: state.floorTier });
|
|
7802
7902
|
}
|
|
7803
7903
|
}
|
|
@@ -7836,14 +7936,14 @@ var EscalationState = class {
|
|
|
7836
7936
|
return "ok";
|
|
7837
7937
|
}
|
|
7838
7938
|
state.failures = 0;
|
|
7839
|
-
const currentRank =
|
|
7840
|
-
if (currentRank >=
|
|
7939
|
+
const currentRank = import_intelligence6.TIER_RANK[state.floorTier];
|
|
7940
|
+
if (currentRank >= import_intelligence6.TIER_RANK.strong) {
|
|
7841
7941
|
state.floorTier = "strong";
|
|
7842
7942
|
state.escalated = true;
|
|
7843
7943
|
this.units.set(coherenceUnit, state);
|
|
7844
7944
|
return "exhausted";
|
|
7845
7945
|
}
|
|
7846
|
-
state.floorTier =
|
|
7946
|
+
state.floorTier = import_intelligence6.RANK_TIER[currentRank + 1];
|
|
7847
7947
|
state.escalated = true;
|
|
7848
7948
|
this.units.set(coherenceUnit, state);
|
|
7849
7949
|
return "escalated";
|
|
@@ -7979,7 +8079,7 @@ var AdaptiveRouter = class _AdaptiveRouter {
|
|
|
7979
8079
|
let budgetStatus = null;
|
|
7980
8080
|
if (budget && budget.capUsd > 0) {
|
|
7981
8081
|
const spentUsd = this.getSpentUsd();
|
|
7982
|
-
const degradeAtPct = budget.degradeAtPct ??
|
|
8082
|
+
const degradeAtPct = budget.degradeAtPct ?? import_intelligence7.DEFAULT_DEGRADE_AT_PCT;
|
|
7983
8083
|
budgetStatus = {
|
|
7984
8084
|
spentUsd,
|
|
7985
8085
|
capUsd: budget.capUsd,
|
|
@@ -8011,8 +8111,8 @@ var AdaptiveRouter = class _AdaptiveRouter {
|
|
|
8011
8111
|
);
|
|
8012
8112
|
}
|
|
8013
8113
|
const unitFloor = this.deps.escalation?.floorFor(req.coherenceUnit) ?? "fast";
|
|
8014
|
-
const escalationFloor =
|
|
8015
|
-
const requiredTier = (0,
|
|
8114
|
+
const escalationFloor = import_intelligence7.RANK_TIER[Math.max(import_intelligence7.TIER_RANK[unitFloor], import_intelligence7.TIER_RANK[req.floor ?? "fast"])];
|
|
8115
|
+
const requiredTier = (0, import_intelligence7.deriveRequiredTier)(
|
|
8016
8116
|
complexity,
|
|
8017
8117
|
req.risk,
|
|
8018
8118
|
this.deps.policy,
|
|
@@ -8169,10 +8269,10 @@ var RoutingDecisionBus = class {
|
|
|
8169
8269
|
};
|
|
8170
8270
|
|
|
8171
8271
|
// src/workflow/execute-workflow.ts
|
|
8172
|
-
var
|
|
8272
|
+
var import_intelligence8 = require("@harness-engineering/intelligence");
|
|
8173
8273
|
function nextTier(t) {
|
|
8174
|
-
const next = Math.min(
|
|
8175
|
-
return
|
|
8274
|
+
const next = Math.min(import_intelligence8.TIER_RANK[t] + 1, import_intelligence8.TIER_RANK.strong);
|
|
8275
|
+
return import_intelligence8.RANK_TIER[next];
|
|
8176
8276
|
}
|
|
8177
8277
|
var DEFAULT_STAGE_DEADLINE_MS = 12e4;
|
|
8178
8278
|
function stageAttemptKey(stageIndex, attempt) {
|
|
@@ -8394,7 +8494,7 @@ function buildRoutingUseCase(issue, backendParam, catalog) {
|
|
|
8394
8494
|
}
|
|
8395
8495
|
|
|
8396
8496
|
// src/agent/live-classify.ts
|
|
8397
|
-
var
|
|
8497
|
+
var import_intelligence9 = require("@harness-engineering/intelligence");
|
|
8398
8498
|
var CONSERVATIVE = {
|
|
8399
8499
|
level: "moderate",
|
|
8400
8500
|
confidence: "low",
|
|
@@ -8417,7 +8517,7 @@ function makeLiveClassify(resolveProvider) {
|
|
|
8417
8517
|
riskHigh,
|
|
8418
8518
|
prompt: taskText.prompt
|
|
8419
8519
|
};
|
|
8420
|
-
return (0,
|
|
8520
|
+
return (0, import_intelligence9.classify)(input, resolveProvider());
|
|
8421
8521
|
};
|
|
8422
8522
|
}
|
|
8423
8523
|
|
|
@@ -8940,7 +9040,7 @@ function extractChunks(event) {
|
|
|
8940
9040
|
}
|
|
8941
9041
|
|
|
8942
9042
|
// src/server/routes/analyze.ts
|
|
8943
|
-
var
|
|
9043
|
+
var import_intelligence10 = require("@harness-engineering/intelligence");
|
|
8944
9044
|
var import_zod7 = require("zod");
|
|
8945
9045
|
var AnalyzeRequestSchema = import_zod7.z.object({
|
|
8946
9046
|
title: import_zod7.z.string().min(1),
|
|
@@ -8970,7 +9070,7 @@ async function runPipeline(res, pipeline, parsed) {
|
|
|
8970
9070
|
disconnected = true;
|
|
8971
9071
|
});
|
|
8972
9072
|
emit2(res, { type: "status", text: "Converting to work item..." });
|
|
8973
|
-
const rawItem = (0,
|
|
9073
|
+
const rawItem = (0, import_intelligence10.manualToRawWorkItem)({
|
|
8974
9074
|
title: parsed.title,
|
|
8975
9075
|
description: parsed.description ?? "",
|
|
8976
9076
|
labels: parsed.labels ?? []
|
|
@@ -9013,7 +9113,7 @@ async function runPipeline(res, pipeline, parsed) {
|
|
|
9013
9113
|
}
|
|
9014
9114
|
}
|
|
9015
9115
|
if (disconnected) return;
|
|
9016
|
-
const signals = (0,
|
|
9116
|
+
const signals = (0, import_intelligence10.scoreToConcernSignals)(score);
|
|
9017
9117
|
if (signals.length > 0) {
|
|
9018
9118
|
emit2(res, { type: "signals", data: signals });
|
|
9019
9119
|
}
|
|
@@ -10673,7 +10773,7 @@ function handleV1LocalModelsMutationRoute(req, res, deps) {
|
|
|
10673
10773
|
|
|
10674
10774
|
// src/server/routes/v1/routing.ts
|
|
10675
10775
|
var import_zod14 = require("zod");
|
|
10676
|
-
var
|
|
10776
|
+
var import_intelligence11 = require("@harness-engineering/intelligence");
|
|
10677
10777
|
var CONFIG_RE = /^\/api\/v1\/routing\/config(?:\?.*)?$/;
|
|
10678
10778
|
var DECISIONS_RE = /^\/api\/v1\/routing\/decisions(?:\?.*)?$/;
|
|
10679
10779
|
var TRACE_RE = /^\/api\/v1\/routing\/trace(?:\?.*)?$/;
|
|
@@ -10777,7 +10877,7 @@ function deriveTraceCost(body, decision, def, routing, backends) {
|
|
|
10777
10877
|
source: "static"
|
|
10778
10878
|
};
|
|
10779
10879
|
const risk = body.risk === "high" ? { blastRadius: 10, sensitivePath: true } : { blastRadius: 0, sensitivePath: false };
|
|
10780
|
-
const tierRequired = (0,
|
|
10880
|
+
const tierRequired = (0, import_intelligence11.deriveRequiredTier)(
|
|
10781
10881
|
verdict,
|
|
10782
10882
|
risk,
|
|
10783
10883
|
routing.policy ?? {},
|
|
@@ -10792,7 +10892,12 @@ function deriveTraceCost(body, decision, def, routing, backends) {
|
|
|
10792
10892
|
backends
|
|
10793
10893
|
);
|
|
10794
10894
|
const estCostUsd = estimateCost(costedDef, { useCase: body.useCase });
|
|
10795
|
-
return {
|
|
10895
|
+
return {
|
|
10896
|
+
tierRequired,
|
|
10897
|
+
estCostUsd,
|
|
10898
|
+
costedBackendName: costedName,
|
|
10899
|
+
costedBackendType: costedDef.type
|
|
10900
|
+
};
|
|
10796
10901
|
}
|
|
10797
10902
|
function selectCostedBackend(tierRequired, decision, def, routing, backends) {
|
|
10798
10903
|
const registry = buildCapabilityRegistry(backends);
|
|
@@ -10857,7 +10962,7 @@ async function handleTrace(req, res, deps) {
|
|
|
10857
10962
|
opts
|
|
10858
10963
|
);
|
|
10859
10964
|
if (r.data.complexity !== void 0 || r.data.risk !== void 0) {
|
|
10860
|
-
const { tierRequired, estCostUsd, costedBackendName } = deriveTraceCost(
|
|
10965
|
+
const { tierRequired, estCostUsd, costedBackendName, costedBackendType } = deriveTraceCost(
|
|
10861
10966
|
r.data,
|
|
10862
10967
|
decision,
|
|
10863
10968
|
def,
|
|
@@ -10869,9 +10974,12 @@ async function handleTrace(req, res, deps) {
|
|
|
10869
10974
|
def: { type: def.type },
|
|
10870
10975
|
tierRequired,
|
|
10871
10976
|
estCostUsd,
|
|
10872
|
-
//
|
|
10873
|
-
//
|
|
10874
|
-
|
|
10977
|
+
// The backend AMR would ACTUALLY dispatch to at this tier (cheapest
|
|
10978
|
+
// qualifying) + its type — distinct from the identity-chain `decision`
|
|
10979
|
+
// above. The CLI shows this as the effective backend so tier↔cost↔backend
|
|
10980
|
+
// read consistently (was implicit + divergent before).
|
|
10981
|
+
costedBackendName,
|
|
10982
|
+
costedBackendType
|
|
10875
10983
|
});
|
|
10876
10984
|
return true;
|
|
10877
10985
|
}
|
|
@@ -11426,8 +11534,8 @@ function parseToken(raw) {
|
|
|
11426
11534
|
return { id: raw.slice(0, dot), secret: raw.slice(dot + 1) };
|
|
11427
11535
|
}
|
|
11428
11536
|
var TokenStore = class {
|
|
11429
|
-
constructor(
|
|
11430
|
-
this.path =
|
|
11537
|
+
constructor(path25) {
|
|
11538
|
+
this.path = path25;
|
|
11431
11539
|
}
|
|
11432
11540
|
path;
|
|
11433
11541
|
cache = null;
|
|
@@ -11534,8 +11642,8 @@ var import_promises2 = require("fs/promises");
|
|
|
11534
11642
|
var import_node_path2 = require("path");
|
|
11535
11643
|
var import_types29 = require("@harness-engineering/types");
|
|
11536
11644
|
var AuditLogger = class {
|
|
11537
|
-
constructor(
|
|
11538
|
-
this.path =
|
|
11645
|
+
constructor(path25, opts = {}) {
|
|
11646
|
+
this.path = path25;
|
|
11539
11647
|
this.opts = opts;
|
|
11540
11648
|
}
|
|
11541
11649
|
path;
|
|
@@ -11773,9 +11881,9 @@ var V1_BRIDGE_ROUTES = [
|
|
|
11773
11881
|
function isV1Bridge(method, url) {
|
|
11774
11882
|
return V1_BRIDGE_ROUTES.some((r) => r.method === method && r.pattern.test(url));
|
|
11775
11883
|
}
|
|
11776
|
-
function requiredBridgeScope(method,
|
|
11884
|
+
function requiredBridgeScope(method, path25) {
|
|
11777
11885
|
for (const r of V1_BRIDGE_ROUTES) {
|
|
11778
|
-
if (r.method === method && r.pattern.test(
|
|
11886
|
+
if (r.method === method && r.pattern.test(path25)) return r.scope;
|
|
11779
11887
|
}
|
|
11780
11888
|
return null;
|
|
11781
11889
|
}
|
|
@@ -11785,11 +11893,11 @@ function hasScope(held, required) {
|
|
|
11785
11893
|
if (held.includes("admin")) return true;
|
|
11786
11894
|
return held.includes(required);
|
|
11787
11895
|
}
|
|
11788
|
-
function exactScopeForRoute(method,
|
|
11789
|
-
if (
|
|
11790
|
-
if (
|
|
11791
|
-
if (/^\/api\/v1\/auth\/tokens\/[^/]+$/.test(
|
|
11792
|
-
if ((
|
|
11896
|
+
function exactScopeForRoute(method, path25) {
|
|
11897
|
+
if (path25 === "/api/v1/auth/token" && method === "POST") return "admin";
|
|
11898
|
+
if (path25 === "/api/v1/auth/tokens" && method === "GET") return "admin";
|
|
11899
|
+
if (/^\/api\/v1\/auth\/tokens\/[^/]+$/.test(path25) && method === "DELETE") return "admin";
|
|
11900
|
+
if ((path25 === "/api/state" || path25 === "/api/v1/state") && method === "GET") return "read-status";
|
|
11793
11901
|
return null;
|
|
11794
11902
|
}
|
|
11795
11903
|
var PREFIX_SCOPES = [
|
|
@@ -11806,19 +11914,19 @@ var PREFIX_SCOPES = [
|
|
|
11806
11914
|
["/api/sessions", "read-status"],
|
|
11807
11915
|
["/api/chat-proxy", "trigger-job"]
|
|
11808
11916
|
];
|
|
11809
|
-
function prefixScopeForPath(
|
|
11810
|
-
if (
|
|
11917
|
+
function prefixScopeForPath(path25) {
|
|
11918
|
+
if (path25 === "/api/chat") return "trigger-job";
|
|
11811
11919
|
for (const [prefix, scope] of PREFIX_SCOPES) {
|
|
11812
|
-
if (
|
|
11920
|
+
if (path25.startsWith(prefix)) return scope;
|
|
11813
11921
|
}
|
|
11814
11922
|
return null;
|
|
11815
11923
|
}
|
|
11816
|
-
function requiredScopeForRoute(method,
|
|
11817
|
-
const bridgeScope = requiredBridgeScope(method,
|
|
11924
|
+
function requiredScopeForRoute(method, path25) {
|
|
11925
|
+
const bridgeScope = requiredBridgeScope(method, path25);
|
|
11818
11926
|
if (bridgeScope) return bridgeScope;
|
|
11819
|
-
const exactScope = exactScopeForRoute(method,
|
|
11927
|
+
const exactScope = exactScopeForRoute(method, path25);
|
|
11820
11928
|
if (exactScope) return exactScope;
|
|
11821
|
-
return prefixScopeForPath(
|
|
11929
|
+
return prefixScopeForPath(path25);
|
|
11822
11930
|
}
|
|
11823
11931
|
|
|
11824
11932
|
// src/server/http.ts
|
|
@@ -12347,8 +12455,8 @@ function genSecret2() {
|
|
|
12347
12455
|
return (0, import_node_crypto10.randomBytes)(32).toString("base64url");
|
|
12348
12456
|
}
|
|
12349
12457
|
var WebhookStore = class {
|
|
12350
|
-
constructor(
|
|
12351
|
-
this.path =
|
|
12458
|
+
constructor(path25) {
|
|
12459
|
+
this.path = path25;
|
|
12352
12460
|
}
|
|
12353
12461
|
path;
|
|
12354
12462
|
cache = null;
|
|
@@ -15193,8 +15301,8 @@ function validateCheckShape(prefix, task, errors) {
|
|
|
15193
15301
|
});
|
|
15194
15302
|
}
|
|
15195
15303
|
if (hasScript) {
|
|
15196
|
-
const
|
|
15197
|
-
if (!
|
|
15304
|
+
const path25 = task.checkScript?.path;
|
|
15305
|
+
if (!path25 || path25.trim().length === 0) {
|
|
15198
15306
|
errors.push({ path: `${prefix}.checkScript.path`, message: "checkScript.path is required" });
|
|
15199
15307
|
}
|
|
15200
15308
|
if (task.checkScript?.timeoutMs !== void 0 && task.checkScript.timeoutMs <= 0) {
|
|
@@ -15320,9 +15428,9 @@ function handleEdge(top, next, color, stack, errors, reported) {
|
|
|
15320
15428
|
stack.push({ id: next, nextIdx: 0, path: [...top.path, next] });
|
|
15321
15429
|
}
|
|
15322
15430
|
}
|
|
15323
|
-
function reportCycle(
|
|
15324
|
-
const cycleStart =
|
|
15325
|
-
const cyclePath = cycleStart >= 0 ? [...
|
|
15431
|
+
function reportCycle(path25, next, errors, reported) {
|
|
15432
|
+
const cycleStart = path25.indexOf(next);
|
|
15433
|
+
const cyclePath = cycleStart >= 0 ? [...path25.slice(cycleStart), next] : [...path25, next];
|
|
15326
15434
|
const key = cyclePath.join("\u2192");
|
|
15327
15435
|
if (reported.has(key)) return;
|
|
15328
15436
|
reported.add(key);
|
|
@@ -16810,7 +16918,9 @@ ${messages}`);
|
|
|
16810
16918
|
await this.emitWorkerExit(issue.id, "error", attempt, "Stopped by reconciliation");
|
|
16811
16919
|
}
|
|
16812
16920
|
} else {
|
|
16813
|
-
const
|
|
16921
|
+
const qualityClass = await this.deriveSingleAgentQualityVerdict(issue, workspacePath);
|
|
16922
|
+
const retroClass = await this.deriveRoutingRetrospectiveVerdict(issue, workspacePath);
|
|
16923
|
+
const outcomeClass = qualityClass ?? retroClass;
|
|
16814
16924
|
await this.emitWorkerExit(issue.id, "normal", attempt, void 0, outcomeClass);
|
|
16815
16925
|
}
|
|
16816
16926
|
} catch (error) {
|
|
@@ -16885,7 +16995,7 @@ ${messages}`);
|
|
|
16885
16995
|
try {
|
|
16886
16996
|
const diff = await this.workspace.getIntroducedDiffText(issue.identifier);
|
|
16887
16997
|
if (diff.trim() === "") return void 0;
|
|
16888
|
-
const evaluator = new
|
|
16998
|
+
const evaluator = new import_intelligence12.OutcomeEvaluator(provider, this.graphStore ?? new import_graph2.GraphStore(), {
|
|
16889
16999
|
...acceptanceEval.model !== void 0 ? { model: acceptanceEval.model } : {}
|
|
16890
17000
|
});
|
|
16891
17001
|
const verdict = await evaluator.evaluate({
|
|
@@ -16912,6 +17022,150 @@ ${messages}`);
|
|
|
16912
17022
|
return void 0;
|
|
16913
17023
|
}
|
|
16914
17024
|
}
|
|
17025
|
+
/**
|
|
17026
|
+
* Roadmap Auto-Triage Phase 4 (SC1–SC3, SC5, SC7): the post-diff routing
|
|
17027
|
+
* retrospective — a SIBLING quality-verdict source to the 4c feeder above,
|
|
17028
|
+
* extending (not replacing) the proven escalation path. On a normal exit, when AMR
|
|
17029
|
+
* is live AND auto-triage is enabled AND this unit carries a stored pre-dispatch
|
|
17030
|
+
* prediction, it classifies the ACTUAL introduced diff at full strength
|
|
17031
|
+
* (phase:'post-diff') and grades it against that prediction:
|
|
17032
|
+
* - MATCH → records the graded outcome + annotates the PR "AI autonomous —
|
|
17033
|
+
* verify" (stage-2 human-verify handling, SC4) → returns `undefined`
|
|
17034
|
+
* (neutral: a match is not an escalation).
|
|
17035
|
+
* - MISMATCH (diff exceeded prediction, or a missing/garbled prediction, or ANY
|
|
17036
|
+
* error) → records the outcome (when a shape is known) + returns
|
|
17037
|
+
* `'quality-fail'`, which climbs the coherence unit's escalation floor
|
|
17038
|
+
* via `recordAmrOutcome`; exhaustion queues `needs-human` (SC3/SC7).
|
|
17039
|
+
*
|
|
17040
|
+
* Fail-safe & guarded (SC7): an error never a silent pass — it takes the MISMATCH
|
|
17041
|
+
* (block+escalate) path. No-op (byte-identical) when AMR is off, auto-triage is
|
|
17042
|
+
* off, or the unit was not dispatched through triage (no stored prediction) — the
|
|
17043
|
+
* last means an ordinary non-triaged run is never graded.
|
|
17044
|
+
*/
|
|
17045
|
+
async deriveRoutingRetrospectiveVerdict(issue, _workspacePath) {
|
|
17046
|
+
if (this.adaptiveRouter === null) return void 0;
|
|
17047
|
+
if (this.config.roadmap?.autoTriage?.enabled !== true) return void 0;
|
|
17048
|
+
const externalId = issue.externalId;
|
|
17049
|
+
if (externalId === null || externalId === "") return void 0;
|
|
17050
|
+
let record;
|
|
17051
|
+
try {
|
|
17052
|
+
const loaded = await import_core17.eventSourcing.loadTriageRecords(this.projectRoot);
|
|
17053
|
+
if (!loaded.ok) throw loaded.error;
|
|
17054
|
+
record = loaded.value.find((r) => r.externalId === externalId);
|
|
17055
|
+
} catch (err) {
|
|
17056
|
+
this.logger.debug("amr retrospective skipped (record load failed)", {
|
|
17057
|
+
issueId: issue.id,
|
|
17058
|
+
error: err instanceof Error ? err.message : String(err)
|
|
17059
|
+
});
|
|
17060
|
+
if (issue.spec !== null && issue.spec !== void 0 && issue.spec !== "") {
|
|
17061
|
+
this.logger.info(
|
|
17062
|
+
"amr:quality-fail \u2014 spec-bearing (triaged) unit but the triage store is unreadable (fail-safe block: a triaged unit whose prediction we cannot read never silently passes)",
|
|
17063
|
+
{ issueId: issue.id, externalId }
|
|
17064
|
+
);
|
|
17065
|
+
return "quality-fail";
|
|
17066
|
+
}
|
|
17067
|
+
return void 0;
|
|
17068
|
+
}
|
|
17069
|
+
if (record === void 0) return void 0;
|
|
17070
|
+
const stored = record.prediction;
|
|
17071
|
+
if (stored === void 0) {
|
|
17072
|
+
this.logger.info(
|
|
17073
|
+
"amr:quality-fail \u2014 triaged unit has no stored prediction (fail-safe block)",
|
|
17074
|
+
{
|
|
17075
|
+
issueId: issue.id,
|
|
17076
|
+
externalId
|
|
17077
|
+
}
|
|
17078
|
+
);
|
|
17079
|
+
return "quality-fail";
|
|
17080
|
+
}
|
|
17081
|
+
const prediction = {
|
|
17082
|
+
verdict: stored.verdict,
|
|
17083
|
+
levers: stored.levers,
|
|
17084
|
+
scopeEstimate: stored.scopeEstimate,
|
|
17085
|
+
ratchetStage: stored.ratchetStage
|
|
17086
|
+
};
|
|
17087
|
+
try {
|
|
17088
|
+
const hunks = await this.workspace.getIntroducedDiff(issue.identifier);
|
|
17089
|
+
const taskText = buildTaskText(issue);
|
|
17090
|
+
const exceededByBands = this.config.roadmap?.autoTriage?.thresholds?.exceededByBands ?? 1;
|
|
17091
|
+
const result = await runRetrospective(
|
|
17092
|
+
{
|
|
17093
|
+
hunks,
|
|
17094
|
+
textOnly: {
|
|
17095
|
+
descriptionLength: taskText.descriptionLength,
|
|
17096
|
+
specExists: taskText.specExists,
|
|
17097
|
+
acceptanceMeasurable: taskText.acceptanceMeasurable
|
|
17098
|
+
},
|
|
17099
|
+
prediction,
|
|
17100
|
+
riskHigh: false,
|
|
17101
|
+
prompt: taskText.prompt,
|
|
17102
|
+
comparatorConfig: { exceededByBands }
|
|
17103
|
+
},
|
|
17104
|
+
this.resolveComplexityProvider()
|
|
17105
|
+
);
|
|
17106
|
+
const outcomeInput = buildTriageOutcomeInput(externalId, record.shapeKey, result);
|
|
17107
|
+
const recorded = await import_core17.eventSourcing.recordTriageOutcome(this.projectRoot, outcomeInput);
|
|
17108
|
+
if (!recorded.ok) {
|
|
17109
|
+
this.logger.warn("amr retrospective outcome record failed (best-effort)", {
|
|
17110
|
+
issueId: issue.id,
|
|
17111
|
+
error: recorded.error.message
|
|
17112
|
+
});
|
|
17113
|
+
}
|
|
17114
|
+
if (result.comparison.action === "block-escalate") {
|
|
17115
|
+
this.logger.info("amr:quality-fail \u2014 post-diff retrospective mispredict (block+escalate)", {
|
|
17116
|
+
issueId: issue.id,
|
|
17117
|
+
externalId,
|
|
17118
|
+
predicted: prediction.verdict.level,
|
|
17119
|
+
actual: result.actual.level,
|
|
17120
|
+
exceededBy: result.comparison.exceededBy
|
|
17121
|
+
});
|
|
17122
|
+
return "quality-fail";
|
|
17123
|
+
}
|
|
17124
|
+
await this.annotateRetrospectiveMatch(issue, externalId, result.actual.level);
|
|
17125
|
+
return void 0;
|
|
17126
|
+
} catch (err) {
|
|
17127
|
+
this.logger.info("amr:quality-fail \u2014 post-diff retrospective errored (fail-safe block)", {
|
|
17128
|
+
issueId: issue.id,
|
|
17129
|
+
externalId,
|
|
17130
|
+
error: err instanceof Error ? err.message : String(err)
|
|
17131
|
+
});
|
|
17132
|
+
return "quality-fail";
|
|
17133
|
+
}
|
|
17134
|
+
}
|
|
17135
|
+
/**
|
|
17136
|
+
* v1 stage-2 match handling (SC4): annotate the unit's PR/issue with a "verify this
|
|
17137
|
+
* autonomous change" note so a human reviews every autonomous PR. Best-effort — a
|
|
17138
|
+
* missing tracker config / token / a failed comment never breaks completion (the
|
|
17139
|
+
* grade already recorded above). Mirrors `postLifecycleComment`'s adapter wiring.
|
|
17140
|
+
*/
|
|
17141
|
+
async annotateRetrospectiveMatch(issue, externalId, actualLevel) {
|
|
17142
|
+
try {
|
|
17143
|
+
const trackerConfig = (0, import_core17.loadTrackerSyncConfig)(this.projectRoot);
|
|
17144
|
+
if (!trackerConfig) return;
|
|
17145
|
+
const token = process.env.GITHUB_TOKEN;
|
|
17146
|
+
if (!token) return;
|
|
17147
|
+
const orchestratorId = await this.orchestratorIdPromise;
|
|
17148
|
+
const adapter = new import_core17.GitHubIssuesSyncAdapter({ token, config: trackerConfig });
|
|
17149
|
+
const body = [
|
|
17150
|
+
`**AI autonomous change \u2014 please verify** \`${orchestratorId}\``,
|
|
17151
|
+
"",
|
|
17152
|
+
"The post-diff retrospective classified this change WITHIN its pre-dispatch",
|
|
17153
|
+
`prediction (actual complexity: \`${actualLevel}\`). Under autonomy ratchet`,
|
|
17154
|
+
"stage 2, a human must verify every autonomous PR before it merges."
|
|
17155
|
+
].join("\n");
|
|
17156
|
+
const result = await adapter.addComment(externalId, body);
|
|
17157
|
+
if (!result.ok) {
|
|
17158
|
+
this.logger.warn(`amr retrospective annotation failed for ${issue.identifier}`, {
|
|
17159
|
+
error: result.error.message
|
|
17160
|
+
});
|
|
17161
|
+
}
|
|
17162
|
+
} catch (err) {
|
|
17163
|
+
this.logger.debug("amr retrospective annotation skipped (best-effort)", {
|
|
17164
|
+
issueId: issue.id,
|
|
17165
|
+
error: err instanceof Error ? err.message : String(err)
|
|
17166
|
+
});
|
|
17167
|
+
}
|
|
17168
|
+
}
|
|
16915
17169
|
/**
|
|
16916
17170
|
* Informs the state machine that an agent worker has exited.
|
|
16917
17171
|
*/
|
|
@@ -17998,6 +18252,333 @@ function launchTUI(orchestrator) {
|
|
|
17998
18252
|
return { waitUntilExit };
|
|
17999
18253
|
}
|
|
18000
18254
|
|
|
18255
|
+
// src/agent/triage-wiring.ts
|
|
18256
|
+
var import_graph3 = require("@harness-engineering/graph");
|
|
18257
|
+
var import_intelligence13 = require("@harness-engineering/intelligence");
|
|
18258
|
+
function buildProbeInput(issue) {
|
|
18259
|
+
const taskText = buildTaskText(issue);
|
|
18260
|
+
const combined = `${issue.title ?? ""}
|
|
18261
|
+
${issue.description ?? ""}`.trim();
|
|
18262
|
+
return {
|
|
18263
|
+
// An item lacking a stable externalId is not dispatch-eligible (proposal Assumptions);
|
|
18264
|
+
// we still probe it (read-only report), keyed by its identifier as a fallback.
|
|
18265
|
+
externalId: issue.externalId ?? issue.identifier ?? issue.id,
|
|
18266
|
+
taskText,
|
|
18267
|
+
entityCandidates: (0, import_intelligence13.extractEntities)(combined),
|
|
18268
|
+
labels: issue.labels ?? []
|
|
18269
|
+
};
|
|
18270
|
+
}
|
|
18271
|
+
function findNode(store, candidate) {
|
|
18272
|
+
const trimmed = candidate.trim();
|
|
18273
|
+
if (trimmed.length === 0) return null;
|
|
18274
|
+
const byName = store.findNodes({ name: trimmed });
|
|
18275
|
+
if (byName.length > 0) return byName[0] ?? null;
|
|
18276
|
+
const byPath = store.findNodes({ path: trimmed });
|
|
18277
|
+
if (byPath.length > 0) return byPath[0] ?? null;
|
|
18278
|
+
const byId = store.getNode(`file:${trimmed}`);
|
|
18279
|
+
if (byId) return byId;
|
|
18280
|
+
return null;
|
|
18281
|
+
}
|
|
18282
|
+
function makeGraphScope(store) {
|
|
18283
|
+
const simulator = new import_graph3.CascadeSimulator(store);
|
|
18284
|
+
return {
|
|
18285
|
+
resolve(candidate) {
|
|
18286
|
+
try {
|
|
18287
|
+
const node = findNode(store, candidate);
|
|
18288
|
+
if (!node) return null;
|
|
18289
|
+
const result = simulator.simulate(node.id);
|
|
18290
|
+
return {
|
|
18291
|
+
candidate,
|
|
18292
|
+
nodeId: node.id,
|
|
18293
|
+
blastRadius: result.summary.totalAffected
|
|
18294
|
+
};
|
|
18295
|
+
} catch {
|
|
18296
|
+
return null;
|
|
18297
|
+
}
|
|
18298
|
+
}
|
|
18299
|
+
};
|
|
18300
|
+
}
|
|
18301
|
+
async function triageIssue2(issue, deps = {}) {
|
|
18302
|
+
const input = buildProbeInput(issue);
|
|
18303
|
+
const graph = deps.graphStore ? makeGraphScope(deps.graphStore) : void 0;
|
|
18304
|
+
return (0, import_intelligence13.runScopingProbe)(input, {
|
|
18305
|
+
...deps.provider ? { provider: deps.provider } : {},
|
|
18306
|
+
...graph ? { graph } : {},
|
|
18307
|
+
...deps.precedent ? { precedent: deps.precedent } : {},
|
|
18308
|
+
...deps.config ? { config: deps.config } : {},
|
|
18309
|
+
...deps.models ? { models: deps.models } : {}
|
|
18310
|
+
});
|
|
18311
|
+
}
|
|
18312
|
+
|
|
18313
|
+
// src/agent/brainstorm-wiring.ts
|
|
18314
|
+
var fs16 = __toESM(require("fs"));
|
|
18315
|
+
var path22 = __toESM(require("path"));
|
|
18316
|
+
var import_zod18 = require("zod");
|
|
18317
|
+
var import_intelligence14 = require("@harness-engineering/intelligence");
|
|
18318
|
+
var ForkStepSchema = import_zod18.z.object({
|
|
18319
|
+
done: import_zod18.z.boolean().describe("true when there are no more design forks to decide"),
|
|
18320
|
+
forkId: import_zod18.z.string().describe('short stable id for this fork, e.g. "storage-backend"').optional(),
|
|
18321
|
+
question: import_zod18.z.string().describe("the design question this fork decides").optional(),
|
|
18322
|
+
options: import_zod18.z.array(import_zod18.z.string()).describe("the mutually-exclusive options considered").optional(),
|
|
18323
|
+
// The AUTHORITATIVE recommendation validity checks (non-empty AND one of `options`) live in
|
|
18324
|
+
// the GENERATOR, where `fork.options` is known and where a degenerate value must map to a
|
|
18325
|
+
// `confidence:'low'` DOWNGRADE (→ runner halts with reason 'low-confidence'), not a hard
|
|
18326
|
+
// schema/provider throw. We deliberately keep this `optional()`/unconstrained so an empty or
|
|
18327
|
+
// absent recommendation still reaches the generator's semantic gate rather than short-
|
|
18328
|
+
// circuiting to `halted{ reason:'error' }`. See makeSelForkGenerator's SAFETY GATE 2.
|
|
18329
|
+
recommendation: import_zod18.z.string().describe("the recommended option (one of options)").optional(),
|
|
18330
|
+
confidence: import_zod18.z.enum(["high", "medium", "low"]).describe("self-assessed confidence in the recommendation").optional(),
|
|
18331
|
+
rationale: import_zod18.z.string().describe("why this option").optional()
|
|
18332
|
+
});
|
|
18333
|
+
var BRAINSTORM_RUBRIC = 'You are running an AUTONOMOUS brainstorm over one backlog item: enumerate each design fork (a decision with mutually-exclusive options), then recommend a default for the NEXT unresolved fork with a self-assessed confidence. Report confidence HIGH only when the recommendation is clearly correct on technical grounds alone. Report LOW (so a human decides) whenever the fork involves ANY of: a product or UX tradeoff, an unrevealed business priority, a security-sensitive or irreversible/outward-facing action, or a genuine judgment call between comparable options. Never invent forks to pad the spec; set done=true once every real fork is resolved. Be conservative \u2014 a false "high" is far more costly than asking a human.';
|
|
18334
|
+
function makeSelForkGenerator(provider, input, opts = {}) {
|
|
18335
|
+
const samples = Math.max(2, opts.samples ?? 3);
|
|
18336
|
+
const maxTokens = opts.maxTokens ?? 512;
|
|
18337
|
+
return {
|
|
18338
|
+
async next(index, priorDecisions) {
|
|
18339
|
+
const prompt = buildForkPrompt(input, priorDecisions, index);
|
|
18340
|
+
const steps = [];
|
|
18341
|
+
for (let s = 0; s < samples; s++) {
|
|
18342
|
+
const { result } = await provider.analyze({
|
|
18343
|
+
prompt,
|
|
18344
|
+
systemPrompt: BRAINSTORM_RUBRIC,
|
|
18345
|
+
responseSchema: ForkStepSchema,
|
|
18346
|
+
maxTokens,
|
|
18347
|
+
...opts.model !== void 0 ? { model: opts.model } : {}
|
|
18348
|
+
});
|
|
18349
|
+
steps.push(result);
|
|
18350
|
+
}
|
|
18351
|
+
if (steps.some((st) => st.done)) return null;
|
|
18352
|
+
const first = steps[0];
|
|
18353
|
+
if (!first) return null;
|
|
18354
|
+
const fork = {
|
|
18355
|
+
id: first.forkId ?? `fork-${index}`,
|
|
18356
|
+
question: first.question ?? "unspecified fork",
|
|
18357
|
+
options: first.options ?? []
|
|
18358
|
+
};
|
|
18359
|
+
const recommendation = first.recommendation ?? "";
|
|
18360
|
+
const normQ = (q) => (q ?? "").trim().toLowerCase();
|
|
18361
|
+
const firstForkId = first.forkId ?? "";
|
|
18362
|
+
const firstQuestion = normQ(first.question);
|
|
18363
|
+
const identityStable = steps.every(
|
|
18364
|
+
(st) => (st.forkId ?? "") === firstForkId && normQ(st.question) === firstQuestion
|
|
18365
|
+
);
|
|
18366
|
+
const recStable = steps.every((st) => (st.recommendation ?? "") === recommendation);
|
|
18367
|
+
const recEmpty = recommendation.trim() === "";
|
|
18368
|
+
const recNotAnOption = !recEmpty && fork.options.length > 0 && !fork.options.includes(recommendation);
|
|
18369
|
+
const stable = identityStable && recStable && !recEmpty && !recNotAnOption;
|
|
18370
|
+
const reported = first.confidence ?? "low";
|
|
18371
|
+
const confidence = stable ? reported : "low";
|
|
18372
|
+
let downgradeReason = "";
|
|
18373
|
+
if (!identityStable) {
|
|
18374
|
+
downgradeReason = `fork identity drift across ${samples} samples (self-consistency downgrade to low): ` + steps.map((st) => `${st.forkId ?? "\u2205"}:"${(st.question ?? "\u2205").trim()}"`).join(" / ");
|
|
18375
|
+
} else if (recEmpty) {
|
|
18376
|
+
downgradeReason = "recommendation was empty/absent (a content-free recommendation is not a confident decision; downgrade to low)";
|
|
18377
|
+
} else if (recNotAnOption) {
|
|
18378
|
+
downgradeReason = `recommendation '${recommendation}' is not one of the offered options [${fork.options.join(", ")}] (downgrade to low)`;
|
|
18379
|
+
} else if (!recStable) {
|
|
18380
|
+
downgradeReason = `recommendation was unstable across ${samples} samples (self-consistency downgrade to low): ` + steps.map((st) => st.recommendation ?? "\u2205").join(" / ");
|
|
18381
|
+
}
|
|
18382
|
+
return {
|
|
18383
|
+
fork,
|
|
18384
|
+
recommendation,
|
|
18385
|
+
confidence,
|
|
18386
|
+
rationale: stable ? first.rationale ?? "" : downgradeReason
|
|
18387
|
+
};
|
|
18388
|
+
}
|
|
18389
|
+
};
|
|
18390
|
+
}
|
|
18391
|
+
function buildForkPrompt(input, priorDecisions, index) {
|
|
18392
|
+
const resolved = priorDecisions.length === 0 ? "(none yet)" : priorDecisions.map((d, i) => `${i + 1}. ${d.fork.question} \u2192 ${d.recommendation}`).join("\n");
|
|
18393
|
+
return `Item: ${input.title}
|
|
18394
|
+
Summary: ${input.summary}
|
|
18395
|
+
Complexity: ${input.level}
|
|
18396
|
+
|
|
18397
|
+
Forks already resolved:
|
|
18398
|
+
${resolved}
|
|
18399
|
+
|
|
18400
|
+
Decide fork #${index + 1}: the NEXT unresolved design fork. If every real fork is already resolved, return { "done": true }.`;
|
|
18401
|
+
}
|
|
18402
|
+
function brainstormInputFromIssue(issue, level) {
|
|
18403
|
+
return {
|
|
18404
|
+
externalId: issue.externalId ?? issue.identifier ?? issue.id,
|
|
18405
|
+
title: issue.title ?? issue.identifier ?? issue.id,
|
|
18406
|
+
summary: issue.description ?? "",
|
|
18407
|
+
level
|
|
18408
|
+
};
|
|
18409
|
+
}
|
|
18410
|
+
async function runBrainstormForIssue(issue, level, deps) {
|
|
18411
|
+
const input = brainstormInputFromIssue(issue, level);
|
|
18412
|
+
const generator = deps.generator ?? (deps.provider ? makeSelForkGenerator(deps.provider, input, deps.generatorOptions ?? {}) : void 0);
|
|
18413
|
+
if (!generator) {
|
|
18414
|
+
return {
|
|
18415
|
+
outcome: {
|
|
18416
|
+
kind: "halted",
|
|
18417
|
+
fork: { id: "no-generator", question: "no fork generator or provider wired", options: [] },
|
|
18418
|
+
reason: "error",
|
|
18419
|
+
detail: "runBrainstormForIssue requires either an injected generator or a SEL provider"
|
|
18420
|
+
}
|
|
18421
|
+
};
|
|
18422
|
+
}
|
|
18423
|
+
const depth = (0, import_intelligence14.depthForLevel)(level);
|
|
18424
|
+
const outcome = await (0, import_intelligence14.runAutoBrainstorm)(input, generator, depth);
|
|
18425
|
+
if (outcome.kind !== "completed") {
|
|
18426
|
+
return { outcome };
|
|
18427
|
+
}
|
|
18428
|
+
const result = { outcome };
|
|
18429
|
+
if (deps.docsRoot) {
|
|
18430
|
+
try {
|
|
18431
|
+
result.specPath = writeSpecDoc(deps.docsRoot, outcome.spec);
|
|
18432
|
+
} catch {
|
|
18433
|
+
}
|
|
18434
|
+
}
|
|
18435
|
+
if (deps.rescore) {
|
|
18436
|
+
try {
|
|
18437
|
+
const enrichedIssue = enrichIssueWithSpec(issue, outcome.spec);
|
|
18438
|
+
result.rescore = await triageIssue2(enrichedIssue, {
|
|
18439
|
+
...deps.rescore.graphStore ? { graphStore: deps.rescore.graphStore } : {},
|
|
18440
|
+
...deps.rescore.provider ? { provider: deps.rescore.provider } : {},
|
|
18441
|
+
...deps.rescore.precedent ? { precedent: deps.rescore.precedent } : {},
|
|
18442
|
+
...deps.rescore.config ? { config: deps.rescore.config } : {},
|
|
18443
|
+
...deps.rescore.models ? { models: deps.rescore.models } : {}
|
|
18444
|
+
});
|
|
18445
|
+
} catch {
|
|
18446
|
+
}
|
|
18447
|
+
}
|
|
18448
|
+
return result;
|
|
18449
|
+
}
|
|
18450
|
+
function slugFor(spec) {
|
|
18451
|
+
const base = spec.title || spec.externalId;
|
|
18452
|
+
return base.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60) || "item";
|
|
18453
|
+
}
|
|
18454
|
+
function renderSpecMarkdown(spec) {
|
|
18455
|
+
const lines = [];
|
|
18456
|
+
lines.push(`# ${spec.title}`);
|
|
18457
|
+
lines.push("");
|
|
18458
|
+
lines.push("Status: **Auto-drafted (autonomous brainstorm \u2014 awaiting human review)**");
|
|
18459
|
+
lines.push("");
|
|
18460
|
+
lines.push(`Item: \`${spec.externalId}\``);
|
|
18461
|
+
lines.push("");
|
|
18462
|
+
lines.push("## Summary");
|
|
18463
|
+
lines.push("");
|
|
18464
|
+
lines.push(spec.summary || "_No summary provided._");
|
|
18465
|
+
lines.push("");
|
|
18466
|
+
lines.push("## Design decisions");
|
|
18467
|
+
lines.push("");
|
|
18468
|
+
if (spec.decisions.length === 0) {
|
|
18469
|
+
lines.push("_No forks required a decision \u2014 the item was unambiguous._");
|
|
18470
|
+
} else {
|
|
18471
|
+
for (const [i, d] of spec.decisions.entries()) {
|
|
18472
|
+
lines.push(`### ${i + 1}. ${d.fork.question}`);
|
|
18473
|
+
lines.push("");
|
|
18474
|
+
if (d.fork.options.length > 0) {
|
|
18475
|
+
lines.push(`Options considered: ${d.fork.options.map((o) => `\`${o}\``).join(", ")}`);
|
|
18476
|
+
lines.push("");
|
|
18477
|
+
}
|
|
18478
|
+
lines.push(`**Recommendation:** ${d.recommendation} (confidence: ${d.confidence})`);
|
|
18479
|
+
lines.push("");
|
|
18480
|
+
if (d.rationale) {
|
|
18481
|
+
lines.push(d.rationale);
|
|
18482
|
+
lines.push("");
|
|
18483
|
+
}
|
|
18484
|
+
}
|
|
18485
|
+
}
|
|
18486
|
+
lines.push("---");
|
|
18487
|
+
lines.push("");
|
|
18488
|
+
lines.push(
|
|
18489
|
+
"_This spec was drafted autonomously. Every fork above cleared the high-confidence bar; any fork the system could not confidently resolve would have halted the brainstorm and handed that fork to a human instead._"
|
|
18490
|
+
);
|
|
18491
|
+
lines.push("");
|
|
18492
|
+
return lines.join("\n");
|
|
18493
|
+
}
|
|
18494
|
+
function writeSpecDoc(docsRoot, spec) {
|
|
18495
|
+
const dir = path22.join(docsRoot, "changes", slugFor(spec));
|
|
18496
|
+
fs16.mkdirSync(dir, { recursive: true });
|
|
18497
|
+
const file = path22.join(dir, "proposal.md");
|
|
18498
|
+
fs16.writeFileSync(file, renderSpecMarkdown(spec), "utf-8");
|
|
18499
|
+
return file;
|
|
18500
|
+
}
|
|
18501
|
+
function enrichIssueWithSpec(issue, spec) {
|
|
18502
|
+
const decisionText = spec.decisions.map((d) => `${d.fork.question} \u2192 ${d.recommendation}. ${d.rationale}`).join("\n");
|
|
18503
|
+
const enrichedDescription = [issue.description ?? "", decisionText].filter(Boolean).join("\n\n");
|
|
18504
|
+
return {
|
|
18505
|
+
...issue,
|
|
18506
|
+
description: enrichedDescription,
|
|
18507
|
+
// The item now HAS a drafted spec — reflect it so the re-score's spec-exists signal is true.
|
|
18508
|
+
spec: issue.spec ?? `changes/${slugFor(spec)}/proposal.md`
|
|
18509
|
+
};
|
|
18510
|
+
}
|
|
18511
|
+
|
|
18512
|
+
// src/agent/triage-mark.ts
|
|
18513
|
+
var import_core20 = require("@harness-engineering/core");
|
|
18514
|
+
var import_intelligence15 = require("@harness-engineering/intelligence");
|
|
18515
|
+
var ELIGIBLE_STATUS = "planned";
|
|
18516
|
+
async function markApprovedForDispatch(items, deps) {
|
|
18517
|
+
const { store, config, recordPrediction, selfAssignee } = deps;
|
|
18518
|
+
if (!config.enabled) {
|
|
18519
|
+
return { ok: true, value: { marked: [], skipped: [] } };
|
|
18520
|
+
}
|
|
18521
|
+
const loaded = await store.load();
|
|
18522
|
+
if (!loaded.ok) return { ok: false, error: loaded.error };
|
|
18523
|
+
const bySlug = /* @__PURE__ */ new Map();
|
|
18524
|
+
for (const m of loaded.value.milestones) {
|
|
18525
|
+
for (const f of m.features) bySlug.set((0, import_core20.slugifyFeatureName)(f.name), f);
|
|
18526
|
+
}
|
|
18527
|
+
const marked = [];
|
|
18528
|
+
const skipped = [];
|
|
18529
|
+
for (const it of items) {
|
|
18530
|
+
const externalId = it.candidate.externalId;
|
|
18531
|
+
if (!it.candidate.humanApproved) {
|
|
18532
|
+
skipped.push({ externalId, reason: "not-approved" });
|
|
18533
|
+
continue;
|
|
18534
|
+
}
|
|
18535
|
+
const slug = (0, import_core20.slugifyFeatureName)(it.featureName);
|
|
18536
|
+
const target = bySlug.get(slug);
|
|
18537
|
+
if (!target) {
|
|
18538
|
+
skipped.push({ externalId, reason: "feature-not-found" });
|
|
18539
|
+
continue;
|
|
18540
|
+
}
|
|
18541
|
+
if (selfAssignee !== void 0 && target.assignee != null && target.assignee !== selfAssignee) {
|
|
18542
|
+
skipped.push({ externalId, reason: "assigned-elsewhere" });
|
|
18543
|
+
continue;
|
|
18544
|
+
}
|
|
18545
|
+
const mutate = (f) => ({
|
|
18546
|
+
...f,
|
|
18547
|
+
spec: it.specPath,
|
|
18548
|
+
status: isActiveStatus(f.status) ? f.status : ELIGIBLE_STATUS
|
|
18549
|
+
});
|
|
18550
|
+
const patched = await store.patchFeature(slug, mutate);
|
|
18551
|
+
if (!patched.ok) return { ok: false, error: patched.error };
|
|
18552
|
+
const key = (0, import_intelligence15.dispatchableShapeKey)(it.labels, it.verdict.level);
|
|
18553
|
+
const prediction = {
|
|
18554
|
+
externalId,
|
|
18555
|
+
shapeKey: key,
|
|
18556
|
+
verdict: it.verdict,
|
|
18557
|
+
// `levers` is an OPAQUE DIAGNOSTIC SNAPSHOT (the verdict's static signals), NOT the
|
|
18558
|
+
// typed ProbeLevers. The Phase-4 comparator grades on `verdict.level` +
|
|
18559
|
+
// `scopeEstimate` ONLY (see retrospective.ts) and never reads `levers`, so this
|
|
18560
|
+
// human-legible signal dump cannot mislead the grade. The full typed ProbeLevers
|
|
18561
|
+
// are not threaded here on purpose (it would balloon ReadyCandidate→marker for a
|
|
18562
|
+
// field nothing downstream consumes); `record.ts` re-documents the field to match.
|
|
18563
|
+
levers: it.verdict.signals,
|
|
18564
|
+
scopeEstimate: it.scopeEstimate,
|
|
18565
|
+
// Prefer the per-shape evidence-derived stage (SC6) when the caller resolved one;
|
|
18566
|
+
// fall back to the uniform config stage (Phase-3 behavior). Cold-start ⇒ the caller
|
|
18567
|
+
// resolves stage 1, so this is byte-identical to `config.ratchetStage` at cold-start.
|
|
18568
|
+
ratchetStage: it.effectiveStage ?? config.ratchetStage
|
|
18569
|
+
};
|
|
18570
|
+
const recorded = await recordPrediction(prediction);
|
|
18571
|
+
if (!recorded.ok) {
|
|
18572
|
+
return { ok: false, error: recorded.error ?? new Error("recordPrediction failed") };
|
|
18573
|
+
}
|
|
18574
|
+
marked.push(externalId);
|
|
18575
|
+
}
|
|
18576
|
+
return { ok: true, value: { marked, skipped } };
|
|
18577
|
+
}
|
|
18578
|
+
function isActiveStatus(status) {
|
|
18579
|
+
return status === "planned" || status === "in-progress";
|
|
18580
|
+
}
|
|
18581
|
+
|
|
18001
18582
|
// src/maintenance/sync-main.ts
|
|
18002
18583
|
var import_node_child_process14 = require("child_process");
|
|
18003
18584
|
var import_node_util5 = require("util");
|
|
@@ -18191,8 +18772,8 @@ function selectTasks(tasks, history, filter) {
|
|
|
18191
18772
|
}
|
|
18192
18773
|
|
|
18193
18774
|
// src/sessions/search-index.ts
|
|
18194
|
-
var
|
|
18195
|
-
var
|
|
18775
|
+
var fs17 = __toESM(require("fs"));
|
|
18776
|
+
var path23 = __toESM(require("path"));
|
|
18196
18777
|
var import_better_sqlite32 = __toESM(require("better-sqlite3"));
|
|
18197
18778
|
var import_types35 = require("@harness-engineering/types");
|
|
18198
18779
|
var SEARCH_INDEX_FILE = "search-index.sqlite";
|
|
@@ -18237,7 +18818,7 @@ function normalizeFts5Query(query) {
|
|
|
18237
18818
|
return query.split(/\s+/).filter((tok) => tok.length > 0).map((tok) => `"${tok.replace(/"/g, '""')}"`).join(" ");
|
|
18238
18819
|
}
|
|
18239
18820
|
function searchIndexPath(projectPath) {
|
|
18240
|
-
return
|
|
18821
|
+
return path23.join(projectPath, ".harness", SEARCH_INDEX_FILE);
|
|
18241
18822
|
}
|
|
18242
18823
|
var FILE_KIND_TO_FILENAME = {
|
|
18243
18824
|
summary: "summary.md",
|
|
@@ -18252,7 +18833,7 @@ var SqliteSearchIndex = class {
|
|
|
18252
18833
|
removeSessionStmt;
|
|
18253
18834
|
totalStmt;
|
|
18254
18835
|
constructor(dbPath) {
|
|
18255
|
-
|
|
18836
|
+
fs17.mkdirSync(path23.dirname(dbPath), { recursive: true });
|
|
18256
18837
|
this.db = new import_better_sqlite32.default(dbPath);
|
|
18257
18838
|
this.db.pragma("journal_mode = WAL");
|
|
18258
18839
|
this.db.pragma("synchronous = NORMAL");
|
|
@@ -18357,14 +18938,14 @@ function indexSessionDirectory(idx, args) {
|
|
|
18357
18938
|
let docsWritten = 0;
|
|
18358
18939
|
for (const kind of kinds) {
|
|
18359
18940
|
const fileName = FILE_KIND_TO_FILENAME[kind];
|
|
18360
|
-
const filePath =
|
|
18361
|
-
if (!
|
|
18362
|
-
let body =
|
|
18941
|
+
const filePath = path23.join(args.sessionDir, fileName);
|
|
18942
|
+
if (!fs17.existsSync(filePath)) continue;
|
|
18943
|
+
let body = fs17.readFileSync(filePath, "utf8");
|
|
18363
18944
|
if (Buffer.byteLength(body, "utf8") > cap) {
|
|
18364
18945
|
body = body.slice(0, cap) + "\n\n[TRUNCATED]";
|
|
18365
18946
|
}
|
|
18366
|
-
const stat =
|
|
18367
|
-
const relPath =
|
|
18947
|
+
const stat = fs17.statSync(filePath);
|
|
18948
|
+
const relPath = path23.relative(args.projectPath, filePath).replaceAll("\\", "/");
|
|
18368
18949
|
idx.upsertSessionDoc({
|
|
18369
18950
|
sessionId: args.sessionId,
|
|
18370
18951
|
archived: args.archived,
|
|
@@ -18379,17 +18960,17 @@ function indexSessionDirectory(idx, args) {
|
|
|
18379
18960
|
}
|
|
18380
18961
|
function reindexFromArchive(projectPath, opts = {}) {
|
|
18381
18962
|
const start = Date.now();
|
|
18382
|
-
const archiveBase =
|
|
18963
|
+
const archiveBase = path23.join(projectPath, ".harness", "archive", "sessions");
|
|
18383
18964
|
const idx = openSearchIndex(projectPath);
|
|
18384
18965
|
try {
|
|
18385
18966
|
idx.resetArchived();
|
|
18386
18967
|
let sessionsIndexed = 0;
|
|
18387
18968
|
let docsWritten = 0;
|
|
18388
|
-
if (
|
|
18389
|
-
const entries =
|
|
18969
|
+
if (fs17.existsSync(archiveBase)) {
|
|
18970
|
+
const entries = fs17.readdirSync(archiveBase, { withFileTypes: true });
|
|
18390
18971
|
for (const entry of entries) {
|
|
18391
18972
|
if (!entry.isDirectory()) continue;
|
|
18392
|
-
const sessionDir =
|
|
18973
|
+
const sessionDir = path23.join(archiveBase, entry.name);
|
|
18393
18974
|
const result = indexSessionDirectory(idx, {
|
|
18394
18975
|
sessionId: entry.name,
|
|
18395
18976
|
sessionDir,
|
|
@@ -18409,8 +18990,8 @@ function reindexFromArchive(projectPath, opts = {}) {
|
|
|
18409
18990
|
}
|
|
18410
18991
|
|
|
18411
18992
|
// src/sessions/summarize.ts
|
|
18412
|
-
var
|
|
18413
|
-
var
|
|
18993
|
+
var fs18 = __toESM(require("fs"));
|
|
18994
|
+
var path24 = __toESM(require("path"));
|
|
18414
18995
|
var import_types36 = require("@harness-engineering/types");
|
|
18415
18996
|
var import_types37 = require("@harness-engineering/types");
|
|
18416
18997
|
var LLM_SUMMARY_FILE = "llm-summary.md";
|
|
@@ -18438,10 +19019,10 @@ var USER_PROMPT_PREAMBLE = `Below are the archived files for a single harness-en
|
|
|
18438
19019
|
function readInputCorpus(archiveDir) {
|
|
18439
19020
|
const parts = [];
|
|
18440
19021
|
for (const { filename, kind } of SUMMARY_INPUT_FILES) {
|
|
18441
|
-
const p =
|
|
18442
|
-
if (!
|
|
19022
|
+
const p = path24.join(archiveDir, filename);
|
|
19023
|
+
if (!fs18.existsSync(p)) continue;
|
|
18443
19024
|
try {
|
|
18444
|
-
const content =
|
|
19025
|
+
const content = fs18.readFileSync(p, "utf8");
|
|
18445
19026
|
if (content.trim().length === 0) continue;
|
|
18446
19027
|
parts.push(`## FILE: ${kind}
|
|
18447
19028
|
|
|
@@ -18492,7 +19073,7 @@ function renderLlmSummaryMarkdown(summary, meta) {
|
|
|
18492
19073
|
return lines.join("\n");
|
|
18493
19074
|
}
|
|
18494
19075
|
function writeStubMarkdown(archiveDir, reason) {
|
|
18495
|
-
const filePath =
|
|
19076
|
+
const filePath = path24.join(archiveDir, LLM_SUMMARY_FILE);
|
|
18496
19077
|
const body = `---
|
|
18497
19078
|
generatedAt: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
18498
19079
|
schemaVersion: 1
|
|
@@ -18503,12 +19084,12 @@ status: failed
|
|
|
18503
19084
|
|
|
18504
19085
|
- reason: ${reason}
|
|
18505
19086
|
`;
|
|
18506
|
-
|
|
19087
|
+
fs18.writeFileSync(filePath, body, "utf8");
|
|
18507
19088
|
return filePath;
|
|
18508
19089
|
}
|
|
18509
19090
|
async function summarizeArchivedSession(ctx) {
|
|
18510
19091
|
const writeStubOnError = ctx.writeStubOnError ?? true;
|
|
18511
|
-
if (!
|
|
19092
|
+
if (!fs18.existsSync(ctx.archiveDir)) {
|
|
18512
19093
|
return (0, import_types37.Err)(new Error(`archive directory not found: ${ctx.archiveDir}`));
|
|
18513
19094
|
}
|
|
18514
19095
|
const corpus = readInputCorpus(ctx.archiveDir);
|
|
@@ -18569,9 +19150,9 @@ async function summarizeArchivedSession(ctx) {
|
|
|
18569
19150
|
outputTokens: response.tokenUsage.outputTokens,
|
|
18570
19151
|
schemaVersion: 1
|
|
18571
19152
|
};
|
|
18572
|
-
const filePath =
|
|
19153
|
+
const filePath = path24.join(ctx.archiveDir, LLM_SUMMARY_FILE);
|
|
18573
19154
|
const body = renderLlmSummaryMarkdown(parsed.data, meta);
|
|
18574
|
-
|
|
19155
|
+
fs18.writeFileSync(filePath, body, "utf8");
|
|
18575
19156
|
return (0, import_types37.Ok)({ summary: parsed.data, meta, filePath });
|
|
18576
19157
|
}
|
|
18577
19158
|
function isSummaryEnabled(config) {
|
|
@@ -18653,6 +19234,7 @@ var import_local_models7 = require("@harness-engineering/local-models");
|
|
|
18653
19234
|
0 && (module.exports = {
|
|
18654
19235
|
AdaptiveRouter,
|
|
18655
19236
|
AnalysisArchive,
|
|
19237
|
+
BRAINSTORM_RUBRIC,
|
|
18656
19238
|
BUILT_IN_TASKS,
|
|
18657
19239
|
BackendDefSchema,
|
|
18658
19240
|
BackendRouter,
|
|
@@ -18695,8 +19277,10 @@ var import_local_models7 = require("@harness-engineering/local-models");
|
|
|
18695
19277
|
WorkspaceManager,
|
|
18696
19278
|
applyEvent,
|
|
18697
19279
|
artifactPresenceFromIssue,
|
|
19280
|
+
brainstormInputFromIssue,
|
|
18698
19281
|
buildArchiveHooks,
|
|
18699
19282
|
buildCapabilityRegistry,
|
|
19283
|
+
buildProbeInput,
|
|
18700
19284
|
buildWorkflowContext,
|
|
18701
19285
|
calculateRetryDelay,
|
|
18702
19286
|
canDispatch,
|
|
@@ -18716,6 +19300,7 @@ var import_local_models7 = require("@harness-engineering/local-models");
|
|
|
18716
19300
|
emitProposalApproved,
|
|
18717
19301
|
emitProposalCreated,
|
|
18718
19302
|
emitProposalRejected,
|
|
19303
|
+
enrichIssueWithSpec,
|
|
18719
19304
|
estimateCost,
|
|
18720
19305
|
explicitFindingsCount,
|
|
18721
19306
|
extractHighlights,
|
|
@@ -18730,22 +19315,30 @@ var import_local_models7 = require("@harness-engineering/local-models");
|
|
|
18730
19315
|
launchTUI,
|
|
18731
19316
|
loadPublishedIndex,
|
|
18732
19317
|
makeBackendResolver,
|
|
19318
|
+
makeGraphScope,
|
|
19319
|
+
makeSelForkGenerator,
|
|
19320
|
+
markApprovedForDispatch,
|
|
18733
19321
|
migrateAgentConfig,
|
|
18734
19322
|
normalizeFts5Query,
|
|
18735
19323
|
normalizeHarnessCommand,
|
|
18736
19324
|
normalizeLocalModel,
|
|
18737
19325
|
openSearchIndex,
|
|
19326
|
+
pilotScore,
|
|
19327
|
+
precedentLookupFromStored,
|
|
18738
19328
|
promote,
|
|
19329
|
+
rankTriageCandidates,
|
|
18739
19330
|
reconcile,
|
|
18740
19331
|
recoverFindingsCount,
|
|
18741
19332
|
reindexFromArchive,
|
|
18742
19333
|
renderAnalysisComment,
|
|
18743
19334
|
renderLlmSummaryMarkdown,
|
|
18744
19335
|
renderPRComment,
|
|
19336
|
+
renderSpecMarkdown,
|
|
18745
19337
|
resolveEscalationConfig,
|
|
18746
19338
|
resolveOrchestratorId,
|
|
18747
19339
|
routeIssue,
|
|
18748
19340
|
routingWarnings,
|
|
19341
|
+
runBrainstormForIssue,
|
|
18749
19342
|
runGate,
|
|
18750
19343
|
runHarnessCheck,
|
|
18751
19344
|
savePublishedIndex,
|
|
@@ -18753,6 +19346,7 @@ var import_local_models7 = require("@harness-engineering/local-models");
|
|
|
18753
19346
|
selectCandidates,
|
|
18754
19347
|
selectCheapestQualifying,
|
|
18755
19348
|
selectTasks,
|
|
19349
|
+
slugFor,
|
|
18756
19350
|
sortCandidates,
|
|
18757
19351
|
summarizeArchivedSession,
|
|
18758
19352
|
syncMain,
|