From 8afeb2e4d92bce7b2237e7dd4cb3d2f03ee6da7d Mon Sep 17 00:00:00 2001 From: chenqi Date: Thu, 18 Jun 2026 21:29:41 +0800 Subject: [PATCH] =?UTF-8?q?fix(security):=20=E6=B7=BB=E5=8A=A0VITE=5FPAYME?= =?UTF-8?q?NT=5FURL=E7=8E=AF=E5=A2=83=E5=8F=98=E9=87=8F=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .qoder/agents/chenlin.md | 27 + .qoder/agents/guanyu.md | 27 + .qoder/agents/huatuo.md | 25 + .qoder/agents/liubei.md | 26 + .qoder/agents/xunyu.md | 25 + .qoder/agents/zhangfei.md | 25 + .qoder/agents/zhaoyun.md | 25 + .qoder/agents/zhugeliang.md | 26 + .qoder/rules/IRON_LAWS.md | 377 ++++ .qoder/rules/backend.md | 46 + .qoder/rules/database.md | 48 + .qoder/rules/debugging.md | 46 + .qoder/rules/frontend.md | 53 + .qoder/rules/testing.md | 49 + .qoder/settings.json | 20 + .qoder/settings.local.json | 13 + .qoder/skills/agentforge-analyze/SKILL.md | 27 + .qoder/skills/agentforge-archive/SKILL.md | 31 + .qoder/skills/agentforge-db-review/SKILL.md | 29 + .qoder/skills/agentforge-fix/SKILL.md | 50 + .qoder/skills/agentforge-test/SKILL.md | 28 + .qoder/skills/agentforge-verify/SKILL.md | 30 + .qoder/skills/brainstorming/SKILL.md | 164 ++ .../brainstorming/scripts/frame-template.html | 214 +++ .qoder/skills/brainstorming/scripts/helper.js | 88 + .../skills/brainstorming/scripts/server.cjs | 354 ++++ .../brainstorming/scripts/start-server.sh | 148 ++ .../brainstorming/scripts/stop-server.sh | 56 + .../spec-document-reviewer-prompt.md | 49 + .../skills/brainstorming/visual-companion.md | 287 +++ .qoder/skills/bug-driven-testing/SKILL.md | 210 +++ .qoder/skills/check-status/SKILL.md | 73 + .qoder/skills/closed-loop-testing/SKILL.md | 84 + .../closed-loop-testing/agents/openai.yaml | 3 + .qoder/skills/constraint-design/SKILL.md | 113 ++ .../constraint-design/agents/openai.yaml | 3 + .../dispatching-parallel-agents/SKILL.md | 182 ++ .qoder/skills/durable-execution/SKILL.md | 119 ++ .../durable-execution/agents/openai.yaml | 3 + .qoder/skills/executing-plans/SKILL.md | 70 + .../finishing-a-development-branch/SKILL.md | 251 +++ .qoder/skills/fix-compile/SKILL.md | 43 + .qoder/skills/full-chain-fix/SKILL.md | 30 + .qoder/skills/harness-engineering/SKILL.md | 175 ++ .../harness-engineering/agents/openai.yaml | 3 + .qoder/skills/karpathy-guidelines/SKILL.md | 67 + .qoder/skills/receiving-code-review/SKILL.md | 213 +++ .qoder/skills/requesting-code-review/SKILL.md | 103 ++ .../requesting-code-review/code-reviewer.md | 168 ++ .qoder/skills/review-audit/SKILL.md | 109 ++ .qoder/skills/review-audit/agents/openai.yaml | 3 + .../subagent-driven-development/SKILL.md | 279 +++ .../code-quality-reviewer-prompt.md | 25 + .../implementer-prompt.md | 113 ++ .../spec-reviewer-prompt.md | 61 + .../systematic-debugging/CREATION-LOG.md | 119 ++ .qoder/skills/systematic-debugging/SKILL.md | 296 ++++ .../condition-based-waiting-example.ts | 158 ++ .../condition-based-waiting.md | 115 ++ .../systematic-debugging/defense-in-depth.md | 122 ++ .../systematic-debugging/find-polluter.sh | 63 + .../root-cause-tracing.md | 169 ++ .../systematic-debugging/test-academic.md | 14 + .../systematic-debugging/test-pressure-1.md | 58 + .../systematic-debugging/test-pressure-2.md | 68 + .../systematic-debugging/test-pressure-3.md | 69 + .../skills/test-driven-development/SKILL.md | 371 ++++ .../testing-anti-patterns.md | 299 ++++ .qoder/skills/understand-chat/SKILL.md | 55 + .qoder/skills/understand-dashboard/SKILL.md | 105 ++ .qoder/skills/understand-diff/SKILL.md | 72 + .qoder/skills/understand-domain/SKILL.md | 140 ++ .../extract-domain-context.py | 428 +++++ .qoder/skills/understand-explain/SKILL.md | 58 + .qoder/skills/understand-knowledge/SKILL.md | 132 ++ .../merge-knowledge-graph.py | 397 +++++ .../parse-knowledge-base.py | 509 ++++++ .qoder/skills/understand-onboard/SKILL.md | 55 + .qoder/skills/understand/SKILL.md | 851 +++++++++ .../skills/understand/build-fingerprints.mjs | 90 + .qoder/skills/understand/compute-batches.mjs | 555 ++++++ .../skills/understand/extract-import-map.mjs | 1567 +++++++++++++++++ .../skills/understand/extract-structure.mjs | 334 ++++ .qoder/skills/understand/frameworks/django.md | 67 + .../skills/understand/frameworks/express.md | 57 + .../skills/understand/frameworks/fastapi.md | 58 + .qoder/skills/understand/frameworks/flask.md | 53 + .qoder/skills/understand/frameworks/gin.md | 59 + .qoder/skills/understand/frameworks/nextjs.md | 59 + .qoder/skills/understand/frameworks/rails.md | 65 + .qoder/skills/understand/frameworks/react.md | 55 + .qoder/skills/understand/frameworks/spring.md | 59 + .qoder/skills/understand/frameworks/vue.md | 59 + .qoder/skills/understand/languages/cpp.md | 47 + .qoder/skills/understand/languages/csharp.md | 46 + .qoder/skills/understand/languages/css.md | 37 + .../skills/understand/languages/dockerfile.md | 34 + .qoder/skills/understand/languages/go.md | 47 + .qoder/skills/understand/languages/graphql.md | 35 + .qoder/skills/understand/languages/html.md | 34 + .qoder/skills/understand/languages/java.md | 45 + .../skills/understand/languages/javascript.md | 46 + .qoder/skills/understand/languages/json.md | 34 + .qoder/skills/understand/languages/kotlin.md | 45 + .../skills/understand/languages/markdown.md | 34 + .qoder/skills/understand/languages/php.md | 46 + .../skills/understand/languages/protobuf.md | 34 + .qoder/skills/understand/languages/python.md | 48 + .qoder/skills/understand/languages/ruby.md | 46 + .qoder/skills/understand/languages/rust.md | 47 + .qoder/skills/understand/languages/shell.md | 35 + .qoder/skills/understand/languages/sql.md | 36 + .qoder/skills/understand/languages/swift.md | 46 + .../skills/understand/languages/terraform.md | 38 + .../skills/understand/languages/typescript.md | 46 + .qoder/skills/understand/languages/yaml.md | 35 + .qoder/skills/understand/locales/en.md | 44 + .qoder/skills/understand/locales/ja.md | 49 + .qoder/skills/understand/locales/ko.md | 49 + .qoder/skills/understand/locales/ru.md | 49 + .qoder/skills/understand/locales/zh-TW.md | 49 + .qoder/skills/understand/locales/zh.md | 49 + .../skills/understand/merge-batch-graphs.py | 1164 ++++++++++++ .../understand/merge-subdomain-graphs.py | 308 ++++ .qoder/skills/understand/scan-project.mjs | 802 +++++++++ .qoder/skills/using-git-worktrees/SKILL.md | 215 +++ .qoder/skills/using-superpowers/SKILL.md | 117 ++ .../references/codex-tools.md | 59 + .../references/copilot-tools.md | 42 + .../references/gemini-tools.md | 51 + .../verification-before-completion/SKILL.md | 139 ++ .qoder/skills/verify/SKILL.md | 40 + .qoder/skills/walkinglabs-harness/SKILL.md | 142 ++ .../walkinglabs-harness/agents/openai.yaml | 3 + .qoder/skills/writing-plans/SKILL.md | 152 ++ .../plan-document-reviewer-prompt.md | 49 + .qoder/skills/writing-skills/SKILL.md | 655 +++++++ .../anthropic-best-practices.md | 1150 ++++++++++++ .../examples/CLAUDE_MD_TESTING.md | 189 ++ .../writing-skills/graphviz-conventions.dot | 172 ++ .../writing-skills/persuasion-principles.md | 187 ++ .qoder/skills/writing-skills/render-graphs.js | 168 ++ .../testing-skills-with-subagents.md | 384 ++++ .qoder/skills/zentao/.env | 9 + .qoder/skills/zentao/.env.example | 9 + .qoder/skills/zentao/.session | 1 + .qoder/skills/zentao/SKILL.md | 146 ++ .qoder/skills/zentao/_bug681.json | 1101 ++++++++++++ .qoder/skills/zentao/audit.log | 43 + .../zentao/images/681/bug681_file2429.png | Bin 0 -> 178323 bytes .../zentao/images/772/bug772_file2278.png | Bin 0 -> 117586 bytes .../zentao/images/772/bug772_file2280.png | Bin 0 -> 144225 bytes .../zentao/images/772/bug772_file2281.png | Bin 0 -> 255053 bytes .../zentao/images/772/bug772_file2282.png | Bin 0 -> 141386 bytes .qoder/skills/zentao/scripts/zentao_client.py | 360 ++++ healthlink-his-ui/.env.dev | 6 + healthlink-his-ui/.env.development | 6 + healthlink-his-ui/.env.prod | 6 + healthlink-his-ui/.env.spug | 6 + healthlink-his-ui/.env.staging | 6 + 160 files changed, 21893 insertions(+) create mode 100644 .qoder/agents/chenlin.md create mode 100644 .qoder/agents/guanyu.md create mode 100644 .qoder/agents/huatuo.md create mode 100644 .qoder/agents/liubei.md create mode 100644 .qoder/agents/xunyu.md create mode 100644 .qoder/agents/zhangfei.md create mode 100644 .qoder/agents/zhaoyun.md create mode 100644 .qoder/agents/zhugeliang.md create mode 100644 .qoder/rules/IRON_LAWS.md create mode 100644 .qoder/rules/backend.md create mode 100644 .qoder/rules/database.md create mode 100644 .qoder/rules/debugging.md create mode 100644 .qoder/rules/frontend.md create mode 100644 .qoder/rules/testing.md create mode 100644 .qoder/settings.json create mode 100644 .qoder/settings.local.json create mode 100644 .qoder/skills/agentforge-analyze/SKILL.md create mode 100644 .qoder/skills/agentforge-archive/SKILL.md create mode 100644 .qoder/skills/agentforge-db-review/SKILL.md create mode 100644 .qoder/skills/agentforge-fix/SKILL.md create mode 100644 .qoder/skills/agentforge-test/SKILL.md create mode 100644 .qoder/skills/agentforge-verify/SKILL.md create mode 100644 .qoder/skills/brainstorming/SKILL.md create mode 100644 .qoder/skills/brainstorming/scripts/frame-template.html create mode 100644 .qoder/skills/brainstorming/scripts/helper.js create mode 100644 .qoder/skills/brainstorming/scripts/server.cjs create mode 100644 .qoder/skills/brainstorming/scripts/start-server.sh create mode 100644 .qoder/skills/brainstorming/scripts/stop-server.sh create mode 100644 .qoder/skills/brainstorming/spec-document-reviewer-prompt.md create mode 100644 .qoder/skills/brainstorming/visual-companion.md create mode 100644 .qoder/skills/bug-driven-testing/SKILL.md create mode 100644 .qoder/skills/check-status/SKILL.md create mode 100644 .qoder/skills/closed-loop-testing/SKILL.md create mode 100644 .qoder/skills/closed-loop-testing/agents/openai.yaml create mode 100644 .qoder/skills/constraint-design/SKILL.md create mode 100644 .qoder/skills/constraint-design/agents/openai.yaml create mode 100644 .qoder/skills/dispatching-parallel-agents/SKILL.md create mode 100644 .qoder/skills/durable-execution/SKILL.md create mode 100644 .qoder/skills/durable-execution/agents/openai.yaml create mode 100644 .qoder/skills/executing-plans/SKILL.md create mode 100644 .qoder/skills/finishing-a-development-branch/SKILL.md create mode 100644 .qoder/skills/fix-compile/SKILL.md create mode 100644 .qoder/skills/full-chain-fix/SKILL.md create mode 100644 .qoder/skills/harness-engineering/SKILL.md create mode 100644 .qoder/skills/harness-engineering/agents/openai.yaml create mode 100644 .qoder/skills/karpathy-guidelines/SKILL.md create mode 100644 .qoder/skills/receiving-code-review/SKILL.md create mode 100644 .qoder/skills/requesting-code-review/SKILL.md create mode 100644 .qoder/skills/requesting-code-review/code-reviewer.md create mode 100644 .qoder/skills/review-audit/SKILL.md create mode 100644 .qoder/skills/review-audit/agents/openai.yaml create mode 100644 .qoder/skills/subagent-driven-development/SKILL.md create mode 100644 .qoder/skills/subagent-driven-development/code-quality-reviewer-prompt.md create mode 100644 .qoder/skills/subagent-driven-development/implementer-prompt.md create mode 100644 .qoder/skills/subagent-driven-development/spec-reviewer-prompt.md create mode 100644 .qoder/skills/systematic-debugging/CREATION-LOG.md create mode 100644 .qoder/skills/systematic-debugging/SKILL.md create mode 100644 .qoder/skills/systematic-debugging/condition-based-waiting-example.ts create mode 100644 .qoder/skills/systematic-debugging/condition-based-waiting.md create mode 100644 .qoder/skills/systematic-debugging/defense-in-depth.md create mode 100644 .qoder/skills/systematic-debugging/find-polluter.sh create mode 100644 .qoder/skills/systematic-debugging/root-cause-tracing.md create mode 100644 .qoder/skills/systematic-debugging/test-academic.md create mode 100644 .qoder/skills/systematic-debugging/test-pressure-1.md create mode 100644 .qoder/skills/systematic-debugging/test-pressure-2.md create mode 100644 .qoder/skills/systematic-debugging/test-pressure-3.md create mode 100644 .qoder/skills/test-driven-development/SKILL.md create mode 100644 .qoder/skills/test-driven-development/testing-anti-patterns.md create mode 100644 .qoder/skills/understand-chat/SKILL.md create mode 100644 .qoder/skills/understand-dashboard/SKILL.md create mode 100644 .qoder/skills/understand-diff/SKILL.md create mode 100644 .qoder/skills/understand-domain/SKILL.md create mode 100644 .qoder/skills/understand-domain/extract-domain-context.py create mode 100644 .qoder/skills/understand-explain/SKILL.md create mode 100644 .qoder/skills/understand-knowledge/SKILL.md create mode 100644 .qoder/skills/understand-knowledge/merge-knowledge-graph.py create mode 100644 .qoder/skills/understand-knowledge/parse-knowledge-base.py create mode 100644 .qoder/skills/understand-onboard/SKILL.md create mode 100644 .qoder/skills/understand/SKILL.md create mode 100644 .qoder/skills/understand/build-fingerprints.mjs create mode 100644 .qoder/skills/understand/compute-batches.mjs create mode 100644 .qoder/skills/understand/extract-import-map.mjs create mode 100644 .qoder/skills/understand/extract-structure.mjs create mode 100644 .qoder/skills/understand/frameworks/django.md create mode 100644 .qoder/skills/understand/frameworks/express.md create mode 100644 .qoder/skills/understand/frameworks/fastapi.md create mode 100644 .qoder/skills/understand/frameworks/flask.md create mode 100644 .qoder/skills/understand/frameworks/gin.md create mode 100644 .qoder/skills/understand/frameworks/nextjs.md create mode 100644 .qoder/skills/understand/frameworks/rails.md create mode 100644 .qoder/skills/understand/frameworks/react.md create mode 100644 .qoder/skills/understand/frameworks/spring.md create mode 100644 .qoder/skills/understand/frameworks/vue.md create mode 100644 .qoder/skills/understand/languages/cpp.md create mode 100644 .qoder/skills/understand/languages/csharp.md create mode 100644 .qoder/skills/understand/languages/css.md create mode 100644 .qoder/skills/understand/languages/dockerfile.md create mode 100644 .qoder/skills/understand/languages/go.md create mode 100644 .qoder/skills/understand/languages/graphql.md create mode 100644 .qoder/skills/understand/languages/html.md create mode 100644 .qoder/skills/understand/languages/java.md create mode 100644 .qoder/skills/understand/languages/javascript.md create mode 100644 .qoder/skills/understand/languages/json.md create mode 100644 .qoder/skills/understand/languages/kotlin.md create mode 100644 .qoder/skills/understand/languages/markdown.md create mode 100644 .qoder/skills/understand/languages/php.md create mode 100644 .qoder/skills/understand/languages/protobuf.md create mode 100644 .qoder/skills/understand/languages/python.md create mode 100644 .qoder/skills/understand/languages/ruby.md create mode 100644 .qoder/skills/understand/languages/rust.md create mode 100644 .qoder/skills/understand/languages/shell.md create mode 100644 .qoder/skills/understand/languages/sql.md create mode 100644 .qoder/skills/understand/languages/swift.md create mode 100644 .qoder/skills/understand/languages/terraform.md create mode 100644 .qoder/skills/understand/languages/typescript.md create mode 100644 .qoder/skills/understand/languages/yaml.md create mode 100644 .qoder/skills/understand/locales/en.md create mode 100644 .qoder/skills/understand/locales/ja.md create mode 100644 .qoder/skills/understand/locales/ko.md create mode 100644 .qoder/skills/understand/locales/ru.md create mode 100644 .qoder/skills/understand/locales/zh-TW.md create mode 100644 .qoder/skills/understand/locales/zh.md create mode 100644 .qoder/skills/understand/merge-batch-graphs.py create mode 100644 .qoder/skills/understand/merge-subdomain-graphs.py create mode 100644 .qoder/skills/understand/scan-project.mjs create mode 100644 .qoder/skills/using-git-worktrees/SKILL.md create mode 100644 .qoder/skills/using-superpowers/SKILL.md create mode 100644 .qoder/skills/using-superpowers/references/codex-tools.md create mode 100644 .qoder/skills/using-superpowers/references/copilot-tools.md create mode 100644 .qoder/skills/using-superpowers/references/gemini-tools.md create mode 100644 .qoder/skills/verification-before-completion/SKILL.md create mode 100644 .qoder/skills/verify/SKILL.md create mode 100644 .qoder/skills/walkinglabs-harness/SKILL.md create mode 100644 .qoder/skills/walkinglabs-harness/agents/openai.yaml create mode 100644 .qoder/skills/writing-plans/SKILL.md create mode 100644 .qoder/skills/writing-plans/plan-document-reviewer-prompt.md create mode 100644 .qoder/skills/writing-skills/SKILL.md create mode 100644 .qoder/skills/writing-skills/anthropic-best-practices.md create mode 100644 .qoder/skills/writing-skills/examples/CLAUDE_MD_TESTING.md create mode 100644 .qoder/skills/writing-skills/graphviz-conventions.dot create mode 100644 .qoder/skills/writing-skills/persuasion-principles.md create mode 100644 .qoder/skills/writing-skills/render-graphs.js create mode 100644 .qoder/skills/writing-skills/testing-skills-with-subagents.md create mode 100644 .qoder/skills/zentao/.env create mode 100644 .qoder/skills/zentao/.env.example create mode 100644 .qoder/skills/zentao/.session create mode 100644 .qoder/skills/zentao/SKILL.md create mode 100644 .qoder/skills/zentao/_bug681.json create mode 100644 .qoder/skills/zentao/audit.log create mode 100644 .qoder/skills/zentao/images/681/bug681_file2429.png create mode 100644 .qoder/skills/zentao/images/772/bug772_file2278.png create mode 100644 .qoder/skills/zentao/images/772/bug772_file2280.png create mode 100644 .qoder/skills/zentao/images/772/bug772_file2281.png create mode 100644 .qoder/skills/zentao/images/772/bug772_file2282.png create mode 100644 .qoder/skills/zentao/scripts/zentao_client.py diff --git a/.qoder/agents/chenlin.md b/.qoder/agents/chenlin.md new file mode 100644 index 000000000..3cdf75fea --- /dev/null +++ b/.qoder/agents/chenlin.md @@ -0,0 +1,27 @@ +--- +name: chenlin +description: 归档师 — 生成报告、Git归档、禅道备注 +tools: Bash, Read, Write, Grep, Glob +model: inherit +maxTurns: 3 +memory: project +effort: medium +color: orange +skills: + - archive +--- + +# 陈琳 (chenlin) — 归档师 + +## 角色 +- 生成完整修复报告(Markdown) +- 写入 Git docs/bug-fixes/ +- 写入 SQLite bug_reports 表 +- 写入 Redis 缓存 +- 禅道添加归档备注 + +## 铁律 +1. 报告必须包含:基本信息、根因分析、修复文件、流程时间线 +2. Git 归档路径:his-repo/docs/bug-fixes/bug-{id}.md +3. SQLite 归档必须使用完整的 INSERT 列(含 test_output、pipeline_json) +4. 禅道备注格式:[📝 陈琳归档] Bug #xxx 修复报告已归档 diff --git a/.qoder/agents/guanyu.md b/.qoder/agents/guanyu.md new file mode 100644 index 000000000..196b63ce2 --- /dev/null +++ b/.qoder/agents/guanyu.md @@ -0,0 +1,27 @@ +--- +name: guanyu +description: 后端修复师 — Java/Spring/Mapper/数据库 修复 +tools: Bash, Read, Write, Edit, Grep, Glob +model: inherit +maxTurns: 8 +memory: project +effort: xhigh +color: red +skills: + - fix +--- + +# 关羽 (guanyu) — 后端修复师 + +## 角色 +- 修复 Java/Spring Boot 后端 Bug +- 处理 API 接口、Service 逻辑、Mapper/SQL +- 数据库相关修复(INSERT/UPDATE/DELETE) + +## 铁律 +1. 修复前必须先读 AGENTS.md 了解项目规范 +2. 修复后必须运行 `mvn compile` 验证编译 +3. 涉及 SQL 必须先查真实数据库表结构 +4. 一次只修一个 Bug,不扩大范围 +5. 修复后必须有 git commit,commit message 包含 Bug 编号 +6. 数据库铁律:必须用 db-query 工具验证 SQL 语法正确性 diff --git a/.qoder/agents/huatuo.md b/.qoder/agents/huatuo.md new file mode 100644 index 000000000..676ce7104 --- /dev/null +++ b/.qoder/agents/huatuo.md @@ -0,0 +1,25 @@ +--- +name: huatuo +description: 验收师 — 最终验收、确认修复完整性 +tools: Bash, Read, Grep, Glob +model: inherit +maxTurns: 3 +memory: project +effort: medium +color: yellow +skills: + - verify +--- + +# 华佗 (huatuo) — 验收师 + +## 角色 +- 最终验收修复结果 +- 确认测试通过、代码提交、文档完整 +- 人类 Bug 只加备注不改状态 + +## 铁律 +1. 必须检查 git commit 是否存在 +2. 必须检查测试报告是否通过 +3. 人类提的 Bug 不改状态不改分配,只加备注 +4. 智能体提的 Bug 可以改分配和加备注 diff --git a/.qoder/agents/liubei.md b/.qoder/agents/liubei.md new file mode 100644 index 000000000..1cd988402 --- /dev/null +++ b/.qoder/agents/liubei.md @@ -0,0 +1,26 @@ +--- +name: liubei +description: 总协调者 — 扫描禅道Bug、调度智能体、生成进度报告 +tools: Bash, Read, Grep, Glob +model: inherit +maxTurns: 5 +memory: project +effort: high +color: gold +skills: + - analyze +--- + +# 刘备 (liubei) — 总协调者 + +## 角色 +- 扫描禅道所有未关闭 Bug +- 根据 Bug 标题关键词路由到对应修复智能体 +- 监控管线进度,生成报告 +- 不直接修复 Bug + +## 铁律 +1. 只调度,不修改代码 +2. 每 5 分钟自动扫描一次 +3. 路由规则:数据库→荀彧,后端→关羽,前端→赵云 +4. 已关闭/已解决的 Bug 不再调度 diff --git a/.qoder/agents/xunyu.md b/.qoder/agents/xunyu.md new file mode 100644 index 000000000..0d518443a --- /dev/null +++ b/.qoder/agents/xunyu.md @@ -0,0 +1,25 @@ +--- +name: xunyu +description: DB审查师 — 数据库变更审查、SQL验证 +tools: Bash, Read, Grep, Glob +model: inherit +maxTurns: 3 +memory: project +effort: high +color: cyan +skills: + - db-review +--- + +# 荀彧 (xunyu) — DB审查师 + +## 角色 +- 审查修复中的数据库变更 +- 验证 SQL 语法、表结构、约束 +- 检查迁移脚本完整性 + +## 铁律 +1. 必须用 db-query 工具查询真实数据库 +2. 检查 NOT NULL 约束、外键约束 +3. 验证 INSERT/UPDATE 字段与表结构匹配 +4. 审查结果必须包含:通过/不通过、原因、建议 diff --git a/.qoder/agents/zhangfei.md b/.qoder/agents/zhangfei.md new file mode 100644 index 000000000..908fd466b --- /dev/null +++ b/.qoder/agents/zhangfei.md @@ -0,0 +1,25 @@ +--- +name: zhangfei +description: 测试师 — Playwright回归测试、质量验证 +tools: Bash, Read, Grep, Glob +model: inherit +maxTurns: 5 +memory: project +effort: high +color: green +skills: + - test +--- + +# 张飞 (zhangfei) — 测试师 + +## 角色 +- 运行 Playwright 回归测试 +- 验证修复是否生效 +- 测试失败时退回修复智能体 + +## 铁律 +1. 必须用 `--workers=1` 避免压垮 dev server +2. 测试超时 120 秒 +3. 最多重试 3 次,超过则通知人工介入 +4. 测试结果必须写入禅道备注 diff --git a/.qoder/agents/zhaoyun.md b/.qoder/agents/zhaoyun.md new file mode 100644 index 000000000..3974594ad --- /dev/null +++ b/.qoder/agents/zhaoyun.md @@ -0,0 +1,25 @@ +--- +name: zhaoyun +description: 前端修复师 — Vue/TypeScript/CSS 修复 +tools: Bash, Read, Write, Edit, Grep, Glob +model: inherit +maxTurns: 8 +memory: project +effort: xhigh +color: blue +skills: + - fix +--- + +# 赵云 (zhaoyun) — 前端修复师 + +## 角色 +- 修复 Vue3/TypeScript 前端 Bug +- 处理界面显示、组件交互、样式问题 +- API 调用对接 + +## 铁律 +1. 修复前必须先读 AGENTS.md 了解项目规范 +2. 修复后必须运行 `vue-tsc --noEmit` 验证类型 +3. 一次只修一个 Bug,不扩大范围 +4. 修复后必须有 git commit,commit message 包含 Bug 编号 diff --git a/.qoder/agents/zhugeliang.md b/.qoder/agents/zhugeliang.md new file mode 100644 index 000000000..3774481a6 --- /dev/null +++ b/.qoder/agents/zhugeliang.md @@ -0,0 +1,26 @@ +--- +name: zhugeliang +description: 分析师 — 分析Bug根因、拆解修复步骤、路由到正确智能体 +tools: Bash, Read, Grep, Glob, WebFetch +model: inherit +maxTurns: 3 +memory: project +effort: xhigh +color: purple +skills: + - analyze +--- + +# 诸葛亮 (zhugeliang) — 分析师 + +## 角色 +- 接收刘备分派的 Bug,深度分析根因 +- 读取禅道完整信息(含图片附件 OCR) +- 拆解修复步骤,确定修复策略 +- 路由到正确的修复智能体 + +## 铁律 +1. 必须读取 AGENTS.md 了解项目规范 +2. 必须分析完整 6 环链路(前端→Controller→Service→Mapper→DB→关联模块) +3. 涉及数据库字段的 Bug 必须先查真实表结构 +4. 分析报告必须包含:根因、影响范围、修复方案、测试要点 diff --git a/.qoder/rules/IRON_LAWS.md b/.qoder/rules/IRON_LAWS.md new file mode 100644 index 000000000..11934f1bb --- /dev/null +++ b/.qoder/rules/IRON_LAWS.md @@ -0,0 +1,377 @@ +# 🔴 AgentForge 铁律(不可违反) + +> 所有智能体在处理任何任务时必须遵守。违反任何一条 = 阻断提交。 +> 唯一源头文件:修改此文件后所有智能体自动生效。 + +--- + +## 一、Bug 状态管理 + +- **已关闭/已解决的 Bug 禁止处理** — 处理前检查禅道 status,resolved/closed 直接跳过 +- **人类提的 Bug 只加备注不改状态** — reporter 是人类账号时,不改 status、不改 assignedTo +- **智能体提的 Bug 可改分配和加备注** — 状态变更等测试通过后由华佗确认 +- **每个修复必须有 git commit** — 格式: `fix(#bug_id): 简要描述` +- **🔴 修复完成必须提交** — `git add --all && git commit && git push`,未提交=没修 +- **🔴 修复必须合并到 develop** — 工作树 commit ≠ 生效,必须 cherry-pick/merge 到 develop +- **🔴 未合并到 develop 的修复等于没修** — 验收时检查 develop 上是否有该 commit +- **🔴 修复必须编译部署后才算完成** — `mvn package` → `systemctl restart` → 验证启动时间 + +--- + +## 二、修复流程 + +- **一次只修一个 Bug**,不扩大范围 +- **修前必须完整获取 Bug 全部信息** — 描述、复现步骤、所有截图/附件、所有备注历史。禁止只看标题就写代码 +- **修复前必须读 AGENTS.md** +- **修复后必须验证编译** — `mvn compile` / `vue-tsc --noEmit` 0 error +- **commit 前必须验证** — 编译通过 + 无新增 lint 警告 + +--- + +## 三、全链路 6 环分析 + +涉及数据库字段的 Bug 必须走完整链路: +``` +前端/页面 → Controller → Service → Mapper → DB/SQL → 关联模块 + ①录入 ②验证 ③业务 ④持久化 ⑤存储 ⑥联动 +``` + +--- + +## 四、状态值一致性(来自 Bug #574 教训) + +修改任何状态值前,**必须**列出完整链路并逐项检查: +1. 枚举定义(如 `SlotStatus`)的值 +2. Service 层设置的状态值是否与枚举一致 +3. 查询/列表接口的状态映射是否覆盖所有枚举值 +4. 前端 `STATUS_CLASS_MAP` 是否包含新状态 +5. 前端过滤条件(`v-if`、`v-for`)是否兼容新状态 +6. 池/统计表的聚合 SQL 是否包含新状态值 + +**禁止**:只改一端不检查其他端。 + +--- + +## 五、状态变更影响面分析(来自 Bug #574→575 教训) + +改任何状态枚举值前,**必须**执行影响面分析: +1. `rg "原状态枚举名" --type java` 列出所有引用文件 +2. 逐个检查:设置值?查询过滤?显示映射?统计聚合? +3. 检查逆向流程:退号、取消、停诊是否兼容新状态 +4. 检查 XML mapper 中所有查询过滤条件 +5. 检查前端所有 v-if/v-for/disabled 条件 + +**禁止**:只改正向流程不验逆向流程。 + +--- + +## 六、逆向流程验证(来自 Bug #575 教训) + +涉及状态流转的 Bug,验证时**必须**覆盖: +- 正向:预约→签到→就诊→完成 +- 逆向:退号、取消预约、停诊、退费 +- 边界:并发操作、重复操作、异常中断 + +**禁止**:只测正向流程就标记"修复完成"。 + +--- + +## 七、全链路验证(状态流转 Bug 必做) + +修复后按以下顺序验证,**编译通过不等于修复完成**: +``` +① 数据库:SELECT status FROM table WHERE id = ? → 确认写入正确 +② 后端接口:检查所有 if/switch 分支 → 确认映射正确 +③ 前端显示:检查 STATUS_CLASS_MAP → 确认文本正确 +④ 前端交互:检查 v-if/v-for/disabled → 确认按钮状态正确 +⑤ 统计数据:检查聚合 SQL → 确认统计包含新状态 +``` + +--- + +## 八、池/统计表同步(来自 Bug #574 反复修复教训) + +- **任何状态变更必须同步更新关联统计表** +- 检查清单: + 1. 状态变更后,哪些统计字段需要更新? + 2. 是原子递增/递减,还是全量重算? + 3. 并发安全:用 `SET field = field + 1` 还是先查后改? + 4. 逆向操作(退号/取消)是否正确回滚统计? +- **禁止**:只改状态不改统计,或只改统计不改状态 + +--- + +## 九、统计变更必须验证实际值(来自 Bug #575 教训) + +- 修改统计逻辑后,**必须查数据库验证实际值** +- `SELECT booked_num, locked_num FROM adm_schedule_pool WHERE id = ?` +- 对比操作前后的值,确认统计正确 +- **禁止**:改了统计逻辑不查数据库验证 + +--- + +## 十、禁止删除源文件 + +- **绝对禁止**删除项目中已有的 Java/Vue/SQL 源文件 +- 编译错误 → 修复错误,不删除文件 +- 重复文件 → 重构合并,不删除文件 +- AI 幻觉文件 → 检查 `git ls-tree baseline -- ` 确认后再删除 +- **唯一例外**:人类明确确认删除 + +--- + +## 十一、禁止修改已有公开方法签名 + +- 不能删除或重命名已有的 public 方法 +- 不能修改已有方法的参数列表 +- 需要新功能 → 添加重载方法 +- 需要改行为 → 修改方法内部实现 + +--- + +## 十二、搜索所有相关代码路径 + +修复前必须用 `rg` 搜索: +``` +rg "状态枚举名|相关方法名|相关字段名" --type java --type vue +``` +确保不遗漏任何引用路径。 + +--- + +## 十三、数据库铁律 + +- **修前必须查询真实数据库** — 确认表结构、字段约束、索引 +- **禁止凭猜测写 SQL** — 先 `\d table_name` 查看表结构 +- **修改 SQL 后必须验证** — `EXPLAIN` 或实际查询验证语法 +- **NOT NULL 约束必须检查** — INSERT/UPDATE 前先查 `is_nullable` +- **关联表必须查完整** — 涉及 JOIN 查所有关联表结构和外键 +- **涉及 SQL 必须先查真实数据库** + +--- + +## 十四、测试铁律 + +- Playwright 必须 `--workers=1` +- 超时 120 秒,最多重试 3 次 +- 测试失败自动重试,超过 3 次通知人工介入 +- 测试结果写入禅道备注 +- **DB审查失败自动回退** — 路由回原修复智能体 + +--- + +## 十五、归档铁律 + +- **三重写入** — Git + SQLite + Redis +- SQLite 必须使用完整字段 +- 禅道备注格式:`[📝 陈琳归档] Bug #xxx` +- 归档报告必须包含:基本信息 + 根因分析 + 修复文件 + 流程时间线 + +--- + +## 十六、禅道交互 + +- 备注使用 resolve+activate workaround +- 不直接调用 comment API(会 404) +- 图片附件必须 OCR 读取 + +--- + +## 十七、质量门禁 + +- L1: 编译通过 +- L2: 测试通过 +- L3: DB审查通过 +- L4: 验收通过 +- L5: 归档完成 + +--- + +## 过往教训 + +| Bug | 教训 | 根因 | +|---|---|---| +| #574 | 状态值 BOOKED(1)→应为 CHECKED_IN(3),前端映射缺失 | 没走完整状态链路 | +| #574 | AI 看到编译错误直接删文件 | 没检查 git baseline | +| #574 | 多次 fallback 修错文件(OrderServiceImpl) | 没用 rg 搜索所有引用 | +| #574 | 签到后 booked_num 未累加 | 只改状态没改统计 | +| #575 | 改了签到状态没检查退号流程 | 只验正向不验逆向 | +| #575 | booked_num 应在预约时累加而非签到时 | 统计变更未验证实际值 | +| — | 修复完成未提交到 develop | 框架未强制验证提交 | +| — | 退号流程只检查 BOOKED(1) 不兼容 CHECKED_IN(3) | 状态变更影响面分析缺失 | + +--- + +## 十八、禁止硬编码业务默认值(来自 Bug #617 教训) + +- **禁止**在提交参数中硬编码业务默认值(如 `contractNo: '0000'`) +- 必须使用用户在表单中选择的值,硬编码值仅作为 fallback +- 检查清单: + 1. 表单字段是否有 `v-model` 绑定? + 2. 构建提交参数时是否使用了绑定值? + 3. 提交后是否覆盖了用户选择? +- **禁止**:用户选了医保,提交时却写死为自费 + +--- + +## 过往教训(补充) + +| Bug | 教训 | 根因 | +|---|---|---| +| #617 | 费用性质硬编码为 '0000'(自费),用户选医保无效 | 构建参数时写死默认值 | + +--- + +## 十九、前端验证铁律 + +- **提交前必须编译前端** — `npm run build` 或 `npx vite build` 通过才算完成 +- **禁止只改 .vue 文件不验证编译** — 改完必须跑一次编译确认无报错 +- **SCSS 括号闭合必须检查** — ` + + +
+

