@rt-tools/agent-kit 0.22.0 → 0.24.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 +8 -2
- package/assets/checks/board-long-work.github.mjs +101 -0
- package/assets/checks/board-runs.github.mjs +34 -0
- package/assets/checks/board.github.mjs +1 -1
- package/assets/checks/check-board.github.mjs +30 -2
- package/assets/checks/check-doc-paths.mjs +24 -5
- package/assets/checks/check-file-size.mjs +8 -2
- package/assets/checks/check-prose-style.mjs +10 -1
- package/assets/checks/check-reuse.mjs +4 -1
- package/assets/checks/check-schema-drift.mjs +65 -6
- package/assets/checks/rt-kit-checks.config.mjs +13 -0
- package/assets/checks/signals.mjs +41 -1
- package/assets/commands/next-session.md +16 -5
- package/assets/defaults/gate-map.sh +13 -0
- package/assets/defaults/project.sh +26 -0
- package/assets/defaults/shell.sh +18 -3
- package/assets/hooks/browser-guard-device-id.sh +42 -12
- package/assets/hooks/browser-guard-no-asking.sh +5 -1
- package/assets/hooks/dispatch.sh +40 -9
- package/assets/hooks/docs-guard.sh +10 -0
- package/assets/hooks/exam-guard.sh +66 -16
- package/assets/hooks/git-guard-delivery-draft.sh +78 -0
- package/assets/hooks/git-guard-delivery.sh +64 -122
- package/assets/hooks/git-guard-main.sh +39 -4
- package/assets/hooks/git-guard-push-tests.sh +71 -3
- package/assets/hooks/glossary-load.sh +23 -2
- package/assets/hooks/grill-gate.sh +62 -0
- package/assets/hooks/hook-input.sh +17 -6
- package/assets/hooks/rule-source-guard.sh +11 -0
- package/assets/hooks/stand-login-guard.sh +101 -0
- package/assets/hooks/write-targets.sh +37 -4
- package/assets/laws/autonomous-work.md +30 -0
- package/assets/laws/project-documentation.md +8 -0
- package/assets/laws/verifiability.md +12 -2
- package/assets/laws/work-conduct.md +59 -65
- package/assets/patterns/autonomous-work-run.md +105 -0
- package/assets/patterns/browser-verification-measure.md +41 -1
- package/assets/patterns/browser-verification-stand.md +57 -16
- package/assets/patterns/doc-style-human.md +75 -0
- package/assets/patterns/doc-style-write.md +16 -0
- package/assets/patterns/git-workflow-commit.azure.md +12 -0
- package/assets/patterns/git-workflow-commit.github.md +16 -3
- package/assets/patterns/git-workflow-commit.gitlab.md +12 -0
- package/assets/patterns/git-workflow-merge.md +8 -0
- package/assets/patterns/git-workflow-pr-ready.md +93 -0
- package/assets/patterns/git-workflow-pr.azure.md +1 -1
- package/assets/patterns/git-workflow-pr.github.md +1 -1
- package/assets/patterns/git-workflow-pr.gitlab.md +1 -1
- package/assets/patterns/task-flow-start.md +48 -48
- package/assets/patterns/testing-e2e.md +18 -8
- package/assets/patterns/ts-procedure.md +3 -2
- package/assets/pitfalls/task-flow.md +40 -40
- package/assets/rules/autonomous-work.md +92 -0
- package/assets/rules/browser-verification.md +35 -3
- package/assets/rules/deploy-flow.azure.md +7 -0
- package/assets/rules/deploy-flow.github.md +7 -0
- package/assets/rules/deploy-flow.gitlab.md +7 -0
- package/assets/rules/doc-style.md +54 -0
- package/assets/rules/git-workflow.azure.md +8 -0
- package/assets/rules/git-workflow.github.md +59 -63
- package/assets/rules/git-workflow.gitlab.md +8 -0
- package/assets/rules/reuse-first.md +25 -5
- package/assets/rules/styling-bem.md +8 -1
- package/assets/rules/task-flow.md +108 -109
- package/assets/rules/testing.md +21 -0
- package/assets/skills/agent-kit.md +72 -82
- package/lib/commands.d.ts.map +1 -1
- package/lib/commands.js +69 -2
- package/lib/commands.js.map +1 -1
- package/lib/enroll.d.ts.map +1 -1
- package/lib/enroll.js +1 -1
- package/lib/enroll.js.map +1 -1
- package/lib/observations.d.ts +10 -1
- package/lib/observations.d.ts.map +1 -1
- package/lib/observations.js +1 -0
- package/lib/observations.js.map +1 -1
- package/lib/override-marks.d.ts +24 -0
- package/lib/override-marks.d.ts.map +1 -0
- package/lib/override-marks.js +98 -0
- package/lib/override-marks.js.map +1 -0
- package/lib/push-gate.d.ts +14 -0
- package/lib/push-gate.d.ts.map +1 -0
- package/lib/push-gate.js +93 -0
- package/lib/push-gate.js.map +1 -0
- package/lib/shipment.d.ts.map +1 -1
- package/lib/shipment.js +1 -1
- package/lib/shipment.js.map +1 -1
- package/package.json +1 -1
- package/rt-tools-agent-kit-0.24.0.tgz +0 -0
- package/assets/laws/application/access.md +0 -34
- package/assets/laws/application/locales.md +0 -33
- package/assets/laws/application/search-visibility.md +0 -24
- package/assets/patterns/permissions-procedure.md +0 -71
- package/assets/patterns/seo-page.md +0 -104
- package/assets/patterns/seo-verify.md +0 -83
- package/assets/patterns/translations-content.md +0 -107
- package/assets/patterns/translations-key.md +0 -64
- package/assets/rules/permissions.md +0 -116
- package/assets/rules/seo.md +0 -139
- package/assets/rules/translations.md +0 -96
- package/rt-tools-agent-kit-0.22.0.tgz +0 -0
package/lib/enroll.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"enroll.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/enroll.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC/D,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAG1C,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAEvD,mEAAmE;AACnE,MAAM,UAAU,GAAW,KAAK,CAAC;AAiDjC,8DAA8D;AAC9D,SAAS,OAAO,CAAC,GAAG,KAAwB;IACxC,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC;AAC9B,CAAC;AAED,mGAAmG;AACnG,SAAS,MAAM,CAAC,IAAY;IACxB,IAAI,CAAC;QACD,MAAM,IAAI,GAA4B,IAAI,CAAC,KAAK,CAAC,IAAI,CAA4B,CAAC;QAElF,OAAO,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;IAC/E,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,IAAI,CAAC,IAAI,EAAE,CAAC;IACvB,CAAC;AACL,CAAC;AAED,kGAAkG;AAClG,SAAS,SAAS,CAAC,IAAY;IAC3B,IAAI,CAAC;QACD,MAAM,IAAI,GAA4B,IAAI,CAAC,KAAK,CAAC,IAAI,CAA4B,CAAC;QAElF,OAAO,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,OAAO,IAAI,CAAC,MAAM,CAAC,KAAK,QAAQ,IAAI,OAAO,IAAI,CAAC,MAAM,CAAC,KAAK,QAAQ;YAC5G,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,EAAE;YAClE,CAAC,CAAC,IAAI,CAAC;IACf,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,IAAI,CAAC;IAChB,CAAC;AACL,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAC,MAAc;IACxC,MAAM,OAAO,GAAW,MAAM,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAEpD,IAAI,OAAO,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QACjC,OAAO,IAAI,CAAC;IAChB,CAAC;IAED,OAAO,oDAAoD,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;AAC9E,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,SAAS,CAAC,IAAY,EAAE,MAAc;IAClD,OAAO,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AAC3F,CAAC;AAED,+EAA+E;AAC/E,SAAS,GAAG,CAAC,IAAY,EAAE,KAAa;IACpC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9C,aAAa,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC;AAC9E,CAAC;AAED,4FAA4F;AAC5F,SAAS,MAAM,CAAC,OAAuB,EAAE,IAAY;IACjD,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;IAE1B,OAAO;QACH,IAAI,EAAE,CAAC;QACP,KAAK,EAAE;YACH,oDAAoD;YACpD,iBAAiB,IAAI,mDAAmD;YACxE,0DAA0D;SAC7D;KACJ,CAAC;AACN,CAAC;AAED,qFAAqF;AACrF,KAAK,UAAU,KAAK,CAAC,OAAuB,EAAE,IAAY;IACtD,MAAM,MAAM,GAAkB,MAAM,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;IAE1H,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QAChC,OAAO,OAAO,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,qBAAqB,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,kBAAkB,MAAM,CAAC,MAAM,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;IAClI,CAAC;IAED,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAEhC,OAAO;QACH,IAAI,EAAE,CAAC;QACP,KAAK,EAAE;YACH,qBAAqB,MAAM,CAAC,OAAO,CAAC,IAAI,MAAM,MAAM,CAAC,OAAO,CAAC,IAAI,GAAG;YACpE,iBAAiB,IAAI,mDAAmD;YACxE,wDAAwD;SAC3D;KACJ,CAAC;AACN,CAAC;AAED;;;;;GAKG;AACH,SAAS,UAAU,CAAC,OAAuB;IACvC,IAAI,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;QACjC,OAAO,OAAO,CACV,yFAAyF,EACzF,wDAAwD,CAC3D,CAAC;IACN,CAAC;IAED,IAAI,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;QACnC,OAAO,OAAO,CACV,8BAA8B,EAC9B,gEAAgE,EAChE,oEAAoE,CACvE,CAAC;IACN,CAAC;IAED,OAAO,IAAI,CAAC;AAChB,CAAC;AAED,iGAAiG;AACjG,SAAS,WAAW,CAAC,OAAuB;IACxC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;QAClB,OAAO,OAAO,CAAC,oEAAoE,CAAC,CAAC;IACzF,CAAC;IAED,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QACjC,OAAO,OAAO,CACV,gBAAgB,OAAO,CAAC,MAAM,uFAAuF,EACrH,uFAAuF,CAC1F,CAAC;IACN,CAAC;IAED,OAAO,IAAI,CAAC;AAChB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,OAAuB;IAChD,MAAM,MAAM,GAA0B,UAAU,CAAC,OAAO,CAAC,CAAC;IAE1D,IAAI,MAAM,EAAE,CAAC;QACT,OAAO,MAAM,CAAC;IAClB,CAAC;IAED,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;QACjB,OAAO,OAAO,CAAC,kEAAkE,CAAC,CAAC;IACvF,CAAC;IAED,MAAM,IAAI,GAAW,SAAS,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;IAE5D,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;QACrC,OAAO,OAAO,CACV,qBAAqB,IAAI,+DAA+D,EACxF,+DAA+D,CAClE,CAAC;IACN,CAAC;IAED,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;QACjB,OAAO,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IACjC,CAAC;IAED,MAAM,SAAS,GAA0B,WAAW,CAAC,OAAO,CAAC,CAAC;IAE9D,OAAO,SAAS,IAAI,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;AAC7C,CAAC;AAED,oFAAoF;AACpF,MAAM,CAAC,MAAM,UAAU,GAAgB,KAAK,EAAE,MAAc,EAAE,IAAiB,EAA0B,EAAE;IACvG,IAAI,MAAgB,CAAC;IAErB,IAAI,CAAC;QACD,MAAM,GAAG,MAAM,KAAK,CAAC,SAAS,CAAC,MAAM,EAAE,QAAQ,CAAC,EAAE;YAC9C,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;YAC1B,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,eAAe,CAAC;SAC/C,CAAC,CAAC;IACP,CAAC;IAAC,OAAO,KAAc,EAAE,CAAC;QACtB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE,IAAI,EAAG,KAAe,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IACnF,CAAC;IAED,MAAM,IAAI,GAAW,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;IAEzC,OAAO,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;AACrH,CAAC,CAAC","sourcesContent":["/**\n * Заведение дерева: токен ложится на диск правами «читает и пишет только владелец файла».\n *\n * Путей два, и человек выбирает доводом.\n *\n * Обмен по коду — дерево просит токен у приёма, неся одноразовый код. Это вторая и последняя\n * команда пакета, которая ходит в сеть, и единственная, которая ходит туда без токена: токена у\n * дерева ещё нет, за ним и обращаются.\n *\n * Выданный токен — тот, что человек получил прямо в админке приёма. Обменивать не на что, и в\n * сеть команда не идёт вовсе: она кладёт полученное на диск теми же правами. Отсюда и разный\n * набор проверок — адрес приёма и запрет открытого пути этому пути не нужны.\n *\n * Ходит она только по слову человека. Гарды в сеть не ходят вовсе, и заведение — не исключение:\n * дерево заводят один раз, руками.\n *\n * Сеть здесь отделена от решения так же, как у отправки груза: чем сходить, приходит доводом, и\n * спека подставляет двойник, никуда не стучась.\n */\nimport { existsSync, mkdirSync, writeFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { dirname, join } from 'node:path';\n\nimport { IEnrollGranted } from './cargo.js';\nimport { intakeUrl, SHIP_TIMEOUT_MS } from './ship.js';\n\n/** Права файла с токеном: читает и пишет только владелец файла. */\nconst TOKEN_MODE: number = 0o600;\n\n/** Тело обращения: признак дерева и код приглашения. Имени в нём нет — его знает приглашение. */\nexport interface IEnrollBody {\n readonly schema: string;\n readonly tree: string;\n readonly code: string;\n}\n\n/** Чем кончилось обращение. Отказ — такой же ответ, как выданный токен: его печатает вызывающий. */\nexport interface IEnrollAnswer {\n readonly ok: boolean;\n /** Код ответа. Ноль — приём не ответил вовсе: не дозвонились, оборвалось, вышло время. */\n readonly status: number;\n readonly said: string;\n /** Токен и имя дерева. Пусто у отказа: приём называет их только у годного обращения. */\n readonly granted: IEnrollGranted | null;\n}\n\n/** Чем обращение уходит в приём. Двойник в спеке — того же вида. */\nexport type TEnrollCall = (intake: string, body: IEnrollBody) => Promise<IEnrollAnswer>;\n\n/** Что нужно заведению: где дерево, куда идти, чем платить и куда класть токен. */\nexport interface IEnrollOptions {\n readonly root: string;\n readonly intake: string;\n readonly code: string;\n /**\n * Токен, выданный человеку прямо в админке приёма.\n *\n * Второй путь завести дерево, и сети он не требует вовсе: токен уже на руках, обменивать не\n * на что. С кодом приглашения не сочетается — это два пути к одному и тому же.\n */\n readonly issued: string;\n /** Признак дерева, посчитанный от адреса репозитория. */\n readonly tree: string;\n /** Файл токена — путь от корня дерева или от домашнего каталога. */\n readonly token: string;\n /** Перезаписать лежащий токен намеренно. Без него дерево с токеном обращения не шлёт. */\n readonly force: boolean;\n readonly call: TEnrollCall;\n}\n\n/** Что команда напечатала и чем кончилась. Строки печатает вызывающий. */\nexport interface IEnrollOutcome {\n readonly code: number;\n readonly lines: readonly string[];\n}\n\n/** Отказ команды: причина строками и ненулевой код выхода. */\nfunction refusal(...lines: readonly string[]): IEnrollOutcome {\n return { code: 1, lines };\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/** Токен и имя из ответа приёма. Ответ не тем — пусто: своё устройство приём не пересказывает. */\nfunction grantedOf(text: string): IEnrollGranted | null {\n try {\n const said: Record<string, unknown> = JSON.parse(text) as Record<string, unknown>;\n\n return typeof said['token'] === 'string' && typeof said['tree'] === 'string' && typeof said['name'] === 'string'\n ? { tree: said['tree'], name: said['name'], token: said['token'] }\n : null;\n } catch {\n return null;\n }\n}\n\n/**\n * Годен ли адрес приёма для обращения без TLS.\n *\n * Токен уходит единственным ответом на это обращение, и по открытому пути его читает всякий, кто\n * стоит между сторонами. Исключение одно — локальная машина: там приём поднимают для проверки, и\n * сети между сторонами нет вовсе.\n */\nexport function intakeAllowed(intake: string): boolean {\n const address: string = intake.trim().toLowerCase();\n\n if (address.startsWith('https://')) {\n return true;\n }\n\n return /^http:\\/\\/(localhost|127\\.0\\.0\\.1|\\[::1\\])([:/]|$)/.test(address);\n}\n\n/** Куда ложится токен: путь от домашнего каталога или от корня дерева. */\nexport function tokenPath(root: string, spoken: string): string {\n return spoken.startsWith('~/') ? join(homedir(), spoken.slice(2)) : join(root, spoken);\n}\n\n/** Запись токена на диск: у обоих путей она одна, и права файла у неё одни. */\nfunction lay(path: string, token: string): void {\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, `${token}\\n`, { encoding: 'utf8', mode: TOKEN_MODE });\n}\n\n/** Токен с рук: приём о нём не спрашивается — он же его и выдал, и обменивать не на что. */\nfunction accept(options: IEnrollOptions, path: string): IEnrollOutcome {\n lay(path, options.issued);\n\n return {\n code: 0,\n lines: [\n 'дерево заведено токеном, выданным в админке приёма',\n `токен лежит в ${path}, читать и писать его может только владелец файла`,\n 'проверить связь с приёмом: `agent-kit propose --dry-run`',\n ],\n };\n}\n\n/** Обращение и запись токена: сюда доходит только то, что прошло проверки у себя. */\nasync function grant(options: IEnrollOptions, path: string): Promise<IEnrollOutcome> {\n const answer: IEnrollAnswer = await options.call(options.intake, { schema: '1', tree: options.tree, code: options.code });\n\n if (!answer.ok || !answer.granted) {\n return refusal(answer.status === 0 ? `приём не ответил: ${answer.said}` : `приём отказал (${answer.status}): ${answer.said}`);\n }\n\n lay(path, answer.granted.token);\n\n return {\n code: 0,\n lines: [\n `дерево заведено: «${answer.granted.name}» (${answer.granted.tree})`,\n `токен лежит в ${path}, читать и писать его может только владелец файла`,\n 'приглашение погашено: второй раз им не воспользоваться',\n ],\n };\n}\n\n/**\n * Выбран ли путь: отказ, когда доводов два или ни одного, и пусто, когда выбран ровно один.\n *\n * Стоит отдельно от самого заведения: разбор довода и порядок проверок — два разных решения, и\n * вместе они не читаются.\n */\nfunction pathChosen(options: IEnrollOptions): IEnrollOutcome | null {\n if (options.code && options.issued) {\n return refusal(\n 'код приглашения и выданный токен — два разных пути завести дерево, и вместе они не идут',\n 'оставьте один довод: --code <код> либо --token <токен>'\n );\n }\n\n if (!options.code && !options.issued) {\n return refusal(\n 'заведение ждёт одно из двух:',\n ' --code <код> код приглашения, выданный владельцем приёма',\n ' --token <токен> токен, выданный человеку прямо в админке приёма'\n );\n }\n\n return null;\n}\n\n/** Годен ли приём для обмена по коду. Пути с рук эти проверки не касаются: он в сеть не идёт. */\nfunction intakeReady(options: IEnrollOptions): IEnrollOutcome | null {\n if (!options.intake) {\n return refusal('в настройке дерева не назван адрес приёма: заполните ключ `intake`');\n }\n\n if (!intakeAllowed(options.intake)) {\n return refusal(\n `адрес приёма ${options.intake} без TLS: токен уходит единственным ответом, и по открытому пути его прочитает всякий`,\n 'поправьте ключ `intake` на https://… — исключение сделано только для локальной машины'\n );\n }\n\n return null;\n}\n\n/**\n * Заведение дерева.\n *\n * Порядок проверок такой, что до сети доходит только то, что имеет шанс: незаполненная настройка,\n * открытый адрес и уже лежащий токен отбиваются здесь, у себя. Общие обоим путям проверки стоят\n * до развилки: поставленные после, они защищали бы только один из них.\n */\nexport async function enroll(options: IEnrollOptions): Promise<IEnrollOutcome> {\n const chosen: IEnrollOutcome | null = pathChosen(options);\n\n if (chosen) {\n return chosen;\n }\n\n if (!options.token) {\n return refusal('в настройке дерева не назван файл токена: заполните ключ `token`');\n }\n\n const laid: string = tokenPath(options.root, options.token);\n\n if (existsSync(laid) && !options.force) {\n return refusal(\n `токен уже лежит в ${laid}: заведённое дерево потеряло бы связь со своим прежним грузом`,\n 'если это намеренно — позовите ту же команду с доводом --force'\n );\n }\n\n if (options.issued) {\n return accept(options, laid);\n }\n\n const reachable: IEnrollOutcome | null = intakeReady(options);\n\n return reachable ?? grant(options, laid);\n}\n\n/** Обращение в приём по-настоящему. Код уезжает телом и в заголовки не попадает. */\nexport const httpEnroll: TEnrollCall = async (intake: string, body: IEnrollBody): Promise<IEnrollAnswer> => {\n let answer: Response;\n\n try {\n answer = await fetch(intakeUrl(intake, 'enroll'), {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\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, granted: null };\n }\n\n const text: string = await answer.text();\n\n return { ok: answer.ok, status: answer.status, said: saidOf(text), granted: answer.ok ? grantedOf(text) : null };\n};\n"]}
|
|
1
|
+
{"version":3,"file":"enroll.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/enroll.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Заведение дерева: токен ложится на диск правами «читает и пишет только владелец файла».\n *\n * Путей два, и человек выбирает доводом.\n *\n * Обмен по коду — дерево просит токен у приёма, неся одноразовый код. Это вторая и последняя\n * команда пакета, которая ходит в сеть, и единственная, которая ходит туда без токена: токена у\n * дерева ещё нет, за ним и обращаются.\n *\n * Выданный токен — тот, что человек получил прямо в админке приёма. Обменивать не на что, и в\n * сеть команда не идёт вовсе: она кладёт полученное на диск теми же правами. Отсюда и разный\n * набор проверок — адрес приёма и запрет открытого пути этому пути не нужны.\n *\n * Ходит она только по слову человека. Гарды в сеть не ходят вовсе, и заведение — не исключение:\n * дерево заводят один раз, руками.\n *\n * Сеть здесь отделена от решения так же, как у отправки груза: чем сходить, приходит доводом, и\n * спека подставляет двойник, никуда не стучась.\n */\nimport { existsSync, mkdirSync, writeFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { dirname, join } from 'node:path';\n\nimport { IEnrollGranted } from './cargo.js';\nimport { intakeUrl, SHIP_TIMEOUT_MS } from './ship.js';\n\n/** Права файла с токеном: читает и пишет только владелец файла. */\nconst TOKEN_MODE: number = 0o600;\n\n/** Тело обращения: признак дерева и код приглашения. Имени в нём нет — его знает приглашение. */\nexport interface IEnrollBody {\n readonly schema: string;\n readonly tree: string;\n readonly code: string;\n}\n\n/** Чем кончилось обращение. Отказ — такой же ответ, как выданный токен: его печатает вызывающий. */\nexport interface IEnrollAnswer {\n readonly ok: boolean;\n /** Код ответа. Ноль — приём не ответил вовсе: не дозвонились, оборвалось, вышло время. */\n readonly status: number;\n readonly said: string;\n /** Токен и имя дерева. Пусто у отказа: приём называет их только у годного обращения. */\n readonly granted: IEnrollGranted | null;\n}\n\n/** Чем обращение уходит в приём. Двойник в спеке — того же вида. */\nexport type TEnrollCall = (intake: string, body: IEnrollBody) => Promise<IEnrollAnswer>;\n\n/** Что нужно заведению: где дерево, куда идти, чем платить и куда класть токен. */\nexport interface IEnrollOptions {\n readonly root: string;\n readonly intake: string;\n readonly code: string;\n /**\n * Токен, выданный человеку прямо в админке приёма.\n *\n * Второй путь завести дерево, и сети он не требует вовсе: токен уже на руках, обменивать не\n * на что. С кодом приглашения не сочетается — это два пути к одному и тому же.\n */\n readonly issued: string;\n /** Признак дерева, посчитанный от адреса репозитория. */\n readonly tree: string;\n /** Файл токена — путь от корня дерева или от домашнего каталога. */\n readonly token: string;\n /** Перезаписать лежащий токен намеренно. Без него дерево с токеном обращения не шлёт. */\n readonly force: boolean;\n readonly call: TEnrollCall;\n}\n\n/** Что команда напечатала и чем кончилась. Строки печатает вызывающий. */\nexport interface IEnrollOutcome {\n readonly code: number;\n readonly lines: readonly string[];\n}\n\n/** Отказ команды: причина строками и ненулевой код выхода. */\nfunction refusal(...lines: readonly string[]): IEnrollOutcome {\n return { code: 1, lines };\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/** Токен и имя из ответа приёма. Ответ не тем — пусто: своё устройство приём не пересказывает. */\nfunction grantedOf(text: string): IEnrollGranted | null {\n try {\n const said: Record<string, unknown> = JSON.parse(text) as Record<string, unknown>;\n\n return typeof said['token'] === 'string' && typeof said['tree'] === 'string' && typeof said['name'] === 'string'\n ? { tree: said['tree'], name: said['name'], token: said['token'] }\n : null;\n } catch {\n return null;\n }\n}\n\n/**\n * Годен ли адрес приёма для обращения без TLS.\n *\n * Токен уходит единственным ответом на это обращение, и по открытому пути его читает всякий, кто\n * стоит между сторонами. Исключение одно — локальная машина: там приём поднимают для проверки, и\n * сети между сторонами нет вовсе.\n */\nexport function intakeAllowed(intake: string): boolean {\n const address: string = intake.trim().toLowerCase();\n\n if (address.startsWith('https://')) {\n return true;\n }\n\n return /^http:\\/\\/(localhost|127\\.0\\.0\\.1|\\[::1\\])([:/]|$)/.test(address);\n}\n\n/** Куда ложится токен: путь от домашнего каталога или от корня дерева. */\nexport function tokenPath(root: string, spoken: string): string {\n return spoken.startsWith('~/') ? join(homedir(), spoken.slice(2)) : join(root, spoken);\n}\n\n/** Запись токена на диск: у обоих путей она одна, и права файла у неё одни. */\nfunction lay(path: string, token: string): void {\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, `${token}\\n`, { encoding: 'utf8', mode: TOKEN_MODE });\n}\n\n/** Токен с рук: приём о нём не спрашивается — он же его и выдал, и обменивать не на что. */\nfunction accept(options: IEnrollOptions, path: string): IEnrollOutcome {\n lay(path, options.issued);\n\n return {\n code: 0,\n lines: [\n 'дерево заведено токеном, выданным в админке приёма',\n `токен лежит в ${path}, читать и писать его может только владелец файла`,\n 'проверить связь с приёмом: `agent-kit propose --dry-run`',\n ],\n };\n}\n\n/** Обращение и запись токена: сюда доходит только то, что прошло проверки у себя. */\nasync function grant(options: IEnrollOptions, path: string): Promise<IEnrollOutcome> {\n const answer: IEnrollAnswer = await options.call(options.intake, { schema: '1', tree: options.tree, code: options.code });\n\n if (!answer.ok || !answer.granted) {\n return refusal(answer.status === 0 ? `приём не ответил: ${answer.said}` : `приём отказал (${answer.status}): ${answer.said}`);\n }\n\n lay(path, answer.granted.token);\n\n return {\n code: 0,\n lines: [\n `дерево заведено: «${answer.granted.name}» (${answer.granted.tree})`,\n `токен лежит в ${path}, читать и писать его может только владелец файла`,\n 'приглашение погашено: второй раз им не воспользоваться',\n ],\n };\n}\n\n/**\n * Выбран ли путь: отказ, когда доводов два или ни одного, и пусто, когда выбран ровно один.\n *\n * Стоит отдельно от самого заведения: разбор довода и порядок проверок — два разных решения, и\n * вместе они не читаются.\n */\nfunction pathChosen(options: IEnrollOptions): IEnrollOutcome | null {\n if (options.code && options.issued) {\n return refusal(\n 'код приглашения и выданный токен — два разных пути завести дерево, и вместе они не идут',\n 'оставьте один довод: --code <код> либо --token <токен>'\n );\n }\n\n if (!options.code && !options.issued) {\n return refusal(\n 'заведение ждёт одно из двух:',\n ' --code <код> код приглашения, выданный владельцем приёма',\n ' --token <токен> токен, выданный человеку прямо в админке приёма'\n );\n }\n\n return null;\n}\n\n/** Годен ли приём для обмена по коду. Пути с рук эти проверки не касаются: он в сеть не идёт. */\nfunction intakeReady(options: IEnrollOptions): IEnrollOutcome | null {\n if (!options.intake) {\n return refusal(\n 'в настройке дерева не назван адрес приёма: заполните ключ `intake`',\n 'адрес берётся у того же человека, который выдал код приглашения: код и адрес идут парой'\n );\n }\n\n if (!intakeAllowed(options.intake)) {\n return refusal(\n `адрес приёма ${options.intake} без TLS: токен уходит единственным ответом, и по открытому пути его прочитает всякий`,\n 'поправьте ключ `intake` на https://… — исключение сделано только для локальной машины'\n );\n }\n\n return null;\n}\n\n/**\n * Заведение дерева.\n *\n * Порядок проверок такой, что до сети доходит только то, что имеет шанс: незаполненная настройка,\n * открытый адрес и уже лежащий токен отбиваются здесь, у себя. Общие обоим путям проверки стоят\n * до развилки: поставленные после, они защищали бы только один из них.\n */\nexport async function enroll(options: IEnrollOptions): Promise<IEnrollOutcome> {\n const chosen: IEnrollOutcome | null = pathChosen(options);\n\n if (chosen) {\n return chosen;\n }\n\n if (!options.token) {\n return refusal('в настройке дерева не назван файл токена: заполните ключ `token`');\n }\n\n const laid: string = tokenPath(options.root, options.token);\n\n if (existsSync(laid) && !options.force) {\n return refusal(\n `токен уже лежит в ${laid}: заведённое дерево потеряло бы связь со своим прежним грузом`,\n 'если это намеренно — позовите ту же команду с доводом --force'\n );\n }\n\n if (options.issued) {\n return accept(options, laid);\n }\n\n const reachable: IEnrollOutcome | null = intakeReady(options);\n\n return reachable ?? grant(options, laid);\n}\n\n/** Обращение в приём по-настоящему. Код уезжает телом и в заголовки не попадает. */\nexport const httpEnroll: TEnrollCall = async (intake: string, body: IEnrollBody): Promise<IEnrollAnswer> => {\n let answer: Response;\n\n try {\n answer = await fetch(intakeUrl(intake, 'enroll'), {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\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, granted: null };\n }\n\n const text: string = await answer.text();\n\n return { ok: answer.ok, status: answer.status, said: saidOf(text), granted: answer.ok ? grantedOf(text) : null };\n};\n"]}
|
package/lib/observations.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export declare const KEEP_DAYS: number;
|
|
|
5
5
|
/** За сколько дней сводка отвечает, когда отрезок не назван. */
|
|
6
6
|
export declare const DEFAULT_DAYS: number;
|
|
7
7
|
/** Роды событий, которые пишут гарды. */
|
|
8
|
-
export type TEvent = 'skill-load' | 'gate-deny' | 'guard-deny';
|
|
8
|
+
export type TEvent = 'skill-load' | 'gate-deny' | 'guard-deny' | 'push-gate';
|
|
9
9
|
export interface IObservation {
|
|
10
10
|
readonly event: string;
|
|
11
11
|
/** Имя правила или гарда — имя ресурса пакета, и только оно. */
|
|
@@ -60,6 +60,15 @@ export interface ISummary {
|
|
|
60
60
|
*/
|
|
61
61
|
readonly denialsOffFile: number;
|
|
62
62
|
readonly guards: readonly ICount[];
|
|
63
|
+
/**
|
|
64
|
+
* Исходы гейта пуша: `green` — набор прогнан и зелёный, `red` — набор красный, `no-checks` —
|
|
65
|
+
* проверок в дереве не нашлось.
|
|
66
|
+
*
|
|
67
|
+
* Отбои гарда считаются строкой выше и отвечают на один вопрос из трёх. Гейт, у которого
|
|
68
|
+
* набор не нашёлся ни разу, в отбоях выглядит как гейт, у которого всё зелено: и тот и
|
|
69
|
+
* другой молчат. Разводит их только этот счёт.
|
|
70
|
+
*/
|
|
71
|
+
readonly pushGate: readonly ICount[];
|
|
63
72
|
/**
|
|
64
73
|
* Гарды, разложенные в дерево и не отбившие за отрезок ни разу.
|
|
65
74
|
*
|
|
@@ -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;
|
|
1
|
+
{"version":3,"file":"observations.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/observations.ts"],"names":[],"mappings":"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"}
|
package/lib/observations.js
CHANGED
|
@@ -148,6 +148,7 @@ export function summarize(observations, known, days, weights = {}, guards = [])
|
|
|
148
148
|
kinds: countBy(denials.map((entry) => entry.kind)),
|
|
149
149
|
denialsOffFile: denials.filter((entry) => OFF_FILE_KINDS.includes(entry.kind)).length,
|
|
150
150
|
guards: countBy(of(observations, 'guard-deny').map((entry) => entry.resource)),
|
|
151
|
+
pushGate: countBy(of(observations, 'push-gate').map((entry) => entry.resource)),
|
|
151
152
|
silentGuards: guards.filter((name) => !denied.has(name)).sort(byText),
|
|
152
153
|
unused: known.filter((name) => !loaded.has(name)).sort(byText),
|
|
153
154
|
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\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 * — молчащий гард и ненужный выглядят одинаково ровно до разбора.\n */\n readonly silentGuards: readonly string[];\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 * `guards` — имена гардов, разложенных в это дерево. Гардом здесь считается тот, кто заявил своё\n * имя для наблюдений: хук без заявки в наблюдениях не появится ни при каком отрезке, и молчащим\n * он назывался бы неверно — он молчит не потому, что не понадобился.\n *\n * `weights` — вес этих правил на диске. Без них сводка говорит, сколько раз правило открывали, и\n * молчит о том, во что это обошлось: тридцать загрузок паттерна и тридцать загрузок правила на\n * пятьсот строк стоят разного, а в счёте выглядят одинаково. Не назван — вес считается нулём, и\n * строки о нём в выводе не будет вовсе.\n */\nexport function summarize(\n observations: readonly IObservation[],\n known: readonly string[],\n days: number,\n weights: TWeights = {},\n guards: readonly string[] = []\n): 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 const denied: Set<string> = new Set(of(observations, 'guard-deny').map((entry: IObservation): string => entry.resource));\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 silentGuards: guards.filter((name: string): boolean => !denied.has(name)).sort(byText),\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' | 'push-gate';\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 * Исходы гейта пуша: `green` — набор прогнан и зелёный, `red` — набор красный, `no-checks` —\n * проверок в дереве не нашлось.\n *\n * Отбои гарда считаются строкой выше и отвечают на один вопрос из трёх. Гейт, у которого\n * набор не нашёлся ни разу, в отбоях выглядит как гейт, у которого всё зелено: и тот и\n * другой молчат. Разводит их только этот счёт.\n */\n readonly pushGate: readonly ICount[];\n /**\n * Гарды, разложенные в дерево и не отбившие за отрезок ни разу.\n *\n * Считается той же разностью, что и незагруженные правила, и по той же причине: в самих\n * наблюдениях молчащего гарда нет по определению, а из перечня защит его никто не вычеркнет\n * — молчащий гард и ненужный выглядят одинаково ровно до разбора.\n */\n readonly silentGuards: readonly string[];\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 * `guards` — имена гардов, разложенных в это дерево. Гардом здесь считается тот, кто заявил своё\n * имя для наблюдений: хук без заявки в наблюдениях не появится ни при каком отрезке, и молчащим\n * он назывался бы неверно — он молчит не потому, что не понадобился.\n *\n * `weights` — вес этих правил на диске. Без них сводка говорит, сколько раз правило открывали, и\n * молчит о том, во что это обошлось: тридцать загрузок паттерна и тридцать загрузок правила на\n * пятьсот строк стоят разного, а в счёте выглядят одинаково. Не назван — вес считается нулём, и\n * строки о нём в выводе не будет вовсе.\n */\nexport function summarize(\n observations: readonly IObservation[],\n known: readonly string[],\n days: number,\n weights: TWeights = {},\n guards: readonly string[] = []\n): 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 const denied: Set<string> = new Set(of(observations, 'guard-deny').map((entry: IObservation): string => entry.resource));\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 pushGate: countBy(of(observations, 'push-gate').map((entry: IObservation): string => entry.resource)),\n silentGuards: guards.filter((name: string): boolean => !denied.has(name)).sort(byText),\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"]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface IOverrideMark {
|
|
2
|
+
/** Файл надстройки от корня дерева. */
|
|
3
|
+
readonly file: string;
|
|
4
|
+
/** Заголовок раздела, который пометка держит. */
|
|
5
|
+
readonly heading: string;
|
|
6
|
+
/** Ресурс пакета, ради статьи которого раздел заведён. */
|
|
7
|
+
readonly resource: string;
|
|
8
|
+
/** Статья: та самая цитата, которую называло предложение. */
|
|
9
|
+
readonly article: string;
|
|
10
|
+
/** День отправки, `ГГГГ-ММ-ДД`. */
|
|
11
|
+
readonly day: string;
|
|
12
|
+
}
|
|
13
|
+
/** Пометки одного файла надстройки. Раздел без пометки считается постоянным. */
|
|
14
|
+
export declare function marksOfOverride(text: string, file: string): readonly IOverrideMark[];
|
|
15
|
+
/**
|
|
16
|
+
* Надстройки, чья статья в новой редакции пакета уже есть.
|
|
17
|
+
*
|
|
18
|
+
* `assetsDir` — каталог ресурсов той редакции, которая раскладывается сейчас: сравнение идёт с
|
|
19
|
+
* ней, а не с тем, что уже лежит в дереве. Ресурса в редакции нет — пометка молчит: пакет мог его
|
|
20
|
+
* переименовать, и называть надстройку лишней на этом основании значило бы советовать снять то,
|
|
21
|
+
* чему замена не пришла.
|
|
22
|
+
*/
|
|
23
|
+
export declare function staleOverrides(root: string, assetsDir: string): readonly IOverrideMark[];
|
|
24
|
+
//# sourceMappingURL=override-marks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"override-marks.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/override-marks.ts"],"names":[],"mappings":"AAiCA,MAAM,WAAW,aAAa;IAC1B,uCAAuC;IACvC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,iDAAiD;IACjD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,0DAA0D;IAC1D,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,6DAA6D;IAC7D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,mCAAmC;IACnC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACxB;AAOD,gFAAgF;AAChF,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,aAAa,EAAE,CAmBpF;AA0BD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS,aAAa,EAAE,CAqBxF"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Пометки на надстройках: какой раздел заведён ради отправленного предложения и когда его можно
|
|
3
|
+
* снять.
|
|
4
|
+
*
|
|
5
|
+
* Надстройку заводят в день отправки предложения, а снимают, когда правка выходит в новой
|
|
6
|
+
* редакции пакета. Между этими днями проходит месяц, другой заход и другой повод: в минуту
|
|
7
|
+
* обновления у исполнителя в руках разложенный ресурс и надстройка, а записи предложений он не
|
|
8
|
+
* открывает — на них ничто не указывает. Надстройка остаётся замещать раздел, который пакет
|
|
9
|
+
* давно содержит сам.
|
|
10
|
+
*
|
|
11
|
+
* Пометка держит эту связь на самой надстройке: имя ресурса пакета, статья, ради которой раздел
|
|
12
|
+
* заведён, и день отправки. Форма — комментарий разметки: он не виден в собранном тексте,
|
|
13
|
+
* переживает слияние по заголовкам и читается машиной.
|
|
14
|
+
*
|
|
15
|
+
* Снимает раздел человек, а не команда: пометка называет одну статью, а в раздел могли дописать
|
|
16
|
+
* и другое — снятие вслепую удалило бы то, чего пометка не касалась.
|
|
17
|
+
*/
|
|
18
|
+
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
|
|
19
|
+
import { join, relative } from 'node:path';
|
|
20
|
+
import { OVERRIDES_DIR } from './config.js';
|
|
21
|
+
import { parseDocument } from './sections.js';
|
|
22
|
+
import { byText } from './order.js';
|
|
23
|
+
/**
|
|
24
|
+
* Пометка раздела надстройки:
|
|
25
|
+
* `<!-- rt-proposed: rules/task-flow.md · «статья целиком» · 2026-09-03 -->`
|
|
26
|
+
*
|
|
27
|
+
* Разделители те же, что у шапки раскладки: строку читает человек, и второй знак разделения в
|
|
28
|
+
* тех же файлах читался бы как другая пометка.
|
|
29
|
+
*/
|
|
30
|
+
const MARK = /<!-- rt-proposed:\s?([^·]+)·\s?«([^»]*)»\s?·\s?([0-9-]+) -->/;
|
|
31
|
+
/** Один пробел вместо любой последовательности пробелов: статья в разметке перенесена по ширине строки. */
|
|
32
|
+
function flat(text) {
|
|
33
|
+
return text.replace(/\s+/g, ' ').trim();
|
|
34
|
+
}
|
|
35
|
+
/** Пометки одного файла надстройки. Раздел без пометки считается постоянным. */
|
|
36
|
+
export function marksOfOverride(text, file) {
|
|
37
|
+
const document = parseDocument(text);
|
|
38
|
+
const found = [];
|
|
39
|
+
for (const section of document.sections) {
|
|
40
|
+
const match = MARK.exec(section.body);
|
|
41
|
+
if (match) {
|
|
42
|
+
found.push({
|
|
43
|
+
file,
|
|
44
|
+
heading: section.heading.replace(/^#+\s*/, ''),
|
|
45
|
+
resource: flat(match[1]),
|
|
46
|
+
article: flat(match[2]),
|
|
47
|
+
day: match[3].trim(),
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return found;
|
|
52
|
+
}
|
|
53
|
+
/** Файлы каталога надстроек, путями от самого каталога. */
|
|
54
|
+
function overrideFiles(dir) {
|
|
55
|
+
if (!existsSync(dir)) {
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
const found = [];
|
|
59
|
+
const walk = (at) => {
|
|
60
|
+
for (const name of readdirSync(at).sort(byText)) {
|
|
61
|
+
const path = join(at, name);
|
|
62
|
+
if (statSync(path).isDirectory()) {
|
|
63
|
+
walk(path);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
found.push(path);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
walk(dir);
|
|
71
|
+
return found;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Надстройки, чья статья в новой редакции пакета уже есть.
|
|
75
|
+
*
|
|
76
|
+
* `assetsDir` — каталог ресурсов той редакции, которая раскладывается сейчас: сравнение идёт с
|
|
77
|
+
* ней, а не с тем, что уже лежит в дереве. Ресурса в редакции нет — пометка молчит: пакет мог его
|
|
78
|
+
* переименовать, и называть надстройку лишней на этом основании значило бы советовать снять то,
|
|
79
|
+
* чему замена не пришла.
|
|
80
|
+
*/
|
|
81
|
+
export function staleOverrides(root, assetsDir) {
|
|
82
|
+
const dir = join(root, OVERRIDES_DIR);
|
|
83
|
+
const stale = [];
|
|
84
|
+
for (const path of overrideFiles(dir)) {
|
|
85
|
+
const marks = marksOfOverride(readFileSync(path, 'utf8'), relative(root, path));
|
|
86
|
+
for (const mark of marks) {
|
|
87
|
+
const source = join(assetsDir, mark.resource);
|
|
88
|
+
if (!mark.article || !existsSync(source)) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (flat(readFileSync(source, 'utf8')).includes(mark.article)) {
|
|
92
|
+
stale.push(mark);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return stale;
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=override-marks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"override-marks.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/override-marks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC1E,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAE3C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAa,MAAM,eAAe,CAAC;AACzD,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAEpC;;;;;;GAMG;AACH,MAAM,IAAI,GAAW,8DAA8D,CAAC;AAepF,2GAA2G;AAC3G,SAAS,IAAI,CAAC,IAAY;IACtB,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;AAC5C,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,eAAe,CAAC,IAAY,EAAE,IAAY;IACtD,MAAM,QAAQ,GAAc,aAAa,CAAC,IAAI,CAAC,CAAC;IAChD,MAAM,KAAK,GAAoB,EAAE,CAAC;IAElC,KAAK,MAAM,OAAO,IAAI,QAAQ,CAAC,QAAQ,EAAE,CAAC;QACtC,MAAM,KAAK,GAA2B,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAE9D,IAAI,KAAK,EAAE,CAAC;YACR,KAAK,CAAC,IAAI,CAAC;gBACP,IAAI;gBACJ,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC;gBAC9C,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;gBACxB,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;gBACvB,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE;aACvB,CAAC,CAAC;QACP,CAAC;IACL,CAAC;IAED,OAAO,KAAK,CAAC;AACjB,CAAC;AAED,2DAA2D;AAC3D,SAAS,aAAa,CAAC,GAAW;IAC9B,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACnB,OAAO,EAAE,CAAC;IACd,CAAC;IAED,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,MAAM,IAAI,GAAyB,CAAC,EAAU,EAAQ,EAAE;QACpD,KAAK,MAAM,IAAI,IAAI,WAAW,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;YAC9C,MAAM,IAAI,GAAW,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;YAEpC,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC;gBAC/B,IAAI,CAAC,IAAI,CAAC,CAAC;YACf,CAAC;iBAAM,CAAC;gBACJ,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACrB,CAAC;QACL,CAAC;IACL,CAAC,CAAC;IAEF,IAAI,CAAC,GAAG,CAAC,CAAC;IAEV,OAAO,KAAK,CAAC;AACjB,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY,EAAE,SAAiB;IAC1D,MAAM,GAAG,GAAW,IAAI,CAAC,IAAI,EAAE,aAAa,CAAC,CAAC;IAC9C,MAAM,KAAK,GAAoB,EAAE,CAAC;IAElC,KAAK,MAAM,IAAI,IAAI,aAAa,CAAC,GAAG,CAAC,EAAE,CAAC;QACpC,MAAM,KAAK,GAA6B,eAAe,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;QAE1G,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACvB,MAAM,MAAM,GAAW,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;YAEtD,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;gBACvC,SAAS;YACb,CAAC;YAED,IAAI,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC5D,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACrB,CAAC;QACL,CAAC;IACL,CAAC;IAED,OAAO,KAAK,CAAC;AACjB,CAAC","sourcesContent":["/**\n * Пометки на надстройках: какой раздел заведён ради отправленного предложения и когда его можно\n * снять.\n *\n * Надстройку заводят в день отправки предложения, а снимают, когда правка выходит в новой\n * редакции пакета. Между этими днями проходит месяц, другой заход и другой повод: в минуту\n * обновления у исполнителя в руках разложенный ресурс и надстройка, а записи предложений он не\n * открывает — на них ничто не указывает. Надстройка остаётся замещать раздел, который пакет\n * давно содержит сам.\n *\n * Пометка держит эту связь на самой надстройке: имя ресурса пакета, статья, ради которой раздел\n * заведён, и день отправки. Форма — комментарий разметки: он не виден в собранном тексте,\n * переживает слияние по заголовкам и читается машиной.\n *\n * Снимает раздел человек, а не команда: пометка называет одну статью, а в раздел могли дописать\n * и другое — снятие вслепую удалило бы то, чего пометка не касалась.\n */\nimport { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';\nimport { join, relative } from 'node:path';\n\nimport { OVERRIDES_DIR } from './config.js';\nimport { parseDocument, IDocument } from './sections.js';\nimport { byText } from './order.js';\n\n/**\n * Пометка раздела надстройки:\n * `<!-- rt-proposed: rules/task-flow.md · «статья целиком» · 2026-09-03 -->`\n *\n * Разделители те же, что у шапки раскладки: строку читает человек, и второй знак разделения в\n * тех же файлах читался бы как другая пометка.\n */\nconst MARK: RegExp = /<!-- rt-proposed:\\s?([^·]+)·\\s?«([^»]*)»\\s?·\\s?([0-9-]+) -->/;\n\nexport interface IOverrideMark {\n /** Файл надстройки от корня дерева. */\n readonly file: string;\n /** Заголовок раздела, который пометка держит. */\n readonly heading: string;\n /** Ресурс пакета, ради статьи которого раздел заведён. */\n readonly resource: string;\n /** Статья: та самая цитата, которую называло предложение. */\n readonly article: string;\n /** День отправки, `ГГГГ-ММ-ДД`. */\n readonly day: string;\n}\n\n/** Один пробел вместо любой последовательности пробелов: статья в разметке перенесена по ширине строки. */\nfunction flat(text: string): string {\n return text.replace(/\\s+/g, ' ').trim();\n}\n\n/** Пометки одного файла надстройки. Раздел без пометки считается постоянным. */\nexport function marksOfOverride(text: string, file: string): readonly IOverrideMark[] {\n const document: IDocument = parseDocument(text);\n const found: IOverrideMark[] = [];\n\n for (const section of document.sections) {\n const match: RegExpExecArray | null = MARK.exec(section.body);\n\n if (match) {\n found.push({\n file,\n heading: section.heading.replace(/^#+\\s*/, ''),\n resource: flat(match[1]),\n article: flat(match[2]),\n day: match[3].trim(),\n });\n }\n }\n\n return found;\n}\n\n/** Файлы каталога надстроек, путями от самого каталога. */\nfunction overrideFiles(dir: string): readonly string[] {\n if (!existsSync(dir)) {\n return [];\n }\n\n const found: string[] = [];\n const walk: (at: string) => void = (at: string): void => {\n for (const name of readdirSync(at).sort(byText)) {\n const path: string = join(at, name);\n\n if (statSync(path).isDirectory()) {\n walk(path);\n } else {\n found.push(path);\n }\n }\n };\n\n walk(dir);\n\n return found;\n}\n\n/**\n * Надстройки, чья статья в новой редакции пакета уже есть.\n *\n * `assetsDir` — каталог ресурсов той редакции, которая раскладывается сейчас: сравнение идёт с\n * ней, а не с тем, что уже лежит в дереве. Ресурса в редакции нет — пометка молчит: пакет мог его\n * переименовать, и называть надстройку лишней на этом основании значило бы советовать снять то,\n * чему замена не пришла.\n */\nexport function staleOverrides(root: string, assetsDir: string): readonly IOverrideMark[] {\n const dir: string = join(root, OVERRIDES_DIR);\n const stale: IOverrideMark[] = [];\n\n for (const path of overrideFiles(dir)) {\n const marks: readonly IOverrideMark[] = marksOfOverride(readFileSync(path, 'utf8'), relative(root, path));\n\n for (const mark of marks) {\n const source: string = join(assetsDir, mark.resource);\n\n if (!mark.article || !existsSync(source)) {\n continue;\n }\n\n if (flat(readFileSync(source, 'utf8')).includes(mark.article)) {\n stale.push(mark);\n }\n }\n }\n\n return stale;\n}\n"]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Итоговый набор перед пушем и то, что печатало умолчание, а в итог не попало.
|
|
3
|
+
*
|
|
4
|
+
* Разница считается по вызовам, а не по признаку переопределения: дерево, переписавшее функцию
|
|
5
|
+
* целиком и вернувшее тот же набор, из умолчания не убрало ничего, а дерево, дописавшее фильтр,
|
|
6
|
+
* убрало — и по одному факту переопределения эти два случая неразличимы.
|
|
7
|
+
*
|
|
8
|
+
* Названа разница ровно тем, что она есть: строкой умолчания, которой в итоге нет. Снятием
|
|
9
|
+
* охраны она НЕ называется — дерево могло заменить команду своим вариантом, зовущим ту же
|
|
10
|
+
* проверку другим прогонщиком, и такая замена выглядит отсюда точно так же. Что из двух
|
|
11
|
+
* случилось, видно только в надстройке, и читает её человек.
|
|
12
|
+
*/
|
|
13
|
+
export declare function pushGateLines(root: string, defaults: string): string[];
|
|
14
|
+
//# sourceMappingURL=push-gate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"push-gate.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/push-gate.ts"],"names":[],"mappings":"AAsEA;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAoBtE"}
|
package/lib/push-gate.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Набор проверок перед пушем — итоговый и снятое из умолчания.
|
|
3
|
+
*
|
|
4
|
+
* Набор собирается из двух файлов: умолчания пакета и надстройки профиля дерева. Результат
|
|
5
|
+
* сборки не показывался нигде — чтобы его прочитать, надо было звать функцию профиля руками из
|
|
6
|
+
* оболочки. Из этой невидимости выросли три разных промаха: дерево дописало в надстройку команду,
|
|
7
|
+
* уже стоявшую в умолчании, и гоняло проверку дважды за пуш; другое дерево выкусило из умолчания
|
|
8
|
+
* сплошную проверку одной строкой `rt_push_checks_default "$@" | grep -v …` — строка выглядела
|
|
9
|
+
* настройкой, а была снятием охраны; а расхождение набора с конвейером узнавалось из красного
|
|
10
|
+
* конвейера после заявки.
|
|
11
|
+
*
|
|
12
|
+
* Набор ЗОВЁТСЯ, а не пересказывается чтением: он ветвится по содержимому дерева — есть ли
|
|
13
|
+
* настройка раскладки, есть ли конфиг линтера стилей, какие проверки разложены, — и разобранный
|
|
14
|
+
* по тексту функции разойдётся с настоящим молча.
|
|
15
|
+
*
|
|
16
|
+
* ОТКАЗ В ПОЛЬЗУ ЧИТАТЕЛЯ: нет профиля, нет функции, вызов упал или завис — раздела нет, а
|
|
17
|
+
* причина названа строкой. Разбор состояния ничего не пишет, и своим отказом чинить тут нечего.
|
|
18
|
+
*/
|
|
19
|
+
import { execFileSync } from 'node:child_process';
|
|
20
|
+
import { existsSync } from 'node:fs';
|
|
21
|
+
import { join } from 'node:path';
|
|
22
|
+
/** Имя функции профиля, собирающей набор, и имя её умолчания. */
|
|
23
|
+
const PUSH_CHECKS = 'rt_push_checks';
|
|
24
|
+
const PUSH_CHECKS_DEFAULT = `${PUSH_CHECKS}_default`;
|
|
25
|
+
/** Сколько ждать сборки набора. Сама она ничего не гоняет — печатает строки. */
|
|
26
|
+
const TIMEOUT_MS = 10_000;
|
|
27
|
+
/**
|
|
28
|
+
* Строки, которые вернула названная функция профиля. Профиль собирается из тех же файлов и в том
|
|
29
|
+
* же порядке, что читает сам гард: сперва разложенное умолчание пакета, поверх — надстройка
|
|
30
|
+
* дерева. Судить по одной надстройке значило бы получить пустой набор у всякого дерева, которое
|
|
31
|
+
* умолчаний не переписывало.
|
|
32
|
+
*/
|
|
33
|
+
function callProfile(root, defaults, fn) {
|
|
34
|
+
const sources = [join(root, defaults, 'project.sh'), join(root, '.claude/rt-kit', 'project.sh')].filter((one) => existsSync(one));
|
|
35
|
+
if (!sources.length) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
// База пустая: с ней умолчание печатает полный набор, а не набор по вкладу ветки. Разбор
|
|
39
|
+
// состояния читают, чтобы увидеть набор целиком, а не тот, что выпал бы на сегодняшнюю ветку.
|
|
40
|
+
const script = sources.map((one) => `. ${JSON.stringify(one)} 2>/dev/null`).join('; ') +
|
|
41
|
+
`; command -v ${fn} >/dev/null 2>&1 || exit 3; ${fn} ''`;
|
|
42
|
+
try {
|
|
43
|
+
// Полный путь к оболочке, а не имя: имя разрешается через `PATH`, а он в чужой среде
|
|
44
|
+
// бывает записываемым — тогда «bash» указывает не туда. Ищется он тем же способом, каким
|
|
45
|
+
// ищут его хуки: в двух местах, где оболочка лежит на всякой машине.
|
|
46
|
+
const shell = ['/bin/bash', '/usr/bin/bash'].find((one) => existsSync(one)) ?? '/bin/bash';
|
|
47
|
+
const out = execFileSync(shell, ['-c', script], {
|
|
48
|
+
cwd: root,
|
|
49
|
+
encoding: 'utf8',
|
|
50
|
+
timeout: TIMEOUT_MS,
|
|
51
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
52
|
+
});
|
|
53
|
+
return out
|
|
54
|
+
.split('\n')
|
|
55
|
+
.map((line) => line.trim())
|
|
56
|
+
.filter((line) => line.length > 0);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Итоговый набор перед пушем и то, что печатало умолчание, а в итог не попало.
|
|
64
|
+
*
|
|
65
|
+
* Разница считается по вызовам, а не по признаку переопределения: дерево, переписавшее функцию
|
|
66
|
+
* целиком и вернувшее тот же набор, из умолчания не убрало ничего, а дерево, дописавшее фильтр,
|
|
67
|
+
* убрало — и по одному факту переопределения эти два случая неразличимы.
|
|
68
|
+
*
|
|
69
|
+
* Названа разница ровно тем, что она есть: строкой умолчания, которой в итоге нет. Снятием
|
|
70
|
+
* охраны она НЕ называется — дерево могло заменить команду своим вариантом, зовущим ту же
|
|
71
|
+
* проверку другим прогонщиком, и такая замена выглядит отсюда точно так же. Что из двух
|
|
72
|
+
* случилось, видно только в надстройке, и читает её человек.
|
|
73
|
+
*/
|
|
74
|
+
export function pushGateLines(root, defaults) {
|
|
75
|
+
const final = callProfile(root, defaults, PUSH_CHECKS);
|
|
76
|
+
if (final === null) {
|
|
77
|
+
return ['набор перед пушем: собрать не удалось — нет профиля дерева либо функции rt_push_checks'];
|
|
78
|
+
}
|
|
79
|
+
const base = callProfile(root, defaults, PUSH_CHECKS_DEFAULT);
|
|
80
|
+
const cut = base === null ? [] : base.filter((one) => !final.includes(one));
|
|
81
|
+
return [
|
|
82
|
+
`набор перед пушем: ${final.length}`,
|
|
83
|
+
...final.map((one) => ` ${one}`),
|
|
84
|
+
...(cut.length
|
|
85
|
+
? [
|
|
86
|
+
`умолчание печатало, а в наборе нет: ${cut.length}`,
|
|
87
|
+
...cut.map((one) => ` ${one}`),
|
|
88
|
+
' это либо снятие охраны, либо замена своим вариантом той же проверки — различает их надстройка профиля',
|
|
89
|
+
]
|
|
90
|
+
: []),
|
|
91
|
+
];
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=push-gate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"push-gate.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/push-gate.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Набор проверок перед пушем — итоговый и снятое из умолчания.\n *\n * Набор собирается из двух файлов: умолчания пакета и надстройки профиля дерева. Результат\n * сборки не показывался нигде — чтобы его прочитать, надо было звать функцию профиля руками из\n * оболочки. Из этой невидимости выросли три разных промаха: дерево дописало в надстройку команду,\n * уже стоявшую в умолчании, и гоняло проверку дважды за пуш; другое дерево выкусило из умолчания\n * сплошную проверку одной строкой `rt_push_checks_default \"$@\" | grep -v …` — строка выглядела\n * настройкой, а была снятием охраны; а расхождение набора с конвейером узнавалось из красного\n * конвейера после заявки.\n *\n * Набор ЗОВЁТСЯ, а не пересказывается чтением: он ветвится по содержимому дерева — есть ли\n * настройка раскладки, есть ли конфиг линтера стилей, какие проверки разложены, — и разобранный\n * по тексту функции разойдётся с настоящим молча.\n *\n * ОТКАЗ В ПОЛЬЗУ ЧИТАТЕЛЯ: нет профиля, нет функции, вызов упал или завис — раздела нет, а\n * причина названа строкой. Разбор состояния ничего не пишет, и своим отказом чинить тут нечего.\n */\nimport { execFileSync } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { join } from 'node:path';\n\n/** Имя функции профиля, собирающей набор, и имя её умолчания. */\nconst PUSH_CHECKS: string = 'rt_push_checks';\nconst PUSH_CHECKS_DEFAULT: string = `${PUSH_CHECKS}_default`;\n\n/** Сколько ждать сборки набора. Сама она ничего не гоняет — печатает строки. */\nconst TIMEOUT_MS: number = 10_000;\n\n/**\n * Строки, которые вернула названная функция профиля. Профиль собирается из тех же файлов и в том\n * же порядке, что читает сам гард: сперва разложенное умолчание пакета, поверх — надстройка\n * дерева. Судить по одной надстройке значило бы получить пустой набор у всякого дерева, которое\n * умолчаний не переписывало.\n */\nfunction callProfile(root: string, defaults: string, fn: string): readonly string[] | null {\n const sources: readonly string[] = [join(root, defaults, 'project.sh'), join(root, '.claude/rt-kit', 'project.sh')].filter(\n (one: string): boolean => existsSync(one)\n );\n if (!sources.length) {\n return null;\n }\n\n // База пустая: с ней умолчание печатает полный набор, а не набор по вкладу ветки. Разбор\n // состояния читают, чтобы увидеть набор целиком, а не тот, что выпал бы на сегодняшнюю ветку.\n const script: string =\n sources.map((one: string): string => `. ${JSON.stringify(one)} 2>/dev/null`).join('; ') +\n `; command -v ${fn} >/dev/null 2>&1 || exit 3; ${fn} ''`;\n\n try {\n // Полный путь к оболочке, а не имя: имя разрешается через `PATH`, а он в чужой среде\n // бывает записываемым — тогда «bash» указывает не туда. Ищется он тем же способом, каким\n // ищут его хуки: в двух местах, где оболочка лежит на всякой машине.\n const shell: string = ['/bin/bash', '/usr/bin/bash'].find((one: string): boolean => existsSync(one)) ?? '/bin/bash';\n const out: string = execFileSync(shell, ['-c', script], {\n cwd: root,\n encoding: 'utf8',\n timeout: TIMEOUT_MS,\n stdio: ['ignore', 'pipe', 'ignore'],\n });\n\n return out\n .split('\\n')\n .map((line: string): string => line.trim())\n .filter((line: string): boolean => line.length > 0);\n } catch {\n return null;\n }\n}\n\n/**\n * Итоговый набор перед пушем и то, что печатало умолчание, а в итог не попало.\n *\n * Разница считается по вызовам, а не по признаку переопределения: дерево, переписавшее функцию\n * целиком и вернувшее тот же набор, из умолчания не убрало ничего, а дерево, дописавшее фильтр,\n * убрало — и по одному факту переопределения эти два случая неразличимы.\n *\n * Названа разница ровно тем, что она есть: строкой умолчания, которой в итоге нет. Снятием\n * охраны она НЕ называется — дерево могло заменить команду своим вариантом, зовущим ту же\n * проверку другим прогонщиком, и такая замена выглядит отсюда точно так же. Что из двух\n * случилось, видно только в надстройке, и читает её человек.\n */\nexport function pushGateLines(root: string, defaults: string): string[] {\n const final: readonly string[] | null = callProfile(root, defaults, PUSH_CHECKS);\n if (final === null) {\n return ['набор перед пушем: собрать не удалось — нет профиля дерева либо функции rt_push_checks'];\n }\n\n const base: readonly string[] | null = callProfile(root, defaults, PUSH_CHECKS_DEFAULT);\n const cut: readonly string[] = base === null ? [] : base.filter((one: string): boolean => !final.includes(one));\n\n return [\n `набор перед пушем: ${final.length}`,\n ...final.map((one: string): string => ` ${one}`),\n ...(cut.length\n ? [\n `умолчание печатало, а в наборе нет: ${cut.length}`,\n ...cut.map((one: string): string => ` ${one}`),\n ' это либо снятие охраны, либо замена своим вариантом той же проверки — различает их надстройка профиля',\n ]\n : []),\n ];\n}\n"]}
|
package/lib/shipment.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shipment.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/shipment.ts"],"names":[],"mappings":"AAcA,OAAO,EAGH,cAAc,EACd,eAAe,EACf,iBAAiB,EAEjB,eAAe,EACf,aAAa,EAChB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAEhE,OAAO,EAAe,QAAQ,EAA+B,MAAM,mBAAmB,CAAC;AACvF,OAAO,EAAS,SAAS,EAAyE,MAAM,gBAAgB,CAAC;AACzH,OAAO,EAAE,SAAS,EAAuB,KAAK,EAAE,MAAM,WAAW,CAAC;AAalE,4FAA4F;AAC5F,MAAM,WAAW,YAAY;IACzB,uDAAuD;IACvD,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IACrB,4EAA4E;IAC5E,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,0FAA0F;IAC1F,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,iGAAiG;IACjG,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAcnD;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAIjE;AAaD,uFAAuF;AACvF,wBAAgB,YAAY,CACxB,OAAO,EAAE,QAAQ,EACjB,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,SAAS,cAAc,EAAE,EACpC,QAAQ,EAAE,SAAS,MAAM,EAAE,EAC3B,KAAK,EAAE,WAAW,CAAC,MAAM,CAAC,GAC3B,aAAa,CAkBf;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,eAAe,EAAE,CAe5E;AAED,iFAAiF;AACjF,wBAAgB,WAAW,CAAC,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,eAAe,EAAE,WAAW,EAAE,iBAAiB,GAAG,SAAS,SAAS,EAAE,CAMpI;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,SAAS,SAAS,EAAE,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,CAWxH;AAsJD;;;;;;GAMG;AACH,wBAAsB,OAAO,CAAC,GAAG,EAAE,YAAY,EAAE,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,iBAAiB,CAAC,
|
|
1
|
+
{"version":3,"file":"shipment.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/shipment.ts"],"names":[],"mappings":"AAcA,OAAO,EAGH,cAAc,EACd,eAAe,EACf,iBAAiB,EAEjB,eAAe,EACf,aAAa,EAChB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAEhE,OAAO,EAAe,QAAQ,EAA+B,MAAM,mBAAmB,CAAC;AACvF,OAAO,EAAS,SAAS,EAAyE,MAAM,gBAAgB,CAAC;AACzH,OAAO,EAAE,SAAS,EAAuB,KAAK,EAAE,MAAM,WAAW,CAAC;AAalE,4FAA4F;AAC5F,MAAM,WAAW,YAAY;IACzB,uDAAuD;IACvD,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IACrB,4EAA4E;IAC5E,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,0FAA0F;IAC1F,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,iGAAiG;IACjG,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAcnD;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAIjE;AAaD,uFAAuF;AACvF,wBAAgB,YAAY,CACxB,OAAO,EAAE,QAAQ,EACjB,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,SAAS,cAAc,EAAE,EACpC,QAAQ,EAAE,SAAS,MAAM,EAAE,EAC3B,KAAK,EAAE,WAAW,CAAC,MAAM,CAAC,GAC3B,aAAa,CAkBf;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,eAAe,EAAE,CAe5E;AAED,iFAAiF;AACjF,wBAAgB,WAAW,CAAC,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,eAAe,EAAE,WAAW,EAAE,iBAAiB,GAAG,SAAS,SAAS,EAAE,CAMpI;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,SAAS,SAAS,EAAE,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,CAWxH;AAsJD;;;;;;GAMG;AACH,wBAAsB,OAAO,CAAC,GAAG,EAAE,YAAY,EAAE,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAgHlG"}
|
package/lib/shipment.js
CHANGED
|
@@ -250,7 +250,7 @@ export async function propose(env, options) {
|
|
|
250
250
|
};
|
|
251
251
|
}
|
|
252
252
|
if (!config.intake) {
|
|
253
|
-
return refusal('отправлять некуда: адреса приёма нет', `он объявляется ключом \`intake\` в ${CONFIG_PATH}
|
|
253
|
+
return refusal('отправлять некуда: адреса приёма нет', `он объявляется ключом \`intake\` в ${CONFIG_PATH}, а само значение берётся у владельца приёма — вместе с кодом приглашения`);
|
|
254
254
|
}
|
|
255
255
|
const tree = treeSlugOf(options.remote, config.tree);
|
|
256
256
|
if (!tree) {
|