@harness-engineering/intelligence 0.2.6 → 0.3.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 +263 -1
- package/dist/index.d.ts +263 -1
- package/dist/index.js +419 -27
- package/dist/index.mjs +410 -25
- package/package.json +6 -3
package/dist/index.mjs
CHANGED
|
@@ -106,6 +106,105 @@ function manualToRawWorkItem(input) {
|
|
|
106
106
|
};
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
+
// src/adapters/canary.ts
|
|
110
|
+
import { z } from "zod";
|
|
111
|
+
import { execFile } from "child_process";
|
|
112
|
+
var frameworkRecommendationSchema = z.object({
|
|
113
|
+
status: z.string(),
|
|
114
|
+
test_type: z.string(),
|
|
115
|
+
framework: z.string(),
|
|
116
|
+
file_extension: z.string(),
|
|
117
|
+
reasoning: z.array(z.string()),
|
|
118
|
+
alternatives: z.array(z.string())
|
|
119
|
+
});
|
|
120
|
+
var canaryFindingSchema = z.object({
|
|
121
|
+
file: z.string(),
|
|
122
|
+
line: z.number(),
|
|
123
|
+
rule: z.string(),
|
|
124
|
+
severity: z.string(),
|
|
125
|
+
message: z.string(),
|
|
126
|
+
suggestion: z.string()
|
|
127
|
+
});
|
|
128
|
+
var canaryFindingsSchema = z.array(canaryFindingSchema);
|
|
129
|
+
var EXEC_TIMEOUT_MS = 3e4;
|
|
130
|
+
var EXEC_MAX_BUFFER = 16 * 1024 * 1024;
|
|
131
|
+
var defaultExec = (cmd, args) => new Promise((resolve, reject) => {
|
|
132
|
+
execFile(
|
|
133
|
+
cmd,
|
|
134
|
+
args,
|
|
135
|
+
{ encoding: "utf8", timeout: EXEC_TIMEOUT_MS, maxBuffer: EXEC_MAX_BUFFER },
|
|
136
|
+
(err, stdout) => {
|
|
137
|
+
if (err) {
|
|
138
|
+
reject(err);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
resolve({ stdout });
|
|
142
|
+
}
|
|
143
|
+
);
|
|
144
|
+
});
|
|
145
|
+
function canaryInvocation(subArgs) {
|
|
146
|
+
return ["canary", subArgs];
|
|
147
|
+
}
|
|
148
|
+
function parseVersion(stdout) {
|
|
149
|
+
return stdout.match(/\d+\.\d+\.\d+/)?.[0];
|
|
150
|
+
}
|
|
151
|
+
function safeJson(stdout) {
|
|
152
|
+
try {
|
|
153
|
+
return JSON.parse(stdout);
|
|
154
|
+
} catch {
|
|
155
|
+
return void 0;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
function degradedRecommendation() {
|
|
159
|
+
return {
|
|
160
|
+
status: "degraded",
|
|
161
|
+
test_type: "",
|
|
162
|
+
framework: "",
|
|
163
|
+
file_extension: "",
|
|
164
|
+
reasoning: [],
|
|
165
|
+
alternatives: []
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
function createCanaryAdapter(exec = defaultExec) {
|
|
169
|
+
async function execCanary(subArgs) {
|
|
170
|
+
const [cmd, args] = canaryInvocation(subArgs);
|
|
171
|
+
try {
|
|
172
|
+
const { stdout } = await exec(cmd, args);
|
|
173
|
+
return { ok: true, stdout };
|
|
174
|
+
} catch (err) {
|
|
175
|
+
const e = err;
|
|
176
|
+
if (e.code === "ENOENT") return { ok: false, reason: "not-installed" };
|
|
177
|
+
if (e.code === 1 && /canary binary not found/i.test(e.stderr ?? "")) {
|
|
178
|
+
return { ok: false, reason: "binary-missing" };
|
|
179
|
+
}
|
|
180
|
+
return { ok: false, reason: "exec-failed" };
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
let cachedProbe;
|
|
184
|
+
const probe = () => cachedProbe ??= (async () => {
|
|
185
|
+
const res = await execCanary(["version"]);
|
|
186
|
+
if (!res.ok) return { status: "degraded", reason: res.reason };
|
|
187
|
+
if (res.stdout.trim() === "") return { status: "degraded", reason: "bad-output" };
|
|
188
|
+
const version = parseVersion(res.stdout);
|
|
189
|
+
return version ? { status: "available", version } : { status: "available" };
|
|
190
|
+
})();
|
|
191
|
+
const recommendFramework = async (prompt) => {
|
|
192
|
+
const res = await execCanary(["recommend", prompt, "--json"]);
|
|
193
|
+
if (!res.ok) return degradedRecommendation();
|
|
194
|
+
const parsed = frameworkRecommendationSchema.safeParse(safeJson(res.stdout));
|
|
195
|
+
return parsed.success ? parsed.data : degradedRecommendation();
|
|
196
|
+
};
|
|
197
|
+
const reviewTest = async (path2, framework) => {
|
|
198
|
+
const args = ["review-test", path2, "--json"];
|
|
199
|
+
if (framework) args.push("--framework", framework);
|
|
200
|
+
const res = await execCanary(args);
|
|
201
|
+
if (!res.ok) return [];
|
|
202
|
+
const parsed = canaryFindingsSchema.safeParse(safeJson(res.stdout));
|
|
203
|
+
return parsed.success ? parsed.data : [];
|
|
204
|
+
};
|
|
205
|
+
return { probe, recommendFramework, reviewTest };
|
|
206
|
+
}
|
|
207
|
+
|
|
109
208
|
// src/analysis-provider/anthropic.ts
|
|
110
209
|
import Anthropic from "@anthropic-ai/sdk";
|
|
111
210
|
|
|
@@ -397,7 +496,7 @@ ${request.prompt}` : request.prompt;
|
|
|
397
496
|
};
|
|
398
497
|
|
|
399
498
|
// src/sel/prompts.ts
|
|
400
|
-
import { z } from "zod";
|
|
499
|
+
import { z as z2 } from "zod";
|
|
401
500
|
var SEL_SYSTEM_PROMPT = `You are a spec enrichment agent. Your job is to analyze a work item (feature request, bug report, task, etc.) and produce structured output that captures the full engineering intent.
|
|
402
501
|
|
|
403
502
|
Analyze the provided work item and extract:
|
|
@@ -461,22 +560,22 @@ ${metaEntries.map(([k, v]) => `- **${k}:** ${String(v)}`).join("\n")}`
|
|
|
461
560
|
}
|
|
462
561
|
return parts.join("\n");
|
|
463
562
|
}
|
|
464
|
-
var selResponseSchema =
|
|
465
|
-
intent:
|
|
466
|
-
summary:
|
|
467
|
-
affectedSystems:
|
|
468
|
-
functionalRequirements:
|
|
469
|
-
nonFunctionalRequirements:
|
|
470
|
-
apiChanges:
|
|
471
|
-
dbChanges:
|
|
472
|
-
integrationPoints:
|
|
473
|
-
assumptions:
|
|
474
|
-
unknowns:
|
|
475
|
-
ambiguities:
|
|
476
|
-
riskSignals:
|
|
477
|
-
initialComplexityHints:
|
|
478
|
-
textualComplexity:
|
|
479
|
-
structuralComplexity:
|
|
563
|
+
var selResponseSchema = z2.object({
|
|
564
|
+
intent: z2.string(),
|
|
565
|
+
summary: z2.string(),
|
|
566
|
+
affectedSystems: z2.array(z2.object({ name: z2.string() })),
|
|
567
|
+
functionalRequirements: z2.array(z2.string()),
|
|
568
|
+
nonFunctionalRequirements: z2.array(z2.string()),
|
|
569
|
+
apiChanges: z2.array(z2.string()),
|
|
570
|
+
dbChanges: z2.array(z2.string()),
|
|
571
|
+
integrationPoints: z2.array(z2.string()),
|
|
572
|
+
assumptions: z2.array(z2.string()),
|
|
573
|
+
unknowns: z2.array(z2.string()),
|
|
574
|
+
ambiguities: z2.array(z2.string()),
|
|
575
|
+
riskSignals: z2.array(z2.string()),
|
|
576
|
+
initialComplexityHints: z2.object({
|
|
577
|
+
textualComplexity: z2.number().min(0).max(1),
|
|
578
|
+
structuralComplexity: z2.number().min(0).max(1)
|
|
480
579
|
})
|
|
481
580
|
});
|
|
482
581
|
|
|
@@ -850,7 +949,7 @@ function runGraphOnlyChecks(spec, score2, store) {
|
|
|
850
949
|
}
|
|
851
950
|
|
|
852
951
|
// src/pesl/prompts.ts
|
|
853
|
-
import { z as
|
|
952
|
+
import { z as z3 } from "zod";
|
|
854
953
|
var PESL_SYSTEM_PROMPT = `You are a pre-execution simulation agent. Your job is to analyze an enriched specification and its complexity assessment, then simulate what would happen if an autonomous coding agent attempted to implement this change.
|
|
855
954
|
|
|
856
955
|
Your simulation should:
|
|
@@ -862,13 +961,13 @@ Your simulation should:
|
|
|
862
961
|
Be realistic and specific. Reference actual system names from the spec. Err on the side of flagging potential issues -- it is better to over-predict failures than to miss them.
|
|
863
962
|
|
|
864
963
|
Return your analysis using the structured_output tool.`;
|
|
865
|
-
var peslResponseSchema =
|
|
866
|
-
simulatedPlan:
|
|
867
|
-
predictedFailures:
|
|
868
|
-
riskHotspots:
|
|
869
|
-
missingSteps:
|
|
870
|
-
testGaps:
|
|
871
|
-
recommendedChanges:
|
|
964
|
+
var peslResponseSchema = z3.object({
|
|
965
|
+
simulatedPlan: z3.array(z3.string()).describe("Ordered implementation steps the agent would take"),
|
|
966
|
+
predictedFailures: z3.array(z3.string()).describe("Likely failure modes during implementation"),
|
|
967
|
+
riskHotspots: z3.array(z3.string()).describe("Files or modules that are high-risk change points"),
|
|
968
|
+
missingSteps: z3.array(z3.string()).describe("Steps the agent might miss or overlook"),
|
|
969
|
+
testGaps: z3.array(z3.string()).describe("Tests that should exist but likely do not"),
|
|
970
|
+
recommendedChanges: z3.array(z3.string()).describe("Adjustments to improve success likelihood")
|
|
872
971
|
});
|
|
873
972
|
function appendSection(parts, heading, items) {
|
|
874
973
|
if (items.length === 0) return;
|
|
@@ -1006,6 +1105,30 @@ var PeslSimulator = class {
|
|
|
1006
1105
|
};
|
|
1007
1106
|
|
|
1008
1107
|
// src/outcome/connector.ts
|
|
1108
|
+
var RESERVED_METADATA_KEYS = /* @__PURE__ */ new Set([
|
|
1109
|
+
"id",
|
|
1110
|
+
"identifier",
|
|
1111
|
+
"type",
|
|
1112
|
+
"name",
|
|
1113
|
+
"result",
|
|
1114
|
+
"retryCount",
|
|
1115
|
+
"failureReasons",
|
|
1116
|
+
"durationMs",
|
|
1117
|
+
"linkedSpecId",
|
|
1118
|
+
"timestamp",
|
|
1119
|
+
"issueId",
|
|
1120
|
+
"agentPersona",
|
|
1121
|
+
"taskType",
|
|
1122
|
+
"affectedSystemNodeIds",
|
|
1123
|
+
"edges"
|
|
1124
|
+
]);
|
|
1125
|
+
function stripReservedKeys(metadata) {
|
|
1126
|
+
const safe = {};
|
|
1127
|
+
for (const [key, value] of Object.entries(metadata)) {
|
|
1128
|
+
if (!RESERVED_METADATA_KEYS.has(key)) safe[key] = value;
|
|
1129
|
+
}
|
|
1130
|
+
return safe;
|
|
1131
|
+
}
|
|
1009
1132
|
var ExecutionOutcomeConnector = class {
|
|
1010
1133
|
constructor(store) {
|
|
1011
1134
|
this.store = store;
|
|
@@ -1018,6 +1141,11 @@ var ExecutionOutcomeConnector = class {
|
|
|
1018
1141
|
type: "execution_outcome",
|
|
1019
1142
|
name: `${outcome.result}: ${outcome.identifier}`,
|
|
1020
1143
|
metadata: {
|
|
1144
|
+
// Caller-supplied extras are STRIPPED of all reserved/core keys before
|
|
1145
|
+
// merge, so they can never shadow a core field — even conditionally-
|
|
1146
|
+
// written ones like agentPersona/taskType. Only genuinely additive
|
|
1147
|
+
// keys (e.g. verdict/confidence/source) survive.
|
|
1148
|
+
...stripReservedKeys(outcome.metadata ?? {}),
|
|
1021
1149
|
issueId: outcome.issueId,
|
|
1022
1150
|
identifier: outcome.identifier,
|
|
1023
1151
|
result: outcome.result,
|
|
@@ -1045,6 +1173,256 @@ var ExecutionOutcomeConnector = class {
|
|
|
1045
1173
|
}
|
|
1046
1174
|
};
|
|
1047
1175
|
|
|
1176
|
+
// src/outcome-eval/authority.ts
|
|
1177
|
+
function deriveAuthority(verdict, confidence) {
|
|
1178
|
+
return verdict === "NOT_SATISFIED" && confidence === "high" ? "blocking" : "advisory";
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
// src/outcome-eval/prompts.ts
|
|
1182
|
+
import { z as z4 } from "zod";
|
|
1183
|
+
var verdictSchema = z4.object({
|
|
1184
|
+
verdict: z4.enum(["SATISFIED", "NOT_SATISFIED", "INCONCLUSIVE"]).describe("Whether the change satisfies the judged spec section"),
|
|
1185
|
+
confidence: z4.enum(["low", "medium", "high"]).describe("Confidence in the verdict; high requires a named criterion"),
|
|
1186
|
+
rationale: z4.string().describe("Cites specific met / unmet criteria"),
|
|
1187
|
+
unmetCriteria: z4.array(z4.string()).describe("Unmet criteria; empty when SATISFIED")
|
|
1188
|
+
}).strict();
|
|
1189
|
+
var OUTCOME_EVAL_SYSTEM_PROMPT = `You are a post-execution outcome judge. Given a spec acceptance section, a unified diff, and test output, decide whether the change SATISFIED, NOT_SATISFIED, or is INCONCLUSIVE against that section.
|
|
1190
|
+
|
|
1191
|
+
Confidence calibration (be conservative \u2014 false alarms are costly):
|
|
1192
|
+
- Default to "medium" confidence.
|
|
1193
|
+
- Use "high" ONLY when you can name a SPECIFIC criterion from the section that the diff and test output clearly met or clearly failed to meet, and quote or paraphrase it in the rationale.
|
|
1194
|
+
- Use "low" when the diff or test output is ambiguous, partial, or insufficient to judge.
|
|
1195
|
+
- When the change only PARTIALLY meets the criteria, do not exceed "medium" confidence.
|
|
1196
|
+
- Bias toward advisory caution: if unsure between two confidence levels, choose the lower one.
|
|
1197
|
+
|
|
1198
|
+
Rules:
|
|
1199
|
+
- The rationale MUST cite specific met or unmet criteria from the section.
|
|
1200
|
+
- "unmetCriteria" lists the section criteria the change failed to meet; it is empty when the verdict is SATISFIED.
|
|
1201
|
+
- Do NOT emit an "authority" field. Authority is computed downstream in TypeScript from (verdict, confidence) and must never come from you.
|
|
1202
|
+
|
|
1203
|
+
Return your judgment using the structured_output tool.`;
|
|
1204
|
+
var PROMPT_FIELD_MAX_CHARS = 12e3;
|
|
1205
|
+
var FENCE = "````";
|
|
1206
|
+
function clampField(text) {
|
|
1207
|
+
const trimmed = text.trim();
|
|
1208
|
+
if (trimmed.length <= PROMPT_FIELD_MAX_CHARS) return trimmed;
|
|
1209
|
+
const dropped = trimmed.length - PROMPT_FIELD_MAX_CHARS;
|
|
1210
|
+
return `${trimmed.slice(0, PROMPT_FIELD_MAX_CHARS)}
|
|
1211
|
+
\u2026 [truncated ${dropped} chars]`;
|
|
1212
|
+
}
|
|
1213
|
+
function buildUserPrompt2(section, diff, testOutput) {
|
|
1214
|
+
return [
|
|
1215
|
+
"## Spec Acceptance Criteria (judge against this section)",
|
|
1216
|
+
section.trim() || "(empty \u2014 treat as inconclusive)",
|
|
1217
|
+
"",
|
|
1218
|
+
"## Change Diff",
|
|
1219
|
+
`${FENCE}diff`,
|
|
1220
|
+
clampField(diff) || "(empty diff)",
|
|
1221
|
+
FENCE,
|
|
1222
|
+
"",
|
|
1223
|
+
"## Test Output",
|
|
1224
|
+
FENCE,
|
|
1225
|
+
clampField(testOutput) || "(no test output captured)",
|
|
1226
|
+
FENCE,
|
|
1227
|
+
"",
|
|
1228
|
+
"## Instructions",
|
|
1229
|
+
"Judge whether the diff satisfies the acceptance criteria above. Calibrate confidence conservatively per your system instructions. Cite specific criteria in the rationale."
|
|
1230
|
+
].join("\n");
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
// src/outcome-eval/section-resolver.ts
|
|
1234
|
+
var CHAIN = [
|
|
1235
|
+
{ tag: "success-criteria", matches: (h) => h === "success criteria" },
|
|
1236
|
+
{ tag: "user-visible-behavior", matches: (h) => h === "user visible behavior" },
|
|
1237
|
+
{ tag: "overview", matches: (h) => h === "overview" }
|
|
1238
|
+
];
|
|
1239
|
+
var HEADING_RE = /^(#{1,6})\s+(.*\S)\s*$/;
|
|
1240
|
+
var FENCE_RE = /^\s*(```|~~~)/;
|
|
1241
|
+
var normalizeHeading = (text) => text.toLowerCase().replace(/-/g, " ").replace(/\s+/g, " ").trim();
|
|
1242
|
+
var parseHeading = (line, index) => {
|
|
1243
|
+
const m = HEADING_RE.exec(line);
|
|
1244
|
+
if (!m || m[1] === void 0 || m[2] === void 0) return null;
|
|
1245
|
+
const normalized = normalizeHeading(m[2]);
|
|
1246
|
+
const entry = CHAIN.find((c) => c.matches(normalized));
|
|
1247
|
+
return { index, level: m[1].length, tag: entry ? entry.tag : null };
|
|
1248
|
+
};
|
|
1249
|
+
function resolveSection(markdown) {
|
|
1250
|
+
const lines = markdown.split(/\r?\n/);
|
|
1251
|
+
const headings = [];
|
|
1252
|
+
let inFence = false;
|
|
1253
|
+
lines.forEach((line, index) => {
|
|
1254
|
+
if (FENCE_RE.test(line)) {
|
|
1255
|
+
inFence = !inFence;
|
|
1256
|
+
return;
|
|
1257
|
+
}
|
|
1258
|
+
if (inFence) return;
|
|
1259
|
+
const heading = parseHeading(line, index);
|
|
1260
|
+
if (heading) headings.push(heading);
|
|
1261
|
+
});
|
|
1262
|
+
for (const { tag } of CHAIN) {
|
|
1263
|
+
const start = headings.find((h) => h.tag === tag);
|
|
1264
|
+
if (!start) continue;
|
|
1265
|
+
const next = headings.find((h) => h.index > start.index && h.level <= start.level);
|
|
1266
|
+
const endExclusive = next ? next.index : lines.length;
|
|
1267
|
+
const body = lines.slice(start.index + 1, endExclusive).join("\n").trim();
|
|
1268
|
+
return { judgedAgainst: tag, body };
|
|
1269
|
+
}
|
|
1270
|
+
return null;
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
// src/outcome-eval/evaluator.ts
|
|
1274
|
+
import { readFile } from "fs/promises";
|
|
1275
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
1276
|
+
var OutcomeEvaluator = class {
|
|
1277
|
+
provider;
|
|
1278
|
+
store;
|
|
1279
|
+
options;
|
|
1280
|
+
constructor(provider, store, options = {}) {
|
|
1281
|
+
this.provider = provider;
|
|
1282
|
+
this.store = store;
|
|
1283
|
+
this.options = options;
|
|
1284
|
+
}
|
|
1285
|
+
async evaluate(input) {
|
|
1286
|
+
let resolved;
|
|
1287
|
+
try {
|
|
1288
|
+
resolved = await this.resolveJudgmentSection(input);
|
|
1289
|
+
} catch {
|
|
1290
|
+
return this.finish(this.degradedVerdict("overview"), input);
|
|
1291
|
+
}
|
|
1292
|
+
if (resolved === null) {
|
|
1293
|
+
const verdict2 = this.buildVerdict(
|
|
1294
|
+
"INCONCLUSIVE",
|
|
1295
|
+
"low",
|
|
1296
|
+
"No judgable spec section found.",
|
|
1297
|
+
"overview",
|
|
1298
|
+
[]
|
|
1299
|
+
);
|
|
1300
|
+
return this.finish(verdict2, input);
|
|
1301
|
+
}
|
|
1302
|
+
const verdict = await this.judge(resolved, input);
|
|
1303
|
+
return this.finish(verdict, input);
|
|
1304
|
+
}
|
|
1305
|
+
/**
|
|
1306
|
+
* Run the provider call and strict re-parse. ANY failure here — provider
|
|
1307
|
+
* rejection (rate limit/network), or a strict-parse rejection of a malformed
|
|
1308
|
+
* or authority-injected payload — degrades safely to INCONCLUSIVE/low/
|
|
1309
|
+
* advisory rather than throwing. This reconciles Criterion 3 (never block on
|
|
1310
|
+
* noise) with Criterion 4: an injected `authority` key is discarded by the
|
|
1311
|
+
* .strict() re-parse, and the degraded verdict's authority is DERIVED from
|
|
1312
|
+
* INCONCLUSIVE/low = advisory — so the LLM gains nothing by injecting it.
|
|
1313
|
+
*/
|
|
1314
|
+
async judge(resolved, input) {
|
|
1315
|
+
try {
|
|
1316
|
+
const response = await this.provider.analyze({
|
|
1317
|
+
prompt: buildUserPrompt2(resolved.body, input.diff, input.testOutput),
|
|
1318
|
+
systemPrompt: OUTCOME_EVAL_SYSTEM_PROMPT,
|
|
1319
|
+
responseSchema: verdictSchema,
|
|
1320
|
+
...this.options.model !== void 0 && { model: this.options.model }
|
|
1321
|
+
});
|
|
1322
|
+
const llm = verdictSchema.parse(response.result);
|
|
1323
|
+
return this.buildVerdict(
|
|
1324
|
+
llm.verdict,
|
|
1325
|
+
llm.confidence,
|
|
1326
|
+
llm.rationale,
|
|
1327
|
+
resolved.judgedAgainst,
|
|
1328
|
+
llm.unmetCriteria
|
|
1329
|
+
);
|
|
1330
|
+
} catch {
|
|
1331
|
+
return this.degradedVerdict(resolved.judgedAgainst);
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
/**
|
|
1335
|
+
* Build the safe-degradation verdict. INCONCLUSIVE/low yields advisory
|
|
1336
|
+
* authority via deriveAuthority — never blocking. The rationale names only a
|
|
1337
|
+
* coarse reason category, never a stack trace or secret.
|
|
1338
|
+
*/
|
|
1339
|
+
degradedVerdict(judgedAgainst) {
|
|
1340
|
+
return this.buildVerdict(
|
|
1341
|
+
"INCONCLUSIVE",
|
|
1342
|
+
"low",
|
|
1343
|
+
"Evaluation could not be completed; defaulting to an inconclusive, advisory verdict.",
|
|
1344
|
+
judgedAgainst,
|
|
1345
|
+
[]
|
|
1346
|
+
);
|
|
1347
|
+
}
|
|
1348
|
+
/** Persist (Phase 4 seam) then return the verdict. */
|
|
1349
|
+
async finish(verdict, input) {
|
|
1350
|
+
await this.persistOutcome(verdict, input);
|
|
1351
|
+
return verdict;
|
|
1352
|
+
}
|
|
1353
|
+
async resolveJudgmentSection(input) {
|
|
1354
|
+
if (input.specSection !== void 0) {
|
|
1355
|
+
return input.specSection.trim() === "" ? null : { judgedAgainst: "success-criteria", body: input.specSection };
|
|
1356
|
+
}
|
|
1357
|
+
const markdown = await readFile(input.specPath, "utf8");
|
|
1358
|
+
return resolveSection(markdown);
|
|
1359
|
+
}
|
|
1360
|
+
buildVerdict(verdict, confidence, rationale, judgedAgainst, unmetCriteria) {
|
|
1361
|
+
return {
|
|
1362
|
+
verdict,
|
|
1363
|
+
confidence,
|
|
1364
|
+
rationale,
|
|
1365
|
+
judgedAgainst,
|
|
1366
|
+
unmetCriteria,
|
|
1367
|
+
authority: deriveAuthority(verdict, confidence)
|
|
1368
|
+
};
|
|
1369
|
+
}
|
|
1370
|
+
/**
|
|
1371
|
+
* Map an OutcomeVerdict + OutcomeEvalInput to the connector's ExecutionOutcome.
|
|
1372
|
+
* - result: SATISFIED -> 'success'; otherwise 'failure'. INCONCLUSIVE is
|
|
1373
|
+
* 'failure' for type-validity but omits agentPersona/affected systems so
|
|
1374
|
+
* the effectiveness scorer ignores it (plan D2).
|
|
1375
|
+
* - linkedSpecId: input.specPath (metadata only; no spec edge — plan D1).
|
|
1376
|
+
* - affectedSystemNodeIds: [] in v1 (not available from OutcomeEvalInput — D4).
|
|
1377
|
+
* - id: one node per EVALUATION. GraphStore.addNode upserts by id, so the id
|
|
1378
|
+
* carries a collision-free randomUUID() — two evaluate() calls in the same
|
|
1379
|
+
* millisecond can never overwrite each other (data-loss fix). specPath is
|
|
1380
|
+
* included for human readability only.
|
|
1381
|
+
* - taskType: OMITTED. The outcome-eval judge has no task categorization, and
|
|
1382
|
+
* asserting a false 'feature' would mislead specialization analytics (SUG-2).
|
|
1383
|
+
* - metadata: verdict-specific signal carried through the connector's
|
|
1384
|
+
* additive pass-through (verdict/confidence/judgedAgainst/source) so the
|
|
1385
|
+
* true 3-valued verdict is durable on the node (Truth 3).
|
|
1386
|
+
*/
|
|
1387
|
+
toExecutionOutcome(verdict, input) {
|
|
1388
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
1389
|
+
return {
|
|
1390
|
+
id: `outcome:outcome-eval:${input.specPath}:${randomUUID2()}`,
|
|
1391
|
+
issueId: "outcome-eval",
|
|
1392
|
+
identifier: `outcome-eval:${input.specPath}`,
|
|
1393
|
+
result: verdict.verdict === "SATISFIED" ? "success" : "failure",
|
|
1394
|
+
retryCount: 0,
|
|
1395
|
+
failureReasons: verdict.unmetCriteria,
|
|
1396
|
+
// 0 means "not applicable to outcome-eval" — the judge does not time work.
|
|
1397
|
+
durationMs: 0,
|
|
1398
|
+
linkedSpecId: input.specPath,
|
|
1399
|
+
affectedSystemNodeIds: [],
|
|
1400
|
+
timestamp,
|
|
1401
|
+
metadata: {
|
|
1402
|
+
verdict: verdict.verdict,
|
|
1403
|
+
confidence: verdict.confidence,
|
|
1404
|
+
judgedAgainst: verdict.judgedAgainst,
|
|
1405
|
+
source: "outcome-eval"
|
|
1406
|
+
}
|
|
1407
|
+
};
|
|
1408
|
+
}
|
|
1409
|
+
/**
|
|
1410
|
+
* Phase 4: writes exactly one execution_outcome node via
|
|
1411
|
+
* ExecutionOutcomeConnector. Degrade-safe (plan D3): a graph-write failure is
|
|
1412
|
+
* swallowed-and-logged, never thrown — the verdict is already computed before
|
|
1413
|
+
* this runs, so swallowing keeps evaluate() total. No secrets/stack frames in
|
|
1414
|
+
* the log message.
|
|
1415
|
+
*/
|
|
1416
|
+
async persistOutcome(verdict, input) {
|
|
1417
|
+
try {
|
|
1418
|
+
const connector = new ExecutionOutcomeConnector(this.store);
|
|
1419
|
+
connector.ingest(this.toExecutionOutcome(verdict, input));
|
|
1420
|
+
} catch {
|
|
1421
|
+
console.warn("[outcome-eval] execution_outcome persistence failed; verdict unaffected.");
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
};
|
|
1425
|
+
|
|
1048
1426
|
// src/pipeline.ts
|
|
1049
1427
|
var IntelligencePipeline = class {
|
|
1050
1428
|
provider;
|
|
@@ -1521,16 +1899,21 @@ export {
|
|
|
1521
1899
|
ExecutionOutcomeConnector,
|
|
1522
1900
|
GraphValidator,
|
|
1523
1901
|
IntelligencePipeline,
|
|
1902
|
+
OUTCOME_EVAL_SYSTEM_PROMPT,
|
|
1524
1903
|
OpenAICompatibleAnalysisProvider,
|
|
1904
|
+
OutcomeEvaluator,
|
|
1525
1905
|
PeslSimulator,
|
|
1526
1906
|
buildSpecializationProfile,
|
|
1907
|
+
buildUserPrompt2 as buildUserPrompt,
|
|
1527
1908
|
computeExpertiseLevel,
|
|
1528
1909
|
computeHistoricalComplexity,
|
|
1529
1910
|
computePersonaEffectiveness,
|
|
1530
1911
|
computeSemanticComplexity,
|
|
1531
1912
|
computeSpecialization,
|
|
1532
1913
|
computeStructuralComplexity,
|
|
1914
|
+
createCanaryAdapter,
|
|
1533
1915
|
decayWeight,
|
|
1916
|
+
deriveAuthority,
|
|
1534
1917
|
detectBlindSpots,
|
|
1535
1918
|
enrich,
|
|
1536
1919
|
githubToRawWorkItem,
|
|
@@ -1540,6 +1923,7 @@ export {
|
|
|
1540
1923
|
manualToRawWorkItem,
|
|
1541
1924
|
recommendPersona,
|
|
1542
1925
|
refreshProfiles,
|
|
1926
|
+
resolveSection,
|
|
1543
1927
|
runGraphOnlyChecks,
|
|
1544
1928
|
runLlmSimulation,
|
|
1545
1929
|
saveProfiles,
|
|
@@ -1547,5 +1931,6 @@ export {
|
|
|
1547
1931
|
scoreToConcernSignals,
|
|
1548
1932
|
temporalSuccessRate,
|
|
1549
1933
|
toRawWorkItem,
|
|
1934
|
+
verdictSchema,
|
|
1550
1935
|
weightedRecommendPersona
|
|
1551
1936
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@harness-engineering/intelligence",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Intelligence pipeline for spec enrichment, complexity modeling, and pre-execution simulation",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -40,8 +40,11 @@
|
|
|
40
40
|
"@anthropic-ai/sdk": "^0.95.1",
|
|
41
41
|
"openai": "^6.0.0",
|
|
42
42
|
"zod": "^3.25.76",
|
|
43
|
-
"@harness-engineering/graph": "0.
|
|
44
|
-
"@harness-engineering/types": "0.
|
|
43
|
+
"@harness-engineering/graph": "0.11.0",
|
|
44
|
+
"@harness-engineering/types": "0.16.0"
|
|
45
|
+
},
|
|
46
|
+
"optionalDependencies": {
|
|
47
|
+
"canary-test-cli": "^5.4.0"
|
|
45
48
|
},
|
|
46
49
|
"devDependencies": {
|
|
47
50
|
"@types/node": "^22.19.15",
|