@harness-engineering/core 0.9.2 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.js +140 -85
  2. package/dist/index.mjs +140 -85
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -5239,6 +5239,15 @@ var FAILURES_FILE = "failures.md";
5239
5239
  var HANDOFF_FILE = "handoff.json";
5240
5240
  var GATE_CONFIG_FILE = "gate.json";
5241
5241
  var INDEX_FILE2 = "index.json";
5242
+ var MAX_CACHE_ENTRIES = 8;
5243
+ var learningsCacheMap = /* @__PURE__ */ new Map();
5244
+ var failuresCacheMap = /* @__PURE__ */ new Map();
5245
+ function evictIfNeeded(map) {
5246
+ if (map.size > MAX_CACHE_ENTRIES) {
5247
+ const oldest = map.keys().next().value;
5248
+ if (oldest !== void 0) map.delete(oldest);
5249
+ }
5250
+ }
5242
5251
  async function getStateDir(projectPath, stream) {
5243
5252
  const streamsIndexPath = path3.join(projectPath, HARNESS_DIR2, "streams", INDEX_FILE2);
5244
5253
  const hasStreams = fs4.existsSync(streamsIndexPath);
@@ -5318,6 +5327,7 @@ ${entry}`);
5318
5327
  } else {
5319
5328
  fs4.appendFileSync(learningsPath, entry);
5320
5329
  }
5330
+ learningsCacheMap.delete(learningsPath);
5321
5331
  return (0, import_types.Ok)(void 0);
5322
5332
  } catch (error) {
5323
5333
  return (0, import_types.Err)(
@@ -5336,25 +5346,35 @@ async function loadRelevantLearnings(projectPath, skillName, stream) {
5336
5346
  if (!fs4.existsSync(learningsPath)) {
5337
5347
  return (0, import_types.Ok)([]);
5338
5348
  }
5339
- const content = fs4.readFileSync(learningsPath, "utf-8");
5340
- const lines = content.split("\n");
5341
- const entries = [];
5342
- let currentBlock = [];
5343
- for (const line of lines) {
5344
- if (line.startsWith("# ")) continue;
5345
- const isDatedBullet = /^- \*\*\d{4}-\d{2}-\d{2}/.test(line);
5346
- const isHeading = /^## \d{4}-\d{2}-\d{2}/.test(line);
5347
- if (isDatedBullet || isHeading) {
5348
- if (currentBlock.length > 0) {
5349
- entries.push(currentBlock.join("\n"));
5349
+ const stats = fs4.statSync(learningsPath);
5350
+ const cacheKey = learningsPath;
5351
+ const cached = learningsCacheMap.get(cacheKey);
5352
+ let entries;
5353
+ if (cached && cached.mtimeMs === stats.mtimeMs) {
5354
+ entries = cached.entries;
5355
+ } else {
5356
+ const content = fs4.readFileSync(learningsPath, "utf-8");
5357
+ const lines = content.split("\n");
5358
+ entries = [];
5359
+ let currentBlock = [];
5360
+ for (const line of lines) {
5361
+ if (line.startsWith("# ")) continue;
5362
+ const isDatedBullet = /^- \*\*\d{4}-\d{2}-\d{2}/.test(line);
5363
+ const isHeading = /^## \d{4}-\d{2}-\d{2}/.test(line);
5364
+ if (isDatedBullet || isHeading) {
5365
+ if (currentBlock.length > 0) {
5366
+ entries.push(currentBlock.join("\n"));
5367
+ }
5368
+ currentBlock = [line];
5369
+ } else if (line.trim() !== "" && currentBlock.length > 0) {
5370
+ currentBlock.push(line);
5350
5371
  }
5351
- currentBlock = [line];
5352
- } else if (line.trim() !== "" && currentBlock.length > 0) {
5353
- currentBlock.push(line);
5354
5372
  }
5355
- }
5356
- if (currentBlock.length > 0) {
5357
- entries.push(currentBlock.join("\n"));
5373
+ if (currentBlock.length > 0) {
5374
+ entries.push(currentBlock.join("\n"));
5375
+ }
5376
+ learningsCacheMap.set(cacheKey, { mtimeMs: stats.mtimeMs, entries });
5377
+ evictIfNeeded(learningsCacheMap);
5358
5378
  }
5359
5379
  if (!skillName) {
5360
5380
  return (0, import_types.Ok)(entries);
@@ -5387,6 +5407,7 @@ ${entry}`);
5387
5407
  } else {
5388
5408
  fs4.appendFileSync(failuresPath, entry);
5389
5409
  }
