@rt-tools/agent-kit 0.9.0 → 0.10.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.
Files changed (74) hide show
  1. package/README.md +21 -7
  2. package/assets/agents/conscience.md +58 -0
  3. package/assets/agents/prose-editor.md +44 -0
  4. package/assets/agents/strict-teacher.md +59 -0
  5. package/assets/checks/board.github.mjs +56 -1
  6. package/assets/checks/check-board.github.mjs +24 -0
  7. package/assets/checks/check-doc-paths.mjs +4 -15
  8. package/assets/checks/check-dupes.mjs +5 -6
  9. package/assets/checks/check-file-size.mjs +6 -20
  10. package/assets/checks/check-prose-style.mjs +137 -0
  11. package/assets/checks/check-reuse.mjs +5 -5
  12. package/assets/checks/check-styles.mjs +5 -5
  13. package/assets/checks/lib-common.mjs +3 -3
  14. package/assets/checks/rt-kit-checks.config.mjs +85 -1
  15. package/assets/defaults/project.sh +50 -0
  16. package/assets/docs/GLOSSARY.md +17 -15
  17. package/assets/hooks/browser-guard-device-id.sh +9 -1
  18. package/assets/hooks/browser-guard-no-asking.sh +9 -1
  19. package/assets/hooks/browser-guard-no-listing.sh +9 -1
  20. package/assets/hooks/browser-guard-no-other-drivers.sh +7 -1
  21. package/assets/hooks/browser-guard-require-select.sh +11 -2
  22. package/assets/hooks/claim-guard.sh +113 -0
  23. package/assets/hooks/conscience-guard.sh +98 -0
  24. package/assets/hooks/deny-tail.sh +32 -0
  25. package/assets/hooks/dev-server-guard.sh +8 -2
  26. package/assets/hooks/docs-guard.sh +14 -2
  27. package/assets/hooks/exam-guard.sh +121 -0
  28. package/assets/hooks/git-guard-delivery-signature.sh +75 -0
  29. package/assets/hooks/git-guard-delivery.sh +154 -68
  30. package/assets/hooks/git-guard-main.sh +12 -0
  31. package/assets/hooks/git-guard-push-tests.sh +49 -2
  32. package/assets/hooks/grill-gate.sh +12 -0
  33. package/assets/hooks/handoff-entry-guard.sh +71 -0
  34. package/assets/hooks/postmortem-guard.sh +12 -0
  35. package/assets/hooks/proposal-guard.sh +12 -0
  36. package/assets/hooks/prose-style-guard.sh +73 -0
  37. package/assets/hooks/qa-dataid-guard.sh +12 -1
  38. package/assets/hooks/rerun-guard.sh +86 -0
  39. package/assets/hooks/reuse-first-guard.sh +12 -1
  40. package/assets/hooks/roles.sh +32 -0
  41. package/assets/hooks/skill-gate.sh +12 -0
  42. package/assets/hooks/sql-guard-write.sh +2 -1
  43. package/assets/hooks/sql-guard.sh +12 -1
  44. package/assets/hooks/task-flow-guard.sh +71 -7
  45. package/assets/hooks/turn-exit-guard.sh +189 -0
  46. package/assets/hooks/waiting-turn-guard.sh +12 -0
  47. package/assets/hooks/window-fill-guard.sh +12 -1
  48. package/assets/patterns/task-flow-close.md +8 -8
  49. package/assets/patterns/task-flow-handoff.md +1 -1
  50. package/assets/patterns/task-flow-resume.md +30 -4
  51. package/assets/patterns/task-flow-start.md +15 -6
  52. package/assets/rules/git-workflow.azure.md +36 -9
  53. package/assets/rules/git-workflow.github.md +74 -9
  54. package/assets/rules/git-workflow.gitlab.md +40 -12
  55. package/assets/rules/task-flow.md +123 -71
  56. package/assets/rules/testing.md +15 -1
  57. package/assets/samples/tasks/_template/plan.md +4 -1
  58. package/assets/samples/tasks/_template/progress.md +1 -0
  59. package/assets/skills/agent-kit.md +33 -0
  60. package/assets/templates/project.sh +16 -0
  61. package/bin/agent-kit.d.ts.map +1 -1
  62. package/bin/agent-kit.js +4 -2
  63. package/bin/agent-kit.js.map +1 -1
  64. package/lib/config.d.ts +8 -0
  65. package/lib/config.d.ts.map +1 -1
  66. package/lib/config.js +1 -0
  67. package/lib/config.js.map +1 -1
  68. package/lib/enroll.d.ts +9 -1
  69. package/lib/enroll.d.ts.map +1 -1
  70. package/lib/enroll.js +65 -18
  71. package/lib/enroll.js.map +1 -1
  72. package/package.json +1 -1
  73. package/rt-tools-agent-kit-0.10.0.tgz +0 -0
  74. package/rt-tools-agent-kit-0.9.0.tgz +0 -0
