@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/README.md +1 -0
- package/dist/architecture/matchers.d.mts +1 -1
- package/dist/architecture/matchers.d.ts +1 -1
- package/dist/index.d.mts +467 -85
- package/dist/index.d.ts +467 -85
- package/dist/index.js +1834 -150
- package/dist/index.mjs +1744 -98
- package/dist/{matchers-Dj1t5vpg.d.mts → matchers-D20x48U9.d.mts} +46 -46
- package/dist/{matchers-Dj1t5vpg.d.ts → matchers-D20x48U9.d.ts} +46 -46
- package/package.json +14 -14
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(
|
|
348
|
+
async function fileExists(path26) {
|
|
311
349
|
try {
|
|
312
|
-
await accessAsync(
|
|
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(
|
|
356
|
+
async function readFileContent(path26) {
|
|
319
357
|
try {
|
|
320
|
-
const content = await readFileAsync(
|
|
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
|
|
372
|
-
const pathDisplay =
|
|
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 "${
|
|
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(
|
|
596
|
-
return
|
|
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(
|
|
602
|
-
const contentResult = await readFileContent(
|
|
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:
|
|
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)(
|
|
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(
|
|
757
|
-
const targetName = (0, import_path3.basename)(
|
|
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 "${
|
|
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
|
|
1372
|
-
return
|
|
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(
|
|
2005
|
-
const contentResult = await readFileContent(
|
|
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: ${
|
|
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:
|
|
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 ${
|
|
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(
|
|
2215
|
-
const contentResult = await readFileContent(
|
|
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: ${
|
|
2221
|
-
{ file:
|
|
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 =
|
|
2265
|
+
const type = path26.endsWith(".md") ? "markdown" : "text";
|
|
2228
2266
|
return (0, import_types.Ok)({
|
|
2229
|
-
path:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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 :
|
|
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
|
-
|
|
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
|
-
|
|
11732
|
-
|
|
11733
|
-
|
|
11734
|
-
|
|
11735
|
-
|
|
11736
|
-
|
|
11737
|
-
|
|
11738
|
-
|
|
11739
|
-
|
|
11740
|
-
|
|
11741
|
-
|
|
11742
|
-
|
|
11743
|
-
|
|
11744
|
-
|
|
11745
|
-
|
|
11746
|
-
|
|
11747
|
-
|
|
11748
|
-
|
|
11749
|
-
|
|
11750
|
-
|
|
11751
|
-
|
|
11752
|
-
|
|
11753
|
-
|
|
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
|
-
|
|
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
|
-
|
|
11771
|
-
|
|
11772
|
-
|
|
11773
|
-
|
|
11774
|
-
|
|
11775
|
-
|
|
11776
|
-
|
|
11777
|
-
|
|
11778
|
-
|
|
11779
|
-
|
|
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
|
-
|
|
11782
|
-
|
|
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
|
|
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
|
|
11787
|
-
|
|
11788
|
-
|
|
11789
|
-
|
|
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
|
-
|
|
11797
|
-
|
|
11798
|
-
|
|
11799
|
-
|
|
11800
|
-
|
|
11801
|
-
|
|
11802
|
-
|
|
11803
|
-
|
|
11804
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
11978
|
-
await
|
|
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
|
|
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 =
|
|
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
|
|
12110
|
-
const pkgDir =
|
|
12111
|
-
return
|
|
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
|
});
|