@harness-engineering/cli 1.11.0 → 1.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/agents/skills/claude-code/harness-autopilot/SKILL.md +57 -9
- package/dist/agents/skills/claude-code/harness-brainstorming/SKILL.md +1 -1
- package/dist/agents/skills/claude-code/harness-code-review/SKILL.md +19 -2
- package/dist/agents/skills/claude-code/harness-execution/SKILL.md +39 -12
- package/dist/agents/skills/claude-code/harness-planning/SKILL.md +28 -11
- package/dist/agents/skills/claude-code/harness-roadmap/SKILL.md +34 -0
- package/dist/agents/skills/claude-code/harness-verification/SKILL.md +42 -0
- package/dist/agents/skills/gemini-cli/harness-autopilot/SKILL.md +57 -9
- package/dist/agents/skills/gemini-cli/harness-brainstorming/SKILL.md +1 -1
- package/dist/agents/skills/gemini-cli/harness-code-review/SKILL.md +19 -2
- package/dist/agents/skills/gemini-cli/harness-execution/SKILL.md +39 -12
- package/dist/agents/skills/gemini-cli/harness-planning/SKILL.md +28 -11
- package/dist/agents/skills/gemini-cli/harness-roadmap/SKILL.md +34 -0
- package/dist/agents/skills/gemini-cli/harness-verification/SKILL.md +42 -0
- package/dist/{agents-md-ZFV6RR5J.js → agents-md-P2RHSUV7.js} +1 -1
- package/dist/{architecture-EXNUMH5R.js → architecture-ESOOE26S.js} +2 -2
- package/dist/bin/harness-mcp.js +10 -10
- package/dist/bin/harness.js +12 -12
- package/dist/{check-phase-gate-VZFOY2PO.js → check-phase-gate-S2MZKLFQ.js} +2 -2
- package/dist/{chunk-GSIVNYVJ.js → chunk-2VU4MFM3.js} +4 -4
- package/dist/{chunk-2NCIKJES.js → chunk-3KOLLWWE.js} +1 -1
- package/dist/{chunk-X3MN5UQJ.js → chunk-5VY23YK3.js} +1 -1
- package/dist/{chunk-I6JZYEGT.js → chunk-7KQSUZVG.js} +96 -50
- package/dist/{chunk-PA2XHK75.js → chunk-7PZWR4LI.js} +3 -3
- package/dist/{chunk-2YSQOUHO.js → chunk-KELT6K6M.js} +662 -283
- package/dist/{chunk-WUJTCNOU.js → chunk-LD3DKUK5.js} +1 -1
- package/dist/{chunk-Z75JC6I2.js → chunk-MACVXDZK.js} +2 -2
- package/dist/{chunk-NC6PXVWT.js → chunk-MI5XJQDY.js} +3 -3
- package/dist/{chunk-WJZDO6OY.js → chunk-PSNN4LWX.js} +2 -2
- package/dist/{chunk-ZWC3MN5E.js → chunk-RZSUJBZZ.js} +765 -203
- package/dist/{chunk-TI4TGEX6.js → chunk-WPPDRIJL.js} +1 -1
- package/dist/{ci-workflow-K5RCRNYR.js → ci-workflow-4NYBUG6R.js} +1 -1
- package/dist/{dist-JVZ2MKBC.js → dist-WF4C7A4A.js} +27 -1
- package/dist/{docs-PWCUVYWU.js → docs-BPYCN2DR.js} +2 -2
- package/dist/{engine-6XUP6GAK.js → engine-LXLIWQQ3.js} +1 -1
- package/dist/{entropy-4I6JEYAC.js → entropy-4VDVV5CR.js} +2 -2
- package/dist/{feedback-TNIW534S.js → feedback-63QB5RCA.js} +1 -1
- package/dist/{generate-agent-definitions-MWKEA5NU.js → generate-agent-definitions-QABOJG56.js} +1 -1
- package/dist/index.d.ts +80 -43
- package/dist/index.js +17 -13
- package/dist/{loader-4FIPIFII.js → loader-Z2IT7QX3.js} +1 -1
- package/dist/{mcp-MOKLYNZL.js → mcp-KQHEL5IF.js} +10 -10
- package/dist/{performance-BTOJCPXU.js → performance-26BH47O4.js} +2 -2
- package/dist/{review-pipeline-3YTW3463.js → review-pipeline-GHR3WFBI.js} +1 -1
- package/dist/{runtime-GO7K2PJE.js → runtime-PDWD7UIK.js} +1 -1
- package/dist/{security-4P2GGFF6.js → security-UQFUZXEN.js} +1 -1
- package/dist/{validate-JN44D2Q7.js → validate-N7QJOKFZ.js} +2 -2
- package/dist/{validate-cross-check-DB7RIFFF.js → validate-cross-check-EDQ5QGTM.js} +1 -1
- package/package.json +4 -4
|
@@ -106,8 +106,8 @@ var ConstraintRuleSchema = z.object({
|
|
|
106
106
|
// forward-compat for governs edges
|
|
107
107
|
});
|
|
108
108
|
function violationId(relativePath, category, normalizedDetail) {
|
|
109
|
-
const
|
|
110
|
-
const input = `${
|
|
109
|
+
const path20 = relativePath.replace(/\\/g, "/");
|
|
110
|
+
const input = `${path20}:${category}:${normalizedDetail}`;
|
|
111
111
|
return createHash("sha256").update(input).digest("hex");
|
|
112
112
|
}
|
|
113
113
|
function constraintRuleId(category, scope, description) {
|
|
@@ -139,17 +139,17 @@ function resolveFileToLayer(file, layers) {
|
|
|
139
139
|
}
|
|
140
140
|
var accessAsync = promisify(access);
|
|
141
141
|
var readFileAsync = promisify(readFile);
|
|
142
|
-
async function fileExists(
|
|
142
|
+
async function fileExists(path20) {
|
|
143
143
|
try {
|
|
144
|
-
await accessAsync(
|
|
144
|
+
await accessAsync(path20, constants.F_OK);
|
|
145
145
|
return true;
|
|
146
146
|
} catch {
|
|
147
147
|
return false;
|
|
148
148
|
}
|
|
149
149
|
}
|
|
150
|
-
async function readFileContent(
|
|
150
|
+
async function readFileContent(path20) {
|
|
151
151
|
try {
|
|
152
|
-
const content = await readFileAsync(
|
|
152
|
+
const content = await readFileAsync(path20, "utf-8");
|
|
153
153
|
return Ok(content);
|
|
154
154
|
} catch (error) {
|
|
155
155
|
return Err(error);
|
|
@@ -1770,31 +1770,45 @@ import * as path from "path";
|
|
|
1770
1770
|
import { appendFileSync, writeFileSync as writeFileSync22, existsSync as existsSync22, mkdirSync as mkdirSync22 } from "fs";
|
|
1771
1771
|
import { dirname as dirname7 } from "path";
|
|
1772
1772
|
import { z as z3 } from "zod";
|
|
1773
|
-
import * as
|
|
1774
|
-
import * as
|
|
1775
|
-
import
|
|
1773
|
+
import * as fs8 from "fs";
|
|
1774
|
+
import * as path5 from "path";
|
|
1775
|
+
import * as fs7 from "fs";
|
|
1776
|
+
import * as path4 from "path";
|
|
1776
1777
|
import * as fs5 from "fs";
|
|
1777
1778
|
import * as path2 from "path";
|
|
1778
1779
|
import { execSync } from "child_process";
|
|
1779
1780
|
import { z as z4 } from "zod";
|
|
1780
|
-
import * as
|
|
1781
|
-
import
|
|
1782
|
-
import * as
|
|
1783
|
-
import * as path4 from "path";
|
|
1784
|
-
import * as path5 from "path";
|
|
1781
|
+
import * as fs6 from "fs";
|
|
1782
|
+
import * as path3 from "path";
|
|
1783
|
+
import * as fs9 from "fs";
|
|
1785
1784
|
import * as path6 from "path";
|
|
1785
|
+
import * as fs10 from "fs";
|
|
1786
1786
|
import * as path7 from "path";
|
|
1787
|
+
import * as fs11 from "fs";
|
|
1787
1788
|
import * as path8 from "path";
|
|
1788
|
-
import * as
|
|
1789
|
+
import * as fs12 from "fs";
|
|
1789
1790
|
import * as path9 from "path";
|
|
1790
|
-
import {
|
|
1791
|
-
import * as
|
|
1791
|
+
import { execSync as execSync2 } from "child_process";
|
|
1792
|
+
import * as fs13 from "fs";
|
|
1792
1793
|
import * as path10 from "path";
|
|
1793
|
-
import * as
|
|
1794
|
+
import * as fs15 from "fs/promises";
|
|
1795
|
+
import { z as z5 } from "zod";
|
|
1796
|
+
import * as fs14 from "fs";
|
|
1794
1797
|
import * as path11 from "path";
|
|
1795
|
-
import * as ejs from "ejs";
|
|
1796
|
-
import * as fs12 from "fs";
|
|
1797
1798
|
import * as path12 from "path";
|
|
1799
|
+
import * as path13 from "path";
|
|
1800
|
+
import * as path14 from "path";
|
|
1801
|
+
import * as path15 from "path";
|
|
1802
|
+
import * as fs16 from "fs";
|
|
1803
|
+
import * as path16 from "path";
|
|
1804
|
+
import { z as z6 } from "zod";
|
|
1805
|
+
import * as fs17 from "fs/promises";
|
|
1806
|
+
import * as path17 from "path";
|
|
1807
|
+
import * as fs18 from "fs/promises";
|
|
1808
|
+
import * as path18 from "path";
|
|
1809
|
+
import * as ejs from "ejs";
|
|
1810
|
+
import * as fs19 from "fs";
|
|
1811
|
+
import * as path19 from "path";
|
|
1798
1812
|
import * as os from "os";
|
|
1799
1813
|
import { spawn } from "child_process";
|
|
1800
1814
|
async function validateFileStructure(projectPath, conventions) {
|
|
@@ -1832,15 +1846,15 @@ function validateConfig(data, schema) {
|
|
|
1832
1846
|
let message = "Configuration validation failed";
|
|
1833
1847
|
const suggestions = [];
|
|
1834
1848
|
if (firstError) {
|
|
1835
|
-
const
|
|
1836
|
-
const pathDisplay =
|
|
1849
|
+
const path20 = firstError.path.join(".");
|
|
1850
|
+
const pathDisplay = path20 ? ` at "${path20}"` : "";
|
|
1837
1851
|
if (firstError.code === "invalid_type") {
|
|
1838
1852
|
const received = firstError.received;
|
|
1839
1853
|
const expected = firstError.expected;
|
|
1840
1854
|
if (received === "undefined") {
|
|
1841
1855
|
code = "MISSING_FIELD";
|
|
1842
1856
|
message = `Missing required field${pathDisplay}: ${firstError.message}`;
|
|
1843
|
-
suggestions.push(`Field "${
|
|
1857
|
+
suggestions.push(`Field "${path20}" is required and must be of type "${expected}"`);
|
|
1844
1858
|
} else {
|
|
1845
1859
|
code = "INVALID_TYPE";
|
|
1846
1860
|
message = `Invalid type${pathDisplay}: ${firstError.message}`;
|
|
@@ -2046,30 +2060,27 @@ function extractSections(content) {
|
|
|
2046
2060
|
return result;
|
|
2047
2061
|
});
|
|
2048
2062
|
}
|
|
2049
|
-
function isExternalLink(
|
|
2050
|
-
return
|
|
2063
|
+
function isExternalLink(path20) {
|
|
2064
|
+
return path20.startsWith("http://") || path20.startsWith("https://") || path20.startsWith("#") || path20.startsWith("mailto:");
|
|
2051
2065
|
}
|
|
2052
2066
|
function resolveLinkPath(linkPath, baseDir) {
|
|
2053
2067
|
return linkPath.startsWith(".") ? join4(baseDir, linkPath) : linkPath;
|
|
2054
2068
|
}
|
|
2055
|
-
async function validateAgentsMap(
|
|
2056
|
-
|
|
2057
|
-
"[harness] validateAgentsMap() is deprecated. Use graph-based validation via Assembler.checkCoverage() from @harness-engineering/graph"
|
|
2058
|
-
);
|
|
2059
|
-
const contentResult = await readFileContent(path13);
|
|
2069
|
+
async function validateAgentsMap(path20 = "./AGENTS.md") {
|
|
2070
|
+
const contentResult = await readFileContent(path20);
|
|
2060
2071
|
if (!contentResult.ok) {
|
|
2061
2072
|
return Err(
|
|
2062
2073
|
createError(
|
|
2063
2074
|
"PARSE_ERROR",
|
|
2064
2075
|
`Failed to read AGENTS.md: ${contentResult.error.message}`,
|
|
2065
|
-
{ path:
|
|
2076
|
+
{ path: path20 },
|
|
2066
2077
|
["Ensure the file exists", "Check file permissions"]
|
|
2067
2078
|
)
|
|
2068
2079
|
);
|
|
2069
2080
|
}
|
|
2070
2081
|
const content = contentResult.value;
|
|
2071
2082
|
const sections = extractSections(content);
|
|
2072
|
-
const baseDir = dirname4(
|
|
2083
|
+
const baseDir = dirname4(path20);
|
|
2073
2084
|
const sectionTitles = sections.map((s) => s.title);
|
|
2074
2085
|
const missingSections = REQUIRED_SECTIONS.filter(
|
|
2075
2086
|
(required) => !sectionTitles.some((title) => title.toLowerCase().includes(required.toLowerCase()))
|
|
@@ -2203,8 +2214,8 @@ async function checkDocCoverage(domain, options = {}) {
|
|
|
2203
2214
|
);
|
|
2204
2215
|
}
|
|
2205
2216
|
}
|
|
2206
|
-
function suggestFix(
|
|
2207
|
-
const targetName = basename2(
|
|
2217
|
+
function suggestFix(path20, existingFiles) {
|
|
2218
|
+
const targetName = basename2(path20).toLowerCase();
|
|
2208
2219
|
const similar = existingFiles.find((file) => {
|
|
2209
2220
|
const fileName = basename2(file).toLowerCase();
|
|
2210
2221
|
return fileName.includes(targetName) || targetName.includes(fileName);
|
|
@@ -2212,12 +2223,9 @@ function suggestFix(path13, existingFiles) {
|
|
|
2212
2223
|
if (similar) {
|
|
2213
2224
|
return `Did you mean "${similar}"?`;
|
|
2214
2225
|
}
|
|
2215
|
-
return `Create the file "${
|
|
2226
|
+
return `Create the file "${path20}" or remove the link`;
|
|
2216
2227
|
}
|
|
2217
2228
|
async function validateKnowledgeMap(rootDir = process.cwd()) {
|
|
2218
|
-
console.warn(
|
|
2219
|
-
"[harness] validateKnowledgeMap() is deprecated. Use graph-based validation via Assembler.checkCoverage() from @harness-engineering/graph"
|
|
2220
|
-
);
|
|
2221
2229
|
const agentsPath = join22(rootDir, "AGENTS.md");
|
|
2222
2230
|
const agentsResult = await validateAgentsMap(agentsPath);
|
|
2223
2231
|
if (!agentsResult.ok) {
|
|
@@ -2558,8 +2566,8 @@ function createBoundaryValidator(schema, name) {
|
|
|
2558
2566
|
return Ok(result.data);
|
|
2559
2567
|
}
|
|
2560
2568
|
const suggestions = result.error.issues.map((issue) => {
|
|
2561
|
-
const
|
|
2562
|
-
return
|
|
2569
|
+
const path20 = issue.path.join(".");
|
|
2570
|
+
return path20 ? `${path20}: ${issue.message}` : issue.message;
|
|
2563
2571
|
});
|
|
2564
2572
|
return Err(
|
|
2565
2573
|
createError(
|
|
@@ -2854,8 +2862,6 @@ function deepMergeConstraints(localConfig, bundleConstraints, _existingContribut
|
|
|
2854
2862
|
}
|
|
2855
2863
|
if (bundleConstraints.architecture) {
|
|
2856
2864
|
const localArch = localConfig.architecture ?? {
|
|
2857
|
-
enabled: true,
|
|
2858
|
-
baselinePath: ".harness/arch/baselines.json",
|
|
2859
2865
|
thresholds: {},
|
|
2860
2866
|
modules: {}
|
|
2861
2867
|
};
|
|
@@ -2975,7 +2981,7 @@ async function readLockfile(lockfilePath) {
|
|
|
2975
2981
|
return { ok: true, value: result.data };
|
|
2976
2982
|
}
|
|
2977
2983
|
async function writeLockfile(lockfilePath, lockfile) {
|
|
2978
|
-
|
|
2984
|
+
return writeConfig(lockfilePath, lockfile);
|
|
2979
2985
|
}
|
|
2980
2986
|
function addProvenance(lockfile, packageName, entry) {
|
|
2981
2987
|
return {
|
|
@@ -3003,6 +3009,75 @@ function removeProvenance(lockfile, packageName) {
|
|
|
3003
3009
|
function isNodeError(err) {
|
|
3004
3010
|
return err instanceof Error && "code" in err;
|
|
3005
3011
|
}
|
|
3012
|
+
function removeContributions(config, contributions) {
|
|
3013
|
+
const result = { ...config };
|
|
3014
|
+
const layerNames = contributions.layers;
|
|
3015
|
+
if (layerNames && layerNames.length > 0 && Array.isArray(result.layers)) {
|
|
3016
|
+
const nameSet = new Set(layerNames);
|
|
3017
|
+
result.layers = result.layers.filter((l) => !nameSet.has(l.name));
|
|
3018
|
+
}
|
|
3019
|
+
const fromKeys = contributions.forbiddenImports;
|
|
3020
|
+
if (fromKeys && fromKeys.length > 0 && Array.isArray(result.forbiddenImports)) {
|
|
3021
|
+
const fromSet = new Set(fromKeys);
|
|
3022
|
+
result.forbiddenImports = result.forbiddenImports.filter(
|
|
3023
|
+
(r) => !fromSet.has(r.from)
|
|
3024
|
+
);
|
|
3025
|
+
}
|
|
3026
|
+
const boundarySchemas = contributions.boundaries;
|
|
3027
|
+
if (boundarySchemas && boundarySchemas.length > 0 && result.boundaries) {
|
|
3028
|
+
const boundaries = result.boundaries;
|
|
3029
|
+
if (boundaries.requireSchema) {
|
|
3030
|
+
const schemaSet = new Set(boundarySchemas);
|
|
3031
|
+
result.boundaries = {
|
|
3032
|
+
...boundaries,
|
|
3033
|
+
requireSchema: boundaries.requireSchema.filter((s) => !schemaSet.has(s))
|
|
3034
|
+
};
|
|
3035
|
+
}
|
|
3036
|
+
}
|
|
3037
|
+
const thresholdKeys = contributions["architecture.thresholds"];
|
|
3038
|
+
if (thresholdKeys && thresholdKeys.length > 0 && result.architecture) {
|
|
3039
|
+
const arch = { ...result.architecture };
|
|
3040
|
+
const thresholds = { ...arch.thresholds };
|
|
3041
|
+
for (const key of thresholdKeys) {
|
|
3042
|
+
delete thresholds[key];
|
|
3043
|
+
}
|
|
3044
|
+
arch.thresholds = thresholds;
|
|
3045
|
+
result.architecture = arch;
|
|
3046
|
+
}
|
|
3047
|
+
const moduleKeys = contributions["architecture.modules"];
|
|
3048
|
+
if (moduleKeys && moduleKeys.length > 0 && result.architecture) {
|
|
3049
|
+
const arch = { ...result.architecture };
|
|
3050
|
+
const modules = { ...arch.modules };
|
|
3051
|
+
for (const key of moduleKeys) {
|
|
3052
|
+
const colonIdx = key.indexOf(":");
|
|
3053
|
+
if (colonIdx === -1) continue;
|
|
3054
|
+
const modulePath = key.substring(0, colonIdx);
|
|
3055
|
+
const category = key.substring(colonIdx + 1);
|
|
3056
|
+
if (modules[modulePath]) {
|
|
3057
|
+
const moduleCategories = { ...modules[modulePath] };
|
|
3058
|
+
delete moduleCategories[category];
|
|
3059
|
+
if (Object.keys(moduleCategories).length === 0) {
|
|
3060
|
+
delete modules[modulePath];
|
|
3061
|
+
} else {
|
|
3062
|
+
modules[modulePath] = moduleCategories;
|
|
3063
|
+
}
|
|
3064
|
+
}
|
|
3065
|
+
}
|
|
3066
|
+
arch.modules = modules;
|
|
3067
|
+
result.architecture = arch;
|
|
3068
|
+
}
|
|
3069
|
+
const ruleIds = contributions["security.rules"];
|
|
3070
|
+
if (ruleIds && ruleIds.length > 0 && result.security) {
|
|
3071
|
+
const security = { ...result.security };
|
|
3072
|
+
const rules = { ...security.rules };
|
|
3073
|
+
for (const id of ruleIds) {
|
|
3074
|
+
delete rules[id];
|
|
3075
|
+
}
|
|
3076
|
+
security.rules = rules;
|
|
3077
|
+
result.security = security;
|
|
3078
|
+
}
|
|
3079
|
+
return result;
|
|
3080
|
+
}
|
|
3006
3081
|
function createParseError(code, message, details = {}, suggestions = []) {
|
|
3007
3082
|
return { code, message, details, suggestions };
|
|
3008
3083
|
}
|
|
@@ -3022,11 +3097,11 @@ function walk(node, visitor) {
|
|
|
3022
3097
|
var TypeScriptParser = class {
|
|
3023
3098
|
name = "typescript";
|
|
3024
3099
|
extensions = [".ts", ".tsx", ".mts", ".cts"];
|
|
3025
|
-
async parseFile(
|
|
3026
|
-
const contentResult = await readFileContent(
|
|
3100
|
+
async parseFile(path20) {
|
|
3101
|
+
const contentResult = await readFileContent(path20);
|
|
3027
3102
|
if (!contentResult.ok) {
|
|
3028
3103
|
return Err(
|
|
3029
|
-
createParseError("NOT_FOUND", `File not found: ${
|
|
3104
|
+
createParseError("NOT_FOUND", `File not found: ${path20}`, { path: path20 }, [
|
|
3030
3105
|
"Check that the file exists",
|
|
3031
3106
|
"Verify the path is correct"
|
|
3032
3107
|
])
|
|
@@ -3036,7 +3111,7 @@ var TypeScriptParser = class {
|
|
|
3036
3111
|
const ast = parse(contentResult.value, {
|
|
3037
3112
|
loc: true,
|
|
3038
3113
|
range: true,
|
|
3039
|
-
jsx:
|
|
3114
|
+
jsx: path20.endsWith(".tsx"),
|
|
3040
3115
|
errorOnUnknownASTType: false
|
|
3041
3116
|
});
|
|
3042
3117
|
return Ok({
|
|
@@ -3047,7 +3122,7 @@ var TypeScriptParser = class {
|
|
|
3047
3122
|
} catch (e) {
|
|
3048
3123
|
const error = e;
|
|
3049
3124
|
return Err(
|
|
3050
|
-
createParseError("SYNTAX_ERROR", `Failed to parse ${
|
|
3125
|
+
createParseError("SYNTAX_ERROR", `Failed to parse ${path20}: ${error.message}`, { path: path20 }, [
|
|
3051
3126
|
"Check for syntax errors in the file",
|
|
3052
3127
|
"Ensure valid TypeScript syntax"
|
|
3053
3128
|
])
|
|
@@ -3327,22 +3402,22 @@ function extractInlineRefs(content) {
|
|
|
3327
3402
|
}
|
|
3328
3403
|
return refs;
|
|
3329
3404
|
}
|
|
3330
|
-
async function parseDocumentationFile(
|
|
3331
|
-
const contentResult = await readFileContent(
|
|
3405
|
+
async function parseDocumentationFile(path20) {
|
|
3406
|
+
const contentResult = await readFileContent(path20);
|
|
3332
3407
|
if (!contentResult.ok) {
|
|
3333
3408
|
return Err(
|
|
3334
3409
|
createEntropyError(
|
|
3335
3410
|
"PARSE_ERROR",
|
|
3336
|
-
`Failed to read documentation file: ${
|
|
3337
|
-
{ file:
|
|
3411
|
+
`Failed to read documentation file: ${path20}`,
|
|
3412
|
+
{ file: path20 },
|
|
3338
3413
|
["Check that the file exists"]
|
|
3339
3414
|
)
|
|
3340
3415
|
);
|
|
3341
3416
|
}
|
|
3342
3417
|
const content = contentResult.value;
|
|
3343
|
-
const type =
|
|
3418
|
+
const type = path20.endsWith(".md") ? "markdown" : "text";
|
|
3344
3419
|
return Ok({
|
|
3345
|
-
path:
|
|
3420
|
+
path: path20,
|
|
3346
3421
|
type,
|
|
3347
3422
|
content,
|
|
3348
3423
|
codeBlocks: extractCodeBlocks(content),
|
|
@@ -4143,15 +4218,34 @@ async function detectPatternViolations(snapshot, config) {
|
|
|
4143
4218
|
}
|
|
4144
4219
|
}
|
|
4145
4220
|
}
|
|
4221
|
+
if (config?.customPatterns) {
|
|
4222
|
+
for (const file of snapshot.files) {
|
|
4223
|
+
for (const custom of config.customPatterns) {
|
|
4224
|
+
const matches = custom.check(file, snapshot);
|
|
4225
|
+
for (const match of matches) {
|
|
4226
|
+
violations.push({
|
|
4227
|
+
pattern: custom.name,
|
|
4228
|
+
file: file.path,
|
|
4229
|
+
line: match.line,
|
|
4230
|
+
message: match.message,
|
|
4231
|
+
suggestion: match.suggestion || "Review and fix this pattern violation",
|
|
4232
|
+
severity: custom.severity
|
|
4233
|
+
});
|
|
4234
|
+
}
|
|
4235
|
+
}
|
|
4236
|
+
}
|
|
4237
|
+
}
|
|
4146
4238
|
const errorCount = violations.filter((v) => v.severity === "error").length;
|
|
4147
4239
|
const warningCount = violations.filter((v) => v.severity === "warning").length;
|
|
4148
|
-
const
|
|
4149
|
-
const
|
|
4240
|
+
const customCount = config?.customPatterns?.length ?? 0;
|
|
4241
|
+
const allPatternsCount = patterns.length + customCount;
|
|
4242
|
+
const totalChecks = snapshot.files.length * allPatternsCount;
|
|
4243
|
+
const passRate = totalChecks > 0 ? Math.max(0, (totalChecks - violations.length) / totalChecks) : 1;
|
|
4150
4244
|
return Ok({
|
|
4151
4245
|
violations,
|
|
4152
4246
|
stats: {
|
|
4153
4247
|
filesChecked: snapshot.files.length,
|
|
4154
|
-
patternsApplied:
|
|
4248
|
+
patternsApplied: allPatternsCount,
|
|
4155
4249
|
violationCount: violations.length,
|
|
4156
4250
|
errorCount,
|
|
4157
4251
|
warningCount
|
|
@@ -6282,8 +6376,16 @@ var DEFAULT_STREAM_INDEX = {
|
|
|
6282
6376
|
streams: {}
|
|
6283
6377
|
};
|
|
6284
6378
|
var HARNESS_DIR = ".harness";
|
|
6285
|
-
var
|
|
6379
|
+
var STATE_FILE = "state.json";
|
|
6380
|
+
var LEARNINGS_FILE = "learnings.md";
|
|
6381
|
+
var FAILURES_FILE = "failures.md";
|
|
6382
|
+
var HANDOFF_FILE = "handoff.json";
|
|
6383
|
+
var GATE_CONFIG_FILE = "gate.json";
|
|
6286
6384
|
var INDEX_FILE = "index.json";
|
|
6385
|
+
var SESSIONS_DIR = "sessions";
|
|
6386
|
+
var SESSION_INDEX_FILE = "index.md";
|
|
6387
|
+
var SUMMARY_FILE = "summary.md";
|
|
6388
|
+
var STREAMS_DIR = "streams";
|
|
6287
6389
|
var STREAM_NAME_REGEX = /^[a-z0-9][a-z0-9._-]*$/;
|
|
6288
6390
|
function streamsDir(projectPath) {
|
|
6289
6391
|
return path2.join(projectPath, HARNESS_DIR, STREAMS_DIR);
|
|
@@ -6509,25 +6611,60 @@ async function migrateToStreams(projectPath) {
|
|
|
6509
6611
|
};
|
|
6510
6612
|
return saveStreamIndex(projectPath, index);
|
|
6511
6613
|
}
|
|
6512
|
-
|
|
6513
|
-
|
|
6514
|
-
|
|
6515
|
-
|
|
6516
|
-
|
|
6517
|
-
|
|
6518
|
-
|
|
6614
|
+
function resolveSessionDir(projectPath, sessionSlug, options) {
|
|
6615
|
+
if (!sessionSlug || sessionSlug.trim() === "") {
|
|
6616
|
+
return Err(new Error("Session slug must not be empty"));
|
|
6617
|
+
}
|
|
6618
|
+
if (sessionSlug.includes("..") || sessionSlug.includes("/") || sessionSlug.includes("\\")) {
|
|
6619
|
+
return Err(
|
|
6620
|
+
new Error(`Invalid session slug '${sessionSlug}': must not contain path traversal characters`)
|
|
6621
|
+
);
|
|
6622
|
+
}
|
|
6623
|
+
const sessionDir = path3.join(projectPath, HARNESS_DIR, SESSIONS_DIR, sessionSlug);
|
|
6624
|
+
if (options?.create) {
|
|
6625
|
+
fs6.mkdirSync(sessionDir, { recursive: true });
|
|
6626
|
+
}
|
|
6627
|
+
return Ok(sessionDir);
|
|
6628
|
+
}
|
|
6629
|
+
function updateSessionIndex(projectPath, sessionSlug, description) {
|
|
6630
|
+
const sessionsDir = path3.join(projectPath, HARNESS_DIR, SESSIONS_DIR);
|
|
6631
|
+
fs6.mkdirSync(sessionsDir, { recursive: true });
|
|
6632
|
+
const indexPath2 = path3.join(sessionsDir, SESSION_INDEX_FILE);
|
|
6633
|
+
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
6634
|
+
const newLine = `- [${sessionSlug}](${sessionSlug}/summary.md) \u2014 ${description} (${date})`;
|
|
6635
|
+
if (!fs6.existsSync(indexPath2)) {
|
|
6636
|
+
fs6.writeFileSync(indexPath2, `## Active Sessions
|
|
6637
|
+
|
|
6638
|
+
${newLine}
|
|
6639
|
+
`);
|
|
6640
|
+
return;
|
|
6641
|
+
}
|
|
6642
|
+
const content = fs6.readFileSync(indexPath2, "utf-8");
|
|
6643
|
+
const lines = content.split("\n");
|
|
6644
|
+
const slugPattern = `- [${sessionSlug}]`;
|
|
6645
|
+
const existingIdx = lines.findIndex((l) => l.startsWith(slugPattern));
|
|
6646
|
+
if (existingIdx >= 0) {
|
|
6647
|
+
lines[existingIdx] = newLine;
|
|
6648
|
+
} else {
|
|
6649
|
+
const lastNonEmpty = lines.reduce((last, line, i) => line.trim() !== "" ? i : last, 0);
|
|
6650
|
+
lines.splice(lastNonEmpty + 1, 0, newLine);
|
|
6651
|
+
}
|
|
6652
|
+
fs6.writeFileSync(indexPath2, lines.join("\n"));
|
|
6653
|
+
}
|
|
6519
6654
|
var MAX_CACHE_ENTRIES = 8;
|
|
6520
|
-
var learningsCacheMap = /* @__PURE__ */ new Map();
|
|
6521
|
-
var failuresCacheMap = /* @__PURE__ */ new Map();
|
|
6522
6655
|
function evictIfNeeded(map) {
|
|
6523
6656
|
if (map.size > MAX_CACHE_ENTRIES) {
|
|
6524
6657
|
const oldest = map.keys().next().value;
|
|
6525
6658
|
if (oldest !== void 0) map.delete(oldest);
|
|
6526
6659
|
}
|
|
6527
6660
|
}
|
|
6528
|
-
async function getStateDir(projectPath, stream) {
|
|
6529
|
-
|
|
6530
|
-
|
|
6661
|
+
async function getStateDir(projectPath, stream, session) {
|
|
6662
|
+
if (session) {
|
|
6663
|
+
const sessionResult = resolveSessionDir(projectPath, session, { create: true });
|
|
6664
|
+
return sessionResult;
|
|
6665
|
+
}
|
|
6666
|
+
const streamsIndexPath = path4.join(projectPath, HARNESS_DIR, "streams", INDEX_FILE);
|
|
6667
|
+
const hasStreams = fs7.existsSync(streamsIndexPath);
|
|
6531
6668
|
if (stream || hasStreams) {
|
|
6532
6669
|
const result = await resolveStreamPath(projectPath, stream ? { stream } : void 0);
|
|
6533
6670
|
if (result.ok) {
|
|
@@ -6537,18 +6674,18 @@ async function getStateDir(projectPath, stream) {
|
|
|
6537
6674
|
return result;
|
|
6538
6675
|
}
|
|
6539
6676
|
}
|
|
6540
|
-
return Ok(
|
|
6677
|
+
return Ok(path4.join(projectPath, HARNESS_DIR));
|
|
6541
6678
|
}
|
|
6542
|
-
async function loadState(projectPath, stream) {
|
|
6679
|
+
async function loadState(projectPath, stream, session) {
|
|
6543
6680
|
try {
|
|
6544
|
-
const dirResult = await getStateDir(projectPath, stream);
|
|
6681
|
+
const dirResult = await getStateDir(projectPath, stream, session);
|
|
6545
6682
|
if (!dirResult.ok) return dirResult;
|
|
6546
6683
|
const stateDir = dirResult.value;
|
|
6547
|
-
const statePath =
|
|
6548
|
-
if (!
|
|
6684
|
+
const statePath = path5.join(stateDir, STATE_FILE);
|
|
6685
|
+
if (!fs8.existsSync(statePath)) {
|
|
6549
6686
|
return Ok({ ...DEFAULT_STATE });
|
|
6550
6687
|
}
|
|
6551
|
-
const raw =
|
|
6688
|
+
const raw = fs8.readFileSync(statePath, "utf-8");
|
|
6552
6689
|
const parsed = JSON.parse(raw);
|
|
6553
6690
|
const result = HarnessStateSchema.safeParse(parsed);
|
|
6554
6691
|
if (!result.success) {
|
|
@@ -6561,14 +6698,14 @@ async function loadState(projectPath, stream) {
|
|
|
6561
6698
|
);
|
|
6562
6699
|
}
|
|
6563
6700
|
}
|
|
6564
|
-
async function saveState(projectPath, state, stream) {
|
|
6701
|
+
async function saveState(projectPath, state, stream, session) {
|
|
6565
6702
|
try {
|
|
6566
|
-
const dirResult = await getStateDir(projectPath, stream);
|
|
6703
|
+
const dirResult = await getStateDir(projectPath, stream, session);
|
|
6567
6704
|
if (!dirResult.ok) return dirResult;
|
|
6568
6705
|
const stateDir = dirResult.value;
|
|
6569
|
-
const statePath =
|
|
6570
|
-
|
|
6571
|
-
|
|
6706
|
+
const statePath = path5.join(stateDir, STATE_FILE);
|
|
6707
|
+
fs8.mkdirSync(stateDir, { recursive: true });
|
|
6708
|
+
fs8.writeFileSync(statePath, JSON.stringify(state, null, 2));
|
|
6572
6709
|
return Ok(void 0);
|
|
6573
6710
|
} catch (error) {
|
|
6574
6711
|
return Err(
|
|
@@ -6576,13 +6713,17 @@ async function saveState(projectPath, state, stream) {
|
|
|
6576
6713
|
);
|
|
6577
6714
|
}
|
|
6578
6715
|
}
|
|
6579
|
-
|
|
6716
|
+
var learningsCacheMap = /* @__PURE__ */ new Map();
|
|
6717
|
+
function clearLearningsCache() {
|
|
6718
|
+
learningsCacheMap.clear();
|
|
6719
|
+
}
|
|
6720
|
+
async function appendLearning(projectPath, learning, skillName, outcome, stream, session) {
|
|
6580
6721
|
try {
|
|
6581
|
-
const dirResult = await getStateDir(projectPath, stream);
|
|
6722
|
+
const dirResult = await getStateDir(projectPath, stream, session);
|
|
6582
6723
|
if (!dirResult.ok) return dirResult;
|
|
6583
6724
|
const stateDir = dirResult.value;
|
|
6584
|
-
const learningsPath =
|
|
6585
|
-
|
|
6725
|
+
const learningsPath = path6.join(stateDir, LEARNINGS_FILE);
|
|
6726
|
+
fs9.mkdirSync(stateDir, { recursive: true });
|
|
6586
6727
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
6587
6728
|
let entry;
|
|
6588
6729
|
if (skillName && outcome) {
|
|
@@ -6598,11 +6739,11 @@ async function appendLearning(projectPath, learning, skillName, outcome, stream)
|
|
|
6598
6739
|
- **${timestamp}:** ${learning}
|
|
6599
6740
|
`;
|
|
6600
6741
|
}
|
|
6601
|
-
if (!
|
|
6602
|
-
|
|
6742
|
+
if (!fs9.existsSync(learningsPath)) {
|
|
6743
|
+
fs9.writeFileSync(learningsPath, `# Learnings
|
|
6603
6744
|
${entry}`);
|
|
6604
6745
|
} else {
|
|
6605
|
-
|
|
6746
|
+
fs9.appendFileSync(learningsPath, entry);
|
|
6606
6747
|
}
|
|
6607
6748
|
learningsCacheMap.delete(learningsPath);
|
|
6608
6749
|
return Ok(void 0);
|
|
@@ -6614,23 +6755,92 @@ ${entry}`);
|
|
|
6614
6755
|
);
|
|
6615
6756
|
}
|
|
6616
6757
|
}
|
|
6617
|
-
|
|
6758
|
+
function estimateTokens(text) {
|
|
6759
|
+
return Math.ceil(text.length / 4);
|
|
6760
|
+
}
|
|
6761
|
+
function scoreRelevance(entry, intent) {
|
|
6762
|
+
if (!intent || intent.trim() === "") return 0;
|
|
6763
|
+
const intentWords = intent.toLowerCase().split(/\s+/).filter((w) => w.length > 2);
|
|
6764
|
+
if (intentWords.length === 0) return 0;
|
|
6765
|
+
const entryLower = entry.toLowerCase();
|
|
6766
|
+
const matches = intentWords.filter((word) => entryLower.includes(word));
|
|
6767
|
+
return matches.length / intentWords.length;
|
|
6768
|
+
}
|
|
6769
|
+
function parseDateFromEntry(entry) {
|
|
6770
|
+
const match = entry.match(/(\d{4}-\d{2}-\d{2})/);
|
|
6771
|
+
return match ? match[1] ?? null : null;
|
|
6772
|
+
}
|
|
6773
|
+
function analyzeLearningPatterns(entries) {
|
|
6774
|
+
const tagGroups = /* @__PURE__ */ new Map();
|
|
6775
|
+
for (const entry of entries) {
|
|
6776
|
+
const tagMatches = entry.matchAll(/\[(skill:[^\]]+)\]|\[(outcome:[^\]]+)\]/g);
|
|
6777
|
+
for (const match of tagMatches) {
|
|
6778
|
+
const tag = match[1] ?? match[2];
|
|
6779
|
+
if (tag) {
|
|
6780
|
+
const group = tagGroups.get(tag) ?? [];
|
|
6781
|
+
group.push(entry);
|
|
6782
|
+
tagGroups.set(tag, group);
|
|
6783
|
+
}
|
|
6784
|
+
}
|
|
6785
|
+
}
|
|
6786
|
+
const patterns = [];
|
|
6787
|
+
for (const [tag, groupEntries] of tagGroups) {
|
|
6788
|
+
if (groupEntries.length >= 3) {
|
|
6789
|
+
patterns.push({ tag, count: groupEntries.length, entries: groupEntries });
|
|
6790
|
+
}
|
|
6791
|
+
}
|
|
6792
|
+
return patterns.sort((a, b) => b.count - a.count);
|
|
6793
|
+
}
|
|
6794
|
+
async function loadBudgetedLearnings(projectPath, options) {
|
|
6795
|
+
const { intent, tokenBudget = 1e3, skill, session, stream } = options;
|
|
6796
|
+
const sortByRecencyAndRelevance = (entries) => {
|
|
6797
|
+
return [...entries].sort((a, b) => {
|
|
6798
|
+
const dateA = parseDateFromEntry(a) ?? "0000-00-00";
|
|
6799
|
+
const dateB = parseDateFromEntry(b) ?? "0000-00-00";
|
|
6800
|
+
const dateCompare = dateB.localeCompare(dateA);
|
|
6801
|
+
if (dateCompare !== 0) return dateCompare;
|
|
6802
|
+
return scoreRelevance(b, intent) - scoreRelevance(a, intent);
|
|
6803
|
+
});
|
|
6804
|
+
};
|
|
6805
|
+
const allEntries = [];
|
|
6806
|
+
if (session) {
|
|
6807
|
+
const sessionResult = await loadRelevantLearnings(projectPath, skill, stream, session);
|
|
6808
|
+
if (sessionResult.ok) {
|
|
6809
|
+
allEntries.push(...sortByRecencyAndRelevance(sessionResult.value));
|
|
6810
|
+
}
|
|
6811
|
+
}
|
|
6812
|
+
const globalResult = await loadRelevantLearnings(projectPath, skill, stream);
|
|
6813
|
+
if (globalResult.ok) {
|
|
6814
|
+
allEntries.push(...sortByRecencyAndRelevance(globalResult.value));
|
|
6815
|
+
}
|
|
6816
|
+
const budgeted = [];
|
|
6817
|
+
let totalTokens = 0;
|
|
6818
|
+
for (const entry of allEntries) {
|
|
6819
|
+
const separator = budgeted.length > 0 ? "\n" : "";
|
|
6820
|
+
const entryCost = estimateTokens(entry + separator);
|
|
6821
|
+
if (totalTokens + entryCost > tokenBudget) break;
|
|
6822
|
+
budgeted.push(entry);
|
|
6823
|
+
totalTokens += entryCost;
|
|
6824
|
+
}
|
|
6825
|
+
return Ok(budgeted);
|
|
6826
|
+
}
|
|
6827
|
+
async function loadRelevantLearnings(projectPath, skillName, stream, session) {
|
|
6618
6828
|
try {
|
|
6619
|
-
const dirResult = await getStateDir(projectPath, stream);
|
|
6829
|
+
const dirResult = await getStateDir(projectPath, stream, session);
|
|
6620
6830
|
if (!dirResult.ok) return dirResult;
|
|
6621
6831
|
const stateDir = dirResult.value;
|
|
6622
|
-
const learningsPath =
|
|
6623
|
-
if (!
|
|
6832
|
+
const learningsPath = path6.join(stateDir, LEARNINGS_FILE);
|
|
6833
|
+
if (!fs9.existsSync(learningsPath)) {
|
|
6624
6834
|
return Ok([]);
|
|
6625
6835
|
}
|
|
6626
|
-
const stats =
|
|
6836
|
+
const stats = fs9.statSync(learningsPath);
|
|
6627
6837
|
const cacheKey = learningsPath;
|
|
6628
6838
|
const cached = learningsCacheMap.get(cacheKey);
|
|
6629
6839
|
let entries;
|
|
6630
6840
|
if (cached && cached.mtimeMs === stats.mtimeMs) {
|
|
6631
6841
|
entries = cached.entries;
|
|
6632
6842
|
} else {
|
|
6633
|
-
const content =
|
|
6843
|
+
const content = fs9.readFileSync(learningsPath, "utf-8");
|
|
6634
6844
|
const lines = content.split("\n");
|
|
6635
6845
|
entries = [];
|
|
6636
6846
|
let currentBlock = [];
|
|
@@ -6666,23 +6876,106 @@ async function loadRelevantLearnings(projectPath, skillName, stream) {
|
|
|
6666
6876
|
);
|
|
6667
6877
|
}
|
|
6668
6878
|
}
|
|
6669
|
-
|
|
6670
|
-
|
|
6879
|
+
async function archiveLearnings(projectPath, entries, stream) {
|
|
6880
|
+
try {
|
|
6881
|
+
const dirResult = await getStateDir(projectPath, stream);
|
|
6882
|
+
if (!dirResult.ok) return dirResult;
|
|
6883
|
+
const stateDir = dirResult.value;
|
|
6884
|
+
const archiveDir = path6.join(stateDir, "learnings-archive");
|
|
6885
|
+
fs9.mkdirSync(archiveDir, { recursive: true });
|
|
6886
|
+
const now = /* @__PURE__ */ new Date();
|
|
6887
|
+
const yearMonth = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`;
|
|
6888
|
+
const archivePath = path6.join(archiveDir, `${yearMonth}.md`);
|
|
6889
|
+
const archiveContent = entries.join("\n\n") + "\n";
|
|
6890
|
+
if (fs9.existsSync(archivePath)) {
|
|
6891
|
+
fs9.appendFileSync(archivePath, "\n" + archiveContent);
|
|
6892
|
+
} else {
|
|
6893
|
+
fs9.writeFileSync(archivePath, `# Learnings Archive
|
|
6894
|
+
|
|
6895
|
+
${archiveContent}`);
|
|
6896
|
+
}
|
|
6897
|
+
return Ok(void 0);
|
|
6898
|
+
} catch (error) {
|
|
6899
|
+
return Err(
|
|
6900
|
+
new Error(
|
|
6901
|
+
`Failed to archive learnings: ${error instanceof Error ? error.message : String(error)}`
|
|
6902
|
+
)
|
|
6903
|
+
);
|
|
6904
|
+
}
|
|
6905
|
+
}
|
|
6906
|
+
async function pruneLearnings(projectPath, stream) {
|
|
6671
6907
|
try {
|
|
6672
6908
|
const dirResult = await getStateDir(projectPath, stream);
|
|
6673
6909
|
if (!dirResult.ok) return dirResult;
|
|
6674
6910
|
const stateDir = dirResult.value;
|
|
6675
|
-
const
|
|
6676
|
-
|
|
6911
|
+
const learningsPath = path6.join(stateDir, LEARNINGS_FILE);
|
|
6912
|
+
if (!fs9.existsSync(learningsPath)) {
|
|
6913
|
+
return Ok({ kept: 0, archived: 0, patterns: [] });
|
|
6914
|
+
}
|
|
6915
|
+
const loadResult = await loadRelevantLearnings(projectPath, void 0, stream);
|
|
6916
|
+
if (!loadResult.ok) return loadResult;
|
|
6917
|
+
const allEntries = loadResult.value;
|
|
6918
|
+
if (allEntries.length <= 20) {
|
|
6919
|
+
const cutoffDate = /* @__PURE__ */ new Date();
|
|
6920
|
+
cutoffDate.setDate(cutoffDate.getDate() - 14);
|
|
6921
|
+
const cutoffStr = cutoffDate.toISOString().split("T")[0];
|
|
6922
|
+
const hasOld = allEntries.some((entry) => {
|
|
6923
|
+
const date = parseDateFromEntry(entry);
|
|
6924
|
+
return date !== null && date < cutoffStr;
|
|
6925
|
+
});
|
|
6926
|
+
if (!hasOld) {
|
|
6927
|
+
return Ok({ kept: allEntries.length, archived: 0, patterns: [] });
|
|
6928
|
+
}
|
|
6929
|
+
}
|
|
6930
|
+
const sorted = [...allEntries].sort((a, b) => {
|
|
6931
|
+
const dateA = parseDateFromEntry(a) ?? "0000-00-00";
|
|
6932
|
+
const dateB = parseDateFromEntry(b) ?? "0000-00-00";
|
|
6933
|
+
return dateB.localeCompare(dateA);
|
|
6934
|
+
});
|
|
6935
|
+
const toKeep = sorted.slice(0, 20);
|
|
6936
|
+
const toArchive = sorted.slice(20);
|
|
6937
|
+
const patterns = analyzeLearningPatterns(allEntries);
|
|
6938
|
+
if (toArchive.length > 0) {
|
|
6939
|
+
const archiveResult = await archiveLearnings(projectPath, toArchive, stream);
|
|
6940
|
+
if (!archiveResult.ok) return archiveResult;
|
|
6941
|
+
}
|
|
6942
|
+
const newContent = "# Learnings\n\n" + toKeep.join("\n\n") + "\n";
|
|
6943
|
+
fs9.writeFileSync(learningsPath, newContent);
|
|
6944
|
+
learningsCacheMap.delete(learningsPath);
|
|
6945
|
+
return Ok({
|
|
6946
|
+
kept: toKeep.length,
|
|
6947
|
+
archived: toArchive.length,
|
|
6948
|
+
patterns
|
|
6949
|
+
});
|
|
6950
|
+
} catch (error) {
|
|
6951
|
+
return Err(
|
|
6952
|
+
new Error(
|
|
6953
|
+
`Failed to prune learnings: ${error instanceof Error ? error.message : String(error)}`
|
|
6954
|
+
)
|
|
6955
|
+
);
|
|
6956
|
+
}
|
|
6957
|
+
}
|
|
6958
|
+
var failuresCacheMap = /* @__PURE__ */ new Map();
|
|
6959
|
+
function clearFailuresCache() {
|
|
6960
|
+
failuresCacheMap.clear();
|
|
6961
|
+
}
|
|
6962
|
+
var FAILURE_LINE_REGEX = /^- \*\*(\d{4}-\d{2}-\d{2}) \[skill:([^\]]+)\] \[type:([^\]]+)\]:\*\* (.+)$/;
|
|
6963
|
+
async function appendFailure(projectPath, description, skillName, type, stream, session) {
|
|
6964
|
+
try {
|
|
6965
|
+
const dirResult = await getStateDir(projectPath, stream, session);
|
|
6966
|
+
if (!dirResult.ok) return dirResult;
|
|
6967
|
+
const stateDir = dirResult.value;
|
|
6968
|
+
const failuresPath = path7.join(stateDir, FAILURES_FILE);
|
|
6969
|
+
fs10.mkdirSync(stateDir, { recursive: true });
|
|
6677
6970
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
6678
6971
|
const entry = `
|
|
6679
6972
|
- **${timestamp} [skill:${skillName}] [type:${type}]:** ${description}
|
|
6680
6973
|
`;
|
|
6681
|
-
if (!
|
|
6682
|
-
|
|
6974
|
+
if (!fs10.existsSync(failuresPath)) {
|
|
6975
|
+
fs10.writeFileSync(failuresPath, `# Failures
|
|
6683
6976
|
${entry}`);
|
|
6684
6977
|
} else {
|
|
6685
|
-
|
|
6978
|
+
fs10.appendFileSync(failuresPath, entry);
|
|
6686
6979
|
}
|
|
6687
6980
|
failuresCacheMap.delete(failuresPath);
|
|
6688
6981
|
return Ok(void 0);
|
|
@@ -6694,22 +6987,22 @@ ${entry}`);
|
|
|
6694
6987
|
);
|
|
6695
6988
|
}
|
|
6696
6989
|
}
|
|
6697
|
-
async function loadFailures(projectPath, stream) {
|
|
6990
|
+
async function loadFailures(projectPath, stream, session) {
|
|
6698
6991
|
try {
|
|
6699
|
-
const dirResult = await getStateDir(projectPath, stream);
|
|
6992
|
+
const dirResult = await getStateDir(projectPath, stream, session);
|
|
6700
6993
|
if (!dirResult.ok) return dirResult;
|
|
6701
6994
|
const stateDir = dirResult.value;
|
|
6702
|
-
const failuresPath =
|
|
6703
|
-
if (!
|
|
6995
|
+
const failuresPath = path7.join(stateDir, FAILURES_FILE);
|
|
6996
|
+
if (!fs10.existsSync(failuresPath)) {
|
|
6704
6997
|
return Ok([]);
|
|
6705
6998
|
}
|
|
6706
|
-
const stats =
|
|
6999
|
+
const stats = fs10.statSync(failuresPath);
|
|
6707
7000
|
const cacheKey = failuresPath;
|
|
6708
7001
|
const cached = failuresCacheMap.get(cacheKey);
|
|
6709
7002
|
if (cached && cached.mtimeMs === stats.mtimeMs) {
|
|
6710
7003
|
return Ok(cached.entries);
|
|
6711
7004
|
}
|
|
6712
|
-
const content =
|
|
7005
|
+
const content = fs10.readFileSync(failuresPath, "utf-8");
|
|
6713
7006
|
const entries = [];
|
|
6714
7007
|
for (const line of content.split("\n")) {
|
|
6715
7008
|
const match = line.match(FAILURE_LINE_REGEX);
|
|
@@ -6733,25 +7026,25 @@ async function loadFailures(projectPath, stream) {
|
|
|
6733
7026
|
);
|
|
6734
7027
|
}
|
|
6735
7028
|
}
|
|
6736
|
-
async function archiveFailures(projectPath, stream) {
|
|
7029
|
+
async function archiveFailures(projectPath, stream, session) {
|
|
6737
7030
|
try {
|
|
6738
|
-
const dirResult = await getStateDir(projectPath, stream);
|
|
7031
|
+
const dirResult = await getStateDir(projectPath, stream, session);
|
|
6739
7032
|
if (!dirResult.ok) return dirResult;
|
|
6740
7033
|
const stateDir = dirResult.value;
|
|
6741
|
-
const failuresPath =
|
|
6742
|
-
if (!
|
|
7034
|
+
const failuresPath = path7.join(stateDir, FAILURES_FILE);
|
|
7035
|
+
if (!fs10.existsSync(failuresPath)) {
|
|
6743
7036
|
return Ok(void 0);
|
|
6744
7037
|
}
|
|
6745
|
-
const archiveDir =
|
|
6746
|
-
|
|
7038
|
+
const archiveDir = path7.join(stateDir, "archive");
|
|
7039
|
+
fs10.mkdirSync(archiveDir, { recursive: true });
|
|
6747
7040
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
6748
7041
|
let archiveName = `failures-${date}.md`;
|
|
6749
7042
|
let counter = 2;
|
|
6750
|
-
while (
|
|
7043
|
+
while (fs10.existsSync(path7.join(archiveDir, archiveName))) {
|
|
6751
7044
|
archiveName = `failures-${date}-${counter}.md`;
|
|
6752
7045
|
counter++;
|
|
6753
7046
|
}
|
|
6754
|
-
|
|
7047
|
+
fs10.renameSync(failuresPath, path7.join(archiveDir, archiveName));
|
|
6755
7048
|
failuresCacheMap.delete(failuresPath);
|
|
6756
7049
|
return Ok(void 0);
|
|
6757
7050
|
} catch (error) {
|
|
@@ -6762,14 +7055,14 @@ async function archiveFailures(projectPath, stream) {
|
|
|
6762
7055
|
);
|
|
6763
7056
|
}
|
|
6764
7057
|
}
|
|
6765
|
-
async function saveHandoff(projectPath, handoff, stream) {
|
|
7058
|
+
async function saveHandoff(projectPath, handoff, stream, session) {
|
|
6766
7059
|
try {
|
|
6767
|
-
const dirResult = await getStateDir(projectPath, stream);
|
|
7060
|
+
const dirResult = await getStateDir(projectPath, stream, session);
|
|
6768
7061
|
if (!dirResult.ok) return dirResult;
|
|
6769
7062
|
const stateDir = dirResult.value;
|
|
6770
|
-
const handoffPath =
|
|
6771
|
-
|
|
6772
|
-
|
|
7063
|
+
const handoffPath = path8.join(stateDir, HANDOFF_FILE);
|
|
7064
|
+
fs11.mkdirSync(stateDir, { recursive: true });
|
|
7065
|
+
fs11.writeFileSync(handoffPath, JSON.stringify(handoff, null, 2));
|
|
6773
7066
|
return Ok(void 0);
|
|
6774
7067
|
} catch (error) {
|
|
6775
7068
|
return Err(
|
|
@@ -6777,16 +7070,16 @@ async function saveHandoff(projectPath, handoff, stream) {
|
|
|
6777
7070
|
);
|
|
6778
7071
|
}
|
|
6779
7072
|
}
|
|
6780
|
-
async function loadHandoff(projectPath, stream) {
|
|
7073
|
+
async function loadHandoff(projectPath, stream, session) {
|
|
6781
7074
|
try {
|
|
6782
|
-
const dirResult = await getStateDir(projectPath, stream);
|
|
7075
|
+
const dirResult = await getStateDir(projectPath, stream, session);
|
|
6783
7076
|
if (!dirResult.ok) return dirResult;
|
|
6784
7077
|
const stateDir = dirResult.value;
|
|
6785
|
-
const handoffPath =
|
|
6786
|
-
if (!
|
|
7078
|
+
const handoffPath = path8.join(stateDir, HANDOFF_FILE);
|
|
7079
|
+
if (!fs11.existsSync(handoffPath)) {
|
|
6787
7080
|
return Ok(null);
|
|
6788
7081
|
}
|
|
6789
|
-
const raw =
|
|
7082
|
+
const raw = fs11.readFileSync(handoffPath, "utf-8");
|
|
6790
7083
|
const parsed = JSON.parse(raw);
|
|
6791
7084
|
const result = HandoffSchema.safeParse(parsed);
|
|
6792
7085
|
if (!result.success) {
|
|
@@ -6799,73 +7092,77 @@ async function loadHandoff(projectPath, stream) {
|
|
|
6799
7092
|
);
|
|
6800
7093
|
}
|
|
6801
7094
|
}
|
|
7095
|
+
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:.-]+$/;
|
|
7096
|
+
function loadChecksFromConfig(gateConfigPath) {
|
|
7097
|
+
if (!fs12.existsSync(gateConfigPath)) return [];
|
|
7098
|
+
const raw = JSON.parse(fs12.readFileSync(gateConfigPath, "utf-8"));
|
|
7099
|
+
const config = GateConfigSchema.safeParse(raw);
|
|
7100
|
+
if (config.success && config.data.checks) return config.data.checks;
|
|
7101
|
+
return [];
|
|
7102
|
+
}
|
|
7103
|
+
function discoverChecksFromProject(projectPath) {
|
|
7104
|
+
const checks = [];
|
|
7105
|
+
const packageJsonPath = path9.join(projectPath, "package.json");
|
|
7106
|
+
if (fs12.existsSync(packageJsonPath)) {
|
|
7107
|
+
const pkg = JSON.parse(fs12.readFileSync(packageJsonPath, "utf-8"));
|
|
7108
|
+
const scripts = pkg.scripts || {};
|
|
7109
|
+
if (scripts.test) checks.push({ name: "test", command: "npm test" });
|
|
7110
|
+
if (scripts.lint) checks.push({ name: "lint", command: "npm run lint" });
|
|
7111
|
+
if (scripts.typecheck) checks.push({ name: "typecheck", command: "npm run typecheck" });
|
|
7112
|
+
if (scripts.build) checks.push({ name: "build", command: "npm run build" });
|
|
7113
|
+
}
|
|
7114
|
+
if (fs12.existsSync(path9.join(projectPath, "go.mod"))) {
|
|
7115
|
+
checks.push({ name: "test", command: "go test ./..." });
|
|
7116
|
+
checks.push({ name: "build", command: "go build ./..." });
|
|
7117
|
+
}
|
|
7118
|
+
if (fs12.existsSync(path9.join(projectPath, "pyproject.toml")) || fs12.existsSync(path9.join(projectPath, "setup.py"))) {
|
|
7119
|
+
checks.push({ name: "test", command: "python -m pytest" });
|
|
7120
|
+
}
|
|
7121
|
+
return checks;
|
|
7122
|
+
}
|
|
7123
|
+
function executeCheck(check, projectPath) {
|
|
7124
|
+
if (!SAFE_GATE_COMMAND.test(check.command)) {
|
|
7125
|
+
return {
|
|
7126
|
+
name: check.name,
|
|
7127
|
+
passed: false,
|
|
7128
|
+
command: check.command,
|
|
7129
|
+
output: `Blocked: command does not match safe gate pattern. Allowed prefixes: npm, pnpm, yarn, go, python, python3, make, cargo, gradle, mvn`,
|
|
7130
|
+
duration: 0
|
|
7131
|
+
};
|
|
7132
|
+
}
|
|
7133
|
+
const start = Date.now();
|
|
7134
|
+
try {
|
|
7135
|
+
execSync2(check.command, {
|
|
7136
|
+
cwd: projectPath,
|
|
7137
|
+
stdio: "pipe",
|
|
7138
|
+
timeout: 12e4
|
|
7139
|
+
});
|
|
7140
|
+
return {
|
|
7141
|
+
name: check.name,
|
|
7142
|
+
passed: true,
|
|
7143
|
+
command: check.command,
|
|
7144
|
+
duration: Date.now() - start
|
|
7145
|
+
};
|
|
7146
|
+
} catch (error) {
|
|
7147
|
+
const output = error instanceof Error ? error.stderr?.toString() || error.message : String(error);
|
|
7148
|
+
return {
|
|
7149
|
+
name: check.name,
|
|
7150
|
+
passed: false,
|
|
7151
|
+
command: check.command,
|
|
7152
|
+
output: output.slice(0, 2e3),
|
|
7153
|
+
duration: Date.now() - start
|
|
7154
|
+
};
|
|
7155
|
+
}
|
|
7156
|
+
}
|
|
6802
7157
|
async function runMechanicalGate(projectPath) {
|
|
6803
|
-
const harnessDir =
|
|
6804
|
-
const gateConfigPath =
|
|
7158
|
+
const harnessDir = path9.join(projectPath, HARNESS_DIR);
|
|
7159
|
+
const gateConfigPath = path9.join(harnessDir, GATE_CONFIG_FILE);
|
|
6805
7160
|
try {
|
|
6806
|
-
let checks =
|
|
6807
|
-
if (fs6.existsSync(gateConfigPath)) {
|
|
6808
|
-
const raw = JSON.parse(fs6.readFileSync(gateConfigPath, "utf-8"));
|
|
6809
|
-
const config = GateConfigSchema.safeParse(raw);
|
|
6810
|
-
if (config.success && config.data.checks) {
|
|
6811
|
-
checks = config.data.checks;
|
|
6812
|
-
}
|
|
6813
|
-
}
|
|
7161
|
+
let checks = loadChecksFromConfig(gateConfigPath);
|
|
6814
7162
|
if (checks.length === 0) {
|
|
6815
|
-
|
|
6816
|
-
if (fs6.existsSync(packageJsonPath)) {
|
|
6817
|
-
const pkg = JSON.parse(fs6.readFileSync(packageJsonPath, "utf-8"));
|
|
6818
|
-
const scripts = pkg.scripts || {};
|
|
6819
|
-
if (scripts.test) checks.push({ name: "test", command: "npm test" });
|
|
6820
|
-
if (scripts.lint) checks.push({ name: "lint", command: "npm run lint" });
|
|
6821
|
-
if (scripts.typecheck) checks.push({ name: "typecheck", command: "npm run typecheck" });
|
|
6822
|
-
if (scripts.build) checks.push({ name: "build", command: "npm run build" });
|
|
6823
|
-
}
|
|
6824
|
-
if (fs6.existsSync(path3.join(projectPath, "go.mod"))) {
|
|
6825
|
-
checks.push({ name: "test", command: "go test ./..." });
|
|
6826
|
-
checks.push({ name: "build", command: "go build ./..." });
|
|
6827
|
-
}
|
|
6828
|
-
if (fs6.existsSync(path3.join(projectPath, "pyproject.toml")) || fs6.existsSync(path3.join(projectPath, "setup.py"))) {
|
|
6829
|
-
checks.push({ name: "test", command: "python -m pytest" });
|
|
6830
|
-
}
|
|
6831
|
-
}
|
|
6832
|
-
const results = [];
|
|
6833
|
-
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:.-]+$/;
|
|
6834
|
-
for (const check of checks) {
|
|
6835
|
-
if (!SAFE_GATE_COMMAND.test(check.command)) {
|
|
6836
|
-
results.push({
|
|
6837
|
-
name: check.name,
|
|
6838
|
-
passed: false,
|
|
6839
|
-
command: check.command,
|
|
6840
|
-
output: `Blocked: command does not match safe gate pattern. Allowed prefixes: npm, npx, pnpm, yarn, go, python, python3, make, cargo, gradle, mvn`,
|
|
6841
|
-
duration: 0
|
|
6842
|
-
});
|
|
6843
|
-
continue;
|
|
6844
|
-
}
|
|
6845
|
-
const start = Date.now();
|
|
6846
|
-
try {
|
|
6847
|
-
execSync2(check.command, {
|
|
6848
|
-
cwd: projectPath,
|
|
6849
|
-
stdio: "pipe",
|
|
6850
|
-
timeout: 12e4
|
|
6851
|
-
});
|
|
6852
|
-
results.push({
|
|
6853
|
-
name: check.name,
|
|
6854
|
-
passed: true,
|
|
6855
|
-
command: check.command,
|
|
6856
|
-
duration: Date.now() - start
|
|
6857
|
-
});
|
|
6858
|
-
} catch (error) {
|
|
6859
|
-
const output = error instanceof Error ? error.stderr?.toString() || error.message : String(error);
|
|
6860
|
-
results.push({
|
|
6861
|
-
name: check.name,
|
|
6862
|
-
passed: false,
|
|
6863
|
-
command: check.command,
|
|
6864
|
-
output: output.slice(0, 2e3),
|
|
6865
|
-
duration: Date.now() - start
|
|
6866
|
-
});
|
|
6867
|
-
}
|
|
7163
|
+
checks = discoverChecksFromProject(projectPath);
|
|
6868
7164
|
}
|
|
7165
|
+
const results = checks.map((check) => executeCheck(check, projectPath));
|
|
6869
7166
|
return Ok({
|
|
6870
7167
|
passed: results.length === 0 || results.every((r) => r.passed),
|
|
6871
7168
|
checks: results
|
|
@@ -6878,6 +7175,92 @@ async function runMechanicalGate(projectPath) {
|
|
|
6878
7175
|
);
|
|
6879
7176
|
}
|
|
6880
7177
|
}
|
|
7178
|
+
function formatSummary(data) {
|
|
7179
|
+
const lines = [
|
|
7180
|
+
"## Session Summary",
|
|
7181
|
+
"",
|
|
7182
|
+
`**Session:** ${data.session}`,
|
|
7183
|
+
`**Last active:** ${data.lastActive}`,
|
|
7184
|
+
`**Skill:** ${data.skill}`
|
|
7185
|
+
];
|
|
7186
|
+
if (data.phase) {
|
|
7187
|
+
lines.push(`**Phase:** ${data.phase}`);
|
|
7188
|
+
}
|
|
7189
|
+
lines.push(`**Status:** ${data.status}`);
|
|
7190
|
+
if (data.spec) {
|
|
7191
|
+
lines.push(`**Spec:** ${data.spec}`);
|
|
7192
|
+
}
|
|
7193
|
+
if (data.plan) {
|
|
7194
|
+
lines.push(`**Plan:** ${data.plan}`);
|
|
7195
|
+
}
|
|
7196
|
+
lines.push(`**Key context:** ${data.keyContext}`);
|
|
7197
|
+
lines.push(`**Next step:** ${data.nextStep}`);
|
|
7198
|
+
lines.push("");
|
|
7199
|
+
return lines.join("\n");
|
|
7200
|
+
}
|
|
7201
|
+
function deriveIndexDescription(data) {
|
|
7202
|
+
const skillShort = data.skill.replace("harness-", "");
|
|
7203
|
+
const parts = [skillShort];
|
|
7204
|
+
if (data.phase) {
|
|
7205
|
+
parts.push(`phase ${data.phase}`);
|
|
7206
|
+
}
|
|
7207
|
+
parts.push(data.status.toLowerCase());
|
|
7208
|
+
return parts.join(", ");
|
|
7209
|
+
}
|
|
7210
|
+
function writeSessionSummary(projectPath, sessionSlug, data) {
|
|
7211
|
+
try {
|
|
7212
|
+
const dirResult = resolveSessionDir(projectPath, sessionSlug, { create: true });
|
|
7213
|
+
if (!dirResult.ok) return dirResult;
|
|
7214
|
+
const sessionDir = dirResult.value;
|
|
7215
|
+
const summaryPath = path10.join(sessionDir, SUMMARY_FILE);
|
|
7216
|
+
const content = formatSummary(data);
|
|
7217
|
+
fs13.writeFileSync(summaryPath, content);
|
|
7218
|
+
const description = deriveIndexDescription(data);
|
|
7219
|
+
updateSessionIndex(projectPath, sessionSlug, description);
|
|
7220
|
+
return Ok(void 0);
|
|
7221
|
+
} catch (error) {
|
|
7222
|
+
return Err(
|
|
7223
|
+
new Error(
|
|
7224
|
+
`Failed to write session summary: ${error instanceof Error ? error.message : String(error)}`
|
|
7225
|
+
)
|
|
7226
|
+
);
|
|
7227
|
+
}
|
|
7228
|
+
}
|
|
7229
|
+
function loadSessionSummary(projectPath, sessionSlug) {
|
|
7230
|
+
try {
|
|
7231
|
+
const dirResult = resolveSessionDir(projectPath, sessionSlug);
|
|
7232
|
+
if (!dirResult.ok) return dirResult;
|
|
7233
|
+
const sessionDir = dirResult.value;
|
|
7234
|
+
const summaryPath = path10.join(sessionDir, SUMMARY_FILE);
|
|
7235
|
+
if (!fs13.existsSync(summaryPath)) {
|
|
7236
|
+
return Ok(null);
|
|
7237
|
+
}
|
|
7238
|
+
const content = fs13.readFileSync(summaryPath, "utf-8");
|
|
7239
|
+
return Ok(content);
|
|
7240
|
+
} catch (error) {
|
|
7241
|
+
return Err(
|
|
7242
|
+
new Error(
|
|
7243
|
+
`Failed to load session summary: ${error instanceof Error ? error.message : String(error)}`
|
|
7244
|
+
)
|
|
7245
|
+
);
|
|
7246
|
+
}
|
|
7247
|
+
}
|
|
7248
|
+
function listActiveSessions(projectPath) {
|
|
7249
|
+
try {
|
|
7250
|
+
const indexPath2 = path10.join(projectPath, HARNESS_DIR, SESSIONS_DIR, SESSION_INDEX_FILE);
|
|
7251
|
+
if (!fs13.existsSync(indexPath2)) {
|
|
7252
|
+
return Ok(null);
|
|
7253
|
+
}
|
|
7254
|
+
const content = fs13.readFileSync(indexPath2, "utf-8");
|
|
7255
|
+
return Ok(content);
|
|
7256
|
+
} catch (error) {
|
|
7257
|
+
return Err(
|
|
7258
|
+
new Error(
|
|
7259
|
+
`Failed to list active sessions: ${error instanceof Error ? error.message : String(error)}`
|
|
7260
|
+
)
|
|
7261
|
+
);
|
|
7262
|
+
}
|
|
7263
|
+
}
|
|
6881
7264
|
async function executeWorkflow(workflow, executor) {
|
|
6882
7265
|
const stepResults = [];
|
|
6883
7266
|
const startTime = Date.now();
|
|
@@ -7099,11 +7482,11 @@ function resolveRuleSeverity(ruleId, defaultSeverity, overrides, strict) {
|
|
|
7099
7482
|
}
|
|
7100
7483
|
function detectStack(projectRoot) {
|
|
7101
7484
|
const stacks = [];
|
|
7102
|
-
const pkgJsonPath =
|
|
7103
|
-
if (
|
|
7485
|
+
const pkgJsonPath = path11.join(projectRoot, "package.json");
|
|
7486
|
+
if (fs14.existsSync(pkgJsonPath)) {
|
|
7104
7487
|
stacks.push("node");
|
|
7105
7488
|
try {
|
|
7106
|
-
const pkgJson = JSON.parse(
|
|
7489
|
+
const pkgJson = JSON.parse(fs14.readFileSync(pkgJsonPath, "utf-8"));
|
|
7107
7490
|
const allDeps = {
|
|
7108
7491
|
...pkgJson.dependencies,
|
|
7109
7492
|
...pkgJson.devDependencies
|
|
@@ -7118,13 +7501,13 @@ function detectStack(projectRoot) {
|
|
|
7118
7501
|
} catch {
|
|
7119
7502
|
}
|
|
7120
7503
|
}
|
|
7121
|
-
const goModPath =
|
|
7122
|
-
if (
|
|
7504
|
+
const goModPath = path11.join(projectRoot, "go.mod");
|
|
7505
|
+
if (fs14.existsSync(goModPath)) {
|
|
7123
7506
|
stacks.push("go");
|
|
7124
7507
|
}
|
|
7125
|
-
const requirementsPath =
|
|
7126
|
-
const pyprojectPath =
|
|
7127
|
-
if (
|
|
7508
|
+
const requirementsPath = path11.join(projectRoot, "requirements.txt");
|
|
7509
|
+
const pyprojectPath = path11.join(projectRoot, "pyproject.toml");
|
|
7510
|
+
if (fs14.existsSync(requirementsPath) || fs14.existsSync(pyprojectPath)) {
|
|
7128
7511
|
stacks.push("python");
|
|
7129
7512
|
}
|
|
7130
7513
|
return stacks;
|
|
@@ -7527,7 +7910,7 @@ var SecurityScanner = class {
|
|
|
7527
7910
|
}
|
|
7528
7911
|
async scanFile(filePath) {
|
|
7529
7912
|
if (!this.config.enabled) return [];
|
|
7530
|
-
const content = await
|
|
7913
|
+
const content = await fs15.readFile(filePath, "utf-8");
|
|
7531
7914
|
return this.scanContent(content, filePath, 1);
|
|
7532
7915
|
}
|
|
7533
7916
|
async scanFiles(filePaths) {
|
|
@@ -7566,7 +7949,7 @@ async function runSingleCheck(name, projectRoot, config) {
|
|
|
7566
7949
|
try {
|
|
7567
7950
|
switch (name) {
|
|
7568
7951
|
case "validate": {
|
|
7569
|
-
const agentsPath =
|
|
7952
|
+
const agentsPath = path12.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
|
|
7570
7953
|
const result = await validateAgentsMap(agentsPath);
|
|
7571
7954
|
if (!result.ok) {
|
|
7572
7955
|
issues.push({ severity: "error", message: result.error.message });
|
|
@@ -7621,7 +8004,7 @@ async function runSingleCheck(name, projectRoot, config) {
|
|
|
7621
8004
|
break;
|
|
7622
8005
|
}
|
|
7623
8006
|
case "docs": {
|
|
7624
|
-
const docsDir =
|
|
8007
|
+
const docsDir = path12.join(projectRoot, config.docsDir ?? "docs");
|
|
7625
8008
|
const entropyConfig = config.entropy || {};
|
|
7626
8009
|
const result = await checkDocCoverage("project", {
|
|
7627
8010
|
docsDir,
|
|
@@ -7868,7 +8251,7 @@ async function runMechanicalChecks(options) {
|
|
|
7868
8251
|
};
|
|
7869
8252
|
if (!skip.includes("validate")) {
|
|
7870
8253
|
try {
|
|
7871
|
-
const agentsPath =
|
|
8254
|
+
const agentsPath = path13.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
|
|
7872
8255
|
const result = await validateAgentsMap(agentsPath);
|
|
7873
8256
|
if (!result.ok) {
|
|
7874
8257
|
statuses.validate = "fail";
|
|
@@ -7905,7 +8288,7 @@ async function runMechanicalChecks(options) {
|
|
|
7905
8288
|
statuses.validate = "fail";
|
|
7906
8289
|
findings.push({
|
|
7907
8290
|
tool: "validate",
|
|
7908
|
-
file:
|
|
8291
|
+
file: path13.join(projectRoot, "AGENTS.md"),
|
|
7909
8292
|
message: err instanceof Error ? err.message : String(err),
|
|
7910
8293
|
severity: "error"
|
|
7911
8294
|
});
|
|
@@ -7969,7 +8352,7 @@ async function runMechanicalChecks(options) {
|
|
|
7969
8352
|
(async () => {
|
|
7970
8353
|
const localFindings = [];
|
|
7971
8354
|
try {
|
|
7972
|
-
const docsDir =
|
|
8355
|
+
const docsDir = path13.join(projectRoot, config.docsDir ?? "docs");
|
|
7973
8356
|
const result = await checkDocCoverage("project", { docsDir });
|
|
7974
8357
|
if (!result.ok) {
|
|
7975
8358
|
statuses["check-docs"] = "warn";
|
|
@@ -7996,7 +8379,7 @@ async function runMechanicalChecks(options) {
|
|
|
7996
8379
|
statuses["check-docs"] = "warn";
|
|
7997
8380
|
localFindings.push({
|
|
7998
8381
|
tool: "check-docs",
|
|
7999
|
-
file:
|
|
8382
|
+
file: path13.join(projectRoot, "docs"),
|
|
8000
8383
|
message: err instanceof Error ? err.message : String(err),
|
|
8001
8384
|
severity: "warning"
|
|
8002
8385
|
});
|
|
@@ -8145,18 +8528,18 @@ function computeContextBudget(diffLines) {
|
|
|
8145
8528
|
return diffLines;
|
|
8146
8529
|
}
|
|
8147
8530
|
function isWithinProject(absPath, projectRoot) {
|
|
8148
|
-
const resolvedRoot =
|
|
8149
|
-
const resolvedPath =
|
|
8150
|
-
return resolvedPath.startsWith(resolvedRoot) || resolvedPath ===
|
|
8531
|
+
const resolvedRoot = path14.resolve(projectRoot) + path14.sep;
|
|
8532
|
+
const resolvedPath = path14.resolve(absPath);
|
|
8533
|
+
return resolvedPath.startsWith(resolvedRoot) || resolvedPath === path14.resolve(projectRoot);
|
|
8151
8534
|
}
|
|
8152
8535
|
async function readContextFile(projectRoot, filePath, reason) {
|
|
8153
|
-
const absPath =
|
|
8536
|
+
const absPath = path14.isAbsolute(filePath) ? filePath : path14.join(projectRoot, filePath);
|
|
8154
8537
|
if (!isWithinProject(absPath, projectRoot)) return null;
|
|
8155
8538
|
const result = await readFileContent(absPath);
|
|
8156
8539
|
if (!result.ok) return null;
|
|
8157
8540
|
const content = result.value;
|
|
8158
8541
|
const lines = content.split("\n").length;
|
|
8159
|
-
const relPath =
|
|
8542
|
+
const relPath = path14.isAbsolute(filePath) ? path14.relative(projectRoot, filePath) : filePath;
|
|
8160
8543
|
return { path: relPath, content, reason, lines };
|
|
8161
8544
|
}
|
|
8162
8545
|
function extractImportSources2(content) {
|
|
@@ -8171,18 +8554,18 @@ function extractImportSources2(content) {
|
|
|
8171
8554
|
}
|
|
8172
8555
|
async function resolveImportPath2(projectRoot, fromFile, importSource) {
|
|
8173
8556
|
if (!importSource.startsWith(".")) return null;
|
|
8174
|
-
const fromDir =
|
|
8175
|
-
const basePath =
|
|
8557
|
+
const fromDir = path14.dirname(path14.join(projectRoot, fromFile));
|
|
8558
|
+
const basePath = path14.resolve(fromDir, importSource);
|
|
8176
8559
|
if (!isWithinProject(basePath, projectRoot)) return null;
|
|
8177
|
-
const relBase =
|
|
8560
|
+
const relBase = path14.relative(projectRoot, basePath);
|
|
8178
8561
|
const candidates = [
|
|
8179
8562
|
relBase + ".ts",
|
|
8180
8563
|
relBase + ".tsx",
|
|
8181
8564
|
relBase + ".mts",
|
|
8182
|
-
|
|
8565
|
+
path14.join(relBase, "index.ts")
|
|
8183
8566
|
];
|
|
8184
8567
|
for (const candidate of candidates) {
|
|
8185
|
-
const absCandidate =
|
|
8568
|
+
const absCandidate = path14.join(projectRoot, candidate);
|
|
8186
8569
|
if (await fileExists(absCandidate)) {
|
|
8187
8570
|
return candidate;
|
|
8188
8571
|
}
|
|
@@ -8190,10 +8573,10 @@ async function resolveImportPath2(projectRoot, fromFile, importSource) {
|
|
|
8190
8573
|
return null;
|
|
8191
8574
|
}
|
|
8192
8575
|
async function findTestFiles(projectRoot, sourceFile) {
|
|
8193
|
-
const baseName =
|
|
8576
|
+
const baseName = path14.basename(sourceFile, path14.extname(sourceFile));
|
|
8194
8577
|
const pattern = `**/${baseName}.{test,spec}.{ts,tsx,mts}`;
|
|
8195
8578
|
const results = await findFiles(pattern, projectRoot);
|
|
8196
|
-
return results.map((f) =>
|
|
8579
|
+
return results.map((f) => path14.relative(projectRoot, f));
|
|
8197
8580
|
}
|
|
8198
8581
|
async function gatherImportContext(projectRoot, changedFiles, budget) {
|
|
8199
8582
|
const contextFiles = [];
|
|
@@ -8985,7 +9368,7 @@ function normalizePath(filePath, projectRoot) {
|
|
|
8985
9368
|
let normalized = filePath;
|
|
8986
9369
|
normalized = normalized.replace(/\\/g, "/");
|
|
8987
9370
|
const normalizedRoot = projectRoot.replace(/\\/g, "/");
|
|
8988
|
-
if (
|
|
9371
|
+
if (path15.isAbsolute(normalized)) {
|
|
8989
9372
|
const root = normalizedRoot.endsWith("/") ? normalizedRoot : normalizedRoot + "/";
|
|
8990
9373
|
if (normalized.startsWith(root)) {
|
|
8991
9374
|
normalized = normalized.slice(root.length);
|
|
@@ -9010,12 +9393,12 @@ function followImportChain(fromFile, fileContents, maxDepth = 2) {
|
|
|
9010
9393
|
while ((match = importRegex.exec(content)) !== null) {
|
|
9011
9394
|
const importPath = match[1];
|
|
9012
9395
|
if (!importPath.startsWith(".")) continue;
|
|
9013
|
-
const dir =
|
|
9014
|
-
let resolved =
|
|
9396
|
+
const dir = path15.dirname(current.file);
|
|
9397
|
+
let resolved = path15.join(dir, importPath).replace(/\\/g, "/");
|
|
9015
9398
|
if (!resolved.match(/\.(ts|tsx|js|jsx)$/)) {
|
|
9016
9399
|
resolved += ".ts";
|
|
9017
9400
|
}
|
|
9018
|
-
resolved =
|
|
9401
|
+
resolved = path15.normalize(resolved).replace(/\\/g, "/");
|
|
9019
9402
|
if (!visited.has(resolved) && current.depth + 1 <= maxDepth) {
|
|
9020
9403
|
queue.push({ file: resolved, depth: current.depth + 1 });
|
|
9021
9404
|
}
|
|
@@ -9032,7 +9415,7 @@ async function validateFindings(options) {
|
|
|
9032
9415
|
if (exclusionSet.isExcluded(normalizedFile, finding.lineRange) || exclusionSet.isExcluded(finding.file, finding.lineRange)) {
|
|
9033
9416
|
continue;
|
|
9034
9417
|
}
|
|
9035
|
-
const absoluteFile =
|
|
9418
|
+
const absoluteFile = path15.isAbsolute(finding.file) ? finding.file : path15.join(projectRoot, finding.file).replace(/\\/g, "/");
|
|
9036
9419
|
if (exclusionSet.isExcluded(absoluteFile, finding.lineRange)) {
|
|
9037
9420
|
continue;
|
|
9038
9421
|
}
|
|
@@ -9536,6 +9919,8 @@ function parseFrontmatter(raw) {
|
|
|
9536
9919
|
const versionStr = map.get("version");
|
|
9537
9920
|
const lastSynced = map.get("last_synced");
|
|
9538
9921
|
const lastManualEdit = map.get("last_manual_edit");
|
|
9922
|
+
const created = map.get("created");
|
|
9923
|
+
const updated = map.get("updated");
|
|
9539
9924
|
if (!project || !versionStr || !lastSynced || !lastManualEdit) {
|
|
9540
9925
|
return Err(
|
|
9541
9926
|
new Error(
|
|
@@ -9547,7 +9932,10 @@ function parseFrontmatter(raw) {
|
|
|
9547
9932
|
if (isNaN(version)) {
|
|
9548
9933
|
return Err(new Error("Frontmatter version must be a number"));
|
|
9549
9934
|
}
|
|
9550
|
-
|
|
9935
|
+
const fm = { project, version, lastSynced, lastManualEdit };
|
|
9936
|
+
if (created) fm.created = created;
|
|
9937
|
+
if (updated) fm.updated = updated;
|
|
9938
|
+
return Ok(fm);
|
|
9551
9939
|
}
|
|
9552
9940
|
function parseMilestones(body) {
|
|
9553
9941
|
const milestones = [];
|
|
@@ -9555,12 +9943,12 @@ function parseMilestones(body) {
|
|
|
9555
9943
|
const h2Matches = [];
|
|
9556
9944
|
let match;
|
|
9557
9945
|
while ((match = h2Pattern.exec(body)) !== null) {
|
|
9558
|
-
h2Matches.push({ heading: match[1], startIndex: match.index });
|
|
9946
|
+
h2Matches.push({ heading: match[1], startIndex: match.index, fullMatch: match[0] });
|
|
9559
9947
|
}
|
|
9560
9948
|
for (let i = 0; i < h2Matches.length; i++) {
|
|
9561
9949
|
const h2 = h2Matches[i];
|
|
9562
9950
|
const nextStart = i + 1 < h2Matches.length ? h2Matches[i + 1].startIndex : body.length;
|
|
9563
|
-
const sectionBody = body.slice(h2.startIndex + h2.
|
|
9951
|
+
const sectionBody = body.slice(h2.startIndex + h2.fullMatch.length, nextStart);
|
|
9564
9952
|
const isBacklog = h2.heading === "Backlog";
|
|
9565
9953
|
const milestoneName = isBacklog ? "Backlog" : h2.heading.replace(/^Milestone:\s*/, "");
|
|
9566
9954
|
const featuresResult = parseFeatures(sectionBody);
|
|
@@ -9575,19 +9963,16 @@ function parseMilestones(body) {
|
|
|
9575
9963
|
}
|
|
9576
9964
|
function parseFeatures(sectionBody) {
|
|
9577
9965
|
const features = [];
|
|
9578
|
-
const h3Pattern = /^### Feature: (.+)$/gm;
|
|
9966
|
+
const h3Pattern = /^### (?:Feature: )?(.+)$/gm;
|
|
9579
9967
|
const h3Matches = [];
|
|
9580
9968
|
let match;
|
|
9581
9969
|
while ((match = h3Pattern.exec(sectionBody)) !== null) {
|
|
9582
|
-
h3Matches.push({ name: match[1], startIndex: match.index });
|
|
9970
|
+
h3Matches.push({ name: match[1], startIndex: match.index, fullMatch: match[0] });
|
|
9583
9971
|
}
|
|
9584
9972
|
for (let i = 0; i < h3Matches.length; i++) {
|
|
9585
9973
|
const h3 = h3Matches[i];
|
|
9586
9974
|
const nextStart = i + 1 < h3Matches.length ? h3Matches[i + 1].startIndex : sectionBody.length;
|
|
9587
|
-
const featureBody = sectionBody.slice(
|
|
9588
|
-
h3.startIndex + `### Feature: ${h3.name}`.length,
|
|
9589
|
-
nextStart
|
|
9590
|
-
);
|
|
9975
|
+
const featureBody = sectionBody.slice(h3.startIndex + h3.fullMatch.length, nextStart);
|
|
9591
9976
|
const featureResult = parseFeatureFields(h3.name, featureBody);
|
|
9592
9977
|
if (!featureResult.ok) return featureResult;
|
|
9593
9978
|
features.push(featureResult.value);
|
|
@@ -9612,10 +9997,10 @@ function parseFeatureFields(name, body) {
|
|
|
9612
9997
|
const status = statusRaw;
|
|
9613
9998
|
const specRaw = fieldMap.get("Spec") ?? EM_DASH;
|
|
9614
9999
|
const spec = specRaw === EM_DASH ? null : specRaw;
|
|
9615
|
-
const plansRaw = fieldMap.get("Plans") ?? EM_DASH;
|
|
9616
|
-
const plans = plansRaw === EM_DASH ? [] : plansRaw.split(",").map((p) => p.trim());
|
|
9617
|
-
const blockedByRaw = fieldMap.get("Blocked by") ?? EM_DASH;
|
|
9618
|
-
const blockedBy = blockedByRaw === EM_DASH ? [] : blockedByRaw.split(",").map((b) => b.trim());
|
|
10000
|
+
const plansRaw = fieldMap.get("Plans") ?? fieldMap.get("Plan") ?? EM_DASH;
|
|
10001
|
+
const plans = plansRaw === EM_DASH || plansRaw === "none" ? [] : plansRaw.split(",").map((p) => p.trim());
|
|
10002
|
+
const blockedByRaw = fieldMap.get("Blocked by") ?? fieldMap.get("Blockers") ?? EM_DASH;
|
|
10003
|
+
const blockedBy = blockedByRaw === EM_DASH || blockedByRaw === "none" ? [] : blockedByRaw.split(",").map((b) => b.trim());
|
|
9619
10004
|
const summary = fieldMap.get("Summary") ?? "";
|
|
9620
10005
|
return Ok({ name, status, spec, plans, blockedBy, summary });
|
|
9621
10006
|
}
|
|
@@ -9625,11 +10010,17 @@ function serializeRoadmap(roadmap) {
|
|
|
9625
10010
|
lines.push("---");
|
|
9626
10011
|
lines.push(`project: ${roadmap.frontmatter.project}`);
|
|
9627
10012
|
lines.push(`version: ${roadmap.frontmatter.version}`);
|
|
10013
|
+
if (roadmap.frontmatter.created) {
|
|
10014
|
+
lines.push(`created: ${roadmap.frontmatter.created}`);
|
|
10015
|
+
}
|
|
10016
|
+
if (roadmap.frontmatter.updated) {
|
|
10017
|
+
lines.push(`updated: ${roadmap.frontmatter.updated}`);
|
|
10018
|
+
}
|
|
9628
10019
|
lines.push(`last_synced: ${roadmap.frontmatter.lastSynced}`);
|
|
9629
10020
|
lines.push(`last_manual_edit: ${roadmap.frontmatter.lastManualEdit}`);
|
|
9630
10021
|
lines.push("---");
|
|
9631
10022
|
lines.push("");
|
|
9632
|
-
lines.push("#
|
|
10023
|
+
lines.push("# Roadmap");
|
|
9633
10024
|
for (const milestone of roadmap.milestones) {
|
|
9634
10025
|
lines.push("");
|
|
9635
10026
|
lines.push(serializeMilestoneHeading(milestone));
|
|
@@ -9642,19 +10033,20 @@ function serializeRoadmap(roadmap) {
|
|
|
9642
10033
|
return lines.join("\n");
|
|
9643
10034
|
}
|
|
9644
10035
|
function serializeMilestoneHeading(milestone) {
|
|
9645
|
-
return milestone.isBacklog ? "## Backlog" : `##
|
|
10036
|
+
return milestone.isBacklog ? "## Backlog" : `## ${milestone.name}`;
|
|
9646
10037
|
}
|
|
9647
10038
|
function serializeFeature(feature) {
|
|
9648
10039
|
const spec = feature.spec ?? EM_DASH2;
|
|
9649
10040
|
const plans = feature.plans.length > 0 ? feature.plans.join(", ") : EM_DASH2;
|
|
9650
10041
|
const blockedBy = feature.blockedBy.length > 0 ? feature.blockedBy.join(", ") : EM_DASH2;
|
|
9651
10042
|
return [
|
|
9652
|
-
`###
|
|
10043
|
+
`### ${feature.name}`,
|
|
10044
|
+
"",
|
|
9653
10045
|
`- **Status:** ${feature.status}`,
|
|
9654
10046
|
`- **Spec:** ${spec}`,
|
|
9655
|
-
`- **
|
|
9656
|
-
`- **
|
|
9657
|
-
`- **
|
|
10047
|
+
`- **Summary:** ${feature.summary}`,
|
|
10048
|
+
`- **Blockers:** ${blockedBy}`,
|
|
10049
|
+
`- **Plan:** ${plans}`
|
|
9658
10050
|
];
|
|
9659
10051
|
}
|
|
9660
10052
|
function inferStatus(feature, projectPath, allFeatures) {
|
|
@@ -9670,10 +10062,10 @@ function inferStatus(feature, projectPath, allFeatures) {
|
|
|
9670
10062
|
const featuresWithPlans = allFeatures.filter((f) => f.plans.length > 0);
|
|
9671
10063
|
const useRootState = featuresWithPlans.length <= 1;
|
|
9672
10064
|
if (useRootState) {
|
|
9673
|
-
const rootStatePath =
|
|
9674
|
-
if (
|
|
10065
|
+
const rootStatePath = path16.join(projectPath, ".harness", "state.json");
|
|
10066
|
+
if (fs16.existsSync(rootStatePath)) {
|
|
9675
10067
|
try {
|
|
9676
|
-
const raw =
|
|
10068
|
+
const raw = fs16.readFileSync(rootStatePath, "utf-8");
|
|
9677
10069
|
const state = JSON.parse(raw);
|
|
9678
10070
|
if (state.progress) {
|
|
9679
10071
|
for (const status of Object.values(state.progress)) {
|
|
@@ -9684,16 +10076,16 @@ function inferStatus(feature, projectPath, allFeatures) {
|
|
|
9684
10076
|
}
|
|
9685
10077
|
}
|
|
9686
10078
|
}
|
|
9687
|
-
const sessionsDir =
|
|
9688
|
-
if (
|
|
10079
|
+
const sessionsDir = path16.join(projectPath, ".harness", "sessions");
|
|
10080
|
+
if (fs16.existsSync(sessionsDir)) {
|
|
9689
10081
|
try {
|
|
9690
|
-
const sessionDirs =
|
|
10082
|
+
const sessionDirs = fs16.readdirSync(sessionsDir, { withFileTypes: true });
|
|
9691
10083
|
for (const entry of sessionDirs) {
|
|
9692
10084
|
if (!entry.isDirectory()) continue;
|
|
9693
|
-
const autopilotPath =
|
|
9694
|
-
if (!
|
|
10085
|
+
const autopilotPath = path16.join(sessionsDir, entry.name, "autopilot-state.json");
|
|
10086
|
+
if (!fs16.existsSync(autopilotPath)) continue;
|
|
9695
10087
|
try {
|
|
9696
|
-
const raw =
|
|
10088
|
+
const raw = fs16.readFileSync(autopilotPath, "utf-8");
|
|
9697
10089
|
const autopilot = JSON.parse(raw);
|
|
9698
10090
|
if (!autopilot.phases) continue;
|
|
9699
10091
|
const linkedPhases = autopilot.phases.filter(
|
|
@@ -9773,10 +10165,10 @@ var ProjectScanner = class {
|
|
|
9773
10165
|
this.rootDir = rootDir;
|
|
9774
10166
|
}
|
|
9775
10167
|
async scan() {
|
|
9776
|
-
let projectName =
|
|
10168
|
+
let projectName = path17.basename(this.rootDir);
|
|
9777
10169
|
try {
|
|
9778
|
-
const pkgPath =
|
|
9779
|
-
const pkgRaw = await
|
|
10170
|
+
const pkgPath = path17.join(this.rootDir, "package.json");
|
|
10171
|
+
const pkgRaw = await fs17.readFile(pkgPath, "utf-8");
|
|
9780
10172
|
const pkg = JSON.parse(pkgRaw);
|
|
9781
10173
|
if (pkg.name) projectName = pkg.name;
|
|
9782
10174
|
} catch {
|
|
@@ -9839,16 +10231,6 @@ var SHELL_TEMPLATE = `
|
|
|
9839
10231
|
<div class="content">
|
|
9840
10232
|
<h3>Code Translation</h3>
|
|
9841
10233
|
<div class="translation"><%- module.content.codeTranslation %></div>
|
|
9842
|
-
<h3>Knowledge Check</h3>
|
|
9843
|
-
<div class="quiz">
|
|
9844
|
-
<% module.content.quiz.questions.forEach((q, i) => { %>
|
|
9845
|
-
<div class="question">
|
|
9846
|
-
<p><%= q.question %></p>
|
|
9847
|
-
<button onclick="reveal(this)">Reveal Answer</button>
|
|
9848
|
-
<p class="answer" style="display:none;"><%= q.answer %></p>
|
|
9849
|
-
</div>
|
|
9850
|
-
<% }) %>
|
|
9851
|
-
</div>
|
|
9852
10234
|
</div>
|
|
9853
10235
|
</article>
|
|
9854
10236
|
|
|
@@ -9867,10 +10249,6 @@ header { border-bottom: 2px solid #eee; margin-bottom: 20px; padding-bottom: 10p
|
|
|
9867
10249
|
.module h2 { margin-top: 0; color: #0066cc; }
|
|
9868
10250
|
`;
|
|
9869
10251
|
var SCRIPTS = `
|
|
9870
|
-
function reveal(btn) {
|
|
9871
|
-
btn.nextElementSibling.style.display = 'block';
|
|
9872
|
-
btn.style.display = 'none';
|
|
9873
|
-
}
|
|
9874
10252
|
console.log('Blueprint player initialized.');
|
|
9875
10253
|
`;
|
|
9876
10254
|
var MockLLMService = class {
|
|
@@ -9885,20 +10263,8 @@ var ContentPipeline = class {
|
|
|
9885
10263
|
const translation = await llmService.generate(
|
|
9886
10264
|
`You are a technical educator. Explain the following code clearly and concisely: ${codeContext}`
|
|
9887
10265
|
);
|
|
9888
|
-
const quizJson = await llmService.generate(
|
|
9889
|
-
`Create 3 technical quiz questions for this code. Return ONLY valid JSON in this format: { "questions": [{ "question": "...", "answer": "..." }] }. Code: ${codeContext}`
|
|
9890
|
-
);
|
|
9891
|
-
let quiz;
|
|
9892
|
-
try {
|
|
9893
|
-
const cleanJson = quizJson.replace(/```json/g, "").replace(/```/g, "").trim();
|
|
9894
|
-
quiz = JSON.parse(cleanJson);
|
|
9895
|
-
} catch (e) {
|
|
9896
|
-
console.error("Failed to parse quiz JSON", e);
|
|
9897
|
-
quiz = { questions: [{ question: "Failed to generate quiz", answer: "N/A" }] };
|
|
9898
|
-
}
|
|
9899
10266
|
return {
|
|
9900
|
-
codeTranslation: translation
|
|
9901
|
-
quiz
|
|
10267
|
+
codeTranslation: translation
|
|
9902
10268
|
};
|
|
9903
10269
|
}
|
|
9904
10270
|
};
|
|
@@ -9915,13 +10281,13 @@ var BlueprintGenerator = class {
|
|
|
9915
10281
|
styles: STYLES,
|
|
9916
10282
|
scripts: SCRIPTS
|
|
9917
10283
|
});
|
|
9918
|
-
await
|
|
9919
|
-
await
|
|
10284
|
+
await fs18.mkdir(options.outputDir, { recursive: true });
|
|
10285
|
+
await fs18.writeFile(path18.join(options.outputDir, "index.html"), html);
|
|
9920
10286
|
}
|
|
9921
10287
|
};
|
|
9922
10288
|
function getStatePath() {
|
|
9923
10289
|
const home = process.env["HOME"] || os.homedir();
|
|
9924
|
-
return
|
|
10290
|
+
return path19.join(home, ".harness", "update-check.json");
|
|
9925
10291
|
}
|
|
9926
10292
|
function isUpdateCheckEnabled(configInterval) {
|
|
9927
10293
|
if (process.env["HARNESS_NO_UPDATE_CHECK"] === "1") return false;
|
|
@@ -9934,7 +10300,7 @@ function shouldRunCheck(state, intervalMs) {
|
|
|
9934
10300
|
}
|
|
9935
10301
|
function readCheckState() {
|
|
9936
10302
|
try {
|
|
9937
|
-
const raw =
|
|
10303
|
+
const raw = fs19.readFileSync(getStatePath(), "utf-8");
|
|
9938
10304
|
const parsed = JSON.parse(raw);
|
|
9939
10305
|
if (typeof parsed === "object" && parsed !== null && "lastCheckTime" in parsed && typeof parsed.lastCheckTime === "number" && "currentVersion" in parsed && typeof parsed.currentVersion === "string") {
|
|
9940
10306
|
const state = parsed;
|
|
@@ -9951,7 +10317,7 @@ function readCheckState() {
|
|
|
9951
10317
|
}
|
|
9952
10318
|
function spawnBackgroundCheck(currentVersion) {
|
|
9953
10319
|
const statePath = getStatePath();
|
|
9954
|
-
const stateDir =
|
|
10320
|
+
const stateDir = path19.dirname(statePath);
|
|
9955
10321
|
const script = `
|
|
9956
10322
|
const { execSync } = require('child_process');
|
|
9957
10323
|
const fs = require('fs');
|
|
@@ -10074,6 +10440,7 @@ export {
|
|
|
10074
10440
|
writeLockfile,
|
|
10075
10441
|
addProvenance,
|
|
10076
10442
|
removeProvenance,
|
|
10443
|
+
removeContributions,
|
|
10077
10444
|
createParseError,
|
|
10078
10445
|
TypeScriptParser,
|
|
10079
10446
|
buildSnapshot,
|
|
@@ -10140,16 +10507,28 @@ export {
|
|
|
10140
10507
|
archiveStream,
|
|
10141
10508
|
getStreamForBranch,
|
|
10142
10509
|
migrateToStreams,
|
|
10510
|
+
resolveSessionDir,
|
|
10511
|
+
updateSessionIndex,
|
|
10143
10512
|
loadState,
|
|
10144
10513
|
saveState,
|
|
10514
|
+
clearLearningsCache,
|
|
10145
10515
|
appendLearning,
|
|
10516
|
+
parseDateFromEntry,
|
|
10517
|
+
analyzeLearningPatterns,
|
|
10518
|
+
loadBudgetedLearnings,
|
|
10146
10519
|
loadRelevantLearnings,
|
|
10520
|
+
archiveLearnings,
|
|
10521
|
+
pruneLearnings,
|
|
10522
|
+
clearFailuresCache,
|
|
10147
10523
|
appendFailure,
|
|
10148
10524
|
loadFailures,
|
|
10149
10525
|
archiveFailures,
|
|
10150
10526
|
saveHandoff,
|
|
10151
10527
|
loadHandoff,
|
|
10152
10528
|
runMechanicalGate,
|
|
10529
|
+
writeSessionSummary,
|
|
10530
|
+
loadSessionSummary,
|
|
10531
|
+
listActiveSessions,
|
|
10153
10532
|
executeWorkflow,
|
|
10154
10533
|
runPipeline,
|
|
10155
10534
|
runMultiTurnPipeline,
|