@lemoncode/lemony 0.4.0 → 0.4.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/catalog/VERSION +1 -1
- package/catalog/agents/spec-author.md +3 -1
- package/catalog/skills/prd-to-spec/SKILL.md +20 -0
- package/dist/cli.mjs +75 -39
- package/package.json +6 -6
package/catalog/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.4.
|
|
1
|
+
0.4.1
|
|
@@ -44,7 +44,9 @@ the task branch before invoking you, so you are handed a real `<id>` from the st
|
|
|
44
44
|
acceptance criteria. Always include the unwanted-behavior (`If … then …`) paths.
|
|
45
45
|
- `design.md` — files, functions/interfaces, approach, edge cases, testing.
|
|
46
46
|
- `tasks.md` — atomic, ordered checkboxes (vertical slices for TDD), each
|
|
47
|
-
referencing the requirements it satisfies
|
|
47
|
+
referencing the requirements it satisfies as a comma-separated `(R<n>, R<m>)`
|
|
48
|
+
list that ends a line (rule stated in `prd-to-spec`; a validator reads it),
|
|
49
|
+
grouped under **risk-sized step
|
|
48
50
|
headers** (grouping criterion in `prd-to-spec`) — in step-by-step mode the
|
|
49
51
|
loop runs one implement→review→checkpoint cycle per group, and the human
|
|
50
52
|
approves the grouping with the rest of the spec.
|
|
@@ -193,8 +193,28 @@ of the spec. Grouping criterion:
|
|
|
193
193
|
## Group 2 — <name> _(<one-line boundary rationale>)_ [risk: data-loss]
|
|
194
194
|
|
|
195
195
|
- [ ] T3 — <error path> (R2, R3)
|
|
196
|
+
- [ ] **T4 — <a task that needs detail>.**
|
|
197
|
+
<detail prose, as many indented lines as it needs, with the ref list
|
|
198
|
+
ending the last one> (R3, R4)
|
|
196
199
|
```
|
|
197
200
|
|
|
201
|
+
**Ref list — one rule, read by a script.** A task's requirement refs are one
|
|
202
|
+
comma-separated `(R<n>, R<m>)` group that **ends a line**: the end of the task's first
|
|
203
|
+
line, or the end of one continuation line (a line of its own, or the end of a detail
|
|
204
|
+
line). Nothing after the closing paren on that line, not even a period. No ranges —
|
|
205
|
+
`(R1–R4)` is not a list, write `(R1, R2, R3, R4)` — and no prose inside the parens.
|
|
206
|
+
|
|
207
|
+
**Mentions.** A `(R<n>` on the task's first line, or right after the title's bold
|
|
208
|
+
close, is always read as the declaration: it is reported if it does not end the line,
|
|
209
|
+
even when a list ends a later line. Keep every other `(R<n>)` mention in detail prose
|
|
210
|
+
**mid-line**. When two lists end continuation lines the validator cannot tell which is
|
|
211
|
+
the declaration and reports the task; on a task that declares no list, the one
|
|
212
|
+
line-ending mention is read as its declaration, and a mid-line one is reported.
|
|
213
|
+
|
|
214
|
+
The review evidence ledger's validator enumerates each group's review slice from these
|
|
215
|
+
refs; a task it cannot read is reported as `malformed-task-refs` — a spec defect that
|
|
216
|
+
pauses the loop, never read as "declares none".
|
|
217
|
+
|
|
198
218
|
Rules: order so the first task is a tracer bullet; never "write all tests" then
|
|
199
219
|
"write all code"; keep each task small enough to verify on its own. Grouping never
|
|
200
220
|
changes task granularity — checkboxes stay atomic and TDD runs per task; only review
|
package/dist/cli.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import { basename, delimiter, dirname, extname, join, relative, resolve, sep } f
|
|
|
5
5
|
import { argv, cwd, env, exit, stderr, stdin, stdout } from "node:process";
|
|
6
6
|
import { createInterface } from "node:readline/promises";
|
|
7
7
|
import { promisify } from "node:util";
|
|
8
|
-
import {
|
|
8
|
+
import { parse, parseDocument } from "yaml";
|
|
9
9
|
import { z } from "zod";
|
|
10
10
|
import { createHash, randomBytes } from "node:crypto";
|
|
11
11
|
import { constants } from "node:fs";
|
|
@@ -230,8 +230,6 @@ const setConfigValues = (rawYaml, updates) => {
|
|
|
230
230
|
}
|
|
231
231
|
for (const key of DEPRECATED_CONFIG_KEYS) if (doc.has(key)) doc.delete(key);
|
|
232
232
|
for (const key of DEPRECATED_PATHS_KEYS) if (doc.hasIn(["paths", key])) doc.deleteIn(["paths", key]);
|
|
233
|
-
const pathsNode = doc.get("paths", true);
|
|
234
|
-
if (isMap(pathsNode) && pathsNode.items.length === 0) doc.delete("paths");
|
|
235
233
|
for (const key of DEPRECATED_IMPLEMENTATION_KEYS) if (doc.hasIn(["implementation", key])) doc.deleteIn(["implementation", key]);
|
|
236
234
|
return doc.toString();
|
|
237
235
|
};
|
|
@@ -2105,8 +2103,9 @@ const RISK_MARKER_ALL = /\[risk:/gi;
|
|
|
2105
2103
|
const RISK_MARKER_LOOKALIKE = /\[\s*risks?\s*:/i;
|
|
2106
2104
|
const TASK_LINE = /^\s*[-*+]\s*\[[ xX]\]\s*(?:\*\*|__|`)?\s*(T\d+)\b/;
|
|
2107
2105
|
const TASK_REFS = /^\(((?:R\d+)(?:\s*,\s*R\d+)*)\)/;
|
|
2108
|
-
const
|
|
2109
|
-
const
|
|
2106
|
+
const TASK_REFS_LOOKALIKE_ALL = /\(\s*R\d+/g;
|
|
2107
|
+
const TASK_TITLE_WRAPPER = /^\s*[-*+]\s*\[[ xX]\]\s*(\*\*|__)/;
|
|
2108
|
+
const TASK_REFS_AFTER_TITLE_CLOSE = /^\s*(\(\s*R\d+)/;
|
|
2110
2109
|
const REQUIREMENT_LINE = /^\s*-\s*\*\*(R\d+)\*\*/;
|
|
2111
2110
|
const TEST_FILE = /(^|\/)__tests__\/|\.(spec|test)\.[cm]?[jt]sx?$/;
|
|
2112
2111
|
//#endregion
|
|
@@ -2209,7 +2208,7 @@ const parseTaskBlock = (id, parts) => {
|
|
|
2209
2208
|
id,
|
|
2210
2209
|
requirementRefs: [...new Set(requirementRefs)]
|
|
2211
2210
|
},
|
|
2212
|
-
malformedRefs: refs === null &&
|
|
2211
|
+
malformedRefs: refs === null && hasRefLookalike(parts)
|
|
2213
2212
|
};
|
|
2214
2213
|
};
|
|
2215
2214
|
const declarationAt = (parts, block, opener) => {
|
|
@@ -2230,17 +2229,40 @@ const declarationClosesLine = (parts, end) => {
|
|
|
2230
2229
|
return /^(?:\*\*|__)/.test(tail) && lineEnds.has(end + 2);
|
|
2231
2230
|
};
|
|
2232
2231
|
const refsOpener = (parts, block) => {
|
|
2233
|
-
const
|
|
2234
|
-
const
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2232
|
+
const first = parts[0] ?? "";
|
|
2233
|
+
const attempted = [];
|
|
2234
|
+
for (const lookalike of first.matchAll(TASK_REFS_LOOKALIKE_ALL)) {
|
|
2235
|
+
if (isLinkTarget(first, lookalike.index)) continue;
|
|
2236
|
+
attempted.push(lookalike.index);
|
|
2237
|
+
break;
|
|
2238
|
+
}
|
|
2239
|
+
const afterTitle = afterTitleClose(first, block);
|
|
2240
|
+
if (afterTitle !== void 0) attempted.push(afterTitle);
|
|
2241
|
+
if (attempted.length > 0) return Math.min(...attempted);
|
|
2242
|
+
const closing = [];
|
|
2243
|
+
let offset = first.length + 1;
|
|
2239
2244
|
for (const part of parts.slice(1)) {
|
|
2240
|
-
|
|
2245
|
+
for (const lookalike of part.matchAll(TASK_REFS_LOOKALIKE_ALL)) {
|
|
2246
|
+
if (isLinkTarget(part, lookalike.index)) continue;
|
|
2247
|
+
const at = offset + lookalike.index;
|
|
2248
|
+
if (declarationAt(parts, block, at) !== null) closing.push(at);
|
|
2249
|
+
}
|
|
2241
2250
|
offset += part.length + 1;
|
|
2242
2251
|
}
|
|
2243
|
-
return
|
|
2252
|
+
return closing.length === 1 ? closing[0] : void 0;
|
|
2253
|
+
};
|
|
2254
|
+
const hasRefLookalike = (parts) => parts.some((part) => [...part.matchAll(TASK_REFS_LOOKALIKE_ALL)].some((lookalike) => !isLinkTarget(part, lookalike.index)));
|
|
2255
|
+
const isLinkTarget = (line, index) => line[index - 1] === "]";
|
|
2256
|
+
const afterTitleClose = (first, block) => {
|
|
2257
|
+
const wrapper = TASK_TITLE_WRAPPER.exec(first);
|
|
2258
|
+
const marker = wrapper?.[1];
|
|
2259
|
+
if (!wrapper || marker === void 0) return void 0;
|
|
2260
|
+
const close = block.indexOf(marker, wrapper[0].length);
|
|
2261
|
+
if (close < 0) return void 0;
|
|
2262
|
+
const tail = block.slice(close + marker.length);
|
|
2263
|
+
const opener = TASK_REFS_AFTER_TITLE_CLOSE.exec(tail);
|
|
2264
|
+
if (!opener) return void 0;
|
|
2265
|
+
return close + marker.length + opener[0].indexOf("(");
|
|
2244
2266
|
};
|
|
2245
2267
|
const parseRiskMarker = (line) => {
|
|
2246
2268
|
const marker = RISK_MARKER.exec(line);
|
|
@@ -2381,7 +2403,7 @@ const runLedgerValidate = async (inputs) => {
|
|
|
2381
2403
|
});
|
|
2382
2404
|
for (const id of spec.malformedTaskRefs) problems.push({
|
|
2383
2405
|
kind: "malformed-task-refs",
|
|
2384
|
-
message: `${id} carries a "(R<n>"-shaped
|
|
2406
|
+
message: `${id} carries a "(R<n>"-shaped ref list that is not a declaration: a declaration is one comma-separated "(R1, R2)" group that ends a line — no ranges ("R1–R3"), nothing after the closing paren on that line — at the end of the task's first line, right after the title's bold close, or the only group ending a continuation line. Left as-is it reads as a task that references no requirement, and its slice shrinks to itself.`,
|
|
2385
2407
|
subject: id
|
|
2386
2408
|
});
|
|
2387
2409
|
const groups = resolveGroups(spec.groups, address, problems);
|
|
@@ -3039,7 +3061,8 @@ const prunePrefix = async (repoRoot, options = {}) => {
|
|
|
3039
3061
|
}
|
|
3040
3062
|
if (sentOffset > size) return { prunedBytes: 0 };
|
|
3041
3063
|
const stateDir = stateDirOf(repoRoot);
|
|
3042
|
-
const
|
|
3064
|
+
const id = `${process.pid}-${randomBytes(4).toString("hex")}`;
|
|
3065
|
+
const drainPath = join(stateDir, `${DRAINING_BASENAME}.${id}`);
|
|
3043
3066
|
const metaPath = `${drainPath}.meta`;
|
|
3044
3067
|
await mkdir(stateDir, { recursive: true });
|
|
3045
3068
|
await writeFile(metaPath, JSON.stringify({ sent_offset: sentOffset }));
|
|
@@ -3080,14 +3103,15 @@ const appendRejects = async (repoRoot, rejects, now) => {
|
|
|
3080
3103
|
const path = join(repoRoot, REJECTED_RELPATH);
|
|
3081
3104
|
await mkdir(dirname(path), { recursive: true });
|
|
3082
3105
|
const ts = now().toISOString();
|
|
3083
|
-
|
|
3106
|
+
const body = rejects.map((reject) => {
|
|
3084
3107
|
const entry = {
|
|
3085
3108
|
ts,
|
|
3086
3109
|
reason: reject.reason,
|
|
3087
3110
|
line: reject.line
|
|
3088
3111
|
};
|
|
3089
3112
|
return JSON.stringify(entry);
|
|
3090
|
-
}).join("\n")
|
|
3113
|
+
}).join("\n");
|
|
3114
|
+
await appendFile(path, `${body}\n`);
|
|
3091
3115
|
return rejects.length;
|
|
3092
3116
|
};
|
|
3093
3117
|
//#endregion
|
|
@@ -3759,7 +3783,8 @@ const checkVersionPin = (deps, config) => {
|
|
|
3759
3783
|
};
|
|
3760
3784
|
const checkCliResolution = async (deps) => {
|
|
3761
3785
|
const name = "cli-resolution";
|
|
3762
|
-
|
|
3786
|
+
const localBin = join(deps.repoRoot, "node_modules", ".bin", LEMONY_BIN);
|
|
3787
|
+
if (await isExecutable(localBin)) return {
|
|
3763
3788
|
name,
|
|
3764
3789
|
status: "ok",
|
|
3765
3790
|
detail: `Telemetry CLI resolves from node_modules/.bin/${LEMONY_BIN} (the durable setup).`
|
|
@@ -4236,7 +4261,8 @@ const snapshotDestPaths = (version, bundle) => {
|
|
|
4236
4261
|
];
|
|
4237
4262
|
};
|
|
4238
4263
|
const writeSnapshot = async (repoRoot, version, bundle) => {
|
|
4239
|
-
const
|
|
4264
|
+
const root = snapshotsRootDir(repoRoot);
|
|
4265
|
+
const staging = join(root, STAGING_DIR);
|
|
4240
4266
|
await assertNoSymlinkTraversal(repoRoot, snapshotDestPaths(version, bundle));
|
|
4241
4267
|
await rm(staging, {
|
|
4242
4268
|
recursive: true,
|
|
@@ -4595,9 +4621,10 @@ const COMPANION_DOCS = [
|
|
|
4595
4621
|
"partition"
|
|
4596
4622
|
];
|
|
4597
4623
|
const renderFile = async (templatePath, relPath, vars) => {
|
|
4624
|
+
const template = await readFile(templatePath, "utf8");
|
|
4598
4625
|
return {
|
|
4599
4626
|
relPath,
|
|
4600
|
-
content: renderTemplate(
|
|
4627
|
+
content: renderTemplate(template, vars),
|
|
4601
4628
|
executable: false
|
|
4602
4629
|
};
|
|
4603
4630
|
};
|
|
@@ -4673,7 +4700,8 @@ const appendGitignoreBlock = async (repoRoot) => {
|
|
|
4673
4700
|
await writeFile(gitignorePath, next);
|
|
4674
4701
|
return ".gitignore";
|
|
4675
4702
|
}
|
|
4676
|
-
|
|
4703
|
+
const prefix = current.length === 0 ? "" : current.endsWith("\n") ? "\n" : "\n\n";
|
|
4704
|
+
await writeFile(gitignorePath, `${current}${prefix}${GITIGNORE_BLOCK}\n`);
|
|
4677
4705
|
return ".gitignore";
|
|
4678
4706
|
};
|
|
4679
4707
|
const removeGitignoreBlock = async (repoRoot) => {
|
|
@@ -4834,9 +4862,10 @@ const runInstall = async (options) => {
|
|
|
4834
4862
|
const losers = await collectLosers(repoRoot, actions);
|
|
4835
4863
|
let snapshotVersion = null;
|
|
4836
4864
|
if (losers.length > 0) {
|
|
4865
|
+
const baseline = new Map(vendorFiles.map((file) => [file.relPath, file.content]));
|
|
4837
4866
|
await writeSnapshot(repoRoot, vendorVersion, {
|
|
4838
4867
|
working: losers,
|
|
4839
|
-
baseline
|
|
4868
|
+
baseline
|
|
4840
4869
|
});
|
|
4841
4870
|
snapshotVersion = vendorVersion;
|
|
4842
4871
|
}
|
|
@@ -4893,7 +4922,8 @@ const runInstall = async (options) => {
|
|
|
4893
4922
|
};
|
|
4894
4923
|
};
|
|
4895
4924
|
const collectLosers = (repoRoot, actions) => {
|
|
4896
|
-
|
|
4925
|
+
const losingPaths = actions.filter((action) => action.kind === "pick-vendor").map((action) => action.relPath);
|
|
4926
|
+
return collectWorkingFiles(repoRoot, losingPaths);
|
|
4897
4927
|
};
|
|
4898
4928
|
//#endregion
|
|
4899
4929
|
//#region src/install/resolve-collision.ts
|
|
@@ -4932,12 +4962,14 @@ const runReconcile = async (inputs) => {
|
|
|
4932
4962
|
const fromVersion = config.vendor_version;
|
|
4933
4963
|
const target = config.target;
|
|
4934
4964
|
const taskStorageRepo = config.task_storage.repo;
|
|
4935
|
-
const
|
|
4965
|
+
const rawConfig = await readFile(join(repoRoot, HARNESS_CONFIG_FILENAME), "utf8");
|
|
4966
|
+
const predatedKeys = findPredatedKeys(rawConfig, fromVersion, toVersion, inputs.configKeySince ?? CONFIG_KEY_SINCE);
|
|
4936
4967
|
const baselineVersion = await findBaselineVersion(repoRoot);
|
|
4937
4968
|
const baseline = baselineVersion ? await readBaseline(repoRoot, baselineVersion) : /* @__PURE__ */ new Map();
|
|
4938
4969
|
const hadBaseline = baseline.size > 0;
|
|
4939
4970
|
const capabilities = await scanRepo(repoRoot);
|
|
4940
|
-
const
|
|
4971
|
+
const catalogSkills = await readCatalogSkills(vendorRoot);
|
|
4972
|
+
const skills = selectSkills(catalogSkills, capabilities);
|
|
4941
4973
|
const vendorNew = await materializeVendorFiles({
|
|
4942
4974
|
vendorRoot,
|
|
4943
4975
|
target,
|
|
@@ -4973,14 +5005,16 @@ const runReconcile = async (inputs) => {
|
|
|
4973
5005
|
predatedKeys
|
|
4974
5006
|
};
|
|
4975
5007
|
if (hadBaseline) {
|
|
5008
|
+
const working = await collectWorkingFiles(repoRoot, [...baseline.keys()]);
|
|
4976
5009
|
await writeSnapshot(repoRoot, fromVersion, {
|
|
4977
|
-
working
|
|
5010
|
+
working,
|
|
4978
5011
|
baseline
|
|
4979
5012
|
});
|
|
4980
5013
|
await rotateSnapshots(repoRoot, config.rollback.keep_snapshots);
|
|
4981
5014
|
}
|
|
4982
5015
|
await applyActions(repoRoot, actions);
|
|
4983
|
-
const
|
|
5016
|
+
const templateRoot = join(vendorRoot, "templates", target);
|
|
5017
|
+
const { lostHookCommands } = await resyncSpecialCased(repoRoot, templateRoot);
|
|
4984
5018
|
const labelSync = inputs.runCommand ? await syncLabels(taskStorageRepo, createTaskTrackerProvider(config.task_storage.type, inputs.runCommand)) : null;
|
|
4985
5019
|
await writeBaseline(repoRoot, toVersion, vendorNew);
|
|
4986
5020
|
await writeConfigValues(repoRoot, configBumps);
|
|
@@ -5014,7 +5048,8 @@ const applyActions = async (repoRoot, actions) => {
|
|
|
5014
5048
|
default: return;
|
|
5015
5049
|
}
|
|
5016
5050
|
}));
|
|
5017
|
-
|
|
5051
|
+
const prunedPaths = actions.filter((action) => action.kind === "prune").map((action) => action.relPath);
|
|
5052
|
+
await pruneEmptyDirs(repoRoot, prunedPaths);
|
|
5018
5053
|
};
|
|
5019
5054
|
const summarize = (actions) => {
|
|
5020
5055
|
const conflictedFiles = [];
|
|
@@ -5044,12 +5079,10 @@ const summarize = (actions) => {
|
|
|
5044
5079
|
winner: "vendor"
|
|
5045
5080
|
});
|
|
5046
5081
|
break;
|
|
5047
|
-
case "pick-client":
|
|
5048
|
-
|
|
5049
|
-
|
|
5050
|
-
|
|
5051
|
-
});
|
|
5052
|
-
break;
|
|
5082
|
+
case "pick-client": pickedFiles.push({
|
|
5083
|
+
relPath: action.relPath,
|
|
5084
|
+
winner: "client"
|
|
5085
|
+
});
|
|
5053
5086
|
}
|
|
5054
5087
|
return {
|
|
5055
5088
|
conflictedFiles,
|
|
@@ -5155,11 +5188,12 @@ const runRollback = async (options) => {
|
|
|
5155
5188
|
await assertNoSymlinkTraversal(repoRoot, [...restorePaths, ...removePaths]);
|
|
5156
5189
|
await Promise.all(bundle.working.map((file) => writeManaged(repoRoot, file.relPath, file.content, file.executable)));
|
|
5157
5190
|
await Promise.all(removePaths.map((path) => rm(join(repoRoot, path), { force: true })));
|
|
5158
|
-
|
|
5191
|
+
const baselineFiles = [...bundle.baseline].map(([relPath, content]) => ({
|
|
5159
5192
|
relPath,
|
|
5160
5193
|
content,
|
|
5161
5194
|
executable: false
|
|
5162
|
-
}))
|
|
5195
|
+
}));
|
|
5196
|
+
await writeBaseline(repoRoot, target, baselineFiles);
|
|
5163
5197
|
await writeConfigValues(repoRoot, { vendor_version: target });
|
|
5164
5198
|
return {
|
|
5165
5199
|
repoRoot,
|
|
@@ -5700,16 +5734,18 @@ const telemetryEnable = async (repoRoot) => {
|
|
|
5700
5734
|
for (const line of formatTelemetryStatus(report)) console.log(line);
|
|
5701
5735
|
};
|
|
5702
5736
|
const telemetrySend = async (repoRoot) => {
|
|
5737
|
+
const endpoint = env["LEMONY_TELEMETRY_ENDPOINT"] ?? "https://lemony-telemetry.lemoncode.workers.dev";
|
|
5703
5738
|
const result = await sendTelemetry({
|
|
5704
5739
|
repoRoot,
|
|
5705
|
-
endpoint
|
|
5740
|
+
endpoint
|
|
5706
5741
|
});
|
|
5707
5742
|
console.log(`telemetry ${result.outcome} (${result.segmentsSent} sent, ${result.quarantined} quarantined)`);
|
|
5708
5743
|
};
|
|
5709
5744
|
const telemetryFlush = async (repoRoot) => {
|
|
5745
|
+
const endpoint = env["LEMONY_TELEMETRY_ENDPOINT"] ?? "https://lemony-telemetry.lemoncode.workers.dev";
|
|
5710
5746
|
const result = await sendTelemetry({
|
|
5711
5747
|
repoRoot,
|
|
5712
|
-
endpoint
|
|
5748
|
+
endpoint
|
|
5713
5749
|
});
|
|
5714
5750
|
for (const line of formatTelemetryFlush(result)) console.log(line);
|
|
5715
5751
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lemoncode/lemony",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Lemony — a Harness for AI Coding. Vendor package: installer, agent role catalog, generic skill catalog, hooks, and templates for a Spec-Driven Development workflow.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -50,16 +50,16 @@
|
|
|
50
50
|
"@types/node": "24.13.3",
|
|
51
51
|
"husky": "9.1.7",
|
|
52
52
|
"lint-staged": "17.4.1",
|
|
53
|
-
"oxlint": "1.
|
|
53
|
+
"oxlint": "1.81.0",
|
|
54
54
|
"prettier": "3.9.6",
|
|
55
|
-
"tsdown": "0.
|
|
56
|
-
"tsx": "4.23.
|
|
55
|
+
"tsdown": "0.23.0",
|
|
56
|
+
"tsx": "4.23.13",
|
|
57
57
|
"typescript": "7.0.2",
|
|
58
|
-
"vitest": "
|
|
58
|
+
"vitest": "5.0.0"
|
|
59
59
|
},
|
|
60
60
|
"dependencies": {
|
|
61
61
|
"yaml": "2.9.0",
|
|
62
|
-
"zod": "4.4
|
|
62
|
+
"zod": "4.5.4"
|
|
63
63
|
},
|
|
64
64
|
"scripts": {
|
|
65
65
|
"build": "tsdown",
|