@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.js
CHANGED
|
@@ -106,6 +106,7 @@ __export(index_exports, {
|
|
|
106
106
|
ViolationSchema: () => ViolationSchema,
|
|
107
107
|
addProvenance: () => addProvenance,
|
|
108
108
|
analyzeDiff: () => analyzeDiff,
|
|
109
|
+
analyzeLearningPatterns: () => analyzeLearningPatterns,
|
|
109
110
|
appendFailure: () => appendFailure,
|
|
110
111
|
appendLearning: () => appendLearning,
|
|
111
112
|
applyFixes: () => applyFixes,
|
|
@@ -114,6 +115,7 @@ __export(index_exports, {
|
|
|
114
115
|
archModule: () => archModule,
|
|
115
116
|
architecture: () => architecture,
|
|
116
117
|
archiveFailures: () => archiveFailures,
|
|
118
|
+
archiveLearnings: () => archiveLearnings,
|
|
117
119
|
archiveStream: () => archiveStream,
|
|
118
120
|
buildDependencyGraph: () => buildDependencyGraph,
|
|
119
121
|
buildExclusionSet: () => buildExclusionSet,
|
|
@@ -121,6 +123,8 @@ __export(index_exports, {
|
|
|
121
123
|
checkDocCoverage: () => checkDocCoverage,
|
|
122
124
|
checkEligibility: () => checkEligibility,
|
|
123
125
|
classifyFinding: () => classifyFinding,
|
|
126
|
+
clearFailuresCache: () => clearFailuresCache,
|
|
127
|
+
clearLearningsCache: () => clearLearningsCache,
|
|
124
128
|
configureFeedback: () => configureFeedback,
|
|
125
129
|
constraintRuleId: () => constraintRuleId,
|
|
126
130
|
contextBudget: () => contextBudget,
|
|
@@ -176,16 +180,20 @@ __export(index_exports, {
|
|
|
176
180
|
injectionRules: () => injectionRules,
|
|
177
181
|
isSmallSuggestion: () => isSmallSuggestion,
|
|
178
182
|
isUpdateCheckEnabled: () => isUpdateCheckEnabled,
|
|
183
|
+
listActiveSessions: () => listActiveSessions,
|
|
179
184
|
listStreams: () => listStreams,
|
|
185
|
+
loadBudgetedLearnings: () => loadBudgetedLearnings,
|
|
180
186
|
loadFailures: () => loadFailures,
|
|
181
187
|
loadHandoff: () => loadHandoff,
|
|
182
188
|
loadRelevantLearnings: () => loadRelevantLearnings,
|
|
189
|
+
loadSessionSummary: () => loadSessionSummary,
|
|
183
190
|
loadState: () => loadState,
|
|
184
191
|
loadStreamIndex: () => loadStreamIndex,
|
|
185
192
|
logAgentAction: () => logAgentAction,
|
|
186
193
|
migrateToStreams: () => migrateToStreams,
|
|
187
194
|
networkRules: () => networkRules,
|
|
188
195
|
nodeRules: () => nodeRules,
|
|
196
|
+
parseDateFromEntry: () => parseDateFromEntry,
|
|
189
197
|
parseDiff: () => parseDiff,
|
|
190
198
|
parseManifest: () => parseManifest,
|
|
191
199
|
parseRoadmap: () => parseRoadmap,
|
|
@@ -193,6 +201,7 @@ __export(index_exports, {
|
|
|
193
201
|
parseSize: () => parseSize,
|
|
194
202
|
pathTraversalRules: () => pathTraversalRules,
|
|
195
203
|
previewFix: () => previewFix,
|
|
204
|
+
pruneLearnings: () => pruneLearnings,
|
|
196
205
|
reactRules: () => reactRules,
|
|
197
206
|
readCheckState: () => readCheckState,
|
|
198
207
|
readLockfile: () => readLockfile,
|
|
@@ -204,6 +213,7 @@ __export(index_exports, {
|
|
|
204
213
|
resolveFileToLayer: () => resolveFileToLayer,
|
|
205
214
|
resolveModelTier: () => resolveModelTier,
|
|
206
215
|
resolveRuleSeverity: () => resolveRuleSeverity,
|
|
216
|
+
resolveSessionDir: () => resolveSessionDir,
|
|
207
217
|
resolveStreamPath: () => resolveStreamPath,
|
|
208
218
|
resolveThresholds: () => resolveThresholds,
|
|
209
219
|
runAll: () => runAll,
|
|
@@ -230,6 +240,7 @@ __export(index_exports, {
|
|
|
230
240
|
syncRoadmap: () => syncRoadmap,
|
|
231
241
|
touchStream: () => touchStream,
|
|
232
242
|
trackAction: () => trackAction,
|
|
243
|
+
updateSessionIndex: () => updateSessionIndex,
|
|
233
244
|
validateAgentsMap: () => validateAgentsMap,
|
|
234
245
|
validateBoundaries: () => validateBoundaries,
|
|
235
246
|
validateCommitMessage: () => validateCommitMessage,
|
|
@@ -242,6 +253,7 @@ __export(index_exports, {
|
|
|
242
253
|
violationId: () => violationId,
|
|
243
254
|
writeConfig: () => writeConfig,
|
|
244
255
|
writeLockfile: () => writeLockfile,
|
|
256
|
+
writeSessionSummary: () => writeSessionSummary,
|
|
245
257
|
xssRules: () => xssRules
|
|
246
258
|
});
|
|
247
259
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -261,20 +273,21 @@ var import_types = require("@harness-engineering/types");
|
|
|
261
273
|
// src/shared/fs-utils.ts
|
|
262
274
|
var import_fs = require("fs");
|
|
263
275
|
var import_util = require("util");
|
|
276
|
+
var import_node_path = require("path");
|
|
264
277
|
var import_glob = require("glob");
|
|
265
278
|
var accessAsync = (0, import_util.promisify)(import_fs.access);
|
|
266
279
|
var readFileAsync = (0, import_util.promisify)(import_fs.readFile);
|
|
267
|
-
async function fileExists(
|
|
280
|
+
async function fileExists(path20) {
|
|
268
281
|
try {
|
|
269
|
-
await accessAsync(
|
|
282
|
+
await accessAsync(path20, import_fs.constants.F_OK);
|
|
270
283
|
return true;
|
|
271
284
|
} catch {
|
|
272
285
|
return false;
|
|
273
286
|
}
|
|
274
287
|
}
|
|
275
|
-
async function readFileContent(
|
|
288
|
+
async function readFileContent(path20) {
|
|
276
289
|
try {
|
|
277
|
-
const content = await readFileAsync(
|
|
290
|
+
const content = await readFileAsync(path20, "utf-8");
|
|
278
291
|
return (0, import_types.Ok)(content);
|
|
279
292
|
} catch (error) {
|
|
280
293
|
return (0, import_types.Err)(error);
|
|
@@ -283,6 +296,9 @@ async function readFileContent(path13) {
|
|
|
283
296
|
async function findFiles(pattern, cwd = process.cwd()) {
|
|
284
297
|
return (0, import_glob.glob)(pattern, { cwd, absolute: true });
|
|
285
298
|
}
|
|
299
|
+
function relativePosix(from, to) {
|
|
300
|
+
return (0, import_node_path.relative)(from, to).replaceAll("\\", "/");
|
|
301
|
+
}
|
|
286
302
|
|
|
287
303
|
// src/validation/file-structure.ts
|
|
288
304
|
async function validateFileStructure(projectPath, conventions) {
|
|
@@ -322,15 +338,15 @@ function validateConfig(data, schema) {
|
|
|
322
338
|
let message = "Configuration validation failed";
|
|
323
339
|
const suggestions = [];
|
|
324
340
|
if (firstError) {
|
|
325
|
-
const
|
|
326
|
-
const pathDisplay =
|
|
341
|
+
const path20 = firstError.path.join(".");
|
|
342
|
+
const pathDisplay = path20 ? ` at "${path20}"` : "";
|
|
327
343
|
if (firstError.code === "invalid_type") {
|
|
328
344
|
const received = firstError.received;
|
|
329
345
|
const expected = firstError.expected;
|
|
330
346
|
if (received === "undefined") {
|
|
331
347
|
code = "MISSING_FIELD";
|
|
332
348
|
message = `Missing required field${pathDisplay}: ${firstError.message}`;
|
|
333
|
-
suggestions.push(`Field "${
|
|
349
|
+
suggestions.push(`Field "${path20}" is required and must be of type "${expected}"`);
|
|
334
350
|
} else {
|
|
335
351
|
code = "INVALID_TYPE";
|
|
336
352
|
message = `Invalid type${pathDisplay}: ${firstError.message}`;
|
|
@@ -543,30 +559,27 @@ function extractSections(content) {
|
|
|
543
559
|
return result;
|
|
544
560
|
});
|
|
545
561
|
}
|
|
546
|
-
function isExternalLink(
|
|
547
|
-
return
|
|
562
|
+
function isExternalLink(path20) {
|
|
563
|
+
return path20.startsWith("http://") || path20.startsWith("https://") || path20.startsWith("#") || path20.startsWith("mailto:");
|
|
548
564
|
}
|
|
549
565
|
function resolveLinkPath(linkPath, baseDir) {
|
|
550
566
|
return linkPath.startsWith(".") ? (0, import_path.join)(baseDir, linkPath) : linkPath;
|
|
551
567
|
}
|
|
552
|
-
async function validateAgentsMap(
|
|
553
|
-
|
|
554
|
-
"[harness] validateAgentsMap() is deprecated. Use graph-based validation via Assembler.checkCoverage() from @harness-engineering/graph"
|
|
555
|
-
);
|
|
556
|
-
const contentResult = await readFileContent(path13);
|
|
568
|
+
async function validateAgentsMap(path20 = "./AGENTS.md") {
|
|
569
|
+
const contentResult = await readFileContent(path20);
|
|
557
570
|
if (!contentResult.ok) {
|
|
558
571
|
return (0, import_types.Err)(
|
|
559
572
|
createError(
|
|
560
573
|
"PARSE_ERROR",
|
|
561
574
|
`Failed to read AGENTS.md: ${contentResult.error.message}`,
|
|
562
|
-
{ path:
|
|
575
|
+
{ path: path20 },
|
|
563
576
|
["Ensure the file exists", "Check file permissions"]
|
|
564
577
|
)
|
|
565
578
|
);
|
|
566
579
|
}
|
|
567
580
|
const content = contentResult.value;
|
|
568
581
|
const sections = extractSections(content);
|
|
569
|
-
const baseDir = (0, import_path.dirname)(
|
|
582
|
+
const baseDir = (0, import_path.dirname)(path20);
|
|
570
583
|
const sectionTitles = sections.map((s) => s.title);
|
|
571
584
|
const missingSections = REQUIRED_SECTIONS.filter(
|
|
572
585
|
(required) => !sectionTitles.some((title) => title.toLowerCase().includes(required.toLowerCase()))
|
|
@@ -647,7 +660,7 @@ async function checkDocCoverage(domain, options = {}) {
|
|
|
647
660
|
try {
|
|
648
661
|
const sourceFiles = await findFiles("**/*.{ts,js,tsx,jsx}", sourceDir);
|
|
649
662
|
const filteredSourceFiles = sourceFiles.filter((file) => {
|
|
650
|
-
const relativePath = (
|
|
663
|
+
const relativePath = relativePosix(sourceDir, file);
|
|
651
664
|
return !excludePatterns.some((pattern) => {
|
|
652
665
|
return (0, import_minimatch.minimatch)(relativePath, pattern, { dot: true }) || (0, import_minimatch.minimatch)(file, pattern, { dot: true });
|
|
653
666
|
});
|
|
@@ -670,7 +683,7 @@ async function checkDocCoverage(domain, options = {}) {
|
|
|
670
683
|
const undocumented = [];
|
|
671
684
|
const gaps = [];
|
|
672
685
|
for (const sourceFile of filteredSourceFiles) {
|
|
673
|
-
const relativePath = (
|
|
686
|
+
const relativePath = relativePosix(sourceDir, sourceFile);
|
|
674
687
|
const fileName = (0, import_path2.basename)(sourceFile);
|
|
675
688
|
const isDocumented = documentedPaths.has(relativePath) || documentedPaths.has(fileName) || documentedPaths.has(`src/${relativePath}`);
|
|
676
689
|
if (isDocumented) {
|
|
@@ -707,8 +720,8 @@ async function checkDocCoverage(domain, options = {}) {
|
|
|
707
720
|
|
|
708
721
|
// src/context/knowledge-map.ts
|
|
709
722
|
var import_path3 = require("path");
|
|
710
|
-
function suggestFix(
|
|
711
|
-
const targetName = (0, import_path3.basename)(
|
|
723
|
+
function suggestFix(path20, existingFiles) {
|
|
724
|
+
const targetName = (0, import_path3.basename)(path20).toLowerCase();
|
|
712
725
|
const similar = existingFiles.find((file) => {
|
|
713
726
|
const fileName = (0, import_path3.basename)(file).toLowerCase();
|
|
714
727
|
return fileName.includes(targetName) || targetName.includes(fileName);
|
|
@@ -716,12 +729,9 @@ function suggestFix(path13, existingFiles) {
|
|
|
716
729
|
if (similar) {
|
|
717
730
|
return `Did you mean "${similar}"?`;
|
|
718
731
|
}
|
|
719
|
-
return `Create the file "${
|
|
732
|
+
return `Create the file "${path20}" or remove the link`;
|
|
720
733
|
}
|
|
721
734
|
async function validateKnowledgeMap(rootDir = process.cwd()) {
|
|
722
|
-
console.warn(
|
|
723
|
-
"[harness] validateKnowledgeMap() is deprecated. Use graph-based validation via Assembler.checkCoverage() from @harness-engineering/graph"
|
|
724
|
-
);
|
|
725
735
|
const agentsPath = (0, import_path3.join)(rootDir, "AGENTS.md");
|
|
726
736
|
const agentsResult = await validateAgentsMap(agentsPath);
|
|
727
737
|
if (!agentsResult.ok) {
|
|
@@ -733,7 +743,7 @@ async function validateKnowledgeMap(rootDir = process.cwd()) {
|
|
|
733
743
|
totalLinks: agentsTotalLinks
|
|
734
744
|
} = agentsResult.value;
|
|
735
745
|
const existingFiles = await findFiles("**/*", rootDir);
|
|
736
|
-
const relativeExistingFiles = existingFiles.map((f) => (
|
|
746
|
+
const relativeExistingFiles = existingFiles.map((f) => relativePosix(rootDir, f));
|
|
737
747
|
const brokenLinks = agentsBrokenLinks.map((link) => {
|
|
738
748
|
const section = sections.find(
|
|
739
749
|
(s) => s.links.some((l) => l.path === link.path && l.line === link.line)
|
|
@@ -774,7 +784,7 @@ var DEFAULT_SECTIONS = [
|
|
|
774
784
|
function groupByDirectory(files, rootDir) {
|
|
775
785
|
const groups = /* @__PURE__ */ new Map();
|
|
776
786
|
for (const file of files) {
|
|
777
|
-
const relativePath = (
|
|
787
|
+
const relativePath = relativePosix(rootDir, file);
|
|
778
788
|
const dir = (0, import_path4.dirname)(relativePath);
|
|
779
789
|
if (!groups.has(dir)) {
|
|
780
790
|
groups.set(dir, []);
|
|
@@ -830,7 +840,7 @@ async function generateAgentsMap(config, graphSections) {
|
|
|
830
840
|
allFiles.push(...files);
|
|
831
841
|
}
|
|
832
842
|
const filteredFiles = allFiles.filter((file) => {
|
|
833
|
-
const relativePath = (
|
|
843
|
+
const relativePath = relativePosix(rootDir, file);
|
|
834
844
|
return !matchesExcludePattern(relativePath, excludePaths);
|
|
835
845
|
});
|
|
836
846
|
lines.push("## Repository Structure");
|
|
@@ -858,11 +868,11 @@ async function generateAgentsMap(config, graphSections) {
|
|
|
858
868
|
}
|
|
859
869
|
const sectionFiles = await findFiles(section.pattern, rootDir);
|
|
860
870
|
const filteredSectionFiles = sectionFiles.filter((file) => {
|
|
861
|
-
const relativePath = (
|
|
871
|
+
const relativePath = relativePosix(rootDir, file);
|
|
862
872
|
return !matchesExcludePattern(relativePath, excludePaths);
|
|
863
873
|
});
|
|
864
874
|
for (const file of filteredSectionFiles.slice(0, 20)) {
|
|
865
|
-
lines.push(formatFileLink((
|
|
875
|
+
lines.push(formatFileLink(relativePosix(rootDir, file)));
|
|
866
876
|
}
|
|
867
877
|
if (filteredSectionFiles.length > 20) {
|
|
868
878
|
lines.push(`- _... and ${filteredSectionFiles.length - 20} more files_`);
|
|
@@ -1133,8 +1143,8 @@ async function buildDependencyGraph(files, parser, graphDependencyData) {
|
|
|
1133
1143
|
function checkLayerViolations(graph, layers, rootDir) {
|
|
1134
1144
|
const violations = [];
|
|
1135
1145
|
for (const edge of graph.edges) {
|
|
1136
|
-
const fromRelative = (
|
|
1137
|
-
const toRelative = (
|
|
1146
|
+
const fromRelative = relativePosix(rootDir, edge.from);
|
|
1147
|
+
const toRelative = relativePosix(rootDir, edge.to);
|
|
1138
1148
|
const fromLayer = resolveFileToLayer(fromRelative, layers);
|
|
1139
1149
|
const toLayer = resolveFileToLayer(toRelative, layers);
|
|
1140
1150
|
if (!fromLayer || !toLayer) continue;
|
|
@@ -1319,8 +1329,8 @@ function createBoundaryValidator(schema, name) {
|
|
|
1319
1329
|
return (0, import_types.Ok)(result.data);
|
|
1320
1330
|
}
|
|
1321
1331
|
const suggestions = result.error.issues.map((issue) => {
|
|
1322
|
-
const
|
|
1323
|
-
return
|
|
1332
|
+
const path20 = issue.path.join(".");
|
|
1333
|
+
return path20 ? `${path20}: ${issue.message}` : issue.message;
|
|
1324
1334
|
});
|
|
1325
1335
|
return (0, import_types.Err)(
|
|
1326
1336
|
createError(
|
|
@@ -1874,11 +1884,11 @@ function walk(node, visitor) {
|
|
|
1874
1884
|
var TypeScriptParser = class {
|
|
1875
1885
|
name = "typescript";
|
|
1876
1886
|
extensions = [".ts", ".tsx", ".mts", ".cts"];
|
|
1877
|
-
async parseFile(
|
|
1878
|
-
const contentResult = await readFileContent(
|
|
1887
|
+
async parseFile(path20) {
|
|
1888
|
+
const contentResult = await readFileContent(path20);
|
|
1879
1889
|
if (!contentResult.ok) {
|
|
1880
1890
|
return (0, import_types.Err)(
|
|
1881
|
-
createParseError("NOT_FOUND", `File not found: ${
|
|
1891
|
+
createParseError("NOT_FOUND", `File not found: ${path20}`, { path: path20 }, [
|
|
1882
1892
|
"Check that the file exists",
|
|
1883
1893
|
"Verify the path is correct"
|
|
1884
1894
|
])
|
|
@@ -1888,7 +1898,7 @@ var TypeScriptParser = class {
|
|
|
1888
1898
|
const ast = (0, import_typescript_estree.parse)(contentResult.value, {
|
|
1889
1899
|
loc: true,
|
|
1890
1900
|
range: true,
|
|
1891
|
-
jsx:
|
|
1901
|
+
jsx: path20.endsWith(".tsx"),
|
|
1892
1902
|
errorOnUnknownASTType: false
|
|
1893
1903
|
});
|
|
1894
1904
|
return (0, import_types.Ok)({
|
|
@@ -1899,7 +1909,7 @@ var TypeScriptParser = class {
|
|
|
1899
1909
|
} catch (e) {
|
|
1900
1910
|
const error = e;
|
|
1901
1911
|
return (0, import_types.Err)(
|
|
1902
|
-
createParseError("SYNTAX_ERROR", `Failed to parse ${
|
|
1912
|
+
createParseError("SYNTAX_ERROR", `Failed to parse ${path20}: ${error.message}`, { path: path20 }, [
|
|
1903
1913
|
"Check for syntax errors in the file",
|
|
1904
1914
|
"Ensure valid TypeScript syntax"
|
|
1905
1915
|
])
|
|
@@ -2183,22 +2193,22 @@ function extractInlineRefs(content) {
|
|
|
2183
2193
|
}
|
|
2184
2194
|
return refs;
|
|
2185
2195
|
}
|
|
2186
|
-
async function parseDocumentationFile(
|
|
2187
|
-
const contentResult = await readFileContent(
|
|
2196
|
+
async function parseDocumentationFile(path20) {
|
|
2197
|
+
const contentResult = await readFileContent(path20);
|
|
2188
2198
|
if (!contentResult.ok) {
|
|
2189
2199
|
return (0, import_types.Err)(
|
|
2190
2200
|
createEntropyError(
|
|
2191
2201
|
"PARSE_ERROR",
|
|
2192
|
-
`Failed to read documentation file: ${
|
|
2193
|
-
{ file:
|
|
2202
|
+
`Failed to read documentation file: ${path20}`,
|
|
2203
|
+
{ file: path20 },
|
|
2194
2204
|
["Check that the file exists"]
|
|
2195
2205
|
)
|
|
2196
2206
|
);
|
|
2197
2207
|
}
|
|
2198
2208
|
const content = contentResult.value;
|
|
2199
|
-
const type =
|
|
2209
|
+
const type = path20.endsWith(".md") ? "markdown" : "text";
|
|
2200
2210
|
return (0, import_types.Ok)({
|
|
2201
|
-
path:
|
|
2211
|
+
path: path20,
|
|
2202
2212
|
type,
|
|
2203
2213
|
content,
|
|
2204
2214
|
codeBlocks: extractCodeBlocks(content),
|
|
@@ -2329,7 +2339,7 @@ async function buildSnapshot(config) {
|
|
|
2329
2339
|
sourceFilePaths.push(...files2);
|
|
2330
2340
|
}
|
|
2331
2341
|
sourceFilePaths = sourceFilePaths.filter((f) => {
|
|
2332
|
-
const rel = (
|
|
2342
|
+
const rel = relativePosix(rootDir, f);
|
|
2333
2343
|
return !excludePatterns.some((p) => (0, import_minimatch3.minimatch)(rel, p));
|
|
2334
2344
|
});
|
|
2335
2345
|
const files = [];
|
|
@@ -2861,9 +2871,8 @@ async function detectDeadCode(snapshot, graphDeadCodeData) {
|
|
|
2861
2871
|
|
|
2862
2872
|
// src/entropy/detectors/patterns.ts
|
|
2863
2873
|
var import_minimatch4 = require("minimatch");
|
|
2864
|
-
var import_path9 = require("path");
|
|
2865
2874
|
function fileMatchesPattern(filePath, pattern, rootDir) {
|
|
2866
|
-
const relativePath = (
|
|
2875
|
+
const relativePath = relativePosix(rootDir, filePath);
|
|
2867
2876
|
return (0, import_minimatch4.minimatch)(relativePath, pattern);
|
|
2868
2877
|
}
|
|
2869
2878
|
function checkConfigPattern(pattern, file, rootDir) {
|
|
@@ -3009,15 +3018,34 @@ async function detectPatternViolations(snapshot, config) {
|
|
|
3009
3018
|
}
|
|
3010
3019
|
}
|
|
3011
3020
|
}
|
|
3021
|
+
if (config?.customPatterns) {
|
|
3022
|
+
for (const file of snapshot.files) {
|
|
3023
|
+
for (const custom of config.customPatterns) {
|
|
3024
|
+
const matches = custom.check(file, snapshot);
|
|
3025
|
+
for (const match of matches) {
|
|
3026
|
+
violations.push({
|
|
3027
|
+
pattern: custom.name,
|
|
3028
|
+
file: file.path,
|
|
3029
|
+
line: match.line,
|
|
3030
|
+
message: match.message,
|
|
3031
|
+
suggestion: match.suggestion || "Review and fix this pattern violation",
|
|
3032
|
+
severity: custom.severity
|
|
3033
|
+
});
|
|
3034
|
+
}
|
|
3035
|
+
}
|
|
3036
|
+
}
|
|
3037
|
+
}
|
|
3012
3038
|
const errorCount = violations.filter((v) => v.severity === "error").length;
|
|
3013
3039
|
const warningCount = violations.filter((v) => v.severity === "warning").length;
|
|
3014
|
-
const
|
|
3015
|
-
const
|
|
3040
|
+
const customCount = config?.customPatterns?.length ?? 0;
|
|
3041
|
+
const allPatternsCount = patterns.length + customCount;
|
|
3042
|
+
const totalChecks = snapshot.files.length * allPatternsCount;
|
|
3043
|
+
const passRate = totalChecks > 0 ? Math.max(0, (totalChecks - violations.length) / totalChecks) : 1;
|
|
3016
3044
|
return (0, import_types.Ok)({
|
|
3017
3045
|
violations,
|
|
3018
3046
|
stats: {
|
|
3019
3047
|
filesChecked: snapshot.files.length,
|
|
3020
|
-
patternsApplied:
|
|
3048
|
+
patternsApplied: allPatternsCount,
|
|
3021
3049
|
violationCount: violations.length,
|
|
3022
3050
|
errorCount,
|
|
3023
3051
|
warningCount
|
|
@@ -3440,7 +3468,7 @@ async function detectCouplingViolations(snapshot, config, graphData) {
|
|
|
3440
3468
|
|
|
3441
3469
|
// src/entropy/detectors/size-budget.ts
|
|
3442
3470
|
var import_node_fs = require("fs");
|
|
3443
|
-
var
|
|
3471
|
+
var import_node_path2 = require("path");
|
|
3444
3472
|
function parseSize(size) {
|
|
3445
3473
|
const match = size.trim().match(/^(\d+(?:\.\d+)?)\s*(KB|MB|GB|B)?$/i);
|
|
3446
3474
|
if (!match) return 0;
|
|
@@ -3467,7 +3495,7 @@ function dirSize(dirPath) {
|
|
|
3467
3495
|
}
|
|
3468
3496
|
for (const entry of entries) {
|
|
3469
3497
|
if (entry === "node_modules" || entry === ".git") continue;
|
|
3470
|
-
const fullPath = (0,
|
|
3498
|
+
const fullPath = (0, import_node_path2.join)(dirPath, entry);
|
|
3471
3499
|
try {
|
|
3472
3500
|
const stat = (0, import_node_fs.statSync)(fullPath);
|
|
3473
3501
|
if (stat.isDirectory()) {
|
|
@@ -3487,7 +3515,7 @@ async function detectSizeBudgetViolations(rootDir, config) {
|
|
|
3487
3515
|
let packagesChecked = 0;
|
|
3488
3516
|
for (const [pkgPath, budget] of Object.entries(budgets)) {
|
|
3489
3517
|
packagesChecked++;
|
|
3490
|
-
const distPath = (0,
|
|
3518
|
+
const distPath = (0, import_node_path2.join)(rootDir, pkgPath, "dist");
|
|
3491
3519
|
const currentSize = dirSize(distPath);
|
|
3492
3520
|
if (budget.warn) {
|
|
3493
3521
|
const budgetBytes = parseSize(budget.warn);
|
|
@@ -3868,7 +3896,7 @@ var EntropyAnalyzer = class {
|
|
|
3868
3896
|
// src/entropy/fixers/safe-fixes.ts
|
|
3869
3897
|
var fs3 = __toESM(require("fs"));
|
|
3870
3898
|
var import_util2 = require("util");
|
|
3871
|
-
var
|
|
3899
|
+
var import_path9 = require("path");
|
|
3872
3900
|
var readFile5 = (0, import_util2.promisify)(fs3.readFile);
|
|
3873
3901
|
var writeFile3 = (0, import_util2.promisify)(fs3.writeFile);
|
|
3874
3902
|
var unlink2 = (0, import_util2.promisify)(fs3.unlink);
|
|
@@ -3884,7 +3912,7 @@ function createDeadFileFixes(deadCodeReport) {
|
|
|
3884
3912
|
return deadCodeReport.deadFiles.map((file) => ({
|
|
3885
3913
|
type: "dead-files",
|
|
3886
3914
|
file: file.path,
|
|
3887
|
-
description: `Delete dead file (${file.reason}): ${(0,
|
|
3915
|
+
description: `Delete dead file (${file.reason}): ${(0, import_path9.basename)(file.path)}`,
|
|
3888
3916
|
action: "delete-file",
|
|
3889
3917
|
safe: true,
|
|
3890
3918
|
reversible: true
|
|
@@ -3967,9 +3995,9 @@ function previewFix(fix) {
|
|
|
3967
3995
|
}
|
|
3968
3996
|
}
|
|
3969
3997
|
async function createBackup(filePath, backupDir) {
|
|
3970
|
-
const backupPath = (0,
|
|
3998
|
+
const backupPath = (0, import_path9.join)(backupDir, `${Date.now()}-${(0, import_path9.basename)(filePath)}`);
|
|
3971
3999
|
try {
|
|
3972
|
-
await mkdir2((0,
|
|
4000
|
+
await mkdir2((0, import_path9.dirname)(backupPath), { recursive: true });
|
|
3973
4001
|
await copyFile2(filePath, backupPath);
|
|
3974
4002
|
return (0, import_types.Ok)(backupPath);
|
|
3975
4003
|
} catch (e) {
|
|
@@ -4305,11 +4333,11 @@ function validatePatternConfig(config) {
|
|
|
4305
4333
|
|
|
4306
4334
|
// src/performance/baseline-manager.ts
|
|
4307
4335
|
var import_node_fs2 = require("fs");
|
|
4308
|
-
var
|
|
4336
|
+
var import_node_path3 = require("path");
|
|
4309
4337
|
var BaselineManager = class {
|
|
4310
4338
|
baselinesPath;
|
|
4311
4339
|
constructor(projectRoot) {
|
|
4312
|
-
this.baselinesPath = (0,
|
|
4340
|
+
this.baselinesPath = (0, import_node_path3.join)(projectRoot, ".harness", "perf", "baselines.json");
|
|
4313
4341
|
}
|
|
4314
4342
|
/**
|
|
4315
4343
|
* Load the baselines file from disk.
|
|
@@ -4349,7 +4377,7 @@ var BaselineManager = class {
|
|
|
4349
4377
|
updatedFrom: commitHash,
|
|
4350
4378
|
benchmarks
|
|
4351
4379
|
};
|
|
4352
|
-
const dir = (0,
|
|
4380
|
+
const dir = (0, import_node_path3.dirname)(this.baselinesPath);
|
|
4353
4381
|
if (!(0, import_node_fs2.existsSync)(dir)) {
|
|
4354
4382
|
(0, import_node_fs2.mkdirSync)(dir, { recursive: true });
|
|
4355
4383
|
}
|
|
@@ -5340,7 +5368,7 @@ async function requestMultiplePeerReviews(requests) {
|
|
|
5340
5368
|
|
|
5341
5369
|
// src/feedback/logging/file-sink.ts
|
|
5342
5370
|
var import_fs2 = require("fs");
|
|
5343
|
-
var
|
|
5371
|
+
var import_path10 = require("path");
|
|
5344
5372
|
var FileSink = class {
|
|
5345
5373
|
name = "file";
|
|
5346
5374
|
filePath;
|
|
@@ -5363,7 +5391,7 @@ var FileSink = class {
|
|
|
5363
5391
|
}
|
|
5364
5392
|
ensureDirectory() {
|
|
5365
5393
|
if (!this.initialized) {
|
|
5366
|
-
const dir = (0,
|
|
5394
|
+
const dir = (0, import_path10.dirname)(this.filePath);
|
|
5367
5395
|
if (!(0, import_fs2.existsSync)(dir)) {
|
|
5368
5396
|
(0, import_fs2.mkdirSync)(dir, { recursive: true });
|
|
5369
5397
|
}
|
|
@@ -5510,14 +5538,10 @@ var ConstraintRuleSchema = import_zod3.z.object({
|
|
|
5510
5538
|
// forward-compat for governs edges
|
|
5511
5539
|
});
|
|
5512
5540
|
|
|
5513
|
-
// src/architecture/collectors/circular-deps.ts
|
|
5514
|
-
var import_node_path3 = require("path");
|
|
5515
|
-
|
|
5516
5541
|
// src/architecture/collectors/hash.ts
|
|
5517
5542
|
var import_node_crypto = require("crypto");
|
|
5518
5543
|
function violationId(relativePath, category, normalizedDetail) {
|
|
5519
|
-
const
|
|
5520
|
-
const input = `${path13}:${category}:${normalizedDetail}`;
|
|
5544
|
+
const input = `${relativePath}:${category}:${normalizedDetail}`;
|
|
5521
5545
|
return (0, import_node_crypto.createHash)("sha256").update(input).digest("hex");
|
|
5522
5546
|
}
|
|
5523
5547
|
function constraintRuleId(category, scope, description) {
|
|
@@ -5581,8 +5605,8 @@ var CircularDepsCollector = class {
|
|
|
5581
5605
|
}
|
|
5582
5606
|
const { cycles, largestCycle } = result.value;
|
|
5583
5607
|
const violations = cycles.map((cycle) => {
|
|
5584
|
-
const cyclePath = cycle.cycle.map((f) => (
|
|
5585
|
-
const firstFile = (
|
|
5608
|
+
const cyclePath = cycle.cycle.map((f) => relativePosix(rootDir, f)).join(" -> ");
|
|
5609
|
+
const firstFile = relativePosix(rootDir, cycle.cycle[0]);
|
|
5586
5610
|
return {
|
|
5587
5611
|
id: violationId(firstFile, this.category, cyclePath),
|
|
5588
5612
|
file: firstFile,
|
|
@@ -5603,7 +5627,6 @@ var CircularDepsCollector = class {
|
|
|
5603
5627
|
};
|
|
5604
5628
|
|
|
5605
5629
|
// src/architecture/collectors/layer-violations.ts
|
|
5606
|
-
var import_node_path4 = require("path");
|
|
5607
5630
|
var LayerViolationCollector = class {
|
|
5608
5631
|
category = "layer-violations";
|
|
5609
5632
|
getRules(_config, _rootDir) {
|
|
@@ -5647,8 +5670,8 @@ var LayerViolationCollector = class {
|
|
|
5647
5670
|
(v) => v.reason === "WRONG_LAYER"
|
|
5648
5671
|
);
|
|
5649
5672
|
const violations = layerViolations.map((v) => {
|
|
5650
|
-
const relFile = (
|
|
5651
|
-
const relImport = (
|
|
5673
|
+
const relFile = relativePosix(rootDir, v.file);
|
|
5674
|
+
const relImport = relativePosix(rootDir, v.imports);
|
|
5652
5675
|
const detail = `${v.fromLayer} -> ${v.toLayer}: ${relFile} imports ${relImport}`;
|
|
5653
5676
|
return {
|
|
5654
5677
|
id: violationId(relFile, this.category, detail),
|
|
@@ -5670,7 +5693,6 @@ var LayerViolationCollector = class {
|
|
|
5670
5693
|
};
|
|
5671
5694
|
|
|
5672
5695
|
// src/architecture/collectors/complexity.ts
|
|
5673
|
-
var import_node_path5 = require("path");
|
|
5674
5696
|
var ComplexityCollector = class {
|
|
5675
5697
|
category = "complexity";
|
|
5676
5698
|
getRules(_config, _rootDir) {
|
|
@@ -5731,7 +5753,7 @@ var ComplexityCollector = class {
|
|
|
5731
5753
|
(v) => v.severity === "error" || v.severity === "warning"
|
|
5732
5754
|
);
|
|
5733
5755
|
const violations = filtered.map((v) => {
|
|
5734
|
-
const relFile = (
|
|
5756
|
+
const relFile = relativePosix(rootDir, v.file);
|
|
5735
5757
|
const idDetail = `${v.metric}:${v.function}`;
|
|
5736
5758
|
return {
|
|
5737
5759
|
id: violationId(relFile, this.category, idDetail),
|
|
@@ -5757,7 +5779,6 @@ var ComplexityCollector = class {
|
|
|
5757
5779
|
};
|
|
5758
5780
|
|
|
5759
5781
|
// src/architecture/collectors/coupling.ts
|
|
5760
|
-
var import_node_path6 = require("path");
|
|
5761
5782
|
var CouplingCollector = class {
|
|
5762
5783
|
category = "coupling";
|
|
5763
5784
|
getRules(_config, _rootDir) {
|
|
@@ -5808,7 +5829,7 @@ var CouplingCollector = class {
|
|
|
5808
5829
|
(v) => v.severity === "error" || v.severity === "warning"
|
|
5809
5830
|
);
|
|
5810
5831
|
const violations = filtered.map((v) => {
|
|
5811
|
-
const relFile = (
|
|
5832
|
+
const relFile = relativePosix(rootDir, v.file);
|
|
5812
5833
|
const idDetail = `${v.metric}`;
|
|
5813
5834
|
return {
|
|
5814
5835
|
id: violationId(relFile, this.category, idDetail),
|
|
@@ -5831,7 +5852,6 @@ var CouplingCollector = class {
|
|
|
5831
5852
|
};
|
|
5832
5853
|
|
|
5833
5854
|
// src/architecture/collectors/forbidden-imports.ts
|
|
5834
|
-
var import_node_path7 = require("path");
|
|
5835
5855
|
var ForbiddenImportCollector = class {
|
|
5836
5856
|
category = "forbidden-imports";
|
|
5837
5857
|
getRules(_config, _rootDir) {
|
|
@@ -5875,8 +5895,8 @@ var ForbiddenImportCollector = class {
|
|
|
5875
5895
|
(v) => v.reason === "FORBIDDEN_IMPORT"
|
|
5876
5896
|
);
|
|
5877
5897
|
const violations = forbidden.map((v) => {
|
|
5878
|
-
const relFile = (
|
|
5879
|
-
const relImport = (
|
|
5898
|
+
const relFile = relativePosix(rootDir, v.file);
|
|
5899
|
+
const relImport = relativePosix(rootDir, v.imports);
|
|
5880
5900
|
const detail = `forbidden import: ${relFile} -> ${relImport}`;
|
|
5881
5901
|
return {
|
|
5882
5902
|
id: violationId(relFile, this.category, detail),
|
|
@@ -5899,7 +5919,7 @@ var ForbiddenImportCollector = class {
|
|
|
5899
5919
|
|
|
5900
5920
|
// src/architecture/collectors/module-size.ts
|
|
5901
5921
|
var import_promises2 = require("fs/promises");
|
|
5902
|
-
var
|
|
5922
|
+
var import_node_path4 = require("path");
|
|
5903
5923
|
async function discoverModules(rootDir) {
|
|
5904
5924
|
const modules = [];
|
|
5905
5925
|
async function scanDir(dir) {
|
|
@@ -5915,7 +5935,7 @@ async function discoverModules(rootDir) {
|
|
|
5915
5935
|
if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist") {
|
|
5916
5936
|
continue;
|
|
5917
5937
|
}
|
|
5918
|
-
const fullPath = (0,
|
|
5938
|
+
const fullPath = (0, import_node_path4.join)(dir, entry.name);
|
|
5919
5939
|
if (entry.isDirectory()) {
|
|
5920
5940
|
subdirs.push(fullPath);
|
|
5921
5941
|
} else if (entry.isFile() && (entry.name.endsWith(".ts") || entry.name.endsWith(".tsx")) && !entry.name.endsWith(".test.ts") && !entry.name.endsWith(".test.tsx") && !entry.name.endsWith(".spec.ts")) {
|
|
@@ -5932,10 +5952,10 @@ async function discoverModules(rootDir) {
|
|
|
5932
5952
|
}
|
|
5933
5953
|
}
|
|
5934
5954
|
modules.push({
|
|
5935
|
-
modulePath: (
|
|
5955
|
+
modulePath: relativePosix(rootDir, dir),
|
|
5936
5956
|
fileCount: tsFiles.length,
|
|
5937
5957
|
totalLoc,
|
|
5938
|
-
files: tsFiles.map((f) => (
|
|
5958
|
+
files: tsFiles.map((f) => relativePosix(rootDir, f))
|
|
5939
5959
|
});
|
|
5940
5960
|
}
|
|
5941
5961
|
for (const sub of subdirs) {
|
|
@@ -6027,16 +6047,16 @@ var ModuleSizeCollector = class {
|
|
|
6027
6047
|
|
|
6028
6048
|
// src/architecture/collectors/dep-depth.ts
|
|
6029
6049
|
var import_promises3 = require("fs/promises");
|
|
6030
|
-
var
|
|
6050
|
+
var import_node_path5 = require("path");
|
|
6031
6051
|
function extractImportSources(content, filePath) {
|
|
6032
6052
|
const importRegex = /(?:import|export)\s+.*?from\s+['"](\.[^'"]+)['"]/g;
|
|
6033
6053
|
const dynamicRegex = /import\s*\(\s*['"](\.[^'"]+)['"]\s*\)/g;
|
|
6034
6054
|
const sources = [];
|
|
6035
|
-
const dir = (0,
|
|
6055
|
+
const dir = (0, import_node_path5.dirname)(filePath);
|
|
6036
6056
|
for (const regex of [importRegex, dynamicRegex]) {
|
|
6037
6057
|
let match;
|
|
6038
6058
|
while ((match = regex.exec(content)) !== null) {
|
|
6039
|
-
let resolved = (0,
|
|
6059
|
+
let resolved = (0, import_node_path5.resolve)(dir, match[1]);
|
|
6040
6060
|
if (!resolved.endsWith(".ts") && !resolved.endsWith(".tsx")) {
|
|
6041
6061
|
resolved += ".ts";
|
|
6042
6062
|
}
|
|
@@ -6057,7 +6077,7 @@ async function collectTsFiles(dir) {
|
|
|
6057
6077
|
for (const entry of entries) {
|
|
6058
6078
|
if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist")
|
|
6059
6079
|
continue;
|
|
6060
|
-
const fullPath = (0,
|
|
6080
|
+
const fullPath = (0, import_node_path5.join)(d, entry.name);
|
|
6061
6081
|
if (entry.isDirectory()) {
|
|
6062
6082
|
await scan(fullPath);
|
|
6063
6083
|
} else if (entry.isFile() && (entry.name.endsWith(".ts") || entry.name.endsWith(".tsx")) && !entry.name.endsWith(".test.ts") && !entry.name.endsWith(".test.tsx") && !entry.name.endsWith(".spec.ts")) {
|
|
@@ -6111,7 +6131,7 @@ var DepDepthCollector = class {
|
|
|
6111
6131
|
}
|
|
6112
6132
|
const moduleMap = /* @__PURE__ */ new Map();
|
|
6113
6133
|
for (const file of allFiles) {
|
|
6114
|
-
const relDir = (
|
|
6134
|
+
const relDir = relativePosix(rootDir, (0, import_node_path5.dirname)(file));
|
|
6115
6135
|
if (!moduleMap.has(relDir)) moduleMap.set(relDir, []);
|
|
6116
6136
|
moduleMap.get(relDir).push(file);
|
|
6117
6137
|
}
|
|
@@ -6255,11 +6275,11 @@ function detectStaleConstraints(store, windowDays = 30, category) {
|
|
|
6255
6275
|
// src/architecture/baseline-manager.ts
|
|
6256
6276
|
var import_node_fs3 = require("fs");
|
|
6257
6277
|
var import_node_crypto2 = require("crypto");
|
|
6258
|
-
var
|
|
6278
|
+
var import_node_path6 = require("path");
|
|
6259
6279
|
var ArchBaselineManager = class {
|
|
6260
6280
|
baselinesPath;
|
|
6261
6281
|
constructor(projectRoot, baselinePath) {
|
|
6262
|
-
this.baselinesPath = baselinePath ? (0,
|
|
6282
|
+
this.baselinesPath = baselinePath ? (0, import_node_path6.join)(projectRoot, baselinePath) : (0, import_node_path6.join)(projectRoot, ".harness", "arch", "baselines.json");
|
|
6263
6283
|
}
|
|
6264
6284
|
/**
|
|
6265
6285
|
* Snapshot the current metric results into an ArchBaseline.
|
|
@@ -6320,7 +6340,7 @@ var ArchBaselineManager = class {
|
|
|
6320
6340
|
* Uses atomic write (write to temp file, then rename) to prevent corruption.
|
|
6321
6341
|
*/
|
|
6322
6342
|
save(baseline) {
|
|
6323
|
-
const dir = (0,
|
|
6343
|
+
const dir = (0, import_node_path6.dirname)(this.baselinesPath);
|
|
6324
6344
|
if (!(0, import_node_fs3.existsSync)(dir)) {
|
|
6325
6345
|
(0, import_node_fs3.mkdirSync)(dir, { recursive: true });
|
|
6326
6346
|
}
|
|
@@ -6695,10 +6715,13 @@ var DEFAULT_STATE = {
|
|
|
6695
6715
|
progress: {}
|
|
6696
6716
|
};
|
|
6697
6717
|
|
|
6698
|
-
// src/state/state-
|
|
6699
|
-
var
|
|
6700
|
-
var
|
|
6701
|
-
|
|
6718
|
+
// src/state/state-persistence.ts
|
|
6719
|
+
var fs8 = __toESM(require("fs"));
|
|
6720
|
+
var path5 = __toESM(require("path"));
|
|
6721
|
+
|
|
6722
|
+
// src/state/state-shared.ts
|
|
6723
|
+
var fs7 = __toESM(require("fs"));
|
|
6724
|
+
var path4 = __toESM(require("path"));
|
|
6702
6725
|
|
|
6703
6726
|
// src/state/stream-resolver.ts
|
|
6704
6727
|
var fs5 = __toESM(require("fs"));
|
|
@@ -6724,10 +6747,20 @@ var DEFAULT_STREAM_INDEX = {
|
|
|
6724
6747
|
streams: {}
|
|
6725
6748
|
};
|
|
6726
6749
|
|
|
6727
|
-
// src/state/
|
|
6750
|
+
// src/state/constants.ts
|
|
6728
6751
|
var HARNESS_DIR = ".harness";
|
|
6729
|
-
var
|
|
6752
|
+
var STATE_FILE = "state.json";
|
|
6753
|
+
var LEARNINGS_FILE = "learnings.md";
|
|
6754
|
+
var FAILURES_FILE = "failures.md";
|
|
6755
|
+
var HANDOFF_FILE = "handoff.json";
|
|
6756
|
+
var GATE_CONFIG_FILE = "gate.json";
|
|
6730
6757
|
var INDEX_FILE = "index.json";
|
|
6758
|
+
var SESSIONS_DIR = "sessions";
|
|
6759
|
+
var SESSION_INDEX_FILE = "index.md";
|
|
6760
|
+
var SUMMARY_FILE = "summary.md";
|
|
6761
|
+
|
|
6762
|
+
// src/state/stream-resolver.ts
|
|
6763
|
+
var STREAMS_DIR = "streams";
|
|
6731
6764
|
var STREAM_NAME_REGEX = /^[a-z0-9][a-z0-9._-]*$/;
|
|
6732
6765
|
function streamsDir(projectPath) {
|
|
6733
6766
|
return path2.join(projectPath, HARNESS_DIR, STREAMS_DIR);
|
|
@@ -6954,26 +6987,65 @@ async function migrateToStreams(projectPath) {
|
|
|
6954
6987
|
return saveStreamIndex(projectPath, index);
|
|
6955
6988
|
}
|
|
6956
6989
|
|
|
6957
|
-
// src/state/
|
|
6958
|
-
var
|
|
6959
|
-
var
|
|
6960
|
-
|
|
6961
|
-
|
|
6962
|
-
|
|
6963
|
-
|
|
6964
|
-
|
|
6990
|
+
// src/state/session-resolver.ts
|
|
6991
|
+
var fs6 = __toESM(require("fs"));
|
|
6992
|
+
var path3 = __toESM(require("path"));
|
|
6993
|
+
function resolveSessionDir(projectPath, sessionSlug, options) {
|
|
6994
|
+
if (!sessionSlug || sessionSlug.trim() === "") {
|
|
6995
|
+
return (0, import_types.Err)(new Error("Session slug must not be empty"));
|
|
6996
|
+
}
|
|
6997
|
+
if (sessionSlug.includes("..") || sessionSlug.includes("/") || sessionSlug.includes("\\")) {
|
|
6998
|
+
return (0, import_types.Err)(
|
|
6999
|
+
new Error(`Invalid session slug '${sessionSlug}': must not contain path traversal characters`)
|
|
7000
|
+
);
|
|
7001
|
+
}
|
|
7002
|
+
const sessionDir = path3.join(projectPath, HARNESS_DIR, SESSIONS_DIR, sessionSlug);
|
|
7003
|
+
if (options?.create) {
|
|
7004
|
+
fs6.mkdirSync(sessionDir, { recursive: true });
|
|
7005
|
+
}
|
|
7006
|
+
return (0, import_types.Ok)(sessionDir);
|
|
7007
|
+
}
|
|
7008
|
+
function updateSessionIndex(projectPath, sessionSlug, description) {
|
|
7009
|
+
const sessionsDir = path3.join(projectPath, HARNESS_DIR, SESSIONS_DIR);
|
|
7010
|
+
fs6.mkdirSync(sessionsDir, { recursive: true });
|
|
7011
|
+
const indexPath2 = path3.join(sessionsDir, SESSION_INDEX_FILE);
|
|
7012
|
+
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
7013
|
+
const newLine = `- [${sessionSlug}](${sessionSlug}/summary.md) \u2014 ${description} (${date})`;
|
|
7014
|
+
if (!fs6.existsSync(indexPath2)) {
|
|
7015
|
+
fs6.writeFileSync(indexPath2, `## Active Sessions
|
|
7016
|
+
|
|
7017
|
+
${newLine}
|
|
7018
|
+
`);
|
|
7019
|
+
return;
|
|
7020
|
+
}
|
|
7021
|
+
const content = fs6.readFileSync(indexPath2, "utf-8");
|
|
7022
|
+
const lines = content.split("\n");
|
|
7023
|
+
const slugPattern = `- [${sessionSlug}]`;
|
|
7024
|
+
const existingIdx = lines.findIndex((l) => l.startsWith(slugPattern));
|
|
7025
|
+
if (existingIdx >= 0) {
|
|
7026
|
+
lines[existingIdx] = newLine;
|
|
7027
|
+
} else {
|
|
7028
|
+
const lastNonEmpty = lines.reduce((last, line, i) => line.trim() !== "" ? i : last, 0);
|
|
7029
|
+
lines.splice(lastNonEmpty + 1, 0, newLine);
|
|
7030
|
+
}
|
|
7031
|
+
fs6.writeFileSync(indexPath2, lines.join("\n"));
|
|
7032
|
+
}
|
|
7033
|
+
|
|
7034
|
+
// src/state/state-shared.ts
|
|
6965
7035
|
var MAX_CACHE_ENTRIES = 8;
|
|
6966
|
-
var learningsCacheMap = /* @__PURE__ */ new Map();
|
|
6967
|
-
var failuresCacheMap = /* @__PURE__ */ new Map();
|
|
6968
7036
|
function evictIfNeeded(map) {
|
|
6969
7037
|
if (map.size > MAX_CACHE_ENTRIES) {
|
|
6970
7038
|
const oldest = map.keys().next().value;
|
|
6971
7039
|
if (oldest !== void 0) map.delete(oldest);
|
|
6972
7040
|
}
|
|
6973
7041
|
}
|
|
6974
|
-
async function getStateDir(projectPath, stream) {
|
|
6975
|
-
|
|
6976
|
-
|
|
7042
|
+
async function getStateDir(projectPath, stream, session) {
|
|
7043
|
+
if (session) {
|
|
7044
|
+
const sessionResult = resolveSessionDir(projectPath, session, { create: true });
|
|
7045
|
+
return sessionResult;
|
|
7046
|
+
}
|
|
7047
|
+
const streamsIndexPath = path4.join(projectPath, HARNESS_DIR, "streams", INDEX_FILE);
|
|
7048
|
+
const hasStreams = fs7.existsSync(streamsIndexPath);
|
|
6977
7049
|
if (stream || hasStreams) {
|
|
6978
7050
|
const result = await resolveStreamPath(projectPath, stream ? { stream } : void 0);
|
|
6979
7051
|
if (result.ok) {
|
|
@@ -6983,18 +7055,20 @@ async function getStateDir(projectPath, stream) {
|
|
|
6983
7055
|
return result;
|
|
6984
7056
|
}
|
|
6985
7057
|
}
|
|
6986
|
-
return (0, import_types.Ok)(
|
|
7058
|
+
return (0, import_types.Ok)(path4.join(projectPath, HARNESS_DIR));
|
|
6987
7059
|
}
|
|
6988
|
-
|
|
7060
|
+
|
|
7061
|
+
// src/state/state-persistence.ts
|
|
7062
|
+
async function loadState(projectPath, stream, session) {
|
|
6989
7063
|
try {
|
|
6990
|
-
const dirResult = await getStateDir(projectPath, stream);
|
|
7064
|
+
const dirResult = await getStateDir(projectPath, stream, session);
|
|
6991
7065
|
if (!dirResult.ok) return dirResult;
|
|
6992
7066
|
const stateDir = dirResult.value;
|
|
6993
|
-
const statePath =
|
|
6994
|
-
if (!
|
|
7067
|
+
const statePath = path5.join(stateDir, STATE_FILE);
|
|
7068
|
+
if (!fs8.existsSync(statePath)) {
|
|
6995
7069
|
return (0, import_types.Ok)({ ...DEFAULT_STATE });
|
|
6996
7070
|
}
|
|
6997
|
-
const raw =
|
|
7071
|
+
const raw = fs8.readFileSync(statePath, "utf-8");
|
|
6998
7072
|
const parsed = JSON.parse(raw);
|
|
6999
7073
|
const result = HarnessStateSchema.safeParse(parsed);
|
|
7000
7074
|
if (!result.success) {
|
|
@@ -7007,14 +7081,14 @@ async function loadState(projectPath, stream) {
|
|
|
7007
7081
|
);
|
|
7008
7082
|
}
|
|
7009
7083
|
}
|
|
7010
|
-
async function saveState(projectPath, state, stream) {
|
|
7084
|
+
async function saveState(projectPath, state, stream, session) {
|
|
7011
7085
|
try {
|
|
7012
|
-
const dirResult = await getStateDir(projectPath, stream);
|
|
7086
|
+
const dirResult = await getStateDir(projectPath, stream, session);
|
|
7013
7087
|
if (!dirResult.ok) return dirResult;
|
|
7014
7088
|
const stateDir = dirResult.value;
|
|
7015
|
-
const statePath =
|
|
7016
|
-
|
|
7017
|
-
|
|
7089
|
+
const statePath = path5.join(stateDir, STATE_FILE);
|
|
7090
|
+
fs8.mkdirSync(stateDir, { recursive: true });
|
|
7091
|
+
fs8.writeFileSync(statePath, JSON.stringify(state, null, 2));
|
|
7018
7092
|
return (0, import_types.Ok)(void 0);
|
|
7019
7093
|
} catch (error) {
|
|
7020
7094
|
return (0, import_types.Err)(
|
|
@@ -7022,13 +7096,21 @@ async function saveState(projectPath, state, stream) {
|
|
|
7022
7096
|
);
|
|
7023
7097
|
}
|
|
7024
7098
|
}
|
|
7025
|
-
|
|
7099
|
+
|
|
7100
|
+
// src/state/learnings.ts
|
|
7101
|
+
var fs9 = __toESM(require("fs"));
|
|
7102
|
+
var path6 = __toESM(require("path"));
|
|
7103
|
+
var learningsCacheMap = /* @__PURE__ */ new Map();
|
|
7104
|
+
function clearLearningsCache() {
|
|
7105
|
+
learningsCacheMap.clear();
|
|
7106
|
+
}
|
|
7107
|
+
async function appendLearning(projectPath, learning, skillName, outcome, stream, session) {
|
|
7026
7108
|
try {
|
|
7027
|
-
const dirResult = await getStateDir(projectPath, stream);
|
|
7109
|
+
const dirResult = await getStateDir(projectPath, stream, session);
|
|
7028
7110
|
if (!dirResult.ok) return dirResult;
|
|
7029
7111
|
const stateDir = dirResult.value;
|
|
7030
|
-
const learningsPath =
|
|
7031
|
-
|
|
7112
|
+
const learningsPath = path6.join(stateDir, LEARNINGS_FILE);
|
|
7113
|
+
fs9.mkdirSync(stateDir, { recursive: true });
|
|
7032
7114
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
7033
7115
|
let entry;
|
|
7034
7116
|
if (skillName && outcome) {
|
|
@@ -7044,11 +7126,11 @@ async function appendLearning(projectPath, learning, skillName, outcome, stream)
|
|
|
7044
7126
|
- **${timestamp}:** ${learning}
|
|
7045
7127
|
`;
|
|
7046
7128
|
}
|
|
7047
|
-
if (!
|
|
7048
|
-
|
|
7129
|
+
if (!fs9.existsSync(learningsPath)) {
|
|
7130
|
+
fs9.writeFileSync(learningsPath, `# Learnings
|
|
7049
7131
|
${entry}`);
|
|
7050
7132
|
} else {
|
|
7051
|
-
|
|
7133
|
+
fs9.appendFileSync(learningsPath, entry);
|
|
7052
7134
|
}
|
|
7053
7135
|
learningsCacheMap.delete(learningsPath);
|
|
7054
7136
|
return (0, import_types.Ok)(void 0);
|
|
@@ -7060,23 +7142,92 @@ ${entry}`);
|
|
|
7060
7142
|
);
|
|
7061
7143
|
}
|
|
7062
7144
|
}
|
|
7063
|
-
|
|
7145
|
+
function estimateTokens(text) {
|
|
7146
|
+
return Math.ceil(text.length / 4);
|
|
7147
|
+
}
|
|
7148
|
+
function scoreRelevance(entry, intent) {
|
|
7149
|
+
if (!intent || intent.trim() === "") return 0;
|
|
7150
|
+
const intentWords = intent.toLowerCase().split(/\s+/).filter((w) => w.length > 2);
|
|
7151
|
+
if (intentWords.length === 0) return 0;
|
|
7152
|
+
const entryLower = entry.toLowerCase();
|
|
7153
|
+
const matches = intentWords.filter((word) => entryLower.includes(word));
|
|
7154
|
+
return matches.length / intentWords.length;
|
|
7155
|
+
}
|
|
7156
|
+
function parseDateFromEntry(entry) {
|
|
7157
|
+
const match = entry.match(/(\d{4}-\d{2}-\d{2})/);
|
|
7158
|
+
return match ? match[1] ?? null : null;
|
|
7159
|
+
}
|
|
7160
|
+
function analyzeLearningPatterns(entries) {
|
|
7161
|
+
const tagGroups = /* @__PURE__ */ new Map();
|
|
7162
|
+
for (const entry of entries) {
|
|
7163
|
+
const tagMatches = entry.matchAll(/\[(skill:[^\]]+)\]|\[(outcome:[^\]]+)\]/g);
|
|
7164
|
+
for (const match of tagMatches) {
|
|
7165
|
+
const tag = match[1] ?? match[2];
|
|
7166
|
+
if (tag) {
|
|
7167
|
+
const group = tagGroups.get(tag) ?? [];
|
|
7168
|
+
group.push(entry);
|
|
7169
|
+
tagGroups.set(tag, group);
|
|
7170
|
+
}
|
|
7171
|
+
}
|
|
7172
|
+
}
|
|
7173
|
+
const patterns = [];
|
|
7174
|
+
for (const [tag, groupEntries] of tagGroups) {
|
|
7175
|
+
if (groupEntries.length >= 3) {
|
|
7176
|
+
patterns.push({ tag, count: groupEntries.length, entries: groupEntries });
|
|
7177
|
+
}
|
|
7178
|
+
}
|
|
7179
|
+
return patterns.sort((a, b) => b.count - a.count);
|
|
7180
|
+
}
|
|
7181
|
+
async function loadBudgetedLearnings(projectPath, options) {
|
|
7182
|
+
const { intent, tokenBudget = 1e3, skill, session, stream } = options;
|
|
7183
|
+
const sortByRecencyAndRelevance = (entries) => {
|
|
7184
|
+
return [...entries].sort((a, b) => {
|
|
7185
|
+
const dateA = parseDateFromEntry(a) ?? "0000-00-00";
|
|
7186
|
+
const dateB = parseDateFromEntry(b) ?? "0000-00-00";
|
|
7187
|
+
const dateCompare = dateB.localeCompare(dateA);
|
|
7188
|
+
if (dateCompare !== 0) return dateCompare;
|
|
7189
|
+
return scoreRelevance(b, intent) - scoreRelevance(a, intent);
|
|
7190
|
+
});
|
|
7191
|
+
};
|
|
7192
|
+
const allEntries = [];
|
|
7193
|
+
if (session) {
|
|
7194
|
+
const sessionResult = await loadRelevantLearnings(projectPath, skill, stream, session);
|
|
7195
|
+
if (sessionResult.ok) {
|
|
7196
|
+
allEntries.push(...sortByRecencyAndRelevance(sessionResult.value));
|
|
7197
|
+
}
|
|
7198
|
+
}
|
|
7199
|
+
const globalResult = await loadRelevantLearnings(projectPath, skill, stream);
|
|
7200
|
+
if (globalResult.ok) {
|
|
7201
|
+
allEntries.push(...sortByRecencyAndRelevance(globalResult.value));
|
|
7202
|
+
}
|
|
7203
|
+
const budgeted = [];
|
|
7204
|
+
let totalTokens = 0;
|
|
7205
|
+
for (const entry of allEntries) {
|
|
7206
|
+
const separator = budgeted.length > 0 ? "\n" : "";
|
|
7207
|
+
const entryCost = estimateTokens(entry + separator);
|
|
7208
|
+
if (totalTokens + entryCost > tokenBudget) break;
|
|
7209
|
+
budgeted.push(entry);
|
|
7210
|
+
totalTokens += entryCost;
|
|
7211
|
+
}
|
|
7212
|
+
return (0, import_types.Ok)(budgeted);
|
|
7213
|
+
}
|
|
7214
|
+
async function loadRelevantLearnings(projectPath, skillName, stream, session) {
|
|
7064
7215
|
try {
|
|
7065
|
-
const dirResult = await getStateDir(projectPath, stream);
|
|
7216
|
+
const dirResult = await getStateDir(projectPath, stream, session);
|
|
7066
7217
|
if (!dirResult.ok) return dirResult;
|
|
7067
7218
|
const stateDir = dirResult.value;
|
|
7068
|
-
const learningsPath =
|
|
7069
|
-
if (!
|
|
7219
|
+
const learningsPath = path6.join(stateDir, LEARNINGS_FILE);
|
|
7220
|
+
if (!fs9.existsSync(learningsPath)) {
|
|
7070
7221
|
return (0, import_types.Ok)([]);
|
|
7071
7222
|
}
|
|
7072
|
-
const stats =
|
|
7223
|
+
const stats = fs9.statSync(learningsPath);
|
|
7073
7224
|
const cacheKey = learningsPath;
|
|
7074
7225
|
const cached = learningsCacheMap.get(cacheKey);
|
|
7075
7226
|
let entries;
|
|
7076
7227
|
if (cached && cached.mtimeMs === stats.mtimeMs) {
|
|
7077
7228
|
entries = cached.entries;
|
|
7078
7229
|
} else {
|
|
7079
|
-
const content =
|
|
7230
|
+
const content = fs9.readFileSync(learningsPath, "utf-8");
|
|
7080
7231
|
const lines = content.split("\n");
|
|
7081
7232
|
entries = [];
|
|
7082
7233
|
let currentBlock = [];
|
|
@@ -7112,23 +7263,110 @@ async function loadRelevantLearnings(projectPath, skillName, stream) {
|
|
|
7112
7263
|
);
|
|
7113
7264
|
}
|
|
7114
7265
|
}
|
|
7115
|
-
|
|
7116
|
-
async function appendFailure(projectPath, description, skillName, type, stream) {
|
|
7266
|
+
async function archiveLearnings(projectPath, entries, stream) {
|
|
7117
7267
|
try {
|
|
7118
7268
|
const dirResult = await getStateDir(projectPath, stream);
|
|
7119
7269
|
if (!dirResult.ok) return dirResult;
|
|
7120
7270
|
const stateDir = dirResult.value;
|
|
7121
|
-
const
|
|
7122
|
-
|
|
7271
|
+
const archiveDir = path6.join(stateDir, "learnings-archive");
|
|
7272
|
+
fs9.mkdirSync(archiveDir, { recursive: true });
|
|
7273
|
+
const now = /* @__PURE__ */ new Date();
|
|
7274
|
+
const yearMonth = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`;
|
|
7275
|
+
const archivePath = path6.join(archiveDir, `${yearMonth}.md`);
|
|
7276
|
+
const archiveContent = entries.join("\n\n") + "\n";
|
|
7277
|
+
if (fs9.existsSync(archivePath)) {
|
|
7278
|
+
fs9.appendFileSync(archivePath, "\n" + archiveContent);
|
|
7279
|
+
} else {
|
|
7280
|
+
fs9.writeFileSync(archivePath, `# Learnings Archive
|
|
7281
|
+
|
|
7282
|
+
${archiveContent}`);
|
|
7283
|
+
}
|
|
7284
|
+
return (0, import_types.Ok)(void 0);
|
|
7285
|
+
} catch (error) {
|
|
7286
|
+
return (0, import_types.Err)(
|
|
7287
|
+
new Error(
|
|
7288
|
+
`Failed to archive learnings: ${error instanceof Error ? error.message : String(error)}`
|
|
7289
|
+
)
|
|
7290
|
+
);
|
|
7291
|
+
}
|
|
7292
|
+
}
|
|
7293
|
+
async function pruneLearnings(projectPath, stream) {
|
|
7294
|
+
try {
|
|
7295
|
+
const dirResult = await getStateDir(projectPath, stream);
|
|
7296
|
+
if (!dirResult.ok) return dirResult;
|
|
7297
|
+
const stateDir = dirResult.value;
|
|
7298
|
+
const learningsPath = path6.join(stateDir, LEARNINGS_FILE);
|
|
7299
|
+
if (!fs9.existsSync(learningsPath)) {
|
|
7300
|
+
return (0, import_types.Ok)({ kept: 0, archived: 0, patterns: [] });
|
|
7301
|
+
}
|
|
7302
|
+
const loadResult = await loadRelevantLearnings(projectPath, void 0, stream);
|
|
7303
|
+
if (!loadResult.ok) return loadResult;
|
|
7304
|
+
const allEntries = loadResult.value;
|
|
7305
|
+
if (allEntries.length <= 20) {
|
|
7306
|
+
const cutoffDate = /* @__PURE__ */ new Date();
|
|
7307
|
+
cutoffDate.setDate(cutoffDate.getDate() - 14);
|
|
7308
|
+
const cutoffStr = cutoffDate.toISOString().split("T")[0];
|
|
7309
|
+
const hasOld = allEntries.some((entry) => {
|
|
7310
|
+
const date = parseDateFromEntry(entry);
|
|
7311
|
+
return date !== null && date < cutoffStr;
|
|
7312
|
+
});
|
|
7313
|
+
if (!hasOld) {
|
|
7314
|
+
return (0, import_types.Ok)({ kept: allEntries.length, archived: 0, patterns: [] });
|
|
7315
|
+
}
|
|
7316
|
+
}
|
|
7317
|
+
const sorted = [...allEntries].sort((a, b) => {
|
|
7318
|
+
const dateA = parseDateFromEntry(a) ?? "0000-00-00";
|
|
7319
|
+
const dateB = parseDateFromEntry(b) ?? "0000-00-00";
|
|
7320
|
+
return dateB.localeCompare(dateA);
|
|
7321
|
+
});
|
|
7322
|
+
const toKeep = sorted.slice(0, 20);
|
|
7323
|
+
const toArchive = sorted.slice(20);
|
|
7324
|
+
const patterns = analyzeLearningPatterns(allEntries);
|
|
7325
|
+
if (toArchive.length > 0) {
|
|
7326
|
+
const archiveResult = await archiveLearnings(projectPath, toArchive, stream);
|
|
7327
|
+
if (!archiveResult.ok) return archiveResult;
|
|
7328
|
+
}
|
|
7329
|
+
const newContent = "# Learnings\n\n" + toKeep.join("\n\n") + "\n";
|
|
7330
|
+
fs9.writeFileSync(learningsPath, newContent);
|
|
7331
|
+
learningsCacheMap.delete(learningsPath);
|
|
7332
|
+
return (0, import_types.Ok)({
|
|
7333
|
+
kept: toKeep.length,
|
|
7334
|
+
archived: toArchive.length,
|
|
7335
|
+
patterns
|
|
7336
|
+
});
|
|
7337
|
+
} catch (error) {
|
|
7338
|
+
return (0, import_types.Err)(
|
|
7339
|
+
new Error(
|
|
7340
|
+
`Failed to prune learnings: ${error instanceof Error ? error.message : String(error)}`
|
|
7341
|
+
)
|
|
7342
|
+
);
|
|
7343
|
+
}
|
|
7344
|
+
}
|
|
7345
|
+
|
|
7346
|
+
// src/state/failures.ts
|
|
7347
|
+
var fs10 = __toESM(require("fs"));
|
|
7348
|
+
var path7 = __toESM(require("path"));
|
|
7349
|
+
var failuresCacheMap = /* @__PURE__ */ new Map();
|
|
7350
|
+
function clearFailuresCache() {
|
|
7351
|
+
failuresCacheMap.clear();
|
|
7352
|
+
}
|
|
7353
|
+
var FAILURE_LINE_REGEX = /^- \*\*(\d{4}-\d{2}-\d{2}) \[skill:([^\]]+)\] \[type:([^\]]+)\]:\*\* (.+)$/;
|
|
7354
|
+
async function appendFailure(projectPath, description, skillName, type, stream, session) {
|
|
7355
|
+
try {
|
|
7356
|
+
const dirResult = await getStateDir(projectPath, stream, session);
|
|
7357
|
+
if (!dirResult.ok) return dirResult;
|
|
7358
|
+
const stateDir = dirResult.value;
|
|
7359
|
+
const failuresPath = path7.join(stateDir, FAILURES_FILE);
|
|
7360
|
+
fs10.mkdirSync(stateDir, { recursive: true });
|
|
7123
7361
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
7124
7362
|
const entry = `
|
|
7125
7363
|
- **${timestamp} [skill:${skillName}] [type:${type}]:** ${description}
|
|
7126
7364
|
`;
|
|
7127
|
-
if (!
|
|
7128
|
-
|
|
7365
|
+
if (!fs10.existsSync(failuresPath)) {
|
|
7366
|
+
fs10.writeFileSync(failuresPath, `# Failures
|
|
7129
7367
|
${entry}`);
|
|
7130
7368
|
} else {
|
|
7131
|
-
|
|
7369
|
+
fs10.appendFileSync(failuresPath, entry);
|
|
7132
7370
|
}
|
|
7133
7371
|
failuresCacheMap.delete(failuresPath);
|
|
7134
7372
|
return (0, import_types.Ok)(void 0);
|
|
@@ -7140,22 +7378,22 @@ ${entry}`);
|
|
|
7140
7378
|
);
|
|
7141
7379
|
}
|
|
7142
7380
|
}
|
|
7143
|
-
async function loadFailures(projectPath, stream) {
|
|
7381
|
+
async function loadFailures(projectPath, stream, session) {
|
|
7144
7382
|
try {
|
|
7145
|
-
const dirResult = await getStateDir(projectPath, stream);
|
|
7383
|
+
const dirResult = await getStateDir(projectPath, stream, session);
|
|
7146
7384
|
if (!dirResult.ok) return dirResult;
|
|
7147
7385
|
const stateDir = dirResult.value;
|
|
7148
|
-
const failuresPath =
|
|
7149
|
-
if (!
|
|
7386
|
+
const failuresPath = path7.join(stateDir, FAILURES_FILE);
|
|
7387
|
+
if (!fs10.existsSync(failuresPath)) {
|
|
7150
7388
|
return (0, import_types.Ok)([]);
|
|
7151
7389
|
}
|
|
7152
|
-
const stats =
|
|
7390
|
+
const stats = fs10.statSync(failuresPath);
|
|
7153
7391
|
const cacheKey = failuresPath;
|
|
7154
7392
|
const cached = failuresCacheMap.get(cacheKey);
|
|
7155
7393
|
if (cached && cached.mtimeMs === stats.mtimeMs) {
|
|
7156
7394
|
return (0, import_types.Ok)(cached.entries);
|
|
7157
7395
|
}
|
|
7158
|
-
const content =
|
|
7396
|
+
const content = fs10.readFileSync(failuresPath, "utf-8");
|
|
7159
7397
|
const entries = [];
|
|
7160
7398
|
for (const line of content.split("\n")) {
|
|
7161
7399
|
const match = line.match(FAILURE_LINE_REGEX);
|
|
@@ -7179,25 +7417,25 @@ async function loadFailures(projectPath, stream) {
|
|
|
7179
7417
|
);
|
|
7180
7418
|
}
|
|
7181
7419
|
}
|
|
7182
|
-
async function archiveFailures(projectPath, stream) {
|
|
7420
|
+
async function archiveFailures(projectPath, stream, session) {
|
|
7183
7421
|
try {
|
|
7184
|
-
const dirResult = await getStateDir(projectPath, stream);
|
|
7422
|
+
const dirResult = await getStateDir(projectPath, stream, session);
|
|
7185
7423
|
if (!dirResult.ok) return dirResult;
|
|
7186
7424
|
const stateDir = dirResult.value;
|
|
7187
|
-
const failuresPath =
|
|
7188
|
-
if (!
|
|
7425
|
+
const failuresPath = path7.join(stateDir, FAILURES_FILE);
|
|
7426
|
+
if (!fs10.existsSync(failuresPath)) {
|
|
7189
7427
|
return (0, import_types.Ok)(void 0);
|
|
7190
7428
|
}
|
|
7191
|
-
const archiveDir =
|
|
7192
|
-
|
|
7429
|
+
const archiveDir = path7.join(stateDir, "archive");
|
|
7430
|
+
fs10.mkdirSync(archiveDir, { recursive: true });
|
|
7193
7431
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
7194
7432
|
let archiveName = `failures-${date}.md`;
|
|
7195
7433
|
let counter = 2;
|
|
7196
|
-
while (
|
|
7434
|
+
while (fs10.existsSync(path7.join(archiveDir, archiveName))) {
|
|
7197
7435
|
archiveName = `failures-${date}-${counter}.md`;
|
|
7198
7436
|
counter++;
|
|
7199
7437
|
}
|
|
7200
|
-
|
|
7438
|
+
fs10.renameSync(failuresPath, path7.join(archiveDir, archiveName));
|
|
7201
7439
|
failuresCacheMap.delete(failuresPath);
|
|
7202
7440
|
return (0, import_types.Ok)(void 0);
|
|
7203
7441
|
} catch (error) {
|
|
@@ -7208,14 +7446,18 @@ async function archiveFailures(projectPath, stream) {
|
|
|
7208
7446
|
);
|
|
7209
7447
|
}
|
|
7210
7448
|
}
|
|
7211
|
-
|
|
7449
|
+
|
|
7450
|
+
// src/state/handoff.ts
|
|
7451
|
+
var fs11 = __toESM(require("fs"));
|
|
7452
|
+
var path8 = __toESM(require("path"));
|
|
7453
|
+
async function saveHandoff(projectPath, handoff, stream, session) {
|
|
7212
7454
|
try {
|
|
7213
|
-
const dirResult = await getStateDir(projectPath, stream);
|
|
7455
|
+
const dirResult = await getStateDir(projectPath, stream, session);
|
|
7214
7456
|
if (!dirResult.ok) return dirResult;
|
|
7215
7457
|
const stateDir = dirResult.value;
|
|
7216
|
-
const handoffPath =
|
|
7217
|
-
|
|
7218
|
-
|
|
7458
|
+
const handoffPath = path8.join(stateDir, HANDOFF_FILE);
|
|
7459
|
+
fs11.mkdirSync(stateDir, { recursive: true });
|
|
7460
|
+
fs11.writeFileSync(handoffPath, JSON.stringify(handoff, null, 2));
|
|
7219
7461
|
return (0, import_types.Ok)(void 0);
|
|
7220
7462
|
} catch (error) {
|
|
7221
7463
|
return (0, import_types.Err)(
|
|
@@ -7223,16 +7465,16 @@ async function saveHandoff(projectPath, handoff, stream) {
|
|
|
7223
7465
|
);
|
|
7224
7466
|
}
|
|
7225
7467
|
}
|
|
7226
|
-
async function loadHandoff(projectPath, stream) {
|
|
7468
|
+
async function loadHandoff(projectPath, stream, session) {
|
|
7227
7469
|
try {
|
|
7228
|
-
const dirResult = await getStateDir(projectPath, stream);
|
|
7470
|
+
const dirResult = await getStateDir(projectPath, stream, session);
|
|
7229
7471
|
if (!dirResult.ok) return dirResult;
|
|
7230
7472
|
const stateDir = dirResult.value;
|
|
7231
|
-
const handoffPath =
|
|
7232
|
-
if (!
|
|
7473
|
+
const handoffPath = path8.join(stateDir, HANDOFF_FILE);
|
|
7474
|
+
if (!fs11.existsSync(handoffPath)) {
|
|
7233
7475
|
return (0, import_types.Ok)(null);
|
|
7234
7476
|
}
|
|
7235
|
-
const raw =
|
|
7477
|
+
const raw = fs11.readFileSync(handoffPath, "utf-8");
|
|
7236
7478
|
const parsed = JSON.parse(raw);
|
|
7237
7479
|
const result = HandoffSchema.safeParse(parsed);
|
|
7238
7480
|
if (!result.success) {
|
|
@@ -7245,73 +7487,82 @@ async function loadHandoff(projectPath, stream) {
|
|
|
7245
7487
|
);
|
|
7246
7488
|
}
|
|
7247
7489
|
}
|
|
7490
|
+
|
|
7491
|
+
// src/state/mechanical-gate.ts
|
|
7492
|
+
var fs12 = __toESM(require("fs"));
|
|
7493
|
+
var path9 = __toESM(require("path"));
|
|
7494
|
+
var import_child_process2 = require("child_process");
|
|
7495
|
+
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:.-]+$/;
|
|
7496
|
+
function loadChecksFromConfig(gateConfigPath) {
|
|
7497
|
+
if (!fs12.existsSync(gateConfigPath)) return [];
|
|
7498
|
+
const raw = JSON.parse(fs12.readFileSync(gateConfigPath, "utf-8"));
|
|
7499
|
+
const config = GateConfigSchema.safeParse(raw);
|
|
7500
|
+
if (config.success && config.data.checks) return config.data.checks;
|
|
7501
|
+
return [];
|
|
7502
|
+
}
|
|
7503
|
+
function discoverChecksFromProject(projectPath) {
|
|
7504
|
+
const checks = [];
|
|
7505
|
+
const packageJsonPath = path9.join(projectPath, "package.json");
|
|
7506
|
+
if (fs12.existsSync(packageJsonPath)) {
|
|
7507
|
+
const pkg = JSON.parse(fs12.readFileSync(packageJsonPath, "utf-8"));
|
|
7508
|
+
const scripts = pkg.scripts || {};
|
|
7509
|
+
if (scripts.test) checks.push({ name: "test", command: "npm test" });
|
|
7510
|
+
if (scripts.lint) checks.push({ name: "lint", command: "npm run lint" });
|
|
7511
|
+
if (scripts.typecheck) checks.push({ name: "typecheck", command: "npm run typecheck" });
|
|
7512
|
+
if (scripts.build) checks.push({ name: "build", command: "npm run build" });
|
|
7513
|
+
}
|
|
7514
|
+
if (fs12.existsSync(path9.join(projectPath, "go.mod"))) {
|
|
7515
|
+
checks.push({ name: "test", command: "go test ./..." });
|
|
7516
|
+
checks.push({ name: "build", command: "go build ./..." });
|
|
7517
|
+
}
|
|
7518
|
+
if (fs12.existsSync(path9.join(projectPath, "pyproject.toml")) || fs12.existsSync(path9.join(projectPath, "setup.py"))) {
|
|
7519
|
+
checks.push({ name: "test", command: "python -m pytest" });
|
|
7520
|
+
}
|
|
7521
|
+
return checks;
|
|
7522
|
+
}
|
|
7523
|
+
function executeCheck(check, projectPath) {
|
|
7524
|
+
if (!SAFE_GATE_COMMAND.test(check.command)) {
|
|
7525
|
+
return {
|
|
7526
|
+
name: check.name,
|
|
7527
|
+
passed: false,
|
|
7528
|
+
command: check.command,
|
|
7529
|
+
output: `Blocked: command does not match safe gate pattern. Allowed prefixes: npm, pnpm, yarn, go, python, python3, make, cargo, gradle, mvn`,
|
|
7530
|
+
duration: 0
|
|
7531
|
+
};
|
|
7532
|
+
}
|
|
7533
|
+
const start = Date.now();
|
|
7534
|
+
try {
|
|
7535
|
+
(0, import_child_process2.execSync)(check.command, {
|
|
7536
|
+
cwd: projectPath,
|
|
7537
|
+
stdio: "pipe",
|
|
7538
|
+
timeout: 12e4
|
|
7539
|
+
});
|
|
7540
|
+
return {
|
|
7541
|
+
name: check.name,
|
|
7542
|
+
passed: true,
|
|
7543
|
+
command: check.command,
|
|
7544
|
+
duration: Date.now() - start
|
|
7545
|
+
};
|
|
7546
|
+
} catch (error) {
|
|
7547
|
+
const output = error instanceof Error ? error.stderr?.toString() || error.message : String(error);
|
|
7548
|
+
return {
|
|
7549
|
+
name: check.name,
|
|
7550
|
+
passed: false,
|
|
7551
|
+
command: check.command,
|
|
7552
|
+
output: output.slice(0, 2e3),
|
|
7553
|
+
duration: Date.now() - start
|
|
7554
|
+
};
|
|
7555
|
+
}
|
|
7556
|
+
}
|
|
7248
7557
|
async function runMechanicalGate(projectPath) {
|
|
7249
|
-
const harnessDir =
|
|
7250
|
-
const gateConfigPath =
|
|
7558
|
+
const harnessDir = path9.join(projectPath, HARNESS_DIR);
|
|
7559
|
+
const gateConfigPath = path9.join(harnessDir, GATE_CONFIG_FILE);
|
|
7251
7560
|
try {
|
|
7252
|
-
let checks =
|
|
7253
|
-
if (fs6.existsSync(gateConfigPath)) {
|
|
7254
|
-
const raw = JSON.parse(fs6.readFileSync(gateConfigPath, "utf-8"));
|
|
7255
|
-
const config = GateConfigSchema.safeParse(raw);
|
|
7256
|
-
if (config.success && config.data.checks) {
|
|
7257
|
-
checks = config.data.checks;
|
|
7258
|
-
}
|
|
7259
|
-
}
|
|
7561
|
+
let checks = loadChecksFromConfig(gateConfigPath);
|
|
7260
7562
|
if (checks.length === 0) {
|
|
7261
|
-
|
|
7262
|
-
if (fs6.existsSync(packageJsonPath)) {
|
|
7263
|
-
const pkg = JSON.parse(fs6.readFileSync(packageJsonPath, "utf-8"));
|
|
7264
|
-
const scripts = pkg.scripts || {};
|
|
7265
|
-
if (scripts.test) checks.push({ name: "test", command: "npm test" });
|
|
7266
|
-
if (scripts.lint) checks.push({ name: "lint", command: "npm run lint" });
|
|
7267
|
-
if (scripts.typecheck) checks.push({ name: "typecheck", command: "npm run typecheck" });
|
|
7268
|
-
if (scripts.build) checks.push({ name: "build", command: "npm run build" });
|
|
7269
|
-
}
|
|
7270
|
-
if (fs6.existsSync(path3.join(projectPath, "go.mod"))) {
|
|
7271
|
-
checks.push({ name: "test", command: "go test ./..." });
|
|
7272
|
-
checks.push({ name: "build", command: "go build ./..." });
|
|
7273
|
-
}
|
|
7274
|
-
if (fs6.existsSync(path3.join(projectPath, "pyproject.toml")) || fs6.existsSync(path3.join(projectPath, "setup.py"))) {
|
|
7275
|
-
checks.push({ name: "test", command: "python -m pytest" });
|
|
7276
|
-
}
|
|
7277
|
-
}
|
|
7278
|
-
const results = [];
|
|
7279
|
-
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:.-]+$/;
|
|
7280
|
-
for (const check of checks) {
|
|
7281
|
-
if (!SAFE_GATE_COMMAND.test(check.command)) {
|
|
7282
|
-
results.push({
|
|
7283
|
-
name: check.name,
|
|
7284
|
-
passed: false,
|
|
7285
|
-
command: check.command,
|
|
7286
|
-
output: `Blocked: command does not match safe gate pattern. Allowed prefixes: npm, npx, pnpm, yarn, go, python, python3, make, cargo, gradle, mvn`,
|
|
7287
|
-
duration: 0
|
|
7288
|
-
});
|
|
7289
|
-
continue;
|
|
7290
|
-
}
|
|
7291
|
-
const start = Date.now();
|
|
7292
|
-
try {
|
|
7293
|
-
(0, import_child_process2.execSync)(check.command, {
|
|
7294
|
-
cwd: projectPath,
|
|
7295
|
-
stdio: "pipe",
|
|
7296
|
-
timeout: 12e4
|
|
7297
|
-
});
|
|
7298
|
-
results.push({
|
|
7299
|
-
name: check.name,
|
|
7300
|
-
passed: true,
|
|
7301
|
-
command: check.command,
|
|
7302
|
-
duration: Date.now() - start
|
|
7303
|
-
});
|
|
7304
|
-
} catch (error) {
|
|
7305
|
-
const output = error instanceof Error ? error.stderr?.toString() || error.message : String(error);
|
|
7306
|
-
results.push({
|
|
7307
|
-
name: check.name,
|
|
7308
|
-
passed: false,
|
|
7309
|
-
command: check.command,
|
|
7310
|
-
output: output.slice(0, 2e3),
|
|
7311
|
-
duration: Date.now() - start
|
|
7312
|
-
});
|
|
7313
|
-
}
|
|
7563
|
+
checks = discoverChecksFromProject(projectPath);
|
|
7314
7564
|
}
|
|
7565
|
+
const results = checks.map((check) => executeCheck(check, projectPath));
|
|
7315
7566
|
return (0, import_types.Ok)({
|
|
7316
7567
|
passed: results.length === 0 || results.every((r) => r.passed),
|
|
7317
7568
|
checks: results
|
|
@@ -7325,6 +7576,96 @@ async function runMechanicalGate(projectPath) {
|
|
|
7325
7576
|
}
|
|
7326
7577
|
}
|
|
7327
7578
|
|
|
7579
|
+
// src/state/session-summary.ts
|
|
7580
|
+
var fs13 = __toESM(require("fs"));
|
|
7581
|
+
var path10 = __toESM(require("path"));
|
|
7582
|
+
function formatSummary(data) {
|
|
7583
|
+
const lines = [
|
|
7584
|
+
"## Session Summary",
|
|
7585
|
+
"",
|
|
7586
|
+
`**Session:** ${data.session}`,
|
|
7587
|
+
`**Last active:** ${data.lastActive}`,
|
|
7588
|
+
`**Skill:** ${data.skill}`
|
|
7589
|
+
];
|
|
7590
|
+
if (data.phase) {
|
|
7591
|
+
lines.push(`**Phase:** ${data.phase}`);
|
|
7592
|
+
}
|
|
7593
|
+
lines.push(`**Status:** ${data.status}`);
|
|
7594
|
+
if (data.spec) {
|
|
7595
|
+
lines.push(`**Spec:** ${data.spec}`);
|
|
7596
|
+
}
|
|
7597
|
+
if (data.plan) {
|
|
7598
|
+
lines.push(`**Plan:** ${data.plan}`);
|
|
7599
|
+
}
|
|
7600
|
+
lines.push(`**Key context:** ${data.keyContext}`);
|
|
7601
|
+
lines.push(`**Next step:** ${data.nextStep}`);
|
|
7602
|
+
lines.push("");
|
|
7603
|
+
return lines.join("\n");
|
|
7604
|
+
}
|
|
7605
|
+
function deriveIndexDescription(data) {
|
|
7606
|
+
const skillShort = data.skill.replace("harness-", "");
|
|
7607
|
+
const parts = [skillShort];
|
|
7608
|
+
if (data.phase) {
|
|
7609
|
+
parts.push(`phase ${data.phase}`);
|
|
7610
|
+
}
|
|
7611
|
+
parts.push(data.status.toLowerCase());
|
|
7612
|
+
return parts.join(", ");
|
|
7613
|
+
}
|
|
7614
|
+
function writeSessionSummary(projectPath, sessionSlug, data) {
|
|
7615
|
+
try {
|
|
7616
|
+
const dirResult = resolveSessionDir(projectPath, sessionSlug, { create: true });
|
|
7617
|
+
if (!dirResult.ok) return dirResult;
|
|
7618
|
+
const sessionDir = dirResult.value;
|
|
7619
|
+
const summaryPath = path10.join(sessionDir, SUMMARY_FILE);
|
|
7620
|
+
const content = formatSummary(data);
|
|
7621
|
+
fs13.writeFileSync(summaryPath, content);
|
|
7622
|
+
const description = deriveIndexDescription(data);
|
|
7623
|
+
updateSessionIndex(projectPath, sessionSlug, description);
|
|
7624
|
+
return (0, import_types.Ok)(void 0);
|
|
7625
|
+
} catch (error) {
|
|
7626
|
+
return (0, import_types.Err)(
|
|
7627
|
+
new Error(
|
|
7628
|
+
`Failed to write session summary: ${error instanceof Error ? error.message : String(error)}`
|
|
7629
|
+
)
|
|
7630
|
+
);
|
|
7631
|
+
}
|
|
7632
|
+
}
|
|
7633
|
+
function loadSessionSummary(projectPath, sessionSlug) {
|
|
7634
|
+
try {
|
|
7635
|
+
const dirResult = resolveSessionDir(projectPath, sessionSlug);
|
|
7636
|
+
if (!dirResult.ok) return dirResult;
|
|
7637
|
+
const sessionDir = dirResult.value;
|
|
7638
|
+
const summaryPath = path10.join(sessionDir, SUMMARY_FILE);
|
|
7639
|
+
if (!fs13.existsSync(summaryPath)) {
|
|
7640
|
+
return (0, import_types.Ok)(null);
|
|
7641
|
+
}
|
|
7642
|
+
const content = fs13.readFileSync(summaryPath, "utf-8");
|
|
7643
|
+
return (0, import_types.Ok)(content);
|
|
7644
|
+
} catch (error) {
|
|
7645
|
+
return (0, import_types.Err)(
|
|
7646
|
+
new Error(
|
|
7647
|
+
`Failed to load session summary: ${error instanceof Error ? error.message : String(error)}`
|
|
7648
|
+
)
|
|
7649
|
+
);
|
|
7650
|
+
}
|
|
7651
|
+
}
|
|
7652
|
+
function listActiveSessions(projectPath) {
|
|
7653
|
+
try {
|
|
7654
|
+
const indexPath2 = path10.join(projectPath, HARNESS_DIR, SESSIONS_DIR, SESSION_INDEX_FILE);
|
|
7655
|
+
if (!fs13.existsSync(indexPath2)) {
|
|
7656
|
+
return (0, import_types.Ok)(null);
|
|
7657
|
+
}
|
|
7658
|
+
const content = fs13.readFileSync(indexPath2, "utf-8");
|
|
7659
|
+
return (0, import_types.Ok)(content);
|
|
7660
|
+
} catch (error) {
|
|
7661
|
+
return (0, import_types.Err)(
|
|
7662
|
+
new Error(
|
|
7663
|
+
`Failed to list active sessions: ${error instanceof Error ? error.message : String(error)}`
|
|
7664
|
+
)
|
|
7665
|
+
);
|
|
7666
|
+
}
|
|
7667
|
+
}
|
|
7668
|
+
|
|
7328
7669
|
// src/workflow/runner.ts
|
|
7329
7670
|
async function executeWorkflow(workflow, executor) {
|
|
7330
7671
|
const stepResults = [];
|
|
@@ -7474,7 +7815,7 @@ async function runMultiTurnPipeline(initialContext, turnExecutor, options) {
|
|
|
7474
7815
|
}
|
|
7475
7816
|
|
|
7476
7817
|
// src/security/scanner.ts
|
|
7477
|
-
var
|
|
7818
|
+
var fs15 = __toESM(require("fs/promises"));
|
|
7478
7819
|
|
|
7479
7820
|
// src/security/rules/registry.ts
|
|
7480
7821
|
var RuleRegistry = class {
|
|
@@ -7561,15 +7902,15 @@ function resolveRuleSeverity(ruleId, defaultSeverity, overrides, strict) {
|
|
|
7561
7902
|
}
|
|
7562
7903
|
|
|
7563
7904
|
// src/security/stack-detector.ts
|
|
7564
|
-
var
|
|
7565
|
-
var
|
|
7905
|
+
var fs14 = __toESM(require("fs"));
|
|
7906
|
+
var path11 = __toESM(require("path"));
|
|
7566
7907
|
function detectStack(projectRoot) {
|
|
7567
7908
|
const stacks = [];
|
|
7568
|
-
const pkgJsonPath =
|
|
7569
|
-
if (
|
|
7909
|
+
const pkgJsonPath = path11.join(projectRoot, "package.json");
|
|
7910
|
+
if (fs14.existsSync(pkgJsonPath)) {
|
|
7570
7911
|
stacks.push("node");
|
|
7571
7912
|
try {
|
|
7572
|
-
const pkgJson = JSON.parse(
|
|
7913
|
+
const pkgJson = JSON.parse(fs14.readFileSync(pkgJsonPath, "utf-8"));
|
|
7573
7914
|
const allDeps = {
|
|
7574
7915
|
...pkgJson.dependencies,
|
|
7575
7916
|
...pkgJson.devDependencies
|
|
@@ -7584,13 +7925,13 @@ function detectStack(projectRoot) {
|
|
|
7584
7925
|
} catch {
|
|
7585
7926
|
}
|
|
7586
7927
|
}
|
|
7587
|
-
const goModPath =
|
|
7588
|
-
if (
|
|
7928
|
+
const goModPath = path11.join(projectRoot, "go.mod");
|
|
7929
|
+
if (fs14.existsSync(goModPath)) {
|
|
7589
7930
|
stacks.push("go");
|
|
7590
7931
|
}
|
|
7591
|
-
const requirementsPath =
|
|
7592
|
-
const pyprojectPath =
|
|
7593
|
-
if (
|
|
7932
|
+
const requirementsPath = path11.join(projectRoot, "requirements.txt");
|
|
7933
|
+
const pyprojectPath = path11.join(projectRoot, "pyproject.toml");
|
|
7934
|
+
if (fs14.existsSync(requirementsPath) || fs14.existsSync(pyprojectPath)) {
|
|
7594
7935
|
stacks.push("python");
|
|
7595
7936
|
}
|
|
7596
7937
|
return stacks;
|
|
@@ -8017,7 +8358,7 @@ var SecurityScanner = class {
|
|
|
8017
8358
|
}
|
|
8018
8359
|
async scanFile(filePath) {
|
|
8019
8360
|
if (!this.config.enabled) return [];
|
|
8020
|
-
const content = await
|
|
8361
|
+
const content = await fs15.readFile(filePath, "utf-8");
|
|
8021
8362
|
return this.scanContent(content, filePath, 1);
|
|
8022
8363
|
}
|
|
8023
8364
|
async scanFiles(filePaths) {
|
|
@@ -8042,7 +8383,7 @@ var SecurityScanner = class {
|
|
|
8042
8383
|
};
|
|
8043
8384
|
|
|
8044
8385
|
// src/ci/check-orchestrator.ts
|
|
8045
|
-
var
|
|
8386
|
+
var path12 = __toESM(require("path"));
|
|
8046
8387
|
var ALL_CHECKS = [
|
|
8047
8388
|
"validate",
|
|
8048
8389
|
"deps",
|
|
@@ -8053,238 +8394,270 @@ var ALL_CHECKS = [
|
|
|
8053
8394
|
"phase-gate",
|
|
8054
8395
|
"arch"
|
|
8055
8396
|
];
|
|
8056
|
-
async function
|
|
8057
|
-
const start = Date.now();
|
|
8397
|
+
async function runValidateCheck(projectRoot, config) {
|
|
8058
8398
|
const issues = [];
|
|
8059
|
-
|
|
8060
|
-
|
|
8061
|
-
|
|
8062
|
-
|
|
8063
|
-
|
|
8064
|
-
|
|
8065
|
-
|
|
8066
|
-
|
|
8067
|
-
if (result.value.errors) {
|
|
8068
|
-
for (const err of result.value.errors) {
|
|
8069
|
-
issues.push({ severity: "error", message: err.message });
|
|
8070
|
-
}
|
|
8071
|
-
}
|
|
8072
|
-
for (const section of result.value.missingSections) {
|
|
8073
|
-
issues.push({ severity: "warning", message: `Missing section: ${section}` });
|
|
8074
|
-
}
|
|
8075
|
-
for (const link of result.value.brokenLinks) {
|
|
8076
|
-
issues.push({
|
|
8077
|
-
severity: "warning",
|
|
8078
|
-
message: `Broken link: ${link.text} \u2192 ${link.path}`,
|
|
8079
|
-
file: link.path
|
|
8080
|
-
});
|
|
8081
|
-
}
|
|
8082
|
-
}
|
|
8083
|
-
break;
|
|
8399
|
+
const agentsPath = path12.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
|
|
8400
|
+
const result = await validateAgentsMap(agentsPath);
|
|
8401
|
+
if (!result.ok) {
|
|
8402
|
+
issues.push({ severity: "error", message: result.error.message });
|
|
8403
|
+
} else if (!result.value.valid) {
|
|
8404
|
+
if (result.value.errors) {
|
|
8405
|
+
for (const err of result.value.errors) {
|
|
8406
|
+
issues.push({ severity: "error", message: err.message });
|
|
8084
8407
|
}
|
|
8085
|
-
|
|
8086
|
-
|
|
8087
|
-
|
|
8088
|
-
|
|
8089
|
-
|
|
8090
|
-
|
|
8091
|
-
|
|
8092
|
-
|
|
8093
|
-
|
|
8094
|
-
|
|
8095
|
-
|
|
8096
|
-
|
|
8097
|
-
|
|
8098
|
-
|
|
8099
|
-
|
|
8100
|
-
|
|
8101
|
-
|
|
8102
|
-
|
|
8103
|
-
|
|
8104
|
-
|
|
8105
|
-
|
|
8106
|
-
|
|
8107
|
-
|
|
8108
|
-
|
|
8109
|
-
|
|
8110
|
-
|
|
8111
|
-
|
|
8112
|
-
|
|
8113
|
-
|
|
8114
|
-
|
|
8408
|
+
}
|
|
8409
|
+
for (const section of result.value.missingSections) {
|
|
8410
|
+
issues.push({ severity: "warning", message: `Missing section: ${section}` });
|
|
8411
|
+
}
|
|
8412
|
+
for (const link of result.value.brokenLinks) {
|
|
8413
|
+
issues.push({
|
|
8414
|
+
severity: "warning",
|
|
8415
|
+
message: `Broken link: ${link.text} \u2192 ${link.path}`,
|
|
8416
|
+
file: link.path
|
|
8417
|
+
});
|
|
8418
|
+
}
|
|
8419
|
+
}
|
|
8420
|
+
return issues;
|
|
8421
|
+
}
|
|
8422
|
+
async function runDepsCheck(projectRoot, config) {
|
|
8423
|
+
const issues = [];
|
|
8424
|
+
const rawLayers = config.layers;
|
|
8425
|
+
if (rawLayers && rawLayers.length > 0) {
|
|
8426
|
+
const parser = new TypeScriptParser();
|
|
8427
|
+
const layers = rawLayers.map(
|
|
8428
|
+
(l) => defineLayer(
|
|
8429
|
+
l.name,
|
|
8430
|
+
Array.isArray(l.patterns) ? l.patterns : [l.pattern],
|
|
8431
|
+
l.allowedDependencies
|
|
8432
|
+
)
|
|
8433
|
+
);
|
|
8434
|
+
const result = await validateDependencies({
|
|
8435
|
+
layers,
|
|
8436
|
+
rootDir: projectRoot,
|
|
8437
|
+
parser
|
|
8438
|
+
});
|
|
8439
|
+
if (!result.ok) {
|
|
8440
|
+
issues.push({ severity: "error", message: result.error.message });
|
|
8441
|
+
} else if (result.value.violations.length > 0) {
|
|
8442
|
+
for (const v of result.value.violations) {
|
|
8443
|
+
issues.push({
|
|
8444
|
+
severity: "error",
|
|
8445
|
+
message: `${v.reason}: ${v.file} imports ${v.imports} (${v.fromLayer} \u2192 ${v.toLayer})`,
|
|
8446
|
+
file: v.file,
|
|
8447
|
+
line: v.line
|
|
8448
|
+
});
|
|
8115
8449
|
}
|
|
8116
|
-
|
|
8117
|
-
|
|
8118
|
-
|
|
8119
|
-
|
|
8120
|
-
|
|
8121
|
-
|
|
8122
|
-
|
|
8123
|
-
|
|
8124
|
-
|
|
8125
|
-
|
|
8126
|
-
|
|
8127
|
-
|
|
8450
|
+
}
|
|
8451
|
+
}
|
|
8452
|
+
return issues;
|
|
8453
|
+
}
|
|
8454
|
+
async function runDocsCheck(projectRoot, config) {
|
|
8455
|
+
const issues = [];
|
|
8456
|
+
const docsDir = path12.join(projectRoot, config.docsDir ?? "docs");
|
|
8457
|
+
const entropyConfig = config.entropy || {};
|
|
8458
|
+
const result = await checkDocCoverage("project", {
|
|
8459
|
+
docsDir,
|
|
8460
|
+
sourceDir: projectRoot,
|
|
8461
|
+
excludePatterns: entropyConfig.excludePatterns || [
|
|
8462
|
+
"**/node_modules/**",
|
|
8463
|
+
"**/dist/**",
|
|
8464
|
+
"**/*.test.ts",
|
|
8465
|
+
"**/fixtures/**"
|
|
8466
|
+
]
|
|
8467
|
+
});
|
|
8468
|
+
if (!result.ok) {
|
|
8469
|
+
issues.push({ severity: "warning", message: result.error.message });
|
|
8470
|
+
} else if (result.value.gaps.length > 0) {
|
|
8471
|
+
for (const gap of result.value.gaps) {
|
|
8472
|
+
issues.push({
|
|
8473
|
+
severity: "warning",
|
|
8474
|
+
message: `Undocumented: ${gap.file} (suggested: ${gap.suggestedSection})`,
|
|
8475
|
+
file: gap.file
|
|
8476
|
+
});
|
|
8477
|
+
}
|
|
8478
|
+
}
|
|
8479
|
+
return issues;
|
|
8480
|
+
}
|
|
8481
|
+
async function runEntropyCheck(projectRoot, _config) {
|
|
8482
|
+
const issues = [];
|
|
8483
|
+
const analyzer = new EntropyAnalyzer({
|
|
8484
|
+
rootDir: projectRoot,
|
|
8485
|
+
analyze: { drift: true, deadCode: true, patterns: false }
|
|
8486
|
+
});
|
|
8487
|
+
const result = await analyzer.analyze();
|
|
8488
|
+
if (!result.ok) {
|
|
8489
|
+
issues.push({ severity: "warning", message: result.error.message });
|
|
8490
|
+
} else {
|
|
8491
|
+
const report = result.value;
|
|
8492
|
+
if (report.drift) {
|
|
8493
|
+
for (const drift of report.drift.drifts) {
|
|
8494
|
+
issues.push({
|
|
8495
|
+
severity: "warning",
|
|
8496
|
+
message: `Doc drift (${drift.type}): ${drift.details}`,
|
|
8497
|
+
file: drift.docFile,
|
|
8498
|
+
line: drift.line
|
|
8128
8499
|
});
|
|
8129
|
-
if (!result.ok) {
|
|
8130
|
-
issues.push({ severity: "warning", message: result.error.message });
|
|
8131
|
-
} else if (result.value.gaps.length > 0) {
|
|
8132
|
-
for (const gap of result.value.gaps) {
|
|
8133
|
-
issues.push({
|
|
8134
|
-
severity: "warning",
|
|
8135
|
-
message: `Undocumented: ${gap.file} (suggested: ${gap.suggestedSection})`,
|
|
8136
|
-
file: gap.file
|
|
8137
|
-
});
|
|
8138
|
-
}
|
|
8139
|
-
}
|
|
8140
|
-
break;
|
|
8141
8500
|
}
|
|
8142
|
-
|
|
8143
|
-
|
|
8144
|
-
|
|
8145
|
-
|
|
8501
|
+
}
|
|
8502
|
+
if (report.deadCode) {
|
|
8503
|
+
for (const dead of report.deadCode.deadExports) {
|
|
8504
|
+
issues.push({
|
|
8505
|
+
severity: "warning",
|
|
8506
|
+
message: `Dead export: ${dead.name}`,
|
|
8507
|
+
file: dead.file,
|
|
8508
|
+
line: dead.line
|
|
8146
8509
|
});
|
|
8147
|
-
const result = await analyzer.analyze();
|
|
8148
|
-
if (!result.ok) {
|
|
8149
|
-
issues.push({ severity: "warning", message: result.error.message });
|
|
8150
|
-
} else {
|
|
8151
|
-
const report = result.value;
|
|
8152
|
-
if (report.drift) {
|
|
8153
|
-
for (const drift of report.drift.drifts) {
|
|
8154
|
-
issues.push({
|
|
8155
|
-
severity: "warning",
|
|
8156
|
-
message: `Doc drift (${drift.type}): ${drift.details}`,
|
|
8157
|
-
file: drift.docFile,
|
|
8158
|
-
line: drift.line
|
|
8159
|
-
});
|
|
8160
|
-
}
|
|
8161
|
-
}
|
|
8162
|
-
if (report.deadCode) {
|
|
8163
|
-
for (const dead of report.deadCode.deadExports) {
|
|
8164
|
-
issues.push({
|
|
8165
|
-
severity: "warning",
|
|
8166
|
-
message: `Dead export: ${dead.name}`,
|
|
8167
|
-
file: dead.file,
|
|
8168
|
-
line: dead.line
|
|
8169
|
-
});
|
|
8170
|
-
}
|
|
8171
|
-
}
|
|
8172
|
-
}
|
|
8173
|
-
break;
|
|
8174
8510
|
}
|
|
8175
|
-
|
|
8176
|
-
|
|
8177
|
-
|
|
8178
|
-
|
|
8179
|
-
|
|
8180
|
-
|
|
8181
|
-
|
|
8182
|
-
|
|
8183
|
-
|
|
8184
|
-
|
|
8185
|
-
|
|
8186
|
-
|
|
8187
|
-
|
|
8188
|
-
|
|
8189
|
-
|
|
8511
|
+
}
|
|
8512
|
+
}
|
|
8513
|
+
return issues;
|
|
8514
|
+
}
|
|
8515
|
+
async function runSecurityCheck(projectRoot, config) {
|
|
8516
|
+
const issues = [];
|
|
8517
|
+
const securityConfig = parseSecurityConfig(config.security);
|
|
8518
|
+
if (!securityConfig.enabled) return issues;
|
|
8519
|
+
const scanner = new SecurityScanner(securityConfig);
|
|
8520
|
+
scanner.configureForProject(projectRoot);
|
|
8521
|
+
const { glob: globFn } = await import("glob");
|
|
8522
|
+
const sourceFiles = await globFn("**/*.{ts,tsx,js,jsx,go,py}", {
|
|
8523
|
+
cwd: projectRoot,
|
|
8524
|
+
ignore: securityConfig.exclude ?? [
|
|
8525
|
+
"**/node_modules/**",
|
|
8526
|
+
"**/dist/**",
|
|
8527
|
+
"**/*.test.ts",
|
|
8528
|
+
"**/fixtures/**"
|
|
8529
|
+
],
|
|
8530
|
+
absolute: true
|
|
8531
|
+
});
|
|
8532
|
+
const scanResult = await scanner.scanFiles(sourceFiles);
|
|
8533
|
+
for (const finding of scanResult.findings) {
|
|
8534
|
+
issues.push({
|
|
8535
|
+
severity: finding.severity === "info" ? "warning" : finding.severity,
|
|
8536
|
+
message: `[${finding.ruleId}] ${finding.message}: ${finding.match}`,
|
|
8537
|
+
file: finding.file,
|
|
8538
|
+
line: finding.line
|
|
8539
|
+
});
|
|
8540
|
+
}
|
|
8541
|
+
return issues;
|
|
8542
|
+
}
|
|
8543
|
+
async function runPerfCheck(projectRoot, config) {
|
|
8544
|
+
const issues = [];
|
|
8545
|
+
const perfConfig = config.performance || {};
|
|
8546
|
+
const perfAnalyzer = new EntropyAnalyzer({
|
|
8547
|
+
rootDir: projectRoot,
|
|
8548
|
+
analyze: {
|
|
8549
|
+
complexity: perfConfig.complexity || true,
|
|
8550
|
+
coupling: perfConfig.coupling || true,
|
|
8551
|
+
sizeBudget: perfConfig.sizeBudget || false
|
|
8552
|
+
}
|
|
8553
|
+
});
|
|
8554
|
+
const perfResult = await perfAnalyzer.analyze();
|
|
8555
|
+
if (!perfResult.ok) {
|
|
8556
|
+
issues.push({ severity: "warning", message: perfResult.error.message });
|
|
8557
|
+
} else {
|
|
8558
|
+
const perfReport = perfResult.value;
|
|
8559
|
+
if (perfReport.complexity) {
|
|
8560
|
+
for (const v of perfReport.complexity.violations) {
|
|
8561
|
+
issues.push({
|
|
8562
|
+
severity: v.severity === "info" ? "warning" : v.severity,
|
|
8563
|
+
message: `[Tier ${v.tier}] ${v.metric}: ${v.function} in ${v.file} (${v.value} > ${v.threshold})`,
|
|
8564
|
+
file: v.file,
|
|
8565
|
+
line: v.line
|
|
8190
8566
|
});
|
|
8191
|
-
const scanResult = await scanner.scanFiles(sourceFiles);
|
|
8192
|
-
for (const finding of scanResult.findings) {
|
|
8193
|
-
issues.push({
|
|
8194
|
-
severity: finding.severity === "info" ? "warning" : finding.severity,
|
|
8195
|
-
message: `[${finding.ruleId}] ${finding.message}: ${finding.match}`,
|
|
8196
|
-
file: finding.file,
|
|
8197
|
-
line: finding.line
|
|
8198
|
-
});
|
|
8199
|
-
}
|
|
8200
|
-
break;
|
|
8201
8567
|
}
|
|
8202
|
-
|
|
8203
|
-
|
|
8204
|
-
|
|
8205
|
-
|
|
8206
|
-
|
|
8207
|
-
|
|
8208
|
-
|
|
8209
|
-
sizeBudget: perfConfig.sizeBudget || false
|
|
8210
|
-
}
|
|
8568
|
+
}
|
|
8569
|
+
if (perfReport.coupling) {
|
|
8570
|
+
for (const v of perfReport.coupling.violations) {
|
|
8571
|
+
issues.push({
|
|
8572
|
+
severity: v.severity === "info" ? "warning" : v.severity,
|
|
8573
|
+
message: `[Tier ${v.tier}] ${v.metric}: ${v.file} (${v.value} > ${v.threshold})`,
|
|
8574
|
+
file: v.file
|
|
8211
8575
|
});
|
|
8212
|
-
const perfResult = await perfAnalyzer.analyze();
|
|
8213
|
-
if (!perfResult.ok) {
|
|
8214
|
-
issues.push({ severity: "warning", message: perfResult.error.message });
|
|
8215
|
-
} else {
|
|
8216
|
-
const perfReport = perfResult.value;
|
|
8217
|
-
if (perfReport.complexity) {
|
|
8218
|
-
for (const v of perfReport.complexity.violations) {
|
|
8219
|
-
issues.push({
|
|
8220
|
-
severity: v.severity === "info" ? "warning" : v.severity,
|
|
8221
|
-
message: `[Tier ${v.tier}] ${v.metric}: ${v.function} in ${v.file} (${v.value} > ${v.threshold})`,
|
|
8222
|
-
file: v.file,
|
|
8223
|
-
line: v.line
|
|
8224
|
-
});
|
|
8225
|
-
}
|
|
8226
|
-
}
|
|
8227
|
-
if (perfReport.coupling) {
|
|
8228
|
-
for (const v of perfReport.coupling.violations) {
|
|
8229
|
-
issues.push({
|
|
8230
|
-
severity: v.severity === "info" ? "warning" : v.severity,
|
|
8231
|
-
message: `[Tier ${v.tier}] ${v.metric}: ${v.file} (${v.value} > ${v.threshold})`,
|
|
8232
|
-
file: v.file
|
|
8233
|
-
});
|
|
8234
|
-
}
|
|
8235
|
-
}
|
|
8236
|
-
}
|
|
8237
|
-
break;
|
|
8238
8576
|
}
|
|
8239
|
-
|
|
8240
|
-
|
|
8241
|
-
|
|
8242
|
-
|
|
8243
|
-
|
|
8577
|
+
}
|
|
8578
|
+
}
|
|
8579
|
+
return issues;
|
|
8580
|
+
}
|
|
8581
|
+
async function runPhaseGateCheck(_projectRoot, config) {
|
|
8582
|
+
const issues = [];
|
|
8583
|
+
const phaseGates = config.phaseGates;
|
|
8584
|
+
if (!phaseGates?.enabled) {
|
|
8585
|
+
return issues;
|
|
8586
|
+
}
|
|
8587
|
+
issues.push({
|
|
8588
|
+
severity: "warning",
|
|
8589
|
+
message: "Phase gate is enabled but requires CLI context. Run `harness check-phase-gate` separately for full validation."
|
|
8590
|
+
});
|
|
8591
|
+
return issues;
|
|
8592
|
+
}
|
|
8593
|
+
async function runArchCheck(projectRoot, config) {
|
|
8594
|
+
const issues = [];
|
|
8595
|
+
const rawArchConfig = config.architecture;
|
|
8596
|
+
const archConfig = ArchConfigSchema.parse(rawArchConfig ?? {});
|
|
8597
|
+
if (!archConfig.enabled) return issues;
|
|
8598
|
+
const results = await runAll(archConfig, projectRoot);
|
|
8599
|
+
const baselineManager = new ArchBaselineManager(projectRoot, archConfig.baselinePath);
|
|
8600
|
+
const baseline = baselineManager.load();
|
|
8601
|
+
if (baseline) {
|
|
8602
|
+
const diffResult = diff(results, baseline);
|
|
8603
|
+
if (!diffResult.passed) {
|
|
8604
|
+
for (const v of diffResult.newViolations) {
|
|
8244
8605
|
issues.push({
|
|
8245
|
-
severity:
|
|
8246
|
-
message:
|
|
8606
|
+
severity: v.severity,
|
|
8607
|
+
message: `[${v.category || "arch"}] NEW: ${v.detail}`,
|
|
8608
|
+
file: v.file
|
|
8247
8609
|
});
|
|
8248
|
-
break;
|
|
8249
8610
|
}
|
|
8250
|
-
|
|
8251
|
-
|
|
8252
|
-
|
|
8253
|
-
|
|
8254
|
-
|
|
8255
|
-
|
|
8256
|
-
|
|
8257
|
-
|
|
8258
|
-
|
|
8259
|
-
|
|
8260
|
-
|
|
8261
|
-
|
|
8262
|
-
|
|
8263
|
-
|
|
8264
|
-
|
|
8265
|
-
});
|
|
8266
|
-
}
|
|
8267
|
-
for (const r of diffResult.regressions) {
|
|
8268
|
-
issues.push({
|
|
8269
|
-
severity: "error",
|
|
8270
|
-
message: `[${r.category}] REGRESSION: ${r.currentValue} > ${r.baselineValue} (delta: ${r.delta})`
|
|
8271
|
-
});
|
|
8272
|
-
}
|
|
8273
|
-
}
|
|
8274
|
-
} else {
|
|
8275
|
-
for (const result of results) {
|
|
8276
|
-
for (const v of result.violations) {
|
|
8277
|
-
issues.push({
|
|
8278
|
-
severity: v.severity,
|
|
8279
|
-
message: `[${result.category}] ${v.detail}`,
|
|
8280
|
-
file: v.file
|
|
8281
|
-
});
|
|
8282
|
-
}
|
|
8283
|
-
}
|
|
8284
|
-
}
|
|
8285
|
-
break;
|
|
8611
|
+
for (const r of diffResult.regressions) {
|
|
8612
|
+
issues.push({
|
|
8613
|
+
severity: "error",
|
|
8614
|
+
message: `[${r.category}] REGRESSION: ${r.currentValue} > ${r.baselineValue} (delta: ${r.delta})`
|
|
8615
|
+
});
|
|
8616
|
+
}
|
|
8617
|
+
}
|
|
8618
|
+
} else {
|
|
8619
|
+
for (const result of results) {
|
|
8620
|
+
for (const v of result.violations) {
|
|
8621
|
+
issues.push({
|
|
8622
|
+
severity: v.severity,
|
|
8623
|
+
message: `[${result.category}] ${v.detail}`,
|
|
8624
|
+
file: v.file
|
|
8625
|
+
});
|
|
8286
8626
|
}
|
|
8287
8627
|
}
|
|
8628
|
+
}
|
|
8629
|
+
return issues;
|
|
8630
|
+
}
|
|
8631
|
+
async function runSingleCheck(name, projectRoot, config) {
|
|
8632
|
+
const start = Date.now();
|
|
8633
|
+
const issues = [];
|
|
8634
|
+
try {
|
|
8635
|
+
switch (name) {
|
|
8636
|
+
case "validate":
|
|
8637
|
+
issues.push(...await runValidateCheck(projectRoot, config));
|
|
8638
|
+
break;
|
|
8639
|
+
case "deps":
|
|
8640
|
+
issues.push(...await runDepsCheck(projectRoot, config));
|
|
8641
|
+
break;
|
|
8642
|
+
case "docs":
|
|
8643
|
+
issues.push(...await runDocsCheck(projectRoot, config));
|
|
8644
|
+
break;
|
|
8645
|
+
case "entropy":
|
|
8646
|
+
issues.push(...await runEntropyCheck(projectRoot, config));
|
|
8647
|
+
break;
|
|
8648
|
+
case "security":
|
|
8649
|
+
issues.push(...await runSecurityCheck(projectRoot, config));
|
|
8650
|
+
break;
|
|
8651
|
+
case "perf":
|
|
8652
|
+
issues.push(...await runPerfCheck(projectRoot, config));
|
|
8653
|
+
break;
|
|
8654
|
+
case "phase-gate":
|
|
8655
|
+
issues.push(...await runPhaseGateCheck(projectRoot, config));
|
|
8656
|
+
break;
|
|
8657
|
+
case "arch":
|
|
8658
|
+
issues.push(...await runArchCheck(projectRoot, config));
|
|
8659
|
+
break;
|
|
8660
|
+
}
|
|
8288
8661
|
} catch (error) {
|
|
8289
8662
|
issues.push({
|
|
8290
8663
|
severity: "error",
|
|
@@ -8352,7 +8725,7 @@ async function runCIChecks(input) {
|
|
|
8352
8725
|
}
|
|
8353
8726
|
|
|
8354
8727
|
// src/review/mechanical-checks.ts
|
|
8355
|
-
var
|
|
8728
|
+
var path13 = __toESM(require("path"));
|
|
8356
8729
|
async function runMechanicalChecks(options) {
|
|
8357
8730
|
const { projectRoot, config, skip = [], changedFiles } = options;
|
|
8358
8731
|
const findings = [];
|
|
@@ -8364,7 +8737,7 @@ async function runMechanicalChecks(options) {
|
|
|
8364
8737
|
};
|
|
8365
8738
|
if (!skip.includes("validate")) {
|
|
8366
8739
|
try {
|
|
8367
|
-
const agentsPath =
|
|
8740
|
+
const agentsPath = path13.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
|
|
8368
8741
|
const result = await validateAgentsMap(agentsPath);
|
|
8369
8742
|
if (!result.ok) {
|
|
8370
8743
|
statuses.validate = "fail";
|
|
@@ -8401,7 +8774,7 @@ async function runMechanicalChecks(options) {
|
|
|
8401
8774
|
statuses.validate = "fail";
|
|
8402
8775
|
findings.push({
|
|
8403
8776
|
tool: "validate",
|
|
8404
|
-
file:
|
|
8777
|
+
file: path13.join(projectRoot, "AGENTS.md"),
|
|
8405
8778
|
message: err instanceof Error ? err.message : String(err),
|
|
8406
8779
|
severity: "error"
|
|
8407
8780
|
});
|
|
@@ -8465,7 +8838,7 @@ async function runMechanicalChecks(options) {
|
|
|
8465
8838
|
(async () => {
|
|
8466
8839
|
const localFindings = [];
|
|
8467
8840
|
try {
|
|
8468
|
-
const docsDir =
|
|
8841
|
+
const docsDir = path13.join(projectRoot, config.docsDir ?? "docs");
|
|
8469
8842
|
const result = await checkDocCoverage("project", { docsDir });
|
|
8470
8843
|
if (!result.ok) {
|
|
8471
8844
|
statuses["check-docs"] = "warn";
|
|
@@ -8492,7 +8865,7 @@ async function runMechanicalChecks(options) {
|
|
|
8492
8865
|
statuses["check-docs"] = "warn";
|
|
8493
8866
|
localFindings.push({
|
|
8494
8867
|
tool: "check-docs",
|
|
8495
|
-
file:
|
|
8868
|
+
file: path13.join(projectRoot, "docs"),
|
|
8496
8869
|
message: err instanceof Error ? err.message : String(err),
|
|
8497
8870
|
severity: "warning"
|
|
8498
8871
|
});
|
|
@@ -8640,7 +9013,7 @@ function detectChangeType(commitMessage, diff2) {
|
|
|
8640
9013
|
}
|
|
8641
9014
|
|
|
8642
9015
|
// src/review/context-scoper.ts
|
|
8643
|
-
var
|
|
9016
|
+
var path14 = __toESM(require("path"));
|
|
8644
9017
|
var ALL_DOMAINS = ["compliance", "bug", "security", "architecture"];
|
|
8645
9018
|
var SECURITY_PATTERNS = /auth|crypto|password|secret|token|session|cookie|hash|encrypt|decrypt|sql|shell|exec|eval/i;
|
|
8646
9019
|
function computeContextBudget(diffLines) {
|
|
@@ -8648,18 +9021,18 @@ function computeContextBudget(diffLines) {
|
|
|
8648
9021
|
return diffLines;
|
|
8649
9022
|
}
|
|
8650
9023
|
function isWithinProject(absPath, projectRoot) {
|
|
8651
|
-
const resolvedRoot =
|
|
8652
|
-
const resolvedPath =
|
|
8653
|
-
return resolvedPath.startsWith(resolvedRoot) || resolvedPath ===
|
|
9024
|
+
const resolvedRoot = path14.resolve(projectRoot) + path14.sep;
|
|
9025
|
+
const resolvedPath = path14.resolve(absPath);
|
|
9026
|
+
return resolvedPath.startsWith(resolvedRoot) || resolvedPath === path14.resolve(projectRoot);
|
|
8654
9027
|
}
|
|
8655
9028
|
async function readContextFile(projectRoot, filePath, reason) {
|
|
8656
|
-
const absPath =
|
|
9029
|
+
const absPath = path14.isAbsolute(filePath) ? filePath : path14.join(projectRoot, filePath);
|
|
8657
9030
|
if (!isWithinProject(absPath, projectRoot)) return null;
|
|
8658
9031
|
const result = await readFileContent(absPath);
|
|
8659
9032
|
if (!result.ok) return null;
|
|
8660
9033
|
const content = result.value;
|
|
8661
9034
|
const lines = content.split("\n").length;
|
|
8662
|
-
const relPath =
|
|
9035
|
+
const relPath = path14.isAbsolute(filePath) ? relativePosix(projectRoot, filePath) : filePath;
|
|
8663
9036
|
return { path: relPath, content, reason, lines };
|
|
8664
9037
|
}
|
|
8665
9038
|
function extractImportSources2(content) {
|
|
@@ -8674,18 +9047,18 @@ function extractImportSources2(content) {
|
|
|
8674
9047
|
}
|
|
8675
9048
|
async function resolveImportPath2(projectRoot, fromFile, importSource) {
|
|
8676
9049
|
if (!importSource.startsWith(".")) return null;
|
|
8677
|
-
const fromDir =
|
|
8678
|
-
const basePath =
|
|
9050
|
+
const fromDir = path14.dirname(path14.join(projectRoot, fromFile));
|
|
9051
|
+
const basePath = path14.resolve(fromDir, importSource);
|
|
8679
9052
|
if (!isWithinProject(basePath, projectRoot)) return null;
|
|
8680
|
-
const relBase =
|
|
9053
|
+
const relBase = relativePosix(projectRoot, basePath);
|
|
8681
9054
|
const candidates = [
|
|
8682
9055
|
relBase + ".ts",
|
|
8683
9056
|
relBase + ".tsx",
|
|
8684
9057
|
relBase + ".mts",
|
|
8685
|
-
|
|
9058
|
+
path14.join(relBase, "index.ts")
|
|
8686
9059
|
];
|
|
8687
9060
|
for (const candidate of candidates) {
|
|
8688
|
-
const absCandidate =
|
|
9061
|
+
const absCandidate = path14.join(projectRoot, candidate);
|
|
8689
9062
|
if (await fileExists(absCandidate)) {
|
|
8690
9063
|
return candidate;
|
|
8691
9064
|
}
|
|
@@ -8693,10 +9066,10 @@ async function resolveImportPath2(projectRoot, fromFile, importSource) {
|
|
|
8693
9066
|
return null;
|
|
8694
9067
|
}
|
|
8695
9068
|
async function findTestFiles(projectRoot, sourceFile) {
|
|
8696
|
-
const baseName =
|
|
9069
|
+
const baseName = path14.basename(sourceFile, path14.extname(sourceFile));
|
|
8697
9070
|
const pattern = `**/${baseName}.{test,spec}.{ts,tsx,mts}`;
|
|
8698
9071
|
const results = await findFiles(pattern, projectRoot);
|
|
8699
|
-
return results.map((f) =>
|
|
9072
|
+
return results.map((f) => relativePosix(projectRoot, f));
|
|
8700
9073
|
}
|
|
8701
9074
|
async function gatherImportContext(projectRoot, changedFiles, budget) {
|
|
8702
9075
|
const contextFiles = [];
|
|
@@ -9484,7 +9857,7 @@ async function fanOutReview(options) {
|
|
|
9484
9857
|
}
|
|
9485
9858
|
|
|
9486
9859
|
// src/review/validate-findings.ts
|
|
9487
|
-
var
|
|
9860
|
+
var path15 = __toESM(require("path"));
|
|
9488
9861
|
var DOWNGRADE_MAP = {
|
|
9489
9862
|
critical: "important",
|
|
9490
9863
|
important: "suggestion",
|
|
@@ -9505,7 +9878,7 @@ function normalizePath(filePath, projectRoot) {
|
|
|
9505
9878
|
let normalized = filePath;
|
|
9506
9879
|
normalized = normalized.replace(/\\/g, "/");
|
|
9507
9880
|
const normalizedRoot = projectRoot.replace(/\\/g, "/");
|
|
9508
|
-
if (
|
|
9881
|
+
if (path15.isAbsolute(normalized)) {
|
|
9509
9882
|
const root = normalizedRoot.endsWith("/") ? normalizedRoot : normalizedRoot + "/";
|
|
9510
9883
|
if (normalized.startsWith(root)) {
|
|
9511
9884
|
normalized = normalized.slice(root.length);
|
|
@@ -9530,12 +9903,12 @@ function followImportChain(fromFile, fileContents, maxDepth = 2) {
|
|
|
9530
9903
|
while ((match = importRegex.exec(content)) !== null) {
|
|
9531
9904
|
const importPath = match[1];
|
|
9532
9905
|
if (!importPath.startsWith(".")) continue;
|
|
9533
|
-
const dir =
|
|
9534
|
-
let resolved =
|
|
9906
|
+
const dir = path15.dirname(current.file);
|
|
9907
|
+
let resolved = path15.join(dir, importPath).replace(/\\/g, "/");
|
|
9535
9908
|
if (!resolved.match(/\.(ts|tsx|js|jsx)$/)) {
|
|
9536
9909
|
resolved += ".ts";
|
|
9537
9910
|
}
|
|
9538
|
-
resolved =
|
|
9911
|
+
resolved = path15.normalize(resolved).replace(/\\/g, "/");
|
|
9539
9912
|
if (!visited.has(resolved) && current.depth + 1 <= maxDepth) {
|
|
9540
9913
|
queue.push({ file: resolved, depth: current.depth + 1 });
|
|
9541
9914
|
}
|
|
@@ -9552,7 +9925,7 @@ async function validateFindings(options) {
|
|
|
9552
9925
|
if (exclusionSet.isExcluded(normalizedFile, finding.lineRange) || exclusionSet.isExcluded(finding.file, finding.lineRange)) {
|
|
9553
9926
|
continue;
|
|
9554
9927
|
}
|
|
9555
|
-
const absoluteFile =
|
|
9928
|
+
const absoluteFile = path15.isAbsolute(finding.file) ? finding.file : path15.join(projectRoot, finding.file).replace(/\\/g, "/");
|
|
9556
9929
|
if (exclusionSet.isExcluded(absoluteFile, finding.lineRange)) {
|
|
9557
9930
|
continue;
|
|
9558
9931
|
}
|
|
@@ -10035,7 +10408,7 @@ async function runReviewPipeline(options) {
|
|
|
10035
10408
|
}
|
|
10036
10409
|
|
|
10037
10410
|
// src/roadmap/parse.ts
|
|
10038
|
-
var
|
|
10411
|
+
var import_types18 = require("@harness-engineering/types");
|
|
10039
10412
|
var VALID_STATUSES = /* @__PURE__ */ new Set([
|
|
10040
10413
|
"backlog",
|
|
10041
10414
|
"planned",
|
|
@@ -10047,14 +10420,14 @@ var EM_DASH = "\u2014";
|
|
|
10047
10420
|
function parseRoadmap(markdown) {
|
|
10048
10421
|
const fmMatch = markdown.match(/^---\n([\s\S]*?)\n---/);
|
|
10049
10422
|
if (!fmMatch) {
|
|
10050
|
-
return (0,
|
|
10423
|
+
return (0, import_types18.Err)(new Error("Missing or malformed YAML frontmatter"));
|
|
10051
10424
|
}
|
|
10052
10425
|
const fmResult = parseFrontmatter(fmMatch[1]);
|
|
10053
10426
|
if (!fmResult.ok) return fmResult;
|
|
10054
10427
|
const body = markdown.slice(fmMatch[0].length);
|
|
10055
10428
|
const milestonesResult = parseMilestones(body);
|
|
10056
10429
|
if (!milestonesResult.ok) return milestonesResult;
|
|
10057
|
-
return (0,
|
|
10430
|
+
return (0, import_types18.Ok)({
|
|
10058
10431
|
frontmatter: fmResult.value,
|
|
10059
10432
|
milestones: milestonesResult.value
|
|
10060
10433
|
});
|
|
@@ -10073,8 +10446,10 @@ function parseFrontmatter(raw) {
|
|
|
10073
10446
|
const versionStr = map.get("version");
|
|
10074
10447
|
const lastSynced = map.get("last_synced");
|
|
10075
10448
|
const lastManualEdit = map.get("last_manual_edit");
|
|
10449
|
+
const created = map.get("created");
|
|
10450
|
+
const updated = map.get("updated");
|
|
10076
10451
|
if (!project || !versionStr || !lastSynced || !lastManualEdit) {
|
|
10077
|
-
return (0,
|
|
10452
|
+
return (0, import_types18.Err)(
|
|
10078
10453
|
new Error(
|
|
10079
10454
|
"Frontmatter missing required fields: project, version, last_synced, last_manual_edit"
|
|
10080
10455
|
)
|
|
@@ -10082,9 +10457,12 @@ function parseFrontmatter(raw) {
|
|
|
10082
10457
|
}
|
|
10083
10458
|
const version = parseInt(versionStr, 10);
|
|
10084
10459
|
if (isNaN(version)) {
|
|
10085
|
-
return (0,
|
|
10460
|
+
return (0, import_types18.Err)(new Error("Frontmatter version must be a number"));
|
|
10086
10461
|
}
|
|
10087
|
-
|
|
10462
|
+
const fm = { project, version, lastSynced, lastManualEdit };
|
|
10463
|
+
if (created) fm.created = created;
|
|
10464
|
+
if (updated) fm.updated = updated;
|
|
10465
|
+
return (0, import_types18.Ok)(fm);
|
|
10088
10466
|
}
|
|
10089
10467
|
function parseMilestones(body) {
|
|
10090
10468
|
const milestones = [];
|
|
@@ -10092,12 +10470,12 @@ function parseMilestones(body) {
|
|
|
10092
10470
|
const h2Matches = [];
|
|
10093
10471
|
let match;
|
|
10094
10472
|
while ((match = h2Pattern.exec(body)) !== null) {
|
|
10095
|
-
h2Matches.push({ heading: match[1], startIndex: match.index });
|
|
10473
|
+
h2Matches.push({ heading: match[1], startIndex: match.index, fullMatch: match[0] });
|
|
10096
10474
|
}
|
|
10097
10475
|
for (let i = 0; i < h2Matches.length; i++) {
|
|
10098
10476
|
const h2 = h2Matches[i];
|
|
10099
10477
|
const nextStart = i + 1 < h2Matches.length ? h2Matches[i + 1].startIndex : body.length;
|
|
10100
|
-
const sectionBody = body.slice(h2.startIndex + h2.
|
|
10478
|
+
const sectionBody = body.slice(h2.startIndex + h2.fullMatch.length, nextStart);
|
|
10101
10479
|
const isBacklog = h2.heading === "Backlog";
|
|
10102
10480
|
const milestoneName = isBacklog ? "Backlog" : h2.heading.replace(/^Milestone:\s*/, "");
|
|
10103
10481
|
const featuresResult = parseFeatures(sectionBody);
|
|
@@ -10108,28 +10486,25 @@ function parseMilestones(body) {
|
|
|
10108
10486
|
features: featuresResult.value
|
|
10109
10487
|
});
|
|
10110
10488
|
}
|
|
10111
|
-
return (0,
|
|
10489
|
+
return (0, import_types18.Ok)(milestones);
|
|
10112
10490
|
}
|
|
10113
10491
|
function parseFeatures(sectionBody) {
|
|
10114
10492
|
const features = [];
|
|
10115
|
-
const h3Pattern = /^### Feature: (.+)$/gm;
|
|
10493
|
+
const h3Pattern = /^### (?:Feature: )?(.+)$/gm;
|
|
10116
10494
|
const h3Matches = [];
|
|
10117
10495
|
let match;
|
|
10118
10496
|
while ((match = h3Pattern.exec(sectionBody)) !== null) {
|
|
10119
|
-
h3Matches.push({ name: match[1], startIndex: match.index });
|
|
10497
|
+
h3Matches.push({ name: match[1], startIndex: match.index, fullMatch: match[0] });
|
|
10120
10498
|
}
|
|
10121
10499
|
for (let i = 0; i < h3Matches.length; i++) {
|
|
10122
10500
|
const h3 = h3Matches[i];
|
|
10123
10501
|
const nextStart = i + 1 < h3Matches.length ? h3Matches[i + 1].startIndex : sectionBody.length;
|
|
10124
|
-
const featureBody = sectionBody.slice(
|
|
10125
|
-
h3.startIndex + `### Feature: ${h3.name}`.length,
|
|
10126
|
-
nextStart
|
|
10127
|
-
);
|
|
10502
|
+
const featureBody = sectionBody.slice(h3.startIndex + h3.fullMatch.length, nextStart);
|
|
10128
10503
|
const featureResult = parseFeatureFields(h3.name, featureBody);
|
|
10129
10504
|
if (!featureResult.ok) return featureResult;
|
|
10130
10505
|
features.push(featureResult.value);
|
|
10131
10506
|
}
|
|
10132
|
-
return (0,
|
|
10507
|
+
return (0, import_types18.Ok)(features);
|
|
10133
10508
|
}
|
|
10134
10509
|
function parseFeatureFields(name, body) {
|
|
10135
10510
|
const fieldMap = /* @__PURE__ */ new Map();
|
|
@@ -10140,7 +10515,7 @@ function parseFeatureFields(name, body) {
|
|
|
10140
10515
|
}
|
|
10141
10516
|
const statusRaw = fieldMap.get("Status");
|
|
10142
10517
|
if (!statusRaw || !VALID_STATUSES.has(statusRaw)) {
|
|
10143
|
-
return (0,
|
|
10518
|
+
return (0, import_types18.Err)(
|
|
10144
10519
|
new Error(
|
|
10145
10520
|
`Feature "${name}" has invalid status: "${statusRaw ?? "(missing)"}". Valid statuses: ${[...VALID_STATUSES].join(", ")}`
|
|
10146
10521
|
)
|
|
@@ -10149,12 +10524,12 @@ function parseFeatureFields(name, body) {
|
|
|
10149
10524
|
const status = statusRaw;
|
|
10150
10525
|
const specRaw = fieldMap.get("Spec") ?? EM_DASH;
|
|
10151
10526
|
const spec = specRaw === EM_DASH ? null : specRaw;
|
|
10152
|
-
const plansRaw = fieldMap.get("Plans") ?? EM_DASH;
|
|
10153
|
-
const plans = plansRaw === EM_DASH ? [] : plansRaw.split(",").map((p) => p.trim());
|
|
10154
|
-
const blockedByRaw = fieldMap.get("Blocked by") ?? EM_DASH;
|
|
10155
|
-
const blockedBy = blockedByRaw === EM_DASH ? [] : blockedByRaw.split(",").map((b) => b.trim());
|
|
10527
|
+
const plansRaw = fieldMap.get("Plans") ?? fieldMap.get("Plan") ?? EM_DASH;
|
|
10528
|
+
const plans = plansRaw === EM_DASH || plansRaw === "none" ? [] : plansRaw.split(",").map((p) => p.trim());
|
|
10529
|
+
const blockedByRaw = fieldMap.get("Blocked by") ?? fieldMap.get("Blockers") ?? EM_DASH;
|
|
10530
|
+
const blockedBy = blockedByRaw === EM_DASH || blockedByRaw === "none" ? [] : blockedByRaw.split(",").map((b) => b.trim());
|
|
10156
10531
|
const summary = fieldMap.get("Summary") ?? "";
|
|
10157
|
-
return (0,
|
|
10532
|
+
return (0, import_types18.Ok)({ name, status, spec, plans, blockedBy, summary });
|
|
10158
10533
|
}
|
|
10159
10534
|
|
|
10160
10535
|
// src/roadmap/serialize.ts
|
|
@@ -10164,11 +10539,17 @@ function serializeRoadmap(roadmap) {
|
|
|
10164
10539
|
lines.push("---");
|
|
10165
10540
|
lines.push(`project: ${roadmap.frontmatter.project}`);
|
|
10166
10541
|
lines.push(`version: ${roadmap.frontmatter.version}`);
|
|
10542
|
+
if (roadmap.frontmatter.created) {
|
|
10543
|
+
lines.push(`created: ${roadmap.frontmatter.created}`);
|
|
10544
|
+
}
|
|
10545
|
+
if (roadmap.frontmatter.updated) {
|
|
10546
|
+
lines.push(`updated: ${roadmap.frontmatter.updated}`);
|
|
10547
|
+
}
|
|
10167
10548
|
lines.push(`last_synced: ${roadmap.frontmatter.lastSynced}`);
|
|
10168
10549
|
lines.push(`last_manual_edit: ${roadmap.frontmatter.lastManualEdit}`);
|
|
10169
10550
|
lines.push("---");
|
|
10170
10551
|
lines.push("");
|
|
10171
|
-
lines.push("#
|
|
10552
|
+
lines.push("# Roadmap");
|
|
10172
10553
|
for (const milestone of roadmap.milestones) {
|
|
10173
10554
|
lines.push("");
|
|
10174
10555
|
lines.push(serializeMilestoneHeading(milestone));
|
|
@@ -10181,26 +10562,27 @@ function serializeRoadmap(roadmap) {
|
|
|
10181
10562
|
return lines.join("\n");
|
|
10182
10563
|
}
|
|
10183
10564
|
function serializeMilestoneHeading(milestone) {
|
|
10184
|
-
return milestone.isBacklog ? "## Backlog" : `##
|
|
10565
|
+
return milestone.isBacklog ? "## Backlog" : `## ${milestone.name}`;
|
|
10185
10566
|
}
|
|
10186
10567
|
function serializeFeature(feature) {
|
|
10187
10568
|
const spec = feature.spec ?? EM_DASH2;
|
|
10188
10569
|
const plans = feature.plans.length > 0 ? feature.plans.join(", ") : EM_DASH2;
|
|
10189
10570
|
const blockedBy = feature.blockedBy.length > 0 ? feature.blockedBy.join(", ") : EM_DASH2;
|
|
10190
10571
|
return [
|
|
10191
|
-
`###
|
|
10572
|
+
`### ${feature.name}`,
|
|
10573
|
+
"",
|
|
10192
10574
|
`- **Status:** ${feature.status}`,
|
|
10193
10575
|
`- **Spec:** ${spec}`,
|
|
10194
|
-
`- **
|
|
10195
|
-
`- **
|
|
10196
|
-
`- **
|
|
10576
|
+
`- **Summary:** ${feature.summary}`,
|
|
10577
|
+
`- **Blockers:** ${blockedBy}`,
|
|
10578
|
+
`- **Plan:** ${plans}`
|
|
10197
10579
|
];
|
|
10198
10580
|
}
|
|
10199
10581
|
|
|
10200
10582
|
// src/roadmap/sync.ts
|
|
10201
|
-
var
|
|
10202
|
-
var
|
|
10203
|
-
var
|
|
10583
|
+
var fs16 = __toESM(require("fs"));
|
|
10584
|
+
var path16 = __toESM(require("path"));
|
|
10585
|
+
var import_types19 = require("@harness-engineering/types");
|
|
10204
10586
|
function inferStatus(feature, projectPath, allFeatures) {
|
|
10205
10587
|
if (feature.blockedBy.length > 0) {
|
|
10206
10588
|
const blockerNotDone = feature.blockedBy.some((blockerName) => {
|
|
@@ -10214,10 +10596,10 @@ function inferStatus(feature, projectPath, allFeatures) {
|
|
|
10214
10596
|
const featuresWithPlans = allFeatures.filter((f) => f.plans.length > 0);
|
|
10215
10597
|
const useRootState = featuresWithPlans.length <= 1;
|
|
10216
10598
|
if (useRootState) {
|
|
10217
|
-
const rootStatePath =
|
|
10218
|
-
if (
|
|
10599
|
+
const rootStatePath = path16.join(projectPath, ".harness", "state.json");
|
|
10600
|
+
if (fs16.existsSync(rootStatePath)) {
|
|
10219
10601
|
try {
|
|
10220
|
-
const raw =
|
|
10602
|
+
const raw = fs16.readFileSync(rootStatePath, "utf-8");
|
|
10221
10603
|
const state = JSON.parse(raw);
|
|
10222
10604
|
if (state.progress) {
|
|
10223
10605
|
for (const status of Object.values(state.progress)) {
|
|
@@ -10228,16 +10610,16 @@ function inferStatus(feature, projectPath, allFeatures) {
|
|
|
10228
10610
|
}
|
|
10229
10611
|
}
|
|
10230
10612
|
}
|
|
10231
|
-
const sessionsDir =
|
|
10232
|
-
if (
|
|
10613
|
+
const sessionsDir = path16.join(projectPath, ".harness", "sessions");
|
|
10614
|
+
if (fs16.existsSync(sessionsDir)) {
|
|
10233
10615
|
try {
|
|
10234
|
-
const sessionDirs =
|
|
10616
|
+
const sessionDirs = fs16.readdirSync(sessionsDir, { withFileTypes: true });
|
|
10235
10617
|
for (const entry of sessionDirs) {
|
|
10236
10618
|
if (!entry.isDirectory()) continue;
|
|
10237
|
-
const autopilotPath =
|
|
10238
|
-
if (!
|
|
10619
|
+
const autopilotPath = path16.join(sessionsDir, entry.name, "autopilot-state.json");
|
|
10620
|
+
if (!fs16.existsSync(autopilotPath)) continue;
|
|
10239
10621
|
try {
|
|
10240
|
-
const raw =
|
|
10622
|
+
const raw = fs16.readFileSync(autopilotPath, "utf-8");
|
|
10241
10623
|
const autopilot = JSON.parse(raw);
|
|
10242
10624
|
if (!autopilot.phases) continue;
|
|
10243
10625
|
const linkedPhases = autopilot.phases.filter(
|
|
@@ -10284,7 +10666,7 @@ function syncRoadmap(options) {
|
|
|
10284
10666
|
to: inferred
|
|
10285
10667
|
});
|
|
10286
10668
|
}
|
|
10287
|
-
return (0,
|
|
10669
|
+
return (0, import_types19.Ok)(changes);
|
|
10288
10670
|
}
|
|
10289
10671
|
|
|
10290
10672
|
// src/interaction/types.ts
|
|
@@ -10317,17 +10699,17 @@ var EmitInteractionInputSchema = import_zod7.z.object({
|
|
|
10317
10699
|
});
|
|
10318
10700
|
|
|
10319
10701
|
// src/blueprint/scanner.ts
|
|
10320
|
-
var
|
|
10321
|
-
var
|
|
10702
|
+
var fs17 = __toESM(require("fs/promises"));
|
|
10703
|
+
var path17 = __toESM(require("path"));
|
|
10322
10704
|
var ProjectScanner = class {
|
|
10323
10705
|
constructor(rootDir) {
|
|
10324
10706
|
this.rootDir = rootDir;
|
|
10325
10707
|
}
|
|
10326
10708
|
async scan() {
|
|
10327
|
-
let projectName =
|
|
10709
|
+
let projectName = path17.basename(this.rootDir);
|
|
10328
10710
|
try {
|
|
10329
|
-
const pkgPath =
|
|
10330
|
-
const pkgRaw = await
|
|
10711
|
+
const pkgPath = path17.join(this.rootDir, "package.json");
|
|
10712
|
+
const pkgRaw = await fs17.readFile(pkgPath, "utf-8");
|
|
10331
10713
|
const pkg = JSON.parse(pkgRaw);
|
|
10332
10714
|
if (pkg.name) projectName = pkg.name;
|
|
10333
10715
|
} catch {
|
|
@@ -10368,8 +10750,8 @@ var ProjectScanner = class {
|
|
|
10368
10750
|
};
|
|
10369
10751
|
|
|
10370
10752
|
// src/blueprint/generator.ts
|
|
10371
|
-
var
|
|
10372
|
-
var
|
|
10753
|
+
var fs18 = __toESM(require("fs/promises"));
|
|
10754
|
+
var path18 = __toESM(require("path"));
|
|
10373
10755
|
var ejs = __toESM(require("ejs"));
|
|
10374
10756
|
|
|
10375
10757
|
// src/blueprint/templates.ts
|
|
@@ -10453,19 +10835,19 @@ var BlueprintGenerator = class {
|
|
|
10453
10835
|
styles: STYLES,
|
|
10454
10836
|
scripts: SCRIPTS
|
|
10455
10837
|
});
|
|
10456
|
-
await
|
|
10457
|
-
await
|
|
10838
|
+
await fs18.mkdir(options.outputDir, { recursive: true });
|
|
10839
|
+
await fs18.writeFile(path18.join(options.outputDir, "index.html"), html);
|
|
10458
10840
|
}
|
|
10459
10841
|
};
|
|
10460
10842
|
|
|
10461
10843
|
// src/update-checker.ts
|
|
10462
|
-
var
|
|
10463
|
-
var
|
|
10844
|
+
var fs19 = __toESM(require("fs"));
|
|
10845
|
+
var path19 = __toESM(require("path"));
|
|
10464
10846
|
var os = __toESM(require("os"));
|
|
10465
10847
|
var import_child_process3 = require("child_process");
|
|
10466
10848
|
function getStatePath() {
|
|
10467
10849
|
const home = process.env["HOME"] || os.homedir();
|
|
10468
|
-
return
|
|
10850
|
+
return path19.join(home, ".harness", "update-check.json");
|
|
10469
10851
|
}
|
|
10470
10852
|
function isUpdateCheckEnabled(configInterval) {
|
|
10471
10853
|
if (process.env["HARNESS_NO_UPDATE_CHECK"] === "1") return false;
|
|
@@ -10478,7 +10860,7 @@ function shouldRunCheck(state, intervalMs) {
|
|
|
10478
10860
|
}
|
|
10479
10861
|
function readCheckState() {
|
|
10480
10862
|
try {
|
|
10481
|
-
const raw =
|
|
10863
|
+
const raw = fs19.readFileSync(getStatePath(), "utf-8");
|
|
10482
10864
|
const parsed = JSON.parse(raw);
|
|
10483
10865
|
if (typeof parsed === "object" && parsed !== null && "lastCheckTime" in parsed && typeof parsed.lastCheckTime === "number" && "currentVersion" in parsed && typeof parsed.currentVersion === "string") {
|
|
10484
10866
|
const state = parsed;
|
|
@@ -10495,7 +10877,7 @@ function readCheckState() {
|
|
|
10495
10877
|
}
|
|
10496
10878
|
function spawnBackgroundCheck(currentVersion) {
|
|
10497
10879
|
const statePath = getStatePath();
|
|
10498
|
-
const stateDir =
|
|
10880
|
+
const stateDir = path19.dirname(statePath);
|
|
10499
10881
|
const script = `
|
|
10500
10882
|
const { execSync } = require('child_process');
|
|
10501
10883
|
const fs = require('fs');
|
|
@@ -10549,7 +10931,7 @@ Run "harness update" to upgrade.`;
|
|
|
10549
10931
|
}
|
|
10550
10932
|
|
|
10551
10933
|
// src/index.ts
|
|
10552
|
-
var VERSION = "0.
|
|
10934
|
+
var VERSION = "0.13.0";
|
|
10553
10935
|
// Annotate the CommonJS export names for ESM import in node:
|
|
10554
10936
|
0 && (module.exports = {
|
|
10555
10937
|
AGENT_DESCRIPTORS,
|
|
@@ -10627,6 +11009,7 @@ var VERSION = "0.11.0";
|
|
|
10627
11009
|
ViolationSchema,
|
|
10628
11010
|
addProvenance,
|
|
10629
11011
|
analyzeDiff,
|
|
11012
|
+
analyzeLearningPatterns,
|
|
10630
11013
|
appendFailure,
|
|
10631
11014
|
appendLearning,
|
|
10632
11015
|
applyFixes,
|
|
@@ -10635,6 +11018,7 @@ var VERSION = "0.11.0";
|
|
|
10635
11018
|
archModule,
|
|
10636
11019
|
architecture,
|
|
10637
11020
|
archiveFailures,
|
|
11021
|
+
archiveLearnings,
|
|
10638
11022
|
archiveStream,
|
|
10639
11023
|
buildDependencyGraph,
|
|
10640
11024
|
buildExclusionSet,
|
|
@@ -10642,6 +11026,8 @@ var VERSION = "0.11.0";
|
|
|
10642
11026
|
checkDocCoverage,
|
|
10643
11027
|
checkEligibility,
|
|
10644
11028
|
classifyFinding,
|
|
11029
|
+
clearFailuresCache,
|
|
11030
|
+
clearLearningsCache,
|
|
10645
11031
|
configureFeedback,
|
|
10646
11032
|
constraintRuleId,
|
|
10647
11033
|
contextBudget,
|
|
@@ -10697,16 +11083,20 @@ var VERSION = "0.11.0";
|
|
|
10697
11083
|
injectionRules,
|
|
10698
11084
|
isSmallSuggestion,
|
|
10699
11085
|
isUpdateCheckEnabled,
|
|
11086
|
+
listActiveSessions,
|
|
10700
11087
|
listStreams,
|
|
11088
|
+
loadBudgetedLearnings,
|
|
10701
11089
|
loadFailures,
|
|
10702
11090
|
loadHandoff,
|
|
10703
11091
|
loadRelevantLearnings,
|
|
11092
|
+
loadSessionSummary,
|
|
10704
11093
|
loadState,
|
|
10705
11094
|
loadStreamIndex,
|
|
10706
11095
|
logAgentAction,
|
|
10707
11096
|
migrateToStreams,
|
|
10708
11097
|
networkRules,
|
|
10709
11098
|
nodeRules,
|
|
11099
|
+
parseDateFromEntry,
|
|
10710
11100
|
parseDiff,
|
|
10711
11101
|
parseManifest,
|
|
10712
11102
|
parseRoadmap,
|
|
@@ -10714,6 +11104,7 @@ var VERSION = "0.11.0";
|
|
|
10714
11104
|
parseSize,
|
|
10715
11105
|
pathTraversalRules,
|
|
10716
11106
|
previewFix,
|
|
11107
|
+
pruneLearnings,
|
|
10717
11108
|
reactRules,
|
|
10718
11109
|
readCheckState,
|
|
10719
11110
|
readLockfile,
|
|
@@ -10725,6 +11116,7 @@ var VERSION = "0.11.0";
|
|
|
10725
11116
|
resolveFileToLayer,
|
|
10726
11117
|
resolveModelTier,
|
|
10727
11118
|
resolveRuleSeverity,
|
|
11119
|
+
resolveSessionDir,
|
|
10728
11120
|
resolveStreamPath,
|
|
10729
11121
|
resolveThresholds,
|
|
10730
11122
|
runAll,
|
|
@@ -10751,6 +11143,7 @@ var VERSION = "0.11.0";
|
|
|
10751
11143
|
syncRoadmap,
|
|
10752
11144
|
touchStream,
|
|
10753
11145
|
trackAction,
|
|
11146
|
+
updateSessionIndex,
|
|
10754
11147
|
validateAgentsMap,
|
|
10755
11148
|
validateBoundaries,
|
|
10756
11149
|
validateCommitMessage,
|
|
@@ -10763,6 +11156,7 @@ var VERSION = "0.11.0";
|
|
|
10763
11156
|
violationId,
|
|
10764
11157
|
writeConfig,
|
|
10765
11158
|
writeLockfile,
|
|
11159
|
+
writeSessionSummary,
|
|
10766
11160
|
xssRules,
|
|
10767
11161
|
...require("@harness-engineering/types")
|
|
10768
11162
|
});
|