@entelligentsia/forgecli 1.0.2 → 1.0.3
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/CHANGELOG.md +23 -0
- package/dist/CHANGELOG-forge-plugin.md +24 -0
- package/dist/extensions/forgecli/audience-gate.js +1 -1
- package/dist/extensions/forgecli/audience-gate.js.map +1 -1
- package/dist/extensions/forgecli/fix-bug.d.ts +1 -2
- package/dist/extensions/forgecli/fix-bug.js +678 -609
- package/dist/extensions/forgecli/fix-bug.js.map +1 -1
- package/dist/extensions/forgecli/forge-artifact-tool.js +15 -3
- package/dist/extensions/forgecli/forge-artifact-tool.js.map +1 -1
- package/dist/extensions/forgecli/forge-subagent.d.ts +17 -0
- package/dist/extensions/forgecli/forge-subagent.js +31 -12
- package/dist/extensions/forgecli/forge-subagent.js.map +1 -1
- package/dist/extensions/forgecli/forge-tools.d.ts +6 -0
- package/dist/extensions/forgecli/forge-tools.js +69 -6
- package/dist/extensions/forgecli/forge-tools.js.map +1 -1
- package/dist/extensions/forgecli/run-task.js +461 -391
- package/dist/extensions/forgecli/run-task.js.map +1 -1
- package/dist/extensions/forgecli/session-registry.d.ts +12 -0
- package/dist/extensions/forgecli/session-registry.js +23 -0
- package/dist/extensions/forgecli/session-registry.js.map +1 -1
- package/dist/extensions/forgecli/subagent/caller-context.d.ts +35 -11
- package/dist/extensions/forgecli/subagent/caller-context.js +49 -21
- package/dist/extensions/forgecli/subagent/caller-context.js.map +1 -1
- package/dist/extensions/forgecli/subagent/orchestrator-transcript.d.ts +66 -0
- package/dist/extensions/forgecli/subagent/orchestrator-transcript.js +66 -0
- package/dist/extensions/forgecli/subagent/orchestrator-transcript.js.map +1 -0
- package/dist/extensions/forgecli/subagent/phase-guard.d.ts +34 -0
- package/dist/extensions/forgecli/subagent/phase-guard.js +139 -0
- package/dist/extensions/forgecli/subagent/phase-guard.js.map +1 -0
- package/dist/extensions/forgecli/subagent/phase-summary-map.d.ts +1 -0
- package/dist/extensions/forgecli/subagent/phase-summary-map.js +22 -0
- package/dist/extensions/forgecli/subagent/phase-summary-map.js.map +1 -0
- package/dist/extensions/forgecli/thread-switcher.js +2 -2
- package/dist/extensions/forgecli/thread-switcher.js.map +1 -1
- package/dist/extensions/forgecli/viewport-events.d.ts +4 -0
- package/dist/extensions/forgecli/viewport-events.js +18 -1
- package/dist/extensions/forgecli/viewport-events.js.map +1 -1
- package/dist/extensions/forgecli/viewport-renderer.d.ts +12 -2
- package/dist/extensions/forgecli/viewport-renderer.js +8 -6
- package/dist/extensions/forgecli/viewport-renderer.js.map +1 -1
- package/dist/forge-payload/.base-pack/workflows/fix_bug.md +10 -28
- package/dist/forge-payload/.base-pack/workflows/triage.md +190 -0
- package/dist/forge-payload/.claude-plugin/plugin.json +1 -1
- package/dist/forge-payload/.schemas/enum-catalog.json +1 -1
- package/dist/forge-payload/.schemas/migrations.json +9 -0
- package/dist/forge-payload/integrity.json +3 -3
- package/dist/forge-payload/meta/fragments/tool-discipline.md +21 -2
- package/dist/forge-payload/meta/workflows/meta-bug-triage.md +210 -0
- package/dist/forge-payload/meta/workflows/meta-fix-bug.md +10 -28
- package/dist/forge-payload/schemas/enum-catalog.json +1 -1
- package/dist/forge-payload/schemas/structure-manifest.json +20 -1
- package/dist/forge-payload/tools/artifact.cjs +34 -5
- package/node_modules/@entelligentsia/forge-compress/dist/compressor.d.ts +6 -0
- package/node_modules/@entelligentsia/forge-compress/dist/compressor.js +137 -0
- package/node_modules/@entelligentsia/forge-compress/dist/entropy.d.ts +3 -0
- package/node_modules/@entelligentsia/forge-compress/dist/entropy.js +99 -0
- package/node_modules/@entelligentsia/forge-compress/dist/forge/entity.d.ts +8 -0
- package/node_modules/@entelligentsia/forge-compress/dist/forge/entity.js +149 -0
- package/node_modules/@entelligentsia/forge-compress/dist/forge/index.d.ts +7 -0
- package/node_modules/@entelligentsia/forge-compress/dist/forge/index.js +4 -0
- package/node_modules/@entelligentsia/forge-compress/dist/forge/markdown.d.ts +5 -0
- package/node_modules/@entelligentsia/forge-compress/dist/forge/markdown.js +92 -0
- package/node_modules/@entelligentsia/forge-compress/dist/forge/query.d.ts +7 -0
- package/node_modules/@entelligentsia/forge-compress/dist/forge/query.js +60 -0
- package/node_modules/@entelligentsia/forge-compress/dist/forge/validate.d.ts +1 -0
- package/node_modules/@entelligentsia/forge-compress/dist/forge/validate.js +82 -0
- package/node_modules/@entelligentsia/forge-compress/dist/index.d.ts +6 -0
- package/node_modules/@entelligentsia/forge-compress/dist/index.js +5 -0
- package/node_modules/@entelligentsia/forge-compress/dist/progressive.d.ts +1 -0
- package/node_modules/@entelligentsia/forge-compress/dist/progressive.js +108 -0
- package/node_modules/@entelligentsia/forge-compress/dist/strip.d.ts +4 -0
- package/node_modules/@entelligentsia/forge-compress/dist/strip.js +55 -0
- package/node_modules/@entelligentsia/forge-compress/dist/tokens.d.ts +2 -0
- package/node_modules/@entelligentsia/forge-compress/dist/tokens.js +17 -0
- package/node_modules/@entelligentsia/forge-compress/package.json +45 -0
- package/node_modules/@entelligentsia/forge-compress/src/__tests__/compress.test.ts +409 -0
- package/node_modules/@entelligentsia/forge-compress/src/compressor.ts +147 -0
- package/node_modules/@entelligentsia/forge-compress/src/entropy.ts +105 -0
- package/node_modules/@entelligentsia/forge-compress/src/forge/entity.ts +184 -0
- package/node_modules/@entelligentsia/forge-compress/src/forge/index.ts +10 -0
- package/node_modules/@entelligentsia/forge-compress/src/forge/markdown.ts +122 -0
- package/node_modules/@entelligentsia/forge-compress/src/forge/query.ts +105 -0
- package/node_modules/@entelligentsia/forge-compress/src/forge/validate.ts +86 -0
- package/node_modules/@entelligentsia/forge-compress/src/index.ts +22 -0
- package/node_modules/@entelligentsia/forge-compress/src/progressive.ts +123 -0
- package/node_modules/@entelligentsia/forge-compress/src/strip.ts +58 -0
- package/node_modules/@entelligentsia/forge-compress/src/tokens.ts +19 -0
- package/package.json +5 -10
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
export function compressValidateStore(input) {
|
|
2
|
+
let report;
|
|
3
|
+
try {
|
|
4
|
+
report = JSON.parse(input);
|
|
5
|
+
}
|
|
6
|
+
catch {
|
|
7
|
+
return compressPlainValidation(input);
|
|
8
|
+
}
|
|
9
|
+
const errors = (report.errors ?? []);
|
|
10
|
+
const warnings = (report.warnings ?? []);
|
|
11
|
+
const fixes = (report.fixes ?? []);
|
|
12
|
+
const summary = report.summary;
|
|
13
|
+
const parts = [];
|
|
14
|
+
if (summary) {
|
|
15
|
+
const counts = Object.entries(summary)
|
|
16
|
+
.filter(([, v]) => v > 0)
|
|
17
|
+
.map(([k, v]) => `${v} ${k}`)
|
|
18
|
+
.join(", ");
|
|
19
|
+
if (counts)
|
|
20
|
+
parts.push(counts);
|
|
21
|
+
}
|
|
22
|
+
if (errors.length > 0) {
|
|
23
|
+
const grouped = groupBy(errors, "category");
|
|
24
|
+
for (const [cat, items] of Object.entries(grouped)) {
|
|
25
|
+
parts.push(`ERR ${cat} (${items.length}):`);
|
|
26
|
+
for (const item of items.slice(0, 5)) {
|
|
27
|
+
parts.push(` ${item.id}: ${item.message}`);
|
|
28
|
+
}
|
|
29
|
+
if (items.length > 5)
|
|
30
|
+
parts.push(` ... +${items.length - 5} more`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (warnings.length > 0) {
|
|
34
|
+
const grouped = groupBy(warnings, "category");
|
|
35
|
+
for (const [cat, items] of Object.entries(grouped)) {
|
|
36
|
+
parts.push(`WARN ${cat} (${items.length}):`);
|
|
37
|
+
for (const item of items.slice(0, 3)) {
|
|
38
|
+
parts.push(` ${item.id}: ${item.message}`);
|
|
39
|
+
}
|
|
40
|
+
if (items.length > 3)
|
|
41
|
+
parts.push(` ... +${items.length - 3} more`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (fixes.length > 0) {
|
|
45
|
+
const applied = fixes.filter((f) => f.applied).length;
|
|
46
|
+
parts.push(`${fixes.length} fixes (${applied} applied)`);
|
|
47
|
+
}
|
|
48
|
+
if (parts.length === 0)
|
|
49
|
+
return "ok (store valid)";
|
|
50
|
+
return parts.join("\n");
|
|
51
|
+
}
|
|
52
|
+
function compressPlainValidation(input) {
|
|
53
|
+
const lines = input.split("\n").filter((l) => l.trim());
|
|
54
|
+
if (lines.length <= 10)
|
|
55
|
+
return input;
|
|
56
|
+
const errors = lines.filter((l) => l.includes("ERROR"));
|
|
57
|
+
const warns = lines.filter((l) => l.includes("WARN"));
|
|
58
|
+
const fixed = lines.filter((l) => l.includes("FIXED"));
|
|
59
|
+
const parts = [];
|
|
60
|
+
if (errors.length > 0) {
|
|
61
|
+
parts.push(`${errors.length} errors:`);
|
|
62
|
+
for (const e of errors.slice(0, 5))
|
|
63
|
+
parts.push(` ${e.trim()}`);
|
|
64
|
+
if (errors.length > 5)
|
|
65
|
+
parts.push(` ... +${errors.length - 5} more`);
|
|
66
|
+
}
|
|
67
|
+
if (warns.length > 0)
|
|
68
|
+
parts.push(`${warns.length} warnings`);
|
|
69
|
+
if (fixed.length > 0)
|
|
70
|
+
parts.push(`${fixed.length} fixed`);
|
|
71
|
+
return parts.length > 0 ? parts.join("\n") : input;
|
|
72
|
+
}
|
|
73
|
+
function groupBy(items, key) {
|
|
74
|
+
const result = {};
|
|
75
|
+
for (const item of items) {
|
|
76
|
+
const k = String(item[key] ?? "unknown");
|
|
77
|
+
if (!result[k])
|
|
78
|
+
result[k] = [];
|
|
79
|
+
result[k].push(item);
|
|
80
|
+
}
|
|
81
|
+
return result;
|
|
82
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { compressStoreQuery, compressEntity, compressEntityList, compressMarkdown, compressValidateStore, } from "./forge/index.js";
|
|
2
|
+
export type { CompressQueryOptions, CompressEntityOptions, CompressMarkdownOptions, } from "./forge/index.js";
|
|
3
|
+
export { countTokens, truncateToTokenBudget } from "./tokens.js";
|
|
4
|
+
export { compressIb } from "./entropy.js";
|
|
5
|
+
export { compressProgressive } from "./progressive.js";
|
|
6
|
+
export { lightweightCleanup, verbatimCompact, stripAnsi, } from "./compressor.js";
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { compressStoreQuery, compressEntity, compressEntityList, compressMarkdown, compressValidateStore, } from "./forge/index.js";
|
|
2
|
+
export { countTokens, truncateToTokenBudget } from "./tokens.js";
|
|
3
|
+
export { compressIb } from "./entropy.js";
|
|
4
|
+
export { compressProgressive } from "./progressive.js";
|
|
5
|
+
export { lightweightCleanup, verbatimCompact, stripAnsi, } from "./compressor.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function compressProgressive(segments: string[], budgetTokens: number): string[];
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { truncateToTokenBudget } from "./tokens.js";
|
|
2
|
+
const LAMBDA = 1.35;
|
|
3
|
+
function expWeights(n) {
|
|
4
|
+
return Array.from({ length: n }, (_, i) => Math.exp(LAMBDA * i));
|
|
5
|
+
}
|
|
6
|
+
function allocateBudget(budgetTokens, weights) {
|
|
7
|
+
const n = weights.length;
|
|
8
|
+
if (n === 0 || budgetTokens === 0)
|
|
9
|
+
return new Array(n).fill(0);
|
|
10
|
+
const sumW = Math.max(weights.reduce((a, b) => a + b, 0), Number.EPSILON);
|
|
11
|
+
const base = new Array(n).fill(0);
|
|
12
|
+
const frac = new Array(n).fill(0);
|
|
13
|
+
for (let i = 0; i < n; i++) {
|
|
14
|
+
const exact = (budgetTokens * weights[i]) / sumW;
|
|
15
|
+
base[i] = Math.floor(exact);
|
|
16
|
+
frac[i] = exact - base[i];
|
|
17
|
+
}
|
|
18
|
+
const given = base.reduce((a, b) => a + b, 0);
|
|
19
|
+
const order = Array.from({ length: n }, (_, i) => i).sort((a, b) => frac[b] - frac[a]);
|
|
20
|
+
let extra = budgetTokens - given;
|
|
21
|
+
for (const i of order) {
|
|
22
|
+
if (extra <= 0)
|
|
23
|
+
break;
|
|
24
|
+
base[i]++;
|
|
25
|
+
extra--;
|
|
26
|
+
}
|
|
27
|
+
return base;
|
|
28
|
+
}
|
|
29
|
+
function tierForIndex(i, n) {
|
|
30
|
+
if (n <= 1)
|
|
31
|
+
return 2;
|
|
32
|
+
const r = i / (n - 1);
|
|
33
|
+
if (r < 1 / 3)
|
|
34
|
+
return 0;
|
|
35
|
+
if (r < 2 / 3)
|
|
36
|
+
return 1;
|
|
37
|
+
return 2;
|
|
38
|
+
}
|
|
39
|
+
const STRUCTURE_KEYWORDS = [
|
|
40
|
+
"fn ",
|
|
41
|
+
"pub ",
|
|
42
|
+
"struct ",
|
|
43
|
+
"enum ",
|
|
44
|
+
"trait ",
|
|
45
|
+
"impl ",
|
|
46
|
+
"mod ",
|
|
47
|
+
"use ",
|
|
48
|
+
"def ",
|
|
49
|
+
"class ",
|
|
50
|
+
"function ",
|
|
51
|
+
"export ",
|
|
52
|
+
"import ",
|
|
53
|
+
"const ",
|
|
54
|
+
"interface ",
|
|
55
|
+
];
|
|
56
|
+
function mapLike(s, maxTokens) {
|
|
57
|
+
const picked = [];
|
|
58
|
+
const lines = s.split("\n");
|
|
59
|
+
for (let i = 0; i < lines.length; i++) {
|
|
60
|
+
if (i === 0 || STRUCTURE_KEYWORDS.some((k) => lines[i].includes(k))) {
|
|
61
|
+
picked.push(lines[i]);
|
|
62
|
+
}
|
|
63
|
+
if (picked.length >= 48)
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
if (picked.length === 0 && lines.length > 0) {
|
|
67
|
+
picked.push(lines[0]);
|
|
68
|
+
}
|
|
69
|
+
return truncateToTokenBudget(picked.join("\n"), Math.max(4, maxTokens));
|
|
70
|
+
}
|
|
71
|
+
function oneLineSummary(segIdx, s, maxTokens) {
|
|
72
|
+
const preview = s.split("\n")[0]?.slice(0, 120) ?? "";
|
|
73
|
+
const lineCount = s.split("\n").length;
|
|
74
|
+
const draft = `// seg[${segIdx}] ${lineCount} lines, ${s.length} chars | ${preview}`;
|
|
75
|
+
return truncateToTokenBudget(draft, Math.max(8, maxTokens));
|
|
76
|
+
}
|
|
77
|
+
export function compressProgressive(segments, budgetTokens) {
|
|
78
|
+
const n = segments.length;
|
|
79
|
+
if (n === 0)
|
|
80
|
+
return [];
|
|
81
|
+
if (budgetTokens === 0)
|
|
82
|
+
return segments.map(() => "");
|
|
83
|
+
const weights = expWeights(n);
|
|
84
|
+
const allocs = allocateBudget(budgetTokens, weights);
|
|
85
|
+
const out = [];
|
|
86
|
+
for (let i = 0; i < n; i++) {
|
|
87
|
+
const alloc = allocs[i];
|
|
88
|
+
const tier = tierForIndex(i, n);
|
|
89
|
+
if (alloc === 0) {
|
|
90
|
+
out.push("");
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
let compressed;
|
|
94
|
+
switch (tier) {
|
|
95
|
+
case 2:
|
|
96
|
+
compressed = truncateToTokenBudget(segments[i], alloc);
|
|
97
|
+
break;
|
|
98
|
+
case 1:
|
|
99
|
+
compressed = mapLike(segments[i], alloc);
|
|
100
|
+
break;
|
|
101
|
+
default:
|
|
102
|
+
compressed = oneLineSummary(i, segments[i], alloc);
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
out.push(truncateToTokenBudget(compressed, alloc));
|
|
106
|
+
}
|
|
107
|
+
return out;
|
|
108
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const ANSI_RE = /\x1b\[[0-9;]*[a-zA-Z]/g;
|
|
2
|
+
export function stripAnsi(s) {
|
|
3
|
+
if (!s.includes("\x1b"))
|
|
4
|
+
return s;
|
|
5
|
+
return s.replace(ANSI_RE, "");
|
|
6
|
+
}
|
|
7
|
+
const TS_RE = /\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})?/g;
|
|
8
|
+
const HASH_RE = /\b[0-9a-f]{32,64}\b/g;
|
|
9
|
+
export function stripTimestampsAndHashes(line) {
|
|
10
|
+
return line.replace(TS_RE, "[TS]").replace(HASH_RE, "[HASH]");
|
|
11
|
+
}
|
|
12
|
+
export function normalizeWhitespace(line) {
|
|
13
|
+
let result = "";
|
|
14
|
+
let prevSpace = false;
|
|
15
|
+
for (const ch of line) {
|
|
16
|
+
if (ch === " " || ch === "\t") {
|
|
17
|
+
if (!prevSpace) {
|
|
18
|
+
result += " ";
|
|
19
|
+
prevSpace = true;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
result += ch;
|
|
24
|
+
prevSpace = false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
const BOILERPLATE_PREFIXES = [
|
|
30
|
+
"copyright",
|
|
31
|
+
"licensed under",
|
|
32
|
+
"license:",
|
|
33
|
+
"all rights reserved",
|
|
34
|
+
"generated by",
|
|
35
|
+
"auto-generated",
|
|
36
|
+
];
|
|
37
|
+
const SEPARATOR_CHARS = new Set(["=", "-", "*", "─", "━"]);
|
|
38
|
+
export function isBoilerplate(line) {
|
|
39
|
+
const lower = line.toLowerCase();
|
|
40
|
+
if (BOILERPLATE_PREFIXES.some((p) => lower.startsWith(p)))
|
|
41
|
+
return true;
|
|
42
|
+
if (line.length >= 4) {
|
|
43
|
+
const first = line[0];
|
|
44
|
+
if (SEPARATOR_CHARS.has(first)) {
|
|
45
|
+
let same = 0;
|
|
46
|
+
for (const c of line) {
|
|
47
|
+
if (c === first)
|
|
48
|
+
same++;
|
|
49
|
+
}
|
|
50
|
+
if (same / line.length > 0.8)
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const CHARS_PER_TOKEN = 3.7;
|
|
2
|
+
export function countTokens(text) {
|
|
3
|
+
if (!text)
|
|
4
|
+
return 0;
|
|
5
|
+
return Math.ceil(text.length / CHARS_PER_TOKEN);
|
|
6
|
+
}
|
|
7
|
+
export function truncateToTokenBudget(text, maxTokens) {
|
|
8
|
+
if (maxTokens <= 0)
|
|
9
|
+
return "";
|
|
10
|
+
if (countTokens(text) <= maxTokens)
|
|
11
|
+
return text;
|
|
12
|
+
const charBudget = Math.floor(maxTokens * CHARS_PER_TOKEN);
|
|
13
|
+
const truncated = text.slice(0, charBudget);
|
|
14
|
+
const lastNewline = truncated.lastIndexOf("\n");
|
|
15
|
+
const clean = lastNewline > 0 ? truncated.slice(0, lastNewline) : truncated;
|
|
16
|
+
return `${clean} …`;
|
|
17
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@entelligentsia/forge-compress",
|
|
3
|
+
"repository": {
|
|
4
|
+
"type": "git",
|
|
5
|
+
"url": "https://github.com/Entelligentsia/forge-compress.git"
|
|
6
|
+
},
|
|
7
|
+
"version": "0.1.0",
|
|
8
|
+
"description": "Token-efficient compression for Forge SDLC tool outputs — store queries, entities, artifacts",
|
|
9
|
+
"type": "module",
|
|
10
|
+
"main": "dist/index.js",
|
|
11
|
+
"types": "dist/index.d.ts",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"import": "./dist/index.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"src"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsc",
|
|
24
|
+
"test": "vitest run",
|
|
25
|
+
"test:watch": "vitest",
|
|
26
|
+
"lint": "biome check src/",
|
|
27
|
+
"prepublishOnly": "npm run build"
|
|
28
|
+
},
|
|
29
|
+
"keywords": [
|
|
30
|
+
"compression",
|
|
31
|
+
"forge",
|
|
32
|
+
"sdlc",
|
|
33
|
+
"ai-agent",
|
|
34
|
+
"token-reduction"
|
|
35
|
+
],
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"typescript": "^5.7.0",
|
|
39
|
+
"vitest": "^3.0.0",
|
|
40
|
+
"@biomejs/biome": "^1.9.0"
|
|
41
|
+
},
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=18"
|
|
44
|
+
}
|
|
45
|
+
}
|