@rt-tools/agent-kit 0.12.0 → 0.14.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 +132 -0
- package/assets/checks/board.github.mjs +43 -41
- package/assets/checks/check-board.github.mjs +73 -8
- package/assets/checks/check-schema-drift.mjs +28 -5
- package/assets/checks/rt-kit-checks.config.mjs +12 -0
- package/assets/checks/task-new.github.mjs +15 -3
- package/assets/commands/feedback.md +8 -0
- package/assets/defaults/project.sh +50 -5
- package/assets/hooks/browser-guard-no-other-drivers.sh +1 -1
- package/assets/hooks/claim-guard.sh +34 -0
- package/assets/hooks/dev-server-guard.sh +1 -1
- package/assets/hooks/dispatch.sh +48 -15
- 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 +21 -69
- package/assets/hooks/git-guard-push-tests.sh +2 -2
- package/assets/hooks/hook-input.sh +62 -0
- package/assets/hooks/override-write-guard.sh +107 -0
- package/assets/hooks/rerun-guard.sh +1 -1
- package/assets/hooks/rule-source-guard.sh +126 -0
- package/assets/hooks/task-flow-guard.sh +18 -0
- package/assets/hooks/turn-exit-guard.sh +33 -1
- package/assets/hooks/waiting-turn-guard.sh +39 -3
- package/assets/patterns/git-workflow-pr.azure.md +4 -4
- package/assets/patterns/git-workflow-pr.github.md +4 -4
- package/assets/patterns/git-workflow-pr.gitlab.md +4 -4
- package/assets/patterns/task-flow-archive.md +23 -21
- package/assets/patterns/task-flow-close.md +98 -96
- package/assets/patterns/task-flow-resume.md +5 -3
- package/assets/pitfalls/agent-kit.md +80 -0
- package/assets/pitfalls/git-workflow.github.md +8 -0
- package/assets/rules/deploy-flow.azure.md +15 -7
- package/assets/rules/deploy-flow.github.md +15 -6
- package/assets/rules/deploy-flow.gitlab.md +15 -7
- package/assets/rules/git-workflow.github.md +10 -5
- package/assets/rules/task-flow.md +44 -26
- package/assets/rules/turn-conduct.md +33 -0
- package/assets/skills/agent-kit.md +32 -77
- package/assets/templates/proposal.md +16 -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/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.14.0.tgz +0 -0
- package/rt-tools-agent-kit-0.12.0.tgz +0 -0
package/lib/proposals.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proposals.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/proposals.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Предложения по слою правил: разбор файла, отбор того, что уезжает наружу, и проверка на адрес\n * дерева.\n *\n * Файл пишет главный агент после разбора закрытой задачи — по блоку на предложение, с адресом в\n * заголовке. Адрес роль проставляет и так; здесь он впервые становится тем, что читает машина.\n *\n * Проверка на утечку живёт в этом же модуле не для удобства: предложение уезжает в чужой\n * репозиторий целиком, и запрет называть чужое дерево обязан держаться проверкой, а не памятью\n * того, кто пишет.\n */\nimport { existsSync, readdirSync, readFileSync } from 'node:fs';\nimport { basename, join } from 'node:path';\nimport { byText } from './order.js';\n\n/** Куда главный агент кладёт предложения. Путь от корня дерева. */\nexport const PROPOSALS_DIR: string = '.claude/rt-kit/proposals';\n\n/** Адрес предложения: куда правка идёт. Наружу уезжает только первый. */\nexport const TO_PACKAGE: string = 'пакет';\nexport const TO_COMPANION: string = 'компаньон';\nexport const TO_TREE: string = 'дерево';\n\nconst ADDRESSES: readonly string[] = [TO_PACKAGE, TO_COMPANION, TO_TREE];\n\n/** Разобранный заголовок блока. */\ninterface IHeading {\n readonly address: string;\n readonly resource: string;\n}\n\n/**\n * Заголовок блока: `## <адрес> · <ресурс>`. Разделитель — тот же, что в шаблоне.\n *\n * Разбирается строкой, а не образцом: образец на «слово, разделитель, остаток» перебирает\n * границу слова столько раз, сколько в строке знаков.\n */\nfunction headingOf(line: string): IHeading | null {\n if (!line.startsWith('## ')) {\n return null;\n }\n\n const rest: string = line.slice(3);\n const at: number = rest.indexOf('·');\n\n if (at < 0) {\n return null;\n }\n\n const address: string = rest.slice(0, at).trim();\n const resource: string = rest.slice(at + 1).trim();\n\n return address && !address.includes(' ') && resource ? { address, resource } : null;\n}\n\n/** Пометка об отправке. По ней же предложение узнаётся отправленным. */\nconst SENT: RegExp = /^-\\s+\\*\\*отправлено:\\*\\*\\s*(\\S+)/m;\n\nexport interface IProposal {\n readonly address: string;\n readonly resource: string;\n /** Тело блока без заголовка: место, повод и готовый текст. */\n readonly body: string;\n /** Ссылка на заведённую запись; пусто — не отправлялось. */\n readonly sent: string;\n /** Файл, в котором блок лежит, путём от корня дерева. */\n readonly file: string;\n /** Строка заголовка в файле, считая с единицы. */\n readonly line: number;\n}\n\n/**\n * Блоки файла предложений.\n *\n * Незаполненный образец пропускается: в шаблоне ресурс стоит скобками — `rules/<правило>.md`, —\n * и отправить его значило бы завести запись о правке несуществующего файла.\n */\nexport function parseProposals(text: string, file: string): readonly IProposal[] {\n const lines: readonly string[] = text.split('\\n');\n const found: IProposal[] = [];\n let at: number = -1;\n\n const close: (end: number) => void = (end: number): void => {\n if (at < 0) {\n return;\n }\n const heading: IHeading = headingOf(lines[at]) as IHeading;\n const body: string = lines\n .slice(at + 1, end)\n .join('\\n')\n .trim();\n const { address, resource }: IHeading = heading;\n if (ADDRESSES.includes(address) && !resource.includes('<')) {\n found.push({ address, resource, body, file, sent: SENT.exec(body)?.[1] ?? '', line: at + 1 });\n }\n at = -1;\n };\n\n lines.forEach((line: string, index: number): void => {\n if (line.startsWith('## ')) {\n close(index);\n if (headingOf(line) !== null) {\n at = index;\n }\n }\n });\n close(lines.length);\n\n return found;\n}\n\n/** Все файлы предложений дерева, в порядке имён. */\nexport function readProposals(root: string): readonly IProposal[] {\n const dir: string = join(root, PROPOSALS_DIR);\n if (!existsSync(dir)) {\n return [];\n }\n\n return readdirSync(dir)\n .filter((name: string): boolean => name.endsWith('.md'))\n .sort(byText)\n .flatMap((name: string): readonly IProposal[] => parseProposals(readFileSync(join(dir, name), 'utf8'), join(PROPOSALS_DIR, name)));\n}\n\nexport interface ILeak {\n /** Строка блока, считая с единицы от заголовка. */\n readonly line: number;\n readonly text: string;\n readonly why: string;\n}\n\n/** Абсолютный путь машины: он не бывает общим ни для одного дерева, кроме этого. */\nconst ABSOLUTE: RegExp = /(^|[\\s(`'\"])(~\\/|\\/(Users|home|var|opt|srv|mnt)\\/)\\S+/;\n\n/**\n * Что в тексте называет дерево, из которого предложение уезжает.\n *\n * `marks` — имена, которые знает только это дерево: корень, его последнее звено, адрес его\n * удалённого репозитория. Пакет их не угадывает — они приходят доводом с края.\n */\nexport function leaksIn(text: string, marks: readonly string[]): readonly ILeak[] {\n const found: ILeak[] = [];\n\n text.split('\\n').forEach((line: string, index: number): void => {\n if (ABSOLUTE.test(line)) {\n found.push({ line: index + 1, text: line.trim(), why: 'абсолютный путь этой машины' });\n\n return;\n }\n const mark: string | undefined = marks.find((value: string): boolean => Boolean(value) && line.includes(value));\n if (mark) {\n found.push({ line: index + 1, text: line.trim(), why: `имя этого дерева: ${mark}` });\n }\n });\n\n return found;\n}\n\n/**\n * Чем дерево себя выдаёт. Последнее звено пути берётся отдельно от полного: в тексте оно\n * встречается чаще, чем путь целиком.\n */\nexport function marksOf(root: string, remote: string): readonly string[] {\n const name: string = basename(root);\n\n return [root, name, remote].filter(Boolean);\n}\n\n/**\n * Пометка об отправке дописывается в блок, а не в конец файла: блоков в файле несколько.\n *\n * Место считается от номера строки заголовка — но номера у всех блоков посчитаны при разборе, до\n * единой вставки, а каждая вставка сдвигает файл на строку вниз. Поэтому к номеру прибавляется\n * число пометок, уже стоящих выше: без этого вторая пометка встаёт на строку выше своего места,\n * пятая — на четыре, то есть посреди готового текста правки, а последнему блоку её не достаётся\n * вовсе, и при следующей отправке он уезжает вторым разом.\n */\nexport function markSent(text: string, proposal: IProposal, url: string): string {\n const lines: string[] = text.split('\\n');\n const shift: number = lines.slice(0, proposal.line).filter((line: string): boolean => SENT.test(line)).length;\n // Пометка встаёт сразу под заголовок: конец блока определяется следующим заголовком, а его\n // может и не быть — тогда «конец» пришлось бы искать по пустым строкам в хвосте файла.\n lines.splice(proposal.line + shift, 0, `- **отправлено:** ${url}`);\n\n return lines.join('\\n');\n}\n"]}
|
|
1
|
+
{"version":3,"file":"proposals.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/proposals.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Предложения по слою правил: разбор файла, отбор того, что уезжает наружу, и проверка на адрес\n * дерева.\n *\n * Файл пишет главный агент после разбора закрытой задачи — по блоку на предложение, с адресом в\n * заголовке. Адрес роль проставляет и так; здесь он впервые становится тем, что читает машина.\n *\n * Проверка на утечку живёт в этом же модуле не для удобства: предложение уезжает в чужой\n * репозиторий целиком, и запрет называть чужое дерево обязан держаться проверкой, а не памятью\n * того, кто пишет.\n */\nimport { existsSync, readdirSync, readFileSync } from 'node:fs';\nimport { basename, join } from 'node:path';\nimport { byText } from './order.js';\n\n/** Куда главный агент кладёт предложения. Путь от корня дерева. */\nexport const PROPOSALS_DIR: string = '.claude/rt-kit/proposals';\n\n/** Адрес предложения: куда правка идёт. Наружу уезжает только первый. */\nexport const TO_PACKAGE: string = 'пакет';\nexport const TO_COMPANION: string = 'компаньон';\nexport const TO_TREE: string = 'дерево';\n\nconst ADDRESSES: readonly string[] = [TO_PACKAGE, TO_COMPANION, TO_TREE];\n\n/** Разобранный заголовок блока. */\ninterface IHeading {\n readonly address: string;\n readonly resource: string;\n}\n\n/**\n * Заголовок блока: `## <адрес> · <ресурс>`. Разделитель — тот же, что в шаблоне.\n *\n * Разбирается строкой, а не образцом: образец на «слово, разделитель, остаток» перебирает\n * границу слова столько раз, сколько в строке знаков.\n */\nfunction headingOf(line: string): IHeading | null {\n if (!line.startsWith('## ')) {\n return null;\n }\n\n const rest: string = line.slice(3);\n const at: number = rest.indexOf('·');\n\n if (at < 0) {\n return null;\n }\n\n const address: string = rest.slice(0, at).trim();\n const resource: string = rest.slice(at + 1).trim();\n\n return address && !address.includes(' ') && resource ? { address, resource } : null;\n}\n\n/** Пометка об отправке. По ней же предложение узнаётся отправленным. */\nconst SENT: RegExp = /^- +\\*\\*отправлено:\\*\\* *(\\S+)/m;\n\n/**\n * Поле ближайшего утверждения: точная цитата строки ресурса, к которой блок относится, — либо\n * слово о том, что ближайшего нет вовсе.\n *\n * Поле обязательно, и обязательно оно затем, чтобы ресурс был прочитан. Мерить похожесть текстов\n * пробовали замером: законное соседство двух статей одного правила дало 0.345 общих значимых\n * слов, а законный перенос удачной статьи на соседнее место — 0.355. Порога между ними нет, и\n * назначенный наугад он отбивал бы правки вместо повторов. Названная цитата судится фактом: она\n * в ресурсе либо есть, либо её там нет.\n */\nconst NEAREST: RegExp = /^- +\\*\\*ближайшее:\\*\\* *([^\\n]+)/m;\n\n/**\n * Слово о том, что ближайшего утверждения в ресурсе нет: цитировать нечего.\n *\n * Конец слова здесь проверяется отрицательным просмотром, а не границей слова: границу движок\n * считает по латинице и цифрам, и между «т» и пробелом её нет вовсе — образец с `\\b` не совпал\n * бы ни с одним русским словом.\n */\nconst NOTHING_NEAR: RegExp = /^нет(?![а-яё])/i;\n\n/**\n * Цитата ближайшего утверждения: кавычки-ёлочки, как в остальных текстах дерева.\n *\n * Ищется позициями, а не образцом: образец на «открыть, набрать не-закрывающих, закрыть» линтер\n * отбивает как ветвящийся, и поводы у него есть — строка без закрывающей кавычки перебирается им\n * до конца.\n */\nfunction quotedIn(text: string): string {\n const from: number = text.indexOf('«');\n const to: number = from < 0 ? -1 : text.indexOf('»', from + 1);\n\n return from >= 0 && to > from ? text.slice(from + 1, to) : '';\n}\n\n/** Пометка любого рода: по ней считается сдвиг, когда в файл вставляют ещё одну. */\nconst MARKED: RegExp = /^- +\\*\\*(отправлено|отбито):\\*\\*/m;\n\nexport interface IProposal {\n readonly address: string;\n readonly resource: string;\n /** Тело блока без заголовка: место, повод и готовый текст. */\n readonly body: string;\n /** Ссылка на заведённую запись; пусто — не отправлялось. */\n readonly sent: string;\n /** Поле ближайшего утверждения, как его написали; пусто — поля нет вовсе. */\n readonly nearest: string;\n /** Файл, в котором блок лежит, путём от корня дерева. */\n readonly file: string;\n /** Строка заголовка в файле, считая с единицы. */\n readonly line: number;\n}\n\n/**\n * Блоки файла предложений.\n *\n * Незаполненный образец пропускается: в шаблоне ресурс стоит скобками — `rules/<правило>.md`, —\n * и отправить его значило бы завести запись о правке несуществующего файла.\n */\nexport function parseProposals(text: string, file: string): readonly IProposal[] {\n const lines: readonly string[] = text.split('\\n');\n const found: IProposal[] = [];\n let at: number = -1;\n\n const close: (end: number) => void = (end: number): void => {\n if (at < 0) {\n return;\n }\n const heading: IHeading = headingOf(lines[at]) as IHeading;\n const body: string = lines\n .slice(at + 1, end)\n .join('\\n')\n .trim();\n const { address, resource }: IHeading = heading;\n if (ADDRESSES.includes(address) && !resource.includes('<')) {\n found.push({\n address,\n resource,\n body,\n file,\n sent: SENT.exec(body)?.[1] ?? '',\n nearest: (NEAREST.exec(body)?.[1] ?? '').trim(),\n line: at + 1,\n });\n }\n at = -1;\n };\n\n lines.forEach((line: string, index: number): void => {\n if (line.startsWith('## ')) {\n close(index);\n if (headingOf(line) !== null) {\n at = index;\n }\n }\n });\n close(lines.length);\n\n return found;\n}\n\n/** Все файлы предложений дерева, в порядке имён. */\nexport function readProposals(root: string): readonly IProposal[] {\n const dir: string = join(root, PROPOSALS_DIR);\n if (!existsSync(dir)) {\n return [];\n }\n\n return readdirSync(dir)\n .filter((name: string): boolean => name.endsWith('.md'))\n .sort(byText)\n .flatMap((name: string): readonly IProposal[] => parseProposals(readFileSync(join(dir, name), 'utf8'), join(PROPOSALS_DIR, name)));\n}\n\nexport interface ILeak {\n /** Строка блока, считая с единицы от заголовка. */\n readonly line: number;\n readonly text: string;\n readonly why: string;\n}\n\n/** Абсолютный путь машины: он не бывает общим ни для одного дерева, кроме этого. */\nconst ABSOLUTE: RegExp = /(^|[\\s(`'\"])(~\\/|\\/(Users|home|var|opt|srv|mnt)\\/)\\S+/;\n\n/**\n * Что в тексте называет дерево, из которого предложение уезжает.\n *\n * `marks` — имена, которые знает только это дерево: корень, его последнее звено, адрес его\n * удалённого репозитория. Пакет их не угадывает — они приходят доводом с края.\n */\nexport function leaksIn(text: string, marks: readonly string[]): readonly ILeak[] {\n const found: ILeak[] = [];\n\n text.split('\\n').forEach((line: string, index: number): void => {\n if (ABSOLUTE.test(line)) {\n found.push({ line: index + 1, text: line.trim(), why: 'абсолютный путь этой машины' });\n\n return;\n }\n const mark: string | undefined = marks.find((value: string): boolean => Boolean(value) && line.includes(value));\n if (mark) {\n found.push({ line: index + 1, text: line.trim(), why: `имя этого дерева: ${mark}` });\n }\n });\n\n return found;\n}\n\n/**\n * Чем дерево себя выдаёт. Последнее звено пути берётся отдельно от полного: в тексте оно\n * встречается чаще, чем путь целиком.\n */\nexport function marksOf(root: string, remote: string): readonly string[] {\n const name: string = basename(root);\n\n return [root, name, remote].filter(Boolean);\n}\n\n/** Один пробел вместо любой пробельной вереницы: цитата в блоке перенесена по своей ширине. */\nfunction flat(text: string): string {\n return text.replace(/\\s+/g, ' ').trim();\n}\n\n/** Почему блок не уезжает; пустая строка — уезжает. */\nexport function nearestMissing(proposal: IProposal, assetsDir: string): string {\n if (!proposal.nearest) {\n return 'поля «ближайшее» нет: назови точную строку ресурса, к которой это относится, и то, чего она не покрывает, — либо напиши «нет»';\n }\n if (NOTHING_NEAR.test(proposal.nearest)) {\n return '';\n }\n\n const quoted: string = quotedIn(proposal.nearest);\n if (!quoted) {\n return 'ближайшее названо без цитаты: строка ресурса приводится в кавычках-ёлочках дословно — иначе проверить нечего';\n }\n\n const path: string = join(assetsDir, proposal.resource);\n if (!existsSync(path)) {\n // Ресурса у этого дерева нет — сверять не с чем, и отбивать нечего: блок про ресурс,\n // которого пакет здесь не держит, судится на приёмной стороне.\n return '';\n }\n\n return flat(readFileSync(path, 'utf8')).includes(flat(quoted))\n ? ''\n : `цитаты нет в «${proposal.resource}»: ${flat(quoted).slice(0, 60)}… — либо ресурс не читали, либо утверждение переписано с тех пор`;\n}\n\n/**\n * Пометка об отправке дописывается в блок, а не в конец файла: блоков в файле несколько.\n *\n * Место считается от номера строки заголовка — но номера у всех блоков посчитаны при разборе, до\n * единой вставки, а каждая вставка сдвигает файл на строку вниз. Поэтому к номеру прибавляется\n * число пометок, уже стоящих выше: без этого вторая пометка встаёт на строку выше своего места,\n * пятая — на четыре, то есть посреди готового текста правки, а последнему блоку её не достаётся\n * вовсе, и при следующей отправке он уезжает вторым разом.\n */\nexport function markSent(text: string, proposal: IProposal, url: string, field: string = 'отправлено'): string {\n const lines: string[] = text.split('\\n');\n // Сдвиг считает пометки обоих родов: отбитый блок получает свою, и следующая за ним встала бы\n // строкой выше своего места, если бы её не посчитали.\n const shift: number = lines.slice(0, proposal.line).filter((line: string): boolean => MARKED.test(line)).length;\n // Пометка встаёт сразу под заголовок: конец блока определяется следующим заголовком, а его\n // может и не быть — тогда «конец» пришлось бы искать по пустым строкам в хвосте файла.\n lines.splice(proposal.line + shift, 0, `- **${field}:** ${url}`);\n\n return lines.join('\\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":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC/E,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EACH,oBAAoB,GAQvB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,WAAW,EAAW,UAAU,EAAE,MAAM,aAAa,CAAC;AAC/D,OAAO,EAAyB,gBAAgB,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACvF,OAAO,EAAoB,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACzH,OAAO,EAAuB,SAAS,EAAS,MAAM,WAAW,CAAC;AAClE,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AACvF,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAEpC,8FAA8F;AAC9F,MAAM,WAAW,GAAW,EAAE,CAAC;AAE/B,iFAAiF;AACjF,MAAM,OAAO,GAAW,CAAC,CAAC;AAE1B,0FAA0F;AAC1F,MAAM,aAAa,GAAW,GAAG,CAAC;AAclC;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,MAAc;IACvC,MAAM,KAAK,GAAW,MAAM;SACvB,IAAI,EAAE;SACN,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC;SACrB,OAAO,CAAC,gBAAgB,EAAE,EAAE,CAAC;SAC7B,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IACxB,IAAI,GAAG,GAAW,KAAK,CAAC,MAAM,CAAC;IAE/B,OAAO,GAAG,GAAG,CAAC,IAAI,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;QACvC,GAAG,IAAI,CAAC,CAAC;IACb,CAAC;IAED,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;AAC7C,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CAAC,MAAc,EAAE,MAAc;IACrD,MAAM,IAAI,GAAW,YAAY,CAAC,MAAM,CAAC,CAAC;IAE1C,OAAO,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;AACzG,CAAC;AAED;;;;;;GAMG;AACH,SAAS,WAAW,CAAC,MAA8B,EAAE,KAA0B;IAC3E,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,KAAkB,EAAW,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;AACjF,CAAC;AAED,uFAAuF;AACvF,MAAM,UAAU,YAAY,CACxB,OAAiB,EACjB,IAAY,EACZ,SAAoC,EACpC,QAA2B,EAC3B,KAA0B;IAE1B,OAAO;QACH,IAAI;QACJ,QAAQ;QACR,SAAS;QACT,MAAM,EAAE,oBAAoB;QAC5B,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,KAAK,EAAE,WAAW,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC;QACxC,OAAO,EAAE,WAAW,CAAC,OAAO,CAAC,OAAO,EAAE,KAAK,CAAC;QAC5C,wFAAwF;QACxF,4DAA4D;QAC5D,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,MAAM,EAAE,KAAK,CAAC;QAC1C,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,KAAK,EAAE,OAAO,CAAC,KAAK;KACvB,CAAC;AACN,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,IAAY,EAAE,GAAW;IACrD,MAAM,IAAI,GAAW,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAErC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACpB,OAAO,EAAE,CAAC;IACd,CAAC;IAED,OAAO,CACH,WAAW,CAAC,IAAI,CAAC;QACb,wFAAwF;QACxF,mEAAmE;SAClE,MAAM,CAAC,CAAC,IAAY,EAAW,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,IAAI,KAAK,WAAW,CAAC;SAC/E,IAAI,CAAC,MAAM,CAAC;SACZ,GAAG,CAAC,CAAC,IAAY,EAAmB,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC,CACxG,CAAC;AACN,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,WAAW,CAAC,OAAsB,EAAE,SAA0B,EAAE,WAA8B;IAC1G,OAAO;QACH,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE;QACvD,GAAG,CAAC,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,SAAS,EAAE,WAAW,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACrG,GAAG,CAAC,WAAW,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,aAAa,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;KAC1G,CAAC;AACN,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,OAAsB,EAAE,SAA+B,EAAE,KAAwB;IAC1G,MAAM,SAAS,GAAa,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,GAAG,CAC5E,CAAC,IAAW,EAAU,EAAE,CAAC,aAAa,IAAI,CAAC,GAAG,WAAW,IAAI,CAAC,IAAI,EAAE,CACvE,CAAC;IACF,MAAM,WAAW,GAAa,SAAS,CAAC,OAAO,CAAC,CAAC,KAAgB,EAAY,EAAE,CAC3E,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,GAAG,CAC1B,CAAC,IAAW,EAAU,EAAE,CAAC,KAAK,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,MAAM,IAAI,CAAC,GAAG,WAAW,IAAI,CAAC,IAAI,EAAE,CACzG,CACJ,CAAC;IAEF,OAAO,CAAC,GAAG,SAAS,EAAE,GAAG,WAAW,CAAC,CAAC;AAC1C,CAAC;AAED,sBAAsB;AACtB,SAAS,OAAO,CAAC,GAAG,KAAwB;IACxC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,GAAG,KAAK,CAAC,EAAE,CAAC;AAChD,CAAC;AAED,6FAA6F;AAC7F,SAAS,QAAQ,CAAC,QAAmB,EAAE,OAAsB,EAAE,SAA0B,EAAE,WAA8B;IACrH,IAAI,QAAQ,CAAC,SAAS,KAAK,WAAW,EAAE,CAAC;QACrC,OAAO,eAAe,SAAS,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;IACnD,CAAC;IACD,IAAI,QAAQ,CAAC,SAAS,KAAK,aAAa,EAAE,CAAC;QACvC,OAAO,YAAY,WAAW,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;IAClD,CAAC;IAED,OAAO,cAAc,OAAO,CAAC,KAAK,OAAO,OAAO,CAAC,IAAI,mBAAmB,OAAO,CAAC,SAAS,CAAC,MAAM,kBAAkB,OAAO,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;AAChJ,CAAC;AAQD;;;;;GAKG;AACH,SAAS,WAAW,CAAC,IAAY,EAAE,OAAoC;IACnE,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;QACxB,MAAM,IAAI,GAAW,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QACnD,aAAa,CAAC,IAAI,EAAE,QAAQ,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC,GAAG,EAAE,QAAQ,CAAC,EAAE,MAAM,CAAC,CAAC;IACvG,CAAC;AACL,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,QAAQ,CACb,KAA2B,EAC3B,OAAsB,EACtB,SAA0B,EAC1B,WAA8B;IAE9B,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,QAAmB,EAAU,EAAE,CAAC,KAAK,QAAQ,CAAC,SAAS,MAAM,QAAQ,CAAC,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,WAAW,CAAC,EAAE,CAAC,CAAC;AAC1I,CAAC;AAED,qEAAqE;AACrE,SAAS,aAAa,CAAC,IAAY,EAAE,SAA+B,EAAE,OAAiB;IACnF,MAAM,IAAI,GAAW,SAAS,OAAO,CAAC,QAAQ,EAAE,KAAK,IAAI,SAAS,EAAE,CAAC;IAErE,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;QAC/B,MAAM,IAAI,GAAW,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC;QAC/C,aAAa,CAAC,IAAI,EAAE,QAAQ,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC;IACtF,CAAC;AACL,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,QAAQ,CAAC,OAAiB;IAC/B,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;QACpB,OAAO,SAAS,CAAC;IACrB,CAAC;IAED,MAAM,MAAM,GAAW,GAAG,OAAO,CAAC,QAAQ,CAAC,KAAK,GAAG,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,mBAAmB,EAAE,CAAC;IAC1H,MAAM,KAAK,GAAuB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC;IAEzD,OAAO,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,aAAa,KAAK,gBAAgB,OAAO,CAAC,QAAQ,CAAC,KAAK,IAAI,CAAC,EAAE,CAAC;AACnH,CAAC;AAED;;;GAGG;AACH,KAAK,UAAU,IAAI,CACf,IAAY,EACZ,MAAc,EACd,IAAY,EACZ,KAAa,EACb,IAAW,EACX,KAA2B,EAC3B,SAA+B,EAC/B,MAAyB;IAEzB,6FAA6F;IAC7F,+EAA+E;IAC/E,MAAM,IAAI,GAAsB,CAAC,4BAA4B,MAAM,YAAY,IAAI,GAAG,EAAE,GAAG,MAAM,CAAC,CAAC;IACnG,MAAM,IAAI,GAAa,EAAE,CAAC;IAE1B,KAAK,MAAM,QAAQ,IAAI,KAAK,EAAE,CAAC;QAC3B,MAAM,OAAO,GAAa,MAAM,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC;QAE9D,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC;YACd,OAAO;gBACH,IAAI,EAAE,OAAO;gBACb,KAAK,EAAE;oBACH,GAAG,IAAI;oBACP,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,qBAAqB,IAAI,CAAC,MAAM,EAAE,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC;oBACvF,GAAG,QAAQ,CAAC,IAAI,KAAK,MAAM,YAAY,OAAO,CAAC,MAAM,IAAI,WAAW,MAAM,OAAO,CAAC,IAAI,EAAE;oBACxF,GAAG,CAAC,OAAO,CAAC,MAAM,KAAK,aAAa;wBAChC,CAAC,CAAC,CAAC,8EAA8E,CAAC;wBAClF,CAAC,CAAC,EAAE,CAAC;iBACZ;aACJ,CAAC;QACN,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,KAAK,QAAQ,CAAC,IAAI,MAAM,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAEvD,IAAI,QAAQ,CAAC,SAAS,KAAK,WAAW,EAAE,CAAC;YACrC,aAAa,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;QAC5C,CAAC;IACL,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,SAAS,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC;AAC7D,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,GAAiB,EAAE,OAAqB;IAClE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,GAAG,CAAC;IAChC,MAAM,MAAM,GAAmB,UAAU,CAAC,IAAI,CAAC,CAAC;IAEhD,IAAI,CAAC,MAAM,EAAE,CAAC;QACV,OAAO,OAAO,CAAC,gBAAgB,WAAW,iCAAiC,CAAC,CAAC;IACjF,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QAClB,OAAO;YACH,IAAI,EAAE,CAAC;YACP,KAAK,EAAE;gBACH,gFAAgF;gBAChF,mEAAmE;aACtE;SACJ,CAAC;IACN,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;QACjB,OAAO,OAAO,CAAC,sCAAsC,EAAE,sCAAsC,WAAW,EAAE,CAAC,CAAC;IAChH,CAAC;IAED,MAAM,IAAI,GAAW,UAAU,CAAC,OAAO,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;IAC7D,IAAI,CAAC,IAAI,EAAE,CAAC;QACR,OAAO,OAAO,CACV,yDAAyD,EACzD,gCAAgC,WAAW,kDAAkD,CAChG,CAAC;IACN,CAAC;IAED,MAAM,KAAK,GAAW,SAAS,CAAC,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;IACpD,IAAI,CAAC,KAAK,EAAE,CAAC;QACT,OAAO,OAAO,CACV,gDAAgD,EAChD,4CAA4C,WAAW,qBAAqB,EAC5E,8DAA8D,CACjE,CAAC;IACN,CAAC;IAED,MAAM,IAAI,GAAgB,gBAAgB,CAAC,IAAI,EAAE,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9E,MAAM,OAAO,GAAa,SAAS,CAAC,IAAI,CAAC,YAAY,EAAE,aAAa,CAAC,MAAM,EAAE,SAAS,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IACvG,MAAM,KAAK,GAAkB,YAAY,CACrC,OAAO,EACP,IAAI,EACJ,YAAY,CAAC,MAAM,EAAE,SAAS,EAAE,IAAI,CAAC,EACrC,UAAU,CAAC,MAAM,EAAE,SAAS,CAAC,EAC7B,aAAa,CAAC,MAAM,EAAE,SAAS,CAAC,CACnC,CAAC;IAEF,MAAM,KAAK,GAAyB,aAAa,CAAC,IAAI,CAAC,CAAC,MAAM,CAC1D,CAAC,KAAgB,EAAW,EAAE,CAAC,KAAK,CAAC,OAAO,KAAK,UAAU,IAAI,CAAC,KAAK,CAAC,IAAI,CAC7E,CAAC;IACF,yFAAyF;IACzF,0FAA0F;IAC1F,uFAAuF;IACvF,gCAAgC;IAChC,MAAM,OAAO,GAAgC,KAAK;SAC7C,GAAG,CAAC,CAAC,KAAgB,EAAoB,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,EAAE,cAAc,CAAC,KAAK,EAAE,SAAS,CAAC,EAAE,CAAC,CAAC;SACzG,MAAM,CAAC,CAAC,KAAuB,EAAW,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;IACtE,MAAM,IAAI,GAAyB,KAAK,CAAC,MAAM,CAC3C,CAAC,KAAgB,EAAW,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,GAAqB,EAAW,EAAE,CAAC,GAAG,CAAC,QAAQ,KAAK,KAAK,CAAC,CAC3G,CAAC;IACF,MAAM,SAAS,GAAoB;QAC/B,IAAI;QACJ,MAAM,EAAE,oBAAoB;QAC5B,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,KAAgB,EAAiB,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC;KACjI,CAAC;IACF,MAAM,WAAW,GAAsB,EAAE,IAAI,EAAE,MAAM,EAAE,oBAAoB,EAAE,KAAK,EAAE,eAAe,CAAC,IAAI,EAAE,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC;IAEhI,2FAA2F;IAC3F,sFAAsF;IACtF,8CAA8C;IAC9C,MAAM,MAAM,GAAsB,YAAY,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;IAC5F,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;QAChB,OAAO,OAAO,CACV,2DAA2D,MAAM,CAAC,MAAM,EAAE,EAC1E,GAAG,MAAM,EACT,wFAAwF,CAC3F,CAAC;IACN,CAAC;IAED,MAAM,KAAK,GAAyB,WAAW,CAAC,KAAK,EAAE,SAAS,EAAE,WAAW,CAAC,CAAC;IAE/E,MAAM,MAAM,GAAsB,QAAQ,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,WAAW,CAAC,CAAC;IACjF,4FAA4F;IAC5F,iCAAiC;IACjC,MAAM,YAAY,GAAsB,OAAO,CAAC,GAAG,CAC/C,CAAC,GAAqB,EAAU,EAAE,CAAC,YAAY,GAAG,CAAC,QAAQ,CAAC,IAAI,IAAI,GAAG,CAAC,QAAQ,CAAC,IAAI,MAAM,GAAG,CAAC,GAAG,EAAE,CACvG,CAAC;IAEF,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;QACjB,OAAO;YACH,IAAI,EAAE,CAAC;YACP,KAAK,EAAE;gBACH,yEAAyE;gBACzE,eAAe,MAAM,CAAC,MAAM,YAAY,IAAI,GAAG;gBAC/C,GAAG,MAAM;gBACT,GAAG,YAAY;gBACf,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,iEAAiE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC3F,6CAA6C;aAChD;SACJ,CAAC;IACN,CAAC;IAED,uFAAuF;IACvF,WAAW;IACX,WAAW,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAE3B,MAAM,OAAO,GAAsB,MAAM,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE;QACvG,GAAG,MAAM;QACT,GAAG,YAAY;KAClB,CAAC,CAAC;IAEH,OAAO,OAAO,CAAC;AACnB,CAAC","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
|
|
Binary file
|