@harness-engineering/cli 1.20.1 → 1.22.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/cleanup-dead-code/skill.yaml +3 -0
- package/dist/agents/skills/claude-code/detect-doc-drift/skill.yaml +5 -0
- package/dist/agents/skills/claude-code/enforce-architecture/skill.yaml +13 -0
- package/dist/agents/skills/claude-code/harness-accessibility/skill.yaml +20 -0
- package/dist/agents/skills/claude-code/harness-code-review/skill.yaml +5 -0
- package/dist/agents/skills/claude-code/harness-codebase-cleanup/skill.yaml +5 -0
- package/dist/agents/skills/claude-code/harness-debugging/skill.yaml +5 -0
- package/dist/agents/skills/claude-code/harness-dependency-health/skill.yaml +9 -0
- package/dist/agents/skills/claude-code/harness-design/skill.yaml +20 -0
- package/dist/agents/skills/claude-code/harness-design-mobile/skill.yaml +20 -0
- package/dist/agents/skills/claude-code/harness-design-system/skill.yaml +22 -0
- package/dist/agents/skills/claude-code/harness-design-web/skill.yaml +22 -0
- package/dist/agents/skills/claude-code/harness-diagnostics/skill.yaml +19 -0
- package/dist/agents/skills/claude-code/harness-git-workflow/skill.yaml +15 -0
- package/dist/agents/skills/claude-code/harness-hotspot-detector/skill.yaml +9 -0
- package/dist/agents/skills/claude-code/harness-i18n/skill.yaml +22 -0
- package/dist/agents/skills/claude-code/harness-i18n-process/skill.yaml +15 -0
- package/dist/agents/skills/claude-code/harness-i18n-workflow/skill.yaml +19 -0
- package/dist/agents/skills/claude-code/harness-integrity/skill.yaml +5 -0
- package/dist/agents/skills/claude-code/harness-perf/skill.yaml +3 -0
- package/dist/agents/skills/claude-code/harness-perf-tdd/skill.yaml +20 -0
- package/dist/agents/skills/claude-code/harness-pre-commit-review/skill.yaml +18 -0
- package/dist/agents/skills/claude-code/harness-refactoring/skill.yaml +9 -0
- package/dist/agents/skills/claude-code/harness-security-review/skill.yaml +23 -0
- package/dist/agents/skills/claude-code/harness-security-scan/skill.yaml +3 -0
- package/dist/agents/skills/claude-code/harness-soundness-review/skill.yaml +5 -0
- package/dist/agents/skills/claude-code/harness-supply-chain-audit/skill.yaml +3 -0
- package/dist/agents/skills/claude-code/harness-tdd/skill.yaml +3 -0
- package/dist/agents/skills/codex/cleanup-dead-code/skill.yaml +3 -0
- package/dist/agents/skills/codex/detect-doc-drift/skill.yaml +5 -0
- package/dist/agents/skills/codex/enforce-architecture/skill.yaml +13 -0
- package/dist/agents/skills/codex/harness-accessibility/skill.yaml +20 -0
- package/dist/agents/skills/codex/harness-code-review/skill.yaml +5 -0
- package/dist/agents/skills/codex/harness-codebase-cleanup/skill.yaml +5 -0
- package/dist/agents/skills/codex/harness-debugging/skill.yaml +5 -0
- package/dist/agents/skills/codex/harness-dependency-health/skill.yaml +9 -0
- package/dist/agents/skills/codex/harness-design/skill.yaml +20 -0
- package/dist/agents/skills/codex/harness-design-mobile/skill.yaml +20 -0
- package/dist/agents/skills/codex/harness-design-system/skill.yaml +22 -0
- package/dist/agents/skills/codex/harness-design-web/skill.yaml +22 -0
- package/dist/agents/skills/codex/harness-diagnostics/skill.yaml +19 -0
- package/dist/agents/skills/codex/harness-git-workflow/skill.yaml +15 -0
- package/dist/agents/skills/codex/harness-hotspot-detector/skill.yaml +9 -0
- package/dist/agents/skills/codex/harness-i18n/skill.yaml +22 -0
- package/dist/agents/skills/codex/harness-i18n-process/skill.yaml +15 -0
- package/dist/agents/skills/codex/harness-i18n-workflow/skill.yaml +19 -0
- package/dist/agents/skills/codex/harness-integrity/skill.yaml +5 -0
- package/dist/agents/skills/codex/harness-perf/skill.yaml +3 -0
- package/dist/agents/skills/codex/harness-perf-tdd/skill.yaml +20 -0
- package/dist/agents/skills/codex/harness-pre-commit-review/skill.yaml +18 -0
- package/dist/agents/skills/codex/harness-refactoring/skill.yaml +9 -0
- package/dist/agents/skills/codex/harness-security-review/skill.yaml +23 -0
- package/dist/agents/skills/codex/harness-security-scan/skill.yaml +3 -0
- package/dist/agents/skills/codex/harness-soundness-review/skill.yaml +5 -0
- package/dist/agents/skills/codex/harness-supply-chain-audit/skill.yaml +3 -0
- package/dist/agents/skills/codex/harness-tdd/skill.yaml +3 -0
- package/dist/agents/skills/cursor/cleanup-dead-code/skill.yaml +3 -0
- package/dist/agents/skills/cursor/detect-doc-drift/skill.yaml +5 -0
- package/dist/agents/skills/cursor/enforce-architecture/skill.yaml +13 -0
- package/dist/agents/skills/cursor/harness-accessibility/skill.yaml +20 -0
- package/dist/agents/skills/cursor/harness-code-review/skill.yaml +5 -0
- package/dist/agents/skills/cursor/harness-codebase-cleanup/skill.yaml +5 -0
- package/dist/agents/skills/cursor/harness-debugging/skill.yaml +5 -0
- package/dist/agents/skills/cursor/harness-dependency-health/skill.yaml +9 -0
- package/dist/agents/skills/cursor/harness-design/skill.yaml +20 -0
- package/dist/agents/skills/cursor/harness-design-mobile/skill.yaml +20 -0
- package/dist/agents/skills/cursor/harness-design-system/skill.yaml +22 -0
- package/dist/agents/skills/cursor/harness-design-web/skill.yaml +22 -0
- package/dist/agents/skills/cursor/harness-diagnostics/skill.yaml +19 -0
- package/dist/agents/skills/cursor/harness-git-workflow/skill.yaml +15 -0
- package/dist/agents/skills/cursor/harness-hotspot-detector/skill.yaml +9 -0
- package/dist/agents/skills/cursor/harness-i18n/skill.yaml +22 -0
- package/dist/agents/skills/cursor/harness-i18n-process/skill.yaml +15 -0
- package/dist/agents/skills/cursor/harness-i18n-workflow/skill.yaml +19 -0
- package/dist/agents/skills/cursor/harness-integrity/skill.yaml +5 -0
- package/dist/agents/skills/cursor/harness-perf/skill.yaml +3 -0
- package/dist/agents/skills/cursor/harness-perf-tdd/skill.yaml +20 -0
- package/dist/agents/skills/cursor/harness-pre-commit-review/skill.yaml +18 -0
- package/dist/agents/skills/cursor/harness-refactoring/skill.yaml +9 -0
- package/dist/agents/skills/cursor/harness-security-review/skill.yaml +23 -0
- package/dist/agents/skills/cursor/harness-security-scan/skill.yaml +3 -0
- package/dist/agents/skills/cursor/harness-soundness-review/skill.yaml +5 -0
- package/dist/agents/skills/cursor/harness-supply-chain-audit/skill.yaml +3 -0
- package/dist/agents/skills/cursor/harness-tdd/skill.yaml +3 -0
- package/dist/agents/skills/gemini-cli/cleanup-dead-code/skill.yaml +3 -0
- package/dist/agents/skills/gemini-cli/detect-doc-drift/skill.yaml +5 -0
- package/dist/agents/skills/gemini-cli/enforce-architecture/skill.yaml +13 -0
- package/dist/agents/skills/gemini-cli/harness-accessibility/skill.yaml +20 -0
- package/dist/agents/skills/gemini-cli/harness-code-review/skill.yaml +5 -0
- package/dist/agents/skills/gemini-cli/harness-codebase-cleanup/skill.yaml +5 -0
- package/dist/agents/skills/gemini-cli/harness-debugging/skill.yaml +5 -0
- package/dist/agents/skills/gemini-cli/harness-dependency-health/skill.yaml +9 -0
- package/dist/agents/skills/gemini-cli/harness-design/skill.yaml +20 -0
- package/dist/agents/skills/gemini-cli/harness-design-mobile/skill.yaml +20 -0
- package/dist/agents/skills/gemini-cli/harness-design-system/skill.yaml +22 -0
- package/dist/agents/skills/gemini-cli/harness-design-web/skill.yaml +22 -0
- package/dist/agents/skills/gemini-cli/harness-diagnostics/skill.yaml +19 -0
- package/dist/agents/skills/gemini-cli/harness-git-workflow/skill.yaml +15 -0
- package/dist/agents/skills/gemini-cli/harness-hotspot-detector/skill.yaml +9 -0
- package/dist/agents/skills/gemini-cli/harness-i18n/skill.yaml +22 -0
- package/dist/agents/skills/gemini-cli/harness-i18n-process/skill.yaml +15 -0
- package/dist/agents/skills/gemini-cli/harness-i18n-workflow/skill.yaml +19 -0
- package/dist/agents/skills/gemini-cli/harness-integrity/skill.yaml +5 -0
- package/dist/agents/skills/gemini-cli/harness-perf/skill.yaml +3 -0
- package/dist/agents/skills/gemini-cli/harness-perf-tdd/skill.yaml +20 -0
- package/dist/agents/skills/gemini-cli/harness-pre-commit-review/skill.yaml +18 -0
- package/dist/agents/skills/gemini-cli/harness-refactoring/skill.yaml +9 -0
- package/dist/agents/skills/gemini-cli/harness-security-review/skill.yaml +23 -0
- package/dist/agents/skills/gemini-cli/harness-security-scan/skill.yaml +3 -0
- package/dist/agents/skills/gemini-cli/harness-soundness-review/skill.yaml +5 -0
- package/dist/agents/skills/gemini-cli/harness-supply-chain-audit/skill.yaml +3 -0
- package/dist/agents/skills/gemini-cli/harness-tdd/skill.yaml +3 -0
- package/dist/{agents-md-WHXVPOK2.js → agents-md-PM7LO74M.js} +2 -1
- package/dist/{architecture-45YCLD26.js → architecture-OVOCDTI6.js} +3 -2
- package/dist/assess-project-R2OZIDDS.js +9 -0
- package/dist/bin/harness-mcp.js +15 -13
- package/dist/bin/harness.js +21 -19
- package/dist/{check-phase-gate-2VXVOUJ5.js → check-phase-gate-7JQ6EW5R.js} +4 -3
- package/dist/{chunk-M6TIO6NF.js → chunk-2PAPHA77.js} +1 -1
- package/dist/chunk-5FBWWMY2.js +293 -0
- package/dist/{chunk-H6KZAGHZ.js → chunk-5QTWFO24.js} +8 -8
- package/dist/{chunk-CZZXE6BL.js → chunk-ASS5TD2Y.js} +1 -1
- package/dist/{chunk-45ZJPG24.js → chunk-B4WHXHF7.js} +1 -1
- package/dist/{chunk-SQY4AAKP.js → chunk-DJEBBENF.js} +1005 -408
- package/dist/{chunk-LEWXD6PR.js → chunk-DXYOAQQC.js} +1 -1
- package/dist/{chunk-PDEEQJHH.js → chunk-FSLFBLYW.js} +7 -7
- package/dist/{chunk-YDOGGQSF.js → chunk-GRJ7A4WT.js} +17 -2
- package/dist/{chunk-4U4V7A6U.js → chunk-IK5GSLW6.js} +4 -4
- package/dist/{chunk-LVJ7SCD7.js → chunk-J7W4LTRK.js} +2 -2
- package/dist/{chunk-PDOSLTWP.js → chunk-PUOMFNRO.js} +26 -3
- package/dist/{chunk-HKUX2X7O.js → chunk-SE4YPMLH.js} +9 -1
- package/dist/{chunk-HAJD5LTI.js → chunk-SOTTK27D.js} +459 -37
- package/dist/{chunk-LRG3B43J.js → chunk-T5QWCVGK.js} +1 -1
- package/dist/{dist-U7EAO6T2.js → chunk-TEZI27SA.js} +401 -60
- package/dist/{chunk-IC5CZSHF.js → chunk-U44JNY3Y.js} +1549 -593
- package/dist/{chunk-A33LHIRD.js → chunk-W6MPLFXU.js} +3 -3
- package/dist/{chunk-V73TEHIF.js → chunk-ZEIEUCZL.js} +9 -9
- package/dist/{ci-workflow-HWX5OVLI.js → ci-workflow-OTTEERPF.js} +2 -1
- package/dist/{create-skill-NDXQSTIK.js → create-skill-U3XCFRZN.js} +2 -2
- package/dist/dist-IA6XYKNO.js +92 -0
- package/dist/{dist-WHL3NN5S.js → dist-LCR2IO7U.js} +56 -3
- package/dist/{docs-FJFY7GF2.js → docs-CHAYSGOP.js} +4 -3
- package/dist/{engine-R5BZHIZB.js → engine-4MY2U5RZ.js} +2 -1
- package/dist/{entropy-Y2GE4MYS.js → entropy-AKSZG7G5.js} +3 -2
- package/dist/{feedback-FKZ7GMPO.js → feedback-QGCSW7SB.js} +1 -1
- package/dist/{generate-agent-definitions-LN3A45OL.js → generate-agent-definitions-KU6X2UQN.js} +2 -1
- package/dist/{graph-loader-KMHDQYDT.js → graph-loader-FJN4H7Y4.js} +1 -1
- package/dist/index.d.ts +58 -2
- package/dist/index.js +27 -23
- package/dist/{loader-2TBQUFWX.js → loader-AV5XEMER.js} +2 -1
- package/dist/{mcp-KEY575NJ.js → mcp-LWHVQRG7.js} +15 -13
- package/dist/{performance-BSOMMWK5.js → performance-ETZVXXGQ.js} +4 -3
- package/dist/{review-pipeline-KUBHP3RV.js → review-pipeline-3ZS3GJSP.js} +1 -1
- package/dist/{runtime-BN7KGJAO.js → runtime-KQTJRK3H.js} +2 -1
- package/dist/{security-3T4JGDZP.js → security-LJCLZES6.js} +1 -1
- package/dist/{skill-executor-XEVDGXUM.js → skill-executor-2BZQLHYN.js} +2 -2
- package/dist/{validate-R5WGB2AV.js → validate-4IA5RPEX.js} +3 -2
- package/dist/{validate-cross-check-76Z5P6EX.js → validate-cross-check-VX2BAHQI.js} +2 -1
- package/package.json +4 -4
|
@@ -3,6 +3,10 @@ import {
|
|
|
3
3
|
Ok,
|
|
4
4
|
SESSION_SECTION_NAMES
|
|
5
5
|
} from "./chunk-ERS5EVUZ.js";
|
|
6
|
+
import {
|
|
7
|
+
GraphStore,
|
|
8
|
+
queryTraceability
|
|
9
|
+
} from "./chunk-TEZI27SA.js";
|
|
6
10
|
|
|
7
11
|
// ../core/dist/chunk-BQUWXBGR.mjs
|
|
8
12
|
import { z } from "zod";
|
|
@@ -135,17 +139,17 @@ function resolveFileToLayer(file, layers) {
|
|
|
135
139
|
}
|
|
136
140
|
var accessAsync = promisify(access);
|
|
137
141
|
var readFileAsync = promisify(readFile);
|
|
138
|
-
async function fileExists(
|
|
142
|
+
async function fileExists(path28) {
|
|
139
143
|
try {
|
|
140
|
-
await accessAsync(
|
|
144
|
+
await accessAsync(path28, constants.F_OK);
|
|
141
145
|
return true;
|
|
142
146
|
} catch {
|
|
143
147
|
return false;
|
|
144
148
|
}
|
|
145
149
|
}
|
|
146
|
-
async function readFileContent(
|
|
150
|
+
async function readFileContent(path28) {
|
|
147
151
|
try {
|
|
148
|
-
const content = await readFileAsync(
|
|
152
|
+
const content = await readFileAsync(path28, "utf-8");
|
|
149
153
|
return Ok(content);
|
|
150
154
|
} catch (error) {
|
|
151
155
|
return Err(error);
|
|
@@ -1820,66 +1824,75 @@ import * as path from "path";
|
|
|
1820
1824
|
import { appendFileSync, writeFileSync as writeFileSync22, existsSync as existsSync22, mkdirSync as mkdirSync22 } from "fs";
|
|
1821
1825
|
import { dirname as dirname7 } from "path";
|
|
1822
1826
|
import { z as z3 } from "zod";
|
|
1823
|
-
import
|
|
1824
|
-
import
|
|
1825
|
-
import
|
|
1826
|
-
import
|
|
1827
|
+
import { readFileSync as readFileSync32, writeFileSync as writeFileSync3, renameSync as renameSync2, mkdirSync as mkdirSync3, existsSync as existsSync3 } from "fs";
|
|
1828
|
+
import { randomBytes as randomBytes2 } from "crypto";
|
|
1829
|
+
import { join as join8, dirname as dirname8 } from "path";
|
|
1830
|
+
import { z as z4 } from "zod";
|
|
1827
1831
|
import * as fs5 from "fs";
|
|
1828
1832
|
import * as path2 from "path";
|
|
1829
|
-
import { execSync } from "child_process";
|
|
1830
|
-
import { z as z4 } from "zod";
|
|
1831
1833
|
import * as fs6 from "fs";
|
|
1832
1834
|
import * as path3 from "path";
|
|
1833
|
-
import
|
|
1834
|
-
import * as path6 from "path";
|
|
1835
|
-
import * as crypto from "crypto";
|
|
1835
|
+
import { z as z5 } from "zod";
|
|
1836
1836
|
import * as fs10 from "fs";
|
|
1837
1837
|
import * as path7 from "path";
|
|
1838
|
+
import * as fs9 from "fs";
|
|
1839
|
+
import * as path6 from "path";
|
|
1840
|
+
import * as fs7 from "fs";
|
|
1841
|
+
import * as path4 from "path";
|
|
1842
|
+
import { execSync } from "child_process";
|
|
1843
|
+
import { z as z6 } from "zod";
|
|
1844
|
+
import * as fs8 from "fs";
|
|
1845
|
+
import * as path5 from "path";
|
|
1838
1846
|
import * as fs11 from "fs";
|
|
1839
1847
|
import * as path8 from "path";
|
|
1848
|
+
import * as crypto from "crypto";
|
|
1840
1849
|
import * as fs12 from "fs";
|
|
1841
1850
|
import * as path9 from "path";
|
|
1842
|
-
import { execSync as execSync2 } from "child_process";
|
|
1843
1851
|
import * as fs13 from "fs";
|
|
1844
1852
|
import * as path10 from "path";
|
|
1845
1853
|
import * as fs14 from "fs";
|
|
1846
1854
|
import * as path11 from "path";
|
|
1855
|
+
import { execSync as execSync2 } from "child_process";
|
|
1847
1856
|
import * as fs15 from "fs";
|
|
1848
1857
|
import * as path12 from "path";
|
|
1849
1858
|
import * as fs16 from "fs";
|
|
1850
1859
|
import * as path13 from "path";
|
|
1851
|
-
import { z as z5 } from "zod";
|
|
1852
|
-
import * as fs18 from "fs/promises";
|
|
1853
|
-
import { minimatch as minimatch4 } from "minimatch";
|
|
1854
|
-
import { z as z6 } from "zod";
|
|
1855
1860
|
import * as fs17 from "fs";
|
|
1856
1861
|
import * as path14 from "path";
|
|
1857
|
-
import
|
|
1858
|
-
import { join as join21, dirname as dirname8 } from "path";
|
|
1862
|
+
import * as fs18 from "fs";
|
|
1859
1863
|
import * as path15 from "path";
|
|
1864
|
+
import { z as z7 } from "zod";
|
|
1865
|
+
import * as fs20 from "fs/promises";
|
|
1866
|
+
import { minimatch as minimatch4 } from "minimatch";
|
|
1867
|
+
import { z as z8 } from "zod";
|
|
1868
|
+
import * as fs19 from "fs";
|
|
1860
1869
|
import * as path16 from "path";
|
|
1870
|
+
import { readFileSync as readFileSync17, writeFileSync as writeFileSync12, unlinkSync, mkdirSync as mkdirSync12, readdirSync as readdirSync3 } from "fs";
|
|
1871
|
+
import { join as join242, dirname as dirname9 } from "path";
|
|
1861
1872
|
import * as path17 from "path";
|
|
1862
1873
|
import * as path18 from "path";
|
|
1863
|
-
import * as fs19 from "fs";
|
|
1864
1874
|
import * as path19 from "path";
|
|
1865
|
-
import * as fs20 from "fs";
|
|
1866
|
-
import { z as z7 } from "zod";
|
|
1867
|
-
import * as fs21 from "fs/promises";
|
|
1868
1875
|
import * as path20 from "path";
|
|
1869
|
-
import * as
|
|
1876
|
+
import * as fs21 from "fs";
|
|
1870
1877
|
import * as path21 from "path";
|
|
1871
|
-
import * as
|
|
1872
|
-
import
|
|
1878
|
+
import * as fs22 from "fs";
|
|
1879
|
+
import { z as z9 } from "zod";
|
|
1880
|
+
import * as fs23 from "fs/promises";
|
|
1873
1881
|
import * as path22 from "path";
|
|
1874
|
-
import * as os from "os";
|
|
1875
|
-
import { spawn } from "child_process";
|
|
1876
|
-
import Parser from "web-tree-sitter";
|
|
1877
1882
|
import * as fs24 from "fs/promises";
|
|
1878
1883
|
import * as path23 from "path";
|
|
1884
|
+
import * as ejs from "ejs";
|
|
1879
1885
|
import * as fs25 from "fs";
|
|
1880
1886
|
import * as path24 from "path";
|
|
1881
|
-
import * as
|
|
1887
|
+
import * as os from "os";
|
|
1888
|
+
import { spawn } from "child_process";
|
|
1889
|
+
import Parser from "web-tree-sitter";
|
|
1890
|
+
import * as fs26 from "fs/promises";
|
|
1882
1891
|
import * as path25 from "path";
|
|
1892
|
+
import * as fs27 from "fs";
|
|
1893
|
+
import * as path26 from "path";
|
|
1894
|
+
import * as fs28 from "fs";
|
|
1895
|
+
import * as path27 from "path";
|
|
1883
1896
|
import * as os2 from "os";
|
|
1884
1897
|
async function validateFileStructure(projectPath, conventions) {
|
|
1885
1898
|
const missing = [];
|
|
@@ -1916,15 +1929,15 @@ function validateConfig(data, schema) {
|
|
|
1916
1929
|
let message = "Configuration validation failed";
|
|
1917
1930
|
const suggestions = [];
|
|
1918
1931
|
if (firstError) {
|
|
1919
|
-
const
|
|
1920
|
-
const pathDisplay =
|
|
1932
|
+
const path28 = firstError.path.join(".");
|
|
1933
|
+
const pathDisplay = path28 ? ` at "${path28}"` : "";
|
|
1921
1934
|
if (firstError.code === "invalid_type") {
|
|
1922
1935
|
const received = firstError.received;
|
|
1923
1936
|
const expected = firstError.expected;
|
|
1924
1937
|
if (received === "undefined") {
|
|
1925
1938
|
code = "MISSING_FIELD";
|
|
1926
1939
|
message = `Missing required field${pathDisplay}: ${firstError.message}`;
|
|
1927
|
-
suggestions.push(`Field "${
|
|
1940
|
+
suggestions.push(`Field "${path28}" is required and must be of type "${expected}"`);
|
|
1928
1941
|
} else {
|
|
1929
1942
|
code = "INVALID_TYPE";
|
|
1930
1943
|
message = `Invalid type${pathDisplay}: ${firstError.message}`;
|
|
@@ -2133,27 +2146,27 @@ function extractSections(content) {
|
|
|
2133
2146
|
}
|
|
2134
2147
|
return sections.map((section) => buildAgentMapSection(section, lines));
|
|
2135
2148
|
}
|
|
2136
|
-
function isExternalLink(
|
|
2137
|
-
return
|
|
2149
|
+
function isExternalLink(path28) {
|
|
2150
|
+
return path28.startsWith("http://") || path28.startsWith("https://") || path28.startsWith("#") || path28.startsWith("mailto:");
|
|
2138
2151
|
}
|
|
2139
2152
|
function resolveLinkPath(linkPath, baseDir) {
|
|
2140
2153
|
return linkPath.startsWith(".") ? join4(baseDir, linkPath) : linkPath;
|
|
2141
2154
|
}
|
|
2142
|
-
async function validateAgentsMap(
|
|
2143
|
-
const contentResult = await readFileContent(
|
|
2155
|
+
async function validateAgentsMap(path28 = "./AGENTS.md") {
|
|
2156
|
+
const contentResult = await readFileContent(path28);
|
|
2144
2157
|
if (!contentResult.ok) {
|
|
2145
2158
|
return Err(
|
|
2146
2159
|
createError(
|
|
2147
2160
|
"PARSE_ERROR",
|
|
2148
2161
|
`Failed to read AGENTS.md: ${contentResult.error.message}`,
|
|
2149
|
-
{ path:
|
|
2162
|
+
{ path: path28 },
|
|
2150
2163
|
["Ensure the file exists", "Check file permissions"]
|
|
2151
2164
|
)
|
|
2152
2165
|
);
|
|
2153
2166
|
}
|
|
2154
2167
|
const content = contentResult.value;
|
|
2155
2168
|
const sections = extractSections(content);
|
|
2156
|
-
const baseDir = dirname4(
|
|
2169
|
+
const baseDir = dirname4(path28);
|
|
2157
2170
|
const sectionTitles = sections.map((s) => s.title);
|
|
2158
2171
|
const missingSections = REQUIRED_SECTIONS.filter(
|
|
2159
2172
|
(required) => !sectionTitles.some((title) => title.toLowerCase().includes(required.toLowerCase()))
|
|
@@ -2287,8 +2300,8 @@ async function checkDocCoverage(domain, options = {}) {
|
|
|
2287
2300
|
);
|
|
2288
2301
|
}
|
|
2289
2302
|
}
|
|
2290
|
-
function suggestFix(
|
|
2291
|
-
const targetName = basename2(
|
|
2303
|
+
function suggestFix(path28, existingFiles) {
|
|
2304
|
+
const targetName = basename2(path28).toLowerCase();
|
|
2292
2305
|
const similar = existingFiles.find((file) => {
|
|
2293
2306
|
const fileName = basename2(file).toLowerCase();
|
|
2294
2307
|
return fileName.includes(targetName) || targetName.includes(fileName);
|
|
@@ -2296,7 +2309,7 @@ function suggestFix(path26, existingFiles) {
|
|
|
2296
2309
|
if (similar) {
|
|
2297
2310
|
return `Did you mean "${similar}"?`;
|
|
2298
2311
|
}
|
|
2299
|
-
return `Create the file "${
|
|
2312
|
+
return `Create the file "${path28}" or remove the link`;
|
|
2300
2313
|
}
|
|
2301
2314
|
async function validateKnowledgeMap(rootDir = process.cwd()) {
|
|
2302
2315
|
const agentsPath = join22(rootDir, "AGENTS.md");
|
|
@@ -2639,8 +2652,8 @@ function createBoundaryValidator(schema, name) {
|
|
|
2639
2652
|
return Ok(result.data);
|
|
2640
2653
|
}
|
|
2641
2654
|
const suggestions = result.error.issues.map((issue) => {
|
|
2642
|
-
const
|
|
2643
|
-
return
|
|
2655
|
+
const path28 = issue.path.join(".");
|
|
2656
|
+
return path28 ? `${path28}: ${issue.message}` : issue.message;
|
|
2644
2657
|
});
|
|
2645
2658
|
return Err(
|
|
2646
2659
|
createError(
|
|
@@ -3248,11 +3261,11 @@ function processExportListSpecifiers(exportDecl, exports) {
|
|
|
3248
3261
|
var TypeScriptParser = class {
|
|
3249
3262
|
name = "typescript";
|
|
3250
3263
|
extensions = [".ts", ".tsx", ".mts", ".cts"];
|
|
3251
|
-
async parseFile(
|
|
3252
|
-
const contentResult = await readFileContent(
|
|
3264
|
+
async parseFile(path28) {
|
|
3265
|
+
const contentResult = await readFileContent(path28);
|
|
3253
3266
|
if (!contentResult.ok) {
|
|
3254
3267
|
return Err(
|
|
3255
|
-
createParseError("NOT_FOUND", `File not found: ${
|
|
3268
|
+
createParseError("NOT_FOUND", `File not found: ${path28}`, { path: path28 }, [
|
|
3256
3269
|
"Check that the file exists",
|
|
3257
3270
|
"Verify the path is correct"
|
|
3258
3271
|
])
|
|
@@ -3262,7 +3275,7 @@ var TypeScriptParser = class {
|
|
|
3262
3275
|
const ast = parse(contentResult.value, {
|
|
3263
3276
|
loc: true,
|
|
3264
3277
|
range: true,
|
|
3265
|
-
jsx:
|
|
3278
|
+
jsx: path28.endsWith(".tsx"),
|
|
3266
3279
|
errorOnUnknownASTType: false
|
|
3267
3280
|
});
|
|
3268
3281
|
return Ok({
|
|
@@ -3273,7 +3286,7 @@ var TypeScriptParser = class {
|
|
|
3273
3286
|
} catch (e) {
|
|
3274
3287
|
const error = e;
|
|
3275
3288
|
return Err(
|
|
3276
|
-
createParseError("SYNTAX_ERROR", `Failed to parse ${
|
|
3289
|
+
createParseError("SYNTAX_ERROR", `Failed to parse ${path28}: ${error.message}`, { path: path28 }, [
|
|
3277
3290
|
"Check for syntax errors in the file",
|
|
3278
3291
|
"Ensure valid TypeScript syntax"
|
|
3279
3292
|
])
|
|
@@ -3454,22 +3467,22 @@ function extractInlineRefs(content) {
|
|
|
3454
3467
|
}
|
|
3455
3468
|
return refs;
|
|
3456
3469
|
}
|
|
3457
|
-
async function parseDocumentationFile(
|
|
3458
|
-
const contentResult = await readFileContent(
|
|
3470
|
+
async function parseDocumentationFile(path28) {
|
|
3471
|
+
const contentResult = await readFileContent(path28);
|
|
3459
3472
|
if (!contentResult.ok) {
|
|
3460
3473
|
return Err(
|
|
3461
3474
|
createEntropyError(
|
|
3462
3475
|
"PARSE_ERROR",
|
|
3463
|
-
`Failed to read documentation file: ${
|
|
3464
|
-
{ file:
|
|
3476
|
+
`Failed to read documentation file: ${path28}`,
|
|
3477
|
+
{ file: path28 },
|
|
3465
3478
|
["Check that the file exists"]
|
|
3466
3479
|
)
|
|
3467
3480
|
);
|
|
3468
3481
|
}
|
|
3469
3482
|
const content = contentResult.value;
|
|
3470
|
-
const type =
|
|
3483
|
+
const type = path28.endsWith(".md") ? "markdown" : "text";
|
|
3471
3484
|
return Ok({
|
|
3472
|
-
path:
|
|
3485
|
+
path: path28,
|
|
3473
3486
|
type,
|
|
3474
3487
|
content,
|
|
3475
3488
|
codeBlocks: extractCodeBlocks(content),
|
|
@@ -6379,96 +6392,1166 @@ function resolveThresholds2(scope, config) {
|
|
|
6379
6392
|
for (const [key, val] of Object.entries(config.thresholds)) {
|
|
6380
6393
|
projectThresholds[key] = typeof val === "object" && val !== null && !Array.isArray(val) ? { ...val } : val;
|
|
6381
6394
|
}
|
|
6382
|
-
if (scope === "project") {
|
|
6383
|
-
return projectThresholds;
|
|
6395
|
+
if (scope === "project") {
|
|
6396
|
+
return projectThresholds;
|
|
6397
|
+
}
|
|
6398
|
+
const moduleOverrides = config.modules[scope];
|
|
6399
|
+
if (!moduleOverrides) {
|
|
6400
|
+
return projectThresholds;
|
|
6401
|
+
}
|
|
6402
|
+
const merged = { ...projectThresholds };
|
|
6403
|
+
for (const [category, moduleValue] of Object.entries(moduleOverrides)) {
|
|
6404
|
+
const projectValue2 = projectThresholds[category];
|
|
6405
|
+
if (projectValue2 !== void 0 && typeof projectValue2 === "object" && !Array.isArray(projectValue2) && typeof moduleValue === "object" && !Array.isArray(moduleValue)) {
|
|
6406
|
+
merged[category] = {
|
|
6407
|
+
...projectValue2,
|
|
6408
|
+
...moduleValue
|
|
6409
|
+
};
|
|
6410
|
+
} else {
|
|
6411
|
+
merged[category] = moduleValue;
|
|
6412
|
+
}
|
|
6413
|
+
}
|
|
6414
|
+
return merged;
|
|
6415
|
+
}
|
|
6416
|
+
var CategorySnapshotSchema = z3.object({
|
|
6417
|
+
/** Aggregate metric value (e.g., violation count, avg complexity) */
|
|
6418
|
+
value: z3.number(),
|
|
6419
|
+
/** Count of violations in this category */
|
|
6420
|
+
violationCount: z3.number()
|
|
6421
|
+
});
|
|
6422
|
+
var TimelineSnapshotSchema = z3.object({
|
|
6423
|
+
/** ISO 8601 timestamp of capture */
|
|
6424
|
+
capturedAt: z3.string().datetime(),
|
|
6425
|
+
/** Git commit hash at capture time */
|
|
6426
|
+
commitHash: z3.string(),
|
|
6427
|
+
/** Composite stability score (0-100, higher is healthier) */
|
|
6428
|
+
stabilityScore: z3.number().min(0).max(100),
|
|
6429
|
+
/** Per-category metric aggregates */
|
|
6430
|
+
metrics: z3.record(ArchMetricCategorySchema, CategorySnapshotSchema)
|
|
6431
|
+
});
|
|
6432
|
+
var TimelineFileSchema = z3.object({
|
|
6433
|
+
version: z3.literal(1),
|
|
6434
|
+
snapshots: z3.array(TimelineSnapshotSchema)
|
|
6435
|
+
});
|
|
6436
|
+
var TrendLineSchema = z3.object({
|
|
6437
|
+
/** Current value */
|
|
6438
|
+
current: z3.number(),
|
|
6439
|
+
/** Previous value (from comparison snapshot) */
|
|
6440
|
+
previous: z3.number(),
|
|
6441
|
+
/** Absolute delta (current - previous) */
|
|
6442
|
+
delta: z3.number(),
|
|
6443
|
+
/** Direction indicator */
|
|
6444
|
+
direction: z3.enum(["improving", "stable", "declining"])
|
|
6445
|
+
});
|
|
6446
|
+
var TrendResultSchema = z3.object({
|
|
6447
|
+
/** Overall stability trend */
|
|
6448
|
+
stability: TrendLineSchema,
|
|
6449
|
+
/** Per-category trends */
|
|
6450
|
+
categories: z3.record(ArchMetricCategorySchema, TrendLineSchema),
|
|
6451
|
+
/** Number of snapshots analyzed */
|
|
6452
|
+
snapshotCount: z3.number(),
|
|
6453
|
+
/** Time range covered */
|
|
6454
|
+
from: z3.string(),
|
|
6455
|
+
to: z3.string()
|
|
6456
|
+
});
|
|
6457
|
+
var DEFAULT_STABILITY_THRESHOLDS = {
|
|
6458
|
+
"circular-deps": 5,
|
|
6459
|
+
"layer-violations": 10,
|
|
6460
|
+
complexity: 100,
|
|
6461
|
+
coupling: 2,
|
|
6462
|
+
"forbidden-imports": 5,
|
|
6463
|
+
"module-size": 10,
|
|
6464
|
+
"dependency-depth": 10
|
|
6465
|
+
};
|
|
6466
|
+
var ALL_CATEGORIES = ArchMetricCategorySchema.options;
|
|
6467
|
+
var TimelineManager = class {
|
|
6468
|
+
timelinePath;
|
|
6469
|
+
constructor(rootDir) {
|
|
6470
|
+
this.timelinePath = join8(rootDir, ".harness", "arch", "timeline.json");
|
|
6471
|
+
}
|
|
6472
|
+
/**
|
|
6473
|
+
* Load timeline from disk.
|
|
6474
|
+
* Returns empty TimelineFile if file does not exist or is invalid.
|
|
6475
|
+
*/
|
|
6476
|
+
load() {
|
|
6477
|
+
if (!existsSync3(this.timelinePath)) {
|
|
6478
|
+
return { version: 1, snapshots: [] };
|
|
6479
|
+
}
|
|
6480
|
+
try {
|
|
6481
|
+
const raw = readFileSync32(this.timelinePath, "utf-8");
|
|
6482
|
+
const data = JSON.parse(raw);
|
|
6483
|
+
const parsed = TimelineFileSchema.safeParse(data);
|
|
6484
|
+
if (!parsed.success) {
|
|
6485
|
+
console.error(
|
|
6486
|
+
`Timeline validation failed for ${this.timelinePath}:`,
|
|
6487
|
+
parsed.error.format()
|
|
6488
|
+
);
|
|
6489
|
+
return { version: 1, snapshots: [] };
|
|
6490
|
+
}
|
|
6491
|
+
return parsed.data;
|
|
6492
|
+
} catch (error) {
|
|
6493
|
+
console.error(`Error loading timeline from ${this.timelinePath}:`, error);
|
|
6494
|
+
return { version: 1, snapshots: [] };
|
|
6495
|
+
}
|
|
6496
|
+
}
|
|
6497
|
+
/**
|
|
6498
|
+
* Save timeline to disk using atomic write (temp file + rename).
|
|
6499
|
+
* Creates parent directories if they do not exist.
|
|
6500
|
+
*/
|
|
6501
|
+
save(timeline) {
|
|
6502
|
+
const dir = dirname8(this.timelinePath);
|
|
6503
|
+
if (!existsSync3(dir)) {
|
|
6504
|
+
mkdirSync3(dir, { recursive: true });
|
|
6505
|
+
}
|
|
6506
|
+
const tmp = this.timelinePath + "." + randomBytes2(4).toString("hex") + ".tmp";
|
|
6507
|
+
writeFileSync3(tmp, JSON.stringify(timeline, null, 2));
|
|
6508
|
+
renameSync2(tmp, this.timelinePath);
|
|
6509
|
+
}
|
|
6510
|
+
/**
|
|
6511
|
+
* Capture a new snapshot from current metric results.
|
|
6512
|
+
* Aggregates MetricResult[] by category, computes stability score,
|
|
6513
|
+
* appends to timeline (or replaces if same commitHash), and saves.
|
|
6514
|
+
*/
|
|
6515
|
+
capture(results, commitHash) {
|
|
6516
|
+
const metrics = this.aggregateByCategory(results);
|
|
6517
|
+
const stabilityScore = this.computeStabilityScore(metrics);
|
|
6518
|
+
const snapshot = {
|
|
6519
|
+
capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6520
|
+
commitHash,
|
|
6521
|
+
stabilityScore,
|
|
6522
|
+
metrics
|
|
6523
|
+
};
|
|
6524
|
+
const timeline = this.load();
|
|
6525
|
+
const lastIndex = timeline.snapshots.length - 1;
|
|
6526
|
+
if (lastIndex >= 0 && timeline.snapshots[lastIndex].commitHash === commitHash) {
|
|
6527
|
+
timeline.snapshots[lastIndex] = snapshot;
|
|
6528
|
+
} else {
|
|
6529
|
+
timeline.snapshots.push(snapshot);
|
|
6530
|
+
}
|
|
6531
|
+
this.save(timeline);
|
|
6532
|
+
return snapshot;
|
|
6533
|
+
}
|
|
6534
|
+
/**
|
|
6535
|
+
* Compute trends between snapshots over a window.
|
|
6536
|
+
* @param options.last - Number of recent snapshots to analyze (default: 10)
|
|
6537
|
+
* @param options.since - ISO date string to filter snapshots from
|
|
6538
|
+
*/
|
|
6539
|
+
trends(options) {
|
|
6540
|
+
const timeline = this.load();
|
|
6541
|
+
let snapshots = timeline.snapshots;
|
|
6542
|
+
if (options?.since) {
|
|
6543
|
+
const sinceDate = new Date(options.since);
|
|
6544
|
+
snapshots = snapshots.filter((s) => new Date(s.capturedAt) >= sinceDate);
|
|
6545
|
+
}
|
|
6546
|
+
if (options?.last && snapshots.length > options.last) {
|
|
6547
|
+
snapshots = snapshots.slice(-options.last);
|
|
6548
|
+
}
|
|
6549
|
+
if (snapshots.length === 0) {
|
|
6550
|
+
return this.emptyTrendResult();
|
|
6551
|
+
}
|
|
6552
|
+
if (snapshots.length === 1) {
|
|
6553
|
+
const only = snapshots[0];
|
|
6554
|
+
const m = only.metrics;
|
|
6555
|
+
return {
|
|
6556
|
+
stability: this.buildTrendLine(only.stabilityScore, only.stabilityScore, true),
|
|
6557
|
+
categories: this.buildCategoryTrends(m, m),
|
|
6558
|
+
snapshotCount: 1,
|
|
6559
|
+
from: only.capturedAt,
|
|
6560
|
+
to: only.capturedAt
|
|
6561
|
+
};
|
|
6562
|
+
}
|
|
6563
|
+
const first = snapshots[0];
|
|
6564
|
+
const last = snapshots[snapshots.length - 1];
|
|
6565
|
+
return {
|
|
6566
|
+
stability: this.buildTrendLine(last.stabilityScore, first.stabilityScore, true),
|
|
6567
|
+
categories: this.buildCategoryTrends(
|
|
6568
|
+
last.metrics,
|
|
6569
|
+
first.metrics
|
|
6570
|
+
),
|
|
6571
|
+
snapshotCount: snapshots.length,
|
|
6572
|
+
from: first.capturedAt,
|
|
6573
|
+
to: last.capturedAt
|
|
6574
|
+
};
|
|
6575
|
+
}
|
|
6576
|
+
/**
|
|
6577
|
+
* Compute composite stability score from category metrics.
|
|
6578
|
+
* Equal weight across all categories. Score is 0-100 (higher = healthier).
|
|
6579
|
+
* health = max(0, 1 - (value / threshold)) per category.
|
|
6580
|
+
*/
|
|
6581
|
+
computeStabilityScore(metrics, thresholds = DEFAULT_STABILITY_THRESHOLDS) {
|
|
6582
|
+
const healthScores = [];
|
|
6583
|
+
for (const category of ALL_CATEGORIES) {
|
|
6584
|
+
const snapshot = metrics[category];
|
|
6585
|
+
if (!snapshot) {
|
|
6586
|
+
healthScores.push(1);
|
|
6587
|
+
continue;
|
|
6588
|
+
}
|
|
6589
|
+
const threshold = thresholds[category] ?? 10;
|
|
6590
|
+
const health = Math.max(0, 1 - snapshot.value / threshold);
|
|
6591
|
+
healthScores.push(health);
|
|
6592
|
+
}
|
|
6593
|
+
const mean = healthScores.reduce((sum, h) => sum + h, 0) / healthScores.length;
|
|
6594
|
+
return Math.round(mean * 100);
|
|
6595
|
+
}
|
|
6596
|
+
// --- Private helpers ---
|
|
6597
|
+
aggregateByCategory(results) {
|
|
6598
|
+
const metrics = {};
|
|
6599
|
+
for (const result of results) {
|
|
6600
|
+
const existing = metrics[result.category];
|
|
6601
|
+
if (existing) {
|
|
6602
|
+
existing.value += result.value;
|
|
6603
|
+
existing.violationCount += result.violations.length;
|
|
6604
|
+
} else {
|
|
6605
|
+
metrics[result.category] = {
|
|
6606
|
+
value: result.value,
|
|
6607
|
+
violationCount: result.violations.length
|
|
6608
|
+
};
|
|
6609
|
+
}
|
|
6610
|
+
}
|
|
6611
|
+
for (const category of ALL_CATEGORIES) {
|
|
6612
|
+
if (!metrics[category]) {
|
|
6613
|
+
metrics[category] = { value: 0, violationCount: 0 };
|
|
6614
|
+
}
|
|
6615
|
+
}
|
|
6616
|
+
return metrics;
|
|
6617
|
+
}
|
|
6618
|
+
buildTrendLine(current, previous, isStabilityScore) {
|
|
6619
|
+
const delta = current - previous;
|
|
6620
|
+
let direction;
|
|
6621
|
+
if (Math.abs(delta) < 2) {
|
|
6622
|
+
direction = "stable";
|
|
6623
|
+
} else if (isStabilityScore) {
|
|
6624
|
+
direction = delta > 0 ? "improving" : "declining";
|
|
6625
|
+
} else {
|
|
6626
|
+
direction = delta < 0 ? "improving" : "declining";
|
|
6627
|
+
}
|
|
6628
|
+
return { current, previous, delta, direction };
|
|
6629
|
+
}
|
|
6630
|
+
buildCategoryTrends(currentMetrics, previousMetrics) {
|
|
6631
|
+
const trends = {};
|
|
6632
|
+
for (const category of ALL_CATEGORIES) {
|
|
6633
|
+
const current = currentMetrics[category]?.value ?? 0;
|
|
6634
|
+
const previous = previousMetrics[category]?.value ?? 0;
|
|
6635
|
+
trends[category] = this.buildTrendLine(current, previous, false);
|
|
6636
|
+
}
|
|
6637
|
+
return trends;
|
|
6638
|
+
}
|
|
6639
|
+
emptyTrendResult() {
|
|
6640
|
+
const zeroLine = { current: 0, previous: 0, delta: 0, direction: "stable" };
|
|
6641
|
+
const categories = {};
|
|
6642
|
+
for (const category of ALL_CATEGORIES) {
|
|
6643
|
+
categories[category] = { ...zeroLine };
|
|
6644
|
+
}
|
|
6645
|
+
return {
|
|
6646
|
+
stability: { ...zeroLine },
|
|
6647
|
+
categories,
|
|
6648
|
+
snapshotCount: 0,
|
|
6649
|
+
from: "",
|
|
6650
|
+
to: ""
|
|
6651
|
+
};
|
|
6652
|
+
}
|
|
6653
|
+
};
|
|
6654
|
+
var ConfidenceTierSchema = z4.enum(["high", "medium", "low"]);
|
|
6655
|
+
var RegressionResultSchema = z4.object({
|
|
6656
|
+
slope: z4.number(),
|
|
6657
|
+
intercept: z4.number(),
|
|
6658
|
+
rSquared: z4.number().min(0).max(1),
|
|
6659
|
+
dataPoints: z4.number().int().min(0)
|
|
6660
|
+
});
|
|
6661
|
+
var DirectionSchema = z4.enum(["improving", "stable", "declining"]);
|
|
6662
|
+
var CategoryForecastSchema = z4.object({
|
|
6663
|
+
category: ArchMetricCategorySchema,
|
|
6664
|
+
current: z4.number(),
|
|
6665
|
+
threshold: z4.number(),
|
|
6666
|
+
projectedValue4w: z4.number(),
|
|
6667
|
+
projectedValue8w: z4.number(),
|
|
6668
|
+
projectedValue12w: z4.number(),
|
|
6669
|
+
thresholdCrossingWeeks: z4.number().nullable(),
|
|
6670
|
+
confidence: ConfidenceTierSchema,
|
|
6671
|
+
regression: RegressionResultSchema,
|
|
6672
|
+
direction: DirectionSchema
|
|
6673
|
+
});
|
|
6674
|
+
var SpecImpactSignalsSchema = z4.object({
|
|
6675
|
+
newFileCount: z4.number().int().min(0),
|
|
6676
|
+
affectedLayers: z4.array(z4.string()),
|
|
6677
|
+
newDependencies: z4.number().int().min(0),
|
|
6678
|
+
phaseCount: z4.number().int().min(0)
|
|
6679
|
+
});
|
|
6680
|
+
var SpecImpactEstimateSchema = z4.object({
|
|
6681
|
+
specPath: z4.string(),
|
|
6682
|
+
featureName: z4.string(),
|
|
6683
|
+
signals: SpecImpactSignalsSchema,
|
|
6684
|
+
deltas: z4.record(ArchMetricCategorySchema, z4.number()).optional()
|
|
6685
|
+
});
|
|
6686
|
+
var ContributingFeatureSchema = z4.object({
|
|
6687
|
+
name: z4.string(),
|
|
6688
|
+
specPath: z4.string(),
|
|
6689
|
+
delta: z4.number()
|
|
6690
|
+
});
|
|
6691
|
+
var AdjustedForecastSchema = z4.object({
|
|
6692
|
+
baseline: CategoryForecastSchema,
|
|
6693
|
+
adjusted: CategoryForecastSchema,
|
|
6694
|
+
contributingFeatures: z4.array(ContributingFeatureSchema)
|
|
6695
|
+
});
|
|
6696
|
+
var PredictionWarningSchema = z4.object({
|
|
6697
|
+
severity: z4.enum(["critical", "warning", "info"]),
|
|
6698
|
+
category: ArchMetricCategorySchema,
|
|
6699
|
+
message: z4.string(),
|
|
6700
|
+
weeksUntil: z4.number(),
|
|
6701
|
+
confidence: ConfidenceTierSchema,
|
|
6702
|
+
contributingFeatures: z4.array(z4.string())
|
|
6703
|
+
});
|
|
6704
|
+
var StabilityForecastSchema = z4.object({
|
|
6705
|
+
current: z4.number(),
|
|
6706
|
+
projected4w: z4.number(),
|
|
6707
|
+
projected8w: z4.number(),
|
|
6708
|
+
projected12w: z4.number(),
|
|
6709
|
+
confidence: ConfidenceTierSchema,
|
|
6710
|
+
direction: DirectionSchema
|
|
6711
|
+
});
|
|
6712
|
+
var PredictionResultSchema = z4.object({
|
|
6713
|
+
generatedAt: z4.string(),
|
|
6714
|
+
snapshotsUsed: z4.number().int().min(0),
|
|
6715
|
+
timelineRange: z4.object({
|
|
6716
|
+
from: z4.string(),
|
|
6717
|
+
to: z4.string()
|
|
6718
|
+
}),
|
|
6719
|
+
stabilityForecast: StabilityForecastSchema,
|
|
6720
|
+
categories: z4.record(ArchMetricCategorySchema, AdjustedForecastSchema),
|
|
6721
|
+
warnings: z4.array(PredictionWarningSchema)
|
|
6722
|
+
});
|
|
6723
|
+
var PredictionOptionsSchema = z4.object({
|
|
6724
|
+
horizon: z4.number().int().min(1).default(12),
|
|
6725
|
+
includeRoadmap: z4.boolean().default(true),
|
|
6726
|
+
categories: z4.array(ArchMetricCategorySchema).optional(),
|
|
6727
|
+
thresholds: z4.record(ArchMetricCategorySchema, z4.number()).optional()
|
|
6728
|
+
});
|
|
6729
|
+
function computeWeightedSums(points) {
|
|
6730
|
+
let sumW = 0, sumWt = 0, sumWv = 0, sumWtt = 0, sumWtv = 0;
|
|
6731
|
+
for (const p of points) {
|
|
6732
|
+
const w = p.weight;
|
|
6733
|
+
sumW += w;
|
|
6734
|
+
sumWt += w * p.t;
|
|
6735
|
+
sumWv += w * p.value;
|
|
6736
|
+
sumWtt += w * p.t * p.t;
|
|
6737
|
+
sumWtv += w * p.t * p.value;
|
|
6738
|
+
}
|
|
6739
|
+
return { sumW, sumWt, sumWv, sumWtt, sumWtv };
|
|
6740
|
+
}
|
|
6741
|
+
function computeRSquared(points, slope, intercept, meanV) {
|
|
6742
|
+
let ssRes = 0, ssTot = 0;
|
|
6743
|
+
for (const p of points) {
|
|
6744
|
+
const predicted = slope * p.t + intercept;
|
|
6745
|
+
ssRes += p.weight * (p.value - predicted) ** 2;
|
|
6746
|
+
ssTot += p.weight * (p.value - meanV) ** 2;
|
|
6747
|
+
}
|
|
6748
|
+
return ssTot < 1e-12 ? 1 : Math.max(0, 1 - ssRes / ssTot);
|
|
6749
|
+
}
|
|
6750
|
+
function weightedLinearRegression(points) {
|
|
6751
|
+
if (points.length < 2) {
|
|
6752
|
+
throw new Error(`Regression requires at least 2 data points, got ${points.length}`);
|
|
6753
|
+
}
|
|
6754
|
+
const n = points.length;
|
|
6755
|
+
const { sumW, sumWt, sumWv, sumWtt, sumWtv } = computeWeightedSums(points);
|
|
6756
|
+
const meanT = sumWt / sumW;
|
|
6757
|
+
const meanV = sumWv / sumW;
|
|
6758
|
+
const denominator = sumWtt - sumWt * sumWt / sumW;
|
|
6759
|
+
if (Math.abs(denominator) < 1e-12) {
|
|
6760
|
+
return { slope: 0, intercept: meanV, rSquared: 0, dataPoints: n };
|
|
6761
|
+
}
|
|
6762
|
+
const slope = (sumWtv - sumWt * sumWv / sumW) / denominator;
|
|
6763
|
+
const intercept = meanV - slope * meanT;
|
|
6764
|
+
const rSquared = computeRSquared(points, slope, intercept, meanV);
|
|
6765
|
+
return { slope, intercept, rSquared, dataPoints: n };
|
|
6766
|
+
}
|
|
6767
|
+
function applyRecencyWeights(values, decay = 0.85) {
|
|
6768
|
+
const n = values.length;
|
|
6769
|
+
return values.map((v, i) => ({
|
|
6770
|
+
t: v.t,
|
|
6771
|
+
value: v.value,
|
|
6772
|
+
weight: Math.pow(decay, n - 1 - i)
|
|
6773
|
+
}));
|
|
6774
|
+
}
|
|
6775
|
+
function projectValue(fit, t) {
|
|
6776
|
+
return fit.slope * t + fit.intercept;
|
|
6777
|
+
}
|
|
6778
|
+
function weeksUntilThreshold(fit, currentT, threshold) {
|
|
6779
|
+
if (fit.slope <= 0) {
|
|
6780
|
+
return null;
|
|
6781
|
+
}
|
|
6782
|
+
const currentProjected = projectValue(fit, currentT);
|
|
6783
|
+
if (currentProjected >= threshold) {
|
|
6784
|
+
return null;
|
|
6785
|
+
}
|
|
6786
|
+
const weeks = (threshold - currentProjected) / fit.slope;
|
|
6787
|
+
return Math.ceil(weeks);
|
|
6788
|
+
}
|
|
6789
|
+
function classifyConfidence(rSquared, dataPoints) {
|
|
6790
|
+
if (rSquared >= 0.7 && dataPoints >= 5) return "high";
|
|
6791
|
+
if (rSquared >= 0.4 && dataPoints >= 3) return "medium";
|
|
6792
|
+
return "low";
|
|
6793
|
+
}
|
|
6794
|
+
var VALID_STATUSES = /* @__PURE__ */ new Set([
|
|
6795
|
+
"backlog",
|
|
6796
|
+
"planned",
|
|
6797
|
+
"in-progress",
|
|
6798
|
+
"done",
|
|
6799
|
+
"blocked"
|
|
6800
|
+
]);
|
|
6801
|
+
var EM_DASH = "\u2014";
|
|
6802
|
+
var VALID_PRIORITIES = /* @__PURE__ */ new Set(["P0", "P1", "P2", "P3"]);
|
|
6803
|
+
function parseRoadmap(markdown) {
|
|
6804
|
+
const fmMatch = markdown.match(/^---\n([\s\S]*?)\n---/);
|
|
6805
|
+
if (!fmMatch) {
|
|
6806
|
+
return Err(new Error("Missing or malformed YAML frontmatter"));
|
|
6807
|
+
}
|
|
6808
|
+
const fmResult = parseFrontmatter(fmMatch[1]);
|
|
6809
|
+
if (!fmResult.ok) return fmResult;
|
|
6810
|
+
const body = markdown.slice(fmMatch[0].length);
|
|
6811
|
+
const milestonesResult = parseMilestones(body);
|
|
6812
|
+
if (!milestonesResult.ok) return milestonesResult;
|
|
6813
|
+
const historyResult = parseAssignmentHistory(body);
|
|
6814
|
+
if (!historyResult.ok) return historyResult;
|
|
6815
|
+
return Ok({
|
|
6816
|
+
frontmatter: fmResult.value,
|
|
6817
|
+
milestones: milestonesResult.value,
|
|
6818
|
+
assignmentHistory: historyResult.value
|
|
6819
|
+
});
|
|
6820
|
+
}
|
|
6821
|
+
function parseFrontmatter(raw) {
|
|
6822
|
+
const lines = raw.split("\n");
|
|
6823
|
+
const map = /* @__PURE__ */ new Map();
|
|
6824
|
+
for (const line of lines) {
|
|
6825
|
+
const idx = line.indexOf(":");
|
|
6826
|
+
if (idx === -1) continue;
|
|
6827
|
+
const key = line.slice(0, idx).trim();
|
|
6828
|
+
const val = line.slice(idx + 1).trim();
|
|
6829
|
+
map.set(key, val);
|
|
6830
|
+
}
|
|
6831
|
+
const project = map.get("project");
|
|
6832
|
+
const versionStr = map.get("version");
|
|
6833
|
+
const lastSynced = map.get("last_synced");
|
|
6834
|
+
const lastManualEdit = map.get("last_manual_edit");
|
|
6835
|
+
const created = map.get("created");
|
|
6836
|
+
const updated = map.get("updated");
|
|
6837
|
+
if (!project || !versionStr || !lastSynced || !lastManualEdit) {
|
|
6838
|
+
return Err(
|
|
6839
|
+
new Error(
|
|
6840
|
+
"Frontmatter missing required fields: project, version, last_synced, last_manual_edit"
|
|
6841
|
+
)
|
|
6842
|
+
);
|
|
6843
|
+
}
|
|
6844
|
+
const version = parseInt(versionStr, 10);
|
|
6845
|
+
if (isNaN(version)) {
|
|
6846
|
+
return Err(new Error("Frontmatter version must be a number"));
|
|
6847
|
+
}
|
|
6848
|
+
const fm = { project, version, lastSynced, lastManualEdit };
|
|
6849
|
+
if (created) fm.created = created;
|
|
6850
|
+
if (updated) fm.updated = updated;
|
|
6851
|
+
return Ok(fm);
|
|
6852
|
+
}
|
|
6853
|
+
function parseMilestones(body) {
|
|
6854
|
+
const milestones = [];
|
|
6855
|
+
const h2Pattern = /^## (.+)$/gm;
|
|
6856
|
+
const h2Matches = [];
|
|
6857
|
+
let match;
|
|
6858
|
+
let bodyEnd = body.length;
|
|
6859
|
+
while ((match = h2Pattern.exec(body)) !== null) {
|
|
6860
|
+
if (match[1] === "Assignment History") {
|
|
6861
|
+
bodyEnd = match.index;
|
|
6862
|
+
break;
|
|
6863
|
+
}
|
|
6864
|
+
h2Matches.push({ heading: match[1], startIndex: match.index, fullMatch: match[0] });
|
|
6865
|
+
}
|
|
6866
|
+
for (let i = 0; i < h2Matches.length; i++) {
|
|
6867
|
+
const h2 = h2Matches[i];
|
|
6868
|
+
const nextStart = i + 1 < h2Matches.length ? h2Matches[i + 1].startIndex : bodyEnd;
|
|
6869
|
+
const sectionBody = body.slice(h2.startIndex + h2.fullMatch.length, nextStart);
|
|
6870
|
+
const isBacklog = h2.heading === "Backlog";
|
|
6871
|
+
const milestoneName = isBacklog ? "Backlog" : h2.heading.replace(/^Milestone:\s*/, "");
|
|
6872
|
+
const featuresResult = parseFeatures(sectionBody);
|
|
6873
|
+
if (!featuresResult.ok) return featuresResult;
|
|
6874
|
+
milestones.push({
|
|
6875
|
+
name: milestoneName,
|
|
6876
|
+
isBacklog,
|
|
6877
|
+
features: featuresResult.value
|
|
6878
|
+
});
|
|
6879
|
+
}
|
|
6880
|
+
return Ok(milestones);
|
|
6881
|
+
}
|
|
6882
|
+
function parseFeatures(sectionBody) {
|
|
6883
|
+
const features = [];
|
|
6884
|
+
const h3Pattern = /^### (?:Feature: )?(.+)$/gm;
|
|
6885
|
+
const h3Matches = [];
|
|
6886
|
+
let match;
|
|
6887
|
+
while ((match = h3Pattern.exec(sectionBody)) !== null) {
|
|
6888
|
+
h3Matches.push({ name: match[1], startIndex: match.index, fullMatch: match[0] });
|
|
6889
|
+
}
|
|
6890
|
+
for (let i = 0; i < h3Matches.length; i++) {
|
|
6891
|
+
const h3 = h3Matches[i];
|
|
6892
|
+
const nextStart = i + 1 < h3Matches.length ? h3Matches[i + 1].startIndex : sectionBody.length;
|
|
6893
|
+
const featureBody = sectionBody.slice(h3.startIndex + h3.fullMatch.length, nextStart);
|
|
6894
|
+
const featureResult = parseFeatureFields(h3.name, featureBody);
|
|
6895
|
+
if (!featureResult.ok) return featureResult;
|
|
6896
|
+
features.push(featureResult.value);
|
|
6897
|
+
}
|
|
6898
|
+
return Ok(features);
|
|
6899
|
+
}
|
|
6900
|
+
function extractFieldMap(body) {
|
|
6901
|
+
const fieldMap = /* @__PURE__ */ new Map();
|
|
6902
|
+
const fieldPattern = /^- \*\*(.+?):\*\* (.+)$/gm;
|
|
6903
|
+
let match;
|
|
6904
|
+
while ((match = fieldPattern.exec(body)) !== null) {
|
|
6905
|
+
fieldMap.set(match[1], match[2]);
|
|
6906
|
+
}
|
|
6907
|
+
return fieldMap;
|
|
6908
|
+
}
|
|
6909
|
+
function parseListField(fieldMap, ...keys) {
|
|
6910
|
+
let raw = EM_DASH;
|
|
6911
|
+
for (const key of keys) {
|
|
6912
|
+
const val = fieldMap.get(key);
|
|
6913
|
+
if (val !== void 0) {
|
|
6914
|
+
raw = val;
|
|
6915
|
+
break;
|
|
6916
|
+
}
|
|
6917
|
+
}
|
|
6918
|
+
if (raw === EM_DASH || raw === "none") return [];
|
|
6919
|
+
return raw.split(",").map((s) => s.trim());
|
|
6920
|
+
}
|
|
6921
|
+
function parseFeatureFields(name, body) {
|
|
6922
|
+
const fieldMap = extractFieldMap(body);
|
|
6923
|
+
const statusRaw = fieldMap.get("Status");
|
|
6924
|
+
if (!statusRaw || !VALID_STATUSES.has(statusRaw)) {
|
|
6925
|
+
return Err(
|
|
6926
|
+
new Error(
|
|
6927
|
+
`Feature "${name}" has invalid status: "${statusRaw ?? "(missing)"}". Valid statuses: ${[...VALID_STATUSES].join(", ")}`
|
|
6928
|
+
)
|
|
6929
|
+
);
|
|
6930
|
+
}
|
|
6931
|
+
const specRaw = fieldMap.get("Spec") ?? EM_DASH;
|
|
6932
|
+
const plans = parseListField(fieldMap, "Plans", "Plan");
|
|
6933
|
+
const blockedBy = parseListField(fieldMap, "Blocked by", "Blockers");
|
|
6934
|
+
const assigneeRaw = fieldMap.get("Assignee") ?? EM_DASH;
|
|
6935
|
+
const priorityRaw = fieldMap.get("Priority") ?? EM_DASH;
|
|
6936
|
+
const externalIdRaw = fieldMap.get("External-ID") ?? EM_DASH;
|
|
6937
|
+
if (priorityRaw !== EM_DASH && !VALID_PRIORITIES.has(priorityRaw)) {
|
|
6938
|
+
return Err(
|
|
6939
|
+
new Error(
|
|
6940
|
+
`Feature "${name}" has invalid priority: "${priorityRaw}". Valid priorities: ${[...VALID_PRIORITIES].join(", ")}`
|
|
6941
|
+
)
|
|
6942
|
+
);
|
|
6943
|
+
}
|
|
6944
|
+
return Ok({
|
|
6945
|
+
name,
|
|
6946
|
+
status: statusRaw,
|
|
6947
|
+
spec: specRaw === EM_DASH ? null : specRaw,
|
|
6948
|
+
plans,
|
|
6949
|
+
blockedBy,
|
|
6950
|
+
summary: fieldMap.get("Summary") ?? "",
|
|
6951
|
+
assignee: assigneeRaw === EM_DASH ? null : assigneeRaw,
|
|
6952
|
+
priority: priorityRaw === EM_DASH ? null : priorityRaw,
|
|
6953
|
+
externalId: externalIdRaw === EM_DASH ? null : externalIdRaw
|
|
6954
|
+
});
|
|
6955
|
+
}
|
|
6956
|
+
function parseAssignmentHistory(body) {
|
|
6957
|
+
const historyMatch = body.match(/^## Assignment History\s*\n/m);
|
|
6958
|
+
if (!historyMatch || historyMatch.index === void 0) return Ok([]);
|
|
6959
|
+
const historyStart = historyMatch.index + historyMatch[0].length;
|
|
6960
|
+
const rawHistoryBody = body.slice(historyStart);
|
|
6961
|
+
const nextH2 = rawHistoryBody.search(/^## /m);
|
|
6962
|
+
const historyBody = nextH2 === -1 ? rawHistoryBody : rawHistoryBody.slice(0, nextH2);
|
|
6963
|
+
const records = [];
|
|
6964
|
+
const lines = historyBody.split("\n");
|
|
6965
|
+
let pastHeader = false;
|
|
6966
|
+
for (const line of lines) {
|
|
6967
|
+
const trimmed = line.trim();
|
|
6968
|
+
if (!trimmed.startsWith("|")) continue;
|
|
6969
|
+
if (!pastHeader) {
|
|
6970
|
+
if (trimmed.match(/^\|[-\s|]+\|$/)) {
|
|
6971
|
+
pastHeader = true;
|
|
6972
|
+
}
|
|
6973
|
+
continue;
|
|
6974
|
+
}
|
|
6975
|
+
const cells = trimmed.split("|").map((c) => c.trim()).filter((c) => c.length > 0);
|
|
6976
|
+
if (cells.length < 4) continue;
|
|
6977
|
+
const action = cells[2];
|
|
6978
|
+
if (!["assigned", "completed", "unassigned"].includes(action)) continue;
|
|
6979
|
+
records.push({
|
|
6980
|
+
feature: cells[0],
|
|
6981
|
+
assignee: cells[1],
|
|
6982
|
+
action,
|
|
6983
|
+
date: cells[3]
|
|
6984
|
+
});
|
|
6985
|
+
}
|
|
6986
|
+
return Ok(records);
|
|
6987
|
+
}
|
|
6988
|
+
var ALL_CATEGORIES2 = ArchMetricCategorySchema.options;
|
|
6989
|
+
var DIRECTION_THRESHOLD = 1e-3;
|
|
6990
|
+
var PredictionEngine = class {
|
|
6991
|
+
constructor(rootDir, timelineManager, estimator) {
|
|
6992
|
+
this.rootDir = rootDir;
|
|
6993
|
+
this.timelineManager = timelineManager;
|
|
6994
|
+
this.estimator = estimator;
|
|
6995
|
+
}
|
|
6996
|
+
rootDir;
|
|
6997
|
+
timelineManager;
|
|
6998
|
+
estimator;
|
|
6999
|
+
/**
|
|
7000
|
+
* Produce a PredictionResult with per-category forecasts and warnings.
|
|
7001
|
+
* Throws if fewer than 3 snapshots are available.
|
|
7002
|
+
*/
|
|
7003
|
+
predict(options) {
|
|
7004
|
+
const opts = this.resolveOptions(options);
|
|
7005
|
+
const timeline = this.timelineManager.load();
|
|
7006
|
+
const snapshots = timeline.snapshots;
|
|
7007
|
+
if (snapshots.length < 3) {
|
|
7008
|
+
throw new Error(
|
|
7009
|
+
`PredictionEngine requires at least 3 snapshots, got ${snapshots.length}. Run "harness snapshot" to capture more data points.`
|
|
7010
|
+
);
|
|
7011
|
+
}
|
|
7012
|
+
const thresholds = this.resolveThresholds(opts);
|
|
7013
|
+
const categoriesToProcess = opts.categories ?? [...ALL_CATEGORIES2];
|
|
7014
|
+
const firstDate = new Date(snapshots[0].capturedAt).getTime();
|
|
7015
|
+
const lastSnapshot = snapshots[snapshots.length - 1];
|
|
7016
|
+
const currentT = (new Date(lastSnapshot.capturedAt).getTime() - firstDate) / (7 * 24 * 60 * 60 * 1e3);
|
|
7017
|
+
const baselines = {};
|
|
7018
|
+
for (const category of ALL_CATEGORIES2) {
|
|
7019
|
+
const threshold = thresholds[category];
|
|
7020
|
+
const shouldProcess = categoriesToProcess.includes(category);
|
|
7021
|
+
if (!shouldProcess) {
|
|
7022
|
+
baselines[category] = this.zeroForecast(category, threshold);
|
|
7023
|
+
continue;
|
|
7024
|
+
}
|
|
7025
|
+
const timeSeries = this.extractTimeSeries(snapshots, category, firstDate);
|
|
7026
|
+
baselines[category] = this.forecastCategory(
|
|
7027
|
+
category,
|
|
7028
|
+
timeSeries,
|
|
7029
|
+
currentT,
|
|
7030
|
+
threshold,
|
|
7031
|
+
opts.horizon
|
|
7032
|
+
);
|
|
7033
|
+
}
|
|
7034
|
+
const specImpacts = this.computeSpecImpacts(opts);
|
|
7035
|
+
const categories = {};
|
|
7036
|
+
for (const category of ALL_CATEGORIES2) {
|
|
7037
|
+
const baseline = baselines[category];
|
|
7038
|
+
const threshold = thresholds[category];
|
|
7039
|
+
if (!specImpacts || specImpacts.length === 0) {
|
|
7040
|
+
categories[category] = {
|
|
7041
|
+
baseline,
|
|
7042
|
+
adjusted: baseline,
|
|
7043
|
+
contributingFeatures: []
|
|
7044
|
+
};
|
|
7045
|
+
continue;
|
|
7046
|
+
}
|
|
7047
|
+
let totalDelta = 0;
|
|
7048
|
+
const contributing = [];
|
|
7049
|
+
for (const impact of specImpacts) {
|
|
7050
|
+
const delta = impact.deltas?.[category] ?? 0;
|
|
7051
|
+
if (delta !== 0) {
|
|
7052
|
+
totalDelta += delta;
|
|
7053
|
+
contributing.push({
|
|
7054
|
+
name: impact.featureName,
|
|
7055
|
+
specPath: impact.specPath,
|
|
7056
|
+
delta
|
|
7057
|
+
});
|
|
7058
|
+
}
|
|
7059
|
+
}
|
|
7060
|
+
if (totalDelta === 0) {
|
|
7061
|
+
categories[category] = {
|
|
7062
|
+
baseline,
|
|
7063
|
+
adjusted: baseline,
|
|
7064
|
+
contributingFeatures: []
|
|
7065
|
+
};
|
|
7066
|
+
continue;
|
|
7067
|
+
}
|
|
7068
|
+
const adjusted = {
|
|
7069
|
+
...baseline,
|
|
7070
|
+
projectedValue4w: baseline.projectedValue4w + totalDelta,
|
|
7071
|
+
projectedValue8w: baseline.projectedValue8w + totalDelta,
|
|
7072
|
+
projectedValue12w: baseline.projectedValue12w + totalDelta
|
|
7073
|
+
};
|
|
7074
|
+
const adjustedFit = {
|
|
7075
|
+
slope: baseline.regression.slope,
|
|
7076
|
+
intercept: baseline.regression.intercept + totalDelta,
|
|
7077
|
+
rSquared: baseline.regression.rSquared,
|
|
7078
|
+
dataPoints: baseline.regression.dataPoints
|
|
7079
|
+
};
|
|
7080
|
+
adjusted.thresholdCrossingWeeks = weeksUntilThreshold(adjustedFit, currentT, threshold);
|
|
7081
|
+
adjusted.regression = {
|
|
7082
|
+
slope: adjustedFit.slope,
|
|
7083
|
+
intercept: adjustedFit.intercept,
|
|
7084
|
+
rSquared: adjustedFit.rSquared,
|
|
7085
|
+
dataPoints: adjustedFit.dataPoints
|
|
7086
|
+
};
|
|
7087
|
+
categories[category] = {
|
|
7088
|
+
baseline,
|
|
7089
|
+
adjusted,
|
|
7090
|
+
contributingFeatures: contributing
|
|
7091
|
+
};
|
|
7092
|
+
}
|
|
7093
|
+
const warnings = this.generateWarnings(
|
|
7094
|
+
categories,
|
|
7095
|
+
opts.horizon
|
|
7096
|
+
);
|
|
7097
|
+
const stabilityForecast = this.computeStabilityForecast(
|
|
7098
|
+
categories,
|
|
7099
|
+
thresholds,
|
|
7100
|
+
snapshots
|
|
7101
|
+
);
|
|
7102
|
+
return {
|
|
7103
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7104
|
+
snapshotsUsed: snapshots.length,
|
|
7105
|
+
timelineRange: {
|
|
7106
|
+
from: snapshots[0].capturedAt,
|
|
7107
|
+
to: lastSnapshot.capturedAt
|
|
7108
|
+
},
|
|
7109
|
+
stabilityForecast,
|
|
7110
|
+
categories,
|
|
7111
|
+
warnings
|
|
7112
|
+
};
|
|
7113
|
+
}
|
|
7114
|
+
// --- Private helpers ---
|
|
7115
|
+
resolveOptions(options) {
|
|
7116
|
+
return {
|
|
7117
|
+
horizon: options?.horizon ?? 12,
|
|
7118
|
+
includeRoadmap: options?.includeRoadmap ?? true,
|
|
7119
|
+
categories: options?.categories,
|
|
7120
|
+
thresholds: options?.thresholds
|
|
7121
|
+
};
|
|
7122
|
+
}
|
|
7123
|
+
resolveThresholds(opts) {
|
|
7124
|
+
const base = { ...DEFAULT_STABILITY_THRESHOLDS };
|
|
7125
|
+
if (opts.thresholds) {
|
|
7126
|
+
for (const [key, value] of Object.entries(opts.thresholds)) {
|
|
7127
|
+
if (value !== void 0) {
|
|
7128
|
+
base[key] = value;
|
|
7129
|
+
}
|
|
7130
|
+
}
|
|
7131
|
+
}
|
|
7132
|
+
return base;
|
|
7133
|
+
}
|
|
7134
|
+
/**
|
|
7135
|
+
* Extract time series for a single category from snapshots.
|
|
7136
|
+
* Returns array of { t (weeks from first), value } sorted oldest first.
|
|
7137
|
+
*/
|
|
7138
|
+
extractTimeSeries(snapshots, category, firstDateMs) {
|
|
7139
|
+
return snapshots.map((s) => {
|
|
7140
|
+
const t = (new Date(s.capturedAt).getTime() - firstDateMs) / (7 * 24 * 60 * 60 * 1e3);
|
|
7141
|
+
const metrics = s.metrics;
|
|
7142
|
+
const value = metrics[category]?.value ?? 0;
|
|
7143
|
+
return { t, value };
|
|
7144
|
+
});
|
|
7145
|
+
}
|
|
7146
|
+
/**
|
|
7147
|
+
* Produce a CategoryForecast for a single category using regression.
|
|
7148
|
+
*/
|
|
7149
|
+
forecastCategory(category, timeSeries, currentT, threshold, horizon = 12) {
|
|
7150
|
+
const weighted = applyRecencyWeights(timeSeries, 0.85);
|
|
7151
|
+
const fit = weightedLinearRegression(weighted);
|
|
7152
|
+
const current = timeSeries[timeSeries.length - 1].value;
|
|
7153
|
+
const h3 = Math.round(horizon / 3);
|
|
7154
|
+
const h2 = Math.round(horizon * 2 / 3);
|
|
7155
|
+
const projected4w = projectValue(fit, currentT + h3);
|
|
7156
|
+
const projected8w = projectValue(fit, currentT + h2);
|
|
7157
|
+
const projected12w = projectValue(fit, currentT + horizon);
|
|
7158
|
+
const crossing = weeksUntilThreshold(fit, currentT, threshold);
|
|
7159
|
+
const confidence = classifyConfidence(fit.rSquared, fit.dataPoints);
|
|
7160
|
+
const direction = this.classifyDirection(fit.slope);
|
|
7161
|
+
return {
|
|
7162
|
+
category,
|
|
7163
|
+
current,
|
|
7164
|
+
threshold,
|
|
7165
|
+
projectedValue4w: projected4w,
|
|
7166
|
+
projectedValue8w: projected8w,
|
|
7167
|
+
projectedValue12w: projected12w,
|
|
7168
|
+
thresholdCrossingWeeks: crossing,
|
|
7169
|
+
confidence,
|
|
7170
|
+
regression: {
|
|
7171
|
+
slope: fit.slope,
|
|
7172
|
+
intercept: fit.intercept,
|
|
7173
|
+
rSquared: fit.rSquared,
|
|
7174
|
+
dataPoints: fit.dataPoints
|
|
7175
|
+
},
|
|
7176
|
+
direction
|
|
7177
|
+
};
|
|
7178
|
+
}
|
|
7179
|
+
classifyDirection(slope) {
|
|
7180
|
+
if (Math.abs(slope) < DIRECTION_THRESHOLD) return "stable";
|
|
7181
|
+
return slope > 0 ? "declining" : "improving";
|
|
7182
|
+
}
|
|
7183
|
+
zeroForecast(category, threshold) {
|
|
7184
|
+
return {
|
|
7185
|
+
category,
|
|
7186
|
+
current: 0,
|
|
7187
|
+
threshold,
|
|
7188
|
+
projectedValue4w: 0,
|
|
7189
|
+
projectedValue8w: 0,
|
|
7190
|
+
projectedValue12w: 0,
|
|
7191
|
+
thresholdCrossingWeeks: null,
|
|
7192
|
+
confidence: "low",
|
|
7193
|
+
regression: { slope: 0, intercept: 0, rSquared: 0, dataPoints: 0 },
|
|
7194
|
+
direction: "stable"
|
|
7195
|
+
};
|
|
7196
|
+
}
|
|
7197
|
+
/**
|
|
7198
|
+
* Generate warnings based on severity rules from spec:
|
|
7199
|
+
* - critical: threshold crossing <= 4w, confidence high or medium
|
|
7200
|
+
* - warning: threshold crossing <= 8w, confidence high or medium
|
|
7201
|
+
* - info: threshold crossing <= 12w, any confidence
|
|
7202
|
+
*/
|
|
7203
|
+
generateWarnings(categories, horizon = 12) {
|
|
7204
|
+
const warnings = [];
|
|
7205
|
+
const criticalWindow = Math.round(horizon / 3);
|
|
7206
|
+
const warningWindow = Math.round(horizon * 2 / 3);
|
|
7207
|
+
for (const category of ALL_CATEGORIES2) {
|
|
7208
|
+
const af = categories[category];
|
|
7209
|
+
if (!af) continue;
|
|
7210
|
+
const forecast = af.adjusted;
|
|
7211
|
+
const crossing = forecast.thresholdCrossingWeeks;
|
|
7212
|
+
if (crossing === null || crossing <= 0) continue;
|
|
7213
|
+
let severity = null;
|
|
7214
|
+
if (crossing <= criticalWindow && (forecast.confidence === "high" || forecast.confidence === "medium")) {
|
|
7215
|
+
severity = "critical";
|
|
7216
|
+
} else if (crossing <= warningWindow && (forecast.confidence === "high" || forecast.confidence === "medium")) {
|
|
7217
|
+
severity = "warning";
|
|
7218
|
+
} else if (crossing <= horizon) {
|
|
7219
|
+
severity = "info";
|
|
7220
|
+
}
|
|
7221
|
+
if (severity) {
|
|
7222
|
+
const contributingNames = af.contributingFeatures.map((f) => f.name);
|
|
7223
|
+
warnings.push({
|
|
7224
|
+
severity,
|
|
7225
|
+
category,
|
|
7226
|
+
message: `${category} projected to exceed threshold (~${crossing}w, ${forecast.confidence} confidence)`,
|
|
7227
|
+
weeksUntil: crossing,
|
|
7228
|
+
confidence: forecast.confidence,
|
|
7229
|
+
contributingFeatures: contributingNames
|
|
7230
|
+
});
|
|
7231
|
+
}
|
|
7232
|
+
}
|
|
7233
|
+
return warnings;
|
|
7234
|
+
}
|
|
7235
|
+
/**
|
|
7236
|
+
* Compute composite stability forecast by projecting per-category values
|
|
7237
|
+
* forward and computing stability scores at each horizon.
|
|
7238
|
+
*/
|
|
7239
|
+
computeStabilityForecast(categories, thresholds, _snapshots) {
|
|
7240
|
+
const currentMetrics = this.buildMetricsFromForecasts(categories, "current");
|
|
7241
|
+
const current = this.timelineManager.computeStabilityScore(currentMetrics, thresholds);
|
|
7242
|
+
const metrics4w = this.buildMetricsFromForecasts(categories, "4w");
|
|
7243
|
+
const projected4w = this.timelineManager.computeStabilityScore(metrics4w, thresholds);
|
|
7244
|
+
const metrics8w = this.buildMetricsFromForecasts(categories, "8w");
|
|
7245
|
+
const projected8w = this.timelineManager.computeStabilityScore(metrics8w, thresholds);
|
|
7246
|
+
const metrics12w = this.buildMetricsFromForecasts(categories, "12w");
|
|
7247
|
+
const projected12w = this.timelineManager.computeStabilityScore(metrics12w, thresholds);
|
|
7248
|
+
const delta = projected12w - current;
|
|
7249
|
+
let direction;
|
|
7250
|
+
if (Math.abs(delta) < 2) {
|
|
7251
|
+
direction = "stable";
|
|
7252
|
+
} else {
|
|
7253
|
+
direction = delta > 0 ? "improving" : "declining";
|
|
7254
|
+
}
|
|
7255
|
+
const confidences = ALL_CATEGORIES2.map((c) => categories[c]?.adjusted.confidence ?? "low");
|
|
7256
|
+
const confidence = this.medianConfidence(confidences);
|
|
7257
|
+
return { current, projected4w, projected8w, projected12w, confidence, direction };
|
|
7258
|
+
}
|
|
7259
|
+
buildMetricsFromForecasts(categories, horizon) {
|
|
7260
|
+
const metrics = {};
|
|
7261
|
+
for (const cat of ALL_CATEGORIES2) {
|
|
7262
|
+
const forecast = categories[cat]?.adjusted;
|
|
7263
|
+
let value = 0;
|
|
7264
|
+
if (forecast) {
|
|
7265
|
+
switch (horizon) {
|
|
7266
|
+
case "current":
|
|
7267
|
+
value = forecast.current;
|
|
7268
|
+
break;
|
|
7269
|
+
case "4w":
|
|
7270
|
+
value = forecast.projectedValue4w;
|
|
7271
|
+
break;
|
|
7272
|
+
case "8w":
|
|
7273
|
+
value = forecast.projectedValue8w;
|
|
7274
|
+
break;
|
|
7275
|
+
case "12w":
|
|
7276
|
+
value = forecast.projectedValue12w;
|
|
7277
|
+
break;
|
|
7278
|
+
}
|
|
7279
|
+
}
|
|
7280
|
+
metrics[cat] = { value: Math.max(0, value), violationCount: 0 };
|
|
7281
|
+
}
|
|
7282
|
+
return metrics;
|
|
7283
|
+
}
|
|
7284
|
+
medianConfidence(confidences) {
|
|
7285
|
+
const order = { low: 0, medium: 1, high: 2 };
|
|
7286
|
+
const sorted = [...confidences].sort((a, b) => order[a] - order[b]);
|
|
7287
|
+
const mid = Math.floor(sorted.length / 2);
|
|
7288
|
+
return sorted[mid] ?? "low";
|
|
7289
|
+
}
|
|
7290
|
+
/**
|
|
7291
|
+
* Load roadmap features, estimate spec impacts via the estimator.
|
|
7292
|
+
* Returns null if estimator is null or includeRoadmap is false.
|
|
7293
|
+
*/
|
|
7294
|
+
computeSpecImpacts(opts) {
|
|
7295
|
+
if (!this.estimator || !opts.includeRoadmap) {
|
|
7296
|
+
return null;
|
|
7297
|
+
}
|
|
7298
|
+
try {
|
|
7299
|
+
const roadmapPath = path2.join(this.rootDir, "roadmap.md");
|
|
7300
|
+
const raw = fs5.readFileSync(roadmapPath, "utf-8");
|
|
7301
|
+
const parseResult = parseRoadmap(raw);
|
|
7302
|
+
if (!parseResult.ok) return null;
|
|
7303
|
+
const features = [];
|
|
7304
|
+
for (const milestone of parseResult.value.milestones) {
|
|
7305
|
+
for (const feature of milestone.features) {
|
|
7306
|
+
if (feature.status === "planned" || feature.status === "in-progress") {
|
|
7307
|
+
features.push({ name: feature.name, spec: feature.spec });
|
|
7308
|
+
}
|
|
7309
|
+
}
|
|
7310
|
+
}
|
|
7311
|
+
if (features.length === 0) return null;
|
|
7312
|
+
return this.estimator.estimateAll(features);
|
|
7313
|
+
} catch {
|
|
7314
|
+
return null;
|
|
7315
|
+
}
|
|
7316
|
+
}
|
|
7317
|
+
};
|
|
7318
|
+
var DEFAULT_COEFFICIENTS = {
|
|
7319
|
+
newFileModuleSize: 0.3,
|
|
7320
|
+
newFileComplexity: 1.5,
|
|
7321
|
+
layerViolation: 0.5,
|
|
7322
|
+
depCoupling: 0.2,
|
|
7323
|
+
depDepth: 0.3,
|
|
7324
|
+
phaseComplexity: 2
|
|
7325
|
+
};
|
|
7326
|
+
var SpecImpactEstimator = class {
|
|
7327
|
+
constructor(rootDir, coefficients) {
|
|
7328
|
+
this.rootDir = rootDir;
|
|
7329
|
+
this.coefficients = { ...DEFAULT_COEFFICIENTS, ...coefficients };
|
|
7330
|
+
this.layerNames = this.loadLayerNames();
|
|
7331
|
+
}
|
|
7332
|
+
rootDir;
|
|
7333
|
+
coefficients;
|
|
7334
|
+
layerNames;
|
|
7335
|
+
/**
|
|
7336
|
+
* Estimate impact of a single spec file.
|
|
7337
|
+
* @param specPath - Relative path from rootDir to the spec file.
|
|
7338
|
+
*/
|
|
7339
|
+
estimate(specPath) {
|
|
7340
|
+
const absolutePath = path3.join(this.rootDir, specPath);
|
|
7341
|
+
const content = fs6.readFileSync(absolutePath, "utf-8");
|
|
7342
|
+
const newFileCount = this.extractNewFileCount(content);
|
|
7343
|
+
const affectedLayers = this.extractAffectedLayers(content);
|
|
7344
|
+
const newDependencies = this.extractNewDependencies(content);
|
|
7345
|
+
const phaseCount = this.extractPhaseCount(content);
|
|
7346
|
+
const deltas = this.computeDeltas(
|
|
7347
|
+
newFileCount,
|
|
7348
|
+
affectedLayers.length,
|
|
7349
|
+
newDependencies,
|
|
7350
|
+
phaseCount
|
|
7351
|
+
);
|
|
7352
|
+
const h1Match = content.match(/^#\s+(.+)$/m);
|
|
7353
|
+
const featureName = h1Match ? h1Match[1].trim() : path3.basename(specPath, ".md");
|
|
7354
|
+
return {
|
|
7355
|
+
specPath,
|
|
7356
|
+
featureName,
|
|
7357
|
+
signals: {
|
|
7358
|
+
newFileCount,
|
|
7359
|
+
affectedLayers,
|
|
7360
|
+
newDependencies,
|
|
7361
|
+
phaseCount
|
|
7362
|
+
},
|
|
7363
|
+
deltas
|
|
7364
|
+
};
|
|
7365
|
+
}
|
|
7366
|
+
/**
|
|
7367
|
+
* Estimate impact for all planned features that have specs.
|
|
7368
|
+
* Skips features with null specs or specs that don't exist on disk.
|
|
7369
|
+
*/
|
|
7370
|
+
estimateAll(features) {
|
|
7371
|
+
const results = [];
|
|
7372
|
+
for (const feature of features) {
|
|
7373
|
+
if (!feature.spec) continue;
|
|
7374
|
+
const absolutePath = path3.join(this.rootDir, feature.spec);
|
|
7375
|
+
if (!fs6.existsSync(absolutePath)) continue;
|
|
7376
|
+
const estimate = this.estimate(feature.spec);
|
|
7377
|
+
results.push({ ...estimate, featureName: feature.name });
|
|
7378
|
+
}
|
|
7379
|
+
return results;
|
|
7380
|
+
}
|
|
7381
|
+
// --- Private: Signal Extraction ---
|
|
7382
|
+
/**
|
|
7383
|
+
* Count file paths in Technical Design sections that don't exist on disk.
|
|
7384
|
+
* Looks for paths in code blocks (```) under ## Technical Design.
|
|
7385
|
+
*/
|
|
7386
|
+
extractNewFileCount(content) {
|
|
7387
|
+
const techDesignMatch = content.match(/## Technical Design\b[\s\S]*?(?=\n## |\n# |$)/i);
|
|
7388
|
+
if (!techDesignMatch) return 0;
|
|
7389
|
+
const section = techDesignMatch[0];
|
|
7390
|
+
const codeBlocks = section.match(/```[\s\S]*?```/g) ?? [];
|
|
7391
|
+
const filePaths = [];
|
|
7392
|
+
for (const block of codeBlocks) {
|
|
7393
|
+
const inner = block.replace(/^```\w*\n?/, "").replace(/\n?```$/, "");
|
|
7394
|
+
for (const line of inner.split("\n")) {
|
|
7395
|
+
const trimmed = line.trim();
|
|
7396
|
+
if (trimmed.match(/^[\w@.-]+\/[\w./-]+\.\w+$/)) {
|
|
7397
|
+
filePaths.push(trimmed);
|
|
7398
|
+
}
|
|
7399
|
+
}
|
|
7400
|
+
}
|
|
7401
|
+
let count = 0;
|
|
7402
|
+
for (const fp of filePaths) {
|
|
7403
|
+
const absolute = path3.join(this.rootDir, fp);
|
|
7404
|
+
if (!fs6.existsSync(absolute)) {
|
|
7405
|
+
count++;
|
|
7406
|
+
}
|
|
7407
|
+
}
|
|
7408
|
+
return count;
|
|
7409
|
+
}
|
|
7410
|
+
/**
|
|
7411
|
+
* Match layer names from harness.config.json mentioned in the spec.
|
|
7412
|
+
* Returns deduplicated array of matched layer names.
|
|
7413
|
+
*/
|
|
7414
|
+
extractAffectedLayers(content) {
|
|
7415
|
+
if (this.layerNames.length === 0) return [];
|
|
7416
|
+
const matched = /* @__PURE__ */ new Set();
|
|
7417
|
+
for (const layer of this.layerNames) {
|
|
7418
|
+
const pattern = new RegExp(`\\b${this.escapeRegex(layer)}\\b`, "i");
|
|
7419
|
+
if (pattern.test(content)) {
|
|
7420
|
+
matched.add(layer);
|
|
7421
|
+
}
|
|
7422
|
+
}
|
|
7423
|
+
return [...matched].sort();
|
|
7424
|
+
}
|
|
7425
|
+
/**
|
|
7426
|
+
* Count dependency-related keywords: "import", "depend" (covers depends/dependency),
|
|
7427
|
+
* "package" in dependency context.
|
|
7428
|
+
*/
|
|
7429
|
+
extractNewDependencies(content) {
|
|
7430
|
+
const patterns = [/\bimport\b/gi, /\bdepend\w*\b/gi, /\bpackage\b/gi];
|
|
7431
|
+
let count = 0;
|
|
7432
|
+
for (const pattern of patterns) {
|
|
7433
|
+
const matches = content.match(pattern);
|
|
7434
|
+
if (matches) count += matches.length;
|
|
7435
|
+
}
|
|
7436
|
+
return count;
|
|
6384
7437
|
}
|
|
6385
|
-
|
|
6386
|
-
|
|
6387
|
-
|
|
7438
|
+
/**
|
|
7439
|
+
* Count H3/H4 headings under "Implementation" or "Implementation Order" sections.
|
|
7440
|
+
*/
|
|
7441
|
+
extractPhaseCount(content) {
|
|
7442
|
+
const implMatch = content.match(/## Implementation\b[\s\S]*?(?=\n## |\n# |$)/i);
|
|
7443
|
+
if (!implMatch) return 0;
|
|
7444
|
+
const section = implMatch[0];
|
|
7445
|
+
const headings = section.match(/^#{3,4}\s+.+$/gm);
|
|
7446
|
+
return headings ? headings.length : 0;
|
|
7447
|
+
}
|
|
7448
|
+
// --- Private: Delta Computation ---
|
|
7449
|
+
computeDeltas(newFileCount, crossLayerCount, newDependencies, phaseCount) {
|
|
7450
|
+
const deltas = {};
|
|
7451
|
+
const c = this.coefficients;
|
|
7452
|
+
const addDelta = (category, value) => {
|
|
7453
|
+
deltas[category] = (deltas[category] ?? 0) + value;
|
|
7454
|
+
};
|
|
7455
|
+
if (newFileCount > 0) {
|
|
7456
|
+
addDelta("module-size", newFileCount * c.newFileModuleSize);
|
|
7457
|
+
addDelta("complexity", newFileCount * c.newFileComplexity);
|
|
7458
|
+
}
|
|
7459
|
+
if (crossLayerCount > 0) {
|
|
7460
|
+
addDelta("layer-violations", crossLayerCount * c.layerViolation);
|
|
7461
|
+
}
|
|
7462
|
+
if (newDependencies > 0) {
|
|
7463
|
+
addDelta("coupling", newDependencies * c.depCoupling);
|
|
7464
|
+
addDelta("dependency-depth", newDependencies * c.depDepth);
|
|
7465
|
+
}
|
|
7466
|
+
if (phaseCount > 1) {
|
|
7467
|
+
addDelta("complexity", (phaseCount - 1) * c.phaseComplexity);
|
|
7468
|
+
}
|
|
7469
|
+
return deltas;
|
|
6388
7470
|
}
|
|
6389
|
-
|
|
6390
|
-
|
|
6391
|
-
|
|
6392
|
-
|
|
6393
|
-
|
|
6394
|
-
|
|
6395
|
-
|
|
6396
|
-
|
|
6397
|
-
|
|
6398
|
-
merged[category] = moduleValue;
|
|
7471
|
+
// --- Private: Config Loading ---
|
|
7472
|
+
loadLayerNames() {
|
|
7473
|
+
try {
|
|
7474
|
+
const configPath = path3.join(this.rootDir, "harness.config.json");
|
|
7475
|
+
const raw = fs6.readFileSync(configPath, "utf-8");
|
|
7476
|
+
const config = JSON.parse(raw);
|
|
7477
|
+
return (config.layers ?? []).map((l) => l.name);
|
|
7478
|
+
} catch {
|
|
7479
|
+
return [];
|
|
6399
7480
|
}
|
|
6400
7481
|
}
|
|
6401
|
-
|
|
6402
|
-
}
|
|
6403
|
-
|
|
6404
|
-
|
|
6405
|
-
|
|
6406
|
-
|
|
6407
|
-
|
|
7482
|
+
escapeRegex(str) {
|
|
7483
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
7484
|
+
}
|
|
7485
|
+
};
|
|
7486
|
+
var FailureEntrySchema = z5.object({
|
|
7487
|
+
date: z5.string(),
|
|
7488
|
+
skill: z5.string(),
|
|
7489
|
+
type: z5.string(),
|
|
7490
|
+
description: z5.string()
|
|
6408
7491
|
});
|
|
6409
|
-
var HandoffSchema =
|
|
6410
|
-
timestamp:
|
|
6411
|
-
fromSkill:
|
|
6412
|
-
phase:
|
|
6413
|
-
summary:
|
|
6414
|
-
completed:
|
|
6415
|
-
pending:
|
|
6416
|
-
concerns:
|
|
6417
|
-
decisions:
|
|
6418
|
-
|
|
6419
|
-
what:
|
|
6420
|
-
why:
|
|
7492
|
+
var HandoffSchema = z5.object({
|
|
7493
|
+
timestamp: z5.string(),
|
|
7494
|
+
fromSkill: z5.string(),
|
|
7495
|
+
phase: z5.string(),
|
|
7496
|
+
summary: z5.string(),
|
|
7497
|
+
completed: z5.array(z5.string()).default([]),
|
|
7498
|
+
pending: z5.array(z5.string()).default([]),
|
|
7499
|
+
concerns: z5.array(z5.string()).default([]),
|
|
7500
|
+
decisions: z5.array(
|
|
7501
|
+
z5.object({
|
|
7502
|
+
what: z5.string(),
|
|
7503
|
+
why: z5.string()
|
|
6421
7504
|
})
|
|
6422
7505
|
).default([]),
|
|
6423
|
-
blockers:
|
|
6424
|
-
contextKeywords:
|
|
7506
|
+
blockers: z5.array(z5.string()).default([]),
|
|
7507
|
+
contextKeywords: z5.array(z5.string()).default([])
|
|
6425
7508
|
});
|
|
6426
|
-
var GateCheckSchema =
|
|
6427
|
-
name:
|
|
6428
|
-
passed:
|
|
6429
|
-
command:
|
|
6430
|
-
output:
|
|
6431
|
-
duration:
|
|
7509
|
+
var GateCheckSchema = z5.object({
|
|
7510
|
+
name: z5.string(),
|
|
7511
|
+
passed: z5.boolean(),
|
|
7512
|
+
command: z5.string(),
|
|
7513
|
+
output: z5.string().optional(),
|
|
7514
|
+
duration: z5.number().optional()
|
|
6432
7515
|
});
|
|
6433
|
-
var GateResultSchema =
|
|
6434
|
-
passed:
|
|
6435
|
-
checks:
|
|
7516
|
+
var GateResultSchema = z5.object({
|
|
7517
|
+
passed: z5.boolean(),
|
|
7518
|
+
checks: z5.array(GateCheckSchema)
|
|
6436
7519
|
});
|
|
6437
|
-
var GateConfigSchema =
|
|
6438
|
-
checks:
|
|
6439
|
-
|
|
6440
|
-
name:
|
|
6441
|
-
command:
|
|
7520
|
+
var GateConfigSchema = z5.object({
|
|
7521
|
+
checks: z5.array(
|
|
7522
|
+
z5.object({
|
|
7523
|
+
name: z5.string(),
|
|
7524
|
+
command: z5.string()
|
|
6442
7525
|
})
|
|
6443
7526
|
).optional(),
|
|
6444
|
-
trace:
|
|
7527
|
+
trace: z5.boolean().optional()
|
|
6445
7528
|
});
|
|
6446
|
-
var HarnessStateSchema =
|
|
6447
|
-
schemaVersion:
|
|
6448
|
-
position:
|
|
6449
|
-
phase:
|
|
6450
|
-
task:
|
|
7529
|
+
var HarnessStateSchema = z5.object({
|
|
7530
|
+
schemaVersion: z5.literal(1),
|
|
7531
|
+
position: z5.object({
|
|
7532
|
+
phase: z5.string().optional(),
|
|
7533
|
+
task: z5.string().optional()
|
|
6451
7534
|
}).default({}),
|
|
6452
|
-
decisions:
|
|
6453
|
-
|
|
6454
|
-
date:
|
|
6455
|
-
decision:
|
|
6456
|
-
context:
|
|
7535
|
+
decisions: z5.array(
|
|
7536
|
+
z5.object({
|
|
7537
|
+
date: z5.string(),
|
|
7538
|
+
decision: z5.string(),
|
|
7539
|
+
context: z5.string()
|
|
6457
7540
|
})
|
|
6458
7541
|
).default([]),
|
|
6459
|
-
blockers:
|
|
6460
|
-
|
|
6461
|
-
id:
|
|
6462
|
-
description:
|
|
6463
|
-
status:
|
|
7542
|
+
blockers: z5.array(
|
|
7543
|
+
z5.object({
|
|
7544
|
+
id: z5.string(),
|
|
7545
|
+
description: z5.string(),
|
|
7546
|
+
status: z5.enum(["open", "resolved"])
|
|
6464
7547
|
})
|
|
6465
7548
|
).default([]),
|
|
6466
|
-
progress:
|
|
6467
|
-
lastSession:
|
|
6468
|
-
date:
|
|
6469
|
-
summary:
|
|
6470
|
-
lastSkill:
|
|
6471
|
-
pendingTasks:
|
|
7549
|
+
progress: z5.record(z5.enum(["pending", "in_progress", "complete"])).default({}),
|
|
7550
|
+
lastSession: z5.object({
|
|
7551
|
+
date: z5.string(),
|
|
7552
|
+
summary: z5.string(),
|
|
7553
|
+
lastSkill: z5.string().optional(),
|
|
7554
|
+
pendingTasks: z5.array(z5.string()).optional()
|
|
6472
7555
|
}).optional()
|
|
6473
7556
|
});
|
|
6474
7557
|
var DEFAULT_STATE = {
|
|
@@ -6478,16 +7561,16 @@ var DEFAULT_STATE = {
|
|
|
6478
7561
|
blockers: [],
|
|
6479
7562
|
progress: {}
|
|
6480
7563
|
};
|
|
6481
|
-
var StreamInfoSchema =
|
|
6482
|
-
name:
|
|
6483
|
-
branch:
|
|
6484
|
-
createdAt:
|
|
6485
|
-
lastActiveAt:
|
|
7564
|
+
var StreamInfoSchema = z6.object({
|
|
7565
|
+
name: z6.string(),
|
|
7566
|
+
branch: z6.string().optional(),
|
|
7567
|
+
createdAt: z6.string(),
|
|
7568
|
+
lastActiveAt: z6.string()
|
|
6486
7569
|
});
|
|
6487
|
-
var StreamIndexSchema =
|
|
6488
|
-
schemaVersion:
|
|
6489
|
-
activeStream:
|
|
6490
|
-
streams:
|
|
7570
|
+
var StreamIndexSchema = z6.object({
|
|
7571
|
+
schemaVersion: z6.literal(1),
|
|
7572
|
+
activeStream: z6.string().nullable(),
|
|
7573
|
+
streams: z6.record(StreamInfoSchema)
|
|
6491
7574
|
});
|
|
6492
7575
|
var DEFAULT_STREAM_INDEX = {
|
|
6493
7576
|
schemaVersion: 1,
|
|
@@ -6511,10 +7594,10 @@ var EVENTS_FILE = "events.jsonl";
|
|
|
6511
7594
|
var STREAMS_DIR = "streams";
|
|
6512
7595
|
var STREAM_NAME_REGEX = /^[a-z0-9][a-z0-9._-]*$/;
|
|
6513
7596
|
function streamsDir(projectPath) {
|
|
6514
|
-
return
|
|
7597
|
+
return path4.join(projectPath, HARNESS_DIR, STREAMS_DIR);
|
|
6515
7598
|
}
|
|
6516
7599
|
function indexPath(projectPath) {
|
|
6517
|
-
return
|
|
7600
|
+
return path4.join(streamsDir(projectPath), INDEX_FILE);
|
|
6518
7601
|
}
|
|
6519
7602
|
function validateStreamName(name) {
|
|
6520
7603
|
if (!STREAM_NAME_REGEX.test(name)) {
|
|
@@ -6528,11 +7611,11 @@ function validateStreamName(name) {
|
|
|
6528
7611
|
}
|
|
6529
7612
|
async function loadStreamIndex(projectPath) {
|
|
6530
7613
|
const idxPath = indexPath(projectPath);
|
|
6531
|
-
if (!
|
|
7614
|
+
if (!fs7.existsSync(idxPath)) {
|
|
6532
7615
|
return Ok({ ...DEFAULT_STREAM_INDEX, streams: {} });
|
|
6533
7616
|
}
|
|
6534
7617
|
try {
|
|
6535
|
-
const raw =
|
|
7618
|
+
const raw = fs7.readFileSync(idxPath, "utf-8");
|
|
6536
7619
|
const parsed = JSON.parse(raw);
|
|
6537
7620
|
const result = StreamIndexSchema.safeParse(parsed);
|
|
6538
7621
|
if (!result.success) {
|
|
@@ -6550,8 +7633,8 @@ async function loadStreamIndex(projectPath) {
|
|
|
6550
7633
|
async function saveStreamIndex(projectPath, index) {
|
|
6551
7634
|
const dir = streamsDir(projectPath);
|
|
6552
7635
|
try {
|
|
6553
|
-
|
|
6554
|
-
|
|
7636
|
+
fs7.mkdirSync(dir, { recursive: true });
|
|
7637
|
+
fs7.writeFileSync(indexPath(projectPath), JSON.stringify(index, null, 2));
|
|
6555
7638
|
return Ok(void 0);
|
|
6556
7639
|
} catch (error) {
|
|
6557
7640
|
return Err(
|
|
@@ -6592,18 +7675,18 @@ async function resolveStreamPath(projectPath, options) {
|
|
|
6592
7675
|
)
|
|
6593
7676
|
);
|
|
6594
7677
|
}
|
|
6595
|
-
return Ok(
|
|
7678
|
+
return Ok(path4.join(streamsDir(projectPath), options.stream));
|
|
6596
7679
|
}
|
|
6597
7680
|
const branch = getCurrentBranch(projectPath);
|
|
6598
7681
|
if (branch && branch !== "main" && branch !== "master") {
|
|
6599
7682
|
for (const [name, info] of Object.entries(index.streams)) {
|
|
6600
7683
|
if (info.branch === branch) {
|
|
6601
|
-
return Ok(
|
|
7684
|
+
return Ok(path4.join(streamsDir(projectPath), name));
|
|
6602
7685
|
}
|
|
6603
7686
|
}
|
|
6604
7687
|
}
|
|
6605
7688
|
if (index.activeStream && index.streams[index.activeStream]) {
|
|
6606
|
-
return Ok(
|
|
7689
|
+
return Ok(path4.join(streamsDir(projectPath), index.activeStream));
|
|
6607
7690
|
}
|
|
6608
7691
|
return Err(
|
|
6609
7692
|
new Error(
|
|
@@ -6631,9 +7714,9 @@ async function createStream(projectPath, name, branch) {
|
|
|
6631
7714
|
if (index.streams[name]) {
|
|
6632
7715
|
return Err(new Error(`Stream '${name}' already exists`));
|
|
6633
7716
|
}
|
|
6634
|
-
const streamPath =
|
|
7717
|
+
const streamPath = path4.join(streamsDir(projectPath), name);
|
|
6635
7718
|
try {
|
|
6636
|
-
|
|
7719
|
+
fs7.mkdirSync(streamPath, { recursive: true });
|
|
6637
7720
|
} catch (error) {
|
|
6638
7721
|
return Err(
|
|
6639
7722
|
new Error(
|
|
@@ -6674,12 +7757,12 @@ async function archiveStream(projectPath, name) {
|
|
|
6674
7757
|
if (!index.streams[name]) {
|
|
6675
7758
|
return Err(new Error(`Stream '${name}' not found`));
|
|
6676
7759
|
}
|
|
6677
|
-
const streamPath =
|
|
6678
|
-
const archiveDir =
|
|
7760
|
+
const streamPath = path4.join(streamsDir(projectPath), name);
|
|
7761
|
+
const archiveDir = path4.join(projectPath, HARNESS_DIR, "archive", "streams");
|
|
6679
7762
|
try {
|
|
6680
|
-
|
|
7763
|
+
fs7.mkdirSync(archiveDir, { recursive: true });
|
|
6681
7764
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
6682
|
-
|
|
7765
|
+
fs7.renameSync(streamPath, path4.join(archiveDir, `${name}-${date}`));
|
|
6683
7766
|
} catch (error) {
|
|
6684
7767
|
return Err(
|
|
6685
7768
|
new Error(
|
|
@@ -6701,19 +7784,19 @@ function getStreamForBranch(index, branch) {
|
|
|
6701
7784
|
}
|
|
6702
7785
|
var STATE_FILES = ["state.json", "handoff.json", "learnings.md", "failures.md"];
|
|
6703
7786
|
async function migrateToStreams(projectPath) {
|
|
6704
|
-
const harnessDir =
|
|
6705
|
-
if (
|
|
7787
|
+
const harnessDir = path4.join(projectPath, HARNESS_DIR);
|
|
7788
|
+
if (fs7.existsSync(indexPath(projectPath))) {
|
|
6706
7789
|
return Ok(void 0);
|
|
6707
7790
|
}
|
|
6708
|
-
const filesToMove = STATE_FILES.filter((f) =>
|
|
7791
|
+
const filesToMove = STATE_FILES.filter((f) => fs7.existsSync(path4.join(harnessDir, f)));
|
|
6709
7792
|
if (filesToMove.length === 0) {
|
|
6710
7793
|
return Ok(void 0);
|
|
6711
7794
|
}
|
|
6712
|
-
const defaultDir =
|
|
7795
|
+
const defaultDir = path4.join(streamsDir(projectPath), "default");
|
|
6713
7796
|
try {
|
|
6714
|
-
|
|
7797
|
+
fs7.mkdirSync(defaultDir, { recursive: true });
|
|
6715
7798
|
for (const file of filesToMove) {
|
|
6716
|
-
|
|
7799
|
+
fs7.renameSync(path4.join(harnessDir, file), path4.join(defaultDir, file));
|
|
6717
7800
|
}
|
|
6718
7801
|
} catch (error) {
|
|
6719
7802
|
return Err(
|
|
@@ -6743,26 +7826,26 @@ function resolveSessionDir(projectPath, sessionSlug, options) {
|
|
|
6743
7826
|
new Error(`Invalid session slug '${sessionSlug}': must not contain path traversal characters`)
|
|
6744
7827
|
);
|
|
6745
7828
|
}
|
|
6746
|
-
const sessionDir =
|
|
7829
|
+
const sessionDir = path5.join(projectPath, HARNESS_DIR, SESSIONS_DIR, sessionSlug);
|
|
6747
7830
|
if (options?.create) {
|
|
6748
|
-
|
|
7831
|
+
fs8.mkdirSync(sessionDir, { recursive: true });
|
|
6749
7832
|
}
|
|
6750
7833
|
return Ok(sessionDir);
|
|
6751
7834
|
}
|
|
6752
7835
|
function updateSessionIndex(projectPath, sessionSlug, description) {
|
|
6753
|
-
const sessionsDir =
|
|
6754
|
-
|
|
6755
|
-
const indexPath2 =
|
|
7836
|
+
const sessionsDir = path5.join(projectPath, HARNESS_DIR, SESSIONS_DIR);
|
|
7837
|
+
fs8.mkdirSync(sessionsDir, { recursive: true });
|
|
7838
|
+
const indexPath2 = path5.join(sessionsDir, SESSION_INDEX_FILE);
|
|
6756
7839
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
6757
7840
|
const newLine = `- [${sessionSlug}](${sessionSlug}/summary.md) \u2014 ${description} (${date})`;
|
|
6758
|
-
if (!
|
|
6759
|
-
|
|
7841
|
+
if (!fs8.existsSync(indexPath2)) {
|
|
7842
|
+
fs8.writeFileSync(indexPath2, `## Active Sessions
|
|
6760
7843
|
|
|
6761
7844
|
${newLine}
|
|
6762
7845
|
`);
|
|
6763
7846
|
return;
|
|
6764
7847
|
}
|
|
6765
|
-
const content =
|
|
7848
|
+
const content = fs8.readFileSync(indexPath2, "utf-8");
|
|
6766
7849
|
const lines = content.split("\n");
|
|
6767
7850
|
const slugPattern = `- [${sessionSlug}]`;
|
|
6768
7851
|
const existingIdx = lines.findIndex((l) => l.startsWith(slugPattern));
|
|
@@ -6772,7 +7855,7 @@ ${newLine}
|
|
|
6772
7855
|
const lastNonEmpty = lines.reduce((last, line, i) => line.trim() !== "" ? i : last, 0);
|
|
6773
7856
|
lines.splice(lastNonEmpty + 1, 0, newLine);
|
|
6774
7857
|
}
|
|
6775
|
-
|
|
7858
|
+
fs8.writeFileSync(indexPath2, lines.join("\n"));
|
|
6776
7859
|
}
|
|
6777
7860
|
var MAX_CACHE_ENTRIES = 8;
|
|
6778
7861
|
function evictIfNeeded(map) {
|
|
@@ -6786,8 +7869,8 @@ async function getStateDir(projectPath, stream, session) {
|
|
|
6786
7869
|
const sessionResult = resolveSessionDir(projectPath, session, { create: true });
|
|
6787
7870
|
return sessionResult;
|
|
6788
7871
|
}
|
|
6789
|
-
const streamsIndexPath =
|
|
6790
|
-
const hasStreams =
|
|
7872
|
+
const streamsIndexPath = path6.join(projectPath, HARNESS_DIR, "streams", INDEX_FILE);
|
|
7873
|
+
const hasStreams = fs9.existsSync(streamsIndexPath);
|
|
6791
7874
|
if (stream || hasStreams) {
|
|
6792
7875
|
const result = await resolveStreamPath(projectPath, stream ? { stream } : void 0);
|
|
6793
7876
|
if (result.ok) {
|
|
@@ -6797,18 +7880,18 @@ async function getStateDir(projectPath, stream, session) {
|
|
|
6797
7880
|
return result;
|
|
6798
7881
|
}
|
|
6799
7882
|
}
|
|
6800
|
-
return Ok(
|
|
7883
|
+
return Ok(path6.join(projectPath, HARNESS_DIR));
|
|
6801
7884
|
}
|
|
6802
7885
|
async function loadState(projectPath, stream, session) {
|
|
6803
7886
|
try {
|
|
6804
7887
|
const dirResult = await getStateDir(projectPath, stream, session);
|
|
6805
7888
|
if (!dirResult.ok) return dirResult;
|
|
6806
7889
|
const stateDir = dirResult.value;
|
|
6807
|
-
const statePath =
|
|
6808
|
-
if (!
|
|
7890
|
+
const statePath = path7.join(stateDir, STATE_FILE);
|
|
7891
|
+
if (!fs10.existsSync(statePath)) {
|
|
6809
7892
|
return Ok({ ...DEFAULT_STATE });
|
|
6810
7893
|
}
|
|
6811
|
-
const raw =
|
|
7894
|
+
const raw = fs10.readFileSync(statePath, "utf-8");
|
|
6812
7895
|
const parsed = JSON.parse(raw);
|
|
6813
7896
|
const result = HarnessStateSchema.safeParse(parsed);
|
|
6814
7897
|
if (!result.success) {
|
|
@@ -6826,9 +7909,9 @@ async function saveState(projectPath, state, stream, session) {
|
|
|
6826
7909
|
const dirResult = await getStateDir(projectPath, stream, session);
|
|
6827
7910
|
if (!dirResult.ok) return dirResult;
|
|
6828
7911
|
const stateDir = dirResult.value;
|
|
6829
|
-
const statePath =
|
|
6830
|
-
|
|
6831
|
-
|
|
7912
|
+
const statePath = path7.join(stateDir, STATE_FILE);
|
|
7913
|
+
fs10.mkdirSync(stateDir, { recursive: true });
|
|
7914
|
+
fs10.writeFileSync(statePath, JSON.stringify(state, null, 2));
|
|
6832
7915
|
return Ok(void 0);
|
|
6833
7916
|
} catch (error) {
|
|
6834
7917
|
return Err(
|
|
@@ -6836,7 +7919,7 @@ async function saveState(projectPath, state, stream, session) {
|
|
|
6836
7919
|
);
|
|
6837
7920
|
}
|
|
6838
7921
|
}
|
|
6839
|
-
function
|
|
7922
|
+
function parseFrontmatter2(line) {
|
|
6840
7923
|
const match = line.match(/^<!--\s+hash:([a-f0-9]+)(?:\s+tags:([^\s]+))?\s+-->/);
|
|
6841
7924
|
if (!match) return null;
|
|
6842
7925
|
const hash = match[1];
|
|
@@ -6862,10 +7945,10 @@ function computeContentHash(text) {
|
|
|
6862
7945
|
return crypto.createHash("sha256").update(text).digest("hex").slice(0, 16);
|
|
6863
7946
|
}
|
|
6864
7947
|
function loadContentHashes(stateDir) {
|
|
6865
|
-
const hashesPath =
|
|
6866
|
-
if (!
|
|
7948
|
+
const hashesPath = path8.join(stateDir, CONTENT_HASHES_FILE);
|
|
7949
|
+
if (!fs11.existsSync(hashesPath)) return {};
|
|
6867
7950
|
try {
|
|
6868
|
-
const raw =
|
|
7951
|
+
const raw = fs11.readFileSync(hashesPath, "utf-8");
|
|
6869
7952
|
const parsed = JSON.parse(raw);
|
|
6870
7953
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return {};
|
|
6871
7954
|
return parsed;
|
|
@@ -6874,13 +7957,13 @@ function loadContentHashes(stateDir) {
|
|
|
6874
7957
|
}
|
|
6875
7958
|
}
|
|
6876
7959
|
function saveContentHashes(stateDir, index) {
|
|
6877
|
-
const hashesPath =
|
|
6878
|
-
|
|
7960
|
+
const hashesPath = path8.join(stateDir, CONTENT_HASHES_FILE);
|
|
7961
|
+
fs11.writeFileSync(hashesPath, JSON.stringify(index, null, 2) + "\n");
|
|
6879
7962
|
}
|
|
6880
7963
|
function rebuildContentHashes(stateDir) {
|
|
6881
|
-
const learningsPath =
|
|
6882
|
-
if (!
|
|
6883
|
-
const content =
|
|
7964
|
+
const learningsPath = path8.join(stateDir, LEARNINGS_FILE);
|
|
7965
|
+
if (!fs11.existsSync(learningsPath)) return {};
|
|
7966
|
+
const content = fs11.readFileSync(learningsPath, "utf-8");
|
|
6884
7967
|
const lines = content.split("\n");
|
|
6885
7968
|
const index = {};
|
|
6886
7969
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -6923,18 +8006,18 @@ async function appendLearning(projectPath, learning, skillName, outcome, stream,
|
|
|
6923
8006
|
const dirResult = await getStateDir(projectPath, stream, session);
|
|
6924
8007
|
if (!dirResult.ok) return dirResult;
|
|
6925
8008
|
const stateDir = dirResult.value;
|
|
6926
|
-
const learningsPath =
|
|
6927
|
-
|
|
8009
|
+
const learningsPath = path8.join(stateDir, LEARNINGS_FILE);
|
|
8010
|
+
fs11.mkdirSync(stateDir, { recursive: true });
|
|
6928
8011
|
const normalizedContent = normalizeLearningContent(learning);
|
|
6929
8012
|
const contentHash = computeContentHash(normalizedContent);
|
|
6930
|
-
const hashesPath =
|
|
8013
|
+
const hashesPath = path8.join(stateDir, CONTENT_HASHES_FILE);
|
|
6931
8014
|
let contentHashes;
|
|
6932
|
-
if (
|
|
8015
|
+
if (fs11.existsSync(hashesPath)) {
|
|
6933
8016
|
contentHashes = loadContentHashes(stateDir);
|
|
6934
|
-
if (Object.keys(contentHashes).length === 0 &&
|
|
8017
|
+
if (Object.keys(contentHashes).length === 0 && fs11.existsSync(learningsPath)) {
|
|
6935
8018
|
contentHashes = rebuildContentHashes(stateDir);
|
|
6936
8019
|
}
|
|
6937
|
-
} else if (
|
|
8020
|
+
} else if (fs11.existsSync(learningsPath)) {
|
|
6938
8021
|
contentHashes = rebuildContentHashes(stateDir);
|
|
6939
8022
|
} else {
|
|
6940
8023
|
contentHashes = {};
|
|
@@ -6962,14 +8045,14 @@ ${frontmatter}
|
|
|
6962
8045
|
${bulletLine}
|
|
6963
8046
|
`;
|
|
6964
8047
|
let existingLineCount;
|
|
6965
|
-
if (!
|
|
6966
|
-
|
|
8048
|
+
if (!fs11.existsSync(learningsPath)) {
|
|
8049
|
+
fs11.writeFileSync(learningsPath, `# Learnings
|
|
6967
8050
|
${entry}`);
|
|
6968
8051
|
existingLineCount = 1;
|
|
6969
8052
|
} else {
|
|
6970
|
-
const existingContent =
|
|
8053
|
+
const existingContent = fs11.readFileSync(learningsPath, "utf-8");
|
|
6971
8054
|
existingLineCount = existingContent.split("\n").length;
|
|
6972
|
-
|
|
8055
|
+
fs11.appendFileSync(learningsPath, entry);
|
|
6973
8056
|
}
|
|
6974
8057
|
const bulletLine_lineNum = existingLineCount + 2;
|
|
6975
8058
|
contentHashes[contentHash] = { date: timestamp ?? "", line: bulletLine_lineNum };
|
|
@@ -7083,18 +8166,18 @@ async function loadIndexEntries(projectPath, skillName, stream, session) {
|
|
|
7083
8166
|
const dirResult = await getStateDir(projectPath, stream, session);
|
|
7084
8167
|
if (!dirResult.ok) return dirResult;
|
|
7085
8168
|
const stateDir = dirResult.value;
|
|
7086
|
-
const learningsPath =
|
|
7087
|
-
if (!
|
|
8169
|
+
const learningsPath = path8.join(stateDir, LEARNINGS_FILE);
|
|
8170
|
+
if (!fs11.existsSync(learningsPath)) {
|
|
7088
8171
|
return Ok([]);
|
|
7089
8172
|
}
|
|
7090
|
-
const content =
|
|
8173
|
+
const content = fs11.readFileSync(learningsPath, "utf-8");
|
|
7091
8174
|
const lines = content.split("\n");
|
|
7092
8175
|
const indexEntries = [];
|
|
7093
8176
|
let pendingFrontmatter = null;
|
|
7094
8177
|
let currentBlock = [];
|
|
7095
8178
|
for (const line of lines) {
|
|
7096
8179
|
if (line.startsWith("# ")) continue;
|
|
7097
|
-
const fm =
|
|
8180
|
+
const fm = parseFrontmatter2(line);
|
|
7098
8181
|
if (fm) {
|
|
7099
8182
|
pendingFrontmatter = fm;
|
|
7100
8183
|
continue;
|
|
@@ -7145,18 +8228,18 @@ async function loadRelevantLearnings(projectPath, skillName, stream, session) {
|
|
|
7145
8228
|
const dirResult = await getStateDir(projectPath, stream, session);
|
|
7146
8229
|
if (!dirResult.ok) return dirResult;
|
|
7147
8230
|
const stateDir = dirResult.value;
|
|
7148
|
-
const learningsPath =
|
|
7149
|
-
if (!
|
|
8231
|
+
const learningsPath = path8.join(stateDir, LEARNINGS_FILE);
|
|
8232
|
+
if (!fs11.existsSync(learningsPath)) {
|
|
7150
8233
|
return Ok([]);
|
|
7151
8234
|
}
|
|
7152
|
-
const stats =
|
|
8235
|
+
const stats = fs11.statSync(learningsPath);
|
|
7153
8236
|
const cacheKey = learningsPath;
|
|
7154
8237
|
const cached = learningsCacheMap.get(cacheKey);
|
|
7155
8238
|
let entries;
|
|
7156
8239
|
if (cached && cached.mtimeMs === stats.mtimeMs) {
|
|
7157
8240
|
entries = cached.entries;
|
|
7158
8241
|
} else {
|
|
7159
|
-
const content =
|
|
8242
|
+
const content = fs11.readFileSync(learningsPath, "utf-8");
|
|
7160
8243
|
const lines = content.split("\n");
|
|
7161
8244
|
entries = [];
|
|
7162
8245
|
let currentBlock = [];
|
|
@@ -7198,16 +8281,16 @@ async function archiveLearnings(projectPath, entries, stream) {
|
|
|
7198
8281
|
const dirResult = await getStateDir(projectPath, stream);
|
|
7199
8282
|
if (!dirResult.ok) return dirResult;
|
|
7200
8283
|
const stateDir = dirResult.value;
|
|
7201
|
-
const archiveDir =
|
|
7202
|
-
|
|
8284
|
+
const archiveDir = path8.join(stateDir, "learnings-archive");
|
|
8285
|
+
fs11.mkdirSync(archiveDir, { recursive: true });
|
|
7203
8286
|
const now = /* @__PURE__ */ new Date();
|
|
7204
8287
|
const yearMonth = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`;
|
|
7205
|
-
const archivePath =
|
|
8288
|
+
const archivePath = path8.join(archiveDir, `${yearMonth}.md`);
|
|
7206
8289
|
const archiveContent = entries.join("\n\n") + "\n";
|
|
7207
|
-
if (
|
|
7208
|
-
|
|
8290
|
+
if (fs11.existsSync(archivePath)) {
|
|
8291
|
+
fs11.appendFileSync(archivePath, "\n" + archiveContent);
|
|
7209
8292
|
} else {
|
|
7210
|
-
|
|
8293
|
+
fs11.writeFileSync(archivePath, `# Learnings Archive
|
|
7211
8294
|
|
|
7212
8295
|
${archiveContent}`);
|
|
7213
8296
|
}
|
|
@@ -7225,8 +8308,8 @@ async function pruneLearnings(projectPath, stream) {
|
|
|
7225
8308
|
const dirResult = await getStateDir(projectPath, stream);
|
|
7226
8309
|
if (!dirResult.ok) return dirResult;
|
|
7227
8310
|
const stateDir = dirResult.value;
|
|
7228
|
-
const learningsPath =
|
|
7229
|
-
if (!
|
|
8311
|
+
const learningsPath = path8.join(stateDir, LEARNINGS_FILE);
|
|
8312
|
+
if (!fs11.existsSync(learningsPath)) {
|
|
7230
8313
|
return Ok({ kept: 0, archived: 0, patterns: [] });
|
|
7231
8314
|
}
|
|
7232
8315
|
const loadResult = await loadRelevantLearnings(projectPath, void 0, stream);
|
|
@@ -7257,7 +8340,7 @@ async function pruneLearnings(projectPath, stream) {
|
|
|
7257
8340
|
if (!archiveResult.ok) return archiveResult;
|
|
7258
8341
|
}
|
|
7259
8342
|
const newContent = "# Learnings\n\n" + toKeep.join("\n\n") + "\n";
|
|
7260
|
-
|
|
8343
|
+
fs11.writeFileSync(learningsPath, newContent);
|
|
7261
8344
|
learningsCacheMap.delete(learningsPath);
|
|
7262
8345
|
return Ok({
|
|
7263
8346
|
kept: toKeep.length,
|
|
@@ -7302,19 +8385,19 @@ async function promoteSessionLearnings(projectPath, sessionSlug, stream) {
|
|
|
7302
8385
|
const dirResult = await getStateDir(projectPath, stream);
|
|
7303
8386
|
if (!dirResult.ok) return dirResult;
|
|
7304
8387
|
const stateDir = dirResult.value;
|
|
7305
|
-
const globalPath =
|
|
7306
|
-
const existingGlobal =
|
|
8388
|
+
const globalPath = path8.join(stateDir, LEARNINGS_FILE);
|
|
8389
|
+
const existingGlobal = fs11.existsSync(globalPath) ? fs11.readFileSync(globalPath, "utf-8") : "";
|
|
7307
8390
|
const newEntries = toPromote.filter((entry) => !existingGlobal.includes(entry.trim()));
|
|
7308
8391
|
if (newEntries.length === 0) {
|
|
7309
8392
|
return Ok({ promoted: 0, skipped: skipped + toPromote.length });
|
|
7310
8393
|
}
|
|
7311
8394
|
const promotedContent = newEntries.join("\n\n") + "\n";
|
|
7312
8395
|
if (!existingGlobal) {
|
|
7313
|
-
|
|
8396
|
+
fs11.writeFileSync(globalPath, `# Learnings
|
|
7314
8397
|
|
|
7315
8398
|
${promotedContent}`);
|
|
7316
8399
|
} else {
|
|
7317
|
-
|
|
8400
|
+
fs11.appendFileSync(globalPath, "\n\n" + promotedContent);
|
|
7318
8401
|
}
|
|
7319
8402
|
learningsCacheMap.delete(globalPath);
|
|
7320
8403
|
return Ok({
|
|
@@ -7344,17 +8427,17 @@ async function appendFailure(projectPath, description, skillName, type, stream,
|
|
|
7344
8427
|
const dirResult = await getStateDir(projectPath, stream, session);
|
|
7345
8428
|
if (!dirResult.ok) return dirResult;
|
|
7346
8429
|
const stateDir = dirResult.value;
|
|
7347
|
-
const failuresPath =
|
|
7348
|
-
|
|
8430
|
+
const failuresPath = path9.join(stateDir, FAILURES_FILE);
|
|
8431
|
+
fs12.mkdirSync(stateDir, { recursive: true });
|
|
7349
8432
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
7350
8433
|
const entry = `
|
|
7351
8434
|
- **${timestamp} [skill:${skillName}] [type:${type}]:** ${description}
|
|
7352
8435
|
`;
|
|
7353
|
-
if (!
|
|
7354
|
-
|
|
8436
|
+
if (!fs12.existsSync(failuresPath)) {
|
|
8437
|
+
fs12.writeFileSync(failuresPath, `# Failures
|
|
7355
8438
|
${entry}`);
|
|
7356
8439
|
} else {
|
|
7357
|
-
|
|
8440
|
+
fs12.appendFileSync(failuresPath, entry);
|
|
7358
8441
|
}
|
|
7359
8442
|
failuresCacheMap.delete(failuresPath);
|
|
7360
8443
|
return Ok(void 0);
|
|
@@ -7371,17 +8454,17 @@ async function loadFailures(projectPath, stream, session) {
|
|
|
7371
8454
|
const dirResult = await getStateDir(projectPath, stream, session);
|
|
7372
8455
|
if (!dirResult.ok) return dirResult;
|
|
7373
8456
|
const stateDir = dirResult.value;
|
|
7374
|
-
const failuresPath =
|
|
7375
|
-
if (!
|
|
8457
|
+
const failuresPath = path9.join(stateDir, FAILURES_FILE);
|
|
8458
|
+
if (!fs12.existsSync(failuresPath)) {
|
|
7376
8459
|
return Ok([]);
|
|
7377
8460
|
}
|
|
7378
|
-
const stats =
|
|
8461
|
+
const stats = fs12.statSync(failuresPath);
|
|
7379
8462
|
const cacheKey = failuresPath;
|
|
7380
8463
|
const cached = failuresCacheMap.get(cacheKey);
|
|
7381
8464
|
if (cached && cached.mtimeMs === stats.mtimeMs) {
|
|
7382
8465
|
return Ok(cached.entries);
|
|
7383
8466
|
}
|
|
7384
|
-
const content =
|
|
8467
|
+
const content = fs12.readFileSync(failuresPath, "utf-8");
|
|
7385
8468
|
const entries = [];
|
|
7386
8469
|
for (const line of content.split("\n")) {
|
|
7387
8470
|
const match = line.match(FAILURE_LINE_REGEX);
|
|
@@ -7410,20 +8493,20 @@ async function archiveFailures(projectPath, stream, session) {
|
|
|
7410
8493
|
const dirResult = await getStateDir(projectPath, stream, session);
|
|
7411
8494
|
if (!dirResult.ok) return dirResult;
|
|
7412
8495
|
const stateDir = dirResult.value;
|
|
7413
|
-
const failuresPath =
|
|
7414
|
-
if (!
|
|
8496
|
+
const failuresPath = path9.join(stateDir, FAILURES_FILE);
|
|
8497
|
+
if (!fs12.existsSync(failuresPath)) {
|
|
7415
8498
|
return Ok(void 0);
|
|
7416
8499
|
}
|
|
7417
|
-
const archiveDir =
|
|
7418
|
-
|
|
8500
|
+
const archiveDir = path9.join(stateDir, "archive");
|
|
8501
|
+
fs12.mkdirSync(archiveDir, { recursive: true });
|
|
7419
8502
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
7420
8503
|
let archiveName = `failures-${date}.md`;
|
|
7421
8504
|
let counter = 2;
|
|
7422
|
-
while (
|
|
8505
|
+
while (fs12.existsSync(path9.join(archiveDir, archiveName))) {
|
|
7423
8506
|
archiveName = `failures-${date}-${counter}.md`;
|
|
7424
8507
|
counter++;
|
|
7425
8508
|
}
|
|
7426
|
-
|
|
8509
|
+
fs12.renameSync(failuresPath, path9.join(archiveDir, archiveName));
|
|
7427
8510
|
failuresCacheMap.delete(failuresPath);
|
|
7428
8511
|
return Ok(void 0);
|
|
7429
8512
|
} catch (error) {
|
|
@@ -7439,9 +8522,9 @@ async function saveHandoff(projectPath, handoff, stream, session) {
|
|
|
7439
8522
|
const dirResult = await getStateDir(projectPath, stream, session);
|
|
7440
8523
|
if (!dirResult.ok) return dirResult;
|
|
7441
8524
|
const stateDir = dirResult.value;
|
|
7442
|
-
const handoffPath =
|
|
7443
|
-
|
|
7444
|
-
|
|
8525
|
+
const handoffPath = path10.join(stateDir, HANDOFF_FILE);
|
|
8526
|
+
fs13.mkdirSync(stateDir, { recursive: true });
|
|
8527
|
+
fs13.writeFileSync(handoffPath, JSON.stringify(handoff, null, 2));
|
|
7445
8528
|
return Ok(void 0);
|
|
7446
8529
|
} catch (error) {
|
|
7447
8530
|
return Err(
|
|
@@ -7454,11 +8537,11 @@ async function loadHandoff(projectPath, stream, session) {
|
|
|
7454
8537
|
const dirResult = await getStateDir(projectPath, stream, session);
|
|
7455
8538
|
if (!dirResult.ok) return dirResult;
|
|
7456
8539
|
const stateDir = dirResult.value;
|
|
7457
|
-
const handoffPath =
|
|
7458
|
-
if (!
|
|
8540
|
+
const handoffPath = path10.join(stateDir, HANDOFF_FILE);
|
|
8541
|
+
if (!fs13.existsSync(handoffPath)) {
|
|
7459
8542
|
return Ok(null);
|
|
7460
8543
|
}
|
|
7461
|
-
const raw =
|
|
8544
|
+
const raw = fs13.readFileSync(handoffPath, "utf-8");
|
|
7462
8545
|
const parsed = JSON.parse(raw);
|
|
7463
8546
|
const result = HandoffSchema.safeParse(parsed);
|
|
7464
8547
|
if (!result.success) {
|
|
@@ -7473,28 +8556,28 @@ async function loadHandoff(projectPath, stream, session) {
|
|
|
7473
8556
|
}
|
|
7474
8557
|
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:.-]+$/;
|
|
7475
8558
|
function loadChecksFromConfig(gateConfigPath) {
|
|
7476
|
-
if (!
|
|
7477
|
-
const raw = JSON.parse(
|
|
8559
|
+
if (!fs14.existsSync(gateConfigPath)) return [];
|
|
8560
|
+
const raw = JSON.parse(fs14.readFileSync(gateConfigPath, "utf-8"));
|
|
7478
8561
|
const config = GateConfigSchema.safeParse(raw);
|
|
7479
8562
|
if (config.success && config.data.checks) return config.data.checks;
|
|
7480
8563
|
return [];
|
|
7481
8564
|
}
|
|
7482
8565
|
function discoverChecksFromProject(projectPath) {
|
|
7483
8566
|
const checks = [];
|
|
7484
|
-
const packageJsonPath =
|
|
7485
|
-
if (
|
|
7486
|
-
const pkg = JSON.parse(
|
|
8567
|
+
const packageJsonPath = path11.join(projectPath, "package.json");
|
|
8568
|
+
if (fs14.existsSync(packageJsonPath)) {
|
|
8569
|
+
const pkg = JSON.parse(fs14.readFileSync(packageJsonPath, "utf-8"));
|
|
7487
8570
|
const scripts = pkg.scripts || {};
|
|
7488
8571
|
if (scripts.test) checks.push({ name: "test", command: "npm test" });
|
|
7489
8572
|
if (scripts.lint) checks.push({ name: "lint", command: "npm run lint" });
|
|
7490
8573
|
if (scripts.typecheck) checks.push({ name: "typecheck", command: "npm run typecheck" });
|
|
7491
8574
|
if (scripts.build) checks.push({ name: "build", command: "npm run build" });
|
|
7492
8575
|
}
|
|
7493
|
-
if (
|
|
8576
|
+
if (fs14.existsSync(path11.join(projectPath, "go.mod"))) {
|
|
7494
8577
|
checks.push({ name: "test", command: "go test ./..." });
|
|
7495
8578
|
checks.push({ name: "build", command: "go build ./..." });
|
|
7496
8579
|
}
|
|
7497
|
-
if (
|
|
8580
|
+
if (fs14.existsSync(path11.join(projectPath, "pyproject.toml")) || fs14.existsSync(path11.join(projectPath, "setup.py"))) {
|
|
7498
8581
|
checks.push({ name: "test", command: "python -m pytest" });
|
|
7499
8582
|
}
|
|
7500
8583
|
return checks;
|
|
@@ -7534,8 +8617,8 @@ function executeCheck(check, projectPath) {
|
|
|
7534
8617
|
}
|
|
7535
8618
|
}
|
|
7536
8619
|
async function runMechanicalGate(projectPath) {
|
|
7537
|
-
const harnessDir =
|
|
7538
|
-
const gateConfigPath =
|
|
8620
|
+
const harnessDir = path11.join(projectPath, HARNESS_DIR);
|
|
8621
|
+
const gateConfigPath = path11.join(harnessDir, GATE_CONFIG_FILE);
|
|
7539
8622
|
try {
|
|
7540
8623
|
let checks = loadChecksFromConfig(gateConfigPath);
|
|
7541
8624
|
if (checks.length === 0) {
|
|
@@ -7591,9 +8674,9 @@ function writeSessionSummary(projectPath, sessionSlug, data) {
|
|
|
7591
8674
|
const dirResult = resolveSessionDir(projectPath, sessionSlug, { create: true });
|
|
7592
8675
|
if (!dirResult.ok) return dirResult;
|
|
7593
8676
|
const sessionDir = dirResult.value;
|
|
7594
|
-
const summaryPath =
|
|
8677
|
+
const summaryPath = path12.join(sessionDir, SUMMARY_FILE);
|
|
7595
8678
|
const content = formatSummary(data);
|
|
7596
|
-
|
|
8679
|
+
fs15.writeFileSync(summaryPath, content);
|
|
7597
8680
|
const description = deriveIndexDescription(data);
|
|
7598
8681
|
updateSessionIndex(projectPath, sessionSlug, description);
|
|
7599
8682
|
return Ok(void 0);
|
|
@@ -7610,11 +8693,11 @@ function loadSessionSummary(projectPath, sessionSlug) {
|
|
|
7610
8693
|
const dirResult = resolveSessionDir(projectPath, sessionSlug);
|
|
7611
8694
|
if (!dirResult.ok) return dirResult;
|
|
7612
8695
|
const sessionDir = dirResult.value;
|
|
7613
|
-
const summaryPath =
|
|
7614
|
-
if (!
|
|
8696
|
+
const summaryPath = path12.join(sessionDir, SUMMARY_FILE);
|
|
8697
|
+
if (!fs15.existsSync(summaryPath)) {
|
|
7615
8698
|
return Ok(null);
|
|
7616
8699
|
}
|
|
7617
|
-
const content =
|
|
8700
|
+
const content = fs15.readFileSync(summaryPath, "utf-8");
|
|
7618
8701
|
return Ok(content);
|
|
7619
8702
|
} catch (error) {
|
|
7620
8703
|
return Err(
|
|
@@ -7626,11 +8709,11 @@ function loadSessionSummary(projectPath, sessionSlug) {
|
|
|
7626
8709
|
}
|
|
7627
8710
|
function listActiveSessions(projectPath) {
|
|
7628
8711
|
try {
|
|
7629
|
-
const indexPath2 =
|
|
7630
|
-
if (!
|
|
8712
|
+
const indexPath2 = path12.join(projectPath, HARNESS_DIR, SESSIONS_DIR, SESSION_INDEX_FILE);
|
|
8713
|
+
if (!fs15.existsSync(indexPath2)) {
|
|
7631
8714
|
return Ok(null);
|
|
7632
8715
|
}
|
|
7633
|
-
const content =
|
|
8716
|
+
const content = fs15.readFileSync(indexPath2, "utf-8");
|
|
7634
8717
|
return Ok(content);
|
|
7635
8718
|
} catch (error) {
|
|
7636
8719
|
return Err(
|
|
@@ -7651,12 +8734,12 @@ async function loadSessionState(projectPath, sessionSlug) {
|
|
|
7651
8734
|
const dirResult = resolveSessionDir(projectPath, sessionSlug);
|
|
7652
8735
|
if (!dirResult.ok) return dirResult;
|
|
7653
8736
|
const sessionDir = dirResult.value;
|
|
7654
|
-
const filePath =
|
|
7655
|
-
if (!
|
|
8737
|
+
const filePath = path13.join(sessionDir, SESSION_STATE_FILE);
|
|
8738
|
+
if (!fs16.existsSync(filePath)) {
|
|
7656
8739
|
return Ok(emptySections());
|
|
7657
8740
|
}
|
|
7658
8741
|
try {
|
|
7659
|
-
const raw =
|
|
8742
|
+
const raw = fs16.readFileSync(filePath, "utf-8");
|
|
7660
8743
|
const parsed = JSON.parse(raw);
|
|
7661
8744
|
const sections = emptySections();
|
|
7662
8745
|
for (const name of SESSION_SECTION_NAMES) {
|
|
@@ -7677,9 +8760,9 @@ async function saveSessionState(projectPath, sessionSlug, sections) {
|
|
|
7677
8760
|
const dirResult = resolveSessionDir(projectPath, sessionSlug, { create: true });
|
|
7678
8761
|
if (!dirResult.ok) return dirResult;
|
|
7679
8762
|
const sessionDir = dirResult.value;
|
|
7680
|
-
const filePath =
|
|
8763
|
+
const filePath = path13.join(sessionDir, SESSION_STATE_FILE);
|
|
7681
8764
|
try {
|
|
7682
|
-
|
|
8765
|
+
fs16.writeFileSync(filePath, JSON.stringify(sections, null, 2));
|
|
7683
8766
|
return Ok(void 0);
|
|
7684
8767
|
} catch (error) {
|
|
7685
8768
|
return Err(
|
|
@@ -7735,26 +8818,26 @@ async function archiveSession(projectPath, sessionSlug) {
|
|
|
7735
8818
|
const dirResult = resolveSessionDir(projectPath, sessionSlug);
|
|
7736
8819
|
if (!dirResult.ok) return dirResult;
|
|
7737
8820
|
const sessionDir = dirResult.value;
|
|
7738
|
-
if (!
|
|
8821
|
+
if (!fs17.existsSync(sessionDir)) {
|
|
7739
8822
|
return Err(new Error(`Session '${sessionSlug}' not found at ${sessionDir}`));
|
|
7740
8823
|
}
|
|
7741
|
-
const archiveBase =
|
|
8824
|
+
const archiveBase = path14.join(projectPath, HARNESS_DIR, ARCHIVE_DIR, "sessions");
|
|
7742
8825
|
try {
|
|
7743
|
-
|
|
8826
|
+
fs17.mkdirSync(archiveBase, { recursive: true });
|
|
7744
8827
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
7745
8828
|
let archiveName = `${sessionSlug}-${date}`;
|
|
7746
8829
|
let counter = 1;
|
|
7747
|
-
while (
|
|
8830
|
+
while (fs17.existsSync(path14.join(archiveBase, archiveName))) {
|
|
7748
8831
|
archiveName = `${sessionSlug}-${date}-${counter}`;
|
|
7749
8832
|
counter++;
|
|
7750
8833
|
}
|
|
7751
|
-
const dest =
|
|
8834
|
+
const dest = path14.join(archiveBase, archiveName);
|
|
7752
8835
|
try {
|
|
7753
|
-
|
|
8836
|
+
fs17.renameSync(sessionDir, dest);
|
|
7754
8837
|
} catch (renameErr) {
|
|
7755
8838
|
if (renameErr instanceof Error && "code" in renameErr && renameErr.code === "EXDEV") {
|
|
7756
|
-
|
|
7757
|
-
|
|
8839
|
+
fs17.cpSync(sessionDir, dest, { recursive: true });
|
|
8840
|
+
fs17.rmSync(sessionDir, { recursive: true });
|
|
7758
8841
|
} else {
|
|
7759
8842
|
throw renameErr;
|
|
7760
8843
|
}
|
|
@@ -7768,15 +8851,15 @@ async function archiveSession(projectPath, sessionSlug) {
|
|
|
7768
8851
|
);
|
|
7769
8852
|
}
|
|
7770
8853
|
}
|
|
7771
|
-
var SkillEventSchema =
|
|
7772
|
-
timestamp:
|
|
7773
|
-
skill:
|
|
7774
|
-
session:
|
|
7775
|
-
type:
|
|
7776
|
-
summary:
|
|
7777
|
-
data:
|
|
7778
|
-
refs:
|
|
7779
|
-
contentHash:
|
|
8854
|
+
var SkillEventSchema = z7.object({
|
|
8855
|
+
timestamp: z7.string(),
|
|
8856
|
+
skill: z7.string(),
|
|
8857
|
+
session: z7.string().optional(),
|
|
8858
|
+
type: z7.enum(["phase_transition", "decision", "gate_result", "handoff", "error", "checkpoint"]),
|
|
8859
|
+
summary: z7.string(),
|
|
8860
|
+
data: z7.record(z7.unknown()).optional(),
|
|
8861
|
+
refs: z7.array(z7.string()).optional(),
|
|
8862
|
+
contentHash: z7.string().optional()
|
|
7780
8863
|
});
|
|
7781
8864
|
function computeEventHash(event, session) {
|
|
7782
8865
|
const identity = `${event.skill}|${event.type}|${event.summary}|${session ?? ""}`;
|
|
@@ -7787,8 +8870,8 @@ function loadKnownHashes(eventsPath) {
|
|
|
7787
8870
|
const cached = knownHashesCache.get(eventsPath);
|
|
7788
8871
|
if (cached) return cached;
|
|
7789
8872
|
const hashes = /* @__PURE__ */ new Set();
|
|
7790
|
-
if (
|
|
7791
|
-
const content =
|
|
8873
|
+
if (fs18.existsSync(eventsPath)) {
|
|
8874
|
+
const content = fs18.readFileSync(eventsPath, "utf-8");
|
|
7792
8875
|
const lines = content.split("\n").filter((line) => line.trim() !== "");
|
|
7793
8876
|
for (const line of lines) {
|
|
7794
8877
|
try {
|
|
@@ -7811,8 +8894,8 @@ async function emitEvent(projectPath, event, options) {
|
|
|
7811
8894
|
const dirResult = await getStateDir(projectPath, options?.stream, options?.session);
|
|
7812
8895
|
if (!dirResult.ok) return dirResult;
|
|
7813
8896
|
const stateDir = dirResult.value;
|
|
7814
|
-
const eventsPath =
|
|
7815
|
-
|
|
8897
|
+
const eventsPath = path15.join(stateDir, EVENTS_FILE);
|
|
8898
|
+
fs18.mkdirSync(stateDir, { recursive: true });
|
|
7816
8899
|
const contentHash = computeEventHash(event, options?.session);
|
|
7817
8900
|
const knownHashes = loadKnownHashes(eventsPath);
|
|
7818
8901
|
if (knownHashes.has(contentHash)) {
|
|
@@ -7826,7 +8909,7 @@ async function emitEvent(projectPath, event, options) {
|
|
|
7826
8909
|
if (options?.session) {
|
|
7827
8910
|
fullEvent.session = options.session;
|
|
7828
8911
|
}
|
|
7829
|
-
|
|
8912
|
+
fs18.appendFileSync(eventsPath, JSON.stringify(fullEvent) + "\n");
|
|
7830
8913
|
knownHashes.add(contentHash);
|
|
7831
8914
|
return Ok({ written: true });
|
|
7832
8915
|
} catch (error) {
|
|
@@ -7840,11 +8923,11 @@ async function loadEvents(projectPath, options) {
|
|
|
7840
8923
|
const dirResult = await getStateDir(projectPath, options?.stream, options?.session);
|
|
7841
8924
|
if (!dirResult.ok) return dirResult;
|
|
7842
8925
|
const stateDir = dirResult.value;
|
|
7843
|
-
const eventsPath =
|
|
7844
|
-
if (!
|
|
8926
|
+
const eventsPath = path15.join(stateDir, EVENTS_FILE);
|
|
8927
|
+
if (!fs18.existsSync(eventsPath)) {
|
|
7845
8928
|
return Ok([]);
|
|
7846
8929
|
}
|
|
7847
|
-
const content =
|
|
8930
|
+
const content = fs18.readFileSync(eventsPath, "utf-8");
|
|
7848
8931
|
const lines = content.split("\n").filter((line) => line.trim() !== "");
|
|
7849
8932
|
const events = [];
|
|
7850
8933
|
for (const line of lines) {
|
|
@@ -8084,19 +9167,19 @@ var DEFAULT_SECURITY_CONFIG = {
|
|
|
8084
9167
|
rules: {},
|
|
8085
9168
|
exclude: ["**/node_modules/**", "**/dist/**", "**/*.test.ts", "**/fixtures/**"]
|
|
8086
9169
|
};
|
|
8087
|
-
var RuleOverrideSchema =
|
|
8088
|
-
var SecurityConfigSchema =
|
|
8089
|
-
enabled:
|
|
8090
|
-
strict:
|
|
8091
|
-
rules:
|
|
8092
|
-
exclude:
|
|
8093
|
-
external:
|
|
8094
|
-
semgrep:
|
|
8095
|
-
enabled:
|
|
8096
|
-
rulesets:
|
|
9170
|
+
var RuleOverrideSchema = z8.enum(["off", "error", "warning", "info"]);
|
|
9171
|
+
var SecurityConfigSchema = z8.object({
|
|
9172
|
+
enabled: z8.boolean().default(true),
|
|
9173
|
+
strict: z8.boolean().default(false),
|
|
9174
|
+
rules: z8.record(z8.string(), RuleOverrideSchema).optional().default({}),
|
|
9175
|
+
exclude: z8.array(z8.string()).optional().default(["**/node_modules/**", "**/dist/**", "**/*.test.ts", "**/fixtures/**"]),
|
|
9176
|
+
external: z8.object({
|
|
9177
|
+
semgrep: z8.object({
|
|
9178
|
+
enabled: z8.union([z8.literal("auto"), z8.boolean()]).default("auto"),
|
|
9179
|
+
rulesets: z8.array(z8.string()).optional()
|
|
8097
9180
|
}).optional(),
|
|
8098
|
-
gitleaks:
|
|
8099
|
-
enabled:
|
|
9181
|
+
gitleaks: z8.object({
|
|
9182
|
+
enabled: z8.union([z8.literal("auto"), z8.boolean()]).default("auto")
|
|
8100
9183
|
}).optional()
|
|
8101
9184
|
}).optional()
|
|
8102
9185
|
});
|
|
@@ -8129,11 +9212,11 @@ function resolveRuleSeverity(ruleId, defaultSeverity, overrides, strict) {
|
|
|
8129
9212
|
}
|
|
8130
9213
|
function detectStack(projectRoot) {
|
|
8131
9214
|
const stacks = [];
|
|
8132
|
-
const pkgJsonPath =
|
|
8133
|
-
if (
|
|
9215
|
+
const pkgJsonPath = path16.join(projectRoot, "package.json");
|
|
9216
|
+
if (fs19.existsSync(pkgJsonPath)) {
|
|
8134
9217
|
stacks.push("node");
|
|
8135
9218
|
try {
|
|
8136
|
-
const pkgJson = JSON.parse(
|
|
9219
|
+
const pkgJson = JSON.parse(fs19.readFileSync(pkgJsonPath, "utf-8"));
|
|
8137
9220
|
const allDeps = {
|
|
8138
9221
|
...pkgJson.dependencies,
|
|
8139
9222
|
...pkgJson.devDependencies
|
|
@@ -8148,13 +9231,13 @@ function detectStack(projectRoot) {
|
|
|
8148
9231
|
} catch {
|
|
8149
9232
|
}
|
|
8150
9233
|
}
|
|
8151
|
-
const goModPath =
|
|
8152
|
-
if (
|
|
9234
|
+
const goModPath = path16.join(projectRoot, "go.mod");
|
|
9235
|
+
if (fs19.existsSync(goModPath)) {
|
|
8153
9236
|
stacks.push("go");
|
|
8154
9237
|
}
|
|
8155
|
-
const requirementsPath =
|
|
8156
|
-
const pyprojectPath =
|
|
8157
|
-
if (
|
|
9238
|
+
const requirementsPath = path16.join(projectRoot, "requirements.txt");
|
|
9239
|
+
const pyprojectPath = path16.join(projectRoot, "pyproject.toml");
|
|
9240
|
+
if (fs19.existsSync(requirementsPath) || fs19.existsSync(pyprojectPath)) {
|
|
8158
9241
|
stacks.push("python");
|
|
8159
9242
|
}
|
|
8160
9243
|
return stacks;
|
|
@@ -8953,7 +10036,7 @@ var SecurityScanner = class {
|
|
|
8953
10036
|
}
|
|
8954
10037
|
async scanFile(filePath) {
|
|
8955
10038
|
if (!this.config.enabled) return [];
|
|
8956
|
-
const content = await
|
|
10039
|
+
const content = await fs20.readFile(filePath, "utf-8");
|
|
8957
10040
|
return this.scanContentForFile(content, filePath, 1);
|
|
8958
10041
|
}
|
|
8959
10042
|
scanContentForFile(content, filePath, startLine = 1) {
|
|
@@ -9280,13 +10363,13 @@ var TAINT_DURATION_MS = 30 * 60 * 1e3;
|
|
|
9280
10363
|
var DEFAULT_SESSION_ID = "default";
|
|
9281
10364
|
function getTaintFilePath(projectRoot, sessionId) {
|
|
9282
10365
|
const id = sessionId || DEFAULT_SESSION_ID;
|
|
9283
|
-
return
|
|
10366
|
+
return join242(projectRoot, ".harness", `session-taint-${id}.json`);
|
|
9284
10367
|
}
|
|
9285
10368
|
function readTaint(projectRoot, sessionId) {
|
|
9286
10369
|
const filePath = getTaintFilePath(projectRoot, sessionId);
|
|
9287
10370
|
let content;
|
|
9288
10371
|
try {
|
|
9289
|
-
content =
|
|
10372
|
+
content = readFileSync17(filePath, "utf8");
|
|
9290
10373
|
} catch {
|
|
9291
10374
|
return null;
|
|
9292
10375
|
}
|
|
@@ -9330,8 +10413,8 @@ function writeTaint(projectRoot, sessionId, reason, findings, source) {
|
|
|
9330
10413
|
const id = sessionId || DEFAULT_SESSION_ID;
|
|
9331
10414
|
const filePath = getTaintFilePath(projectRoot, id);
|
|
9332
10415
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9333
|
-
const dir =
|
|
9334
|
-
|
|
10416
|
+
const dir = dirname9(filePath);
|
|
10417
|
+
mkdirSync12(dir, { recursive: true });
|
|
9335
10418
|
const existing = readTaint(projectRoot, id);
|
|
9336
10419
|
const maxSeverity = findings.some((f) => f.severity === "high") ? "high" : "medium";
|
|
9337
10420
|
const taintFindings = findings.map((f) => ({
|
|
@@ -9349,7 +10432,7 @@ function writeTaint(projectRoot, sessionId, reason, findings, source) {
|
|
|
9349
10432
|
severity: existing?.severity === "high" || maxSeverity === "high" ? "high" : "medium",
|
|
9350
10433
|
findings: [...existing?.findings || [], ...taintFindings]
|
|
9351
10434
|
};
|
|
9352
|
-
|
|
10435
|
+
writeFileSync12(filePath, JSON.stringify(state, null, 2) + "\n");
|
|
9353
10436
|
return state;
|
|
9354
10437
|
}
|
|
9355
10438
|
function clearTaint(projectRoot, sessionId) {
|
|
@@ -9362,14 +10445,14 @@ function clearTaint(projectRoot, sessionId) {
|
|
|
9362
10445
|
return 0;
|
|
9363
10446
|
}
|
|
9364
10447
|
}
|
|
9365
|
-
const harnessDir =
|
|
10448
|
+
const harnessDir = join242(projectRoot, ".harness");
|
|
9366
10449
|
let count = 0;
|
|
9367
10450
|
try {
|
|
9368
10451
|
const files = readdirSync3(harnessDir);
|
|
9369
10452
|
for (const file of files) {
|
|
9370
10453
|
if (file.startsWith("session-taint-") && file.endsWith(".json")) {
|
|
9371
10454
|
try {
|
|
9372
|
-
unlinkSync(
|
|
10455
|
+
unlinkSync(join242(harnessDir, file));
|
|
9373
10456
|
count++;
|
|
9374
10457
|
} catch {
|
|
9375
10458
|
}
|
|
@@ -9380,7 +10463,7 @@ function clearTaint(projectRoot, sessionId) {
|
|
|
9380
10463
|
return count;
|
|
9381
10464
|
}
|
|
9382
10465
|
function listTaintedSessions(projectRoot) {
|
|
9383
|
-
const harnessDir =
|
|
10466
|
+
const harnessDir = join242(projectRoot, ".harness");
|
|
9384
10467
|
const sessions = [];
|
|
9385
10468
|
try {
|
|
9386
10469
|
const files = readdirSync3(harnessDir);
|
|
@@ -9454,11 +10537,12 @@ var ALL_CHECKS = [
|
|
|
9454
10537
|
"security",
|
|
9455
10538
|
"perf",
|
|
9456
10539
|
"phase-gate",
|
|
9457
|
-
"arch"
|
|
10540
|
+
"arch",
|
|
10541
|
+
"traceability"
|
|
9458
10542
|
];
|
|
9459
10543
|
async function runValidateCheck(projectRoot, config) {
|
|
9460
10544
|
const issues = [];
|
|
9461
|
-
const agentsPath =
|
|
10545
|
+
const agentsPath = path17.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
|
|
9462
10546
|
const result = await validateAgentsMap(agentsPath);
|
|
9463
10547
|
if (!result.ok) {
|
|
9464
10548
|
issues.push({ severity: "error", message: result.error.message });
|
|
@@ -9515,7 +10599,7 @@ async function runDepsCheck(projectRoot, config) {
|
|
|
9515
10599
|
}
|
|
9516
10600
|
async function runDocsCheck(projectRoot, config) {
|
|
9517
10601
|
const issues = [];
|
|
9518
|
-
const docsDir =
|
|
10602
|
+
const docsDir = path17.join(projectRoot, config.docsDir ?? "docs");
|
|
9519
10603
|
const entropyConfig = config.entropy || {};
|
|
9520
10604
|
const result = await checkDocCoverage("project", {
|
|
9521
10605
|
docsDir,
|
|
@@ -9696,6 +10780,39 @@ async function runArchCheck(projectRoot, config) {
|
|
|
9696
10780
|
}
|
|
9697
10781
|
return issues;
|
|
9698
10782
|
}
|
|
10783
|
+
async function runTraceabilityCheck(projectRoot, config) {
|
|
10784
|
+
const issues = [];
|
|
10785
|
+
const traceConfig = config.traceability || {};
|
|
10786
|
+
if (traceConfig.enabled === false) return issues;
|
|
10787
|
+
const graphDir = path17.join(projectRoot, ".harness", "graph");
|
|
10788
|
+
const store = new GraphStore();
|
|
10789
|
+
const loaded = await store.load(graphDir);
|
|
10790
|
+
if (!loaded) {
|
|
10791
|
+
return issues;
|
|
10792
|
+
}
|
|
10793
|
+
const results = queryTraceability(store);
|
|
10794
|
+
if (results.length === 0) return issues;
|
|
10795
|
+
const minCoverage = traceConfig.minCoverage ?? 0;
|
|
10796
|
+
const severity = traceConfig.severity ?? "warning";
|
|
10797
|
+
for (const result of results) {
|
|
10798
|
+
const pct = result.summary.coveragePercent;
|
|
10799
|
+
if (pct < minCoverage) {
|
|
10800
|
+
issues.push({
|
|
10801
|
+
severity,
|
|
10802
|
+
message: `Traceability coverage for "${result.featureName}" is ${pct}% (minimum: ${minCoverage}%)`
|
|
10803
|
+
});
|
|
10804
|
+
}
|
|
10805
|
+
for (const req of result.requirements) {
|
|
10806
|
+
if (req.status === "none") {
|
|
10807
|
+
issues.push({
|
|
10808
|
+
severity: "warning",
|
|
10809
|
+
message: `Requirement "${req.requirementName}" has no traced code or tests`
|
|
10810
|
+
});
|
|
10811
|
+
}
|
|
10812
|
+
}
|
|
10813
|
+
}
|
|
10814
|
+
return issues;
|
|
10815
|
+
}
|
|
9699
10816
|
async function runSingleCheck(name, projectRoot, config) {
|
|
9700
10817
|
const start = Date.now();
|
|
9701
10818
|
const issues = [];
|
|
@@ -9725,6 +10842,9 @@ async function runSingleCheck(name, projectRoot, config) {
|
|
|
9725
10842
|
case "arch":
|
|
9726
10843
|
issues.push(...await runArchCheck(projectRoot, config));
|
|
9727
10844
|
break;
|
|
10845
|
+
case "traceability":
|
|
10846
|
+
issues.push(...await runTraceabilityCheck(projectRoot, config));
|
|
10847
|
+
break;
|
|
9728
10848
|
}
|
|
9729
10849
|
} catch (error) {
|
|
9730
10850
|
issues.push({
|
|
@@ -9802,7 +10922,7 @@ async function runMechanicalChecks(options) {
|
|
|
9802
10922
|
};
|
|
9803
10923
|
if (!skip.includes("validate")) {
|
|
9804
10924
|
try {
|
|
9805
|
-
const agentsPath =
|
|
10925
|
+
const agentsPath = path18.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
|
|
9806
10926
|
const result = await validateAgentsMap(agentsPath);
|
|
9807
10927
|
if (!result.ok) {
|
|
9808
10928
|
statuses.validate = "fail";
|
|
@@ -9839,7 +10959,7 @@ async function runMechanicalChecks(options) {
|
|
|
9839
10959
|
statuses.validate = "fail";
|
|
9840
10960
|
findings.push({
|
|
9841
10961
|
tool: "validate",
|
|
9842
|
-
file:
|
|
10962
|
+
file: path18.join(projectRoot, "AGENTS.md"),
|
|
9843
10963
|
message: err instanceof Error ? err.message : String(err),
|
|
9844
10964
|
severity: "error"
|
|
9845
10965
|
});
|
|
@@ -9903,7 +11023,7 @@ async function runMechanicalChecks(options) {
|
|
|
9903
11023
|
(async () => {
|
|
9904
11024
|
const localFindings = [];
|
|
9905
11025
|
try {
|
|
9906
|
-
const docsDir =
|
|
11026
|
+
const docsDir = path18.join(projectRoot, config.docsDir ?? "docs");
|
|
9907
11027
|
const result = await checkDocCoverage("project", { docsDir });
|
|
9908
11028
|
if (!result.ok) {
|
|
9909
11029
|
statuses["check-docs"] = "warn";
|
|
@@ -9930,7 +11050,7 @@ async function runMechanicalChecks(options) {
|
|
|
9930
11050
|
statuses["check-docs"] = "warn";
|
|
9931
11051
|
localFindings.push({
|
|
9932
11052
|
tool: "check-docs",
|
|
9933
|
-
file:
|
|
11053
|
+
file: path18.join(projectRoot, "docs"),
|
|
9934
11054
|
message: err instanceof Error ? err.message : String(err),
|
|
9935
11055
|
severity: "warning"
|
|
9936
11056
|
});
|
|
@@ -10079,18 +11199,18 @@ function computeContextBudget(diffLines) {
|
|
|
10079
11199
|
return diffLines;
|
|
10080
11200
|
}
|
|
10081
11201
|
function isWithinProject(absPath, projectRoot) {
|
|
10082
|
-
const resolvedRoot =
|
|
10083
|
-
const resolvedPath =
|
|
10084
|
-
return resolvedPath.startsWith(resolvedRoot) || resolvedPath ===
|
|
11202
|
+
const resolvedRoot = path19.resolve(projectRoot) + path19.sep;
|
|
11203
|
+
const resolvedPath = path19.resolve(absPath);
|
|
11204
|
+
return resolvedPath.startsWith(resolvedRoot) || resolvedPath === path19.resolve(projectRoot);
|
|
10085
11205
|
}
|
|
10086
11206
|
async function readContextFile(projectRoot, filePath, reason) {
|
|
10087
|
-
const absPath =
|
|
11207
|
+
const absPath = path19.isAbsolute(filePath) ? filePath : path19.join(projectRoot, filePath);
|
|
10088
11208
|
if (!isWithinProject(absPath, projectRoot)) return null;
|
|
10089
11209
|
const result = await readFileContent(absPath);
|
|
10090
11210
|
if (!result.ok) return null;
|
|
10091
11211
|
const content = result.value;
|
|
10092
11212
|
const lines = content.split("\n").length;
|
|
10093
|
-
const relPath =
|
|
11213
|
+
const relPath = path19.isAbsolute(filePath) ? relativePosix(projectRoot, filePath) : filePath;
|
|
10094
11214
|
return { path: relPath, content, reason, lines };
|
|
10095
11215
|
}
|
|
10096
11216
|
function extractImportSources2(content) {
|
|
@@ -10105,18 +11225,18 @@ function extractImportSources2(content) {
|
|
|
10105
11225
|
}
|
|
10106
11226
|
async function resolveImportPath2(projectRoot, fromFile, importSource) {
|
|
10107
11227
|
if (!importSource.startsWith(".")) return null;
|
|
10108
|
-
const fromDir =
|
|
10109
|
-
const basePath =
|
|
11228
|
+
const fromDir = path19.dirname(path19.join(projectRoot, fromFile));
|
|
11229
|
+
const basePath = path19.resolve(fromDir, importSource);
|
|
10110
11230
|
if (!isWithinProject(basePath, projectRoot)) return null;
|
|
10111
11231
|
const relBase = relativePosix(projectRoot, basePath);
|
|
10112
11232
|
const candidates = [
|
|
10113
11233
|
relBase + ".ts",
|
|
10114
11234
|
relBase + ".tsx",
|
|
10115
11235
|
relBase + ".mts",
|
|
10116
|
-
|
|
11236
|
+
path19.join(relBase, "index.ts")
|
|
10117
11237
|
];
|
|
10118
11238
|
for (const candidate of candidates) {
|
|
10119
|
-
const absCandidate =
|
|
11239
|
+
const absCandidate = path19.join(projectRoot, candidate);
|
|
10120
11240
|
if (await fileExists(absCandidate)) {
|
|
10121
11241
|
return candidate;
|
|
10122
11242
|
}
|
|
@@ -10124,7 +11244,7 @@ async function resolveImportPath2(projectRoot, fromFile, importSource) {
|
|
|
10124
11244
|
return null;
|
|
10125
11245
|
}
|
|
10126
11246
|
async function findTestFiles(projectRoot, sourceFile) {
|
|
10127
|
-
const baseName =
|
|
11247
|
+
const baseName = path19.basename(sourceFile, path19.extname(sourceFile));
|
|
10128
11248
|
const pattern = `**/${baseName}.{test,spec}.{ts,tsx,mts}`;
|
|
10129
11249
|
const results = await findFiles(pattern, projectRoot);
|
|
10130
11250
|
return results.map((f) => relativePosix(projectRoot, f));
|
|
@@ -10937,7 +12057,7 @@ function normalizePath(filePath, projectRoot) {
|
|
|
10937
12057
|
let normalized = filePath;
|
|
10938
12058
|
normalized = normalized.replace(/\\/g, "/");
|
|
10939
12059
|
const normalizedRoot = projectRoot.replace(/\\/g, "/");
|
|
10940
|
-
if (
|
|
12060
|
+
if (path20.isAbsolute(normalized)) {
|
|
10941
12061
|
const root = normalizedRoot.endsWith("/") ? normalizedRoot : normalizedRoot + "/";
|
|
10942
12062
|
if (normalized.startsWith(root)) {
|
|
10943
12063
|
normalized = normalized.slice(root.length);
|
|
@@ -10962,12 +12082,12 @@ function followImportChain(fromFile, fileContents, maxDepth = 2) {
|
|
|
10962
12082
|
while ((match = importRegex.exec(content)) !== null) {
|
|
10963
12083
|
const importPath = match[1];
|
|
10964
12084
|
if (!importPath.startsWith(".")) continue;
|
|
10965
|
-
const dir =
|
|
10966
|
-
let resolved =
|
|
12085
|
+
const dir = path20.dirname(current.file);
|
|
12086
|
+
let resolved = path20.join(dir, importPath).replace(/\\/g, "/");
|
|
10967
12087
|
if (!resolved.match(/\.(ts|tsx|js|jsx)$/)) {
|
|
10968
12088
|
resolved += ".ts";
|
|
10969
12089
|
}
|
|
10970
|
-
resolved =
|
|
12090
|
+
resolved = path20.normalize(resolved).replace(/\\/g, "/");
|
|
10971
12091
|
if (!visited.has(resolved) && current.depth + 1 <= maxDepth) {
|
|
10972
12092
|
queue.push({ file: resolved, depth: current.depth + 1 });
|
|
10973
12093
|
}
|
|
@@ -10984,7 +12104,7 @@ async function validateFindings(options) {
|
|
|
10984
12104
|
if (exclusionSet.isExcluded(normalizedFile, finding.lineRange) || exclusionSet.isExcluded(finding.file, finding.lineRange)) {
|
|
10985
12105
|
continue;
|
|
10986
12106
|
}
|
|
10987
|
-
const absoluteFile =
|
|
12107
|
+
const absoluteFile = path20.isAbsolute(finding.file) ? finding.file : path20.join(projectRoot, finding.file).replace(/\\/g, "/");
|
|
10988
12108
|
if (exclusionSet.isExcluded(absoluteFile, finding.lineRange)) {
|
|
10989
12109
|
continue;
|
|
10990
12110
|
}
|
|
@@ -11580,200 +12700,6 @@ async function runReviewPipeline(options) {
|
|
|
11580
12700
|
...evidenceCoverage != null ? { evidenceCoverage } : {}
|
|
11581
12701
|
};
|
|
11582
12702
|
}
|
|
11583
|
-
var VALID_STATUSES = /* @__PURE__ */ new Set([
|
|
11584
|
-
"backlog",
|
|
11585
|
-
"planned",
|
|
11586
|
-
"in-progress",
|
|
11587
|
-
"done",
|
|
11588
|
-
"blocked"
|
|
11589
|
-
]);
|
|
11590
|
-
var EM_DASH = "\u2014";
|
|
11591
|
-
var VALID_PRIORITIES = /* @__PURE__ */ new Set(["P0", "P1", "P2", "P3"]);
|
|
11592
|
-
function parseRoadmap(markdown) {
|
|
11593
|
-
const fmMatch = markdown.match(/^---\n([\s\S]*?)\n---/);
|
|
11594
|
-
if (!fmMatch) {
|
|
11595
|
-
return Err(new Error("Missing or malformed YAML frontmatter"));
|
|
11596
|
-
}
|
|
11597
|
-
const fmResult = parseFrontmatter2(fmMatch[1]);
|
|
11598
|
-
if (!fmResult.ok) return fmResult;
|
|
11599
|
-
const body = markdown.slice(fmMatch[0].length);
|
|
11600
|
-
const milestonesResult = parseMilestones(body);
|
|
11601
|
-
if (!milestonesResult.ok) return milestonesResult;
|
|
11602
|
-
const historyResult = parseAssignmentHistory(body);
|
|
11603
|
-
if (!historyResult.ok) return historyResult;
|
|
11604
|
-
return Ok({
|
|
11605
|
-
frontmatter: fmResult.value,
|
|
11606
|
-
milestones: milestonesResult.value,
|
|
11607
|
-
assignmentHistory: historyResult.value
|
|
11608
|
-
});
|
|
11609
|
-
}
|
|
11610
|
-
function parseFrontmatter2(raw) {
|
|
11611
|
-
const lines = raw.split("\n");
|
|
11612
|
-
const map = /* @__PURE__ */ new Map();
|
|
11613
|
-
for (const line of lines) {
|
|
11614
|
-
const idx = line.indexOf(":");
|
|
11615
|
-
if (idx === -1) continue;
|
|
11616
|
-
const key = line.slice(0, idx).trim();
|
|
11617
|
-
const val = line.slice(idx + 1).trim();
|
|
11618
|
-
map.set(key, val);
|
|
11619
|
-
}
|
|
11620
|
-
const project = map.get("project");
|
|
11621
|
-
const versionStr = map.get("version");
|
|
11622
|
-
const lastSynced = map.get("last_synced");
|
|
11623
|
-
const lastManualEdit = map.get("last_manual_edit");
|
|
11624
|
-
const created = map.get("created");
|
|
11625
|
-
const updated = map.get("updated");
|
|
11626
|
-
if (!project || !versionStr || !lastSynced || !lastManualEdit) {
|
|
11627
|
-
return Err(
|
|
11628
|
-
new Error(
|
|
11629
|
-
"Frontmatter missing required fields: project, version, last_synced, last_manual_edit"
|
|
11630
|
-
)
|
|
11631
|
-
);
|
|
11632
|
-
}
|
|
11633
|
-
const version = parseInt(versionStr, 10);
|
|
11634
|
-
if (isNaN(version)) {
|
|
11635
|
-
return Err(new Error("Frontmatter version must be a number"));
|
|
11636
|
-
}
|
|
11637
|
-
const fm = { project, version, lastSynced, lastManualEdit };
|
|
11638
|
-
if (created) fm.created = created;
|
|
11639
|
-
if (updated) fm.updated = updated;
|
|
11640
|
-
return Ok(fm);
|
|
11641
|
-
}
|
|
11642
|
-
function parseMilestones(body) {
|
|
11643
|
-
const milestones = [];
|
|
11644
|
-
const h2Pattern = /^## (.+)$/gm;
|
|
11645
|
-
const h2Matches = [];
|
|
11646
|
-
let match;
|
|
11647
|
-
let bodyEnd = body.length;
|
|
11648
|
-
while ((match = h2Pattern.exec(body)) !== null) {
|
|
11649
|
-
if (match[1] === "Assignment History") {
|
|
11650
|
-
bodyEnd = match.index;
|
|
11651
|
-
break;
|
|
11652
|
-
}
|
|
11653
|
-
h2Matches.push({ heading: match[1], startIndex: match.index, fullMatch: match[0] });
|
|
11654
|
-
}
|
|
11655
|
-
for (let i = 0; i < h2Matches.length; i++) {
|
|
11656
|
-
const h2 = h2Matches[i];
|
|
11657
|
-
const nextStart = i + 1 < h2Matches.length ? h2Matches[i + 1].startIndex : bodyEnd;
|
|
11658
|
-
const sectionBody = body.slice(h2.startIndex + h2.fullMatch.length, nextStart);
|
|
11659
|
-
const isBacklog = h2.heading === "Backlog";
|
|
11660
|
-
const milestoneName = isBacklog ? "Backlog" : h2.heading.replace(/^Milestone:\s*/, "");
|
|
11661
|
-
const featuresResult = parseFeatures(sectionBody);
|
|
11662
|
-
if (!featuresResult.ok) return featuresResult;
|
|
11663
|
-
milestones.push({
|
|
11664
|
-
name: milestoneName,
|
|
11665
|
-
isBacklog,
|
|
11666
|
-
features: featuresResult.value
|
|
11667
|
-
});
|
|
11668
|
-
}
|
|
11669
|
-
return Ok(milestones);
|
|
11670
|
-
}
|
|
11671
|
-
function parseFeatures(sectionBody) {
|
|
11672
|
-
const features = [];
|
|
11673
|
-
const h3Pattern = /^### (?:Feature: )?(.+)$/gm;
|
|
11674
|
-
const h3Matches = [];
|
|
11675
|
-
let match;
|
|
11676
|
-
while ((match = h3Pattern.exec(sectionBody)) !== null) {
|
|
11677
|
-
h3Matches.push({ name: match[1], startIndex: match.index, fullMatch: match[0] });
|
|
11678
|
-
}
|
|
11679
|
-
for (let i = 0; i < h3Matches.length; i++) {
|
|
11680
|
-
const h3 = h3Matches[i];
|
|
11681
|
-
const nextStart = i + 1 < h3Matches.length ? h3Matches[i + 1].startIndex : sectionBody.length;
|
|
11682
|
-
const featureBody = sectionBody.slice(h3.startIndex + h3.fullMatch.length, nextStart);
|
|
11683
|
-
const featureResult = parseFeatureFields(h3.name, featureBody);
|
|
11684
|
-
if (!featureResult.ok) return featureResult;
|
|
11685
|
-
features.push(featureResult.value);
|
|
11686
|
-
}
|
|
11687
|
-
return Ok(features);
|
|
11688
|
-
}
|
|
11689
|
-
function extractFieldMap(body) {
|
|
11690
|
-
const fieldMap = /* @__PURE__ */ new Map();
|
|
11691
|
-
const fieldPattern = /^- \*\*(.+?):\*\* (.+)$/gm;
|
|
11692
|
-
let match;
|
|
11693
|
-
while ((match = fieldPattern.exec(body)) !== null) {
|
|
11694
|
-
fieldMap.set(match[1], match[2]);
|
|
11695
|
-
}
|
|
11696
|
-
return fieldMap;
|
|
11697
|
-
}
|
|
11698
|
-
function parseListField(fieldMap, ...keys) {
|
|
11699
|
-
let raw = EM_DASH;
|
|
11700
|
-
for (const key of keys) {
|
|
11701
|
-
const val = fieldMap.get(key);
|
|
11702
|
-
if (val !== void 0) {
|
|
11703
|
-
raw = val;
|
|
11704
|
-
break;
|
|
11705
|
-
}
|
|
11706
|
-
}
|
|
11707
|
-
if (raw === EM_DASH || raw === "none") return [];
|
|
11708
|
-
return raw.split(",").map((s) => s.trim());
|
|
11709
|
-
}
|
|
11710
|
-
function parseFeatureFields(name, body) {
|
|
11711
|
-
const fieldMap = extractFieldMap(body);
|
|
11712
|
-
const statusRaw = fieldMap.get("Status");
|
|
11713
|
-
if (!statusRaw || !VALID_STATUSES.has(statusRaw)) {
|
|
11714
|
-
return Err(
|
|
11715
|
-
new Error(
|
|
11716
|
-
`Feature "${name}" has invalid status: "${statusRaw ?? "(missing)"}". Valid statuses: ${[...VALID_STATUSES].join(", ")}`
|
|
11717
|
-
)
|
|
11718
|
-
);
|
|
11719
|
-
}
|
|
11720
|
-
const specRaw = fieldMap.get("Spec") ?? EM_DASH;
|
|
11721
|
-
const plans = parseListField(fieldMap, "Plans", "Plan");
|
|
11722
|
-
const blockedBy = parseListField(fieldMap, "Blocked by", "Blockers");
|
|
11723
|
-
const assigneeRaw = fieldMap.get("Assignee") ?? EM_DASH;
|
|
11724
|
-
const priorityRaw = fieldMap.get("Priority") ?? EM_DASH;
|
|
11725
|
-
const externalIdRaw = fieldMap.get("External-ID") ?? EM_DASH;
|
|
11726
|
-
if (priorityRaw !== EM_DASH && !VALID_PRIORITIES.has(priorityRaw)) {
|
|
11727
|
-
return Err(
|
|
11728
|
-
new Error(
|
|
11729
|
-
`Feature "${name}" has invalid priority: "${priorityRaw}". Valid priorities: ${[...VALID_PRIORITIES].join(", ")}`
|
|
11730
|
-
)
|
|
11731
|
-
);
|
|
11732
|
-
}
|
|
11733
|
-
return Ok({
|
|
11734
|
-
name,
|
|
11735
|
-
status: statusRaw,
|
|
11736
|
-
spec: specRaw === EM_DASH ? null : specRaw,
|
|
11737
|
-
plans,
|
|
11738
|
-
blockedBy,
|
|
11739
|
-
summary: fieldMap.get("Summary") ?? "",
|
|
11740
|
-
assignee: assigneeRaw === EM_DASH ? null : assigneeRaw,
|
|
11741
|
-
priority: priorityRaw === EM_DASH ? null : priorityRaw,
|
|
11742
|
-
externalId: externalIdRaw === EM_DASH ? null : externalIdRaw
|
|
11743
|
-
});
|
|
11744
|
-
}
|
|
11745
|
-
function parseAssignmentHistory(body) {
|
|
11746
|
-
const historyMatch = body.match(/^## Assignment History\s*\n/m);
|
|
11747
|
-
if (!historyMatch || historyMatch.index === void 0) return Ok([]);
|
|
11748
|
-
const historyStart = historyMatch.index + historyMatch[0].length;
|
|
11749
|
-
const rawHistoryBody = body.slice(historyStart);
|
|
11750
|
-
const nextH2 = rawHistoryBody.search(/^## /m);
|
|
11751
|
-
const historyBody = nextH2 === -1 ? rawHistoryBody : rawHistoryBody.slice(0, nextH2);
|
|
11752
|
-
const records = [];
|
|
11753
|
-
const lines = historyBody.split("\n");
|
|
11754
|
-
let pastHeader = false;
|
|
11755
|
-
for (const line of lines) {
|
|
11756
|
-
const trimmed = line.trim();
|
|
11757
|
-
if (!trimmed.startsWith("|")) continue;
|
|
11758
|
-
if (!pastHeader) {
|
|
11759
|
-
if (trimmed.match(/^\|[-\s|]+\|$/)) {
|
|
11760
|
-
pastHeader = true;
|
|
11761
|
-
}
|
|
11762
|
-
continue;
|
|
11763
|
-
}
|
|
11764
|
-
const cells = trimmed.split("|").map((c) => c.trim()).filter((c) => c.length > 0);
|
|
11765
|
-
if (cells.length < 4) continue;
|
|
11766
|
-
const action = cells[2];
|
|
11767
|
-
if (!["assigned", "completed", "unassigned"].includes(action)) continue;
|
|
11768
|
-
records.push({
|
|
11769
|
-
feature: cells[0],
|
|
11770
|
-
assignee: cells[1],
|
|
11771
|
-
action,
|
|
11772
|
-
date: cells[3]
|
|
11773
|
-
});
|
|
11774
|
-
}
|
|
11775
|
-
return Ok(records);
|
|
11776
|
-
}
|
|
11777
12703
|
var EM_DASH2 = "\u2014";
|
|
11778
12704
|
function serializeRoadmap(roadmap) {
|
|
11779
12705
|
const lines = [];
|
|
@@ -11865,10 +12791,10 @@ function inferStatus(feature, projectPath, allFeatures) {
|
|
|
11865
12791
|
const featuresWithPlans = allFeatures.filter((f) => f.plans.length > 0);
|
|
11866
12792
|
const useRootState = featuresWithPlans.length <= 1;
|
|
11867
12793
|
if (useRootState) {
|
|
11868
|
-
const rootStatePath =
|
|
11869
|
-
if (
|
|
12794
|
+
const rootStatePath = path21.join(projectPath, ".harness", "state.json");
|
|
12795
|
+
if (fs21.existsSync(rootStatePath)) {
|
|
11870
12796
|
try {
|
|
11871
|
-
const raw =
|
|
12797
|
+
const raw = fs21.readFileSync(rootStatePath, "utf-8");
|
|
11872
12798
|
const state = JSON.parse(raw);
|
|
11873
12799
|
if (state.progress) {
|
|
11874
12800
|
for (const status of Object.values(state.progress)) {
|
|
@@ -11879,16 +12805,16 @@ function inferStatus(feature, projectPath, allFeatures) {
|
|
|
11879
12805
|
}
|
|
11880
12806
|
}
|
|
11881
12807
|
}
|
|
11882
|
-
const sessionsDir =
|
|
11883
|
-
if (
|
|
12808
|
+
const sessionsDir = path21.join(projectPath, ".harness", "sessions");
|
|
12809
|
+
if (fs21.existsSync(sessionsDir)) {
|
|
11884
12810
|
try {
|
|
11885
|
-
const sessionDirs =
|
|
12811
|
+
const sessionDirs = fs21.readdirSync(sessionsDir, { withFileTypes: true });
|
|
11886
12812
|
for (const entry of sessionDirs) {
|
|
11887
12813
|
if (!entry.isDirectory()) continue;
|
|
11888
|
-
const autopilotPath =
|
|
11889
|
-
if (!
|
|
12814
|
+
const autopilotPath = path21.join(sessionsDir, entry.name, "autopilot-state.json");
|
|
12815
|
+
if (!fs21.existsSync(autopilotPath)) continue;
|
|
11890
12816
|
try {
|
|
11891
|
-
const raw =
|
|
12817
|
+
const raw = fs21.readFileSync(autopilotPath, "utf-8");
|
|
11892
12818
|
const autopilot = JSON.parse(raw);
|
|
11893
12819
|
if (!autopilot.phases) continue;
|
|
11894
12820
|
const linkedPhases = autopilot.phases.filter(
|
|
@@ -11980,6 +12906,13 @@ function labelsForStatus(status, config) {
|
|
|
11980
12906
|
return [...base];
|
|
11981
12907
|
}
|
|
11982
12908
|
var RETRY_DEFAULTS = { maxRetries: 5, baseDelayMs: 1e3 };
|
|
12909
|
+
function parseRepoParts(repo) {
|
|
12910
|
+
const parts = (repo ?? "").split("/");
|
|
12911
|
+
if (parts.length !== 2 || !parts[0] || !parts[1]) {
|
|
12912
|
+
throw new Error(`Invalid repo format: "${repo}". Expected "owner/repo".`);
|
|
12913
|
+
}
|
|
12914
|
+
return { owner: parts[0], repo: parts[1] };
|
|
12915
|
+
}
|
|
11983
12916
|
function sleep(ms) {
|
|
11984
12917
|
return new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
11985
12918
|
}
|
|
@@ -12023,12 +12956,9 @@ var GitHubIssuesSyncAdapter = class {
|
|
|
12023
12956
|
maxRetries: options.maxRetries ?? RETRY_DEFAULTS.maxRetries,
|
|
12024
12957
|
baseDelayMs: options.baseDelayMs ?? RETRY_DEFAULTS.baseDelayMs
|
|
12025
12958
|
};
|
|
12026
|
-
const
|
|
12027
|
-
|
|
12028
|
-
|
|
12029
|
-
}
|
|
12030
|
-
this.owner = repoParts[0];
|
|
12031
|
-
this.repo = repoParts[1];
|
|
12959
|
+
const { owner, repo } = parseRepoParts(options.config.repo);
|
|
12960
|
+
this.owner = owner;
|
|
12961
|
+
this.repo = repo;
|
|
12032
12962
|
}
|
|
12033
12963
|
/**
|
|
12034
12964
|
* Fetch all GitHub milestones and build the name -> ID cache.
|
|
@@ -12364,7 +13294,7 @@ async function fullSync(roadmapPath, adapter, config, options) {
|
|
|
12364
13294
|
});
|
|
12365
13295
|
await previousSync;
|
|
12366
13296
|
try {
|
|
12367
|
-
const raw =
|
|
13297
|
+
const raw = fs22.readFileSync(roadmapPath, "utf-8");
|
|
12368
13298
|
const parseResult = parseRoadmap(raw);
|
|
12369
13299
|
if (!parseResult.ok) {
|
|
12370
13300
|
return {
|
|
@@ -12375,7 +13305,7 @@ async function fullSync(roadmapPath, adapter, config, options) {
|
|
|
12375
13305
|
const roadmap = parseResult.value;
|
|
12376
13306
|
const pushResult = await syncToExternal(roadmap, adapter, config);
|
|
12377
13307
|
const pullResult = await syncFromExternal(roadmap, adapter, config, options);
|
|
12378
|
-
|
|
13308
|
+
fs22.writeFileSync(roadmapPath, serializeRoadmap(roadmap), "utf-8");
|
|
12379
13309
|
return {
|
|
12380
13310
|
created: pushResult.created,
|
|
12381
13311
|
updated: pushResult.updated,
|
|
@@ -12501,28 +13431,28 @@ function assignFeature(roadmap, feature, assignee, date) {
|
|
|
12501
13431
|
date
|
|
12502
13432
|
});
|
|
12503
13433
|
}
|
|
12504
|
-
var InteractionTypeSchema =
|
|
12505
|
-
var QuestionSchema =
|
|
12506
|
-
text:
|
|
12507
|
-
options:
|
|
12508
|
-
default:
|
|
13434
|
+
var InteractionTypeSchema = z9.enum(["question", "confirmation", "transition"]);
|
|
13435
|
+
var QuestionSchema = z9.object({
|
|
13436
|
+
text: z9.string(),
|
|
13437
|
+
options: z9.array(z9.string()).optional(),
|
|
13438
|
+
default: z9.string().optional()
|
|
12509
13439
|
});
|
|
12510
|
-
var ConfirmationSchema =
|
|
12511
|
-
text:
|
|
12512
|
-
context:
|
|
13440
|
+
var ConfirmationSchema = z9.object({
|
|
13441
|
+
text: z9.string(),
|
|
13442
|
+
context: z9.string()
|
|
12513
13443
|
});
|
|
12514
|
-
var TransitionSchema =
|
|
12515
|
-
completedPhase:
|
|
12516
|
-
suggestedNext:
|
|
12517
|
-
reason:
|
|
12518
|
-
artifacts:
|
|
12519
|
-
requiresConfirmation:
|
|
12520
|
-
summary:
|
|
13444
|
+
var TransitionSchema = z9.object({
|
|
13445
|
+
completedPhase: z9.string(),
|
|
13446
|
+
suggestedNext: z9.string(),
|
|
13447
|
+
reason: z9.string(),
|
|
13448
|
+
artifacts: z9.array(z9.string()),
|
|
13449
|
+
requiresConfirmation: z9.boolean(),
|
|
13450
|
+
summary: z9.string()
|
|
12521
13451
|
});
|
|
12522
|
-
var EmitInteractionInputSchema =
|
|
12523
|
-
path:
|
|
13452
|
+
var EmitInteractionInputSchema = z9.object({
|
|
13453
|
+
path: z9.string(),
|
|
12524
13454
|
type: InteractionTypeSchema,
|
|
12525
|
-
stream:
|
|
13455
|
+
stream: z9.string().optional(),
|
|
12526
13456
|
question: QuestionSchema.optional(),
|
|
12527
13457
|
confirmation: ConfirmationSchema.optional(),
|
|
12528
13458
|
transition: TransitionSchema.optional()
|
|
@@ -12533,10 +13463,10 @@ var ProjectScanner = class {
|
|
|
12533
13463
|
}
|
|
12534
13464
|
rootDir;
|
|
12535
13465
|
async scan() {
|
|
12536
|
-
let projectName =
|
|
13466
|
+
let projectName = path22.basename(this.rootDir);
|
|
12537
13467
|
try {
|
|
12538
|
-
const pkgPath =
|
|
12539
|
-
const pkgRaw = await
|
|
13468
|
+
const pkgPath = path22.join(this.rootDir, "package.json");
|
|
13469
|
+
const pkgRaw = await fs23.readFile(pkgPath, "utf-8");
|
|
12540
13470
|
const pkg = JSON.parse(pkgRaw);
|
|
12541
13471
|
if (pkg.name) projectName = pkg.name;
|
|
12542
13472
|
} catch {
|
|
@@ -12649,13 +13579,13 @@ var BlueprintGenerator = class {
|
|
|
12649
13579
|
styles: STYLES,
|
|
12650
13580
|
scripts: SCRIPTS
|
|
12651
13581
|
});
|
|
12652
|
-
await
|
|
12653
|
-
await
|
|
13582
|
+
await fs24.mkdir(options.outputDir, { recursive: true });
|
|
13583
|
+
await fs24.writeFile(path23.join(options.outputDir, "index.html"), html);
|
|
12654
13584
|
}
|
|
12655
13585
|
};
|
|
12656
13586
|
function getStatePath() {
|
|
12657
13587
|
const home = process.env["HOME"] || os.homedir();
|
|
12658
|
-
return
|
|
13588
|
+
return path24.join(home, ".harness", "update-check.json");
|
|
12659
13589
|
}
|
|
12660
13590
|
function isUpdateCheckEnabled(configInterval) {
|
|
12661
13591
|
if (process.env["HARNESS_NO_UPDATE_CHECK"] === "1") return false;
|
|
@@ -12668,7 +13598,7 @@ function shouldRunCheck(state, intervalMs) {
|
|
|
12668
13598
|
}
|
|
12669
13599
|
function readCheckState() {
|
|
12670
13600
|
try {
|
|
12671
|
-
const raw =
|
|
13601
|
+
const raw = fs25.readFileSync(getStatePath(), "utf-8");
|
|
12672
13602
|
const parsed = JSON.parse(raw);
|
|
12673
13603
|
if (typeof parsed === "object" && parsed !== null && "lastCheckTime" in parsed && typeof parsed.lastCheckTime === "number" && "currentVersion" in parsed && typeof parsed.currentVersion === "string") {
|
|
12674
13604
|
const state = parsed;
|
|
@@ -12685,7 +13615,7 @@ function readCheckState() {
|
|
|
12685
13615
|
}
|
|
12686
13616
|
function spawnBackgroundCheck(currentVersion) {
|
|
12687
13617
|
const statePath = getStatePath();
|
|
12688
|
-
const stateDir =
|
|
13618
|
+
const stateDir = path24.dirname(statePath);
|
|
12689
13619
|
const script = `
|
|
12690
13620
|
const { execSync } = require('child_process');
|
|
12691
13621
|
const fs = require('fs');
|
|
@@ -12769,9 +13699,9 @@ async function resolveWasmPath(grammarName) {
|
|
|
12769
13699
|
const { createRequire } = await import("module");
|
|
12770
13700
|
const require2 = createRequire(import.meta.url ?? __filename);
|
|
12771
13701
|
const pkgPath = require2.resolve("tree-sitter-wasms/package.json");
|
|
12772
|
-
const
|
|
12773
|
-
const pkgDir =
|
|
12774
|
-
return
|
|
13702
|
+
const path28 = await import("path");
|
|
13703
|
+
const pkgDir = path28.dirname(pkgPath);
|
|
13704
|
+
return path28.join(pkgDir, "out", `${grammarName}.wasm`);
|
|
12775
13705
|
}
|
|
12776
13706
|
async function loadLanguage(lang) {
|
|
12777
13707
|
const grammarName = GRAMMAR_MAP[lang];
|
|
@@ -13213,14 +14143,14 @@ var LITELLM_PRICING_URL = "https://raw.githubusercontent.com/BerriAI/litellm/mai
|
|
|
13213
14143
|
var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
13214
14144
|
var STALENESS_WARNING_DAYS = 7;
|
|
13215
14145
|
function getCachePath(projectRoot) {
|
|
13216
|
-
return
|
|
14146
|
+
return path25.join(projectRoot, ".harness", "cache", "pricing.json");
|
|
13217
14147
|
}
|
|
13218
14148
|
function getStalenessMarkerPath(projectRoot) {
|
|
13219
|
-
return
|
|
14149
|
+
return path25.join(projectRoot, ".harness", "cache", "staleness-marker.json");
|
|
13220
14150
|
}
|
|
13221
14151
|
async function readDiskCache(projectRoot) {
|
|
13222
14152
|
try {
|
|
13223
|
-
const raw = await
|
|
14153
|
+
const raw = await fs26.readFile(getCachePath(projectRoot), "utf-8");
|
|
13224
14154
|
return JSON.parse(raw);
|
|
13225
14155
|
} catch {
|
|
13226
14156
|
return null;
|
|
@@ -13228,8 +14158,8 @@ async function readDiskCache(projectRoot) {
|
|
|
13228
14158
|
}
|
|
13229
14159
|
async function writeDiskCache(projectRoot, data) {
|
|
13230
14160
|
const cachePath = getCachePath(projectRoot);
|
|
13231
|
-
await
|
|
13232
|
-
await
|
|
14161
|
+
await fs26.mkdir(path25.dirname(cachePath), { recursive: true });
|
|
14162
|
+
await fs26.writeFile(cachePath, JSON.stringify(data, null, 2));
|
|
13233
14163
|
}
|
|
13234
14164
|
async function fetchFromNetwork() {
|
|
13235
14165
|
try {
|
|
@@ -13256,7 +14186,7 @@ function loadFallbackDataset() {
|
|
|
13256
14186
|
async function checkAndWarnStaleness(projectRoot) {
|
|
13257
14187
|
const markerPath = getStalenessMarkerPath(projectRoot);
|
|
13258
14188
|
try {
|
|
13259
|
-
const raw = await
|
|
14189
|
+
const raw = await fs26.readFile(markerPath, "utf-8");
|
|
13260
14190
|
const marker = JSON.parse(raw);
|
|
13261
14191
|
const firstUse = new Date(marker.firstFallbackUse).getTime();
|
|
13262
14192
|
const now = Date.now();
|
|
@@ -13268,8 +14198,8 @@ async function checkAndWarnStaleness(projectRoot) {
|
|
|
13268
14198
|
}
|
|
13269
14199
|
} catch {
|
|
13270
14200
|
try {
|
|
13271
|
-
await
|
|
13272
|
-
await
|
|
14201
|
+
await fs26.mkdir(path25.dirname(markerPath), { recursive: true });
|
|
14202
|
+
await fs26.writeFile(
|
|
13273
14203
|
markerPath,
|
|
13274
14204
|
JSON.stringify({ firstFallbackUse: (/* @__PURE__ */ new Date()).toISOString() })
|
|
13275
14205
|
);
|
|
@@ -13279,7 +14209,7 @@ async function checkAndWarnStaleness(projectRoot) {
|
|
|
13279
14209
|
}
|
|
13280
14210
|
async function clearStalenessMarker(projectRoot) {
|
|
13281
14211
|
try {
|
|
13282
|
-
await
|
|
14212
|
+
await fs26.unlink(getStalenessMarkerPath(projectRoot));
|
|
13283
14213
|
} catch {
|
|
13284
14214
|
}
|
|
13285
14215
|
}
|
|
@@ -13481,10 +14411,10 @@ function parseLine(line, lineNumber) {
|
|
|
13481
14411
|
return record;
|
|
13482
14412
|
}
|
|
13483
14413
|
function readCostRecords(projectRoot) {
|
|
13484
|
-
const costsFile =
|
|
14414
|
+
const costsFile = path26.join(projectRoot, ".harness", "metrics", "costs.jsonl");
|
|
13485
14415
|
let raw;
|
|
13486
14416
|
try {
|
|
13487
|
-
raw =
|
|
14417
|
+
raw = fs27.readFileSync(costsFile, "utf-8");
|
|
13488
14418
|
} catch {
|
|
13489
14419
|
return [];
|
|
13490
14420
|
}
|
|
@@ -13531,7 +14461,7 @@ function parseCCLine(line, filePath, lineNumber) {
|
|
|
13531
14461
|
entry = JSON.parse(line);
|
|
13532
14462
|
} catch {
|
|
13533
14463
|
console.warn(
|
|
13534
|
-
`[harness usage] Skipping malformed CC JSONL line ${lineNumber} in ${
|
|
14464
|
+
`[harness usage] Skipping malformed CC JSONL line ${lineNumber} in ${path27.basename(filePath)}`
|
|
13535
14465
|
);
|
|
13536
14466
|
return null;
|
|
13537
14467
|
}
|
|
@@ -13545,7 +14475,7 @@ function parseCCLine(line, filePath, lineNumber) {
|
|
|
13545
14475
|
function readCCFile(filePath) {
|
|
13546
14476
|
let raw;
|
|
13547
14477
|
try {
|
|
13548
|
-
raw =
|
|
14478
|
+
raw = fs28.readFileSync(filePath, "utf-8");
|
|
13549
14479
|
} catch {
|
|
13550
14480
|
return [];
|
|
13551
14481
|
}
|
|
@@ -13567,10 +14497,10 @@ function readCCFile(filePath) {
|
|
|
13567
14497
|
}
|
|
13568
14498
|
function parseCCRecords() {
|
|
13569
14499
|
const homeDir = process.env.HOME ?? os2.homedir();
|
|
13570
|
-
const projectsDir =
|
|
14500
|
+
const projectsDir = path27.join(homeDir, ".claude", "projects");
|
|
13571
14501
|
let projectDirs;
|
|
13572
14502
|
try {
|
|
13573
|
-
projectDirs =
|
|
14503
|
+
projectDirs = fs28.readdirSync(projectsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => path27.join(projectsDir, d.name));
|
|
13574
14504
|
} catch {
|
|
13575
14505
|
return [];
|
|
13576
14506
|
}
|
|
@@ -13578,7 +14508,7 @@ function parseCCRecords() {
|
|
|
13578
14508
|
for (const dir of projectDirs) {
|
|
13579
14509
|
let files;
|
|
13580
14510
|
try {
|
|
13581
|
-
files =
|
|
14511
|
+
files = fs28.readdirSync(dir).filter((f) => f.endsWith(".jsonl")).map((f) => path27.join(dir, f));
|
|
13582
14512
|
} catch {
|
|
13583
14513
|
continue;
|
|
13584
14514
|
}
|
|
@@ -13707,6 +14637,33 @@ export {
|
|
|
13707
14637
|
syncConstraintNodes,
|
|
13708
14638
|
detectStaleConstraints,
|
|
13709
14639
|
resolveThresholds2 as resolveThresholds,
|
|
14640
|
+
CategorySnapshotSchema,
|
|
14641
|
+
TimelineSnapshotSchema,
|
|
14642
|
+
TimelineFileSchema,
|
|
14643
|
+
TrendLineSchema,
|
|
14644
|
+
TrendResultSchema,
|
|
14645
|
+
DEFAULT_STABILITY_THRESHOLDS,
|
|
14646
|
+
TimelineManager,
|
|
14647
|
+
ConfidenceTierSchema,
|
|
14648
|
+
RegressionResultSchema,
|
|
14649
|
+
DirectionSchema,
|
|
14650
|
+
CategoryForecastSchema,
|
|
14651
|
+
SpecImpactSignalsSchema,
|
|
14652
|
+
SpecImpactEstimateSchema,
|
|
14653
|
+
ContributingFeatureSchema,
|
|
14654
|
+
AdjustedForecastSchema,
|
|
14655
|
+
PredictionWarningSchema,
|
|
14656
|
+
StabilityForecastSchema,
|
|
14657
|
+
PredictionResultSchema,
|
|
14658
|
+
PredictionOptionsSchema,
|
|
14659
|
+
weightedLinearRegression,
|
|
14660
|
+
applyRecencyWeights,
|
|
14661
|
+
projectValue,
|
|
14662
|
+
weeksUntilThreshold,
|
|
14663
|
+
classifyConfidence,
|
|
14664
|
+
parseRoadmap,
|
|
14665
|
+
PredictionEngine,
|
|
14666
|
+
SpecImpactEstimator,
|
|
13710
14667
|
FailureEntrySchema,
|
|
13711
14668
|
HandoffSchema,
|
|
13712
14669
|
GateResultSchema,
|
|
@@ -13730,7 +14687,7 @@ export {
|
|
|
13730
14687
|
updateSessionIndex,
|
|
13731
14688
|
loadState,
|
|
13732
14689
|
saveState,
|
|
13733
|
-
|
|
14690
|
+
parseFrontmatter2,
|
|
13734
14691
|
extractIndexEntry,
|
|
13735
14692
|
clearLearningsCache,
|
|
13736
14693
|
appendLearning,
|
|
@@ -13835,7 +14792,6 @@ export {
|
|
|
13835
14792
|
checkEvidenceCoverage,
|
|
13836
14793
|
tagUncitedFindings,
|
|
13837
14794
|
runReviewPipeline,
|
|
13838
|
-
parseRoadmap,
|
|
13839
14795
|
serializeRoadmap,
|
|
13840
14796
|
STATUS_RANK,
|
|
13841
14797
|
isRegression,
|