@harness-engineering/cli 2.7.0 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/personas/adversarial-reviewer.yaml +43 -0
- package/dist/agents/personas/frontend-races-reviewer.yaml +54 -0
- package/dist/agents/personas/typescript-strict-reviewer.yaml +53 -0
- package/dist/agents/skills/CHANGELOG.md +12 -0
- package/dist/agents/skills/claude-code/align-design-system/SKILL.md +18 -0
- package/dist/agents/skills/claude-code/align-design-system/skill.yaml +3 -0
- package/dist/agents/skills/claude-code/harness-brainstorming/SKILL.md +37 -5
- package/dist/agents/skills/claude-code/harness-code-review/SKILL.md +107 -22
- package/dist/agents/skills/claude-code/harness-code-review/references/confidence-rubric.md +45 -0
- package/dist/agents/skills/claude-code/harness-code-review/references/risk-keywords.md +40 -0
- package/dist/agents/skills/claude-code/harness-code-review/skill.yaml +3 -0
- package/dist/agents/skills/claude-code/harness-compound/SKILL.md +7 -4
- package/dist/agents/skills/claude-code/harness-ideate/SKILL.md +277 -0
- package/dist/agents/skills/claude-code/harness-ideate/references/scoring.md +88 -0
- package/dist/agents/skills/claude-code/harness-ideate/skill.yaml +66 -0
- package/dist/agents/skills/claude-code/harness-planning/SKILL.md +14 -4
- package/dist/agents/skills/claude-code/harness-pulse/SKILL.md +12 -16
- package/dist/agents/skills/claude-code/harness-roadmap-pilot/SKILL.md +34 -6
- package/dist/agents/skills/claude-code/harness-strategy/SKILL.md +152 -0
- package/dist/agents/skills/claude-code/harness-strategy/references/interview.md +122 -0
- package/dist/agents/skills/claude-code/harness-strategy/skill.yaml +54 -0
- package/dist/agents/skills/claude-code/initialize-harness-project/SKILL.md +76 -10
- package/dist/agents/skills/claude-code/naming-craft/SKILL.md +74 -2
- package/dist/agents/skills/codex/align-design-system/SKILL.md +18 -0
- package/dist/agents/skills/codex/align-design-system/skill.yaml +3 -0
- package/dist/agents/skills/codex/harness-brainstorming/SKILL.md +37 -5
- package/dist/agents/skills/codex/harness-code-review/SKILL.md +107 -22
- package/dist/agents/skills/codex/harness-code-review/references/confidence-rubric.md +45 -0
- package/dist/agents/skills/codex/harness-code-review/references/risk-keywords.md +40 -0
- package/dist/agents/skills/codex/harness-code-review/skill.yaml +3 -0
- package/dist/agents/skills/codex/harness-compound/SKILL.md +7 -4
- package/dist/agents/skills/codex/harness-ideate/SKILL.md +277 -0
- package/dist/agents/skills/codex/harness-ideate/references/scoring.md +88 -0
- package/dist/agents/skills/codex/harness-ideate/skill.yaml +66 -0
- package/dist/agents/skills/codex/harness-planning/SKILL.md +14 -4
- package/dist/agents/skills/codex/harness-pulse/SKILL.md +12 -16
- package/dist/agents/skills/codex/harness-roadmap-pilot/SKILL.md +34 -6
- package/dist/agents/skills/codex/harness-strategy/SKILL.md +152 -0
- package/dist/agents/skills/codex/harness-strategy/references/interview.md +122 -0
- package/dist/agents/skills/codex/harness-strategy/skill.yaml +54 -0
- package/dist/agents/skills/codex/initialize-harness-project/SKILL.md +76 -10
- package/dist/agents/skills/codex/naming-craft/SKILL.md +74 -2
- package/dist/agents/skills/cursor/align-design-system/SKILL.md +18 -0
- package/dist/agents/skills/cursor/align-design-system/skill.yaml +3 -0
- package/dist/agents/skills/cursor/harness-brainstorming/SKILL.md +37 -5
- package/dist/agents/skills/cursor/harness-code-review/SKILL.md +107 -22
- package/dist/agents/skills/cursor/harness-code-review/references/confidence-rubric.md +45 -0
- package/dist/agents/skills/cursor/harness-code-review/references/risk-keywords.md +40 -0
- package/dist/agents/skills/cursor/harness-code-review/skill.yaml +3 -0
- package/dist/agents/skills/cursor/harness-compound/SKILL.md +7 -4
- package/dist/agents/skills/cursor/harness-ideate/SKILL.md +277 -0
- package/dist/agents/skills/cursor/harness-ideate/references/scoring.md +88 -0
- package/dist/agents/skills/cursor/harness-ideate/skill.yaml +66 -0
- package/dist/agents/skills/cursor/harness-planning/SKILL.md +14 -4
- package/dist/agents/skills/cursor/harness-pulse/SKILL.md +12 -16
- package/dist/agents/skills/cursor/harness-roadmap-pilot/SKILL.md +34 -6
- package/dist/agents/skills/cursor/harness-strategy/SKILL.md +152 -0
- package/dist/agents/skills/cursor/harness-strategy/references/interview.md +122 -0
- package/dist/agents/skills/cursor/harness-strategy/skill.yaml +54 -0
- package/dist/agents/skills/cursor/initialize-harness-project/SKILL.md +76 -10
- package/dist/agents/skills/cursor/naming-craft/SKILL.md +74 -2
- package/dist/agents/skills/gemini-cli/align-design-system/SKILL.md +18 -0
- package/dist/agents/skills/gemini-cli/align-design-system/skill.yaml +3 -0
- package/dist/agents/skills/gemini-cli/harness-brainstorming/SKILL.md +37 -5
- package/dist/agents/skills/gemini-cli/harness-code-review/SKILL.md +107 -22
- package/dist/agents/skills/gemini-cli/harness-code-review/references/confidence-rubric.md +45 -0
- package/dist/agents/skills/gemini-cli/harness-code-review/references/risk-keywords.md +40 -0
- package/dist/agents/skills/gemini-cli/harness-code-review/skill.yaml +3 -0
- package/dist/agents/skills/gemini-cli/harness-compound/SKILL.md +7 -4
- package/dist/agents/skills/gemini-cli/harness-ideate/SKILL.md +277 -0
- package/dist/agents/skills/gemini-cli/harness-ideate/references/scoring.md +88 -0
- package/dist/agents/skills/gemini-cli/harness-ideate/skill.yaml +66 -0
- package/dist/agents/skills/gemini-cli/harness-planning/SKILL.md +14 -4
- package/dist/agents/skills/gemini-cli/harness-pulse/SKILL.md +12 -16
- package/dist/agents/skills/gemini-cli/harness-roadmap-pilot/SKILL.md +34 -6
- package/dist/agents/skills/gemini-cli/harness-strategy/SKILL.md +152 -0
- package/dist/agents/skills/gemini-cli/harness-strategy/references/interview.md +122 -0
- package/dist/agents/skills/gemini-cli/harness-strategy/skill.yaml +54 -0
- package/dist/agents/skills/gemini-cli/initialize-harness-project/SKILL.md +76 -10
- package/dist/agents/skills/gemini-cli/naming-craft/SKILL.md +74 -2
- package/dist/agents/skills/package.json +1 -1
- package/dist/agents/skills/tests/harness-strategy.test.ts +145 -0
- package/dist/agents-md-WMJO5HSM.js +13 -0
- package/dist/analyzer-V633GUHY-MBCQWKDU.js +26 -0
- package/dist/{architecture-ZTEHGIHD.js → architecture-EVYVGPRZ.js} +6 -6
- package/dist/{assess-project-OKDW3YSK.js → assess-project-KZG563SO.js} +1 -1
- package/dist/bin/harness-mcp.js +17 -17
- package/dist/bin/harness.js +27 -27
- package/dist/{check-phase-gate-YJWFDW66.js → check-phase-gate-MK364VXS.js} +6 -6
- package/dist/{chunk-6UHDQP2N.js → chunk-2N4OENGE.js} +1 -1
- package/dist/{chunk-OLTVDPA2.js → chunk-2SNRCAC3.js} +3 -3
- package/dist/{chunk-HLPY3RHE.js → chunk-4BDOSCNY.js} +909 -559
- package/dist/{chunk-S7GEONXL.js → chunk-5XL5QJ5B.js} +2 -2
- package/dist/{chunk-OQE6ME2Z.js → chunk-7EQAZKJ5.js} +2 -2
- package/dist/{chunk-QUPC37UB.js → chunk-ACOOG3UW.js} +6 -6
- package/dist/{chunk-B4OLYXZ4.js → chunk-B7KMTHN3.js} +89 -7
- package/dist/{chunk-OH3NXXUY.js → chunk-BDGTZRZ6.js} +11 -1
- package/dist/{chunk-P7EO7USC.js → chunk-C7J55C6E.js} +10 -10
- package/dist/{chunk-4EGPFB7V.js → chunk-CAQPSFPT.js} +4753 -744
- package/dist/{chunk-TIH53QXY.js → chunk-CLI4K2CH.js} +1 -1
- package/dist/{chunk-ZKA3TGYS.js → chunk-EEFRPEWT.js} +1 -1
- package/dist/{chunk-TIZ3L6CU.js → chunk-FZ5MBXEG.js} +6 -6
- package/dist/{chunk-IY2TZLY5.js → chunk-GOHWCHCS.js} +1809 -812
- package/dist/{chunk-VG6UK6G6.js → chunk-HRSE6IQD.js} +2 -2
- package/dist/{chunk-67RP7MLR.js → chunk-KBCPELBC.js} +3 -3
- package/dist/{chunk-MQG7FHXX.js → chunk-KI6PHH6Q.js} +3 -3
- package/dist/{chunk-HMMSWRWR.js → chunk-LADPYELQ.js} +23 -3
- package/dist/{chunk-W3PDVZ4I.js → chunk-N55WOGN3.js} +90 -21
- package/dist/{chunk-H2ZJOJ7A.js → chunk-PP6ZRL5T.js} +316 -42
- package/dist/{chunk-B3B64OPD.js → chunk-STESM73J.js} +2 -2
- package/dist/{chunk-WOXR6I3R.js → chunk-UBZQE3JN.js} +2 -2
- package/dist/{chunk-6T5AYG5S.js → chunk-UJFNIWC7.js} +5 -5
- package/dist/{chunk-CXAHK5PJ.js → chunk-UUO4K7NX.js} +9 -9
- package/dist/{chunk-Z2GUITAS.js → chunk-UXPWSV3B.js} +9 -9
- package/dist/{chunk-PARV3G2F.js → chunk-VBNJAKUH.js} +2 -2
- package/dist/{chunk-XTIPZUPR.js → chunk-X4EFYVCZ.js} +2 -2
- package/dist/{ci-workflow-YGTQTNVC.js → ci-workflow-IW2H5DC4.js} +5 -5
- package/dist/{dist-E3SAAHCM.js → dist-ICW4QUEW.js} +5 -1
- package/dist/{dist-XIJWWQ5N.js → dist-JCFK263L.js} +52 -4
- package/dist/{dist-R3TOOPJ7.js → dist-OWHMNL4W.js} +1 -1
- package/dist/{docs-KCTLXK2G.js → docs-7CP6VV42.js} +6 -6
- package/dist/engine-MKJEBWU7.js +13 -0
- package/dist/{entropy-M27JUL3V.js → entropy-6JOZJYG7.js} +5 -5
- package/dist/{feedback-W25QO3OL.js → feedback-T65AEJEG.js} +1 -1
- package/dist/{generate-agent-definitions-RNMU5U5R.js → generate-agent-definitions-4VI4YTO2.js} +6 -6
- package/dist/{glob-helper-EX4YZKZO.js → glob-helper-GLZAURJC.js} +1 -1
- package/dist/{graph-loader-EIFEOUVI.js → graph-loader-HHXN5FLP.js} +1 -1
- package/dist/index.d.ts +627 -6
- package/dist/index.js +27 -27
- package/dist/{loader-HZKJ5ABV.js → loader-AWTHHE3A.js} +5 -5
- package/dist/{mcp-NEAPIDTT.js → mcp-JBIBINO2.js} +17 -17
- package/dist/{performance-3AVQUDRG.js → performance-NCOUDOMW.js} +6 -6
- package/dist/{review-pipeline-KA7SGUXJ.js → review-pipeline-YIC6JPMY.js} +5 -5
- package/dist/{runtime-IP6FVV5M.js → runtime-5MHV4UEE.js} +5 -5
- package/dist/{scan-LYKLM4EQ.js → scan-P7YFKB77.js} +1 -1
- package/dist/{security-EAR4FJWI.js → security-VZPJNIDP.js} +1 -1
- package/dist/{validate-EVH3UXIC.js → validate-7BE4VTFI.js} +6 -6
- package/dist/validate-cross-check-F7GEBRDH.js +13 -0
- package/package.json +7 -7
- package/dist/agents-md-267R2NUJ.js +0 -13
- package/dist/analyzer-VY6DJVOU-WYKV3TTW.js +0 -26
- package/dist/engine-J64WPH5K.js +0 -13
- package/dist/validate-cross-check-SNE3SQOB.js +0 -13
|
@@ -22,8 +22,8 @@ import {
|
|
|
22
22
|
getDefaultRegistry,
|
|
23
23
|
getOutline,
|
|
24
24
|
getParser,
|
|
25
|
-
init_chunk_BTUDWWB4,
|
|
26
25
|
init_chunk_MUWJHO2S,
|
|
26
|
+
init_chunk_S4DTDJ6J,
|
|
27
27
|
parseFile,
|
|
28
28
|
parseSize,
|
|
29
29
|
readFileContent,
|
|
@@ -31,7 +31,7 @@ import {
|
|
|
31
31
|
resetParserCache,
|
|
32
32
|
resolveFileToLayer,
|
|
33
33
|
validateDependencies
|
|
34
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-N55WOGN3.js";
|
|
35
35
|
import {
|
|
36
36
|
AuthAuditEntrySchema,
|
|
37
37
|
AuthTokenPublicSchema,
|
|
@@ -51,6 +51,7 @@ import {
|
|
|
51
51
|
NotificationSinkConfigSchema,
|
|
52
52
|
NotificationSinkKindSchema,
|
|
53
53
|
NotificationsConfigSchema,
|
|
54
|
+
OPTIONAL_STRATEGY_SECTIONS,
|
|
54
55
|
OTLPKeyValueSchema,
|
|
55
56
|
OTLPSpanSchema,
|
|
56
57
|
Ok,
|
|
@@ -62,6 +63,7 @@ import {
|
|
|
62
63
|
ProposalKindSchema,
|
|
63
64
|
ProposalSourceSchema,
|
|
64
65
|
ProposalStatusSchema,
|
|
66
|
+
REQUIRED_STRATEGY_SECTIONS,
|
|
65
67
|
SESSIONS_DEFAULTS,
|
|
66
68
|
SESSION_SECTION_NAMES,
|
|
67
69
|
STANDARD_COGNITIVE_MODES,
|
|
@@ -77,7 +79,7 @@ import {
|
|
|
77
79
|
init_dist,
|
|
78
80
|
isErr,
|
|
79
81
|
isOk
|
|
80
|
-
} from "./chunk-
|
|
82
|
+
} from "./chunk-BDGTZRZ6.js";
|
|
81
83
|
import {
|
|
82
84
|
DEFAULT_SKIP_DIRS,
|
|
83
85
|
GraphStore,
|
|
@@ -85,7 +87,7 @@ import {
|
|
|
85
87
|
init_dist as init_dist2,
|
|
86
88
|
queryTraceability,
|
|
87
89
|
skipDirGlobs
|
|
88
|
-
} from "./chunk-
|
|
90
|
+
} from "./chunk-PP6ZRL5T.js";
|
|
89
91
|
import {
|
|
90
92
|
ArchBaselineSchema,
|
|
91
93
|
ArchConfigSchema,
|
|
@@ -1244,6 +1246,7 @@ var init_chunk_FYTJQ2ZY = __esm({
|
|
|
1244
1246
|
// ../core/dist/index.mjs
|
|
1245
1247
|
var dist_exports = {};
|
|
1246
1248
|
__export(dist_exports, {
|
|
1249
|
+
ADVERSARIAL_DESCRIPTOR: () => ADVERSARIAL_DESCRIPTOR,
|
|
1247
1250
|
AGENT_DESCRIPTORS: () => AGENT_DESCRIPTORS,
|
|
1248
1251
|
AGREEMENT_LINE_GAP: () => AGREEMENT_LINE_GAP,
|
|
1249
1252
|
ALLOWED_FIELD_KEYS: () => ALLOWED_FIELD_KEYS,
|
|
@@ -1271,6 +1274,7 @@ __export(dist_exports, {
|
|
|
1271
1274
|
CACHE_TTL_MS: () => CACHE_TTL_MS,
|
|
1272
1275
|
CINotifier: () => CINotifier,
|
|
1273
1276
|
COMPLIANCE_DESCRIPTOR: () => COMPLIANCE_DESCRIPTOR,
|
|
1277
|
+
CONDITIONAL_SUBAGENT_DESCRIPTORS: () => CONDITIONAL_SUBAGENT_DESCRIPTORS,
|
|
1274
1278
|
CORROBORATED_AGREEMENT: () => CORROBORATED_AGREEMENT,
|
|
1275
1279
|
CacheMetricsRecorder: () => CacheMetricsRecorder,
|
|
1276
1280
|
CategoryBaselineSchema: () => CategoryBaselineSchema,
|
|
@@ -1318,6 +1322,7 @@ __export(dist_exports, {
|
|
|
1318
1322
|
Err: () => Err,
|
|
1319
1323
|
ExclusionSet: () => ExclusionSet,
|
|
1320
1324
|
FACTOR_WEIGHTS: () => FACTOR_WEIGHTS,
|
|
1325
|
+
FRONTEND_RACES_DESCRIPTOR: () => FRONTEND_RACES_DESCRIPTOR,
|
|
1321
1326
|
FailureEntrySchema: () => FailureEntrySchema,
|
|
1322
1327
|
FileSink: () => FileSink,
|
|
1323
1328
|
FindingLifecycleSchema: () => FindingLifecycleSchema,
|
|
@@ -1351,6 +1356,7 @@ __export(dist_exports, {
|
|
|
1351
1356
|
NotificationSinkConfigSchema: () => NotificationSinkConfigSchema,
|
|
1352
1357
|
NotificationSinkKindSchema: () => NotificationSinkKindSchema,
|
|
1353
1358
|
NotificationsConfigSchema: () => NotificationsConfigSchema,
|
|
1359
|
+
OPTIONAL_STRATEGY_SECTIONS: () => OPTIONAL_STRATEGY_SECTIONS,
|
|
1354
1360
|
OTLPExporter: () => OTLPExporter,
|
|
1355
1361
|
OTLPKeyValueSchema: () => OTLPKeyValueSchema,
|
|
1356
1362
|
OTLPSpanSchema: () => OTLPSpanSchema,
|
|
@@ -1382,6 +1388,8 @@ __export(dist_exports, {
|
|
|
1382
1388
|
PulseSourcesSchema: () => PulseSourcesSchema,
|
|
1383
1389
|
QuestionSchema: () => QuestionSchema,
|
|
1384
1390
|
REQUIRED_SECTIONS: () => REQUIRED_SECTIONS,
|
|
1391
|
+
REQUIRED_STRATEGY_SECTIONS: () => REQUIRED_STRATEGY_SECTIONS,
|
|
1392
|
+
RISK_KEYWORDS: () => RISK_KEYWORDS,
|
|
1385
1393
|
RegressionDetector: () => RegressionDetector,
|
|
1386
1394
|
RuleRegistry: () => RuleRegistry,
|
|
1387
1395
|
SECURITY_DESCRIPTOR: () => SECURITY_DESCRIPTOR,
|
|
@@ -1391,6 +1399,7 @@ __export(dist_exports, {
|
|
|
1391
1399
|
STANDALONE_AGREEMENT: () => STANDALONE_AGREEMENT,
|
|
1392
1400
|
STANDARD_COGNITIVE_MODES: () => STANDARD_COGNITIVE_MODES,
|
|
1393
1401
|
STATUS_RANK: () => STATUS_RANK,
|
|
1402
|
+
SUBAGENT_ORDER: () => SUBAGENT_ORDER,
|
|
1394
1403
|
SecurityCategorySnapshotSchema: () => SecurityCategorySnapshotSchema,
|
|
1395
1404
|
SecurityConfigSchema: () => SecurityConfigSchema,
|
|
1396
1405
|
SecurityDirectionSchema: () => DirectionSchema2,
|
|
@@ -1414,10 +1423,13 @@ __export(dist_exports, {
|
|
|
1414
1423
|
SpecImpactEstimator: () => SpecImpactEstimator,
|
|
1415
1424
|
SpecImpactSignalsSchema: () => SpecImpactSignalsSchema,
|
|
1416
1425
|
StabilityForecastSchema: () => StabilityForecastSchema,
|
|
1426
|
+
StrategyDocSchema: () => StrategyDocSchema,
|
|
1427
|
+
StrategyFrontmatterSchema: () => StrategyFrontmatterSchema,
|
|
1417
1428
|
StreamIndexSchema: () => StreamIndexSchema,
|
|
1418
1429
|
StreamInfoSchema: () => StreamInfoSchema,
|
|
1419
1430
|
StructuralStrategy: () => StructuralStrategy,
|
|
1420
1431
|
SupplyChainSnapshotSchema: () => SupplyChainSnapshotSchema,
|
|
1432
|
+
TYPESCRIPT_STRICT_DESCRIPTOR: () => TYPESCRIPT_STRICT_DESCRIPTOR,
|
|
1421
1433
|
ThresholdConfigSchema: () => ThresholdConfigSchema,
|
|
1422
1434
|
TimeToFixResultSchema: () => TimeToFixResultSchema,
|
|
1423
1435
|
TimeToFixStatsSchema: () => TimeToFixStatsSchema,
|
|
@@ -1467,6 +1479,7 @@ __export(dist_exports, {
|
|
|
1467
1479
|
archiveLearnings: () => archiveLearnings,
|
|
1468
1480
|
archiveSession: () => archiveSession,
|
|
1469
1481
|
archiveStream: () => archiveStream,
|
|
1482
|
+
asStrategyDoc: () => asStrategyDoc,
|
|
1470
1483
|
assembleCandidateReport: () => assembleCandidateReport,
|
|
1471
1484
|
assembleReport: () => assembleReport,
|
|
1472
1485
|
assertPortUsable: () => assertPortUsable,
|
|
@@ -1477,6 +1490,7 @@ __export(dist_exports, {
|
|
|
1477
1490
|
buildSnapshot: () => buildSnapshot,
|
|
1478
1491
|
calculateCacheSavings: () => calculateCacheSavings,
|
|
1479
1492
|
calculateCost: () => calculateCost,
|
|
1493
|
+
calibrateDepth: () => calibrateDepth,
|
|
1480
1494
|
checkDocCoverage: () => checkDocCoverage,
|
|
1481
1495
|
checkEligibility: () => checkEligibility,
|
|
1482
1496
|
checkEvidenceCoverage: () => checkEvidenceCoverage,
|
|
@@ -1492,7 +1506,9 @@ __export(dist_exports, {
|
|
|
1492
1506
|
clusterViolations: () => clusterViolations,
|
|
1493
1507
|
collectEvents: () => collectEvents,
|
|
1494
1508
|
composeInsights: () => composeInsights,
|
|
1509
|
+
computeActivations: () => computeActivations,
|
|
1495
1510
|
computeContentHash: () => computeContentHash,
|
|
1511
|
+
computeDepth: () => computeDepth,
|
|
1496
1512
|
computeHotspots: () => computeHotspots,
|
|
1497
1513
|
computeLexicalSimilarity: () => computeLexicalSimilarity,
|
|
1498
1514
|
computeLoadPlan: () => computeLoadPlan,
|
|
@@ -1504,6 +1520,7 @@ __export(dist_exports, {
|
|
|
1504
1520
|
constraintRuleId: () => constraintRuleId,
|
|
1505
1521
|
contextBudget: () => contextBudget,
|
|
1506
1522
|
contextFilter: () => contextFilter,
|
|
1523
|
+
countChangedLines: () => countChangedLines,
|
|
1507
1524
|
countLearningEntries: () => countLearningEntries,
|
|
1508
1525
|
createBoundaryValidator: () => createBoundaryValidator,
|
|
1509
1526
|
createCommentedCodeFixes: () => createCommentedCodeFixes,
|
|
@@ -1537,6 +1554,7 @@ __export(dist_exports, {
|
|
|
1537
1554
|
detectEmergentConstraints: () => detectEmergentConstraints,
|
|
1538
1555
|
detectLanguage: () => detectLanguage,
|
|
1539
1556
|
detectPatternViolations: () => detectPatternViolations,
|
|
1557
|
+
detectRiskKeywords: () => detectRiskKeywords,
|
|
1540
1558
|
detectSizeBudgetViolations: () => detectSizeBudgetViolations,
|
|
1541
1559
|
detectStack: () => detectStack,
|
|
1542
1560
|
detectStaleConstraints: () => detectStaleConstraints,
|
|
@@ -1555,6 +1573,7 @@ __export(dist_exports, {
|
|
|
1555
1573
|
extractLevel: () => extractLevel,
|
|
1556
1574
|
extractMarkdownLinks: () => extractMarkdownLinks,
|
|
1557
1575
|
extractSections: () => extractSections,
|
|
1576
|
+
fanOutConditionalSubagents: () => fanOutConditionalSubagents,
|
|
1558
1577
|
fanOutReview: () => fanOutReview,
|
|
1559
1578
|
findParallelGroups: () => findParallelGroups,
|
|
1560
1579
|
formatCIReportAsMarkdown: () => formatCIReportAsMarkdown,
|
|
@@ -1647,6 +1666,7 @@ __export(dist_exports, {
|
|
|
1647
1666
|
parseSections: () => parseSections,
|
|
1648
1667
|
parseSecurityConfig: () => parseSecurityConfig,
|
|
1649
1668
|
parseSize: () => parseSize,
|
|
1669
|
+
parseStrategyDoc: () => parseStrategyDoc,
|
|
1650
1670
|
pathTraversalRules: () => pathTraversalRules,
|
|
1651
1671
|
previewFix: () => previewFix,
|
|
1652
1672
|
projectValue: () => projectValue,
|
|
@@ -1679,12 +1699,14 @@ __export(dist_exports, {
|
|
|
1679
1699
|
resolveStability: () => resolveStability,
|
|
1680
1700
|
resolveStreamPath: () => resolveStreamPath,
|
|
1681
1701
|
resolveThresholds: () => resolveThresholds,
|
|
1702
|
+
runAdversarialAgent: () => runAdversarialAgent,
|
|
1682
1703
|
runAgentConfigFallbackRules: () => runFallbackRules,
|
|
1683
1704
|
runAll: () => runAll,
|
|
1684
1705
|
runArchitectureAgent: () => runArchitectureAgent,
|
|
1685
1706
|
runBugDetectionAgent: () => runBugDetectionAgent,
|
|
1686
1707
|
runCIChecks: () => runCIChecks,
|
|
1687
1708
|
runComplianceAgent: () => runComplianceAgent,
|
|
1709
|
+
runFrontendRacesAgent: () => runFrontendRacesAgent,
|
|
1688
1710
|
runMechanicalChecks: () => runMechanicalChecks,
|
|
1689
1711
|
runMechanicalGate: () => runMechanicalGate,
|
|
1690
1712
|
runMultiTurnPipeline: () => runMultiTurnPipeline,
|
|
@@ -1692,6 +1714,7 @@ __export(dist_exports, {
|
|
|
1692
1714
|
runPulse: () => runPulse,
|
|
1693
1715
|
runReviewPipeline: () => runReviewPipeline,
|
|
1694
1716
|
runSecurityAgent: () => runSecurityAgent,
|
|
1717
|
+
runTypescriptStrictAgent: () => runTypescriptStrictAgent,
|
|
1695
1718
|
saveHandoff: () => saveHandoff,
|
|
1696
1719
|
saveState: () => saveState,
|
|
1697
1720
|
saveStreamIndex: () => saveStreamIndex,
|
|
@@ -1707,6 +1730,7 @@ __export(dist_exports, {
|
|
|
1707
1730
|
send: () => send,
|
|
1708
1731
|
serializeEnvelope: () => serializeEnvelope,
|
|
1709
1732
|
serializeRoadmap: () => serializeRoadmap,
|
|
1733
|
+
serializeStrategyDoc: () => serializeStrategyDoc,
|
|
1710
1734
|
setActiveStream: () => setActiveStream,
|
|
1711
1735
|
sharpEdgesRules: () => sharpEdgesRules,
|
|
1712
1736
|
shouldRunCheck: () => shouldRunCheck,
|
|
@@ -1741,6 +1765,7 @@ __export(dist_exports, {
|
|
|
1741
1765
|
validatePulseConfig: () => validatePulseConfig,
|
|
1742
1766
|
validateRoadmapMode: () => validateRoadmapMode,
|
|
1743
1767
|
validateSolutionsDir: () => validateSolutionsDir,
|
|
1768
|
+
validateStrategy: () => validateStrategy,
|
|
1744
1769
|
violationId: () => violationId,
|
|
1745
1770
|
weeksUntilThreshold: () => weeksUntilThreshold,
|
|
1746
1771
|
weightedLinearRegression: () => weightedLinearRegression,
|
|
@@ -1748,6 +1773,7 @@ __export(dist_exports, {
|
|
|
1748
1773
|
writeLockfile: () => writeLockfile,
|
|
1749
1774
|
writePulseConfig: () => writePulseConfig,
|
|
1750
1775
|
writeSessionSummary: () => writeSessionSummary,
|
|
1776
|
+
writeStrategyDoc: () => writeStrategyDoc,
|
|
1751
1777
|
writeTaint: () => writeTaint,
|
|
1752
1778
|
xssRules: () => xssRules
|
|
1753
1779
|
});
|
|
@@ -1758,84 +1784,86 @@ import * as fs2 from "fs/promises";
|
|
|
1758
1784
|
import * as path2 from "path";
|
|
1759
1785
|
import matter from "gray-matter";
|
|
1760
1786
|
import { z as z2 } from "zod";
|
|
1787
|
+
import * as fs3 from "fs/promises";
|
|
1788
|
+
import * as path3 from "path";
|
|
1789
|
+
import matter2 from "gray-matter";
|
|
1790
|
+
import { z as z3 } from "zod";
|
|
1761
1791
|
import { spawn } from "child_process";
|
|
1762
1792
|
import { existsSync as existsSync2 } from "fs";
|
|
1763
|
-
import { join as
|
|
1793
|
+
import { join as join42, isAbsolute } from "path";
|
|
1764
1794
|
import { existsSync as existsSync22 } from "fs";
|
|
1765
|
-
import { join as
|
|
1795
|
+
import { join as join52 } from "path";
|
|
1766
1796
|
import { readFileSync as readFileSync2, statSync } from "fs";
|
|
1767
1797
|
import { glob } from "glob";
|
|
1768
1798
|
import { existsSync as existsSync3 } from "fs";
|
|
1769
|
-
import { isAbsolute as isAbsolute2, resolve as resolve2, join as
|
|
1799
|
+
import { isAbsolute as isAbsolute2, resolve as resolve2, join as join62 } from "path";
|
|
1770
1800
|
import { glob as glob2 } from "glob";
|
|
1771
1801
|
import { existsSync as existsSync4 } from "fs";
|
|
1772
|
-
import { basename, dirname as dirname3, join as
|
|
1802
|
+
import { basename, dirname as dirname3, join as join7 } from "path";
|
|
1773
1803
|
import { existsSync as existsSync5 } from "fs";
|
|
1774
|
-
import { join as
|
|
1804
|
+
import { join as join8 } from "path";
|
|
1775
1805
|
import { existsSync as existsSync6 } from "fs";
|
|
1776
|
-
import { join as
|
|
1777
|
-
import { join as
|
|
1806
|
+
import { join as join10 } from "path";
|
|
1807
|
+
import { join as join9, dirname as dirname22 } from "path";
|
|
1778
1808
|
import { glob as glob3 } from "glob";
|
|
1779
1809
|
import { existsSync as existsSync7 } from "fs";
|
|
1780
1810
|
import { dirname as dirname32, resolve as resolve22, isAbsolute as isAbsolute3 } from "path";
|
|
1781
1811
|
import { existsSync as existsSync8 } from "fs";
|
|
1782
|
-
import { join as
|
|
1812
|
+
import { join as join11 } from "path";
|
|
1783
1813
|
import { glob as glob4 } from "glob";
|
|
1784
1814
|
import { existsSync as existsSync9 } from "fs";
|
|
1785
|
-
import { join as join11 } from "path";
|
|
1786
|
-
import { existsSync as existsSync10 } from "fs";
|
|
1787
1815
|
import { join as join12 } from "path";
|
|
1788
|
-
import
|
|
1789
|
-
import
|
|
1816
|
+
import { existsSync as existsSync10 } from "fs";
|
|
1817
|
+
import { join as join13 } from "path";
|
|
1818
|
+
import * as fs4 from "fs";
|
|
1819
|
+
import * as path4 from "path";
|
|
1790
1820
|
import { minimatch } from "minimatch";
|
|
1791
1821
|
import { minimatch as minimatch2 } from "minimatch";
|
|
1792
1822
|
import { basename as basename2 } from "path";
|
|
1793
|
-
import { join as
|
|
1823
|
+
import { join as join15, basename as basename3 } from "path";
|
|
1794
1824
|
import { basename as basename4, dirname as dirname4 } from "path";
|
|
1795
|
-
import { z as z3 } from "zod";
|
|
1796
|
-
import * as fs4 from "fs/promises";
|
|
1797
|
-
import * as fs5 from "fs/promises";
|
|
1798
|
-
import * as fs6 from "fs";
|
|
1799
|
-
import { promisify } from "util";
|
|
1800
|
-
import { dirname as dirname5, basename as basename5, join as join15 } from "path";
|
|
1801
1825
|
import { z as z4 } from "zod";
|
|
1826
|
+
import * as fs5 from "fs/promises";
|
|
1827
|
+
import * as fs6 from "fs/promises";
|
|
1802
1828
|
import * as fs7 from "fs";
|
|
1803
|
-
import
|
|
1804
|
-
import {
|
|
1805
|
-
import {
|
|
1806
|
-
import { execFileSync } from "child_process";
|
|
1829
|
+
import { promisify } from "util";
|
|
1830
|
+
import { dirname as dirname5, basename as basename5, join as join16 } from "path";
|
|
1831
|
+
import { z as z5 } from "zod";
|
|
1807
1832
|
import * as fs8 from "fs";
|
|
1808
1833
|
import * as path5 from "path";
|
|
1809
|
-
import {
|
|
1810
|
-
import { dirname as
|
|
1811
|
-
import {
|
|
1834
|
+
import { readFileSync as readFileSync32, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync13 } from "fs";
|
|
1835
|
+
import { join as join18, dirname as dirname6 } from "path";
|
|
1836
|
+
import { execFileSync } from "child_process";
|
|
1812
1837
|
import * as fs9 from "fs";
|
|
1813
1838
|
import * as path6 from "path";
|
|
1839
|
+
import { appendFileSync, writeFileSync as writeFileSync22, existsSync as existsSync14, mkdirSync as mkdirSync22 } from "fs";
|
|
1840
|
+
import { dirname as dirname7 } from "path";
|
|
1841
|
+
import { z as z6 } from "zod";
|
|
1814
1842
|
import * as fs10 from "fs";
|
|
1815
1843
|
import * as path7 from "path";
|
|
1816
1844
|
import * as fs11 from "fs";
|
|
1817
1845
|
import * as path8 from "path";
|
|
1818
|
-
import { z as z6 } from "zod";
|
|
1819
|
-
import * as fs15 from "fs";
|
|
1820
|
-
import * as path12 from "path";
|
|
1821
|
-
import * as fs14 from "fs";
|
|
1822
|
-
import * as path11 from "path";
|
|
1823
1846
|
import * as fs12 from "fs";
|
|
1824
1847
|
import * as path9 from "path";
|
|
1825
|
-
import { execSync } from "child_process";
|
|
1826
1848
|
import { z as z7 } from "zod";
|
|
1827
|
-
import * as fs13 from "fs";
|
|
1828
|
-
import * as path10 from "path";
|
|
1829
1849
|
import * as fs16 from "fs";
|
|
1830
1850
|
import * as path13 from "path";
|
|
1831
|
-
import * as
|
|
1851
|
+
import * as fs15 from "fs";
|
|
1852
|
+
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
|
+
import * as fs14 from "fs";
|
|
1858
|
+
import * as path11 from "path";
|
|
1832
1859
|
import * as fs17 from "fs";
|
|
1833
1860
|
import * as path14 from "path";
|
|
1861
|
+
import * as crypto2 from "crypto";
|
|
1834
1862
|
import * as fs18 from "fs";
|
|
1835
1863
|
import * as path15 from "path";
|
|
1836
|
-
import * as crypto3 from "crypto";
|
|
1837
1864
|
import * as fs19 from "fs";
|
|
1838
1865
|
import * as path16 from "path";
|
|
1866
|
+
import * as crypto3 from "crypto";
|
|
1839
1867
|
import * as fs20 from "fs";
|
|
1840
1868
|
import * as path17 from "path";
|
|
1841
1869
|
import * as fs21 from "fs";
|
|
@@ -1844,90 +1872,94 @@ import * as fs22 from "fs";
|
|
|
1844
1872
|
import * as path19 from "path";
|
|
1845
1873
|
import * as fs23 from "fs";
|
|
1846
1874
|
import * as path20 from "path";
|
|
1847
|
-
import { execSync as execSync2 } from "child_process";
|
|
1848
1875
|
import * as fs24 from "fs";
|
|
1849
1876
|
import * as path21 from "path";
|
|
1877
|
+
import { execSync as execSync2 } from "child_process";
|
|
1850
1878
|
import * as fs25 from "fs";
|
|
1851
1879
|
import * as path22 from "path";
|
|
1852
1880
|
import * as fs26 from "fs";
|
|
1853
1881
|
import * as path23 from "path";
|
|
1854
1882
|
import * as fs27 from "fs";
|
|
1855
1883
|
import * as path24 from "path";
|
|
1856
|
-
import { z as z8 } from "zod";
|
|
1857
|
-
import * as fs29 from "fs/promises";
|
|
1858
|
-
import { minimatch as minimatch3 } from "minimatch";
|
|
1859
|
-
import { z as z9 } from "zod";
|
|
1860
1884
|
import * as fs28 from "fs";
|
|
1861
1885
|
import * as path25 from "path";
|
|
1862
|
-
import
|
|
1886
|
+
import { z as z9 } from "zod";
|
|
1887
|
+
import * as fs30 from "fs/promises";
|
|
1888
|
+
import { minimatch as minimatch3 } from "minimatch";
|
|
1889
|
+
import { z as z10 } from "zod";
|
|
1890
|
+
import * as fs29 from "fs";
|
|
1863
1891
|
import * as path26 from "path";
|
|
1892
|
+
import * as fs31 from "fs";
|
|
1893
|
+
import * as path27 from "path";
|
|
1864
1894
|
import { readFileSync as readFileSync22, writeFileSync as writeFileSync14, unlinkSync as unlinkSync2, mkdirSync as mkdirSync13, readdirSync as readdirSync3 } from "fs";
|
|
1865
|
-
import { join as
|
|
1895
|
+
import { join as join39, dirname as dirname10 } from "path";
|
|
1866
1896
|
import { createHash as createHash32 } from "crypto";
|
|
1867
|
-
import { z as
|
|
1897
|
+
import { z as z11 } from "zod";
|
|
1868
1898
|
import { readFileSync as readFileSync23, writeFileSync as writeFileSync15, renameSync as renameSync72, mkdirSync as mkdirSync14, existsSync as existsSync342 } from "fs";
|
|
1869
1899
|
import { execSync as execSync3 } from "child_process";
|
|
1870
1900
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
1871
|
-
import { isAbsolute as isAbsolute4, join as
|
|
1872
|
-
import * as path27 from "path";
|
|
1901
|
+
import { isAbsolute as isAbsolute4, join as join40, relative as relative3, dirname as dirname11 } from "path";
|
|
1873
1902
|
import * as path28 from "path";
|
|
1874
1903
|
import * as path29 from "path";
|
|
1875
1904
|
import * as path30 from "path";
|
|
1876
|
-
import * as fs31 from "fs";
|
|
1877
1905
|
import * as path31 from "path";
|
|
1878
1906
|
import * as fs32 from "fs";
|
|
1879
1907
|
import * as path32 from "path";
|
|
1880
1908
|
import * as fs33 from "fs";
|
|
1881
|
-
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
1882
|
-
import * as fs34 from "fs";
|
|
1883
1909
|
import * as path33 from "path";
|
|
1910
|
+
import * as fs34 from "fs";
|
|
1911
|
+
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
1884
1912
|
import * as fs35 from "fs";
|
|
1885
1913
|
import * as path34 from "path";
|
|
1886
|
-
import
|
|
1914
|
+
import * as fs36 from "fs";
|
|
1887
1915
|
import * as path35 from "path";
|
|
1888
|
-
import {
|
|
1889
|
-
import * as fs36 from "fs/promises";
|
|
1916
|
+
import { createHash as createHash4 } from "crypto";
|
|
1890
1917
|
import * as path36 from "path";
|
|
1918
|
+
import { z as z12 } from "zod";
|
|
1891
1919
|
import * as fs37 from "fs/promises";
|
|
1892
1920
|
import * as path37 from "path";
|
|
1893
|
-
import * as
|
|
1894
|
-
import * as fs38 from "fs";
|
|
1921
|
+
import * as fs38 from "fs/promises";
|
|
1895
1922
|
import * as path38 from "path";
|
|
1923
|
+
import * as ejs from "ejs";
|
|
1924
|
+
import * as fs39 from "fs";
|
|
1925
|
+
import * as path39 from "path";
|
|
1896
1926
|
import * as os from "os";
|
|
1897
1927
|
import { spawn as spawn2 } from "child_process";
|
|
1898
|
-
import * as
|
|
1899
|
-
import * as path39 from "path";
|
|
1900
|
-
import * as fs40 from "fs";
|
|
1928
|
+
import * as fs40 from "fs/promises";
|
|
1901
1929
|
import * as path40 from "path";
|
|
1902
1930
|
import * as fs41 from "fs";
|
|
1903
1931
|
import * as path41 from "path";
|
|
1904
|
-
import * as os2 from "os";
|
|
1905
1932
|
import * as fs42 from "fs";
|
|
1906
1933
|
import * as path42 from "path";
|
|
1934
|
+
import * as os2 from "os";
|
|
1907
1935
|
import * as fs43 from "fs";
|
|
1908
1936
|
import * as path43 from "path";
|
|
1909
|
-
import * as fs45 from "fs";
|
|
1910
|
-
import * as path45 from "path";
|
|
1911
|
-
import { execFileSync as execFileSync2 } from "child_process";
|
|
1912
1937
|
import * as fs44 from "fs";
|
|
1913
1938
|
import * as path44 from "path";
|
|
1914
|
-
import * as crypto4 from "crypto";
|
|
1915
1939
|
import * as fs46 from "fs";
|
|
1916
1940
|
import * as path46 from "path";
|
|
1941
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
1942
|
+
import * as fs45 from "fs";
|
|
1943
|
+
import * as path45 from "path";
|
|
1944
|
+
import * as crypto4 from "crypto";
|
|
1917
1945
|
import * as fs47 from "fs";
|
|
1918
|
-
import * as fs48 from "fs";
|
|
1919
1946
|
import * as path47 from "path";
|
|
1947
|
+
import * as fs48 from "fs";
|
|
1948
|
+
import * as fs49 from "fs";
|
|
1949
|
+
import * as path48 from "path";
|
|
1920
1950
|
import { readFileSync as readFileSync392 } from "fs";
|
|
1921
1951
|
import { fileURLToPath } from "url";
|
|
1922
|
-
import { dirname as dirname16, join as
|
|
1952
|
+
import { dirname as dirname16, join as join622 } from "path";
|
|
1923
1953
|
import { execFile } from "child_process";
|
|
1924
1954
|
import { promisify as promisify2 } from "util";
|
|
1925
1955
|
import { execFile as execFile2 } from "child_process";
|
|
1926
1956
|
import { promisify as promisify3 } from "util";
|
|
1927
|
-
import * as
|
|
1928
|
-
import * as path48 from "path";
|
|
1929
|
-
import * as fs50 from "fs";
|
|
1957
|
+
import * as fs50 from "fs/promises";
|
|
1930
1958
|
import * as path49 from "path";
|
|
1959
|
+
import * as fs51 from "fs";
|
|
1960
|
+
import * as path50 from "path";
|
|
1961
|
+
import * as fs52 from "fs";
|
|
1962
|
+
import * as path51 from "path";
|
|
1931
1963
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
1932
1964
|
function isBadPort(port) {
|
|
1933
1965
|
return BAD_PORT_SET.has(port);
|
|
@@ -1974,15 +2006,15 @@ function validateConfig(data, schema) {
|
|
|
1974
2006
|
let message = "Configuration validation failed";
|
|
1975
2007
|
const suggestions = [];
|
|
1976
2008
|
if (firstError) {
|
|
1977
|
-
const
|
|
1978
|
-
const pathDisplay =
|
|
2009
|
+
const path52 = firstError.path.join(".");
|
|
2010
|
+
const pathDisplay = path52 ? ` at "${path52}"` : "";
|
|
1979
2011
|
if (firstError.code === "invalid_type") {
|
|
1980
2012
|
const received = firstError.received;
|
|
1981
2013
|
const expected = firstError.expected;
|
|
1982
2014
|
if (received === "undefined") {
|
|
1983
2015
|
code = "MISSING_FIELD";
|
|
1984
2016
|
message = `Missing required field${pathDisplay}: ${firstError.message}`;
|
|
1985
|
-
suggestions.push(`Field "${
|
|
2017
|
+
suggestions.push(`Field "${path52}" is required and must be of type "${expected}"`);
|
|
1986
2018
|
} else {
|
|
1987
2019
|
code = "INVALID_TYPE";
|
|
1988
2020
|
message = `Invalid type${pathDisplay}: ${firstError.message}`;
|
|
@@ -2118,23 +2150,127 @@ async function validatePulseConfig(cwd) {
|
|
|
2118
2150
|
if (!result.ok) return Err(result.error);
|
|
2119
2151
|
return Ok({ present: true, valid: true });
|
|
2120
2152
|
}
|
|
2153
|
+
function coerceFrontmatter(raw) {
|
|
2154
|
+
if (raw === null || typeof raw !== "object") return raw;
|
|
2155
|
+
const out = { ...raw };
|
|
2156
|
+
const lu = out.last_updated;
|
|
2157
|
+
if (lu instanceof Date && !Number.isNaN(lu.getTime())) {
|
|
2158
|
+
const iso = lu.toISOString();
|
|
2159
|
+
out.last_updated = iso.slice(0, 10);
|
|
2160
|
+
}
|
|
2161
|
+
return out;
|
|
2162
|
+
}
|
|
2163
|
+
function findH2Matches(body) {
|
|
2164
|
+
const h2Re = /^##[ \t]+(.+?)[ \t]*$/gm;
|
|
2165
|
+
const matches = [];
|
|
2166
|
+
let m;
|
|
2167
|
+
while ((m = h2Re.exec(body)) !== null) {
|
|
2168
|
+
matches.push({
|
|
2169
|
+
name: (m[1] ?? "").trim(),
|
|
2170
|
+
headingStart: m.index,
|
|
2171
|
+
bodyStart: m.index + m[0].length
|
|
2172
|
+
});
|
|
2173
|
+
}
|
|
2174
|
+
return matches;
|
|
2175
|
+
}
|
|
2176
|
+
function accumulateSection(acc, name, body) {
|
|
2177
|
+
if (KNOWN_SECTION_NAMES.has(name)) {
|
|
2178
|
+
acc.sections.push({ name, body });
|
|
2179
|
+
} else {
|
|
2180
|
+
acc.unknownSectionNames.push(name);
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2183
|
+
function buildSections(body, matches) {
|
|
2184
|
+
const acc = { sections: [], unknownSectionNames: [] };
|
|
2185
|
+
for (let i = 0; i < matches.length; i++) {
|
|
2186
|
+
const current = matches[i];
|
|
2187
|
+
if (current === void 0) continue;
|
|
2188
|
+
const sliceEnd = matches[i + 1]?.headingStart ?? body.length;
|
|
2189
|
+
const sectionBody = body.slice(current.bodyStart, sliceEnd).trim();
|
|
2190
|
+
accumulateSection(acc, current.name, sectionBody);
|
|
2191
|
+
}
|
|
2192
|
+
return acc;
|
|
2193
|
+
}
|
|
2194
|
+
function parseStrategyDoc(raw) {
|
|
2195
|
+
const parsed = matter(raw);
|
|
2196
|
+
const body = parsed.content;
|
|
2197
|
+
const frontmatter = coerceFrontmatter(parsed.data);
|
|
2198
|
+
const matches = findH2Matches(body);
|
|
2199
|
+
const { sections, unknownSectionNames } = buildSections(body, matches);
|
|
2200
|
+
return { frontmatter, sections, unknownSectionNames };
|
|
2201
|
+
}
|
|
2202
|
+
function asStrategyDoc(parsed) {
|
|
2203
|
+
const fm = parsed.frontmatter;
|
|
2204
|
+
if (fm === null || typeof fm !== "object" || typeof fm.name !== "string" || typeof fm.last_updated !== "string" || typeof fm.version !== "number") {
|
|
2205
|
+
return null;
|
|
2206
|
+
}
|
|
2207
|
+
return {
|
|
2208
|
+
frontmatter: {
|
|
2209
|
+
name: fm.name,
|
|
2210
|
+
last_updated: fm.last_updated,
|
|
2211
|
+
version: fm.version
|
|
2212
|
+
},
|
|
2213
|
+
sections: parsed.sections
|
|
2214
|
+
};
|
|
2215
|
+
}
|
|
2216
|
+
function nonBlankLines(body) {
|
|
2217
|
+
return body.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
|
|
2218
|
+
}
|
|
2219
|
+
function bodyContentIssue(body) {
|
|
2220
|
+
const lines = nonBlankLines(body);
|
|
2221
|
+
if (lines.length === 0) {
|
|
2222
|
+
return "section is empty";
|
|
2223
|
+
}
|
|
2224
|
+
for (const line of lines) {
|
|
2225
|
+
if (PLACEHOLDER_INLINE.test(line)) {
|
|
2226
|
+
return `unfilled template placeholder detected (${line})`;
|
|
2227
|
+
}
|
|
2228
|
+
}
|
|
2229
|
+
return null;
|
|
2230
|
+
}
|
|
2231
|
+
async function validateStrategy(cwd) {
|
|
2232
|
+
const strategyPath = path2.join(cwd, "STRATEGY.md");
|
|
2233
|
+
let raw;
|
|
2234
|
+
try {
|
|
2235
|
+
raw = await fs2.readFile(strategyPath, "utf-8");
|
|
2236
|
+
} catch {
|
|
2237
|
+
return Ok({ present: false, valid: true });
|
|
2238
|
+
}
|
|
2239
|
+
const parsed = parseStrategyDoc(raw);
|
|
2240
|
+
const doc = asStrategyDoc(parsed);
|
|
2241
|
+
if (doc === null) {
|
|
2242
|
+
return Err(
|
|
2243
|
+
createError(
|
|
2244
|
+
"VALIDATION_FAILED",
|
|
2245
|
+
"STRATEGY.md frontmatter is missing required fields (name, last_updated, version)",
|
|
2246
|
+
{},
|
|
2247
|
+
[
|
|
2248
|
+
"Add YAML frontmatter at the top of STRATEGY.md with name (string), last_updated (YYYY-MM-DD), and version (positive integer)"
|
|
2249
|
+
]
|
|
2250
|
+
)
|
|
2251
|
+
);
|
|
2252
|
+
}
|
|
2253
|
+
const result = validateConfig(doc, StrategyDocSchema);
|
|
2254
|
+
if (!result.ok) return Err(result.error);
|
|
2255
|
+
return Ok({ present: true, valid: true });
|
|
2256
|
+
}
|
|
2121
2257
|
async function* walk(dir) {
|
|
2122
2258
|
let entries;
|
|
2123
2259
|
try {
|
|
2124
|
-
entries = await
|
|
2260
|
+
entries = await fs3.readdir(dir, { withFileTypes: true });
|
|
2125
2261
|
} catch {
|
|
2126
2262
|
return;
|
|
2127
2263
|
}
|
|
2128
2264
|
for (const e of entries) {
|
|
2129
|
-
const p =
|
|
2265
|
+
const p = path3.join(dir, e.name);
|
|
2130
2266
|
if (e.isDirectory()) yield* walk(p);
|
|
2131
2267
|
else if (e.isFile() && e.name.endsWith(".md")) yield p;
|
|
2132
2268
|
}
|
|
2133
2269
|
}
|
|
2134
2270
|
async function validateSolutionsDir(cwd) {
|
|
2135
|
-
const root =
|
|
2271
|
+
const root = path3.join(cwd, "docs", "solutions");
|
|
2136
2272
|
try {
|
|
2137
|
-
await
|
|
2273
|
+
await fs3.stat(root);
|
|
2138
2274
|
} catch {
|
|
2139
2275
|
return Ok({ filesChecked: 0, issues: [] });
|
|
2140
2276
|
}
|
|
@@ -2142,15 +2278,15 @@ async function validateSolutionsDir(cwd) {
|
|
|
2142
2278
|
let count = 0;
|
|
2143
2279
|
for (const [track, categories] of TRACK_CATEGORIES) {
|
|
2144
2280
|
for (const category of categories) {
|
|
2145
|
-
const categoryDir =
|
|
2281
|
+
const categoryDir = path3.join(root, track, category);
|
|
2146
2282
|
for await (const file of walk(categoryDir)) {
|
|
2147
2283
|
count++;
|
|
2148
|
-
const raw = await
|
|
2149
|
-
const parsed =
|
|
2284
|
+
const raw = await fs3.readFile(file, "utf-8");
|
|
2285
|
+
const parsed = matter2(raw);
|
|
2150
2286
|
const result = validateConfig(parsed.data, SolutionDocFrontmatterSchema);
|
|
2151
2287
|
if (!result.ok)
|
|
2152
2288
|
issues.push({
|
|
2153
|
-
file:
|
|
2289
|
+
file: path3.relative(cwd, file).replaceAll("\\", "/"),
|
|
2154
2290
|
message: result.error.message
|
|
2155
2291
|
});
|
|
2156
2292
|
}
|
|
@@ -2188,7 +2324,7 @@ function lookupOnPath() {
|
|
|
2188
2324
|
function firstExistingIn(dir, candidates) {
|
|
2189
2325
|
if (!dir) return null;
|
|
2190
2326
|
for (const name of candidates) {
|
|
2191
|
-
const candidate = isAbsolute(dir) ?
|
|
2327
|
+
const candidate = isAbsolute(dir) ? join42(dir, name) : join42(process.cwd(), dir, name);
|
|
2192
2328
|
if (existsSync2(candidate)) return candidate;
|
|
2193
2329
|
}
|
|
2194
2330
|
return null;
|
|
@@ -2265,7 +2401,7 @@ function extractDiagnostics(parsed) {
|
|
|
2265
2401
|
}
|
|
2266
2402
|
function normalizeDiagnostic(diag, cwd) {
|
|
2267
2403
|
const finding = {
|
|
2268
|
-
file: normalizeFilePath(
|
|
2404
|
+
file: normalizeFilePath(readFile42(diag), cwd),
|
|
2269
2405
|
ruleId: readRuleId(diag),
|
|
2270
2406
|
severity: normalizeSeverity(readSeverity(diag)),
|
|
2271
2407
|
message: diag.message ?? "agnix diagnostic"
|
|
@@ -2273,7 +2409,7 @@ function normalizeDiagnostic(diag, cwd) {
|
|
|
2273
2409
|
attachOptionalFields(finding, diag);
|
|
2274
2410
|
return finding;
|
|
2275
2411
|
}
|
|
2276
|
-
function
|
|
2412
|
+
function readFile42(diag) {
|
|
2277
2413
|
return diag.file ?? diag.path ?? "";
|
|
2278
2414
|
}
|
|
2279
2415
|
function readRuleId(diag) {
|
|
@@ -2316,16 +2452,16 @@ function makeFinding(input) {
|
|
|
2316
2452
|
if (input.suggestion) finding.suggestion = input.suggestion;
|
|
2317
2453
|
return finding;
|
|
2318
2454
|
}
|
|
2319
|
-
function readTextSafe(
|
|
2455
|
+
function readTextSafe(path52) {
|
|
2320
2456
|
try {
|
|
2321
|
-
return readFileSync2(
|
|
2457
|
+
return readFileSync2(path52, "utf-8");
|
|
2322
2458
|
} catch {
|
|
2323
2459
|
return null;
|
|
2324
2460
|
}
|
|
2325
2461
|
}
|
|
2326
|
-
function safeFileSize(
|
|
2462
|
+
function safeFileSize(path52) {
|
|
2327
2463
|
try {
|
|
2328
|
-
return statSync(
|
|
2464
|
+
return statSync(path52).size;
|
|
2329
2465
|
} catch {
|
|
2330
2466
|
return null;
|
|
2331
2467
|
}
|
|
@@ -2362,7 +2498,7 @@ function stripQuotes(value) {
|
|
|
2362
2498
|
function runClaudeMdRules(cwd) {
|
|
2363
2499
|
const findings = [];
|
|
2364
2500
|
for (const name of CONTEXT_FILES) {
|
|
2365
|
-
const abs =
|
|
2501
|
+
const abs = join52(cwd, name);
|
|
2366
2502
|
if (!existsSync22(abs)) continue;
|
|
2367
2503
|
findings.push(...inspectContextFile(abs, name));
|
|
2368
2504
|
}
|
|
@@ -2509,7 +2645,7 @@ function appendDescriptionFindings(out, rel, description) {
|
|
|
2509
2645
|
function runHookRules(cwd) {
|
|
2510
2646
|
const findings = [];
|
|
2511
2647
|
for (const rel of SETTINGS_PATHS) {
|
|
2512
|
-
const abs =
|
|
2648
|
+
const abs = join62(cwd, rel);
|
|
2513
2649
|
if (!existsSync3(abs)) continue;
|
|
2514
2650
|
const content = readTextSafe(abs);
|
|
2515
2651
|
if (content === null) continue;
|
|
@@ -2625,7 +2761,7 @@ async function runSkillRules(cwd) {
|
|
|
2625
2761
|
for (const abs of skillFiles) {
|
|
2626
2762
|
const rel = relPath(cwd, abs);
|
|
2627
2763
|
const dirName = basename(dirname3(abs));
|
|
2628
|
-
const siblingYaml =
|
|
2764
|
+
const siblingYaml = join7(dirname3(abs), "skill.yaml");
|
|
2629
2765
|
if (existsSync4(siblingYaml)) {
|
|
2630
2766
|
const yamlFields = parseFrontmatterFields(readTextSafe(siblingYaml) ?? "");
|
|
2631
2767
|
findings.push(...checkSkillMetadata(relPath(cwd, siblingYaml), dirName, yamlFields));
|
|
@@ -2731,7 +2867,7 @@ function runMcpRules(cwd) {
|
|
|
2731
2867
|
return findings;
|
|
2732
2868
|
}
|
|
2733
2869
|
function loadMcpServers(cwd, rel) {
|
|
2734
|
-
const abs =
|
|
2870
|
+
const abs = join8(cwd, rel);
|
|
2735
2871
|
if (!existsSync5(abs)) return null;
|
|
2736
2872
|
const content = readTextSafe(abs);
|
|
2737
2873
|
if (content === null) return null;
|
|
@@ -2870,27 +3006,27 @@ function extractSections(content) {
|
|
|
2870
3006
|
}
|
|
2871
3007
|
return sections.map((section) => buildAgentMapSection(section, lines));
|
|
2872
3008
|
}
|
|
2873
|
-
function isExternalLink(
|
|
2874
|
-
return
|
|
3009
|
+
function isExternalLink(path52) {
|
|
3010
|
+
return path52.startsWith("http://") || path52.startsWith("https://") || path52.startsWith("#") || path52.startsWith("mailto:");
|
|
2875
3011
|
}
|
|
2876
3012
|
function resolveLinkPath(linkPath, baseDir) {
|
|
2877
|
-
return linkPath.startsWith(".") ?
|
|
3013
|
+
return linkPath.startsWith(".") ? join9(baseDir, linkPath) : linkPath;
|
|
2878
3014
|
}
|
|
2879
|
-
async function validateAgentsMap(
|
|
2880
|
-
const contentResult = await readFileContent(
|
|
3015
|
+
async function validateAgentsMap(path52 = "./AGENTS.md") {
|
|
3016
|
+
const contentResult = await readFileContent(path52);
|
|
2881
3017
|
if (!contentResult.ok) {
|
|
2882
3018
|
return Err(
|
|
2883
3019
|
createError(
|
|
2884
3020
|
"PARSE_ERROR",
|
|
2885
3021
|
`Failed to read AGENTS.md: ${contentResult.error.message}`,
|
|
2886
|
-
{ path:
|
|
3022
|
+
{ path: path52 },
|
|
2887
3023
|
["Ensure the file exists", "Check file permissions"]
|
|
2888
3024
|
)
|
|
2889
3025
|
);
|
|
2890
3026
|
}
|
|
2891
3027
|
const content = contentResult.value;
|
|
2892
3028
|
const sections = extractSections(content);
|
|
2893
|
-
const baseDir = dirname22(
|
|
3029
|
+
const baseDir = dirname22(path52);
|
|
2894
3030
|
const sectionTitles = sections.map((s) => s.title);
|
|
2895
3031
|
const missingSections = REQUIRED_SECTIONS.filter(
|
|
2896
3032
|
(required) => !sectionTitles.some((title) => title.toLowerCase().includes(required.toLowerCase()))
|
|
@@ -2929,7 +3065,7 @@ async function validateAgentsMap(path50 = "./AGENTS.md") {
|
|
|
2929
3065
|
});
|
|
2930
3066
|
}
|
|
2931
3067
|
async function runAgentsMdRules(cwd) {
|
|
2932
|
-
const abs =
|
|
3068
|
+
const abs = join10(cwd, "AGENTS.md");
|
|
2933
3069
|
if (!existsSync6(abs)) return [];
|
|
2934
3070
|
const result = await validateAgentsMap(abs);
|
|
2935
3071
|
if (result.ok) return [];
|
|
@@ -2986,7 +3122,7 @@ function extractLinkTargets(content) {
|
|
|
2986
3122
|
function runSettingsJsonRules(cwd) {
|
|
2987
3123
|
const findings = [];
|
|
2988
3124
|
for (const rel of SETTINGS_FILES) {
|
|
2989
|
-
const abs =
|
|
3125
|
+
const abs = join11(cwd, rel);
|
|
2990
3126
|
if (!existsSync8(abs)) continue;
|
|
2991
3127
|
const content = readTextSafe(abs);
|
|
2992
3128
|
if (content === null) continue;
|
|
@@ -3062,15 +3198,15 @@ function stripQuotes2(value) {
|
|
|
3062
3198
|
return value;
|
|
3063
3199
|
}
|
|
3064
3200
|
function skillExists(cwd, slug) {
|
|
3065
|
-
if (existsSync9(
|
|
3201
|
+
if (existsSync9(join12(cwd, "agents", "skills", slug, "SKILL.md"))) return true;
|
|
3066
3202
|
for (const platform of ["claude-code", "gemini-cli", "codex", "cursor", "shared"]) {
|
|
3067
|
-
if (existsSync9(
|
|
3203
|
+
if (existsSync9(join12(cwd, "agents", "skills", platform, slug, "SKILL.md"))) return true;
|
|
3068
3204
|
}
|
|
3069
3205
|
return false;
|
|
3070
3206
|
}
|
|
3071
3207
|
function runAgnixTomlRules(cwd) {
|
|
3072
3208
|
const rel = ".agnix.toml";
|
|
3073
|
-
const abs =
|
|
3209
|
+
const abs = join13(cwd, rel);
|
|
3074
3210
|
if (!existsSync10(abs)) return [];
|
|
3075
3211
|
const content = readTextSafe(abs);
|
|
3076
3212
|
if (content === null) return [];
|
|
@@ -3240,7 +3376,7 @@ function getRoadmapMode(config) {
|
|
|
3240
3376
|
return mode === "file-less" ? "file-less" : "file-backed";
|
|
3241
3377
|
}
|
|
3242
3378
|
function validateRoadmapMode(config, projectRoot) {
|
|
3243
|
-
const root =
|
|
3379
|
+
const root = path4.resolve(projectRoot);
|
|
3244
3380
|
const mode = getRoadmapMode(config);
|
|
3245
3381
|
if (mode === "file-backed") return Ok(void 0);
|
|
3246
3382
|
const tracker = config.roadmap?.tracker;
|
|
@@ -3257,8 +3393,8 @@ function validateRoadmapMode(config, projectRoot) {
|
|
|
3257
3393
|
)
|
|
3258
3394
|
);
|
|
3259
3395
|
}
|
|
3260
|
-
const roadmapPath =
|
|
3261
|
-
if (
|
|
3396
|
+
const roadmapPath = path4.join(root, "docs", "roadmap.md");
|
|
3397
|
+
if (fs4.existsSync(roadmapPath)) {
|
|
3262
3398
|
return Err(
|
|
3263
3399
|
createError(
|
|
3264
3400
|
"ROADMAP_MODE_FILE_PRESENT",
|
|
@@ -3441,8 +3577,8 @@ async function checkDocCoverage(domain, options = {}) {
|
|
|
3441
3577
|
);
|
|
3442
3578
|
}
|
|
3443
3579
|
}
|
|
3444
|
-
function suggestFix(
|
|
3445
|
-
const targetName = basename3(
|
|
3580
|
+
function suggestFix(path52, existingFiles) {
|
|
3581
|
+
const targetName = basename3(path52).toLowerCase();
|
|
3446
3582
|
const similar = existingFiles.find((file) => {
|
|
3447
3583
|
const fileName = basename3(file).toLowerCase();
|
|
3448
3584
|
return fileName.includes(targetName) || targetName.includes(fileName);
|
|
@@ -3450,10 +3586,10 @@ function suggestFix(path50, existingFiles) {
|
|
|
3450
3586
|
if (similar) {
|
|
3451
3587
|
return `Did you mean "${similar}"?`;
|
|
3452
3588
|
}
|
|
3453
|
-
return `Create the file "${
|
|
3589
|
+
return `Create the file "${path52}" or remove the link`;
|
|
3454
3590
|
}
|
|
3455
3591
|
async function validateKnowledgeMap(rootDir = process.cwd()) {
|
|
3456
|
-
const agentsPath =
|
|
3592
|
+
const agentsPath = join15(rootDir, "AGENTS.md");
|
|
3457
3593
|
const agentsResult = await validateAgentsMap(agentsPath);
|
|
3458
3594
|
if (!agentsResult.ok) {
|
|
3459
3595
|
return Err(agentsResult.error);
|
|
@@ -3808,8 +3944,8 @@ function createBoundaryValidator(schema, name) {
|
|
|
3808
3944
|
return Ok(result.data);
|
|
3809
3945
|
}
|
|
3810
3946
|
const suggestions = result.error.issues.map((issue) => {
|
|
3811
|
-
const
|
|
3812
|
-
return
|
|
3947
|
+
const path52 = issue.path.join(".");
|
|
3948
|
+
return path52 ? `${path52}: ${issue.message}` : issue.message;
|
|
3813
3949
|
});
|
|
3814
3950
|
return Err(
|
|
3815
3951
|
createError(
|
|
@@ -3855,7 +3991,7 @@ function validateBoundaries(boundaries, data) {
|
|
|
3855
3991
|
async function writeConfig(filePath, content) {
|
|
3856
3992
|
try {
|
|
3857
3993
|
const json = JSON.stringify(content, null, 2) + "\n";
|
|
3858
|
-
await
|
|
3994
|
+
await fs5.writeFile(filePath, json, "utf-8");
|
|
3859
3995
|
return Ok(void 0);
|
|
3860
3996
|
} catch (error) {
|
|
3861
3997
|
return Err(error instanceof Error ? error : new Error(String(error)));
|
|
@@ -4129,7 +4265,7 @@ function deepMergeConstraints(localConfig, bundleConstraints, _existingContribut
|
|
|
4129
4265
|
async function readLockfile(lockfilePath) {
|
|
4130
4266
|
let raw;
|
|
4131
4267
|
try {
|
|
4132
|
-
raw = await
|
|
4268
|
+
raw = await fs6.readFile(lockfilePath, "utf-8");
|
|
4133
4269
|
} catch (err) {
|
|
4134
4270
|
if (isNodeError(err) && err.code === "ENOENT") {
|
|
4135
4271
|
return { ok: true, value: null };
|
|
@@ -4507,7 +4643,7 @@ function previewFix(fix) {
|
|
|
4507
4643
|
}
|
|
4508
4644
|
}
|
|
4509
4645
|
async function createBackup(filePath, backupDir) {
|
|
4510
|
-
const backupPath =
|
|
4646
|
+
const backupPath = join16(backupDir, `${Date.now()}-${basename5(filePath)}`);
|
|
4511
4647
|
try {
|
|
4512
4648
|
await mkdir22(dirname5(backupPath), { recursive: true });
|
|
4513
4649
|
await copyFile2(filePath, backupPath);
|
|
@@ -4533,7 +4669,7 @@ async function applyDeleteFile(fix, config) {
|
|
|
4533
4669
|
}
|
|
4534
4670
|
async function applyDeleteLines(fix) {
|
|
4535
4671
|
if (fix.line !== void 0) {
|
|
4536
|
-
const content = await
|
|
4672
|
+
const content = await readFile72(fix.file, "utf-8");
|
|
4537
4673
|
const lines = content.split("\n");
|
|
4538
4674
|
lines.splice(fix.line - 1, 1);
|
|
4539
4675
|
await writeFile32(fix.file, lines.join("\n"));
|
|
@@ -4541,13 +4677,13 @@ async function applyDeleteLines(fix) {
|
|
|
4541
4677
|
}
|
|
4542
4678
|
async function applyReplace(fix) {
|
|
4543
4679
|
if (fix.oldContent && fix.newContent !== void 0) {
|
|
4544
|
-
const content = await
|
|
4680
|
+
const content = await readFile72(fix.file, "utf-8");
|
|
4545
4681
|
await writeFile32(fix.file, content.replace(fix.oldContent, fix.newContent));
|
|
4546
4682
|
}
|
|
4547
4683
|
}
|
|
4548
4684
|
async function applyInsert(fix) {
|
|
4549
4685
|
if (fix.line !== void 0 && fix.newContent) {
|
|
4550
|
-
const content = await
|
|
4686
|
+
const content = await readFile72(fix.file, "utf-8");
|
|
4551
4687
|
const lines = content.split("\n");
|
|
4552
4688
|
lines.splice(fix.line - 1, 0, fix.newContent);
|
|
4553
4689
|
await writeFile32(fix.file, lines.join("\n"));
|
|
@@ -4814,7 +4950,7 @@ function arrayLen(arr) {
|
|
|
4814
4950
|
return Array.isArray(arr) ? arr.length : 0;
|
|
4815
4951
|
}
|
|
4816
4952
|
async function gatherEntropyBlock(projectPath) {
|
|
4817
|
-
const { EntropyAnalyzer: EntropyAnalyzer2 } = await import("./analyzer-
|
|
4953
|
+
const { EntropyAnalyzer: EntropyAnalyzer2 } = await import("./analyzer-V633GUHY-MBCQWKDU.js");
|
|
4818
4954
|
const analyzer = new EntropyAnalyzer2({
|
|
4819
4955
|
rootDir: projectPath,
|
|
4820
4956
|
include: ["src/**/*.ts", "src/**/*.tsx", "packages/*/src/**/*.ts"],
|
|
@@ -4851,18 +4987,18 @@ function collectTopAffectedLabels(affected) {
|
|
|
4851
4987
|
return out;
|
|
4852
4988
|
}
|
|
4853
4989
|
async function gatherAttentionBlock(projectPath) {
|
|
4854
|
-
const sessionsDir =
|
|
4855
|
-
if (!
|
|
4990
|
+
const sessionsDir = path5.join(projectPath, ".harness", "sessions");
|
|
4991
|
+
if (!fs8.existsSync(sessionsDir)) return { activeThreadCount: 0, staleThreadCount: 0 };
|
|
4856
4992
|
const now = Date.now();
|
|
4857
4993
|
const STALE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
4858
4994
|
let active = 0;
|
|
4859
4995
|
let stale = 0;
|
|
4860
|
-
const entries =
|
|
4996
|
+
const entries = fs8.readdirSync(sessionsDir, { withFileTypes: true });
|
|
4861
4997
|
for (const entry of entries) {
|
|
4862
4998
|
if (!entry.isDirectory()) continue;
|
|
4863
|
-
const dirPath =
|
|
4999
|
+
const dirPath = path5.join(sessionsDir, entry.name);
|
|
4864
5000
|
try {
|
|
4865
|
-
const stat2 =
|
|
5001
|
+
const stat2 = fs8.statSync(dirPath);
|
|
4866
5002
|
if (now - stat2.mtimeMs > STALE_MS) {
|
|
4867
5003
|
stale++;
|
|
4868
5004
|
} else {
|
|
@@ -4874,10 +5010,10 @@ async function gatherAttentionBlock(projectPath) {
|
|
|
4874
5010
|
return { activeThreadCount: active, staleThreadCount: stale };
|
|
4875
5011
|
}
|
|
4876
5012
|
async function gatherImpactBlock(projectPath) {
|
|
4877
|
-
const cachePath =
|
|
4878
|
-
if (!
|
|
5013
|
+
const cachePath = path5.join(projectPath, ".harness", "cache", "impact.json");
|
|
5014
|
+
if (!fs8.existsSync(cachePath)) return { recentBlastRadius: [] };
|
|
4879
5015
|
try {
|
|
4880
|
-
const raw = JSON.parse(
|
|
5016
|
+
const raw = JSON.parse(fs8.readFileSync(cachePath, "utf8"));
|
|
4881
5017
|
const radii = raw.blastRadius ?? [];
|
|
4882
5018
|
return {
|
|
4883
5019
|
recentBlastRadius: radii.slice(0, 5).map((r) => ({
|
|
@@ -4927,9 +5063,9 @@ async function composeInsights(projectPath, opts = {}) {
|
|
|
4927
5063
|
}
|
|
4928
5064
|
function pickProjectName(projectPath) {
|
|
4929
5065
|
try {
|
|
4930
|
-
const pkgPath =
|
|
4931
|
-
if (!
|
|
4932
|
-
const pkg = JSON.parse(
|
|
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"));
|
|
4933
5069
|
return pkg.name;
|
|
4934
5070
|
} catch {
|
|
4935
5071
|
return void 0;
|
|
@@ -5015,12 +5151,12 @@ function parseDiffHeader(part) {
|
|
|
5015
5151
|
return headerMatch[2];
|
|
5016
5152
|
}
|
|
5017
5153
|
function parseDiffPart(part) {
|
|
5018
|
-
const
|
|
5019
|
-
if (!
|
|
5154
|
+
const path52 = parseDiffHeader(part);
|
|
5155
|
+
if (!path52) return null;
|
|
5020
5156
|
const additionRegex = /^\+(?!\+\+)/gm;
|
|
5021
5157
|
const deletionRegex = /^-(?!--)/gm;
|
|
5022
5158
|
return {
|
|
5023
|
-
path:
|
|
5159
|
+
path: path52,
|
|
5024
5160
|
status: detectFileStatus(part),
|
|
5025
5161
|
additions: (part.match(additionRegex) || []).length,
|
|
5026
5162
|
deletions: (part.match(deletionRegex) || []).length
|
|
@@ -5805,10 +5941,10 @@ function detectEmergentConstraints(history, options) {
|
|
|
5805
5941
|
return { suggestions, totalViolationsAnalyzed, windowWeeks, minOccurrences };
|
|
5806
5942
|
}
|
|
5807
5943
|
function streamsDir(projectPath) {
|
|
5808
|
-
return
|
|
5944
|
+
return path10.join(projectPath, HARNESS_DIR, STREAMS_DIR);
|
|
5809
5945
|
}
|
|
5810
5946
|
function indexPath(projectPath) {
|
|
5811
|
-
return
|
|
5947
|
+
return path10.join(streamsDir(projectPath), INDEX_FILE);
|
|
5812
5948
|
}
|
|
5813
5949
|
function validateStreamName(name) {
|
|
5814
5950
|
if (!STREAM_NAME_REGEX.test(name)) {
|
|
@@ -5822,11 +5958,11 @@ function validateStreamName(name) {
|
|
|
5822
5958
|
}
|
|
5823
5959
|
async function loadStreamIndex(projectPath) {
|
|
5824
5960
|
const idxPath = indexPath(projectPath);
|
|
5825
|
-
if (!
|
|
5961
|
+
if (!fs13.existsSync(idxPath)) {
|
|
5826
5962
|
return Ok({ ...DEFAULT_STREAM_INDEX, streams: {} });
|
|
5827
5963
|
}
|
|
5828
5964
|
try {
|
|
5829
|
-
const raw =
|
|
5965
|
+
const raw = fs13.readFileSync(idxPath, "utf-8");
|
|
5830
5966
|
const parsed = JSON.parse(raw);
|
|
5831
5967
|
const result = StreamIndexSchema.safeParse(parsed);
|
|
5832
5968
|
if (!result.success) {
|
|
@@ -5844,8 +5980,8 @@ async function loadStreamIndex(projectPath) {
|
|
|
5844
5980
|
async function saveStreamIndex(projectPath, index) {
|
|
5845
5981
|
const dir = streamsDir(projectPath);
|
|
5846
5982
|
try {
|
|
5847
|
-
|
|
5848
|
-
|
|
5983
|
+
fs13.mkdirSync(dir, { recursive: true });
|
|
5984
|
+
fs13.writeFileSync(indexPath(projectPath), JSON.stringify(index, null, 2));
|
|
5849
5985
|
return Ok(void 0);
|
|
5850
5986
|
} catch (error) {
|
|
5851
5987
|
return Err(
|
|
@@ -5884,18 +6020,18 @@ async function resolveStreamPath(projectPath, options) {
|
|
|
5884
6020
|
)
|
|
5885
6021
|
);
|
|
5886
6022
|
}
|
|
5887
|
-
return Ok(
|
|
6023
|
+
return Ok(path10.join(streamsDir(projectPath), options.stream));
|
|
5888
6024
|
}
|
|
5889
6025
|
const branch = getCurrentBranch(projectPath);
|
|
5890
6026
|
if (branch && branch !== "main" && branch !== "master") {
|
|
5891
6027
|
for (const [name, info] of Object.entries(index.streams)) {
|
|
5892
6028
|
if (info.branch === branch) {
|
|
5893
|
-
return Ok(
|
|
6029
|
+
return Ok(path10.join(streamsDir(projectPath), name));
|
|
5894
6030
|
}
|
|
5895
6031
|
}
|
|
5896
6032
|
}
|
|
5897
6033
|
if (index.activeStream && index.streams[index.activeStream]) {
|
|
5898
|
-
return Ok(
|
|
6034
|
+
return Ok(path10.join(streamsDir(projectPath), index.activeStream));
|
|
5899
6035
|
}
|
|
5900
6036
|
return Err(
|
|
5901
6037
|
new Error(
|
|
@@ -5923,9 +6059,9 @@ async function createStream(projectPath, name, branch) {
|
|
|
5923
6059
|
if (index.streams[name]) {
|
|
5924
6060
|
return Err(new Error(`Stream '${name}' already exists`));
|
|
5925
6061
|
}
|
|
5926
|
-
const streamPath =
|
|
6062
|
+
const streamPath = path10.join(streamsDir(projectPath), name);
|
|
5927
6063
|
try {
|
|
5928
|
-
|
|
6064
|
+
fs13.mkdirSync(streamPath, { recursive: true });
|
|
5929
6065
|
} catch (error) {
|
|
5930
6066
|
return Err(
|
|
5931
6067
|
new Error(
|
|
@@ -5966,12 +6102,12 @@ async function archiveStream(projectPath, name) {
|
|
|
5966
6102
|
if (!index.streams[name]) {
|
|
5967
6103
|
return Err(new Error(`Stream '${name}' not found`));
|
|
5968
6104
|
}
|
|
5969
|
-
const streamPath =
|
|
5970
|
-
const archiveDir =
|
|
6105
|
+
const streamPath = path10.join(streamsDir(projectPath), name);
|
|
6106
|
+
const archiveDir = path10.join(projectPath, HARNESS_DIR, "archive", "streams");
|
|
5971
6107
|
try {
|
|
5972
|
-
|
|
6108
|
+
fs13.mkdirSync(archiveDir, { recursive: true });
|
|
5973
6109
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
5974
|
-
|
|
6110
|
+
fs13.renameSync(streamPath, path10.join(archiveDir, `${name}-${date}`));
|
|
5975
6111
|
} catch (error) {
|
|
5976
6112
|
return Err(
|
|
5977
6113
|
new Error(
|
|
@@ -5992,19 +6128,19 @@ function getStreamForBranch(index, branch) {
|
|
|
5992
6128
|
return null;
|
|
5993
6129
|
}
|
|
5994
6130
|
async function migrateToStreams(projectPath) {
|
|
5995
|
-
const harnessDir =
|
|
5996
|
-
if (
|
|
6131
|
+
const harnessDir = path10.join(projectPath, HARNESS_DIR);
|
|
6132
|
+
if (fs13.existsSync(indexPath(projectPath))) {
|
|
5997
6133
|
return Ok(void 0);
|
|
5998
6134
|
}
|
|
5999
|
-
const filesToMove = STATE_FILES.filter((f) =>
|
|
6135
|
+
const filesToMove = STATE_FILES.filter((f) => fs13.existsSync(path10.join(harnessDir, f)));
|
|
6000
6136
|
if (filesToMove.length === 0) {
|
|
6001
6137
|
return Ok(void 0);
|
|
6002
6138
|
}
|
|
6003
|
-
const defaultDir =
|
|
6139
|
+
const defaultDir = path10.join(streamsDir(projectPath), "default");
|
|
6004
6140
|
try {
|
|
6005
|
-
|
|
6141
|
+
fs13.mkdirSync(defaultDir, { recursive: true });
|
|
6006
6142
|
for (const file of filesToMove) {
|
|
6007
|
-
|
|
6143
|
+
fs13.renameSync(path10.join(harnessDir, file), path10.join(defaultDir, file));
|
|
6008
6144
|
}
|
|
6009
6145
|
} catch (error) {
|
|
6010
6146
|
return Err(
|
|
@@ -6034,26 +6170,26 @@ function resolveSessionDir(projectPath, sessionSlug, options) {
|
|
|
6034
6170
|
new Error(`Invalid session slug '${sessionSlug}': must not contain path traversal characters`)
|
|
6035
6171
|
);
|
|
6036
6172
|
}
|
|
6037
|
-
const sessionDir =
|
|
6173
|
+
const sessionDir = path11.join(projectPath, HARNESS_DIR, SESSIONS_DIR, sessionSlug);
|
|
6038
6174
|
if (options?.create) {
|
|
6039
|
-
|
|
6175
|
+
fs14.mkdirSync(sessionDir, { recursive: true });
|
|
6040
6176
|
}
|
|
6041
6177
|
return Ok(sessionDir);
|
|
6042
6178
|
}
|
|
6043
6179
|
function updateSessionIndex(projectPath, sessionSlug, description) {
|
|
6044
|
-
const sessionsDir =
|
|
6045
|
-
|
|
6046
|
-
const indexPath2 =
|
|
6180
|
+
const sessionsDir = path11.join(projectPath, HARNESS_DIR, SESSIONS_DIR);
|
|
6181
|
+
fs14.mkdirSync(sessionsDir, { recursive: true });
|
|
6182
|
+
const indexPath2 = path11.join(sessionsDir, SESSION_INDEX_FILE);
|
|
6047
6183
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
6048
6184
|
const newLine = `- [${sessionSlug}](${sessionSlug}/summary.md) \u2014 ${description} (${date})`;
|
|
6049
|
-
if (!
|
|
6050
|
-
|
|
6185
|
+
if (!fs14.existsSync(indexPath2)) {
|
|
6186
|
+
fs14.writeFileSync(indexPath2, `## Active Sessions
|
|
6051
6187
|
|
|
6052
6188
|
${newLine}
|
|
6053
6189
|
`);
|
|
6054
6190
|
return;
|
|
6055
6191
|
}
|
|
6056
|
-
const content =
|
|
6192
|
+
const content = fs14.readFileSync(indexPath2, "utf-8");
|
|
6057
6193
|
const lines = content.split("\n");
|
|
6058
6194
|
const slugPattern = `- [${sessionSlug}]`;
|
|
6059
6195
|
const existingIdx = lines.findIndex((l) => l.startsWith(slugPattern));
|
|
@@ -6063,7 +6199,7 @@ ${newLine}
|
|
|
6063
6199
|
const lastNonEmpty = lines.reduce((last, line, i) => line.trim() !== "" ? i : last, 0);
|
|
6064
6200
|
lines.splice(lastNonEmpty + 1, 0, newLine);
|
|
6065
6201
|
}
|
|
6066
|
-
|
|
6202
|
+
fs14.writeFileSync(indexPath2, lines.join("\n"));
|
|
6067
6203
|
}
|
|
6068
6204
|
function evictIfNeeded(map) {
|
|
6069
6205
|
if (map.size > MAX_CACHE_ENTRIES) {
|
|
@@ -6076,8 +6212,8 @@ async function getStateDir(projectPath, stream, session) {
|
|
|
6076
6212
|
const sessionResult = resolveSessionDir(projectPath, session, { create: true });
|
|
6077
6213
|
return sessionResult;
|
|
6078
6214
|
}
|
|
6079
|
-
const streamsIndexPath =
|
|
6080
|
-
const hasStreams =
|
|
6215
|
+
const streamsIndexPath = path12.join(projectPath, HARNESS_DIR, "streams", INDEX_FILE);
|
|
6216
|
+
const hasStreams = fs15.existsSync(streamsIndexPath);
|
|
6081
6217
|
if (stream || hasStreams) {
|
|
6082
6218
|
const result = await resolveStreamPath(projectPath, stream ? { stream } : void 0);
|
|
6083
6219
|
if (result.ok) {
|
|
@@ -6087,18 +6223,18 @@ async function getStateDir(projectPath, stream, session) {
|
|
|
6087
6223
|
return result;
|
|
6088
6224
|
}
|
|
6089
6225
|
}
|
|
6090
|
-
return Ok(
|
|
6226
|
+
return Ok(path12.join(projectPath, HARNESS_DIR));
|
|
6091
6227
|
}
|
|
6092
6228
|
async function loadState(projectPath, stream, session) {
|
|
6093
6229
|
try {
|
|
6094
6230
|
const dirResult = await getStateDir(projectPath, stream, session);
|
|
6095
6231
|
if (!dirResult.ok) return dirResult;
|
|
6096
6232
|
const stateDir = dirResult.value;
|
|
6097
|
-
const statePath =
|
|
6098
|
-
if (!
|
|
6233
|
+
const statePath = path13.join(stateDir, STATE_FILE);
|
|
6234
|
+
if (!fs16.existsSync(statePath)) {
|
|
6099
6235
|
return Ok({ ...DEFAULT_STATE });
|
|
6100
6236
|
}
|
|
6101
|
-
const raw =
|
|
6237
|
+
const raw = fs16.readFileSync(statePath, "utf-8");
|
|
6102
6238
|
const parsed = JSON.parse(raw);
|
|
6103
6239
|
const result = HarnessStateSchema.safeParse(parsed);
|
|
6104
6240
|
if (!result.success) {
|
|
@@ -6116,11 +6252,11 @@ async function saveState(projectPath, state, stream, session) {
|
|
|
6116
6252
|
const dirResult = await getStateDir(projectPath, stream, session);
|
|
6117
6253
|
if (!dirResult.ok) return dirResult;
|
|
6118
6254
|
const stateDir = dirResult.value;
|
|
6119
|
-
const statePath =
|
|
6120
|
-
|
|
6255
|
+
const statePath = path13.join(stateDir, STATE_FILE);
|
|
6256
|
+
fs16.mkdirSync(stateDir, { recursive: true });
|
|
6121
6257
|
const tmpPath = statePath + ".tmp";
|
|
6122
|
-
|
|
6123
|
-
|
|
6258
|
+
fs16.writeFileSync(tmpPath, JSON.stringify(state, null, 2));
|
|
6259
|
+
fs16.renameSync(tmpPath, statePath);
|
|
6124
6260
|
return Ok(void 0);
|
|
6125
6261
|
} catch (error) {
|
|
6126
6262
|
return Err(
|
|
@@ -6206,10 +6342,10 @@ function computeContentHash(text) {
|
|
|
6206
6342
|
return crypto2.createHash("sha256").update(text).digest("hex").slice(0, 16);
|
|
6207
6343
|
}
|
|
6208
6344
|
function loadContentHashes(stateDir) {
|
|
6209
|
-
const hashesPath =
|
|
6210
|
-
if (!
|
|
6345
|
+
const hashesPath = path14.join(stateDir, CONTENT_HASHES_FILE);
|
|
6346
|
+
if (!fs17.existsSync(hashesPath)) return {};
|
|
6211
6347
|
try {
|
|
6212
|
-
const raw =
|
|
6348
|
+
const raw = fs17.readFileSync(hashesPath, "utf-8");
|
|
6213
6349
|
const parsed = JSON.parse(raw);
|
|
6214
6350
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return {};
|
|
6215
6351
|
return parsed;
|
|
@@ -6218,15 +6354,15 @@ function loadContentHashes(stateDir) {
|
|
|
6218
6354
|
}
|
|
6219
6355
|
}
|
|
6220
6356
|
function saveContentHashes(stateDir, index) {
|
|
6221
|
-
const hashesPath =
|
|
6357
|
+
const hashesPath = path14.join(stateDir, CONTENT_HASHES_FILE);
|
|
6222
6358
|
const tmpPath = hashesPath + ".tmp";
|
|
6223
|
-
|
|
6224
|
-
|
|
6359
|
+
fs17.writeFileSync(tmpPath, JSON.stringify(index, null, 2) + "\n");
|
|
6360
|
+
fs17.renameSync(tmpPath, hashesPath);
|
|
6225
6361
|
}
|
|
6226
6362
|
function rebuildContentHashes(stateDir, learningsFile) {
|
|
6227
|
-
const learningsPath =
|
|
6228
|
-
if (!
|
|
6229
|
-
const content =
|
|
6363
|
+
const learningsPath = path14.join(stateDir, learningsFile);
|
|
6364
|
+
if (!fs17.existsSync(learningsPath)) return {};
|
|
6365
|
+
const content = fs17.readFileSync(learningsPath, "utf-8");
|
|
6230
6366
|
const lines = content.split("\n");
|
|
6231
6367
|
const index = {};
|
|
6232
6368
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -6285,18 +6421,18 @@ async function loadRelevantLearnings(projectPath, skillName, stream, session) {
|
|
|
6285
6421
|
const dirResult = await getStateDir(projectPath, stream, session);
|
|
6286
6422
|
if (!dirResult.ok) return dirResult;
|
|
6287
6423
|
const stateDir = dirResult.value;
|
|
6288
|
-
const learningsPath =
|
|
6289
|
-
if (!
|
|
6424
|
+
const learningsPath = path15.join(stateDir, LEARNINGS_FILE);
|
|
6425
|
+
if (!fs18.existsSync(learningsPath)) {
|
|
6290
6426
|
return Ok([]);
|
|
6291
6427
|
}
|
|
6292
|
-
const stats =
|
|
6428
|
+
const stats = fs18.statSync(learningsPath);
|
|
6293
6429
|
const cacheKey = learningsPath;
|
|
6294
6430
|
const cached = learningsCacheMap.get(cacheKey);
|
|
6295
6431
|
let entries;
|
|
6296
6432
|
if (cached && cached.mtimeMs === stats.mtimeMs) {
|
|
6297
6433
|
entries = cached.entries;
|
|
6298
6434
|
} else {
|
|
6299
|
-
const content =
|
|
6435
|
+
const content = fs18.readFileSync(learningsPath, "utf-8");
|
|
6300
6436
|
const lines = content.split("\n");
|
|
6301
6437
|
entries = [];
|
|
6302
6438
|
let currentBlock = [];
|
|
@@ -6437,11 +6573,11 @@ function sleep(ms) {
|
|
|
6437
6573
|
async function acquireFileLock(lockPath) {
|
|
6438
6574
|
for (let attempt = 0; attempt < LOCK_RETRIES; attempt++) {
|
|
6439
6575
|
try {
|
|
6440
|
-
const fd =
|
|
6576
|
+
const fd = fs19.openSync(
|
|
6441
6577
|
lockPath,
|
|
6442
|
-
|
|
6578
|
+
fs19.constants.O_CREAT | fs19.constants.O_EXCL | fs19.constants.O_WRONLY
|
|
6443
6579
|
);
|
|
6444
|
-
|
|
6580
|
+
fs19.closeSync(fd);
|
|
6445
6581
|
return;
|
|
6446
6582
|
} catch (err) {
|
|
6447
6583
|
const isExist = err instanceof Error && "code" in err && err.code === "EEXIST";
|
|
@@ -6455,20 +6591,20 @@ async function acquireFileLock(lockPath) {
|
|
|
6455
6591
|
}
|
|
6456
6592
|
function releaseFileLock(lockPath) {
|
|
6457
6593
|
try {
|
|
6458
|
-
|
|
6594
|
+
fs19.unlinkSync(lockPath);
|
|
6459
6595
|
} catch {
|
|
6460
6596
|
}
|
|
6461
6597
|
}
|
|
6462
6598
|
function loadOrRebuildHashes(stateDir, learningsPath) {
|
|
6463
|
-
const hashesPath =
|
|
6464
|
-
if (
|
|
6599
|
+
const hashesPath = path16.join(stateDir, CONTENT_HASHES_FILE);
|
|
6600
|
+
if (fs19.existsSync(hashesPath)) {
|
|
6465
6601
|
const hashes = loadContentHashes(stateDir);
|
|
6466
|
-
if (Object.keys(hashes).length === 0 &&
|
|
6602
|
+
if (Object.keys(hashes).length === 0 && fs19.existsSync(learningsPath)) {
|
|
6467
6603
|
return rebuildContentHashes(stateDir, LEARNINGS_FILE);
|
|
6468
6604
|
}
|
|
6469
6605
|
return hashes;
|
|
6470
6606
|
}
|
|
6471
|
-
if (
|
|
6607
|
+
if (fs19.existsSync(learningsPath)) {
|
|
6472
6608
|
return rebuildContentHashes(stateDir, LEARNINGS_FILE);
|
|
6473
6609
|
}
|
|
6474
6610
|
return {};
|
|
@@ -6492,8 +6628,8 @@ async function appendLearning(projectPath, learning, skillName, outcome, stream,
|
|
|
6492
6628
|
const dirResult = await getStateDir(projectPath, stream, session);
|
|
6493
6629
|
if (!dirResult.ok) return dirResult;
|
|
6494
6630
|
const stateDir = dirResult.value;
|
|
6495
|
-
const learningsPath =
|
|
6496
|
-
|
|
6631
|
+
const learningsPath = path16.join(stateDir, LEARNINGS_FILE);
|
|
6632
|
+
fs19.mkdirSync(stateDir, { recursive: true });
|
|
6497
6633
|
const lockPath = learningsPath + ".lock";
|
|
6498
6634
|
await acquireFileLock(lockPath);
|
|
6499
6635
|
try {
|
|
@@ -6523,17 +6659,17 @@ ${bulletLine}
|
|
|
6523
6659
|
`;
|
|
6524
6660
|
let overlapResult;
|
|
6525
6661
|
let existingLineCount;
|
|
6526
|
-
if (!
|
|
6527
|
-
|
|
6662
|
+
if (!fs19.existsSync(learningsPath)) {
|
|
6663
|
+
fs19.writeFileSync(learningsPath, `# Learnings
|
|
6528
6664
|
${entry}`);
|
|
6529
6665
|
existingLineCount = 1;
|
|
6530
6666
|
} else {
|
|
6531
|
-
const existingContent =
|
|
6667
|
+
const existingContent = fs19.readFileSync(learningsPath, "utf-8");
|
|
6532
6668
|
const existingEntries = existingContent.split("\n").filter((line) => /^- \*\*\d{4}-\d{2}-\d{2}/.test(line));
|
|
6533
6669
|
const overlap = checkOverlap(bulletLine, existingEntries);
|
|
6534
6670
|
if (overlap.score >= 0.7) overlapResult = overlap;
|
|
6535
6671
|
existingLineCount = existingContent.split("\n").length;
|
|
6536
|
-
|
|
6672
|
+
fs19.appendFileSync(learningsPath, entry);
|
|
6537
6673
|
}
|
|
6538
6674
|
const bulletLineNum = existingLineCount + 2;
|
|
6539
6675
|
contentHashes[contentHash] = { date: timestamp ?? "", line: bulletLineNum };
|
|
@@ -6638,11 +6774,11 @@ async function loadIndexEntries(projectPath, skillName, stream, session) {
|
|
|
6638
6774
|
const dirResult = await getStateDir(projectPath, stream, session);
|
|
6639
6775
|
if (!dirResult.ok) return dirResult;
|
|
6640
6776
|
const stateDir = dirResult.value;
|
|
6641
|
-
const learningsPath =
|
|
6642
|
-
if (!
|
|
6777
|
+
const learningsPath = path16.join(stateDir, LEARNINGS_FILE);
|
|
6778
|
+
if (!fs19.existsSync(learningsPath)) {
|
|
6643
6779
|
return Ok([]);
|
|
6644
6780
|
}
|
|
6645
|
-
const content =
|
|
6781
|
+
const content = fs19.readFileSync(learningsPath, "utf-8");
|
|
6646
6782
|
const lines = content.split("\n");
|
|
6647
6783
|
const indexEntries = [];
|
|
6648
6784
|
let pendingFrontmatter = null;
|
|
@@ -6684,16 +6820,16 @@ async function archiveLearnings(projectPath, entries, stream) {
|
|
|
6684
6820
|
const dirResult = await getStateDir(projectPath, stream);
|
|
6685
6821
|
if (!dirResult.ok) return dirResult;
|
|
6686
6822
|
const stateDir = dirResult.value;
|
|
6687
|
-
const archiveDir =
|
|
6688
|
-
|
|
6823
|
+
const archiveDir = path17.join(stateDir, "learnings-archive");
|
|
6824
|
+
fs20.mkdirSync(archiveDir, { recursive: true });
|
|
6689
6825
|
const now = /* @__PURE__ */ new Date();
|
|
6690
6826
|
const yearMonth = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`;
|
|
6691
|
-
const archivePath =
|
|
6827
|
+
const archivePath = path17.join(archiveDir, `${yearMonth}.md`);
|
|
6692
6828
|
const archiveContent = entries.join("\n\n") + "\n";
|
|
6693
|
-
if (
|
|
6694
|
-
|
|
6829
|
+
if (fs20.existsSync(archivePath)) {
|
|
6830
|
+
fs20.appendFileSync(archivePath, "\n" + archiveContent);
|
|
6695
6831
|
} else {
|
|
6696
|
-
|
|
6832
|
+
fs20.writeFileSync(archivePath, `# Learnings Archive
|
|
6697
6833
|
|
|
6698
6834
|
${archiveContent}`);
|
|
6699
6835
|
}
|
|
@@ -6711,8 +6847,8 @@ async function pruneLearnings(projectPath, stream) {
|
|
|
6711
6847
|
const dirResult = await getStateDir(projectPath, stream);
|
|
6712
6848
|
if (!dirResult.ok) return dirResult;
|
|
6713
6849
|
const stateDir = dirResult.value;
|
|
6714
|
-
const learningsPath =
|
|
6715
|
-
if (!
|
|
6850
|
+
const learningsPath = path17.join(stateDir, LEARNINGS_FILE);
|
|
6851
|
+
if (!fs20.existsSync(learningsPath)) {
|
|
6716
6852
|
return Ok({ kept: 0, archived: 0, patterns: [] });
|
|
6717
6853
|
}
|
|
6718
6854
|
const loadResult = await loadRelevantLearnings(projectPath, void 0, stream);
|
|
@@ -6743,7 +6879,7 @@ async function pruneLearnings(projectPath, stream) {
|
|
|
6743
6879
|
if (!archiveResult.ok) return archiveResult;
|
|
6744
6880
|
}
|
|
6745
6881
|
const newContent = "# Learnings\n\n" + toKeep.join("\n\n") + "\n";
|
|
6746
|
-
|
|
6882
|
+
fs20.writeFileSync(learningsPath, newContent);
|
|
6747
6883
|
invalidateLearningsCacheEntry(learningsPath);
|
|
6748
6884
|
return Ok({
|
|
6749
6885
|
kept: toKeep.length,
|
|
@@ -6787,19 +6923,19 @@ async function promoteSessionLearnings(projectPath, sessionSlug, stream) {
|
|
|
6787
6923
|
const dirResult = await getStateDir(projectPath, stream);
|
|
6788
6924
|
if (!dirResult.ok) return dirResult;
|
|
6789
6925
|
const stateDir = dirResult.value;
|
|
6790
|
-
const globalPath =
|
|
6791
|
-
const existingGlobal =
|
|
6926
|
+
const globalPath = path17.join(stateDir, LEARNINGS_FILE);
|
|
6927
|
+
const existingGlobal = fs20.existsSync(globalPath) ? fs20.readFileSync(globalPath, "utf-8") : "";
|
|
6792
6928
|
const newEntries = toPromote.filter((entry) => !existingGlobal.includes(entry.trim()));
|
|
6793
6929
|
if (newEntries.length === 0) {
|
|
6794
6930
|
return Ok({ promoted: 0, skipped: skipped + toPromote.length });
|
|
6795
6931
|
}
|
|
6796
6932
|
const promotedContent = newEntries.join("\n\n") + "\n";
|
|
6797
6933
|
if (!existingGlobal) {
|
|
6798
|
-
|
|
6934
|
+
fs20.writeFileSync(globalPath, `# Learnings
|
|
6799
6935
|
|
|
6800
6936
|
${promotedContent}`);
|
|
6801
6937
|
} else {
|
|
6802
|
-
|
|
6938
|
+
fs20.appendFileSync(globalPath, "\n\n" + promotedContent);
|
|
6803
6939
|
}
|
|
6804
6940
|
invalidateLearningsCacheEntry(globalPath);
|
|
6805
6941
|
return Ok({
|
|
@@ -6835,13 +6971,13 @@ async function detectStaleLearnings(projectPath, stream, session) {
|
|
|
6835
6971
|
freshCount++;
|
|
6836
6972
|
continue;
|
|
6837
6973
|
}
|
|
6838
|
-
const resolvedRoot =
|
|
6974
|
+
const resolvedRoot = path18.resolve(projectPath);
|
|
6839
6975
|
const missing = refs.filter((ref) => {
|
|
6840
|
-
const absPath =
|
|
6841
|
-
if (!absPath.startsWith(resolvedRoot +
|
|
6976
|
+
const absPath = path18.resolve(projectPath, ref);
|
|
6977
|
+
if (!absPath.startsWith(resolvedRoot + path18.sep) && absPath !== resolvedRoot) {
|
|
6842
6978
|
return false;
|
|
6843
6979
|
}
|
|
6844
|
-
return !
|
|
6980
|
+
return !fs21.existsSync(absPath);
|
|
6845
6981
|
});
|
|
6846
6982
|
if (missing.length > 0) {
|
|
6847
6983
|
const idx = extractIndexEntry(entry);
|
|
@@ -6876,17 +7012,17 @@ async function appendFailure(projectPath, description, skillName, type, stream,
|
|
|
6876
7012
|
const dirResult = await getStateDir(projectPath, stream, session);
|
|
6877
7013
|
if (!dirResult.ok) return dirResult;
|
|
6878
7014
|
const stateDir = dirResult.value;
|
|
6879
|
-
const failuresPath =
|
|
6880
|
-
|
|
7015
|
+
const failuresPath = path19.join(stateDir, FAILURES_FILE);
|
|
7016
|
+
fs22.mkdirSync(stateDir, { recursive: true });
|
|
6881
7017
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
6882
7018
|
const entry = `
|
|
6883
7019
|
- **${timestamp} [skill:${skillName}] [type:${type}]:** ${description}
|
|
6884
7020
|
`;
|
|
6885
|
-
if (!
|
|
6886
|
-
|
|
7021
|
+
if (!fs22.existsSync(failuresPath)) {
|
|
7022
|
+
fs22.writeFileSync(failuresPath, `# Failures
|
|
6887
7023
|
${entry}`);
|
|
6888
7024
|
} else {
|
|
6889
|
-
|
|
7025
|
+
fs22.appendFileSync(failuresPath, entry);
|
|
6890
7026
|
}
|
|
6891
7027
|
failuresCacheMap.delete(failuresPath);
|
|
6892
7028
|
return Ok(void 0);
|
|
@@ -6903,17 +7039,17 @@ async function loadFailures(projectPath, stream, session) {
|
|
|
6903
7039
|
const dirResult = await getStateDir(projectPath, stream, session);
|
|
6904
7040
|
if (!dirResult.ok) return dirResult;
|
|
6905
7041
|
const stateDir = dirResult.value;
|
|
6906
|
-
const failuresPath =
|
|
6907
|
-
if (!
|
|
7042
|
+
const failuresPath = path19.join(stateDir, FAILURES_FILE);
|
|
7043
|
+
if (!fs22.existsSync(failuresPath)) {
|
|
6908
7044
|
return Ok([]);
|
|
6909
7045
|
}
|
|
6910
|
-
const stats =
|
|
7046
|
+
const stats = fs22.statSync(failuresPath);
|
|
6911
7047
|
const cacheKey = failuresPath;
|
|
6912
7048
|
const cached = failuresCacheMap.get(cacheKey);
|
|
6913
7049
|
if (cached && cached.mtimeMs === stats.mtimeMs) {
|
|
6914
7050
|
return Ok(cached.entries);
|
|
6915
7051
|
}
|
|
6916
|
-
const content =
|
|
7052
|
+
const content = fs22.readFileSync(failuresPath, "utf-8");
|
|
6917
7053
|
const entries = [];
|
|
6918
7054
|
for (const line of content.split("\n")) {
|
|
6919
7055
|
const match = line.match(FAILURE_LINE_REGEX);
|
|
@@ -6942,20 +7078,20 @@ async function archiveFailures(projectPath, stream, session) {
|
|
|
6942
7078
|
const dirResult = await getStateDir(projectPath, stream, session);
|
|
6943
7079
|
if (!dirResult.ok) return dirResult;
|
|
6944
7080
|
const stateDir = dirResult.value;
|
|
6945
|
-
const failuresPath =
|
|
6946
|
-
if (!
|
|
7081
|
+
const failuresPath = path19.join(stateDir, FAILURES_FILE);
|
|
7082
|
+
if (!fs22.existsSync(failuresPath)) {
|
|
6947
7083
|
return Ok(void 0);
|
|
6948
7084
|
}
|
|
6949
|
-
const archiveDir =
|
|
6950
|
-
|
|
7085
|
+
const archiveDir = path19.join(stateDir, "archive");
|
|
7086
|
+
fs22.mkdirSync(archiveDir, { recursive: true });
|
|
6951
7087
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
6952
7088
|
let archiveName = `failures-${date}.md`;
|
|
6953
7089
|
let counter = 2;
|
|
6954
|
-
while (
|
|
7090
|
+
while (fs22.existsSync(path19.join(archiveDir, archiveName))) {
|
|
6955
7091
|
archiveName = `failures-${date}-${counter}.md`;
|
|
6956
7092
|
counter++;
|
|
6957
7093
|
}
|
|
6958
|
-
|
|
7094
|
+
fs22.renameSync(failuresPath, path19.join(archiveDir, archiveName));
|
|
6959
7095
|
failuresCacheMap.delete(failuresPath);
|
|
6960
7096
|
return Ok(void 0);
|
|
6961
7097
|
} catch (error) {
|
|
@@ -6971,11 +7107,11 @@ async function saveHandoff(projectPath, handoff, stream, session) {
|
|
|
6971
7107
|
const dirResult = await getStateDir(projectPath, stream, session);
|
|
6972
7108
|
if (!dirResult.ok) return dirResult;
|
|
6973
7109
|
const stateDir = dirResult.value;
|
|
6974
|
-
const handoffPath =
|
|
6975
|
-
|
|
7110
|
+
const handoffPath = path20.join(stateDir, HANDOFF_FILE);
|
|
7111
|
+
fs23.mkdirSync(stateDir, { recursive: true });
|
|
6976
7112
|
const tmpPath = handoffPath + ".tmp";
|
|
6977
|
-
|
|
6978
|
-
|
|
7113
|
+
fs23.writeFileSync(tmpPath, JSON.stringify(handoff, null, 2));
|
|
7114
|
+
fs23.renameSync(tmpPath, handoffPath);
|
|
6979
7115
|
return Ok(void 0);
|
|
6980
7116
|
} catch (error) {
|
|
6981
7117
|
return Err(
|
|
@@ -6988,11 +7124,11 @@ async function loadHandoff(projectPath, stream, session) {
|
|
|
6988
7124
|
const dirResult = await getStateDir(projectPath, stream, session);
|
|
6989
7125
|
if (!dirResult.ok) return dirResult;
|
|
6990
7126
|
const stateDir = dirResult.value;
|
|
6991
|
-
const handoffPath =
|
|
6992
|
-
if (!
|
|
7127
|
+
const handoffPath = path20.join(stateDir, HANDOFF_FILE);
|
|
7128
|
+
if (!fs23.existsSync(handoffPath)) {
|
|
6993
7129
|
return Ok(null);
|
|
6994
7130
|
}
|
|
6995
|
-
const raw =
|
|
7131
|
+
const raw = fs23.readFileSync(handoffPath, "utf-8");
|
|
6996
7132
|
const parsed = JSON.parse(raw);
|
|
6997
7133
|
const result = HandoffSchema.safeParse(parsed);
|
|
6998
7134
|
if (!result.success) {
|
|
@@ -7006,28 +7142,28 @@ async function loadHandoff(projectPath, stream, session) {
|
|
|
7006
7142
|
}
|
|
7007
7143
|
}
|
|
7008
7144
|
function loadChecksFromConfig(gateConfigPath) {
|
|
7009
|
-
if (!
|
|
7010
|
-
const raw = JSON.parse(
|
|
7145
|
+
if (!fs24.existsSync(gateConfigPath)) return [];
|
|
7146
|
+
const raw = JSON.parse(fs24.readFileSync(gateConfigPath, "utf-8"));
|
|
7011
7147
|
const config = GateConfigSchema.safeParse(raw);
|
|
7012
7148
|
if (config.success && config.data.checks) return config.data.checks;
|
|
7013
7149
|
return [];
|
|
7014
7150
|
}
|
|
7015
7151
|
function discoverChecksFromProject(projectPath) {
|
|
7016
7152
|
const checks = [];
|
|
7017
|
-
const packageJsonPath =
|
|
7018
|
-
if (
|
|
7019
|
-
const pkg = JSON.parse(
|
|
7153
|
+
const packageJsonPath = path21.join(projectPath, "package.json");
|
|
7154
|
+
if (fs24.existsSync(packageJsonPath)) {
|
|
7155
|
+
const pkg = JSON.parse(fs24.readFileSync(packageJsonPath, "utf-8"));
|
|
7020
7156
|
const scripts = pkg.scripts || {};
|
|
7021
7157
|
if (scripts.test) checks.push({ name: "test", command: "npm test" });
|
|
7022
7158
|
if (scripts.lint) checks.push({ name: "lint", command: "npm run lint" });
|
|
7023
7159
|
if (scripts.typecheck) checks.push({ name: "typecheck", command: "npm run typecheck" });
|
|
7024
7160
|
if (scripts.build) checks.push({ name: "build", command: "npm run build" });
|
|
7025
7161
|
}
|
|
7026
|
-
if (
|
|
7162
|
+
if (fs24.existsSync(path21.join(projectPath, "go.mod"))) {
|
|
7027
7163
|
checks.push({ name: "test", command: "go test ./..." });
|
|
7028
7164
|
checks.push({ name: "build", command: "go build ./..." });
|
|
7029
7165
|
}
|
|
7030
|
-
if (
|
|
7166
|
+
if (fs24.existsSync(path21.join(projectPath, "pyproject.toml")) || fs24.existsSync(path21.join(projectPath, "setup.py"))) {
|
|
7031
7167
|
checks.push({ name: "test", command: "python -m pytest" });
|
|
7032
7168
|
}
|
|
7033
7169
|
return checks;
|
|
@@ -7067,8 +7203,8 @@ function executeCheck(check, projectPath) {
|
|
|
7067
7203
|
}
|
|
7068
7204
|
}
|
|
7069
7205
|
async function runMechanicalGate(projectPath) {
|
|
7070
|
-
const harnessDir =
|
|
7071
|
-
const gateConfigPath =
|
|
7206
|
+
const harnessDir = path21.join(projectPath, HARNESS_DIR);
|
|
7207
|
+
const gateConfigPath = path21.join(harnessDir, GATE_CONFIG_FILE);
|
|
7072
7208
|
try {
|
|
7073
7209
|
let checks = loadChecksFromConfig(gateConfigPath);
|
|
7074
7210
|
if (checks.length === 0) {
|
|
@@ -7124,9 +7260,9 @@ function writeSessionSummary(projectPath, sessionSlug, data) {
|
|
|
7124
7260
|
const dirResult = resolveSessionDir(projectPath, sessionSlug, { create: true });
|
|
7125
7261
|
if (!dirResult.ok) return dirResult;
|
|
7126
7262
|
const sessionDir = dirResult.value;
|
|
7127
|
-
const summaryPath =
|
|
7263
|
+
const summaryPath = path22.join(sessionDir, SUMMARY_FILE);
|
|
7128
7264
|
const content = formatSummary(data);
|
|
7129
|
-
|
|
7265
|
+
fs25.writeFileSync(summaryPath, content);
|
|
7130
7266
|
const description = deriveIndexDescription(data);
|
|
7131
7267
|
updateSessionIndex(projectPath, sessionSlug, description);
|
|
7132
7268
|
return Ok(void 0);
|
|
@@ -7143,11 +7279,11 @@ function loadSessionSummary(projectPath, sessionSlug) {
|
|
|
7143
7279
|
const dirResult = resolveSessionDir(projectPath, sessionSlug);
|
|
7144
7280
|
if (!dirResult.ok) return dirResult;
|
|
7145
7281
|
const sessionDir = dirResult.value;
|
|
7146
|
-
const summaryPath =
|
|
7147
|
-
if (!
|
|
7282
|
+
const summaryPath = path22.join(sessionDir, SUMMARY_FILE);
|
|
7283
|
+
if (!fs25.existsSync(summaryPath)) {
|
|
7148
7284
|
return Ok(null);
|
|
7149
7285
|
}
|
|
7150
|
-
const content =
|
|
7286
|
+
const content = fs25.readFileSync(summaryPath, "utf-8");
|
|
7151
7287
|
return Ok(content);
|
|
7152
7288
|
} catch (error) {
|
|
7153
7289
|
return Err(
|
|
@@ -7159,11 +7295,11 @@ function loadSessionSummary(projectPath, sessionSlug) {
|
|
|
7159
7295
|
}
|
|
7160
7296
|
function listActiveSessions(projectPath) {
|
|
7161
7297
|
try {
|
|
7162
|
-
const indexPath2 =
|
|
7163
|
-
if (!
|
|
7298
|
+
const indexPath2 = path22.join(projectPath, HARNESS_DIR, SESSIONS_DIR, SESSION_INDEX_FILE);
|
|
7299
|
+
if (!fs25.existsSync(indexPath2)) {
|
|
7164
7300
|
return Ok(null);
|
|
7165
7301
|
}
|
|
7166
|
-
const content =
|
|
7302
|
+
const content = fs25.readFileSync(indexPath2, "utf-8");
|
|
7167
7303
|
return Ok(content);
|
|
7168
7304
|
} catch (error) {
|
|
7169
7305
|
return Err(
|
|
@@ -7184,12 +7320,12 @@ async function loadSessionState(projectPath, sessionSlug) {
|
|
|
7184
7320
|
const dirResult = resolveSessionDir(projectPath, sessionSlug);
|
|
7185
7321
|
if (!dirResult.ok) return dirResult;
|
|
7186
7322
|
const sessionDir = dirResult.value;
|
|
7187
|
-
const filePath =
|
|
7188
|
-
if (!
|
|
7323
|
+
const filePath = path23.join(sessionDir, SESSION_STATE_FILE);
|
|
7324
|
+
if (!fs26.existsSync(filePath)) {
|
|
7189
7325
|
return Ok(emptySections());
|
|
7190
7326
|
}
|
|
7191
7327
|
try {
|
|
7192
|
-
const raw =
|
|
7328
|
+
const raw = fs26.readFileSync(filePath, "utf-8");
|
|
7193
7329
|
const parsed = JSON.parse(raw);
|
|
7194
7330
|
const sections = emptySections();
|
|
7195
7331
|
for (const name of SESSION_SECTION_NAMES) {
|
|
@@ -7210,9 +7346,9 @@ async function saveSessionState(projectPath, sessionSlug, sections) {
|
|
|
7210
7346
|
const dirResult = resolveSessionDir(projectPath, sessionSlug, { create: true });
|
|
7211
7347
|
if (!dirResult.ok) return dirResult;
|
|
7212
7348
|
const sessionDir = dirResult.value;
|
|
7213
|
-
const filePath =
|
|
7349
|
+
const filePath = path23.join(sessionDir, SESSION_STATE_FILE);
|
|
7214
7350
|
try {
|
|
7215
|
-
|
|
7351
|
+
fs26.writeFileSync(filePath, JSON.stringify(sections, null, 2));
|
|
7216
7352
|
return Ok(void 0);
|
|
7217
7353
|
} catch (error) {
|
|
7218
7354
|
return Err(
|
|
@@ -7268,26 +7404,26 @@ async function archiveSession(projectPath, sessionSlug, options = {}) {
|
|
|
7268
7404
|
const dirResult = resolveSessionDir(projectPath, sessionSlug);
|
|
7269
7405
|
if (!dirResult.ok) return dirResult;
|
|
7270
7406
|
const sessionDir = dirResult.value;
|
|
7271
|
-
if (!
|
|
7407
|
+
if (!fs27.existsSync(sessionDir)) {
|
|
7272
7408
|
return Err(new Error(`Session '${sessionSlug}' not found at ${sessionDir}`));
|
|
7273
7409
|
}
|
|
7274
|
-
const archiveBase =
|
|
7410
|
+
const archiveBase = path24.join(projectPath, HARNESS_DIR, ARCHIVE_DIR, "sessions");
|
|
7275
7411
|
try {
|
|
7276
|
-
|
|
7412
|
+
fs27.mkdirSync(archiveBase, { recursive: true });
|
|
7277
7413
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
7278
7414
|
let archiveName = `${sessionSlug}-${date}`;
|
|
7279
7415
|
let counter = 1;
|
|
7280
|
-
while (
|
|
7416
|
+
while (fs27.existsSync(path24.join(archiveBase, archiveName))) {
|
|
7281
7417
|
archiveName = `${sessionSlug}-${date}-${counter}`;
|
|
7282
7418
|
counter++;
|
|
7283
7419
|
}
|
|
7284
|
-
const dest =
|
|
7420
|
+
const dest = path24.join(archiveBase, archiveName);
|
|
7285
7421
|
try {
|
|
7286
|
-
|
|
7422
|
+
fs27.renameSync(sessionDir, dest);
|
|
7287
7423
|
} catch (renameErr) {
|
|
7288
7424
|
if (renameErr instanceof Error && "code" in renameErr && renameErr.code === "EXDEV") {
|
|
7289
|
-
|
|
7290
|
-
|
|
7425
|
+
fs27.cpSync(sessionDir, dest, { recursive: true });
|
|
7426
|
+
fs27.rmSync(sessionDir, { recursive: true });
|
|
7291
7427
|
} else {
|
|
7292
7428
|
throw renameErr;
|
|
7293
7429
|
}
|
|
@@ -7322,8 +7458,8 @@ function loadKnownHashes(eventsPath) {
|
|
|
7322
7458
|
const cached = knownHashesCache.get(eventsPath);
|
|
7323
7459
|
if (cached) return cached;
|
|
7324
7460
|
const hashes = /* @__PURE__ */ new Set();
|
|
7325
|
-
if (
|
|
7326
|
-
const content =
|
|
7461
|
+
if (fs28.existsSync(eventsPath)) {
|
|
7462
|
+
const content = fs28.readFileSync(eventsPath, "utf-8");
|
|
7327
7463
|
const lines = content.split("\n").filter((line) => line.trim() !== "");
|
|
7328
7464
|
for (const line of lines) {
|
|
7329
7465
|
try {
|
|
@@ -7346,8 +7482,8 @@ async function emitEvent(projectPath, event, options) {
|
|
|
7346
7482
|
const dirResult = await getStateDir(projectPath, options?.stream, options?.session);
|
|
7347
7483
|
if (!dirResult.ok) return dirResult;
|
|
7348
7484
|
const stateDir = dirResult.value;
|
|
7349
|
-
const eventsPath =
|
|
7350
|
-
|
|
7485
|
+
const eventsPath = path25.join(stateDir, EVENTS_FILE);
|
|
7486
|
+
fs28.mkdirSync(stateDir, { recursive: true });
|
|
7351
7487
|
const contentHash = computeEventHash(event, options?.session);
|
|
7352
7488
|
const knownHashes = loadKnownHashes(eventsPath);
|
|
7353
7489
|
if (knownHashes.has(contentHash)) {
|
|
@@ -7361,7 +7497,7 @@ async function emitEvent(projectPath, event, options) {
|
|
|
7361
7497
|
if (options?.session) {
|
|
7362
7498
|
fullEvent.session = options.session;
|
|
7363
7499
|
}
|
|
7364
|
-
|
|
7500
|
+
fs28.appendFileSync(eventsPath, JSON.stringify(fullEvent) + "\n");
|
|
7365
7501
|
knownHashes.add(contentHash);
|
|
7366
7502
|
return Ok({ written: true });
|
|
7367
7503
|
} catch (error) {
|
|
@@ -7375,11 +7511,11 @@ async function loadEvents(projectPath, options) {
|
|
|
7375
7511
|
const dirResult = await getStateDir(projectPath, options?.stream, options?.session);
|
|
7376
7512
|
if (!dirResult.ok) return dirResult;
|
|
7377
7513
|
const stateDir = dirResult.value;
|
|
7378
|
-
const eventsPath =
|
|
7379
|
-
if (!
|
|
7514
|
+
const eventsPath = path25.join(stateDir, EVENTS_FILE);
|
|
7515
|
+
if (!fs28.existsSync(eventsPath)) {
|
|
7380
7516
|
return Ok([]);
|
|
7381
7517
|
}
|
|
7382
|
-
const content =
|
|
7518
|
+
const content = fs28.readFileSync(eventsPath, "utf-8");
|
|
7383
7519
|
const lines = content.split("\n").filter((line) => line.trim() !== "");
|
|
7384
7520
|
const events = [];
|
|
7385
7521
|
for (const line of lines) {
|
|
@@ -7628,11 +7764,11 @@ function nodeSubStacks(allDeps) {
|
|
|
7628
7764
|
return found;
|
|
7629
7765
|
}
|
|
7630
7766
|
function detectNodeStacks(projectRoot) {
|
|
7631
|
-
const pkgJsonPath =
|
|
7632
|
-
if (!
|
|
7767
|
+
const pkgJsonPath = path26.join(projectRoot, "package.json");
|
|
7768
|
+
if (!fs29.existsSync(pkgJsonPath)) return [];
|
|
7633
7769
|
const stacks = ["node"];
|
|
7634
7770
|
try {
|
|
7635
|
-
const pkgJson = JSON.parse(
|
|
7771
|
+
const pkgJson = JSON.parse(fs29.readFileSync(pkgJsonPath, "utf-8"));
|
|
7636
7772
|
const allDeps = { ...pkgJson.dependencies, ...pkgJson.devDependencies };
|
|
7637
7773
|
stacks.push(...nodeSubStacks(allDeps));
|
|
7638
7774
|
} catch {
|
|
@@ -7641,10 +7777,10 @@ function detectNodeStacks(projectRoot) {
|
|
|
7641
7777
|
}
|
|
7642
7778
|
function detectStack(projectRoot) {
|
|
7643
7779
|
const stacks = [...detectNodeStacks(projectRoot)];
|
|
7644
|
-
if (
|
|
7780
|
+
if (fs29.existsSync(path26.join(projectRoot, "go.mod"))) {
|
|
7645
7781
|
stacks.push("go");
|
|
7646
7782
|
}
|
|
7647
|
-
const hasPython =
|
|
7783
|
+
const hasPython = fs29.existsSync(path26.join(projectRoot, "requirements.txt")) || fs29.existsSync(path26.join(projectRoot, "pyproject.toml"));
|
|
7648
7784
|
if (hasPython) {
|
|
7649
7785
|
stacks.push("python");
|
|
7650
7786
|
}
|
|
@@ -7667,7 +7803,7 @@ function createOsvClient(options = {}) {
|
|
|
7667
7803
|
return {
|
|
7668
7804
|
async check(pkg) {
|
|
7669
7805
|
const cacheKey = `${pkg.ecosystem}-${sanitizePkgName(pkg.name)}@${pkg.version ?? "latest"}.json`;
|
|
7670
|
-
const cachePath =
|
|
7806
|
+
const cachePath = path27.join(cacheDir, cacheKey);
|
|
7671
7807
|
const cached = await readCache(cachePath, ttlMs);
|
|
7672
7808
|
if (cached) {
|
|
7673
7809
|
return { ...cached, source: "cache" };
|
|
@@ -7688,7 +7824,7 @@ function createOsvClient(options = {}) {
|
|
|
7688
7824
|
},
|
|
7689
7825
|
async clearCache() {
|
|
7690
7826
|
try {
|
|
7691
|
-
await
|
|
7827
|
+
await fs31.promises.rm(cacheDir, { recursive: true, force: true });
|
|
7692
7828
|
} catch {
|
|
7693
7829
|
}
|
|
7694
7830
|
}
|
|
@@ -7724,9 +7860,9 @@ function classify(advisories) {
|
|
|
7724
7860
|
}
|
|
7725
7861
|
async function readCache(cachePath, ttlMs) {
|
|
7726
7862
|
try {
|
|
7727
|
-
const stat2 = await
|
|
7863
|
+
const stat2 = await fs31.promises.stat(cachePath);
|
|
7728
7864
|
if (Date.now() - stat2.mtimeMs > ttlMs) return null;
|
|
7729
|
-
const buf = await
|
|
7865
|
+
const buf = await fs31.promises.readFile(cachePath, "utf-8");
|
|
7730
7866
|
const parsed = JSON.parse(buf);
|
|
7731
7867
|
return { malicious: parsed.malicious ?? [], other: parsed.other ?? [] };
|
|
7732
7868
|
} catch {
|
|
@@ -7735,8 +7871,8 @@ async function readCache(cachePath, ttlMs) {
|
|
|
7735
7871
|
}
|
|
7736
7872
|
async function writeCache(cachePath, payload) {
|
|
7737
7873
|
try {
|
|
7738
|
-
await
|
|
7739
|
-
await
|
|
7874
|
+
await fs31.promises.mkdir(path27.dirname(cachePath), { recursive: true });
|
|
7875
|
+
await fs31.promises.writeFile(cachePath, JSON.stringify(payload, null, 2), "utf-8");
|
|
7740
7876
|
} catch {
|
|
7741
7877
|
}
|
|
7742
7878
|
}
|
|
@@ -7772,7 +7908,7 @@ function getInjectionPatterns() {
|
|
|
7772
7908
|
}
|
|
7773
7909
|
function getTaintFilePath(projectRoot, sessionId) {
|
|
7774
7910
|
const id = sessionId || DEFAULT_SESSION_ID;
|
|
7775
|
-
return
|
|
7911
|
+
return join39(projectRoot, ".harness", `session-taint-${id}.json`);
|
|
7776
7912
|
}
|
|
7777
7913
|
function readTaint(projectRoot, sessionId) {
|
|
7778
7914
|
const filePath = getTaintFilePath(projectRoot, sessionId);
|
|
@@ -7854,14 +7990,14 @@ function clearTaint(projectRoot, sessionId) {
|
|
|
7854
7990
|
return 0;
|
|
7855
7991
|
}
|
|
7856
7992
|
}
|
|
7857
|
-
const harnessDir =
|
|
7993
|
+
const harnessDir = join39(projectRoot, ".harness");
|
|
7858
7994
|
let count = 0;
|
|
7859
7995
|
try {
|
|
7860
7996
|
const files = readdirSync3(harnessDir);
|
|
7861
7997
|
for (const file of files) {
|
|
7862
7998
|
if (file.startsWith("session-taint-") && file.endsWith(".json")) {
|
|
7863
7999
|
try {
|
|
7864
|
-
unlinkSync2(
|
|
8000
|
+
unlinkSync2(join39(harnessDir, file));
|
|
7865
8001
|
count++;
|
|
7866
8002
|
} catch {
|
|
7867
8003
|
}
|
|
@@ -7872,7 +8008,7 @@ function clearTaint(projectRoot, sessionId) {
|
|
|
7872
8008
|
return count;
|
|
7873
8009
|
}
|
|
7874
8010
|
function listTaintedSessions(projectRoot) {
|
|
7875
|
-
const harnessDir =
|
|
8011
|
+
const harnessDir = join39(projectRoot, ".harness");
|
|
7876
8012
|
const sessions = [];
|
|
7877
8013
|
try {
|
|
7878
8014
|
const files = readdirSync3(harnessDir);
|
|
@@ -7944,7 +8080,7 @@ function securityFindingId(finding) {
|
|
|
7944
8080
|
}
|
|
7945
8081
|
async function runValidateCheck(projectRoot, config) {
|
|
7946
8082
|
const issues = [];
|
|
7947
|
-
const agentsPath =
|
|
8083
|
+
const agentsPath = path28.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
|
|
7948
8084
|
const result = await validateAgentsMap(agentsPath);
|
|
7949
8085
|
if (!result.ok) {
|
|
7950
8086
|
issues.push({ severity: "error", message: result.error.message });
|
|
@@ -8001,7 +8137,7 @@ async function runDepsCheck(projectRoot, config) {
|
|
|
8001
8137
|
}
|
|
8002
8138
|
async function runDocsCheck(projectRoot, config) {
|
|
8003
8139
|
const issues = [];
|
|
8004
|
-
const docsDir =
|
|
8140
|
+
const docsDir = path28.join(projectRoot, config.docsDir ?? "docs");
|
|
8005
8141
|
const entropyConfig = config.entropy || {};
|
|
8006
8142
|
const result = await checkDocCoverage("project", {
|
|
8007
8143
|
docsDir,
|
|
@@ -8180,7 +8316,7 @@ async function runTraceabilityCheck(projectRoot, config) {
|
|
|
8180
8316
|
const issues = [];
|
|
8181
8317
|
const traceConfig = config.traceability || {};
|
|
8182
8318
|
if (traceConfig.enabled === false) return issues;
|
|
8183
|
-
const graphDir =
|
|
8319
|
+
const graphDir = path28.join(projectRoot, ".harness", "graph");
|
|
8184
8320
|
const store = new GraphStore();
|
|
8185
8321
|
const loaded = await store.load(graphDir);
|
|
8186
8322
|
if (!loaded) {
|
|
@@ -8358,7 +8494,7 @@ async function runMechanicalChecks(options) {
|
|
|
8358
8494
|
};
|
|
8359
8495
|
if (!skip.includes("validate")) {
|
|
8360
8496
|
try {
|
|
8361
|
-
const agentsPath =
|
|
8497
|
+
const agentsPath = path29.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
|
|
8362
8498
|
const result = await validateAgentsMap(agentsPath);
|
|
8363
8499
|
if (!result.ok) {
|
|
8364
8500
|
statuses.validate = "fail";
|
|
@@ -8395,7 +8531,7 @@ async function runMechanicalChecks(options) {
|
|
|
8395
8531
|
statuses.validate = "fail";
|
|
8396
8532
|
findings.push({
|
|
8397
8533
|
tool: "validate",
|
|
8398
|
-
file:
|
|
8534
|
+
file: path29.join(projectRoot, "AGENTS.md"),
|
|
8399
8535
|
message: err instanceof Error ? err.message : String(err),
|
|
8400
8536
|
severity: "error"
|
|
8401
8537
|
});
|
|
@@ -8459,7 +8595,7 @@ async function runMechanicalChecks(options) {
|
|
|
8459
8595
|
(async () => {
|
|
8460
8596
|
const localFindings = [];
|
|
8461
8597
|
try {
|
|
8462
|
-
const docsDir =
|
|
8598
|
+
const docsDir = path29.join(projectRoot, config.docsDir ?? "docs");
|
|
8463
8599
|
const result = await checkDocCoverage("project", { docsDir });
|
|
8464
8600
|
if (!result.ok) {
|
|
8465
8601
|
statuses["check-docs"] = "warn";
|
|
@@ -8486,7 +8622,7 @@ async function runMechanicalChecks(options) {
|
|
|
8486
8622
|
statuses["check-docs"] = "warn";
|
|
8487
8623
|
localFindings.push({
|
|
8488
8624
|
tool: "check-docs",
|
|
8489
|
-
file:
|
|
8625
|
+
file: path29.join(projectRoot, "docs"),
|
|
8490
8626
|
message: err instanceof Error ? err.message : String(err),
|
|
8491
8627
|
severity: "warning"
|
|
8492
8628
|
});
|
|
@@ -8583,18 +8719,18 @@ function computeContextBudget(diffLines) {
|
|
|
8583
8719
|
return diffLines;
|
|
8584
8720
|
}
|
|
8585
8721
|
function isWithinProject(absPath, projectRoot) {
|
|
8586
|
-
const resolvedRoot =
|
|
8587
|
-
const resolvedPath =
|
|
8588
|
-
return resolvedPath.startsWith(resolvedRoot) || resolvedPath ===
|
|
8722
|
+
const resolvedRoot = path30.resolve(projectRoot) + path30.sep;
|
|
8723
|
+
const resolvedPath = path30.resolve(absPath);
|
|
8724
|
+
return resolvedPath.startsWith(resolvedRoot) || resolvedPath === path30.resolve(projectRoot);
|
|
8589
8725
|
}
|
|
8590
8726
|
async function readContextFile(projectRoot, filePath, reason) {
|
|
8591
|
-
const absPath =
|
|
8727
|
+
const absPath = path30.isAbsolute(filePath) ? filePath : path30.join(projectRoot, filePath);
|
|
8592
8728
|
if (!isWithinProject(absPath, projectRoot)) return null;
|
|
8593
8729
|
const result = await readFileContent(absPath);
|
|
8594
8730
|
if (!result.ok) return null;
|
|
8595
8731
|
const content = result.value;
|
|
8596
8732
|
const lines = content.split("\n").length;
|
|
8597
|
-
const relPath2 =
|
|
8733
|
+
const relPath2 = path30.isAbsolute(filePath) ? relativePosix(projectRoot, filePath) : filePath;
|
|
8598
8734
|
return { path: relPath2, content, reason, lines };
|
|
8599
8735
|
}
|
|
8600
8736
|
function extractImportSources2(content) {
|
|
@@ -8609,24 +8745,24 @@ function extractImportSources2(content) {
|
|
|
8609
8745
|
}
|
|
8610
8746
|
async function resolveImportPath(projectRoot, fromFile, importSource) {
|
|
8611
8747
|
if (!importSource.startsWith(".")) return null;
|
|
8612
|
-
const fromDir =
|
|
8613
|
-
const basePath =
|
|
8748
|
+
const fromDir = path30.dirname(path30.join(projectRoot, fromFile));
|
|
8749
|
+
const basePath = path30.resolve(fromDir, importSource);
|
|
8614
8750
|
if (!isWithinProject(basePath, projectRoot)) return null;
|
|
8615
8751
|
const relBase = relativePosix(projectRoot, basePath);
|
|
8616
|
-
const sourceExt =
|
|
8752
|
+
const sourceExt = path30.extname(relBase);
|
|
8617
8753
|
const candidates = [];
|
|
8618
8754
|
const fallbacks = JS_EXT_FALLBACKS[sourceExt];
|
|
8619
8755
|
if (fallbacks) {
|
|
8620
8756
|
const stripped = relBase.slice(0, -sourceExt.length);
|
|
8621
8757
|
for (const ext of fallbacks) candidates.push(stripped + ext);
|
|
8622
|
-
candidates.push(
|
|
8623
|
-
candidates.push(
|
|
8624
|
-
candidates.push(
|
|
8758
|
+
candidates.push(path30.join(stripped, "index.ts"));
|
|
8759
|
+
candidates.push(path30.join(stripped, "index.tsx"));
|
|
8760
|
+
candidates.push(path30.join(stripped, "index.jsx"));
|
|
8625
8761
|
}
|
|
8626
8762
|
candidates.push(relBase + ".ts", relBase + ".tsx", relBase + ".mts");
|
|
8627
|
-
candidates.push(
|
|
8763
|
+
candidates.push(path30.join(relBase, "index.ts"));
|
|
8628
8764
|
for (const candidate of candidates) {
|
|
8629
|
-
const absCandidate =
|
|
8765
|
+
const absCandidate = path30.join(projectRoot, candidate);
|
|
8630
8766
|
if (await fileExists(absCandidate)) {
|
|
8631
8767
|
return candidate;
|
|
8632
8768
|
}
|
|
@@ -8634,7 +8770,7 @@ async function resolveImportPath(projectRoot, fromFile, importSource) {
|
|
|
8634
8770
|
return null;
|
|
8635
8771
|
}
|
|
8636
8772
|
async function findTestFiles(projectRoot, sourceFile) {
|
|
8637
|
-
const baseName =
|
|
8773
|
+
const baseName = path30.basename(sourceFile, path30.extname(sourceFile));
|
|
8638
8774
|
const pattern = `**/${baseName}.{test,spec}.{ts,tsx,mts}`;
|
|
8639
8775
|
const results = await findFiles(pattern, projectRoot);
|
|
8640
8776
|
return results.map((f) => relativePosix(projectRoot, f));
|
|
@@ -9370,6 +9506,383 @@ function runLearningsAgent(bundle) {
|
|
|
9370
9506
|
const allEntries = learningsContextFiles.flatMap((lcf) => parseLearningEntries(lcf.content));
|
|
9371
9507
|
return allEntries.map((entry) => matchEntryToFiles(entry, changedPaths)).filter((f) => f !== null);
|
|
9372
9508
|
}
|
|
9509
|
+
function scan(bundle, predicate) {
|
|
9510
|
+
const matches = [];
|
|
9511
|
+
for (const cf of bundle.changedFiles) {
|
|
9512
|
+
const lines = cf.content.split("\n");
|
|
9513
|
+
for (let i = 0; i < lines.length; i++) {
|
|
9514
|
+
const line = lines[i];
|
|
9515
|
+
if (predicate(line)) matches.push({ file: cf.path, line: i + 1, snippet: line.trim() });
|
|
9516
|
+
}
|
|
9517
|
+
}
|
|
9518
|
+
return matches;
|
|
9519
|
+
}
|
|
9520
|
+
function makeFinding2(category, match, title, rationale, suggestion, confidence) {
|
|
9521
|
+
return {
|
|
9522
|
+
id: makeFindingId("adversarial", match.file, match.line, `${category} ${title}`),
|
|
9523
|
+
file: match.file,
|
|
9524
|
+
lineRange: [match.line, match.line],
|
|
9525
|
+
domain: "bug",
|
|
9526
|
+
severity: confidence >= 75 ? "important" : "suggestion",
|
|
9527
|
+
title,
|
|
9528
|
+
rationale,
|
|
9529
|
+
suggestion,
|
|
9530
|
+
evidence: [`Line ${match.line}: ${match.snippet}`],
|
|
9531
|
+
validatedBy: "heuristic",
|
|
9532
|
+
subagent: "adversarial",
|
|
9533
|
+
confidence
|
|
9534
|
+
};
|
|
9535
|
+
}
|
|
9536
|
+
function detectAssumptionViolations(bundle) {
|
|
9537
|
+
return scan(bundle, (line) => UNVERIFIED_INPUT_PATTERN.test(line)).map(
|
|
9538
|
+
(m) => makeFinding2(
|
|
9539
|
+
"assumption",
|
|
9540
|
+
m,
|
|
9541
|
+
"JSON.parse on untrusted input without shape validation",
|
|
9542
|
+
"The diff assumes the parsed value matches an expected schema. Malformed JSON throws synchronously; structurally-valid-but-semantically-wrong JSON propagates downstream as a malformed object.",
|
|
9543
|
+
"Validate the parsed value against a schema (zod, valibot) or guard each access with a narrow type predicate before use.",
|
|
9544
|
+
75
|
|
9545
|
+
)
|
|
9546
|
+
);
|
|
9547
|
+
}
|
|
9548
|
+
function detectCompositionFailures(bundle) {
|
|
9549
|
+
const findings = [];
|
|
9550
|
+
findings.push(
|
|
9551
|
+
...scan(bundle, (line) => COMPOSE_NO_AWAIT.test(line) && !line.includes("await ")).map(
|
|
9552
|
+
(m) => makeFinding2(
|
|
9553
|
+
"composition",
|
|
9554
|
+
m,
|
|
9555
|
+
"Floating Promise chain without await or .catch",
|
|
9556
|
+
"A `.then(...)` chain without `await` or `.catch(...)` produces an unhandled rejection if any link rejects. Composing two such chains lets a failure in one silently delay or stall the other.",
|
|
9557
|
+
"Await the chain or attach `.catch(handler)` and verify the handler is observable.",
|
|
9558
|
+
75
|
|
9559
|
+
)
|
|
9560
|
+
)
|
|
9561
|
+
);
|
|
9562
|
+
findings.push(
|
|
9563
|
+
...scan(bundle, (line) => UNGUARDED_OPTIONAL_CALL.test(line)).map(
|
|
9564
|
+
(m) => makeFinding2(
|
|
9565
|
+
"composition",
|
|
9566
|
+
m,
|
|
9567
|
+
"Optional chain over a callable masks composition failure",
|
|
9568
|
+
"Calling `foo?.foo(...)` silently becomes `undefined` when the receiver is nil. When composed with downstream code that expects a value, this propagates as a confusing `undefined` rather than a clear error.",
|
|
9569
|
+
"Decide explicitly: either guard up front and throw a meaningful error, or rename the variable so the optionality is visible at the call site.",
|
|
9570
|
+
50
|
|
9571
|
+
)
|
|
9572
|
+
)
|
|
9573
|
+
);
|
|
9574
|
+
return findings;
|
|
9575
|
+
}
|
|
9576
|
+
function detectAbuseCases(bundle) {
|
|
9577
|
+
return scan(
|
|
9578
|
+
bundle,
|
|
9579
|
+
(line) => FETCH_NO_TIMEOUT.test(line) && !line.includes("AbortController")
|
|
9580
|
+
).map(
|
|
9581
|
+
(m) => makeFinding2(
|
|
9582
|
+
"abuse",
|
|
9583
|
+
m,
|
|
9584
|
+
"fetch() without timeout or AbortController",
|
|
9585
|
+
"A `fetch` with no timeout allows a slow or malicious upstream to hold a request open indefinitely. Multiple such calls can exhaust the calling pool.",
|
|
9586
|
+
"Pass `signal: AbortSignal.timeout(ms)` (Node 18+) or wire an `AbortController` with an explicit timeout.",
|
|
9587
|
+
100
|
|
9588
|
+
)
|
|
9589
|
+
);
|
|
9590
|
+
}
|
|
9591
|
+
function detectCascades(bundle) {
|
|
9592
|
+
return scan(bundle, (line) => PROMISE_NO_REJECT_PATTERN.test(line)).map(
|
|
9593
|
+
(m) => makeFinding2(
|
|
9594
|
+
"cascade",
|
|
9595
|
+
m,
|
|
9596
|
+
"new Promise constructor without a reject parameter",
|
|
9597
|
+
"A Promise that only captures `resolve` cannot signal failure. Errors thrown inside the executor become unhandled, and downstream chains await indefinitely if no `resolve` path is reached.",
|
|
9598
|
+
"Add a `reject` parameter and forward thrown errors, or refactor to an `async` function that returns the value or throws.",
|
|
9599
|
+
100
|
|
9600
|
+
)
|
|
9601
|
+
);
|
|
9602
|
+
}
|
|
9603
|
+
function runAdversarialAgent(bundle, options = {}) {
|
|
9604
|
+
const { runCascades = true } = options;
|
|
9605
|
+
const findings = [];
|
|
9606
|
+
findings.push(...detectAssumptionViolations(bundle));
|
|
9607
|
+
findings.push(...detectCompositionFailures(bundle));
|
|
9608
|
+
findings.push(...detectAbuseCases(bundle));
|
|
9609
|
+
if (runCascades) findings.push(...detectCascades(bundle));
|
|
9610
|
+
return findings;
|
|
9611
|
+
}
|
|
9612
|
+
function isProductionTsFile(path52) {
|
|
9613
|
+
if (!/\.(ts|tsx)$/.test(path52)) return false;
|
|
9614
|
+
if (/\.d\.ts$/.test(path52)) return false;
|
|
9615
|
+
if (/\.(test|spec)\.(ts|tsx)$/.test(path52)) return false;
|
|
9616
|
+
if (/__tests__\//.test(path52)) return false;
|
|
9617
|
+
return true;
|
|
9618
|
+
}
|
|
9619
|
+
function isProductionFile(path52) {
|
|
9620
|
+
return isProductionTsFile(path52);
|
|
9621
|
+
}
|
|
9622
|
+
function scan2(bundle, predicate) {
|
|
9623
|
+
const matches = [];
|
|
9624
|
+
for (const cf of bundle.changedFiles) {
|
|
9625
|
+
if (!isProductionFile(cf.path)) continue;
|
|
9626
|
+
const lines = cf.content.split("\n");
|
|
9627
|
+
for (let i = 0; i < lines.length; i++) {
|
|
9628
|
+
const line = lines[i];
|
|
9629
|
+
const codePart = stripLineComment(line);
|
|
9630
|
+
if (predicate(codePart) || predicate(line)) {
|
|
9631
|
+
matches.push({ file: cf.path, line: i + 1, snippet: line.trim() });
|
|
9632
|
+
}
|
|
9633
|
+
}
|
|
9634
|
+
}
|
|
9635
|
+
return matches;
|
|
9636
|
+
}
|
|
9637
|
+
function stripLineComment(line) {
|
|
9638
|
+
const idx = line.indexOf("//");
|
|
9639
|
+
if (idx === -1) return line;
|
|
9640
|
+
if (TS_IGNORE_PATTERN.test(line)) return line;
|
|
9641
|
+
return line.slice(0, idx);
|
|
9642
|
+
}
|
|
9643
|
+
function detectExplicitAny(bundle) {
|
|
9644
|
+
return scan2(bundle, (codeLine) => {
|
|
9645
|
+
const cleaned = codeLine.replace(/(['"`])[^'"`]*\1/g, '""');
|
|
9646
|
+
return /:\s*any(?![A-Za-z0-9_$])/.test(cleaned) || /\bas\s+any(?![A-Za-z0-9_$])/.test(cleaned);
|
|
9647
|
+
}).map((m) => ({
|
|
9648
|
+
id: makeFindingId("ts-strict", m.file, m.line, "explicit any"),
|
|
9649
|
+
file: m.file,
|
|
9650
|
+
lineRange: [m.line, m.line],
|
|
9651
|
+
domain: "bug",
|
|
9652
|
+
severity: "important",
|
|
9653
|
+
title: "Explicit `any` disables the type checker",
|
|
9654
|
+
rationale: "An `any`-typed value bypasses every downstream check. Future callers receive no help from the compiler when they pass the wrong shape.",
|
|
9655
|
+
suggestion: "Replace `any` with the narrowest type that compiles \u2014 a union, `unknown` with a type predicate, or a generic. If the shape is truly external, parse it with a schema (zod, valibot) at the boundary.",
|
|
9656
|
+
evidence: [`Line ${m.line}: ${m.snippet}`],
|
|
9657
|
+
validatedBy: "heuristic",
|
|
9658
|
+
subagent: "typescript-strict",
|
|
9659
|
+
confidence: 100
|
|
9660
|
+
})).filter((f) => EXPLICIT_ANY_PATTERN.test(f.evidence[0]));
|
|
9661
|
+
}
|
|
9662
|
+
function detectTsIgnore(bundle) {
|
|
9663
|
+
return scan2(bundle, (line) => TS_IGNORE_PATTERN.test(line)).map((m) => ({
|
|
9664
|
+
id: makeFindingId("ts-strict", m.file, m.line, "ts ignore"),
|
|
9665
|
+
file: m.file,
|
|
9666
|
+
lineRange: [m.line, m.line],
|
|
9667
|
+
domain: "bug",
|
|
9668
|
+
severity: "important",
|
|
9669
|
+
title: "ts-ignore / ts-expect-error suppresses a type error",
|
|
9670
|
+
rationale: "Suppressing a type error hides a real signal from the compiler. The reason for the suppression is rarely captured, and the original error is invisible at the call site.",
|
|
9671
|
+
suggestion: "Either remove the directive and fix the underlying type, or replace it with a typed assertion accompanied by a comment that names the invariant being asserted.",
|
|
9672
|
+
evidence: [`Line ${m.line}: ${m.snippet}`],
|
|
9673
|
+
validatedBy: "heuristic",
|
|
9674
|
+
subagent: "typescript-strict",
|
|
9675
|
+
confidence: 100
|
|
9676
|
+
}));
|
|
9677
|
+
}
|
|
9678
|
+
function detectUnsafeAsUnknown(bundle) {
|
|
9679
|
+
return scan2(bundle, (line) => UNSAFE_AS_UNKNOWN.test(line)).map((m) => ({
|
|
9680
|
+
id: makeFindingId("ts-strict", m.file, m.line, "as unknown as"),
|
|
9681
|
+
file: m.file,
|
|
9682
|
+
lineRange: [m.line, m.line],
|
|
9683
|
+
domain: "bug",
|
|
9684
|
+
severity: "important",
|
|
9685
|
+
title: "`as unknown as X` is a double-cast escape hatch",
|
|
9686
|
+
rationale: "The pattern bypasses the type system entirely. If the runtime shape diverges from X, downstream code crashes with a type-confusion error that points at the wrong line.",
|
|
9687
|
+
suggestion: "Parse the value at the boundary with a schema, or refine via a narrowing predicate so the cast becomes unnecessary.",
|
|
9688
|
+
evidence: [`Line ${m.line}: ${m.snippet}`],
|
|
9689
|
+
validatedBy: "heuristic",
|
|
9690
|
+
subagent: "typescript-strict",
|
|
9691
|
+
confidence: 100
|
|
9692
|
+
}));
|
|
9693
|
+
}
|
|
9694
|
+
function detectNonNullAssertion(bundle) {
|
|
9695
|
+
return scan2(bundle, (line) => NON_NULL_ASSERTION.test(line)).filter((m) => !/process\.env\./.test(m.snippet)).map((m) => ({
|
|
9696
|
+
id: makeFindingId("ts-strict", m.file, m.line, "non null assertion"),
|
|
9697
|
+
file: m.file,
|
|
9698
|
+
lineRange: [m.line, m.line],
|
|
9699
|
+
domain: "bug",
|
|
9700
|
+
severity: "suggestion",
|
|
9701
|
+
title: "Non-null assertion (`!`) bypasses the null check",
|
|
9702
|
+
rationale: "The `!` operator promises the compiler that the value is not nullish. If that promise is wrong, the failure shows up as a `Cannot read properties of undefined` at runtime far from the assertion.",
|
|
9703
|
+
suggestion: "Guard with an explicit `if (value == null) throw new Error(...)` so the assertion is verified at runtime, or refactor the producer to return a non-nullable type.",
|
|
9704
|
+
evidence: [`Line ${m.line}: ${m.snippet}`],
|
|
9705
|
+
validatedBy: "heuristic",
|
|
9706
|
+
subagent: "typescript-strict",
|
|
9707
|
+
confidence: 75
|
|
9708
|
+
}));
|
|
9709
|
+
}
|
|
9710
|
+
function detectLargeFileGrowth(bundle) {
|
|
9711
|
+
return bundle.changedFiles.filter((cf) => isProductionFile(cf.path) && cf.lines > 400).map((cf) => ({
|
|
9712
|
+
id: makeFindingId("ts-strict", cf.path, 1, "file growth"),
|
|
9713
|
+
file: cf.path,
|
|
9714
|
+
lineRange: [1, cf.lines],
|
|
9715
|
+
domain: "architecture",
|
|
9716
|
+
severity: "suggestion",
|
|
9717
|
+
title: `File grew to ${cf.lines} lines \u2014 Single Responsibility risk`,
|
|
9718
|
+
rationale: "Past 400 lines a single TypeScript file typically encodes more than one responsibility. Adding to it compounds the burden on future readers and reviewers.",
|
|
9719
|
+
suggestion: "Identify two responsibilities and split the larger one into a new module. A new file with a clear name reads better than a longer file with vague boundaries.",
|
|
9720
|
+
evidence: [`File length: ${cf.lines} lines`],
|
|
9721
|
+
validatedBy: "heuristic",
|
|
9722
|
+
subagent: "typescript-strict",
|
|
9723
|
+
confidence: 50
|
|
9724
|
+
}));
|
|
9725
|
+
}
|
|
9726
|
+
function detectVagueNames(bundle) {
|
|
9727
|
+
return scan2(bundle, (line) => VAGUE_HELPER_NAME.test(line)).map((m) => ({
|
|
9728
|
+
id: makeFindingId("ts-strict", m.file, m.line, "vague name"),
|
|
9729
|
+
file: m.file,
|
|
9730
|
+
lineRange: [m.line, m.line],
|
|
9731
|
+
domain: "bug",
|
|
9732
|
+
severity: "suggestion",
|
|
9733
|
+
title: "Vague function name \u2014 fails the five-second rule",
|
|
9734
|
+
rationale: "A name like `handle*` or `process*` or `doStuff` does not let a reader predict what the function does from a call site. Future maintainers must read the body to know \u2014 every time.",
|
|
9735
|
+
suggestion: "Rename to a verb-object phrase that says what changes (e.g., `applyDiscount`, `serializeOrder`). If you cannot name it, the function probably does more than one thing.",
|
|
9736
|
+
evidence: [`Line ${m.line}: ${m.snippet}`],
|
|
9737
|
+
validatedBy: "heuristic",
|
|
9738
|
+
subagent: "typescript-strict",
|
|
9739
|
+
confidence: 50
|
|
9740
|
+
}));
|
|
9741
|
+
}
|
|
9742
|
+
function runTypescriptStrictAgent(bundle) {
|
|
9743
|
+
const findings = [];
|
|
9744
|
+
findings.push(...detectExplicitAny(bundle));
|
|
9745
|
+
findings.push(...detectTsIgnore(bundle));
|
|
9746
|
+
findings.push(...detectUnsafeAsUnknown(bundle));
|
|
9747
|
+
findings.push(...detectNonNullAssertion(bundle));
|
|
9748
|
+
findings.push(...detectLargeFileGrowth(bundle));
|
|
9749
|
+
findings.push(...detectVagueNames(bundle));
|
|
9750
|
+
return findings;
|
|
9751
|
+
}
|
|
9752
|
+
function frontendFiles(bundle) {
|
|
9753
|
+
return bundle.changedFiles.filter((cf) => /\.(ts|tsx|jsx|js)$/.test(cf.path)).filter((cf) => !/\.(test|spec)\.(ts|tsx|js|jsx)$/.test(cf.path)).filter((cf) => !/__tests__\//.test(cf.path)).filter((cf) => !/\.d\.ts$/.test(cf.path)).map((cf) => ({ path: cf.path, content: cf.content, lines: cf.content.split("\n") }));
|
|
9754
|
+
}
|
|
9755
|
+
function detectUnclearedTimers(bundle) {
|
|
9756
|
+
const findings = [];
|
|
9757
|
+
for (const file of frontendFiles(bundle)) {
|
|
9758
|
+
const hasClearInterval = /\bclearInterval\s*\(/.test(file.content);
|
|
9759
|
+
const hasClearTimeout = /\bclearTimeout\s*\(/.test(file.content);
|
|
9760
|
+
for (let i = 0; i < file.lines.length; i++) {
|
|
9761
|
+
const line = file.lines[i];
|
|
9762
|
+
if (/\bsetInterval\s*\(/.test(line) && !hasClearInterval) {
|
|
9763
|
+
findings.push({
|
|
9764
|
+
id: makeFindingId("frontend-races", file.path, i + 1, "setInterval no clear"),
|
|
9765
|
+
file: file.path,
|
|
9766
|
+
lineRange: [i + 1, i + 1],
|
|
9767
|
+
domain: "bug",
|
|
9768
|
+
severity: "important",
|
|
9769
|
+
title: "setInterval without matching clearInterval in the same module",
|
|
9770
|
+
rationale: "An interval that is never cleared keeps firing after the owning component, controller, or scope is gone. Each render creates a new timer; old timers continue mutating state that no longer exists.",
|
|
9771
|
+
suggestion: "Capture the handle and clear it in the matching teardown path (useEffect return, controller `disconnect`, or explicit `dispose`). For one-shot work, prefer setTimeout with a clearTimeout in the cleanup path.",
|
|
9772
|
+
evidence: [`Line ${i + 1}: ${line.trim()}`],
|
|
9773
|
+
validatedBy: "heuristic",
|
|
9774
|
+
subagent: "frontend-races",
|
|
9775
|
+
confidence: 100
|
|
9776
|
+
});
|
|
9777
|
+
}
|
|
9778
|
+
if (/\bsetTimeout\s*\(/.test(line) && !hasClearTimeout && // ignore intentional fire-and-forget where the callback is a single bound function with no closure access
|
|
9779
|
+
!/setTimeout\s*\(\s*[A-Za-z_$][\w$]*\s*(?:,|\))/.test(line)) {
|
|
9780
|
+
findings.push({
|
|
9781
|
+
id: makeFindingId("frontend-races", file.path, i + 1, "setTimeout no clear"),
|
|
9782
|
+
file: file.path,
|
|
9783
|
+
lineRange: [i + 1, i + 1],
|
|
9784
|
+
domain: "bug",
|
|
9785
|
+
severity: "suggestion",
|
|
9786
|
+
title: "setTimeout with closure access but no clearTimeout in the same module",
|
|
9787
|
+
rationale: 'A timeout that closes over mutable state can fire after the owner is gone, updating stale references and triggering "set state on unmounted component" warnings.',
|
|
9788
|
+
suggestion: "Capture the timeout id and clear it in the teardown path, or extract a pure function and bind explicit arguments.",
|
|
9789
|
+
evidence: [`Line ${i + 1}: ${line.trim()}`],
|
|
9790
|
+
validatedBy: "heuristic",
|
|
9791
|
+
subagent: "frontend-races",
|
|
9792
|
+
confidence: 75
|
|
9793
|
+
});
|
|
9794
|
+
}
|
|
9795
|
+
}
|
|
9796
|
+
}
|
|
9797
|
+
return findings;
|
|
9798
|
+
}
|
|
9799
|
+
function detectDanglingListeners(bundle) {
|
|
9800
|
+
const findings = [];
|
|
9801
|
+
for (const file of frontendFiles(bundle)) {
|
|
9802
|
+
const hasRemove = /\bremoveEventListener\s*\(/.test(file.content);
|
|
9803
|
+
for (let i = 0; i < file.lines.length; i++) {
|
|
9804
|
+
const line = file.lines[i];
|
|
9805
|
+
if (/\baddEventListener\s*\(/.test(line) && !hasRemove) {
|
|
9806
|
+
findings.push({
|
|
9807
|
+
id: makeFindingId("frontend-races", file.path, i + 1, "listener no remove"),
|
|
9808
|
+
file: file.path,
|
|
9809
|
+
lineRange: [i + 1, i + 1],
|
|
9810
|
+
domain: "bug",
|
|
9811
|
+
severity: "important",
|
|
9812
|
+
title: "addEventListener without removeEventListener in the same module",
|
|
9813
|
+
rationale: "Event listeners that are not removed accumulate across mounts. Each mount adds another callback, and old callbacks fire against stale state.",
|
|
9814
|
+
suggestion: "Pair every `addEventListener` with a `removeEventListener` in the teardown path. In React, return the cleanup from `useEffect`; in Stimulus, remove in `disconnect()`.",
|
|
9815
|
+
evidence: [`Line ${i + 1}: ${line.trim()}`],
|
|
9816
|
+
validatedBy: "heuristic",
|
|
9817
|
+
subagent: "frontend-races",
|
|
9818
|
+
confidence: 100
|
|
9819
|
+
});
|
|
9820
|
+
}
|
|
9821
|
+
}
|
|
9822
|
+
}
|
|
9823
|
+
return findings;
|
|
9824
|
+
}
|
|
9825
|
+
function detectMisplacedStateUpdate(bundle) {
|
|
9826
|
+
const findings = [];
|
|
9827
|
+
const useEffectStateRe = /useEffect\s*\(\s*\(\)\s*=>\s*\{\s*set[A-Z]\w*\s*\(/;
|
|
9828
|
+
for (const file of frontendFiles(bundle)) {
|
|
9829
|
+
for (let i = 0; i < file.lines.length; i++) {
|
|
9830
|
+
const window = file.lines.slice(i, Math.min(i + 3, file.lines.length)).join("\n");
|
|
9831
|
+
if (useEffectStateRe.test(window)) {
|
|
9832
|
+
findings.push({
|
|
9833
|
+
id: makeFindingId("frontend-races", file.path, i + 1, "state in useEffect"),
|
|
9834
|
+
file: file.path,
|
|
9835
|
+
lineRange: [i + 1, Math.min(i + 3, file.lines.length)],
|
|
9836
|
+
domain: "bug",
|
|
9837
|
+
severity: "suggestion",
|
|
9838
|
+
title: "useEffect immediately calling setState may indicate derived state",
|
|
9839
|
+
rationale: "When an effect runs only to set state from props or render-time values, the same value can be derived inline. The effect adds a render, a stale-closure risk, and a window where the state and its source disagree.",
|
|
9840
|
+
suggestion: "Compute the value inline from props. Reserve `useEffect` for true side effects (subscriptions, IO).",
|
|
9841
|
+
evidence: [`Line ${i + 1}: ${file.lines[i].trim()}`],
|
|
9842
|
+
validatedBy: "heuristic",
|
|
9843
|
+
subagent: "frontend-races",
|
|
9844
|
+
confidence: 50
|
|
9845
|
+
});
|
|
9846
|
+
}
|
|
9847
|
+
}
|
|
9848
|
+
}
|
|
9849
|
+
return findings;
|
|
9850
|
+
}
|
|
9851
|
+
function detectStaleResponseRace(bundle) {
|
|
9852
|
+
const findings = [];
|
|
9853
|
+
for (const file of frontendFiles(bundle)) {
|
|
9854
|
+
for (let i = 0; i < file.lines.length - 4; i++) {
|
|
9855
|
+
const window = file.lines.slice(i, i + 5).join("\n");
|
|
9856
|
+
if (!/\bawait\s+(?:fetch|axios|api)/.test(window)) continue;
|
|
9857
|
+
if (!/\bset[A-Z]\w*\s*\(/.test(window)) continue;
|
|
9858
|
+
if (/AbortController|AbortSignal\.timeout|signal:/.test(window)) continue;
|
|
9859
|
+
findings.push({
|
|
9860
|
+
id: makeFindingId("frontend-races", file.path, i + 1, "stale response race"),
|
|
9861
|
+
file: file.path,
|
|
9862
|
+
lineRange: [i + 1, i + 5],
|
|
9863
|
+
domain: "bug",
|
|
9864
|
+
severity: "important",
|
|
9865
|
+
title: "await fetch followed by setState without an abort signal",
|
|
9866
|
+
rationale: "When the component unmounts (or re-renders with new inputs) before the response resolves, the late-arriving setState writes stale data onto a now-replaced component, causing flashes of wrong content or warnings about unmounted components.",
|
|
9867
|
+
suggestion: "Pass `signal: AbortSignal.timeout(ms)` or an `AbortController` and abort it in the cleanup path. Alternatively, ignore the response when a request-generation counter has advanced.",
|
|
9868
|
+
evidence: [`Lines ${i + 1}-${i + 5}`],
|
|
9869
|
+
validatedBy: "heuristic",
|
|
9870
|
+
subagent: "frontend-races",
|
|
9871
|
+
confidence: 75
|
|
9872
|
+
});
|
|
9873
|
+
i += 4;
|
|
9874
|
+
}
|
|
9875
|
+
}
|
|
9876
|
+
return findings;
|
|
9877
|
+
}
|
|
9878
|
+
function runFrontendRacesAgent(bundle) {
|
|
9879
|
+
const findings = [];
|
|
9880
|
+
findings.push(...detectUnclearedTimers(bundle));
|
|
9881
|
+
findings.push(...detectDanglingListeners(bundle));
|
|
9882
|
+
findings.push(...detectMisplacedStateUpdate(bundle));
|
|
9883
|
+
findings.push(...detectStaleResponseRace(bundle));
|
|
9884
|
+
return findings;
|
|
9885
|
+
}
|
|
9373
9886
|
async function runAgent(bundle) {
|
|
9374
9887
|
const start = Date.now();
|
|
9375
9888
|
const runner = AGENT_RUNNERS[bundle.domain];
|
|
@@ -9387,6 +9900,160 @@ async function fanOutReview(options) {
|
|
|
9387
9900
|
const results = await Promise.all(bundles.map((bundle) => runAgent(bundle)));
|
|
9388
9901
|
return results;
|
|
9389
9902
|
}
|
|
9903
|
+
async function fanOutConditionalSubagents(options) {
|
|
9904
|
+
const { bundles, activations, depth } = options;
|
|
9905
|
+
if (activations.size === 0 || bundles.length === 0) return [];
|
|
9906
|
+
const bugBundle = bundles.find((b) => b.domain === "bug") ?? bundles[0];
|
|
9907
|
+
const tasks = [];
|
|
9908
|
+
if (activations.has("adversarial")) {
|
|
9909
|
+
tasks.push(
|
|
9910
|
+
Promise.resolve().then(() => {
|
|
9911
|
+
const start = Date.now();
|
|
9912
|
+
const findings = runAdversarialAgent(bugBundle, { runCascades: depth === "deep" });
|
|
9913
|
+
return {
|
|
9914
|
+
subagent: "adversarial",
|
|
9915
|
+
findings,
|
|
9916
|
+
durationMs: Date.now() - start
|
|
9917
|
+
};
|
|
9918
|
+
})
|
|
9919
|
+
);
|
|
9920
|
+
}
|
|
9921
|
+
if (activations.has("typescript-strict")) {
|
|
9922
|
+
tasks.push(
|
|
9923
|
+
Promise.resolve().then(() => {
|
|
9924
|
+
const start = Date.now();
|
|
9925
|
+
const findings = runTypescriptStrictAgent(bugBundle);
|
|
9926
|
+
return {
|
|
9927
|
+
subagent: "typescript-strict",
|
|
9928
|
+
findings,
|
|
9929
|
+
durationMs: Date.now() - start
|
|
9930
|
+
};
|
|
9931
|
+
})
|
|
9932
|
+
);
|
|
9933
|
+
}
|
|
9934
|
+
if (activations.has("frontend-races")) {
|
|
9935
|
+
tasks.push(
|
|
9936
|
+
Promise.resolve().then(() => {
|
|
9937
|
+
const start = Date.now();
|
|
9938
|
+
const findings = runFrontendRacesAgent(bugBundle);
|
|
9939
|
+
return {
|
|
9940
|
+
subagent: "frontend-races",
|
|
9941
|
+
findings,
|
|
9942
|
+
durationMs: Date.now() - start
|
|
9943
|
+
};
|
|
9944
|
+
})
|
|
9945
|
+
);
|
|
9946
|
+
}
|
|
9947
|
+
return Promise.all(tasks);
|
|
9948
|
+
}
|
|
9949
|
+
function isExcludedPath(path52) {
|
|
9950
|
+
return EXCLUDED_PATH_PATTERNS.some((re) => re.test(path52));
|
|
9951
|
+
}
|
|
9952
|
+
function isProductionTsFile2(path52) {
|
|
9953
|
+
if (!/\.(ts|tsx)$/.test(path52)) return false;
|
|
9954
|
+
if (/\.d\.ts$/.test(path52)) return false;
|
|
9955
|
+
return !isExcludedPath(path52);
|
|
9956
|
+
}
|
|
9957
|
+
function countDiffLines(diffContent) {
|
|
9958
|
+
let count = 0;
|
|
9959
|
+
for (const line of diffContent.split("\n")) {
|
|
9960
|
+
if (line.length === 0) continue;
|
|
9961
|
+
const first = line[0];
|
|
9962
|
+
if (first === "+" && !line.startsWith("+++")) count++;
|
|
9963
|
+
else if (first === "-" && !line.startsWith("---")) count++;
|
|
9964
|
+
}
|
|
9965
|
+
return count;
|
|
9966
|
+
}
|
|
9967
|
+
function countChangedLines(diff2) {
|
|
9968
|
+
let total = 0;
|
|
9969
|
+
let counted = false;
|
|
9970
|
+
for (const [path52, content] of diff2.fileDiffs) {
|
|
9971
|
+
if (isExcludedPath(path52)) continue;
|
|
9972
|
+
total += countDiffLines(content);
|
|
9973
|
+
counted = true;
|
|
9974
|
+
}
|
|
9975
|
+
if (counted) return total;
|
|
9976
|
+
if (diff2.changedFiles.length === 0) return 0;
|
|
9977
|
+
const includedFiles = diff2.changedFiles.filter((p) => !isExcludedPath(p)).length;
|
|
9978
|
+
if (includedFiles === 0) return 0;
|
|
9979
|
+
const ratio = includedFiles / diff2.changedFiles.length;
|
|
9980
|
+
return Math.round(diff2.totalDiffLines * ratio);
|
|
9981
|
+
}
|
|
9982
|
+
function buildHaystack(diff2, commitMessage) {
|
|
9983
|
+
const parts = [commitMessage];
|
|
9984
|
+
for (const [path52, content] of diff2.fileDiffs) {
|
|
9985
|
+
parts.push(path52);
|
|
9986
|
+
parts.push(content);
|
|
9987
|
+
}
|
|
9988
|
+
if (diff2.fileDiffs.size === 0) {
|
|
9989
|
+
parts.push(...diff2.changedFiles);
|
|
9990
|
+
}
|
|
9991
|
+
return parts.join("\n");
|
|
9992
|
+
}
|
|
9993
|
+
function keywordMatches(keyword, haystack) {
|
|
9994
|
+
const lowerKey = keyword.toLowerCase();
|
|
9995
|
+
const lowerHay = haystack.toLowerCase();
|
|
9996
|
+
if (keyword.includes(" ")) {
|
|
9997
|
+
return lowerHay.includes(lowerKey);
|
|
9998
|
+
}
|
|
9999
|
+
let idx = lowerHay.indexOf(lowerKey);
|
|
10000
|
+
while (idx !== -1) {
|
|
10001
|
+
const before = idx === 0 ? "" : lowerHay[idx - 1];
|
|
10002
|
+
const after = lowerHay[idx + lowerKey.length] ?? "";
|
|
10003
|
+
const leftOk = before === "" || !WORD_BOUNDARY.test(before);
|
|
10004
|
+
const rightOk = after === "" || !WORD_BOUNDARY.test(after);
|
|
10005
|
+
if (leftOk && rightOk) return true;
|
|
10006
|
+
idx = lowerHay.indexOf(lowerKey, idx + 1);
|
|
10007
|
+
}
|
|
10008
|
+
return false;
|
|
10009
|
+
}
|
|
10010
|
+
function detectRiskKeywords(diff2, commitMessage) {
|
|
10011
|
+
const haystack = buildHaystack(diff2, commitMessage);
|
|
10012
|
+
const matched = [];
|
|
10013
|
+
for (const keyword of RISK_KEYWORDS) {
|
|
10014
|
+
if (keywordMatches(keyword, haystack)) matched.push(keyword);
|
|
10015
|
+
}
|
|
10016
|
+
return matched;
|
|
10017
|
+
}
|
|
10018
|
+
function computeDepth(changedLines, riskKeywordCount) {
|
|
10019
|
+
if (changedLines >= 200 || riskKeywordCount >= 2) return "deep";
|
|
10020
|
+
if (changedLines < 50 && riskKeywordCount === 0) return "quick";
|
|
10021
|
+
return "standard";
|
|
10022
|
+
}
|
|
10023
|
+
function computeActivations(depth, diff2, overridden) {
|
|
10024
|
+
if (overridden && depth === "deep") {
|
|
10025
|
+
return /* @__PURE__ */ new Set(["adversarial", "typescript-strict", "frontend-races"]);
|
|
10026
|
+
}
|
|
10027
|
+
const activations = /* @__PURE__ */ new Set();
|
|
10028
|
+
if (depth !== "quick") activations.add("adversarial");
|
|
10029
|
+
const hasTsFile = diff2.changedFiles.some(isProductionTsFile2);
|
|
10030
|
+
if (hasTsFile) activations.add("typescript-strict");
|
|
10031
|
+
if (activations.has("typescript-strict") && hasAsyncUiSignal(diff2)) {
|
|
10032
|
+
activations.add("frontend-races");
|
|
10033
|
+
}
|
|
10034
|
+
return activations;
|
|
10035
|
+
}
|
|
10036
|
+
function hasAsyncUiSignal(diff2) {
|
|
10037
|
+
const tsxInDiff = diff2.changedFiles.some((p) => /\.tsx$/.test(p) && !isExcludedPath(p));
|
|
10038
|
+
if (tsxInDiff) return true;
|
|
10039
|
+
for (const [, content] of diff2.fileDiffs) {
|
|
10040
|
+
for (const signal of ASYNC_UI_SIGNALS) {
|
|
10041
|
+
if (signal === ".tsx") continue;
|
|
10042
|
+
if (content.includes(signal)) return true;
|
|
10043
|
+
}
|
|
10044
|
+
}
|
|
10045
|
+
return false;
|
|
10046
|
+
}
|
|
10047
|
+
function calibrateDepth(options) {
|
|
10048
|
+
const { diff: diff2, commitMessage, override } = options;
|
|
10049
|
+
const changedLines = countChangedLines(diff2);
|
|
10050
|
+
const riskSignals = detectRiskKeywords(diff2, commitMessage);
|
|
10051
|
+
const computed = computeDepth(changedLines, riskSignals.length);
|
|
10052
|
+
const depth = override ?? computed;
|
|
10053
|
+
const overridden = override !== void 0;
|
|
10054
|
+
const activations = computeActivations(depth, diff2, overridden);
|
|
10055
|
+
return { depth, changedLines, riskSignals, activations, overridden };
|
|
10056
|
+
}
|
|
9390
10057
|
function extractCrossFileRefs(finding) {
|
|
9391
10058
|
const refs = [];
|
|
9392
10059
|
const crossFilePattern = /([^\s]+\.(?:ts|tsx|js|jsx))\s+affects\s+([^\s]+\.(?:ts|tsx|js|jsx))/i;
|
|
@@ -9402,7 +10069,7 @@ function normalizePath(filePath, projectRoot) {
|
|
|
9402
10069
|
let normalized = filePath;
|
|
9403
10070
|
normalized = normalized.replace(/\\/g, "/");
|
|
9404
10071
|
const normalizedRoot = projectRoot.replace(/\\/g, "/");
|
|
9405
|
-
if (
|
|
10072
|
+
if (path31.isAbsolute(normalized)) {
|
|
9406
10073
|
const root = normalizedRoot.endsWith("/") ? normalizedRoot : normalizedRoot + "/";
|
|
9407
10074
|
if (normalized.startsWith(root)) {
|
|
9408
10075
|
normalized = normalized.slice(root.length);
|
|
@@ -9414,12 +10081,12 @@ function normalizePath(filePath, projectRoot) {
|
|
|
9414
10081
|
return normalized;
|
|
9415
10082
|
}
|
|
9416
10083
|
function resolveImportPath2(currentFile, importPath) {
|
|
9417
|
-
const dir =
|
|
9418
|
-
let resolved =
|
|
10084
|
+
const dir = path31.dirname(currentFile);
|
|
10085
|
+
let resolved = path31.join(dir, importPath).replace(/\\/g, "/");
|
|
9419
10086
|
if (!resolved.match(/\.(ts|tsx|js|jsx)$/)) {
|
|
9420
10087
|
resolved += ".ts";
|
|
9421
10088
|
}
|
|
9422
|
-
return
|
|
10089
|
+
return path31.normalize(resolved).replace(/\\/g, "/");
|
|
9423
10090
|
}
|
|
9424
10091
|
function enqueueImports(content, current, visited, queue, maxDepth) {
|
|
9425
10092
|
const importRegex = /import\s+.*?from\s+['"]([^'"]+)['"]/g;
|
|
@@ -9451,7 +10118,7 @@ function isMechanicallyExcluded(finding, exclusionSet, projectRoot) {
|
|
|
9451
10118
|
const normalizedFile = normalizePath(finding.file, projectRoot);
|
|
9452
10119
|
if (exclusionSet.isExcluded(normalizedFile, finding.lineRange)) return true;
|
|
9453
10120
|
if (exclusionSet.isExcluded(finding.file, finding.lineRange)) return true;
|
|
9454
|
-
const absoluteFile =
|
|
10121
|
+
const absoluteFile = path31.isAbsolute(finding.file) ? finding.file : path31.join(projectRoot, finding.file).replace(/\\/g, "/");
|
|
9455
10122
|
return exclusionSet.isExcluded(absoluteFile, finding.lineRange);
|
|
9456
10123
|
}
|
|
9457
10124
|
async function validateWithGraph(crossFileRefs, graph) {
|
|
@@ -9509,8 +10176,22 @@ function pickLongest(a, b) {
|
|
|
9509
10176
|
if (a && b) return a.length >= b.length ? a : b;
|
|
9510
10177
|
return a ?? b;
|
|
9511
10178
|
}
|
|
10179
|
+
function confidenceStrength(f) {
|
|
10180
|
+
if (typeof f.confidence === "number") return f.confidence;
|
|
10181
|
+
if (f.confidence === "high") return 75;
|
|
10182
|
+
if (f.confidence === "medium") return 50;
|
|
10183
|
+
if (f.confidence === "low") return 25;
|
|
10184
|
+
return 0;
|
|
10185
|
+
}
|
|
10186
|
+
function pickPrimary(a, b) {
|
|
10187
|
+
const sevDiff = SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity];
|
|
10188
|
+
if (sevDiff !== 0) return sevDiff > 0 ? a : b;
|
|
10189
|
+
const confDiff = confidenceStrength(a) - confidenceStrength(b);
|
|
10190
|
+
if (confDiff !== 0) return confDiff > 0 ? a : b;
|
|
10191
|
+
return a;
|
|
10192
|
+
}
|
|
9512
10193
|
function buildMergedTitle(a, b, domains) {
|
|
9513
|
-
const primaryFinding =
|
|
10194
|
+
const primaryFinding = pickPrimary(a, b);
|
|
9514
10195
|
const domainList = [...domains].sort().join(", ");
|
|
9515
10196
|
const cleanTitle = primaryFinding.title.replace(/^\[.*?\]\s*/, "");
|
|
9516
10197
|
return { title: `[${domainList}] ${cleanTitle}`, primaryFinding };
|
|
@@ -9556,6 +10237,7 @@ function mergeFindings(a, b) {
|
|
|
9556
10237
|
};
|
|
9557
10238
|
setIfDefined(merged, "suggestion", pickLongest(a.suggestion, b.suggestion));
|
|
9558
10239
|
setIfDefined(merged, "trustScore", mergeTrustScore(a, b));
|
|
10240
|
+
setIfDefined(merged, "subagent", primaryFinding.subagent ?? a.subagent ?? b.subagent);
|
|
9559
10241
|
mergeSecurityFields(merged, primaryFinding, a, b);
|
|
9560
10242
|
return merged;
|
|
9561
10243
|
}
|
|
@@ -9646,11 +10328,19 @@ function determineAssessment(findings) {
|
|
|
9646
10328
|
function getExitCode(assessment) {
|
|
9647
10329
|
return assessment === "request-changes" ? 1 : 0;
|
|
9648
10330
|
}
|
|
10331
|
+
function formatConfidence(c) {
|
|
10332
|
+
if (c === void 0) return "";
|
|
10333
|
+
if (typeof c === "number") return ` (conf ${c})`;
|
|
10334
|
+
return ` (conf ${c})`;
|
|
10335
|
+
}
|
|
9649
10336
|
function formatFindingBlock(finding) {
|
|
9650
10337
|
const lines = [];
|
|
9651
10338
|
const location = `${finding.file}:L${finding.lineRange[0]}-${finding.lineRange[1]}`;
|
|
9652
10339
|
const trustBadge = finding.trustScore != null ? ` [${finding.trustScore}%]` : "";
|
|
9653
|
-
|
|
10340
|
+
const subagentBadge = finding.subagent && finding.subagent !== finding.domain ? `:${finding.subagent}` : "";
|
|
10341
|
+
lines.push(
|
|
10342
|
+
` [${finding.domain}${subagentBadge}] ${finding.title}${formatConfidence(finding.confidence)}${trustBadge}`
|
|
10343
|
+
);
|
|
9654
10344
|
lines.push(` Location: ${location}`);
|
|
9655
10345
|
lines.push(` Rationale: ${finding.rationale}`);
|
|
9656
10346
|
if (finding.suggestion) {
|
|
@@ -9658,9 +10348,28 @@ function formatFindingBlock(finding) {
|
|
|
9658
10348
|
}
|
|
9659
10349
|
return lines.join("\n");
|
|
9660
10350
|
}
|
|
10351
|
+
function titleCase(s) {
|
|
10352
|
+
if (s.length === 0) return s;
|
|
10353
|
+
return s[0].toUpperCase() + s.slice(1);
|
|
10354
|
+
}
|
|
10355
|
+
function formatDepthHeader(calibration) {
|
|
10356
|
+
const overrideTag = calibration.overridden ? " (override)" : "";
|
|
10357
|
+
const signals = calibration.riskSignals.length === 0 ? "none" : calibration.riskSignals.join(", ");
|
|
10358
|
+
const activations = calibration.activations.size === 0 ? "none" : [...calibration.activations].join(", ");
|
|
10359
|
+
return [
|
|
10360
|
+
`## Review Depth: ${titleCase(calibration.depth)}${overrideTag}`,
|
|
10361
|
+
` Changed lines: ${calibration.changedLines}`,
|
|
10362
|
+
` Risk signals: ${signals}`,
|
|
10363
|
+
` Conditional subagents: ${activations}`,
|
|
10364
|
+
""
|
|
10365
|
+
].join("\n");
|
|
10366
|
+
}
|
|
9661
10367
|
function formatTerminalOutput(options) {
|
|
9662
10368
|
const { findings, strengths } = options;
|
|
9663
10369
|
const sections = [];
|
|
10370
|
+
if (options.depthCalibration) {
|
|
10371
|
+
sections.push(formatDepthHeader(options.depthCalibration));
|
|
10372
|
+
}
|
|
9664
10373
|
sections.push("## Strengths\n");
|
|
9665
10374
|
if (strengths.length === 0) {
|
|
9666
10375
|
sections.push(" No specific strengths noted.\n");
|
|
@@ -10252,6 +10961,22 @@ async function runReviewPipeline(options) {
|
|
|
10252
10961
|
if (rubric) {
|
|
10253
10962
|
contextBundles = contextBundles.map((b) => ({ ...b, rubric }));
|
|
10254
10963
|
}
|
|
10964
|
+
let depthCalibration;
|
|
10965
|
+
try {
|
|
10966
|
+
depthCalibration = calibrateDepth({
|
|
10967
|
+
diff: diff2,
|
|
10968
|
+
commitMessage,
|
|
10969
|
+
...flags.depth != null ? { override: flags.depth } : {}
|
|
10970
|
+
});
|
|
10971
|
+
} catch {
|
|
10972
|
+
depthCalibration = {
|
|
10973
|
+
depth: "standard",
|
|
10974
|
+
changedLines: diff2.totalDiffLines,
|
|
10975
|
+
riskSignals: [],
|
|
10976
|
+
activations: /* @__PURE__ */ new Set(),
|
|
10977
|
+
overridden: false
|
|
10978
|
+
};
|
|
10979
|
+
}
|
|
10255
10980
|
let agentResults;
|
|
10256
10981
|
if (flags.isolated) {
|
|
10257
10982
|
const specBundles = splitBundlesByStage(contextBundles, "spec-compliance");
|
|
@@ -10264,7 +10989,15 @@ async function runReviewPipeline(options) {
|
|
|
10264
10989
|
} else {
|
|
10265
10990
|
agentResults = await fanOutReview({ bundles: contextBundles });
|
|
10266
10991
|
}
|
|
10267
|
-
const
|
|
10992
|
+
const conditionalResults = await fanOutConditionalSubagents({
|
|
10993
|
+
bundles: contextBundles,
|
|
10994
|
+
activations: depthCalibration.activations,
|
|
10995
|
+
depth: depthCalibration.depth
|
|
10996
|
+
});
|
|
10997
|
+
const rawFindings = [
|
|
10998
|
+
...agentResults.flatMap((r) => r.findings),
|
|
10999
|
+
...conditionalResults.flatMap((r) => r.findings)
|
|
11000
|
+
];
|
|
10268
11001
|
const fileContents = /* @__PURE__ */ new Map();
|
|
10269
11002
|
for (const [file, content] of diff2.fileDiffs) {
|
|
10270
11003
|
fileContents.set(file, content);
|
|
@@ -10298,7 +11031,8 @@ async function runReviewPipeline(options) {
|
|
|
10298
11031
|
const terminalOutput = formatTerminalOutput({
|
|
10299
11032
|
findings: dedupedFindings,
|
|
10300
11033
|
strengths,
|
|
10301
|
-
...evidenceCoverage != null ? { evidenceCoverage } : {}
|
|
11034
|
+
...evidenceCoverage != null ? { evidenceCoverage } : {},
|
|
11035
|
+
depthCalibration
|
|
10302
11036
|
});
|
|
10303
11037
|
let githubComments = [];
|
|
10304
11038
|
if (flags.comment) {
|
|
@@ -10313,6 +11047,7 @@ async function runReviewPipeline(options) {
|
|
|
10313
11047
|
terminalOutput,
|
|
10314
11048
|
githubComments,
|
|
10315
11049
|
exitCode,
|
|
11050
|
+
depthCalibration,
|
|
10316
11051
|
...mechanicalResult != null ? { mechanicalResult } : {},
|
|
10317
11052
|
...evidenceCoverage != null ? { evidenceCoverage } : {}
|
|
10318
11053
|
};
|
|
@@ -10459,7 +11194,7 @@ function isRegression(from, to) {
|
|
|
10459
11194
|
}
|
|
10460
11195
|
function collectAutopilotStatuses(autopilotPath, featurePlans, allTaskStatuses) {
|
|
10461
11196
|
try {
|
|
10462
|
-
const raw =
|
|
11197
|
+
const raw = fs32.readFileSync(autopilotPath, "utf-8");
|
|
10463
11198
|
const autopilot = JSON.parse(raw);
|
|
10464
11199
|
if (!autopilot.phases) return;
|
|
10465
11200
|
const linkedPhases = autopilot.phases.filter(
|
|
@@ -10491,10 +11226,10 @@ function inferStatus(feature, projectPath, allFeatures) {
|
|
|
10491
11226
|
const featuresWithPlans = allFeatures.filter((f) => f.plans.length > 0);
|
|
10492
11227
|
const useRootState = featuresWithPlans.length <= 1;
|
|
10493
11228
|
if (useRootState) {
|
|
10494
|
-
const rootStatePath =
|
|
10495
|
-
if (
|
|
11229
|
+
const rootStatePath = path32.join(projectPath, ".harness", "state.json");
|
|
11230
|
+
if (fs32.existsSync(rootStatePath)) {
|
|
10496
11231
|
try {
|
|
10497
|
-
const raw =
|
|
11232
|
+
const raw = fs32.readFileSync(rootStatePath, "utf-8");
|
|
10498
11233
|
const state = JSON.parse(raw);
|
|
10499
11234
|
if (state.progress) {
|
|
10500
11235
|
for (const status of Object.values(state.progress)) {
|
|
@@ -10505,14 +11240,14 @@ function inferStatus(feature, projectPath, allFeatures) {
|
|
|
10505
11240
|
}
|
|
10506
11241
|
}
|
|
10507
11242
|
}
|
|
10508
|
-
const sessionsDir =
|
|
10509
|
-
if (
|
|
11243
|
+
const sessionsDir = path32.join(projectPath, ".harness", "sessions");
|
|
11244
|
+
if (fs32.existsSync(sessionsDir)) {
|
|
10510
11245
|
try {
|
|
10511
|
-
const sessionDirs =
|
|
11246
|
+
const sessionDirs = fs32.readdirSync(sessionsDir, { withFileTypes: true });
|
|
10512
11247
|
for (const entry of sessionDirs) {
|
|
10513
11248
|
if (!entry.isDirectory()) continue;
|
|
10514
|
-
const autopilotPath =
|
|
10515
|
-
if (!
|
|
11249
|
+
const autopilotPath = path32.join(sessionsDir, entry.name, "autopilot-state.json");
|
|
11250
|
+
if (!fs32.existsSync(autopilotPath)) continue;
|
|
10516
11251
|
collectAutopilotStatuses(autopilotPath, feature.plans, allTaskStatuses);
|
|
10517
11252
|
}
|
|
10518
11253
|
} catch {
|
|
@@ -10627,9 +11362,9 @@ function isValidTrackerShape(tracker) {
|
|
|
10627
11362
|
}
|
|
10628
11363
|
function loadTrackerSyncConfig(projectRoot) {
|
|
10629
11364
|
try {
|
|
10630
|
-
const configPath =
|
|
10631
|
-
if (!
|
|
10632
|
-
const raw =
|
|
11365
|
+
const configPath = path33.join(projectRoot, "harness.config.json");
|
|
11366
|
+
if (!fs33.existsSync(configPath)) return null;
|
|
11367
|
+
const raw = fs33.readFileSync(configPath, "utf-8");
|
|
10633
11368
|
const config = JSON.parse(raw);
|
|
10634
11369
|
const tracker = config.roadmap?.tracker;
|
|
10635
11370
|
if (!isValidTrackerShape(tracker)) return null;
|
|
@@ -10749,7 +11484,7 @@ async function fullSync(roadmapPath, adapter2, config, options) {
|
|
|
10749
11484
|
});
|
|
10750
11485
|
await previousSync;
|
|
10751
11486
|
try {
|
|
10752
|
-
const raw =
|
|
11487
|
+
const raw = fs34.readFileSync(roadmapPath, "utf-8");
|
|
10753
11488
|
const parseResult = parseRoadmap(raw);
|
|
10754
11489
|
if (!parseResult.ok) {
|
|
10755
11490
|
return {
|
|
@@ -10762,7 +11497,7 @@ async function fullSync(roadmapPath, adapter2, config, options) {
|
|
|
10762
11497
|
const tickets = fetchResult.ok ? fetchResult.value : void 0;
|
|
10763
11498
|
const pushResult = await syncToExternal(roadmap, adapter2, config, tickets);
|
|
10764
11499
|
const pullResult = await syncFromExternal(roadmap, adapter2, config, options);
|
|
10765
|
-
|
|
11500
|
+
fs34.writeFileSync(roadmapPath, serializeRoadmap(roadmap), "utf-8");
|
|
10766
11501
|
return {
|
|
10767
11502
|
created: pushResult.created,
|
|
10768
11503
|
updated: pushResult.updated,
|
|
@@ -11122,9 +11857,9 @@ function makeTrackerConflictBody(err, opts = {}) {
|
|
|
11122
11857
|
}
|
|
11123
11858
|
function loadProjectRoadmapMode(projectRoot) {
|
|
11124
11859
|
try {
|
|
11125
|
-
const configPath =
|
|
11126
|
-
if (!
|
|
11127
|
-
const raw =
|
|
11860
|
+
const configPath = path34.join(projectRoot, "harness.config.json");
|
|
11861
|
+
if (!fs35.existsSync(configPath)) return getRoadmapMode(null);
|
|
11862
|
+
const raw = fs35.readFileSync(configPath, "utf-8");
|
|
11128
11863
|
const parsed = JSON.parse(raw);
|
|
11129
11864
|
return getRoadmapMode(parsed);
|
|
11130
11865
|
} catch {
|
|
@@ -11133,11 +11868,11 @@ function loadProjectRoadmapMode(projectRoot) {
|
|
|
11133
11868
|
}
|
|
11134
11869
|
function loadTrackerClientConfigFromProject(projectRoot) {
|
|
11135
11870
|
try {
|
|
11136
|
-
const configPath =
|
|
11137
|
-
if (!
|
|
11871
|
+
const configPath = path35.join(projectRoot, "harness.config.json");
|
|
11872
|
+
if (!fs36.existsSync(configPath)) {
|
|
11138
11873
|
return Err(new Error("harness.config.json not found"));
|
|
11139
11874
|
}
|
|
11140
|
-
const cfg = JSON.parse(
|
|
11875
|
+
const cfg = JSON.parse(fs36.readFileSync(configPath, "utf-8"));
|
|
11141
11876
|
const tracker = cfg.roadmap?.tracker;
|
|
11142
11877
|
if (!tracker) {
|
|
11143
11878
|
return Err(
|
|
@@ -11352,10 +12087,10 @@ function mapAction(action) {
|
|
|
11352
12087
|
}
|
|
11353
12088
|
async function runMigrationPlan(plan, deps, opts) {
|
|
11354
12089
|
const projectRoot = opts.projectRoot;
|
|
11355
|
-
const roadmapPath =
|
|
11356
|
-
const archivedPath =
|
|
11357
|
-
const configPath =
|
|
11358
|
-
const configBackupPath =
|
|
12090
|
+
const roadmapPath = path36.join(projectRoot, "docs", "roadmap.md");
|
|
12091
|
+
const archivedPath = path36.join(projectRoot, "docs", "roadmap.md.archived");
|
|
12092
|
+
const configPath = path36.join(projectRoot, "harness.config.json");
|
|
12093
|
+
const configBackupPath = path36.join(projectRoot, "harness.config.json.pre-migration");
|
|
11359
12094
|
const createdSoFar = [];
|
|
11360
12095
|
const report = {
|
|
11361
12096
|
created: 0,
|
|
@@ -11467,7 +12202,7 @@ async function runMigrationPlan(plan, deps, opts) {
|
|
|
11467
12202
|
}
|
|
11468
12203
|
function getStatePath() {
|
|
11469
12204
|
const home = process.env["HOME"] || os.homedir();
|
|
11470
|
-
return
|
|
12205
|
+
return path39.join(home, ".harness", "update-check.json");
|
|
11471
12206
|
}
|
|
11472
12207
|
function isUpdateCheckEnabled(configInterval) {
|
|
11473
12208
|
if (process.env["HARNESS_NO_UPDATE_CHECK"] === "1") return false;
|
|
@@ -11480,13 +12215,13 @@ function shouldRunCheck(state, intervalMs) {
|
|
|
11480
12215
|
}
|
|
11481
12216
|
function invalidateCheckState() {
|
|
11482
12217
|
try {
|
|
11483
|
-
|
|
12218
|
+
fs39.unlinkSync(getStatePath());
|
|
11484
12219
|
} catch {
|
|
11485
12220
|
}
|
|
11486
12221
|
}
|
|
11487
12222
|
function readCheckState() {
|
|
11488
12223
|
try {
|
|
11489
|
-
const raw =
|
|
12224
|
+
const raw = fs39.readFileSync(getStatePath(), "utf-8");
|
|
11490
12225
|
const parsed = JSON.parse(raw);
|
|
11491
12226
|
if (typeof parsed === "object" && parsed !== null && "lastCheckTime" in parsed && typeof parsed.lastCheckTime === "number" && "currentVersion" in parsed && typeof parsed.currentVersion === "string") {
|
|
11492
12227
|
const state = parsed;
|
|
@@ -11503,7 +12238,7 @@ function readCheckState() {
|
|
|
11503
12238
|
}
|
|
11504
12239
|
function spawnBackgroundCheck(currentVersion) {
|
|
11505
12240
|
const statePath = getStatePath();
|
|
11506
|
-
const stateDir =
|
|
12241
|
+
const stateDir = path39.dirname(statePath);
|
|
11507
12242
|
const script = `
|
|
11508
12243
|
const { execSync } = require('child_process');
|
|
11509
12244
|
const fs = require('fs');
|
|
@@ -11746,14 +12481,14 @@ function getModelPrice(model, dataset) {
|
|
|
11746
12481
|
return pricing;
|
|
11747
12482
|
}
|
|
11748
12483
|
function getCachePath(projectRoot) {
|
|
11749
|
-
return
|
|
12484
|
+
return path40.join(projectRoot, ".harness", "cache", "pricing.json");
|
|
11750
12485
|
}
|
|
11751
12486
|
function getStalenessMarkerPath(projectRoot) {
|
|
11752
|
-
return
|
|
12487
|
+
return path40.join(projectRoot, ".harness", "cache", "staleness-marker.json");
|
|
11753
12488
|
}
|
|
11754
12489
|
async function readDiskCache(projectRoot) {
|
|
11755
12490
|
try {
|
|
11756
|
-
const raw = await
|
|
12491
|
+
const raw = await fs40.readFile(getCachePath(projectRoot), "utf-8");
|
|
11757
12492
|
return JSON.parse(raw);
|
|
11758
12493
|
} catch {
|
|
11759
12494
|
return null;
|
|
@@ -11761,8 +12496,8 @@ async function readDiskCache(projectRoot) {
|
|
|
11761
12496
|
}
|
|
11762
12497
|
async function writeDiskCache(projectRoot, data) {
|
|
11763
12498
|
const cachePath = getCachePath(projectRoot);
|
|
11764
|
-
await
|
|
11765
|
-
await
|
|
12499
|
+
await fs40.mkdir(path40.dirname(cachePath), { recursive: true });
|
|
12500
|
+
await fs40.writeFile(cachePath, JSON.stringify(data, null, 2));
|
|
11766
12501
|
}
|
|
11767
12502
|
async function fetchFromNetwork() {
|
|
11768
12503
|
try {
|
|
@@ -11789,7 +12524,7 @@ function loadFallbackDataset() {
|
|
|
11789
12524
|
async function checkAndWarnStaleness(projectRoot) {
|
|
11790
12525
|
const markerPath = getStalenessMarkerPath(projectRoot);
|
|
11791
12526
|
try {
|
|
11792
|
-
const raw = await
|
|
12527
|
+
const raw = await fs40.readFile(markerPath, "utf-8");
|
|
11793
12528
|
const marker = JSON.parse(raw);
|
|
11794
12529
|
const firstUse = new Date(marker.firstFallbackUse).getTime();
|
|
11795
12530
|
const now = Date.now();
|
|
@@ -11801,8 +12536,8 @@ async function checkAndWarnStaleness(projectRoot) {
|
|
|
11801
12536
|
}
|
|
11802
12537
|
} catch {
|
|
11803
12538
|
try {
|
|
11804
|
-
await
|
|
11805
|
-
await
|
|
12539
|
+
await fs40.mkdir(path40.dirname(markerPath), { recursive: true });
|
|
12540
|
+
await fs40.writeFile(
|
|
11806
12541
|
markerPath,
|
|
11807
12542
|
JSON.stringify({ firstFallbackUse: (/* @__PURE__ */ new Date()).toISOString() })
|
|
11808
12543
|
);
|
|
@@ -11812,7 +12547,7 @@ async function checkAndWarnStaleness(projectRoot) {
|
|
|
11812
12547
|
}
|
|
11813
12548
|
async function clearStalenessMarker(projectRoot) {
|
|
11814
12549
|
try {
|
|
11815
|
-
await
|
|
12550
|
+
await fs40.unlink(getStalenessMarkerPath(projectRoot));
|
|
11816
12551
|
} catch {
|
|
11817
12552
|
}
|
|
11818
12553
|
}
|
|
@@ -12049,10 +12784,10 @@ function parseLine(line, lineNumber) {
|
|
|
12049
12784
|
return record;
|
|
12050
12785
|
}
|
|
12051
12786
|
function readCostRecords(projectRoot) {
|
|
12052
|
-
const costsFile =
|
|
12787
|
+
const costsFile = path41.join(projectRoot, ".harness", "metrics", "costs.jsonl");
|
|
12053
12788
|
let raw;
|
|
12054
12789
|
try {
|
|
12055
|
-
raw =
|
|
12790
|
+
raw = fs41.readFileSync(costsFile, "utf-8");
|
|
12056
12791
|
} catch {
|
|
12057
12792
|
return [];
|
|
12058
12793
|
}
|
|
@@ -12102,7 +12837,7 @@ function parseCCLine(line, filePath, lineNumber) {
|
|
|
12102
12837
|
entry = JSON.parse(line);
|
|
12103
12838
|
} catch {
|
|
12104
12839
|
console.warn(
|
|
12105
|
-
`[harness usage] Skipping malformed CC JSONL line ${lineNumber} in ${
|
|
12840
|
+
`[harness usage] Skipping malformed CC JSONL line ${lineNumber} in ${path42.basename(filePath)}`
|
|
12106
12841
|
);
|
|
12107
12842
|
return null;
|
|
12108
12843
|
}
|
|
@@ -12116,7 +12851,7 @@ function parseCCLine(line, filePath, lineNumber) {
|
|
|
12116
12851
|
function readCCFile(filePath) {
|
|
12117
12852
|
let raw;
|
|
12118
12853
|
try {
|
|
12119
|
-
raw =
|
|
12854
|
+
raw = fs42.readFileSync(filePath, "utf-8");
|
|
12120
12855
|
} catch {
|
|
12121
12856
|
return [];
|
|
12122
12857
|
}
|
|
@@ -12138,10 +12873,10 @@ function readCCFile(filePath) {
|
|
|
12138
12873
|
}
|
|
12139
12874
|
function parseCCRecords() {
|
|
12140
12875
|
const homeDir = process.env.HOME ?? os2.homedir();
|
|
12141
|
-
const projectsDir =
|
|
12876
|
+
const projectsDir = path42.join(homeDir, ".claude", "projects");
|
|
12142
12877
|
let projectDirs;
|
|
12143
12878
|
try {
|
|
12144
|
-
projectDirs =
|
|
12879
|
+
projectDirs = fs42.readdirSync(projectsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => path42.join(projectsDir, d.name));
|
|
12145
12880
|
} catch {
|
|
12146
12881
|
return [];
|
|
12147
12882
|
}
|
|
@@ -12149,7 +12884,7 @@ function parseCCRecords() {
|
|
|
12149
12884
|
for (const dir of projectDirs) {
|
|
12150
12885
|
let files;
|
|
12151
12886
|
try {
|
|
12152
|
-
files =
|
|
12887
|
+
files = fs42.readdirSync(dir).filter((f) => f.endsWith(".jsonl")).map((f) => path42.join(dir, f));
|
|
12153
12888
|
} catch {
|
|
12154
12889
|
continue;
|
|
12155
12890
|
}
|
|
@@ -12177,10 +12912,10 @@ function parseLine2(line, lineNumber) {
|
|
|
12177
12912
|
}
|
|
12178
12913
|
}
|
|
12179
12914
|
function readAdoptionRecords(projectRoot) {
|
|
12180
|
-
const adoptionFile =
|
|
12915
|
+
const adoptionFile = path43.join(projectRoot, ".harness", "metrics", "adoption.jsonl");
|
|
12181
12916
|
let raw;
|
|
12182
12917
|
try {
|
|
12183
|
-
raw =
|
|
12918
|
+
raw = fs43.readFileSync(adoptionFile, "utf-8");
|
|
12184
12919
|
} catch {
|
|
12185
12920
|
return [];
|
|
12186
12921
|
}
|
|
@@ -12253,13 +12988,13 @@ function topSkills(records, n) {
|
|
|
12253
12988
|
return aggregateBySkill(records).slice(0, n);
|
|
12254
12989
|
}
|
|
12255
12990
|
function loadNotificationsConfig(projectRoot) {
|
|
12256
|
-
const configPath =
|
|
12257
|
-
if (!
|
|
12991
|
+
const configPath = path44.join(projectRoot, "harness.config.json");
|
|
12992
|
+
if (!fs44.existsSync(configPath)) {
|
|
12258
12993
|
return Ok({ sinks: [] });
|
|
12259
12994
|
}
|
|
12260
12995
|
let raw;
|
|
12261
12996
|
try {
|
|
12262
|
-
raw =
|
|
12997
|
+
raw = fs44.readFileSync(configPath, "utf-8");
|
|
12263
12998
|
} catch (err) {
|
|
12264
12999
|
return Err(err instanceof Error ? err : new Error(String(err)));
|
|
12265
13000
|
}
|
|
@@ -12360,25 +13095,25 @@ function resolveStability(contentType) {
|
|
|
12360
13095
|
return STABILITY_LOOKUP[contentType] ?? "ephemeral";
|
|
12361
13096
|
}
|
|
12362
13097
|
function getOrCreateInstallId(projectRoot) {
|
|
12363
|
-
const harnessDir =
|
|
12364
|
-
const installIdFile =
|
|
13098
|
+
const harnessDir = path45.join(projectRoot, ".harness");
|
|
13099
|
+
const installIdFile = path45.join(harnessDir, ".install-id");
|
|
12365
13100
|
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;
|
|
12366
13101
|
try {
|
|
12367
|
-
const existing =
|
|
13102
|
+
const existing = fs45.readFileSync(installIdFile, "utf-8").trim();
|
|
12368
13103
|
if (UUID_V4_RE.test(existing)) {
|
|
12369
13104
|
return existing;
|
|
12370
13105
|
}
|
|
12371
13106
|
} catch {
|
|
12372
13107
|
}
|
|
12373
13108
|
const id = crypto4.randomUUID();
|
|
12374
|
-
|
|
12375
|
-
|
|
13109
|
+
fs45.mkdirSync(harnessDir, { recursive: true });
|
|
13110
|
+
fs45.writeFileSync(installIdFile, id, { encoding: "utf-8", mode: 384 });
|
|
12376
13111
|
return id;
|
|
12377
13112
|
}
|
|
12378
13113
|
function parseIdentityFromTelemetryFile(filePath) {
|
|
12379
13114
|
const identity = {};
|
|
12380
13115
|
try {
|
|
12381
|
-
const raw =
|
|
13116
|
+
const raw = fs46.readFileSync(filePath, "utf-8");
|
|
12382
13117
|
const parsed = JSON.parse(raw);
|
|
12383
13118
|
const src = parsed?.identity;
|
|
12384
13119
|
if (!src || typeof src !== "object") return identity;
|
|
@@ -12391,7 +13126,7 @@ function parseIdentityFromTelemetryFile(filePath) {
|
|
|
12391
13126
|
}
|
|
12392
13127
|
function readProjectNameFallback(configPath) {
|
|
12393
13128
|
try {
|
|
12394
|
-
const raw =
|
|
13129
|
+
const raw = fs46.readFileSync(configPath, "utf-8");
|
|
12395
13130
|
const config = JSON.parse(raw);
|
|
12396
13131
|
return typeof config?.name === "string" ? config.name : void 0;
|
|
12397
13132
|
} catch {
|
|
@@ -12412,10 +13147,10 @@ function readGitAliasFallback(cwd) {
|
|
|
12412
13147
|
}
|
|
12413
13148
|
}
|
|
12414
13149
|
function readIdentity(projectRoot) {
|
|
12415
|
-
const filePath =
|
|
13150
|
+
const filePath = path46.join(projectRoot, ".harness", "telemetry.json");
|
|
12416
13151
|
const identity = parseIdentityFromTelemetryFile(filePath);
|
|
12417
13152
|
if (!identity.project) {
|
|
12418
|
-
const fallbackProject = readProjectNameFallback(
|
|
13153
|
+
const fallbackProject = readProjectNameFallback(path46.join(projectRoot, "harness.config.json"));
|
|
12419
13154
|
if (fallbackProject) identity.project = fallbackProject;
|
|
12420
13155
|
}
|
|
12421
13156
|
if (!identity.alias) {
|
|
@@ -12545,12 +13280,12 @@ function acquireCompoundLock(category, opts = {}) {
|
|
|
12545
13280
|
);
|
|
12546
13281
|
}
|
|
12547
13282
|
const cwd = opts.cwd ?? process.cwd();
|
|
12548
|
-
const lockDir =
|
|
12549
|
-
|
|
12550
|
-
const lockPath =
|
|
13283
|
+
const lockDir = path47.join(cwd, ".harness", "locks");
|
|
13284
|
+
fs47.mkdirSync(lockDir, { recursive: true });
|
|
13285
|
+
const lockPath = path47.join(lockDir, `compound-${category}.lock`);
|
|
12551
13286
|
let fd;
|
|
12552
13287
|
try {
|
|
12553
|
-
fd =
|
|
13288
|
+
fd = fs47.openSync(lockPath, "wx");
|
|
12554
13289
|
} catch (e) {
|
|
12555
13290
|
const err = e;
|
|
12556
13291
|
if (err.code === "EEXIST") {
|
|
@@ -12559,14 +13294,14 @@ function acquireCompoundLock(category, opts = {}) {
|
|
|
12559
13294
|
}
|
|
12560
13295
|
throw err;
|
|
12561
13296
|
}
|
|
12562
|
-
|
|
12563
|
-
|
|
13297
|
+
fs47.writeSync(fd, String(process.pid));
|
|
13298
|
+
fs47.closeSync(fd);
|
|
12564
13299
|
let released = false;
|
|
12565
13300
|
const release = () => {
|
|
12566
13301
|
if (released) return;
|
|
12567
13302
|
released = true;
|
|
12568
13303
|
try {
|
|
12569
|
-
|
|
13304
|
+
fs47.unlinkSync(lockPath);
|
|
12570
13305
|
} catch {
|
|
12571
13306
|
}
|
|
12572
13307
|
process.removeListener("exit", onExit);
|
|
@@ -12592,7 +13327,7 @@ function acquireCompoundLock(category, opts = {}) {
|
|
|
12592
13327
|
}
|
|
12593
13328
|
function readHolderPid(lockPath) {
|
|
12594
13329
|
try {
|
|
12595
|
-
const raw =
|
|
13330
|
+
const raw = fs47.readFileSync(lockPath, "utf-8").trim();
|
|
12596
13331
|
const n = Number.parseInt(raw, 10);
|
|
12597
13332
|
return Number.isFinite(n) ? n : -1;
|
|
12598
13333
|
} catch {
|
|
@@ -12624,10 +13359,10 @@ function assertSanitized(value) {
|
|
|
12624
13359
|
}
|
|
12625
13360
|
function writePulseConfig(config, opts) {
|
|
12626
13361
|
PulseConfigSchema.parse(config);
|
|
12627
|
-
if (!
|
|
13362
|
+
if (!fs48.existsSync(opts.configPath)) {
|
|
12628
13363
|
throw new Error(`harness.config.json not found at ${opts.configPath}`);
|
|
12629
13364
|
}
|
|
12630
|
-
const raw =
|
|
13365
|
+
const raw = fs48.readFileSync(opts.configPath, "utf-8");
|
|
12631
13366
|
let parsed;
|
|
12632
13367
|
try {
|
|
12633
13368
|
parsed = JSON.parse(raw);
|
|
@@ -12635,18 +13370,18 @@ function writePulseConfig(config, opts) {
|
|
|
12635
13370
|
throw new Error(`Invalid JSON in ${opts.configPath}: ${e.message}`, { cause: e });
|
|
12636
13371
|
}
|
|
12637
13372
|
const bakPath = `${opts.configPath}.bak`;
|
|
12638
|
-
if (!opts.skipBackup && !
|
|
12639
|
-
|
|
13373
|
+
if (!opts.skipBackup && !fs48.existsSync(bakPath)) {
|
|
13374
|
+
fs48.writeFileSync(bakPath, raw, "utf-8");
|
|
12640
13375
|
}
|
|
12641
13376
|
parsed.pulse = config;
|
|
12642
13377
|
const serialized = JSON.stringify(parsed, null, 2) + "\n";
|
|
12643
13378
|
const tmpPath = `${opts.configPath}.tmp-${process.pid}`;
|
|
12644
|
-
|
|
13379
|
+
fs48.writeFileSync(tmpPath, serialized, "utf-8");
|
|
12645
13380
|
try {
|
|
12646
|
-
|
|
13381
|
+
fs48.renameSync(tmpPath, opts.configPath);
|
|
12647
13382
|
} catch (e) {
|
|
12648
13383
|
try {
|
|
12649
|
-
|
|
13384
|
+
fs48.unlinkSync(tmpPath);
|
|
12650
13385
|
} catch {
|
|
12651
13386
|
}
|
|
12652
13387
|
throw e;
|
|
@@ -12654,12 +13389,12 @@ function writePulseConfig(config, opts) {
|
|
|
12654
13389
|
}
|
|
12655
13390
|
function seedFromStrategy(opts = {}) {
|
|
12656
13391
|
const cwd = opts.cwd ?? process.cwd();
|
|
12657
|
-
const strategyPath =
|
|
13392
|
+
const strategyPath = path48.join(cwd, "STRATEGY.md");
|
|
12658
13393
|
const warnings = [];
|
|
12659
|
-
if (!
|
|
13394
|
+
if (!fs49.existsSync(strategyPath)) {
|
|
12660
13395
|
return { name: null, keyMetrics: [], warnings: ["STRATEGY.md not found"] };
|
|
12661
13396
|
}
|
|
12662
|
-
const raw =
|
|
13397
|
+
const raw = fs49.readFileSync(strategyPath, "utf-8");
|
|
12663
13398
|
let name = null;
|
|
12664
13399
|
const fmMatch = /^---\s*\n([\s\S]*?)\n---\s*\n/.exec(raw);
|
|
12665
13400
|
const fm = fmMatch?.[1];
|
|
@@ -12811,7 +13546,7 @@ function loadTemplate() {
|
|
|
12811
13546
|
const url = import.meta?.url;
|
|
12812
13547
|
if (!url) return INLINE_TEMPLATE;
|
|
12813
13548
|
const here = dirname16(fileURLToPath(url));
|
|
12814
|
-
return readFileSync392(
|
|
13549
|
+
return readFileSync392(join622(here, "template.md"), "utf-8");
|
|
12815
13550
|
} catch {
|
|
12816
13551
|
return INLINE_TEMPLATE;
|
|
12817
13552
|
}
|
|
@@ -13000,11 +13735,11 @@ async function computeHotspots(opts) {
|
|
|
13000
13735
|
}
|
|
13001
13736
|
const counts = /* @__PURE__ */ new Map();
|
|
13002
13737
|
for (const line of stdout.split("\n")) {
|
|
13003
|
-
const
|
|
13004
|
-
if (
|
|
13005
|
-
counts.set(
|
|
13738
|
+
const path52 = line.trim();
|
|
13739
|
+
if (path52.length === 0) continue;
|
|
13740
|
+
counts.set(path52, (counts.get(path52) ?? 0) + 1);
|
|
13006
13741
|
}
|
|
13007
|
-
return [...counts.entries()].filter(([, c]) => c > opts.threshold).map(([
|
|
13742
|
+
return [...counts.entries()].filter(([, c]) => c > opts.threshold).map(([path52, churn]) => ({ path: path52, churn })).sort((a, b) => b.churn - a.churn);
|
|
13008
13743
|
}
|
|
13009
13744
|
function tokenize(text) {
|
|
13010
13745
|
return new Set(
|
|
@@ -13014,12 +13749,12 @@ function tokenize(text) {
|
|
|
13014
13749
|
async function* walk2(dir) {
|
|
13015
13750
|
let entries;
|
|
13016
13751
|
try {
|
|
13017
|
-
entries = await
|
|
13752
|
+
entries = await fs50.readdir(dir, { withFileTypes: true });
|
|
13018
13753
|
} catch {
|
|
13019
13754
|
return;
|
|
13020
13755
|
}
|
|
13021
13756
|
for (const e of entries) {
|
|
13022
|
-
const p =
|
|
13757
|
+
const p = path49.join(dir, e.name);
|
|
13023
13758
|
if (e.isDirectory()) yield* walk2(p);
|
|
13024
13759
|
else if (e.isFile() && e.name.endsWith(".md")) yield p;
|
|
13025
13760
|
}
|
|
@@ -13028,11 +13763,11 @@ async function readDocumentedTokens(solutionsDir) {
|
|
|
13028
13763
|
const docs = [];
|
|
13029
13764
|
for (const [track, categories] of TRACK_CATEGORIES2) {
|
|
13030
13765
|
for (const category of categories) {
|
|
13031
|
-
const dir =
|
|
13766
|
+
const dir = path49.join(solutionsDir, track, category);
|
|
13032
13767
|
for await (const file of walk2(dir)) {
|
|
13033
|
-
const raw = await
|
|
13768
|
+
const raw = await fs50.readFile(file, "utf-8");
|
|
13034
13769
|
const m = /^#\s+(.+)$/m.exec(raw);
|
|
13035
|
-
const title = m?.[1] ??
|
|
13770
|
+
const title = m?.[1] ?? path49.basename(file, ".md");
|
|
13036
13771
|
docs.push(tokenize(title));
|
|
13037
13772
|
}
|
|
13038
13773
|
}
|
|
@@ -13095,19 +13830,89 @@ function assembleCandidateReport(input) {
|
|
|
13095
13830
|
}
|
|
13096
13831
|
return lines.join("\n");
|
|
13097
13832
|
}
|
|
13833
|
+
function emitFrontmatter(doc) {
|
|
13834
|
+
const lines = [
|
|
13835
|
+
"---",
|
|
13836
|
+
`name: ${doc.frontmatter.name}`,
|
|
13837
|
+
`last_updated: "${doc.frontmatter.last_updated}"`,
|
|
13838
|
+
`version: ${doc.frontmatter.version}`,
|
|
13839
|
+
"---"
|
|
13840
|
+
];
|
|
13841
|
+
return lines.join("\n");
|
|
13842
|
+
}
|
|
13843
|
+
function emitH1(doc, opts) {
|
|
13844
|
+
if (typeof opts.h1 === "string" && opts.h1.trim().length > 0) {
|
|
13845
|
+
return opts.h1.trimEnd();
|
|
13846
|
+
}
|
|
13847
|
+
return `# ${doc.frontmatter.name} Strategy`;
|
|
13848
|
+
}
|
|
13849
|
+
function emitSection(name, body) {
|
|
13850
|
+
const trimmed = body.trim();
|
|
13851
|
+
return trimmed.length === 0 ? `## ${name}
|
|
13852
|
+
` : `## ${name}
|
|
13853
|
+
|
|
13854
|
+
${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
|
+
}
|
|
13098
13903
|
function proposalsDir(projectPath) {
|
|
13099
|
-
return
|
|
13904
|
+
return path51.join(projectPath, ".harness", "proposals");
|
|
13100
13905
|
}
|
|
13101
13906
|
function proposalPath(projectPath, id) {
|
|
13102
|
-
return
|
|
13907
|
+
return path51.join(proposalsDir(projectPath), `${id}.json`);
|
|
13103
13908
|
}
|
|
13104
13909
|
function ensureDir(dir) {
|
|
13105
|
-
|
|
13910
|
+
fs52.mkdirSync(dir, { recursive: true });
|
|
13106
13911
|
}
|
|
13107
13912
|
function writeAtomic(filePath, content) {
|
|
13108
13913
|
const tmpPath = `${filePath}.tmp`;
|
|
13109
|
-
|
|
13110
|
-
|
|
13914
|
+
fs52.writeFileSync(tmpPath, content);
|
|
13915
|
+
fs52.renameSync(tmpPath, filePath);
|
|
13111
13916
|
}
|
|
13112
13917
|
async function createProposal(projectPath, input) {
|
|
13113
13918
|
const validated = EmitSkillProposalInputSchema.parse(input);
|
|
@@ -13145,7 +13950,7 @@ async function createProposal(projectPath, input) {
|
|
|
13145
13950
|
async function getProposal(projectPath, id) {
|
|
13146
13951
|
const file = proposalPath(projectPath, id);
|
|
13147
13952
|
try {
|
|
13148
|
-
const raw =
|
|
13953
|
+
const raw = fs52.readFileSync(file, "utf-8");
|
|
13149
13954
|
const parsed = SkillProposalSchema.safeParse(JSON.parse(raw));
|
|
13150
13955
|
return parsed.success ? parsed.data : null;
|
|
13151
13956
|
} catch {
|
|
@@ -13156,7 +13961,7 @@ async function listProposals(projectPath, opts = {}) {
|
|
|
13156
13961
|
const dir = proposalsDir(projectPath);
|
|
13157
13962
|
let files;
|
|
13158
13963
|
try {
|
|
13159
|
-
files =
|
|
13964
|
+
files = fs52.readdirSync(dir);
|
|
13160
13965
|
} catch {
|
|
13161
13966
|
return [];
|
|
13162
13967
|
}
|
|
@@ -13201,17 +14006,19 @@ function deriveSkillUsage(projectRoot, skillName, windowDays = 30) {
|
|
|
13201
14006
|
if (lastUsed) stats.lastUsed = lastUsed;
|
|
13202
14007
|
return stats;
|
|
13203
14008
|
}
|
|
13204
|
-
var WHATWG_BAD_PORTS, BAD_PORT_SET, VALID_TYPES, CONVENTIONAL_PATTERN, LOOKBACK_PATTERN, PulseDbSourceSchema, PulseSourcesSchema, PulseConfigSchema, BUG_TRACK_CATEGORIES, KNOWLEDGE_TRACK_CATEGORIES, ALL_SOLUTION_CATEGORIES,
|
|
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, MAX_TOP_AFFECTED, 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, EM_DASH2, STATUS_RANK, 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, 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, CompoundLockHeldError, KNOWN_CATEGORIES, 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, ProposalNotFoundError, ProposalConflictError;
|
|
13205
14010
|
var init_dist3 = __esm({
|
|
13206
14011
|
"../core/dist/index.mjs"() {
|
|
13207
14012
|
init_chunk_FYTJQ2ZY();
|
|
13208
|
-
|
|
14013
|
+
init_chunk_S4DTDJ6J();
|
|
13209
14014
|
init_chunk_MUWJHO2S();
|
|
13210
14015
|
init_chunk_XKLVJIPL();
|
|
13211
14016
|
init_chunk_IIEDD47I();
|
|
13212
14017
|
init_chunk_7P6ASYW6();
|
|
13213
14018
|
init_dist();
|
|
13214
14019
|
init_dist();
|
|
14020
|
+
init_dist();
|
|
14021
|
+
init_dist();
|
|
13215
14022
|
init_dist2();
|
|
13216
14023
|
init_dist();
|
|
13217
14024
|
init_dist();
|
|
@@ -13228,6 +14035,8 @@ var init_dist3 = __esm({
|
|
|
13228
14035
|
init_dist();
|
|
13229
14036
|
init_dist2();
|
|
13230
14037
|
init_dist();
|
|
14038
|
+
init_dist();
|
|
14039
|
+
init_dist();
|
|
13231
14040
|
WHATWG_BAD_PORTS = Object.freeze([
|
|
13232
14041
|
1,
|
|
13233
14042
|
7,
|
|
@@ -13351,6 +14160,67 @@ var init_dist3 = __esm({
|
|
|
13351
14160
|
pendingMetrics: z.array(z.string()),
|
|
13352
14161
|
excludedMetrics: z.array(z.string())
|
|
13353
14162
|
});
|
|
14163
|
+
KNOWN_SECTION_NAMES = /* @__PURE__ */ new Set([
|
|
14164
|
+
...REQUIRED_STRATEGY_SECTIONS,
|
|
14165
|
+
...OPTIONAL_STRATEGY_SECTIONS
|
|
14166
|
+
]);
|
|
14167
|
+
ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;
|
|
14168
|
+
PLACEHOLDER_INLINE = /<[^<>\n]*\s[^<>\n]*>/;
|
|
14169
|
+
REQUIRED = new Set(REQUIRED_STRATEGY_SECTIONS);
|
|
14170
|
+
ALLOWED = /* @__PURE__ */ new Set([
|
|
14171
|
+
...REQUIRED_STRATEGY_SECTIONS,
|
|
14172
|
+
...OPTIONAL_STRATEGY_SECTIONS
|
|
14173
|
+
]);
|
|
14174
|
+
StrategyFrontmatterSchema = z2.object({
|
|
14175
|
+
name: z2.string().min(1, "frontmatter.name must be a non-empty string"),
|
|
14176
|
+
last_updated: z2.string().regex(ISO_DATE, "frontmatter.last_updated must be ISO date YYYY-MM-DD"),
|
|
14177
|
+
version: z2.number().int("frontmatter.version must be an integer").positive("frontmatter.version must be \u2265 1")
|
|
14178
|
+
});
|
|
14179
|
+
StrategySectionSchema = z2.object({
|
|
14180
|
+
name: z2.enum([...REQUIRED_STRATEGY_SECTIONS, ...OPTIONAL_STRATEGY_SECTIONS]),
|
|
14181
|
+
body: z2.string()
|
|
14182
|
+
});
|
|
14183
|
+
StrategyDocSchema = z2.object({
|
|
14184
|
+
frontmatter: StrategyFrontmatterSchema,
|
|
14185
|
+
sections: z2.array(StrategySectionSchema)
|
|
14186
|
+
}).superRefine((doc, ctx) => {
|
|
14187
|
+
const seen = /* @__PURE__ */ new Set();
|
|
14188
|
+
for (const section of doc.sections) {
|
|
14189
|
+
if (!ALLOWED.has(section.name)) {
|
|
14190
|
+
ctx.addIssue({
|
|
14191
|
+
code: z2.ZodIssueCode.custom,
|
|
14192
|
+
message: `unknown section "${section.name}" \u2014 only documented sections are allowed`,
|
|
14193
|
+
path: ["sections"]
|
|
14194
|
+
});
|
|
14195
|
+
continue;
|
|
14196
|
+
}
|
|
14197
|
+
if (seen.has(section.name)) {
|
|
14198
|
+
ctx.addIssue({
|
|
14199
|
+
code: z2.ZodIssueCode.custom,
|
|
14200
|
+
message: `section "${section.name}" appears more than once`,
|
|
14201
|
+
path: ["sections"]
|
|
14202
|
+
});
|
|
14203
|
+
}
|
|
14204
|
+
seen.add(section.name);
|
|
14205
|
+
const issue = bodyContentIssue(section.body);
|
|
14206
|
+
if (issue !== null) {
|
|
14207
|
+
ctx.addIssue({
|
|
14208
|
+
code: z2.ZodIssueCode.custom,
|
|
14209
|
+
message: `section "${section.name}": ${issue}`,
|
|
14210
|
+
path: ["sections", section.name]
|
|
14211
|
+
});
|
|
14212
|
+
}
|
|
14213
|
+
}
|
|
14214
|
+
for (const required of REQUIRED) {
|
|
14215
|
+
if (!seen.has(required)) {
|
|
14216
|
+
ctx.addIssue({
|
|
14217
|
+
code: z2.ZodIssueCode.custom,
|
|
14218
|
+
message: `required section "${required}" is missing`,
|
|
14219
|
+
path: ["sections", required]
|
|
14220
|
+
});
|
|
14221
|
+
}
|
|
14222
|
+
}
|
|
14223
|
+
});
|
|
13354
14224
|
BUG_TRACK_CATEGORIES = [
|
|
13355
14225
|
"build-errors",
|
|
13356
14226
|
"test-failures",
|
|
@@ -13374,22 +14244,22 @@ var init_dist3 = __esm({
|
|
|
13374
14244
|
...BUG_TRACK_CATEGORIES,
|
|
13375
14245
|
...KNOWLEDGE_TRACK_CATEGORIES
|
|
13376
14246
|
];
|
|
13377
|
-
|
|
13378
|
-
BugTrackSchema =
|
|
13379
|
-
track:
|
|
13380
|
-
category:
|
|
14247
|
+
ISO_DATE2 = /^\d{4}-\d{2}-\d{2}$/;
|
|
14248
|
+
BugTrackSchema = z3.object({
|
|
14249
|
+
track: z3.literal("bug-track"),
|
|
14250
|
+
category: z3.enum(BUG_TRACK_CATEGORIES)
|
|
13381
14251
|
});
|
|
13382
|
-
KnowledgeTrackSchema =
|
|
13383
|
-
track:
|
|
13384
|
-
category:
|
|
14252
|
+
KnowledgeTrackSchema = z3.object({
|
|
14253
|
+
track: z3.literal("knowledge-track"),
|
|
14254
|
+
category: z3.enum(KNOWLEDGE_TRACK_CATEGORIES)
|
|
13385
14255
|
});
|
|
13386
|
-
BaseFrontmatter =
|
|
13387
|
-
module:
|
|
13388
|
-
tags:
|
|
13389
|
-
problem_type:
|
|
13390
|
-
last_updated:
|
|
14256
|
+
BaseFrontmatter = z3.object({
|
|
14257
|
+
module: z3.string().min(1),
|
|
14258
|
+
tags: z3.array(z3.string()),
|
|
14259
|
+
problem_type: z3.string().min(1),
|
|
14260
|
+
last_updated: z3.string().regex(ISO_DATE2, "last_updated must be ISO date YYYY-MM-DD")
|
|
13391
14261
|
});
|
|
13392
|
-
SolutionDocFrontmatterSchema =
|
|
14262
|
+
SolutionDocFrontmatterSchema = z3.discriminatedUnion("track", [
|
|
13393
14263
|
BaseFrontmatter.merge(BugTrackSchema),
|
|
13394
14264
|
BaseFrontmatter.merge(KnowledgeTrackSchema)
|
|
13395
14265
|
]);
|
|
@@ -13545,81 +14415,81 @@ var init_dist3 = __esm({
|
|
|
13545
14415
|
{ category: "config", patterns: ["harness.config.json", "package.json"], priority: 5 }
|
|
13546
14416
|
]
|
|
13547
14417
|
};
|
|
13548
|
-
ManifestSchema =
|
|
13549
|
-
name:
|
|
13550
|
-
version:
|
|
13551
|
-
description:
|
|
13552
|
-
include:
|
|
13553
|
-
minHarnessVersion:
|
|
13554
|
-
keywords:
|
|
13555
|
-
layers:
|
|
13556
|
-
boundaries:
|
|
13557
|
-
|
|
13558
|
-
name:
|
|
13559
|
-
layer:
|
|
13560
|
-
direction:
|
|
13561
|
-
schema:
|
|
14418
|
+
ManifestSchema = z4.object({
|
|
14419
|
+
name: z4.string(),
|
|
14420
|
+
version: z4.string(),
|
|
14421
|
+
description: z4.string().optional(),
|
|
14422
|
+
include: z4.array(z4.string()).min(1),
|
|
14423
|
+
minHarnessVersion: z4.string().optional(),
|
|
14424
|
+
keywords: z4.array(z4.string()).optional().default([]),
|
|
14425
|
+
layers: z4.record(z4.array(z4.string())).optional(),
|
|
14426
|
+
boundaries: z4.array(
|
|
14427
|
+
z4.object({
|
|
14428
|
+
name: z4.string(),
|
|
14429
|
+
layer: z4.string(),
|
|
14430
|
+
direction: z4.enum(["input", "output"]),
|
|
14431
|
+
schema: z4.string()
|
|
13562
14432
|
})
|
|
13563
14433
|
).optional()
|
|
13564
14434
|
});
|
|
13565
|
-
BundleConstraintsSchema =
|
|
13566
|
-
layers:
|
|
13567
|
-
|
|
13568
|
-
name:
|
|
13569
|
-
pattern:
|
|
13570
|
-
allowedDependencies:
|
|
14435
|
+
BundleConstraintsSchema = z4.object({
|
|
14436
|
+
layers: z4.array(
|
|
14437
|
+
z4.object({
|
|
14438
|
+
name: z4.string(),
|
|
14439
|
+
pattern: z4.string(),
|
|
14440
|
+
allowedDependencies: z4.array(z4.string())
|
|
13571
14441
|
})
|
|
13572
14442
|
).optional(),
|
|
13573
|
-
forbiddenImports:
|
|
13574
|
-
|
|
13575
|
-
from:
|
|
13576
|
-
disallow:
|
|
13577
|
-
message:
|
|
14443
|
+
forbiddenImports: z4.array(
|
|
14444
|
+
z4.object({
|
|
14445
|
+
from: z4.string(),
|
|
14446
|
+
disallow: z4.array(z4.string()),
|
|
14447
|
+
message: z4.string().optional()
|
|
13578
14448
|
})
|
|
13579
14449
|
).optional(),
|
|
13580
|
-
boundaries:
|
|
13581
|
-
requireSchema:
|
|
14450
|
+
boundaries: z4.object({
|
|
14451
|
+
requireSchema: z4.array(z4.string()).optional()
|
|
13582
14452
|
}).optional(),
|
|
13583
|
-
architecture:
|
|
13584
|
-
thresholds:
|
|
13585
|
-
modules:
|
|
14453
|
+
architecture: z4.object({
|
|
14454
|
+
thresholds: z4.record(z4.unknown()).optional(),
|
|
14455
|
+
modules: z4.record(z4.record(z4.unknown())).optional()
|
|
13586
14456
|
}).optional(),
|
|
13587
|
-
security:
|
|
13588
|
-
rules:
|
|
14457
|
+
security: z4.object({
|
|
14458
|
+
rules: z4.record(z4.string()).optional()
|
|
13589
14459
|
}).optional()
|
|
13590
14460
|
});
|
|
13591
|
-
BundleSchema =
|
|
13592
|
-
name:
|
|
13593
|
-
version:
|
|
13594
|
-
description:
|
|
13595
|
-
minHarnessVersion:
|
|
14461
|
+
BundleSchema = z4.object({
|
|
14462
|
+
name: z4.string(),
|
|
14463
|
+
version: z4.string(),
|
|
14464
|
+
description: z4.string().optional(),
|
|
14465
|
+
minHarnessVersion: z4.string().optional(),
|
|
13596
14466
|
manifest: ManifestSchema,
|
|
13597
14467
|
constraints: BundleConstraintsSchema,
|
|
13598
|
-
contributions:
|
|
14468
|
+
contributions: z4.record(z4.unknown()).optional()
|
|
13599
14469
|
});
|
|
13600
|
-
ContributionsSchema =
|
|
13601
|
-
LockfilePackageSchema =
|
|
13602
|
-
version:
|
|
13603
|
-
source:
|
|
13604
|
-
installedAt:
|
|
13605
|
-
contributions:
|
|
13606
|
-
resolved:
|
|
13607
|
-
integrity:
|
|
13608
|
-
provenance:
|
|
14470
|
+
ContributionsSchema = z4.record(z4.unknown());
|
|
14471
|
+
LockfilePackageSchema = z4.object({
|
|
14472
|
+
version: z4.string(),
|
|
14473
|
+
source: z4.string(),
|
|
14474
|
+
installedAt: z4.string(),
|
|
14475
|
+
contributions: z4.record(z4.unknown()).optional().nullable(),
|
|
14476
|
+
resolved: z4.string().optional(),
|
|
14477
|
+
integrity: z4.string().optional(),
|
|
14478
|
+
provenance: z4.array(z4.string()).optional()
|
|
13609
14479
|
});
|
|
13610
|
-
LockfileSchema =
|
|
13611
|
-
version:
|
|
14480
|
+
LockfileSchema = z4.object({
|
|
14481
|
+
version: z4.literal(1).optional(),
|
|
13612
14482
|
// Used by some tests
|
|
13613
|
-
lockfileVersion:
|
|
14483
|
+
lockfileVersion: z4.literal(1).optional(),
|
|
13614
14484
|
// Standard field
|
|
13615
|
-
packages:
|
|
14485
|
+
packages: z4.record(LockfilePackageSchema)
|
|
13616
14486
|
}).refine((data) => data.version !== void 0 || data.lockfileVersion !== void 0, {
|
|
13617
14487
|
message: "Either 'version' or 'lockfileVersion' must be present and equal to 1"
|
|
13618
14488
|
});
|
|
13619
|
-
SharableLayerSchema =
|
|
13620
|
-
SharableForbiddenImportSchema =
|
|
13621
|
-
SharableBoundaryConfigSchema =
|
|
13622
|
-
SharableSecurityRulesSchema =
|
|
14489
|
+
SharableLayerSchema = z4.unknown();
|
|
14490
|
+
SharableForbiddenImportSchema = z4.unknown();
|
|
14491
|
+
SharableBoundaryConfigSchema = z4.unknown();
|
|
14492
|
+
SharableSecurityRulesSchema = z4.unknown();
|
|
13623
14493
|
VALID_SCOPES = /* @__PURE__ */ new Set([
|
|
13624
14494
|
"entropy",
|
|
13625
14495
|
"architecture",
|
|
@@ -13629,11 +14499,11 @@ var init_dist3 = __esm({
|
|
|
13629
14499
|
LINE_PATTERN = /^[ \t]*(?:\/\/|#)\s*harness-ignore(?!\s*(?:SEC-|-start|-end))(?:\s+([\w,]+))?(?::\s*(.+))?$/;
|
|
13630
14500
|
START_PATTERN = /^[ \t]*(?:\/\/|#)\s*harness-ignore-start(?:\s+([\w,]+))?(?::\s*(.+))?$/;
|
|
13631
14501
|
END_PATTERN = /^[ \t]*(?:\/\/|#)\s*harness-ignore-end\s*$/;
|
|
13632
|
-
|
|
13633
|
-
writeFile32 = promisify(
|
|
13634
|
-
unlink22 = promisify(
|
|
13635
|
-
mkdir22 = promisify(
|
|
13636
|
-
copyFile2 = promisify(
|
|
14502
|
+
readFile72 = promisify(fs7.readFile);
|
|
14503
|
+
writeFile32 = promisify(fs7.writeFile);
|
|
14504
|
+
unlink22 = promisify(fs7.unlink);
|
|
14505
|
+
mkdir22 = promisify(fs7.mkdir);
|
|
14506
|
+
copyFile2 = promisify(fs7.copyFile);
|
|
13637
14507
|
DEFAULT_FIX_CONFIG = {
|
|
13638
14508
|
dryRun: false,
|
|
13639
14509
|
fixTypes: ["unused-imports", "dead-files"],
|
|
@@ -13661,45 +14531,45 @@ var init_dist3 = __esm({
|
|
|
13661
14531
|
"commented-code": "Delete commented block",
|
|
13662
14532
|
"unused-import": "Remove import"
|
|
13663
14533
|
};
|
|
13664
|
-
MustExportRuleSchema =
|
|
13665
|
-
type:
|
|
13666
|
-
names:
|
|
14534
|
+
MustExportRuleSchema = z5.object({
|
|
14535
|
+
type: z5.literal("must-export"),
|
|
14536
|
+
names: z5.array(z5.string())
|
|
13667
14537
|
});
|
|
13668
|
-
MustExportDefaultRuleSchema =
|
|
13669
|
-
type:
|
|
13670
|
-
kind:
|
|
14538
|
+
MustExportDefaultRuleSchema = z5.object({
|
|
14539
|
+
type: z5.literal("must-export-default"),
|
|
14540
|
+
kind: z5.enum(["class", "function", "object"]).optional()
|
|
13671
14541
|
});
|
|
13672
|
-
NoExportRuleSchema =
|
|
13673
|
-
type:
|
|
13674
|
-
names:
|
|
14542
|
+
NoExportRuleSchema = z5.object({
|
|
14543
|
+
type: z5.literal("no-export"),
|
|
14544
|
+
names: z5.array(z5.string())
|
|
13675
14545
|
});
|
|
13676
|
-
MustImportRuleSchema =
|
|
13677
|
-
type:
|
|
13678
|
-
from:
|
|
13679
|
-
names:
|
|
14546
|
+
MustImportRuleSchema = z5.object({
|
|
14547
|
+
type: z5.literal("must-import"),
|
|
14548
|
+
from: z5.string(),
|
|
14549
|
+
names: z5.array(z5.string()).optional()
|
|
13680
14550
|
});
|
|
13681
|
-
NoImportRuleSchema =
|
|
13682
|
-
type:
|
|
13683
|
-
from:
|
|
14551
|
+
NoImportRuleSchema = z5.object({
|
|
14552
|
+
type: z5.literal("no-import"),
|
|
14553
|
+
from: z5.string()
|
|
13684
14554
|
});
|
|
13685
|
-
NamingRuleSchema =
|
|
13686
|
-
type:
|
|
13687
|
-
match:
|
|
13688
|
-
convention:
|
|
14555
|
+
NamingRuleSchema = z5.object({
|
|
14556
|
+
type: z5.literal("naming"),
|
|
14557
|
+
match: z5.string(),
|
|
14558
|
+
convention: z5.enum(["camelCase", "PascalCase", "UPPER_SNAKE", "kebab-case"])
|
|
13689
14559
|
});
|
|
13690
|
-
MaxExportsRuleSchema =
|
|
13691
|
-
type:
|
|
13692
|
-
count:
|
|
14560
|
+
MaxExportsRuleSchema = z5.object({
|
|
14561
|
+
type: z5.literal("max-exports"),
|
|
14562
|
+
count: z5.number().positive()
|
|
13693
14563
|
});
|
|
13694
|
-
MaxLinesRuleSchema =
|
|
13695
|
-
type:
|
|
13696
|
-
count:
|
|
14564
|
+
MaxLinesRuleSchema = z5.object({
|
|
14565
|
+
type: z5.literal("max-lines"),
|
|
14566
|
+
count: z5.number().positive()
|
|
13697
14567
|
});
|
|
13698
|
-
RequireJSDocRuleSchema =
|
|
13699
|
-
type:
|
|
13700
|
-
for:
|
|
14568
|
+
RequireJSDocRuleSchema = z5.object({
|
|
14569
|
+
type: z5.literal("require-jsdoc"),
|
|
14570
|
+
for: z5.array(z5.enum(["function", "class", "export"]))
|
|
13701
14571
|
});
|
|
13702
|
-
RuleSchema =
|
|
14572
|
+
RuleSchema = z5.discriminatedUnion("type", [
|
|
13703
14573
|
MustExportRuleSchema,
|
|
13704
14574
|
MustExportDefaultRuleSchema,
|
|
13705
14575
|
NoExportRuleSchema,
|
|
@@ -13710,53 +14580,54 @@ var init_dist3 = __esm({
|
|
|
13710
14580
|
MaxLinesRuleSchema,
|
|
13711
14581
|
RequireJSDocRuleSchema
|
|
13712
14582
|
]);
|
|
13713
|
-
ConfigPatternSchema =
|
|
13714
|
-
name:
|
|
13715
|
-
description:
|
|
13716
|
-
severity:
|
|
13717
|
-
files:
|
|
14583
|
+
ConfigPatternSchema = z5.object({
|
|
14584
|
+
name: z5.string().min(1),
|
|
14585
|
+
description: z5.string(),
|
|
14586
|
+
severity: z5.enum(["error", "warning"]),
|
|
14587
|
+
files: z5.array(z5.string()),
|
|
13718
14588
|
rule: RuleSchema,
|
|
13719
|
-
message:
|
|
14589
|
+
message: z5.string().optional()
|
|
13720
14590
|
});
|
|
13721
|
-
PatternConfigSchema =
|
|
13722
|
-
patterns:
|
|
13723
|
-
customPatterns:
|
|
14591
|
+
PatternConfigSchema = z5.object({
|
|
14592
|
+
patterns: z5.array(ConfigPatternSchema),
|
|
14593
|
+
customPatterns: z5.array(z5.any()).optional(),
|
|
13724
14594
|
// Code patterns are functions, can't validate
|
|
13725
|
-
ignoreFiles:
|
|
14595
|
+
ignoreFiles: z5.array(z5.string()).optional()
|
|
13726
14596
|
});
|
|
13727
|
-
DriftConfigSchema =
|
|
13728
|
-
docPaths:
|
|
13729
|
-
checkApiSignatures:
|
|
13730
|
-
checkExamples:
|
|
13731
|
-
checkStructure:
|
|
13732
|
-
ignorePatterns:
|
|
14597
|
+
DriftConfigSchema = z5.object({
|
|
14598
|
+
docPaths: z5.array(z5.string()).optional(),
|
|
14599
|
+
checkApiSignatures: z5.boolean().optional(),
|
|
14600
|
+
checkExamples: z5.boolean().optional(),
|
|
14601
|
+
checkStructure: z5.boolean().optional(),
|
|
14602
|
+
ignorePatterns: z5.array(z5.string()).optional(),
|
|
14603
|
+
forwardLookingPaths: z5.array(z5.string()).optional()
|
|
13733
14604
|
});
|
|
13734
|
-
DeadCodeConfigSchema =
|
|
13735
|
-
entryPoints:
|
|
13736
|
-
includeTypes:
|
|
13737
|
-
includeInternals:
|
|
13738
|
-
ignorePatterns:
|
|
13739
|
-
treatDynamicImportsAs:
|
|
14605
|
+
DeadCodeConfigSchema = z5.object({
|
|
14606
|
+
entryPoints: z5.array(z5.string()).optional(),
|
|
14607
|
+
includeTypes: z5.boolean().optional(),
|
|
14608
|
+
includeInternals: z5.boolean().optional(),
|
|
14609
|
+
ignorePatterns: z5.array(z5.string()).optional(),
|
|
14610
|
+
treatDynamicImportsAs: z5.enum(["used", "unknown"]).optional()
|
|
13740
14611
|
});
|
|
13741
|
-
EntropyConfigSchema =
|
|
13742
|
-
rootDir:
|
|
13743
|
-
parser:
|
|
14612
|
+
EntropyConfigSchema = z5.object({
|
|
14613
|
+
rootDir: z5.string(),
|
|
14614
|
+
parser: z5.any().optional(),
|
|
13744
14615
|
// LanguageParser instance, can't validate
|
|
13745
|
-
entryPoints:
|
|
13746
|
-
analyze:
|
|
13747
|
-
drift:
|
|
13748
|
-
deadCode:
|
|
13749
|
-
patterns:
|
|
14616
|
+
entryPoints: z5.array(z5.string()).optional(),
|
|
14617
|
+
analyze: z5.object({
|
|
14618
|
+
drift: z5.union([z5.boolean(), DriftConfigSchema]).optional(),
|
|
14619
|
+
deadCode: z5.union([z5.boolean(), DeadCodeConfigSchema]).optional(),
|
|
14620
|
+
patterns: z5.union([z5.boolean(), PatternConfigSchema]).optional()
|
|
13750
14621
|
}),
|
|
13751
|
-
include:
|
|
13752
|
-
exclude:
|
|
13753
|
-
docPaths:
|
|
14622
|
+
include: z5.array(z5.string()).optional(),
|
|
14623
|
+
exclude: z5.array(z5.string()).optional(),
|
|
14624
|
+
docPaths: z5.array(z5.string()).optional()
|
|
13754
14625
|
});
|
|
13755
14626
|
MAX_TOP_AFFECTED = 5;
|
|
13756
14627
|
BaselineManager = class {
|
|
13757
14628
|
baselinesPath;
|
|
13758
14629
|
constructor(projectRoot) {
|
|
13759
|
-
this.baselinesPath =
|
|
14630
|
+
this.baselinesPath = join18(projectRoot, ".harness", "perf", "baselines.json");
|
|
13760
14631
|
}
|
|
13761
14632
|
/**
|
|
13762
14633
|
* Load the baselines file from disk.
|
|
@@ -14030,16 +14901,16 @@ var init_dist3 = __esm({
|
|
|
14030
14901
|
walkDir(dir, entries) {
|
|
14031
14902
|
let items;
|
|
14032
14903
|
try {
|
|
14033
|
-
items =
|
|
14904
|
+
items = fs9.readdirSync(dir, { withFileTypes: true });
|
|
14034
14905
|
} catch {
|
|
14035
14906
|
return;
|
|
14036
14907
|
}
|
|
14037
14908
|
for (const item of items) {
|
|
14038
14909
|
if (item.isDirectory()) {
|
|
14039
14910
|
if (DEFAULT_SKIP_DIRS.has(item.name)) continue;
|
|
14040
|
-
this.walkDir(
|
|
14041
|
-
} else if (item.isFile() && SOURCE_EXTENSIONS.has(
|
|
14042
|
-
this.scanFile(
|
|
14911
|
+
this.walkDir(path6.join(dir, item.name), entries);
|
|
14912
|
+
} else if (item.isFile() && SOURCE_EXTENSIONS.has(path6.extname(item.name))) {
|
|
14913
|
+
this.scanFile(path6.join(dir, item.name), entries);
|
|
14043
14914
|
}
|
|
14044
14915
|
}
|
|
14045
14916
|
}
|
|
@@ -14054,12 +14925,12 @@ var init_dist3 = __esm({
|
|
|
14054
14925
|
scanFile(filePath, entries) {
|
|
14055
14926
|
let content;
|
|
14056
14927
|
try {
|
|
14057
|
-
content =
|
|
14928
|
+
content = fs9.readFileSync(filePath, "utf-8");
|
|
14058
14929
|
} catch {
|
|
14059
14930
|
return;
|
|
14060
14931
|
}
|
|
14061
14932
|
const lines = content.split("\n");
|
|
14062
|
-
const relativePath =
|
|
14933
|
+
const relativePath = path6.relative(this.projectRoot, filePath).replace(/\\/g, "/");
|
|
14063
14934
|
for (let i = 0; i < lines.length; i++) {
|
|
14064
14935
|
if (!lines[i].includes("@perf-critical")) continue;
|
|
14065
14936
|
const fnName = this.resolveFunctionName(lines, i + 1);
|
|
@@ -14458,80 +15329,80 @@ var init_dist3 = __esm({
|
|
|
14458
15329
|
return Ok(void 0);
|
|
14459
15330
|
}
|
|
14460
15331
|
};
|
|
14461
|
-
ConfidenceTierSchema =
|
|
14462
|
-
RegressionResultSchema =
|
|
14463
|
-
slope:
|
|
14464
|
-
intercept:
|
|
14465
|
-
rSquared:
|
|
14466
|
-
dataPoints:
|
|
15332
|
+
ConfidenceTierSchema = z6.enum(["high", "medium", "low"]);
|
|
15333
|
+
RegressionResultSchema = z6.object({
|
|
15334
|
+
slope: z6.number(),
|
|
15335
|
+
intercept: z6.number(),
|
|
15336
|
+
rSquared: z6.number().min(0).max(1),
|
|
15337
|
+
dataPoints: z6.number().int().min(0)
|
|
14467
15338
|
});
|
|
14468
|
-
DirectionSchema =
|
|
14469
|
-
CategoryForecastSchema =
|
|
15339
|
+
DirectionSchema = z6.enum(["improving", "stable", "declining"]);
|
|
15340
|
+
CategoryForecastSchema = z6.object({
|
|
14470
15341
|
category: ArchMetricCategorySchema,
|
|
14471
|
-
current:
|
|
14472
|
-
threshold:
|
|
14473
|
-
projectedValue4w:
|
|
14474
|
-
projectedValue8w:
|
|
14475
|
-
projectedValue12w:
|
|
14476
|
-
thresholdCrossingWeeks:
|
|
15342
|
+
current: z6.number(),
|
|
15343
|
+
threshold: z6.number(),
|
|
15344
|
+
projectedValue4w: z6.number(),
|
|
15345
|
+
projectedValue8w: z6.number(),
|
|
15346
|
+
projectedValue12w: z6.number(),
|
|
15347
|
+
thresholdCrossingWeeks: z6.number().nullable(),
|
|
14477
15348
|
confidence: ConfidenceTierSchema,
|
|
14478
15349
|
regression: RegressionResultSchema,
|
|
14479
15350
|
direction: DirectionSchema
|
|
14480
15351
|
});
|
|
14481
|
-
SpecImpactSignalsSchema =
|
|
14482
|
-
newFileCount:
|
|
14483
|
-
affectedLayers:
|
|
14484
|
-
newDependencies:
|
|
14485
|
-
phaseCount:
|
|
15352
|
+
SpecImpactSignalsSchema = z6.object({
|
|
15353
|
+
newFileCount: z6.number().int().min(0),
|
|
15354
|
+
affectedLayers: z6.array(z6.string()),
|
|
15355
|
+
newDependencies: z6.number().int().min(0),
|
|
15356
|
+
phaseCount: z6.number().int().min(0)
|
|
14486
15357
|
});
|
|
14487
|
-
SpecImpactEstimateSchema =
|
|
14488
|
-
specPath:
|
|
14489
|
-
featureName:
|
|
15358
|
+
SpecImpactEstimateSchema = z6.object({
|
|
15359
|
+
specPath: z6.string(),
|
|
15360
|
+
featureName: z6.string(),
|
|
14490
15361
|
signals: SpecImpactSignalsSchema,
|
|
14491
|
-
deltas:
|
|
15362
|
+
deltas: z6.record(ArchMetricCategorySchema, z6.number()).optional()
|
|
14492
15363
|
});
|
|
14493
|
-
ContributingFeatureSchema =
|
|
14494
|
-
name:
|
|
14495
|
-
specPath:
|
|
14496
|
-
delta:
|
|
15364
|
+
ContributingFeatureSchema = z6.object({
|
|
15365
|
+
name: z6.string(),
|
|
15366
|
+
specPath: z6.string(),
|
|
15367
|
+
delta: z6.number()
|
|
14497
15368
|
});
|
|
14498
|
-
AdjustedForecastSchema =
|
|
15369
|
+
AdjustedForecastSchema = z6.object({
|
|
14499
15370
|
baseline: CategoryForecastSchema,
|
|
14500
15371
|
adjusted: CategoryForecastSchema,
|
|
14501
|
-
contributingFeatures:
|
|
15372
|
+
contributingFeatures: z6.array(ContributingFeatureSchema)
|
|
14502
15373
|
});
|
|
14503
|
-
PredictionWarningSchema =
|
|
14504
|
-
severity:
|
|
15374
|
+
PredictionWarningSchema = z6.object({
|
|
15375
|
+
severity: z6.enum(["critical", "warning", "info"]),
|
|
14505
15376
|
category: ArchMetricCategorySchema,
|
|
14506
|
-
message:
|
|
14507
|
-
weeksUntil:
|
|
15377
|
+
message: z6.string(),
|
|
15378
|
+
weeksUntil: z6.number(),
|
|
14508
15379
|
confidence: ConfidenceTierSchema,
|
|
14509
|
-
contributingFeatures:
|
|
15380
|
+
contributingFeatures: z6.array(z6.string())
|
|
14510
15381
|
});
|
|
14511
|
-
StabilityForecastSchema =
|
|
14512
|
-
current:
|
|
14513
|
-
projected4w:
|
|
14514
|
-
projected8w:
|
|
14515
|
-
projected12w:
|
|
15382
|
+
StabilityForecastSchema = z6.object({
|
|
15383
|
+
current: z6.number(),
|
|
15384
|
+
projected4w: z6.number(),
|
|
15385
|
+
projected8w: z6.number(),
|
|
15386
|
+
projected12w: z6.number(),
|
|
14516
15387
|
confidence: ConfidenceTierSchema,
|
|
14517
15388
|
direction: DirectionSchema
|
|
14518
15389
|
});
|
|
14519
|
-
PredictionResultSchema =
|
|
14520
|
-
generatedAt:
|
|
14521
|
-
snapshotsUsed:
|
|
14522
|
-
timelineRange:
|
|
14523
|
-
from:
|
|
14524
|
-
to:
|
|
15390
|
+
PredictionResultSchema = z6.object({
|
|
15391
|
+
generatedAt: z6.string(),
|
|
15392
|
+
snapshotsUsed: z6.number().int().min(0),
|
|
15393
|
+
timelineRange: z6.object({
|
|
15394
|
+
from: z6.string(),
|
|
15395
|
+
to: z6.string()
|
|
14525
15396
|
}),
|
|
14526
15397
|
stabilityForecast: StabilityForecastSchema,
|
|
14527
|
-
categories:
|
|
14528
|
-
warnings:
|
|
15398
|
+
categories: z6.record(ArchMetricCategorySchema, AdjustedForecastSchema),
|
|
15399
|
+
warnings: z6.array(PredictionWarningSchema)
|
|
14529
15400
|
});
|
|
14530
|
-
PredictionOptionsSchema =
|
|
14531
|
-
horizon:
|
|
14532
|
-
includeRoadmap:
|
|
14533
|
-
categories:
|
|
14534
|
-
thresholds:
|
|
15401
|
+
PredictionOptionsSchema = z6.object({
|
|
15402
|
+
horizon: z6.number().int().min(1).default(12),
|
|
15403
|
+
includeRoadmap: z6.boolean().default(true),
|
|
15404
|
+
categories: z6.array(ArchMetricCategorySchema).optional(),
|
|
15405
|
+
thresholds: z6.record(ArchMetricCategorySchema, z6.number()).optional()
|
|
14535
15406
|
});
|
|
14536
15407
|
VALID_STATUSES = /* @__PURE__ */ new Set([
|
|
14537
15408
|
"backlog",
|
|
@@ -14864,8 +15735,8 @@ var init_dist3 = __esm({
|
|
|
14864
15735
|
return null;
|
|
14865
15736
|
}
|
|
14866
15737
|
try {
|
|
14867
|
-
const roadmapPath =
|
|
14868
|
-
const raw =
|
|
15738
|
+
const roadmapPath = path7.join(this.rootDir, "roadmap.md");
|
|
15739
|
+
const raw = fs10.readFileSync(roadmapPath, "utf-8");
|
|
14869
15740
|
const parseResult = parseRoadmap(raw);
|
|
14870
15741
|
if (!parseResult.ok) return null;
|
|
14871
15742
|
const features = parseResult.value.milestones.flatMap(
|
|
@@ -14900,8 +15771,8 @@ var init_dist3 = __esm({
|
|
|
14900
15771
|
* @param specPath - Relative path from rootDir to the spec file.
|
|
14901
15772
|
*/
|
|
14902
15773
|
estimate(specPath) {
|
|
14903
|
-
const absolutePath =
|
|
14904
|
-
const content =
|
|
15774
|
+
const absolutePath = path8.join(this.rootDir, specPath);
|
|
15775
|
+
const content = fs11.readFileSync(absolutePath, "utf-8");
|
|
14905
15776
|
const newFileCount = this.extractNewFileCount(content);
|
|
14906
15777
|
const affectedLayers = this.extractAffectedLayers(content);
|
|
14907
15778
|
const newDependencies = this.extractNewDependencies(content);
|
|
@@ -14913,7 +15784,7 @@ var init_dist3 = __esm({
|
|
|
14913
15784
|
phaseCount
|
|
14914
15785
|
);
|
|
14915
15786
|
const h1Match = content.match(/^#\s+(.+)$/m);
|
|
14916
|
-
const featureName = h1Match ? h1Match[1].trim() :
|
|
15787
|
+
const featureName = h1Match ? h1Match[1].trim() : path8.basename(specPath, ".md");
|
|
14917
15788
|
return {
|
|
14918
15789
|
specPath,
|
|
14919
15790
|
featureName,
|
|
@@ -14934,8 +15805,8 @@ var init_dist3 = __esm({
|
|
|
14934
15805
|
const results = [];
|
|
14935
15806
|
for (const feature of features) {
|
|
14936
15807
|
if (!feature.spec) continue;
|
|
14937
|
-
const absolutePath =
|
|
14938
|
-
if (!
|
|
15808
|
+
const absolutePath = path8.join(this.rootDir, feature.spec);
|
|
15809
|
+
if (!fs11.existsSync(absolutePath)) continue;
|
|
14939
15810
|
const estimate = this.estimate(feature.spec);
|
|
14940
15811
|
results.push({ ...estimate, featureName: feature.name });
|
|
14941
15812
|
}
|
|
@@ -14963,8 +15834,8 @@ var init_dist3 = __esm({
|
|
|
14963
15834
|
}
|
|
14964
15835
|
let count = 0;
|
|
14965
15836
|
for (const fp of filePaths) {
|
|
14966
|
-
const absolute =
|
|
14967
|
-
if (!
|
|
15837
|
+
const absolute = path8.join(this.rootDir, fp);
|
|
15838
|
+
if (!fs11.existsSync(absolute)) {
|
|
14968
15839
|
count++;
|
|
14969
15840
|
}
|
|
14970
15841
|
}
|
|
@@ -15034,8 +15905,8 @@ var init_dist3 = __esm({
|
|
|
15034
15905
|
// --- Private: Config Loading ---
|
|
15035
15906
|
loadLayerNames() {
|
|
15036
15907
|
try {
|
|
15037
|
-
const configPath =
|
|
15038
|
-
const raw =
|
|
15908
|
+
const configPath = path8.join(this.rootDir, "harness.config.json");
|
|
15909
|
+
const raw = fs11.readFileSync(configPath, "utf-8");
|
|
15039
15910
|
const config = JSON.parse(raw);
|
|
15040
15911
|
return (config.layers ?? []).map((l) => l.name);
|
|
15041
15912
|
} catch {
|
|
@@ -15054,8 +15925,8 @@ var init_dist3 = __esm({
|
|
|
15054
15925
|
historyPath;
|
|
15055
15926
|
/** Load history from disk, returning empty history if file doesn't exist. */
|
|
15056
15927
|
load() {
|
|
15057
|
-
if (!
|
|
15058
|
-
const raw =
|
|
15928
|
+
if (!fs12.existsSync(this.historyPath)) return { ...EMPTY_HISTORY, snapshots: [] };
|
|
15929
|
+
const raw = fs12.readFileSync(this.historyPath, "utf-8");
|
|
15059
15930
|
return JSON.parse(raw);
|
|
15060
15931
|
}
|
|
15061
15932
|
/** Append a timestamped snapshot of current violations. */
|
|
@@ -15069,18 +15940,18 @@ var init_dist3 = __esm({
|
|
|
15069
15940
|
}
|
|
15070
15941
|
/** Prune snapshots older than retentionDays. */
|
|
15071
15942
|
prune(retentionDays) {
|
|
15072
|
-
if (!
|
|
15943
|
+
if (!fs12.existsSync(this.historyPath)) return;
|
|
15073
15944
|
const history = this.load();
|
|
15074
15945
|
const cutoff = Date.now() - retentionDays * 24 * 60 * 60 * 1e3;
|
|
15075
15946
|
history.snapshots = history.snapshots.filter((s) => new Date(s.timestamp).getTime() >= cutoff);
|
|
15076
15947
|
this.write(history);
|
|
15077
15948
|
}
|
|
15078
15949
|
write(history) {
|
|
15079
|
-
const dir =
|
|
15080
|
-
if (!
|
|
15081
|
-
|
|
15950
|
+
const dir = path9.dirname(this.historyPath);
|
|
15951
|
+
if (!fs12.existsSync(dir)) {
|
|
15952
|
+
fs12.mkdirSync(dir, { recursive: true });
|
|
15082
15953
|
}
|
|
15083
|
-
|
|
15954
|
+
fs12.writeFileSync(this.historyPath, JSON.stringify(history, null, 2), "utf-8");
|
|
15084
15955
|
}
|
|
15085
15956
|
};
|
|
15086
15957
|
NORMALIZERS = {
|
|
@@ -15095,81 +15966,81 @@ var init_dist3 = __esm({
|
|
|
15095
15966
|
MAX_SAMPLES = 5;
|
|
15096
15967
|
DEFAULT_WINDOW_WEEKS = 4;
|
|
15097
15968
|
DEFAULT_MIN_OCCURRENCES = 3;
|
|
15098
|
-
FailureEntrySchema =
|
|
15099
|
-
date:
|
|
15100
|
-
skill:
|
|
15101
|
-
type:
|
|
15102
|
-
description:
|
|
15969
|
+
FailureEntrySchema = z7.object({
|
|
15970
|
+
date: z7.string(),
|
|
15971
|
+
skill: z7.string(),
|
|
15972
|
+
type: z7.string(),
|
|
15973
|
+
description: z7.string()
|
|
15103
15974
|
});
|
|
15104
|
-
HandoffSchema =
|
|
15105
|
-
timestamp:
|
|
15106
|
-
fromSkill:
|
|
15107
|
-
phase:
|
|
15108
|
-
summary:
|
|
15109
|
-
completed:
|
|
15110
|
-
pending:
|
|
15111
|
-
concerns:
|
|
15112
|
-
decisions:
|
|
15113
|
-
|
|
15114
|
-
what:
|
|
15115
|
-
why:
|
|
15975
|
+
HandoffSchema = z7.object({
|
|
15976
|
+
timestamp: z7.string(),
|
|
15977
|
+
fromSkill: z7.string(),
|
|
15978
|
+
phase: z7.string(),
|
|
15979
|
+
summary: z7.string(),
|
|
15980
|
+
completed: z7.array(z7.string()).default([]),
|
|
15981
|
+
pending: z7.array(z7.string()).default([]),
|
|
15982
|
+
concerns: z7.array(z7.string()).default([]),
|
|
15983
|
+
decisions: z7.array(
|
|
15984
|
+
z7.object({
|
|
15985
|
+
what: z7.string(),
|
|
15986
|
+
why: z7.string()
|
|
15116
15987
|
})
|
|
15117
15988
|
).default([]),
|
|
15118
|
-
blockers:
|
|
15119
|
-
contextKeywords:
|
|
15120
|
-
recommendedSkills:
|
|
15121
|
-
apply:
|
|
15122
|
-
reference:
|
|
15123
|
-
consider:
|
|
15124
|
-
skillsPath:
|
|
15989
|
+
blockers: z7.array(z7.string()).default([]),
|
|
15990
|
+
contextKeywords: z7.array(z7.string()).default([]),
|
|
15991
|
+
recommendedSkills: z7.object({
|
|
15992
|
+
apply: z7.array(z7.string()),
|
|
15993
|
+
reference: z7.array(z7.string()),
|
|
15994
|
+
consider: z7.array(z7.string()),
|
|
15995
|
+
skillsPath: z7.string()
|
|
15125
15996
|
}).optional()
|
|
15126
15997
|
});
|
|
15127
|
-
GateCheckSchema =
|
|
15128
|
-
name:
|
|
15129
|
-
passed:
|
|
15130
|
-
command:
|
|
15131
|
-
output:
|
|
15132
|
-
duration:
|
|
15998
|
+
GateCheckSchema = z7.object({
|
|
15999
|
+
name: z7.string(),
|
|
16000
|
+
passed: z7.boolean(),
|
|
16001
|
+
command: z7.string(),
|
|
16002
|
+
output: z7.string().optional(),
|
|
16003
|
+
duration: z7.number().optional()
|
|
15133
16004
|
});
|
|
15134
|
-
GateResultSchema =
|
|
15135
|
-
passed:
|
|
15136
|
-
checks:
|
|
16005
|
+
GateResultSchema = z7.object({
|
|
16006
|
+
passed: z7.boolean(),
|
|
16007
|
+
checks: z7.array(GateCheckSchema)
|
|
15137
16008
|
});
|
|
15138
|
-
GateConfigSchema =
|
|
15139
|
-
checks:
|
|
15140
|
-
|
|
15141
|
-
name:
|
|
15142
|
-
command:
|
|
16009
|
+
GateConfigSchema = z7.object({
|
|
16010
|
+
checks: z7.array(
|
|
16011
|
+
z7.object({
|
|
16012
|
+
name: z7.string(),
|
|
16013
|
+
command: z7.string()
|
|
15143
16014
|
})
|
|
15144
16015
|
).optional(),
|
|
15145
|
-
trace:
|
|
16016
|
+
trace: z7.boolean().optional()
|
|
15146
16017
|
});
|
|
15147
|
-
HarnessStateSchema =
|
|
15148
|
-
schemaVersion:
|
|
15149
|
-
position:
|
|
15150
|
-
phase:
|
|
15151
|
-
task:
|
|
16018
|
+
HarnessStateSchema = z7.object({
|
|
16019
|
+
schemaVersion: z7.literal(1),
|
|
16020
|
+
position: z7.object({
|
|
16021
|
+
phase: z7.string().optional(),
|
|
16022
|
+
task: z7.string().optional()
|
|
15152
16023
|
}).default({}),
|
|
15153
|
-
decisions:
|
|
15154
|
-
|
|
15155
|
-
date:
|
|
15156
|
-
decision:
|
|
15157
|
-
context:
|
|
16024
|
+
decisions: z7.array(
|
|
16025
|
+
z7.object({
|
|
16026
|
+
date: z7.string(),
|
|
16027
|
+
decision: z7.string(),
|
|
16028
|
+
context: z7.string()
|
|
15158
16029
|
})
|
|
15159
16030
|
).default([]),
|
|
15160
|
-
blockers:
|
|
15161
|
-
|
|
15162
|
-
id:
|
|
15163
|
-
description:
|
|
15164
|
-
status:
|
|
16031
|
+
blockers: z7.array(
|
|
16032
|
+
z7.object({
|
|
16033
|
+
id: z7.string(),
|
|
16034
|
+
description: z7.string(),
|
|
16035
|
+
status: z7.enum(["open", "resolved"])
|
|
15165
16036
|
})
|
|
15166
16037
|
).default([]),
|
|
15167
|
-
progress:
|
|
15168
|
-
lastSession:
|
|
15169
|
-
date:
|
|
15170
|
-
summary:
|
|
15171
|
-
lastSkill:
|
|
15172
|
-
pendingTasks:
|
|
16038
|
+
progress: z7.record(z7.enum(["pending", "in_progress", "complete"])).default({}),
|
|
16039
|
+
lastSession: z7.object({
|
|
16040
|
+
date: z7.string(),
|
|
16041
|
+
summary: z7.string(),
|
|
16042
|
+
lastSkill: z7.string().optional(),
|
|
16043
|
+
pendingTasks: z7.array(z7.string()).optional()
|
|
15173
16044
|
}).optional()
|
|
15174
16045
|
});
|
|
15175
16046
|
DEFAULT_STATE = {
|
|
@@ -15179,16 +16050,16 @@ var init_dist3 = __esm({
|
|
|
15179
16050
|
blockers: [],
|
|
15180
16051
|
progress: {}
|
|
15181
16052
|
};
|
|
15182
|
-
StreamInfoSchema =
|
|
15183
|
-
name:
|
|
15184
|
-
branch:
|
|
15185
|
-
createdAt:
|
|
15186
|
-
lastActiveAt:
|
|
16053
|
+
StreamInfoSchema = z8.object({
|
|
16054
|
+
name: z8.string(),
|
|
16055
|
+
branch: z8.string().optional(),
|
|
16056
|
+
createdAt: z8.string(),
|
|
16057
|
+
lastActiveAt: z8.string()
|
|
15187
16058
|
});
|
|
15188
|
-
StreamIndexSchema =
|
|
15189
|
-
schemaVersion:
|
|
15190
|
-
activeStream:
|
|
15191
|
-
streams:
|
|
16059
|
+
StreamIndexSchema = z8.object({
|
|
16060
|
+
schemaVersion: z8.literal(1),
|
|
16061
|
+
activeStream: z8.string().nullable(),
|
|
16062
|
+
streams: z8.record(StreamInfoSchema)
|
|
15192
16063
|
});
|
|
15193
16064
|
DEFAULT_STREAM_INDEX = {
|
|
15194
16065
|
schemaVersion: 1,
|
|
@@ -15229,15 +16100,15 @@ var init_dist3 = __esm({
|
|
|
15229
16100
|
failuresCacheMap = /* @__PURE__ */ new Map();
|
|
15230
16101
|
FAILURE_LINE_REGEX = /^- \*\*(\d{4}-\d{2}-\d{2}) \[skill:([^\]]+)\] \[type:([^\]]+)\]:\*\* (.+)$/;
|
|
15231
16102
|
SAFE_GATE_COMMAND = /^(?:npm|pnpm|yarn)\s+(?:test|run\s+[\w.-]+|run-script\s+[\w.-]+)$|^go\s+(?:test|build|vet|fmt)\s+[\w./ -]+$|^(?:python|python3)\s+-m\s+[\w.-]+$|^make\s+[\w.-]+$|^cargo\s+(?:test|build|check|clippy)(?:\s+[\w./ -]+)?$|^(?:gradle|mvn)\s+[\w:.-]+$/;
|
|
15232
|
-
SkillEventSchema =
|
|
15233
|
-
timestamp:
|
|
15234
|
-
skill:
|
|
15235
|
-
session:
|
|
15236
|
-
type:
|
|
15237
|
-
summary:
|
|
15238
|
-
data:
|
|
15239
|
-
refs:
|
|
15240
|
-
contentHash:
|
|
16103
|
+
SkillEventSchema = z9.object({
|
|
16104
|
+
timestamp: z9.string(),
|
|
16105
|
+
skill: z9.string(),
|
|
16106
|
+
session: z9.string().optional(),
|
|
16107
|
+
type: z9.enum(["phase_transition", "decision", "gate_result", "handoff", "error", "checkpoint"]),
|
|
16108
|
+
summary: z9.string(),
|
|
16109
|
+
data: z9.record(z9.unknown()).optional(),
|
|
16110
|
+
refs: z9.array(z9.string()).optional(),
|
|
16111
|
+
contentHash: z9.string().optional()
|
|
15241
16112
|
});
|
|
15242
16113
|
knownHashesCache = /* @__PURE__ */ new Map();
|
|
15243
16114
|
EVENT_FORMATTERS = {
|
|
@@ -15280,19 +16151,19 @@ var init_dist3 = __esm({
|
|
|
15280
16151
|
rules: {},
|
|
15281
16152
|
exclude: [...skipDirGlobs(), "**/*.test.ts", "**/fixtures/**"]
|
|
15282
16153
|
};
|
|
15283
|
-
RuleOverrideSchema =
|
|
15284
|
-
SecurityConfigSchema =
|
|
15285
|
-
enabled:
|
|
15286
|
-
strict:
|
|
15287
|
-
rules:
|
|
15288
|
-
exclude:
|
|
15289
|
-
external:
|
|
15290
|
-
semgrep:
|
|
15291
|
-
enabled:
|
|
15292
|
-
rulesets:
|
|
16154
|
+
RuleOverrideSchema = z10.enum(["off", "error", "warning", "info"]);
|
|
16155
|
+
SecurityConfigSchema = z10.object({
|
|
16156
|
+
enabled: z10.boolean().default(true),
|
|
16157
|
+
strict: z10.boolean().default(false),
|
|
16158
|
+
rules: z10.record(z10.string(), RuleOverrideSchema).optional().default({}),
|
|
16159
|
+
exclude: z10.array(z10.string()).optional().default([...skipDirGlobs(), "**/*.test.ts", "**/fixtures/**"]),
|
|
16160
|
+
external: z10.object({
|
|
16161
|
+
semgrep: z10.object({
|
|
16162
|
+
enabled: z10.union([z10.literal("auto"), z10.boolean()]).default("auto"),
|
|
16163
|
+
rulesets: z10.array(z10.string()).optional()
|
|
15293
16164
|
}).optional(),
|
|
15294
|
-
gitleaks:
|
|
15295
|
-
enabled:
|
|
16165
|
+
gitleaks: z10.object({
|
|
16166
|
+
enabled: z10.union([z10.literal("auto"), z10.boolean()]).default("auto")
|
|
15296
16167
|
}).optional()
|
|
15297
16168
|
}).optional()
|
|
15298
16169
|
});
|
|
@@ -16082,7 +16953,7 @@ var init_dist3 = __esm({
|
|
|
16082
16953
|
}
|
|
16083
16954
|
async scanFile(filePath) {
|
|
16084
16955
|
if (!this.config.enabled) return [];
|
|
16085
|
-
const content = await
|
|
16956
|
+
const content = await fs30.readFile(filePath, "utf-8");
|
|
16086
16957
|
return this.scanContentForFile(content, filePath, 1);
|
|
16087
16958
|
}
|
|
16088
16959
|
scanContentForFile(content, filePath, startLine = 1) {
|
|
@@ -16204,7 +17075,7 @@ var init_dist3 = __esm({
|
|
|
16204
17075
|
}
|
|
16205
17076
|
};
|
|
16206
17077
|
OSV_ENDPOINT = "https://api.osv.dev/v1/query";
|
|
16207
|
-
DEFAULT_CACHE_DIR =
|
|
17078
|
+
DEFAULT_CACHE_DIR = path27.join(".harness", "cache", "osv");
|
|
16208
17079
|
DEFAULT_TTL_HOURS = 24;
|
|
16209
17080
|
hiddenUnicodePatterns = [
|
|
16210
17081
|
{
|
|
@@ -16415,88 +17286,88 @@ var init_dist3 = __esm({
|
|
|
16415
17286
|
];
|
|
16416
17287
|
TAINT_DURATION_MS = 30 * 60 * 1e3;
|
|
16417
17288
|
DEFAULT_SESSION_ID = "default";
|
|
16418
|
-
SecurityCategorySnapshotSchema =
|
|
16419
|
-
findingCount:
|
|
16420
|
-
errorCount:
|
|
16421
|
-
warningCount:
|
|
16422
|
-
infoCount:
|
|
17289
|
+
SecurityCategorySnapshotSchema = z11.object({
|
|
17290
|
+
findingCount: z11.number(),
|
|
17291
|
+
errorCount: z11.number(),
|
|
17292
|
+
warningCount: z11.number(),
|
|
17293
|
+
infoCount: z11.number()
|
|
16423
17294
|
});
|
|
16424
|
-
SupplyChainSnapshotSchema =
|
|
16425
|
-
critical:
|
|
16426
|
-
high:
|
|
16427
|
-
moderate:
|
|
16428
|
-
low:
|
|
16429
|
-
info:
|
|
16430
|
-
total:
|
|
17295
|
+
SupplyChainSnapshotSchema = z11.object({
|
|
17296
|
+
critical: z11.number(),
|
|
17297
|
+
high: z11.number(),
|
|
17298
|
+
moderate: z11.number(),
|
|
17299
|
+
low: z11.number(),
|
|
17300
|
+
info: z11.number(),
|
|
17301
|
+
total: z11.number()
|
|
16431
17302
|
});
|
|
16432
|
-
SecurityTimelineSnapshotSchema =
|
|
16433
|
-
capturedAt:
|
|
16434
|
-
commitHash:
|
|
16435
|
-
securityScore:
|
|
16436
|
-
totalFindings:
|
|
16437
|
-
bySeverity:
|
|
16438
|
-
error:
|
|
16439
|
-
warning:
|
|
16440
|
-
info:
|
|
17303
|
+
SecurityTimelineSnapshotSchema = z11.object({
|
|
17304
|
+
capturedAt: z11.string().datetime(),
|
|
17305
|
+
commitHash: z11.string(),
|
|
17306
|
+
securityScore: z11.number().min(0).max(100),
|
|
17307
|
+
totalFindings: z11.number(),
|
|
17308
|
+
bySeverity: z11.object({
|
|
17309
|
+
error: z11.number(),
|
|
17310
|
+
warning: z11.number(),
|
|
17311
|
+
info: z11.number()
|
|
16441
17312
|
}),
|
|
16442
|
-
byCategory:
|
|
17313
|
+
byCategory: z11.record(z11.string(), SecurityCategorySnapshotSchema),
|
|
16443
17314
|
supplyChain: SupplyChainSnapshotSchema,
|
|
16444
|
-
suppressionCount:
|
|
16445
|
-
findingIds:
|
|
17315
|
+
suppressionCount: z11.number(),
|
|
17316
|
+
findingIds: z11.array(z11.string())
|
|
16446
17317
|
});
|
|
16447
|
-
FindingLifecycleSchema =
|
|
16448
|
-
findingId:
|
|
16449
|
-
ruleId:
|
|
16450
|
-
category:
|
|
16451
|
-
severity:
|
|
16452
|
-
file:
|
|
16453
|
-
firstSeenAt:
|
|
16454
|
-
firstSeenCommit:
|
|
16455
|
-
resolvedAt:
|
|
16456
|
-
resolvedCommit:
|
|
17318
|
+
FindingLifecycleSchema = z11.object({
|
|
17319
|
+
findingId: z11.string(),
|
|
17320
|
+
ruleId: z11.string(),
|
|
17321
|
+
category: z11.string(),
|
|
17322
|
+
severity: z11.string(),
|
|
17323
|
+
file: z11.string(),
|
|
17324
|
+
firstSeenAt: z11.string().datetime(),
|
|
17325
|
+
firstSeenCommit: z11.string(),
|
|
17326
|
+
resolvedAt: z11.string().datetime().nullable(),
|
|
17327
|
+
resolvedCommit: z11.string().nullable()
|
|
16457
17328
|
});
|
|
16458
|
-
SecurityTimelineFileSchema =
|
|
16459
|
-
version:
|
|
16460
|
-
snapshots:
|
|
16461
|
-
findingLifecycles:
|
|
17329
|
+
SecurityTimelineFileSchema = z11.object({
|
|
17330
|
+
version: z11.literal(1),
|
|
17331
|
+
snapshots: z11.array(SecurityTimelineSnapshotSchema),
|
|
17332
|
+
findingLifecycles: z11.array(FindingLifecycleSchema)
|
|
16462
17333
|
});
|
|
16463
|
-
DirectionSchema2 =
|
|
16464
|
-
SecurityTrendLineSchema =
|
|
16465
|
-
current:
|
|
16466
|
-
previous:
|
|
16467
|
-
delta:
|
|
17334
|
+
DirectionSchema2 = z11.enum(["improving", "stable", "declining"]);
|
|
17335
|
+
SecurityTrendLineSchema = z11.object({
|
|
17336
|
+
current: z11.number(),
|
|
17337
|
+
previous: z11.number(),
|
|
17338
|
+
delta: z11.number(),
|
|
16468
17339
|
direction: DirectionSchema2
|
|
16469
17340
|
});
|
|
16470
|
-
TrendAttributionSchema =
|
|
16471
|
-
category:
|
|
16472
|
-
delta:
|
|
17341
|
+
TrendAttributionSchema = z11.object({
|
|
17342
|
+
category: z11.string(),
|
|
17343
|
+
delta: z11.number(),
|
|
16473
17344
|
direction: DirectionSchema2,
|
|
16474
|
-
description:
|
|
17345
|
+
description: z11.string()
|
|
16475
17346
|
});
|
|
16476
|
-
SecurityTrendResultSchema =
|
|
17347
|
+
SecurityTrendResultSchema = z11.object({
|
|
16477
17348
|
score: SecurityTrendLineSchema,
|
|
16478
17349
|
totalFindings: SecurityTrendLineSchema,
|
|
16479
|
-
bySeverity:
|
|
17350
|
+
bySeverity: z11.object({
|
|
16480
17351
|
error: SecurityTrendLineSchema,
|
|
16481
17352
|
warning: SecurityTrendLineSchema,
|
|
16482
17353
|
info: SecurityTrendLineSchema
|
|
16483
17354
|
}),
|
|
16484
17355
|
supplyChain: SecurityTrendLineSchema,
|
|
16485
|
-
snapshotCount:
|
|
16486
|
-
from:
|
|
16487
|
-
to:
|
|
16488
|
-
attribution:
|
|
17356
|
+
snapshotCount: z11.number(),
|
|
17357
|
+
from: z11.string(),
|
|
17358
|
+
to: z11.string(),
|
|
17359
|
+
attribution: z11.array(TrendAttributionSchema)
|
|
16489
17360
|
});
|
|
16490
|
-
TimeToFixStatsSchema =
|
|
16491
|
-
mean:
|
|
16492
|
-
median:
|
|
16493
|
-
count:
|
|
17361
|
+
TimeToFixStatsSchema = z11.object({
|
|
17362
|
+
mean: z11.number(),
|
|
17363
|
+
median: z11.number(),
|
|
17364
|
+
count: z11.number()
|
|
16494
17365
|
});
|
|
16495
|
-
TimeToFixResultSchema =
|
|
17366
|
+
TimeToFixResultSchema = z11.object({
|
|
16496
17367
|
overall: TimeToFixStatsSchema,
|
|
16497
|
-
byCategory:
|
|
16498
|
-
openFindings:
|
|
16499
|
-
oldestOpenDays:
|
|
17368
|
+
byCategory: z11.record(z11.string(), TimeToFixStatsSchema),
|
|
17369
|
+
openFindings: z11.number(),
|
|
17370
|
+
oldestOpenDays: z11.number().nullable()
|
|
16500
17371
|
});
|
|
16501
17372
|
EMPTY_SUPPLY_CHAIN = {
|
|
16502
17373
|
critical: 0,
|
|
@@ -16511,7 +17382,7 @@ var init_dist3 = __esm({
|
|
|
16511
17382
|
timelinePath;
|
|
16512
17383
|
constructor(rootDir) {
|
|
16513
17384
|
this.rootDir = rootDir;
|
|
16514
|
-
this.timelinePath =
|
|
17385
|
+
this.timelinePath = join40(rootDir, ".harness", "security", "timeline.json");
|
|
16515
17386
|
}
|
|
16516
17387
|
/**
|
|
16517
17388
|
* Load timeline from disk.
|
|
@@ -17133,6 +18004,49 @@ var init_dist3 = __esm({
|
|
|
17133
18004
|
]
|
|
17134
18005
|
};
|
|
17135
18006
|
RELEVANCE_THRESHOLD = 0.3;
|
|
18007
|
+
ADVERSARIAL_DESCRIPTOR = {
|
|
18008
|
+
domain: "bug",
|
|
18009
|
+
tier: "strong",
|
|
18010
|
+
displayName: "Adversarial",
|
|
18011
|
+
focusAreas: [
|
|
18012
|
+
"Assumption violation \u2014 hidden invariants the diff assumes but does not enforce",
|
|
18013
|
+
"Composition failures \u2014 two correct pieces that misbehave together",
|
|
18014
|
+
"Abuse cases \u2014 adversarial input shapes the diff does not anticipate",
|
|
18015
|
+
"Cascade chains (Deep only) \u2014 a single failure propagating through downstream callers"
|
|
18016
|
+
]
|
|
18017
|
+
};
|
|
18018
|
+
UNVERIFIED_INPUT_PATTERN = /\bJSON\.parse\s*\(\s*(?:req\.|request\.|body\.|input\.|params\.|query\.)/;
|
|
18019
|
+
PROMISE_NO_REJECT_PATTERN = /\bnew\s+Promise\s*\(\s*\(?\s*(?:resolve|res)\b\s*(?:\)|,\s*\))/;
|
|
18020
|
+
UNGUARDED_OPTIONAL_CALL = /\b([A-Za-z_$][\w$]*)\?\.\1\s*\(/;
|
|
18021
|
+
COMPOSE_NO_AWAIT = /^\s*[A-Za-z_$][\w$]*\s*\(.*\)\s*\.\s*then\s*\(/;
|
|
18022
|
+
FETCH_NO_TIMEOUT = /\bfetch\s*\(\s*[^,)]+\)/;
|
|
18023
|
+
TYPESCRIPT_STRICT_DESCRIPTOR = {
|
|
18024
|
+
domain: "bug",
|
|
18025
|
+
tier: "standard",
|
|
18026
|
+
displayName: "TypeScript-strict",
|
|
18027
|
+
focusAreas: [
|
|
18028
|
+
"Type holes that disable the checker (any, unknown-as-X assertions, ts-ignore/ts-expect-error)",
|
|
18029
|
+
"Existing-file complexity (large file growth as a Single-Responsibility risk)",
|
|
18030
|
+
"Refactor regression risk \u2014 changed exports without companion test updates",
|
|
18031
|
+
"Five-second rule \u2014 names a reader cannot guess from the call site"
|
|
18032
|
+
]
|
|
18033
|
+
};
|
|
18034
|
+
EXPLICIT_ANY_PATTERN = /(?<![A-Za-z0-9_$])(?:any|Array\s*<\s*any\s*>)(?![A-Za-z0-9_$])/;
|
|
18035
|
+
TS_IGNORE_PATTERN = /\/\/\s*@?ts-(ignore|expect-error)\b/;
|
|
18036
|
+
UNSAFE_AS_UNKNOWN = /\bas\s+unknown\s+as\s+\w+/;
|
|
18037
|
+
NON_NULL_ASSERTION = /\)!|\]\s*!|[\w$]+!\.(?:[\w$])/;
|
|
18038
|
+
VAGUE_HELPER_NAME = /^\s*(?:export\s+)?(?:async\s+)?function\s+(handle|process|do|stuff|util|helper)\w*\s*\(/i;
|
|
18039
|
+
FRONTEND_RACES_DESCRIPTOR = {
|
|
18040
|
+
domain: "bug",
|
|
18041
|
+
tier: "standard",
|
|
18042
|
+
displayName: "Frontend-races",
|
|
18043
|
+
focusAreas: [
|
|
18044
|
+
"Lifecycle cleanup gaps \u2014 listeners or timers outliving the owner",
|
|
18045
|
+
"Hook timing mistakes \u2014 state set in the wrong hook, async after unmount",
|
|
18046
|
+
"Concurrent interactions \u2014 overlapping clicks/requests, impossible-state booleans",
|
|
18047
|
+
"Stale work \u2014 promises and timers whose callbacks fire after teardown"
|
|
18048
|
+
]
|
|
18049
|
+
};
|
|
17136
18050
|
AGENT_DESCRIPTORS = {
|
|
17137
18051
|
compliance: COMPLIANCE_DESCRIPTOR,
|
|
17138
18052
|
bug: BUG_DETECTION_DESCRIPTOR,
|
|
@@ -17140,6 +18054,11 @@ var init_dist3 = __esm({
|
|
|
17140
18054
|
architecture: ARCHITECTURE_DESCRIPTOR,
|
|
17141
18055
|
learnings: LEARNINGS_DESCRIPTOR
|
|
17142
18056
|
};
|
|
18057
|
+
CONDITIONAL_SUBAGENT_DESCRIPTORS = {
|
|
18058
|
+
adversarial: ADVERSARIAL_DESCRIPTOR,
|
|
18059
|
+
"typescript-strict": TYPESCRIPT_STRICT_DESCRIPTOR,
|
|
18060
|
+
"frontend-races": FRONTEND_RACES_DESCRIPTOR
|
|
18061
|
+
};
|
|
17143
18062
|
AGENT_RUNNERS = {
|
|
17144
18063
|
compliance: runComplianceAgent,
|
|
17145
18064
|
bug: runBugDetectionAgent,
|
|
@@ -17147,6 +18066,57 @@ var init_dist3 = __esm({
|
|
|
17147
18066
|
architecture: runArchitectureAgent,
|
|
17148
18067
|
learnings: runLearningsAgent
|
|
17149
18068
|
};
|
|
18069
|
+
SUBAGENT_ORDER = [
|
|
18070
|
+
"compliance",
|
|
18071
|
+
"bug",
|
|
18072
|
+
"security",
|
|
18073
|
+
"architecture",
|
|
18074
|
+
"learnings",
|
|
18075
|
+
"adversarial",
|
|
18076
|
+
"typescript-strict",
|
|
18077
|
+
"frontend-races"
|
|
18078
|
+
];
|
|
18079
|
+
RISK_KEYWORDS = Object.freeze([
|
|
18080
|
+
"auth",
|
|
18081
|
+
"authn",
|
|
18082
|
+
"authz",
|
|
18083
|
+
"password",
|
|
18084
|
+
"token",
|
|
18085
|
+
"payment",
|
|
18086
|
+
"billing",
|
|
18087
|
+
"migration",
|
|
18088
|
+
"migrate",
|
|
18089
|
+
"external API",
|
|
18090
|
+
"webhook",
|
|
18091
|
+
"cryptography",
|
|
18092
|
+
"crypto",
|
|
18093
|
+
"session",
|
|
18094
|
+
"cookie",
|
|
18095
|
+
"personally identifiable",
|
|
18096
|
+
"PII",
|
|
18097
|
+
"compliance"
|
|
18098
|
+
]);
|
|
18099
|
+
EXCLUDED_PATH_PATTERNS = [
|
|
18100
|
+
/\.test\.(ts|tsx|js|jsx|mjs|cjs)$/,
|
|
18101
|
+
/\.spec\.(ts|tsx|js|jsx|mjs|cjs)$/,
|
|
18102
|
+
/__tests__\//,
|
|
18103
|
+
/(^|\/)dist\//,
|
|
18104
|
+
/(^|\/)build\//,
|
|
18105
|
+
/\.generated\.(ts|tsx|js|jsx)$/,
|
|
18106
|
+
/(^|\/)pnpm-lock\.yaml$/,
|
|
18107
|
+
/(^|\/)package-lock\.json$/,
|
|
18108
|
+
/(^|\/)yarn\.lock$/
|
|
18109
|
+
];
|
|
18110
|
+
ASYNC_UI_SIGNALS = Object.freeze([
|
|
18111
|
+
".tsx",
|
|
18112
|
+
"useEffect",
|
|
18113
|
+
"useState",
|
|
18114
|
+
"setTimeout",
|
|
18115
|
+
"setInterval",
|
|
18116
|
+
"addEventListener",
|
|
18117
|
+
"data-controller="
|
|
18118
|
+
]);
|
|
18119
|
+
WORD_BOUNDARY = /[A-Za-z0-9_]/;
|
|
17150
18120
|
DOWNGRADE_MAP = {
|
|
17151
18121
|
critical: "important",
|
|
17152
18122
|
important: "suggestion",
|
|
@@ -18160,28 +19130,28 @@ ${JSON.stringify(event)}`;
|
|
|
18160
19130
|
runMigrationPlan: () => runMigrationPlan
|
|
18161
19131
|
});
|
|
18162
19132
|
HASH_RE = /<!--\s*harness-history\s+hash:([0-9a-f]{8})\s*-->/i;
|
|
18163
|
-
InteractionTypeSchema =
|
|
18164
|
-
QuestionSchema =
|
|
18165
|
-
text:
|
|
18166
|
-
options:
|
|
18167
|
-
default:
|
|
19133
|
+
InteractionTypeSchema = z12.enum(["question", "confirmation", "transition"]);
|
|
19134
|
+
QuestionSchema = z12.object({
|
|
19135
|
+
text: z12.string(),
|
|
19136
|
+
options: z12.array(z12.string()).optional(),
|
|
19137
|
+
default: z12.string().optional()
|
|
18168
19138
|
});
|
|
18169
|
-
ConfirmationSchema =
|
|
18170
|
-
text:
|
|
18171
|
-
context:
|
|
19139
|
+
ConfirmationSchema = z12.object({
|
|
19140
|
+
text: z12.string(),
|
|
19141
|
+
context: z12.string()
|
|
18172
19142
|
});
|
|
18173
|
-
TransitionSchema =
|
|
18174
|
-
completedPhase:
|
|
18175
|
-
suggestedNext:
|
|
18176
|
-
reason:
|
|
18177
|
-
artifacts:
|
|
18178
|
-
requiresConfirmation:
|
|
18179
|
-
summary:
|
|
19143
|
+
TransitionSchema = z12.object({
|
|
19144
|
+
completedPhase: z12.string(),
|
|
19145
|
+
suggestedNext: z12.string(),
|
|
19146
|
+
reason: z12.string(),
|
|
19147
|
+
artifacts: z12.array(z12.string()),
|
|
19148
|
+
requiresConfirmation: z12.boolean(),
|
|
19149
|
+
summary: z12.string()
|
|
18180
19150
|
});
|
|
18181
|
-
EmitInteractionInputSchema =
|
|
18182
|
-
path:
|
|
19151
|
+
EmitInteractionInputSchema = z12.object({
|
|
19152
|
+
path: z12.string(),
|
|
18183
19153
|
type: InteractionTypeSchema,
|
|
18184
|
-
stream:
|
|
19154
|
+
stream: z12.string().optional(),
|
|
18185
19155
|
question: QuestionSchema.optional(),
|
|
18186
19156
|
confirmation: ConfirmationSchema.optional(),
|
|
18187
19157
|
transition: TransitionSchema.optional()
|
|
@@ -18192,10 +19162,10 @@ ${JSON.stringify(event)}`;
|
|
|
18192
19162
|
}
|
|
18193
19163
|
rootDir;
|
|
18194
19164
|
async scan() {
|
|
18195
|
-
let projectName =
|
|
19165
|
+
let projectName = path37.basename(this.rootDir);
|
|
18196
19166
|
try {
|
|
18197
|
-
const pkgPath =
|
|
18198
|
-
const pkgRaw = await
|
|
19167
|
+
const pkgPath = path37.join(this.rootDir, "package.json");
|
|
19168
|
+
const pkgRaw = await fs37.readFile(pkgPath, "utf-8");
|
|
18199
19169
|
const pkg = JSON.parse(pkgRaw);
|
|
18200
19170
|
if (pkg.name) projectName = pkg.name;
|
|
18201
19171
|
} catch {
|
|
@@ -18308,8 +19278,8 @@ console.log('Blueprint player initialized.');
|
|
|
18308
19278
|
styles: STYLES,
|
|
18309
19279
|
scripts: SCRIPTS
|
|
18310
19280
|
});
|
|
18311
|
-
await
|
|
18312
|
-
await
|
|
19281
|
+
await fs38.mkdir(options.outputDir, { recursive: true });
|
|
19282
|
+
await fs38.writeFile(path38.join(options.outputDir, "index.html"), html);
|
|
18313
19283
|
}
|
|
18314
19284
|
};
|
|
18315
19285
|
TOKENS_PER_MILLION = 1e6;
|
|
@@ -18847,6 +19817,11 @@ console.log('Blueprint player initialized.');
|
|
|
18847
19817
|
[/\b(runtime|crash|exception|panic)\b/i, "bug-track/runtime-errors"],
|
|
18848
19818
|
[/\(orchestrator\)|\bintegrat|\blease\b|\brace\b|\bconcurren/i, "bug-track/integration-issues"]
|
|
18849
19819
|
];
|
|
19820
|
+
SECTION_ORDER = [
|
|
19821
|
+
...REQUIRED_STRATEGY_SECTIONS,
|
|
19822
|
+
...OPTIONAL_STRATEGY_SECTIONS
|
|
19823
|
+
];
|
|
19824
|
+
FILENAME = "STRATEGY.md";
|
|
18850
19825
|
ProposalNotFoundError = class extends Error {
|
|
18851
19826
|
constructor(id) {
|
|
18852
19827
|
super(`proposal not found: ${id}`);
|
|
@@ -18891,6 +19866,11 @@ export {
|
|
|
18891
19866
|
PulseSourcesSchema,
|
|
18892
19867
|
PulseConfigSchema,
|
|
18893
19868
|
validatePulseConfig,
|
|
19869
|
+
parseStrategyDoc,
|
|
19870
|
+
asStrategyDoc,
|
|
19871
|
+
StrategyFrontmatterSchema,
|
|
19872
|
+
StrategyDocSchema,
|
|
19873
|
+
validateStrategy,
|
|
18894
19874
|
BUG_TRACK_CATEGORIES,
|
|
18895
19875
|
KNOWLEDGE_TRACK_CATEGORIES,
|
|
18896
19876
|
ALL_SOLUTION_CATEGORIES,
|
|
@@ -19147,8 +20127,23 @@ export {
|
|
|
19147
20127
|
runSecurityAgent,
|
|
19148
20128
|
ARCHITECTURE_DESCRIPTOR,
|
|
19149
20129
|
runArchitectureAgent,
|
|
20130
|
+
ADVERSARIAL_DESCRIPTOR,
|
|
20131
|
+
runAdversarialAgent,
|
|
20132
|
+
TYPESCRIPT_STRICT_DESCRIPTOR,
|
|
20133
|
+
runTypescriptStrictAgent,
|
|
20134
|
+
FRONTEND_RACES_DESCRIPTOR,
|
|
20135
|
+
runFrontendRacesAgent,
|
|
19150
20136
|
AGENT_DESCRIPTORS,
|
|
20137
|
+
CONDITIONAL_SUBAGENT_DESCRIPTORS,
|
|
19151
20138
|
fanOutReview,
|
|
20139
|
+
fanOutConditionalSubagents,
|
|
20140
|
+
SUBAGENT_ORDER,
|
|
20141
|
+
RISK_KEYWORDS,
|
|
20142
|
+
countChangedLines,
|
|
20143
|
+
detectRiskKeywords,
|
|
20144
|
+
computeDepth,
|
|
20145
|
+
computeActivations,
|
|
20146
|
+
calibrateDepth,
|
|
19152
20147
|
validateFindings,
|
|
19153
20148
|
deduplicateFindings,
|
|
19154
20149
|
checkEligibility,
|
|
@@ -19272,6 +20267,8 @@ export {
|
|
|
19272
20267
|
crossReferenceUndocumentedFixes,
|
|
19273
20268
|
suggestCategory,
|
|
19274
20269
|
assembleCandidateReport,
|
|
20270
|
+
serializeStrategyDoc,
|
|
20271
|
+
writeStrategyDoc,
|
|
19275
20272
|
proposalsDir,
|
|
19276
20273
|
ProposalNotFoundError,
|
|
19277
20274
|
ProposalConflictError,
|