@cullit/core 1.17.0 → 1.18.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/LICENSE +8 -17
- package/dist/index.d.ts +58 -2
- package/dist/index.js +106 -14
- package/package.json +3 -3
package/LICENSE
CHANGED
|
@@ -1,21 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
Copyright (c) 2026 Cullit (Matt). All rights reserved.
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This software and its source code are proprietary to Cullit.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
5
|
+
You may view and fork this repository for evaluation purposes. You may not
|
|
6
|
+
use, copy, modify, merge, publish, distribute, sublicense, or sell copies
|
|
7
|
+
of this software without explicit written permission from the copyright holder.
|
|
11
8
|
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
Commercial use requires a valid Cullit license. See https://cullit.io/pricing
|
|
10
|
+
for available plans.
|
|
14
11
|
|
|
15
|
-
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
12
|
+
For full terms of use, see TERMS.md.
|
package/dist/index.d.ts
CHANGED
|
@@ -71,7 +71,7 @@ interface PipelineResult {
|
|
|
71
71
|
duration: number;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
declare const VERSION = "1.
|
|
74
|
+
declare const VERSION = "1.18.1";
|
|
75
75
|
declare const DEFAULT_CATEGORIES: string[];
|
|
76
76
|
declare const DEFAULT_MODELS: Record<string, string>;
|
|
77
77
|
declare const AI_PROVIDERS: readonly ["anthropic", "openai", "gemini", "ollama", "none"];
|
|
@@ -312,6 +312,62 @@ declare function listPublishers(): string[];
|
|
|
312
312
|
*/
|
|
313
313
|
declare function fetchWithTimeout(url: string, init: RequestInit, timeoutMs?: number): Promise<Response>;
|
|
314
314
|
|
|
315
|
+
/**
|
|
316
|
+
* Rate Limiter — Sliding-window rate limiter with pluggable backends.
|
|
317
|
+
*
|
|
318
|
+
* Usage:
|
|
319
|
+
* const limiter = createRateLimiter({ limit: 30, windowMs: 60_000 });
|
|
320
|
+
* const result = limiter.check('user-ip-or-key');
|
|
321
|
+
* if (!result.allowed) { // reject }
|
|
322
|
+
*/
|
|
323
|
+
interface RateLimitResult {
|
|
324
|
+
allowed: boolean;
|
|
325
|
+
remaining: number;
|
|
326
|
+
/** Unix timestamp (seconds) when the window resets */
|
|
327
|
+
resetAt: number;
|
|
328
|
+
}
|
|
329
|
+
interface RateLimiter {
|
|
330
|
+
check(key: string): RateLimitResult;
|
|
331
|
+
/** Remove all tracked entries */
|
|
332
|
+
reset(): void;
|
|
333
|
+
}
|
|
334
|
+
interface RateLimiterOptions {
|
|
335
|
+
/** Max requests per window (default: 30) */
|
|
336
|
+
limit?: number;
|
|
337
|
+
/** Window duration in ms (default: 60_000) */
|
|
338
|
+
windowMs?: number;
|
|
339
|
+
/** Max tracked keys before eviction (default: 10_000) */
|
|
340
|
+
maxBuckets?: number;
|
|
341
|
+
}
|
|
342
|
+
declare function createRateLimiter(opts?: RateLimiterOptions): RateLimiter;
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Structured error codes for the Cullit core pipeline.
|
|
346
|
+
* Used in CullitError instances so callers can branch on code rather than message parsing.
|
|
347
|
+
*/
|
|
348
|
+
declare const CoreErrorCode: {
|
|
349
|
+
readonly GIT_REF_INVALID: "GIT_REF_INVALID";
|
|
350
|
+
readonly GIT_LOG_FAILED: "GIT_LOG_FAILED";
|
|
351
|
+
readonly MULTI_REPO_EMPTY: "MULTI_REPO_EMPTY";
|
|
352
|
+
readonly MULTI_REPO_MISSING_TARGET: "MULTI_REPO_MISSING_TARGET";
|
|
353
|
+
readonly MULTI_REPO_INVALID_URL: "MULTI_REPO_INVALID_URL";
|
|
354
|
+
readonly PIPELINE_NO_CHANGES: "PIPELINE_NO_CHANGES";
|
|
355
|
+
readonly PIPELINE_COLLECTOR_MISSING: "PIPELINE_COLLECTOR_MISSING";
|
|
356
|
+
readonly PIPELINE_GENERATOR_MISSING: "PIPELINE_GENERATOR_MISSING";
|
|
357
|
+
readonly LICENSE_INVALID: "LICENSE_INVALID";
|
|
358
|
+
readonly LICENSE_TIER_INSUFFICIENT: "LICENSE_TIER_INSUFFICIENT";
|
|
359
|
+
readonly FETCH_TIMEOUT: "FETCH_TIMEOUT";
|
|
360
|
+
readonly PUBLISHER_PATH_TRAVERSAL: "PUBLISHER_PATH_TRAVERSAL";
|
|
361
|
+
};
|
|
362
|
+
type CoreErrorCodeValue = typeof CoreErrorCode[keyof typeof CoreErrorCode];
|
|
363
|
+
/**
|
|
364
|
+
* Error class carrying a structured code alongside the human-readable message.
|
|
365
|
+
*/
|
|
366
|
+
declare class CullitError extends Error {
|
|
367
|
+
readonly code: CoreErrorCodeValue;
|
|
368
|
+
constructor(code: CoreErrorCodeValue, message: string);
|
|
369
|
+
}
|
|
370
|
+
|
|
315
371
|
/**
|
|
316
372
|
* Main pipeline: Collect → Enrich → Generate → Format → Publish
|
|
317
373
|
*/
|
|
@@ -322,4 +378,4 @@ declare function runPipeline(from: string, to: string, config: CullConfig, optio
|
|
|
322
378
|
templateProfile?: string;
|
|
323
379
|
}): Promise<PipelineResult>;
|
|
324
380
|
|
|
325
|
-
export { AI_PROVIDERS, AUDIENCES, CHANGE_CATEGORIES, type ChangeCategory, type ChangeEntry, type Collector, type CollectorFactory, DEFAULT_CATEGORIES, DEFAULT_MODELS, ENRICHMENT_TYPES, type EnrichedContext, type EnrichedTicket, type Enricher, type EnricherFactory, FilePublisher, type Generator, type GeneratorFactory, GitCollector, type GitCommit, type GitDiff, type LicenseStatus, type LicenseTier, type LogLevel, type Logger, MultiRepoCollector, OUTPUT_FORMATS, PUBLISHER_TYPES, type PipelineResult, type Publisher, type PublisherFactory, type ReleaseAdvisory, type ReleaseNotes, SOURCE_TYPES, type SemverBump, StdoutPublisher, TONES, type TeamFeature, TemplateGenerator, type UsageLimits, VERSION, analyzeReleaseReadiness, createLogger, escapeHtml, fetchWithTimeout, formatNotes, getCollector, getEnricher, getFeatureGating, getFormatter, getGenerator, getLatestTag, getPublisher, getRecentTags, getTierLimits, hasCollector, hasEnricher, hasGenerator, hasPublisher, isEnrichmentAllowed, isFeatureAllowed, isProviderAllowed, isPublisherAllowed, listCollectors, listEnrichers, listFormatters, listGenerators, listPublishers, registerCollector, registerEnricher, registerFormatter, registerGenerator, registerPublisher, reportUsage, resolveLicense, runPipeline, upgradeMessage, validateLicense };
|
|
381
|
+
export { AI_PROVIDERS, AUDIENCES, CHANGE_CATEGORIES, type ChangeCategory, type ChangeEntry, type Collector, type CollectorFactory, CoreErrorCode, type CoreErrorCodeValue, CullitError, DEFAULT_CATEGORIES, DEFAULT_MODELS, ENRICHMENT_TYPES, type EnrichedContext, type EnrichedTicket, type Enricher, type EnricherFactory, FilePublisher, type Generator, type GeneratorFactory, GitCollector, type GitCommit, type GitDiff, type LicenseStatus, type LicenseTier, type LogLevel, type Logger, MultiRepoCollector, OUTPUT_FORMATS, PUBLISHER_TYPES, type PipelineResult, type Publisher, type PublisherFactory, type RateLimitResult, type RateLimiter, type RateLimiterOptions, type ReleaseAdvisory, type ReleaseNotes, SOURCE_TYPES, type SemverBump, StdoutPublisher, TONES, type TeamFeature, TemplateGenerator, type UsageLimits, VERSION, analyzeReleaseReadiness, createLogger, createRateLimiter, escapeHtml, fetchWithTimeout, formatNotes, getCollector, getEnricher, getFeatureGating, getFormatter, getGenerator, getLatestTag, getPublisher, getRecentTags, getTierLimits, hasCollector, hasEnricher, hasGenerator, hasPublisher, isEnrichmentAllowed, isFeatureAllowed, isProviderAllowed, isPublisherAllowed, listCollectors, listEnrichers, listFormatters, listGenerators, listPublishers, registerCollector, registerEnricher, registerFormatter, registerGenerator, registerPublisher, reportUsage, resolveLicense, runPipeline, upgradeMessage, validateLicense };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/constants.ts
|
|
2
|
-
var VERSION = "1.
|
|
2
|
+
var VERSION = "1.18.1";
|
|
3
3
|
var DEFAULT_CATEGORIES = ["features", "fixes", "breaking", "improvements", "chores"];
|
|
4
4
|
var DEFAULT_MODELS = {
|
|
5
5
|
anthropic: "claude-sonnet-4-20250514",
|
|
@@ -36,12 +36,44 @@ function createLogger(level = "normal") {
|
|
|
36
36
|
|
|
37
37
|
// src/collectors/git.ts
|
|
38
38
|
import { execFileSync } from "child_process";
|
|
39
|
+
|
|
40
|
+
// src/errors.ts
|
|
41
|
+
var CoreErrorCode = {
|
|
42
|
+
// Git / Source
|
|
43
|
+
GIT_REF_INVALID: "GIT_REF_INVALID",
|
|
44
|
+
GIT_LOG_FAILED: "GIT_LOG_FAILED",
|
|
45
|
+
// Multi-repo
|
|
46
|
+
MULTI_REPO_EMPTY: "MULTI_REPO_EMPTY",
|
|
47
|
+
MULTI_REPO_MISSING_TARGET: "MULTI_REPO_MISSING_TARGET",
|
|
48
|
+
MULTI_REPO_INVALID_URL: "MULTI_REPO_INVALID_URL",
|
|
49
|
+
// Pipeline
|
|
50
|
+
PIPELINE_NO_CHANGES: "PIPELINE_NO_CHANGES",
|
|
51
|
+
PIPELINE_COLLECTOR_MISSING: "PIPELINE_COLLECTOR_MISSING",
|
|
52
|
+
PIPELINE_GENERATOR_MISSING: "PIPELINE_GENERATOR_MISSING",
|
|
53
|
+
// License
|
|
54
|
+
LICENSE_INVALID: "LICENSE_INVALID",
|
|
55
|
+
LICENSE_TIER_INSUFFICIENT: "LICENSE_TIER_INSUFFICIENT",
|
|
56
|
+
// Fetch
|
|
57
|
+
FETCH_TIMEOUT: "FETCH_TIMEOUT",
|
|
58
|
+
// Publisher
|
|
59
|
+
PUBLISHER_PATH_TRAVERSAL: "PUBLISHER_PATH_TRAVERSAL"
|
|
60
|
+
};
|
|
61
|
+
var CullitError = class extends Error {
|
|
62
|
+
code;
|
|
63
|
+
constructor(code, message) {
|
|
64
|
+
super(message);
|
|
65
|
+
this.name = "CullitError";
|
|
66
|
+
this.code = code;
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// src/collectors/git.ts
|
|
39
71
|
function validateRef(ref) {
|
|
40
72
|
if (!ref || ref.length > 256) {
|
|
41
|
-
throw new
|
|
73
|
+
throw new CullitError(CoreErrorCode.GIT_REF_INVALID, `Invalid git ref: too ${ref ? "long" : "short"}`);
|
|
42
74
|
}
|
|
43
75
|
if (!/^[a-zA-Z0-9._\-/~^]+$/.test(ref)) {
|
|
44
|
-
throw new
|
|
76
|
+
throw new CullitError(CoreErrorCode.GIT_REF_INVALID, `Invalid git ref "${ref}" \u2014 only alphanumeric, dots, dashes, underscores, slashes, tildes, and carets are allowed`);
|
|
45
77
|
}
|
|
46
78
|
}
|
|
47
79
|
var GitCollector = class {
|
|
@@ -74,7 +106,8 @@ var GitCollector = class {
|
|
|
74
106
|
const errWithStderr = typeof error === "object" && error !== null && "stderr" in error ? error : void 0;
|
|
75
107
|
const stderr = errWithStderr?.stderr?.toString?.() || "";
|
|
76
108
|
const hint = stderr.includes("unknown revision") ? 'Check that both refs exist (run "cullit tags" to see tags).' : stderr.includes("not a git repository") ? "Run this command inside a git repository." : `Make sure both refs exist and you're in a git repository.`;
|
|
77
|
-
throw new
|
|
109
|
+
throw new CullitError(
|
|
110
|
+
CoreErrorCode.GIT_LOG_FAILED,
|
|
78
111
|
`Failed to read git log between ${from} and ${to}. ${hint}`
|
|
79
112
|
);
|
|
80
113
|
}
|
|
@@ -194,7 +227,7 @@ var MultiRepoCollector = class {
|
|
|
194
227
|
repos;
|
|
195
228
|
tempDirs = [];
|
|
196
229
|
constructor(repos) {
|
|
197
|
-
if (!repos.length) throw new
|
|
230
|
+
if (!repos.length) throw new CullitError(CoreErrorCode.MULTI_REPO_EMPTY, "Multi-repo collector requires at least one repo");
|
|
198
231
|
this.repos = repos;
|
|
199
232
|
}
|
|
200
233
|
async collect(from, to) {
|
|
@@ -229,10 +262,10 @@ var MultiRepoCollector = class {
|
|
|
229
262
|
async resolveRepoPath(repo) {
|
|
230
263
|
if (repo.path) return repo.path;
|
|
231
264
|
if (!repo.url) {
|
|
232
|
-
throw new
|
|
265
|
+
throw new CullitError(CoreErrorCode.MULTI_REPO_MISSING_TARGET, 'Each repo must have either "url" or "path"');
|
|
233
266
|
}
|
|
234
267
|
if (!/^(https?:\/\/|git@|ssh:\/\/)/.test(repo.url)) {
|
|
235
|
-
throw new
|
|
268
|
+
throw new CullitError(CoreErrorCode.MULTI_REPO_INVALID_URL, `Invalid repo URL: ${repo.url}`);
|
|
236
269
|
}
|
|
237
270
|
const tempDir = mkdtempSync(join(tmpdir(), "cullit-repo-"));
|
|
238
271
|
this.tempDirs.push(tempDir);
|
|
@@ -560,7 +593,7 @@ var FilePublisher = class {
|
|
|
560
593
|
const cwd = resolve(".");
|
|
561
594
|
const rel = relative(cwd, resolved);
|
|
562
595
|
if (rel.startsWith("..") || isAbsolute(rel)) {
|
|
563
|
-
throw new
|
|
596
|
+
throw new CullitError(CoreErrorCode.PUBLISHER_PATH_TRAVERSAL, `File output path must be within the project directory. Got: ${path}`);
|
|
564
597
|
}
|
|
565
598
|
}
|
|
566
599
|
async publish(notes, format, preformatted) {
|
|
@@ -706,7 +739,7 @@ async function fetchWithTimeout(url, init, timeoutMs = DEFAULT_TIMEOUT) {
|
|
|
706
739
|
return await fetch(url, { ...init, signal: controller.signal });
|
|
707
740
|
} catch (err) {
|
|
708
741
|
if (err.name === "AbortError") {
|
|
709
|
-
throw new
|
|
742
|
+
throw new CullitError(CoreErrorCode.FETCH_TIMEOUT, `Request to ${new URL(url).hostname} timed out after ${timeoutMs / 1e3}s`);
|
|
710
743
|
}
|
|
711
744
|
throw err;
|
|
712
745
|
} finally {
|
|
@@ -746,6 +779,14 @@ async function validateLicense() {
|
|
|
746
779
|
if (!validationUrl) {
|
|
747
780
|
return { tier: "pro", valid: true };
|
|
748
781
|
}
|
|
782
|
+
try {
|
|
783
|
+
const parsed = new URL(validationUrl);
|
|
784
|
+
if (parsed.protocol !== "https:" && !(parsed.protocol === "http:" && parsed.hostname === "localhost")) {
|
|
785
|
+
return { tier: "pro", valid: true, message: "CULLIT_LICENSE_URL must use https." };
|
|
786
|
+
}
|
|
787
|
+
} catch {
|
|
788
|
+
return { tier: "pro", valid: true, message: "CULLIT_LICENSE_URL is not a valid URL." };
|
|
789
|
+
}
|
|
749
790
|
try {
|
|
750
791
|
const res = await fetchWithTimeout(validationUrl, {
|
|
751
792
|
method: "POST",
|
|
@@ -902,6 +943,52 @@ function listPublishers() {
|
|
|
902
943
|
return Array.from(publishers.keys());
|
|
903
944
|
}
|
|
904
945
|
|
|
946
|
+
// src/rate-limiter.ts
|
|
947
|
+
var MemoryRateLimiter = class {
|
|
948
|
+
limit;
|
|
949
|
+
windowMs;
|
|
950
|
+
maxBuckets;
|
|
951
|
+
buckets = /* @__PURE__ */ new Map();
|
|
952
|
+
pruneTimer;
|
|
953
|
+
constructor(opts = {}) {
|
|
954
|
+
this.limit = opts.limit ?? 30;
|
|
955
|
+
this.windowMs = opts.windowMs ?? 6e4;
|
|
956
|
+
this.maxBuckets = opts.maxBuckets ?? 1e4;
|
|
957
|
+
this.pruneTimer = setInterval(() => {
|
|
958
|
+
const now = Date.now();
|
|
959
|
+
for (const [key, times] of this.buckets) {
|
|
960
|
+
const active = times.filter((t) => now - t < this.windowMs);
|
|
961
|
+
if (active.length === 0) this.buckets.delete(key);
|
|
962
|
+
else this.buckets.set(key, active);
|
|
963
|
+
}
|
|
964
|
+
}, 12e4);
|
|
965
|
+
this.pruneTimer.unref();
|
|
966
|
+
}
|
|
967
|
+
check(key) {
|
|
968
|
+
const now = Date.now();
|
|
969
|
+
const timestamps = this.buckets.get(key) || [];
|
|
970
|
+
const recent = timestamps.filter((t) => now - t < this.windowMs);
|
|
971
|
+
const remaining = Math.max(0, this.limit - recent.length);
|
|
972
|
+
const resetAt = recent.length > 0 ? Math.ceil((recent[0] + this.windowMs) / 1e3) : Math.ceil((now + this.windowMs) / 1e3);
|
|
973
|
+
if (recent.length >= this.limit) {
|
|
974
|
+
return { allowed: false, remaining: 0, resetAt };
|
|
975
|
+
}
|
|
976
|
+
if (!this.buckets.has(key) && this.buckets.size >= this.maxBuckets) {
|
|
977
|
+
const oldest = this.buckets.keys().next().value;
|
|
978
|
+
if (oldest) this.buckets.delete(oldest);
|
|
979
|
+
}
|
|
980
|
+
recent.push(now);
|
|
981
|
+
this.buckets.set(key, recent);
|
|
982
|
+
return { allowed: true, remaining: remaining - 1, resetAt };
|
|
983
|
+
}
|
|
984
|
+
reset() {
|
|
985
|
+
this.buckets.clear();
|
|
986
|
+
}
|
|
987
|
+
};
|
|
988
|
+
function createRateLimiter(opts) {
|
|
989
|
+
return new MemoryRateLimiter(opts);
|
|
990
|
+
}
|
|
991
|
+
|
|
905
992
|
// src/index.ts
|
|
906
993
|
registerCollector("local", (config) => new GitCollector(config.source?.repoPath));
|
|
907
994
|
registerCollector("multi-repo", (config) => {
|
|
@@ -979,16 +1066,17 @@ async function runPipeline(from, to, config, options = {}) {
|
|
|
979
1066
|
const license = await validateLicense();
|
|
980
1067
|
if (!license.valid) {
|
|
981
1068
|
if (!isProviderAllowed(config.ai.provider, license)) {
|
|
982
|
-
throw new
|
|
1069
|
+
throw new CullitError(CoreErrorCode.LICENSE_INVALID, license.message || "Invalid CULLIT_API_KEY");
|
|
983
1070
|
}
|
|
984
1071
|
log.warn(`\u26A0 ${license.message || "Invalid CULLIT_API_KEY \u2014 running in free mode."}`);
|
|
985
1072
|
}
|
|
986
1073
|
if (!isProviderAllowed(config.ai.provider, license)) {
|
|
987
|
-
throw new
|
|
1074
|
+
throw new CullitError(CoreErrorCode.LICENSE_TIER_INSUFFICIENT, upgradeMessage(`AI provider "${config.ai.provider}"`));
|
|
988
1075
|
}
|
|
989
1076
|
const collectorFactory = getCollector(config.source.type);
|
|
990
1077
|
if (!collectorFactory) {
|
|
991
|
-
throw new
|
|
1078
|
+
throw new CullitError(
|
|
1079
|
+
CoreErrorCode.PIPELINE_COLLECTOR_MISSING,
|
|
992
1080
|
`Source type "${config.source.type}" is not available. ` + (config.source.type !== "local" ? "Install @cullit/licensed (private distribution) to use this source." : "Valid sources: local")
|
|
993
1081
|
);
|
|
994
1082
|
}
|
|
@@ -1000,7 +1088,7 @@ async function runPipeline(from, to, config, options = {}) {
|
|
|
1000
1088
|
log.info(`\xBB Found ${diff.commits.length} ${itemLabel}${diff.filesChanged ? `, ${diff.filesChanged} files changed` : ""}`);
|
|
1001
1089
|
if (diff.commits.length === 0) {
|
|
1002
1090
|
const source = config.source.type === "jira" ? "Jira" : config.source.type === "linear" ? "Linear" : `${from} and ${to}`;
|
|
1003
|
-
throw new
|
|
1091
|
+
throw new CullitError(CoreErrorCode.PIPELINE_NO_CHANGES, `No ${itemLabel} found from ${source}`);
|
|
1004
1092
|
}
|
|
1005
1093
|
const tickets = [];
|
|
1006
1094
|
const enrichmentSources = config.source.enrichment || [];
|
|
@@ -1033,7 +1121,8 @@ async function runPipeline(from, to, config, options = {}) {
|
|
|
1033
1121
|
log.info(`\xBB Generating with ${providerName} (${modelName})...`);
|
|
1034
1122
|
const generatorFactory = getGenerator(config.ai.provider);
|
|
1035
1123
|
if (!generatorFactory) {
|
|
1036
|
-
throw new
|
|
1124
|
+
throw new CullitError(
|
|
1125
|
+
CoreErrorCode.PIPELINE_GENERATOR_MISSING,
|
|
1037
1126
|
`AI provider "${config.ai.provider}" is not available. ` + (config.ai.provider !== "none" ? "Install @cullit/licensed (private distribution) to use AI providers." : "")
|
|
1038
1127
|
);
|
|
1039
1128
|
}
|
|
@@ -1112,6 +1201,8 @@ export {
|
|
|
1112
1201
|
AI_PROVIDERS,
|
|
1113
1202
|
AUDIENCES,
|
|
1114
1203
|
CHANGE_CATEGORIES,
|
|
1204
|
+
CoreErrorCode,
|
|
1205
|
+
CullitError,
|
|
1115
1206
|
DEFAULT_CATEGORIES,
|
|
1116
1207
|
DEFAULT_MODELS,
|
|
1117
1208
|
ENRICHMENT_TYPES,
|
|
@@ -1127,6 +1218,7 @@ export {
|
|
|
1127
1218
|
VERSION,
|
|
1128
1219
|
analyzeReleaseReadiness,
|
|
1129
1220
|
createLogger,
|
|
1221
|
+
createRateLimiter,
|
|
1130
1222
|
escapeHtml,
|
|
1131
1223
|
fetchWithTimeout,
|
|
1132
1224
|
formatNotes,
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cullit/core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.18.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Core engine for Cullit — AI-powered release note generation.",
|
|
6
|
-
"license": "
|
|
6
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
7
7
|
"author": "Cullit <matt@cullit.io>",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"access": "public"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@cullit/config": "1.
|
|
41
|
+
"@cullit/config": "1.18.1"
|
|
42
42
|
},
|
|
43
43
|
"scripts": {
|
|
44
44
|
"build": "tsup src/index.ts --format esm --dts --clean",
|