@harness-engineering/core 0.12.0 → 0.13.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/architecture/matchers.js +27 -35
- package/dist/architecture/matchers.mjs +1 -1
- package/dist/{chunk-ZHGBWFYD.mjs → chunk-D6VFA6AS.mjs} +22 -29
- package/dist/index.d.mts +420 -292
- package/dist/index.d.ts +420 -292
- package/dist/index.js +931 -537
- package/dist/index.mjs +871 -484
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -36,11 +36,12 @@ import {
|
|
|
36
36
|
fileExists,
|
|
37
37
|
findFiles,
|
|
38
38
|
readFileContent,
|
|
39
|
+
relativePosix,
|
|
39
40
|
resolveFileToLayer,
|
|
40
41
|
runAll,
|
|
41
42
|
validateDependencies,
|
|
42
43
|
violationId
|
|
43
|
-
} from "./chunk-
|
|
44
|
+
} from "./chunk-D6VFA6AS.mjs";
|
|
44
45
|
|
|
45
46
|
// src/index.ts
|
|
46
47
|
export * from "@harness-engineering/types";
|
|
@@ -83,15 +84,15 @@ function validateConfig(data, schema) {
|
|
|
83
84
|
let message = "Configuration validation failed";
|
|
84
85
|
const suggestions = [];
|
|
85
86
|
if (firstError) {
|
|
86
|
-
const
|
|
87
|
-
const pathDisplay =
|
|
87
|
+
const path20 = firstError.path.join(".");
|
|
88
|
+
const pathDisplay = path20 ? ` at "${path20}"` : "";
|
|
88
89
|
if (firstError.code === "invalid_type") {
|
|
89
90
|
const received = firstError.received;
|
|
90
91
|
const expected = firstError.expected;
|
|
91
92
|
if (received === "undefined") {
|
|
92
93
|
code = "MISSING_FIELD";
|
|
93
94
|
message = `Missing required field${pathDisplay}: ${firstError.message}`;
|
|
94
|
-
suggestions.push(`Field "${
|
|
95
|
+
suggestions.push(`Field "${path20}" is required and must be of type "${expected}"`);
|
|
95
96
|
} else {
|
|
96
97
|
code = "INVALID_TYPE";
|
|
97
98
|
message = `Invalid type${pathDisplay}: ${firstError.message}`;
|
|
@@ -304,30 +305,27 @@ function extractSections(content) {
|
|
|
304
305
|
return result;
|
|
305
306
|
});
|
|
306
307
|
}
|
|
307
|
-
function isExternalLink(
|
|
308
|
-
return
|
|
308
|
+
function isExternalLink(path20) {
|
|
309
|
+
return path20.startsWith("http://") || path20.startsWith("https://") || path20.startsWith("#") || path20.startsWith("mailto:");
|
|
309
310
|
}
|
|
310
311
|
function resolveLinkPath(linkPath, baseDir) {
|
|
311
312
|
return linkPath.startsWith(".") ? join(baseDir, linkPath) : linkPath;
|
|
312
313
|
}
|
|
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);
|
|
314
|
+
async function validateAgentsMap(path20 = "./AGENTS.md") {
|
|
315
|
+
const contentResult = await readFileContent(path20);
|
|
318
316
|
if (!contentResult.ok) {
|
|
319
317
|
return Err(
|
|
320
318
|
createError(
|
|
321
319
|
"PARSE_ERROR",
|
|
322
320
|
`Failed to read AGENTS.md: ${contentResult.error.message}`,
|
|
323
|
-
{ path:
|
|
321
|
+
{ path: path20 },
|
|
324
322
|
["Ensure the file exists", "Check file permissions"]
|
|
325
323
|
)
|
|
326
324
|
);
|
|
327
325
|
}
|
|
328
326
|
const content = contentResult.value;
|
|
329
327
|
const sections = extractSections(content);
|
|
330
|
-
const baseDir = dirname(
|
|
328
|
+
const baseDir = dirname(path20);
|
|
331
329
|
const sectionTitles = sections.map((s) => s.title);
|
|
332
330
|
const missingSections = REQUIRED_SECTIONS.filter(
|
|
333
331
|
(required) => !sectionTitles.some((title) => title.toLowerCase().includes(required.toLowerCase()))
|
|
@@ -368,7 +366,7 @@ async function validateAgentsMap(path13 = "./AGENTS.md") {
|
|
|
368
366
|
|
|
369
367
|
// src/context/doc-coverage.ts
|
|
370
368
|
import { minimatch } from "minimatch";
|
|
371
|
-
import { basename
|
|
369
|
+
import { basename } from "path";
|
|
372
370
|
function determineImportance(filePath) {
|
|
373
371
|
const name = basename(filePath).toLowerCase();
|
|
374
372
|
if (name === "index.ts" || name === "index.js" || name === "main.ts") {
|
|
@@ -408,7 +406,7 @@ async function checkDocCoverage(domain, options = {}) {
|
|
|
408
406
|
try {
|
|
409
407
|
const sourceFiles = await findFiles("**/*.{ts,js,tsx,jsx}", sourceDir);
|
|
410
408
|
const filteredSourceFiles = sourceFiles.filter((file) => {
|
|
411
|
-
const relativePath =
|
|
409
|
+
const relativePath = relativePosix(sourceDir, file);
|
|
412
410
|
return !excludePatterns.some((pattern) => {
|
|
413
411
|
return minimatch(relativePath, pattern, { dot: true }) || minimatch(file, pattern, { dot: true });
|
|
414
412
|
});
|
|
@@ -431,7 +429,7 @@ async function checkDocCoverage(domain, options = {}) {
|
|
|
431
429
|
const undocumented = [];
|
|
432
430
|
const gaps = [];
|
|
433
431
|
for (const sourceFile of filteredSourceFiles) {
|
|
434
|
-
const relativePath =
|
|
432
|
+
const relativePath = relativePosix(sourceDir, sourceFile);
|
|
435
433
|
const fileName = basename(sourceFile);
|
|
436
434
|
const isDocumented = documentedPaths.has(relativePath) || documentedPaths.has(fileName) || documentedPaths.has(`src/${relativePath}`);
|
|
437
435
|
if (isDocumented) {
|
|
@@ -467,9 +465,9 @@ async function checkDocCoverage(domain, options = {}) {
|
|
|
467
465
|
}
|
|
468
466
|
|
|
469
467
|
// src/context/knowledge-map.ts
|
|
470
|
-
import { join as join2, basename as basename2
|
|
471
|
-
function suggestFix(
|
|
472
|
-
const targetName = basename2(
|
|
468
|
+
import { join as join2, basename as basename2 } from "path";
|
|
469
|
+
function suggestFix(path20, existingFiles) {
|
|
470
|
+
const targetName = basename2(path20).toLowerCase();
|
|
473
471
|
const similar = existingFiles.find((file) => {
|
|
474
472
|
const fileName = basename2(file).toLowerCase();
|
|
475
473
|
return fileName.includes(targetName) || targetName.includes(fileName);
|
|
@@ -477,12 +475,9 @@ function suggestFix(path13, existingFiles) {
|
|
|
477
475
|
if (similar) {
|
|
478
476
|
return `Did you mean "${similar}"?`;
|
|
479
477
|
}
|
|
480
|
-
return `Create the file "${
|
|
478
|
+
return `Create the file "${path20}" or remove the link`;
|
|
481
479
|
}
|
|
482
480
|
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
481
|
const agentsPath = join2(rootDir, "AGENTS.md");
|
|
487
482
|
const agentsResult = await validateAgentsMap(agentsPath);
|
|
488
483
|
if (!agentsResult.ok) {
|
|
@@ -494,7 +489,7 @@ async function validateKnowledgeMap(rootDir = process.cwd()) {
|
|
|
494
489
|
totalLinks: agentsTotalLinks
|
|
495
490
|
} = agentsResult.value;
|
|
496
491
|
const existingFiles = await findFiles("**/*", rootDir);
|
|
497
|
-
const relativeExistingFiles = existingFiles.map((f) =>
|
|
492
|
+
const relativeExistingFiles = existingFiles.map((f) => relativePosix(rootDir, f));
|
|
498
493
|
const brokenLinks = agentsBrokenLinks.map((link) => {
|
|
499
494
|
const section = sections.find(
|
|
500
495
|
(s) => s.links.some((l) => l.path === link.path && l.line === link.line)
|
|
@@ -519,7 +514,7 @@ async function validateKnowledgeMap(rootDir = process.cwd()) {
|
|
|
519
514
|
}
|
|
520
515
|
|
|
521
516
|
// src/context/generate.ts
|
|
522
|
-
import {
|
|
517
|
+
import { basename as basename3, dirname as dirname2 } from "path";
|
|
523
518
|
var DEFAULT_SECTIONS = [
|
|
524
519
|
{
|
|
525
520
|
name: "Documentation",
|
|
@@ -535,7 +530,7 @@ var DEFAULT_SECTIONS = [
|
|
|
535
530
|
function groupByDirectory(files, rootDir) {
|
|
536
531
|
const groups = /* @__PURE__ */ new Map();
|
|
537
532
|
for (const file of files) {
|
|
538
|
-
const relativePath =
|
|
533
|
+
const relativePath = relativePosix(rootDir, file);
|
|
539
534
|
const dir = dirname2(relativePath);
|
|
540
535
|
if (!groups.has(dir)) {
|
|
541
536
|
groups.set(dir, []);
|
|
@@ -591,7 +586,7 @@ async function generateAgentsMap(config, graphSections) {
|
|
|
591
586
|
allFiles.push(...files);
|
|
592
587
|
}
|
|
593
588
|
const filteredFiles = allFiles.filter((file) => {
|
|
594
|
-
const relativePath =
|
|
589
|
+
const relativePath = relativePosix(rootDir, file);
|
|
595
590
|
return !matchesExcludePattern(relativePath, excludePaths);
|
|
596
591
|
});
|
|
597
592
|
lines.push("## Repository Structure");
|
|
@@ -619,11 +614,11 @@ async function generateAgentsMap(config, graphSections) {
|
|
|
619
614
|
}
|
|
620
615
|
const sectionFiles = await findFiles(section.pattern, rootDir);
|
|
621
616
|
const filteredSectionFiles = sectionFiles.filter((file) => {
|
|
622
|
-
const relativePath =
|
|
617
|
+
const relativePath = relativePosix(rootDir, file);
|
|
623
618
|
return !matchesExcludePattern(relativePath, excludePaths);
|
|
624
619
|
});
|
|
625
620
|
for (const file of filteredSectionFiles.slice(0, 20)) {
|
|
626
|
-
lines.push(formatFileLink(
|
|
621
|
+
lines.push(formatFileLink(relativePosix(rootDir, file)));
|
|
627
622
|
}
|
|
628
623
|
if (filteredSectionFiles.length > 20) {
|
|
629
624
|
lines.push(`- _... and ${filteredSectionFiles.length - 20} more files_`);
|
|
@@ -832,8 +827,8 @@ function createBoundaryValidator(schema, name) {
|
|
|
832
827
|
return Ok(result.data);
|
|
833
828
|
}
|
|
834
829
|
const suggestions = result.error.issues.map((issue) => {
|
|
835
|
-
const
|
|
836
|
-
return
|
|
830
|
+
const path20 = issue.path.join(".");
|
|
831
|
+
return path20 ? `${path20}: ${issue.message}` : issue.message;
|
|
837
832
|
});
|
|
838
833
|
return Err(
|
|
839
834
|
createError(
|
|
@@ -1387,11 +1382,11 @@ function walk(node, visitor) {
|
|
|
1387
1382
|
var TypeScriptParser = class {
|
|
1388
1383
|
name = "typescript";
|
|
1389
1384
|
extensions = [".ts", ".tsx", ".mts", ".cts"];
|
|
1390
|
-
async parseFile(
|
|
1391
|
-
const contentResult = await readFileContent(
|
|
1385
|
+
async parseFile(path20) {
|
|
1386
|
+
const contentResult = await readFileContent(path20);
|
|
1392
1387
|
if (!contentResult.ok) {
|
|
1393
1388
|
return Err(
|
|
1394
|
-
createParseError("NOT_FOUND", `File not found: ${
|
|
1389
|
+
createParseError("NOT_FOUND", `File not found: ${path20}`, { path: path20 }, [
|
|
1395
1390
|
"Check that the file exists",
|
|
1396
1391
|
"Verify the path is correct"
|
|
1397
1392
|
])
|
|
@@ -1401,7 +1396,7 @@ var TypeScriptParser = class {
|
|
|
1401
1396
|
const ast = parse(contentResult.value, {
|
|
1402
1397
|
loc: true,
|
|
1403
1398
|
range: true,
|
|
1404
|
-
jsx:
|
|
1399
|
+
jsx: path20.endsWith(".tsx"),
|
|
1405
1400
|
errorOnUnknownASTType: false
|
|
1406
1401
|
});
|
|
1407
1402
|
return Ok({
|
|
@@ -1412,7 +1407,7 @@ var TypeScriptParser = class {
|
|
|
1412
1407
|
} catch (e) {
|
|
1413
1408
|
const error = e;
|
|
1414
1409
|
return Err(
|
|
1415
|
-
createParseError("SYNTAX_ERROR", `Failed to parse ${
|
|
1410
|
+
createParseError("SYNTAX_ERROR", `Failed to parse ${path20}: ${error.message}`, { path: path20 }, [
|
|
1416
1411
|
"Check for syntax errors in the file",
|
|
1417
1412
|
"Ensure valid TypeScript syntax"
|
|
1418
1413
|
])
|
|
@@ -1578,7 +1573,7 @@ var TypeScriptParser = class {
|
|
|
1578
1573
|
};
|
|
1579
1574
|
|
|
1580
1575
|
// src/entropy/snapshot.ts
|
|
1581
|
-
import { join as join3, resolve
|
|
1576
|
+
import { join as join3, resolve } from "path";
|
|
1582
1577
|
import { minimatch as minimatch2 } from "minimatch";
|
|
1583
1578
|
async function resolveEntryPoints(rootDir, explicitEntries) {
|
|
1584
1579
|
if (explicitEntries && explicitEntries.length > 0) {
|
|
@@ -1696,22 +1691,22 @@ function extractInlineRefs(content) {
|
|
|
1696
1691
|
}
|
|
1697
1692
|
return refs;
|
|
1698
1693
|
}
|
|
1699
|
-
async function parseDocumentationFile(
|
|
1700
|
-
const contentResult = await readFileContent(
|
|
1694
|
+
async function parseDocumentationFile(path20) {
|
|
1695
|
+
const contentResult = await readFileContent(path20);
|
|
1701
1696
|
if (!contentResult.ok) {
|
|
1702
1697
|
return Err(
|
|
1703
1698
|
createEntropyError(
|
|
1704
1699
|
"PARSE_ERROR",
|
|
1705
|
-
`Failed to read documentation file: ${
|
|
1706
|
-
{ file:
|
|
1700
|
+
`Failed to read documentation file: ${path20}`,
|
|
1701
|
+
{ file: path20 },
|
|
1707
1702
|
["Check that the file exists"]
|
|
1708
1703
|
)
|
|
1709
1704
|
);
|
|
1710
1705
|
}
|
|
1711
1706
|
const content = contentResult.value;
|
|
1712
|
-
const type =
|
|
1707
|
+
const type = path20.endsWith(".md") ? "markdown" : "text";
|
|
1713
1708
|
return Ok({
|
|
1714
|
-
path:
|
|
1709
|
+
path: path20,
|
|
1715
1710
|
type,
|
|
1716
1711
|
content,
|
|
1717
1712
|
codeBlocks: extractCodeBlocks(content),
|
|
@@ -1842,7 +1837,7 @@ async function buildSnapshot(config) {
|
|
|
1842
1837
|
sourceFilePaths.push(...files2);
|
|
1843
1838
|
}
|
|
1844
1839
|
sourceFilePaths = sourceFilePaths.filter((f) => {
|
|
1845
|
-
const rel =
|
|
1840
|
+
const rel = relativePosix(rootDir, f);
|
|
1846
1841
|
return !excludePatterns.some((p) => minimatch2(rel, p));
|
|
1847
1842
|
});
|
|
1848
1843
|
const files = [];
|
|
@@ -2374,9 +2369,8 @@ async function detectDeadCode(snapshot, graphDeadCodeData) {
|
|
|
2374
2369
|
|
|
2375
2370
|
// src/entropy/detectors/patterns.ts
|
|
2376
2371
|
import { minimatch as minimatch3 } from "minimatch";
|
|
2377
|
-
import { relative as relative5 } from "path";
|
|
2378
2372
|
function fileMatchesPattern(filePath, pattern, rootDir) {
|
|
2379
|
-
const relativePath =
|
|
2373
|
+
const relativePath = relativePosix(rootDir, filePath);
|
|
2380
2374
|
return minimatch3(relativePath, pattern);
|
|
2381
2375
|
}
|
|
2382
2376
|
function checkConfigPattern(pattern, file, rootDir) {
|
|
@@ -2522,15 +2516,34 @@ async function detectPatternViolations(snapshot, config) {
|
|
|
2522
2516
|
}
|
|
2523
2517
|
}
|
|
2524
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
|
+
}
|
|
2525
2536
|
const errorCount = violations.filter((v) => v.severity === "error").length;
|
|
2526
2537
|
const warningCount = violations.filter((v) => v.severity === "warning").length;
|
|
2527
|
-
const
|
|
2528
|
-
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;
|
|
2529
2542
|
return Ok({
|
|
2530
2543
|
violations,
|
|
2531
2544
|
stats: {
|
|
2532
2545
|
filesChecked: snapshot.files.length,
|
|
2533
|
-
patternsApplied:
|
|
2546
|
+
patternsApplied: allPatternsCount,
|
|
2534
2547
|
violationCount: violations.length,
|
|
2535
2548
|
errorCount,
|
|
2536
2549
|
warningCount
|
|
@@ -4713,10 +4726,13 @@ var DEFAULT_STATE = {
|
|
|
4713
4726
|
progress: {}
|
|
4714
4727
|
};
|
|
4715
4728
|
|
|
4716
|
-
// src/state/state-
|
|
4717
|
-
import * as
|
|
4718
|
-
import * as
|
|
4719
|
-
|
|
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";
|
|
4720
4736
|
|
|
4721
4737
|
// src/state/stream-resolver.ts
|
|
4722
4738
|
import * as fs5 from "fs";
|
|
@@ -4742,10 +4758,20 @@ var DEFAULT_STREAM_INDEX = {
|
|
|
4742
4758
|
streams: {}
|
|
4743
4759
|
};
|
|
4744
4760
|
|
|
4745
|
-
// src/state/
|
|
4761
|
+
// src/state/constants.ts
|
|
4746
4762
|
var HARNESS_DIR = ".harness";
|
|
4747
|
-
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";
|
|
4748
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";
|
|
4749
4775
|
var STREAM_NAME_REGEX = /^[a-z0-9][a-z0-9._-]*$/;
|
|
4750
4776
|
function streamsDir(projectPath) {
|
|
4751
4777
|
return path2.join(projectPath, HARNESS_DIR, STREAMS_DIR);
|
|
@@ -4972,26 +4998,65 @@ async function migrateToStreams(projectPath) {
|
|
|
4972
4998
|
return saveStreamIndex(projectPath, index);
|
|
4973
4999
|
}
|
|
4974
5000
|
|
|
4975
|
-
// src/state/
|
|
4976
|
-
|
|
4977
|
-
|
|
4978
|
-
|
|
4979
|
-
|
|
4980
|
-
|
|
4981
|
-
|
|
4982
|
-
|
|
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
|
|
4983
5046
|
var MAX_CACHE_ENTRIES = 8;
|
|
4984
|
-
var learningsCacheMap = /* @__PURE__ */ new Map();
|
|
4985
|
-
var failuresCacheMap = /* @__PURE__ */ new Map();
|
|
4986
5047
|
function evictIfNeeded(map) {
|
|
4987
5048
|
if (map.size > MAX_CACHE_ENTRIES) {
|
|
4988
5049
|
const oldest = map.keys().next().value;
|
|
4989
5050
|
if (oldest !== void 0) map.delete(oldest);
|
|
4990
5051
|
}
|
|
4991
5052
|
}
|
|
4992
|
-
async function getStateDir(projectPath, stream) {
|
|
4993
|
-
|
|
4994
|
-
|
|
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);
|
|
4995
5060
|
if (stream || hasStreams) {
|
|
4996
5061
|
const result = await resolveStreamPath(projectPath, stream ? { stream } : void 0);
|
|
4997
5062
|
if (result.ok) {
|
|
@@ -5001,18 +5066,20 @@ async function getStateDir(projectPath, stream) {
|
|
|
5001
5066
|
return result;
|
|
5002
5067
|
}
|
|
5003
5068
|
}
|
|
5004
|
-
return Ok(
|
|
5069
|
+
return Ok(path4.join(projectPath, HARNESS_DIR));
|
|
5005
5070
|
}
|
|
5006
|
-
|
|
5071
|
+
|
|
5072
|
+
// src/state/state-persistence.ts
|
|
5073
|
+
async function loadState(projectPath, stream, session) {
|
|
5007
5074
|
try {
|
|
5008
|
-
const dirResult = await getStateDir(projectPath, stream);
|
|
5075
|
+
const dirResult = await getStateDir(projectPath, stream, session);
|
|
5009
5076
|
if (!dirResult.ok) return dirResult;
|
|
5010
5077
|
const stateDir = dirResult.value;
|
|
5011
|
-
const statePath =
|
|
5012
|
-
if (!
|
|
5078
|
+
const statePath = path5.join(stateDir, STATE_FILE);
|
|
5079
|
+
if (!fs8.existsSync(statePath)) {
|
|
5013
5080
|
return Ok({ ...DEFAULT_STATE });
|
|
5014
5081
|
}
|
|
5015
|
-
const raw =
|
|
5082
|
+
const raw = fs8.readFileSync(statePath, "utf-8");
|
|
5016
5083
|
const parsed = JSON.parse(raw);
|
|
5017
5084
|
const result = HarnessStateSchema.safeParse(parsed);
|
|
5018
5085
|
if (!result.success) {
|
|
@@ -5025,14 +5092,14 @@ async function loadState(projectPath, stream) {
|
|
|
5025
5092
|
);
|
|
5026
5093
|
}
|
|
5027
5094
|
}
|
|
5028
|
-
async function saveState(projectPath, state, stream) {
|
|
5095
|
+
async function saveState(projectPath, state, stream, session) {
|
|
5029
5096
|
try {
|
|
5030
|
-
const dirResult = await getStateDir(projectPath, stream);
|
|
5097
|
+
const dirResult = await getStateDir(projectPath, stream, session);
|
|
5031
5098
|
if (!dirResult.ok) return dirResult;
|
|
5032
5099
|
const stateDir = dirResult.value;
|
|
5033
|
-
const statePath =
|
|
5034
|
-
|
|
5035
|
-
|
|
5100
|
+
const statePath = path5.join(stateDir, STATE_FILE);
|
|
5101
|
+
fs8.mkdirSync(stateDir, { recursive: true });
|
|
5102
|
+
fs8.writeFileSync(statePath, JSON.stringify(state, null, 2));
|
|
5036
5103
|
return Ok(void 0);
|
|
5037
5104
|
} catch (error) {
|
|
5038
5105
|
return Err(
|
|
@@ -5040,13 +5107,21 @@ async function saveState(projectPath, state, stream) {
|
|
|
5040
5107
|
);
|
|
5041
5108
|
}
|
|
5042
5109
|
}
|
|
5043
|
-
|
|
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) {
|
|
5044
5119
|
try {
|
|
5045
|
-
const dirResult = await getStateDir(projectPath, stream);
|
|
5120
|
+
const dirResult = await getStateDir(projectPath, stream, session);
|
|
5046
5121
|
if (!dirResult.ok) return dirResult;
|
|
5047
5122
|
const stateDir = dirResult.value;
|
|
5048
|
-
const learningsPath =
|
|
5049
|
-
|
|
5123
|
+
const learningsPath = path6.join(stateDir, LEARNINGS_FILE);
|
|
5124
|
+
fs9.mkdirSync(stateDir, { recursive: true });
|
|
5050
5125
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
5051
5126
|
let entry;
|
|
5052
5127
|
if (skillName && outcome) {
|
|
@@ -5062,11 +5137,11 @@ async function appendLearning(projectPath, learning, skillName, outcome, stream)
|
|
|
5062
5137
|
- **${timestamp}:** ${learning}
|
|
5063
5138
|
`;
|
|
5064
5139
|
}
|
|
5065
|
-
if (!
|
|
5066
|
-
|
|
5140
|
+
if (!fs9.existsSync(learningsPath)) {
|
|
5141
|
+
fs9.writeFileSync(learningsPath, `# Learnings
|
|
5067
5142
|
${entry}`);
|
|
5068
5143
|
} else {
|
|
5069
|
-
|
|
5144
|
+
fs9.appendFileSync(learningsPath, entry);
|
|
5070
5145
|
}
|
|
5071
5146
|
learningsCacheMap.delete(learningsPath);
|
|
5072
5147
|
return Ok(void 0);
|
|
@@ -5078,23 +5153,92 @@ ${entry}`);
|
|
|
5078
5153
|
);
|
|
5079
5154
|
}
|
|
5080
5155
|
}
|
|
5081
|
-
|
|
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) {
|
|
5082
5226
|
try {
|
|
5083
|
-
const dirResult = await getStateDir(projectPath, stream);
|
|
5227
|
+
const dirResult = await getStateDir(projectPath, stream, session);
|
|
5084
5228
|
if (!dirResult.ok) return dirResult;
|
|
5085
5229
|
const stateDir = dirResult.value;
|
|
5086
|
-
const learningsPath =
|
|
5087
|
-
if (!
|
|
5230
|
+
const learningsPath = path6.join(stateDir, LEARNINGS_FILE);
|
|
5231
|
+
if (!fs9.existsSync(learningsPath)) {
|
|
5088
5232
|
return Ok([]);
|
|
5089
5233
|
}
|
|
5090
|
-
const stats =
|
|
5234
|
+
const stats = fs9.statSync(learningsPath);
|
|
5091
5235
|
const cacheKey = learningsPath;
|
|
5092
5236
|
const cached = learningsCacheMap.get(cacheKey);
|
|
5093
5237
|
let entries;
|
|
5094
5238
|
if (cached && cached.mtimeMs === stats.mtimeMs) {
|
|
5095
5239
|
entries = cached.entries;
|
|
5096
5240
|
} else {
|
|
5097
|
-
const content =
|
|
5241
|
+
const content = fs9.readFileSync(learningsPath, "utf-8");
|
|
5098
5242
|
const lines = content.split("\n");
|
|
5099
5243
|
entries = [];
|
|
5100
5244
|
let currentBlock = [];
|
|
@@ -5130,23 +5274,110 @@ async function loadRelevantLearnings(projectPath, skillName, stream) {
|
|
|
5130
5274
|
);
|
|
5131
5275
|
}
|
|
5132
5276
|
}
|
|
5133
|
-
|
|
5134
|
-
|
|
5277
|
+
async function archiveLearnings(projectPath, entries, stream) {
|
|
5278
|
+
try {
|
|
5279
|
+
const dirResult = await getStateDir(projectPath, stream);
|
|
5280
|
+
if (!dirResult.ok) return dirResult;
|
|
5281
|
+
const stateDir = dirResult.value;
|
|
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) {
|
|
5135
5305
|
try {
|
|
5136
5306
|
const dirResult = await getStateDir(projectPath, stream);
|
|
5137
5307
|
if (!dirResult.ok) return dirResult;
|
|
5138
5308
|
const stateDir = dirResult.value;
|
|
5139
|
-
const
|
|
5140
|
-
|
|
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 });
|
|
5141
5372
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
5142
5373
|
const entry = `
|
|
5143
5374
|
- **${timestamp} [skill:${skillName}] [type:${type}]:** ${description}
|
|
5144
5375
|
`;
|
|
5145
|
-
if (!
|
|
5146
|
-
|
|
5376
|
+
if (!fs10.existsSync(failuresPath)) {
|
|
5377
|
+
fs10.writeFileSync(failuresPath, `# Failures
|
|
5147
5378
|
${entry}`);
|
|
5148
5379
|
} else {
|
|
5149
|
-
|
|
5380
|
+
fs10.appendFileSync(failuresPath, entry);
|
|
5150
5381
|
}
|
|
5151
5382
|
failuresCacheMap.delete(failuresPath);
|
|
5152
5383
|
return Ok(void 0);
|
|
@@ -5158,22 +5389,22 @@ ${entry}`);
|
|
|
5158
5389
|
);
|
|
5159
5390
|
}
|
|
5160
5391
|
}
|
|
5161
|
-
async function loadFailures(projectPath, stream) {
|
|
5392
|
+
async function loadFailures(projectPath, stream, session) {
|
|
5162
5393
|
try {
|
|
5163
|
-
const dirResult = await getStateDir(projectPath, stream);
|
|
5394
|
+
const dirResult = await getStateDir(projectPath, stream, session);
|
|
5164
5395
|
if (!dirResult.ok) return dirResult;
|
|
5165
5396
|
const stateDir = dirResult.value;
|
|
5166
|
-
const failuresPath =
|
|
5167
|
-
if (!
|
|
5397
|
+
const failuresPath = path7.join(stateDir, FAILURES_FILE);
|
|
5398
|
+
if (!fs10.existsSync(failuresPath)) {
|
|
5168
5399
|
return Ok([]);
|
|
5169
5400
|
}
|
|
5170
|
-
const stats =
|
|
5401
|
+
const stats = fs10.statSync(failuresPath);
|
|
5171
5402
|
const cacheKey = failuresPath;
|
|
5172
5403
|
const cached = failuresCacheMap.get(cacheKey);
|
|
5173
5404
|
if (cached && cached.mtimeMs === stats.mtimeMs) {
|
|
5174
5405
|
return Ok(cached.entries);
|
|
5175
5406
|
}
|
|
5176
|
-
const content =
|
|
5407
|
+
const content = fs10.readFileSync(failuresPath, "utf-8");
|
|
5177
5408
|
const entries = [];
|
|
5178
5409
|
for (const line of content.split("\n")) {
|
|
5179
5410
|
const match = line.match(FAILURE_LINE_REGEX);
|
|
@@ -5197,25 +5428,25 @@ async function loadFailures(projectPath, stream) {
|
|
|
5197
5428
|
);
|
|
5198
5429
|
}
|
|
5199
5430
|
}
|
|
5200
|
-
async function archiveFailures(projectPath, stream) {
|
|
5431
|
+
async function archiveFailures(projectPath, stream, session) {
|
|
5201
5432
|
try {
|
|
5202
|
-
const dirResult = await getStateDir(projectPath, stream);
|
|
5433
|
+
const dirResult = await getStateDir(projectPath, stream, session);
|
|
5203
5434
|
if (!dirResult.ok) return dirResult;
|
|
5204
5435
|
const stateDir = dirResult.value;
|
|
5205
|
-
const failuresPath =
|
|
5206
|
-
if (!
|
|
5436
|
+
const failuresPath = path7.join(stateDir, FAILURES_FILE);
|
|
5437
|
+
if (!fs10.existsSync(failuresPath)) {
|
|
5207
5438
|
return Ok(void 0);
|
|
5208
5439
|
}
|
|
5209
|
-
const archiveDir =
|
|
5210
|
-
|
|
5440
|
+
const archiveDir = path7.join(stateDir, "archive");
|
|
5441
|
+
fs10.mkdirSync(archiveDir, { recursive: true });
|
|
5211
5442
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
5212
5443
|
let archiveName = `failures-${date}.md`;
|
|
5213
5444
|
let counter = 2;
|
|
5214
|
-
while (
|
|
5445
|
+
while (fs10.existsSync(path7.join(archiveDir, archiveName))) {
|
|
5215
5446
|
archiveName = `failures-${date}-${counter}.md`;
|
|
5216
5447
|
counter++;
|
|
5217
5448
|
}
|
|
5218
|
-
|
|
5449
|
+
fs10.renameSync(failuresPath, path7.join(archiveDir, archiveName));
|
|
5219
5450
|
failuresCacheMap.delete(failuresPath);
|
|
5220
5451
|
return Ok(void 0);
|
|
5221
5452
|
} catch (error) {
|
|
@@ -5226,14 +5457,18 @@ async function archiveFailures(projectPath, stream) {
|
|
|
5226
5457
|
);
|
|
5227
5458
|
}
|
|
5228
5459
|
}
|
|
5229
|
-
|
|
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) {
|
|
5230
5465
|
try {
|
|
5231
|
-
const dirResult = await getStateDir(projectPath, stream);
|
|
5466
|
+
const dirResult = await getStateDir(projectPath, stream, session);
|
|
5232
5467
|
if (!dirResult.ok) return dirResult;
|
|
5233
5468
|
const stateDir = dirResult.value;
|
|
5234
|
-
const handoffPath =
|
|
5235
|
-
|
|
5236
|
-
|
|
5469
|
+
const handoffPath = path8.join(stateDir, HANDOFF_FILE);
|
|
5470
|
+
fs11.mkdirSync(stateDir, { recursive: true });
|
|
5471
|
+
fs11.writeFileSync(handoffPath, JSON.stringify(handoff, null, 2));
|
|
5237
5472
|
return Ok(void 0);
|
|
5238
5473
|
} catch (error) {
|
|
5239
5474
|
return Err(
|
|
@@ -5241,16 +5476,16 @@ async function saveHandoff(projectPath, handoff, stream) {
|
|
|
5241
5476
|
);
|
|
5242
5477
|
}
|
|
5243
5478
|
}
|
|
5244
|
-
async function loadHandoff(projectPath, stream) {
|
|
5479
|
+
async function loadHandoff(projectPath, stream, session) {
|
|
5245
5480
|
try {
|
|
5246
|
-
const dirResult = await getStateDir(projectPath, stream);
|
|
5481
|
+
const dirResult = await getStateDir(projectPath, stream, session);
|
|
5247
5482
|
if (!dirResult.ok) return dirResult;
|
|
5248
5483
|
const stateDir = dirResult.value;
|
|
5249
|
-
const handoffPath =
|
|
5250
|
-
if (!
|
|
5484
|
+
const handoffPath = path8.join(stateDir, HANDOFF_FILE);
|
|
5485
|
+
if (!fs11.existsSync(handoffPath)) {
|
|
5251
5486
|
return Ok(null);
|
|
5252
5487
|
}
|
|
5253
|
-
const raw =
|
|
5488
|
+
const raw = fs11.readFileSync(handoffPath, "utf-8");
|
|
5254
5489
|
const parsed = JSON.parse(raw);
|
|
5255
5490
|
const result = HandoffSchema.safeParse(parsed);
|
|
5256
5491
|
if (!result.success) {
|
|
@@ -5263,73 +5498,82 @@ async function loadHandoff(projectPath, stream) {
|
|
|
5263
5498
|
);
|
|
5264
5499
|
}
|
|
5265
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
|
+
}
|
|
5266
5568
|
async function runMechanicalGate(projectPath) {
|
|
5267
|
-
const harnessDir =
|
|
5268
|
-
const gateConfigPath =
|
|
5569
|
+
const harnessDir = path9.join(projectPath, HARNESS_DIR);
|
|
5570
|
+
const gateConfigPath = path9.join(harnessDir, GATE_CONFIG_FILE);
|
|
5269
5571
|
try {
|
|
5270
|
-
let checks =
|
|
5271
|
-
if (fs6.existsSync(gateConfigPath)) {
|
|
5272
|
-
const raw = JSON.parse(fs6.readFileSync(gateConfigPath, "utf-8"));
|
|
5273
|
-
const config = GateConfigSchema.safeParse(raw);
|
|
5274
|
-
if (config.success && config.data.checks) {
|
|
5275
|
-
checks = config.data.checks;
|
|
5276
|
-
}
|
|
5277
|
-
}
|
|
5572
|
+
let checks = loadChecksFromConfig(gateConfigPath);
|
|
5278
5573
|
if (checks.length === 0) {
|
|
5279
|
-
|
|
5280
|
-
if (fs6.existsSync(packageJsonPath)) {
|
|
5281
|
-
const pkg = JSON.parse(fs6.readFileSync(packageJsonPath, "utf-8"));
|
|
5282
|
-
const scripts = pkg.scripts || {};
|
|
5283
|
-
if (scripts.test) checks.push({ name: "test", command: "npm test" });
|
|
5284
|
-
if (scripts.lint) checks.push({ name: "lint", command: "npm run lint" });
|
|
5285
|
-
if (scripts.typecheck) checks.push({ name: "typecheck", command: "npm run typecheck" });
|
|
5286
|
-
if (scripts.build) checks.push({ name: "build", command: "npm run build" });
|
|
5287
|
-
}
|
|
5288
|
-
if (fs6.existsSync(path3.join(projectPath, "go.mod"))) {
|
|
5289
|
-
checks.push({ name: "test", command: "go test ./..." });
|
|
5290
|
-
checks.push({ name: "build", command: "go build ./..." });
|
|
5291
|
-
}
|
|
5292
|
-
if (fs6.existsSync(path3.join(projectPath, "pyproject.toml")) || fs6.existsSync(path3.join(projectPath, "setup.py"))) {
|
|
5293
|
-
checks.push({ name: "test", command: "python -m pytest" });
|
|
5294
|
-
}
|
|
5295
|
-
}
|
|
5296
|
-
const results = [];
|
|
5297
|
-
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:.-]+$/;
|
|
5298
|
-
for (const check of checks) {
|
|
5299
|
-
if (!SAFE_GATE_COMMAND.test(check.command)) {
|
|
5300
|
-
results.push({
|
|
5301
|
-
name: check.name,
|
|
5302
|
-
passed: false,
|
|
5303
|
-
command: check.command,
|
|
5304
|
-
output: `Blocked: command does not match safe gate pattern. Allowed prefixes: npm, npx, pnpm, yarn, go, python, python3, make, cargo, gradle, mvn`,
|
|
5305
|
-
duration: 0
|
|
5306
|
-
});
|
|
5307
|
-
continue;
|
|
5308
|
-
}
|
|
5309
|
-
const start = Date.now();
|
|
5310
|
-
try {
|
|
5311
|
-
execSync2(check.command, {
|
|
5312
|
-
cwd: projectPath,
|
|
5313
|
-
stdio: "pipe",
|
|
5314
|
-
timeout: 12e4
|
|
5315
|
-
});
|
|
5316
|
-
results.push({
|
|
5317
|
-
name: check.name,
|
|
5318
|
-
passed: true,
|
|
5319
|
-
command: check.command,
|
|
5320
|
-
duration: Date.now() - start
|
|
5321
|
-
});
|
|
5322
|
-
} catch (error) {
|
|
5323
|
-
const output = error instanceof Error ? error.stderr?.toString() || error.message : String(error);
|
|
5324
|
-
results.push({
|
|
5325
|
-
name: check.name,
|
|
5326
|
-
passed: false,
|
|
5327
|
-
command: check.command,
|
|
5328
|
-
output: output.slice(0, 2e3),
|
|
5329
|
-
duration: Date.now() - start
|
|
5330
|
-
});
|
|
5331
|
-
}
|
|
5574
|
+
checks = discoverChecksFromProject(projectPath);
|
|
5332
5575
|
}
|
|
5576
|
+
const results = checks.map((check) => executeCheck(check, projectPath));
|
|
5333
5577
|
return Ok({
|
|
5334
5578
|
passed: results.length === 0 || results.every((r) => r.passed),
|
|
5335
5579
|
checks: results
|
|
@@ -5343,6 +5587,96 @@ async function runMechanicalGate(projectPath) {
|
|
|
5343
5587
|
}
|
|
5344
5588
|
}
|
|
5345
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
|
+
|
|
5346
5680
|
// src/workflow/runner.ts
|
|
5347
5681
|
async function executeWorkflow(workflow, executor) {
|
|
5348
5682
|
const stepResults = [];
|
|
@@ -5492,7 +5826,7 @@ async function runMultiTurnPipeline(initialContext, turnExecutor, options) {
|
|
|
5492
5826
|
}
|
|
5493
5827
|
|
|
5494
5828
|
// src/security/scanner.ts
|
|
5495
|
-
import * as
|
|
5829
|
+
import * as fs15 from "fs/promises";
|
|
5496
5830
|
|
|
5497
5831
|
// src/security/rules/registry.ts
|
|
5498
5832
|
var RuleRegistry = class {
|
|
@@ -5579,15 +5913,15 @@ function resolveRuleSeverity(ruleId, defaultSeverity, overrides, strict) {
|
|
|
5579
5913
|
}
|
|
5580
5914
|
|
|
5581
5915
|
// src/security/stack-detector.ts
|
|
5582
|
-
import * as
|
|
5583
|
-
import * as
|
|
5916
|
+
import * as fs14 from "fs";
|
|
5917
|
+
import * as path11 from "path";
|
|
5584
5918
|
function detectStack(projectRoot) {
|
|
5585
5919
|
const stacks = [];
|
|
5586
|
-
const pkgJsonPath =
|
|
5587
|
-
if (
|
|
5920
|
+
const pkgJsonPath = path11.join(projectRoot, "package.json");
|
|
5921
|
+
if (fs14.existsSync(pkgJsonPath)) {
|
|
5588
5922
|
stacks.push("node");
|
|
5589
5923
|
try {
|
|
5590
|
-
const pkgJson = JSON.parse(
|
|
5924
|
+
const pkgJson = JSON.parse(fs14.readFileSync(pkgJsonPath, "utf-8"));
|
|
5591
5925
|
const allDeps = {
|
|
5592
5926
|
...pkgJson.dependencies,
|
|
5593
5927
|
...pkgJson.devDependencies
|
|
@@ -5602,13 +5936,13 @@ function detectStack(projectRoot) {
|
|
|
5602
5936
|
} catch {
|
|
5603
5937
|
}
|
|
5604
5938
|
}
|
|
5605
|
-
const goModPath =
|
|
5606
|
-
if (
|
|
5939
|
+
const goModPath = path11.join(projectRoot, "go.mod");
|
|
5940
|
+
if (fs14.existsSync(goModPath)) {
|
|
5607
5941
|
stacks.push("go");
|
|
5608
5942
|
}
|
|
5609
|
-
const requirementsPath =
|
|
5610
|
-
const pyprojectPath =
|
|
5611
|
-
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)) {
|
|
5612
5946
|
stacks.push("python");
|
|
5613
5947
|
}
|
|
5614
5948
|
return stacks;
|
|
@@ -6035,7 +6369,7 @@ var SecurityScanner = class {
|
|
|
6035
6369
|
}
|
|
6036
6370
|
async scanFile(filePath) {
|
|
6037
6371
|
if (!this.config.enabled) return [];
|
|
6038
|
-
const content = await
|
|
6372
|
+
const content = await fs15.readFile(filePath, "utf-8");
|
|
6039
6373
|
return this.scanContent(content, filePath, 1);
|
|
6040
6374
|
}
|
|
6041
6375
|
async scanFiles(filePaths) {
|
|
@@ -6060,7 +6394,7 @@ var SecurityScanner = class {
|
|
|
6060
6394
|
};
|
|
6061
6395
|
|
|
6062
6396
|
// src/ci/check-orchestrator.ts
|
|
6063
|
-
import * as
|
|
6397
|
+
import * as path12 from "path";
|
|
6064
6398
|
var ALL_CHECKS = [
|
|
6065
6399
|
"validate",
|
|
6066
6400
|
"deps",
|
|
@@ -6071,238 +6405,270 @@ var ALL_CHECKS = [
|
|
|
6071
6405
|
"phase-gate",
|
|
6072
6406
|
"arch"
|
|
6073
6407
|
];
|
|
6074
|
-
async function
|
|
6075
|
-
const start = Date.now();
|
|
6408
|
+
async function runValidateCheck(projectRoot, config) {
|
|
6076
6409
|
const issues = [];
|
|
6077
|
-
|
|
6078
|
-
|
|
6079
|
-
|
|
6080
|
-
|
|
6081
|
-
|
|
6082
|
-
|
|
6083
|
-
|
|
6084
|
-
|
|
6085
|
-
|
|
6086
|
-
|
|
6087
|
-
|
|
6088
|
-
|
|
6089
|
-
|
|
6090
|
-
|
|
6091
|
-
|
|
6092
|
-
|
|
6093
|
-
|
|
6094
|
-
|
|
6095
|
-
|
|
6096
|
-
|
|
6097
|
-
|
|
6098
|
-
|
|
6099
|
-
|
|
6100
|
-
|
|
6101
|
-
|
|
6410
|
+
const agentsPath = path12.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
|
|
6411
|
+
const result = await validateAgentsMap(agentsPath);
|
|
6412
|
+
if (!result.ok) {
|
|
6413
|
+
issues.push({ severity: "error", message: result.error.message });
|
|
6414
|
+
} else if (!result.value.valid) {
|
|
6415
|
+
if (result.value.errors) {
|
|
6416
|
+
for (const err of result.value.errors) {
|
|
6417
|
+
issues.push({ severity: "error", message: err.message });
|
|
6418
|
+
}
|
|
6419
|
+
}
|
|
6420
|
+
for (const section of result.value.missingSections) {
|
|
6421
|
+
issues.push({ severity: "warning", message: `Missing section: ${section}` });
|
|
6422
|
+
}
|
|
6423
|
+
for (const link of result.value.brokenLinks) {
|
|
6424
|
+
issues.push({
|
|
6425
|
+
severity: "warning",
|
|
6426
|
+
message: `Broken link: ${link.text} \u2192 ${link.path}`,
|
|
6427
|
+
file: link.path
|
|
6428
|
+
});
|
|
6429
|
+
}
|
|
6430
|
+
}
|
|
6431
|
+
return issues;
|
|
6432
|
+
}
|
|
6433
|
+
async function runDepsCheck(projectRoot, config) {
|
|
6434
|
+
const issues = [];
|
|
6435
|
+
const rawLayers = config.layers;
|
|
6436
|
+
if (rawLayers && rawLayers.length > 0) {
|
|
6437
|
+
const parser = new TypeScriptParser();
|
|
6438
|
+
const layers = rawLayers.map(
|
|
6439
|
+
(l) => defineLayer(
|
|
6440
|
+
l.name,
|
|
6441
|
+
Array.isArray(l.patterns) ? l.patterns : [l.pattern],
|
|
6442
|
+
l.allowedDependencies
|
|
6443
|
+
)
|
|
6444
|
+
);
|
|
6445
|
+
const result = await validateDependencies({
|
|
6446
|
+
layers,
|
|
6447
|
+
rootDir: projectRoot,
|
|
6448
|
+
parser
|
|
6449
|
+
});
|
|
6450
|
+
if (!result.ok) {
|
|
6451
|
+
issues.push({ severity: "error", message: result.error.message });
|
|
6452
|
+
} else if (result.value.violations.length > 0) {
|
|
6453
|
+
for (const v of result.value.violations) {
|
|
6454
|
+
issues.push({
|
|
6455
|
+
severity: "error",
|
|
6456
|
+
message: `${v.reason}: ${v.file} imports ${v.imports} (${v.fromLayer} \u2192 ${v.toLayer})`,
|
|
6457
|
+
file: v.file,
|
|
6458
|
+
line: v.line
|
|
6459
|
+
});
|
|
6102
6460
|
}
|
|
6103
|
-
|
|
6104
|
-
|
|
6105
|
-
|
|
6106
|
-
|
|
6107
|
-
|
|
6108
|
-
|
|
6109
|
-
|
|
6110
|
-
|
|
6111
|
-
|
|
6112
|
-
|
|
6113
|
-
|
|
6114
|
-
|
|
6115
|
-
|
|
6116
|
-
|
|
6117
|
-
|
|
6118
|
-
|
|
6119
|
-
|
|
6120
|
-
|
|
6121
|
-
|
|
6122
|
-
|
|
6123
|
-
|
|
6124
|
-
|
|
6125
|
-
|
|
6126
|
-
|
|
6127
|
-
|
|
6128
|
-
|
|
6129
|
-
|
|
6130
|
-
|
|
6131
|
-
|
|
6132
|
-
|
|
6461
|
+
}
|
|
6462
|
+
}
|
|
6463
|
+
return issues;
|
|
6464
|
+
}
|
|
6465
|
+
async function runDocsCheck(projectRoot, config) {
|
|
6466
|
+
const issues = [];
|
|
6467
|
+
const docsDir = path12.join(projectRoot, config.docsDir ?? "docs");
|
|
6468
|
+
const entropyConfig = config.entropy || {};
|
|
6469
|
+
const result = await checkDocCoverage("project", {
|
|
6470
|
+
docsDir,
|
|
6471
|
+
sourceDir: projectRoot,
|
|
6472
|
+
excludePatterns: entropyConfig.excludePatterns || [
|
|
6473
|
+
"**/node_modules/**",
|
|
6474
|
+
"**/dist/**",
|
|
6475
|
+
"**/*.test.ts",
|
|
6476
|
+
"**/fixtures/**"
|
|
6477
|
+
]
|
|
6478
|
+
});
|
|
6479
|
+
if (!result.ok) {
|
|
6480
|
+
issues.push({ severity: "warning", message: result.error.message });
|
|
6481
|
+
} else if (result.value.gaps.length > 0) {
|
|
6482
|
+
for (const gap of result.value.gaps) {
|
|
6483
|
+
issues.push({
|
|
6484
|
+
severity: "warning",
|
|
6485
|
+
message: `Undocumented: ${gap.file} (suggested: ${gap.suggestedSection})`,
|
|
6486
|
+
file: gap.file
|
|
6487
|
+
});
|
|
6488
|
+
}
|
|
6489
|
+
}
|
|
6490
|
+
return issues;
|
|
6491
|
+
}
|
|
6492
|
+
async function runEntropyCheck(projectRoot, _config) {
|
|
6493
|
+
const issues = [];
|
|
6494
|
+
const analyzer = new EntropyAnalyzer({
|
|
6495
|
+
rootDir: projectRoot,
|
|
6496
|
+
analyze: { drift: true, deadCode: true, patterns: false }
|
|
6497
|
+
});
|
|
6498
|
+
const result = await analyzer.analyze();
|
|
6499
|
+
if (!result.ok) {
|
|
6500
|
+
issues.push({ severity: "warning", message: result.error.message });
|
|
6501
|
+
} else {
|
|
6502
|
+
const report = result.value;
|
|
6503
|
+
if (report.drift) {
|
|
6504
|
+
for (const drift of report.drift.drifts) {
|
|
6505
|
+
issues.push({
|
|
6506
|
+
severity: "warning",
|
|
6507
|
+
message: `Doc drift (${drift.type}): ${drift.details}`,
|
|
6508
|
+
file: drift.docFile,
|
|
6509
|
+
line: drift.line
|
|
6510
|
+
});
|
|
6133
6511
|
}
|
|
6134
|
-
|
|
6135
|
-
|
|
6136
|
-
|
|
6137
|
-
|
|
6138
|
-
|
|
6139
|
-
|
|
6140
|
-
|
|
6141
|
-
|
|
6142
|
-
"**/dist/**",
|
|
6143
|
-
"**/*.test.ts",
|
|
6144
|
-
"**/fixtures/**"
|
|
6145
|
-
]
|
|
6512
|
+
}
|
|
6513
|
+
if (report.deadCode) {
|
|
6514
|
+
for (const dead of report.deadCode.deadExports) {
|
|
6515
|
+
issues.push({
|
|
6516
|
+
severity: "warning",
|
|
6517
|
+
message: `Dead export: ${dead.name}`,
|
|
6518
|
+
file: dead.file,
|
|
6519
|
+
line: dead.line
|
|
6146
6520
|
});
|
|
6147
|
-
if (!result.ok) {
|
|
6148
|
-
issues.push({ severity: "warning", message: result.error.message });
|
|
6149
|
-
} else if (result.value.gaps.length > 0) {
|
|
6150
|
-
for (const gap of result.value.gaps) {
|
|
6151
|
-
issues.push({
|
|
6152
|
-
severity: "warning",
|
|
6153
|
-
message: `Undocumented: ${gap.file} (suggested: ${gap.suggestedSection})`,
|
|
6154
|
-
file: gap.file
|
|
6155
|
-
});
|
|
6156
|
-
}
|
|
6157
|
-
}
|
|
6158
|
-
break;
|
|
6159
6521
|
}
|
|
6160
|
-
|
|
6161
|
-
|
|
6162
|
-
|
|
6163
|
-
|
|
6522
|
+
}
|
|
6523
|
+
}
|
|
6524
|
+
return issues;
|
|
6525
|
+
}
|
|
6526
|
+
async function runSecurityCheck(projectRoot, config) {
|
|
6527
|
+
const issues = [];
|
|
6528
|
+
const securityConfig = parseSecurityConfig(config.security);
|
|
6529
|
+
if (!securityConfig.enabled) return issues;
|
|
6530
|
+
const scanner = new SecurityScanner(securityConfig);
|
|
6531
|
+
scanner.configureForProject(projectRoot);
|
|
6532
|
+
const { glob: globFn } = await import("glob");
|
|
6533
|
+
const sourceFiles = await globFn("**/*.{ts,tsx,js,jsx,go,py}", {
|
|
6534
|
+
cwd: projectRoot,
|
|
6535
|
+
ignore: securityConfig.exclude ?? [
|
|
6536
|
+
"**/node_modules/**",
|
|
6537
|
+
"**/dist/**",
|
|
6538
|
+
"**/*.test.ts",
|
|
6539
|
+
"**/fixtures/**"
|
|
6540
|
+
],
|
|
6541
|
+
absolute: true
|
|
6542
|
+
});
|
|
6543
|
+
const scanResult = await scanner.scanFiles(sourceFiles);
|
|
6544
|
+
for (const finding of scanResult.findings) {
|
|
6545
|
+
issues.push({
|
|
6546
|
+
severity: finding.severity === "info" ? "warning" : finding.severity,
|
|
6547
|
+
message: `[${finding.ruleId}] ${finding.message}: ${finding.match}`,
|
|
6548
|
+
file: finding.file,
|
|
6549
|
+
line: finding.line
|
|
6550
|
+
});
|
|
6551
|
+
}
|
|
6552
|
+
return issues;
|
|
6553
|
+
}
|
|
6554
|
+
async function runPerfCheck(projectRoot, config) {
|
|
6555
|
+
const issues = [];
|
|
6556
|
+
const perfConfig = config.performance || {};
|
|
6557
|
+
const perfAnalyzer = new EntropyAnalyzer({
|
|
6558
|
+
rootDir: projectRoot,
|
|
6559
|
+
analyze: {
|
|
6560
|
+
complexity: perfConfig.complexity || true,
|
|
6561
|
+
coupling: perfConfig.coupling || true,
|
|
6562
|
+
sizeBudget: perfConfig.sizeBudget || false
|
|
6563
|
+
}
|
|
6564
|
+
});
|
|
6565
|
+
const perfResult = await perfAnalyzer.analyze();
|
|
6566
|
+
if (!perfResult.ok) {
|
|
6567
|
+
issues.push({ severity: "warning", message: perfResult.error.message });
|
|
6568
|
+
} else {
|
|
6569
|
+
const perfReport = perfResult.value;
|
|
6570
|
+
if (perfReport.complexity) {
|
|
6571
|
+
for (const v of perfReport.complexity.violations) {
|
|
6572
|
+
issues.push({
|
|
6573
|
+
severity: v.severity === "info" ? "warning" : v.severity,
|
|
6574
|
+
message: `[Tier ${v.tier}] ${v.metric}: ${v.function} in ${v.file} (${v.value} > ${v.threshold})`,
|
|
6575
|
+
file: v.file,
|
|
6576
|
+
line: v.line
|
|
6164
6577
|
});
|
|
6165
|
-
const result = await analyzer.analyze();
|
|
6166
|
-
if (!result.ok) {
|
|
6167
|
-
issues.push({ severity: "warning", message: result.error.message });
|
|
6168
|
-
} else {
|
|
6169
|
-
const report = result.value;
|
|
6170
|
-
if (report.drift) {
|
|
6171
|
-
for (const drift of report.drift.drifts) {
|
|
6172
|
-
issues.push({
|
|
6173
|
-
severity: "warning",
|
|
6174
|
-
message: `Doc drift (${drift.type}): ${drift.details}`,
|
|
6175
|
-
file: drift.docFile,
|
|
6176
|
-
line: drift.line
|
|
6177
|
-
});
|
|
6178
|
-
}
|
|
6179
|
-
}
|
|
6180
|
-
if (report.deadCode) {
|
|
6181
|
-
for (const dead of report.deadCode.deadExports) {
|
|
6182
|
-
issues.push({
|
|
6183
|
-
severity: "warning",
|
|
6184
|
-
message: `Dead export: ${dead.name}`,
|
|
6185
|
-
file: dead.file,
|
|
6186
|
-
line: dead.line
|
|
6187
|
-
});
|
|
6188
|
-
}
|
|
6189
|
-
}
|
|
6190
|
-
}
|
|
6191
|
-
break;
|
|
6192
6578
|
}
|
|
6193
|
-
|
|
6194
|
-
|
|
6195
|
-
|
|
6196
|
-
|
|
6197
|
-
|
|
6198
|
-
|
|
6199
|
-
|
|
6200
|
-
cwd: projectRoot,
|
|
6201
|
-
ignore: securityConfig.exclude ?? [
|
|
6202
|
-
"**/node_modules/**",
|
|
6203
|
-
"**/dist/**",
|
|
6204
|
-
"**/*.test.ts",
|
|
6205
|
-
"**/fixtures/**"
|
|
6206
|
-
],
|
|
6207
|
-
absolute: true
|
|
6579
|
+
}
|
|
6580
|
+
if (perfReport.coupling) {
|
|
6581
|
+
for (const v of perfReport.coupling.violations) {
|
|
6582
|
+
issues.push({
|
|
6583
|
+
severity: v.severity === "info" ? "warning" : v.severity,
|
|
6584
|
+
message: `[Tier ${v.tier}] ${v.metric}: ${v.file} (${v.value} > ${v.threshold})`,
|
|
6585
|
+
file: v.file
|
|
6208
6586
|
});
|
|
6209
|
-
const scanResult = await scanner.scanFiles(sourceFiles);
|
|
6210
|
-
for (const finding of scanResult.findings) {
|
|
6211
|
-
issues.push({
|
|
6212
|
-
severity: finding.severity === "info" ? "warning" : finding.severity,
|
|
6213
|
-
message: `[${finding.ruleId}] ${finding.message}: ${finding.match}`,
|
|
6214
|
-
file: finding.file,
|
|
6215
|
-
line: finding.line
|
|
6216
|
-
});
|
|
6217
|
-
}
|
|
6218
|
-
break;
|
|
6219
6587
|
}
|
|
6220
|
-
|
|
6221
|
-
|
|
6222
|
-
|
|
6223
|
-
|
|
6224
|
-
|
|
6225
|
-
|
|
6226
|
-
|
|
6227
|
-
|
|
6228
|
-
|
|
6588
|
+
}
|
|
6589
|
+
}
|
|
6590
|
+
return issues;
|
|
6591
|
+
}
|
|
6592
|
+
async function runPhaseGateCheck(_projectRoot, config) {
|
|
6593
|
+
const issues = [];
|
|
6594
|
+
const phaseGates = config.phaseGates;
|
|
6595
|
+
if (!phaseGates?.enabled) {
|
|
6596
|
+
return issues;
|
|
6597
|
+
}
|
|
6598
|
+
issues.push({
|
|
6599
|
+
severity: "warning",
|
|
6600
|
+
message: "Phase gate is enabled but requires CLI context. Run `harness check-phase-gate` separately for full validation."
|
|
6601
|
+
});
|
|
6602
|
+
return issues;
|
|
6603
|
+
}
|
|
6604
|
+
async function runArchCheck(projectRoot, config) {
|
|
6605
|
+
const issues = [];
|
|
6606
|
+
const rawArchConfig = config.architecture;
|
|
6607
|
+
const archConfig = ArchConfigSchema.parse(rawArchConfig ?? {});
|
|
6608
|
+
if (!archConfig.enabled) return issues;
|
|
6609
|
+
const results = await runAll(archConfig, projectRoot);
|
|
6610
|
+
const baselineManager = new ArchBaselineManager(projectRoot, archConfig.baselinePath);
|
|
6611
|
+
const baseline = baselineManager.load();
|
|
6612
|
+
if (baseline) {
|
|
6613
|
+
const diffResult = diff(results, baseline);
|
|
6614
|
+
if (!diffResult.passed) {
|
|
6615
|
+
for (const v of diffResult.newViolations) {
|
|
6616
|
+
issues.push({
|
|
6617
|
+
severity: v.severity,
|
|
6618
|
+
message: `[${v.category || "arch"}] NEW: ${v.detail}`,
|
|
6619
|
+
file: v.file
|
|
6229
6620
|
});
|
|
6230
|
-
const perfResult = await perfAnalyzer.analyze();
|
|
6231
|
-
if (!perfResult.ok) {
|
|
6232
|
-
issues.push({ severity: "warning", message: perfResult.error.message });
|
|
6233
|
-
} else {
|
|
6234
|
-
const perfReport = perfResult.value;
|
|
6235
|
-
if (perfReport.complexity) {
|
|
6236
|
-
for (const v of perfReport.complexity.violations) {
|
|
6237
|
-
issues.push({
|
|
6238
|
-
severity: v.severity === "info" ? "warning" : v.severity,
|
|
6239
|
-
message: `[Tier ${v.tier}] ${v.metric}: ${v.function} in ${v.file} (${v.value} > ${v.threshold})`,
|
|
6240
|
-
file: v.file,
|
|
6241
|
-
line: v.line
|
|
6242
|
-
});
|
|
6243
|
-
}
|
|
6244
|
-
}
|
|
6245
|
-
if (perfReport.coupling) {
|
|
6246
|
-
for (const v of perfReport.coupling.violations) {
|
|
6247
|
-
issues.push({
|
|
6248
|
-
severity: v.severity === "info" ? "warning" : v.severity,
|
|
6249
|
-
message: `[Tier ${v.tier}] ${v.metric}: ${v.file} (${v.value} > ${v.threshold})`,
|
|
6250
|
-
file: v.file
|
|
6251
|
-
});
|
|
6252
|
-
}
|
|
6253
|
-
}
|
|
6254
|
-
}
|
|
6255
|
-
break;
|
|
6256
6621
|
}
|
|
6257
|
-
|
|
6258
|
-
const phaseGates = config.phaseGates;
|
|
6259
|
-
if (!phaseGates?.enabled) {
|
|
6260
|
-
break;
|
|
6261
|
-
}
|
|
6622
|
+
for (const r of diffResult.regressions) {
|
|
6262
6623
|
issues.push({
|
|
6263
|
-
severity: "
|
|
6264
|
-
message:
|
|
6624
|
+
severity: "error",
|
|
6625
|
+
message: `[${r.category}] REGRESSION: ${r.currentValue} > ${r.baselineValue} (delta: ${r.delta})`
|
|
6265
6626
|
});
|
|
6266
|
-
break;
|
|
6267
6627
|
}
|
|
6268
|
-
|
|
6269
|
-
|
|
6270
|
-
|
|
6271
|
-
|
|
6272
|
-
|
|
6273
|
-
|
|
6274
|
-
|
|
6275
|
-
|
|
6276
|
-
|
|
6277
|
-
if (!diffResult.passed) {
|
|
6278
|
-
for (const v of diffResult.newViolations) {
|
|
6279
|
-
issues.push({
|
|
6280
|
-
severity: v.severity,
|
|
6281
|
-
message: `[${v.category || "arch"}] NEW: ${v.detail}`,
|
|
6282
|
-
file: v.file
|
|
6283
|
-
});
|
|
6284
|
-
}
|
|
6285
|
-
for (const r of diffResult.regressions) {
|
|
6286
|
-
issues.push({
|
|
6287
|
-
severity: "error",
|
|
6288
|
-
message: `[${r.category}] REGRESSION: ${r.currentValue} > ${r.baselineValue} (delta: ${r.delta})`
|
|
6289
|
-
});
|
|
6290
|
-
}
|
|
6291
|
-
}
|
|
6292
|
-
} else {
|
|
6293
|
-
for (const result of results) {
|
|
6294
|
-
for (const v of result.violations) {
|
|
6295
|
-
issues.push({
|
|
6296
|
-
severity: v.severity,
|
|
6297
|
-
message: `[${result.category}] ${v.detail}`,
|
|
6298
|
-
file: v.file
|
|
6299
|
-
});
|
|
6300
|
-
}
|
|
6301
|
-
}
|
|
6302
|
-
}
|
|
6303
|
-
break;
|
|
6628
|
+
}
|
|
6629
|
+
} else {
|
|
6630
|
+
for (const result of results) {
|
|
6631
|
+
for (const v of result.violations) {
|
|
6632
|
+
issues.push({
|
|
6633
|
+
severity: v.severity,
|
|
6634
|
+
message: `[${result.category}] ${v.detail}`,
|
|
6635
|
+
file: v.file
|
|
6636
|
+
});
|
|
6304
6637
|
}
|
|
6305
6638
|
}
|
|
6639
|
+
}
|
|
6640
|
+
return issues;
|
|
6641
|
+
}
|
|
6642
|
+
async function runSingleCheck(name, projectRoot, config) {
|
|
6643
|
+
const start = Date.now();
|
|
6644
|
+
const issues = [];
|
|
6645
|
+
try {
|
|
6646
|
+
switch (name) {
|
|
6647
|
+
case "validate":
|
|
6648
|
+
issues.push(...await runValidateCheck(projectRoot, config));
|
|
6649
|
+
break;
|
|
6650
|
+
case "deps":
|
|
6651
|
+
issues.push(...await runDepsCheck(projectRoot, config));
|
|
6652
|
+
break;
|
|
6653
|
+
case "docs":
|
|
6654
|
+
issues.push(...await runDocsCheck(projectRoot, config));
|
|
6655
|
+
break;
|
|
6656
|
+
case "entropy":
|
|
6657
|
+
issues.push(...await runEntropyCheck(projectRoot, config));
|
|
6658
|
+
break;
|
|
6659
|
+
case "security":
|
|
6660
|
+
issues.push(...await runSecurityCheck(projectRoot, config));
|
|
6661
|
+
break;
|
|
6662
|
+
case "perf":
|
|
6663
|
+
issues.push(...await runPerfCheck(projectRoot, config));
|
|
6664
|
+
break;
|
|
6665
|
+
case "phase-gate":
|
|
6666
|
+
issues.push(...await runPhaseGateCheck(projectRoot, config));
|
|
6667
|
+
break;
|
|
6668
|
+
case "arch":
|
|
6669
|
+
issues.push(...await runArchCheck(projectRoot, config));
|
|
6670
|
+
break;
|
|
6671
|
+
}
|
|
6306
6672
|
} catch (error) {
|
|
6307
6673
|
issues.push({
|
|
6308
6674
|
severity: "error",
|
|
@@ -6370,7 +6736,7 @@ async function runCIChecks(input) {
|
|
|
6370
6736
|
}
|
|
6371
6737
|
|
|
6372
6738
|
// src/review/mechanical-checks.ts
|
|
6373
|
-
import * as
|
|
6739
|
+
import * as path13 from "path";
|
|
6374
6740
|
async function runMechanicalChecks(options) {
|
|
6375
6741
|
const { projectRoot, config, skip = [], changedFiles } = options;
|
|
6376
6742
|
const findings = [];
|
|
@@ -6382,7 +6748,7 @@ async function runMechanicalChecks(options) {
|
|
|
6382
6748
|
};
|
|
6383
6749
|
if (!skip.includes("validate")) {
|
|
6384
6750
|
try {
|
|
6385
|
-
const agentsPath =
|
|
6751
|
+
const agentsPath = path13.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
|
|
6386
6752
|
const result = await validateAgentsMap(agentsPath);
|
|
6387
6753
|
if (!result.ok) {
|
|
6388
6754
|
statuses.validate = "fail";
|
|
@@ -6419,7 +6785,7 @@ async function runMechanicalChecks(options) {
|
|
|
6419
6785
|
statuses.validate = "fail";
|
|
6420
6786
|
findings.push({
|
|
6421
6787
|
tool: "validate",
|
|
6422
|
-
file:
|
|
6788
|
+
file: path13.join(projectRoot, "AGENTS.md"),
|
|
6423
6789
|
message: err instanceof Error ? err.message : String(err),
|
|
6424
6790
|
severity: "error"
|
|
6425
6791
|
});
|
|
@@ -6483,7 +6849,7 @@ async function runMechanicalChecks(options) {
|
|
|
6483
6849
|
(async () => {
|
|
6484
6850
|
const localFindings = [];
|
|
6485
6851
|
try {
|
|
6486
|
-
const docsDir =
|
|
6852
|
+
const docsDir = path13.join(projectRoot, config.docsDir ?? "docs");
|
|
6487
6853
|
const result = await checkDocCoverage("project", { docsDir });
|
|
6488
6854
|
if (!result.ok) {
|
|
6489
6855
|
statuses["check-docs"] = "warn";
|
|
@@ -6510,7 +6876,7 @@ async function runMechanicalChecks(options) {
|
|
|
6510
6876
|
statuses["check-docs"] = "warn";
|
|
6511
6877
|
localFindings.push({
|
|
6512
6878
|
tool: "check-docs",
|
|
6513
|
-
file:
|
|
6879
|
+
file: path13.join(projectRoot, "docs"),
|
|
6514
6880
|
message: err instanceof Error ? err.message : String(err),
|
|
6515
6881
|
severity: "warning"
|
|
6516
6882
|
});
|
|
@@ -6658,7 +7024,7 @@ function detectChangeType(commitMessage, diff2) {
|
|
|
6658
7024
|
}
|
|
6659
7025
|
|
|
6660
7026
|
// src/review/context-scoper.ts
|
|
6661
|
-
import * as
|
|
7027
|
+
import * as path14 from "path";
|
|
6662
7028
|
var ALL_DOMAINS = ["compliance", "bug", "security", "architecture"];
|
|
6663
7029
|
var SECURITY_PATTERNS = /auth|crypto|password|secret|token|session|cookie|hash|encrypt|decrypt|sql|shell|exec|eval/i;
|
|
6664
7030
|
function computeContextBudget(diffLines) {
|
|
@@ -6666,18 +7032,18 @@ function computeContextBudget(diffLines) {
|
|
|
6666
7032
|
return diffLines;
|
|
6667
7033
|
}
|
|
6668
7034
|
function isWithinProject(absPath, projectRoot) {
|
|
6669
|
-
const resolvedRoot =
|
|
6670
|
-
const resolvedPath =
|
|
6671
|
-
return resolvedPath.startsWith(resolvedRoot) || resolvedPath ===
|
|
7035
|
+
const resolvedRoot = path14.resolve(projectRoot) + path14.sep;
|
|
7036
|
+
const resolvedPath = path14.resolve(absPath);
|
|
7037
|
+
return resolvedPath.startsWith(resolvedRoot) || resolvedPath === path14.resolve(projectRoot);
|
|
6672
7038
|
}
|
|
6673
7039
|
async function readContextFile(projectRoot, filePath, reason) {
|
|
6674
|
-
const absPath =
|
|
7040
|
+
const absPath = path14.isAbsolute(filePath) ? filePath : path14.join(projectRoot, filePath);
|
|
6675
7041
|
if (!isWithinProject(absPath, projectRoot)) return null;
|
|
6676
7042
|
const result = await readFileContent(absPath);
|
|
6677
7043
|
if (!result.ok) return null;
|
|
6678
7044
|
const content = result.value;
|
|
6679
7045
|
const lines = content.split("\n").length;
|
|
6680
|
-
const relPath =
|
|
7046
|
+
const relPath = path14.isAbsolute(filePath) ? relativePosix(projectRoot, filePath) : filePath;
|
|
6681
7047
|
return { path: relPath, content, reason, lines };
|
|
6682
7048
|
}
|
|
6683
7049
|
function extractImportSources(content) {
|
|
@@ -6692,18 +7058,18 @@ function extractImportSources(content) {
|
|
|
6692
7058
|
}
|
|
6693
7059
|
async function resolveImportPath(projectRoot, fromFile, importSource) {
|
|
6694
7060
|
if (!importSource.startsWith(".")) return null;
|
|
6695
|
-
const fromDir =
|
|
6696
|
-
const basePath =
|
|
7061
|
+
const fromDir = path14.dirname(path14.join(projectRoot, fromFile));
|
|
7062
|
+
const basePath = path14.resolve(fromDir, importSource);
|
|
6697
7063
|
if (!isWithinProject(basePath, projectRoot)) return null;
|
|
6698
|
-
const relBase =
|
|
7064
|
+
const relBase = relativePosix(projectRoot, basePath);
|
|
6699
7065
|
const candidates = [
|
|
6700
7066
|
relBase + ".ts",
|
|
6701
7067
|
relBase + ".tsx",
|
|
6702
7068
|
relBase + ".mts",
|
|
6703
|
-
|
|
7069
|
+
path14.join(relBase, "index.ts")
|
|
6704
7070
|
];
|
|
6705
7071
|
for (const candidate of candidates) {
|
|
6706
|
-
const absCandidate =
|
|
7072
|
+
const absCandidate = path14.join(projectRoot, candidate);
|
|
6707
7073
|
if (await fileExists(absCandidate)) {
|
|
6708
7074
|
return candidate;
|
|
6709
7075
|
}
|
|
@@ -6711,10 +7077,10 @@ async function resolveImportPath(projectRoot, fromFile, importSource) {
|
|
|
6711
7077
|
return null;
|
|
6712
7078
|
}
|
|
6713
7079
|
async function findTestFiles(projectRoot, sourceFile) {
|
|
6714
|
-
const baseName =
|
|
7080
|
+
const baseName = path14.basename(sourceFile, path14.extname(sourceFile));
|
|
6715
7081
|
const pattern = `**/${baseName}.{test,spec}.{ts,tsx,mts}`;
|
|
6716
7082
|
const results = await findFiles(pattern, projectRoot);
|
|
6717
|
-
return results.map((f) =>
|
|
7083
|
+
return results.map((f) => relativePosix(projectRoot, f));
|
|
6718
7084
|
}
|
|
6719
7085
|
async function gatherImportContext(projectRoot, changedFiles, budget) {
|
|
6720
7086
|
const contextFiles = [];
|
|
@@ -7502,7 +7868,7 @@ async function fanOutReview(options) {
|
|
|
7502
7868
|
}
|
|
7503
7869
|
|
|
7504
7870
|
// src/review/validate-findings.ts
|
|
7505
|
-
import * as
|
|
7871
|
+
import * as path15 from "path";
|
|
7506
7872
|
var DOWNGRADE_MAP = {
|
|
7507
7873
|
critical: "important",
|
|
7508
7874
|
important: "suggestion",
|
|
@@ -7523,7 +7889,7 @@ function normalizePath(filePath, projectRoot) {
|
|
|
7523
7889
|
let normalized = filePath;
|
|
7524
7890
|
normalized = normalized.replace(/\\/g, "/");
|
|
7525
7891
|
const normalizedRoot = projectRoot.replace(/\\/g, "/");
|
|
7526
|
-
if (
|
|
7892
|
+
if (path15.isAbsolute(normalized)) {
|
|
7527
7893
|
const root = normalizedRoot.endsWith("/") ? normalizedRoot : normalizedRoot + "/";
|
|
7528
7894
|
if (normalized.startsWith(root)) {
|
|
7529
7895
|
normalized = normalized.slice(root.length);
|
|
@@ -7548,12 +7914,12 @@ function followImportChain(fromFile, fileContents, maxDepth = 2) {
|
|
|
7548
7914
|
while ((match = importRegex.exec(content)) !== null) {
|
|
7549
7915
|
const importPath = match[1];
|
|
7550
7916
|
if (!importPath.startsWith(".")) continue;
|
|
7551
|
-
const dir =
|
|
7552
|
-
let resolved =
|
|
7917
|
+
const dir = path15.dirname(current.file);
|
|
7918
|
+
let resolved = path15.join(dir, importPath).replace(/\\/g, "/");
|
|
7553
7919
|
if (!resolved.match(/\.(ts|tsx|js|jsx)$/)) {
|
|
7554
7920
|
resolved += ".ts";
|
|
7555
7921
|
}
|
|
7556
|
-
resolved =
|
|
7922
|
+
resolved = path15.normalize(resolved).replace(/\\/g, "/");
|
|
7557
7923
|
if (!visited.has(resolved) && current.depth + 1 <= maxDepth) {
|
|
7558
7924
|
queue.push({ file: resolved, depth: current.depth + 1 });
|
|
7559
7925
|
}
|
|
@@ -7570,7 +7936,7 @@ async function validateFindings(options) {
|
|
|
7570
7936
|
if (exclusionSet.isExcluded(normalizedFile, finding.lineRange) || exclusionSet.isExcluded(finding.file, finding.lineRange)) {
|
|
7571
7937
|
continue;
|
|
7572
7938
|
}
|
|
7573
|
-
const absoluteFile =
|
|
7939
|
+
const absoluteFile = path15.isAbsolute(finding.file) ? finding.file : path15.join(projectRoot, finding.file).replace(/\\/g, "/");
|
|
7574
7940
|
if (exclusionSet.isExcluded(absoluteFile, finding.lineRange)) {
|
|
7575
7941
|
continue;
|
|
7576
7942
|
}
|
|
@@ -8091,6 +8457,8 @@ function parseFrontmatter(raw) {
|
|
|
8091
8457
|
const versionStr = map.get("version");
|
|
8092
8458
|
const lastSynced = map.get("last_synced");
|
|
8093
8459
|
const lastManualEdit = map.get("last_manual_edit");
|
|
8460
|
+
const created = map.get("created");
|
|
8461
|
+
const updated = map.get("updated");
|
|
8094
8462
|
if (!project || !versionStr || !lastSynced || !lastManualEdit) {
|
|
8095
8463
|
return Err2(
|
|
8096
8464
|
new Error(
|
|
@@ -8102,7 +8470,10 @@ function parseFrontmatter(raw) {
|
|
|
8102
8470
|
if (isNaN(version)) {
|
|
8103
8471
|
return Err2(new Error("Frontmatter version must be a number"));
|
|
8104
8472
|
}
|
|
8105
|
-
|
|
8473
|
+
const fm = { project, version, lastSynced, lastManualEdit };
|
|
8474
|
+
if (created) fm.created = created;
|
|
8475
|
+
if (updated) fm.updated = updated;
|
|
8476
|
+
return Ok2(fm);
|
|
8106
8477
|
}
|
|
8107
8478
|
function parseMilestones(body) {
|
|
8108
8479
|
const milestones = [];
|
|
@@ -8110,12 +8481,12 @@ function parseMilestones(body) {
|
|
|
8110
8481
|
const h2Matches = [];
|
|
8111
8482
|
let match;
|
|
8112
8483
|
while ((match = h2Pattern.exec(body)) !== null) {
|
|
8113
|
-
h2Matches.push({ heading: match[1], startIndex: match.index });
|
|
8484
|
+
h2Matches.push({ heading: match[1], startIndex: match.index, fullMatch: match[0] });
|
|
8114
8485
|
}
|
|
8115
8486
|
for (let i = 0; i < h2Matches.length; i++) {
|
|
8116
8487
|
const h2 = h2Matches[i];
|
|
8117
8488
|
const nextStart = i + 1 < h2Matches.length ? h2Matches[i + 1].startIndex : body.length;
|
|
8118
|
-
const sectionBody = body.slice(h2.startIndex + h2.
|
|
8489
|
+
const sectionBody = body.slice(h2.startIndex + h2.fullMatch.length, nextStart);
|
|
8119
8490
|
const isBacklog = h2.heading === "Backlog";
|
|
8120
8491
|
const milestoneName = isBacklog ? "Backlog" : h2.heading.replace(/^Milestone:\s*/, "");
|
|
8121
8492
|
const featuresResult = parseFeatures(sectionBody);
|
|
@@ -8130,19 +8501,16 @@ function parseMilestones(body) {
|
|
|
8130
8501
|
}
|
|
8131
8502
|
function parseFeatures(sectionBody) {
|
|
8132
8503
|
const features = [];
|
|
8133
|
-
const h3Pattern = /^### Feature: (.+)$/gm;
|
|
8504
|
+
const h3Pattern = /^### (?:Feature: )?(.+)$/gm;
|
|
8134
8505
|
const h3Matches = [];
|
|
8135
8506
|
let match;
|
|
8136
8507
|
while ((match = h3Pattern.exec(sectionBody)) !== null) {
|
|
8137
|
-
h3Matches.push({ name: match[1], startIndex: match.index });
|
|
8508
|
+
h3Matches.push({ name: match[1], startIndex: match.index, fullMatch: match[0] });
|
|
8138
8509
|
}
|
|
8139
8510
|
for (let i = 0; i < h3Matches.length; i++) {
|
|
8140
8511
|
const h3 = h3Matches[i];
|
|
8141
8512
|
const nextStart = i + 1 < h3Matches.length ? h3Matches[i + 1].startIndex : sectionBody.length;
|
|
8142
|
-
const featureBody = sectionBody.slice(
|
|
8143
|
-
h3.startIndex + `### Feature: ${h3.name}`.length,
|
|
8144
|
-
nextStart
|
|
8145
|
-
);
|
|
8513
|
+
const featureBody = sectionBody.slice(h3.startIndex + h3.fullMatch.length, nextStart);
|
|
8146
8514
|
const featureResult = parseFeatureFields(h3.name, featureBody);
|
|
8147
8515
|
if (!featureResult.ok) return featureResult;
|
|
8148
8516
|
features.push(featureResult.value);
|
|
@@ -8167,10 +8535,10 @@ function parseFeatureFields(name, body) {
|
|
|
8167
8535
|
const status = statusRaw;
|
|
8168
8536
|
const specRaw = fieldMap.get("Spec") ?? EM_DASH;
|
|
8169
8537
|
const spec = specRaw === EM_DASH ? null : specRaw;
|
|
8170
|
-
const plansRaw = fieldMap.get("Plans") ?? EM_DASH;
|
|
8171
|
-
const plans = plansRaw === EM_DASH ? [] : plansRaw.split(",").map((p) => p.trim());
|
|
8172
|
-
const blockedByRaw = fieldMap.get("Blocked by") ?? EM_DASH;
|
|
8173
|
-
const blockedBy = blockedByRaw === EM_DASH ? [] : blockedByRaw.split(",").map((b) => b.trim());
|
|
8538
|
+
const plansRaw = fieldMap.get("Plans") ?? fieldMap.get("Plan") ?? EM_DASH;
|
|
8539
|
+
const plans = plansRaw === EM_DASH || plansRaw === "none" ? [] : plansRaw.split(",").map((p) => p.trim());
|
|
8540
|
+
const blockedByRaw = fieldMap.get("Blocked by") ?? fieldMap.get("Blockers") ?? EM_DASH;
|
|
8541
|
+
const blockedBy = blockedByRaw === EM_DASH || blockedByRaw === "none" ? [] : blockedByRaw.split(",").map((b) => b.trim());
|
|
8174
8542
|
const summary = fieldMap.get("Summary") ?? "";
|
|
8175
8543
|
return Ok2({ name, status, spec, plans, blockedBy, summary });
|
|
8176
8544
|
}
|
|
@@ -8182,11 +8550,17 @@ function serializeRoadmap(roadmap) {
|
|
|
8182
8550
|
lines.push("---");
|
|
8183
8551
|
lines.push(`project: ${roadmap.frontmatter.project}`);
|
|
8184
8552
|
lines.push(`version: ${roadmap.frontmatter.version}`);
|
|
8553
|
+
if (roadmap.frontmatter.created) {
|
|
8554
|
+
lines.push(`created: ${roadmap.frontmatter.created}`);
|
|
8555
|
+
}
|
|
8556
|
+
if (roadmap.frontmatter.updated) {
|
|
8557
|
+
lines.push(`updated: ${roadmap.frontmatter.updated}`);
|
|
8558
|
+
}
|
|
8185
8559
|
lines.push(`last_synced: ${roadmap.frontmatter.lastSynced}`);
|
|
8186
8560
|
lines.push(`last_manual_edit: ${roadmap.frontmatter.lastManualEdit}`);
|
|
8187
8561
|
lines.push("---");
|
|
8188
8562
|
lines.push("");
|
|
8189
|
-
lines.push("#
|
|
8563
|
+
lines.push("# Roadmap");
|
|
8190
8564
|
for (const milestone of roadmap.milestones) {
|
|
8191
8565
|
lines.push("");
|
|
8192
8566
|
lines.push(serializeMilestoneHeading(milestone));
|
|
@@ -8199,25 +8573,26 @@ function serializeRoadmap(roadmap) {
|
|
|
8199
8573
|
return lines.join("\n");
|
|
8200
8574
|
}
|
|
8201
8575
|
function serializeMilestoneHeading(milestone) {
|
|
8202
|
-
return milestone.isBacklog ? "## Backlog" : `##
|
|
8576
|
+
return milestone.isBacklog ? "## Backlog" : `## ${milestone.name}`;
|
|
8203
8577
|
}
|
|
8204
8578
|
function serializeFeature(feature) {
|
|
8205
8579
|
const spec = feature.spec ?? EM_DASH2;
|
|
8206
8580
|
const plans = feature.plans.length > 0 ? feature.plans.join(", ") : EM_DASH2;
|
|
8207
8581
|
const blockedBy = feature.blockedBy.length > 0 ? feature.blockedBy.join(", ") : EM_DASH2;
|
|
8208
8582
|
return [
|
|
8209
|
-
`###
|
|
8583
|
+
`### ${feature.name}`,
|
|
8584
|
+
"",
|
|
8210
8585
|
`- **Status:** ${feature.status}`,
|
|
8211
8586
|
`- **Spec:** ${spec}`,
|
|
8212
|
-
`- **
|
|
8213
|
-
`- **
|
|
8214
|
-
`- **
|
|
8587
|
+
`- **Summary:** ${feature.summary}`,
|
|
8588
|
+
`- **Blockers:** ${blockedBy}`,
|
|
8589
|
+
`- **Plan:** ${plans}`
|
|
8215
8590
|
];
|
|
8216
8591
|
}
|
|
8217
8592
|
|
|
8218
8593
|
// src/roadmap/sync.ts
|
|
8219
|
-
import * as
|
|
8220
|
-
import * as
|
|
8594
|
+
import * as fs16 from "fs";
|
|
8595
|
+
import * as path16 from "path";
|
|
8221
8596
|
import { Ok as Ok3 } from "@harness-engineering/types";
|
|
8222
8597
|
function inferStatus(feature, projectPath, allFeatures) {
|
|
8223
8598
|
if (feature.blockedBy.length > 0) {
|
|
@@ -8232,10 +8607,10 @@ function inferStatus(feature, projectPath, allFeatures) {
|
|
|
8232
8607
|
const featuresWithPlans = allFeatures.filter((f) => f.plans.length > 0);
|
|
8233
8608
|
const useRootState = featuresWithPlans.length <= 1;
|
|
8234
8609
|
if (useRootState) {
|
|
8235
|
-
const rootStatePath =
|
|
8236
|
-
if (
|
|
8610
|
+
const rootStatePath = path16.join(projectPath, ".harness", "state.json");
|
|
8611
|
+
if (fs16.existsSync(rootStatePath)) {
|
|
8237
8612
|
try {
|
|
8238
|
-
const raw =
|
|
8613
|
+
const raw = fs16.readFileSync(rootStatePath, "utf-8");
|
|
8239
8614
|
const state = JSON.parse(raw);
|
|
8240
8615
|
if (state.progress) {
|
|
8241
8616
|
for (const status of Object.values(state.progress)) {
|
|
@@ -8246,16 +8621,16 @@ function inferStatus(feature, projectPath, allFeatures) {
|
|
|
8246
8621
|
}
|
|
8247
8622
|
}
|
|
8248
8623
|
}
|
|
8249
|
-
const sessionsDir =
|
|
8250
|
-
if (
|
|
8624
|
+
const sessionsDir = path16.join(projectPath, ".harness", "sessions");
|
|
8625
|
+
if (fs16.existsSync(sessionsDir)) {
|
|
8251
8626
|
try {
|
|
8252
|
-
const sessionDirs =
|
|
8627
|
+
const sessionDirs = fs16.readdirSync(sessionsDir, { withFileTypes: true });
|
|
8253
8628
|
for (const entry of sessionDirs) {
|
|
8254
8629
|
if (!entry.isDirectory()) continue;
|
|
8255
|
-
const autopilotPath =
|
|
8256
|
-
if (!
|
|
8630
|
+
const autopilotPath = path16.join(sessionsDir, entry.name, "autopilot-state.json");
|
|
8631
|
+
if (!fs16.existsSync(autopilotPath)) continue;
|
|
8257
8632
|
try {
|
|
8258
|
-
const raw =
|
|
8633
|
+
const raw = fs16.readFileSync(autopilotPath, "utf-8");
|
|
8259
8634
|
const autopilot = JSON.parse(raw);
|
|
8260
8635
|
if (!autopilot.phases) continue;
|
|
8261
8636
|
const linkedPhases = autopilot.phases.filter(
|
|
@@ -8335,17 +8710,17 @@ var EmitInteractionInputSchema = z6.object({
|
|
|
8335
8710
|
});
|
|
8336
8711
|
|
|
8337
8712
|
// src/blueprint/scanner.ts
|
|
8338
|
-
import * as
|
|
8339
|
-
import * as
|
|
8713
|
+
import * as fs17 from "fs/promises";
|
|
8714
|
+
import * as path17 from "path";
|
|
8340
8715
|
var ProjectScanner = class {
|
|
8341
8716
|
constructor(rootDir) {
|
|
8342
8717
|
this.rootDir = rootDir;
|
|
8343
8718
|
}
|
|
8344
8719
|
async scan() {
|
|
8345
|
-
let projectName =
|
|
8720
|
+
let projectName = path17.basename(this.rootDir);
|
|
8346
8721
|
try {
|
|
8347
|
-
const pkgPath =
|
|
8348
|
-
const pkgRaw = await
|
|
8722
|
+
const pkgPath = path17.join(this.rootDir, "package.json");
|
|
8723
|
+
const pkgRaw = await fs17.readFile(pkgPath, "utf-8");
|
|
8349
8724
|
const pkg = JSON.parse(pkgRaw);
|
|
8350
8725
|
if (pkg.name) projectName = pkg.name;
|
|
8351
8726
|
} catch {
|
|
@@ -8386,8 +8761,8 @@ var ProjectScanner = class {
|
|
|
8386
8761
|
};
|
|
8387
8762
|
|
|
8388
8763
|
// src/blueprint/generator.ts
|
|
8389
|
-
import * as
|
|
8390
|
-
import * as
|
|
8764
|
+
import * as fs18 from "fs/promises";
|
|
8765
|
+
import * as path18 from "path";
|
|
8391
8766
|
import * as ejs from "ejs";
|
|
8392
8767
|
|
|
8393
8768
|
// src/blueprint/templates.ts
|
|
@@ -8471,19 +8846,19 @@ var BlueprintGenerator = class {
|
|
|
8471
8846
|
styles: STYLES,
|
|
8472
8847
|
scripts: SCRIPTS
|
|
8473
8848
|
});
|
|
8474
|
-
await
|
|
8475
|
-
await
|
|
8849
|
+
await fs18.mkdir(options.outputDir, { recursive: true });
|
|
8850
|
+
await fs18.writeFile(path18.join(options.outputDir, "index.html"), html);
|
|
8476
8851
|
}
|
|
8477
8852
|
};
|
|
8478
8853
|
|
|
8479
8854
|
// src/update-checker.ts
|
|
8480
|
-
import * as
|
|
8481
|
-
import * as
|
|
8855
|
+
import * as fs19 from "fs";
|
|
8856
|
+
import * as path19 from "path";
|
|
8482
8857
|
import * as os from "os";
|
|
8483
8858
|
import { spawn } from "child_process";
|
|
8484
8859
|
function getStatePath() {
|
|
8485
8860
|
const home = process.env["HOME"] || os.homedir();
|
|
8486
|
-
return
|
|
8861
|
+
return path19.join(home, ".harness", "update-check.json");
|
|
8487
8862
|
}
|
|
8488
8863
|
function isUpdateCheckEnabled(configInterval) {
|
|
8489
8864
|
if (process.env["HARNESS_NO_UPDATE_CHECK"] === "1") return false;
|
|
@@ -8496,7 +8871,7 @@ function shouldRunCheck(state, intervalMs) {
|
|
|
8496
8871
|
}
|
|
8497
8872
|
function readCheckState() {
|
|
8498
8873
|
try {
|
|
8499
|
-
const raw =
|
|
8874
|
+
const raw = fs19.readFileSync(getStatePath(), "utf-8");
|
|
8500
8875
|
const parsed = JSON.parse(raw);
|
|
8501
8876
|
if (typeof parsed === "object" && parsed !== null && "lastCheckTime" in parsed && typeof parsed.lastCheckTime === "number" && "currentVersion" in parsed && typeof parsed.currentVersion === "string") {
|
|
8502
8877
|
const state = parsed;
|
|
@@ -8513,7 +8888,7 @@ function readCheckState() {
|
|
|
8513
8888
|
}
|
|
8514
8889
|
function spawnBackgroundCheck(currentVersion) {
|
|
8515
8890
|
const statePath = getStatePath();
|
|
8516
|
-
const stateDir =
|
|
8891
|
+
const stateDir = path19.dirname(statePath);
|
|
8517
8892
|
const script = `
|
|
8518
8893
|
const { execSync } = require('child_process');
|
|
8519
8894
|
const fs = require('fs');
|
|
@@ -8567,7 +8942,7 @@ Run "harness update" to upgrade.`;
|
|
|
8567
8942
|
}
|
|
8568
8943
|
|
|
8569
8944
|
// src/index.ts
|
|
8570
|
-
var VERSION = "0.
|
|
8945
|
+
var VERSION = "0.13.0";
|
|
8571
8946
|
export {
|
|
8572
8947
|
AGENT_DESCRIPTORS,
|
|
8573
8948
|
ARCHITECTURE_DESCRIPTOR,
|
|
@@ -8644,6 +9019,7 @@ export {
|
|
|
8644
9019
|
ViolationSchema,
|
|
8645
9020
|
addProvenance,
|
|
8646
9021
|
analyzeDiff,
|
|
9022
|
+
analyzeLearningPatterns,
|
|
8647
9023
|
appendFailure,
|
|
8648
9024
|
appendLearning,
|
|
8649
9025
|
applyFixes,
|
|
@@ -8652,6 +9028,7 @@ export {
|
|
|
8652
9028
|
archModule,
|
|
8653
9029
|
architecture,
|
|
8654
9030
|
archiveFailures,
|
|
9031
|
+
archiveLearnings,
|
|
8655
9032
|
archiveStream,
|
|
8656
9033
|
buildDependencyGraph,
|
|
8657
9034
|
buildExclusionSet,
|
|
@@ -8659,6 +9036,8 @@ export {
|
|
|
8659
9036
|
checkDocCoverage,
|
|
8660
9037
|
checkEligibility,
|
|
8661
9038
|
classifyFinding,
|
|
9039
|
+
clearFailuresCache,
|
|
9040
|
+
clearLearningsCache,
|
|
8662
9041
|
configureFeedback,
|
|
8663
9042
|
constraintRuleId,
|
|
8664
9043
|
contextBudget,
|
|
@@ -8714,16 +9093,20 @@ export {
|
|
|
8714
9093
|
injectionRules,
|
|
8715
9094
|
isSmallSuggestion,
|
|
8716
9095
|
isUpdateCheckEnabled,
|
|
9096
|
+
listActiveSessions,
|
|
8717
9097
|
listStreams,
|
|
9098
|
+
loadBudgetedLearnings,
|
|
8718
9099
|
loadFailures,
|
|
8719
9100
|
loadHandoff,
|
|
8720
9101
|
loadRelevantLearnings,
|
|
9102
|
+
loadSessionSummary,
|
|
8721
9103
|
loadState,
|
|
8722
9104
|
loadStreamIndex,
|
|
8723
9105
|
logAgentAction,
|
|
8724
9106
|
migrateToStreams,
|
|
8725
9107
|
networkRules,
|
|
8726
9108
|
nodeRules,
|
|
9109
|
+
parseDateFromEntry,
|
|
8727
9110
|
parseDiff,
|
|
8728
9111
|
parseManifest,
|
|
8729
9112
|
parseRoadmap,
|
|
@@ -8731,6 +9114,7 @@ export {
|
|
|
8731
9114
|
parseSize,
|
|
8732
9115
|
pathTraversalRules,
|
|
8733
9116
|
previewFix,
|
|
9117
|
+
pruneLearnings,
|
|
8734
9118
|
reactRules,
|
|
8735
9119
|
readCheckState,
|
|
8736
9120
|
readLockfile,
|
|
@@ -8742,6 +9126,7 @@ export {
|
|
|
8742
9126
|
resolveFileToLayer,
|
|
8743
9127
|
resolveModelTier,
|
|
8744
9128
|
resolveRuleSeverity,
|
|
9129
|
+
resolveSessionDir,
|
|
8745
9130
|
resolveStreamPath,
|
|
8746
9131
|
resolveThresholds,
|
|
8747
9132
|
runAll,
|
|
@@ -8768,6 +9153,7 @@ export {
|
|
|
8768
9153
|
syncRoadmap,
|
|
8769
9154
|
touchStream,
|
|
8770
9155
|
trackAction,
|
|
9156
|
+
updateSessionIndex,
|
|
8771
9157
|
validateAgentsMap,
|
|
8772
9158
|
validateBoundaries,
|
|
8773
9159
|
validateCommitMessage,
|
|
@@ -8780,5 +9166,6 @@ export {
|
|
|
8780
9166
|
violationId,
|
|
8781
9167
|
writeConfig,
|
|
8782
9168
|
writeLockfile,
|
|
9169
|
+
writeSessionSummary,
|
|
8783
9170
|
xssRules
|
|
8784
9171
|
};
|