@harness-engineering/cli 2.2.1 → 2.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/skills/claude-code/harness-roadmap-pilot/SKILL.md +13 -7
- package/dist/agents/skills/codex/harness-roadmap-pilot/SKILL.md +13 -7
- package/dist/agents/skills/cursor/harness-roadmap-pilot/SKILL.md +13 -7
- package/dist/agents/skills/gemini-cli/harness-roadmap-pilot/SKILL.md +13 -7
- package/dist/{agents-md-L4X4MOWT.js → agents-md-5JG3AE5A.js} +3 -3
- package/dist/{architecture-WJRIYQ45.js → architecture-544ISXDI.js} +4 -4
- package/dist/{assess-project-PHC2HO3V.js → assess-project-PBO6XFJC.js} +1 -1
- package/dist/bin/harness-mcp.js +15 -15
- package/dist/bin/harness.js +25 -25
- package/dist/{check-phase-gate-646KY67H.js → check-phase-gate-BZU4DGF3.js} +4 -4
- package/dist/{chunk-2QPHCR22.js → chunk-22OKI5QI.js} +9 -9
- package/dist/{chunk-CPYL6LJR.js → chunk-2564TBT6.js} +1 -1
- package/dist/{chunk-YG4ZBGEH.js → chunk-7BNK4M23.js} +1 -1
- package/dist/{chunk-L2IDE7WS.js → chunk-A36MGB3V.js} +277 -111
- package/dist/{chunk-LO7ZLV7S.js → chunk-DBJC4ULT.js} +12 -5
- package/dist/{chunk-JPWICLXQ.js → chunk-DHWFOHPX.js} +1 -1
- package/dist/{chunk-W4AFGFFB.js → chunk-EMJFHTPZ.js} +3 -3
- package/dist/{chunk-4HKVZO7R.js → chunk-IYA3NS5P.js} +1 -1
- package/dist/{chunk-YLOAQZLA.js → chunk-J4MX5DIA.js} +5 -5
- package/dist/{chunk-224XKVLP.js → chunk-JMACX5FN.js} +1 -1
- package/dist/{chunk-YLTRFZAT.js → chunk-KJR4Z2YC.js} +8 -8
- package/dist/{chunk-VJQLUS5I.js → chunk-KT5PWWWO.js} +1 -1
- package/dist/{chunk-OJMO4JBQ.js → chunk-LBYGGNEZ.js} +1 -1
- package/dist/{chunk-36X7TRUI.js → chunk-LYMPKO6N.js} +2 -2
- package/dist/{chunk-GOOTYTTV.js → chunk-N6DVSOLZ.js} +933 -500
- package/dist/{chunk-VKDBM3RX.js → chunk-QZ67TDLJ.js} +3 -3
- package/dist/{chunk-2ZURBWEV.js → chunk-R6GLLMRZ.js} +9 -9
- package/dist/{chunk-QTUPHTTK.js → chunk-T2ZMHXCX.js} +1 -1
- package/dist/{chunk-YWD6WXXM.js → chunk-TIJXG4UB.js} +6 -6
- package/dist/{chunk-KTWKW6KJ.js → chunk-TIQU5VY7.js} +1 -1
- package/dist/{chunk-E7W3YOXL.js → chunk-U2SF3KAQ.js} +2 -2
- package/dist/{chunk-TNZYQTEE.js → chunk-WMZKZCCE.js} +1 -1
- package/dist/{chunk-BMM3GXV6.js → chunk-XPRGWHBL.js} +80 -37
- package/dist/{chunk-KV7A5OZT.js → chunk-YI6NTL72.js} +5 -5
- package/dist/{chunk-K4WRQTEF.js → chunk-YNG7BWGB.js} +2034 -490
- package/dist/{ci-workflow-TOS5X527.js → ci-workflow-ZO6FPO3V.js} +3 -3
- package/dist/{dist-D27LJR2T.js → dist-SQAOKWXM.js} +5 -3
- package/dist/{dist-6RT2AVYU.js → dist-ZS3Y3YWP.js} +30 -2
- package/dist/{docs-HOJMQYDZ.js → docs-VW2KDPI3.js} +4 -4
- package/dist/{engine-D6UYH6DB.js → engine-LFQ2JSHY.js} +3 -3
- package/dist/{entropy-LZ4IDGZR.js → entropy-P62LCVJI.js} +3 -3
- package/dist/{feedback-WV7EQVK7.js → feedback-O3TZAPGX.js} +1 -1
- package/dist/{generate-agent-definitions-GFNXJCBY.js → generate-agent-definitions-WVCOZDKI.js} +4 -4
- package/dist/{glob-helper-FZPPTGJN.js → glob-helper-JE3MO4UD.js} +1 -1
- package/dist/{graph-loader-5MKHAJJF.js → graph-loader-TJU4E7KL.js} +1 -1
- package/dist/index.d.ts +15 -9
- package/dist/index.js +25 -25
- package/dist/{loader-Z23JTXIZ.js → loader-UB2A65DN.js} +3 -3
- package/dist/{mcp-JDJGELGD.js → mcp-ST26QMDK.js} +15 -15
- package/dist/{performance-7ATLHTQU.js → performance-5HQG3IU6.js} +4 -4
- package/dist/{review-pipeline-COBGGXHJ.js → review-pipeline-VRDL5A7J.js} +3 -3
- package/dist/{runtime-KCBNQUXK.js → runtime-TQQT2MWQ.js} +3 -3
- package/dist/{scan-BIJYHOE7.js → scan-SFX5U3TE.js} +1 -1
- package/dist/{security-MAPRIFQT.js → security-IMFAE6MW.js} +1 -1
- package/dist/{validate-74XTQR7Q.js → validate-GW2DG64P.js} +4 -4
- package/dist/{validate-cross-check-MI5MZ7MB.js → validate-cross-check-F5YQVXLT.js} +3 -3
- package/package.json +6 -6
|
@@ -9,17 +9,19 @@ import {
|
|
|
9
9
|
isOk
|
|
10
10
|
} from "./chunk-RL4VMEXL.js";
|
|
11
11
|
import {
|
|
12
|
+
DEFAULT_SKIP_DIRS,
|
|
12
13
|
GraphStore,
|
|
13
14
|
NODE_STABILITY,
|
|
14
15
|
init_dist as init_dist2,
|
|
15
|
-
queryTraceability
|
|
16
|
-
|
|
16
|
+
queryTraceability,
|
|
17
|
+
skipDirGlobs
|
|
18
|
+
} from "./chunk-XPRGWHBL.js";
|
|
17
19
|
import {
|
|
18
20
|
__esm,
|
|
19
21
|
__export
|
|
20
22
|
} from "./chunk-KFQGP6VL.js";
|
|
21
23
|
|
|
22
|
-
// ../core/dist/chunk-
|
|
24
|
+
// ../core/dist/chunk-4UI65RLE.mjs
|
|
23
25
|
import { z } from "zod";
|
|
24
26
|
import { createHash } from "crypto";
|
|
25
27
|
import { minimatch } from "minimatch";
|
|
@@ -69,17 +71,17 @@ function resolveFileToLayer(file, layers) {
|
|
|
69
71
|
}
|
|
70
72
|
return void 0;
|
|
71
73
|
}
|
|
72
|
-
async function fileExists(
|
|
74
|
+
async function fileExists(path46) {
|
|
73
75
|
try {
|
|
74
|
-
await accessAsync(
|
|
76
|
+
await accessAsync(path46, constants.F_OK);
|
|
75
77
|
return true;
|
|
76
78
|
} catch {
|
|
77
79
|
return false;
|
|
78
80
|
}
|
|
79
81
|
}
|
|
80
|
-
async function readFileContent(
|
|
82
|
+
async function readFileContent(path46) {
|
|
81
83
|
try {
|
|
82
|
-
const content = await readFileAsync(
|
|
84
|
+
const content = await readFileAsync(path46, "utf-8");
|
|
83
85
|
return Ok(content);
|
|
84
86
|
} catch (error) {
|
|
85
87
|
return Err(error);
|
|
@@ -485,9 +487,9 @@ async function resolveWasmPath(grammarName) {
|
|
|
485
487
|
const { createRequire } = await import("module");
|
|
486
488
|
const require2 = createRequire(import.meta.url ?? __filename);
|
|
487
489
|
const pkgPath = require2.resolve("tree-sitter-wasms/package.json");
|
|
488
|
-
const
|
|
489
|
-
const pkgDir =
|
|
490
|
-
return
|
|
490
|
+
const path46 = await import("path");
|
|
491
|
+
const pkgDir = path46.dirname(pkgPath);
|
|
492
|
+
return path46.join(pkgDir, "out", `${grammarName}.wasm`);
|
|
491
493
|
}
|
|
492
494
|
async function loadLanguage(lang) {
|
|
493
495
|
const grammarName = GRAMMAR_MAP[lang];
|
|
@@ -1421,7 +1423,7 @@ function mapForbiddenImportViolations(forbidden, rootDir, category) {
|
|
|
1421
1423
|
});
|
|
1422
1424
|
}
|
|
1423
1425
|
function isSkippedEntry(name) {
|
|
1424
|
-
return name.startsWith(".") || name
|
|
1426
|
+
return name.startsWith(".") || DEFAULT_SKIP_DIRS.has(name);
|
|
1425
1427
|
}
|
|
1426
1428
|
function isTsSourceFile(name) {
|
|
1427
1429
|
if (!name.endsWith(".ts") && !name.endsWith(".tsx")) return false;
|
|
@@ -1510,7 +1512,7 @@ function extractImportSources(content, filePath) {
|
|
|
1510
1512
|
return sources;
|
|
1511
1513
|
}
|
|
1512
1514
|
function isSkippedEntry2(name) {
|
|
1513
|
-
return name.startsWith(".") || name
|
|
1515
|
+
return name.startsWith(".") || DEFAULT_SKIP_DIRS.has(name);
|
|
1514
1516
|
}
|
|
1515
1517
|
function isTsSourceFile2(name) {
|
|
1516
1518
|
if (!name.endsWith(".ts") && !name.endsWith(".tsx")) return false;
|
|
@@ -1745,11 +1747,18 @@ async function toHaveMaxDepDepth(received, maxDepth) {
|
|
|
1745
1747
|
message: () => pass ? `Expected dependency depth in '${received.scope}' to exceed ${maxDepth} but it was ${maxActual}` : `Module '${received.scope}' has dependency depth ${maxActual} (limit: ${maxDepth})`
|
|
1746
1748
|
};
|
|
1747
1749
|
}
|
|
1748
|
-
var ArchMetricCategorySchema, ViolationSchema, MetricResultSchema, CategoryBaselineSchema, ArchBaselineSchema, CategoryRegressionSchema, ArchDiffResultSchema, ThresholdConfigSchema, ArchConfigSchema, ConstraintRuleSchema, ViolationSnapshotSchema, ViolationHistorySchema, EmergenceConfidenceSchema, EmergentConstraintSuggestionSchema, EmergenceResultSchema, accessAsync, readFileAsync, EXTENSION_BY_LANG, JS_EXT_FALLBACKS, CircularDepsCollector, TypeScriptParser, EXTENSION_MAP, parserCache, initialized, GRAMMAR_MAP, TOP_LEVEL_TYPES, METHOD_TYPES, IDENTIFIER_TYPES, NAME_EXTRACTORS, pythonStrategy, goStrategy, RUST_USE_ARG_TYPES, RUST_PUB_ITEM_TYPES, rustStrategy, JAVA_IMPORT_TYPES, JAVA_EXPORT_TYPES, javaStrategy, STRATEGIES, TreeSitterParser, ParserRegistry, defaultRegistry, LayerViolationCollector, ArchBaselineManager, DEFAULT_THRESHOLDS, FUNCTION_PATTERNS, DECISION_PATTERNS, ComplexityCollector, DEFAULT_THRESHOLDS2, CouplingCollector, ForbiddenImportCollector, ModuleSizeCollector, DepDepthCollector, defaultCollectors, archMatchers;
|
|
1749
|
-
var
|
|
1750
|
-
"../core/dist/chunk-
|
|
1750
|
+
var __defProp, __export2, ArchMetricCategorySchema, ViolationSchema, MetricResultSchema, CategoryBaselineSchema, ArchBaselineSchema, CategoryRegressionSchema, ArchDiffResultSchema, ThresholdConfigSchema, ArchConfigSchema, ConstraintRuleSchema, ViolationSnapshotSchema, ViolationHistorySchema, EmergenceConfidenceSchema, EmergentConstraintSuggestionSchema, EmergenceResultSchema, accessAsync, readFileAsync, EXTENSION_BY_LANG, JS_EXT_FALLBACKS, CircularDepsCollector, TypeScriptParser, EXTENSION_MAP, parserCache, initialized, GRAMMAR_MAP, TOP_LEVEL_TYPES, METHOD_TYPES, IDENTIFIER_TYPES, NAME_EXTRACTORS, pythonStrategy, goStrategy, RUST_USE_ARG_TYPES, RUST_PUB_ITEM_TYPES, rustStrategy, JAVA_IMPORT_TYPES, JAVA_EXPORT_TYPES, javaStrategy, STRATEGIES, TreeSitterParser, ParserRegistry, defaultRegistry, LayerViolationCollector, ArchBaselineManager, DEFAULT_THRESHOLDS, FUNCTION_PATTERNS, DECISION_PATTERNS, ComplexityCollector, DEFAULT_THRESHOLDS2, CouplingCollector, ForbiddenImportCollector, ModuleSizeCollector, DepDepthCollector, defaultCollectors, archMatchers;
|
|
1751
|
+
var init_chunk_4UI65RLE = __esm({
|
|
1752
|
+
"../core/dist/chunk-4UI65RLE.mjs"() {
|
|
1751
1753
|
"use strict";
|
|
1752
1754
|
init_dist();
|
|
1755
|
+
init_dist2();
|
|
1756
|
+
init_dist2();
|
|
1757
|
+
__defProp = Object.defineProperty;
|
|
1758
|
+
__export2 = (target, all) => {
|
|
1759
|
+
for (var name in all)
|
|
1760
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
1761
|
+
};
|
|
1753
1762
|
ArchMetricCategorySchema = z.enum([
|
|
1754
1763
|
"circular-deps",
|
|
1755
1764
|
"layer-violations",
|
|
@@ -1926,11 +1935,11 @@ var init_chunk_CE225WRK = __esm({
|
|
|
1926
1935
|
TypeScriptParser = class {
|
|
1927
1936
|
name = "typescript";
|
|
1928
1937
|
extensions = [".ts", ".tsx", ".mts", ".cts"];
|
|
1929
|
-
async parseFile(
|
|
1930
|
-
const contentResult = await readFileContent(
|
|
1938
|
+
async parseFile(path46) {
|
|
1939
|
+
const contentResult = await readFileContent(path46);
|
|
1931
1940
|
if (!contentResult.ok) {
|
|
1932
1941
|
return Err(
|
|
1933
|
-
createParseError("NOT_FOUND", `File not found: ${
|
|
1942
|
+
createParseError("NOT_FOUND", `File not found: ${path46}`, { path: path46 }, [
|
|
1934
1943
|
"Check that the file exists",
|
|
1935
1944
|
"Verify the path is correct"
|
|
1936
1945
|
])
|
|
@@ -1940,7 +1949,7 @@ var init_chunk_CE225WRK = __esm({
|
|
|
1940
1949
|
const ast = parse(contentResult.value, {
|
|
1941
1950
|
loc: true,
|
|
1942
1951
|
range: true,
|
|
1943
|
-
jsx:
|
|
1952
|
+
jsx: path46.endsWith(".tsx"),
|
|
1944
1953
|
errorOnUnknownASTType: false
|
|
1945
1954
|
});
|
|
1946
1955
|
return Ok({
|
|
@@ -1951,7 +1960,7 @@ var init_chunk_CE225WRK = __esm({
|
|
|
1951
1960
|
} catch (e) {
|
|
1952
1961
|
const error = e;
|
|
1953
1962
|
return Err(
|
|
1954
|
-
createParseError("SYNTAX_ERROR", `Failed to parse ${
|
|
1963
|
+
createParseError("SYNTAX_ERROR", `Failed to parse ${path46}: ${error.message}`, { path: path46 }, [
|
|
1955
1964
|
"Check for syntax errors in the file",
|
|
1956
1965
|
"Ensure valid TypeScript syntax"
|
|
1957
1966
|
])
|
|
@@ -2266,11 +2275,11 @@ var init_chunk_CE225WRK = __esm({
|
|
|
2266
2275
|
this.extensions = extensions;
|
|
2267
2276
|
this.strategy = strategy;
|
|
2268
2277
|
}
|
|
2269
|
-
async parseFile(
|
|
2270
|
-
const contentResult = await readFileContent(
|
|
2278
|
+
async parseFile(path46) {
|
|
2279
|
+
const contentResult = await readFileContent(path46);
|
|
2271
2280
|
if (!contentResult.ok) {
|
|
2272
2281
|
return Err(
|
|
2273
|
-
createParseError("NOT_FOUND", `File not found: ${
|
|
2282
|
+
createParseError("NOT_FOUND", `File not found: ${path46}`, { path: path46 }, [
|
|
2274
2283
|
"Check that the file exists"
|
|
2275
2284
|
])
|
|
2276
2285
|
);
|
|
@@ -2286,7 +2295,7 @@ var init_chunk_CE225WRK = __esm({
|
|
|
2286
2295
|
} catch (e) {
|
|
2287
2296
|
const error = e;
|
|
2288
2297
|
return Err(
|
|
2289
|
-
createParseError("SYNTAX_ERROR", `Failed to parse ${
|
|
2298
|
+
createParseError("SYNTAX_ERROR", `Failed to parse ${path46}: ${error.message}`, { path: path46 }, [
|
|
2290
2299
|
"Check for syntax errors in the file"
|
|
2291
2300
|
])
|
|
2292
2301
|
);
|
|
@@ -2854,6 +2863,7 @@ __export(dist_exports, {
|
|
|
2854
2863
|
CompoundLockHeldError: () => CompoundLockHeldError,
|
|
2855
2864
|
ConfidenceTierSchema: () => ConfidenceTierSchema,
|
|
2856
2865
|
ConfirmationSchema: () => ConfirmationSchema,
|
|
2866
|
+
ConflictError: () => ConflictError,
|
|
2857
2867
|
ConsoleSink: () => ConsoleSink,
|
|
2858
2868
|
ConstraintRuleSchema: () => ConstraintRuleSchema,
|
|
2859
2869
|
ContributingFeatureSchema: () => ContributingFeatureSchema,
|
|
@@ -2873,6 +2883,7 @@ __export(dist_exports, {
|
|
|
2873
2883
|
DepDepthCollector: () => DepDepthCollector,
|
|
2874
2884
|
DirectionSchema: () => DirectionSchema,
|
|
2875
2885
|
EMPTY_SUPPLY_CHAIN: () => EMPTY_SUPPLY_CHAIN,
|
|
2886
|
+
ETagStore: () => ETagStore,
|
|
2876
2887
|
EVIDENCE_SATURATION: () => EVIDENCE_SATURATION,
|
|
2877
2888
|
EXTENSION_MAP: () => EXTENSION_MAP,
|
|
2878
2889
|
EmergenceConfidenceSchema: () => EmergenceConfidenceSchema,
|
|
@@ -2975,6 +2986,7 @@ __export(dist_exports, {
|
|
|
2975
2986
|
ViolationHistorySchema: () => ViolationHistorySchema,
|
|
2976
2987
|
ViolationSchema: () => ViolationSchema,
|
|
2977
2988
|
ViolationSnapshotSchema: () => ViolationSnapshotSchema,
|
|
2989
|
+
WHATWG_BAD_PORTS: () => WHATWG_BAD_PORTS,
|
|
2978
2990
|
acquireCompoundLock: () => acquireCompoundLock,
|
|
2979
2991
|
addProvenance: () => addProvenance,
|
|
2980
2992
|
agentConfigRules: () => agentConfigRules,
|
|
@@ -3000,6 +3012,7 @@ __export(dist_exports, {
|
|
|
3000
3012
|
archiveStream: () => archiveStream,
|
|
3001
3013
|
assembleCandidateReport: () => assembleCandidateReport,
|
|
3002
3014
|
assembleReport: () => assembleReport,
|
|
3015
|
+
assertPortUsable: () => assertPortUsable,
|
|
3003
3016
|
assertSanitized: () => assertSanitized,
|
|
3004
3017
|
assignFeature: () => assignFeature,
|
|
3005
3018
|
buildDependencyGraph: () => buildDependencyGraph,
|
|
@@ -3044,6 +3057,7 @@ __export(dist_exports, {
|
|
|
3044
3057
|
createRegionMap: () => createRegionMap,
|
|
3045
3058
|
createSelfReview: () => createSelfReview,
|
|
3046
3059
|
createStream: () => createStream,
|
|
3060
|
+
createTrackerClient: () => createTrackerClient,
|
|
3047
3061
|
crossReferenceUndocumentedFixes: () => crossReferenceUndocumentedFixes,
|
|
3048
3062
|
cryptoRules: () => cryptoRules,
|
|
3049
3063
|
deduplicateCleanupFindings: () => deduplicateCleanupFindings,
|
|
@@ -3104,6 +3118,7 @@ __export(dist_exports, {
|
|
|
3104
3118
|
getParser: () => getParser,
|
|
3105
3119
|
getPhaseCategories: () => getPhaseCategories,
|
|
3106
3120
|
getPulseAdapter: () => getPulseAdapter,
|
|
3121
|
+
getRoadmapMode: () => getRoadmapMode,
|
|
3107
3122
|
getStreamForBranch: () => getStreamForBranch,
|
|
3108
3123
|
getTaintFilePath: () => getTaintFilePath,
|
|
3109
3124
|
getTrustLevel: () => getTrustLevel,
|
|
@@ -3112,6 +3127,7 @@ __export(dist_exports, {
|
|
|
3112
3127
|
goRules: () => goRules,
|
|
3113
3128
|
injectionRules: () => injectionRules,
|
|
3114
3129
|
insecureDefaultsRules: () => insecureDefaultsRules,
|
|
3130
|
+
isBadPort: () => isBadPort,
|
|
3115
3131
|
isDuplicateFinding: () => isDuplicateFinding,
|
|
3116
3132
|
isErr: () => isErr,
|
|
3117
3133
|
isOk: () => isOk,
|
|
@@ -3130,17 +3146,21 @@ __export(dist_exports, {
|
|
|
3130
3146
|
loadHandoff: () => loadHandoff,
|
|
3131
3147
|
loadIndexEntries: () => loadIndexEntries,
|
|
3132
3148
|
loadPricingData: () => loadPricingData,
|
|
3149
|
+
loadProjectRoadmapMode: () => loadProjectRoadmapMode,
|
|
3133
3150
|
loadRelevantLearnings: () => loadRelevantLearnings,
|
|
3134
3151
|
loadSessionSummary: () => loadSessionSummary,
|
|
3135
3152
|
loadState: () => loadState,
|
|
3136
3153
|
loadStreamIndex: () => loadStreamIndex,
|
|
3154
|
+
loadTrackerClientConfigFromProject: () => loadTrackerClientConfigFromProject,
|
|
3137
3155
|
loadTrackerSyncConfig: () => loadTrackerSyncConfig,
|
|
3138
3156
|
logAgentAction: () => logAgentAction,
|
|
3157
|
+
makeTrackerConflictBody: () => makeTrackerConflictBody,
|
|
3139
3158
|
mapInjectionFindings: () => mapInjectionFindings,
|
|
3140
3159
|
mapSecurityFindings: () => mapSecurityFindings,
|
|
3141
3160
|
mapSecuritySeverity: () => mapSecuritySeverity,
|
|
3142
3161
|
markProtectedFindings: () => markProtectedFindings,
|
|
3143
3162
|
mcpRules: () => mcpRules,
|
|
3163
|
+
migrate: () => migrate_exports,
|
|
3144
3164
|
migrateToStreams: () => migrateToStreams,
|
|
3145
3165
|
networkRules: () => networkRules,
|
|
3146
3166
|
nodeRules: () => nodeRules,
|
|
@@ -3212,6 +3232,8 @@ __export(dist_exports, {
|
|
|
3212
3232
|
scanForInjection: () => scanForInjection,
|
|
3213
3233
|
scopeContext: () => scopeContext,
|
|
3214
3234
|
scoreRoadmapCandidates: () => scoreRoadmapCandidates,
|
|
3235
|
+
scoreRoadmapCandidatesFileLess: () => scoreRoadmapCandidatesFileLess,
|
|
3236
|
+
scoreRoadmapCandidatesForMode: () => scoreRoadmapCandidatesForMode,
|
|
3215
3237
|
searchSymbols: () => searchSymbols,
|
|
3216
3238
|
secretRules: () => secretRules,
|
|
3217
3239
|
securityFindingId: () => securityFindingId,
|
|
@@ -3249,6 +3271,7 @@ __export(dist_exports, {
|
|
|
3249
3271
|
validateKnowledgeMap: () => validateKnowledgeMap,
|
|
3250
3272
|
validatePatternConfig: () => validatePatternConfig,
|
|
3251
3273
|
validatePulseConfig: () => validatePulseConfig,
|
|
3274
|
+
validateRoadmapMode: () => validateRoadmapMode,
|
|
3252
3275
|
validateSolutionsDir: () => validateSolutionsDir,
|
|
3253
3276
|
violationId: () => violationId,
|
|
3254
3277
|
weeksUntilThreshold: () => weeksUntilThreshold,
|
|
@@ -3294,63 +3317,64 @@ import { existsSync as existsSync9 } from "fs";
|
|
|
3294
3317
|
import { join as join11 } from "path";
|
|
3295
3318
|
import { existsSync as existsSync10 } from "fs";
|
|
3296
3319
|
import { join as join12 } from "path";
|
|
3320
|
+
import * as fs3 from "fs";
|
|
3321
|
+
import * as path3 from "path";
|
|
3297
3322
|
import { minimatch as minimatch2 } from "minimatch";
|
|
3298
3323
|
import { basename as basename2 } from "path";
|
|
3299
|
-
import { join as
|
|
3324
|
+
import { join as join14, basename as basename3 } from "path";
|
|
3300
3325
|
import { basename as basename4, dirname as dirname42 } from "path";
|
|
3301
3326
|
import { z as z3 } from "zod";
|
|
3302
|
-
import * as fs3 from "fs/promises";
|
|
3303
3327
|
import * as fs4 from "fs/promises";
|
|
3304
|
-
import
|
|
3328
|
+
import * as fs5 from "fs/promises";
|
|
3329
|
+
import { resolve as resolve5 } from "path";
|
|
3305
3330
|
import { minimatch as minimatch22 } from "minimatch";
|
|
3306
|
-
import {
|
|
3307
|
-
import { dirname as
|
|
3331
|
+
import { join as join15, resolve as resolve42 } from "path";
|
|
3332
|
+
import { dirname as dirname5, resolve as resolve6 } from "path";
|
|
3333
|
+
import { dirname as dirname6, extname as extname2, resolve as resolve7 } from "path";
|
|
3308
3334
|
import { minimatch as minimatch3 } from "minimatch";
|
|
3309
3335
|
import { readdirSync, statSync as statSync2 } from "fs";
|
|
3310
|
-
import { join as
|
|
3311
|
-
import * as
|
|
3336
|
+
import { join as join16 } from "path";
|
|
3337
|
+
import * as fs6 from "fs";
|
|
3312
3338
|
import { promisify as promisify2 } from "util";
|
|
3313
|
-
import { dirname as dirname7, basename as basename5, join as
|
|
3339
|
+
import { dirname as dirname7, basename as basename5, join as join17 } from "path";
|
|
3314
3340
|
import { z as z4 } from "zod";
|
|
3315
|
-
import { readFileSync as readFileSync22, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as
|
|
3316
|
-
import { join as
|
|
3341
|
+
import { readFileSync as readFileSync22, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync12 } from "fs";
|
|
3342
|
+
import { join as join18, dirname as dirname8 } from "path";
|
|
3317
3343
|
import { execFileSync } from "child_process";
|
|
3318
|
-
import * as
|
|
3319
|
-
import * as
|
|
3320
|
-
import { appendFileSync, writeFileSync as writeFileSync22, existsSync as
|
|
3344
|
+
import * as fs7 from "fs";
|
|
3345
|
+
import * as path4 from "path";
|
|
3346
|
+
import { appendFileSync, writeFileSync as writeFileSync22, existsSync as existsSync13, mkdirSync as mkdirSync22 } from "fs";
|
|
3321
3347
|
import { dirname as dirname9 } from "path";
|
|
3322
3348
|
import { z as z5 } from "zod";
|
|
3323
|
-
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, renameSync as renameSync2, mkdirSync as mkdirSync3, existsSync as
|
|
3349
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, renameSync as renameSync2, mkdirSync as mkdirSync3, existsSync as existsSync14 } from "fs";
|
|
3324
3350
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
3325
|
-
import { join as
|
|
3351
|
+
import { join as join20, dirname as dirname10 } from "path";
|
|
3326
3352
|
import { z as z6 } from "zod";
|
|
3327
|
-
import * as fs7 from "fs";
|
|
3328
|
-
import * as path4 from "path";
|
|
3329
3353
|
import * as fs8 from "fs";
|
|
3330
3354
|
import * as path5 from "path";
|
|
3331
3355
|
import * as fs9 from "fs";
|
|
3332
3356
|
import * as path6 from "path";
|
|
3357
|
+
import * as fs10 from "fs";
|
|
3358
|
+
import * as path7 from "path";
|
|
3333
3359
|
import { z as z7 } from "zod";
|
|
3360
|
+
import * as fs14 from "fs";
|
|
3361
|
+
import * as path11 from "path";
|
|
3334
3362
|
import * as fs13 from "fs";
|
|
3335
3363
|
import * as path10 from "path";
|
|
3336
|
-
import * as fs12 from "fs";
|
|
3337
|
-
import * as path9 from "path";
|
|
3338
|
-
import * as fs10 from "fs";
|
|
3339
|
-
import * as path7 from "path";
|
|
3340
|
-
import { execSync } from "child_process";
|
|
3341
|
-
import { z as z8 } from "zod";
|
|
3342
3364
|
import * as fs11 from "fs";
|
|
3343
3365
|
import * as path8 from "path";
|
|
3344
|
-
import
|
|
3345
|
-
import
|
|
3346
|
-
import * as
|
|
3366
|
+
import { execSync } from "child_process";
|
|
3367
|
+
import { z as z8 } from "zod";
|
|
3368
|
+
import * as fs12 from "fs";
|
|
3369
|
+
import * as path9 from "path";
|
|
3347
3370
|
import * as fs15 from "fs";
|
|
3348
3371
|
import * as path12 from "path";
|
|
3372
|
+
import * as crypto2 from "crypto";
|
|
3349
3373
|
import * as fs16 from "fs";
|
|
3350
3374
|
import * as path13 from "path";
|
|
3351
|
-
import * as crypto3 from "crypto";
|
|
3352
3375
|
import * as fs17 from "fs";
|
|
3353
3376
|
import * as path14 from "path";
|
|
3377
|
+
import * as crypto3 from "crypto";
|
|
3354
3378
|
import * as fs18 from "fs";
|
|
3355
3379
|
import * as path15 from "path";
|
|
3356
3380
|
import * as fs19 from "fs";
|
|
@@ -3359,77 +3383,96 @@ import * as fs20 from "fs";
|
|
|
3359
3383
|
import * as path17 from "path";
|
|
3360
3384
|
import * as fs21 from "fs";
|
|
3361
3385
|
import * as path18 from "path";
|
|
3362
|
-
import { execSync as execSync2 } from "child_process";
|
|
3363
3386
|
import * as fs22 from "fs";
|
|
3364
3387
|
import * as path19 from "path";
|
|
3388
|
+
import { execSync as execSync2 } from "child_process";
|
|
3365
3389
|
import * as fs23 from "fs";
|
|
3366
3390
|
import * as path20 from "path";
|
|
3367
3391
|
import * as fs24 from "fs";
|
|
3368
3392
|
import * as path21 from "path";
|
|
3369
3393
|
import * as fs25 from "fs";
|
|
3370
3394
|
import * as path22 from "path";
|
|
3395
|
+
import * as fs26 from "fs";
|
|
3396
|
+
import * as path23 from "path";
|
|
3371
3397
|
import { z as z9 } from "zod";
|
|
3372
|
-
import * as
|
|
3398
|
+
import * as fs28 from "fs/promises";
|
|
3373
3399
|
import { minimatch as minimatch4 } from "minimatch";
|
|
3374
3400
|
import { z as z10 } from "zod";
|
|
3375
|
-
import * as
|
|
3376
|
-
import * as
|
|
3401
|
+
import * as fs27 from "fs";
|
|
3402
|
+
import * as path24 from "path";
|
|
3377
3403
|
import { readFileSync as readFileSync222, writeFileSync as writeFileSync15, unlinkSync as unlinkSync2, mkdirSync as mkdirSync14, readdirSync as readdirSync3 } from "fs";
|
|
3378
|
-
import { join as
|
|
3404
|
+
import { join as join392, dirname as dirname12 } from "path";
|
|
3379
3405
|
import { createHash as createHash32 } from "crypto";
|
|
3380
3406
|
import { z as z11 } from "zod";
|
|
3381
|
-
import { readFileSync as readFileSync23, writeFileSync as writeFileSync16, renameSync as renameSync82, mkdirSync as mkdirSync15, existsSync as
|
|
3407
|
+
import { readFileSync as readFileSync23, writeFileSync as writeFileSync16, renameSync as renameSync82, mkdirSync as mkdirSync15, existsSync as existsSync342 } from "fs";
|
|
3382
3408
|
import { execSync as execSync3 } from "child_process";
|
|
3383
3409
|
import { randomBytes as randomBytes22 } from "crypto";
|
|
3384
|
-
import { isAbsolute as isAbsolute4, join as
|
|
3385
|
-
import * as path24 from "path";
|
|
3410
|
+
import { isAbsolute as isAbsolute4, join as join40, relative as relative32, dirname as dirname13 } from "path";
|
|
3386
3411
|
import * as path25 from "path";
|
|
3387
3412
|
import * as path26 from "path";
|
|
3388
3413
|
import * as path27 from "path";
|
|
3389
|
-
import * as fs28 from "fs";
|
|
3390
3414
|
import * as path28 from "path";
|
|
3391
3415
|
import * as fs29 from "fs";
|
|
3392
3416
|
import * as path29 from "path";
|
|
3393
3417
|
import * as fs30 from "fs";
|
|
3394
|
-
import { z as z12 } from "zod";
|
|
3395
|
-
import * as fs31 from "fs/promises";
|
|
3396
3418
|
import * as path30 from "path";
|
|
3397
|
-
import * as
|
|
3419
|
+
import * as fs31 from "fs";
|
|
3420
|
+
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
3421
|
+
import * as fs32 from "fs";
|
|
3398
3422
|
import * as path31 from "path";
|
|
3399
|
-
import * as ejs from "ejs";
|
|
3400
3423
|
import * as fs33 from "fs";
|
|
3401
3424
|
import * as path32 from "path";
|
|
3402
|
-
import
|
|
3403
|
-
import { spawn as spawn2 } from "child_process";
|
|
3404
|
-
import * as fs34 from "fs/promises";
|
|
3425
|
+
import { createHash as createHash4 } from "crypto";
|
|
3405
3426
|
import * as path33 from "path";
|
|
3406
|
-
import
|
|
3427
|
+
import { z as z12 } from "zod";
|
|
3428
|
+
import * as fs34 from "fs/promises";
|
|
3407
3429
|
import * as path34 from "path";
|
|
3408
|
-
import * as
|
|
3430
|
+
import * as fs35 from "fs/promises";
|
|
3409
3431
|
import * as path35 from "path";
|
|
3410
|
-
import * as
|
|
3411
|
-
import * as
|
|
3432
|
+
import * as ejs from "ejs";
|
|
3433
|
+
import * as fs36 from "fs";
|
|
3412
3434
|
import * as path36 from "path";
|
|
3413
|
-
import * as
|
|
3414
|
-
import
|
|
3415
|
-
import
|
|
3416
|
-
import * as fs38 from "fs";
|
|
3435
|
+
import * as os from "os";
|
|
3436
|
+
import { spawn as spawn2 } from "child_process";
|
|
3437
|
+
import * as fs37 from "fs/promises";
|
|
3417
3438
|
import * as path37 from "path";
|
|
3418
|
-
import * as
|
|
3419
|
-
import * as
|
|
3439
|
+
import * as fs38 from "fs";
|
|
3440
|
+
import * as path38 from "path";
|
|
3441
|
+
import * as fs39 from "fs";
|
|
3420
3442
|
import * as path39 from "path";
|
|
3421
|
-
import * as
|
|
3422
|
-
import * as
|
|
3443
|
+
import * as os2 from "os";
|
|
3444
|
+
import * as fs40 from "fs";
|
|
3423
3445
|
import * as path40 from "path";
|
|
3424
|
-
import
|
|
3446
|
+
import * as fs42 from "fs";
|
|
3447
|
+
import * as path42 from "path";
|
|
3448
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
3449
|
+
import * as fs41 from "fs";
|
|
3450
|
+
import * as path41 from "path";
|
|
3451
|
+
import * as crypto4 from "crypto";
|
|
3452
|
+
import * as fs43 from "fs";
|
|
3453
|
+
import * as path43 from "path";
|
|
3454
|
+
import * as fs44 from "fs";
|
|
3455
|
+
import * as fs45 from "fs";
|
|
3456
|
+
import * as path44 from "path";
|
|
3457
|
+
import { readFileSync as readFileSync38 } from "fs";
|
|
3425
3458
|
import { fileURLToPath } from "url";
|
|
3426
|
-
import { dirname as dirname18, join as
|
|
3459
|
+
import { dirname as dirname18, join as join612 } from "path";
|
|
3427
3460
|
import { execFile } from "child_process";
|
|
3428
3461
|
import { promisify as promisify22 } from "util";
|
|
3429
3462
|
import { execFile as execFile2 } from "child_process";
|
|
3430
3463
|
import { promisify as promisify3 } from "util";
|
|
3431
|
-
import * as
|
|
3432
|
-
import * as
|
|
3464
|
+
import * as fs46 from "fs/promises";
|
|
3465
|
+
import * as path45 from "path";
|
|
3466
|
+
function isBadPort(port) {
|
|
3467
|
+
return BAD_PORT_SET.has(port);
|
|
3468
|
+
}
|
|
3469
|
+
function assertPortUsable(port, label = "server") {
|
|
3470
|
+
if (isBadPort(port)) {
|
|
3471
|
+
throw new Error(
|
|
3472
|
+
`Refusing to bind ${label} to port ${port}: this port is on the WHATWG fetch bad-ports list, so browsers and Node's fetch() will reject every connection with "bad port". Choose a different port. See https://fetch.spec.whatwg.org/#port-blocking for the full list.`
|
|
3473
|
+
);
|
|
3474
|
+
}
|
|
3475
|
+
}
|
|
3433
3476
|
async function validateFileStructure(projectPath, conventions) {
|
|
3434
3477
|
const missing = [];
|
|
3435
3478
|
const unexpected = [];
|
|
@@ -3465,15 +3508,15 @@ function validateConfig(data, schema) {
|
|
|
3465
3508
|
let message = "Configuration validation failed";
|
|
3466
3509
|
const suggestions = [];
|
|
3467
3510
|
if (firstError) {
|
|
3468
|
-
const
|
|
3469
|
-
const pathDisplay =
|
|
3511
|
+
const path46 = firstError.path.join(".");
|
|
3512
|
+
const pathDisplay = path46 ? ` at "${path46}"` : "";
|
|
3470
3513
|
if (firstError.code === "invalid_type") {
|
|
3471
3514
|
const received = firstError.received;
|
|
3472
3515
|
const expected = firstError.expected;
|
|
3473
3516
|
if (received === "undefined") {
|
|
3474
3517
|
code = "MISSING_FIELD";
|
|
3475
3518
|
message = `Missing required field${pathDisplay}: ${firstError.message}`;
|
|
3476
|
-
suggestions.push(`Field "${
|
|
3519
|
+
suggestions.push(`Field "${path46}" is required and must be of type "${expected}"`);
|
|
3477
3520
|
} else {
|
|
3478
3521
|
code = "INVALID_TYPE";
|
|
3479
3522
|
message = `Invalid type${pathDisplay}: ${firstError.message}`;
|
|
@@ -3689,7 +3732,7 @@ function isAgnixDisabled() {
|
|
|
3689
3732
|
return v === "1" || v === "true";
|
|
3690
3733
|
}
|
|
3691
3734
|
function runAgnix(cwd, strict, binPath, timeoutMs = DEFAULT_AGNIX_TIMEOUT_MS, spawnFn = spawn) {
|
|
3692
|
-
return new Promise((
|
|
3735
|
+
return new Promise((resolve10) => {
|
|
3693
3736
|
const args = ["--format", "json"];
|
|
3694
3737
|
if (strict) args.push("--strict");
|
|
3695
3738
|
args.push(cwd);
|
|
@@ -3703,7 +3746,7 @@ function runAgnix(cwd, strict, binPath, timeoutMs = DEFAULT_AGNIX_TIMEOUT_MS, sp
|
|
|
3703
3746
|
if (settled) return;
|
|
3704
3747
|
settled = true;
|
|
3705
3748
|
clearTimeout(timer);
|
|
3706
|
-
|
|
3749
|
+
resolve10(outcome);
|
|
3707
3750
|
};
|
|
3708
3751
|
const timer = setTimeout(() => {
|
|
3709
3752
|
child.kill("SIGKILL");
|
|
@@ -3807,16 +3850,16 @@ function makeFinding(input) {
|
|
|
3807
3850
|
if (input.suggestion) finding.suggestion = input.suggestion;
|
|
3808
3851
|
return finding;
|
|
3809
3852
|
}
|
|
3810
|
-
function readTextSafe(
|
|
3853
|
+
function readTextSafe(path46) {
|
|
3811
3854
|
try {
|
|
3812
|
-
return readFileSync2(
|
|
3855
|
+
return readFileSync2(path46, "utf-8");
|
|
3813
3856
|
} catch {
|
|
3814
3857
|
return null;
|
|
3815
3858
|
}
|
|
3816
3859
|
}
|
|
3817
|
-
function safeFileSize(
|
|
3860
|
+
function safeFileSize(path46) {
|
|
3818
3861
|
try {
|
|
3819
|
-
return statSync(
|
|
3862
|
+
return statSync(path46).size;
|
|
3820
3863
|
} catch {
|
|
3821
3864
|
return null;
|
|
3822
3865
|
}
|
|
@@ -4361,27 +4404,27 @@ function extractSections(content) {
|
|
|
4361
4404
|
}
|
|
4362
4405
|
return sections.map((section) => buildAgentMapSection(section, lines));
|
|
4363
4406
|
}
|
|
4364
|
-
function isExternalLink(
|
|
4365
|
-
return
|
|
4407
|
+
function isExternalLink(path46) {
|
|
4408
|
+
return path46.startsWith("http://") || path46.startsWith("https://") || path46.startsWith("#") || path46.startsWith("mailto:");
|
|
4366
4409
|
}
|
|
4367
4410
|
function resolveLinkPath(linkPath, baseDir) {
|
|
4368
4411
|
return linkPath.startsWith(".") ? join8(baseDir, linkPath) : linkPath;
|
|
4369
4412
|
}
|
|
4370
|
-
async function validateAgentsMap(
|
|
4371
|
-
const contentResult = await readFileContent(
|
|
4413
|
+
async function validateAgentsMap(path46 = "./AGENTS.md") {
|
|
4414
|
+
const contentResult = await readFileContent(path46);
|
|
4372
4415
|
if (!contentResult.ok) {
|
|
4373
4416
|
return Err(
|
|
4374
4417
|
createError(
|
|
4375
4418
|
"PARSE_ERROR",
|
|
4376
4419
|
`Failed to read AGENTS.md: ${contentResult.error.message}`,
|
|
4377
|
-
{ path:
|
|
4420
|
+
{ path: path46 },
|
|
4378
4421
|
["Ensure the file exists", "Check file permissions"]
|
|
4379
4422
|
)
|
|
4380
4423
|
);
|
|
4381
4424
|
}
|
|
4382
4425
|
const content = contentResult.value;
|
|
4383
4426
|
const sections = extractSections(content);
|
|
4384
|
-
const baseDir = dirname22(
|
|
4427
|
+
const baseDir = dirname22(path46);
|
|
4385
4428
|
const sectionTitles = sections.map((s) => s.title);
|
|
4386
4429
|
const missingSections = REQUIRED_SECTIONS.filter(
|
|
4387
4430
|
(required) => !sectionTitles.some((title) => title.toLowerCase().includes(required.toLowerCase()))
|
|
@@ -4725,6 +4768,45 @@ function promoteWarningsToErrors(findings) {
|
|
|
4725
4768
|
function hasErrors(findings) {
|
|
4726
4769
|
return findings.some((f) => f.severity === "error");
|
|
4727
4770
|
}
|
|
4771
|
+
function getRoadmapMode(config) {
|
|
4772
|
+
if (!config || !config.roadmap) return "file-backed";
|
|
4773
|
+
const mode = config.roadmap.mode;
|
|
4774
|
+
return mode === "file-less" ? "file-less" : "file-backed";
|
|
4775
|
+
}
|
|
4776
|
+
function validateRoadmapMode(config, projectRoot) {
|
|
4777
|
+
const root = path3.resolve(projectRoot);
|
|
4778
|
+
const mode = getRoadmapMode(config);
|
|
4779
|
+
if (mode === "file-backed") return Ok(void 0);
|
|
4780
|
+
const tracker = config.roadmap?.tracker;
|
|
4781
|
+
if (!tracker || typeof tracker !== "object") {
|
|
4782
|
+
return Err(
|
|
4783
|
+
createError(
|
|
4784
|
+
"ROADMAP_MODE_MISSING_TRACKER",
|
|
4785
|
+
'roadmap.mode is "file-less" but roadmap.tracker is not configured. File-less mode requires an external tracker as the source of truth.',
|
|
4786
|
+
{ mode },
|
|
4787
|
+
[
|
|
4788
|
+
'Configure roadmap.tracker in harness.config.json (e.g., kind: "github", repo: "owner/name").',
|
|
4789
|
+
'Or set roadmap.mode to "file-backed" if you want to keep using docs/roadmap.md.'
|
|
4790
|
+
]
|
|
4791
|
+
)
|
|
4792
|
+
);
|
|
4793
|
+
}
|
|
4794
|
+
const roadmapPath = path3.join(root, "docs", "roadmap.md");
|
|
4795
|
+
if (fs3.existsSync(roadmapPath)) {
|
|
4796
|
+
return Err(
|
|
4797
|
+
createError(
|
|
4798
|
+
"ROADMAP_MODE_FILE_PRESENT",
|
|
4799
|
+
'roadmap.mode is "file-less" but docs/roadmap.md still exists. In file-less mode the tracker is canonical; the markdown file must be migrated. Run `harness roadmap migrate --to=file-less` to migrate features into the tracker.',
|
|
4800
|
+
{ mode, roadmapPath },
|
|
4801
|
+
[
|
|
4802
|
+
"Run `harness roadmap migrate --to=file-less` to migrate features into the tracker.",
|
|
4803
|
+
'Or set roadmap.mode to "file-backed" if you want to keep docs/roadmap.md.'
|
|
4804
|
+
]
|
|
4805
|
+
)
|
|
4806
|
+
);
|
|
4807
|
+
}
|
|
4808
|
+
return Ok(void 0);
|
|
4809
|
+
}
|
|
4728
4810
|
function determineImportance(filePath) {
|
|
4729
4811
|
const name = basename2(filePath).toLowerCase();
|
|
4730
4812
|
if (name === "index.ts" || name === "index.js" || name === "main.ts") {
|
|
@@ -4821,8 +4903,8 @@ async function checkDocCoverage(domain, options = {}) {
|
|
|
4821
4903
|
);
|
|
4822
4904
|
}
|
|
4823
4905
|
}
|
|
4824
|
-
function suggestFix(
|
|
4825
|
-
const targetName = basename3(
|
|
4906
|
+
function suggestFix(path46, existingFiles) {
|
|
4907
|
+
const targetName = basename3(path46).toLowerCase();
|
|
4826
4908
|
const similar = existingFiles.find((file) => {
|
|
4827
4909
|
const fileName = basename3(file).toLowerCase();
|
|
4828
4910
|
return fileName.includes(targetName) || targetName.includes(fileName);
|
|
@@ -4830,10 +4912,10 @@ function suggestFix(path42, existingFiles) {
|
|
|
4830
4912
|
if (similar) {
|
|
4831
4913
|
return `Did you mean "${similar}"?`;
|
|
4832
4914
|
}
|
|
4833
|
-
return `Create the file "${
|
|
4915
|
+
return `Create the file "${path46}" or remove the link`;
|
|
4834
4916
|
}
|
|
4835
4917
|
async function validateKnowledgeMap(rootDir = process.cwd()) {
|
|
4836
|
-
const agentsPath =
|
|
4918
|
+
const agentsPath = join14(rootDir, "AGENTS.md");
|
|
4837
4919
|
const agentsResult = await validateAgentsMap(agentsPath);
|
|
4838
4920
|
if (!agentsResult.ok) {
|
|
4839
4921
|
return Err(agentsResult.error);
|
|
@@ -5188,8 +5270,8 @@ function createBoundaryValidator(schema, name) {
|
|
|
5188
5270
|
return Ok(result.data);
|
|
5189
5271
|
}
|
|
5190
5272
|
const suggestions = result.error.issues.map((issue) => {
|
|
5191
|
-
const
|
|
5192
|
-
return
|
|
5273
|
+
const path46 = issue.path.join(".");
|
|
5274
|
+
return path46 ? `${path46}: ${issue.message}` : issue.message;
|
|
5193
5275
|
});
|
|
5194
5276
|
return Err(
|
|
5195
5277
|
createError(
|
|
@@ -5235,7 +5317,7 @@ function validateBoundaries(boundaries, data) {
|
|
|
5235
5317
|
async function writeConfig(filePath, content) {
|
|
5236
5318
|
try {
|
|
5237
5319
|
const json = JSON.stringify(content, null, 2) + "\n";
|
|
5238
|
-
await
|
|
5320
|
+
await fs4.writeFile(filePath, json, "utf-8");
|
|
5239
5321
|
return Ok(void 0);
|
|
5240
5322
|
} catch (error) {
|
|
5241
5323
|
return Err(error instanceof Error ? error : new Error(String(error)));
|
|
@@ -5509,7 +5591,7 @@ function deepMergeConstraints(localConfig, bundleConstraints, _existingContribut
|
|
|
5509
5591
|
async function readLockfile(lockfilePath) {
|
|
5510
5592
|
let raw;
|
|
5511
5593
|
try {
|
|
5512
|
-
raw = await
|
|
5594
|
+
raw = await fs5.readFile(lockfilePath, "utf-8");
|
|
5513
5595
|
} catch (err) {
|
|
5514
5596
|
if (isNodeError(err) && err.code === "ENOENT") {
|
|
5515
5597
|
return { ok: true, value: null };
|
|
@@ -5791,9 +5873,9 @@ function parseProtectedRegions(files) {
|
|
|
5791
5873
|
};
|
|
5792
5874
|
}
|
|
5793
5875
|
function collectFieldEntries(rootDir, field) {
|
|
5794
|
-
if (typeof field === "string") return [
|
|
5876
|
+
if (typeof field === "string") return [resolve42(rootDir, field)];
|
|
5795
5877
|
if (typeof field === "object" && field !== null) {
|
|
5796
|
-
return Object.values(field).filter((v) => typeof v === "string").map((v) =>
|
|
5878
|
+
return Object.values(field).filter((v) => typeof v === "string").map((v) => resolve42(rootDir, v));
|
|
5797
5879
|
}
|
|
5798
5880
|
return [];
|
|
5799
5881
|
}
|
|
@@ -5801,46 +5883,234 @@ function extractPackageEntries(rootDir, pkg) {
|
|
|
5801
5883
|
const entries = [];
|
|
5802
5884
|
entries.push(...collectFieldEntries(rootDir, pkg["exports"]));
|
|
5803
5885
|
if (entries.length === 0 && typeof pkg["main"] === "string") {
|
|
5804
|
-
entries.push(
|
|
5886
|
+
entries.push(resolve42(rootDir, pkg["main"]));
|
|
5805
5887
|
}
|
|
5806
|
-
if (pkg["bin"])
|
|
5807
|
-
|
|
5888
|
+
if (pkg["bin"]) entries.push(...collectFieldEntries(rootDir, pkg["bin"]));
|
|
5889
|
+
return entries;
|
|
5890
|
+
}
|
|
5891
|
+
async function readPackageJsonEntries(rootDir, pkgPath) {
|
|
5892
|
+
const content = await readFileContent(pkgPath);
|
|
5893
|
+
if (!content.ok) return [];
|
|
5894
|
+
try {
|
|
5895
|
+
const pkg = JSON.parse(content.value);
|
|
5896
|
+
return extractPackageEntries(rootDir, pkg);
|
|
5897
|
+
} catch {
|
|
5898
|
+
return [];
|
|
5899
|
+
}
|
|
5900
|
+
}
|
|
5901
|
+
async function resolveTypeScript(rootDir) {
|
|
5902
|
+
const pkgPath = join15(rootDir, "package.json");
|
|
5903
|
+
const detected = await fileExists(pkgPath);
|
|
5904
|
+
if (detected) {
|
|
5905
|
+
const entries = await readPackageJsonEntries(rootDir, pkgPath);
|
|
5906
|
+
if (entries.length > 0) {
|
|
5907
|
+
return { language: "typescript", detected: true, entries, hints: TS_HINTS };
|
|
5908
|
+
}
|
|
5909
|
+
}
|
|
5910
|
+
for (const conv of TS_CONVENTIONS) {
|
|
5911
|
+
const p = join15(rootDir, conv);
|
|
5912
|
+
if (await fileExists(p)) {
|
|
5913
|
+
return { language: "typescript", detected: true, entries: [p], hints: TS_HINTS };
|
|
5914
|
+
}
|
|
5915
|
+
}
|
|
5916
|
+
return { language: "typescript", detected, entries: [], hints: TS_HINTS };
|
|
5917
|
+
}
|
|
5918
|
+
async function detectPython(rootDir) {
|
|
5919
|
+
return await fileExists(join15(rootDir, "pyproject.toml")) || await fileExists(join15(rootDir, "setup.py")) || await fileExists(join15(rootDir, "requirements.txt"));
|
|
5920
|
+
}
|
|
5921
|
+
async function readPyProject(rootDir) {
|
|
5922
|
+
const pyproject = join15(rootDir, "pyproject.toml");
|
|
5923
|
+
if (!await fileExists(pyproject)) return { scriptTargets: [] };
|
|
5924
|
+
const content = await readFileContent(pyproject);
|
|
5925
|
+
if (!content.ok) return { scriptTargets: [] };
|
|
5926
|
+
return parsePyProject(content.value);
|
|
5927
|
+
}
|
|
5928
|
+
async function resolveScriptTargetEntry(rootDir, target) {
|
|
5929
|
+
const mod = target.split(":")[0];
|
|
5930
|
+
if (!mod) return void 0;
|
|
5931
|
+
const relPath2 = mod.replaceAll(".", "/") + ".py";
|
|
5932
|
+
for (const candidate of [join15(rootDir, relPath2), join15(rootDir, "src", relPath2)]) {
|
|
5933
|
+
if (await fileExists(candidate)) return candidate;
|
|
5934
|
+
}
|
|
5935
|
+
return void 0;
|
|
5936
|
+
}
|
|
5937
|
+
async function resolvePythonFromScripts(rootDir, targets) {
|
|
5938
|
+
const entries = [];
|
|
5939
|
+
for (const target of targets) {
|
|
5940
|
+
const entry = await resolveScriptTargetEntry(rootDir, target);
|
|
5941
|
+
if (entry) entries.push(entry);
|
|
5808
5942
|
}
|
|
5809
5943
|
return entries;
|
|
5810
5944
|
}
|
|
5811
|
-
async function
|
|
5812
|
-
if (
|
|
5813
|
-
|
|
5945
|
+
async function resolvePythonFromProjectName(rootDir, projectName) {
|
|
5946
|
+
if (!projectName) return [];
|
|
5947
|
+
const normalized = projectName.replaceAll("-", "_");
|
|
5948
|
+
const candidates = [
|
|
5949
|
+
join15(rootDir, normalized, "__init__.py"),
|
|
5950
|
+
join15(rootDir, normalized, "__main__.py"),
|
|
5951
|
+
join15(rootDir, "src", normalized, "__init__.py"),
|
|
5952
|
+
join15(rootDir, "src", normalized, "__main__.py")
|
|
5953
|
+
];
|
|
5954
|
+
const entries = [];
|
|
5955
|
+
for (const c of candidates) {
|
|
5956
|
+
if (await fileExists(c)) entries.push(c);
|
|
5814
5957
|
}
|
|
5815
|
-
|
|
5816
|
-
|
|
5817
|
-
|
|
5818
|
-
|
|
5819
|
-
|
|
5820
|
-
|
|
5821
|
-
|
|
5822
|
-
|
|
5823
|
-
|
|
5824
|
-
|
|
5958
|
+
return entries;
|
|
5959
|
+
}
|
|
5960
|
+
async function resolvePythonConventions(rootDir) {
|
|
5961
|
+
const entries = [];
|
|
5962
|
+
for (const conv of PYTHON_CONVENTIONS) {
|
|
5963
|
+
const p = join15(rootDir, conv);
|
|
5964
|
+
if (await fileExists(p)) entries.push(p);
|
|
5965
|
+
}
|
|
5966
|
+
return entries;
|
|
5967
|
+
}
|
|
5968
|
+
async function findPythonTopLevelPackages(rootDir) {
|
|
5969
|
+
const found = await findFiles("*/__init__.py", rootDir);
|
|
5970
|
+
if (found.length > 0) return found;
|
|
5971
|
+
return findFiles("src/*/__init__.py", rootDir);
|
|
5972
|
+
}
|
|
5973
|
+
async function resolvePython(rootDir) {
|
|
5974
|
+
if (!await detectPython(rootDir)) {
|
|
5975
|
+
return { language: "python", detected: false, entries: [], hints: PYTHON_HINTS };
|
|
5976
|
+
}
|
|
5977
|
+
const info = await readPyProject(rootDir);
|
|
5978
|
+
const strategies = [
|
|
5979
|
+
() => resolvePythonFromScripts(rootDir, info.scriptTargets),
|
|
5980
|
+
() => resolvePythonFromProjectName(rootDir, info.projectName),
|
|
5981
|
+
() => resolvePythonConventions(rootDir),
|
|
5982
|
+
() => findPythonTopLevelPackages(rootDir)
|
|
5983
|
+
];
|
|
5984
|
+
for (const strategy of strategies) {
|
|
5985
|
+
const entries = await strategy();
|
|
5986
|
+
if (entries.length > 0) {
|
|
5987
|
+
return { language: "python", detected: true, entries, hints: PYTHON_HINTS };
|
|
5988
|
+
}
|
|
5989
|
+
}
|
|
5990
|
+
return { language: "python", detected: true, entries: [], hints: PYTHON_HINTS };
|
|
5991
|
+
}
|
|
5992
|
+
async function resolveGo(rootDir) {
|
|
5993
|
+
const hints = ["Create main.go at the project root, or use the cmd/<name>/main.go layout"];
|
|
5994
|
+
const detected = await fileExists(join15(rootDir, "go.mod"));
|
|
5995
|
+
if (!detected) return { language: "go", detected: false, entries: [], hints };
|
|
5996
|
+
const entries = [];
|
|
5997
|
+
const mainGo = join15(rootDir, "main.go");
|
|
5998
|
+
if (await fileExists(mainGo)) entries.push(mainGo);
|
|
5999
|
+
entries.push(...await findFiles("cmd/*/main.go", rootDir));
|
|
6000
|
+
return { language: "go", detected: true, entries, hints };
|
|
6001
|
+
}
|
|
6002
|
+
async function resolveRust(rootDir) {
|
|
6003
|
+
const hints = [
|
|
6004
|
+
"Create src/main.rs or src/lib.rs",
|
|
6005
|
+
"Declare [[bin]] entries with a `path` in Cargo.toml"
|
|
6006
|
+
];
|
|
6007
|
+
const cargoPath = join15(rootDir, "Cargo.toml");
|
|
6008
|
+
const detected = await fileExists(cargoPath);
|
|
6009
|
+
if (!detected) return { language: "rust", detected: false, entries: [], hints };
|
|
6010
|
+
const entries = [];
|
|
6011
|
+
const content = await readFileContent(cargoPath);
|
|
6012
|
+
if (content.ok) {
|
|
6013
|
+
for (const bp of parseCargoBinPaths(content.value)) {
|
|
6014
|
+
const abs = resolve42(rootDir, bp);
|
|
6015
|
+
if (await fileExists(abs)) entries.push(abs);
|
|
6016
|
+
}
|
|
6017
|
+
}
|
|
6018
|
+
if (entries.length === 0) {
|
|
6019
|
+
for (const conv of ["src/main.rs", "src/lib.rs"]) {
|
|
6020
|
+
const p = join15(rootDir, conv);
|
|
6021
|
+
if (await fileExists(p)) entries.push(p);
|
|
6022
|
+
}
|
|
6023
|
+
entries.push(...await findFiles("src/bin/*.rs", rootDir));
|
|
6024
|
+
}
|
|
6025
|
+
return { language: "rust", detected: true, entries, hints };
|
|
6026
|
+
}
|
|
6027
|
+
async function resolveJava(rootDir) {
|
|
6028
|
+
const hints = [
|
|
6029
|
+
"Place an entry class at src/main/java/**/Main.java (or *Application.java for Spring Boot)"
|
|
6030
|
+
];
|
|
6031
|
+
const detected = await fileExists(join15(rootDir, "pom.xml")) || await fileExists(join15(rootDir, "build.gradle")) || await fileExists(join15(rootDir, "build.gradle.kts"));
|
|
6032
|
+
if (!detected) return { language: "java", detected: false, entries: [], hints };
|
|
6033
|
+
const entries = [];
|
|
6034
|
+
entries.push(...await findFiles("src/main/java/**/Main.java", rootDir));
|
|
6035
|
+
entries.push(...await findFiles("src/main/java/**/*Application.java", rootDir));
|
|
6036
|
+
return { language: "java", detected: true, entries, hints };
|
|
6037
|
+
}
|
|
6038
|
+
function parseTomlLine(raw) {
|
|
6039
|
+
const line = raw.replace(/(^|\s)#.*$/, "").trim();
|
|
6040
|
+
if (!line) return {};
|
|
6041
|
+
const sectionMatch = /^\[([^\]]+)\]$/.exec(line);
|
|
6042
|
+
if (sectionMatch) return { section: sectionMatch[1] ?? "" };
|
|
6043
|
+
const eq = line.indexOf("=");
|
|
6044
|
+
if (eq <= 0) return {};
|
|
6045
|
+
return {
|
|
6046
|
+
key: line.slice(0, eq).trim(),
|
|
6047
|
+
value: stripTomlString2(line.slice(eq + 1).trim())
|
|
6048
|
+
};
|
|
6049
|
+
}
|
|
6050
|
+
function parsePyProject(content) {
|
|
6051
|
+
const result = { scriptTargets: [] };
|
|
6052
|
+
let section = null;
|
|
6053
|
+
for (const raw of content.split(/\r?\n/)) {
|
|
6054
|
+
const parsed = parseTomlLine(raw);
|
|
6055
|
+
if (parsed.section !== void 0) {
|
|
6056
|
+
section = parsed.section;
|
|
6057
|
+
continue;
|
|
5825
6058
|
}
|
|
6059
|
+
if (parsed.key === void 0 || parsed.value === void 0) continue;
|
|
6060
|
+
if (section === "project" && parsed.key === "name") result.projectName = parsed.value;
|
|
6061
|
+
else if (section === "project.scripts") result.scriptTargets.push(parsed.value);
|
|
5826
6062
|
}
|
|
5827
|
-
|
|
5828
|
-
|
|
5829
|
-
|
|
5830
|
-
|
|
5831
|
-
|
|
6063
|
+
return result;
|
|
6064
|
+
}
|
|
6065
|
+
function parseCargoBinPaths(content) {
|
|
6066
|
+
const paths = [];
|
|
6067
|
+
let inBin = false;
|
|
6068
|
+
for (const raw of content.split(/\r?\n/)) {
|
|
6069
|
+
const line = raw.replace(/(^|\s)#.*$/, "").trim();
|
|
6070
|
+
if (!line) continue;
|
|
6071
|
+
if (line === "[[bin]]") {
|
|
6072
|
+
inBin = true;
|
|
6073
|
+
continue;
|
|
6074
|
+
}
|
|
6075
|
+
if (line.startsWith("[")) {
|
|
6076
|
+
inBin = false;
|
|
6077
|
+
continue;
|
|
5832
6078
|
}
|
|
6079
|
+
if (!inBin) continue;
|
|
6080
|
+
const eq = line.indexOf("=");
|
|
6081
|
+
if (eq <= 0) continue;
|
|
6082
|
+
const key = line.slice(0, eq).trim();
|
|
6083
|
+
if (key === "path") paths.push(stripTomlString2(line.slice(eq + 1).trim()));
|
|
5833
6084
|
}
|
|
6085
|
+
return paths;
|
|
6086
|
+
}
|
|
6087
|
+
function stripTomlString2(value) {
|
|
6088
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
6089
|
+
return value.slice(1, -1);
|
|
6090
|
+
}
|
|
6091
|
+
return value;
|
|
6092
|
+
}
|
|
6093
|
+
async function resolveEntryPoints(rootDir, explicitEntries) {
|
|
6094
|
+
if (explicitEntries && explicitEntries.length > 0) {
|
|
6095
|
+
return Ok(explicitEntries.map((e) => resolve42(rootDir, e)));
|
|
6096
|
+
}
|
|
6097
|
+
const resolvers = [resolveTypeScript, resolvePython, resolveGo, resolveRust, resolveJava];
|
|
6098
|
+
const resolutions = [];
|
|
6099
|
+
for (const resolver of resolvers) {
|
|
6100
|
+
const res = await resolver(rootDir);
|
|
6101
|
+
resolutions.push(res);
|
|
6102
|
+
if (res.entries.length > 0) return Ok(res.entries);
|
|
6103
|
+
}
|
|
6104
|
+
const detectedLangs = resolutions.filter((r) => r.detected);
|
|
6105
|
+
const suggestions = detectedLangs.length > 0 ? detectedLangs.flatMap((r) => r.hints) : resolutions.flatMap((r) => r.hints);
|
|
6106
|
+
suggestions.push("Specify entryPoints in config");
|
|
6107
|
+
const reason = detectedLangs.length > 0 ? `Detected ${detectedLangs.map((r) => r.language).join(", ")} project but found no entry points` : "No language manifest (package.json, pyproject.toml, go.mod, Cargo.toml, pom.xml) and no conventional entry files found";
|
|
5834
6108
|
return Err(
|
|
5835
6109
|
createEntropyError(
|
|
5836
6110
|
"ENTRY_POINT_NOT_FOUND",
|
|
5837
6111
|
"Could not resolve entry points",
|
|
5838
|
-
{ reason
|
|
5839
|
-
|
|
5840
|
-
'Add "exports" or "main" to package.json',
|
|
5841
|
-
"Create src/index.ts",
|
|
5842
|
-
"Specify entryPoints in config"
|
|
5843
|
-
]
|
|
6112
|
+
{ reason },
|
|
6113
|
+
suggestions
|
|
5844
6114
|
)
|
|
5845
6115
|
);
|
|
5846
6116
|
}
|
|
@@ -5893,22 +6163,22 @@ function extractInlineRefs(content) {
|
|
|
5893
6163
|
}
|
|
5894
6164
|
return refs;
|
|
5895
6165
|
}
|
|
5896
|
-
async function parseDocumentationFile(
|
|
5897
|
-
const contentResult = await readFileContent(
|
|
6166
|
+
async function parseDocumentationFile(path46) {
|
|
6167
|
+
const contentResult = await readFileContent(path46);
|
|
5898
6168
|
if (!contentResult.ok) {
|
|
5899
6169
|
return Err(
|
|
5900
6170
|
createEntropyError(
|
|
5901
6171
|
"PARSE_ERROR",
|
|
5902
|
-
`Failed to read documentation file: ${
|
|
5903
|
-
{ file:
|
|
6172
|
+
`Failed to read documentation file: ${path46}`,
|
|
6173
|
+
{ file: path46 },
|
|
5904
6174
|
["Check that the file exists"]
|
|
5905
6175
|
)
|
|
5906
6176
|
);
|
|
5907
6177
|
}
|
|
5908
6178
|
const content = contentResult.value;
|
|
5909
|
-
const type =
|
|
6179
|
+
const type = path46.endsWith(".md") ? "markdown" : "text";
|
|
5910
6180
|
return Ok({
|
|
5911
|
-
path:
|
|
6181
|
+
path: path46,
|
|
5912
6182
|
type,
|
|
5913
6183
|
content,
|
|
5914
6184
|
codeBlocks: extractCodeBlocks(content),
|
|
@@ -5937,6 +6207,7 @@ function extractSymbolsFromNode(node) {
|
|
|
5937
6207
|
return [];
|
|
5938
6208
|
}
|
|
5939
6209
|
function extractInternalSymbols(ast) {
|
|
6210
|
+
if (ast.language !== "typescript" && ast.language !== "javascript") return [];
|
|
5940
6211
|
const body = ast.body;
|
|
5941
6212
|
if (!body?.body) return [];
|
|
5942
6213
|
const nodes = body.body;
|
|
@@ -5947,6 +6218,7 @@ function toJSDocComment(comment) {
|
|
|
5947
6218
|
return { content: comment.value, line: comment.loc?.start?.line || 0 };
|
|
5948
6219
|
}
|
|
5949
6220
|
function extractJSDocComments(ast) {
|
|
6221
|
+
if (ast.language !== "typescript" && ast.language !== "javascript") return [];
|
|
5950
6222
|
const body = ast.body;
|
|
5951
6223
|
if (!body?.comments) return [];
|
|
5952
6224
|
return body.comments.flatMap((c) => {
|
|
@@ -6004,19 +6276,16 @@ function extractAllCodeReferences(docs) {
|
|
|
6004
6276
|
}
|
|
6005
6277
|
async function buildSnapshot2(config) {
|
|
6006
6278
|
const startTime = Date.now();
|
|
6007
|
-
const
|
|
6008
|
-
const rootDir = resolve32(config.rootDir);
|
|
6279
|
+
const rootDir = resolve5(config.rootDir);
|
|
6009
6280
|
const entryPointsResult = await resolveEntryPoints(rootDir, config.entryPoints);
|
|
6010
6281
|
if (!entryPointsResult.ok) {
|
|
6011
6282
|
return Err(entryPointsResult.error);
|
|
6012
6283
|
}
|
|
6013
|
-
const
|
|
6014
|
-
const
|
|
6015
|
-
|
|
6016
|
-
|
|
6017
|
-
|
|
6018
|
-
"**/*.spec.ts"
|
|
6019
|
-
];
|
|
6284
|
+
const registry = getDefaultRegistry();
|
|
6285
|
+
const singleParser = config.parser;
|
|
6286
|
+
const parserForFile = (filePath) => singleParser ?? registry.getForFile(filePath);
|
|
6287
|
+
const includePatterns = config.include || DEFAULT_INCLUDE_PATTERNS;
|
|
6288
|
+
const excludePatterns = config.exclude || [...skipDirGlobs(), "**/*.test.ts", "**/*.spec.ts"];
|
|
6020
6289
|
let sourceFilePaths = [];
|
|
6021
6290
|
for (const pattern of includePatterns) {
|
|
6022
6291
|
const files2 = await findFiles(pattern, rootDir);
|
|
@@ -6028,10 +6297,12 @@ async function buildSnapshot2(config) {
|
|
|
6028
6297
|
});
|
|
6029
6298
|
const files = [];
|
|
6030
6299
|
for (const filePath of sourceFilePaths) {
|
|
6031
|
-
const
|
|
6300
|
+
const fileParser = parserForFile(filePath);
|
|
6301
|
+
if (!fileParser) continue;
|
|
6302
|
+
const parseResult = await fileParser.parseFile(filePath);
|
|
6032
6303
|
if (!parseResult.ok) continue;
|
|
6033
|
-
const importsResult =
|
|
6034
|
-
const exportsResult =
|
|
6304
|
+
const importsResult = fileParser.extractImports(parseResult.value);
|
|
6305
|
+
const exportsResult = fileParser.extractExports(parseResult.value);
|
|
6035
6306
|
const internalSymbols = extractInternalSymbols(parseResult.value);
|
|
6036
6307
|
const jsDocComments = extractJSDocComments(parseResult.value);
|
|
6037
6308
|
files.push({
|
|
@@ -6043,7 +6314,7 @@ async function buildSnapshot2(config) {
|
|
|
6043
6314
|
jsDocComments
|
|
6044
6315
|
});
|
|
6045
6316
|
}
|
|
6046
|
-
const graphResult = await buildDependencyGraph(sourceFilePaths,
|
|
6317
|
+
const graphResult = await buildDependencyGraph(sourceFilePaths, singleParser ?? registry);
|
|
6047
6318
|
const dependencyGraph = graphResult.ok ? graphResult.value : { nodes: [], edges: [] };
|
|
6048
6319
|
const docPatterns = config.docPaths || ["docs/**/*.md", "README.md", "**/README.md"];
|
|
6049
6320
|
let docFilePaths = [];
|
|
@@ -6178,7 +6449,7 @@ async function checkStructureDrift(snapshot, _config) {
|
|
|
6178
6449
|
for (const doc of snapshot.docs) {
|
|
6179
6450
|
const fileLinks = extractFileLinks(doc.content);
|
|
6180
6451
|
for (const { link, line } of fileLinks) {
|
|
6181
|
-
const resolvedPath =
|
|
6452
|
+
const resolvedPath = resolve6(dirname5(doc.path), link);
|
|
6182
6453
|
const exists = await fileExists(resolvedPath);
|
|
6183
6454
|
if (!exists) {
|
|
6184
6455
|
drifts.push({
|
|
@@ -6280,7 +6551,7 @@ function resolveImportToFile(importSource, fromFile, snapshot, fileIndex) {
|
|
|
6280
6551
|
}
|
|
6281
6552
|
const hasFile = fileIndex ? (p) => fileIndex.has(p) : (p) => snapshot.files.some((f) => f.path === p);
|
|
6282
6553
|
const fromDir = dirname6(fromFile);
|
|
6283
|
-
const resolved =
|
|
6554
|
+
const resolved = resolve7(fromDir, importSource);
|
|
6284
6555
|
const sourceExt = extname2(resolved);
|
|
6285
6556
|
const fallbacks = JS_EXT_FALLBACKS2[sourceExt];
|
|
6286
6557
|
if (fallbacks) {
|
|
@@ -6290,7 +6561,7 @@ function resolveImportToFile(importSource, fromFile, snapshot, fileIndex) {
|
|
|
6290
6561
|
if (hasFile(candidate)) return candidate;
|
|
6291
6562
|
}
|
|
6292
6563
|
for (const indexExt of [".ts", ".tsx", ".jsx"]) {
|
|
6293
|
-
const indexPath2 =
|
|
6564
|
+
const indexPath2 = resolve7(base, "index" + indexExt);
|
|
6294
6565
|
if (hasFile(indexPath2)) return indexPath2;
|
|
6295
6566
|
}
|
|
6296
6567
|
}
|
|
@@ -6301,7 +6572,7 @@ function resolveImportToFile(importSource, fromFile, snapshot, fileIndex) {
|
|
|
6301
6572
|
if (hasFile(candidate)) return candidate;
|
|
6302
6573
|
}
|
|
6303
6574
|
for (const indexExt of [".ts", ".tsx"]) {
|
|
6304
|
-
const indexPath2 =
|
|
6575
|
+
const indexPath2 = resolve7(resolved, "index" + indexExt);
|
|
6305
6576
|
if (hasFile(indexPath2)) return indexPath2;
|
|
6306
6577
|
}
|
|
6307
6578
|
}
|
|
@@ -6817,8 +7088,8 @@ function dirSize(dirPath) {
|
|
|
6817
7088
|
return 0;
|
|
6818
7089
|
}
|
|
6819
7090
|
for (const entry of entries) {
|
|
6820
|
-
if (entry
|
|
6821
|
-
const fullPath =
|
|
7091
|
+
if (DEFAULT_SKIP_DIRS.has(entry)) continue;
|
|
7092
|
+
const fullPath = join16(dirPath, entry);
|
|
6822
7093
|
try {
|
|
6823
7094
|
const stat2 = statSync2(fullPath);
|
|
6824
7095
|
if (stat2.isDirectory()) {
|
|
@@ -6838,7 +7109,7 @@ async function detectSizeBudgetViolations(rootDir, config) {
|
|
|
6838
7109
|
let packagesChecked = 0;
|
|
6839
7110
|
for (const [pkgPath, budget] of Object.entries(budgets)) {
|
|
6840
7111
|
packagesChecked++;
|
|
6841
|
-
const distPath =
|
|
7112
|
+
const distPath = join16(rootDir, pkgPath, "dist");
|
|
6842
7113
|
const currentSize = dirSize(distPath);
|
|
6843
7114
|
if (budget.warn) {
|
|
6844
7115
|
const budgetBytes = parseSize(budget.warn);
|
|
@@ -7082,7 +7353,7 @@ function previewFix(fix) {
|
|
|
7082
7353
|
}
|
|
7083
7354
|
}
|
|
7084
7355
|
async function createBackup(filePath, backupDir) {
|
|
7085
|
-
const backupPath =
|
|
7356
|
+
const backupPath = join17(backupDir, `${Date.now()}-${basename5(filePath)}`);
|
|
7086
7357
|
try {
|
|
7087
7358
|
await mkdir22(dirname7(backupPath), { recursive: true });
|
|
7088
7359
|
await copyFile2(filePath, backupPath);
|
|
@@ -7457,12 +7728,12 @@ function parseDiffHeader(part) {
|
|
|
7457
7728
|
return headerMatch[2];
|
|
7458
7729
|
}
|
|
7459
7730
|
function parseDiffPart(part) {
|
|
7460
|
-
const
|
|
7461
|
-
if (!
|
|
7731
|
+
const path46 = parseDiffHeader(part);
|
|
7732
|
+
if (!path46) return null;
|
|
7462
7733
|
const additionRegex = /^\+(?!\+\+)/gm;
|
|
7463
7734
|
const deletionRegex = /^-(?!--)/gm;
|
|
7464
7735
|
return {
|
|
7465
|
-
path:
|
|
7736
|
+
path: path46,
|
|
7466
7737
|
status: detectFileStatus(part),
|
|
7467
7738
|
additions: (part.match(additionRegex) || []).length,
|
|
7468
7739
|
deletions: (part.match(deletionRegex) || []).length
|
|
@@ -8247,10 +8518,10 @@ function detectEmergentConstraints(history, options) {
|
|
|
8247
8518
|
return { suggestions, totalViolationsAnalyzed, windowWeeks, minOccurrences };
|
|
8248
8519
|
}
|
|
8249
8520
|
function streamsDir(projectPath) {
|
|
8250
|
-
return
|
|
8521
|
+
return path8.join(projectPath, HARNESS_DIR, STREAMS_DIR);
|
|
8251
8522
|
}
|
|
8252
8523
|
function indexPath(projectPath) {
|
|
8253
|
-
return
|
|
8524
|
+
return path8.join(streamsDir(projectPath), INDEX_FILE);
|
|
8254
8525
|
}
|
|
8255
8526
|
function validateStreamName(name) {
|
|
8256
8527
|
if (!STREAM_NAME_REGEX.test(name)) {
|
|
@@ -8264,11 +8535,11 @@ function validateStreamName(name) {
|
|
|
8264
8535
|
}
|
|
8265
8536
|
async function loadStreamIndex(projectPath) {
|
|
8266
8537
|
const idxPath = indexPath(projectPath);
|
|
8267
|
-
if (!
|
|
8538
|
+
if (!fs11.existsSync(idxPath)) {
|
|
8268
8539
|
return Ok({ ...DEFAULT_STREAM_INDEX, streams: {} });
|
|
8269
8540
|
}
|
|
8270
8541
|
try {
|
|
8271
|
-
const raw =
|
|
8542
|
+
const raw = fs11.readFileSync(idxPath, "utf-8");
|
|
8272
8543
|
const parsed = JSON.parse(raw);
|
|
8273
8544
|
const result = StreamIndexSchema.safeParse(parsed);
|
|
8274
8545
|
if (!result.success) {
|
|
@@ -8286,8 +8557,8 @@ async function loadStreamIndex(projectPath) {
|
|
|
8286
8557
|
async function saveStreamIndex(projectPath, index) {
|
|
8287
8558
|
const dir = streamsDir(projectPath);
|
|
8288
8559
|
try {
|
|
8289
|
-
|
|
8290
|
-
|
|
8560
|
+
fs11.mkdirSync(dir, { recursive: true });
|
|
8561
|
+
fs11.writeFileSync(indexPath(projectPath), JSON.stringify(index, null, 2));
|
|
8291
8562
|
return Ok(void 0);
|
|
8292
8563
|
} catch (error) {
|
|
8293
8564
|
return Err(
|
|
@@ -8326,18 +8597,18 @@ async function resolveStreamPath(projectPath, options) {
|
|
|
8326
8597
|
)
|
|
8327
8598
|
);
|
|
8328
8599
|
}
|
|
8329
|
-
return Ok(
|
|
8600
|
+
return Ok(path8.join(streamsDir(projectPath), options.stream));
|
|
8330
8601
|
}
|
|
8331
8602
|
const branch = getCurrentBranch(projectPath);
|
|
8332
8603
|
if (branch && branch !== "main" && branch !== "master") {
|
|
8333
8604
|
for (const [name, info] of Object.entries(index.streams)) {
|
|
8334
8605
|
if (info.branch === branch) {
|
|
8335
|
-
return Ok(
|
|
8606
|
+
return Ok(path8.join(streamsDir(projectPath), name));
|
|
8336
8607
|
}
|
|
8337
8608
|
}
|
|
8338
8609
|
}
|
|
8339
8610
|
if (index.activeStream && index.streams[index.activeStream]) {
|
|
8340
|
-
return Ok(
|
|
8611
|
+
return Ok(path8.join(streamsDir(projectPath), index.activeStream));
|
|
8341
8612
|
}
|
|
8342
8613
|
return Err(
|
|
8343
8614
|
new Error(
|
|
@@ -8365,9 +8636,9 @@ async function createStream(projectPath, name, branch) {
|
|
|
8365
8636
|
if (index.streams[name]) {
|
|
8366
8637
|
return Err(new Error(`Stream '${name}' already exists`));
|
|
8367
8638
|
}
|
|
8368
|
-
const streamPath =
|
|
8639
|
+
const streamPath = path8.join(streamsDir(projectPath), name);
|
|
8369
8640
|
try {
|
|
8370
|
-
|
|
8641
|
+
fs11.mkdirSync(streamPath, { recursive: true });
|
|
8371
8642
|
} catch (error) {
|
|
8372
8643
|
return Err(
|
|
8373
8644
|
new Error(
|
|
@@ -8408,12 +8679,12 @@ async function archiveStream(projectPath, name) {
|
|
|
8408
8679
|
if (!index.streams[name]) {
|
|
8409
8680
|
return Err(new Error(`Stream '${name}' not found`));
|
|
8410
8681
|
}
|
|
8411
|
-
const streamPath =
|
|
8412
|
-
const archiveDir =
|
|
8682
|
+
const streamPath = path8.join(streamsDir(projectPath), name);
|
|
8683
|
+
const archiveDir = path8.join(projectPath, HARNESS_DIR, "archive", "streams");
|
|
8413
8684
|
try {
|
|
8414
|
-
|
|
8685
|
+
fs11.mkdirSync(archiveDir, { recursive: true });
|
|
8415
8686
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
8416
|
-
|
|
8687
|
+
fs11.renameSync(streamPath, path8.join(archiveDir, `${name}-${date}`));
|
|
8417
8688
|
} catch (error) {
|
|
8418
8689
|
return Err(
|
|
8419
8690
|
new Error(
|
|
@@ -8434,19 +8705,19 @@ function getStreamForBranch(index, branch) {
|
|
|
8434
8705
|
return null;
|
|
8435
8706
|
}
|
|
8436
8707
|
async function migrateToStreams(projectPath) {
|
|
8437
|
-
const harnessDir =
|
|
8438
|
-
if (
|
|
8708
|
+
const harnessDir = path8.join(projectPath, HARNESS_DIR);
|
|
8709
|
+
if (fs11.existsSync(indexPath(projectPath))) {
|
|
8439
8710
|
return Ok(void 0);
|
|
8440
8711
|
}
|
|
8441
|
-
const filesToMove = STATE_FILES.filter((f) =>
|
|
8712
|
+
const filesToMove = STATE_FILES.filter((f) => fs11.existsSync(path8.join(harnessDir, f)));
|
|
8442
8713
|
if (filesToMove.length === 0) {
|
|
8443
8714
|
return Ok(void 0);
|
|
8444
8715
|
}
|
|
8445
|
-
const defaultDir =
|
|
8716
|
+
const defaultDir = path8.join(streamsDir(projectPath), "default");
|
|
8446
8717
|
try {
|
|
8447
|
-
|
|
8718
|
+
fs11.mkdirSync(defaultDir, { recursive: true });
|
|
8448
8719
|
for (const file of filesToMove) {
|
|
8449
|
-
|
|
8720
|
+
fs11.renameSync(path8.join(harnessDir, file), path8.join(defaultDir, file));
|
|
8450
8721
|
}
|
|
8451
8722
|
} catch (error) {
|
|
8452
8723
|
return Err(
|
|
@@ -8476,26 +8747,26 @@ function resolveSessionDir(projectPath, sessionSlug, options) {
|
|
|
8476
8747
|
new Error(`Invalid session slug '${sessionSlug}': must not contain path traversal characters`)
|
|
8477
8748
|
);
|
|
8478
8749
|
}
|
|
8479
|
-
const sessionDir =
|
|
8750
|
+
const sessionDir = path9.join(projectPath, HARNESS_DIR, SESSIONS_DIR, sessionSlug);
|
|
8480
8751
|
if (options?.create) {
|
|
8481
|
-
|
|
8752
|
+
fs12.mkdirSync(sessionDir, { recursive: true });
|
|
8482
8753
|
}
|
|
8483
8754
|
return Ok(sessionDir);
|
|
8484
8755
|
}
|
|
8485
8756
|
function updateSessionIndex(projectPath, sessionSlug, description) {
|
|
8486
|
-
const sessionsDir =
|
|
8487
|
-
|
|
8488
|
-
const indexPath2 =
|
|
8757
|
+
const sessionsDir = path9.join(projectPath, HARNESS_DIR, SESSIONS_DIR);
|
|
8758
|
+
fs12.mkdirSync(sessionsDir, { recursive: true });
|
|
8759
|
+
const indexPath2 = path9.join(sessionsDir, SESSION_INDEX_FILE);
|
|
8489
8760
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
8490
8761
|
const newLine = `- [${sessionSlug}](${sessionSlug}/summary.md) \u2014 ${description} (${date})`;
|
|
8491
|
-
if (!
|
|
8492
|
-
|
|
8762
|
+
if (!fs12.existsSync(indexPath2)) {
|
|
8763
|
+
fs12.writeFileSync(indexPath2, `## Active Sessions
|
|
8493
8764
|
|
|
8494
8765
|
${newLine}
|
|
8495
8766
|
`);
|
|
8496
8767
|
return;
|
|
8497
8768
|
}
|
|
8498
|
-
const content =
|
|
8769
|
+
const content = fs12.readFileSync(indexPath2, "utf-8");
|
|
8499
8770
|
const lines = content.split("\n");
|
|
8500
8771
|
const slugPattern = `- [${sessionSlug}]`;
|
|
8501
8772
|
const existingIdx = lines.findIndex((l) => l.startsWith(slugPattern));
|
|
@@ -8505,7 +8776,7 @@ ${newLine}
|
|
|
8505
8776
|
const lastNonEmpty = lines.reduce((last, line, i) => line.trim() !== "" ? i : last, 0);
|
|
8506
8777
|
lines.splice(lastNonEmpty + 1, 0, newLine);
|
|
8507
8778
|
}
|
|
8508
|
-
|
|
8779
|
+
fs12.writeFileSync(indexPath2, lines.join("\n"));
|
|
8509
8780
|
}
|
|
8510
8781
|
function evictIfNeeded(map) {
|
|
8511
8782
|
if (map.size > MAX_CACHE_ENTRIES) {
|
|
@@ -8518,8 +8789,8 @@ async function getStateDir(projectPath, stream, session) {
|
|
|
8518
8789
|
const sessionResult = resolveSessionDir(projectPath, session, { create: true });
|
|
8519
8790
|
return sessionResult;
|
|
8520
8791
|
}
|
|
8521
|
-
const streamsIndexPath =
|
|
8522
|
-
const hasStreams =
|
|
8792
|
+
const streamsIndexPath = path10.join(projectPath, HARNESS_DIR, "streams", INDEX_FILE);
|
|
8793
|
+
const hasStreams = fs13.existsSync(streamsIndexPath);
|
|
8523
8794
|
if (stream || hasStreams) {
|
|
8524
8795
|
const result = await resolveStreamPath(projectPath, stream ? { stream } : void 0);
|
|
8525
8796
|
if (result.ok) {
|
|
@@ -8529,18 +8800,18 @@ async function getStateDir(projectPath, stream, session) {
|
|
|
8529
8800
|
return result;
|
|
8530
8801
|
}
|
|
8531
8802
|
}
|
|
8532
|
-
return Ok(
|
|
8803
|
+
return Ok(path10.join(projectPath, HARNESS_DIR));
|
|
8533
8804
|
}
|
|
8534
8805
|
async function loadState(projectPath, stream, session) {
|
|
8535
8806
|
try {
|
|
8536
8807
|
const dirResult = await getStateDir(projectPath, stream, session);
|
|
8537
8808
|
if (!dirResult.ok) return dirResult;
|
|
8538
8809
|
const stateDir = dirResult.value;
|
|
8539
|
-
const statePath =
|
|
8540
|
-
if (!
|
|
8810
|
+
const statePath = path11.join(stateDir, STATE_FILE);
|
|
8811
|
+
if (!fs14.existsSync(statePath)) {
|
|
8541
8812
|
return Ok({ ...DEFAULT_STATE });
|
|
8542
8813
|
}
|
|
8543
|
-
const raw =
|
|
8814
|
+
const raw = fs14.readFileSync(statePath, "utf-8");
|
|
8544
8815
|
const parsed = JSON.parse(raw);
|
|
8545
8816
|
const result = HarnessStateSchema.safeParse(parsed);
|
|
8546
8817
|
if (!result.success) {
|
|
@@ -8558,11 +8829,11 @@ async function saveState(projectPath, state, stream, session) {
|
|
|
8558
8829
|
const dirResult = await getStateDir(projectPath, stream, session);
|
|
8559
8830
|
if (!dirResult.ok) return dirResult;
|
|
8560
8831
|
const stateDir = dirResult.value;
|
|
8561
|
-
const statePath =
|
|
8562
|
-
|
|
8832
|
+
const statePath = path11.join(stateDir, STATE_FILE);
|
|
8833
|
+
fs14.mkdirSync(stateDir, { recursive: true });
|
|
8563
8834
|
const tmpPath = statePath + ".tmp";
|
|
8564
|
-
|
|
8565
|
-
|
|
8835
|
+
fs14.writeFileSync(tmpPath, JSON.stringify(state, null, 2));
|
|
8836
|
+
fs14.renameSync(tmpPath, statePath);
|
|
8566
8837
|
return Ok(void 0);
|
|
8567
8838
|
} catch (error) {
|
|
8568
8839
|
return Err(
|
|
@@ -8648,10 +8919,10 @@ function computeContentHash(text) {
|
|
|
8648
8919
|
return crypto2.createHash("sha256").update(text).digest("hex").slice(0, 16);
|
|
8649
8920
|
}
|
|
8650
8921
|
function loadContentHashes(stateDir) {
|
|
8651
|
-
const hashesPath =
|
|
8652
|
-
if (!
|
|
8922
|
+
const hashesPath = path12.join(stateDir, CONTENT_HASHES_FILE);
|
|
8923
|
+
if (!fs15.existsSync(hashesPath)) return {};
|
|
8653
8924
|
try {
|
|
8654
|
-
const raw =
|
|
8925
|
+
const raw = fs15.readFileSync(hashesPath, "utf-8");
|
|
8655
8926
|
const parsed = JSON.parse(raw);
|
|
8656
8927
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return {};
|
|
8657
8928
|
return parsed;
|
|
@@ -8660,15 +8931,15 @@ function loadContentHashes(stateDir) {
|
|
|
8660
8931
|
}
|
|
8661
8932
|
}
|
|
8662
8933
|
function saveContentHashes(stateDir, index) {
|
|
8663
|
-
const hashesPath =
|
|
8934
|
+
const hashesPath = path12.join(stateDir, CONTENT_HASHES_FILE);
|
|
8664
8935
|
const tmpPath = hashesPath + ".tmp";
|
|
8665
|
-
|
|
8666
|
-
|
|
8936
|
+
fs15.writeFileSync(tmpPath, JSON.stringify(index, null, 2) + "\n");
|
|
8937
|
+
fs15.renameSync(tmpPath, hashesPath);
|
|
8667
8938
|
}
|
|
8668
8939
|
function rebuildContentHashes(stateDir, learningsFile) {
|
|
8669
|
-
const learningsPath =
|
|
8670
|
-
if (!
|
|
8671
|
-
const content =
|
|
8940
|
+
const learningsPath = path12.join(stateDir, learningsFile);
|
|
8941
|
+
if (!fs15.existsSync(learningsPath)) return {};
|
|
8942
|
+
const content = fs15.readFileSync(learningsPath, "utf-8");
|
|
8672
8943
|
const lines = content.split("\n");
|
|
8673
8944
|
const index = {};
|
|
8674
8945
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -8727,18 +8998,18 @@ async function loadRelevantLearnings(projectPath, skillName, stream, session) {
|
|
|
8727
8998
|
const dirResult = await getStateDir(projectPath, stream, session);
|
|
8728
8999
|
if (!dirResult.ok) return dirResult;
|
|
8729
9000
|
const stateDir = dirResult.value;
|
|
8730
|
-
const learningsPath =
|
|
8731
|
-
if (!
|
|
9001
|
+
const learningsPath = path13.join(stateDir, LEARNINGS_FILE);
|
|
9002
|
+
if (!fs16.existsSync(learningsPath)) {
|
|
8732
9003
|
return Ok([]);
|
|
8733
9004
|
}
|
|
8734
|
-
const stats =
|
|
9005
|
+
const stats = fs16.statSync(learningsPath);
|
|
8735
9006
|
const cacheKey = learningsPath;
|
|
8736
9007
|
const cached = learningsCacheMap.get(cacheKey);
|
|
8737
9008
|
let entries;
|
|
8738
9009
|
if (cached && cached.mtimeMs === stats.mtimeMs) {
|
|
8739
9010
|
entries = cached.entries;
|
|
8740
9011
|
} else {
|
|
8741
|
-
const content =
|
|
9012
|
+
const content = fs16.readFileSync(learningsPath, "utf-8");
|
|
8742
9013
|
const lines = content.split("\n");
|
|
8743
9014
|
entries = [];
|
|
8744
9015
|
let currentBlock = [];
|
|
@@ -8874,16 +9145,16 @@ function checkOverlap(newEntry, existingEntries, options) {
|
|
|
8874
9145
|
};
|
|
8875
9146
|
}
|
|
8876
9147
|
function sleep(ms) {
|
|
8877
|
-
return new Promise((
|
|
9148
|
+
return new Promise((resolve10) => setTimeout(resolve10, ms));
|
|
8878
9149
|
}
|
|
8879
9150
|
async function acquireFileLock(lockPath) {
|
|
8880
9151
|
for (let attempt = 0; attempt < LOCK_RETRIES; attempt++) {
|
|
8881
9152
|
try {
|
|
8882
|
-
const fd =
|
|
9153
|
+
const fd = fs17.openSync(
|
|
8883
9154
|
lockPath,
|
|
8884
|
-
|
|
9155
|
+
fs17.constants.O_CREAT | fs17.constants.O_EXCL | fs17.constants.O_WRONLY
|
|
8885
9156
|
);
|
|
8886
|
-
|
|
9157
|
+
fs17.closeSync(fd);
|
|
8887
9158
|
return;
|
|
8888
9159
|
} catch (err) {
|
|
8889
9160
|
const isExist = err instanceof Error && "code" in err && err.code === "EEXIST";
|
|
@@ -8897,20 +9168,20 @@ async function acquireFileLock(lockPath) {
|
|
|
8897
9168
|
}
|
|
8898
9169
|
function releaseFileLock(lockPath) {
|
|
8899
9170
|
try {
|
|
8900
|
-
|
|
9171
|
+
fs17.unlinkSync(lockPath);
|
|
8901
9172
|
} catch {
|
|
8902
9173
|
}
|
|
8903
9174
|
}
|
|
8904
9175
|
function loadOrRebuildHashes(stateDir, learningsPath) {
|
|
8905
|
-
const hashesPath =
|
|
8906
|
-
if (
|
|
9176
|
+
const hashesPath = path14.join(stateDir, CONTENT_HASHES_FILE);
|
|
9177
|
+
if (fs17.existsSync(hashesPath)) {
|
|
8907
9178
|
const hashes = loadContentHashes(stateDir);
|
|
8908
|
-
if (Object.keys(hashes).length === 0 &&
|
|
9179
|
+
if (Object.keys(hashes).length === 0 && fs17.existsSync(learningsPath)) {
|
|
8909
9180
|
return rebuildContentHashes(stateDir, LEARNINGS_FILE);
|
|
8910
9181
|
}
|
|
8911
9182
|
return hashes;
|
|
8912
9183
|
}
|
|
8913
|
-
if (
|
|
9184
|
+
if (fs17.existsSync(learningsPath)) {
|
|
8914
9185
|
return rebuildContentHashes(stateDir, LEARNINGS_FILE);
|
|
8915
9186
|
}
|
|
8916
9187
|
return {};
|
|
@@ -8934,8 +9205,8 @@ async function appendLearning(projectPath, learning, skillName, outcome, stream,
|
|
|
8934
9205
|
const dirResult = await getStateDir(projectPath, stream, session);
|
|
8935
9206
|
if (!dirResult.ok) return dirResult;
|
|
8936
9207
|
const stateDir = dirResult.value;
|
|
8937
|
-
const learningsPath =
|
|
8938
|
-
|
|
9208
|
+
const learningsPath = path14.join(stateDir, LEARNINGS_FILE);
|
|
9209
|
+
fs17.mkdirSync(stateDir, { recursive: true });
|
|
8939
9210
|
const lockPath = learningsPath + ".lock";
|
|
8940
9211
|
await acquireFileLock(lockPath);
|
|
8941
9212
|
try {
|
|
@@ -8965,17 +9236,17 @@ ${bulletLine}
|
|
|
8965
9236
|
`;
|
|
8966
9237
|
let overlapResult;
|
|
8967
9238
|
let existingLineCount;
|
|
8968
|
-
if (!
|
|
8969
|
-
|
|
9239
|
+
if (!fs17.existsSync(learningsPath)) {
|
|
9240
|
+
fs17.writeFileSync(learningsPath, `# Learnings
|
|
8970
9241
|
${entry}`);
|
|
8971
9242
|
existingLineCount = 1;
|
|
8972
9243
|
} else {
|
|
8973
|
-
const existingContent =
|
|
9244
|
+
const existingContent = fs17.readFileSync(learningsPath, "utf-8");
|
|
8974
9245
|
const existingEntries = existingContent.split("\n").filter((line) => /^- \*\*\d{4}-\d{2}-\d{2}/.test(line));
|
|
8975
9246
|
const overlap = checkOverlap(bulletLine, existingEntries);
|
|
8976
9247
|
if (overlap.score >= 0.7) overlapResult = overlap;
|
|
8977
9248
|
existingLineCount = existingContent.split("\n").length;
|
|
8978
|
-
|
|
9249
|
+
fs17.appendFileSync(learningsPath, entry);
|
|
8979
9250
|
}
|
|
8980
9251
|
const bulletLineNum = existingLineCount + 2;
|
|
8981
9252
|
contentHashes[contentHash] = { date: timestamp ?? "", line: bulletLineNum };
|
|
@@ -9080,11 +9351,11 @@ async function loadIndexEntries(projectPath, skillName, stream, session) {
|
|
|
9080
9351
|
const dirResult = await getStateDir(projectPath, stream, session);
|
|
9081
9352
|
if (!dirResult.ok) return dirResult;
|
|
9082
9353
|
const stateDir = dirResult.value;
|
|
9083
|
-
const learningsPath =
|
|
9084
|
-
if (!
|
|
9354
|
+
const learningsPath = path14.join(stateDir, LEARNINGS_FILE);
|
|
9355
|
+
if (!fs17.existsSync(learningsPath)) {
|
|
9085
9356
|
return Ok([]);
|
|
9086
9357
|
}
|
|
9087
|
-
const content =
|
|
9358
|
+
const content = fs17.readFileSync(learningsPath, "utf-8");
|
|
9088
9359
|
const lines = content.split("\n");
|
|
9089
9360
|
const indexEntries = [];
|
|
9090
9361
|
let pendingFrontmatter = null;
|
|
@@ -9126,16 +9397,16 @@ async function archiveLearnings(projectPath, entries, stream) {
|
|
|
9126
9397
|
const dirResult = await getStateDir(projectPath, stream);
|
|
9127
9398
|
if (!dirResult.ok) return dirResult;
|
|
9128
9399
|
const stateDir = dirResult.value;
|
|
9129
|
-
const archiveDir =
|
|
9130
|
-
|
|
9400
|
+
const archiveDir = path15.join(stateDir, "learnings-archive");
|
|
9401
|
+
fs18.mkdirSync(archiveDir, { recursive: true });
|
|
9131
9402
|
const now = /* @__PURE__ */ new Date();
|
|
9132
9403
|
const yearMonth = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`;
|
|
9133
|
-
const archivePath =
|
|
9404
|
+
const archivePath = path15.join(archiveDir, `${yearMonth}.md`);
|
|
9134
9405
|
const archiveContent = entries.join("\n\n") + "\n";
|
|
9135
|
-
if (
|
|
9136
|
-
|
|
9406
|
+
if (fs18.existsSync(archivePath)) {
|
|
9407
|
+
fs18.appendFileSync(archivePath, "\n" + archiveContent);
|
|
9137
9408
|
} else {
|
|
9138
|
-
|
|
9409
|
+
fs18.writeFileSync(archivePath, `# Learnings Archive
|
|
9139
9410
|
|
|
9140
9411
|
${archiveContent}`);
|
|
9141
9412
|
}
|
|
@@ -9153,8 +9424,8 @@ async function pruneLearnings(projectPath, stream) {
|
|
|
9153
9424
|
const dirResult = await getStateDir(projectPath, stream);
|
|
9154
9425
|
if (!dirResult.ok) return dirResult;
|
|
9155
9426
|
const stateDir = dirResult.value;
|
|
9156
|
-
const learningsPath =
|
|
9157
|
-
if (!
|
|
9427
|
+
const learningsPath = path15.join(stateDir, LEARNINGS_FILE);
|
|
9428
|
+
if (!fs18.existsSync(learningsPath)) {
|
|
9158
9429
|
return Ok({ kept: 0, archived: 0, patterns: [] });
|
|
9159
9430
|
}
|
|
9160
9431
|
const loadResult = await loadRelevantLearnings(projectPath, void 0, stream);
|
|
@@ -9185,7 +9456,7 @@ async function pruneLearnings(projectPath, stream) {
|
|
|
9185
9456
|
if (!archiveResult.ok) return archiveResult;
|
|
9186
9457
|
}
|
|
9187
9458
|
const newContent = "# Learnings\n\n" + toKeep.join("\n\n") + "\n";
|
|
9188
|
-
|
|
9459
|
+
fs18.writeFileSync(learningsPath, newContent);
|
|
9189
9460
|
invalidateLearningsCacheEntry(learningsPath);
|
|
9190
9461
|
return Ok({
|
|
9191
9462
|
kept: toKeep.length,
|
|
@@ -9229,19 +9500,19 @@ async function promoteSessionLearnings(projectPath, sessionSlug, stream) {
|
|
|
9229
9500
|
const dirResult = await getStateDir(projectPath, stream);
|
|
9230
9501
|
if (!dirResult.ok) return dirResult;
|
|
9231
9502
|
const stateDir = dirResult.value;
|
|
9232
|
-
const globalPath =
|
|
9233
|
-
const existingGlobal =
|
|
9503
|
+
const globalPath = path15.join(stateDir, LEARNINGS_FILE);
|
|
9504
|
+
const existingGlobal = fs18.existsSync(globalPath) ? fs18.readFileSync(globalPath, "utf-8") : "";
|
|
9234
9505
|
const newEntries = toPromote.filter((entry) => !existingGlobal.includes(entry.trim()));
|
|
9235
9506
|
if (newEntries.length === 0) {
|
|
9236
9507
|
return Ok({ promoted: 0, skipped: skipped + toPromote.length });
|
|
9237
9508
|
}
|
|
9238
9509
|
const promotedContent = newEntries.join("\n\n") + "\n";
|
|
9239
9510
|
if (!existingGlobal) {
|
|
9240
|
-
|
|
9511
|
+
fs18.writeFileSync(globalPath, `# Learnings
|
|
9241
9512
|
|
|
9242
9513
|
${promotedContent}`);
|
|
9243
9514
|
} else {
|
|
9244
|
-
|
|
9515
|
+
fs18.appendFileSync(globalPath, "\n\n" + promotedContent);
|
|
9245
9516
|
}
|
|
9246
9517
|
invalidateLearningsCacheEntry(globalPath);
|
|
9247
9518
|
return Ok({
|
|
@@ -9277,13 +9548,13 @@ async function detectStaleLearnings(projectPath, stream, session) {
|
|
|
9277
9548
|
freshCount++;
|
|
9278
9549
|
continue;
|
|
9279
9550
|
}
|
|
9280
|
-
const resolvedRoot =
|
|
9551
|
+
const resolvedRoot = path16.resolve(projectPath);
|
|
9281
9552
|
const missing = refs.filter((ref) => {
|
|
9282
|
-
const absPath =
|
|
9283
|
-
if (!absPath.startsWith(resolvedRoot +
|
|
9553
|
+
const absPath = path16.resolve(projectPath, ref);
|
|
9554
|
+
if (!absPath.startsWith(resolvedRoot + path16.sep) && absPath !== resolvedRoot) {
|
|
9284
9555
|
return false;
|
|
9285
9556
|
}
|
|
9286
|
-
return !
|
|
9557
|
+
return !fs19.existsSync(absPath);
|
|
9287
9558
|
});
|
|
9288
9559
|
if (missing.length > 0) {
|
|
9289
9560
|
const idx = extractIndexEntry(entry);
|
|
@@ -9318,17 +9589,17 @@ async function appendFailure(projectPath, description, skillName, type, stream,
|
|
|
9318
9589
|
const dirResult = await getStateDir(projectPath, stream, session);
|
|
9319
9590
|
if (!dirResult.ok) return dirResult;
|
|
9320
9591
|
const stateDir = dirResult.value;
|
|
9321
|
-
const failuresPath =
|
|
9322
|
-
|
|
9592
|
+
const failuresPath = path17.join(stateDir, FAILURES_FILE);
|
|
9593
|
+
fs20.mkdirSync(stateDir, { recursive: true });
|
|
9323
9594
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
9324
9595
|
const entry = `
|
|
9325
9596
|
- **${timestamp} [skill:${skillName}] [type:${type}]:** ${description}
|
|
9326
9597
|
`;
|
|
9327
|
-
if (!
|
|
9328
|
-
|
|
9598
|
+
if (!fs20.existsSync(failuresPath)) {
|
|
9599
|
+
fs20.writeFileSync(failuresPath, `# Failures
|
|
9329
9600
|
${entry}`);
|
|
9330
9601
|
} else {
|
|
9331
|
-
|
|
9602
|
+
fs20.appendFileSync(failuresPath, entry);
|
|
9332
9603
|
}
|
|
9333
9604
|
failuresCacheMap.delete(failuresPath);
|
|
9334
9605
|
return Ok(void 0);
|
|
@@ -9345,17 +9616,17 @@ async function loadFailures(projectPath, stream, session) {
|
|
|
9345
9616
|
const dirResult = await getStateDir(projectPath, stream, session);
|
|
9346
9617
|
if (!dirResult.ok) return dirResult;
|
|
9347
9618
|
const stateDir = dirResult.value;
|
|
9348
|
-
const failuresPath =
|
|
9349
|
-
if (!
|
|
9619
|
+
const failuresPath = path17.join(stateDir, FAILURES_FILE);
|
|
9620
|
+
if (!fs20.existsSync(failuresPath)) {
|
|
9350
9621
|
return Ok([]);
|
|
9351
9622
|
}
|
|
9352
|
-
const stats =
|
|
9623
|
+
const stats = fs20.statSync(failuresPath);
|
|
9353
9624
|
const cacheKey = failuresPath;
|
|
9354
9625
|
const cached = failuresCacheMap.get(cacheKey);
|
|
9355
9626
|
if (cached && cached.mtimeMs === stats.mtimeMs) {
|
|
9356
9627
|
return Ok(cached.entries);
|
|
9357
9628
|
}
|
|
9358
|
-
const content =
|
|
9629
|
+
const content = fs20.readFileSync(failuresPath, "utf-8");
|
|
9359
9630
|
const entries = [];
|
|
9360
9631
|
for (const line of content.split("\n")) {
|
|
9361
9632
|
const match = line.match(FAILURE_LINE_REGEX);
|
|
@@ -9384,20 +9655,20 @@ async function archiveFailures(projectPath, stream, session) {
|
|
|
9384
9655
|
const dirResult = await getStateDir(projectPath, stream, session);
|
|
9385
9656
|
if (!dirResult.ok) return dirResult;
|
|
9386
9657
|
const stateDir = dirResult.value;
|
|
9387
|
-
const failuresPath =
|
|
9388
|
-
if (!
|
|
9658
|
+
const failuresPath = path17.join(stateDir, FAILURES_FILE);
|
|
9659
|
+
if (!fs20.existsSync(failuresPath)) {
|
|
9389
9660
|
return Ok(void 0);
|
|
9390
9661
|
}
|
|
9391
|
-
const archiveDir =
|
|
9392
|
-
|
|
9662
|
+
const archiveDir = path17.join(stateDir, "archive");
|
|
9663
|
+
fs20.mkdirSync(archiveDir, { recursive: true });
|
|
9393
9664
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
9394
9665
|
let archiveName = `failures-${date}.md`;
|
|
9395
9666
|
let counter = 2;
|
|
9396
|
-
while (
|
|
9667
|
+
while (fs20.existsSync(path17.join(archiveDir, archiveName))) {
|
|
9397
9668
|
archiveName = `failures-${date}-${counter}.md`;
|
|
9398
9669
|
counter++;
|
|
9399
9670
|
}
|
|
9400
|
-
|
|
9671
|
+
fs20.renameSync(failuresPath, path17.join(archiveDir, archiveName));
|
|
9401
9672
|
failuresCacheMap.delete(failuresPath);
|
|
9402
9673
|
return Ok(void 0);
|
|
9403
9674
|
} catch (error) {
|
|
@@ -9413,11 +9684,11 @@ async function saveHandoff(projectPath, handoff, stream, session) {
|
|
|
9413
9684
|
const dirResult = await getStateDir(projectPath, stream, session);
|
|
9414
9685
|
if (!dirResult.ok) return dirResult;
|
|
9415
9686
|
const stateDir = dirResult.value;
|
|
9416
|
-
const handoffPath =
|
|
9417
|
-
|
|
9687
|
+
const handoffPath = path18.join(stateDir, HANDOFF_FILE);
|
|
9688
|
+
fs21.mkdirSync(stateDir, { recursive: true });
|
|
9418
9689
|
const tmpPath = handoffPath + ".tmp";
|
|
9419
|
-
|
|
9420
|
-
|
|
9690
|
+
fs21.writeFileSync(tmpPath, JSON.stringify(handoff, null, 2));
|
|
9691
|
+
fs21.renameSync(tmpPath, handoffPath);
|
|
9421
9692
|
return Ok(void 0);
|
|
9422
9693
|
} catch (error) {
|
|
9423
9694
|
return Err(
|
|
@@ -9430,11 +9701,11 @@ async function loadHandoff(projectPath, stream, session) {
|
|
|
9430
9701
|
const dirResult = await getStateDir(projectPath, stream, session);
|
|
9431
9702
|
if (!dirResult.ok) return dirResult;
|
|
9432
9703
|
const stateDir = dirResult.value;
|
|
9433
|
-
const handoffPath =
|
|
9434
|
-
if (!
|
|
9704
|
+
const handoffPath = path18.join(stateDir, HANDOFF_FILE);
|
|
9705
|
+
if (!fs21.existsSync(handoffPath)) {
|
|
9435
9706
|
return Ok(null);
|
|
9436
9707
|
}
|
|
9437
|
-
const raw =
|
|
9708
|
+
const raw = fs21.readFileSync(handoffPath, "utf-8");
|
|
9438
9709
|
const parsed = JSON.parse(raw);
|
|
9439
9710
|
const result = HandoffSchema.safeParse(parsed);
|
|
9440
9711
|
if (!result.success) {
|
|
@@ -9448,28 +9719,28 @@ async function loadHandoff(projectPath, stream, session) {
|
|
|
9448
9719
|
}
|
|
9449
9720
|
}
|
|
9450
9721
|
function loadChecksFromConfig(gateConfigPath) {
|
|
9451
|
-
if (!
|
|
9452
|
-
const raw = JSON.parse(
|
|
9722
|
+
if (!fs22.existsSync(gateConfigPath)) return [];
|
|
9723
|
+
const raw = JSON.parse(fs22.readFileSync(gateConfigPath, "utf-8"));
|
|
9453
9724
|
const config = GateConfigSchema.safeParse(raw);
|
|
9454
9725
|
if (config.success && config.data.checks) return config.data.checks;
|
|
9455
9726
|
return [];
|
|
9456
9727
|
}
|
|
9457
9728
|
function discoverChecksFromProject(projectPath) {
|
|
9458
9729
|
const checks = [];
|
|
9459
|
-
const packageJsonPath =
|
|
9460
|
-
if (
|
|
9461
|
-
const pkg = JSON.parse(
|
|
9730
|
+
const packageJsonPath = path19.join(projectPath, "package.json");
|
|
9731
|
+
if (fs22.existsSync(packageJsonPath)) {
|
|
9732
|
+
const pkg = JSON.parse(fs22.readFileSync(packageJsonPath, "utf-8"));
|
|
9462
9733
|
const scripts = pkg.scripts || {};
|
|
9463
9734
|
if (scripts.test) checks.push({ name: "test", command: "npm test" });
|
|
9464
9735
|
if (scripts.lint) checks.push({ name: "lint", command: "npm run lint" });
|
|
9465
9736
|
if (scripts.typecheck) checks.push({ name: "typecheck", command: "npm run typecheck" });
|
|
9466
9737
|
if (scripts.build) checks.push({ name: "build", command: "npm run build" });
|
|
9467
9738
|
}
|
|
9468
|
-
if (
|
|
9739
|
+
if (fs22.existsSync(path19.join(projectPath, "go.mod"))) {
|
|
9469
9740
|
checks.push({ name: "test", command: "go test ./..." });
|
|
9470
9741
|
checks.push({ name: "build", command: "go build ./..." });
|
|
9471
9742
|
}
|
|
9472
|
-
if (
|
|
9743
|
+
if (fs22.existsSync(path19.join(projectPath, "pyproject.toml")) || fs22.existsSync(path19.join(projectPath, "setup.py"))) {
|
|
9473
9744
|
checks.push({ name: "test", command: "python -m pytest" });
|
|
9474
9745
|
}
|
|
9475
9746
|
return checks;
|
|
@@ -9509,8 +9780,8 @@ function executeCheck(check, projectPath) {
|
|
|
9509
9780
|
}
|
|
9510
9781
|
}
|
|
9511
9782
|
async function runMechanicalGate(projectPath) {
|
|
9512
|
-
const harnessDir =
|
|
9513
|
-
const gateConfigPath =
|
|
9783
|
+
const harnessDir = path19.join(projectPath, HARNESS_DIR);
|
|
9784
|
+
const gateConfigPath = path19.join(harnessDir, GATE_CONFIG_FILE);
|
|
9514
9785
|
try {
|
|
9515
9786
|
let checks = loadChecksFromConfig(gateConfigPath);
|
|
9516
9787
|
if (checks.length === 0) {
|
|
@@ -9566,9 +9837,9 @@ function writeSessionSummary(projectPath, sessionSlug, data) {
|
|
|
9566
9837
|
const dirResult = resolveSessionDir(projectPath, sessionSlug, { create: true });
|
|
9567
9838
|
if (!dirResult.ok) return dirResult;
|
|
9568
9839
|
const sessionDir = dirResult.value;
|
|
9569
|
-
const summaryPath =
|
|
9840
|
+
const summaryPath = path20.join(sessionDir, SUMMARY_FILE);
|
|
9570
9841
|
const content = formatSummary(data);
|
|
9571
|
-
|
|
9842
|
+
fs23.writeFileSync(summaryPath, content);
|
|
9572
9843
|
const description = deriveIndexDescription(data);
|
|
9573
9844
|
updateSessionIndex(projectPath, sessionSlug, description);
|
|
9574
9845
|
return Ok(void 0);
|
|
@@ -9585,11 +9856,11 @@ function loadSessionSummary(projectPath, sessionSlug) {
|
|
|
9585
9856
|
const dirResult = resolveSessionDir(projectPath, sessionSlug);
|
|
9586
9857
|
if (!dirResult.ok) return dirResult;
|
|
9587
9858
|
const sessionDir = dirResult.value;
|
|
9588
|
-
const summaryPath =
|
|
9589
|
-
if (!
|
|
9859
|
+
const summaryPath = path20.join(sessionDir, SUMMARY_FILE);
|
|
9860
|
+
if (!fs23.existsSync(summaryPath)) {
|
|
9590
9861
|
return Ok(null);
|
|
9591
9862
|
}
|
|
9592
|
-
const content =
|
|
9863
|
+
const content = fs23.readFileSync(summaryPath, "utf-8");
|
|
9593
9864
|
return Ok(content);
|
|
9594
9865
|
} catch (error) {
|
|
9595
9866
|
return Err(
|
|
@@ -9601,11 +9872,11 @@ function loadSessionSummary(projectPath, sessionSlug) {
|
|
|
9601
9872
|
}
|
|
9602
9873
|
function listActiveSessions(projectPath) {
|
|
9603
9874
|
try {
|
|
9604
|
-
const indexPath2 =
|
|
9605
|
-
if (!
|
|
9875
|
+
const indexPath2 = path20.join(projectPath, HARNESS_DIR, SESSIONS_DIR, SESSION_INDEX_FILE);
|
|
9876
|
+
if (!fs23.existsSync(indexPath2)) {
|
|
9606
9877
|
return Ok(null);
|
|
9607
9878
|
}
|
|
9608
|
-
const content =
|
|
9879
|
+
const content = fs23.readFileSync(indexPath2, "utf-8");
|
|
9609
9880
|
return Ok(content);
|
|
9610
9881
|
} catch (error) {
|
|
9611
9882
|
return Err(
|
|
@@ -9626,12 +9897,12 @@ async function loadSessionState(projectPath, sessionSlug) {
|
|
|
9626
9897
|
const dirResult = resolveSessionDir(projectPath, sessionSlug);
|
|
9627
9898
|
if (!dirResult.ok) return dirResult;
|
|
9628
9899
|
const sessionDir = dirResult.value;
|
|
9629
|
-
const filePath =
|
|
9630
|
-
if (!
|
|
9900
|
+
const filePath = path21.join(sessionDir, SESSION_STATE_FILE);
|
|
9901
|
+
if (!fs24.existsSync(filePath)) {
|
|
9631
9902
|
return Ok(emptySections());
|
|
9632
9903
|
}
|
|
9633
9904
|
try {
|
|
9634
|
-
const raw =
|
|
9905
|
+
const raw = fs24.readFileSync(filePath, "utf-8");
|
|
9635
9906
|
const parsed = JSON.parse(raw);
|
|
9636
9907
|
const sections = emptySections();
|
|
9637
9908
|
for (const name of SESSION_SECTION_NAMES) {
|
|
@@ -9652,9 +9923,9 @@ async function saveSessionState(projectPath, sessionSlug, sections) {
|
|
|
9652
9923
|
const dirResult = resolveSessionDir(projectPath, sessionSlug, { create: true });
|
|
9653
9924
|
if (!dirResult.ok) return dirResult;
|
|
9654
9925
|
const sessionDir = dirResult.value;
|
|
9655
|
-
const filePath =
|
|
9926
|
+
const filePath = path21.join(sessionDir, SESSION_STATE_FILE);
|
|
9656
9927
|
try {
|
|
9657
|
-
|
|
9928
|
+
fs24.writeFileSync(filePath, JSON.stringify(sections, null, 2));
|
|
9658
9929
|
return Ok(void 0);
|
|
9659
9930
|
} catch (error) {
|
|
9660
9931
|
return Err(
|
|
@@ -9710,26 +9981,26 @@ async function archiveSession(projectPath, sessionSlug) {
|
|
|
9710
9981
|
const dirResult = resolveSessionDir(projectPath, sessionSlug);
|
|
9711
9982
|
if (!dirResult.ok) return dirResult;
|
|
9712
9983
|
const sessionDir = dirResult.value;
|
|
9713
|
-
if (!
|
|
9984
|
+
if (!fs25.existsSync(sessionDir)) {
|
|
9714
9985
|
return Err(new Error(`Session '${sessionSlug}' not found at ${sessionDir}`));
|
|
9715
9986
|
}
|
|
9716
|
-
const archiveBase =
|
|
9987
|
+
const archiveBase = path22.join(projectPath, HARNESS_DIR, ARCHIVE_DIR, "sessions");
|
|
9717
9988
|
try {
|
|
9718
|
-
|
|
9989
|
+
fs25.mkdirSync(archiveBase, { recursive: true });
|
|
9719
9990
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
9720
9991
|
let archiveName = `${sessionSlug}-${date}`;
|
|
9721
9992
|
let counter = 1;
|
|
9722
|
-
while (
|
|
9993
|
+
while (fs25.existsSync(path22.join(archiveBase, archiveName))) {
|
|
9723
9994
|
archiveName = `${sessionSlug}-${date}-${counter}`;
|
|
9724
9995
|
counter++;
|
|
9725
9996
|
}
|
|
9726
|
-
const dest =
|
|
9997
|
+
const dest = path22.join(archiveBase, archiveName);
|
|
9727
9998
|
try {
|
|
9728
|
-
|
|
9999
|
+
fs25.renameSync(sessionDir, dest);
|
|
9729
10000
|
} catch (renameErr) {
|
|
9730
10001
|
if (renameErr instanceof Error && "code" in renameErr && renameErr.code === "EXDEV") {
|
|
9731
|
-
|
|
9732
|
-
|
|
10002
|
+
fs25.cpSync(sessionDir, dest, { recursive: true });
|
|
10003
|
+
fs25.rmSync(sessionDir, { recursive: true });
|
|
9733
10004
|
} else {
|
|
9734
10005
|
throw renameErr;
|
|
9735
10006
|
}
|
|
@@ -9751,8 +10022,8 @@ function loadKnownHashes(eventsPath) {
|
|
|
9751
10022
|
const cached = knownHashesCache.get(eventsPath);
|
|
9752
10023
|
if (cached) return cached;
|
|
9753
10024
|
const hashes = /* @__PURE__ */ new Set();
|
|
9754
|
-
if (
|
|
9755
|
-
const content =
|
|
10025
|
+
if (fs26.existsSync(eventsPath)) {
|
|
10026
|
+
const content = fs26.readFileSync(eventsPath, "utf-8");
|
|
9756
10027
|
const lines = content.split("\n").filter((line) => line.trim() !== "");
|
|
9757
10028
|
for (const line of lines) {
|
|
9758
10029
|
try {
|
|
@@ -9775,8 +10046,8 @@ async function emitEvent(projectPath, event, options) {
|
|
|
9775
10046
|
const dirResult = await getStateDir(projectPath, options?.stream, options?.session);
|
|
9776
10047
|
if (!dirResult.ok) return dirResult;
|
|
9777
10048
|
const stateDir = dirResult.value;
|
|
9778
|
-
const eventsPath =
|
|
9779
|
-
|
|
10049
|
+
const eventsPath = path23.join(stateDir, EVENTS_FILE);
|
|
10050
|
+
fs26.mkdirSync(stateDir, { recursive: true });
|
|
9780
10051
|
const contentHash = computeEventHash(event, options?.session);
|
|
9781
10052
|
const knownHashes = loadKnownHashes(eventsPath);
|
|
9782
10053
|
if (knownHashes.has(contentHash)) {
|
|
@@ -9790,7 +10061,7 @@ async function emitEvent(projectPath, event, options) {
|
|
|
9790
10061
|
if (options?.session) {
|
|
9791
10062
|
fullEvent.session = options.session;
|
|
9792
10063
|
}
|
|
9793
|
-
|
|
10064
|
+
fs26.appendFileSync(eventsPath, JSON.stringify(fullEvent) + "\n");
|
|
9794
10065
|
knownHashes.add(contentHash);
|
|
9795
10066
|
return Ok({ written: true });
|
|
9796
10067
|
} catch (error) {
|
|
@@ -9804,11 +10075,11 @@ async function loadEvents(projectPath, options) {
|
|
|
9804
10075
|
const dirResult = await getStateDir(projectPath, options?.stream, options?.session);
|
|
9805
10076
|
if (!dirResult.ok) return dirResult;
|
|
9806
10077
|
const stateDir = dirResult.value;
|
|
9807
|
-
const eventsPath =
|
|
9808
|
-
if (!
|
|
10078
|
+
const eventsPath = path23.join(stateDir, EVENTS_FILE);
|
|
10079
|
+
if (!fs26.existsSync(eventsPath)) {
|
|
9809
10080
|
return Ok([]);
|
|
9810
10081
|
}
|
|
9811
|
-
const content =
|
|
10082
|
+
const content = fs26.readFileSync(eventsPath, "utf-8");
|
|
9812
10083
|
const lines = content.split("\n").filter((line) => line.trim() !== "");
|
|
9813
10084
|
const events = [];
|
|
9814
10085
|
for (const line of lines) {
|
|
@@ -10057,11 +10328,11 @@ function nodeSubStacks(allDeps) {
|
|
|
10057
10328
|
return found;
|
|
10058
10329
|
}
|
|
10059
10330
|
function detectNodeStacks(projectRoot) {
|
|
10060
|
-
const pkgJsonPath =
|
|
10061
|
-
if (!
|
|
10331
|
+
const pkgJsonPath = path24.join(projectRoot, "package.json");
|
|
10332
|
+
if (!fs27.existsSync(pkgJsonPath)) return [];
|
|
10062
10333
|
const stacks = ["node"];
|
|
10063
10334
|
try {
|
|
10064
|
-
const pkgJson = JSON.parse(
|
|
10335
|
+
const pkgJson = JSON.parse(fs27.readFileSync(pkgJsonPath, "utf-8"));
|
|
10065
10336
|
const allDeps = { ...pkgJson.dependencies, ...pkgJson.devDependencies };
|
|
10066
10337
|
stacks.push(...nodeSubStacks(allDeps));
|
|
10067
10338
|
} catch {
|
|
@@ -10070,10 +10341,10 @@ function detectNodeStacks(projectRoot) {
|
|
|
10070
10341
|
}
|
|
10071
10342
|
function detectStack(projectRoot) {
|
|
10072
10343
|
const stacks = [...detectNodeStacks(projectRoot)];
|
|
10073
|
-
if (
|
|
10344
|
+
if (fs27.existsSync(path24.join(projectRoot, "go.mod"))) {
|
|
10074
10345
|
stacks.push("go");
|
|
10075
10346
|
}
|
|
10076
|
-
const hasPython =
|
|
10347
|
+
const hasPython = fs27.existsSync(path24.join(projectRoot, "requirements.txt")) || fs27.existsSync(path24.join(projectRoot, "pyproject.toml"));
|
|
10077
10348
|
if (hasPython) {
|
|
10078
10349
|
stacks.push("python");
|
|
10079
10350
|
}
|
|
@@ -10116,7 +10387,7 @@ function getInjectionPatterns() {
|
|
|
10116
10387
|
}
|
|
10117
10388
|
function getTaintFilePath(projectRoot, sessionId) {
|
|
10118
10389
|
const id = sessionId || DEFAULT_SESSION_ID;
|
|
10119
|
-
return
|
|
10390
|
+
return join392(projectRoot, ".harness", `session-taint-${id}.json`);
|
|
10120
10391
|
}
|
|
10121
10392
|
function readTaint(projectRoot, sessionId) {
|
|
10122
10393
|
const filePath = getTaintFilePath(projectRoot, sessionId);
|
|
@@ -10198,14 +10469,14 @@ function clearTaint(projectRoot, sessionId) {
|
|
|
10198
10469
|
return 0;
|
|
10199
10470
|
}
|
|
10200
10471
|
}
|
|
10201
|
-
const harnessDir =
|
|
10472
|
+
const harnessDir = join392(projectRoot, ".harness");
|
|
10202
10473
|
let count = 0;
|
|
10203
10474
|
try {
|
|
10204
10475
|
const files = readdirSync3(harnessDir);
|
|
10205
10476
|
for (const file of files) {
|
|
10206
10477
|
if (file.startsWith("session-taint-") && file.endsWith(".json")) {
|
|
10207
10478
|
try {
|
|
10208
|
-
unlinkSync2(
|
|
10479
|
+
unlinkSync2(join392(harnessDir, file));
|
|
10209
10480
|
count++;
|
|
10210
10481
|
} catch {
|
|
10211
10482
|
}
|
|
@@ -10216,7 +10487,7 @@ function clearTaint(projectRoot, sessionId) {
|
|
|
10216
10487
|
return count;
|
|
10217
10488
|
}
|
|
10218
10489
|
function listTaintedSessions(projectRoot) {
|
|
10219
|
-
const harnessDir =
|
|
10490
|
+
const harnessDir = join392(projectRoot, ".harness");
|
|
10220
10491
|
const sessions = [];
|
|
10221
10492
|
try {
|
|
10222
10493
|
const files = readdirSync3(harnessDir);
|
|
@@ -10288,7 +10559,7 @@ function securityFindingId(finding) {
|
|
|
10288
10559
|
}
|
|
10289
10560
|
async function runValidateCheck(projectRoot, config) {
|
|
10290
10561
|
const issues = [];
|
|
10291
|
-
const agentsPath =
|
|
10562
|
+
const agentsPath = path25.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
|
|
10292
10563
|
const result = await validateAgentsMap(agentsPath);
|
|
10293
10564
|
if (!result.ok) {
|
|
10294
10565
|
issues.push({ severity: "error", message: result.error.message });
|
|
@@ -10345,14 +10616,13 @@ async function runDepsCheck(projectRoot, config) {
|
|
|
10345
10616
|
}
|
|
10346
10617
|
async function runDocsCheck(projectRoot, config) {
|
|
10347
10618
|
const issues = [];
|
|
10348
|
-
const docsDir =
|
|
10619
|
+
const docsDir = path25.join(projectRoot, config.docsDir ?? "docs");
|
|
10349
10620
|
const entropyConfig = config.entropy || {};
|
|
10350
10621
|
const result = await checkDocCoverage("project", {
|
|
10351
10622
|
docsDir,
|
|
10352
10623
|
sourceDir: projectRoot,
|
|
10353
10624
|
excludePatterns: entropyConfig.excludePatterns || [
|
|
10354
|
-
|
|
10355
|
-
"**/dist/**",
|
|
10625
|
+
...skipDirGlobs(),
|
|
10356
10626
|
"**/*.test.ts",
|
|
10357
10627
|
"**/fixtures/**"
|
|
10358
10628
|
]
|
|
@@ -10417,12 +10687,7 @@ async function runSecurityCheck(projectRoot, config) {
|
|
|
10417
10687
|
const { glob: globFn } = await import("glob");
|
|
10418
10688
|
const sourceFiles = await globFn("**/*.{ts,tsx,js,jsx,go,py}", {
|
|
10419
10689
|
cwd: projectRoot,
|
|
10420
|
-
ignore: securityConfig.exclude ?? [
|
|
10421
|
-
"**/node_modules/**",
|
|
10422
|
-
"**/dist/**",
|
|
10423
|
-
"**/*.test.ts",
|
|
10424
|
-
"**/fixtures/**"
|
|
10425
|
-
],
|
|
10690
|
+
ignore: securityConfig.exclude ?? [...skipDirGlobs(), "**/*.test.ts", "**/fixtures/**"],
|
|
10426
10691
|
absolute: true
|
|
10427
10692
|
});
|
|
10428
10693
|
const scanResult = await scanner.scanFiles(sourceFiles);
|
|
@@ -10530,7 +10795,7 @@ async function runTraceabilityCheck(projectRoot, config) {
|
|
|
10530
10795
|
const issues = [];
|
|
10531
10796
|
const traceConfig = config.traceability || {};
|
|
10532
10797
|
if (traceConfig.enabled === false) return issues;
|
|
10533
|
-
const graphDir =
|
|
10798
|
+
const graphDir = path25.join(projectRoot, ".harness", "graph");
|
|
10534
10799
|
const store = new GraphStore();
|
|
10535
10800
|
const loaded = await store.load(graphDir);
|
|
10536
10801
|
if (!loaded) {
|
|
@@ -10708,7 +10973,7 @@ async function runMechanicalChecks(options) {
|
|
|
10708
10973
|
};
|
|
10709
10974
|
if (!skip.includes("validate")) {
|
|
10710
10975
|
try {
|
|
10711
|
-
const agentsPath =
|
|
10976
|
+
const agentsPath = path26.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
|
|
10712
10977
|
const result = await validateAgentsMap(agentsPath);
|
|
10713
10978
|
if (!result.ok) {
|
|
10714
10979
|
statuses.validate = "fail";
|
|
@@ -10745,7 +11010,7 @@ async function runMechanicalChecks(options) {
|
|
|
10745
11010
|
statuses.validate = "fail";
|
|
10746
11011
|
findings.push({
|
|
10747
11012
|
tool: "validate",
|
|
10748
|
-
file:
|
|
11013
|
+
file: path26.join(projectRoot, "AGENTS.md"),
|
|
10749
11014
|
message: err instanceof Error ? err.message : String(err),
|
|
10750
11015
|
severity: "error"
|
|
10751
11016
|
});
|
|
@@ -10809,7 +11074,7 @@ async function runMechanicalChecks(options) {
|
|
|
10809
11074
|
(async () => {
|
|
10810
11075
|
const localFindings = [];
|
|
10811
11076
|
try {
|
|
10812
|
-
const docsDir =
|
|
11077
|
+
const docsDir = path26.join(projectRoot, config.docsDir ?? "docs");
|
|
10813
11078
|
const result = await checkDocCoverage("project", { docsDir });
|
|
10814
11079
|
if (!result.ok) {
|
|
10815
11080
|
statuses["check-docs"] = "warn";
|
|
@@ -10836,7 +11101,7 @@ async function runMechanicalChecks(options) {
|
|
|
10836
11101
|
statuses["check-docs"] = "warn";
|
|
10837
11102
|
localFindings.push({
|
|
10838
11103
|
tool: "check-docs",
|
|
10839
|
-
file:
|
|
11104
|
+
file: path26.join(projectRoot, "docs"),
|
|
10840
11105
|
message: err instanceof Error ? err.message : String(err),
|
|
10841
11106
|
severity: "warning"
|
|
10842
11107
|
});
|
|
@@ -10933,18 +11198,18 @@ function computeContextBudget(diffLines) {
|
|
|
10933
11198
|
return diffLines;
|
|
10934
11199
|
}
|
|
10935
11200
|
function isWithinProject(absPath, projectRoot) {
|
|
10936
|
-
const resolvedRoot =
|
|
10937
|
-
const resolvedPath =
|
|
10938
|
-
return resolvedPath.startsWith(resolvedRoot) || resolvedPath ===
|
|
11201
|
+
const resolvedRoot = path27.resolve(projectRoot) + path27.sep;
|
|
11202
|
+
const resolvedPath = path27.resolve(absPath);
|
|
11203
|
+
return resolvedPath.startsWith(resolvedRoot) || resolvedPath === path27.resolve(projectRoot);
|
|
10939
11204
|
}
|
|
10940
11205
|
async function readContextFile(projectRoot, filePath, reason) {
|
|
10941
|
-
const absPath =
|
|
11206
|
+
const absPath = path27.isAbsolute(filePath) ? filePath : path27.join(projectRoot, filePath);
|
|
10942
11207
|
if (!isWithinProject(absPath, projectRoot)) return null;
|
|
10943
11208
|
const result = await readFileContent(absPath);
|
|
10944
11209
|
if (!result.ok) return null;
|
|
10945
11210
|
const content = result.value;
|
|
10946
11211
|
const lines = content.split("\n").length;
|
|
10947
|
-
const relPath2 =
|
|
11212
|
+
const relPath2 = path27.isAbsolute(filePath) ? relativePosix(projectRoot, filePath) : filePath;
|
|
10948
11213
|
return { path: relPath2, content, reason, lines };
|
|
10949
11214
|
}
|
|
10950
11215
|
function extractImportSources2(content) {
|
|
@@ -10959,24 +11224,24 @@ function extractImportSources2(content) {
|
|
|
10959
11224
|
}
|
|
10960
11225
|
async function resolveImportPath2(projectRoot, fromFile, importSource) {
|
|
10961
11226
|
if (!importSource.startsWith(".")) return null;
|
|
10962
|
-
const fromDir =
|
|
10963
|
-
const basePath =
|
|
11227
|
+
const fromDir = path27.dirname(path27.join(projectRoot, fromFile));
|
|
11228
|
+
const basePath = path27.resolve(fromDir, importSource);
|
|
10964
11229
|
if (!isWithinProject(basePath, projectRoot)) return null;
|
|
10965
11230
|
const relBase = relativePosix(projectRoot, basePath);
|
|
10966
|
-
const sourceExt =
|
|
11231
|
+
const sourceExt = path27.extname(relBase);
|
|
10967
11232
|
const candidates = [];
|
|
10968
11233
|
const fallbacks = JS_EXT_FALLBACKS22[sourceExt];
|
|
10969
11234
|
if (fallbacks) {
|
|
10970
11235
|
const stripped = relBase.slice(0, -sourceExt.length);
|
|
10971
11236
|
for (const ext of fallbacks) candidates.push(stripped + ext);
|
|
10972
|
-
candidates.push(
|
|
10973
|
-
candidates.push(
|
|
10974
|
-
candidates.push(
|
|
11237
|
+
candidates.push(path27.join(stripped, "index.ts"));
|
|
11238
|
+
candidates.push(path27.join(stripped, "index.tsx"));
|
|
11239
|
+
candidates.push(path27.join(stripped, "index.jsx"));
|
|
10975
11240
|
}
|
|
10976
11241
|
candidates.push(relBase + ".ts", relBase + ".tsx", relBase + ".mts");
|
|
10977
|
-
candidates.push(
|
|
11242
|
+
candidates.push(path27.join(relBase, "index.ts"));
|
|
10978
11243
|
for (const candidate of candidates) {
|
|
10979
|
-
const absCandidate =
|
|
11244
|
+
const absCandidate = path27.join(projectRoot, candidate);
|
|
10980
11245
|
if (await fileExists(absCandidate)) {
|
|
10981
11246
|
return candidate;
|
|
10982
11247
|
}
|
|
@@ -10984,7 +11249,7 @@ async function resolveImportPath2(projectRoot, fromFile, importSource) {
|
|
|
10984
11249
|
return null;
|
|
10985
11250
|
}
|
|
10986
11251
|
async function findTestFiles(projectRoot, sourceFile) {
|
|
10987
|
-
const baseName =
|
|
11252
|
+
const baseName = path27.basename(sourceFile, path27.extname(sourceFile));
|
|
10988
11253
|
const pattern = `**/${baseName}.{test,spec}.{ts,tsx,mts}`;
|
|
10989
11254
|
const results = await findFiles(pattern, projectRoot);
|
|
10990
11255
|
return results.map((f) => relativePosix(projectRoot, f));
|
|
@@ -11752,7 +12017,7 @@ function normalizePath(filePath, projectRoot) {
|
|
|
11752
12017
|
let normalized = filePath;
|
|
11753
12018
|
normalized = normalized.replace(/\\/g, "/");
|
|
11754
12019
|
const normalizedRoot = projectRoot.replace(/\\/g, "/");
|
|
11755
|
-
if (
|
|
12020
|
+
if (path28.isAbsolute(normalized)) {
|
|
11756
12021
|
const root = normalizedRoot.endsWith("/") ? normalizedRoot : normalizedRoot + "/";
|
|
11757
12022
|
if (normalized.startsWith(root)) {
|
|
11758
12023
|
normalized = normalized.slice(root.length);
|
|
@@ -11764,12 +12029,12 @@ function normalizePath(filePath, projectRoot) {
|
|
|
11764
12029
|
return normalized;
|
|
11765
12030
|
}
|
|
11766
12031
|
function resolveImportPath22(currentFile, importPath) {
|
|
11767
|
-
const dir =
|
|
11768
|
-
let resolved =
|
|
12032
|
+
const dir = path28.dirname(currentFile);
|
|
12033
|
+
let resolved = path28.join(dir, importPath).replace(/\\/g, "/");
|
|
11769
12034
|
if (!resolved.match(/\.(ts|tsx|js|jsx)$/)) {
|
|
11770
12035
|
resolved += ".ts";
|
|
11771
12036
|
}
|
|
11772
|
-
return
|
|
12037
|
+
return path28.normalize(resolved).replace(/\\/g, "/");
|
|
11773
12038
|
}
|
|
11774
12039
|
function enqueueImports(content, current, visited, queue, maxDepth) {
|
|
11775
12040
|
const importRegex = /import\s+.*?from\s+['"]([^'"]+)['"]/g;
|
|
@@ -11801,7 +12066,7 @@ function isMechanicallyExcluded(finding, exclusionSet, projectRoot) {
|
|
|
11801
12066
|
const normalizedFile = normalizePath(finding.file, projectRoot);
|
|
11802
12067
|
if (exclusionSet.isExcluded(normalizedFile, finding.lineRange)) return true;
|
|
11803
12068
|
if (exclusionSet.isExcluded(finding.file, finding.lineRange)) return true;
|
|
11804
|
-
const absoluteFile =
|
|
12069
|
+
const absoluteFile = path28.isAbsolute(finding.file) ? finding.file : path28.join(projectRoot, finding.file).replace(/\\/g, "/");
|
|
11805
12070
|
return exclusionSet.isExcluded(absoluteFile, finding.lineRange);
|
|
11806
12071
|
}
|
|
11807
12072
|
async function validateWithGraph(crossFileRefs, graph) {
|
|
@@ -12809,7 +13074,7 @@ function isRegression(from, to) {
|
|
|
12809
13074
|
}
|
|
12810
13075
|
function collectAutopilotStatuses(autopilotPath, featurePlans, allTaskStatuses) {
|
|
12811
13076
|
try {
|
|
12812
|
-
const raw =
|
|
13077
|
+
const raw = fs29.readFileSync(autopilotPath, "utf-8");
|
|
12813
13078
|
const autopilot = JSON.parse(raw);
|
|
12814
13079
|
if (!autopilot.phases) return;
|
|
12815
13080
|
const linkedPhases = autopilot.phases.filter(
|
|
@@ -12841,10 +13106,10 @@ function inferStatus(feature, projectPath, allFeatures) {
|
|
|
12841
13106
|
const featuresWithPlans = allFeatures.filter((f) => f.plans.length > 0);
|
|
12842
13107
|
const useRootState = featuresWithPlans.length <= 1;
|
|
12843
13108
|
if (useRootState) {
|
|
12844
|
-
const rootStatePath =
|
|
12845
|
-
if (
|
|
13109
|
+
const rootStatePath = path29.join(projectPath, ".harness", "state.json");
|
|
13110
|
+
if (fs29.existsSync(rootStatePath)) {
|
|
12846
13111
|
try {
|
|
12847
|
-
const raw =
|
|
13112
|
+
const raw = fs29.readFileSync(rootStatePath, "utf-8");
|
|
12848
13113
|
const state = JSON.parse(raw);
|
|
12849
13114
|
if (state.progress) {
|
|
12850
13115
|
for (const status of Object.values(state.progress)) {
|
|
@@ -12855,14 +13120,14 @@ function inferStatus(feature, projectPath, allFeatures) {
|
|
|
12855
13120
|
}
|
|
12856
13121
|
}
|
|
12857
13122
|
}
|
|
12858
|
-
const sessionsDir =
|
|
12859
|
-
if (
|
|
13123
|
+
const sessionsDir = path29.join(projectPath, ".harness", "sessions");
|
|
13124
|
+
if (fs29.existsSync(sessionsDir)) {
|
|
12860
13125
|
try {
|
|
12861
|
-
const sessionDirs =
|
|
13126
|
+
const sessionDirs = fs29.readdirSync(sessionsDir, { withFileTypes: true });
|
|
12862
13127
|
for (const entry of sessionDirs) {
|
|
12863
13128
|
if (!entry.isDirectory()) continue;
|
|
12864
|
-
const autopilotPath =
|
|
12865
|
-
if (!
|
|
13129
|
+
const autopilotPath = path29.join(sessionsDir, entry.name, "autopilot-state.json");
|
|
13130
|
+
if (!fs29.existsSync(autopilotPath)) continue;
|
|
12866
13131
|
collectAutopilotStatuses(autopilotPath, feature.plans, allTaskStatuses);
|
|
12867
13132
|
}
|
|
12868
13133
|
} catch {
|
|
@@ -12944,7 +13209,7 @@ function parseRepoParts(repo) {
|
|
|
12944
13209
|
return { owner: parts[0], repo: parts[1] };
|
|
12945
13210
|
}
|
|
12946
13211
|
function sleep2(ms) {
|
|
12947
|
-
return new Promise((
|
|
13212
|
+
return new Promise((resolve10) => setTimeout(resolve10, ms));
|
|
12948
13213
|
}
|
|
12949
13214
|
async function fetchWithRetry(fetchFn, input, init, opts = RETRY_DEFAULTS) {
|
|
12950
13215
|
let lastResponse;
|
|
@@ -12977,9 +13242,9 @@ function isValidTrackerShape(tracker) {
|
|
|
12977
13242
|
}
|
|
12978
13243
|
function loadTrackerSyncConfig(projectRoot) {
|
|
12979
13244
|
try {
|
|
12980
|
-
const configPath =
|
|
12981
|
-
if (!
|
|
12982
|
-
const raw =
|
|
13245
|
+
const configPath = path30.join(projectRoot, "harness.config.json");
|
|
13246
|
+
if (!fs30.existsSync(configPath)) return null;
|
|
13247
|
+
const raw = fs30.readFileSync(configPath, "utf-8");
|
|
12983
13248
|
const config = JSON.parse(raw);
|
|
12984
13249
|
const tracker = config.roadmap?.tracker;
|
|
12985
13250
|
if (!isValidTrackerShape(tracker)) return null;
|
|
@@ -13094,12 +13359,12 @@ async function syncFromExternal(roadmap, adapter2, config, options, prefetchedTi
|
|
|
13094
13359
|
async function fullSync(roadmapPath, adapter2, config, options) {
|
|
13095
13360
|
const previousSync = syncMutex;
|
|
13096
13361
|
let releaseMutex;
|
|
13097
|
-
syncMutex = new Promise((
|
|
13098
|
-
releaseMutex =
|
|
13362
|
+
syncMutex = new Promise((resolve10) => {
|
|
13363
|
+
releaseMutex = resolve10;
|
|
13099
13364
|
});
|
|
13100
13365
|
await previousSync;
|
|
13101
13366
|
try {
|
|
13102
|
-
const raw =
|
|
13367
|
+
const raw = fs31.readFileSync(roadmapPath, "utf-8");
|
|
13103
13368
|
const parseResult = parseRoadmap(raw);
|
|
13104
13369
|
if (!parseResult.ok) {
|
|
13105
13370
|
return {
|
|
@@ -13112,7 +13377,7 @@ async function fullSync(roadmapPath, adapter2, config, options) {
|
|
|
13112
13377
|
const tickets = fetchResult.ok ? fetchResult.value : void 0;
|
|
13113
13378
|
const pushResult = await syncToExternal(roadmap, adapter2, config, tickets);
|
|
13114
13379
|
const pullResult = await syncFromExternal(roadmap, adapter2, config, options);
|
|
13115
|
-
|
|
13380
|
+
fs31.writeFileSync(roadmapPath, serializeRoadmap(roadmap), "utf-8");
|
|
13116
13381
|
return {
|
|
13117
13382
|
created: pushResult.created,
|
|
13118
13383
|
updated: pushResult.updated,
|
|
@@ -13123,6 +13388,32 @@ async function fullSync(roadmapPath, adapter2, config, options) {
|
|
|
13123
13388
|
releaseMutex();
|
|
13124
13389
|
}
|
|
13125
13390
|
}
|
|
13391
|
+
function scoreRoadmapCandidatesFileLess(features, _options) {
|
|
13392
|
+
const allNames = new Set(features.map((f) => f.name.toLowerCase()));
|
|
13393
|
+
const doneNames = new Set(
|
|
13394
|
+
features.filter((f) => f.status === "done").map((f) => f.name.toLowerCase())
|
|
13395
|
+
);
|
|
13396
|
+
const eligible = features.filter((f) => {
|
|
13397
|
+
if (f.status !== "planned" && f.status !== "backlog") return false;
|
|
13398
|
+
return !f.blockedBy.some((b) => {
|
|
13399
|
+
const k = b.toLowerCase();
|
|
13400
|
+
return allNames.has(k) && !doneNames.has(k);
|
|
13401
|
+
});
|
|
13402
|
+
});
|
|
13403
|
+
const candidates = eligible.map((f) => ({
|
|
13404
|
+
feature: f,
|
|
13405
|
+
priorityTier: f.priority ? PRIORITY_RANK[f.priority] : null
|
|
13406
|
+
}));
|
|
13407
|
+
candidates.sort((a, b) => {
|
|
13408
|
+
const ap = a.priorityTier;
|
|
13409
|
+
const bp = b.priorityTier;
|
|
13410
|
+
if (ap !== null && bp === null) return -1;
|
|
13411
|
+
if (ap === null && bp !== null) return 1;
|
|
13412
|
+
if (ap !== null && bp !== null && ap !== bp) return ap - bp;
|
|
13413
|
+
return a.feature.createdAt.localeCompare(b.feature.createdAt);
|
|
13414
|
+
});
|
|
13415
|
+
return candidates;
|
|
13416
|
+
}
|
|
13126
13417
|
function isEligibleCandidate(feature, allFeatureNames, doneFeatures) {
|
|
13127
13418
|
if (feature.status !== "planned" && feature.status !== "backlog") return false;
|
|
13128
13419
|
const isBlocked = feature.blockedBy.some((blocker) => {
|
|
@@ -13192,7 +13483,7 @@ function scoreRoadmapCandidates(roadmap, options) {
|
|
|
13192
13483
|
userCompletedFeatures
|
|
13193
13484
|
);
|
|
13194
13485
|
const weightedScore = POSITION_WEIGHT * positionScore + DEPENDENTS_WEIGHT * dependentsScore + AFFINITY_WEIGHT * affinityScore;
|
|
13195
|
-
const priorityTier = feature.priority ?
|
|
13486
|
+
const priorityTier = feature.priority ? PRIORITY_RANK2[feature.priority] : null;
|
|
13196
13487
|
candidates.push({
|
|
13197
13488
|
feature,
|
|
13198
13489
|
milestone: ms.name,
|
|
@@ -13232,9 +13523,550 @@ function assignFeature(roadmap, feature, assignee, date) {
|
|
|
13232
13523
|
date
|
|
13233
13524
|
});
|
|
13234
13525
|
}
|
|
13526
|
+
function scoreRoadmapCandidatesForMode(roadmap, options, config) {
|
|
13527
|
+
const mode = getRoadmapMode(config);
|
|
13528
|
+
if (mode === "file-less") {
|
|
13529
|
+
const flatFeatures = roadmap.milestones.flatMap((m) => m.features);
|
|
13530
|
+
const tracked = flatFeatures.map(featureToTrackedFeature);
|
|
13531
|
+
const fileLess = scoreRoadmapCandidatesFileLess(tracked, options);
|
|
13532
|
+
const featuresByName = new Map(flatFeatures.map((f) => [f.name, f]));
|
|
13533
|
+
return fileLess.map((c) => {
|
|
13534
|
+
const feature = featuresByName.get(c.feature.name);
|
|
13535
|
+
if (!feature) {
|
|
13536
|
+
throw new Error(`Internal: feature ${c.feature.name} lost during mode dispatch`);
|
|
13537
|
+
}
|
|
13538
|
+
return {
|
|
13539
|
+
feature,
|
|
13540
|
+
milestone: "",
|
|
13541
|
+
positionScore: 0,
|
|
13542
|
+
dependentsScore: 0,
|
|
13543
|
+
affinityScore: 0,
|
|
13544
|
+
weightedScore: 0,
|
|
13545
|
+
priorityTier: c.priorityTier
|
|
13546
|
+
};
|
|
13547
|
+
});
|
|
13548
|
+
}
|
|
13549
|
+
return scoreRoadmapCandidates(roadmap, options);
|
|
13550
|
+
}
|
|
13551
|
+
function featureToTrackedFeature(f) {
|
|
13552
|
+
return {
|
|
13553
|
+
externalId: f.externalId ?? "",
|
|
13554
|
+
name: f.name,
|
|
13555
|
+
status: f.status,
|
|
13556
|
+
summary: f.summary,
|
|
13557
|
+
spec: f.spec,
|
|
13558
|
+
plans: f.plans,
|
|
13559
|
+
blockedBy: f.blockedBy,
|
|
13560
|
+
assignee: f.assignee,
|
|
13561
|
+
priority: f.priority,
|
|
13562
|
+
milestone: null,
|
|
13563
|
+
createdAt: "1970-01-01T00:00:00Z",
|
|
13564
|
+
updatedAt: f.updatedAt
|
|
13565
|
+
};
|
|
13566
|
+
}
|
|
13567
|
+
function parseExternalId2(externalId) {
|
|
13568
|
+
const m = externalId.match(/^github:([^/]+)\/([^#]+)#(\d+)$/);
|
|
13569
|
+
if (!m) return null;
|
|
13570
|
+
return { owner: m[1], repo: m[2], number: parseInt(m[3], 10) };
|
|
13571
|
+
}
|
|
13572
|
+
function buildExternalId2(owner, repo, n) {
|
|
13573
|
+
return `github:${owner}/${repo}#${n}`;
|
|
13574
|
+
}
|
|
13575
|
+
function parseBodyBlock(body) {
|
|
13576
|
+
const startIdx = body.indexOf(START_MARKER);
|
|
13577
|
+
if (startIdx === -1) return { summary: body.trim(), meta: {} };
|
|
13578
|
+
const endIdx = body.indexOf(END_MARKER, startIdx + START_MARKER.length);
|
|
13579
|
+
if (endIdx === -1) return { summary: body.trim(), meta: {} };
|
|
13580
|
+
const secondStart = body.indexOf(START_MARKER, endIdx + END_MARKER.length);
|
|
13581
|
+
if (secondStart !== -1) {
|
|
13582
|
+
console.warn("harness-meta: multiple blocks found; first wins.");
|
|
13583
|
+
}
|
|
13584
|
+
const yamlText = body.slice(startIdx + START_MARKER.length, endIdx).trim();
|
|
13585
|
+
let meta = {};
|
|
13586
|
+
try {
|
|
13587
|
+
const parsed = parseYaml(yamlText);
|
|
13588
|
+
if (parsed && typeof parsed === "object") {
|
|
13589
|
+
meta = normalizeBodyMeta(parsed);
|
|
13590
|
+
}
|
|
13591
|
+
} catch (e) {
|
|
13592
|
+
console.warn(
|
|
13593
|
+
`harness-meta: malformed YAML, treating block as missing: ${e.message}`
|
|
13594
|
+
);
|
|
13595
|
+
meta = {};
|
|
13596
|
+
}
|
|
13597
|
+
const summary = body.slice(0, startIdx).trim();
|
|
13598
|
+
return { summary, meta };
|
|
13599
|
+
}
|
|
13600
|
+
function normalizeBodyMeta(raw) {
|
|
13601
|
+
const out = {};
|
|
13602
|
+
if (typeof raw.spec === "string") out.spec = raw.spec;
|
|
13603
|
+
if (typeof raw.plan === "string") out.plan = raw.plan;
|
|
13604
|
+
if (typeof raw.blocked_by === "string") {
|
|
13605
|
+
out.blocked_by = raw.blocked_by.split(",").map((s) => s.trim()).filter(Boolean);
|
|
13606
|
+
} else if (Array.isArray(raw.blocked_by)) {
|
|
13607
|
+
out.blocked_by = raw.blocked_by.filter(
|
|
13608
|
+
(v) => typeof v === "string"
|
|
13609
|
+
);
|
|
13610
|
+
}
|
|
13611
|
+
if (typeof raw.priority === "string" && ["P0", "P1", "P2", "P3"].includes(raw.priority)) {
|
|
13612
|
+
out.priority = raw.priority;
|
|
13613
|
+
}
|
|
13614
|
+
if (typeof raw.milestone === "string") out.milestone = raw.milestone;
|
|
13615
|
+
return out;
|
|
13616
|
+
}
|
|
13617
|
+
function serializeBodyBlock(summary, meta) {
|
|
13618
|
+
const ordered = {};
|
|
13619
|
+
for (const [key, yamlKey, mapValue] of SERIALIZER_FIELDS) {
|
|
13620
|
+
const raw = meta[key];
|
|
13621
|
+
if (raw === void 0 || raw === null) continue;
|
|
13622
|
+
const out = mapValue(raw);
|
|
13623
|
+
if (out === void 0) continue;
|
|
13624
|
+
ordered[yamlKey] = out;
|
|
13625
|
+
}
|
|
13626
|
+
const trimmed = summary.trim();
|
|
13627
|
+
if (Object.keys(ordered).length === 0) return trimmed;
|
|
13628
|
+
const yamlBody = stringifyYaml(ordered).trimEnd();
|
|
13629
|
+
const block = `${START_MARKER}
|
|
13630
|
+
${yamlBody}
|
|
13631
|
+
${END_MARKER}`;
|
|
13632
|
+
return trimmed.length > 0 ? `${trimmed}
|
|
13633
|
+
|
|
13634
|
+
${block}` : block;
|
|
13635
|
+
}
|
|
13636
|
+
function compareStatus(server, patch) {
|
|
13637
|
+
if (patch.status === void 0) return { idempotent: true };
|
|
13638
|
+
if (server.status === "done" && patch.status !== "done") {
|
|
13639
|
+
return { diff: { ours: patch.status, theirs: server.status }, idempotent: false };
|
|
13640
|
+
}
|
|
13641
|
+
return { idempotent: server.status === patch.status };
|
|
13642
|
+
}
|
|
13643
|
+
function compareAssignee(server, patch) {
|
|
13644
|
+
if (patch.assignee === void 0) return { idempotent: true };
|
|
13645
|
+
if (server.assignee !== null && server.assignee !== patch.assignee) {
|
|
13646
|
+
return { diff: { ours: patch.assignee, theirs: server.assignee }, idempotent: false };
|
|
13647
|
+
}
|
|
13648
|
+
return { idempotent: server.assignee === patch.assignee };
|
|
13649
|
+
}
|
|
13650
|
+
function compareScalar(field, server, patch) {
|
|
13651
|
+
const next = patch[field];
|
|
13652
|
+
if (next === void 0) return { idempotent: true };
|
|
13653
|
+
const cur = server[field];
|
|
13654
|
+
if (cur !== null && cur !== next) {
|
|
13655
|
+
return { diff: { ours: next, theirs: cur }, idempotent: false };
|
|
13656
|
+
}
|
|
13657
|
+
return { idempotent: cur === next };
|
|
13658
|
+
}
|
|
13659
|
+
function compareArray(field, server, patch) {
|
|
13660
|
+
const next = patch[field];
|
|
13661
|
+
if (next === void 0) return { idempotent: true };
|
|
13662
|
+
const cur = server[field];
|
|
13663
|
+
if (arraysEqual2(next, cur)) return { idempotent: true };
|
|
13664
|
+
if (cur.length > 0 && cur.some((v) => !next.includes(v))) {
|
|
13665
|
+
return { diff: { ours: next, theirs: cur }, idempotent: false };
|
|
13666
|
+
}
|
|
13667
|
+
return { idempotent: false };
|
|
13668
|
+
}
|
|
13669
|
+
function refetchAndCompare(server, patch) {
|
|
13670
|
+
const verdicts = [
|
|
13671
|
+
["status", compareStatus(server, patch)],
|
|
13672
|
+
["assignee", compareAssignee(server, patch)],
|
|
13673
|
+
["priority", compareScalar("priority", server, patch)],
|
|
13674
|
+
["milestone", compareScalar("milestone", server, patch)],
|
|
13675
|
+
["spec", compareScalar("spec", server, patch)],
|
|
13676
|
+
["plans", compareArray("plans", server, patch)],
|
|
13677
|
+
["blockedBy", compareArray("blockedBy", server, patch)]
|
|
13678
|
+
];
|
|
13679
|
+
const diff2 = {};
|
|
13680
|
+
let idempotent = true;
|
|
13681
|
+
for (const [field, verdict] of verdicts) {
|
|
13682
|
+
if (verdict.diff) diff2[field] = verdict.diff;
|
|
13683
|
+
if (!verdict.idempotent) idempotent = false;
|
|
13684
|
+
}
|
|
13685
|
+
if (Object.keys(diff2).length > 0) return { ok: false, diff: diff2 };
|
|
13686
|
+
return { ok: true, idempotent };
|
|
13687
|
+
}
|
|
13688
|
+
function arraysEqual2(a, b) {
|
|
13689
|
+
if (a.length !== b.length) return false;
|
|
13690
|
+
const sa = [...a].sort();
|
|
13691
|
+
const sb = [...b].sort();
|
|
13692
|
+
return sa.every((v, i) => v === sb[i]);
|
|
13693
|
+
}
|
|
13694
|
+
function metaFromFeatureFields(src) {
|
|
13695
|
+
const meta = {};
|
|
13696
|
+
if (src.spec !== void 0 && src.spec !== null) meta.spec = src.spec;
|
|
13697
|
+
if (src.plans && src.plans.length > 0) meta.plan = src.plans[0];
|
|
13698
|
+
if (src.blockedBy && src.blockedBy.length > 0) meta.blocked_by = src.blockedBy;
|
|
13699
|
+
if (src.priority !== void 0 && src.priority !== null) meta.priority = src.priority;
|
|
13700
|
+
if (src.milestone !== void 0 && src.milestone !== null) meta.milestone = src.milestone;
|
|
13701
|
+
return meta;
|
|
13702
|
+
}
|
|
13703
|
+
function buildCreateMeta(feature) {
|
|
13704
|
+
return metaFromFeatureFields(feature);
|
|
13705
|
+
}
|
|
13706
|
+
function createTrackerClient(config) {
|
|
13707
|
+
if (config.kind !== "github-issues") {
|
|
13708
|
+
return Err(new Error(`Unsupported tracker kind: "${String(config.kind)}"`));
|
|
13709
|
+
}
|
|
13710
|
+
const token = config.token ?? process.env.GITHUB_TOKEN;
|
|
13711
|
+
if (!token) {
|
|
13712
|
+
return Err(
|
|
13713
|
+
new Error("createTrackerClient: missing GitHub token (config.token or GITHUB_TOKEN env)")
|
|
13714
|
+
);
|
|
13715
|
+
}
|
|
13716
|
+
const opts = { token, repo: config.repo };
|
|
13717
|
+
if (config.apiBase !== void 0) opts.apiBase = config.apiBase;
|
|
13718
|
+
if (config.selectorLabel !== void 0) opts.selectorLabel = config.selectorLabel;
|
|
13719
|
+
if (config.etagStore !== void 0) opts.etagStore = config.etagStore;
|
|
13720
|
+
return Ok(new GitHubIssuesTrackerAdapter(opts));
|
|
13721
|
+
}
|
|
13722
|
+
function makeTrackerConflictBody(err, opts = {}) {
|
|
13723
|
+
return {
|
|
13724
|
+
error: err.message,
|
|
13725
|
+
code: "TRACKER_CONFLICT",
|
|
13726
|
+
externalId: err.externalId,
|
|
13727
|
+
conflictedWith: opts.conflictedWith ?? err.diff,
|
|
13728
|
+
refreshHint: "reload-roadmap"
|
|
13729
|
+
};
|
|
13730
|
+
}
|
|
13731
|
+
function loadProjectRoadmapMode(projectRoot) {
|
|
13732
|
+
try {
|
|
13733
|
+
const configPath = path31.join(projectRoot, "harness.config.json");
|
|
13734
|
+
if (!fs32.existsSync(configPath)) return getRoadmapMode(null);
|
|
13735
|
+
const raw = fs32.readFileSync(configPath, "utf-8");
|
|
13736
|
+
const parsed = JSON.parse(raw);
|
|
13737
|
+
return getRoadmapMode(parsed);
|
|
13738
|
+
} catch {
|
|
13739
|
+
return getRoadmapMode(null);
|
|
13740
|
+
}
|
|
13741
|
+
}
|
|
13742
|
+
function loadTrackerClientConfigFromProject(projectRoot) {
|
|
13743
|
+
try {
|
|
13744
|
+
const configPath = path32.join(projectRoot, "harness.config.json");
|
|
13745
|
+
if (!fs33.existsSync(configPath)) {
|
|
13746
|
+
return Err(new Error("harness.config.json not found"));
|
|
13747
|
+
}
|
|
13748
|
+
const cfg = JSON.parse(fs33.readFileSync(configPath, "utf-8"));
|
|
13749
|
+
const tracker = cfg.roadmap?.tracker;
|
|
13750
|
+
if (!tracker) {
|
|
13751
|
+
return Err(
|
|
13752
|
+
new Error(
|
|
13753
|
+
"file-less tracker config missing: set roadmap.tracker.kind in harness.config.json"
|
|
13754
|
+
)
|
|
13755
|
+
);
|
|
13756
|
+
}
|
|
13757
|
+
if (tracker.kind !== "github") {
|
|
13758
|
+
return Err(
|
|
13759
|
+
new Error(`file-less tracker only supports kind: "github" today; got "${tracker.kind}"`)
|
|
13760
|
+
);
|
|
13761
|
+
}
|
|
13762
|
+
if (!tracker.repo) {
|
|
13763
|
+
return Err(
|
|
13764
|
+
new Error(
|
|
13765
|
+
"roadmap.tracker.repo is required for file-less mode (set it in harness.config.json)"
|
|
13766
|
+
)
|
|
13767
|
+
);
|
|
13768
|
+
}
|
|
13769
|
+
return Ok({ kind: "github-issues", repo: tracker.repo });
|
|
13770
|
+
} catch (e) {
|
|
13771
|
+
return Err(e instanceof Error ? e : new Error(String(e)));
|
|
13772
|
+
}
|
|
13773
|
+
}
|
|
13774
|
+
function bodyMetaMatches(a, b) {
|
|
13775
|
+
return eqOpt(a.spec, b.spec) && eqOpt(a.plan, b.plan) && eqOpt(a.priority, b.priority) && eqOpt(a.milestone, b.milestone) && eqList(a.blocked_by, b.blocked_by);
|
|
13776
|
+
}
|
|
13777
|
+
function eqOpt(x, y) {
|
|
13778
|
+
const xx = x ?? null;
|
|
13779
|
+
const yy = y ?? null;
|
|
13780
|
+
return xx === yy;
|
|
13781
|
+
}
|
|
13782
|
+
function eqList(x, y) {
|
|
13783
|
+
const xx = (x ?? []).slice().sort();
|
|
13784
|
+
const yy = (y ?? []).slice().sort();
|
|
13785
|
+
if (xx.length !== yy.length) return false;
|
|
13786
|
+
for (let i = 0; i < xx.length; i++) if (xx[i] !== yy[i]) return false;
|
|
13787
|
+
return true;
|
|
13788
|
+
}
|
|
13789
|
+
function normalizeAt(at) {
|
|
13790
|
+
if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/.test(at)) return at;
|
|
13791
|
+
const d = new Date(at);
|
|
13792
|
+
if (Number.isNaN(d.getTime())) return at;
|
|
13793
|
+
const seconds = Math.floor(d.getTime() / 1e3) * 1e3;
|
|
13794
|
+
return new Date(seconds).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
13795
|
+
}
|
|
13796
|
+
function hashHistoryEvent(event) {
|
|
13797
|
+
const detailsStr = JSON.stringify(event.details ?? {});
|
|
13798
|
+
const at = normalizeAt(event.at);
|
|
13799
|
+
const input = `${event.type}|${event.actor}|${at}|${detailsStr}`;
|
|
13800
|
+
return createHash4("sha256").update(input).digest("hex").slice(0, 8);
|
|
13801
|
+
}
|
|
13802
|
+
function parseHashFromCommentBody(commentBody) {
|
|
13803
|
+
const match = commentBody.match(HASH_RE);
|
|
13804
|
+
return match ? match[1] : null;
|
|
13805
|
+
}
|
|
13806
|
+
function buildHistoryCommentBody(event) {
|
|
13807
|
+
const hash = hashHistoryEvent(event);
|
|
13808
|
+
return `<!-- harness-history hash:${hash} -->
|
|
13809
|
+
${JSON.stringify(event)}`;
|
|
13810
|
+
}
|
|
13811
|
+
async function buildMigrationPlan(roadmap, existingFeatures, fetchHistoryHashes, getRawBodyForExternalId) {
|
|
13812
|
+
const plan = {
|
|
13813
|
+
toCreate: [],
|
|
13814
|
+
toUpdate: [],
|
|
13815
|
+
unchanged: [],
|
|
13816
|
+
historyToAppend: [],
|
|
13817
|
+
ambiguous: []
|
|
13818
|
+
};
|
|
13819
|
+
const byExternalId = /* @__PURE__ */ new Map();
|
|
13820
|
+
const byNameLower = /* @__PURE__ */ new Map();
|
|
13821
|
+
for (const t of existingFeatures) {
|
|
13822
|
+
byExternalId.set(t.externalId, t);
|
|
13823
|
+
byNameLower.set(t.name.toLowerCase(), t);
|
|
13824
|
+
}
|
|
13825
|
+
const featureToExternalId = /* @__PURE__ */ new Map();
|
|
13826
|
+
for (const ms of roadmap.milestones) {
|
|
13827
|
+
for (const f of ms.features) {
|
|
13828
|
+
if (f.externalId) featureToExternalId.set(f.name, f.externalId);
|
|
13829
|
+
}
|
|
13830
|
+
}
|
|
13831
|
+
for (const ms of roadmap.milestones) {
|
|
13832
|
+
for (const feature of ms.features) {
|
|
13833
|
+
if (feature.externalId == null) {
|
|
13834
|
+
const existing = byNameLower.get(feature.name.toLowerCase());
|
|
13835
|
+
if (existing) {
|
|
13836
|
+
plan.ambiguous.push({ name: feature.name, existingIssueRef: existing.externalId });
|
|
13837
|
+
} else {
|
|
13838
|
+
plan.toCreate.push({
|
|
13839
|
+
name: feature.name,
|
|
13840
|
+
input: featureToNewInput(feature, ms)
|
|
13841
|
+
});
|
|
13842
|
+
}
|
|
13843
|
+
continue;
|
|
13844
|
+
}
|
|
13845
|
+
const tracked = byExternalId.get(feature.externalId);
|
|
13846
|
+
if (!tracked) {
|
|
13847
|
+
plan.ambiguous.push({
|
|
13848
|
+
name: feature.name,
|
|
13849
|
+
existingIssueRef: `${feature.externalId} (recorded external id not found)`
|
|
13850
|
+
});
|
|
13851
|
+
continue;
|
|
13852
|
+
}
|
|
13853
|
+
const expectedMeta = featureToExpectedMeta(feature, ms);
|
|
13854
|
+
const rawBody = await getRawBodyForExternalId(feature.externalId);
|
|
13855
|
+
const actualMeta = rawBody == null ? {} : parseBodyBlock(rawBody).meta;
|
|
13856
|
+
if (rawBody != null && bodyMetaMatches(actualMeta, expectedMeta)) {
|
|
13857
|
+
plan.unchanged.push({ externalId: feature.externalId, name: feature.name });
|
|
13858
|
+
} else {
|
|
13859
|
+
const diff2 = formatDiff(actualMeta, expectedMeta);
|
|
13860
|
+
const patch = metaToPatch(feature, expectedMeta);
|
|
13861
|
+
plan.toUpdate.push({
|
|
13862
|
+
externalId: feature.externalId,
|
|
13863
|
+
name: feature.name,
|
|
13864
|
+
patch,
|
|
13865
|
+
diff: diff2
|
|
13866
|
+
});
|
|
13867
|
+
}
|
|
13868
|
+
}
|
|
13869
|
+
}
|
|
13870
|
+
for (const record of roadmap.assignmentHistory) {
|
|
13871
|
+
const externalId = featureToExternalId.get(record.feature);
|
|
13872
|
+
if (!externalId) {
|
|
13873
|
+
console.warn(
|
|
13874
|
+
`harness-migrate: cannot resolve external id for assignment-history feature "${record.feature}"; skipping.`
|
|
13875
|
+
);
|
|
13876
|
+
continue;
|
|
13877
|
+
}
|
|
13878
|
+
const type = mapAction(record.action);
|
|
13879
|
+
const event = {
|
|
13880
|
+
type,
|
|
13881
|
+
actor: record.assignee,
|
|
13882
|
+
at: record.date
|
|
13883
|
+
};
|
|
13884
|
+
const hash = hashHistoryEvent(event);
|
|
13885
|
+
const existingHashes = await fetchHistoryHashes(externalId);
|
|
13886
|
+
if (existingHashes.has(hash)) continue;
|
|
13887
|
+
plan.historyToAppend.push({ externalId, event, hash });
|
|
13888
|
+
}
|
|
13889
|
+
return plan;
|
|
13890
|
+
}
|
|
13891
|
+
function featureToExpectedMeta(feature, milestone) {
|
|
13892
|
+
const meta = {};
|
|
13893
|
+
if (feature.spec != null) meta.spec = feature.spec;
|
|
13894
|
+
if (feature.plans.length > 0) meta.plan = feature.plans[0] ?? null;
|
|
13895
|
+
if (feature.blockedBy.length > 0) meta.blocked_by = feature.blockedBy.slice().sort();
|
|
13896
|
+
if (feature.priority != null) meta.priority = feature.priority;
|
|
13897
|
+
if (!milestone.isBacklog) meta.milestone = milestone.name;
|
|
13898
|
+
return meta;
|
|
13899
|
+
}
|
|
13900
|
+
function featureToNewInput(feature, milestone) {
|
|
13901
|
+
const input = {
|
|
13902
|
+
name: feature.name,
|
|
13903
|
+
summary: feature.summary,
|
|
13904
|
+
status: feature.status,
|
|
13905
|
+
spec: feature.spec,
|
|
13906
|
+
plans: feature.plans,
|
|
13907
|
+
blockedBy: feature.blockedBy,
|
|
13908
|
+
priority: feature.priority,
|
|
13909
|
+
milestone: milestone.isBacklog ? null : milestone.name,
|
|
13910
|
+
assignee: feature.assignee
|
|
13911
|
+
};
|
|
13912
|
+
return input;
|
|
13913
|
+
}
|
|
13914
|
+
function metaToPatch(feature, expected) {
|
|
13915
|
+
const patch = {};
|
|
13916
|
+
patch.summary = feature.summary;
|
|
13917
|
+
if (expected.spec !== void 0) patch.spec = expected.spec ?? null;
|
|
13918
|
+
if (expected.plan !== void 0 && expected.plan != null) patch.plans = [expected.plan];
|
|
13919
|
+
if (expected.blocked_by !== void 0) patch.blockedBy = expected.blocked_by ?? [];
|
|
13920
|
+
if (expected.priority !== void 0) patch.priority = expected.priority ?? null;
|
|
13921
|
+
if (expected.milestone !== void 0) patch.milestone = expected.milestone ?? null;
|
|
13922
|
+
return patch;
|
|
13923
|
+
}
|
|
13924
|
+
function formatDiff(actual, expected) {
|
|
13925
|
+
const keys = /* @__PURE__ */ new Set();
|
|
13926
|
+
const collect = (m) => {
|
|
13927
|
+
for (const k of Object.keys(m)) {
|
|
13928
|
+
const v = m[k];
|
|
13929
|
+
if (v !== void 0 && v !== null && !(Array.isArray(v) && v.length === 0)) keys.add(k);
|
|
13930
|
+
}
|
|
13931
|
+
};
|
|
13932
|
+
collect(actual);
|
|
13933
|
+
collect(expected);
|
|
13934
|
+
const changed = [];
|
|
13935
|
+
for (const key of [...keys].sort()) {
|
|
13936
|
+
const a = actual[key];
|
|
13937
|
+
const e = expected[key];
|
|
13938
|
+
if (JSON.stringify(a ?? null) !== JSON.stringify(e ?? null)) changed.push(key);
|
|
13939
|
+
}
|
|
13940
|
+
return changed.join(",");
|
|
13941
|
+
}
|
|
13942
|
+
function mapAction(action) {
|
|
13943
|
+
switch (action) {
|
|
13944
|
+
case "assigned":
|
|
13945
|
+
return "claimed";
|
|
13946
|
+
case "completed":
|
|
13947
|
+
return "completed";
|
|
13948
|
+
case "unassigned":
|
|
13949
|
+
return "released";
|
|
13950
|
+
}
|
|
13951
|
+
}
|
|
13952
|
+
async function runMigrationPlan(plan, deps, opts) {
|
|
13953
|
+
const projectRoot = opts.projectRoot;
|
|
13954
|
+
const roadmapPath = path33.join(projectRoot, "docs", "roadmap.md");
|
|
13955
|
+
const archivedPath = path33.join(projectRoot, "docs", "roadmap.md.archived");
|
|
13956
|
+
const configPath = path33.join(projectRoot, "harness.config.json");
|
|
13957
|
+
const configBackupPath = path33.join(projectRoot, "harness.config.json.pre-migration");
|
|
13958
|
+
const createdSoFar = [];
|
|
13959
|
+
const report = {
|
|
13960
|
+
created: 0,
|
|
13961
|
+
updated: 0,
|
|
13962
|
+
unchanged: plan.unchanged.length,
|
|
13963
|
+
historyAppended: 0,
|
|
13964
|
+
archivedFrom: null,
|
|
13965
|
+
archivedTo: null,
|
|
13966
|
+
configBackup: null,
|
|
13967
|
+
mode: "applied"
|
|
13968
|
+
};
|
|
13969
|
+
if (plan.ambiguous.length > 0) {
|
|
13970
|
+
const names = plan.ambiguous.map((a) => a.name).join(", ");
|
|
13971
|
+
return Ok({
|
|
13972
|
+
...report,
|
|
13973
|
+
mode: "aborted",
|
|
13974
|
+
abortReason: `ambiguous features (title-collision or dangling external-id): ${names}`
|
|
13975
|
+
});
|
|
13976
|
+
}
|
|
13977
|
+
if (opts.dryRun) {
|
|
13978
|
+
return Ok({ ...report, mode: "dry-run" });
|
|
13979
|
+
}
|
|
13980
|
+
for (const entry of plan.toCreate) {
|
|
13981
|
+
const r = await deps.client.create(entry.input);
|
|
13982
|
+
if (!r.ok) {
|
|
13983
|
+
return Ok({
|
|
13984
|
+
...report,
|
|
13985
|
+
created: createdSoFar.length,
|
|
13986
|
+
createdSoFar,
|
|
13987
|
+
mode: "aborted",
|
|
13988
|
+
abortReason: `create failed for "${entry.name}": ${r.error.message}`
|
|
13989
|
+
});
|
|
13990
|
+
}
|
|
13991
|
+
createdSoFar.push({ name: entry.name, externalId: r.value.externalId });
|
|
13992
|
+
}
|
|
13993
|
+
report.created = createdSoFar.length;
|
|
13994
|
+
for (const entry of plan.toUpdate) {
|
|
13995
|
+
const r = await deps.client.update(entry.externalId, entry.patch);
|
|
13996
|
+
if (!r.ok) {
|
|
13997
|
+
return Ok({
|
|
13998
|
+
...report,
|
|
13999
|
+
createdSoFar,
|
|
14000
|
+
mode: "aborted",
|
|
14001
|
+
abortReason: `update failed for "${entry.name}" (${entry.externalId}): ${r.error.message}`
|
|
14002
|
+
});
|
|
14003
|
+
}
|
|
14004
|
+
report.updated++;
|
|
14005
|
+
}
|
|
14006
|
+
for (const entry of plan.historyToAppend) {
|
|
14007
|
+
const r = await deps.client.appendHistory(entry.externalId, entry.event);
|
|
14008
|
+
if (!r.ok) {
|
|
14009
|
+
return Ok({
|
|
14010
|
+
...report,
|
|
14011
|
+
createdSoFar,
|
|
14012
|
+
mode: "aborted",
|
|
14013
|
+
abortReason: `appendHistory failed for ${entry.externalId} (${entry.event.type}): ${r.error.message}`
|
|
14014
|
+
});
|
|
14015
|
+
}
|
|
14016
|
+
report.historyAppended++;
|
|
14017
|
+
}
|
|
14018
|
+
if (deps.existsFile(archivedPath)) {
|
|
14019
|
+
return Ok({
|
|
14020
|
+
...report,
|
|
14021
|
+
createdSoFar,
|
|
14022
|
+
mode: "aborted",
|
|
14023
|
+
abortReason: `archive-collision: ${archivedPath} already exists; refusing to overwrite`
|
|
14024
|
+
});
|
|
14025
|
+
}
|
|
14026
|
+
if (deps.existsFile(roadmapPath)) {
|
|
14027
|
+
try {
|
|
14028
|
+
deps.renameFile(roadmapPath, archivedPath);
|
|
14029
|
+
report.archivedFrom = roadmapPath;
|
|
14030
|
+
report.archivedTo = archivedPath;
|
|
14031
|
+
} catch (err) {
|
|
14032
|
+
return Ok({
|
|
14033
|
+
...report,
|
|
14034
|
+
createdSoFar,
|
|
14035
|
+
mode: "aborted",
|
|
14036
|
+
abortReason: `archive failed: ${err.message}`
|
|
14037
|
+
});
|
|
14038
|
+
}
|
|
14039
|
+
}
|
|
14040
|
+
try {
|
|
14041
|
+
if (!deps.existsFile(configPath)) {
|
|
14042
|
+
return Ok({
|
|
14043
|
+
...report,
|
|
14044
|
+
createdSoFar,
|
|
14045
|
+
mode: "aborted",
|
|
14046
|
+
abortReason: `config rewrite failed: ${configPath} not found`
|
|
14047
|
+
});
|
|
14048
|
+
}
|
|
14049
|
+
const original = deps.readFile(configPath);
|
|
14050
|
+
deps.writeFile(configBackupPath, original);
|
|
14051
|
+
report.configBackup = configBackupPath;
|
|
14052
|
+
const parsed = JSON.parse(original);
|
|
14053
|
+
const roadmapSection = parsed.roadmap ?? {};
|
|
14054
|
+
roadmapSection.mode = "file-less";
|
|
14055
|
+
parsed.roadmap = roadmapSection;
|
|
14056
|
+
deps.writeFile(configPath, JSON.stringify(parsed, null, 2) + "\n");
|
|
14057
|
+
} catch (err) {
|
|
14058
|
+
return Ok({
|
|
14059
|
+
...report,
|
|
14060
|
+
createdSoFar,
|
|
14061
|
+
mode: "aborted",
|
|
14062
|
+
abortReason: `config rewrite failed: ${err.message}`
|
|
14063
|
+
});
|
|
14064
|
+
}
|
|
14065
|
+
return Ok(report);
|
|
14066
|
+
}
|
|
13235
14067
|
function getStatePath() {
|
|
13236
14068
|
const home = process.env["HOME"] || os.homedir();
|
|
13237
|
-
return
|
|
14069
|
+
return path36.join(home, ".harness", "update-check.json");
|
|
13238
14070
|
}
|
|
13239
14071
|
function isUpdateCheckEnabled(configInterval) {
|
|
13240
14072
|
if (process.env["HARNESS_NO_UPDATE_CHECK"] === "1") return false;
|
|
@@ -13247,7 +14079,7 @@ function shouldRunCheck(state, intervalMs) {
|
|
|
13247
14079
|
}
|
|
13248
14080
|
function readCheckState() {
|
|
13249
14081
|
try {
|
|
13250
|
-
const raw =
|
|
14082
|
+
const raw = fs36.readFileSync(getStatePath(), "utf-8");
|
|
13251
14083
|
const parsed = JSON.parse(raw);
|
|
13252
14084
|
if (typeof parsed === "object" && parsed !== null && "lastCheckTime" in parsed && typeof parsed.lastCheckTime === "number" && "currentVersion" in parsed && typeof parsed.currentVersion === "string") {
|
|
13253
14085
|
const state = parsed;
|
|
@@ -13264,7 +14096,7 @@ function readCheckState() {
|
|
|
13264
14096
|
}
|
|
13265
14097
|
function spawnBackgroundCheck(currentVersion) {
|
|
13266
14098
|
const statePath = getStatePath();
|
|
13267
|
-
const stateDir =
|
|
14099
|
+
const stateDir = path36.dirname(statePath);
|
|
13268
14100
|
const script = `
|
|
13269
14101
|
const { execSync } = require('child_process');
|
|
13270
14102
|
const fs = require('fs');
|
|
@@ -13507,14 +14339,14 @@ function getModelPrice(model, dataset) {
|
|
|
13507
14339
|
return pricing;
|
|
13508
14340
|
}
|
|
13509
14341
|
function getCachePath(projectRoot) {
|
|
13510
|
-
return
|
|
14342
|
+
return path37.join(projectRoot, ".harness", "cache", "pricing.json");
|
|
13511
14343
|
}
|
|
13512
14344
|
function getStalenessMarkerPath(projectRoot) {
|
|
13513
|
-
return
|
|
14345
|
+
return path37.join(projectRoot, ".harness", "cache", "staleness-marker.json");
|
|
13514
14346
|
}
|
|
13515
14347
|
async function readDiskCache(projectRoot) {
|
|
13516
14348
|
try {
|
|
13517
|
-
const raw = await
|
|
14349
|
+
const raw = await fs37.readFile(getCachePath(projectRoot), "utf-8");
|
|
13518
14350
|
return JSON.parse(raw);
|
|
13519
14351
|
} catch {
|
|
13520
14352
|
return null;
|
|
@@ -13522,8 +14354,8 @@ async function readDiskCache(projectRoot) {
|
|
|
13522
14354
|
}
|
|
13523
14355
|
async function writeDiskCache(projectRoot, data) {
|
|
13524
14356
|
const cachePath = getCachePath(projectRoot);
|
|
13525
|
-
await
|
|
13526
|
-
await
|
|
14357
|
+
await fs37.mkdir(path37.dirname(cachePath), { recursive: true });
|
|
14358
|
+
await fs37.writeFile(cachePath, JSON.stringify(data, null, 2));
|
|
13527
14359
|
}
|
|
13528
14360
|
async function fetchFromNetwork() {
|
|
13529
14361
|
try {
|
|
@@ -13550,7 +14382,7 @@ function loadFallbackDataset() {
|
|
|
13550
14382
|
async function checkAndWarnStaleness(projectRoot) {
|
|
13551
14383
|
const markerPath = getStalenessMarkerPath(projectRoot);
|
|
13552
14384
|
try {
|
|
13553
|
-
const raw = await
|
|
14385
|
+
const raw = await fs37.readFile(markerPath, "utf-8");
|
|
13554
14386
|
const marker = JSON.parse(raw);
|
|
13555
14387
|
const firstUse = new Date(marker.firstFallbackUse).getTime();
|
|
13556
14388
|
const now = Date.now();
|
|
@@ -13562,8 +14394,8 @@ async function checkAndWarnStaleness(projectRoot) {
|
|
|
13562
14394
|
}
|
|
13563
14395
|
} catch {
|
|
13564
14396
|
try {
|
|
13565
|
-
await
|
|
13566
|
-
await
|
|
14397
|
+
await fs37.mkdir(path37.dirname(markerPath), { recursive: true });
|
|
14398
|
+
await fs37.writeFile(
|
|
13567
14399
|
markerPath,
|
|
13568
14400
|
JSON.stringify({ firstFallbackUse: (/* @__PURE__ */ new Date()).toISOString() })
|
|
13569
14401
|
);
|
|
@@ -13573,7 +14405,7 @@ async function checkAndWarnStaleness(projectRoot) {
|
|
|
13573
14405
|
}
|
|
13574
14406
|
async function clearStalenessMarker(projectRoot) {
|
|
13575
14407
|
try {
|
|
13576
|
-
await
|
|
14408
|
+
await fs37.unlink(getStalenessMarkerPath(projectRoot));
|
|
13577
14409
|
} catch {
|
|
13578
14410
|
}
|
|
13579
14411
|
}
|
|
@@ -13810,10 +14642,10 @@ function parseLine(line, lineNumber) {
|
|
|
13810
14642
|
return record;
|
|
13811
14643
|
}
|
|
13812
14644
|
function readCostRecords(projectRoot) {
|
|
13813
|
-
const costsFile =
|
|
14645
|
+
const costsFile = path38.join(projectRoot, ".harness", "metrics", "costs.jsonl");
|
|
13814
14646
|
let raw;
|
|
13815
14647
|
try {
|
|
13816
|
-
raw =
|
|
14648
|
+
raw = fs38.readFileSync(costsFile, "utf-8");
|
|
13817
14649
|
} catch {
|
|
13818
14650
|
return [];
|
|
13819
14651
|
}
|
|
@@ -13863,7 +14695,7 @@ function parseCCLine(line, filePath, lineNumber) {
|
|
|
13863
14695
|
entry = JSON.parse(line);
|
|
13864
14696
|
} catch {
|
|
13865
14697
|
console.warn(
|
|
13866
|
-
`[harness usage] Skipping malformed CC JSONL line ${lineNumber} in ${
|
|
14698
|
+
`[harness usage] Skipping malformed CC JSONL line ${lineNumber} in ${path39.basename(filePath)}`
|
|
13867
14699
|
);
|
|
13868
14700
|
return null;
|
|
13869
14701
|
}
|
|
@@ -13877,7 +14709,7 @@ function parseCCLine(line, filePath, lineNumber) {
|
|
|
13877
14709
|
function readCCFile(filePath) {
|
|
13878
14710
|
let raw;
|
|
13879
14711
|
try {
|
|
13880
|
-
raw =
|
|
14712
|
+
raw = fs39.readFileSync(filePath, "utf-8");
|
|
13881
14713
|
} catch {
|
|
13882
14714
|
return [];
|
|
13883
14715
|
}
|
|
@@ -13899,10 +14731,10 @@ function readCCFile(filePath) {
|
|
|
13899
14731
|
}
|
|
13900
14732
|
function parseCCRecords() {
|
|
13901
14733
|
const homeDir = process.env.HOME ?? os2.homedir();
|
|
13902
|
-
const projectsDir =
|
|
14734
|
+
const projectsDir = path39.join(homeDir, ".claude", "projects");
|
|
13903
14735
|
let projectDirs;
|
|
13904
14736
|
try {
|
|
13905
|
-
projectDirs =
|
|
14737
|
+
projectDirs = fs39.readdirSync(projectsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => path39.join(projectsDir, d.name));
|
|
13906
14738
|
} catch {
|
|
13907
14739
|
return [];
|
|
13908
14740
|
}
|
|
@@ -13910,7 +14742,7 @@ function parseCCRecords() {
|
|
|
13910
14742
|
for (const dir of projectDirs) {
|
|
13911
14743
|
let files;
|
|
13912
14744
|
try {
|
|
13913
|
-
files =
|
|
14745
|
+
files = fs39.readdirSync(dir).filter((f) => f.endsWith(".jsonl")).map((f) => path39.join(dir, f));
|
|
13914
14746
|
} catch {
|
|
13915
14747
|
continue;
|
|
13916
14748
|
}
|
|
@@ -13938,10 +14770,10 @@ function parseLine2(line, lineNumber) {
|
|
|
13938
14770
|
}
|
|
13939
14771
|
}
|
|
13940
14772
|
function readAdoptionRecords(projectRoot) {
|
|
13941
|
-
const adoptionFile =
|
|
14773
|
+
const adoptionFile = path40.join(projectRoot, ".harness", "metrics", "adoption.jsonl");
|
|
13942
14774
|
let raw;
|
|
13943
14775
|
try {
|
|
13944
|
-
raw =
|
|
14776
|
+
raw = fs40.readFileSync(adoptionFile, "utf-8");
|
|
13945
14777
|
} catch {
|
|
13946
14778
|
return [];
|
|
13947
14779
|
}
|
|
@@ -14089,25 +14921,25 @@ function resolveStability(contentType) {
|
|
|
14089
14921
|
return STABILITY_LOOKUP[contentType] ?? "ephemeral";
|
|
14090
14922
|
}
|
|
14091
14923
|
function getOrCreateInstallId(projectRoot) {
|
|
14092
|
-
const harnessDir =
|
|
14093
|
-
const installIdFile =
|
|
14924
|
+
const harnessDir = path41.join(projectRoot, ".harness");
|
|
14925
|
+
const installIdFile = path41.join(harnessDir, ".install-id");
|
|
14094
14926
|
const UUID_V4_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
14095
14927
|
try {
|
|
14096
|
-
const existing =
|
|
14928
|
+
const existing = fs41.readFileSync(installIdFile, "utf-8").trim();
|
|
14097
14929
|
if (UUID_V4_RE.test(existing)) {
|
|
14098
14930
|
return existing;
|
|
14099
14931
|
}
|
|
14100
14932
|
} catch {
|
|
14101
14933
|
}
|
|
14102
14934
|
const id = crypto4.randomUUID();
|
|
14103
|
-
|
|
14104
|
-
|
|
14935
|
+
fs41.mkdirSync(harnessDir, { recursive: true });
|
|
14936
|
+
fs41.writeFileSync(installIdFile, id, { encoding: "utf-8", mode: 384 });
|
|
14105
14937
|
return id;
|
|
14106
14938
|
}
|
|
14107
14939
|
function parseIdentityFromTelemetryFile(filePath) {
|
|
14108
14940
|
const identity = {};
|
|
14109
14941
|
try {
|
|
14110
|
-
const raw =
|
|
14942
|
+
const raw = fs42.readFileSync(filePath, "utf-8");
|
|
14111
14943
|
const parsed = JSON.parse(raw);
|
|
14112
14944
|
const src = parsed?.identity;
|
|
14113
14945
|
if (!src || typeof src !== "object") return identity;
|
|
@@ -14120,7 +14952,7 @@ function parseIdentityFromTelemetryFile(filePath) {
|
|
|
14120
14952
|
}
|
|
14121
14953
|
function readProjectNameFallback(configPath) {
|
|
14122
14954
|
try {
|
|
14123
|
-
const raw =
|
|
14955
|
+
const raw = fs42.readFileSync(configPath, "utf-8");
|
|
14124
14956
|
const config = JSON.parse(raw);
|
|
14125
14957
|
return typeof config?.name === "string" ? config.name : void 0;
|
|
14126
14958
|
} catch {
|
|
@@ -14141,10 +14973,10 @@ function readGitAliasFallback(cwd) {
|
|
|
14141
14973
|
}
|
|
14142
14974
|
}
|
|
14143
14975
|
function readIdentity(projectRoot) {
|
|
14144
|
-
const filePath =
|
|
14976
|
+
const filePath = path42.join(projectRoot, ".harness", "telemetry.json");
|
|
14145
14977
|
const identity = parseIdentityFromTelemetryFile(filePath);
|
|
14146
14978
|
if (!identity.project) {
|
|
14147
|
-
const fallbackProject = readProjectNameFallback(
|
|
14979
|
+
const fallbackProject = readProjectNameFallback(path42.join(projectRoot, "harness.config.json"));
|
|
14148
14980
|
if (fallbackProject) identity.project = fallbackProject;
|
|
14149
14981
|
}
|
|
14150
14982
|
if (!identity.alias) {
|
|
@@ -14191,7 +15023,7 @@ function collectEvents(projectRoot, consent) {
|
|
|
14191
15023
|
);
|
|
14192
15024
|
}
|
|
14193
15025
|
function sleep3(ms) {
|
|
14194
|
-
return new Promise((
|
|
15026
|
+
return new Promise((resolve10) => setTimeout(resolve10, ms));
|
|
14195
15027
|
}
|
|
14196
15028
|
async function send(events, apiKey) {
|
|
14197
15029
|
if (events.length === 0) return;
|
|
@@ -14221,12 +15053,12 @@ function acquireCompoundLock(category, opts = {}) {
|
|
|
14221
15053
|
);
|
|
14222
15054
|
}
|
|
14223
15055
|
const cwd = opts.cwd ?? process.cwd();
|
|
14224
|
-
const lockDir =
|
|
14225
|
-
|
|
14226
|
-
const lockPath =
|
|
15056
|
+
const lockDir = path43.join(cwd, ".harness", "locks");
|
|
15057
|
+
fs43.mkdirSync(lockDir, { recursive: true });
|
|
15058
|
+
const lockPath = path43.join(lockDir, `compound-${category}.lock`);
|
|
14227
15059
|
let fd;
|
|
14228
15060
|
try {
|
|
14229
|
-
fd =
|
|
15061
|
+
fd = fs43.openSync(lockPath, "wx");
|
|
14230
15062
|
} catch (e) {
|
|
14231
15063
|
const err = e;
|
|
14232
15064
|
if (err.code === "EEXIST") {
|
|
@@ -14235,14 +15067,14 @@ function acquireCompoundLock(category, opts = {}) {
|
|
|
14235
15067
|
}
|
|
14236
15068
|
throw err;
|
|
14237
15069
|
}
|
|
14238
|
-
|
|
14239
|
-
|
|
15070
|
+
fs43.writeSync(fd, String(process.pid));
|
|
15071
|
+
fs43.closeSync(fd);
|
|
14240
15072
|
let released = false;
|
|
14241
15073
|
const release = () => {
|
|
14242
15074
|
if (released) return;
|
|
14243
15075
|
released = true;
|
|
14244
15076
|
try {
|
|
14245
|
-
|
|
15077
|
+
fs43.unlinkSync(lockPath);
|
|
14246
15078
|
} catch {
|
|
14247
15079
|
}
|
|
14248
15080
|
process.removeListener("exit", onExit);
|
|
@@ -14268,7 +15100,7 @@ function acquireCompoundLock(category, opts = {}) {
|
|
|
14268
15100
|
}
|
|
14269
15101
|
function readHolderPid(lockPath) {
|
|
14270
15102
|
try {
|
|
14271
|
-
const raw =
|
|
15103
|
+
const raw = fs43.readFileSync(lockPath, "utf-8").trim();
|
|
14272
15104
|
const n = Number.parseInt(raw, 10);
|
|
14273
15105
|
return Number.isFinite(n) ? n : -1;
|
|
14274
15106
|
} catch {
|
|
@@ -14293,10 +15125,10 @@ function assertSanitized(value) {
|
|
|
14293
15125
|
}
|
|
14294
15126
|
function writePulseConfig(config, opts) {
|
|
14295
15127
|
PulseConfigSchema.parse(config);
|
|
14296
|
-
if (!
|
|
15128
|
+
if (!fs44.existsSync(opts.configPath)) {
|
|
14297
15129
|
throw new Error(`harness.config.json not found at ${opts.configPath}`);
|
|
14298
15130
|
}
|
|
14299
|
-
const raw =
|
|
15131
|
+
const raw = fs44.readFileSync(opts.configPath, "utf-8");
|
|
14300
15132
|
let parsed;
|
|
14301
15133
|
try {
|
|
14302
15134
|
parsed = JSON.parse(raw);
|
|
@@ -14304,18 +15136,18 @@ function writePulseConfig(config, opts) {
|
|
|
14304
15136
|
throw new Error(`Invalid JSON in ${opts.configPath}: ${e.message}`, { cause: e });
|
|
14305
15137
|
}
|
|
14306
15138
|
const bakPath = `${opts.configPath}.bak`;
|
|
14307
|
-
if (!opts.skipBackup && !
|
|
14308
|
-
|
|
15139
|
+
if (!opts.skipBackup && !fs44.existsSync(bakPath)) {
|
|
15140
|
+
fs44.writeFileSync(bakPath, raw, "utf-8");
|
|
14309
15141
|
}
|
|
14310
15142
|
parsed.pulse = config;
|
|
14311
15143
|
const serialized = JSON.stringify(parsed, null, 2) + "\n";
|
|
14312
15144
|
const tmpPath = `${opts.configPath}.tmp-${process.pid}`;
|
|
14313
|
-
|
|
15145
|
+
fs44.writeFileSync(tmpPath, serialized, "utf-8");
|
|
14314
15146
|
try {
|
|
14315
|
-
|
|
15147
|
+
fs44.renameSync(tmpPath, opts.configPath);
|
|
14316
15148
|
} catch (e) {
|
|
14317
15149
|
try {
|
|
14318
|
-
|
|
15150
|
+
fs44.unlinkSync(tmpPath);
|
|
14319
15151
|
} catch {
|
|
14320
15152
|
}
|
|
14321
15153
|
throw e;
|
|
@@ -14323,12 +15155,12 @@ function writePulseConfig(config, opts) {
|
|
|
14323
15155
|
}
|
|
14324
15156
|
function seedFromStrategy(opts = {}) {
|
|
14325
15157
|
const cwd = opts.cwd ?? process.cwd();
|
|
14326
|
-
const strategyPath =
|
|
15158
|
+
const strategyPath = path44.join(cwd, "STRATEGY.md");
|
|
14327
15159
|
const warnings = [];
|
|
14328
|
-
if (!
|
|
15160
|
+
if (!fs45.existsSync(strategyPath)) {
|
|
14329
15161
|
return { name: null, keyMetrics: [], warnings: ["STRATEGY.md not found"] };
|
|
14330
15162
|
}
|
|
14331
|
-
const raw =
|
|
15163
|
+
const raw = fs45.readFileSync(strategyPath, "utf-8");
|
|
14332
15164
|
let name = null;
|
|
14333
15165
|
const fmMatch = /^---\s*\n([\s\S]*?)\n---\s*\n/.exec(raw);
|
|
14334
15166
|
const fm = fmMatch?.[1];
|
|
@@ -14480,7 +15312,7 @@ function loadTemplate() {
|
|
|
14480
15312
|
const url = import.meta?.url;
|
|
14481
15313
|
if (!url) return INLINE_TEMPLATE;
|
|
14482
15314
|
const here = dirname18(fileURLToPath(url));
|
|
14483
|
-
return
|
|
15315
|
+
return readFileSync38(join612(here, "template.md"), "utf-8");
|
|
14484
15316
|
} catch {
|
|
14485
15317
|
return INLINE_TEMPLATE;
|
|
14486
15318
|
}
|
|
@@ -14669,11 +15501,11 @@ async function computeHotspots(opts) {
|
|
|
14669
15501
|
}
|
|
14670
15502
|
const counts = /* @__PURE__ */ new Map();
|
|
14671
15503
|
for (const line of stdout.split("\n")) {
|
|
14672
|
-
const
|
|
14673
|
-
if (
|
|
14674
|
-
counts.set(
|
|
15504
|
+
const path46 = line.trim();
|
|
15505
|
+
if (path46.length === 0) continue;
|
|
15506
|
+
counts.set(path46, (counts.get(path46) ?? 0) + 1);
|
|
14675
15507
|
}
|
|
14676
|
-
return [...counts.entries()].filter(([, c]) => c > opts.threshold).map(([
|
|
15508
|
+
return [...counts.entries()].filter(([, c]) => c > opts.threshold).map(([path46, churn]) => ({ path: path46, churn })).sort((a, b) => b.churn - a.churn);
|
|
14677
15509
|
}
|
|
14678
15510
|
function tokenize(text) {
|
|
14679
15511
|
return new Set(
|
|
@@ -14683,12 +15515,12 @@ function tokenize(text) {
|
|
|
14683
15515
|
async function* walk22(dir) {
|
|
14684
15516
|
let entries;
|
|
14685
15517
|
try {
|
|
14686
|
-
entries = await
|
|
15518
|
+
entries = await fs46.readdir(dir, { withFileTypes: true });
|
|
14687
15519
|
} catch {
|
|
14688
15520
|
return;
|
|
14689
15521
|
}
|
|
14690
15522
|
for (const e of entries) {
|
|
14691
|
-
const p =
|
|
15523
|
+
const p = path45.join(dir, e.name);
|
|
14692
15524
|
if (e.isDirectory()) yield* walk22(p);
|
|
14693
15525
|
else if (e.isFile() && e.name.endsWith(".md")) yield p;
|
|
14694
15526
|
}
|
|
@@ -14697,11 +15529,11 @@ async function readDocumentedTokens(solutionsDir) {
|
|
|
14697
15529
|
const docs = [];
|
|
14698
15530
|
for (const [track, categories] of TRACK_CATEGORIES2) {
|
|
14699
15531
|
for (const category of categories) {
|
|
14700
|
-
const dir =
|
|
15532
|
+
const dir = path45.join(solutionsDir, track, category);
|
|
14701
15533
|
for await (const file of walk22(dir)) {
|
|
14702
|
-
const raw = await
|
|
15534
|
+
const raw = await fs46.readFile(file, "utf-8");
|
|
14703
15535
|
const m = /^#\s+(.+)$/m.exec(raw);
|
|
14704
|
-
const title = m?.[1] ??
|
|
15536
|
+
const title = m?.[1] ?? path45.basename(file, ".md");
|
|
14705
15537
|
docs.push(tokenize(title));
|
|
14706
15538
|
}
|
|
14707
15539
|
}
|
|
@@ -14764,17 +15596,112 @@ function assembleCandidateReport(input) {
|
|
|
14764
15596
|
}
|
|
14765
15597
|
return lines.join("\n");
|
|
14766
15598
|
}
|
|
14767
|
-
var VALID_TYPES, CONVENTIONAL_PATTERN, LOOKBACK_PATTERN, PulseDbSourceSchema, PulseSourcesSchema, PulseConfigSchema, BUG_TRACK_CATEGORIES, KNOWLEDGE_TRACK_CATEGORIES, ALL_SOLUTION_CATEGORIES, ISO_DATE, BugTrackSchema, KnowledgeTrackSchema, BaseFrontmatter, SolutionDocFrontmatterSchema, TRACK_CATEGORIES, DEFAULT_AGNIX_TIMEOUT_MS, HARNESS_AGNIX_DISABLE, HARNESS_AGNIX_BIN, CLAUDE_MD_WARN_BYTES, CLAUDE_MD_ERROR_BYTES, CONTEXT_FILES, MIN_DESCRIPTION_LENGTH, SETTINGS_PATHS, KNOWN_HOOK_EVENTS, MCP_SETTINGS_FILES, REQUIRED_SECTIONS, SETTINGS_FILES, KNOWN_AGNIX_TOOLS, DEFAULT_SECTIONS, DEFAULT_RATIOS, NODE_TYPE_TO_CATEGORY, SECTION_LEVEL_MAP, DEFAULT_LEVEL, DEFAULT_BUDGET, DEFAULT_LOADER_CONFIG, LEVEL_FRACTIONS, PHASE_PRIORITIES, ManifestSchema, BundleConstraintsSchema, BundleSchema, ContributionsSchema, LockfilePackageSchema, LockfileSchema, SharableLayerSchema, SharableForbiddenImportSchema, SharableBoundaryConfigSchema, SharableSecurityRulesSchema, VALID_SCOPES, LINE_PATTERN, START_PATTERN, END_PATTERN, CODE_BLOCK_LANGUAGES, DEFAULT_DRIFT_CONFIG, JS_EXT_FALLBACKS2, FILE_TYPES, EXPORT_TYPES, CONVENTION_DESCRIPTIONS, RULE_CHECKERS, EntropyAnalyzer, readFile62, writeFile32, unlink22, mkdir22, copyFile2, DEFAULT_FIX_CONFIG, EXPORT_TYPE_KEYWORD, ALWAYS_UNSAFE_TYPES, idCounter, DEAD_CODE_FIX_ACTIONS, MustExportRuleSchema, MustExportDefaultRuleSchema, NoExportRuleSchema, MustImportRuleSchema, NoImportRuleSchema, NamingRuleSchema, MaxExportsRuleSchema, MaxLinesRuleSchema, RequireJSDocRuleSchema, RuleSchema, ConfigPatternSchema, PatternConfigSchema, DriftConfigSchema, DeadCodeConfigSchema, EntropyConfigSchema, BaselineManager, BenchmarkRunner, RegressionDetector,
|
|
15599
|
+
var WHATWG_BAD_PORTS, BAD_PORT_SET, VALID_TYPES, CONVENTIONAL_PATTERN, LOOKBACK_PATTERN, PulseDbSourceSchema, PulseSourcesSchema, PulseConfigSchema, BUG_TRACK_CATEGORIES, KNOWLEDGE_TRACK_CATEGORIES, ALL_SOLUTION_CATEGORIES, ISO_DATE, BugTrackSchema, KnowledgeTrackSchema, BaseFrontmatter, SolutionDocFrontmatterSchema, TRACK_CATEGORIES, DEFAULT_AGNIX_TIMEOUT_MS, HARNESS_AGNIX_DISABLE, HARNESS_AGNIX_BIN, CLAUDE_MD_WARN_BYTES, CLAUDE_MD_ERROR_BYTES, CONTEXT_FILES, MIN_DESCRIPTION_LENGTH, SETTINGS_PATHS, KNOWN_HOOK_EVENTS, MCP_SETTINGS_FILES, REQUIRED_SECTIONS, SETTINGS_FILES, KNOWN_AGNIX_TOOLS, DEFAULT_SECTIONS, DEFAULT_RATIOS, NODE_TYPE_TO_CATEGORY, SECTION_LEVEL_MAP, DEFAULT_LEVEL, DEFAULT_BUDGET, DEFAULT_LOADER_CONFIG, LEVEL_FRACTIONS, PHASE_PRIORITIES, ManifestSchema, BundleConstraintsSchema, BundleSchema, ContributionsSchema, LockfilePackageSchema, LockfileSchema, SharableLayerSchema, SharableForbiddenImportSchema, SharableBoundaryConfigSchema, SharableSecurityRulesSchema, VALID_SCOPES, LINE_PATTERN, START_PATTERN, END_PATTERN, TS_HINTS, TS_CONVENTIONS, PYTHON_HINTS, PYTHON_CONVENTIONS, DEFAULT_INCLUDE_PATTERNS, CODE_BLOCK_LANGUAGES, DEFAULT_DRIFT_CONFIG, JS_EXT_FALLBACKS2, FILE_TYPES, EXPORT_TYPES, CONVENTION_DESCRIPTIONS, RULE_CHECKERS, EntropyAnalyzer, readFile62, writeFile32, unlink22, mkdir22, copyFile2, DEFAULT_FIX_CONFIG, EXPORT_TYPE_KEYWORD, ALWAYS_UNSAFE_TYPES, idCounter, DEAD_CODE_FIX_ACTIONS, MustExportRuleSchema, MustExportDefaultRuleSchema, NoExportRuleSchema, MustImportRuleSchema, NoImportRuleSchema, NamingRuleSchema, MaxExportsRuleSchema, MaxLinesRuleSchema, RequireJSDocRuleSchema, RuleSchema, ConfigPatternSchema, PatternConfigSchema, DriftConfigSchema, DeadCodeConfigSchema, EntropyConfigSchema, BaselineManager, BenchmarkRunner, RegressionDetector, SOURCE_EXTENSIONS, FUNCTION_DECL_RE, CONST_DECL_RE, CriticalPathResolver, NoOpTelemetryAdapter, NoOpExecutor, ConsoleSink, feedbackConfig, ChecklistBuilder, AgentActionEmitter, globalEmitter, FileSink, NoOpSink, CategorySnapshotSchema, TimelineSnapshotSchema, TimelineFileSchema, TrendLineSchema, TrendResultSchema, DEFAULT_STABILITY_THRESHOLDS, ALL_CATEGORIES, TimelineManager, ConfidenceTierSchema, RegressionResultSchema, DirectionSchema, CategoryForecastSchema, SpecImpactSignalsSchema, SpecImpactEstimateSchema, ContributingFeatureSchema, AdjustedForecastSchema, PredictionWarningSchema, StabilityForecastSchema, PredictionResultSchema, PredictionOptionsSchema, VALID_STATUSES, EM_DASH, VALID_PRIORITIES, ALL_CATEGORIES2, DIRECTION_THRESHOLD, PredictionEngine, DEFAULT_COEFFICIENTS, SpecImpactEstimator, EMPTY_HISTORY, ViolationHistoryManager, NORMALIZERS, MAX_SAMPLES, DEFAULT_WINDOW_WEEKS, DEFAULT_MIN_OCCURRENCES, FailureEntrySchema, HandoffSchema, GateCheckSchema, GateResultSchema, GateConfigSchema, HarnessStateSchema, DEFAULT_STATE, StreamInfoSchema, StreamIndexSchema, DEFAULT_STREAM_INDEX, HARNESS_DIR, STATE_FILE, LEARNINGS_FILE, FAILURES_FILE, HANDOFF_FILE, GATE_CONFIG_FILE, INDEX_FILE, SESSIONS_DIR, SESSION_INDEX_FILE, SUMMARY_FILE, SESSION_STATE_FILE, ARCHIVE_DIR, CONTENT_HASHES_FILE, EVENTS_FILE, STREAMS_DIR, STREAM_NAME_REGEX, branchCache, BRANCH_CACHE_TTL_MS, STATE_FILES, MAX_CACHE_ENTRIES, learningsCacheMap, WEIGHTS, LOCK_RETRIES, LOCK_BACKOFFS, PROMOTABLE_OUTCOMES, failuresCacheMap, FAILURE_LINE_REGEX, SAFE_GATE_COMMAND, SkillEventSchema, knownHashesCache, EVENT_FORMATTERS, RuleRegistry, DEFAULT_SECURITY_CONFIG, RuleOverrideSchema, SecurityConfigSchema, secretRules, injectionRules, xssRules, cryptoRules, pathTraversalRules, networkRules, deserializationRules, agentConfigRules, mcpRules, insecureDefaultsRules, sharpEdgesRules, nodeRules, expressRules, reactRules, goRules, SecurityScanner, hiddenUnicodePatterns, reRolingPatterns, permissionEscalationPatterns, encodedPayloadPatterns, indirectInjectionPatterns, contextManipulationPatterns, socialEngineeringPatterns, suspiciousPatterns, ALL_PATTERNS, DESTRUCTIVE_BASH, TAINT_DURATION_MS, DEFAULT_SESSION_ID, SecurityCategorySnapshotSchema, SupplyChainSnapshotSchema, SecurityTimelineSnapshotSchema, FindingLifecycleSchema, SecurityTimelineFileSchema, DirectionSchema2, SecurityTrendLineSchema, TrendAttributionSchema, SecurityTrendResultSchema, TimeToFixStatsSchema, TimeToFixResultSchema, EMPTY_SUPPLY_CHAIN, SecurityTimelineManager, ALL_CHECKS, STATUS_ICONS, CINotifier, ExclusionSet, PREFIX_PATTERNS, TEST_FILE_PATTERN, MD_FILE_PATTERN, ALL_DOMAINS, SECURITY_PATTERNS, JS_EXT_FALLBACKS22, SEVERITY_RANK, SEVERITY_ORDER, SEVERITY_LABELS, VALIDATED_BY_RANK, VALIDATION_SCORES, DOMAIN_BASELINES, FACTOR_WEIGHTS, EVIDENCE_SATURATION, CORROBORATED_AGREEMENT, STANDALONE_AGREEMENT, AGREEMENT_LINE_GAP, COMPLIANCE_DESCRIPTOR, EXPORT_RE, BUG_DETECTION_DESCRIPTOR, SECURITY_DESCRIPTOR, EVAL_PATTERN, SECRET_PATTERNS, SQL_CONCAT_PATTERN, SHELL_EXEC_PATTERN, ARCHITECTURE_DESCRIPTOR, LARGE_FILE_THRESHOLD, VIOLATION_FILE_RE, LEARNINGS_DESCRIPTOR, RELEVANCE_THRESHOLD, AGENT_DESCRIPTORS, AGENT_RUNNERS, DOWNGRADE_MAP, DEFAULT_PROVIDER_TIERS, SMALL_SUGGESTION_LINE_LIMIT, FILE_LINE_RANGE_PATTERN, FILE_LINE_PATTERN, FILE_ONLY_PATTERN, SECURITY_SENSITIVE_PATH, MIGRATION_PATH, TEST_PATH, CONFIG_PATH, STAGE_DOMAINS, EM_DASH2, STATUS_RANK, RETRY_DEFAULTS, GitHubIssuesSyncAdapter, syncMutex, PRIORITY_RANK, PRIORITY_RANK2, POSITION_WEIGHT, DEPENDENTS_WEIGHT, AFFINITY_WEIGHT, ConflictError, DEFAULTS, GitHubHttp, ETagStore, START_MARKER, END_MARKER, SERIALIZER_FIELDS, GitHubIssuesTrackerAdapter, migrate_exports, HASH_RE, InteractionTypeSchema, QuestionSchema, ConfirmationSchema, TransitionSchema, EmitInteractionInputSchema, ProjectScanner, SHELL_TEMPLATE, STYLES, SCRIPTS, MockLLMService, llmService, ContentPipeline, BlueprintGenerator, TOKENS_PER_MILLION, fallback_default, LITELLM_PRICING_URL, CACHE_TTL_MS, STALENESS_WARNING_DAYS, MICRODOLLARS_PER_DOLLAR, TOKENS_PER_MILLION2, StructuralStrategy, DEFAULT_TOKEN_BUDGET, CHARS_PER_TOKEN, TRUNCATION_MARKER, TruncationStrategy, CompactionPipeline, STABILITY_LOOKUP, TIER_ORDER, AnthropicCacheAdapter, TIER_ORDER2, OpenAICacheAdapter, TIER_ORDER3, CACHED_CONTENT_MARKER, GeminiCacheAdapter, VERSION, POSTHOG_BATCH_URL, MAX_ATTEMPTS, TIMEOUT_MS, CompoundLockHeldError, KNOWN_CATEGORIES, ALLOWED_FIELD_KEYS, PII_TOKENS, PII_FIELD_DENYLIST, PII_LINE_RE, ALLOWED_SET, PulseAdapterAlreadyRegisteredError, REGISTRY, MOCK_ADAPTER_NAME, ALLOWED_FIELD_SET, adapter, LOOKBACK_RE, MAX_LINES, INLINE_TEMPLATE, TRUNCATION_MARKER2, execFileAsync, FIX_RE, EMPTY_REPO_RE, execFileAsync2, TRACK_CATEGORIES2, STOPWORDS, KEYWORD_TO_CATEGORY;
|
|
14768
15600
|
var init_dist3 = __esm({
|
|
14769
15601
|
"../core/dist/index.mjs"() {
|
|
14770
|
-
|
|
15602
|
+
init_chunk_4UI65RLE();
|
|
14771
15603
|
init_dist();
|
|
15604
|
+
init_dist2();
|
|
15605
|
+
init_dist2();
|
|
15606
|
+
init_dist2();
|
|
14772
15607
|
init_dist();
|
|
14773
15608
|
init_dist();
|
|
14774
15609
|
init_dist2();
|
|
15610
|
+
init_dist2();
|
|
15611
|
+
init_dist2();
|
|
15612
|
+
init_dist2();
|
|
15613
|
+
init_dist();
|
|
15614
|
+
init_dist();
|
|
15615
|
+
init_dist();
|
|
15616
|
+
init_dist();
|
|
14775
15617
|
init_dist();
|
|
14776
15618
|
init_dist();
|
|
14777
15619
|
init_dist2();
|
|
15620
|
+
WHATWG_BAD_PORTS = Object.freeze([
|
|
15621
|
+
1,
|
|
15622
|
+
7,
|
|
15623
|
+
9,
|
|
15624
|
+
11,
|
|
15625
|
+
13,
|
|
15626
|
+
15,
|
|
15627
|
+
17,
|
|
15628
|
+
19,
|
|
15629
|
+
20,
|
|
15630
|
+
21,
|
|
15631
|
+
22,
|
|
15632
|
+
23,
|
|
15633
|
+
25,
|
|
15634
|
+
37,
|
|
15635
|
+
42,
|
|
15636
|
+
43,
|
|
15637
|
+
53,
|
|
15638
|
+
69,
|
|
15639
|
+
77,
|
|
15640
|
+
79,
|
|
15641
|
+
87,
|
|
15642
|
+
95,
|
|
15643
|
+
101,
|
|
15644
|
+
102,
|
|
15645
|
+
103,
|
|
15646
|
+
104,
|
|
15647
|
+
109,
|
|
15648
|
+
110,
|
|
15649
|
+
111,
|
|
15650
|
+
113,
|
|
15651
|
+
115,
|
|
15652
|
+
117,
|
|
15653
|
+
119,
|
|
15654
|
+
123,
|
|
15655
|
+
135,
|
|
15656
|
+
137,
|
|
15657
|
+
139,
|
|
15658
|
+
143,
|
|
15659
|
+
161,
|
|
15660
|
+
179,
|
|
15661
|
+
389,
|
|
15662
|
+
427,
|
|
15663
|
+
465,
|
|
15664
|
+
512,
|
|
15665
|
+
513,
|
|
15666
|
+
514,
|
|
15667
|
+
515,
|
|
15668
|
+
526,
|
|
15669
|
+
530,
|
|
15670
|
+
531,
|
|
15671
|
+
532,
|
|
15672
|
+
540,
|
|
15673
|
+
548,
|
|
15674
|
+
554,
|
|
15675
|
+
556,
|
|
15676
|
+
563,
|
|
15677
|
+
587,
|
|
15678
|
+
601,
|
|
15679
|
+
636,
|
|
15680
|
+
989,
|
|
15681
|
+
990,
|
|
15682
|
+
993,
|
|
15683
|
+
995,
|
|
15684
|
+
1719,
|
|
15685
|
+
1720,
|
|
15686
|
+
1723,
|
|
15687
|
+
2049,
|
|
15688
|
+
3659,
|
|
15689
|
+
4045,
|
|
15690
|
+
4190,
|
|
15691
|
+
5060,
|
|
15692
|
+
5061,
|
|
15693
|
+
6e3,
|
|
15694
|
+
6566,
|
|
15695
|
+
6665,
|
|
15696
|
+
6666,
|
|
15697
|
+
6667,
|
|
15698
|
+
6668,
|
|
15699
|
+
6669,
|
|
15700
|
+
6679,
|
|
15701
|
+
6697,
|
|
15702
|
+
10080
|
|
15703
|
+
]);
|
|
15704
|
+
BAD_PORT_SET = new Set(WHATWG_BAD_PORTS);
|
|
14778
15705
|
VALID_TYPES = [
|
|
14779
15706
|
"feat",
|
|
14780
15707
|
"fix",
|
|
@@ -15089,6 +16016,32 @@ var init_dist3 = __esm({
|
|
|
15089
16016
|
LINE_PATTERN = /^[ \t]*(?:\/\/|#)\s*harness-ignore(?!\s*(?:SEC-|-start|-end))(?:\s+([\w,]+))?(?::\s*(.+))?$/;
|
|
15090
16017
|
START_PATTERN = /^[ \t]*(?:\/\/|#)\s*harness-ignore-start(?:\s+([\w,]+))?(?::\s*(.+))?$/;
|
|
15091
16018
|
END_PATTERN = /^[ \t]*(?:\/\/|#)\s*harness-ignore-end\s*$/;
|
|
16019
|
+
TS_HINTS = ['Add "exports" or "main" to package.json', "Create src/index.ts"];
|
|
16020
|
+
TS_CONVENTIONS = ["src/index.ts", "src/main.ts", "src/index.tsx", "index.ts", "main.ts"];
|
|
16021
|
+
PYTHON_HINTS = [
|
|
16022
|
+
"Add an entry to [project.scripts] in pyproject.toml",
|
|
16023
|
+
"Create main.py or <package>/__main__.py"
|
|
16024
|
+
];
|
|
16025
|
+
PYTHON_CONVENTIONS = [
|
|
16026
|
+
"__main__.py",
|
|
16027
|
+
"main.py",
|
|
16028
|
+
"app.py",
|
|
16029
|
+
"src/__main__.py",
|
|
16030
|
+
"src/main.py",
|
|
16031
|
+
"src/app.py"
|
|
16032
|
+
];
|
|
16033
|
+
DEFAULT_INCLUDE_PATTERNS = [
|
|
16034
|
+
"**/*.ts",
|
|
16035
|
+
"**/*.tsx",
|
|
16036
|
+
"**/*.js",
|
|
16037
|
+
"**/*.jsx",
|
|
16038
|
+
"**/*.mjs",
|
|
16039
|
+
"**/*.cjs",
|
|
16040
|
+
"**/*.py",
|
|
16041
|
+
"**/*.go",
|
|
16042
|
+
"**/*.rs",
|
|
16043
|
+
"**/*.java"
|
|
16044
|
+
];
|
|
15092
16045
|
CODE_BLOCK_LANGUAGES = /* @__PURE__ */ new Set(["typescript", "ts", "javascript", "js"]);
|
|
15093
16046
|
DEFAULT_DRIFT_CONFIG = {
|
|
15094
16047
|
docPaths: [],
|
|
@@ -15127,10 +16080,7 @@ var init_dist3 = __esm({
|
|
|
15127
16080
|
snapshot;
|
|
15128
16081
|
report;
|
|
15129
16082
|
constructor(config) {
|
|
15130
|
-
this.config = {
|
|
15131
|
-
...config,
|
|
15132
|
-
parser: config.parser || new TypeScriptParser()
|
|
15133
|
-
};
|
|
16083
|
+
this.config = { ...config };
|
|
15134
16084
|
}
|
|
15135
16085
|
/**
|
|
15136
16086
|
* Run full entropy analysis.
|
|
@@ -15356,11 +16306,11 @@ var init_dist3 = __esm({
|
|
|
15356
16306
|
return detectPatternViolations(snapshotResult.value, config);
|
|
15357
16307
|
}
|
|
15358
16308
|
};
|
|
15359
|
-
readFile62 = promisify2(
|
|
15360
|
-
writeFile32 = promisify2(
|
|
15361
|
-
unlink22 = promisify2(
|
|
15362
|
-
mkdir22 = promisify2(
|
|
15363
|
-
copyFile2 = promisify2(
|
|
16309
|
+
readFile62 = promisify2(fs6.readFile);
|
|
16310
|
+
writeFile32 = promisify2(fs6.writeFile);
|
|
16311
|
+
unlink22 = promisify2(fs6.unlink);
|
|
16312
|
+
mkdir22 = promisify2(fs6.mkdir);
|
|
16313
|
+
copyFile2 = promisify2(fs6.copyFile);
|
|
15364
16314
|
DEFAULT_FIX_CONFIG = {
|
|
15365
16315
|
dryRun: false,
|
|
15366
16316
|
fixTypes: ["unused-imports", "dead-files"],
|
|
@@ -15482,14 +16432,14 @@ var init_dist3 = __esm({
|
|
|
15482
16432
|
BaselineManager = class {
|
|
15483
16433
|
baselinesPath;
|
|
15484
16434
|
constructor(projectRoot) {
|
|
15485
|
-
this.baselinesPath =
|
|
16435
|
+
this.baselinesPath = join18(projectRoot, ".harness", "perf", "baselines.json");
|
|
15486
16436
|
}
|
|
15487
16437
|
/**
|
|
15488
16438
|
* Load the baselines file from disk.
|
|
15489
16439
|
* Returns null if the file does not exist or contains invalid JSON.
|
|
15490
16440
|
*/
|
|
15491
16441
|
load() {
|
|
15492
|
-
if (!
|
|
16442
|
+
if (!existsSync12(this.baselinesPath)) {
|
|
15493
16443
|
return null;
|
|
15494
16444
|
}
|
|
15495
16445
|
try {
|
|
@@ -15523,7 +16473,7 @@ var init_dist3 = __esm({
|
|
|
15523
16473
|
benchmarks
|
|
15524
16474
|
};
|
|
15525
16475
|
const dir = dirname8(this.baselinesPath);
|
|
15526
|
-
if (!
|
|
16476
|
+
if (!existsSync12(dir)) {
|
|
15527
16477
|
mkdirSync2(dir, { recursive: true });
|
|
15528
16478
|
}
|
|
15529
16479
|
writeFileSync2(this.baselinesPath, JSON.stringify(file, null, 2));
|
|
@@ -15722,7 +16672,6 @@ var init_dist3 = __esm({
|
|
|
15722
16672
|
};
|
|
15723
16673
|
}
|
|
15724
16674
|
};
|
|
15725
|
-
SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", "dist", ".git"]);
|
|
15726
16675
|
SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx"]);
|
|
15727
16676
|
FUNCTION_DECL_RE = /(?:export\s+)?(?:async\s+)?function\s+(\w+)/;
|
|
15728
16677
|
CONST_DECL_RE = /(?:export\s+)?(?:const|let)\s+(\w+)\s*=/;
|
|
@@ -15757,16 +16706,16 @@ var init_dist3 = __esm({
|
|
|
15757
16706
|
walkDir(dir, entries) {
|
|
15758
16707
|
let items;
|
|
15759
16708
|
try {
|
|
15760
|
-
items =
|
|
16709
|
+
items = fs7.readdirSync(dir, { withFileTypes: true });
|
|
15761
16710
|
} catch {
|
|
15762
16711
|
return;
|
|
15763
16712
|
}
|
|
15764
16713
|
for (const item of items) {
|
|
15765
16714
|
if (item.isDirectory()) {
|
|
15766
|
-
if (
|
|
15767
|
-
this.walkDir(
|
|
15768
|
-
} else if (item.isFile() && SOURCE_EXTENSIONS.has(
|
|
15769
|
-
this.scanFile(
|
|
16715
|
+
if (DEFAULT_SKIP_DIRS.has(item.name)) continue;
|
|
16716
|
+
this.walkDir(path4.join(dir, item.name), entries);
|
|
16717
|
+
} else if (item.isFile() && SOURCE_EXTENSIONS.has(path4.extname(item.name))) {
|
|
16718
|
+
this.scanFile(path4.join(dir, item.name), entries);
|
|
15770
16719
|
}
|
|
15771
16720
|
}
|
|
15772
16721
|
}
|
|
@@ -15781,12 +16730,12 @@ var init_dist3 = __esm({
|
|
|
15781
16730
|
scanFile(filePath, entries) {
|
|
15782
16731
|
let content;
|
|
15783
16732
|
try {
|
|
15784
|
-
content =
|
|
16733
|
+
content = fs7.readFileSync(filePath, "utf-8");
|
|
15785
16734
|
} catch {
|
|
15786
16735
|
return;
|
|
15787
16736
|
}
|
|
15788
16737
|
const lines = content.split("\n");
|
|
15789
|
-
const relativePath =
|
|
16738
|
+
const relativePath = path4.relative(this.projectRoot, filePath).replace(/\\/g, "/");
|
|
15790
16739
|
for (let i = 0; i < lines.length; i++) {
|
|
15791
16740
|
if (!lines[i].includes("@perf-critical")) continue;
|
|
15792
16741
|
const fnName = this.resolveFunctionName(lines, i + 1);
|
|
@@ -16126,7 +17075,7 @@ var init_dist3 = __esm({
|
|
|
16126
17075
|
ensureDirectory() {
|
|
16127
17076
|
if (!this.initialized) {
|
|
16128
17077
|
const dir = dirname9(this.filePath);
|
|
16129
|
-
if (!
|
|
17078
|
+
if (!existsSync13(dir)) {
|
|
16130
17079
|
mkdirSync22(dir, { recursive: true });
|
|
16131
17080
|
}
|
|
16132
17081
|
this.initialized = true;
|
|
@@ -16157,7 +17106,7 @@ var init_dist3 = __esm({
|
|
|
16157
17106
|
this.ensureDirectory();
|
|
16158
17107
|
const content = this.buffer.join("");
|
|
16159
17108
|
this.buffer = [];
|
|
16160
|
-
if (this.options.mode === "overwrite" && !
|
|
17109
|
+
if (this.options.mode === "overwrite" && !existsSync13(this.filePath)) {
|
|
16161
17110
|
writeFileSync22(this.filePath, content);
|
|
16162
17111
|
} else {
|
|
16163
17112
|
appendFileSync(this.filePath, content);
|
|
@@ -16239,14 +17188,14 @@ var init_dist3 = __esm({
|
|
|
16239
17188
|
TimelineManager = class {
|
|
16240
17189
|
timelinePath;
|
|
16241
17190
|
constructor(rootDir) {
|
|
16242
|
-
this.timelinePath =
|
|
17191
|
+
this.timelinePath = join20(rootDir, ".harness", "arch", "timeline.json");
|
|
16243
17192
|
}
|
|
16244
17193
|
/**
|
|
16245
17194
|
* Load timeline from disk.
|
|
16246
17195
|
* Returns empty TimelineFile if file does not exist or is invalid.
|
|
16247
17196
|
*/
|
|
16248
17197
|
load() {
|
|
16249
|
-
if (!
|
|
17198
|
+
if (!existsSync14(this.timelinePath)) {
|
|
16250
17199
|
return { version: 1, snapshots: [] };
|
|
16251
17200
|
}
|
|
16252
17201
|
try {
|
|
@@ -16272,7 +17221,7 @@ var init_dist3 = __esm({
|
|
|
16272
17221
|
*/
|
|
16273
17222
|
save(timeline) {
|
|
16274
17223
|
const dir = dirname10(this.timelinePath);
|
|
16275
|
-
if (!
|
|
17224
|
+
if (!existsSync14(dir)) {
|
|
16276
17225
|
mkdirSync3(dir, { recursive: true });
|
|
16277
17226
|
}
|
|
16278
17227
|
const tmp = this.timelinePath + "." + randomBytes2(4).toString("hex") + ".tmp";
|
|
@@ -16829,8 +17778,8 @@ var init_dist3 = __esm({
|
|
|
16829
17778
|
return null;
|
|
16830
17779
|
}
|
|
16831
17780
|
try {
|
|
16832
|
-
const roadmapPath =
|
|
16833
|
-
const raw =
|
|
17781
|
+
const roadmapPath = path5.join(this.rootDir, "roadmap.md");
|
|
17782
|
+
const raw = fs8.readFileSync(roadmapPath, "utf-8");
|
|
16834
17783
|
const parseResult = parseRoadmap(raw);
|
|
16835
17784
|
if (!parseResult.ok) return null;
|
|
16836
17785
|
const features = parseResult.value.milestones.flatMap(
|
|
@@ -16865,8 +17814,8 @@ var init_dist3 = __esm({
|
|
|
16865
17814
|
* @param specPath - Relative path from rootDir to the spec file.
|
|
16866
17815
|
*/
|
|
16867
17816
|
estimate(specPath) {
|
|
16868
|
-
const absolutePath =
|
|
16869
|
-
const content =
|
|
17817
|
+
const absolutePath = path6.join(this.rootDir, specPath);
|
|
17818
|
+
const content = fs9.readFileSync(absolutePath, "utf-8");
|
|
16870
17819
|
const newFileCount = this.extractNewFileCount(content);
|
|
16871
17820
|
const affectedLayers = this.extractAffectedLayers(content);
|
|
16872
17821
|
const newDependencies = this.extractNewDependencies(content);
|
|
@@ -16878,7 +17827,7 @@ var init_dist3 = __esm({
|
|
|
16878
17827
|
phaseCount
|
|
16879
17828
|
);
|
|
16880
17829
|
const h1Match = content.match(/^#\s+(.+)$/m);
|
|
16881
|
-
const featureName = h1Match ? h1Match[1].trim() :
|
|
17830
|
+
const featureName = h1Match ? h1Match[1].trim() : path6.basename(specPath, ".md");
|
|
16882
17831
|
return {
|
|
16883
17832
|
specPath,
|
|
16884
17833
|
featureName,
|
|
@@ -16899,8 +17848,8 @@ var init_dist3 = __esm({
|
|
|
16899
17848
|
const results = [];
|
|
16900
17849
|
for (const feature of features) {
|
|
16901
17850
|
if (!feature.spec) continue;
|
|
16902
|
-
const absolutePath =
|
|
16903
|
-
if (!
|
|
17851
|
+
const absolutePath = path6.join(this.rootDir, feature.spec);
|
|
17852
|
+
if (!fs9.existsSync(absolutePath)) continue;
|
|
16904
17853
|
const estimate = this.estimate(feature.spec);
|
|
16905
17854
|
results.push({ ...estimate, featureName: feature.name });
|
|
16906
17855
|
}
|
|
@@ -16928,8 +17877,8 @@ var init_dist3 = __esm({
|
|
|
16928
17877
|
}
|
|
16929
17878
|
let count = 0;
|
|
16930
17879
|
for (const fp of filePaths) {
|
|
16931
|
-
const absolute =
|
|
16932
|
-
if (!
|
|
17880
|
+
const absolute = path6.join(this.rootDir, fp);
|
|
17881
|
+
if (!fs9.existsSync(absolute)) {
|
|
16933
17882
|
count++;
|
|
16934
17883
|
}
|
|
16935
17884
|
}
|
|
@@ -16999,8 +17948,8 @@ var init_dist3 = __esm({
|
|
|
16999
17948
|
// --- Private: Config Loading ---
|
|
17000
17949
|
loadLayerNames() {
|
|
17001
17950
|
try {
|
|
17002
|
-
const configPath =
|
|
17003
|
-
const raw =
|
|
17951
|
+
const configPath = path6.join(this.rootDir, "harness.config.json");
|
|
17952
|
+
const raw = fs9.readFileSync(configPath, "utf-8");
|
|
17004
17953
|
const config = JSON.parse(raw);
|
|
17005
17954
|
return (config.layers ?? []).map((l) => l.name);
|
|
17006
17955
|
} catch {
|
|
@@ -17019,8 +17968,8 @@ var init_dist3 = __esm({
|
|
|
17019
17968
|
historyPath;
|
|
17020
17969
|
/** Load history from disk, returning empty history if file doesn't exist. */
|
|
17021
17970
|
load() {
|
|
17022
|
-
if (!
|
|
17023
|
-
const raw =
|
|
17971
|
+
if (!fs10.existsSync(this.historyPath)) return { ...EMPTY_HISTORY, snapshots: [] };
|
|
17972
|
+
const raw = fs10.readFileSync(this.historyPath, "utf-8");
|
|
17024
17973
|
return JSON.parse(raw);
|
|
17025
17974
|
}
|
|
17026
17975
|
/** Append a timestamped snapshot of current violations. */
|
|
@@ -17034,18 +17983,18 @@ var init_dist3 = __esm({
|
|
|
17034
17983
|
}
|
|
17035
17984
|
/** Prune snapshots older than retentionDays. */
|
|
17036
17985
|
prune(retentionDays) {
|
|
17037
|
-
if (!
|
|
17986
|
+
if (!fs10.existsSync(this.historyPath)) return;
|
|
17038
17987
|
const history = this.load();
|
|
17039
17988
|
const cutoff = Date.now() - retentionDays * 24 * 60 * 60 * 1e3;
|
|
17040
17989
|
history.snapshots = history.snapshots.filter((s) => new Date(s.timestamp).getTime() >= cutoff);
|
|
17041
17990
|
this.write(history);
|
|
17042
17991
|
}
|
|
17043
17992
|
write(history) {
|
|
17044
|
-
const dir =
|
|
17045
|
-
if (!
|
|
17046
|
-
|
|
17993
|
+
const dir = path7.dirname(this.historyPath);
|
|
17994
|
+
if (!fs10.existsSync(dir)) {
|
|
17995
|
+
fs10.mkdirSync(dir, { recursive: true });
|
|
17047
17996
|
}
|
|
17048
|
-
|
|
17997
|
+
fs10.writeFileSync(this.historyPath, JSON.stringify(history, null, 2), "utf-8");
|
|
17049
17998
|
}
|
|
17050
17999
|
};
|
|
17051
18000
|
NORMALIZERS = {
|
|
@@ -17243,14 +18192,14 @@ var init_dist3 = __esm({
|
|
|
17243
18192
|
enabled: true,
|
|
17244
18193
|
strict: false,
|
|
17245
18194
|
rules: {},
|
|
17246
|
-
exclude: [
|
|
18195
|
+
exclude: [...skipDirGlobs(), "**/*.test.ts", "**/fixtures/**"]
|
|
17247
18196
|
};
|
|
17248
18197
|
RuleOverrideSchema = z10.enum(["off", "error", "warning", "info"]);
|
|
17249
18198
|
SecurityConfigSchema = z10.object({
|
|
17250
18199
|
enabled: z10.boolean().default(true),
|
|
17251
18200
|
strict: z10.boolean().default(false),
|
|
17252
18201
|
rules: z10.record(z10.string(), RuleOverrideSchema).optional().default({}),
|
|
17253
|
-
exclude: z10.array(z10.string()).optional().default([
|
|
18202
|
+
exclude: z10.array(z10.string()).optional().default([...skipDirGlobs(), "**/*.test.ts", "**/fixtures/**"]),
|
|
17254
18203
|
external: z10.object({
|
|
17255
18204
|
semgrep: z10.object({
|
|
17256
18205
|
enabled: z10.union([z10.literal("auto"), z10.boolean()]).default("auto"),
|
|
@@ -18047,7 +18996,7 @@ var init_dist3 = __esm({
|
|
|
18047
18996
|
}
|
|
18048
18997
|
async scanFile(filePath) {
|
|
18049
18998
|
if (!this.config.enabled) return [];
|
|
18050
|
-
const content = await
|
|
18999
|
+
const content = await fs28.readFile(filePath, "utf-8");
|
|
18051
19000
|
return this.scanContentForFile(content, filePath, 1);
|
|
18052
19001
|
}
|
|
18053
19002
|
scanContentForFile(content, filePath, startLine = 1) {
|
|
@@ -18452,7 +19401,7 @@ var init_dist3 = __esm({
|
|
|
18452
19401
|
timelinePath;
|
|
18453
19402
|
constructor(rootDir) {
|
|
18454
19403
|
this.rootDir = rootDir;
|
|
18455
|
-
this.timelinePath =
|
|
19404
|
+
this.timelinePath = join40(rootDir, ".harness", "security", "timeline.json");
|
|
18456
19405
|
}
|
|
18457
19406
|
/**
|
|
18458
19407
|
* Load timeline from disk.
|
|
@@ -18463,7 +19412,7 @@ var init_dist3 = __esm({
|
|
|
18463
19412
|
* fixup is one-shot.
|
|
18464
19413
|
*/
|
|
18465
19414
|
load() {
|
|
18466
|
-
if (!
|
|
19415
|
+
if (!existsSync342(this.timelinePath)) {
|
|
18467
19416
|
return { version: 1, snapshots: [], findingLifecycles: [] };
|
|
18468
19417
|
}
|
|
18469
19418
|
try {
|
|
@@ -18497,7 +19446,7 @@ var init_dist3 = __esm({
|
|
|
18497
19446
|
*/
|
|
18498
19447
|
save(timeline) {
|
|
18499
19448
|
const dir = dirname13(this.timelinePath);
|
|
18500
|
-
if (!
|
|
19449
|
+
if (!existsSync342(dir)) {
|
|
18501
19450
|
mkdirSync15(dir, { recursive: true });
|
|
18502
19451
|
}
|
|
18503
19452
|
const tmp = this.timelinePath + "." + randomBytes22(4).toString("hex") + ".tmp";
|
|
@@ -19511,7 +20460,8 @@ var init_dist3 = __esm({
|
|
|
19511
20460
|
}
|
|
19512
20461
|
};
|
|
19513
20462
|
syncMutex = Promise.resolve();
|
|
19514
|
-
PRIORITY_RANK = {
|
|
20463
|
+
PRIORITY_RANK = { P0: 0, P1: 1, P2: 2, P3: 3 };
|
|
20464
|
+
PRIORITY_RANK2 = {
|
|
19515
20465
|
P0: 0,
|
|
19516
20466
|
P1: 1,
|
|
19517
20467
|
P2: 2,
|
|
@@ -19520,6 +20470,586 @@ var init_dist3 = __esm({
|
|
|
19520
20470
|
POSITION_WEIGHT = 0.5;
|
|
19521
20471
|
DEPENDENTS_WEIGHT = 0.3;
|
|
19522
20472
|
AFFINITY_WEIGHT = 0.2;
|
|
20473
|
+
ConflictError = class extends Error {
|
|
20474
|
+
code = "TRACKER_CONFLICT";
|
|
20475
|
+
externalId;
|
|
20476
|
+
diff;
|
|
20477
|
+
serverUpdatedAt;
|
|
20478
|
+
constructor(externalId, diff2, serverUpdatedAt = null, message) {
|
|
20479
|
+
super(message ?? `Conflict on ${externalId}: ${Object.keys(diff2).join(", ")}`);
|
|
20480
|
+
this.name = "ConflictError";
|
|
20481
|
+
this.externalId = externalId;
|
|
20482
|
+
this.diff = diff2;
|
|
20483
|
+
this.serverUpdatedAt = serverUpdatedAt;
|
|
20484
|
+
}
|
|
20485
|
+
};
|
|
20486
|
+
DEFAULTS = { maxRetries: 5, baseDelayMs: 1e3 };
|
|
20487
|
+
GitHubHttp = class {
|
|
20488
|
+
token;
|
|
20489
|
+
fetchFn;
|
|
20490
|
+
apiBase;
|
|
20491
|
+
retryOpts;
|
|
20492
|
+
constructor(opts) {
|
|
20493
|
+
this.token = opts.token;
|
|
20494
|
+
this.fetchFn = opts.fetchFn ?? globalThis.fetch;
|
|
20495
|
+
this.apiBase = opts.apiBase ?? "https://api.github.com";
|
|
20496
|
+
this.retryOpts = {
|
|
20497
|
+
maxRetries: opts.maxRetries ?? DEFAULTS.maxRetries,
|
|
20498
|
+
baseDelayMs: opts.baseDelayMs ?? DEFAULTS.baseDelayMs
|
|
20499
|
+
};
|
|
20500
|
+
}
|
|
20501
|
+
headers(extra) {
|
|
20502
|
+
return {
|
|
20503
|
+
Authorization: `Bearer ${this.token}`,
|
|
20504
|
+
Accept: "application/vnd.github+json",
|
|
20505
|
+
"Content-Type": "application/json",
|
|
20506
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
20507
|
+
...extra ?? {}
|
|
20508
|
+
};
|
|
20509
|
+
}
|
|
20510
|
+
async request(url, init) {
|
|
20511
|
+
const { extraHeaders, ...rest } = init;
|
|
20512
|
+
const merged = {
|
|
20513
|
+
...rest,
|
|
20514
|
+
headers: this.headers(extraHeaders)
|
|
20515
|
+
};
|
|
20516
|
+
return this.fetchWithRetry(url, merged);
|
|
20517
|
+
}
|
|
20518
|
+
async fetchWithRetry(url, init) {
|
|
20519
|
+
let last;
|
|
20520
|
+
for (let attempt = 0; attempt <= this.retryOpts.maxRetries; attempt++) {
|
|
20521
|
+
const res = await this.fetchFn(url, init);
|
|
20522
|
+
if (res.status !== 403 && res.status !== 429 && res.status < 500) return res;
|
|
20523
|
+
last = res;
|
|
20524
|
+
if (attempt === this.retryOpts.maxRetries) break;
|
|
20525
|
+
const retryAfter = res.headers.get("Retry-After");
|
|
20526
|
+
let delayMs;
|
|
20527
|
+
if (retryAfter) {
|
|
20528
|
+
const seconds = parseInt(retryAfter, 10);
|
|
20529
|
+
delayMs = isNaN(seconds) ? this.retryOpts.baseDelayMs : seconds * 1e3;
|
|
20530
|
+
} else {
|
|
20531
|
+
delayMs = this.retryOpts.baseDelayMs * Math.pow(2, attempt) + Math.random() * 500;
|
|
20532
|
+
}
|
|
20533
|
+
await new Promise((r) => setTimeout(r, delayMs));
|
|
20534
|
+
}
|
|
20535
|
+
return last;
|
|
20536
|
+
}
|
|
20537
|
+
/**
|
|
20538
|
+
* Walk all pages of a paginated GET. Stops when a page returns < perPage items.
|
|
20539
|
+
*
|
|
20540
|
+
* Status semantics:
|
|
20541
|
+
* - `status: 304` is returned ONLY when the first page returned 304 (the
|
|
20542
|
+
* server confirmed the caller's If-None-Match matched and there are no
|
|
20543
|
+
* items to read). Callers may then serve cached data.
|
|
20544
|
+
* - `status: 200` is returned in all other terminal cases, including the
|
|
20545
|
+
* mid-walk case where page 1 returned 200 (with items) and a later page
|
|
20546
|
+
* returned 304. Mid-walk 304 is treated as "no further items"; already-
|
|
20547
|
+
* accumulated items are preserved and `lastEtag` is the latest ETag seen.
|
|
20548
|
+
*/
|
|
20549
|
+
async paginate(buildUrl, perPage = 100, extraHeaders) {
|
|
20550
|
+
const items = [];
|
|
20551
|
+
let page = 1;
|
|
20552
|
+
let lastEtag = null;
|
|
20553
|
+
let status = 200;
|
|
20554
|
+
for (; ; ) {
|
|
20555
|
+
const init = { method: "GET" };
|
|
20556
|
+
if (extraHeaders) init.extraHeaders = extraHeaders;
|
|
20557
|
+
const res = await this.request(buildUrl(page), init);
|
|
20558
|
+
const etag = res.headers.get("ETag");
|
|
20559
|
+
if (res.status === 304) {
|
|
20560
|
+
if (page === 1) return { items, lastEtag: etag, status: 304 };
|
|
20561
|
+
if (etag) lastEtag = etag;
|
|
20562
|
+
break;
|
|
20563
|
+
}
|
|
20564
|
+
if (!res.ok) {
|
|
20565
|
+
throw new Error(`GitHub ${res.status}: ${await res.text()}`);
|
|
20566
|
+
}
|
|
20567
|
+
const data = await res.json();
|
|
20568
|
+
items.push(...data);
|
|
20569
|
+
lastEtag = etag;
|
|
20570
|
+
status = res.status;
|
|
20571
|
+
if (data.length < perPage) break;
|
|
20572
|
+
page++;
|
|
20573
|
+
}
|
|
20574
|
+
return { items, lastEtag, status };
|
|
20575
|
+
}
|
|
20576
|
+
};
|
|
20577
|
+
ETagStore = class {
|
|
20578
|
+
max;
|
|
20579
|
+
cache = /* @__PURE__ */ new Map();
|
|
20580
|
+
constructor(max = 500) {
|
|
20581
|
+
this.max = max;
|
|
20582
|
+
}
|
|
20583
|
+
get(key) {
|
|
20584
|
+
const entry = this.cache.get(key);
|
|
20585
|
+
if (!entry) return null;
|
|
20586
|
+
this.cache.delete(key);
|
|
20587
|
+
this.cache.set(key, entry);
|
|
20588
|
+
return entry;
|
|
20589
|
+
}
|
|
20590
|
+
set(key, etag, data) {
|
|
20591
|
+
if (this.cache.has(key)) this.cache.delete(key);
|
|
20592
|
+
this.cache.set(key, { etag, data });
|
|
20593
|
+
if (this.cache.size > this.max) {
|
|
20594
|
+
const oldestKey = this.cache.keys().next().value;
|
|
20595
|
+
if (oldestKey !== void 0) this.cache.delete(oldestKey);
|
|
20596
|
+
}
|
|
20597
|
+
}
|
|
20598
|
+
invalidate(key) {
|
|
20599
|
+
this.cache.delete(key);
|
|
20600
|
+
}
|
|
20601
|
+
invalidateAll() {
|
|
20602
|
+
this.cache.clear();
|
|
20603
|
+
}
|
|
20604
|
+
/** Invalidate all keys matching a prefix. Used when writes invalidate `list:*`. */
|
|
20605
|
+
invalidatePrefix(prefix) {
|
|
20606
|
+
for (const key of [...this.cache.keys()]) {
|
|
20607
|
+
if (key.startsWith(prefix)) this.cache.delete(key);
|
|
20608
|
+
}
|
|
20609
|
+
}
|
|
20610
|
+
get size() {
|
|
20611
|
+
return this.cache.size;
|
|
20612
|
+
}
|
|
20613
|
+
};
|
|
20614
|
+
START_MARKER = "<!-- harness-meta:start -->";
|
|
20615
|
+
END_MARKER = "<!-- harness-meta:end -->";
|
|
20616
|
+
SERIALIZER_FIELDS = [
|
|
20617
|
+
["spec", "spec", (v) => v],
|
|
20618
|
+
["plan", "plan", (v) => v],
|
|
20619
|
+
[
|
|
20620
|
+
"blocked_by",
|
|
20621
|
+
"blocked_by",
|
|
20622
|
+
(v) => Array.isArray(v) && v.length > 0 ? v : void 0
|
|
20623
|
+
],
|
|
20624
|
+
["priority", "priority", (v) => v],
|
|
20625
|
+
["milestone", "milestone", (v) => v]
|
|
20626
|
+
];
|
|
20627
|
+
GitHubIssuesTrackerAdapter = class _GitHubIssuesTrackerAdapter {
|
|
20628
|
+
http;
|
|
20629
|
+
owner;
|
|
20630
|
+
repo;
|
|
20631
|
+
cache;
|
|
20632
|
+
selectorLabel;
|
|
20633
|
+
constructor(opts) {
|
|
20634
|
+
this.http = new GitHubHttp(opts);
|
|
20635
|
+
const [owner, repo] = opts.repo.split("/");
|
|
20636
|
+
if (!owner || !repo) throw new Error(`Invalid repo "${opts.repo}", expected "owner/repo"`);
|
|
20637
|
+
this.owner = owner;
|
|
20638
|
+
this.repo = repo;
|
|
20639
|
+
this.cache = opts.etagStore ?? new ETagStore(500);
|
|
20640
|
+
this.selectorLabel = opts.selectorLabel ?? "harness-managed";
|
|
20641
|
+
}
|
|
20642
|
+
// --- Reads ---
|
|
20643
|
+
async fetchAll() {
|
|
20644
|
+
try {
|
|
20645
|
+
const cacheKey = "list:all";
|
|
20646
|
+
const cached = this.cache.get(cacheKey);
|
|
20647
|
+
const buildUrl = (page) => {
|
|
20648
|
+
const params = new URLSearchParams({
|
|
20649
|
+
state: "all",
|
|
20650
|
+
per_page: "100",
|
|
20651
|
+
page: String(page),
|
|
20652
|
+
labels: this.selectorLabel
|
|
20653
|
+
});
|
|
20654
|
+
return `${this.http.apiBase}/repos/${this.owner}/${this.repo}/issues?${params.toString()}`;
|
|
20655
|
+
};
|
|
20656
|
+
const headers = cached ? { "If-None-Match": cached.etag } : void 0;
|
|
20657
|
+
const { items, lastEtag, status } = await this.http.paginate(
|
|
20658
|
+
buildUrl,
|
|
20659
|
+
100,
|
|
20660
|
+
headers
|
|
20661
|
+
);
|
|
20662
|
+
if (status === 304 && cached) {
|
|
20663
|
+
return Ok({ features: cached.data, etag: cached.etag });
|
|
20664
|
+
}
|
|
20665
|
+
const issues = items.filter((i) => !i.pull_request);
|
|
20666
|
+
const nameIndex = /* @__PURE__ */ new Map();
|
|
20667
|
+
for (const i of issues) {
|
|
20668
|
+
nameIndex.set(i.title, buildExternalId2(this.owner, this.repo, i.number));
|
|
20669
|
+
}
|
|
20670
|
+
const features = issues.map((i) => this.mapIssue(i, nameIndex));
|
|
20671
|
+
features.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
20672
|
+
if (lastEtag) this.cache.set(cacheKey, lastEtag, features);
|
|
20673
|
+
return Ok({ features, etag: lastEtag });
|
|
20674
|
+
} catch (err) {
|
|
20675
|
+
return Err(err instanceof Error ? err : new Error(String(err)));
|
|
20676
|
+
}
|
|
20677
|
+
}
|
|
20678
|
+
/**
|
|
20679
|
+
* Fetches a feature by externalId.
|
|
20680
|
+
*
|
|
20681
|
+
* Confused-deputy guard: the externalId's owner/repo MUST match the
|
|
20682
|
+
* adapter's configured owner/repo. If they differ we return Err rather
|
|
20683
|
+
* than silently issuing a request against the externalId's repo (which
|
|
20684
|
+
* the adapter is not configured to manage). The adapter's configured
|
|
20685
|
+
* owner/repo always wins.
|
|
20686
|
+
*/
|
|
20687
|
+
async fetchById(externalId) {
|
|
20688
|
+
try {
|
|
20689
|
+
const parsed = parseExternalId2(externalId);
|
|
20690
|
+
if (!parsed) return Err(new Error(`Invalid externalId: "${externalId}"`));
|
|
20691
|
+
if (parsed.owner !== this.owner || parsed.repo !== this.repo) {
|
|
20692
|
+
return Err(new Error("externalId repo does not match adapter repo"));
|
|
20693
|
+
}
|
|
20694
|
+
const cacheKey = `feature:${externalId}`;
|
|
20695
|
+
const cached = this.cache.get(cacheKey);
|
|
20696
|
+
const init = { method: "GET" };
|
|
20697
|
+
if (cached) init.extraHeaders = { "If-None-Match": cached.etag };
|
|
20698
|
+
const res = await this.http.request(
|
|
20699
|
+
`${this.http.apiBase}/repos/${parsed.owner}/${parsed.repo}/issues/${parsed.number}`,
|
|
20700
|
+
init
|
|
20701
|
+
);
|
|
20702
|
+
if (res.status === 404) return Ok(null);
|
|
20703
|
+
if (res.status === 304 && cached) {
|
|
20704
|
+
return Ok({ feature: cached.data, etag: cached.etag });
|
|
20705
|
+
}
|
|
20706
|
+
if (!res.ok) return Err(new Error(`GitHub ${res.status}: ${await res.text()}`));
|
|
20707
|
+
const data = await res.json();
|
|
20708
|
+
const etag = res.headers.get("ETag");
|
|
20709
|
+
if (data.pull_request) return Ok(null);
|
|
20710
|
+
const feature = this.mapIssue(data, /* @__PURE__ */ new Map());
|
|
20711
|
+
if (etag) this.cache.set(cacheKey, etag, feature);
|
|
20712
|
+
return Ok({ feature, etag: etag ?? "" });
|
|
20713
|
+
} catch (err) {
|
|
20714
|
+
return Err(err instanceof Error ? err : new Error(String(err)));
|
|
20715
|
+
}
|
|
20716
|
+
}
|
|
20717
|
+
async fetchByStatus(statuses) {
|
|
20718
|
+
const all = await this.fetchAll();
|
|
20719
|
+
if (!all.ok) return all;
|
|
20720
|
+
return Ok(all.value.features.filter((f) => statuses.includes(f.status)));
|
|
20721
|
+
}
|
|
20722
|
+
// --- Writes ---
|
|
20723
|
+
async create(feature) {
|
|
20724
|
+
try {
|
|
20725
|
+
const meta = buildCreateMeta(feature);
|
|
20726
|
+
const body = serializeBodyBlock(feature.summary ?? "", meta);
|
|
20727
|
+
const labels = this.buildCreateLabels(feature);
|
|
20728
|
+
const payload = { title: feature.name, body, labels };
|
|
20729
|
+
if (feature.assignee) payload.assignees = [feature.assignee.replace(/^@/, "")];
|
|
20730
|
+
const res = await this.http.request(
|
|
20731
|
+
`${this.http.apiBase}/repos/${this.owner}/${this.repo}/issues`,
|
|
20732
|
+
{ method: "POST", body: JSON.stringify(payload) }
|
|
20733
|
+
);
|
|
20734
|
+
if (!res.ok) return Err(new Error(`GitHub ${res.status}: ${await res.text()}`));
|
|
20735
|
+
const data = await res.json();
|
|
20736
|
+
this.cache.invalidatePrefix("list:");
|
|
20737
|
+
return Ok(this.mapIssue(data, /* @__PURE__ */ new Map()));
|
|
20738
|
+
} catch (err) {
|
|
20739
|
+
return Err(err instanceof Error ? err : new Error(String(err)));
|
|
20740
|
+
}
|
|
20741
|
+
}
|
|
20742
|
+
buildCreateLabels(feature) {
|
|
20743
|
+
const labels = [this.selectorLabel];
|
|
20744
|
+
if (feature.status && feature.status !== "backlog") labels.push(feature.status);
|
|
20745
|
+
return labels;
|
|
20746
|
+
}
|
|
20747
|
+
/**
|
|
20748
|
+
* Patches a feature.
|
|
20749
|
+
*
|
|
20750
|
+
* Confused-deputy guard: the externalId's owner/repo MUST match the
|
|
20751
|
+
* adapter's configured owner/repo. If they differ we return Err rather
|
|
20752
|
+
* than silently writing to the externalId's repo. The adapter's
|
|
20753
|
+
* configured owner/repo always wins.
|
|
20754
|
+
*/
|
|
20755
|
+
async update(externalId, patch, ifMatch) {
|
|
20756
|
+
const r = await this.updateInternal(externalId, patch, ifMatch);
|
|
20757
|
+
if (!r.ok) return r;
|
|
20758
|
+
return Ok(r.value.feature);
|
|
20759
|
+
}
|
|
20760
|
+
/**
|
|
20761
|
+
* Internal update that also reports whether a real PATCH was issued.
|
|
20762
|
+
* Returns wrote=false on the idempotent path so callers (claim/release/complete)
|
|
20763
|
+
* can skip best-effort history logging when no state change occurred.
|
|
20764
|
+
*
|
|
20765
|
+
* When ifMatch is supplied, updateInternal performs a refetch GET; the
|
|
20766
|
+
* refetched feature is returned as `priorFeature` so callers (release/complete)
|
|
20767
|
+
* can extract the pre-PATCH assignee for history attribution without a second GET.
|
|
20768
|
+
*/
|
|
20769
|
+
async updateInternal(externalId, patch, ifMatch) {
|
|
20770
|
+
try {
|
|
20771
|
+
const parsed = parseExternalId2(externalId);
|
|
20772
|
+
if (!parsed) return Err(new Error(`Invalid externalId: "${externalId}"`));
|
|
20773
|
+
if (parsed.owner !== this.owner || parsed.repo !== this.repo) {
|
|
20774
|
+
return Err(new Error("externalId repo does not match adapter repo"));
|
|
20775
|
+
}
|
|
20776
|
+
let priorFeature;
|
|
20777
|
+
if (ifMatch) {
|
|
20778
|
+
const cur = await this.fetchById(externalId);
|
|
20779
|
+
if (!cur.ok) return cur;
|
|
20780
|
+
if (!cur.value) return Err(new Error(`Not found: ${externalId}`));
|
|
20781
|
+
priorFeature = cur.value.feature;
|
|
20782
|
+
const cmp = refetchAndCompare(cur.value.feature, patch);
|
|
20783
|
+
if (!cmp.ok) {
|
|
20784
|
+
return Err(new ConflictError(externalId, cmp.diff, cur.value.feature.updatedAt));
|
|
20785
|
+
}
|
|
20786
|
+
if (cmp.idempotent) return Ok({ feature: cur.value.feature, wrote: false, priorFeature });
|
|
20787
|
+
}
|
|
20788
|
+
const reqBody = await this.buildIssuePatchBody(externalId, patch, priorFeature);
|
|
20789
|
+
const res = await this.http.request(
|
|
20790
|
+
`${this.http.apiBase}/repos/${parsed.owner}/${parsed.repo}/issues/${parsed.number}`,
|
|
20791
|
+
{ method: "PATCH", body: JSON.stringify(reqBody) }
|
|
20792
|
+
);
|
|
20793
|
+
if (!res.ok) return Err(new Error(`GitHub ${res.status}: ${await res.text()}`));
|
|
20794
|
+
const data = await res.json();
|
|
20795
|
+
this.cache.invalidate(`feature:${externalId}`);
|
|
20796
|
+
this.cache.invalidatePrefix("list:");
|
|
20797
|
+
return Ok({
|
|
20798
|
+
feature: this.mapIssue(data, /* @__PURE__ */ new Map()),
|
|
20799
|
+
wrote: true,
|
|
20800
|
+
...priorFeature !== void 0 ? { priorFeature } : {}
|
|
20801
|
+
});
|
|
20802
|
+
} catch (err) {
|
|
20803
|
+
return Err(err instanceof Error ? err : new Error(String(err)));
|
|
20804
|
+
}
|
|
20805
|
+
}
|
|
20806
|
+
/** Status labels recognized by mapStatus; 'backlog' and 'done' are state-driven. */
|
|
20807
|
+
static STATUS_LABELS = /* @__PURE__ */ new Set([
|
|
20808
|
+
"planned",
|
|
20809
|
+
"in-progress",
|
|
20810
|
+
"blocked",
|
|
20811
|
+
"needs-human"
|
|
20812
|
+
]);
|
|
20813
|
+
async buildIssuePatchBody(externalId, patch, priorFeature) {
|
|
20814
|
+
const out = {};
|
|
20815
|
+
if (patch.name !== void 0) out.title = patch.name;
|
|
20816
|
+
if (patch.assignee !== void 0) {
|
|
20817
|
+
out.assignees = patch.assignee ? [patch.assignee.replace(/^@/, "")] : [];
|
|
20818
|
+
}
|
|
20819
|
+
if (patch.status !== void 0) {
|
|
20820
|
+
if (patch.status === "done") out.state = "closed";
|
|
20821
|
+
else out.state = "open";
|
|
20822
|
+
const labelsR = await this.fetchRawLabels(externalId);
|
|
20823
|
+
if (labelsR.ok) {
|
|
20824
|
+
const filtered = labelsR.value.filter(
|
|
20825
|
+
(l) => !_GitHubIssuesTrackerAdapter.STATUS_LABELS.has(l)
|
|
20826
|
+
);
|
|
20827
|
+
if (patch.status !== "backlog" && patch.status !== "done" && _GitHubIssuesTrackerAdapter.STATUS_LABELS.has(patch.status)) {
|
|
20828
|
+
filtered.push(patch.status);
|
|
20829
|
+
}
|
|
20830
|
+
out.labels = filtered;
|
|
20831
|
+
} else {
|
|
20832
|
+
console.warn(
|
|
20833
|
+
`harness-tracker: skipping label sync on ${externalId} due to GET error: ${labelsR.error.message}`
|
|
20834
|
+
);
|
|
20835
|
+
}
|
|
20836
|
+
}
|
|
20837
|
+
const bodyTouches = [
|
|
20838
|
+
"summary",
|
|
20839
|
+
"spec",
|
|
20840
|
+
"plans",
|
|
20841
|
+
"blockedBy",
|
|
20842
|
+
"priority",
|
|
20843
|
+
"milestone"
|
|
20844
|
+
];
|
|
20845
|
+
if (bodyTouches.some((k) => patch[k] !== void 0)) {
|
|
20846
|
+
let current;
|
|
20847
|
+
if (priorFeature) {
|
|
20848
|
+
current = priorFeature;
|
|
20849
|
+
} else {
|
|
20850
|
+
const cur = await this.fetchById(externalId);
|
|
20851
|
+
if (!cur.ok || !cur.value) throw new Error("Cannot rebuild body without current state");
|
|
20852
|
+
current = cur.value.feature;
|
|
20853
|
+
}
|
|
20854
|
+
const meta = metaFromFeatureFields({
|
|
20855
|
+
spec: patch.spec !== void 0 ? patch.spec : current.spec,
|
|
20856
|
+
plans: patch.plans !== void 0 ? patch.plans : current.plans,
|
|
20857
|
+
blockedBy: patch.blockedBy !== void 0 ? patch.blockedBy : current.blockedBy,
|
|
20858
|
+
priority: patch.priority !== void 0 ? patch.priority : current.priority,
|
|
20859
|
+
milestone: patch.milestone !== void 0 ? patch.milestone : current.milestone
|
|
20860
|
+
});
|
|
20861
|
+
const summaryText = patch.summary !== void 0 ? patch.summary : current.summary;
|
|
20862
|
+
out.body = serializeBodyBlock(summaryText, meta);
|
|
20863
|
+
}
|
|
20864
|
+
return out;
|
|
20865
|
+
}
|
|
20866
|
+
/**
|
|
20867
|
+
* Lightweight GET that returns the raw label name list.
|
|
20868
|
+
*
|
|
20869
|
+
* P2-RR2-IMP-1: returns Result so callers can distinguish a real Err (network
|
|
20870
|
+
* failure / non-200 response) from an empty label set. The previous shape
|
|
20871
|
+
* silently swallowed errors and returned [], which combined with the
|
|
20872
|
+
* unconditional `out.labels = filtered` assignment in buildIssuePatchBody
|
|
20873
|
+
* caused PATCH requests to wipe ALL labels (including the selectorLabel and
|
|
20874
|
+
* user-added labels) on a transient GET blip.
|
|
20875
|
+
*/
|
|
20876
|
+
async fetchRawLabels(externalId) {
|
|
20877
|
+
try {
|
|
20878
|
+
const parsed = parseExternalId2(externalId);
|
|
20879
|
+
if (!parsed) return Err(new Error(`Invalid externalId: "${externalId}"`));
|
|
20880
|
+
const res = await this.http.request(
|
|
20881
|
+
`${this.http.apiBase}/repos/${parsed.owner}/${parsed.repo}/issues/${parsed.number}`,
|
|
20882
|
+
{ method: "GET" }
|
|
20883
|
+
);
|
|
20884
|
+
if (!res.ok) return Err(new Error(`GitHub ${res.status}: ${await res.text()}`));
|
|
20885
|
+
const data = await res.json();
|
|
20886
|
+
return Ok(data.labels.map((l) => l.name));
|
|
20887
|
+
} catch (err) {
|
|
20888
|
+
return Err(err instanceof Error ? err : new Error(String(err)));
|
|
20889
|
+
}
|
|
20890
|
+
}
|
|
20891
|
+
async claim(externalId, assignee, ifMatch) {
|
|
20892
|
+
const r = await this.updateInternal(externalId, { assignee, status: "in-progress" }, ifMatch);
|
|
20893
|
+
if (!r.ok) return r;
|
|
20894
|
+
if (r.value.wrote) await this.logEvent(externalId, "claimed", assignee);
|
|
20895
|
+
return Ok(r.value.feature);
|
|
20896
|
+
}
|
|
20897
|
+
async release(externalId, ifMatch) {
|
|
20898
|
+
const priorAssignee = await this.fetchPriorAssigneeIfNeeded(externalId, ifMatch);
|
|
20899
|
+
const r = await this.updateInternal(externalId, { assignee: null, status: "backlog" }, ifMatch);
|
|
20900
|
+
if (!r.ok) return r;
|
|
20901
|
+
if (r.value.wrote) {
|
|
20902
|
+
const actor = r.value.priorFeature?.assignee ?? priorAssignee ?? null;
|
|
20903
|
+
await this.logEvent(externalId, "released", actor ?? "unknown");
|
|
20904
|
+
}
|
|
20905
|
+
return Ok(r.value.feature);
|
|
20906
|
+
}
|
|
20907
|
+
async complete(externalId, ifMatch) {
|
|
20908
|
+
const priorAssignee = await this.fetchPriorAssigneeIfNeeded(externalId, ifMatch);
|
|
20909
|
+
const r = await this.updateInternal(externalId, { status: "done" }, ifMatch);
|
|
20910
|
+
if (!r.ok) return r;
|
|
20911
|
+
if (r.value.wrote) {
|
|
20912
|
+
const actor = r.value.priorFeature?.assignee ?? priorAssignee ?? null;
|
|
20913
|
+
await this.logEvent(externalId, "completed", actor ?? "unknown");
|
|
20914
|
+
}
|
|
20915
|
+
return Ok(r.value.feature);
|
|
20916
|
+
}
|
|
20917
|
+
/**
|
|
20918
|
+
* Best-effort GET to capture the assignee BEFORE a PATCH that may clear it.
|
|
20919
|
+
* Skipped when ifMatch is supplied because updateInternal will refetch and
|
|
20920
|
+
* thread the prior feature through.
|
|
20921
|
+
*/
|
|
20922
|
+
async fetchPriorAssigneeIfNeeded(externalId, ifMatch) {
|
|
20923
|
+
if (ifMatch) return null;
|
|
20924
|
+
const cur = await this.fetchById(externalId);
|
|
20925
|
+
if (cur.ok && cur.value) return cur.value.feature.assignee;
|
|
20926
|
+
return null;
|
|
20927
|
+
}
|
|
20928
|
+
/**
|
|
20929
|
+
* Trust model for history events.
|
|
20930
|
+
*
|
|
20931
|
+
* `HISTORY_PREFIX` (`<!-- harness-history -->`) marks issue comments as
|
|
20932
|
+
* authentic harness events. fetchHistory reads only comments that begin
|
|
20933
|
+
* with this prefix; appendHistory writes the prefix unconditionally.
|
|
20934
|
+
*
|
|
20935
|
+
* Trust is per-repo: only repository contributors authorized to post
|
|
20936
|
+
* comments on a given issue can synthesize harness history events.
|
|
20937
|
+
* GitHub's per-repo permissions are the authorization boundary; the
|
|
20938
|
+
* prefix itself is not a cryptographic credential — a contributor with
|
|
20939
|
+
* comment access can forge events by hand-writing the prefix.
|
|
20940
|
+
*
|
|
20941
|
+
* HMAC-signing of event payloads is a future hardening (post-GA) for
|
|
20942
|
+
* deployments that need cross-actor tamper-evidence within the same
|
|
20943
|
+
* repo. The current shape is intentional: optimized for transparency
|
|
20944
|
+
* (events are human-readable, no key distribution required).
|
|
20945
|
+
*/
|
|
20946
|
+
static HISTORY_PREFIX = "<!-- harness-history -->";
|
|
20947
|
+
async appendHistory(externalId, event) {
|
|
20948
|
+
try {
|
|
20949
|
+
const parsed = parseExternalId2(externalId);
|
|
20950
|
+
if (!parsed) return Err(new Error(`Invalid externalId: "${externalId}"`));
|
|
20951
|
+
const body = `${_GitHubIssuesTrackerAdapter.HISTORY_PREFIX}
|
|
20952
|
+
${JSON.stringify(event)}`;
|
|
20953
|
+
const res = await this.http.request(
|
|
20954
|
+
`${this.http.apiBase}/repos/${parsed.owner}/${parsed.repo}/issues/${parsed.number}/comments`,
|
|
20955
|
+
{ method: "POST", body: JSON.stringify({ body }) }
|
|
20956
|
+
);
|
|
20957
|
+
if (!res.ok) return Err(new Error(`GitHub ${res.status}: ${await res.text()}`));
|
|
20958
|
+
return Ok(void 0);
|
|
20959
|
+
} catch (err) {
|
|
20960
|
+
return Err(err instanceof Error ? err : new Error(String(err)));
|
|
20961
|
+
}
|
|
20962
|
+
}
|
|
20963
|
+
async fetchHistory(externalId, limit) {
|
|
20964
|
+
try {
|
|
20965
|
+
const parsed = parseExternalId2(externalId);
|
|
20966
|
+
if (!parsed) return Err(new Error(`Invalid externalId: "${externalId}"`));
|
|
20967
|
+
const buildUrl = (page) => `${this.http.apiBase}/repos/${parsed.owner}/${parsed.repo}/issues/${parsed.number}/comments?per_page=100&page=${page}`;
|
|
20968
|
+
const { items } = await this.http.paginate(buildUrl);
|
|
20969
|
+
const events = [];
|
|
20970
|
+
for (const c of items) {
|
|
20971
|
+
if (!c.body.startsWith(_GitHubIssuesTrackerAdapter.HISTORY_PREFIX)) continue;
|
|
20972
|
+
const json = c.body.slice(_GitHubIssuesTrackerAdapter.HISTORY_PREFIX.length).trim();
|
|
20973
|
+
try {
|
|
20974
|
+
events.push(JSON.parse(json));
|
|
20975
|
+
} catch (e) {
|
|
20976
|
+
console.warn(`harness-history: malformed JSON in ${externalId}: ${e.message}`);
|
|
20977
|
+
}
|
|
20978
|
+
}
|
|
20979
|
+
events.sort((a, b) => a.at.localeCompare(b.at));
|
|
20980
|
+
return Ok(limit ? events.slice(-limit) : events);
|
|
20981
|
+
} catch (err) {
|
|
20982
|
+
return Err(err instanceof Error ? err : new Error(String(err)));
|
|
20983
|
+
}
|
|
20984
|
+
}
|
|
20985
|
+
async logEvent(externalId, type, actor) {
|
|
20986
|
+
const event = {
|
|
20987
|
+
type,
|
|
20988
|
+
actor,
|
|
20989
|
+
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
20990
|
+
};
|
|
20991
|
+
const r = await this.appendHistory(externalId, event);
|
|
20992
|
+
if (!r.ok) {
|
|
20993
|
+
console.warn(`harness-history: append failed for ${externalId}: ${r.error.message}`);
|
|
20994
|
+
}
|
|
20995
|
+
}
|
|
20996
|
+
// --- Helpers ---
|
|
20997
|
+
// The nameIndex parameter is retained for backwards-compatibility with the
|
|
20998
|
+
// fetchAll signature but is no longer consulted: blockedBy holds feature
|
|
20999
|
+
// names verbatim from the body-meta block (see client.ts TrackedFeature.blockedBy).
|
|
21000
|
+
mapIssue(issue, _nameIndex) {
|
|
21001
|
+
const { summary, meta } = parseBodyBlock(issue.body ?? "");
|
|
21002
|
+
const status = this.mapStatus(issue, meta);
|
|
21003
|
+
return {
|
|
21004
|
+
externalId: buildExternalId2(this.owner, this.repo, issue.number),
|
|
21005
|
+
name: issue.title,
|
|
21006
|
+
status,
|
|
21007
|
+
summary,
|
|
21008
|
+
spec: meta.spec ?? null,
|
|
21009
|
+
plans: meta.plan ? [meta.plan] : [],
|
|
21010
|
+
blockedBy: meta.blocked_by ?? [],
|
|
21011
|
+
assignee: issue.assignees[0]?.login ? `@${issue.assignees[0].login}` : null,
|
|
21012
|
+
priority: meta.priority ?? null,
|
|
21013
|
+
milestone: issue.milestone?.title ?? meta.milestone ?? null,
|
|
21014
|
+
createdAt: issue.created_at,
|
|
21015
|
+
updatedAt: issue.updated_at ?? null
|
|
21016
|
+
};
|
|
21017
|
+
}
|
|
21018
|
+
/**
|
|
21019
|
+
* Status precedence (highest wins):
|
|
21020
|
+
* 1. issue.state === 'closed' → 'done'
|
|
21021
|
+
* 2. 'blocked' label → 'blocked'
|
|
21022
|
+
* 3. 'needs-human' label → 'needs-human'
|
|
21023
|
+
* 4. 'in-progress' label OR any assignee → 'in-progress'
|
|
21024
|
+
* 5. 'planned' label → 'planned'
|
|
21025
|
+
* 6. otherwise → 'backlog'
|
|
21026
|
+
*
|
|
21027
|
+
* Note step 4: an assignment overrides a 'planned' label. This is
|
|
21028
|
+
* intentional — a feature that someone has picked up is "in-progress"
|
|
21029
|
+
* regardless of whether the planning label was cleared first. The reverse
|
|
21030
|
+
* (planned label + assignee) is the common case where the orchestrator
|
|
21031
|
+
* (or a human) starts work without scrubbing the planning label.
|
|
21032
|
+
*/
|
|
21033
|
+
mapStatus(issue, _meta) {
|
|
21034
|
+
if (issue.state === "closed") return "done";
|
|
21035
|
+
const labels = issue.labels.map((l) => l.name);
|
|
21036
|
+
if (labels.includes("blocked")) return "blocked";
|
|
21037
|
+
if (labels.includes("needs-human")) return "needs-human";
|
|
21038
|
+
if (labels.includes("in-progress") || issue.assignees.length > 0) return "in-progress";
|
|
21039
|
+
if (labels.includes("planned")) return "planned";
|
|
21040
|
+
return "backlog";
|
|
21041
|
+
}
|
|
21042
|
+
};
|
|
21043
|
+
migrate_exports = {};
|
|
21044
|
+
__export2(migrate_exports, {
|
|
21045
|
+
bodyMetaMatches: () => bodyMetaMatches,
|
|
21046
|
+
buildHistoryCommentBody: () => buildHistoryCommentBody,
|
|
21047
|
+
buildMigrationPlan: () => buildMigrationPlan,
|
|
21048
|
+
hashHistoryEvent: () => hashHistoryEvent,
|
|
21049
|
+
parseHashFromCommentBody: () => parseHashFromCommentBody,
|
|
21050
|
+
runMigrationPlan: () => runMigrationPlan
|
|
21051
|
+
});
|
|
21052
|
+
HASH_RE = /<!--\s*harness-history\s+hash:([0-9a-f]{8})\s*-->/i;
|
|
19523
21053
|
InteractionTypeSchema = z12.enum(["question", "confirmation", "transition"]);
|
|
19524
21054
|
QuestionSchema = z12.object({
|
|
19525
21055
|
text: z12.string(),
|
|
@@ -19552,10 +21082,10 @@ var init_dist3 = __esm({
|
|
|
19552
21082
|
}
|
|
19553
21083
|
rootDir;
|
|
19554
21084
|
async scan() {
|
|
19555
|
-
let projectName =
|
|
21085
|
+
let projectName = path34.basename(this.rootDir);
|
|
19556
21086
|
try {
|
|
19557
|
-
const pkgPath =
|
|
19558
|
-
const pkgRaw = await
|
|
21087
|
+
const pkgPath = path34.join(this.rootDir, "package.json");
|
|
21088
|
+
const pkgRaw = await fs34.readFile(pkgPath, "utf-8");
|
|
19559
21089
|
const pkg = JSON.parse(pkgRaw);
|
|
19560
21090
|
if (pkg.name) projectName = pkg.name;
|
|
19561
21091
|
} catch {
|
|
@@ -19668,8 +21198,8 @@ console.log('Blueprint player initialized.');
|
|
|
19668
21198
|
styles: STYLES,
|
|
19669
21199
|
scripts: SCRIPTS
|
|
19670
21200
|
});
|
|
19671
|
-
await
|
|
19672
|
-
await
|
|
21201
|
+
await fs35.mkdir(options.outputDir, { recursive: true });
|
|
21202
|
+
await fs35.writeFile(path35.join(options.outputDir, "index.html"), html);
|
|
19673
21203
|
}
|
|
19674
21204
|
};
|
|
19675
21205
|
TOKENS_PER_MILLION = 1e6;
|
|
@@ -20084,6 +21614,9 @@ export {
|
|
|
20084
21614
|
architecture,
|
|
20085
21615
|
archModule,
|
|
20086
21616
|
archMatchers,
|
|
21617
|
+
WHATWG_BAD_PORTS,
|
|
21618
|
+
isBadPort,
|
|
21619
|
+
assertPortUsable,
|
|
20087
21620
|
validateFileStructure,
|
|
20088
21621
|
validateConfig,
|
|
20089
21622
|
validateCommitMessage,
|
|
@@ -20102,6 +21635,8 @@ export {
|
|
|
20102
21635
|
validateAgentsMap,
|
|
20103
21636
|
runFallbackRules,
|
|
20104
21637
|
validateAgentConfigs,
|
|
21638
|
+
getRoadmapMode,
|
|
21639
|
+
validateRoadmapMode,
|
|
20105
21640
|
checkDocCoverage,
|
|
20106
21641
|
validateKnowledgeMap,
|
|
20107
21642
|
generateAgentsMap,
|
|
@@ -20391,8 +21926,17 @@ export {
|
|
|
20391
21926
|
syncToExternal,
|
|
20392
21927
|
syncFromExternal,
|
|
20393
21928
|
fullSync,
|
|
21929
|
+
scoreRoadmapCandidatesFileLess,
|
|
20394
21930
|
scoreRoadmapCandidates,
|
|
20395
21931
|
assignFeature,
|
|
21932
|
+
scoreRoadmapCandidatesForMode,
|
|
21933
|
+
ConflictError,
|
|
21934
|
+
ETagStore,
|
|
21935
|
+
createTrackerClient,
|
|
21936
|
+
makeTrackerConflictBody,
|
|
21937
|
+
loadProjectRoadmapMode,
|
|
21938
|
+
loadTrackerClientConfigFromProject,
|
|
21939
|
+
migrate_exports,
|
|
20396
21940
|
InteractionTypeSchema,
|
|
20397
21941
|
QuestionSchema,
|
|
20398
21942
|
ConfirmationSchema,
|