From 43178e621808318f42fe576b7c41bed93dcbf2a1 Mon Sep 17 00:00:00 2001 From: ClawWorld <4101513+KeepSilenceQP@users.noreply.github.com> Date: Thu, 23 Jul 2026 00:36:00 +0800 Subject: [PATCH 1/5] docs: make harness orchestration event driven --- CHANGELOG.md | 8 + HARNESS.md | 28 +-- HARNESS_DESIGN_PRINCIPLES.md | 37 ++-- README.md | 3 + docs/2026-07-20-harness-core-workflow-spec.md | 26 ++- ...event-driven-orchestration-mindset-plan.md | 208 ++++++++++++++++++ ...event-driven-orchestration-mindset-spec.md | 180 +++++++++++++++ roles/coordinator.md | 10 +- roles/plan-writer.md | 2 +- 9 files changed, 462 insertions(+), 40 deletions(-) create mode 100644 docs/2026-07-23-event-driven-orchestration-mindset-plan.md create mode 100644 docs/2026-07-23-event-driven-orchestration-mindset-spec.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 18bc2a0..a782dab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## Event-driven orchestration mindset - 2026-07-23 + +- 在设计北极星中正向定义秦鹏、小P、云上C总、小C或云上小C的稳定身份:小P是中心编排者和状态所有者,云上C总是承担 Plan Writer 与 Code Reviewer 的架构师,实现 Bot 负责编码与自检。 +- 将 Harness 编排明确为真实 `at-bot` 派发和正式回传驱动的跨 Agent turn 接力;小P派发成功后结束当前 turn,不持续监控流式卡片、过程消息或群内活动。 +- 保留小P依据正式结果维护 Plan Checkbox、判断 gate 和路由下一步的责任;删除“持续回写”“派工、跟踪”“连续执行”等容易暗示常驻监控的表达。 +- 明确 `at-bot sent` 只表示发送成功,不保证目标执行、完成、回传或超时恢复;没有正式结果时不迁移完成状态、不轮询、不自动重派。 +- 本次只调整 Harness 文档和角色心智,不引入 waiting 状态、轮询器、后台调度器或自动恢复机制。 + ## Plan progress writeback - 2026-07-22 - 明确 Plan 进度回写责任:Plan Writer 定义可追踪的 Execution Unit 和完成条件,小P在消费角色结果后、派发下一阶段前同步 Plan Checkbox 与状态;运行进度更新不得借机改写 Plan 内容。 diff --git a/HARNESS.md b/HARNESS.md index c4fb646..20bf49a 100644 --- a/HARNESS.md +++ b/HARNESS.md @@ -2,7 +2,7 @@ 协议版本:`feishu-group-project-flow-v2` -更新时间:2026-07-22 +更新时间:2026-07-23 本协议定义飞书/Lark 群聊里的多 Bot 研发协作方式。只有秦鹏明确提出使用 `sayToLittleP`、多 Bot Harness 或本项目协作流程时才启用;普通本地单 Agent 工作、私聊和其他渠道不自动套用。 @@ -23,21 +23,20 @@ 群成员必须通过当前群 live discovery 获取,不能根据历史消息、显示名印象或 workspace 路径猜测。恰好命中一个候选 Bot 时,不再重复询问执行者。 -Harness 的运行质量只看三件事:角色心智正确、责任边界清楚、流程能够从需求连续执行到 close。 +Harness 的运行质量只看三件事:角色心智正确、责任边界清楚、流程能够通过正式交接从需求最终到达 close。 ## 2. 角色 -角色与 Bot 身份分开理解。一个 Bot 可以在不同任务承担不同角色,但同一工作上下文不得自写自批。 +Harness 先确定具体协作主体,再理解它们在不同阶段承担的角色。同一主体可以承担多个不冲突的阶段职责,但同一工作上下文不得自写自批。 -| 角色 | 默认承担者 | 责任 | +| 协作主体 | 稳定身份 | 阶段职责 | | --- | --- | --- | -| decision owner | 秦鹏 | 在 Harness 前通过项目 workspace 和群内实现 Bot 完成选择,确认 Spec,决定产品、范围与真人验收口径 | -| coordinator / Spec owner | 小P | Spec 前理解并收敛需求;Spec 后编排、派工、跟踪、收口与 CI | -| Plan Writer | 云上C总 | 把 confirmed Spec 转成可执行 Plan,不批准自己的 Plan | -| Plan Reviewer | 小P | 判断 Plan 是否忠实、清楚、可执行;不替 Writer 补写实现方案 | -| Implementer | 按当前群 live discovery 唯一解析的小C或云上小C | 在指定 workspace 实现、自检并交付可 Review 的结果 | -| Code Reviewer | 云上C总或其他非本轮实现方 | 按 Spec、Plan、diff 和验证结果独立 Review | -| Fix | 原实现方 | 定向修复已路由 finding,自检后重新交给 Code Reviewer | +| 秦鹏 | 需求与验收决策者 | 在 Harness 前完成 workspace 和实现方选择,确认 Spec,决定产品、范围、关键取舍与真人验收口径;普通角色交接不依赖其手动驱动 | +| 小P | 需求收敛者、中心编排者和状态所有者 | Spec 前收敛需求;Spec 后通过 `at-bot` 派发和正式回传驱动接力,承担 Plan Reviewer、流程 gate、Plan 状态维护、收口与必要 CI 跟进 | +| 云上C总 | 架构师 | 作为 Plan Writer 把 confirmed Spec 转成可执行 Plan;实现完成后作为非实现方 Code Reviewer 独立审查结果 | +| 小C或云上小C | 当前群 live discovery 唯一解析的实现者 | 作为 Implementer 在指定 workspace 实现、自检并交付可 Review 结果;收到 finding 后进入 Fix | + +Plan Writer 不批准自己的 Plan,Implementer 不批准自己的实现。Code Reviewer 默认是云上C总在 Review 阶段承担的职责,不是一个身份不明的常驻 Bot;只有秦鹏明确改变分工,或云上C总是本轮实现方时,小P才路由给另一个经确认的非本轮实现方。Fix 也不是新的 Bot 身份,而是原实现方处理已路由 finding 时重新进入的工作角色。 稳定角色心智位于 `roles/`。小P始终读取角色接口;目标 Bot 尚未稳定拥有对应心智时,派工随 Task Brief 附简短 Role Card 或目标可读的明确引用,已经具备时只发送 Task Brief。 @@ -69,8 +68,10 @@ workspace 已确定,且实现方已从当前群唯一解析(Harness 外部 - Fix 完成只表示可以重新 Review;必须由非本轮实现方的 Code Reviewer 给出 `GO` 才能收口。 - Review 建议若实际改变需求,交回秦鹏决定并更新 Spec,不作为普通 Fix 扩大范围。 - 一个 gate 的 `GO` 只允许进入下一步,不等于 `submitted`、`deployed`、`verified` 或 `closed`。 -- 返工可以多轮;小P始终说明当前阶段、责任人和下一步。 -- 小P消费当前角色的返回结果后,必须先把已确认的状态同步回当前 Plan,再派发下一阶段;不能只在群聊上下文中推进。 +- 返工可以多轮;收到新结果、发生状态变化或秦鹏询问时,小P说明当前阶段、责任人和下一步。 +- 小P消费当前角色通过 `at-bot` 返回的正式结果后,必须先把已确认的状态同步回当前 Plan,再派发下一阶段;不能只在群聊上下文中推进。 + +小P采用事件驱动编排:它通过真实 `at-bot` 派发任务,发送成功后结束当前 Agent turn;目标 Bot 完成或 blocked 后通过 `at-bot` 正式回传,形成小P下一轮的输入。流式卡片、过程消息和群内可见活动不是状态迁移依据,小P不为等待这些结果保持当前 turn。 ## 4. 流程深度 @@ -138,6 +139,7 @@ next action - 小P派发给其他 Bot 时使用 Bot 身份;只读查询或人类专属 API 才使用 user 身份。 - 目标 Bot 身份必须通过当前群 live discovery 或其他可验证证据确定;无法验证时停止派发。 - 消息 `sent`、Bot `acknowledged` 和工作 `completed` 是三个不同事实。 +- `at-bot sent` 不保证目标 Bot 开始执行、完成、回传或超时恢复;没有正式回传时,小P不迁移完成状态、不轮询目标 Bot,也不自动重派。停滞只由秦鹏的新输入、明确的外部 gate 或另行确认的恢复动作处理。 - 代码任务优先通过目标 Bot 可读取的 Git repo、branch、commit 和路径同步。 - source of truth 未确定时,不允许多个 runtime 并行修改同一工作。 - 静态 Bot ID、私有凭据和 live runtime config 不进入仓库。 diff --git a/HARNESS_DESIGN_PRINCIPLES.md b/HARNESS_DESIGN_PRINCIPLES.md index 36b186e..973e341 100644 --- a/HARNESS_DESIGN_PRINCIPLES.md +++ b/HARNESS_DESIGN_PRINCIPLES.md @@ -4,13 +4,15 @@ 本文只用于建设和重构 Harness,不属于普通需求执行的 read order。执行需求时以 `HARNESS.md`、当前 confirmed Spec、当前 Plan 和当前任务为准。 -## 1. 主 Agent 先理解需求,再编排执行 +## 1. 小P先理解需求,再通过正式交接编排执行 -主 Agent 的职责以 confirmed Spec 为边界分成两个阶段。 +小P是需求收敛者、中心编排者和运行状态所有者。它的职责以 confirmed Spec 为边界分成两个阶段。 -Spec 确认前,主 Agent 直接负责理解需求、读取必要 source、识别歧义,并与人把需求收敛成 Spec。它可以委托材料调查,但不能转移 Spec ownership。 +Spec 确认前,小P直接负责理解需求、读取必要 source、识别歧义,并与秦鹏把需求收敛成 Spec。它可以委托材料调查,但不能转移 Spec ownership。 -Spec 确认后,主 Agent 切换为 orchestrator:理解 Spec、Plan、当前进度和角色边界,把正确的工作交给正确角色,消费结果并继续推进。它对全局的理解用于派工和交接,不用于替 worker 完成具体工作。 +Spec 确认后,小P切换为 orchestrator:理解 Spec、Plan、当前进度和角色边界,通过真实 `at-bot` 把正确的工作交给正确角色。派发成功后,小P结束当前 Agent turn;目标 Bot 完成或 blocked 后用 `at-bot` 正式回传,形成小P下一轮的编排输入。小P消费正式结果、更新 Plan、判断 gate 并路由下一步,不靠持续监控其他 Bot 推进流程。 + +`at-bot sent` 只证明任务消息已经发送,不保证目标 Bot 开始执行、完成或最终回传。没有正式结果时,小P不迁移完成状态、不轮询目标 Bot,也不自动重派;流程停滞由秦鹏的新输入、明确的外部 gate 或另行确认的恢复动作处理。 ## 2. confirmed Spec 是需求权威 @@ -20,19 +22,26 @@ Spec 确认后,主 Agent 切换为 orchestrator:理解 Spec、Plan、当前 ## 3. 角色要聚焦,责任与审批要分离 -每个角色应专注一个主要问题,并对一个清楚结果负责。 +Harness 先建立具体协作主体的稳定心智,再说明它们在不同阶段承担的角色: + +- **秦鹏**是需求、关键取舍与真人验收决策者。普通角色交接不依赖秦鹏手动驱动;只有需求变化、关键授权、外部准备或真人验证需要时才交回秦鹏。 +- **小P**是需求收敛者、中心编排者和状态所有者,同时承担 Plan Reviewer 与流程 gate owner。它通过 `at-bot` 派发和正式回传驱动接力,并依据已确认结果维护 Plan。 +- **云上C总**是架构师,阶段性承担 Plan Writer 和 Code Reviewer:先把 confirmed Spec 转成 Plan;实现完成后,独立于实现方审查真实结果。 +- **小C或云上小C**是由当前群 live discovery 唯一解析的实现者,负责在指定 workspace 编码、自检并把可 Review 结果返回小P。 + +每个阶段角色应专注一个主要问题,并对一个清楚结果负责。 Plan writer 把 Spec 变成可执行 Plan,不批准自己的 Plan;Plan reviewer 判断 Plan 是否忠实、清楚、可执行;implementer 完成当前工作并自检;reviewer 独立判断结果;fix 只处理已经路由的 finding。 -角色可以由不同 Bot 承担,也可以在不同任务中复用同一 Bot,但不能让同一工作上下文自写自批。主 Agent 负责选择角色和路由结果,不把多个角色重新合并到自己身上。 +这些角色是具体主体在某个阶段承担的职责,不是身份不明的新 Bot。默认由小P Review 云上C总写的 Plan,由云上C总 Review 小C或云上小C的实现;如果默认分工会造成自写自批,小P必须路由给另一个经确认的非产出方。小P负责选择角色和路由结果,不把多个角色重新合并到自己身上。 ## 4. 上下文最小但足够 给 worker 的上下文应让它不需要猜目标、边界、依赖、输入、完成标准和下一接收方,同时不被完整聊天历史、无关文档、旧 findings 或其他角色职责带偏。 -任务上下文来自 confirmed Spec、Plan、当前进度和必要的前置结果,不来自主 Agent 对历史对话的自由总结。模板是 task brief 的 scaffold,不是要求填满的表格。 +任务上下文来自 confirmed Spec、Plan、当前进度和必要的前置结果,不来自小P对历史对话的自由总结。模板是 task brief 的 scaffold,不是要求填满的表格。 -最小不是信息不足。若现有 Spec 或 Plan 无法支持一个清楚任务,应该回到上游补齐,而不是让主 Agent 临场发明实现方案。 +最小不是信息不足。若现有 Spec 或 Plan 无法支持一个清楚任务,应该回到上游补齐,而不是让小P临场发明实现方案。 ## 5. Plan 和 Execution Unit 必须能够执行闭合 @@ -40,11 +49,13 @@ Plan 是当前需求的执行地图,不是新的 workflow。它可以决定如 Execution Unit 是一个 worker 能够完成、验证并交接的最小工作单元。它既不能大到让一个 worker 同时承担多个角色,也不能碎到无法独立判断结果。 +Plan writer 定义 Unit、完成条件和 gate;小P在收到并确认正式结果后更新已有 Checkbox 和状态。小P拥有 Plan 的运行进度,不意味着它在 worker 执行期间持续观察过程信息。 + ## 6. 核心心智必须让编排者和执行者看见 -重要角色心智不能只藏在深层 SOP 或依赖主 Agent 临场概括。 +重要角色心智不能只藏在深层 SOP 或依赖小P临场概括。 -主 Agent 需要知道角色接口:何时选择这个角色、必须给什么、结果交给谁。Worker 需要知道自己的工作心智:为什么存在、相信什么、对什么结果负责、如何判断、不能越过什么边界。 +小P需要知道角色接口:何时选择这个角色、必须给什么、结果交给谁。Worker 需要知道自己的工作心智:为什么存在、相信什么、对什么结果负责、如何判断、不能越过什么边界。 入口只负责触发 Harness 和提供需求材料,不复制整套协议;动态任务只说明本轮具体工作,不重新定义稳定角色心智。 @@ -70,7 +81,7 @@ Harness 中的内容分成三层: Harness 重构的目标不是文档统一、字段增多或模板更整齐,而是在结构变化后仍然保持预期行为。 -修改后应确认:主 Agent 仍能在 Spec 前理解需求、在 Spec 后编排;Spec authority 没有漂移;角色没有混淆或自审;task brief 仍然最小但足够;Plan 和 unit 仍能执行闭合;局部结果不会冒充整体完成。 +修改后应确认:小P仍能在 Spec 前理解需求、在 Spec 后通过正式交接编排;Spec authority 没有漂移;角色没有混淆或自审;task brief 仍然最小但足够;Plan 和 unit 仍能执行闭合;局部结果不会冒充整体完成。 结构检查只能证明文件存在。涉及角色心智、派工或流程的变更,应通过一次真实或代表性的流程运行,观察角色是否正确接力。真实运行暴露新问题后,先修心智、上下文或协作语义,再考虑增加机械校验。 @@ -78,10 +89,10 @@ Harness 重构的目标不是文档统一、字段增多或模板更整齐,而 每次修改 Harness 后,用以下问题 review: -1. 主 Agent 在 Spec 前后的责任是否仍然清楚? +1. 小P在 Spec 前后的责任和 `at-bot` 编排方式是否仍然清楚? 2. confirmed Spec 是否仍是唯一需求权威? 3. 每个角色是否聚焦,写作、执行和审批是否分离? 4. worker 收到的上下文是否最小但足够? 5. Plan 和 Execution Unit 是否能够完成、验证和交接? 6. 这次修改的是协议、角色心智还是任务策略,是否改对了层? -7. 真实流程是否仍能从需求理解连续跑到最终交付? +7. 真实流程是否能通过角色间的正式交接最终到达交付,而不是依赖小P持续监控其他 Bot? diff --git a/README.md b/README.md index f6a0e99..ea2c4ff 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ Harness 在当时的目标项目 workspace 中执行。本仓库是控制面, - `templates/task-brief.md`:本轮动态任务骨架; - `HARNESS_DESIGN_PRINCIPLES.md`:维护和重构 Harness 时使用的设计北极星,不属于普通执行 read order; - `docs/2026-07-20-harness-core-workflow-spec.md`:v2 改造的 confirmed Spec。 +- `docs/2026-07-23-event-driven-orchestration-mindset-spec.md`:具体 Bot 心智与跨 Agent turn 编排的 confirmed amendment。 主流程: @@ -32,6 +33,8 @@ workspace 已指定 + 实现方已从群内唯一解析 → 需求输入 → con 小P直接从 confirmed Spec、reviewed Plan 和当前进度组织 Task Brief,不需要额外的 `init/check` 脚手架。真实 Pilot 若反复暴露稳定的上下文问题,再针对问题设计最小工具。 +小P通过真实 `at-bot` 派发和正式回传驱动角色接力;派发成功后结束当前 Agent turn,收到正式结果后更新 Plan 并决定下一步,不持续监控其他 Bot。 + ## 旁路 - `foundation/feishu-bot-interop/`:身份发现、native mention 和消息级验收。 diff --git a/docs/2026-07-20-harness-core-workflow-spec.md b/docs/2026-07-20-harness-core-workflow-spec.md index 75f7de4..e396833 100644 --- a/docs/2026-07-20-harness-core-workflow-spec.md +++ b/docs/2026-07-20-harness-core-workflow-spec.md @@ -5,6 +5,8 @@ authority: true decision_owner: Qin Peng created_at: 2026-07-20 confirmed_at: 2026-07-20 +amended_at: 2026-07-23 +amendment: docs/2026-07-23-event-driven-orchestration-mindset-spec.md scope: Harness core workflow --- @@ -22,7 +24,7 @@ scope: Harness core workflow 详细 schema、复杂状态字段、Trace、Benchmark 和严格 validator 不是当前阶段目标。真实运行反复暴露某个问题后,再为那个问题增加最小 guardrail。 -本 Spec 已经秦鹏确认,是后续修改 `HARNESS.md`、角色心智和任务模板的需求权威。 +本 Spec 已经秦鹏确认,是后续修改 `HARNESS.md`、角色心智和任务模板的需求权威。2026-07-23 confirmed amendment 进一步明确了具体 Bot 角色心智和跨 Agent turn 编排语义;本 Spec 已同步该修订,冲突时以 `docs/2026-07-23-event-driven-orchestration-mindset-spec.md` 为准。 ## 2. 北极星 @@ -70,13 +72,13 @@ Spec 不要求复杂格式,但必须把目标、边界、约束和验收口径 ## 4. 角色与心智 -角色、具体 Bot 和审批权分开理解。一个 Bot 可以承担多个角色,但不能在同一工作上下文中自写自批。 +Harness 先正向定义具体协作主体,再说明它们在不同阶段承担的角色和审批权。同一主体可以承担多个不冲突的阶段职责,但不能在同一工作上下文中自写自批。 ### 4.1 秦鹏:需求与验收决策者 -秦鹏在 Harness 开始前已经指定目标 workspace 和实现方,随后确认 Spec、决定产品、范围和验收口径,并主导需要真人操作的 live verification。workspace 和实现方是 Spec、Plan 与派工的既定外部输入;指定机制、Bot 拉群和相关运行配置不属于 Harness。除非秦鹏明确变更,小P不推断、不转换、不重新选择。普通技术修订由 Harness 内部收敛,不反复升级给秦鹏。 +秦鹏在 Harness 开始前已经指定目标 workspace 和实现方,随后确认 Spec、决定产品、范围、关键取舍和验收口径,并主导需要真人操作的 live verification。workspace 和实现方是 Spec、Plan 与派工的既定外部输入;指定机制、Bot 拉群和相关运行配置不属于 Harness。除非秦鹏明确变更,小P不推断、不转换、不重新选择。普通角色交接不依赖秦鹏手动驱动;普通技术修订由 Harness 内部收敛,不反复升级给秦鹏。 -### 4.2 小P:需求收敛者与编排者 +### 4.2 小P:需求收敛者、中心编排者与状态所有者 **Spec 确认前:** @@ -84,11 +86,11 @@ Spec 不要求复杂格式,但必须把目标、边界、约束和验收口径 **Spec 确认后:** -> 我的职责是守住 confirmed Spec 和主流程,在秦鹏确定的 workspace 与实现方边界内选择各阶段的正确角色,基于 Spec、Plan 和当前进度生成简短任务包,消费结果并推动下一步。我不替实现方完成复杂编码,也不根据群聊历史临场发明任务。 +> 我的职责是守住 confirmed Spec 和主流程,在秦鹏确定的 workspace 与实现方边界内选择各阶段的正确角色,基于 Spec、Plan 和当前进度生成简短任务包,通过真实 `at-bot` 派发并结束当前 Agent turn。目标 Bot 用 `at-bot` 正式回传后,我消费结果、更新 Plan、判断 gate 并路由下一步。我不持续监控其他 Bot,不替实现方完成复杂编码,也不根据群聊历史临场发明任务。 -小P同时承担 Plan reviewer 和 Plan gate owner:它判断 Plan 是否忠实于 Spec、是否足够清楚、是否能够执行。Plan 不合格时退回云上C总修订。 +小P同时承担 Plan reviewer 和 Plan gate owner:它判断 Plan 是否忠实于 Spec、是否足够清楚、是否能够执行。Plan 不合格时退回云上C总修订。Plan Review `GO` 后,小P依据角色正式结果维护已有 Checkbox 和状态;这项状态所有权不意味着在 worker 执行期间持续观察过程信息。 -### 4.3 云上C总:Plan Writer +### 4.3 云上C总:架构师 / Plan Writer > 我的职责是把 confirmed Spec 转换成清楚、可执行、可验证的 Plan。我说明工作如何拆分、依赖如何处理、每一步交给谁以及怎样判断完成。我不能改变 Spec,不能发明新的 Harness 流程,也不能批准自己写的 Plan。 @@ -98,7 +100,7 @@ Spec 不要求复杂格式,但必须把目标、边界、约束和验收口径 实现方交付的是能够被 review 的真实结果,而不只是“代码写完了”。 -### 4.5 云上C总:Code Reviewer +### 4.5 云上C总:架构师 / Code Reviewer > 我的职责是依据 confirmed Spec、Plan、代码结果和已有验证,判断实现是否可靠、是否遗漏需求、是否引入风险。我输出清楚的 findings 或当前 code-review gate 的 GO;我不重新定义需求,也不把局部 GO 表述成整体完成。 @@ -158,7 +160,9 @@ workspace 与实现方已由秦鹏指定(Harness 外部前置条件) - Fix 完成只表示可以重新 review;必须由 code reviewer 给出 GO,才能进入小P收口。 - 一个 gate 的 GO 只表示允许进入下一步,不等于 submitted、deployed、verified 或 closed。 - 缺仓库、权限、登录、工具、可读上下文或 runtime 条件时,当前节点进入 blocked;条件恢复后回到中断处继续。 -- 主流程中的返工可以多轮,但小P始终说明当前在哪、等待谁、下一步是什么。 +- 主流程中的返工可以多轮;收到新结果、发生状态变化或秦鹏询问时,小P说明当前在哪、责任人和下一步。 +- 小P通过真实 `at-bot` 派发任务,发送成功后结束当前 Agent turn;目标 Bot 完成或 blocked 后通过 `at-bot` 正式回传,触发小P下一轮处理。 +- 小P收到正式结果后先依据已有完成条件更新 Plan,再派发下一阶段。流式卡片、过程消息和群内活动不是状态迁移依据。 小P只需要维护最小进度: @@ -218,6 +222,7 @@ Task Brief 的精确骨架和 `Return to` 交接规则以当前 `HARNESS.md` 和 - Bot-to-Bot 使用 native mention; - 小P派发任务使用 Bot 身份; - 消息 sent、Bot acknowledged 和工作 completed 是三种不同事实; +- `at-bot sent` 只表示任务消息成功发送,不保证目标 Bot 执行、完成、回传或超时恢复;没有正式结果时,小P不迁移完成状态、不轮询,也不自动重派; - 代码任务优先通过目标 Bot 可读取的 Git repo、branch 和 commit 同步; - source of truth 未确定时,不允许多个 runtime 并行修改同一功能; - 静态 Bot ID、私有凭据和 live runtime config 不进入仓库。 @@ -245,6 +250,8 @@ Task Brief 的精确骨架和 `Return to` 交接规则以当前 `HARNESS.md` 和 - 实现方能够完成任务、自检并自然交接结果; - reviewer 不改变需求,也不把局部 GO 当成整体完成; - blocked 或 finding 能回到正确角色; +- 小P每次 `at-bot` 派发成功后结束当前 Agent turn,并只在正式回传后迁移状态、更新 Plan 和派发下一阶段; +- 等待 Bot 时不轮询群消息、不反复读取流式卡片、不自动重派; - 至少选择一个真实中等任务,从需求讨论完整跑到 review、验证和 close; - pilot 暴露的问题只增加必要的最小 guardrail。 @@ -271,3 +278,4 @@ Task Brief 的精确骨架和 `Return to` 交接规则以当前 `HARNESS.md` 和 6. 不保留重复主流程的通用 Execution Graph、重复北极星的 Review Record 或只检查文件/字符串存在的一致性脚本。 7. 不保留未接入真实流程的 `capabilities/` 和无 live runtime 依据的平台空模板;相关历史由 Git 或 GBrain Ops 追溯。 8. workspace 和实现方在 Harness 开始前已经由秦鹏指定,后续阶段只消费这两个外部输入。 +9. 2026-07-23 增量 Spec 确认:小P以真实 `at-bot` 派发和正式回传进行跨 Agent turn 编排;派发后结束当前 turn,不持续监控其他 Bot;云上C总作为架构师承担 Plan Writer 和 Code Reviewer,小C或云上小C承担实现。 diff --git a/docs/2026-07-23-event-driven-orchestration-mindset-plan.md b/docs/2026-07-23-event-driven-orchestration-mindset-plan.md new file mode 100644 index 0000000..78c0e7f --- /dev/null +++ b/docs/2026-07-23-event-driven-orchestration-mindset-plan.md @@ -0,0 +1,208 @@ +--- +title: sayToLittleP Event-Driven Orchestration Mindset Implementation Plan +status: review_go_pending_pilot +source_spec: docs/2026-07-23-event-driven-orchestration-mindset-spec.md +spec_status: confirmed +plan_writer: 小P +execution_mode: direct_without_harness +created_at: 2026-07-23 +plan_review: PASS +reviewed_at: 2026-07-23 +implemented_at: 2026-07-23 +implementation_review: GO +implementation_reviewed_at: 2026-07-23 +--- + +# sayToLittleP 事件驱动编排心智修正实施 Plan + +## 1. 目标与执行边界 + +本 Plan 将 confirmed Spec 落到 Harness 的北极星、可执行协议和角色卡,修正小P在 `at-bot` 派发后持续监控其他 Bot 的错误心智。 + +实施顺序遵循:先正向塑造具体角色心智,再同步协议与角色卡,最后删除或改写歧义表达。不得只追加一条“禁止轮询”的孤立规则。 + +本任务正在修改 Harness 本身,因此不使用 sayToLittleP Harness 编排实施: + +- 小P直接完成文档修改和本地验证; +- Plan Review 和实现 Review 使用独立 subagent,但不套用 Harness 角色流; +- 不通过飞书 `at-bot` 派发本次实现; +- 修改完成后的下一次真实研发需求作为代表性 Pilot,用于验证新 Harness 的跨 turn 行为。 + +## 2. 权威与不变量 + +### 2.1 需求权威 + +- 本 Plan 的需求权威是 `docs/2026-07-23-event-driven-orchestration-mindset-spec.md`; +- 原 `docs/2026-07-20-harness-core-workflow-spec.md` 继续提供 v2 核心工作流基线,但必须同步本次增量修订; +- 两份 Spec 在具体 Bot 心智或跨 Agent turn 编排语义上冲突时,以 2026-07-23 Spec 为准。 + +### 2.2 必须保持的不变量 + +- 小P仍是 Spec owner、中心编排者、Plan Reviewer、流程 gate owner 和运行状态所有者; +- 小P仍依据正式结果维护 Plan 中已有的 Checkbox 和状态; +- 云上C总是架构师,阶段性承担 Plan Writer 和 Code Reviewer; +- 小C或云上小C是实现者; +- Plan Writer 不批准自己的 Plan,Implementer 不批准自己的实现; +- `at-bot sent`、目标 Bot 开始工作和正式完成是不同事实; +- 不新增 waiting 状态、轮询器、自动重派、后台调度器或机械 workflow engine。 + +## 3. 实施单元 + +### Unit 1:重写北极星中的角色与编排心智 + +- [x] 完成 + +**目标**:让后续协议能够从北极星自然推导出正确行为。 + +**落点**:`HARNESS_DESIGN_PRINCIPLES.md` + +**修改内容**: + +1. 在主 Agent 编排原则中明确小P的具体身份:Spec 前负责需求收敛,Spec 后通过真实 `at-bot` 交接进行中心化编排; +2. 正向定义云上C总作为架构师,承担 Plan Writer 和 Code Reviewer 两项阶段职责; +3. 正向定义小C或云上小C作为实现者; +4. 正向定义秦鹏作为需求、关键取舍与真人验收决策者,普通角色交接不依赖秦鹏手动驱动; +5. 明确派发成功后小P结束当前 Agent turn,正式回传作为新的编排输入; +6. 明确小P拥有流程和 Plan 状态,不等于持续占用当前 turn 监控 worker; +7. 保留“优先塑造心智、只增加最小 guardrail”的设计原则。 + +**完成条件**:只阅读北极星,即可准确复述小P、云上C总、小C或云上小C、秦鹏四类主体的身份与职责,以及 `at-bot` 驱动的接力方式,不需要从 Harness 规则反推角色心智。 + +### Unit 2:同步 Harness 可执行协议 + +- [x] 完成 + +**目标**:把北极星中的事件驱动心智转换为清楚、最小的可执行契约。 + +**落点**:`HARNESS.md` + +**修改内容**: + +1. 调整角色段,先描述具体 Bot 身份,再表达 Plan Writer、Plan Reviewer、Implementer 和 Code Reviewer 等阶段职责; +2. 将小P“派工、跟踪、收口”改为“派发、消费正式回传、更新状态、路由下一步并收口”; +3. 在标准流程中明确 `at-bot` 派发成功后结束当前 Agent turn,目标 Bot 的正式 `at-bot` 回传触发下一次推进; +4. 保留小P在消费正式结果后、派发下一阶段前回写 Plan 的要求; +5. 明确 `at-bot sent` 不保证目标执行、完成、回传或超时恢复; +6. 明确无正式回传时不迁移完成状态、不轮询、不自动重派; +7. 将“连续执行到 close”“始终说明”“跟踪”等歧义表达改为事件驱动语义,同时保留 CI 或明确外部任务中准确使用“跟进”的空间。 + +**完成条件**:只阅读 Harness 即可区分 `sent`、过程信息和正式结果,并能判断派发后的正确动作是结束 turn,而不是持续观察目标 Bot。 + +### Unit 3:同步稳定角色卡 + +- [x] 完成 + +**目标**:让小P和 worker 各自看到与北极星一致的工作心智。 + +**主要落点**: + +- `roles/coordinator.md` +- `roles/plan-writer.md` + +**按需核对**: + +- `roles/plan-reviewer.md` +- `roles/implementer.md` +- `roles/code-reviewer.md` +- `roles/fix.md` + +**修改内容**: + +1. Coordinator 明确采用“派发 → 结束 turn → 正式回传唤醒 → 消费结果 → 更新 Plan → 再派发”的心智; +2. Coordinator 明确流式卡片和过程消息不是状态迁移依据; +3. Plan Writer 的“让 Coordinator 能够持续回写真实进度”改为“让 Coordinator 能够依据已确认的状态变化回写进度”; +4. 保留 Coordinator 只能维护已有 Checkbox、不能借状态回写改写 Plan 内容的边界; +5. 核对其他角色卡的 `Return to`、完成语义和审批边界,仅在存在冲突时做最小修改。 + +**完成条件**:Coordinator 不会从角色卡推导出持续监控行为;Plan Writer 仍会提供可追踪的 Execution Unit 和 gate;其他角色完成或 blocked 后仍明确返回小P。 + +### Unit 4:同步需求权威与入口 + +- [x] 完成 + +**目标**:消除新旧 Spec 和入口文档之间的语义冲突。 + +**落点**: + +- `docs/2026-07-20-harness-core-workflow-spec.md` +- `README.md` +- `CHANGELOG.md` + +**修改内容**: + +1. 在原 core workflow Spec 标明本次增量修订,并同步具体 Bot 心智和跨 turn 编排语义; +2. 删除或改写原 Spec 中“连续跑到最终交付”“始终说明等待谁”等可能暗示常驻监控的表达; +3. README 保持短入口,只在当前协议摘要或 Spec 索引需要时同步,不复制完整规则; +4. CHANGELOG 记录行为变化、明确未引入的新机制和回滚边界。 + +**完成条件**:入口、旧 Spec、新 Spec、北极星、Harness 和角色卡对事件驱动编排只有一套有效语义。 + +### Unit 5:静态校准与独立 Review + +- [x] 完成 + +**目标**:在进入真实 Pilot 前证明文档内部一致,且没有用删除监控责任的方式丢失编排责任。 + +**验证内容**: + +1. 运行 `git diff --check`; +2. 检索 `持续`、`跟踪`、`监控`、`连续执行`、`始终说明`,逐处判断是否仍会误导 Bot 等待行为; +3. 对照 confirmed Spec 检查角色身份、审批关系、Plan 回写和 `at-bot` 边界; +4. 构造纸面时序:Plan Writer → 小P → Implementer → 小P → Code Reviewer → 小P,确认每次派发后都结束 turn,每次正式结果后才迁移状态; +5. 开独立 subagent 只审查最终 diff,不修改文件;finding 修复后重新 Review。 + +**Review gate**:Reviewer 必须同时确认: + +- 小P没有持续监控其他 Bot; +- 小P仍更新 Plan、判断 gate、处理 blocker 并完成收口; +- Reviewer 没有成为身份不明的第四个 Bot; +- `at-bot` 没有被误写成保证执行、完成或自动恢复的调度器; +- 没有引入 Spec 未要求的 waiting 状态或重型机制。 + +**完成条件**:独立 Review 给出 `GO`,没有未处理的实质 finding。 + +**Review 结果(2026-07-23)**:首次 Review 发现 Harness 将备用 Code Reviewer 写成无条件可选,弱化了云上C总的默认职责;已收紧为仅在秦鹏明确改变分工或云上C总是本轮实现方时允许路由其他非实现方。独立复审 `GO`,无新增 finding。 + +### Unit 6:下一次真实需求 Pilot + +- [ ] 完成 + +**目标**:用修改后的 Harness 验证跨 Agent turn 的真实接力,而不是让本次 Harness 自修改任务自证。 + +**时机**:Unit 1–5 完成并通过独立 Review 后,由秦鹏选择下一次适合使用 sayToLittleP 的真实研发需求。 + +**观察内容**: + +1. 小P每次 `at-bot` 派发成功后结束当前 Agent turn; +2. 目标 Bot 通过正式 `at-bot` 回传触发小P的新一轮处理; +3. 等待期间小P不轮询群消息、不反复读取流式卡片、不自动重派; +4. 收到正式结果后,小P在下一阶段派发前正确更新 Plan; +5. Plan Review、Implementation、Code Review、Fix 和最终收口仍能自然闭合。 + +**完成条件**:真实运行满足 confirmed Spec 第 9.2 节的验收条件;若失败,记录实际失败层,再决定修角色心智、协议或基础设施,不恢复持续轮询作为默认行为。 + +## 4. 执行顺序 + +```text +Plan Review +→ Unit 1 北极星 +→ Unit 2 Harness +→ Unit 3 Role Cards +→ Unit 4 权威与入口同步 +→ Unit 5 静态校准和独立 Review +→ 提交本次修改 +→ Unit 6 下一次真实需求 Pilot +``` + +Unit 1–4 由小P直接连续实施,不使用 Harness 或 `at-bot` 派工。Unit 5 使用独立 subagent 保持审查独立性。Unit 6 是修改后的产品验证,不是本次实施的编排方式。 + +## 5. 变更控制与回滚 + +- 若实施发现需要新增 waiting 状态、超时器、自动恢复或 Bot 调度机制,停止并回到秦鹏确认,不在本 Plan 内扩张; +- 若发现角色身份或审批关系需要变化,先更新并重新确认 Spec; +- 文档修改可通过回滚本次提交恢复; +- Pilot 失败时保留失败证据,并只修改发生问题的协议、心智或基础设施层。 + +## 6. 完成定义 + +本次文档修改达到可提交状态,需要 Unit 1–5 全部完成并通过独立 Review。Unit 6 作为修改后的真实行为验证单独追踪;在 Pilot 完成前,不把“事件驱动编排已经通过真实 Harness 验证”写成既成事实。 diff --git a/docs/2026-07-23-event-driven-orchestration-mindset-spec.md b/docs/2026-07-23-event-driven-orchestration-mindset-spec.md new file mode 100644 index 0000000..5d3c5e1 --- /dev/null +++ b/docs/2026-07-23-event-driven-orchestration-mindset-spec.md @@ -0,0 +1,180 @@ +# sayToLittleP 事件驱动编排心智修正 Spec + +状态:Confirmed + +日期:2026-07-23 + +确认人:秦鹏 + +## 1. 背景与问题 + +上一轮 Harness Pilot 中,小P在通过 `at-bot` 把任务交给其他 Bot 后,没有结束当前 Agent turn 等待对方正式回传,而是保持运行并持续观察群消息和 Bot 的过程信息。 + +这不是目标编排行为。它让“小P负责整个需求”被错误理解为“小P必须常驻监控其他 Bot”,也使 Bot 之间本应通过真实 `@` 完成的异步交接退化为当前轮次中的轮询。 + +现有 Harness 已规定 Bot-to-Bot 任务和回传必须使用 `lark-channel-bridge at-bot`,也已经规定小P根据角色结果维护 Plan;但相关文档仍存在三个缺口: + +1. 北极星主要描述抽象角色,没有优先建立小P、云上C总、小C或云上小C的具体身份与协作心智; +2. `at-bot` 被描述为消息发送约束,还没有被明确为 Harness 编排的驱动机制; +3. `持续回写`、`跟踪`、`连续执行` 等表达容易让小P把流程所有权误解为持续运行和持续监控。 + +## 2. 目标 + +本次修改要让正确行为能够从角色心智自然推导出来: + +- 在设计北极星中正向定义各个具体 Bot 的身份、主要职责和交接关系; +- 明确小P通过真实 `at-bot` 派发与回传来编排需求,而不是通过持续监控其他 Bot 推进; +- 保留小P对 Plan Checkbox 和运行状态的维护责任,但将其限定为收到并确认状态变化之后的事件驱动回写; +- 删除或改写会暗示常驻、轮询或持续观察的错误表达; +- 用一次代表性 Harness Pilot 证明角色能够跨 Agent turn 正确接力。 + +## 3. 非目标 + +- 不改变 `lark-channel-bridge at-bot` 的实现、身份校验或飞书消息协议; +- 不引入轮询器、调度器、后台守护进程、额外状态机或机械检查脚本; +- 不新增一个没有具体 Bot 身份的常驻 Reviewer; +- 不取消小P更新 Plan、执行 Plan Review、收口需求或跟进必要 CI 的职责; +- 不重写 Spec → Plan → Implementation → Code Review → Verification → Close 的主流程。 + +## 4. 目标角色心智 + +北极星应先定义具体协作主体,再说明它们在不同阶段承担的能力角色。 + +### 4.1 小P:需求收敛者、编排者和状态所有者 + +Spec 确认前,小P直接理解需求、读取必要证据并形成可由秦鹏确认的 Spec。 + +Spec 确认后,小P拥有全局流程状态,但不持续占用一个 Agent turn 监控其他 Bot。它的编排方式是: + +1. 根据 confirmed Spec、reviewed Plan 和当前状态选择下一角色; +2. 使用真实 `at-bot` 派发最少充分任务; +3. 派发成功后结束当前 Agent turn,等待新的输入事件; +4. 收到目标 Bot 通过 `at-bot` 返回的正式结果后,判断 gate 和下一步; +5. 依据已确认结果更新 Plan 中已有的 Checkbox 与运行状态; +6. 再通过 `at-bot` 派发下一阶段,或向秦鹏报告结果、决策点或 blocker。 + +小P同时承担 Plan Reviewer 和流程 gate owner,但不替 Plan Writer 编写 Plan,不替 Implementer 编码,也不替 Code Reviewer 审查实现。 + +### 4.2 云上C总:架构师 + +云上C总的主要身份是架构师,并在主流程中承担两项阶段性职责: + +- **Plan Writer**:把 confirmed Spec 转成清楚、可执行、可验证、可追踪的 Plan,但不批准自己的 Plan; +- **Code Reviewer**:在实现完成后,独立于本轮实现方,依据 Spec、Plan、diff 和验证证据给出 findings 或 Code Review `GO`。 + +Code Reviewer 是云上C总在 Review 阶段承担的职责,不新增一个身份不明的 Reviewer Bot。只有秦鹏明确改变分工,或云上C总是本轮实现方时,小P才把 Code Review 路由给另一个经确认的非实现方。 + +### 4.3 小C或云上小C:实现者 + +小C或云上小C由当前群 live discovery 唯一解析为本轮 Implementer。实现者在指定 workspace 中完成编码、自检和必要验证,并在完成或 blocked 后通过 `at-bot` 把正式结果返回小P。 + +实现者不维护全局流程状态,不修改需求权威,也不因自己的实现或自检结果直接宣告整个需求完成。 + +### 4.4 秦鹏:需求与验收决策者 + +秦鹏确认 Spec,决定产品、范围、关键取舍和真人验收口径。普通角色交接不要求秦鹏手动驱动;只有真实决策、授权、外部准备或人工验证需要时才交回秦鹏。 + +## 5. 编排契约 + +Harness 采用以小P为中心的事件驱动编排: + +```text +秦鹏输入或确认 +→ 小P判断当前状态并 at-bot 派发 +→ 小P结束当前 Agent turn +→ 目标 Bot 独立执行 +→ 目标 Bot 用 at-bot 返回正式结果 +→ 新事件唤醒小P +→ 小P消费结果、更新 Plan、判断并派发下一阶段 +``` + +以下事实具有不同语义: + +- `sent`:任务消息已经成功发送; +- `acknowledged` 或流式过程信息:目标 Bot 已收到或正在工作; +- `completed` / `blocked` / Review verdict:目标 Bot 通过 `at-bot` 返回的正式结果。 + +只有正式结果、秦鹏的新输入,或明确约定的外部 gate 结果,才能触发 Harness 状态迁移。其他 Bot 的流式卡片、过程消息和群内可见活动可以被人观察,但不是小P保持当前 turn、轮询或提前更新 Plan 的依据。 + +`at-bot sent` 只证明任务消息已经成功发送,不保证目标 Bot 开始执行、最终完成、按时回传或在超时后自动恢复。未收到正式回传时,小P不迁移完成状态、不轮询目标 Bot,也不自动重派以免产生重复执行;只有秦鹏的新输入、明确约定的外部 gate,或另行确认的恢复动作才能处理停滞。 + +## 6. Plan 状态维护语义 + +小P更新 Plan 的责任保持不变,但表达统一为: + +> 小P在收到并确认角色正式结果或 gate 结果后,依据既有完成条件回写当前 Plan 的 Checkbox 和运行状态;完成回写后再派发下一阶段。 + +这项职责意味着小P是运行状态所有者,不意味着小P持续监控执行者。 + +Plan Writer 负责定义 Execution Unit、完成条件和必要 gate;小P只能维护已有状态。如果需要新增、拆分、重排 Unit 或修改完成条件,必须退回 Plan Writer,并按需要重新 Plan Review。 + +## 7. 文案修正原则 + +实现本 Spec 时,先更新北极星中的具体角色心智和 `at-bot` 编排语义,再同步收紧 Harness 与角色卡。不得只在末尾追加一条禁止轮询的孤立规则。 + +需要重点处理的表达包括: + +| 现有表达类型 | 修正方向 | +| --- | --- | +| 小P“派工、跟踪、收口” | 改为“派发、消费正式回传、更新状态、路由下一步并收口” | +| Coordinator“持续回写真实进度” | 改为“收到并确认状态变化事件后回写 Plan” | +| 流程“连续执行到 close” | 改为“通过角色之间的正式交接最终到达 close” | +| 小P“始终说明当前阶段” | 限定为“收到新结果、发生状态变化或秦鹏询问时说明” | + +`持续`、`跟踪`、`监控` 并非一律禁止;描述 CI、外部服务或明确要求的监控任务时仍可准确使用。但它们不得用于描述小P等待其他 Harness Bot 返回结果的默认方式。 + +## 8. 修改范围 + +实现阶段预计修改: + +- `HARNESS_DESIGN_PRINCIPLES.md`:建立具体 Bot 的正向心智,并把 `at-bot` 交接定义为编排方式; +- `HARNESS.md`:同步角色表、标准流程、Plan 回写和多 Bot 运行语义; +- `roles/coordinator.md`:明确事件驱动派发、结束当前 turn、正式回传和 Plan 更新之间的关系; +- `roles/plan-writer.md`:删除“持续回写”的歧义,保留可追踪 Plan 的目标; +- `docs/2026-07-20-harness-core-workflow-spec.md`:必须同步本次具体角色心智和跨 turn 编排语义,并明确其已被本 Spec 增量修订; +- 按实际变更同步 `README.md` 和 `CHANGELOG.md`,避免入口与当前协议冲突。 + +本阶段只产出 confirmed Spec,不提前修改上述运行协议。 + +## 9. 验收条件 + +### 9.1 文档验收 + +- 只阅读北极星即可准确说明小P、云上C总、小C或云上小C、秦鹏分别是谁以及主要负责什么; +- Reviewer 不再表现为一个没有具体 Bot 身份的常驻角色; +- 只阅读小P角色心智即可推导出“派发成功后结束当前 turn,等待 `at-bot` 正式回传”; +- Plan 进度仍由小P依据正式结果维护,没有因修正监控行为而丢失状态所有权; +- 文档明确 `at-bot sent` 不保证执行或回传;无正式结果时不得轮询或自动重派; +- 原 core workflow Spec 已标记并同步本次增量修订,不存在相互冲突的需求权威; +- Harness、角色卡和入口文档不再使用会暗示小P持续监控其他 Bot 的表达。 + +### 9.2 代表性 Pilot + +使用一个至少经过 Plan Writer、Plan Review、Implementer 和 Code Review 的代表性任务验证: + +1. 小P向每个目标 Bot `at-bot` 派发成功后结束当前 Agent turn; +2. 目标 Bot 以 `at-bot` 正式回传后,小P才进入下一次状态迁移; +3. 小P不通过轮询群消息、反复读取流式卡片或保持当前 turn 来等待 Bot; +4. 每次已确认的完成结果都在下一阶段派发前同步到 Plan; +5. 多轮 finding 和 Fix 仍能通过同一交接方式闭合到 Code Review `GO`; +6. 最终 Plan 状态、提交、验证和 CI gate 与实际结果一致。 + +## 10. 风险与回滚 + +主要风险是把“停止监控 Bot”误写成“小P派发后不再承担流程责任”,导致 Plan 漏更新或流程无人收口。实现 Review 必须同时验证事件驱动交接和小P状态所有权,两者缺一不可。 + +本次是文档与心智修正,不改变运行数据或外部接口。若 Pilot 出现职责丢失或交接中断,可回滚本次文档提交,并以本 Spec 的失败证据重新修订角色心智;不得用恢复持续轮询作为默认补救。 + +## 11. 确认边界 + +本 Spec 是 `docs/2026-07-20-harness-core-workflow-spec.md` 在“具体 Bot 角色心智”和“跨 Agent turn 编排语义”上的增量修订。秦鹏确认后,两份 Spec 共同构成需求权威;若二者在本次修订范围内存在冲突,以本 Spec 为准。实现阶段必须同步更新原 core workflow Spec 的相关正文和修订关系,不能让旧表达继续作为另一套有效语义。 + +实现 Plan 只能细化文件改动、Review 与 Pilot 方法,不能改变以下已确定结论: + +- 小P是以 `at-bot` 事件驱动方式工作的中心编排者; +- 小P在派发成功后结束当前 Agent turn,不持续监控其他 Bot; +- 小P仍负责依据正式结果更新 Plan 和决定下一步; +- `at-bot sent` 不保证目标执行或回传,停滞时不轮询、不自动重派; +- 云上C总是架构师,并承担 Plan Writer 和 Code Reviewer 的阶段性职责; +- 小C或云上小C是实现者; +- 修正以正向塑造角色心智为先,以删除错误表达和增加最小护栏为后。 diff --git a/roles/coordinator.md b/roles/coordinator.md index 11d1826..809add0 100644 --- a/roles/coordinator.md +++ b/roles/coordinator.md @@ -1,10 +1,10 @@ -# Coordinator / Spec Owner +# 小P:Coordinator / Spec Owner ## 角色接口 - **何时选择**:需求进入 Harness 后始终由小P承担;Spec 确认前负责需求收敛,确认后负责流程编排。 - **接收**:需求输入、必要 source、confirmed Spec、当前 Plan、各角色返回结果和当前进度。 -- **交付**:可确认的 Spec、清楚的 Task Brief、正确的结果路由、已同步回 Plan 的当前状态与最终收口。 +- **交付**:可确认的 Spec、清楚的 Task Brief、通过正式交接完成的结果路由、已同步回 Plan 的当前状态与最终收口。 - **交给谁**:按阶段交给秦鹏、Plan Writer、Implementer、Code Reviewer 或 Fix;所有结果最终返回小P消费。 ## 工作心智 @@ -13,9 +13,11 @@ Spec 确认前,我直接理解需求、读取必要 source、识别真正歧义并收敛 Spec。我可以委托调查,但不能转移 Spec ownership。 -Spec 确认后,我以 orchestrator 身份工作:依据 Spec、Plan 和当前进度派工,消费结果并推动下一步。我不根据群聊历史临场发明任务,也不替 worker 完成其主要职责。 +Spec 确认后,我以 orchestrator 身份工作:依据 Spec、Plan 和当前进度,通过真实 `at-bot` 派工。派发成功后我结束当前 Agent turn;目标 Bot 完成或 blocked 后用 `at-bot` 正式回传,形成我下一轮的输入。我不根据群聊历史临场发明任务,也不替 worker 完成其主要职责。 -每次消费 Implementer、Reviewer 或真人验证的结果后,我先依据对应完成条件和 gate 把状态同步回当前 Plan,再派发下一阶段。进度不能只留在群聊上下文中。 +每次消费 Implementer、Reviewer 或真人验证的正式结果后,我先依据对应完成条件和 gate 把状态同步回当前 Plan,再派发下一阶段。进度不能只留在群聊上下文中。 + +`at-bot sent` 只证明消息已发送,不保证目标 Bot 执行、完成或回传。流式卡片、过程消息和群内活动不是状态迁移依据;没有正式回传时,我不迁移完成状态、不轮询目标 Bot,也不自动重派。停滞只由秦鹏的新输入、明确的外部 gate 或另行确认的恢复动作处理。 ## 判断与边界 diff --git a/roles/plan-writer.md b/roles/plan-writer.md index 6c3e397..c6dca80 100644 --- a/roles/plan-writer.md +++ b/roles/plan-writer.md @@ -13,7 +13,7 @@ Plan 要让实现者知道先做什么、依赖什么、修改落在哪里、怎样验证和怎样交接。拆分以能够独立完成和判断结果为准,不为形式制造大量任务。 -Plan 还要让 Coordinator 能够持续回写真实进度。中、重任务的 Execution Unit 和必要 gate 应具有明确完成条件,并能用 Checkbox 或等价的简洁状态追踪;轻量任务不为此增加多余结构。 +Plan 还要让小P能够依据已确认的状态变化回写真实进度。中、重任务的 Execution Unit 和必要 gate 应具有明确完成条件,并能用 Checkbox 或等价的简洁状态追踪;轻量任务不为此增加多余结构。 ## 判断与边界 From a41dfd90c8359df52317b1203e67c2f25159f8e6 Mon Sep 17 00:00:00 2001 From: ClawWorld <4101513+KeepSilenceQP@users.noreply.github.com> Date: Thu, 23 Jul 2026 00:58:18 +0800 Subject: [PATCH 2/5] ci: validate Harness repository contracts --- .github/workflows/ci.yml | 30 +++++++++ AGENTS.md | 1 + CHANGELOG.md | 6 ++ README.md | 2 +- scripts/validate_repository.py | 119 +++++++++++++++++++++++++++++++++ 5 files changed, 157 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/ci.yml create mode 100644 scripts/validate_repository.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..e41c52d --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,30 @@ +name: CI + +on: + push: + pull_request: + workflow_dispatch: + +permissions: + contents: read + +# A push to an open PR can trigger both push and pull_request events. Keep only +# the newest run for the same commit. +concurrency: + group: ci-${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} + cancel-in-progress: true + +jobs: + validate: + name: Validate Harness repository + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Check whitespace + shell: bash + run: git diff --check "$(git hash-object -t tree /dev/null)" HEAD + + - name: Validate repository contracts + run: python3 scripts/validate_repository.py diff --git a/AGENTS.md b/AGENTS.md index 8073137..812cfbf 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -27,4 +27,5 @@ After structural or protocol changes, run: ```bash git diff --check +python3 scripts/validate_repository.py ``` diff --git a/CHANGELOG.md b/CHANGELOG.md index a782dab..7b22bcd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Repository CI - 2026-07-23 + +- 新增 push、pull request 和手动触发的 GitHub Actions CI,检查整个仓库快照的空白错误和 Harness 文档契约。 +- 新增零第三方依赖的仓库校验器,验证关键控制面文件、Markdown UTF-8/结尾换行、冲突标记、相对链接和静态 Bot open_id 泄漏。 +- 本地发布检查与 CI 使用同一校验入口,避免只在 GitHub 上发现文档结构问题。 + ## Event-driven orchestration mindset - 2026-07-23 - 在设计北极星中正向定义秦鹏、小P、云上C总、小C或云上小C的稳定身份:小P是中心编排者和状态所有者,云上C总是承担 Plan Writer 与 Code Reviewer 的架构师,实现 Bot 负责编码与自检。 diff --git a/README.md b/README.md index ea2c4ff..cb4d12b 100644 --- a/README.md +++ b/README.md @@ -48,4 +48,4 @@ workspace 已指定 + 实现方已从群内唯一解析 → 需求输入 → con ## 发布与回滚 -发布前更新 `CHANGELOG.md`,按北极星完成独立 Review,并运行 `git diff --check`。v2 发布前的可恢复基线是 Git revision `ab6bcbd`;涉及 Bot 接入问题时按 `foundation/feishu-bot-interop/Bot接入验收清单.md` 验收,不把基建修复混回 Harness 主协议。 +发布前更新 `CHANGELOG.md`,按北极星完成独立 Review,并运行 `git diff --check` 和 `python3 scripts/validate_repository.py`。GitHub Actions 会在 push 和 pull request 上执行同一组仓库契约检查。v2 发布前的可恢复基线是 Git revision `ab6bcbd`;涉及 Bot 接入问题时按 `foundation/feishu-bot-interop/Bot接入验收清单.md` 验收,不把基建修复混回 Harness 主协议。 diff --git a/scripts/validate_repository.py b/scripts/validate_repository.py new file mode 100644 index 0000000..3289e12 --- /dev/null +++ b/scripts/validate_repository.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python3 +"""Validate the dependency-free contracts of the sayToLittleP repository.""" + +from __future__ import annotations + +import re +import subprocess +import sys +from pathlib import Path +from urllib.parse import unquote + + +ROOT = Path(__file__).resolve().parents[1] + +REQUIRED_FILES = ( + "AGENTS.md", + "CHANGELOG.md", + "HARNESS.md", + "HARNESS_DESIGN_PRINCIPLES.md", + "README.md", + "roles/code-reviewer.md", + "roles/coordinator.md", + "roles/fix.md", + "roles/implementer.md", + "roles/plan-reviewer.md", + "roles/plan-writer.md", + "templates/task-brief.md", +) + +INLINE_LINK = re.compile(r"!?\[[^\]]*\]\(([^)]+)\)") +REFERENCE_LINK = re.compile(r"^\s*\[[^\]]+\]:\s*(\S+)", re.MULTILINE) +CONFLICT_MARKER = re.compile(r"^(<<<<<<<|=======|>>>>>>>)", re.MULTILINE) +STATIC_BOT_ID = re.compile(r"\bou_[A-Za-z0-9]{16,}\b") +EXTERNAL_SCHEMES = ("http://", "https://", "mailto:", "tel:", "data:") + + +def tracked_markdown_files() -> list[Path]: + result = subprocess.run( + ["git", "ls-files", "-z", "*.md"], + cwd=ROOT, + check=True, + capture_output=True, + ) + return [ROOT / item.decode() for item in result.stdout.split(b"\0") if item] + + +def link_target(raw_target: str) -> str: + target = raw_target.strip() + if target.startswith("<") and ">" in target: + return target[1 : target.index(">")] + return target.split(maxsplit=1)[0] + + +def validate_local_link(source: Path, raw_target: str) -> str | None: + target = link_target(raw_target) + if not target or target.startswith(("#", "/")) or target.lower().startswith(EXTERNAL_SCHEMES): + return None + + relative_target = unquote(target.split("#", 1)[0].split("?", 1)[0]) + if not relative_target: + return None + + destination = (source.parent / relative_target).resolve() + try: + destination.relative_to(ROOT) + except ValueError: + return f"local link escapes repository: {target}" + if not destination.exists(): + return f"broken local link: {target}" + return None + + +def main() -> int: + errors: list[str] = [] + + for relative_path in REQUIRED_FILES: + if not (ROOT / relative_path).is_file(): + errors.append(f"{relative_path}: required Harness file is missing") + + markdown_files = tracked_markdown_files() + if not markdown_files: + errors.append("repository contains no tracked Markdown files") + + for path in markdown_files: + relative_path = path.relative_to(ROOT) + try: + content = path.read_text(encoding="utf-8") + except UnicodeDecodeError as error: + errors.append(f"{relative_path}: invalid UTF-8 ({error})") + continue + + if not content.strip(): + errors.append(f"{relative_path}: Markdown file is empty") + if content and not content.endswith("\n"): + errors.append(f"{relative_path}: missing final newline") + if CONFLICT_MARKER.search(content): + errors.append(f"{relative_path}: unresolved merge conflict marker") + if STATIC_BOT_ID.search(content): + errors.append(f"{relative_path}: static Lark Bot open_id must not be committed") + + targets = [match.group(1) for match in INLINE_LINK.finditer(content)] + targets.extend(match.group(1) for match in REFERENCE_LINK.finditer(content)) + for target in targets: + error = validate_local_link(path, target) + if error: + errors.append(f"{relative_path}: {error}") + + if errors: + print("Repository validation failed:", file=sys.stderr) + for error in errors: + print(f"- {error}", file=sys.stderr) + return 1 + + print(f"Validated {len(markdown_files)} Markdown files and repository contracts.") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From 0650a1dab8c0351134d4bd56919815871fbb1f63 Mon Sep 17 00:00:00 2001 From: ClawWorld <4101513+KeepSilenceQP@users.noreply.github.com> Date: Thu, 23 Jul 2026 00:59:15 +0800 Subject: [PATCH 3/5] ci: avoid duplicate pull request runs --- .github/workflows/ci.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e41c52d..f7d4903 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,16 +2,17 @@ name: CI on: push: + branches: + - main pull_request: workflow_dispatch: permissions: contents: read -# A push to an open PR can trigger both push and pull_request events. Keep only -# the newest run for the same commit. +# Keep only the newest run for a branch or pull request. concurrency: - group: ci-${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} + group: ci-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true jobs: From 92c03a61c656ab8dbfdfd500e046378b3c28c0db Mon Sep 17 00:00:00 2001 From: ClawWorld <4101513+KeepSilenceQP@users.noreply.github.com> Date: Thu, 23 Jul 2026 01:05:51 +0800 Subject: [PATCH 4/5] ci: add layered Harness quality gates --- .github/workflows/ci.yml | 61 +- .gitignore | 1 + .markdownlint-cli2.jsonc | 13 + AGENTS.md | 3 +- CHANGELOG.md | 8 +- README.md | 2 +- ...14\346\224\266\346\270\205\345\215\225.md" | 4 +- package-lock.json | 1368 +++++++++++++++++ package.json | 15 + scripts/validate_repository.py | 65 +- tests/test_validate_repository.py | 47 + 11 files changed, 1572 insertions(+), 15 deletions(-) create mode 100644 .markdownlint-cli2.jsonc create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 tests/test_validate_repository.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f7d4903..3d52cba 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,22 +10,69 @@ on: permissions: contents: read -# Keep only the newest run for a branch or pull request. concurrency: group: ci-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true jobs: - validate: - name: Validate Harness repository + markdown: + name: Markdown lint runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - - name: Check whitespace + - name: Setup Node.js + uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6 + with: + node-version: 24 + cache: npm + + - name: Install locked dependencies + run: npm ci + + - name: Lint Markdown + run: npm run lint:markdown + + contracts: + name: Harness contract tests + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + + - name: Setup Python + uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 + with: + python-version: "3.13" + + - name: Check whitespace across the repository snapshot shell: bash run: git diff --check "$(git hash-object -t tree /dev/null)" HEAD - - name: Validate repository contracts - run: python3 scripts/validate_repository.py + - name: Test the contract validator + run: python -m unittest discover -s tests -p "test_*.py" -v + + - name: Validate Harness repository contracts + run: python scripts/validate_repository.py + + workflows: + name: GitHub Actions lint + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + + - name: Install actionlint 1.7.12 with checksum verification + shell: bash + run: | + archive="$RUNNER_TEMP/actionlint.tar.gz" + curl --fail --silent --show-error --location \ + --output "$archive" \ + https://github.com/rhysd/actionlint/releases/download/v1.7.12/actionlint_1.7.12_linux_amd64.tar.gz + echo "8aca8db96f1b94770f1b0d72b6dddcb1ebb8123cb3712530b08cc387b349a3d8 $archive" \ + | sha256sum --check + tar --extract --gzip --file "$archive" --directory "$RUNNER_TEMP" actionlint + + - name: Lint GitHub Actions workflows + run: "$RUNNER_TEMP/actionlint" diff --git a/.gitignore b/.gitignore index e43b0f9..646ac51 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ .DS_Store +node_modules/ diff --git a/.markdownlint-cli2.jsonc b/.markdownlint-cli2.jsonc new file mode 100644 index 0000000..c968cd8 --- /dev/null +++ b/.markdownlint-cli2.jsonc @@ -0,0 +1,13 @@ +{ + "globs": ["**/*.md"], + "gitignore": true, + "frontMatter": "^---\\s*\\r?\\n[^]*?\\r?\\n---\\s*(?:\\r?\\n|$)", + "config": { + // Chinese prose is intentionally written as semantic paragraphs rather + // than hard-wrapped at an arbitrary Latin-character column. + "MD013": false, + // Plan front matter has a metadata `title` plus one rendered H1. MD025 + // counts both even though readers see only one document title. + "MD025": false + } +} diff --git a/AGENTS.md b/AGENTS.md index 812cfbf..41da4ce 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -27,5 +27,6 @@ After structural or protocol changes, run: ```bash git diff --check -python3 scripts/validate_repository.py +npm ci +npm run ci ``` diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b22bcd..ad2304b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,11 @@ ## Repository CI - 2026-07-23 -- 新增 push、pull request 和手动触发的 GitHub Actions CI,检查整个仓库快照的空白错误和 Harness 文档契约。 -- 新增零第三方依赖的仓库校验器,验证关键控制面文件、Markdown UTF-8/结尾换行、冲突标记、相对链接和静态 Bot open_id 泄漏。 -- 本地发布检查与 CI 使用同一校验入口,避免只在 GitHub 上发现文档结构问题。 +- 新增 pull request、`main` push 和手动触发的 GitHub Actions CI,提供 Markdown、Harness contract 和 Workflow 三个独立 gate。 +- 使用锁定版本的 `markdownlint-cli2` 检查标准 Markdown 规则;仅针对中文段落换行和 Plan front matter 关闭不适用规则。 +- 使用带单元测试的仓库校验器验证关键控制面文件、Markdown UTF-8/结尾换行、冲突标记、相对链接、角色卡/Task Brief/Harness 结构和静态 Bot open_id 泄漏。 +- 使用校验和固定的 `actionlint` 检查 GitHub Actions 语义,并将官方 Actions 固定到完整 commit SHA。 +- 本地发布检查与 CI 使用相同的 Markdown 和 Harness contract 入口,避免只在 GitHub 上发现结构问题。 ## Event-driven orchestration mindset - 2026-07-23 diff --git a/README.md b/README.md index cb4d12b..4478268 100644 --- a/README.md +++ b/README.md @@ -48,4 +48,4 @@ workspace 已指定 + 实现方已从群内唯一解析 → 需求输入 → con ## 发布与回滚 -发布前更新 `CHANGELOG.md`,按北极星完成独立 Review,并运行 `git diff --check` 和 `python3 scripts/validate_repository.py`。GitHub Actions 会在 push 和 pull request 上执行同一组仓库契约检查。v2 发布前的可恢复基线是 Git revision `ab6bcbd`;涉及 Bot 接入问题时按 `foundation/feishu-bot-interop/Bot接入验收清单.md` 验收,不把基建修复混回 Harness 主协议。 +发布前更新 `CHANGELOG.md`,按北极星完成独立 Review,并运行 `git diff --check`、`npm ci` 和 `npm run ci`。GitHub Actions 会对 Markdown、Harness 结构契约和 Workflow 本身分别给出独立 gate。v2 发布前的可恢复基线是 Git revision `ab6bcbd`;涉及 Bot 接入问题时按 `foundation/feishu-bot-interop/Bot接入验收清单.md` 验收,不把基建修复混回 Harness 主协议。 diff --git "a/foundation/feishu-bot-interop/Bot\346\216\245\345\205\245\351\252\214\346\224\266\346\270\205\345\215\225.md" "b/foundation/feishu-bot-interop/Bot\346\216\245\345\205\245\351\252\214\346\224\266\346\270\205\345\215\225.md" index ac2167d..240fdee 100644 --- "a/foundation/feishu-bot-interop/Bot\346\216\245\345\205\245\351\252\214\346\224\266\346\270\205\345\215\225.md" +++ "b/foundation/feishu-bot-interop/Bot\346\216\245\345\205\245\351\252\214\346\224\266\346\270\205\345\215\225.md" @@ -37,13 +37,13 @@ Hermes 或 OpenClaw 若尚未在目标运行时确认该字段,直接判定本 以下两个证据分支二选一,并把另一个分支的字段标为 `N/A`: -**A. 目标回复分支** +### A. 目标回复分支 - [ ] 目标 Bot 回复对象的 `sender.id` 和 `sender.id_type` 与目标 Bot 的已验证身份映射一致。 - [ ] 回复通过 `reply_to`、`parent_id`、`root_id`、`thread_id` 或引用关系关联到本次出站消息。 - [ ] 若平台不保留回复关系,目标回复原样带回唯一验收串,并记录这一限制。 -**B. 目标运行时日志分支** +### B. 目标运行时日志分支 - [ ] 目标 Bot 不应或无法回复时,用目标运行时的收件事件或日志证明它收到本次 `message_id` 和验收串,并证明该运行时身份就是目标 Bot。 diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..1a7a9ae --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1368 @@ +{ + "name": "say-to-little-p-harness", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "say-to-little-p-harness", + "devDependencies": { + "markdownlint-cli2": "0.23.1" + }, + "engines": { + "node": ">=24" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@types/debug": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/katex": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.8.tgz", + "integrity": "sha512-trgaNyfU+Xh2Tc+ABIb44a5AYUpicB3uwirOioeOkNPPbmgRNtcWyDeeFRzjPZENO9Vq8gvVqfhaaXWLlevVwg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-named-character-reference": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globby": { + "version": "16.2.1", + "resolved": "https://registry.npmjs.org/globby/-/globby-16.2.1.tgz", + "integrity": "sha512-JmsqJalahxxgW8V2ecSQ2G7UjPlI9cpKdrkG9KoNiXhd/YslXOTEB0cViENWUznuovIuNT+FkMbraDGjr4FCUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^4.0.0", + "fast-glob": "^3.3.3", + "ignore": "^7.0.5", + "is-path-inside": "^4.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.4.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ignore": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", + "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-4.0.0.tgz", + "integrity": "sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/js-yaml": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-5.2.1.tgz", + "integrity": "sha512-zfLtNfQqxVqq3uaTqSkh4x4hZw3KHobGUA0fJUj4wawW8bsQLTVqpHdXSIzidh7o+4lEW36tANuAGdaFx6Zgnw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.mjs" + } + }, + "node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsonpointer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", + "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/katex": { + "version": "0.16.47", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.47.tgz", + "integrity": "sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==", + "dev": true, + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "license": "MIT", + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, + "node_modules/linkify-it": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.2.tgz", + "integrity": "sha512-ONTm2jCMAVZjgQa/Fy1kScXsuOoF5NPTsoFBdE1KVIZ2vAh/r9+Bqo+0jINCBYnavTPQZz38QzFTme79ENoN3Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/markdown-it" + } + ], + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, + "node_modules/markdown-it": { + "version": "14.3.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.3.0.tgz", + "integrity": "sha512-RCEsPjR+sr0x+AuYp601tKTkgFG4YEPLCzHST3cQ/fhlJkqAkz1L2/Qbp1j9qw5SBwQHFBoW8+hoN5xssOF0Tw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/markdown-it" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.5.0", + "linkify-it": "^5.0.2", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/markdownlint": { + "version": "0.41.1", + "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.41.1.tgz", + "integrity": "sha512-qHKeU2E1bdyNAT077go2FVTNXvYcktN5IHtF6XyeD1l0PClxzSp2tUApAV14ORI8DGX4H9bNKZEzelZp4qn8IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "micromark": "4.0.2", + "micromark-core-commonmark": "2.0.3", + "micromark-extension-directive": "4.0.0", + "micromark-extension-gfm-autolink-literal": "2.1.0", + "micromark-extension-gfm-footnote": "2.1.0", + "micromark-extension-gfm-table": "2.1.1", + "micromark-extension-math": "3.1.0", + "micromark-util-types": "2.0.2", + "string-width": "8.2.1" + }, + "engines": { + "node": ">=22" + }, + "funding": { + "url": "https://github.com/sponsors/DavidAnson" + } + }, + "node_modules/markdownlint-cli2": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/markdownlint-cli2/-/markdownlint-cli2-0.23.1.tgz", + "integrity": "sha512-20JPI5W+HpV1OA+pUM712wgvL4GzYNUvbmhLU8KlEYJ1kCDx4soZ4/Xqd+WkLrPTOKMAn8SfO3zYFrK8GLlwQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "globby": "16.2.1", + "js-yaml": "5.2.1", + "jsonc-parser": "3.3.1", + "jsonpointer": "5.0.1", + "markdown-it": "14.3.0", + "markdownlint": "0.41.1", + "markdownlint-cli2-formatter-default": "0.0.6", + "micromatch": "4.0.8", + "smol-toml": "1.7.0" + }, + "bin": { + "markdownlint-cli2": "markdownlint-cli2-bin.mjs" + }, + "engines": { + "node": ">=22" + }, + "funding": { + "url": "https://github.com/sponsors/DavidAnson" + } + }, + "node_modules/markdownlint-cli2-formatter-default": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/markdownlint-cli2-formatter-default/-/markdownlint-cli2-formatter-default-0.0.6.tgz", + "integrity": "sha512-VVDGKsq9sgzu378swJ0fcHfSicUnMxnL8gnLm/Q4J/xsNJ4e5bA6lvAz7PCzIl0/No0lHyaWdqVD2jotxOSFMQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/DavidAnson" + }, + "peerDependencies": { + "markdownlint-cli2": ">=0.0.4" + } + }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "dev": true, + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-directive": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-4.0.0.tgz", + "integrity": "sha512-/C2nqVmXXmiseSSuCdItCMho7ybwwop6RrrRPk0KbOHW21JKoCldC+8rFOaundDoRBUWBnJJcxeA/Kvi34WQXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "parse-entities": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "dev": true, + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-math": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-math/-/micromark-extension-math-3.1.0.tgz", + "integrity": "sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/katex": "^0.16.0", + "devlop": "^1.0.0", + "katex": "^0.16.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/smol-toml": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.7.0.tgz", + "integrity": "sha512-aqVvWoyO21L23mb+drl4RmMXbf6N7FdHjAhTRA9ZBL7apWBgfWC16KjrASI+1p9GAroljyMHj6fK67i0UiTNvQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 18" + }, + "funding": { + "url": "https://github.com/sponsors/cyyynthia" + } + }, + "node_modules/string-width": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.1.tgz", + "integrity": "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "dev": true, + "license": "MIT" + }, + "node_modules/unicorn-magic": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.4.0.tgz", + "integrity": "sha512-wH590V9VNgYH9g3lH9wWjTrUoKsjLF6sGLjhR4sH1LWpLmCOH0Zf7PukhDA8BiS7KHe4oPNkcTHqYkj7SOGUOw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..cbb7cd4 --- /dev/null +++ b/package.json @@ -0,0 +1,15 @@ +{ + "name": "say-to-little-p-harness", + "private": true, + "engines": { + "node": ">=24" + }, + "scripts": { + "lint:markdown": "markdownlint-cli2", + "test:contracts": "python3 -m unittest discover -s tests -p 'test_*.py' -v && python3 scripts/validate_repository.py", + "ci": "npm run lint:markdown && npm run test:contracts" + }, + "devDependencies": { + "markdownlint-cli2": "0.23.1" + } +} diff --git a/scripts/validate_repository.py b/scripts/validate_repository.py index 3289e12..ece3dca 100644 --- a/scripts/validate_repository.py +++ b/scripts/validate_repository.py @@ -13,20 +13,54 @@ ROOT = Path(__file__).resolve().parents[1] REQUIRED_FILES = ( + ".github/workflows/ci.yml", + ".markdownlint-cli2.jsonc", "AGENTS.md", "CHANGELOG.md", "HARNESS.md", "HARNESS_DESIGN_PRINCIPLES.md", "README.md", + "package-lock.json", + "package.json", "roles/code-reviewer.md", "roles/coordinator.md", "roles/fix.md", "roles/implementer.md", "roles/plan-reviewer.md", "roles/plan-writer.md", + "scripts/validate_repository.py", "templates/task-brief.md", ) +ROLE_CARD_HEADINGS = ( + "## 角色接口", + "## 工作心智", + "## 判断与边界", +) + +HARNESS_HEADINGS = tuple(f"## {index}. {title}" for index, title in enumerate(( + "权威与目标", + "角色", + "标准流程", + "流程深度", + "Task Brief", + "最小进度与 blocked", + "多 Bot 运行不变量", + "执行资产", + "验证、提交与关闭", +), start=1)) + +TASK_BRIEF_FIELDS = ( + "Role:", + "Goal:", + "Relevant Spec facts:", + "Current Plan step:", + "Scope / boundaries:", + "Available context:", + "Expected result and verification:", + "Return to:<目标名> (Bot)", +) + INLINE_LINK = re.compile(r"!?\[[^\]]*\]\(([^)]+)\)") REFERENCE_LINK = re.compile(r"^\s*\[[^\]]+\]:\s*(\S+)", re.MULTILINE) CONFLICT_MARKER = re.compile(r"^(<<<<<<<|=======|>>>>>>>)", re.MULTILINE) @@ -70,6 +104,20 @@ def validate_local_link(source: Path, raw_target: str) -> str | None: return None +def validate_ordered_markers(label: str, content: str, markers: tuple[str, ...]) -> list[str]: + errors: list[str] = [] + positions: list[int] = [] + for marker in markers: + position = content.find(marker) + if position < 0: + errors.append(f"{label}: required marker is missing: {marker}") + else: + positions.append(position) + if not errors and positions != sorted(positions): + errors.append(f"{label}: required markers are out of order") + return errors + + def main() -> int: errors: list[str] = [] @@ -105,13 +153,28 @@ def main() -> int: if error: errors.append(f"{relative_path}: {error}") + for role_path in sorted((ROOT / "roles").glob("*.md")): + role_content = role_path.read_text(encoding="utf-8") + errors.extend(validate_ordered_markers(str(role_path.relative_to(ROOT)), role_content, ROLE_CARD_HEADINGS)) + + harness_content = (ROOT / "HARNESS.md").read_text(encoding="utf-8") + errors.extend(validate_ordered_markers("HARNESS.md", harness_content, HARNESS_HEADINGS)) + + task_brief_content = (ROOT / "templates/task-brief.md").read_text(encoding="utf-8") + errors.extend(validate_ordered_markers("templates/task-brief.md", task_brief_content, TASK_BRIEF_FIELDS)) + if "lark-channel-bridge at-bot" not in task_brief_content: + errors.append("templates/task-brief.md: required at-bot return contract is missing") + if errors: print("Repository validation failed:", file=sys.stderr) for error in errors: print(f"- {error}", file=sys.stderr) return 1 - print(f"Validated {len(markdown_files)} Markdown files and repository contracts.") + print( + f"Validated {len(markdown_files)} Markdown files, " + f"{len(tuple((ROOT / 'roles').glob('*.md')))} role cards, and Harness contracts." + ) return 0 diff --git a/tests/test_validate_repository.py b/tests/test_validate_repository.py new file mode 100644 index 0000000..f2ef617 --- /dev/null +++ b/tests/test_validate_repository.py @@ -0,0 +1,47 @@ +from __future__ import annotations + +import unittest + +from scripts.validate_repository import ( + ROOT, + STATIC_BOT_ID, + link_target, + validate_local_link, + validate_ordered_markers, +) + + +class LinkValidationTests(unittest.TestCase): + def test_existing_relative_link_passes(self) -> None: + self.assertIsNone(validate_local_link(ROOT / "README.md", "HARNESS.md")) + + def test_missing_relative_link_fails(self) -> None: + self.assertEqual( + validate_local_link(ROOT / "README.md", "missing.md"), + "broken local link: missing.md", + ) + + def test_link_cannot_escape_repository(self) -> None: + self.assertEqual( + validate_local_link(ROOT / "README.md", "../../outside.md"), + "local link escapes repository: ../../outside.md", + ) + + def test_markdown_link_titles_and_angle_brackets_are_parsed(self) -> None: + self.assertEqual(link_target('HARNESS.md "Harness"'), "HARNESS.md") + self.assertEqual(link_target(""), "docs/a file.md") + + +class ContractPrimitiveTests(unittest.TestCase): + def test_ordered_markers_detect_missing_and_reordered_contracts(self) -> None: + self.assertEqual(validate_ordered_markers("x", "A B C", ("A", "B", "C")), []) + self.assertTrue(validate_ordered_markers("x", "A C B", ("A", "B", "C"))) + self.assertTrue(validate_ordered_markers("x", "A B", ("A", "B", "C"))) + + def test_static_bot_id_guard_ignores_placeholder_but_catches_real_shape(self) -> None: + self.assertIsNone(STATIC_BOT_ID.search("ou_xxx")) + self.assertIsNotNone(STATIC_BOT_ID.search("ou_0123456789abcdef0123456789abcdef")) + + +if __name__ == "__main__": + unittest.main() From 547044956b59a3ebf970fb4d7a91437cf11a9e64 Mon Sep 17 00:00:00 2001 From: ClawWorld <4101513+KeepSilenceQP@users.noreply.github.com> Date: Thu, 23 Jul 2026 01:06:43 +0800 Subject: [PATCH 5/5] ci: preserve actionlint command quoting --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3d52cba..47eba37 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -75,4 +75,5 @@ jobs: tar --extract --gzip --file "$archive" --directory "$RUNNER_TEMP" actionlint - name: Lint GitHub Actions workflows - run: "$RUNNER_TEMP/actionlint" + run: | + "$RUNNER_TEMP/actionlint"