@harness-engineering/core 0.24.0 → 0.26.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/index.mjs CHANGED
@@ -46,6 +46,7 @@ import {
46
46
  fileExists,
47
47
  findFiles,
48
48
  formatOutline,
49
+ getDefaultRegistry,
49
50
  getOutline,
50
51
  getParser,
51
52
  parseFile,
@@ -56,11 +57,108 @@ import {
56
57
  runAll,
57
58
  validateDependencies,
58
59
  violationId
59
- } from "./chunk-NC4RPKD4.mjs";
60
+ } from "./chunk-4UI65RLE.mjs";
60
61
 
61
62
  // src/index.ts
62
63
  export * from "@harness-engineering/types";
63
64
 
65
+ // src/shared/port.ts
66
+ var WHATWG_BAD_PORTS = Object.freeze([
67
+ 1,
68
+ 7,
69
+ 9,
70
+ 11,
71
+ 13,
72
+ 15,
73
+ 17,
74
+ 19,
75
+ 20,
76
+ 21,
77
+ 22,
78
+ 23,
79
+ 25,
80
+ 37,
81
+ 42,
82
+ 43,
83
+ 53,
84
+ 69,
85
+ 77,
86
+ 79,
87
+ 87,
88
+ 95,
89
+ 101,
90
+ 102,
91
+ 103,
92
+ 104,
93
+ 109,
94
+ 110,
95
+ 111,
96
+ 113,
97
+ 115,
98
+ 117,
99
+ 119,
100
+ 123,
101
+ 135,
102
+ 137,
103
+ 139,
104
+ 143,
105
+ 161,
106
+ 179,
107
+ 389,
108
+ 427,
109
+ 465,
110
+ 512,
111
+ 513,
112
+ 514,
113
+ 515,
114
+ 526,
115
+ 530,
116
+ 531,
117
+ 532,
118
+ 540,
119
+ 548,
120
+ 554,
121
+ 556,
122
+ 563,
123
+ 587,
124
+ 601,
125
+ 636,
126
+ 989,
127
+ 990,
128
+ 993,
129
+ 995,
130
+ 1719,
131
+ 1720,
132
+ 1723,
133
+ 2049,
134
+ 3659,
135
+ 4045,
136
+ 4190,
137
+ 5060,
138
+ 5061,
139
+ 6e3,
140
+ 6566,
141
+ 6665,
142
+ 6666,
143
+ 6667,
144
+ 6668,
145
+ 6669,
146
+ 6679,
147
+ 6697,
148
+ 10080
149
+ ]);
150
+ var BAD_PORT_SET = new Set(WHATWG_BAD_PORTS);
151
+ function isBadPort(port) {
152
+ return BAD_PORT_SET.has(port);
153
+ }
154
+ function assertPortUsable(port, label = "server") {
155
+ if (isBadPort(port)) {
156
+ throw new Error(
157
+ `Refusing to bind ${label} to port ${port}: this port is on the WHATWG fetch bad-ports list, so browsers and Node's fetch() will reject every connection with "bad port". Choose a different port. See https://fetch.spec.whatwg.org/#port-blocking for the full list.`
158
+ );
159
+ }
160
+ }
161
+
64
162
  // src/validation/file-structure.ts