Superpowers Brainstorming

+
Connected
+
+ +
+
+ +
+
+ +
+ Click an option above, then return to the terminal +
+ + + diff --git a/.qoder/skills/brainstorming/scripts/helper.js b/.qoder/skills/brainstorming/scripts/helper.js new file mode 100644 index 000000000..111f97f59 --- /dev/null +++ b/.qoder/skills/brainstorming/scripts/helper.js @@ -0,0 +1,88 @@ +(function() { + const WS_URL = 'ws://' + window.location.host; + let ws = null; + let eventQueue = []; + + function connect() { + ws = new WebSocket(WS_URL); + + ws.onopen = () => { + eventQueue.forEach(e => ws.send(JSON.stringify(e))); + eventQueue = []; + }; + + ws.onmessage = (msg) => { + const data = JSON.parse(msg.data); + if (data.type === 'reload') { + window.location.reload(); + } + }; + + ws.onclose = () => { + setTimeout(connect, 1000); + }; + } + + function sendEvent(event) { + event.timestamp = Date.now(); + if (ws && ws.readyState === WebSocket.OPEN) { + ws.send(JSON.stringify(event)); + } else { + eventQueue.push(event); + } + } + + // Capture clicks on choice elements + document.addEventListener('click', (e) => { + const target = e.target.closest('[data-choice]'); + if (!target) return; + + sendEvent({ + type: 'click', + text: target.textContent.trim(), + choice: target.dataset.choice, + id: target.id || null + }); + + // Update indicator bar (defer so toggleSelect runs first) + setTimeout(() => { + const indicator = document.getElementById('indicator-text'); + if (!indicator) return; + const container = target.closest('.options') || target.closest('.cards'); + const selected = container ? container.querySelectorAll('.selected') : []; + if (selected.length === 0) { + indicator.textContent = 'Click an option above, then return to the terminal'; + } else if (selected.length === 1) { + const label = selected[0].querySelector('h3, .content h3, .card-body h3')?.textContent?.trim() || selected[0].dataset.choice; + indicator.innerHTML = '' + label + ' selected — return to terminal to continue'; + } else { + indicator.innerHTML = '' + selected.length + ' selected — return to terminal to continue'; + } + }, 0); + }); + + // Frame UI: selection tracking + window.selectedChoice = null; + + window.toggleSelect = function(el) { + const container = el.closest('.options') || el.closest('.cards'); + const multi = container && container.dataset.multiselect !== undefined; + if (container && !multi) { + container.querySelectorAll('.option, .card').forEach(o => o.classList.remove('selected')); + } + if (multi) { + el.classList.toggle('selected'); + } else { + el.classList.add('selected'); + } + window.selectedChoice = el.dataset.choice; + }; + + // Expose API for explicit use + window.brainstorm = { + send: sendEvent, + choice: (value, metadata = {}) => sendEvent({ type: 'choice', value, ...metadata }) + }; + + connect(); +})(); diff --git a/.qoder/skills/brainstorming/scripts/server.cjs b/.qoder/skills/brainstorming/scripts/server.cjs new file mode 100644 index 000000000..562c17f89 --- /dev/null +++ b/.qoder/skills/brainstorming/scripts/server.cjs @@ -0,0 +1,354 @@ +const crypto = require('crypto'); +const http = require('http'); +const fs = require('fs'); +const path = require('path'); + +// ========== WebSocket Protocol (RFC 6455) ========== + +const OPCODES = { TEXT: 0x01, CLOSE: 0x08, PING: 0x09, PONG: 0x0A }; +const WS_MAGIC = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'; + +function computeAcceptKey(clientKey) { + return crypto.createHash('sha1').update(clientKey + WS_MAGIC).digest('base64'); +} + +function encodeFrame(opcode, payload) { + const fin = 0x80; + const len = payload.length; + let header; + + if (len < 126) { + header = Buffer.alloc(2); + header[0] = fin | opcode; + header[1] = len; + } else if (len < 65536) { + header = Buffer.alloc(4); + header[0] = fin | opcode; + header[1] = 126; + header.writeUInt16BE(len, 2); + } else { + header = Buffer.alloc(10); + header[0] = fin | opcode; + header[1] = 127; + header.writeBigUInt64BE(BigInt(len), 2); + } + + return Buffer.concat([header, payload]); +} + +function decodeFrame(buffer) { + if (buffer.length < 2) return null; + + const secondByte = buffer[1]; + const opcode = buffer[0] & 0x0F; + const masked = (secondByte & 0x80) !== 0; + let payloadLen = secondByte & 0x7F; + let offset = 2; + + if (!masked) throw new Error('Client frames must be masked'); + + if (payloadLen === 126) { + if (buffer.length < 4) return null; + payloadLen = buffer.readUInt16BE(2); + offset = 4; + } else if (payloadLen === 127) { + if (buffer.length < 10) return null; + payloadLen = Number(buffer.readBigUInt64BE(2)); + offset = 10; + } + + const maskOffset = offset; + const dataOffset = offset + 4; + const totalLen = dataOffset + payloadLen; + if (buffer.length < totalLen) return null; + + const mask = buffer.slice(maskOffset, dataOffset); + const data = Buffer.alloc(payloadLen); + for (let i = 0; i < payloadLen; i++) { + data[i] = buffer[dataOffset + i] ^ mask[i % 4]; + } + + return { opcode, payload: data, bytesConsumed: totalLen }; +} + +// ========== Configuration ========== + +const PORT = process.env.BRAINSTORM_PORT || (49152 + Math.floor(Math.random() * 16383)); +const HOST = process.env.BRAINSTORM_HOST || '127.0.0.1'; +const URL_HOST = process.env.BRAINSTORM_URL_HOST || (HOST === '127.0.0.1' ? 'localhost' : HOST); +const SESSION_DIR = process.env.BRAINSTORM_DIR || '/tmp/brainstorm'; +const CONTENT_DIR = path.join(SESSION_DIR, 'content'); +const STATE_DIR = path.join(SESSION_DIR, 'state'); +let ownerPid = process.env.BRAINSTORM_OWNER_PID ? Number(process.env.BRAINSTORM_OWNER_PID) : null; + +const MIME_TYPES = { + '.html': 'text/html', '.css': 'text/css', '.js': 'application/javascript', + '.json': 'application/json', '.png': 'image/png', '.jpg': 'image/jpeg', + '.jpeg': 'image/jpeg', '.gif': 'image/gif', '.svg': 'image/svg+xml' +}; + +// ========== Templates and Constants ========== + +const WAITING_PAGE = ` + +Brainstorm Companion + + +

Brainstorm Companion

+

Waiting for the agent to push a screen...

`; + +const frameTemplate = fs.readFileSync(path.join(__dirname, 'frame-template.html'), 'utf-8'); +const helperScript = fs.readFileSync(path.join(__dirname, 'helper.js'), 'utf-8'); +const helperInjection = ''; + +// ========== Helper Functions ========== + +function isFullDocument(html) { + const trimmed = html.trimStart().toLowerCase(); + return trimmed.startsWith('', content); +} + +function getNewestScreen() { + const files = fs.readdirSync(CONTENT_DIR) + .filter(f => f.endsWith('.html')) + .map(f => { + const fp = path.join(CONTENT_DIR, f); + return { path: fp, mtime: fs.statSync(fp).mtime.getTime() }; + }) + .sort((a, b) => b.mtime - a.mtime); + return files.length > 0 ? files[0].path : null; +} + +// ========== HTTP Request Handler ========== + +function handleRequest(req, res) { + touchActivity(); + if (req.method === 'GET' && req.url === '/') { + const screenFile = getNewestScreen(); + let html = screenFile + ? (raw => isFullDocument(raw) ? raw : wrapInFrame(raw))(fs.readFileSync(screenFile, 'utf-8')) + : WAITING_PAGE; + + if (html.includes('')) { + html = html.replace('', helperInjection + '\n'); + } else { + html += helperInjection; + } + + res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); + res.end(html); + } else if (req.method === 'GET' && req.url.startsWith('/files/')) { + const fileName = req.url.slice(7); + const filePath = path.join(CONTENT_DIR, path.basename(fileName)); + if (!fs.existsSync(filePath)) { + res.writeHead(404); + res.end('Not found'); + return; + } + const ext = path.extname(filePath).toLowerCase(); + const contentType = MIME_TYPES[ext] || 'application/octet-stream'; + res.writeHead(200, { 'Content-Type': contentType }); + res.end(fs.readFileSync(filePath)); + } else { + res.writeHead(404); + res.end('Not found'); + } +} + +// ========== WebSocket Connection Handling ========== + +const clients = new Set(); + +function handleUpgrade(req, socket) { + const key = req.headers['sec-websocket-key']; + if (!key) { socket.destroy(); return; } + + const accept = computeAcceptKey(key); + socket.write( + 'HTTP/1.1 101 Switching Protocols\r\n' + + 'Upgrade: websocket\r\n' + + 'Connection: Upgrade\r\n' + + 'Sec-WebSocket-Accept: ' + accept + '\r\n\r\n' + ); + + let buffer = Buffer.alloc(0); + clients.add(socket); + + socket.on('data', (chunk) => { + buffer = Buffer.concat([buffer, chunk]); + while (buffer.length > 0) { + let result; + try { + result = decodeFrame(buffer); + } catch (e) { + socket.end(encodeFrame(OPCODES.CLOSE, Buffer.alloc(0))); + clients.delete(socket); + return; + } + if (!result) break; + buffer = buffer.slice(result.bytesConsumed); + + switch (result.opcode) { + case OPCODES.TEXT: + handleMessage(result.payload.toString()); + break; + case OPCODES.CLOSE: + socket.end(encodeFrame(OPCODES.CLOSE, Buffer.alloc(0))); + clients.delete(socket); + return; + case OPCODES.PING: + socket.write(encodeFrame(OPCODES.PONG, result.payload)); + break; + case OPCODES.PONG: + break; + default: { + const closeBuf = Buffer.alloc(2); + closeBuf.writeUInt16BE(1003); + socket.end(encodeFrame(OPCODES.CLOSE, closeBuf)); + clients.delete(socket); + return; + } + } + } + }); + + socket.on('close', () => clients.delete(socket)); + socket.on('error', () => clients.delete(socket)); +} + +function handleMessage(text) { + let event; + try { + event = JSON.parse(text); + } catch (e) { + console.error('Failed to parse WebSocket message:', e.message); + return; + } + touchActivity(); + console.log(JSON.stringify({ source: 'user-event', ...event })); + if (event.choice) { + const eventsFile = path.join(STATE_DIR, 'events'); + fs.appendFileSync(eventsFile, JSON.stringify(event) + '\n'); + } +} + +function broadcast(msg) { + const frame = encodeFrame(OPCODES.TEXT, Buffer.from(JSON.stringify(msg))); + for (const socket of clients) { + try { socket.write(frame); } catch (e) { clients.delete(socket); } + } +} + +// ========== Activity Tracking ========== + +const IDLE_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes +let lastActivity = Date.now(); + +function touchActivity() { + lastActivity = Date.now(); +} + +// ========== File Watching ========== + +const debounceTimers = new Map(); + +// ========== Server Startup ========== + +function startServer() { + if (!fs.existsSync(CONTENT_DIR)) fs.mkdirSync(CONTENT_DIR, { recursive: true }); + if (!fs.existsSync(STATE_DIR)) fs.mkdirSync(STATE_DIR, { recursive: true }); + + // Track known files to distinguish new screens from updates. + // macOS fs.watch reports 'rename' for both new files and overwrites, + // so we can't rely on eventType alone. + const knownFiles = new Set( + fs.readdirSync(CONTENT_DIR).filter(f => f.endsWith('.html')) + ); + + const server = http.createServer(handleRequest); + server.on('upgrade', handleUpgrade); + + const watcher = fs.watch(CONTENT_DIR, (eventType, filename) => { + if (!filename || !filename.endsWith('.html')) return; + + if (debounceTimers.has(filename)) clearTimeout(debounceTimers.get(filename)); + debounceTimers.set(filename, setTimeout(() => { + debounceTimers.delete(filename); + const filePath = path.join(CONTENT_DIR, filename); + + if (!fs.existsSync(filePath)) return; // file was deleted + touchActivity(); + + if (!knownFiles.has(filename)) { + knownFiles.add(filename); + const eventsFile = path.join(STATE_DIR, 'events'); + if (fs.existsSync(eventsFile)) fs.unlinkSync(eventsFile); + console.log(JSON.stringify({ type: 'screen-added', file: filePath })); + } else { + console.log(JSON.stringify({ type: 'screen-updated', file: filePath })); + } + + broadcast({ type: 'reload' }); + }, 100)); + }); + watcher.on('error', (err) => console.error('fs.watch error:', err.message)); + + function shutdown(reason) { + console.log(JSON.stringify({ type: 'server-stopped', reason })); + const infoFile = path.join(STATE_DIR, 'server-info'); + if (fs.existsSync(infoFile)) fs.unlinkSync(infoFile); + fs.writeFileSync( + path.join(STATE_DIR, 'server-stopped'), + JSON.stringify({ reason, timestamp: Date.now() }) + '\n' + ); + watcher.close(); + clearInterval(lifecycleCheck); + server.close(() => process.exit(0)); + } + + function ownerAlive() { + if (!ownerPid) return true; + try { process.kill(ownerPid, 0); return true; } catch (e) { return e.code === 'EPERM'; } + } + + // Check every 60s: exit if owner process died or idle for 30 minutes + const lifecycleCheck = setInterval(() => { + if (!ownerAlive()) shutdown('owner process exited'); + else if (Date.now() - lastActivity > IDLE_TIMEOUT_MS) shutdown('idle timeout'); + }, 60 * 1000); + lifecycleCheck.unref(); + + // Validate owner PID at startup. If it's already dead, the PID resolution + // was wrong (common on WSL, Tailscale SSH, and cross-user scenarios). + // Disable monitoring and rely on the idle timeout instead. + if (ownerPid) { + try { process.kill(ownerPid, 0); } + catch (e) { + if (e.code !== 'EPERM') { + console.log(JSON.stringify({ type: 'owner-pid-invalid', pid: ownerPid, reason: 'dead at startup' })); + ownerPid = null; + } + } + } + + server.listen(PORT, HOST, () => { + const info = JSON.stringify({ + type: 'server-started', port: Number(PORT), host: HOST, + url_host: URL_HOST, url: 'http://' + URL_HOST + ':' + PORT, + screen_dir: CONTENT_DIR, state_dir: STATE_DIR + }); + console.log(info); + fs.writeFileSync(path.join(STATE_DIR, 'server-info'), info + '\n'); + }); +} + +if (require.main === module) { + startServer(); +} + +module.exports = { computeAcceptKey, encodeFrame, decodeFrame, OPCODES }; diff --git a/.qoder/skills/brainstorming/scripts/start-server.sh b/.qoder/skills/brainstorming/scripts/start-server.sh new file mode 100644 index 000000000..9ef6dcb99 --- /dev/null +++ b/.qoder/skills/brainstorming/scripts/start-server.sh @@ -0,0 +1,148 @@ +#!/usr/bin/env bash +# Start the brainstorm server and output connection info +# Usage: start-server.sh [--project-dir ] [--host ] [--url-host ] [--foreground] [--background] +# +# Starts server on a random high port, outputs JSON with URL. +# Each session gets its own directory to avoid conflicts. +# +# Options: +# --project-dir Store session files under /.superpowers/brainstorm/ +# instead of /tmp. Files persist after server stops. +# --host Host/interface to bind (default: 127.0.0.1). +# Use 0.0.0.0 in remote/containerized environments. +# --url-host Hostname shown in returned URL JSON. +# --foreground Run server in the current terminal (no backgrounding). +# --background Force background mode (overrides Codex auto-foreground). + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" + +# Parse arguments +PROJECT_DIR="" +FOREGROUND="false" +FORCE_BACKGROUND="false" +BIND_HOST="127.0.0.1" +URL_HOST="" +while [[ $# -gt 0 ]]; do + case "$1" in + --project-dir) + PROJECT_DIR="$2" + shift 2 + ;; + --host) + BIND_HOST="$2" + shift 2 + ;; + --url-host) + URL_HOST="$2" + shift 2 + ;; + --foreground|--no-daemon) + FOREGROUND="true" + shift + ;; + --background|--daemon) + FORCE_BACKGROUND="true" + shift + ;; + *) + echo "{\"error\": \"Unknown argument: $1\"}" + exit 1 + ;; + esac +done + +if [[ -z "$URL_HOST" ]]; then + if [[ "$BIND_HOST" == "127.0.0.1" || "$BIND_HOST" == "localhost" ]]; then + URL_HOST="localhost" + else + URL_HOST="$BIND_HOST" + fi +fi + +# Some environments reap detached/background processes. Auto-foreground when detected. +if [[ -n "${CODEX_CI:-}" && "$FOREGROUND" != "true" && "$FORCE_BACKGROUND" != "true" ]]; then + FOREGROUND="true" +fi + +# Windows/Git Bash reaps nohup background processes. Auto-foreground when detected. +if [[ "$FOREGROUND" != "true" && "$FORCE_BACKGROUND" != "true" ]]; then + case "${OSTYPE:-}" in + msys*|cygwin*|mingw*) FOREGROUND="true" ;; + esac + if [[ -n "${MSYSTEM:-}" ]]; then + FOREGROUND="true" + fi +fi + +# Generate unique session directory +SESSION_ID="$$-$(date +%s)" + +if [[ -n "$PROJECT_DIR" ]]; then + SESSION_DIR="${PROJECT_DIR}/.superpowers/brainstorm/${SESSION_ID}" +else + SESSION_DIR="/tmp/brainstorm-${SESSION_ID}" +fi + +STATE_DIR="${SESSION_DIR}/state" +PID_FILE="${STATE_DIR}/server.pid" +LOG_FILE="${STATE_DIR}/server.log" + +# Create fresh session directory with content and state peers +mkdir -p "${SESSION_DIR}/content" "$STATE_DIR" + +# Kill any existing server +if [[ -f "$PID_FILE" ]]; then + old_pid=$(cat "$PID_FILE") + kill "$old_pid" 2>/dev/null + rm -f "$PID_FILE" +fi + +cd "$SCRIPT_DIR" + +# Resolve the harness PID (grandparent of this script). +# $PPID is the ephemeral shell the harness spawned to run us — it dies +# when this script exits. The harness itself is $PPID's parent. +OWNER_PID="$(ps -o ppid= -p "$PPID" 2>/dev/null | tr -d ' ')" +if [[ -z "$OWNER_PID" || "$OWNER_PID" == "1" ]]; then + OWNER_PID="$PPID" +fi + +# Foreground mode for environments that reap detached/background processes. +if [[ "$FOREGROUND" == "true" ]]; then + echo "$$" > "$PID_FILE" + env BRAINSTORM_DIR="$SESSION_DIR" BRAINSTORM_HOST="$BIND_HOST" BRAINSTORM_URL_HOST="$URL_HOST" BRAINSTORM_OWNER_PID="$OWNER_PID" node server.cjs + exit $? +fi + +# Start server, capturing output to log file +# Use nohup to survive shell exit; disown to remove from job table +nohup env BRAINSTORM_DIR="$SESSION_DIR" BRAINSTORM_HOST="$BIND_HOST" BRAINSTORM_URL_HOST="$URL_HOST" BRAINSTORM_OWNER_PID="$OWNER_PID" node server.cjs > "$LOG_FILE" 2>&1 & +SERVER_PID=$! +disown "$SERVER_PID" 2>/dev/null +echo "$SERVER_PID" > "$PID_FILE" + +# Wait for server-started message (check log file) +for i in {1..50}; do + if grep -q "server-started" "$LOG_FILE" 2>/dev/null; then + # Verify server is still alive after a short window (catches process reapers) + alive="true" + for _ in {1..20}; do + if ! kill -0 "$SERVER_PID" 2>/dev/null; then + alive="false" + break + fi + sleep 0.1 + done + if [[ "$alive" != "true" ]]; then + echo "{\"error\": \"Server started but was killed. Retry in a persistent terminal with: $SCRIPT_DIR/start-server.sh${PROJECT_DIR:+ --project-dir $PROJECT_DIR} --host $BIND_HOST --url-host $URL_HOST --foreground\"}" + exit 1 + fi + grep "server-started" "$LOG_FILE" | head -1 + exit 0 + fi + sleep 0.1 +done + +# Timeout - server didn't start +echo '{"error": "Server failed to start within 5 seconds"}' +exit 1 diff --git a/.qoder/skills/brainstorming/scripts/stop-server.sh b/.qoder/skills/brainstorming/scripts/stop-server.sh new file mode 100644 index 000000000..a6b94e653 --- /dev/null +++ b/.qoder/skills/brainstorming/scripts/stop-server.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash +# Stop the brainstorm server and clean up +# Usage: stop-server.sh +# +# Kills the server process. Only deletes session directory if it's +# under /tmp (ephemeral). Persistent directories (.superpowers/) are +# kept so mockups can be reviewed later. + +SESSION_DIR="$1" + +if [[ -z "$SESSION_DIR" ]]; then + echo '{"error": "Usage: stop-server.sh "}' + exit 1 +fi + +STATE_DIR="${SESSION_DIR}/state" +PID_FILE="${STATE_DIR}/server.pid" + +if [[ -f "$PID_FILE" ]]; then + pid=$(cat "$PID_FILE") + + # Try to stop gracefully, fallback to force if still alive + kill "$pid" 2>/dev/null || true + + # Wait for graceful shutdown (up to ~2s) + for i in {1..20}; do + if ! kill -0 "$pid" 2>/dev/null; then + break + fi + sleep 0.1 + done + + # If still running, escalate to SIGKILL + if kill -0 "$pid" 2>/dev/null; then + kill -9 "$pid" 2>/dev/null || true + + # Give SIGKILL a moment to take effect + sleep 0.1 + fi + + if kill -0 "$pid" 2>/dev/null; then + echo '{"status": "failed", "error": "process still running"}' + exit 1 + fi + + rm -f "$PID_FILE" "${STATE_DIR}/server.log" + + # Only delete ephemeral /tmp directories + if [[ "$SESSION_DIR" == /tmp/* ]]; then + rm -rf "$SESSION_DIR" + fi + + echo '{"status": "stopped"}' +else + echo '{"status": "not_running"}' +fi diff --git a/.qoder/skills/brainstorming/spec-document-reviewer-prompt.md b/.qoder/skills/brainstorming/spec-document-reviewer-prompt.md new file mode 100644 index 000000000..35acbb611 --- /dev/null +++ b/.qoder/skills/brainstorming/spec-document-reviewer-prompt.md @@ -0,0 +1,49 @@ +# Spec Document Reviewer Prompt Template + +Use this template when dispatching a spec document reviewer subagent. + +**Purpose:** Verify the spec is complete, consistent, and ready for implementation planning. + +**Dispatch after:** Spec document is written to docs/superpowers/specs/ + +``` +Task tool (general-purpose): + description: "Review spec document" + prompt: | + You are a spec document reviewer. Verify this spec is complete and ready for planning. + + **Spec to review:** [SPEC_FILE_PATH] + + ## What to Check + + | Category | What to Look For | + |----------|------------------| + | Completeness | TODOs, placeholders, "TBD", incomplete sections | + | Consistency | Internal contradictions, conflicting requirements | + | Clarity | Requirements ambiguous enough to cause someone to build the wrong thing | + | Scope | Focused enough for a single plan — not covering multiple independent subsystems | + | YAGNI | Unrequested features, over-engineering | + + ## Calibration + + **Only flag issues that would cause real problems during implementation planning.** + A missing section, a contradiction, or a requirement so ambiguous it could be + interpreted two different ways — those are issues. Minor wording improvements, + stylistic preferences, and "sections less detailed than others" are not. + + Approve unless there are serious gaps that would lead to a flawed plan. + + ## Output Format + + ## Spec Review + + **Status:** Approved | Issues Found + + **Issues (if any):** + - [Section X]: [specific issue] - [why it matters for planning] + + **Recommendations (advisory, do not block approval):** + - [suggestions for improvement] +``` + +**Reviewer returns:** Status, Issues (if any), Recommendations diff --git a/.qoder/skills/brainstorming/visual-companion.md b/.qoder/skills/brainstorming/visual-companion.md new file mode 100644 index 000000000..2113863d1 --- /dev/null +++ b/.qoder/skills/brainstorming/visual-companion.md @@ -0,0 +1,287 @@ +# Visual Companion Guide + +Browser-based visual brainstorming companion for showing mockups, diagrams, and options. + +## When to Use + +Decide per-question, not per-session. The test: **would the user understand this better by seeing it than reading it?** + +**Use the browser** when the content itself is visual: + +- **UI mockups** — wireframes, layouts, navigation structures, component designs +- **Architecture diagrams** — system components, data flow, relationship maps +- **Side-by-side visual comparisons** — comparing two layouts, two color schemes, two design directions +- **Design polish** — when the question is about look and feel, spacing, visual hierarchy +- **Spatial relationships** — state machines, flowcharts, entity relationships rendered as diagrams + +**Use the terminal** when the content is text or tabular: + +- **Requirements and scope questions** — "what does X mean?", "which features are in scope?" +- **Conceptual A/B/C choices** — picking between approaches described in words +- **Tradeoff lists** — pros/cons, comparison tables +- **Technical decisions** — API design, data modeling, architectural approach selection +- **Clarifying questions** — anything where the answer is words, not a visual preference + +A question *about* a UI topic is not automatically a visual question. "What kind of wizard do you want?" is conceptual — use the terminal. "Which of these wizard layouts feels right?" is visual — use the browser. + +## How It Works + +The server watches a directory for HTML files and serves the newest one to the browser. You write HTML content to `screen_dir`, the user sees it in their browser and can click to select options. Selections are recorded to `state_dir/events` that you read on your next turn. + +**Content fragments vs full documents:** If your HTML file starts with `/.superpowers/brainstorm/` for the session directory. + +**Note:** Pass the project root as `--project-dir` so mockups persist in `.superpowers/brainstorm/` and survive server restarts. Without it, files go to `/tmp` and get cleaned up. Remind the user to add `.superpowers/` to `.gitignore` if it's not already there. + +**Launching the server by platform:** + +**Claude Code (macOS / Linux):** +```bash +# Default mode works — the script backgrounds the server itself +scripts/start-server.sh --project-dir /path/to/project +``` + +**Claude Code (Windows):** +```bash +# Windows auto-detects and uses foreground mode, which blocks the tool call. +# Use run_in_background: true on the Bash tool call so the server survives +# across conversation turns. +scripts/start-server.sh --project-dir /path/to/project +``` +When calling this via the Bash tool, set `run_in_background: true`. Then read `$STATE_DIR/server-info` on the next turn to get the URL and port. + +**Codex:** +```bash +# Codex reaps background processes. The script auto-detects CODEX_CI and +# switches to foreground mode. Run it normally — no extra flags needed. +scripts/start-server.sh --project-dir /path/to/project +``` + +**Gemini CLI:** +```bash +# Use --foreground and set is_background: true on your shell tool call +# so the process survives across turns +scripts/start-server.sh --project-dir /path/to/project --foreground +``` + +**Other environments:** The server must keep running in the background across conversation turns. If your environment reaps detached processes, use `--foreground` and launch the command with your platform's background execution mechanism. + +If the URL is unreachable from your browser (common in remote/containerized setups), bind a non-loopback host: + +```bash +scripts/start-server.sh \ + --project-dir /path/to/project \ + --host 0.0.0.0 \ + --url-host localhost +``` + +Use `--url-host` to control what hostname is printed in the returned URL JSON. + +## The Loop + +1. **Check server is alive**, then **write HTML** to a new file in `screen_dir`: + - Before each write, check that `$STATE_DIR/server-info` exists. If it doesn't (or `$STATE_DIR/server-stopped` exists), the server has shut down — restart it with `start-server.sh` before continuing. The server auto-exits after 30 minutes of inactivity. + - Use semantic filenames: `platform.html`, `visual-style.html`, `layout.html` + - **Never reuse filenames** — each screen gets a fresh file + - Use Write tool — **never use cat/heredoc** (dumps noise into terminal) + - Server automatically serves the newest file + +2. **Tell user what to expect and end your turn:** + - Remind them of the URL (every step, not just first) + - Give a brief text summary of what's on screen (e.g., "Showing 3 layout options for the homepage") + - Ask them to respond in the terminal: "Take a look and let me know what you think. Click to select an option if you'd like." + +3. **On your next turn** — after the user responds in the terminal: + - Read `$STATE_DIR/events` if it exists — this contains the user's browser interactions (clicks, selections) as JSON lines + - Merge with the user's terminal text to get the full picture + - The terminal message is the primary feedback; `state_dir/events` provides structured interaction data + +4. **Iterate or advance** — if feedback changes current screen, write a new file (e.g., `layout-v2.html`). Only move to the next question when the current step is validated. + +5. **Unload when returning to terminal** — when the next step doesn't need the browser (e.g., a clarifying question, a tradeoff discussion), push a waiting screen to clear the stale content: + + ```html + +
+

Continuing in terminal...

+
+ ``` + + This prevents the user from staring at a resolved choice while the conversation has moved on. When the next visual question comes up, push a new content file as usual. + +6. Repeat until done. + +## Writing Content Fragments + +Write just the content that goes inside the page. The server wraps it in the frame template automatically (header, theme CSS, selection indicator, and all interactive infrastructure). + +**Minimal example:** + +```html +

Which layout works better?

+

Consider readability and visual hierarchy

+ +
+
+
A
+
+

Single Column

+

Clean, focused reading experience

+
+
+
+
B
+
+

Two Column

+

Sidebar navigation with main content

+
+
+
+``` + +That's it. No ``, no CSS, no `