@harness-engineering/core 0.21.0 → 0.21.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 +24 -8
- package/dist/index.d.ts +24 -8
- package/dist/index.js +594 -524
- package/dist/index.mjs +597 -529
- package/package.json +2 -2
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 path31 = firstError.path.join(".");
|
|
88
|
+
const pathDisplay = path31 ? ` at "${path31}"` : "";
|
|
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 "${path31}" is required and must be of type "${expected}"`);
|
|
96
96
|
} else {
|
|
97
97
|
code = "INVALID_TYPE";
|
|
98
98
|
message = `Invalid type${pathDisplay}: ${firstError.message}`;
|
|
@@ -308,27 +308,27 @@ function extractSections(content) {
|
|
|
308
308
|
}
|
|
309
309
|
return sections.map((section) => buildAgentMapSection(section, lines));
|
|
310
310
|
}
|
|
311
|
-
function isExternalLink(
|
|
312
|
-
return
|
|
311
|
+
function isExternalLink(path31) {
|
|
312
|
+
return path31.startsWith("http://") || path31.startsWith("https://") || path31.startsWith("#") || path31.startsWith("mailto:");
|
|
313
313
|
}
|
|
314
314
|
function resolveLinkPath(linkPath, baseDir) {
|
|
315
315
|
return linkPath.startsWith(".") ? join(baseDir, linkPath) : linkPath;
|
|
316
316
|
}
|
|
317
|
-
async function validateAgentsMap(
|
|
318
|
-
const contentResult = await readFileContent(
|
|
317
|
+
async function validateAgentsMap(path31 = "./AGENTS.md") {
|
|
318
|
+
const contentResult = await readFileContent(path31);
|
|
319
319
|
if (!contentResult.ok) {
|
|
320
320
|
return Err(
|
|
321
321
|
createError(
|
|
322
322
|
"PARSE_ERROR",
|
|
323
323
|
`Failed to read AGENTS.md: ${contentResult.error.message}`,
|
|
324
|
-
{ path:
|
|
324
|
+
{ path: path31 },
|
|
325
325
|
["Ensure the file exists", "Check file permissions"]
|
|
326
326
|
)
|
|
327
327
|
);
|
|
328
328
|
}
|
|
329
329
|
const content = contentResult.value;
|
|
330
330
|
const sections = extractSections(content);
|
|
331
|
-
const baseDir = dirname(
|
|
331
|
+
const baseDir = dirname(path31);
|
|
332
332
|
const sectionTitles = sections.map((s) => s.title);
|
|
333
333
|
const missingSections = REQUIRED_SECTIONS.filter(
|
|
334
334
|
(required) => !sectionTitles.some((title) => title.toLowerCase().includes(required.toLowerCase()))
|
|
@@ -469,8 +469,8 @@ async function checkDocCoverage(domain, options = {}) {
|
|
|
469
469
|
|
|
470
470
|
// src/context/knowledge-map.ts
|
|
471
471
|
import { join as join2, basename as basename2 } from "path";
|
|
472
|
-
function suggestFix(
|
|
473
|
-
const targetName = basename2(
|
|
472
|
+
function suggestFix(path31, existingFiles) {
|
|
473
|
+
const targetName = basename2(path31).toLowerCase();
|
|
474
474
|
const similar = existingFiles.find((file) => {
|
|
475
475
|
const fileName = basename2(file).toLowerCase();
|
|
476
476
|
return fileName.includes(targetName) || targetName.includes(fileName);
|
|
@@ -478,7 +478,7 @@ function suggestFix(path28, existingFiles) {
|
|
|
478
478
|
if (similar) {
|
|
479
479
|
return `Did you mean "${similar}"?`;
|
|
480
480
|
}
|
|
481
|
-
return `Create the file "${
|
|
481
|
+
return `Create the file "${path31}" or remove the link`;
|
|
482
482
|
}
|
|
483
483
|
async function validateKnowledgeMap(rootDir = process.cwd()) {
|
|
484
484
|
const agentsPath = join2(rootDir, "AGENTS.md");
|
|
@@ -830,8 +830,8 @@ function createBoundaryValidator(schema, name) {
|
|
|
830
830
|
return Ok(result.data);
|
|
831
831
|
}
|
|
832
832
|
const suggestions = result.error.issues.map((issue) => {
|
|
833
|
-
const
|
|
834
|
-
return
|
|
833
|
+
const path31 = issue.path.join(".");
|
|
834
|
+
return path31 ? `${path31}: ${issue.message}` : issue.message;
|
|
835
835
|
});
|
|
836
836
|
return Err(
|
|
837
837
|
createError(
|
|
@@ -1463,11 +1463,11 @@ function processExportListSpecifiers(exportDecl, exports) {
|
|
|
1463
1463
|
var TypeScriptParser = class {
|
|
1464
1464
|
name = "typescript";
|
|
1465
1465
|
extensions = [".ts", ".tsx", ".mts", ".cts"];
|
|
1466
|
-
async parseFile(
|
|
1467
|
-
const contentResult = await readFileContent(
|
|
1466
|
+
async parseFile(path31) {
|
|
1467
|
+
const contentResult = await readFileContent(path31);
|
|
1468
1468
|
if (!contentResult.ok) {
|
|
1469
1469
|
return Err(
|
|
1470
|
-
createParseError("NOT_FOUND", `File not found: ${
|
|
1470
|
+
createParseError("NOT_FOUND", `File not found: ${path31}`, { path: path31 }, [
|
|
1471
1471
|
"Check that the file exists",
|
|
1472
1472
|
"Verify the path is correct"
|
|
1473
1473
|
])
|
|
@@ -1477,7 +1477,7 @@ var TypeScriptParser = class {
|
|
|
1477
1477
|
const ast = parse(contentResult.value, {
|
|
1478
1478
|
loc: true,
|
|
1479
1479
|
range: true,
|
|
1480
|
-
jsx:
|
|
1480
|
+
jsx: path31.endsWith(".tsx"),
|
|
1481
1481
|
errorOnUnknownASTType: false
|
|
1482
1482
|
});
|
|
1483
1483
|
return Ok({
|
|
@@ -1488,7 +1488,7 @@ var TypeScriptParser = class {
|
|
|
1488
1488
|
} catch (e) {
|
|
1489
1489
|
const error = e;
|
|
1490
1490
|
return Err(
|
|
1491
|
-
createParseError("SYNTAX_ERROR", `Failed to parse ${
|
|
1491
|
+
createParseError("SYNTAX_ERROR", `Failed to parse ${path31}: ${error.message}`, { path: path31 }, [
|
|
1492
1492
|
"Check for syntax errors in the file",
|
|
1493
1493
|
"Ensure valid TypeScript syntax"
|
|
1494
1494
|
])
|
|
@@ -1673,22 +1673,22 @@ function extractInlineRefs(content) {
|
|
|
1673
1673
|
}
|
|
1674
1674
|
return refs;
|
|
1675
1675
|
}
|
|
1676
|
-
async function parseDocumentationFile(
|
|
1677
|
-
const contentResult = await readFileContent(
|
|
1676
|
+
async function parseDocumentationFile(path31) {
|
|
1677
|
+
const contentResult = await readFileContent(path31);
|
|
1678
1678
|
if (!contentResult.ok) {
|
|
1679
1679
|
return Err(
|
|
1680
1680
|
createEntropyError(
|
|
1681
1681
|
"PARSE_ERROR",
|
|
1682
|
-
`Failed to read documentation file: ${
|
|
1683
|
-
{ file:
|
|
1682
|
+
`Failed to read documentation file: ${path31}`,
|
|
1683
|
+
{ file: path31 },
|
|
1684
1684
|
["Check that the file exists"]
|
|
1685
1685
|
)
|
|
1686
1686
|
);
|
|
1687
1687
|
}
|
|
1688
1688
|
const content = contentResult.value;
|
|
1689
|
-
const type =
|
|
1689
|
+
const type = path31.endsWith(".md") ? "markdown" : "text";
|
|
1690
1690
|
return Ok({
|
|
1691
|
-
path:
|
|
1691
|
+
path: path31,
|
|
1692
1692
|
type,
|
|
1693
1693
|
content,
|
|
1694
1694
|
codeBlocks: extractCodeBlocks(content),
|
|
@@ -5218,8 +5218,7 @@ function parseListField(fieldMap, ...keys) {
|
|
|
5218
5218
|
if (raw === EM_DASH || raw === "none") return [];
|
|
5219
5219
|
return raw.split(",").map((s) => s.trim());
|
|
5220
5220
|
}
|
|
5221
|
-
function
|
|
5222
|
-
const fieldMap = extractFieldMap(body);
|
|
5221
|
+
function validateStatus(name, fieldMap) {
|
|
5223
5222
|
const statusRaw = fieldMap.get("Status");
|
|
5224
5223
|
if (!statusRaw || !VALID_STATUSES.has(statusRaw)) {
|
|
5225
5224
|
return Err2(
|
|
@@ -5228,12 +5227,10 @@ function parseFeatureFields(name, body) {
|
|
|
5228
5227
|
)
|
|
5229
5228
|
);
|
|
5230
5229
|
}
|
|
5231
|
-
|
|
5232
|
-
|
|
5233
|
-
|
|
5234
|
-
const assigneeRaw = fieldMap.get("Assignee") ?? EM_DASH;
|
|
5230
|
+
return Ok2(statusRaw);
|
|
5231
|
+
}
|
|
5232
|
+
function validatePriority(name, fieldMap) {
|
|
5235
5233
|
const priorityRaw = fieldMap.get("Priority") ?? EM_DASH;
|
|
5236
|
-
const externalIdRaw = fieldMap.get("External-ID") ?? EM_DASH;
|
|
5237
5234
|
if (priorityRaw !== EM_DASH && !VALID_PRIORITIES.has(priorityRaw)) {
|
|
5238
5235
|
return Err2(
|
|
5239
5236
|
new Error(
|
|
@@ -5241,16 +5238,28 @@ function parseFeatureFields(name, body) {
|
|
|
5241
5238
|
)
|
|
5242
5239
|
);
|
|
5243
5240
|
}
|
|
5241
|
+
return Ok2(priorityRaw === EM_DASH ? null : priorityRaw);
|
|
5242
|
+
}
|
|
5243
|
+
function optionalField(fieldMap, key) {
|
|
5244
|
+
const raw = fieldMap.get(key) ?? EM_DASH;
|
|
5245
|
+
return raw === EM_DASH ? null : raw;
|
|
5246
|
+
}
|
|
5247
|
+
function parseFeatureFields(name, body) {
|
|
5248
|
+
const fieldMap = extractFieldMap(body);
|
|
5249
|
+
const statusResult = validateStatus(name, fieldMap);
|
|
5250
|
+
if (!statusResult.ok) return statusResult;
|
|
5251
|
+
const priorityResult = validatePriority(name, fieldMap);
|
|
5252
|
+
if (!priorityResult.ok) return priorityResult;
|
|
5244
5253
|
return Ok2({
|
|
5245
5254
|
name,
|
|
5246
|
-
status:
|
|
5247
|
-
spec:
|
|
5248
|
-
plans,
|
|
5249
|
-
blockedBy,
|
|
5255
|
+
status: statusResult.value,
|
|
5256
|
+
spec: optionalField(fieldMap, "Spec"),
|
|
5257
|
+
plans: parseListField(fieldMap, "Plans", "Plan"),
|
|
5258
|
+
blockedBy: parseListField(fieldMap, "Blocked by", "Blockers"),
|
|
5250
5259
|
summary: fieldMap.get("Summary") ?? "",
|
|
5251
|
-
assignee:
|
|
5252
|
-
priority:
|
|
5253
|
-
externalId:
|
|
5260
|
+
assignee: optionalField(fieldMap, "Assignee"),
|
|
5261
|
+
priority: priorityResult.value,
|
|
5262
|
+
externalId: optionalField(fieldMap, "External-ID")
|
|
5254
5263
|
});
|
|
5255
5264
|
}
|
|
5256
5265
|
function parseAssignmentHistory(body) {
|
|
@@ -5316,82 +5325,16 @@ var PredictionEngine = class {
|
|
|
5316
5325
|
const firstDate = new Date(snapshots[0].capturedAt).getTime();
|
|
5317
5326
|
const lastSnapshot = snapshots[snapshots.length - 1];
|
|
5318
5327
|
const currentT = (new Date(lastSnapshot.capturedAt).getTime() - firstDate) / (7 * 24 * 60 * 60 * 1e3);
|
|
5319
|
-
const baselines =
|
|
5320
|
-
|
|
5321
|
-
|
|
5322
|
-
|
|
5323
|
-
|
|
5324
|
-
|
|
5325
|
-
|
|
5326
|
-
|
|
5327
|
-
const timeSeries = this.extractTimeSeries(snapshots, category, firstDate);
|
|
5328
|
-
baselines[category] = this.forecastCategory(
|
|
5329
|
-
category,
|
|
5330
|
-
timeSeries,
|
|
5331
|
-
currentT,
|
|
5332
|
-
threshold,
|
|
5333
|
-
opts.horizon
|
|
5334
|
-
);
|
|
5335
|
-
}
|
|
5328
|
+
const baselines = this.computeBaselines(
|
|
5329
|
+
categoriesToProcess,
|
|
5330
|
+
thresholds,
|
|
5331
|
+
snapshots,
|
|
5332
|
+
firstDate,
|
|
5333
|
+
currentT,
|
|
5334
|
+
opts.horizon
|
|
5335
|
+
);
|
|
5336
5336
|
const specImpacts = this.computeSpecImpacts(opts);
|
|
5337
|
-
const categories =
|
|
5338
|
-
for (const category of ALL_CATEGORIES2) {
|
|
5339
|
-
const baseline = baselines[category];
|
|
5340
|
-
const threshold = thresholds[category];
|
|
5341
|
-
if (!specImpacts || specImpacts.length === 0) {
|
|
5342
|
-
categories[category] = {
|
|
5343
|
-
baseline,
|
|
5344
|
-
adjusted: baseline,
|
|
5345
|
-
contributingFeatures: []
|
|
5346
|
-
};
|
|
5347
|
-
continue;
|
|
5348
|
-
}
|
|
5349
|
-
let totalDelta = 0;
|
|
5350
|
-
const contributing = [];
|
|
5351
|
-
for (const impact of specImpacts) {
|
|
5352
|
-
const delta = impact.deltas?.[category] ?? 0;
|
|
5353
|
-
if (delta !== 0) {
|
|
5354
|
-
totalDelta += delta;
|
|
5355
|
-
contributing.push({
|
|
5356
|
-
name: impact.featureName,
|
|
5357
|
-
specPath: impact.specPath,
|
|
5358
|
-
delta
|
|
5359
|
-
});
|
|
5360
|
-
}
|
|
5361
|
-
}
|
|
5362
|
-
if (totalDelta === 0) {
|
|
5363
|
-
categories[category] = {
|
|
5364
|
-
baseline,
|
|
5365
|
-
adjusted: baseline,
|
|
5366
|
-
contributingFeatures: []
|
|
5367
|
-
};
|
|
5368
|
-
continue;
|
|
5369
|
-
}
|
|
5370
|
-
const adjusted = {
|
|
5371
|
-
...baseline,
|
|
5372
|
-
projectedValue4w: baseline.projectedValue4w + totalDelta,
|
|
5373
|
-
projectedValue8w: baseline.projectedValue8w + totalDelta,
|
|
5374
|
-
projectedValue12w: baseline.projectedValue12w + totalDelta
|
|
5375
|
-
};
|
|
5376
|
-
const adjustedFit = {
|
|
5377
|
-
slope: baseline.regression.slope,
|
|
5378
|
-
intercept: baseline.regression.intercept + totalDelta,
|
|
5379
|
-
rSquared: baseline.regression.rSquared,
|
|
5380
|
-
dataPoints: baseline.regression.dataPoints
|
|
5381
|
-
};
|
|
5382
|
-
adjusted.thresholdCrossingWeeks = weeksUntilThreshold(adjustedFit, currentT, threshold);
|
|
5383
|
-
adjusted.regression = {
|
|
5384
|
-
slope: adjustedFit.slope,
|
|
5385
|
-
intercept: adjustedFit.intercept,
|
|
5386
|
-
rSquared: adjustedFit.rSquared,
|
|
5387
|
-
dataPoints: adjustedFit.dataPoints
|
|
5388
|
-
};
|
|
5389
|
-
categories[category] = {
|
|
5390
|
-
baseline,
|
|
5391
|
-
adjusted,
|
|
5392
|
-
contributingFeatures: contributing
|
|
5393
|
-
};
|
|
5394
|
-
}
|
|
5337
|
+
const categories = this.computeAdjustedForecasts(baselines, thresholds, specImpacts, currentT);
|
|
5395
5338
|
const warnings = this.generateWarnings(
|
|
5396
5339
|
categories,
|
|
5397
5340
|
opts.horizon
|
|
@@ -5433,6 +5376,76 @@ var PredictionEngine = class {
|
|
|
5433
5376
|
}
|
|
5434
5377
|
return base;
|
|
5435
5378
|
}
|
|
5379
|
+
computeBaselines(categoriesToProcess, thresholds, snapshots, firstDate, currentT, horizon) {
|
|
5380
|
+
const baselines = {};
|
|
5381
|
+
for (const category of ALL_CATEGORIES2) {
|
|
5382
|
+
const threshold = thresholds[category];
|
|
5383
|
+
if (!categoriesToProcess.includes(category)) {
|
|
5384
|
+
baselines[category] = this.zeroForecast(category, threshold);
|
|
5385
|
+
continue;
|
|
5386
|
+
}
|
|
5387
|
+
const timeSeries = this.extractTimeSeries(snapshots, category, firstDate);
|
|
5388
|
+
baselines[category] = this.forecastCategory(
|
|
5389
|
+
category,
|
|
5390
|
+
timeSeries,
|
|
5391
|
+
currentT,
|
|
5392
|
+
threshold,
|
|
5393
|
+
horizon
|
|
5394
|
+
);
|
|
5395
|
+
}
|
|
5396
|
+
return baselines;
|
|
5397
|
+
}
|
|
5398
|
+
computeAdjustedForecasts(baselines, thresholds, specImpacts, currentT) {
|
|
5399
|
+
const categories = {};
|
|
5400
|
+
for (const category of ALL_CATEGORIES2) {
|
|
5401
|
+
const baseline = baselines[category];
|
|
5402
|
+
categories[category] = this.adjustForecastForCategory(
|
|
5403
|
+
category,
|
|
5404
|
+
baseline,
|
|
5405
|
+
thresholds[category],
|
|
5406
|
+
specImpacts,
|
|
5407
|
+
currentT
|
|
5408
|
+
);
|
|
5409
|
+
}
|
|
5410
|
+
return categories;
|
|
5411
|
+
}
|
|
5412
|
+
adjustForecastForCategory(category, baseline, threshold, specImpacts, currentT) {
|
|
5413
|
+
if (!specImpacts || specImpacts.length === 0) {
|
|
5414
|
+
return { baseline, adjusted: baseline, contributingFeatures: [] };
|
|
5415
|
+
}
|
|
5416
|
+
let totalDelta = 0;
|
|
5417
|
+
const contributing = [];
|
|
5418
|
+
for (const impact of specImpacts) {
|
|
5419
|
+
const delta = impact.deltas?.[category] ?? 0;
|
|
5420
|
+
if (delta !== 0) {
|
|
5421
|
+
totalDelta += delta;
|
|
5422
|
+
contributing.push({ name: impact.featureName, specPath: impact.specPath, delta });
|
|
5423
|
+
}
|
|
5424
|
+
}
|
|
5425
|
+
if (totalDelta === 0) {
|
|
5426
|
+
return { baseline, adjusted: baseline, contributingFeatures: [] };
|
|
5427
|
+
}
|
|
5428
|
+
const adjusted = {
|
|
5429
|
+
...baseline,
|
|
5430
|
+
projectedValue4w: baseline.projectedValue4w + totalDelta,
|
|
5431
|
+
projectedValue8w: baseline.projectedValue8w + totalDelta,
|
|
5432
|
+
projectedValue12w: baseline.projectedValue12w + totalDelta
|
|
5433
|
+
};
|
|
5434
|
+
const adjustedFit = {
|
|
5435
|
+
slope: baseline.regression.slope,
|
|
5436
|
+
intercept: baseline.regression.intercept + totalDelta,
|
|
5437
|
+
rSquared: baseline.regression.rSquared,
|
|
5438
|
+
dataPoints: baseline.regression.dataPoints
|
|
5439
|
+
};
|
|
5440
|
+
adjusted.thresholdCrossingWeeks = weeksUntilThreshold(adjustedFit, currentT, threshold);
|
|
5441
|
+
adjusted.regression = {
|
|
5442
|
+
slope: adjustedFit.slope,
|
|
5443
|
+
intercept: adjustedFit.intercept,
|
|
5444
|
+
rSquared: adjustedFit.rSquared,
|
|
5445
|
+
dataPoints: adjustedFit.dataPoints
|
|
5446
|
+
};
|
|
5447
|
+
return { baseline, adjusted, contributingFeatures: contributing };
|
|
5448
|
+
}
|
|
5436
5449
|
/**
|
|
5437
5450
|
* Extract time series for a single category from snapshots.
|
|
5438
5451
|
* Returns array of { t (weeks from first), value } sorted oldest first.
|
|
@@ -6257,7 +6270,7 @@ async function saveState(projectPath, state, stream, session) {
|
|
|
6257
6270
|
}
|
|
6258
6271
|
}
|
|
6259
6272
|
|
|
6260
|
-
// src/state/learnings.ts
|
|
6273
|
+
// src/state/learnings-content.ts
|
|
6261
6274
|
import * as fs11 from "fs";
|
|
6262
6275
|
import * as path8 from "path";
|
|
6263
6276
|
import * as crypto from "crypto";
|
|
@@ -6268,6 +6281,25 @@ function parseFrontmatter2(line) {
|
|
|
6268
6281
|
const tags = match[2] ? match[2].split(",").filter(Boolean) : [];
|
|
6269
6282
|
return { hash, tags };
|
|
6270
6283
|
}
|
|
6284
|
+
function parseDateFromEntry(entry) {
|
|
6285
|
+
const match = entry.match(/(\d{4}-\d{2}-\d{2})/);
|
|
6286
|
+
return match ? match[1] ?? null : null;
|
|
6287
|
+
}
|
|
6288
|
+
function extractIndexEntry(entry) {
|
|
6289
|
+
const lines = entry.split("\n");
|
|
6290
|
+
const summary = lines[0] ?? entry;
|
|
6291
|
+
const tags = [];
|
|
6292
|
+
const skillMatch = entry.match(/\[skill:([^\]]+)\]/);
|
|
6293
|
+
if (skillMatch?.[1]) tags.push(skillMatch[1]);
|
|
6294
|
+
const outcomeMatch = entry.match(/\[outcome:([^\]]+)\]/);
|
|
6295
|
+
if (outcomeMatch?.[1]) tags.push(outcomeMatch[1]);
|
|
6296
|
+
return {
|
|
6297
|
+
hash: computeEntryHash(entry),
|
|
6298
|
+
tags,
|
|
6299
|
+
summary,
|
|
6300
|
+
fullText: entry
|
|
6301
|
+
};
|
|
6302
|
+
}
|
|
6271
6303
|
function computeEntryHash(text) {
|
|
6272
6304
|
return crypto.createHash("sha256").update(text).digest("hex").slice(0, 8);
|
|
6273
6305
|
}
|
|
@@ -6302,8 +6334,8 @@ function saveContentHashes(stateDir, index) {
|
|
|
6302
6334
|
const hashesPath = path8.join(stateDir, CONTENT_HASHES_FILE);
|
|
6303
6335
|
fs11.writeFileSync(hashesPath, JSON.stringify(index, null, 2) + "\n");
|
|
6304
6336
|
}
|
|
6305
|
-
function rebuildContentHashes(stateDir) {
|
|
6306
|
-
const learningsPath = path8.join(stateDir,
|
|
6337
|
+
function rebuildContentHashes(stateDir, learningsFile) {
|
|
6338
|
+
const learningsPath = path8.join(stateDir, learningsFile);
|
|
6307
6339
|
if (!fs11.existsSync(learningsPath)) return {};
|
|
6308
6340
|
const content = fs11.readFileSync(learningsPath, "utf-8");
|
|
6309
6341
|
const lines = content.split("\n");
|
|
@@ -6324,43 +6356,125 @@ function rebuildContentHashes(stateDir) {
|
|
|
6324
6356
|
saveContentHashes(stateDir, index);
|
|
6325
6357
|
return index;
|
|
6326
6358
|
}
|
|
6327
|
-
function
|
|
6328
|
-
const
|
|
6329
|
-
const
|
|
6330
|
-
|
|
6331
|
-
|
|
6332
|
-
|
|
6333
|
-
|
|
6334
|
-
|
|
6335
|
-
|
|
6336
|
-
|
|
6337
|
-
|
|
6338
|
-
|
|
6339
|
-
|
|
6340
|
-
|
|
6359
|
+
function analyzeLearningPatterns(entries) {
|
|
6360
|
+
const tagGroups = /* @__PURE__ */ new Map();
|
|
6361
|
+
for (const entry of entries) {
|
|
6362
|
+
const tagMatches = entry.matchAll(/\[(skill:[^\]]+)\]|\[(outcome:[^\]]+)\]/g);
|
|
6363
|
+
for (const match of tagMatches) {
|
|
6364
|
+
const tag = match[1] ?? match[2];
|
|
6365
|
+
if (tag) {
|
|
6366
|
+
const group = tagGroups.get(tag) ?? [];
|
|
6367
|
+
group.push(entry);
|
|
6368
|
+
tagGroups.set(tag, group);
|
|
6369
|
+
}
|
|
6370
|
+
}
|
|
6371
|
+
}
|
|
6372
|
+
const patterns = [];
|
|
6373
|
+
for (const [tag, groupEntries] of tagGroups) {
|
|
6374
|
+
if (groupEntries.length >= 3) {
|
|
6375
|
+
patterns.push({ tag, count: groupEntries.length, entries: groupEntries });
|
|
6376
|
+
}
|
|
6377
|
+
}
|
|
6378
|
+
return patterns.sort((a, b) => b.count - a.count);
|
|
6379
|
+
}
|
|
6380
|
+
function estimateTokens(text) {
|
|
6381
|
+
return Math.ceil(text.length / 4);
|
|
6341
6382
|
}
|
|
6383
|
+
function scoreRelevance(entry, intent) {
|
|
6384
|
+
if (!intent || intent.trim() === "") return 0;
|
|
6385
|
+
const intentWords = intent.toLowerCase().split(/\s+/).filter((w) => w.length > 2);
|
|
6386
|
+
if (intentWords.length === 0) return 0;
|
|
6387
|
+
const entryLower = entry.toLowerCase();
|
|
6388
|
+
const matches = intentWords.filter((word) => entryLower.includes(word));
|
|
6389
|
+
return matches.length / intentWords.length;
|
|
6390
|
+
}
|
|
6391
|
+
|
|
6392
|
+
// src/state/learnings-loader.ts
|
|
6393
|
+
import * as fs12 from "fs";
|
|
6394
|
+
import * as path9 from "path";
|
|
6342
6395
|
var learningsCacheMap = /* @__PURE__ */ new Map();
|
|
6343
6396
|
function clearLearningsCache() {
|
|
6344
6397
|
learningsCacheMap.clear();
|
|
6345
6398
|
}
|
|
6399
|
+
function invalidateLearningsCacheEntry(key) {
|
|
6400
|
+
learningsCacheMap.delete(key);
|
|
6401
|
+
}
|
|
6402
|
+
async function loadRelevantLearnings(projectPath, skillName, stream, session) {
|
|
6403
|
+
try {
|
|
6404
|
+
const dirResult = await getStateDir(projectPath, stream, session);
|
|
6405
|
+
if (!dirResult.ok) return dirResult;
|
|
6406
|
+
const stateDir = dirResult.value;
|
|
6407
|
+
const learningsPath = path9.join(stateDir, LEARNINGS_FILE);
|
|
6408
|
+
if (!fs12.existsSync(learningsPath)) {
|
|
6409
|
+
return Ok([]);
|
|
6410
|
+
}
|
|
6411
|
+
const stats = fs12.statSync(learningsPath);
|
|
6412
|
+
const cacheKey = learningsPath;
|
|
6413
|
+
const cached = learningsCacheMap.get(cacheKey);
|
|
6414
|
+
let entries;
|
|
6415
|
+
if (cached && cached.mtimeMs === stats.mtimeMs) {
|
|
6416
|
+
entries = cached.entries;
|
|
6417
|
+
} else {
|
|
6418
|
+
const content = fs12.readFileSync(learningsPath, "utf-8");
|
|
6419
|
+
const lines = content.split("\n");
|
|
6420
|
+
entries = [];
|
|
6421
|
+
let currentBlock = [];
|
|
6422
|
+
for (const line of lines) {
|
|
6423
|
+
if (line.startsWith("# ")) continue;
|
|
6424
|
+
if (/^<!--\s+hash:[a-f0-9]+/.test(line)) continue;
|
|
6425
|
+
const isDatedBullet = /^- \*\*\d{4}-\d{2}-\d{2}/.test(line);
|
|
6426
|
+
const isHeading = /^## \d{4}-\d{2}-\d{2}/.test(line);
|
|
6427
|
+
if (isDatedBullet || isHeading) {
|
|
6428
|
+
if (currentBlock.length > 0) {
|
|
6429
|
+
entries.push(currentBlock.join("\n"));
|
|
6430
|
+
}
|
|
6431
|
+
currentBlock = [line];
|
|
6432
|
+
} else if (line.trim() !== "" && currentBlock.length > 0) {
|
|
6433
|
+
currentBlock.push(line);
|
|
6434
|
+
}
|
|
6435
|
+
}
|
|
6436
|
+
if (currentBlock.length > 0) {
|
|
6437
|
+
entries.push(currentBlock.join("\n"));
|
|
6438
|
+
}
|
|
6439
|
+
learningsCacheMap.set(cacheKey, { mtimeMs: stats.mtimeMs, entries });
|
|
6440
|
+
evictIfNeeded(learningsCacheMap);
|
|
6441
|
+
}
|
|
6442
|
+
if (!skillName) {
|
|
6443
|
+
return Ok(entries);
|
|
6444
|
+
}
|
|
6445
|
+
const filtered = entries.filter((entry) => entry.includes(`[skill:${skillName}]`));
|
|
6446
|
+
return Ok(filtered);
|
|
6447
|
+
} catch (error) {
|
|
6448
|
+
return Err(
|
|
6449
|
+
new Error(
|
|
6450
|
+
`Failed to load learnings: ${error instanceof Error ? error.message : String(error)}`
|
|
6451
|
+
)
|
|
6452
|
+
);
|
|
6453
|
+
}
|
|
6454
|
+
}
|
|
6455
|
+
|
|
6456
|
+
// src/state/learnings.ts
|
|
6457
|
+
import * as fs13 from "fs";
|
|
6458
|
+
import * as path10 from "path";
|
|
6459
|
+
import * as crypto2 from "crypto";
|
|
6346
6460
|
async function appendLearning(projectPath, learning, skillName, outcome, stream, session) {
|
|
6347
6461
|
try {
|
|
6348
6462
|
const dirResult = await getStateDir(projectPath, stream, session);
|
|
6349
6463
|
if (!dirResult.ok) return dirResult;
|
|
6350
6464
|
const stateDir = dirResult.value;
|
|
6351
|
-
const learningsPath =
|
|
6352
|
-
|
|
6465
|
+
const learningsPath = path10.join(stateDir, LEARNINGS_FILE);
|
|
6466
|
+
fs13.mkdirSync(stateDir, { recursive: true });
|
|
6353
6467
|
const normalizedContent = normalizeLearningContent(learning);
|
|
6354
6468
|
const contentHash = computeContentHash(normalizedContent);
|
|
6355
|
-
const hashesPath =
|
|
6469
|
+
const hashesPath = path10.join(stateDir, CONTENT_HASHES_FILE);
|
|
6356
6470
|
let contentHashes;
|
|
6357
|
-
if (
|
|
6471
|
+
if (fs13.existsSync(hashesPath)) {
|
|
6358
6472
|
contentHashes = loadContentHashes(stateDir);
|
|
6359
|
-
if (Object.keys(contentHashes).length === 0 &&
|
|
6360
|
-
contentHashes = rebuildContentHashes(stateDir);
|
|
6473
|
+
if (Object.keys(contentHashes).length === 0 && fs13.existsSync(learningsPath)) {
|
|
6474
|
+
contentHashes = rebuildContentHashes(stateDir, LEARNINGS_FILE);
|
|
6361
6475
|
}
|
|
6362
|
-
} else if (
|
|
6363
|
-
contentHashes = rebuildContentHashes(stateDir);
|
|
6476
|
+
} else if (fs13.existsSync(learningsPath)) {
|
|
6477
|
+
contentHashes = rebuildContentHashes(stateDir, LEARNINGS_FILE);
|
|
6364
6478
|
} else {
|
|
6365
6479
|
contentHashes = {};
|
|
6366
6480
|
}
|
|
@@ -6379,7 +6493,7 @@ async function appendLearning(projectPath, learning, skillName, outcome, stream,
|
|
|
6379
6493
|
} else {
|
|
6380
6494
|
bulletLine = `- **${timestamp}:** ${learning}`;
|
|
6381
6495
|
}
|
|
6382
|
-
const hash =
|
|
6496
|
+
const hash = crypto2.createHash("sha256").update(bulletLine).digest("hex").slice(0, 8);
|
|
6383
6497
|
const tagsStr = fmTags.length > 0 ? ` tags:${fmTags.join(",")}` : "";
|
|
6384
6498
|
const frontmatter = `<!-- hash:${hash}${tagsStr} -->`;
|
|
6385
6499
|
const entry = `
|
|
@@ -6387,19 +6501,19 @@ ${frontmatter}
|
|
|
6387
6501
|
${bulletLine}
|
|
6388
6502
|
`;
|
|
6389
6503
|
let existingLineCount;
|
|
6390
|
-
if (!
|
|
6391
|
-
|
|
6504
|
+
if (!fs13.existsSync(learningsPath)) {
|
|
6505
|
+
fs13.writeFileSync(learningsPath, `# Learnings
|
|
6392
6506
|
${entry}`);
|
|
6393
6507
|
existingLineCount = 1;
|
|
6394
6508
|
} else {
|
|
6395
|
-
const existingContent =
|
|
6509
|
+
const existingContent = fs13.readFileSync(learningsPath, "utf-8");
|
|
6396
6510
|
existingLineCount = existingContent.split("\n").length;
|
|
6397
|
-
|
|
6511
|
+
fs13.appendFileSync(learningsPath, entry);
|
|
6398
6512
|
}
|
|
6399
6513
|
const bulletLine_lineNum = existingLineCount + 2;
|
|
6400
6514
|
contentHashes[contentHash] = { date: timestamp ?? "", line: bulletLine_lineNum };
|
|
6401
6515
|
saveContentHashes(stateDir, contentHashes);
|
|
6402
|
-
|
|
6516
|
+
invalidateLearningsCacheEntry(learningsPath);
|
|
6403
6517
|
return Ok(void 0);
|
|
6404
6518
|
} catch (error) {
|
|
6405
6519
|
return Err(
|
|
@@ -6409,42 +6523,6 @@ ${entry}`);
|
|
|
6409
6523
|
);
|
|
6410
6524
|
}
|
|
6411
6525
|
}
|
|
6412
|
-
function estimateTokens(text) {
|
|
6413
|
-
return Math.ceil(text.length / 4);
|
|
6414
|
-
}
|
|
6415
|
-
function scoreRelevance(entry, intent) {
|
|
6416
|
-
if (!intent || intent.trim() === "") return 0;
|
|
6417
|
-
const intentWords = intent.toLowerCase().split(/\s+/).filter((w) => w.length > 2);
|
|
6418
|
-
if (intentWords.length === 0) return 0;
|
|
6419
|
-
const entryLower = entry.toLowerCase();
|
|
6420
|
-
const matches = intentWords.filter((word) => entryLower.includes(word));
|
|
6421
|
-
return matches.length / intentWords.length;
|
|
6422
|
-
}
|
|
6423
|
-
function parseDateFromEntry(entry) {
|
|
6424
|
-
const match = entry.match(/(\d{4}-\d{2}-\d{2})/);
|
|
6425
|
-
return match ? match[1] ?? null : null;
|
|
6426
|
-
}
|
|
6427
|
-
function analyzeLearningPatterns(entries) {
|
|
6428
|
-
const tagGroups = /* @__PURE__ */ new Map();
|
|
6429
|
-
for (const entry of entries) {
|
|
6430
|
-
const tagMatches = entry.matchAll(/\[(skill:[^\]]+)\]|\[(outcome:[^\]]+)\]/g);
|
|
6431
|
-
for (const match of tagMatches) {
|
|
6432
|
-
const tag = match[1] ?? match[2];
|
|
6433
|
-
if (tag) {
|
|
6434
|
-
const group = tagGroups.get(tag) ?? [];
|
|
6435
|
-
group.push(entry);
|
|
6436
|
-
tagGroups.set(tag, group);
|
|
6437
|
-
}
|
|
6438
|
-
}
|
|
6439
|
-
}
|
|
6440
|
-
const patterns = [];
|
|
6441
|
-
for (const [tag, groupEntries] of tagGroups) {
|
|
6442
|
-
if (groupEntries.length >= 3) {
|
|
6443
|
-
patterns.push({ tag, count: groupEntries.length, entries: groupEntries });
|
|
6444
|
-
}
|
|
6445
|
-
}
|
|
6446
|
-
return patterns.sort((a, b) => b.count - a.count);
|
|
6447
|
-
}
|
|
6448
6526
|
async function loadBudgetedLearnings(projectPath, options) {
|
|
6449
6527
|
const { intent, tokenBudget = 1e3, skill, session, stream, depth = "summary" } = options;
|
|
6450
6528
|
if (depth === "index") {
|
|
@@ -6508,11 +6586,11 @@ async function loadIndexEntries(projectPath, skillName, stream, session) {
|
|
|
6508
6586
|
const dirResult = await getStateDir(projectPath, stream, session);
|
|
6509
6587
|
if (!dirResult.ok) return dirResult;
|
|
6510
6588
|
const stateDir = dirResult.value;
|
|
6511
|
-
const learningsPath =
|
|
6512
|
-
if (!
|
|
6589
|
+
const learningsPath = path10.join(stateDir, LEARNINGS_FILE);
|
|
6590
|
+
if (!fs13.existsSync(learningsPath)) {
|
|
6513
6591
|
return Ok([]);
|
|
6514
6592
|
}
|
|
6515
|
-
const content =
|
|
6593
|
+
const content = fs13.readFileSync(learningsPath, "utf-8");
|
|
6516
6594
|
const lines = content.split("\n");
|
|
6517
6595
|
const indexEntries = [];
|
|
6518
6596
|
let pendingFrontmatter = null;
|
|
@@ -6565,74 +6643,25 @@ async function loadIndexEntries(projectPath, skillName, stream, session) {
|
|
|
6565
6643
|
);
|
|
6566
6644
|
}
|
|
6567
6645
|
}
|
|
6568
|
-
|
|
6569
|
-
|
|
6570
|
-
|
|
6571
|
-
|
|
6572
|
-
const stateDir = dirResult.value;
|
|
6573
|
-
const learningsPath = path8.join(stateDir, LEARNINGS_FILE);
|
|
6574
|
-
if (!fs11.existsSync(learningsPath)) {
|
|
6575
|
-
return Ok([]);
|
|
6576
|
-
}
|
|
6577
|
-
const stats = fs11.statSync(learningsPath);
|
|
6578
|
-
const cacheKey = learningsPath;
|
|
6579
|
-
const cached = learningsCacheMap.get(cacheKey);
|
|
6580
|
-
let entries;
|
|
6581
|
-
if (cached && cached.mtimeMs === stats.mtimeMs) {
|
|
6582
|
-
entries = cached.entries;
|
|
6583
|
-
} else {
|
|
6584
|
-
const content = fs11.readFileSync(learningsPath, "utf-8");
|
|
6585
|
-
const lines = content.split("\n");
|
|
6586
|
-
entries = [];
|
|
6587
|
-
let currentBlock = [];
|
|
6588
|
-
for (const line of lines) {
|
|
6589
|
-
if (line.startsWith("# ")) continue;
|
|
6590
|
-
if (/^<!--\s+hash:[a-f0-9]+/.test(line)) continue;
|
|
6591
|
-
const isDatedBullet = /^- \*\*\d{4}-\d{2}-\d{2}/.test(line);
|
|
6592
|
-
const isHeading = /^## \d{4}-\d{2}-\d{2}/.test(line);
|
|
6593
|
-
if (isDatedBullet || isHeading) {
|
|
6594
|
-
if (currentBlock.length > 0) {
|
|
6595
|
-
entries.push(currentBlock.join("\n"));
|
|
6596
|
-
}
|
|
6597
|
-
currentBlock = [line];
|
|
6598
|
-
} else if (line.trim() !== "" && currentBlock.length > 0) {
|
|
6599
|
-
currentBlock.push(line);
|
|
6600
|
-
}
|
|
6601
|
-
}
|
|
6602
|
-
if (currentBlock.length > 0) {
|
|
6603
|
-
entries.push(currentBlock.join("\n"));
|
|
6604
|
-
}
|
|
6605
|
-
learningsCacheMap.set(cacheKey, { mtimeMs: stats.mtimeMs, entries });
|
|
6606
|
-
evictIfNeeded(learningsCacheMap);
|
|
6607
|
-
}
|
|
6608
|
-
if (!skillName) {
|
|
6609
|
-
return Ok(entries);
|
|
6610
|
-
}
|
|
6611
|
-
const filtered = entries.filter((entry) => entry.includes(`[skill:${skillName}]`));
|
|
6612
|
-
return Ok(filtered);
|
|
6613
|
-
} catch (error) {
|
|
6614
|
-
return Err(
|
|
6615
|
-
new Error(
|
|
6616
|
-
`Failed to load learnings: ${error instanceof Error ? error.message : String(error)}`
|
|
6617
|
-
)
|
|
6618
|
-
);
|
|
6619
|
-
}
|
|
6620
|
-
}
|
|
6646
|
+
|
|
6647
|
+
// src/state/learnings-lifecycle.ts
|
|
6648
|
+
import * as fs14 from "fs";
|
|
6649
|
+
import * as path11 from "path";
|
|
6621
6650
|
async function archiveLearnings(projectPath, entries, stream) {
|
|
6622
6651
|
try {
|
|
6623
6652
|
const dirResult = await getStateDir(projectPath, stream);
|
|
6624
6653
|
if (!dirResult.ok) return dirResult;
|
|
6625
6654
|
const stateDir = dirResult.value;
|
|
6626
|
-
const archiveDir =
|
|
6627
|
-
|
|
6655
|
+
const archiveDir = path11.join(stateDir, "learnings-archive");
|
|
6656
|
+
fs14.mkdirSync(archiveDir, { recursive: true });
|
|
6628
6657
|
const now = /* @__PURE__ */ new Date();
|
|
6629
6658
|
const yearMonth = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`;
|
|
6630
|
-
const archivePath =
|
|
6659
|
+
const archivePath = path11.join(archiveDir, `${yearMonth}.md`);
|
|
6631
6660
|
const archiveContent = entries.join("\n\n") + "\n";
|
|
6632
|
-
if (
|
|
6633
|
-
|
|
6661
|
+
if (fs14.existsSync(archivePath)) {
|
|
6662
|
+
fs14.appendFileSync(archivePath, "\n" + archiveContent);
|
|
6634
6663
|
} else {
|
|
6635
|
-
|
|
6664
|
+
fs14.writeFileSync(archivePath, `# Learnings Archive
|
|
6636
6665
|
|
|
6637
6666
|
${archiveContent}`);
|
|
6638
6667
|
}
|
|
@@ -6650,8 +6679,8 @@ async function pruneLearnings(projectPath, stream) {
|
|
|
6650
6679
|
const dirResult = await getStateDir(projectPath, stream);
|
|
6651
6680
|
if (!dirResult.ok) return dirResult;
|
|
6652
6681
|
const stateDir = dirResult.value;
|
|
6653
|
-
const learningsPath =
|
|
6654
|
-
if (!
|
|
6682
|
+
const learningsPath = path11.join(stateDir, LEARNINGS_FILE);
|
|
6683
|
+
if (!fs14.existsSync(learningsPath)) {
|
|
6655
6684
|
return Ok({ kept: 0, archived: 0, patterns: [] });
|
|
6656
6685
|
}
|
|
6657
6686
|
const loadResult = await loadRelevantLearnings(projectPath, void 0, stream);
|
|
@@ -6682,8 +6711,8 @@ async function pruneLearnings(projectPath, stream) {
|
|
|
6682
6711
|
if (!archiveResult.ok) return archiveResult;
|
|
6683
6712
|
}
|
|
6684
6713
|
const newContent = "# Learnings\n\n" + toKeep.join("\n\n") + "\n";
|
|
6685
|
-
|
|
6686
|
-
|
|
6714
|
+
fs14.writeFileSync(learningsPath, newContent);
|
|
6715
|
+
invalidateLearningsCacheEntry(learningsPath);
|
|
6687
6716
|
return Ok({
|
|
6688
6717
|
kept: toKeep.length,
|
|
6689
6718
|
archived: toArchive.length,
|
|
@@ -6727,21 +6756,21 @@ async function promoteSessionLearnings(projectPath, sessionSlug, stream) {
|
|
|
6727
6756
|
const dirResult = await getStateDir(projectPath, stream);
|
|
6728
6757
|
if (!dirResult.ok) return dirResult;
|
|
6729
6758
|
const stateDir = dirResult.value;
|
|
6730
|
-
const globalPath =
|
|
6731
|
-
const existingGlobal =
|
|
6759
|
+
const globalPath = path11.join(stateDir, LEARNINGS_FILE);
|
|
6760
|
+
const existingGlobal = fs14.existsSync(globalPath) ? fs14.readFileSync(globalPath, "utf-8") : "";
|
|
6732
6761
|
const newEntries = toPromote.filter((entry) => !existingGlobal.includes(entry.trim()));
|
|
6733
6762
|
if (newEntries.length === 0) {
|
|
6734
6763
|
return Ok({ promoted: 0, skipped: skipped + toPromote.length });
|
|
6735
6764
|
}
|
|
6736
6765
|
const promotedContent = newEntries.join("\n\n") + "\n";
|
|
6737
6766
|
if (!existingGlobal) {
|
|
6738
|
-
|
|
6767
|
+
fs14.writeFileSync(globalPath, `# Learnings
|
|
6739
6768
|
|
|
6740
6769
|
${promotedContent}`);
|
|
6741
6770
|
} else {
|
|
6742
|
-
|
|
6771
|
+
fs14.appendFileSync(globalPath, "\n\n" + promotedContent);
|
|
6743
6772
|
}
|
|
6744
|
-
|
|
6773
|
+
invalidateLearningsCacheEntry(globalPath);
|
|
6745
6774
|
return Ok({
|
|
6746
6775
|
promoted: newEntries.length,
|
|
6747
6776
|
skipped: skipped + (toPromote.length - newEntries.length)
|
|
@@ -6761,8 +6790,8 @@ async function countLearningEntries(projectPath, stream) {
|
|
|
6761
6790
|
}
|
|
6762
6791
|
|
|
6763
6792
|
// src/state/failures.ts
|
|
6764
|
-
import * as
|
|
6765
|
-
import * as
|
|
6793
|
+
import * as fs15 from "fs";
|
|
6794
|
+
import * as path12 from "path";
|
|
6766
6795
|
var failuresCacheMap = /* @__PURE__ */ new Map();
|
|
6767
6796
|
function clearFailuresCache() {
|
|
6768
6797
|
failuresCacheMap.clear();
|
|
@@ -6773,17 +6802,17 @@ async function appendFailure(projectPath, description, skillName, type, stream,
|
|
|
6773
6802
|
const dirResult = await getStateDir(projectPath, stream, session);
|
|
6774
6803
|
if (!dirResult.ok) return dirResult;
|
|
6775
6804
|
const stateDir = dirResult.value;
|
|
6776
|
-
const failuresPath =
|
|
6777
|
-
|
|
6805
|
+
const failuresPath = path12.join(stateDir, FAILURES_FILE);
|
|
6806
|
+
fs15.mkdirSync(stateDir, { recursive: true });
|
|
6778
6807
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
6779
6808
|
const entry = `
|
|
6780
6809
|
- **${timestamp} [skill:${skillName}] [type:${type}]:** ${description}
|
|
6781
6810
|
`;
|
|
6782
|
-
if (!
|
|
6783
|
-
|
|
6811
|
+
if (!fs15.existsSync(failuresPath)) {
|
|
6812
|
+
fs15.writeFileSync(failuresPath, `# Failures
|
|
6784
6813
|
${entry}`);
|
|
6785
6814
|
} else {
|
|
6786
|
-
|
|
6815
|
+
fs15.appendFileSync(failuresPath, entry);
|
|
6787
6816
|
}
|
|
6788
6817
|
failuresCacheMap.delete(failuresPath);
|
|
6789
6818
|
return Ok(void 0);
|
|
@@ -6800,17 +6829,17 @@ async function loadFailures(projectPath, stream, session) {
|
|
|
6800
6829
|
const dirResult = await getStateDir(projectPath, stream, session);
|
|
6801
6830
|
if (!dirResult.ok) return dirResult;
|
|
6802
6831
|
const stateDir = dirResult.value;
|
|
6803
|
-
const failuresPath =
|
|
6804
|
-
if (!
|
|
6832
|
+
const failuresPath = path12.join(stateDir, FAILURES_FILE);
|
|
6833
|
+
if (!fs15.existsSync(failuresPath)) {
|
|
6805
6834
|
return Ok([]);
|
|
6806
6835
|
}
|
|
6807
|
-
const stats =
|
|
6836
|
+
const stats = fs15.statSync(failuresPath);
|
|
6808
6837
|
const cacheKey = failuresPath;
|
|
6809
6838
|
const cached = failuresCacheMap.get(cacheKey);
|
|
6810
6839
|
if (cached && cached.mtimeMs === stats.mtimeMs) {
|
|
6811
6840
|
return Ok(cached.entries);
|
|
6812
6841
|
}
|
|
6813
|
-
const content =
|
|
6842
|
+
const content = fs15.readFileSync(failuresPath, "utf-8");
|
|
6814
6843
|
const entries = [];
|
|
6815
6844
|
for (const line of content.split("\n")) {
|
|
6816
6845
|
const match = line.match(FAILURE_LINE_REGEX);
|
|
@@ -6839,20 +6868,20 @@ async function archiveFailures(projectPath, stream, session) {
|
|
|
6839
6868
|
const dirResult = await getStateDir(projectPath, stream, session);
|
|
6840
6869
|
if (!dirResult.ok) return dirResult;
|
|
6841
6870
|
const stateDir = dirResult.value;
|
|
6842
|
-
const failuresPath =
|
|
6843
|
-
if (!
|
|
6871
|
+
const failuresPath = path12.join(stateDir, FAILURES_FILE);
|
|
6872
|
+
if (!fs15.existsSync(failuresPath)) {
|
|
6844
6873
|
return Ok(void 0);
|
|
6845
6874
|
}
|
|
6846
|
-
const archiveDir =
|
|
6847
|
-
|
|
6875
|
+
const archiveDir = path12.join(stateDir, "archive");
|
|
6876
|
+
fs15.mkdirSync(archiveDir, { recursive: true });
|
|
6848
6877
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
6849
6878
|
let archiveName = `failures-${date}.md`;
|
|
6850
6879
|
let counter = 2;
|
|
6851
|
-
while (
|
|
6880
|
+
while (fs15.existsSync(path12.join(archiveDir, archiveName))) {
|
|
6852
6881
|
archiveName = `failures-${date}-${counter}.md`;
|
|
6853
6882
|
counter++;
|
|
6854
6883
|
}
|
|
6855
|
-
|
|
6884
|
+
fs15.renameSync(failuresPath, path12.join(archiveDir, archiveName));
|
|
6856
6885
|
failuresCacheMap.delete(failuresPath);
|
|
6857
6886
|
return Ok(void 0);
|
|
6858
6887
|
} catch (error) {
|
|
@@ -6865,16 +6894,16 @@ async function archiveFailures(projectPath, stream, session) {
|
|
|
6865
6894
|
}
|
|
6866
6895
|
|
|
6867
6896
|
// src/state/handoff.ts
|
|
6868
|
-
import * as
|
|
6869
|
-
import * as
|
|
6897
|
+
import * as fs16 from "fs";
|
|
6898
|
+
import * as path13 from "path";
|
|
6870
6899
|
async function saveHandoff(projectPath, handoff, stream, session) {
|
|
6871
6900
|
try {
|
|
6872
6901
|
const dirResult = await getStateDir(projectPath, stream, session);
|
|
6873
6902
|
if (!dirResult.ok) return dirResult;
|
|
6874
6903
|
const stateDir = dirResult.value;
|
|
6875
|
-
const handoffPath =
|
|
6876
|
-
|
|
6877
|
-
|
|
6904
|
+
const handoffPath = path13.join(stateDir, HANDOFF_FILE);
|
|
6905
|
+
fs16.mkdirSync(stateDir, { recursive: true });
|
|
6906
|
+
fs16.writeFileSync(handoffPath, JSON.stringify(handoff, null, 2));
|
|
6878
6907
|
return Ok(void 0);
|
|
6879
6908
|
} catch (error) {
|
|
6880
6909
|
return Err(
|
|
@@ -6887,11 +6916,11 @@ async function loadHandoff(projectPath, stream, session) {
|
|
|
6887
6916
|
const dirResult = await getStateDir(projectPath, stream, session);
|
|
6888
6917
|
if (!dirResult.ok) return dirResult;
|
|
6889
6918
|
const stateDir = dirResult.value;
|
|
6890
|
-
const handoffPath =
|
|
6891
|
-
if (!
|
|
6919
|
+
const handoffPath = path13.join(stateDir, HANDOFF_FILE);
|
|
6920
|
+
if (!fs16.existsSync(handoffPath)) {
|
|
6892
6921
|
return Ok(null);
|
|
6893
6922
|
}
|
|
6894
|
-
const raw =
|
|
6923
|
+
const raw = fs16.readFileSync(handoffPath, "utf-8");
|
|
6895
6924
|
const parsed = JSON.parse(raw);
|
|
6896
6925
|
const result = HandoffSchema.safeParse(parsed);
|
|
6897
6926
|
if (!result.success) {
|
|
@@ -6906,33 +6935,33 @@ async function loadHandoff(projectPath, stream, session) {
|
|
|
6906
6935
|
}
|
|
6907
6936
|
|
|
6908
6937
|
// src/state/mechanical-gate.ts
|
|
6909
|
-
import * as
|
|
6910
|
-
import * as
|
|
6938
|
+
import * as fs17 from "fs";
|
|
6939
|
+
import * as path14 from "path";
|
|
6911
6940
|
import { execSync as execSync2 } from "child_process";
|
|
6912
6941
|
var SAFE_GATE_COMMAND = /^(?:npm|pnpm|yarn)\s+(?:test|run\s+[\w.-]+|run-script\s+[\w.-]+)$|^go\s+(?:test|build|vet|fmt)\s+[\w./ -]+$|^(?:python|python3)\s+-m\s+[\w.-]+$|^make\s+[\w.-]+$|^cargo\s+(?:test|build|check|clippy)(?:\s+[\w./ -]+)?$|^(?:gradle|mvn)\s+[\w:.-]+$/;
|
|
6913
6942
|
function loadChecksFromConfig(gateConfigPath) {
|
|
6914
|
-
if (!
|
|
6915
|
-
const raw = JSON.parse(
|
|
6943
|
+
if (!fs17.existsSync(gateConfigPath)) return [];
|
|
6944
|
+
const raw = JSON.parse(fs17.readFileSync(gateConfigPath, "utf-8"));
|
|
6916
6945
|
const config = GateConfigSchema.safeParse(raw);
|
|
6917
6946
|
if (config.success && config.data.checks) return config.data.checks;
|
|
6918
6947
|
return [];
|
|
6919
6948
|
}
|
|
6920
6949
|
function discoverChecksFromProject(projectPath) {
|
|
6921
6950
|
const checks = [];
|
|
6922
|
-
const packageJsonPath =
|
|
6923
|
-
if (
|
|
6924
|
-
const pkg = JSON.parse(
|
|
6951
|
+
const packageJsonPath = path14.join(projectPath, "package.json");
|
|
6952
|
+
if (fs17.existsSync(packageJsonPath)) {
|
|
6953
|
+
const pkg = JSON.parse(fs17.readFileSync(packageJsonPath, "utf-8"));
|
|
6925
6954
|
const scripts = pkg.scripts || {};
|
|
6926
6955
|
if (scripts.test) checks.push({ name: "test", command: "npm test" });
|
|
6927
6956
|
if (scripts.lint) checks.push({ name: "lint", command: "npm run lint" });
|
|
6928
6957
|
if (scripts.typecheck) checks.push({ name: "typecheck", command: "npm run typecheck" });
|
|
6929
6958
|
if (scripts.build) checks.push({ name: "build", command: "npm run build" });
|
|
6930
6959
|
}
|
|
6931
|
-
if (
|
|
6960
|
+
if (fs17.existsSync(path14.join(projectPath, "go.mod"))) {
|
|
6932
6961
|
checks.push({ name: "test", command: "go test ./..." });
|
|
6933
6962
|
checks.push({ name: "build", command: "go build ./..." });
|
|
6934
6963
|
}
|
|
6935
|
-
if (
|
|
6964
|
+
if (fs17.existsSync(path14.join(projectPath, "pyproject.toml")) || fs17.existsSync(path14.join(projectPath, "setup.py"))) {
|
|
6936
6965
|
checks.push({ name: "test", command: "python -m pytest" });
|
|
6937
6966
|
}
|
|
6938
6967
|
return checks;
|
|
@@ -6972,8 +7001,8 @@ function executeCheck(check, projectPath) {
|
|
|
6972
7001
|
}
|
|
6973
7002
|
}
|
|
6974
7003
|
async function runMechanicalGate(projectPath) {
|
|
6975
|
-
const harnessDir =
|
|
6976
|
-
const gateConfigPath =
|
|
7004
|
+
const harnessDir = path14.join(projectPath, HARNESS_DIR);
|
|
7005
|
+
const gateConfigPath = path14.join(harnessDir, GATE_CONFIG_FILE);
|
|
6977
7006
|
try {
|
|
6978
7007
|
let checks = loadChecksFromConfig(gateConfigPath);
|
|
6979
7008
|
if (checks.length === 0) {
|
|
@@ -6994,8 +7023,8 @@ async function runMechanicalGate(projectPath) {
|
|
|
6994
7023
|
}
|
|
6995
7024
|
|
|
6996
7025
|
// src/state/session-summary.ts
|
|
6997
|
-
import * as
|
|
6998
|
-
import * as
|
|
7026
|
+
import * as fs18 from "fs";
|
|
7027
|
+
import * as path15 from "path";
|
|
6999
7028
|
function formatSummary(data) {
|
|
7000
7029
|
const lines = [
|
|
7001
7030
|
"## Session Summary",
|
|
@@ -7033,9 +7062,9 @@ function writeSessionSummary(projectPath, sessionSlug, data) {
|
|
|
7033
7062
|
const dirResult = resolveSessionDir(projectPath, sessionSlug, { create: true });
|
|
7034
7063
|
if (!dirResult.ok) return dirResult;
|
|
7035
7064
|
const sessionDir = dirResult.value;
|
|
7036
|
-
const summaryPath =
|
|
7065
|
+
const summaryPath = path15.join(sessionDir, SUMMARY_FILE);
|
|
7037
7066
|
const content = formatSummary(data);
|
|
7038
|
-
|
|
7067
|
+
fs18.writeFileSync(summaryPath, content);
|
|
7039
7068
|
const description = deriveIndexDescription(data);
|
|
7040
7069
|
updateSessionIndex(projectPath, sessionSlug, description);
|
|
7041
7070
|
return Ok(void 0);
|
|
@@ -7052,11 +7081,11 @@ function loadSessionSummary(projectPath, sessionSlug) {
|
|
|
7052
7081
|
const dirResult = resolveSessionDir(projectPath, sessionSlug);
|
|
7053
7082
|
if (!dirResult.ok) return dirResult;
|
|
7054
7083
|
const sessionDir = dirResult.value;
|
|
7055
|
-
const summaryPath =
|
|
7056
|
-
if (!
|
|
7084
|
+
const summaryPath = path15.join(sessionDir, SUMMARY_FILE);
|
|
7085
|
+
if (!fs18.existsSync(summaryPath)) {
|
|
7057
7086
|
return Ok(null);
|
|
7058
7087
|
}
|
|
7059
|
-
const content =
|
|
7088
|
+
const content = fs18.readFileSync(summaryPath, "utf-8");
|
|
7060
7089
|
return Ok(content);
|
|
7061
7090
|
} catch (error) {
|
|
7062
7091
|
return Err(
|
|
@@ -7068,11 +7097,11 @@ function loadSessionSummary(projectPath, sessionSlug) {
|
|
|
7068
7097
|
}
|
|
7069
7098
|
function listActiveSessions(projectPath) {
|
|
7070
7099
|
try {
|
|
7071
|
-
const indexPath2 =
|
|
7072
|
-
if (!
|
|
7100
|
+
const indexPath2 = path15.join(projectPath, HARNESS_DIR, SESSIONS_DIR, SESSION_INDEX_FILE);
|
|
7101
|
+
if (!fs18.existsSync(indexPath2)) {
|
|
7073
7102
|
return Ok(null);
|
|
7074
7103
|
}
|
|
7075
|
-
const content =
|
|
7104
|
+
const content = fs18.readFileSync(indexPath2, "utf-8");
|
|
7076
7105
|
return Ok(content);
|
|
7077
7106
|
} catch (error) {
|
|
7078
7107
|
return Err(
|
|
@@ -7084,8 +7113,8 @@ function listActiveSessions(projectPath) {
|
|
|
7084
7113
|
}
|
|
7085
7114
|
|
|
7086
7115
|
// src/state/session-sections.ts
|
|
7087
|
-
import * as
|
|
7088
|
-
import * as
|
|
7116
|
+
import * as fs19 from "fs";
|
|
7117
|
+
import * as path16 from "path";
|
|
7089
7118
|
import { SESSION_SECTION_NAMES } from "@harness-engineering/types";
|
|
7090
7119
|
function emptySections() {
|
|
7091
7120
|
const sections = {};
|
|
@@ -7098,12 +7127,12 @@ async function loadSessionState(projectPath, sessionSlug) {
|
|
|
7098
7127
|
const dirResult = resolveSessionDir(projectPath, sessionSlug);
|
|
7099
7128
|
if (!dirResult.ok) return dirResult;
|
|
7100
7129
|
const sessionDir = dirResult.value;
|
|
7101
|
-
const filePath =
|
|
7102
|
-
if (!
|
|
7130
|
+
const filePath = path16.join(sessionDir, SESSION_STATE_FILE);
|
|
7131
|
+
if (!fs19.existsSync(filePath)) {
|
|
7103
7132
|
return Ok(emptySections());
|
|
7104
7133
|
}
|
|
7105
7134
|
try {
|
|
7106
|
-
const raw =
|
|
7135
|
+
const raw = fs19.readFileSync(filePath, "utf-8");
|
|
7107
7136
|
const parsed = JSON.parse(raw);
|
|
7108
7137
|
const sections = emptySections();
|
|
7109
7138
|
for (const name of SESSION_SECTION_NAMES) {
|
|
@@ -7124,9 +7153,9 @@ async function saveSessionState(projectPath, sessionSlug, sections) {
|
|
|
7124
7153
|
const dirResult = resolveSessionDir(projectPath, sessionSlug, { create: true });
|
|
7125
7154
|
if (!dirResult.ok) return dirResult;
|
|
7126
7155
|
const sessionDir = dirResult.value;
|
|
7127
|
-
const filePath =
|
|
7156
|
+
const filePath = path16.join(sessionDir, SESSION_STATE_FILE);
|
|
7128
7157
|
try {
|
|
7129
|
-
|
|
7158
|
+
fs19.writeFileSync(filePath, JSON.stringify(sections, null, 2));
|
|
7130
7159
|
return Ok(void 0);
|
|
7131
7160
|
} catch (error) {
|
|
7132
7161
|
return Err(
|
|
@@ -7180,32 +7209,32 @@ function generateEntryId() {
|
|
|
7180
7209
|
}
|
|
7181
7210
|
|
|
7182
7211
|
// src/state/session-archive.ts
|
|
7183
|
-
import * as
|
|
7184
|
-
import * as
|
|
7212
|
+
import * as fs20 from "fs";
|
|
7213
|
+
import * as path17 from "path";
|
|
7185
7214
|
async function archiveSession(projectPath, sessionSlug) {
|
|
7186
7215
|
const dirResult = resolveSessionDir(projectPath, sessionSlug);
|
|
7187
7216
|
if (!dirResult.ok) return dirResult;
|
|
7188
7217
|
const sessionDir = dirResult.value;
|
|
7189
|
-
if (!
|
|
7218
|
+
if (!fs20.existsSync(sessionDir)) {
|
|
7190
7219
|
return Err(new Error(`Session '${sessionSlug}' not found at ${sessionDir}`));
|
|
7191
7220
|
}
|
|
7192
|
-
const archiveBase =
|
|
7221
|
+
const archiveBase = path17.join(projectPath, HARNESS_DIR, ARCHIVE_DIR, "sessions");
|
|
7193
7222
|
try {
|
|
7194
|
-
|
|
7223
|
+
fs20.mkdirSync(archiveBase, { recursive: true });
|
|
7195
7224
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
7196
7225
|
let archiveName = `${sessionSlug}-${date}`;
|
|
7197
7226
|
let counter = 1;
|
|
7198
|
-
while (
|
|
7227
|
+
while (fs20.existsSync(path17.join(archiveBase, archiveName))) {
|
|
7199
7228
|
archiveName = `${sessionSlug}-${date}-${counter}`;
|
|
7200
7229
|
counter++;
|
|
7201
7230
|
}
|
|
7202
|
-
const dest =
|
|
7231
|
+
const dest = path17.join(archiveBase, archiveName);
|
|
7203
7232
|
try {
|
|
7204
|
-
|
|
7233
|
+
fs20.renameSync(sessionDir, dest);
|
|
7205
7234
|
} catch (renameErr) {
|
|
7206
7235
|
if (renameErr instanceof Error && "code" in renameErr && renameErr.code === "EXDEV") {
|
|
7207
|
-
|
|
7208
|
-
|
|
7236
|
+
fs20.cpSync(sessionDir, dest, { recursive: true });
|
|
7237
|
+
fs20.rmSync(sessionDir, { recursive: true });
|
|
7209
7238
|
} else {
|
|
7210
7239
|
throw renameErr;
|
|
7211
7240
|
}
|
|
@@ -7221,8 +7250,8 @@ async function archiveSession(projectPath, sessionSlug) {
|
|
|
7221
7250
|
}
|
|
7222
7251
|
|
|
7223
7252
|
// src/state/events.ts
|
|
7224
|
-
import * as
|
|
7225
|
-
import * as
|
|
7253
|
+
import * as fs21 from "fs";
|
|
7254
|
+
import * as path18 from "path";
|
|
7226
7255
|
import { z as z7 } from "zod";
|
|
7227
7256
|
var SkillEventSchema = z7.object({
|
|
7228
7257
|
timestamp: z7.string(),
|
|
@@ -7243,8 +7272,8 @@ function loadKnownHashes(eventsPath) {
|
|
|
7243
7272
|
const cached = knownHashesCache.get(eventsPath);
|
|
7244
7273
|
if (cached) return cached;
|
|
7245
7274
|
const hashes = /* @__PURE__ */ new Set();
|
|
7246
|
-
if (
|
|
7247
|
-
const content =
|
|
7275
|
+
if (fs21.existsSync(eventsPath)) {
|
|
7276
|
+
const content = fs21.readFileSync(eventsPath, "utf-8");
|
|
7248
7277
|
const lines = content.split("\n").filter((line) => line.trim() !== "");
|
|
7249
7278
|
for (const line of lines) {
|
|
7250
7279
|
try {
|
|
@@ -7267,8 +7296,8 @@ async function emitEvent(projectPath, event, options) {
|
|
|
7267
7296
|
const dirResult = await getStateDir(projectPath, options?.stream, options?.session);
|
|
7268
7297
|
if (!dirResult.ok) return dirResult;
|
|
7269
7298
|
const stateDir = dirResult.value;
|
|
7270
|
-
const eventsPath =
|
|
7271
|
-
|
|
7299
|
+
const eventsPath = path18.join(stateDir, EVENTS_FILE);
|
|
7300
|
+
fs21.mkdirSync(stateDir, { recursive: true });
|
|
7272
7301
|
const contentHash = computeEventHash(event, options?.session);
|
|
7273
7302
|
const knownHashes = loadKnownHashes(eventsPath);
|
|
7274
7303
|
if (knownHashes.has(contentHash)) {
|
|
@@ -7282,7 +7311,7 @@ async function emitEvent(projectPath, event, options) {
|
|
|
7282
7311
|
if (options?.session) {
|
|
7283
7312
|
fullEvent.session = options.session;
|
|
7284
7313
|
}
|
|
7285
|
-
|
|
7314
|
+
fs21.appendFileSync(eventsPath, JSON.stringify(fullEvent) + "\n");
|
|
7286
7315
|
knownHashes.add(contentHash);
|
|
7287
7316
|
return Ok({ written: true });
|
|
7288
7317
|
} catch (error) {
|
|
@@ -7296,11 +7325,11 @@ async function loadEvents(projectPath, options) {
|
|
|
7296
7325
|
const dirResult = await getStateDir(projectPath, options?.stream, options?.session);
|
|
7297
7326
|
if (!dirResult.ok) return dirResult;
|
|
7298
7327
|
const stateDir = dirResult.value;
|
|
7299
|
-
const eventsPath =
|
|
7300
|
-
if (!
|
|
7328
|
+
const eventsPath = path18.join(stateDir, EVENTS_FILE);
|
|
7329
|
+
if (!fs21.existsSync(eventsPath)) {
|
|
7301
7330
|
return Ok([]);
|
|
7302
7331
|
}
|
|
7303
|
-
const content =
|
|
7332
|
+
const content = fs21.readFileSync(eventsPath, "utf-8");
|
|
7304
7333
|
const lines = content.split("\n").filter((line) => line.trim() !== "");
|
|
7305
7334
|
const events = [];
|
|
7306
7335
|
for (const line of lines) {
|
|
@@ -7514,7 +7543,7 @@ async function runMultiTurnPipeline(initialContext, turnExecutor, options) {
|
|
|
7514
7543
|
}
|
|
7515
7544
|
|
|
7516
7545
|
// src/security/scanner.ts
|
|
7517
|
-
import * as
|
|
7546
|
+
import * as fs23 from "fs/promises";
|
|
7518
7547
|
import { minimatch as minimatch4 } from "minimatch";
|
|
7519
7548
|
|
|
7520
7549
|
// src/security/rules/registry.ts
|
|
@@ -7602,15 +7631,15 @@ function resolveRuleSeverity(ruleId, defaultSeverity, overrides, strict) {
|
|
|
7602
7631
|
}
|
|
7603
7632
|
|
|
7604
7633
|
// src/security/stack-detector.ts
|
|
7605
|
-
import * as
|
|
7606
|
-
import * as
|
|
7634
|
+
import * as fs22 from "fs";
|
|
7635
|
+
import * as path19 from "path";
|
|
7607
7636
|
function detectStack(projectRoot) {
|
|
7608
7637
|
const stacks = [];
|
|
7609
|
-
const pkgJsonPath =
|
|
7610
|
-
if (
|
|
7638
|
+
const pkgJsonPath = path19.join(projectRoot, "package.json");
|
|
7639
|
+
if (fs22.existsSync(pkgJsonPath)) {
|
|
7611
7640
|
stacks.push("node");
|
|
7612
7641
|
try {
|
|
7613
|
-
const pkgJson = JSON.parse(
|
|
7642
|
+
const pkgJson = JSON.parse(fs22.readFileSync(pkgJsonPath, "utf-8"));
|
|
7614
7643
|
const allDeps = {
|
|
7615
7644
|
...pkgJson.dependencies,
|
|
7616
7645
|
...pkgJson.devDependencies
|
|
@@ -7625,13 +7654,13 @@ function detectStack(projectRoot) {
|
|
|
7625
7654
|
} catch {
|
|
7626
7655
|
}
|
|
7627
7656
|
}
|
|
7628
|
-
const goModPath =
|
|
7629
|
-
if (
|
|
7657
|
+
const goModPath = path19.join(projectRoot, "go.mod");
|
|
7658
|
+
if (fs22.existsSync(goModPath)) {
|
|
7630
7659
|
stacks.push("go");
|
|
7631
7660
|
}
|
|
7632
|
-
const requirementsPath =
|
|
7633
|
-
const pyprojectPath =
|
|
7634
|
-
if (
|
|
7661
|
+
const requirementsPath = path19.join(projectRoot, "requirements.txt");
|
|
7662
|
+
const pyprojectPath = path19.join(projectRoot, "pyproject.toml");
|
|
7663
|
+
if (fs22.existsSync(requirementsPath) || fs22.existsSync(pyprojectPath)) {
|
|
7635
7664
|
stacks.push("python");
|
|
7636
7665
|
}
|
|
7637
7666
|
return stacks;
|
|
@@ -8462,7 +8491,7 @@ var SecurityScanner = class {
|
|
|
8462
8491
|
}
|
|
8463
8492
|
async scanFile(filePath) {
|
|
8464
8493
|
if (!this.config.enabled) return [];
|
|
8465
|
-
const content = await
|
|
8494
|
+
const content = await fs23.readFile(filePath, "utf-8");
|
|
8466
8495
|
return this.scanContentForFile(content, filePath, 1);
|
|
8467
8496
|
}
|
|
8468
8497
|
scanContentForFile(content, filePath, startLine = 1) {
|
|
@@ -8789,19 +8818,19 @@ var DESTRUCTIVE_BASH = [
|
|
|
8789
8818
|
];
|
|
8790
8819
|
|
|
8791
8820
|
// src/security/taint.ts
|
|
8792
|
-
import { readFileSync as
|
|
8793
|
-
import { join as
|
|
8821
|
+
import { readFileSync as readFileSync20, writeFileSync as writeFileSync14, unlinkSync, mkdirSync as mkdirSync13, readdirSync as readdirSync3 } from "fs";
|
|
8822
|
+
import { join as join27, dirname as dirname9 } from "path";
|
|
8794
8823
|
var TAINT_DURATION_MS = 30 * 60 * 1e3;
|
|
8795
8824
|
var DEFAULT_SESSION_ID = "default";
|
|
8796
8825
|
function getTaintFilePath(projectRoot, sessionId) {
|
|
8797
8826
|
const id = sessionId || DEFAULT_SESSION_ID;
|
|
8798
|
-
return
|
|
8827
|
+
return join27(projectRoot, ".harness", `session-taint-${id}.json`);
|
|
8799
8828
|
}
|
|
8800
8829
|
function readTaint(projectRoot, sessionId) {
|
|
8801
8830
|
const filePath = getTaintFilePath(projectRoot, sessionId);
|
|
8802
8831
|
let content;
|
|
8803
8832
|
try {
|
|
8804
|
-
content =
|
|
8833
|
+
content = readFileSync20(filePath, "utf8");
|
|
8805
8834
|
} catch {
|
|
8806
8835
|
return null;
|
|
8807
8836
|
}
|
|
@@ -8846,7 +8875,7 @@ function writeTaint(projectRoot, sessionId, reason, findings, source) {
|
|
|
8846
8875
|
const filePath = getTaintFilePath(projectRoot, id);
|
|
8847
8876
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
8848
8877
|
const dir = dirname9(filePath);
|
|
8849
|
-
|
|
8878
|
+
mkdirSync13(dir, { recursive: true });
|
|
8850
8879
|
const existing = readTaint(projectRoot, id);
|
|
8851
8880
|
const maxSeverity = findings.some((f) => f.severity === "high") ? "high" : "medium";
|
|
8852
8881
|
const taintFindings = findings.map((f) => ({
|
|
@@ -8864,7 +8893,7 @@ function writeTaint(projectRoot, sessionId, reason, findings, source) {
|
|
|
8864
8893
|
severity: existing?.severity === "high" || maxSeverity === "high" ? "high" : "medium",
|
|
8865
8894
|
findings: [...existing?.findings || [], ...taintFindings]
|
|
8866
8895
|
};
|
|
8867
|
-
|
|
8896
|
+
writeFileSync14(filePath, JSON.stringify(state, null, 2) + "\n");
|
|
8868
8897
|
return state;
|
|
8869
8898
|
}
|
|
8870
8899
|
function clearTaint(projectRoot, sessionId) {
|
|
@@ -8877,14 +8906,14 @@ function clearTaint(projectRoot, sessionId) {
|
|
|
8877
8906
|
return 0;
|
|
8878
8907
|
}
|
|
8879
8908
|
}
|
|
8880
|
-
const harnessDir =
|
|
8909
|
+
const harnessDir = join27(projectRoot, ".harness");
|
|
8881
8910
|
let count = 0;
|
|
8882
8911
|
try {
|
|
8883
8912
|
const files = readdirSync3(harnessDir);
|
|
8884
8913
|
for (const file of files) {
|
|
8885
8914
|
if (file.startsWith("session-taint-") && file.endsWith(".json")) {
|
|
8886
8915
|
try {
|
|
8887
|
-
unlinkSync(
|
|
8916
|
+
unlinkSync(join27(harnessDir, file));
|
|
8888
8917
|
count++;
|
|
8889
8918
|
} catch {
|
|
8890
8919
|
}
|
|
@@ -8895,7 +8924,7 @@ function clearTaint(projectRoot, sessionId) {
|
|
|
8895
8924
|
return count;
|
|
8896
8925
|
}
|
|
8897
8926
|
function listTaintedSessions(projectRoot) {
|
|
8898
|
-
const harnessDir =
|
|
8927
|
+
const harnessDir = join27(projectRoot, ".harness");
|
|
8899
8928
|
const sessions = [];
|
|
8900
8929
|
try {
|
|
8901
8930
|
const files = readdirSync3(harnessDir);
|
|
@@ -8965,7 +8994,7 @@ function mapSecurityFindings(secFindings, existing) {
|
|
|
8965
8994
|
}
|
|
8966
8995
|
|
|
8967
8996
|
// src/ci/check-orchestrator.ts
|
|
8968
|
-
import * as
|
|
8997
|
+
import * as path20 from "path";
|
|
8969
8998
|
import { GraphStore, queryTraceability } from "@harness-engineering/graph";
|
|
8970
8999
|
var ALL_CHECKS = [
|
|
8971
9000
|
"validate",
|
|
@@ -8980,7 +9009,7 @@ var ALL_CHECKS = [
|
|
|
8980
9009
|
];
|
|
8981
9010
|
async function runValidateCheck(projectRoot, config) {
|
|
8982
9011
|
const issues = [];
|
|
8983
|
-
const agentsPath =
|
|
9012
|
+
const agentsPath = path20.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
|
|
8984
9013
|
const result = await validateAgentsMap(agentsPath);
|
|
8985
9014
|
if (!result.ok) {
|
|
8986
9015
|
issues.push({ severity: "error", message: result.error.message });
|
|
@@ -9037,7 +9066,7 @@ async function runDepsCheck(projectRoot, config) {
|
|
|
9037
9066
|
}
|
|
9038
9067
|
async function runDocsCheck(projectRoot, config) {
|
|
9039
9068
|
const issues = [];
|
|
9040
|
-
const docsDir =
|
|
9069
|
+
const docsDir = path20.join(projectRoot, config.docsDir ?? "docs");
|
|
9041
9070
|
const entropyConfig = config.entropy || {};
|
|
9042
9071
|
const result = await checkDocCoverage("project", {
|
|
9043
9072
|
docsDir,
|
|
@@ -9222,7 +9251,7 @@ async function runTraceabilityCheck(projectRoot, config) {
|
|
|
9222
9251
|
const issues = [];
|
|
9223
9252
|
const traceConfig = config.traceability || {};
|
|
9224
9253
|
if (traceConfig.enabled === false) return issues;
|
|
9225
|
-
const graphDir =
|
|
9254
|
+
const graphDir = path20.join(projectRoot, ".harness", "graph");
|
|
9226
9255
|
const store = new GraphStore();
|
|
9227
9256
|
const loaded = await store.load(graphDir);
|
|
9228
9257
|
if (!loaded) {
|
|
@@ -9351,7 +9380,7 @@ async function runCIChecks(input) {
|
|
|
9351
9380
|
}
|
|
9352
9381
|
|
|
9353
9382
|
// src/review/mechanical-checks.ts
|
|
9354
|
-
import * as
|
|
9383
|
+
import * as path21 from "path";
|
|
9355
9384
|
async function runMechanicalChecks(options) {
|
|
9356
9385
|
const { projectRoot, config, skip = [], changedFiles } = options;
|
|
9357
9386
|
const findings = [];
|
|
@@ -9363,7 +9392,7 @@ async function runMechanicalChecks(options) {
|
|
|
9363
9392
|
};
|
|
9364
9393
|
if (!skip.includes("validate")) {
|
|
9365
9394
|
try {
|
|
9366
|
-
const agentsPath =
|
|
9395
|
+
const agentsPath = path21.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
|
|
9367
9396
|
const result = await validateAgentsMap(agentsPath);
|
|
9368
9397
|
if (!result.ok) {
|
|
9369
9398
|
statuses.validate = "fail";
|
|
@@ -9400,7 +9429,7 @@ async function runMechanicalChecks(options) {
|
|
|
9400
9429
|
statuses.validate = "fail";
|
|
9401
9430
|
findings.push({
|
|
9402
9431
|
tool: "validate",
|
|
9403
|
-
file:
|
|
9432
|
+
file: path21.join(projectRoot, "AGENTS.md"),
|
|
9404
9433
|
message: err instanceof Error ? err.message : String(err),
|
|
9405
9434
|
severity: "error"
|
|
9406
9435
|
});
|
|
@@ -9464,7 +9493,7 @@ async function runMechanicalChecks(options) {
|
|
|
9464
9493
|
(async () => {
|
|
9465
9494
|
const localFindings = [];
|
|
9466
9495
|
try {
|
|
9467
|
-
const docsDir =
|
|
9496
|
+
const docsDir = path21.join(projectRoot, config.docsDir ?? "docs");
|
|
9468
9497
|
const result = await checkDocCoverage("project", { docsDir });
|
|
9469
9498
|
if (!result.ok) {
|
|
9470
9499
|
statuses["check-docs"] = "warn";
|
|
@@ -9491,7 +9520,7 @@ async function runMechanicalChecks(options) {
|
|
|
9491
9520
|
statuses["check-docs"] = "warn";
|
|
9492
9521
|
localFindings.push({
|
|
9493
9522
|
tool: "check-docs",
|
|
9494
|
-
file:
|
|
9523
|
+
file: path21.join(projectRoot, "docs"),
|
|
9495
9524
|
message: err instanceof Error ? err.message : String(err),
|
|
9496
9525
|
severity: "warning"
|
|
9497
9526
|
});
|
|
@@ -9639,7 +9668,7 @@ function detectChangeType(commitMessage, diff2) {
|
|
|
9639
9668
|
}
|
|
9640
9669
|
|
|
9641
9670
|
// src/review/context-scoper.ts
|
|
9642
|
-
import * as
|
|
9671
|
+
import * as path22 from "path";
|
|
9643
9672
|
var ALL_DOMAINS = ["compliance", "bug", "security", "architecture"];
|
|
9644
9673
|
var SECURITY_PATTERNS = /auth|crypto|password|secret|token|session|cookie|hash|encrypt|decrypt|sql|shell|exec|eval/i;
|
|
9645
9674
|
function computeContextBudget(diffLines) {
|
|
@@ -9647,18 +9676,18 @@ function computeContextBudget(diffLines) {
|
|
|
9647
9676
|
return diffLines;
|
|
9648
9677
|
}
|
|
9649
9678
|
function isWithinProject(absPath, projectRoot) {
|
|
9650
|
-
const resolvedRoot =
|
|
9651
|
-
const resolvedPath =
|
|
9652
|
-
return resolvedPath.startsWith(resolvedRoot) || resolvedPath ===
|
|
9679
|
+
const resolvedRoot = path22.resolve(projectRoot) + path22.sep;
|
|
9680
|
+
const resolvedPath = path22.resolve(absPath);
|
|
9681
|
+
return resolvedPath.startsWith(resolvedRoot) || resolvedPath === path22.resolve(projectRoot);
|
|
9653
9682
|
}
|
|
9654
9683
|
async function readContextFile(projectRoot, filePath, reason) {
|
|
9655
|
-
const absPath =
|
|
9684
|
+
const absPath = path22.isAbsolute(filePath) ? filePath : path22.join(projectRoot, filePath);
|
|
9656
9685
|
if (!isWithinProject(absPath, projectRoot)) return null;
|
|
9657
9686
|
const result = await readFileContent(absPath);
|
|
9658
9687
|
if (!result.ok) return null;
|
|
9659
9688
|
const content = result.value;
|
|
9660
9689
|
const lines = content.split("\n").length;
|
|
9661
|
-
const relPath =
|
|
9690
|
+
const relPath = path22.isAbsolute(filePath) ? relativePosix(projectRoot, filePath) : filePath;
|
|
9662
9691
|
return { path: relPath, content, reason, lines };
|
|
9663
9692
|
}
|
|
9664
9693
|
function extractImportSources(content) {
|
|
@@ -9673,18 +9702,18 @@ function extractImportSources(content) {
|
|
|
9673
9702
|
}
|
|
9674
9703
|
async function resolveImportPath(projectRoot, fromFile, importSource) {
|
|
9675
9704
|
if (!importSource.startsWith(".")) return null;
|
|
9676
|
-
const fromDir =
|
|
9677
|
-
const basePath =
|
|
9705
|
+
const fromDir = path22.dirname(path22.join(projectRoot, fromFile));
|
|
9706
|
+
const basePath = path22.resolve(fromDir, importSource);
|
|
9678
9707
|
if (!isWithinProject(basePath, projectRoot)) return null;
|
|
9679
9708
|
const relBase = relativePosix(projectRoot, basePath);
|
|
9680
9709
|
const candidates = [
|
|
9681
9710
|
relBase + ".ts",
|
|
9682
9711
|
relBase + ".tsx",
|
|
9683
9712
|
relBase + ".mts",
|
|
9684
|
-
|
|
9713
|
+
path22.join(relBase, "index.ts")
|
|
9685
9714
|
];
|
|
9686
9715
|
for (const candidate of candidates) {
|
|
9687
|
-
const absCandidate =
|
|
9716
|
+
const absCandidate = path22.join(projectRoot, candidate);
|
|
9688
9717
|
if (await fileExists(absCandidate)) {
|
|
9689
9718
|
return candidate;
|
|
9690
9719
|
}
|
|
@@ -9692,7 +9721,7 @@ async function resolveImportPath(projectRoot, fromFile, importSource) {
|
|
|
9692
9721
|
return null;
|
|
9693
9722
|
}
|
|
9694
9723
|
async function findTestFiles(projectRoot, sourceFile) {
|
|
9695
|
-
const baseName =
|
|
9724
|
+
const baseName = path22.basename(sourceFile, path22.extname(sourceFile));
|
|
9696
9725
|
const pattern = `**/${baseName}.{test,spec}.{ts,tsx,mts}`;
|
|
9697
9726
|
const results = await findFiles(pattern, projectRoot);
|
|
9698
9727
|
return results.map((f) => relativePosix(projectRoot, f));
|
|
@@ -10501,7 +10530,7 @@ async function fanOutReview(options) {
|
|
|
10501
10530
|
}
|
|
10502
10531
|
|
|
10503
10532
|
// src/review/validate-findings.ts
|
|
10504
|
-
import * as
|
|
10533
|
+
import * as path23 from "path";
|
|
10505
10534
|
var DOWNGRADE_MAP = {
|
|
10506
10535
|
critical: "important",
|
|
10507
10536
|
important: "suggestion",
|
|
@@ -10522,7 +10551,7 @@ function normalizePath(filePath, projectRoot) {
|
|
|
10522
10551
|
let normalized = filePath;
|
|
10523
10552
|
normalized = normalized.replace(/\\/g, "/");
|
|
10524
10553
|
const normalizedRoot = projectRoot.replace(/\\/g, "/");
|
|
10525
|
-
if (
|
|
10554
|
+
if (path23.isAbsolute(normalized)) {
|
|
10526
10555
|
const root = normalizedRoot.endsWith("/") ? normalizedRoot : normalizedRoot + "/";
|
|
10527
10556
|
if (normalized.startsWith(root)) {
|
|
10528
10557
|
normalized = normalized.slice(root.length);
|
|
@@ -10547,12 +10576,12 @@ function followImportChain(fromFile, fileContents, maxDepth = 2) {
|
|
|
10547
10576
|
while ((match = importRegex.exec(content)) !== null) {
|
|
10548
10577
|
const importPath = match[1];
|
|
10549
10578
|
if (!importPath.startsWith(".")) continue;
|
|
10550
|
-
const dir =
|
|
10551
|
-
let resolved =
|
|
10579
|
+
const dir = path23.dirname(current.file);
|
|
10580
|
+
let resolved = path23.join(dir, importPath).replace(/\\/g, "/");
|
|
10552
10581
|
if (!resolved.match(/\.(ts|tsx|js|jsx)$/)) {
|
|
10553
10582
|
resolved += ".ts";
|
|
10554
10583
|
}
|
|
10555
|
-
resolved =
|
|
10584
|
+
resolved = path23.normalize(resolved).replace(/\\/g, "/");
|
|
10556
10585
|
if (!visited.has(resolved) && current.depth + 1 <= maxDepth) {
|
|
10557
10586
|
queue.push({ file: resolved, depth: current.depth + 1 });
|
|
10558
10587
|
}
|
|
@@ -10569,7 +10598,7 @@ async function validateFindings(options) {
|
|
|
10569
10598
|
if (exclusionSet.isExcluded(normalizedFile, finding.lineRange) || exclusionSet.isExcluded(finding.file, finding.lineRange)) {
|
|
10570
10599
|
continue;
|
|
10571
10600
|
}
|
|
10572
|
-
const absoluteFile =
|
|
10601
|
+
const absoluteFile = path23.isAbsolute(finding.file) ? finding.file : path23.join(projectRoot, finding.file).replace(/\\/g, "/");
|
|
10573
10602
|
if (exclusionSet.isExcluded(absoluteFile, finding.lineRange)) {
|
|
10574
10603
|
continue;
|
|
10575
10604
|
}
|
|
@@ -11252,8 +11281,8 @@ function serializeAssignmentHistory(records) {
|
|
|
11252
11281
|
}
|
|
11253
11282
|
|
|
11254
11283
|
// src/roadmap/sync.ts
|
|
11255
|
-
import * as
|
|
11256
|
-
import * as
|
|
11284
|
+
import * as fs24 from "fs";
|
|
11285
|
+
import * as path24 from "path";
|
|
11257
11286
|
import { Ok as Ok3 } from "@harness-engineering/types";
|
|
11258
11287
|
|
|
11259
11288
|
// src/roadmap/status-rank.ts
|
|
@@ -11283,10 +11312,10 @@ function inferStatus(feature, projectPath, allFeatures) {
|
|
|
11283
11312
|
const featuresWithPlans = allFeatures.filter((f) => f.plans.length > 0);
|
|
11284
11313
|
const useRootState = featuresWithPlans.length <= 1;
|
|
11285
11314
|
if (useRootState) {
|
|
11286
|
-
const rootStatePath =
|
|
11287
|
-
if (
|
|
11315
|
+
const rootStatePath = path24.join(projectPath, ".harness", "state.json");
|
|
11316
|
+
if (fs24.existsSync(rootStatePath)) {
|
|
11288
11317
|
try {
|
|
11289
|
-
const raw =
|
|
11318
|
+
const raw = fs24.readFileSync(rootStatePath, "utf-8");
|
|
11290
11319
|
const state = JSON.parse(raw);
|
|
11291
11320
|
if (state.progress) {
|
|
11292
11321
|
for (const status of Object.values(state.progress)) {
|
|
@@ -11297,16 +11326,16 @@ function inferStatus(feature, projectPath, allFeatures) {
|
|
|
11297
11326
|
}
|
|
11298
11327
|
}
|
|
11299
11328
|
}
|
|
11300
|
-
const sessionsDir =
|
|
11301
|
-
if (
|
|
11329
|
+
const sessionsDir = path24.join(projectPath, ".harness", "sessions");
|
|
11330
|
+
if (fs24.existsSync(sessionsDir)) {
|
|
11302
11331
|
try {
|
|
11303
|
-
const sessionDirs =
|
|
11332
|
+
const sessionDirs = fs24.readdirSync(sessionsDir, { withFileTypes: true });
|
|
11304
11333
|
for (const entry of sessionDirs) {
|
|
11305
11334
|
if (!entry.isDirectory()) continue;
|
|
11306
|
-
const autopilotPath =
|
|
11307
|
-
if (!
|
|
11335
|
+
const autopilotPath = path24.join(sessionsDir, entry.name, "autopilot-state.json");
|
|
11336
|
+
if (!fs24.existsSync(autopilotPath)) continue;
|
|
11308
11337
|
try {
|
|
11309
|
-
const raw =
|
|
11338
|
+
const raw = fs24.readFileSync(autopilotPath, "utf-8");
|
|
11310
11339
|
const autopilot = JSON.parse(raw);
|
|
11311
11340
|
if (!autopilot.phases) continue;
|
|
11312
11341
|
const linkedPhases = autopilot.phases.filter(
|
|
@@ -11708,42 +11737,54 @@ var GitHubIssuesSyncAdapter = class {
|
|
|
11708
11737
|
};
|
|
11709
11738
|
|
|
11710
11739
|
// src/roadmap/sync-engine.ts
|
|
11711
|
-
import * as
|
|
11740
|
+
import * as fs25 from "fs";
|
|
11712
11741
|
function emptySyncResult() {
|
|
11713
11742
|
return { created: [], updated: [], assignmentChanges: [], errors: [] };
|
|
11714
11743
|
}
|
|
11715
|
-
|
|
11716
|
-
const
|
|
11717
|
-
|
|
11744
|
+
function buildDedupIndex(tickets, config) {
|
|
11745
|
+
const index = /* @__PURE__ */ new Map();
|
|
11746
|
+
if (!tickets) return index;
|
|
11718
11747
|
const configLabels = new Set((config.labels ?? []).map((l) => l.toLowerCase()));
|
|
11719
|
-
|
|
11720
|
-
|
|
11721
|
-
|
|
11722
|
-
|
|
11723
|
-
|
|
11724
|
-
|
|
11725
|
-
|
|
11726
|
-
existingByTitle.set(key, ticket);
|
|
11727
|
-
}
|
|
11748
|
+
for (const ticket of tickets) {
|
|
11749
|
+
const hasConfigLabels = configLabels.size === 0 || ticket.labels.some((l) => configLabels.has(l.toLowerCase()));
|
|
11750
|
+
if (!hasConfigLabels) continue;
|
|
11751
|
+
const key = ticket.title.toLowerCase();
|
|
11752
|
+
const prev = index.get(key);
|
|
11753
|
+
if (!prev || prev.status === "closed" && ticket.status === "open") {
|
|
11754
|
+
index.set(key, ticket);
|
|
11728
11755
|
}
|
|
11729
11756
|
}
|
|
11757
|
+
return index;
|
|
11758
|
+
}
|
|
11759
|
+
async function resolveExternalId(feature, milestone, adapter, dedupIndex, result) {
|
|
11760
|
+
if (feature.externalId) return true;
|
|
11761
|
+
const existing = dedupIndex.get(feature.name.toLowerCase());
|
|
11762
|
+
if (existing) {
|
|
11763
|
+
feature.externalId = existing.externalId;
|
|
11764
|
+
return true;
|
|
11765
|
+
}
|
|
11766
|
+
const createResult = await adapter.createTicket(feature, milestone);
|
|
11767
|
+
if (createResult.ok) {
|
|
11768
|
+
feature.externalId = createResult.value.externalId;
|
|
11769
|
+
result.created.push(createResult.value);
|
|
11770
|
+
} else {
|
|
11771
|
+
result.errors.push({ featureOrId: feature.name, error: createResult.error });
|
|
11772
|
+
}
|
|
11773
|
+
return false;
|
|
11774
|
+
}
|
|
11775
|
+
async function syncToExternal(roadmap, adapter, config, prefetchedTickets) {
|
|
11776
|
+
const result = emptySyncResult();
|
|
11777
|
+
const dedupIndex = buildDedupIndex(prefetchedTickets, config);
|
|
11730
11778
|
for (const milestone of roadmap.milestones) {
|
|
11731
11779
|
for (const feature of milestone.features) {
|
|
11732
|
-
|
|
11733
|
-
|
|
11734
|
-
|
|
11735
|
-
|
|
11736
|
-
|
|
11737
|
-
|
|
11738
|
-
|
|
11739
|
-
|
|
11740
|
-
result.created.push(createResult.value);
|
|
11741
|
-
} else {
|
|
11742
|
-
result.errors.push({ featureOrId: feature.name, error: createResult.error });
|
|
11743
|
-
}
|
|
11744
|
-
continue;
|
|
11745
|
-
}
|
|
11746
|
-
}
|
|
11780
|
+
const shouldUpdate = await resolveExternalId(
|
|
11781
|
+
feature,
|
|
11782
|
+
milestone.name,
|
|
11783
|
+
adapter,
|
|
11784
|
+
dedupIndex,
|
|
11785
|
+
result
|
|
11786
|
+
);
|
|
11787
|
+
if (!shouldUpdate) continue;
|
|
11747
11788
|
const updateResult = await adapter.updateTicket(feature.externalId, feature, milestone.name);
|
|
11748
11789
|
if (updateResult.ok) {
|
|
11749
11790
|
result.updated.push(feature.externalId);
|
|
@@ -11794,6 +11835,9 @@ async function syncFromExternal(roadmap, adapter, config, options, prefetchedTic
|
|
|
11794
11835
|
if (!forceSync && isRegression(feature.status, newStatus)) {
|
|
11795
11836
|
continue;
|
|
11796
11837
|
}
|
|
11838
|
+
if (!forceSync && feature.status === "blocked" && newStatus === "planned") {
|
|
11839
|
+
continue;
|
|
11840
|
+
}
|
|
11797
11841
|
feature.status = newStatus;
|
|
11798
11842
|
}
|
|
11799
11843
|
}
|
|
@@ -11808,7 +11852,7 @@ async function fullSync(roadmapPath, adapter, config, options) {
|
|
|
11808
11852
|
});
|
|
11809
11853
|
await previousSync;
|
|
11810
11854
|
try {
|
|
11811
|
-
const raw =
|
|
11855
|
+
const raw = fs25.readFileSync(roadmapPath, "utf-8");
|
|
11812
11856
|
const parseResult = parseRoadmap(raw);
|
|
11813
11857
|
if (!parseResult.ok) {
|
|
11814
11858
|
return {
|
|
@@ -11821,7 +11865,7 @@ async function fullSync(roadmapPath, adapter, config, options) {
|
|
|
11821
11865
|
const tickets = fetchResult.ok ? fetchResult.value : void 0;
|
|
11822
11866
|
const pushResult = await syncToExternal(roadmap, adapter, config, tickets);
|
|
11823
11867
|
const pullResult = await syncFromExternal(roadmap, adapter, config, options, tickets);
|
|
11824
|
-
|
|
11868
|
+
fs25.writeFileSync(roadmapPath, serializeRoadmap(roadmap), "utf-8");
|
|
11825
11869
|
return {
|
|
11826
11870
|
created: pushResult.created,
|
|
11827
11871
|
updated: pushResult.updated,
|
|
@@ -11980,18 +12024,18 @@ var EmitInteractionInputSchema = z9.object({
|
|
|
11980
12024
|
});
|
|
11981
12025
|
|
|
11982
12026
|
// src/blueprint/scanner.ts
|
|
11983
|
-
import * as
|
|
11984
|
-
import * as
|
|
12027
|
+
import * as fs26 from "fs/promises";
|
|
12028
|
+
import * as path25 from "path";
|
|
11985
12029
|
var ProjectScanner = class {
|
|
11986
12030
|
constructor(rootDir) {
|
|
11987
12031
|
this.rootDir = rootDir;
|
|
11988
12032
|
}
|
|
11989
12033
|
rootDir;
|
|
11990
12034
|
async scan() {
|
|
11991
|
-
let projectName =
|
|
12035
|
+
let projectName = path25.basename(this.rootDir);
|
|
11992
12036
|
try {
|
|
11993
|
-
const pkgPath =
|
|
11994
|
-
const pkgRaw = await
|
|
12037
|
+
const pkgPath = path25.join(this.rootDir, "package.json");
|
|
12038
|
+
const pkgRaw = await fs26.readFile(pkgPath, "utf-8");
|
|
11995
12039
|
const pkg = JSON.parse(pkgRaw);
|
|
11996
12040
|
if (pkg.name) projectName = pkg.name;
|
|
11997
12041
|
} catch {
|
|
@@ -12032,8 +12076,8 @@ var ProjectScanner = class {
|
|
|
12032
12076
|
};
|
|
12033
12077
|
|
|
12034
12078
|
// src/blueprint/generator.ts
|
|
12035
|
-
import * as
|
|
12036
|
-
import * as
|
|
12079
|
+
import * as fs27 from "fs/promises";
|
|
12080
|
+
import * as path26 from "path";
|
|
12037
12081
|
import * as ejs from "ejs";
|
|
12038
12082
|
|
|
12039
12083
|
// src/blueprint/templates.ts
|
|
@@ -12117,19 +12161,19 @@ var BlueprintGenerator = class {
|
|
|
12117
12161
|
styles: STYLES,
|
|
12118
12162
|
scripts: SCRIPTS
|
|
12119
12163
|
});
|
|
12120
|
-
await
|
|
12121
|
-
await
|
|
12164
|
+
await fs27.mkdir(options.outputDir, { recursive: true });
|
|
12165
|
+
await fs27.writeFile(path26.join(options.outputDir, "index.html"), html);
|
|
12122
12166
|
}
|
|
12123
12167
|
};
|
|
12124
12168
|
|
|
12125
12169
|
// src/update-checker.ts
|
|
12126
|
-
import * as
|
|
12127
|
-
import * as
|
|
12170
|
+
import * as fs28 from "fs";
|
|
12171
|
+
import * as path27 from "path";
|
|
12128
12172
|
import * as os from "os";
|
|
12129
12173
|
import { spawn } from "child_process";
|
|
12130
12174
|
function getStatePath() {
|
|
12131
12175
|
const home = process.env["HOME"] || os.homedir();
|
|
12132
|
-
return
|
|
12176
|
+
return path27.join(home, ".harness", "update-check.json");
|
|
12133
12177
|
}
|
|
12134
12178
|
function isUpdateCheckEnabled(configInterval) {
|
|
12135
12179
|
if (process.env["HARNESS_NO_UPDATE_CHECK"] === "1") return false;
|
|
@@ -12142,7 +12186,7 @@ function shouldRunCheck(state, intervalMs) {
|
|
|
12142
12186
|
}
|
|
12143
12187
|
function readCheckState() {
|
|
12144
12188
|
try {
|
|
12145
|
-
const raw =
|
|
12189
|
+
const raw = fs28.readFileSync(getStatePath(), "utf-8");
|
|
12146
12190
|
const parsed = JSON.parse(raw);
|
|
12147
12191
|
if (typeof parsed === "object" && parsed !== null && "lastCheckTime" in parsed && typeof parsed.lastCheckTime === "number" && "currentVersion" in parsed && typeof parsed.currentVersion === "string") {
|
|
12148
12192
|
const state = parsed;
|
|
@@ -12159,7 +12203,7 @@ function readCheckState() {
|
|
|
12159
12203
|
}
|
|
12160
12204
|
function spawnBackgroundCheck(currentVersion) {
|
|
12161
12205
|
const statePath = getStatePath();
|
|
12162
|
-
const stateDir =
|
|
12206
|
+
const stateDir = path27.dirname(statePath);
|
|
12163
12207
|
const script = `
|
|
12164
12208
|
const { execSync } = require('child_process');
|
|
12165
12209
|
const fs = require('fs');
|
|
@@ -12248,9 +12292,9 @@ async function resolveWasmPath(grammarName) {
|
|
|
12248
12292
|
const { createRequire } = await import("module");
|
|
12249
12293
|
const require2 = createRequire(import.meta.url ?? __filename);
|
|
12250
12294
|
const pkgPath = require2.resolve("tree-sitter-wasms/package.json");
|
|
12251
|
-
const
|
|
12252
|
-
const pkgDir =
|
|
12253
|
-
return
|
|
12295
|
+
const path31 = await import("path");
|
|
12296
|
+
const pkgDir = path31.dirname(pkgPath);
|
|
12297
|
+
return path31.join(pkgDir, "out", `${grammarName}.wasm`);
|
|
12254
12298
|
}
|
|
12255
12299
|
async function loadLanguage(lang) {
|
|
12256
12300
|
const grammarName = GRAMMAR_MAP[lang];
|
|
@@ -12654,8 +12698,8 @@ function getModelPrice(model, dataset) {
|
|
|
12654
12698
|
}
|
|
12655
12699
|
|
|
12656
12700
|
// src/pricing/cache.ts
|
|
12657
|
-
import * as
|
|
12658
|
-
import * as
|
|
12701
|
+
import * as fs29 from "fs/promises";
|
|
12702
|
+
import * as path28 from "path";
|
|
12659
12703
|
|
|
12660
12704
|
// src/pricing/fallback.json
|
|
12661
12705
|
var fallback_default = {
|
|
@@ -12708,14 +12752,14 @@ var LITELLM_PRICING_URL = "https://raw.githubusercontent.com/BerriAI/litellm/mai
|
|
|
12708
12752
|
var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
12709
12753
|
var STALENESS_WARNING_DAYS = 7;
|
|
12710
12754
|
function getCachePath(projectRoot) {
|
|
12711
|
-
return
|
|
12755
|
+
return path28.join(projectRoot, ".harness", "cache", "pricing.json");
|
|
12712
12756
|
}
|
|
12713
12757
|
function getStalenessMarkerPath(projectRoot) {
|
|
12714
|
-
return
|
|
12758
|
+
return path28.join(projectRoot, ".harness", "cache", "staleness-marker.json");
|
|
12715
12759
|
}
|
|
12716
12760
|
async function readDiskCache(projectRoot) {
|
|
12717
12761
|
try {
|
|
12718
|
-
const raw = await
|
|
12762
|
+
const raw = await fs29.readFile(getCachePath(projectRoot), "utf-8");
|
|
12719
12763
|
return JSON.parse(raw);
|
|
12720
12764
|
} catch {
|
|
12721
12765
|
return null;
|
|
@@ -12723,8 +12767,8 @@ async function readDiskCache(projectRoot) {
|
|
|
12723
12767
|
}
|
|
12724
12768
|
async function writeDiskCache(projectRoot, data) {
|
|
12725
12769
|
const cachePath = getCachePath(projectRoot);
|
|
12726
|
-
await
|
|
12727
|
-
await
|
|
12770
|
+
await fs29.mkdir(path28.dirname(cachePath), { recursive: true });
|
|
12771
|
+
await fs29.writeFile(cachePath, JSON.stringify(data, null, 2));
|
|
12728
12772
|
}
|
|
12729
12773
|
async function fetchFromNetwork() {
|
|
12730
12774
|
try {
|
|
@@ -12751,7 +12795,7 @@ function loadFallbackDataset() {
|
|
|
12751
12795
|
async function checkAndWarnStaleness(projectRoot) {
|
|
12752
12796
|
const markerPath = getStalenessMarkerPath(projectRoot);
|
|
12753
12797
|
try {
|
|
12754
|
-
const raw = await
|
|
12798
|
+
const raw = await fs29.readFile(markerPath, "utf-8");
|
|
12755
12799
|
const marker = JSON.parse(raw);
|
|
12756
12800
|
const firstUse = new Date(marker.firstFallbackUse).getTime();
|
|
12757
12801
|
const now = Date.now();
|
|
@@ -12763,8 +12807,8 @@ async function checkAndWarnStaleness(projectRoot) {
|
|
|
12763
12807
|
}
|
|
12764
12808
|
} catch {
|
|
12765
12809
|
try {
|
|
12766
|
-
await
|
|
12767
|
-
await
|
|
12810
|
+
await fs29.mkdir(path28.dirname(markerPath), { recursive: true });
|
|
12811
|
+
await fs29.writeFile(
|
|
12768
12812
|
markerPath,
|
|
12769
12813
|
JSON.stringify({ firstFallbackUse: (/* @__PURE__ */ new Date()).toISOString() })
|
|
12770
12814
|
);
|
|
@@ -12774,7 +12818,7 @@ async function checkAndWarnStaleness(projectRoot) {
|
|
|
12774
12818
|
}
|
|
12775
12819
|
async function clearStalenessMarker(projectRoot) {
|
|
12776
12820
|
try {
|
|
12777
|
-
await
|
|
12821
|
+
await fs29.unlink(getStalenessMarkerPath(projectRoot));
|
|
12778
12822
|
} catch {
|
|
12779
12823
|
}
|
|
12780
12824
|
}
|
|
@@ -12820,8 +12864,7 @@ function calculateCost(record, dataset) {
|
|
|
12820
12864
|
}
|
|
12821
12865
|
|
|
12822
12866
|
// src/usage/aggregator.ts
|
|
12823
|
-
function
|
|
12824
|
-
if (records.length === 0) return [];
|
|
12867
|
+
function bucketRecordsBySession(records) {
|
|
12825
12868
|
const sessionMap = /* @__PURE__ */ new Map();
|
|
12826
12869
|
for (const record of records) {
|
|
12827
12870
|
const tagged = record;
|
|
@@ -12837,58 +12880,104 @@ function aggregateBySession(records) {
|
|
|
12837
12880
|
}
|
|
12838
12881
|
bucket.allRecords.push(tagged);
|
|
12839
12882
|
}
|
|
12883
|
+
return sessionMap;
|
|
12884
|
+
}
|
|
12885
|
+
function accumulateCost(running, recordCost) {
|
|
12886
|
+
if (recordCost != null && running != null) {
|
|
12887
|
+
return running + recordCost;
|
|
12888
|
+
}
|
|
12889
|
+
if (recordCost == null) {
|
|
12890
|
+
return null;
|
|
12891
|
+
}
|
|
12892
|
+
return running;
|
|
12893
|
+
}
|
|
12894
|
+
function sumRecordTokens(tokenSource) {
|
|
12895
|
+
const tokens = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
|
|
12896
|
+
let cacheCreation;
|
|
12897
|
+
let cacheRead;
|
|
12898
|
+
let costMicroUSD = 0;
|
|
12899
|
+
let model;
|
|
12900
|
+
for (const r of tokenSource) {
|
|
12901
|
+
tokens.inputTokens += r.tokens.inputTokens;
|
|
12902
|
+
tokens.outputTokens += r.tokens.outputTokens;
|
|
12903
|
+
tokens.totalTokens += r.tokens.totalTokens;
|
|
12904
|
+
if (r.cacheCreationTokens != null) {
|
|
12905
|
+
cacheCreation = (cacheCreation ?? 0) + r.cacheCreationTokens;
|
|
12906
|
+
}
|
|
12907
|
+
if (r.cacheReadTokens != null) {
|
|
12908
|
+
cacheRead = (cacheRead ?? 0) + r.cacheReadTokens;
|
|
12909
|
+
}
|
|
12910
|
+
costMicroUSD = accumulateCost(costMicroUSD, r.costMicroUSD);
|
|
12911
|
+
if (!model && r.model) {
|
|
12912
|
+
model = r.model;
|
|
12913
|
+
}
|
|
12914
|
+
}
|
|
12915
|
+
return { tokens, cacheCreation, cacheRead, costMicroUSD, model };
|
|
12916
|
+
}
|
|
12917
|
+
function findModel(records) {
|
|
12918
|
+
for (const r of records) {
|
|
12919
|
+
if (r.model) return r.model;
|
|
12920
|
+
}
|
|
12921
|
+
return void 0;
|
|
12922
|
+
}
|
|
12923
|
+
function determineSource(hasHarness, hasCC) {
|
|
12924
|
+
if (hasHarness && hasCC) return "merged";
|
|
12925
|
+
if (hasCC) return "claude-code";
|
|
12926
|
+
return "harness";
|
|
12927
|
+
}
|
|
12928
|
+
function applyOptionalFields(session, totals, model) {
|
|
12929
|
+
if (model) session.model = model;
|
|
12930
|
+
if (totals.cacheCreation != null) session.cacheCreationTokens = totals.cacheCreation;
|
|
12931
|
+
if (totals.cacheRead != null) session.cacheReadTokens = totals.cacheRead;
|
|
12932
|
+
}
|
|
12933
|
+
function buildSessionUsage(sessionId, bucket) {
|
|
12934
|
+
const hasHarness = bucket.harnessRecords.length > 0;
|
|
12935
|
+
const hasCC = bucket.ccRecords.length > 0;
|
|
12936
|
+
const tokenSource = hasHarness ? bucket.harnessRecords : bucket.ccRecords;
|
|
12937
|
+
const totals = sumRecordTokens(tokenSource);
|
|
12938
|
+
const model = totals.model ?? (hasCC ? findModel(bucket.ccRecords) : void 0);
|
|
12939
|
+
const timestamps = bucket.allRecords.map((r) => r.timestamp).sort();
|
|
12940
|
+
const session = {
|
|
12941
|
+
sessionId,
|
|
12942
|
+
firstTimestamp: timestamps[0] ?? "",
|
|
12943
|
+
lastTimestamp: timestamps[timestamps.length - 1] ?? "",
|
|
12944
|
+
tokens: totals.tokens,
|
|
12945
|
+
costMicroUSD: totals.costMicroUSD,
|
|
12946
|
+
source: determineSource(hasHarness, hasCC)
|
|
12947
|
+
};
|
|
12948
|
+
applyOptionalFields(session, totals, model);
|
|
12949
|
+
return session;
|
|
12950
|
+
}
|
|
12951
|
+
function accumulateIntoDayBucket(day, record) {
|
|
12952
|
+
day.sessions.add(record.sessionId);
|
|
12953
|
+
day.tokens.inputTokens += record.tokens.inputTokens;
|
|
12954
|
+
day.tokens.outputTokens += record.tokens.outputTokens;
|
|
12955
|
+
day.tokens.totalTokens += record.tokens.totalTokens;
|
|
12956
|
+
if (record.cacheCreationTokens != null) {
|
|
12957
|
+
day.cacheCreation = (day.cacheCreation ?? 0) + record.cacheCreationTokens;
|
|
12958
|
+
}
|
|
12959
|
+
if (record.cacheReadTokens != null) {
|
|
12960
|
+
day.cacheRead = (day.cacheRead ?? 0) + record.cacheReadTokens;
|
|
12961
|
+
}
|
|
12962
|
+
day.costMicroUSD = accumulateCost(day.costMicroUSD, record.costMicroUSD);
|
|
12963
|
+
if (record.model) {
|
|
12964
|
+
day.models.add(record.model);
|
|
12965
|
+
}
|
|
12966
|
+
}
|
|
12967
|
+
function createDayBucket() {
|
|
12968
|
+
return {
|
|
12969
|
+
sessions: /* @__PURE__ */ new Set(),
|
|
12970
|
+
tokens: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
|
|
12971
|
+
costMicroUSD: 0,
|
|
12972
|
+
models: /* @__PURE__ */ new Set()
|
|
12973
|
+
};
|
|
12974
|
+
}
|
|
12975
|
+
function aggregateBySession(records) {
|
|
12976
|
+
if (records.length === 0) return [];
|
|
12977
|
+
const sessionMap = bucketRecordsBySession(records);
|
|
12840
12978
|
const results = [];
|
|
12841
12979
|
for (const [sessionId, bucket] of sessionMap) {
|
|
12842
|
-
|
|
12843
|
-
const hasCC = bucket.ccRecords.length > 0;
|
|
12844
|
-
const isMerged = hasHarness && hasCC;
|
|
12845
|
-
const tokenSource = hasHarness ? bucket.harnessRecords : bucket.ccRecords;
|
|
12846
|
-
const tokens = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
|
|
12847
|
-
let cacheCreation;
|
|
12848
|
-
let cacheRead;
|
|
12849
|
-
let costMicroUSD = 0;
|
|
12850
|
-
let model;
|
|
12851
|
-
for (const r of tokenSource) {
|
|
12852
|
-
tokens.inputTokens += r.tokens.inputTokens;
|
|
12853
|
-
tokens.outputTokens += r.tokens.outputTokens;
|
|
12854
|
-
tokens.totalTokens += r.tokens.totalTokens;
|
|
12855
|
-
if (r.cacheCreationTokens != null) {
|
|
12856
|
-
cacheCreation = (cacheCreation ?? 0) + r.cacheCreationTokens;
|
|
12857
|
-
}
|
|
12858
|
-
if (r.cacheReadTokens != null) {
|
|
12859
|
-
cacheRead = (cacheRead ?? 0) + r.cacheReadTokens;
|
|
12860
|
-
}
|
|
12861
|
-
if (r.costMicroUSD != null && costMicroUSD != null) {
|
|
12862
|
-
costMicroUSD += r.costMicroUSD;
|
|
12863
|
-
} else if (r.costMicroUSD == null) {
|
|
12864
|
-
costMicroUSD = null;
|
|
12865
|
-
}
|
|
12866
|
-
if (!model && r.model) {
|
|
12867
|
-
model = r.model;
|
|
12868
|
-
}
|
|
12869
|
-
}
|
|
12870
|
-
if (!model && hasCC) {
|
|
12871
|
-
for (const r of bucket.ccRecords) {
|
|
12872
|
-
if (r.model) {
|
|
12873
|
-
model = r.model;
|
|
12874
|
-
break;
|
|
12875
|
-
}
|
|
12876
|
-
}
|
|
12877
|
-
}
|
|
12878
|
-
const timestamps = bucket.allRecords.map((r) => r.timestamp).sort();
|
|
12879
|
-
const source = isMerged ? "merged" : hasCC ? "claude-code" : "harness";
|
|
12880
|
-
const session = {
|
|
12881
|
-
sessionId,
|
|
12882
|
-
firstTimestamp: timestamps[0] ?? "",
|
|
12883
|
-
lastTimestamp: timestamps[timestamps.length - 1] ?? "",
|
|
12884
|
-
tokens,
|
|
12885
|
-
costMicroUSD,
|
|
12886
|
-
source
|
|
12887
|
-
};
|
|
12888
|
-
if (model) session.model = model;
|
|
12889
|
-
if (cacheCreation != null) session.cacheCreationTokens = cacheCreation;
|
|
12890
|
-
if (cacheRead != null) session.cacheReadTokens = cacheRead;
|
|
12891
|
-
results.push(session);
|
|
12980
|
+
results.push(buildSessionUsage(sessionId, bucket));
|
|
12892
12981
|
}
|
|
12893
12982
|
results.sort((a, b) => b.firstTimestamp.localeCompare(a.firstTimestamp));
|
|
12894
12983
|
return results;
|
|
@@ -12899,32 +12988,9 @@ function aggregateByDay(records) {
|
|
|
12899
12988
|
for (const record of records) {
|
|
12900
12989
|
const date = record.timestamp.slice(0, 10);
|
|
12901
12990
|
if (!dayMap.has(date)) {
|
|
12902
|
-
dayMap.set(date,
|
|
12903
|
-
sessions: /* @__PURE__ */ new Set(),
|
|
12904
|
-
tokens: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
|
|
12905
|
-
costMicroUSD: 0,
|
|
12906
|
-
models: /* @__PURE__ */ new Set()
|
|
12907
|
-
});
|
|
12908
|
-
}
|
|
12909
|
-
const day = dayMap.get(date);
|
|
12910
|
-
day.sessions.add(record.sessionId);
|
|
12911
|
-
day.tokens.inputTokens += record.tokens.inputTokens;
|
|
12912
|
-
day.tokens.outputTokens += record.tokens.outputTokens;
|
|
12913
|
-
day.tokens.totalTokens += record.tokens.totalTokens;
|
|
12914
|
-
if (record.cacheCreationTokens != null) {
|
|
12915
|
-
day.cacheCreation = (day.cacheCreation ?? 0) + record.cacheCreationTokens;
|
|
12916
|
-
}
|
|
12917
|
-
if (record.cacheReadTokens != null) {
|
|
12918
|
-
day.cacheRead = (day.cacheRead ?? 0) + record.cacheReadTokens;
|
|
12919
|
-
}
|
|
12920
|
-
if (record.costMicroUSD != null && day.costMicroUSD != null) {
|
|
12921
|
-
day.costMicroUSD += record.costMicroUSD;
|
|
12922
|
-
} else if (record.costMicroUSD == null) {
|
|
12923
|
-
day.costMicroUSD = null;
|
|
12924
|
-
}
|
|
12925
|
-
if (record.model) {
|
|
12926
|
-
day.models.add(record.model);
|
|
12991
|
+
dayMap.set(date, createDayBucket());
|
|
12927
12992
|
}
|
|
12993
|
+
accumulateIntoDayBucket(dayMap.get(date), record);
|
|
12928
12994
|
}
|
|
12929
12995
|
const results = [];
|
|
12930
12996
|
for (const [date, day] of dayMap) {
|
|
@@ -12944,8 +13010,8 @@ function aggregateByDay(records) {
|
|
|
12944
13010
|
}
|
|
12945
13011
|
|
|
12946
13012
|
// src/usage/jsonl-reader.ts
|
|
12947
|
-
import * as
|
|
12948
|
-
import * as
|
|
13013
|
+
import * as fs30 from "fs";
|
|
13014
|
+
import * as path29 from "path";
|
|
12949
13015
|
function parseLine(line, lineNumber) {
|
|
12950
13016
|
let entry;
|
|
12951
13017
|
try {
|
|
@@ -12984,10 +13050,10 @@ function parseLine(line, lineNumber) {
|
|
|
12984
13050
|
return record;
|
|
12985
13051
|
}
|
|
12986
13052
|
function readCostRecords(projectRoot) {
|
|
12987
|
-
const costsFile =
|
|
13053
|
+
const costsFile = path29.join(projectRoot, ".harness", "metrics", "costs.jsonl");
|
|
12988
13054
|
let raw;
|
|
12989
13055
|
try {
|
|
12990
|
-
raw =
|
|
13056
|
+
raw = fs30.readFileSync(costsFile, "utf-8");
|
|
12991
13057
|
} catch {
|
|
12992
13058
|
return [];
|
|
12993
13059
|
}
|
|
@@ -13005,8 +13071,8 @@ function readCostRecords(projectRoot) {
|
|
|
13005
13071
|
}
|
|
13006
13072
|
|
|
13007
13073
|
// src/usage/cc-parser.ts
|
|
13008
|
-
import * as
|
|
13009
|
-
import * as
|
|
13074
|
+
import * as fs31 from "fs";
|
|
13075
|
+
import * as path30 from "path";
|
|
13010
13076
|
import * as os2 from "os";
|
|
13011
13077
|
function extractUsage(entry) {
|
|
13012
13078
|
if (entry.type !== "assistant") return null;
|
|
@@ -13039,7 +13105,7 @@ function parseCCLine(line, filePath, lineNumber) {
|
|
|
13039
13105
|
entry = JSON.parse(line);
|
|
13040
13106
|
} catch {
|
|
13041
13107
|
console.warn(
|
|
13042
|
-
`[harness usage] Skipping malformed CC JSONL line ${lineNumber} in ${
|
|
13108
|
+
`[harness usage] Skipping malformed CC JSONL line ${lineNumber} in ${path30.basename(filePath)}`
|
|
13043
13109
|
);
|
|
13044
13110
|
return null;
|
|
13045
13111
|
}
|
|
@@ -13053,7 +13119,7 @@ function parseCCLine(line, filePath, lineNumber) {
|
|
|
13053
13119
|
function readCCFile(filePath) {
|
|
13054
13120
|
let raw;
|
|
13055
13121
|
try {
|
|
13056
|
-
raw =
|
|
13122
|
+
raw = fs31.readFileSync(filePath, "utf-8");
|
|
13057
13123
|
} catch {
|
|
13058
13124
|
return [];
|
|
13059
13125
|
}
|
|
@@ -13075,10 +13141,10 @@ function readCCFile(filePath) {
|
|
|
13075
13141
|
}
|
|
13076
13142
|
function parseCCRecords() {
|
|
13077
13143
|
const homeDir = process.env.HOME ?? os2.homedir();
|
|
13078
|
-
const projectsDir =
|
|
13144
|
+
const projectsDir = path30.join(homeDir, ".claude", "projects");
|
|
13079
13145
|
let projectDirs;
|
|
13080
13146
|
try {
|
|
13081
|
-
projectDirs =
|
|
13147
|
+
projectDirs = fs31.readdirSync(projectsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => path30.join(projectsDir, d.name));
|
|
13082
13148
|
} catch {
|
|
13083
13149
|
return [];
|
|
13084
13150
|
}
|
|
@@ -13086,7 +13152,7 @@ function parseCCRecords() {
|
|
|
13086
13152
|
for (const dir of projectDirs) {
|
|
13087
13153
|
let files;
|
|
13088
13154
|
try {
|
|
13089
|
-
files =
|
|
13155
|
+
files = fs31.readdirSync(dir).filter((f) => f.endsWith(".jsonl")).map((f) => path30.join(dir, f));
|
|
13090
13156
|
} catch {
|
|
13091
13157
|
continue;
|
|
13092
13158
|
}
|
|
@@ -13237,6 +13303,7 @@ export {
|
|
|
13237
13303
|
clearFailuresCache,
|
|
13238
13304
|
clearLearningsCache,
|
|
13239
13305
|
clearTaint,
|
|
13306
|
+
computeContentHash,
|
|
13240
13307
|
computeOverallSeverity,
|
|
13241
13308
|
computeScanExitCode,
|
|
13242
13309
|
configureFeedback,
|
|
@@ -13330,6 +13397,7 @@ export {
|
|
|
13330
13397
|
migrateToStreams,
|
|
13331
13398
|
networkRules,
|
|
13332
13399
|
nodeRules,
|
|
13400
|
+
normalizeLearningContent,
|
|
13333
13401
|
parseCCRecords,
|
|
13334
13402
|
parseDateFromEntry,
|
|
13335
13403
|
parseDiff,
|