feat: add three-layer rule architecture with genre profiles

- GenreProfile and BookRules Zod schemas (genre-profile.ts, book-rules.ts)
- rules-reader.ts: readGenreProfile() with project→builtin fallback
- 5 built-in genre profiles (xuanhuan, xianxia, urban, horror, other)
- js-yaml dependency for YAML frontmatter parsing
- Version bump to 0.3.0
This commit is contained in:
majx_mac
2026-03-13 15:04:33 +08:00
parent f8bc93e1b5
commit 0746468dbf
13 changed files with 465 additions and 5 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "inkos",
"version": "0.2.0",
"version": "0.3.0",
"private": true,
"description": "Multi-agent novel production system",
"license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@actalk/inkos",
"version": "0.2.0",
"version": "0.3.0",
"description": "InkOS CLI — multi-agent novel production system",
"type": "module",
"bin": {
+51
View File
@@ -0,0 +1,51 @@
---
name: 恐怖
id: horror
chapterTypes: ["氛围章", "事件章", "揭示章", "过渡章", "回收章"]
fatigueWords: ["毛骨悚然", "不寒而栗", "浑身发冷", "头皮发麻", "鸡皮疙瘩", "心跳加速", "仿佛", "不禁", "宛如", "竟然"]
numericalSystem: false
powerScaling: false
eraResearch: false
pacingRule: "氛围递进:安全感→微妙不适→确认异常→恐惧升级→高潮→喘息,循环推进"
satisfactionTypes: ["真相揭示", "成功逃脱", "反杀怪物", "谜团解开", "同伴获救", "规则发现"]
auditDimensions: [1,2,3,6,7,8,9,10,13,14,15,16,17,18,19]
---
## 题材禁忌
- 恐怖源头过早完全暴露(未知才恐怖)
- 主角无脑刚正面解决一切
- 用打脸/升级等爽文套路替代恐怖氛围
- 恐怖元素与日常场景割裂(好的恐怖来自日常的扭曲)
- 角色面对恐怖事件完全不害怕
- 用大量血腥描写替代心理恐惧
## 恐惧层级
- 第一层:不适感(微妙的错位、违和)
- 第二层:不安(确认有异常,但看不清全貌)
- 第三层:恐惧(威胁明确化,逃生本能启动)
- 第四层:绝望(规则被打破,安全感彻底崩塌)
- 不要跳过层级直达高潮,递进才有力量
## 语言铁律
- 恐怖用事实传达,不用情绪标签。✗"他感到一阵恐惧" → ✓"他后颈的汗毛一根根立起来"
- 禁止过度解释恐怖。异常现象只需呈现,不需叙述者出来总结"这一切都太不正常了"
- 克制叙事:越恐怖越冷静。句子随恐惧升级而变短,但叙述者语气始终平稳
- 被淘汰/伤害的配角必须有至少一个暗示其个人故事的细节(书包里的补习班收据、手机壳上的贴纸),让淘汰有重量
## 叙事指导
氛围是第一生产力。用五感细节(声音、气味、温度、触感)建立不安。
恐怖来自对未知的恐惧,信息揭示要克制。
"看不见的"永远比"看见的"更可怕。
角色的恐惧反应必须真实:颤抖、口干、思维混乱、判断力下降。
求生本能驱动行为,不是英雄主义。
每个安全区都是暂时的,喘息之后是更深的恐惧。
规则感:恐怖世界有自己的规则,发现规则是生存的关键。
信息管理:读者知道的和角色知道的之间的差距制造悬念。
日常的扭曲比凭空出现的怪物更恐怖。
每3章必须打破一次已建立的模式(规则矛盾、可信来源说谎、安全区失效),避免机械重复。
+24
View File
@@ -0,0 +1,24 @@
---
name: 通用
id: other
chapterTypes: ["推进章", "布局章", "过渡章", "回收章"]
fatigueWords: ["震惊", "不可思议", "难以置信", "深吸一口气", "仿佛", "不禁", "宛如", "竟然"]
numericalSystem: false
powerScaling: false
eraResearch: false
pacingRule: "每2-3章有一个明确的进展或反馈"
satisfactionTypes: ["目标达成", "困难克服", "真相揭示", "关系转变"]
auditDimensions: [1,2,3,6,7,8,9,10,13,14,15,16,17,18,19]
---
## 题材禁忌
- 无逻辑的巧合推进剧情
- 配角降智配合主角
- 无铺垫的高潮
## 叙事指导
根据具体题材调整叙事重心。
保持因果逻辑链完整。
人物行为由动机驱动,不由剧情需要驱动。
+53
View File
@@ -0,0 +1,53 @@
---
name: 都市
id: urban
chapterTypes: ["商战章", "社交章", "布局章", "过渡章", "回收章"]
fatigueWords: ["冷笑", "不可思议", "震惊", "难以置信", "深吸一口气", "眼中闪过一丝", "仿佛", "不禁", "宛如", "竟然", "核心动机", "信息边界"]
numericalSystem: false
powerScaling: false
eraResearch: true
pacingRule: "每2-3章一个小回报:商业收益、人脉拓展、对手受挫、信息优势"
satisfactionTypes: ["商战碾压", "身份揭示", "人脉兑现", "对手打脸", "资源收割", "地位跃升"]
auditDimensions: [1,2,3,6,7,8,9,10,11,12,13,14,15,16,17,18,19]
---
## 题材禁忌
- 无逻辑的商业奇迹(没有铺垫的暴富)
- 反派降智配合主角表演
- 无视现实法律和商业规则
- 用"一个电话搞定"跳过具体操作过程
- 女性角色沦为花瓶或奖励
- 混入玄幻/仙侠战力体系
## 年代与现实约束
- 涉及法律、政策、商业规则必须符合设定年代
- 金融操作、公司运营必须有基本可信度
- 人物身份、职位、权限不能超出现实合理范围
- 地名、机构名、行业术语必须准确
- 物价、收入、生活水平符合时代设定
## 语言铁律
- 人物内心独白必须口语化、直觉化,禁止商业分析/博弈论术语渗入叙事
- ✗"他迅速分析了当前的债务状况" → ✓"他把那叠皱巴巴的白条翻了三遍"
- ✗"信息落差就在这儿" → ✓"他们不知道的,他知道"
- ✗"以这种人的性格,这时候不会撕破脸" → ✓直接写对方的行为反应
- 法律/商业术语必须匹配设定年代的真实语感:2003年民间借条不会写"逾期处置授权",而是"到期没还,房子归乙方处理"
- 主角的判断通过行动和对话体现,不通过上帝视角的分析段落
## 叙事指导
以商战、社交博弈和信息差驱动剧情。
权力来自人脉、资本、信息和制度位置,不来自武力。
冲突解决靠谈判、交易、威慑、法律手段和利益交换。
钱权必须落地,通过物、势、地位变化和小人物反应兑现爽点。
人物关系网是核心资产,每次社交互动都应有利益计算。
主角需要保留非功能性时刻:思考、犹豫、社交润滑。
主角不是全知全能,必须在前5章内至少出现一次判断失误或信息偏差。
时代厚重感、人情债与制度摩擦是都市文的灵魂。
用场面、气味、动作、交易、压迫感切入,不要历史课件式开头。
嵌入1-2个时代锚点(物价、新闻事件、流行用语)增强年代沉浸感。
+46
View File
@@ -0,0 +1,46 @@
---
name: 仙侠
id: xianxia
chapterTypes: ["战斗章", "悟道章", "布局章", "过渡章", "回收章"]
fatigueWords: ["冷笑", "蝼蚁", "倒吸凉气", "瞳孔骤缩", "天道", "大道", "因果", "气运", "仿佛", "不禁", "宛如", "竟然"]
numericalSystem: true
powerScaling: true
eraResearch: false
pacingRule: "修炼/悟道与战斗交替,每3-5章一次小突破或关键收获"
satisfactionTypes: ["悟道突破", "斗法碾压", "法宝收获", "身份揭示", "天劫渡过", "因果了结"]
auditDimensions: [1,2,3,4,5,6,7,8,9,10,11,13,14,15,16,17,18,19]
---
## 题材禁忌
- 主角为推剧情突然仁慈、犯蠢
- 修为无铺垫跳跃式突破
- 法宝凭空出现解决危机
- 天道规则前后矛盾
- 用"大道无形""天道感应"跳过具体修炼过程
- 同质资源不写衰减默认全额结算
- 风格混入都市腔、游戏系统播报腔
## 修炼规则
- 境界突破必须有积累过程:悟道、丹药、战斗领悟、机缘
- 同质资源重复炼化必须写明衰减
- 法宝体系分品级,使用有代价(灵力、寿元、因果)
- 金手指/功法四维约束:
- 能力上限:有明确的境界/品阶天花板
- 附加代价:修炼/使用伴随代价(寿元、因果、心魔)
- 触发条件:突破/觉醒需要特定条件(悟道、机缘、天劫)
- 成长路径:功法随修为递进,不可跳阶获得
- 天道规则一旦设定不可违反,除非有明确的特殊机制
- 期初修为/资源从账本取,增量逐笔列出
- 跨大境界突破需要天劫或特殊条件
## 叙事指导
修炼与悟道是叙事核心,但必须融入剧情而非独立说教。
悟道场景用五感描写,不用抽象哲理灌输。
仙侠世界的规则感要强:因果、天劫、气运都是叙事工具。
人情债与道义约束是仙侠特有的驱动力。
门派政治、宗门博弈是重要的布局手段。
战斗以法术、法宝、阵法为核心,注重空间感和规模感。
+64
View File
@@ -0,0 +1,64 @@
---
name: 玄幻
id: xuanhuan
chapterTypes: ["战斗章", "布局章", "过渡章", "回收章"]
fatigueWords: ["冷笑", "蝼蚁", "倒吸凉气", "瞳孔骤缩", "不可置信", "轰然炸裂", "满场死寂", "难以置信", "仿佛", "不禁", "宛如", "竟然"]
numericalSystem: true
powerScaling: true
eraResearch: false
pacingRule: "三章内必有明确反馈:打脸、收益兑现、信息反转、地位变化"
satisfactionTypes: ["打脸", "升级突破", "收益兑现", "智斗碾压", "身份揭示", "底牌亮出"]
auditDimensions: [1,2,3,4,5,6,7,8,9,10,11,13,14,15,16,17,18,19]
---
## 题材禁忌
- 主角为推剧情突然仁慈、犯蠢、讲武德
- 同质资源不写衰减默认全额结算
- 用"暴涨""海量"跳过数值结算
- 无铺垫的能力觉醒
- 反派像木桩一样排队送死
- 无铺垫强行让退场角色回归
- 在没有铺垫的情况下突然塞入新体系、新地图、新外挂解决问题
- 把所有章节都写成高爆裂战斗章
- 拆解知识库反向污染正文,写成"似曾相识"的拼装文
- 风格混入都市腔、科幻腔、游戏系统播报腔、轻小说吐槽腔
## 数值规则
- 设定不可吃书:前文确立的设定数值后文不可无升级过程地随意改变
- 金手指四维约束:
- 能力上限:必须设定明确的能力天花板,不可无限升级
- 附加代价:使用伴随代价(寿命、体力、副作用),权衡利弊增强冲突
- 触发条件:激活与特定场景/事件关联,不可随时随地无条件使用
- 成长路径:随主角经历同步升级,解锁过程与剧情节点绑定
- 同质资源重复吞噬必须写明衰减,不得默认全额结算
- 同质吞噬衰减公式:收益 = 基础值 × max(0.3, 1 - 0.15×(N-1))
- 不要用"暴涨""海量""难以估量"跳过数值结算
- 期初值从账本取(不凭记忆),增量逐笔列出并注明来源
- 消耗逐笔列出并注明用途,期末 = 期初 + 增量 - 消耗,不得跳步
- 正文中出现的系统提示(如【气血值+X】)必须与POST_SETTLEMENT一致
- 若正文写了"比A还高"这类比较句,必须数值验证后再保留
- 数值连续性必须可追溯:同层级、同类型样本的增量不得无说明跨越一个数量级
## 语言铁律
- 力量体系的量级感用体感传达,不用抽象数字。✗"他的火元从12缕增加到24缕" → ✓"手臂比先前有力了,握拳时指骨发紧"
- 同一高潮段(如吞火/突破/觉醒)中,同一意象域的渲染不超过两轮,第三轮必须切入新信息或新动作
- 搜尸/清点/装备段落禁止清单式列举,必须带入角色判断或取舍:✗"他翻出粗盐、水囊、黑面饼" → ✓"水囊最值钱,剩下那点水比命轻不了多少"
## 叙事指导
以战斗和资源获取驱动剧情。主角行为由利益驱动,杀伐果断。
金手指/能力系统必须有限制:使用频率、范围限制或使用代价。
设定不可吃书:前文确立的数值后文不可无升级过程地随意改变。
三章内应有明确反馈,但反馈可以是打脸、收益兑现、信息反转、地位变化,不限于杀人。
涉及吞噬时,收益必须同时落到资源说明与具体增量,不能只写抽象提升。
小冲突尽快兑现反馈;不要把爽点无限后置。
核心对手必须有脑子,有试探、有误判、有反扑。
可以留人、钓鱼、示弱、借刀杀人,但前提只能是利益更大,绝不能是心软。
用动作、伤势、声音、重量、冲击、温度来落地"强",少用空泛判断。
每个场景至少推进一项:信息、地位、资源、伤亡、仇恨、境界。
+7 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@actalk/inkos-core",
"version": "0.2.0",
"version": "0.3.0",
"description": "InkOS core: models, agents, pipeline, LLM integration",
"type": "module",
"main": "dist/index.js",
@@ -11,7 +11,10 @@
"import": "./dist/index.js"
}
},
"files": ["dist"],
"files": [
"dist",
"genres"
],
"license": "MIT",
"repository": {
"type": "git",
@@ -25,10 +28,12 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"js-yaml": "^4.1.1",
"openai": "^4.80.0",
"zod": "^3.24.0"
},
"devDependencies": {
"@types/js-yaml": "^4.0.9",
"typescript": "^5.8.0",
"vitest": "^3.0.0"
}
+95
View File
@@ -0,0 +1,95 @@
import { readFile, readdir } from "node:fs/promises";
import { join, dirname } from "node:path";
import { fileURLToPath } from "node:url";
import { parseGenreProfile, type ParsedGenreProfile } from "../models/genre-profile.js";
import { parseBookRules, type ParsedBookRules } from "../models/book-rules.js";
const BUILTIN_GENRES_DIR = join(dirname(fileURLToPath(import.meta.url)), "../../genres");
async function tryReadFile(path: string): Promise<string | null> {
try {
return await readFile(path, "utf-8");
} catch {
return null;
}
}
/**
* Load genre profile. Lookup order:
* 1. Project-level: {projectRoot}/genres/{genreId}.md
* 2. Built-in: packages/core/genres/{genreId}.md
* 3. Fallback: built-in other.md
*/
export async function readGenreProfile(
projectRoot: string,
genreId: string,
): Promise<ParsedGenreProfile> {
const projectPath = join(projectRoot, "genres", `${genreId}.md`);
const builtinPath = join(BUILTIN_GENRES_DIR, `${genreId}.md`);
const fallbackPath = join(BUILTIN_GENRES_DIR, "other.md");
const raw =
(await tryReadFile(projectPath)) ??
(await tryReadFile(builtinPath)) ??
(await tryReadFile(fallbackPath));
if (!raw) {
throw new Error(`Genre profile not found for "${genreId}" and fallback "other.md" is missing`);
}
return parseGenreProfile(raw);
}
/**
* List all available genre profiles (project-level + built-in, deduped).
* Returns array of { id, name, source }.
*/
export async function listAvailableGenres(
projectRoot: string,
): Promise<ReadonlyArray<{ readonly id: string; readonly name: string; readonly source: "project" | "builtin" }>> {
const results = new Map<string, { id: string; name: string; source: "project" | "builtin" }>();
// Built-in genres first
try {
const builtinFiles = await readdir(BUILTIN_GENRES_DIR);
for (const file of builtinFiles) {
if (!file.endsWith(".md")) continue;
const id = file.replace(/\.md$/, "");
const raw = await tryReadFile(join(BUILTIN_GENRES_DIR, file));
if (!raw) continue;
const parsed = parseGenreProfile(raw);
results.set(id, { id, name: parsed.profile.name, source: "builtin" });
}
} catch { /* no builtin dir */ }
// Project-level genres override
const projectDir = join(projectRoot, "genres");
try {
const projectFiles = await readdir(projectDir);
for (const file of projectFiles) {
if (!file.endsWith(".md")) continue;
const id = file.replace(/\.md$/, "");
const raw = await tryReadFile(join(projectDir, file));
if (!raw) continue;
const parsed = parseGenreProfile(raw);
results.set(id, { id, name: parsed.profile.name, source: "project" });
}
} catch { /* no project genres dir */ }
return [...results.values()].sort((a, b) => a.id.localeCompare(b.id));
}
/** Return the path to the built-in genres directory. */
export function getBuiltinGenresDir(): string {
return BUILTIN_GENRES_DIR;
}
/**
* Load book_rules.md from the book's story directory.
* Returns null if the file doesn't exist.
*/
export async function readBookRules(bookDir: string): Promise<ParsedBookRules | null> {
const raw = await tryReadFile(join(bookDir, "story/book_rules.md"));
if (!raw) return null;
return parseBookRules(raw);
}
+5 -1
View File
@@ -3,6 +3,8 @@ export { type BookConfig, type Platform, type Genre, type BookStatus, BookConfig
export { type ChapterMeta, type ChapterStatus, ChapterMetaSchema, ChapterStatusSchema } from "./models/chapter.js";
export { type ProjectConfig, type LLMConfig, type NotifyChannel, ProjectConfigSchema, LLMConfigSchema } from "./models/project.js";
export { type CurrentState, type ParticleLedger, type PendingHooks, type PendingHook, type LedgerEntry } from "./models/state.js";
export { type GenreProfile, type ParsedGenreProfile, GenreProfileSchema, parseGenreProfile } from "./models/genre-profile.js";
export { type BookRules, type ParsedBookRules, BookRulesSchema, parseBookRules } from "./models/book-rules.js";
// LLM
export { createLLMClient, chatCompletion, type LLMResponse, type LLMMessage } from "./llm/provider.js";
@@ -12,9 +14,11 @@ export { BaseAgent, type AgentContext } from "./agents/base.js";
export { ArchitectAgent, type ArchitectOutput } from "./agents/architect.js";
export { WriterAgent, type WriteChapterInput, type WriteChapterOutput } from "./agents/writer.js";
export { ContinuityAuditor, type AuditResult, type AuditIssue } from "./agents/continuity.js";
export { ReviserAgent, type ReviseOutput } from "./agents/reviser.js";
export { ReviserAgent, type ReviseOutput, type ReviseMode } from "./agents/reviser.js";
export { RadarAgent, type RadarResult, type RadarRecommendation } from "./agents/radar.js";
export { FanqieRadarSource, QidianRadarSource, TextRadarSource, type RadarSource, type PlatformRankings, type RankingEntry } from "./agents/radar-source.js";
export { readGenreProfile, readBookRules, listAvailableGenres, getBuiltinGenresDir } from "./agents/rules-reader.js";
export { buildWriterSystemPrompt } from "./agents/writer-prompts.js";
// Pipeline
export { PipelineRunner, type PipelineConfig, type ChapterPipelineResult, type DraftResult, type ReviseResult, type TruthFiles, type BookStatusInfo } from "./pipeline/runner.js";
+59
View File
@@ -0,0 +1,59 @@
import { z } from "zod";
import yaml from "js-yaml";
const ProtagonistSchema = z.object({
name: z.string(),
personalityLock: z.array(z.string()).default([]),
behavioralConstraints: z.array(z.string()).default([]),
}).optional();
const GenreLockSchema = z.object({
primary: z.string(),
forbidden: z.array(z.string()).default([]),
}).optional();
const NumericalOverridesSchema = z.object({
hardCap: z.union([z.number(), z.string()]).optional(),
resourceTypes: z.array(z.string()).default([]),
}).optional();
const EraConstraintsSchema = z.object({
enabled: z.boolean().default(false),
period: z.string().optional(),
region: z.string().optional(),
}).optional();
export const BookRulesSchema = z.object({
version: z.string().default("1.0"),
protagonist: ProtagonistSchema,
genreLock: GenreLockSchema,
numericalSystemOverrides: NumericalOverridesSchema,
eraConstraints: EraConstraintsSchema,
prohibitions: z.array(z.string()).default([]),
chapterTypesOverride: z.array(z.string()).default([]),
fatigueWordsOverride: z.array(z.string()).default([]),
additionalAuditDimensions: z.array(z.union([z.number(), z.string()])).default([]),
enableFullCastTracking: z.boolean().default(false),
});
export type BookRules = z.infer<typeof BookRulesSchema>;
export interface ParsedBookRules {
readonly rules: BookRules;
readonly body: string;
}
export function parseBookRules(raw: string): ParsedBookRules {
// Strip markdown code block wrappers if present (LLM often wraps output in ```md ... ```)
const stripped = raw.replace(/^```(?:md|markdown|yaml)?\s*\n/, "").replace(/\n```\s*$/, "");
const fmMatch = stripped.match(/^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/);
if (!fmMatch) {
throw new Error("book_rules.md missing YAML frontmatter (--- ... ---)");
}
const frontmatter = yaml.load(fmMatch[1]) as Record<string, unknown>;
const rules = BookRulesSchema.parse(frontmatter);
const body = fmMatch[2].trim();
return { rules, body };
}
+35
View File
@@ -0,0 +1,35 @@
import { z } from "zod";
import yaml from "js-yaml";
export const GenreProfileSchema = z.object({
name: z.string(),
id: z.string(),
chapterTypes: z.array(z.string()),
fatigueWords: z.array(z.string()),
numericalSystem: z.boolean().default(false),
powerScaling: z.boolean().default(false),
eraResearch: z.boolean().default(false),
pacingRule: z.string().default(""),
satisfactionTypes: z.array(z.string()).default([]),
auditDimensions: z.array(z.number()).default([]),
});
export type GenreProfile = z.infer<typeof GenreProfileSchema>;
export interface ParsedGenreProfile {
readonly profile: GenreProfile;
readonly body: string;
}
export function parseGenreProfile(raw: string): ParsedGenreProfile {
const fmMatch = raw.match(/^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/);
if (!fmMatch) {
throw new Error("Genre profile missing YAML frontmatter (--- ... ---)");
}
const frontmatter = yaml.load(fmMatch[1]) as Record<string, unknown>;
const profile = GenreProfileSchema.parse(frontmatter);
const body = fmMatch[2].trim();
return { profile, body };
}
+24
View File
@@ -32,6 +32,9 @@ importers:
packages/core:
dependencies:
js-yaml:
specifier: ^4.1.1
version: 4.1.1
openai:
specifier: ^4.80.0
version: 4.104.0(zod@3.25.76)
@@ -39,6 +42,9 @@ importers:
specifier: ^3.24.0
version: 3.25.76
devDependencies:
'@types/js-yaml':
specifier: ^4.0.9
version: 4.0.9
typescript:
specifier: ^5.8.0
version: 5.9.3
@@ -354,6 +360,9 @@ packages:
'@types/estree@1.0.8':
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
'@types/js-yaml@4.0.9':
resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==}
'@types/node-fetch@2.6.13':
resolution: {integrity: sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==}
@@ -400,6 +409,9 @@ packages:
resolution: {integrity: sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==}
engines: {node: '>= 8.0.0'}
argparse@2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
assertion-error@2.0.1:
resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
engines: {node: '>=12'}
@@ -549,6 +561,10 @@ packages:
js-tokens@9.0.1:
resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==}
js-yaml@4.1.1:
resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==}
hasBin: true
loupe@3.2.1:
resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==}
@@ -933,6 +949,8 @@ snapshots:
'@types/estree@1.0.8': {}
'@types/js-yaml@4.0.9': {}
'@types/node-fetch@2.6.13':
dependencies:
'@types/node': 22.19.15
@@ -996,6 +1014,8 @@ snapshots:
dependencies:
humanize-ms: 1.2.1
argparse@2.0.1: {}
assertion-error@2.0.1: {}
asynckit@0.4.0: {}
@@ -1153,6 +1173,10 @@ snapshots:
js-tokens@9.0.1: {}
js-yaml@4.1.1:
dependencies:
argparse: 2.0.1
loupe@3.2.1: {}
magic-string@0.30.21: