@harness-engineering/core 0.11.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/architecture/matchers.d.mts +1 -1
- package/dist/architecture/matchers.d.ts +1 -1
- package/dist/index.d.mts +496 -377
- package/dist/index.d.ts +496 -377
- package/dist/index.js +713 -301
- package/dist/index.mjs +683 -284
- package/dist/{matchers-D20x48U9.d.mts → matchers-Dj1t5vpg.d.mts} +46 -46
- package/dist/{matchers-D20x48U9.d.ts → matchers-Dj1t5vpg.d.ts} +46 -46
- package/package.json +3 -3
package/dist/index.mjs
CHANGED
|
@@ -83,15 +83,15 @@ function validateConfig(data, schema) {
|
|
|
83
83
|
let message = "Configuration validation failed";
|
|
84
84
|
const suggestions = [];
|
|
85
85
|
if (firstError) {
|
|
86
|
-
const
|
|
87
|
-
const pathDisplay =
|
|
86
|
+
const path20 = firstError.path.join(".");
|
|
87
|
+
const pathDisplay = path20 ? ` at "${path20}"` : "";
|
|
88
88
|
if (firstError.code === "invalid_type") {
|
|
89
89
|
const received = firstError.received;
|
|
90
90
|
const expected = firstError.expected;
|
|
91
91
|
if (received === "undefined") {
|
|
92
92
|
code = "MISSING_FIELD";
|
|
93
93
|
message = `Missing required field${pathDisplay}: ${firstError.message}`;
|
|
94
|
-
suggestions.push(`Field "${
|
|
94
|
+
suggestions.push(`Field "${path20}" is required and must be of type "${expected}"`);
|
|
95
95
|
} else {
|
|
96
96
|
code = "INVALID_TYPE";
|
|
97
97
|
message = `Invalid type${pathDisplay}: ${firstError.message}`;
|
|
@@ -304,30 +304,27 @@ function extractSections(content) {
|
|
|
304
304
|
return result;
|
|
305
305
|
});
|
|
306
306
|
}
|
|
307
|
-
function isExternalLink(
|
|
308
|
-
return
|
|
307
|
+
function isExternalLink(path20) {
|
|
308
|
+
return path20.startsWith("http://") || path20.startsWith("https://") || path20.startsWith("#") || path20.startsWith("mailto:");
|
|
309
309
|
}
|
|
310
310
|
function resolveLinkPath(linkPath, baseDir) {
|
|
311
311
|
return linkPath.startsWith(".") ? join(baseDir, linkPath) : linkPath;
|
|
312
312
|
}
|
|
313
|
-
async function validateAgentsMap(
|
|
314
|
-
|
|
315
|
-
"[harness] validateAgentsMap() is deprecated. Use graph-based validation via Assembler.checkCoverage() from @harness-engineering/graph"
|
|
316
|
-
);
|
|
317
|
-
const contentResult = await readFileContent(path13);
|
|
313
|
+
async function validateAgentsMap(path20 = "./AGENTS.md") {
|
|
314
|
+
const contentResult = await readFileContent(path20);
|
|
318
315
|
if (!contentResult.ok) {
|
|
319
316
|
return Err(
|
|
320
317
|
createError(
|
|
321
318
|
"PARSE_ERROR",
|
|
322
319
|
`Failed to read AGENTS.md: ${contentResult.error.message}`,
|
|
323
|
-
{ path:
|
|
320
|
+
{ path: path20 },
|
|
324
321
|
["Ensure the file exists", "Check file permissions"]
|
|
325
322
|
)
|
|
326
323
|
);
|
|
327
324
|
}
|
|
328
325
|
const content = contentResult.value;
|
|
329
326
|
const sections = extractSections(content);
|
|
330
|
-
const baseDir = dirname(
|
|
327
|
+
const baseDir = dirname(path20);
|
|
331
328
|
const sectionTitles = sections.map((s) => s.title);
|
|
332
329
|
const missingSections = REQUIRED_SECTIONS.filter(
|
|
333
330
|
(required) => !sectionTitles.some((title) => title.toLowerCase().includes(required.toLowerCase()))
|
|
@@ -468,8 +465,8 @@ async function checkDocCoverage(domain, options = {}) {
|
|
|
468
465
|
|
|
469
466
|
// src/context/knowledge-map.ts
|
|
470
467
|
import { join as join2, basename as basename2, relative as relative2 } from "path";
|
|
471
|
-
function suggestFix(
|
|
472
|
-
const targetName = basename2(
|
|
468
|
+
function suggestFix(path20, existingFiles) {
|
|
469
|
+
const targetName = basename2(path20).toLowerCase();
|
|
473
470
|
const similar = existingFiles.find((file) => {
|
|
474
471
|
const fileName = basename2(file).toLowerCase();
|
|
475
472
|
return fileName.includes(targetName) || targetName.includes(fileName);
|
|
@@ -477,12 +474,9 @@ function suggestFix(path13, existingFiles) {
|
|
|
477
474
|
if (similar) {
|
|
478
475
|
return `Did you mean "${similar}"?`;
|
|
479
476
|
}
|
|
480
|
-
return `Create the file "${
|
|
477
|
+
return `Create the file "${path20}" or remove the link`;
|
|
481
478
|
}
|
|
482
479
|
async function validateKnowledgeMap(rootDir = process.cwd()) {
|
|
483
|
-
console.warn(
|
|
484
|
-
"[harness] validateKnowledgeMap() is deprecated. Use graph-based validation via Assembler.checkCoverage() from @harness-engineering/graph"
|
|
485
|
-
);
|
|
486
480
|
const agentsPath = join2(rootDir, "AGENTS.md");
|
|
487
481
|
const agentsResult = await validateAgentsMap(agentsPath);
|
|
488
482
|
if (!agentsResult.ok) {
|
|
@@ -832,8 +826,8 @@ function createBoundaryValidator(schema, name) {
|
|
|
832
826
|
return Ok(result.data);
|
|
833
827
|
}
|
|
834
828
|
const suggestions = result.error.issues.map((issue) => {
|
|
835
|
-
const
|
|
836
|
-
return
|
|
829
|
+
const path20 = issue.path.join(".");
|
|
830
|
+
return path20 ? `${path20}: ${issue.message}` : issue.message;
|
|
837
831
|
});
|
|
838
832
|
return Err(
|
|
839
833
|
createError(
|
|
@@ -1140,8 +1134,6 @@ function deepMergeConstraints(localConfig, bundleConstraints, _existingContribut
|
|
|
1140
1134
|
}
|
|
1141
1135
|
if (bundleConstraints.architecture) {
|
|
1142
1136
|
const localArch = localConfig.architecture ?? {
|
|
1143
|
-
enabled: true,
|
|
1144
|
-
baselinePath: ".harness/arch/baselines.json",
|
|
1145
1137
|
thresholds: {},
|
|
1146
1138
|
modules: {}
|
|
1147
1139
|
};
|
|
@@ -1264,7 +1256,7 @@ async function readLockfile(lockfilePath) {
|
|
|
1264
1256
|
return { ok: true, value: result.data };
|
|
1265
1257
|
}
|
|
1266
1258
|
async function writeLockfile(lockfilePath, lockfile) {
|
|
1267
|
-
|
|
1259
|
+
return writeConfig(lockfilePath, lockfile);
|
|
1268
1260
|
}
|
|
1269
1261
|
function addProvenance(lockfile, packageName, entry) {
|
|
1270
1262
|
return {
|
|
@@ -1293,6 +1285,77 @@ function isNodeError(err) {
|
|
|
1293
1285
|
return err instanceof Error && "code" in err;
|
|
1294
1286
|
}
|
|
1295
1287
|
|
|
1288
|
+
// src/constraints/sharing/remove.ts
|
|
1289
|
+
function removeContributions(config, contributions) {
|
|
1290
|
+
const result = { ...config };
|
|
1291
|
+
const layerNames = contributions.layers;
|
|
1292
|
+
if (layerNames && layerNames.length > 0 && Array.isArray(result.layers)) {
|
|
1293
|
+
const nameSet = new Set(layerNames);
|
|
1294
|
+
result.layers = result.layers.filter((l) => !nameSet.has(l.name));
|
|
1295
|
+
}
|
|
1296
|
+
const fromKeys = contributions.forbiddenImports;
|
|
1297
|
+
if (fromKeys && fromKeys.length > 0 && Array.isArray(result.forbiddenImports)) {
|
|
1298
|
+
const fromSet = new Set(fromKeys);
|
|
1299
|
+
result.forbiddenImports = result.forbiddenImports.filter(
|
|
1300
|
+
(r) => !fromSet.has(r.from)
|
|
1301
|
+
);
|
|
1302
|
+
}
|
|
1303
|
+
const boundarySchemas = contributions.boundaries;
|
|
1304
|
+
if (boundarySchemas && boundarySchemas.length > 0 && result.boundaries) {
|
|
1305
|
+
const boundaries = result.boundaries;
|
|
1306
|
+
if (boundaries.requireSchema) {
|
|
1307
|
+
const schemaSet = new Set(boundarySchemas);
|
|
1308
|
+
result.boundaries = {
|
|
1309
|
+
...boundaries,
|
|
1310
|
+
requireSchema: boundaries.requireSchema.filter((s) => !schemaSet.has(s))
|
|
1311
|
+
};
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
const thresholdKeys = contributions["architecture.thresholds"];
|
|
1315
|
+
if (thresholdKeys && thresholdKeys.length > 0 && result.architecture) {
|
|
1316
|
+
const arch = { ...result.architecture };
|
|
1317
|
+
const thresholds = { ...arch.thresholds };
|
|
1318
|
+
for (const key of thresholdKeys) {
|
|
1319
|
+
delete thresholds[key];
|
|
1320
|
+
}
|
|
1321
|
+
arch.thresholds = thresholds;
|
|
1322
|
+
result.architecture = arch;
|
|
1323
|
+
}
|
|
1324
|
+
const moduleKeys = contributions["architecture.modules"];
|
|
1325
|
+
if (moduleKeys && moduleKeys.length > 0 && result.architecture) {
|
|
1326
|
+
const arch = { ...result.architecture };
|
|
1327
|
+
const modules = { ...arch.modules };
|
|
1328
|
+
for (const key of moduleKeys) {
|
|
1329
|
+
const colonIdx = key.indexOf(":");
|
|
1330
|
+
if (colonIdx === -1) continue;
|
|
1331
|
+
const modulePath = key.substring(0, colonIdx);
|
|
1332
|
+
const category = key.substring(colonIdx + 1);
|
|
1333
|
+
if (modules[modulePath]) {
|
|
1334
|
+
const moduleCategories = { ...modules[modulePath] };
|
|
1335
|
+
delete moduleCategories[category];
|
|
1336
|
+
if (Object.keys(moduleCategories).length === 0) {
|
|
1337
|
+
delete modules[modulePath];
|
|
1338
|
+
} else {
|
|
1339
|
+
modules[modulePath] = moduleCategories;
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
arch.modules = modules;
|
|
1344
|
+
result.architecture = arch;
|
|
1345
|
+
}
|
|
1346
|
+
const ruleIds = contributions["security.rules"];
|
|
1347
|
+
if (ruleIds && ruleIds.length > 0 && result.security) {
|
|
1348
|
+
const security = { ...result.security };
|
|
1349
|
+
const rules = { ...security.rules };
|
|
1350
|
+
for (const id of ruleIds) {
|
|
1351
|
+
delete rules[id];
|
|
1352
|
+
}
|
|
1353
|
+
security.rules = rules;
|
|
1354
|
+
result.security = security;
|
|
1355
|
+
}
|
|
1356
|
+
return result;
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1296
1359
|
// src/shared/parsers/typescript.ts
|
|
1297
1360
|
import { parse } from "@typescript-eslint/typescript-estree";
|
|
1298
1361
|
|
|
@@ -1318,11 +1381,11 @@ function walk(node, visitor) {
|
|
|
1318
1381
|
var TypeScriptParser = class {
|
|
1319
1382
|
name = "typescript";
|
|
1320
1383
|
extensions = [".ts", ".tsx", ".mts", ".cts"];
|
|
1321
|
-
async parseFile(
|
|
1322
|
-
const contentResult = await readFileContent(
|
|
1384
|
+
async parseFile(path20) {
|
|
1385
|
+
const contentResult = await readFileContent(path20);
|
|
1323
1386
|
if (!contentResult.ok) {
|
|
1324
1387
|
return Err(
|
|
1325
|
-
createParseError("NOT_FOUND", `File not found: ${
|
|
1388
|
+
createParseError("NOT_FOUND", `File not found: ${path20}`, { path: path20 }, [
|
|
1326
1389
|
"Check that the file exists",
|
|
1327
1390
|
"Verify the path is correct"
|
|
1328
1391
|
])
|
|
@@ -1332,7 +1395,7 @@ var TypeScriptParser = class {
|
|
|
1332
1395
|
const ast = parse(contentResult.value, {
|
|
1333
1396
|
loc: true,
|
|
1334
1397
|
range: true,
|
|
1335
|
-
jsx:
|
|
1398
|
+
jsx: path20.endsWith(".tsx"),
|
|
1336
1399
|
errorOnUnknownASTType: false
|
|
1337
1400
|
});
|
|
1338
1401
|
return Ok({
|
|
@@ -1343,7 +1406,7 @@ var TypeScriptParser = class {
|
|
|
1343
1406
|
} catch (e) {
|
|
1344
1407
|
const error = e;
|
|
1345
1408
|
return Err(
|
|
1346
|
-
createParseError("SYNTAX_ERROR", `Failed to parse ${
|
|
1409
|
+
createParseError("SYNTAX_ERROR", `Failed to parse ${path20}: ${error.message}`, { path: path20 }, [
|
|
1347
1410
|
"Check for syntax errors in the file",
|
|
1348
1411
|
"Ensure valid TypeScript syntax"
|
|
1349
1412
|
])
|
|
@@ -1627,22 +1690,22 @@ function extractInlineRefs(content) {
|
|
|
1627
1690
|
}
|
|
1628
1691
|
return refs;
|
|
1629
1692
|
}
|
|
1630
|
-
async function parseDocumentationFile(
|
|
1631
|
-
const contentResult = await readFileContent(
|
|
1693
|
+
async function parseDocumentationFile(path20) {
|
|
1694
|
+
const contentResult = await readFileContent(path20);
|
|
1632
1695
|
if (!contentResult.ok) {
|
|
1633
1696
|
return Err(
|
|
1634
1697
|
createEntropyError(
|
|
1635
1698
|
"PARSE_ERROR",
|
|
1636
|
-
`Failed to read documentation file: ${
|
|
1637
|
-
{ file:
|
|
1699
|
+
`Failed to read documentation file: ${path20}`,
|
|
1700
|
+
{ file: path20 },
|
|
1638
1701
|
["Check that the file exists"]
|
|
1639
1702
|
)
|
|
1640
1703
|
);
|
|
1641
1704
|
}
|
|
1642
1705
|
const content = contentResult.value;
|
|
1643
|
-
const type =
|
|
1706
|
+
const type = path20.endsWith(".md") ? "markdown" : "text";
|
|
1644
1707
|
return Ok({
|
|
1645
|
-
path:
|
|
1708
|
+
path: path20,
|
|
1646
1709
|
type,
|
|
1647
1710
|
content,
|
|
1648
1711
|
codeBlocks: extractCodeBlocks(content),
|
|
@@ -2453,15 +2516,34 @@ async function detectPatternViolations(snapshot, config) {
|
|
|
2453
2516
|
}
|
|
2454
2517
|
}
|
|
2455
2518
|
}
|
|
2519
|
+
if (config?.customPatterns) {
|
|
2520
|
+
for (const file of snapshot.files) {
|
|
2521
|
+
for (const custom of config.customPatterns) {
|
|
2522
|
+
const matches = custom.check(file, snapshot);
|
|
2523
|
+
for (const match of matches) {
|
|
2524
|
+
violations.push({
|
|
2525
|
+
pattern: custom.name,
|
|
2526
|
+
file: file.path,
|
|
2527
|
+
line: match.line,
|
|
2528
|
+
message: match.message,
|
|
2529
|
+
suggestion: match.suggestion || "Review and fix this pattern violation",
|
|
2530
|
+
severity: custom.severity
|
|
2531
|
+
});
|
|
2532
|
+
}
|
|
2533
|
+
}
|
|
2534
|
+
}
|
|
2535
|
+
}
|
|
2456
2536
|
const errorCount = violations.filter((v) => v.severity === "error").length;
|
|
2457
2537
|
const warningCount = violations.filter((v) => v.severity === "warning").length;
|
|
2458
|
-
const
|
|
2459
|
-
const
|
|
2538
|
+
const customCount = config?.customPatterns?.length ?? 0;
|
|
2539
|
+
const allPatternsCount = patterns.length + customCount;
|
|
2540
|
+
const totalChecks = snapshot.files.length * allPatternsCount;
|
|
2541
|
+
const passRate = totalChecks > 0 ? Math.max(0, (totalChecks - violations.length) / totalChecks) : 1;
|
|
2460
2542
|
return Ok({
|
|
2461
2543
|
violations,
|
|
2462
2544
|
stats: {
|
|
2463
2545
|
filesChecked: snapshot.files.length,
|
|
2464
|
-
patternsApplied:
|
|
2546
|
+
patternsApplied: allPatternsCount,
|
|
2465
2547
|
violationCount: violations.length,
|
|
2466
2548
|
errorCount,
|
|
2467
2549
|
warningCount
|
|
@@ -4644,10 +4726,13 @@ var DEFAULT_STATE = {
|
|
|
4644
4726
|
progress: {}
|
|
4645
4727
|
};
|
|
4646
4728
|
|
|
4647
|
-
// src/state/state-
|
|
4648
|
-
import * as
|
|
4649
|
-
import * as
|
|
4650
|
-
|
|
4729
|
+
// src/state/state-persistence.ts
|
|
4730
|
+
import * as fs8 from "fs";
|
|
4731
|
+
import * as path5 from "path";
|
|
4732
|
+
|
|
4733
|
+
// src/state/state-shared.ts
|
|
4734
|
+
import * as fs7 from "fs";
|
|
4735
|
+
import * as path4 from "path";
|
|
4651
4736
|
|
|
4652
4737
|
// src/state/stream-resolver.ts
|
|
4653
4738
|
import * as fs5 from "fs";
|
|
@@ -4673,10 +4758,20 @@ var DEFAULT_STREAM_INDEX = {
|
|
|
4673
4758
|
streams: {}
|
|
4674
4759
|
};
|
|
4675
4760
|
|
|
4676
|
-
// src/state/
|
|
4761
|
+
// src/state/constants.ts
|
|
4677
4762
|
var HARNESS_DIR = ".harness";
|
|
4678
|
-
var
|
|
4763
|
+
var STATE_FILE = "state.json";
|
|
4764
|
+
var LEARNINGS_FILE = "learnings.md";
|
|
4765
|
+
var FAILURES_FILE = "failures.md";
|
|
4766
|
+
var HANDOFF_FILE = "handoff.json";
|
|
4767
|
+
var GATE_CONFIG_FILE = "gate.json";
|
|
4679
4768
|
var INDEX_FILE = "index.json";
|
|
4769
|
+
var SESSIONS_DIR = "sessions";
|
|
4770
|
+
var SESSION_INDEX_FILE = "index.md";
|
|
4771
|
+
var SUMMARY_FILE = "summary.md";
|
|
4772
|
+
|
|
4773
|
+
// src/state/stream-resolver.ts
|
|
4774
|
+
var STREAMS_DIR = "streams";
|
|
4680
4775
|
var STREAM_NAME_REGEX = /^[a-z0-9][a-z0-9._-]*$/;
|
|
4681
4776
|
function streamsDir(projectPath) {
|
|
4682
4777
|
return path2.join(projectPath, HARNESS_DIR, STREAMS_DIR);
|
|
@@ -4903,26 +4998,65 @@ async function migrateToStreams(projectPath) {
|
|
|
4903
4998
|
return saveStreamIndex(projectPath, index);
|
|
4904
4999
|
}
|
|
4905
5000
|
|
|
4906
|
-
// src/state/
|
|
4907
|
-
|
|
4908
|
-
|
|
4909
|
-
|
|
4910
|
-
|
|
4911
|
-
|
|
4912
|
-
|
|
4913
|
-
|
|
5001
|
+
// src/state/session-resolver.ts
|
|
5002
|
+
import * as fs6 from "fs";
|
|
5003
|
+
import * as path3 from "path";
|
|
5004
|
+
function resolveSessionDir(projectPath, sessionSlug, options) {
|
|
5005
|
+
if (!sessionSlug || sessionSlug.trim() === "") {
|
|
5006
|
+
return Err(new Error("Session slug must not be empty"));
|
|
5007
|
+
}
|
|
5008
|
+
if (sessionSlug.includes("..") || sessionSlug.includes("/") || sessionSlug.includes("\\")) {
|
|
5009
|
+
return Err(
|
|
5010
|
+
new Error(`Invalid session slug '${sessionSlug}': must not contain path traversal characters`)
|
|
5011
|
+
);
|
|
5012
|
+
}
|
|
5013
|
+
const sessionDir = path3.join(projectPath, HARNESS_DIR, SESSIONS_DIR, sessionSlug);
|
|
5014
|
+
if (options?.create) {
|
|
5015
|
+
fs6.mkdirSync(sessionDir, { recursive: true });
|
|
5016
|
+
}
|
|
5017
|
+
return Ok(sessionDir);
|
|
5018
|
+
}
|
|
5019
|
+
function updateSessionIndex(projectPath, sessionSlug, description) {
|
|
5020
|
+
const sessionsDir = path3.join(projectPath, HARNESS_DIR, SESSIONS_DIR);
|
|
5021
|
+
fs6.mkdirSync(sessionsDir, { recursive: true });
|
|
5022
|
+
const indexPath2 = path3.join(sessionsDir, SESSION_INDEX_FILE);
|
|
5023
|
+
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
5024
|
+
const newLine = `- [${sessionSlug}](${sessionSlug}/summary.md) \u2014 ${description} (${date})`;
|
|
5025
|
+
if (!fs6.existsSync(indexPath2)) {
|
|
5026
|
+
fs6.writeFileSync(indexPath2, `## Active Sessions
|
|
5027
|
+
|
|
5028
|
+
${newLine}
|
|
5029
|
+
`);
|
|
5030
|
+
return;
|
|
5031
|
+
}
|
|
5032
|
+
const content = fs6.readFileSync(indexPath2, "utf-8");
|
|
5033
|
+
const lines = content.split("\n");
|
|
5034
|
+
const slugPattern = `- [${sessionSlug}]`;
|
|
5035
|
+
const existingIdx = lines.findIndex((l) => l.startsWith(slugPattern));
|
|
5036
|
+
if (existingIdx >= 0) {
|
|
5037
|
+
lines[existingIdx] = newLine;
|
|
5038
|
+
} else {
|
|
5039
|
+
const lastNonEmpty = lines.reduce((last, line, i) => line.trim() !== "" ? i : last, 0);
|
|
5040
|
+
lines.splice(lastNonEmpty + 1, 0, newLine);
|
|
5041
|
+
}
|
|
5042
|
+
fs6.writeFileSync(indexPath2, lines.join("\n"));
|
|
5043
|
+
}
|
|
5044
|
+
|
|
5045
|
+
// src/state/state-shared.ts
|
|
4914
5046
|
var MAX_CACHE_ENTRIES = 8;
|
|
4915
|
-
var learningsCacheMap = /* @__PURE__ */ new Map();
|
|
4916
|
-
var failuresCacheMap = /* @__PURE__ */ new Map();
|
|
4917
5047
|
function evictIfNeeded(map) {
|
|
4918
5048
|
if (map.size > MAX_CACHE_ENTRIES) {
|
|
4919
5049
|
const oldest = map.keys().next().value;
|
|
4920
5050
|
if (oldest !== void 0) map.delete(oldest);
|
|
4921
5051
|
}
|
|
4922
5052
|
}
|
|
4923
|
-
async function getStateDir(projectPath, stream) {
|
|
4924
|
-
|
|
4925
|
-
|
|
5053
|
+
async function getStateDir(projectPath, stream, session) {
|
|
5054
|
+
if (session) {
|
|
5055
|
+
const sessionResult = resolveSessionDir(projectPath, session, { create: true });
|
|
5056
|
+
return sessionResult;
|
|
5057
|
+
}
|
|
5058
|
+
const streamsIndexPath = path4.join(projectPath, HARNESS_DIR, "streams", INDEX_FILE);
|
|
5059
|
+
const hasStreams = fs7.existsSync(streamsIndexPath);
|
|
4926
5060
|
if (stream || hasStreams) {
|
|
4927
5061
|
const result = await resolveStreamPath(projectPath, stream ? { stream } : void 0);
|
|
4928
5062
|
if (result.ok) {
|
|
@@ -4932,18 +5066,20 @@ async function getStateDir(projectPath, stream) {
|
|
|
4932
5066
|
return result;
|
|
4933
5067
|
}
|
|
4934
5068
|
}
|
|
4935
|
-
return Ok(
|
|
5069
|
+
return Ok(path4.join(projectPath, HARNESS_DIR));
|
|
4936
5070
|
}
|
|
4937
|
-
|
|
5071
|
+
|
|
5072
|
+
// src/state/state-persistence.ts
|
|
5073
|
+
async function loadState(projectPath, stream, session) {
|
|
4938
5074
|
try {
|
|
4939
|
-
const dirResult = await getStateDir(projectPath, stream);
|
|
5075
|
+
const dirResult = await getStateDir(projectPath, stream, session);
|
|
4940
5076
|
if (!dirResult.ok) return dirResult;
|
|
4941
5077
|
const stateDir = dirResult.value;
|
|
4942
|
-
const statePath =
|
|
4943
|
-
if (!
|
|
5078
|
+
const statePath = path5.join(stateDir, STATE_FILE);
|
|
5079
|
+
if (!fs8.existsSync(statePath)) {
|
|
4944
5080
|
return Ok({ ...DEFAULT_STATE });
|
|
4945
5081
|
}
|
|
4946
|
-
const raw =
|
|
5082
|
+
const raw = fs8.readFileSync(statePath, "utf-8");
|
|
4947
5083
|
const parsed = JSON.parse(raw);
|
|
4948
5084
|
const result = HarnessStateSchema.safeParse(parsed);
|
|
4949
5085
|
if (!result.success) {
|
|
@@ -4956,14 +5092,14 @@ async function loadState(projectPath, stream) {
|
|
|
4956
5092
|
);
|
|
4957
5093
|
}
|
|
4958
5094
|
}
|
|
4959
|
-
async function saveState(projectPath, state, stream) {
|
|
5095
|
+
async function saveState(projectPath, state, stream, session) {
|
|
4960
5096
|
try {
|
|
4961
|
-
const dirResult = await getStateDir(projectPath, stream);
|
|
5097
|
+
const dirResult = await getStateDir(projectPath, stream, session);
|
|
4962
5098
|
if (!dirResult.ok) return dirResult;
|
|
4963
5099
|
const stateDir = dirResult.value;
|
|
4964
|
-
const statePath =
|
|
4965
|
-
|
|
4966
|
-
|
|
5100
|
+
const statePath = path5.join(stateDir, STATE_FILE);
|
|
5101
|
+
fs8.mkdirSync(stateDir, { recursive: true });
|
|
5102
|
+
fs8.writeFileSync(statePath, JSON.stringify(state, null, 2));
|
|
4967
5103
|
return Ok(void 0);
|
|
4968
5104
|
} catch (error) {
|
|
4969
5105
|
return Err(
|
|
@@ -4971,13 +5107,21 @@ async function saveState(projectPath, state, stream) {
|
|
|
4971
5107
|
);
|
|
4972
5108
|
}
|
|
4973
5109
|
}
|
|
4974
|
-
|
|
5110
|
+
|
|
5111
|
+
// src/state/learnings.ts
|
|
5112
|
+
import * as fs9 from "fs";
|
|
5113
|
+
import * as path6 from "path";
|
|
5114
|
+
var learningsCacheMap = /* @__PURE__ */ new Map();
|
|
5115
|
+
function clearLearningsCache() {
|
|
5116
|
+
learningsCacheMap.clear();
|
|
5117
|
+
}
|
|
5118
|
+
async function appendLearning(projectPath, learning, skillName, outcome, stream, session) {
|
|
4975
5119
|
try {
|
|
4976
|
-
const dirResult = await getStateDir(projectPath, stream);
|
|
5120
|
+
const dirResult = await getStateDir(projectPath, stream, session);
|
|
4977
5121
|
if (!dirResult.ok) return dirResult;
|
|
4978
5122
|
const stateDir = dirResult.value;
|
|
4979
|
-
const learningsPath =
|
|
4980
|
-
|
|
5123
|
+
const learningsPath = path6.join(stateDir, LEARNINGS_FILE);
|
|
5124
|
+
fs9.mkdirSync(stateDir, { recursive: true });
|
|
4981
5125
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
4982
5126
|
let entry;
|
|
4983
5127
|
if (skillName && outcome) {
|
|
@@ -4993,11 +5137,11 @@ async function appendLearning(projectPath, learning, skillName, outcome, stream)
|
|
|
4993
5137
|
- **${timestamp}:** ${learning}
|
|
4994
5138
|
`;
|
|
4995
5139
|
}
|
|
4996
|
-
if (!
|
|
4997
|
-
|
|
5140
|
+
if (!fs9.existsSync(learningsPath)) {
|
|
5141
|
+
fs9.writeFileSync(learningsPath, `# Learnings
|
|
4998
5142
|
${entry}`);
|
|
4999
5143
|
} else {
|
|
5000
|
-
|
|
5144
|
+
fs9.appendFileSync(learningsPath, entry);
|
|
5001
5145
|
}
|
|
5002
5146
|
learningsCacheMap.delete(learningsPath);
|
|
5003
5147
|
return Ok(void 0);
|
|
@@ -5009,23 +5153,92 @@ ${entry}`);
|
|
|
5009
5153
|
);
|
|
5010
5154
|
}
|
|
5011
5155
|
}
|
|
5012
|
-
|
|
5156
|
+
function estimateTokens(text) {
|
|
5157
|
+
return Math.ceil(text.length / 4);
|
|
5158
|
+
}
|
|
5159
|
+
function scoreRelevance(entry, intent) {
|
|
5160
|
+
if (!intent || intent.trim() === "") return 0;
|
|
5161
|
+
const intentWords = intent.toLowerCase().split(/\s+/).filter((w) => w.length > 2);
|
|
5162
|
+
if (intentWords.length === 0) return 0;
|
|
5163
|
+
const entryLower = entry.toLowerCase();
|
|
5164
|
+
const matches = intentWords.filter((word) => entryLower.includes(word));
|
|
5165
|
+
return matches.length / intentWords.length;
|
|
5166
|
+
}
|
|
5167
|
+
function parseDateFromEntry(entry) {
|
|
5168
|
+
const match = entry.match(/(\d{4}-\d{2}-\d{2})/);
|
|
5169
|
+
return match ? match[1] ?? null : null;
|
|
5170
|
+
}
|
|
5171
|
+
function analyzeLearningPatterns(entries) {
|
|
5172
|
+
const tagGroups = /* @__PURE__ */ new Map();
|
|
5173
|
+
for (const entry of entries) {
|
|
5174
|
+
const tagMatches = entry.matchAll(/\[(skill:[^\]]+)\]|\[(outcome:[^\]]+)\]/g);
|
|
5175
|
+
for (const match of tagMatches) {
|
|
5176
|
+
const tag = match[1] ?? match[2];
|
|
5177
|
+
if (tag) {
|
|
5178
|
+
const group = tagGroups.get(tag) ?? [];
|
|
5179
|
+
group.push(entry);
|
|
5180
|
+
tagGroups.set(tag, group);
|
|
5181
|
+
}
|
|
5182
|
+
}
|
|
5183
|
+
}
|
|
5184
|
+
const patterns = [];
|
|
5185
|
+
for (const [tag, groupEntries] of tagGroups) {
|
|
5186
|
+
if (groupEntries.length >= 3) {
|
|
5187
|
+
patterns.push({ tag, count: groupEntries.length, entries: groupEntries });
|
|
5188
|
+
}
|
|
5189
|
+
}
|
|
5190
|
+
return patterns.sort((a, b) => b.count - a.count);
|
|
5191
|
+
}
|
|
5192
|
+
async function loadBudgetedLearnings(projectPath, options) {
|
|
5193
|
+
const { intent, tokenBudget = 1e3, skill, session, stream } = options;
|
|
5194
|
+
const sortByRecencyAndRelevance = (entries) => {
|
|
5195
|
+
return [...entries].sort((a, b) => {
|
|
5196
|
+
const dateA = parseDateFromEntry(a) ?? "0000-00-00";
|
|
5197
|
+
const dateB = parseDateFromEntry(b) ?? "0000-00-00";
|
|
5198
|
+
const dateCompare = dateB.localeCompare(dateA);
|
|
5199
|
+
if (dateCompare !== 0) return dateCompare;
|
|
5200
|
+
return scoreRelevance(b, intent) - scoreRelevance(a, intent);
|
|
5201
|
+
});
|
|
5202
|
+
};
|
|
5203
|
+
const allEntries = [];
|
|
5204
|
+
if (session) {
|
|
5205
|
+
const sessionResult = await loadRelevantLearnings(projectPath, skill, stream, session);
|
|
5206
|
+
if (sessionResult.ok) {
|
|
5207
|
+
allEntries.push(...sortByRecencyAndRelevance(sessionResult.value));
|
|
5208
|
+
}
|
|
5209
|
+
}
|
|
5210
|
+
const globalResult = await loadRelevantLearnings(projectPath, skill, stream);
|
|
5211
|
+
if (globalResult.ok) {
|
|
5212
|
+
allEntries.push(...sortByRecencyAndRelevance(globalResult.value));
|
|
5213
|
+
}
|
|
5214
|
+
const budgeted = [];
|
|
5215
|
+
let totalTokens = 0;
|
|
5216
|
+
for (const entry of allEntries) {
|
|
5217
|
+
const separator = budgeted.length > 0 ? "\n" : "";
|
|
5218
|
+
const entryCost = estimateTokens(entry + separator);
|
|
5219
|
+
if (totalTokens + entryCost > tokenBudget) break;
|
|
5220
|
+
budgeted.push(entry);
|
|
5221
|
+
totalTokens += entryCost;
|
|
5222
|
+
}
|
|
5223
|
+
return Ok(budgeted);
|
|
5224
|
+
}
|
|
5225
|
+
async function loadRelevantLearnings(projectPath, skillName, stream, session) {
|
|
5013
5226
|
try {
|
|
5014
|
-
const dirResult = await getStateDir(projectPath, stream);
|
|
5227
|
+
const dirResult = await getStateDir(projectPath, stream, session);
|
|
5015
5228
|
if (!dirResult.ok) return dirResult;
|
|
5016
5229
|
const stateDir = dirResult.value;
|
|
5017
|
-
const learningsPath =
|
|
5018
|
-
if (!
|
|
5230
|
+
const learningsPath = path6.join(stateDir, LEARNINGS_FILE);
|
|
5231
|
+
if (!fs9.existsSync(learningsPath)) {
|
|
5019
5232
|
return Ok([]);
|
|
5020
5233
|
}
|
|
5021
|
-
const stats =
|
|
5234
|
+
const stats = fs9.statSync(learningsPath);
|
|
5022
5235
|
const cacheKey = learningsPath;
|
|
5023
5236
|
const cached = learningsCacheMap.get(cacheKey);
|
|
5024
5237
|
let entries;
|
|
5025
5238
|
if (cached && cached.mtimeMs === stats.mtimeMs) {
|
|
5026
5239
|
entries = cached.entries;
|
|
5027
5240
|
} else {
|
|
5028
|
-
const content =
|
|
5241
|
+
const content = fs9.readFileSync(learningsPath, "utf-8");
|
|
5029
5242
|
const lines = content.split("\n");
|
|
5030
5243
|
entries = [];
|
|
5031
5244
|
let currentBlock = [];
|
|
@@ -5061,23 +5274,110 @@ async function loadRelevantLearnings(projectPath, skillName, stream) {
|
|
|
5061
5274
|
);
|
|
5062
5275
|
}
|
|
5063
5276
|
}
|
|
5064
|
-
|
|
5065
|
-
async function appendFailure(projectPath, description, skillName, type, stream) {
|
|
5277
|
+
async function archiveLearnings(projectPath, entries, stream) {
|
|
5066
5278
|
try {
|
|
5067
5279
|
const dirResult = await getStateDir(projectPath, stream);
|
|
5068
5280
|
if (!dirResult.ok) return dirResult;
|
|
5069
5281
|
const stateDir = dirResult.value;
|
|
5070
|
-
const
|
|
5071
|
-
|
|
5282
|
+
const archiveDir = path6.join(stateDir, "learnings-archive");
|
|
5283
|
+
fs9.mkdirSync(archiveDir, { recursive: true });
|
|
5284
|
+
const now = /* @__PURE__ */ new Date();
|
|
5285
|
+
const yearMonth = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`;
|
|
5286
|
+
const archivePath = path6.join(archiveDir, `${yearMonth}.md`);
|
|
5287
|
+
const archiveContent = entries.join("\n\n") + "\n";
|
|
5288
|
+
if (fs9.existsSync(archivePath)) {
|
|
5289
|
+
fs9.appendFileSync(archivePath, "\n" + archiveContent);
|
|
5290
|
+
} else {
|
|
5291
|
+
fs9.writeFileSync(archivePath, `# Learnings Archive
|
|
5292
|
+
|
|
5293
|
+
${archiveContent}`);
|
|
5294
|
+
}
|
|
5295
|
+
return Ok(void 0);
|
|
5296
|
+
} catch (error) {
|
|
5297
|
+
return Err(
|
|
5298
|
+
new Error(
|
|
5299
|
+
`Failed to archive learnings: ${error instanceof Error ? error.message : String(error)}`
|
|
5300
|
+
)
|
|
5301
|
+
);
|
|
5302
|
+
}
|
|
5303
|
+
}
|
|
5304
|
+
async function pruneLearnings(projectPath, stream) {
|
|
5305
|
+
try {
|
|
5306
|
+
const dirResult = await getStateDir(projectPath, stream);
|
|
5307
|
+
if (!dirResult.ok) return dirResult;
|
|
5308
|
+
const stateDir = dirResult.value;
|
|
5309
|
+
const learningsPath = path6.join(stateDir, LEARNINGS_FILE);
|
|
5310
|
+
if (!fs9.existsSync(learningsPath)) {
|
|
5311
|
+
return Ok({ kept: 0, archived: 0, patterns: [] });
|
|
5312
|
+
}
|
|
5313
|
+
const loadResult = await loadRelevantLearnings(projectPath, void 0, stream);
|
|
5314
|
+
if (!loadResult.ok) return loadResult;
|
|
5315
|
+
const allEntries = loadResult.value;
|
|
5316
|
+
if (allEntries.length <= 20) {
|
|
5317
|
+
const cutoffDate = /* @__PURE__ */ new Date();
|
|
5318
|
+
cutoffDate.setDate(cutoffDate.getDate() - 14);
|
|
5319
|
+
const cutoffStr = cutoffDate.toISOString().split("T")[0];
|
|
5320
|
+
const hasOld = allEntries.some((entry) => {
|
|
5321
|
+
const date = parseDateFromEntry(entry);
|
|
5322
|
+
return date !== null && date < cutoffStr;
|
|
5323
|
+
});
|
|
5324
|
+
if (!hasOld) {
|
|
5325
|
+
return Ok({ kept: allEntries.length, archived: 0, patterns: [] });
|
|
5326
|
+
}
|
|
5327
|
+
}
|
|
5328
|
+
const sorted = [...allEntries].sort((a, b) => {
|
|
5329
|
+
const dateA = parseDateFromEntry(a) ?? "0000-00-00";
|
|
5330
|
+
const dateB = parseDateFromEntry(b) ?? "0000-00-00";
|
|
5331
|
+
return dateB.localeCompare(dateA);
|
|
5332
|
+
});
|
|
5333
|
+
const toKeep = sorted.slice(0, 20);
|
|
5334
|
+
const toArchive = sorted.slice(20);
|
|
5335
|
+
const patterns = analyzeLearningPatterns(allEntries);
|
|
5336
|
+
if (toArchive.length > 0) {
|
|
5337
|
+
const archiveResult = await archiveLearnings(projectPath, toArchive, stream);
|
|
5338
|
+
if (!archiveResult.ok) return archiveResult;
|
|
5339
|
+
}
|
|
5340
|
+
const newContent = "# Learnings\n\n" + toKeep.join("\n\n") + "\n";
|
|
5341
|
+
fs9.writeFileSync(learningsPath, newContent);
|
|
5342
|
+
learningsCacheMap.delete(learningsPath);
|
|
5343
|
+
return Ok({
|
|
5344
|
+
kept: toKeep.length,
|
|
5345
|
+
archived: toArchive.length,
|
|
5346
|
+
patterns
|
|
5347
|
+
});
|
|
5348
|
+
} catch (error) {
|
|
5349
|
+
return Err(
|
|
5350
|
+
new Error(
|
|
5351
|
+
`Failed to prune learnings: ${error instanceof Error ? error.message : String(error)}`
|
|
5352
|
+
)
|
|
5353
|
+
);
|
|
5354
|
+
}
|
|
5355
|
+
}
|
|
5356
|
+
|
|
5357
|
+
// src/state/failures.ts
|
|
5358
|
+
import * as fs10 from "fs";
|
|
5359
|
+
import * as path7 from "path";
|
|
5360
|
+
var failuresCacheMap = /* @__PURE__ */ new Map();
|
|
5361
|
+
function clearFailuresCache() {
|
|
5362
|
+
failuresCacheMap.clear();
|
|
5363
|
+
}
|
|
5364
|
+
var FAILURE_LINE_REGEX = /^- \*\*(\d{4}-\d{2}-\d{2}) \[skill:([^\]]+)\] \[type:([^\]]+)\]:\*\* (.+)$/;
|
|
5365
|
+
async function appendFailure(projectPath, description, skillName, type, stream, session) {
|
|
5366
|
+
try {
|
|
5367
|
+
const dirResult = await getStateDir(projectPath, stream, session);
|
|
5368
|
+
if (!dirResult.ok) return dirResult;
|
|
5369
|
+
const stateDir = dirResult.value;
|
|
5370
|
+
const failuresPath = path7.join(stateDir, FAILURES_FILE);
|
|
5371
|
+
fs10.mkdirSync(stateDir, { recursive: true });
|
|
5072
5372
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
5073
5373
|
const entry = `
|
|
5074
5374
|
- **${timestamp} [skill:${skillName}] [type:${type}]:** ${description}
|
|
5075
5375
|
`;
|
|
5076
|
-
if (!
|
|
5077
|
-
|
|
5376
|
+
if (!fs10.existsSync(failuresPath)) {
|
|
5377
|
+
fs10.writeFileSync(failuresPath, `# Failures
|
|
5078
5378
|
${entry}`);
|
|
5079
5379
|
} else {
|
|
5080
|
-
|
|
5380
|
+
fs10.appendFileSync(failuresPath, entry);
|
|
5081
5381
|
}
|
|
5082
5382
|
failuresCacheMap.delete(failuresPath);
|
|
5083
5383
|
return Ok(void 0);
|
|
@@ -5089,22 +5389,22 @@ ${entry}`);
|
|
|
5089
5389
|
);
|
|
5090
5390
|
}
|
|
5091
5391
|
}
|
|
5092
|
-
async function loadFailures(projectPath, stream) {
|
|
5392
|
+
async function loadFailures(projectPath, stream, session) {
|
|
5093
5393
|
try {
|
|
5094
|
-
const dirResult = await getStateDir(projectPath, stream);
|
|
5394
|
+
const dirResult = await getStateDir(projectPath, stream, session);
|
|
5095
5395
|
if (!dirResult.ok) return dirResult;
|
|
5096
5396
|
const stateDir = dirResult.value;
|
|
5097
|
-
const failuresPath =
|
|
5098
|
-
if (!
|
|
5397
|
+
const failuresPath = path7.join(stateDir, FAILURES_FILE);
|
|
5398
|
+
if (!fs10.existsSync(failuresPath)) {
|
|
5099
5399
|
return Ok([]);
|
|
5100
5400
|
}
|
|
5101
|
-
const stats =
|
|
5401
|
+
const stats = fs10.statSync(failuresPath);
|
|
5102
5402
|
const cacheKey = failuresPath;
|
|
5103
5403
|
const cached = failuresCacheMap.get(cacheKey);
|
|
5104
5404
|
if (cached && cached.mtimeMs === stats.mtimeMs) {
|
|
5105
5405
|
return Ok(cached.entries);
|
|
5106
5406
|
}
|
|
5107
|
-
const content =
|
|
5407
|
+
const content = fs10.readFileSync(failuresPath, "utf-8");
|
|
5108
5408
|
const entries = [];
|
|
5109
5409
|
for (const line of content.split("\n")) {
|
|
5110
5410
|
const match = line.match(FAILURE_LINE_REGEX);
|
|
@@ -5128,25 +5428,25 @@ async function loadFailures(projectPath, stream) {
|
|
|
5128
5428
|
);
|
|
5129
5429
|
}
|
|
5130
5430
|
}
|
|
5131
|
-
async function archiveFailures(projectPath, stream) {
|
|
5431
|
+
async function archiveFailures(projectPath, stream, session) {
|
|
5132
5432
|
try {
|
|
5133
|
-
const dirResult = await getStateDir(projectPath, stream);
|
|
5433
|
+
const dirResult = await getStateDir(projectPath, stream, session);
|
|
5134
5434
|
if (!dirResult.ok) return dirResult;
|
|
5135
5435
|
const stateDir = dirResult.value;
|
|
5136
|
-
const failuresPath =
|
|
5137
|
-
if (!
|
|
5436
|
+
const failuresPath = path7.join(stateDir, FAILURES_FILE);
|
|
5437
|
+
if (!fs10.existsSync(failuresPath)) {
|
|
5138
5438
|
return Ok(void 0);
|
|
5139
5439
|
}
|
|
5140
|
-
const archiveDir =
|
|
5141
|
-
|
|
5440
|
+
const archiveDir = path7.join(stateDir, "archive");
|
|
5441
|
+
fs10.mkdirSync(archiveDir, { recursive: true });
|
|
5142
5442
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
5143
5443
|
let archiveName = `failures-${date}.md`;
|
|
5144
5444
|
let counter = 2;
|
|
5145
|
-
while (
|
|
5445
|
+
while (fs10.existsSync(path7.join(archiveDir, archiveName))) {
|
|
5146
5446
|
archiveName = `failures-${date}-${counter}.md`;
|
|
5147
5447
|
counter++;
|
|
5148
5448
|
}
|
|
5149
|
-
|
|
5449
|
+
fs10.renameSync(failuresPath, path7.join(archiveDir, archiveName));
|
|
5150
5450
|
failuresCacheMap.delete(failuresPath);
|
|
5151
5451
|
return Ok(void 0);
|
|
5152
5452
|
} catch (error) {
|
|
@@ -5157,14 +5457,18 @@ async function archiveFailures(projectPath, stream) {
|
|
|
5157
5457
|
);
|
|
5158
5458
|
}
|
|
5159
5459
|
}
|
|
5160
|
-
|
|
5460
|
+
|
|
5461
|
+
// src/state/handoff.ts
|
|
5462
|
+
import * as fs11 from "fs";
|
|
5463
|
+
import * as path8 from "path";
|
|
5464
|
+
async function saveHandoff(projectPath, handoff, stream, session) {
|
|
5161
5465
|
try {
|
|
5162
|
-
const dirResult = await getStateDir(projectPath, stream);
|
|
5466
|
+
const dirResult = await getStateDir(projectPath, stream, session);
|
|
5163
5467
|
if (!dirResult.ok) return dirResult;
|
|
5164
5468
|
const stateDir = dirResult.value;
|
|
5165
|
-
const handoffPath =
|
|
5166
|
-
|
|
5167
|
-
|
|
5469
|
+
const handoffPath = path8.join(stateDir, HANDOFF_FILE);
|
|
5470
|
+
fs11.mkdirSync(stateDir, { recursive: true });
|
|
5471
|
+
fs11.writeFileSync(handoffPath, JSON.stringify(handoff, null, 2));
|
|
5168
5472
|
return Ok(void 0);
|
|
5169
5473
|
} catch (error) {
|
|
5170
5474
|
return Err(
|
|
@@ -5172,16 +5476,16 @@ async function saveHandoff(projectPath, handoff, stream) {
|
|
|
5172
5476
|
);
|
|
5173
5477
|
}
|
|
5174
5478
|
}
|
|
5175
|
-
async function loadHandoff(projectPath, stream) {
|
|
5479
|
+
async function loadHandoff(projectPath, stream, session) {
|
|
5176
5480
|
try {
|
|
5177
|
-
const dirResult = await getStateDir(projectPath, stream);
|
|
5481
|
+
const dirResult = await getStateDir(projectPath, stream, session);
|
|
5178
5482
|
if (!dirResult.ok) return dirResult;
|
|
5179
5483
|
const stateDir = dirResult.value;
|
|
5180
|
-
const handoffPath =
|
|
5181
|
-
if (!
|
|
5484
|
+
const handoffPath = path8.join(stateDir, HANDOFF_FILE);
|
|
5485
|
+
if (!fs11.existsSync(handoffPath)) {
|
|
5182
5486
|
return Ok(null);
|
|
5183
5487
|
}
|
|
5184
|
-
const raw =
|
|
5488
|
+
const raw = fs11.readFileSync(handoffPath, "utf-8");
|
|
5185
5489
|
const parsed = JSON.parse(raw);
|
|
5186
5490
|
const result = HandoffSchema.safeParse(parsed);
|
|
5187
5491
|
if (!result.success) {
|
|
@@ -5194,73 +5498,82 @@ async function loadHandoff(projectPath, stream) {
|
|
|
5194
5498
|
);
|
|
5195
5499
|
}
|
|
5196
5500
|
}
|
|
5501
|
+
|
|
5502
|
+
// src/state/mechanical-gate.ts
|
|
5503
|
+
import * as fs12 from "fs";
|
|
5504
|
+
import * as path9 from "path";
|
|
5505
|
+
import { execSync as execSync2 } from "child_process";
|
|
5506
|
+
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:.-]+$/;
|
|
5507
|
+
function loadChecksFromConfig(gateConfigPath) {
|
|
5508
|
+
if (!fs12.existsSync(gateConfigPath)) return [];
|
|
5509
|
+
const raw = JSON.parse(fs12.readFileSync(gateConfigPath, "utf-8"));
|
|
5510
|
+
const config = GateConfigSchema.safeParse(raw);
|
|
5511
|
+
if (config.success && config.data.checks) return config.data.checks;
|
|
5512
|
+
return [];
|
|
5513
|
+
}
|
|
5514
|
+
function discoverChecksFromProject(projectPath) {
|
|
5515
|
+
const checks = [];
|
|
5516
|
+
const packageJsonPath = path9.join(projectPath, "package.json");
|
|
5517
|
+
if (fs12.existsSync(packageJsonPath)) {
|
|
5518
|
+
const pkg = JSON.parse(fs12.readFileSync(packageJsonPath, "utf-8"));
|
|
5519
|
+
const scripts = pkg.scripts || {};
|
|
5520
|
+
if (scripts.test) checks.push({ name: "test", command: "npm test" });
|
|
5521
|
+
if (scripts.lint) checks.push({ name: "lint", command: "npm run lint" });
|
|
5522
|
+
if (scripts.typecheck) checks.push({ name: "typecheck", command: "npm run typecheck" });
|
|
5523
|
+
if (scripts.build) checks.push({ name: "build", command: "npm run build" });
|
|
5524
|
+
}
|
|
5525
|
+
if (fs12.existsSync(path9.join(projectPath, "go.mod"))) {
|
|
5526
|
+
checks.push({ name: "test", command: "go test ./..." });
|
|
5527
|
+
checks.push({ name: "build", command: "go build ./..." });
|
|
5528
|
+
}
|
|
5529
|
+
if (fs12.existsSync(path9.join(projectPath, "pyproject.toml")) || fs12.existsSync(path9.join(projectPath, "setup.py"))) {
|
|
5530
|
+
checks.push({ name: "test", command: "python -m pytest" });
|
|
5531
|
+
}
|
|
5532
|
+
return checks;
|
|
5533
|
+
}
|
|
5534
|
+
function executeCheck(check, projectPath) {
|
|
5535
|
+
if (!SAFE_GATE_COMMAND.test(check.command)) {
|
|
5536
|
+
return {
|
|
5537
|
+
name: check.name,
|
|
5538
|
+
passed: false,
|
|
5539
|
+
command: check.command,
|
|
5540
|
+
output: `Blocked: command does not match safe gate pattern. Allowed prefixes: npm, pnpm, yarn, go, python, python3, make, cargo, gradle, mvn`,
|
|
5541
|
+
duration: 0
|
|
5542
|
+
};
|
|
5543
|
+
}
|
|
5544
|
+
const start = Date.now();
|
|
5545
|
+
try {
|
|
5546
|
+
execSync2(check.command, {
|
|
5547
|
+
cwd: projectPath,
|
|
5548
|
+
stdio: "pipe",
|
|
5549
|
+
timeout: 12e4
|
|
5550
|
+
});
|
|
5551
|
+
return {
|
|
5552
|
+
name: check.name,
|
|
5553
|
+
passed: true,
|
|
5554
|
+
command: check.command,
|
|
5555
|
+
duration: Date.now() - start
|
|
5556
|
+
};
|
|
5557
|
+
} catch (error) {
|
|
5558
|
+
const output = error instanceof Error ? error.stderr?.toString() || error.message : String(error);
|
|
5559
|
+
return {
|
|
5560
|
+
name: check.name,
|
|
5561
|
+
passed: false,
|
|
5562
|
+
command: check.command,
|
|
5563
|
+
output: output.slice(0, 2e3),
|
|
5564
|
+
duration: Date.now() - start
|
|
5565
|
+
};
|
|
5566
|
+
}
|
|
5567
|
+
}
|
|
5197
5568
|
async function runMechanicalGate(projectPath) {
|
|
5198
|
-
const harnessDir =
|
|
5199
|
-
const gateConfigPath =
|
|
5569
|
+
const harnessDir = path9.join(projectPath, HARNESS_DIR);
|
|
5570
|
+
const gateConfigPath = path9.join(harnessDir, GATE_CONFIG_FILE);
|
|
5200
5571
|
try {
|
|
5201
|
-
let checks =
|
|
5202
|
-
if (fs6.existsSync(gateConfigPath)) {
|
|
5203
|
-
const raw = JSON.parse(fs6.readFileSync(gateConfigPath, "utf-8"));
|
|
5204
|
-
const config = GateConfigSchema.safeParse(raw);
|
|
5205
|
-
if (config.success && config.data.checks) {
|
|
5206
|
-
checks = config.data.checks;
|
|
5207
|
-
}
|
|
5208
|
-
}
|
|
5572
|
+
let checks = loadChecksFromConfig(gateConfigPath);
|
|
5209
5573
|
if (checks.length === 0) {
|
|
5210
|
-
|
|
5211
|
-
if (fs6.existsSync(packageJsonPath)) {
|
|
5212
|
-
const pkg = JSON.parse(fs6.readFileSync(packageJsonPath, "utf-8"));
|
|
5213
|
-
const scripts = pkg.scripts || {};
|
|
5214
|
-
if (scripts.test) checks.push({ name: "test", command: "npm test" });
|
|
5215
|
-
if (scripts.lint) checks.push({ name: "lint", command: "npm run lint" });
|
|
5216
|
-
if (scripts.typecheck) checks.push({ name: "typecheck", command: "npm run typecheck" });
|
|
5217
|
-
if (scripts.build) checks.push({ name: "build", command: "npm run build" });
|
|
5218
|
-
}
|
|
5219
|
-
if (fs6.existsSync(path3.join(projectPath, "go.mod"))) {
|
|
5220
|
-
checks.push({ name: "test", command: "go test ./..." });
|
|
5221
|
-
checks.push({ name: "build", command: "go build ./..." });
|
|
5222
|
-
}
|
|
5223
|
-
if (fs6.existsSync(path3.join(projectPath, "pyproject.toml")) || fs6.existsSync(path3.join(projectPath, "setup.py"))) {
|
|
5224
|
-
checks.push({ name: "test", command: "python -m pytest" });
|
|
5225
|
-
}
|
|
5226
|
-
}
|
|
5227
|
-
const results = [];
|
|
5228
|
-
const 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:.-]+$/;
|
|
5229
|
-
for (const check of checks) {
|
|
5230
|
-
if (!SAFE_GATE_COMMAND.test(check.command)) {
|
|
5231
|
-
results.push({
|
|
5232
|
-
name: check.name,
|
|
5233
|
-
passed: false,
|
|
5234
|
-
command: check.command,
|
|
5235
|
-
output: `Blocked: command does not match safe gate pattern. Allowed prefixes: npm, npx, pnpm, yarn, go, python, python3, make, cargo, gradle, mvn`,
|
|
5236
|
-
duration: 0
|
|
5237
|
-
});
|
|
5238
|
-
continue;
|
|
5239
|
-
}
|
|
5240
|
-
const start = Date.now();
|
|
5241
|
-
try {
|
|
5242
|
-
execSync2(check.command, {
|
|
5243
|
-
cwd: projectPath,
|
|
5244
|
-
stdio: "pipe",
|
|
5245
|
-
timeout: 12e4
|
|
5246
|
-
});
|
|
5247
|
-
results.push({
|
|
5248
|
-
name: check.name,
|
|
5249
|
-
passed: true,
|
|
5250
|
-
command: check.command,
|
|
5251
|
-
duration: Date.now() - start
|
|
5252
|
-
});
|
|
5253
|
-
} catch (error) {
|
|
5254
|
-
const output = error instanceof Error ? error.stderr?.toString() || error.message : String(error);
|
|
5255
|
-
results.push({
|
|
5256
|
-
name: check.name,
|
|
5257
|
-
passed: false,
|
|
5258
|
-
command: check.command,
|
|
5259
|
-
output: output.slice(0, 2e3),
|
|
5260
|
-
duration: Date.now() - start
|
|
5261
|
-
});
|
|
5262
|
-
}
|
|
5574
|
+
checks = discoverChecksFromProject(projectPath);
|
|
5263
5575
|
}
|
|
5576
|
+
const results = checks.map((check) => executeCheck(check, projectPath));
|
|
5264
5577
|
return Ok({
|
|
5265
5578
|
passed: results.length === 0 || results.every((r) => r.passed),
|
|
5266
5579
|
checks: results
|
|
@@ -5274,6 +5587,96 @@ async function runMechanicalGate(projectPath) {
|
|
|
5274
5587
|
}
|
|
5275
5588
|
}
|
|
5276
5589
|
|
|
5590
|
+
// src/state/session-summary.ts
|
|
5591
|
+
import * as fs13 from "fs";
|
|
5592
|
+
import * as path10 from "path";
|
|
5593
|
+
function formatSummary(data) {
|
|
5594
|
+
const lines = [
|
|
5595
|
+
"## Session Summary",
|
|
5596
|
+
"",
|
|
5597
|
+
`**Session:** ${data.session}`,
|
|
5598
|
+
`**Last active:** ${data.lastActive}`,
|
|
5599
|
+
`**Skill:** ${data.skill}`
|
|
5600
|
+
];
|
|
5601
|
+
if (data.phase) {
|
|
5602
|
+
lines.push(`**Phase:** ${data.phase}`);
|
|
5603
|
+
}
|
|
5604
|
+
lines.push(`**Status:** ${data.status}`);
|
|
5605
|
+
if (data.spec) {
|
|
5606
|
+
lines.push(`**Spec:** ${data.spec}`);
|
|
5607
|
+
}
|
|
5608
|
+
if (data.plan) {
|
|
5609
|
+
lines.push(`**Plan:** ${data.plan}`);
|
|
5610
|
+
}
|
|
5611
|
+
lines.push(`**Key context:** ${data.keyContext}`);
|
|
5612
|
+
lines.push(`**Next step:** ${data.nextStep}`);
|
|
5613
|
+
lines.push("");
|
|
5614
|
+
return lines.join("\n");
|
|
5615
|
+
}
|
|
5616
|
+
function deriveIndexDescription(data) {
|
|
5617
|
+
const skillShort = data.skill.replace("harness-", "");
|
|
5618
|
+
const parts = [skillShort];
|
|
5619
|
+
if (data.phase) {
|
|
5620
|
+
parts.push(`phase ${data.phase}`);
|
|
5621
|
+
}
|
|
5622
|
+
parts.push(data.status.toLowerCase());
|
|
5623
|
+
return parts.join(", ");
|
|
5624
|
+
}
|
|
5625
|
+
function writeSessionSummary(projectPath, sessionSlug, data) {
|
|
5626
|
+
try {
|
|
5627
|
+
const dirResult = resolveSessionDir(projectPath, sessionSlug, { create: true });
|
|
5628
|
+
if (!dirResult.ok) return dirResult;
|
|
5629
|
+
const sessionDir = dirResult.value;
|
|
5630
|
+
const summaryPath = path10.join(sessionDir, SUMMARY_FILE);
|
|
5631
|
+
const content = formatSummary(data);
|
|
5632
|
+
fs13.writeFileSync(summaryPath, content);
|
|
5633
|
+
const description = deriveIndexDescription(data);
|
|
5634
|
+
updateSessionIndex(projectPath, sessionSlug, description);
|
|
5635
|
+
return Ok(void 0);
|
|
5636
|
+
} catch (error) {
|
|
5637
|
+
return Err(
|
|
5638
|
+
new Error(
|
|
5639
|
+
`Failed to write session summary: ${error instanceof Error ? error.message : String(error)}`
|
|
5640
|
+
)
|
|
5641
|
+
);
|
|
5642
|
+
}
|
|
5643
|
+
}
|
|
5644
|
+
function loadSessionSummary(projectPath, sessionSlug) {
|
|
5645
|
+
try {
|
|
5646
|
+
const dirResult = resolveSessionDir(projectPath, sessionSlug);
|
|
5647
|
+
if (!dirResult.ok) return dirResult;
|
|
5648
|
+
const sessionDir = dirResult.value;
|
|
5649
|
+
const summaryPath = path10.join(sessionDir, SUMMARY_FILE);
|
|
5650
|
+
if (!fs13.existsSync(summaryPath)) {
|
|
5651
|
+
return Ok(null);
|
|
5652
|
+
}
|
|
5653
|
+
const content = fs13.readFileSync(summaryPath, "utf-8");
|
|
5654
|
+
return Ok(content);
|
|
5655
|
+
} catch (error) {
|
|
5656
|
+
return Err(
|
|
5657
|
+
new Error(
|
|
5658
|
+
`Failed to load session summary: ${error instanceof Error ? error.message : String(error)}`
|
|
5659
|
+
)
|
|
5660
|
+
);
|
|
5661
|
+
}
|
|
5662
|
+
}
|
|
5663
|
+
function listActiveSessions(projectPath) {
|
|
5664
|
+
try {
|
|
5665
|
+
const indexPath2 = path10.join(projectPath, HARNESS_DIR, SESSIONS_DIR, SESSION_INDEX_FILE);
|
|
5666
|
+
if (!fs13.existsSync(indexPath2)) {
|
|
5667
|
+
return Ok(null);
|
|
5668
|
+
}
|
|
5669
|
+
const content = fs13.readFileSync(indexPath2, "utf-8");
|
|
5670
|
+
return Ok(content);
|
|
5671
|
+
} catch (error) {
|
|
5672
|
+
return Err(
|
|
5673
|
+
new Error(
|
|
5674
|
+
`Failed to list active sessions: ${error instanceof Error ? error.message : String(error)}`
|
|
5675
|
+
)
|
|
5676
|
+
);
|
|
5677
|
+
}
|
|
5678
|
+
}
|
|
5679
|
+
|
|
5277
5680
|
// src/workflow/runner.ts
|
|
5278
5681
|
async function executeWorkflow(workflow, executor) {
|
|
5279
5682
|
const stepResults = [];
|
|
@@ -5423,7 +5826,7 @@ async function runMultiTurnPipeline(initialContext, turnExecutor, options) {
|
|
|
5423
5826
|
}
|
|
5424
5827
|
|
|
5425
5828
|
// src/security/scanner.ts
|
|
5426
|
-
import * as
|
|
5829
|
+
import * as fs15 from "fs/promises";
|
|
5427
5830
|
|
|
5428
5831
|
// src/security/rules/registry.ts
|
|
5429
5832
|
var RuleRegistry = class {
|
|
@@ -5510,15 +5913,15 @@ function resolveRuleSeverity(ruleId, defaultSeverity, overrides, strict) {
|
|
|
5510
5913
|
}
|
|
5511
5914
|
|
|
5512
5915
|
// src/security/stack-detector.ts
|
|
5513
|
-
import * as
|
|
5514
|
-
import * as
|
|
5916
|
+
import * as fs14 from "fs";
|
|
5917
|
+
import * as path11 from "path";
|
|
5515
5918
|
function detectStack(projectRoot) {
|
|
5516
5919
|
const stacks = [];
|
|
5517
|
-
const pkgJsonPath =
|
|
5518
|
-
if (
|
|
5920
|
+
const pkgJsonPath = path11.join(projectRoot, "package.json");
|
|
5921
|
+
if (fs14.existsSync(pkgJsonPath)) {
|
|
5519
5922
|
stacks.push("node");
|
|
5520
5923
|
try {
|
|
5521
|
-
const pkgJson = JSON.parse(
|
|
5924
|
+
const pkgJson = JSON.parse(fs14.readFileSync(pkgJsonPath, "utf-8"));
|
|
5522
5925
|
const allDeps = {
|
|
5523
5926
|
...pkgJson.dependencies,
|
|
5524
5927
|
...pkgJson.devDependencies
|
|
@@ -5533,13 +5936,13 @@ function detectStack(projectRoot) {
|
|
|
5533
5936
|
} catch {
|
|
5534
5937
|
}
|
|
5535
5938
|
}
|
|
5536
|
-
const goModPath =
|
|
5537
|
-
if (
|
|
5939
|
+
const goModPath = path11.join(projectRoot, "go.mod");
|
|
5940
|
+
if (fs14.existsSync(goModPath)) {
|
|
5538
5941
|
stacks.push("go");
|
|
5539
5942
|
}
|
|
5540
|
-
const requirementsPath =
|
|
5541
|
-
const pyprojectPath =
|
|
5542
|
-
if (
|
|
5943
|
+
const requirementsPath = path11.join(projectRoot, "requirements.txt");
|
|
5944
|
+
const pyprojectPath = path11.join(projectRoot, "pyproject.toml");
|
|
5945
|
+
if (fs14.existsSync(requirementsPath) || fs14.existsSync(pyprojectPath)) {
|
|
5543
5946
|
stacks.push("python");
|
|
5544
5947
|
}
|
|
5545
5948
|
return stacks;
|
|
@@ -5966,7 +6369,7 @@ var SecurityScanner = class {
|
|
|
5966
6369
|
}
|
|
5967
6370
|
async scanFile(filePath) {
|
|
5968
6371
|
if (!this.config.enabled) return [];
|
|
5969
|
-
const content = await
|
|
6372
|
+
const content = await fs15.readFile(filePath, "utf-8");
|
|
5970
6373
|
return this.scanContent(content, filePath, 1);
|
|
5971
6374
|
}
|
|
5972
6375
|
async scanFiles(filePaths) {
|
|
@@ -5991,7 +6394,7 @@ var SecurityScanner = class {
|
|
|
5991
6394
|
};
|
|
5992
6395
|
|
|
5993
6396
|
// src/ci/check-orchestrator.ts
|
|
5994
|
-
import * as
|
|
6397
|
+
import * as path12 from "path";
|
|
5995
6398
|
var ALL_CHECKS = [
|
|
5996
6399
|
"validate",
|
|
5997
6400
|
"deps",
|
|
@@ -6008,7 +6411,7 @@ async function runSingleCheck(name, projectRoot, config) {
|
|
|
6008
6411
|
try {
|
|
6009
6412
|
switch (name) {
|
|
6010
6413
|
case "validate": {
|
|
6011
|
-
const agentsPath =
|
|
6414
|
+
const agentsPath = path12.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
|
|
6012
6415
|
const result = await validateAgentsMap(agentsPath);
|
|
6013
6416
|
if (!result.ok) {
|
|
6014
6417
|
issues.push({ severity: "error", message: result.error.message });
|
|
@@ -6063,7 +6466,7 @@ async function runSingleCheck(name, projectRoot, config) {
|
|
|
6063
6466
|
break;
|
|
6064
6467
|
}
|
|
6065
6468
|
case "docs": {
|
|
6066
|
-
const docsDir =
|
|
6469
|
+
const docsDir = path12.join(projectRoot, config.docsDir ?? "docs");
|
|
6067
6470
|
const entropyConfig = config.entropy || {};
|
|
6068
6471
|
const result = await checkDocCoverage("project", {
|
|
6069
6472
|
docsDir,
|
|
@@ -6301,7 +6704,7 @@ async function runCIChecks(input) {
|
|
|
6301
6704
|
}
|
|
6302
6705
|
|
|
6303
6706
|
// src/review/mechanical-checks.ts
|
|
6304
|
-
import * as
|
|
6707
|
+
import * as path13 from "path";
|
|
6305
6708
|
async function runMechanicalChecks(options) {
|
|
6306
6709
|
const { projectRoot, config, skip = [], changedFiles } = options;
|
|
6307
6710
|
const findings = [];
|
|
@@ -6313,7 +6716,7 @@ async function runMechanicalChecks(options) {
|
|
|
6313
6716
|
};
|
|
6314
6717
|
if (!skip.includes("validate")) {
|
|
6315
6718
|
try {
|
|
6316
|
-
const agentsPath =
|
|
6719
|
+
const agentsPath = path13.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
|
|
6317
6720
|
const result = await validateAgentsMap(agentsPath);
|
|
6318
6721
|
if (!result.ok) {
|
|
6319
6722
|
statuses.validate = "fail";
|
|
@@ -6350,7 +6753,7 @@ async function runMechanicalChecks(options) {
|
|
|
6350
6753
|
statuses.validate = "fail";
|
|
6351
6754
|
findings.push({
|
|
6352
6755
|
tool: "validate",
|
|
6353
|
-
file:
|
|
6756
|
+
file: path13.join(projectRoot, "AGENTS.md"),
|
|
6354
6757
|
message: err instanceof Error ? err.message : String(err),
|
|
6355
6758
|
severity: "error"
|
|
6356
6759
|
});
|
|
@@ -6414,7 +6817,7 @@ async function runMechanicalChecks(options) {
|
|
|
6414
6817
|
(async () => {
|
|
6415
6818
|
const localFindings = [];
|
|
6416
6819
|
try {
|
|
6417
|
-
const docsDir =
|
|
6820
|
+
const docsDir = path13.join(projectRoot, config.docsDir ?? "docs");
|
|
6418
6821
|
const result = await checkDocCoverage("project", { docsDir });
|
|
6419
6822
|
if (!result.ok) {
|
|
6420
6823
|
statuses["check-docs"] = "warn";
|
|
@@ -6441,7 +6844,7 @@ async function runMechanicalChecks(options) {
|
|
|
6441
6844
|
statuses["check-docs"] = "warn";
|
|
6442
6845
|
localFindings.push({
|
|
6443
6846
|
tool: "check-docs",
|
|
6444
|
-
file:
|
|
6847
|
+
file: path13.join(projectRoot, "docs"),
|
|
6445
6848
|
message: err instanceof Error ? err.message : String(err),
|
|
6446
6849
|
severity: "warning"
|
|
6447
6850
|
});
|
|
@@ -6589,7 +6992,7 @@ function detectChangeType(commitMessage, diff2) {
|
|
|
6589
6992
|
}
|
|
6590
6993
|
|
|
6591
6994
|
// src/review/context-scoper.ts
|
|
6592
|
-
import * as
|
|
6995
|
+
import * as path14 from "path";
|
|
6593
6996
|
var ALL_DOMAINS = ["compliance", "bug", "security", "architecture"];
|
|
6594
6997
|
var SECURITY_PATTERNS = /auth|crypto|password|secret|token|session|cookie|hash|encrypt|decrypt|sql|shell|exec|eval/i;
|
|
6595
6998
|
function computeContextBudget(diffLines) {
|
|
@@ -6597,18 +7000,18 @@ function computeContextBudget(diffLines) {
|
|
|
6597
7000
|
return diffLines;
|
|
6598
7001
|
}
|
|
6599
7002
|
function isWithinProject(absPath, projectRoot) {
|
|
6600
|
-
const resolvedRoot =
|
|
6601
|
-
const resolvedPath =
|
|
6602
|
-
return resolvedPath.startsWith(resolvedRoot) || resolvedPath ===
|
|
7003
|
+
const resolvedRoot = path14.resolve(projectRoot) + path14.sep;
|
|
7004
|
+
const resolvedPath = path14.resolve(absPath);
|
|
7005
|
+
return resolvedPath.startsWith(resolvedRoot) || resolvedPath === path14.resolve(projectRoot);
|
|
6603
7006
|
}
|
|
6604
7007
|
async function readContextFile(projectRoot, filePath, reason) {
|
|
6605
|
-
const absPath =
|
|
7008
|
+
const absPath = path14.isAbsolute(filePath) ? filePath : path14.join(projectRoot, filePath);
|
|
6606
7009
|
if (!isWithinProject(absPath, projectRoot)) return null;
|
|
6607
7010
|
const result = await readFileContent(absPath);
|
|
6608
7011
|
if (!result.ok) return null;
|
|
6609
7012
|
const content = result.value;
|
|
6610
7013
|
const lines = content.split("\n").length;
|
|
6611
|
-
const relPath =
|
|
7014
|
+
const relPath = path14.isAbsolute(filePath) ? path14.relative(projectRoot, filePath) : filePath;
|
|
6612
7015
|
return { path: relPath, content, reason, lines };
|
|
6613
7016
|
}
|
|
6614
7017
|
function extractImportSources(content) {
|
|
@@ -6623,18 +7026,18 @@ function extractImportSources(content) {
|
|
|
6623
7026
|
}
|
|
6624
7027
|
async function resolveImportPath(projectRoot, fromFile, importSource) {
|
|
6625
7028
|
if (!importSource.startsWith(".")) return null;
|
|
6626
|
-
const fromDir =
|
|
6627
|
-
const basePath =
|
|
7029
|
+
const fromDir = path14.dirname(path14.join(projectRoot, fromFile));
|
|
7030
|
+
const basePath = path14.resolve(fromDir, importSource);
|
|
6628
7031
|
if (!isWithinProject(basePath, projectRoot)) return null;
|
|
6629
|
-
const relBase =
|
|
7032
|
+
const relBase = path14.relative(projectRoot, basePath);
|
|
6630
7033
|
const candidates = [
|
|
6631
7034
|
relBase + ".ts",
|
|
6632
7035
|
relBase + ".tsx",
|
|
6633
7036
|
relBase + ".mts",
|
|
6634
|
-
|
|
7037
|
+
path14.join(relBase, "index.ts")
|
|
6635
7038
|
];
|
|
6636
7039
|
for (const candidate of candidates) {
|
|
6637
|
-
const absCandidate =
|
|
7040
|
+
const absCandidate = path14.join(projectRoot, candidate);
|
|
6638
7041
|
if (await fileExists(absCandidate)) {
|
|
6639
7042
|
return candidate;
|
|
6640
7043
|
}
|
|
@@ -6642,10 +7045,10 @@ async function resolveImportPath(projectRoot, fromFile, importSource) {
|
|
|
6642
7045
|
return null;
|
|
6643
7046
|
}
|
|
6644
7047
|
async function findTestFiles(projectRoot, sourceFile) {
|
|
6645
|
-
const baseName =
|
|
7048
|
+
const baseName = path14.basename(sourceFile, path14.extname(sourceFile));
|
|
6646
7049
|
const pattern = `**/${baseName}.{test,spec}.{ts,tsx,mts}`;
|
|
6647
7050
|
const results = await findFiles(pattern, projectRoot);
|
|
6648
|
-
return results.map((f) =>
|
|
7051
|
+
return results.map((f) => path14.relative(projectRoot, f));
|
|
6649
7052
|
}
|
|
6650
7053
|
async function gatherImportContext(projectRoot, changedFiles, budget) {
|
|
6651
7054
|
const contextFiles = [];
|
|
@@ -7433,7 +7836,7 @@ async function fanOutReview(options) {
|
|
|
7433
7836
|
}
|
|
7434
7837
|
|
|
7435
7838
|
// src/review/validate-findings.ts
|
|
7436
|
-
import * as
|
|
7839
|
+
import * as path15 from "path";
|
|
7437
7840
|
var DOWNGRADE_MAP = {
|
|
7438
7841
|
critical: "important",
|
|
7439
7842
|
important: "suggestion",
|
|
@@ -7454,7 +7857,7 @@ function normalizePath(filePath, projectRoot) {
|
|
|
7454
7857
|
let normalized = filePath;
|
|
7455
7858
|
normalized = normalized.replace(/\\/g, "/");
|
|
7456
7859
|
const normalizedRoot = projectRoot.replace(/\\/g, "/");
|
|
7457
|
-
if (
|
|
7860
|
+
if (path15.isAbsolute(normalized)) {
|
|
7458
7861
|
const root = normalizedRoot.endsWith("/") ? normalizedRoot : normalizedRoot + "/";
|
|
7459
7862
|
if (normalized.startsWith(root)) {
|
|
7460
7863
|
normalized = normalized.slice(root.length);
|
|
@@ -7479,12 +7882,12 @@ function followImportChain(fromFile, fileContents, maxDepth = 2) {
|
|
|
7479
7882
|
while ((match = importRegex.exec(content)) !== null) {
|
|
7480
7883
|
const importPath = match[1];
|
|
7481
7884
|
if (!importPath.startsWith(".")) continue;
|
|
7482
|
-
const dir =
|
|
7483
|
-
let resolved =
|
|
7885
|
+
const dir = path15.dirname(current.file);
|
|
7886
|
+
let resolved = path15.join(dir, importPath).replace(/\\/g, "/");
|
|
7484
7887
|
if (!resolved.match(/\.(ts|tsx|js|jsx)$/)) {
|
|
7485
7888
|
resolved += ".ts";
|
|
7486
7889
|
}
|
|
7487
|
-
resolved =
|
|
7890
|
+
resolved = path15.normalize(resolved).replace(/\\/g, "/");
|
|
7488
7891
|
if (!visited.has(resolved) && current.depth + 1 <= maxDepth) {
|
|
7489
7892
|
queue.push({ file: resolved, depth: current.depth + 1 });
|
|
7490
7893
|
}
|
|
@@ -7501,7 +7904,7 @@ async function validateFindings(options) {
|
|
|
7501
7904
|
if (exclusionSet.isExcluded(normalizedFile, finding.lineRange) || exclusionSet.isExcluded(finding.file, finding.lineRange)) {
|
|
7502
7905
|
continue;
|
|
7503
7906
|
}
|
|
7504
|
-
const absoluteFile =
|
|
7907
|
+
const absoluteFile = path15.isAbsolute(finding.file) ? finding.file : path15.join(projectRoot, finding.file).replace(/\\/g, "/");
|
|
7505
7908
|
if (exclusionSet.isExcluded(absoluteFile, finding.lineRange)) {
|
|
7506
7909
|
continue;
|
|
7507
7910
|
}
|
|
@@ -8022,6 +8425,8 @@ function parseFrontmatter(raw) {
|
|
|
8022
8425
|
const versionStr = map.get("version");
|
|
8023
8426
|
const lastSynced = map.get("last_synced");
|
|
8024
8427
|
const lastManualEdit = map.get("last_manual_edit");
|
|
8428
|
+
const created = map.get("created");
|
|
8429
|
+
const updated = map.get("updated");
|
|
8025
8430
|
if (!project || !versionStr || !lastSynced || !lastManualEdit) {
|
|
8026
8431
|
return Err2(
|
|
8027
8432
|
new Error(
|
|
@@ -8033,7 +8438,10 @@ function parseFrontmatter(raw) {
|
|
|
8033
8438
|
if (isNaN(version)) {
|
|
8034
8439
|
return Err2(new Error("Frontmatter version must be a number"));
|
|
8035
8440
|
}
|
|
8036
|
-
|
|
8441
|
+
const fm = { project, version, lastSynced, lastManualEdit };
|
|
8442
|
+
if (created) fm.created = created;
|
|
8443
|
+
if (updated) fm.updated = updated;
|
|
8444
|
+
return Ok2(fm);
|
|
8037
8445
|
}
|
|
8038
8446
|
function parseMilestones(body) {
|
|
8039
8447
|
const milestones = [];
|
|
@@ -8041,12 +8449,12 @@ function parseMilestones(body) {
|
|
|
8041
8449
|
const h2Matches = [];
|
|
8042
8450
|
let match;
|
|
8043
8451
|
while ((match = h2Pattern.exec(body)) !== null) {
|
|
8044
|
-
h2Matches.push({ heading: match[1], startIndex: match.index });
|
|
8452
|
+
h2Matches.push({ heading: match[1], startIndex: match.index, fullMatch: match[0] });
|
|
8045
8453
|
}
|
|
8046
8454
|
for (let i = 0; i < h2Matches.length; i++) {
|
|
8047
8455
|
const h2 = h2Matches[i];
|
|
8048
8456
|
const nextStart = i + 1 < h2Matches.length ? h2Matches[i + 1].startIndex : body.length;
|
|
8049
|
-
const sectionBody = body.slice(h2.startIndex + h2.
|
|
8457
|
+
const sectionBody = body.slice(h2.startIndex + h2.fullMatch.length, nextStart);
|
|
8050
8458
|
const isBacklog = h2.heading === "Backlog";
|
|
8051
8459
|
const milestoneName = isBacklog ? "Backlog" : h2.heading.replace(/^Milestone:\s*/, "");
|
|
8052
8460
|
const featuresResult = parseFeatures(sectionBody);
|
|
@@ -8061,19 +8469,16 @@ function parseMilestones(body) {
|
|
|
8061
8469
|
}
|
|
8062
8470
|
function parseFeatures(sectionBody) {
|
|
8063
8471
|
const features = [];
|
|
8064
|
-
const h3Pattern = /^### Feature: (.+)$/gm;
|
|
8472
|
+
const h3Pattern = /^### (?:Feature: )?(.+)$/gm;
|
|
8065
8473
|
const h3Matches = [];
|
|
8066
8474
|
let match;
|
|
8067
8475
|
while ((match = h3Pattern.exec(sectionBody)) !== null) {
|
|
8068
|
-
h3Matches.push({ name: match[1], startIndex: match.index });
|
|
8476
|
+
h3Matches.push({ name: match[1], startIndex: match.index, fullMatch: match[0] });
|
|
8069
8477
|
}
|
|
8070
8478
|
for (let i = 0; i < h3Matches.length; i++) {
|
|
8071
8479
|
const h3 = h3Matches[i];
|
|
8072
8480
|
const nextStart = i + 1 < h3Matches.length ? h3Matches[i + 1].startIndex : sectionBody.length;
|
|
8073
|
-
const featureBody = sectionBody.slice(
|
|
8074
|
-
h3.startIndex + `### Feature: ${h3.name}`.length,
|
|
8075
|
-
nextStart
|
|
8076
|
-
);
|
|
8481
|
+
const featureBody = sectionBody.slice(h3.startIndex + h3.fullMatch.length, nextStart);
|
|
8077
8482
|
const featureResult = parseFeatureFields(h3.name, featureBody);
|
|
8078
8483
|
if (!featureResult.ok) return featureResult;
|
|
8079
8484
|
features.push(featureResult.value);
|
|
@@ -8098,10 +8503,10 @@ function parseFeatureFields(name, body) {
|
|
|
8098
8503
|
const status = statusRaw;
|
|
8099
8504
|
const specRaw = fieldMap.get("Spec") ?? EM_DASH;
|
|
8100
8505
|
const spec = specRaw === EM_DASH ? null : specRaw;
|
|
8101
|
-
const plansRaw = fieldMap.get("Plans") ?? EM_DASH;
|
|
8102
|
-
const plans = plansRaw === EM_DASH ? [] : plansRaw.split(",").map((p) => p.trim());
|
|
8103
|
-
const blockedByRaw = fieldMap.get("Blocked by") ?? EM_DASH;
|
|
8104
|
-
const blockedBy = blockedByRaw === EM_DASH ? [] : blockedByRaw.split(",").map((b) => b.trim());
|
|
8506
|
+
const plansRaw = fieldMap.get("Plans") ?? fieldMap.get("Plan") ?? EM_DASH;
|
|
8507
|
+
const plans = plansRaw === EM_DASH || plansRaw === "none" ? [] : plansRaw.split(",").map((p) => p.trim());
|
|
8508
|
+
const blockedByRaw = fieldMap.get("Blocked by") ?? fieldMap.get("Blockers") ?? EM_DASH;
|
|
8509
|
+
const blockedBy = blockedByRaw === EM_DASH || blockedByRaw === "none" ? [] : blockedByRaw.split(",").map((b) => b.trim());
|
|
8105
8510
|
const summary = fieldMap.get("Summary") ?? "";
|
|
8106
8511
|
return Ok2({ name, status, spec, plans, blockedBy, summary });
|
|
8107
8512
|
}
|
|
@@ -8113,11 +8518,17 @@ function serializeRoadmap(roadmap) {
|
|
|
8113
8518
|
lines.push("---");
|
|
8114
8519
|
lines.push(`project: ${roadmap.frontmatter.project}`);
|
|
8115
8520
|
lines.push(`version: ${roadmap.frontmatter.version}`);
|
|
8521
|
+
if (roadmap.frontmatter.created) {
|
|
8522
|
+
lines.push(`created: ${roadmap.frontmatter.created}`);
|
|
8523
|
+
}
|
|
8524
|
+
if (roadmap.frontmatter.updated) {
|
|
8525
|
+
lines.push(`updated: ${roadmap.frontmatter.updated}`);
|
|
8526
|
+
}
|
|
8116
8527
|
lines.push(`last_synced: ${roadmap.frontmatter.lastSynced}`);
|
|
8117
8528
|
lines.push(`last_manual_edit: ${roadmap.frontmatter.lastManualEdit}`);
|
|
8118
8529
|
lines.push("---");
|
|
8119
8530
|
lines.push("");
|
|
8120
|
-
lines.push("#
|
|
8531
|
+
lines.push("# Roadmap");
|
|
8121
8532
|
for (const milestone of roadmap.milestones) {
|
|
8122
8533
|
lines.push("");
|
|
8123
8534
|
lines.push(serializeMilestoneHeading(milestone));
|
|
@@ -8130,25 +8541,26 @@ function serializeRoadmap(roadmap) {
|
|
|
8130
8541
|
return lines.join("\n");
|
|
8131
8542
|
}
|
|
8132
8543
|
function serializeMilestoneHeading(milestone) {
|
|
8133
|
-
return milestone.isBacklog ? "## Backlog" : `##
|
|
8544
|
+
return milestone.isBacklog ? "## Backlog" : `## ${milestone.name}`;
|
|
8134
8545
|
}
|
|
8135
8546
|
function serializeFeature(feature) {
|
|
8136
8547
|
const spec = feature.spec ?? EM_DASH2;
|
|
8137
8548
|
const plans = feature.plans.length > 0 ? feature.plans.join(", ") : EM_DASH2;
|
|
8138
8549
|
const blockedBy = feature.blockedBy.length > 0 ? feature.blockedBy.join(", ") : EM_DASH2;
|
|
8139
8550
|
return [
|
|
8140
|
-
`###
|
|
8551
|
+
`### ${feature.name}`,
|
|
8552
|
+
"",
|
|
8141
8553
|
`- **Status:** ${feature.status}`,
|
|
8142
8554
|
`- **Spec:** ${spec}`,
|
|
8143
|
-
`- **
|
|
8144
|
-
`- **
|
|
8145
|
-
`- **
|
|
8555
|
+
`- **Summary:** ${feature.summary}`,
|
|
8556
|
+
`- **Blockers:** ${blockedBy}`,
|
|
8557
|
+
`- **Plan:** ${plans}`
|
|
8146
8558
|
];
|
|
8147
8559
|
}
|
|
8148
8560
|
|
|
8149
8561
|
// src/roadmap/sync.ts
|
|
8150
|
-
import * as
|
|
8151
|
-
import * as
|
|
8562
|
+
import * as fs16 from "fs";
|
|
8563
|
+
import * as path16 from "path";
|
|
8152
8564
|
import { Ok as Ok3 } from "@harness-engineering/types";
|
|
8153
8565
|
function inferStatus(feature, projectPath, allFeatures) {
|
|
8154
8566
|
if (feature.blockedBy.length > 0) {
|
|
@@ -8163,10 +8575,10 @@ function inferStatus(feature, projectPath, allFeatures) {
|
|
|
8163
8575
|
const featuresWithPlans = allFeatures.filter((f) => f.plans.length > 0);
|
|
8164
8576
|
const useRootState = featuresWithPlans.length <= 1;
|
|
8165
8577
|
if (useRootState) {
|
|
8166
|
-
const rootStatePath =
|
|
8167
|
-
if (
|
|
8578
|
+
const rootStatePath = path16.join(projectPath, ".harness", "state.json");
|
|
8579
|
+
if (fs16.existsSync(rootStatePath)) {
|
|
8168
8580
|
try {
|
|
8169
|
-
const raw =
|
|
8581
|
+
const raw = fs16.readFileSync(rootStatePath, "utf-8");
|
|
8170
8582
|
const state = JSON.parse(raw);
|
|
8171
8583
|
if (state.progress) {
|
|
8172
8584
|
for (const status of Object.values(state.progress)) {
|
|
@@ -8177,16 +8589,16 @@ function inferStatus(feature, projectPath, allFeatures) {
|
|
|
8177
8589
|
}
|
|
8178
8590
|
}
|
|
8179
8591
|
}
|
|
8180
|
-
const sessionsDir =
|
|
8181
|
-
if (
|
|
8592
|
+
const sessionsDir = path16.join(projectPath, ".harness", "sessions");
|
|
8593
|
+
if (fs16.existsSync(sessionsDir)) {
|
|
8182
8594
|
try {
|
|
8183
|
-
const sessionDirs =
|
|
8595
|
+
const sessionDirs = fs16.readdirSync(sessionsDir, { withFileTypes: true });
|
|
8184
8596
|
for (const entry of sessionDirs) {
|
|
8185
8597
|
if (!entry.isDirectory()) continue;
|
|
8186
|
-
const autopilotPath =
|
|
8187
|
-
if (!
|
|
8598
|
+
const autopilotPath = path16.join(sessionsDir, entry.name, "autopilot-state.json");
|
|
8599
|
+
if (!fs16.existsSync(autopilotPath)) continue;
|
|
8188
8600
|
try {
|
|
8189
|
-
const raw =
|
|
8601
|
+
const raw = fs16.readFileSync(autopilotPath, "utf-8");
|
|
8190
8602
|
const autopilot = JSON.parse(raw);
|
|
8191
8603
|
if (!autopilot.phases) continue;
|
|
8192
8604
|
const linkedPhases = autopilot.phases.filter(
|
|
@@ -8266,17 +8678,17 @@ var EmitInteractionInputSchema = z6.object({
|
|
|
8266
8678
|
});
|
|
8267
8679
|
|
|
8268
8680
|
// src/blueprint/scanner.ts
|
|
8269
|
-
import * as
|
|
8270
|
-
import * as
|
|
8681
|
+
import * as fs17 from "fs/promises";
|
|
8682
|
+
import * as path17 from "path";
|
|
8271
8683
|
var ProjectScanner = class {
|
|
8272
8684
|
constructor(rootDir) {
|
|
8273
8685
|
this.rootDir = rootDir;
|
|
8274
8686
|
}
|
|
8275
8687
|
async scan() {
|
|
8276
|
-
let projectName =
|
|
8688
|
+
let projectName = path17.basename(this.rootDir);
|
|
8277
8689
|
try {
|
|
8278
|
-
const pkgPath =
|
|
8279
|
-
const pkgRaw = await
|
|
8690
|
+
const pkgPath = path17.join(this.rootDir, "package.json");
|
|
8691
|
+
const pkgRaw = await fs17.readFile(pkgPath, "utf-8");
|
|
8280
8692
|
const pkg = JSON.parse(pkgRaw);
|
|
8281
8693
|
if (pkg.name) projectName = pkg.name;
|
|
8282
8694
|
} catch {
|
|
@@ -8317,8 +8729,8 @@ var ProjectScanner = class {
|
|
|
8317
8729
|
};
|
|
8318
8730
|
|
|
8319
8731
|
// src/blueprint/generator.ts
|
|
8320
|
-
import * as
|
|
8321
|
-
import * as
|
|
8732
|
+
import * as fs18 from "fs/promises";
|
|
8733
|
+
import * as path18 from "path";
|
|
8322
8734
|
import * as ejs from "ejs";
|
|
8323
8735
|
|
|
8324
8736
|
// src/blueprint/templates.ts
|
|
@@ -8346,16 +8758,6 @@ var SHELL_TEMPLATE = `
|
|
|
8346
8758
|
<div class="content">
|
|
8347
8759
|
<h3>Code Translation</h3>
|
|
8348
8760
|
<div class="translation"><%- module.content.codeTranslation %></div>
|
|
8349
|
-
<h3>Knowledge Check</h3>
|
|
8350
|
-
<div class="quiz">
|
|
8351
|
-
<% module.content.quiz.questions.forEach((q, i) => { %>
|
|
8352
|
-
<div class="question">
|
|
8353
|
-
<p><%= q.question %></p>
|
|
8354
|
-
<button onclick="reveal(this)">Reveal Answer</button>
|
|
8355
|
-
<p class="answer" style="display:none;"><%= q.answer %></p>
|
|
8356
|
-
</div>
|
|
8357
|
-
<% }) %>
|
|
8358
|
-
</div>
|
|
8359
8761
|
</div>
|
|
8360
8762
|
</article>
|
|
8361
8763
|
|
|
@@ -8374,10 +8776,6 @@ header { border-bottom: 2px solid #eee; margin-bottom: 20px; padding-bottom: 10p
|
|
|
8374
8776
|
.module h2 { margin-top: 0; color: #0066cc; }
|
|
8375
8777
|
`;
|
|
8376
8778
|
var SCRIPTS = `
|
|
8377
|
-
function reveal(btn) {
|
|
8378
|
-
btn.nextElementSibling.style.display = 'block';
|
|
8379
|
-
btn.style.display = 'none';
|
|
8380
|
-
}
|
|
8381
8779
|
console.log('Blueprint player initialized.');
|
|
8382
8780
|
`;
|
|
8383
8781
|
|
|
@@ -8396,20 +8794,8 @@ var ContentPipeline = class {
|
|
|
8396
8794
|
const translation = await llmService.generate(
|
|
8397
8795
|
`You are a technical educator. Explain the following code clearly and concisely: ${codeContext}`
|
|
8398
8796
|
);
|
|
8399
|
-
const quizJson = await llmService.generate(
|
|
8400
|
-
`Create 3 technical quiz questions for this code. Return ONLY valid JSON in this format: { "questions": [{ "question": "...", "answer": "..." }] }. Code: ${codeContext}`
|
|
8401
|
-
);
|
|
8402
|
-
let quiz;
|
|
8403
|
-
try {
|
|
8404
|
-
const cleanJson = quizJson.replace(/```json/g, "").replace(/```/g, "").trim();
|
|
8405
|
-
quiz = JSON.parse(cleanJson);
|
|
8406
|
-
} catch (e) {
|
|
8407
|
-
console.error("Failed to parse quiz JSON", e);
|
|
8408
|
-
quiz = { questions: [{ question: "Failed to generate quiz", answer: "N/A" }] };
|
|
8409
|
-
}
|
|
8410
8797
|
return {
|
|
8411
|
-
codeTranslation: translation
|
|
8412
|
-
quiz
|
|
8798
|
+
codeTranslation: translation
|
|
8413
8799
|
};
|
|
8414
8800
|
}
|
|
8415
8801
|
};
|
|
@@ -8428,19 +8814,19 @@ var BlueprintGenerator = class {
|
|
|
8428
8814
|
styles: STYLES,
|
|
8429
8815
|
scripts: SCRIPTS
|
|
8430
8816
|
});
|
|
8431
|
-
await
|
|
8432
|
-
await
|
|
8817
|
+
await fs18.mkdir(options.outputDir, { recursive: true });
|
|
8818
|
+
await fs18.writeFile(path18.join(options.outputDir, "index.html"), html);
|
|
8433
8819
|
}
|
|
8434
8820
|
};
|
|
8435
8821
|
|
|
8436
8822
|
// src/update-checker.ts
|
|
8437
|
-
import * as
|
|
8438
|
-
import * as
|
|
8823
|
+
import * as fs19 from "fs";
|
|
8824
|
+
import * as path19 from "path";
|
|
8439
8825
|
import * as os from "os";
|
|
8440
8826
|
import { spawn } from "child_process";
|
|
8441
8827
|
function getStatePath() {
|
|
8442
8828
|
const home = process.env["HOME"] || os.homedir();
|
|
8443
|
-
return
|
|
8829
|
+
return path19.join(home, ".harness", "update-check.json");
|
|
8444
8830
|
}
|
|
8445
8831
|
function isUpdateCheckEnabled(configInterval) {
|
|
8446
8832
|
if (process.env["HARNESS_NO_UPDATE_CHECK"] === "1") return false;
|
|
@@ -8453,7 +8839,7 @@ function shouldRunCheck(state, intervalMs) {
|
|
|
8453
8839
|
}
|
|
8454
8840
|
function readCheckState() {
|
|
8455
8841
|
try {
|
|
8456
|
-
const raw =
|
|
8842
|
+
const raw = fs19.readFileSync(getStatePath(), "utf-8");
|
|
8457
8843
|
const parsed = JSON.parse(raw);
|
|
8458
8844
|
if (typeof parsed === "object" && parsed !== null && "lastCheckTime" in parsed && typeof parsed.lastCheckTime === "number" && "currentVersion" in parsed && typeof parsed.currentVersion === "string") {
|
|
8459
8845
|
const state = parsed;
|
|
@@ -8470,7 +8856,7 @@ function readCheckState() {
|
|
|
8470
8856
|
}
|
|
8471
8857
|
function spawnBackgroundCheck(currentVersion) {
|
|
8472
8858
|
const statePath = getStatePath();
|
|
8473
|
-
const stateDir =
|
|
8859
|
+
const stateDir = path19.dirname(statePath);
|
|
8474
8860
|
const script = `
|
|
8475
8861
|
const { execSync } = require('child_process');
|
|
8476
8862
|
const fs = require('fs');
|
|
@@ -8601,6 +8987,7 @@ export {
|
|
|
8601
8987
|
ViolationSchema,
|
|
8602
8988
|
addProvenance,
|
|
8603
8989
|
analyzeDiff,
|
|
8990
|
+
analyzeLearningPatterns,
|
|
8604
8991
|
appendFailure,
|
|
8605
8992
|
appendLearning,
|
|
8606
8993
|
applyFixes,
|
|
@@ -8609,6 +8996,7 @@ export {
|
|
|
8609
8996
|
archModule,
|
|
8610
8997
|
architecture,
|
|
8611
8998
|
archiveFailures,
|
|
8999
|
+
archiveLearnings,
|
|
8612
9000
|
archiveStream,
|
|
8613
9001
|
buildDependencyGraph,
|
|
8614
9002
|
buildExclusionSet,
|
|
@@ -8616,6 +9004,8 @@ export {
|
|
|
8616
9004
|
checkDocCoverage,
|
|
8617
9005
|
checkEligibility,
|
|
8618
9006
|
classifyFinding,
|
|
9007
|
+
clearFailuresCache,
|
|
9008
|
+
clearLearningsCache,
|
|
8619
9009
|
configureFeedback,
|
|
8620
9010
|
constraintRuleId,
|
|
8621
9011
|
contextBudget,
|
|
@@ -8671,16 +9061,20 @@ export {
|
|
|
8671
9061
|
injectionRules,
|
|
8672
9062
|
isSmallSuggestion,
|
|
8673
9063
|
isUpdateCheckEnabled,
|
|
9064
|
+
listActiveSessions,
|
|
8674
9065
|
listStreams,
|
|
9066
|
+
loadBudgetedLearnings,
|
|
8675
9067
|
loadFailures,
|
|
8676
9068
|
loadHandoff,
|
|
8677
9069
|
loadRelevantLearnings,
|
|
9070
|
+
loadSessionSummary,
|
|
8678
9071
|
loadState,
|
|
8679
9072
|
loadStreamIndex,
|
|
8680
9073
|
logAgentAction,
|
|
8681
9074
|
migrateToStreams,
|
|
8682
9075
|
networkRules,
|
|
8683
9076
|
nodeRules,
|
|
9077
|
+
parseDateFromEntry,
|
|
8684
9078
|
parseDiff,
|
|
8685
9079
|
parseManifest,
|
|
8686
9080
|
parseRoadmap,
|
|
@@ -8688,9 +9082,11 @@ export {
|
|
|
8688
9082
|
parseSize,
|
|
8689
9083
|
pathTraversalRules,
|
|
8690
9084
|
previewFix,
|
|
9085
|
+
pruneLearnings,
|
|
8691
9086
|
reactRules,
|
|
8692
9087
|
readCheckState,
|
|
8693
9088
|
readLockfile,
|
|
9089
|
+
removeContributions,
|
|
8694
9090
|
removeProvenance,
|
|
8695
9091
|
requestMultiplePeerReviews,
|
|
8696
9092
|
requestPeerReview,
|
|
@@ -8698,6 +9094,7 @@ export {
|
|
|
8698
9094
|
resolveFileToLayer,
|
|
8699
9095
|
resolveModelTier,
|
|
8700
9096
|
resolveRuleSeverity,
|
|
9097
|
+
resolveSessionDir,
|
|
8701
9098
|
resolveStreamPath,
|
|
8702
9099
|
resolveThresholds,
|
|
8703
9100
|
runAll,
|
|
@@ -8724,6 +9121,7 @@ export {
|
|
|
8724
9121
|
syncRoadmap,
|
|
8725
9122
|
touchStream,
|
|
8726
9123
|
trackAction,
|
|
9124
|
+
updateSessionIndex,
|
|
8727
9125
|
validateAgentsMap,
|
|
8728
9126
|
validateBoundaries,
|
|
8729
9127
|
validateCommitMessage,
|
|
@@ -8736,5 +9134,6 @@ export {
|
|
|
8736
9134
|
violationId,
|
|
8737
9135
|
writeConfig,
|
|
8738
9136
|
writeLockfile,
|
|
9137
|
+
writeSessionSummary,
|
|
8739
9138
|
xssRules
|
|
8740
9139
|
};
|