@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/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: () => 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(path26) {
374
+ async function fileExists(path28) {
349
375
  try {
350
- await accessAsync(path26, import_fs.constants.F_OK);
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(path26) {
382
+ async function readFileContent(path28) {
357
383
  try {
358
- const content = await readFileAsync(path26, "utf-8");
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 path26 = firstError.path.join(".");
410
- const pathDisplay = path26 ? ` at "${path26}"` : "";
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 "${path26}" is required and must be of type "${expected}"`);
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(path26) {
634
- return path26.startsWith("http://") || path26.startsWith("https://") || path26.startsWith("#") || path26.startsWith("mailto:");
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(path26 = "./AGENTS.md") {
640
- const contentResult = await readFileContent(path26);
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: path26 },
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)(path26);
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(path26, existingFiles) {
795
- const targetName = (0, import_path3.basename)(path26).toLowerCase();
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 "${path26}" or remove the link`;
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 path26 = issue.path.join(".");
1410
- return path26 ? `${path26}: ${issue.message}` : issue.message;
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(path26) {
2043
- const contentResult = await readFileContent(path26);
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: ${path26}`, { path: path26 }, [
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: path26.endsWith(".tsx"),
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 ${path26}: ${error.message}`, { path: path26 }, [
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(path26) {
2253
- const contentResult = await readFileContent(path26);
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: ${path26}`,
2259
- { file: path26 },
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 = path26.endsWith(".md") ? "markdown" : "text";
2291
+ const type = path28.endsWith(".md") ? "markdown" : "text";
2266
2292
  return (0, import_types.Ok)({
2267
- path: path26,
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 projectValue = projectThresholds[category];
6608
- if (projectValue !== void 0 && typeof projectValue === "object" && !Array.isArray(projectValue) && typeof moduleValue === "object" && !Array.isArray(moduleValue)) {
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
- ...projectValue,
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/state/types.ts
6832
+ // src/architecture/timeline-types.ts
6807
6833
  var import_zod4 = require("zod");
6808
- var FailureEntrySchema = import_zod4.z.object({
6809
- date: import_zod4.z.string(),
6810
- skill: import_zod4.z.string(),
6811
- type: import_zod4.z.string(),
6812
- description: import_zod4.z.string()
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 = import_zod4.z.object({
6815
- timestamp: import_zod4.z.string(),
6816
- fromSkill: import_zod4.z.string(),
6817
- phase: import_zod4.z.string(),
6818
- summary: import_zod4.z.string(),
6819
- completed: import_zod4.z.array(import_zod4.z.string()).default([]),
6820
- pending: import_zod4.z.array(import_zod4.z.string()).default([]),
6821
- concerns: import_zod4.z.array(import_zod4.z.string()).default([]),
6822
- decisions: import_zod4.z.array(
6823
- import_zod4.z.object({
6824
- what: import_zod4.z.string(),
6825
- why: import_zod4.z.string()
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: import_zod4.z.array(import_zod4.z.string()).default([]),
6829
- contextKeywords: import_zod4.z.array(import_zod4.z.string()).default([])
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 = import_zod4.z.object({
6832
- name: import_zod4.z.string(),
6833
- passed: import_zod4.z.boolean(),
6834
- command: import_zod4.z.string(),
6835
- output: import_zod4.z.string().optional(),
6836
- duration: import_zod4.z.number().optional()
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 = import_zod4.z.object({
6839
- passed: import_zod4.z.boolean(),
6840
- checks: import_zod4.z.array(GateCheckSchema)
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 = import_zod4.z.object({
6843
- checks: import_zod4.z.array(
6844
- import_zod4.z.object({
6845
- name: import_zod4.z.string(),
6846
- command: import_zod4.z.string()
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: import_zod4.z.boolean().optional()
7971
+ trace: import_zod6.z.boolean().optional()
6850
7972
  });
6851
- var HarnessStateSchema = import_zod4.z.object({
6852
- schemaVersion: import_zod4.z.literal(1),
6853
- position: import_zod4.z.object({
6854
- phase: import_zod4.z.string().optional(),
6855
- task: import_zod4.z.string().optional()
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: import_zod4.z.array(
6858
- import_zod4.z.object({
6859
- date: import_zod4.z.string(),
6860
- decision: import_zod4.z.string(),
6861
- context: import_zod4.z.string()
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: import_zod4.z.array(
6865
- import_zod4.z.object({
6866
- id: import_zod4.z.string(),
6867
- description: import_zod4.z.string(),
6868
- status: import_zod4.z.enum(["open", "resolved"])
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: import_zod4.z.record(import_zod4.z.enum(["pending", "in_progress", "complete"])).default({}),
6872
- lastSession: import_zod4.z.object({
6873
- date: import_zod4.z.string(),
6874
- summary: import_zod4.z.string(),
6875
- lastSkill: import_zod4.z.string().optional(),
6876
- pendingTasks: import_zod4.z.array(import_zod4.z.string()).optional()
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 fs8 = __toESM(require("fs"));
6889
- var path5 = __toESM(require("path"));
8010
+ var fs10 = __toESM(require("fs"));
8011
+ var path7 = __toESM(require("path"));
6890
8012
 
6891
8013
  // src/state/state-shared.ts
6892
- var fs7 = __toESM(require("fs"));
6893
- var path4 = __toESM(require("path"));
8014
+ var fs9 = __toESM(require("fs"));
8015
+ var path6 = __toESM(require("path"));
6894
8016
 
6895
8017
  // src/state/stream-resolver.ts
6896
- var fs5 = __toESM(require("fs"));
6897
- var path2 = __toESM(require("path"));
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 import_zod5 = require("zod");
6902
- var StreamInfoSchema = import_zod5.z.object({
6903
- name: import_zod5.z.string(),
6904
- branch: import_zod5.z.string().optional(),
6905
- createdAt: import_zod5.z.string(),
6906
- lastActiveAt: import_zod5.z.string()
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 = import_zod5.z.object({
6909
- schemaVersion: import_zod5.z.literal(1),
6910
- activeStream: import_zod5.z.string().nullable(),
6911
- streams: import_zod5.z.record(StreamInfoSchema)
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 path2.join(projectPath, HARNESS_DIR, STREAMS_DIR);
8061
+ return path4.join(projectPath, HARNESS_DIR, STREAMS_DIR);
6940
8062
  }
6941
8063
  function indexPath(projectPath) {
6942
- return path2.join(streamsDir(projectPath), INDEX_FILE);
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 (!fs5.existsSync(idxPath)) {
8078
+ if (!fs7.existsSync(idxPath)) {
6957
8079
  return (0, import_types.Ok)({ ...DEFAULT_STREAM_INDEX, streams: {} });
6958
8080
  }
6959
8081
  try {
6960
- const raw = fs5.readFileSync(idxPath, "utf-8");
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
- fs5.mkdirSync(dir, { recursive: true });
6979
- fs5.writeFileSync(indexPath(projectPath), JSON.stringify(index, null, 2));
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)(path2.join(streamsDir(projectPath), options.stream));
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)(path2.join(streamsDir(projectPath), name));
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)(path2.join(streamsDir(projectPath), index.activeStream));
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 = path2.join(streamsDir(projectPath), name);
8181
+ const streamPath = path4.join(streamsDir(projectPath), name);
7060
8182
  try {
7061
- fs5.mkdirSync(streamPath, { recursive: true });
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 = path2.join(streamsDir(projectPath), name);
7103
- const archiveDir = path2.join(projectPath, HARNESS_DIR, "archive", "streams");
8224
+ const streamPath = path4.join(streamsDir(projectPath), name);
8225
+ const archiveDir = path4.join(projectPath, HARNESS_DIR, "archive", "streams");
7104
8226
  try {
7105
- fs5.mkdirSync(archiveDir, { recursive: true });
8227
+ fs7.mkdirSync(archiveDir, { recursive: true });
7106
8228
  const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
7107
- fs5.renameSync(streamPath, path2.join(archiveDir, `${name}-${date}`));
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 = path2.join(projectPath, HARNESS_DIR);
7130
- if (fs5.existsSync(indexPath(projectPath))) {
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) => fs5.existsSync(path2.join(harnessDir, 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 = path2.join(streamsDir(projectPath), "default");
8259
+ const defaultDir = path4.join(streamsDir(projectPath), "default");
7138
8260
  try {
7139
- fs5.mkdirSync(defaultDir, { recursive: true });
8261
+ fs7.mkdirSync(defaultDir, { recursive: true });
7140
8262
  for (const file of filesToMove) {
7141
- fs5.renameSync(path2.join(harnessDir, file), path2.join(defaultDir, file));
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 fs6 = __toESM(require("fs"));
7165
- var path3 = __toESM(require("path"));
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 = path3.join(projectPath, HARNESS_DIR, SESSIONS_DIR, sessionSlug);
8297
+ const sessionDir = path5.join(projectPath, HARNESS_DIR, SESSIONS_DIR, sessionSlug);
7176
8298
  if (options?.create) {
7177
- fs6.mkdirSync(sessionDir, { recursive: true });
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 = path3.join(projectPath, HARNESS_DIR, SESSIONS_DIR);
7183
- fs6.mkdirSync(sessionsDir, { recursive: true });
7184
- const indexPath2 = path3.join(sessionsDir, SESSION_INDEX_FILE);
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 (!fs6.existsSync(indexPath2)) {
7188
- fs6.writeFileSync(indexPath2, `## Active Sessions
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 = fs6.readFileSync(indexPath2, "utf-8");
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
- fs6.writeFileSync(indexPath2, lines.join("\n"));
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 = path4.join(projectPath, HARNESS_DIR, "streams", INDEX_FILE);
7221
- const hasStreams = fs7.existsSync(streamsIndexPath);
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)(path4.join(projectPath, HARNESS_DIR));
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 = path5.join(stateDir, STATE_FILE);
7241
- if (!fs8.existsSync(statePath)) {
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 = fs8.readFileSync(statePath, "utf-8");
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 = path5.join(stateDir, STATE_FILE);
7263
- fs8.mkdirSync(stateDir, { recursive: true });
7264
- fs8.writeFileSync(statePath, JSON.stringify(state, null, 2));
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 fs9 = __toESM(require("fs"));
7275
- var path6 = __toESM(require("path"));
8396
+ var fs11 = __toESM(require("fs"));
8397
+ var path8 = __toESM(require("path"));
7276
8398
  var crypto = __toESM(require("crypto"));
7277
- function parseFrontmatter(line) {
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 = path6.join(stateDir, CONTENT_HASHES_FILE);
7304
- if (!fs9.existsSync(hashesPath)) return {};
8425
+ const hashesPath = path8.join(stateDir, CONTENT_HASHES_FILE);
8426
+ if (!fs11.existsSync(hashesPath)) return {};
7305
8427
  try {
7306
- const raw = fs9.readFileSync(hashesPath, "utf-8");
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 = path6.join(stateDir, CONTENT_HASHES_FILE);
7316
- fs9.writeFileSync(hashesPath, JSON.stringify(index, null, 2) + "\n");
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 = path6.join(stateDir, LEARNINGS_FILE);
7320
- if (!fs9.existsSync(learningsPath)) return {};
7321
- const content = fs9.readFileSync(learningsPath, "utf-8");
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 = path6.join(stateDir, LEARNINGS_FILE);
7365
- fs9.mkdirSync(stateDir, { recursive: true });
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 = path6.join(stateDir, CONTENT_HASHES_FILE);
8490
+ const hashesPath = path8.join(stateDir, CONTENT_HASHES_FILE);
7369
8491
  let contentHashes;
7370
- if (fs9.existsSync(hashesPath)) {
8492
+ if (fs11.existsSync(hashesPath)) {
7371
8493
  contentHashes = loadContentHashes(stateDir);
7372
- if (Object.keys(contentHashes).length === 0 && fs9.existsSync(learningsPath)) {
8494
+ if (Object.keys(contentHashes).length === 0 && fs11.existsSync(learningsPath)) {
7373
8495
  contentHashes = rebuildContentHashes(stateDir);
7374
8496
  }
7375
- } else if (fs9.existsSync(learningsPath)) {
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 (!fs9.existsSync(learningsPath)) {
7404
- fs9.writeFileSync(learningsPath, `# Learnings
8525
+ if (!fs11.existsSync(learningsPath)) {
8526
+ fs11.writeFileSync(learningsPath, `# Learnings
7405
8527
  ${entry}`);
7406
8528
  existingLineCount = 1;
7407
8529
  } else {
7408
- const existingContent = fs9.readFileSync(learningsPath, "utf-8");
8530
+ const existingContent = fs11.readFileSync(learningsPath, "utf-8");
7409
8531
  existingLineCount = existingContent.split("\n").length;
7410
- fs9.appendFileSync(learningsPath, entry);
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 = path6.join(stateDir, LEARNINGS_FILE);
7525
- if (!fs9.existsSync(learningsPath)) {
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 = fs9.readFileSync(learningsPath, "utf-8");
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 = parseFrontmatter(line);
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 = path6.join(stateDir, LEARNINGS_FILE);
7587
- if (!fs9.existsSync(learningsPath)) {
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 = fs9.statSync(learningsPath);
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 = fs9.readFileSync(learningsPath, "utf-8");
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 = path6.join(stateDir, "learnings-archive");
7640
- fs9.mkdirSync(archiveDir, { recursive: true });
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 = path6.join(archiveDir, `${yearMonth}.md`);
8765
+ const archivePath = path8.join(archiveDir, `${yearMonth}.md`);
7644
8766
  const archiveContent = entries.join("\n\n") + "\n";
7645
- if (fs9.existsSync(archivePath)) {
7646
- fs9.appendFileSync(archivePath, "\n" + archiveContent);
8767
+ if (fs11.existsSync(archivePath)) {
8768
+ fs11.appendFileSync(archivePath, "\n" + archiveContent);
7647
8769
  } else {
7648
- fs9.writeFileSync(archivePath, `# Learnings Archive
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 = path6.join(stateDir, LEARNINGS_FILE);
7667
- if (!fs9.existsSync(learningsPath)) {
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
- fs9.writeFileSync(learningsPath, newContent);
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 = path6.join(stateDir, LEARNINGS_FILE);
7744
- const existingGlobal = fs9.existsSync(globalPath) ? fs9.readFileSync(globalPath, "utf-8") : "";
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
- fs9.writeFileSync(globalPath, `# Learnings
8873
+ fs11.writeFileSync(globalPath, `# Learnings
7752
8874
 
7753
8875
  ${promotedContent}`);
7754
8876
  } else {
7755
- fs9.appendFileSync(globalPath, "\n\n" + promotedContent);
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 fs10 = __toESM(require("fs"));
7778
- var path7 = __toESM(require("path"));
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 = path7.join(stateDir, FAILURES_FILE);
7790
- fs10.mkdirSync(stateDir, { recursive: true });
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 (!fs10.existsSync(failuresPath)) {
7796
- fs10.writeFileSync(failuresPath, `# Failures
8917
+ if (!fs12.existsSync(failuresPath)) {
8918
+ fs12.writeFileSync(failuresPath, `# Failures
7797
8919
  ${entry}`);
7798
8920
  } else {
7799
- fs10.appendFileSync(failuresPath, entry);
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 = path7.join(stateDir, FAILURES_FILE);
7817
- if (!fs10.existsSync(failuresPath)) {
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 = fs10.statSync(failuresPath);
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 = fs10.readFileSync(failuresPath, "utf-8");
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 = path7.join(stateDir, FAILURES_FILE);
7856
- if (!fs10.existsSync(failuresPath)) {
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 = path7.join(stateDir, "archive");
7860
- fs10.mkdirSync(archiveDir, { recursive: true });
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 (fs10.existsSync(path7.join(archiveDir, archiveName))) {
8986
+ while (fs12.existsSync(path9.join(archiveDir, archiveName))) {
7865
8987
  archiveName = `failures-${date}-${counter}.md`;
7866
8988
  counter++;
7867
8989
  }
7868
- fs10.renameSync(failuresPath, path7.join(archiveDir, archiveName));
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 fs11 = __toESM(require("fs"));
7882
- var path8 = __toESM(require("path"));
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 = path8.join(stateDir, HANDOFF_FILE);
7889
- fs11.mkdirSync(stateDir, { recursive: true });
7890
- fs11.writeFileSync(handoffPath, JSON.stringify(handoff, null, 2));
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 = path8.join(stateDir, HANDOFF_FILE);
7904
- if (!fs11.existsSync(handoffPath)) {
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 = fs11.readFileSync(handoffPath, "utf-8");
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 fs12 = __toESM(require("fs"));
7923
- var path9 = __toESM(require("path"));
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 (!fs12.existsSync(gateConfigPath)) return [];
7928
- const raw = JSON.parse(fs12.readFileSync(gateConfigPath, "utf-8"));
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 = path9.join(projectPath, "package.json");
7936
- if (fs12.existsSync(packageJsonPath)) {
7937
- const pkg = JSON.parse(fs12.readFileSync(packageJsonPath, "utf-8"));
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 (fs12.existsSync(path9.join(projectPath, "go.mod"))) {
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 (fs12.existsSync(path9.join(projectPath, "pyproject.toml")) || fs12.existsSync(path9.join(projectPath, "setup.py"))) {
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 = path9.join(projectPath, HARNESS_DIR);
7989
- const gateConfigPath = path9.join(harnessDir, GATE_CONFIG_FILE);
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 fs13 = __toESM(require("fs"));
8011
- var path10 = __toESM(require("path"));
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 = path10.join(sessionDir, SUMMARY_FILE);
9171
+ const summaryPath = path12.join(sessionDir, SUMMARY_FILE);
8050
9172
  const content = formatSummary(data);
8051
- fs13.writeFileSync(summaryPath, content);
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 = path10.join(sessionDir, SUMMARY_FILE);
8069
- if (!fs13.existsSync(summaryPath)) {
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 = fs13.readFileSync(summaryPath, "utf-8");
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 = path10.join(projectPath, HARNESS_DIR, SESSIONS_DIR, SESSION_INDEX_FILE);
8085
- if (!fs13.existsSync(indexPath2)) {
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 = fs13.readFileSync(indexPath2, "utf-8");
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 fs14 = __toESM(require("fs"));
8101
- var path11 = __toESM(require("path"));
8102
- var import_types14 = require("@harness-engineering/types");
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 import_types14.SESSION_SECTION_NAMES) {
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 = path11.join(sessionDir, SESSION_STATE_FILE);
8115
- if (!fs14.existsSync(filePath)) {
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 = fs14.readFileSync(filePath, "utf-8");
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 import_types14.SESSION_SECTION_NAMES) {
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 = path11.join(sessionDir, SESSION_STATE_FILE);
9262
+ const filePath = path13.join(sessionDir, SESSION_STATE_FILE);
8141
9263
  try {
8142
- fs14.writeFileSync(filePath, JSON.stringify(sections, null, 2));
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 fs15 = __toESM(require("fs"));
8197
- var path12 = __toESM(require("path"));
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 (!fs15.existsSync(sessionDir)) {
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 = path12.join(projectPath, HARNESS_DIR, ARCHIVE_DIR, "sessions");
9327
+ const archiveBase = path14.join(projectPath, HARNESS_DIR, ARCHIVE_DIR, "sessions");
8206
9328
  try {
8207
- fs15.mkdirSync(archiveBase, { recursive: true });
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 (fs15.existsSync(path12.join(archiveBase, archiveName))) {
9333
+ while (fs17.existsSync(path14.join(archiveBase, archiveName))) {
8212
9334
  archiveName = `${sessionSlug}-${date}-${counter}`;
8213
9335
  counter++;
8214
9336
  }
8215
- const dest = path12.join(archiveBase, archiveName);
9337
+ const dest = path14.join(archiveBase, archiveName);
8216
9338
  try {
8217
- fs15.renameSync(sessionDir, dest);
9339
+ fs17.renameSync(sessionDir, dest);
8218
9340
  } catch (renameErr) {
8219
9341
  if (renameErr instanceof Error && "code" in renameErr && renameErr.code === "EXDEV") {
8220
- fs15.cpSync(sessionDir, dest, { recursive: true });
8221
- fs15.rmSync(sessionDir, { recursive: true });
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 fs16 = __toESM(require("fs"));
8238
- var path13 = __toESM(require("path"));
8239
- var import_zod6 = require("zod");
8240
- var SkillEventSchema = import_zod6.z.object({
8241
- timestamp: import_zod6.z.string(),
8242
- skill: import_zod6.z.string(),
8243
- session: import_zod6.z.string().optional(),
8244
- type: import_zod6.z.enum(["phase_transition", "decision", "gate_result", "handoff", "error", "checkpoint"]),
8245
- summary: import_zod6.z.string(),
8246
- data: import_zod6.z.record(import_zod6.z.unknown()).optional(),
8247
- refs: import_zod6.z.array(import_zod6.z.string()).optional(),
8248
- contentHash: import_zod6.z.string().optional()
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 (fs16.existsSync(eventsPath)) {
8260
- const content = fs16.readFileSync(eventsPath, "utf-8");
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 = path13.join(stateDir, EVENTS_FILE);
8284
- fs16.mkdirSync(stateDir, { recursive: true });
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
- fs16.appendFileSync(eventsPath, JSON.stringify(fullEvent) + "\n");
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 = path13.join(stateDir, EVENTS_FILE);
8313
- if (!fs16.existsSync(eventsPath)) {
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 = fs16.readFileSync(eventsPath, "utf-8");
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 fs18 = __toESM(require("fs/promises"));
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 import_zod7 = require("zod");
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 = import_zod7.z.enum(["off", "error", "warning", "info"]);
8574
- var SecurityConfigSchema = import_zod7.z.object({
8575
- enabled: import_zod7.z.boolean().default(true),
8576
- strict: import_zod7.z.boolean().default(false),
8577
- rules: import_zod7.z.record(import_zod7.z.string(), RuleOverrideSchema).optional().default({}),
8578
- exclude: import_zod7.z.array(import_zod7.z.string()).optional().default(["**/node_modules/**", "**/dist/**", "**/*.test.ts", "**/fixtures/**"]),
8579
- external: import_zod7.z.object({
8580
- semgrep: import_zod7.z.object({
8581
- enabled: import_zod7.z.union([import_zod7.z.literal("auto"), import_zod7.z.boolean()]).default("auto"),
8582
- rulesets: import_zod7.z.array(import_zod7.z.string()).optional()
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: import_zod7.z.object({
8585
- enabled: import_zod7.z.union([import_zod7.z.literal("auto"), import_zod7.z.boolean()]).default("auto")
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 fs17 = __toESM(require("fs"));
8619
- var path14 = __toESM(require("path"));
9740
+ var fs19 = __toESM(require("fs"));
9741
+ var path16 = __toESM(require("path"));
8620
9742
  function detectStack(projectRoot) {
8621
9743
  const stacks = [];
8622
- const pkgJsonPath = path14.join(projectRoot, "package.json");
8623
- if (fs17.existsSync(pkgJsonPath)) {
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(fs17.readFileSync(pkgJsonPath, "utf-8"));
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 = path14.join(projectRoot, "go.mod");
8642
- if (fs17.existsSync(goModPath)) {
9763
+ const goModPath = path16.join(projectRoot, "go.mod");
9764
+ if (fs19.existsSync(goModPath)) {
8643
9765
  stacks.push("go");
8644
9766
  }
8645
- const requirementsPath = path14.join(projectRoot, "requirements.txt");
8646
- const pyprojectPath = path14.join(projectRoot, "pyproject.toml");
8647
- if (fs17.existsSync(requirementsPath) || fs17.existsSync(pyprojectPath)) {
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 fs18.readFile(filePath, "utf-8");
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 import_node_fs4 = require("fs");
9806
- var import_node_path7 = require("path");
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, import_node_path7.join)(projectRoot, ".harness", `session-taint-${id}.json`);
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, import_node_fs4.readFileSync)(filePath, "utf8");
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, import_node_fs4.unlinkSync)(filePath);
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, import_node_fs4.unlinkSync)(filePath);
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, import_node_fs4.unlinkSync)(filePath);
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, import_node_path7.dirname)(filePath);
9862
- (0, import_node_fs4.mkdirSync)(dir, { recursive: true });
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, import_node_fs4.writeFileSync)(filePath, JSON.stringify(state, null, 2) + "\n");
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, import_node_fs4.unlinkSync)(filePath);
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, import_node_path7.join)(projectRoot, ".harness");
11015
+ const harnessDir = (0, import_node_path8.join)(projectRoot, ".harness");
9894
11016
  let count = 0;
9895
11017
  try {
9896
- const files = (0, import_node_fs4.readdirSync)(harnessDir);
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, import_node_fs4.unlinkSync)((0, import_node_path7.join)(harnessDir, file));
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, import_node_path7.join)(projectRoot, ".harness");
11033
+ const harnessDir = (0, import_node_path8.join)(projectRoot, ".harness");
9912
11034
  const sessions = [];
9913
11035
  try {
9914
- const files = (0, import_node_fs4.readdirSync)(harnessDir);
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 path15 = __toESM(require("path"));
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 = path15.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
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 = path15.join(projectRoot, config.docsDir ?? "docs");
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 path16 = __toESM(require("path"));
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 = path16.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
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: path16.join(projectRoot, "AGENTS.md"),
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 = path16.join(projectRoot, config.docsDir ?? "docs");
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: path16.join(projectRoot, "docs"),
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 path17 = __toESM(require("path"));
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 = path17.resolve(projectRoot) + path17.sep;
10626
- const resolvedPath = path17.resolve(absPath);
10627
- return resolvedPath.startsWith(resolvedRoot) || resolvedPath === path17.resolve(projectRoot);
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 = path17.isAbsolute(filePath) ? filePath : path17.join(projectRoot, filePath);
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 = path17.isAbsolute(filePath) ? relativePosix(projectRoot, filePath) : filePath;
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 = path17.dirname(path17.join(projectRoot, fromFile));
10652
- const basePath = path17.resolve(fromDir, importSource);
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
- path17.join(relBase, "index.ts")
11819
+ path19.join(relBase, "index.ts")
10660
11820
  ];
10661
11821
  for (const candidate of candidates) {
10662
- const absCandidate = path17.join(projectRoot, candidate);
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 = path17.basename(sourceFile, path17.extname(sourceFile));
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 path18 = __toESM(require("path"));
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 (path18.isAbsolute(normalized)) {
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 = path18.dirname(current.file);
11526
- let resolved = path18.join(dir, importPath).replace(/\\/g, "/");
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 = path18.normalize(resolved).replace(/\\/g, "/");
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 = path18.isAbsolute(finding.file) ? finding.file : path18.join(projectRoot, finding.file).replace(/\\/g, "/");
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 fs19 = __toESM(require("fs"));
12428
- var path19 = __toESM(require("path"));
12429
- var import_types20 = require("@harness-engineering/types");
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 = path19.join(projectPath, ".harness", "state.json");
12459
- if (fs19.existsSync(rootStatePath)) {
13421
+ const rootStatePath = path21.join(projectPath, ".harness", "state.json");
13422
+ if (fs21.existsSync(rootStatePath)) {
12460
13423
  try {
12461
- const raw = fs19.readFileSync(rootStatePath, "utf-8");
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 = path19.join(projectPath, ".harness", "sessions");
12473
- if (fs19.existsSync(sessionsDir)) {
13435
+ const sessionsDir = path21.join(projectPath, ".harness", "sessions");
13436
+ if (fs21.existsSync(sessionsDir)) {
12474
13437
  try {
12475
- const sessionDirs = fs19.readdirSync(sessionsDir, { withFileTypes: true });
13438
+ const sessionDirs = fs21.readdirSync(sessionsDir, { withFileTypes: true });
12476
13439
  for (const entry of sessionDirs) {
12477
13440
  if (!entry.isDirectory()) continue;
12478
- const autopilotPath = path19.join(sessionsDir, entry.name, "autopilot-state.json");
12479
- if (!fs19.existsSync(autopilotPath)) continue;
13441
+ const autopilotPath = path21.join(sessionsDir, entry.name, "autopilot-state.json");
13442
+ if (!fs21.existsSync(autopilotPath)) continue;
12480
13443
  try {
12481
- const raw = fs19.readFileSync(autopilotPath, "utf-8");
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, import_types20.Ok)(changes);
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 import_types21 = require("@harness-engineering/types");
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 repoParts = (options.config.repo ?? "").split("/");
12622
- if (repoParts.length !== 2 || !repoParts[0] || !repoParts[1]) {
12623
- throw new Error(`Invalid repo format: "${options.config.repo}". Expected "owner/repo".`);
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, import_types21.Ok)(this.authenticatedUserCache);
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, import_types21.Err)(new Error(`GitHub API error ${response.status}: ${text}`));
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, import_types21.Ok)(this.authenticatedUserCache);
13647
+ return (0, import_types25.Ok)(this.authenticatedUserCache);
12681
13648
  } catch (error) {
12682
- return (0, import_types21.Err)(error instanceof Error ? error : new Error(String(error)));
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, import_types21.Err)(new Error(`GitHub API error ${response.status}: ${text}`));
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, import_types21.Ok)({ externalId, url: data.html_url });
13698
+ return (0, import_types25.Ok)({ externalId, url: data.html_url });
12732
13699
  } catch (error) {
12733
- return (0, import_types21.Err)(error instanceof Error ? error : new Error(String(error)));
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, import_types21.Err)(new Error(`Invalid externalId format: "${externalId}"`));
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, import_types21.Err)(new Error(`GitHub API error ${response.status}: ${text}`));
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, import_types21.Ok)({ externalId, url: data.html_url });
13745
+ return (0, import_types25.Ok)({ externalId, url: data.html_url });
12779
13746
  } catch (error) {
12780
- return (0, import_types21.Err)(error instanceof Error ? error : new Error(String(error)));
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, import_types21.Err)(new Error(`Invalid externalId format: "${externalId}"`));
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, import_types21.Err)(new Error(`GitHub API error ${response.status}: ${text}`));
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, import_types21.Ok)({
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, import_types21.Err)(error instanceof Error ? error : new Error(String(error)));
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, import_types21.Err)(new Error(`GitHub API error ${response.status}: ${text}`));
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, import_types21.Ok)(tickets);
13812
+ return (0, import_types25.Ok)(tickets);
12846
13813
  } catch (error) {
12847
- return (0, import_types21.Err)(error instanceof Error ? error : new Error(String(error)));
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, import_types21.Err)(new Error(`Invalid externalId format: "${externalId}"`));
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, import_types21.Err)(new Error(`GitHub API error ${response.status}: ${text}`));
13834
+ return (0, import_types25.Err)(new Error(`GitHub API error ${response.status}: ${text}`));
12868
13835
  }
12869
- return (0, import_types21.Ok)(void 0);
13836
+ return (0, import_types25.Ok)(void 0);
12870
13837
  } catch (error) {
12871
- return (0, import_types21.Err)(error instanceof Error ? error : new Error(String(error)));
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 fs20 = __toESM(require("fs"));
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 = fs20.readFileSync(roadmapPath, "utf-8");
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
- fs20.writeFileSync(roadmapPath, serializeRoadmap(roadmap), "utf-8");
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 import_zod8 = require("zod");
13107
- var InteractionTypeSchema = import_zod8.z.enum(["question", "confirmation", "transition"]);
13108
- var QuestionSchema = import_zod8.z.object({
13109
- text: import_zod8.z.string(),
13110
- options: import_zod8.z.array(import_zod8.z.string()).optional(),
13111
- default: import_zod8.z.string().optional()
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 = import_zod8.z.object({
13114
- text: import_zod8.z.string(),
13115
- context: import_zod8.z.string()
14080
+ var ConfirmationSchema = import_zod10.z.object({
14081
+ text: import_zod10.z.string(),
14082
+ context: import_zod10.z.string()
13116
14083
  });
13117
- var TransitionSchema = import_zod8.z.object({
13118
- completedPhase: import_zod8.z.string(),
13119
- suggestedNext: import_zod8.z.string(),
13120
- reason: import_zod8.z.string(),
13121
- artifacts: import_zod8.z.array(import_zod8.z.string()),
13122
- requiresConfirmation: import_zod8.z.boolean(),
13123
- summary: import_zod8.z.string()
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 = import_zod8.z.object({
13126
- path: import_zod8.z.string(),
14092
+ var EmitInteractionInputSchema = import_zod10.z.object({
14093
+ path: import_zod10.z.string(),
13127
14094
  type: InteractionTypeSchema,
13128
- stream: import_zod8.z.string().optional(),
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 fs21 = __toESM(require("fs/promises"));
13136
- var path20 = __toESM(require("path"));
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 = path20.basename(this.rootDir);
14110
+ let projectName = path22.basename(this.rootDir);
13144
14111
  try {
13145
- const pkgPath = path20.join(this.rootDir, "package.json");
13146
- const pkgRaw = await fs21.readFile(pkgPath, "utf-8");
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 fs22 = __toESM(require("fs/promises"));
13188
- var path21 = __toESM(require("path"));
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 fs22.mkdir(options.outputDir, { recursive: true });
13273
- await fs22.writeFile(path21.join(options.outputDir, "index.html"), html);
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 fs23 = __toESM(require("fs"));
13279
- var path22 = __toESM(require("path"));
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 path22.join(home, ".harness", "update-check.json");
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 = fs23.readFileSync(getStatePath(), "utf-8");
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 = path22.dirname(statePath);
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 path26 = await import("path");
13405
- const pkgDir = path26.dirname(pkgPath);
13406
- return path26.join(pkgDir, "out", `${grammarName}.wasm`);
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 fs24 = __toESM(require("fs/promises"));
13811
- var path23 = __toESM(require("path"));
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 path23.join(projectRoot, ".harness", "cache", "pricing.json");
14831
+ return path25.join(projectRoot, ".harness", "cache", "pricing.json");
13865
14832
  }
13866
14833
  function getStalenessMarkerPath(projectRoot) {
13867
- return path23.join(projectRoot, ".harness", "cache", "staleness-marker.json");
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 fs24.readFile(getCachePath(projectRoot), "utf-8");
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 fs24.mkdir(path23.dirname(cachePath), { recursive: true });
13880
- await fs24.writeFile(cachePath, JSON.stringify(data, null, 2));
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 fs24.readFile(markerPath, "utf-8");
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 fs24.mkdir(path23.dirname(markerPath), { recursive: true });
13920
- await fs24.writeFile(
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 fs24.unlink(getStalenessMarkerPath(projectRoot));
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 fs25 = __toESM(require("fs"));
14101
- var path24 = __toESM(require("path"));
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 = path24.join(projectRoot, ".harness", "metrics", "costs.jsonl");
15107
+ const costsFile = path26.join(projectRoot, ".harness", "metrics", "costs.jsonl");
14141
15108
  let raw;
14142
15109
  try {
14143
- raw = fs25.readFileSync(costsFile, "utf-8");
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 fs26 = __toESM(require("fs"));
14162
- var path25 = __toESM(require("path"));
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 ${path25.basename(filePath)}`
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 = fs26.readFileSync(filePath, "utf-8");
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 = path25.join(homeDir, ".claude", "projects");
15198
+ const projectsDir = path27.join(homeDir, ".claude", "projects");
14232
15199
  let projectDirs;
14233
15200
  try {
14234
- projectDirs = fs26.readdirSync(projectsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => path25.join(projectsDir, d.name));
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 = fs26.readdirSync(dir).filter((f) => f.endsWith(".jsonl")).map((f) => path25.join(dir, f));
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,