65
163
  async function validateFileStructure(projectPath, conventions) {
66
164
  const missing = [];
@@ -436,7 +534,7 @@ function isAgnixDisabled() {
436
534
  return v === "1" || v === "true";
437
535
  }
438
536
  function runAgnix(cwd, strict, binPath, timeoutMs = DEFAULT_AGNIX_TIMEOUT_MS, spawnFn = spawn) {
439
- return new Promise((resolve9) => {
537
+ return new Promise((resolve10) => {
440
538
  const args = ["--format", "json"];
441
539
  if (strict) args.push("--strict");
442
540
  args.push(cwd);
@@ -450,7 +548,7 @@ function runAgnix(cwd, strict, binPath, timeoutMs = DEFAULT_AGNIX_TIMEOUT_MS, sp
450
548
  if (settled) return;
451
549
  settled = true;
452
550
  clearTimeout(timer);
453
- resolve9(outcome);
551
+ resolve10(outcome);
454
552
  };
455
553
  const timer = setTimeout(() => {
456
554
  child.kill("SIGKILL");
@@ -1615,8 +1713,82 @@ function validateRoadmapMode(config, projectRoot) {
1615
1713
  return Ok(void 0);
1616
1714
  }
1617
1715
 
1618
- // src/context/doc-coverage.ts
1716
+ // src/validation/branch.ts
1619
1717
  import { minimatch } from "minimatch";
1718
+ var KEBAB_CASE = /^[a-z0-9]+(-[a-z0-9]+)*$/;
1719
+ var TICKET_ID = /^([A-Z0-9]+-[0-9]+)-(.*)$/;
1720
+ function validateBranchName(branchName, config) {
1721
+ for (const pattern of config.ignore) {
1722
+ if (minimatch(branchName, pattern)) {
1723
+ return { valid: true, branchName };
1724
+ }
1725
+ }
1726
+ if (config.customRegex) {
1727
+ const regex = new RegExp(config.customRegex);
1728
+ if (!regex.test(branchName)) {
1729
+ return {
1730
+ valid: false,
1731
+ branchName,
1732
+ message: `Branch name "${branchName}" does not match the custom regex: ${config.customRegex}`
1733
+ };
1734
+ }
1735
+ return { valid: true, branchName };
1736
+ }
1737
+ const parts = branchName.split("/");
1738
+ if (parts.length < 2) {
1739
+ return {
1740
+ valid: false,
1741
+ branchName,
1742
+ message: `Branch name "${branchName}" must have a prefix followed by a slash (e.g., "feat/my-feature").`,
1743
+ suggestion: `Try renaming to "feat/${branchName}" or "fix/${branchName}".`
1744
+ };
1745
+ }
1746
+ const prefix = parts[0];
1747
+ const slug = parts.slice(1).join("/");
1748
+ if (!config.prefixes.includes(prefix)) {
1749
+ return {
1750
+ valid: false,
1751
+ branchName,
1752
+ message: `Prefix "${prefix}" is not allowed.`,
1753
+ suggestion: `Allowed prefixes: ${config.prefixes.join(", ")}.`
1754
+ };
1755
+ }
1756
+ if (config.enforceKebabCase) {
1757
+ for (const part of slug.split("/")) {
1758
+ const ticketMatch = part.match(TICKET_ID);
1759
+ if (ticketMatch) {
1760
+ const rest = ticketMatch[2];
1761
+ if (rest && !KEBAB_CASE.test(rest)) {
1762
+ return {
1763
+ valid: false,
1764
+ branchName,
1765
+ message: `Branch slug part "${part}" does not follow kebab-case after the ticket ID.`,
1766
+ suggestion: `Ensure the description after "${ticketMatch[1]}" uses kebab-case (lowercase, single hyphens, no leading/trailing hyphen).`
1767
+ };
1768
+ }
1769
+ } else if (!KEBAB_CASE.test(part)) {
1770
+ return {
1771
+ valid: false,
1772
+ branchName,
1773
+ message: `Branch slug part "${part}" must be in kebab-case (lowercase, single hyphens, no leading/trailing hyphen).`,
1774
+ suggestion: `Change "${part}" to match the convention.`
1775
+ };
1776
+ }
1777
+ }
1778
+ }
1779
+ if (typeof config.maxLength === "number" && config.maxLength > 0 && slug.length > config.maxLength) {
1780
+ return {
1781
+ valid: false,
1782
+ branchName,
1783
+ message: `Branch slug is ${slug.length} characters; max allowed is ${config.maxLength}.`,
1784
+ suggestion: `Shorten the description after "${prefix}/" to ${config.maxLength} characters or fewer.`
1785
+ };
1786
+ }
1787
+ return { valid: true, branchName };
1788
+ }
1789
+
1790
+ // src/context/doc-coverage.ts
1791
+ import { minimatch as minimatch2 } from "minimatch";
1620
1792
  import { basename as basename2 } from "path";
1621
1793
  function determineImportance(filePath) {
1622
1794
  const name = basename2(filePath).toLowerCase();
@@ -1659,7 +1831,7 @@ async function checkDocCoverage(domain, options = {}) {
1659
1831
  const filteredSourceFiles = sourceFiles.filter((file) => {
1660
1832
  const relativePath = relativePosix(sourceDir, file);
1661
1833
  return !excludePatterns.some((pattern) => {
1662
- return minimatch(relativePath, pattern, { dot: true }) || minimatch(file, pattern, { dot: true });
1834
+ return minimatch2(relativePath, pattern, { dot: true }) || minimatch2(file, pattern, { dot: true });
1663
1835
  });
1664
1836
  });
1665
1837
  const docFiles = await findFiles("**/*.md", docsDir);
@@ -2912,8 +3084,12 @@ function parseProtectedRegions(files) {
2912
3084
  }
2913
3085
 
2914
3086
  // src/entropy/snapshot.ts
3087
+ import { skipDirGlobs } from "@harness-engineering/graph";
3088
+ import { resolve as resolve5 } from "path";
3089
+ import { minimatch as minimatch3 } from "minimatch";
3090
+
3091
+ // src/entropy/entry-points.ts
2915
3092
  import { join as join15, resolve as resolve4 } from "path";
2916
- import { minimatch as minimatch2 } from "minimatch";
2917
3093
  function collectFieldEntries(rootDir, field) {
2918
3094
  if (typeof field === "string") return [resolve4(rootDir, field)];
2919
3095
  if (typeof field === "object" && field !== null) {
@@ -2927,47 +3103,263 @@ function extractPackageEntries(rootDir, pkg) {
2927
3103
  if (entries.length === 0 && typeof pkg["main"] === "string") {
2928
3104
  entries.push(resolve4(rootDir, pkg["main"]));
2929
3105
  }
2930
- if (pkg["bin"]) {
2931
- entries.push(...collectFieldEntries(rootDir, pkg["bin"]));
2932
- }
3106
+ if (pkg["bin"]) entries.push(...collectFieldEntries(rootDir, pkg["bin"]));
2933
3107
  return entries;
2934
3108
  }
2935
- async function resolveEntryPoints(rootDir, explicitEntries) {
2936
- if (explicitEntries && explicitEntries.length > 0) {
2937
- return Ok(explicitEntries.map((e) => resolve4(rootDir, e)));
3109
+ var TS_HINTS = ['Add "exports" or "main" to package.json', "Create src/index.ts"];
3110
+ var TS_CONVENTIONS = ["src/index.ts", "src/main.ts", "src/index.tsx", "index.ts", "main.ts"];
3111
+ async function readPackageJsonEntries(rootDir, pkgPath) {
3112
+ const content = await readFileContent(pkgPath);
3113
+ if (!content.ok) return [];
3114
+ try {
3115
+ const pkg = JSON.parse(content.value);
3116
+ return extractPackageEntries(rootDir, pkg);
3117
+ } catch {
3118
+ return [];
2938
3119
  }
3120
+ }
3121
+ async function resolveTypeScript(rootDir) {
2939
3122
  const pkgPath = join15(rootDir, "package.json");
2940
- if (await fileExists(pkgPath)) {
2941
- const pkgContent = await readFileContent(pkgPath);
2942
- if (pkgContent.ok) {
2943
- try {
2944
- const pkg = JSON.parse(pkgContent.value);
2945
- const entries = extractPackageEntries(rootDir, pkg);
2946
- if (entries.length > 0) return Ok(entries);
2947
- } catch {
2948
- }
3123
+ const detected = await fileExists(pkgPath);
3124
+ if (detected) {
3125
+ const entries = await readPackageJsonEntries(rootDir, pkgPath);
3126
+ if (entries.length > 0) {
3127
+ return { language: "typescript", detected: true, entries, hints: TS_HINTS };
2949
3128
  }
2950
3129
  }
2951
- const conventions = ["src/index.ts", "src/main.ts", "index.ts", "main.ts"];
2952
- for (const conv of conventions) {
2953
- const convPath = join15(rootDir, conv);
2954
- if (await fileExists(convPath)) {
2955
- return Ok([convPath]);
3130
+ for (const conv of TS_CONVENTIONS) {
3131
+ const p = join15(rootDir, conv);
3132
+ if (await fileExists(p)) {
3133
+ return { language: "typescript", detected: true, entries: [p], hints: TS_HINTS };
2956
3134
  }
2957
3135
  }
3136
+ return { language: "typescript", detected, entries: [], hints: TS_HINTS };
3137
+ }
3138
+ var PYTHON_HINTS = [
3139
+ "Add an entry to [project.scripts] in pyproject.toml",
3140
+ "Create main.py or <package>/__main__.py"
3141
+ ];
3142
+ var PYTHON_CONVENTIONS = [
3143
+ "__main__.py",
3144
+ "main.py",
3145
+ "app.py",
3146
+ "src/__main__.py",
3147
+ "src/main.py",
3148
+ "src/app.py"
3149
+ ];
3150
+ async function detectPython(rootDir) {
3151
+ return await fileExists(join15(rootDir, "pyproject.toml")) || await fileExists(join15(rootDir, "setup.py")) || await fileExists(join15(rootDir, "requirements.txt"));
3152
+ }
3153
+ async function readPyProject(rootDir) {
3154
+ const pyproject = join15(rootDir, "pyproject.toml");
3155
+ if (!await fileExists(pyproject)) return { scriptTargets: [] };
3156
+ const content = await readFileContent(pyproject);
3157
+ if (!content.ok) return { scriptTargets: [] };
3158
+ return parsePyProject(content.value);
3159
+ }
3160
+ async function resolveScriptTargetEntry(rootDir, target) {
3161
+ const mod = target.split(":")[0];
3162
+ if (!mod) return void 0;
3163
+ const relPath2 = mod.replaceAll(".", "/") + ".py";
3164
+ for (const candidate of [join15(rootDir, relPath2), join15(rootDir, "src", relPath2)]) {
3165
+ if (await fileExists(candidate)) return candidate;
3166
+ }
3167
+ return void 0;
3168
+ }
3169
+ async function resolvePythonFromScripts(rootDir, targets) {
3170
+ const entries = [];
3171
+ for (const target of targets) {
3172
+ const entry = await resolveScriptTargetEntry(rootDir, target);
3173
+ if (entry) entries.push(entry);
3174
+ }
3175
+ return entries;
3176
+ }
3177
+ async function resolvePythonFromProjectName(rootDir, projectName) {
3178
+ if (!projectName) return [];
3179
+ const normalized = projectName.replaceAll("-", "_");
3180
+ const candidates = [
3181
+ join15(rootDir, normalized, "__init__.py"),
3182
+ join15(rootDir, normalized, "__main__.py"),
3183
+ join15(rootDir, "src", normalized, "__init__.py"),
3184
+ join15(rootDir, "src", normalized, "__main__.py")
3185
+ ];
3186
+ const entries = [];
3187
+ for (const c of candidates) {
3188
+ if (await fileExists(c)) entries.push(c);
3189
+ }
3190
+ return entries;
3191
+ }
3192
+ async function resolvePythonConventions(rootDir) {
3193
+ const entries = [];
3194
+ for (const conv of PYTHON_CONVENTIONS) {
3195
+ const p = join15(rootDir, conv);
3196
+ if (await fileExists(p)) entries.push(p);
3197
+ }
3198
+ return entries;
3199
+ }
3200
+ async function findPythonTopLevelPackages(rootDir) {
3201
+ const found = await findFiles("*/__init__.py", rootDir);
3202
+ if (found.length > 0) return found;
3203
+ return findFiles("src/*/__init__.py", rootDir);
3204
+ }
3205
+ async function resolvePython(rootDir) {
3206
+ if (!await detectPython(rootDir)) {
3207
+ return { language: "python", detected: false, entries: [], hints: PYTHON_HINTS };
3208
+ }
3209
+ const info = await readPyProject(rootDir);
3210
+ const strategies = [
3211
+ () => resolvePythonFromScripts(rootDir, info.scriptTargets),
3212
+ () => resolvePythonFromProjectName(rootDir, info.projectName),
3213
+ () => resolvePythonConventions(rootDir),
3214
+ () => findPythonTopLevelPackages(rootDir)
3215
+ ];
3216
+ for (const strategy of strategies) {
3217
+ const entries = await strategy();
3218
+ if (entries.length > 0) {
3219
+ return { language: "python", detected: true, entries, hints: PYTHON_HINTS };
3220
+ }
3221
+ }
3222
+ return { language: "python", detected: true, entries: [], hints: PYTHON_HINTS };
3223
+ }
3224
+ async function resolveGo(rootDir) {
3225
+ const hints = ["Create main.go at the project root, or use the cmd/<name>/main.go layout"];
3226
+ const detected = await fileExists(join15(rootDir, "go.mod"));
3227
+ if (!detected) return { language: "go", detected: false, entries: [], hints };
3228
+ const entries = [];
3229
+ const mainGo = join15(rootDir, "main.go");
3230
+ if (await fileExists(mainGo)) entries.push(mainGo);
3231
+ entries.push(...await findFiles("cmd/*/main.go", rootDir));
3232
+ return { language: "go", detected: true, entries, hints };
3233
+ }
3234
+ async function resolveRust(rootDir) {
3235
+ const hints = [
3236
+ "Create src/main.rs or src/lib.rs",
3237
+ "Declare [[bin]] entries with a `path` in Cargo.toml"
3238
+ ];
3239
+ const cargoPath = join15(rootDir, "Cargo.toml");
3240
+ const detected = await fileExists(cargoPath);
3241
+ if (!detected) return { language: "rust", detected: false, entries: [], hints };
3242
+ const entries = [];
3243
+ const content = await readFileContent(cargoPath);
3244
+ if (content.ok) {
3245
+ for (const bp of parseCargoBinPaths(content.value)) {
3246
+ const abs = resolve4(rootDir, bp);
3247
+ if (await fileExists(abs)) entries.push(abs);
3248
+ }
3249
+ }
3250
+ if (entries.length === 0) {
3251
+ for (const conv of ["src/main.rs", "src/lib.rs"]) {
3252
+ const p = join15(rootDir, conv);
3253
+ if (await fileExists(p)) entries.push(p);
3254
+ }
3255
+ entries.push(...await findFiles("src/bin/*.rs", rootDir));
3256
+ }
3257
+ return { language: "rust", detected: true, entries, hints };
3258
+ }
3259
+ async function resolveJava(rootDir) {
3260
+ const hints = [
3261
+ "Place an entry class at src/main/java/**/Main.java (or *Application.java for Spring Boot)"
3262
+ ];
3263
+ const detected = await fileExists(join15(rootDir, "pom.xml")) || await fileExists(join15(rootDir, "build.gradle")) || await fileExists(join15(rootDir, "build.gradle.kts"));
3264
+ if (!detected) return { language: "java", detected: false, entries: [], hints };
3265
+ const entries = [];
3266
+ entries.push(...await findFiles("src/main/java/**/Main.java", rootDir));
3267
+ entries.push(...await findFiles("src/main/java/**/*Application.java", rootDir));
3268
+ return { language: "java", detected: true, entries, hints };
3269
+ }
3270
+ function parseTomlLine(raw) {
3271
+ const line = raw.replace(/(^|\s)#.*$/, "").trim();
3272
+ if (!line) return {};
3273
+ const sectionMatch = /^\[([^\]]+)\]$/.exec(line);
3274
+ if (sectionMatch) return { section: sectionMatch[1] ?? "" };
3275
+ const eq = line.indexOf("=");
3276
+ if (eq <= 0) return {};
3277
+ return {
3278
+ key: line.slice(0, eq).trim(),
3279
+ value: stripTomlString2(line.slice(eq + 1).trim())
3280
+ };
3281
+ }
3282
+ function parsePyProject(content) {
3283
+ const result = { scriptTargets: [] };
3284
+ let section = null;
3285
+ for (const raw of content.split(/\r?\n/)) {
3286
+ const parsed = parseTomlLine(raw);
3287
+ if (parsed.section !== void 0) {
3288
+ section = parsed.section;
3289
+ continue;
3290
+ }
3291
+ if (parsed.key === void 0 || parsed.value === void 0) continue;
3292
+ if (section === "project" && parsed.key === "name") result.projectName = parsed.value;
3293
+ else if (section === "project.scripts") result.scriptTargets.push(parsed.value);
3294
+ }
3295
+ return result;
3296
+ }
3297
+ function parseCargoBinPaths(content) {
3298
+ const paths = [];
3299
+ let inBin = false;
3300
+ for (const raw of content.split(/\r?\n/)) {
3301
+ const line = raw.replace(/(^|\s)#.*$/, "").trim();
3302
+ if (!line) continue;
3303
+ if (line === "[[bin]]") {
3304
+ inBin = true;
3305
+ continue;
3306
+ }
3307
+ if (line.startsWith("[")) {
3308
+ inBin = false;
3309
+ continue;
3310
+ }
3311
+ if (!inBin) continue;
3312
+ const eq = line.indexOf("=");
3313
+ if (eq <= 0) continue;
3314
+ const key = line.slice(0, eq).trim();
3315
+ if (key === "path") paths.push(stripTomlString2(line.slice(eq + 1).trim()));
3316
+ }
3317
+ return paths;
3318
+ }
3319
+ function stripTomlString2(value) {
3320
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
3321
+ return value.slice(1, -1);
3322
+ }
3323
+ return value;
3324
+ }
3325
+ async function resolveEntryPoints(rootDir, explicitEntries) {
3326
+ if (explicitEntries && explicitEntries.length > 0) {
3327
+ return Ok(explicitEntries.map((e) => resolve4(rootDir, e)));
3328
+ }
3329
+ const resolvers = [resolveTypeScript, resolvePython, resolveGo, resolveRust, resolveJava];
3330
+ const resolutions = [];
3331
+ for (const resolver of resolvers) {
3332
+ const res = await resolver(rootDir);
3333
+ resolutions.push(res);
3334
+ if (res.entries.length > 0) return Ok(res.entries);
3335
+ }
3336
+ const detectedLangs = resolutions.filter((r) => r.detected);
3337
+ const suggestions = detectedLangs.length > 0 ? detectedLangs.flatMap((r) => r.hints) : resolutions.flatMap((r) => r.hints);
3338
+ suggestions.push("Specify entryPoints in config");
3339
+ const reason = detectedLangs.length > 0 ? `Detected ${detectedLangs.map((r) => r.language).join(", ")} project but found no entry points` : "No language manifest (package.json, pyproject.toml, go.mod, Cargo.toml, pom.xml) and no conventional entry files found";
2958
3340
  return Err(
2959
3341
  createEntropyError(
2960
3342
  "ENTRY_POINT_NOT_FOUND",
2961
3343
  "Could not resolve entry points",
2962
- { reason: "No package.json exports/main and no conventional entry files found" },
2963
- [
2964
- 'Add "exports" or "main" to package.json',
2965
- "Create src/index.ts",
2966
- "Specify entryPoints in config"
2967
- ]
3344
+ { reason },
3345
+ suggestions
2968
3346
  )
2969
3347
  );
2970
3348
  }
3349
+
3350
+ // src/entropy/snapshot.ts
3351
+ var DEFAULT_INCLUDE_PATTERNS = [
3352
+ "**/*.ts",
3353
+ "**/*.tsx",
3354
+ "**/*.js",
3355
+ "**/*.jsx",
3356
+ "**/*.mjs",
3357
+ "**/*.cjs",
3358
+ "**/*.py",
3359
+ "**/*.go",
3360
+ "**/*.rs",
3361
+ "**/*.java"
3362
+ ];
2971
3363
  function extractCodeBlocks(content) {
2972
3364
  const blocks = [];
2973
3365
  const lines = content.split("\n");
@@ -3061,6 +3453,7 @@ function extractSymbolsFromNode(node) {
3061
3453
  return [];
3062
3454
  }
3063
3455
  function extractInternalSymbols(ast) {
3456
+ if (ast.language !== "typescript" && ast.language !== "javascript") return [];
3064
3457
  const body = ast.body;
3065
3458
  if (!body?.body) return [];
3066
3459
  const nodes = body.body;
@@ -3071,6 +3464,7 @@ function toJSDocComment(comment) {
3071
3464
  return { content: comment.value, line: comment.loc?.start?.line || 0 };
3072
3465
  }
3073
3466
  function extractJSDocComments(ast) {
3467
+ if (ast.language !== "typescript" && ast.language !== "javascript") return [];
3074
3468
  const body = ast.body;
3075
3469
  if (!body?.comments) return [];
3076
3470
  return body.comments.flatMap((c) => {
@@ -3129,19 +3523,16 @@ function extractAllCodeReferences(docs) {
3129
3523
  }
3130
3524
  async function buildSnapshot(config) {
3131
3525
  const startTime = Date.now();
3132
- const parser = config.parser || new TypeScriptParser();
3133
- const rootDir = resolve4(config.rootDir);
3526
+ const rootDir = resolve5(config.rootDir);
3134
3527
  const entryPointsResult = await resolveEntryPoints(rootDir, config.entryPoints);
3135
3528
  if (!entryPointsResult.ok) {
3136
3529
  return Err(entryPointsResult.error);
3137
3530
  }
3138
- const includePatterns = config.include || ["**/*.ts", "**/*.tsx"];
3139
- const excludePatterns = config.exclude || [
3140
- "node_modules/**",
3141
- "dist/**",
3142
- "**/*.test.ts",
3143
- "**/*.spec.ts"
3144
- ];
3531
+ const registry = getDefaultRegistry();
3532
+ const singleParser = config.parser;
3533
+ const parserForFile = (filePath) => singleParser ?? registry.getForFile(filePath);
3534
+ const includePatterns = config.include || DEFAULT_INCLUDE_PATTERNS;
3535
+ const excludePatterns = config.exclude || [...skipDirGlobs(), "**/*.test.ts", "**/*.spec.ts"];
3145
3536
  let sourceFilePaths = [];
3146
3537
  for (const pattern of includePatterns) {
3147
3538
  const files2 = await findFiles(pattern, rootDir);
@@ -3149,14 +3540,16 @@ async function buildSnapshot(config) {
3149
3540
  }
3150
3541
  sourceFilePaths = sourceFilePaths.filter((f) => {
3151
3542
  const rel = relativePosix(rootDir, f);
3152
- return !excludePatterns.some((p) => minimatch2(rel, p));
3543
+ return !excludePatterns.some((p) => minimatch3(rel, p));
3153
3544
  });
3154
3545
  const files = [];
3155
3546
  for (const filePath of sourceFilePaths) {
3156
- const parseResult = await parser.parseFile(filePath);
3547
+ const fileParser = parserForFile(filePath);
3548
+ if (!fileParser) continue;
3549
+ const parseResult = await fileParser.parseFile(filePath);
3157
3550
  if (!parseResult.ok) continue;
3158
- const importsResult = parser.extractImports(parseResult.value);
3159
- const exportsResult = parser.extractExports(parseResult.value);
3551
+ const importsResult = fileParser.extractImports(parseResult.value);
3552
+ const exportsResult = fileParser.extractExports(parseResult.value);
3160
3553
  const internalSymbols = extractInternalSymbols(parseResult.value);
3161
3554
  const jsDocComments = extractJSDocComments(parseResult.value);
3162
3555
  files.push({
@@ -3168,7 +3561,7 @@ async function buildSnapshot(config) {
3168
3561
  jsDocComments
3169
3562
  });
3170
3563
  }
3171
- const graphResult = await buildDependencyGraph(sourceFilePaths, parser);
3564
+ const graphResult = await buildDependencyGraph(sourceFilePaths, singleParser ?? registry);
3172
3565
  const dependencyGraph = graphResult.ok ? graphResult.value : { nodes: [], edges: [] };
3173
3566
  const docPatterns = config.docPaths || ["docs/**/*.md", "README.md", "**/README.md"];
3174
3567
  let docFilePaths = [];
@@ -3201,7 +3594,7 @@ async function buildSnapshot(config) {
3201
3594
  }
3202
3595
 
3203
3596
  // src/entropy/detectors/drift.ts
3204
- import { dirname as dirname5, resolve as resolve5 } from "path";
3597
+ import { dirname as dirname5, resolve as resolve6 } from "path";
3205
3598
  function initLevenshteinMatrix(aLen, bLen) {
3206
3599
  const matrix = [];
3207
3600
  for (let i = 0; i <= bLen; i++) {
@@ -3313,7 +3706,7 @@ async function checkStructureDrift(snapshot, _config) {
3313
3706
  for (const doc of snapshot.docs) {
3314
3707
  const fileLinks = extractFileLinks(doc.content);
3315
3708
  for (const { link, line } of fileLinks) {
3316
- const resolvedPath = resolve5(dirname5(doc.path), link);
3709
+ const resolvedPath = resolve6(dirname5(doc.path), link);
3317
3710
  const exists = await fileExists(resolvedPath);
3318
3711
  if (!exists) {
3319
3712
  drifts.push({
@@ -3404,7 +3797,7 @@ async function detectDocDrift(snapshot, config, graphDriftData) {
3404
3797
  }
3405
3798
 
3406
3799
  // src/entropy/detectors/dead-code.ts
3407
- import { dirname as dirname6, extname, resolve as resolve6 } from "path";
3800
+ import { dirname as dirname6, extname, resolve as resolve7 } from "path";
3408
3801
  var JS_EXT_FALLBACKS = {
3409
3802
  ".js": [".ts", ".tsx", ".jsx"],
3410
3803
  ".jsx": [".tsx"],
@@ -3424,7 +3817,7 @@ function resolveImportToFile(importSource, fromFile, snapshot, fileIndex) {
3424
3817
  }
3425
3818
  const hasFile = fileIndex ? (p) => fileIndex.has(p) : (p) => snapshot.files.some((f) => f.path === p);
3426
3819
  const fromDir = dirname6(fromFile);
3427
- const resolved = resolve6(fromDir, importSource);
3820
+ const resolved = resolve7(fromDir, importSource);
3428
3821
  const sourceExt = extname(resolved);
3429
3822
  const fallbacks = JS_EXT_FALLBACKS[sourceExt];
3430
3823
  if (fallbacks) {
@@ -3434,7 +3827,7 @@ function resolveImportToFile(importSource, fromFile, snapshot, fileIndex) {
3434
3827
  if (hasFile(candidate)) return candidate;
3435
3828
  }
3436
3829
  for (const indexExt of [".ts", ".tsx", ".jsx"]) {
3437
- const indexPath2 = resolve6(base, "index" + indexExt);
3830
+ const indexPath2 = resolve7(base, "index" + indexExt);
3438
3831
  if (hasFile(indexPath2)) return indexPath2;
3439
3832
  }
3440
3833
  }
@@ -3445,7 +3838,7 @@ function resolveImportToFile(importSource, fromFile, snapshot, fileIndex) {
3445
3838
  if (hasFile(candidate)) return candidate;
3446
3839
  }
3447
3840
  for (const indexExt of [".ts", ".tsx"]) {
3448
- const indexPath2 = resolve6(resolved, "index" + indexExt);
3841
+ const indexPath2 = resolve7(resolved, "index" + indexExt);
3449
3842
  if (hasFile(indexPath2)) return indexPath2;
3450
3843
  }
3451
3844
  }
@@ -3759,10 +4152,10 @@ async function detectDeadCode(snapshot, graphDeadCodeData, protectedRegions) {
3759
4152
  }
3760
4153
 
3761
4154
  // src/entropy/detectors/patterns.ts
3762
- import { minimatch as minimatch3 } from "minimatch";
4155
+ import { minimatch as minimatch4 } from "minimatch";
3763
4156
  function fileMatchesPattern(filePath, pattern, rootDir) {
3764
4157
  const relativePath = relativePosix(rootDir, filePath);
3765
- return minimatch3(relativePath, pattern);
4158
+ return minimatch4(relativePath, pattern);
3766
4159
  }
3767
4160
  var CONVENTION_DESCRIPTIONS = {
3768
4161
  camelCase: "camelCase (e.g., myFunction)",
@@ -3962,6 +4355,7 @@ async function detectPatternViolations(snapshot, config) {
3962
4355
  // src/entropy/detectors/size-budget.ts
3963
4356
  import { readdirSync, statSync as statSync2 } from "fs";
3964
4357
  import { join as join16 } from "path";
4358
+ import { DEFAULT_SKIP_DIRS } from "@harness-engineering/graph";
3965
4359
  function parseSize(size) {
3966
4360
  const match = size.trim().match(/^(\d+(?:\.\d+)?)\s*(KB|MB|GB|B)?$/i);
3967
4361
  if (!match) return 0;
@@ -3987,7 +4381,7 @@ function dirSize(dirPath) {
3987
4381
  return 0;
3988
4382
  }
3989
4383
  for (const entry of entries) {
3990
- if (entry === "node_modules" || entry === ".git") continue;
4384
+ if (DEFAULT_SKIP_DIRS.has(entry)) continue;
3991
4385
  const fullPath = join16(dirPath, entry);
3992
4386
  try {
3993
4387
  const stat2 = statSync2(fullPath);
@@ -4164,10 +4558,7 @@ var EntropyAnalyzer = class {
4164
4558
  snapshot;
4165
4559
  report;
4166
4560
  constructor(config) {
4167
- this.config = {
4168
- ...config,
4169
- parser: config.parser || new TypeScriptParser()
4170
- };
4561
+ this.config = { ...config };
4171
4562
  }
4172
4563
  /**
4173
4564
  * Run full entropy analysis.
@@ -5176,7 +5567,7 @@ var RegressionDetector = class {
5176
5567
  // src/performance/critical-path.ts
5177
5568
  import * as fs7 from "fs";
5178
5569
  import * as path4 from "path";
5179
- var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", "dist", ".git"]);
5570
+ import { DEFAULT_SKIP_DIRS as DEFAULT_SKIP_DIRS2 } from "@harness-engineering/graph";
5180
5571
  var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx"]);
5181
5572
  var FUNCTION_DECL_RE = /(?:export\s+)?(?:async\s+)?function\s+(\w+)/;
5182
5573
  var CONST_DECL_RE = /(?:export\s+)?(?:const|let)\s+(\w+)\s*=/;
@@ -5243,7 +5634,7 @@ var CriticalPathResolver = class {
5243
5634
  }
5244
5635
  for (const item of items) {
5245
5636
  if (item.isDirectory()) {
5246
- if (SKIP_DIRS.has(item.name)) continue;
5637
+ if (DEFAULT_SKIP_DIRS2.has(item.name)) continue;
5247
5638
  this.walkDir(path4.join(dir, item.name), entries);
5248
5639
  } else if (item.isFile() && SOURCE_EXTENSIONS.has(path4.extname(item.name))) {
5249
5640
  this.scanFile(path4.join(dir, item.name), entries);
@@ -8262,7 +8653,7 @@ function checkOverlap(newEntry, existingEntries, options) {
8262
8653
  var LOCK_RETRIES = 3;
8263
8654
  var LOCK_BACKOFFS = [50, 100, 200];
8264
8655
  function sleep(ms) {
8265
- return new Promise((resolve9) => setTimeout(resolve9, ms));
8656
+ return new Promise((resolve10) => setTimeout(resolve10, ms));
8266
8657
  }
8267
8658
  async function acquireFileLock(lockPath) {
8268
8659
  for (let attempt = 0; attempt < LOCK_RETRIES; attempt++) {
@@ -9475,7 +9866,7 @@ async function runMultiTurnPipeline(initialContext, turnExecutor, options) {
9475
9866
 
9476
9867
  // src/security/scanner.ts
9477
9868
  import * as fs28 from "fs/promises";
9478
- import { minimatch as minimatch4 } from "minimatch";
9869
+ import { minimatch as minimatch5 } from "minimatch";
9479
9870
 
9480
9871
  // src/security/rules/registry.ts
9481
9872
  var RuleRegistry = class {
@@ -9507,13 +9898,15 @@ var RuleRegistry = class {
9507
9898
 
9508
9899
  // src/security/config.ts
9509
9900
  import { z as z10 } from "zod";
9901
+ import { skipDirGlobs as skipDirGlobs3 } from "@harness-engineering/graph";
9510
9902
 
9511
9903
  // src/security/types.ts
9904
+ import { skipDirGlobs as skipDirGlobs2 } from "@harness-engineering/graph";
9512
9905
  var DEFAULT_SECURITY_CONFIG = {
9513
9906
  enabled: true,
9514
9907
  strict: false,
9515
9908
  rules: {},
9516
- exclude: ["**/node_modules/**", "**/dist/**", "**/*.test.ts", "**/fixtures/**"]
9909
+ exclude: [...skipDirGlobs2(), "**/*.test.ts", "**/fixtures/**"]
9517
9910
  };
9518
9911
 
9519
9912
  // src/security/config.ts
@@ -9522,7 +9915,7 @@ var SecurityConfigSchema = z10.object({
9522
9915
  enabled: z10.boolean().default(true),
9523
9916
  strict: z10.boolean().default(false),
9524
9917
  rules: z10.record(z10.string(), RuleOverrideSchema).optional().default({}),
9525
- exclude: z10.array(z10.string()).optional().default(["**/node_modules/**", "**/dist/**", "**/*.test.ts", "**/fixtures/**"]),
9918
+ exclude: z10.array(z10.string()).optional().default([...skipDirGlobs3(), "**/*.test.ts", "**/fixtures/**"]),
9526
9919
  external: z10.object({
9527
9920
  semgrep: z10.object({
9528
9921
  enabled: z10.union([z10.literal("auto"), z10.boolean()]).default("auto"),
@@ -10433,7 +10826,7 @@ var SecurityScanner = class {
10433
10826
  const applicableRules = this.activeRules.filter((rule) => {
10434
10827
  if (!rule.fileGlob) return true;
10435
10828
  const globs = rule.fileGlob.split(",").map((g) => g.trim());
10436
- return globs.some((glob5) => minimatch4(filePath, glob5, { dot: true }));
10829
+ return globs.some((glob5) => minimatch5(filePath, glob5, { dot: true }));
10437
10830
  });
10438
10831
  return this.scanLinesWithRules(lines, applicableRules, filePath, startLine);
10439
10832
  }
@@ -11441,6 +11834,7 @@ var SecurityTimelineManager = class _SecurityTimelineManager {
11441
11834
 
11442
11835
  // src/ci/check-orchestrator.ts
11443
11836
  import * as path25 from "path";
11837
+ import { skipDirGlobs as skipDirGlobs4 } from "@harness-engineering/graph";
11444
11838
  import { GraphStore, queryTraceability } from "@harness-engineering/graph";
11445
11839
  var ALL_CHECKS = [
11446
11840
  "validate",
@@ -11518,8 +11912,7 @@ async function runDocsCheck(projectRoot, config) {
11518
11912
  docsDir,
11519
11913
  sourceDir: projectRoot,
11520
11914
  excludePatterns: entropyConfig.excludePatterns || [
11521
- "**/node_modules/**",
11522
- "**/dist/**",
11915
+ ...skipDirGlobs4(),
11523
11916
  "**/*.test.ts",
11524
11917
  "**/fixtures/**"
11525
11918
  ]
@@ -11584,12 +11977,7 @@ async function runSecurityCheck(projectRoot, config) {
11584
11977
  const { glob: globFn } = await import("glob");
11585
11978
  const sourceFiles = await globFn("**/*.{ts,tsx,js,jsx,go,py}", {
11586
11979
  cwd: projectRoot,
11587
- ignore: securityConfig.exclude ?? [
11588
- "**/node_modules/**",
11589
- "**/dist/**",
11590
- "**/*.test.ts",
11591
- "**/fixtures/**"
11592
- ],
11980
+ ignore: securityConfig.exclude ?? [...skipDirGlobs4(), "**/*.test.ts", "**/fixtures/**"],
11593
11981
  absolute: true
11594
11982
  });
11595
11983
  const scanResult = await scanner.scanFiles(sourceFiles);
@@ -14463,7 +14851,7 @@ function parseRepoParts(repo) {
14463
14851
  return { owner: parts[0], repo: parts[1] };
14464
14852
  }
14465
14853
  function sleep2(ms) {
14466
- return new Promise((resolve9) => setTimeout(resolve9, ms));
14854
+ return new Promise((resolve10) => setTimeout(resolve10, ms));
14467
14855
  }
14468
14856
  async function fetchWithRetry(fetchFn, input, init, opts = RETRY_DEFAULTS) {
14469
14857
  let lastResponse;
@@ -14997,8 +15385,8 @@ var syncMutex = Promise.resolve();
14997
15385
  async function fullSync(roadmapPath, adapter2, config, options) {
14998
15386
  const previousSync = syncMutex;
14999
15387
  let releaseMutex;
15000
- syncMutex = new Promise((resolve9) => {
15001
- releaseMutex = resolve9;
15388
+ syncMutex = new Promise((resolve10) => {
15389
+ releaseMutex = resolve10;
15002
15390
  });
15003
15391
  await previousSync;
15004
15392
  try {
@@ -17766,7 +18154,7 @@ var POSTHOG_BATCH_URL = "https://app.posthog.com/batch";
17766
18154
  var MAX_ATTEMPTS = 3;
17767
18155
  var TIMEOUT_MS = 5e3;
17768
18156
  function sleep3(ms) {
17769
- return new Promise((resolve9) => setTimeout(resolve9, ms));
18157
+ return new Promise((resolve10) => setTimeout(resolve10, ms));
17770
18158
  }
17771
18159
  async function send(events, apiKey) {
17772
18160
  if (events.length === 0) return;
@@ -18676,6 +19064,7 @@ export {
18676
19064
  ViolationHistorySchema,
18677
19065
  ViolationSchema,
18678
19066
  ViolationSnapshotSchema,
19067
+ WHATWG_BAD_PORTS,
18679
19068
  acquireCompoundLock,
18680
19069
  addProvenance,
18681
19070
  agentConfigRules,
@@ -18701,6 +19090,7 @@ export {
18701
19090
  archiveStream,
18702
19091
  assembleCandidateReport,
18703
19092
  assembleReport,
19093
+ assertPortUsable,
18704
19094
  assertSanitized,
18705
19095
  assignFeature,
18706
19096
  buildDependencyGraph,
@@ -18815,6 +19205,7 @@ export {
18815
19205
  goRules,
18816
19206
  injectionRules,
18817
19207
  insecureDefaultsRules,
19208
+ isBadPort,
18818
19209
  isDuplicateFinding,
18819
19210
  isRegression,
18820
19211
  isSanitizedResult,
@@ -18948,6 +19339,7 @@ export {
18948
19339
  validateAgentConfigs,
18949
19340
  validateAgentsMap,
18950
19341
  validateBoundaries,
19342
+ validateBranchName,
18951
19343
  validateCommitMessage,
18952
19344
  validateConfig,
18953
19345
  validateDependencies,