@rt-tools/agent-kit 0.9.1 → 0.11.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/README.md +5 -0
- package/assets/agents/conscience.md +58 -0
- package/assets/agents/prose-editor.md +44 -0
- package/assets/agents/strict-teacher.md +59 -0
- package/assets/checks/board.github.mjs +56 -1
- package/assets/checks/check-board.github.mjs +24 -0
- package/assets/checks/check-doc-paths.mjs +4 -15
- package/assets/checks/check-dupes.mjs +5 -6
- package/assets/checks/check-file-size.mjs +6 -20
- package/assets/checks/check-prose-style.mjs +137 -0
- package/assets/checks/check-reuse.mjs +5 -5
- package/assets/checks/check-state-next.mjs +194 -0
- package/assets/checks/check-states.mjs +142 -0
- package/assets/checks/check-styles.mjs +5 -5
- package/assets/checks/check-turn-map.mjs +146 -0
- package/assets/checks/lib-common.mjs +3 -3
- package/assets/checks/rt-kit-checks.config.mjs +85 -1
- package/assets/commands/agent-kit-digest.md +6 -5
- package/assets/defaults/project.sh +108 -6
- package/assets/defaults/turn-map.md +46 -0
- package/assets/docs/GLOSSARY.md +17 -15
- package/assets/hooks/browser-device-id.sh +2 -0
- package/assets/hooks/browser-guard-device-id.sh +11 -1
- package/assets/hooks/browser-guard-no-asking.sh +11 -1
- package/assets/hooks/browser-guard-no-listing.sh +11 -1
- package/assets/hooks/browser-guard-no-other-drivers.sh +9 -1
- package/assets/hooks/browser-guard-require-select.sh +13 -2
- package/assets/hooks/claim-guard.sh +115 -0
- package/assets/hooks/commit-msg.sh +2 -0
- package/assets/hooks/conscience-guard.sh +100 -0
- package/assets/hooks/constitution-index.sh +2 -0
- package/assets/hooks/deny-tail.sh +32 -0
- package/assets/hooks/dev-server-guard.sh +10 -2
- package/assets/hooks/docs-guard.sh +16 -2
- package/assets/hooks/exam-guard.sh +123 -0
- package/assets/hooks/git-guard-delivery-signature.sh +77 -0
- package/assets/hooks/git-guard-delivery.sh +156 -68
- package/assets/hooks/git-guard-main.sh +14 -0
- package/assets/hooks/git-guard-push-tests.sh +51 -2
- package/assets/hooks/glossary-load.sh +2 -0
- package/assets/hooks/grill-gate.sh +14 -0
- package/assets/hooks/handoff-entry-guard.sh +73 -0
- package/assets/hooks/handoff-write.sh +103 -0
- package/assets/hooks/lint-after-edit.sh +2 -0
- package/assets/hooks/observe.sh +2 -0
- package/assets/hooks/postmortem-guard.sh +14 -0
- package/assets/hooks/proposal-guard.sh +14 -0
- package/assets/hooks/prose-style-guard.sh +75 -0
- package/assets/hooks/qa-dataid-guard.sh +14 -1
- package/assets/hooks/rerun-guard.sh +88 -0
- package/assets/hooks/reuse-first-guard.sh +14 -1
- package/assets/hooks/roles.sh +34 -0
- package/assets/hooks/skill-gate-layers.sh +2 -0
- package/assets/hooks/skill-gate-rearm.sh +2 -0
- package/assets/hooks/skill-gate.sh +14 -0
- package/assets/hooks/skill-loaded.sh +2 -0
- package/assets/hooks/sql-guard-parse.sh +2 -0
- package/assets/hooks/sql-guard-request.sh +2 -0
- package/assets/hooks/sql-guard-target.sh +2 -0
- package/assets/hooks/sql-guard-write.sh +4 -1
- package/assets/hooks/sql-guard.sh +14 -1
- package/assets/hooks/task-context-load.sh +2 -0
- package/assets/hooks/task-flow-guard.sh +73 -7
- package/assets/hooks/turn-entry-load.sh +62 -0
- package/assets/hooks/turn-exit-guard.sh +191 -0
- package/assets/hooks/utf8.sh +35 -0
- package/assets/hooks/waiting-turn-guard.sh +14 -0
- package/assets/hooks/window-fill-guard.sh +43 -2
- package/assets/laws/work-conduct.md +29 -0
- package/assets/patterns/cargo-triage-mark.md +119 -0
- package/assets/patterns/task-flow-close.md +29 -8
- package/assets/patterns/task-flow-handoff.md +19 -1
- package/assets/patterns/task-flow-resume.md +36 -4
- package/assets/patterns/task-flow-start.md +56 -6
- package/assets/patterns/turn-entry-map.md +81 -0
- package/assets/rules/cargo-triage.md +126 -0
- package/assets/rules/git-workflow.azure.md +36 -9
- package/assets/rules/git-workflow.github.md +74 -9
- package/assets/rules/git-workflow.gitlab.md +40 -12
- package/assets/rules/task-flow.md +136 -71
- package/assets/rules/testing.md +15 -1
- package/assets/rules/turn-entry.md +93 -0
- package/assets/samples/tasks/_template/plan.md +4 -1
- package/assets/samples/tasks/_template/progress.md +1 -0
- package/assets/skills/agent-kit.md +33 -0
- package/assets/templates/project.sh +16 -0
- package/bin/agent-kit.d.ts.map +1 -1
- package/bin/agent-kit.js +42 -1
- package/bin/agent-kit.js.map +1 -1
- package/lib/cargo-state.d.ts +62 -0
- package/lib/cargo-state.d.ts.map +1 -0
- package/lib/cargo-state.js +118 -0
- package/lib/cargo-state.js.map +1 -0
- package/lib/cargo.d.ts +42 -0
- package/lib/cargo.d.ts.map +1 -1
- package/lib/cargo.js +2 -0
- package/lib/cargo.js.map +1 -1
- package/lib/commands.d.ts.map +1 -1
- package/lib/commands.js +64 -4
- package/lib/commands.js.map +1 -1
- package/lib/config.d.ts +8 -0
- package/lib/config.d.ts.map +1 -1
- package/lib/config.js +1 -0
- package/lib/config.js.map +1 -1
- package/lib/observations.d.ts +35 -1
- package/lib/observations.d.ts.map +1 -1
- package/lib/observations.js +14 -2
- package/lib/observations.js.map +1 -1
- package/lib/ship.d.ts +3 -0
- package/lib/ship.d.ts.map +1 -1
- package/lib/ship.js +56 -0
- package/lib/ship.js.map +1 -1
- package/lib/thresholds.d.ts +49 -0
- package/lib/thresholds.d.ts.map +1 -0
- package/lib/thresholds.js +151 -0
- package/lib/thresholds.js.map +1 -0
- package/package.json +1 -1
- package/rt-tools-agent-kit-0.11.0.tgz +0 -0
- package/rt-tools-agent-kit-0.9.1.tgz +0 -0
package/lib/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/config.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Конфиг проекта — `.claude/rt-kit.json`.\n *\n * Всё, что пакет не может знать сам: значения для дырок, куда класть каждый род ресурса и от\n * чего проект отказался. Конфиг лежит в дереве проекта и коммитится: раскладка обязана\n * повторяться на чужой машине без вопросов, иначе разложенное расходится между разработчиками.\n */\nimport { readFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\n/** Род ресурса. Он же имя каталога в `assets/` пакета и ключ раскладки. */\nexport type TKind =\n | 'laws'\n | 'rules'\n | 'patterns'\n | 'skills'\n | 'hooks'\n | 'defaults'\n | 'checks'\n | 'agents'\n | 'commands'\n | 'workflows'\n | 'templates'\n /** Документы дерева, которые читает и человек, и агент: словарь проекта. */\n | 'docs'\n /**\n * Образцы, которые копируют в рабочий файл и заполняют: папка задачи, спек домена.\n *\n * От шаблонов отличаются читателем и местом. Шаблон ложится в каталог слоя правил, и с него\n * пишут ресурс этого слоя; образец ложится туда, где идёт работа, и копирует его тот, кто\n * работу ведёт. Одним родом их не свести: дерево вправе отказаться от одних, оставив\n * другие, и назвать им разные каталоги.\n */\n | 'samples';\n\nexport const KINDS: readonly TKind[] = [\n 'laws',\n 'rules',\n 'patterns',\n 'skills',\n 'hooks',\n 'defaults',\n 'checks',\n 'agents',\n 'commands',\n 'workflows',\n 'templates',\n 'docs',\n 'samples',\n];\n\n/**\n * Роды, которые ложатся не файлом в каталог, а каталогом по имени ресурса: скил читается как\n * `<имя>/SKILL.md`, и рядом с ним лежит то, что пишет проект.\n *\n * `skills` — скил, у которого нет закона над собой: он не про то, что должно быть верно в\n * продукте, а про то, как здесь делается работа. Правилом его называть нельзя — тогда придётся\n * выдумать ему закон, — а паттерном тем более: паттерн стоит при правиле.\n */\nexport const SKILL_KINDS: readonly TKind[] = ['rules', 'patterns', 'skills'];\n\n/** Имя файла скила: так его ищет агент, и другого имени у него быть не может. */\nexport const SKILL_FILE: string = 'SKILL.md';\n\n/**\n * Файл-компаньон правила: чем названо в этом дереве то, о чём правило говорит приёмом.\n *\n * Пакет знает приём, но не знает ни путей, ни имён — их пишет проект. Черновик кладётся один\n * раз и дальше не сверяется: это единственный файл раскладки, который принадлежит проекту.\n */\nexport const COMPANION_FILE: string = 'implementation.md';\n\nexport interface IConfig {\n /** Значения дырок `{{имя}}`. */\n readonly vars: Readonly<Record<string, string>>;\n /** Куда класть каждый род ресурса, путями от корня проекта. */\n readonly layout: Readonly<Record<TKind, string>>;\n /**\n * Ресурсы, которые проект выбрал, идентификаторами: `laws/access.md`. Пусто — берётся всё.\n * Ограничивает только те роды, которые сам называет; как именно — `isChosen`.\n */\n readonly only: readonly string[];\n /** Ресурсы, от которых проект отказался, идентификаторами: `laws/application/money.md`. */\n readonly skip: readonly string[];\n /**\n * Выбор проекта по осям различия: `{ \"host\": \"gitlab\" }`. Оси объявлены пакетом в\n * `assets/variants.json`, а ресурс с вариантом в имени берётся, только если проект выбрал\n * его значение; как именно — `isChosen`.\n */\n readonly variants: Readonly<Record<string, string>>;\n /**\n * Что у дерева есть: `[\"packages\", \"app\"]`. Свойства объявлены пакетом в `assets/traits.json`,\n * а ресурс, требующий свойства именем файла, дереву без него не кладётся вовсе — ни сам, ни\n * черновик его компаньона. Пусто — дерево о себе ничего не сказало, и помеченного не получает.\n */\n readonly has: readonly string[];\n /**\n * Пишут ли гарды наблюдения о слое правил. Умолчание — да; выключает дерево, и целиком, а\n * не частями. Ключ читают двое: сводка — отсюда, гард — тем же именем прямо из файла, потому\n * что разборщика конфига у него нет.\n */\n readonly observe: boolean;\n /**\n * Адрес приёма, куда уезжает груз: `https://…`. Пусто — отправлять некуда, и отправка\n * отказывает вместо молчания. Зашитый в код адрес назвал бы чужое дерево в текстах пакета.\n */\n readonly intake: string;\n /**\n * Файл с токеном дерева — путь от корня дерева или от домашнего каталога (`~/…`). Сам токен\n * в конфиге не лежит: конфиг коммитится, а токен обязан остаться вне истории.\n */\n readonly token: string;\n /**\n * Признак дерева, когда удалённого репозитория нет. При нём — считается из адреса\n * репозитория: две рабочие копии одного репозитория обязаны дать один признак.\n */\n readonly tree: string;\n /** Где дерево держит разборы происшествий. Путь от корня дерева. */\n readonly postmortems: string;\n}\n\nexport const CONFIG_PATH: string = '.claude/rt-kit.json';\n/** Каталог, в котором дерево держит своё при пакете: надстройки, умолчания, свою карту и профиль. */\nexport const RT_KIT_DIR: string = '.claude/rt-kit';\nexport const OVERRIDES_DIR: string = '.claude/rt-kit/overrides';\n\n/**\n * Где разборы происшествий лежат, когда конфиг не сказал иначе. Роды ресурсов сюда не годятся:\n * разборы пишет дерево про себя, пакет их не раскладывает и не сверяет.\n */\nexport const DEFAULT_POSTMORTEMS_DIR: string = 'docs/postmortems';\n\n/**\n * Карта «что правится — какое правило» и профиль дерева: команды, стенды, пары «правка —\n * документ». Оба лежат в двух видах, и это не дублирование, а разделение ответственности.\n *\n * Умолчание везёт пакет: проекты этой мастерской устроены одинаково — Nx, те же расширения, те\n * же имена каталогов, — и переписывать одну и ту же карту в каждом дереве заново значило бы\n * заводить пятнадцать её редакций, расходящихся молча. Оно разложено, под шапкой, и правится\n * не на месте.\n *\n * Надстройку пишет проект, и она необязательна: витрина, свой род файлов, другой запускатель\n * тестов есть не у всех. Объявленная в ней функция замещает умолчание целиком и вправе позвать\n * его обратно суффиксом `_default` — так дерево дописывает своё, не теряя общего.\n */\n/** Каталог правил дерева: законы туда не идут, а правила, паттерны и скилы лежат вместе. */\nexport const SKILLS_DIR: string = '.claude/skills';\n\nexport const DEFAULTS_DIR: string = '.claude/rt-kit/defaults';\nexport const GATE_MAP_FILE: string = 'gate-map.sh';\nexport const PROFILE_FILE: string = 'project.sh';\n\n/**\n * Умолчания раскладки. Это не единственно возможные пути, но менять их без нужды не стоит:\n * агент ищет законы в `docs/constitution`, потому что так написано в правилах, которые пакет\n * же и везёт.\n */\nexport const DEFAULT_LAYOUT: Readonly<Record<TKind, string>> = {\n laws: 'docs/constitution',\n rules: SKILLS_DIR,\n patterns: SKILLS_DIR,\n skills: SKILLS_DIR,\n hooks: '.claude/hooks',\n defaults: DEFAULTS_DIR,\n checks: 'tools',\n agents: '.claude/agents',\n commands: '.claude/commands',\n workflows: '.claude/workflows',\n templates: '.claude/rt-kit/templates',\n docs: 'docs',\n // Внутри рода путь повторяет раскладку дерева — папки задач и спеки, — поэтому одного\n // умолчания хватает всем образцам сразу.\n samples: 'docs',\n};\n\nexport class ConfigError extends Error {}\n\nconst isRecord: (value: unknown) => value is Record<string, unknown> = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\nfunction stringMap(value: unknown, where: string): Record<string, string> {\n if (value === undefined) {\n return {};\n }\n if (!isRecord(value)) {\n throw new ConfigError(`${where}: ожидается объект «имя — значение»`);\n }\n const result: Record<string, string> = {};\n for (const [key, entry] of Object.entries(value)) {\n if (typeof entry !== 'string') {\n throw new ConfigError(`${where}.${key}: ожидается строка`);\n }\n result[key] = entry;\n }\n\n return result;\n}\n\n/** Строковый ключ конфига. Ключа нет — пустая строка: значение необязательное у всех четырёх. */\nfunction textOf(value: unknown, where: string): string {\n if (value === undefined || value === null) {\n return '';\n }\n if (typeof value !== 'string') {\n throw new ConfigError(`${where}: ожидается строка`);\n }\n\n return value.trim();\n}\n\nfunction idList(value: unknown, where: string): readonly string[] {\n const list: unknown = value ?? [];\n if (!Array.isArray(list) || list.some((entry: unknown): boolean => typeof entry !== 'string')) {\n throw new ConfigError(`${where}: ожидается список идентификаторов ресурсов`);\n }\n\n return list as string[];\n}\n\nexport function parseConfig(text: string): IConfig {\n let raw: unknown;\n try {\n raw = JSON.parse(text);\n } catch (error: unknown) {\n throw new ConfigError(`${CONFIG_PATH} — не разбирается как JSON: ${(error as Error).message}`);\n }\n if (!isRecord(raw)) {\n throw new ConfigError(`${CONFIG_PATH}: ожидается объект`);\n }\n\n const layout: Record<string, string> = { ...DEFAULT_LAYOUT, ...stringMap(raw['layout'], 'layout') };\n for (const kind of Object.keys(layout)) {\n if (!KINDS.includes(kind as TKind)) {\n throw new ConfigError(`layout.${kind}: такого рода ресурсов нет — есть ${KINDS.join(', ')}`);\n }\n }\n\n const only: readonly string[] = idList(raw['only'], 'only');\n const skip: readonly string[] = idList(raw['skip'], 'skip');\n\n // Пути раскладки — тоже значения проекта, и пакет их уже знает. Требовать их вторым списком\n // значило бы держать одно и то же в двух местах: расходятся такие пары молча.\n const derived: Record<string, string> = {};\n for (const kind of KINDS) {\n derived[`${kind.replace(/s$/, '')}sDir`] = layout[kind];\n }\n\n return {\n only,\n skip,\n vars: { ...derived, ...stringMap(raw['vars'], 'vars') },\n layout: layout as Record<TKind, string>,\n variants: stringMap(raw['variants'], 'variants'),\n has: idList(raw['has'], 'has'),\n // Выключателем считается только явное «нет»: ключа нет — запись идёт, как и у гарда.\n observe: raw['observe'] !== false,\n intake: textOf(raw['intake'], 'intake'),\n token: textOf(raw['token'], 'token'),\n tree: textOf(raw['tree'], 'tree'),\n postmortems: textOf(raw['postmortems'], 'postmortems') || DEFAULT_POSTMORTEMS_DIR,\n };\n}\n\n/** Конфиг проекта; его отсутствие — не отказ, а повод сказать про `agent-kit init`. */\nexport function readConfig(root: string): IConfig | null {\n try {\n return parseConfig(readFileSync(join(root, CONFIG_PATH), 'utf8'));\n } catch (error: unknown) {\n if (error instanceof ConfigError) {\n throw error;\n }\n\n return null;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/config.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AA2BjC,MAAM,CAAC,MAAM,KAAK,GAAqB;IACnC,MAAM;IACN,OAAO;IACP,UAAU;IACV,QAAQ;IACR,OAAO;IACP,UAAU;IACV,QAAQ;IACR,QAAQ;IACR,UAAU;IACV,WAAW;IACX,WAAW;IACX,MAAM;IACN,SAAS;CACZ,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,WAAW,GAAqB,CAAC,OAAO,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC;AAE7E,iFAAiF;AACjF,MAAM,CAAC,MAAM,UAAU,GAAW,UAAU,CAAC;AAE7C;;;;;GAKG;AACH,MAAM,CAAC,MAAM,cAAc,GAAW,mBAAmB,CAAC;AA2D1D,MAAM,CAAC,MAAM,WAAW,GAAW,qBAAqB,CAAC;AACzD,qGAAqG;AACrG,MAAM,CAAC,MAAM,UAAU,GAAW,gBAAgB,CAAC;AACnD,MAAM,CAAC,MAAM,aAAa,GAAW,0BAA0B,CAAC;AAEhE;;;GAGG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAW,kBAAkB,CAAC;AAElE;;;;;;;;;;;;GAYG;AACH,4FAA4F;AAC5F,MAAM,CAAC,MAAM,UAAU,GAAW,gBAAgB,CAAC;AAEnD,MAAM,CAAC,MAAM,YAAY,GAAW,yBAAyB,CAAC;AAC9D,MAAM,CAAC,MAAM,aAAa,GAAW,aAAa,CAAC;AACnD,MAAM,CAAC,MAAM,YAAY,GAAW,YAAY,CAAC;AAEjD;;;;GAIG;AACH,MAAM,CAAC,MAAM,cAAc,GAAoC;IAC3D,IAAI,EAAE,mBAAmB;IACzB,KAAK,EAAE,UAAU;IACjB,QAAQ,EAAE,UAAU;IACpB,MAAM,EAAE,UAAU;IAClB,KAAK,EAAE,eAAe;IACtB,QAAQ,EAAE,YAAY;IACtB,MAAM,EAAE,OAAO;IACf,MAAM,EAAE,gBAAgB;IACxB,QAAQ,EAAE,kBAAkB;IAC5B,SAAS,EAAE,mBAAmB;IAC9B,SAAS,EAAE,0BAA0B;IACrC,IAAI,EAAE,MAAM;IACZ,sFAAsF;IACtF,yCAAyC;IACzC,OAAO,EAAE,MAAM;CAClB,CAAC;AAEF,MAAM,OAAO,WAAY,SAAQ,KAAK;CAAG;AAEzC,MAAM,QAAQ,GAAyD,CAAC,KAAc,EAAoC,EAAE,CACxH,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAEzE,SAAS,SAAS,CAAC,KAAc,EAAE,KAAa;IAC5C,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACtB,OAAO,EAAE,CAAC;IACd,CAAC;IACD,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACnB,MAAM,IAAI,WAAW,CAAC,GAAG,KAAK,qCAAqC,CAAC,CAAC;IACzE,CAAC;IACD,MAAM,MAAM,GAA2B,EAAE,CAAC;IAC1C,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAC/C,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC5B,MAAM,IAAI,WAAW,CAAC,GAAG,KAAK,IAAI,GAAG,oBAAoB,CAAC,CAAC;QAC/D,CAAC;QACD,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;IACxB,CAAC;IAED,OAAO,MAAM,CAAC;AAClB,CAAC;AAED,iGAAiG;AACjG,SAAS,MAAM,CAAC,KAAc,EAAE,KAAa;IACzC,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QACxC,OAAO,EAAE,CAAC;IACd,CAAC;IACD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC5B,MAAM,IAAI,WAAW,CAAC,GAAG,KAAK,oBAAoB,CAAC,CAAC;IACxD,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC;AACxB,CAAC;AAED,SAAS,MAAM,CAAC,KAAc,EAAE,KAAa;IACzC,MAAM,IAAI,GAAY,KAAK,IAAI,EAAE,CAAC;IAClC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC,KAAc,EAAW,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC,EAAE,CAAC;QAC5F,MAAM,IAAI,WAAW,CAAC,GAAG,KAAK,6CAA6C,CAAC,CAAC;IACjF,CAAC;IAED,OAAO,IAAgB,CAAC;AAC5B,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,IAAY;IACpC,IAAI,GAAY,CAAC;IACjB,IAAI,CAAC;QACD,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC3B,CAAC;IAAC,OAAO,KAAc,EAAE,CAAC;QACtB,MAAM,IAAI,WAAW,CAAC,GAAG,WAAW,+BAAgC,KAAe,CAAC,OAAO,EAAE,CAAC,CAAC;IACnG,CAAC;IACD,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACjB,MAAM,IAAI,WAAW,CAAC,GAAG,WAAW,oBAAoB,CAAC,CAAC;IAC9D,CAAC;IAED,MAAM,MAAM,GAA2B,EAAE,GAAG,cAAc,EAAE,GAAG,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC,EAAE,CAAC;IACpG,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;QACrC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAa,CAAC,EAAE,CAAC;YACjC,MAAM,IAAI,WAAW,CAAC,UAAU,IAAI,qCAAqC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACjG,CAAC;IACL,CAAC;IAED,MAAM,IAAI,GAAsB,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC;IAC5D,MAAM,IAAI,GAAsB,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC;IAE5D,4FAA4F;IAC5F,8EAA8E;IAC9E,MAAM,OAAO,GAA2B,EAAE,CAAC;IAC3C,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACvB,OAAO,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;IAC5D,CAAC;IAED,OAAO;QACH,IAAI;QACJ,IAAI;QACJ,IAAI,EAAE,EAAE,GAAG,OAAO,EAAE,GAAG,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,EAAE;QACvD,MAAM,EAAE,MAA+B;QACvC,QAAQ,EAAE,SAAS,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,UAAU,CAAC;QAChD,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC;QAC9B,qFAAqF;QACrF,OAAO,EAAE,GAAG,CAAC,SAAS,CAAC,KAAK,KAAK;QACjC,QAAQ,EAAE,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,UAAU,CAAC;QAC7C,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC;QACvC,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;QACpC,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QACjC,WAAW,EAAE,MAAM,CAAC,GAAG,CAAC,aAAa,CAAC,EAAE,aAAa,CAAC,IAAI,uBAAuB;KACpF,CAAC;AACN,CAAC;AAED,uFAAuF;AACvF,MAAM,UAAU,UAAU,CAAC,IAAY;IACnC,IAAI,CAAC;QACD,OAAO,WAAW,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;IACtE,CAAC;IAAC,OAAO,KAAc,EAAE,CAAC;QACtB,IAAI,KAAK,YAAY,WAAW,EAAE,CAAC;YAC/B,MAAM,KAAK,CAAC;QAChB,CAAC;QAED,OAAO,IAAI,CAAC;IAChB,CAAC;AACL,CAAC","sourcesContent":["/**\n * Конфиг проекта — `.claude/rt-kit.json`.\n *\n * Всё, что пакет не может знать сам: значения для дырок, куда класть каждый род ресурса и от\n * чего проект отказался. Конфиг лежит в дереве проекта и коммитится: раскладка обязана\n * повторяться на чужой машине без вопросов, иначе разложенное расходится между разработчиками.\n */\nimport { readFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\n/** Род ресурса. Он же имя каталога в `assets/` пакета и ключ раскладки. */\nexport type TKind =\n | 'laws'\n | 'rules'\n | 'patterns'\n | 'skills'\n | 'hooks'\n | 'defaults'\n | 'checks'\n | 'agents'\n | 'commands'\n | 'workflows'\n | 'templates'\n /** Документы дерева, которые читает и человек, и агент: словарь проекта. */\n | 'docs'\n /**\n * Образцы, которые копируют в рабочий файл и заполняют: папка задачи, спек домена.\n *\n * От шаблонов отличаются читателем и местом. Шаблон ложится в каталог слоя правил, и с него\n * пишут ресурс этого слоя; образец ложится туда, где идёт работа, и копирует его тот, кто\n * работу ведёт. Одним родом их не свести: дерево вправе отказаться от одних, оставив\n * другие, и назвать им разные каталоги.\n */\n | 'samples';\n\nexport const KINDS: readonly TKind[] = [\n 'laws',\n 'rules',\n 'patterns',\n 'skills',\n 'hooks',\n 'defaults',\n 'checks',\n 'agents',\n 'commands',\n 'workflows',\n 'templates',\n 'docs',\n 'samples',\n];\n\n/**\n * Роды, которые ложатся не файлом в каталог, а каталогом по имени ресурса: скил читается как\n * `<имя>/SKILL.md`, и рядом с ним лежит то, что пишет проект.\n *\n * `skills` — скил, у которого нет закона над собой: он не про то, что должно быть верно в\n * продукте, а про то, как здесь делается работа. Правилом его называть нельзя — тогда придётся\n * выдумать ему закон, — а паттерном тем более: паттерн стоит при правиле.\n */\nexport const SKILL_KINDS: readonly TKind[] = ['rules', 'patterns', 'skills'];\n\n/** Имя файла скила: так его ищет агент, и другого имени у него быть не может. */\nexport const SKILL_FILE: string = 'SKILL.md';\n\n/**\n * Файл-компаньон правила: чем названо в этом дереве то, о чём правило говорит приёмом.\n *\n * Пакет знает приём, но не знает ни путей, ни имён — их пишет проект. Черновик кладётся один\n * раз и дальше не сверяется: это единственный файл раскладки, который принадлежит проекту.\n */\nexport const COMPANION_FILE: string = 'implementation.md';\n\nexport interface IConfig {\n /** Значения дырок `{{имя}}`. */\n readonly vars: Readonly<Record<string, string>>;\n /** Куда класть каждый род ресурса, путями от корня проекта. */\n readonly layout: Readonly<Record<TKind, string>>;\n /**\n * Ресурсы, которые проект выбрал, идентификаторами: `laws/access.md`. Пусто — берётся всё.\n * Ограничивает только те роды, которые сам называет; как именно — `isChosen`.\n */\n readonly only: readonly string[];\n /** Ресурсы, от которых проект отказался, идентификаторами: `laws/application/money.md`. */\n readonly skip: readonly string[];\n /**\n * Выбор проекта по осям различия: `{ \"host\": \"gitlab\" }`. Оси объявлены пакетом в\n * `assets/variants.json`, а ресурс с вариантом в имени берётся, только если проект выбрал\n * его значение; как именно — `isChosen`.\n */\n readonly variants: Readonly<Record<string, string>>;\n /**\n * Что у дерева есть: `[\"packages\", \"app\"]`. Свойства объявлены пакетом в `assets/traits.json`,\n * а ресурс, требующий свойства именем файла, дереву без него не кладётся вовсе — ни сам, ни\n * черновик его компаньона. Пусто — дерево о себе ничего не сказало, и помеченного не получает.\n */\n readonly has: readonly string[];\n /**\n * Пишут ли гарды наблюдения о слое правил. Умолчание — да; выключает дерево, и целиком, а\n * не частями. Ключ читают двое: сводка — отсюда, гард — тем же именем прямо из файла, потому\n * что разборщика конфига у него нет.\n */\n readonly observe: boolean;\n /**\n * Роли слоя правил, которые дерево выключило, именами файлов без расширения:\n * `[\"strict-teacher\"]`. Выключается обязательность вызова, а не сама роль: файл роли\n * остаётся разложенным и зовётся руками, а гард при выключенной роли выходит молча.\n * Ключ читают двое: сводка — отсюда, гард — тем же именем прямо из файла, потому что\n * разборщика конфига у него нет.\n */\n readonly rolesOff: readonly string[];\n /**\n * Адрес приёма, куда уезжает груз: `https://…`. Пусто — отправлять некуда, и отправка\n * отказывает вместо молчания. Зашитый в код адрес назвал бы чужое дерево в текстах пакета.\n */\n readonly intake: string;\n /**\n * Файл с токеном дерева — путь от корня дерева или от домашнего каталога (`~/…`). Сам токен\n * в конфиге не лежит: конфиг коммитится, а токен обязан остаться вне истории.\n */\n readonly token: string;\n /**\n * Признак дерева, когда удалённого репозитория нет. При нём — считается из адреса\n * репозитория: две рабочие копии одного репозитория обязаны дать один признак.\n */\n readonly tree: string;\n /** Где дерево держит разборы происшествий. Путь от корня дерева. */\n readonly postmortems: string;\n}\n\nexport const CONFIG_PATH: string = '.claude/rt-kit.json';\n/** Каталог, в котором дерево держит своё при пакете: надстройки, умолчания, свою карту и профиль. */\nexport const RT_KIT_DIR: string = '.claude/rt-kit';\nexport const OVERRIDES_DIR: string = '.claude/rt-kit/overrides';\n\n/**\n * Где разборы происшествий лежат, когда конфиг не сказал иначе. Роды ресурсов сюда не годятся:\n * разборы пишет дерево про себя, пакет их не раскладывает и не сверяет.\n */\nexport const DEFAULT_POSTMORTEMS_DIR: string = 'docs/postmortems';\n\n/**\n * Карта «что правится — какое правило» и профиль дерева: команды, стенды, пары «правка —\n * документ». Оба лежат в двух видах, и это не дублирование, а разделение ответственности.\n *\n * Умолчание везёт пакет: проекты этой мастерской устроены одинаково — Nx, те же расширения, те\n * же имена каталогов, — и переписывать одну и ту же карту в каждом дереве заново значило бы\n * заводить пятнадцать её редакций, расходящихся молча. Оно разложено, под шапкой, и правится\n * не на месте.\n *\n * Надстройку пишет проект, и она необязательна: витрина, свой род файлов, другой запускатель\n * тестов есть не у всех. Объявленная в ней функция замещает умолчание целиком и вправе позвать\n * его обратно суффиксом `_default` — так дерево дописывает своё, не теряя общего.\n */\n/** Каталог правил дерева: законы туда не идут, а правила, паттерны и скилы лежат вместе. */\nexport const SKILLS_DIR: string = '.claude/skills';\n\nexport const DEFAULTS_DIR: string = '.claude/rt-kit/defaults';\nexport const GATE_MAP_FILE: string = 'gate-map.sh';\nexport const PROFILE_FILE: string = 'project.sh';\n\n/**\n * Умолчания раскладки. Это не единственно возможные пути, но менять их без нужды не стоит:\n * агент ищет законы в `docs/constitution`, потому что так написано в правилах, которые пакет\n * же и везёт.\n */\nexport const DEFAULT_LAYOUT: Readonly<Record<TKind, string>> = {\n laws: 'docs/constitution',\n rules: SKILLS_DIR,\n patterns: SKILLS_DIR,\n skills: SKILLS_DIR,\n hooks: '.claude/hooks',\n defaults: DEFAULTS_DIR,\n checks: 'tools',\n agents: '.claude/agents',\n commands: '.claude/commands',\n workflows: '.claude/workflows',\n templates: '.claude/rt-kit/templates',\n docs: 'docs',\n // Внутри рода путь повторяет раскладку дерева — папки задач и спеки, — поэтому одного\n // умолчания хватает всем образцам сразу.\n samples: 'docs',\n};\n\nexport class ConfigError extends Error {}\n\nconst isRecord: (value: unknown) => value is Record<string, unknown> = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\nfunction stringMap(value: unknown, where: string): Record<string, string> {\n if (value === undefined) {\n return {};\n }\n if (!isRecord(value)) {\n throw new ConfigError(`${where}: ожидается объект «имя — значение»`);\n }\n const result: Record<string, string> = {};\n for (const [key, entry] of Object.entries(value)) {\n if (typeof entry !== 'string') {\n throw new ConfigError(`${where}.${key}: ожидается строка`);\n }\n result[key] = entry;\n }\n\n return result;\n}\n\n/** Строковый ключ конфига. Ключа нет — пустая строка: значение необязательное у всех четырёх. */\nfunction textOf(value: unknown, where: string): string {\n if (value === undefined || value === null) {\n return '';\n }\n if (typeof value !== 'string') {\n throw new ConfigError(`${where}: ожидается строка`);\n }\n\n return value.trim();\n}\n\nfunction idList(value: unknown, where: string): readonly string[] {\n const list: unknown = value ?? [];\n if (!Array.isArray(list) || list.some((entry: unknown): boolean => typeof entry !== 'string')) {\n throw new ConfigError(`${where}: ожидается список идентификаторов ресурсов`);\n }\n\n return list as string[];\n}\n\nexport function parseConfig(text: string): IConfig {\n let raw: unknown;\n try {\n raw = JSON.parse(text);\n } catch (error: unknown) {\n throw new ConfigError(`${CONFIG_PATH} — не разбирается как JSON: ${(error as Error).message}`);\n }\n if (!isRecord(raw)) {\n throw new ConfigError(`${CONFIG_PATH}: ожидается объект`);\n }\n\n const layout: Record<string, string> = { ...DEFAULT_LAYOUT, ...stringMap(raw['layout'], 'layout') };\n for (const kind of Object.keys(layout)) {\n if (!KINDS.includes(kind as TKind)) {\n throw new ConfigError(`layout.${kind}: такого рода ресурсов нет — есть ${KINDS.join(', ')}`);\n }\n }\n\n const only: readonly string[] = idList(raw['only'], 'only');\n const skip: readonly string[] = idList(raw['skip'], 'skip');\n\n // Пути раскладки — тоже значения проекта, и пакет их уже знает. Требовать их вторым списком\n // значило бы держать одно и то же в двух местах: расходятся такие пары молча.\n const derived: Record<string, string> = {};\n for (const kind of KINDS) {\n derived[`${kind.replace(/s$/, '')}sDir`] = layout[kind];\n }\n\n return {\n only,\n skip,\n vars: { ...derived, ...stringMap(raw['vars'], 'vars') },\n layout: layout as Record<TKind, string>,\n variants: stringMap(raw['variants'], 'variants'),\n has: idList(raw['has'], 'has'),\n // Выключателем считается только явное «нет»: ключа нет — запись идёт, как и у гарда.\n observe: raw['observe'] !== false,\n rolesOff: idList(raw['rolesOff'], 'rolesOff'),\n intake: textOf(raw['intake'], 'intake'),\n token: textOf(raw['token'], 'token'),\n tree: textOf(raw['tree'], 'tree'),\n postmortems: textOf(raw['postmortems'], 'postmortems') || DEFAULT_POSTMORTEMS_DIR,\n };\n}\n\n/** Конфиг проекта; его отсутствие — не отказ, а повод сказать про `agent-kit init`. */\nexport function readConfig(root: string): IConfig | null {\n try {\n return parseConfig(readFileSync(join(root, CONFIG_PATH), 'utf8'));\n } catch (error: unknown) {\n if (error instanceof ConfigError) {\n throw error;\n }\n\n return null;\n }\n}\n"]}
|
package/lib/observations.d.ts
CHANGED
|
@@ -21,15 +21,44 @@ export interface ICount {
|
|
|
21
21
|
readonly name: string;
|
|
22
22
|
readonly count: number;
|
|
23
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Вес ресурса на диске, байтами: имя правила в число.
|
|
26
|
+
*
|
|
27
|
+
* Приходит доводом, а не читается здесь, по той же причине, что и день: разбор отделён от диска,
|
|
28
|
+
* и сводку, читающую каталог правил сама, пришлось бы проверять подложенными файлами.
|
|
29
|
+
*/
|
|
30
|
+
export type TWeights = Readonly<Record<string, number>>;
|
|
24
31
|
export interface ISummary {
|
|
25
32
|
readonly days: number;
|
|
26
33
|
/** Сколько заходов оставило хоть одно наблюдение. */
|
|
27
34
|
readonly sessions: number;
|
|
28
35
|
readonly loads: readonly ICount[];
|
|
36
|
+
/**
|
|
37
|
+
* Вес того, что заходы прочитали за отрезок, байтами. Каждая загрузка считается своим весом:
|
|
38
|
+
* правило, открытое в трёх заходах, стоило места трижды — тем, что оно одно, это не дешевле.
|
|
39
|
+
* Ресурс, веса которого не назвали, считается нулём: врать числом хуже, чем не знать его.
|
|
40
|
+
*/
|
|
41
|
+
readonly bytes: number;
|
|
42
|
+
/**
|
|
43
|
+
* Тот же вес, приходящийся на один заход, — цена входа в работу.
|
|
44
|
+
*
|
|
45
|
+
* Ноль заходов даёт ноль, а не отказ: сводка за пустой отрезок печатается наравне с полной,
|
|
46
|
+
* и деление на ноль уронило бы её на том единственном случае, ради которого её и зовут, —
|
|
47
|
+
* «а было ли вообще что-нибудь».
|
|
48
|
+
*/
|
|
49
|
+
readonly bytesPerSession: number;
|
|
29
50
|
/** Отбития гейта по правилам. */
|
|
30
51
|
readonly denials: readonly ICount[];
|
|
31
52
|
/** Род правки, на котором гейт отбивал. */
|
|
32
53
|
readonly kinds: readonly ICount[];
|
|
54
|
+
/**
|
|
55
|
+
* Отбития, пришедшие не на правку файла: род правки — команда оболочки или браузер.
|
|
56
|
+
*
|
|
57
|
+
* Считается здесь, а не выводится читателем из разбивки по родам: доля, которую надо
|
|
58
|
+
* сложить глазами из десятка строк, не читается вовсе — а именно она говорит, требует ли
|
|
59
|
+
* гейт правило под то, что правкой не является.
|
|
60
|
+
*/
|
|
61
|
+
readonly denialsOffFile: number;
|
|
33
62
|
readonly guards: readonly ICount[];
|
|
34
63
|
/**
|
|
35
64
|
* Правила, разложенные в дерево и не загруженные за отрезок ни разу. Мёртвый ресурс иначе
|
|
@@ -67,6 +96,11 @@ export declare function readObservations(root: string, today: string, days: numb
|
|
|
67
96
|
* `known` — имена правил, разложенных в это дерево. Без них сводка отвечает только на вопрос
|
|
68
97
|
* «чем пользовались», а самое ценное — чем не пользовались ни разу — сказать нечем: в самих
|
|
69
98
|
* наблюдениях незагруженного правила нет по определению.
|
|
99
|
+
*
|
|
100
|
+
* `weights` — вес этих правил на диске. Без них сводка говорит, сколько раз правило открывали, и
|
|
101
|
+
* молчит о том, во что это обошлось: тридцать загрузок паттерна и тридцать загрузок правила на
|
|
102
|
+
* пятьсот строк стоят разного, а в счёте выглядят одинаково. Не назван — вес считается нулём, и
|
|
103
|
+
* строки о нём в выводе не будет вовсе.
|
|
70
104
|
*/
|
|
71
|
-
export declare function summarize(observations: readonly IObservation[], known: readonly string[], days: number): ISummary;
|
|
105
|
+
export declare function summarize(observations: readonly IObservation[], known: readonly string[], days: number, weights?: TWeights): ISummary;
|
|
72
106
|
//# sourceMappingURL=observations.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"observations.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/observations.ts"],"names":[],"mappings":"AAgBA,gEAAgE;AAChE,eAAO,MAAM,gBAAgB,EAAE,MAAsC,CAAC;AAEtE,oGAAoG;AACpG,eAAO,MAAM,SAAS,EAAE,MAAW,CAAC;AAEpC,gEAAgE;AAChE,eAAO,MAAM,YAAY,EAAE,MAAU,CAAC;AAEtC,yCAAyC;AACzC,MAAM,MAAM,MAAM,GAAG,YAAY,GAAG,WAAW,GAAG,YAAY,CAAC;AAE/D,MAAM,WAAW,YAAY;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,gEAAgE;IAChE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,8EAA8E;IAC9E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,8DAA8D;IAC9D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC5B;AAED,qFAAqF;AACrF,MAAM,WAAW,MAAM;IACnB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,QAAQ;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,qDAAqD;IACrD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,iCAAiC;IACjC,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,2CAA2C;IAC3C,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CAC1B;AAOD,gGAAgG;AAChG,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAI7D;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAoBlE;AAED,MAAM,WAAW,WAAW;IACxB,QAAQ,CAAC,YAAY,EAAE,SAAS,YAAY,EAAE,CAAC;IAC/C,8EAA8E;IAC9E,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,qDAAqD;IACrD,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;CAC5B;AAsBD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,WAAW,CA8BvF;
|
|
1
|
+
{"version":3,"file":"observations.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/observations.ts"],"names":[],"mappings":"AAgBA,gEAAgE;AAChE,eAAO,MAAM,gBAAgB,EAAE,MAAsC,CAAC;AAEtE,oGAAoG;AACpG,eAAO,MAAM,SAAS,EAAE,MAAW,CAAC;AAEpC,gEAAgE;AAChE,eAAO,MAAM,YAAY,EAAE,MAAU,CAAC;AAEtC,yCAAyC;AACzC,MAAM,MAAM,MAAM,GAAG,YAAY,GAAG,WAAW,GAAG,YAAY,CAAC;AAE/D,MAAM,WAAW,YAAY;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,gEAAgE;IAChE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,8EAA8E;IAC9E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,8DAA8D;IAC9D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC5B;AAED,qFAAqF;AACrF,MAAM,WAAW,MAAM;IACnB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;GAKG;AACH,MAAM,MAAM,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;AAExD,MAAM,WAAW,QAAQ;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,qDAAqD;IACrD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC;;;;OAIG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB;;;;;;OAMG;IACH,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,iCAAiC;IACjC,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,2CAA2C;IAC3C,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC;;;;;;OAMG;IACH,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CAC1B;AAOD,gGAAgG;AAChG,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAI7D;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAoBlE;AAED,MAAM,WAAW,WAAW;IACxB,QAAQ,CAAC,YAAY,EAAE,SAAS,YAAY,EAAE,CAAC;IAC/C,8EAA8E;IAC9E,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,qDAAqD;IACrD,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;CAC5B;AAsBD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,WAAW,CA8BvF;AAuBD;;;;;;;;;;;GAWG;AACH,wBAAgB,SAAS,CAAC,YAAY,EAAE,SAAS,YAAY,EAAE,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,QAAa,GAAG,QAAQ,CAqBzI"}
|
package/lib/observations.js
CHANGED
|
@@ -113,23 +113,35 @@ const countBy = (values) => {
|
|
|
113
113
|
.sort((left, right) => right.count - left.count || left.name.localeCompare(right.name));
|
|
114
114
|
};
|
|
115
115
|
const of = (observations, event) => observations.filter((entry) => entry.event === event);
|
|
116
|
+
/** Роды правки, которые правкой файла не являются: гейт сработал не на файле. */
|
|
117
|
+
const OFF_FILE_KINDS = ['command', 'browser'];
|
|
116
118
|
/**
|
|
117
119
|
* Сводка за отрезок.
|
|
118
120
|
*
|
|
119
121
|
* `known` — имена правил, разложенных в это дерево. Без них сводка отвечает только на вопрос
|
|
120
122
|
* «чем пользовались», а самое ценное — чем не пользовались ни разу — сказать нечем: в самих
|
|
121
123
|
* наблюдениях незагруженного правила нет по определению.
|
|
124
|
+
*
|
|
125
|
+
* `weights` — вес этих правил на диске. Без них сводка говорит, сколько раз правило открывали, и
|
|
126
|
+
* молчит о том, во что это обошлось: тридцать загрузок паттерна и тридцать загрузок правила на
|
|
127
|
+
* пятьсот строк стоят разного, а в счёте выглядят одинаково. Не назван — вес считается нулём, и
|
|
128
|
+
* строки о нём в выводе не будет вовсе.
|
|
122
129
|
*/
|
|
123
|
-
export function summarize(observations, known, days) {
|
|
130
|
+
export function summarize(observations, known, days, weights = {}) {
|
|
124
131
|
const loads = of(observations, 'skill-load');
|
|
125
132
|
const denials = of(observations, 'gate-deny');
|
|
126
133
|
const loaded = new Set(loads.map((entry) => entry.resource));
|
|
134
|
+
const sessions = new Set(observations.map((entry) => entry.session).filter(Boolean)).size;
|
|
135
|
+
const bytes = loads.reduce((found, entry) => found + (weights[entry.resource] ?? 0), 0);
|
|
127
136
|
return {
|
|
128
137
|
days,
|
|
129
|
-
sessions
|
|
138
|
+
sessions,
|
|
139
|
+
bytes,
|
|
130
140
|
loads: countBy(loads.map((entry) => entry.resource)),
|
|
141
|
+
bytesPerSession: sessions > 0 ? Math.round(bytes / sessions) : 0,
|
|
131
142
|
denials: countBy(denials.map((entry) => entry.resource)),
|
|
132
143
|
kinds: countBy(denials.map((entry) => entry.kind)),
|
|
144
|
+
denialsOffFile: denials.filter((entry) => OFF_FILE_KINDS.includes(entry.kind)).length,
|
|
133
145
|
guards: countBy(of(observations, 'guard-deny').map((entry) => entry.resource)),
|
|
134
146
|
unused: known.filter((name) => !loaded.has(name)).sort(byText),
|
|
135
147
|
versions: [...new Set(observations.map((entry) => entry.version).filter(Boolean))].sort(byText),
|
package/lib/observations.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"observations.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/observations.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Наблюдения и сводка по ним.\n *\n * Наблюдения пишет `hooks/observe.sh` — по строке на событие, по файлу на день. Здесь они\n * читаются и сводятся: чем пользовались, чем не пользовались ни разу, обо что спотыкались.\n *\n * Разбор отделён от чтения диска намеренно: сводка — то, ради чего наблюдения ведутся, и\n * проверять её на подложенных файлах значило бы проверять файловую систему.\n *\n * Дата приходит доводом, а не берётся из часов: сводка за отрезок дней иначе не проверяется\n * спекой — вчерашний файл фикстуры завтра оказывается позавчерашним.\n */\nimport { existsSync, readFileSync, readdirSync, rmSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { byText } from './order.js';\n\n/** Куда `observe.sh` пишет наблюдения. Путь от корня дерева. */\nexport const OBSERVATIONS_DIR: string = '.claude/rt-kit/observations';\n\n/** Сколько дней наблюдения лежат. Каталог, который только растёт, через год перестают открывать. */\nexport const KEEP_DAYS: number = 30;\n\n/** За сколько дней сводка отвечает, когда отрезок не назван. */\nexport const DEFAULT_DAYS: number = 3;\n\n/** Роды событий, которые пишут гарды. */\nexport type TEvent = 'skill-load' | 'gate-deny' | 'guard-deny';\n\nexport interface IObservation {\n readonly event: string;\n /** Имя правила или гарда — имя ресурса пакета, и только оно. */\n readonly resource: string;\n /** Род правки: расширение, `command` или `browser`. Пути в наблюдении нет. */\n readonly kind: string;\n /** Признак сессии: считает заходы, имени сессии не выдаёт. */\n readonly session: string;\n readonly version: string;\n}\n\n/** Сколько раз что-то встретилось. Порядок — по убыванию, при равенстве по имени. */\nexport interface ICount {\n readonly name: string;\n readonly count: number;\n}\n\nexport interface ISummary {\n readonly days: number;\n /** Сколько заходов оставило хоть одно наблюдение. */\n readonly sessions: number;\n readonly loads: readonly ICount[];\n /** Отбития гейта по правилам. */\n readonly denials: readonly ICount[];\n /** Род правки, на котором гейт отбивал. */\n readonly kinds: readonly ICount[];\n readonly guards: readonly ICount[];\n /**\n * Правила, разложенные в дерево и не загруженные за отрезок ни разу. Мёртвый ресурс иначе\n * неотличим от работающего — и это единственная строка сводки, которую нельзя получить из\n * самих наблюдений.\n */\n readonly unused: readonly string[];\n readonly versions: readonly string[];\n readonly total: number;\n}\n\n/** Дата файла наблюдений: `2026-08-12.jsonl`. Чужое имя в каталоге не наблюдение. */\nconst DAY_FILE: RegExp = /^(\\d{4}-\\d{2}-\\d{2})\\.jsonl$/;\n\nconst dayOf: (name: string) => string | null = (name: string): string | null => DAY_FILE.exec(name)?.[1] ?? null;\n\n/** День, отстоящий от названного на столько-то суток назад. Считается в UTC — как и пишется. */\nexport function dayBefore(today: string, days: number): string {\n const at: number = Date.parse(`${today}T00:00:00Z`);\n\n return Number.isNaN(at) ? today : new Date(at - days * 86_400_000).toISOString().slice(0, 10);\n}\n\n/**\n * Одна строка наблюдения. Битая пропускается молча: наблюдение — побочная запись гарда, и\n * оборванная на полуслове строка не имеет права уронить сводку.\n */\nexport function parseObservation(line: string): IObservation | null {\n if (!line.trim()) {\n return null;\n }\n try {\n const found: Record<string, unknown> = JSON.parse(line) as Record<string, unknown>;\n const event: unknown = found['ev'];\n\n return typeof event === 'string' && event\n ? {\n event,\n resource: typeof found['res'] === 'string' ? found['res'] : '',\n kind: typeof found['kind'] === 'string' ? found['kind'] : '',\n session: typeof found['sid'] === 'string' ? found['sid'] : '',\n version: typeof found['v'] === 'string' ? found['v'] : '',\n }\n : null;\n } catch {\n return null;\n }\n}\n\nexport interface IReadResult {\n readonly observations: readonly IObservation[];\n /** Файлы, снятые по сроку хранения: их называют вслух, а не убирают молча. */\n readonly swept: readonly string[];\n /** Каталога нет вовсе — записи не велось ни разу. */\n readonly silent: boolean;\n}\n\n/** Раньше ли день черты. Дни записаны `ГГГГ-ММ-ДД`, и порядок строк у них хронологический. */\nfunction isBefore(day: string, edge: string): boolean {\n return byText(day, edge) < 0;\n}\n\n/** Записи одного дня из его файла. Строка, которая не разбирается, пропускается молча. */\nfunction observationsOfDay(path: string): readonly IObservation[] {\n const found: IObservation[] = [];\n\n for (const line of readFileSync(path, 'utf8').split('\\n')) {\n const one: IObservation | null = parseObservation(line);\n\n if (one) {\n found.push(one);\n }\n }\n\n return found;\n}\n\n/**\n * Наблюдения за отрезок и уборка того, что старше срока хранения.\n *\n * Уборка идёт здесь, а не отдельной командой: каталог растёт от работы, а не от запусков, и\n * команда, о которой надо помнить, до второго месяца не доживает.\n */\nexport function readObservations(root: string, today: string, days: number): IReadResult {\n const dir: string = join(root, OBSERVATIONS_DIR);\n if (!existsSync(dir)) {\n return { observations: [], swept: [], silent: true };\n }\n\n const since: string = dayBefore(today, days - 1);\n const keepFrom: string = dayBefore(today, KEEP_DAYS);\n const observations: IObservation[] = [];\n const swept: string[] = [];\n\n for (const name of readdirSync(dir).sort(byText)) {\n const day: string | null = dayOf(name);\n\n if (!day) {\n continue;\n }\n\n if (isBefore(day, keepFrom)) {\n rmSync(join(dir, name), { force: true });\n swept.push(name);\n } else if (isBefore(day, since)) {\n // День старше запрошенного отрезка, но моложе черты хранения: файл остаётся лежать,\n // а в этот отчёт не идёт\n } else {\n observations.push(...observationsOfDay(join(dir, name)));\n }\n }\n\n return { observations, swept, silent: false };\n}\n\nconst countBy: (values: readonly string[]) => readonly ICount[] = (values: readonly string[]): readonly ICount[] => {\n const counted: Map<string, number> = new Map();\n for (const value of values) {\n if (value) {\n counted.set(value, (counted.get(value) ?? 0) + 1);\n }\n }\n\n return [...counted]\n .map(([name, count]: [string, number]): ICount => ({ name, count }))\n .sort((left: ICount, right: ICount): number => right.count - left.count || left.name.localeCompare(right.name));\n};\n\nconst of: (observations: readonly IObservation[], event: TEvent) => readonly IObservation[] = (\n observations: readonly IObservation[],\n event: TEvent\n): readonly IObservation[] => observations.filter((entry: IObservation): boolean => entry.event === event);\n\n/**\n * Сводка за отрезок.\n *\n * `known` — имена правил, разложенных в это дерево. Без них сводка отвечает только на вопрос\n * «чем пользовались», а самое ценное — чем не пользовались ни разу — сказать нечем: в самих\n * наблюдениях незагруженного правила нет по определению.\n */\nexport function summarize(observations: readonly IObservation[], known: readonly string[], days: number): ISummary {\n const loads: readonly IObservation[] = of(observations, 'skill-load');\n const denials: readonly IObservation[] = of(observations, 'gate-deny');\n const loaded: Set<string> = new Set(loads.map((entry: IObservation): string => entry.resource));\n\n return {\n days,\n sessions: new Set(observations.map((entry: IObservation): string => entry.session).filter(Boolean)).size,\n loads: countBy(loads.map((entry: IObservation): string => entry.resource)),\n denials: countBy(denials.map((entry: IObservation): string => entry.resource)),\n kinds: countBy(denials.map((entry: IObservation): string => entry.kind)),\n guards: countBy(of(observations, 'guard-deny').map((entry: IObservation): string => entry.resource)),\n unused: known.filter((name: string): boolean => !loaded.has(name)).sort(byText),\n versions: [...new Set(observations.map((entry: IObservation): string => entry.version).filter(Boolean))].sort(byText),\n total: observations.length,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"observations.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/observations.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Наблюдения и сводка по ним.\n *\n * Наблюдения пишет `hooks/observe.sh` — по строке на событие, по файлу на день. Здесь они\n * читаются и сводятся: чем пользовались, чем не пользовались ни разу, обо что спотыкались.\n *\n * Разбор отделён от чтения диска намеренно: сводка — то, ради чего наблюдения ведутся, и\n * проверять её на подложенных файлах значило бы проверять файловую систему.\n *\n * Дата приходит доводом, а не берётся из часов: сводка за отрезок дней иначе не проверяется\n * спекой — вчерашний файл фикстуры завтра оказывается позавчерашним.\n */\nimport { existsSync, readFileSync, readdirSync, rmSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { byText } from './order.js';\n\n/** Куда `observe.sh` пишет наблюдения. Путь от корня дерева. */\nexport const OBSERVATIONS_DIR: string = '.claude/rt-kit/observations';\n\n/** Сколько дней наблюдения лежат. Каталог, который только растёт, через год перестают открывать. */\nexport const KEEP_DAYS: number = 30;\n\n/** За сколько дней сводка отвечает, когда отрезок не назван. */\nexport const DEFAULT_DAYS: number = 3;\n\n/** Роды событий, которые пишут гарды. */\nexport type TEvent = 'skill-load' | 'gate-deny' | 'guard-deny';\n\nexport interface IObservation {\n readonly event: string;\n /** Имя правила или гарда — имя ресурса пакета, и только оно. */\n readonly resource: string;\n /** Род правки: расширение, `command` или `browser`. Пути в наблюдении нет. */\n readonly kind: string;\n /** Признак сессии: считает заходы, имени сессии не выдаёт. */\n readonly session: string;\n readonly version: string;\n}\n\n/** Сколько раз что-то встретилось. Порядок — по убыванию, при равенстве по имени. */\nexport interface ICount {\n readonly name: string;\n readonly count: number;\n}\n\n/**\n * Вес ресурса на диске, байтами: имя правила в число.\n *\n * Приходит доводом, а не читается здесь, по той же причине, что и день: разбор отделён от диска,\n * и сводку, читающую каталог правил сама, пришлось бы проверять подложенными файлами.\n */\nexport type TWeights = Readonly<Record<string, number>>;\n\nexport interface ISummary {\n readonly days: number;\n /** Сколько заходов оставило хоть одно наблюдение. */\n readonly sessions: number;\n readonly loads: readonly ICount[];\n /**\n * Вес того, что заходы прочитали за отрезок, байтами. Каждая загрузка считается своим весом:\n * правило, открытое в трёх заходах, стоило места трижды — тем, что оно одно, это не дешевле.\n * Ресурс, веса которого не назвали, считается нулём: врать числом хуже, чем не знать его.\n */\n readonly bytes: number;\n /**\n * Тот же вес, приходящийся на один заход, — цена входа в работу.\n *\n * Ноль заходов даёт ноль, а не отказ: сводка за пустой отрезок печатается наравне с полной,\n * и деление на ноль уронило бы её на том единственном случае, ради которого её и зовут, —\n * «а было ли вообще что-нибудь».\n */\n readonly bytesPerSession: number;\n /** Отбития гейта по правилам. */\n readonly denials: readonly ICount[];\n /** Род правки, на котором гейт отбивал. */\n readonly kinds: readonly ICount[];\n /**\n * Отбития, пришедшие не на правку файла: род правки — команда оболочки или браузер.\n *\n * Считается здесь, а не выводится читателем из разбивки по родам: доля, которую надо\n * сложить глазами из десятка строк, не читается вовсе — а именно она говорит, требует ли\n * гейт правило под то, что правкой не является.\n */\n readonly denialsOffFile: number;\n readonly guards: readonly ICount[];\n /**\n * Правила, разложенные в дерево и не загруженные за отрезок ни разу. Мёртвый ресурс иначе\n * неотличим от работающего — и это единственная строка сводки, которую нельзя получить из\n * самих наблюдений.\n */\n readonly unused: readonly string[];\n readonly versions: readonly string[];\n readonly total: number;\n}\n\n/** Дата файла наблюдений: `2026-08-12.jsonl`. Чужое имя в каталоге не наблюдение. */\nconst DAY_FILE: RegExp = /^(\\d{4}-\\d{2}-\\d{2})\\.jsonl$/;\n\nconst dayOf: (name: string) => string | null = (name: string): string | null => DAY_FILE.exec(name)?.[1] ?? null;\n\n/** День, отстоящий от названного на столько-то суток назад. Считается в UTC — как и пишется. */\nexport function dayBefore(today: string, days: number): string {\n const at: number = Date.parse(`${today}T00:00:00Z`);\n\n return Number.isNaN(at) ? today : new Date(at - days * 86_400_000).toISOString().slice(0, 10);\n}\n\n/**\n * Одна строка наблюдения. Битая пропускается молча: наблюдение — побочная запись гарда, и\n * оборванная на полуслове строка не имеет права уронить сводку.\n */\nexport function parseObservation(line: string): IObservation | null {\n if (!line.trim()) {\n return null;\n }\n try {\n const found: Record<string, unknown> = JSON.parse(line) as Record<string, unknown>;\n const event: unknown = found['ev'];\n\n return typeof event === 'string' && event\n ? {\n event,\n resource: typeof found['res'] === 'string' ? found['res'] : '',\n kind: typeof found['kind'] === 'string' ? found['kind'] : '',\n session: typeof found['sid'] === 'string' ? found['sid'] : '',\n version: typeof found['v'] === 'string' ? found['v'] : '',\n }\n : null;\n } catch {\n return null;\n }\n}\n\nexport interface IReadResult {\n readonly observations: readonly IObservation[];\n /** Файлы, снятые по сроку хранения: их называют вслух, а не убирают молча. */\n readonly swept: readonly string[];\n /** Каталога нет вовсе — записи не велось ни разу. */\n readonly silent: boolean;\n}\n\n/** Раньше ли день черты. Дни записаны `ГГГГ-ММ-ДД`, и порядок строк у них хронологический. */\nfunction isBefore(day: string, edge: string): boolean {\n return byText(day, edge) < 0;\n}\n\n/** Записи одного дня из его файла. Строка, которая не разбирается, пропускается молча. */\nfunction observationsOfDay(path: string): readonly IObservation[] {\n const found: IObservation[] = [];\n\n for (const line of readFileSync(path, 'utf8').split('\\n')) {\n const one: IObservation | null = parseObservation(line);\n\n if (one) {\n found.push(one);\n }\n }\n\n return found;\n}\n\n/**\n * Наблюдения за отрезок и уборка того, что старше срока хранения.\n *\n * Уборка идёт здесь, а не отдельной командой: каталог растёт от работы, а не от запусков, и\n * команда, о которой надо помнить, до второго месяца не доживает.\n */\nexport function readObservations(root: string, today: string, days: number): IReadResult {\n const dir: string = join(root, OBSERVATIONS_DIR);\n if (!existsSync(dir)) {\n return { observations: [], swept: [], silent: true };\n }\n\n const since: string = dayBefore(today, days - 1);\n const keepFrom: string = dayBefore(today, KEEP_DAYS);\n const observations: IObservation[] = [];\n const swept: string[] = [];\n\n for (const name of readdirSync(dir).sort(byText)) {\n const day: string | null = dayOf(name);\n\n if (!day) {\n continue;\n }\n\n if (isBefore(day, keepFrom)) {\n rmSync(join(dir, name), { force: true });\n swept.push(name);\n } else if (isBefore(day, since)) {\n // День старше запрошенного отрезка, но моложе черты хранения: файл остаётся лежать,\n // а в этот отчёт не идёт\n } else {\n observations.push(...observationsOfDay(join(dir, name)));\n }\n }\n\n return { observations, swept, silent: false };\n}\n\nconst countBy: (values: readonly string[]) => readonly ICount[] = (values: readonly string[]): readonly ICount[] => {\n const counted: Map<string, number> = new Map();\n for (const value of values) {\n if (value) {\n counted.set(value, (counted.get(value) ?? 0) + 1);\n }\n }\n\n return [...counted]\n .map(([name, count]: [string, number]): ICount => ({ name, count }))\n .sort((left: ICount, right: ICount): number => right.count - left.count || left.name.localeCompare(right.name));\n};\n\nconst of: (observations: readonly IObservation[], event: TEvent) => readonly IObservation[] = (\n observations: readonly IObservation[],\n event: TEvent\n): readonly IObservation[] => observations.filter((entry: IObservation): boolean => entry.event === event);\n\n/** Роды правки, которые правкой файла не являются: гейт сработал не на файле. */\nconst OFF_FILE_KINDS: readonly string[] = ['command', 'browser'];\n\n/**\n * Сводка за отрезок.\n *\n * `known` — имена правил, разложенных в это дерево. Без них сводка отвечает только на вопрос\n * «чем пользовались», а самое ценное — чем не пользовались ни разу — сказать нечем: в самих\n * наблюдениях незагруженного правила нет по определению.\n *\n * `weights` — вес этих правил на диске. Без них сводка говорит, сколько раз правило открывали, и\n * молчит о том, во что это обошлось: тридцать загрузок паттерна и тридцать загрузок правила на\n * пятьсот строк стоят разного, а в счёте выглядят одинаково. Не назван — вес считается нулём, и\n * строки о нём в выводе не будет вовсе.\n */\nexport function summarize(observations: readonly IObservation[], known: readonly string[], days: number, weights: TWeights = {}): ISummary {\n const loads: readonly IObservation[] = of(observations, 'skill-load');\n const denials: readonly IObservation[] = of(observations, 'gate-deny');\n const loaded: Set<string> = new Set(loads.map((entry: IObservation): string => entry.resource));\n const sessions: number = new Set(observations.map((entry: IObservation): string => entry.session).filter(Boolean)).size;\n const bytes: number = loads.reduce((found: number, entry: IObservation): number => found + (weights[entry.resource] ?? 0), 0);\n\n return {\n days,\n sessions,\n bytes,\n loads: countBy(loads.map((entry: IObservation): string => entry.resource)),\n bytesPerSession: sessions > 0 ? Math.round(bytes / sessions) : 0,\n denials: countBy(denials.map((entry: IObservation): string => entry.resource)),\n kinds: countBy(denials.map((entry: IObservation): string => entry.kind)),\n denialsOffFile: denials.filter((entry: IObservation): boolean => OFF_FILE_KINDS.includes(entry.kind)).length,\n guards: countBy(of(observations, 'guard-deny').map((entry: IObservation): string => entry.resource)),\n unused: known.filter((name: string): boolean => !loaded.has(name)).sort(byText),\n versions: [...new Set(observations.map((entry: IObservation): string => entry.version).filter(Boolean))].sort(byText),\n total: observations.length,\n };\n}\n"]}
|
package/lib/ship.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { IIntakeAccepted } from './cargo.js';
|
|
2
|
+
import { TMarkCall } from './cargo-state.js';
|
|
2
3
|
/**
|
|
3
4
|
* Предел ожидания приёма. Без него молчащая — не отказавшая — служба держит отправку до
|
|
4
5
|
* умолчания среды, а это минуты: человек, позвавший команду, видит не отказ, а зависший запуск.
|
|
@@ -36,4 +37,6 @@ export declare function intakeUrl(intake: string, operation: string): string;
|
|
|
36
37
|
export declare function readToken(root: string, spoken: string): string;
|
|
37
38
|
/** Запрос приёма. Токен уезжает заголовком и в теле не появляется ни разу. */
|
|
38
39
|
export declare const httpShip: TShip;
|
|
40
|
+
/** Запрос правки состояния. Токен уезжает заголовком и в теле не появляется ни разу. */
|
|
41
|
+
export declare const httpMark: TMarkCall;
|
|
39
42
|
//# sourceMappingURL=ship.d.ts.map
|
package/lib/ship.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ship.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/ship.ts"],"names":[],"mappings":"AAcA,OAAO,
|
|
1
|
+
{"version":3,"file":"ship.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/ship.ts"],"names":[],"mappings":"AAcA,OAAO,EAA2D,eAAe,EAAsC,MAAM,YAAY,CAAC;AAC1I,OAAO,EAAW,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAEtD;;;GAGG;AACH,eAAO,MAAM,eAAe,EAAE,MAAe,CAAC;AAE9C,4EAA4E;AAC5E,MAAM,WAAW,SAAS;IACtB,gGAAgG;IAChG,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,8EAA8E;IAC9E,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;CAC1B;AAED,+FAA+F;AAC/F,MAAM,WAAW,QAAQ;IACrB,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB,0FAA0F;IAC1F,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,uFAAuF;IACvF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,yFAAyF;IACzF,QAAQ,CAAC,QAAQ,EAAE,eAAe,GAAG,IAAI,CAAC;CAC7C;AAED,wDAAwD;AACxD,MAAM,MAAM,KAAK,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;AAa9F,qFAAqF;AACrF,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAEnE;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAQ9D;AAqCD,8EAA8E;AAC9E,eAAO,MAAM,QAAQ,EAAE,KAiBtB,CAAC;AAwCF,wFAAwF;AACxF,eAAO,MAAM,QAAQ,EAAE,SAsBtB,CAAC"}
|
package/lib/ship.js
CHANGED
|
@@ -92,4 +92,60 @@ export const httpShip = async (intake, token, shipment) => {
|
|
|
92
92
|
const text = await answer.text();
|
|
93
93
|
return { ok: answer.ok, status: answer.status, said: saidOf(text), accepted: answer.ok ? acceptedOf(text) : null };
|
|
94
94
|
};
|
|
95
|
+
/** Отбитые строки из ответа приёма. Не список — пусто: своё устройство приём не пересказывает. */
|
|
96
|
+
function deniedOf(raw) {
|
|
97
|
+
if (!Array.isArray(raw)) {
|
|
98
|
+
return [];
|
|
99
|
+
}
|
|
100
|
+
return raw
|
|
101
|
+
.filter((one) => typeof one === 'object' && one !== null)
|
|
102
|
+
.map((one) => {
|
|
103
|
+
const line = one;
|
|
104
|
+
return {
|
|
105
|
+
at: typeof line['at'] === 'number' ? line['at'] : 0,
|
|
106
|
+
kind: String(line['kind']),
|
|
107
|
+
key: String(line['key']),
|
|
108
|
+
denial: String(line['denial']),
|
|
109
|
+
};
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
/** Два числа и отбитые строки из ответа приёма. Ответ не тем — пусто. */
|
|
113
|
+
function stateAcceptedOf(text) {
|
|
114
|
+
try {
|
|
115
|
+
const said = JSON.parse(text);
|
|
116
|
+
return typeof said['tree'] === 'string' && typeof said['changed'] === 'number'
|
|
117
|
+
? {
|
|
118
|
+
tree: said['tree'],
|
|
119
|
+
changed: said['changed'],
|
|
120
|
+
same: typeof said['same'] === 'number' ? said['same'] : 0,
|
|
121
|
+
denied: deniedOf(said['denied']),
|
|
122
|
+
}
|
|
123
|
+
: null;
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/** Запрос правки состояния. Токен уезжает заголовком и в теле не появляется ни разу. */
|
|
130
|
+
export const httpMark = async (intake, token, body) => {
|
|
131
|
+
let answer;
|
|
132
|
+
try {
|
|
133
|
+
answer = await fetch(intakeUrl(intake, 'states'), {
|
|
134
|
+
method: 'POST',
|
|
135
|
+
headers: { 'content-type': 'application/json', [TREE_TOKEN_HEADER]: token },
|
|
136
|
+
body: JSON.stringify(body),
|
|
137
|
+
signal: AbortSignal.timeout(SHIP_TIMEOUT_MS),
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
return { ok: false, status: 0, said: error.message, accepted: null };
|
|
142
|
+
}
|
|
143
|
+
const text = await answer.text();
|
|
144
|
+
return {
|
|
145
|
+
ok: answer.ok,
|
|
146
|
+
status: answer.status,
|
|
147
|
+
said: saidOf(text),
|
|
148
|
+
accepted: answer.ok ? stateAcceptedOf(text) : null,
|
|
149
|
+
};
|
|
150
|
+
};
|
|
95
151
|
//# sourceMappingURL=ship.js.map
|
package/lib/ship.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ship.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/ship.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Отправка груза в приём: единственное место пакета, которое ходит в сеть.\n *\n * Ходит оно только по команде человека — ни один гард в сеть не ходит вовсе. Вынесено отдельно,\n * чтобы сборка груза осталась проверяемой: спека подставляет двойник и никуда не стучится.\n *\n * Груз уезжает в закрытый приём, а не в открытую очередь работ: сводка говорит о рабочих\n * привычках команды — чем пользуются, обо что спотыкаются, сколько раз признавали промах, — и в\n * открытой очереди это выложено всему свету.\n */\nimport { existsSync, readFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\nimport { IIntakeAccepted, TREE_TOKEN_HEADER } from './cargo.js';\n\n/**\n * Предел ожидания приёма. Без него молчащая — не отказавшая — служба держит отправку до\n * умолчания среды, а это минуты: человек, позвавший команду, видит не отказ, а зависший запуск.\n */\nexport const SHIP_TIMEOUT_MS: number = 15_000;\n\n/** Что уезжает одним запросом: род груза словом, операция приёма и тело. */\nexport interface IShipment {\n /** Род груза словами — его называет отказ: дерево шлёт три рода и должно знать, какой отбит. */\n readonly kind: string;\n /** Последнее звено адреса операции: `summary`, `proposals`, `postmortems`. */\n readonly operation: string;\n readonly body: unknown;\n}\n\n/** Чем кончился один запрос. Отказ — такой же ответ, как принятое: его печатает вызывающий. */\nexport interface IShipped {\n readonly ok: boolean;\n /** Код ответа. Ноль — приём не ответил вовсе: не дозвонились, оборвалось, вышло время. */\n readonly status: number;\n /** Что сказал приём. У неотвеченного запроса — причина, по которой он не состоялся. */\n readonly said: string;\n /** Дерево и месяц записи. Пусто у отказа: приём их называет только у принятого груза. */\n readonly accepted: IIntakeAccepted | null;\n}\n\n/** Чем груз уезжает. Двойник в спеке — того же вида. */\nexport type TShip = (intake: string, token: string, shipment: IShipment) => Promise<IShipped>;\n\n/** Адрес без косых черт в конце: их считает сам вызывающий, а не движок разбора образцов. */\nfunction withoutTrailingSlash(address: string): string {\n let end: number = address.length;\n\n while (end > 0 && address[end - 1] === '/') {\n end -= 1;\n }\n\n return address.slice(0, end);\n}\n\n/** Адрес операции приёма. Косая черта в конце адреса приёма второй не становится. */\nexport function intakeUrl(intake: string, operation: string): string {\n return `${withoutTrailingSlash(intake)}/api/intake/${operation}`;\n}\n\n/**\n * Токен дерева из файла, названного настройкой.\n *\n * Путь читается и от корня дерева, и от домашнего каталога: токен обязан лежать вне дерева —\n * конфиг коммитится, а токен переживать историю не должен. Файла нет — пустая строка, и отказ\n * об этом скажет словами.\n */\nexport function readToken(root: string, spoken: string): string {\n if (!spoken) {\n return '';\n }\n\n const path: string = spoken.startsWith('~/') ? join(homedir(), spoken.slice(2)) : join(root, spoken);\n\n return existsSync(path) ? readFileSync(path, 'utf8').trim() : '';\n}\n\n/** Число из ответа приёма. Не число — пусто: поля есть только у предложений и не у всякого приёма. */\nfunction countOf(said: Record<string, unknown>, field: string): number | undefined {\n return typeof said[field] === 'number' ? said[field] : undefined;\n}\n\n/** Дерево и месяц из ответа приёма. Ответ не тем — пусто: своё устройство приём не пересказывает. */\nfunction acceptedOf(text: string): IIntakeAccepted | null {\n try {\n const said: Record<string, unknown> = JSON.parse(text) as Record<string, unknown>;\n\n return typeof said['tree'] === 'string' && typeof said['month'] === 'string'\n ? {\n tree: said['tree'],\n month: said['month'],\n created: said['created'] === true,\n added: countOf(said, 'added'),\n known: countOf(said, 'known'),\n }\n : null;\n } catch {\n return null;\n }\n}\n\n/** Что сказал приём: его собственное сообщение, а если тело не разбирается — тело как приехало. */\nfunction saidOf(text: string): string {\n try {\n const said: Record<string, unknown> = JSON.parse(text) as Record<string, unknown>;\n\n return typeof said['message'] === 'string' ? said['message'] : text.trim();\n } catch {\n return text.trim();\n }\n}\n\n/** Запрос приёма. Токен уезжает заголовком и в теле не появляется ни разу. */\nexport const httpShip: TShip = async (intake: string, token: string, shipment: IShipment): Promise<IShipped> => {\n let answer: Response;\n\n try {\n answer = await fetch(intakeUrl(intake, shipment.operation), {\n method: 'POST',\n headers: { 'content-type': 'application/json', [TREE_TOKEN_HEADER]: token },\n body: JSON.stringify(shipment.body),\n signal: AbortSignal.timeout(SHIP_TIMEOUT_MS),\n });\n } catch (error: unknown) {\n return { ok: false, status: 0, said: (error as Error).message, accepted: null };\n }\n\n const text: string = await answer.text();\n\n return { ok: answer.ok, status: answer.status, said: saidOf(text), accepted: answer.ok ? acceptedOf(text) : null };\n};\n"]}
|
|
1
|
+
{"version":3,"file":"ship.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/ship.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Отправка груза в приём: единственное место пакета, которое ходит в сеть.\n *\n * Ходит оно только по команде человека — ни один гард в сеть не ходит вовсе. Вынесено отдельно,\n * чтобы сборка груза осталась проверяемой: спека подставляет двойник и никуда не стучится.\n *\n * Груз уезжает в закрытый приём, а не в открытую очередь работ: сводка говорит о рабочих\n * привычках команды — чем пользуются, обо что спотыкаются, сколько раз признавали промах, — и в\n * открытой очереди это выложено всему свету.\n */\nimport { existsSync, readFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\nimport { ICargoStateAccepted, ICargoStateBody, ICargoStateDenied, IIntakeAccepted, TCargoStateKind, TREE_TOKEN_HEADER } from './cargo.js';\nimport { IMarked, TMarkCall } from './cargo-state.js';\n\n/**\n * Предел ожидания приёма. Без него молчащая — не отказавшая — служба держит отправку до\n * умолчания среды, а это минуты: человек, позвавший команду, видит не отказ, а зависший запуск.\n */\nexport const SHIP_TIMEOUT_MS: number = 15_000;\n\n/** Что уезжает одним запросом: род груза словом, операция приёма и тело. */\nexport interface IShipment {\n /** Род груза словами — его называет отказ: дерево шлёт три рода и должно знать, какой отбит. */\n readonly kind: string;\n /** Последнее звено адреса операции: `summary`, `proposals`, `postmortems`. */\n readonly operation: string;\n readonly body: unknown;\n}\n\n/** Чем кончился один запрос. Отказ — такой же ответ, как принятое: его печатает вызывающий. */\nexport interface IShipped {\n readonly ok: boolean;\n /** Код ответа. Ноль — приём не ответил вовсе: не дозвонились, оборвалось, вышло время. */\n readonly status: number;\n /** Что сказал приём. У неотвеченного запроса — причина, по которой он не состоялся. */\n readonly said: string;\n /** Дерево и месяц записи. Пусто у отказа: приём их называет только у принятого груза. */\n readonly accepted: IIntakeAccepted | null;\n}\n\n/** Чем груз уезжает. Двойник в спеке — того же вида. */\nexport type TShip = (intake: string, token: string, shipment: IShipment) => Promise<IShipped>;\n\n/** Адрес без косых черт в конце: их считает сам вызывающий, а не движок разбора образцов. */\nfunction withoutTrailingSlash(address: string): string {\n let end: number = address.length;\n\n while (end > 0 && address[end - 1] === '/') {\n end -= 1;\n }\n\n return address.slice(0, end);\n}\n\n/** Адрес операции приёма. Косая черта в конце адреса приёма второй не становится. */\nexport function intakeUrl(intake: string, operation: string): string {\n return `${withoutTrailingSlash(intake)}/api/intake/${operation}`;\n}\n\n/**\n * Токен дерева из файла, названного настройкой.\n *\n * Путь читается и от корня дерева, и от домашнего каталога: токен обязан лежать вне дерева —\n * конфиг коммитится, а токен переживать историю не должен. Файла нет — пустая строка, и отказ\n * об этом скажет словами.\n */\nexport function readToken(root: string, spoken: string): string {\n if (!spoken) {\n return '';\n }\n\n const path: string = spoken.startsWith('~/') ? join(homedir(), spoken.slice(2)) : join(root, spoken);\n\n return existsSync(path) ? readFileSync(path, 'utf8').trim() : '';\n}\n\n/** Число из ответа приёма. Не число — пусто: поля есть только у предложений и не у всякого приёма. */\nfunction countOf(said: Record<string, unknown>, field: string): number | undefined {\n return typeof said[field] === 'number' ? said[field] : undefined;\n}\n\n/** Дерево и месяц из ответа приёма. Ответ не тем — пусто: своё устройство приём не пересказывает. */\nfunction acceptedOf(text: string): IIntakeAccepted | null {\n try {\n const said: Record<string, unknown> = JSON.parse(text) as Record<string, unknown>;\n\n return typeof said['tree'] === 'string' && typeof said['month'] === 'string'\n ? {\n tree: said['tree'],\n month: said['month'],\n created: said['created'] === true,\n added: countOf(said, 'added'),\n known: countOf(said, 'known'),\n }\n : null;\n } catch {\n return null;\n }\n}\n\n/** Что сказал приём: его собственное сообщение, а если тело не разбирается — тело как приехало. */\nfunction saidOf(text: string): string {\n try {\n const said: Record<string, unknown> = JSON.parse(text) as Record<string, unknown>;\n\n return typeof said['message'] === 'string' ? said['message'] : text.trim();\n } catch {\n return text.trim();\n }\n}\n\n/** Запрос приёма. Токен уезжает заголовком и в теле не появляется ни разу. */\nexport const httpShip: TShip = async (intake: string, token: string, shipment: IShipment): Promise<IShipped> => {\n let answer: Response;\n\n try {\n answer = await fetch(intakeUrl(intake, shipment.operation), {\n method: 'POST',\n headers: { 'content-type': 'application/json', [TREE_TOKEN_HEADER]: token },\n body: JSON.stringify(shipment.body),\n signal: AbortSignal.timeout(SHIP_TIMEOUT_MS),\n });\n } catch (error: unknown) {\n return { ok: false, status: 0, said: (error as Error).message, accepted: null };\n }\n\n const text: string = await answer.text();\n\n return { ok: answer.ok, status: answer.status, said: saidOf(text), accepted: answer.ok ? acceptedOf(text) : null };\n};\n\n/** Отбитые строки из ответа приёма. Не список — пусто: своё устройство приём не пересказывает. */\nfunction deniedOf(raw: unknown): readonly ICargoStateDenied[] {\n if (!Array.isArray(raw)) {\n return [];\n }\n\n return raw\n .filter((one: unknown): boolean => typeof one === 'object' && one !== null)\n .map((one: unknown): ICargoStateDenied => {\n const line: Record<string, unknown> = one as Record<string, unknown>;\n\n return {\n at: typeof line['at'] === 'number' ? line['at'] : 0,\n kind: String(line['kind']) as TCargoStateKind,\n key: String(line['key']),\n denial: String(line['denial']),\n };\n });\n}\n\n/** Два числа и отбитые строки из ответа приёма. Ответ не тем — пусто. */\nfunction stateAcceptedOf(text: string): ICargoStateAccepted | null {\n try {\n const said: Record<string, unknown> = JSON.parse(text) as Record<string, unknown>;\n\n return typeof said['tree'] === 'string' && typeof said['changed'] === 'number'\n ? {\n tree: said['tree'],\n changed: said['changed'],\n same: typeof said['same'] === 'number' ? said['same'] : 0,\n denied: deniedOf(said['denied']),\n }\n : null;\n } catch {\n return null;\n }\n}\n\n/** Запрос правки состояния. Токен уезжает заголовком и в теле не появляется ни разу. */\nexport const httpMark: TMarkCall = async (intake: string, token: string, body: ICargoStateBody): Promise<IMarked> => {\n let answer: Response;\n\n try {\n answer = await fetch(intakeUrl(intake, 'states'), {\n method: 'POST',\n headers: { 'content-type': 'application/json', [TREE_TOKEN_HEADER]: token },\n body: JSON.stringify(body),\n signal: AbortSignal.timeout(SHIP_TIMEOUT_MS),\n });\n } catch (error: unknown) {\n return { ok: false, status: 0, said: (error as Error).message, accepted: null };\n }\n\n const text: string = await answer.text();\n\n return {\n ok: answer.ok,\n status: answer.status,\n said: saidOf(text),\n accepted: answer.ok ? stateAcceptedOf(text) : null,\n };\n};\n"]}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/** Пороги, объявленные деревом. Размер окна не объявлен — стража нет, и судить нечего. */
|
|
2
|
+
export interface IThresholds {
|
|
3
|
+
/** Размер окна захода в токенах: `RT_WINDOW_TOKENS`. */
|
|
4
|
+
readonly window: number;
|
|
5
|
+
/** Доля окна, на которой страж напоминает: `RT_WINDOW_WARN_PCT`. */
|
|
6
|
+
readonly warnPct: number;
|
|
7
|
+
/** Доля окна, на которой страж отбивает работу: `RT_WINDOW_STOP_PCT`. */
|
|
8
|
+
readonly stopPct: number;
|
|
9
|
+
/** Запас между порогом сжатия и порогом остановки: `RT_WINDOW_MARGIN_PCT`. */
|
|
10
|
+
readonly marginPct: number;
|
|
11
|
+
/** Размер окна автосжатия: настройка `autoCompactWindow`. Не объявлен — `null`. */
|
|
12
|
+
readonly compactWindow: number | null;
|
|
13
|
+
/** Доля, на которой приходит сжатие: `CLAUDE_AUTOCOMPACT_PCT_OVERRIDE`. Не объявлена — `null`. */
|
|
14
|
+
readonly compactPct: number | null;
|
|
15
|
+
}
|
|
16
|
+
/** Расхождение пары: что с чем разъехалось, какими числами и чем это кончится для захода. */
|
|
17
|
+
export interface IThresholdDrift {
|
|
18
|
+
/** Что разошлось, словами читателя. */
|
|
19
|
+
readonly what: string;
|
|
20
|
+
/** Что объявлено стражу. */
|
|
21
|
+
readonly guard: string;
|
|
22
|
+
/** Что объявлено сжатию. */
|
|
23
|
+
readonly compact: string;
|
|
24
|
+
/** Чем это кончится для захода. Пусто у расхождения, которое говорит само за себя. */
|
|
25
|
+
readonly why: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Что дерево объявило о порогах.
|
|
29
|
+
*
|
|
30
|
+
* Настройку читаем разбором и только им: пороги лежат полями объекта, и вынуть их из текста
|
|
31
|
+
* поиском значило бы гадать. Не разобралась — молчим целиком, как молчит карта хуков: назвать
|
|
32
|
+
* расхождением непрочитанное хуже, чем не назвать ничего.
|
|
33
|
+
*/
|
|
34
|
+
export declare function readThresholds(root: string): IThresholds | null;
|
|
35
|
+
/**
|
|
36
|
+
* Чем пара стража разошлась с парой сжатия.
|
|
37
|
+
*
|
|
38
|
+
* Незаданное расхождением не считается: дерево вправе не задавать порога сжатия вовсе — тогда
|
|
39
|
+
* его назначает инструмент, и сказать об этом надо один раз, а не двумя строками о разнице.
|
|
40
|
+
*
|
|
41
|
+
* Долю судит расстояние, а не равенство. Три случая дают расхождение, и у каждого свой исход
|
|
42
|
+
* для захода: сжатие выше остановки — страж отбивает раньше, чем клиент успевает начать;
|
|
43
|
+
* сжатие вровень с остановкой — гонка, которую страж выигрывает; сжатие ниже, но ближе
|
|
44
|
+
* объявленного запаса — клиент начать успевает, а докончить нет.
|
|
45
|
+
*/
|
|
46
|
+
export declare function thresholdDrift(thresholds: IThresholds): readonly IThresholdDrift[];
|
|
47
|
+
/** Раздел разбора состояния: числа порогов и то, что о них известно. */
|
|
48
|
+
export declare function thresholdLines(root: string): string[];
|
|
49
|
+
//# sourceMappingURL=thresholds.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"thresholds.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/thresholds.ts"],"names":[],"mappings":"AA0CA,0FAA0F;AAC1F,MAAM,WAAW,WAAW;IACxB,wDAAwD;IACxD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,oEAAoE;IACpE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,yEAAyE;IACzE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,8EAA8E;IAC9E,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,mFAAmF;IACnF,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,kGAAkG;IAClG,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CACtC;AAED,6FAA6F;AAC7F,MAAM,WAAW,eAAe;IAC5B,uCAAuC;IACvC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,4BAA4B;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,4BAA4B;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,sFAAsF;IACtF,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACxB;AAYD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CA2B/D;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,WAAW,GAAG,SAAS,eAAe,EAAE,CA0ClF;AAED,wEAAwE;AACxE,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAkCrD"}
|