@harness-engineering/cli 2.8.0 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/skills/claude-code/harness-audit-harness-strength/SKILL.md +188 -0
- package/dist/agents/skills/claude-code/harness-audit-harness-strength/skill.yaml +54 -0
- package/dist/agents/skills/claude-code/harness-autopilot/SKILL.md +2 -0
- package/dist/agents/skills/claude-code/harness-brainstorming/SKILL.md +28 -14
- package/dist/agents/skills/claude-code/harness-execution/SKILL.md +1 -1
- package/dist/agents/skills/claude-code/harness-git-workflow/SKILL.md +2 -0
- package/dist/agents/skills/claude-code/harness-ideate/SKILL.md +2 -0
- package/dist/agents/skills/claude-code/harness-knowledge-pipeline/SKILL.md +2 -0
- package/dist/agents/skills/claude-code/harness-roadmap/SKILL.md +62 -4
- package/dist/agents/skills/claude-code/harness-roadmap-pilot/SKILL.md +2 -0
- package/dist/agents/skills/claude-code/harness-test-advisor/SKILL.md +80 -2
- package/dist/agents/skills/claude-code/harness-test-advisor/skill.yaml +4 -1
- package/dist/agents/skills/claude-code/initialize-harness-project/SKILL.md +27 -25
- package/dist/agents/skills/claude-code/initialize-harness-project/skill.yaml +1 -0
- package/dist/agents/skills/claude-code/outcome-eval/SKILL.md +97 -0
- package/dist/agents/skills/claude-code/outcome-eval/skill.yaml +51 -0
- package/dist/agents/skills/codex/harness-audit-harness-strength/SKILL.md +188 -0
- package/dist/agents/skills/codex/harness-audit-harness-strength/skill.yaml +54 -0
- package/dist/agents/skills/codex/harness-autopilot/SKILL.md +2 -0
- package/dist/agents/skills/codex/harness-brainstorming/SKILL.md +28 -14
- package/dist/agents/skills/codex/harness-execution/SKILL.md +1 -1
- package/dist/agents/skills/codex/harness-git-workflow/SKILL.md +2 -0
- package/dist/agents/skills/codex/harness-ideate/SKILL.md +2 -0
- package/dist/agents/skills/codex/harness-knowledge-pipeline/SKILL.md +2 -0
- package/dist/agents/skills/codex/harness-roadmap/SKILL.md +62 -4
- package/dist/agents/skills/codex/harness-roadmap-pilot/SKILL.md +2 -0
- package/dist/agents/skills/codex/harness-test-advisor/SKILL.md +80 -2
- package/dist/agents/skills/codex/harness-test-advisor/skill.yaml +4 -1
- package/dist/agents/skills/codex/initialize-harness-project/SKILL.md +27 -25
- package/dist/agents/skills/codex/initialize-harness-project/skill.yaml +1 -0
- package/dist/agents/skills/codex/outcome-eval/SKILL.md +97 -0
- package/dist/agents/skills/codex/outcome-eval/skill.yaml +51 -0
- package/dist/agents/skills/cursor/harness-audit-harness-strength/SKILL.md +188 -0
- package/dist/agents/skills/cursor/harness-audit-harness-strength/skill.yaml +54 -0
- package/dist/agents/skills/cursor/harness-autopilot/SKILL.md +2 -0
- package/dist/agents/skills/cursor/harness-brainstorming/SKILL.md +28 -14
- package/dist/agents/skills/cursor/harness-execution/SKILL.md +1 -1
- package/dist/agents/skills/cursor/harness-git-workflow/SKILL.md +2 -0
- package/dist/agents/skills/cursor/harness-ideate/SKILL.md +2 -0
- package/dist/agents/skills/cursor/harness-knowledge-pipeline/SKILL.md +2 -0
- package/dist/agents/skills/cursor/harness-roadmap/SKILL.md +62 -4
- package/dist/agents/skills/cursor/harness-roadmap-pilot/SKILL.md +2 -0
- package/dist/agents/skills/cursor/harness-test-advisor/SKILL.md +80 -2
- package/dist/agents/skills/cursor/harness-test-advisor/skill.yaml +4 -1
- package/dist/agents/skills/cursor/initialize-harness-project/SKILL.md +27 -25
- package/dist/agents/skills/cursor/initialize-harness-project/skill.yaml +1 -0
- package/dist/agents/skills/cursor/outcome-eval/SKILL.md +97 -0
- package/dist/agents/skills/cursor/outcome-eval/skill.yaml +51 -0
- package/dist/agents/skills/gemini-cli/harness-audit-harness-strength/SKILL.md +188 -0
- package/dist/agents/skills/gemini-cli/harness-audit-harness-strength/skill.yaml +54 -0
- package/dist/agents/skills/gemini-cli/harness-autopilot/SKILL.md +2 -0
- package/dist/agents/skills/gemini-cli/harness-brainstorming/SKILL.md +28 -14
- package/dist/agents/skills/gemini-cli/harness-execution/SKILL.md +1 -1
- package/dist/agents/skills/gemini-cli/harness-git-workflow/SKILL.md +2 -0
- package/dist/agents/skills/gemini-cli/harness-ideate/SKILL.md +2 -0
- package/dist/agents/skills/gemini-cli/harness-knowledge-pipeline/SKILL.md +2 -0
- package/dist/agents/skills/gemini-cli/harness-roadmap/SKILL.md +62 -4
- package/dist/agents/skills/gemini-cli/harness-roadmap-pilot/SKILL.md +2 -0
- package/dist/agents/skills/gemini-cli/harness-test-advisor/SKILL.md +80 -2
- package/dist/agents/skills/gemini-cli/harness-test-advisor/skill.yaml +4 -1
- package/dist/agents/skills/gemini-cli/initialize-harness-project/SKILL.md +27 -25
- package/dist/agents/skills/gemini-cli/initialize-harness-project/skill.yaml +1 -0
- package/dist/agents/skills/gemini-cli/outcome-eval/SKILL.md +97 -0
- package/dist/agents/skills/gemini-cli/outcome-eval/skill.yaml +51 -0
- package/dist/agents/skills/tests/harness-test-advisor.test.ts +94 -0
- package/dist/{agents-md-WMJO5HSM.js → agents-md-XOJE5ETA.js} +2 -2
- package/dist/{architecture-EVYVGPRZ.js → architecture-JEZPJ725.js} +3 -3
- package/dist/{assess-project-KZG563SO.js → assess-project-RHXOQNMM.js} +1 -1
- package/dist/bin/harness-mcp.js +13 -13
- package/dist/bin/harness.js +22 -22
- package/dist/{check-phase-gate-MK364VXS.js → check-phase-gate-GNDOMS35.js} +3 -3
- package/dist/{chunk-VBNJAKUH.js → chunk-3ISHDWO7.js} +1 -1
- package/dist/{chunk-UJFNIWC7.js → chunk-527G27VI.js} +2 -2
- package/dist/{chunk-5XL5QJ5B.js → chunk-6WQCLCBO.js} +1 -1
- package/dist/{chunk-UXPWSV3B.js → chunk-BYVT5LVO.js} +3 -3
- package/dist/{chunk-HRSE6IQD.js → chunk-DE5U6KOL.js} +1 -1
- package/dist/{chunk-B7KMTHN3.js → chunk-GGKRA7A7.js} +1 -1
- package/dist/{chunk-STESM73J.js → chunk-GSP2XIVS.js} +1 -1
- package/dist/{chunk-KBCPELBC.js → chunk-HEIMJJI4.js} +2 -2
- package/dist/{chunk-KI6PHH6Q.js → chunk-HWTUUQOW.js} +3 -3
- package/dist/{chunk-2SNRCAC3.js → chunk-KAJOS3BA.js} +2 -2
- package/dist/{chunk-C7J55C6E.js → chunk-MGUPSE6D.js} +5 -5
- package/dist/{chunk-GOHWCHCS.js → chunk-N24HCQA4.js} +2243 -776
- package/dist/{chunk-EEFRPEWT.js → chunk-NWLGL3IR.js} +1 -1
- package/dist/{chunk-UUO4K7NX.js → chunk-ONCPJGHY.js} +6 -6
- package/dist/{chunk-X4EFYVCZ.js → chunk-PC2R5RUJ.js} +1 -1
- package/dist/{chunk-7EQAZKJ5.js → chunk-QBC7DI4Q.js} +1 -1
- package/dist/{chunk-UBZQE3JN.js → chunk-UX3PQKVZ.js} +7 -2
- package/dist/{chunk-CAQPSFPT.js → chunk-WWEVS7RO.js} +453 -91
- package/dist/{chunk-4BDOSCNY.js → chunk-XFU6SB2Q.js} +1015 -648
- package/dist/{chunk-LADPYELQ.js → chunk-XX4OLNWQ.js} +2 -2
- package/dist/{chunk-FZ5MBXEG.js → chunk-YQSYCBAH.js} +6 -6
- package/dist/{chunk-ACOOG3UW.js → chunk-Z4AWEIBY.js} +3 -3
- package/dist/{ci-workflow-IW2H5DC4.js → ci-workflow-ORZ4F45F.js} +2 -2
- package/dist/{dist-JCFK263L.js → dist-LHINSVK4.js} +85 -1
- package/dist/{docs-7CP6VV42.js → docs-AU3DXFFO.js} +3 -3
- package/dist/{engine-MKJEBWU7.js → engine-6GPWOYQH.js} +2 -2
- package/dist/{entropy-6JOZJYG7.js → entropy-VLKDRMRT.js} +2 -2
- package/dist/{feedback-T65AEJEG.js → feedback-YXYY44ZC.js} +1 -1
- package/dist/{generate-agent-definitions-4VI4YTO2.js → generate-agent-definitions-XIHQPKG3.js} +3 -3
- package/dist/hooks/cost-tracker.js +29 -6
- package/dist/index.d.ts +8 -8
- package/dist/index.js +22 -22
- package/dist/{loader-AWTHHE3A.js → loader-YAN56MT3.js} +2 -2
- package/dist/{mcp-JBIBINO2.js → mcp-RK3SFVYB.js} +13 -13
- package/dist/{performance-NCOUDOMW.js → performance-64DPTLLQ.js} +3 -3
- package/dist/{review-pipeline-YIC6JPMY.js → review-pipeline-NENG6LW7.js} +2 -2
- package/dist/{runtime-5MHV4UEE.js → runtime-FFIIRT2T.js} +2 -2
- package/dist/{security-VZPJNIDP.js → security-3PIBN35B.js} +1 -1
- package/dist/templates/basic/harness.config.json.hbs +34 -0
- package/dist/templates/ci/README.md +86 -0
- package/dist/templates/ci/required-review.ruleset.json +20 -0
- package/dist/templates/ci/required-review.yml.hbs +57 -0
- package/dist/templates/ci/template.json +5 -0
- package/dist/templates/intermediate/harness.config.json.hbs +36 -0
- package/dist/templates/orchestrator/harness.orchestrator.md +9 -0
- package/dist/{tool-tiers-B7JC2XC3.js → tool-tiers-OKK5FE57.js} +2 -0
- package/dist/{validate-7BE4VTFI.js → validate-7IAF2ES3.js} +3 -3
- package/dist/{validate-cross-check-F7GEBRDH.js → validate-cross-check-QPSAOZKD.js} +2 -2
- package/package.json +7 -7
|
@@ -1250,6 +1250,7 @@ __export(dist_exports, {
|
|
|
1250
1250
|
AGENT_DESCRIPTORS: () => AGENT_DESCRIPTORS,
|
|
1251
1251
|
AGREEMENT_LINE_GAP: () => AGREEMENT_LINE_GAP,
|
|
1252
1252
|
ALLOWED_FIELD_KEYS: () => ALLOWED_FIELD_KEYS,
|
|
1253
|
+
ALL_RULES: () => ALL_RULES,
|
|
1253
1254
|
ALL_SOLUTION_CATEGORIES: () => ALL_SOLUTION_CATEGORIES,
|
|
1254
1255
|
ARCHITECTURE_DESCRIPTOR: () => ARCHITECTURE_DESCRIPTOR,
|
|
1255
1256
|
AdjustedForecastSchema: () => AdjustedForecastSchema,
|
|
@@ -1260,6 +1261,8 @@ __export(dist_exports, {
|
|
|
1260
1261
|
ArchConfigSchema: () => ArchConfigSchema,
|
|
1261
1262
|
ArchDiffResultSchema: () => ArchDiffResultSchema,
|
|
1262
1263
|
ArchMetricCategorySchema: () => ArchMetricCategorySchema,
|
|
1264
|
+
AuditResultSchema: () => AuditResultSchema,
|
|
1265
|
+
AuditSummarySchema: () => AuditSummarySchema,
|
|
1263
1266
|
AuthAuditEntrySchema: () => AuthAuditEntrySchema,
|
|
1264
1267
|
AuthTokenPublicSchema: () => AuthTokenPublicSchema,
|
|
1265
1268
|
AuthTokenSchema: () => AuthTokenSchema,
|
|
@@ -1273,6 +1276,10 @@ __export(dist_exports, {
|
|
|
1273
1276
|
BundleSchema: () => BundleSchema,
|
|
1274
1277
|
CACHE_TTL_MS: () => CACHE_TTL_MS,
|
|
1275
1278
|
CINotifier: () => CINotifier,
|
|
1279
|
+
CI_ASSESSMENTS: () => CI_ASSESSMENTS,
|
|
1280
|
+
CI_REVIEW_DOMAINS: () => CI_REVIEW_DOMAINS,
|
|
1281
|
+
CI_REVIEW_VERDICT_SCHEMA_VERSION: () => CI_REVIEW_VERDICT_SCHEMA_VERSION,
|
|
1282
|
+
CI_RUNNERS: () => CI_RUNNERS,
|
|
1276
1283
|
COMPLIANCE_DESCRIPTOR: () => COMPLIANCE_DESCRIPTOR,
|
|
1277
1284
|
CONDITIONAL_SUBAGENT_DESCRIPTORS: () => CONDITIONAL_SUBAGENT_DESCRIPTORS,
|
|
1278
1285
|
CORROBORATED_AGREEMENT: () => CORROBORATED_AGREEMENT,
|
|
@@ -1282,6 +1289,7 @@ __export(dist_exports, {
|
|
|
1282
1289
|
CategoryRegressionSchema: () => CategoryRegressionSchema,
|
|
1283
1290
|
CategorySnapshotSchema: () => CategorySnapshotSchema,
|
|
1284
1291
|
ChecklistBuilder: () => ChecklistBuilder,
|
|
1292
|
+
CiReviewVerdictSchema: () => CiReviewVerdictSchema,
|
|
1285
1293
|
CircularDepsCollector: () => CircularDepsCollector,
|
|
1286
1294
|
CompactionPipeline: () => CompactionPipeline,
|
|
1287
1295
|
ComplexityCollector: () => ComplexityCollector,
|
|
@@ -1295,6 +1303,8 @@ __export(dist_exports, {
|
|
|
1295
1303
|
ContributionsSchema: () => ContributionsSchema,
|
|
1296
1304
|
CouplingCollector: () => CouplingCollector,
|
|
1297
1305
|
CriticalPathResolver: () => CriticalPathResolver,
|
|
1306
|
+
DEFAULT_EXEC_MAX_STDOUT_BYTES: () => DEFAULT_EXEC_MAX_STDOUT_BYTES,
|
|
1307
|
+
DEFAULT_EXEC_TIMEOUT_MS: () => DEFAULT_EXEC_TIMEOUT_MS,
|
|
1298
1308
|
DEFAULT_LOADER_CONFIG: () => DEFAULT_LOADER_CONFIG,
|
|
1299
1309
|
DEFAULT_PROVIDER_TIERS: () => DEFAULT_PROVIDER_TIERS,
|
|
1300
1310
|
DEFAULT_SECURITY_CONFIG: () => DEFAULT_SECURITY_CONFIG,
|
|
@@ -1333,7 +1343,10 @@ __export(dist_exports, {
|
|
|
1333
1343
|
GeminiCacheAdapter: () => GeminiCacheAdapter,
|
|
1334
1344
|
GitHubIssuesSyncAdapter: () => GitHubIssuesSyncAdapter,
|
|
1335
1345
|
HandoffSchema: () => HandoffSchema,
|
|
1346
|
+
HarnessConfigSubsetSchema: () => HarnessConfigSubsetSchema,
|
|
1336
1347
|
HarnessStateSchema: () => HarnessStateSchema,
|
|
1348
|
+
HarnessStrengthAuditor: () => HarnessStrengthAuditor,
|
|
1349
|
+
HookFileSchema: () => HookFileSchema,
|
|
1337
1350
|
INDEXED_FILE_KINDS: () => INDEXED_FILE_KINDS,
|
|
1338
1351
|
INSIGHTS_KEYS: () => INSIGHTS_KEYS,
|
|
1339
1352
|
InteractionTypeSchema: () => InteractionTypeSchema,
|
|
@@ -1345,6 +1358,7 @@ __export(dist_exports, {
|
|
|
1345
1358
|
MOCK_ADAPTER_NAME: () => MOCK_ADAPTER_NAME,
|
|
1346
1359
|
ManifestSchema: () => ManifestSchema,
|
|
1347
1360
|
MetricResultSchema: () => MetricResultSchema,
|
|
1361
|
+
ModeSchema: () => ModeSchema,
|
|
1348
1362
|
ModuleSizeCollector: () => ModuleSizeCollector,
|
|
1349
1363
|
NoOpExecutor: () => NoOpExecutor,
|
|
1350
1364
|
NoOpSink: () => NoOpSink,
|
|
@@ -1371,6 +1385,7 @@ __export(dist_exports, {
|
|
|
1371
1385
|
PredictionRegressionResultSchema: () => RegressionResultSchema,
|
|
1372
1386
|
PredictionResultSchema: () => PredictionResultSchema,
|
|
1373
1387
|
PredictionWarningSchema: () => PredictionWarningSchema,
|
|
1388
|
+
ProjectContextSchema: () => ProjectContextSchema,
|
|
1374
1389
|
ProjectScanner: () => ProjectScanner,
|
|
1375
1390
|
PromptCacheStatsSchema: () => PromptCacheStatsSchema,
|
|
1376
1391
|
ProposalConflictError: () => ProposalConflictError,
|
|
@@ -1390,11 +1405,13 @@ __export(dist_exports, {
|
|
|
1390
1405
|
REQUIRED_SECTIONS: () => REQUIRED_SECTIONS,
|
|
1391
1406
|
REQUIRED_STRATEGY_SECTIONS: () => REQUIRED_STRATEGY_SECTIONS,
|
|
1392
1407
|
RISK_KEYWORDS: () => RISK_KEYWORDS,
|
|
1408
|
+
RUNNER_PRESETS: () => RUNNER_PRESETS,
|
|
1393
1409
|
RegressionDetector: () => RegressionDetector,
|
|
1394
1410
|
RuleRegistry: () => RuleRegistry,
|
|
1395
1411
|
SECURITY_DESCRIPTOR: () => SECURITY_DESCRIPTOR,
|
|
1396
1412
|
SESSIONS_DEFAULTS: () => SESSIONS_DEFAULTS,
|
|
1397
1413
|
SESSION_SECTION_NAMES: () => SESSION_SECTION_NAMES,
|
|
1414
|
+
SEVERITY_WEIGHTS: () => SEVERITY_WEIGHTS,
|
|
1398
1415
|
STALENESS_WARNING_DAYS: () => STALENESS_WARNING_DAYS,
|
|
1399
1416
|
STANDALONE_AGREEMENT: () => STANDALONE_AGREEMENT,
|
|
1400
1417
|
STANDARD_COGNITIVE_MODES: () => STANDARD_COGNITIVE_MODES,
|
|
@@ -1410,6 +1427,7 @@ __export(dist_exports, {
|
|
|
1410
1427
|
SecurityTrendLineSchema: () => SecurityTrendLineSchema,
|
|
1411
1428
|
SecurityTrendResultSchema: () => SecurityTrendResultSchema,
|
|
1412
1429
|
SessionSummarySchema: () => SessionSummarySchema,
|
|
1430
|
+
SeveritySchema: () => SeveritySchema,
|
|
1413
1431
|
SharableBoundaryConfigSchema: () => SharableBoundaryConfigSchema,
|
|
1414
1432
|
SharableForbiddenImportSchema: () => SharableForbiddenImportSchema,
|
|
1415
1433
|
SharableLayerSchema: () => SharableLayerSchema,
|
|
@@ -1427,10 +1445,12 @@ __export(dist_exports, {
|
|
|
1427
1445
|
StrategyFrontmatterSchema: () => StrategyFrontmatterSchema,
|
|
1428
1446
|
StreamIndexSchema: () => StreamIndexSchema,
|
|
1429
1447
|
StreamInfoSchema: () => StreamInfoSchema,
|
|
1448
|
+
StrengthFindingSchema: () => StrengthFindingSchema,
|
|
1430
1449
|
StructuralStrategy: () => StructuralStrategy,
|
|
1431
1450
|
SupplyChainSnapshotSchema: () => SupplyChainSnapshotSchema,
|
|
1432
1451
|
TYPESCRIPT_STRICT_DESCRIPTOR: () => TYPESCRIPT_STRICT_DESCRIPTOR,
|
|
1433
1452
|
ThresholdConfigSchema: () => ThresholdConfigSchema,
|
|
1453
|
+
TierSchema: () => TierSchema,
|
|
1434
1454
|
TimeToFixResultSchema: () => TimeToFixResultSchema,
|
|
1435
1455
|
TimeToFixStatsSchema: () => TimeToFixStatsSchema,
|
|
1436
1456
|
TimelineFileSchema: () => TimelineFileSchema,
|
|
@@ -1485,8 +1505,10 @@ __export(dist_exports, {
|
|
|
1485
1505
|
assertPortUsable: () => assertPortUsable,
|
|
1486
1506
|
assertSanitized: () => assertSanitized,
|
|
1487
1507
|
assignFeature: () => assignFeature,
|
|
1508
|
+
buildCiReviewVerdict: () => buildCiReviewVerdict,
|
|
1488
1509
|
buildDependencyGraph: () => buildDependencyGraph,
|
|
1489
1510
|
buildExclusionSet: () => buildExclusionSet,
|
|
1511
|
+
buildProjectContext: () => buildProjectContext,
|
|
1490
1512
|
buildSnapshot: () => buildSnapshot,
|
|
1491
1513
|
calculateCacheSavings: () => calculateCacheSavings,
|
|
1492
1514
|
calculateCost: () => calculateCost,
|
|
@@ -1495,6 +1517,7 @@ __export(dist_exports, {
|
|
|
1495
1517
|
checkEligibility: () => checkEligibility,
|
|
1496
1518
|
checkEvidenceCoverage: () => checkEvidenceCoverage,
|
|
1497
1519
|
checkOverlap: () => checkOverlap,
|
|
1520
|
+
checkRoadmapHealth: () => checkRoadmapHealth,
|
|
1498
1521
|
checkTaint: () => checkTaint,
|
|
1499
1522
|
classifyConfidence: () => classifyConfidence,
|
|
1500
1523
|
classifyFinding: () => classifyFinding,
|
|
@@ -1537,11 +1560,16 @@ __export(dist_exports, {
|
|
|
1537
1560
|
createTrackerClient: () => createTrackerClient,
|
|
1538
1561
|
crossReferenceUndocumentedFixes: () => crossReferenceUndocumentedFixes,
|
|
1539
1562
|
cryptoRules: () => cryptoRules,
|
|
1563
|
+
decidePromotionForRow: () => decidePromotionForRow,
|
|
1540
1564
|
deduplicateCleanupFindings: () => deduplicateCleanupFindings,
|
|
1541
1565
|
deduplicateFindings: () => deduplicateFindings,
|
|
1542
1566
|
deepMergeConstraints: () => deepMergeConstraints,
|
|
1543
1567
|
defaultCollectors: () => defaultCollectors,
|
|
1568
|
+
defaultExecFile: () => defaultExecFile,
|
|
1569
|
+
defaultIsArchive: () => defaultIsArchive,
|
|
1544
1570
|
defineLayer: () => defineLayer,
|
|
1571
|
+
deriveBlockingFindings: () => deriveBlockingFindings,
|
|
1572
|
+
deriveExitCode: () => deriveExitCode,
|
|
1545
1573
|
deriveSkillUsage: () => deriveSkillUsage,
|
|
1546
1574
|
deserializationRules: () => deserializationRules,
|
|
1547
1575
|
detectChangeType: () => detectChangeType,
|
|
@@ -1606,6 +1634,7 @@ __export(dist_exports, {
|
|
|
1606
1634
|
getUpdateNotification: () => getUpdateNotification,
|
|
1607
1635
|
gitScan: () => gitScan,
|
|
1608
1636
|
goRules: () => goRules,
|
|
1637
|
+
groomRoadmap: () => groomRoadmap,
|
|
1609
1638
|
injectionRules: () => injectionRules,
|
|
1610
1639
|
insecureDefaultsRules: () => insecureDefaultsRules,
|
|
1611
1640
|
invalidateCheckState: () => invalidateCheckState,
|
|
@@ -1616,6 +1645,8 @@ __export(dist_exports, {
|
|
|
1616
1645
|
isRegression: () => isRegression,
|
|
1617
1646
|
isSanitizedResult: () => isSanitizedResult,
|
|
1618
1647
|
isSmallSuggestion: () => isSmallSuggestion,
|
|
1648
|
+
isSupportedRunner: () => isSupportedRunner,
|
|
1649
|
+
isUnactionablePlanned: () => isUnactionablePlanned,
|
|
1619
1650
|
isUpdateCheckEnabled: () => isUpdateCheckEnabled,
|
|
1620
1651
|
isoWeek: () => isoWeek,
|
|
1621
1652
|
listActiveSessions: () => listActiveSessions,
|
|
@@ -1651,14 +1682,20 @@ __export(dist_exports, {
|
|
|
1651
1682
|
normalizeLearningContent: () => normalizeLearningContent,
|
|
1652
1683
|
normalizeViolationPattern: () => normalizeViolationPattern,
|
|
1653
1684
|
paginate: () => paginate,
|
|
1685
|
+
parseAntigravityVerdict: () => parseAntigravityVerdict,
|
|
1654
1686
|
parseCCRecords: () => parseCCRecords,
|
|
1687
|
+
parseCiReviewVerdict: () => parseCiReviewVerdict,
|
|
1688
|
+
parseClaudeVerdict: () => parseClaudeVerdict,
|
|
1689
|
+
parseCodexVerdict: () => parseCodexVerdict,
|
|
1655
1690
|
parseDateFromEntry: () => parseDateFromEntry,
|
|
1656
1691
|
parseDiff: () => parseDiff,
|
|
1657
1692
|
parseFile: () => parseFile,
|
|
1658
1693
|
parseFileRegions: () => parseFileRegions,
|
|
1659
1694
|
parseFrontmatter: () => parseFrontmatter2,
|
|
1695
|
+
parseGeminiVerdict: () => parseGeminiVerdict,
|
|
1660
1696
|
parseHarnessIgnore: () => parseHarnessIgnore,
|
|
1661
1697
|
parseLiteLLMData: () => parseLiteLLMData,
|
|
1698
|
+
parseLocalVerdict: () => parseLocalVerdict,
|
|
1662
1699
|
parseLookback: () => parseLookback,
|
|
1663
1700
|
parseManifest: () => parseManifest,
|
|
1664
1701
|
parseProtectedRegions: () => parseProtectedRegions,
|
|
@@ -1668,8 +1705,10 @@ __export(dist_exports, {
|
|
|
1668
1705
|
parseSize: () => parseSize,
|
|
1669
1706
|
parseStrategyDoc: () => parseStrategyDoc,
|
|
1670
1707
|
pathTraversalRules: () => pathTraversalRules,
|
|
1708
|
+
presetKind: () => presetKind,
|
|
1671
1709
|
previewFix: () => previewFix,
|
|
1672
1710
|
projectValue: () => projectValue,
|
|
1711
|
+
promoteFeature: () => promoteFeature,
|
|
1673
1712
|
promoteSessionLearnings: () => promoteSessionLearnings,
|
|
1674
1713
|
proposalsDir: () => proposalsDir,
|
|
1675
1714
|
pruneLearnings: () => pruneLearnings,
|
|
@@ -1692,6 +1731,7 @@ __export(dist_exports, {
|
|
|
1692
1731
|
resetParserCache: () => resetParserCache,
|
|
1693
1732
|
resolveConsent: () => resolveConsent,
|
|
1694
1733
|
resolveFileToLayer: () => resolveFileToLayer,
|
|
1734
|
+
resolveMode: () => resolveMode,
|
|
1695
1735
|
resolveModelTier: () => resolveModelTier,
|
|
1696
1736
|
resolveReverseStatus: () => resolveReverseStatus,
|
|
1697
1737
|
resolveRuleSeverity: () => resolveRuleSeverity,
|
|
@@ -1699,12 +1739,14 @@ __export(dist_exports, {
|
|
|
1699
1739
|
resolveStability: () => resolveStability,
|
|
1700
1740
|
resolveStreamPath: () => resolveStreamPath,
|
|
1701
1741
|
resolveThresholds: () => resolveThresholds,
|
|
1742
|
+
rollupScore: () => rollupScore,
|
|
1702
1743
|
runAdversarialAgent: () => runAdversarialAgent,
|
|
1703
1744
|
runAgentConfigFallbackRules: () => runFallbackRules,
|
|
1704
1745
|
runAll: () => runAll,
|
|
1705
1746
|
runArchitectureAgent: () => runArchitectureAgent,
|
|
1706
1747
|
runBugDetectionAgent: () => runBugDetectionAgent,
|
|
1707
1748
|
runCIChecks: () => runCIChecks,
|
|
1749
|
+
runCiReview: () => runCiReview,
|
|
1708
1750
|
runComplianceAgent: () => runComplianceAgent,
|
|
1709
1751
|
runFrontendRacesAgent: () => runFrontendRacesAgent,
|
|
1710
1752
|
runMechanicalChecks: () => runMechanicalChecks,
|
|
@@ -1829,41 +1871,41 @@ import * as fs7 from "fs";
|
|
|
1829
1871
|
import { promisify } from "util";
|
|
1830
1872
|
import { dirname as dirname5, basename as basename5, join as join16 } from "path";
|
|
1831
1873
|
import { z as z5 } from "zod";
|
|
1874
|
+
import { readFileSync as readFileSync22, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync12 } from "fs";
|
|
1875
|
+
import { join as join17, dirname as dirname6 } from "path";
|
|
1876
|
+
import { execFileSync } from "child_process";
|
|
1832
1877
|
import * as fs8 from "fs";
|
|
1833
1878
|
import * as path5 from "path";
|
|
1834
|
-
import {
|
|
1835
|
-
import { join as join18, dirname as dirname6 } from "path";
|
|
1836
|
-
import { execFileSync } from "child_process";
|
|
1837
|
-
import * as fs9 from "fs";
|
|
1838
|
-
import * as path6 from "path";
|
|
1839
|
-
import { appendFileSync, writeFileSync as writeFileSync22, existsSync as existsSync14, mkdirSync as mkdirSync22 } from "fs";
|
|
1879
|
+
import { appendFileSync, writeFileSync as writeFileSync22, existsSync as existsSync13, mkdirSync as mkdirSync22 } from "fs";
|
|
1840
1880
|
import { dirname as dirname7 } from "path";
|
|
1841
1881
|
import { z as z6 } from "zod";
|
|
1882
|
+
import * as fs9 from "fs";
|
|
1883
|
+
import * as path6 from "path";
|
|
1842
1884
|
import * as fs10 from "fs";
|
|
1843
1885
|
import * as path7 from "path";
|
|
1844
1886
|
import * as fs11 from "fs";
|
|
1845
1887
|
import * as path8 from "path";
|
|
1846
|
-
import * as fs12 from "fs";
|
|
1847
|
-
import * as path9 from "path";
|
|
1848
1888
|
import { z as z7 } from "zod";
|
|
1849
|
-
import * as fs16 from "fs";
|
|
1850
|
-
import * as path13 from "path";
|
|
1851
1889
|
import * as fs15 from "fs";
|
|
1852
1890
|
import * as path12 from "path";
|
|
1853
|
-
import * as fs13 from "fs";
|
|
1854
|
-
import * as path10 from "path";
|
|
1855
|
-
import { execSync } from "child_process";
|
|
1856
|
-
import { z as z8 } from "zod";
|
|
1857
1891
|
import * as fs14 from "fs";
|
|
1858
1892
|
import * as path11 from "path";
|
|
1893
|
+
import * as fs12 from "fs";
|
|
1894
|
+
import * as path9 from "path";
|
|
1895
|
+
import { execSync } from "child_process";
|
|
1896
|
+
import { z as z8 } from "zod";
|
|
1897
|
+
import * as fs13 from "fs";
|
|
1898
|
+
import * as path10 from "path";
|
|
1899
|
+
import * as fs16 from "fs";
|
|
1900
|
+
import * as path13 from "path";
|
|
1901
|
+
import * as crypto2 from "crypto";
|
|
1859
1902
|
import * as fs17 from "fs";
|
|
1860
1903
|
import * as path14 from "path";
|
|
1861
|
-
import * as crypto2 from "crypto";
|
|
1862
1904
|
import * as fs18 from "fs";
|
|
1863
1905
|
import * as path15 from "path";
|
|
1906
|
+
import * as crypto3 from "crypto";
|
|
1864
1907
|
import * as fs19 from "fs";
|
|
1865
1908
|
import * as path16 from "path";
|
|
1866
|
-
import * as crypto3 from "crypto";
|
|
1867
1909
|
import * as fs20 from "fs";
|
|
1868
1910
|
import * as path17 from "path";
|
|
1869
1911
|
import * as fs21 from "fs";
|
|
@@ -1872,95 +1914,100 @@ import * as fs22 from "fs";
|
|
|
1872
1914
|
import * as path19 from "path";
|
|
1873
1915
|
import * as fs23 from "fs";
|
|
1874
1916
|
import * as path20 from "path";
|
|
1917
|
+
import { execSync as execSync2 } from "child_process";
|
|
1875
1918
|
import * as fs24 from "fs";
|
|
1876
1919
|
import * as path21 from "path";
|
|
1877
|
-
import { execSync as execSync2 } from "child_process";
|
|
1878
1920
|
import * as fs25 from "fs";
|
|
1879
1921
|
import * as path22 from "path";
|
|
1880
1922
|
import * as fs26 from "fs";
|
|
1881
1923
|
import * as path23 from "path";
|
|
1882
1924
|
import * as fs27 from "fs";
|
|
1883
1925
|
import * as path24 from "path";
|
|
1884
|
-
import * as fs28 from "fs";
|
|
1885
|
-
import * as path25 from "path";
|
|
1886
1926
|
import { z as z9 } from "zod";
|
|
1887
|
-
import * as
|
|
1927
|
+
import * as fs29 from "fs/promises";
|
|
1888
1928
|
import { minimatch as minimatch3 } from "minimatch";
|
|
1889
1929
|
import { z as z10 } from "zod";
|
|
1890
|
-
import * as
|
|
1930
|
+
import * as fs28 from "fs";
|
|
1931
|
+
import * as path25 from "path";
|
|
1932
|
+
import * as fs30 from "fs";
|
|
1891
1933
|
import * as path26 from "path";
|
|
1892
|
-
import
|
|
1893
|
-
import
|
|
1894
|
-
import { readFileSync as readFileSync22, writeFileSync as writeFileSync14, unlinkSync as unlinkSync2, mkdirSync as mkdirSync13, readdirSync as readdirSync3 } from "fs";
|
|
1895
|
-
import { join as join39, dirname as dirname10 } from "path";
|
|
1934
|
+
import { readFileSync as readFileSync21, writeFileSync as writeFileSync14, unlinkSync as unlinkSync2, mkdirSync as mkdirSync13, readdirSync as readdirSync2 } from "fs";
|
|
1935
|
+
import { join as join38, dirname as dirname10 } from "path";
|
|
1896
1936
|
import { createHash as createHash32 } from "crypto";
|
|
1897
1937
|
import { z as z11 } from "zod";
|
|
1898
|
-
import { readFileSync as
|
|
1938
|
+
import { readFileSync as readFileSync222, writeFileSync as writeFileSync15, renameSync as renameSync72, mkdirSync as mkdirSync14, existsSync as existsSync332 } from "fs";
|
|
1899
1939
|
import { execSync as execSync3 } from "child_process";
|
|
1900
1940
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
1901
|
-
import { isAbsolute as isAbsolute4, join as
|
|
1941
|
+
import { isAbsolute as isAbsolute4, join as join39, relative as relative3, dirname as dirname11 } from "path";
|
|
1942
|
+
import * as path27 from "path";
|
|
1902
1943
|
import * as path28 from "path";
|
|
1903
1944
|
import * as path29 from "path";
|
|
1904
1945
|
import * as path30 from "path";
|
|
1946
|
+
import { z as z12 } from "zod";
|
|
1947
|
+
import { execFile as nodeExecFile } from "child_process";
|
|
1948
|
+
import * as fs31 from "fs";
|
|
1905
1949
|
import * as path31 from "path";
|
|
1906
1950
|
import * as fs32 from "fs";
|
|
1907
1951
|
import * as path32 from "path";
|
|
1908
1952
|
import * as fs33 from "fs";
|
|
1909
|
-
import * as path33 from "path";
|
|
1910
|
-
import * as fs34 from "fs";
|
|
1911
1953
|
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
1954
|
+
import * as fs34 from "fs";
|
|
1955
|
+
import * as path33 from "path";
|
|
1912
1956
|
import * as fs35 from "fs";
|
|
1913
1957
|
import * as path34 from "path";
|
|
1914
|
-
import * as fs36 from "fs";
|
|
1915
|
-
import * as path35 from "path";
|
|
1916
1958
|
import { createHash as createHash4 } from "crypto";
|
|
1959
|
+
import * as path35 from "path";
|
|
1960
|
+
import { z as z13 } from "zod";
|
|
1961
|
+
import * as fs36 from "fs/promises";
|
|
1917
1962
|
import * as path36 from "path";
|
|
1918
|
-
import { z as z12 } from "zod";
|
|
1919
1963
|
import * as fs37 from "fs/promises";
|
|
1920
1964
|
import * as path37 from "path";
|
|
1921
|
-
import * as fs38 from "fs/promises";
|
|
1922
|
-
import * as path38 from "path";
|
|
1923
1965
|
import * as ejs from "ejs";
|
|
1924
|
-
import * as
|
|
1925
|
-
import * as
|
|
1966
|
+
import * as fs38 from "fs";
|
|
1967
|
+
import * as path38 from "path";
|
|
1926
1968
|
import * as os from "os";
|
|
1927
1969
|
import { spawn as spawn2 } from "child_process";
|
|
1928
|
-
import * as
|
|
1970
|
+
import * as fs39 from "fs/promises";
|
|
1971
|
+
import * as path39 from "path";
|
|
1972
|
+
import * as fs40 from "fs";
|
|
1929
1973
|
import * as path40 from "path";
|
|
1930
1974
|
import * as fs41 from "fs";
|
|
1931
1975
|
import * as path41 from "path";
|
|
1976
|
+
import * as os2 from "os";
|
|
1932
1977
|
import * as fs42 from "fs";
|
|
1933
1978
|
import * as path42 from "path";
|
|
1934
|
-
import * as os2 from "os";
|
|
1935
|
-
import * as fs43 from "fs";
|
|
1936
|
-
import * as path43 from "path";
|
|
1937
1979
|
import * as fs44 from "fs";
|
|
1938
1980
|
import * as path44 from "path";
|
|
1939
|
-
import * as fs46 from "fs";
|
|
1940
|
-
import * as path46 from "path";
|
|
1941
1981
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
1982
|
+
import * as fs43 from "fs";
|
|
1983
|
+
import * as path43 from "path";
|
|
1984
|
+
import * as crypto4 from "crypto";
|
|
1985
|
+
import { z as z14 } from "zod";
|
|
1986
|
+
import { existsSync as existsSync38, readFileSync as readFileSync34, readdirSync as readdirSync5, statSync as statSync4 } from "fs";
|
|
1987
|
+
import { join as join582, basename as basename10, resolve as resolve6, relative as relative4 } from "path";
|
|
1942
1988
|
import * as fs45 from "fs";
|
|
1943
1989
|
import * as path45 from "path";
|
|
1944
|
-
import * as
|
|
1990
|
+
import * as fs46 from "fs";
|
|
1991
|
+
import * as path46 from "path";
|
|
1945
1992
|
import * as fs47 from "fs";
|
|
1946
1993
|
import * as path47 from "path";
|
|
1947
1994
|
import * as fs48 from "fs";
|
|
1948
|
-
import * as fs49 from "fs";
|
|
1949
1995
|
import * as path48 from "path";
|
|
1950
|
-
import {
|
|
1996
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
1997
|
+
import * as fs49 from "fs";
|
|
1998
|
+
import * as fs50 from "fs";
|
|
1999
|
+
import * as path49 from "path";
|
|
2000
|
+
import { readFileSync as readFileSync41 } from "fs";
|
|
1951
2001
|
import { fileURLToPath } from "url";
|
|
1952
|
-
import { dirname as dirname16, join as
|
|
2002
|
+
import { dirname as dirname16, join as join642 } from "path";
|
|
1953
2003
|
import { execFile } from "child_process";
|
|
1954
2004
|
import { promisify as promisify2 } from "util";
|
|
1955
2005
|
import { execFile as execFile2 } from "child_process";
|
|
1956
2006
|
import { promisify as promisify3 } from "util";
|
|
1957
|
-
import * as
|
|
1958
|
-
import * as path49 from "path";
|
|
1959
|
-
import * as fs51 from "fs";
|
|
2007
|
+
import * as fs51 from "fs/promises";
|
|
1960
2008
|
import * as path50 from "path";
|
|
1961
2009
|
import * as fs52 from "fs";
|
|
1962
2010
|
import * as path51 from "path";
|
|
1963
|
-
import { randomUUID as randomUUID2 } from "crypto";
|
|
1964
2011
|
function isBadPort(port) {
|
|
1965
2012
|
return BAD_PORT_SET.has(port);
|
|
1966
2013
|
}
|
|
@@ -2334,7 +2381,7 @@ function isAgnixDisabled() {
|
|
|
2334
2381
|
return v === "1" || v === "true";
|
|
2335
2382
|
}
|
|
2336
2383
|
function runAgnix(cwd, strict, binPath, timeoutMs = DEFAULT_AGNIX_TIMEOUT_MS, spawnFn = spawn) {
|
|
2337
|
-
return new Promise((
|
|
2384
|
+
return new Promise((resolve7) => {
|
|
2338
2385
|
const args = ["--format", "json"];
|
|
2339
2386
|
if (strict) args.push("--strict");
|
|
2340
2387
|
args.push(cwd);
|
|
@@ -2348,7 +2395,7 @@ function runAgnix(cwd, strict, binPath, timeoutMs = DEFAULT_AGNIX_TIMEOUT_MS, sp
|
|
|
2348
2395
|
if (settled) return;
|
|
2349
2396
|
settled = true;
|
|
2350
2397
|
clearTimeout(timer);
|
|
2351
|
-
|
|
2398
|
+
resolve7(outcome);
|
|
2352
2399
|
};
|
|
2353
2400
|
const timer = setTimeout(() => {
|
|
2354
2401
|
child.kill("SIGKILL");
|
|
@@ -4938,139 +4985,6 @@ function validatePatternConfig(config) {
|
|
|
4938
4985
|
}
|
|
4939
4986
|
return Ok(result.data);
|
|
4940
4987
|
}
|
|
4941
|
-
async function safeGather(key, fn, warnings) {
|
|
4942
|
-
try {
|
|
4943
|
-
return await fn();
|
|
4944
|
-
} catch (e) {
|
|
4945
|
-
warnings.push(`${key}: ${e instanceof Error ? e.message : String(e)}`);
|
|
4946
|
-
return null;
|
|
4947
|
-
}
|
|
4948
|
-
}
|
|
4949
|
-
function arrayLen(arr) {
|
|
4950
|
-
return Array.isArray(arr) ? arr.length : 0;
|
|
4951
|
-
}
|
|
4952
|
-
async function gatherEntropyBlock(projectPath) {
|
|
4953
|
-
const { EntropyAnalyzer: EntropyAnalyzer2 } = await import("./analyzer-V633GUHY-MBCQWKDU.js");
|
|
4954
|
-
const analyzer = new EntropyAnalyzer2({
|
|
4955
|
-
rootDir: projectPath,
|
|
4956
|
-
include: ["src/**/*.ts", "src/**/*.tsx", "packages/*/src/**/*.ts"],
|
|
4957
|
-
exclude: ["node_modules/**", "dist/**", "**/*.test.ts", "**/*.spec.ts"],
|
|
4958
|
-
analyze: { drift: false, deadCode: true, patterns: false, complexity: false }
|
|
4959
|
-
});
|
|
4960
|
-
const result = await analyzer.analyze();
|
|
4961
|
-
const empty = { driftCount: 0, deadFiles: 0, deadExports: 0 };
|
|
4962
|
-
if (!result.ok) return empty;
|
|
4963
|
-
const report = result.value;
|
|
4964
|
-
return {
|
|
4965
|
-
driftCount: arrayLen(report.drift?.drifts),
|
|
4966
|
-
deadFiles: arrayLen(report.deadCode?.deadFiles),
|
|
4967
|
-
deadExports: arrayLen(report.deadCode?.deadExports)
|
|
4968
|
-
};
|
|
4969
|
-
}
|
|
4970
|
-
async function gatherDecayBlock(projectPath) {
|
|
4971
|
-
const { TimelineManager: TimelineManager2 } = await import("./timeline-manager-FPYKJRHR-VVX7CSGW.js");
|
|
4972
|
-
const mgr = new TimelineManager2(projectPath);
|
|
4973
|
-
const trends = mgr.trends();
|
|
4974
|
-
return {
|
|
4975
|
-
recentBumps: arrayLen(trends.recentSnapshots),
|
|
4976
|
-
topAffected: collectTopAffectedLabels(trends.topAffected)
|
|
4977
|
-
};
|
|
4978
|
-
}
|
|
4979
|
-
function collectTopAffectedLabels(affected) {
|
|
4980
|
-
if (!Array.isArray(affected)) return [];
|
|
4981
|
-
const out = [];
|
|
4982
|
-
for (const node of affected) {
|
|
4983
|
-
const label = node.id ?? node.name;
|
|
4984
|
-
if (typeof label === "string" && label.length > 0) out.push(label);
|
|
4985
|
-
if (out.length >= MAX_TOP_AFFECTED) break;
|
|
4986
|
-
}
|
|
4987
|
-
return out;
|
|
4988
|
-
}
|
|
4989
|
-
async function gatherAttentionBlock(projectPath) {
|
|
4990
|
-
const sessionsDir = path5.join(projectPath, ".harness", "sessions");
|
|
4991
|
-
if (!fs8.existsSync(sessionsDir)) return { activeThreadCount: 0, staleThreadCount: 0 };
|
|
4992
|
-
const now = Date.now();
|
|
4993
|
-
const STALE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
4994
|
-
let active = 0;
|
|
4995
|
-
let stale = 0;
|
|
4996
|
-
const entries = fs8.readdirSync(sessionsDir, { withFileTypes: true });
|
|
4997
|
-
for (const entry of entries) {
|
|
4998
|
-
if (!entry.isDirectory()) continue;
|
|
4999
|
-
const dirPath = path5.join(sessionsDir, entry.name);
|
|
5000
|
-
try {
|
|
5001
|
-
const stat2 = fs8.statSync(dirPath);
|
|
5002
|
-
if (now - stat2.mtimeMs > STALE_MS) {
|
|
5003
|
-
stale++;
|
|
5004
|
-
} else {
|
|
5005
|
-
active++;
|
|
5006
|
-
}
|
|
5007
|
-
} catch {
|
|
5008
|
-
}
|
|
5009
|
-
}
|
|
5010
|
-
return { activeThreadCount: active, staleThreadCount: stale };
|
|
5011
|
-
}
|
|
5012
|
-
async function gatherImpactBlock(projectPath) {
|
|
5013
|
-
const cachePath = path5.join(projectPath, ".harness", "cache", "impact.json");
|
|
5014
|
-
if (!fs8.existsSync(cachePath)) return { recentBlastRadius: [] };
|
|
5015
|
-
try {
|
|
5016
|
-
const raw = JSON.parse(fs8.readFileSync(cachePath, "utf8"));
|
|
5017
|
-
const radii = raw.blastRadius ?? [];
|
|
5018
|
-
return {
|
|
5019
|
-
recentBlastRadius: radii.slice(0, 5).map((r) => ({
|
|
5020
|
-
node: r.node,
|
|
5021
|
-
affected: r.affected
|
|
5022
|
-
}))
|
|
5023
|
-
};
|
|
5024
|
-
} catch {
|
|
5025
|
-
return { recentBlastRadius: [] };
|
|
5026
|
-
}
|
|
5027
|
-
}
|
|
5028
|
-
async function gatherHealthBlock(projectPath) {
|
|
5029
|
-
const entropy = await gatherEntropyBlock(projectPath);
|
|
5030
|
-
const signals = [];
|
|
5031
|
-
if (entropy.driftCount > 0) signals.push(`${entropy.driftCount} drift findings`);
|
|
5032
|
-
if (entropy.deadFiles > 0) signals.push(`${entropy.deadFiles} dead files`);
|
|
5033
|
-
if (entropy.deadExports > 0) signals.push(`${entropy.deadExports} dead exports`);
|
|
5034
|
-
const passed = signals.length === 0;
|
|
5035
|
-
return {
|
|
5036
|
-
passed,
|
|
5037
|
-
signals,
|
|
5038
|
-
summary: passed ? "No structural health findings." : signals.join("; ")
|
|
5039
|
-
};
|
|
5040
|
-
}
|
|
5041
|
-
async function composeInsights(projectPath, opts = {}) {
|
|
5042
|
-
const skip = new Set(opts.skip ?? []);
|
|
5043
|
-
const warnings = [];
|
|
5044
|
-
const wantedKeys = INSIGHTS_KEYS.filter((k) => !skip.has(k));
|
|
5045
|
-
const [healthRes, entropyRes, decayRes, attentionRes, impactRes] = await Promise.all([
|
|
5046
|
-
wantedKeys.includes("health") ? safeGather("health", () => gatherHealthBlock(projectPath), warnings) : Promise.resolve(null),
|
|
5047
|
-
wantedKeys.includes("entropy") ? safeGather("entropy", () => gatherEntropyBlock(projectPath), warnings) : Promise.resolve(null),
|
|
5048
|
-
wantedKeys.includes("decay") ? safeGather("decay", () => gatherDecayBlock(projectPath), warnings) : Promise.resolve(null),
|
|
5049
|
-
wantedKeys.includes("attention") ? safeGather("attention", () => gatherAttentionBlock(projectPath), warnings) : Promise.resolve(null),
|
|
5050
|
-
wantedKeys.includes("impact") ? safeGather("impact", () => gatherImpactBlock(projectPath), warnings) : Promise.resolve(null)
|
|
5051
|
-
]);
|
|
5052
|
-
const projectName = pickProjectName(projectPath);
|
|
5053
|
-
return {
|
|
5054
|
-
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5055
|
-
project: { root: projectPath, ...projectName && { name: projectName } },
|
|
5056
|
-
health: healthRes,
|
|
5057
|
-
entropy: entropyRes,
|
|
5058
|
-
decay: decayRes,
|
|
5059
|
-
attention: attentionRes,
|
|
5060
|
-
impact: impactRes,
|
|
5061
|
-
warnings
|
|
5062
|
-
};
|
|
5063
|
-
}
|
|
5064
|
-
function pickProjectName(projectPath) {
|
|
5065
|
-
try {
|
|
5066
|
-
const pkgPath = path5.join(projectPath, "package.json");
|
|
5067
|
-
if (!fs8.existsSync(pkgPath)) return void 0;
|
|
5068
|
-
const pkg = JSON.parse(fs8.readFileSync(pkgPath, "utf8"));
|
|
5069
|
-
return pkg.name;
|
|
5070
|
-
} catch {
|
|
5071
|
-
return void 0;
|
|
5072
|
-
}
|
|
5073
|
-
}
|
|
5074
4988
|
function mergeGraphInferred(highFanInFunctions, seen) {
|
|
5075
4989
|
let added = 0;
|
|
5076
4990
|
for (const item of highFanInFunctions) {
|
|
@@ -5941,10 +5855,10 @@ function detectEmergentConstraints(history, options) {
|
|
|
5941
5855
|
return { suggestions, totalViolationsAnalyzed, windowWeeks, minOccurrences };
|
|
5942
5856
|
}
|
|
5943
5857
|
function streamsDir(projectPath) {
|
|
5944
|
-
return
|
|
5858
|
+
return path9.join(projectPath, HARNESS_DIR, STREAMS_DIR);
|
|
5945
5859
|
}
|
|
5946
5860
|
function indexPath(projectPath) {
|
|
5947
|
-
return
|
|
5861
|
+
return path9.join(streamsDir(projectPath), INDEX_FILE);
|
|
5948
5862
|
}
|
|
5949
5863
|
function validateStreamName(name) {
|
|
5950
5864
|
if (!STREAM_NAME_REGEX.test(name)) {
|
|
@@ -5958,11 +5872,11 @@ function validateStreamName(name) {
|
|
|
5958
5872
|
}
|
|
5959
5873
|
async function loadStreamIndex(projectPath) {
|
|
5960
5874
|
const idxPath = indexPath(projectPath);
|
|
5961
|
-
if (!
|
|
5875
|
+
if (!fs12.existsSync(idxPath)) {
|
|
5962
5876
|
return Ok({ ...DEFAULT_STREAM_INDEX, streams: {} });
|
|
5963
5877
|
}
|
|
5964
5878
|
try {
|
|
5965
|
-
const raw =
|
|
5879
|
+
const raw = fs12.readFileSync(idxPath, "utf-8");
|
|
5966
5880
|
const parsed = JSON.parse(raw);
|
|
5967
5881
|
const result = StreamIndexSchema.safeParse(parsed);
|
|
5968
5882
|
if (!result.success) {
|
|
@@ -5980,8 +5894,8 @@ async function loadStreamIndex(projectPath) {
|
|
|
5980
5894
|
async function saveStreamIndex(projectPath, index) {
|
|
5981
5895
|
const dir = streamsDir(projectPath);
|
|
5982
5896
|
try {
|
|
5983
|
-
|
|
5984
|
-
|
|
5897
|
+
fs12.mkdirSync(dir, { recursive: true });
|
|
5898
|
+
fs12.writeFileSync(indexPath(projectPath), JSON.stringify(index, null, 2));
|
|
5985
5899
|
return Ok(void 0);
|
|
5986
5900
|
} catch (error) {
|
|
5987
5901
|
return Err(
|
|
@@ -6020,18 +5934,18 @@ async function resolveStreamPath(projectPath, options) {
|
|
|
6020
5934
|
)
|
|
6021
5935
|
);
|
|
6022
5936
|
}
|
|
6023
|
-
return Ok(
|
|
5937
|
+
return Ok(path9.join(streamsDir(projectPath), options.stream));
|
|
6024
5938
|
}
|
|
6025
5939
|
const branch = getCurrentBranch(projectPath);
|
|
6026
5940
|
if (branch && branch !== "main" && branch !== "master") {
|
|
6027
5941
|
for (const [name, info] of Object.entries(index.streams)) {
|
|
6028
5942
|
if (info.branch === branch) {
|
|
6029
|
-
return Ok(
|
|
5943
|
+
return Ok(path9.join(streamsDir(projectPath), name));
|
|
6030
5944
|
}
|
|
6031
5945
|
}
|
|
6032
5946
|
}
|
|
6033
5947
|
if (index.activeStream && index.streams[index.activeStream]) {
|
|
6034
|
-
return Ok(
|
|
5948
|
+
return Ok(path9.join(streamsDir(projectPath), index.activeStream));
|
|
6035
5949
|
}
|
|
6036
5950
|
return Err(
|
|
6037
5951
|
new Error(
|
|
@@ -6059,9 +5973,9 @@ async function createStream(projectPath, name, branch) {
|
|
|
6059
5973
|
if (index.streams[name]) {
|
|
6060
5974
|
return Err(new Error(`Stream '${name}' already exists`));
|
|
6061
5975
|
}
|
|
6062
|
-
const streamPath =
|
|
5976
|
+
const streamPath = path9.join(streamsDir(projectPath), name);
|
|
6063
5977
|
try {
|
|
6064
|
-
|
|
5978
|
+
fs12.mkdirSync(streamPath, { recursive: true });
|
|
6065
5979
|
} catch (error) {
|
|
6066
5980
|
return Err(
|
|
6067
5981
|
new Error(
|
|
@@ -6102,12 +6016,12 @@ async function archiveStream(projectPath, name) {
|
|
|
6102
6016
|
if (!index.streams[name]) {
|
|
6103
6017
|
return Err(new Error(`Stream '${name}' not found`));
|
|
6104
6018
|
}
|
|
6105
|
-
const streamPath =
|
|
6106
|
-
const archiveDir =
|
|
6019
|
+
const streamPath = path9.join(streamsDir(projectPath), name);
|
|
6020
|
+
const archiveDir = path9.join(projectPath, HARNESS_DIR, "archive", "streams");
|
|
6107
6021
|
try {
|
|
6108
|
-
|
|
6022
|
+
fs12.mkdirSync(archiveDir, { recursive: true });
|
|
6109
6023
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
6110
|
-
|
|
6024
|
+
fs12.renameSync(streamPath, path9.join(archiveDir, `${name}-${date}`));
|
|
6111
6025
|
} catch (error) {
|
|
6112
6026
|
return Err(
|
|
6113
6027
|
new Error(
|
|
@@ -6128,19 +6042,19 @@ function getStreamForBranch(index, branch) {
|
|
|
6128
6042
|
return null;
|
|
6129
6043
|
}
|
|
6130
6044
|
async function migrateToStreams(projectPath) {
|
|
6131
|
-
const harnessDir =
|
|
6132
|
-
if (
|
|
6045
|
+
const harnessDir = path9.join(projectPath, HARNESS_DIR);
|
|
6046
|
+
if (fs12.existsSync(indexPath(projectPath))) {
|
|
6133
6047
|
return Ok(void 0);
|
|
6134
6048
|
}
|
|
6135
|
-
const filesToMove = STATE_FILES.filter((f) =>
|
|
6049
|
+
const filesToMove = STATE_FILES.filter((f) => fs12.existsSync(path9.join(harnessDir, f)));
|
|
6136
6050
|
if (filesToMove.length === 0) {
|
|
6137
6051
|
return Ok(void 0);
|
|
6138
6052
|
}
|
|
6139
|
-
const defaultDir =
|
|
6053
|
+
const defaultDir = path9.join(streamsDir(projectPath), "default");
|
|
6140
6054
|
try {
|
|
6141
|
-
|
|
6055
|
+
fs12.mkdirSync(defaultDir, { recursive: true });
|
|
6142
6056
|
for (const file of filesToMove) {
|
|
6143
|
-
|
|
6057
|
+
fs12.renameSync(path9.join(harnessDir, file), path9.join(defaultDir, file));
|
|
6144
6058
|
}
|
|
6145
6059
|
} catch (error) {
|
|
6146
6060
|
return Err(
|
|
@@ -6170,26 +6084,26 @@ function resolveSessionDir(projectPath, sessionSlug, options) {
|
|
|
6170
6084
|
new Error(`Invalid session slug '${sessionSlug}': must not contain path traversal characters`)
|
|
6171
6085
|
);
|
|
6172
6086
|
}
|
|
6173
|
-
const sessionDir =
|
|
6087
|
+
const sessionDir = path10.join(projectPath, HARNESS_DIR, SESSIONS_DIR, sessionSlug);
|
|
6174
6088
|
if (options?.create) {
|
|
6175
|
-
|
|
6089
|
+
fs13.mkdirSync(sessionDir, { recursive: true });
|
|
6176
6090
|
}
|
|
6177
6091
|
return Ok(sessionDir);
|
|
6178
6092
|
}
|
|
6179
6093
|
function updateSessionIndex(projectPath, sessionSlug, description) {
|
|
6180
|
-
const sessionsDir =
|
|
6181
|
-
|
|
6182
|
-
const indexPath2 =
|
|
6094
|
+
const sessionsDir = path10.join(projectPath, HARNESS_DIR, SESSIONS_DIR);
|
|
6095
|
+
fs13.mkdirSync(sessionsDir, { recursive: true });
|
|
6096
|
+
const indexPath2 = path10.join(sessionsDir, SESSION_INDEX_FILE);
|
|
6183
6097
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
6184
6098
|
const newLine = `- [${sessionSlug}](${sessionSlug}/summary.md) \u2014 ${description} (${date})`;
|
|
6185
|
-
if (!
|
|
6186
|
-
|
|
6099
|
+
if (!fs13.existsSync(indexPath2)) {
|
|
6100
|
+
fs13.writeFileSync(indexPath2, `## Active Sessions
|
|
6187
6101
|
|
|
6188
6102
|
${newLine}
|
|
6189
6103
|
`);
|
|
6190
6104
|
return;
|
|
6191
6105
|
}
|
|
6192
|
-
const content =
|
|
6106
|
+
const content = fs13.readFileSync(indexPath2, "utf-8");
|
|
6193
6107
|
const lines = content.split("\n");
|
|
6194
6108
|
const slugPattern = `- [${sessionSlug}]`;
|
|
6195
6109
|
const existingIdx = lines.findIndex((l) => l.startsWith(slugPattern));
|
|
@@ -6199,7 +6113,7 @@ ${newLine}
|
|
|
6199
6113
|
const lastNonEmpty = lines.reduce((last, line, i) => line.trim() !== "" ? i : last, 0);
|
|
6200
6114
|
lines.splice(lastNonEmpty + 1, 0, newLine);
|
|
6201
6115
|
}
|
|
6202
|
-
|
|
6116
|
+
fs13.writeFileSync(indexPath2, lines.join("\n"));
|
|
6203
6117
|
}
|
|
6204
6118
|
function evictIfNeeded(map) {
|
|
6205
6119
|
if (map.size > MAX_CACHE_ENTRIES) {
|
|
@@ -6212,8 +6126,8 @@ async function getStateDir(projectPath, stream, session) {
|
|
|
6212
6126
|
const sessionResult = resolveSessionDir(projectPath, session, { create: true });
|
|
6213
6127
|
return sessionResult;
|
|
6214
6128
|
}
|
|
6215
|
-
const streamsIndexPath =
|
|
6216
|
-
const hasStreams =
|
|
6129
|
+
const streamsIndexPath = path11.join(projectPath, HARNESS_DIR, "streams", INDEX_FILE);
|
|
6130
|
+
const hasStreams = fs14.existsSync(streamsIndexPath);
|
|
6217
6131
|
if (stream || hasStreams) {
|
|
6218
6132
|
const result = await resolveStreamPath(projectPath, stream ? { stream } : void 0);
|
|
6219
6133
|
if (result.ok) {
|
|
@@ -6223,18 +6137,18 @@ async function getStateDir(projectPath, stream, session) {
|
|
|
6223
6137
|
return result;
|
|
6224
6138
|
}
|
|
6225
6139
|
}
|
|
6226
|
-
return Ok(
|
|
6140
|
+
return Ok(path11.join(projectPath, HARNESS_DIR));
|
|
6227
6141
|
}
|
|
6228
6142
|
async function loadState(projectPath, stream, session) {
|
|
6229
6143
|
try {
|
|
6230
6144
|
const dirResult = await getStateDir(projectPath, stream, session);
|
|
6231
6145
|
if (!dirResult.ok) return dirResult;
|
|
6232
6146
|
const stateDir = dirResult.value;
|
|
6233
|
-
const statePath =
|
|
6234
|
-
if (!
|
|
6147
|
+
const statePath = path12.join(stateDir, STATE_FILE);
|
|
6148
|
+
if (!fs15.existsSync(statePath)) {
|
|
6235
6149
|
return Ok({ ...DEFAULT_STATE });
|
|
6236
6150
|
}
|
|
6237
|
-
const raw =
|
|
6151
|
+
const raw = fs15.readFileSync(statePath, "utf-8");
|
|
6238
6152
|
const parsed = JSON.parse(raw);
|
|
6239
6153
|
const result = HarnessStateSchema.safeParse(parsed);
|
|
6240
6154
|
if (!result.success) {
|
|
@@ -6252,11 +6166,11 @@ async function saveState(projectPath, state, stream, session) {
|
|
|
6252
6166
|
const dirResult = await getStateDir(projectPath, stream, session);
|
|
6253
6167
|
if (!dirResult.ok) return dirResult;
|
|
6254
6168
|
const stateDir = dirResult.value;
|
|
6255
|
-
const statePath =
|
|
6256
|
-
|
|
6169
|
+
const statePath = path12.join(stateDir, STATE_FILE);
|
|
6170
|
+
fs15.mkdirSync(stateDir, { recursive: true });
|
|
6257
6171
|
const tmpPath = statePath + ".tmp";
|
|
6258
|
-
|
|
6259
|
-
|
|
6172
|
+
fs15.writeFileSync(tmpPath, JSON.stringify(state, null, 2));
|
|
6173
|
+
fs15.renameSync(tmpPath, statePath);
|
|
6260
6174
|
return Ok(void 0);
|
|
6261
6175
|
} catch (error) {
|
|
6262
6176
|
return Err(
|
|
@@ -6342,10 +6256,10 @@ function computeContentHash(text) {
|
|
|
6342
6256
|
return crypto2.createHash("sha256").update(text).digest("hex").slice(0, 16);
|
|
6343
6257
|
}
|
|
6344
6258
|
function loadContentHashes(stateDir) {
|
|
6345
|
-
const hashesPath =
|
|
6346
|
-
if (!
|
|
6259
|
+
const hashesPath = path13.join(stateDir, CONTENT_HASHES_FILE);
|
|
6260
|
+
if (!fs16.existsSync(hashesPath)) return {};
|
|
6347
6261
|
try {
|
|
6348
|
-
const raw =
|
|
6262
|
+
const raw = fs16.readFileSync(hashesPath, "utf-8");
|
|
6349
6263
|
const parsed = JSON.parse(raw);
|
|
6350
6264
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return {};
|
|
6351
6265
|
return parsed;
|
|
@@ -6354,15 +6268,15 @@ function loadContentHashes(stateDir) {
|
|
|
6354
6268
|
}
|
|
6355
6269
|
}
|
|
6356
6270
|
function saveContentHashes(stateDir, index) {
|
|
6357
|
-
const hashesPath =
|
|
6271
|
+
const hashesPath = path13.join(stateDir, CONTENT_HASHES_FILE);
|
|
6358
6272
|
const tmpPath = hashesPath + ".tmp";
|
|
6359
|
-
|
|
6360
|
-
|
|
6273
|
+
fs16.writeFileSync(tmpPath, JSON.stringify(index, null, 2) + "\n");
|
|
6274
|
+
fs16.renameSync(tmpPath, hashesPath);
|
|
6361
6275
|
}
|
|
6362
6276
|
function rebuildContentHashes(stateDir, learningsFile) {
|
|
6363
|
-
const learningsPath =
|
|
6364
|
-
if (!
|
|
6365
|
-
const content =
|
|
6277
|
+
const learningsPath = path13.join(stateDir, learningsFile);
|
|
6278
|
+
if (!fs16.existsSync(learningsPath)) return {};
|
|
6279
|
+
const content = fs16.readFileSync(learningsPath, "utf-8");
|
|
6366
6280
|
const lines = content.split("\n");
|
|
6367
6281
|
const index = {};
|
|
6368
6282
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -6421,18 +6335,18 @@ async function loadRelevantLearnings(projectPath, skillName, stream, session) {
|
|
|
6421
6335
|
const dirResult = await getStateDir(projectPath, stream, session);
|
|
6422
6336
|
if (!dirResult.ok) return dirResult;
|
|
6423
6337
|
const stateDir = dirResult.value;
|
|
6424
|
-
const learningsPath =
|
|
6425
|
-
if (!
|
|
6338
|
+
const learningsPath = path14.join(stateDir, LEARNINGS_FILE);
|
|
6339
|
+
if (!fs17.existsSync(learningsPath)) {
|
|
6426
6340
|
return Ok([]);
|
|
6427
6341
|
}
|
|
6428
|
-
const stats =
|
|
6342
|
+
const stats = fs17.statSync(learningsPath);
|
|
6429
6343
|
const cacheKey = learningsPath;
|
|
6430
6344
|
const cached = learningsCacheMap.get(cacheKey);
|
|
6431
6345
|
let entries;
|
|
6432
6346
|
if (cached && cached.mtimeMs === stats.mtimeMs) {
|
|
6433
6347
|
entries = cached.entries;
|
|
6434
6348
|
} else {
|
|
6435
|
-
const content =
|
|
6349
|
+
const content = fs17.readFileSync(learningsPath, "utf-8");
|
|
6436
6350
|
const lines = content.split("\n");
|
|
6437
6351
|
entries = [];
|
|
6438
6352
|
let currentBlock = [];
|
|
@@ -6568,16 +6482,16 @@ function checkOverlap(newEntry, existingEntries, options) {
|
|
|
6568
6482
|
};
|
|
6569
6483
|
}
|
|
6570
6484
|
function sleep(ms) {
|
|
6571
|
-
return new Promise((
|
|
6485
|
+
return new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
6572
6486
|
}
|
|
6573
6487
|
async function acquireFileLock(lockPath) {
|
|
6574
6488
|
for (let attempt = 0; attempt < LOCK_RETRIES; attempt++) {
|
|
6575
6489
|
try {
|
|
6576
|
-
const fd =
|
|
6490
|
+
const fd = fs18.openSync(
|
|
6577
6491
|
lockPath,
|
|
6578
|
-
|
|
6492
|
+
fs18.constants.O_CREAT | fs18.constants.O_EXCL | fs18.constants.O_WRONLY
|
|
6579
6493
|
);
|
|
6580
|
-
|
|
6494
|
+
fs18.closeSync(fd);
|
|
6581
6495
|
return;
|
|
6582
6496
|
} catch (err) {
|
|
6583
6497
|
const isExist = err instanceof Error && "code" in err && err.code === "EEXIST";
|
|
@@ -6591,20 +6505,20 @@ async function acquireFileLock(lockPath) {
|
|
|
6591
6505
|
}
|
|
6592
6506
|
function releaseFileLock(lockPath) {
|
|
6593
6507
|
try {
|
|
6594
|
-
|
|
6508
|
+
fs18.unlinkSync(lockPath);
|
|
6595
6509
|
} catch {
|
|
6596
6510
|
}
|
|
6597
6511
|
}
|
|
6598
6512
|
function loadOrRebuildHashes(stateDir, learningsPath) {
|
|
6599
|
-
const hashesPath =
|
|
6600
|
-
if (
|
|
6513
|
+
const hashesPath = path15.join(stateDir, CONTENT_HASHES_FILE);
|
|
6514
|
+
if (fs18.existsSync(hashesPath)) {
|
|
6601
6515
|
const hashes = loadContentHashes(stateDir);
|
|
6602
|
-
if (Object.keys(hashes).length === 0 &&
|
|
6516
|
+
if (Object.keys(hashes).length === 0 && fs18.existsSync(learningsPath)) {
|
|
6603
6517
|
return rebuildContentHashes(stateDir, LEARNINGS_FILE);
|
|
6604
6518
|
}
|
|
6605
6519
|
return hashes;
|
|
6606
6520
|
}
|
|
6607
|
-
if (
|
|
6521
|
+
if (fs18.existsSync(learningsPath)) {
|
|
6608
6522
|
return rebuildContentHashes(stateDir, LEARNINGS_FILE);
|
|
6609
6523
|
}
|
|
6610
6524
|
return {};
|
|
@@ -6628,8 +6542,8 @@ async function appendLearning(projectPath, learning, skillName, outcome, stream,
|
|
|
6628
6542
|
const dirResult = await getStateDir(projectPath, stream, session);
|
|
6629
6543
|
if (!dirResult.ok) return dirResult;
|
|
6630
6544
|
const stateDir = dirResult.value;
|
|
6631
|
-
const learningsPath =
|
|
6632
|
-
|
|
6545
|
+
const learningsPath = path15.join(stateDir, LEARNINGS_FILE);
|
|
6546
|
+
fs18.mkdirSync(stateDir, { recursive: true });
|
|
6633
6547
|
const lockPath = learningsPath + ".lock";
|
|
6634
6548
|
await acquireFileLock(lockPath);
|
|
6635
6549
|
try {
|
|
@@ -6659,17 +6573,17 @@ ${bulletLine}
|
|
|
6659
6573
|
`;
|
|
6660
6574
|
let overlapResult;
|
|
6661
6575
|
let existingLineCount;
|
|
6662
|
-
if (!
|
|
6663
|
-
|
|
6576
|
+
if (!fs18.existsSync(learningsPath)) {
|
|
6577
|
+
fs18.writeFileSync(learningsPath, `# Learnings
|
|
6664
6578
|
${entry}`);
|
|
6665
6579
|
existingLineCount = 1;
|
|
6666
6580
|
} else {
|
|
6667
|
-
const existingContent =
|
|
6581
|
+
const existingContent = fs18.readFileSync(learningsPath, "utf-8");
|
|
6668
6582
|
const existingEntries = existingContent.split("\n").filter((line) => /^- \*\*\d{4}-\d{2}-\d{2}/.test(line));
|
|
6669
6583
|
const overlap = checkOverlap(bulletLine, existingEntries);
|
|
6670
6584
|
if (overlap.score >= 0.7) overlapResult = overlap;
|
|
6671
6585
|
existingLineCount = existingContent.split("\n").length;
|
|
6672
|
-
|
|
6586
|
+
fs18.appendFileSync(learningsPath, entry);
|
|
6673
6587
|
}
|
|
6674
6588
|
const bulletLineNum = existingLineCount + 2;
|
|
6675
6589
|
contentHashes[contentHash] = { date: timestamp ?? "", line: bulletLineNum };
|
|
@@ -6774,11 +6688,11 @@ async function loadIndexEntries(projectPath, skillName, stream, session) {
|
|
|
6774
6688
|
const dirResult = await getStateDir(projectPath, stream, session);
|
|
6775
6689
|
if (!dirResult.ok) return dirResult;
|
|
6776
6690
|
const stateDir = dirResult.value;
|
|
6777
|
-
const learningsPath =
|
|
6778
|
-
if (!
|
|
6691
|
+
const learningsPath = path15.join(stateDir, LEARNINGS_FILE);
|
|
6692
|
+
if (!fs18.existsSync(learningsPath)) {
|
|
6779
6693
|
return Ok([]);
|
|
6780
6694
|
}
|
|
6781
|
-
const content =
|
|
6695
|
+
const content = fs18.readFileSync(learningsPath, "utf-8");
|
|
6782
6696
|
const lines = content.split("\n");
|
|
6783
6697
|
const indexEntries = [];
|
|
6784
6698
|
let pendingFrontmatter = null;
|
|
@@ -6820,16 +6734,16 @@ async function archiveLearnings(projectPath, entries, stream) {
|
|
|
6820
6734
|
const dirResult = await getStateDir(projectPath, stream);
|
|
6821
6735
|
if (!dirResult.ok) return dirResult;
|
|
6822
6736
|
const stateDir = dirResult.value;
|
|
6823
|
-
const archiveDir =
|
|
6824
|
-
|
|
6737
|
+
const archiveDir = path16.join(stateDir, "learnings-archive");
|
|
6738
|
+
fs19.mkdirSync(archiveDir, { recursive: true });
|
|
6825
6739
|
const now = /* @__PURE__ */ new Date();
|
|
6826
6740
|
const yearMonth = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`;
|
|
6827
|
-
const archivePath =
|
|
6741
|
+
const archivePath = path16.join(archiveDir, `${yearMonth}.md`);
|
|
6828
6742
|
const archiveContent = entries.join("\n\n") + "\n";
|
|
6829
|
-
if (
|
|
6830
|
-
|
|
6743
|
+
if (fs19.existsSync(archivePath)) {
|
|
6744
|
+
fs19.appendFileSync(archivePath, "\n" + archiveContent);
|
|
6831
6745
|
} else {
|
|
6832
|
-
|
|
6746
|
+
fs19.writeFileSync(archivePath, `# Learnings Archive
|
|
6833
6747
|
|
|
6834
6748
|
${archiveContent}`);
|
|
6835
6749
|
}
|
|
@@ -6847,8 +6761,8 @@ async function pruneLearnings(projectPath, stream) {
|
|
|
6847
6761
|
const dirResult = await getStateDir(projectPath, stream);
|
|
6848
6762
|
if (!dirResult.ok) return dirResult;
|
|
6849
6763
|
const stateDir = dirResult.value;
|
|
6850
|
-
const learningsPath =
|
|
6851
|
-
if (!
|
|
6764
|
+
const learningsPath = path16.join(stateDir, LEARNINGS_FILE);
|
|
6765
|
+
if (!fs19.existsSync(learningsPath)) {
|
|
6852
6766
|
return Ok({ kept: 0, archived: 0, patterns: [] });
|
|
6853
6767
|
}
|
|
6854
6768
|
const loadResult = await loadRelevantLearnings(projectPath, void 0, stream);
|
|
@@ -6879,7 +6793,7 @@ async function pruneLearnings(projectPath, stream) {
|
|
|
6879
6793
|
if (!archiveResult.ok) return archiveResult;
|
|
6880
6794
|
}
|
|
6881
6795
|
const newContent = "# Learnings\n\n" + toKeep.join("\n\n") + "\n";
|
|
6882
|
-
|
|
6796
|
+
fs19.writeFileSync(learningsPath, newContent);
|
|
6883
6797
|
invalidateLearningsCacheEntry(learningsPath);
|
|
6884
6798
|
return Ok({
|
|
6885
6799
|
kept: toKeep.length,
|
|
@@ -6923,19 +6837,19 @@ async function promoteSessionLearnings(projectPath, sessionSlug, stream) {
|
|
|
6923
6837
|
const dirResult = await getStateDir(projectPath, stream);
|
|
6924
6838
|
if (!dirResult.ok) return dirResult;
|
|
6925
6839
|
const stateDir = dirResult.value;
|
|
6926
|
-
const globalPath =
|
|
6927
|
-
const existingGlobal =
|
|
6840
|
+
const globalPath = path16.join(stateDir, LEARNINGS_FILE);
|
|
6841
|
+
const existingGlobal = fs19.existsSync(globalPath) ? fs19.readFileSync(globalPath, "utf-8") : "";
|
|
6928
6842
|
const newEntries = toPromote.filter((entry) => !existingGlobal.includes(entry.trim()));
|
|
6929
6843
|
if (newEntries.length === 0) {
|
|
6930
6844
|
return Ok({ promoted: 0, skipped: skipped + toPromote.length });
|
|
6931
6845
|
}
|
|
6932
6846
|
const promotedContent = newEntries.join("\n\n") + "\n";
|
|
6933
6847
|
if (!existingGlobal) {
|
|
6934
|
-
|
|
6848
|
+
fs19.writeFileSync(globalPath, `# Learnings
|
|
6935
6849
|
|
|
6936
6850
|
${promotedContent}`);
|
|
6937
6851
|
} else {
|
|
6938
|
-
|
|
6852
|
+
fs19.appendFileSync(globalPath, "\n\n" + promotedContent);
|
|
6939
6853
|
}
|
|
6940
6854
|
invalidateLearningsCacheEntry(globalPath);
|
|
6941
6855
|
return Ok({
|
|
@@ -6971,13 +6885,13 @@ async function detectStaleLearnings(projectPath, stream, session) {
|
|
|
6971
6885
|
freshCount++;
|
|
6972
6886
|
continue;
|
|
6973
6887
|
}
|
|
6974
|
-
const resolvedRoot =
|
|
6888
|
+
const resolvedRoot = path17.resolve(projectPath);
|
|
6975
6889
|
const missing = refs.filter((ref) => {
|
|
6976
|
-
const absPath =
|
|
6977
|
-
if (!absPath.startsWith(resolvedRoot +
|
|
6890
|
+
const absPath = path17.resolve(projectPath, ref);
|
|
6891
|
+
if (!absPath.startsWith(resolvedRoot + path17.sep) && absPath !== resolvedRoot) {
|
|
6978
6892
|
return false;
|
|
6979
6893
|
}
|
|
6980
|
-
return !
|
|
6894
|
+
return !fs20.existsSync(absPath);
|
|
6981
6895
|
});
|
|
6982
6896
|
if (missing.length > 0) {
|
|
6983
6897
|
const idx = extractIndexEntry(entry);
|
|
@@ -7012,17 +6926,17 @@ async function appendFailure(projectPath, description, skillName, type, stream,
|
|
|
7012
6926
|
const dirResult = await getStateDir(projectPath, stream, session);
|
|
7013
6927
|
if (!dirResult.ok) return dirResult;
|
|
7014
6928
|
const stateDir = dirResult.value;
|
|
7015
|
-
const failuresPath =
|
|
7016
|
-
|
|
6929
|
+
const failuresPath = path18.join(stateDir, FAILURES_FILE);
|
|
6930
|
+
fs21.mkdirSync(stateDir, { recursive: true });
|
|
7017
6931
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
7018
6932
|
const entry = `
|
|
7019
6933
|
- **${timestamp} [skill:${skillName}] [type:${type}]:** ${description}
|
|
7020
6934
|
`;
|
|
7021
|
-
if (!
|
|
7022
|
-
|
|
6935
|
+
if (!fs21.existsSync(failuresPath)) {
|
|
6936
|
+
fs21.writeFileSync(failuresPath, `# Failures
|
|
7023
6937
|
${entry}`);
|
|
7024
6938
|
} else {
|
|
7025
|
-
|
|
6939
|
+
fs21.appendFileSync(failuresPath, entry);
|
|
7026
6940
|
}
|
|
7027
6941
|
failuresCacheMap.delete(failuresPath);
|
|
7028
6942
|
return Ok(void 0);
|
|
@@ -7039,17 +6953,17 @@ async function loadFailures(projectPath, stream, session) {
|
|
|
7039
6953
|
const dirResult = await getStateDir(projectPath, stream, session);
|
|
7040
6954
|
if (!dirResult.ok) return dirResult;
|
|
7041
6955
|
const stateDir = dirResult.value;
|
|
7042
|
-
const failuresPath =
|
|
7043
|
-
if (!
|
|
6956
|
+
const failuresPath = path18.join(stateDir, FAILURES_FILE);
|
|
6957
|
+
if (!fs21.existsSync(failuresPath)) {
|
|
7044
6958
|
return Ok([]);
|
|
7045
6959
|
}
|
|
7046
|
-
const stats =
|
|
6960
|
+
const stats = fs21.statSync(failuresPath);
|
|
7047
6961
|
const cacheKey = failuresPath;
|
|
7048
6962
|
const cached = failuresCacheMap.get(cacheKey);
|
|
7049
6963
|
if (cached && cached.mtimeMs === stats.mtimeMs) {
|
|
7050
6964
|
return Ok(cached.entries);
|
|
7051
6965
|
}
|
|
7052
|
-
const content =
|
|
6966
|
+
const content = fs21.readFileSync(failuresPath, "utf-8");
|
|
7053
6967
|
const entries = [];
|
|
7054
6968
|
for (const line of content.split("\n")) {
|
|
7055
6969
|
const match = line.match(FAILURE_LINE_REGEX);
|
|
@@ -7078,20 +6992,20 @@ async function archiveFailures(projectPath, stream, session) {
|
|
|
7078
6992
|
const dirResult = await getStateDir(projectPath, stream, session);
|
|
7079
6993
|
if (!dirResult.ok) return dirResult;
|
|
7080
6994
|
const stateDir = dirResult.value;
|
|
7081
|
-
const failuresPath =
|
|
7082
|
-
if (!
|
|
6995
|
+
const failuresPath = path18.join(stateDir, FAILURES_FILE);
|
|
6996
|
+
if (!fs21.existsSync(failuresPath)) {
|
|
7083
6997
|
return Ok(void 0);
|
|
7084
6998
|
}
|
|
7085
|
-
const archiveDir =
|
|
7086
|
-
|
|
6999
|
+
const archiveDir = path18.join(stateDir, "archive");
|
|
7000
|
+
fs21.mkdirSync(archiveDir, { recursive: true });
|
|
7087
7001
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
7088
7002
|
let archiveName = `failures-${date}.md`;
|
|
7089
7003
|
let counter = 2;
|
|
7090
|
-
while (
|
|
7004
|
+
while (fs21.existsSync(path18.join(archiveDir, archiveName))) {
|
|
7091
7005
|
archiveName = `failures-${date}-${counter}.md`;
|
|
7092
7006
|
counter++;
|
|
7093
7007
|
}
|
|
7094
|
-
|
|
7008
|
+
fs21.renameSync(failuresPath, path18.join(archiveDir, archiveName));
|
|
7095
7009
|
failuresCacheMap.delete(failuresPath);
|
|
7096
7010
|
return Ok(void 0);
|
|
7097
7011
|
} catch (error) {
|
|
@@ -7107,11 +7021,11 @@ async function saveHandoff(projectPath, handoff, stream, session) {
|
|
|
7107
7021
|
const dirResult = await getStateDir(projectPath, stream, session);
|
|
7108
7022
|
if (!dirResult.ok) return dirResult;
|
|
7109
7023
|
const stateDir = dirResult.value;
|
|
7110
|
-
const handoffPath =
|
|
7111
|
-
|
|
7024
|
+
const handoffPath = path19.join(stateDir, HANDOFF_FILE);
|
|
7025
|
+
fs22.mkdirSync(stateDir, { recursive: true });
|
|
7112
7026
|
const tmpPath = handoffPath + ".tmp";
|
|
7113
|
-
|
|
7114
|
-
|
|
7027
|
+
fs22.writeFileSync(tmpPath, JSON.stringify(handoff, null, 2));
|
|
7028
|
+
fs22.renameSync(tmpPath, handoffPath);
|
|
7115
7029
|
return Ok(void 0);
|
|
7116
7030
|
} catch (error) {
|
|
7117
7031
|
return Err(
|
|
@@ -7124,11 +7038,11 @@ async function loadHandoff(projectPath, stream, session) {
|
|
|
7124
7038
|
const dirResult = await getStateDir(projectPath, stream, session);
|
|
7125
7039
|
if (!dirResult.ok) return dirResult;
|
|
7126
7040
|
const stateDir = dirResult.value;
|
|
7127
|
-
const handoffPath =
|
|
7128
|
-
if (!
|
|
7041
|
+
const handoffPath = path19.join(stateDir, HANDOFF_FILE);
|
|
7042
|
+
if (!fs22.existsSync(handoffPath)) {
|
|
7129
7043
|
return Ok(null);
|
|
7130
7044
|
}
|
|
7131
|
-
const raw =
|
|
7045
|
+
const raw = fs22.readFileSync(handoffPath, "utf-8");
|
|
7132
7046
|
const parsed = JSON.parse(raw);
|
|
7133
7047
|
const result = HandoffSchema.safeParse(parsed);
|
|
7134
7048
|
if (!result.success) {
|
|
@@ -7142,28 +7056,28 @@ async function loadHandoff(projectPath, stream, session) {
|
|
|
7142
7056
|
}
|
|
7143
7057
|
}
|
|
7144
7058
|
function loadChecksFromConfig(gateConfigPath) {
|
|
7145
|
-
if (!
|
|
7146
|
-
const raw = JSON.parse(
|
|
7059
|
+
if (!fs23.existsSync(gateConfigPath)) return [];
|
|
7060
|
+
const raw = JSON.parse(fs23.readFileSync(gateConfigPath, "utf-8"));
|
|
7147
7061
|
const config = GateConfigSchema.safeParse(raw);
|
|
7148
7062
|
if (config.success && config.data.checks) return config.data.checks;
|
|
7149
7063
|
return [];
|
|
7150
7064
|
}
|
|
7151
7065
|
function discoverChecksFromProject(projectPath) {
|
|
7152
7066
|
const checks = [];
|
|
7153
|
-
const packageJsonPath =
|
|
7154
|
-
if (
|
|
7155
|
-
const pkg = JSON.parse(
|
|
7067
|
+
const packageJsonPath = path20.join(projectPath, "package.json");
|
|
7068
|
+
if (fs23.existsSync(packageJsonPath)) {
|
|
7069
|
+
const pkg = JSON.parse(fs23.readFileSync(packageJsonPath, "utf-8"));
|
|
7156
7070
|
const scripts = pkg.scripts || {};
|
|
7157
7071
|
if (scripts.test) checks.push({ name: "test", command: "npm test" });
|
|
7158
7072
|
if (scripts.lint) checks.push({ name: "lint", command: "npm run lint" });
|
|
7159
7073
|
if (scripts.typecheck) checks.push({ name: "typecheck", command: "npm run typecheck" });
|
|
7160
7074
|
if (scripts.build) checks.push({ name: "build", command: "npm run build" });
|
|
7161
7075
|
}
|
|
7162
|
-
if (
|
|
7076
|
+
if (fs23.existsSync(path20.join(projectPath, "go.mod"))) {
|
|
7163
7077
|
checks.push({ name: "test", command: "go test ./..." });
|
|
7164
7078
|
checks.push({ name: "build", command: "go build ./..." });
|
|
7165
7079
|
}
|
|
7166
|
-
if (
|
|
7080
|
+
if (fs23.existsSync(path20.join(projectPath, "pyproject.toml")) || fs23.existsSync(path20.join(projectPath, "setup.py"))) {
|
|
7167
7081
|
checks.push({ name: "test", command: "python -m pytest" });
|
|
7168
7082
|
}
|
|
7169
7083
|
return checks;
|
|
@@ -7203,8 +7117,8 @@ function executeCheck(check, projectPath) {
|
|
|
7203
7117
|
}
|
|
7204
7118
|
}
|
|
7205
7119
|
async function runMechanicalGate(projectPath) {
|
|
7206
|
-
const harnessDir =
|
|
7207
|
-
const gateConfigPath =
|
|
7120
|
+
const harnessDir = path20.join(projectPath, HARNESS_DIR);
|
|
7121
|
+
const gateConfigPath = path20.join(harnessDir, GATE_CONFIG_FILE);
|
|
7208
7122
|
try {
|
|
7209
7123
|
let checks = loadChecksFromConfig(gateConfigPath);
|
|
7210
7124
|
if (checks.length === 0) {
|
|
@@ -7260,9 +7174,9 @@ function writeSessionSummary(projectPath, sessionSlug, data) {
|
|
|
7260
7174
|
const dirResult = resolveSessionDir(projectPath, sessionSlug, { create: true });
|
|
7261
7175
|
if (!dirResult.ok) return dirResult;
|
|
7262
7176
|
const sessionDir = dirResult.value;
|
|
7263
|
-
const summaryPath =
|
|
7177
|
+
const summaryPath = path21.join(sessionDir, SUMMARY_FILE);
|
|
7264
7178
|
const content = formatSummary(data);
|
|
7265
|
-
|
|
7179
|
+
fs24.writeFileSync(summaryPath, content);
|
|
7266
7180
|
const description = deriveIndexDescription(data);
|
|
7267
7181
|
updateSessionIndex(projectPath, sessionSlug, description);
|
|
7268
7182
|
return Ok(void 0);
|
|
@@ -7279,11 +7193,11 @@ function loadSessionSummary(projectPath, sessionSlug) {
|
|
|
7279
7193
|
const dirResult = resolveSessionDir(projectPath, sessionSlug);
|
|
7280
7194
|
if (!dirResult.ok) return dirResult;
|
|
7281
7195
|
const sessionDir = dirResult.value;
|
|
7282
|
-
const summaryPath =
|
|
7283
|
-
if (!
|
|
7196
|
+
const summaryPath = path21.join(sessionDir, SUMMARY_FILE);
|
|
7197
|
+
if (!fs24.existsSync(summaryPath)) {
|
|
7284
7198
|
return Ok(null);
|
|
7285
7199
|
}
|
|
7286
|
-
const content =
|
|
7200
|
+
const content = fs24.readFileSync(summaryPath, "utf-8");
|
|
7287
7201
|
return Ok(content);
|
|
7288
7202
|
} catch (error) {
|
|
7289
7203
|
return Err(
|
|
@@ -7295,11 +7209,11 @@ function loadSessionSummary(projectPath, sessionSlug) {
|
|
|
7295
7209
|
}
|
|
7296
7210
|
function listActiveSessions(projectPath) {
|
|
7297
7211
|
try {
|
|
7298
|
-
const indexPath2 =
|
|
7299
|
-
if (!
|
|
7212
|
+
const indexPath2 = path21.join(projectPath, HARNESS_DIR, SESSIONS_DIR, SESSION_INDEX_FILE);
|
|
7213
|
+
if (!fs24.existsSync(indexPath2)) {
|
|
7300
7214
|
return Ok(null);
|
|
7301
7215
|
}
|
|
7302
|
-
const content =
|
|
7216
|
+
const content = fs24.readFileSync(indexPath2, "utf-8");
|
|
7303
7217
|
return Ok(content);
|
|
7304
7218
|
} catch (error) {
|
|
7305
7219
|
return Err(
|
|
@@ -7320,12 +7234,12 @@ async function loadSessionState(projectPath, sessionSlug) {
|
|
|
7320
7234
|
const dirResult = resolveSessionDir(projectPath, sessionSlug);
|
|
7321
7235
|
if (!dirResult.ok) return dirResult;
|
|
7322
7236
|
const sessionDir = dirResult.value;
|
|
7323
|
-
const filePath =
|
|
7324
|
-
if (!
|
|
7237
|
+
const filePath = path22.join(sessionDir, SESSION_STATE_FILE);
|
|
7238
|
+
if (!fs25.existsSync(filePath)) {
|
|
7325
7239
|
return Ok(emptySections());
|
|
7326
7240
|
}
|
|
7327
7241
|
try {
|
|
7328
|
-
const raw =
|
|
7242
|
+
const raw = fs25.readFileSync(filePath, "utf-8");
|
|
7329
7243
|
const parsed = JSON.parse(raw);
|
|
7330
7244
|
const sections = emptySections();
|
|
7331
7245
|
for (const name of SESSION_SECTION_NAMES) {
|
|
@@ -7346,9 +7260,9 @@ async function saveSessionState(projectPath, sessionSlug, sections) {
|
|
|
7346
7260
|
const dirResult = resolveSessionDir(projectPath, sessionSlug, { create: true });
|
|
7347
7261
|
if (!dirResult.ok) return dirResult;
|
|
7348
7262
|
const sessionDir = dirResult.value;
|
|
7349
|
-
const filePath =
|
|
7263
|
+
const filePath = path22.join(sessionDir, SESSION_STATE_FILE);
|
|
7350
7264
|
try {
|
|
7351
|
-
|
|
7265
|
+
fs25.writeFileSync(filePath, JSON.stringify(sections, null, 2));
|
|
7352
7266
|
return Ok(void 0);
|
|
7353
7267
|
} catch (error) {
|
|
7354
7268
|
return Err(
|
|
@@ -7404,26 +7318,26 @@ async function archiveSession(projectPath, sessionSlug, options = {}) {
|
|
|
7404
7318
|
const dirResult = resolveSessionDir(projectPath, sessionSlug);
|
|
7405
7319
|
if (!dirResult.ok) return dirResult;
|
|
7406
7320
|
const sessionDir = dirResult.value;
|
|
7407
|
-
if (!
|
|
7321
|
+
if (!fs26.existsSync(sessionDir)) {
|
|
7408
7322
|
return Err(new Error(`Session '${sessionSlug}' not found at ${sessionDir}`));
|
|
7409
7323
|
}
|
|
7410
|
-
const archiveBase =
|
|
7324
|
+
const archiveBase = path23.join(projectPath, HARNESS_DIR, ARCHIVE_DIR, "sessions");
|
|
7411
7325
|
try {
|
|
7412
|
-
|
|
7326
|
+
fs26.mkdirSync(archiveBase, { recursive: true });
|
|
7413
7327
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
7414
7328
|
let archiveName = `${sessionSlug}-${date}`;
|
|
7415
7329
|
let counter = 1;
|
|
7416
|
-
while (
|
|
7330
|
+
while (fs26.existsSync(path23.join(archiveBase, archiveName))) {
|
|
7417
7331
|
archiveName = `${sessionSlug}-${date}-${counter}`;
|
|
7418
7332
|
counter++;
|
|
7419
7333
|
}
|
|
7420
|
-
const dest =
|
|
7334
|
+
const dest = path23.join(archiveBase, archiveName);
|
|
7421
7335
|
try {
|
|
7422
|
-
|
|
7336
|
+
fs26.renameSync(sessionDir, dest);
|
|
7423
7337
|
} catch (renameErr) {
|
|
7424
7338
|
if (renameErr instanceof Error && "code" in renameErr && renameErr.code === "EXDEV") {
|
|
7425
|
-
|
|
7426
|
-
|
|
7339
|
+
fs26.cpSync(sessionDir, dest, { recursive: true });
|
|
7340
|
+
fs26.rmSync(sessionDir, { recursive: true });
|
|
7427
7341
|
} else {
|
|
7428
7342
|
throw renameErr;
|
|
7429
7343
|
}
|
|
@@ -7458,8 +7372,8 @@ function loadKnownHashes(eventsPath) {
|
|
|
7458
7372
|
const cached = knownHashesCache.get(eventsPath);
|
|
7459
7373
|
if (cached) return cached;
|
|
7460
7374
|
const hashes = /* @__PURE__ */ new Set();
|
|
7461
|
-
if (
|
|
7462
|
-
const content =
|
|
7375
|
+
if (fs27.existsSync(eventsPath)) {
|
|
7376
|
+
const content = fs27.readFileSync(eventsPath, "utf-8");
|
|
7463
7377
|
const lines = content.split("\n").filter((line) => line.trim() !== "");
|
|
7464
7378
|
for (const line of lines) {
|
|
7465
7379
|
try {
|
|
@@ -7482,8 +7396,8 @@ async function emitEvent(projectPath, event, options) {
|
|
|
7482
7396
|
const dirResult = await getStateDir(projectPath, options?.stream, options?.session);
|
|
7483
7397
|
if (!dirResult.ok) return dirResult;
|
|
7484
7398
|
const stateDir = dirResult.value;
|
|
7485
|
-
const eventsPath =
|
|
7486
|
-
|
|
7399
|
+
const eventsPath = path24.join(stateDir, EVENTS_FILE);
|
|
7400
|
+
fs27.mkdirSync(stateDir, { recursive: true });
|
|
7487
7401
|
const contentHash = computeEventHash(event, options?.session);
|
|
7488
7402
|
const knownHashes = loadKnownHashes(eventsPath);
|
|
7489
7403
|
if (knownHashes.has(contentHash)) {
|
|
@@ -7497,7 +7411,7 @@ async function emitEvent(projectPath, event, options) {
|
|
|
7497
7411
|
if (options?.session) {
|
|
7498
7412
|
fullEvent.session = options.session;
|
|
7499
7413
|
}
|
|
7500
|
-
|
|
7414
|
+
fs27.appendFileSync(eventsPath, JSON.stringify(fullEvent) + "\n");
|
|
7501
7415
|
knownHashes.add(contentHash);
|
|
7502
7416
|
return Ok({ written: true });
|
|
7503
7417
|
} catch (error) {
|
|
@@ -7511,11 +7425,11 @@ async function loadEvents(projectPath, options) {
|
|
|
7511
7425
|
const dirResult = await getStateDir(projectPath, options?.stream, options?.session);
|
|
7512
7426
|
if (!dirResult.ok) return dirResult;
|
|
7513
7427
|
const stateDir = dirResult.value;
|
|
7514
|
-
const eventsPath =
|
|
7515
|
-
if (!
|
|
7428
|
+
const eventsPath = path24.join(stateDir, EVENTS_FILE);
|
|
7429
|
+
if (!fs27.existsSync(eventsPath)) {
|
|
7516
7430
|
return Ok([]);
|
|
7517
7431
|
}
|
|
7518
|
-
const content =
|
|
7432
|
+
const content = fs27.readFileSync(eventsPath, "utf-8");
|
|
7519
7433
|
const lines = content.split("\n").filter((line) => line.trim() !== "");
|
|
7520
7434
|
const events = [];
|
|
7521
7435
|
for (const line of lines) {
|
|
@@ -7764,11 +7678,11 @@ function nodeSubStacks(allDeps) {
|
|
|
7764
7678
|
return found;
|
|
7765
7679
|
}
|
|
7766
7680
|
function detectNodeStacks(projectRoot) {
|
|
7767
|
-
const pkgJsonPath =
|
|
7768
|
-
if (!
|
|
7681
|
+
const pkgJsonPath = path25.join(projectRoot, "package.json");
|
|
7682
|
+
if (!fs28.existsSync(pkgJsonPath)) return [];
|
|
7769
7683
|
const stacks = ["node"];
|
|
7770
7684
|
try {
|
|
7771
|
-
const pkgJson = JSON.parse(
|
|
7685
|
+
const pkgJson = JSON.parse(fs28.readFileSync(pkgJsonPath, "utf-8"));
|
|
7772
7686
|
const allDeps = { ...pkgJson.dependencies, ...pkgJson.devDependencies };
|
|
7773
7687
|
stacks.push(...nodeSubStacks(allDeps));
|
|
7774
7688
|
} catch {
|
|
@@ -7777,10 +7691,10 @@ function detectNodeStacks(projectRoot) {
|
|
|
7777
7691
|
}
|
|
7778
7692
|
function detectStack(projectRoot) {
|
|
7779
7693
|
const stacks = [...detectNodeStacks(projectRoot)];
|
|
7780
|
-
if (
|
|
7694
|
+
if (fs28.existsSync(path25.join(projectRoot, "go.mod"))) {
|
|
7781
7695
|
stacks.push("go");
|
|
7782
7696
|
}
|
|
7783
|
-
const hasPython =
|
|
7697
|
+
const hasPython = fs28.existsSync(path25.join(projectRoot, "requirements.txt")) || fs28.existsSync(path25.join(projectRoot, "pyproject.toml"));
|
|
7784
7698
|
if (hasPython) {
|
|
7785
7699
|
stacks.push("python");
|
|
7786
7700
|
}
|
|
@@ -7803,7 +7717,7 @@ function createOsvClient(options = {}) {
|
|
|
7803
7717
|
return {
|
|
7804
7718
|
async check(pkg) {
|
|
7805
7719
|
const cacheKey = `${pkg.ecosystem}-${sanitizePkgName(pkg.name)}@${pkg.version ?? "latest"}.json`;
|
|
7806
|
-
const cachePath =
|
|
7720
|
+
const cachePath = path26.join(cacheDir, cacheKey);
|
|
7807
7721
|
const cached = await readCache(cachePath, ttlMs);
|
|
7808
7722
|
if (cached) {
|
|
7809
7723
|
return { ...cached, source: "cache" };
|
|
@@ -7824,7 +7738,7 @@ function createOsvClient(options = {}) {
|
|
|
7824
7738
|
},
|
|
7825
7739
|
async clearCache() {
|
|
7826
7740
|
try {
|
|
7827
|
-
await
|
|
7741
|
+
await fs30.promises.rm(cacheDir, { recursive: true, force: true });
|
|
7828
7742
|
} catch {
|
|
7829
7743
|
}
|
|
7830
7744
|
}
|
|
@@ -7860,9 +7774,9 @@ function classify(advisories) {
|
|
|
7860
7774
|
}
|
|
7861
7775
|
async function readCache(cachePath, ttlMs) {
|
|
7862
7776
|
try {
|
|
7863
|
-
const stat2 = await
|
|
7777
|
+
const stat2 = await fs30.promises.stat(cachePath);
|
|
7864
7778
|
if (Date.now() - stat2.mtimeMs > ttlMs) return null;
|
|
7865
|
-
const buf = await
|
|
7779
|
+
const buf = await fs30.promises.readFile(cachePath, "utf-8");
|
|
7866
7780
|
const parsed = JSON.parse(buf);
|
|
7867
7781
|
return { malicious: parsed.malicious ?? [], other: parsed.other ?? [] };
|
|
7868
7782
|
} catch {
|
|
@@ -7871,8 +7785,8 @@ async function readCache(cachePath, ttlMs) {
|
|
|
7871
7785
|
}
|
|
7872
7786
|
async function writeCache(cachePath, payload) {
|
|
7873
7787
|
try {
|
|
7874
|
-
await
|
|
7875
|
-
await
|
|
7788
|
+
await fs30.promises.mkdir(path26.dirname(cachePath), { recursive: true });
|
|
7789
|
+
await fs30.promises.writeFile(cachePath, JSON.stringify(payload, null, 2), "utf-8");
|
|
7876
7790
|
} catch {
|
|
7877
7791
|
}
|
|
7878
7792
|
}
|
|
@@ -7908,13 +7822,13 @@ function getInjectionPatterns() {
|
|
|
7908
7822
|
}
|
|
7909
7823
|
function getTaintFilePath(projectRoot, sessionId) {
|
|
7910
7824
|
const id = sessionId || DEFAULT_SESSION_ID;
|
|
7911
|
-
return
|
|
7825
|
+
return join38(projectRoot, ".harness", `session-taint-${id}.json`);
|
|
7912
7826
|
}
|
|
7913
7827
|
function readTaint(projectRoot, sessionId) {
|
|
7914
7828
|
const filePath = getTaintFilePath(projectRoot, sessionId);
|
|
7915
7829
|
let content;
|
|
7916
7830
|
try {
|
|
7917
|
-
content =
|
|
7831
|
+
content = readFileSync21(filePath, "utf8");
|
|
7918
7832
|
} catch {
|
|
7919
7833
|
return null;
|
|
7920
7834
|
}
|
|
@@ -7990,14 +7904,14 @@ function clearTaint(projectRoot, sessionId) {
|
|
|
7990
7904
|
return 0;
|
|
7991
7905
|
}
|
|
7992
7906
|
}
|
|
7993
|
-
const harnessDir =
|
|
7907
|
+
const harnessDir = join38(projectRoot, ".harness");
|
|
7994
7908
|
let count = 0;
|
|
7995
7909
|
try {
|
|
7996
|
-
const files =
|
|
7910
|
+
const files = readdirSync2(harnessDir);
|
|
7997
7911
|
for (const file of files) {
|
|
7998
7912
|
if (file.startsWith("session-taint-") && file.endsWith(".json")) {
|
|
7999
7913
|
try {
|
|
8000
|
-
unlinkSync2(
|
|
7914
|
+
unlinkSync2(join38(harnessDir, file));
|
|
8001
7915
|
count++;
|
|
8002
7916
|
} catch {
|
|
8003
7917
|
}
|
|
@@ -8008,10 +7922,10 @@ function clearTaint(projectRoot, sessionId) {
|
|
|
8008
7922
|
return count;
|
|
8009
7923
|
}
|
|
8010
7924
|
function listTaintedSessions(projectRoot) {
|
|
8011
|
-
const harnessDir =
|
|
7925
|
+
const harnessDir = join38(projectRoot, ".harness");
|
|
8012
7926
|
const sessions = [];
|
|
8013
7927
|
try {
|
|
8014
|
-
const files =
|
|
7928
|
+
const files = readdirSync2(harnessDir);
|
|
8015
7929
|
for (const file of files) {
|
|
8016
7930
|
if (file.startsWith("session-taint-") && file.endsWith(".json")) {
|
|
8017
7931
|
const sessionId = file.replace("session-taint-", "").replace(".json", "");
|
|
@@ -8080,7 +7994,7 @@ function securityFindingId(finding) {
|
|
|
8080
7994
|
}
|
|
8081
7995
|
async function runValidateCheck(projectRoot, config) {
|
|
8082
7996
|
const issues = [];
|
|
8083
|
-
const agentsPath =
|
|
7997
|
+
const agentsPath = path27.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
|
|
8084
7998
|
const result = await validateAgentsMap(agentsPath);
|
|
8085
7999
|
if (!result.ok) {
|
|
8086
8000
|
issues.push({ severity: "error", message: result.error.message });
|
|
@@ -8137,7 +8051,7 @@ async function runDepsCheck(projectRoot, config) {
|
|
|
8137
8051
|
}
|
|
8138
8052
|
async function runDocsCheck(projectRoot, config) {
|
|
8139
8053
|
const issues = [];
|
|
8140
|
-
const docsDir =
|
|
8054
|
+
const docsDir = path27.join(projectRoot, config.docsDir ?? "docs");
|
|
8141
8055
|
const entropyConfig = config.entropy || {};
|
|
8142
8056
|
const result = await checkDocCoverage("project", {
|
|
8143
8057
|
docsDir,
|
|
@@ -8316,7 +8230,7 @@ async function runTraceabilityCheck(projectRoot, config) {
|
|
|
8316
8230
|
const issues = [];
|
|
8317
8231
|
const traceConfig = config.traceability || {};
|
|
8318
8232
|
if (traceConfig.enabled === false) return issues;
|
|
8319
|
-
const graphDir =
|
|
8233
|
+
const graphDir = path27.join(projectRoot, ".harness", "graph");
|
|
8320
8234
|
const store = new GraphStore();
|
|
8321
8235
|
const loaded = await store.load(graphDir);
|
|
8322
8236
|
if (!loaded) {
|
|
@@ -8494,7 +8408,7 @@ async function runMechanicalChecks(options) {
|
|
|
8494
8408
|
};
|
|
8495
8409
|
if (!skip.includes("validate")) {
|
|
8496
8410
|
try {
|
|
8497
|
-
const agentsPath =
|
|
8411
|
+
const agentsPath = path28.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
|
|
8498
8412
|
const result = await validateAgentsMap(agentsPath);
|
|
8499
8413
|
if (!result.ok) {
|
|
8500
8414
|
statuses.validate = "fail";
|
|
@@ -8531,7 +8445,7 @@ async function runMechanicalChecks(options) {
|
|
|
8531
8445
|
statuses.validate = "fail";
|
|
8532
8446
|
findings.push({
|
|
8533
8447
|
tool: "validate",
|
|
8534
|
-
file:
|
|
8448
|
+
file: path28.join(projectRoot, "AGENTS.md"),
|
|
8535
8449
|
message: err instanceof Error ? err.message : String(err),
|
|
8536
8450
|
severity: "error"
|
|
8537
8451
|
});
|
|
@@ -8595,7 +8509,7 @@ async function runMechanicalChecks(options) {
|
|
|
8595
8509
|
(async () => {
|
|
8596
8510
|
const localFindings = [];
|
|
8597
8511
|
try {
|
|
8598
|
-
const docsDir =
|
|
8512
|
+
const docsDir = path28.join(projectRoot, config.docsDir ?? "docs");
|
|
8599
8513
|
const result = await checkDocCoverage("project", { docsDir });
|
|
8600
8514
|
if (!result.ok) {
|
|
8601
8515
|
statuses["check-docs"] = "warn";
|
|
@@ -8622,7 +8536,7 @@ async function runMechanicalChecks(options) {
|
|
|
8622
8536
|
statuses["check-docs"] = "warn";
|
|
8623
8537
|
localFindings.push({
|
|
8624
8538
|
tool: "check-docs",
|
|
8625
|
-
file:
|
|
8539
|
+
file: path28.join(projectRoot, "docs"),
|
|
8626
8540
|
message: err instanceof Error ? err.message : String(err),
|
|
8627
8541
|
severity: "warning"
|
|
8628
8542
|
});
|
|
@@ -8719,18 +8633,18 @@ function computeContextBudget(diffLines) {
|
|
|
8719
8633
|
return diffLines;
|
|
8720
8634
|
}
|
|
8721
8635
|
function isWithinProject(absPath, projectRoot) {
|
|
8722
|
-
const resolvedRoot =
|
|
8723
|
-
const resolvedPath =
|
|
8724
|
-
return resolvedPath.startsWith(resolvedRoot) || resolvedPath ===
|
|
8636
|
+
const resolvedRoot = path29.resolve(projectRoot) + path29.sep;
|
|
8637
|
+
const resolvedPath = path29.resolve(absPath);
|
|
8638
|
+
return resolvedPath.startsWith(resolvedRoot) || resolvedPath === path29.resolve(projectRoot);
|
|
8725
8639
|
}
|
|
8726
8640
|
async function readContextFile(projectRoot, filePath, reason) {
|
|
8727
|
-
const absPath =
|
|
8641
|
+
const absPath = path29.isAbsolute(filePath) ? filePath : path29.join(projectRoot, filePath);
|
|
8728
8642
|
if (!isWithinProject(absPath, projectRoot)) return null;
|
|
8729
8643
|
const result = await readFileContent(absPath);
|
|
8730
8644
|
if (!result.ok) return null;
|
|
8731
8645
|
const content = result.value;
|
|
8732
8646
|
const lines = content.split("\n").length;
|
|
8733
|
-
const relPath2 =
|
|
8647
|
+
const relPath2 = path29.isAbsolute(filePath) ? relativePosix(projectRoot, filePath) : filePath;
|
|
8734
8648
|
return { path: relPath2, content, reason, lines };
|
|
8735
8649
|
}
|
|
8736
8650
|
function extractImportSources2(content) {
|
|
@@ -8745,24 +8659,24 @@ function extractImportSources2(content) {
|
|
|
8745
8659
|
}
|
|
8746
8660
|
async function resolveImportPath(projectRoot, fromFile, importSource) {
|
|
8747
8661
|
if (!importSource.startsWith(".")) return null;
|
|
8748
|
-
const fromDir =
|
|
8749
|
-
const basePath =
|
|
8662
|
+
const fromDir = path29.dirname(path29.join(projectRoot, fromFile));
|
|
8663
|
+
const basePath = path29.resolve(fromDir, importSource);
|
|
8750
8664
|
if (!isWithinProject(basePath, projectRoot)) return null;
|
|
8751
8665
|
const relBase = relativePosix(projectRoot, basePath);
|
|
8752
|
-
const sourceExt =
|
|
8666
|
+
const sourceExt = path29.extname(relBase);
|
|
8753
8667
|
const candidates = [];
|
|
8754
8668
|
const fallbacks = JS_EXT_FALLBACKS[sourceExt];
|
|
8755
8669
|
if (fallbacks) {
|
|
8756
8670
|
const stripped = relBase.slice(0, -sourceExt.length);
|
|
8757
8671
|
for (const ext of fallbacks) candidates.push(stripped + ext);
|
|
8758
|
-
candidates.push(
|
|
8759
|
-
candidates.push(
|
|
8760
|
-
candidates.push(
|
|
8672
|
+
candidates.push(path29.join(stripped, "index.ts"));
|
|
8673
|
+
candidates.push(path29.join(stripped, "index.tsx"));
|
|
8674
|
+
candidates.push(path29.join(stripped, "index.jsx"));
|
|
8761
8675
|
}
|
|
8762
8676
|
candidates.push(relBase + ".ts", relBase + ".tsx", relBase + ".mts");
|
|
8763
|
-
candidates.push(
|
|
8677
|
+
candidates.push(path29.join(relBase, "index.ts"));
|
|
8764
8678
|
for (const candidate of candidates) {
|
|
8765
|
-
const absCandidate =
|
|
8679
|
+
const absCandidate = path29.join(projectRoot, candidate);
|
|
8766
8680
|
if (await fileExists(absCandidate)) {
|
|
8767
8681
|
return candidate;
|
|
8768
8682
|
}
|
|
@@ -8770,7 +8684,7 @@ async function resolveImportPath(projectRoot, fromFile, importSource) {
|
|
|
8770
8684
|
return null;
|
|
8771
8685
|
}
|
|
8772
8686
|
async function findTestFiles(projectRoot, sourceFile) {
|
|
8773
|
-
const baseName =
|
|
8687
|
+
const baseName = path29.basename(sourceFile, path29.extname(sourceFile));
|
|
8774
8688
|
const pattern = `**/${baseName}.{test,spec}.{ts,tsx,mts}`;
|
|
8775
8689
|
const results = await findFiles(pattern, projectRoot);
|
|
8776
8690
|
return results.map((f) => relativePosix(projectRoot, f));
|
|
@@ -10069,7 +9983,7 @@ function normalizePath(filePath, projectRoot) {
|
|
|
10069
9983
|
let normalized = filePath;
|
|
10070
9984
|
normalized = normalized.replace(/\\/g, "/");
|
|
10071
9985
|
const normalizedRoot = projectRoot.replace(/\\/g, "/");
|
|
10072
|
-
if (
|
|
9986
|
+
if (path30.isAbsolute(normalized)) {
|
|
10073
9987
|
const root = normalizedRoot.endsWith("/") ? normalizedRoot : normalizedRoot + "/";
|
|
10074
9988
|
if (normalized.startsWith(root)) {
|
|
10075
9989
|
normalized = normalized.slice(root.length);
|
|
@@ -10081,12 +9995,12 @@ function normalizePath(filePath, projectRoot) {
|
|
|
10081
9995
|
return normalized;
|
|
10082
9996
|
}
|
|
10083
9997
|
function resolveImportPath2(currentFile, importPath) {
|
|
10084
|
-
const dir =
|
|
10085
|
-
let resolved =
|
|
9998
|
+
const dir = path30.dirname(currentFile);
|
|
9999
|
+
let resolved = path30.join(dir, importPath).replace(/\\/g, "/");
|
|
10086
10000
|
if (!resolved.match(/\.(ts|tsx|js|jsx)$/)) {
|
|
10087
10001
|
resolved += ".ts";
|
|
10088
10002
|
}
|
|
10089
|
-
return
|
|
10003
|
+
return path30.normalize(resolved).replace(/\\/g, "/");
|
|
10090
10004
|
}
|
|
10091
10005
|
function enqueueImports(content, current, visited, queue, maxDepth) {
|
|
10092
10006
|
const importRegex = /import\s+.*?from\s+['"]([^'"]+)['"]/g;
|
|
@@ -10118,7 +10032,7 @@ function isMechanicallyExcluded(finding, exclusionSet, projectRoot) {
|
|
|
10118
10032
|
const normalizedFile = normalizePath(finding.file, projectRoot);
|
|
10119
10033
|
if (exclusionSet.isExcluded(normalizedFile, finding.lineRange)) return true;
|
|
10120
10034
|
if (exclusionSet.isExcluded(finding.file, finding.lineRange)) return true;
|
|
10121
|
-
const absoluteFile =
|
|
10035
|
+
const absoluteFile = path30.isAbsolute(finding.file) ? finding.file : path30.join(projectRoot, finding.file).replace(/\\/g, "/");
|
|
10122
10036
|
return exclusionSet.isExcluded(absoluteFile, finding.lineRange);
|
|
10123
10037
|
}
|
|
10124
10038
|
async function validateWithGraph(crossFileRefs, graph) {
|
|
@@ -11112,45 +11026,363 @@ function findParallelGroups(nodes) {
|
|
|
11112
11026
|
orphaned: [...index.orphaned].sort()
|
|
11113
11027
|
};
|
|
11114
11028
|
}
|
|
11115
|
-
function
|
|
11116
|
-
|
|
11117
|
-
|
|
11118
|
-
|
|
11119
|
-
|
|
11120
|
-
|
|
11121
|
-
|
|
11029
|
+
function findingsMatch(a, b) {
|
|
11030
|
+
if (a.id && b.id) return a.id === b.id;
|
|
11031
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
11032
|
+
}
|
|
11033
|
+
function deriveBlockingFindings(findings) {
|
|
11034
|
+
return findings.filter((f) => f.severity === "critical");
|
|
11035
|
+
}
|
|
11036
|
+
function deriveExitCode(assessment, blockingFindings) {
|
|
11037
|
+
return blockingFindings.length > 0 || assessment === "request-changes" ? 1 : 0;
|
|
11038
|
+
}
|
|
11039
|
+
function parseCiReviewVerdict(input) {
|
|
11040
|
+
return CiReviewVerdictSchema.parse(input);
|
|
11041
|
+
}
|
|
11042
|
+
function buildCiReviewVerdict(parts) {
|
|
11043
|
+
const findings = z12.array(ReviewFindingSchema).parse(parts.findings ?? []);
|
|
11044
|
+
const blockingFindings = deriveBlockingFindings(findings);
|
|
11045
|
+
const exitCode = deriveExitCode(parts.assessment, blockingFindings);
|
|
11046
|
+
return parseCiReviewVerdict({
|
|
11047
|
+
schemaVersion: CI_REVIEW_VERDICT_SCHEMA_VERSION,
|
|
11048
|
+
runner: parts.runner,
|
|
11049
|
+
ranLlmTier: parts.ranLlmTier,
|
|
11050
|
+
assessment: parts.assessment,
|
|
11051
|
+
findings,
|
|
11052
|
+
blockingFindings,
|
|
11053
|
+
exitCode,
|
|
11054
|
+
skipped: parts.skipped ?? false,
|
|
11055
|
+
skipReason: parts.skipReason
|
|
11056
|
+
});
|
|
11057
|
+
}
|
|
11058
|
+
function parseClaudeVerdict(raw) {
|
|
11059
|
+
const envelope = JSON.parse(raw);
|
|
11060
|
+
if (envelope.is_error === true) {
|
|
11061
|
+
throw new Error(
|
|
11062
|
+
`parseClaudeVerdict: claude transcript reported is_error=true (subtype=${envelope.subtype ?? "unknown"})`
|
|
11063
|
+
);
|
|
11122
11064
|
}
|
|
11123
|
-
if (
|
|
11124
|
-
|
|
11065
|
+
if (typeof envelope.result !== "string") {
|
|
11066
|
+
throw new Error(
|
|
11067
|
+
"parseClaudeVerdict: claude transcript envelope is missing a string `.result` field"
|
|
11068
|
+
);
|
|
11125
11069
|
}
|
|
11126
|
-
|
|
11127
|
-
|
|
11128
|
-
|
|
11129
|
-
|
|
11130
|
-
|
|
11131
|
-
|
|
11132
|
-
|
|
11133
|
-
|
|
11134
|
-
|
|
11135
|
-
|
|
11136
|
-
|
|
11070
|
+
const inner = JSON.parse(envelope.result);
|
|
11071
|
+
return buildCiReviewVerdict({
|
|
11072
|
+
runner: "claude",
|
|
11073
|
+
ranLlmTier: true,
|
|
11074
|
+
assessment: inner.assessment,
|
|
11075
|
+
findings: inner.findings
|
|
11076
|
+
});
|
|
11077
|
+
}
|
|
11078
|
+
function parseCodexVerdict(raw) {
|
|
11079
|
+
let lastAgentMessageText;
|
|
11080
|
+
for (const line of raw.split("\n")) {
|
|
11081
|
+
const trimmed = line.trim();
|
|
11082
|
+
if (trimmed.length === 0) continue;
|
|
11083
|
+
let event;
|
|
11084
|
+
try {
|
|
11085
|
+
event = JSON.parse(trimmed);
|
|
11086
|
+
} catch {
|
|
11087
|
+
continue;
|
|
11088
|
+
}
|
|
11089
|
+
if (event.type === "item.completed" && event.item?.type === "agent_message" && typeof event.item.text === "string") {
|
|
11090
|
+
lastAgentMessageText = event.item.text;
|
|
11137
11091
|
}
|
|
11138
11092
|
}
|
|
11139
|
-
if (
|
|
11140
|
-
|
|
11141
|
-
|
|
11093
|
+
if (lastAgentMessageText === void 0) {
|
|
11094
|
+
throw new Error(
|
|
11095
|
+
"parseCodexVerdict: no `item.completed` agent_message event found in codex JSONL stream"
|
|
11096
|
+
);
|
|
11142
11097
|
}
|
|
11143
|
-
|
|
11144
|
-
return
|
|
11098
|
+
const inner = JSON.parse(lastAgentMessageText);
|
|
11099
|
+
return buildCiReviewVerdict({
|
|
11100
|
+
runner: "codex",
|
|
11101
|
+
ranLlmTier: true,
|
|
11102
|
+
assessment: inner.assessment,
|
|
11103
|
+
findings: inner.findings
|
|
11104
|
+
});
|
|
11145
11105
|
}
|
|
11146
|
-
function
|
|
11147
|
-
|
|
11106
|
+
function stripJsonFence(text) {
|
|
11107
|
+
const fence = /^```(?:json)?\s*\n?([\s\S]*?)\n?```$/i.exec(text.trim());
|
|
11108
|
+
const inner = fence?.[1];
|
|
11109
|
+
return inner !== void 0 ? inner.trim() : text;
|
|
11148
11110
|
}
|
|
11149
|
-
function
|
|
11150
|
-
|
|
11111
|
+
function extractFirstJsonObject(text) {
|
|
11112
|
+
const start = text.indexOf("{");
|
|
11113
|
+
if (start === -1) return void 0;
|
|
11114
|
+
let depth = 0;
|
|
11115
|
+
for (let i = start; i < text.length; i++) {
|
|
11116
|
+
const ch = text[i];
|
|
11117
|
+
if (ch === "{") depth++;
|
|
11118
|
+
else if (ch === "}") {
|
|
11119
|
+
depth--;
|
|
11120
|
+
if (depth === 0) return text.slice(start, i + 1);
|
|
11121
|
+
}
|
|
11122
|
+
}
|
|
11123
|
+
return void 0;
|
|
11151
11124
|
}
|
|
11152
|
-
function
|
|
11153
|
-
|
|
11125
|
+
function parseAntigravityVerdict(raw) {
|
|
11126
|
+
const unfenced = stripJsonFence(raw.trim());
|
|
11127
|
+
let inner;
|
|
11128
|
+
try {
|
|
11129
|
+
inner = JSON.parse(unfenced);
|
|
11130
|
+
} catch {
|
|
11131
|
+
const candidate = extractFirstJsonObject(unfenced);
|
|
11132
|
+
if (candidate === void 0) {
|
|
11133
|
+
throw new Error(
|
|
11134
|
+
"parseAntigravityVerdict: no parseable JSON object found in antigravity (`agy --print`) output"
|
|
11135
|
+
);
|
|
11136
|
+
}
|
|
11137
|
+
try {
|
|
11138
|
+
inner = JSON.parse(candidate);
|
|
11139
|
+
} catch {
|
|
11140
|
+
throw new Error(
|
|
11141
|
+
"parseAntigravityVerdict: extracted candidate from antigravity output was not valid JSON"
|
|
11142
|
+
);
|
|
11143
|
+
}
|
|
11144
|
+
}
|
|
11145
|
+
return buildCiReviewVerdict({
|
|
11146
|
+
runner: "antigravity",
|
|
11147
|
+
ranLlmTier: true,
|
|
11148
|
+
assessment: inner.assessment,
|
|
11149
|
+
findings: inner.findings
|
|
11150
|
+
});
|
|
11151
|
+
}
|
|
11152
|
+
function parseLocalVerdict(raw) {
|
|
11153
|
+
const parsed = JSON.parse(raw);
|
|
11154
|
+
const assessment = VERDICT_MAP[parsed.assessment ?? "comment"] ?? "comment";
|
|
11155
|
+
return buildCiReviewVerdict({
|
|
11156
|
+
runner: "local",
|
|
11157
|
+
ranLlmTier: true,
|
|
11158
|
+
assessment,
|
|
11159
|
+
findings: parsed.findings
|
|
11160
|
+
});
|
|
11161
|
+
}
|
|
11162
|
+
function presetKind(id) {
|
|
11163
|
+
return RUNNER_PRESETS[id].kind;
|
|
11164
|
+
}
|
|
11165
|
+
function isSupportedRunner(id) {
|
|
11166
|
+
return RUNNER_PRESETS[id].supported === true;
|
|
11167
|
+
}
|
|
11168
|
+
function parseGeminiVerdict(raw) {
|
|
11169
|
+
const parsed = JSON.parse(raw);
|
|
11170
|
+
const assessment = VERDICT_MAP2[parsed.review?.verdict ?? "comment"] ?? "comment";
|
|
11171
|
+
return buildCiReviewVerdict({
|
|
11172
|
+
runner: "gemini",
|
|
11173
|
+
ranLlmTier: true,
|
|
11174
|
+
assessment,
|
|
11175
|
+
findings: parsed.review?.issues
|
|
11176
|
+
});
|
|
11177
|
+
}
|
|
11178
|
+
function diffToStdin(diff2) {
|
|
11179
|
+
return Array.from(diff2.fileDiffs.values()).join("\n");
|
|
11180
|
+
}
|
|
11181
|
+
function deriveAssessment(findings) {
|
|
11182
|
+
if (findings.some((f) => f.severity === "critical")) return "request-changes";
|
|
11183
|
+
if (findings.some((f) => f.severity === "important")) return "comment";
|
|
11184
|
+
return "approve";
|
|
11185
|
+
}
|
|
11186
|
+
function maxAssessment(a, b) {
|
|
11187
|
+
return CI_ASSESSMENTS.indexOf(a) >= CI_ASSESSMENTS.indexOf(b) ? a : b;
|
|
11188
|
+
}
|
|
11189
|
+
function floorOnlyResult(args) {
|
|
11190
|
+
const verdict = buildCiReviewVerdict({
|
|
11191
|
+
runner: "floor-only",
|
|
11192
|
+
ranLlmTier: false,
|
|
11193
|
+
assessment: args.assessment,
|
|
11194
|
+
findings: args.findings,
|
|
11195
|
+
skipped: args.skipped,
|
|
11196
|
+
skipReason: args.skipReason
|
|
11197
|
+
});
|
|
11198
|
+
return {
|
|
11199
|
+
verdict,
|
|
11200
|
+
exitCode: applyThreshold(verdict, args.blockOn, args.requiredRunnerFailed),
|
|
11201
|
+
terminalOutput: summarize(verdict),
|
|
11202
|
+
llmSkipReason: args.skipReason,
|
|
11203
|
+
ranLlmTier: false
|
|
11204
|
+
};
|
|
11205
|
+
}
|
|
11206
|
+
async function runAgentCliRunner(preset, ctx) {
|
|
11207
|
+
const instruction = "Run the harness code-review skill on the unified diff piped via STDIN. Emit ONLY the CiReviewVerdict JSON ({assessment, findings}).";
|
|
11208
|
+
const { command, args } = preset.headlessInvocation({ instruction });
|
|
11209
|
+
const { stdout } = await ctx.execFile(command, args, {
|
|
11210
|
+
stdin: diffToStdin(ctx.diff),
|
|
11211
|
+
env: ctx.env,
|
|
11212
|
+
timeoutMs: ctx.options.execTimeoutMs ?? DEFAULT_EXEC_TIMEOUT_MS,
|
|
11213
|
+
maxStdoutBytes: ctx.options.execMaxStdoutBytes ?? DEFAULT_EXEC_MAX_STDOUT_BYTES
|
|
11214
|
+
});
|
|
11215
|
+
return preset.verdictParser(stdout).findings;
|
|
11216
|
+
}
|
|
11217
|
+
async function runEndpointRunner(preset, endpoint, model, invoke, ctx) {
|
|
11218
|
+
const instruction = "Review the unified diff and emit ONLY the CiReviewVerdict JSON ({assessment, findings}).";
|
|
11219
|
+
const raw = await invoke({ endpoint, model, instruction, diff: diffToStdin(ctx.diff) });
|
|
11220
|
+
return preset.verdictParser(raw).findings;
|
|
11221
|
+
}
|
|
11222
|
+
async function runLlmTier(runner, ctx) {
|
|
11223
|
+
const preset = RUNNER_PRESETS[runner];
|
|
11224
|
+
if (preset.supported !== true) {
|
|
11225
|
+
return { ...EMPTY_TIER, skipReason: `LLM tier skipped \u2014 runner '${runner}' is unsupported` };
|
|
11226
|
+
}
|
|
11227
|
+
if (preset.kind === "agent-cli") {
|
|
11228
|
+
if (!ctx.env[preset.secretEnvVar]) {
|
|
11229
|
+
return {
|
|
11230
|
+
...EMPTY_TIER,
|
|
11231
|
+
skipReason: `LLM tier skipped \u2014 secret ${preset.secretEnvVar} not set (floor-only)`
|
|
11232
|
+
};
|
|
11233
|
+
}
|
|
11234
|
+
try {
|
|
11235
|
+
return {
|
|
11236
|
+
findings: await runAgentCliRunner(preset, ctx),
|
|
11237
|
+
ranLlmTier: true,
|
|
11238
|
+
requiredRunnerFailed: false
|
|
11239
|
+
};
|
|
11240
|
+
} catch (err) {
|
|
11241
|
+
return {
|
|
11242
|
+
...EMPTY_TIER,
|
|
11243
|
+
requiredRunnerFailed: true,
|
|
11244
|
+
skipReason: `LLM tier failed \u2014 ${err.message}`
|
|
11245
|
+
};
|
|
11246
|
+
}
|
|
11247
|
+
}
|
|
11248
|
+
const invoke = ctx.options.localInvoke ?? preset.invoke;
|
|
11249
|
+
const endpoint = ctx.env[preset.endpointEnvVar];
|
|
11250
|
+
const model = ctx.env[preset.modelEnvVar];
|
|
11251
|
+
if (!invoke || !endpoint || !model) {
|
|
11252
|
+
return {
|
|
11253
|
+
...EMPTY_TIER,
|
|
11254
|
+
skipReason: "LLM tier skipped \u2014 local endpoint not configured (no invoke seam or missing endpoint/model env)"
|
|
11255
|
+
};
|
|
11256
|
+
}
|
|
11257
|
+
try {
|
|
11258
|
+
return {
|
|
11259
|
+
findings: await runEndpointRunner(preset, endpoint, model, invoke, ctx),
|
|
11260
|
+
ranLlmTier: true,
|
|
11261
|
+
requiredRunnerFailed: false
|
|
11262
|
+
};
|
|
11263
|
+
} catch (err) {
|
|
11264
|
+
return {
|
|
11265
|
+
...EMPTY_TIER,
|
|
11266
|
+
requiredRunnerFailed: true,
|
|
11267
|
+
skipReason: `LLM tier failed \u2014 ${err.message}`
|
|
11268
|
+
};
|
|
11269
|
+
}
|
|
11270
|
+
}
|
|
11271
|
+
async function runCiReview(options) {
|
|
11272
|
+
const { projectRoot, diff: diff2, commitMessage = "", runner, blockOn = "request-changes" } = options;
|
|
11273
|
+
const floor = await runReviewPipeline({
|
|
11274
|
+
projectRoot,
|
|
11275
|
+
diff: diff2,
|
|
11276
|
+
commitMessage,
|
|
11277
|
+
flags: { ci: true, comment: false, deep: false, noMechanical: false }
|
|
11278
|
+
});
|
|
11279
|
+
if (floor.skipped) {
|
|
11280
|
+
return floorOnlyResult({
|
|
11281
|
+
assessment: "request-changes",
|
|
11282
|
+
findings: [],
|
|
11283
|
+
skipped: true,
|
|
11284
|
+
skipReason: `floor review skipped \u2014 ${floor.skipReason ?? "ineligible"} (CI fails closed)`,
|
|
11285
|
+
blockOn,
|
|
11286
|
+
requiredRunnerFailed: true
|
|
11287
|
+
});
|
|
11288
|
+
}
|
|
11289
|
+
const floorFindings = floor.findings;
|
|
11290
|
+
const floorAssessment = maxAssessment(
|
|
11291
|
+
floor.assessment ?? "approve",
|
|
11292
|
+
deriveAssessment(floorFindings)
|
|
11293
|
+
);
|
|
11294
|
+
if (floor.stoppedByMechanical) {
|
|
11295
|
+
return floorOnlyResult({
|
|
11296
|
+
assessment: floorAssessment,
|
|
11297
|
+
findings: floorFindings,
|
|
11298
|
+
skipped: false,
|
|
11299
|
+
skipReason: "LLM tier skipped \u2014 floor mechanical-stop (short-circuit)",
|
|
11300
|
+
blockOn,
|
|
11301
|
+
requiredRunnerFailed: false
|
|
11302
|
+
});
|
|
11303
|
+
}
|
|
11304
|
+
const env = options.env ?? process.env;
|
|
11305
|
+
const execFile3 = options.execFile ?? defaultExecFile;
|
|
11306
|
+
const tier = runner ? await runLlmTier(runner, { diff: diff2, env, execFile: execFile3, options }) : EMPTY_TIER;
|
|
11307
|
+
const {
|
|
11308
|
+
findings: llmFindings,
|
|
11309
|
+
ranLlmTier,
|
|
11310
|
+
requiredRunnerFailed,
|
|
11311
|
+
skipReason: llmSkipReason
|
|
11312
|
+
} = tier;
|
|
11313
|
+
const mergedFindings = [...floorFindings, ...llmFindings];
|
|
11314
|
+
const mergedAssessment = maxAssessment(floorAssessment, deriveAssessment(mergedFindings));
|
|
11315
|
+
const verdict = buildCiReviewVerdict({
|
|
11316
|
+
runner: ranLlmTier ? runner : "floor-only",
|
|
11317
|
+
ranLlmTier,
|
|
11318
|
+
assessment: mergedAssessment,
|
|
11319
|
+
findings: mergedFindings,
|
|
11320
|
+
...llmSkipReason ? { skipReason: llmSkipReason } : {}
|
|
11321
|
+
});
|
|
11322
|
+
return {
|
|
11323
|
+
verdict,
|
|
11324
|
+
exitCode: applyThreshold(verdict, blockOn, requiredRunnerFailed),
|
|
11325
|
+
terminalOutput: summarize(verdict),
|
|
11326
|
+
...llmSkipReason ? { llmSkipReason } : {},
|
|
11327
|
+
ranLlmTier
|
|
11328
|
+
};
|
|
11329
|
+
}
|
|
11330
|
+
function applyThreshold(v, blockOn, requiredRunnerFailed) {
|
|
11331
|
+
if (requiredRunnerFailed) return 1;
|
|
11332
|
+
if (blockOn === "none") return 0;
|
|
11333
|
+
const rank = (a) => CI_ASSESSMENTS.indexOf(a);
|
|
11334
|
+
return rank(v.assessment) >= rank(blockOn) ? 1 : 0;
|
|
11335
|
+
}
|
|
11336
|
+
function summarize(v) {
|
|
11337
|
+
const lines = [
|
|
11338
|
+
`runner: ${v.runner}`,
|
|
11339
|
+
`ranLlmTier: ${v.ranLlmTier}`,
|
|
11340
|
+
`assessment: ${v.assessment}`,
|
|
11341
|
+
`findings: ${v.findings.length} (blocking: ${v.blockingFindings.length})`,
|
|
11342
|
+
`exitCode: ${v.exitCode}`
|
|
11343
|
+
];
|
|
11344
|
+
if (v.skipReason) lines.push(`note: ${v.skipReason}`);
|
|
11345
|
+
return lines.join("\n");
|
|
11346
|
+
}
|
|
11347
|
+
function serializeRoadmap(roadmap) {
|
|
11348
|
+
const lines = [];
|
|
11349
|
+
lines.push("---");
|
|
11350
|
+
lines.push(`project: ${roadmap.frontmatter.project}`);
|
|
11351
|
+
lines.push(`version: ${roadmap.frontmatter.version}`);
|
|
11352
|
+
if (roadmap.frontmatter.created) {
|
|
11353
|
+
lines.push(`created: ${roadmap.frontmatter.created}`);
|
|
11354
|
+
}
|
|
11355
|
+
if (roadmap.frontmatter.updated) {
|
|
11356
|
+
lines.push(`updated: ${roadmap.frontmatter.updated}`);
|
|
11357
|
+
}
|
|
11358
|
+
lines.push(`last_synced: ${roadmap.frontmatter.lastSynced}`);
|
|
11359
|
+
lines.push(`last_manual_edit: ${roadmap.frontmatter.lastManualEdit}`);
|
|
11360
|
+
lines.push("---");
|
|
11361
|
+
lines.push("");
|
|
11362
|
+
lines.push("# Roadmap");
|
|
11363
|
+
for (const milestone of roadmap.milestones) {
|
|
11364
|
+
lines.push("");
|
|
11365
|
+
lines.push(serializeMilestoneHeading(milestone));
|
|
11366
|
+
for (const feature of milestone.features) {
|
|
11367
|
+
lines.push("");
|
|
11368
|
+
lines.push(...serializeFeature(feature));
|
|
11369
|
+
}
|
|
11370
|
+
}
|
|
11371
|
+
if (roadmap.assignmentHistory && roadmap.assignmentHistory.length > 0) {
|
|
11372
|
+
lines.push("");
|
|
11373
|
+
lines.push(...serializeAssignmentHistory(roadmap.assignmentHistory));
|
|
11374
|
+
}
|
|
11375
|
+
lines.push("");
|
|
11376
|
+
return lines.join("\n");
|
|
11377
|
+
}
|
|
11378
|
+
function serializeMilestoneHeading(milestone) {
|
|
11379
|
+
return milestone.isBacklog ? "## Backlog" : `## ${milestone.name}`;
|
|
11380
|
+
}
|
|
11381
|
+
function orDash(value) {
|
|
11382
|
+
return value ?? EM_DASH2;
|
|
11383
|
+
}
|
|
11384
|
+
function listOrDash(items) {
|
|
11385
|
+
return items.length > 0 ? items.join(", ") : EM_DASH2;
|
|
11154
11386
|
}
|
|
11155
11387
|
function serializeExtendedLines(feature) {
|
|
11156
11388
|
const hasExtended = feature.assignee !== null || feature.priority !== null || feature.externalId !== null || feature.updatedAt !== null;
|
|
@@ -11194,7 +11426,7 @@ function isRegression(from, to) {
|
|
|
11194
11426
|
}
|
|
11195
11427
|
function collectAutopilotStatuses(autopilotPath, featurePlans, allTaskStatuses) {
|
|
11196
11428
|
try {
|
|
11197
|
-
const raw =
|
|
11429
|
+
const raw = fs31.readFileSync(autopilotPath, "utf-8");
|
|
11198
11430
|
const autopilot = JSON.parse(raw);
|
|
11199
11431
|
if (!autopilot.phases) return;
|
|
11200
11432
|
const linkedPhases = autopilot.phases.filter(
|
|
@@ -11226,10 +11458,10 @@ function inferStatus(feature, projectPath, allFeatures) {
|
|
|
11226
11458
|
const featuresWithPlans = allFeatures.filter((f) => f.plans.length > 0);
|
|
11227
11459
|
const useRootState = featuresWithPlans.length <= 1;
|
|
11228
11460
|
if (useRootState) {
|
|
11229
|
-
const rootStatePath =
|
|
11230
|
-
if (
|
|
11461
|
+
const rootStatePath = path31.join(projectPath, ".harness", "state.json");
|
|
11462
|
+
if (fs31.existsSync(rootStatePath)) {
|
|
11231
11463
|
try {
|
|
11232
|
-
const raw =
|
|
11464
|
+
const raw = fs31.readFileSync(rootStatePath, "utf-8");
|
|
11233
11465
|
const state = JSON.parse(raw);
|
|
11234
11466
|
if (state.progress) {
|
|
11235
11467
|
for (const status of Object.values(state.progress)) {
|
|
@@ -11240,14 +11472,14 @@ function inferStatus(feature, projectPath, allFeatures) {
|
|
|
11240
11472
|
}
|
|
11241
11473
|
}
|
|
11242
11474
|
}
|
|
11243
|
-
const sessionsDir =
|
|
11244
|
-
if (
|
|
11475
|
+
const sessionsDir = path31.join(projectPath, ".harness", "sessions");
|
|
11476
|
+
if (fs31.existsSync(sessionsDir)) {
|
|
11245
11477
|
try {
|
|
11246
|
-
const sessionDirs =
|
|
11478
|
+
const sessionDirs = fs31.readdirSync(sessionsDir, { withFileTypes: true });
|
|
11247
11479
|
for (const entry of sessionDirs) {
|
|
11248
11480
|
if (!entry.isDirectory()) continue;
|
|
11249
|
-
const autopilotPath =
|
|
11250
|
-
if (!
|
|
11481
|
+
const autopilotPath = path31.join(sessionsDir, entry.name, "autopilot-state.json");
|
|
11482
|
+
if (!fs31.existsSync(autopilotPath)) continue;
|
|
11251
11483
|
collectAutopilotStatuses(autopilotPath, feature.plans, allTaskStatuses);
|
|
11252
11484
|
}
|
|
11253
11485
|
} catch {
|
|
@@ -11289,6 +11521,171 @@ function applySyncChanges(roadmap, changes) {
|
|
|
11289
11521
|
}
|
|
11290
11522
|
roadmap.frontmatter.lastSynced = (/* @__PURE__ */ new Date()).toISOString();
|
|
11291
11523
|
}
|
|
11524
|
+
function isEmptySummary(summary) {
|
|
11525
|
+
const trimmed = summary.trim();
|
|
11526
|
+
return trimmed === "" || trimmed === EM_DASH3;
|
|
11527
|
+
}
|
|
11528
|
+
function rowWouldChange(currentSpec, currentSummary, args) {
|
|
11529
|
+
const specChanges = currentSpec !== args.spec;
|
|
11530
|
+
const summaryChanges = args.summary !== void 0 && args.summary !== "" && isEmptySummary(currentSummary);
|
|
11531
|
+
return specChanges || summaryChanges;
|
|
11532
|
+
}
|
|
11533
|
+
function decidePromotionForRow(status, currentSpec, currentSummary, args) {
|
|
11534
|
+
switch (status) {
|
|
11535
|
+
case "in-progress":
|
|
11536
|
+
return { action: "refuse", reason: "in-progress" };
|
|
11537
|
+
case "done":
|
|
11538
|
+
return { action: "refuse", reason: "done" };
|
|
11539
|
+
case "backlog":
|
|
11540
|
+
return { action: "set-planned" };
|
|
11541
|
+
// planned, blocked, needs-human — lateral states (see status-rank.ts).
|
|
11542
|
+
// Refresh the spec link but preserve status; warn upstream.
|
|
11543
|
+
default:
|
|
11544
|
+
return rowWouldChange(currentSpec, currentSummary, args) ? { action: "update-spec" } : { action: "noop" };
|
|
11545
|
+
}
|
|
11546
|
+
}
|
|
11547
|
+
function applySpecAndSummary(feature, args) {
|
|
11548
|
+
feature.spec = args.spec;
|
|
11549
|
+
if (args.summary !== void 0 && args.summary !== "" && isEmptySummary(feature.summary)) {
|
|
11550
|
+
feature.summary = args.summary;
|
|
11551
|
+
}
|
|
11552
|
+
}
|
|
11553
|
+
function levenshtein(a, b) {
|
|
11554
|
+
const m = a.length;
|
|
11555
|
+
const n = b.length;
|
|
11556
|
+
if (m === 0) return n;
|
|
11557
|
+
if (n === 0) return m;
|
|
11558
|
+
const row = new Array(n + 1);
|
|
11559
|
+
for (let j = 0; j <= n; j++) row[j] = j;
|
|
11560
|
+
for (let i = 1; i <= m; i++) {
|
|
11561
|
+
let prevDiag = row[0];
|
|
11562
|
+
row[0] = i;
|
|
11563
|
+
for (let j = 1; j <= n; j++) {
|
|
11564
|
+
const above = row[j];
|
|
11565
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
11566
|
+
row[j] = Math.min(row[j] + 1, row[j - 1] + 1, prevDiag + cost);
|
|
11567
|
+
prevDiag = above;
|
|
11568
|
+
}
|
|
11569
|
+
}
|
|
11570
|
+
return row[n];
|
|
11571
|
+
}
|
|
11572
|
+
function isProbableTypo(distance, queryLen) {
|
|
11573
|
+
if (distance === 0) return false;
|
|
11574
|
+
return distance <= Math.max(2, Math.floor(queryLen / 4));
|
|
11575
|
+
}
|
|
11576
|
+
function locateRows(roadmap) {
|
|
11577
|
+
const rows = [];
|
|
11578
|
+
roadmap.milestones.forEach((milestone, milestoneIndex) => {
|
|
11579
|
+
milestone.features.forEach((feature, featureIndex) => {
|
|
11580
|
+
rows.push({ milestoneIndex, featureIndex, milestoneName: milestone.name, feature });
|
|
11581
|
+
});
|
|
11582
|
+
});
|
|
11583
|
+
return rows;
|
|
11584
|
+
}
|
|
11585
|
+
function cloneRoadmap(roadmap) {
|
|
11586
|
+
return structuredClone(roadmap);
|
|
11587
|
+
}
|
|
11588
|
+
function makeCreatedFeature(args) {
|
|
11589
|
+
return {
|
|
11590
|
+
name: args.feature.trim(),
|
|
11591
|
+
status: "planned",
|
|
11592
|
+
spec: args.spec,
|
|
11593
|
+
plans: [],
|
|
11594
|
+
blockedBy: [],
|
|
11595
|
+
summary: args.summary ?? "",
|
|
11596
|
+
assignee: null,
|
|
11597
|
+
priority: null,
|
|
11598
|
+
externalId: null,
|
|
11599
|
+
updatedAt: null
|
|
11600
|
+
};
|
|
11601
|
+
}
|
|
11602
|
+
function appendCreatedRow(roadmap, args) {
|
|
11603
|
+
const next = cloneRoadmap(roadmap);
|
|
11604
|
+
let milestone = next.milestones.find(
|
|
11605
|
+
(m) => m.name.toLowerCase() === DEFAULT_MILESTONE.toLowerCase()
|
|
11606
|
+
);
|
|
11607
|
+
if (!milestone) {
|
|
11608
|
+
milestone = { name: DEFAULT_MILESTONE, isBacklog: false, features: [] };
|
|
11609
|
+
next.milestones.push(milestone);
|
|
11610
|
+
}
|
|
11611
|
+
milestone.features.push(makeCreatedFeature(args));
|
|
11612
|
+
return next;
|
|
11613
|
+
}
|
|
11614
|
+
function promoteFeature(roadmap, args) {
|
|
11615
|
+
const query = args.feature.trim();
|
|
11616
|
+
const queryLower = query.toLowerCase();
|
|
11617
|
+
const rows = locateRows(roadmap);
|
|
11618
|
+
const exact = rows.filter((r) => r.feature.name.trim().toLowerCase() === queryLower);
|
|
11619
|
+
if (exact.length > 1) {
|
|
11620
|
+
const matches = exact.map((r) => `${r.milestoneName} > ${r.feature.name}`);
|
|
11621
|
+
return {
|
|
11622
|
+
result: {
|
|
11623
|
+
ok: false,
|
|
11624
|
+
reason: "ambiguous",
|
|
11625
|
+
feature: query,
|
|
11626
|
+
detail: `"${query}" matches ${matches.length} rows across milestones. Re-invoke milestone-qualified: ${matches.join(", ")}.`,
|
|
11627
|
+
matches
|
|
11628
|
+
},
|
|
11629
|
+
nextRoadmap: roadmap
|
|
11630
|
+
};
|
|
11631
|
+
}
|
|
11632
|
+
if (exact.length === 1) {
|
|
11633
|
+
const located = exact[0];
|
|
11634
|
+
const decision = decidePromotionForRow(
|
|
11635
|
+
located.feature.status,
|
|
11636
|
+
located.feature.spec,
|
|
11637
|
+
located.feature.summary,
|
|
11638
|
+
args
|
|
11639
|
+
);
|
|
11640
|
+
if (decision.action === "refuse") {
|
|
11641
|
+
const detail = decision.reason === "in-progress" ? `"${query}" is in-progress: an agent is dispatched against this row. Stop the agent or use a different feature name.` : `"${query}" is already 'done'. To revise a shipped feature, use a new name.`;
|
|
11642
|
+
return {
|
|
11643
|
+
result: { ok: false, reason: decision.reason, detail, feature: query },
|
|
11644
|
+
nextRoadmap: roadmap
|
|
11645
|
+
};
|
|
11646
|
+
}
|
|
11647
|
+
if (decision.action === "noop") {
|
|
11648
|
+
return { result: { ok: true, transitioned: "noop", feature: query }, nextRoadmap: roadmap };
|
|
11649
|
+
}
|
|
11650
|
+
const next = cloneRoadmap(roadmap);
|
|
11651
|
+
const target = next.milestones[located.milestoneIndex].features[located.featureIndex];
|
|
11652
|
+
if (decision.action === "set-planned") {
|
|
11653
|
+
target.status = "planned";
|
|
11654
|
+
applySpecAndSummary(target, args);
|
|
11655
|
+
return {
|
|
11656
|
+
result: { ok: true, transitioned: "backlog\u2192planned", feature: query },
|
|
11657
|
+
nextRoadmap: next
|
|
11658
|
+
};
|
|
11659
|
+
}
|
|
11660
|
+
applySpecAndSummary(target, args);
|
|
11661
|
+
return {
|
|
11662
|
+
result: { ok: true, transitioned: "spec-updated", feature: query },
|
|
11663
|
+
nextRoadmap: next
|
|
11664
|
+
};
|
|
11665
|
+
}
|
|
11666
|
+
const ranked = rows.map((r) => ({
|
|
11667
|
+
name: r.feature.name,
|
|
11668
|
+
distance: levenshtein(queryLower, r.feature.name.toLowerCase())
|
|
11669
|
+
})).sort((a, b) => a.distance - b.distance);
|
|
11670
|
+
const nearest = ranked[0];
|
|
11671
|
+
if (nearest && isProbableTypo(nearest.distance, queryLower.length)) {
|
|
11672
|
+
const closestMatches = ranked.slice(0, MAX_CLOSEST_MATCHES).map((r) => r.name);
|
|
11673
|
+
return {
|
|
11674
|
+
result: {
|
|
11675
|
+
ok: false,
|
|
11676
|
+
reason: "not-found",
|
|
11677
|
+
feature: query,
|
|
11678
|
+
detail: `"${query}" not found. Did you mean: ${closestMatches.join(", ")}?`,
|
|
11679
|
+
closestMatches
|
|
11680
|
+
},
|
|
11681
|
+
nextRoadmap: roadmap
|
|
11682
|
+
};
|
|
11683
|
+
}
|
|
11684
|
+
return {
|
|
11685
|
+
result: { ok: true, transitioned: "created", feature: query },
|
|
11686
|
+
nextRoadmap: appendCreatedRow(roadmap, args)
|
|
11687
|
+
};
|
|
11688
|
+
}
|
|
11292
11689
|
function resolveReverseStatus(externalStatus, labels, config) {
|
|
11293
11690
|
const reverseMap = config.reverseStatusMap;
|
|
11294
11691
|
if (!reverseMap) return null;
|
|
@@ -11329,7 +11726,7 @@ function parseRepoParts(repo) {
|
|
|
11329
11726
|
return { owner: parts[0], repo: parts[1] };
|
|
11330
11727
|
}
|
|
11331
11728
|
function sleep2(ms) {
|
|
11332
|
-
return new Promise((
|
|
11729
|
+
return new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
11333
11730
|
}
|
|
11334
11731
|
async function fetchWithRetry(fetchFn, input, init, opts = RETRY_DEFAULTS) {
|
|
11335
11732
|
let lastResponse;
|
|
@@ -11362,9 +11759,9 @@ function isValidTrackerShape(tracker) {
|
|
|
11362
11759
|
}
|
|
11363
11760
|
function loadTrackerSyncConfig(projectRoot) {
|
|
11364
11761
|
try {
|
|
11365
|
-
const configPath =
|
|
11366
|
-
if (!
|
|
11367
|
-
const raw =
|
|
11762
|
+
const configPath = path32.join(projectRoot, "harness.config.json");
|
|
11763
|
+
if (!fs32.existsSync(configPath)) return null;
|
|
11764
|
+
const raw = fs32.readFileSync(configPath, "utf-8");
|
|
11368
11765
|
const config = JSON.parse(raw);
|
|
11369
11766
|
const tracker = config.roadmap?.tracker;
|
|
11370
11767
|
if (!isValidTrackerShape(tracker)) return null;
|
|
@@ -11479,12 +11876,12 @@ async function syncFromExternal(roadmap, adapter2, config, options, prefetchedTi
|
|
|
11479
11876
|
async function fullSync(roadmapPath, adapter2, config, options) {
|
|
11480
11877
|
const previousSync = syncMutex;
|
|
11481
11878
|
let releaseMutex;
|
|
11482
|
-
syncMutex = new Promise((
|
|
11483
|
-
releaseMutex =
|
|
11879
|
+
syncMutex = new Promise((resolve7) => {
|
|
11880
|
+
releaseMutex = resolve7;
|
|
11484
11881
|
});
|
|
11485
11882
|
await previousSync;
|
|
11486
11883
|
try {
|
|
11487
|
-
const raw =
|
|
11884
|
+
const raw = fs33.readFileSync(roadmapPath, "utf-8");
|
|
11488
11885
|
const parseResult = parseRoadmap(raw);
|
|
11489
11886
|
if (!parseResult.ok) {
|
|
11490
11887
|
return {
|
|
@@ -11497,7 +11894,7 @@ async function fullSync(roadmapPath, adapter2, config, options) {
|
|
|
11497
11894
|
const tickets = fetchResult.ok ? fetchResult.value : void 0;
|
|
11498
11895
|
const pushResult = await syncToExternal(roadmap, adapter2, config, tickets);
|
|
11499
11896
|
const pullResult = await syncFromExternal(roadmap, adapter2, config, options);
|
|
11500
|
-
|
|
11897
|
+
fs33.writeFileSync(roadmapPath, serializeRoadmap(roadmap), "utf-8");
|
|
11501
11898
|
return {
|
|
11502
11899
|
created: pushResult.created,
|
|
11503
11900
|
updated: pushResult.updated,
|
|
@@ -11857,9 +12254,9 @@ function makeTrackerConflictBody(err, opts = {}) {
|
|
|
11857
12254
|
}
|
|
11858
12255
|
function loadProjectRoadmapMode(projectRoot) {
|
|
11859
12256
|
try {
|
|
11860
|
-
const configPath =
|
|
11861
|
-
if (!
|
|
11862
|
-
const raw =
|
|
12257
|
+
const configPath = path33.join(projectRoot, "harness.config.json");
|
|
12258
|
+
if (!fs34.existsSync(configPath)) return getRoadmapMode(null);
|
|
12259
|
+
const raw = fs34.readFileSync(configPath, "utf-8");
|
|
11863
12260
|
const parsed = JSON.parse(raw);
|
|
11864
12261
|
return getRoadmapMode(parsed);
|
|
11865
12262
|
} catch {
|
|
@@ -11868,11 +12265,11 @@ function loadProjectRoadmapMode(projectRoot) {
|
|
|
11868
12265
|
}
|
|
11869
12266
|
function loadTrackerClientConfigFromProject(projectRoot) {
|
|
11870
12267
|
try {
|
|
11871
|
-
const configPath =
|
|
11872
|
-
if (!
|
|
12268
|
+
const configPath = path34.join(projectRoot, "harness.config.json");
|
|
12269
|
+
if (!fs35.existsSync(configPath)) {
|
|
11873
12270
|
return Err(new Error("harness.config.json not found"));
|
|
11874
12271
|
}
|
|
11875
|
-
const cfg = JSON.parse(
|
|
12272
|
+
const cfg = JSON.parse(fs35.readFileSync(configPath, "utf-8"));
|
|
11876
12273
|
const tracker = cfg.roadmap?.tracker;
|
|
11877
12274
|
if (!tracker) {
|
|
11878
12275
|
return Err(
|
|
@@ -12087,10 +12484,10 @@ function mapAction(action) {
|
|
|
12087
12484
|
}
|
|
12088
12485
|
async function runMigrationPlan(plan, deps, opts) {
|
|
12089
12486
|
const projectRoot = opts.projectRoot;
|
|
12090
|
-
const roadmapPath =
|
|
12091
|
-
const archivedPath =
|
|
12092
|
-
const configPath =
|
|
12093
|
-
const configBackupPath =
|
|
12487
|
+
const roadmapPath = path35.join(projectRoot, "docs", "roadmap.md");
|
|
12488
|
+
const archivedPath = path35.join(projectRoot, "docs", "roadmap.md.archived");
|
|
12489
|
+
const configPath = path35.join(projectRoot, "harness.config.json");
|
|
12490
|
+
const configBackupPath = path35.join(projectRoot, "harness.config.json.pre-migration");
|
|
12094
12491
|
const createdSoFar = [];
|
|
12095
12492
|
const report = {
|
|
12096
12493
|
created: 0,
|
|
@@ -12200,9 +12597,111 @@ async function runMigrationPlan(plan, deps, opts) {
|
|
|
12200
12597
|
}
|
|
12201
12598
|
return Ok(report);
|
|
12202
12599
|
}
|
|
12600
|
+
function defaultIsArchive(name) {
|
|
12601
|
+
return /^v\d/.test(name) || name === "Shipped" || name === "Hermes Adoption";
|
|
12602
|
+
}
|
|
12603
|
+
function isUnactionablePlanned(feature) {
|
|
12604
|
+
return feature.status === "planned" && feature.spec === null && feature.plans.length === 0;
|
|
12605
|
+
}
|
|
12606
|
+
function resolveOptions(options) {
|
|
12607
|
+
return {
|
|
12608
|
+
isArchive: options?.isArchive ?? defaultIsArchive,
|
|
12609
|
+
maxActiveMilestoneSize: options?.maxActiveMilestoneSize ?? DEFAULT_MAX_ACTIVE,
|
|
12610
|
+
catchAllMilestones: options?.catchAllMilestones ?? DEFAULT_CATCH_ALL,
|
|
12611
|
+
intakeMilestone: options?.intakeMilestone ?? DEFAULT_INTAKE
|
|
12612
|
+
};
|
|
12613
|
+
}
|
|
12614
|
+
function checkRoadmapHealth(roadmap, options) {
|
|
12615
|
+
const opts = resolveOptions(options);
|
|
12616
|
+
const findings = [];
|
|
12617
|
+
for (const milestone of roadmap.milestones) {
|
|
12618
|
+
const archive = opts.isArchive(milestone.name);
|
|
12619
|
+
const isIntake = milestone.name === opts.intakeMilestone;
|
|
12620
|
+
if (opts.catchAllMilestones.some((n) => n.toLowerCase() === milestone.name.toLowerCase())) {
|
|
12621
|
+
findings.push({
|
|
12622
|
+
ruleId: "RMH003",
|
|
12623
|
+
severity: "error",
|
|
12624
|
+
milestone: milestone.name,
|
|
12625
|
+
message: `Lifecycle catch-all milestone "${milestone.name}" exists. Milestones should be themes, not stages.`,
|
|
12626
|
+
suggestion: `Rename to a theme or run the roadmap skill's --groom mode to drain it into themed milestones.`
|
|
12627
|
+
});
|
|
12628
|
+
}
|
|
12629
|
+
if (!archive && !isIntake && milestone.features.length > opts.maxActiveMilestoneSize) {
|
|
12630
|
+
findings.push({
|
|
12631
|
+
ruleId: "RMH004",
|
|
12632
|
+
severity: "warning",
|
|
12633
|
+
milestone: milestone.name,
|
|
12634
|
+
message: `Active milestone "${milestone.name}" has ${milestone.features.length} features (cap ${opts.maxActiveMilestoneSize}); it is becoming a dump.`,
|
|
12635
|
+
suggestion: `Split it into sub-themes.`
|
|
12636
|
+
});
|
|
12637
|
+
}
|
|
12638
|
+
for (const feature of milestone.features) {
|
|
12639
|
+
if (feature.status === "done" && !archive && !isIntake) {
|
|
12640
|
+
findings.push({
|
|
12641
|
+
ruleId: "RMH001",
|
|
12642
|
+
severity: "warning",
|
|
12643
|
+
milestone: milestone.name,
|
|
12644
|
+
feature: feature.name,
|
|
12645
|
+
message: `Completed feature "${feature.name}" is still in active milestone "${milestone.name}".`,
|
|
12646
|
+
suggestion: `Archive it (move to Shipped) \u2014 run the roadmap skill's --groom mode.`
|
|
12647
|
+
});
|
|
12648
|
+
}
|
|
12649
|
+
if (isUnactionablePlanned(feature)) {
|
|
12650
|
+
findings.push({
|
|
12651
|
+
ruleId: "RMH002",
|
|
12652
|
+
severity: "warning",
|
|
12653
|
+
milestone: milestone.name,
|
|
12654
|
+
feature: feature.name,
|
|
12655
|
+
message: `"${feature.name}" is "planned" with no spec and no plan; the orchestrator cannot auto-execute it (it escalates to a human).`,
|
|
12656
|
+
suggestion: `Link a spec/plan to make it actionable, or demote it to "backlog".`
|
|
12657
|
+
});
|
|
12658
|
+
}
|
|
12659
|
+
}
|
|
12660
|
+
}
|
|
12661
|
+
return findings;
|
|
12662
|
+
}
|
|
12663
|
+
function groomRoadmap(roadmap, options) {
|
|
12664
|
+
const opts = resolveOptions(options);
|
|
12665
|
+
const archiveDone = options?.archiveDone ?? true;
|
|
12666
|
+
const next = structuredClone(roadmap);
|
|
12667
|
+
const archived = [];
|
|
12668
|
+
const changes = [];
|
|
12669
|
+
for (const milestone of next.milestones) {
|
|
12670
|
+
const archive = opts.isArchive(milestone.name);
|
|
12671
|
+
const isIntake = milestone.name === opts.intakeMilestone;
|
|
12672
|
+
const kept = [];
|
|
12673
|
+
for (const feature of milestone.features) {
|
|
12674
|
+
if (isUnactionablePlanned(feature)) {
|
|
12675
|
+
feature.status = "backlog";
|
|
12676
|
+
changes.push({
|
|
12677
|
+
kind: "demoted",
|
|
12678
|
+
feature: feature.name,
|
|
12679
|
+
from: milestone.name,
|
|
12680
|
+
to: "backlog"
|
|
12681
|
+
});
|
|
12682
|
+
}
|
|
12683
|
+
if (archiveDone && feature.status === "done" && !archive && !isIntake) {
|
|
12684
|
+
archived.push(feature);
|
|
12685
|
+
changes.push({
|
|
12686
|
+
kind: "archived",
|
|
12687
|
+
feature: feature.name,
|
|
12688
|
+
from: milestone.name,
|
|
12689
|
+
to: "Shipped"
|
|
12690
|
+
});
|
|
12691
|
+
continue;
|
|
12692
|
+
}
|
|
12693
|
+
kept.push(feature);
|
|
12694
|
+
}
|
|
12695
|
+
milestone.features = kept;
|
|
12696
|
+
}
|
|
12697
|
+
next.milestones = next.milestones.filter(
|
|
12698
|
+
(m) => m.features.length > 0 || m.name === opts.intakeMilestone
|
|
12699
|
+
);
|
|
12700
|
+
return { roadmap: next, archived, changes };
|
|
12701
|
+
}
|
|
12203
12702
|
function getStatePath() {
|
|
12204
12703
|
const home = process.env["HOME"] || os.homedir();
|
|
12205
|
-
return
|
|
12704
|
+
return path38.join(home, ".harness", "update-check.json");
|
|
12206
12705
|
}
|
|
12207
12706
|
function isUpdateCheckEnabled(configInterval) {
|
|
12208
12707
|
if (process.env["HARNESS_NO_UPDATE_CHECK"] === "1") return false;
|
|
@@ -12215,13 +12714,13 @@ function shouldRunCheck(state, intervalMs) {
|
|
|
12215
12714
|
}
|
|
12216
12715
|
function invalidateCheckState() {
|
|
12217
12716
|
try {
|
|
12218
|
-
|
|
12717
|
+
fs38.unlinkSync(getStatePath());
|
|
12219
12718
|
} catch {
|
|
12220
12719
|
}
|
|
12221
12720
|
}
|
|
12222
12721
|
function readCheckState() {
|
|
12223
12722
|
try {
|
|
12224
|
-
const raw =
|
|
12723
|
+
const raw = fs38.readFileSync(getStatePath(), "utf-8");
|
|
12225
12724
|
const parsed = JSON.parse(raw);
|
|
12226
12725
|
if (typeof parsed === "object" && parsed !== null && "lastCheckTime" in parsed && typeof parsed.lastCheckTime === "number" && "currentVersion" in parsed && typeof parsed.currentVersion === "string") {
|
|
12227
12726
|
const state = parsed;
|
|
@@ -12238,7 +12737,7 @@ function readCheckState() {
|
|
|
12238
12737
|
}
|
|
12239
12738
|
function spawnBackgroundCheck(currentVersion) {
|
|
12240
12739
|
const statePath = getStatePath();
|
|
12241
|
-
const stateDir =
|
|
12740
|
+
const stateDir = path38.dirname(statePath);
|
|
12242
12741
|
const script = `
|
|
12243
12742
|
const { execSync } = require('child_process');
|
|
12244
12743
|
const fs = require('fs');
|
|
@@ -12481,14 +12980,14 @@ function getModelPrice(model, dataset) {
|
|
|
12481
12980
|
return pricing;
|
|
12482
12981
|
}
|
|
12483
12982
|
function getCachePath(projectRoot) {
|
|
12484
|
-
return
|
|
12983
|
+
return path39.join(projectRoot, ".harness", "cache", "pricing.json");
|
|
12485
12984
|
}
|
|
12486
12985
|
function getStalenessMarkerPath(projectRoot) {
|
|
12487
|
-
return
|
|
12986
|
+
return path39.join(projectRoot, ".harness", "cache", "staleness-marker.json");
|
|
12488
12987
|
}
|
|
12489
12988
|
async function readDiskCache(projectRoot) {
|
|
12490
12989
|
try {
|
|
12491
|
-
const raw = await
|
|
12990
|
+
const raw = await fs39.readFile(getCachePath(projectRoot), "utf-8");
|
|
12492
12991
|
return JSON.parse(raw);
|
|
12493
12992
|
} catch {
|
|
12494
12993
|
return null;
|
|
@@ -12496,8 +12995,8 @@ async function readDiskCache(projectRoot) {
|
|
|
12496
12995
|
}
|
|
12497
12996
|
async function writeDiskCache(projectRoot, data) {
|
|
12498
12997
|
const cachePath = getCachePath(projectRoot);
|
|
12499
|
-
await
|
|
12500
|
-
await
|
|
12998
|
+
await fs39.mkdir(path39.dirname(cachePath), { recursive: true });
|
|
12999
|
+
await fs39.writeFile(cachePath, JSON.stringify(data, null, 2));
|
|
12501
13000
|
}
|
|
12502
13001
|
async function fetchFromNetwork() {
|
|
12503
13002
|
try {
|
|
@@ -12524,7 +13023,7 @@ function loadFallbackDataset() {
|
|
|
12524
13023
|
async function checkAndWarnStaleness(projectRoot) {
|
|
12525
13024
|
const markerPath = getStalenessMarkerPath(projectRoot);
|
|
12526
13025
|
try {
|
|
12527
|
-
const raw = await
|
|
13026
|
+
const raw = await fs39.readFile(markerPath, "utf-8");
|
|
12528
13027
|
const marker = JSON.parse(raw);
|
|
12529
13028
|
const firstUse = new Date(marker.firstFallbackUse).getTime();
|
|
12530
13029
|
const now = Date.now();
|
|
@@ -12536,8 +13035,8 @@ async function checkAndWarnStaleness(projectRoot) {
|
|
|
12536
13035
|
}
|
|
12537
13036
|
} catch {
|
|
12538
13037
|
try {
|
|
12539
|
-
await
|
|
12540
|
-
await
|
|
13038
|
+
await fs39.mkdir(path39.dirname(markerPath), { recursive: true });
|
|
13039
|
+
await fs39.writeFile(
|
|
12541
13040
|
markerPath,
|
|
12542
13041
|
JSON.stringify({ firstFallbackUse: (/* @__PURE__ */ new Date()).toISOString() })
|
|
12543
13042
|
);
|
|
@@ -12547,7 +13046,7 @@ async function checkAndWarnStaleness(projectRoot) {
|
|
|
12547
13046
|
}
|
|
12548
13047
|
async function clearStalenessMarker(projectRoot) {
|
|
12549
13048
|
try {
|
|
12550
|
-
await
|
|
13049
|
+
await fs39.unlink(getStalenessMarkerPath(projectRoot));
|
|
12551
13050
|
} catch {
|
|
12552
13051
|
}
|
|
12553
13052
|
}
|
|
@@ -12784,10 +13283,10 @@ function parseLine(line, lineNumber) {
|
|
|
12784
13283
|
return record;
|
|
12785
13284
|
}
|
|
12786
13285
|
function readCostRecords(projectRoot) {
|
|
12787
|
-
const costsFile =
|
|
13286
|
+
const costsFile = path40.join(projectRoot, ".harness", "metrics", "costs.jsonl");
|
|
12788
13287
|
let raw;
|
|
12789
13288
|
try {
|
|
12790
|
-
raw =
|
|
13289
|
+
raw = fs40.readFileSync(costsFile, "utf-8");
|
|
12791
13290
|
} catch {
|
|
12792
13291
|
return [];
|
|
12793
13292
|
}
|
|
@@ -12837,7 +13336,7 @@ function parseCCLine(line, filePath, lineNumber) {
|
|
|
12837
13336
|
entry = JSON.parse(line);
|
|
12838
13337
|
} catch {
|
|
12839
13338
|
console.warn(
|
|
12840
|
-
`[harness usage] Skipping malformed CC JSONL line ${lineNumber} in ${
|
|
13339
|
+
`[harness usage] Skipping malformed CC JSONL line ${lineNumber} in ${path41.basename(filePath)}`
|
|
12841
13340
|
);
|
|
12842
13341
|
return null;
|
|
12843
13342
|
}
|
|
@@ -12851,7 +13350,7 @@ function parseCCLine(line, filePath, lineNumber) {
|
|
|
12851
13350
|
function readCCFile(filePath) {
|
|
12852
13351
|
let raw;
|
|
12853
13352
|
try {
|
|
12854
|
-
raw =
|
|
13353
|
+
raw = fs41.readFileSync(filePath, "utf-8");
|
|
12855
13354
|
} catch {
|
|
12856
13355
|
return [];
|
|
12857
13356
|
}
|
|
@@ -12873,10 +13372,10 @@ function readCCFile(filePath) {
|
|
|
12873
13372
|
}
|
|
12874
13373
|
function parseCCRecords() {
|
|
12875
13374
|
const homeDir = process.env.HOME ?? os2.homedir();
|
|
12876
|
-
const projectsDir =
|
|
13375
|
+
const projectsDir = path41.join(homeDir, ".claude", "projects");
|
|
12877
13376
|
let projectDirs;
|
|
12878
13377
|
try {
|
|
12879
|
-
projectDirs =
|
|
13378
|
+
projectDirs = fs41.readdirSync(projectsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => path41.join(projectsDir, d.name));
|
|
12880
13379
|
} catch {
|
|
12881
13380
|
return [];
|
|
12882
13381
|
}
|
|
@@ -12884,7 +13383,7 @@ function parseCCRecords() {
|
|
|
12884
13383
|
for (const dir of projectDirs) {
|
|
12885
13384
|
let files;
|
|
12886
13385
|
try {
|
|
12887
|
-
files =
|
|
13386
|
+
files = fs41.readdirSync(dir).filter((f) => f.endsWith(".jsonl")).map((f) => path41.join(dir, f));
|
|
12888
13387
|
} catch {
|
|
12889
13388
|
continue;
|
|
12890
13389
|
}
|
|
@@ -12912,10 +13411,10 @@ function parseLine2(line, lineNumber) {
|
|
|
12912
13411
|
}
|
|
12913
13412
|
}
|
|
12914
13413
|
function readAdoptionRecords(projectRoot) {
|
|
12915
|
-
const adoptionFile =
|
|
13414
|
+
const adoptionFile = path42.join(projectRoot, ".harness", "metrics", "adoption.jsonl");
|
|
12916
13415
|
let raw;
|
|
12917
13416
|
try {
|
|
12918
|
-
raw =
|
|
13417
|
+
raw = fs42.readFileSync(adoptionFile, "utf-8");
|
|
12919
13418
|
} catch {
|
|
12920
13419
|
return [];
|
|
12921
13420
|
}
|
|
@@ -12987,38 +13486,6 @@ function aggregateByDay2(records) {
|
|
|
12987
13486
|
function topSkills(records, n) {
|
|
12988
13487
|
return aggregateBySkill(records).slice(0, n);
|
|
12989
13488
|
}
|
|
12990
|
-
function loadNotificationsConfig(projectRoot) {
|
|
12991
|
-
const configPath = path44.join(projectRoot, "harness.config.json");
|
|
12992
|
-
if (!fs44.existsSync(configPath)) {
|
|
12993
|
-
return Ok({ sinks: [] });
|
|
12994
|
-
}
|
|
12995
|
-
let raw;
|
|
12996
|
-
try {
|
|
12997
|
-
raw = fs44.readFileSync(configPath, "utf-8");
|
|
12998
|
-
} catch (err) {
|
|
12999
|
-
return Err(err instanceof Error ? err : new Error(String(err)));
|
|
13000
|
-
}
|
|
13001
|
-
let parsed;
|
|
13002
|
-
try {
|
|
13003
|
-
parsed = JSON.parse(raw);
|
|
13004
|
-
} catch (err) {
|
|
13005
|
-
return Err(
|
|
13006
|
-
new Error(
|
|
13007
|
-
`Failed to parse harness.config.json: ${err instanceof Error ? err.message : String(err)}`
|
|
13008
|
-
)
|
|
13009
|
-
);
|
|
13010
|
-
}
|
|
13011
|
-
if (parsed.notifications === void 0) {
|
|
13012
|
-
return Ok({ sinks: [] });
|
|
13013
|
-
}
|
|
13014
|
-
const result = NotificationsConfigSchema.safeParse(parsed.notifications);
|
|
13015
|
-
if (!result.success) {
|
|
13016
|
-
const issues = result.error.issues.map((i) => ` - notifications.${i.path.join(".")}: ${i.message}`).join("\n");
|
|
13017
|
-
return Err(new Error(`Invalid notifications config:
|
|
13018
|
-
${issues}`));
|
|
13019
|
-
}
|
|
13020
|
-
return Ok(result.data);
|
|
13021
|
-
}
|
|
13022
13489
|
function isEmptyObject(v) {
|
|
13023
13490
|
return typeof v === "object" && v !== null && !Array.isArray(v) && Object.keys(v).length === 0;
|
|
13024
13491
|
}
|
|
@@ -13095,25 +13562,25 @@ function resolveStability(contentType) {
|
|
|
13095
13562
|
return STABILITY_LOOKUP[contentType] ?? "ephemeral";
|
|
13096
13563
|
}
|
|
13097
13564
|
function getOrCreateInstallId(projectRoot) {
|
|
13098
|
-
const harnessDir =
|
|
13099
|
-
const installIdFile =
|
|
13565
|
+
const harnessDir = path43.join(projectRoot, ".harness");
|
|
13566
|
+
const installIdFile = path43.join(harnessDir, ".install-id");
|
|
13100
13567
|
const UUID_V4_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
13101
13568
|
try {
|
|
13102
|
-
const existing =
|
|
13569
|
+
const existing = fs43.readFileSync(installIdFile, "utf-8").trim();
|
|
13103
13570
|
if (UUID_V4_RE.test(existing)) {
|
|
13104
13571
|
return existing;
|
|
13105
13572
|
}
|
|
13106
13573
|
} catch {
|
|
13107
13574
|
}
|
|
13108
13575
|
const id = crypto4.randomUUID();
|
|
13109
|
-
|
|
13110
|
-
|
|
13576
|
+
fs43.mkdirSync(harnessDir, { recursive: true });
|
|
13577
|
+
fs43.writeFileSync(installIdFile, id, { encoding: "utf-8", mode: 384 });
|
|
13111
13578
|
return id;
|
|
13112
13579
|
}
|
|
13113
13580
|
function parseIdentityFromTelemetryFile(filePath) {
|
|
13114
13581
|
const identity = {};
|
|
13115
13582
|
try {
|
|
13116
|
-
const raw =
|
|
13583
|
+
const raw = fs44.readFileSync(filePath, "utf-8");
|
|
13117
13584
|
const parsed = JSON.parse(raw);
|
|
13118
13585
|
const src = parsed?.identity;
|
|
13119
13586
|
if (!src || typeof src !== "object") return identity;
|
|
@@ -13126,7 +13593,7 @@ function parseIdentityFromTelemetryFile(filePath) {
|
|
|
13126
13593
|
}
|
|
13127
13594
|
function readProjectNameFallback(configPath) {
|
|
13128
13595
|
try {
|
|
13129
|
-
const raw =
|
|
13596
|
+
const raw = fs44.readFileSync(configPath, "utf-8");
|
|
13130
13597
|
const config = JSON.parse(raw);
|
|
13131
13598
|
return typeof config?.name === "string" ? config.name : void 0;
|
|
13132
13599
|
} catch {
|
|
@@ -13147,10 +13614,10 @@ function readGitAliasFallback(cwd) {
|
|
|
13147
13614
|
}
|
|
13148
13615
|
}
|
|
13149
13616
|
function readIdentity(projectRoot) {
|
|
13150
|
-
const filePath =
|
|
13617
|
+
const filePath = path44.join(projectRoot, ".harness", "telemetry.json");
|
|
13151
13618
|
const identity = parseIdentityFromTelemetryFile(filePath);
|
|
13152
13619
|
if (!identity.project) {
|
|
13153
|
-
const fallbackProject = readProjectNameFallback(
|
|
13620
|
+
const fallbackProject = readProjectNameFallback(path44.join(projectRoot, "harness.config.json"));
|
|
13154
13621
|
if (fallbackProject) identity.project = fallbackProject;
|
|
13155
13622
|
}
|
|
13156
13623
|
if (!identity.alias) {
|
|
@@ -13197,7 +13664,7 @@ function collectEvents(projectRoot, consent) {
|
|
|
13197
13664
|
);
|
|
13198
13665
|
}
|
|
13199
13666
|
function sleep3(ms) {
|
|
13200
|
-
return new Promise((
|
|
13667
|
+
return new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
13201
13668
|
}
|
|
13202
13669
|
async function send(events, apiKey) {
|
|
13203
13670
|
if (events.length === 0) return;
|
|
@@ -13273,36 +13740,383 @@ function spanToOTLP(s) {
|
|
|
13273
13740
|
if (s.statusCode !== void 0) span["status"] = { code: s.statusCode };
|
|
13274
13741
|
return span;
|
|
13275
13742
|
}
|
|
13276
|
-
function
|
|
13277
|
-
if (
|
|
13278
|
-
|
|
13279
|
-
|
|
13280
|
-
|
|
13743
|
+
function tierFor(score) {
|
|
13744
|
+
if (score >= 85) return "solid";
|
|
13745
|
+
if (score >= 50) return "at-risk";
|
|
13746
|
+
return "theatre";
|
|
13747
|
+
}
|
|
13748
|
+
function rollupScore(findings) {
|
|
13749
|
+
const deduction = findings.reduce((sum, f) => sum + SEVERITY_WEIGHTS[f.severity], 0);
|
|
13750
|
+
const score = clamp(100 - deduction, 0, 100);
|
|
13751
|
+
return { score, tier: tierFor(score) };
|
|
13752
|
+
}
|
|
13753
|
+
function resolveMode(opts, root) {
|
|
13754
|
+
if (opts.mode) return opts.mode;
|
|
13755
|
+
const hasTemplates = existsSync38(join582(root, "templates"));
|
|
13756
|
+
const hasSkills = existsSync38(join582(root, "agents", "skills"));
|
|
13757
|
+
return hasTemplates && hasSkills ? "toolkit" : "adopter";
|
|
13758
|
+
}
|
|
13759
|
+
function readTextOrNull(path52) {
|
|
13760
|
+
try {
|
|
13761
|
+
return existsSync38(path52) ? readFileSync34(path52, "utf8") : null;
|
|
13762
|
+
} catch {
|
|
13763
|
+
return null;
|
|
13281
13764
|
}
|
|
13282
|
-
|
|
13283
|
-
|
|
13284
|
-
|
|
13285
|
-
|
|
13286
|
-
let fd;
|
|
13765
|
+
}
|
|
13766
|
+
function readJsonOrNull(path52) {
|
|
13767
|
+
const text = readTextOrNull(path52);
|
|
13768
|
+
if (text === null) return null;
|
|
13287
13769
|
try {
|
|
13288
|
-
|
|
13289
|
-
} catch
|
|
13290
|
-
|
|
13291
|
-
if (err.code === "EEXIST") {
|
|
13292
|
-
const holderPid = readHolderPid(lockPath);
|
|
13293
|
-
throw new CompoundLockHeldError(category, holderPid, lockPath);
|
|
13294
|
-
}
|
|
13295
|
-
throw err;
|
|
13770
|
+
return JSON.parse(text);
|
|
13771
|
+
} catch {
|
|
13772
|
+
return null;
|
|
13296
13773
|
}
|
|
13297
|
-
|
|
13298
|
-
|
|
13299
|
-
|
|
13300
|
-
|
|
13301
|
-
|
|
13302
|
-
|
|
13303
|
-
|
|
13304
|
-
|
|
13305
|
-
|
|
13774
|
+
}
|
|
13775
|
+
function toRootRelative(root, abs) {
|
|
13776
|
+
return relative4(root, abs).replaceAll("\\", "/");
|
|
13777
|
+
}
|
|
13778
|
+
function readConfig(root) {
|
|
13779
|
+
const raw = readJsonOrNull(join582(root, "harness.config.json"));
|
|
13780
|
+
if (raw === null) return null;
|
|
13781
|
+
const parsed = HarnessConfigSubsetSchema.safeParse(raw);
|
|
13782
|
+
return parsed.success ? parsed.data : null;
|
|
13783
|
+
}
|
|
13784
|
+
function readHookDir(root, dir) {
|
|
13785
|
+
if (!existsSync38(dir)) return [];
|
|
13786
|
+
try {
|
|
13787
|
+
return readdirSync5(dir).map((name) => ({ name, full: join582(dir, name) })).filter(({ full }) => {
|
|
13788
|
+
try {
|
|
13789
|
+
return statSync4(full).isFile();
|
|
13790
|
+
} catch {
|
|
13791
|
+
return false;
|
|
13792
|
+
}
|
|
13793
|
+
}).map(({ name, full }) => ({
|
|
13794
|
+
name,
|
|
13795
|
+
path: toRootRelative(root, full),
|
|
13796
|
+
text: readTextOrNull(full) ?? ""
|
|
13797
|
+
}));
|
|
13798
|
+
} catch {
|
|
13799
|
+
return [];
|
|
13800
|
+
}
|
|
13801
|
+
}
|
|
13802
|
+
function resolveHookFiles(root) {
|
|
13803
|
+
const collected = /* @__PURE__ */ new Map();
|
|
13804
|
+
for (const h of [
|
|
13805
|
+
...readHookDir(root, join582(root, ".husky")),
|
|
13806
|
+
...readHookDir(root, join582(root, ".claude", "hooks")),
|
|
13807
|
+
...readHookDir(root, join582(root, ".harness", "hooks"))
|
|
13808
|
+
]) {
|
|
13809
|
+
collected.set(resolve6(root, h.path), h);
|
|
13810
|
+
}
|
|
13811
|
+
const settings = readJsonOrNull(join582(root, ".claude", "settings.json"));
|
|
13812
|
+
for (const ref of extractSettingsHookScripts(settings, root)) {
|
|
13813
|
+
const abs = resolve6(root, ref);
|
|
13814
|
+
if (collected.has(abs)) continue;
|
|
13815
|
+
const text = readTextOrNull(abs);
|
|
13816
|
+
if (text !== null) {
|
|
13817
|
+
collected.set(abs, { name: basename10(abs), path: toRootRelative(root, abs), text });
|
|
13818
|
+
}
|
|
13819
|
+
}
|
|
13820
|
+
return [...collected.values()];
|
|
13821
|
+
}
|
|
13822
|
+
function extractSettingsHookScripts(settings, root) {
|
|
13823
|
+
const out = [];
|
|
13824
|
+
if (settings === null || typeof settings !== "object") return out;
|
|
13825
|
+
const hooks = settings.hooks;
|
|
13826
|
+
const walk3 = (v) => {
|
|
13827
|
+
if (typeof v === "string") {
|
|
13828
|
+
for (const m of v.matchAll(HOOK_SCRIPT_TOKEN)) {
|
|
13829
|
+
const token = m[1];
|
|
13830
|
+
if (token) out.push(normalizeHookRef(token, root));
|
|
13831
|
+
}
|
|
13832
|
+
} else if (Array.isArray(v)) {
|
|
13833
|
+
v.forEach(walk3);
|
|
13834
|
+
} else if (v && typeof v === "object") {
|
|
13835
|
+
Object.values(v).forEach(walk3);
|
|
13836
|
+
}
|
|
13837
|
+
};
|
|
13838
|
+
walk3(hooks);
|
|
13839
|
+
return out;
|
|
13840
|
+
}
|
|
13841
|
+
function normalizeHookRef(token, root) {
|
|
13842
|
+
let ref = token.replace(/^["']|["']$/g, "");
|
|
13843
|
+
ref = ref.replace(/^\$\(git rev-parse --show-toplevel\)\/?/, `${root}/`);
|
|
13844
|
+
return ref;
|
|
13845
|
+
}
|
|
13846
|
+
function readWorkflows(root) {
|
|
13847
|
+
const dir = join582(root, ".github", "workflows");
|
|
13848
|
+
if (!existsSync38(dir)) return [];
|
|
13849
|
+
try {
|
|
13850
|
+
return readdirSync5(dir).filter((n) => n.endsWith(".yml") || n.endsWith(".yaml")).map((n) => join582(dir, n)).filter((p) => {
|
|
13851
|
+
try {
|
|
13852
|
+
return statSync4(p).isFile();
|
|
13853
|
+
} catch {
|
|
13854
|
+
return false;
|
|
13855
|
+
}
|
|
13856
|
+
}).map((p) => ({ path: toRootRelative(root, p), text: readTextOrNull(p) ?? "" }));
|
|
13857
|
+
} catch {
|
|
13858
|
+
return [];
|
|
13859
|
+
}
|
|
13860
|
+
}
|
|
13861
|
+
function readTemplates(root) {
|
|
13862
|
+
const dir = join582(root, "templates");
|
|
13863
|
+
if (!existsSync38(dir)) return [];
|
|
13864
|
+
const out = [];
|
|
13865
|
+
const walk3 = (d) => {
|
|
13866
|
+
let entries;
|
|
13867
|
+
try {
|
|
13868
|
+
entries = readdirSync5(d);
|
|
13869
|
+
} catch {
|
|
13870
|
+
return;
|
|
13871
|
+
}
|
|
13872
|
+
for (const name of entries) {
|
|
13873
|
+
const full = join582(d, name);
|
|
13874
|
+
let st;
|
|
13875
|
+
try {
|
|
13876
|
+
st = statSync4(full);
|
|
13877
|
+
} catch {
|
|
13878
|
+
continue;
|
|
13879
|
+
}
|
|
13880
|
+
if (st.isDirectory()) walk3(full);
|
|
13881
|
+
else if (name.endsWith(".hbs"))
|
|
13882
|
+
out.push({ path: toRootRelative(root, full), text: readTextOrNull(full) ?? "" });
|
|
13883
|
+
}
|
|
13884
|
+
};
|
|
13885
|
+
walk3(dir);
|
|
13886
|
+
return out;
|
|
13887
|
+
}
|
|
13888
|
+
function readInitSkill(root) {
|
|
13889
|
+
return readTextOrNull(
|
|
13890
|
+
join582(root, "agents", "skills", "claude-code", "initialize-harness-project", "SKILL.md")
|
|
13891
|
+
);
|
|
13892
|
+
}
|
|
13893
|
+
function buildProjectContext(root, mode) {
|
|
13894
|
+
const ctx = {
|
|
13895
|
+
root,
|
|
13896
|
+
mode,
|
|
13897
|
+
config: readConfig(root),
|
|
13898
|
+
preCommit: readTextOrNull(join582(root, ".husky", "pre-commit")),
|
|
13899
|
+
hookFiles: resolveHookFiles(root),
|
|
13900
|
+
workflows: readWorkflows(root),
|
|
13901
|
+
healthSnapshot: readJsonOrNull(join582(root, ".harness", "health-snapshot.json"))
|
|
13902
|
+
};
|
|
13903
|
+
if (mode === "toolkit") {
|
|
13904
|
+
ctx.templates = readTemplates(root);
|
|
13905
|
+
ctx.initSkill = readInitSkill(root);
|
|
13906
|
+
}
|
|
13907
|
+
return ctx;
|
|
13908
|
+
}
|
|
13909
|
+
function stripComments(text) {
|
|
13910
|
+
return text.split("\n").map((l) => l.replace(/(^|\s)#.*$/, "$1")).join("\n");
|
|
13911
|
+
}
|
|
13912
|
+
function findRewriteInFailureBranch(lines) {
|
|
13913
|
+
const ifStack = [];
|
|
13914
|
+
for (let i = 0; i < lines.length; i++) {
|
|
13915
|
+
const l = lines[i];
|
|
13916
|
+
const ifCount = (l.match(/\bif\b/g) ?? []).length;
|
|
13917
|
+
const fiCount = (l.match(/\bfi\b/g) ?? []).length;
|
|
13918
|
+
const opensFailure = /\bif\s+!/.test(l);
|
|
13919
|
+
for (let k = 0; k < ifCount; k++) ifStack.push(opensFailure);
|
|
13920
|
+
const inFailureBranch = ifStack.includes(true);
|
|
13921
|
+
for (let k = 0; k < fiCount; k++) ifStack.pop();
|
|
13922
|
+
if (inFailureBranch && BASELINE_REWRITE.test(l)) return i;
|
|
13923
|
+
}
|
|
13924
|
+
return -1;
|
|
13925
|
+
}
|
|
13926
|
+
function thresholdsEmpty(cfg) {
|
|
13927
|
+
if (!cfg) return false;
|
|
13928
|
+
const hasLayers = (cfg.layers?.length ?? 0) > 0;
|
|
13929
|
+
if (!hasLayers) return false;
|
|
13930
|
+
const thresholds = cfg.architecture?.thresholds;
|
|
13931
|
+
return !thresholds || Object.keys(thresholds).length === 0;
|
|
13932
|
+
}
|
|
13933
|
+
function makeFinding3(file) {
|
|
13934
|
+
return {
|
|
13935
|
+
id: "STRENGTH-004",
|
|
13936
|
+
gearPiece: "architecture-thresholds",
|
|
13937
|
+
file,
|
|
13938
|
+
message: "Architecture layers are declared but no thresholds are set \u2014 the architecture check has nothing to enforce, so drift goes undetected.",
|
|
13939
|
+
remediation: "Add concrete `architecture.thresholds` (e.g. maxFanOut, maxDependencyDepth) so layer/coupling regressions actually fail the check."
|
|
13940
|
+
};
|
|
13941
|
+
}
|
|
13942
|
+
function isAutoLine(line) {
|
|
13943
|
+
return AUTO_PATTERN.test(line) && !COMMENT_LINE.test(line) && !NAME_VALUE_LINE.test(line);
|
|
13944
|
+
}
|
|
13945
|
+
function hasAutoStep(text) {
|
|
13946
|
+
return text.split("\n").some(isAutoLine);
|
|
13947
|
+
}
|
|
13948
|
+
function isSnapshot(value) {
|
|
13949
|
+
if (value === null || typeof value !== "object") return false;
|
|
13950
|
+
const checks = value.checks;
|
|
13951
|
+
return checks !== null && typeof checks === "object";
|
|
13952
|
+
}
|
|
13953
|
+
function severityFor(rule, ctx) {
|
|
13954
|
+
const override = ctx.config?.audit?.harnessStrength?.severities?.[rule.id];
|
|
13955
|
+
return override ?? rule.defaultSeverity;
|
|
13956
|
+
}
|
|
13957
|
+
async function safeGather(key, fn, warnings) {
|
|
13958
|
+
try {
|
|
13959
|
+
return await fn();
|
|
13960
|
+
} catch (e) {
|
|
13961
|
+
warnings.push(`${key}: ${e instanceof Error ? e.message : String(e)}`);
|
|
13962
|
+
return null;
|
|
13963
|
+
}
|
|
13964
|
+
}
|
|
13965
|
+
function arrayLen(arr) {
|
|
13966
|
+
return Array.isArray(arr) ? arr.length : 0;
|
|
13967
|
+
}
|
|
13968
|
+
async function gatherEntropyBlock(projectPath) {
|
|
13969
|
+
const { EntropyAnalyzer: EntropyAnalyzer2 } = await import("./analyzer-V633GUHY-MBCQWKDU.js");
|
|
13970
|
+
const analyzer = new EntropyAnalyzer2({
|
|
13971
|
+
rootDir: projectPath,
|
|
13972
|
+
include: ["src/**/*.ts", "src/**/*.tsx", "packages/*/src/**/*.ts"],
|
|
13973
|
+
exclude: ["node_modules/**", "dist/**", "**/*.test.ts", "**/*.spec.ts"],
|
|
13974
|
+
analyze: { drift: false, deadCode: true, patterns: false, complexity: false }
|
|
13975
|
+
});
|
|
13976
|
+
const result = await analyzer.analyze();
|
|
13977
|
+
const empty = { driftCount: 0, deadFiles: 0, deadExports: 0 };
|
|
13978
|
+
if (!result.ok) return empty;
|
|
13979
|
+
const report = result.value;
|
|
13980
|
+
return {
|
|
13981
|
+
driftCount: arrayLen(report.drift?.drifts),
|
|
13982
|
+
deadFiles: arrayLen(report.deadCode?.deadFiles),
|
|
13983
|
+
deadExports: arrayLen(report.deadCode?.deadExports)
|
|
13984
|
+
};
|
|
13985
|
+
}
|
|
13986
|
+
async function gatherDecayBlock(projectPath) {
|
|
13987
|
+
const { TimelineManager: TimelineManager2 } = await import("./timeline-manager-FPYKJRHR-VVX7CSGW.js");
|
|
13988
|
+
const mgr = new TimelineManager2(projectPath);
|
|
13989
|
+
const trends = mgr.trends();
|
|
13990
|
+
return {
|
|
13991
|
+
recentBumps: arrayLen(trends.recentSnapshots),
|
|
13992
|
+
topAffected: collectTopAffectedLabels(trends.topAffected)
|
|
13993
|
+
};
|
|
13994
|
+
}
|
|
13995
|
+
function collectTopAffectedLabels(affected) {
|
|
13996
|
+
if (!Array.isArray(affected)) return [];
|
|
13997
|
+
const out = [];
|
|
13998
|
+
for (const node of affected) {
|
|
13999
|
+
const label = node.id ?? node.name;
|
|
14000
|
+
if (typeof label === "string" && label.length > 0) out.push(label);
|
|
14001
|
+
if (out.length >= MAX_TOP_AFFECTED) break;
|
|
14002
|
+
}
|
|
14003
|
+
return out;
|
|
14004
|
+
}
|
|
14005
|
+
async function gatherAttentionBlock(projectPath) {
|
|
14006
|
+
const sessionsDir = path45.join(projectPath, ".harness", "sessions");
|
|
14007
|
+
if (!fs45.existsSync(sessionsDir)) return { activeThreadCount: 0, staleThreadCount: 0 };
|
|
14008
|
+
const now = Date.now();
|
|
14009
|
+
const STALE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
14010
|
+
let active = 0;
|
|
14011
|
+
let stale = 0;
|
|
14012
|
+
const entries = fs45.readdirSync(sessionsDir, { withFileTypes: true });
|
|
14013
|
+
for (const entry of entries) {
|
|
14014
|
+
if (!entry.isDirectory()) continue;
|
|
14015
|
+
const dirPath = path45.join(sessionsDir, entry.name);
|
|
14016
|
+
try {
|
|
14017
|
+
const stat2 = fs45.statSync(dirPath);
|
|
14018
|
+
if (now - stat2.mtimeMs > STALE_MS) {
|
|
14019
|
+
stale++;
|
|
14020
|
+
} else {
|
|
14021
|
+
active++;
|
|
14022
|
+
}
|
|
14023
|
+
} catch {
|
|
14024
|
+
}
|
|
14025
|
+
}
|
|
14026
|
+
return { activeThreadCount: active, staleThreadCount: stale };
|
|
14027
|
+
}
|
|
14028
|
+
async function gatherImpactBlock(projectPath) {
|
|
14029
|
+
const cachePath = path45.join(projectPath, ".harness", "cache", "impact.json");
|
|
14030
|
+
if (!fs45.existsSync(cachePath)) return { recentBlastRadius: [] };
|
|
14031
|
+
try {
|
|
14032
|
+
const raw = JSON.parse(fs45.readFileSync(cachePath, "utf8"));
|
|
14033
|
+
const radii = raw.blastRadius ?? [];
|
|
14034
|
+
return {
|
|
14035
|
+
recentBlastRadius: radii.slice(0, 5).map((r) => ({
|
|
14036
|
+
node: r.node,
|
|
14037
|
+
affected: r.affected
|
|
14038
|
+
}))
|
|
14039
|
+
};
|
|
14040
|
+
} catch {
|
|
14041
|
+
return { recentBlastRadius: [] };
|
|
14042
|
+
}
|
|
14043
|
+
}
|
|
14044
|
+
async function gatherHealthBlock(projectPath) {
|
|
14045
|
+
const entropy = await gatherEntropyBlock(projectPath);
|
|
14046
|
+
const signals = [];
|
|
14047
|
+
if (entropy.driftCount > 0) signals.push(`${entropy.driftCount} drift findings`);
|
|
14048
|
+
if (entropy.deadFiles > 0) signals.push(`${entropy.deadFiles} dead files`);
|
|
14049
|
+
if (entropy.deadExports > 0) signals.push(`${entropy.deadExports} dead exports`);
|
|
14050
|
+
const passed = signals.length === 0;
|
|
14051
|
+
return {
|
|
14052
|
+
passed,
|
|
14053
|
+
signals,
|
|
14054
|
+
summary: passed ? "No structural health findings." : signals.join("; ")
|
|
14055
|
+
};
|
|
14056
|
+
}
|
|
14057
|
+
async function composeInsights(projectPath, opts = {}) {
|
|
14058
|
+
const skip = new Set(opts.skip ?? []);
|
|
14059
|
+
const warnings = [];
|
|
14060
|
+
const wantedKeys = INSIGHTS_KEYS.filter((k) => !skip.has(k));
|
|
14061
|
+
const [healthRes, entropyRes, decayRes, attentionRes, impactRes] = await Promise.all([
|
|
14062
|
+
wantedKeys.includes("health") ? safeGather("health", () => gatherHealthBlock(projectPath), warnings) : Promise.resolve(null),
|
|
14063
|
+
wantedKeys.includes("entropy") ? safeGather("entropy", () => gatherEntropyBlock(projectPath), warnings) : Promise.resolve(null),
|
|
14064
|
+
wantedKeys.includes("decay") ? safeGather("decay", () => gatherDecayBlock(projectPath), warnings) : Promise.resolve(null),
|
|
14065
|
+
wantedKeys.includes("attention") ? safeGather("attention", () => gatherAttentionBlock(projectPath), warnings) : Promise.resolve(null),
|
|
14066
|
+
wantedKeys.includes("impact") ? safeGather("impact", () => gatherImpactBlock(projectPath), warnings) : Promise.resolve(null)
|
|
14067
|
+
]);
|
|
14068
|
+
const projectName = pickProjectName(projectPath);
|
|
14069
|
+
return {
|
|
14070
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
14071
|
+
project: { root: projectPath, ...projectName && { name: projectName } },
|
|
14072
|
+
health: healthRes,
|
|
14073
|
+
entropy: entropyRes,
|
|
14074
|
+
decay: decayRes,
|
|
14075
|
+
attention: attentionRes,
|
|
14076
|
+
impact: impactRes,
|
|
14077
|
+
warnings
|
|
14078
|
+
};
|
|
14079
|
+
}
|
|
14080
|
+
function pickProjectName(projectPath) {
|
|
14081
|
+
try {
|
|
14082
|
+
const pkgPath = path45.join(projectPath, "package.json");
|
|
14083
|
+
if (!fs45.existsSync(pkgPath)) return void 0;
|
|
14084
|
+
const pkg = JSON.parse(fs45.readFileSync(pkgPath, "utf8"));
|
|
14085
|
+
return pkg.name;
|
|
14086
|
+
} catch {
|
|
14087
|
+
return void 0;
|
|
14088
|
+
}
|
|
14089
|
+
}
|
|
14090
|
+
function acquireCompoundLock(category, opts = {}) {
|
|
14091
|
+
if (!KNOWN_CATEGORIES.has(category)) {
|
|
14092
|
+
throw new Error(
|
|
14093
|
+
`Unknown category "${category}". Must be one of: ${[...KNOWN_CATEGORIES].join(", ")}`
|
|
14094
|
+
);
|
|
14095
|
+
}
|
|
14096
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
14097
|
+
const lockDir = path46.join(cwd, ".harness", "locks");
|
|
14098
|
+
fs46.mkdirSync(lockDir, { recursive: true });
|
|
14099
|
+
const lockPath = path46.join(lockDir, `compound-${category}.lock`);
|
|
14100
|
+
let fd;
|
|
14101
|
+
try {
|
|
14102
|
+
fd = fs46.openSync(lockPath, "wx");
|
|
14103
|
+
} catch (e) {
|
|
14104
|
+
const err = e;
|
|
14105
|
+
if (err.code === "EEXIST") {
|
|
14106
|
+
const holderPid = readHolderPid(lockPath);
|
|
14107
|
+
throw new CompoundLockHeldError(category, holderPid, lockPath);
|
|
14108
|
+
}
|
|
14109
|
+
throw err;
|
|
14110
|
+
}
|
|
14111
|
+
fs46.writeSync(fd, String(process.pid));
|
|
14112
|
+
fs46.closeSync(fd);
|
|
14113
|
+
let released = false;
|
|
14114
|
+
const release = () => {
|
|
14115
|
+
if (released) return;
|
|
14116
|
+
released = true;
|
|
14117
|
+
try {
|
|
14118
|
+
fs46.unlinkSync(lockPath);
|
|
14119
|
+
} catch {
|
|
13306
14120
|
}
|
|
13307
14121
|
process.removeListener("exit", onExit);
|
|
13308
14122
|
process.removeListener("SIGINT", onSignal);
|
|
@@ -13327,13 +14141,151 @@ function acquireCompoundLock(category, opts = {}) {
|
|
|
13327
14141
|
}
|
|
13328
14142
|
function readHolderPid(lockPath) {
|
|
13329
14143
|
try {
|
|
13330
|
-
const raw =
|
|
14144
|
+
const raw = fs46.readFileSync(lockPath, "utf-8").trim();
|
|
13331
14145
|
const n = Number.parseInt(raw, 10);
|
|
13332
14146
|
return Number.isFinite(n) ? n : -1;
|
|
13333
14147
|
} catch {
|
|
13334
14148
|
return -1;
|
|
13335
14149
|
}
|
|
13336
14150
|
}
|
|
14151
|
+
function loadNotificationsConfig(projectRoot) {
|
|
14152
|
+
const configPath = path47.join(projectRoot, "harness.config.json");
|
|
14153
|
+
if (!fs47.existsSync(configPath)) {
|
|
14154
|
+
return Ok({ sinks: [] });
|
|
14155
|
+
}
|
|
14156
|
+
let raw;
|
|
14157
|
+
try {
|
|
14158
|
+
raw = fs47.readFileSync(configPath, "utf-8");
|
|
14159
|
+
} catch (err) {
|
|
14160
|
+
return Err(err instanceof Error ? err : new Error(String(err)));
|
|
14161
|
+
}
|
|
14162
|
+
let parsed;
|
|
14163
|
+
try {
|
|
14164
|
+
parsed = JSON.parse(raw);
|
|
14165
|
+
} catch (err) {
|
|
14166
|
+
return Err(
|
|
14167
|
+
new Error(
|
|
14168
|
+
`Failed to parse harness.config.json: ${err instanceof Error ? err.message : String(err)}`
|
|
14169
|
+
)
|
|
14170
|
+
);
|
|
14171
|
+
}
|
|
14172
|
+
if (parsed.notifications === void 0) {
|
|
14173
|
+
return Ok({ sinks: [] });
|
|
14174
|
+
}
|
|
14175
|
+
const result = NotificationsConfigSchema.safeParse(parsed.notifications);
|
|
14176
|
+
if (!result.success) {
|
|
14177
|
+
const issues = result.error.issues.map((i) => ` - notifications.${i.path.join(".")}: ${i.message}`).join("\n");
|
|
14178
|
+
return Err(new Error(`Invalid notifications config:
|
|
14179
|
+
${issues}`));
|
|
14180
|
+
}
|
|
14181
|
+
return Ok(result.data);
|
|
14182
|
+
}
|
|
14183
|
+
function proposalsDir(projectPath) {
|
|
14184
|
+
return path48.join(projectPath, ".harness", "proposals");
|
|
14185
|
+
}
|
|
14186
|
+
function proposalPath(projectPath, id) {
|
|
14187
|
+
return path48.join(proposalsDir(projectPath), `${id}.json`);
|
|
14188
|
+
}
|
|
14189
|
+
function ensureDir(dir) {
|
|
14190
|
+
fs48.mkdirSync(dir, { recursive: true });
|
|
14191
|
+
}
|
|
14192
|
+
function writeAtomic(filePath, content) {
|
|
14193
|
+
const tmpPath = `${filePath}.tmp`;
|
|
14194
|
+
fs48.writeFileSync(tmpPath, content);
|
|
14195
|
+
fs48.renameSync(tmpPath, filePath);
|
|
14196
|
+
}
|
|
14197
|
+
async function createProposal(projectPath, input) {
|
|
14198
|
+
const validated = EmitSkillProposalInputSchema.parse(input);
|
|
14199
|
+
const id = `proposal_${randomUUID2().replace(/-/g, "")}`;
|
|
14200
|
+
const proposal = SkillProposalSchema.parse({
|
|
14201
|
+
id,
|
|
14202
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
14203
|
+
kind: validated.kind,
|
|
14204
|
+
targetSkill: validated.targetSkill,
|
|
14205
|
+
proposedBy: validated.proposedBy,
|
|
14206
|
+
source: {
|
|
14207
|
+
sessionId: validated.sessionId,
|
|
14208
|
+
taskId: validated.taskId,
|
|
14209
|
+
justification: validated.justification
|
|
14210
|
+
},
|
|
14211
|
+
content: validated.content,
|
|
14212
|
+
status: "open"
|
|
14213
|
+
});
|
|
14214
|
+
if (proposal.kind === "refinement" && proposal.targetSkill) {
|
|
14215
|
+
const existing = await listProposals(projectPath, { status: "open" });
|
|
14216
|
+
const clash = existing.find(
|
|
14217
|
+
(p) => p.kind === "refinement" && p.targetSkill === proposal.targetSkill
|
|
14218
|
+
);
|
|
14219
|
+
if (clash) {
|
|
14220
|
+
throw new ProposalConflictError(
|
|
14221
|
+
`An open refinement proposal already exists for skill "${proposal.targetSkill}" (id: ${clash.id})`
|
|
14222
|
+
);
|
|
14223
|
+
}
|
|
14224
|
+
}
|
|
14225
|
+
const dir = proposalsDir(projectPath);
|
|
14226
|
+
ensureDir(dir);
|
|
14227
|
+
writeAtomic(proposalPath(projectPath, id), JSON.stringify(proposal, null, 2));
|
|
14228
|
+
return proposal;
|
|
14229
|
+
}
|
|
14230
|
+
async function getProposal(projectPath, id) {
|
|
14231
|
+
const file = proposalPath(projectPath, id);
|
|
14232
|
+
try {
|
|
14233
|
+
const raw = fs48.readFileSync(file, "utf-8");
|
|
14234
|
+
const parsed = SkillProposalSchema.safeParse(JSON.parse(raw));
|
|
14235
|
+
return parsed.success ? parsed.data : null;
|
|
14236
|
+
} catch {
|
|
14237
|
+
return null;
|
|
14238
|
+
}
|
|
14239
|
+
}
|
|
14240
|
+
async function listProposals(projectPath, opts = {}) {
|
|
14241
|
+
const dir = proposalsDir(projectPath);
|
|
14242
|
+
let files;
|
|
14243
|
+
try {
|
|
14244
|
+
files = fs48.readdirSync(dir);
|
|
14245
|
+
} catch {
|
|
14246
|
+
return [];
|
|
14247
|
+
}
|
|
14248
|
+
const out = [];
|
|
14249
|
+
for (const file of files) {
|
|
14250
|
+
if (!file.endsWith(".json")) continue;
|
|
14251
|
+
const id = file.slice(0, -".json".length);
|
|
14252
|
+
const proposal = await getProposal(projectPath, id);
|
|
14253
|
+
if (!proposal) continue;
|
|
14254
|
+
if (opts.status && opts.status !== "all" && proposal.status !== opts.status) continue;
|
|
14255
|
+
out.push(proposal);
|
|
14256
|
+
}
|
|
14257
|
+
out.sort((a, b) => a.createdAt > b.createdAt ? -1 : a.createdAt < b.createdAt ? 1 : 0);
|
|
14258
|
+
return out;
|
|
14259
|
+
}
|
|
14260
|
+
async function updateProposal(projectPath, id, patch) {
|
|
14261
|
+
const current = await getProposal(projectPath, id);
|
|
14262
|
+
if (!current) throw new ProposalNotFoundError(id);
|
|
14263
|
+
const next = SkillProposalSchema.parse({
|
|
14264
|
+
...current,
|
|
14265
|
+
...patch,
|
|
14266
|
+
id: current.id,
|
|
14267
|
+
createdAt: current.createdAt,
|
|
14268
|
+
kind: current.kind
|
|
14269
|
+
});
|
|
14270
|
+
writeAtomic(proposalPath(projectPath, id), JSON.stringify(next, null, 2));
|
|
14271
|
+
return next;
|
|
14272
|
+
}
|
|
14273
|
+
function deriveSkillUsage(projectRoot, skillName, windowDays = 30) {
|
|
14274
|
+
const records = readAdoptionRecords(projectRoot);
|
|
14275
|
+
const cutoffMs = Date.now() - windowDays * 24 * 60 * 60 * 1e3;
|
|
14276
|
+
let count = 0;
|
|
14277
|
+
let lastUsed;
|
|
14278
|
+
for (const rec of records) {
|
|
14279
|
+
if (rec.skill !== skillName) continue;
|
|
14280
|
+
const startedMs = Date.parse(rec.startedAt);
|
|
14281
|
+
if (Number.isFinite(startedMs) && startedMs < cutoffMs) continue;
|
|
14282
|
+
count += 1;
|
|
14283
|
+
if (!lastUsed || rec.startedAt > lastUsed) lastUsed = rec.startedAt;
|
|
14284
|
+
}
|
|
14285
|
+
const stats = { count, windowDays };
|
|
14286
|
+
if (lastUsed) stats.lastUsed = lastUsed;
|
|
14287
|
+
return stats;
|
|
14288
|
+
}
|
|
13337
14289
|
function isObject(value) {
|
|
13338
14290
|
return Boolean(value) && typeof value === "object";
|
|
13339
14291
|
}
|
|
@@ -13359,10 +14311,10 @@ function assertSanitized(value) {
|
|
|
13359
14311
|
}
|
|
13360
14312
|
function writePulseConfig(config, opts) {
|
|
13361
14313
|
PulseConfigSchema.parse(config);
|
|
13362
|
-
if (!
|
|
14314
|
+
if (!fs49.existsSync(opts.configPath)) {
|
|
13363
14315
|
throw new Error(`harness.config.json not found at ${opts.configPath}`);
|
|
13364
14316
|
}
|
|
13365
|
-
const raw =
|
|
14317
|
+
const raw = fs49.readFileSync(opts.configPath, "utf-8");
|
|
13366
14318
|
let parsed;
|
|
13367
14319
|
try {
|
|
13368
14320
|
parsed = JSON.parse(raw);
|
|
@@ -13370,18 +14322,18 @@ function writePulseConfig(config, opts) {
|
|
|
13370
14322
|
throw new Error(`Invalid JSON in ${opts.configPath}: ${e.message}`, { cause: e });
|
|
13371
14323
|
}
|
|
13372
14324
|
const bakPath = `${opts.configPath}.bak`;
|
|
13373
|
-
if (!opts.skipBackup && !
|
|
13374
|
-
|
|
14325
|
+
if (!opts.skipBackup && !fs49.existsSync(bakPath)) {
|
|
14326
|
+
fs49.writeFileSync(bakPath, raw, "utf-8");
|
|
13375
14327
|
}
|
|
13376
14328
|
parsed.pulse = config;
|
|
13377
14329
|
const serialized = JSON.stringify(parsed, null, 2) + "\n";
|
|
13378
14330
|
const tmpPath = `${opts.configPath}.tmp-${process.pid}`;
|
|
13379
|
-
|
|
14331
|
+
fs49.writeFileSync(tmpPath, serialized, "utf-8");
|
|
13380
14332
|
try {
|
|
13381
|
-
|
|
14333
|
+
fs49.renameSync(tmpPath, opts.configPath);
|
|
13382
14334
|
} catch (e) {
|
|
13383
14335
|
try {
|
|
13384
|
-
|
|
14336
|
+
fs49.unlinkSync(tmpPath);
|
|
13385
14337
|
} catch {
|
|
13386
14338
|
}
|
|
13387
14339
|
throw e;
|
|
@@ -13389,12 +14341,12 @@ function writePulseConfig(config, opts) {
|
|
|
13389
14341
|
}
|
|
13390
14342
|
function seedFromStrategy(opts = {}) {
|
|
13391
14343
|
const cwd = opts.cwd ?? process.cwd();
|
|
13392
|
-
const strategyPath =
|
|
14344
|
+
const strategyPath = path49.join(cwd, "STRATEGY.md");
|
|
13393
14345
|
const warnings = [];
|
|
13394
|
-
if (!
|
|
14346
|
+
if (!fs50.existsSync(strategyPath)) {
|
|
13395
14347
|
return { name: null, keyMetrics: [], warnings: ["STRATEGY.md not found"] };
|
|
13396
14348
|
}
|
|
13397
|
-
const raw =
|
|
14349
|
+
const raw = fs50.readFileSync(strategyPath, "utf-8");
|
|
13398
14350
|
let name = null;
|
|
13399
14351
|
const fmMatch = /^---\s*\n([\s\S]*?)\n---\s*\n/.exec(raw);
|
|
13400
14352
|
const fm = fmMatch?.[1];
|
|
@@ -13546,7 +14498,7 @@ function loadTemplate() {
|
|
|
13546
14498
|
const url = import.meta?.url;
|
|
13547
14499
|
if (!url) return INLINE_TEMPLATE;
|
|
13548
14500
|
const here = dirname16(fileURLToPath(url));
|
|
13549
|
-
return
|
|
14501
|
+
return readFileSync41(join642(here, "template.md"), "utf-8");
|
|
13550
14502
|
} catch {
|
|
13551
14503
|
return INLINE_TEMPLATE;
|
|
13552
14504
|
}
|
|
@@ -13749,12 +14701,12 @@ function tokenize(text) {
|
|
|
13749
14701
|
async function* walk2(dir) {
|
|
13750
14702
|
let entries;
|
|
13751
14703
|
try {
|
|
13752
|
-
entries = await
|
|
14704
|
+
entries = await fs51.readdir(dir, { withFileTypes: true });
|
|
13753
14705
|
} catch {
|
|
13754
14706
|
return;
|
|
13755
14707
|
}
|
|
13756
14708
|
for (const e of entries) {
|
|
13757
|
-
const p =
|
|
14709
|
+
const p = path50.join(dir, e.name);
|
|
13758
14710
|
if (e.isDirectory()) yield* walk2(p);
|
|
13759
14711
|
else if (e.isFile() && e.name.endsWith(".md")) yield p;
|
|
13760
14712
|
}
|
|
@@ -13763,11 +14715,11 @@ async function readDocumentedTokens(solutionsDir) {
|
|
|
13763
14715
|
const docs = [];
|
|
13764
14716
|
for (const [track, categories] of TRACK_CATEGORIES2) {
|
|
13765
14717
|
for (const category of categories) {
|
|
13766
|
-
const dir =
|
|
14718
|
+
const dir = path50.join(solutionsDir, track, category);
|
|
13767
14719
|
for await (const file of walk2(dir)) {
|
|
13768
|
-
const raw = await
|
|
14720
|
+
const raw = await fs51.readFile(file, "utf-8");
|
|
13769
14721
|
const m = /^#\s+(.+)$/m.exec(raw);
|
|
13770
|
-
const title = m?.[1] ??
|
|
14722
|
+
const title = m?.[1] ?? path50.basename(file, ".md");
|
|
13771
14723
|
docs.push(tokenize(title));
|
|
13772
14724
|
}
|
|
13773
14725
|
}
|
|
@@ -13852,161 +14804,55 @@ function emitSection(name, body) {
|
|
|
13852
14804
|
` : `## ${name}
|
|
13853
14805
|
|
|
13854
14806
|
${trimmed}
|
|
13855
|
-
`;
|
|
13856
|
-
}
|
|
13857
|
-
function serializeStrategyDoc(doc, opts = {}) {
|
|
13858
|
-
const sectionByName = new Map(doc.sections.map((s) => [s.name, s.body]));
|
|
13859
|
-
const sectionBlocks = [];
|
|
13860
|
-
for (const name of SECTION_ORDER) {
|
|
13861
|
-
const body = sectionByName.get(name);
|
|
13862
|
-
if (body === void 0) continue;
|
|
13863
|
-
sectionBlocks.push(emitSection(name, body));
|
|
13864
|
-
}
|
|
13865
|
-
const blocks = [emitFrontmatter(doc), "", emitH1(doc, opts), "", sectionBlocks.join("\n")];
|
|
13866
|
-
const out = blocks.join("\n");
|
|
13867
|
-
return out.endsWith("\n") ? out : out + "\n";
|
|
13868
|
-
}
|
|
13869
|
-
function readExistingH1(filePath) {
|
|
13870
|
-
if (!fs51.existsSync(filePath)) return void 0;
|
|
13871
|
-
const raw = fs51.readFileSync(filePath, "utf-8");
|
|
13872
|
-
const lines = raw.split("\n").slice(0, 50);
|
|
13873
|
-
for (const line of lines) {
|
|
13874
|
-
if (line.startsWith("# ")) return line.trimEnd();
|
|
13875
|
-
}
|
|
13876
|
-
return void 0;
|
|
13877
|
-
}
|
|
13878
|
-
function writeStrategyDoc(doc, opts) {
|
|
13879
|
-
StrategyDocSchema.parse(doc);
|
|
13880
|
-
const targetPath = path50.join(opts.cwd, FILENAME);
|
|
13881
|
-
const bakPath = `${targetPath}.bak`;
|
|
13882
|
-
const tmpPath = `${targetPath}.tmp-${process.pid}`;
|
|
13883
|
-
const preservedH1 = readExistingH1(targetPath);
|
|
13884
|
-
if (fs51.existsSync(targetPath) && !opts.skipBackup && !fs51.existsSync(bakPath)) {
|
|
13885
|
-
const raw = fs51.readFileSync(targetPath, "utf-8");
|
|
13886
|
-
fs51.writeFileSync(bakPath, raw, "utf-8");
|
|
13887
|
-
}
|
|
13888
|
-
const serialized = serializeStrategyDoc(
|
|
13889
|
-
doc,
|
|
13890
|
-
preservedH1 !== void 0 ? { h1: preservedH1 } : {}
|
|
13891
|
-
);
|
|
13892
|
-
fs51.writeFileSync(tmpPath, serialized, "utf-8");
|
|
13893
|
-
try {
|
|
13894
|
-
fs51.renameSync(tmpPath, targetPath);
|
|
13895
|
-
} catch (e) {
|
|
13896
|
-
try {
|
|
13897
|
-
fs51.unlinkSync(tmpPath);
|
|
13898
|
-
} catch {
|
|
13899
|
-
}
|
|
13900
|
-
throw e;
|
|
13901
|
-
}
|
|
13902
|
-
}
|
|
13903
|
-
function proposalsDir(projectPath) {
|
|
13904
|
-
return path51.join(projectPath, ".harness", "proposals");
|
|
13905
|
-
}
|
|
13906
|
-
function proposalPath(projectPath, id) {
|
|
13907
|
-
return path51.join(proposalsDir(projectPath), `${id}.json`);
|
|
13908
|
-
}
|
|
13909
|
-
function ensureDir(dir) {
|
|
13910
|
-
fs52.mkdirSync(dir, { recursive: true });
|
|
13911
|
-
}
|
|
13912
|
-
function writeAtomic(filePath, content) {
|
|
13913
|
-
const tmpPath = `${filePath}.tmp`;
|
|
13914
|
-
fs52.writeFileSync(tmpPath, content);
|
|
13915
|
-
fs52.renameSync(tmpPath, filePath);
|
|
13916
|
-
}
|
|
13917
|
-
async function createProposal(projectPath, input) {
|
|
13918
|
-
const validated = EmitSkillProposalInputSchema.parse(input);
|
|
13919
|
-
const id = `proposal_${randomUUID2().replace(/-/g, "")}`;
|
|
13920
|
-
const proposal = SkillProposalSchema.parse({
|
|
13921
|
-
id,
|
|
13922
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13923
|
-
kind: validated.kind,
|
|
13924
|
-
targetSkill: validated.targetSkill,
|
|
13925
|
-
proposedBy: validated.proposedBy,
|
|
13926
|
-
source: {
|
|
13927
|
-
sessionId: validated.sessionId,
|
|
13928
|
-
taskId: validated.taskId,
|
|
13929
|
-
justification: validated.justification
|
|
13930
|
-
},
|
|
13931
|
-
content: validated.content,
|
|
13932
|
-
status: "open"
|
|
13933
|
-
});
|
|
13934
|
-
if (proposal.kind === "refinement" && proposal.targetSkill) {
|
|
13935
|
-
const existing = await listProposals(projectPath, { status: "open" });
|
|
13936
|
-
const clash = existing.find(
|
|
13937
|
-
(p) => p.kind === "refinement" && p.targetSkill === proposal.targetSkill
|
|
13938
|
-
);
|
|
13939
|
-
if (clash) {
|
|
13940
|
-
throw new ProposalConflictError(
|
|
13941
|
-
`An open refinement proposal already exists for skill "${proposal.targetSkill}" (id: ${clash.id})`
|
|
13942
|
-
);
|
|
13943
|
-
}
|
|
13944
|
-
}
|
|
13945
|
-
const dir = proposalsDir(projectPath);
|
|
13946
|
-
ensureDir(dir);
|
|
13947
|
-
writeAtomic(proposalPath(projectPath, id), JSON.stringify(proposal, null, 2));
|
|
13948
|
-
return proposal;
|
|
14807
|
+
`;
|
|
13949
14808
|
}
|
|
13950
|
-
|
|
13951
|
-
const
|
|
13952
|
-
|
|
13953
|
-
|
|
13954
|
-
const
|
|
13955
|
-
|
|
13956
|
-
|
|
13957
|
-
return null;
|
|
14809
|
+
function serializeStrategyDoc(doc, opts = {}) {
|
|
14810
|
+
const sectionByName = new Map(doc.sections.map((s) => [s.name, s.body]));
|
|
14811
|
+
const sectionBlocks = [];
|
|
14812
|
+
for (const name of SECTION_ORDER) {
|
|
14813
|
+
const body = sectionByName.get(name);
|
|
14814
|
+
if (body === void 0) continue;
|
|
14815
|
+
sectionBlocks.push(emitSection(name, body));
|
|
13958
14816
|
}
|
|
14817
|
+
const blocks = [emitFrontmatter(doc), "", emitH1(doc, opts), "", sectionBlocks.join("\n")];
|
|
14818
|
+
const out = blocks.join("\n");
|
|
14819
|
+
return out.endsWith("\n") ? out : out + "\n";
|
|
13959
14820
|
}
|
|
13960
|
-
|
|
13961
|
-
|
|
13962
|
-
|
|
13963
|
-
|
|
13964
|
-
|
|
13965
|
-
|
|
13966
|
-
return [];
|
|
13967
|
-
}
|
|
13968
|
-
const out = [];
|
|
13969
|
-
for (const file of files) {
|
|
13970
|
-
if (!file.endsWith(".json")) continue;
|
|
13971
|
-
const id = file.slice(0, -".json".length);
|
|
13972
|
-
const proposal = await getProposal(projectPath, id);
|
|
13973
|
-
if (!proposal) continue;
|
|
13974
|
-
if (opts.status && opts.status !== "all" && proposal.status !== opts.status) continue;
|
|
13975
|
-
out.push(proposal);
|
|
14821
|
+
function readExistingH1(filePath) {
|
|
14822
|
+
if (!fs52.existsSync(filePath)) return void 0;
|
|
14823
|
+
const raw = fs52.readFileSync(filePath, "utf-8");
|
|
14824
|
+
const lines = raw.split("\n").slice(0, 50);
|
|
14825
|
+
for (const line of lines) {
|
|
14826
|
+
if (line.startsWith("# ")) return line.trimEnd();
|
|
13976
14827
|
}
|
|
13977
|
-
|
|
13978
|
-
return out;
|
|
13979
|
-
}
|
|
13980
|
-
async function updateProposal(projectPath, id, patch) {
|
|
13981
|
-
const current = await getProposal(projectPath, id);
|
|
13982
|
-
if (!current) throw new ProposalNotFoundError(id);
|
|
13983
|
-
const next = SkillProposalSchema.parse({
|
|
13984
|
-
...current,
|
|
13985
|
-
...patch,
|
|
13986
|
-
id: current.id,
|
|
13987
|
-
createdAt: current.createdAt,
|
|
13988
|
-
kind: current.kind
|
|
13989
|
-
});
|
|
13990
|
-
writeAtomic(proposalPath(projectPath, id), JSON.stringify(next, null, 2));
|
|
13991
|
-
return next;
|
|
14828
|
+
return void 0;
|
|
13992
14829
|
}
|
|
13993
|
-
function
|
|
13994
|
-
|
|
13995
|
-
const
|
|
13996
|
-
|
|
13997
|
-
|
|
13998
|
-
|
|
13999
|
-
|
|
14000
|
-
const
|
|
14001
|
-
|
|
14002
|
-
|
|
14003
|
-
|
|
14830
|
+
function writeStrategyDoc(doc, opts) {
|
|
14831
|
+
StrategyDocSchema.parse(doc);
|
|
14832
|
+
const targetPath = path51.join(opts.cwd, FILENAME);
|
|
14833
|
+
const bakPath = `${targetPath}.bak`;
|
|
14834
|
+
const tmpPath = `${targetPath}.tmp-${process.pid}`;
|
|
14835
|
+
const preservedH1 = readExistingH1(targetPath);
|
|
14836
|
+
if (fs52.existsSync(targetPath) && !opts.skipBackup && !fs52.existsSync(bakPath)) {
|
|
14837
|
+
const raw = fs52.readFileSync(targetPath, "utf-8");
|
|
14838
|
+
fs52.writeFileSync(bakPath, raw, "utf-8");
|
|
14839
|
+
}
|
|
14840
|
+
const serialized = serializeStrategyDoc(
|
|
14841
|
+
doc,
|
|
14842
|
+
preservedH1 !== void 0 ? { h1: preservedH1 } : {}
|
|
14843
|
+
);
|
|
14844
|
+
fs52.writeFileSync(tmpPath, serialized, "utf-8");
|
|
14845
|
+
try {
|
|
14846
|
+
fs52.renameSync(tmpPath, targetPath);
|
|
14847
|
+
} catch (e) {
|
|
14848
|
+
try {
|
|
14849
|
+
fs52.unlinkSync(tmpPath);
|
|
14850
|
+
} catch {
|
|
14851
|
+
}
|
|
14852
|
+
throw e;
|
|
14004
14853
|
}
|
|
14005
|
-
const stats = { count, windowDays };
|
|
14006
|
-
if (lastUsed) stats.lastUsed = lastUsed;
|
|
14007
|
-
return stats;
|
|
14008
14854
|
}
|
|
14009
|
-
var WHATWG_BAD_PORTS, BAD_PORT_SET, VALID_TYPES, CONVENTIONAL_PATTERN, LOOKBACK_PATTERN, PulseDbSourceSchema, PulseSourcesSchema, PulseConfigSchema, KNOWN_SECTION_NAMES, ISO_DATE, PLACEHOLDER_INLINE, REQUIRED, ALLOWED, StrategyFrontmatterSchema, StrategySectionSchema, StrategyDocSchema, BUG_TRACK_CATEGORIES, KNOWLEDGE_TRACK_CATEGORIES, ALL_SOLUTION_CATEGORIES, ISO_DATE2, BugTrackSchema, KnowledgeTrackSchema, BaseFrontmatter, SolutionDocFrontmatterSchema, TRACK_CATEGORIES, DEFAULT_AGNIX_TIMEOUT_MS, HARNESS_AGNIX_DISABLE, HARNESS_AGNIX_BIN, CLAUDE_MD_WARN_BYTES, CLAUDE_MD_ERROR_BYTES, CONTEXT_FILES, MIN_DESCRIPTION_LENGTH, SETTINGS_PATHS, KNOWN_HOOK_EVENTS, MCP_SETTINGS_FILES, REQUIRED_SECTIONS, SETTINGS_FILES, KNOWN_AGNIX_TOOLS, KEBAB_CASE, TICKET_ID, DEFAULT_SECTIONS, DEFAULT_RATIOS, NODE_TYPE_TO_CATEGORY, SECTION_LEVEL_MAP, DEFAULT_LEVEL, DEFAULT_BUDGET, DEFAULT_LOADER_CONFIG, LEVEL_FRACTIONS, PHASE_PRIORITIES, ManifestSchema, BundleConstraintsSchema, BundleSchema, ContributionsSchema, LockfilePackageSchema, LockfileSchema, SharableLayerSchema, SharableForbiddenImportSchema, SharableBoundaryConfigSchema, SharableSecurityRulesSchema, VALID_SCOPES, LINE_PATTERN, START_PATTERN, END_PATTERN, readFile72, writeFile32, unlink22, mkdir22, copyFile2, DEFAULT_FIX_CONFIG, EXPORT_TYPE_KEYWORD, ALWAYS_UNSAFE_TYPES, idCounter, DEAD_CODE_FIX_ACTIONS, MustExportRuleSchema, MustExportDefaultRuleSchema, NoExportRuleSchema, MustImportRuleSchema, NoImportRuleSchema, NamingRuleSchema, MaxExportsRuleSchema, MaxLinesRuleSchema, RequireJSDocRuleSchema, RuleSchema, ConfigPatternSchema, PatternConfigSchema, DriftConfigSchema, DeadCodeConfigSchema, EntropyConfigSchema,
|
|
14855
|
+
var WHATWG_BAD_PORTS, BAD_PORT_SET, VALID_TYPES, CONVENTIONAL_PATTERN, LOOKBACK_PATTERN, PulseDbSourceSchema, PulseSourcesSchema, PulseConfigSchema, KNOWN_SECTION_NAMES, ISO_DATE, PLACEHOLDER_INLINE, REQUIRED, ALLOWED, StrategyFrontmatterSchema, StrategySectionSchema, StrategyDocSchema, BUG_TRACK_CATEGORIES, KNOWLEDGE_TRACK_CATEGORIES, ALL_SOLUTION_CATEGORIES, ISO_DATE2, BugTrackSchema, KnowledgeTrackSchema, BaseFrontmatter, SolutionDocFrontmatterSchema, TRACK_CATEGORIES, DEFAULT_AGNIX_TIMEOUT_MS, HARNESS_AGNIX_DISABLE, HARNESS_AGNIX_BIN, CLAUDE_MD_WARN_BYTES, CLAUDE_MD_ERROR_BYTES, CONTEXT_FILES, MIN_DESCRIPTION_LENGTH, SETTINGS_PATHS, KNOWN_HOOK_EVENTS, MCP_SETTINGS_FILES, REQUIRED_SECTIONS, SETTINGS_FILES, KNOWN_AGNIX_TOOLS, KEBAB_CASE, TICKET_ID, DEFAULT_SECTIONS, DEFAULT_RATIOS, NODE_TYPE_TO_CATEGORY, SECTION_LEVEL_MAP, DEFAULT_LEVEL, DEFAULT_BUDGET, DEFAULT_LOADER_CONFIG, LEVEL_FRACTIONS, PHASE_PRIORITIES, ManifestSchema, BundleConstraintsSchema, BundleSchema, ContributionsSchema, LockfilePackageSchema, LockfileSchema, SharableLayerSchema, SharableForbiddenImportSchema, SharableBoundaryConfigSchema, SharableSecurityRulesSchema, VALID_SCOPES, LINE_PATTERN, START_PATTERN, END_PATTERN, readFile72, writeFile32, unlink22, mkdir22, copyFile2, DEFAULT_FIX_CONFIG, EXPORT_TYPE_KEYWORD, ALWAYS_UNSAFE_TYPES, idCounter, DEAD_CODE_FIX_ACTIONS, MustExportRuleSchema, MustExportDefaultRuleSchema, NoExportRuleSchema, MustImportRuleSchema, NoImportRuleSchema, NamingRuleSchema, MaxExportsRuleSchema, MaxLinesRuleSchema, RequireJSDocRuleSchema, RuleSchema, ConfigPatternSchema, PatternConfigSchema, DriftConfigSchema, DeadCodeConfigSchema, EntropyConfigSchema, BaselineManager, BenchmarkRunner, RegressionDetector, SOURCE_EXTENSIONS, FUNCTION_DECL_RE, CONST_DECL_RE, CriticalPathResolver, NoOpTelemetryAdapter, NoOpExecutor, ConsoleSink, feedbackConfig, ChecklistBuilder, AgentActionEmitter, globalEmitter, FileSink, NoOpSink, ConfidenceTierSchema, RegressionResultSchema, DirectionSchema, CategoryForecastSchema, SpecImpactSignalsSchema, SpecImpactEstimateSchema, ContributingFeatureSchema, AdjustedForecastSchema, PredictionWarningSchema, StabilityForecastSchema, PredictionResultSchema, PredictionOptionsSchema, VALID_STATUSES, EM_DASH, VALID_PRIORITIES, ALL_CATEGORIES, DIRECTION_THRESHOLD, PredictionEngine, DEFAULT_COEFFICIENTS, SpecImpactEstimator, EMPTY_HISTORY, ViolationHistoryManager, NORMALIZERS, MAX_SAMPLES, DEFAULT_WINDOW_WEEKS, DEFAULT_MIN_OCCURRENCES, FailureEntrySchema, HandoffSchema, GateCheckSchema, GateResultSchema, GateConfigSchema, HarnessStateSchema, DEFAULT_STATE, StreamInfoSchema, StreamIndexSchema, DEFAULT_STREAM_INDEX, HARNESS_DIR, STATE_FILE, LEARNINGS_FILE, FAILURES_FILE, HANDOFF_FILE, GATE_CONFIG_FILE, INDEX_FILE, SESSIONS_DIR, SESSION_INDEX_FILE, SUMMARY_FILE, SESSION_STATE_FILE, ARCHIVE_DIR, CONTENT_HASHES_FILE, EVENTS_FILE, STREAMS_DIR, STREAM_NAME_REGEX, branchCache, BRANCH_CACHE_TTL_MS, STATE_FILES, MAX_CACHE_ENTRIES, learningsCacheMap, WEIGHTS, LOCK_RETRIES, LOCK_BACKOFFS, PROMOTABLE_OUTCOMES, failuresCacheMap, FAILURE_LINE_REGEX, SAFE_GATE_COMMAND, SkillEventSchema, knownHashesCache, EVENT_FORMATTERS, RuleRegistry, DEFAULT_SECURITY_CONFIG, RuleOverrideSchema, SecurityConfigSchema, secretRules, injectionRules, xssRules, cryptoRules, pathTraversalRules, networkRules, deserializationRules, agentConfigRules, mcpRules, insecureDefaultsRules, sharpEdgesRules, nodeRules, expressRules, reactRules, goRules, SecurityScanner, OSV_ENDPOINT, DEFAULT_CACHE_DIR, DEFAULT_TTL_HOURS, hiddenUnicodePatterns, reRolingPatterns, permissionEscalationPatterns, encodedPayloadPatterns, indirectInjectionPatterns, contextManipulationPatterns, socialEngineeringPatterns, suspiciousPatterns, ALL_PATTERNS, DESTRUCTIVE_BASH, TAINT_DURATION_MS, DEFAULT_SESSION_ID, SecurityCategorySnapshotSchema, SupplyChainSnapshotSchema, SecurityTimelineSnapshotSchema, FindingLifecycleSchema, SecurityTimelineFileSchema, DirectionSchema2, SecurityTrendLineSchema, TrendAttributionSchema, SecurityTrendResultSchema, TimeToFixStatsSchema, TimeToFixResultSchema, EMPTY_SUPPLY_CHAIN, SecurityTimelineManager, ALL_CHECKS, STATUS_ICONS, CINotifier, ExclusionSet, PREFIX_PATTERNS, TEST_FILE_PATTERN, MD_FILE_PATTERN, ALL_DOMAINS, SECURITY_PATTERNS, JS_EXT_FALLBACKS, SEVERITY_RANK, SEVERITY_ORDER, SEVERITY_LABELS, VALIDATED_BY_RANK, VALIDATION_SCORES, DOMAIN_BASELINES, FACTOR_WEIGHTS, EVIDENCE_SATURATION, CORROBORATED_AGREEMENT, STANDALONE_AGREEMENT, AGREEMENT_LINE_GAP, COMPLIANCE_DESCRIPTOR, EXPORT_RE, BUG_DETECTION_DESCRIPTOR, SECURITY_DESCRIPTOR, EVAL_PATTERN, SECRET_PATTERNS, SQL_CONCAT_PATTERN, SHELL_EXEC_PATTERN, ARCHITECTURE_DESCRIPTOR, LARGE_FILE_THRESHOLD, VIOLATION_FILE_RE, LEARNINGS_DESCRIPTOR, RELEVANCE_THRESHOLD, ADVERSARIAL_DESCRIPTOR, UNVERIFIED_INPUT_PATTERN, PROMISE_NO_REJECT_PATTERN, UNGUARDED_OPTIONAL_CALL, COMPOSE_NO_AWAIT, FETCH_NO_TIMEOUT, TYPESCRIPT_STRICT_DESCRIPTOR, EXPLICIT_ANY_PATTERN, TS_IGNORE_PATTERN, UNSAFE_AS_UNKNOWN, NON_NULL_ASSERTION, VAGUE_HELPER_NAME, FRONTEND_RACES_DESCRIPTOR, AGENT_DESCRIPTORS, CONDITIONAL_SUBAGENT_DESCRIPTORS, AGENT_RUNNERS, SUBAGENT_ORDER, RISK_KEYWORDS, EXCLUDED_PATH_PATTERNS, ASYNC_UI_SIGNALS, WORD_BOUNDARY, DOWNGRADE_MAP, DEFAULT_PROVIDER_TIERS, SMALL_SUGGESTION_LINE_LIMIT, FILE_LINE_RANGE_PATTERN, FILE_LINE_PATTERN, FILE_ONLY_PATTERN, SECURITY_SENSITIVE_PATH, MIGRATION_PATH, TEST_PATH, CONFIG_PATH, STAGE_DOMAINS, CI_REVIEW_VERDICT_SCHEMA_VERSION, CI_RUNNERS, CI_ASSESSMENTS, CI_REVIEW_DOMAINS, ReviewFindingSchema, CiReviewVerdictSchema, VERDICT_MAP, RUNNER_PRESETS, VERDICT_MAP2, DEFAULT_EXEC_TIMEOUT_MS, DEFAULT_EXEC_MAX_STDOUT_BYTES, STDERR_CAP_BYTES, STDERR_TAIL_BYTES, defaultExecFile, EMPTY_TIER, EM_DASH2, STATUS_RANK, EM_DASH3, DEFAULT_MILESTONE, MAX_CLOSEST_MATCHES, RETRY_DEFAULTS, GitHubIssuesSyncAdapter, syncMutex, PRIORITY_RANK, PRIORITY_RANK2, POSITION_WEIGHT, DEPENDENTS_WEIGHT, AFFINITY_WEIGHT, ConflictError, DEFAULTS, GitHubHttp, ETagStore, START_MARKER, END_MARKER, SERIALIZER_FIELDS, GitHubIssuesTrackerAdapter, migrate_exports, HASH_RE, DEFAULT_CATCH_ALL, DEFAULT_INTAKE, DEFAULT_MAX_ACTIVE, InteractionTypeSchema, QuestionSchema, ConfirmationSchema, TransitionSchema, EmitInteractionInputSchema, ProjectScanner, SHELL_TEMPLATE, STYLES, SCRIPTS, MockLLMService, llmService, ContentPipeline, BlueprintGenerator, TOKENS_PER_MILLION, fallback_default, LITELLM_PRICING_URL, CACHE_TTL_MS, STALENESS_WARNING_DAYS, MICRODOLLARS_PER_DOLLAR, TOKENS_PER_MILLION2, StructuralStrategy, DEFAULT_TOKEN_BUDGET, CHARS_PER_TOKEN, TRUNCATION_MARKER, TruncationStrategy, CompactionPipeline, STABILITY_LOOKUP, TIER_ORDER, AnthropicCacheAdapter, TIER_ORDER2, OpenAICacheAdapter, TIER_ORDER3, CACHED_CONTENT_MARKER, GeminiCacheAdapter, VERSION, POSTHOG_BATCH_URL, MAX_ATTEMPTS, TIMEOUT_MS, DEFAULT_CAPACITY, CacheMetricsRecorder, RETRY_BACKOFFS_MS, DEFAULT_FLUSH_INTERVAL_MS, DEFAULT_BATCH_SIZE, defaultFetch, defaultWarn, OTLPExporter, SERVICE_NAME_ATTR, SpanKind, ModeSchema, SeveritySchema, TierSchema, StrengthFindingSchema, HarnessConfigSubsetSchema, HookFileSchema, ProjectContextSchema, AuditSummarySchema, AuditResultSchema, SEVERITY_WEIGHTS, clamp, HOOK_SCRIPT_TOKEN, NEVER_BLOCKS, ALWAYS_EXIT_ZERO, EXIT_ZERO_LINE, NONZERO_EXIT, GATE, ERREXIT, strength001NonblockingHooks, BASELINE_REWRITE, strength002Autobaseline, SKIP_RE, strength003SkipList, strength004EmptyThresholds, INIT_SKILL_PATH, DEFAULT_BASIC, strength005LowestTier, AUTO_PATTERN, COMMENT_LINE, NAME_VALUE_LINE, PAT_GATING, REVIEW_SIGNAL, strength006AutoapproveBaseline, CHECK_SIGNAL_MAP, strength007SnapshotSignalMismatch, ALL_RULES, HarnessStrengthAuditor, MAX_TOP_AFFECTED, CompoundLockHeldError, KNOWN_CATEGORIES, ProposalNotFoundError, ProposalConflictError, ALLOWED_FIELD_KEYS, PII_TOKENS, PII_FIELD_DENYLIST, PII_LINE_RE, ALLOWED_SET, PulseAdapterAlreadyRegisteredError, REGISTRY, MOCK_ADAPTER_NAME, ALLOWED_FIELD_SET, adapter, LOOKBACK_RE, MAX_LINES, INLINE_TEMPLATE, TRUNCATION_MARKER2, execFileAsync, FIX_RE, EMPTY_REPO_RE, execFileAsync2, TRACK_CATEGORIES2, STOPWORDS, KEYWORD_TO_CATEGORY, SECTION_ORDER, FILENAME;
|
|
14010
14856
|
var init_dist3 = __esm({
|
|
14011
14857
|
"../core/dist/index.mjs"() {
|
|
14012
14858
|
init_chunk_FYTJQ2ZY();
|
|
@@ -14018,7 +14864,6 @@ var init_dist3 = __esm({
|
|
|
14018
14864
|
init_dist();
|
|
14019
14865
|
init_dist();
|
|
14020
14866
|
init_dist();
|
|
14021
|
-
init_dist();
|
|
14022
14867
|
init_dist2();
|
|
14023
14868
|
init_dist();
|
|
14024
14869
|
init_dist();
|
|
@@ -14032,11 +14877,12 @@ var init_dist3 = __esm({
|
|
|
14032
14877
|
init_dist();
|
|
14033
14878
|
init_dist();
|
|
14034
14879
|
init_dist();
|
|
14035
|
-
init_dist();
|
|
14036
14880
|
init_dist2();
|
|
14037
14881
|
init_dist();
|
|
14038
14882
|
init_dist();
|
|
14039
14883
|
init_dist();
|
|
14884
|
+
init_dist();
|
|
14885
|
+
init_dist();
|
|
14040
14886
|
WHATWG_BAD_PORTS = Object.freeze([
|
|
14041
14887
|
1,
|
|
14042
14888
|
7,
|
|
@@ -14623,22 +15469,21 @@ var init_dist3 = __esm({
|
|
|
14623
15469
|
exclude: z5.array(z5.string()).optional(),
|
|
14624
15470
|
docPaths: z5.array(z5.string()).optional()
|
|
14625
15471
|
});
|
|
14626
|
-
MAX_TOP_AFFECTED = 5;
|
|
14627
15472
|
BaselineManager = class {
|
|
14628
15473
|
baselinesPath;
|
|
14629
15474
|
constructor(projectRoot) {
|
|
14630
|
-
this.baselinesPath =
|
|
15475
|
+
this.baselinesPath = join17(projectRoot, ".harness", "perf", "baselines.json");
|
|
14631
15476
|
}
|
|
14632
15477
|
/**
|
|
14633
15478
|
* Load the baselines file from disk.
|
|
14634
15479
|
* Returns null if the file does not exist or contains invalid JSON.
|
|
14635
15480
|
*/
|
|
14636
15481
|
load() {
|
|
14637
|
-
if (!
|
|
15482
|
+
if (!existsSync12(this.baselinesPath)) {
|
|
14638
15483
|
return null;
|
|
14639
15484
|
}
|
|
14640
15485
|
try {
|
|
14641
|
-
const raw =
|
|
15486
|
+
const raw = readFileSync22(this.baselinesPath, "utf-8");
|
|
14642
15487
|
return JSON.parse(raw);
|
|
14643
15488
|
} catch {
|
|
14644
15489
|
return null;
|
|
@@ -14668,7 +15513,7 @@ var init_dist3 = __esm({
|
|
|
14668
15513
|
benchmarks
|
|
14669
15514
|
};
|
|
14670
15515
|
const dir = dirname6(this.baselinesPath);
|
|
14671
|
-
if (!
|
|
15516
|
+
if (!existsSync12(dir)) {
|
|
14672
15517
|
mkdirSync2(dir, { recursive: true });
|
|
14673
15518
|
}
|
|
14674
15519
|
writeFileSync2(this.baselinesPath, JSON.stringify(file, null, 2));
|
|
@@ -14901,16 +15746,16 @@ var init_dist3 = __esm({
|
|
|
14901
15746
|
walkDir(dir, entries) {
|
|
14902
15747
|
let items;
|
|
14903
15748
|
try {
|
|
14904
|
-
items =
|
|
15749
|
+
items = fs8.readdirSync(dir, { withFileTypes: true });
|
|
14905
15750
|
} catch {
|
|
14906
15751
|
return;
|
|
14907
15752
|
}
|
|
14908
15753
|
for (const item of items) {
|
|
14909
15754
|
if (item.isDirectory()) {
|
|
14910
15755
|
if (DEFAULT_SKIP_DIRS.has(item.name)) continue;
|
|
14911
|
-
this.walkDir(
|
|
14912
|
-
} else if (item.isFile() && SOURCE_EXTENSIONS.has(
|
|
14913
|
-
this.scanFile(
|
|
15756
|
+
this.walkDir(path5.join(dir, item.name), entries);
|
|
15757
|
+
} else if (item.isFile() && SOURCE_EXTENSIONS.has(path5.extname(item.name))) {
|
|
15758
|
+
this.scanFile(path5.join(dir, item.name), entries);
|
|
14914
15759
|
}
|
|
14915
15760
|
}
|
|
14916
15761
|
}
|
|
@@ -14925,12 +15770,12 @@ var init_dist3 = __esm({
|
|
|
14925
15770
|
scanFile(filePath, entries) {
|
|
14926
15771
|
let content;
|
|
14927
15772
|
try {
|
|
14928
|
-
content =
|
|
15773
|
+
content = fs8.readFileSync(filePath, "utf-8");
|
|
14929
15774
|
} catch {
|
|
14930
15775
|
return;
|
|
14931
15776
|
}
|
|
14932
15777
|
const lines = content.split("\n");
|
|
14933
|
-
const relativePath =
|
|
15778
|
+
const relativePath = path5.relative(this.projectRoot, filePath).replace(/\\/g, "/");
|
|
14934
15779
|
for (let i = 0; i < lines.length; i++) {
|
|
14935
15780
|
if (!lines[i].includes("@perf-critical")) continue;
|
|
14936
15781
|
const fnName = this.resolveFunctionName(lines, i + 1);
|
|
@@ -15270,7 +16115,7 @@ var init_dist3 = __esm({
|
|
|
15270
16115
|
ensureDirectory() {
|
|
15271
16116
|
if (!this.initialized) {
|
|
15272
16117
|
const dir = dirname7(this.filePath);
|
|
15273
|
-
if (!
|
|
16118
|
+
if (!existsSync13(dir)) {
|
|
15274
16119
|
mkdirSync22(dir, { recursive: true });
|
|
15275
16120
|
}
|
|
15276
16121
|
this.initialized = true;
|
|
@@ -15301,7 +16146,7 @@ var init_dist3 = __esm({
|
|
|
15301
16146
|
this.ensureDirectory();
|
|
15302
16147
|
const content = this.buffer.join("");
|
|
15303
16148
|
this.buffer = [];
|
|
15304
|
-
if (this.options.mode === "overwrite" && !
|
|
16149
|
+
if (this.options.mode === "overwrite" && !existsSync13(this.filePath)) {
|
|
15305
16150
|
writeFileSync22(this.filePath, content);
|
|
15306
16151
|
} else {
|
|
15307
16152
|
appendFileSync(this.filePath, content);
|
|
@@ -15735,8 +16580,8 @@ var init_dist3 = __esm({
|
|
|
15735
16580
|
return null;
|
|
15736
16581
|
}
|
|
15737
16582
|
try {
|
|
15738
|
-
const roadmapPath =
|
|
15739
|
-
const raw =
|
|
16583
|
+
const roadmapPath = path6.join(this.rootDir, "roadmap.md");
|
|
16584
|
+
const raw = fs9.readFileSync(roadmapPath, "utf-8");
|
|
15740
16585
|
const parseResult = parseRoadmap(raw);
|
|
15741
16586
|
if (!parseResult.ok) return null;
|
|
15742
16587
|
const features = parseResult.value.milestones.flatMap(
|
|
@@ -15771,8 +16616,8 @@ var init_dist3 = __esm({
|
|
|
15771
16616
|
* @param specPath - Relative path from rootDir to the spec file.
|
|
15772
16617
|
*/
|
|
15773
16618
|
estimate(specPath) {
|
|
15774
|
-
const absolutePath =
|
|
15775
|
-
const content =
|
|
16619
|
+
const absolutePath = path7.join(this.rootDir, specPath);
|
|
16620
|
+
const content = fs10.readFileSync(absolutePath, "utf-8");
|
|
15776
16621
|
const newFileCount = this.extractNewFileCount(content);
|
|
15777
16622
|
const affectedLayers = this.extractAffectedLayers(content);
|
|
15778
16623
|
const newDependencies = this.extractNewDependencies(content);
|
|
@@ -15784,7 +16629,7 @@ var init_dist3 = __esm({
|
|
|
15784
16629
|
phaseCount
|
|
15785
16630
|
);
|
|
15786
16631
|
const h1Match = content.match(/^#\s+(.+)$/m);
|
|
15787
|
-
const featureName = h1Match ? h1Match[1].trim() :
|
|
16632
|
+
const featureName = h1Match ? h1Match[1].trim() : path7.basename(specPath, ".md");
|
|
15788
16633
|
return {
|
|
15789
16634
|
specPath,
|
|
15790
16635
|
featureName,
|
|
@@ -15805,8 +16650,8 @@ var init_dist3 = __esm({
|
|
|
15805
16650
|
const results = [];
|
|
15806
16651
|
for (const feature of features) {
|
|
15807
16652
|
if (!feature.spec) continue;
|
|
15808
|
-
const absolutePath =
|
|
15809
|
-
if (!
|
|
16653
|
+
const absolutePath = path7.join(this.rootDir, feature.spec);
|
|
16654
|
+
if (!fs10.existsSync(absolutePath)) continue;
|
|
15810
16655
|
const estimate = this.estimate(feature.spec);
|
|
15811
16656
|
results.push({ ...estimate, featureName: feature.name });
|
|
15812
16657
|
}
|
|
@@ -15834,8 +16679,8 @@ var init_dist3 = __esm({
|
|
|
15834
16679
|
}
|
|
15835
16680
|
let count = 0;
|
|
15836
16681
|
for (const fp of filePaths) {
|
|
15837
|
-
const absolute =
|
|
15838
|
-
if (!
|
|
16682
|
+
const absolute = path7.join(this.rootDir, fp);
|
|
16683
|
+
if (!fs10.existsSync(absolute)) {
|
|
15839
16684
|
count++;
|
|
15840
16685
|
}
|
|
15841
16686
|
}
|
|
@@ -15905,8 +16750,8 @@ var init_dist3 = __esm({
|
|
|
15905
16750
|
// --- Private: Config Loading ---
|
|
15906
16751
|
loadLayerNames() {
|
|
15907
16752
|
try {
|
|
15908
|
-
const configPath =
|
|
15909
|
-
const raw =
|
|
16753
|
+
const configPath = path7.join(this.rootDir, "harness.config.json");
|
|
16754
|
+
const raw = fs10.readFileSync(configPath, "utf-8");
|
|
15910
16755
|
const config = JSON.parse(raw);
|
|
15911
16756
|
return (config.layers ?? []).map((l) => l.name);
|
|
15912
16757
|
} catch {
|
|
@@ -15925,8 +16770,8 @@ var init_dist3 = __esm({
|
|
|
15925
16770
|
historyPath;
|
|
15926
16771
|
/** Load history from disk, returning empty history if file doesn't exist. */
|
|
15927
16772
|
load() {
|
|
15928
|
-
if (!
|
|
15929
|
-
const raw =
|
|
16773
|
+
if (!fs11.existsSync(this.historyPath)) return { ...EMPTY_HISTORY, snapshots: [] };
|
|
16774
|
+
const raw = fs11.readFileSync(this.historyPath, "utf-8");
|
|
15930
16775
|
return JSON.parse(raw);
|
|
15931
16776
|
}
|
|
15932
16777
|
/** Append a timestamped snapshot of current violations. */
|
|
@@ -15940,18 +16785,18 @@ var init_dist3 = __esm({
|
|
|
15940
16785
|
}
|
|
15941
16786
|
/** Prune snapshots older than retentionDays. */
|
|
15942
16787
|
prune(retentionDays) {
|
|
15943
|
-
if (!
|
|
16788
|
+
if (!fs11.existsSync(this.historyPath)) return;
|
|
15944
16789
|
const history = this.load();
|
|
15945
16790
|
const cutoff = Date.now() - retentionDays * 24 * 60 * 60 * 1e3;
|
|
15946
16791
|
history.snapshots = history.snapshots.filter((s) => new Date(s.timestamp).getTime() >= cutoff);
|
|
15947
16792
|
this.write(history);
|
|
15948
16793
|
}
|
|
15949
16794
|
write(history) {
|
|
15950
|
-
const dir =
|
|
15951
|
-
if (!
|
|
15952
|
-
|
|
16795
|
+
const dir = path8.dirname(this.historyPath);
|
|
16796
|
+
if (!fs11.existsSync(dir)) {
|
|
16797
|
+
fs11.mkdirSync(dir, { recursive: true });
|
|
15953
16798
|
}
|
|
15954
|
-
|
|
16799
|
+
fs11.writeFileSync(this.historyPath, JSON.stringify(history, null, 2), "utf-8");
|
|
15955
16800
|
}
|
|
15956
16801
|
};
|
|
15957
16802
|
NORMALIZERS = {
|
|
@@ -16953,7 +17798,7 @@ var init_dist3 = __esm({
|
|
|
16953
17798
|
}
|
|
16954
17799
|
async scanFile(filePath) {
|
|
16955
17800
|
if (!this.config.enabled) return [];
|
|
16956
|
-
const content = await
|
|
17801
|
+
const content = await fs29.readFile(filePath, "utf-8");
|
|
16957
17802
|
return this.scanContentForFile(content, filePath, 1);
|
|
16958
17803
|
}
|
|
16959
17804
|
scanContentForFile(content, filePath, startLine = 1) {
|
|
@@ -17075,7 +17920,7 @@ var init_dist3 = __esm({
|
|
|
17075
17920
|
}
|
|
17076
17921
|
};
|
|
17077
17922
|
OSV_ENDPOINT = "https://api.osv.dev/v1/query";
|
|
17078
|
-
DEFAULT_CACHE_DIR =
|
|
17923
|
+
DEFAULT_CACHE_DIR = path26.join(".harness", "cache", "osv");
|
|
17079
17924
|
DEFAULT_TTL_HOURS = 24;
|
|
17080
17925
|
hiddenUnicodePatterns = [
|
|
17081
17926
|
{
|
|
@@ -17382,7 +18227,7 @@ var init_dist3 = __esm({
|
|
|
17382
18227
|
timelinePath;
|
|
17383
18228
|
constructor(rootDir) {
|
|
17384
18229
|
this.rootDir = rootDir;
|
|
17385
|
-
this.timelinePath =
|
|
18230
|
+
this.timelinePath = join39(rootDir, ".harness", "security", "timeline.json");
|
|
17386
18231
|
}
|
|
17387
18232
|
/**
|
|
17388
18233
|
* Load timeline from disk.
|
|
@@ -17393,11 +18238,11 @@ var init_dist3 = __esm({
|
|
|
17393
18238
|
* fixup is one-shot.
|
|
17394
18239
|
*/
|
|
17395
18240
|
load() {
|
|
17396
|
-
if (!
|
|
18241
|
+
if (!existsSync332(this.timelinePath)) {
|
|
17397
18242
|
return { version: 1, snapshots: [], findingLifecycles: [] };
|
|
17398
18243
|
}
|
|
17399
18244
|
try {
|
|
17400
|
-
const raw =
|
|
18245
|
+
const raw = readFileSync222(this.timelinePath, "utf-8");
|
|
17401
18246
|
const data = JSON.parse(raw);
|
|
17402
18247
|
const parsed = SecurityTimelineFileSchema.safeParse(data);
|
|
17403
18248
|
if (!parsed.success) {
|
|
@@ -17427,7 +18272,7 @@ var init_dist3 = __esm({
|
|
|
17427
18272
|
*/
|
|
17428
18273
|
save(timeline) {
|
|
17429
18274
|
const dir = dirname11(this.timelinePath);
|
|
17430
|
-
if (!
|
|
18275
|
+
if (!existsSync332(dir)) {
|
|
17431
18276
|
mkdirSync14(dir, { recursive: true });
|
|
17432
18277
|
}
|
|
17433
18278
|
const tmp = this.timelinePath + "." + randomBytes2(4).toString("hex") + ".tmp";
|
|
@@ -18151,6 +18996,244 @@ var init_dist3 = __esm({
|
|
|
18151
18996
|
"spec-compliance": ["compliance", "architecture"],
|
|
18152
18997
|
"code-quality": ["bug", "security"]
|
|
18153
18998
|
};
|
|
18999
|
+
CI_REVIEW_VERDICT_SCHEMA_VERSION = 1;
|
|
19000
|
+
CI_RUNNERS = [
|
|
19001
|
+
"claude",
|
|
19002
|
+
"gemini",
|
|
19003
|
+
"antigravity",
|
|
19004
|
+
"codex",
|
|
19005
|
+
"cursor",
|
|
19006
|
+
"local",
|
|
19007
|
+
"floor-only"
|
|
19008
|
+
];
|
|
19009
|
+
CI_ASSESSMENTS = ["approve", "comment", "request-changes"];
|
|
19010
|
+
CI_REVIEW_DOMAINS = [
|
|
19011
|
+
"compliance",
|
|
19012
|
+
"bug",
|
|
19013
|
+
"security",
|
|
19014
|
+
"architecture",
|
|
19015
|
+
"learnings"
|
|
19016
|
+
];
|
|
19017
|
+
ReviewFindingSchema = z12.object({
|
|
19018
|
+
id: z12.string().min(1),
|
|
19019
|
+
file: z12.string().min(1),
|
|
19020
|
+
lineRange: z12.tuple([z12.number(), z12.number()]),
|
|
19021
|
+
domain: z12.enum(CI_REVIEW_DOMAINS),
|
|
19022
|
+
severity: z12.enum(["critical", "important", "suggestion"]),
|
|
19023
|
+
title: z12.string().min(1),
|
|
19024
|
+
rationale: z12.string().min(1),
|
|
19025
|
+
suggestion: z12.string().optional(),
|
|
19026
|
+
evidence: z12.array(z12.string()),
|
|
19027
|
+
validatedBy: z12.enum(["mechanical", "graph", "heuristic"]),
|
|
19028
|
+
cweId: z12.string().optional(),
|
|
19029
|
+
owaspCategory: z12.string().optional(),
|
|
19030
|
+
confidence: z12.union([
|
|
19031
|
+
z12.enum(["high", "medium", "low"]),
|
|
19032
|
+
z12.literal(25),
|
|
19033
|
+
z12.literal(50),
|
|
19034
|
+
z12.literal(75),
|
|
19035
|
+
z12.literal(100)
|
|
19036
|
+
]).optional(),
|
|
19037
|
+
remediation: z12.string().optional(),
|
|
19038
|
+
references: z12.array(z12.string()).optional(),
|
|
19039
|
+
trustScore: z12.number().optional(),
|
|
19040
|
+
rubricItemId: z12.string().optional(),
|
|
19041
|
+
subagent: z12.string().optional()
|
|
19042
|
+
}).passthrough();
|
|
19043
|
+
CiReviewVerdictSchema = z12.object({
|
|
19044
|
+
schemaVersion: z12.literal(CI_REVIEW_VERDICT_SCHEMA_VERSION),
|
|
19045
|
+
runner: z12.enum(CI_RUNNERS),
|
|
19046
|
+
ranLlmTier: z12.boolean(),
|
|
19047
|
+
assessment: z12.enum(CI_ASSESSMENTS),
|
|
19048
|
+
findings: z12.array(ReviewFindingSchema),
|
|
19049
|
+
blockingFindings: z12.array(ReviewFindingSchema),
|
|
19050
|
+
exitCode: z12.number().int(),
|
|
19051
|
+
skipped: z12.boolean(),
|
|
19052
|
+
skipReason: z12.string().optional()
|
|
19053
|
+
}).superRefine((v, ctx) => {
|
|
19054
|
+
for (const [i, f] of v.blockingFindings.entries()) {
|
|
19055
|
+
if (f.severity !== "critical") {
|
|
19056
|
+
ctx.addIssue({
|
|
19057
|
+
code: z12.ZodIssueCode.custom,
|
|
19058
|
+
path: ["blockingFindings", i, "severity"],
|
|
19059
|
+
message: `blockingFindings[${i}] has severity '${f.severity}'; only 'critical' findings may block`
|
|
19060
|
+
});
|
|
19061
|
+
}
|
|
19062
|
+
}
|
|
19063
|
+
for (const [i, bf] of v.blockingFindings.entries()) {
|
|
19064
|
+
const present = v.findings.some((f) => findingsMatch(f, bf));
|
|
19065
|
+
if (!present) {
|
|
19066
|
+
ctx.addIssue({
|
|
19067
|
+
code: z12.ZodIssueCode.custom,
|
|
19068
|
+
path: ["blockingFindings", i],
|
|
19069
|
+
message: `blockingFindings[${i}] (id='${bf.id}') is not present in findings`
|
|
19070
|
+
});
|
|
19071
|
+
}
|
|
19072
|
+
}
|
|
19073
|
+
const criticalCount = v.findings.filter((f) => f.severity === "critical").length;
|
|
19074
|
+
if (v.blockingFindings.length !== criticalCount) {
|
|
19075
|
+
ctx.addIssue({
|
|
19076
|
+
code: z12.ZodIssueCode.custom,
|
|
19077
|
+
path: ["blockingFindings"],
|
|
19078
|
+
message: `blockingFindings (${v.blockingFindings.length}) must equal the critical findings in findings (${criticalCount})`
|
|
19079
|
+
});
|
|
19080
|
+
}
|
|
19081
|
+
const blocking = v.blockingFindings.length > 0;
|
|
19082
|
+
if (blocking && v.assessment !== "request-changes") {
|
|
19083
|
+
ctx.addIssue({
|
|
19084
|
+
code: z12.ZodIssueCode.custom,
|
|
19085
|
+
path: ["assessment"],
|
|
19086
|
+
message: `assessment must be 'request-changes' when blockingFindings is non-empty (got '${v.assessment}')`
|
|
19087
|
+
});
|
|
19088
|
+
}
|
|
19089
|
+
const mustExitNonZero = blocking || v.assessment === "request-changes";
|
|
19090
|
+
if (mustExitNonZero && v.exitCode === 0) {
|
|
19091
|
+
ctx.addIssue({
|
|
19092
|
+
code: z12.ZodIssueCode.custom,
|
|
19093
|
+
path: ["exitCode"],
|
|
19094
|
+
message: `exitCode must be non-zero when blockingFindings is non-empty or assessment is 'request-changes'`
|
|
19095
|
+
});
|
|
19096
|
+
}
|
|
19097
|
+
if (!mustExitNonZero && v.exitCode !== 0) {
|
|
19098
|
+
ctx.addIssue({
|
|
19099
|
+
code: z12.ZodIssueCode.custom,
|
|
19100
|
+
path: ["exitCode"],
|
|
19101
|
+
message: `exitCode must be 0 when there are no blocking findings and assessment is not 'request-changes'`
|
|
19102
|
+
});
|
|
19103
|
+
}
|
|
19104
|
+
});
|
|
19105
|
+
VERDICT_MAP = {
|
|
19106
|
+
approve: "approve",
|
|
19107
|
+
comment: "comment",
|
|
19108
|
+
"request-changes": "request-changes"
|
|
19109
|
+
};
|
|
19110
|
+
RUNNER_PRESETS = {
|
|
19111
|
+
claude: {
|
|
19112
|
+
kind: "agent-cli",
|
|
19113
|
+
supported: true,
|
|
19114
|
+
secretEnvVar: "ANTHROPIC_API_KEY",
|
|
19115
|
+
// Verified (Task 10 smoke test): diff piped via STDIN, instruction as the
|
|
19116
|
+
// `-p` prompt, JSON transcript on stdout. There is NO `--input-file` flag.
|
|
19117
|
+
headlessInvocation: ({ instruction }) => ({
|
|
19118
|
+
command: "claude",
|
|
19119
|
+
args: ["-p", instruction, "--output-format", "json"]
|
|
19120
|
+
}),
|
|
19121
|
+
verdictParser: parseClaudeVerdict
|
|
19122
|
+
},
|
|
19123
|
+
gemini: {
|
|
19124
|
+
kind: "agent-cli",
|
|
19125
|
+
supported: false,
|
|
19126
|
+
// argv corrected from `gemini --help` (NOT `--json`; it is `-o json`, and there
|
|
19127
|
+
// is no `--file` flag — the diff goes on STDIN):
|
|
19128
|
+
// command: 'gemini'
|
|
19129
|
+
// args: ['-p', instruction, '-o', 'json'] // diff piped via STDIN
|
|
19130
|
+
// Re-enable by restoring `supported: true`, `secretEnvVar: 'GEMINI_API_KEY'`,
|
|
19131
|
+
// the headlessInvocation above, and `verdictParser: parseGeminiVerdict` once
|
|
19132
|
+
// the output envelope is captured in CI.
|
|
19133
|
+
unsupportedReason: "SUPERSEDED by the `antigravity` runner (use `--runner antigravity`): the Gemini agentic CLI was renamed to Antigravity (command `agy`), which authenticates locally and produces a capturable verdict where this `gemini` CLI did not. Retained as a valid runner id for back-compat. Original deferral note: gemini argv corrected from --help (`gemini -p <instruction> -o json`, diff on STDIN), but the JSON output envelope is UNVERIFIED: no GEMINI_API_KEY was available in the authoring environment, so the CLI fell through to interactive OAuth and produced no capturable result. Verification deferred to a real CI run with credentials."
|
|
19134
|
+
},
|
|
19135
|
+
antigravity: {
|
|
19136
|
+
kind: "agent-cli",
|
|
19137
|
+
supported: true,
|
|
19138
|
+
// agy is authenticated locally via OAuth here (no API key needed for local
|
|
19139
|
+
// runs). For CI, the gating secret is uncertain — this is a BEST-GUESS pending
|
|
19140
|
+
// CI verification: antigravity is Gemini-family (the renamed `gemini` CLI), so
|
|
19141
|
+
// GEMINI_API_KEY is the most likely credential. Local auth is OAuth-based and
|
|
19142
|
+
// needs no secret; confirm/correct this against a real CI run.
|
|
19143
|
+
secretEnvVar: "GEMINI_API_KEY",
|
|
19144
|
+
// Verified live against the real CLI: diff piped via STDIN, instruction as the
|
|
19145
|
+
// `--print` prompt. There is NO `--output-format`/`--json` flag (it errors
|
|
19146
|
+
// `flags provided but not defined: -output-format`); `--print` (alias
|
|
19147
|
+
// `--prompt`) runs a single non-interactive prompt and emits the model's
|
|
19148
|
+
// response as plain text. Consistent with the claude/codex STDIN diff model.
|
|
19149
|
+
headlessInvocation: ({ instruction }) => ({
|
|
19150
|
+
command: "agy",
|
|
19151
|
+
args: ["--print", instruction]
|
|
19152
|
+
}),
|
|
19153
|
+
verdictParser: parseAntigravityVerdict
|
|
19154
|
+
},
|
|
19155
|
+
codex: {
|
|
19156
|
+
kind: "agent-cli",
|
|
19157
|
+
supported: true,
|
|
19158
|
+
secretEnvVar: "OPENAI_API_KEY",
|
|
19159
|
+
// Verified (Task 10 smoke test): diff piped via STDIN, instruction as the
|
|
19160
|
+
// positional prompt, JSONL event stream on stdout. There is NO `--file` flag.
|
|
19161
|
+
headlessInvocation: ({ instruction }) => ({
|
|
19162
|
+
command: "codex",
|
|
19163
|
+
args: ["exec", "--json", instruction]
|
|
19164
|
+
}),
|
|
19165
|
+
verdictParser: parseCodexVerdict
|
|
19166
|
+
},
|
|
19167
|
+
cursor: {
|
|
19168
|
+
kind: "agent-cli",
|
|
19169
|
+
supported: false,
|
|
19170
|
+
unsupportedReason: "cursor headless CI invocation is unverified and the CLI is not present in this environment; deferred to a real CI run per the required-review-ci re-scope decision."
|
|
19171
|
+
},
|
|
19172
|
+
local: {
|
|
19173
|
+
kind: "endpoint",
|
|
19174
|
+
supported: true,
|
|
19175
|
+
endpointEnvVar: "HARNESS_LOCAL_ENDPOINT",
|
|
19176
|
+
modelEnvVar: "HARNESS_LOCAL_MODEL",
|
|
19177
|
+
// `invoke` is intentionally left unset here — it is the injected seam Phase 2
|
|
19178
|
+
// wires to the openai-compatible provider (see LocalEndpointInvoke). The
|
|
19179
|
+
// deterministic surface (env-var seams + verdictParser) is what Phase 1 ships
|
|
19180
|
+
// and unit-tests; live single-pass verification is DEFERRED (no endpoint here).
|
|
19181
|
+
verdictParser: parseLocalVerdict
|
|
19182
|
+
}
|
|
19183
|
+
};
|
|
19184
|
+
VERDICT_MAP2 = {
|
|
19185
|
+
approve: "approve",
|
|
19186
|
+
comment: "comment",
|
|
19187
|
+
"request-changes": "request-changes"
|
|
19188
|
+
};
|
|
19189
|
+
DEFAULT_EXEC_TIMEOUT_MS = 12e4;
|
|
19190
|
+
DEFAULT_EXEC_MAX_STDOUT_BYTES = 1024 * 1024 * 64;
|
|
19191
|
+
STDERR_CAP_BYTES = 1024 * 1024;
|
|
19192
|
+
STDERR_TAIL_BYTES = 2048;
|
|
19193
|
+
defaultExecFile = async (command, args, opts) => {
|
|
19194
|
+
const timeout = opts.timeoutMs ?? DEFAULT_EXEC_TIMEOUT_MS;
|
|
19195
|
+
const maxStdoutBytes = opts.maxStdoutBytes ?? DEFAULT_EXEC_MAX_STDOUT_BYTES;
|
|
19196
|
+
const child = nodeExecFile(command, args, {
|
|
19197
|
+
env: opts.env,
|
|
19198
|
+
timeout,
|
|
19199
|
+
killSignal: "SIGTERM"
|
|
19200
|
+
});
|
|
19201
|
+
child.stdin?.end(opts.stdin);
|
|
19202
|
+
return new Promise((resolve7, reject) => {
|
|
19203
|
+
let out = "";
|
|
19204
|
+
let outBytes = 0;
|
|
19205
|
+
let err = "";
|
|
19206
|
+
let errBytes = 0;
|
|
19207
|
+
let overflowed = false;
|
|
19208
|
+
child.stdout?.on("data", (d) => {
|
|
19209
|
+
const chunk = Buffer.isBuffer(d) ? d : Buffer.from(String(d));
|
|
19210
|
+
outBytes += chunk.length;
|
|
19211
|
+
out += chunk.toString();
|
|
19212
|
+
if (!overflowed && outBytes > maxStdoutBytes) {
|
|
19213
|
+
overflowed = true;
|
|
19214
|
+
child.kill("SIGTERM");
|
|
19215
|
+
reject(new Error(`runner output exceeded ${maxStdoutBytes} bytes`));
|
|
19216
|
+
}
|
|
19217
|
+
});
|
|
19218
|
+
child.stderr?.on("data", (d) => {
|
|
19219
|
+
const chunk = Buffer.isBuffer(d) ? d : Buffer.from(String(d));
|
|
19220
|
+
errBytes += chunk.length;
|
|
19221
|
+
err += chunk.toString();
|
|
19222
|
+
if (errBytes > STDERR_CAP_BYTES) err = err.slice(-STDERR_CAP_BYTES);
|
|
19223
|
+
});
|
|
19224
|
+
child.on("error", reject);
|
|
19225
|
+
child.on("close", (code) => {
|
|
19226
|
+
if (overflowed) return;
|
|
19227
|
+
if (code === 0) {
|
|
19228
|
+
resolve7({ stdout: out });
|
|
19229
|
+
return;
|
|
19230
|
+
}
|
|
19231
|
+
const tail = err.length > 0 ? ` \u2014 stderr: ${err.slice(-STDERR_TAIL_BYTES)}` : "";
|
|
19232
|
+
reject(new Error(`exited with code ${code}${tail}`));
|
|
19233
|
+
});
|
|
19234
|
+
});
|
|
19235
|
+
};
|
|
19236
|
+
EMPTY_TIER = { findings: [], ranLlmTier: false, requiredRunnerFailed: false };
|
|
18154
19237
|
EM_DASH2 = "\u2014";
|
|
18155
19238
|
STATUS_RANK = {
|
|
18156
19239
|
backlog: 0,
|
|
@@ -18162,6 +19245,9 @@ var init_dist3 = __esm({
|
|
|
18162
19245
|
"in-progress": 2,
|
|
18163
19246
|
done: 3
|
|
18164
19247
|
};
|
|
19248
|
+
EM_DASH3 = "\u2014";
|
|
19249
|
+
DEFAULT_MILESTONE = "Intake";
|
|
19250
|
+
MAX_CLOSEST_MATCHES = 3;
|
|
18165
19251
|
RETRY_DEFAULTS = { maxRetries: 5, baseDelayMs: 1e3 };
|
|
18166
19252
|
GitHubIssuesSyncAdapter = class {
|
|
18167
19253
|
token;
|
|
@@ -19130,28 +20216,31 @@ ${JSON.stringify(event)}`;
|
|
|
19130
20216
|
runMigrationPlan: () => runMigrationPlan
|
|
19131
20217
|
});
|
|
19132
20218
|
HASH_RE = /<!--\s*harness-history\s+hash:([0-9a-f]{8})\s*-->/i;
|
|
19133
|
-
|
|
19134
|
-
|
|
19135
|
-
|
|
19136
|
-
|
|
19137
|
-
|
|
20219
|
+
DEFAULT_CATCH_ALL = ["Backlog", "Current Work"];
|
|
20220
|
+
DEFAULT_INTAKE = "Intake";
|
|
20221
|
+
DEFAULT_MAX_ACTIVE = 25;
|
|
20222
|
+
InteractionTypeSchema = z13.enum(["question", "confirmation", "transition"]);
|
|
20223
|
+
QuestionSchema = z13.object({
|
|
20224
|
+
text: z13.string(),
|
|
20225
|
+
options: z13.array(z13.string()).optional(),
|
|
20226
|
+
default: z13.string().optional()
|
|
19138
20227
|
});
|
|
19139
|
-
ConfirmationSchema =
|
|
19140
|
-
text:
|
|
19141
|
-
context:
|
|
20228
|
+
ConfirmationSchema = z13.object({
|
|
20229
|
+
text: z13.string(),
|
|
20230
|
+
context: z13.string()
|
|
19142
20231
|
});
|
|
19143
|
-
TransitionSchema =
|
|
19144
|
-
completedPhase:
|
|
19145
|
-
suggestedNext:
|
|
19146
|
-
reason:
|
|
19147
|
-
artifacts:
|
|
19148
|
-
requiresConfirmation:
|
|
19149
|
-
summary:
|
|
20232
|
+
TransitionSchema = z13.object({
|
|
20233
|
+
completedPhase: z13.string(),
|
|
20234
|
+
suggestedNext: z13.string(),
|
|
20235
|
+
reason: z13.string(),
|
|
20236
|
+
artifacts: z13.array(z13.string()),
|
|
20237
|
+
requiresConfirmation: z13.boolean(),
|
|
20238
|
+
summary: z13.string()
|
|
19150
20239
|
});
|
|
19151
|
-
EmitInteractionInputSchema =
|
|
19152
|
-
path:
|
|
20240
|
+
EmitInteractionInputSchema = z13.object({
|
|
20241
|
+
path: z13.string(),
|
|
19153
20242
|
type: InteractionTypeSchema,
|
|
19154
|
-
stream:
|
|
20243
|
+
stream: z13.string().optional(),
|
|
19155
20244
|
question: QuestionSchema.optional(),
|
|
19156
20245
|
confirmation: ConfirmationSchema.optional(),
|
|
19157
20246
|
transition: TransitionSchema.optional()
|
|
@@ -19162,10 +20251,10 @@ ${JSON.stringify(event)}`;
|
|
|
19162
20251
|
}
|
|
19163
20252
|
rootDir;
|
|
19164
20253
|
async scan() {
|
|
19165
|
-
let projectName =
|
|
20254
|
+
let projectName = path36.basename(this.rootDir);
|
|
19166
20255
|
try {
|
|
19167
|
-
const pkgPath =
|
|
19168
|
-
const pkgRaw = await
|
|
20256
|
+
const pkgPath = path36.join(this.rootDir, "package.json");
|
|
20257
|
+
const pkgRaw = await fs36.readFile(pkgPath, "utf-8");
|
|
19169
20258
|
const pkg = JSON.parse(pkgRaw);
|
|
19170
20259
|
if (pkg.name) projectName = pkg.name;
|
|
19171
20260
|
} catch {
|
|
@@ -19278,8 +20367,8 @@ console.log('Blueprint player initialized.');
|
|
|
19278
20367
|
styles: STYLES,
|
|
19279
20368
|
scripts: SCRIPTS
|
|
19280
20369
|
});
|
|
19281
|
-
await
|
|
19282
|
-
await
|
|
20370
|
+
await fs37.mkdir(options.outputDir, { recursive: true });
|
|
20371
|
+
await fs37.writeFile(path37.join(options.outputDir, "index.html"), html);
|
|
19283
20372
|
}
|
|
19284
20373
|
};
|
|
19285
20374
|
TOKENS_PER_MILLION = 1e6;
|
|
@@ -19641,7 +20730,7 @@ console.log('Blueprint player initialized.');
|
|
|
19641
20730
|
}
|
|
19642
20731
|
const backoff = RETRY_BACKOFFS_MS[attempt] ?? 0;
|
|
19643
20732
|
if (attempt < RETRY_BACKOFFS_MS.length - 1) {
|
|
19644
|
-
await new Promise((
|
|
20733
|
+
await new Promise((resolve7) => setTimeout(resolve7, backoff));
|
|
19645
20734
|
}
|
|
19646
20735
|
}
|
|
19647
20736
|
this.warn(
|
|
@@ -19678,6 +20767,342 @@ console.log('Blueprint player initialized.');
|
|
|
19678
20767
|
SpanKind2[SpanKind2["CONSUMER"] = 5] = "CONSUMER";
|
|
19679
20768
|
return SpanKind2;
|
|
19680
20769
|
})(SpanKind || {});
|
|
20770
|
+
ModeSchema = z14.enum(["adopter", "toolkit"]);
|
|
20771
|
+
SeveritySchema = z14.enum(["error", "warning", "info"]);
|
|
20772
|
+
TierSchema = z14.enum(["solid", "at-risk", "theatre"]);
|
|
20773
|
+
StrengthFindingSchema = z14.object({
|
|
20774
|
+
id: z14.string(),
|
|
20775
|
+
// e.g. "STRENGTH-001"
|
|
20776
|
+
gearPiece: z14.string(),
|
|
20777
|
+
// label only (v1) — gear piece this defends
|
|
20778
|
+
severity: SeveritySchema,
|
|
20779
|
+
file: z14.string(),
|
|
20780
|
+
// relative to root
|
|
20781
|
+
line: z14.number().int().positive().optional(),
|
|
20782
|
+
// when locatable
|
|
20783
|
+
message: z14.string(),
|
|
20784
|
+
// what's wrong
|
|
20785
|
+
remediation: z14.string()
|
|
20786
|
+
// concrete fix
|
|
20787
|
+
});
|
|
20788
|
+
HarnessConfigSubsetSchema = z14.object({
|
|
20789
|
+
layers: z14.array(z14.unknown()).optional(),
|
|
20790
|
+
architecture: z14.object({ thresholds: z14.record(z14.unknown()).optional() }).passthrough().optional(),
|
|
20791
|
+
template: z14.object({ level: z14.string().optional() }).passthrough().optional(),
|
|
20792
|
+
audit: z14.object({
|
|
20793
|
+
harnessStrength: z14.object({ severities: z14.record(SeveritySchema).optional() }).passthrough().optional()
|
|
20794
|
+
}).passthrough().optional()
|
|
20795
|
+
}).passthrough();
|
|
20796
|
+
HookFileSchema = z14.object({
|
|
20797
|
+
name: z14.string(),
|
|
20798
|
+
path: z14.string(),
|
|
20799
|
+
text: z14.string()
|
|
20800
|
+
});
|
|
20801
|
+
ProjectContextSchema = z14.object({
|
|
20802
|
+
root: z14.string(),
|
|
20803
|
+
mode: ModeSchema,
|
|
20804
|
+
config: HarnessConfigSubsetSchema.nullable(),
|
|
20805
|
+
preCommit: z14.string().nullable(),
|
|
20806
|
+
hookFiles: z14.array(HookFileSchema),
|
|
20807
|
+
workflows: z14.array(z14.object({ path: z14.string(), text: z14.string() })),
|
|
20808
|
+
healthSnapshot: z14.unknown().nullable(),
|
|
20809
|
+
templates: z14.array(z14.object({ path: z14.string(), text: z14.string() })).optional(),
|
|
20810
|
+
initSkill: z14.string().nullable().optional()
|
|
20811
|
+
});
|
|
20812
|
+
AuditSummarySchema = z14.object({
|
|
20813
|
+
errors: z14.number().int().nonnegative(),
|
|
20814
|
+
warnings: z14.number().int().nonnegative(),
|
|
20815
|
+
info: z14.number().int().nonnegative(),
|
|
20816
|
+
rulesRun: z14.number().int().nonnegative(),
|
|
20817
|
+
rulesPassing: z14.number().int().nonnegative()
|
|
20818
|
+
});
|
|
20819
|
+
AuditResultSchema = z14.object({
|
|
20820
|
+
mode: ModeSchema,
|
|
20821
|
+
score: z14.number().min(0).max(100),
|
|
20822
|
+
tier: TierSchema,
|
|
20823
|
+
findings: z14.array(StrengthFindingSchema),
|
|
20824
|
+
summary: AuditSummarySchema
|
|
20825
|
+
});
|
|
20826
|
+
SEVERITY_WEIGHTS = {
|
|
20827
|
+
error: 14,
|
|
20828
|
+
warning: 6,
|
|
20829
|
+
info: 2
|
|
20830
|
+
};
|
|
20831
|
+
clamp = (n, lo, hi) => Math.max(lo, Math.min(hi, n));
|
|
20832
|
+
HOOK_SCRIPT_TOKEN = /(?:^|[\s"'])([^\s"']+\.(?:sh|mjs|cjs|js|ts))(?=$|[\s"'])/g;
|
|
20833
|
+
NEVER_BLOCKS = /never\s+blocks/i;
|
|
20834
|
+
ALWAYS_EXIT_ZERO = /always\s+exits?\s+0/i;
|
|
20835
|
+
EXIT_ZERO_LINE = /^\s*exit\s+0\s*$/;
|
|
20836
|
+
NONZERO_EXIT = /\bexit\s+[1-9]\b/;
|
|
20837
|
+
GATE = /\bif\b|\|\||&&|\bthen\b/;
|
|
20838
|
+
ERREXIT = /\bset\s+-(?:[a-df-z]*e[a-z]*|o\s+errexit)\b/;
|
|
20839
|
+
strength001NonblockingHooks = {
|
|
20840
|
+
id: "STRENGTH-001",
|
|
20841
|
+
gearPiece: "blocking-gate",
|
|
20842
|
+
defaultSeverity: "error",
|
|
20843
|
+
appliesIn: () => true,
|
|
20844
|
+
evaluable: (ctx) => ctx.hookFiles.length > 0,
|
|
20845
|
+
detect(ctx) {
|
|
20846
|
+
const findings = [];
|
|
20847
|
+
for (const hook of ctx.hookFiles) {
|
|
20848
|
+
const lines = hook.text.split("\n");
|
|
20849
|
+
let line;
|
|
20850
|
+
const markerIdx = lines.findIndex((l) => NEVER_BLOCKS.test(l) || ALWAYS_EXIT_ZERO.test(l));
|
|
20851
|
+
if (markerIdx >= 0) {
|
|
20852
|
+
line = markerIdx + 1;
|
|
20853
|
+
} else {
|
|
20854
|
+
const exitIdx = lines.findIndex((l) => EXIT_ZERO_LINE.test(l));
|
|
20855
|
+
const code = stripComments(hook.text);
|
|
20856
|
+
const hasGuard = NONZERO_EXIT.test(code) || GATE.test(code) || ERREXIT.test(code);
|
|
20857
|
+
if (exitIdx >= 0 && !hasGuard) {
|
|
20858
|
+
line = exitIdx + 1;
|
|
20859
|
+
}
|
|
20860
|
+
}
|
|
20861
|
+
if (line !== void 0) {
|
|
20862
|
+
findings.push({
|
|
20863
|
+
id: "STRENGTH-001",
|
|
20864
|
+
gearPiece: "blocking-gate",
|
|
20865
|
+
file: hook.path,
|
|
20866
|
+
line,
|
|
20867
|
+
message: `Hook "${hook.name}" cannot block the commit \u2014 it always succeeds, so a regression passes through.`,
|
|
20868
|
+
remediation: 'Remove the unconditional `exit 0` / "never blocks" escape hatch so the hook fails (non-zero exit) when a check fails.'
|
|
20869
|
+
});
|
|
20870
|
+
}
|
|
20871
|
+
}
|
|
20872
|
+
return findings;
|
|
20873
|
+
}
|
|
20874
|
+
};
|
|
20875
|
+
BASELINE_REWRITE = /--update-baseline|(?:check-arch|baselines?\.json).*(?:--update-baseline|>\s*\S*baselines?\.json)/i;
|
|
20876
|
+
strength002Autobaseline = {
|
|
20877
|
+
id: "STRENGTH-002",
|
|
20878
|
+
gearPiece: "regression-baseline",
|
|
20879
|
+
defaultSeverity: "error",
|
|
20880
|
+
appliesIn: () => true,
|
|
20881
|
+
evaluable: (ctx) => ctx.preCommit !== null,
|
|
20882
|
+
detect(ctx) {
|
|
20883
|
+
if (ctx.preCommit === null) return [];
|
|
20884
|
+
const idx = findRewriteInFailureBranch(ctx.preCommit.split("\n"));
|
|
20885
|
+
if (idx < 0) return [];
|
|
20886
|
+
return [
|
|
20887
|
+
{
|
|
20888
|
+
id: "STRENGTH-002",
|
|
20889
|
+
gearPiece: "regression-baseline",
|
|
20890
|
+
file: ".husky/pre-commit",
|
|
20891
|
+
line: idx + 1,
|
|
20892
|
+
message: "pre-commit auto-rewrites the baseline inside a failure branch \u2014 regressions are silently absorbed instead of blocking the commit.",
|
|
20893
|
+
remediation: "Remove the auto `--update-baseline` from the failure path; let the hook fail and update baselines deliberately in a separate, reviewed step."
|
|
20894
|
+
}
|
|
20895
|
+
];
|
|
20896
|
+
}
|
|
20897
|
+
};
|
|
20898
|
+
SKIP_RE = /--skip[= ]+([\w,-]+)/;
|
|
20899
|
+
strength003SkipList = {
|
|
20900
|
+
id: "STRENGTH-003",
|
|
20901
|
+
gearPiece: "skip-discipline",
|
|
20902
|
+
defaultSeverity: "warning",
|
|
20903
|
+
appliesIn: () => true,
|
|
20904
|
+
evaluable: (ctx) => ctx.preCommit !== null,
|
|
20905
|
+
detect(ctx) {
|
|
20906
|
+
if (ctx.preCommit === null) return [];
|
|
20907
|
+
const lines = ctx.preCommit.split("\n");
|
|
20908
|
+
const findings = [];
|
|
20909
|
+
for (let i = 0; i < lines.length; i++) {
|
|
20910
|
+
const line = lines[i];
|
|
20911
|
+
const m = SKIP_RE.exec(line);
|
|
20912
|
+
if (!m) continue;
|
|
20913
|
+
const categories = m[1].split(",").filter(Boolean);
|
|
20914
|
+
if (categories.length <= 2) continue;
|
|
20915
|
+
const afterSkip = line.slice(m.index + m[0].length);
|
|
20916
|
+
if (/(^|\s)#/.test(afterSkip)) continue;
|
|
20917
|
+
findings.push({
|
|
20918
|
+
id: "STRENGTH-003",
|
|
20919
|
+
gearPiece: "skip-discipline",
|
|
20920
|
+
file: ".husky/pre-commit",
|
|
20921
|
+
line: i + 1,
|
|
20922
|
+
message: `pre-commit skips ${categories.length} check categories (${categories.join(", ")}) with no inline justification \u2014 the local gate barely guards anything.`,
|
|
20923
|
+
remediation: "Reduce the --skip list to at most 2 categories, or add an inline `# justified: ...` comment explaining why each is deferred (e.g. runs in CI)."
|
|
20924
|
+
});
|
|
20925
|
+
}
|
|
20926
|
+
return findings;
|
|
20927
|
+
}
|
|
20928
|
+
};
|
|
20929
|
+
strength004EmptyThresholds = {
|
|
20930
|
+
id: "STRENGTH-004",
|
|
20931
|
+
gearPiece: "architecture-thresholds",
|
|
20932
|
+
defaultSeverity: "error",
|
|
20933
|
+
appliesIn: () => true,
|
|
20934
|
+
evaluable: (ctx) => ctx.config !== null || ctx.mode === "toolkit" && (ctx.templates?.length ?? 0) > 0,
|
|
20935
|
+
detect(ctx) {
|
|
20936
|
+
const findings = [];
|
|
20937
|
+
if (thresholdsEmpty(ctx.config)) {
|
|
20938
|
+
findings.push(makeFinding3("harness.config.json"));
|
|
20939
|
+
}
|
|
20940
|
+
if (ctx.mode === "toolkit") {
|
|
20941
|
+
for (const tpl of ctx.templates ?? []) {
|
|
20942
|
+
if (!tpl.path.endsWith("harness.config.json.hbs")) continue;
|
|
20943
|
+
let parsed;
|
|
20944
|
+
try {
|
|
20945
|
+
parsed = JSON.parse(tpl.text);
|
|
20946
|
+
} catch {
|
|
20947
|
+
continue;
|
|
20948
|
+
}
|
|
20949
|
+
if (thresholdsEmpty(parsed)) findings.push(makeFinding3(tpl.path));
|
|
20950
|
+
}
|
|
20951
|
+
}
|
|
20952
|
+
return findings;
|
|
20953
|
+
}
|
|
20954
|
+
};
|
|
20955
|
+
INIT_SKILL_PATH = "agents/skills/claude-code/initialize-harness-project/SKILL.md";
|
|
20956
|
+
DEFAULT_BASIC = /default(?:s| recommendation)?[^\n]*\bbasic\b|\bbasic\b[^\n]*\b(?:default|recommend)/i;
|
|
20957
|
+
strength005LowestTier = {
|
|
20958
|
+
id: "STRENGTH-005",
|
|
20959
|
+
gearPiece: "tier-default",
|
|
20960
|
+
defaultSeverity: "warning",
|
|
20961
|
+
appliesIn: () => true,
|
|
20962
|
+
evaluable: (ctx) => ctx.mode === "adopter" ? ctx.config !== null : (ctx.initSkill ?? null) !== null,
|
|
20963
|
+
detect(ctx) {
|
|
20964
|
+
if (ctx.mode === "adopter") {
|
|
20965
|
+
if (ctx.config?.template?.level === "basic") {
|
|
20966
|
+
return [
|
|
20967
|
+
{
|
|
20968
|
+
id: "STRENGTH-005",
|
|
20969
|
+
gearPiece: "tier-default",
|
|
20970
|
+
file: "harness.config.json",
|
|
20971
|
+
message: "Project is pinned to the `basic` tier \u2014 the weakest gate set. Most projects warrant `standard` or higher.",
|
|
20972
|
+
remediation: "Raise `template.level` to `standard` (or higher) unless `basic` is a deliberate, documented choice."
|
|
20973
|
+
}
|
|
20974
|
+
];
|
|
20975
|
+
}
|
|
20976
|
+
return [];
|
|
20977
|
+
}
|
|
20978
|
+
const text = ctx.initSkill ?? null;
|
|
20979
|
+
if (text === null) return [];
|
|
20980
|
+
const lines = text.split("\n");
|
|
20981
|
+
const idx = lines.findIndex((l) => DEFAULT_BASIC.test(l));
|
|
20982
|
+
if (idx < 0) return [];
|
|
20983
|
+
return [
|
|
20984
|
+
{
|
|
20985
|
+
id: "STRENGTH-005",
|
|
20986
|
+
gearPiece: "tier-default",
|
|
20987
|
+
file: INIT_SKILL_PATH,
|
|
20988
|
+
line: idx + 1,
|
|
20989
|
+
message: "The init skill recommends the `basic` tier by default \u2014 new projects ship with the weakest gate set.",
|
|
20990
|
+
remediation: "Change the default recommendation to `standard` so new projects start with a meaningful gate set."
|
|
20991
|
+
}
|
|
20992
|
+
];
|
|
20993
|
+
}
|
|
20994
|
+
};
|
|
20995
|
+
AUTO_PATTERN = /auto-?approve|auto-?merge|--auto\b|enable-pull-request-automerge/i;
|
|
20996
|
+
COMMENT_LINE = /^\s*#/;
|
|
20997
|
+
NAME_VALUE_LINE = /^\s*(?:-\s*)?name\s*:/i;
|
|
20998
|
+
PAT_GATING = /secrets\.(?!GITHUB_TOKEN\b)\w*(?:PAT|TOKEN)\w*/;
|
|
20999
|
+
REVIEW_SIGNAL = /required_reviewers|approvals?\s*:\s*[1-9]|CODEOWNERS|github\.event\.review\.state\s*==\s*['"]approved['"]/i;
|
|
21000
|
+
strength006AutoapproveBaseline = {
|
|
21001
|
+
id: "STRENGTH-006",
|
|
21002
|
+
gearPiece: "review-gate",
|
|
21003
|
+
defaultSeverity: "error",
|
|
21004
|
+
appliesIn: () => true,
|
|
21005
|
+
evaluable: (ctx) => ctx.workflows.length > 0,
|
|
21006
|
+
detect(ctx) {
|
|
21007
|
+
const findings = [];
|
|
21008
|
+
for (const wf of ctx.workflows) {
|
|
21009
|
+
const hasAuto = hasAutoStep(wf.text);
|
|
21010
|
+
const hasPat = PAT_GATING.test(wf.text);
|
|
21011
|
+
const hasReview = REVIEW_SIGNAL.test(wf.text);
|
|
21012
|
+
if (hasAuto && hasPat && !hasReview) {
|
|
21013
|
+
const lines = wf.text.split("\n");
|
|
21014
|
+
const idx = lines.findIndex(isAutoLine);
|
|
21015
|
+
findings.push({
|
|
21016
|
+
id: "STRENGTH-006",
|
|
21017
|
+
gearPiece: "review-gate",
|
|
21018
|
+
file: wf.path,
|
|
21019
|
+
...idx >= 0 ? { line: idx + 1 } : {},
|
|
21020
|
+
message: "Workflow auto-approves/auto-merges a PR using a token with no independent-review gate \u2014 the bot can rubber-stamp its own changes.",
|
|
21021
|
+
remediation: "Gate auto-merge behind a human review (e.g. require an approved review, required_reviewers, or CODEOWNERS) instead of a PAT alone."
|
|
21022
|
+
});
|
|
21023
|
+
}
|
|
21024
|
+
}
|
|
21025
|
+
return findings;
|
|
21026
|
+
}
|
|
21027
|
+
};
|
|
21028
|
+
CHECK_SIGNAL_MAP = {
|
|
21029
|
+
security: "security-findings",
|
|
21030
|
+
entropy: "entropy-drift",
|
|
21031
|
+
deps: "dependency-violations",
|
|
21032
|
+
perf: "perf-regression",
|
|
21033
|
+
docs: "doc-coverage",
|
|
21034
|
+
lint: "lint-issues"
|
|
21035
|
+
};
|
|
21036
|
+
strength007SnapshotSignalMismatch = {
|
|
21037
|
+
id: "STRENGTH-007",
|
|
21038
|
+
gearPiece: "snapshot-honesty",
|
|
21039
|
+
defaultSeverity: "error",
|
|
21040
|
+
appliesIn: () => true,
|
|
21041
|
+
evaluable: (ctx) => isSnapshot(ctx.healthSnapshot),
|
|
21042
|
+
detect(ctx) {
|
|
21043
|
+
if (!isSnapshot(ctx.healthSnapshot)) return [];
|
|
21044
|
+
const { checks, signals } = ctx.healthSnapshot;
|
|
21045
|
+
const present = new Set(Array.isArray(signals) ? signals : []);
|
|
21046
|
+
const findings = [];
|
|
21047
|
+
for (const [check, result] of Object.entries(checks)) {
|
|
21048
|
+
const signal = CHECK_SIGNAL_MAP[check];
|
|
21049
|
+
if (signal === void 0) continue;
|
|
21050
|
+
if (result?.passed === true && present.has(signal)) {
|
|
21051
|
+
findings.push({
|
|
21052
|
+
id: "STRENGTH-007",
|
|
21053
|
+
gearPiece: "snapshot-honesty",
|
|
21054
|
+
file: ".harness/health-snapshot.json",
|
|
21055
|
+
message: `Health snapshot reports the "${check}" check as passing while the contradicting signal "${signal}" is present \u2014 the snapshot is dishonest.`,
|
|
21056
|
+
remediation: `Reconcile the snapshot: either the "${check}" check did not actually pass, or the "${signal}" signal is stale. Regenerate the snapshot from a clean run.`
|
|
21057
|
+
});
|
|
21058
|
+
}
|
|
21059
|
+
}
|
|
21060
|
+
return findings;
|
|
21061
|
+
}
|
|
21062
|
+
};
|
|
21063
|
+
ALL_RULES = [
|
|
21064
|
+
strength001NonblockingHooks,
|
|
21065
|
+
strength002Autobaseline,
|
|
21066
|
+
strength003SkipList,
|
|
21067
|
+
strength004EmptyThresholds,
|
|
21068
|
+
strength005LowestTier,
|
|
21069
|
+
strength006AutoapproveBaseline,
|
|
21070
|
+
strength007SnapshotSignalMismatch
|
|
21071
|
+
];
|
|
21072
|
+
HarnessStrengthAuditor = class {
|
|
21073
|
+
audit(root, opts = {}) {
|
|
21074
|
+
try {
|
|
21075
|
+
const mode = resolveMode(opts, root);
|
|
21076
|
+
const ctx = buildProjectContext(root, mode);
|
|
21077
|
+
const evaluable = ALL_RULES.filter(
|
|
21078
|
+
(r) => r.appliesIn(mode) && (r.evaluable ? r.evaluable(ctx) : true)
|
|
21079
|
+
);
|
|
21080
|
+
const findings = [];
|
|
21081
|
+
let rulesPassing = 0;
|
|
21082
|
+
for (const rule of evaluable) {
|
|
21083
|
+
const raw = rule.detect(ctx);
|
|
21084
|
+
if (raw.length === 0) {
|
|
21085
|
+
rulesPassing++;
|
|
21086
|
+
continue;
|
|
21087
|
+
}
|
|
21088
|
+
const severity = severityFor(rule, ctx);
|
|
21089
|
+
for (const f of raw) findings.push({ ...f, severity });
|
|
21090
|
+
}
|
|
21091
|
+
const { score, tier } = rollupScore(findings);
|
|
21092
|
+
const summary = {
|
|
21093
|
+
errors: findings.filter((f) => f.severity === "error").length,
|
|
21094
|
+
warnings: findings.filter((f) => f.severity === "warning").length,
|
|
21095
|
+
info: findings.filter((f) => f.severity === "info").length,
|
|
21096
|
+
rulesRun: evaluable.length,
|
|
21097
|
+
rulesPassing
|
|
21098
|
+
};
|
|
21099
|
+
return Ok({ mode, score, tier, findings, summary });
|
|
21100
|
+
} catch (err) {
|
|
21101
|
+
return Err(err instanceof Error ? err : new Error(String(err)));
|
|
21102
|
+
}
|
|
21103
|
+
}
|
|
21104
|
+
};
|
|
21105
|
+
MAX_TOP_AFFECTED = 5;
|
|
19681
21106
|
CompoundLockHeldError = class extends Error {
|
|
19682
21107
|
constructor(category, holderPid, lockPath) {
|
|
19683
21108
|
super(
|
|
@@ -19693,6 +21118,18 @@ console.log('Blueprint player initialized.');
|
|
|
19693
21118
|
lockPath;
|
|
19694
21119
|
};
|
|
19695
21120
|
KNOWN_CATEGORIES = new Set(ALL_SOLUTION_CATEGORIES);
|
|
21121
|
+
ProposalNotFoundError = class extends Error {
|
|
21122
|
+
constructor(id) {
|
|
21123
|
+
super(`proposal not found: ${id}`);
|
|
21124
|
+
this.name = "ProposalNotFoundError";
|
|
21125
|
+
}
|
|
21126
|
+
};
|
|
21127
|
+
ProposalConflictError = class extends Error {
|
|
21128
|
+
constructor(message) {
|
|
21129
|
+
super(message);
|
|
21130
|
+
this.name = "ProposalConflictError";
|
|
21131
|
+
}
|
|
21132
|
+
};
|
|
19696
21133
|
ALLOWED_FIELD_KEYS = [
|
|
19697
21134
|
"event_name",
|
|
19698
21135
|
"count",
|
|
@@ -19822,18 +21259,6 @@ console.log('Blueprint player initialized.');
|
|
|
19822
21259
|
...OPTIONAL_STRATEGY_SECTIONS
|
|
19823
21260
|
];
|
|
19824
21261
|
FILENAME = "STRATEGY.md";
|
|
19825
|
-
ProposalNotFoundError = class extends Error {
|
|
19826
|
-
constructor(id) {
|
|
19827
|
-
super(`proposal not found: ${id}`);
|
|
19828
|
-
this.name = "ProposalNotFoundError";
|
|
19829
|
-
}
|
|
19830
|
-
};
|
|
19831
|
-
ProposalConflictError = class extends Error {
|
|
19832
|
-
constructor(message) {
|
|
19833
|
-
super(message);
|
|
19834
|
-
this.name = "ProposalConflictError";
|
|
19835
|
-
}
|
|
19836
|
-
};
|
|
19837
21262
|
}
|
|
19838
21263
|
});
|
|
19839
21264
|
|
|
@@ -19933,7 +21358,6 @@ export {
|
|
|
19933
21358
|
PatternConfigSchema,
|
|
19934
21359
|
EntropyConfigSchema,
|
|
19935
21360
|
validatePatternConfig,
|
|
19936
|
-
composeInsights,
|
|
19937
21361
|
BaselineManager,
|
|
19938
21362
|
BenchmarkRunner,
|
|
19939
21363
|
RegressionDetector,
|
|
@@ -20165,11 +21589,34 @@ export {
|
|
|
20165
21589
|
splitBundlesByStage,
|
|
20166
21590
|
runReviewPipeline,
|
|
20167
21591
|
findParallelGroups,
|
|
21592
|
+
CI_REVIEW_VERDICT_SCHEMA_VERSION,
|
|
21593
|
+
CI_RUNNERS,
|
|
21594
|
+
CI_ASSESSMENTS,
|
|
21595
|
+
CI_REVIEW_DOMAINS,
|
|
21596
|
+
deriveBlockingFindings,
|
|
21597
|
+
deriveExitCode,
|
|
21598
|
+
CiReviewVerdictSchema,
|
|
21599
|
+
parseCiReviewVerdict,
|
|
21600
|
+
buildCiReviewVerdict,
|
|
21601
|
+
parseClaudeVerdict,
|
|
21602
|
+
parseCodexVerdict,
|
|
21603
|
+
parseAntigravityVerdict,
|
|
21604
|
+
parseLocalVerdict,
|
|
21605
|
+
RUNNER_PRESETS,
|
|
21606
|
+
presetKind,
|
|
21607
|
+
isSupportedRunner,
|
|
21608
|
+
parseGeminiVerdict,
|
|
21609
|
+
DEFAULT_EXEC_TIMEOUT_MS,
|
|
21610
|
+
DEFAULT_EXEC_MAX_STDOUT_BYTES,
|
|
21611
|
+
defaultExecFile,
|
|
21612
|
+
runCiReview,
|
|
20168
21613
|
serializeRoadmap,
|
|
20169
21614
|
STATUS_RANK,
|
|
20170
21615
|
isRegression,
|
|
20171
21616
|
syncRoadmap,
|
|
20172
21617
|
applySyncChanges,
|
|
21618
|
+
decidePromotionForRow,
|
|
21619
|
+
promoteFeature,
|
|
20173
21620
|
resolveReverseStatus,
|
|
20174
21621
|
GitHubIssuesSyncAdapter,
|
|
20175
21622
|
loadTrackerSyncConfig,
|
|
@@ -20187,6 +21634,10 @@ export {
|
|
|
20187
21634
|
loadProjectRoadmapMode,
|
|
20188
21635
|
loadTrackerClientConfigFromProject,
|
|
20189
21636
|
migrate_exports,
|
|
21637
|
+
defaultIsArchive,
|
|
21638
|
+
isUnactionablePlanned,
|
|
21639
|
+
checkRoadmapHealth,
|
|
21640
|
+
groomRoadmap,
|
|
20190
21641
|
InteractionTypeSchema,
|
|
20191
21642
|
QuestionSchema,
|
|
20192
21643
|
ConfirmationSchema,
|
|
@@ -20219,7 +21670,6 @@ export {
|
|
|
20219
21670
|
aggregateBySkill,
|
|
20220
21671
|
aggregateByDay2,
|
|
20221
21672
|
topSkills,
|
|
20222
|
-
loadNotificationsConfig,
|
|
20223
21673
|
StructuralStrategy,
|
|
20224
21674
|
DEFAULT_TOKEN_BUDGET,
|
|
20225
21675
|
TruncationStrategy,
|
|
@@ -20238,8 +21688,33 @@ export {
|
|
|
20238
21688
|
CacheMetricsRecorder,
|
|
20239
21689
|
OTLPExporter,
|
|
20240
21690
|
SpanKind,
|
|
21691
|
+
ModeSchema,
|
|
21692
|
+
SeveritySchema,
|
|
21693
|
+
TierSchema,
|
|
21694
|
+
StrengthFindingSchema,
|
|
21695
|
+
HarnessConfigSubsetSchema,
|
|
21696
|
+
HookFileSchema,
|
|
21697
|
+
ProjectContextSchema,
|
|
21698
|
+
AuditSummarySchema,
|
|
21699
|
+
AuditResultSchema,
|
|
21700
|
+
SEVERITY_WEIGHTS,
|
|
21701
|
+
rollupScore,
|
|
21702
|
+
resolveMode,
|
|
21703
|
+
buildProjectContext,
|
|
21704
|
+
ALL_RULES,
|
|
21705
|
+
HarnessStrengthAuditor,
|
|
21706
|
+
composeInsights,
|
|
20241
21707
|
CompoundLockHeldError,
|
|
20242
21708
|
acquireCompoundLock,
|
|
21709
|
+
loadNotificationsConfig,
|
|
21710
|
+
proposalsDir,
|
|
21711
|
+
ProposalNotFoundError,
|
|
21712
|
+
ProposalConflictError,
|
|
21713
|
+
createProposal,
|
|
21714
|
+
getProposal,
|
|
21715
|
+
listProposals,
|
|
21716
|
+
updateProposal,
|
|
21717
|
+
deriveSkillUsage,
|
|
20243
21718
|
ALLOWED_FIELD_KEYS,
|
|
20244
21719
|
PII_TOKENS,
|
|
20245
21720
|
PII_FIELD_DENYLIST,
|
|
@@ -20269,14 +21744,6 @@ export {
|
|
|
20269
21744
|
assembleCandidateReport,
|
|
20270
21745
|
serializeStrategyDoc,
|
|
20271
21746
|
writeStrategyDoc,
|
|
20272
|
-
proposalsDir,
|
|
20273
|
-
ProposalNotFoundError,
|
|
20274
|
-
ProposalConflictError,
|
|
20275
|
-
createProposal,
|
|
20276
|
-
getProposal,
|
|
20277
|
-
listProposals,
|
|
20278
|
-
updateProposal,
|
|
20279
|
-
deriveSkillUsage,
|
|
20280
21747
|
dist_exports,
|
|
20281
21748
|
init_dist3 as init_dist
|
|
20282
21749
|
};
|