package/lib/enroll.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"enroll.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/enroll.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Заведение дерева по приглашению: дерево просит у приёма свой токен и кладёт его на диск.\n *\n * Вторая и последняя команда пакета, которая ходит в сеть, — и единственная, которая ходит туда\n * без токена: токена у дерева ещё нет, за ним и обращаются. Взамен обращение несёт одноразовый\n * код, выданный владельцем.\n *\n * Ходит она только по слову человека. Гарды в сеть не ходят вовсе, и заведение — не исключение:\n * дерево заводят один раз, руками.\n *\n * Сеть здесь отделена от решения так же, как у отправки груза: чем сходить, приходит доводом, и\n * спека подставляет двойник, никуда не стучась.\n */\nimport { existsSync, mkdirSync, writeFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { dirname, join } from 'node:path';\n\nimport { IEnrollGranted } from './cargo.js';\nimport { intakeUrl, SHIP_TIMEOUT_MS } from './ship.js';\n\n/** Права файла с токеном: читает и пишет только владелец файла. */\nconst TOKEN_MODE: number = 0o600;\n\n/** Тело обращения: признак дерева и код приглашения. Имени в нём нет — его знает приглашение. */\nexport interface IEnrollBody {\n readonly schema: string;\n readonly tree: string;\n readonly code: string;\n}\n\n/** Чем кончилось обращение. Отказ — такой же ответ, как выданный токен: его печатает вызывающий. */\nexport interface IEnrollAnswer {\n readonly ok: boolean;\n /** Код ответа. Ноль — приём не ответил вовсе: не дозвонились, оборвалось, вышло время. */\n readonly status: number;\n readonly said: string;\n /** Токен и имя дерева. Пусто у отказа: приём называет их только у годного обращения. */\n readonly granted: IEnrollGranted | null;\n}\n\n/** Чем обращение уходит в приём. Двойник в спеке — того же вида. */\nexport type TEnrollCall = (intake: string, body: IEnrollBody) => Promise<IEnrollAnswer>;\n\n/** Что нужно заведению: где дерево, куда идти, чем платить и куда класть токен. */\nexport interface IEnrollOptions {\n readonly root: string;\n readonly intake: string;\n readonly code: string;\n /** Признак дерева, посчитанный от адреса репозитория. */\n readonly tree: string;\n /** Файл токена — путь от корня дерева или от домашнего каталога. */\n readonly token: string;\n /** Перезаписать лежащий токен намеренно. Без него дерево с токеном обращения не шлёт. */\n readonly force: boolean;\n readonly call: TEnrollCall;\n}\n\n/** Что команда напечатала и чем кончилась. Строки печатает вызывающий. */\nexport interface IEnrollOutcome {\n readonly code: number;\n readonly lines: readonly string[];\n}\n\n/** Отказ команды: причина строками и ненулевой код выхода. */\nfunction refusal(...lines: readonly string[]): IEnrollOutcome {\n return { code: 1, lines };\n}\n\n/** Что сказал приём: его собственное сообщение, а если тело не разбирается — тело как приехало. */\nfunction saidOf(text: string): string {\n try {\n const said: Record<string, unknown> = JSON.parse(text) as Record<string, unknown>;\n\n return typeof said['message'] === 'string' ? said['message'] : text.trim();\n } catch {\n return text.trim();\n }\n}\n\n/** Токен и имя из ответа приёма. Ответ не тем — пусто: своё устройство приём не пересказывает. */\nfunction grantedOf(text: string): IEnrollGranted | null {\n try {\n const said: Record<string, unknown> = JSON.parse(text) as Record<string, unknown>;\n\n return typeof said['token'] === 'string' && typeof said['tree'] === 'string' && typeof said['name'] === 'string'\n ? { tree: said['tree'], name: said['name'], token: said['token'] }\n : null;\n } catch {\n return null;\n }\n}\n\n/**\n * Годен ли адрес приёма для обращения без TLS.\n *\n * Токен уходит единственным ответом на это обращение, и по открытому пути его читает всякий, кто\n * стоит между сторонами. Исключение одно — локальная машина: там приём поднимают для проверки, и\n * сети между сторонами нет вовсе.\n */\nexport function intakeAllowed(intake: string): boolean {\n const address: string = intake.trim().toLowerCase();\n\n if (address.startsWith('https://')) {\n return true;\n }\n\n return /^http:\\/\\/(localhost|127\\.0\\.0\\.1|\\[::1\\])([:/]|$)/.test(address);\n}\n\n/** Куда ложится токен: путь от домашнего каталога или от корня дерева. */\nexport function tokenPath(root: string, spoken: string): string {\n return spoken.startsWith('~/') ? join(homedir(), spoken.slice(2)) : join(root, spoken);\n}\n\n/** Обращение и запись токена: сюда доходит только то, что прошло проверки у себя. */\nasync function grant(options: IEnrollOptions, path: string): Promise<IEnrollOutcome> {\n const answer: IEnrollAnswer = await options.call(options.intake, { schema: '1', tree: options.tree, code: options.code });\n\n if (!answer.ok || !answer.granted) {\n return refusal(answer.status === 0 ? `приём не ответил: ${answer.said}` : `приём отказал (${answer.status}): ${answer.said}`);\n }\n\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, `${answer.granted.token}\\n`, { encoding: 'utf8', mode: TOKEN_MODE });\n\n return {\n code: 0,\n lines: [\n `дерево заведено: «${answer.granted.name}» (${answer.granted.tree})`,\n `токен лежит в ${path}, читать и писать его может только владелец файла`,\n 'приглашение погашено: второй раз им не воспользоваться',\n ],\n };\n}\n\n/**\n * Заведение дерева.\n *\n * Порядок проверок такой, что до сети доходит только то, что имеет шанс: незаполненная настройка,\n * открытый адрес и уже лежащий токен отбиваются здесь, у себя.\n */\nexport async function enroll(options: IEnrollOptions): Promise<IEnrollOutcome> {\n if (!options.code) {\n return refusal('заведение ждёт код приглашения: `agent-kit enroll --code <код>`');\n }\n\n if (!options.intake) {\n return refusal('в настройке дерева не назван адрес приёма: заполните ключ `intake`');\n }\n\n if (!options.token) {\n return refusal('в настройке дерева не назван файл токена: заполните ключ `token`');\n }\n\n if (!intakeAllowed(options.intake)) {\n return refusal(\n `адрес приёма ${options.intake} без TLS: токен уходит единственным ответом, и по открытому пути его прочитает всякий`,\n 'поправьте ключ `intake` на https://… — исключение сделано только для локальной машины'\n );\n }\n\n const path: string = tokenPath(options.root, options.token);\n\n if (existsSync(path) && !options.force) {\n return refusal(\n `токен уже лежит в ${path}: заведённое дерево потеряло бы связь со своим прежним грузом`,\n 'если это намеренно — позовите ту же команду с доводом --force'\n );\n }\n\n return grant(options, path);\n}\n\n/** Обращение в приём по-настоящему. Код уезжает телом и в заголовки не попадает. */\nexport const httpEnroll: TEnrollCall = async (intake: string, body: IEnrollBody): Promise<IEnrollAnswer> => {\n let answer: Response;\n\n try {\n answer = await fetch(intakeUrl(intake, 'enroll'), {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body),\n signal: AbortSignal.timeout(SHIP_TIMEOUT_MS),\n });\n } catch (error: unknown) {\n return { ok: false, status: 0, said: (error as Error).message, granted: null };\n }\n\n const text: string = await answer.text();\n\n return { ok: answer.ok, status: answer.status, said: saidOf(text), granted: answer.ok ? grantedOf(text) : null };\n};\n"]}
1
+ {"version":3,"file":"enroll.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/enroll.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Заведение дерева: токен ложится на диск правами «читает и пишет только владелец файла».\n *\n * Путей два, и человек выбирает доводом.\n *\n * Обмен по коду — дерево просит токен у приёма, неся одноразовый код. Это вторая и последняя\n * команда пакета, которая ходит в сеть, и единственная, которая ходит туда без токена: токена у\n * дерева ещё нет, за ним и обращаются.\n *\n * Выданный токен — тот, что человек получил прямо в админке приёма. Обменивать не на что, и в\n * сеть команда не идёт вовсе: она кладёт полученное на диск теми же правами. Отсюда и разный\n * набор проверок — адрес приёма и запрет открытого пути этому пути не нужны.\n *\n * Ходит она только по слову человека. Гарды в сеть не ходят вовсе, и заведение — не исключение:\n * дерево заводят один раз, руками.\n *\n * Сеть здесь отделена от решения так же, как у отправки груза: чем сходить, приходит доводом, и\n * спека подставляет двойник, никуда не стучась.\n */\nimport { existsSync, mkdirSync, writeFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { dirname, join } from 'node:path';\n\nimport { IEnrollGranted } from './cargo.js';\nimport { intakeUrl, SHIP_TIMEOUT_MS } from './ship.js';\n\n/** Права файла с токеном: читает и пишет только владелец файла. */\nconst TOKEN_MODE: number = 0o600;\n\n/** Тело обращения: признак дерева и код приглашения. Имени в нём нет — его знает приглашение. */\nexport interface IEnrollBody {\n readonly schema: string;\n readonly tree: string;\n readonly code: string;\n}\n\n/** Чем кончилось обращение. Отказ — такой же ответ, как выданный токен: его печатает вызывающий. */\nexport interface IEnrollAnswer {\n readonly ok: boolean;\n /** Код ответа. Ноль — приём не ответил вовсе: не дозвонились, оборвалось, вышло время. */\n readonly status: number;\n readonly said: string;\n /** Токен и имя дерева. Пусто у отказа: приём называет их только у годного обращения. */\n readonly granted: IEnrollGranted | null;\n}\n\n/** Чем обращение уходит в приём. Двойник в спеке — того же вида. */\nexport type TEnrollCall = (intake: string, body: IEnrollBody) => Promise<IEnrollAnswer>;\n\n/** Что нужно заведению: где дерево, куда идти, чем платить и куда класть токен. */\nexport interface IEnrollOptions {\n readonly root: string;\n readonly intake: string;\n readonly code: string;\n /**\n * Токен, выданный человеку прямо в админке приёма.\n *\n * Второй путь завести дерево, и сети он не требует вовсе: токен уже на руках, обменивать не\n * на что. С кодом приглашения не сочетается — это два пути к одному и тому же.\n */\n readonly issued: string;\n /** Признак дерева, посчитанный от адреса репозитория. */\n readonly tree: string;\n /** Файл токена — путь от корня дерева или от домашнего каталога. */\n readonly token: string;\n /** Перезаписать лежащий токен намеренно. Без него дерево с токеном обращения не шлёт. */\n readonly force: boolean;\n readonly call: TEnrollCall;\n}\n\n/** Что команда напечатала и чем кончилась. Строки печатает вызывающий. */\nexport interface IEnrollOutcome {\n readonly code: number;\n readonly lines: readonly string[];\n}\n\n/** Отказ команды: причина строками и ненулевой код выхода. */\nfunction refusal(...lines: readonly string[]): IEnrollOutcome {\n return { code: 1, lines };\n}\n\n/** Что сказал приём: его собственное сообщение, а если тело не разбирается — тело как приехало. */\nfunction saidOf(text: string): string {\n try {\n const said: Record<string, unknown> = JSON.parse(text) as Record<string, unknown>;\n\n return typeof said['message'] === 'string' ? said['message'] : text.trim();\n } catch {\n return text.trim();\n }\n}\n\n/** Токен и имя из ответа приёма. Ответ не тем — пусто: своё устройство приём не пересказывает. */\nfunction grantedOf(text: string): IEnrollGranted | null {\n try {\n const said: Record<string, unknown> = JSON.parse(text) as Record<string, unknown>;\n\n return typeof said['token'] === 'string' && typeof said['tree'] === 'string' && typeof said['name'] === 'string'\n ? { tree: said['tree'], name: said['name'], token: said['token'] }\n : null;\n } catch {\n return null;\n }\n}\n\n/**\n * Годен ли адрес приёма для обращения без TLS.\n *\n * Токен уходит единственным ответом на это обращение, и по открытому пути его читает всякий, кто\n * стоит между сторонами. Исключение одно — локальная машина: там приём поднимают для проверки, и\n * сети между сторонами нет вовсе.\n */\nexport function intakeAllowed(intake: string): boolean {\n const address: string = intake.trim().toLowerCase();\n\n if (address.startsWith('https://')) {\n return true;\n }\n\n return /^http:\\/\\/(localhost|127\\.0\\.0\\.1|\\[::1\\])([:/]|$)/.test(address);\n}\n\n/** Куда ложится токен: путь от домашнего каталога или от корня дерева. */\nexport function tokenPath(root: string, spoken: string): string {\n return spoken.startsWith('~/') ? join(homedir(), spoken.slice(2)) : join(root, spoken);\n}\n\n/** Запись токена на диск: у обоих путей она одна, и права файла у неё одни. */\nfunction lay(path: string, token: string): void {\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, `${token}\\n`, { encoding: 'utf8', mode: TOKEN_MODE });\n}\n\n/** Токен с рук: приём о нём не спрашивается — он же его и выдал, и обменивать не на что. */\nfunction accept(options: IEnrollOptions, path: string): IEnrollOutcome {\n lay(path, options.issued);\n\n return {\n code: 0,\n lines: [\n 'дерево заведено токеном, выданным в админке приёма',\n `токен лежит в ${path}, читать и писать его может только владелец файла`,\n 'проверить связь с приёмом: `agent-kit propose --dry-run`',\n ],\n };\n}\n\n/** Обращение и запись токена: сюда доходит только то, что прошло проверки у себя. */\nasync function grant(options: IEnrollOptions, path: string): Promise<IEnrollOutcome> {\n const answer: IEnrollAnswer = await options.call(options.intake, { schema: '1', tree: options.tree, code: options.code });\n\n if (!answer.ok || !answer.granted) {\n return refusal(answer.status === 0 ? `приём не ответил: ${answer.said}` : `приём отказал (${answer.status}): ${answer.said}`);\n }\n\n lay(path, answer.granted.token);\n\n return {\n code: 0,\n lines: [\n `дерево заведено: «${answer.granted.name}» (${answer.granted.tree})`,\n `токен лежит в ${path}, читать и писать его может только владелец файла`,\n 'приглашение погашено: второй раз им не воспользоваться',\n ],\n };\n}\n\n/**\n * Выбран ли путь: отказ, когда доводов два или ни одного, и пусто, когда выбран ровно один.\n *\n * Стоит отдельно от самого заведения: разбор довода и порядок проверок — два разных решения, и\n * вместе они не читаются.\n */\nfunction pathChosen(options: IEnrollOptions): IEnrollOutcome | null {\n if (options.code && options.issued) {\n return refusal(\n 'код приглашения и выданный токен — два разных пути завести дерево, и вместе они не идут',\n 'оставьте один довод: --code <код> либо --token <токен>'\n );\n }\n\n if (!options.code && !options.issued) {\n return refusal(\n 'заведение ждёт одно из двух:',\n ' --code <код> код приглашения, выданный владельцем приёма',\n ' --token <токен> токен, выданный человеку прямо в админке приёма'\n );\n }\n\n return null;\n}\n\n/** Годен ли приём для обмена по коду. Пути с рук эти проверки не касаются: он в сеть не идёт. */\nfunction intakeReady(options: IEnrollOptions): IEnrollOutcome | null {\n if (!options.intake) {\n return refusal('в настройке дерева не назван адрес приёма: заполните ключ `intake`');\n }\n\n if (!intakeAllowed(options.intake)) {\n return refusal(\n `адрес приёма ${options.intake} без TLS: токен уходит единственным ответом, и по открытому пути его прочитает всякий`,\n 'поправьте ключ `intake` на https://… — исключение сделано только для локальной машины'\n );\n }\n\n return null;\n}\n\n/**\n * Заведение дерева.\n *\n * Порядок проверок такой, что до сети доходит только то, что имеет шанс: незаполненная настройка,\n * открытый адрес и уже лежащий токен отбиваются здесь, у себя. Общие обоим путям проверки стоят\n * до развилки: поставленные после, они защищали бы только один из них.\n */\nexport async function enroll(options: IEnrollOptions): Promise<IEnrollOutcome> {\n const chosen: IEnrollOutcome | null = pathChosen(options);\n\n if (chosen) {\n return chosen;\n }\n\n if (!options.token) {\n return refusal('в настройке дерева не назван файл токена: заполните ключ `token`');\n }\n\n const laid: string = tokenPath(options.root, options.token);\n\n if (existsSync(laid) && !options.force) {\n return refusal(\n `токен уже лежит в ${laid}: заведённое дерево потеряло бы связь со своим прежним грузом`,\n 'если это намеренно — позовите ту же команду с доводом --force'\n );\n }\n\n if (options.issued) {\n return accept(options, laid);\n }\n\n const reachable: IEnrollOutcome | null = intakeReady(options);\n\n return reachable ?? grant(options, laid);\n}\n\n/** Обращение в приём по-настоящему. Код уезжает телом и в заголовки не попадает. */\nexport const httpEnroll: TEnrollCall = async (intake: string, body: IEnrollBody): Promise<IEnrollAnswer> => {\n let answer: Response;\n\n try {\n answer = await fetch(intakeUrl(intake, 'enroll'), {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body),\n signal: AbortSignal.timeout(SHIP_TIMEOUT_MS),\n });\n } catch (error: unknown) {\n return { ok: false, status: 0, said: (error as Error).message, granted: null };\n }\n\n const text: string = await answer.text();\n\n return { ok: answer.ok, status: answer.status, said: saidOf(text), granted: answer.ok ? grantedOf(text) : null };\n};\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rt-tools/agent-kit",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "Переносимый слой правил для агента: законы, хуки, проверки и агенты, раскладываемые в репозиторий одной командой",
5
5
  "author": "RT Team",
6
6
  "license": "Apache-2.0",
Binary file
Binary file