@harness-engineering/core 0.21.2 → 0.21.3
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/architecture/matchers.d.mts +1 -1
- package/dist/architecture/matchers.d.ts +1 -1
- package/dist/index.d.mts +486 -162
- package/dist/index.d.ts +486 -162
- package/dist/index.js +547 -40
- package/dist/index.mjs +524 -36
- package/dist/{matchers-Dj1t5vpg.d.mts → matchers-D20x48U9.d.mts} +46 -46
- package/dist/{matchers-Dj1t5vpg.d.ts → matchers-D20x48U9.d.ts} +46 -46
- package/package.json +3 -3
package/dist/index.mjs
CHANGED
|
@@ -84,15 +84,15 @@ function validateConfig(data, schema) {
|
|
|
84
84
|
let message = "Configuration validation failed";
|
|
85
85
|
const suggestions = [];
|
|
86
86
|
if (firstError) {
|
|
87
|
-
const
|
|
88
|
-
const pathDisplay =
|
|
87
|
+
const path34 = firstError.path.join(".");
|
|
88
|
+
const pathDisplay = path34 ? ` at "${path34}"` : "";
|
|
89
89
|
if (firstError.code === "invalid_type") {
|
|
90
90
|
const received = firstError.received;
|
|
91
91
|
const expected = firstError.expected;
|
|
92
92
|
if (received === "undefined") {
|
|
93
93
|
code = "MISSING_FIELD";
|
|
94
94
|
message = `Missing required field${pathDisplay}: ${firstError.message}`;
|
|
95
|
-
suggestions.push(`Field "${
|
|
95
|
+
suggestions.push(`Field "${path34}" is required and must be of type "${expected}"`);
|
|
96
96
|
} else {
|
|
97
97
|
code = "INVALID_TYPE";
|
|
98
98
|
message = `Invalid type${pathDisplay}: ${firstError.message}`;
|
|
@@ -311,27 +311,27 @@ function extractSections(content) {
|
|
|
311
311
|
}
|
|
312
312
|
return sections.map((section) => buildAgentMapSection(section, lines));
|
|
313
313
|
}
|
|
314
|
-
function isExternalLink(
|
|
315
|
-
return
|
|
314
|
+
function isExternalLink(path34) {
|
|
315
|
+
return path34.startsWith("http://") || path34.startsWith("https://") || path34.startsWith("#") || path34.startsWith("mailto:");
|
|
316
316
|
}
|
|
317
317
|
function resolveLinkPath(linkPath, baseDir) {
|
|
318
318
|
return linkPath.startsWith(".") ? join(baseDir, linkPath) : linkPath;
|
|
319
319
|
}
|
|
320
|
-
async function validateAgentsMap(
|
|
321
|
-
const contentResult = await readFileContent(
|
|
320
|
+
async function validateAgentsMap(path34 = "./AGENTS.md") {
|
|
321
|
+
const contentResult = await readFileContent(path34);
|
|
322
322
|
if (!contentResult.ok) {
|
|
323
323
|
return Err(
|
|
324
324
|
createError(
|
|
325
325
|
"PARSE_ERROR",
|
|
326
326
|
`Failed to read AGENTS.md: ${contentResult.error.message}`,
|
|
327
|
-
{ path:
|
|
327
|
+
{ path: path34 },
|
|
328
328
|
["Ensure the file exists", "Check file permissions"]
|
|
329
329
|
)
|
|
330
330
|
);
|
|
331
331
|
}
|
|
332
332
|
const content = contentResult.value;
|
|
333
333
|
const sections = extractSections(content);
|
|
334
|
-
const baseDir = dirname(
|
|
334
|
+
const baseDir = dirname(path34);
|
|
335
335
|
const sectionTitles = sections.map((s) => s.title);
|
|
336
336
|
const missingSections = REQUIRED_SECTIONS.filter(
|
|
337
337
|
(required) => !sectionTitles.some((title) => title.toLowerCase().includes(required.toLowerCase()))
|
|
@@ -472,8 +472,8 @@ async function checkDocCoverage(domain, options = {}) {
|
|
|
472
472
|
|
|
473
473
|
// src/context/knowledge-map.ts
|
|
474
474
|
import { join as join2, basename as basename2 } from "path";
|
|
475
|
-
function suggestFix(
|
|
476
|
-
const targetName = basename2(
|
|
475
|
+
function suggestFix(path34, existingFiles) {
|
|
476
|
+
const targetName = basename2(path34).toLowerCase();
|
|
477
477
|
const similar = existingFiles.find((file) => {
|
|
478
478
|
const fileName = basename2(file).toLowerCase();
|
|
479
479
|
return fileName.includes(targetName) || targetName.includes(fileName);
|
|
@@ -481,7 +481,7 @@ function suggestFix(path31, existingFiles) {
|
|
|
481
481
|
if (similar) {
|
|
482
482
|
return `Did you mean "${similar}"?`;
|
|
483
483
|
}
|
|
484
|
-
return `Create the file "${
|
|
484
|
+
return `Create the file "${path34}" or remove the link`;
|
|
485
485
|
}
|
|
486
486
|
async function validateKnowledgeMap(rootDir = process.cwd()) {
|
|
487
487
|
const agentsPath = join2(rootDir, "AGENTS.md");
|
|
@@ -831,8 +831,8 @@ function createBoundaryValidator(schema, name) {
|
|
|
831
831
|
return Ok(result.data);
|
|
832
832
|
}
|
|
833
833
|
const suggestions = result.error.issues.map((issue) => {
|
|
834
|
-
const
|
|
835
|
-
return
|
|
834
|
+
const path34 = issue.path.join(".");
|
|
835
|
+
return path34 ? `${path34}: ${issue.message}` : issue.message;
|
|
836
836
|
});
|
|
837
837
|
return Err(
|
|
838
838
|
createError(
|
|
@@ -1466,11 +1466,11 @@ function processExportListSpecifiers(exportDecl, exports) {
|
|
|
1466
1466
|
var TypeScriptParser = class {
|
|
1467
1467
|
name = "typescript";
|
|
1468
1468
|
extensions = [".ts", ".tsx", ".mts", ".cts"];
|
|
1469
|
-
async parseFile(
|
|
1470
|
-
const contentResult = await readFileContent(
|
|
1469
|
+
async parseFile(path34) {
|
|
1470
|
+
const contentResult = await readFileContent(path34);
|
|
1471
1471
|
if (!contentResult.ok) {
|
|
1472
1472
|
return Err(
|
|
1473
|
-
createParseError("NOT_FOUND", `File not found: ${
|
|
1473
|
+
createParseError("NOT_FOUND", `File not found: ${path34}`, { path: path34 }, [
|
|
1474
1474
|
"Check that the file exists",
|
|
1475
1475
|
"Verify the path is correct"
|
|
1476
1476
|
])
|
|
@@ -1480,7 +1480,7 @@ var TypeScriptParser = class {
|
|
|
1480
1480
|
const ast = parse(contentResult.value, {
|
|
1481
1481
|
loc: true,
|
|
1482
1482
|
range: true,
|
|
1483
|
-
jsx:
|
|
1483
|
+
jsx: path34.endsWith(".tsx"),
|
|
1484
1484
|
errorOnUnknownASTType: false
|
|
1485
1485
|
});
|
|
1486
1486
|
return Ok({
|
|
@@ -1491,7 +1491,7 @@ var TypeScriptParser = class {
|
|
|
1491
1491
|
} catch (e) {
|
|
1492
1492
|
const error = e;
|
|
1493
1493
|
return Err(
|
|
1494
|
-
createParseError("SYNTAX_ERROR", `Failed to parse ${
|
|
1494
|
+
createParseError("SYNTAX_ERROR", `Failed to parse ${path34}: ${error.message}`, { path: path34 }, [
|
|
1495
1495
|
"Check for syntax errors in the file",
|
|
1496
1496
|
"Ensure valid TypeScript syntax"
|
|
1497
1497
|
])
|
|
@@ -1676,22 +1676,22 @@ function extractInlineRefs(content) {
|
|
|
1676
1676
|
}
|
|
1677
1677
|
return refs;
|
|
1678
1678
|
}
|
|
1679
|
-
async function parseDocumentationFile(
|
|
1680
|
-
const contentResult = await readFileContent(
|
|
1679
|
+
async function parseDocumentationFile(path34) {
|
|
1680
|
+
const contentResult = await readFileContent(path34);
|
|
1681
1681
|
if (!contentResult.ok) {
|
|
1682
1682
|
return Err(
|
|
1683
1683
|
createEntropyError(
|
|
1684
1684
|
"PARSE_ERROR",
|
|
1685
|
-
`Failed to read documentation file: ${
|
|
1686
|
-
{ file:
|
|
1685
|
+
`Failed to read documentation file: ${path34}`,
|
|
1686
|
+
{ file: path34 },
|
|
1687
1687
|
["Check that the file exists"]
|
|
1688
1688
|
)
|
|
1689
1689
|
);
|
|
1690
1690
|
}
|
|
1691
1691
|
const content = contentResult.value;
|
|
1692
|
-
const type =
|
|
1692
|
+
const type = path34.endsWith(".md") ? "markdown" : "text";
|
|
1693
1693
|
return Ok({
|
|
1694
|
-
path:
|
|
1694
|
+
path: path34,
|
|
1695
1695
|
type,
|
|
1696
1696
|
content,
|
|
1697
1697
|
codeBlocks: extractCodeBlocks(content),
|
|
@@ -4002,12 +4002,12 @@ function parseDiffHeader(part) {
|
|
|
4002
4002
|
return headerMatch[2];
|
|
4003
4003
|
}
|
|
4004
4004
|
function parseDiffPart(part) {
|
|
4005
|
-
const
|
|
4006
|
-
if (!
|
|
4005
|
+
const path34 = parseDiffHeader(part);
|
|
4006
|
+
if (!path34) return null;
|
|
4007
4007
|
const additionRegex = /^\+(?!\+\+)/gm;
|
|
4008
4008
|
const deletionRegex = /^-(?!--)/gm;
|
|
4009
4009
|
return {
|
|
4010
|
-
path:
|
|
4010
|
+
path: path34,
|
|
4011
4011
|
status: detectFileStatus(part),
|
|
4012
4012
|
additions: (part.match(additionRegex) || []).length,
|
|
4013
4013
|
deletions: (part.match(deletionRegex) || []).length
|
|
@@ -6349,6 +6349,35 @@ async function saveState(projectPath, state, stream, session) {
|
|
|
6349
6349
|
import * as fs11 from "fs";
|
|
6350
6350
|
import * as path8 from "path";
|
|
6351
6351
|
import * as crypto2 from "crypto";
|
|
6352
|
+
|
|
6353
|
+
// src/compaction/envelope.ts
|
|
6354
|
+
function estimateTokens(content) {
|
|
6355
|
+
return Math.ceil(content.length / 4);
|
|
6356
|
+
}
|
|
6357
|
+
function serializeEnvelope(envelope) {
|
|
6358
|
+
const { meta, sections } = envelope;
|
|
6359
|
+
const strategyLabel = meta.strategy.length > 0 ? meta.strategy.join("+") : "none";
|
|
6360
|
+
let cacheLabel = "";
|
|
6361
|
+
if (meta.cached) {
|
|
6362
|
+
if (meta.cacheReadTokens != null && meta.cacheInputTokens != null && meta.cacheInputTokens > 0) {
|
|
6363
|
+
const hitPct = Math.round(meta.cacheReadTokens / meta.cacheInputTokens * 100);
|
|
6364
|
+
const readFormatted = meta.cacheReadTokens >= 1e3 ? (meta.cacheReadTokens / 1e3).toFixed(1) + "K" : String(meta.cacheReadTokens);
|
|
6365
|
+
cacheLabel = ` [cached | cache: ${readFormatted} read, ${hitPct}% hit]`;
|
|
6366
|
+
} else {
|
|
6367
|
+
cacheLabel = " [cached]";
|
|
6368
|
+
}
|
|
6369
|
+
}
|
|
6370
|
+
const header = `<!-- packed: ${strategyLabel} | ${meta.originalTokenEstimate}\u2192${meta.compactedTokenEstimate} tokens (-${meta.reductionPct}%)${cacheLabel} -->`;
|
|
6371
|
+
if (sections.length === 0) {
|
|
6372
|
+
return header;
|
|
6373
|
+
}
|
|
6374
|
+
const body = sections.map((section) => `### [${section.source}]
|
|
6375
|
+
${section.content}`).join("\n\n");
|
|
6376
|
+
return `${header}
|
|
6377
|
+
${body}`;
|
|
6378
|
+
}
|
|
6379
|
+
|
|
6380
|
+
// src/state/learnings-content.ts
|
|
6352
6381
|
function parseFrontmatter2(line) {
|
|
6353
6382
|
const match = line.match(/^<!--\s+hash:([a-f0-9]+)(?:\s+tags:([^\s]+))?\s+-->/);
|
|
6354
6383
|
if (!match) return null;
|
|
@@ -6452,9 +6481,6 @@ function analyzeLearningPatterns(entries) {
|
|
|
6452
6481
|
}
|
|
6453
6482
|
return patterns.sort((a, b) => b.count - a.count);
|
|
6454
6483
|
}
|
|
6455
|
-
function estimateTokens(text) {
|
|
6456
|
-
return Math.ceil(text.length / 4);
|
|
6457
|
-
}
|
|
6458
6484
|
function scoreRelevance(entry, intent) {
|
|
6459
6485
|
if (!intent || intent.trim() === "") return 0;
|
|
6460
6486
|
const intentWords = intent.toLowerCase().split(/\s+/).filter((w) => w.length > 2);
|
|
@@ -12414,9 +12440,9 @@ async function resolveWasmPath(grammarName) {
|
|
|
12414
12440
|
const { createRequire } = await import("module");
|
|
12415
12441
|
const require2 = createRequire(import.meta.url ?? __filename);
|
|
12416
12442
|
const pkgPath = require2.resolve("tree-sitter-wasms/package.json");
|
|
12417
|
-
const
|
|
12418
|
-
const pkgDir =
|
|
12419
|
-
return
|
|
12443
|
+
const path34 = await import("path");
|
|
12444
|
+
const pkgDir = path34.dirname(pkgPath);
|
|
12445
|
+
return path34.join(pkgDir, "out", `${grammarName}.wasm`);
|
|
12420
12446
|
}
|
|
12421
12447
|
async function loadLanguage(lang) {
|
|
12422
12448
|
const grammarName = GRAMMAR_MAP[lang];
|
|
@@ -13296,13 +13322,457 @@ function parseCCRecords() {
|
|
|
13296
13322
|
return records;
|
|
13297
13323
|
}
|
|
13298
13324
|
|
|
13299
|
-
// src/
|
|
13300
|
-
|
|
13325
|
+
// src/adoption/reader.ts
|
|
13326
|
+
import * as fs32 from "fs";
|
|
13327
|
+
import * as path31 from "path";
|
|
13328
|
+
function parseLine2(line, lineNumber) {
|
|
13329
|
+
try {
|
|
13330
|
+
const parsed = JSON.parse(line);
|
|
13331
|
+
if (typeof parsed.skill !== "string" || typeof parsed.startedAt !== "string" || typeof parsed.duration !== "number" || typeof parsed.outcome !== "string" || !Array.isArray(parsed.phasesReached)) {
|
|
13332
|
+
process.stderr.write(
|
|
13333
|
+
`[harness adoption] Skipping malformed JSONL line ${lineNumber}: missing required fields
|
|
13334
|
+
`
|
|
13335
|
+
);
|
|
13336
|
+
return null;
|
|
13337
|
+
}
|
|
13338
|
+
return parsed;
|
|
13339
|
+
} catch {
|
|
13340
|
+
process.stderr.write(`[harness adoption] Skipping malformed JSONL line ${lineNumber}
|
|
13341
|
+
`);
|
|
13342
|
+
return null;
|
|
13343
|
+
}
|
|
13344
|
+
}
|
|
13345
|
+
function readAdoptionRecords(projectRoot) {
|
|
13346
|
+
const adoptionFile = path31.join(projectRoot, ".harness", "metrics", "adoption.jsonl");
|
|
13347
|
+
let raw;
|
|
13348
|
+
try {
|
|
13349
|
+
raw = fs32.readFileSync(adoptionFile, "utf-8");
|
|
13350
|
+
} catch {
|
|
13351
|
+
return [];
|
|
13352
|
+
}
|
|
13353
|
+
const records = [];
|
|
13354
|
+
const lines = raw.split("\n");
|
|
13355
|
+
for (let i = 0; i < lines.length; i++) {
|
|
13356
|
+
const line = lines[i]?.trim();
|
|
13357
|
+
if (!line) continue;
|
|
13358
|
+
const record = parseLine2(line, i + 1);
|
|
13359
|
+
if (record) {
|
|
13360
|
+
records.push(record);
|
|
13361
|
+
}
|
|
13362
|
+
}
|
|
13363
|
+
return records;
|
|
13364
|
+
}
|
|
13365
|
+
|
|
13366
|
+
// src/adoption/aggregator.ts
|
|
13367
|
+
function aggregateBySkill(records) {
|
|
13368
|
+
if (records.length === 0) return [];
|
|
13369
|
+
const skillMap = /* @__PURE__ */ new Map();
|
|
13370
|
+
for (const record of records) {
|
|
13371
|
+
if (!skillMap.has(record.skill)) {
|
|
13372
|
+
const entry = { records: [] };
|
|
13373
|
+
if (record.tier != null) entry.tier = record.tier;
|
|
13374
|
+
skillMap.set(record.skill, entry);
|
|
13375
|
+
}
|
|
13376
|
+
skillMap.get(record.skill).records.push(record);
|
|
13377
|
+
}
|
|
13378
|
+
const results = [];
|
|
13379
|
+
for (const [skill, bucket] of skillMap) {
|
|
13380
|
+
const invocations = bucket.records.length;
|
|
13381
|
+
const completedCount = bucket.records.filter((r) => r.outcome === "completed").length;
|
|
13382
|
+
const totalDuration = bucket.records.reduce((sum, r) => sum + r.duration, 0);
|
|
13383
|
+
const timestamps = bucket.records.map((r) => r.startedAt).sort();
|
|
13384
|
+
const summary = {
|
|
13385
|
+
skill,
|
|
13386
|
+
invocations,
|
|
13387
|
+
successRate: completedCount / invocations,
|
|
13388
|
+
avgDuration: totalDuration / invocations,
|
|
13389
|
+
lastUsed: timestamps[timestamps.length - 1]
|
|
13390
|
+
};
|
|
13391
|
+
if (bucket.tier != null) summary.tier = bucket.tier;
|
|
13392
|
+
results.push(summary);
|
|
13393
|
+
}
|
|
13394
|
+
results.sort((a, b) => b.invocations - a.invocations);
|
|
13395
|
+
return results;
|
|
13396
|
+
}
|
|
13397
|
+
function aggregateByDay2(records) {
|
|
13398
|
+
if (records.length === 0) return [];
|
|
13399
|
+
const dayMap = /* @__PURE__ */ new Map();
|
|
13400
|
+
for (const record of records) {
|
|
13401
|
+
const date = record.startedAt.slice(0, 10);
|
|
13402
|
+
if (!dayMap.has(date)) {
|
|
13403
|
+
dayMap.set(date, { invocations: 0, skills: /* @__PURE__ */ new Set() });
|
|
13404
|
+
}
|
|
13405
|
+
const bucket = dayMap.get(date);
|
|
13406
|
+
bucket.invocations++;
|
|
13407
|
+
bucket.skills.add(record.skill);
|
|
13408
|
+
}
|
|
13409
|
+
const results = [];
|
|
13410
|
+
for (const [date, bucket] of dayMap) {
|
|
13411
|
+
results.push({
|
|
13412
|
+
date,
|
|
13413
|
+
invocations: bucket.invocations,
|
|
13414
|
+
uniqueSkills: bucket.skills.size
|
|
13415
|
+
});
|
|
13416
|
+
}
|
|
13417
|
+
results.sort((a, b) => b.date.localeCompare(a.date));
|
|
13418
|
+
return results;
|
|
13419
|
+
}
|
|
13420
|
+
function topSkills(records, n) {
|
|
13421
|
+
return aggregateBySkill(records).slice(0, n);
|
|
13422
|
+
}
|
|
13423
|
+
|
|
13424
|
+
// src/compaction/strategies/structural.ts
|
|
13425
|
+
function isEmptyObject(v) {
|
|
13426
|
+
return typeof v === "object" && v !== null && !Array.isArray(v) && Object.keys(v).length === 0;
|
|
13427
|
+
}
|
|
13428
|
+
function isRetainable(v) {
|
|
13429
|
+
return v !== void 0 && v !== "" && v !== null && !isEmptyObject(v);
|
|
13430
|
+
}
|
|
13431
|
+
function cleanArray(value) {
|
|
13432
|
+
const cleaned = value.map(cleanValue).filter(isRetainable);
|
|
13433
|
+
if (cleaned.length === 0) return void 0;
|
|
13434
|
+
if (cleaned.length === 1) return cleaned[0];
|
|
13435
|
+
return cleaned;
|
|
13436
|
+
}
|
|
13437
|
+
function cleanRecord(value) {
|
|
13438
|
+
const cleaned = {};
|
|
13439
|
+
for (const [k, v] of Object.entries(value)) {
|
|
13440
|
+
const result = cleanValue(v);
|
|
13441
|
+
if (isRetainable(result)) {
|
|
13442
|
+
cleaned[k] = result;
|
|
13443
|
+
}
|
|
13444
|
+
}
|
|
13445
|
+
if (Object.keys(cleaned).length === 0) return void 0;
|
|
13446
|
+
return cleaned;
|
|
13447
|
+
}
|
|
13448
|
+
function cleanValue(value) {
|
|
13449
|
+
if (value === null || value === void 0) return void 0;
|
|
13450
|
+
if (typeof value === "string") return value.replace(/\s+/g, " ").trim();
|
|
13451
|
+
if (Array.isArray(value)) return cleanArray(value);
|
|
13452
|
+
if (typeof value === "object") return cleanRecord(value);
|
|
13453
|
+
return value;
|
|
13454
|
+
}
|
|
13455
|
+
var StructuralStrategy = class {
|
|
13456
|
+
name = "structural";
|
|
13457
|
+
lossy = false;
|
|
13458
|
+
apply(content, _budget) {
|
|
13459
|
+
let parsed;
|
|
13460
|
+
try {
|
|
13461
|
+
parsed = JSON.parse(content);
|
|
13462
|
+
} catch {
|
|
13463
|
+
return content;
|
|
13464
|
+
}
|
|
13465
|
+
const cleaned = cleanValue(parsed);
|
|
13466
|
+
return JSON.stringify(cleaned) ?? "";
|
|
13467
|
+
}
|
|
13468
|
+
};
|
|
13469
|
+
|
|
13470
|
+
// src/compaction/strategies/truncation.ts
|
|
13471
|
+
var DEFAULT_TOKEN_BUDGET = 4e3;
|
|
13472
|
+
var CHARS_PER_TOKEN = 4;
|
|
13473
|
+
var TRUNCATION_MARKER = "\n[truncated \u2014 prioritized truncation applied]";
|
|
13474
|
+
function lineScore(line) {
|
|
13475
|
+
let score = 0;
|
|
13476
|
+
if (/\/[\w./-]/.test(line)) score += 40;
|
|
13477
|
+
if (/error|Error|ERROR|fail|FAIL|status/i.test(line)) score += 35;
|
|
13478
|
+
if (/\b[A-Z][a-z]+[A-Z]/.test(line) || /\b[a-z]+[A-Z]/.test(line)) score += 20;
|
|
13479
|
+
if (line.trim().length < 40) score += 10;
|
|
13480
|
+
return score;
|
|
13481
|
+
}
|
|
13482
|
+
function selectLines(lines, charBudget) {
|
|
13483
|
+
const scored = lines.map((line, idx) => ({ line, idx, score: lineScore(line) }));
|
|
13484
|
+
scored.sort((a, b) => b.score - a.score || a.idx - b.idx);
|
|
13485
|
+
const kept = [];
|
|
13486
|
+
let used = 0;
|
|
13487
|
+
for (const item of scored) {
|
|
13488
|
+
const lineLen = item.line.length + 1;
|
|
13489
|
+
if (used + lineLen > charBudget) continue;
|
|
13490
|
+
kept.push({ line: item.line, idx: item.idx });
|
|
13491
|
+
used += lineLen;
|
|
13492
|
+
}
|
|
13493
|
+
kept.sort((a, b) => a.idx - b.idx);
|
|
13494
|
+
return kept;
|
|
13495
|
+
}
|
|
13496
|
+
var TruncationStrategy = class {
|
|
13497
|
+
name = "truncate";
|
|
13498
|
+
lossy = false;
|
|
13499
|
+
// deliberate: spec Decision 2 — truncation is classified lossless at the pipeline level
|
|
13500
|
+
apply(content, budget = DEFAULT_TOKEN_BUDGET) {
|
|
13501
|
+
if (!content) return content;
|
|
13502
|
+
const charBudget = budget * CHARS_PER_TOKEN;
|
|
13503
|
+
if (content.length <= charBudget) return content;
|
|
13504
|
+
const lines = content.split("\n");
|
|
13505
|
+
const available = charBudget - TRUNCATION_MARKER.length;
|
|
13506
|
+
const kept = available > 0 ? selectLines(lines, available) : [{ line: (lines[0] ?? "").slice(0, charBudget), idx: 0 }];
|
|
13507
|
+
return kept.map((k) => k.line).join("\n") + TRUNCATION_MARKER;
|
|
13508
|
+
}
|
|
13509
|
+
};
|
|
13510
|
+
|
|
13511
|
+
// src/compaction/pipeline.ts
|
|
13512
|
+
var CompactionPipeline = class {
|
|
13513
|
+
strategies;
|
|
13514
|
+
constructor(strategies) {
|
|
13515
|
+
this.strategies = strategies;
|
|
13516
|
+
}
|
|
13517
|
+
/** The ordered list of strategy names in this pipeline. */
|
|
13518
|
+
get strategyNames() {
|
|
13519
|
+
return this.strategies.map((s) => s.name);
|
|
13520
|
+
}
|
|
13521
|
+
/**
|
|
13522
|
+
* Apply all strategies in order.
|
|
13523
|
+
* @param content — input string
|
|
13524
|
+
* @param budget — optional token budget forwarded to each strategy
|
|
13525
|
+
*/
|
|
13526
|
+
apply(content, budget) {
|
|
13527
|
+
return this.strategies.reduce((current, strategy) => {
|
|
13528
|
+
return strategy.apply(current, budget);
|
|
13529
|
+
}, content);
|
|
13530
|
+
}
|
|
13531
|
+
};
|
|
13532
|
+
|
|
13533
|
+
// src/caching/stability.ts
|
|
13534
|
+
import { NODE_STABILITY } from "@harness-engineering/graph";
|
|
13535
|
+
var STABILITY_LOOKUP = {};
|
|
13536
|
+
for (const [key, tier] of Object.entries(NODE_STABILITY)) {
|
|
13537
|
+
STABILITY_LOOKUP[key] = tier;
|
|
13538
|
+
STABILITY_LOOKUP[key.toLowerCase()] = tier;
|
|
13539
|
+
}
|
|
13540
|
+
STABILITY_LOOKUP["packed_summary"] = "session";
|
|
13541
|
+
STABILITY_LOOKUP["skill"] = "static";
|
|
13542
|
+
function resolveStability(contentType) {
|
|
13543
|
+
return STABILITY_LOOKUP[contentType] ?? "ephemeral";
|
|
13544
|
+
}
|
|
13545
|
+
|
|
13546
|
+
// src/caching/adapters/anthropic.ts
|
|
13547
|
+
var TIER_ORDER = {
|
|
13548
|
+
static: 0,
|
|
13549
|
+
session: 1,
|
|
13550
|
+
ephemeral: 2
|
|
13551
|
+
};
|
|
13552
|
+
var AnthropicCacheAdapter = class {
|
|
13553
|
+
provider = "claude";
|
|
13554
|
+
wrapSystemBlock(content, stability) {
|
|
13555
|
+
if (stability === "ephemeral") {
|
|
13556
|
+
return { type: "text", text: content };
|
|
13557
|
+
}
|
|
13558
|
+
const ttl = stability === "static" ? "1h" : void 0;
|
|
13559
|
+
return {
|
|
13560
|
+
type: "text",
|
|
13561
|
+
text: content,
|
|
13562
|
+
cache_control: {
|
|
13563
|
+
type: "ephemeral",
|
|
13564
|
+
...ttl !== void 0 && { ttl }
|
|
13565
|
+
}
|
|
13566
|
+
};
|
|
13567
|
+
}
|
|
13568
|
+
wrapTools(tools, stability) {
|
|
13569
|
+
if (tools.length === 0 || stability === "ephemeral") {
|
|
13570
|
+
return { tools: tools.map((t) => ({ ...t })) };
|
|
13571
|
+
}
|
|
13572
|
+
const wrapped = tools.map((t) => ({ ...t }));
|
|
13573
|
+
const last = wrapped[wrapped.length - 1];
|
|
13574
|
+
if (last) {
|
|
13575
|
+
last.cache_control = { type: "ephemeral" };
|
|
13576
|
+
}
|
|
13577
|
+
return { tools: wrapped };
|
|
13578
|
+
}
|
|
13579
|
+
orderContent(blocks) {
|
|
13580
|
+
return [...blocks].sort((a, b) => TIER_ORDER[a.stability] - TIER_ORDER[b.stability]);
|
|
13581
|
+
}
|
|
13582
|
+
parseCacheUsage(response) {
|
|
13583
|
+
const resp = response;
|
|
13584
|
+
const usage = resp?.usage;
|
|
13585
|
+
return {
|
|
13586
|
+
cacheCreationTokens: usage?.cache_creation_input_tokens ?? 0,
|
|
13587
|
+
cacheReadTokens: usage?.cache_read_input_tokens ?? 0
|
|
13588
|
+
};
|
|
13589
|
+
}
|
|
13590
|
+
};
|
|
13591
|
+
|
|
13592
|
+
// src/caching/adapters/openai.ts
|
|
13593
|
+
var TIER_ORDER2 = {
|
|
13594
|
+
static: 0,
|
|
13595
|
+
session: 1,
|
|
13596
|
+
ephemeral: 2
|
|
13597
|
+
};
|
|
13598
|
+
var OpenAICacheAdapter = class {
|
|
13599
|
+
provider = "openai";
|
|
13600
|
+
wrapSystemBlock(content, _stability) {
|
|
13601
|
+
return { type: "text", text: content };
|
|
13602
|
+
}
|
|
13603
|
+
wrapTools(tools, _stability) {
|
|
13604
|
+
return { tools: tools.map((t) => ({ ...t })) };
|
|
13605
|
+
}
|
|
13606
|
+
orderContent(blocks) {
|
|
13607
|
+
return [...blocks].sort((a, b) => TIER_ORDER2[a.stability] - TIER_ORDER2[b.stability]);
|
|
13608
|
+
}
|
|
13609
|
+
parseCacheUsage(response) {
|
|
13610
|
+
const resp = response;
|
|
13611
|
+
const usage = resp?.usage;
|
|
13612
|
+
const details = usage?.prompt_tokens_details;
|
|
13613
|
+
return {
|
|
13614
|
+
cacheCreationTokens: 0,
|
|
13615
|
+
cacheReadTokens: details?.cached_tokens ?? 0
|
|
13616
|
+
};
|
|
13617
|
+
}
|
|
13618
|
+
};
|
|
13619
|
+
|
|
13620
|
+
// src/caching/adapters/gemini.ts
|
|
13621
|
+
var TIER_ORDER3 = {
|
|
13622
|
+
static: 0,
|
|
13623
|
+
session: 1,
|
|
13624
|
+
ephemeral: 2
|
|
13625
|
+
};
|
|
13626
|
+
var CACHED_CONTENT_MARKER = "cachedContents:pending";
|
|
13627
|
+
var GeminiCacheAdapter = class {
|
|
13628
|
+
provider = "gemini";
|
|
13629
|
+
wrapSystemBlock(content, stability) {
|
|
13630
|
+
if (stability === "static") {
|
|
13631
|
+
return {
|
|
13632
|
+
type: "text",
|
|
13633
|
+
text: content,
|
|
13634
|
+
cachedContentRef: CACHED_CONTENT_MARKER
|
|
13635
|
+
};
|
|
13636
|
+
}
|
|
13637
|
+
return { type: "text", text: content };
|
|
13638
|
+
}
|
|
13639
|
+
wrapTools(tools, _stability) {
|
|
13640
|
+
return { tools: tools.map((t) => ({ ...t })) };
|
|
13641
|
+
}
|
|
13642
|
+
orderContent(blocks) {
|
|
13643
|
+
return [...blocks].sort((a, b) => TIER_ORDER3[a.stability] - TIER_ORDER3[b.stability]);
|
|
13644
|
+
}
|
|
13645
|
+
parseCacheUsage(response) {
|
|
13646
|
+
const resp = response;
|
|
13647
|
+
const metadata = resp?.usageMetadata;
|
|
13648
|
+
return {
|
|
13649
|
+
cacheCreationTokens: 0,
|
|
13650
|
+
cacheReadTokens: metadata?.cachedContentTokenCount ?? 0
|
|
13651
|
+
};
|
|
13652
|
+
}
|
|
13653
|
+
};
|
|
13654
|
+
|
|
13655
|
+
// src/telemetry/consent.ts
|
|
13656
|
+
import * as fs34 from "fs";
|
|
13657
|
+
import * as path33 from "path";
|
|
13658
|
+
|
|
13659
|
+
// src/telemetry/install-id.ts
|
|
13660
|
+
import * as fs33 from "fs";
|
|
13661
|
+
import * as path32 from "path";
|
|
13662
|
+
import * as crypto4 from "crypto";
|
|
13663
|
+
function getOrCreateInstallId(projectRoot) {
|
|
13664
|
+
const harnessDir = path32.join(projectRoot, ".harness");
|
|
13665
|
+
const installIdFile = path32.join(harnessDir, ".install-id");
|
|
13666
|
+
const UUID_V4_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
13667
|
+
try {
|
|
13668
|
+
const existing = fs33.readFileSync(installIdFile, "utf-8").trim();
|
|
13669
|
+
if (UUID_V4_RE.test(existing)) {
|
|
13670
|
+
return existing;
|
|
13671
|
+
}
|
|
13672
|
+
} catch {
|
|
13673
|
+
}
|
|
13674
|
+
const id = crypto4.randomUUID();
|
|
13675
|
+
fs33.mkdirSync(harnessDir, { recursive: true });
|
|
13676
|
+
fs33.writeFileSync(installIdFile, id, { encoding: "utf-8", mode: 384 });
|
|
13677
|
+
return id;
|
|
13678
|
+
}
|
|
13679
|
+
|
|
13680
|
+
// src/telemetry/consent.ts
|
|
13681
|
+
function readIdentity(projectRoot) {
|
|
13682
|
+
const filePath = path33.join(projectRoot, ".harness", "telemetry.json");
|
|
13683
|
+
try {
|
|
13684
|
+
const raw = fs34.readFileSync(filePath, "utf-8");
|
|
13685
|
+
const parsed = JSON.parse(raw);
|
|
13686
|
+
if (parsed && typeof parsed === "object" && parsed.identity) {
|
|
13687
|
+
const { project, team, alias } = parsed.identity;
|
|
13688
|
+
const identity = {};
|
|
13689
|
+
if (typeof project === "string") identity.project = project;
|
|
13690
|
+
if (typeof team === "string") identity.team = team;
|
|
13691
|
+
if (typeof alias === "string") identity.alias = alias;
|
|
13692
|
+
return identity;
|
|
13693
|
+
}
|
|
13694
|
+
return {};
|
|
13695
|
+
} catch {
|
|
13696
|
+
return {};
|
|
13697
|
+
}
|
|
13698
|
+
}
|
|
13699
|
+
function resolveConsent(projectRoot, config) {
|
|
13700
|
+
if (process.env.DO_NOT_TRACK === "1") return { allowed: false };
|
|
13701
|
+
if (process.env.HARNESS_TELEMETRY_OPTOUT === "1") return { allowed: false };
|
|
13702
|
+
const enabled = config?.enabled ?? true;
|
|
13703
|
+
if (!enabled) return { allowed: false };
|
|
13704
|
+
const installId = getOrCreateInstallId(projectRoot);
|
|
13705
|
+
const identity = readIdentity(projectRoot);
|
|
13706
|
+
return { allowed: true, installId, identity };
|
|
13707
|
+
}
|
|
13708
|
+
|
|
13709
|
+
// src/version.ts
|
|
13710
|
+
var VERSION = "0.21.3";
|
|
13711
|
+
|
|
13712
|
+
// src/telemetry/collector.ts
|
|
13713
|
+
function mapOutcome(outcome) {
|
|
13714
|
+
return outcome === "completed" ? "success" : "failure";
|
|
13715
|
+
}
|
|
13716
|
+
function collectEvents(projectRoot, consent) {
|
|
13717
|
+
const records = readAdoptionRecords(projectRoot);
|
|
13718
|
+
if (records.length === 0) return [];
|
|
13719
|
+
const { installId, identity } = consent;
|
|
13720
|
+
const distinctId = identity.alias ?? installId;
|
|
13721
|
+
return records.map(
|
|
13722
|
+
(record) => ({
|
|
13723
|
+
event: "skill_invocation",
|
|
13724
|
+
distinctId,
|
|
13725
|
+
timestamp: record.startedAt,
|
|
13726
|
+
properties: {
|
|
13727
|
+
installId,
|
|
13728
|
+
os: process.platform,
|
|
13729
|
+
nodeVersion: process.version,
|
|
13730
|
+
harnessVersion: VERSION,
|
|
13731
|
+
skillName: record.skill,
|
|
13732
|
+
duration: record.duration,
|
|
13733
|
+
outcome: mapOutcome(record.outcome),
|
|
13734
|
+
phasesReached: record.phasesReached,
|
|
13735
|
+
...identity.project ? { project: identity.project } : {},
|
|
13736
|
+
...identity.team ? { team: identity.team } : {}
|
|
13737
|
+
}
|
|
13738
|
+
})
|
|
13739
|
+
);
|
|
13740
|
+
}
|
|
13741
|
+
|
|
13742
|
+
// src/telemetry/transport.ts
|
|
13743
|
+
var POSTHOG_BATCH_URL = "https://app.posthog.com/batch";
|
|
13744
|
+
var MAX_ATTEMPTS = 3;
|
|
13745
|
+
var TIMEOUT_MS = 5e3;
|
|
13746
|
+
function sleep2(ms) {
|
|
13747
|
+
return new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
13748
|
+
}
|
|
13749
|
+
async function send(events, apiKey) {
|
|
13750
|
+
if (events.length === 0) return;
|
|
13751
|
+
const payload = { api_key: apiKey, batch: events };
|
|
13752
|
+
const body = JSON.stringify(payload);
|
|
13753
|
+
for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
|
|
13754
|
+
try {
|
|
13755
|
+
const res = await fetch(POSTHOG_BATCH_URL, {
|
|
13756
|
+
method: "POST",
|
|
13757
|
+
headers: { "Content-Type": "application/json" },
|
|
13758
|
+
body,
|
|
13759
|
+
signal: AbortSignal.timeout(TIMEOUT_MS)
|
|
13760
|
+
});
|
|
13761
|
+
if (res.ok) return;
|
|
13762
|
+
if (res.status < 500) return;
|
|
13763
|
+
} catch {
|
|
13764
|
+
}
|
|
13765
|
+
if (attempt < MAX_ATTEMPTS - 1) {
|
|
13766
|
+
await sleep2(1e3 * (attempt + 1));
|
|
13767
|
+
}
|
|
13768
|
+
}
|
|
13769
|
+
}
|
|
13301
13770
|
export {
|
|
13302
13771
|
AGENT_DESCRIPTORS,
|
|
13303
13772
|
ARCHITECTURE_DESCRIPTOR,
|
|
13304
13773
|
AdjustedForecastSchema,
|
|
13305
13774
|
AgentActionEmitter,
|
|
13775
|
+
AnthropicCacheAdapter,
|
|
13306
13776
|
ArchBaselineManager,
|
|
13307
13777
|
ArchBaselineSchema,
|
|
13308
13778
|
ArchConfigSchema,
|
|
@@ -13322,6 +13792,7 @@ export {
|
|
|
13322
13792
|
CategorySnapshotSchema,
|
|
13323
13793
|
ChecklistBuilder,
|
|
13324
13794
|
CircularDepsCollector,
|
|
13795
|
+
CompactionPipeline,
|
|
13325
13796
|
ComplexityCollector,
|
|
13326
13797
|
ConfidenceTierSchema,
|
|
13327
13798
|
ConfirmationSchema,
|
|
@@ -13336,6 +13807,7 @@ export {
|
|
|
13336
13807
|
DEFAULT_STABILITY_THRESHOLDS,
|
|
13337
13808
|
DEFAULT_STATE,
|
|
13338
13809
|
DEFAULT_STREAM_INDEX,
|
|
13810
|
+
DEFAULT_TOKEN_BUDGET,
|
|
13339
13811
|
DESTRUCTIVE_BASH,
|
|
13340
13812
|
DepDepthCollector,
|
|
13341
13813
|
DirectionSchema,
|
|
@@ -13349,6 +13821,7 @@ export {
|
|
|
13349
13821
|
ForbiddenImportCollector,
|
|
13350
13822
|
GateConfigSchema,
|
|
13351
13823
|
GateResultSchema,
|
|
13824
|
+
GeminiCacheAdapter,
|
|
13352
13825
|
GitHubIssuesSyncAdapter,
|
|
13353
13826
|
HandoffSchema,
|
|
13354
13827
|
HarnessStateSchema,
|
|
@@ -13363,6 +13836,7 @@ export {
|
|
|
13363
13836
|
NoOpExecutor,
|
|
13364
13837
|
NoOpSink,
|
|
13365
13838
|
NoOpTelemetryAdapter,
|
|
13839
|
+
OpenAICacheAdapter,
|
|
13366
13840
|
PatternConfigSchema,
|
|
13367
13841
|
PredictionEngine,
|
|
13368
13842
|
PredictionOptionsSchema,
|
|
@@ -13390,6 +13864,7 @@ export {
|
|
|
13390
13864
|
StabilityForecastSchema,
|
|
13391
13865
|
StreamIndexSchema,
|
|
13392
13866
|
StreamInfoSchema,
|
|
13867
|
+
StructuralStrategy,
|
|
13393
13868
|
ThresholdConfigSchema,
|
|
13394
13869
|
TimelineFileSchema,
|
|
13395
13870
|
TimelineManager,
|
|
@@ -13397,13 +13872,16 @@ export {
|
|
|
13397
13872
|
TransitionSchema,
|
|
13398
13873
|
TrendLineSchema,
|
|
13399
13874
|
TrendResultSchema,
|
|
13875
|
+
TruncationStrategy,
|
|
13400
13876
|
TypeScriptParser,
|
|
13401
13877
|
VERSION,
|
|
13402
13878
|
ViolationSchema,
|
|
13403
13879
|
addProvenance,
|
|
13404
13880
|
agentConfigRules,
|
|
13881
|
+
aggregateByDay2 as aggregateAdoptionByDay,
|
|
13405
13882
|
aggregateByDay,
|
|
13406
13883
|
aggregateBySession,
|
|
13884
|
+
aggregateBySkill,
|
|
13407
13885
|
analyzeDiff,
|
|
13408
13886
|
analyzeLearningPatterns,
|
|
13409
13887
|
appendFailure,
|
|
@@ -13435,6 +13913,7 @@ export {
|
|
|
13435
13913
|
clearFailuresCache,
|
|
13436
13914
|
clearLearningsCache,
|
|
13437
13915
|
clearTaint,
|
|
13916
|
+
collectEvents,
|
|
13438
13917
|
computeContentHash,
|
|
13439
13918
|
computeOverallSeverity,
|
|
13440
13919
|
computeScanExitCode,
|
|
@@ -13474,6 +13953,7 @@ export {
|
|
|
13474
13953
|
determineAssessment,
|
|
13475
13954
|
diff,
|
|
13476
13955
|
emitEvent,
|
|
13956
|
+
estimateTokens,
|
|
13477
13957
|
executeWorkflow,
|
|
13478
13958
|
expressRules,
|
|
13479
13959
|
extractBundle,
|
|
@@ -13495,6 +13975,7 @@ export {
|
|
|
13495
13975
|
getFeedbackConfig,
|
|
13496
13976
|
getInjectionPatterns,
|
|
13497
13977
|
getModelPrice,
|
|
13978
|
+
getOrCreateInstallId,
|
|
13498
13979
|
getOutline,
|
|
13499
13980
|
getParser,
|
|
13500
13981
|
getPhaseCategories,
|
|
@@ -13547,8 +14028,10 @@ export {
|
|
|
13547
14028
|
promoteSessionLearnings,
|
|
13548
14029
|
pruneLearnings,
|
|
13549
14030
|
reactRules,
|
|
14031
|
+
readAdoptionRecords,
|
|
13550
14032
|
readCheckState,
|
|
13551
14033
|
readCostRecords,
|
|
14034
|
+
readIdentity,
|
|
13552
14035
|
readLockfile,
|
|
13553
14036
|
readSessionSection,
|
|
13554
14037
|
readSessionSections,
|
|
@@ -13559,11 +14042,13 @@ export {
|
|
|
13559
14042
|
requestPeerReview,
|
|
13560
14043
|
resetFeedbackConfig,
|
|
13561
14044
|
resetParserCache,
|
|
14045
|
+
resolveConsent,
|
|
13562
14046
|
resolveFileToLayer,
|
|
13563
14047
|
resolveModelTier,
|
|
13564
14048
|
resolveReverseStatus,
|
|
13565
14049
|
resolveRuleSeverity,
|
|
13566
14050
|
resolveSessionDir,
|
|
14051
|
+
resolveStability,
|
|
13567
14052
|
resolveStreamPath,
|
|
13568
14053
|
resolveThresholds,
|
|
13569
14054
|
runAll,
|
|
@@ -13585,6 +14070,8 @@ export {
|
|
|
13585
14070
|
scoreRoadmapCandidates,
|
|
13586
14071
|
searchSymbols,
|
|
13587
14072
|
secretRules,
|
|
14073
|
+
send,
|
|
14074
|
+
serializeEnvelope,
|
|
13588
14075
|
serializeRoadmap,
|
|
13589
14076
|
setActiveStream,
|
|
13590
14077
|
sharpEdgesRules,
|
|
@@ -13595,6 +14082,7 @@ export {
|
|
|
13595
14082
|
syncRoadmap,
|
|
13596
14083
|
syncToExternal,
|
|
13597
14084
|
tagUncitedFindings,
|
|
14085
|
+
topSkills,
|
|
13598
14086
|
touchStream,
|
|
13599
14087
|
trackAction,
|
|
13600
14088
|
unfoldRange,
|