@harness-engineering/core 0.11.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/architecture/matchers.d.mts +1 -1
- package/dist/architecture/matchers.d.ts +1 -1
- package/dist/index.d.mts +496 -377
- package/dist/index.d.ts +496 -377
- package/dist/index.js +713 -301
- package/dist/index.mjs +683 -284
- package/dist/{matchers-D20x48U9.d.mts → matchers-Dj1t5vpg.d.mts} +46 -46
- package/dist/{matchers-D20x48U9.d.ts → matchers-Dj1t5vpg.d.ts} +46 -46
- package/package.json +3 -3
package/dist/index.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,9 +201,11 @@ __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,
|
|
208
|
+
removeContributions: () => removeContributions,
|
|
199
209
|
removeProvenance: () => removeProvenance,
|
|
200
210
|
requestMultiplePeerReviews: () => requestMultiplePeerReviews,
|
|
201
211
|
requestPeerReview: () => requestPeerReview,
|
|
@@ -203,6 +213,7 @@ __export(index_exports, {
|
|
|
203
213
|
resolveFileToLayer: () => resolveFileToLayer,
|
|
204
214
|
resolveModelTier: () => resolveModelTier,
|
|
205
215
|
resolveRuleSeverity: () => resolveRuleSeverity,
|
|
216
|
+
resolveSessionDir: () => resolveSessionDir,
|
|
206
217
|
resolveStreamPath: () => resolveStreamPath,
|
|
207
218
|
resolveThresholds: () => resolveThresholds,
|
|
208
219
|
runAll: () => runAll,
|
|
@@ -229,6 +240,7 @@ __export(index_exports, {
|
|
|
229
240
|
syncRoadmap: () => syncRoadmap,
|
|
230
241
|
touchStream: () => touchStream,
|
|
231
242
|
trackAction: () => trackAction,
|
|
243
|
+
updateSessionIndex: () => updateSessionIndex,
|
|
232
244
|
validateAgentsMap: () => validateAgentsMap,
|
|
233
245
|
validateBoundaries: () => validateBoundaries,
|
|
234
246
|
validateCommitMessage: () => validateCommitMessage,
|
|
@@ -241,6 +253,7 @@ __export(index_exports, {
|
|
|
241
253
|
violationId: () => violationId,
|
|
242
254
|
writeConfig: () => writeConfig,
|
|
243
255
|
writeLockfile: () => writeLockfile,
|
|
256
|
+
writeSessionSummary: () => writeSessionSummary,
|
|
244
257
|
xssRules: () => xssRules
|
|
245
258
|
});
|
|
246
259
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -263,17 +276,17 @@ var import_util = require("util");
|
|
|
263
276
|
var import_glob = require("glob");
|
|
264
277
|
var accessAsync = (0, import_util.promisify)(import_fs.access);
|
|
265
278
|
var readFileAsync = (0, import_util.promisify)(import_fs.readFile);
|
|
266
|
-
async function fileExists(
|
|
279
|
+
async function fileExists(path20) {
|
|
267
280
|
try {
|
|
268
|
-
await accessAsync(
|
|
281
|
+
await accessAsync(path20, import_fs.constants.F_OK);
|
|
269
282
|
return true;
|
|
270
283
|
} catch {
|
|
271
284
|
return false;
|
|
272
285
|
}
|
|
273
286
|
}
|
|
274
|
-
async function readFileContent(
|
|
287
|
+
async function readFileContent(path20) {
|
|
275
288
|
try {
|
|
276
|
-
const content = await readFileAsync(
|
|
289
|
+
const content = await readFileAsync(path20, "utf-8");
|
|
277
290
|
return (0, import_types.Ok)(content);
|
|
278
291
|
} catch (error) {
|
|
279
292
|
return (0, import_types.Err)(error);
|
|
@@ -321,15 +334,15 @@ function validateConfig(data, schema) {
|
|
|
321
334
|
let message = "Configuration validation failed";
|
|
322
335
|
const suggestions = [];
|
|
323
336
|
if (firstError) {
|
|
324
|
-
const
|
|
325
|
-
const pathDisplay =
|
|
337
|
+
const path20 = firstError.path.join(".");
|
|
338
|
+
const pathDisplay = path20 ? ` at "${path20}"` : "";
|
|
326
339
|
if (firstError.code === "invalid_type") {
|
|
327
340
|
const received = firstError.received;
|
|
328
341
|
const expected = firstError.expected;
|
|
329
342
|
if (received === "undefined") {
|
|
330
343
|
code = "MISSING_FIELD";
|
|
331
344
|
message = `Missing required field${pathDisplay}: ${firstError.message}`;
|
|
332
|
-
suggestions.push(`Field "${
|
|
345
|
+
suggestions.push(`Field "${path20}" is required and must be of type "${expected}"`);
|
|
333
346
|
} else {
|
|
334
347
|
code = "INVALID_TYPE";
|
|
335
348
|
message = `Invalid type${pathDisplay}: ${firstError.message}`;
|
|
@@ -542,30 +555,27 @@ function extractSections(content) {
|
|
|
542
555
|
return result;
|
|
543
556
|
});
|
|
544
557
|
}
|
|
545
|
-
function isExternalLink(
|
|
546
|
-
return
|
|
558
|
+
function isExternalLink(path20) {
|
|
559
|
+
return path20.startsWith("http://") || path20.startsWith("https://") || path20.startsWith("#") || path20.startsWith("mailto:");
|
|
547
560
|
}
|
|
548
561
|
function resolveLinkPath(linkPath, baseDir) {
|
|
549
562
|
return linkPath.startsWith(".") ? (0, import_path.join)(baseDir, linkPath) : linkPath;
|
|
550
563
|
}
|
|
551
|
-
async function validateAgentsMap(
|
|
552
|
-
|
|
553
|
-
"[harness] validateAgentsMap() is deprecated. Use graph-based validation via Assembler.checkCoverage() from @harness-engineering/graph"
|
|
554
|
-
);
|
|
555
|
-
const contentResult = await readFileContent(path13);
|
|
564
|
+
async function validateAgentsMap(path20 = "./AGENTS.md") {
|
|
565
|
+
const contentResult = await readFileContent(path20);
|
|
556
566
|
if (!contentResult.ok) {
|
|
557
567
|
return (0, import_types.Err)(
|
|
558
568
|
createError(
|
|
559
569
|
"PARSE_ERROR",
|
|
560
570
|
`Failed to read AGENTS.md: ${contentResult.error.message}`,
|
|
561
|
-
{ path:
|
|
571
|
+
{ path: path20 },
|
|
562
572
|
["Ensure the file exists", "Check file permissions"]
|
|
563
573
|
)
|
|
564
574
|
);
|
|
565
575
|
}
|
|
566
576
|
const content = contentResult.value;
|
|
567
577
|
const sections = extractSections(content);
|
|
568
|
-
const baseDir = (0, import_path.dirname)(
|
|
578
|
+
const baseDir = (0, import_path.dirname)(path20);
|
|
569
579
|
const sectionTitles = sections.map((s) => s.title);
|
|
570
580
|
const missingSections = REQUIRED_SECTIONS.filter(
|
|
571
581
|
(required) => !sectionTitles.some((title) => title.toLowerCase().includes(required.toLowerCase()))
|
|
@@ -706,8 +716,8 @@ async function checkDocCoverage(domain, options = {}) {
|
|
|
706
716
|
|
|
707
717
|
// src/context/knowledge-map.ts
|
|
708
718
|
var import_path3 = require("path");
|
|
709
|
-
function suggestFix(
|
|
710
|
-
const targetName = (0, import_path3.basename)(
|
|
719
|
+
function suggestFix(path20, existingFiles) {
|
|
720
|
+
const targetName = (0, import_path3.basename)(path20).toLowerCase();
|
|
711
721
|
const similar = existingFiles.find((file) => {
|
|
712
722
|
const fileName = (0, import_path3.basename)(file).toLowerCase();
|
|
713
723
|
return fileName.includes(targetName) || targetName.includes(fileName);
|
|
@@ -715,12 +725,9 @@ function suggestFix(path13, existingFiles) {
|
|
|
715
725
|
if (similar) {
|
|
716
726
|
return `Did you mean "${similar}"?`;
|
|
717
727
|
}
|
|
718
|
-
return `Create the file "${
|
|
728
|
+
return `Create the file "${path20}" or remove the link`;
|
|
719
729
|
}
|
|
720
730
|
async function validateKnowledgeMap(rootDir = process.cwd()) {
|
|
721
|
-
console.warn(
|
|
722
|
-
"[harness] validateKnowledgeMap() is deprecated. Use graph-based validation via Assembler.checkCoverage() from @harness-engineering/graph"
|
|
723
|
-
);
|
|
724
731
|
const agentsPath = (0, import_path3.join)(rootDir, "AGENTS.md");
|
|
725
732
|
const agentsResult = await validateAgentsMap(agentsPath);
|
|
726
733
|
if (!agentsResult.ok) {
|
|
@@ -1318,8 +1325,8 @@ function createBoundaryValidator(schema, name) {
|
|
|
1318
1325
|
return (0, import_types.Ok)(result.data);
|
|
1319
1326
|
}
|
|
1320
1327
|
const suggestions = result.error.issues.map((issue) => {
|
|
1321
|
-
const
|
|
1322
|
-
return
|
|
1328
|
+
const path20 = issue.path.join(".");
|
|
1329
|
+
return path20 ? `${path20}: ${issue.message}` : issue.message;
|
|
1323
1330
|
});
|
|
1324
1331
|
return (0, import_types.Err)(
|
|
1325
1332
|
createError(
|
|
@@ -1626,8 +1633,6 @@ function deepMergeConstraints(localConfig, bundleConstraints, _existingContribut
|
|
|
1626
1633
|
}
|
|
1627
1634
|
if (bundleConstraints.architecture) {
|
|
1628
1635
|
const localArch = localConfig.architecture ?? {
|
|
1629
|
-
enabled: true,
|
|
1630
|
-
baselinePath: ".harness/arch/baselines.json",
|
|
1631
1636
|
thresholds: {},
|
|
1632
1637
|
modules: {}
|
|
1633
1638
|
};
|
|
@@ -1750,7 +1755,7 @@ async function readLockfile(lockfilePath) {
|
|
|
1750
1755
|
return { ok: true, value: result.data };
|
|
1751
1756
|
}
|
|
1752
1757
|
async function writeLockfile(lockfilePath, lockfile) {
|
|
1753
|
-
|
|
1758
|
+
return writeConfig(lockfilePath, lockfile);
|
|
1754
1759
|
}
|
|
1755
1760
|
function addProvenance(lockfile, packageName, entry) {
|
|
1756
1761
|
return {
|
|
@@ -1779,6 +1784,77 @@ function isNodeError(err) {
|
|
|
1779
1784
|
return err instanceof Error && "code" in err;
|
|
1780
1785
|
}
|
|
1781
1786
|
|
|
1787
|
+
// src/constraints/sharing/remove.ts
|
|
1788
|
+
function removeContributions(config, contributions) {
|
|
1789
|
+
const result = { ...config };
|
|
1790
|
+
const layerNames = contributions.layers;
|
|
1791
|
+
if (layerNames && layerNames.length > 0 && Array.isArray(result.layers)) {
|
|
1792
|
+
const nameSet = new Set(layerNames);
|
|
1793
|
+
result.layers = result.layers.filter((l) => !nameSet.has(l.name));
|
|
1794
|
+
}
|
|
1795
|
+
const fromKeys = contributions.forbiddenImports;
|
|
1796
|
+
if (fromKeys && fromKeys.length > 0 && Array.isArray(result.forbiddenImports)) {
|
|
1797
|
+
const fromSet = new Set(fromKeys);
|
|
1798
|
+
result.forbiddenImports = result.forbiddenImports.filter(
|
|
1799
|
+
(r) => !fromSet.has(r.from)
|
|
1800
|
+
);
|
|
1801
|
+
}
|
|
1802
|
+
const boundarySchemas = contributions.boundaries;
|
|
1803
|
+
if (boundarySchemas && boundarySchemas.length > 0 && result.boundaries) {
|
|
1804
|
+
const boundaries = result.boundaries;
|
|
1805
|
+
if (boundaries.requireSchema) {
|
|
1806
|
+
const schemaSet = new Set(boundarySchemas);
|
|
1807
|
+
result.boundaries = {
|
|
1808
|
+
...boundaries,
|
|
1809
|
+
requireSchema: boundaries.requireSchema.filter((s) => !schemaSet.has(s))
|
|
1810
|
+
};
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
const thresholdKeys = contributions["architecture.thresholds"];
|
|
1814
|
+
if (thresholdKeys && thresholdKeys.length > 0 && result.architecture) {
|
|
1815
|
+
const arch = { ...result.architecture };
|
|
1816
|
+
const thresholds = { ...arch.thresholds };
|
|
1817
|
+
for (const key of thresholdKeys) {
|
|
1818
|
+
delete thresholds[key];
|
|
1819
|
+
}
|
|
1820
|
+
arch.thresholds = thresholds;
|
|
1821
|
+
result.architecture = arch;
|
|
1822
|
+
}
|
|
1823
|
+
const moduleKeys = contributions["architecture.modules"];
|
|
1824
|
+
if (moduleKeys && moduleKeys.length > 0 && result.architecture) {
|
|
1825
|
+
const arch = { ...result.architecture };
|
|
1826
|
+
const modules = { ...arch.modules };
|
|
1827
|
+
for (const key of moduleKeys) {
|
|
1828
|
+
const colonIdx = key.indexOf(":");
|
|
1829
|
+
if (colonIdx === -1) continue;
|
|
1830
|
+
const modulePath = key.substring(0, colonIdx);
|
|
1831
|
+
const category = key.substring(colonIdx + 1);
|
|
1832
|
+
if (modules[modulePath]) {
|
|
1833
|
+
const moduleCategories = { ...modules[modulePath] };
|
|
1834
|
+
delete moduleCategories[category];
|
|
1835
|
+
if (Object.keys(moduleCategories).length === 0) {
|
|
1836
|
+
delete modules[modulePath];
|
|
1837
|
+
} else {
|
|
1838
|
+
modules[modulePath] = moduleCategories;
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1842
|
+
arch.modules = modules;
|
|
1843
|
+
result.architecture = arch;
|
|
1844
|
+
}
|
|
1845
|
+
const ruleIds = contributions["security.rules"];
|
|
1846
|
+
if (ruleIds && ruleIds.length > 0 && result.security) {
|
|
1847
|
+
const security = { ...result.security };
|
|
1848
|
+
const rules = { ...security.rules };
|
|
1849
|
+
for (const id of ruleIds) {
|
|
1850
|
+
delete rules[id];
|
|
1851
|
+
}
|
|
1852
|
+
security.rules = rules;
|
|
1853
|
+
result.security = security;
|
|
1854
|
+
}
|
|
1855
|
+
return result;
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1782
1858
|
// src/shared/parsers/typescript.ts
|
|
1783
1859
|
var import_typescript_estree = require("@typescript-eslint/typescript-estree");
|
|
1784
1860
|
|
|
@@ -1804,11 +1880,11 @@ function walk(node, visitor) {
|
|
|
1804
1880
|
var TypeScriptParser = class {
|
|
1805
1881
|
name = "typescript";
|
|
1806
1882
|
extensions = [".ts", ".tsx", ".mts", ".cts"];
|
|
1807
|
-
async parseFile(
|
|
1808
|
-
const contentResult = await readFileContent(
|
|
1883
|
+
async parseFile(path20) {
|
|
1884
|
+
const contentResult = await readFileContent(path20);
|
|
1809
1885
|
if (!contentResult.ok) {
|
|
1810
1886
|
return (0, import_types.Err)(
|
|
1811
|
-
createParseError("NOT_FOUND", `File not found: ${
|
|
1887
|
+
createParseError("NOT_FOUND", `File not found: ${path20}`, { path: path20 }, [
|
|
1812
1888
|
"Check that the file exists",
|
|
1813
1889
|
"Verify the path is correct"
|
|
1814
1890
|
])
|
|
@@ -1818,7 +1894,7 @@ var TypeScriptParser = class {
|
|
|
1818
1894
|
const ast = (0, import_typescript_estree.parse)(contentResult.value, {
|
|
1819
1895
|
loc: true,
|
|
1820
1896
|
range: true,
|
|
1821
|
-
jsx:
|
|
1897
|
+
jsx: path20.endsWith(".tsx"),
|
|
1822
1898
|
errorOnUnknownASTType: false
|
|
1823
1899
|
});
|
|
1824
1900
|
return (0, import_types.Ok)({
|
|
@@ -1829,7 +1905,7 @@ var TypeScriptParser = class {
|
|
|
1829
1905
|
} catch (e) {
|
|
1830
1906
|
const error = e;
|
|
1831
1907
|
return (0, import_types.Err)(
|
|
1832
|
-
createParseError("SYNTAX_ERROR", `Failed to parse ${
|
|
1908
|
+
createParseError("SYNTAX_ERROR", `Failed to parse ${path20}: ${error.message}`, { path: path20 }, [
|
|
1833
1909
|
"Check for syntax errors in the file",
|
|
1834
1910
|
"Ensure valid TypeScript syntax"
|
|
1835
1911
|
])
|
|
@@ -2113,22 +2189,22 @@ function extractInlineRefs(content) {
|
|
|
2113
2189
|
}
|
|
2114
2190
|
return refs;
|
|
2115
2191
|
}
|
|
2116
|
-
async function parseDocumentationFile(
|
|
2117
|
-
const contentResult = await readFileContent(
|
|
2192
|
+
async function parseDocumentationFile(path20) {
|
|
2193
|
+
const contentResult = await readFileContent(path20);
|
|
2118
2194
|
if (!contentResult.ok) {
|
|
2119
2195
|
return (0, import_types.Err)(
|
|
2120
2196
|
createEntropyError(
|
|
2121
2197
|
"PARSE_ERROR",
|
|
2122
|
-
`Failed to read documentation file: ${
|
|
2123
|
-
{ file:
|
|
2198
|
+
`Failed to read documentation file: ${path20}`,
|
|
2199
|
+
{ file: path20 },
|
|
2124
2200
|
["Check that the file exists"]
|
|
2125
2201
|
)
|
|
2126
2202
|
);
|
|
2127
2203
|
}
|
|
2128
2204
|
const content = contentResult.value;
|
|
2129
|
-
const type =
|
|
2205
|
+
const type = path20.endsWith(".md") ? "markdown" : "text";
|
|
2130
2206
|
return (0, import_types.Ok)({
|
|
2131
|
-
path:
|
|
2207
|
+
path: path20,
|
|
2132
2208
|
type,
|
|
2133
2209
|
content,
|
|
2134
2210
|
codeBlocks: extractCodeBlocks(content),
|
|
@@ -2939,15 +3015,34 @@ async function detectPatternViolations(snapshot, config) {
|
|
|
2939
3015
|
}
|
|
2940
3016
|
}
|
|
2941
3017
|
}
|
|
3018
|
+
if (config?.customPatterns) {
|
|
3019
|
+
for (const file of snapshot.files) {
|
|
3020
|
+
for (const custom of config.customPatterns) {
|
|
3021
|
+
const matches = custom.check(file, snapshot);
|
|
3022
|
+
for (const match of matches) {
|
|
3023
|
+
violations.push({
|
|
3024
|
+
pattern: custom.name,
|
|
3025
|
+
file: file.path,
|
|
3026
|
+
line: match.line,
|
|
3027
|
+
message: match.message,
|
|
3028
|
+
suggestion: match.suggestion || "Review and fix this pattern violation",
|
|
3029
|
+
severity: custom.severity
|
|
3030
|
+
});
|
|
3031
|
+
}
|
|
3032
|
+
}
|
|
3033
|
+
}
|
|
3034
|
+
}
|
|
2942
3035
|
const errorCount = violations.filter((v) => v.severity === "error").length;
|
|
2943
3036
|
const warningCount = violations.filter((v) => v.severity === "warning").length;
|
|
2944
|
-
const
|
|
2945
|
-
const
|
|
3037
|
+
const customCount = config?.customPatterns?.length ?? 0;
|
|
3038
|
+
const allPatternsCount = patterns.length + customCount;
|
|
3039
|
+
const totalChecks = snapshot.files.length * allPatternsCount;
|
|
3040
|
+
const passRate = totalChecks > 0 ? Math.max(0, (totalChecks - violations.length) / totalChecks) : 1;
|
|
2946
3041
|
return (0, import_types.Ok)({
|
|
2947
3042
|
violations,
|
|
2948
3043
|
stats: {
|
|
2949
3044
|
filesChecked: snapshot.files.length,
|
|
2950
|
-
patternsApplied:
|
|
3045
|
+
patternsApplied: allPatternsCount,
|
|
2951
3046
|
violationCount: violations.length,
|
|
2952
3047
|
errorCount,
|
|
2953
3048
|
warningCount
|
|
@@ -5446,8 +5541,8 @@ var import_node_path3 = require("path");
|
|
|
5446
5541
|
// src/architecture/collectors/hash.ts
|
|
5447
5542
|
var import_node_crypto = require("crypto");
|
|
5448
5543
|
function violationId(relativePath, category, normalizedDetail) {
|
|
5449
|
-
const
|
|
5450
|
-
const input = `${
|
|
5544
|
+
const path20 = relativePath.replace(/\\/g, "/");
|
|
5545
|
+
const input = `${path20}:${category}:${normalizedDetail}`;
|
|
5451
5546
|
return (0, import_node_crypto.createHash)("sha256").update(input).digest("hex");
|
|
5452
5547
|
}
|
|
5453
5548
|
function constraintRuleId(category, scope, description) {
|
|
@@ -6625,10 +6720,13 @@ var DEFAULT_STATE = {
|
|
|
6625
6720
|
progress: {}
|
|
6626
6721
|
};
|
|
6627
6722
|
|
|
6628
|
-
// src/state/state-
|
|
6629
|
-
var
|
|
6630
|
-
var
|
|
6631
|
-
|
|
6723
|
+
// src/state/state-persistence.ts
|
|
6724
|
+
var fs8 = __toESM(require("fs"));
|
|
6725
|
+
var path5 = __toESM(require("path"));
|
|
6726
|
+
|
|
6727
|
+
// src/state/state-shared.ts
|
|
6728
|
+
var fs7 = __toESM(require("fs"));
|
|
6729
|
+
var path4 = __toESM(require("path"));
|
|
6632
6730
|
|
|
6633
6731
|
// src/state/stream-resolver.ts
|
|
6634
6732
|
var fs5 = __toESM(require("fs"));
|
|
@@ -6654,10 +6752,20 @@ var DEFAULT_STREAM_INDEX = {
|
|
|
6654
6752
|
streams: {}
|
|
6655
6753
|
};
|
|
6656
6754
|
|
|
6657
|
-
// src/state/
|
|
6755
|
+
// src/state/constants.ts
|
|
6658
6756
|
var HARNESS_DIR = ".harness";
|
|
6659
|
-
var
|
|
6757
|
+
var STATE_FILE = "state.json";
|
|
6758
|
+
var LEARNINGS_FILE = "learnings.md";
|
|
6759
|
+
var FAILURES_FILE = "failures.md";
|
|
6760
|
+
var HANDOFF_FILE = "handoff.json";
|
|
6761
|
+
var GATE_CONFIG_FILE = "gate.json";
|
|
6660
6762
|
var INDEX_FILE = "index.json";
|
|
6763
|
+
var SESSIONS_DIR = "sessions";
|
|
6764
|
+
var SESSION_INDEX_FILE = "index.md";
|
|
6765
|
+
var SUMMARY_FILE = "summary.md";
|
|
6766
|
+
|
|
6767
|
+
// src/state/stream-resolver.ts
|
|
6768
|
+
var STREAMS_DIR = "streams";
|
|
6661
6769
|
var STREAM_NAME_REGEX = /^[a-z0-9][a-z0-9._-]*$/;
|
|
6662
6770
|
function streamsDir(projectPath) {
|
|
6663
6771
|
return path2.join(projectPath, HARNESS_DIR, STREAMS_DIR);
|
|
@@ -6884,26 +6992,65 @@ async function migrateToStreams(projectPath) {
|
|
|
6884
6992
|
return saveStreamIndex(projectPath, index);
|
|
6885
6993
|
}
|
|
6886
6994
|
|
|
6887
|
-
// src/state/
|
|
6888
|
-
var
|
|
6889
|
-
var
|
|
6890
|
-
|
|
6891
|
-
|
|
6892
|
-
|
|
6893
|
-
|
|
6894
|
-
|
|
6995
|
+
// src/state/session-resolver.ts
|
|
6996
|
+
var fs6 = __toESM(require("fs"));
|
|
6997
|
+
var path3 = __toESM(require("path"));
|
|
6998
|
+
function resolveSessionDir(projectPath, sessionSlug, options) {
|
|
6999
|
+
if (!sessionSlug || sessionSlug.trim() === "") {
|
|
7000
|
+
return (0, import_types.Err)(new Error("Session slug must not be empty"));
|
|
7001
|
+
}
|
|
7002
|
+
if (sessionSlug.includes("..") || sessionSlug.includes("/") || sessionSlug.includes("\\")) {
|
|
7003
|
+
return (0, import_types.Err)(
|
|
7004
|
+
new Error(`Invalid session slug '${sessionSlug}': must not contain path traversal characters`)
|
|
7005
|
+
);
|
|
7006
|
+
}
|
|
7007
|
+
const sessionDir = path3.join(projectPath, HARNESS_DIR, SESSIONS_DIR, sessionSlug);
|
|
7008
|
+
if (options?.create) {
|
|
7009
|
+
fs6.mkdirSync(sessionDir, { recursive: true });
|
|
7010
|
+
}
|
|
7011
|
+
return (0, import_types.Ok)(sessionDir);
|
|
7012
|
+
}
|
|
7013
|
+
function updateSessionIndex(projectPath, sessionSlug, description) {
|
|
7014
|
+
const sessionsDir = path3.join(projectPath, HARNESS_DIR, SESSIONS_DIR);
|
|
7015
|
+
fs6.mkdirSync(sessionsDir, { recursive: true });
|
|
7016
|
+
const indexPath2 = path3.join(sessionsDir, SESSION_INDEX_FILE);
|
|
7017
|
+
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
7018
|
+
const newLine = `- [${sessionSlug}](${sessionSlug}/summary.md) \u2014 ${description} (${date})`;
|
|
7019
|
+
if (!fs6.existsSync(indexPath2)) {
|
|
7020
|
+
fs6.writeFileSync(indexPath2, `## Active Sessions
|
|
7021
|
+
|
|
7022
|
+
${newLine}
|
|
7023
|
+
`);
|
|
7024
|
+
return;
|
|
7025
|
+
}
|
|
7026
|
+
const content = fs6.readFileSync(indexPath2, "utf-8");
|
|
7027
|
+
const lines = content.split("\n");
|
|
7028
|
+
const slugPattern = `- [${sessionSlug}]`;
|
|
7029
|
+
const existingIdx = lines.findIndex((l) => l.startsWith(slugPattern));
|
|
7030
|
+
if (existingIdx >= 0) {
|
|
7031
|
+
lines[existingIdx] = newLine;
|
|
7032
|
+
} else {
|
|
7033
|
+
const lastNonEmpty = lines.reduce((last, line, i) => line.trim() !== "" ? i : last, 0);
|
|
7034
|
+
lines.splice(lastNonEmpty + 1, 0, newLine);
|
|
7035
|
+
}
|
|
7036
|
+
fs6.writeFileSync(indexPath2, lines.join("\n"));
|
|
7037
|
+
}
|
|
7038
|
+
|
|
7039
|
+
// src/state/state-shared.ts
|
|
6895
7040
|
var MAX_CACHE_ENTRIES = 8;
|
|
6896
|
-
var learningsCacheMap = /* @__PURE__ */ new Map();
|
|
6897
|
-
var failuresCacheMap = /* @__PURE__ */ new Map();
|
|
6898
7041
|
function evictIfNeeded(map) {
|
|
6899
7042
|
if (map.size > MAX_CACHE_ENTRIES) {
|
|
6900
7043
|
const oldest = map.keys().next().value;
|
|
6901
7044
|
if (oldest !== void 0) map.delete(oldest);
|
|
6902
7045
|
}
|
|
6903
7046
|
}
|
|
6904
|
-
async function getStateDir(projectPath, stream) {
|
|
6905
|
-
|
|
6906
|
-
|
|
7047
|
+
async function getStateDir(projectPath, stream, session) {
|
|
7048
|
+
if (session) {
|
|
7049
|
+
const sessionResult = resolveSessionDir(projectPath, session, { create: true });
|
|
7050
|
+
return sessionResult;
|
|
7051
|
+
}
|
|
7052
|
+
const streamsIndexPath = path4.join(projectPath, HARNESS_DIR, "streams", INDEX_FILE);
|
|
7053
|
+
const hasStreams = fs7.existsSync(streamsIndexPath);
|
|
6907
7054
|
if (stream || hasStreams) {
|
|
6908
7055
|
const result = await resolveStreamPath(projectPath, stream ? { stream } : void 0);
|
|
6909
7056
|
if (result.ok) {
|
|
@@ -6913,18 +7060,20 @@ async function getStateDir(projectPath, stream) {
|
|
|
6913
7060
|
return result;
|
|
6914
7061
|
}
|
|
6915
7062
|
}
|
|
6916
|
-
return (0, import_types.Ok)(
|
|
7063
|
+
return (0, import_types.Ok)(path4.join(projectPath, HARNESS_DIR));
|
|
6917
7064
|
}
|
|
6918
|
-
|
|
7065
|
+
|
|
7066
|
+
// src/state/state-persistence.ts
|
|
7067
|
+
async function loadState(projectPath, stream, session) {
|
|
6919
7068
|
try {
|
|
6920
|
-
const dirResult = await getStateDir(projectPath, stream);
|
|
7069
|
+
const dirResult = await getStateDir(projectPath, stream, session);
|
|
6921
7070
|
if (!dirResult.ok) return dirResult;
|
|
6922
7071
|
const stateDir = dirResult.value;
|
|
6923
|
-
const statePath =
|
|
6924
|
-
if (!
|
|
7072
|
+
const statePath = path5.join(stateDir, STATE_FILE);
|
|
7073
|
+
if (!fs8.existsSync(statePath)) {
|
|
6925
7074
|
return (0, import_types.Ok)({ ...DEFAULT_STATE });
|
|
6926
7075
|
}
|
|
6927
|
-
const raw =
|
|
7076
|
+
const raw = fs8.readFileSync(statePath, "utf-8");
|
|
6928
7077
|
const parsed = JSON.parse(raw);
|
|
6929
7078
|
const result = HarnessStateSchema.safeParse(parsed);
|
|
6930
7079
|
if (!result.success) {
|
|
@@ -6937,14 +7086,14 @@ async function loadState(projectPath, stream) {
|
|
|
6937
7086
|
);
|
|
6938
7087
|
}
|
|
6939
7088
|
}
|
|
6940
|
-
async function saveState(projectPath, state, stream) {
|
|
7089
|
+
async function saveState(projectPath, state, stream, session) {
|
|
6941
7090
|
try {
|
|
6942
|
-
const dirResult = await getStateDir(projectPath, stream);
|
|
7091
|
+
const dirResult = await getStateDir(projectPath, stream, session);
|
|
6943
7092
|
if (!dirResult.ok) return dirResult;
|
|
6944
7093
|
const stateDir = dirResult.value;
|
|
6945
|
-
const statePath =
|
|
6946
|
-
|
|
6947
|
-
|
|
7094
|
+
const statePath = path5.join(stateDir, STATE_FILE);
|
|
7095
|
+
fs8.mkdirSync(stateDir, { recursive: true });
|
|
7096
|
+
fs8.writeFileSync(statePath, JSON.stringify(state, null, 2));
|
|
6948
7097
|
return (0, import_types.Ok)(void 0);
|
|
6949
7098
|
} catch (error) {
|
|
6950
7099
|
return (0, import_types.Err)(
|
|
@@ -6952,13 +7101,21 @@ async function saveState(projectPath, state, stream) {
|
|
|
6952
7101
|
);
|
|
6953
7102
|
}
|
|
6954
7103
|
}
|
|
6955
|
-
|
|
7104
|
+
|
|
7105
|
+
// src/state/learnings.ts
|
|
7106
|
+
var fs9 = __toESM(require("fs"));
|
|
7107
|
+
var path6 = __toESM(require("path"));
|
|
7108
|
+
var learningsCacheMap = /* @__PURE__ */ new Map();
|
|
7109
|
+
function clearLearningsCache() {
|
|
7110
|
+
learningsCacheMap.clear();
|
|
7111
|
+
}
|
|
7112
|
+
async function appendLearning(projectPath, learning, skillName, outcome, stream, session) {
|
|
6956
7113
|
try {
|
|
6957
|
-
const dirResult = await getStateDir(projectPath, stream);
|
|
7114
|
+
const dirResult = await getStateDir(projectPath, stream, session);
|
|
6958
7115
|
if (!dirResult.ok) return dirResult;
|
|
6959
7116
|
const stateDir = dirResult.value;
|
|
6960
|
-
const learningsPath =
|
|
6961
|
-
|
|
7117
|
+
const learningsPath = path6.join(stateDir, LEARNINGS_FILE);
|
|
7118
|
+
fs9.mkdirSync(stateDir, { recursive: true });
|
|
6962
7119
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
6963
7120
|
let entry;
|
|
6964
7121
|
if (skillName && outcome) {
|
|
@@ -6974,11 +7131,11 @@ async function appendLearning(projectPath, learning, skillName, outcome, stream)
|
|
|
6974
7131
|
- **${timestamp}:** ${learning}
|
|
6975
7132
|
`;
|
|
6976
7133
|
}
|
|
6977
|
-
if (!
|
|
6978
|
-
|
|
7134
|
+
if (!fs9.existsSync(learningsPath)) {
|
|
7135
|
+
fs9.writeFileSync(learningsPath, `# Learnings
|
|
6979
7136
|
${entry}`);
|
|
6980
7137
|
} else {
|
|
6981
|
-
|
|
7138
|
+
fs9.appendFileSync(learningsPath, entry);
|
|
6982
7139
|
}
|
|
6983
7140
|
learningsCacheMap.delete(learningsPath);
|
|
6984
7141
|
return (0, import_types.Ok)(void 0);
|
|
@@ -6990,23 +7147,92 @@ ${entry}`);
|
|
|
6990
7147
|
);
|
|
6991
7148
|
}
|
|
6992
7149
|
}
|
|
6993
|
-
|
|
7150
|
+
function estimateTokens(text) {
|
|
7151
|
+
return Math.ceil(text.length / 4);
|
|
7152
|
+
}
|
|
7153
|
+
function scoreRelevance(entry, intent) {
|
|
7154
|
+
if (!intent || intent.trim() === "") return 0;
|
|
7155
|
+
const intentWords = intent.toLowerCase().split(/\s+/).filter((w) => w.length > 2);
|
|
7156
|
+
if (intentWords.length === 0) return 0;
|
|
7157
|
+
const entryLower = entry.toLowerCase();
|
|
7158
|
+
const matches = intentWords.filter((word) => entryLower.includes(word));
|
|
7159
|
+
return matches.length / intentWords.length;
|
|
7160
|
+
}
|
|
7161
|
+
function parseDateFromEntry(entry) {
|
|
7162
|
+
const match = entry.match(/(\d{4}-\d{2}-\d{2})/);
|
|
7163
|
+
return match ? match[1] ?? null : null;
|
|
7164
|
+
}
|
|
7165
|
+
function analyzeLearningPatterns(entries) {
|
|
7166
|
+
const tagGroups = /* @__PURE__ */ new Map();
|
|
7167
|
+
for (const entry of entries) {
|
|
7168
|
+
const tagMatches = entry.matchAll(/\[(skill:[^\]]+)\]|\[(outcome:[^\]]+)\]/g);
|
|
7169
|
+
for (const match of tagMatches) {
|
|
7170
|
+
const tag = match[1] ?? match[2];
|
|
7171
|
+
if (tag) {
|
|
7172
|
+
const group = tagGroups.get(tag) ?? [];
|
|
7173
|
+
group.push(entry);
|
|
7174
|
+
tagGroups.set(tag, group);
|
|
7175
|
+
}
|
|
7176
|
+
}
|
|
7177
|
+
}
|
|
7178
|
+
const patterns = [];
|
|
7179
|
+
for (const [tag, groupEntries] of tagGroups) {
|
|
7180
|
+
if (groupEntries.length >= 3) {
|
|
7181
|
+
patterns.push({ tag, count: groupEntries.length, entries: groupEntries });
|
|
7182
|
+
}
|
|
7183
|
+
}
|
|
7184
|
+
return patterns.sort((a, b) => b.count - a.count);
|
|
7185
|
+
}
|
|
7186
|
+
async function loadBudgetedLearnings(projectPath, options) {
|
|
7187
|
+
const { intent, tokenBudget = 1e3, skill, session, stream } = options;
|
|
7188
|
+
const sortByRecencyAndRelevance = (entries) => {
|
|
7189
|
+
return [...entries].sort((a, b) => {
|
|
7190
|
+
const dateA = parseDateFromEntry(a) ?? "0000-00-00";
|
|
7191
|
+
const dateB = parseDateFromEntry(b) ?? "0000-00-00";
|
|
7192
|
+
const dateCompare = dateB.localeCompare(dateA);
|
|
7193
|
+
if (dateCompare !== 0) return dateCompare;
|
|
7194
|
+
return scoreRelevance(b, intent) - scoreRelevance(a, intent);
|
|
7195
|
+
});
|
|
7196
|
+
};
|
|
7197
|
+
const allEntries = [];
|
|
7198
|
+
if (session) {
|
|
7199
|
+
const sessionResult = await loadRelevantLearnings(projectPath, skill, stream, session);
|
|
7200
|
+
if (sessionResult.ok) {
|
|
7201
|
+
allEntries.push(...sortByRecencyAndRelevance(sessionResult.value));
|
|
7202
|
+
}
|
|
7203
|
+
}
|
|
7204
|
+
const globalResult = await loadRelevantLearnings(projectPath, skill, stream);
|
|
7205
|
+
if (globalResult.ok) {
|
|
7206
|
+
allEntries.push(...sortByRecencyAndRelevance(globalResult.value));
|
|
7207
|
+
}
|
|
7208
|
+
const budgeted = [];
|
|
7209
|
+
let totalTokens = 0;
|
|
7210
|
+
for (const entry of allEntries) {
|
|
7211
|
+
const separator = budgeted.length > 0 ? "\n" : "";
|
|
7212
|
+
const entryCost = estimateTokens(entry + separator);
|
|
7213
|
+
if (totalTokens + entryCost > tokenBudget) break;
|
|
7214
|
+
budgeted.push(entry);
|
|
7215
|
+
totalTokens += entryCost;
|
|
7216
|
+
}
|
|
7217
|
+
return (0, import_types.Ok)(budgeted);
|
|
7218
|
+
}
|
|
7219
|
+
async function loadRelevantLearnings(projectPath, skillName, stream, session) {
|
|
6994
7220
|
try {
|
|
6995
|
-
const dirResult = await getStateDir(projectPath, stream);
|
|
7221
|
+
const dirResult = await getStateDir(projectPath, stream, session);
|
|
6996
7222
|
if (!dirResult.ok) return dirResult;
|
|
6997
7223
|
const stateDir = dirResult.value;
|
|
6998
|
-
const learningsPath =
|
|
6999
|
-
if (!
|
|
7224
|
+
const learningsPath = path6.join(stateDir, LEARNINGS_FILE);
|
|
7225
|
+
if (!fs9.existsSync(learningsPath)) {
|
|
7000
7226
|
return (0, import_types.Ok)([]);
|
|
7001
7227
|
}
|
|
7002
|
-
const stats =
|
|
7228
|
+
const stats = fs9.statSync(learningsPath);
|
|
7003
7229
|
const cacheKey = learningsPath;
|
|
7004
7230
|
const cached = learningsCacheMap.get(cacheKey);
|
|
7005
7231
|
let entries;
|
|
7006
7232
|
if (cached && cached.mtimeMs === stats.mtimeMs) {
|
|
7007
7233
|
entries = cached.entries;
|
|
7008
7234
|
} else {
|
|
7009
|
-
const content =
|
|
7235
|
+
const content = fs9.readFileSync(learningsPath, "utf-8");
|
|
7010
7236
|
const lines = content.split("\n");
|
|
7011
7237
|
entries = [];
|
|
7012
7238
|
let currentBlock = [];
|
|
@@ -7042,23 +7268,110 @@ async function loadRelevantLearnings(projectPath, skillName, stream) {
|
|
|
7042
7268
|
);
|
|
7043
7269
|
}
|
|
7044
7270
|
}
|
|
7045
|
-
|
|
7046
|
-
|
|
7271
|
+
async function archiveLearnings(projectPath, entries, stream) {
|
|
7272
|
+
try {
|
|
7273
|
+
const dirResult = await getStateDir(projectPath, stream);
|
|
7274
|
+
if (!dirResult.ok) return dirResult;
|
|
7275
|
+
const stateDir = dirResult.value;
|
|
7276
|
+
const archiveDir = path6.join(stateDir, "learnings-archive");
|
|
7277
|
+
fs9.mkdirSync(archiveDir, { recursive: true });
|
|
7278
|
+
const now = /* @__PURE__ */ new Date();
|
|
7279
|
+
const yearMonth = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`;
|
|
7280
|
+
const archivePath = path6.join(archiveDir, `${yearMonth}.md`);
|
|
7281
|
+
const archiveContent = entries.join("\n\n") + "\n";
|
|
7282
|
+
if (fs9.existsSync(archivePath)) {
|
|
7283
|
+
fs9.appendFileSync(archivePath, "\n" + archiveContent);
|
|
7284
|
+
} else {
|
|
7285
|
+
fs9.writeFileSync(archivePath, `# Learnings Archive
|
|
7286
|
+
|
|
7287
|
+
${archiveContent}`);
|
|
7288
|
+
}
|
|
7289
|
+
return (0, import_types.Ok)(void 0);
|
|
7290
|
+
} catch (error) {
|
|
7291
|
+
return (0, import_types.Err)(
|
|
7292
|
+
new Error(
|
|
7293
|
+
`Failed to archive learnings: ${error instanceof Error ? error.message : String(error)}`
|
|
7294
|
+
)
|
|
7295
|
+
);
|
|
7296
|
+
}
|
|
7297
|
+
}
|
|
7298
|
+
async function pruneLearnings(projectPath, stream) {
|
|
7047
7299
|
try {
|
|
7048
7300
|
const dirResult = await getStateDir(projectPath, stream);
|
|
7049
7301
|
if (!dirResult.ok) return dirResult;
|
|
7050
7302
|
const stateDir = dirResult.value;
|
|
7051
|
-
const
|
|
7052
|
-
|
|
7303
|
+
const learningsPath = path6.join(stateDir, LEARNINGS_FILE);
|
|
7304
|
+
if (!fs9.existsSync(learningsPath)) {
|
|
7305
|
+
return (0, import_types.Ok)({ kept: 0, archived: 0, patterns: [] });
|
|
7306
|
+
}
|
|
7307
|
+
const loadResult = await loadRelevantLearnings(projectPath, void 0, stream);
|
|
7308
|
+
if (!loadResult.ok) return loadResult;
|
|
7309
|
+
const allEntries = loadResult.value;
|
|
7310
|
+
if (allEntries.length <= 20) {
|
|
7311
|
+
const cutoffDate = /* @__PURE__ */ new Date();
|
|
7312
|
+
cutoffDate.setDate(cutoffDate.getDate() - 14);
|
|
7313
|
+
const cutoffStr = cutoffDate.toISOString().split("T")[0];
|
|
7314
|
+
const hasOld = allEntries.some((entry) => {
|
|
7315
|
+
const date = parseDateFromEntry(entry);
|
|
7316
|
+
return date !== null && date < cutoffStr;
|
|
7317
|
+
});
|
|
7318
|
+
if (!hasOld) {
|
|
7319
|
+
return (0, import_types.Ok)({ kept: allEntries.length, archived: 0, patterns: [] });
|
|
7320
|
+
}
|
|
7321
|
+
}
|
|
7322
|
+
const sorted = [...allEntries].sort((a, b) => {
|
|
7323
|
+
const dateA = parseDateFromEntry(a) ?? "0000-00-00";
|
|
7324
|
+
const dateB = parseDateFromEntry(b) ?? "0000-00-00";
|
|
7325
|
+
return dateB.localeCompare(dateA);
|
|
7326
|
+
});
|
|
7327
|
+
const toKeep = sorted.slice(0, 20);
|
|
7328
|
+
const toArchive = sorted.slice(20);
|
|
7329
|
+
const patterns = analyzeLearningPatterns(allEntries);
|
|
7330
|
+
if (toArchive.length > 0) {
|
|
7331
|
+
const archiveResult = await archiveLearnings(projectPath, toArchive, stream);
|
|
7332
|
+
if (!archiveResult.ok) return archiveResult;
|
|
7333
|
+
}
|
|
7334
|
+
const newContent = "# Learnings\n\n" + toKeep.join("\n\n") + "\n";
|
|
7335
|
+
fs9.writeFileSync(learningsPath, newContent);
|
|
7336
|
+
learningsCacheMap.delete(learningsPath);
|
|
7337
|
+
return (0, import_types.Ok)({
|
|
7338
|
+
kept: toKeep.length,
|
|
7339
|
+
archived: toArchive.length,
|
|
7340
|
+
patterns
|
|
7341
|
+
});
|
|
7342
|
+
} catch (error) {
|
|
7343
|
+
return (0, import_types.Err)(
|
|
7344
|
+
new Error(
|
|
7345
|
+
`Failed to prune learnings: ${error instanceof Error ? error.message : String(error)}`
|
|
7346
|
+
)
|
|
7347
|
+
);
|
|
7348
|
+
}
|
|
7349
|
+
}
|
|
7350
|
+
|
|
7351
|
+
// src/state/failures.ts
|
|
7352
|
+
var fs10 = __toESM(require("fs"));
|
|
7353
|
+
var path7 = __toESM(require("path"));
|
|
7354
|
+
var failuresCacheMap = /* @__PURE__ */ new Map();
|
|
7355
|
+
function clearFailuresCache() {
|
|
7356
|
+
failuresCacheMap.clear();
|
|
7357
|
+
}
|
|
7358
|
+
var FAILURE_LINE_REGEX = /^- \*\*(\d{4}-\d{2}-\d{2}) \[skill:([^\]]+)\] \[type:([^\]]+)\]:\*\* (.+)$/;
|
|
7359
|
+
async function appendFailure(projectPath, description, skillName, type, stream, session) {
|
|
7360
|
+
try {
|
|
7361
|
+
const dirResult = await getStateDir(projectPath, stream, session);
|
|
7362
|
+
if (!dirResult.ok) return dirResult;
|
|
7363
|
+
const stateDir = dirResult.value;
|
|
7364
|
+
const failuresPath = path7.join(stateDir, FAILURES_FILE);
|
|
7365
|
+
fs10.mkdirSync(stateDir, { recursive: true });
|
|
7053
7366
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
7054
7367
|
const entry = `
|
|
7055
7368
|
- **${timestamp} [skill:${skillName}] [type:${type}]:** ${description}
|
|
7056
7369
|
`;
|
|
7057
|
-
if (!
|
|
7058
|
-
|
|
7370
|
+
if (!fs10.existsSync(failuresPath)) {
|
|
7371
|
+
fs10.writeFileSync(failuresPath, `# Failures
|
|
7059
7372
|
${entry}`);
|
|
7060
7373
|
} else {
|
|
7061
|
-
|
|
7374
|
+
fs10.appendFileSync(failuresPath, entry);
|
|
7062
7375
|
}
|
|
7063
7376
|
failuresCacheMap.delete(failuresPath);
|
|
7064
7377
|
return (0, import_types.Ok)(void 0);
|
|
@@ -7070,22 +7383,22 @@ ${entry}`);
|
|
|
7070
7383
|
);
|
|
7071
7384
|
}
|
|
7072
7385
|
}
|
|
7073
|
-
async function loadFailures(projectPath, stream) {
|
|
7386
|
+
async function loadFailures(projectPath, stream, session) {
|
|
7074
7387
|
try {
|
|
7075
|
-
const dirResult = await getStateDir(projectPath, stream);
|
|
7388
|
+
const dirResult = await getStateDir(projectPath, stream, session);
|
|
7076
7389
|
if (!dirResult.ok) return dirResult;
|
|
7077
7390
|
const stateDir = dirResult.value;
|
|
7078
|
-
const failuresPath =
|
|
7079
|
-
if (!
|
|
7391
|
+
const failuresPath = path7.join(stateDir, FAILURES_FILE);
|
|
7392
|
+
if (!fs10.existsSync(failuresPath)) {
|
|
7080
7393
|
return (0, import_types.Ok)([]);
|
|
7081
7394
|
}
|
|
7082
|
-
const stats =
|
|
7395
|
+
const stats = fs10.statSync(failuresPath);
|
|
7083
7396
|
const cacheKey = failuresPath;
|
|
7084
7397
|
const cached = failuresCacheMap.get(cacheKey);
|
|
7085
7398
|
if (cached && cached.mtimeMs === stats.mtimeMs) {
|
|
7086
7399
|
return (0, import_types.Ok)(cached.entries);
|
|
7087
7400
|
}
|
|
7088
|
-
const content =
|
|
7401
|
+
const content = fs10.readFileSync(failuresPath, "utf-8");
|
|
7089
7402
|
const entries = [];
|
|
7090
7403
|
for (const line of content.split("\n")) {
|
|
7091
7404
|
const match = line.match(FAILURE_LINE_REGEX);
|
|
@@ -7109,25 +7422,25 @@ async function loadFailures(projectPath, stream) {
|
|
|
7109
7422
|
);
|
|
7110
7423
|
}
|
|
7111
7424
|
}
|
|
7112
|
-
async function archiveFailures(projectPath, stream) {
|
|
7425
|
+
async function archiveFailures(projectPath, stream, session) {
|
|
7113
7426
|
try {
|
|
7114
|
-
const dirResult = await getStateDir(projectPath, stream);
|
|
7427
|
+
const dirResult = await getStateDir(projectPath, stream, session);
|
|
7115
7428
|
if (!dirResult.ok) return dirResult;
|
|
7116
7429
|
const stateDir = dirResult.value;
|
|
7117
|
-
const failuresPath =
|
|
7118
|
-
if (!
|
|
7430
|
+
const failuresPath = path7.join(stateDir, FAILURES_FILE);
|
|
7431
|
+
if (!fs10.existsSync(failuresPath)) {
|
|
7119
7432
|
return (0, import_types.Ok)(void 0);
|
|
7120
7433
|
}
|
|
7121
|
-
const archiveDir =
|
|
7122
|
-
|
|
7434
|
+
const archiveDir = path7.join(stateDir, "archive");
|
|
7435
|
+
fs10.mkdirSync(archiveDir, { recursive: true });
|
|
7123
7436
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
7124
7437
|
let archiveName = `failures-${date}.md`;
|
|
7125
7438
|
let counter = 2;
|
|
7126
|
-
while (
|
|
7439
|
+
while (fs10.existsSync(path7.join(archiveDir, archiveName))) {
|
|
7127
7440
|
archiveName = `failures-${date}-${counter}.md`;
|
|
7128
7441
|
counter++;
|
|
7129
7442
|
}
|
|
7130
|
-
|
|
7443
|
+
fs10.renameSync(failuresPath, path7.join(archiveDir, archiveName));
|
|
7131
7444
|
failuresCacheMap.delete(failuresPath);
|
|
7132
7445
|
return (0, import_types.Ok)(void 0);
|
|
7133
7446
|
} catch (error) {
|
|
@@ -7138,14 +7451,18 @@ async function archiveFailures(projectPath, stream) {
|
|
|
7138
7451
|
);
|
|
7139
7452
|
}
|
|
7140
7453
|
}
|
|
7141
|
-
|
|
7454
|
+
|
|
7455
|
+
// src/state/handoff.ts
|
|
7456
|
+
var fs11 = __toESM(require("fs"));
|
|
7457
|
+
var path8 = __toESM(require("path"));
|
|
7458
|
+
async function saveHandoff(projectPath, handoff, stream, session) {
|
|
7142
7459
|
try {
|
|
7143
|
-
const dirResult = await getStateDir(projectPath, stream);
|
|
7460
|
+
const dirResult = await getStateDir(projectPath, stream, session);
|
|
7144
7461
|
if (!dirResult.ok) return dirResult;
|
|
7145
7462
|
const stateDir = dirResult.value;
|
|
7146
|
-
const handoffPath =
|
|
7147
|
-
|
|
7148
|
-
|
|
7463
|
+
const handoffPath = path8.join(stateDir, HANDOFF_FILE);
|
|
7464
|
+
fs11.mkdirSync(stateDir, { recursive: true });
|
|
7465
|
+
fs11.writeFileSync(handoffPath, JSON.stringify(handoff, null, 2));
|
|
7149
7466
|
return (0, import_types.Ok)(void 0);
|
|
7150
7467
|
} catch (error) {
|
|
7151
7468
|
return (0, import_types.Err)(
|
|
@@ -7153,16 +7470,16 @@ async function saveHandoff(projectPath, handoff, stream) {
|
|
|
7153
7470
|
);
|
|
7154
7471
|
}
|
|
7155
7472
|
}
|
|
7156
|
-
async function loadHandoff(projectPath, stream) {
|
|
7473
|
+
async function loadHandoff(projectPath, stream, session) {
|
|
7157
7474
|
try {
|
|
7158
|
-
const dirResult = await getStateDir(projectPath, stream);
|
|
7475
|
+
const dirResult = await getStateDir(projectPath, stream, session);
|
|
7159
7476
|
if (!dirResult.ok) return dirResult;
|
|
7160
7477
|
const stateDir = dirResult.value;
|
|
7161
|
-
const handoffPath =
|
|
7162
|
-
if (!
|
|
7478
|
+
const handoffPath = path8.join(stateDir, HANDOFF_FILE);
|
|
7479
|
+
if (!fs11.existsSync(handoffPath)) {
|
|
7163
7480
|
return (0, import_types.Ok)(null);
|
|
7164
7481
|
}
|
|
7165
|
-
const raw =
|
|
7482
|
+
const raw = fs11.readFileSync(handoffPath, "utf-8");
|
|
7166
7483
|
const parsed = JSON.parse(raw);
|
|
7167
7484
|
const result = HandoffSchema.safeParse(parsed);
|
|
7168
7485
|
if (!result.success) {
|
|
@@ -7175,73 +7492,82 @@ async function loadHandoff(projectPath, stream) {
|
|
|
7175
7492
|
);
|
|
7176
7493
|
}
|
|
7177
7494
|
}
|
|
7495
|
+
|
|
7496
|
+
// src/state/mechanical-gate.ts
|
|
7497
|
+
var fs12 = __toESM(require("fs"));
|
|
7498
|
+
var path9 = __toESM(require("path"));
|
|
7499
|
+
var import_child_process2 = require("child_process");
|
|
7500
|
+
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:.-]+$/;
|
|
7501
|
+
function loadChecksFromConfig(gateConfigPath) {
|
|
7502
|
+
if (!fs12.existsSync(gateConfigPath)) return [];
|
|
7503
|
+
const raw = JSON.parse(fs12.readFileSync(gateConfigPath, "utf-8"));
|
|
7504
|
+
const config = GateConfigSchema.safeParse(raw);
|
|
7505
|
+
if (config.success && config.data.checks) return config.data.checks;
|
|
7506
|
+
return [];
|
|
7507
|
+
}
|
|
7508
|
+
function discoverChecksFromProject(projectPath) {
|
|
7509
|
+
const checks = [];
|
|
7510
|
+
const packageJsonPath = path9.join(projectPath, "package.json");
|
|
7511
|
+
if (fs12.existsSync(packageJsonPath)) {
|
|
7512
|
+
const pkg = JSON.parse(fs12.readFileSync(packageJsonPath, "utf-8"));
|
|
7513
|
+
const scripts = pkg.scripts || {};
|
|
7514
|
+
if (scripts.test) checks.push({ name: "test", command: "npm test" });
|
|
7515
|
+
if (scripts.lint) checks.push({ name: "lint", command: "npm run lint" });
|
|
7516
|
+
if (scripts.typecheck) checks.push({ name: "typecheck", command: "npm run typecheck" });
|
|
7517
|
+
if (scripts.build) checks.push({ name: "build", command: "npm run build" });
|
|
7518
|
+
}
|
|
7519
|
+
if (fs12.existsSync(path9.join(projectPath, "go.mod"))) {
|
|
7520
|
+
checks.push({ name: "test", command: "go test ./..." });
|
|
7521
|
+
checks.push({ name: "build", command: "go build ./..." });
|
|
7522
|
+
}
|
|
7523
|
+
if (fs12.existsSync(path9.join(projectPath, "pyproject.toml")) || fs12.existsSync(path9.join(projectPath, "setup.py"))) {
|
|
7524
|
+
checks.push({ name: "test", command: "python -m pytest" });
|
|
7525
|
+
}
|
|
7526
|
+
return checks;
|
|
7527
|
+
}
|
|
7528
|
+
function executeCheck(check, projectPath) {
|
|
7529
|
+
if (!SAFE_GATE_COMMAND.test(check.command)) {
|
|
7530
|
+
return {
|
|
7531
|
+
name: check.name,
|
|
7532
|
+
passed: false,
|
|
7533
|
+
command: check.command,
|
|
7534
|
+
output: `Blocked: command does not match safe gate pattern. Allowed prefixes: npm, pnpm, yarn, go, python, python3, make, cargo, gradle, mvn`,
|
|
7535
|
+
duration: 0
|
|
7536
|
+
};
|
|
7537
|
+
}
|
|
7538
|
+
const start = Date.now();
|
|
7539
|
+
try {
|
|
7540
|
+
(0, import_child_process2.execSync)(check.command, {
|
|
7541
|
+
cwd: projectPath,
|
|
7542
|
+
stdio: "pipe",
|
|
7543
|
+
timeout: 12e4
|
|
7544
|
+
});
|
|
7545
|
+
return {
|
|
7546
|
+
name: check.name,
|
|
7547
|
+
passed: true,
|
|
7548
|
+
command: check.command,
|
|
7549
|
+
duration: Date.now() - start
|
|
7550
|
+
};
|
|
7551
|
+
} catch (error) {
|
|
7552
|
+
const output = error instanceof Error ? error.stderr?.toString() || error.message : String(error);
|
|
7553
|
+
return {
|
|
7554
|
+
name: check.name,
|
|
7555
|
+
passed: false,
|
|
7556
|
+
command: check.command,
|
|
7557
|
+
output: output.slice(0, 2e3),
|
|
7558
|
+
duration: Date.now() - start
|
|
7559
|
+
};
|
|
7560
|
+
}
|
|
7561
|
+
}
|
|
7178
7562
|
async function runMechanicalGate(projectPath) {
|
|
7179
|
-
const harnessDir =
|
|
7180
|
-
const gateConfigPath =
|
|
7563
|
+
const harnessDir = path9.join(projectPath, HARNESS_DIR);
|
|
7564
|
+
const gateConfigPath = path9.join(harnessDir, GATE_CONFIG_FILE);
|
|
7181
7565
|
try {
|
|
7182
|
-
let checks =
|
|
7183
|
-
if (fs6.existsSync(gateConfigPath)) {
|
|
7184
|
-
const raw = JSON.parse(fs6.readFileSync(gateConfigPath, "utf-8"));
|
|
7185
|
-
const config = GateConfigSchema.safeParse(raw);
|
|
7186
|
-
if (config.success && config.data.checks) {
|
|
7187
|
-
checks = config.data.checks;
|
|
7188
|
-
}
|
|
7189
|
-
}
|
|
7566
|
+
let checks = loadChecksFromConfig(gateConfigPath);
|
|
7190
7567
|
if (checks.length === 0) {
|
|
7191
|
-
|
|
7192
|
-
if (fs6.existsSync(packageJsonPath)) {
|
|
7193
|
-
const pkg = JSON.parse(fs6.readFileSync(packageJsonPath, "utf-8"));
|
|
7194
|
-
const scripts = pkg.scripts || {};
|
|
7195
|
-
if (scripts.test) checks.push({ name: "test", command: "npm test" });
|
|
7196
|
-
if (scripts.lint) checks.push({ name: "lint", command: "npm run lint" });
|
|
7197
|
-
if (scripts.typecheck) checks.push({ name: "typecheck", command: "npm run typecheck" });
|
|
7198
|
-
if (scripts.build) checks.push({ name: "build", command: "npm run build" });
|
|
7199
|
-
}
|
|
7200
|
-
if (fs6.existsSync(path3.join(projectPath, "go.mod"))) {
|
|
7201
|
-
checks.push({ name: "test", command: "go test ./..." });
|
|
7202
|
-
checks.push({ name: "build", command: "go build ./..." });
|
|
7203
|
-
}
|
|
7204
|
-
if (fs6.existsSync(path3.join(projectPath, "pyproject.toml")) || fs6.existsSync(path3.join(projectPath, "setup.py"))) {
|
|
7205
|
-
checks.push({ name: "test", command: "python -m pytest" });
|
|
7206
|
-
}
|
|
7207
|
-
}
|
|
7208
|
-
const results = [];
|
|
7209
|
-
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:.-]+$/;
|
|
7210
|
-
for (const check of checks) {
|
|
7211
|
-
if (!SAFE_GATE_COMMAND.test(check.command)) {
|
|
7212
|
-
results.push({
|
|
7213
|
-
name: check.name,
|
|
7214
|
-
passed: false,
|
|
7215
|
-
command: check.command,
|
|
7216
|
-
output: `Blocked: command does not match safe gate pattern. Allowed prefixes: npm, npx, pnpm, yarn, go, python, python3, make, cargo, gradle, mvn`,
|
|
7217
|
-
duration: 0
|
|
7218
|
-
});
|
|
7219
|
-
continue;
|
|
7220
|
-
}
|
|
7221
|
-
const start = Date.now();
|
|
7222
|
-
try {
|
|
7223
|
-
(0, import_child_process2.execSync)(check.command, {
|
|
7224
|
-
cwd: projectPath,
|
|
7225
|
-
stdio: "pipe",
|
|
7226
|
-
timeout: 12e4
|
|
7227
|
-
});
|
|
7228
|
-
results.push({
|
|
7229
|
-
name: check.name,
|
|
7230
|
-
passed: true,
|
|
7231
|
-
command: check.command,
|
|
7232
|
-
duration: Date.now() - start
|
|
7233
|
-
});
|
|
7234
|
-
} catch (error) {
|
|
7235
|
-
const output = error instanceof Error ? error.stderr?.toString() || error.message : String(error);
|
|
7236
|
-
results.push({
|
|
7237
|
-
name: check.name,
|
|
7238
|
-
passed: false,
|
|
7239
|
-
command: check.command,
|
|
7240
|
-
output: output.slice(0, 2e3),
|
|
7241
|
-
duration: Date.now() - start
|
|
7242
|
-
});
|
|
7243
|
-
}
|
|
7568
|
+
checks = discoverChecksFromProject(projectPath);
|
|
7244
7569
|
}
|
|
7570
|
+
const results = checks.map((check) => executeCheck(check, projectPath));
|
|
7245
7571
|
return (0, import_types.Ok)({
|
|
7246
7572
|
passed: results.length === 0 || results.every((r) => r.passed),
|
|
7247
7573
|
checks: results
|
|
@@ -7255,6 +7581,96 @@ async function runMechanicalGate(projectPath) {
|
|
|
7255
7581
|
}
|
|
7256
7582
|
}
|
|
7257
7583
|
|
|
7584
|
+
// src/state/session-summary.ts
|
|
7585
|
+
var fs13 = __toESM(require("fs"));
|
|
7586
|
+
var path10 = __toESM(require("path"));
|
|
7587
|
+
function formatSummary(data) {
|
|
7588
|
+
const lines = [
|
|
7589
|
+
"## Session Summary",
|
|
7590
|
+
"",
|
|
7591
|
+
`**Session:** ${data.session}`,
|
|
7592
|
+
`**Last active:** ${data.lastActive}`,
|
|
7593
|
+
`**Skill:** ${data.skill}`
|
|
7594
|
+
];
|
|
7595
|
+
if (data.phase) {
|
|
7596
|
+
lines.push(`**Phase:** ${data.phase}`);
|
|
7597
|
+
}
|
|
7598
|
+
lines.push(`**Status:** ${data.status}`);
|
|
7599
|
+
if (data.spec) {
|
|
7600
|
+
lines.push(`**Spec:** ${data.spec}`);
|
|
7601
|
+
}
|
|
7602
|
+
if (data.plan) {
|
|
7603
|
+
lines.push(`**Plan:** ${data.plan}`);
|
|
7604
|
+
}
|
|
7605
|
+
lines.push(`**Key context:** ${data.keyContext}`);
|
|
7606
|
+
lines.push(`**Next step:** ${data.nextStep}`);
|
|
7607
|
+
lines.push("");
|
|
7608
|
+
return lines.join("\n");
|
|
7609
|
+
}
|
|
7610
|
+
function deriveIndexDescription(data) {
|
|
7611
|
+
const skillShort = data.skill.replace("harness-", "");
|
|
7612
|
+
const parts = [skillShort];
|
|
7613
|
+
if (data.phase) {
|
|
7614
|
+
parts.push(`phase ${data.phase}`);
|
|
7615
|
+
}
|
|
7616
|
+
parts.push(data.status.toLowerCase());
|
|
7617
|
+
return parts.join(", ");
|
|
7618
|
+
}
|
|
7619
|
+
function writeSessionSummary(projectPath, sessionSlug, data) {
|
|
7620
|
+
try {
|
|
7621
|
+
const dirResult = resolveSessionDir(projectPath, sessionSlug, { create: true });
|
|
7622
|
+
if (!dirResult.ok) return dirResult;
|
|
7623
|
+
const sessionDir = dirResult.value;
|
|
7624
|
+
const summaryPath = path10.join(sessionDir, SUMMARY_FILE);
|
|
7625
|
+
const content = formatSummary(data);
|
|
7626
|
+
fs13.writeFileSync(summaryPath, content);
|
|
7627
|
+
const description = deriveIndexDescription(data);
|
|
7628
|
+
updateSessionIndex(projectPath, sessionSlug, description);
|
|
7629
|
+
return (0, import_types.Ok)(void 0);
|
|
7630
|
+
} catch (error) {
|
|
7631
|
+
return (0, import_types.Err)(
|
|
7632
|
+
new Error(
|
|
7633
|
+
`Failed to write session summary: ${error instanceof Error ? error.message : String(error)}`
|
|
7634
|
+
)
|
|
7635
|
+
);
|
|
7636
|
+
}
|
|
7637
|
+
}
|
|
7638
|
+
function loadSessionSummary(projectPath, sessionSlug) {
|
|
7639
|
+
try {
|
|
7640
|
+
const dirResult = resolveSessionDir(projectPath, sessionSlug);
|
|
7641
|
+
if (!dirResult.ok) return dirResult;
|
|
7642
|
+
const sessionDir = dirResult.value;
|
|
7643
|
+
const summaryPath = path10.join(sessionDir, SUMMARY_FILE);
|
|
7644
|
+
if (!fs13.existsSync(summaryPath)) {
|
|
7645
|
+
return (0, import_types.Ok)(null);
|
|
7646
|
+
}
|
|
7647
|
+
const content = fs13.readFileSync(summaryPath, "utf-8");
|
|
7648
|
+
return (0, import_types.Ok)(content);
|
|
7649
|
+
} catch (error) {
|
|
7650
|
+
return (0, import_types.Err)(
|
|
7651
|
+
new Error(
|
|
7652
|
+
`Failed to load session summary: ${error instanceof Error ? error.message : String(error)}`
|
|
7653
|
+
)
|
|
7654
|
+
);
|
|
7655
|
+
}
|
|
7656
|
+
}
|
|
7657
|
+
function listActiveSessions(projectPath) {
|
|
7658
|
+
try {
|
|
7659
|
+
const indexPath2 = path10.join(projectPath, HARNESS_DIR, SESSIONS_DIR, SESSION_INDEX_FILE);
|
|
7660
|
+
if (!fs13.existsSync(indexPath2)) {
|
|
7661
|
+
return (0, import_types.Ok)(null);
|
|
7662
|
+
}
|
|
7663
|
+
const content = fs13.readFileSync(indexPath2, "utf-8");
|
|
7664
|
+
return (0, import_types.Ok)(content);
|
|
7665
|
+
} catch (error) {
|
|
7666
|
+
return (0, import_types.Err)(
|
|
7667
|
+
new Error(
|
|
7668
|
+
`Failed to list active sessions: ${error instanceof Error ? error.message : String(error)}`
|
|
7669
|
+
)
|
|
7670
|
+
);
|
|
7671
|
+
}
|
|
7672
|
+
}
|
|
7673
|
+
|
|
7258
7674
|
// src/workflow/runner.ts
|
|
7259
7675
|
async function executeWorkflow(workflow, executor) {
|
|
7260
7676
|
const stepResults = [];
|
|
@@ -7404,7 +7820,7 @@ async function runMultiTurnPipeline(initialContext, turnExecutor, options) {
|
|
|
7404
7820
|
}
|
|
7405
7821
|
|
|
7406
7822
|
// src/security/scanner.ts
|
|
7407
|
-
var
|
|
7823
|
+
var fs15 = __toESM(require("fs/promises"));
|
|
7408
7824
|
|
|
7409
7825
|
// src/security/rules/registry.ts
|
|
7410
7826
|
var RuleRegistry = class {
|
|
@@ -7491,15 +7907,15 @@ function resolveRuleSeverity(ruleId, defaultSeverity, overrides, strict) {
|
|
|
7491
7907
|
}
|
|
7492
7908
|
|
|
7493
7909
|
// src/security/stack-detector.ts
|
|
7494
|
-
var
|
|
7495
|
-
var
|
|
7910
|
+
var fs14 = __toESM(require("fs"));
|
|
7911
|
+
var path11 = __toESM(require("path"));
|
|
7496
7912
|
function detectStack(projectRoot) {
|
|
7497
7913
|
const stacks = [];
|
|
7498
|
-
const pkgJsonPath =
|
|
7499
|
-
if (
|
|
7914
|
+
const pkgJsonPath = path11.join(projectRoot, "package.json");
|
|
7915
|
+
if (fs14.existsSync(pkgJsonPath)) {
|
|
7500
7916
|
stacks.push("node");
|
|
7501
7917
|
try {
|
|
7502
|
-
const pkgJson = JSON.parse(
|
|
7918
|
+
const pkgJson = JSON.parse(fs14.readFileSync(pkgJsonPath, "utf-8"));
|
|
7503
7919
|
const allDeps = {
|
|
7504
7920
|
...pkgJson.dependencies,
|
|
7505
7921
|
...pkgJson.devDependencies
|
|
@@ -7514,13 +7930,13 @@ function detectStack(projectRoot) {
|
|
|
7514
7930
|
} catch {
|
|
7515
7931
|
}
|
|
7516
7932
|
}
|
|
7517
|
-
const goModPath =
|
|
7518
|
-
if (
|
|
7933
|
+
const goModPath = path11.join(projectRoot, "go.mod");
|
|
7934
|
+
if (fs14.existsSync(goModPath)) {
|
|
7519
7935
|
stacks.push("go");
|
|
7520
7936
|
}
|
|
7521
|
-
const requirementsPath =
|
|
7522
|
-
const pyprojectPath =
|
|
7523
|
-
if (
|
|
7937
|
+
const requirementsPath = path11.join(projectRoot, "requirements.txt");
|
|
7938
|
+
const pyprojectPath = path11.join(projectRoot, "pyproject.toml");
|
|
7939
|
+
if (fs14.existsSync(requirementsPath) || fs14.existsSync(pyprojectPath)) {
|
|
7524
7940
|
stacks.push("python");
|
|
7525
7941
|
}
|
|
7526
7942
|
return stacks;
|
|
@@ -7947,7 +8363,7 @@ var SecurityScanner = class {
|
|
|
7947
8363
|
}
|
|
7948
8364
|
async scanFile(filePath) {
|
|
7949
8365
|
if (!this.config.enabled) return [];
|
|
7950
|
-
const content = await
|
|
8366
|
+
const content = await fs15.readFile(filePath, "utf-8");
|
|
7951
8367
|
return this.scanContent(content, filePath, 1);
|
|
7952
8368
|
}
|
|
7953
8369
|
async scanFiles(filePaths) {
|
|
@@ -7972,7 +8388,7 @@ var SecurityScanner = class {
|
|
|
7972
8388
|
};
|
|
7973
8389
|
|
|
7974
8390
|
// src/ci/check-orchestrator.ts
|
|
7975
|
-
var
|
|
8391
|
+
var path12 = __toESM(require("path"));
|
|
7976
8392
|
var ALL_CHECKS = [
|
|
7977
8393
|
"validate",
|
|
7978
8394
|
"deps",
|
|
@@ -7989,7 +8405,7 @@ async function runSingleCheck(name, projectRoot, config) {
|
|
|
7989
8405
|
try {
|
|
7990
8406
|
switch (name) {
|
|
7991
8407
|
case "validate": {
|
|
7992
|
-
const agentsPath =
|
|
8408
|
+
const agentsPath = path12.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
|
|
7993
8409
|
const result = await validateAgentsMap(agentsPath);
|
|
7994
8410
|
if (!result.ok) {
|
|
7995
8411
|
issues.push({ severity: "error", message: result.error.message });
|
|
@@ -8044,7 +8460,7 @@ async function runSingleCheck(name, projectRoot, config) {
|
|
|
8044
8460
|
break;
|
|
8045
8461
|
}
|
|
8046
8462
|
case "docs": {
|
|
8047
|
-
const docsDir =
|
|
8463
|
+
const docsDir = path12.join(projectRoot, config.docsDir ?? "docs");
|
|
8048
8464
|
const entropyConfig = config.entropy || {};
|
|
8049
8465
|
const result = await checkDocCoverage("project", {
|
|
8050
8466
|
docsDir,
|
|
@@ -8282,7 +8698,7 @@ async function runCIChecks(input) {
|
|
|
8282
8698
|
}
|
|
8283
8699
|
|
|
8284
8700
|
// src/review/mechanical-checks.ts
|
|
8285
|
-
var
|
|
8701
|
+
var path13 = __toESM(require("path"));
|
|
8286
8702
|
async function runMechanicalChecks(options) {
|
|
8287
8703
|
const { projectRoot, config, skip = [], changedFiles } = options;
|
|
8288
8704
|
const findings = [];
|
|
@@ -8294,7 +8710,7 @@ async function runMechanicalChecks(options) {
|
|
|
8294
8710
|
};
|
|
8295
8711
|
if (!skip.includes("validate")) {
|
|
8296
8712
|
try {
|
|
8297
|
-
const agentsPath =
|
|
8713
|
+
const agentsPath = path13.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
|
|
8298
8714
|
const result = await validateAgentsMap(agentsPath);
|
|
8299
8715
|
if (!result.ok) {
|
|
8300
8716
|
statuses.validate = "fail";
|
|
@@ -8331,7 +8747,7 @@ async function runMechanicalChecks(options) {
|
|
|
8331
8747
|
statuses.validate = "fail";
|
|
8332
8748
|
findings.push({
|
|
8333
8749
|
tool: "validate",
|
|
8334
|
-
file:
|
|
8750
|
+
file: path13.join(projectRoot, "AGENTS.md"),
|
|
8335
8751
|
message: err instanceof Error ? err.message : String(err),
|
|
8336
8752
|
severity: "error"
|
|
8337
8753
|
});
|
|
@@ -8395,7 +8811,7 @@ async function runMechanicalChecks(options) {
|
|
|
8395
8811
|
(async () => {
|
|
8396
8812
|
const localFindings = [];
|
|
8397
8813
|
try {
|
|
8398
|
-
const docsDir =
|
|
8814
|
+
const docsDir = path13.join(projectRoot, config.docsDir ?? "docs");
|
|
8399
8815
|
const result = await checkDocCoverage("project", { docsDir });
|
|
8400
8816
|
if (!result.ok) {
|
|
8401
8817
|
statuses["check-docs"] = "warn";
|
|
@@ -8422,7 +8838,7 @@ async function runMechanicalChecks(options) {
|
|
|
8422
8838
|
statuses["check-docs"] = "warn";
|
|
8423
8839
|
localFindings.push({
|
|
8424
8840
|
tool: "check-docs",
|
|
8425
|
-
file:
|
|
8841
|
+
file: path13.join(projectRoot, "docs"),
|
|
8426
8842
|
message: err instanceof Error ? err.message : String(err),
|
|
8427
8843
|
severity: "warning"
|
|
8428
8844
|
});
|
|
@@ -8570,7 +8986,7 @@ function detectChangeType(commitMessage, diff2) {
|
|
|
8570
8986
|
}
|
|
8571
8987
|
|
|
8572
8988
|
// src/review/context-scoper.ts
|
|
8573
|
-
var
|
|
8989
|
+
var path14 = __toESM(require("path"));
|
|
8574
8990
|
var ALL_DOMAINS = ["compliance", "bug", "security", "architecture"];
|
|
8575
8991
|
var SECURITY_PATTERNS = /auth|crypto|password|secret|token|session|cookie|hash|encrypt|decrypt|sql|shell|exec|eval/i;
|
|
8576
8992
|
function computeContextBudget(diffLines) {
|
|
@@ -8578,18 +8994,18 @@ function computeContextBudget(diffLines) {
|
|
|
8578
8994
|
return diffLines;
|
|
8579
8995
|
}
|
|
8580
8996
|
function isWithinProject(absPath, projectRoot) {
|
|
8581
|
-
const resolvedRoot =
|
|
8582
|
-
const resolvedPath =
|
|
8583
|
-
return resolvedPath.startsWith(resolvedRoot) || resolvedPath ===
|
|
8997
|
+
const resolvedRoot = path14.resolve(projectRoot) + path14.sep;
|
|
8998
|
+
const resolvedPath = path14.resolve(absPath);
|
|
8999
|
+
return resolvedPath.startsWith(resolvedRoot) || resolvedPath === path14.resolve(projectRoot);
|
|
8584
9000
|
}
|
|
8585
9001
|
async function readContextFile(projectRoot, filePath, reason) {
|
|
8586
|
-
const absPath =
|
|
9002
|
+
const absPath = path14.isAbsolute(filePath) ? filePath : path14.join(projectRoot, filePath);
|
|
8587
9003
|
if (!isWithinProject(absPath, projectRoot)) return null;
|
|
8588
9004
|
const result = await readFileContent(absPath);
|
|
8589
9005
|
if (!result.ok) return null;
|
|
8590
9006
|
const content = result.value;
|
|
8591
9007
|
const lines = content.split("\n").length;
|
|
8592
|
-
const relPath =
|
|
9008
|
+
const relPath = path14.isAbsolute(filePath) ? path14.relative(projectRoot, filePath) : filePath;
|
|
8593
9009
|
return { path: relPath, content, reason, lines };
|
|
8594
9010
|
}
|
|
8595
9011
|
function extractImportSources2(content) {
|
|
@@ -8604,18 +9020,18 @@ function extractImportSources2(content) {
|
|
|
8604
9020
|
}
|
|
8605
9021
|
async function resolveImportPath2(projectRoot, fromFile, importSource) {
|
|
8606
9022
|
if (!importSource.startsWith(".")) return null;
|
|
8607
|
-
const fromDir =
|
|
8608
|
-
const basePath =
|
|
9023
|
+
const fromDir = path14.dirname(path14.join(projectRoot, fromFile));
|
|
9024
|
+
const basePath = path14.resolve(fromDir, importSource);
|
|
8609
9025
|
if (!isWithinProject(basePath, projectRoot)) return null;
|
|
8610
|
-
const relBase =
|
|
9026
|
+
const relBase = path14.relative(projectRoot, basePath);
|
|
8611
9027
|
const candidates = [
|
|
8612
9028
|
relBase + ".ts",
|
|
8613
9029
|
relBase + ".tsx",
|
|
8614
9030
|
relBase + ".mts",
|
|
8615
|
-
|
|
9031
|
+
path14.join(relBase, "index.ts")
|
|
8616
9032
|
];
|
|
8617
9033
|
for (const candidate of candidates) {
|
|
8618
|
-
const absCandidate =
|
|
9034
|
+
const absCandidate = path14.join(projectRoot, candidate);
|
|
8619
9035
|
if (await fileExists(absCandidate)) {
|
|
8620
9036
|
return candidate;
|
|
8621
9037
|
}
|
|
@@ -8623,10 +9039,10 @@ async function resolveImportPath2(projectRoot, fromFile, importSource) {
|
|
|
8623
9039
|
return null;
|
|
8624
9040
|
}
|
|
8625
9041
|
async function findTestFiles(projectRoot, sourceFile) {
|
|
8626
|
-
const baseName =
|
|
9042
|
+
const baseName = path14.basename(sourceFile, path14.extname(sourceFile));
|
|
8627
9043
|
const pattern = `**/${baseName}.{test,spec}.{ts,tsx,mts}`;
|
|
8628
9044
|
const results = await findFiles(pattern, projectRoot);
|
|
8629
|
-
return results.map((f) =>
|
|
9045
|
+
return results.map((f) => path14.relative(projectRoot, f));
|
|
8630
9046
|
}
|
|
8631
9047
|
async function gatherImportContext(projectRoot, changedFiles, budget) {
|
|
8632
9048
|
const contextFiles = [];
|
|
@@ -9414,7 +9830,7 @@ async function fanOutReview(options) {
|
|
|
9414
9830
|
}
|
|
9415
9831
|
|
|
9416
9832
|
// src/review/validate-findings.ts
|
|
9417
|
-
var
|
|
9833
|
+
var path15 = __toESM(require("path"));
|
|
9418
9834
|
var DOWNGRADE_MAP = {
|
|
9419
9835
|
critical: "important",
|
|
9420
9836
|
important: "suggestion",
|
|
@@ -9435,7 +9851,7 @@ function normalizePath(filePath, projectRoot) {
|
|
|
9435
9851
|
let normalized = filePath;
|
|
9436
9852
|
normalized = normalized.replace(/\\/g, "/");
|
|
9437
9853
|
const normalizedRoot = projectRoot.replace(/\\/g, "/");
|
|
9438
|
-
if (
|
|
9854
|
+
if (path15.isAbsolute(normalized)) {
|
|
9439
9855
|
const root = normalizedRoot.endsWith("/") ? normalizedRoot : normalizedRoot + "/";
|
|
9440
9856
|
if (normalized.startsWith(root)) {
|
|
9441
9857
|
normalized = normalized.slice(root.length);
|
|
@@ -9460,12 +9876,12 @@ function followImportChain(fromFile, fileContents, maxDepth = 2) {
|
|
|
9460
9876
|
while ((match = importRegex.exec(content)) !== null) {
|
|
9461
9877
|
const importPath = match[1];
|
|
9462
9878
|
if (!importPath.startsWith(".")) continue;
|
|
9463
|
-
const dir =
|
|
9464
|
-
let resolved =
|
|
9879
|
+
const dir = path15.dirname(current.file);
|
|
9880
|
+
let resolved = path15.join(dir, importPath).replace(/\\/g, "/");
|
|
9465
9881
|
if (!resolved.match(/\.(ts|tsx|js|jsx)$/)) {
|
|
9466
9882
|
resolved += ".ts";
|
|
9467
9883
|
}
|
|
9468
|
-
resolved =
|
|
9884
|
+
resolved = path15.normalize(resolved).replace(/\\/g, "/");
|
|
9469
9885
|
if (!visited.has(resolved) && current.depth + 1 <= maxDepth) {
|
|
9470
9886
|
queue.push({ file: resolved, depth: current.depth + 1 });
|
|
9471
9887
|
}
|
|
@@ -9482,7 +9898,7 @@ async function validateFindings(options) {
|
|
|
9482
9898
|
if (exclusionSet.isExcluded(normalizedFile, finding.lineRange) || exclusionSet.isExcluded(finding.file, finding.lineRange)) {
|
|
9483
9899
|
continue;
|
|
9484
9900
|
}
|
|
9485
|
-
const absoluteFile =
|
|
9901
|
+
const absoluteFile = path15.isAbsolute(finding.file) ? finding.file : path15.join(projectRoot, finding.file).replace(/\\/g, "/");
|
|
9486
9902
|
if (exclusionSet.isExcluded(absoluteFile, finding.lineRange)) {
|
|
9487
9903
|
continue;
|
|
9488
9904
|
}
|
|
@@ -9965,7 +10381,7 @@ async function runReviewPipeline(options) {
|
|
|
9965
10381
|
}
|
|
9966
10382
|
|
|
9967
10383
|
// src/roadmap/parse.ts
|
|
9968
|
-
var
|
|
10384
|
+
var import_types18 = require("@harness-engineering/types");
|
|
9969
10385
|
var VALID_STATUSES = /* @__PURE__ */ new Set([
|
|
9970
10386
|
"backlog",
|
|
9971
10387
|
"planned",
|
|
@@ -9977,14 +10393,14 @@ var EM_DASH = "\u2014";
|
|
|
9977
10393
|
function parseRoadmap(markdown) {
|
|
9978
10394
|
const fmMatch = markdown.match(/^---\n([\s\S]*?)\n---/);
|
|
9979
10395
|
if (!fmMatch) {
|
|
9980
|
-
return (0,
|
|
10396
|
+
return (0, import_types18.Err)(new Error("Missing or malformed YAML frontmatter"));
|
|
9981
10397
|
}
|
|
9982
10398
|
const fmResult = parseFrontmatter(fmMatch[1]);
|
|
9983
10399
|
if (!fmResult.ok) return fmResult;
|
|
9984
10400
|
const body = markdown.slice(fmMatch[0].length);
|
|
9985
10401
|
const milestonesResult = parseMilestones(body);
|
|
9986
10402
|
if (!milestonesResult.ok) return milestonesResult;
|
|
9987
|
-
return (0,
|
|
10403
|
+
return (0, import_types18.Ok)({
|
|
9988
10404
|
frontmatter: fmResult.value,
|
|
9989
10405
|
milestones: milestonesResult.value
|
|
9990
10406
|
});
|
|
@@ -10003,8 +10419,10 @@ function parseFrontmatter(raw) {
|
|
|
10003
10419
|
const versionStr = map.get("version");
|
|
10004
10420
|
const lastSynced = map.get("last_synced");
|
|
10005
10421
|
const lastManualEdit = map.get("last_manual_edit");
|
|
10422
|
+
const created = map.get("created");
|
|
10423
|
+
const updated = map.get("updated");
|
|
10006
10424
|
if (!project || !versionStr || !lastSynced || !lastManualEdit) {
|
|
10007
|
-
return (0,
|
|
10425
|
+
return (0, import_types18.Err)(
|
|
10008
10426
|
new Error(
|
|
10009
10427
|
"Frontmatter missing required fields: project, version, last_synced, last_manual_edit"
|
|
10010
10428
|
)
|
|
@@ -10012,9 +10430,12 @@ function parseFrontmatter(raw) {
|
|
|
10012
10430
|
}
|
|
10013
10431
|
const version = parseInt(versionStr, 10);
|
|
10014
10432
|
if (isNaN(version)) {
|
|
10015
|
-
return (0,
|
|
10433
|
+
return (0, import_types18.Err)(new Error("Frontmatter version must be a number"));
|
|
10016
10434
|
}
|
|
10017
|
-
|
|
10435
|
+
const fm = { project, version, lastSynced, lastManualEdit };
|
|
10436
|
+
if (created) fm.created = created;
|
|
10437
|
+
if (updated) fm.updated = updated;
|
|
10438
|
+
return (0, import_types18.Ok)(fm);
|
|
10018
10439
|
}
|
|
10019
10440
|
function parseMilestones(body) {
|
|
10020
10441
|
const milestones = [];
|
|
@@ -10022,12 +10443,12 @@ function parseMilestones(body) {
|
|
|
10022
10443
|
const h2Matches = [];
|
|
10023
10444
|
let match;
|
|
10024
10445
|
while ((match = h2Pattern.exec(body)) !== null) {
|
|
10025
|
-
h2Matches.push({ heading: match[1], startIndex: match.index });
|
|
10446
|
+
h2Matches.push({ heading: match[1], startIndex: match.index, fullMatch: match[0] });
|
|
10026
10447
|
}
|
|
10027
10448
|
for (let i = 0; i < h2Matches.length; i++) {
|
|
10028
10449
|
const h2 = h2Matches[i];
|
|
10029
10450
|
const nextStart = i + 1 < h2Matches.length ? h2Matches[i + 1].startIndex : body.length;
|
|
10030
|
-
const sectionBody = body.slice(h2.startIndex + h2.
|
|
10451
|
+
const sectionBody = body.slice(h2.startIndex + h2.fullMatch.length, nextStart);
|
|
10031
10452
|
const isBacklog = h2.heading === "Backlog";
|
|
10032
10453
|
const milestoneName = isBacklog ? "Backlog" : h2.heading.replace(/^Milestone:\s*/, "");
|
|
10033
10454
|
const featuresResult = parseFeatures(sectionBody);
|
|
@@ -10038,28 +10459,25 @@ function parseMilestones(body) {
|
|
|
10038
10459
|
features: featuresResult.value
|
|
10039
10460
|
});
|
|
10040
10461
|
}
|
|
10041
|
-
return (0,
|
|
10462
|
+
return (0, import_types18.Ok)(milestones);
|
|
10042
10463
|
}
|
|
10043
10464
|
function parseFeatures(sectionBody) {
|
|
10044
10465
|
const features = [];
|
|
10045
|
-
const h3Pattern = /^### Feature: (.+)$/gm;
|
|
10466
|
+
const h3Pattern = /^### (?:Feature: )?(.+)$/gm;
|
|
10046
10467
|
const h3Matches = [];
|
|
10047
10468
|
let match;
|
|
10048
10469
|
while ((match = h3Pattern.exec(sectionBody)) !== null) {
|
|
10049
|
-
h3Matches.push({ name: match[1], startIndex: match.index });
|
|
10470
|
+
h3Matches.push({ name: match[1], startIndex: match.index, fullMatch: match[0] });
|
|
10050
10471
|
}
|
|
10051
10472
|
for (let i = 0; i < h3Matches.length; i++) {
|
|
10052
10473
|
const h3 = h3Matches[i];
|
|
10053
10474
|
const nextStart = i + 1 < h3Matches.length ? h3Matches[i + 1].startIndex : sectionBody.length;
|
|
10054
|
-
const featureBody = sectionBody.slice(
|
|
10055
|
-
h3.startIndex + `### Feature: ${h3.name}`.length,
|
|
10056
|
-
nextStart
|
|
10057
|
-
);
|
|
10475
|
+
const featureBody = sectionBody.slice(h3.startIndex + h3.fullMatch.length, nextStart);
|
|
10058
10476
|
const featureResult = parseFeatureFields(h3.name, featureBody);
|
|
10059
10477
|
if (!featureResult.ok) return featureResult;
|
|
10060
10478
|
features.push(featureResult.value);
|
|
10061
10479
|
}
|
|
10062
|
-
return (0,
|
|
10480
|
+
return (0, import_types18.Ok)(features);
|
|
10063
10481
|
}
|
|
10064
10482
|
function parseFeatureFields(name, body) {
|
|
10065
10483
|
const fieldMap = /* @__PURE__ */ new Map();
|
|
@@ -10070,7 +10488,7 @@ function parseFeatureFields(name, body) {
|
|
|
10070
10488
|
}
|
|
10071
10489
|
const statusRaw = fieldMap.get("Status");
|
|
10072
10490
|
if (!statusRaw || !VALID_STATUSES.has(statusRaw)) {
|
|
10073
|
-
return (0,
|
|
10491
|
+
return (0, import_types18.Err)(
|
|
10074
10492
|
new Error(
|
|
10075
10493
|
`Feature "${name}" has invalid status: "${statusRaw ?? "(missing)"}". Valid statuses: ${[...VALID_STATUSES].join(", ")}`
|
|
10076
10494
|
)
|
|
@@ -10079,12 +10497,12 @@ function parseFeatureFields(name, body) {
|
|
|
10079
10497
|
const status = statusRaw;
|
|
10080
10498
|
const specRaw = fieldMap.get("Spec") ?? EM_DASH;
|
|
10081
10499
|
const spec = specRaw === EM_DASH ? null : specRaw;
|
|
10082
|
-
const plansRaw = fieldMap.get("Plans") ?? EM_DASH;
|
|
10083
|
-
const plans = plansRaw === EM_DASH ? [] : plansRaw.split(",").map((p) => p.trim());
|
|
10084
|
-
const blockedByRaw = fieldMap.get("Blocked by") ?? EM_DASH;
|
|
10085
|
-
const blockedBy = blockedByRaw === EM_DASH ? [] : blockedByRaw.split(",").map((b) => b.trim());
|
|
10500
|
+
const plansRaw = fieldMap.get("Plans") ?? fieldMap.get("Plan") ?? EM_DASH;
|
|
10501
|
+
const plans = plansRaw === EM_DASH || plansRaw === "none" ? [] : plansRaw.split(",").map((p) => p.trim());
|
|
10502
|
+
const blockedByRaw = fieldMap.get("Blocked by") ?? fieldMap.get("Blockers") ?? EM_DASH;
|
|
10503
|
+
const blockedBy = blockedByRaw === EM_DASH || blockedByRaw === "none" ? [] : blockedByRaw.split(",").map((b) => b.trim());
|
|
10086
10504
|
const summary = fieldMap.get("Summary") ?? "";
|
|
10087
|
-
return (0,
|
|
10505
|
+
return (0, import_types18.Ok)({ name, status, spec, plans, blockedBy, summary });
|
|
10088
10506
|
}
|
|
10089
10507
|
|
|
10090
10508
|
// src/roadmap/serialize.ts
|
|
@@ -10094,11 +10512,17 @@ function serializeRoadmap(roadmap) {
|
|
|
10094
10512
|
lines.push("---");
|
|
10095
10513
|
lines.push(`project: ${roadmap.frontmatter.project}`);
|
|
10096
10514
|
lines.push(`version: ${roadmap.frontmatter.version}`);
|
|
10515
|
+
if (roadmap.frontmatter.created) {
|
|
10516
|
+
lines.push(`created: ${roadmap.frontmatter.created}`);
|
|
10517
|
+
}
|
|
10518
|
+
if (roadmap.frontmatter.updated) {
|
|
10519
|
+
lines.push(`updated: ${roadmap.frontmatter.updated}`);
|
|
10520
|
+
}
|
|
10097
10521
|
lines.push(`last_synced: ${roadmap.frontmatter.lastSynced}`);
|
|
10098
10522
|
lines.push(`last_manual_edit: ${roadmap.frontmatter.lastManualEdit}`);
|
|
10099
10523
|
lines.push("---");
|
|
10100
10524
|
lines.push("");
|
|
10101
|
-
lines.push("#
|
|
10525
|
+
lines.push("# Roadmap");
|
|
10102
10526
|
for (const milestone of roadmap.milestones) {
|
|
10103
10527
|
lines.push("");
|
|
10104
10528
|
lines.push(serializeMilestoneHeading(milestone));
|
|
@@ -10111,26 +10535,27 @@ function serializeRoadmap(roadmap) {
|
|
|
10111
10535
|
return lines.join("\n");
|
|
10112
10536
|
}
|
|
10113
10537
|
function serializeMilestoneHeading(milestone) {
|
|
10114
|
-
return milestone.isBacklog ? "## Backlog" : `##
|
|
10538
|
+
return milestone.isBacklog ? "## Backlog" : `## ${milestone.name}`;
|
|
10115
10539
|
}
|
|
10116
10540
|
function serializeFeature(feature) {
|
|
10117
10541
|
const spec = feature.spec ?? EM_DASH2;
|
|
10118
10542
|
const plans = feature.plans.length > 0 ? feature.plans.join(", ") : EM_DASH2;
|
|
10119
10543
|
const blockedBy = feature.blockedBy.length > 0 ? feature.blockedBy.join(", ") : EM_DASH2;
|
|
10120
10544
|
return [
|
|
10121
|
-
`###
|
|
10545
|
+
`### ${feature.name}`,
|
|
10546
|
+
"",
|
|
10122
10547
|
`- **Status:** ${feature.status}`,
|
|
10123
10548
|
`- **Spec:** ${spec}`,
|
|
10124
|
-
`- **
|
|
10125
|
-
`- **
|
|
10126
|
-
`- **
|
|
10549
|
+
`- **Summary:** ${feature.summary}`,
|
|
10550
|
+
`- **Blockers:** ${blockedBy}`,
|
|
10551
|
+
`- **Plan:** ${plans}`
|
|
10127
10552
|
];
|
|
10128
10553
|
}
|
|
10129
10554
|
|
|
10130
10555
|
// src/roadmap/sync.ts
|
|
10131
|
-
var
|
|
10132
|
-
var
|
|
10133
|
-
var
|
|
10556
|
+
var fs16 = __toESM(require("fs"));
|
|
10557
|
+
var path16 = __toESM(require("path"));
|
|
10558
|
+
var import_types19 = require("@harness-engineering/types");
|
|
10134
10559
|
function inferStatus(feature, projectPath, allFeatures) {
|
|
10135
10560
|
if (feature.blockedBy.length > 0) {
|
|
10136
10561
|
const blockerNotDone = feature.blockedBy.some((blockerName) => {
|
|
@@ -10144,10 +10569,10 @@ function inferStatus(feature, projectPath, allFeatures) {
|
|
|
10144
10569
|
const featuresWithPlans = allFeatures.filter((f) => f.plans.length > 0);
|
|
10145
10570
|
const useRootState = featuresWithPlans.length <= 1;
|
|
10146
10571
|
if (useRootState) {
|
|
10147
|
-
const rootStatePath =
|
|
10148
|
-
if (
|
|
10572
|
+
const rootStatePath = path16.join(projectPath, ".harness", "state.json");
|
|
10573
|
+
if (fs16.existsSync(rootStatePath)) {
|
|
10149
10574
|
try {
|
|
10150
|
-
const raw =
|
|
10575
|
+
const raw = fs16.readFileSync(rootStatePath, "utf-8");
|
|
10151
10576
|
const state = JSON.parse(raw);
|
|
10152
10577
|
if (state.progress) {
|
|
10153
10578
|
for (const status of Object.values(state.progress)) {
|
|
@@ -10158,16 +10583,16 @@ function inferStatus(feature, projectPath, allFeatures) {
|
|
|
10158
10583
|
}
|
|
10159
10584
|
}
|
|
10160
10585
|
}
|
|
10161
|
-
const sessionsDir =
|
|
10162
|
-
if (
|
|
10586
|
+
const sessionsDir = path16.join(projectPath, ".harness", "sessions");
|
|
10587
|
+
if (fs16.existsSync(sessionsDir)) {
|
|
10163
10588
|
try {
|
|
10164
|
-
const sessionDirs =
|
|
10589
|
+
const sessionDirs = fs16.readdirSync(sessionsDir, { withFileTypes: true });
|
|
10165
10590
|
for (const entry of sessionDirs) {
|
|
10166
10591
|
if (!entry.isDirectory()) continue;
|
|
10167
|
-
const autopilotPath =
|
|
10168
|
-
if (!
|
|
10592
|
+
const autopilotPath = path16.join(sessionsDir, entry.name, "autopilot-state.json");
|
|
10593
|
+
if (!fs16.existsSync(autopilotPath)) continue;
|
|
10169
10594
|
try {
|
|
10170
|
-
const raw =
|
|
10595
|
+
const raw = fs16.readFileSync(autopilotPath, "utf-8");
|
|
10171
10596
|
const autopilot = JSON.parse(raw);
|
|
10172
10597
|
if (!autopilot.phases) continue;
|
|
10173
10598
|
const linkedPhases = autopilot.phases.filter(
|
|
@@ -10214,7 +10639,7 @@ function syncRoadmap(options) {
|
|
|
10214
10639
|
to: inferred
|
|
10215
10640
|
});
|
|
10216
10641
|
}
|
|
10217
|
-
return (0,
|
|
10642
|
+
return (0, import_types19.Ok)(changes);
|
|
10218
10643
|
}
|
|
10219
10644
|
|
|
10220
10645
|
// src/interaction/types.ts
|
|
@@ -10247,17 +10672,17 @@ var EmitInteractionInputSchema = import_zod7.z.object({
|
|
|
10247
10672
|
});
|
|
10248
10673
|
|
|
10249
10674
|
// src/blueprint/scanner.ts
|
|
10250
|
-
var
|
|
10251
|
-
var
|
|
10675
|
+
var fs17 = __toESM(require("fs/promises"));
|
|
10676
|
+
var path17 = __toESM(require("path"));
|
|
10252
10677
|
var ProjectScanner = class {
|
|
10253
10678
|
constructor(rootDir) {
|
|
10254
10679
|
this.rootDir = rootDir;
|
|
10255
10680
|
}
|
|
10256
10681
|
async scan() {
|
|
10257
|
-
let projectName =
|
|
10682
|
+
let projectName = path17.basename(this.rootDir);
|
|
10258
10683
|
try {
|
|
10259
|
-
const pkgPath =
|
|
10260
|
-
const pkgRaw = await
|
|
10684
|
+
const pkgPath = path17.join(this.rootDir, "package.json");
|
|
10685
|
+
const pkgRaw = await fs17.readFile(pkgPath, "utf-8");
|
|
10261
10686
|
const pkg = JSON.parse(pkgRaw);
|
|
10262
10687
|
if (pkg.name) projectName = pkg.name;
|
|
10263
10688
|
} catch {
|
|
@@ -10298,8 +10723,8 @@ var ProjectScanner = class {
|
|
|
10298
10723
|
};
|
|
10299
10724
|
|
|
10300
10725
|
// src/blueprint/generator.ts
|
|
10301
|
-
var
|
|
10302
|
-
var
|
|
10726
|
+
var fs18 = __toESM(require("fs/promises"));
|
|
10727
|
+
var path18 = __toESM(require("path"));
|
|
10303
10728
|
var ejs = __toESM(require("ejs"));
|
|
10304
10729
|
|
|
10305
10730
|
// src/blueprint/templates.ts
|
|
@@ -10327,16 +10752,6 @@ var SHELL_TEMPLATE = `
|
|
|
10327
10752
|
<div class="content">
|
|
10328
10753
|
<h3>Code Translation</h3>
|
|
10329
10754
|
<div class="translation"><%- module.content.codeTranslation %></div>
|
|
10330
|
-
<h3>Knowledge Check</h3>
|
|
10331
|
-
<div class="quiz">
|
|
10332
|
-
<% module.content.quiz.questions.forEach((q, i) => { %>
|
|
10333
|
-
<div class="question">
|
|
10334
|
-
<p><%= q.question %></p>
|
|
10335
|
-
<button onclick="reveal(this)">Reveal Answer</button>
|
|
10336
|
-
<p class="answer" style="display:none;"><%= q.answer %></p>
|
|
10337
|
-
</div>
|
|
10338
|
-
<% }) %>
|
|
10339
|
-
</div>
|
|
10340
10755
|
</div>
|
|
10341
10756
|
</article>
|
|
10342
10757
|
|
|
@@ -10355,10 +10770,6 @@ header { border-bottom: 2px solid #eee; margin-bottom: 20px; padding-bottom: 10p
|
|
|
10355
10770
|
.module h2 { margin-top: 0; color: #0066cc; }
|
|
10356
10771
|
`;
|
|
10357
10772
|
var SCRIPTS = `
|
|
10358
|
-
function reveal(btn) {
|
|
10359
|
-
btn.nextElementSibling.style.display = 'block';
|
|
10360
|
-
btn.style.display = 'none';
|
|
10361
|
-
}
|
|
10362
10773
|
console.log('Blueprint player initialized.');
|
|
10363
10774
|
`;
|
|
10364
10775
|
|
|
@@ -10377,20 +10788,8 @@ var ContentPipeline = class {
|
|
|
10377
10788
|
const translation = await llmService.generate(
|
|
10378
10789
|
`You are a technical educator. Explain the following code clearly and concisely: ${codeContext}`
|
|
10379
10790
|
);
|
|
10380
|
-
const quizJson = await llmService.generate(
|
|
10381
|
-
`Create 3 technical quiz questions for this code. Return ONLY valid JSON in this format: { "questions": [{ "question": "...", "answer": "..." }] }. Code: ${codeContext}`
|
|
10382
|
-
);
|
|
10383
|
-
let quiz;
|
|
10384
|
-
try {
|
|
10385
|
-
const cleanJson = quizJson.replace(/```json/g, "").replace(/```/g, "").trim();
|
|
10386
|
-
quiz = JSON.parse(cleanJson);
|
|
10387
|
-
} catch (e) {
|
|
10388
|
-
console.error("Failed to parse quiz JSON", e);
|
|
10389
|
-
quiz = { questions: [{ question: "Failed to generate quiz", answer: "N/A" }] };
|
|
10390
|
-
}
|
|
10391
10791
|
return {
|
|
10392
|
-
codeTranslation: translation
|
|
10393
|
-
quiz
|
|
10792
|
+
codeTranslation: translation
|
|
10394
10793
|
};
|
|
10395
10794
|
}
|
|
10396
10795
|
};
|
|
@@ -10409,19 +10808,19 @@ var BlueprintGenerator = class {
|
|
|
10409
10808
|
styles: STYLES,
|
|
10410
10809
|
scripts: SCRIPTS
|
|
10411
10810
|
});
|
|
10412
|
-
await
|
|
10413
|
-
await
|
|
10811
|
+
await fs18.mkdir(options.outputDir, { recursive: true });
|
|
10812
|
+
await fs18.writeFile(path18.join(options.outputDir, "index.html"), html);
|
|
10414
10813
|
}
|
|
10415
10814
|
};
|
|
10416
10815
|
|
|
10417
10816
|
// src/update-checker.ts
|
|
10418
|
-
var
|
|
10419
|
-
var
|
|
10817
|
+
var fs19 = __toESM(require("fs"));
|
|
10818
|
+
var path19 = __toESM(require("path"));
|
|
10420
10819
|
var os = __toESM(require("os"));
|
|
10421
10820
|
var import_child_process3 = require("child_process");
|
|
10422
10821
|
function getStatePath() {
|
|
10423
10822
|
const home = process.env["HOME"] || os.homedir();
|
|
10424
|
-
return
|
|
10823
|
+
return path19.join(home, ".harness", "update-check.json");
|
|
10425
10824
|
}
|
|
10426
10825
|
function isUpdateCheckEnabled(configInterval) {
|
|
10427
10826
|
if (process.env["HARNESS_NO_UPDATE_CHECK"] === "1") return false;
|
|
@@ -10434,7 +10833,7 @@ function shouldRunCheck(state, intervalMs) {
|
|
|
10434
10833
|
}
|
|
10435
10834
|
function readCheckState() {
|
|
10436
10835
|
try {
|
|
10437
|
-
const raw =
|
|
10836
|
+
const raw = fs19.readFileSync(getStatePath(), "utf-8");
|
|
10438
10837
|
const parsed = JSON.parse(raw);
|
|
10439
10838
|
if (typeof parsed === "object" && parsed !== null && "lastCheckTime" in parsed && typeof parsed.lastCheckTime === "number" && "currentVersion" in parsed && typeof parsed.currentVersion === "string") {
|
|
10440
10839
|
const state = parsed;
|
|
@@ -10451,7 +10850,7 @@ function readCheckState() {
|
|
|
10451
10850
|
}
|
|
10452
10851
|
function spawnBackgroundCheck(currentVersion) {
|
|
10453
10852
|
const statePath = getStatePath();
|
|
10454
|
-
const stateDir =
|
|
10853
|
+
const stateDir = path19.dirname(statePath);
|
|
10455
10854
|
const script = `
|
|
10456
10855
|
const { execSync } = require('child_process');
|
|
10457
10856
|
const fs = require('fs');
|
|
@@ -10583,6 +10982,7 @@ var VERSION = "0.11.0";
|
|
|
10583
10982
|
ViolationSchema,
|
|
10584
10983
|
addProvenance,
|
|
10585
10984
|
analyzeDiff,
|
|
10985
|
+
analyzeLearningPatterns,
|
|
10586
10986
|
appendFailure,
|
|
10587
10987
|
appendLearning,
|
|
10588
10988
|
applyFixes,
|
|
@@ -10591,6 +10991,7 @@ var VERSION = "0.11.0";
|
|
|
10591
10991
|
archModule,
|
|
10592
10992
|
architecture,
|
|
10593
10993
|
archiveFailures,
|
|
10994
|
+
archiveLearnings,
|
|
10594
10995
|
archiveStream,
|
|
10595
10996
|
buildDependencyGraph,
|
|
10596
10997
|
buildExclusionSet,
|
|
@@ -10598,6 +10999,8 @@ var VERSION = "0.11.0";
|
|
|
10598
10999
|
checkDocCoverage,
|
|
10599
11000
|
checkEligibility,
|
|
10600
11001
|
classifyFinding,
|
|
11002
|
+
clearFailuresCache,
|
|
11003
|
+
clearLearningsCache,
|
|
10601
11004
|
configureFeedback,
|
|
10602
11005
|
constraintRuleId,
|
|
10603
11006
|
contextBudget,
|
|
@@ -10653,16 +11056,20 @@ var VERSION = "0.11.0";
|
|
|
10653
11056
|
injectionRules,
|
|
10654
11057
|
isSmallSuggestion,
|
|
10655
11058
|
isUpdateCheckEnabled,
|
|
11059
|
+
listActiveSessions,
|
|
10656
11060
|
listStreams,
|
|
11061
|
+
loadBudgetedLearnings,
|
|
10657
11062
|
loadFailures,
|
|
10658
11063
|
loadHandoff,
|
|
10659
11064
|
loadRelevantLearnings,
|
|
11065
|
+
loadSessionSummary,
|
|
10660
11066
|
loadState,
|
|
10661
11067
|
loadStreamIndex,
|
|
10662
11068
|
logAgentAction,
|
|
10663
11069
|
migrateToStreams,
|
|
10664
11070
|
networkRules,
|
|
10665
11071
|
nodeRules,
|
|
11072
|
+
parseDateFromEntry,
|
|
10666
11073
|
parseDiff,
|
|
10667
11074
|
parseManifest,
|
|
10668
11075
|
parseRoadmap,
|
|
@@ -10670,9 +11077,11 @@ var VERSION = "0.11.0";
|
|
|
10670
11077
|
parseSize,
|
|
10671
11078
|
pathTraversalRules,
|
|
10672
11079
|
previewFix,
|
|
11080
|
+
pruneLearnings,
|
|
10673
11081
|
reactRules,
|
|
10674
11082
|
readCheckState,
|
|
10675
11083
|
readLockfile,
|
|
11084
|
+
removeContributions,
|
|
10676
11085
|
removeProvenance,
|
|
10677
11086
|
requestMultiplePeerReviews,
|
|
10678
11087
|
requestPeerReview,
|
|
@@ -10680,6 +11089,7 @@ var VERSION = "0.11.0";
|
|
|
10680
11089
|
resolveFileToLayer,
|
|
10681
11090
|
resolveModelTier,
|
|
10682
11091
|
resolveRuleSeverity,
|
|
11092
|
+
resolveSessionDir,
|
|
10683
11093
|
resolveStreamPath,
|
|
10684
11094
|
resolveThresholds,
|
|
10685
11095
|
runAll,
|
|
@@ -10706,6 +11116,7 @@ var VERSION = "0.11.0";
|
|
|
10706
11116
|
syncRoadmap,
|
|
10707
11117
|
touchStream,
|
|
10708
11118
|
trackAction,
|
|
11119
|
+
updateSessionIndex,
|
|
10709
11120
|
validateAgentsMap,
|
|
10710
11121
|
validateBoundaries,
|
|
10711
11122
|
validateCommitMessage,
|
|
@@ -10718,6 +11129,7 @@ var VERSION = "0.11.0";
|
|
|
10718
11129
|
violationId,
|
|
10719
11130
|
writeConfig,
|
|
10720
11131
|
writeLockfile,
|
|
11132
|
+
writeSessionSummary,
|
|
10721
11133
|
xssRules,
|
|
10722
11134
|
...require("@harness-engineering/types")
|
|
10723
11135
|
});
|