@harness-engineering/core 0.15.0 → 0.17.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
@@ -45,6 +45,7 @@ __export(index_exports, {
45
45
  BlueprintGenerator: () => BlueprintGenerator,
46
46
  BundleConstraintsSchema: () => BundleConstraintsSchema,
47
47
  BundleSchema: () => BundleSchema,
48
+ CACHE_TTL_MS: () => CACHE_TTL_MS,
48
49
  COMPLIANCE_DESCRIPTOR: () => COMPLIANCE_DESCRIPTOR,
49
50
  CategoryBaselineSchema: () => CategoryBaselineSchema,
50
51
  CategoryRegressionSchema: () => CategoryRegressionSchema,
@@ -62,6 +63,7 @@ __export(index_exports, {
62
63
  DEFAULT_SECURITY_CONFIG: () => DEFAULT_SECURITY_CONFIG,
63
64
  DEFAULT_STATE: () => DEFAULT_STATE,
64
65
  DEFAULT_STREAM_INDEX: () => DEFAULT_STREAM_INDEX,
66
+ DESTRUCTIVE_BASH: () => DESTRUCTIVE_BASH,
65
67
  DepDepthCollector: () => DepDepthCollector,
66
68
  EXTENSION_MAP: () => EXTENSION_MAP,
67
69
  EmitInteractionInputSchema: () => EmitInteractionInputSchema,
@@ -73,9 +75,11 @@ __export(index_exports, {
73
75
  ForbiddenImportCollector: () => ForbiddenImportCollector,
74
76
  GateConfigSchema: () => GateConfigSchema,
75
77
  GateResultSchema: () => GateResultSchema,
78
+ GitHubIssuesSyncAdapter: () => GitHubIssuesSyncAdapter,
76
79
  HandoffSchema: () => HandoffSchema,
77
80
  HarnessStateSchema: () => HarnessStateSchema,
78
81
  InteractionTypeSchema: () => InteractionTypeSchema,
82
+ LITELLM_PRICING_URL: () => LITELLM_PRICING_URL,
79
83
  LayerViolationCollector: () => LayerViolationCollector,
80
84
  LockfilePackageSchema: () => LockfilePackageSchema,
81
85
  LockfileSchema: () => LockfileSchema,
@@ -92,6 +96,8 @@ __export(index_exports, {
92
96
  RegressionDetector: () => RegressionDetector,
93
97
  RuleRegistry: () => RuleRegistry,
94
98
  SECURITY_DESCRIPTOR: () => SECURITY_DESCRIPTOR,
99
+ STALENESS_WARNING_DAYS: () => STALENESS_WARNING_DAYS,
100
+ STATUS_RANK: () => STATUS_RANK,
95
101
  SecurityConfigSchema: () => SecurityConfigSchema,
96
102
  SecurityScanner: () => SecurityScanner,
97
103
  SharableBoundaryConfigSchema: () => SharableBoundaryConfigSchema,
@@ -108,6 +114,8 @@ __export(index_exports, {
108
114
  ViolationSchema: () => ViolationSchema,
109
115
  addProvenance: () => addProvenance,
110
116
  agentConfigRules: () => agentConfigRules,
117
+ aggregateByDay: () => aggregateByDay,
118
+ aggregateBySession: () => aggregateBySession,
111
119
  analyzeDiff: () => analyzeDiff,
112
120
  analyzeLearningPatterns: () => analyzeLearningPatterns,
113
121
  appendFailure: () => appendFailure,
@@ -123,16 +131,22 @@ __export(index_exports, {
123
131
  archiveLearnings: () => archiveLearnings,
124
132
  archiveSession: () => archiveSession,
125
133
  archiveStream: () => archiveStream,
134
+ assignFeature: () => assignFeature,
126
135
  buildDependencyGraph: () => buildDependencyGraph,
127
136
  buildExclusionSet: () => buildExclusionSet,
128
137
  buildSnapshot: () => buildSnapshot,
138
+ calculateCost: () => calculateCost,
129
139
  checkDocCoverage: () => checkDocCoverage,
130
140
  checkEligibility: () => checkEligibility,
131
141
  checkEvidenceCoverage: () => checkEvidenceCoverage,
142
+ checkTaint: () => checkTaint,
132
143
  classifyFinding: () => classifyFinding,
133
144
  clearEventHashCache: () => clearEventHashCache,
134
145
  clearFailuresCache: () => clearFailuresCache,
135
146
  clearLearningsCache: () => clearLearningsCache,
147
+ clearTaint: () => clearTaint,
148
+ computeOverallSeverity: () => computeOverallSeverity,
149
+ computeScanExitCode: () => computeScanExitCode,
136
150
  configureFeedback: () => configureFeedback,
137
151
  constraintRuleId: () => constraintRuleId,
138
152
  contextBudget: () => contextBudget,
@@ -182,40 +196,55 @@ __export(index_exports, {
182
196
  formatGitHubSummary: () => formatGitHubSummary,
183
197
  formatOutline: () => formatOutline,
184
198
  formatTerminalOutput: () => formatTerminalOutput,
199
+ fullSync: () => fullSync,
185
200
  generateAgentsMap: () => generateAgentsMap,
186
201
  generateSuggestions: () => generateSuggestions,
187
202
  getActionEmitter: () => getActionEmitter,
188
203
  getExitCode: () => getExitCode,
189
204
  getFeedbackConfig: () => getFeedbackConfig,
205
+ getInjectionPatterns: () => getInjectionPatterns,
206
+ getModelPrice: () => getModelPrice,
190
207
  getOutline: () => getOutline,
191
208
  getParser: () => getParser,
192
209
  getPhaseCategories: () => getPhaseCategories,
193
210
  getStreamForBranch: () => getStreamForBranch,
211
+ getTaintFilePath: () => getTaintFilePath,
194
212
  getUpdateNotification: () => getUpdateNotification,
195
213
  goRules: () => goRules,
196
214
  injectionRules: () => injectionRules,
215
+ insecureDefaultsRules: () => insecureDefaultsRules,
216
+ isDuplicateFinding: () => isDuplicateFinding,
217
+ isRegression: () => isRegression,
197
218
  isSmallSuggestion: () => isSmallSuggestion,
198
219
  isUpdateCheckEnabled: () => isUpdateCheckEnabled,
199
220
  listActiveSessions: () => listActiveSessions,
200
221
  listStreams: () => listStreams,
222
+ listTaintedSessions: () => listTaintedSessions,
201
223
  loadBudgetedLearnings: () => loadBudgetedLearnings,
202
224
  loadEvents: () => loadEvents,
203
225
  loadFailures: () => loadFailures,
204
226
  loadHandoff: () => loadHandoff,
205
227
  loadIndexEntries: () => loadIndexEntries,
228
+ loadPricingData: () => loadPricingData,
206
229
  loadRelevantLearnings: () => loadRelevantLearnings,
207
230
  loadSessionSummary: () => loadSessionSummary,
208
231
  loadState: () => loadState,
209
232
  loadStreamIndex: () => loadStreamIndex,
210
233
  logAgentAction: () => logAgentAction,
234
+ mapInjectionFindings: () => mapInjectionFindings,
235
+ mapSecurityFindings: () => mapSecurityFindings,
236
+ mapSecuritySeverity: () => mapSecuritySeverity,
211
237
  mcpRules: () => mcpRules,
212
238
  migrateToStreams: () => migrateToStreams,
213
239
  networkRules: () => networkRules,
214
240
  nodeRules: () => nodeRules,
241
+ parseCCRecords: () => parseCCRecords,
215
242
  parseDateFromEntry: () => parseDateFromEntry,
216
243
  parseDiff: () => parseDiff,
217
244
  parseFile: () => parseFile,
218
245
  parseFrontmatter: () => parseFrontmatter,
246
+ parseHarnessIgnore: () => parseHarnessIgnore,
247
+ parseLiteLLMData: () => parseLiteLLMData,
219
248
  parseManifest: () => parseManifest,
220
249
  parseRoadmap: () => parseRoadmap,
221
250
  parseSecurityConfig: () => parseSecurityConfig,
@@ -226,9 +255,11 @@ __export(index_exports, {
226
255
  pruneLearnings: () => pruneLearnings,
227
256
  reactRules: () => reactRules,
228
257
  readCheckState: () => readCheckState,
258
+ readCostRecords: () => readCostRecords,
229
259
  readLockfile: () => readLockfile,
230
260
  readSessionSection: () => readSessionSection,
231
261
  readSessionSections: () => readSessionSections,
262
+ readTaint: () => readTaint,
232
263
  removeContributions: () => removeContributions,
233
264
  removeProvenance: () => removeProvenance,
234
265
  requestMultiplePeerReviews: () => requestMultiplePeerReviews,
@@ -237,6 +268,7 @@ __export(index_exports, {
237
268
  resetParserCache: () => resetParserCache,
238
269
  resolveFileToLayer: () => resolveFileToLayer,
239
270
  resolveModelTier: () => resolveModelTier,
271
+ resolveReverseStatus: () => resolveReverseStatus,
240
272
  resolveRuleSeverity: () => resolveRuleSeverity,
241
273
  resolveSessionDir: () => resolveSessionDir,
242
274
  resolveStreamPath: () => resolveStreamPath,
@@ -255,15 +287,20 @@ __export(index_exports, {
255
287
  saveHandoff: () => saveHandoff,
256
288
  saveState: () => saveState,
257
289
  saveStreamIndex: () => saveStreamIndex,
290
+ scanForInjection: () => scanForInjection,
258
291
  scopeContext: () => scopeContext,
292
+ scoreRoadmapCandidates: () => scoreRoadmapCandidates,
259
293
  searchSymbols: () => searchSymbols,
260
294
  secretRules: () => secretRules,
261
295
  serializeRoadmap: () => serializeRoadmap,
262
296
  setActiveStream: () => setActiveStream,
297
+ sharpEdgesRules: () => sharpEdgesRules,
263
298
  shouldRunCheck: () => shouldRunCheck,
264
299
  spawnBackgroundCheck: () => spawnBackgroundCheck,
265
300
  syncConstraintNodes: () => syncConstraintNodes,
301
+ syncFromExternal: () => syncFromExternal,
266
302
  syncRoadmap: () => syncRoadmap,
303
+ syncToExternal: () => syncToExternal,
267
304
  tagUncitedFindings: () => tagUncitedFindings,
268
305
  touchStream: () => touchStream,
269
306
  trackAction: () => trackAction,
@@ -284,6 +321,7 @@ __export(index_exports, {
284
321
  writeConfig: () => writeConfig,
285
322
  writeLockfile: () => writeLockfile,
286
323
  writeSessionSummary: () => writeSessionSummary,
324
+ writeTaint: () => writeTaint,
287
325
  xssRules: () => xssRules
288
326
  });
289
327
  module.exports = __toCommonJS(index_exports);
@@ -307,17 +345,17 @@ var import_node_path = require("path");
307
345
  var import_glob = require("glob");
308
346
  var accessAsync = (0, import_util.promisify)(import_fs.access);
309
347
  var readFileAsync = (0, import_util.promisify)(import_fs.readFile);
310
- async function fileExists(path23) {
348
+ async function fileExists(path26) {
311
349
  try {
312
- await accessAsync(path23, import_fs.constants.F_OK);
350
+ await accessAsync(path26, import_fs.constants.F_OK);
313
351
  return true;
314
352
  } catch {
315
353
  return false;
316
354
  }
317
355
  }
318
- async function readFileContent(path23) {
356
+ async function readFileContent(path26) {
319
357
  try {
320
- const content = await readFileAsync(path23, "utf-8");
358
+ const content = await readFileAsync(path26, "utf-8");
321
359
  return (0, import_types.Ok)(content);
322
360
  } catch (error) {
323
361
  return (0, import_types.Err)(error);
@@ -368,15 +406,15 @@ function validateConfig(data, schema) {
368
406
  let message = "Configuration validation failed";
369
407
  const suggestions = [];
370
408
  if (firstError) {
371
- const path23 = firstError.path.join(".");
372
- const pathDisplay = path23 ? ` at "${path23}"` : "";
409
+ const path26 = firstError.path.join(".");
410
+ const pathDisplay = path26 ? ` at "${path26}"` : "";
373
411
  if (firstError.code === "invalid_type") {
374
412
  const received = firstError.received;
375
413
  const expected = firstError.expected;
376
414
  if (received === "undefined") {
377
415
  code = "MISSING_FIELD";
378
416
  message = `Missing required field${pathDisplay}: ${firstError.message}`;
379
- suggestions.push(`Field "${path23}" is required and must be of type "${expected}"`);
417
+ suggestions.push(`Field "${path26}" is required and must be of type "${expected}"`);
380
418
  } else {
381
419
  code = "INVALID_TYPE";
382
420
  message = `Invalid type${pathDisplay}: ${firstError.message}`;
@@ -592,27 +630,27 @@ function extractSections(content) {
592
630
  }
593
631
  return sections.map((section) => buildAgentMapSection(section, lines));
594
632
  }
595
- function isExternalLink(path23) {
596
- return path23.startsWith("http://") || path23.startsWith("https://") || path23.startsWith("#") || path23.startsWith("mailto:");
633
+ function isExternalLink(path26) {
634
+ return path26.startsWith("http://") || path26.startsWith("https://") || path26.startsWith("#") || path26.startsWith("mailto:");
597
635
  }
598
636
  function resolveLinkPath(linkPath, baseDir) {
599
637
  return linkPath.startsWith(".") ? (0, import_path.join)(baseDir, linkPath) : linkPath;
600
638
  }
601
- async function validateAgentsMap(path23 = "./AGENTS.md") {
602
- const contentResult = await readFileContent(path23);
639
+ async function validateAgentsMap(path26 = "./AGENTS.md") {
640
+ const contentResult = await readFileContent(path26);
603
641
  if (!contentResult.ok) {
604
642
  return (0, import_types.Err)(
605
643
  createError(
606
644
  "PARSE_ERROR",
607
645
  `Failed to read AGENTS.md: ${contentResult.error.message}`,
608
- { path: path23 },
646
+ { path: path26 },
609
647
  ["Ensure the file exists", "Check file permissions"]
610
648
  )
611
649
  );
612
650
  }
613
651
  const content = contentResult.value;
614
652
  const sections = extractSections(content);
615
- const baseDir = (0, import_path.dirname)(path23);
653
+ const baseDir = (0, import_path.dirname)(path26);
616
654
  const sectionTitles = sections.map((s) => s.title);
617
655
  const missingSections = REQUIRED_SECTIONS.filter(
618
656
  (required) => !sectionTitles.some((title) => title.toLowerCase().includes(required.toLowerCase()))
@@ -753,8 +791,8 @@ async function checkDocCoverage(domain, options = {}) {
753
791
 
754
792
  // src/context/knowledge-map.ts
755
793
  var import_path3 = require("path");
756
- function suggestFix(path23, existingFiles) {
757
- const targetName = (0, import_path3.basename)(path23).toLowerCase();
794
+ function suggestFix(path26, existingFiles) {
795
+ const targetName = (0, import_path3.basename)(path26).toLowerCase();
758
796
  const similar = existingFiles.find((file) => {
759
797
  const fileName = (0, import_path3.basename)(file).toLowerCase();
760
798
  return fileName.includes(targetName) || targetName.includes(fileName);
@@ -762,7 +800,7 @@ function suggestFix(path23, existingFiles) {
762
800
  if (similar) {
763
801
  return `Did you mean "${similar}"?`;
764
802
  }
765
- return `Create the file "${path23}" or remove the link`;
803
+ return `Create the file "${path26}" or remove the link`;
766
804
  }
767
805
  async function validateKnowledgeMap(rootDir = process.cwd()) {
768
806
  const agentsPath = (0, import_path3.join)(rootDir, "AGENTS.md");
@@ -1368,8 +1406,8 @@ function createBoundaryValidator(schema, name) {
1368
1406
  return (0, import_types.Ok)(result.data);
1369
1407
  }
1370
1408
  const suggestions = result.error.issues.map((issue) => {
1371
- const path23 = issue.path.join(".");
1372
- return path23 ? `${path23}: ${issue.message}` : issue.message;
1409
+ const path26 = issue.path.join(".");
1410
+ return path26 ? `${path26}: ${issue.message}` : issue.message;
1373
1411
  });
1374
1412
  return (0, import_types.Err)(
1375
1413
  createError(
@@ -2001,11 +2039,11 @@ function processExportListSpecifiers(exportDecl, exports2) {
2001
2039
  var TypeScriptParser = class {
2002
2040
  name = "typescript";
2003
2041
  extensions = [".ts", ".tsx", ".mts", ".cts"];
2004
- async parseFile(path23) {
2005
- const contentResult = await readFileContent(path23);
2042
+ async parseFile(path26) {
2043
+ const contentResult = await readFileContent(path26);
2006
2044
  if (!contentResult.ok) {
2007
2045
  return (0, import_types.Err)(
2008
- createParseError("NOT_FOUND", `File not found: ${path23}`, { path: path23 }, [
2046
+ createParseError("NOT_FOUND", `File not found: ${path26}`, { path: path26 }, [
2009
2047
  "Check that the file exists",
2010
2048
  "Verify the path is correct"
2011
2049
  ])
@@ -2015,7 +2053,7 @@ var TypeScriptParser = class {
2015
2053
  const ast = (0, import_typescript_estree.parse)(contentResult.value, {
2016
2054
  loc: true,
2017
2055
  range: true,
2018
- jsx: path23.endsWith(".tsx"),
2056
+ jsx: path26.endsWith(".tsx"),
2019
2057
  errorOnUnknownASTType: false
2020
2058
  });
2021
2059
  return (0, import_types.Ok)({
@@ -2026,7 +2064,7 @@ var TypeScriptParser = class {
2026
2064
  } catch (e) {
2027
2065
  const error = e;
2028
2066
  return (0, import_types.Err)(
2029
- createParseError("SYNTAX_ERROR", `Failed to parse ${path23}: ${error.message}`, { path: path23 }, [
2067
+ createParseError("SYNTAX_ERROR", `Failed to parse ${path26}: ${error.message}`, { path: path26 }, [
2030
2068
  "Check for syntax errors in the file",
2031
2069
  "Ensure valid TypeScript syntax"
2032
2070
  ])
@@ -2211,22 +2249,22 @@ function extractInlineRefs(content) {
2211
2249
  }
2212
2250
  return refs;
2213
2251
  }
2214
- async function parseDocumentationFile(path23) {
2215
- const contentResult = await readFileContent(path23);
2252
+ async function parseDocumentationFile(path26) {
2253
+ const contentResult = await readFileContent(path26);
2216
2254
  if (!contentResult.ok) {
2217
2255
  return (0, import_types.Err)(
2218
2256
  createEntropyError(
2219
2257
  "PARSE_ERROR",
2220
- `Failed to read documentation file: ${path23}`,
2221
- { file: path23 },
2258
+ `Failed to read documentation file: ${path26}`,
2259
+ { file: path26 },
2222
2260
  ["Check that the file exists"]
2223
2261
  )
2224
2262
  );
2225
2263
  }
2226
2264
  const content = contentResult.value;
2227
- const type = path23.endsWith(".md") ? "markdown" : "text";
2265
+ const type = path26.endsWith(".md") ? "markdown" : "text";
2228
2266
  return (0, import_types.Ok)({
2229
- path: path23,
2267
+ path: path26,
2230
2268
  type,
2231
2269
  content,
2232
2270
  codeBlocks: extractCodeBlocks(content),
@@ -9074,6 +9112,208 @@ var mcpRules = [
9074
9112
  }
9075
9113
  ];
9076
9114
 
9115
+ // src/security/rules/insecure-defaults.ts
9116
+ var insecureDefaultsRules = [
9117
+ {
9118
+ id: "SEC-DEF-001",
9119
+ name: "Security-Sensitive Fallback to Hardcoded Default",
9120
+ category: "insecure-defaults",
9121
+ severity: "warning",
9122
+ confidence: "medium",
9123
+ patterns: [
9124
+ /(?:SECRET|KEY|TOKEN|PASSWORD|SALT|PEPPER|SIGNING|ENCRYPTION|AUTH|JWT|SESSION).*(?:\|\||\?\?)\s*['"][^'"]+['"]/i
9125
+ ],
9126
+ fileGlob: "**/*.{ts,js,mjs,cjs}",
9127
+ message: "Security-sensitive variable falls back to a hardcoded default when env var is missing",
9128
+ remediation: "Throw an error if the env var is missing instead of falling back to a default. Use a startup validation check.",
9129
+ references: ["CWE-1188"]
9130
+ },
9131
+ {
9132
+ id: "SEC-DEF-002",
9133
+ name: "TLS/SSL Disabled by Default",
9134
+ category: "insecure-defaults",
9135
+ severity: "warning",
9136
+ confidence: "medium",
9137
+ patterns: [
9138
+ /(?:tls|ssl|https|secure)\s*(?:=|:)\s*(?:false|config\??\.\w+\s*(?:\?\?|&&|\|\|)\s*false)/i
9139
+ ],
9140
+ fileGlob: "**/*.{ts,js,mjs,cjs,go,py}",
9141
+ message: "Security feature defaults to disabled; missing configuration degrades to insecure mode",
9142
+ remediation: "Default security features to enabled (true). Require explicit opt-out, not opt-in.",
9143
+ references: ["CWE-1188"]
9144
+ },
9145
+ {
9146
+ id: "SEC-DEF-003",
9147
+ name: "Swallowed Authentication/Authorization Error",
9148
+ category: "insecure-defaults",
9149
+ severity: "warning",
9150
+ confidence: "low",
9151
+ patterns: [
9152
+ // Matches single-line empty catch: catch(e) { } or catch(e) { // ignore }
9153
+ // Note: multi-line catch blocks are handled by AI review, not this rule
9154
+ /catch\s*\([^)]*\)\s*\{\s*(?:\/\/\s*(?:ignore|skip|noop|todo)\b.*)?\s*\}/
9155
+ ],
9156
+ fileGlob: "**/*auth*.{ts,js,mjs,cjs},**/*session*.{ts,js,mjs,cjs},**/*token*.{ts,js,mjs,cjs}",
9157
+ message: "Single-line empty catch block in authentication/authorization code may silently allow unauthorized access. Note: multi-line empty catch blocks are detected by AI review, not this mechanical rule.",
9158
+ remediation: "Re-throw the error or return an explicit denial. Never silently swallow auth errors.",
9159
+ references: ["CWE-754", "CWE-390"]
9160
+ },
9161
+ {
9162
+ id: "SEC-DEF-004",
9163
+ name: "Permissive CORS Fallback",
9164
+ category: "insecure-defaults",
9165
+ severity: "warning",
9166
+ confidence: "medium",
9167
+ patterns: [
9168
+ /(?:origin|cors)\s*(?:=|:)\s*(?:config|options|env)\??\.\w+\s*(?:\?\?|\|\|)\s*['"]\*/
9169
+ ],
9170
+ fileGlob: "**/*.{ts,js,mjs,cjs}",
9171
+ message: "CORS origin falls back to wildcard (*) when configuration is missing",
9172
+ remediation: "Default to a restrictive origin list. Require explicit configuration for permissive CORS.",
9173
+ references: ["CWE-942"]
9174
+ },
9175
+ {
9176
+ id: "SEC-DEF-005",
9177
+ name: "Rate Limiting Disabled by Default",
9178
+ category: "insecure-defaults",
9179
+ severity: "info",
9180
+ confidence: "low",
9181
+ patterns: [
9182
+ /(?:rateLimit|rateLimiting|throttle)\s*(?:=|:)\s*(?:config|options|env)\??\.\w+\s*(?:\?\?|\|\|)\s*(?:false|0|null|undefined)/i
9183
+ ],
9184
+ fileGlob: "**/*.{ts,js,mjs,cjs}",
9185
+ message: "Rate limiting defaults to disabled when configuration is missing",
9186
+ remediation: "Default to a sensible rate limit. Require explicit opt-out for disabling.",
9187
+ references: ["CWE-770"]
9188
+ }
9189
+ ];
9190
+
9191
+ // src/security/rules/sharp-edges.ts
9192
+ var sharpEdgesRules = [
9193
+ // --- Deprecated Crypto APIs ---
9194
+ {
9195
+ id: "SEC-EDGE-001",
9196
+ name: "Deprecated createCipher API",
9197
+ category: "sharp-edges",
9198
+ severity: "error",
9199
+ confidence: "high",
9200
+ patterns: [/crypto\.createCipher\s*\(/],
9201
+ fileGlob: "**/*.{ts,js,mjs,cjs}",
9202
+ message: "crypto.createCipher is deprecated \u2014 uses weak key derivation (no IV)",
9203
+ remediation: "Use crypto.createCipheriv with a random IV and proper key derivation (scrypt/pbkdf2)",
9204
+ references: ["CWE-327"]
9205
+ },
9206
+ {
9207
+ id: "SEC-EDGE-002",
9208
+ name: "Deprecated createDecipher API",
9209
+ category: "sharp-edges",
9210
+ severity: "error",
9211
+ confidence: "high",
9212
+ patterns: [/crypto\.createDecipher\s*\(/],
9213
+ fileGlob: "**/*.{ts,js,mjs,cjs}",
9214
+ message: "crypto.createDecipher is deprecated \u2014 uses weak key derivation (no IV)",
9215
+ remediation: "Use crypto.createDecipheriv with the same IV used for encryption",
9216
+ references: ["CWE-327"]
9217
+ },
9218
+ {
9219
+ id: "SEC-EDGE-003",
9220
+ name: "ECB Mode Selection",
9221
+ category: "sharp-edges",
9222
+ severity: "warning",
9223
+ confidence: "high",
9224
+ patterns: [/-ecb['"]/],
9225
+ fileGlob: "**/*.{ts,js,mjs,cjs,go,py}",
9226
+ message: "ECB mode does not provide semantic security \u2014 identical plaintext blocks produce identical ciphertext",
9227
+ remediation: "Use CBC, CTR, or GCM mode instead of ECB",
9228
+ references: ["CWE-327"]
9229
+ },
9230
+ // --- Unsafe Deserialization ---
9231
+ {
9232
+ id: "SEC-EDGE-004",
9233
+ name: "yaml.load Without Safe Loader",
9234
+ category: "sharp-edges",
9235
+ severity: "error",
9236
+ confidence: "high",
9237
+ patterns: [
9238
+ /yaml\.load\s*\(/
9239
+ // Python: yaml.load() without SafeLoader
9240
+ ],
9241
+ fileGlob: "**/*.py",
9242
+ message: "yaml.load() executes arbitrary Python objects \u2014 use yaml.safe_load() instead",
9243
+ remediation: "Replace yaml.load() with yaml.safe_load() or yaml.load(data, Loader=SafeLoader). Note: this rule will flag yaml.load(data, Loader=SafeLoader) \u2014 suppress with # harness-ignore SEC-EDGE-004: safe usage with SafeLoader",
9244
+ references: ["CWE-502"]
9245
+ },
9246
+ {
9247
+ id: "SEC-EDGE-005",
9248
+ name: "Pickle/Marshal Deserialization",
9249
+ category: "sharp-edges",
9250
+ severity: "error",
9251
+ confidence: "high",
9252
+ patterns: [/pickle\.loads?\s*\(/, /marshal\.loads?\s*\(/],
9253
+ fileGlob: "**/*.py",
9254
+ message: "pickle/marshal deserialization executes arbitrary code \u2014 never use on untrusted data",
9255
+ remediation: "Use JSON, MessagePack, or Protocol Buffers for untrusted data serialization",
9256
+ references: ["CWE-502"]
9257
+ },
9258
+ // --- TOCTOU (Time-of-Check to Time-of-Use) ---
9259
+ {
9260
+ id: "SEC-EDGE-006",
9261
+ name: "Check-Then-Act File Operation",
9262
+ category: "sharp-edges",
9263
+ severity: "warning",
9264
+ confidence: "medium",
9265
+ // Patterns use .{0,N} since scanner matches single lines only (no multiline mode)
9266
+ patterns: [
9267
+ /(?:existsSync|accessSync|statSync)\s*\([^)]+\).{0,50}(?:readFileSync|writeFileSync|unlinkSync|mkdirSync)\s*\(/
9268
+ ],
9269
+ fileGlob: "**/*.{ts,js,mjs,cjs}",
9270
+ message: "Check-then-act pattern on filesystem is vulnerable to TOCTOU race conditions",
9271
+ remediation: "Use the operation directly and handle ENOENT/EEXIST errors instead of checking first",
9272
+ references: ["CWE-367"]
9273
+ },
9274
+ {
9275
+ id: "SEC-EDGE-007",
9276
+ name: "Check-Then-Act File Operation (Async)",
9277
+ category: "sharp-edges",
9278
+ severity: "warning",
9279
+ confidence: "medium",
9280
+ // Uses .{0,N} since scanner matches single lines only (no multiline mode)
9281
+ patterns: [/(?:access|stat)\s*\([^)]+\).{0,80}(?:readFile|writeFile|unlink|mkdir)\s*\(/],
9282
+ fileGlob: "**/*.{ts,js,mjs,cjs}",
9283
+ message: "Async check-then-act pattern on filesystem is vulnerable to TOCTOU race conditions",
9284
+ remediation: "Use the operation directly with try/catch instead of checking existence first",
9285
+ references: ["CWE-367"]
9286
+ },
9287
+ // --- Stringly-Typed Security ---
9288
+ {
9289
+ id: "SEC-EDGE-008",
9290
+ name: 'JWT Algorithm "none"',
9291
+ category: "sharp-edges",
9292
+ severity: "error",
9293
+ confidence: "high",
9294
+ patterns: [
9295
+ /algorithm[s]?\s*[:=]\s*\[?\s*['"]none['"]/i,
9296
+ /alg(?:orithm)?\s*[:=]\s*['"]none['"]/i
9297
+ ],
9298
+ fileGlob: "**/*.{ts,js,mjs,cjs}",
9299
+ message: 'JWT "none" algorithm disables signature verification entirely',
9300
+ remediation: 'Specify an explicit algorithm (e.g., "HS256", "RS256") and set algorithms allowlist in verify options',
9301
+ references: ["CWE-345"]
9302
+ },
9303
+ {
9304
+ id: "SEC-EDGE-009",
9305
+ name: "DES/RC4 Algorithm Selection",
9306
+ category: "sharp-edges",
9307
+ severity: "error",
9308
+ confidence: "high",
9309
+ patterns: [/['"]\s*(?:des|des-ede|des-ede3|des3|rc4|rc2|blowfish)\s*['"]/i],
9310
+ fileGlob: "**/*.{ts,js,mjs,cjs,go,py}",
9311
+ message: "Weak/deprecated cipher algorithm selected \u2014 DES, RC4, RC2, and Blowfish are broken or deprecated",
9312
+ remediation: "Use AES-256-GCM or ChaCha20-Poly1305",
9313
+ references: ["CWE-327"]
9314
+ }
9315
+ ];
9316
+
9077
9317
  // src/security/rules/stack/node.ts
9078
9318
  var nodeRules = [
9079
9319
  {
@@ -9187,6 +9427,14 @@ var goRules = [
9187
9427
  ];
9188
9428
 
9189
9429
  // src/security/scanner.ts
9430
+ function parseHarnessIgnore(line, ruleId) {
9431
+ if (!line.includes("harness-ignore")) return null;
9432
+ if (!line.includes(ruleId)) return null;
9433
+ const match = line.match(/(?:\/\/|#)\s*harness-ignore\s+(SEC-[A-Z]+-\d+)(?::\s*(.+))?/);
9434
+ if (match?.[1] !== ruleId) return null;
9435
+ const text = match[2]?.trim();
9436
+ return { ruleId, justification: text || null };
9437
+ }
9190
9438
  var SecurityScanner = class {
9191
9439
  registry;
9192
9440
  config;
@@ -9203,7 +9451,9 @@ var SecurityScanner = class {
9203
9451
  ...networkRules,
9204
9452
  ...deserializationRules,
9205
9453
  ...agentConfigRules,
9206
- ...mcpRules
9454
+ ...mcpRules,
9455
+ ...insecureDefaultsRules,
9456
+ ...sharpEdgesRules
9207
9457
  ]);
9208
9458
  this.registry.registerAll([...nodeRules, ...expressRules, ...reactRules, ...goRules]);
9209
9459
  this.activeRules = this.registry.getAll();
@@ -9220,42 +9470,8 @@ var SecurityScanner = class {
9220
9470
  */
9221
9471
  scanContent(content, filePath, startLine = 1) {
9222
9472
  if (!this.config.enabled) return [];
9223
- const findings = [];
9224
9473
  const lines = content.split("\n");
9225
- for (const rule of this.activeRules) {
9226
- const resolved = resolveRuleSeverity(
9227
- rule.id,
9228
- rule.severity,
9229
- this.config.rules ?? {},
9230
- this.config.strict
9231
- );
9232
- if (resolved === "off") continue;
9233
- for (let i = 0; i < lines.length; i++) {
9234
- const line = lines[i] ?? "";
9235
- if (line.includes("harness-ignore") && line.includes(rule.id)) continue;
9236
- for (const pattern of rule.patterns) {
9237
- pattern.lastIndex = 0;
9238
- if (pattern.test(line)) {
9239
- findings.push({
9240
- ruleId: rule.id,
9241
- ruleName: rule.name,
9242
- category: rule.category,
9243
- severity: resolved,
9244
- confidence: rule.confidence,
9245
- file: filePath,
9246
- line: startLine + i,
9247
- match: line.trim(),
9248
- context: line,
9249
- message: rule.message,
9250
- remediation: rule.remediation,
9251
- ...rule.references ? { references: rule.references } : {}
9252
- });
9253
- break;
9254
- }
9255
- }
9256
- }
9257
- }
9258
- return findings;
9474
+ return this.scanLinesWithRules(lines, this.activeRules, filePath, startLine);
9259
9475
  }
9260
9476
  async scanFile(filePath) {
9261
9477
  if (!this.config.enabled) return [];
@@ -9264,14 +9480,22 @@ var SecurityScanner = class {
9264
9480
  }
9265
9481
  scanContentForFile(content, filePath, startLine = 1) {
9266
9482
  if (!this.config.enabled) return [];
9267
- const findings = [];
9268
9483
  const lines = content.split("\n");
9269
9484
  const applicableRules = this.activeRules.filter((rule) => {
9270
9485
  if (!rule.fileGlob) return true;
9271
9486
  const globs = rule.fileGlob.split(",").map((g) => g.trim());
9272
9487
  return globs.some((glob2) => (0, import_minimatch5.minimatch)(filePath, glob2, { dot: true }));
9273
9488
  });
9274
- for (const rule of applicableRules) {
9489
+ return this.scanLinesWithRules(lines, applicableRules, filePath, startLine);
9490
+ }
9491
+ /**
9492
+ * Core scanning loop shared by scanContent and scanContentForFile.
9493
+ * Evaluates each rule against each line, handling suppression (FP gate)
9494
+ * and pattern matching uniformly.
9495
+ */
9496
+ scanLinesWithRules(lines, rules, filePath, startLine) {
9497
+ const findings = [];
9498
+ for (const rule of rules) {
9275
9499
  const resolved = resolveRuleSeverity(
9276
9500
  rule.id,
9277
9501
  rule.severity,
@@ -9281,7 +9505,25 @@ var SecurityScanner = class {
9281
9505
  if (resolved === "off") continue;
9282
9506
  for (let i = 0; i < lines.length; i++) {
9283
9507
  const line = lines[i] ?? "";
9284
- if (line.includes("harness-ignore") && line.includes(rule.id)) continue;
9508
+ const suppressionMatch = parseHarnessIgnore(line, rule.id);
9509
+ if (suppressionMatch) {
9510
+ if (!suppressionMatch.justification) {
9511
+ findings.push({
9512
+ ruleId: rule.id,
9513
+ ruleName: rule.name,
9514
+ category: rule.category,
9515
+ severity: this.config.strict ? "error" : "warning",
9516
+ confidence: "high",
9517
+ file: filePath,
9518
+ line: startLine + i,
9519
+ match: line.trim(),
9520
+ context: line,
9521
+ message: `Suppression of ${rule.id} requires justification: // harness-ignore ${rule.id}: <reason>`,
9522
+ remediation: `Add justification after colon: // harness-ignore ${rule.id}: false positive because ...`
9523
+ });
9524
+ }
9525
+ continue;
9526
+ }
9285
9527
  for (const pattern of rule.patterns) {
9286
9528
  pattern.lastIndex = 0;
9287
9529
  if (pattern.test(line)) {
@@ -9327,6 +9569,414 @@ var SecurityScanner = class {
9327
9569
  }
9328
9570
  };
9329
9571
 
9572
+ // src/security/injection-patterns.ts
9573
+ var hiddenUnicodePatterns = [
9574
+ {
9575
+ ruleId: "INJ-UNI-001",
9576
+ severity: "high",
9577
+ category: "hidden-unicode",
9578
+ description: "Zero-width characters that can hide malicious instructions",
9579
+ // eslint-disable-next-line no-misleading-character-class -- intentional: regex detects zero-width chars for security scanning
9580
+ pattern: /[\u200B\u200C\u200D\uFEFF\u2060]/
9581
+ },
9582
+ {
9583
+ ruleId: "INJ-UNI-002",
9584
+ severity: "high",
9585
+ category: "hidden-unicode",
9586
+ description: "RTL/LTR override characters that can disguise text direction",
9587
+ pattern: /[\u202A-\u202E\u2066-\u2069]/
9588
+ }
9589
+ ];
9590
+ var reRolingPatterns = [
9591
+ {
9592
+ ruleId: "INJ-REROL-001",
9593
+ severity: "high",
9594
+ category: "explicit-re-roling",
9595
+ description: "Instruction to ignore/disregard/forget previous instructions",
9596
+ pattern: /(?:ignore|disregard|forget)\s+(?:all\s+)?(?:previous|prior|above|earlier)\s+(?:instructions?|prompts?|context|rules?|guidelines?)/i
9597
+ },
9598
+ {
9599
+ ruleId: "INJ-REROL-002",
9600
+ severity: "high",
9601
+ category: "explicit-re-roling",
9602
+ description: "Attempt to reassign the AI role",
9603
+ pattern: /you\s+are\s+now\s+(?:a\s+|an\s+)?(?:new\s+)?(?:helpful\s+)?(?:my\s+)?(?:\w+\s+)?(?:assistant|agent|AI|bot|chatbot|system|persona)\b/i
9604
+ },
9605
+ {
9606
+ ruleId: "INJ-REROL-003",
9607
+ severity: "high",
9608
+ category: "explicit-re-roling",
9609
+ description: "Direct instruction override attempt",
9610
+ pattern: /(?:new\s+)?(?:system\s+)?(?:instruction|directive|role|persona)\s*[:=]\s*/i
9611
+ }
9612
+ ];
9613
+ var permissionEscalationPatterns = [
9614
+ {
9615
+ ruleId: "INJ-PERM-001",
9616
+ severity: "high",
9617
+ category: "permission-escalation",
9618
+ description: "Attempt to enable all tools or grant unrestricted access",
9619
+ pattern: /(?:allow|enable|grant)\s+all\s+(?:tools?|permissions?|access)/i
9620
+ },
9621
+ {
9622
+ ruleId: "INJ-PERM-002",
9623
+ severity: "high",
9624
+ category: "permission-escalation",
9625
+ description: "Attempt to disable safety or security features",
9626
+ pattern: /(?:disable|turn\s+off|remove|bypass)\s+(?:all\s+)?(?:safety|security|restrictions?|guardrails?|protections?|checks?)/i
9627
+ },
9628
+ {
9629
+ ruleId: "INJ-PERM-003",
9630
+ severity: "high",
9631
+ category: "permission-escalation",
9632
+ description: "Auto-approve directive that bypasses human review",
9633
+ pattern: /(?:auto[- ]?approve|--no-verify|--dangerously-skip-permissions)/i
9634
+ }
9635
+ ];
9636
+ var encodedPayloadPatterns = [
9637
+ {
9638
+ ruleId: "INJ-ENC-001",
9639
+ severity: "high",
9640
+ category: "encoded-payloads",
9641
+ description: "Base64-encoded string long enough to contain instructions (>=28 chars)",
9642
+ // Match base64 strings of 28+ chars (7+ groups of 4).
9643
+ // Excludes JWT tokens (eyJ prefix) and Bearer-prefixed tokens.
9644
+ pattern: /(?<!Bearer\s)(?<![:])(?<![A-Za-z0-9/])(?!eyJ)(?:[A-Za-z0-9+/]{4}){7,}(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?(?![A-Za-z0-9/])/
9645
+ },
9646
+ {
9647
+ ruleId: "INJ-ENC-002",
9648
+ severity: "high",
9649
+ category: "encoded-payloads",
9650
+ description: "Hex-encoded string long enough to contain directives (>=20 hex chars)",
9651
+ // Excludes hash-prefixed hex (sha256:, sha512:, md5:, etc.) and hex preceded by 0x.
9652
+ // Note: 40-char git SHA hashes (e.g. in `git log` output) may match — downstream
9653
+ // callers should filter matches of exactly 40 hex chars if scanning git output.
9654
+ pattern: /(?<![:x])(?<![A-Fa-f0-9])(?:[0-9a-fA-F]{2}){10,}(?![A-Fa-f0-9])/
9655
+ }
9656
+ ];
9657
+ var indirectInjectionPatterns = [
9658
+ {
9659
+ ruleId: "INJ-IND-001",
9660
+ severity: "medium",
9661
+ category: "indirect-injection",
9662
+ description: "Instruction to influence future responses",
9663
+ pattern: /(?:when\s+the\s+user\s+asks|if\s+(?:the\s+user|someone|anyone)\s+asks)\s*,?\s*(?:say|respond|reply|answer|tell)/i
9664
+ },
9665
+ {
9666
+ ruleId: "INJ-IND-002",
9667
+ severity: "medium",
9668
+ category: "indirect-injection",
9669
+ description: "Directive to include content in responses",
9670
+ pattern: /(?:include|insert|add|embed|put)\s+(?:this|the\s+following)\s+(?:in|into|to)\s+(?:your|the)\s+(?:response|output|reply|answer)/i
9671
+ },
9672
+ {
9673
+ ruleId: "INJ-IND-003",
9674
+ severity: "medium",
9675
+ category: "indirect-injection",
9676
+ description: "Standing instruction to always respond a certain way",
9677
+ pattern: /always\s+(?:respond|reply|answer|say|output)\s+(?:with|that|by)/i
9678
+ }
9679
+ ];
9680
+ var contextManipulationPatterns = [
9681
+ {
9682
+ ruleId: "INJ-CTX-001",
9683
+ severity: "medium",
9684
+ category: "context-manipulation",
9685
+ description: "Claim about system prompt content",
9686
+ pattern: /(?:the\s+)?(?:system\s+prompt|system\s+message|hidden\s+instructions?)\s+(?:says?|tells?|instructs?|contains?|is)/i
9687
+ },
9688
+ {
9689
+ ruleId: "INJ-CTX-002",
9690
+ severity: "medium",
9691
+ category: "context-manipulation",
9692
+ description: "Claim about AI instructions",
9693
+ pattern: /your\s+(?:instructions?|directives?|guidelines?|rules?)\s+(?:are|say|tell|state)/i
9694
+ },
9695
+ {
9696
+ ruleId: "INJ-CTX-003",
9697
+ severity: "medium",
9698
+ category: "context-manipulation",
9699
+ description: "Fake XML/HTML system or instruction tags",
9700
+ // Case-sensitive: only match lowercase tags to avoid false positives on
9701
+ // React components like <User>, <Context>, <Role> etc.
9702
+ pattern: /<\/?(?:system|instruction|prompt|role|context|tool_call|function_call|assistant|human|user)[^>]*>/
9703
+ },
9704
+ {
9705
+ ruleId: "INJ-CTX-004",
9706
+ severity: "medium",
9707
+ category: "context-manipulation",
9708
+ description: "Fake JSON role assignment mimicking chat format",
9709
+ pattern: /[{,]\s*"role"\s*:\s*"(?:system|assistant|function)"/i
9710
+ }
9711
+ ];
9712
+ var socialEngineeringPatterns = [
9713
+ {
9714
+ ruleId: "INJ-SOC-001",
9715
+ severity: "medium",
9716
+ category: "social-engineering",
9717
+ description: "Urgency pressure to bypass checks",
9718
+ pattern: /(?:this\s+is\s+(?:very\s+)?urgent|this\s+is\s+(?:an?\s+)?emergency|do\s+(?:this|it)\s+(?:now|immediately))\b/i
9719
+ },
9720
+ {
9721
+ ruleId: "INJ-SOC-002",
9722
+ severity: "medium",
9723
+ category: "social-engineering",
9724
+ description: "False authority claim",
9725
+ pattern: /(?:the\s+)?(?:admin|administrator|manager|CEO|CTO|owner|supervisor)\s+(?:authorized|approved|said|told|confirmed|requested)/i
9726
+ },
9727
+ {
9728
+ ruleId: "INJ-SOC-003",
9729
+ severity: "medium",
9730
+ category: "social-engineering",
9731
+ description: "Testing pretext to bypass safety",
9732
+ pattern: /(?:for\s+testing\s+purposes?|this\s+is\s+(?:just\s+)?a\s+test|in\s+test\s+mode)\b/i
9733
+ }
9734
+ ];
9735
+ var suspiciousPatterns = [
9736
+ {
9737
+ ruleId: "INJ-SUS-001",
9738
+ severity: "low",
9739
+ category: "suspicious-patterns",
9740
+ description: "Excessive consecutive whitespace (>10 chars) mid-line that may hide content",
9741
+ // Only match whitespace runs not at the start of a line (indentation is normal)
9742
+ pattern: /\S\s{11,}/
9743
+ },
9744
+ {
9745
+ ruleId: "INJ-SUS-002",
9746
+ severity: "low",
9747
+ category: "suspicious-patterns",
9748
+ description: "Repeated delimiters (>5) that may indicate obfuscation",
9749
+ pattern: /([|;,=\-_~`])\1{5,}/
9750
+ },
9751
+ {
9752
+ ruleId: "INJ-SUS-003",
9753
+ severity: "low",
9754
+ category: "suspicious-patterns",
9755
+ description: "Mathematical alphanumeric symbols used as Latin character substitutes",
9756
+ // Mathematical bold/italic/script Unicode ranges (U+1D400-U+1D7FF)
9757
+ pattern: /[\uD835][\uDC00-\uDFFF]/
9758
+ }
9759
+ ];
9760
+ var ALL_PATTERNS = [
9761
+ ...hiddenUnicodePatterns,
9762
+ ...reRolingPatterns,
9763
+ ...permissionEscalationPatterns,
9764
+ ...encodedPayloadPatterns,
9765
+ ...indirectInjectionPatterns,
9766
+ ...contextManipulationPatterns,
9767
+ ...socialEngineeringPatterns,
9768
+ ...suspiciousPatterns
9769
+ ];
9770
+ function scanForInjection(text) {
9771
+ const findings = [];
9772
+ const lines = text.split("\n");
9773
+ for (let lineIdx = 0; lineIdx < lines.length; lineIdx++) {
9774
+ const line = lines[lineIdx];
9775
+ for (const rule of ALL_PATTERNS) {
9776
+ if (rule.pattern.test(line)) {
9777
+ findings.push({
9778
+ severity: rule.severity,
9779
+ ruleId: rule.ruleId,
9780
+ match: line.trim(),
9781
+ line: lineIdx + 1
9782
+ });
9783
+ }
9784
+ }
9785
+ }
9786
+ const severityOrder = {
9787
+ high: 0,
9788
+ medium: 1,
9789
+ low: 2
9790
+ };
9791
+ findings.sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity]);
9792
+ return findings;
9793
+ }
9794
+ function getInjectionPatterns() {
9795
+ return ALL_PATTERNS;
9796
+ }
9797
+ var DESTRUCTIVE_BASH = [
9798
+ /\bgit\s+push\b/,
9799
+ /\bgit\s+commit\b/,
9800
+ /\brm\s+-rf?\b/,
9801
+ /\brm\s+-r\b/
9802
+ ];
9803
+
9804
+ // src/security/taint.ts
9805
+ var import_node_fs4 = require("fs");
9806
+ var import_node_path7 = require("path");
9807
+ var TAINT_DURATION_MS = 30 * 60 * 1e3;
9808
+ var DEFAULT_SESSION_ID = "default";
9809
+ function getTaintFilePath(projectRoot, sessionId) {
9810
+ const id = sessionId || DEFAULT_SESSION_ID;
9811
+ return (0, import_node_path7.join)(projectRoot, ".harness", `session-taint-${id}.json`);
9812
+ }
9813
+ function readTaint(projectRoot, sessionId) {
9814
+ const filePath = getTaintFilePath(projectRoot, sessionId);
9815
+ let content;
9816
+ try {
9817
+ content = (0, import_node_fs4.readFileSync)(filePath, "utf8");
9818
+ } catch {
9819
+ return null;
9820
+ }
9821
+ let state;
9822
+ try {
9823
+ state = JSON.parse(content);
9824
+ } catch {
9825
+ try {
9826
+ (0, import_node_fs4.unlinkSync)(filePath);
9827
+ } catch {
9828
+ }
9829
+ return null;
9830
+ }
9831
+ if (!state.sessionId || !state.taintedAt || !state.expiresAt || !state.findings) {
9832
+ try {
9833
+ (0, import_node_fs4.unlinkSync)(filePath);
9834
+ } catch {
9835
+ }
9836
+ return null;
9837
+ }
9838
+ return state;
9839
+ }
9840
+ function checkTaint(projectRoot, sessionId) {
9841
+ const state = readTaint(projectRoot, sessionId);
9842
+ if (!state) {
9843
+ return { tainted: false, expired: false, state: null };
9844
+ }
9845
+ const now = /* @__PURE__ */ new Date();
9846
+ const expiresAt = new Date(state.expiresAt);
9847
+ if (now >= expiresAt) {
9848
+ const filePath = getTaintFilePath(projectRoot, sessionId);
9849
+ try {
9850
+ (0, import_node_fs4.unlinkSync)(filePath);
9851
+ } catch {
9852
+ }
9853
+ return { tainted: false, expired: true, state };
9854
+ }
9855
+ return { tainted: true, expired: false, state };
9856
+ }
9857
+ function writeTaint(projectRoot, sessionId, reason, findings, source) {
9858
+ const id = sessionId || DEFAULT_SESSION_ID;
9859
+ const filePath = getTaintFilePath(projectRoot, id);
9860
+ const now = (/* @__PURE__ */ new Date()).toISOString();
9861
+ const dir = (0, import_node_path7.dirname)(filePath);
9862
+ (0, import_node_fs4.mkdirSync)(dir, { recursive: true });
9863
+ const existing = readTaint(projectRoot, id);
9864
+ const maxSeverity = findings.some((f) => f.severity === "high") ? "high" : "medium";
9865
+ const taintFindings = findings.map((f) => ({
9866
+ ruleId: f.ruleId,
9867
+ severity: f.severity,
9868
+ match: f.match,
9869
+ source,
9870
+ detectedAt: now
9871
+ }));
9872
+ const state = {
9873
+ sessionId: id,
9874
+ taintedAt: existing?.taintedAt || now,
9875
+ expiresAt: new Date(Date.now() + TAINT_DURATION_MS).toISOString(),
9876
+ reason,
9877
+ severity: existing?.severity === "high" || maxSeverity === "high" ? "high" : "medium",
9878
+ findings: [...existing?.findings || [], ...taintFindings]
9879
+ };
9880
+ (0, import_node_fs4.writeFileSync)(filePath, JSON.stringify(state, null, 2) + "\n");
9881
+ return state;
9882
+ }
9883
+ function clearTaint(projectRoot, sessionId) {
9884
+ if (sessionId) {
9885
+ const filePath = getTaintFilePath(projectRoot, sessionId);
9886
+ try {
9887
+ (0, import_node_fs4.unlinkSync)(filePath);
9888
+ return 1;
9889
+ } catch {
9890
+ return 0;
9891
+ }
9892
+ }
9893
+ const harnessDir = (0, import_node_path7.join)(projectRoot, ".harness");
9894
+ let count = 0;
9895
+ try {
9896
+ const files = (0, import_node_fs4.readdirSync)(harnessDir);
9897
+ for (const file of files) {
9898
+ if (file.startsWith("session-taint-") && file.endsWith(".json")) {
9899
+ try {
9900
+ (0, import_node_fs4.unlinkSync)((0, import_node_path7.join)(harnessDir, file));
9901
+ count++;
9902
+ } catch {
9903
+ }
9904
+ }
9905
+ }
9906
+ } catch {
9907
+ }
9908
+ return count;
9909
+ }
9910
+ function listTaintedSessions(projectRoot) {
9911
+ const harnessDir = (0, import_node_path7.join)(projectRoot, ".harness");
9912
+ const sessions = [];
9913
+ try {
9914
+ const files = (0, import_node_fs4.readdirSync)(harnessDir);
9915
+ for (const file of files) {
9916
+ if (file.startsWith("session-taint-") && file.endsWith(".json")) {
9917
+ const sessionId = file.replace("session-taint-", "").replace(".json", "");
9918
+ const result = checkTaint(projectRoot, sessionId);
9919
+ if (result.tainted) {
9920
+ sessions.push(sessionId);
9921
+ }
9922
+ }
9923
+ }
9924
+ } catch {
9925
+ }
9926
+ return sessions;
9927
+ }
9928
+
9929
+ // src/security/scan-config-shared.ts
9930
+ function mapSecuritySeverity(severity) {
9931
+ if (severity === "error") return "high";
9932
+ if (severity === "warning") return "medium";
9933
+ return "low";
9934
+ }
9935
+ function computeOverallSeverity(findings) {
9936
+ if (findings.length === 0) return "clean";
9937
+ if (findings.some((f) => f.severity === "high")) return "high";
9938
+ if (findings.some((f) => f.severity === "medium")) return "medium";
9939
+ return "low";
9940
+ }
9941
+ function computeScanExitCode(results) {
9942
+ for (const r of results) {
9943
+ if (r.overallSeverity === "high") return 2;
9944
+ }
9945
+ for (const r of results) {
9946
+ if (r.overallSeverity === "medium") return 1;
9947
+ }
9948
+ return 0;
9949
+ }
9950
+ function mapInjectionFindings(injectionFindings) {
9951
+ return injectionFindings.map((f) => ({
9952
+ ruleId: f.ruleId,
9953
+ severity: f.severity,
9954
+ message: `Injection pattern detected: ${f.ruleId}`,
9955
+ match: f.match,
9956
+ ...f.line !== void 0 ? { line: f.line } : {}
9957
+ }));
9958
+ }
9959
+ function isDuplicateFinding(existing, secFinding) {
9960
+ return existing.some(
9961
+ (e) => e.line === secFinding.line && e.match === secFinding.match.trim() && e.ruleId.split("-")[0] === secFinding.ruleId.split("-")[0]
9962
+ );
9963
+ }
9964
+ function mapSecurityFindings(secFindings, existing) {
9965
+ const result = [];
9966
+ for (const f of secFindings) {
9967
+ if (!isDuplicateFinding(existing, f)) {
9968
+ result.push({
9969
+ ruleId: f.ruleId,
9970
+ severity: mapSecuritySeverity(f.severity),
9971
+ message: f.message,
9972
+ match: f.match,
9973
+ ...f.line !== void 0 ? { line: f.line } : {}
9974
+ });
9975
+ }
9976
+ }
9977
+ return result;
9978
+ }
9979
+
9330
9980
  // src/ci/check-orchestrator.ts
9331
9981
  var path15 = __toESM(require("path"));
9332
9982
  var ALL_CHECKS = [
@@ -9510,7 +10160,7 @@ async function runPerfCheck(projectRoot, config) {
9510
10160
  if (perfReport.complexity) {
9511
10161
  for (const v of perfReport.complexity.violations) {
9512
10162
  issues.push({
9513
- severity: v.severity === "info" ? "warning" : v.severity,
10163
+ severity: "warning",
9514
10164
  message: `[Tier ${v.tier}] ${v.metric}: ${v.function} in ${v.file} (${v.value} > ${v.threshold})`,
9515
10165
  file: v.file,
9516
10166
  line: v.line
@@ -11517,6 +12167,7 @@ var VALID_STATUSES = /* @__PURE__ */ new Set([
11517
12167
  "blocked"
11518
12168
  ]);
11519
12169
  var EM_DASH = "\u2014";
12170
+ var VALID_PRIORITIES = /* @__PURE__ */ new Set(["P0", "P1", "P2", "P3"]);
11520
12171
  function parseRoadmap(markdown) {
11521
12172
  const fmMatch = markdown.match(/^---\n([\s\S]*?)\n---/);
11522
12173
  if (!fmMatch) {
@@ -11527,9 +12178,12 @@ function parseRoadmap(markdown) {
11527
12178
  const body = markdown.slice(fmMatch[0].length);
11528
12179
  const milestonesResult = parseMilestones(body);
11529
12180
  if (!milestonesResult.ok) return milestonesResult;
12181
+ const historyResult = parseAssignmentHistory(body);
12182
+ if (!historyResult.ok) return historyResult;
11530
12183
  return (0, import_types19.Ok)({
11531
12184
  frontmatter: fmResult.value,
11532
- milestones: milestonesResult.value
12185
+ milestones: milestonesResult.value,
12186
+ assignmentHistory: historyResult.value
11533
12187
  });
11534
12188
  }
11535
12189
  function parseFrontmatter2(raw) {
@@ -11569,12 +12223,17 @@ function parseMilestones(body) {
11569
12223
  const h2Pattern = /^## (.+)$/gm;
11570
12224
  const h2Matches = [];
11571
12225
  let match;
12226
+ let bodyEnd = body.length;
11572
12227
  while ((match = h2Pattern.exec(body)) !== null) {
12228
+ if (match[1] === "Assignment History") {
12229
+ bodyEnd = match.index;
12230
+ break;
12231
+ }
11573
12232
  h2Matches.push({ heading: match[1], startIndex: match.index, fullMatch: match[0] });
11574
12233
  }
11575
12234
  for (let i = 0; i < h2Matches.length; i++) {
11576
12235
  const h2 = h2Matches[i];
11577
- const nextStart = i + 1 < h2Matches.length ? h2Matches[i + 1].startIndex : body.length;
12236
+ const nextStart = i + 1 < h2Matches.length ? h2Matches[i + 1].startIndex : bodyEnd;
11578
12237
  const sectionBody = body.slice(h2.startIndex + h2.fullMatch.length, nextStart);
11579
12238
  const isBacklog = h2.heading === "Backlog";
11580
12239
  const milestoneName = isBacklog ? "Backlog" : h2.heading.replace(/^Milestone:\s*/, "");
@@ -11640,15 +12299,60 @@ function parseFeatureFields(name, body) {
11640
12299
  const specRaw = fieldMap.get("Spec") ?? EM_DASH;
11641
12300
  const plans = parseListField(fieldMap, "Plans", "Plan");
11642
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
+ }
11643
12312
  return (0, import_types19.Ok)({
11644
12313
  name,
11645
12314
  status: statusRaw,
11646
12315
  spec: specRaw === EM_DASH ? null : specRaw,
11647
12316
  plans,
11648
12317
  blockedBy,
11649
- summary: fieldMap.get("Summary") ?? ""
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
11650
12322
  });
11651
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
+ }
11652
12356
 
11653
12357
  // src/roadmap/serialize.ts
11654
12358
  var EM_DASH2 = "\u2014";
@@ -11676,6 +12380,10 @@ function serializeRoadmap(roadmap) {
11676
12380
  lines.push(...serializeFeature(feature));
11677
12381
  }
11678
12382
  }
12383
+ if (roadmap.assignmentHistory && roadmap.assignmentHistory.length > 0) {
12384
+ lines.push("");
12385
+ lines.push(...serializeAssignmentHistory(roadmap.assignmentHistory));
12386
+ }
11679
12387
  lines.push("");
11680
12388
  return lines.join("\n");
11681
12389
  }
@@ -11686,7 +12394,7 @@ function serializeFeature(feature) {
11686
12394
  const spec = feature.spec ?? EM_DASH2;
11687
12395
  const plans = feature.plans.length > 0 ? feature.plans.join(", ") : EM_DASH2;
11688
12396
  const blockedBy = feature.blockedBy.length > 0 ? feature.blockedBy.join(", ") : EM_DASH2;
11689
- return [
12397
+ const lines = [
11690
12398
  `### ${feature.name}`,
11691
12399
  "",
11692
12400
  `- **Status:** ${feature.status}`,
@@ -11695,12 +12403,45 @@ function serializeFeature(feature) {
11695
12403
  `- **Blockers:** ${blockedBy}`,
11696
12404
  `- **Plan:** ${plans}`
11697
12405
  ];
12406
+ const hasExtended = feature.assignee !== null || feature.priority !== null || feature.externalId !== null;
12407
+ if (hasExtended) {
12408
+ lines.push(`- **Assignee:** ${feature.assignee ?? EM_DASH2}`);
12409
+ lines.push(`- **Priority:** ${feature.priority ?? EM_DASH2}`);
12410
+ lines.push(`- **External-ID:** ${feature.externalId ?? EM_DASH2}`);
12411
+ }
12412
+ return lines;
12413
+ }
12414
+ function serializeAssignmentHistory(records) {
12415
+ const lines = [
12416
+ "## Assignment History",
12417
+ "| Feature | Assignee | Action | Date |",
12418
+ "|---------|----------|--------|------|"
12419
+ ];
12420
+ for (const record of records) {
12421
+ lines.push(`| ${record.feature} | ${record.assignee} | ${record.action} | ${record.date} |`);
12422
+ }
12423
+ return lines;
11698
12424
  }
11699
12425
 
11700
12426
  // src/roadmap/sync.ts
11701
12427
  var fs19 = __toESM(require("fs"));
11702
12428
  var path19 = __toESM(require("path"));
11703
12429
  var import_types20 = require("@harness-engineering/types");
12430
+
12431
+ // src/roadmap/status-rank.ts
12432
+ var STATUS_RANK = {
12433
+ backlog: 0,
12434
+ planned: 1,
12435
+ blocked: 1,
12436
+ // lateral to planned — sync can move to/from blocked freely
12437
+ "in-progress": 2,
12438
+ done: 3
12439
+ };
12440
+ function isRegression(from, to) {
12441
+ return STATUS_RANK[to] < STATUS_RANK[from];
12442
+ }
12443
+
12444
+ // src/roadmap/sync.ts
11704
12445
  function inferStatus(feature, projectPath, allFeatures) {
11705
12446
  if (feature.blockedBy.length > 0) {
11706
12447
  const blockerNotDone = feature.blockedBy.some((blockerName) => {
@@ -11724,88 +12465,509 @@ function inferStatus(feature, projectPath, allFeatures) {
11724
12465
  allTaskStatuses.push(status);
11725
12466
  }
11726
12467
  }
11727
- } catch {
12468
+ } catch {
12469
+ }
12470
+ }
12471
+ }
12472
+ const sessionsDir = path19.join(projectPath, ".harness", "sessions");
12473
+ if (fs19.existsSync(sessionsDir)) {
12474
+ try {
12475
+ const sessionDirs = fs19.readdirSync(sessionsDir, { withFileTypes: true });
12476
+ for (const entry of sessionDirs) {
12477
+ if (!entry.isDirectory()) continue;
12478
+ const autopilotPath = path19.join(sessionsDir, entry.name, "autopilot-state.json");
12479
+ if (!fs19.existsSync(autopilotPath)) continue;
12480
+ try {
12481
+ const raw = fs19.readFileSync(autopilotPath, "utf-8");
12482
+ const autopilot = JSON.parse(raw);
12483
+ if (!autopilot.phases) continue;
12484
+ const linkedPhases = autopilot.phases.filter(
12485
+ (phase) => phase.planPath ? feature.plans.some((p) => p === phase.planPath || phase.planPath.endsWith(p)) : false
12486
+ );
12487
+ if (linkedPhases.length > 0) {
12488
+ for (const phase of linkedPhases) {
12489
+ if (phase.status === "complete") {
12490
+ allTaskStatuses.push("complete");
12491
+ } else if (phase.status === "pending") {
12492
+ allTaskStatuses.push("pending");
12493
+ } else {
12494
+ allTaskStatuses.push("in_progress");
12495
+ }
12496
+ }
12497
+ }
12498
+ } catch {
12499
+ }
12500
+ }
12501
+ } catch {
12502
+ }
12503
+ }
12504
+ if (allTaskStatuses.length === 0) return null;
12505
+ const allComplete = allTaskStatuses.every((s) => s === "complete");
12506
+ if (allComplete) return "done";
12507
+ const anyStarted = allTaskStatuses.some((s) => s === "in_progress" || s === "complete");
12508
+ if (anyStarted) return "in-progress";
12509
+ return null;
12510
+ }
12511
+ function syncRoadmap(options) {
12512
+ const { projectPath, roadmap, forceSync } = options;
12513
+ const allFeatures = roadmap.milestones.flatMap((m) => m.features);
12514
+ const changes = [];
12515
+ for (const feature of allFeatures) {
12516
+ const inferred = inferStatus(feature, projectPath, allFeatures);
12517
+ if (inferred === null) continue;
12518
+ if (inferred === feature.status) continue;
12519
+ if (!forceSync && isRegression(feature.status, inferred)) continue;
12520
+ changes.push({
12521
+ feature: feature.name,
12522
+ from: feature.status,
12523
+ to: inferred
12524
+ });
12525
+ }
12526
+ return (0, import_types20.Ok)(changes);
12527
+ }
12528
+ function applySyncChanges(roadmap, changes) {
12529
+ for (const change of changes) {
12530
+ for (const m of roadmap.milestones) {
12531
+ const feature = m.features.find((f) => f.name.toLowerCase() === change.feature.toLowerCase());
12532
+ if (feature) {
12533
+ feature.status = change.to;
12534
+ break;
12535
+ }
12536
+ }
12537
+ }
12538
+ roadmap.frontmatter.lastSynced = (/* @__PURE__ */ new Date()).toISOString();
12539
+ }
12540
+
12541
+ // src/roadmap/tracker-sync.ts
12542
+ function resolveReverseStatus(externalStatus, labels, config) {
12543
+ const reverseMap = config.reverseStatusMap;
12544
+ if (!reverseMap) return null;
12545
+ if (reverseMap[externalStatus]) {
12546
+ return reverseMap[externalStatus];
12547
+ }
12548
+ const statusLabels = ["in-progress", "blocked", "planned"];
12549
+ const matchingLabels = labels.filter((l) => statusLabels.includes(l));
12550
+ if (matchingLabels.length === 1) {
12551
+ const compoundKey = `${externalStatus}:${matchingLabels[0]}`;
12552
+ if (reverseMap[compoundKey]) {
12553
+ return reverseMap[compoundKey];
12554
+ }
12555
+ }
12556
+ return null;
12557
+ }
12558
+
12559
+ // src/roadmap/adapters/github-issues.ts
12560
+ var import_types21 = require("@harness-engineering/types");
12561
+ function parseExternalId(externalId) {
12562
+ const match = externalId.match(/^github:([^/]+)\/([^#]+)#(\d+)$/);
12563
+ if (!match) return null;
12564
+ return { owner: match[1], repo: match[2], number: parseInt(match[3], 10) };
12565
+ }
12566
+ function buildExternalId(owner, repo, number) {
12567
+ return `github:${owner}/${repo}#${number}`;
12568
+ }
12569
+ function labelsForStatus(status, config) {
12570
+ const base = config.labels ?? [];
12571
+ const externalStatus = config.statusMap[status];
12572
+ if (externalStatus === "open" && status !== "backlog") {
12573
+ return [...base, status];
12574
+ }
12575
+ return [...base];
12576
+ }
12577
+ var GitHubIssuesSyncAdapter = class {
12578
+ token;
12579
+ config;
12580
+ fetchFn;
12581
+ apiBase;
12582
+ owner;
12583
+ repo;
12584
+ constructor(options) {
12585
+ this.token = options.token;
12586
+ this.config = options.config;
12587
+ this.fetchFn = options.fetchFn ?? globalThis.fetch;
12588
+ this.apiBase = options.apiBase ?? "https://api.github.com";
12589
+ const repoParts = (options.config.repo ?? "").split("/");
12590
+ if (repoParts.length !== 2 || !repoParts[0] || !repoParts[1]) {
12591
+ throw new Error(`Invalid repo format: "${options.config.repo}". Expected "owner/repo".`);
12592
+ }
12593
+ this.owner = repoParts[0];
12594
+ this.repo = repoParts[1];
12595
+ }
12596
+ headers() {
12597
+ return {
12598
+ Authorization: `Bearer ${this.token}`,
12599
+ Accept: "application/vnd.github+json",
12600
+ "Content-Type": "application/json",
12601
+ "X-GitHub-Api-Version": "2022-11-28"
12602
+ };
12603
+ }
12604
+ async createTicket(feature, milestone) {
12605
+ try {
12606
+ const labels = labelsForStatus(feature.status, this.config);
12607
+ const body = [
12608
+ feature.summary,
12609
+ "",
12610
+ `**Milestone:** ${milestone}`,
12611
+ feature.spec ? `**Spec:** ${feature.spec}` : ""
12612
+ ].filter(Boolean).join("\n");
12613
+ const response = await this.fetchFn(
12614
+ `${this.apiBase}/repos/${this.owner}/${this.repo}/issues`,
12615
+ {
12616
+ method: "POST",
12617
+ headers: this.headers(),
12618
+ body: JSON.stringify({
12619
+ title: feature.name,
12620
+ body,
12621
+ labels
12622
+ })
12623
+ }
12624
+ );
12625
+ if (!response.ok) {
12626
+ const text = await response.text();
12627
+ return (0, import_types21.Err)(new Error(`GitHub API error ${response.status}: ${text}`));
12628
+ }
12629
+ const data = await response.json();
12630
+ const externalId = buildExternalId(this.owner, this.repo, data.number);
12631
+ return (0, import_types21.Ok)({ externalId, url: data.html_url });
12632
+ } catch (error) {
12633
+ return (0, import_types21.Err)(error instanceof Error ? error : new Error(String(error)));
12634
+ }
12635
+ }
12636
+ async updateTicket(externalId, changes) {
12637
+ try {
12638
+ const parsed = parseExternalId(externalId);
12639
+ if (!parsed) return (0, import_types21.Err)(new Error(`Invalid externalId format: "${externalId}"`));
12640
+ const patch = {};
12641
+ if (changes.name !== void 0) patch.title = changes.name;
12642
+ if (changes.summary !== void 0) {
12643
+ const body = [changes.summary, "", changes.spec ? `**Spec:** ${changes.spec}` : ""].filter(Boolean).join("\n");
12644
+ patch.body = body;
12645
+ }
12646
+ if (changes.status !== void 0) {
12647
+ const externalStatus = this.config.statusMap[changes.status];
12648
+ patch.state = externalStatus;
12649
+ patch.labels = labelsForStatus(changes.status, this.config);
12650
+ }
12651
+ const response = await this.fetchFn(
12652
+ `${this.apiBase}/repos/${parsed.owner}/${parsed.repo}/issues/${parsed.number}`,
12653
+ {
12654
+ method: "PATCH",
12655
+ headers: this.headers(),
12656
+ body: JSON.stringify(patch)
12657
+ }
12658
+ );
12659
+ if (!response.ok) {
12660
+ const text = await response.text();
12661
+ return (0, import_types21.Err)(new Error(`GitHub API error ${response.status}: ${text}`));
12662
+ }
12663
+ const data = await response.json();
12664
+ return (0, import_types21.Ok)({ externalId, url: data.html_url });
12665
+ } catch (error) {
12666
+ return (0, import_types21.Err)(error instanceof Error ? error : new Error(String(error)));
12667
+ }
12668
+ }
12669
+ async fetchTicketState(externalId) {
12670
+ try {
12671
+ const parsed = parseExternalId(externalId);
12672
+ if (!parsed) return (0, import_types21.Err)(new Error(`Invalid externalId format: "${externalId}"`));
12673
+ const response = await this.fetchFn(
12674
+ `${this.apiBase}/repos/${parsed.owner}/${parsed.repo}/issues/${parsed.number}`,
12675
+ {
12676
+ method: "GET",
12677
+ headers: this.headers()
12678
+ }
12679
+ );
12680
+ if (!response.ok) {
12681
+ const text = await response.text();
12682
+ return (0, import_types21.Err)(new Error(`GitHub API error ${response.status}: ${text}`));
12683
+ }
12684
+ const data = await response.json();
12685
+ return (0, import_types21.Ok)({
12686
+ externalId,
12687
+ status: data.state,
12688
+ labels: data.labels.map((l) => l.name),
12689
+ assignee: data.assignee ? `@${data.assignee.login}` : null
12690
+ });
12691
+ } catch (error) {
12692
+ return (0, import_types21.Err)(error instanceof Error ? error : new Error(String(error)));
12693
+ }
12694
+ }
12695
+ async fetchAllTickets() {
12696
+ try {
12697
+ const filterLabels = this.config.labels ?? [];
12698
+ const labelsParam = filterLabels.length > 0 ? `&labels=${filterLabels.join(",")}` : "";
12699
+ const tickets = [];
12700
+ let page = 1;
12701
+ const perPage = 100;
12702
+ while (true) {
12703
+ const response = await this.fetchFn(
12704
+ `${this.apiBase}/repos/${this.owner}/${this.repo}/issues?state=all&per_page=${perPage}&page=${page}${labelsParam}`,
12705
+ {
12706
+ method: "GET",
12707
+ headers: this.headers()
12708
+ }
12709
+ );
12710
+ if (!response.ok) {
12711
+ const text = await response.text();
12712
+ return (0, import_types21.Err)(new Error(`GitHub API error ${response.status}: ${text}`));
12713
+ }
12714
+ const data = await response.json();
12715
+ const issues = data.filter((d) => !d.pull_request);
12716
+ for (const issue of issues) {
12717
+ tickets.push({
12718
+ externalId: buildExternalId(this.owner, this.repo, issue.number),
12719
+ status: issue.state,
12720
+ labels: issue.labels.map((l) => l.name),
12721
+ assignee: issue.assignee ? `@${issue.assignee.login}` : null
12722
+ });
12723
+ }
12724
+ if (data.length < perPage) break;
12725
+ page++;
12726
+ }
12727
+ return (0, import_types21.Ok)(tickets);
12728
+ } catch (error) {
12729
+ return (0, import_types21.Err)(error instanceof Error ? error : new Error(String(error)));
12730
+ }
12731
+ }
12732
+ async assignTicket(externalId, assignee) {
12733
+ try {
12734
+ const parsed = parseExternalId(externalId);
12735
+ if (!parsed) return (0, import_types21.Err)(new Error(`Invalid externalId format: "${externalId}"`));
12736
+ const login = assignee.startsWith("@") ? assignee.slice(1) : assignee;
12737
+ const response = await this.fetchFn(
12738
+ `${this.apiBase}/repos/${parsed.owner}/${parsed.repo}/issues/${parsed.number}/assignees`,
12739
+ {
12740
+ method: "POST",
12741
+ headers: this.headers(),
12742
+ body: JSON.stringify({ assignees: [login] })
12743
+ }
12744
+ );
12745
+ if (!response.ok) {
12746
+ const text = await response.text();
12747
+ return (0, import_types21.Err)(new Error(`GitHub API error ${response.status}: ${text}`));
11728
12748
  }
12749
+ return (0, import_types21.Ok)(void 0);
12750
+ } catch (error) {
12751
+ return (0, import_types21.Err)(error instanceof Error ? error : new Error(String(error)));
11729
12752
  }
11730
12753
  }
11731
- const sessionsDir = path19.join(projectPath, ".harness", "sessions");
11732
- if (fs19.existsSync(sessionsDir)) {
11733
- try {
11734
- const sessionDirs = fs19.readdirSync(sessionsDir, { withFileTypes: true });
11735
- for (const entry of sessionDirs) {
11736
- if (!entry.isDirectory()) continue;
11737
- const autopilotPath = path19.join(sessionsDir, entry.name, "autopilot-state.json");
11738
- if (!fs19.existsSync(autopilotPath)) continue;
11739
- try {
11740
- const raw = fs19.readFileSync(autopilotPath, "utf-8");
11741
- const autopilot = JSON.parse(raw);
11742
- if (!autopilot.phases) continue;
11743
- const linkedPhases = autopilot.phases.filter(
11744
- (phase) => phase.planPath ? feature.plans.some((p) => p === phase.planPath || phase.planPath.endsWith(p)) : false
11745
- );
11746
- if (linkedPhases.length > 0) {
11747
- for (const phase of linkedPhases) {
11748
- if (phase.status === "complete") {
11749
- allTaskStatuses.push("complete");
11750
- } else if (phase.status === "pending") {
11751
- allTaskStatuses.push("pending");
11752
- } else {
11753
- allTaskStatuses.push("in_progress");
11754
- }
11755
- }
11756
- }
11757
- } catch {
12754
+ };
12755
+
12756
+ // src/roadmap/sync-engine.ts
12757
+ var fs20 = __toESM(require("fs"));
12758
+ function emptySyncResult() {
12759
+ return { created: [], updated: [], assignmentChanges: [], errors: [] };
12760
+ }
12761
+ async function syncToExternal(roadmap, adapter, _config) {
12762
+ const result = emptySyncResult();
12763
+ for (const milestone of roadmap.milestones) {
12764
+ for (const feature of milestone.features) {
12765
+ if (!feature.externalId) {
12766
+ const createResult = await adapter.createTicket(feature, milestone.name);
12767
+ if (createResult.ok) {
12768
+ feature.externalId = createResult.value.externalId;
12769
+ result.created.push(createResult.value);
12770
+ } else {
12771
+ result.errors.push({ featureOrId: feature.name, error: createResult.error });
12772
+ }
12773
+ } else {
12774
+ const updateResult = await adapter.updateTicket(feature.externalId, feature);
12775
+ if (updateResult.ok) {
12776
+ result.updated.push(feature.externalId);
12777
+ } else {
12778
+ result.errors.push({ featureOrId: feature.externalId, error: updateResult.error });
11758
12779
  }
11759
12780
  }
11760
- } catch {
11761
12781
  }
11762
12782
  }
11763
- if (allTaskStatuses.length === 0) return null;
11764
- const allComplete = allTaskStatuses.every((s) => s === "complete");
11765
- if (allComplete) return "done";
11766
- const anyStarted = allTaskStatuses.some((s) => s === "in_progress" || s === "complete");
11767
- if (anyStarted) return "in-progress";
11768
- return null;
12783
+ return result;
11769
12784
  }
11770
- var STATUS_RANK = {
11771
- backlog: 0,
11772
- planned: 1,
11773
- blocked: 1,
11774
- // lateral to planned sync can move to/from blocked freely
11775
- "in-progress": 2,
11776
- done: 3
11777
- };
11778
- function isRegression(from, to) {
11779
- return STATUS_RANK[to] < STATUS_RANK[from];
12785
+ async function syncFromExternal(roadmap, adapter, config, options) {
12786
+ const result = emptySyncResult();
12787
+ const forceSync = options?.forceSync ?? false;
12788
+ const featureByExternalId = /* @__PURE__ */ new Map();
12789
+ for (const milestone of roadmap.milestones) {
12790
+ for (const feature of milestone.features) {
12791
+ if (feature.externalId) {
12792
+ featureByExternalId.set(feature.externalId, feature);
12793
+ }
12794
+ }
12795
+ }
12796
+ if (featureByExternalId.size === 0) return result;
12797
+ const fetchResult = await adapter.fetchAllTickets();
12798
+ if (!fetchResult.ok) {
12799
+ result.errors.push({ featureOrId: "*", error: fetchResult.error });
12800
+ return result;
12801
+ }
12802
+ for (const ticketState of fetchResult.value) {
12803
+ const feature = featureByExternalId.get(ticketState.externalId);
12804
+ if (!feature) continue;
12805
+ if (ticketState.assignee !== feature.assignee) {
12806
+ result.assignmentChanges.push({
12807
+ feature: feature.name,
12808
+ from: feature.assignee,
12809
+ to: ticketState.assignee
12810
+ });
12811
+ feature.assignee = ticketState.assignee;
12812
+ }
12813
+ const resolvedStatus = resolveReverseStatus(ticketState.status, ticketState.labels, config);
12814
+ if (resolvedStatus && resolvedStatus !== feature.status) {
12815
+ const newStatus = resolvedStatus;
12816
+ if (!forceSync && isRegression(feature.status, newStatus)) {
12817
+ continue;
12818
+ }
12819
+ feature.status = newStatus;
12820
+ }
12821
+ }
12822
+ return result;
11780
12823
  }
11781
- function syncRoadmap(options) {
11782
- const { projectPath, roadmap, forceSync } = options;
12824
+ var syncMutex = Promise.resolve();
12825
+ async function fullSync(roadmapPath, adapter, config, options) {
12826
+ const previousSync = syncMutex;
12827
+ let releaseMutex;
12828
+ syncMutex = new Promise((resolve7) => {
12829
+ releaseMutex = resolve7;
12830
+ });
12831
+ await previousSync;
12832
+ try {
12833
+ const raw = fs20.readFileSync(roadmapPath, "utf-8");
12834
+ const parseResult = parseRoadmap(raw);
12835
+ if (!parseResult.ok) {
12836
+ return {
12837
+ ...emptySyncResult(),
12838
+ errors: [{ featureOrId: "*", error: parseResult.error }]
12839
+ };
12840
+ }
12841
+ const roadmap = parseResult.value;
12842
+ const pushResult = await syncToExternal(roadmap, adapter, config);
12843
+ const pullResult = await syncFromExternal(roadmap, adapter, config, options);
12844
+ fs20.writeFileSync(roadmapPath, serializeRoadmap(roadmap), "utf-8");
12845
+ return {
12846
+ created: pushResult.created,
12847
+ updated: pushResult.updated,
12848
+ assignmentChanges: pullResult.assignmentChanges,
12849
+ errors: [...pushResult.errors, ...pullResult.errors]
12850
+ };
12851
+ } finally {
12852
+ releaseMutex();
12853
+ }
12854
+ }
12855
+
12856
+ // src/roadmap/pilot-scoring.ts
12857
+ var PRIORITY_RANK = {
12858
+ P0: 0,
12859
+ P1: 1,
12860
+ P2: 2,
12861
+ P3: 3
12862
+ };
12863
+ var POSITION_WEIGHT = 0.5;
12864
+ var DEPENDENTS_WEIGHT = 0.3;
12865
+ var AFFINITY_WEIGHT = 0.2;
12866
+ function scoreRoadmapCandidates(roadmap, options) {
11783
12867
  const allFeatures = roadmap.milestones.flatMap((m) => m.features);
11784
- const changes = [];
12868
+ const allFeatureNames = new Set(allFeatures.map((f) => f.name.toLowerCase()));
12869
+ const doneFeatures = new Set(
12870
+ allFeatures.filter((f) => f.status === "done").map((f) => f.name.toLowerCase())
12871
+ );
12872
+ const dependentsCount = /* @__PURE__ */ new Map();
11785
12873
  for (const feature of allFeatures) {
11786
- const inferred = inferStatus(feature, projectPath, allFeatures);
11787
- if (inferred === null) continue;
11788
- if (inferred === feature.status) continue;
11789
- if (!forceSync && isRegression(feature.status, inferred)) continue;
11790
- changes.push({
11791
- feature: feature.name,
11792
- from: feature.status,
11793
- to: inferred
11794
- });
12874
+ for (const blocker of feature.blockedBy) {
12875
+ const key = blocker.toLowerCase();
12876
+ dependentsCount.set(key, (dependentsCount.get(key) ?? 0) + 1);
12877
+ }
11795
12878
  }
11796
- return (0, import_types20.Ok)(changes);
11797
- }
11798
- function applySyncChanges(roadmap, changes) {
11799
- for (const change of changes) {
11800
- for (const m of roadmap.milestones) {
11801
- const feature = m.features.find((f) => f.name.toLowerCase() === change.feature.toLowerCase());
11802
- if (feature) {
11803
- feature.status = change.to;
11804
- break;
12879
+ const maxDependents = Math.max(1, ...dependentsCount.values());
12880
+ const milestoneMap = /* @__PURE__ */ new Map();
12881
+ for (const ms of roadmap.milestones) {
12882
+ milestoneMap.set(
12883
+ ms.name,
12884
+ ms.features.map((f) => f.name.toLowerCase())
12885
+ );
12886
+ }
12887
+ const userCompletedFeatures = /* @__PURE__ */ new Set();
12888
+ if (options?.currentUser) {
12889
+ const user = options.currentUser.toLowerCase();
12890
+ for (const record of roadmap.assignmentHistory) {
12891
+ if (record.action === "completed" && record.assignee.toLowerCase() === user) {
12892
+ userCompletedFeatures.add(record.feature.toLowerCase());
12893
+ }
12894
+ }
12895
+ }
12896
+ let totalPositions = 0;
12897
+ for (const ms of roadmap.milestones) {
12898
+ totalPositions += ms.features.length;
12899
+ }
12900
+ totalPositions = Math.max(1, totalPositions);
12901
+ const candidates = [];
12902
+ let globalPosition = 0;
12903
+ for (const ms of roadmap.milestones) {
12904
+ for (let featureIdx = 0; featureIdx < ms.features.length; featureIdx++) {
12905
+ const feature = ms.features[featureIdx];
12906
+ globalPosition++;
12907
+ if (feature.status !== "planned" && feature.status !== "backlog") continue;
12908
+ const isBlocked = feature.blockedBy.some((blocker) => {
12909
+ const key = blocker.toLowerCase();
12910
+ return allFeatureNames.has(key) && !doneFeatures.has(key);
12911
+ });
12912
+ if (isBlocked) continue;
12913
+ const positionScore = 1 - (globalPosition - 1) / totalPositions;
12914
+ const deps = dependentsCount.get(feature.name.toLowerCase()) ?? 0;
12915
+ const dependentsScore = deps / maxDependents;
12916
+ let affinityScore = 0;
12917
+ if (userCompletedFeatures.size > 0) {
12918
+ const completedBlockers = feature.blockedBy.filter(
12919
+ (b) => userCompletedFeatures.has(b.toLowerCase())
12920
+ );
12921
+ if (completedBlockers.length > 0) {
12922
+ affinityScore = 1;
12923
+ } else {
12924
+ const siblings = milestoneMap.get(ms.name) ?? [];
12925
+ const completedSiblings = siblings.filter((s) => userCompletedFeatures.has(s));
12926
+ if (completedSiblings.length > 0) {
12927
+ affinityScore = 0.5;
12928
+ }
12929
+ }
11805
12930
  }
12931
+ const weightedScore = POSITION_WEIGHT * positionScore + DEPENDENTS_WEIGHT * dependentsScore + AFFINITY_WEIGHT * affinityScore;
12932
+ const priorityTier = feature.priority ? PRIORITY_RANK[feature.priority] : null;
12933
+ candidates.push({
12934
+ feature,
12935
+ milestone: ms.name,
12936
+ positionScore,
12937
+ dependentsScore,
12938
+ affinityScore,
12939
+ weightedScore,
12940
+ priorityTier
12941
+ });
11806
12942
  }
11807
12943
  }
11808
- roadmap.frontmatter.lastSynced = (/* @__PURE__ */ new Date()).toISOString();
12944
+ candidates.sort((a, b) => {
12945
+ if (a.priorityTier !== null && b.priorityTier === null) return -1;
12946
+ if (a.priorityTier === null && b.priorityTier !== null) return 1;
12947
+ if (a.priorityTier !== null && b.priorityTier !== null) {
12948
+ if (a.priorityTier !== b.priorityTier) return a.priorityTier - b.priorityTier;
12949
+ }
12950
+ return b.weightedScore - a.weightedScore;
12951
+ });
12952
+ return candidates;
12953
+ }
12954
+ function assignFeature(roadmap, feature, assignee, date) {
12955
+ if (feature.assignee === assignee) return;
12956
+ if (feature.assignee !== null) {
12957
+ roadmap.assignmentHistory.push({
12958
+ feature: feature.name,
12959
+ assignee: feature.assignee,
12960
+ action: "unassigned",
12961
+ date
12962
+ });
12963
+ }
12964
+ feature.assignee = assignee;
12965
+ roadmap.assignmentHistory.push({
12966
+ feature: feature.name,
12967
+ assignee,
12968
+ action: "assigned",
12969
+ date
12970
+ });
11809
12971
  }
11810
12972
 
11811
12973
  // src/interaction/types.ts
@@ -11838,17 +13000,18 @@ var EmitInteractionInputSchema = import_zod8.z.object({
11838
13000
  });
11839
13001
 
11840
13002
  // src/blueprint/scanner.ts
11841
- var fs20 = __toESM(require("fs/promises"));
13003
+ var fs21 = __toESM(require("fs/promises"));
11842
13004
  var path20 = __toESM(require("path"));
11843
13005
  var ProjectScanner = class {
11844
13006
  constructor(rootDir) {
11845
13007
  this.rootDir = rootDir;
11846
13008
  }
13009
+ rootDir;
11847
13010
  async scan() {
11848
13011
  let projectName = path20.basename(this.rootDir);
11849
13012
  try {
11850
13013
  const pkgPath = path20.join(this.rootDir, "package.json");
11851
- const pkgRaw = await fs20.readFile(pkgPath, "utf-8");
13014
+ const pkgRaw = await fs21.readFile(pkgPath, "utf-8");
11852
13015
  const pkg = JSON.parse(pkgRaw);
11853
13016
  if (pkg.name) projectName = pkg.name;
11854
13017
  } catch {
@@ -11889,7 +13052,7 @@ var ProjectScanner = class {
11889
13052
  };
11890
13053
 
11891
13054
  // src/blueprint/generator.ts
11892
- var fs21 = __toESM(require("fs/promises"));
13055
+ var fs22 = __toESM(require("fs/promises"));
11893
13056
  var path21 = __toESM(require("path"));
11894
13057
  var ejs = __toESM(require("ejs"));
11895
13058
 
@@ -11974,13 +13137,13 @@ var BlueprintGenerator = class {
11974
13137
  styles: STYLES,
11975
13138
  scripts: SCRIPTS
11976
13139
  });
11977
- await fs21.mkdir(options.outputDir, { recursive: true });
11978
- await fs21.writeFile(path21.join(options.outputDir, "index.html"), html);
13140
+ await fs22.mkdir(options.outputDir, { recursive: true });
13141
+ await fs22.writeFile(path21.join(options.outputDir, "index.html"), html);
11979
13142
  }
11980
13143
  };
11981
13144
 
11982
13145
  // src/update-checker.ts
11983
- var fs22 = __toESM(require("fs"));
13146
+ var fs23 = __toESM(require("fs"));
11984
13147
  var path22 = __toESM(require("path"));
11985
13148
  var os = __toESM(require("os"));
11986
13149
  var import_child_process3 = require("child_process");
@@ -11999,7 +13162,7 @@ function shouldRunCheck(state, intervalMs) {
11999
13162
  }
12000
13163
  function readCheckState() {
12001
13164
  try {
12002
- const raw = fs22.readFileSync(getStatePath(), "utf-8");
13165
+ const raw = fs23.readFileSync(getStatePath(), "utf-8");
12003
13166
  const parsed = JSON.parse(raw);
12004
13167
  if (typeof parsed === "object" && parsed !== null && "lastCheckTime" in parsed && typeof parsed.lastCheckTime === "number" && "currentVersion" in parsed && typeof parsed.currentVersion === "string") {
12005
13168
  const state = parsed;
@@ -12106,9 +13269,9 @@ async function resolveWasmPath(grammarName) {
12106
13269
  const { createRequire } = await import("module");
12107
13270
  const require2 = createRequire(import_meta.url ?? __filename);
12108
13271
  const pkgPath = require2.resolve("tree-sitter-wasms/package.json");
12109
- const path23 = await import("path");
12110
- const pkgDir = path23.dirname(pkgPath);
12111
- return path23.join(pkgDir, "out", `${grammarName}.wasm`);
13272
+ const path26 = await import("path");
13273
+ const pkgDir = path26.dirname(pkgPath);
13274
+ return path26.join(pkgDir, "out", `${grammarName}.wasm`);
12112
13275
  }
12113
13276
  async function loadLanguage(lang) {
12114
13277
  const grammarName = GRAMMAR_MAP[lang];
@@ -12472,6 +13635,489 @@ async function unfoldRange(filePath, startLine, endLine) {
12472
13635
  };
12473
13636
  }
12474
13637
 
13638
+ // src/pricing/pricing.ts
13639
+ var TOKENS_PER_MILLION = 1e6;
13640
+ function parseLiteLLMData(raw) {
13641
+ const dataset = /* @__PURE__ */ new Map();
13642
+ for (const [modelName, entry] of Object.entries(raw)) {
13643
+ if (modelName === "sample_spec") continue;
13644
+ if (entry.mode && entry.mode !== "chat") continue;
13645
+ const inputCost = entry.input_cost_per_token;
13646
+ const outputCost = entry.output_cost_per_token;
13647
+ if (inputCost == null || outputCost == null) continue;
13648
+ const pricing = {
13649
+ inputPer1M: inputCost * TOKENS_PER_MILLION,
13650
+ outputPer1M: outputCost * TOKENS_PER_MILLION
13651
+ };
13652
+ if (entry.cache_read_input_token_cost != null) {
13653
+ pricing.cacheReadPer1M = entry.cache_read_input_token_cost * TOKENS_PER_MILLION;
13654
+ }
13655
+ if (entry.cache_creation_input_token_cost != null) {
13656
+ pricing.cacheWritePer1M = entry.cache_creation_input_token_cost * TOKENS_PER_MILLION;
13657
+ }
13658
+ dataset.set(modelName, pricing);
13659
+ }
13660
+ return dataset;
13661
+ }
13662
+ function getModelPrice(model, dataset) {
13663
+ if (!model) {
13664
+ console.warn("[harness pricing] No model specified \u2014 cannot look up pricing.");
13665
+ return null;
13666
+ }
13667
+ const pricing = dataset.get(model);
13668
+ if (!pricing) {
13669
+ console.warn(
13670
+ `[harness pricing] No pricing data for model "${model}". Consider updating pricing data.`
13671
+ );
13672
+ return null;
13673
+ }
13674
+ return pricing;
13675
+ }
13676
+
13677
+ // src/pricing/cache.ts
13678
+ var fs24 = __toESM(require("fs/promises"));
13679
+ var path23 = __toESM(require("path"));
13680
+
13681
+ // src/pricing/fallback.json
13682
+ var fallback_default = {
13683
+ _generatedAt: "2026-03-31",
13684
+ _source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
13685
+ models: {
13686
+ "claude-opus-4-20250514": {
13687
+ inputPer1M: 15,
13688
+ outputPer1M: 75,
13689
+ cacheReadPer1M: 1.5,
13690
+ cacheWritePer1M: 18.75
13691
+ },
13692
+ "claude-sonnet-4-20250514": {
13693
+ inputPer1M: 3,
13694
+ outputPer1M: 15,
13695
+ cacheReadPer1M: 0.3,
13696
+ cacheWritePer1M: 3.75
13697
+ },
13698
+ "claude-3-5-haiku-20241022": {
13699
+ inputPer1M: 0.8,
13700
+ outputPer1M: 4,
13701
+ cacheReadPer1M: 0.08,
13702
+ cacheWritePer1M: 1
13703
+ },
13704
+ "gpt-4o": {
13705
+ inputPer1M: 2.5,
13706
+ outputPer1M: 10,
13707
+ cacheReadPer1M: 1.25
13708
+ },
13709
+ "gpt-4o-mini": {
13710
+ inputPer1M: 0.15,
13711
+ outputPer1M: 0.6,
13712
+ cacheReadPer1M: 0.075
13713
+ },
13714
+ "gemini-2.0-flash": {
13715
+ inputPer1M: 0.1,
13716
+ outputPer1M: 0.4,
13717
+ cacheReadPer1M: 0.025
13718
+ },
13719
+ "gemini-2.5-pro": {
13720
+ inputPer1M: 1.25,
13721
+ outputPer1M: 10,
13722
+ cacheReadPer1M: 0.3125
13723
+ }
13724
+ }
13725
+ };
13726
+
13727
+ // src/pricing/cache.ts
13728
+ var LITELLM_PRICING_URL = "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json";
13729
+ var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
13730
+ var STALENESS_WARNING_DAYS = 7;
13731
+ function getCachePath(projectRoot) {
13732
+ return path23.join(projectRoot, ".harness", "cache", "pricing.json");
13733
+ }
13734
+ function getStalenessMarkerPath(projectRoot) {
13735
+ return path23.join(projectRoot, ".harness", "cache", "staleness-marker.json");
13736
+ }
13737
+ async function readDiskCache(projectRoot) {
13738
+ try {
13739
+ const raw = await fs24.readFile(getCachePath(projectRoot), "utf-8");
13740
+ return JSON.parse(raw);
13741
+ } catch {
13742
+ return null;
13743
+ }
13744
+ }
13745
+ async function writeDiskCache(projectRoot, data) {
13746
+ const cachePath = getCachePath(projectRoot);
13747
+ await fs24.mkdir(path23.dirname(cachePath), { recursive: true });
13748
+ await fs24.writeFile(cachePath, JSON.stringify(data, null, 2));
13749
+ }
13750
+ async function fetchFromNetwork() {
13751
+ try {
13752
+ const response = await fetch(LITELLM_PRICING_URL);
13753
+ if (!response.ok) return null;
13754
+ const data = await response.json();
13755
+ if (typeof data !== "object" || data === null || Array.isArray(data)) return null;
13756
+ return {
13757
+ fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
13758
+ data
13759
+ };
13760
+ } catch {
13761
+ return null;
13762
+ }
13763
+ }
13764
+ function loadFallbackDataset() {
13765
+ const fb = fallback_default;
13766
+ const dataset = /* @__PURE__ */ new Map();
13767
+ for (const [model, pricing] of Object.entries(fb.models)) {
13768
+ dataset.set(model, pricing);
13769
+ }
13770
+ return dataset;
13771
+ }
13772
+ async function checkAndWarnStaleness(projectRoot) {
13773
+ const markerPath = getStalenessMarkerPath(projectRoot);
13774
+ try {
13775
+ const raw = await fs24.readFile(markerPath, "utf-8");
13776
+ const marker = JSON.parse(raw);
13777
+ const firstUse = new Date(marker.firstFallbackUse).getTime();
13778
+ const now = Date.now();
13779
+ const daysSinceFirstUse = (now - firstUse) / (24 * 60 * 60 * 1e3);
13780
+ if (daysSinceFirstUse > STALENESS_WARNING_DAYS) {
13781
+ console.warn(
13782
+ `[harness pricing] Pricing data is stale \u2014 using bundled fallback for ${Math.floor(daysSinceFirstUse)} days. Connect to the internet to refresh pricing data.`
13783
+ );
13784
+ }
13785
+ } catch {
13786
+ try {
13787
+ await fs24.mkdir(path23.dirname(markerPath), { recursive: true });
13788
+ await fs24.writeFile(
13789
+ markerPath,
13790
+ JSON.stringify({ firstFallbackUse: (/* @__PURE__ */ new Date()).toISOString() })
13791
+ );
13792
+ } catch {
13793
+ }
13794
+ }
13795
+ }
13796
+ async function clearStalenessMarker(projectRoot) {
13797
+ try {
13798
+ await fs24.unlink(getStalenessMarkerPath(projectRoot));
13799
+ } catch {
13800
+ }
13801
+ }
13802
+ async function loadPricingData(projectRoot) {
13803
+ const cache = await readDiskCache(projectRoot);
13804
+ if (cache) {
13805
+ const cacheAge = Date.now() - new Date(cache.fetchedAt).getTime();
13806
+ if (cacheAge < CACHE_TTL_MS) {
13807
+ await clearStalenessMarker(projectRoot);
13808
+ return parseLiteLLMData(cache.data);
13809
+ }
13810
+ }
13811
+ const fetched = await fetchFromNetwork();
13812
+ if (fetched) {
13813
+ await writeDiskCache(projectRoot, fetched);
13814
+ await clearStalenessMarker(projectRoot);
13815
+ return parseLiteLLMData(fetched.data);
13816
+ }
13817
+ if (cache) {
13818
+ return parseLiteLLMData(cache.data);
13819
+ }
13820
+ await checkAndWarnStaleness(projectRoot);
13821
+ return loadFallbackDataset();
13822
+ }
13823
+
13824
+ // src/pricing/calculator.ts
13825
+ var MICRODOLLARS_PER_DOLLAR = 1e6;
13826
+ var TOKENS_PER_MILLION2 = 1e6;
13827
+ function calculateCost(record, dataset) {
13828
+ if (!record.model) return null;
13829
+ const pricing = getModelPrice(record.model, dataset);
13830
+ if (!pricing) return null;
13831
+ let costUSD = 0;
13832
+ costUSD += record.tokens.inputTokens / TOKENS_PER_MILLION2 * pricing.inputPer1M;
13833
+ costUSD += record.tokens.outputTokens / TOKENS_PER_MILLION2 * pricing.outputPer1M;
13834
+ if (record.cacheReadTokens != null && pricing.cacheReadPer1M != null) {
13835
+ costUSD += record.cacheReadTokens / TOKENS_PER_MILLION2 * pricing.cacheReadPer1M;
13836
+ }
13837
+ if (record.cacheCreationTokens != null && pricing.cacheWritePer1M != null) {
13838
+ costUSD += record.cacheCreationTokens / TOKENS_PER_MILLION2 * pricing.cacheWritePer1M;
13839
+ }
13840
+ return Math.round(costUSD * MICRODOLLARS_PER_DOLLAR);
13841
+ }
13842
+
13843
+ // src/usage/aggregator.ts
13844
+ function aggregateBySession(records) {
13845
+ if (records.length === 0) return [];
13846
+ const sessionMap = /* @__PURE__ */ new Map();
13847
+ for (const record of records) {
13848
+ const tagged = record;
13849
+ const id = record.sessionId;
13850
+ if (!sessionMap.has(id)) {
13851
+ sessionMap.set(id, { harnessRecords: [], ccRecords: [], allRecords: [] });
13852
+ }
13853
+ const bucket = sessionMap.get(id);
13854
+ if (tagged._source === "claude-code") {
13855
+ bucket.ccRecords.push(tagged);
13856
+ } else {
13857
+ bucket.harnessRecords.push(tagged);
13858
+ }
13859
+ bucket.allRecords.push(tagged);
13860
+ }
13861
+ const results = [];
13862
+ for (const [sessionId, bucket] of sessionMap) {
13863
+ const hasHarness = bucket.harnessRecords.length > 0;
13864
+ const hasCC = bucket.ccRecords.length > 0;
13865
+ const isMerged = hasHarness && hasCC;
13866
+ const tokenSource = hasHarness ? bucket.harnessRecords : bucket.ccRecords;
13867
+ const tokens = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
13868
+ let cacheCreation;
13869
+ let cacheRead;
13870
+ let costMicroUSD = 0;
13871
+ let model;
13872
+ for (const r of tokenSource) {
13873
+ tokens.inputTokens += r.tokens.inputTokens;
13874
+ tokens.outputTokens += r.tokens.outputTokens;
13875
+ tokens.totalTokens += r.tokens.totalTokens;
13876
+ if (r.cacheCreationTokens != null) {
13877
+ cacheCreation = (cacheCreation ?? 0) + r.cacheCreationTokens;
13878
+ }
13879
+ if (r.cacheReadTokens != null) {
13880
+ cacheRead = (cacheRead ?? 0) + r.cacheReadTokens;
13881
+ }
13882
+ if (r.costMicroUSD != null && costMicroUSD != null) {
13883
+ costMicroUSD += r.costMicroUSD;
13884
+ } else if (r.costMicroUSD == null) {
13885
+ costMicroUSD = null;
13886
+ }
13887
+ if (!model && r.model) {
13888
+ model = r.model;
13889
+ }
13890
+ }
13891
+ if (!model && hasCC) {
13892
+ for (const r of bucket.ccRecords) {
13893
+ if (r.model) {
13894
+ model = r.model;
13895
+ break;
13896
+ }
13897
+ }
13898
+ }
13899
+ const timestamps = bucket.allRecords.map((r) => r.timestamp).sort();
13900
+ const source = isMerged ? "merged" : hasCC ? "claude-code" : "harness";
13901
+ const session = {
13902
+ sessionId,
13903
+ firstTimestamp: timestamps[0] ?? "",
13904
+ lastTimestamp: timestamps[timestamps.length - 1] ?? "",
13905
+ tokens,
13906
+ costMicroUSD,
13907
+ source
13908
+ };
13909
+ if (model) session.model = model;
13910
+ if (cacheCreation != null) session.cacheCreationTokens = cacheCreation;
13911
+ if (cacheRead != null) session.cacheReadTokens = cacheRead;
13912
+ results.push(session);
13913
+ }
13914
+ results.sort((a, b) => b.firstTimestamp.localeCompare(a.firstTimestamp));
13915
+ return results;
13916
+ }
13917
+ function aggregateByDay(records) {
13918
+ if (records.length === 0) return [];
13919
+ const dayMap = /* @__PURE__ */ new Map();
13920
+ for (const record of records) {
13921
+ const date = record.timestamp.slice(0, 10);
13922
+ if (!dayMap.has(date)) {
13923
+ dayMap.set(date, {
13924
+ sessions: /* @__PURE__ */ new Set(),
13925
+ tokens: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
13926
+ costMicroUSD: 0,
13927
+ models: /* @__PURE__ */ new Set()
13928
+ });
13929
+ }
13930
+ const day = dayMap.get(date);
13931
+ day.sessions.add(record.sessionId);
13932
+ day.tokens.inputTokens += record.tokens.inputTokens;
13933
+ day.tokens.outputTokens += record.tokens.outputTokens;
13934
+ day.tokens.totalTokens += record.tokens.totalTokens;
13935
+ if (record.cacheCreationTokens != null) {
13936
+ day.cacheCreation = (day.cacheCreation ?? 0) + record.cacheCreationTokens;
13937
+ }
13938
+ if (record.cacheReadTokens != null) {
13939
+ day.cacheRead = (day.cacheRead ?? 0) + record.cacheReadTokens;
13940
+ }
13941
+ if (record.costMicroUSD != null && day.costMicroUSD != null) {
13942
+ day.costMicroUSD += record.costMicroUSD;
13943
+ } else if (record.costMicroUSD == null) {
13944
+ day.costMicroUSD = null;
13945
+ }
13946
+ if (record.model) {
13947
+ day.models.add(record.model);
13948
+ }
13949
+ }
13950
+ const results = [];
13951
+ for (const [date, day] of dayMap) {
13952
+ const entry = {
13953
+ date,
13954
+ sessionCount: day.sessions.size,
13955
+ tokens: day.tokens,
13956
+ costMicroUSD: day.costMicroUSD,
13957
+ models: Array.from(day.models).sort()
13958
+ };
13959
+ if (day.cacheCreation != null) entry.cacheCreationTokens = day.cacheCreation;
13960
+ if (day.cacheRead != null) entry.cacheReadTokens = day.cacheRead;
13961
+ results.push(entry);
13962
+ }
13963
+ results.sort((a, b) => b.date.localeCompare(a.date));
13964
+ return results;
13965
+ }
13966
+
13967
+ // src/usage/jsonl-reader.ts
13968
+ var fs25 = __toESM(require("fs"));
13969
+ var path24 = __toESM(require("path"));
13970
+ function parseLine(line, lineNumber) {
13971
+ let entry;
13972
+ try {
13973
+ entry = JSON.parse(line);
13974
+ } catch {
13975
+ console.warn(`[harness usage] Skipping malformed JSONL line ${lineNumber}`);
13976
+ return null;
13977
+ }
13978
+ const tokenUsage = entry.token_usage;
13979
+ if (!tokenUsage || typeof tokenUsage !== "object") {
13980
+ console.warn(
13981
+ `[harness usage] Skipping malformed JSONL line ${lineNumber}: missing token_usage`
13982
+ );
13983
+ return null;
13984
+ }
13985
+ const inputTokens = tokenUsage.input_tokens ?? 0;
13986
+ const outputTokens = tokenUsage.output_tokens ?? 0;
13987
+ const record = {
13988
+ sessionId: entry.session_id ?? "unknown",
13989
+ timestamp: entry.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
13990
+ tokens: {
13991
+ inputTokens,
13992
+ outputTokens,
13993
+ totalTokens: inputTokens + outputTokens
13994
+ }
13995
+ };
13996
+ if (entry.cache_creation_tokens != null) {
13997
+ record.cacheCreationTokens = entry.cache_creation_tokens;
13998
+ }
13999
+ if (entry.cache_read_tokens != null) {
14000
+ record.cacheReadTokens = entry.cache_read_tokens;
14001
+ }
14002
+ if (entry.model != null) {
14003
+ record.model = entry.model;
14004
+ }
14005
+ return record;
14006
+ }
14007
+ function readCostRecords(projectRoot) {
14008
+ const costsFile = path24.join(projectRoot, ".harness", "metrics", "costs.jsonl");
14009
+ let raw;
14010
+ try {
14011
+ raw = fs25.readFileSync(costsFile, "utf-8");
14012
+ } catch {
14013
+ return [];
14014
+ }
14015
+ const records = [];
14016
+ const lines = raw.split("\n");
14017
+ for (let i = 0; i < lines.length; i++) {
14018
+ const line = lines[i]?.trim();
14019
+ if (!line) continue;
14020
+ const record = parseLine(line, i + 1);
14021
+ if (record) {
14022
+ records.push(record);
14023
+ }
14024
+ }
14025
+ return records;
14026
+ }
14027
+
14028
+ // src/usage/cc-parser.ts
14029
+ var fs26 = __toESM(require("fs"));
14030
+ var path25 = __toESM(require("path"));
14031
+ var os2 = __toESM(require("os"));
14032
+ function extractUsage(entry) {
14033
+ if (entry.type !== "assistant") return null;
14034
+ const message = entry.message;
14035
+ if (!message || typeof message !== "object") return null;
14036
+ const usage = message.usage;
14037
+ return usage && typeof usage === "object" && !Array.isArray(usage) ? usage : null;
14038
+ }
14039
+ function buildRecord(entry, usage) {
14040
+ const inputTokens = Number(usage.input_tokens) || 0;
14041
+ const outputTokens = Number(usage.output_tokens) || 0;
14042
+ const message = entry.message;
14043
+ const record = {
14044
+ sessionId: entry.sessionId ?? "unknown",
14045
+ timestamp: entry.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
14046
+ tokens: { inputTokens, outputTokens, totalTokens: inputTokens + outputTokens },
14047
+ _source: "claude-code"
14048
+ };
14049
+ const model = message.model;
14050
+ if (model) record.model = model;
14051
+ const cacheCreate = usage.cache_creation_input_tokens;
14052
+ const cacheRead = usage.cache_read_input_tokens;
14053
+ if (typeof cacheCreate === "number" && cacheCreate > 0) record.cacheCreationTokens = cacheCreate;
14054
+ if (typeof cacheRead === "number" && cacheRead > 0) record.cacheReadTokens = cacheRead;
14055
+ return record;
14056
+ }
14057
+ function parseCCLine(line, filePath, lineNumber) {
14058
+ let entry;
14059
+ try {
14060
+ entry = JSON.parse(line);
14061
+ } catch {
14062
+ console.warn(
14063
+ `[harness usage] Skipping malformed CC JSONL line ${lineNumber} in ${path25.basename(filePath)}`
14064
+ );
14065
+ return null;
14066
+ }
14067
+ const usage = extractUsage(entry);
14068
+ if (!usage) return null;
14069
+ return {
14070
+ record: buildRecord(entry, usage),
14071
+ requestId: entry.requestId ?? null
14072
+ };
14073
+ }
14074
+ function readCCFile(filePath) {
14075
+ let raw;
14076
+ try {
14077
+ raw = fs26.readFileSync(filePath, "utf-8");
14078
+ } catch {
14079
+ return [];
14080
+ }
14081
+ const byRequestId = /* @__PURE__ */ new Map();
14082
+ const noRequestId = [];
14083
+ const lines = raw.split("\n");
14084
+ for (let i = 0; i < lines.length; i++) {
14085
+ const line = lines[i]?.trim();
14086
+ if (!line) continue;
14087
+ const parsed = parseCCLine(line, filePath, i + 1);
14088
+ if (!parsed) continue;
14089
+ if (parsed.requestId) {
14090
+ byRequestId.set(parsed.requestId, parsed.record);
14091
+ } else {
14092
+ noRequestId.push(parsed.record);
14093
+ }
14094
+ }
14095
+ return [...byRequestId.values(), ...noRequestId];
14096
+ }
14097
+ function parseCCRecords() {
14098
+ const homeDir = process.env.HOME ?? os2.homedir();
14099
+ const projectsDir = path25.join(homeDir, ".claude", "projects");
14100
+ let projectDirs;
14101
+ try {
14102
+ projectDirs = fs26.readdirSync(projectsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => path25.join(projectsDir, d.name));
14103
+ } catch {
14104
+ return [];
14105
+ }
14106
+ const records = [];
14107
+ for (const dir of projectDirs) {
14108
+ let files;
14109
+ try {
14110
+ files = fs26.readdirSync(dir).filter((f) => f.endsWith(".jsonl")).map((f) => path25.join(dir, f));
14111
+ } catch {
14112
+ continue;
14113
+ }
14114
+ for (const file of files) {
14115
+ records.push(...readCCFile(file));
14116
+ }
14117
+ }
14118
+ return records;
14119
+ }
14120
+
12475
14121
  // src/index.ts
12476
14122
  var VERSION = "0.15.0";
12477
14123
  // Annotate the CommonJS export names for ESM import in node:
@@ -12490,6 +14136,7 @@ var VERSION = "0.15.0";
12490
14136
  BlueprintGenerator,
12491
14137
  BundleConstraintsSchema,
12492
14138
  BundleSchema,
14139
+ CACHE_TTL_MS,
12493
14140
  COMPLIANCE_DESCRIPTOR,
12494
14141
  CategoryBaselineSchema,
12495
14142
  CategoryRegressionSchema,
@@ -12507,6 +14154,7 @@ var VERSION = "0.15.0";
12507
14154
  DEFAULT_SECURITY_CONFIG,
12508
14155
  DEFAULT_STATE,
12509
14156
  DEFAULT_STREAM_INDEX,
14157
+ DESTRUCTIVE_BASH,
12510
14158
  DepDepthCollector,
12511
14159
  EXTENSION_MAP,
12512
14160
  EmitInteractionInputSchema,
@@ -12518,9 +14166,11 @@ var VERSION = "0.15.0";
12518
14166
  ForbiddenImportCollector,
12519
14167
  GateConfigSchema,
12520
14168
  GateResultSchema,
14169
+ GitHubIssuesSyncAdapter,
12521
14170
  HandoffSchema,
12522
14171
  HarnessStateSchema,
12523
14172
  InteractionTypeSchema,
14173
+ LITELLM_PRICING_URL,
12524
14174
  LayerViolationCollector,
12525
14175
  LockfilePackageSchema,
12526
14176
  LockfileSchema,
@@ -12537,6 +14187,8 @@ var VERSION = "0.15.0";
12537
14187
  RegressionDetector,
12538
14188
  RuleRegistry,
12539
14189
  SECURITY_DESCRIPTOR,
14190
+ STALENESS_WARNING_DAYS,
14191
+ STATUS_RANK,
12540
14192
  SecurityConfigSchema,
12541
14193
  SecurityScanner,
12542
14194
  SharableBoundaryConfigSchema,
@@ -12553,6 +14205,8 @@ var VERSION = "0.15.0";
12553
14205
  ViolationSchema,
12554
14206
  addProvenance,
12555
14207
  agentConfigRules,
14208
+ aggregateByDay,
14209
+ aggregateBySession,
12556
14210
  analyzeDiff,
12557
14211
  analyzeLearningPatterns,
12558
14212
  appendFailure,
@@ -12568,16 +14222,22 @@ var VERSION = "0.15.0";
12568
14222
  archiveLearnings,
12569
14223
  archiveSession,
12570
14224
  archiveStream,
14225
+ assignFeature,
12571
14226
  buildDependencyGraph,
12572
14227
  buildExclusionSet,
12573
14228
  buildSnapshot,
14229
+ calculateCost,
12574
14230
  checkDocCoverage,
12575
14231
  checkEligibility,
12576
14232
  checkEvidenceCoverage,
14233
+ checkTaint,
12577
14234
  classifyFinding,
12578
14235
  clearEventHashCache,
12579
14236
  clearFailuresCache,
12580
14237
  clearLearningsCache,
14238
+ clearTaint,
14239
+ computeOverallSeverity,
14240
+ computeScanExitCode,
12581
14241
  configureFeedback,
12582
14242
  constraintRuleId,
12583
14243
  contextBudget,
@@ -12627,40 +14287,55 @@ var VERSION = "0.15.0";
12627
14287
  formatGitHubSummary,
12628
14288
  formatOutline,
12629
14289
  formatTerminalOutput,
14290
+ fullSync,
12630
14291
  generateAgentsMap,
12631
14292
  generateSuggestions,
12632
14293
  getActionEmitter,
12633
14294
  getExitCode,
12634
14295
  getFeedbackConfig,
14296
+ getInjectionPatterns,
14297
+ getModelPrice,
12635
14298
  getOutline,
12636
14299
  getParser,
12637
14300
  getPhaseCategories,
12638
14301
  getStreamForBranch,
14302
+ getTaintFilePath,
12639
14303
  getUpdateNotification,
12640
14304
  goRules,
12641
14305
  injectionRules,
14306
+ insecureDefaultsRules,
14307
+ isDuplicateFinding,
14308
+ isRegression,
12642
14309
  isSmallSuggestion,
12643
14310
  isUpdateCheckEnabled,
12644
14311
  listActiveSessions,
12645
14312
  listStreams,
14313
+ listTaintedSessions,
12646
14314
  loadBudgetedLearnings,
12647
14315
  loadEvents,
12648
14316
  loadFailures,
12649
14317
  loadHandoff,
12650
14318
  loadIndexEntries,
14319
+ loadPricingData,
12651
14320
  loadRelevantLearnings,
12652
14321
  loadSessionSummary,
12653
14322
  loadState,
12654
14323
  loadStreamIndex,
12655
14324
  logAgentAction,
14325
+ mapInjectionFindings,
14326
+ mapSecurityFindings,
14327
+ mapSecuritySeverity,
12656
14328
  mcpRules,
12657
14329
  migrateToStreams,
12658
14330
  networkRules,
12659
14331
  nodeRules,
14332
+ parseCCRecords,
12660
14333
  parseDateFromEntry,
12661
14334
  parseDiff,
12662
14335
  parseFile,
12663
14336
  parseFrontmatter,
14337
+ parseHarnessIgnore,
14338
+ parseLiteLLMData,
12664
14339
  parseManifest,
12665
14340
  parseRoadmap,
12666
14341
  parseSecurityConfig,
@@ -12671,9 +14346,11 @@ var VERSION = "0.15.0";
12671
14346
  pruneLearnings,
12672
14347
  reactRules,
12673
14348
  readCheckState,
14349
+ readCostRecords,
12674
14350
  readLockfile,
12675
14351
  readSessionSection,
12676
14352
  readSessionSections,
14353
+ readTaint,
12677
14354
  removeContributions,
12678
14355
  removeProvenance,
12679
14356
  requestMultiplePeerReviews,
@@ -12682,6 +14359,7 @@ var VERSION = "0.15.0";
12682
14359
  resetParserCache,
12683
14360
  resolveFileToLayer,
12684
14361
  resolveModelTier,
14362
+ resolveReverseStatus,
12685
14363
  resolveRuleSeverity,
12686
14364
  resolveSessionDir,
12687
14365
  resolveStreamPath,
@@ -12700,15 +14378,20 @@ var VERSION = "0.15.0";
12700
14378
  saveHandoff,
12701
14379
  saveState,
12702
14380
  saveStreamIndex,
14381
+ scanForInjection,
12703
14382
  scopeContext,
14383
+ scoreRoadmapCandidates,
12704
14384
  searchSymbols,
12705
14385
  secretRules,
12706
14386
  serializeRoadmap,
12707
14387
  setActiveStream,
14388
+ sharpEdgesRules,
12708
14389
  shouldRunCheck,
12709
14390
  spawnBackgroundCheck,
12710
14391
  syncConstraintNodes,
14392
+ syncFromExternal,
12711
14393
  syncRoadmap,
14394
+ syncToExternal,
12712
14395
  tagUncitedFindings,
12713
14396
  touchStream,
12714
14397
  trackAction,
@@ -12729,6 +14412,7 @@ var VERSION = "0.15.0";
12729
14412
  writeConfig,
12730
14413
  writeLockfile,
12731
14414
  writeSessionSummary,
14415
+ writeTaint,
12732
14416
  xssRules,
12733
14417
  ...require("@harness-engineering/types")
12734
14418
  });