@rt-tools/agent-kit 0.11.0 → 0.13.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/assets/checks/board-runs.github.mjs +87 -0
- package/assets/checks/board.github.mjs +0 -40
- package/assets/checks/check-board.github.mjs +39 -8
- package/assets/checks/check-file-size.mjs +19 -4
- package/assets/checks/check-schema-drift.mjs +28 -5
- package/assets/checks/check-state-next.mjs +10 -2
- package/assets/checks/rt-kit-checks.config.mjs +28 -2
- package/assets/commands/feedback.md +8 -0
- package/assets/defaults/project.sh +59 -6
- package/assets/defaults/turn-map.md +8 -6
- package/assets/hooks/browser-guard-device-id.sh +3 -1
- package/assets/hooks/browser-guard-no-asking.sh +3 -1
- package/assets/hooks/browser-guard-no-other-drivers.sh +6 -4
- package/assets/hooks/browser-guard-require-select.sh +4 -2
- package/assets/hooks/claim-guard.sh +3 -1
- package/assets/hooks/conscience-guard.sh +3 -1
- package/assets/hooks/dev-server-guard.sh +6 -4
- package/assets/hooks/dispatch.sh +69 -0
- package/assets/hooks/docs-guard.sh +6 -4
- package/assets/hooks/exam-guard.sh +5 -3
- package/assets/hooks/git-guard-delivery-folder.sh +99 -0
- package/assets/hooks/git-guard-delivery-signature.sh +10 -4
- package/assets/hooks/git-guard-delivery.sh +58 -74
- package/assets/hooks/git-guard-main.sh +6 -4
- package/assets/hooks/git-guard-push-tests.sh +8 -6
- package/assets/hooks/grill-gate.sh +4 -2
- package/assets/hooks/handoff-entry-guard.sh +4 -2
- package/assets/hooks/handoff-write.sh +27 -6
- package/assets/hooks/hook-input.sh +77 -0
- package/assets/hooks/lint-after-edit.sh +5 -3
- package/assets/hooks/override-write-guard.sh +107 -0
- package/assets/hooks/postmortem-guard.sh +3 -1
- package/assets/hooks/proposal-guard.sh +3 -1
- package/assets/hooks/prose-style-guard.sh +5 -3
- package/assets/hooks/qa-dataid-guard.sh +4 -2
- package/assets/hooks/rerun-guard.sh +6 -4
- package/assets/hooks/reuse-first-guard.sh +5 -3
- package/assets/hooks/rule-article.sh +99 -0
- package/assets/hooks/rule-source-guard.sh +126 -0
- package/assets/hooks/skill-gate-rearm.sh +3 -1
- package/assets/hooks/skill-gate.sh +23 -2
- package/assets/hooks/skill-loaded.sh +3 -1
- package/assets/hooks/sql-guard-request.sh +2 -1
- package/assets/hooks/sql-guard.sh +4 -2
- package/assets/hooks/task-flow-guard.sh +24 -4
- package/assets/hooks/turn-exit-guard.sh +59 -17
- package/assets/hooks/waiting-turn-guard.sh +3 -1
- package/assets/hooks/window-fill-guard.sh +6 -4
- package/assets/laws/work-conduct.md +5 -9
- package/assets/patterns/dependencies-upgrade.md +1 -1
- package/assets/patterns/doc-style-write.md +3 -3
- package/assets/patterns/git-workflow-commit.azure.md +2 -202
- package/assets/patterns/git-workflow-commit.github.md +2 -258
- package/assets/patterns/git-workflow-commit.gitlab.md +1 -217
- package/assets/patterns/git-workflow-docker.md +3 -3
- package/assets/patterns/git-workflow-merge.md +3 -2
- package/assets/patterns/git-workflow-migration.md +3 -3
- package/assets/patterns/git-workflow-pr.azure.md +224 -0
- package/assets/patterns/git-workflow-pr.github.md +280 -0
- package/assets/patterns/git-workflow-pr.gitlab.md +240 -0
- package/assets/patterns/git-workflow-restart.md +3 -3
- package/assets/patterns/git-workflow-secrets.md +3 -3
- package/assets/patterns/task-flow-archive.md +195 -0
- package/assets/patterns/task-flow-close.md +72 -240
- package/assets/patterns/task-flow-handoff.md +4 -4
- package/assets/patterns/task-flow-resume.md +5 -3
- package/assets/pitfalls/agent-kit.md +80 -0
- package/assets/pitfalls/doc-style.md +80 -0
- package/assets/pitfalls/git-workflow.azure.md +50 -0
- package/assets/pitfalls/git-workflow.github.md +78 -0
- package/assets/pitfalls/git-workflow.gitlab.md +49 -0
- package/assets/pitfalls/spec-driven.md +36 -0
- package/assets/pitfalls/styling-bem.md +45 -0
- package/assets/pitfalls/task-flow.md +62 -0
- package/assets/pitfalls/testing.md +70 -0
- package/assets/rules/deploy-flow.azure.md +114 -0
- package/assets/rules/deploy-flow.github.md +122 -0
- package/assets/rules/deploy-flow.gitlab.md +116 -0
- package/assets/rules/doc-style.md +25 -76
- package/assets/rules/git-workflow.azure.md +6 -92
- package/assets/rules/git-workflow.github.md +20 -128
- package/assets/rules/git-workflow.gitlab.md +6 -93
- package/assets/rules/spec-driven.md +39 -30
- package/assets/rules/styling-bem.md +20 -39
- package/assets/rules/task-flow.md +56 -222
- package/assets/rules/testing.md +3 -64
- package/assets/rules/turn-conduct.md +210 -0
- package/assets/rules/typescript-conventions.md +15 -0
- package/assets/skills/agent-kit.md +67 -89
- package/assets/templates/pitfalls.md +10 -0
- package/assets/templates/proposal.md +16 -1
- package/assets/templates/rule.md +5 -3
- package/bin/agent-kit.d.ts.map +1 -1
- package/bin/agent-kit.js +1 -42
- package/bin/agent-kit.js.map +1 -1
- package/lib/assets.d.ts.map +1 -1
- package/lib/assets.js +6 -1
- package/lib/assets.js.map +1 -1
- package/lib/cascade.d.ts.map +1 -1
- package/lib/cascade.js +19 -1
- package/lib/cascade.js.map +1 -1
- package/lib/commands.d.ts.map +1 -1
- package/lib/commands.js +1 -0
- package/lib/commands.js.map +1 -1
- package/lib/config.d.ts +16 -1
- package/lib/config.d.ts.map +1 -1
- package/lib/config.js +8 -0
- package/lib/config.js.map +1 -1
- package/lib/hooks-map.d.ts +13 -0
- package/lib/hooks-map.d.ts.map +1 -1
- package/lib/hooks-map.js +33 -1
- package/lib/hooks-map.js.map +1 -1
- package/lib/proposals.d.ts +5 -1
- package/lib/proposals.d.ts.map +1 -1
- package/lib/proposals.js +74 -5
- package/lib/proposals.js.map +1 -1
- package/lib/ship.d.ts +1 -2
- package/lib/ship.d.ts.map +1 -1
- package/lib/ship.js +0 -54
- package/lib/ship.js.map +1 -1
- package/lib/shipment.d.ts.map +1 -1
- package/lib/shipment.fixture.d.ts +39 -0
- package/lib/shipment.fixture.d.ts.map +1 -0
- package/lib/shipment.fixture.js +99 -0
- package/lib/shipment.fixture.js.map +1 -0
- package/lib/shipment.js +59 -7
- package/lib/shipment.js.map +1 -1
- package/package.json +1 -1
- package/rt-tools-agent-kit-0.13.0.tgz +0 -0
- package/assets/commands/agent-kit-digest.md +0 -89
- package/assets/commands/rules-review.md +0 -98
- package/assets/patterns/cargo-triage-mark.md +0 -119
- package/assets/rules/cargo-triage.md +0 -126
- package/lib/cargo-state.d.ts +0 -62
- package/lib/cargo-state.d.ts.map +0 -1
- package/lib/cargo-state.js +0 -118
- package/lib/cargo-state.js.map +0 -1
- package/rt-tools-agent-kit-0.11.0.tgz +0 -0
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":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAA6F,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAG1I;;;GAGG;AACH,MAAM,CAAC,MAAM,eAAe,GAAW,MAAM,CAAC;AAyB9C,6FAA6F;AAC7F,SAAS,oBAAoB,CAAC,OAAe;IACzC,IAAI,GAAG,GAAW,OAAO,CAAC,MAAM,CAAC;IAEjC,OAAO,GAAG,GAAG,CAAC,IAAI,OAAO,CAAC,GAAG,GAAG,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;QACzC,GAAG,IAAI,CAAC,CAAC;IACb,CAAC;IAED,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;AACjC,CAAC;AAED,qFAAqF;AACrF,MAAM,UAAU,SAAS,CAAC,MAAc,EAAE,SAAiB;IACvD,OAAO,GAAG,oBAAoB,CAAC,MAAM,CAAC,eAAe,SAAS,EAAE,CAAC;AACrE,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,SAAS,CAAC,IAAY,EAAE,MAAc;IAClD,IAAI,CAAC,MAAM,EAAE,CAAC;QACV,OAAO,EAAE,CAAC;IACd,CAAC;IAED,MAAM,IAAI,GAAW,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;IAErG,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;AACrE,CAAC;AAED,sGAAsG;AACtG,SAAS,OAAO,CAAC,IAA6B,EAAE,KAAa;IACzD,OAAO,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AACrE,CAAC;AAED,qGAAqG;AACrG,SAAS,UAAU,CAAC,IAAY;IAC5B,IAAI,CAAC;QACD,MAAM,IAAI,GAA4B,IAAI,CAAC,KAAK,CAAC,IAAI,CAA4B,CAAC;QAElF,OAAO,OAAO,IAAI,CAAC,MAAM,CAAC,KAAK,QAAQ,IAAI,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,QAAQ;YACxE,CAAC,CAAC;gBACI,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC;gBAClB,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC;gBACpB,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,IAAI;gBACjC,KAAK,EAAE,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;gBAC7B,KAAK,EAAE,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;aAChC;YACH,CAAC,CAAC,IAAI,CAAC;IACf,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,IAAI,CAAC;IAChB,CAAC;AACL,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,8EAA8E;AAC9E,MAAM,CAAC,MAAM,QAAQ,GAAU,KAAK,EAAE,MAAc,EAAE,KAAa,EAAE,QAAmB,EAAqB,EAAE;IAC3G,IAAI,MAAgB,CAAC;IAErB,IAAI,CAAC;QACD,MAAM,GAAG,MAAM,KAAK,CAAC,SAAS,CAAC,MAAM,EAAE,QAAQ,CAAC,SAAS,CAAC,EAAE;YACxD,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,iBAAiB,CAAC,EAAE,KAAK,EAAE;YAC3E,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC;YACnC,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,QAAQ,EAAE,IAAI,EAAE,CAAC;IACpF,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,QAAQ,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;AACvH,CAAC,CAAC;AAEF,kGAAkG;AAClG,SAAS,QAAQ,CAAC,GAAY;IAC1B,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QACtB,OAAO,EAAE,CAAC;IACd,CAAC;IAED,OAAO,GAAG;SACL,MAAM,CAAC,CAAC,GAAY,EAAW,EAAE,CAAC,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI,CAAC;SAC1E,GAAG,CAAC,CAAC,GAAY,EAAqB,EAAE;QACrC,MAAM,IAAI,GAA4B,GAA8B,CAAC;QAErE,OAAO;YACH,EAAE,EAAE,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;YACnD,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAoB;YAC7C,GAAG,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACxB,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;SACjC,CAAC;IACN,CAAC,CAAC,CAAC;AACX,CAAC;AAED,yEAAyE;AACzE,SAAS,eAAe,CAAC,IAAY;IACjC,IAAI,CAAC;QACD,MAAM,IAAI,GAA4B,IAAI,CAAC,KAAK,CAAC,IAAI,CAA4B,CAAC;QAElF,OAAO,OAAO,IAAI,CAAC,MAAM,CAAC,KAAK,QAAQ,IAAI,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,QAAQ;YAC1E,CAAC,CAAC;gBACI,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC;gBAClB,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC;gBACxB,IAAI,EAAE,OAAO,IAAI,CAAC,MAAM,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;gBACzD,MAAM,EAAE,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;aACnC;YACH,CAAC,CAAC,IAAI,CAAC;IACf,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,IAAI,CAAC;IAChB,CAAC;AACL,CAAC;AAED,wFAAwF;AACxF,MAAM,CAAC,MAAM,QAAQ,GAAc,KAAK,EAAE,MAAc,EAAE,KAAa,EAAE,IAAqB,EAAoB,EAAE;IAChH,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,CAAC,iBAAiB,CAAC,EAAE,KAAK,EAAE;YAC3E,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,QAAQ,EAAE,IAAI,EAAE,CAAC;IACpF,CAAC;IAED,MAAM,IAAI,GAAW,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;IAEzC,OAAO;QACH,EAAE,EAAE,MAAM,CAAC,EAAE;QACb,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC;QAClB,QAAQ,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI;KACrD,CAAC;AACN,CAAC,CAAC","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"]}
|
|
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\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,
|
|
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,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;AAkID;;;;;;GAMG;AACH,wBAAsB,OAAO,CAAC,GAAG,EAAE,YAAY,EAAE,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAgHlG"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { IOutcomeOfCommand } from './commands.js';
|
|
2
|
+
import { IShipment, TShip } from './ship.js';
|
|
3
|
+
export declare const VERSION: string;
|
|
4
|
+
export declare const TODAY: string;
|
|
5
|
+
/** Ресурсы берутся из дерева пакета: снимок надстроек снимается с настоящего текста. */
|
|
6
|
+
export declare const ASSETS: string;
|
|
7
|
+
export declare const INTAKE: string;
|
|
8
|
+
export declare const REMOTE: string;
|
|
9
|
+
export declare const TOKEN_FILE: string;
|
|
10
|
+
export declare const TOKEN: string;
|
|
11
|
+
/** Ответ приёма о непринятом токене: им отвечает отозванный. */
|
|
12
|
+
export declare const TOKEN_REFUSED: number;
|
|
13
|
+
/** Файл предложений одноразового дерева: имя одно на все спеки — по нему же читают отметки. */
|
|
14
|
+
export declare const PROPOSALS_FILE: string;
|
|
15
|
+
/** Что уехало двойником — по запросу на строку, в том порядке, в каком уезжало. */
|
|
16
|
+
export declare let sent: IShipment[];
|
|
17
|
+
/** Корень одноразового дерева: он меняется каждой спекой, поэтому спрашивается, а не хранится. */
|
|
18
|
+
export declare function treeRoot(): string;
|
|
19
|
+
export declare function put(path: string, text: string): void;
|
|
20
|
+
export declare function get(path: string): string;
|
|
21
|
+
export declare function said(outcome: IOutcomeOfCommand): string;
|
|
22
|
+
/** Двойник приёма: отвечает принятым и называет месяц, как настоящий. */
|
|
23
|
+
export declare function accepting(created?: boolean): TShip;
|
|
24
|
+
/** Двойник приёма, который груз не принял: отозванный токен отвечает именно так. */
|
|
25
|
+
export declare function refusing(status: number, message: string): TShip;
|
|
26
|
+
/** Настройка дерева: конфиг заводится командой, а ключи отправки дописываются поверх. */
|
|
27
|
+
export declare function start(patch?: Record<string, unknown>): void;
|
|
28
|
+
export declare function shipping(ship?: TShip, dryRun?: boolean): Promise<IOutcomeOfCommand>;
|
|
29
|
+
/** Блоки предложений ложатся одним файлом дня: их порядок и есть порядок в файле. */
|
|
30
|
+
export declare function proposals(blocks: readonly string[]): void;
|
|
31
|
+
/** Забыть уехавшее: спеке о повторной отправке нужен чистый счёт со второго прогона. */
|
|
32
|
+
export declare function clearSent(): void;
|
|
33
|
+
/** Что уехало операциями, по порядку: этим спеки и судят состав груза. */
|
|
34
|
+
export declare function operationsSent(): readonly string[];
|
|
35
|
+
/** Завести одноразовое дерево: зовётся в `beforeEach` спеки. */
|
|
36
|
+
export declare function freshTree(): void;
|
|
37
|
+
/** Снести его: зовётся в `afterEach` спеки. */
|
|
38
|
+
export declare function dropTree(): void;
|
|
39
|
+
//# sourceMappingURL=shipment.fixture.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shipment.fixture.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/shipment.fixture.ts"],"names":[],"mappings":"AAcA,OAAO,EAAsB,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAGtE,OAAO,EAAE,SAAS,EAAY,KAAK,EAAE,MAAM,WAAW,CAAC;AAGvD,eAAO,MAAM,OAAO,EAAE,MAAgB,CAAC;AACvC,eAAO,MAAM,KAAK,EAAE,MAAqB,CAAC;AAC1C,wFAAwF;AACxF,eAAO,MAAM,MAAM,EAAE,MAA8C,CAAC;AACpE,eAAO,MAAM,MAAM,EAAE,MAA8B,CAAC;AACpD,eAAO,MAAM,MAAM,EAAE,MAAyC,CAAC;AAC/D,eAAO,MAAM,UAAU,EAAE,MAAwB,CAAC;AAClD,eAAO,MAAM,KAAK,EAAE,MAA6B,CAAC;AAClD,gEAAgE;AAChE,eAAO,MAAM,aAAa,EAAE,MAAY,CAAC;AACzC,+FAA+F;AAC/F,eAAO,MAAM,cAAc,EAAE,MAA+C,CAAC;AAE7E,mFAAmF;AACnF,eAAO,IAAI,IAAI,EAAE,SAAS,EAAO,CAAC;AAKlC,kGAAkG;AAClG,wBAAgB,QAAQ,IAAI,MAAM,CAEjC;AAED,wBAAgB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAGpD;AAED,wBAAgB,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAExC;AAED,wBAAgB,IAAI,CAAC,OAAO,EAAE,iBAAiB,GAAG,MAAM,CAEvD;AAED,yEAAyE;AACzE,wBAAgB,SAAS,CAAC,OAAO,GAAE,OAAc,GAAG,KAAK,CAWxD;AAED,oFAAoF;AACpF,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,KAAK,CAM/D;AAED,yFAAyF;AACzF,wBAAgB,KAAK,CAAC,KAAK,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAAG,IAAI,CAK/D;AAED,wBAAgB,QAAQ,CAAC,IAAI,GAAE,KAAmB,EAAE,MAAM,GAAE,OAAe,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAEvG;AAED,qFAAqF;AACrF,wBAAgB,SAAS,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAEzD;AAED,wFAAwF;AACxF,wBAAgB,SAAS,IAAI,IAAI,CAEhC;AAED,0EAA0E;AAC1E,wBAAgB,cAAc,IAAI,SAAS,MAAM,EAAE,CAElD;AAED,gEAAgE;AAChE,wBAAgB,SAAS,IAAI,IAAI,CAIhC;AAED,+CAA+C;AAC/C,wBAAgB,QAAQ,IAAI,IAAI,CAE/B"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Одноразовое дерево для спек отправки: настройка, двойники приёма и блоки предложений.
|
|
3
|
+
*
|
|
4
|
+
* Живёт отдельным модулем потому, что спек об отправке две — про сам груз и про отбой блоков, —
|
|
5
|
+
* и обвязка у них одна. Скопированная во второй файл, она разошлась бы с первым молча: двойник
|
|
6
|
+
* приёма отвечает полем, которое читают обе.
|
|
7
|
+
*
|
|
8
|
+
* Хуки жизни дерева модуль не ставит: он не набор спек, и глобальных хуков среды прогона у него
|
|
9
|
+
* нет. Заводит и сносит дерево вызывающий — двумя строками в своих хуках.
|
|
10
|
+
*/
|
|
11
|
+
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
12
|
+
import { tmpdir } from 'node:os';
|
|
13
|
+
import { dirname, join } from 'node:path';
|
|
14
|
+
import { init } from './commands.js';
|
|
15
|
+
import { CONFIG_PATH } from './config.js';
|
|
16
|
+
import { PROPOSALS_DIR } from './proposals.js';
|
|
17
|
+
import { propose } from './shipment.js';
|
|
18
|
+
export const VERSION = '0.1.0';
|
|
19
|
+
export const TODAY = '2026-08-14';
|
|
20
|
+
/** Ресурсы берутся из дерева пакета: снимок надстроек снимается с настоящего текста. */
|
|
21
|
+
export const ASSETS = join(__dirname, '..', '..', 'assets');
|
|
22
|
+
export const INTAKE = 'https://intake.test';
|
|
23
|
+
export const REMOTE = 'git@github.test:owner/tree.git';
|
|
24
|
+
export const TOKEN_FILE = '.secret-token';
|
|
25
|
+
export const TOKEN = 'токен-этого-дерева';
|
|
26
|
+
/** Ответ приёма о непринятом токене: им отвечает отозванный. */
|
|
27
|
+
export const TOKEN_REFUSED = 401;
|
|
28
|
+
/** Файл предложений одноразового дерева: имя одно на все спеки — по нему же читают отметки. */
|
|
29
|
+
export const PROPOSALS_FILE = `${PROPOSALS_DIR}/2026-08-12-probe.md`;
|
|
30
|
+
/** Что уехало двойником — по запросу на строку, в том порядке, в каком уезжало. */
|
|
31
|
+
export let sent = [];
|
|
32
|
+
let root = '';
|
|
33
|
+
let env = { root: '', version: VERSION, assetsDir: ASSETS };
|
|
34
|
+
/** Корень одноразового дерева: он меняется каждой спекой, поэтому спрашивается, а не хранится. */
|
|
35
|
+
export function treeRoot() {
|
|
36
|
+
return root;
|
|
37
|
+
}
|
|
38
|
+
export function put(path, text) {
|
|
39
|
+
mkdirSync(dirname(join(root, path)), { recursive: true });
|
|
40
|
+
writeFileSync(join(root, path), text, 'utf8');
|
|
41
|
+
}
|
|
42
|
+
export function get(path) {
|
|
43
|
+
return readFileSync(join(root, path), 'utf8');
|
|
44
|
+
}
|
|
45
|
+
export function said(outcome) {
|
|
46
|
+
return outcome.lines.join('\n');
|
|
47
|
+
}
|
|
48
|
+
/** Двойник приёма: отвечает принятым и называет месяц, как настоящий. */
|
|
49
|
+
export function accepting(created = true) {
|
|
50
|
+
return async (intake, token, shipment) => {
|
|
51
|
+
sent.push(shipment);
|
|
52
|
+
return {
|
|
53
|
+
ok: Boolean(intake && token),
|
|
54
|
+
status: created ? 201 : 200,
|
|
55
|
+
said: '',
|
|
56
|
+
accepted: { tree: 'дерево', month: '2026-08', created },
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/** Двойник приёма, который груз не принял: отозванный токен отвечает именно так. */
|
|
61
|
+
export function refusing(status, message) {
|
|
62
|
+
return async (_intake, _token, shipment) => {
|
|
63
|
+
sent.push(shipment);
|
|
64
|
+
return { status, ok: false, said: message, accepted: null };
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/** Настройка дерева: конфиг заводится командой, а ключи отправки дописываются поверх. */
|
|
68
|
+
export function start(patch = {}) {
|
|
69
|
+
init(root, [], { host: 'github' });
|
|
70
|
+
const config = JSON.parse(get(CONFIG_PATH));
|
|
71
|
+
writeFileSync(join(root, CONFIG_PATH), JSON.stringify({ ...config, intake: INTAKE, token: TOKEN_FILE, ...patch }, null, 4), 'utf8');
|
|
72
|
+
put(TOKEN_FILE, `${TOKEN}\n`);
|
|
73
|
+
}
|
|
74
|
+
export function shipping(ship = accepting(), dryRun = false) {
|
|
75
|
+
return propose(env, { dryRun, ship, remote: REMOTE, today: TODAY, days: 3 });
|
|
76
|
+
}
|
|
77
|
+
/** Блоки предложений ложатся одним файлом дня: их порядок и есть порядок в файле. */
|
|
78
|
+
export function proposals(blocks) {
|
|
79
|
+
put(PROPOSALS_FILE, `# Предложения\n\n${blocks.join('\n\n')}\n`);
|
|
80
|
+
}
|
|
81
|
+
/** Забыть уехавшее: спеке о повторной отправке нужен чистый счёт со второго прогона. */
|
|
82
|
+
export function clearSent() {
|
|
83
|
+
sent = [];
|
|
84
|
+
}
|
|
85
|
+
/** Что уехало операциями, по порядку: этим спеки и судят состав груза. */
|
|
86
|
+
export function operationsSent() {
|
|
87
|
+
return sent.map((shipment) => shipment.operation);
|
|
88
|
+
}
|
|
89
|
+
/** Завести одноразовое дерево: зовётся в `beforeEach` спеки. */
|
|
90
|
+
export function freshTree() {
|
|
91
|
+
root = mkdtempSync(join(tmpdir(), 'agent-kit-ship-'));
|
|
92
|
+
env = { root, version: VERSION, assetsDir: ASSETS };
|
|
93
|
+
sent = [];
|
|
94
|
+
}
|
|
95
|
+
/** Снести его: зовётся в `afterEach` спеки. */
|
|
96
|
+
export function dropTree() {
|
|
97
|
+
rmSync(root, { recursive: true, force: true });
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=shipment.fixture.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shipment.fixture.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/shipment.fixture.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Одноразовое дерево для спек отправки: настройка, двойники приёма и блоки предложений.\n *\n * Живёт отдельным модулем потому, что спек об отправке две — про сам груз и про отбой блоков, —\n * и обвязка у них одна. Скопированная во второй файл, она разошлась бы с первым молча: двойник\n * приёма отвечает полем, которое читают обе.\n *\n * Хуки жизни дерева модуль не ставит: он не набор спек, и глобальных хуков среды прогона у него\n * нет. Заводит и сносит дерево вызывающий — двумя строками в своих хуках.\n */\nimport { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';\nimport { tmpdir } from 'node:os';\nimport { dirname, join } from 'node:path';\n\nimport { IEnvironment, init, IOutcomeOfCommand } from './commands.js';\nimport { CONFIG_PATH } from './config.js';\nimport { PROPOSALS_DIR } from './proposals.js';\nimport { IShipment, IShipped, TShip } from './ship.js';\nimport { propose } from './shipment.js';\n\nexport const VERSION: string = '0.1.0';\nexport const TODAY: string = '2026-08-14';\n/** Ресурсы берутся из дерева пакета: снимок надстроек снимается с настоящего текста. */\nexport const ASSETS: string = join(__dirname, '..', '..', 'assets');\nexport const INTAKE: string = 'https://intake.test';\nexport const REMOTE: string = 'git@github.test:owner/tree.git';\nexport const TOKEN_FILE: string = '.secret-token';\nexport const TOKEN: string = 'токен-этого-дерева';\n/** Ответ приёма о непринятом токене: им отвечает отозванный. */\nexport const TOKEN_REFUSED: number = 401;\n/** Файл предложений одноразового дерева: имя одно на все спеки — по нему же читают отметки. */\nexport const PROPOSALS_FILE: string = `${PROPOSALS_DIR}/2026-08-12-probe.md`;\n\n/** Что уехало двойником — по запросу на строку, в том порядке, в каком уезжало. */\nexport let sent: IShipment[] = [];\n\nlet root: string = '';\nlet env: IEnvironment = { root: '', version: VERSION, assetsDir: ASSETS };\n\n/** Корень одноразового дерева: он меняется каждой спекой, поэтому спрашивается, а не хранится. */\nexport function treeRoot(): string {\n return root;\n}\n\nexport function put(path: string, text: string): void {\n mkdirSync(dirname(join(root, path)), { recursive: true });\n writeFileSync(join(root, path), text, 'utf8');\n}\n\nexport function get(path: string): string {\n return readFileSync(join(root, path), 'utf8');\n}\n\nexport function said(outcome: IOutcomeOfCommand): string {\n return outcome.lines.join('\\n');\n}\n\n/** Двойник приёма: отвечает принятым и называет месяц, как настоящий. */\nexport function accepting(created: boolean = true): TShip {\n return async (intake: string, token: string, shipment: IShipment): Promise<IShipped> => {\n sent.push(shipment);\n\n return {\n ok: Boolean(intake && token),\n status: created ? 201 : 200,\n said: '',\n accepted: { tree: 'дерево', month: '2026-08', created },\n };\n };\n}\n\n/** Двойник приёма, который груз не принял: отозванный токен отвечает именно так. */\nexport function refusing(status: number, message: string): TShip {\n return async (_intake: string, _token: string, shipment: IShipment): Promise<IShipped> => {\n sent.push(shipment);\n\n return { status, ok: false, said: message, accepted: null };\n };\n}\n\n/** Настройка дерева: конфиг заводится командой, а ключи отправки дописываются поверх. */\nexport function start(patch: Record<string, unknown> = {}): void {\n init(root, [], { host: 'github' });\n const config: Record<string, unknown> = JSON.parse(get(CONFIG_PATH)) as Record<string, unknown>;\n writeFileSync(join(root, CONFIG_PATH), JSON.stringify({ ...config, intake: INTAKE, token: TOKEN_FILE, ...patch }, null, 4), 'utf8');\n put(TOKEN_FILE, `${TOKEN}\\n`);\n}\n\nexport function shipping(ship: TShip = accepting(), dryRun: boolean = false): Promise<IOutcomeOfCommand> {\n return propose(env, { dryRun, ship, remote: REMOTE, today: TODAY, days: 3 });\n}\n\n/** Блоки предложений ложатся одним файлом дня: их порядок и есть порядок в файле. */\nexport function proposals(blocks: readonly string[]): void {\n put(PROPOSALS_FILE, `# Предложения\\n\\n${blocks.join('\\n\\n')}\\n`);\n}\n\n/** Забыть уехавшее: спеке о повторной отправке нужен чистый счёт со второго прогона. */\nexport function clearSent(): void {\n sent = [];\n}\n\n/** Что уехало операциями, по порядку: этим спеки и судят состав груза. */\nexport function operationsSent(): readonly string[] {\n return sent.map((shipment: IShipment): string => shipment.operation);\n}\n\n/** Завести одноразовое дерево: зовётся в `beforeEach` спеки. */\nexport function freshTree(): void {\n root = mkdtempSync(join(tmpdir(), 'agent-kit-ship-'));\n env = { root, version: VERSION, assetsDir: ASSETS };\n sent = [];\n}\n\n/** Снести его: зовётся в `afterEach` спеки. */\nexport function dropTree(): void {\n rmSync(root, { recursive: true, force: true });\n}\n"]}
|
package/lib/shipment.js
CHANGED
|
@@ -14,7 +14,7 @@ import { join } from 'node:path';
|
|
|
14
14
|
import { CARGO_SCHEMA_VERSION, } from './cargo.js';
|
|
15
15
|
import { CONFIG_PATH, readConfig } from './config.js';
|
|
16
16
|
import { readObservations, summarize } from './observations.js';
|
|
17
|
-
import { leaksIn, markSent, marksOf, readProposals, TO_PACKAGE } from './proposals.js';
|
|
17
|
+
import { leaksIn, markSent, marksOf, nearestMissing, readProposals, TO_PACKAGE } from './proposals.js';
|
|
18
18
|
import { readToken } from './ship.js';
|
|
19
19
|
import { laidOutSkills, packagedNames, treeSnapshot, unpickedOf } from './snapshot.js';
|
|
20
20
|
import { byText } from './order.js';
|
|
@@ -137,6 +137,29 @@ function describe(shipment, summary, proposals, postmortems) {
|
|
|
137
137
|
}
|
|
138
138
|
return `наблюдений ${summary.total} за ${summary.days} дн., надстроек ${summary.overrides.length}, невыбранного ${summary.unpicked.length}`;
|
|
139
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* Отметка об отбое: отбитый блок остаётся на диске и несёт причину.
|
|
142
|
+
*
|
|
143
|
+
* Удалённый блок пишется следующим заходом заново — следа разбора нет, и повтор возвращается.
|
|
144
|
+
* Отметка держит и то и другое: видно, что разбор был, и видно, почему он не стал правкой.
|
|
145
|
+
*/
|
|
146
|
+
function markRefused(root, refused) {
|
|
147
|
+
for (const one of refused) {
|
|
148
|
+
const path = join(root, one.proposal.file);
|
|
149
|
+
writeFileSync(path, markSent(readFileSync(path, 'utf8'), one.proposal, one.why, 'отбито'), 'utf8');
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Перечень груза: что именно уедет и куда — строками, которые читаются до самой отправки.
|
|
154
|
+
*
|
|
155
|
+
* Печатается он обоими прогонами, и разделены они не окончанием глагола, а первой строкой:
|
|
156
|
+
* «уехало» и «уехало бы» отличаются двумя буквами в хвосте, а строки под ними одинаковы до
|
|
157
|
+
* знака. Прочитанный не тем прогоном, вывод сухого читается сделанной работой — и наоборот,
|
|
158
|
+
* настоящая отправка читается пробой, после которой ничего не делают.
|
|
159
|
+
*/
|
|
160
|
+
function manifest(going, summary, proposals, postmortems) {
|
|
161
|
+
return going.map((shipment) => ` ${shipment.operation} — ${describe(shipment, summary, proposals, postmortems)}`);
|
|
162
|
+
}
|
|
140
163
|
/** Пометка об отправке: по ней предложение второй раз не уезжает. */
|
|
141
164
|
function markProposals(root, proposals, shipped) {
|
|
142
165
|
const mark = `приём:${shipped.accepted?.month ?? 'принято'}`;
|
|
@@ -165,7 +188,10 @@ function accepted(shipped) {
|
|
|
165
188
|
* Сами запросы. Отправленное помечается сразу, а не после всех: оборвавшаяся отправка иначе
|
|
166
189
|
* увозит половину и не оставляет следа, по которому видно, какую именно.
|
|
167
190
|
*/
|
|
168
|
-
async function send(root, intake, tree, token, ship, going, proposals) {
|
|
191
|
+
async function send(root, intake, tree, token, ship, going, proposals, listed) {
|
|
192
|
+
// Что уедет, названо до того, как уехало: отказ на втором запросе иначе оставляет человека с
|
|
193
|
+
// одной строкой о нём и без перечня, из которого видно, чего этот отказ стоил.
|
|
194
|
+
const head = [`ОТПРАВКА — груз уходит в ${intake}, дерево ${tree}:`, ...listed];
|
|
169
195
|
const done = [];
|
|
170
196
|
for (const shipment of going) {
|
|
171
197
|
const shipped = await ship(intake, token, shipment);
|
|
@@ -173,6 +199,7 @@ async function send(root, intake, tree, token, ship, going, proposals) {
|
|
|
173
199
|
return {
|
|
174
200
|
code: REFUSED,
|
|
175
201
|
lines: [
|
|
202
|
+
...head,
|
|
176
203
|
...(done.length ? [`уехало до отказа: ${done.length}`, ...done] : ['не уехало ничего']),
|
|
177
204
|
`${shipment.kind}: ${intake} ответил ${shipped.status || 'молчанием'} — ${shipped.said}`,
|
|
178
205
|
...(shipped.status === TOKEN_REFUSED
|
|
@@ -186,7 +213,7 @@ async function send(root, intake, tree, token, ship, going, proposals) {
|
|
|
186
213
|
markProposals(root, proposals, shipped);
|
|
187
214
|
}
|
|
188
215
|
}
|
|
189
|
-
return { code: 0, lines: [
|
|
216
|
+
return { code: 0, lines: [...head, 'уехало:', ...done] };
|
|
190
217
|
}
|
|
191
218
|
/**
|
|
192
219
|
* Отправка груза в приём.
|
|
@@ -224,28 +251,53 @@ export async function propose(env, options) {
|
|
|
224
251
|
const read = readObservations(root, options.today, options.days);
|
|
225
252
|
const summary = summarize(read.observations, laidOutSkills(config, assetsDir), options.days);
|
|
226
253
|
const cargo = summaryCargo(summary, tree, treeSnapshot(config, assetsDir, root), unpickedOf(config, assetsDir), packagedNames(config, assetsDir));
|
|
227
|
-
const
|
|
254
|
+
const ready = readProposals(root).filter((entry) => entry.address === TO_PACKAGE && !entry.sent);
|
|
255
|
+
// Блок, не назвавший ближайшего утверждения ресурса, не уезжает: разбор, кончившийся ещё
|
|
256
|
+
// одной статьёй о том, о чём статья уже стоит, снаружи неотличим от разбора, кончившегося
|
|
257
|
+
// исправлением. Отбивается он поимённо, а остальные едут: один непрочитанный ресурс не
|
|
258
|
+
// повод задержать чужую работу.
|
|
259
|
+
const refused = ready
|
|
260
|
+
.map((entry) => ({ proposal: entry, why: nearestMissing(entry, assetsDir) }))
|
|
261
|
+
.filter((entry) => Boolean(entry.why));
|
|
262
|
+
const mine = ready.filter((entry) => !refused.some((one) => one.proposal === entry));
|
|
228
263
|
const proposals = {
|
|
229
264
|
tree,
|
|
230
265
|
schema: CARGO_SCHEMA_VERSION,
|
|
231
266
|
items: mine.map((entry) => ({ text: entry.body, address: entry.address, resource: entry.resource })),
|
|
232
267
|
};
|
|
233
268
|
const postmortems = { tree, schema: CARGO_SCHEMA_VERSION, items: readPostmortems(root, config.postmortems) };
|
|
234
|
-
|
|
269
|
+
// Проверка на адрес дерева судит все готовые блоки, а не одни уезжающие: отбитый по цитате
|
|
270
|
+
// лежит на диске и уедет, как только его починят, — а найденная в нём утечка отбивает
|
|
271
|
+
// отправку целиком и должна называться сразу.
|
|
272
|
+
const leaked = leaksOfCargo(cargo, ready, marksOf(root, options.remote));
|
|
235
273
|
if (leaked.length) {
|
|
236
274
|
return refusal(`отправка не начата: в грузе назван адрес этого дерева — ${leaked.length}`, ...leaked, 'найденное отбивает отправку целиком: «уехало два из трёх» читается как «всё в порядке»');
|
|
237
275
|
}
|
|
238
276
|
const going = shipmentsOf(cargo, proposals, postmortems);
|
|
277
|
+
const listed = manifest(going, cargo, proposals, postmortems);
|
|
278
|
+
// Отбитое называется обоими прогонами: сухой показывает, что уехало бы, — и отбитое к этому
|
|
279
|
+
// относится наравне с уезжающим.
|
|
280
|
+
const refusedLines = refused.map((one) => ` отбито ${one.proposal.file}:${one.proposal.line} — ${one.why}`);
|
|
239
281
|
if (options.dryRun) {
|
|
240
282
|
return {
|
|
241
283
|
code: 0,
|
|
242
284
|
lines: [
|
|
285
|
+
'СУХОЙ ПРОГОН — наружу не ушло ничего, отметок об отправке не поставлено',
|
|
243
286
|
`уехало бы в ${config.intake}, дерево ${tree}:`,
|
|
244
|
-
...
|
|
287
|
+
...listed,
|
|
288
|
+
...refusedLines,
|
|
245
289
|
...(read.silent ? ['наблюдений не велось ни разу — сводка уезжает снимком надстроек'] : []),
|
|
290
|
+
'отправляет это тот же вызов без `--dry-run`',
|
|
246
291
|
],
|
|
247
292
|
};
|
|
248
293
|
}
|
|
249
|
-
|
|
294
|
+
// Отметка ставится до отправки: отбитое наружу не едет вовсе, и ждать ответа приёма ей
|
|
295
|
+
// незачем.
|
|
296
|
+
markRefused(root, refused);
|
|
297
|
+
const outcome = await send(root, config.intake, tree, token, options.ship, going, mine, [
|
|
298
|
+
...listed,
|
|
299
|
+
...refusedLines,
|
|
300
|
+
]);
|
|
301
|
+
return outcome;
|
|
250
302
|
}
|
|
251
303
|
//# sourceMappingURL=shipment.js.map
|
package/lib/shipment.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shipment.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/shipment.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Сборка груза и команда отправки.\n *\n * Груз уезжает при каждом прогоне, а предложения и разборы — когда они есть: прогон без\n * замечаний тоже говорит, чем пользовались, чем не пользовались ни разу и что дерево\n * переопределило. Отправка, построенная вокруг предложения, эти данные теряла.\n *\n * Собирается груз здесь, а уезжает в `ship.ts`: сборка проверяется вызовом, а сеть в спеке\n * подменяется двойником.\n */\nimport { createHash } from 'node:crypto';\nimport { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nimport {\n CARGO_SCHEMA_VERSION,\n ICargoCount,\n ICargoOverride,\n IPostmortemItem,\n IPostmortemsCargo,\n IProposalItem,\n IProposalsCargo,\n ISummaryCargo,\n} from './cargo.js';\nimport { IEnvironment, IOutcomeOfCommand } from './commands.js';\nimport { CONFIG_PATH, IConfig, readConfig } from './config.js';\nimport { IReadResult, ISummary, readObservations, summarize } from './observations.js';\nimport { ILeak, IProposal, leaksIn, markSent, marksOf, readProposals, TO_PACKAGE } from './proposals.js';\nimport { IShipment, IShipped, readToken, TShip } from './ship.js';\nimport { laidOutSkills, packagedNames, treeSnapshot, unpickedOf } from './snapshot.js';\nimport { byText } from './order.js';\n\n/** Длина признака дерева. Двенадцати знаков хватает, чтобы деревья не сталкивались числом. */\nconst SLUG_LENGTH: number = 12;\n\n/** Отказ команды: именованных кодов у пакета нет — есть код возврата и текст. */\nconst REFUSED: number = 1;\n\n/** Ответ приёма о токене: по нему отказ добавляет строку про отзыв, а не гадает вслух. */\nconst TOKEN_REFUSED: number = 401;\n\n/** Чем отправка живёт: чем отправлять, чем себя выдаёт дерево и за какой отрезок сводка. */\nexport interface IShipOptions {\n /** Печатать, что уехало бы, и не отправлять ничего. */\n readonly dryRun: boolean;\n readonly ship: TShip;\n /** Адрес удалённого репозитория этого дерева: из него считается признак. */\n readonly remote: string;\n /** Сегодняшний день: своих часов у команды нет — иначе сводку за отрезок не проверить спекой. */\n readonly today: string;\n readonly days: number;\n}\n\n/**\n * Адрес удалённого репозитория, приведённый к одному виду.\n *\n * Форм у него несколько — `git@host:владелец/имя.git`, `https://host/владелец/имя`, — и все они\n * ведут к одному дереву. Две рабочие копии одного репозитория обязаны дать один признак, а\n * непривёденные формы дали бы два.\n */\nexport function remoteMarkOf(remote: string): string {\n const named: string = remote\n .trim()\n .replace(/\\.git$/, '')\n .replace(/^[a-z+]+:\\/\\//i, '')\n .replace(/^[^@/]+@/, '')\n .replace(/:/g, '/');\n let end: number = named.length;\n\n while (end > 0 && named[end - 1] === '/') {\n end -= 1;\n }\n\n return named.slice(0, end).toLowerCase();\n}\n\n/**\n * Признак дерева: короткое значение, различающее деревья и не выдающее их адреса.\n *\n * Считается хешем от адреса репозитория: по нему адрес не восстанавливается, а у двух рабочих\n * копий одного репозитория он один. Репозитория нет — берётся то, что назвала настройка: иначе\n * все такие деревья слились бы в одно, и число деревьев в сводке стало бы неправдой молча.\n */\nexport function treeSlugOf(remote: string, spoken: string): string {\n const mark: string = remoteMarkOf(remote);\n\n return mark ? createHash('sha256').update(mark, 'utf8').digest('hex').slice(0, SLUG_LENGTH) : spoken;\n}\n\n/**\n * Счётчики по ресурсам пакета. Имя, которого пакет не знает, — имя правила самого дерева, и\n * наружу оно не едет: дерево называет свои правила по своим доменам и по себе самому.\n *\n * Число событий при этом остаётся полным: считается их столько, сколько было, — отброшены имена,\n * а не наблюдения.\n */\nfunction knownCounts(counts: readonly ICargoCount[], known: ReadonlySet<string>): readonly ICargoCount[] {\n return counts.filter((count: ICargoCount): boolean => known.has(count.name));\n}\n\n/** Сводка наблюдений вместе со снимком надстроек — то, что уезжает первым запросом. */\nexport function summaryCargo(\n summary: ISummary,\n tree: string,\n overrides: readonly ICargoOverride[],\n unpicked: readonly string[],\n known: ReadonlySet<string>\n): ISummaryCargo {\n return {\n tree,\n unpicked,\n overrides,\n schema: CARGO_SCHEMA_VERSION,\n days: summary.days,\n sessions: summary.sessions,\n loads: knownCounts(summary.loads, known),\n denials: knownCounts(summary.denials, known),\n // Род правки, на котором гейт отбивал, ресурсом не является: это `extension`, `command`\n // или `browser` — слова пакета, одинаковые у всех деревьев.\n kinds: summary.kinds,\n guards: knownCounts(summary.guards, known),\n unused: summary.unused,\n versions: summary.versions,\n total: summary.total,\n };\n}\n\n/**\n * Разборы происшествий, как они лежат на дереве.\n *\n * Текст едет целиком: разбор объясняет механизм промаха, и шапка без механизма не отвечает ни на\n * один вопрос, ради которого его читают.\n */\nexport function readPostmortems(root: string, dir: string): IPostmortemItem[] {\n const path: string = join(root, dir);\n\n if (!existsSync(path)) {\n return [];\n }\n\n return (\n readdirSync(path)\n // Описание каталога разбором не является: оно объясняет, что здесь лежит, а не механизм\n // промаха, — и в приёме встало бы записью, которой нечего сказать.\n .filter((file: string): boolean => file.endsWith('.md') && file !== 'README.md')\n .sort(byText)\n .map((file: string): IPostmortemItem => ({ file, text: readFileSync(join(path, file), 'utf8') }))\n );\n}\n\n/** Что уезжает и в каком порядке: сводка первой — ею заводится запись месяца. */\nexport function shipmentsOf(summary: ISummaryCargo, proposals: IProposalsCargo, postmortems: IPostmortemsCargo): readonly IShipment[] {\n return [\n { kind: 'сводка', operation: 'summary', body: summary },\n ...(proposals.items.length ? [{ kind: 'предложения', operation: 'proposals', body: proposals }] : []),\n ...(postmortems.items.length ? [{ kind: 'разборы', operation: 'postmortems', body: postmortems }] : []),\n ];\n}\n\n/**\n * Что в грузе называет дерево, из которого он уезжает.\n *\n * Проверка накрывает сводку и предложения, но не разбор происшествия: разбор по устройству\n * называет файлы дерева, где промах случился, и накрытая проверка отбивала бы каждую его\n * отправку.\n */\nexport function leaksOfCargo(summary: ISummaryCargo, proposals: readonly IProposal[], marks: readonly string[]): string[] {\n const inSummary: string[] = leaksIn(JSON.stringify(summary, null, 1), marks).map(\n (leak: ILeak): string => ` сводка: ${leak.why}\\n ${leak.text}`\n );\n const inProposals: string[] = proposals.flatMap((entry: IProposal): string[] =>\n leaksIn(entry.body, marks).map(\n (leak: ILeak): string => ` ${entry.file}:${entry.line + leak.line} — ${leak.why}\\n ${leak.text}`\n )\n );\n\n return [...inSummary, ...inProposals];\n}\n\n/** Отказ строками. */\nfunction refusal(...lines: readonly string[]): IOutcomeOfCommand {\n return { code: REFUSED, lines: [...lines] };\n}\n\n/** Что в отправляемом запросе, одной строкой: её читает человек перед тем, как отправить. */\nfunction describe(shipment: IShipment, summary: ISummaryCargo, proposals: IProposalsCargo, postmortems: IPostmortemsCargo): string {\n if (shipment.operation === 'proposals') {\n return `предложений ${proposals.items.length}`;\n }\n if (shipment.operation === 'postmortems') {\n return `разборов ${postmortems.items.length}`;\n }\n\n return `наблюдений ${summary.total} за ${summary.days} дн., надстроек ${summary.overrides.length}, невыбранного ${summary.unpicked.length}`;\n}\n\n/** Пометка об отправке: по ней предложение второй раз не уезжает. */\nfunction markProposals(root: string, proposals: readonly IProposal[], shipped: IShipped): void {\n const mark: string = `приём:${shipped.accepted?.month ?? 'принято'}`;\n\n for (const proposal of proposals) {\n const path: string = join(root, proposal.file);\n writeFileSync(path, markSent(readFileSync(path, 'utf8'), proposal, mark), 'utf8');\n }\n}\n\n/**\n * Что приём сказал о принятом: месяц записи, судьба самой записи и — у предложений — счёт\n * легшего и уже лежавшего.\n *\n * Без счёта строка одинакова и у прогона, привёзшего новое, и у прогона, у которого всё уже\n * лежало: человек читает второе как первое. Приём, счёта не приславший, оставляет строку\n * прежней — так же читаются сводка и разборы, у которых счёта нет вовсе.\n */\nfunction accepted(shipped: IShipped): string {\n if (!shipped.accepted) {\n return 'принято';\n }\n\n const record: string = `${shipped.accepted.month}${shipped.accepted.created ? ', запись заведена' : ', запись дописана'}`;\n const added: number | undefined = shipped.accepted.added;\n\n return added === undefined ? record : `${record}, принято ${added}, уже лежало ${shipped.accepted.known ?? 0}`;\n}\n\n/**\n * Сами запросы. Отправленное помечается сразу, а не после всех: оборвавшаяся отправка иначе\n * увозит половину и не оставляет следа, по которому видно, какую именно.\n */\nasync function send(\n root: string,\n intake: string,\n tree: string,\n token: string,\n ship: TShip,\n going: readonly IShipment[],\n proposals: readonly IProposal[]\n): Promise<IOutcomeOfCommand> {\n const done: string[] = [];\n\n for (const shipment of going) {\n const shipped: IShipped = await ship(intake, token, shipment);\n\n if (!shipped.ok) {\n return {\n code: REFUSED,\n lines: [\n ...(done.length ? [`уехало до отказа: ${done.length}`, ...done] : ['не уехало ничего']),\n `${shipment.kind}: ${intake} ответил ${shipped.status || 'молчанием'} — ${shipped.said}`,\n ...(shipped.status === TOKEN_REFUSED\n ? ['токен не принят: он отозван или заведён не тот — выдай новый командой приёма']\n : []),\n ],\n };\n }\n\n done.push(` ${shipment.kind} → ${accepted(shipped)}`);\n\n if (shipment.operation === 'proposals') {\n markProposals(root, proposals, shipped);\n }\n }\n\n return { code: 0, lines: [`уехало в ${intake}, дерево ${tree}:`, ...done] };\n}\n\n/**\n * Отправка груза в приём.\n *\n * Порядок отказов не случаен: выключенная запись отвечает нулём и молчит про адрес — она гасит\n * отправку целиком, вместе со снимком надстроек, и спрашивать про адрес у выключенного дерева\n * незачем.\n */\nexport async function propose(env: IEnvironment, options: IShipOptions): Promise<IOutcomeOfCommand> {\n const { root, assetsDir } = env;\n const config: IConfig | null = readConfig(root);\n\n if (!config) {\n return refusal(`конфига нет: ${CONFIG_PATH} — заводится \\`agent-kit init\\``);\n }\n if (!config.observe) {\n return {\n code: 0,\n lines: [\n 'запись наблюдений выключена ключом `observe` в конфиге — наружу не ушло ничего',\n 'выключатель гасит и отправку целиком, вместе со снимком надстроек',\n ],\n };\n }\n if (!config.intake) {\n return refusal('отправлять некуда: адреса приёма нет', `он объявляется ключом \\`intake\\` в ${CONFIG_PATH}`);\n }\n\n const tree: string = treeSlugOf(options.remote, config.tree);\n if (!tree) {\n return refusal(\n 'признак дерева не считается: удалённого репозитория нет',\n `назови его ключом \\`tree\\` в ${CONFIG_PATH} — иначе деревья без репозитория сольются в одно`\n );\n }\n\n const token: string = readToken(root, config.token);\n if (!token) {\n return refusal(\n 'токена дерева нет: груз остался неотправленным',\n `файл с ним называется ключом \\`token\\` в ${CONFIG_PATH} и лежит вне дерева`,\n 'выдаётся токен командой приёма — `tree:add` или `tree:token`'\n );\n }\n\n const read: IReadResult = readObservations(root, options.today, options.days);\n const summary: ISummary = summarize(read.observations, laidOutSkills(config, assetsDir), options.days);\n const cargo: ISummaryCargo = summaryCargo(\n summary,\n tree,\n treeSnapshot(config, assetsDir, root),\n unpickedOf(config, assetsDir),\n packagedNames(config, assetsDir)\n );\n\n const mine: readonly IProposal[] = readProposals(root).filter(\n (entry: IProposal): boolean => entry.address === TO_PACKAGE && !entry.sent\n );\n const proposals: IProposalsCargo = {\n tree,\n schema: CARGO_SCHEMA_VERSION,\n items: mine.map((entry: IProposal): IProposalItem => ({ text: entry.body, address: entry.address, resource: entry.resource })),\n };\n const postmortems: IPostmortemsCargo = { tree, schema: CARGO_SCHEMA_VERSION, items: readPostmortems(root, config.postmortems) };\n\n const leaked: readonly string[] = leaksOfCargo(cargo, mine, marksOf(root, options.remote));\n if (leaked.length) {\n return refusal(\n `отправка не начата: в грузе назван адрес этого дерева — ${leaked.length}`,\n ...leaked,\n 'найденное отбивает отправку целиком: «уехало два из трёх» читается как «всё в порядке»'\n );\n }\n\n const going: readonly IShipment[] = shipmentsOf(cargo, proposals, postmortems);\n\n if (options.dryRun) {\n return {\n code: 0,\n lines: [\n `уехало бы в ${config.intake}, дерево ${tree}:`,\n ...going.map(\n (shipment: IShipment): string => ` ${shipment.operation} — ${describe(shipment, cargo, proposals, postmortems)}`\n ),\n ...(read.silent ? ['наблюдений не велось ни разу — сводка уезжает снимком надстроек'] : []),\n ],\n };\n }\n\n return send(root, config.intake, tree, token, options.ship, going, mine);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"shipment.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/shipment.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Сборка груза и команда отправки.\n *\n * Груз уезжает при каждом прогоне, а предложения и разборы — когда они есть: прогон без\n * замечаний тоже говорит, чем пользовались, чем не пользовались ни разу и что дерево\n * переопределило. Отправка, построенная вокруг предложения, эти данные теряла.\n *\n * Собирается груз здесь, а уезжает в `ship.ts`: сборка проверяется вызовом, а сеть в спеке\n * подменяется двойником.\n */\nimport { createHash } from 'node:crypto';\nimport { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nimport {\n CARGO_SCHEMA_VERSION,\n ICargoCount,\n ICargoOverride,\n IPostmortemItem,\n IPostmortemsCargo,\n IProposalItem,\n IProposalsCargo,\n ISummaryCargo,\n} from './cargo.js';\nimport { IEnvironment, IOutcomeOfCommand } from './commands.js';\nimport { CONFIG_PATH, IConfig, readConfig } from './config.js';\nimport { IReadResult, ISummary, readObservations, summarize } from './observations.js';\nimport { ILeak, IProposal, leaksIn, markSent, marksOf, nearestMissing, readProposals, TO_PACKAGE } from './proposals.js';\nimport { IShipment, IShipped, readToken, TShip } from './ship.js';\nimport { laidOutSkills, packagedNames, treeSnapshot, unpickedOf } from './snapshot.js';\nimport { byText } from './order.js';\n\n/** Длина признака дерева. Двенадцати знаков хватает, чтобы деревья не сталкивались числом. */\nconst SLUG_LENGTH: number = 12;\n\n/** Отказ команды: именованных кодов у пакета нет — есть код возврата и текст. */\nconst REFUSED: number = 1;\n\n/** Ответ приёма о токене: по нему отказ добавляет строку про отзыв, а не гадает вслух. */\nconst TOKEN_REFUSED: number = 401;\n\n/** Чем отправка живёт: чем отправлять, чем себя выдаёт дерево и за какой отрезок сводка. */\nexport interface IShipOptions {\n /** Печатать, что уехало бы, и не отправлять ничего. */\n readonly dryRun: boolean;\n readonly ship: TShip;\n /** Адрес удалённого репозитория этого дерева: из него считается признак. */\n readonly remote: string;\n /** Сегодняшний день: своих часов у команды нет — иначе сводку за отрезок не проверить спекой. */\n readonly today: string;\n readonly days: number;\n}\n\n/**\n * Адрес удалённого репозитория, приведённый к одному виду.\n *\n * Форм у него несколько — `git@host:владелец/имя.git`, `https://host/владелец/имя`, — и все они\n * ведут к одному дереву. Две рабочие копии одного репозитория обязаны дать один признак, а\n * непривёденные формы дали бы два.\n */\nexport function remoteMarkOf(remote: string): string {\n const named: string = remote\n .trim()\n .replace(/\\.git$/, '')\n .replace(/^[a-z+]+:\\/\\//i, '')\n .replace(/^[^@/]+@/, '')\n .replace(/:/g, '/');\n let end: number = named.length;\n\n while (end > 0 && named[end - 1] === '/') {\n end -= 1;\n }\n\n return named.slice(0, end).toLowerCase();\n}\n\n/**\n * Признак дерева: короткое значение, различающее деревья и не выдающее их адреса.\n *\n * Считается хешем от адреса репозитория: по нему адрес не восстанавливается, а у двух рабочих\n * копий одного репозитория он один. Репозитория нет — берётся то, что назвала настройка: иначе\n * все такие деревья слились бы в одно, и число деревьев в сводке стало бы неправдой молча.\n */\nexport function treeSlugOf(remote: string, spoken: string): string {\n const mark: string = remoteMarkOf(remote);\n\n return mark ? createHash('sha256').update(mark, 'utf8').digest('hex').slice(0, SLUG_LENGTH) : spoken;\n}\n\n/**\n * Счётчики по ресурсам пакета. Имя, которого пакет не знает, — имя правила самого дерева, и\n * наружу оно не едет: дерево называет свои правила по своим доменам и по себе самому.\n *\n * Число событий при этом остаётся полным: считается их столько, сколько было, — отброшены имена,\n * а не наблюдения.\n */\nfunction knownCounts(counts: readonly ICargoCount[], known: ReadonlySet<string>): readonly ICargoCount[] {\n return counts.filter((count: ICargoCount): boolean => known.has(count.name));\n}\n\n/** Сводка наблюдений вместе со снимком надстроек — то, что уезжает первым запросом. */\nexport function summaryCargo(\n summary: ISummary,\n tree: string,\n overrides: readonly ICargoOverride[],\n unpicked: readonly string[],\n known: ReadonlySet<string>\n): ISummaryCargo {\n return {\n tree,\n unpicked,\n overrides,\n schema: CARGO_SCHEMA_VERSION,\n days: summary.days,\n sessions: summary.sessions,\n loads: knownCounts(summary.loads, known),\n denials: knownCounts(summary.denials, known),\n // Род правки, на котором гейт отбивал, ресурсом не является: это `extension`, `command`\n // или `browser` — слова пакета, одинаковые у всех деревьев.\n kinds: summary.kinds,\n guards: knownCounts(summary.guards, known),\n unused: summary.unused,\n versions: summary.versions,\n total: summary.total,\n };\n}\n\n/**\n * Разборы происшествий, как они лежат на дереве.\n *\n * Текст едет целиком: разбор объясняет механизм промаха, и шапка без механизма не отвечает ни на\n * один вопрос, ради которого его читают.\n */\nexport function readPostmortems(root: string, dir: string): IPostmortemItem[] {\n const path: string = join(root, dir);\n\n if (!existsSync(path)) {\n return [];\n }\n\n return (\n readdirSync(path)\n // Описание каталога разбором не является: оно объясняет, что здесь лежит, а не механизм\n // промаха, — и в приёме встало бы записью, которой нечего сказать.\n .filter((file: string): boolean => file.endsWith('.md') && file !== 'README.md')\n .sort(byText)\n .map((file: string): IPostmortemItem => ({ file, text: readFileSync(join(path, file), 'utf8') }))\n );\n}\n\n/** Что уезжает и в каком порядке: сводка первой — ею заводится запись месяца. */\nexport function shipmentsOf(summary: ISummaryCargo, proposals: IProposalsCargo, postmortems: IPostmortemsCargo): readonly IShipment[] {\n return [\n { kind: 'сводка', operation: 'summary', body: summary },\n ...(proposals.items.length ? [{ kind: 'предложения', operation: 'proposals', body: proposals }] : []),\n ...(postmortems.items.length ? [{ kind: 'разборы', operation: 'postmortems', body: postmortems }] : []),\n ];\n}\n\n/**\n * Что в грузе называет дерево, из которого он уезжает.\n *\n * Проверка накрывает сводку и предложения, но не разбор происшествия: разбор по устройству\n * называет файлы дерева, где промах случился, и накрытая проверка отбивала бы каждую его\n * отправку.\n */\nexport function leaksOfCargo(summary: ISummaryCargo, proposals: readonly IProposal[], marks: readonly string[]): string[] {\n const inSummary: string[] = leaksIn(JSON.stringify(summary, null, 1), marks).map(\n (leak: ILeak): string => ` сводка: ${leak.why}\\n ${leak.text}`\n );\n const inProposals: string[] = proposals.flatMap((entry: IProposal): string[] =>\n leaksIn(entry.body, marks).map(\n (leak: ILeak): string => ` ${entry.file}:${entry.line + leak.line} — ${leak.why}\\n ${leak.text}`\n )\n );\n\n return [...inSummary, ...inProposals];\n}\n\n/** Отказ строками. */\nfunction refusal(...lines: readonly string[]): IOutcomeOfCommand {\n return { code: REFUSED, lines: [...lines] };\n}\n\n/** Что в отправляемом запросе, одной строкой: её читает человек перед тем, как отправить. */\nfunction describe(shipment: IShipment, summary: ISummaryCargo, proposals: IProposalsCargo, postmortems: IPostmortemsCargo): string {\n if (shipment.operation === 'proposals') {\n return `предложений ${proposals.items.length}`;\n }\n if (shipment.operation === 'postmortems') {\n return `разборов ${postmortems.items.length}`;\n }\n\n return `наблюдений ${summary.total} за ${summary.days} дн., надстроек ${summary.overrides.length}, невыбранного ${summary.unpicked.length}`;\n}\n\n/** Блок, который не уехал, и причина этого. */\ninterface IRefusedProposal {\n readonly proposal: IProposal;\n readonly why: string;\n}\n\n/**\n * Отметка об отбое: отбитый блок остаётся на диске и несёт причину.\n *\n * Удалённый блок пишется следующим заходом заново — следа разбора нет, и повтор возвращается.\n * Отметка держит и то и другое: видно, что разбор был, и видно, почему он не стал правкой.\n */\nfunction markRefused(root: string, refused: readonly IRefusedProposal[]): void {\n for (const one of refused) {\n const path: string = join(root, one.proposal.file);\n writeFileSync(path, markSent(readFileSync(path, 'utf8'), one.proposal, one.why, 'отбито'), 'utf8');\n }\n}\n\n/**\n * Перечень груза: что именно уедет и куда — строками, которые читаются до самой отправки.\n *\n * Печатается он обоими прогонами, и разделены они не окончанием глагола, а первой строкой:\n * «уехало» и «уехало бы» отличаются двумя буквами в хвосте, а строки под ними одинаковы до\n * знака. Прочитанный не тем прогоном, вывод сухого читается сделанной работой — и наоборот,\n * настоящая отправка читается пробой, после которой ничего не делают.\n */\nfunction manifest(\n going: readonly IShipment[],\n summary: ISummaryCargo,\n proposals: IProposalsCargo,\n postmortems: IPostmortemsCargo\n): readonly string[] {\n return going.map((shipment: IShipment): string => ` ${shipment.operation} — ${describe(shipment, summary, proposals, postmortems)}`);\n}\n\n/** Пометка об отправке: по ней предложение второй раз не уезжает. */\nfunction markProposals(root: string, proposals: readonly IProposal[], shipped: IShipped): void {\n const mark: string = `приём:${shipped.accepted?.month ?? 'принято'}`;\n\n for (const proposal of proposals) {\n const path: string = join(root, proposal.file);\n writeFileSync(path, markSent(readFileSync(path, 'utf8'), proposal, mark), 'utf8');\n }\n}\n\n/**\n * Что приём сказал о принятом: месяц записи, судьба самой записи и — у предложений — счёт\n * легшего и уже лежавшего.\n *\n * Без счёта строка одинакова и у прогона, привёзшего новое, и у прогона, у которого всё уже\n * лежало: человек читает второе как первое. Приём, счёта не приславший, оставляет строку\n * прежней — так же читаются сводка и разборы, у которых счёта нет вовсе.\n */\nfunction accepted(shipped: IShipped): string {\n if (!shipped.accepted) {\n return 'принято';\n }\n\n const record: string = `${shipped.accepted.month}${shipped.accepted.created ? ', запись заведена' : ', запись дописана'}`;\n const added: number | undefined = shipped.accepted.added;\n\n return added === undefined ? record : `${record}, принято ${added}, уже лежало ${shipped.accepted.known ?? 0}`;\n}\n\n/**\n * Сами запросы. Отправленное помечается сразу, а не после всех: оборвавшаяся отправка иначе\n * увозит половину и не оставляет следа, по которому видно, какую именно.\n */\nasync function send(\n root: string,\n intake: string,\n tree: string,\n token: string,\n ship: TShip,\n going: readonly IShipment[],\n proposals: readonly IProposal[],\n listed: readonly string[]\n): Promise<IOutcomeOfCommand> {\n // Что уедет, названо до того, как уехало: отказ на втором запросе иначе оставляет человека с\n // одной строкой о нём и без перечня, из которого видно, чего этот отказ стоил.\n const head: readonly string[] = [`ОТПРАВКА — груз уходит в ${intake}, дерево ${tree}:`, ...listed];\n const done: string[] = [];\n\n for (const shipment of going) {\n const shipped: IShipped = await ship(intake, token, shipment);\n\n if (!shipped.ok) {\n return {\n code: REFUSED,\n lines: [\n ...head,\n ...(done.length ? [`уехало до отказа: ${done.length}`, ...done] : ['не уехало ничего']),\n `${shipment.kind}: ${intake} ответил ${shipped.status || 'молчанием'} — ${shipped.said}`,\n ...(shipped.status === TOKEN_REFUSED\n ? ['токен не принят: он отозван или заведён не тот — выдай новый командой приёма']\n : []),\n ],\n };\n }\n\n done.push(` ${shipment.kind} → ${accepted(shipped)}`);\n\n if (shipment.operation === 'proposals') {\n markProposals(root, proposals, shipped);\n }\n }\n\n return { code: 0, lines: [...head, 'уехало:', ...done] };\n}\n\n/**\n * Отправка груза в приём.\n *\n * Порядок отказов не случаен: выключенная запись отвечает нулём и молчит про адрес — она гасит\n * отправку целиком, вместе со снимком надстроек, и спрашивать про адрес у выключенного дерева\n * незачем.\n */\nexport async function propose(env: IEnvironment, options: IShipOptions): Promise<IOutcomeOfCommand> {\n const { root, assetsDir } = env;\n const config: IConfig | null = readConfig(root);\n\n if (!config) {\n return refusal(`конфига нет: ${CONFIG_PATH} — заводится \\`agent-kit init\\``);\n }\n if (!config.observe) {\n return {\n code: 0,\n lines: [\n 'запись наблюдений выключена ключом `observe` в конфиге — наружу не ушло ничего',\n 'выключатель гасит и отправку целиком, вместе со снимком надстроек',\n ],\n };\n }\n if (!config.intake) {\n return refusal('отправлять некуда: адреса приёма нет', `он объявляется ключом \\`intake\\` в ${CONFIG_PATH}`);\n }\n\n const tree: string = treeSlugOf(options.remote, config.tree);\n if (!tree) {\n return refusal(\n 'признак дерева не считается: удалённого репозитория нет',\n `назови его ключом \\`tree\\` в ${CONFIG_PATH} — иначе деревья без репозитория сольются в одно`\n );\n }\n\n const token: string = readToken(root, config.token);\n if (!token) {\n return refusal(\n 'токена дерева нет: груз остался неотправленным',\n `файл с ним называется ключом \\`token\\` в ${CONFIG_PATH} и лежит вне дерева`,\n 'выдаётся токен командой приёма — `tree:add` или `tree:token`'\n );\n }\n\n const read: IReadResult = readObservations(root, options.today, options.days);\n const summary: ISummary = summarize(read.observations, laidOutSkills(config, assetsDir), options.days);\n const cargo: ISummaryCargo = summaryCargo(\n summary,\n tree,\n treeSnapshot(config, assetsDir, root),\n unpickedOf(config, assetsDir),\n packagedNames(config, assetsDir)\n );\n\n const ready: readonly IProposal[] = readProposals(root).filter(\n (entry: IProposal): boolean => entry.address === TO_PACKAGE && !entry.sent\n );\n // Блок, не назвавший ближайшего утверждения ресурса, не уезжает: разбор, кончившийся ещё\n // одной статьёй о том, о чём статья уже стоит, снаружи неотличим от разбора, кончившегося\n // исправлением. Отбивается он поимённо, а остальные едут: один непрочитанный ресурс не\n // повод задержать чужую работу.\n const refused: readonly IRefusedProposal[] = ready\n .map((entry: IProposal): IRefusedProposal => ({ proposal: entry, why: nearestMissing(entry, assetsDir) }))\n .filter((entry: IRefusedProposal): boolean => Boolean(entry.why));\n const mine: readonly IProposal[] = ready.filter(\n (entry: IProposal): boolean => !refused.some((one: IRefusedProposal): boolean => one.proposal === entry)\n );\n const proposals: IProposalsCargo = {\n tree,\n schema: CARGO_SCHEMA_VERSION,\n items: mine.map((entry: IProposal): IProposalItem => ({ text: entry.body, address: entry.address, resource: entry.resource })),\n };\n const postmortems: IPostmortemsCargo = { tree, schema: CARGO_SCHEMA_VERSION, items: readPostmortems(root, config.postmortems) };\n\n // Проверка на адрес дерева судит все готовые блоки, а не одни уезжающие: отбитый по цитате\n // лежит на диске и уедет, как только его починят, — а найденная в нём утечка отбивает\n // отправку целиком и должна называться сразу.\n const leaked: readonly string[] = leaksOfCargo(cargo, ready, marksOf(root, options.remote));\n if (leaked.length) {\n return refusal(\n `отправка не начата: в грузе назван адрес этого дерева — ${leaked.length}`,\n ...leaked,\n 'найденное отбивает отправку целиком: «уехало два из трёх» читается как «всё в порядке»'\n );\n }\n\n const going: readonly IShipment[] = shipmentsOf(cargo, proposals, postmortems);\n\n const listed: readonly string[] = manifest(going, cargo, proposals, postmortems);\n // Отбитое называется обоими прогонами: сухой показывает, что уехало бы, — и отбитое к этому\n // относится наравне с уезжающим.\n const refusedLines: readonly string[] = refused.map(\n (one: IRefusedProposal): string => ` отбито ${one.proposal.file}:${one.proposal.line} — ${one.why}`\n );\n\n if (options.dryRun) {\n return {\n code: 0,\n lines: [\n 'СУХОЙ ПРОГОН — наружу не ушло ничего, отметок об отправке не поставлено',\n `уехало бы в ${config.intake}, дерево ${tree}:`,\n ...listed,\n ...refusedLines,\n ...(read.silent ? ['наблюдений не велось ни разу — сводка уезжает снимком надстроек'] : []),\n 'отправляет это тот же вызов без `--dry-run`',\n ],\n };\n }\n\n // Отметка ставится до отправки: отбитое наружу не едет вовсе, и ждать ответа приёма ей\n // незачем.\n markRefused(root, refused);\n\n const outcome: IOutcomeOfCommand = await send(root, config.intake, tree, token, options.ship, going, mine, [\n ...listed,\n ...refusedLines,\n ]);\n\n return outcome;\n}\n"]}
|
package/package.json
CHANGED
|
Binary file
|