@harness-engineering/core 0.28.0 → 0.28.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +8 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.js +172 -103
- package/dist/index.mjs +172 -103
- package/package.json +3 -3
package/dist/index.d.mts
CHANGED
|
@@ -5874,7 +5874,14 @@ declare class SecurityScanner {
|
|
|
5874
5874
|
private buildSuppressionFinding;
|
|
5875
5875
|
/** Check one line against a rule's patterns; return a finding or null. */
|
|
5876
5876
|
private matchRuleLine;
|
|
5877
|
-
/**
|
|
5877
|
+
/**
|
|
5878
|
+
* Scan a single line against a resolved rule; push any findings into the array.
|
|
5879
|
+
*
|
|
5880
|
+
* Suppression check is two-pass: same line first (trailing-comment style), then the previous
|
|
5881
|
+
* line (the dominant convention: `// harness-ignore` on the line above the flagged code).
|
|
5882
|
+
* Without the prior-line check, every multi-line statement annotated using the over-the-top
|
|
5883
|
+
* convention would slip through and re-trigger the finding.
|
|
5884
|
+
*/
|
|
5878
5885
|
private scanLineForRule;
|
|
5879
5886
|
/**
|
|
5880
5887
|
* Core scanning loop shared by scanContent and scanContentForFile.
|
package/dist/index.d.ts
CHANGED
|
@@ -5874,7 +5874,14 @@ declare class SecurityScanner {
|
|
|
5874
5874
|
private buildSuppressionFinding;
|
|
5875
5875
|
/** Check one line against a rule's patterns; return a finding or null. */
|
|
5876
5876
|
private matchRuleLine;
|
|
5877
|
-
/**
|
|
5877
|
+
/**
|
|
5878
|
+
* Scan a single line against a resolved rule; push any findings into the array.
|
|
5879
|
+
*
|
|
5880
|
+
* Suppression check is two-pass: same line first (trailing-comment style), then the previous
|
|
5881
|
+
* line (the dominant convention: `// harness-ignore` on the line above the flagged code).
|
|
5882
|
+
* Without the prior-line check, every multi-line statement annotated using the over-the-top
|
|
5883
|
+
* convention would slip through and re-trigger the finding.
|
|
5884
|
+
*/
|
|
5878
5885
|
private scanLineForRule;
|
|
5879
5886
|
/**
|
|
5880
5887
|
* Core scanning loop shared by scanContent and scanContentForFile.
|
package/dist/index.js
CHANGED
|
@@ -6102,25 +6102,8 @@ function validateBranchName(branchName, config) {
|
|
|
6102
6102
|
}
|
|
6103
6103
|
if (config.enforceKebabCase) {
|
|
6104
6104
|
for (const part of slug.split("/")) {
|
|
6105
|
-
const
|
|
6106
|
-
if (
|
|
6107
|
-
const rest = ticketMatch[2];
|
|
6108
|
-
if (rest && !KEBAB_CASE.test(rest)) {
|
|
6109
|
-
return {
|
|
6110
|
-
valid: false,
|
|
6111
|
-
branchName,
|
|
6112
|
-
message: `Branch slug part "${part}" does not follow kebab-case after the ticket ID.`,
|
|
6113
|
-
suggestion: `Ensure the description after "${ticketMatch[1]}" uses kebab-case (lowercase, single hyphens, no leading/trailing hyphen).`
|
|
6114
|
-
};
|
|
6115
|
-
}
|
|
6116
|
-
} else if (!KEBAB_CASE.test(part)) {
|
|
6117
|
-
return {
|
|
6118
|
-
valid: false,
|
|
6119
|
-
branchName,
|
|
6120
|
-
message: `Branch slug part "${part}" must be in kebab-case (lowercase, single hyphens, no leading/trailing hyphen).`,
|
|
6121
|
-
suggestion: `Change "${part}" to match the convention.`
|
|
6122
|
-
};
|
|
6123
|
-
}
|
|
6105
|
+
const kebabFailure = validateKebabSlugPart(part, branchName);
|
|
6106
|
+
if (kebabFailure) return kebabFailure;
|
|
6124
6107
|
}
|
|
6125
6108
|
}
|
|
6126
6109
|
if (typeof config.maxLength === "number" && config.maxLength > 0 && slug.length > config.maxLength) {
|
|
@@ -6133,6 +6116,26 @@ function validateBranchName(branchName, config) {
|
|
|
6133
6116
|
}
|
|
6134
6117
|
return { valid: true, branchName };
|
|
6135
6118
|
}
|
|
6119
|
+
function validateKebabSlugPart(part, branchName) {
|
|
6120
|
+
const ticketMatch = part.match(TICKET_ID);
|
|
6121
|
+
if (ticketMatch) {
|
|
6122
|
+
const rest = ticketMatch[2];
|
|
6123
|
+
if (!rest || KEBAB_CASE.test(rest)) return null;
|
|
6124
|
+
return {
|
|
6125
|
+
valid: false,
|
|
6126
|
+
branchName,
|
|
6127
|
+
message: `Branch slug part "${part}" does not follow kebab-case after the ticket ID.`,
|
|
6128
|
+
suggestion: `Ensure the description after "${ticketMatch[1]}" uses kebab-case (lowercase, single hyphens, no leading/trailing hyphen).`
|
|
6129
|
+
};
|
|
6130
|
+
}
|
|
6131
|
+
if (KEBAB_CASE.test(part)) return null;
|
|
6132
|
+
return {
|
|
6133
|
+
valid: false,
|
|
6134
|
+
branchName,
|
|
6135
|
+
message: `Branch slug part "${part}" must be in kebab-case (lowercase, single hyphens, no leading/trailing hyphen).`,
|
|
6136
|
+
suggestion: `Change "${part}" to match the convention.`
|
|
6137
|
+
};
|
|
6138
|
+
}
|
|
6136
6139
|
|
|
6137
6140
|
// src/context/doc-coverage.ts
|
|
6138
6141
|
var import_minimatch2 = require("minimatch");
|
|
@@ -8125,15 +8128,21 @@ async function gatherDecayBlock(projectPath) {
|
|
|
8125
8128
|
const { TimelineManager: TimelineManager2 } = await Promise.resolve().then(() => (init_timeline_manager(), timeline_manager_exports));
|
|
8126
8129
|
const mgr = new TimelineManager2(projectPath);
|
|
8127
8130
|
const trends = mgr.trends();
|
|
8128
|
-
|
|
8129
|
-
|
|
8130
|
-
|
|
8131
|
+
return {
|
|
8132
|
+
recentBumps: arrayLen(trends.recentSnapshots),
|
|
8133
|
+
topAffected: collectTopAffectedLabels(trends.topAffected)
|
|
8134
|
+
};
|
|
8135
|
+
}
|
|
8136
|
+
var MAX_TOP_AFFECTED = 5;
|
|
8137
|
+
function collectTopAffectedLabels(affected) {
|
|
8138
|
+
if (!Array.isArray(affected)) return [];
|
|
8139
|
+
const out = [];
|
|
8131
8140
|
for (const node of affected) {
|
|
8132
8141
|
const label = node.id ?? node.name;
|
|
8133
|
-
if (typeof label === "string" && label.length > 0)
|
|
8134
|
-
if (
|
|
8142
|
+
if (typeof label === "string" && label.length > 0) out.push(label);
|
|
8143
|
+
if (out.length >= MAX_TOP_AFFECTED) break;
|
|
8135
8144
|
}
|
|
8136
|
-
return
|
|
8145
|
+
return out;
|
|
8137
8146
|
}
|
|
8138
8147
|
async function gatherAttentionBlock(projectPath) {
|
|
8139
8148
|
const sessionsDir = path4.join(projectPath, ".harness", "sessions");
|
|
@@ -14639,11 +14648,20 @@ var SecurityScanner = class {
|
|
|
14639
14648
|
}
|
|
14640
14649
|
return null;
|
|
14641
14650
|
}
|
|
14642
|
-
/**
|
|
14643
|
-
|
|
14644
|
-
|
|
14651
|
+
/**
|
|
14652
|
+
* Scan a single line against a resolved rule; push any findings into the array.
|
|
14653
|
+
*
|
|
14654
|
+
* Suppression check is two-pass: same line first (trailing-comment style), then the previous
|
|
14655
|
+
* line (the dominant convention: `// harness-ignore` on the line above the flagged code).
|
|
14656
|
+
* Without the prior-line check, every multi-line statement annotated using the over-the-top
|
|
14657
|
+
* convention would slip through and re-trigger the finding.
|
|
14658
|
+
*/
|
|
14659
|
+
scanLineForRule(rule, resolved, line, lineNumber, filePath, findings, priorLine) {
|
|
14660
|
+
const sameLineMatch = parseHarnessIgnore(line, rule.id);
|
|
14661
|
+
const priorLineMatch = !sameLineMatch && priorLine !== void 0 ? parseHarnessIgnore(priorLine, rule.id) : null;
|
|
14662
|
+
const suppressionMatch = sameLineMatch ?? priorLineMatch;
|
|
14645
14663
|
if (suppressionMatch) {
|
|
14646
|
-
if (!suppressionMatch.justification) {
|
|
14664
|
+
if (!suppressionMatch.justification && sameLineMatch !== null) {
|
|
14647
14665
|
findings.push(this.buildSuppressionFinding(rule, filePath, lineNumber, line));
|
|
14648
14666
|
}
|
|
14649
14667
|
return;
|
|
@@ -14667,7 +14685,15 @@ var SecurityScanner = class {
|
|
|
14667
14685
|
);
|
|
14668
14686
|
if (resolved === "off") continue;
|
|
14669
14687
|
for (let i = 0; i < lines.length; i++) {
|
|
14670
|
-
this.scanLineForRule(
|
|
14688
|
+
this.scanLineForRule(
|
|
14689
|
+
rule,
|
|
14690
|
+
resolved,
|
|
14691
|
+
lines[i] ?? "",
|
|
14692
|
+
startLine + i,
|
|
14693
|
+
filePath,
|
|
14694
|
+
findings,
|
|
14695
|
+
i > 0 ? lines[i - 1] : void 0
|
|
14696
|
+
);
|
|
14671
14697
|
}
|
|
14672
14698
|
}
|
|
14673
14699
|
return findings;
|
|
@@ -19782,13 +19808,20 @@ function arraysEqual2(a, b) {
|
|
|
19782
19808
|
// src/roadmap/tracker/adapters/github-issues.ts
|
|
19783
19809
|
function metaFromFeatureFields(src) {
|
|
19784
19810
|
const meta = {};
|
|
19785
|
-
|
|
19786
|
-
if (src.plans
|
|
19787
|
-
if (src.blockedBy
|
|
19788
|
-
|
|
19789
|
-
|
|
19811
|
+
assignNonNullish(meta, "spec", src.spec);
|
|
19812
|
+
if (hasItems(src.plans)) meta.plan = src.plans[0];
|
|
19813
|
+
if (hasItems(src.blockedBy)) meta.blocked_by = src.blockedBy;
|
|
19814
|
+
assignNonNullish(meta, "priority", src.priority);
|
|
19815
|
+
assignNonNullish(meta, "milestone", src.milestone);
|
|
19790
19816
|
return meta;
|
|
19791
19817
|
}
|
|
19818
|
+
function assignNonNullish(meta, key, value) {
|
|
19819
|
+
if (value === void 0 || value === null) return;
|
|
19820
|
+
meta[key] = value;
|
|
19821
|
+
}
|
|
19822
|
+
function hasItems(arr) {
|
|
19823
|
+
return Array.isArray(arr) && arr.length > 0;
|
|
19824
|
+
}
|
|
19792
19825
|
function buildCreateMeta(feature) {
|
|
19793
19826
|
return metaFromFeatureFields(feature);
|
|
19794
19827
|
}
|
|
@@ -20449,33 +20482,42 @@ function featureToNewInput(feature, milestone) {
|
|
|
20449
20482
|
return input;
|
|
20450
20483
|
}
|
|
20451
20484
|
function metaToPatch(feature, expected) {
|
|
20452
|
-
const patch = {};
|
|
20453
|
-
patch
|
|
20454
|
-
if (expected.
|
|
20455
|
-
|
|
20456
|
-
|
|
20457
|
-
|
|
20458
|
-
if (expected.milestone !== void 0) patch.milestone = expected.milestone ?? null;
|
|
20485
|
+
const patch = { summary: feature.summary };
|
|
20486
|
+
assignIfPresent(patch, "spec", expected.spec, null);
|
|
20487
|
+
if (expected.plan != null) patch.plans = [expected.plan];
|
|
20488
|
+
assignIfPresent(patch, "blockedBy", expected.blocked_by, []);
|
|
20489
|
+
assignIfPresent(patch, "priority", expected.priority, null);
|
|
20490
|
+
assignIfPresent(patch, "milestone", expected.milestone, null);
|
|
20459
20491
|
return patch;
|
|
20460
20492
|
}
|
|
20493
|
+
function assignIfPresent(patch, key, value, fallback2) {
|
|
20494
|
+
if (value === void 0) return;
|
|
20495
|
+
patch[key] = value ?? fallback2;
|
|
20496
|
+
}
|
|
20461
20497
|
function formatDiff(actual, expected) {
|
|
20462
|
-
const keys =
|
|
20463
|
-
const collect = (m) => {
|
|
20464
|
-
for (const k of Object.keys(m)) {
|
|
20465
|
-
const v = m[k];
|
|
20466
|
-
if (v !== void 0 && v !== null && !(Array.isArray(v) && v.length === 0)) keys.add(k);
|
|
20467
|
-
}
|
|
20468
|
-
};
|
|
20469
|
-
collect(actual);
|
|
20470
|
-
collect(expected);
|
|
20498
|
+
const keys = collectPresentKeys(actual, expected);
|
|
20471
20499
|
const changed = [];
|
|
20472
|
-
for (const key of
|
|
20500
|
+
for (const key of keys) {
|
|
20473
20501
|
const a = actual[key];
|
|
20474
20502
|
const e = expected[key];
|
|
20475
20503
|
if (JSON.stringify(a ?? null) !== JSON.stringify(e ?? null)) changed.push(key);
|
|
20476
20504
|
}
|
|
20477
20505
|
return changed.join(",");
|
|
20478
20506
|
}
|
|
20507
|
+
function hasMeaningfulValue(value) {
|
|
20508
|
+
if (value === void 0 || value === null) return false;
|
|
20509
|
+
if (Array.isArray(value) && value.length === 0) return false;
|
|
20510
|
+
return true;
|
|
20511
|
+
}
|
|
20512
|
+
function collectPresentKeys(...metas) {
|
|
20513
|
+
const keys = /* @__PURE__ */ new Set();
|
|
20514
|
+
for (const m of metas) {
|
|
20515
|
+
for (const [k, v] of Object.entries(m)) {
|
|
20516
|
+
if (hasMeaningfulValue(v)) keys.add(k);
|
|
20517
|
+
}
|
|
20518
|
+
}
|
|
20519
|
+
return [...keys].sort();
|
|
20520
|
+
}
|
|
20479
20521
|
function mapAction(action) {
|
|
20480
20522
|
switch (action) {
|
|
20481
20523
|
case "assigned":
|
|
@@ -22166,20 +22208,43 @@ var RETRY_BACKOFFS_MS = [1e3, 2e3, 4e3];
|
|
|
22166
22208
|
function toUnixNanoString(ns) {
|
|
22167
22209
|
return ns.toString(10);
|
|
22168
22210
|
}
|
|
22211
|
+
function encodeAttributeValue(value) {
|
|
22212
|
+
if (typeof value === "string") return { stringValue: value };
|
|
22213
|
+
if (typeof value === "boolean") return { boolValue: value };
|
|
22214
|
+
if (typeof value === "number") {
|
|
22215
|
+
return Number.isInteger(value) ? { intValue: String(value) } : { doubleValue: value };
|
|
22216
|
+
}
|
|
22217
|
+
return null;
|
|
22218
|
+
}
|
|
22219
|
+
var DEFAULT_FLUSH_INTERVAL_MS = 2e3;
|
|
22220
|
+
var DEFAULT_BATCH_SIZE = 64;
|
|
22221
|
+
var defaultFetch = (...args) => globalThis.fetch(...args);
|
|
22222
|
+
var defaultWarn = (...args) => console.warn(...args);
|
|
22223
|
+
function resolveOTLPOptions(opts) {
|
|
22224
|
+
const {
|
|
22225
|
+
endpoint,
|
|
22226
|
+
enabled = true,
|
|
22227
|
+
headers = {},
|
|
22228
|
+
flushIntervalMs = DEFAULT_FLUSH_INTERVAL_MS,
|
|
22229
|
+
batchSize = DEFAULT_BATCH_SIZE,
|
|
22230
|
+
fetchImpl = defaultFetch,
|
|
22231
|
+
warn = defaultWarn
|
|
22232
|
+
} = opts;
|
|
22233
|
+
return {
|
|
22234
|
+
endpoint,
|
|
22235
|
+
enabled,
|
|
22236
|
+
headers: { "Content-Type": "application/json", ...headers },
|
|
22237
|
+
flushIntervalMs,
|
|
22238
|
+
batchSize,
|
|
22239
|
+
fetchImpl,
|
|
22240
|
+
warn
|
|
22241
|
+
};
|
|
22242
|
+
}
|
|
22169
22243
|
function attributesToOTLP(attrs) {
|
|
22170
22244
|
const out = [];
|
|
22171
22245
|
for (const [key, value] of Object.entries(attrs)) {
|
|
22172
|
-
|
|
22173
|
-
|
|
22174
|
-
} else if (typeof value === "boolean") {
|
|
22175
|
-
out.push({ key, value: { boolValue: value } });
|
|
22176
|
-
} else if (typeof value === "number") {
|
|
22177
|
-
if (Number.isInteger(value)) {
|
|
22178
|
-
out.push({ key, value: { intValue: String(value) } });
|
|
22179
|
-
} else {
|
|
22180
|
-
out.push({ key, value: { doubleValue: value } });
|
|
22181
|
-
}
|
|
22182
|
-
}
|
|
22246
|
+
const encoded = encodeAttributeValue(value);
|
|
22247
|
+
if (encoded) out.push({ key, value: encoded });
|
|
22183
22248
|
}
|
|
22184
22249
|
return out;
|
|
22185
22250
|
}
|
|
@@ -22195,13 +22260,14 @@ var OTLPExporter = class {
|
|
|
22195
22260
|
timer = null;
|
|
22196
22261
|
inFlightFlushes = /* @__PURE__ */ new Set();
|
|
22197
22262
|
constructor(opts) {
|
|
22198
|
-
|
|
22199
|
-
this.
|
|
22200
|
-
this.
|
|
22201
|
-
this.
|
|
22202
|
-
this.
|
|
22203
|
-
this.
|
|
22204
|
-
this.
|
|
22263
|
+
const resolved = resolveOTLPOptions(opts);
|
|
22264
|
+
this.endpoint = resolved.endpoint;
|
|
22265
|
+
this.enabled = resolved.enabled;
|
|
22266
|
+
this.headers = resolved.headers;
|
|
22267
|
+
this.flushIntervalMs = resolved.flushIntervalMs;
|
|
22268
|
+
this.batchSize = resolved.batchSize;
|
|
22269
|
+
this.fetchImpl = resolved.fetchImpl;
|
|
22270
|
+
this.warn = resolved.warn;
|
|
22205
22271
|
}
|
|
22206
22272
|
/**
|
|
22207
22273
|
* O(1) buffer push. When `enabled === false` this is a no-op. If the
|
|
@@ -22284,36 +22350,32 @@ var OTLPExporter = class {
|
|
|
22284
22350
|
* tests; not part of the supported API surface.
|
|
22285
22351
|
*/
|
|
22286
22352
|
spansToOTLPJSON(spans) {
|
|
22287
|
-
|
|
22288
|
-
|
|
22289
|
-
|
|
22290
|
-
|
|
22291
|
-
|
|
22292
|
-
|
|
22293
|
-
|
|
22294
|
-
{
|
|
22295
|
-
scope: { name: "harness" },
|
|
22296
|
-
spans: spans.map((s) => {
|
|
22297
|
-
const span = {
|
|
22298
|
-
traceId: s.traceId,
|
|
22299
|
-
spanId: s.spanId,
|
|
22300
|
-
name: s.name,
|
|
22301
|
-
kind: s.kind,
|
|
22302
|
-
startTimeUnixNano: toUnixNanoString(s.startTimeNs),
|
|
22303
|
-
endTimeUnixNano: toUnixNanoString(s.endTimeNs),
|
|
22304
|
-
attributes: attributesToOTLP(s.attributes)
|
|
22305
|
-
};
|
|
22306
|
-
if (s.parentSpanId !== void 0) span["parentSpanId"] = s.parentSpanId;
|
|
22307
|
-
if (s.statusCode !== void 0) span["status"] = { code: s.statusCode };
|
|
22308
|
-
return span;
|
|
22309
|
-
})
|
|
22310
|
-
}
|
|
22311
|
-
]
|
|
22312
|
-
}
|
|
22313
|
-
]
|
|
22353
|
+
const scopeSpan = {
|
|
22354
|
+
scope: { name: "harness" },
|
|
22355
|
+
spans: spans.map(spanToOTLP)
|
|
22356
|
+
};
|
|
22357
|
+
const resourceSpan = {
|
|
22358
|
+
resource: { attributes: SERVICE_NAME_ATTR },
|
|
22359
|
+
scopeSpans: [scopeSpan]
|
|
22314
22360
|
};
|
|
22361
|
+
return { resourceSpans: [resourceSpan] };
|
|
22315
22362
|
}
|
|
22316
22363
|
};
|
|
22364
|
+
var SERVICE_NAME_ATTR = [{ key: "service.name", value: { stringValue: "harness" } }];
|
|
22365
|
+
function spanToOTLP(s) {
|
|
22366
|
+
const span = {
|
|
22367
|
+
traceId: s.traceId,
|
|
22368
|
+
spanId: s.spanId,
|
|
22369
|
+
name: s.name,
|
|
22370
|
+
kind: s.kind,
|
|
22371
|
+
startTimeUnixNano: toUnixNanoString(s.startTimeNs),
|
|
22372
|
+
endTimeUnixNano: toUnixNanoString(s.endTimeNs),
|
|
22373
|
+
attributes: attributesToOTLP(s.attributes)
|
|
22374
|
+
};
|
|
22375
|
+
if (s.parentSpanId !== void 0) span["parentSpanId"] = s.parentSpanId;
|
|
22376
|
+
if (s.statusCode !== void 0) span["status"] = { code: s.statusCode };
|
|
22377
|
+
return span;
|
|
22378
|
+
}
|
|
22317
22379
|
|
|
22318
22380
|
// src/telemetry/exporter/types.ts
|
|
22319
22381
|
var SpanKind = /* @__PURE__ */ ((SpanKind2) => {
|
|
@@ -22429,15 +22491,22 @@ var PII_TOKENS = [
|
|
|
22429
22491
|
var PII_FIELD_DENYLIST = new RegExp(`^(?:${PII_TOKENS.join("|")})$`, "i");
|
|
22430
22492
|
var PII_LINE_RE = new RegExp(`\\b(?:${PII_TOKENS.join("|")})\\b`, "i");
|
|
22431
22493
|
var ALLOWED_SET = new Set(ALLOWED_FIELD_KEYS);
|
|
22432
|
-
function
|
|
22433
|
-
|
|
22434
|
-
|
|
22435
|
-
|
|
22436
|
-
for (const k of Object.keys(
|
|
22494
|
+
function isObject(value) {
|
|
22495
|
+
return Boolean(value) && typeof value === "object";
|
|
22496
|
+
}
|
|
22497
|
+
function hasAllowedFieldKeys(fields) {
|
|
22498
|
+
for (const k of Object.keys(fields)) {
|
|
22437
22499
|
if (!ALLOWED_SET.has(k)) return false;
|
|
22438
22500
|
if (PII_FIELD_DENYLIST.test(k)) return false;
|
|
22439
22501
|
}
|
|
22440
|
-
|
|
22502
|
+
return true;
|
|
22503
|
+
}
|
|
22504
|
+
function isSanitizedResult(value) {
|
|
22505
|
+
if (!isObject(value)) return false;
|
|
22506
|
+
const { fields, distributions } = value;
|
|
22507
|
+
if (!isObject(fields)) return false;
|
|
22508
|
+
if (!hasAllowedFieldKeys(fields)) return false;
|
|
22509
|
+
if (!isObject(distributions)) return false;
|
|
22441
22510
|
return true;
|
|
22442
22511
|
}
|
|
22443
22512
|
function assertSanitized(value) {
|
package/dist/index.mjs
CHANGED
|
@@ -1779,25 +1779,8 @@ function validateBranchName(branchName, config) {
|
|
|
1779
1779
|
}
|
|
1780
1780
|
if (config.enforceKebabCase) {
|
|
1781
1781
|
for (const part of slug.split("/")) {
|
|
1782
|
-
const
|
|
1783
|
-
if (
|
|
1784
|
-
const rest = ticketMatch[2];
|
|
1785
|
-
if (rest && !KEBAB_CASE.test(rest)) {
|
|
1786
|
-
return {
|
|
1787
|
-
valid: false,
|
|
1788
|
-
branchName,
|
|
1789
|
-
message: `Branch slug part "${part}" does not follow kebab-case after the ticket ID.`,
|
|
1790
|
-
suggestion: `Ensure the description after "${ticketMatch[1]}" uses kebab-case (lowercase, single hyphens, no leading/trailing hyphen).`
|
|
1791
|
-
};
|
|
1792
|
-
}
|
|
1793
|
-
} else if (!KEBAB_CASE.test(part)) {
|
|
1794
|
-
return {
|
|
1795
|
-
valid: false,
|
|
1796
|
-
branchName,
|
|
1797
|
-
message: `Branch slug part "${part}" must be in kebab-case (lowercase, single hyphens, no leading/trailing hyphen).`,
|
|
1798
|
-
suggestion: `Change "${part}" to match the convention.`
|
|
1799
|
-
};
|
|
1800
|
-
}
|
|
1782
|
+
const kebabFailure = validateKebabSlugPart(part, branchName);
|
|
1783
|
+
if (kebabFailure) return kebabFailure;
|
|
1801
1784
|
}
|
|
1802
1785
|
}
|
|
1803
1786
|
if (typeof config.maxLength === "number" && config.maxLength > 0 && slug.length > config.maxLength) {
|
|
@@ -1810,6 +1793,26 @@ function validateBranchName(branchName, config) {
|
|
|
1810
1793
|
}
|
|
1811
1794
|
return { valid: true, branchName };
|
|
1812
1795
|
}
|
|
1796
|
+
function validateKebabSlugPart(part, branchName) {
|
|
1797
|
+
const ticketMatch = part.match(TICKET_ID);
|
|
1798
|
+
if (ticketMatch) {
|
|
1799
|
+
const rest = ticketMatch[2];
|
|
1800
|
+
if (!rest || KEBAB_CASE.test(rest)) return null;
|
|
1801
|
+
return {
|
|
1802
|
+
valid: false,
|
|
1803
|
+
branchName,
|
|
1804
|
+
message: `Branch slug part "${part}" does not follow kebab-case after the ticket ID.`,
|
|
1805
|
+
suggestion: `Ensure the description after "${ticketMatch[1]}" uses kebab-case (lowercase, single hyphens, no leading/trailing hyphen).`
|
|
1806
|
+
};
|
|
1807
|
+
}
|
|
1808
|
+
if (KEBAB_CASE.test(part)) return null;
|
|
1809
|
+
return {
|
|
1810
|
+
valid: false,
|
|
1811
|
+
branchName,
|
|
1812
|
+
message: `Branch slug part "${part}" must be in kebab-case (lowercase, single hyphens, no leading/trailing hyphen).`,
|
|
1813
|
+
suggestion: `Change "${part}" to match the convention.`
|
|
1814
|
+
};
|
|
1815
|
+
}
|
|
1813
1816
|
|
|
1814
1817
|
// src/context/doc-coverage.ts
|
|
1815
1818
|
import { minimatch as minimatch2 } from "minimatch";
|
|
@@ -3671,15 +3674,21 @@ async function gatherDecayBlock(projectPath) {
|
|
|
3671
3674
|
const { TimelineManager: TimelineManager2 } = await import("./timeline-manager-FPYKJRHR.mjs");
|
|
3672
3675
|
const mgr = new TimelineManager2(projectPath);
|
|
3673
3676
|
const trends = mgr.trends();
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
+
return {
|
|
3678
|
+
recentBumps: arrayLen(trends.recentSnapshots),
|
|
3679
|
+
topAffected: collectTopAffectedLabels(trends.topAffected)
|
|
3680
|
+
};
|
|
3681
|
+
}
|
|
3682
|
+
var MAX_TOP_AFFECTED = 5;
|
|
3683
|
+
function collectTopAffectedLabels(affected) {
|
|
3684
|
+
if (!Array.isArray(affected)) return [];
|
|
3685
|
+
const out = [];
|
|
3677
3686
|
for (const node of affected) {
|
|
3678
3687
|
const label = node.id ?? node.name;
|
|
3679
|
-
if (typeof label === "string" && label.length > 0)
|
|
3680
|
-
if (
|
|
3688
|
+
if (typeof label === "string" && label.length > 0) out.push(label);
|
|
3689
|
+
if (out.length >= MAX_TOP_AFFECTED) break;
|
|
3681
3690
|
}
|
|
3682
|
-
return
|
|
3691
|
+
return out;
|
|
3683
3692
|
}
|
|
3684
3693
|
async function gatherAttentionBlock(projectPath) {
|
|
3685
3694
|
const sessionsDir = path4.join(projectPath, ".harness", "sessions");
|
|
@@ -9090,11 +9099,20 @@ var SecurityScanner = class {
|
|
|
9090
9099
|
}
|
|
9091
9100
|
return null;
|
|
9092
9101
|
}
|
|
9093
|
-
/**
|
|
9094
|
-
|
|
9095
|
-
|
|
9102
|
+
/**
|
|
9103
|
+
* Scan a single line against a resolved rule; push any findings into the array.
|
|
9104
|
+
*
|
|
9105
|
+
* Suppression check is two-pass: same line first (trailing-comment style), then the previous
|
|
9106
|
+
* line (the dominant convention: `// harness-ignore` on the line above the flagged code).
|
|
9107
|
+
* Without the prior-line check, every multi-line statement annotated using the over-the-top
|
|
9108
|
+
* convention would slip through and re-trigger the finding.
|
|
9109
|
+
*/
|
|
9110
|
+
scanLineForRule(rule, resolved, line, lineNumber, filePath, findings, priorLine) {
|
|
9111
|
+
const sameLineMatch = parseHarnessIgnore(line, rule.id);
|
|
9112
|
+
const priorLineMatch = !sameLineMatch && priorLine !== void 0 ? parseHarnessIgnore(priorLine, rule.id) : null;
|
|
9113
|
+
const suppressionMatch = sameLineMatch ?? priorLineMatch;
|
|
9096
9114
|
if (suppressionMatch) {
|
|
9097
|
-
if (!suppressionMatch.justification) {
|
|
9115
|
+
if (!suppressionMatch.justification && sameLineMatch !== null) {
|
|
9098
9116
|
findings.push(this.buildSuppressionFinding(rule, filePath, lineNumber, line));
|
|
9099
9117
|
}
|
|
9100
9118
|
return;
|
|
@@ -9118,7 +9136,15 @@ var SecurityScanner = class {
|
|
|
9118
9136
|
);
|
|
9119
9137
|
if (resolved === "off") continue;
|
|
9120
9138
|
for (let i = 0; i < lines.length; i++) {
|
|
9121
|
-
this.scanLineForRule(
|
|
9139
|
+
this.scanLineForRule(
|
|
9140
|
+
rule,
|
|
9141
|
+
resolved,
|
|
9142
|
+
lines[i] ?? "",
|
|
9143
|
+
startLine + i,
|
|
9144
|
+
filePath,
|
|
9145
|
+
findings,
|
|
9146
|
+
i > 0 ? lines[i - 1] : void 0
|
|
9147
|
+
);
|
|
9122
9148
|
}
|
|
9123
9149
|
}
|
|
9124
9150
|
return findings;
|
|
@@ -14224,13 +14250,20 @@ function arraysEqual2(a, b) {
|
|
|
14224
14250
|
// src/roadmap/tracker/adapters/github-issues.ts
|
|
14225
14251
|
function metaFromFeatureFields(src) {
|
|
14226
14252
|
const meta = {};
|
|
14227
|
-
|
|
14228
|
-
if (src.plans
|
|
14229
|
-
if (src.blockedBy
|
|
14230
|
-
|
|
14231
|
-
|
|
14253
|
+
assignNonNullish(meta, "spec", src.spec);
|
|
14254
|
+
if (hasItems(src.plans)) meta.plan = src.plans[0];
|
|
14255
|
+
if (hasItems(src.blockedBy)) meta.blocked_by = src.blockedBy;
|
|
14256
|
+
assignNonNullish(meta, "priority", src.priority);
|
|
14257
|
+
assignNonNullish(meta, "milestone", src.milestone);
|
|
14232
14258
|
return meta;
|
|
14233
14259
|
}
|
|
14260
|
+
function assignNonNullish(meta, key, value) {
|
|
14261
|
+
if (value === void 0 || value === null) return;
|
|
14262
|
+
meta[key] = value;
|
|
14263
|
+
}
|
|
14264
|
+
function hasItems(arr) {
|
|
14265
|
+
return Array.isArray(arr) && arr.length > 0;
|
|
14266
|
+
}
|
|
14234
14267
|
function buildCreateMeta(feature) {
|
|
14235
14268
|
return metaFromFeatureFields(feature);
|
|
14236
14269
|
}
|
|
@@ -14891,33 +14924,42 @@ function featureToNewInput(feature, milestone) {
|
|
|
14891
14924
|
return input;
|
|
14892
14925
|
}
|
|
14893
14926
|
function metaToPatch(feature, expected) {
|
|
14894
|
-
const patch = {};
|
|
14895
|
-
patch
|
|
14896
|
-
if (expected.
|
|
14897
|
-
|
|
14898
|
-
|
|
14899
|
-
|
|
14900
|
-
if (expected.milestone !== void 0) patch.milestone = expected.milestone ?? null;
|
|
14927
|
+
const patch = { summary: feature.summary };
|
|
14928
|
+
assignIfPresent(patch, "spec", expected.spec, null);
|
|
14929
|
+
if (expected.plan != null) patch.plans = [expected.plan];
|
|
14930
|
+
assignIfPresent(patch, "blockedBy", expected.blocked_by, []);
|
|
14931
|
+
assignIfPresent(patch, "priority", expected.priority, null);
|
|
14932
|
+
assignIfPresent(patch, "milestone", expected.milestone, null);
|
|
14901
14933
|
return patch;
|
|
14902
14934
|
}
|
|
14935
|
+
function assignIfPresent(patch, key, value, fallback2) {
|
|
14936
|
+
if (value === void 0) return;
|
|
14937
|
+
patch[key] = value ?? fallback2;
|
|
14938
|
+
}
|
|
14903
14939
|
function formatDiff(actual, expected) {
|
|
14904
|
-
const keys =
|
|
14905
|
-
const collect = (m) => {
|
|
14906
|
-
for (const k of Object.keys(m)) {
|
|
14907
|
-
const v = m[k];
|
|
14908
|
-
if (v !== void 0 && v !== null && !(Array.isArray(v) && v.length === 0)) keys.add(k);
|
|
14909
|
-
}
|
|
14910
|
-
};
|
|
14911
|
-
collect(actual);
|
|
14912
|
-
collect(expected);
|
|
14940
|
+
const keys = collectPresentKeys(actual, expected);
|
|
14913
14941
|
const changed = [];
|
|
14914
|
-
for (const key of
|
|
14942
|
+
for (const key of keys) {
|
|
14915
14943
|
const a = actual[key];
|
|
14916
14944
|
const e = expected[key];
|
|
14917
14945
|
if (JSON.stringify(a ?? null) !== JSON.stringify(e ?? null)) changed.push(key);
|
|
14918
14946
|
}
|
|
14919
14947
|
return changed.join(",");
|
|
14920
14948
|
}
|
|
14949
|
+
function hasMeaningfulValue(value) {
|
|
14950
|
+
if (value === void 0 || value === null) return false;
|
|
14951
|
+
if (Array.isArray(value) && value.length === 0) return false;
|
|
14952
|
+
return true;
|
|
14953
|
+
}
|
|
14954
|
+
function collectPresentKeys(...metas) {
|
|
14955
|
+
const keys = /* @__PURE__ */ new Set();
|
|
14956
|
+
for (const m of metas) {
|
|
14957
|
+
for (const [k, v] of Object.entries(m)) {
|
|
14958
|
+
if (hasMeaningfulValue(v)) keys.add(k);
|
|
14959
|
+
}
|
|
14960
|
+
}
|
|
14961
|
+
return [...keys].sort();
|
|
14962
|
+
}
|
|
14921
14963
|
function mapAction(action) {
|
|
14922
14964
|
switch (action) {
|
|
14923
14965
|
case "assigned":
|
|
@@ -16595,20 +16637,43 @@ var RETRY_BACKOFFS_MS = [1e3, 2e3, 4e3];
|
|
|
16595
16637
|
function toUnixNanoString(ns) {
|
|
16596
16638
|
return ns.toString(10);
|
|
16597
16639
|
}
|
|
16640
|
+
function encodeAttributeValue(value) {
|
|
16641
|
+
if (typeof value === "string") return { stringValue: value };
|
|
16642
|
+
if (typeof value === "boolean") return { boolValue: value };
|
|
16643
|
+
if (typeof value === "number") {
|
|
16644
|
+
return Number.isInteger(value) ? { intValue: String(value) } : { doubleValue: value };
|
|
16645
|
+
}
|
|
16646
|
+
return null;
|
|
16647
|
+
}
|
|
16648
|
+
var DEFAULT_FLUSH_INTERVAL_MS = 2e3;
|
|
16649
|
+
var DEFAULT_BATCH_SIZE = 64;
|
|
16650
|
+
var defaultFetch = (...args) => globalThis.fetch(...args);
|
|
16651
|
+
var defaultWarn = (...args) => console.warn(...args);
|
|
16652
|
+
function resolveOTLPOptions(opts) {
|
|
16653
|
+
const {
|
|
16654
|
+
endpoint,
|
|
16655
|
+
enabled = true,
|
|
16656
|
+
headers = {},
|
|
16657
|
+
flushIntervalMs = DEFAULT_FLUSH_INTERVAL_MS,
|
|
16658
|
+
batchSize = DEFAULT_BATCH_SIZE,
|
|
16659
|
+
fetchImpl = defaultFetch,
|
|
16660
|
+
warn = defaultWarn
|
|
16661
|
+
} = opts;
|
|
16662
|
+
return {
|
|
16663
|
+
endpoint,
|
|
16664
|
+
enabled,
|
|
16665
|
+
headers: { "Content-Type": "application/json", ...headers },
|
|
16666
|
+
flushIntervalMs,
|
|
16667
|
+
batchSize,
|
|
16668
|
+
fetchImpl,
|
|
16669
|
+
warn
|
|
16670
|
+
};
|
|
16671
|
+
}
|
|
16598
16672
|
function attributesToOTLP(attrs) {
|
|
16599
16673
|
const out = [];
|
|
16600
16674
|
for (const [key, value] of Object.entries(attrs)) {
|
|
16601
|
-
|
|
16602
|
-
|
|
16603
|
-
} else if (typeof value === "boolean") {
|
|
16604
|
-
out.push({ key, value: { boolValue: value } });
|
|
16605
|
-
} else if (typeof value === "number") {
|
|
16606
|
-
if (Number.isInteger(value)) {
|
|
16607
|
-
out.push({ key, value: { intValue: String(value) } });
|
|
16608
|
-
} else {
|
|
16609
|
-
out.push({ key, value: { doubleValue: value } });
|
|
16610
|
-
}
|
|
16611
|
-
}
|
|
16675
|
+
const encoded = encodeAttributeValue(value);
|
|
16676
|
+
if (encoded) out.push({ key, value: encoded });
|
|
16612
16677
|
}
|
|
16613
16678
|
return out;
|
|
16614
16679
|
}
|
|
@@ -16624,13 +16689,14 @@ var OTLPExporter = class {
|
|
|
16624
16689
|
timer = null;
|
|
16625
16690
|
inFlightFlushes = /* @__PURE__ */ new Set();
|
|
16626
16691
|
constructor(opts) {
|
|
16627
|
-
|
|
16628
|
-
this.
|
|
16629
|
-
this.
|
|
16630
|
-
this.
|
|
16631
|
-
this.
|
|
16632
|
-
this.
|
|
16633
|
-
this.
|
|
16692
|
+
const resolved = resolveOTLPOptions(opts);
|
|
16693
|
+
this.endpoint = resolved.endpoint;
|
|
16694
|
+
this.enabled = resolved.enabled;
|
|
16695
|
+
this.headers = resolved.headers;
|
|
16696
|
+
this.flushIntervalMs = resolved.flushIntervalMs;
|
|
16697
|
+
this.batchSize = resolved.batchSize;
|
|
16698
|
+
this.fetchImpl = resolved.fetchImpl;
|
|
16699
|
+
this.warn = resolved.warn;
|
|
16634
16700
|
}
|
|
16635
16701
|
/**
|
|
16636
16702
|
* O(1) buffer push. When `enabled === false` this is a no-op. If the
|
|
@@ -16713,36 +16779,32 @@ var OTLPExporter = class {
|
|
|
16713
16779
|
* tests; not part of the supported API surface.
|
|
16714
16780
|
*/
|
|
16715
16781
|
spansToOTLPJSON(spans) {
|
|
16716
|
-
|
|
16717
|
-
|
|
16718
|
-
|
|
16719
|
-
|
|
16720
|
-
|
|
16721
|
-
|
|
16722
|
-
|
|
16723
|
-
{
|
|
16724
|
-
scope: { name: "harness" },
|
|
16725
|
-
spans: spans.map((s) => {
|
|
16726
|
-
const span = {
|
|
16727
|
-
traceId: s.traceId,
|
|
16728
|
-
spanId: s.spanId,
|
|
16729
|
-
name: s.name,
|
|
16730
|
-
kind: s.kind,
|
|
16731
|
-
startTimeUnixNano: toUnixNanoString(s.startTimeNs),
|
|
16732
|
-
endTimeUnixNano: toUnixNanoString(s.endTimeNs),
|
|
16733
|
-
attributes: attributesToOTLP(s.attributes)
|
|
16734
|
-
};
|
|
16735
|
-
if (s.parentSpanId !== void 0) span["parentSpanId"] = s.parentSpanId;
|
|
16736
|
-
if (s.statusCode !== void 0) span["status"] = { code: s.statusCode };
|
|
16737
|
-
return span;
|
|
16738
|
-
})
|
|
16739
|
-
}
|
|
16740
|
-
]
|
|
16741
|
-
}
|
|
16742
|
-
]
|
|
16782
|
+
const scopeSpan = {
|
|
16783
|
+
scope: { name: "harness" },
|
|
16784
|
+
spans: spans.map(spanToOTLP)
|
|
16785
|
+
};
|
|
16786
|
+
const resourceSpan = {
|
|
16787
|
+
resource: { attributes: SERVICE_NAME_ATTR },
|
|
16788
|
+
scopeSpans: [scopeSpan]
|
|
16743
16789
|
};
|
|
16790
|
+
return { resourceSpans: [resourceSpan] };
|
|
16744
16791
|
}
|
|
16745
16792
|
};
|
|
16793
|
+
var SERVICE_NAME_ATTR = [{ key: "service.name", value: { stringValue: "harness" } }];
|
|
16794
|
+
function spanToOTLP(s) {
|
|
16795
|
+
const span = {
|
|
16796
|
+
traceId: s.traceId,
|
|
16797
|
+
spanId: s.spanId,
|
|
16798
|
+
name: s.name,
|
|
16799
|
+
kind: s.kind,
|
|
16800
|
+
startTimeUnixNano: toUnixNanoString(s.startTimeNs),
|
|
16801
|
+
endTimeUnixNano: toUnixNanoString(s.endTimeNs),
|
|
16802
|
+
attributes: attributesToOTLP(s.attributes)
|
|
16803
|
+
};
|
|
16804
|
+
if (s.parentSpanId !== void 0) span["parentSpanId"] = s.parentSpanId;
|
|
16805
|
+
if (s.statusCode !== void 0) span["status"] = { code: s.statusCode };
|
|
16806
|
+
return span;
|
|
16807
|
+
}
|
|
16746
16808
|
|
|
16747
16809
|
// src/telemetry/exporter/types.ts
|
|
16748
16810
|
var SpanKind = /* @__PURE__ */ ((SpanKind2) => {
|
|
@@ -16858,15 +16920,22 @@ var PII_TOKENS = [
|
|
|
16858
16920
|
var PII_FIELD_DENYLIST = new RegExp(`^(?:${PII_TOKENS.join("|")})$`, "i");
|
|
16859
16921
|
var PII_LINE_RE = new RegExp(`\\b(?:${PII_TOKENS.join("|")})\\b`, "i");
|
|
16860
16922
|
var ALLOWED_SET = new Set(ALLOWED_FIELD_KEYS);
|
|
16861
|
-
function
|
|
16862
|
-
|
|
16863
|
-
|
|
16864
|
-
|
|
16865
|
-
for (const k of Object.keys(
|
|
16923
|
+
function isObject(value) {
|
|
16924
|
+
return Boolean(value) && typeof value === "object";
|
|
16925
|
+
}
|
|
16926
|
+
function hasAllowedFieldKeys(fields) {
|
|
16927
|
+
for (const k of Object.keys(fields)) {
|
|
16866
16928
|
if (!ALLOWED_SET.has(k)) return false;
|
|
16867
16929
|
if (PII_FIELD_DENYLIST.test(k)) return false;
|
|
16868
16930
|
}
|
|
16869
|
-
|
|
16931
|
+
return true;
|
|
16932
|
+
}
|
|
16933
|
+
function isSanitizedResult(value) {
|
|
16934
|
+
if (!isObject(value)) return false;
|
|
16935
|
+
const { fields, distributions } = value;
|
|
16936
|
+
if (!isObject(fields)) return false;
|
|
16937
|
+
if (!hasAllowedFieldKeys(fields)) return false;
|
|
16938
|
+
if (!isObject(distributions)) return false;
|
|
16870
16939
|
return true;
|
|
16871
16940
|
}
|
|
16872
16941
|
function assertSanitized(value) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@harness-engineering/core",
|
|
3
|
-
"version": "0.28.
|
|
3
|
+
"version": "0.28.1",
|
|
4
4
|
"description": "Core library for Harness Engineering toolkit",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -51,8 +51,8 @@
|
|
|
51
51
|
"web-tree-sitter": "^0.24.7",
|
|
52
52
|
"yaml": "^2.8.3",
|
|
53
53
|
"zod": "^3.25.76",
|
|
54
|
-
"@harness-engineering/graph": "0.
|
|
55
|
-
"@harness-engineering/types": "0.
|
|
54
|
+
"@harness-engineering/graph": "0.10.0",
|
|
55
|
+
"@harness-engineering/types": "0.15.0"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
58
|
"@types/ejs": "^3.1.5",
|