5410
+ failuresCacheMap.delete(failuresPath);
5390
5411
  return (0, import_types.Ok)(void 0);
5391
5412
  } catch (error) {
5392
5413
  return (0, import_types.Err)(
@@ -5405,6 +5426,12 @@ async function loadFailures(projectPath, stream) {
5405
5426
  if (!fs4.existsSync(failuresPath)) {
5406
5427
  return (0, import_types.Ok)([]);
5407
5428
  }
5429
+ const stats = fs4.statSync(failuresPath);
5430
+ const cacheKey = failuresPath;
5431
+ const cached = failuresCacheMap.get(cacheKey);
5432
+ if (cached && cached.mtimeMs === stats.mtimeMs) {
5433
+ return (0, import_types.Ok)(cached.entries);
5434
+ }
5408
5435
  const content = fs4.readFileSync(failuresPath, "utf-8");
5409
5436
  const entries = [];
5410
5437
  for (const line of content.split("\n")) {
@@ -5418,6 +5445,8 @@ async function loadFailures(projectPath, stream) {
5418
5445
  });
5419
5446
  }
5420
5447
  }
5448
+ failuresCacheMap.set(cacheKey, { mtimeMs: stats.mtimeMs, entries });
5449
+ evictIfNeeded(failuresCacheMap);
5421
5450
  return (0, import_types.Ok)(entries);
5422
5451
  } catch (error) {
5423
5452
  return (0, import_types.Err)(
@@ -5446,6 +5475,7 @@ async function archiveFailures(projectPath, stream) {
5446
5475
  counter++;
5447
5476
  }
5448
5477
  fs4.renameSync(failuresPath, path3.join(archiveDir, archiveName));
5478
+ failuresCacheMap.delete(failuresPath);
5449
5479
  return (0, import_types.Ok)(void 0);
5450
5480
  } catch (error) {
5451
5481
  return (0, import_types.Err)(
@@ -6516,14 +6546,22 @@ async function runCIChecks(input) {
6516
6546
  const { projectRoot, config, skip = [], failOn = "error" } = input;
6517
6547
  try {
6518
6548
  const checks = [];
6519
- for (const name of ALL_CHECKS) {
6520
- if (skip.includes(name)) {
6521
- checks.push({ name, status: "skip", issues: [], durationMs: 0 });
6522
- } else {
6523
- const result = await runSingleCheck(name, projectRoot, config);
6524
- checks.push(result);
6525
- }
6549
+ const skippedSet = new Set(skip);
6550
+ if (skippedSet.has("validate")) {
6551
+ checks.push({ name: "validate", status: "skip", issues: [], durationMs: 0 });
6552
+ } else {
6553
+ checks.push(await runSingleCheck("validate", projectRoot, config));
6526
6554
  }
6555
+ const remainingChecks = ALL_CHECKS.slice(1);
6556
+ const phase2Results = await Promise.all(
6557
+ remainingChecks.map(async (name) => {
6558
+ if (skippedSet.has(name)) {
6559
+ return { name, status: "skip", issues: [], durationMs: 0 };
6560
+ }
6561
+ return runSingleCheck(name, projectRoot, config);
6562
+ })
6563
+ );
6564
+ checks.push(...phase2Results);
6527
6565
  const summary = buildSummary(checks);
6528
6566
  const exitCode = determineExitCode(summary, failOn);
6529
6567
  const report = {
@@ -6648,76 +6686,93 @@ async function runMechanicalChecks(options) {
6648
6686
  });
6649
6687
  }
6650
6688
  }
6689
+ const parallelChecks = [];
6651
6690
  if (!skip.includes("check-docs")) {
6652
- try {
6653
- const docsDir = path6.join(projectRoot, config.docsDir ?? "docs");
6654
- const result = await checkDocCoverage("project", { docsDir });
6655
- if (!result.ok) {
6656
- statuses["check-docs"] = "warn";
6657
- findings.push({
6658
- tool: "check-docs",
6659
- file: docsDir,
6660
- message: result.error.message,
6661
- severity: "warning"
6662
- });
6663
- } else if (result.value.gaps && result.value.gaps.length > 0) {
6664
- statuses["check-docs"] = "warn";
6665
- for (const gap of result.value.gaps) {
6666
- findings.push({
6691
+ parallelChecks.push(
6692
+ (async () => {
6693
+ const localFindings = [];
6694
+ try {
6695
+ const docsDir = path6.join(projectRoot, config.docsDir ?? "docs");
6696
+ const result = await checkDocCoverage("project", { docsDir });
6697
+ if (!result.ok) {
6698
+ statuses["check-docs"] = "warn";
6699
+ localFindings.push({
6700
+ tool: "check-docs",
6701
+ file: docsDir,
6702
+ message: result.error.message,
6703
+ severity: "warning"
6704
+ });
6705
+ } else if (result.value.gaps && result.value.gaps.length > 0) {
6706
+ statuses["check-docs"] = "warn";
6707
+ for (const gap of result.value.gaps) {
6708
+ localFindings.push({
6709
+ tool: "check-docs",
6710
+ file: gap.file,
6711
+ message: `Undocumented: ${gap.file} (suggested: ${gap.suggestedSection})`,
6712
+ severity: "warning"
6713
+ });
6714
+ }
6715
+ } else {
6716
+ statuses["check-docs"] = "pass";
6717
+ }
6718
+ } catch (err) {
6719
+ statuses["check-docs"] = "warn";
6720
+ localFindings.push({
6667
6721
  tool: "check-docs",
6668
- file: gap.file,
6669
- message: `Undocumented: ${gap.file} (suggested: ${gap.suggestedSection})`,
6722
+ file: path6.join(projectRoot, "docs"),
6723
+ message: err instanceof Error ? err.message : String(err),
6670
6724
  severity: "warning"
6671
6725
  });
6672
6726
  }
6673
- } else {
6674
- statuses["check-docs"] = "pass";
6675
- }
6676
- } catch (err) {
6677
- statuses["check-docs"] = "warn";
6678
- findings.push({
6679
- tool: "check-docs",
6680
- file: path6.join(projectRoot, "docs"),
6681
- message: err instanceof Error ? err.message : String(err),
6682
- severity: "warning"
6683
- });
6684
- }
6727
+ return localFindings;
6728
+ })()
6729
+ );
6685
6730
  }
6686
6731
  if (!skip.includes("security-scan")) {
6687
- try {
6688
- const securityConfig = parseSecurityConfig(config.security);
6689
- if (!securityConfig.enabled) {
6690
- statuses["security-scan"] = "skip";
6691
- } else {
6692
- const scanner = new SecurityScanner(securityConfig);
6693
- scanner.configureForProject(projectRoot);
6694
- const filesToScan = changedFiles ?? [];
6695
- const scanResult = await scanner.scanFiles(filesToScan);
6696
- if (scanResult.findings.length > 0) {
6697
- statuses["security-scan"] = "warn";
6698
- for (const f of scanResult.findings) {
6699
- findings.push({
6700
- tool: "security-scan",
6701
- file: f.file,
6702
- line: f.line,
6703
- ruleId: f.ruleId,
6704
- message: f.message,
6705
- severity: f.severity === "info" ? "warning" : f.severity
6706
- });
6732
+ parallelChecks.push(
6733
+ (async () => {
6734
+ const localFindings = [];
6735
+ try {
6736
+ const securityConfig = parseSecurityConfig(config.security);
6737
+ if (!securityConfig.enabled) {
6738
+ statuses["security-scan"] = "skip";
6739
+ } else {
6740
+ const scanner = new SecurityScanner(securityConfig);
6741
+ scanner.configureForProject(projectRoot);
6742
+ const filesToScan = changedFiles ?? [];
6743
+ const scanResult = await scanner.scanFiles(filesToScan);
6744
+ if (scanResult.findings.length > 0) {
6745
+ statuses["security-scan"] = "warn";
6746
+ for (const f of scanResult.findings) {
6747
+ localFindings.push({
6748
+ tool: "security-scan",
6749
+ file: f.file,
6750
+ line: f.line,
6751
+ ruleId: f.ruleId,
6752
+ message: f.message,
6753
+ severity: f.severity === "info" ? "warning" : f.severity
6754
+ });
6755
+ }
6756
+ } else {
6757
+ statuses["security-scan"] = "pass";
6758
+ }
6707
6759
  }
6708
- } else {
6709
- statuses["security-scan"] = "pass";
6760
+ } catch (err) {
6761
+ statuses["security-scan"] = "warn";
6762
+ localFindings.push({
6763
+ tool: "security-scan",
6764
+ file: projectRoot,
6765
+ message: err instanceof Error ? err.message : String(err),
6766
+ severity: "warning"
6767
+ });
6710
6768
  }
6711
- }
6712
- } catch (err) {
6713
- statuses["security-scan"] = "warn";
6714
- findings.push({
6715
- tool: "security-scan",
6716
- file: projectRoot,
6717
- message: err instanceof Error ? err.message : String(err),
6718
- severity: "warning"
6719
- });
6720
- }
6769
+ return localFindings;
6770
+ })()
6771
+ );
6772
+ }
6773
+ const parallelResults = await Promise.all(parallelChecks);
6774
+ for (const result of parallelResults) {
6775
+ findings.push(...result);
6721
6776
  }
6722
6777
  const hasErrors = findings.some((f) => f.severity === "error");
6723
6778
  const stopPipeline = statuses.validate === "fail" || statuses["check-deps"] === "fail";
package/dist/index.mjs CHANGED
@@ -5044,6 +5044,15 @@ var FAILURES_FILE = "failures.md";
5044
5044
  var HANDOFF_FILE = "handoff.json";
5045
5045
  var GATE_CONFIG_FILE = "gate.json";
5046
5046
  var INDEX_FILE2 = "index.json";
5047
+ var MAX_CACHE_ENTRIES = 8;
5048
+ var learningsCacheMap = /* @__PURE__ */ new Map();
5049
+ var failuresCacheMap = /* @__PURE__ */ new Map();
5050
+ function evictIfNeeded(map) {
5051
+ if (map.size > MAX_CACHE_ENTRIES) {
5052
+ const oldest = map.keys().next().value;
5053
+ if (oldest !== void 0) map.delete(oldest);
5054
+ }
5055
+ }
5047
5056
  async function getStateDir(projectPath, stream) {
5048
5057
  const streamsIndexPath = path3.join(projectPath, HARNESS_DIR2, "streams", INDEX_FILE2);
5049
5058
  const hasStreams = fs4.existsSync(streamsIndexPath);
@@ -5123,6 +5132,7 @@ ${entry}`);
5123
5132
  } else {
5124
5133
  fs4.appendFileSync(learningsPath, entry);
5125
5134
  }
5135
+ learningsCacheMap.delete(learningsPath);
5126
5136
  return Ok(void 0);
5127
5137
  } catch (error) {
5128
5138
  return Err(
@@ -5141,25 +5151,35 @@ async function loadRelevantLearnings(projectPath, skillName, stream) {
5141
5151
  if (!fs4.existsSync(learningsPath)) {
5142
5152
  return Ok([]);
5143
5153
  }
5144
- const content = fs4.readFileSync(learningsPath, "utf-8");
5145
- const lines = content.split("\n");
5146
- const entries = [];
5147
- let currentBlock = [];
5148
- for (const line of lines) {
5149
- if (line.startsWith("# ")) continue;
5150
- const isDatedBullet = /^- \*\*\d{4}-\d{2}-\d{2}/.test(line);
5151
- const isHeading = /^## \d{4}-\d{2}-\d{2}/.test(line);
5152
- if (isDatedBullet || isHeading) {
5153
- if (currentBlock.length > 0) {
5154
- entries.push(currentBlock.join("\n"));
5154
+ const stats = fs4.statSync(learningsPath);
5155
+ const cacheKey = learningsPath;
5156
+ const cached = learningsCacheMap.get(cacheKey);
5157
+ let entries;
5158
+ if (cached && cached.mtimeMs === stats.mtimeMs) {
5159
+ entries = cached.entries;
5160
+ } else {
5161
+ const content = fs4.readFileSync(learningsPath, "utf-8");
5162
+ const lines = content.split("\n");
5163
+ entries = [];
5164
+ let currentBlock = [];
5165
+ for (const line of lines) {
5166
+ if (line.startsWith("# ")) continue;
5167
+ const isDatedBullet = /^- \*\*\d{4}-\d{2}-\d{2}/.test(line);
5168
+ const isHeading = /^## \d{4}-\d{2}-\d{2}/.test(line);
5169
+ if (isDatedBullet || isHeading) {
5170
+ if (currentBlock.length > 0) {
5171
+ entries.push(currentBlock.join("\n"));
5172
+ }
5173
+ currentBlock = [line];
5174
+ } else if (line.trim() !== "" && currentBlock.length > 0) {
5175
+ currentBlock.push(line);
5155
5176
  }
5156
- currentBlock = [line];
5157
- } else if (line.trim() !== "" && currentBlock.length > 0) {
5158
- currentBlock.push(line);
5159
5177
  }
5160
- }
5161
- if (currentBlock.length > 0) {
5162
- entries.push(currentBlock.join("\n"));
5178
+ if (currentBlock.length > 0) {
5179
+ entries.push(currentBlock.join("\n"));
5180
+ }
5181
+ learningsCacheMap.set(cacheKey, { mtimeMs: stats.mtimeMs, entries });
5182
+ evictIfNeeded(learningsCacheMap);
5163
5183
  }
5164
5184
  if (!skillName) {
5165
5185
  return Ok(entries);
@@ -5192,6 +5212,7 @@ ${entry}`);
5192
5212
  } else {
5193
5213
  fs4.appendFileSync(failuresPath, entry);
5194
5214
  }
5215
+ failuresCacheMap.delete(failuresPath);
5195
5216
  return Ok(void 0);
5196
5217
  } catch (error) {
5197
5218
  return Err(
@@ -5210,6 +5231,12 @@ async function loadFailures(projectPath, stream) {
5210
5231
  if (!fs4.existsSync(failuresPath)) {
5211
5232
  return Ok([]);
5212
5233
  }
5234
+ const stats = fs4.statSync(failuresPath);
5235
+ const cacheKey = failuresPath;
5236
+ const cached = failuresCacheMap.get(cacheKey);
5237
+ if (cached && cached.mtimeMs === stats.mtimeMs) {
5238
+ return Ok(cached.entries);
5239
+ }
5213
5240
  const content = fs4.readFileSync(failuresPath, "utf-8");
5214
5241
  const entries = [];
5215
5242
  for (const line of content.split("\n")) {
@@ -5223,6 +5250,8 @@ async function loadFailures(projectPath, stream) {
5223
5250
  });
5224
5251
  }
5225
5252
  }
5253
+ failuresCacheMap.set(cacheKey, { mtimeMs: stats.mtimeMs, entries });
5254
+ evictIfNeeded(failuresCacheMap);
5226
5255
  return Ok(entries);
5227
5256
  } catch (error) {
5228
5257
  return Err(
@@ -5251,6 +5280,7 @@ async function archiveFailures(projectPath, stream) {
5251
5280
  counter++;
5252
5281
  }
5253
5282
  fs4.renameSync(failuresPath, path3.join(archiveDir, archiveName));
5283
+ failuresCacheMap.delete(failuresPath);
5254
5284
  return Ok(void 0);
5255
5285
  } catch (error) {
5256
5286
  return Err(
@@ -6321,14 +6351,22 @@ async function runCIChecks(input) {
6321
6351
  const { projectRoot, config, skip = [], failOn = "error" } = input;
6322
6352
  try {
6323
6353
  const checks = [];
6324
- for (const name of ALL_CHECKS) {
6325
- if (skip.includes(name)) {
6326
- checks.push({ name, status: "skip", issues: [], durationMs: 0 });
6327
- } else {
6328
- const result = await runSingleCheck(name, projectRoot, config);
6329
- checks.push(result);
6330
- }
6354
+ const skippedSet = new Set(skip);
6355
+ if (skippedSet.has("validate")) {
6356
+ checks.push({ name: "validate", status: "skip", issues: [], durationMs: 0 });
6357
+ } else {
6358
+ checks.push(await runSingleCheck("validate", projectRoot, config));
6331
6359
  }
6360
+ const remainingChecks = ALL_CHECKS.slice(1);
6361
+ const phase2Results = await Promise.all(
6362
+ remainingChecks.map(async (name) => {
6363
+ if (skippedSet.has(name)) {
6364
+ return { name, status: "skip", issues: [], durationMs: 0 };
6365
+ }
6366
+ return runSingleCheck(name, projectRoot, config);
6367
+ })
6368
+ );
6369
+ checks.push(...phase2Results);
6332
6370
  const summary = buildSummary(checks);
6333
6371
  const exitCode = determineExitCode(summary, failOn);
6334
6372
  const report = {
@@ -6453,76 +6491,93 @@ async function runMechanicalChecks(options) {
6453
6491
  });
6454
6492
  }
6455
6493
  }
6494
+ const parallelChecks = [];
6456
6495
  if (!skip.includes("check-docs")) {
6457
- try {
6458
- const docsDir = path6.join(projectRoot, config.docsDir ?? "docs");
6459
- const result = await checkDocCoverage("project", { docsDir });
6460
- if (!result.ok) {
6461
- statuses["check-docs"] = "warn";
6462
- findings.push({
6463
- tool: "check-docs",
6464
- file: docsDir,
6465
- message: result.error.message,
6466
- severity: "warning"
6467
- });
6468
- } else if (result.value.gaps && result.value.gaps.length > 0) {
6469
- statuses["check-docs"] = "warn";
6470
- for (const gap of result.value.gaps) {
6471
- findings.push({
6496
+ parallelChecks.push(
6497
+ (async () => {
6498
+ const localFindings = [];
6499
+ try {
6500
+ const docsDir = path6.join(projectRoot, config.docsDir ?? "docs");
6501
+ const result = await checkDocCoverage("project", { docsDir });
6502
+ if (!result.ok) {
6503
+ statuses["check-docs"] = "warn";
6504
+ localFindings.push({
6505
+ tool: "check-docs",
6506
+ file: docsDir,
6507
+ message: result.error.message,
6508
+ severity: "warning"
6509
+ });
6510
+ } else if (result.value.gaps && result.value.gaps.length > 0) {
6511
+ statuses["check-docs"] = "warn";
6512
+ for (const gap of result.value.gaps) {
6513
+ localFindings.push({
6514
+ tool: "check-docs",
6515
+ file: gap.file,
6516
+ message: `Undocumented: ${gap.file} (suggested: ${gap.suggestedSection})`,
6517
+ severity: "warning"
6518
+ });
6519
+ }
6520
+ } else {
6521
+ statuses["check-docs"] = "pass";
6522
+ }
6523
+ } catch (err) {
6524
+ statuses["check-docs"] = "warn";
6525
+ localFindings.push({
6472
6526
  tool: "check-docs",
6473
- file: gap.file,
6474
- message: `Undocumented: ${gap.file} (suggested: ${gap.suggestedSection})`,
6527
+ file: path6.join(projectRoot, "docs"),
6528
+ message: err instanceof Error ? err.message : String(err),
6475
6529
  severity: "warning"
6476
6530
  });
6477
6531
  }
6478
- } else {
6479
- statuses["check-docs"] = "pass";
6480
- }
6481
- } catch (err) {
6482
- statuses["check-docs"] = "warn";
6483
- findings.push({
6484
- tool: "check-docs",
6485
- file: path6.join(projectRoot, "docs"),
6486
- message: err instanceof Error ? err.message : String(err),
6487
- severity: "warning"
6488
- });
6489
- }
6532
+ return localFindings;
6533
+ })()
6534
+ );
6490
6535
  }
6491
6536
  if (!skip.includes("security-scan")) {
6492
- try {
6493
- const securityConfig = parseSecurityConfig(config.security);
6494
- if (!securityConfig.enabled) {
6495
- statuses["security-scan"] = "skip";
6496
- } else {
6497
- const scanner = new SecurityScanner(securityConfig);
6498
- scanner.configureForProject(projectRoot);
6499
- const filesToScan = changedFiles ?? [];
6500
- const scanResult = await scanner.scanFiles(filesToScan);
6501
- if (scanResult.findings.length > 0) {
6502
- statuses["security-scan"] = "warn";
6503
- for (const f of scanResult.findings) {
6504
- findings.push({
6505
- tool: "security-scan",
6506
- file: f.file,
6507
- line: f.line,
6508
- ruleId: f.ruleId,
6509
- message: f.message,
6510
- severity: f.severity === "info" ? "warning" : f.severity
6511
- });
6537
+ parallelChecks.push(
6538
+ (async () => {
6539
+ const localFindings = [];
6540
+ try {
6541
+ const securityConfig = parseSecurityConfig(config.security);
6542
+ if (!securityConfig.enabled) {
6543
+ statuses["security-scan"] = "skip";
6544
+ } else {
6545
+ const scanner = new SecurityScanner(securityConfig);
6546
+ scanner.configureForProject(projectRoot);
6547
+ const filesToScan = changedFiles ?? [];
6548
+ const scanResult = await scanner.scanFiles(filesToScan);
6549
+ if (scanResult.findings.length > 0) {
6550
+ statuses["security-scan"] = "warn";
6551
+ for (const f of scanResult.findings) {
6552
+ localFindings.push({
6553
+ tool: "security-scan",
6554
+ file: f.file,
6555
+ line: f.line,
6556
+ ruleId: f.ruleId,
6557
+ message: f.message,
6558
+ severity: f.severity === "info" ? "warning" : f.severity
6559
+ });
6560
+ }
6561
+ } else {
6562
+ statuses["security-scan"] = "pass";
6563
+ }
6512
6564
  }
6513
- } else {
6514
- statuses["security-scan"] = "pass";
6565
+ } catch (err) {
6566
+ statuses["security-scan"] = "warn";
6567
+ localFindings.push({
6568
+ tool: "security-scan",
6569
+ file: projectRoot,
6570
+ message: err instanceof Error ? err.message : String(err),
6571
+ severity: "warning"
6572
+ });
6515
6573
  }
6516
- }
6517
- } catch (err) {
6518
- statuses["security-scan"] = "warn";
6519
- findings.push({
6520
- tool: "security-scan",
6521
- file: projectRoot,
6522
- message: err instanceof Error ? err.message : String(err),
6523
- severity: "warning"
6524
- });
6525
- }
6574
+ return localFindings;
6575
+ })()
6576
+ );
6577
+ }
6578
+ const parallelResults = await Promise.all(parallelChecks);
6579
+ for (const result of parallelResults) {
6580
+ findings.push(...result);
6526
6581
  }
6527
6582
  const hasErrors = findings.some((f) => f.severity === "error");
6528
6583
  const stopPipeline = statuses.validate === "fail" || statuses["check-deps"] === "fail";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@harness-engineering/core",
3
- "version": "0.9.2",
3
+ "version": "0.10.1",
4
4
  "description": "Core library for Harness Engineering toolkit",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",