@harness-engineering/core 0.19.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/architecture/matchers.d.mts +1 -1
- package/dist/architecture/matchers.d.ts +1 -1
- package/dist/index.d.mts +1348 -85
- package/dist/index.d.ts +1348 -85
- package/dist/index.js +1635 -642
- package/dist/index.mjs +1571 -604
- package/dist/{matchers-Dj1t5vpg.d.mts → matchers-D20x48U9.d.mts} +46 -46
- package/dist/{matchers-Dj1t5vpg.d.ts → matchers-D20x48U9.d.ts} +46 -46
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -33,6 +33,7 @@ var index_exports = {};
|
|
|
33
33
|
__export(index_exports, {
|
|
34
34
|
AGENT_DESCRIPTORS: () => AGENT_DESCRIPTORS,
|
|
35
35
|
ARCHITECTURE_DESCRIPTOR: () => ARCHITECTURE_DESCRIPTOR,
|
|
36
|
+
AdjustedForecastSchema: () => AdjustedForecastSchema,
|
|
36
37
|
AgentActionEmitter: () => AgentActionEmitter,
|
|
37
38
|
ArchBaselineManager: () => ArchBaselineManager,
|
|
38
39
|
ArchBaselineSchema: () => ArchBaselineSchema,
|
|
@@ -48,23 +49,29 @@ __export(index_exports, {
|
|
|
48
49
|
CACHE_TTL_MS: () => CACHE_TTL_MS,
|
|
49
50
|
COMPLIANCE_DESCRIPTOR: () => COMPLIANCE_DESCRIPTOR,
|
|
50
51
|
CategoryBaselineSchema: () => CategoryBaselineSchema,
|
|
52
|
+
CategoryForecastSchema: () => CategoryForecastSchema,
|
|
51
53
|
CategoryRegressionSchema: () => CategoryRegressionSchema,
|
|
54
|
+
CategorySnapshotSchema: () => CategorySnapshotSchema,
|
|
52
55
|
ChecklistBuilder: () => ChecklistBuilder,
|
|
53
56
|
CircularDepsCollector: () => CircularDepsCollector,
|
|
54
57
|
ComplexityCollector: () => ComplexityCollector,
|
|
58
|
+
ConfidenceTierSchema: () => ConfidenceTierSchema,
|
|
55
59
|
ConfirmationSchema: () => ConfirmationSchema,
|
|
56
60
|
ConsoleSink: () => ConsoleSink,
|
|
57
61
|
ConstraintRuleSchema: () => ConstraintRuleSchema,
|
|
58
62
|
ContentPipeline: () => ContentPipeline,
|
|
63
|
+
ContributingFeatureSchema: () => ContributingFeatureSchema,
|
|
59
64
|
ContributionsSchema: () => ContributionsSchema,
|
|
60
65
|
CouplingCollector: () => CouplingCollector,
|
|
61
66
|
CriticalPathResolver: () => CriticalPathResolver,
|
|
62
67
|
DEFAULT_PROVIDER_TIERS: () => DEFAULT_PROVIDER_TIERS,
|
|
63
68
|
DEFAULT_SECURITY_CONFIG: () => DEFAULT_SECURITY_CONFIG,
|
|
69
|
+
DEFAULT_STABILITY_THRESHOLDS: () => DEFAULT_STABILITY_THRESHOLDS,
|
|
64
70
|
DEFAULT_STATE: () => DEFAULT_STATE,
|
|
65
71
|
DEFAULT_STREAM_INDEX: () => DEFAULT_STREAM_INDEX,
|
|
66
72
|
DESTRUCTIVE_BASH: () => DESTRUCTIVE_BASH,
|
|
67
73
|
DepDepthCollector: () => DepDepthCollector,
|
|
74
|
+
DirectionSchema: () => DirectionSchema,
|
|
68
75
|
EXTENSION_MAP: () => EXTENSION_MAP,
|
|
69
76
|
EmitInteractionInputSchema: () => EmitInteractionInputSchema,
|
|
70
77
|
EntropyAnalyzer: () => EntropyAnalyzer,
|
|
@@ -90,6 +97,11 @@ __export(index_exports, {
|
|
|
90
97
|
NoOpSink: () => NoOpSink,
|
|
91
98
|
NoOpTelemetryAdapter: () => NoOpTelemetryAdapter,
|
|
92
99
|
PatternConfigSchema: () => PatternConfigSchema,
|
|
100
|
+
PredictionEngine: () => PredictionEngine,
|
|
101
|
+
PredictionOptionsSchema: () => PredictionOptionsSchema,
|
|
102
|
+
PredictionRegressionResultSchema: () => RegressionResultSchema,
|
|
103
|
+
PredictionResultSchema: () => PredictionResultSchema,
|
|
104
|
+
PredictionWarningSchema: () => PredictionWarningSchema,
|
|
93
105
|
ProjectScanner: () => ProjectScanner,
|
|
94
106
|
QuestionSchema: () => QuestionSchema,
|
|
95
107
|
REQUIRED_SECTIONS: () => REQUIRED_SECTIONS,
|
|
@@ -105,10 +117,19 @@ __export(index_exports, {
|
|
|
105
117
|
SharableLayerSchema: () => SharableLayerSchema,
|
|
106
118
|
SharableSecurityRulesSchema: () => SharableSecurityRulesSchema,
|
|
107
119
|
SkillEventSchema: () => SkillEventSchema,
|
|
120
|
+
SpecImpactEstimateSchema: () => SpecImpactEstimateSchema,
|
|
121
|
+
SpecImpactEstimator: () => SpecImpactEstimator,
|
|
122
|
+
SpecImpactSignalsSchema: () => SpecImpactSignalsSchema,
|
|
123
|
+
StabilityForecastSchema: () => StabilityForecastSchema,
|
|
108
124
|
StreamIndexSchema: () => StreamIndexSchema,
|
|
109
125
|
StreamInfoSchema: () => StreamInfoSchema,
|
|
110
126
|
ThresholdConfigSchema: () => ThresholdConfigSchema,
|
|
127
|
+
TimelineFileSchema: () => TimelineFileSchema,
|
|
128
|
+
TimelineManager: () => TimelineManager,
|
|
129
|
+
TimelineSnapshotSchema: () => TimelineSnapshotSchema,
|
|
111
130
|
TransitionSchema: () => TransitionSchema,
|
|
131
|
+
TrendLineSchema: () => TrendLineSchema,
|
|
132
|
+
TrendResultSchema: () => TrendResultSchema,
|
|
112
133
|
TypeScriptParser: () => TypeScriptParser,
|
|
113
134
|
VERSION: () => VERSION,
|
|
114
135
|
ViolationSchema: () => ViolationSchema,
|
|
@@ -123,6 +144,7 @@ __export(index_exports, {
|
|
|
123
144
|
appendSessionEntry: () => appendSessionEntry,
|
|
124
145
|
applyFixes: () => applyFixes,
|
|
125
146
|
applyHotspotDowngrade: () => applyHotspotDowngrade,
|
|
147
|
+
applyRecencyWeights: () => applyRecencyWeights,
|
|
126
148
|
applySyncChanges: () => applySyncChanges,
|
|
127
149
|
archMatchers: () => archMatchers,
|
|
128
150
|
archModule: () => archModule,
|
|
@@ -140,6 +162,7 @@ __export(index_exports, {
|
|
|
140
162
|
checkEligibility: () => checkEligibility,
|
|
141
163
|
checkEvidenceCoverage: () => checkEvidenceCoverage,
|
|
142
164
|
checkTaint: () => checkTaint,
|
|
165
|
+
classifyConfidence: () => classifyConfidence,
|
|
143
166
|
classifyFinding: () => classifyFinding,
|
|
144
167
|
clearEventHashCache: () => clearEventHashCache,
|
|
145
168
|
clearFailuresCache: () => clearFailuresCache,
|
|
@@ -242,7 +265,7 @@ __export(index_exports, {
|
|
|
242
265
|
parseDateFromEntry: () => parseDateFromEntry,
|
|
243
266
|
parseDiff: () => parseDiff,
|
|
244
267
|
parseFile: () => parseFile,
|
|
245
|
-
parseFrontmatter: () =>
|
|
268
|
+
parseFrontmatter: () => parseFrontmatter2,
|
|
246
269
|
parseHarnessIgnore: () => parseHarnessIgnore,
|
|
247
270
|
parseLiteLLMData: () => parseLiteLLMData,
|
|
248
271
|
parseManifest: () => parseManifest,
|
|
@@ -251,6 +274,7 @@ __export(index_exports, {
|
|
|
251
274
|
parseSize: () => parseSize,
|
|
252
275
|
pathTraversalRules: () => pathTraversalRules,
|
|
253
276
|
previewFix: () => previewFix,
|
|
277
|
+
projectValue: () => projectValue,
|
|
254
278
|
promoteSessionLearnings: () => promoteSessionLearnings,
|
|
255
279
|
pruneLearnings: () => pruneLearnings,
|
|
256
280
|
reactRules: () => reactRules,
|
|
@@ -318,6 +342,8 @@ __export(index_exports, {
|
|
|
318
342
|
validateKnowledgeMap: () => validateKnowledgeMap,
|
|
319
343
|
validatePatternConfig: () => validatePatternConfig,
|
|
320
344
|
violationId: () => violationId,
|
|
345
|
+
weeksUntilThreshold: () => weeksUntilThreshold,
|
|
346
|
+
weightedLinearRegression: () => weightedLinearRegression,
|
|
321
347
|
writeConfig: () => writeConfig,
|
|
322
348
|
writeLockfile: () => writeLockfile,
|
|
323
349
|
writeSessionSummary: () => writeSessionSummary,
|
|
@@ -345,17 +371,17 @@ var import_node_path = require("path");
|
|
|
345
371
|
var import_glob = require("glob");
|
|
346
372
|
var accessAsync = (0, import_util.promisify)(import_fs.access);
|
|
347
373
|
var readFileAsync = (0, import_util.promisify)(import_fs.readFile);
|
|
348
|
-
async function fileExists(
|
|
374
|
+
async function fileExists(path28) {
|
|
349
375
|
try {
|
|
350
|
-
await accessAsync(
|
|
376
|
+
await accessAsync(path28, import_fs.constants.F_OK);
|
|
351
377
|
return true;
|
|
352
378
|
} catch {
|
|
353
379
|
return false;
|
|
354
380
|
}
|
|
355
381
|
}
|
|
356
|
-
async function readFileContent(
|
|
382
|
+
async function readFileContent(path28) {
|
|
357
383
|
try {
|
|
358
|
-
const content = await readFileAsync(
|
|
384
|
+
const content = await readFileAsync(path28, "utf-8");
|
|
359
385
|
return (0, import_types.Ok)(content);
|
|
360
386
|
} catch (error) {
|
|
361
387
|
return (0, import_types.Err)(error);
|
|
@@ -406,15 +432,15 @@ function validateConfig(data, schema) {
|
|
|
406
432
|
let message = "Configuration validation failed";
|
|
407
433
|
const suggestions = [];
|
|
408
434
|
if (firstError) {
|
|
409
|
-
const
|
|
410
|
-
const pathDisplay =
|
|
435
|
+
const path28 = firstError.path.join(".");
|
|
436
|
+
const pathDisplay = path28 ? ` at "${path28}"` : "";
|
|
411
437
|
if (firstError.code === "invalid_type") {
|
|
412
438
|
const received = firstError.received;
|
|
413
439
|
const expected = firstError.expected;
|
|
414
440
|
if (received === "undefined") {
|
|
415
441
|
code = "MISSING_FIELD";
|
|
416
442
|
message = `Missing required field${pathDisplay}: ${firstError.message}`;
|
|
417
|
-
suggestions.push(`Field "${
|
|
443
|
+
suggestions.push(`Field "${path28}" is required and must be of type "${expected}"`);
|
|
418
444
|
} else {
|
|
419
445
|
code = "INVALID_TYPE";
|
|
420
446
|
message = `Invalid type${pathDisplay}: ${firstError.message}`;
|
|
@@ -630,27 +656,27 @@ function extractSections(content) {
|
|
|
630
656
|
}
|
|
631
657
|
return sections.map((section) => buildAgentMapSection(section, lines));
|
|
632
658
|
}
|
|
633
|
-
function isExternalLink(
|
|
634
|
-
return
|
|
659
|
+
function isExternalLink(path28) {
|
|
660
|
+
return path28.startsWith("http://") || path28.startsWith("https://") || path28.startsWith("#") || path28.startsWith("mailto:");
|
|
635
661
|
}
|
|
636
662
|
function resolveLinkPath(linkPath, baseDir) {
|
|
637
663
|
return linkPath.startsWith(".") ? (0, import_path.join)(baseDir, linkPath) : linkPath;
|
|
638
664
|
}
|
|
639
|
-
async function validateAgentsMap(
|
|
640
|
-
const contentResult = await readFileContent(
|
|
665
|
+
async function validateAgentsMap(path28 = "./AGENTS.md") {
|
|
666
|
+
const contentResult = await readFileContent(path28);
|
|
641
667
|
if (!contentResult.ok) {
|
|
642
668
|
return (0, import_types.Err)(
|
|
643
669
|
createError(
|
|
644
670
|
"PARSE_ERROR",
|
|
645
671
|
`Failed to read AGENTS.md: ${contentResult.error.message}`,
|
|
646
|
-
{ path:
|
|
672
|
+
{ path: path28 },
|
|
647
673
|
["Ensure the file exists", "Check file permissions"]
|
|
648
674
|
)
|
|
649
675
|
);
|
|
650
676
|
}
|
|
651
677
|
const content = contentResult.value;
|
|
652
678
|
const sections = extractSections(content);
|
|
653
|
-
const baseDir = (0, import_path.dirname)(
|
|
679
|
+
const baseDir = (0, import_path.dirname)(path28);
|
|
654
680
|
const sectionTitles = sections.map((s) => s.title);
|
|
655
681
|
const missingSections = REQUIRED_SECTIONS.filter(
|
|
656
682
|
(required) => !sectionTitles.some((title) => title.toLowerCase().includes(required.toLowerCase()))
|
|
@@ -791,8 +817,8 @@ async function checkDocCoverage(domain, options = {}) {
|
|
|
791
817
|
|
|
792
818
|
// src/context/knowledge-map.ts
|
|
793
819
|
var import_path3 = require("path");
|
|
794
|
-
function suggestFix(
|
|
795
|
-
const targetName = (0, import_path3.basename)(
|
|
820
|
+
function suggestFix(path28, existingFiles) {
|
|
821
|
+
const targetName = (0, import_path3.basename)(path28).toLowerCase();
|
|
796
822
|
const similar = existingFiles.find((file) => {
|
|
797
823
|
const fileName = (0, import_path3.basename)(file).toLowerCase();
|
|
798
824
|
return fileName.includes(targetName) || targetName.includes(fileName);
|
|
@@ -800,7 +826,7 @@ function suggestFix(path26, existingFiles) {
|
|
|
800
826
|
if (similar) {
|
|
801
827
|
return `Did you mean "${similar}"?`;
|
|
802
828
|
}
|
|
803
|
-
return `Create the file "${
|
|
829
|
+
return `Create the file "${path28}" or remove the link`;
|
|
804
830
|
}
|
|
805
831
|
async function validateKnowledgeMap(rootDir = process.cwd()) {
|
|
806
832
|
const agentsPath = (0, import_path3.join)(rootDir, "AGENTS.md");
|
|
@@ -1406,8 +1432,8 @@ function createBoundaryValidator(schema, name) {
|
|
|
1406
1432
|
return (0, import_types.Ok)(result.data);
|
|
1407
1433
|
}
|
|
1408
1434
|
const suggestions = result.error.issues.map((issue) => {
|
|
1409
|
-
const
|
|
1410
|
-
return
|
|
1435
|
+
const path28 = issue.path.join(".");
|
|
1436
|
+
return path28 ? `${path28}: ${issue.message}` : issue.message;
|
|
1411
1437
|
});
|
|
1412
1438
|
return (0, import_types.Err)(
|
|
1413
1439
|
createError(
|
|
@@ -2039,11 +2065,11 @@ function processExportListSpecifiers(exportDecl, exports2) {
|
|
|
2039
2065
|
var TypeScriptParser = class {
|
|
2040
2066
|
name = "typescript";
|
|
2041
2067
|
extensions = [".ts", ".tsx", ".mts", ".cts"];
|
|
2042
|
-
async parseFile(
|
|
2043
|
-
const contentResult = await readFileContent(
|
|
2068
|
+
async parseFile(path28) {
|
|
2069
|
+
const contentResult = await readFileContent(path28);
|
|
2044
2070
|
if (!contentResult.ok) {
|
|
2045
2071
|
return (0, import_types.Err)(
|
|
2046
|
-
createParseError("NOT_FOUND", `File not found: ${
|
|
2072
|
+
createParseError("NOT_FOUND", `File not found: ${path28}`, { path: path28 }, [
|
|
2047
2073
|
"Check that the file exists",
|
|
2048
2074
|
"Verify the path is correct"
|
|
2049
2075
|
])
|
|
@@ -2053,7 +2079,7 @@ var TypeScriptParser = class {
|
|
|
2053
2079
|
const ast = (0, import_typescript_estree.parse)(contentResult.value, {
|
|
2054
2080
|
loc: true,
|
|
2055
2081
|
range: true,
|
|
2056
|
-
jsx:
|
|
2082
|
+
jsx: path28.endsWith(".tsx"),
|
|
2057
2083
|
errorOnUnknownASTType: false
|
|
2058
2084
|
});
|
|
2059
2085
|
return (0, import_types.Ok)({
|
|
@@ -2064,7 +2090,7 @@ var TypeScriptParser = class {
|
|
|
2064
2090
|
} catch (e) {
|
|
2065
2091
|
const error = e;
|
|
2066
2092
|
return (0, import_types.Err)(
|
|
2067
|
-
createParseError("SYNTAX_ERROR", `Failed to parse ${
|
|
2093
|
+
createParseError("SYNTAX_ERROR", `Failed to parse ${path28}: ${error.message}`, { path: path28 }, [
|
|
2068
2094
|
"Check for syntax errors in the file",
|
|
2069
2095
|
"Ensure valid TypeScript syntax"
|
|
2070
2096
|
])
|
|
@@ -2249,22 +2275,22 @@ function extractInlineRefs(content) {
|
|
|
2249
2275
|
}
|
|
2250
2276
|
return refs;
|
|
2251
2277
|
}
|
|
2252
|
-
async function parseDocumentationFile(
|
|
2253
|
-
const contentResult = await readFileContent(
|
|
2278
|
+
async function parseDocumentationFile(path28) {
|
|
2279
|
+
const contentResult = await readFileContent(path28);
|
|
2254
2280
|
if (!contentResult.ok) {
|
|
2255
2281
|
return (0, import_types.Err)(
|
|
2256
2282
|
createEntropyError(
|
|
2257
2283
|
"PARSE_ERROR",
|
|
2258
|
-
`Failed to read documentation file: ${
|
|
2259
|
-
{ file:
|
|
2284
|
+
`Failed to read documentation file: ${path28}`,
|
|
2285
|
+
{ file: path28 },
|
|
2260
2286
|
["Check that the file exists"]
|
|
2261
2287
|
)
|
|
2262
2288
|
);
|
|
2263
2289
|
}
|
|
2264
2290
|
const content = contentResult.value;
|
|
2265
|
-
const type =
|
|
2291
|
+
const type = path28.endsWith(".md") ? "markdown" : "text";
|
|
2266
2292
|
return (0, import_types.Ok)({
|
|
2267
|
-
path:
|
|
2293
|
+
path: path28,
|
|
2268
2294
|
type,
|
|
2269
2295
|
content,
|
|
2270
2296
|
codeBlocks: extractCodeBlocks(content),
|
|
@@ -6604,10 +6630,10 @@ function resolveThresholds2(scope, config) {
|
|
|
6604
6630
|
}
|
|
6605
6631
|
const merged = { ...projectThresholds };
|
|
6606
6632
|
for (const [category, moduleValue] of Object.entries(moduleOverrides)) {
|
|
6607
|
-
const
|
|
6608
|
-
if (
|
|
6633
|
+
const projectValue2 = projectThresholds[category];
|
|
6634
|
+
if (projectValue2 !== void 0 && typeof projectValue2 === "object" && !Array.isArray(projectValue2) && typeof moduleValue === "object" && !Array.isArray(moduleValue)) {
|
|
6609
6635
|
merged[category] = {
|
|
6610
|
-
...
|
|
6636
|
+
...projectValue2,
|
|
6611
6637
|
...moduleValue
|
|
6612
6638
|
};
|
|
6613
6639
|
} else {
|
|
@@ -6803,77 +6829,1173 @@ var archMatchers = {
|
|
|
6803
6829
|
toHaveMaxDepDepth
|
|
6804
6830
|
};
|
|
6805
6831
|
|
|
6806
|
-
// src/
|
|
6832
|
+
// src/architecture/timeline-types.ts
|
|
6807
6833
|
var import_zod4 = require("zod");
|
|
6808
|
-
var
|
|
6809
|
-
|
|
6810
|
-
|
|
6811
|
-
|
|
6812
|
-
|
|
6834
|
+
var CategorySnapshotSchema = import_zod4.z.object({
|
|
6835
|
+
/** Aggregate metric value (e.g., violation count, avg complexity) */
|
|
6836
|
+
value: import_zod4.z.number(),
|
|
6837
|
+
/** Count of violations in this category */
|
|
6838
|
+
violationCount: import_zod4.z.number()
|
|
6839
|
+
});
|
|
6840
|
+
var TimelineSnapshotSchema = import_zod4.z.object({
|
|
6841
|
+
/** ISO 8601 timestamp of capture */
|
|
6842
|
+
capturedAt: import_zod4.z.string().datetime(),
|
|
6843
|
+
/** Git commit hash at capture time */
|
|
6844
|
+
commitHash: import_zod4.z.string(),
|
|
6845
|
+
/** Composite stability score (0-100, higher is healthier) */
|
|
6846
|
+
stabilityScore: import_zod4.z.number().min(0).max(100),
|
|
6847
|
+
/** Per-category metric aggregates */
|
|
6848
|
+
metrics: import_zod4.z.record(ArchMetricCategorySchema, CategorySnapshotSchema)
|
|
6849
|
+
});
|
|
6850
|
+
var TimelineFileSchema = import_zod4.z.object({
|
|
6851
|
+
version: import_zod4.z.literal(1),
|
|
6852
|
+
snapshots: import_zod4.z.array(TimelineSnapshotSchema)
|
|
6853
|
+
});
|
|
6854
|
+
var TrendLineSchema = import_zod4.z.object({
|
|
6855
|
+
/** Current value */
|
|
6856
|
+
current: import_zod4.z.number(),
|
|
6857
|
+
/** Previous value (from comparison snapshot) */
|
|
6858
|
+
previous: import_zod4.z.number(),
|
|
6859
|
+
/** Absolute delta (current - previous) */
|
|
6860
|
+
delta: import_zod4.z.number(),
|
|
6861
|
+
/** Direction indicator */
|
|
6862
|
+
direction: import_zod4.z.enum(["improving", "stable", "declining"])
|
|
6863
|
+
});
|
|
6864
|
+
var TrendResultSchema = import_zod4.z.object({
|
|
6865
|
+
/** Overall stability trend */
|
|
6866
|
+
stability: TrendLineSchema,
|
|
6867
|
+
/** Per-category trends */
|
|
6868
|
+
categories: import_zod4.z.record(ArchMetricCategorySchema, TrendLineSchema),
|
|
6869
|
+
/** Number of snapshots analyzed */
|
|
6870
|
+
snapshotCount: import_zod4.z.number(),
|
|
6871
|
+
/** Time range covered */
|
|
6872
|
+
from: import_zod4.z.string(),
|
|
6873
|
+
to: import_zod4.z.string()
|
|
6874
|
+
});
|
|
6875
|
+
var DEFAULT_STABILITY_THRESHOLDS = {
|
|
6876
|
+
"circular-deps": 5,
|
|
6877
|
+
"layer-violations": 10,
|
|
6878
|
+
complexity: 100,
|
|
6879
|
+
coupling: 2,
|
|
6880
|
+
"forbidden-imports": 5,
|
|
6881
|
+
"module-size": 10,
|
|
6882
|
+
"dependency-depth": 10
|
|
6883
|
+
};
|
|
6884
|
+
|
|
6885
|
+
// src/architecture/timeline-manager.ts
|
|
6886
|
+
var import_node_fs4 = require("fs");
|
|
6887
|
+
var import_node_crypto3 = require("crypto");
|
|
6888
|
+
var import_node_path7 = require("path");
|
|
6889
|
+
var ALL_CATEGORIES = ArchMetricCategorySchema.options;
|
|
6890
|
+
var TimelineManager = class {
|
|
6891
|
+
timelinePath;
|
|
6892
|
+
constructor(rootDir) {
|
|
6893
|
+
this.timelinePath = (0, import_node_path7.join)(rootDir, ".harness", "arch", "timeline.json");
|
|
6894
|
+
}
|
|
6895
|
+
/**
|
|
6896
|
+
* Load timeline from disk.
|
|
6897
|
+
* Returns empty TimelineFile if file does not exist or is invalid.
|
|
6898
|
+
*/
|
|
6899
|
+
load() {
|
|
6900
|
+
if (!(0, import_node_fs4.existsSync)(this.timelinePath)) {
|
|
6901
|
+
return { version: 1, snapshots: [] };
|
|
6902
|
+
}
|
|
6903
|
+
try {
|
|
6904
|
+
const raw = (0, import_node_fs4.readFileSync)(this.timelinePath, "utf-8");
|
|
6905
|
+
const data = JSON.parse(raw);
|
|
6906
|
+
const parsed = TimelineFileSchema.safeParse(data);
|
|
6907
|
+
if (!parsed.success) {
|
|
6908
|
+
console.error(
|
|
6909
|
+
`Timeline validation failed for ${this.timelinePath}:`,
|
|
6910
|
+
parsed.error.format()
|
|
6911
|
+
);
|
|
6912
|
+
return { version: 1, snapshots: [] };
|
|
6913
|
+
}
|
|
6914
|
+
return parsed.data;
|
|
6915
|
+
} catch (error) {
|
|
6916
|
+
console.error(`Error loading timeline from ${this.timelinePath}:`, error);
|
|
6917
|
+
return { version: 1, snapshots: [] };
|
|
6918
|
+
}
|
|
6919
|
+
}
|
|
6920
|
+
/**
|
|
6921
|
+
* Save timeline to disk using atomic write (temp file + rename).
|
|
6922
|
+
* Creates parent directories if they do not exist.
|
|
6923
|
+
*/
|
|
6924
|
+
save(timeline) {
|
|
6925
|
+
const dir = (0, import_node_path7.dirname)(this.timelinePath);
|
|
6926
|
+
if (!(0, import_node_fs4.existsSync)(dir)) {
|
|
6927
|
+
(0, import_node_fs4.mkdirSync)(dir, { recursive: true });
|
|
6928
|
+
}
|
|
6929
|
+
const tmp = this.timelinePath + "." + (0, import_node_crypto3.randomBytes)(4).toString("hex") + ".tmp";
|
|
6930
|
+
(0, import_node_fs4.writeFileSync)(tmp, JSON.stringify(timeline, null, 2));
|
|
6931
|
+
(0, import_node_fs4.renameSync)(tmp, this.timelinePath);
|
|
6932
|
+
}
|
|
6933
|
+
/**
|
|
6934
|
+
* Capture a new snapshot from current metric results.
|
|
6935
|
+
* Aggregates MetricResult[] by category, computes stability score,
|
|
6936
|
+
* appends to timeline (or replaces if same commitHash), and saves.
|
|
6937
|
+
*/
|
|
6938
|
+
capture(results, commitHash) {
|
|
6939
|
+
const metrics = this.aggregateByCategory(results);
|
|
6940
|
+
const stabilityScore = this.computeStabilityScore(metrics);
|
|
6941
|
+
const snapshot = {
|
|
6942
|
+
capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6943
|
+
commitHash,
|
|
6944
|
+
stabilityScore,
|
|
6945
|
+
metrics
|
|
6946
|
+
};
|
|
6947
|
+
const timeline = this.load();
|
|
6948
|
+
const lastIndex = timeline.snapshots.length - 1;
|
|
6949
|
+
if (lastIndex >= 0 && timeline.snapshots[lastIndex].commitHash === commitHash) {
|
|
6950
|
+
timeline.snapshots[lastIndex] = snapshot;
|
|
6951
|
+
} else {
|
|
6952
|
+
timeline.snapshots.push(snapshot);
|
|
6953
|
+
}
|
|
6954
|
+
this.save(timeline);
|
|
6955
|
+
return snapshot;
|
|
6956
|
+
}
|
|
6957
|
+
/**
|
|
6958
|
+
* Compute trends between snapshots over a window.
|
|
6959
|
+
* @param options.last - Number of recent snapshots to analyze (default: 10)
|
|
6960
|
+
* @param options.since - ISO date string to filter snapshots from
|
|
6961
|
+
*/
|
|
6962
|
+
trends(options) {
|
|
6963
|
+
const timeline = this.load();
|
|
6964
|
+
let snapshots = timeline.snapshots;
|
|
6965
|
+
if (options?.since) {
|
|
6966
|
+
const sinceDate = new Date(options.since);
|
|
6967
|
+
snapshots = snapshots.filter((s) => new Date(s.capturedAt) >= sinceDate);
|
|
6968
|
+
}
|
|
6969
|
+
if (options?.last && snapshots.length > options.last) {
|
|
6970
|
+
snapshots = snapshots.slice(-options.last);
|
|
6971
|
+
}
|
|
6972
|
+
if (snapshots.length === 0) {
|
|
6973
|
+
return this.emptyTrendResult();
|
|
6974
|
+
}
|
|
6975
|
+
if (snapshots.length === 1) {
|
|
6976
|
+
const only = snapshots[0];
|
|
6977
|
+
const m = only.metrics;
|
|
6978
|
+
return {
|
|
6979
|
+
stability: this.buildTrendLine(only.stabilityScore, only.stabilityScore, true),
|
|
6980
|
+
categories: this.buildCategoryTrends(m, m),
|
|
6981
|
+
snapshotCount: 1,
|
|
6982
|
+
from: only.capturedAt,
|
|
6983
|
+
to: only.capturedAt
|
|
6984
|
+
};
|
|
6985
|
+
}
|
|
6986
|
+
const first = snapshots[0];
|
|
6987
|
+
const last = snapshots[snapshots.length - 1];
|
|
6988
|
+
return {
|
|
6989
|
+
stability: this.buildTrendLine(last.stabilityScore, first.stabilityScore, true),
|
|
6990
|
+
categories: this.buildCategoryTrends(
|
|
6991
|
+
last.metrics,
|
|
6992
|
+
first.metrics
|
|
6993
|
+
),
|
|
6994
|
+
snapshotCount: snapshots.length,
|
|
6995
|
+
from: first.capturedAt,
|
|
6996
|
+
to: last.capturedAt
|
|
6997
|
+
};
|
|
6998
|
+
}
|
|
6999
|
+
/**
|
|
7000
|
+
* Compute composite stability score from category metrics.
|
|
7001
|
+
* Equal weight across all categories. Score is 0-100 (higher = healthier).
|
|
7002
|
+
* health = max(0, 1 - (value / threshold)) per category.
|
|
7003
|
+
*/
|
|
7004
|
+
computeStabilityScore(metrics, thresholds = DEFAULT_STABILITY_THRESHOLDS) {
|
|
7005
|
+
const healthScores = [];
|
|
7006
|
+
for (const category of ALL_CATEGORIES) {
|
|
7007
|
+
const snapshot = metrics[category];
|
|
7008
|
+
if (!snapshot) {
|
|
7009
|
+
healthScores.push(1);
|
|
7010
|
+
continue;
|
|
7011
|
+
}
|
|
7012
|
+
const threshold = thresholds[category] ?? 10;
|
|
7013
|
+
const health = Math.max(0, 1 - snapshot.value / threshold);
|
|
7014
|
+
healthScores.push(health);
|
|
7015
|
+
}
|
|
7016
|
+
const mean = healthScores.reduce((sum, h) => sum + h, 0) / healthScores.length;
|
|
7017
|
+
return Math.round(mean * 100);
|
|
7018
|
+
}
|
|
7019
|
+
// --- Private helpers ---
|
|
7020
|
+
aggregateByCategory(results) {
|
|
7021
|
+
const metrics = {};
|
|
7022
|
+
for (const result of results) {
|
|
7023
|
+
const existing = metrics[result.category];
|
|
7024
|
+
if (existing) {
|
|
7025
|
+
existing.value += result.value;
|
|
7026
|
+
existing.violationCount += result.violations.length;
|
|
7027
|
+
} else {
|
|
7028
|
+
metrics[result.category] = {
|
|
7029
|
+
value: result.value,
|
|
7030
|
+
violationCount: result.violations.length
|
|
7031
|
+
};
|
|
7032
|
+
}
|
|
7033
|
+
}
|
|
7034
|
+
for (const category of ALL_CATEGORIES) {
|
|
7035
|
+
if (!metrics[category]) {
|
|
7036
|
+
metrics[category] = { value: 0, violationCount: 0 };
|
|
7037
|
+
}
|
|
7038
|
+
}
|
|
7039
|
+
return metrics;
|
|
7040
|
+
}
|
|
7041
|
+
buildTrendLine(current, previous, isStabilityScore) {
|
|
7042
|
+
const delta = current - previous;
|
|
7043
|
+
let direction;
|
|
7044
|
+
if (Math.abs(delta) < 2) {
|
|
7045
|
+
direction = "stable";
|
|
7046
|
+
} else if (isStabilityScore) {
|
|
7047
|
+
direction = delta > 0 ? "improving" : "declining";
|
|
7048
|
+
} else {
|
|
7049
|
+
direction = delta < 0 ? "improving" : "declining";
|
|
7050
|
+
}
|
|
7051
|
+
return { current, previous, delta, direction };
|
|
7052
|
+
}
|
|
7053
|
+
buildCategoryTrends(currentMetrics, previousMetrics) {
|
|
7054
|
+
const trends = {};
|
|
7055
|
+
for (const category of ALL_CATEGORIES) {
|
|
7056
|
+
const current = currentMetrics[category]?.value ?? 0;
|
|
7057
|
+
const previous = previousMetrics[category]?.value ?? 0;
|
|
7058
|
+
trends[category] = this.buildTrendLine(current, previous, false);
|
|
7059
|
+
}
|
|
7060
|
+
return trends;
|
|
7061
|
+
}
|
|
7062
|
+
emptyTrendResult() {
|
|
7063
|
+
const zeroLine = { current: 0, previous: 0, delta: 0, direction: "stable" };
|
|
7064
|
+
const categories = {};
|
|
7065
|
+
for (const category of ALL_CATEGORIES) {
|
|
7066
|
+
categories[category] = { ...zeroLine };
|
|
7067
|
+
}
|
|
7068
|
+
return {
|
|
7069
|
+
stability: { ...zeroLine },
|
|
7070
|
+
categories,
|
|
7071
|
+
snapshotCount: 0,
|
|
7072
|
+
from: "",
|
|
7073
|
+
to: ""
|
|
7074
|
+
};
|
|
7075
|
+
}
|
|
7076
|
+
};
|
|
7077
|
+
|
|
7078
|
+
// src/architecture/prediction-types.ts
|
|
7079
|
+
var import_zod5 = require("zod");
|
|
7080
|
+
var ConfidenceTierSchema = import_zod5.z.enum(["high", "medium", "low"]);
|
|
7081
|
+
var RegressionResultSchema = import_zod5.z.object({
|
|
7082
|
+
slope: import_zod5.z.number(),
|
|
7083
|
+
intercept: import_zod5.z.number(),
|
|
7084
|
+
rSquared: import_zod5.z.number().min(0).max(1),
|
|
7085
|
+
dataPoints: import_zod5.z.number().int().min(0)
|
|
7086
|
+
});
|
|
7087
|
+
var DirectionSchema = import_zod5.z.enum(["improving", "stable", "declining"]);
|
|
7088
|
+
var CategoryForecastSchema = import_zod5.z.object({
|
|
7089
|
+
category: ArchMetricCategorySchema,
|
|
7090
|
+
current: import_zod5.z.number(),
|
|
7091
|
+
threshold: import_zod5.z.number(),
|
|
7092
|
+
projectedValue4w: import_zod5.z.number(),
|
|
7093
|
+
projectedValue8w: import_zod5.z.number(),
|
|
7094
|
+
projectedValue12w: import_zod5.z.number(),
|
|
7095
|
+
thresholdCrossingWeeks: import_zod5.z.number().nullable(),
|
|
7096
|
+
confidence: ConfidenceTierSchema,
|
|
7097
|
+
regression: RegressionResultSchema,
|
|
7098
|
+
direction: DirectionSchema
|
|
7099
|
+
});
|
|
7100
|
+
var SpecImpactSignalsSchema = import_zod5.z.object({
|
|
7101
|
+
newFileCount: import_zod5.z.number().int().min(0),
|
|
7102
|
+
affectedLayers: import_zod5.z.array(import_zod5.z.string()),
|
|
7103
|
+
newDependencies: import_zod5.z.number().int().min(0),
|
|
7104
|
+
phaseCount: import_zod5.z.number().int().min(0)
|
|
7105
|
+
});
|
|
7106
|
+
var SpecImpactEstimateSchema = import_zod5.z.object({
|
|
7107
|
+
specPath: import_zod5.z.string(),
|
|
7108
|
+
featureName: import_zod5.z.string(),
|
|
7109
|
+
signals: SpecImpactSignalsSchema,
|
|
7110
|
+
deltas: import_zod5.z.record(ArchMetricCategorySchema, import_zod5.z.number()).optional()
|
|
7111
|
+
});
|
|
7112
|
+
var ContributingFeatureSchema = import_zod5.z.object({
|
|
7113
|
+
name: import_zod5.z.string(),
|
|
7114
|
+
specPath: import_zod5.z.string(),
|
|
7115
|
+
delta: import_zod5.z.number()
|
|
7116
|
+
});
|
|
7117
|
+
var AdjustedForecastSchema = import_zod5.z.object({
|
|
7118
|
+
baseline: CategoryForecastSchema,
|
|
7119
|
+
adjusted: CategoryForecastSchema,
|
|
7120
|
+
contributingFeatures: import_zod5.z.array(ContributingFeatureSchema)
|
|
7121
|
+
});
|
|
7122
|
+
var PredictionWarningSchema = import_zod5.z.object({
|
|
7123
|
+
severity: import_zod5.z.enum(["critical", "warning", "info"]),
|
|
7124
|
+
category: ArchMetricCategorySchema,
|
|
7125
|
+
message: import_zod5.z.string(),
|
|
7126
|
+
weeksUntil: import_zod5.z.number(),
|
|
7127
|
+
confidence: ConfidenceTierSchema,
|
|
7128
|
+
contributingFeatures: import_zod5.z.array(import_zod5.z.string())
|
|
7129
|
+
});
|
|
7130
|
+
var StabilityForecastSchema = import_zod5.z.object({
|
|
7131
|
+
current: import_zod5.z.number(),
|
|
7132
|
+
projected4w: import_zod5.z.number(),
|
|
7133
|
+
projected8w: import_zod5.z.number(),
|
|
7134
|
+
projected12w: import_zod5.z.number(),
|
|
7135
|
+
confidence: ConfidenceTierSchema,
|
|
7136
|
+
direction: DirectionSchema
|
|
7137
|
+
});
|
|
7138
|
+
var PredictionResultSchema = import_zod5.z.object({
|
|
7139
|
+
generatedAt: import_zod5.z.string(),
|
|
7140
|
+
snapshotsUsed: import_zod5.z.number().int().min(0),
|
|
7141
|
+
timelineRange: import_zod5.z.object({
|
|
7142
|
+
from: import_zod5.z.string(),
|
|
7143
|
+
to: import_zod5.z.string()
|
|
7144
|
+
}),
|
|
7145
|
+
stabilityForecast: StabilityForecastSchema,
|
|
7146
|
+
categories: import_zod5.z.record(ArchMetricCategorySchema, AdjustedForecastSchema),
|
|
7147
|
+
warnings: import_zod5.z.array(PredictionWarningSchema)
|
|
7148
|
+
});
|
|
7149
|
+
var PredictionOptionsSchema = import_zod5.z.object({
|
|
7150
|
+
horizon: import_zod5.z.number().int().min(1).default(12),
|
|
7151
|
+
includeRoadmap: import_zod5.z.boolean().default(true),
|
|
7152
|
+
categories: import_zod5.z.array(ArchMetricCategorySchema).optional(),
|
|
7153
|
+
thresholds: import_zod5.z.record(ArchMetricCategorySchema, import_zod5.z.number()).optional()
|
|
7154
|
+
});
|
|
7155
|
+
|
|
7156
|
+
// src/architecture/regression.ts
|
|
7157
|
+
function computeWeightedSums(points) {
|
|
7158
|
+
let sumW = 0, sumWt = 0, sumWv = 0, sumWtt = 0, sumWtv = 0;
|
|
7159
|
+
for (const p of points) {
|
|
7160
|
+
const w = p.weight;
|
|
7161
|
+
sumW += w;
|
|
7162
|
+
sumWt += w * p.t;
|
|
7163
|
+
sumWv += w * p.value;
|
|
7164
|
+
sumWtt += w * p.t * p.t;
|
|
7165
|
+
sumWtv += w * p.t * p.value;
|
|
7166
|
+
}
|
|
7167
|
+
return { sumW, sumWt, sumWv, sumWtt, sumWtv };
|
|
7168
|
+
}
|
|
7169
|
+
function computeRSquared(points, slope, intercept, meanV) {
|
|
7170
|
+
let ssRes = 0, ssTot = 0;
|
|
7171
|
+
for (const p of points) {
|
|
7172
|
+
const predicted = slope * p.t + intercept;
|
|
7173
|
+
ssRes += p.weight * (p.value - predicted) ** 2;
|
|
7174
|
+
ssTot += p.weight * (p.value - meanV) ** 2;
|
|
7175
|
+
}
|
|
7176
|
+
return ssTot < 1e-12 ? 1 : Math.max(0, 1 - ssRes / ssTot);
|
|
7177
|
+
}
|
|
7178
|
+
function weightedLinearRegression(points) {
|
|
7179
|
+
if (points.length < 2) {
|
|
7180
|
+
throw new Error(`Regression requires at least 2 data points, got ${points.length}`);
|
|
7181
|
+
}
|
|
7182
|
+
const n = points.length;
|
|
7183
|
+
const { sumW, sumWt, sumWv, sumWtt, sumWtv } = computeWeightedSums(points);
|
|
7184
|
+
const meanT = sumWt / sumW;
|
|
7185
|
+
const meanV = sumWv / sumW;
|
|
7186
|
+
const denominator = sumWtt - sumWt * sumWt / sumW;
|
|
7187
|
+
if (Math.abs(denominator) < 1e-12) {
|
|
7188
|
+
return { slope: 0, intercept: meanV, rSquared: 0, dataPoints: n };
|
|
7189
|
+
}
|
|
7190
|
+
const slope = (sumWtv - sumWt * sumWv / sumW) / denominator;
|
|
7191
|
+
const intercept = meanV - slope * meanT;
|
|
7192
|
+
const rSquared = computeRSquared(points, slope, intercept, meanV);
|
|
7193
|
+
return { slope, intercept, rSquared, dataPoints: n };
|
|
7194
|
+
}
|
|
7195
|
+
function applyRecencyWeights(values, decay = 0.85) {
|
|
7196
|
+
const n = values.length;
|
|
7197
|
+
return values.map((v, i) => ({
|
|
7198
|
+
t: v.t,
|
|
7199
|
+
value: v.value,
|
|
7200
|
+
weight: Math.pow(decay, n - 1 - i)
|
|
7201
|
+
}));
|
|
7202
|
+
}
|
|
7203
|
+
function projectValue(fit, t) {
|
|
7204
|
+
return fit.slope * t + fit.intercept;
|
|
7205
|
+
}
|
|
7206
|
+
function weeksUntilThreshold(fit, currentT, threshold) {
|
|
7207
|
+
if (fit.slope <= 0) {
|
|
7208
|
+
return null;
|
|
7209
|
+
}
|
|
7210
|
+
const currentProjected = projectValue(fit, currentT);
|
|
7211
|
+
if (currentProjected >= threshold) {
|
|
7212
|
+
return null;
|
|
7213
|
+
}
|
|
7214
|
+
const weeks = (threshold - currentProjected) / fit.slope;
|
|
7215
|
+
return Math.ceil(weeks);
|
|
7216
|
+
}
|
|
7217
|
+
function classifyConfidence(rSquared, dataPoints) {
|
|
7218
|
+
if (rSquared >= 0.7 && dataPoints >= 5) return "high";
|
|
7219
|
+
if (rSquared >= 0.4 && dataPoints >= 3) return "medium";
|
|
7220
|
+
return "low";
|
|
7221
|
+
}
|
|
7222
|
+
|
|
7223
|
+
// src/architecture/prediction-engine.ts
|
|
7224
|
+
var fs5 = __toESM(require("fs"));
|
|
7225
|
+
var path2 = __toESM(require("path"));
|
|
7226
|
+
|
|
7227
|
+
// src/roadmap/parse.ts
|
|
7228
|
+
var import_types13 = require("@harness-engineering/types");
|
|
7229
|
+
var VALID_STATUSES = /* @__PURE__ */ new Set([
|
|
7230
|
+
"backlog",
|
|
7231
|
+
"planned",
|
|
7232
|
+
"in-progress",
|
|
7233
|
+
"done",
|
|
7234
|
+
"blocked"
|
|
7235
|
+
]);
|
|
7236
|
+
var EM_DASH = "\u2014";
|
|
7237
|
+
var VALID_PRIORITIES = /* @__PURE__ */ new Set(["P0", "P1", "P2", "P3"]);
|
|
7238
|
+
function parseRoadmap(markdown) {
|
|
7239
|
+
const fmMatch = markdown.match(/^---\n([\s\S]*?)\n---/);
|
|
7240
|
+
if (!fmMatch) {
|
|
7241
|
+
return (0, import_types13.Err)(new Error("Missing or malformed YAML frontmatter"));
|
|
7242
|
+
}
|
|
7243
|
+
const fmResult = parseFrontmatter(fmMatch[1]);
|
|
7244
|
+
if (!fmResult.ok) return fmResult;
|
|
7245
|
+
const body = markdown.slice(fmMatch[0].length);
|
|
7246
|
+
const milestonesResult = parseMilestones(body);
|
|
7247
|
+
if (!milestonesResult.ok) return milestonesResult;
|
|
7248
|
+
const historyResult = parseAssignmentHistory(body);
|
|
7249
|
+
if (!historyResult.ok) return historyResult;
|
|
7250
|
+
return (0, import_types13.Ok)({
|
|
7251
|
+
frontmatter: fmResult.value,
|
|
7252
|
+
milestones: milestonesResult.value,
|
|
7253
|
+
assignmentHistory: historyResult.value
|
|
7254
|
+
});
|
|
7255
|
+
}
|
|
7256
|
+
function parseFrontmatter(raw) {
|
|
7257
|
+
const lines = raw.split("\n");
|
|
7258
|
+
const map = /* @__PURE__ */ new Map();
|
|
7259
|
+
for (const line of lines) {
|
|
7260
|
+
const idx = line.indexOf(":");
|
|
7261
|
+
if (idx === -1) continue;
|
|
7262
|
+
const key = line.slice(0, idx).trim();
|
|
7263
|
+
const val = line.slice(idx + 1).trim();
|
|
7264
|
+
map.set(key, val);
|
|
7265
|
+
}
|
|
7266
|
+
const project = map.get("project");
|
|
7267
|
+
const versionStr = map.get("version");
|
|
7268
|
+
const lastSynced = map.get("last_synced");
|
|
7269
|
+
const lastManualEdit = map.get("last_manual_edit");
|
|
7270
|
+
const created = map.get("created");
|
|
7271
|
+
const updated = map.get("updated");
|
|
7272
|
+
if (!project || !versionStr || !lastSynced || !lastManualEdit) {
|
|
7273
|
+
return (0, import_types13.Err)(
|
|
7274
|
+
new Error(
|
|
7275
|
+
"Frontmatter missing required fields: project, version, last_synced, last_manual_edit"
|
|
7276
|
+
)
|
|
7277
|
+
);
|
|
7278
|
+
}
|
|
7279
|
+
const version = parseInt(versionStr, 10);
|
|
7280
|
+
if (isNaN(version)) {
|
|
7281
|
+
return (0, import_types13.Err)(new Error("Frontmatter version must be a number"));
|
|
7282
|
+
}
|
|
7283
|
+
const fm = { project, version, lastSynced, lastManualEdit };
|
|
7284
|
+
if (created) fm.created = created;
|
|
7285
|
+
if (updated) fm.updated = updated;
|
|
7286
|
+
return (0, import_types13.Ok)(fm);
|
|
7287
|
+
}
|
|
7288
|
+
function parseMilestones(body) {
|
|
7289
|
+
const milestones = [];
|
|
7290
|
+
const h2Pattern = /^## (.+)$/gm;
|
|
7291
|
+
const h2Matches = [];
|
|
7292
|
+
let match;
|
|
7293
|
+
let bodyEnd = body.length;
|
|
7294
|
+
while ((match = h2Pattern.exec(body)) !== null) {
|
|
7295
|
+
if (match[1] === "Assignment History") {
|
|
7296
|
+
bodyEnd = match.index;
|
|
7297
|
+
break;
|
|
7298
|
+
}
|
|
7299
|
+
h2Matches.push({ heading: match[1], startIndex: match.index, fullMatch: match[0] });
|
|
7300
|
+
}
|
|
7301
|
+
for (let i = 0; i < h2Matches.length; i++) {
|
|
7302
|
+
const h2 = h2Matches[i];
|
|
7303
|
+
const nextStart = i + 1 < h2Matches.length ? h2Matches[i + 1].startIndex : bodyEnd;
|
|
7304
|
+
const sectionBody = body.slice(h2.startIndex + h2.fullMatch.length, nextStart);
|
|
7305
|
+
const isBacklog = h2.heading === "Backlog";
|
|
7306
|
+
const milestoneName = isBacklog ? "Backlog" : h2.heading.replace(/^Milestone:\s*/, "");
|
|
7307
|
+
const featuresResult = parseFeatures(sectionBody);
|
|
7308
|
+
if (!featuresResult.ok) return featuresResult;
|
|
7309
|
+
milestones.push({
|
|
7310
|
+
name: milestoneName,
|
|
7311
|
+
isBacklog,
|
|
7312
|
+
features: featuresResult.value
|
|
7313
|
+
});
|
|
7314
|
+
}
|
|
7315
|
+
return (0, import_types13.Ok)(milestones);
|
|
7316
|
+
}
|
|
7317
|
+
function parseFeatures(sectionBody) {
|
|
7318
|
+
const features = [];
|
|
7319
|
+
const h3Pattern = /^### (?:Feature: )?(.+)$/gm;
|
|
7320
|
+
const h3Matches = [];
|
|
7321
|
+
let match;
|
|
7322
|
+
while ((match = h3Pattern.exec(sectionBody)) !== null) {
|
|
7323
|
+
h3Matches.push({ name: match[1], startIndex: match.index, fullMatch: match[0] });
|
|
7324
|
+
}
|
|
7325
|
+
for (let i = 0; i < h3Matches.length; i++) {
|
|
7326
|
+
const h3 = h3Matches[i];
|
|
7327
|
+
const nextStart = i + 1 < h3Matches.length ? h3Matches[i + 1].startIndex : sectionBody.length;
|
|
7328
|
+
const featureBody = sectionBody.slice(h3.startIndex + h3.fullMatch.length, nextStart);
|
|
7329
|
+
const featureResult = parseFeatureFields(h3.name, featureBody);
|
|
7330
|
+
if (!featureResult.ok) return featureResult;
|
|
7331
|
+
features.push(featureResult.value);
|
|
7332
|
+
}
|
|
7333
|
+
return (0, import_types13.Ok)(features);
|
|
7334
|
+
}
|
|
7335
|
+
function extractFieldMap(body) {
|
|
7336
|
+
const fieldMap = /* @__PURE__ */ new Map();
|
|
7337
|
+
const fieldPattern = /^- \*\*(.+?):\*\* (.+)$/gm;
|
|
7338
|
+
let match;
|
|
7339
|
+
while ((match = fieldPattern.exec(body)) !== null) {
|
|
7340
|
+
fieldMap.set(match[1], match[2]);
|
|
7341
|
+
}
|
|
7342
|
+
return fieldMap;
|
|
7343
|
+
}
|
|
7344
|
+
function parseListField(fieldMap, ...keys) {
|
|
7345
|
+
let raw = EM_DASH;
|
|
7346
|
+
for (const key of keys) {
|
|
7347
|
+
const val = fieldMap.get(key);
|
|
7348
|
+
if (val !== void 0) {
|
|
7349
|
+
raw = val;
|
|
7350
|
+
break;
|
|
7351
|
+
}
|
|
7352
|
+
}
|
|
7353
|
+
if (raw === EM_DASH || raw === "none") return [];
|
|
7354
|
+
return raw.split(",").map((s) => s.trim());
|
|
7355
|
+
}
|
|
7356
|
+
function parseFeatureFields(name, body) {
|
|
7357
|
+
const fieldMap = extractFieldMap(body);
|
|
7358
|
+
const statusRaw = fieldMap.get("Status");
|
|
7359
|
+
if (!statusRaw || !VALID_STATUSES.has(statusRaw)) {
|
|
7360
|
+
return (0, import_types13.Err)(
|
|
7361
|
+
new Error(
|
|
7362
|
+
`Feature "${name}" has invalid status: "${statusRaw ?? "(missing)"}". Valid statuses: ${[...VALID_STATUSES].join(", ")}`
|
|
7363
|
+
)
|
|
7364
|
+
);
|
|
7365
|
+
}
|
|
7366
|
+
const specRaw = fieldMap.get("Spec") ?? EM_DASH;
|
|
7367
|
+
const plans = parseListField(fieldMap, "Plans", "Plan");
|
|
7368
|
+
const blockedBy = parseListField(fieldMap, "Blocked by", "Blockers");
|
|
7369
|
+
const assigneeRaw = fieldMap.get("Assignee") ?? EM_DASH;
|
|
7370
|
+
const priorityRaw = fieldMap.get("Priority") ?? EM_DASH;
|
|
7371
|
+
const externalIdRaw = fieldMap.get("External-ID") ?? EM_DASH;
|
|
7372
|
+
if (priorityRaw !== EM_DASH && !VALID_PRIORITIES.has(priorityRaw)) {
|
|
7373
|
+
return (0, import_types13.Err)(
|
|
7374
|
+
new Error(
|
|
7375
|
+
`Feature "${name}" has invalid priority: "${priorityRaw}". Valid priorities: ${[...VALID_PRIORITIES].join(", ")}`
|
|
7376
|
+
)
|
|
7377
|
+
);
|
|
7378
|
+
}
|
|
7379
|
+
return (0, import_types13.Ok)({
|
|
7380
|
+
name,
|
|
7381
|
+
status: statusRaw,
|
|
7382
|
+
spec: specRaw === EM_DASH ? null : specRaw,
|
|
7383
|
+
plans,
|
|
7384
|
+
blockedBy,
|
|
7385
|
+
summary: fieldMap.get("Summary") ?? "",
|
|
7386
|
+
assignee: assigneeRaw === EM_DASH ? null : assigneeRaw,
|
|
7387
|
+
priority: priorityRaw === EM_DASH ? null : priorityRaw,
|
|
7388
|
+
externalId: externalIdRaw === EM_DASH ? null : externalIdRaw
|
|
7389
|
+
});
|
|
7390
|
+
}
|
|
7391
|
+
function parseAssignmentHistory(body) {
|
|
7392
|
+
const historyMatch = body.match(/^## Assignment History\s*\n/m);
|
|
7393
|
+
if (!historyMatch || historyMatch.index === void 0) return (0, import_types13.Ok)([]);
|
|
7394
|
+
const historyStart = historyMatch.index + historyMatch[0].length;
|
|
7395
|
+
const rawHistoryBody = body.slice(historyStart);
|
|
7396
|
+
const nextH2 = rawHistoryBody.search(/^## /m);
|
|
7397
|
+
const historyBody = nextH2 === -1 ? rawHistoryBody : rawHistoryBody.slice(0, nextH2);
|
|
7398
|
+
const records = [];
|
|
7399
|
+
const lines = historyBody.split("\n");
|
|
7400
|
+
let pastHeader = false;
|
|
7401
|
+
for (const line of lines) {
|
|
7402
|
+
const trimmed = line.trim();
|
|
7403
|
+
if (!trimmed.startsWith("|")) continue;
|
|
7404
|
+
if (!pastHeader) {
|
|
7405
|
+
if (trimmed.match(/^\|[-\s|]+\|$/)) {
|
|
7406
|
+
pastHeader = true;
|
|
7407
|
+
}
|
|
7408
|
+
continue;
|
|
7409
|
+
}
|
|
7410
|
+
const cells = trimmed.split("|").map((c) => c.trim()).filter((c) => c.length > 0);
|
|
7411
|
+
if (cells.length < 4) continue;
|
|
7412
|
+
const action = cells[2];
|
|
7413
|
+
if (!["assigned", "completed", "unassigned"].includes(action)) continue;
|
|
7414
|
+
records.push({
|
|
7415
|
+
feature: cells[0],
|
|
7416
|
+
assignee: cells[1],
|
|
7417
|
+
action,
|
|
7418
|
+
date: cells[3]
|
|
7419
|
+
});
|
|
7420
|
+
}
|
|
7421
|
+
return (0, import_types13.Ok)(records);
|
|
7422
|
+
}
|
|
7423
|
+
|
|
7424
|
+
// src/architecture/prediction-engine.ts
|
|
7425
|
+
var ALL_CATEGORIES2 = ArchMetricCategorySchema.options;
|
|
7426
|
+
var DIRECTION_THRESHOLD = 1e-3;
|
|
7427
|
+
var PredictionEngine = class {
|
|
7428
|
+
constructor(rootDir, timelineManager, estimator) {
|
|
7429
|
+
this.rootDir = rootDir;
|
|
7430
|
+
this.timelineManager = timelineManager;
|
|
7431
|
+
this.estimator = estimator;
|
|
7432
|
+
}
|
|
7433
|
+
rootDir;
|
|
7434
|
+
timelineManager;
|
|
7435
|
+
estimator;
|
|
7436
|
+
/**
|
|
7437
|
+
* Produce a PredictionResult with per-category forecasts and warnings.
|
|
7438
|
+
* Throws if fewer than 3 snapshots are available.
|
|
7439
|
+
*/
|
|
7440
|
+
predict(options) {
|
|
7441
|
+
const opts = this.resolveOptions(options);
|
|
7442
|
+
const timeline = this.timelineManager.load();
|
|
7443
|
+
const snapshots = timeline.snapshots;
|
|
7444
|
+
if (snapshots.length < 3) {
|
|
7445
|
+
throw new Error(
|
|
7446
|
+
`PredictionEngine requires at least 3 snapshots, got ${snapshots.length}. Run "harness snapshot" to capture more data points.`
|
|
7447
|
+
);
|
|
7448
|
+
}
|
|
7449
|
+
const thresholds = this.resolveThresholds(opts);
|
|
7450
|
+
const categoriesToProcess = opts.categories ?? [...ALL_CATEGORIES2];
|
|
7451
|
+
const firstDate = new Date(snapshots[0].capturedAt).getTime();
|
|
7452
|
+
const lastSnapshot = snapshots[snapshots.length - 1];
|
|
7453
|
+
const currentT = (new Date(lastSnapshot.capturedAt).getTime() - firstDate) / (7 * 24 * 60 * 60 * 1e3);
|
|
7454
|
+
const baselines = {};
|
|
7455
|
+
for (const category of ALL_CATEGORIES2) {
|
|
7456
|
+
const threshold = thresholds[category];
|
|
7457
|
+
const shouldProcess = categoriesToProcess.includes(category);
|
|
7458
|
+
if (!shouldProcess) {
|
|
7459
|
+
baselines[category] = this.zeroForecast(category, threshold);
|
|
7460
|
+
continue;
|
|
7461
|
+
}
|
|
7462
|
+
const timeSeries = this.extractTimeSeries(snapshots, category, firstDate);
|
|
7463
|
+
baselines[category] = this.forecastCategory(
|
|
7464
|
+
category,
|
|
7465
|
+
timeSeries,
|
|
7466
|
+
currentT,
|
|
7467
|
+
threshold,
|
|
7468
|
+
opts.horizon
|
|
7469
|
+
);
|
|
7470
|
+
}
|
|
7471
|
+
const specImpacts = this.computeSpecImpacts(opts);
|
|
7472
|
+
const categories = {};
|
|
7473
|
+
for (const category of ALL_CATEGORIES2) {
|
|
7474
|
+
const baseline = baselines[category];
|
|
7475
|
+
const threshold = thresholds[category];
|
|
7476
|
+
if (!specImpacts || specImpacts.length === 0) {
|
|
7477
|
+
categories[category] = {
|
|
7478
|
+
baseline,
|
|
7479
|
+
adjusted: baseline,
|
|
7480
|
+
contributingFeatures: []
|
|
7481
|
+
};
|
|
7482
|
+
continue;
|
|
7483
|
+
}
|
|
7484
|
+
let totalDelta = 0;
|
|
7485
|
+
const contributing = [];
|
|
7486
|
+
for (const impact of specImpacts) {
|
|
7487
|
+
const delta = impact.deltas?.[category] ?? 0;
|
|
7488
|
+
if (delta !== 0) {
|
|
7489
|
+
totalDelta += delta;
|
|
7490
|
+
contributing.push({
|
|
7491
|
+
name: impact.featureName,
|
|
7492
|
+
specPath: impact.specPath,
|
|
7493
|
+
delta
|
|
7494
|
+
});
|
|
7495
|
+
}
|
|
7496
|
+
}
|
|
7497
|
+
if (totalDelta === 0) {
|
|
7498
|
+
categories[category] = {
|
|
7499
|
+
baseline,
|
|
7500
|
+
adjusted: baseline,
|
|
7501
|
+
contributingFeatures: []
|
|
7502
|
+
};
|
|
7503
|
+
continue;
|
|
7504
|
+
}
|
|
7505
|
+
const adjusted = {
|
|
7506
|
+
...baseline,
|
|
7507
|
+
projectedValue4w: baseline.projectedValue4w + totalDelta,
|
|
7508
|
+
projectedValue8w: baseline.projectedValue8w + totalDelta,
|
|
7509
|
+
projectedValue12w: baseline.projectedValue12w + totalDelta
|
|
7510
|
+
};
|
|
7511
|
+
const adjustedFit = {
|
|
7512
|
+
slope: baseline.regression.slope,
|
|
7513
|
+
intercept: baseline.regression.intercept + totalDelta,
|
|
7514
|
+
rSquared: baseline.regression.rSquared,
|
|
7515
|
+
dataPoints: baseline.regression.dataPoints
|
|
7516
|
+
};
|
|
7517
|
+
adjusted.thresholdCrossingWeeks = weeksUntilThreshold(adjustedFit, currentT, threshold);
|
|
7518
|
+
adjusted.regression = {
|
|
7519
|
+
slope: adjustedFit.slope,
|
|
7520
|
+
intercept: adjustedFit.intercept,
|
|
7521
|
+
rSquared: adjustedFit.rSquared,
|
|
7522
|
+
dataPoints: adjustedFit.dataPoints
|
|
7523
|
+
};
|
|
7524
|
+
categories[category] = {
|
|
7525
|
+
baseline,
|
|
7526
|
+
adjusted,
|
|
7527
|
+
contributingFeatures: contributing
|
|
7528
|
+
};
|
|
7529
|
+
}
|
|
7530
|
+
const warnings = this.generateWarnings(
|
|
7531
|
+
categories,
|
|
7532
|
+
opts.horizon
|
|
7533
|
+
);
|
|
7534
|
+
const stabilityForecast = this.computeStabilityForecast(
|
|
7535
|
+
categories,
|
|
7536
|
+
thresholds,
|
|
7537
|
+
snapshots
|
|
7538
|
+
);
|
|
7539
|
+
return {
|
|
7540
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7541
|
+
snapshotsUsed: snapshots.length,
|
|
7542
|
+
timelineRange: {
|
|
7543
|
+
from: snapshots[0].capturedAt,
|
|
7544
|
+
to: lastSnapshot.capturedAt
|
|
7545
|
+
},
|
|
7546
|
+
stabilityForecast,
|
|
7547
|
+
categories,
|
|
7548
|
+
warnings
|
|
7549
|
+
};
|
|
7550
|
+
}
|
|
7551
|
+
// --- Private helpers ---
|
|
7552
|
+
resolveOptions(options) {
|
|
7553
|
+
return {
|
|
7554
|
+
horizon: options?.horizon ?? 12,
|
|
7555
|
+
includeRoadmap: options?.includeRoadmap ?? true,
|
|
7556
|
+
categories: options?.categories,
|
|
7557
|
+
thresholds: options?.thresholds
|
|
7558
|
+
};
|
|
7559
|
+
}
|
|
7560
|
+
resolveThresholds(opts) {
|
|
7561
|
+
const base = { ...DEFAULT_STABILITY_THRESHOLDS };
|
|
7562
|
+
if (opts.thresholds) {
|
|
7563
|
+
for (const [key, value] of Object.entries(opts.thresholds)) {
|
|
7564
|
+
if (value !== void 0) {
|
|
7565
|
+
base[key] = value;
|
|
7566
|
+
}
|
|
7567
|
+
}
|
|
7568
|
+
}
|
|
7569
|
+
return base;
|
|
7570
|
+
}
|
|
7571
|
+
/**
|
|
7572
|
+
* Extract time series for a single category from snapshots.
|
|
7573
|
+
* Returns array of { t (weeks from first), value } sorted oldest first.
|
|
7574
|
+
*/
|
|
7575
|
+
extractTimeSeries(snapshots, category, firstDateMs) {
|
|
7576
|
+
return snapshots.map((s) => {
|
|
7577
|
+
const t = (new Date(s.capturedAt).getTime() - firstDateMs) / (7 * 24 * 60 * 60 * 1e3);
|
|
7578
|
+
const metrics = s.metrics;
|
|
7579
|
+
const value = metrics[category]?.value ?? 0;
|
|
7580
|
+
return { t, value };
|
|
7581
|
+
});
|
|
7582
|
+
}
|
|
7583
|
+
/**
|
|
7584
|
+
* Produce a CategoryForecast for a single category using regression.
|
|
7585
|
+
*/
|
|
7586
|
+
forecastCategory(category, timeSeries, currentT, threshold, horizon = 12) {
|
|
7587
|
+
const weighted = applyRecencyWeights(timeSeries, 0.85);
|
|
7588
|
+
const fit = weightedLinearRegression(weighted);
|
|
7589
|
+
const current = timeSeries[timeSeries.length - 1].value;
|
|
7590
|
+
const h3 = Math.round(horizon / 3);
|
|
7591
|
+
const h2 = Math.round(horizon * 2 / 3);
|
|
7592
|
+
const projected4w = projectValue(fit, currentT + h3);
|
|
7593
|
+
const projected8w = projectValue(fit, currentT + h2);
|
|
7594
|
+
const projected12w = projectValue(fit, currentT + horizon);
|
|
7595
|
+
const crossing = weeksUntilThreshold(fit, currentT, threshold);
|
|
7596
|
+
const confidence = classifyConfidence(fit.rSquared, fit.dataPoints);
|
|
7597
|
+
const direction = this.classifyDirection(fit.slope);
|
|
7598
|
+
return {
|
|
7599
|
+
category,
|
|
7600
|
+
current,
|
|
7601
|
+
threshold,
|
|
7602
|
+
projectedValue4w: projected4w,
|
|
7603
|
+
projectedValue8w: projected8w,
|
|
7604
|
+
projectedValue12w: projected12w,
|
|
7605
|
+
thresholdCrossingWeeks: crossing,
|
|
7606
|
+
confidence,
|
|
7607
|
+
regression: {
|
|
7608
|
+
slope: fit.slope,
|
|
7609
|
+
intercept: fit.intercept,
|
|
7610
|
+
rSquared: fit.rSquared,
|
|
7611
|
+
dataPoints: fit.dataPoints
|
|
7612
|
+
},
|
|
7613
|
+
direction
|
|
7614
|
+
};
|
|
7615
|
+
}
|
|
7616
|
+
classifyDirection(slope) {
|
|
7617
|
+
if (Math.abs(slope) < DIRECTION_THRESHOLD) return "stable";
|
|
7618
|
+
return slope > 0 ? "declining" : "improving";
|
|
7619
|
+
}
|
|
7620
|
+
zeroForecast(category, threshold) {
|
|
7621
|
+
return {
|
|
7622
|
+
category,
|
|
7623
|
+
current: 0,
|
|
7624
|
+
threshold,
|
|
7625
|
+
projectedValue4w: 0,
|
|
7626
|
+
projectedValue8w: 0,
|
|
7627
|
+
projectedValue12w: 0,
|
|
7628
|
+
thresholdCrossingWeeks: null,
|
|
7629
|
+
confidence: "low",
|
|
7630
|
+
regression: { slope: 0, intercept: 0, rSquared: 0, dataPoints: 0 },
|
|
7631
|
+
direction: "stable"
|
|
7632
|
+
};
|
|
7633
|
+
}
|
|
7634
|
+
/**
|
|
7635
|
+
* Generate warnings based on severity rules from spec:
|
|
7636
|
+
* - critical: threshold crossing <= 4w, confidence high or medium
|
|
7637
|
+
* - warning: threshold crossing <= 8w, confidence high or medium
|
|
7638
|
+
* - info: threshold crossing <= 12w, any confidence
|
|
7639
|
+
*/
|
|
7640
|
+
generateWarnings(categories, horizon = 12) {
|
|
7641
|
+
const warnings = [];
|
|
7642
|
+
const criticalWindow = Math.round(horizon / 3);
|
|
7643
|
+
const warningWindow = Math.round(horizon * 2 / 3);
|
|
7644
|
+
for (const category of ALL_CATEGORIES2) {
|
|
7645
|
+
const af = categories[category];
|
|
7646
|
+
if (!af) continue;
|
|
7647
|
+
const forecast = af.adjusted;
|
|
7648
|
+
const crossing = forecast.thresholdCrossingWeeks;
|
|
7649
|
+
if (crossing === null || crossing <= 0) continue;
|
|
7650
|
+
let severity = null;
|
|
7651
|
+
if (crossing <= criticalWindow && (forecast.confidence === "high" || forecast.confidence === "medium")) {
|
|
7652
|
+
severity = "critical";
|
|
7653
|
+
} else if (crossing <= warningWindow && (forecast.confidence === "high" || forecast.confidence === "medium")) {
|
|
7654
|
+
severity = "warning";
|
|
7655
|
+
} else if (crossing <= horizon) {
|
|
7656
|
+
severity = "info";
|
|
7657
|
+
}
|
|
7658
|
+
if (severity) {
|
|
7659
|
+
const contributingNames = af.contributingFeatures.map((f) => f.name);
|
|
7660
|
+
warnings.push({
|
|
7661
|
+
severity,
|
|
7662
|
+
category,
|
|
7663
|
+
message: `${category} projected to exceed threshold (~${crossing}w, ${forecast.confidence} confidence)`,
|
|
7664
|
+
weeksUntil: crossing,
|
|
7665
|
+
confidence: forecast.confidence,
|
|
7666
|
+
contributingFeatures: contributingNames
|
|
7667
|
+
});
|
|
7668
|
+
}
|
|
7669
|
+
}
|
|
7670
|
+
return warnings;
|
|
7671
|
+
}
|
|
7672
|
+
/**
|
|
7673
|
+
* Compute composite stability forecast by projecting per-category values
|
|
7674
|
+
* forward and computing stability scores at each horizon.
|
|
7675
|
+
*/
|
|
7676
|
+
computeStabilityForecast(categories, thresholds, _snapshots) {
|
|
7677
|
+
const currentMetrics = this.buildMetricsFromForecasts(categories, "current");
|
|
7678
|
+
const current = this.timelineManager.computeStabilityScore(currentMetrics, thresholds);
|
|
7679
|
+
const metrics4w = this.buildMetricsFromForecasts(categories, "4w");
|
|
7680
|
+
const projected4w = this.timelineManager.computeStabilityScore(metrics4w, thresholds);
|
|
7681
|
+
const metrics8w = this.buildMetricsFromForecasts(categories, "8w");
|
|
7682
|
+
const projected8w = this.timelineManager.computeStabilityScore(metrics8w, thresholds);
|
|
7683
|
+
const metrics12w = this.buildMetricsFromForecasts(categories, "12w");
|
|
7684
|
+
const projected12w = this.timelineManager.computeStabilityScore(metrics12w, thresholds);
|
|
7685
|
+
const delta = projected12w - current;
|
|
7686
|
+
let direction;
|
|
7687
|
+
if (Math.abs(delta) < 2) {
|
|
7688
|
+
direction = "stable";
|
|
7689
|
+
} else {
|
|
7690
|
+
direction = delta > 0 ? "improving" : "declining";
|
|
7691
|
+
}
|
|
7692
|
+
const confidences = ALL_CATEGORIES2.map((c) => categories[c]?.adjusted.confidence ?? "low");
|
|
7693
|
+
const confidence = this.medianConfidence(confidences);
|
|
7694
|
+
return { current, projected4w, projected8w, projected12w, confidence, direction };
|
|
7695
|
+
}
|
|
7696
|
+
buildMetricsFromForecasts(categories, horizon) {
|
|
7697
|
+
const metrics = {};
|
|
7698
|
+
for (const cat of ALL_CATEGORIES2) {
|
|
7699
|
+
const forecast = categories[cat]?.adjusted;
|
|
7700
|
+
let value = 0;
|
|
7701
|
+
if (forecast) {
|
|
7702
|
+
switch (horizon) {
|
|
7703
|
+
case "current":
|
|
7704
|
+
value = forecast.current;
|
|
7705
|
+
break;
|
|
7706
|
+
case "4w":
|
|
7707
|
+
value = forecast.projectedValue4w;
|
|
7708
|
+
break;
|
|
7709
|
+
case "8w":
|
|
7710
|
+
value = forecast.projectedValue8w;
|
|
7711
|
+
break;
|
|
7712
|
+
case "12w":
|
|
7713
|
+
value = forecast.projectedValue12w;
|
|
7714
|
+
break;
|
|
7715
|
+
}
|
|
7716
|
+
}
|
|
7717
|
+
metrics[cat] = { value: Math.max(0, value), violationCount: 0 };
|
|
7718
|
+
}
|
|
7719
|
+
return metrics;
|
|
7720
|
+
}
|
|
7721
|
+
medianConfidence(confidences) {
|
|
7722
|
+
const order = { low: 0, medium: 1, high: 2 };
|
|
7723
|
+
const sorted = [...confidences].sort((a, b) => order[a] - order[b]);
|
|
7724
|
+
const mid = Math.floor(sorted.length / 2);
|
|
7725
|
+
return sorted[mid] ?? "low";
|
|
7726
|
+
}
|
|
7727
|
+
/**
|
|
7728
|
+
* Load roadmap features, estimate spec impacts via the estimator.
|
|
7729
|
+
* Returns null if estimator is null or includeRoadmap is false.
|
|
7730
|
+
*/
|
|
7731
|
+
computeSpecImpacts(opts) {
|
|
7732
|
+
if (!this.estimator || !opts.includeRoadmap) {
|
|
7733
|
+
return null;
|
|
7734
|
+
}
|
|
7735
|
+
try {
|
|
7736
|
+
const roadmapPath = path2.join(this.rootDir, "roadmap.md");
|
|
7737
|
+
const raw = fs5.readFileSync(roadmapPath, "utf-8");
|
|
7738
|
+
const parseResult = parseRoadmap(raw);
|
|
7739
|
+
if (!parseResult.ok) return null;
|
|
7740
|
+
const features = [];
|
|
7741
|
+
for (const milestone of parseResult.value.milestones) {
|
|
7742
|
+
for (const feature of milestone.features) {
|
|
7743
|
+
if (feature.status === "planned" || feature.status === "in-progress") {
|
|
7744
|
+
features.push({ name: feature.name, spec: feature.spec });
|
|
7745
|
+
}
|
|
7746
|
+
}
|
|
7747
|
+
}
|
|
7748
|
+
if (features.length === 0) return null;
|
|
7749
|
+
return this.estimator.estimateAll(features);
|
|
7750
|
+
} catch {
|
|
7751
|
+
return null;
|
|
7752
|
+
}
|
|
7753
|
+
}
|
|
7754
|
+
};
|
|
7755
|
+
|
|
7756
|
+
// src/architecture/spec-impact-estimator.ts
|
|
7757
|
+
var fs6 = __toESM(require("fs"));
|
|
7758
|
+
var path3 = __toESM(require("path"));
|
|
7759
|
+
var DEFAULT_COEFFICIENTS = {
|
|
7760
|
+
newFileModuleSize: 0.3,
|
|
7761
|
+
newFileComplexity: 1.5,
|
|
7762
|
+
layerViolation: 0.5,
|
|
7763
|
+
depCoupling: 0.2,
|
|
7764
|
+
depDepth: 0.3,
|
|
7765
|
+
phaseComplexity: 2
|
|
7766
|
+
};
|
|
7767
|
+
var SpecImpactEstimator = class {
|
|
7768
|
+
constructor(rootDir, coefficients) {
|
|
7769
|
+
this.rootDir = rootDir;
|
|
7770
|
+
this.coefficients = { ...DEFAULT_COEFFICIENTS, ...coefficients };
|
|
7771
|
+
this.layerNames = this.loadLayerNames();
|
|
7772
|
+
}
|
|
7773
|
+
rootDir;
|
|
7774
|
+
coefficients;
|
|
7775
|
+
layerNames;
|
|
7776
|
+
/**
|
|
7777
|
+
* Estimate impact of a single spec file.
|
|
7778
|
+
* @param specPath - Relative path from rootDir to the spec file.
|
|
7779
|
+
*/
|
|
7780
|
+
estimate(specPath) {
|
|
7781
|
+
const absolutePath = path3.join(this.rootDir, specPath);
|
|
7782
|
+
const content = fs6.readFileSync(absolutePath, "utf-8");
|
|
7783
|
+
const newFileCount = this.extractNewFileCount(content);
|
|
7784
|
+
const affectedLayers = this.extractAffectedLayers(content);
|
|
7785
|
+
const newDependencies = this.extractNewDependencies(content);
|
|
7786
|
+
const phaseCount = this.extractPhaseCount(content);
|
|
7787
|
+
const deltas = this.computeDeltas(
|
|
7788
|
+
newFileCount,
|
|
7789
|
+
affectedLayers.length,
|
|
7790
|
+
newDependencies,
|
|
7791
|
+
phaseCount
|
|
7792
|
+
);
|
|
7793
|
+
const h1Match = content.match(/^#\s+(.+)$/m);
|
|
7794
|
+
const featureName = h1Match ? h1Match[1].trim() : path3.basename(specPath, ".md");
|
|
7795
|
+
return {
|
|
7796
|
+
specPath,
|
|
7797
|
+
featureName,
|
|
7798
|
+
signals: {
|
|
7799
|
+
newFileCount,
|
|
7800
|
+
affectedLayers,
|
|
7801
|
+
newDependencies,
|
|
7802
|
+
phaseCount
|
|
7803
|
+
},
|
|
7804
|
+
deltas
|
|
7805
|
+
};
|
|
7806
|
+
}
|
|
7807
|
+
/**
|
|
7808
|
+
* Estimate impact for all planned features that have specs.
|
|
7809
|
+
* Skips features with null specs or specs that don't exist on disk.
|
|
7810
|
+
*/
|
|
7811
|
+
estimateAll(features) {
|
|
7812
|
+
const results = [];
|
|
7813
|
+
for (const feature of features) {
|
|
7814
|
+
if (!feature.spec) continue;
|
|
7815
|
+
const absolutePath = path3.join(this.rootDir, feature.spec);
|
|
7816
|
+
if (!fs6.existsSync(absolutePath)) continue;
|
|
7817
|
+
const estimate = this.estimate(feature.spec);
|
|
7818
|
+
results.push({ ...estimate, featureName: feature.name });
|
|
7819
|
+
}
|
|
7820
|
+
return results;
|
|
7821
|
+
}
|
|
7822
|
+
// --- Private: Signal Extraction ---
|
|
7823
|
+
/**
|
|
7824
|
+
* Count file paths in Technical Design sections that don't exist on disk.
|
|
7825
|
+
* Looks for paths in code blocks (```) under ## Technical Design.
|
|
7826
|
+
*/
|
|
7827
|
+
extractNewFileCount(content) {
|
|
7828
|
+
const techDesignMatch = content.match(/## Technical Design\b[\s\S]*?(?=\n## |\n# |$)/i);
|
|
7829
|
+
if (!techDesignMatch) return 0;
|
|
7830
|
+
const section = techDesignMatch[0];
|
|
7831
|
+
const codeBlocks = section.match(/```[\s\S]*?```/g) ?? [];
|
|
7832
|
+
const filePaths = [];
|
|
7833
|
+
for (const block of codeBlocks) {
|
|
7834
|
+
const inner = block.replace(/^```\w*\n?/, "").replace(/\n?```$/, "");
|
|
7835
|
+
for (const line of inner.split("\n")) {
|
|
7836
|
+
const trimmed = line.trim();
|
|
7837
|
+
if (trimmed.match(/^[\w@.-]+\/[\w./-]+\.\w+$/)) {
|
|
7838
|
+
filePaths.push(trimmed);
|
|
7839
|
+
}
|
|
7840
|
+
}
|
|
7841
|
+
}
|
|
7842
|
+
let count = 0;
|
|
7843
|
+
for (const fp of filePaths) {
|
|
7844
|
+
const absolute = path3.join(this.rootDir, fp);
|
|
7845
|
+
if (!fs6.existsSync(absolute)) {
|
|
7846
|
+
count++;
|
|
7847
|
+
}
|
|
7848
|
+
}
|
|
7849
|
+
return count;
|
|
7850
|
+
}
|
|
7851
|
+
/**
|
|
7852
|
+
* Match layer names from harness.config.json mentioned in the spec.
|
|
7853
|
+
* Returns deduplicated array of matched layer names.
|
|
7854
|
+
*/
|
|
7855
|
+
extractAffectedLayers(content) {
|
|
7856
|
+
if (this.layerNames.length === 0) return [];
|
|
7857
|
+
const matched = /* @__PURE__ */ new Set();
|
|
7858
|
+
for (const layer of this.layerNames) {
|
|
7859
|
+
const pattern = new RegExp(`\\b${this.escapeRegex(layer)}\\b`, "i");
|
|
7860
|
+
if (pattern.test(content)) {
|
|
7861
|
+
matched.add(layer);
|
|
7862
|
+
}
|
|
7863
|
+
}
|
|
7864
|
+
return [...matched].sort();
|
|
7865
|
+
}
|
|
7866
|
+
/**
|
|
7867
|
+
* Count dependency-related keywords: "import", "depend" (covers depends/dependency),
|
|
7868
|
+
* "package" in dependency context.
|
|
7869
|
+
*/
|
|
7870
|
+
extractNewDependencies(content) {
|
|
7871
|
+
const patterns = [/\bimport\b/gi, /\bdepend\w*\b/gi, /\bpackage\b/gi];
|
|
7872
|
+
let count = 0;
|
|
7873
|
+
for (const pattern of patterns) {
|
|
7874
|
+
const matches = content.match(pattern);
|
|
7875
|
+
if (matches) count += matches.length;
|
|
7876
|
+
}
|
|
7877
|
+
return count;
|
|
7878
|
+
}
|
|
7879
|
+
/**
|
|
7880
|
+
* Count H3/H4 headings under "Implementation" or "Implementation Order" sections.
|
|
7881
|
+
*/
|
|
7882
|
+
extractPhaseCount(content) {
|
|
7883
|
+
const implMatch = content.match(/## Implementation\b[\s\S]*?(?=\n## |\n# |$)/i);
|
|
7884
|
+
if (!implMatch) return 0;
|
|
7885
|
+
const section = implMatch[0];
|
|
7886
|
+
const headings = section.match(/^#{3,4}\s+.+$/gm);
|
|
7887
|
+
return headings ? headings.length : 0;
|
|
7888
|
+
}
|
|
7889
|
+
// --- Private: Delta Computation ---
|
|
7890
|
+
computeDeltas(newFileCount, crossLayerCount, newDependencies, phaseCount) {
|
|
7891
|
+
const deltas = {};
|
|
7892
|
+
const c = this.coefficients;
|
|
7893
|
+
const addDelta = (category, value) => {
|
|
7894
|
+
deltas[category] = (deltas[category] ?? 0) + value;
|
|
7895
|
+
};
|
|
7896
|
+
if (newFileCount > 0) {
|
|
7897
|
+
addDelta("module-size", newFileCount * c.newFileModuleSize);
|
|
7898
|
+
addDelta("complexity", newFileCount * c.newFileComplexity);
|
|
7899
|
+
}
|
|
7900
|
+
if (crossLayerCount > 0) {
|
|
7901
|
+
addDelta("layer-violations", crossLayerCount * c.layerViolation);
|
|
7902
|
+
}
|
|
7903
|
+
if (newDependencies > 0) {
|
|
7904
|
+
addDelta("coupling", newDependencies * c.depCoupling);
|
|
7905
|
+
addDelta("dependency-depth", newDependencies * c.depDepth);
|
|
7906
|
+
}
|
|
7907
|
+
if (phaseCount > 1) {
|
|
7908
|
+
addDelta("complexity", (phaseCount - 1) * c.phaseComplexity);
|
|
7909
|
+
}
|
|
7910
|
+
return deltas;
|
|
7911
|
+
}
|
|
7912
|
+
// --- Private: Config Loading ---
|
|
7913
|
+
loadLayerNames() {
|
|
7914
|
+
try {
|
|
7915
|
+
const configPath = path3.join(this.rootDir, "harness.config.json");
|
|
7916
|
+
const raw = fs6.readFileSync(configPath, "utf-8");
|
|
7917
|
+
const config = JSON.parse(raw);
|
|
7918
|
+
return (config.layers ?? []).map((l) => l.name);
|
|
7919
|
+
} catch {
|
|
7920
|
+
return [];
|
|
7921
|
+
}
|
|
7922
|
+
}
|
|
7923
|
+
escapeRegex(str) {
|
|
7924
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
7925
|
+
}
|
|
7926
|
+
};
|
|
7927
|
+
|
|
7928
|
+
// src/state/types.ts
|
|
7929
|
+
var import_zod6 = require("zod");
|
|
7930
|
+
var FailureEntrySchema = import_zod6.z.object({
|
|
7931
|
+
date: import_zod6.z.string(),
|
|
7932
|
+
skill: import_zod6.z.string(),
|
|
7933
|
+
type: import_zod6.z.string(),
|
|
7934
|
+
description: import_zod6.z.string()
|
|
6813
7935
|
});
|
|
6814
|
-
var HandoffSchema =
|
|
6815
|
-
timestamp:
|
|
6816
|
-
fromSkill:
|
|
6817
|
-
phase:
|
|
6818
|
-
summary:
|
|
6819
|
-
completed:
|
|
6820
|
-
pending:
|
|
6821
|
-
concerns:
|
|
6822
|
-
decisions:
|
|
6823
|
-
|
|
6824
|
-
what:
|
|
6825
|
-
why:
|
|
7936
|
+
var HandoffSchema = import_zod6.z.object({
|
|
7937
|
+
timestamp: import_zod6.z.string(),
|
|
7938
|
+
fromSkill: import_zod6.z.string(),
|
|
7939
|
+
phase: import_zod6.z.string(),
|
|
7940
|
+
summary: import_zod6.z.string(),
|
|
7941
|
+
completed: import_zod6.z.array(import_zod6.z.string()).default([]),
|
|
7942
|
+
pending: import_zod6.z.array(import_zod6.z.string()).default([]),
|
|
7943
|
+
concerns: import_zod6.z.array(import_zod6.z.string()).default([]),
|
|
7944
|
+
decisions: import_zod6.z.array(
|
|
7945
|
+
import_zod6.z.object({
|
|
7946
|
+
what: import_zod6.z.string(),
|
|
7947
|
+
why: import_zod6.z.string()
|
|
6826
7948
|
})
|
|
6827
7949
|
).default([]),
|
|
6828
|
-
blockers:
|
|
6829
|
-
contextKeywords:
|
|
7950
|
+
blockers: import_zod6.z.array(import_zod6.z.string()).default([]),
|
|
7951
|
+
contextKeywords: import_zod6.z.array(import_zod6.z.string()).default([])
|
|
6830
7952
|
});
|
|
6831
|
-
var GateCheckSchema =
|
|
6832
|
-
name:
|
|
6833
|
-
passed:
|
|
6834
|
-
command:
|
|
6835
|
-
output:
|
|
6836
|
-
duration:
|
|
7953
|
+
var GateCheckSchema = import_zod6.z.object({
|
|
7954
|
+
name: import_zod6.z.string(),
|
|
7955
|
+
passed: import_zod6.z.boolean(),
|
|
7956
|
+
command: import_zod6.z.string(),
|
|
7957
|
+
output: import_zod6.z.string().optional(),
|
|
7958
|
+
duration: import_zod6.z.number().optional()
|
|
6837
7959
|
});
|
|
6838
|
-
var GateResultSchema =
|
|
6839
|
-
passed:
|
|
6840
|
-
checks:
|
|
7960
|
+
var GateResultSchema = import_zod6.z.object({
|
|
7961
|
+
passed: import_zod6.z.boolean(),
|
|
7962
|
+
checks: import_zod6.z.array(GateCheckSchema)
|
|
6841
7963
|
});
|
|
6842
|
-
var GateConfigSchema =
|
|
6843
|
-
checks:
|
|
6844
|
-
|
|
6845
|
-
name:
|
|
6846
|
-
command:
|
|
7964
|
+
var GateConfigSchema = import_zod6.z.object({
|
|
7965
|
+
checks: import_zod6.z.array(
|
|
7966
|
+
import_zod6.z.object({
|
|
7967
|
+
name: import_zod6.z.string(),
|
|
7968
|
+
command: import_zod6.z.string()
|
|
6847
7969
|
})
|
|
6848
7970
|
).optional(),
|
|
6849
|
-
trace:
|
|
7971
|
+
trace: import_zod6.z.boolean().optional()
|
|
6850
7972
|
});
|
|
6851
|
-
var HarnessStateSchema =
|
|
6852
|
-
schemaVersion:
|
|
6853
|
-
position:
|
|
6854
|
-
phase:
|
|
6855
|
-
task:
|
|
7973
|
+
var HarnessStateSchema = import_zod6.z.object({
|
|
7974
|
+
schemaVersion: import_zod6.z.literal(1),
|
|
7975
|
+
position: import_zod6.z.object({
|
|
7976
|
+
phase: import_zod6.z.string().optional(),
|
|
7977
|
+
task: import_zod6.z.string().optional()
|
|
6856
7978
|
}).default({}),
|
|
6857
|
-
decisions:
|
|
6858
|
-
|
|
6859
|
-
date:
|
|
6860
|
-
decision:
|
|
6861
|
-
context:
|
|
7979
|
+
decisions: import_zod6.z.array(
|
|
7980
|
+
import_zod6.z.object({
|
|
7981
|
+
date: import_zod6.z.string(),
|
|
7982
|
+
decision: import_zod6.z.string(),
|
|
7983
|
+
context: import_zod6.z.string()
|
|
6862
7984
|
})
|
|
6863
7985
|
).default([]),
|
|
6864
|
-
blockers:
|
|
6865
|
-
|
|
6866
|
-
id:
|
|
6867
|
-
description:
|
|
6868
|
-
status:
|
|
7986
|
+
blockers: import_zod6.z.array(
|
|
7987
|
+
import_zod6.z.object({
|
|
7988
|
+
id: import_zod6.z.string(),
|
|
7989
|
+
description: import_zod6.z.string(),
|
|
7990
|
+
status: import_zod6.z.enum(["open", "resolved"])
|
|
6869
7991
|
})
|
|
6870
7992
|
).default([]),
|
|
6871
|
-
progress:
|
|
6872
|
-
lastSession:
|
|
6873
|
-
date:
|
|
6874
|
-
summary:
|
|
6875
|
-
lastSkill:
|
|
6876
|
-
pendingTasks:
|
|
7993
|
+
progress: import_zod6.z.record(import_zod6.z.enum(["pending", "in_progress", "complete"])).default({}),
|
|
7994
|
+
lastSession: import_zod6.z.object({
|
|
7995
|
+
date: import_zod6.z.string(),
|
|
7996
|
+
summary: import_zod6.z.string(),
|
|
7997
|
+
lastSkill: import_zod6.z.string().optional(),
|
|
7998
|
+
pendingTasks: import_zod6.z.array(import_zod6.z.string()).optional()
|
|
6877
7999
|
}).optional()
|
|
6878
8000
|
});
|
|
6879
8001
|
var DEFAULT_STATE = {
|
|
@@ -6885,30 +8007,30 @@ var DEFAULT_STATE = {
|
|
|
6885
8007
|
};
|
|
6886
8008
|
|
|
6887
8009
|
// src/state/state-persistence.ts
|
|
6888
|
-
var
|
|
6889
|
-
var
|
|
8010
|
+
var fs10 = __toESM(require("fs"));
|
|
8011
|
+
var path7 = __toESM(require("path"));
|
|
6890
8012
|
|
|
6891
8013
|
// src/state/state-shared.ts
|
|
6892
|
-
var
|
|
6893
|
-
var
|
|
8014
|
+
var fs9 = __toESM(require("fs"));
|
|
8015
|
+
var path6 = __toESM(require("path"));
|
|
6894
8016
|
|
|
6895
8017
|
// src/state/stream-resolver.ts
|
|
6896
|
-
var
|
|
6897
|
-
var
|
|
8018
|
+
var fs7 = __toESM(require("fs"));
|
|
8019
|
+
var path4 = __toESM(require("path"));
|
|
6898
8020
|
var import_child_process = require("child_process");
|
|
6899
8021
|
|
|
6900
8022
|
// src/state/stream-types.ts
|
|
6901
|
-
var
|
|
6902
|
-
var StreamInfoSchema =
|
|
6903
|
-
name:
|
|
6904
|
-
branch:
|
|
6905
|
-
createdAt:
|
|
6906
|
-
lastActiveAt:
|
|
8023
|
+
var import_zod7 = require("zod");
|
|
8024
|
+
var StreamInfoSchema = import_zod7.z.object({
|
|
8025
|
+
name: import_zod7.z.string(),
|
|
8026
|
+
branch: import_zod7.z.string().optional(),
|
|
8027
|
+
createdAt: import_zod7.z.string(),
|
|
8028
|
+
lastActiveAt: import_zod7.z.string()
|
|
6907
8029
|
});
|
|
6908
|
-
var StreamIndexSchema =
|
|
6909
|
-
schemaVersion:
|
|
6910
|
-
activeStream:
|
|
6911
|
-
streams:
|
|
8030
|
+
var StreamIndexSchema = import_zod7.z.object({
|
|
8031
|
+
schemaVersion: import_zod7.z.literal(1),
|
|
8032
|
+
activeStream: import_zod7.z.string().nullable(),
|
|
8033
|
+
streams: import_zod7.z.record(StreamInfoSchema)
|
|
6912
8034
|
});
|
|
6913
8035
|
var DEFAULT_STREAM_INDEX = {
|
|
6914
8036
|
schemaVersion: 1,
|
|
@@ -6936,10 +8058,10 @@ var EVENTS_FILE = "events.jsonl";
|
|
|
6936
8058
|
var STREAMS_DIR = "streams";
|
|
6937
8059
|
var STREAM_NAME_REGEX = /^[a-z0-9][a-z0-9._-]*$/;
|
|
6938
8060
|
function streamsDir(projectPath) {
|
|
6939
|
-
return
|
|
8061
|
+
return path4.join(projectPath, HARNESS_DIR, STREAMS_DIR);
|
|
6940
8062
|
}
|
|
6941
8063
|
function indexPath(projectPath) {
|
|
6942
|
-
return
|
|
8064
|
+
return path4.join(streamsDir(projectPath), INDEX_FILE);
|
|
6943
8065
|
}
|
|
6944
8066
|
function validateStreamName(name) {
|
|
6945
8067
|
if (!STREAM_NAME_REGEX.test(name)) {
|
|
@@ -6953,11 +8075,11 @@ function validateStreamName(name) {
|
|
|
6953
8075
|
}
|
|
6954
8076
|
async function loadStreamIndex(projectPath) {
|
|
6955
8077
|
const idxPath = indexPath(projectPath);
|
|
6956
|
-
if (!
|
|
8078
|
+
if (!fs7.existsSync(idxPath)) {
|
|
6957
8079
|
return (0, import_types.Ok)({ ...DEFAULT_STREAM_INDEX, streams: {} });
|
|
6958
8080
|
}
|
|
6959
8081
|
try {
|
|
6960
|
-
const raw =
|
|
8082
|
+
const raw = fs7.readFileSync(idxPath, "utf-8");
|
|
6961
8083
|
const parsed = JSON.parse(raw);
|
|
6962
8084
|
const result = StreamIndexSchema.safeParse(parsed);
|
|
6963
8085
|
if (!result.success) {
|
|
@@ -6975,8 +8097,8 @@ async function loadStreamIndex(projectPath) {
|
|
|
6975
8097
|
async function saveStreamIndex(projectPath, index) {
|
|
6976
8098
|
const dir = streamsDir(projectPath);
|
|
6977
8099
|
try {
|
|
6978
|
-
|
|
6979
|
-
|
|
8100
|
+
fs7.mkdirSync(dir, { recursive: true });
|
|
8101
|
+
fs7.writeFileSync(indexPath(projectPath), JSON.stringify(index, null, 2));
|
|
6980
8102
|
return (0, import_types.Ok)(void 0);
|
|
6981
8103
|
} catch (error) {
|
|
6982
8104
|
return (0, import_types.Err)(
|
|
@@ -7017,18 +8139,18 @@ async function resolveStreamPath(projectPath, options) {
|
|
|
7017
8139
|
)
|
|
7018
8140
|
);
|
|
7019
8141
|
}
|
|
7020
|
-
return (0, import_types.Ok)(
|
|
8142
|
+
return (0, import_types.Ok)(path4.join(streamsDir(projectPath), options.stream));
|
|
7021
8143
|
}
|
|
7022
8144
|
const branch = getCurrentBranch(projectPath);
|
|
7023
8145
|
if (branch && branch !== "main" && branch !== "master") {
|
|
7024
8146
|
for (const [name, info] of Object.entries(index.streams)) {
|
|
7025
8147
|
if (info.branch === branch) {
|
|
7026
|
-
return (0, import_types.Ok)(
|
|
8148
|
+
return (0, import_types.Ok)(path4.join(streamsDir(projectPath), name));
|
|
7027
8149
|
}
|
|
7028
8150
|
}
|
|
7029
8151
|
}
|
|
7030
8152
|
if (index.activeStream && index.streams[index.activeStream]) {
|
|
7031
|
-
return (0, import_types.Ok)(
|
|
8153
|
+
return (0, import_types.Ok)(path4.join(streamsDir(projectPath), index.activeStream));
|
|
7032
8154
|
}
|
|
7033
8155
|
return (0, import_types.Err)(
|
|
7034
8156
|
new Error(
|
|
@@ -7056,9 +8178,9 @@ async function createStream(projectPath, name, branch) {
|
|
|
7056
8178
|
if (index.streams[name]) {
|
|
7057
8179
|
return (0, import_types.Err)(new Error(`Stream '${name}' already exists`));
|
|
7058
8180
|
}
|
|
7059
|
-
const streamPath =
|
|
8181
|
+
const streamPath = path4.join(streamsDir(projectPath), name);
|
|
7060
8182
|
try {
|
|
7061
|
-
|
|
8183
|
+
fs7.mkdirSync(streamPath, { recursive: true });
|
|
7062
8184
|
} catch (error) {
|
|
7063
8185
|
return (0, import_types.Err)(
|
|
7064
8186
|
new Error(
|
|
@@ -7099,12 +8221,12 @@ async function archiveStream(projectPath, name) {
|
|
|
7099
8221
|
if (!index.streams[name]) {
|
|
7100
8222
|
return (0, import_types.Err)(new Error(`Stream '${name}' not found`));
|
|
7101
8223
|
}
|
|
7102
|
-
const streamPath =
|
|
7103
|
-
const archiveDir =
|
|
8224
|
+
const streamPath = path4.join(streamsDir(projectPath), name);
|
|
8225
|
+
const archiveDir = path4.join(projectPath, HARNESS_DIR, "archive", "streams");
|
|
7104
8226
|
try {
|
|
7105
|
-
|
|
8227
|
+
fs7.mkdirSync(archiveDir, { recursive: true });
|
|
7106
8228
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
7107
|
-
|
|
8229
|
+
fs7.renameSync(streamPath, path4.join(archiveDir, `${name}-${date}`));
|
|
7108
8230
|
} catch (error) {
|
|
7109
8231
|
return (0, import_types.Err)(
|
|
7110
8232
|
new Error(
|
|
@@ -7126,19 +8248,19 @@ function getStreamForBranch(index, branch) {
|
|
|
7126
8248
|
}
|
|
7127
8249
|
var STATE_FILES = ["state.json", "handoff.json", "learnings.md", "failures.md"];
|
|
7128
8250
|
async function migrateToStreams(projectPath) {
|
|
7129
|
-
const harnessDir =
|
|
7130
|
-
if (
|
|
8251
|
+
const harnessDir = path4.join(projectPath, HARNESS_DIR);
|
|
8252
|
+
if (fs7.existsSync(indexPath(projectPath))) {
|
|
7131
8253
|
return (0, import_types.Ok)(void 0);
|
|
7132
8254
|
}
|
|
7133
|
-
const filesToMove = STATE_FILES.filter((f) =>
|
|
8255
|
+
const filesToMove = STATE_FILES.filter((f) => fs7.existsSync(path4.join(harnessDir, f)));
|
|
7134
8256
|
if (filesToMove.length === 0) {
|
|
7135
8257
|
return (0, import_types.Ok)(void 0);
|
|
7136
8258
|
}
|
|
7137
|
-
const defaultDir =
|
|
8259
|
+
const defaultDir = path4.join(streamsDir(projectPath), "default");
|
|
7138
8260
|
try {
|
|
7139
|
-
|
|
8261
|
+
fs7.mkdirSync(defaultDir, { recursive: true });
|
|
7140
8262
|
for (const file of filesToMove) {
|
|
7141
|
-
|
|
8263
|
+
fs7.renameSync(path4.join(harnessDir, file), path4.join(defaultDir, file));
|
|
7142
8264
|
}
|
|
7143
8265
|
} catch (error) {
|
|
7144
8266
|
return (0, import_types.Err)(
|
|
@@ -7161,8 +8283,8 @@ async function migrateToStreams(projectPath) {
|
|
|
7161
8283
|
}
|
|
7162
8284
|
|
|
7163
8285
|
// src/state/session-resolver.ts
|
|
7164
|
-
var
|
|
7165
|
-
var
|
|
8286
|
+
var fs8 = __toESM(require("fs"));
|
|
8287
|
+
var path5 = __toESM(require("path"));
|
|
7166
8288
|
function resolveSessionDir(projectPath, sessionSlug, options) {
|
|
7167
8289
|
if (!sessionSlug || sessionSlug.trim() === "") {
|
|
7168
8290
|
return (0, import_types.Err)(new Error("Session slug must not be empty"));
|
|
@@ -7172,26 +8294,26 @@ function resolveSessionDir(projectPath, sessionSlug, options) {
|
|
|
7172
8294
|
new Error(`Invalid session slug '${sessionSlug}': must not contain path traversal characters`)
|
|
7173
8295
|
);
|
|
7174
8296
|
}
|
|
7175
|
-
const sessionDir =
|
|
8297
|
+
const sessionDir = path5.join(projectPath, HARNESS_DIR, SESSIONS_DIR, sessionSlug);
|
|
7176
8298
|
if (options?.create) {
|
|
7177
|
-
|
|
8299
|
+
fs8.mkdirSync(sessionDir, { recursive: true });
|
|
7178
8300
|
}
|
|
7179
8301
|
return (0, import_types.Ok)(sessionDir);
|
|
7180
8302
|
}
|
|
7181
8303
|
function updateSessionIndex(projectPath, sessionSlug, description) {
|
|
7182
|
-
const sessionsDir =
|
|
7183
|
-
|
|
7184
|
-
const indexPath2 =
|
|
8304
|
+
const sessionsDir = path5.join(projectPath, HARNESS_DIR, SESSIONS_DIR);
|
|
8305
|
+
fs8.mkdirSync(sessionsDir, { recursive: true });
|
|
8306
|
+
const indexPath2 = path5.join(sessionsDir, SESSION_INDEX_FILE);
|
|
7185
8307
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
7186
8308
|
const newLine = `- [${sessionSlug}](${sessionSlug}/summary.md) \u2014 ${description} (${date})`;
|
|
7187
|
-
if (!
|
|
7188
|
-
|
|
8309
|
+
if (!fs8.existsSync(indexPath2)) {
|
|
8310
|
+
fs8.writeFileSync(indexPath2, `## Active Sessions
|
|
7189
8311
|
|
|
7190
8312
|
${newLine}
|
|
7191
8313
|
`);
|
|
7192
8314
|
return;
|
|
7193
8315
|
}
|
|
7194
|
-
const content =
|
|
8316
|
+
const content = fs8.readFileSync(indexPath2, "utf-8");
|
|
7195
8317
|
const lines = content.split("\n");
|
|
7196
8318
|
const slugPattern = `- [${sessionSlug}]`;
|
|
7197
8319
|
const existingIdx = lines.findIndex((l) => l.startsWith(slugPattern));
|
|
@@ -7201,7 +8323,7 @@ ${newLine}
|
|
|
7201
8323
|
const lastNonEmpty = lines.reduce((last, line, i) => line.trim() !== "" ? i : last, 0);
|
|
7202
8324
|
lines.splice(lastNonEmpty + 1, 0, newLine);
|
|
7203
8325
|
}
|
|
7204
|
-
|
|
8326
|
+
fs8.writeFileSync(indexPath2, lines.join("\n"));
|
|
7205
8327
|
}
|
|
7206
8328
|
|
|
7207
8329
|
// src/state/state-shared.ts
|
|
@@ -7217,8 +8339,8 @@ async function getStateDir(projectPath, stream, session) {
|
|
|
7217
8339
|
const sessionResult = resolveSessionDir(projectPath, session, { create: true });
|
|
7218
8340
|
return sessionResult;
|
|
7219
8341
|
}
|
|
7220
|
-
const streamsIndexPath =
|
|
7221
|
-
const hasStreams =
|
|
8342
|
+
const streamsIndexPath = path6.join(projectPath, HARNESS_DIR, "streams", INDEX_FILE);
|
|
8343
|
+
const hasStreams = fs9.existsSync(streamsIndexPath);
|
|
7222
8344
|
if (stream || hasStreams) {
|
|
7223
8345
|
const result = await resolveStreamPath(projectPath, stream ? { stream } : void 0);
|
|
7224
8346
|
if (result.ok) {
|
|
@@ -7228,7 +8350,7 @@ async function getStateDir(projectPath, stream, session) {
|
|
|
7228
8350
|
return result;
|
|
7229
8351
|
}
|
|
7230
8352
|
}
|
|
7231
|
-
return (0, import_types.Ok)(
|
|
8353
|
+
return (0, import_types.Ok)(path6.join(projectPath, HARNESS_DIR));
|
|
7232
8354
|
}
|
|
7233
8355
|
|
|
7234
8356
|
// src/state/state-persistence.ts
|
|
@@ -7237,11 +8359,11 @@ async function loadState(projectPath, stream, session) {
|
|
|
7237
8359
|
const dirResult = await getStateDir(projectPath, stream, session);
|
|
7238
8360
|
if (!dirResult.ok) return dirResult;
|
|
7239
8361
|
const stateDir = dirResult.value;
|
|
7240
|
-
const statePath =
|
|
7241
|
-
if (!
|
|
8362
|
+
const statePath = path7.join(stateDir, STATE_FILE);
|
|
8363
|
+
if (!fs10.existsSync(statePath)) {
|
|
7242
8364
|
return (0, import_types.Ok)({ ...DEFAULT_STATE });
|
|
7243
8365
|
}
|
|
7244
|
-
const raw =
|
|
8366
|
+
const raw = fs10.readFileSync(statePath, "utf-8");
|
|
7245
8367
|
const parsed = JSON.parse(raw);
|
|
7246
8368
|
const result = HarnessStateSchema.safeParse(parsed);
|
|
7247
8369
|
if (!result.success) {
|
|
@@ -7259,9 +8381,9 @@ async function saveState(projectPath, state, stream, session) {
|
|
|
7259
8381
|
const dirResult = await getStateDir(projectPath, stream, session);
|
|
7260
8382
|
if (!dirResult.ok) return dirResult;
|
|
7261
8383
|
const stateDir = dirResult.value;
|
|
7262
|
-
const statePath =
|
|
7263
|
-
|
|
7264
|
-
|
|
8384
|
+
const statePath = path7.join(stateDir, STATE_FILE);
|
|
8385
|
+
fs10.mkdirSync(stateDir, { recursive: true });
|
|
8386
|
+
fs10.writeFileSync(statePath, JSON.stringify(state, null, 2));
|
|
7265
8387
|
return (0, import_types.Ok)(void 0);
|
|
7266
8388
|
} catch (error) {
|
|
7267
8389
|
return (0, import_types.Err)(
|
|
@@ -7271,10 +8393,10 @@ async function saveState(projectPath, state, stream, session) {
|
|
|
7271
8393
|
}
|
|
7272
8394
|
|
|
7273
8395
|
// src/state/learnings.ts
|
|
7274
|
-
var
|
|
7275
|
-
var
|
|
8396
|
+
var fs11 = __toESM(require("fs"));
|
|
8397
|
+
var path8 = __toESM(require("path"));
|
|
7276
8398
|
var crypto = __toESM(require("crypto"));
|
|
7277
|
-
function
|
|
8399
|
+
function parseFrontmatter2(line) {
|
|
7278
8400
|
const match = line.match(/^<!--\s+hash:([a-f0-9]+)(?:\s+tags:([^\s]+))?\s+-->/);
|
|
7279
8401
|
if (!match) return null;
|
|
7280
8402
|
const hash = match[1];
|
|
@@ -7300,10 +8422,10 @@ function computeContentHash(text) {
|
|
|
7300
8422
|
return crypto.createHash("sha256").update(text).digest("hex").slice(0, 16);
|
|
7301
8423
|
}
|
|
7302
8424
|
function loadContentHashes(stateDir) {
|
|
7303
|
-
const hashesPath =
|
|
7304
|
-
if (!
|
|
8425
|
+
const hashesPath = path8.join(stateDir, CONTENT_HASHES_FILE);
|
|
8426
|
+
if (!fs11.existsSync(hashesPath)) return {};
|
|
7305
8427
|
try {
|
|
7306
|
-
const raw =
|
|
8428
|
+
const raw = fs11.readFileSync(hashesPath, "utf-8");
|
|
7307
8429
|
const parsed = JSON.parse(raw);
|
|
7308
8430
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return {};
|
|
7309
8431
|
return parsed;
|
|
@@ -7312,13 +8434,13 @@ function loadContentHashes(stateDir) {
|
|
|
7312
8434
|
}
|
|
7313
8435
|
}
|
|
7314
8436
|
function saveContentHashes(stateDir, index) {
|
|
7315
|
-
const hashesPath =
|
|
7316
|
-
|
|
8437
|
+
const hashesPath = path8.join(stateDir, CONTENT_HASHES_FILE);
|
|
8438
|
+
fs11.writeFileSync(hashesPath, JSON.stringify(index, null, 2) + "\n");
|
|
7317
8439
|
}
|
|
7318
8440
|
function rebuildContentHashes(stateDir) {
|
|
7319
|
-
const learningsPath =
|
|
7320
|
-
if (!
|
|
7321
|
-
const content =
|
|
8441
|
+
const learningsPath = path8.join(stateDir, LEARNINGS_FILE);
|
|
8442
|
+
if (!fs11.existsSync(learningsPath)) return {};
|
|
8443
|
+
const content = fs11.readFileSync(learningsPath, "utf-8");
|
|
7322
8444
|
const lines = content.split("\n");
|
|
7323
8445
|
const index = {};
|
|
7324
8446
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -7361,18 +8483,18 @@ async function appendLearning(projectPath, learning, skillName, outcome, stream,
|
|
|
7361
8483
|
const dirResult = await getStateDir(projectPath, stream, session);
|
|
7362
8484
|
if (!dirResult.ok) return dirResult;
|
|
7363
8485
|
const stateDir = dirResult.value;
|
|
7364
|
-
const learningsPath =
|
|
7365
|
-
|
|
8486
|
+
const learningsPath = path8.join(stateDir, LEARNINGS_FILE);
|
|
8487
|
+
fs11.mkdirSync(stateDir, { recursive: true });
|
|
7366
8488
|
const normalizedContent = normalizeLearningContent(learning);
|
|
7367
8489
|
const contentHash = computeContentHash(normalizedContent);
|
|
7368
|
-
const hashesPath =
|
|
8490
|
+
const hashesPath = path8.join(stateDir, CONTENT_HASHES_FILE);
|
|
7369
8491
|
let contentHashes;
|
|
7370
|
-
if (
|
|
8492
|
+
if (fs11.existsSync(hashesPath)) {
|
|
7371
8493
|
contentHashes = loadContentHashes(stateDir);
|
|
7372
|
-
if (Object.keys(contentHashes).length === 0 &&
|
|
8494
|
+
if (Object.keys(contentHashes).length === 0 && fs11.existsSync(learningsPath)) {
|
|
7373
8495
|
contentHashes = rebuildContentHashes(stateDir);
|
|
7374
8496
|
}
|
|
7375
|
-
} else if (
|
|
8497
|
+
} else if (fs11.existsSync(learningsPath)) {
|
|
7376
8498
|
contentHashes = rebuildContentHashes(stateDir);
|
|
7377
8499
|
} else {
|
|
7378
8500
|
contentHashes = {};
|
|
@@ -7400,14 +8522,14 @@ ${frontmatter}
|
|
|
7400
8522
|
${bulletLine}
|
|
7401
8523
|
`;
|
|
7402
8524
|
let existingLineCount;
|
|
7403
|
-
if (!
|
|
7404
|
-
|
|
8525
|
+
if (!fs11.existsSync(learningsPath)) {
|
|
8526
|
+
fs11.writeFileSync(learningsPath, `# Learnings
|
|
7405
8527
|
${entry}`);
|
|
7406
8528
|
existingLineCount = 1;
|
|
7407
8529
|
} else {
|
|
7408
|
-
const existingContent =
|
|
8530
|
+
const existingContent = fs11.readFileSync(learningsPath, "utf-8");
|
|
7409
8531
|
existingLineCount = existingContent.split("\n").length;
|
|
7410
|
-
|
|
8532
|
+
fs11.appendFileSync(learningsPath, entry);
|
|
7411
8533
|
}
|
|
7412
8534
|
const bulletLine_lineNum = existingLineCount + 2;
|
|
7413
8535
|
contentHashes[contentHash] = { date: timestamp ?? "", line: bulletLine_lineNum };
|
|
@@ -7521,18 +8643,18 @@ async function loadIndexEntries(projectPath, skillName, stream, session) {
|
|
|
7521
8643
|
const dirResult = await getStateDir(projectPath, stream, session);
|
|
7522
8644
|
if (!dirResult.ok) return dirResult;
|
|
7523
8645
|
const stateDir = dirResult.value;
|
|
7524
|
-
const learningsPath =
|
|
7525
|
-
if (!
|
|
8646
|
+
const learningsPath = path8.join(stateDir, LEARNINGS_FILE);
|
|
8647
|
+
if (!fs11.existsSync(learningsPath)) {
|
|
7526
8648
|
return (0, import_types.Ok)([]);
|
|
7527
8649
|
}
|
|
7528
|
-
const content =
|
|
8650
|
+
const content = fs11.readFileSync(learningsPath, "utf-8");
|
|
7529
8651
|
const lines = content.split("\n");
|
|
7530
8652
|
const indexEntries = [];
|
|
7531
8653
|
let pendingFrontmatter = null;
|
|
7532
8654
|
let currentBlock = [];
|
|
7533
8655
|
for (const line of lines) {
|
|
7534
8656
|
if (line.startsWith("# ")) continue;
|
|
7535
|
-
const fm =
|
|
8657
|
+
const fm = parseFrontmatter2(line);
|
|
7536
8658
|
if (fm) {
|
|
7537
8659
|
pendingFrontmatter = fm;
|
|
7538
8660
|
continue;
|
|
@@ -7583,18 +8705,18 @@ async function loadRelevantLearnings(projectPath, skillName, stream, session) {
|
|
|
7583
8705
|
const dirResult = await getStateDir(projectPath, stream, session);
|
|
7584
8706
|
if (!dirResult.ok) return dirResult;
|
|
7585
8707
|
const stateDir = dirResult.value;
|
|
7586
|
-
const learningsPath =
|
|
7587
|
-
if (!
|
|
8708
|
+
const learningsPath = path8.join(stateDir, LEARNINGS_FILE);
|
|
8709
|
+
if (!fs11.existsSync(learningsPath)) {
|
|
7588
8710
|
return (0, import_types.Ok)([]);
|
|
7589
8711
|
}
|
|
7590
|
-
const stats =
|
|
8712
|
+
const stats = fs11.statSync(learningsPath);
|
|
7591
8713
|
const cacheKey = learningsPath;
|
|
7592
8714
|
const cached = learningsCacheMap.get(cacheKey);
|
|
7593
8715
|
let entries;
|
|
7594
8716
|
if (cached && cached.mtimeMs === stats.mtimeMs) {
|
|
7595
8717
|
entries = cached.entries;
|
|
7596
8718
|
} else {
|
|
7597
|
-
const content =
|
|
8719
|
+
const content = fs11.readFileSync(learningsPath, "utf-8");
|
|
7598
8720
|
const lines = content.split("\n");
|
|
7599
8721
|
entries = [];
|
|
7600
8722
|
let currentBlock = [];
|
|
@@ -7636,16 +8758,16 @@ async function archiveLearnings(projectPath, entries, stream) {
|
|
|
7636
8758
|
const dirResult = await getStateDir(projectPath, stream);
|
|
7637
8759
|
if (!dirResult.ok) return dirResult;
|
|
7638
8760
|
const stateDir = dirResult.value;
|
|
7639
|
-
const archiveDir =
|
|
7640
|
-
|
|
8761
|
+
const archiveDir = path8.join(stateDir, "learnings-archive");
|
|
8762
|
+
fs11.mkdirSync(archiveDir, { recursive: true });
|
|
7641
8763
|
const now = /* @__PURE__ */ new Date();
|
|
7642
8764
|
const yearMonth = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`;
|
|
7643
|
-
const archivePath =
|
|
8765
|
+
const archivePath = path8.join(archiveDir, `${yearMonth}.md`);
|
|
7644
8766
|
const archiveContent = entries.join("\n\n") + "\n";
|
|
7645
|
-
if (
|
|
7646
|
-
|
|
8767
|
+
if (fs11.existsSync(archivePath)) {
|
|
8768
|
+
fs11.appendFileSync(archivePath, "\n" + archiveContent);
|
|
7647
8769
|
} else {
|
|
7648
|
-
|
|
8770
|
+
fs11.writeFileSync(archivePath, `# Learnings Archive
|
|
7649
8771
|
|
|
7650
8772
|
${archiveContent}`);
|
|
7651
8773
|
}
|
|
@@ -7663,8 +8785,8 @@ async function pruneLearnings(projectPath, stream) {
|
|
|
7663
8785
|
const dirResult = await getStateDir(projectPath, stream);
|
|
7664
8786
|
if (!dirResult.ok) return dirResult;
|
|
7665
8787
|
const stateDir = dirResult.value;
|
|
7666
|
-
const learningsPath =
|
|
7667
|
-
if (!
|
|
8788
|
+
const learningsPath = path8.join(stateDir, LEARNINGS_FILE);
|
|
8789
|
+
if (!fs11.existsSync(learningsPath)) {
|
|
7668
8790
|
return (0, import_types.Ok)({ kept: 0, archived: 0, patterns: [] });
|
|
7669
8791
|
}
|
|
7670
8792
|
const loadResult = await loadRelevantLearnings(projectPath, void 0, stream);
|
|
@@ -7695,7 +8817,7 @@ async function pruneLearnings(projectPath, stream) {
|
|
|
7695
8817
|
if (!archiveResult.ok) return archiveResult;
|
|
7696
8818
|
}
|
|
7697
8819
|
const newContent = "# Learnings\n\n" + toKeep.join("\n\n") + "\n";
|
|
7698
|
-
|
|
8820
|
+
fs11.writeFileSync(learningsPath, newContent);
|
|
7699
8821
|
learningsCacheMap.delete(learningsPath);
|
|
7700
8822
|
return (0, import_types.Ok)({
|
|
7701
8823
|
kept: toKeep.length,
|
|
@@ -7740,19 +8862,19 @@ async function promoteSessionLearnings(projectPath, sessionSlug, stream) {
|
|
|
7740
8862
|
const dirResult = await getStateDir(projectPath, stream);
|
|
7741
8863
|
if (!dirResult.ok) return dirResult;
|
|
7742
8864
|
const stateDir = dirResult.value;
|
|
7743
|
-
const globalPath =
|
|
7744
|
-
const existingGlobal =
|
|
8865
|
+
const globalPath = path8.join(stateDir, LEARNINGS_FILE);
|
|
8866
|
+
const existingGlobal = fs11.existsSync(globalPath) ? fs11.readFileSync(globalPath, "utf-8") : "";
|
|
7745
8867
|
const newEntries = toPromote.filter((entry) => !existingGlobal.includes(entry.trim()));
|
|
7746
8868
|
if (newEntries.length === 0) {
|
|
7747
8869
|
return (0, import_types.Ok)({ promoted: 0, skipped: skipped + toPromote.length });
|
|
7748
8870
|
}
|
|
7749
8871
|
const promotedContent = newEntries.join("\n\n") + "\n";
|
|
7750
8872
|
if (!existingGlobal) {
|
|
7751
|
-
|
|
8873
|
+
fs11.writeFileSync(globalPath, `# Learnings
|
|
7752
8874
|
|
|
7753
8875
|
${promotedContent}`);
|
|
7754
8876
|
} else {
|
|
7755
|
-
|
|
8877
|
+
fs11.appendFileSync(globalPath, "\n\n" + promotedContent);
|
|
7756
8878
|
}
|
|
7757
8879
|
learningsCacheMap.delete(globalPath);
|
|
7758
8880
|
return (0, import_types.Ok)({
|
|
@@ -7774,8 +8896,8 @@ async function countLearningEntries(projectPath, stream) {
|
|
|
7774
8896
|
}
|
|
7775
8897
|
|
|
7776
8898
|
// src/state/failures.ts
|
|
7777
|
-
var
|
|
7778
|
-
var
|
|
8899
|
+
var fs12 = __toESM(require("fs"));
|
|
8900
|
+
var path9 = __toESM(require("path"));
|
|
7779
8901
|
var failuresCacheMap = /* @__PURE__ */ new Map();
|
|
7780
8902
|
function clearFailuresCache() {
|
|
7781
8903
|
failuresCacheMap.clear();
|
|
@@ -7786,17 +8908,17 @@ async function appendFailure(projectPath, description, skillName, type, stream,
|
|
|
7786
8908
|
const dirResult = await getStateDir(projectPath, stream, session);
|
|
7787
8909
|
if (!dirResult.ok) return dirResult;
|
|
7788
8910
|
const stateDir = dirResult.value;
|
|
7789
|
-
const failuresPath =
|
|
7790
|
-
|
|
8911
|
+
const failuresPath = path9.join(stateDir, FAILURES_FILE);
|
|
8912
|
+
fs12.mkdirSync(stateDir, { recursive: true });
|
|
7791
8913
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
7792
8914
|
const entry = `
|
|
7793
8915
|
- **${timestamp} [skill:${skillName}] [type:${type}]:** ${description}
|
|
7794
8916
|
`;
|
|
7795
|
-
if (!
|
|
7796
|
-
|
|
8917
|
+
if (!fs12.existsSync(failuresPath)) {
|
|
8918
|
+
fs12.writeFileSync(failuresPath, `# Failures
|
|
7797
8919
|
${entry}`);
|
|
7798
8920
|
} else {
|
|
7799
|
-
|
|
8921
|
+
fs12.appendFileSync(failuresPath, entry);
|
|
7800
8922
|
}
|
|
7801
8923
|
failuresCacheMap.delete(failuresPath);
|
|
7802
8924
|
return (0, import_types.Ok)(void 0);
|
|
@@ -7813,17 +8935,17 @@ async function loadFailures(projectPath, stream, session) {
|
|
|
7813
8935
|
const dirResult = await getStateDir(projectPath, stream, session);
|
|
7814
8936
|
if (!dirResult.ok) return dirResult;
|
|
7815
8937
|
const stateDir = dirResult.value;
|
|
7816
|
-
const failuresPath =
|
|
7817
|
-
if (!
|
|
8938
|
+
const failuresPath = path9.join(stateDir, FAILURES_FILE);
|
|
8939
|
+
if (!fs12.existsSync(failuresPath)) {
|
|
7818
8940
|
return (0, import_types.Ok)([]);
|
|
7819
8941
|
}
|
|
7820
|
-
const stats =
|
|
8942
|
+
const stats = fs12.statSync(failuresPath);
|
|
7821
8943
|
const cacheKey = failuresPath;
|
|
7822
8944
|
const cached = failuresCacheMap.get(cacheKey);
|
|
7823
8945
|
if (cached && cached.mtimeMs === stats.mtimeMs) {
|
|
7824
8946
|
return (0, import_types.Ok)(cached.entries);
|
|
7825
8947
|
}
|
|
7826
|
-
const content =
|
|
8948
|
+
const content = fs12.readFileSync(failuresPath, "utf-8");
|
|
7827
8949
|
const entries = [];
|
|
7828
8950
|
for (const line of content.split("\n")) {
|
|
7829
8951
|
const match = line.match(FAILURE_LINE_REGEX);
|
|
@@ -7852,20 +8974,20 @@ async function archiveFailures(projectPath, stream, session) {
|
|
|
7852
8974
|
const dirResult = await getStateDir(projectPath, stream, session);
|
|
7853
8975
|
if (!dirResult.ok) return dirResult;
|
|
7854
8976
|
const stateDir = dirResult.value;
|
|
7855
|
-
const failuresPath =
|
|
7856
|
-
if (!
|
|
8977
|
+
const failuresPath = path9.join(stateDir, FAILURES_FILE);
|
|
8978
|
+
if (!fs12.existsSync(failuresPath)) {
|
|
7857
8979
|
return (0, import_types.Ok)(void 0);
|
|
7858
8980
|
}
|
|
7859
|
-
const archiveDir =
|
|
7860
|
-
|
|
8981
|
+
const archiveDir = path9.join(stateDir, "archive");
|
|
8982
|
+
fs12.mkdirSync(archiveDir, { recursive: true });
|
|
7861
8983
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
7862
8984
|
let archiveName = `failures-${date}.md`;
|
|
7863
8985
|
let counter = 2;
|
|
7864
|
-
while (
|
|
8986
|
+
while (fs12.existsSync(path9.join(archiveDir, archiveName))) {
|
|
7865
8987
|
archiveName = `failures-${date}-${counter}.md`;
|
|
7866
8988
|
counter++;
|
|
7867
8989
|
}
|
|
7868
|
-
|
|
8990
|
+
fs12.renameSync(failuresPath, path9.join(archiveDir, archiveName));
|
|
7869
8991
|
failuresCacheMap.delete(failuresPath);
|
|
7870
8992
|
return (0, import_types.Ok)(void 0);
|
|
7871
8993
|
} catch (error) {
|
|
@@ -7878,16 +9000,16 @@ async function archiveFailures(projectPath, stream, session) {
|
|
|
7878
9000
|
}
|
|
7879
9001
|
|
|
7880
9002
|
// src/state/handoff.ts
|
|
7881
|
-
var
|
|
7882
|
-
var
|
|
9003
|
+
var fs13 = __toESM(require("fs"));
|
|
9004
|
+
var path10 = __toESM(require("path"));
|
|
7883
9005
|
async function saveHandoff(projectPath, handoff, stream, session) {
|
|
7884
9006
|
try {
|
|
7885
9007
|
const dirResult = await getStateDir(projectPath, stream, session);
|
|
7886
9008
|
if (!dirResult.ok) return dirResult;
|
|
7887
9009
|
const stateDir = dirResult.value;
|
|
7888
|
-
const handoffPath =
|
|
7889
|
-
|
|
7890
|
-
|
|
9010
|
+
const handoffPath = path10.join(stateDir, HANDOFF_FILE);
|
|
9011
|
+
fs13.mkdirSync(stateDir, { recursive: true });
|
|
9012
|
+
fs13.writeFileSync(handoffPath, JSON.stringify(handoff, null, 2));
|
|
7891
9013
|
return (0, import_types.Ok)(void 0);
|
|
7892
9014
|
} catch (error) {
|
|
7893
9015
|
return (0, import_types.Err)(
|
|
@@ -7900,11 +9022,11 @@ async function loadHandoff(projectPath, stream, session) {
|
|
|
7900
9022
|
const dirResult = await getStateDir(projectPath, stream, session);
|
|
7901
9023
|
if (!dirResult.ok) return dirResult;
|
|
7902
9024
|
const stateDir = dirResult.value;
|
|
7903
|
-
const handoffPath =
|
|
7904
|
-
if (!
|
|
9025
|
+
const handoffPath = path10.join(stateDir, HANDOFF_FILE);
|
|
9026
|
+
if (!fs13.existsSync(handoffPath)) {
|
|
7905
9027
|
return (0, import_types.Ok)(null);
|
|
7906
9028
|
}
|
|
7907
|
-
const raw =
|
|
9029
|
+
const raw = fs13.readFileSync(handoffPath, "utf-8");
|
|
7908
9030
|
const parsed = JSON.parse(raw);
|
|
7909
9031
|
const result = HandoffSchema.safeParse(parsed);
|
|
7910
9032
|
if (!result.success) {
|
|
@@ -7919,33 +9041,33 @@ async function loadHandoff(projectPath, stream, session) {
|
|
|
7919
9041
|
}
|
|
7920
9042
|
|
|
7921
9043
|
// src/state/mechanical-gate.ts
|
|
7922
|
-
var
|
|
7923
|
-
var
|
|
9044
|
+
var fs14 = __toESM(require("fs"));
|
|
9045
|
+
var path11 = __toESM(require("path"));
|
|
7924
9046
|
var import_child_process2 = require("child_process");
|
|
7925
9047
|
var SAFE_GATE_COMMAND = /^(?:npm|pnpm|yarn)\s+(?:test|run\s+[\w.-]+|run-script\s+[\w.-]+)$|^go\s+(?:test|build|vet|fmt)\s+[\w./ -]+$|^(?:python|python3)\s+-m\s+[\w.-]+$|^make\s+[\w.-]+$|^cargo\s+(?:test|build|check|clippy)(?:\s+[\w./ -]+)?$|^(?:gradle|mvn)\s+[\w:.-]+$/;
|
|
7926
9048
|
function loadChecksFromConfig(gateConfigPath) {
|
|
7927
|
-
if (!
|
|
7928
|
-
const raw = JSON.parse(
|
|
9049
|
+
if (!fs14.existsSync(gateConfigPath)) return [];
|
|
9050
|
+
const raw = JSON.parse(fs14.readFileSync(gateConfigPath, "utf-8"));
|
|
7929
9051
|
const config = GateConfigSchema.safeParse(raw);
|
|
7930
9052
|
if (config.success && config.data.checks) return config.data.checks;
|
|
7931
9053
|
return [];
|
|
7932
9054
|
}
|
|
7933
9055
|
function discoverChecksFromProject(projectPath) {
|
|
7934
9056
|
const checks = [];
|
|
7935
|
-
const packageJsonPath =
|
|
7936
|
-
if (
|
|
7937
|
-
const pkg = JSON.parse(
|
|
9057
|
+
const packageJsonPath = path11.join(projectPath, "package.json");
|
|
9058
|
+
if (fs14.existsSync(packageJsonPath)) {
|
|
9059
|
+
const pkg = JSON.parse(fs14.readFileSync(packageJsonPath, "utf-8"));
|
|
7938
9060
|
const scripts = pkg.scripts || {};
|
|
7939
9061
|
if (scripts.test) checks.push({ name: "test", command: "npm test" });
|
|
7940
9062
|
if (scripts.lint) checks.push({ name: "lint", command: "npm run lint" });
|
|
7941
9063
|
if (scripts.typecheck) checks.push({ name: "typecheck", command: "npm run typecheck" });
|
|
7942
9064
|
if (scripts.build) checks.push({ name: "build", command: "npm run build" });
|
|
7943
9065
|
}
|
|
7944
|
-
if (
|
|
9066
|
+
if (fs14.existsSync(path11.join(projectPath, "go.mod"))) {
|
|
7945
9067
|
checks.push({ name: "test", command: "go test ./..." });
|
|
7946
9068
|
checks.push({ name: "build", command: "go build ./..." });
|
|
7947
9069
|
}
|
|
7948
|
-
if (
|
|
9070
|
+
if (fs14.existsSync(path11.join(projectPath, "pyproject.toml")) || fs14.existsSync(path11.join(projectPath, "setup.py"))) {
|
|
7949
9071
|
checks.push({ name: "test", command: "python -m pytest" });
|
|
7950
9072
|
}
|
|
7951
9073
|
return checks;
|
|
@@ -7985,8 +9107,8 @@ function executeCheck(check, projectPath) {
|
|
|
7985
9107
|
}
|
|
7986
9108
|
}
|
|
7987
9109
|
async function runMechanicalGate(projectPath) {
|
|
7988
|
-
const harnessDir =
|
|
7989
|
-
const gateConfigPath =
|
|
9110
|
+
const harnessDir = path11.join(projectPath, HARNESS_DIR);
|
|
9111
|
+
const gateConfigPath = path11.join(harnessDir, GATE_CONFIG_FILE);
|
|
7990
9112
|
try {
|
|
7991
9113
|
let checks = loadChecksFromConfig(gateConfigPath);
|
|
7992
9114
|
if (checks.length === 0) {
|
|
@@ -8006,9 +9128,9 @@ async function runMechanicalGate(projectPath) {
|
|
|
8006
9128
|
}
|
|
8007
9129
|
}
|
|
8008
9130
|
|
|
8009
|
-
// src/state/session-summary.ts
|
|
8010
|
-
var
|
|
8011
|
-
var
|
|
9131
|
+
// src/state/session-summary.ts
|
|
9132
|
+
var fs15 = __toESM(require("fs"));
|
|
9133
|
+
var path12 = __toESM(require("path"));
|
|
8012
9134
|
function formatSummary(data) {
|
|
8013
9135
|
const lines = [
|
|
8014
9136
|
"## Session Summary",
|
|
@@ -8046,9 +9168,9 @@ function writeSessionSummary(projectPath, sessionSlug, data) {
|
|
|
8046
9168
|
const dirResult = resolveSessionDir(projectPath, sessionSlug, { create: true });
|
|
8047
9169
|
if (!dirResult.ok) return dirResult;
|
|
8048
9170
|
const sessionDir = dirResult.value;
|
|
8049
|
-
const summaryPath =
|
|
9171
|
+
const summaryPath = path12.join(sessionDir, SUMMARY_FILE);
|
|
8050
9172
|
const content = formatSummary(data);
|
|
8051
|
-
|
|
9173
|
+
fs15.writeFileSync(summaryPath, content);
|
|
8052
9174
|
const description = deriveIndexDescription(data);
|
|
8053
9175
|
updateSessionIndex(projectPath, sessionSlug, description);
|
|
8054
9176
|
return (0, import_types.Ok)(void 0);
|
|
@@ -8065,11 +9187,11 @@ function loadSessionSummary(projectPath, sessionSlug) {
|
|
|
8065
9187
|
const dirResult = resolveSessionDir(projectPath, sessionSlug);
|
|
8066
9188
|
if (!dirResult.ok) return dirResult;
|
|
8067
9189
|
const sessionDir = dirResult.value;
|
|
8068
|
-
const summaryPath =
|
|
8069
|
-
if (!
|
|
9190
|
+
const summaryPath = path12.join(sessionDir, SUMMARY_FILE);
|
|
9191
|
+
if (!fs15.existsSync(summaryPath)) {
|
|
8070
9192
|
return (0, import_types.Ok)(null);
|
|
8071
9193
|
}
|
|
8072
|
-
const content =
|
|
9194
|
+
const content = fs15.readFileSync(summaryPath, "utf-8");
|
|
8073
9195
|
return (0, import_types.Ok)(content);
|
|
8074
9196
|
} catch (error) {
|
|
8075
9197
|
return (0, import_types.Err)(
|
|
@@ -8081,11 +9203,11 @@ function loadSessionSummary(projectPath, sessionSlug) {
|
|
|
8081
9203
|
}
|
|
8082
9204
|
function listActiveSessions(projectPath) {
|
|
8083
9205
|
try {
|
|
8084
|
-
const indexPath2 =
|
|
8085
|
-
if (!
|
|
9206
|
+
const indexPath2 = path12.join(projectPath, HARNESS_DIR, SESSIONS_DIR, SESSION_INDEX_FILE);
|
|
9207
|
+
if (!fs15.existsSync(indexPath2)) {
|
|
8086
9208
|
return (0, import_types.Ok)(null);
|
|
8087
9209
|
}
|
|
8088
|
-
const content =
|
|
9210
|
+
const content = fs15.readFileSync(indexPath2, "utf-8");
|
|
8089
9211
|
return (0, import_types.Ok)(content);
|
|
8090
9212
|
} catch (error) {
|
|
8091
9213
|
return (0, import_types.Err)(
|
|
@@ -8097,12 +9219,12 @@ function listActiveSessions(projectPath) {
|
|
|
8097
9219
|
}
|
|
8098
9220
|
|
|
8099
9221
|
// src/state/session-sections.ts
|
|
8100
|
-
var
|
|
8101
|
-
var
|
|
8102
|
-
var
|
|
9222
|
+
var fs16 = __toESM(require("fs"));
|
|
9223
|
+
var path13 = __toESM(require("path"));
|
|
9224
|
+
var import_types19 = require("@harness-engineering/types");
|
|
8103
9225
|
function emptySections() {
|
|
8104
9226
|
const sections = {};
|
|
8105
|
-
for (const name of
|
|
9227
|
+
for (const name of import_types19.SESSION_SECTION_NAMES) {
|
|
8106
9228
|
sections[name] = [];
|
|
8107
9229
|
}
|
|
8108
9230
|
return sections;
|
|
@@ -8111,15 +9233,15 @@ async function loadSessionState(projectPath, sessionSlug) {
|
|
|
8111
9233
|
const dirResult = resolveSessionDir(projectPath, sessionSlug);
|
|
8112
9234
|
if (!dirResult.ok) return dirResult;
|
|
8113
9235
|
const sessionDir = dirResult.value;
|
|
8114
|
-
const filePath =
|
|
8115
|
-
if (!
|
|
9236
|
+
const filePath = path13.join(sessionDir, SESSION_STATE_FILE);
|
|
9237
|
+
if (!fs16.existsSync(filePath)) {
|
|
8116
9238
|
return (0, import_types.Ok)(emptySections());
|
|
8117
9239
|
}
|
|
8118
9240
|
try {
|
|
8119
|
-
const raw =
|
|
9241
|
+
const raw = fs16.readFileSync(filePath, "utf-8");
|
|
8120
9242
|
const parsed = JSON.parse(raw);
|
|
8121
9243
|
const sections = emptySections();
|
|
8122
|
-
for (const name of
|
|
9244
|
+
for (const name of import_types19.SESSION_SECTION_NAMES) {
|
|
8123
9245
|
if (Array.isArray(parsed[name])) {
|
|
8124
9246
|
sections[name] = parsed[name];
|
|
8125
9247
|
}
|
|
@@ -8137,9 +9259,9 @@ async function saveSessionState(projectPath, sessionSlug, sections) {
|
|
|
8137
9259
|
const dirResult = resolveSessionDir(projectPath, sessionSlug, { create: true });
|
|
8138
9260
|
if (!dirResult.ok) return dirResult;
|
|
8139
9261
|
const sessionDir = dirResult.value;
|
|
8140
|
-
const filePath =
|
|
9262
|
+
const filePath = path13.join(sessionDir, SESSION_STATE_FILE);
|
|
8141
9263
|
try {
|
|
8142
|
-
|
|
9264
|
+
fs16.writeFileSync(filePath, JSON.stringify(sections, null, 2));
|
|
8143
9265
|
return (0, import_types.Ok)(void 0);
|
|
8144
9266
|
} catch (error) {
|
|
8145
9267
|
return (0, import_types.Err)(
|
|
@@ -8193,32 +9315,32 @@ function generateEntryId() {
|
|
|
8193
9315
|
}
|
|
8194
9316
|
|
|
8195
9317
|
// src/state/session-archive.ts
|
|
8196
|
-
var
|
|
8197
|
-
var
|
|
9318
|
+
var fs17 = __toESM(require("fs"));
|
|
9319
|
+
var path14 = __toESM(require("path"));
|
|
8198
9320
|
async function archiveSession(projectPath, sessionSlug) {
|
|
8199
9321
|
const dirResult = resolveSessionDir(projectPath, sessionSlug);
|
|
8200
9322
|
if (!dirResult.ok) return dirResult;
|
|
8201
9323
|
const sessionDir = dirResult.value;
|
|
8202
|
-
if (!
|
|
9324
|
+
if (!fs17.existsSync(sessionDir)) {
|
|
8203
9325
|
return (0, import_types.Err)(new Error(`Session '${sessionSlug}' not found at ${sessionDir}`));
|
|
8204
9326
|
}
|
|
8205
|
-
const archiveBase =
|
|
9327
|
+
const archiveBase = path14.join(projectPath, HARNESS_DIR, ARCHIVE_DIR, "sessions");
|
|
8206
9328
|
try {
|
|
8207
|
-
|
|
9329
|
+
fs17.mkdirSync(archiveBase, { recursive: true });
|
|
8208
9330
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
8209
9331
|
let archiveName = `${sessionSlug}-${date}`;
|
|
8210
9332
|
let counter = 1;
|
|
8211
|
-
while (
|
|
9333
|
+
while (fs17.existsSync(path14.join(archiveBase, archiveName))) {
|
|
8212
9334
|
archiveName = `${sessionSlug}-${date}-${counter}`;
|
|
8213
9335
|
counter++;
|
|
8214
9336
|
}
|
|
8215
|
-
const dest =
|
|
9337
|
+
const dest = path14.join(archiveBase, archiveName);
|
|
8216
9338
|
try {
|
|
8217
|
-
|
|
9339
|
+
fs17.renameSync(sessionDir, dest);
|
|
8218
9340
|
} catch (renameErr) {
|
|
8219
9341
|
if (renameErr instanceof Error && "code" in renameErr && renameErr.code === "EXDEV") {
|
|
8220
|
-
|
|
8221
|
-
|
|
9342
|
+
fs17.cpSync(sessionDir, dest, { recursive: true });
|
|
9343
|
+
fs17.rmSync(sessionDir, { recursive: true });
|
|
8222
9344
|
} else {
|
|
8223
9345
|
throw renameErr;
|
|
8224
9346
|
}
|
|
@@ -8234,18 +9356,18 @@ async function archiveSession(projectPath, sessionSlug) {
|
|
|
8234
9356
|
}
|
|
8235
9357
|
|
|
8236
9358
|
// src/state/events.ts
|
|
8237
|
-
var
|
|
8238
|
-
var
|
|
8239
|
-
var
|
|
8240
|
-
var SkillEventSchema =
|
|
8241
|
-
timestamp:
|
|
8242
|
-
skill:
|
|
8243
|
-
session:
|
|
8244
|
-
type:
|
|
8245
|
-
summary:
|
|
8246
|
-
data:
|
|
8247
|
-
refs:
|
|
8248
|
-
contentHash:
|
|
9359
|
+
var fs18 = __toESM(require("fs"));
|
|
9360
|
+
var path15 = __toESM(require("path"));
|
|
9361
|
+
var import_zod8 = require("zod");
|
|
9362
|
+
var SkillEventSchema = import_zod8.z.object({
|
|
9363
|
+
timestamp: import_zod8.z.string(),
|
|
9364
|
+
skill: import_zod8.z.string(),
|
|
9365
|
+
session: import_zod8.z.string().optional(),
|
|
9366
|
+
type: import_zod8.z.enum(["phase_transition", "decision", "gate_result", "handoff", "error", "checkpoint"]),
|
|
9367
|
+
summary: import_zod8.z.string(),
|
|
9368
|
+
data: import_zod8.z.record(import_zod8.z.unknown()).optional(),
|
|
9369
|
+
refs: import_zod8.z.array(import_zod8.z.string()).optional(),
|
|
9370
|
+
contentHash: import_zod8.z.string().optional()
|
|
8249
9371
|
});
|
|
8250
9372
|
function computeEventHash(event, session) {
|
|
8251
9373
|
const identity = `${event.skill}|${event.type}|${event.summary}|${session ?? ""}`;
|
|
@@ -8256,8 +9378,8 @@ function loadKnownHashes(eventsPath) {
|
|
|
8256
9378
|
const cached = knownHashesCache.get(eventsPath);
|
|
8257
9379
|
if (cached) return cached;
|
|
8258
9380
|
const hashes = /* @__PURE__ */ new Set();
|
|
8259
|
-
if (
|
|
8260
|
-
const content =
|
|
9381
|
+
if (fs18.existsSync(eventsPath)) {
|
|
9382
|
+
const content = fs18.readFileSync(eventsPath, "utf-8");
|
|
8261
9383
|
const lines = content.split("\n").filter((line) => line.trim() !== "");
|
|
8262
9384
|
for (const line of lines) {
|
|
8263
9385
|
try {
|
|
@@ -8280,8 +9402,8 @@ async function emitEvent(projectPath, event, options) {
|
|
|
8280
9402
|
const dirResult = await getStateDir(projectPath, options?.stream, options?.session);
|
|
8281
9403
|
if (!dirResult.ok) return dirResult;
|
|
8282
9404
|
const stateDir = dirResult.value;
|
|
8283
|
-
const eventsPath =
|
|
8284
|
-
|
|
9405
|
+
const eventsPath = path15.join(stateDir, EVENTS_FILE);
|
|
9406
|
+
fs18.mkdirSync(stateDir, { recursive: true });
|
|
8285
9407
|
const contentHash = computeEventHash(event, options?.session);
|
|
8286
9408
|
const knownHashes = loadKnownHashes(eventsPath);
|
|
8287
9409
|
if (knownHashes.has(contentHash)) {
|
|
@@ -8295,7 +9417,7 @@ async function emitEvent(projectPath, event, options) {
|
|
|
8295
9417
|
if (options?.session) {
|
|
8296
9418
|
fullEvent.session = options.session;
|
|
8297
9419
|
}
|
|
8298
|
-
|
|
9420
|
+
fs18.appendFileSync(eventsPath, JSON.stringify(fullEvent) + "\n");
|
|
8299
9421
|
knownHashes.add(contentHash);
|
|
8300
9422
|
return (0, import_types.Ok)({ written: true });
|
|
8301
9423
|
} catch (error) {
|
|
@@ -8309,11 +9431,11 @@ async function loadEvents(projectPath, options) {
|
|
|
8309
9431
|
const dirResult = await getStateDir(projectPath, options?.stream, options?.session);
|
|
8310
9432
|
if (!dirResult.ok) return dirResult;
|
|
8311
9433
|
const stateDir = dirResult.value;
|
|
8312
|
-
const eventsPath =
|
|
8313
|
-
if (!
|
|
9434
|
+
const eventsPath = path15.join(stateDir, EVENTS_FILE);
|
|
9435
|
+
if (!fs18.existsSync(eventsPath)) {
|
|
8314
9436
|
return (0, import_types.Ok)([]);
|
|
8315
9437
|
}
|
|
8316
|
-
const content =
|
|
9438
|
+
const content = fs18.readFileSync(eventsPath, "utf-8");
|
|
8317
9439
|
const lines = content.split("\n").filter((line) => line.trim() !== "");
|
|
8318
9440
|
const events = [];
|
|
8319
9441
|
for (const line of lines) {
|
|
@@ -8527,7 +9649,7 @@ async function runMultiTurnPipeline(initialContext, turnExecutor, options) {
|
|
|
8527
9649
|
}
|
|
8528
9650
|
|
|
8529
9651
|
// src/security/scanner.ts
|
|
8530
|
-
var
|
|
9652
|
+
var fs20 = __toESM(require("fs/promises"));
|
|
8531
9653
|
var import_minimatch5 = require("minimatch");
|
|
8532
9654
|
|
|
8533
9655
|
// src/security/rules/registry.ts
|
|
@@ -8559,7 +9681,7 @@ var RuleRegistry = class {
|
|
|
8559
9681
|
};
|
|
8560
9682
|
|
|
8561
9683
|
// src/security/config.ts
|
|
8562
|
-
var
|
|
9684
|
+
var import_zod9 = require("zod");
|
|
8563
9685
|
|
|
8564
9686
|
// src/security/types.ts
|
|
8565
9687
|
var DEFAULT_SECURITY_CONFIG = {
|
|
@@ -8570,19 +9692,19 @@ var DEFAULT_SECURITY_CONFIG = {
|
|
|
8570
9692
|
};
|
|
8571
9693
|
|
|
8572
9694
|
// src/security/config.ts
|
|
8573
|
-
var RuleOverrideSchema =
|
|
8574
|
-
var SecurityConfigSchema =
|
|
8575
|
-
enabled:
|
|
8576
|
-
strict:
|
|
8577
|
-
rules:
|
|
8578
|
-
exclude:
|
|
8579
|
-
external:
|
|
8580
|
-
semgrep:
|
|
8581
|
-
enabled:
|
|
8582
|
-
rulesets:
|
|
9695
|
+
var RuleOverrideSchema = import_zod9.z.enum(["off", "error", "warning", "info"]);
|
|
9696
|
+
var SecurityConfigSchema = import_zod9.z.object({
|
|
9697
|
+
enabled: import_zod9.z.boolean().default(true),
|
|
9698
|
+
strict: import_zod9.z.boolean().default(false),
|
|
9699
|
+
rules: import_zod9.z.record(import_zod9.z.string(), RuleOverrideSchema).optional().default({}),
|
|
9700
|
+
exclude: import_zod9.z.array(import_zod9.z.string()).optional().default(["**/node_modules/**", "**/dist/**", "**/*.test.ts", "**/fixtures/**"]),
|
|
9701
|
+
external: import_zod9.z.object({
|
|
9702
|
+
semgrep: import_zod9.z.object({
|
|
9703
|
+
enabled: import_zod9.z.union([import_zod9.z.literal("auto"), import_zod9.z.boolean()]).default("auto"),
|
|
9704
|
+
rulesets: import_zod9.z.array(import_zod9.z.string()).optional()
|
|
8583
9705
|
}).optional(),
|
|
8584
|
-
gitleaks:
|
|
8585
|
-
enabled:
|
|
9706
|
+
gitleaks: import_zod9.z.object({
|
|
9707
|
+
enabled: import_zod9.z.union([import_zod9.z.literal("auto"), import_zod9.z.boolean()]).default("auto")
|
|
8586
9708
|
}).optional()
|
|
8587
9709
|
}).optional()
|
|
8588
9710
|
});
|
|
@@ -8615,15 +9737,15 @@ function resolveRuleSeverity(ruleId, defaultSeverity, overrides, strict) {
|
|
|
8615
9737
|
}
|
|
8616
9738
|
|
|
8617
9739
|
// src/security/stack-detector.ts
|
|
8618
|
-
var
|
|
8619
|
-
var
|
|
9740
|
+
var fs19 = __toESM(require("fs"));
|
|
9741
|
+
var path16 = __toESM(require("path"));
|
|
8620
9742
|
function detectStack(projectRoot) {
|
|
8621
9743
|
const stacks = [];
|
|
8622
|
-
const pkgJsonPath =
|
|
8623
|
-
if (
|
|
9744
|
+
const pkgJsonPath = path16.join(projectRoot, "package.json");
|
|
9745
|
+
if (fs19.existsSync(pkgJsonPath)) {
|
|
8624
9746
|
stacks.push("node");
|
|
8625
9747
|
try {
|
|
8626
|
-
const pkgJson = JSON.parse(
|
|
9748
|
+
const pkgJson = JSON.parse(fs19.readFileSync(pkgJsonPath, "utf-8"));
|
|
8627
9749
|
const allDeps = {
|
|
8628
9750
|
...pkgJson.dependencies,
|
|
8629
9751
|
...pkgJson.devDependencies
|
|
@@ -8638,13 +9760,13 @@ function detectStack(projectRoot) {
|
|
|
8638
9760
|
} catch {
|
|
8639
9761
|
}
|
|
8640
9762
|
}
|
|
8641
|
-
const goModPath =
|
|
8642
|
-
if (
|
|
9763
|
+
const goModPath = path16.join(projectRoot, "go.mod");
|
|
9764
|
+
if (fs19.existsSync(goModPath)) {
|
|
8643
9765
|
stacks.push("go");
|
|
8644
9766
|
}
|
|
8645
|
-
const requirementsPath =
|
|
8646
|
-
const pyprojectPath =
|
|
8647
|
-
if (
|
|
9767
|
+
const requirementsPath = path16.join(projectRoot, "requirements.txt");
|
|
9768
|
+
const pyprojectPath = path16.join(projectRoot, "pyproject.toml");
|
|
9769
|
+
if (fs19.existsSync(requirementsPath) || fs19.existsSync(pyprojectPath)) {
|
|
8648
9770
|
stacks.push("python");
|
|
8649
9771
|
}
|
|
8650
9772
|
return stacks;
|
|
@@ -9475,7 +10597,7 @@ var SecurityScanner = class {
|
|
|
9475
10597
|
}
|
|
9476
10598
|
async scanFile(filePath) {
|
|
9477
10599
|
if (!this.config.enabled) return [];
|
|
9478
|
-
const content = await
|
|
10600
|
+
const content = await fs20.readFile(filePath, "utf-8");
|
|
9479
10601
|
return this.scanContentForFile(content, filePath, 1);
|
|
9480
10602
|
}
|
|
9481
10603
|
scanContentForFile(content, filePath, startLine = 1) {
|
|
@@ -9802,19 +10924,19 @@ var DESTRUCTIVE_BASH = [
|
|
|
9802
10924
|
];
|
|
9803
10925
|
|
|
9804
10926
|
// src/security/taint.ts
|
|
9805
|
-
var
|
|
9806
|
-
var
|
|
10927
|
+
var import_node_fs5 = require("fs");
|
|
10928
|
+
var import_node_path8 = require("path");
|
|
9807
10929
|
var TAINT_DURATION_MS = 30 * 60 * 1e3;
|
|
9808
10930
|
var DEFAULT_SESSION_ID = "default";
|
|
9809
10931
|
function getTaintFilePath(projectRoot, sessionId) {
|
|
9810
10932
|
const id = sessionId || DEFAULT_SESSION_ID;
|
|
9811
|
-
return (0,
|
|
10933
|
+
return (0, import_node_path8.join)(projectRoot, ".harness", `session-taint-${id}.json`);
|
|
9812
10934
|
}
|
|
9813
10935
|
function readTaint(projectRoot, sessionId) {
|
|
9814
10936
|
const filePath = getTaintFilePath(projectRoot, sessionId);
|
|
9815
10937
|
let content;
|
|
9816
10938
|
try {
|
|
9817
|
-
content = (0,
|
|
10939
|
+
content = (0, import_node_fs5.readFileSync)(filePath, "utf8");
|
|
9818
10940
|
} catch {
|
|
9819
10941
|
return null;
|
|
9820
10942
|
}
|
|
@@ -9823,14 +10945,14 @@ function readTaint(projectRoot, sessionId) {
|
|
|
9823
10945
|
state = JSON.parse(content);
|
|
9824
10946
|
} catch {
|
|
9825
10947
|
try {
|
|
9826
|
-
(0,
|
|
10948
|
+
(0, import_node_fs5.unlinkSync)(filePath);
|
|
9827
10949
|
} catch {
|
|
9828
10950
|
}
|
|
9829
10951
|
return null;
|
|
9830
10952
|
}
|
|
9831
10953
|
if (!state.sessionId || !state.taintedAt || !state.expiresAt || !state.findings) {
|
|
9832
10954
|
try {
|
|
9833
|
-
(0,
|
|
10955
|
+
(0, import_node_fs5.unlinkSync)(filePath);
|
|
9834
10956
|
} catch {
|
|
9835
10957
|
}
|
|
9836
10958
|
return null;
|
|
@@ -9847,7 +10969,7 @@ function checkTaint(projectRoot, sessionId) {
|
|
|
9847
10969
|
if (now >= expiresAt) {
|
|
9848
10970
|
const filePath = getTaintFilePath(projectRoot, sessionId);
|
|
9849
10971
|
try {
|
|
9850
|
-
(0,
|
|
10972
|
+
(0, import_node_fs5.unlinkSync)(filePath);
|
|
9851
10973
|
} catch {
|
|
9852
10974
|
}
|
|
9853
10975
|
return { tainted: false, expired: true, state };
|
|
@@ -9858,8 +10980,8 @@ function writeTaint(projectRoot, sessionId, reason, findings, source) {
|
|
|
9858
10980
|
const id = sessionId || DEFAULT_SESSION_ID;
|
|
9859
10981
|
const filePath = getTaintFilePath(projectRoot, id);
|
|
9860
10982
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9861
|
-
const dir = (0,
|
|
9862
|
-
(0,
|
|
10983
|
+
const dir = (0, import_node_path8.dirname)(filePath);
|
|
10984
|
+
(0, import_node_fs5.mkdirSync)(dir, { recursive: true });
|
|
9863
10985
|
const existing = readTaint(projectRoot, id);
|
|
9864
10986
|
const maxSeverity = findings.some((f) => f.severity === "high") ? "high" : "medium";
|
|
9865
10987
|
const taintFindings = findings.map((f) => ({
|
|
@@ -9877,27 +10999,27 @@ function writeTaint(projectRoot, sessionId, reason, findings, source) {
|
|
|
9877
10999
|
severity: existing?.severity === "high" || maxSeverity === "high" ? "high" : "medium",
|
|
9878
11000
|
findings: [...existing?.findings || [], ...taintFindings]
|
|
9879
11001
|
};
|
|
9880
|
-
(0,
|
|
11002
|
+
(0, import_node_fs5.writeFileSync)(filePath, JSON.stringify(state, null, 2) + "\n");
|
|
9881
11003
|
return state;
|
|
9882
11004
|
}
|
|
9883
11005
|
function clearTaint(projectRoot, sessionId) {
|
|
9884
11006
|
if (sessionId) {
|
|
9885
11007
|
const filePath = getTaintFilePath(projectRoot, sessionId);
|
|
9886
11008
|
try {
|
|
9887
|
-
(0,
|
|
11009
|
+
(0, import_node_fs5.unlinkSync)(filePath);
|
|
9888
11010
|
return 1;
|
|
9889
11011
|
} catch {
|
|
9890
11012
|
return 0;
|
|
9891
11013
|
}
|
|
9892
11014
|
}
|
|
9893
|
-
const harnessDir = (0,
|
|
11015
|
+
const harnessDir = (0, import_node_path8.join)(projectRoot, ".harness");
|
|
9894
11016
|
let count = 0;
|
|
9895
11017
|
try {
|
|
9896
|
-
const files = (0,
|
|
11018
|
+
const files = (0, import_node_fs5.readdirSync)(harnessDir);
|
|
9897
11019
|
for (const file of files) {
|
|
9898
11020
|
if (file.startsWith("session-taint-") && file.endsWith(".json")) {
|
|
9899
11021
|
try {
|
|
9900
|
-
(0,
|
|
11022
|
+
(0, import_node_fs5.unlinkSync)((0, import_node_path8.join)(harnessDir, file));
|
|
9901
11023
|
count++;
|
|
9902
11024
|
} catch {
|
|
9903
11025
|
}
|
|
@@ -9908,10 +11030,10 @@ function clearTaint(projectRoot, sessionId) {
|
|
|
9908
11030
|
return count;
|
|
9909
11031
|
}
|
|
9910
11032
|
function listTaintedSessions(projectRoot) {
|
|
9911
|
-
const harnessDir = (0,
|
|
11033
|
+
const harnessDir = (0, import_node_path8.join)(projectRoot, ".harness");
|
|
9912
11034
|
const sessions = [];
|
|
9913
11035
|
try {
|
|
9914
|
-
const files = (0,
|
|
11036
|
+
const files = (0, import_node_fs5.readdirSync)(harnessDir);
|
|
9915
11037
|
for (const file of files) {
|
|
9916
11038
|
if (file.startsWith("session-taint-") && file.endsWith(".json")) {
|
|
9917
11039
|
const sessionId = file.replace("session-taint-", "").replace(".json", "");
|
|
@@ -9978,7 +11100,8 @@ function mapSecurityFindings(secFindings, existing) {
|
|
|
9978
11100
|
}
|
|
9979
11101
|
|
|
9980
11102
|
// src/ci/check-orchestrator.ts
|
|
9981
|
-
var
|
|
11103
|
+
var path17 = __toESM(require("path"));
|
|
11104
|
+
var import_graph = require("@harness-engineering/graph");
|
|
9982
11105
|
var ALL_CHECKS = [
|
|
9983
11106
|
"validate",
|
|
9984
11107
|
"deps",
|
|
@@ -9987,11 +11110,12 @@ var ALL_CHECKS = [
|
|
|
9987
11110
|
"security",
|
|
9988
11111
|
"perf",
|
|
9989
11112
|
"phase-gate",
|
|
9990
|
-
"arch"
|
|
11113
|
+
"arch",
|
|
11114
|
+
"traceability"
|
|
9991
11115
|
];
|
|
9992
11116
|
async function runValidateCheck(projectRoot, config) {
|
|
9993
11117
|
const issues = [];
|
|
9994
|
-
const agentsPath =
|
|
11118
|
+
const agentsPath = path17.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
|
|
9995
11119
|
const result = await validateAgentsMap(agentsPath);
|
|
9996
11120
|
if (!result.ok) {
|
|
9997
11121
|
issues.push({ severity: "error", message: result.error.message });
|
|
@@ -10048,7 +11172,7 @@ async function runDepsCheck(projectRoot, config) {
|
|
|
10048
11172
|
}
|
|
10049
11173
|
async function runDocsCheck(projectRoot, config) {
|
|
10050
11174
|
const issues = [];
|
|
10051
|
-
const docsDir =
|
|
11175
|
+
const docsDir = path17.join(projectRoot, config.docsDir ?? "docs");
|
|
10052
11176
|
const entropyConfig = config.entropy || {};
|
|
10053
11177
|
const result = await checkDocCoverage("project", {
|
|
10054
11178
|
docsDir,
|
|
@@ -10229,6 +11353,39 @@ async function runArchCheck(projectRoot, config) {
|
|
|
10229
11353
|
}
|
|
10230
11354
|
return issues;
|
|
10231
11355
|
}
|
|
11356
|
+
async function runTraceabilityCheck(projectRoot, config) {
|
|
11357
|
+
const issues = [];
|
|
11358
|
+
const traceConfig = config.traceability || {};
|
|
11359
|
+
if (traceConfig.enabled === false) return issues;
|
|
11360
|
+
const graphDir = path17.join(projectRoot, ".harness", "graph");
|
|
11361
|
+
const store = new import_graph.GraphStore();
|
|
11362
|
+
const loaded = await store.load(graphDir);
|
|
11363
|
+
if (!loaded) {
|
|
11364
|
+
return issues;
|
|
11365
|
+
}
|
|
11366
|
+
const results = (0, import_graph.queryTraceability)(store);
|
|
11367
|
+
if (results.length === 0) return issues;
|
|
11368
|
+
const minCoverage = traceConfig.minCoverage ?? 0;
|
|
11369
|
+
const severity = traceConfig.severity ?? "warning";
|
|
11370
|
+
for (const result of results) {
|
|
11371
|
+
const pct = result.summary.coveragePercent;
|
|
11372
|
+
if (pct < minCoverage) {
|
|
11373
|
+
issues.push({
|
|
11374
|
+
severity,
|
|
11375
|
+
message: `Traceability coverage for "${result.featureName}" is ${pct}% (minimum: ${minCoverage}%)`
|
|
11376
|
+
});
|
|
11377
|
+
}
|
|
11378
|
+
for (const req of result.requirements) {
|
|
11379
|
+
if (req.status === "none") {
|
|
11380
|
+
issues.push({
|
|
11381
|
+
severity: "warning",
|
|
11382
|
+
message: `Requirement "${req.requirementName}" has no traced code or tests`
|
|
11383
|
+
});
|
|
11384
|
+
}
|
|
11385
|
+
}
|
|
11386
|
+
}
|
|
11387
|
+
return issues;
|
|
11388
|
+
}
|
|
10232
11389
|
async function runSingleCheck(name, projectRoot, config) {
|
|
10233
11390
|
const start = Date.now();
|
|
10234
11391
|
const issues = [];
|
|
@@ -10258,6 +11415,9 @@ async function runSingleCheck(name, projectRoot, config) {
|
|
|
10258
11415
|
case "arch":
|
|
10259
11416
|
issues.push(...await runArchCheck(projectRoot, config));
|
|
10260
11417
|
break;
|
|
11418
|
+
case "traceability":
|
|
11419
|
+
issues.push(...await runTraceabilityCheck(projectRoot, config));
|
|
11420
|
+
break;
|
|
10261
11421
|
}
|
|
10262
11422
|
} catch (error) {
|
|
10263
11423
|
issues.push({
|
|
@@ -10326,7 +11486,7 @@ async function runCIChecks(input) {
|
|
|
10326
11486
|
}
|
|
10327
11487
|
|
|
10328
11488
|
// src/review/mechanical-checks.ts
|
|
10329
|
-
var
|
|
11489
|
+
var path18 = __toESM(require("path"));
|
|
10330
11490
|
async function runMechanicalChecks(options) {
|
|
10331
11491
|
const { projectRoot, config, skip = [], changedFiles } = options;
|
|
10332
11492
|
const findings = [];
|
|
@@ -10338,7 +11498,7 @@ async function runMechanicalChecks(options) {
|
|
|
10338
11498
|
};
|
|
10339
11499
|
if (!skip.includes("validate")) {
|
|
10340
11500
|
try {
|
|
10341
|
-
const agentsPath =
|
|
11501
|
+
const agentsPath = path18.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
|
|
10342
11502
|
const result = await validateAgentsMap(agentsPath);
|
|
10343
11503
|
if (!result.ok) {
|
|
10344
11504
|
statuses.validate = "fail";
|
|
@@ -10375,7 +11535,7 @@ async function runMechanicalChecks(options) {
|
|
|
10375
11535
|
statuses.validate = "fail";
|
|
10376
11536
|
findings.push({
|
|
10377
11537
|
tool: "validate",
|
|
10378
|
-
file:
|
|
11538
|
+
file: path18.join(projectRoot, "AGENTS.md"),
|
|
10379
11539
|
message: err instanceof Error ? err.message : String(err),
|
|
10380
11540
|
severity: "error"
|
|
10381
11541
|
});
|
|
@@ -10439,7 +11599,7 @@ async function runMechanicalChecks(options) {
|
|
|
10439
11599
|
(async () => {
|
|
10440
11600
|
const localFindings = [];
|
|
10441
11601
|
try {
|
|
10442
|
-
const docsDir =
|
|
11602
|
+
const docsDir = path18.join(projectRoot, config.docsDir ?? "docs");
|
|
10443
11603
|
const result = await checkDocCoverage("project", { docsDir });
|
|
10444
11604
|
if (!result.ok) {
|
|
10445
11605
|
statuses["check-docs"] = "warn";
|
|
@@ -10466,7 +11626,7 @@ async function runMechanicalChecks(options) {
|
|
|
10466
11626
|
statuses["check-docs"] = "warn";
|
|
10467
11627
|
localFindings.push({
|
|
10468
11628
|
tool: "check-docs",
|
|
10469
|
-
file:
|
|
11629
|
+
file: path18.join(projectRoot, "docs"),
|
|
10470
11630
|
message: err instanceof Error ? err.message : String(err),
|
|
10471
11631
|
severity: "warning"
|
|
10472
11632
|
});
|
|
@@ -10614,7 +11774,7 @@ function detectChangeType(commitMessage, diff2) {
|
|
|
10614
11774
|
}
|
|
10615
11775
|
|
|
10616
11776
|
// src/review/context-scoper.ts
|
|
10617
|
-
var
|
|
11777
|
+
var path19 = __toESM(require("path"));
|
|
10618
11778
|
var ALL_DOMAINS = ["compliance", "bug", "security", "architecture"];
|
|
10619
11779
|
var SECURITY_PATTERNS = /auth|crypto|password|secret|token|session|cookie|hash|encrypt|decrypt|sql|shell|exec|eval/i;
|
|
10620
11780
|
function computeContextBudget(diffLines) {
|
|
@@ -10622,18 +11782,18 @@ function computeContextBudget(diffLines) {
|
|
|
10622
11782
|
return diffLines;
|
|
10623
11783
|
}
|
|
10624
11784
|
function isWithinProject(absPath, projectRoot) {
|
|
10625
|
-
const resolvedRoot =
|
|
10626
|
-
const resolvedPath =
|
|
10627
|
-
return resolvedPath.startsWith(resolvedRoot) || resolvedPath ===
|
|
11785
|
+
const resolvedRoot = path19.resolve(projectRoot) + path19.sep;
|
|
11786
|
+
const resolvedPath = path19.resolve(absPath);
|
|
11787
|
+
return resolvedPath.startsWith(resolvedRoot) || resolvedPath === path19.resolve(projectRoot);
|
|
10628
11788
|
}
|
|
10629
11789
|
async function readContextFile(projectRoot, filePath, reason) {
|
|
10630
|
-
const absPath =
|
|
11790
|
+
const absPath = path19.isAbsolute(filePath) ? filePath : path19.join(projectRoot, filePath);
|
|
10631
11791
|
if (!isWithinProject(absPath, projectRoot)) return null;
|
|
10632
11792
|
const result = await readFileContent(absPath);
|
|
10633
11793
|
if (!result.ok) return null;
|
|
10634
11794
|
const content = result.value;
|
|
10635
11795
|
const lines = content.split("\n").length;
|
|
10636
|
-
const relPath =
|
|
11796
|
+
const relPath = path19.isAbsolute(filePath) ? relativePosix(projectRoot, filePath) : filePath;
|
|
10637
11797
|
return { path: relPath, content, reason, lines };
|
|
10638
11798
|
}
|
|
10639
11799
|
function extractImportSources2(content) {
|
|
@@ -10648,18 +11808,18 @@ function extractImportSources2(content) {
|
|
|
10648
11808
|
}
|
|
10649
11809
|
async function resolveImportPath2(projectRoot, fromFile, importSource) {
|
|
10650
11810
|
if (!importSource.startsWith(".")) return null;
|
|
10651
|
-
const fromDir =
|
|
10652
|
-
const basePath =
|
|
11811
|
+
const fromDir = path19.dirname(path19.join(projectRoot, fromFile));
|
|
11812
|
+
const basePath = path19.resolve(fromDir, importSource);
|
|
10653
11813
|
if (!isWithinProject(basePath, projectRoot)) return null;
|
|
10654
11814
|
const relBase = relativePosix(projectRoot, basePath);
|
|
10655
11815
|
const candidates = [
|
|
10656
11816
|
relBase + ".ts",
|
|
10657
11817
|
relBase + ".tsx",
|
|
10658
11818
|
relBase + ".mts",
|
|
10659
|
-
|
|
11819
|
+
path19.join(relBase, "index.ts")
|
|
10660
11820
|
];
|
|
10661
11821
|
for (const candidate of candidates) {
|
|
10662
|
-
const absCandidate =
|
|
11822
|
+
const absCandidate = path19.join(projectRoot, candidate);
|
|
10663
11823
|
if (await fileExists(absCandidate)) {
|
|
10664
11824
|
return candidate;
|
|
10665
11825
|
}
|
|
@@ -10667,7 +11827,7 @@ async function resolveImportPath2(projectRoot, fromFile, importSource) {
|
|
|
10667
11827
|
return null;
|
|
10668
11828
|
}
|
|
10669
11829
|
async function findTestFiles(projectRoot, sourceFile) {
|
|
10670
|
-
const baseName =
|
|
11830
|
+
const baseName = path19.basename(sourceFile, path19.extname(sourceFile));
|
|
10671
11831
|
const pattern = `**/${baseName}.{test,spec}.{ts,tsx,mts}`;
|
|
10672
11832
|
const results = await findFiles(pattern, projectRoot);
|
|
10673
11833
|
return results.map((f) => relativePosix(projectRoot, f));
|
|
@@ -11476,7 +12636,7 @@ async function fanOutReview(options) {
|
|
|
11476
12636
|
}
|
|
11477
12637
|
|
|
11478
12638
|
// src/review/validate-findings.ts
|
|
11479
|
-
var
|
|
12639
|
+
var path20 = __toESM(require("path"));
|
|
11480
12640
|
var DOWNGRADE_MAP = {
|
|
11481
12641
|
critical: "important",
|
|
11482
12642
|
important: "suggestion",
|
|
@@ -11497,7 +12657,7 @@ function normalizePath(filePath, projectRoot) {
|
|
|
11497
12657
|
let normalized = filePath;
|
|
11498
12658
|
normalized = normalized.replace(/\\/g, "/");
|
|
11499
12659
|
const normalizedRoot = projectRoot.replace(/\\/g, "/");
|
|
11500
|
-
if (
|
|
12660
|
+
if (path20.isAbsolute(normalized)) {
|
|
11501
12661
|
const root = normalizedRoot.endsWith("/") ? normalizedRoot : normalizedRoot + "/";
|
|
11502
12662
|
if (normalized.startsWith(root)) {
|
|
11503
12663
|
normalized = normalized.slice(root.length);
|
|
@@ -11522,12 +12682,12 @@ function followImportChain(fromFile, fileContents, maxDepth = 2) {
|
|
|
11522
12682
|
while ((match = importRegex.exec(content)) !== null) {
|
|
11523
12683
|
const importPath = match[1];
|
|
11524
12684
|
if (!importPath.startsWith(".")) continue;
|
|
11525
|
-
const dir =
|
|
11526
|
-
let resolved =
|
|
12685
|
+
const dir = path20.dirname(current.file);
|
|
12686
|
+
let resolved = path20.join(dir, importPath).replace(/\\/g, "/");
|
|
11527
12687
|
if (!resolved.match(/\.(ts|tsx|js|jsx)$/)) {
|
|
11528
12688
|
resolved += ".ts";
|
|
11529
12689
|
}
|
|
11530
|
-
resolved =
|
|
12690
|
+
resolved = path20.normalize(resolved).replace(/\\/g, "/");
|
|
11531
12691
|
if (!visited.has(resolved) && current.depth + 1 <= maxDepth) {
|
|
11532
12692
|
queue.push({ file: resolved, depth: current.depth + 1 });
|
|
11533
12693
|
}
|
|
@@ -11544,7 +12704,7 @@ async function validateFindings(options) {
|
|
|
11544
12704
|
if (exclusionSet.isExcluded(normalizedFile, finding.lineRange) || exclusionSet.isExcluded(finding.file, finding.lineRange)) {
|
|
11545
12705
|
continue;
|
|
11546
12706
|
}
|
|
11547
|
-
const absoluteFile =
|
|
12707
|
+
const absoluteFile = path20.isAbsolute(finding.file) ? finding.file : path20.join(projectRoot, finding.file).replace(/\\/g, "/");
|
|
11548
12708
|
if (exclusionSet.isExcluded(absoluteFile, finding.lineRange)) {
|
|
11549
12709
|
continue;
|
|
11550
12710
|
}
|
|
@@ -12157,203 +13317,6 @@ async function runReviewPipeline(options) {
|
|
|
12157
13317
|
};
|
|
12158
13318
|
}
|
|
12159
13319
|
|
|
12160
|
-
// src/roadmap/parse.ts
|
|
12161
|
-
var import_types19 = require("@harness-engineering/types");
|
|
12162
|
-
var VALID_STATUSES = /* @__PURE__ */ new Set([
|
|
12163
|
-
"backlog",
|
|
12164
|
-
"planned",
|
|
12165
|
-
"in-progress",
|
|
12166
|
-
"done",
|
|
12167
|
-
"blocked"
|
|
12168
|
-
]);
|
|
12169
|
-
var EM_DASH = "\u2014";
|
|
12170
|
-
var VALID_PRIORITIES = /* @__PURE__ */ new Set(["P0", "P1", "P2", "P3"]);
|
|
12171
|
-
function parseRoadmap(markdown) {
|
|
12172
|
-
const fmMatch = markdown.match(/^---\n([\s\S]*?)\n---/);
|
|
12173
|
-
if (!fmMatch) {
|
|
12174
|
-
return (0, import_types19.Err)(new Error("Missing or malformed YAML frontmatter"));
|
|
12175
|
-
}
|
|
12176
|
-
const fmResult = parseFrontmatter2(fmMatch[1]);
|
|
12177
|
-
if (!fmResult.ok) return fmResult;
|
|
12178
|
-
const body = markdown.slice(fmMatch[0].length);
|
|
12179
|
-
const milestonesResult = parseMilestones(body);
|
|
12180
|
-
if (!milestonesResult.ok) return milestonesResult;
|
|
12181
|
-
const historyResult = parseAssignmentHistory(body);
|
|
12182
|
-
if (!historyResult.ok) return historyResult;
|
|
12183
|
-
return (0, import_types19.Ok)({
|
|
12184
|
-
frontmatter: fmResult.value,
|
|
12185
|
-
milestones: milestonesResult.value,
|
|
12186
|
-
assignmentHistory: historyResult.value
|
|
12187
|
-
});
|
|
12188
|
-
}
|
|
12189
|
-
function parseFrontmatter2(raw) {
|
|
12190
|
-
const lines = raw.split("\n");
|
|
12191
|
-
const map = /* @__PURE__ */ new Map();
|
|
12192
|
-
for (const line of lines) {
|
|
12193
|
-
const idx = line.indexOf(":");
|
|
12194
|
-
if (idx === -1) continue;
|
|
12195
|
-
const key = line.slice(0, idx).trim();
|
|
12196
|
-
const val = line.slice(idx + 1).trim();
|
|
12197
|
-
map.set(key, val);
|
|
12198
|
-
}
|
|
12199
|
-
const project = map.get("project");
|
|
12200
|
-
const versionStr = map.get("version");
|
|
12201
|
-
const lastSynced = map.get("last_synced");
|
|
12202
|
-
const lastManualEdit = map.get("last_manual_edit");
|
|
12203
|
-
const created = map.get("created");
|
|
12204
|
-
const updated = map.get("updated");
|
|
12205
|
-
if (!project || !versionStr || !lastSynced || !lastManualEdit) {
|
|
12206
|
-
return (0, import_types19.Err)(
|
|
12207
|
-
new Error(
|
|
12208
|
-
"Frontmatter missing required fields: project, version, last_synced, last_manual_edit"
|
|
12209
|
-
)
|
|
12210
|
-
);
|
|
12211
|
-
}
|
|
12212
|
-
const version = parseInt(versionStr, 10);
|
|
12213
|
-
if (isNaN(version)) {
|
|
12214
|
-
return (0, import_types19.Err)(new Error("Frontmatter version must be a number"));
|
|
12215
|
-
}
|
|
12216
|
-
const fm = { project, version, lastSynced, lastManualEdit };
|
|
12217
|
-
if (created) fm.created = created;
|
|
12218
|
-
if (updated) fm.updated = updated;
|
|
12219
|
-
return (0, import_types19.Ok)(fm);
|
|
12220
|
-
}
|
|
12221
|
-
function parseMilestones(body) {
|
|
12222
|
-
const milestones = [];
|
|
12223
|
-
const h2Pattern = /^## (.+)$/gm;
|
|
12224
|
-
const h2Matches = [];
|
|
12225
|
-
let match;
|
|
12226
|
-
let bodyEnd = body.length;
|
|
12227
|
-
while ((match = h2Pattern.exec(body)) !== null) {
|
|
12228
|
-
if (match[1] === "Assignment History") {
|
|
12229
|
-
bodyEnd = match.index;
|
|
12230
|
-
break;
|
|
12231
|
-
}
|
|
12232
|
-
h2Matches.push({ heading: match[1], startIndex: match.index, fullMatch: match[0] });
|
|
12233
|
-
}
|
|
12234
|
-
for (let i = 0; i < h2Matches.length; i++) {
|
|
12235
|
-
const h2 = h2Matches[i];
|
|
12236
|
-
const nextStart = i + 1 < h2Matches.length ? h2Matches[i + 1].startIndex : bodyEnd;
|
|
12237
|
-
const sectionBody = body.slice(h2.startIndex + h2.fullMatch.length, nextStart);
|
|
12238
|
-
const isBacklog = h2.heading === "Backlog";
|
|
12239
|
-
const milestoneName = isBacklog ? "Backlog" : h2.heading.replace(/^Milestone:\s*/, "");
|
|
12240
|
-
const featuresResult = parseFeatures(sectionBody);
|
|
12241
|
-
if (!featuresResult.ok) return featuresResult;
|
|
12242
|
-
milestones.push({
|
|
12243
|
-
name: milestoneName,
|
|
12244
|
-
isBacklog,
|
|
12245
|
-
features: featuresResult.value
|
|
12246
|
-
});
|
|
12247
|
-
}
|
|
12248
|
-
return (0, import_types19.Ok)(milestones);
|
|
12249
|
-
}
|
|
12250
|
-
function parseFeatures(sectionBody) {
|
|
12251
|
-
const features = [];
|
|
12252
|
-
const h3Pattern = /^### (?:Feature: )?(.+)$/gm;
|
|
12253
|
-
const h3Matches = [];
|
|
12254
|
-
let match;
|
|
12255
|
-
while ((match = h3Pattern.exec(sectionBody)) !== null) {
|
|
12256
|
-
h3Matches.push({ name: match[1], startIndex: match.index, fullMatch: match[0] });
|
|
12257
|
-
}
|
|
12258
|
-
for (let i = 0; i < h3Matches.length; i++) {
|
|
12259
|
-
const h3 = h3Matches[i];
|
|
12260
|
-
const nextStart = i + 1 < h3Matches.length ? h3Matches[i + 1].startIndex : sectionBody.length;
|
|
12261
|
-
const featureBody = sectionBody.slice(h3.startIndex + h3.fullMatch.length, nextStart);
|
|
12262
|
-
const featureResult = parseFeatureFields(h3.name, featureBody);
|
|
12263
|
-
if (!featureResult.ok) return featureResult;
|
|
12264
|
-
features.push(featureResult.value);
|
|
12265
|
-
}
|
|
12266
|
-
return (0, import_types19.Ok)(features);
|
|
12267
|
-
}
|
|
12268
|
-
function extractFieldMap(body) {
|
|
12269
|
-
const fieldMap = /* @__PURE__ */ new Map();
|
|
12270
|
-
const fieldPattern = /^- \*\*(.+?):\*\* (.+)$/gm;
|
|
12271
|
-
let match;
|
|
12272
|
-
while ((match = fieldPattern.exec(body)) !== null) {
|
|
12273
|
-
fieldMap.set(match[1], match[2]);
|
|
12274
|
-
}
|
|
12275
|
-
return fieldMap;
|
|
12276
|
-
}
|
|
12277
|
-
function parseListField(fieldMap, ...keys) {
|
|
12278
|
-
let raw = EM_DASH;
|
|
12279
|
-
for (const key of keys) {
|
|
12280
|
-
const val = fieldMap.get(key);
|
|
12281
|
-
if (val !== void 0) {
|
|
12282
|
-
raw = val;
|
|
12283
|
-
break;
|
|
12284
|
-
}
|
|
12285
|
-
}
|
|
12286
|
-
if (raw === EM_DASH || raw === "none") return [];
|
|
12287
|
-
return raw.split(",").map((s) => s.trim());
|
|
12288
|
-
}
|
|
12289
|
-
function parseFeatureFields(name, body) {
|
|
12290
|
-
const fieldMap = extractFieldMap(body);
|
|
12291
|
-
const statusRaw = fieldMap.get("Status");
|
|
12292
|
-
if (!statusRaw || !VALID_STATUSES.has(statusRaw)) {
|
|
12293
|
-
return (0, import_types19.Err)(
|
|
12294
|
-
new Error(
|
|
12295
|
-
`Feature "${name}" has invalid status: "${statusRaw ?? "(missing)"}". Valid statuses: ${[...VALID_STATUSES].join(", ")}`
|
|
12296
|
-
)
|
|
12297
|
-
);
|
|
12298
|
-
}
|
|
12299
|
-
const specRaw = fieldMap.get("Spec") ?? EM_DASH;
|
|
12300
|
-
const plans = parseListField(fieldMap, "Plans", "Plan");
|
|
12301
|
-
const blockedBy = parseListField(fieldMap, "Blocked by", "Blockers");
|
|
12302
|
-
const assigneeRaw = fieldMap.get("Assignee") ?? EM_DASH;
|
|
12303
|
-
const priorityRaw = fieldMap.get("Priority") ?? EM_DASH;
|
|
12304
|
-
const externalIdRaw = fieldMap.get("External-ID") ?? EM_DASH;
|
|
12305
|
-
if (priorityRaw !== EM_DASH && !VALID_PRIORITIES.has(priorityRaw)) {
|
|
12306
|
-
return (0, import_types19.Err)(
|
|
12307
|
-
new Error(
|
|
12308
|
-
`Feature "${name}" has invalid priority: "${priorityRaw}". Valid priorities: ${[...VALID_PRIORITIES].join(", ")}`
|
|
12309
|
-
)
|
|
12310
|
-
);
|
|
12311
|
-
}
|
|
12312
|
-
return (0, import_types19.Ok)({
|
|
12313
|
-
name,
|
|
12314
|
-
status: statusRaw,
|
|
12315
|
-
spec: specRaw === EM_DASH ? null : specRaw,
|
|
12316
|
-
plans,
|
|
12317
|
-
blockedBy,
|
|
12318
|
-
summary: fieldMap.get("Summary") ?? "",
|
|
12319
|
-
assignee: assigneeRaw === EM_DASH ? null : assigneeRaw,
|
|
12320
|
-
priority: priorityRaw === EM_DASH ? null : priorityRaw,
|
|
12321
|
-
externalId: externalIdRaw === EM_DASH ? null : externalIdRaw
|
|
12322
|
-
});
|
|
12323
|
-
}
|
|
12324
|
-
function parseAssignmentHistory(body) {
|
|
12325
|
-
const historyMatch = body.match(/^## Assignment History\s*\n/m);
|
|
12326
|
-
if (!historyMatch || historyMatch.index === void 0) return (0, import_types19.Ok)([]);
|
|
12327
|
-
const historyStart = historyMatch.index + historyMatch[0].length;
|
|
12328
|
-
const rawHistoryBody = body.slice(historyStart);
|
|
12329
|
-
const nextH2 = rawHistoryBody.search(/^## /m);
|
|
12330
|
-
const historyBody = nextH2 === -1 ? rawHistoryBody : rawHistoryBody.slice(0, nextH2);
|
|
12331
|
-
const records = [];
|
|
12332
|
-
const lines = historyBody.split("\n");
|
|
12333
|
-
let pastHeader = false;
|
|
12334
|
-
for (const line of lines) {
|
|
12335
|
-
const trimmed = line.trim();
|
|
12336
|
-
if (!trimmed.startsWith("|")) continue;
|
|
12337
|
-
if (!pastHeader) {
|
|
12338
|
-
if (trimmed.match(/^\|[-\s|]+\|$/)) {
|
|
12339
|
-
pastHeader = true;
|
|
12340
|
-
}
|
|
12341
|
-
continue;
|
|
12342
|
-
}
|
|
12343
|
-
const cells = trimmed.split("|").map((c) => c.trim()).filter((c) => c.length > 0);
|
|
12344
|
-
if (cells.length < 4) continue;
|
|
12345
|
-
const action = cells[2];
|
|
12346
|
-
if (!["assigned", "completed", "unassigned"].includes(action)) continue;
|
|
12347
|
-
records.push({
|
|
12348
|
-
feature: cells[0],
|
|
12349
|
-
assignee: cells[1],
|
|
12350
|
-
action,
|
|
12351
|
-
date: cells[3]
|
|
12352
|
-
});
|
|
12353
|
-
}
|
|
12354
|
-
return (0, import_types19.Ok)(records);
|
|
12355
|
-
}
|
|
12356
|
-
|
|
12357
13320
|
// src/roadmap/serialize.ts
|
|
12358
13321
|
var EM_DASH2 = "\u2014";
|
|
12359
13322
|
function serializeRoadmap(roadmap) {
|
|
@@ -12424,9 +13387,9 @@ function serializeAssignmentHistory(records) {
|
|
|
12424
13387
|
}
|
|
12425
13388
|
|
|
12426
13389
|
// src/roadmap/sync.ts
|
|
12427
|
-
var
|
|
12428
|
-
var
|
|
12429
|
-
var
|
|
13390
|
+
var fs21 = __toESM(require("fs"));
|
|
13391
|
+
var path21 = __toESM(require("path"));
|
|
13392
|
+
var import_types24 = require("@harness-engineering/types");
|
|
12430
13393
|
|
|
12431
13394
|
// src/roadmap/status-rank.ts
|
|
12432
13395
|
var STATUS_RANK = {
|
|
@@ -12455,10 +13418,10 @@ function inferStatus(feature, projectPath, allFeatures) {
|
|
|
12455
13418
|
const featuresWithPlans = allFeatures.filter((f) => f.plans.length > 0);
|
|
12456
13419
|
const useRootState = featuresWithPlans.length <= 1;
|
|
12457
13420
|
if (useRootState) {
|
|
12458
|
-
const rootStatePath =
|
|
12459
|
-
if (
|
|
13421
|
+
const rootStatePath = path21.join(projectPath, ".harness", "state.json");
|
|
13422
|
+
if (fs21.existsSync(rootStatePath)) {
|
|
12460
13423
|
try {
|
|
12461
|
-
const raw =
|
|
13424
|
+
const raw = fs21.readFileSync(rootStatePath, "utf-8");
|
|
12462
13425
|
const state = JSON.parse(raw);
|
|
12463
13426
|
if (state.progress) {
|
|
12464
13427
|
for (const status of Object.values(state.progress)) {
|
|
@@ -12469,16 +13432,16 @@ function inferStatus(feature, projectPath, allFeatures) {
|
|
|
12469
13432
|
}
|
|
12470
13433
|
}
|
|
12471
13434
|
}
|
|
12472
|
-
const sessionsDir =
|
|
12473
|
-
if (
|
|
13435
|
+
const sessionsDir = path21.join(projectPath, ".harness", "sessions");
|
|
13436
|
+
if (fs21.existsSync(sessionsDir)) {
|
|
12474
13437
|
try {
|
|
12475
|
-
const sessionDirs =
|
|
13438
|
+
const sessionDirs = fs21.readdirSync(sessionsDir, { withFileTypes: true });
|
|
12476
13439
|
for (const entry of sessionDirs) {
|
|
12477
13440
|
if (!entry.isDirectory()) continue;
|
|
12478
|
-
const autopilotPath =
|
|
12479
|
-
if (!
|
|
13441
|
+
const autopilotPath = path21.join(sessionsDir, entry.name, "autopilot-state.json");
|
|
13442
|
+
if (!fs21.existsSync(autopilotPath)) continue;
|
|
12480
13443
|
try {
|
|
12481
|
-
const raw =
|
|
13444
|
+
const raw = fs21.readFileSync(autopilotPath, "utf-8");
|
|
12482
13445
|
const autopilot = JSON.parse(raw);
|
|
12483
13446
|
if (!autopilot.phases) continue;
|
|
12484
13447
|
const linkedPhases = autopilot.phases.filter(
|
|
@@ -12523,7 +13486,7 @@ function syncRoadmap(options) {
|
|
|
12523
13486
|
to: inferred
|
|
12524
13487
|
});
|
|
12525
13488
|
}
|
|
12526
|
-
return (0,
|
|
13489
|
+
return (0, import_types24.Ok)(changes);
|
|
12527
13490
|
}
|
|
12528
13491
|
function applySyncChanges(roadmap, changes) {
|
|
12529
13492
|
for (const change of changes) {
|
|
@@ -12557,7 +13520,7 @@ function resolveReverseStatus(externalStatus, labels, config) {
|
|
|
12557
13520
|
}
|
|
12558
13521
|
|
|
12559
13522
|
// src/roadmap/adapters/github-issues.ts
|
|
12560
|
-
var
|
|
13523
|
+
var import_types25 = require("@harness-engineering/types");
|
|
12561
13524
|
function parseExternalId(externalId) {
|
|
12562
13525
|
const match = externalId.match(/^github:([^/]+)\/([^#]+)#(\d+)$/);
|
|
12563
13526
|
if (!match) return null;
|
|
@@ -12575,6 +13538,13 @@ function labelsForStatus(status, config) {
|
|
|
12575
13538
|
return [...base];
|
|
12576
13539
|
}
|
|
12577
13540
|
var RETRY_DEFAULTS = { maxRetries: 5, baseDelayMs: 1e3 };
|
|
13541
|
+
function parseRepoParts(repo) {
|
|
13542
|
+
const parts = (repo ?? "").split("/");
|
|
13543
|
+
if (parts.length !== 2 || !parts[0] || !parts[1]) {
|
|
13544
|
+
throw new Error(`Invalid repo format: "${repo}". Expected "owner/repo".`);
|
|
13545
|
+
}
|
|
13546
|
+
return { owner: parts[0], repo: parts[1] };
|
|
13547
|
+
}
|
|
12578
13548
|
function sleep(ms) {
|
|
12579
13549
|
return new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
12580
13550
|
}
|
|
@@ -12618,12 +13588,9 @@ var GitHubIssuesSyncAdapter = class {
|
|
|
12618
13588
|
maxRetries: options.maxRetries ?? RETRY_DEFAULTS.maxRetries,
|
|
12619
13589
|
baseDelayMs: options.baseDelayMs ?? RETRY_DEFAULTS.baseDelayMs
|
|
12620
13590
|
};
|
|
12621
|
-
const
|
|
12622
|
-
|
|
12623
|
-
|
|
12624
|
-
}
|
|
12625
|
-
this.owner = repoParts[0];
|
|
12626
|
-
this.repo = repoParts[1];
|
|
13591
|
+
const { owner, repo } = parseRepoParts(options.config.repo);
|
|
13592
|
+
this.owner = owner;
|
|
13593
|
+
this.repo = repo;
|
|
12627
13594
|
}
|
|
12628
13595
|
/**
|
|
12629
13596
|
* Fetch all GitHub milestones and build the name -> ID cache.
|
|
@@ -12663,7 +13630,7 @@ var GitHubIssuesSyncAdapter = class {
|
|
|
12663
13630
|
return data.number;
|
|
12664
13631
|
}
|
|
12665
13632
|
async getAuthenticatedUser() {
|
|
12666
|
-
if (this.authenticatedUserCache) return (0,
|
|
13633
|
+
if (this.authenticatedUserCache) return (0, import_types25.Ok)(this.authenticatedUserCache);
|
|
12667
13634
|
try {
|
|
12668
13635
|
const response = await fetchWithRetry(
|
|
12669
13636
|
this.fetchFn,
|
|
@@ -12673,13 +13640,13 @@ var GitHubIssuesSyncAdapter = class {
|
|
|
12673
13640
|
);
|
|
12674
13641
|
if (!response.ok) {
|
|
12675
13642
|
const text = await response.text();
|
|
12676
|
-
return (0,
|
|
13643
|
+
return (0, import_types25.Err)(new Error(`GitHub API error ${response.status}: ${text}`));
|
|
12677
13644
|
}
|
|
12678
13645
|
const data = await response.json();
|
|
12679
13646
|
this.authenticatedUserCache = `@${data.login}`;
|
|
12680
|
-
return (0,
|
|
13647
|
+
return (0, import_types25.Ok)(this.authenticatedUserCache);
|
|
12681
13648
|
} catch (error) {
|
|
12682
|
-
return (0,
|
|
13649
|
+
return (0, import_types25.Err)(error instanceof Error ? error : new Error(String(error)));
|
|
12683
13650
|
}
|
|
12684
13651
|
}
|
|
12685
13652
|
headers() {
|
|
@@ -12723,20 +13690,20 @@ var GitHubIssuesSyncAdapter = class {
|
|
|
12723
13690
|
);
|
|
12724
13691
|
if (!response.ok) {
|
|
12725
13692
|
const text = await response.text();
|
|
12726
|
-
return (0,
|
|
13693
|
+
return (0, import_types25.Err)(new Error(`GitHub API error ${response.status}: ${text}`));
|
|
12727
13694
|
}
|
|
12728
13695
|
const data = await response.json();
|
|
12729
13696
|
const externalId = buildExternalId(this.owner, this.repo, data.number);
|
|
12730
13697
|
await this.closeIfDone(data.number, feature.status);
|
|
12731
|
-
return (0,
|
|
13698
|
+
return (0, import_types25.Ok)({ externalId, url: data.html_url });
|
|
12732
13699
|
} catch (error) {
|
|
12733
|
-
return (0,
|
|
13700
|
+
return (0, import_types25.Err)(error instanceof Error ? error : new Error(String(error)));
|
|
12734
13701
|
}
|
|
12735
13702
|
}
|
|
12736
13703
|
async updateTicket(externalId, changes, milestone) {
|
|
12737
13704
|
try {
|
|
12738
13705
|
const parsed = parseExternalId(externalId);
|
|
12739
|
-
if (!parsed) return (0,
|
|
13706
|
+
if (!parsed) return (0, import_types25.Err)(new Error(`Invalid externalId format: "${externalId}"`));
|
|
12740
13707
|
const patch = {};
|
|
12741
13708
|
if (changes.name !== void 0) patch.title = changes.name;
|
|
12742
13709
|
if (changes.summary !== void 0) {
|
|
@@ -12772,18 +13739,18 @@ var GitHubIssuesSyncAdapter = class {
|
|
|
12772
13739
|
);
|
|
12773
13740
|
if (!response.ok) {
|
|
12774
13741
|
const text = await response.text();
|
|
12775
|
-
return (0,
|
|
13742
|
+
return (0, import_types25.Err)(new Error(`GitHub API error ${response.status}: ${text}`));
|
|
12776
13743
|
}
|
|
12777
13744
|
const data = await response.json();
|
|
12778
|
-
return (0,
|
|
13745
|
+
return (0, import_types25.Ok)({ externalId, url: data.html_url });
|
|
12779
13746
|
} catch (error) {
|
|
12780
|
-
return (0,
|
|
13747
|
+
return (0, import_types25.Err)(error instanceof Error ? error : new Error(String(error)));
|
|
12781
13748
|
}
|
|
12782
13749
|
}
|
|
12783
13750
|
async fetchTicketState(externalId) {
|
|
12784
13751
|
try {
|
|
12785
13752
|
const parsed = parseExternalId(externalId);
|
|
12786
|
-
if (!parsed) return (0,
|
|
13753
|
+
if (!parsed) return (0, import_types25.Err)(new Error(`Invalid externalId format: "${externalId}"`));
|
|
12787
13754
|
const response = await fetchWithRetry(
|
|
12788
13755
|
this.fetchFn,
|
|
12789
13756
|
`${this.apiBase}/repos/${parsed.owner}/${parsed.repo}/issues/${parsed.number}`,
|
|
@@ -12795,17 +13762,17 @@ var GitHubIssuesSyncAdapter = class {
|
|
|
12795
13762
|
);
|
|
12796
13763
|
if (!response.ok) {
|
|
12797
13764
|
const text = await response.text();
|
|
12798
|
-
return (0,
|
|
13765
|
+
return (0, import_types25.Err)(new Error(`GitHub API error ${response.status}: ${text}`));
|
|
12799
13766
|
}
|
|
12800
13767
|
const data = await response.json();
|
|
12801
|
-
return (0,
|
|
13768
|
+
return (0, import_types25.Ok)({
|
|
12802
13769
|
externalId,
|
|
12803
13770
|
status: data.state,
|
|
12804
13771
|
labels: data.labels.map((l) => l.name),
|
|
12805
13772
|
assignee: data.assignee ? `@${data.assignee.login}` : null
|
|
12806
13773
|
});
|
|
12807
13774
|
} catch (error) {
|
|
12808
|
-
return (0,
|
|
13775
|
+
return (0, import_types25.Err)(error instanceof Error ? error : new Error(String(error)));
|
|
12809
13776
|
}
|
|
12810
13777
|
}
|
|
12811
13778
|
async fetchAllTickets() {
|
|
@@ -12827,7 +13794,7 @@ var GitHubIssuesSyncAdapter = class {
|
|
|
12827
13794
|
);
|
|
12828
13795
|
if (!response.ok) {
|
|
12829
13796
|
const text = await response.text();
|
|
12830
|
-
return (0,
|
|
13797
|
+
return (0, import_types25.Err)(new Error(`GitHub API error ${response.status}: ${text}`));
|
|
12831
13798
|
}
|
|
12832
13799
|
const data = await response.json();
|
|
12833
13800
|
const issues = data.filter((d) => !d.pull_request);
|
|
@@ -12842,15 +13809,15 @@ var GitHubIssuesSyncAdapter = class {
|
|
|
12842
13809
|
if (data.length < perPage) break;
|
|
12843
13810
|
page++;
|
|
12844
13811
|
}
|
|
12845
|
-
return (0,
|
|
13812
|
+
return (0, import_types25.Ok)(tickets);
|
|
12846
13813
|
} catch (error) {
|
|
12847
|
-
return (0,
|
|
13814
|
+
return (0, import_types25.Err)(error instanceof Error ? error : new Error(String(error)));
|
|
12848
13815
|
}
|
|
12849
13816
|
}
|
|
12850
13817
|
async assignTicket(externalId, assignee) {
|
|
12851
13818
|
try {
|
|
12852
13819
|
const parsed = parseExternalId(externalId);
|
|
12853
|
-
if (!parsed) return (0,
|
|
13820
|
+
if (!parsed) return (0, import_types25.Err)(new Error(`Invalid externalId format: "${externalId}"`));
|
|
12854
13821
|
const login = assignee.startsWith("@") ? assignee.slice(1) : assignee;
|
|
12855
13822
|
const response = await fetchWithRetry(
|
|
12856
13823
|
this.fetchFn,
|
|
@@ -12864,17 +13831,17 @@ var GitHubIssuesSyncAdapter = class {
|
|
|
12864
13831
|
);
|
|
12865
13832
|
if (!response.ok) {
|
|
12866
13833
|
const text = await response.text();
|
|
12867
|
-
return (0,
|
|
13834
|
+
return (0, import_types25.Err)(new Error(`GitHub API error ${response.status}: ${text}`));
|
|
12868
13835
|
}
|
|
12869
|
-
return (0,
|
|
13836
|
+
return (0, import_types25.Ok)(void 0);
|
|
12870
13837
|
} catch (error) {
|
|
12871
|
-
return (0,
|
|
13838
|
+
return (0, import_types25.Err)(error instanceof Error ? error : new Error(String(error)));
|
|
12872
13839
|
}
|
|
12873
13840
|
}
|
|
12874
13841
|
};
|
|
12875
13842
|
|
|
12876
13843
|
// src/roadmap/sync-engine.ts
|
|
12877
|
-
var
|
|
13844
|
+
var fs22 = __toESM(require("fs"));
|
|
12878
13845
|
function emptySyncResult() {
|
|
12879
13846
|
return { created: [], updated: [], assignmentChanges: [], errors: [] };
|
|
12880
13847
|
}
|
|
@@ -12962,7 +13929,7 @@ async function fullSync(roadmapPath, adapter, config, options) {
|
|
|
12962
13929
|
});
|
|
12963
13930
|
await previousSync;
|
|
12964
13931
|
try {
|
|
12965
|
-
const raw =
|
|
13932
|
+
const raw = fs22.readFileSync(roadmapPath, "utf-8");
|
|
12966
13933
|
const parseResult = parseRoadmap(raw);
|
|
12967
13934
|
if (!parseResult.ok) {
|
|
12968
13935
|
return {
|
|
@@ -12973,7 +13940,7 @@ async function fullSync(roadmapPath, adapter, config, options) {
|
|
|
12973
13940
|
const roadmap = parseResult.value;
|
|
12974
13941
|
const pushResult = await syncToExternal(roadmap, adapter, config);
|
|
12975
13942
|
const pullResult = await syncFromExternal(roadmap, adapter, config, options);
|
|
12976
|
-
|
|
13943
|
+
fs22.writeFileSync(roadmapPath, serializeRoadmap(roadmap), "utf-8");
|
|
12977
13944
|
return {
|
|
12978
13945
|
created: pushResult.created,
|
|
12979
13946
|
updated: pushResult.updated,
|
|
@@ -13103,47 +14070,47 @@ function assignFeature(roadmap, feature, assignee, date) {
|
|
|
13103
14070
|
}
|
|
13104
14071
|
|
|
13105
14072
|
// src/interaction/types.ts
|
|
13106
|
-
var
|
|
13107
|
-
var InteractionTypeSchema =
|
|
13108
|
-
var QuestionSchema =
|
|
13109
|
-
text:
|
|
13110
|
-
options:
|
|
13111
|
-
default:
|
|
14073
|
+
var import_zod10 = require("zod");
|
|
14074
|
+
var InteractionTypeSchema = import_zod10.z.enum(["question", "confirmation", "transition"]);
|
|
14075
|
+
var QuestionSchema = import_zod10.z.object({
|
|
14076
|
+
text: import_zod10.z.string(),
|
|
14077
|
+
options: import_zod10.z.array(import_zod10.z.string()).optional(),
|
|
14078
|
+
default: import_zod10.z.string().optional()
|
|
13112
14079
|
});
|
|
13113
|
-
var ConfirmationSchema =
|
|
13114
|
-
text:
|
|
13115
|
-
context:
|
|
14080
|
+
var ConfirmationSchema = import_zod10.z.object({
|
|
14081
|
+
text: import_zod10.z.string(),
|
|
14082
|
+
context: import_zod10.z.string()
|
|
13116
14083
|
});
|
|
13117
|
-
var TransitionSchema =
|
|
13118
|
-
completedPhase:
|
|
13119
|
-
suggestedNext:
|
|
13120
|
-
reason:
|
|
13121
|
-
artifacts:
|
|
13122
|
-
requiresConfirmation:
|
|
13123
|
-
summary:
|
|
14084
|
+
var TransitionSchema = import_zod10.z.object({
|
|
14085
|
+
completedPhase: import_zod10.z.string(),
|
|
14086
|
+
suggestedNext: import_zod10.z.string(),
|
|
14087
|
+
reason: import_zod10.z.string(),
|
|
14088
|
+
artifacts: import_zod10.z.array(import_zod10.z.string()),
|
|
14089
|
+
requiresConfirmation: import_zod10.z.boolean(),
|
|
14090
|
+
summary: import_zod10.z.string()
|
|
13124
14091
|
});
|
|
13125
|
-
var EmitInteractionInputSchema =
|
|
13126
|
-
path:
|
|
14092
|
+
var EmitInteractionInputSchema = import_zod10.z.object({
|
|
14093
|
+
path: import_zod10.z.string(),
|
|
13127
14094
|
type: InteractionTypeSchema,
|
|
13128
|
-
stream:
|
|
14095
|
+
stream: import_zod10.z.string().optional(),
|
|
13129
14096
|
question: QuestionSchema.optional(),
|
|
13130
14097
|
confirmation: ConfirmationSchema.optional(),
|
|
13131
14098
|
transition: TransitionSchema.optional()
|
|
13132
14099
|
});
|
|
13133
14100
|
|
|
13134
14101
|
// src/blueprint/scanner.ts
|
|
13135
|
-
var
|
|
13136
|
-
var
|
|
14102
|
+
var fs23 = __toESM(require("fs/promises"));
|
|
14103
|
+
var path22 = __toESM(require("path"));
|
|
13137
14104
|
var ProjectScanner = class {
|
|
13138
14105
|
constructor(rootDir) {
|
|
13139
14106
|
this.rootDir = rootDir;
|
|
13140
14107
|
}
|
|
13141
14108
|
rootDir;
|
|
13142
14109
|
async scan() {
|
|
13143
|
-
let projectName =
|
|
14110
|
+
let projectName = path22.basename(this.rootDir);
|
|
13144
14111
|
try {
|
|
13145
|
-
const pkgPath =
|
|
13146
|
-
const pkgRaw = await
|
|
14112
|
+
const pkgPath = path22.join(this.rootDir, "package.json");
|
|
14113
|
+
const pkgRaw = await fs23.readFile(pkgPath, "utf-8");
|
|
13147
14114
|
const pkg = JSON.parse(pkgRaw);
|
|
13148
14115
|
if (pkg.name) projectName = pkg.name;
|
|
13149
14116
|
} catch {
|
|
@@ -13184,8 +14151,8 @@ var ProjectScanner = class {
|
|
|
13184
14151
|
};
|
|
13185
14152
|
|
|
13186
14153
|
// src/blueprint/generator.ts
|
|
13187
|
-
var
|
|
13188
|
-
var
|
|
14154
|
+
var fs24 = __toESM(require("fs/promises"));
|
|
14155
|
+
var path23 = __toESM(require("path"));
|
|
13189
14156
|
var ejs = __toESM(require("ejs"));
|
|
13190
14157
|
|
|
13191
14158
|
// src/blueprint/templates.ts
|
|
@@ -13269,19 +14236,19 @@ var BlueprintGenerator = class {
|
|
|
13269
14236
|
styles: STYLES,
|
|
13270
14237
|
scripts: SCRIPTS
|
|
13271
14238
|
});
|
|
13272
|
-
await
|
|
13273
|
-
await
|
|
14239
|
+
await fs24.mkdir(options.outputDir, { recursive: true });
|
|
14240
|
+
await fs24.writeFile(path23.join(options.outputDir, "index.html"), html);
|
|
13274
14241
|
}
|
|
13275
14242
|
};
|
|
13276
14243
|
|
|
13277
14244
|
// src/update-checker.ts
|
|
13278
|
-
var
|
|
13279
|
-
var
|
|
14245
|
+
var fs25 = __toESM(require("fs"));
|
|
14246
|
+
var path24 = __toESM(require("path"));
|
|
13280
14247
|
var os = __toESM(require("os"));
|
|
13281
14248
|
var import_child_process3 = require("child_process");
|
|
13282
14249
|
function getStatePath() {
|
|
13283
14250
|
const home = process.env["HOME"] || os.homedir();
|
|
13284
|
-
return
|
|
14251
|
+
return path24.join(home, ".harness", "update-check.json");
|
|
13285
14252
|
}
|
|
13286
14253
|
function isUpdateCheckEnabled(configInterval) {
|
|
13287
14254
|
if (process.env["HARNESS_NO_UPDATE_CHECK"] === "1") return false;
|
|
@@ -13294,7 +14261,7 @@ function shouldRunCheck(state, intervalMs) {
|
|
|
13294
14261
|
}
|
|
13295
14262
|
function readCheckState() {
|
|
13296
14263
|
try {
|
|
13297
|
-
const raw =
|
|
14264
|
+
const raw = fs25.readFileSync(getStatePath(), "utf-8");
|
|
13298
14265
|
const parsed = JSON.parse(raw);
|
|
13299
14266
|
if (typeof parsed === "object" && parsed !== null && "lastCheckTime" in parsed && typeof parsed.lastCheckTime === "number" && "currentVersion" in parsed && typeof parsed.currentVersion === "string") {
|
|
13300
14267
|
const state = parsed;
|
|
@@ -13311,7 +14278,7 @@ function readCheckState() {
|
|
|
13311
14278
|
}
|
|
13312
14279
|
function spawnBackgroundCheck(currentVersion) {
|
|
13313
14280
|
const statePath = getStatePath();
|
|
13314
|
-
const stateDir =
|
|
14281
|
+
const stateDir = path24.dirname(statePath);
|
|
13315
14282
|
const script = `
|
|
13316
14283
|
const { execSync } = require('child_process');
|
|
13317
14284
|
const fs = require('fs');
|
|
@@ -13401,9 +14368,9 @@ async function resolveWasmPath(grammarName) {
|
|
|
13401
14368
|
const { createRequire } = await import("module");
|
|
13402
14369
|
const require2 = createRequire(import_meta.url ?? __filename);
|
|
13403
14370
|
const pkgPath = require2.resolve("tree-sitter-wasms/package.json");
|
|
13404
|
-
const
|
|
13405
|
-
const pkgDir =
|
|
13406
|
-
return
|
|
14371
|
+
const path28 = await import("path");
|
|
14372
|
+
const pkgDir = path28.dirname(pkgPath);
|
|
14373
|
+
return path28.join(pkgDir, "out", `${grammarName}.wasm`);
|
|
13407
14374
|
}
|
|
13408
14375
|
async function loadLanguage(lang) {
|
|
13409
14376
|
const grammarName = GRAMMAR_MAP[lang];
|
|
@@ -13807,8 +14774,8 @@ function getModelPrice(model, dataset) {
|
|
|
13807
14774
|
}
|
|
13808
14775
|
|
|
13809
14776
|
// src/pricing/cache.ts
|
|
13810
|
-
var
|
|
13811
|
-
var
|
|
14777
|
+
var fs26 = __toESM(require("fs/promises"));
|
|
14778
|
+
var path25 = __toESM(require("path"));
|
|
13812
14779
|
|
|
13813
14780
|
// src/pricing/fallback.json
|
|
13814
14781
|
var fallback_default = {
|
|
@@ -13861,14 +14828,14 @@ var LITELLM_PRICING_URL = "https://raw.githubusercontent.com/BerriAI/litellm/mai
|
|
|
13861
14828
|
var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
13862
14829
|
var STALENESS_WARNING_DAYS = 7;
|
|
13863
14830
|
function getCachePath(projectRoot) {
|
|
13864
|
-
return
|
|
14831
|
+
return path25.join(projectRoot, ".harness", "cache", "pricing.json");
|
|
13865
14832
|
}
|
|
13866
14833
|
function getStalenessMarkerPath(projectRoot) {
|
|
13867
|
-
return
|
|
14834
|
+
return path25.join(projectRoot, ".harness", "cache", "staleness-marker.json");
|
|
13868
14835
|
}
|
|
13869
14836
|
async function readDiskCache(projectRoot) {
|
|
13870
14837
|
try {
|
|
13871
|
-
const raw = await
|
|
14838
|
+
const raw = await fs26.readFile(getCachePath(projectRoot), "utf-8");
|
|
13872
14839
|
return JSON.parse(raw);
|
|
13873
14840
|
} catch {
|
|
13874
14841
|
return null;
|
|
@@ -13876,8 +14843,8 @@ async function readDiskCache(projectRoot) {
|
|
|
13876
14843
|
}
|
|
13877
14844
|
async function writeDiskCache(projectRoot, data) {
|
|
13878
14845
|
const cachePath = getCachePath(projectRoot);
|
|
13879
|
-
await
|
|
13880
|
-
await
|
|
14846
|
+
await fs26.mkdir(path25.dirname(cachePath), { recursive: true });
|
|
14847
|
+
await fs26.writeFile(cachePath, JSON.stringify(data, null, 2));
|
|
13881
14848
|
}
|
|
13882
14849
|
async function fetchFromNetwork() {
|
|
13883
14850
|
try {
|
|
@@ -13904,7 +14871,7 @@ function loadFallbackDataset() {
|
|
|
13904
14871
|
async function checkAndWarnStaleness(projectRoot) {
|
|
13905
14872
|
const markerPath = getStalenessMarkerPath(projectRoot);
|
|
13906
14873
|
try {
|
|
13907
|
-
const raw = await
|
|
14874
|
+
const raw = await fs26.readFile(markerPath, "utf-8");
|
|
13908
14875
|
const marker = JSON.parse(raw);
|
|
13909
14876
|
const firstUse = new Date(marker.firstFallbackUse).getTime();
|
|
13910
14877
|
const now = Date.now();
|
|
@@ -13916,8 +14883,8 @@ async function checkAndWarnStaleness(projectRoot) {
|
|
|
13916
14883
|
}
|
|
13917
14884
|
} catch {
|
|
13918
14885
|
try {
|
|
13919
|
-
await
|
|
13920
|
-
await
|
|
14886
|
+
await fs26.mkdir(path25.dirname(markerPath), { recursive: true });
|
|
14887
|
+
await fs26.writeFile(
|
|
13921
14888
|
markerPath,
|
|
13922
14889
|
JSON.stringify({ firstFallbackUse: (/* @__PURE__ */ new Date()).toISOString() })
|
|
13923
14890
|
);
|
|
@@ -13927,7 +14894,7 @@ async function checkAndWarnStaleness(projectRoot) {
|
|
|
13927
14894
|
}
|
|
13928
14895
|
async function clearStalenessMarker(projectRoot) {
|
|
13929
14896
|
try {
|
|
13930
|
-
await
|
|
14897
|
+
await fs26.unlink(getStalenessMarkerPath(projectRoot));
|
|
13931
14898
|
} catch {
|
|
13932
14899
|
}
|
|
13933
14900
|
}
|
|
@@ -14097,8 +15064,8 @@ function aggregateByDay(records) {
|
|
|
14097
15064
|
}
|
|
14098
15065
|
|
|
14099
15066
|
// src/usage/jsonl-reader.ts
|
|
14100
|
-
var
|
|
14101
|
-
var
|
|
15067
|
+
var fs27 = __toESM(require("fs"));
|
|
15068
|
+
var path26 = __toESM(require("path"));
|
|
14102
15069
|
function parseLine(line, lineNumber) {
|
|
14103
15070
|
let entry;
|
|
14104
15071
|
try {
|
|
@@ -14137,10 +15104,10 @@ function parseLine(line, lineNumber) {
|
|
|
14137
15104
|
return record;
|
|
14138
15105
|
}
|
|
14139
15106
|
function readCostRecords(projectRoot) {
|
|
14140
|
-
const costsFile =
|
|
15107
|
+
const costsFile = path26.join(projectRoot, ".harness", "metrics", "costs.jsonl");
|
|
14141
15108
|
let raw;
|
|
14142
15109
|
try {
|
|
14143
|
-
raw =
|
|
15110
|
+
raw = fs27.readFileSync(costsFile, "utf-8");
|
|
14144
15111
|
} catch {
|
|
14145
15112
|
return [];
|
|
14146
15113
|
}
|
|
@@ -14158,8 +15125,8 @@ function readCostRecords(projectRoot) {
|
|
|
14158
15125
|
}
|
|
14159
15126
|
|
|
14160
15127
|
// src/usage/cc-parser.ts
|
|
14161
|
-
var
|
|
14162
|
-
var
|
|
15128
|
+
var fs28 = __toESM(require("fs"));
|
|
15129
|
+
var path27 = __toESM(require("path"));
|
|
14163
15130
|
var os2 = __toESM(require("os"));
|
|
14164
15131
|
function extractUsage(entry) {
|
|
14165
15132
|
if (entry.type !== "assistant") return null;
|
|
@@ -14192,7 +15159,7 @@ function parseCCLine(line, filePath, lineNumber) {
|
|
|
14192
15159
|
entry = JSON.parse(line);
|
|
14193
15160
|
} catch {
|
|
14194
15161
|
console.warn(
|
|
14195
|
-
`[harness usage] Skipping malformed CC JSONL line ${lineNumber} in ${
|
|
15162
|
+
`[harness usage] Skipping malformed CC JSONL line ${lineNumber} in ${path27.basename(filePath)}`
|
|
14196
15163
|
);
|
|
14197
15164
|
return null;
|
|
14198
15165
|
}
|
|
@@ -14206,7 +15173,7 @@ function parseCCLine(line, filePath, lineNumber) {
|
|
|
14206
15173
|
function readCCFile(filePath) {
|
|
14207
15174
|
let raw;
|
|
14208
15175
|
try {
|
|
14209
|
-
raw =
|
|
15176
|
+
raw = fs28.readFileSync(filePath, "utf-8");
|
|
14210
15177
|
} catch {
|
|
14211
15178
|
return [];
|
|
14212
15179
|
}
|
|
@@ -14228,10 +15195,10 @@ function readCCFile(filePath) {
|
|
|
14228
15195
|
}
|
|
14229
15196
|
function parseCCRecords() {
|
|
14230
15197
|
const homeDir = process.env.HOME ?? os2.homedir();
|
|
14231
|
-
const projectsDir =
|
|
15198
|
+
const projectsDir = path27.join(homeDir, ".claude", "projects");
|
|
14232
15199
|
let projectDirs;
|
|
14233
15200
|
try {
|
|
14234
|
-
projectDirs =
|
|
15201
|
+
projectDirs = fs28.readdirSync(projectsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => path27.join(projectsDir, d.name));
|
|
14235
15202
|
} catch {
|
|
14236
15203
|
return [];
|
|
14237
15204
|
}
|
|
@@ -14239,7 +15206,7 @@ function parseCCRecords() {
|
|
|
14239
15206
|
for (const dir of projectDirs) {
|
|
14240
15207
|
let files;
|
|
14241
15208
|
try {
|
|
14242
|
-
files =
|
|
15209
|
+
files = fs28.readdirSync(dir).filter((f) => f.endsWith(".jsonl")).map((f) => path27.join(dir, f));
|
|
14243
15210
|
} catch {
|
|
14244
15211
|
continue;
|
|
14245
15212
|
}
|
|
@@ -14256,6 +15223,7 @@ var VERSION = "0.15.0";
|
|
|
14256
15223
|
0 && (module.exports = {
|
|
14257
15224
|
AGENT_DESCRIPTORS,
|
|
14258
15225
|
ARCHITECTURE_DESCRIPTOR,
|
|
15226
|
+
AdjustedForecastSchema,
|
|
14259
15227
|
AgentActionEmitter,
|
|
14260
15228
|
ArchBaselineManager,
|
|
14261
15229
|
ArchBaselineSchema,
|
|
@@ -14271,23 +15239,29 @@ var VERSION = "0.15.0";
|
|
|
14271
15239
|
CACHE_TTL_MS,
|
|
14272
15240
|
COMPLIANCE_DESCRIPTOR,
|
|
14273
15241
|
CategoryBaselineSchema,
|
|
15242
|
+
CategoryForecastSchema,
|
|
14274
15243
|
CategoryRegressionSchema,
|
|
15244
|
+
CategorySnapshotSchema,
|
|
14275
15245
|
ChecklistBuilder,
|
|
14276
15246
|
CircularDepsCollector,
|
|
14277
15247
|
ComplexityCollector,
|
|
15248
|
+
ConfidenceTierSchema,
|
|
14278
15249
|
ConfirmationSchema,
|
|
14279
15250
|
ConsoleSink,
|
|
14280
15251
|
ConstraintRuleSchema,
|
|
14281
15252
|
ContentPipeline,
|
|
15253
|
+
ContributingFeatureSchema,
|
|
14282
15254
|
ContributionsSchema,
|
|
14283
15255
|
CouplingCollector,
|
|
14284
15256
|
CriticalPathResolver,
|
|
14285
15257
|
DEFAULT_PROVIDER_TIERS,
|
|
14286
15258
|
DEFAULT_SECURITY_CONFIG,
|
|
15259
|
+
DEFAULT_STABILITY_THRESHOLDS,
|
|
14287
15260
|
DEFAULT_STATE,
|
|
14288
15261
|
DEFAULT_STREAM_INDEX,
|
|
14289
15262
|
DESTRUCTIVE_BASH,
|
|
14290
15263
|
DepDepthCollector,
|
|
15264
|
+
DirectionSchema,
|
|
14291
15265
|
EXTENSION_MAP,
|
|
14292
15266
|
EmitInteractionInputSchema,
|
|
14293
15267
|
EntropyAnalyzer,
|
|
@@ -14313,6 +15287,11 @@ var VERSION = "0.15.0";
|
|
|
14313
15287
|
NoOpSink,
|
|
14314
15288
|
NoOpTelemetryAdapter,
|
|
14315
15289
|
PatternConfigSchema,
|
|
15290
|
+
PredictionEngine,
|
|
15291
|
+
PredictionOptionsSchema,
|
|
15292
|
+
PredictionRegressionResultSchema,
|
|
15293
|
+
PredictionResultSchema,
|
|
15294
|
+
PredictionWarningSchema,
|
|
14316
15295
|
ProjectScanner,
|
|
14317
15296
|
QuestionSchema,
|
|
14318
15297
|
REQUIRED_SECTIONS,
|
|
@@ -14328,10 +15307,19 @@ var VERSION = "0.15.0";
|
|
|
14328
15307
|
SharableLayerSchema,
|
|
14329
15308
|
SharableSecurityRulesSchema,
|
|
14330
15309
|
SkillEventSchema,
|
|
15310
|
+
SpecImpactEstimateSchema,
|
|
15311
|
+
SpecImpactEstimator,
|
|
15312
|
+
SpecImpactSignalsSchema,
|
|
15313
|
+
StabilityForecastSchema,
|
|
14331
15314
|
StreamIndexSchema,
|
|
14332
15315
|
StreamInfoSchema,
|
|
14333
15316
|
ThresholdConfigSchema,
|
|
15317
|
+
TimelineFileSchema,
|
|
15318
|
+
TimelineManager,
|
|
15319
|
+
TimelineSnapshotSchema,
|
|
14334
15320
|
TransitionSchema,
|
|
15321
|
+
TrendLineSchema,
|
|
15322
|
+
TrendResultSchema,
|
|
14335
15323
|
TypeScriptParser,
|
|
14336
15324
|
VERSION,
|
|
14337
15325
|
ViolationSchema,
|
|
@@ -14346,6 +15334,7 @@ var VERSION = "0.15.0";
|
|
|
14346
15334
|
appendSessionEntry,
|
|
14347
15335
|
applyFixes,
|
|
14348
15336
|
applyHotspotDowngrade,
|
|
15337
|
+
applyRecencyWeights,
|
|
14349
15338
|
applySyncChanges,
|
|
14350
15339
|
archMatchers,
|
|
14351
15340
|
archModule,
|
|
@@ -14363,6 +15352,7 @@ var VERSION = "0.15.0";
|
|
|
14363
15352
|
checkEligibility,
|
|
14364
15353
|
checkEvidenceCoverage,
|
|
14365
15354
|
checkTaint,
|
|
15355
|
+
classifyConfidence,
|
|
14366
15356
|
classifyFinding,
|
|
14367
15357
|
clearEventHashCache,
|
|
14368
15358
|
clearFailuresCache,
|
|
@@ -14474,6 +15464,7 @@ var VERSION = "0.15.0";
|
|
|
14474
15464
|
parseSize,
|
|
14475
15465
|
pathTraversalRules,
|
|
14476
15466
|
previewFix,
|
|
15467
|
+
projectValue,
|
|
14477
15468
|
promoteSessionLearnings,
|
|
14478
15469
|
pruneLearnings,
|
|
14479
15470
|
reactRules,
|
|
@@ -14541,6 +15532,8 @@ var VERSION = "0.15.0";
|
|
|
14541
15532
|
validateKnowledgeMap,
|
|
14542
15533
|
validatePatternConfig,
|
|
14543
15534
|
violationId,
|
|
15535
|
+
weeksUntilThreshold,
|
|
15536
|
+
weightedLinearRegression,
|
|
14544
15537
|
writeConfig,
|
|
14545
15538
|
writeLockfile,
|
|
14546
15539
|
writeSessionSummary,
|