From 5f590d5a97612e474fcfd062927ca249bca137c3 Mon Sep 17 00:00:00 2001 From: MengChen Date: Sat, 11 Apr 2026 03:53:19 +0800 Subject: [PATCH 1/4] feat: add roadmap seed and placeholder workspace scaffold --- .gitignore | 3 + .oxlintrc.json | 22 ++++ README.md | 33 +++--- bun.lock | 90 ++++++++++++++ .../changes/v0-local-cli-view/.openspec.yaml | 2 + openspec/changes/v0-local-cli-view/design.md | 112 ++++++++++++++++++ .../changes/v0-local-cli-view/proposal.md | 47 ++++++++ .../specs/cli-init-view/spec.md | 60 ++++++++++ .../specs/graph-schema/spec.md | 52 ++++++++ openspec/changes/v0-local-cli-view/tasks.md | 39 ++++++ package.json | 21 ++++ packages/cli/README.md | 39 ++++++ packages/cli/package.json | 20 ++++ packages/cli/src/bin.ts | 7 ++ packages/cli/src/index.ts | 28 +++++ packages/cli/test/smoke.test.ts | 21 ++++ packages/core/README.md | 42 +++++++ packages/core/package.json | 12 ++ packages/core/src/index.ts | 6 + packages/core/test/smoke.test.ts | 7 ++ packages/web/README.md | 42 +++++++ packages/web/package.json | 13 ++ packages/web/src/index.ts | 10 ++ packages/web/test/smoke.test.ts | 7 ++ tsconfig.json | 18 +++ 25 files changed, 737 insertions(+), 16 deletions(-) create mode 100644 .oxlintrc.json create mode 100644 bun.lock create mode 100644 openspec/changes/v0-local-cli-view/.openspec.yaml create mode 100644 openspec/changes/v0-local-cli-view/design.md create mode 100644 openspec/changes/v0-local-cli-view/proposal.md create mode 100644 openspec/changes/v0-local-cli-view/specs/cli-init-view/spec.md create mode 100644 openspec/changes/v0-local-cli-view/specs/graph-schema/spec.md create mode 100644 openspec/changes/v0-local-cli-view/tasks.md create mode 100644 package.json create mode 100644 packages/cli/README.md create mode 100644 packages/cli/package.json create mode 100644 packages/cli/src/bin.ts create mode 100644 packages/cli/src/index.ts create mode 100644 packages/cli/test/smoke.test.ts create mode 100644 packages/core/README.md create mode 100644 packages/core/package.json create mode 100644 packages/core/src/index.ts create mode 100644 packages/core/test/smoke.test.ts create mode 100644 packages/web/README.md create mode 100644 packages/web/package.json create mode 100644 packages/web/src/index.ts create mode 100644 packages/web/test/smoke.test.ts create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore index 9a5aced..4f1e91d 100644 --- a/.gitignore +++ b/.gitignore @@ -137,3 +137,6 @@ dist # Vite logs files vite.config.js.timestamp-* vite.config.ts.timestamp-* + +# Local GraphSpec snapshots +.graphspec/ diff --git a/.oxlintrc.json b/.oxlintrc.json new file mode 100644 index 0000000..4fc4d20 --- /dev/null +++ b/.oxlintrc.json @@ -0,0 +1,22 @@ +{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + "env": { + "node": true, + "es6": true + }, + "globals": { + "Bun": "readonly" + }, + "ignorePatterns": [ + ".opencode/**" + ], + "overrides": [ + { + "files": ["**/*.test.ts"], + "globals": { + "test": "readonly", + "expect": "readonly" + } + } + ] +} diff --git a/README.md b/README.md index 38e8d87..4d4522d 100644 --- a/README.md +++ b/README.md @@ -19,16 +19,16 @@ GraphSpec 想做的,是把"架构态势感知"从"人脑记住项目结构"这 ## 当前进度 -**V0.1** - 最小可用起点(规划中) +**V0.1** - 起盘阶段(规范已立,代码仍是骨架) | 模块 | 当前状态 | 说明 | | --- | --- | --- | -| CLI | 待开发 | `graphspec init` / `graphspec view` | -| Core | 待开发 | 架构图解析、数据存储 | -| Web | 待开发 | 本地可视化服务 | -| 项目基建 | 已完成 | GitHub 配置、AGENTS.md、README.md | +| CLI | 骨架占位 | 仅保留可执行入口与 hello world 级别占位输出 | +| Core | 骨架占位 | 仅保留包入口与最小占位 API | +| Web | 骨架占位 | 仅保留包入口与最小占位 API | +| 项目基建 | 已完成 | GitHub 配置、AGENTS.md、README.md、OpenSpec change、workspace 骨架 | -如果只看仓库现状,可以把它理解成:**基础设施已经搭好,核心功能开发即将开始。** +如果只看仓库现状,可以把它理解成:**规范和仓库骨架已经搭好,真实功能开发还没有开始。** ## 技术栈 @@ -66,40 +66,41 @@ GraphSpec/ git clone https://github.com/OuraAI/GraphSpec.git cd GraphSpec -# 安装依赖(待 packages 初始化后可用) +# 安装依赖 bun install -# 验证命令(待 packages 初始化后可用) +# 验证命令 bun test # 运行测试 -bun lint # 运行 lint 检查 -bun typecheck # 类型检查 +bun run lint # 运行 lint 检查 +bun run typecheck # 类型检查 -# 开发模式(待实现) -bun run dev +# 占位入口 +bun run graphspec ``` ## 模块说明 ### CLI (`packages/cli/`) - **职责**: 命令行入口,用户交互 -- **命令**: `init`, `view`, `sync` +- **当前状态**: 仅占位入口 - **依赖**: `@graphspec/core` ### Core (`packages/core/`) - **职责**: 核心解析和数据处理 -- **能力**: 架构图解析、数据存储、变更检测 +- **当前状态**: 仅占位 API - **接口**: TypeScript API ### Web (`packages/web/`) - **职责**: 本地可视化服务 -- **入口**: `localhost:3000` +- **当前状态**: 仅占位 API ## 开发路线 GraphSpec 遵循 AI Native 开发模式,从最小起点开始,根据实际需求逐步扩展。 ### 近期目标 -- [ ] CLI 基础框架搭建 +- [x] OpenSpec change 与实现任务拆解 +- [x] workspace / package 骨架搭建 - [ ] `init` 命令实现 - [ ] `view` 命令实现 - [ ] 本地可视化页面 diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..163522b --- /dev/null +++ b/bun.lock @@ -0,0 +1,90 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "graphspec", + "devDependencies": { + "@types/bun": "latest", + "oxlint": "^1.59.0", + "typescript": "^5.9.3", + }, + }, + "packages/cli": { + "name": "@graphspec/cli", + "version": "0.1.0", + "bin": { + "graphspec": "./src/bin.ts", + }, + "dependencies": { + "@graphspec/core": "workspace:*", + "@graphspec/web": "workspace:*", + }, + }, + "packages/core": { + "name": "@graphspec/core", + "version": "0.1.0", + }, + "packages/web": { + "name": "@graphspec/web", + "version": "0.1.0", + }, + }, + "packages": { + "@graphspec/cli": ["@graphspec/cli@workspace:packages/cli"], + + "@graphspec/core": ["@graphspec/core@workspace:packages/core"], + + "@graphspec/web": ["@graphspec/web@workspace:packages/web"], + + "@oxlint/binding-android-arm-eabi": ["@oxlint/binding-android-arm-eabi@1.59.0", "", { "os": "android", "cpu": "arm" }, "sha512-etYDw/UaEv936AQUd/CRMBVd+e+XuuU6wC+VzOv1STvsTyZenLChepLWqLtnyTTp4YMlM22ypzogDDwqYxv5cg=="], + + "@oxlint/binding-android-arm64": ["@oxlint/binding-android-arm64@1.59.0", "", { "os": "android", "cpu": "arm64" }, "sha512-TgLc7XVLKH2a4h8j3vn1MDjfK33i9MY60f/bKhRGWyVzbk5LCZ4X01VZG7iHrMmi5vYbAp8//Ponigx03CLsdw=="], + + "@oxlint/binding-darwin-arm64": ["@oxlint/binding-darwin-arm64@1.59.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-DXyFPf5ZKldMLloRHx/B9fsxsiTQomaw7cmEW3YIJko2HgCh+GUhp9gGYwHrqlLJPsEe3dYj9JebjX92D3j3AA=="], + + "@oxlint/binding-darwin-x64": ["@oxlint/binding-darwin-x64@1.59.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-LgvrsdgVLX1qWqIEmNsSmMXJhpAWdtUQ0M+oR0CySwi+9IHWyOGuIL8w8+u/kbZNMyZr4WUyYB5i0+D+AKgkLg=="], + + "@oxlint/binding-freebsd-x64": ["@oxlint/binding-freebsd-x64@1.59.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-bOJhqX/ny4hrFuTPlyk8foSRx/vLRpxJh0jOOKN2NWW6FScXHPAA5rQbrwdQPcgGB5V8Ua51RS03fke8ssBcug=="], + + "@oxlint/binding-linux-arm-gnueabihf": ["@oxlint/binding-linux-arm-gnueabihf@1.59.0", "", { "os": "linux", "cpu": "arm" }, "sha512-vVUXxYMF9trXCsz4m9H6U0IjehosVHxBzVgJUxly1uz4W1PdDyicaBnpC0KRXsHYretLVe+uS9pJy8iM57Kujw=="], + + "@oxlint/binding-linux-arm-musleabihf": ["@oxlint/binding-linux-arm-musleabihf@1.59.0", "", { "os": "linux", "cpu": "arm" }, "sha512-TULQW8YBPGRWg5yZpFPL54HLOnJ3/HiX6VenDPi6YfxB/jlItwSMFh3/hCeSNbh+DAMaE1Py0j5MOaivHkI/9Q=="], + + "@oxlint/binding-linux-arm64-gnu": ["@oxlint/binding-linux-arm64-gnu@1.59.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-Gt54Y4eqSgYJ90xipm24xeyaPV854706o/kiT8oZvUt3VDY7qqxdqyGqchMaujd87ib+/MXvnl9WkK8Cc1BExg=="], + + "@oxlint/binding-linux-arm64-musl": ["@oxlint/binding-linux-arm64-musl@1.59.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-3CtsKp7NFB3OfqQzbuAecrY7GIZeiv7AD+xutU4tefVQzlfmTI7/ygWLrvkzsDEjTlMq41rYHxgsn6Yh8tybmA=="], + + "@oxlint/binding-linux-ppc64-gnu": ["@oxlint/binding-linux-ppc64-gnu@1.59.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-K0diOpT3ncDmOfl9I1HuvpEsAuTxkts0VYwIv/w6Xiy9CdwyPBVX88Ga9l8VlGgMrwBMnSY4xIvVlVY/fkQk7Q=="], + + "@oxlint/binding-linux-riscv64-gnu": ["@oxlint/binding-linux-riscv64-gnu@1.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-xAU7+QDU6kTJJ7mJLOGgo7oOjtAtkKyFZ0Yjdb5cEo3DiCCPFLvyr08rWiQh6evZ7RiUTf+o65NY/bqttzJiQQ=="], + + "@oxlint/binding-linux-riscv64-musl": ["@oxlint/binding-linux-riscv64-musl@1.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-KUmZmKlTTyauOnvUNVxK7G40sSSx0+w5l1UhaGsC6KPpOYHenx2oqJTnabmpLJicok7IC+3Y6fXAUOMyexaeJQ=="], + + "@oxlint/binding-linux-s390x-gnu": ["@oxlint/binding-linux-s390x-gnu@1.59.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-4usRxC8gS0PGdkHnRmwJt/4zrQNZyk6vL0trCxwZSsAKM+OxhB8nKiR+mhjdBbl8lbMh2gc3bZpNN/ik8c4c2A=="], + + "@oxlint/binding-linux-x64-gnu": ["@oxlint/binding-linux-x64-gnu@1.59.0", "", { "os": "linux", "cpu": "x64" }, "sha512-s/rNE2gDmbwAOOP493xk2X7M8LZfI1LJFSSW1+yanz3vuQCFPiHkx4GY+O1HuLUDtkzGlhtMrIcxxzyYLv308w=="], + + "@oxlint/binding-linux-x64-musl": ["@oxlint/binding-linux-x64-musl@1.59.0", "", { "os": "linux", "cpu": "x64" }, "sha512-+yYj1udJa2UvvIUmEm0IcKgc0UlPMgz0nsSTvkPL2y6n0uU5LgIHSwVu4AHhrve6j9BpVSoRksnz8c9QcvITJA=="], + + "@oxlint/binding-openharmony-arm64": ["@oxlint/binding-openharmony-arm64@1.59.0", "", { "os": "none", "cpu": "arm64" }, "sha512-bUplUb48LYsB3hHlQXP2ZMOenpieWoOyppLAnnAhuPag3MGPnt+7caxE3w/Vl9wpQsTA3gzLntQi9rxWrs7Xqg=="], + + "@oxlint/binding-win32-arm64-msvc": ["@oxlint/binding-win32-arm64-msvc@1.59.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-/HLsLuz42rWl7h7ePdmMTpHm2HIDmPtcEMYgm5BBEHiEiuNOrzMaUpd2z7UnNni5LGN9obJy2YoAYBLXQwazrA=="], + + "@oxlint/binding-win32-ia32-msvc": ["@oxlint/binding-win32-ia32-msvc@1.59.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-rUPy+JnanpPwV/aJCPnxAD1fW50+XPI0VkWr7f0vEbqcdsS8NpB24Rw6RsS7SdpFv8Dw+8ugCwao5nCFbqOUSg=="], + + "@oxlint/binding-win32-x64-msvc": ["@oxlint/binding-win32-x64-msvc@1.59.0", "", { "os": "win32", "cpu": "x64" }, "sha512-xkE7puteDS/vUyRngLXW0t8WgdWoS/tfxXjhP/P7SMqPDx+hs44SpssO3h3qmTqECYEuXBUPzcAw5257Ka+ofA=="], + + "@types/bun": ["@types/bun@1.3.11", "", { "dependencies": { "bun-types": "1.3.11" } }, "sha512-5vPne5QvtpjGpsGYXiFyycfpDF2ECyPcTSsFBMa0fraoxiQyMJ3SmuQIGhzPg2WJuWxVBoxWJ2kClYTcw/4fAg=="], + + "@types/node": ["@types/node@25.6.0", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ=="], + + "bun-types": ["bun-types@1.3.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-1KGPpoxQWl9f6wcZh57LvrPIInQMn2TQ7jsgxqpRzg+l0QPOFvJVH7HmvHo/AiPgwXy+/Thf6Ov3EdVn1vOabg=="], + + "oxlint": ["oxlint@1.59.0", "", { "optionalDependencies": { "@oxlint/binding-android-arm-eabi": "1.59.0", "@oxlint/binding-android-arm64": "1.59.0", "@oxlint/binding-darwin-arm64": "1.59.0", "@oxlint/binding-darwin-x64": "1.59.0", "@oxlint/binding-freebsd-x64": "1.59.0", "@oxlint/binding-linux-arm-gnueabihf": "1.59.0", "@oxlint/binding-linux-arm-musleabihf": "1.59.0", "@oxlint/binding-linux-arm64-gnu": "1.59.0", "@oxlint/binding-linux-arm64-musl": "1.59.0", "@oxlint/binding-linux-ppc64-gnu": "1.59.0", "@oxlint/binding-linux-riscv64-gnu": "1.59.0", "@oxlint/binding-linux-riscv64-musl": "1.59.0", "@oxlint/binding-linux-s390x-gnu": "1.59.0", "@oxlint/binding-linux-x64-gnu": "1.59.0", "@oxlint/binding-linux-x64-musl": "1.59.0", "@oxlint/binding-openharmony-arm64": "1.59.0", "@oxlint/binding-win32-arm64-msvc": "1.59.0", "@oxlint/binding-win32-ia32-msvc": "1.59.0", "@oxlint/binding-win32-x64-msvc": "1.59.0" }, "peerDependencies": { "oxlint-tsgolint": ">=0.18.0" }, "optionalPeers": ["oxlint-tsgolint"], "bin": { "oxlint": "bin/oxlint" } }, "sha512-0xBLeGGjP4vD9pygRo8iuOkOzEU1MqOnfiOl7KYezL/QvWL8NUg6n03zXc7ZVqltiOpUxBk2zgHI3PnRIEdAvw=="], + + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="], + } +} diff --git a/openspec/changes/v0-local-cli-view/.openspec.yaml b/openspec/changes/v0-local-cli-view/.openspec.yaml new file mode 100644 index 0000000..e49efd1 --- /dev/null +++ b/openspec/changes/v0-local-cli-view/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-04-10 diff --git a/openspec/changes/v0-local-cli-view/design.md b/openspec/changes/v0-local-cli-view/design.md new file mode 100644 index 0000000..a04207c --- /dev/null +++ b/openspec/changes/v0-local-cli-view/design.md @@ -0,0 +1,112 @@ +## 上下文 + +当前仓库已经有项目定位、技术栈和 OpenSpec 基础设施,也已经有一个最小 workspace 骨架,但还没有正式进入真实功能实现。问题不在于“怎么尽快把依赖图代码写出来”,而在于先把 GraphSpec 的上位定位和演进路径钉住,否则它很容易退化成一个孤立的可视化 demo。 + +GraphSpec 的合理位置,不是和 OpenSpec 竞争同一层职责,而是成为它的互补层: + +- **OpenSpec** 更适合承载 proposal、design、tasks、acceptance、change history 这些文本化和流程化资产。 +- **GraphSpec** 更适合承载结构关系、影响半径、架构草稿、图级变更和空间化浏览。 + +如果把两者放在一起看,理想链路不是 `Issue -> Code`,也不是 `Spec -> Code`,而是: + +`Issue -> Spec -> Graph -> Code -> Validation` + +其中 `Graph` 不是文档附图,而是一份可查询、可演化、未来还能被 AI 调用的工程资产。 + +这意味着 Phase 1 不能只考虑“先做个图”。它还必须为后续两件事留出生长路径: + +1. Graph 能被显式编辑,而不是永远只能被代码扫描结果覆盖。 +2. AI 能通过低噪声、可治理的执行入口使用 GraphSpec,而不是靠 prompt 描述去“想象架构”。 + +## 目标 / 非目标 + +**Goals:** + +- 固定 GraphSpec 与 OpenSpec 的职责分层,避免产品定位从一开始就发虚。 +- 用一个足够小的 Phase 1 起点打通第一条真实闭环:本地生成 graph、查看 graph、回答基本影响半径问题。 +- 保留后续演进路径:从 read-only graph 走向 writable CLI,再走向 Skill 封装和更深的 OpenSpec 桥接。 +- 保持规划粗粒度,让后续 issue-driven / spec-driven 工作流可以按阶段继续细化,而不是现在一次性写死所有细节。 + +**Non-Goals:** + +- 不在这次 change 里细化 Phase 2 以后的完整命令协议、数据模型和交互细节。 +- 不在这次 change 里把 GraphSpec 直接扩成 MCP Server、桌面端或云服务。 +- 不把 GraphSpec 规划成 OpenSpec 的替代品,也不要求它在 Phase 1 就承担 spec 编排职责。 +- 不在 Phase 1 引入复杂语义架构理解、业务域自动识别或多语言统一图模型。 + +## 关键决策 + +### 1. GraphSpec 被定义为 OpenSpec 的互补控制面,而不是替代品 + +- 决定:GraphSpec 的主要职责是承载架构上下文、结构浏览和可操作 graph;OpenSpec 继续负责变更提案、设计推演、任务分解和验收口径。 +- 原因:如果两者职责重叠,项目会同时维护两套半吊子的规格系统;分层清楚后,GraphSpec 才能专注解决 OpenSpec 难以覆盖的结构认知问题。 +- 备选方案: + - 让 GraphSpec 直接替代 OpenSpec:会把项目拖回“重新造一个 spec framework”的方向。 + - 完全切断两者关系:会失去 `Issue -> Spec -> Graph -> Code` 这条更完整的控制链路。 + +### 2. 演进路径采用 CLI-first,而不是直接跳到 MCP 或 Agent 写入 + +- 决定:产品路线按 `read-only CLI/view -> writable CLI -> Skill 封装 -> MCP/更多 AI 接入` 递进。 +- 原因:CLI 是最低噪声、最低开发成本、最容易验证和治理的执行面。先把 CLI 做稳,再让 Skill 调 CLI,比一开始就上 MCP 或直接做 agent 写入更稳妥。 +- 备选方案: + - 直接上 MCP:长期正确,但起步成本高,调试面更复杂。 + - 直接让 Skill 写文件:短期快,但会绕过稳定命令面,后续很难治理。 + +### 3. Phase 1 只交付只读 graph 闭环,但必须为“可写 graph”预留边界 + +- 决定:当前 change 仍然只规格化 Phase 1,也就是 `graphspec init [path]`、`graphspec view` 和 `.graphspec/graph.json` 这条只读链路。 +- 原因:GraphSpec 的第一价值必须是“把结构看清楚”,否则后续写入和 AI 操作都没有可靠对象可依赖。 +- 额外约束:Phase 1 的快照、ID、API 设计必须避免把未来 graph edit 路径完全堵死。 + +### 4. Graph 的“可写”不等于直接改源码,而是先引入独立的 graph 操作面 + +- 决定:未来 Phase 2/3 的 graph editing 优先面向 GraphSpec 自己的 graph 资产和命令面,而不是一上来就承诺“改 graph 就同步改源码”。 +- 原因:如果 graph write 一开始就绑定代码改写,复杂度会陡增,护栏和回滚也会一起失控。先让 graph 成为可编辑草稿和架构意图层,更符合 GraphSpec 的定位。 +- 备选方案: + - graph edit 直接联动代码改写:吸引人,但风险和歧义都太高。 + - 永远只做只读图:无法承接“AI 通过 Skill 调用 CLI 编辑 graph”的产品方向。 + +### 5. GraphSpec 与 OpenSpec 的第一桥接目标是“一致性与校验”,不是“文档替代” + +- 决定:后续 GraphSpec 接入 OpenSpec 时,优先做的是结构一致性检查、架构草稿辅助和 issue/spec/graph/code 对照,而不是把 proposal/design/tasks 本身搬到 graph 里。 +- 原因:文本约束和图结构是两种不同资产。最有价值的桥接方式,是让它们互相校验和互相增信,而不是互相吞并。 +- 备选方案: + - 把 spec 全图化:表达能力会下降,维护成本更高。 + - 让 graph 与 spec 完全无关:会失去项目主线价值。 + +### 6. 当前 change 主要服务于后续 issue 切分,而不是直接全量实施 + +- 决定:当前 change 的 `tasks.md` 采用“issue backlog seed”写法,优先帮助团队后续创建多个聚焦 issue,而不是把全部实现步骤线性排成一张施工单。 +- 原因:你已经明确不会直接把一份 proposal 交给 AI 大推实现,而是会通过多轮 AI 对话、多人接手和 issue-driven 的方式逐步推进。那这份 change 最有价值的产出,就应该是“好切 issue 的上位结构”。 +- 备选方案: + - 把 tasks 写成完整实现分解:适合单线程执行,但不适合当前工作流。 + - 完全不写 tasks:后续 issue 创建会失去统一骨架。 + +### 7. Phase 1 仍然采用三包骨架与本地快照契约 + +- 决定:Phase 1 的实现边界仍固定为 `packages/cli`、`packages/core`、`packages/web` 三包,以及 `.graphspec/graph.json` 作为唯一持久化契约。 +- 原因:这条边界既支持当前最小闭环,也能承接后续 writable CLI 和 Skill 封装。 + +### 8. Phase 1 的 graph 查看能力仍坚持 cluster-first 的最小体验 + +- 决定:第一版 graph 查看仍从 cluster 视图开始,再下钻到 file,并查看直接影响边。 +- 原因:这仍然是最小的用户价值闭环,而且能直接服务后续“AI 查询 blast radius”和“人类审查 graph 变更”的场景。 + +## 风险 / 权衡 + +- [Risk] Proposal 一旦写得太大,团队会误以为现在就要一次性做完全部阶段。 → Mitigation: 在 proposal 和 design 中明确“只规格化 Phase 1,后续阶段通过新的 issues / changes 细化”。 +- [Risk] 把 GraphSpec 和 OpenSpec 关系写得太近,会让职责边界重新混淆。 → Mitigation: 明确 OpenSpec 管文本化 change,GraphSpec 管结构化 graph,并把桥接重点放在一致性而不是替代。 +- [Risk] 过早讨论 Skill / MCP 会把实现节奏拉散。 → Mitigation: 只写演进顺序和边界,不在当前 change 中定义它们的具体协议。 +- [Risk] 如果 Phase 1 只做只读图,团队可能低估“可写 graph”对产品价值的重要性。 → Mitigation: 在 proposal 中把 writable graph 和 AI 调用 CLI 明确列为后续主线,而不是可有可无的增强项。 + +## 推进与迁移计划 + +- 当前仓库已经保留了占位 workspace 和 OpenSpec 资产,因此迁移策略不是直接冲实现,而是按 phase 逐步推进。 +- 当前 change 只承接 Phase 1 的最小闭环;完成后,再分别为 writable graph、Skill/CLI、OpenSpec bridge 创建新的 issues 和 changes。 +- 每个后续 issue 都应只切一个薄 slice,并在需要时生成自己的 proposal / design / tasks,而不是复用当前 change 直接承载所有后续阶段。 +- 如果后续发现产品定位发生变化,应优先更新 proposal/design 的 phase map,而不是让实现先跑、文档后补。 + +## 待后续 issue 细化的问题 + +- 后续的 graph editing 应优先以本地 CLI 命令落地,但具体命令面与 graph draft 模型,刻意留到后续 change 再细化。 +- 第一条 OpenSpec bridge 可以是结构一致性检查、架构草稿生成或 proposal/design 审查辅助,但到底先做哪一条,应在 Phase 1 稳定后通过独立 issue 再决定。 diff --git a/openspec/changes/v0-local-cli-view/proposal.md b/openspec/changes/v0-local-cli-view/proposal.md new file mode 100644 index 0000000..4f31d61 --- /dev/null +++ b/openspec/changes/v0-local-cli-view/proposal.md @@ -0,0 +1,47 @@ +## 背景与动机 + +GraphSpec 不能只被规划成一个“本地依赖图查看器”。如果只是这样,它顶多解决一个局部可视化问题,撑不起后续的 Skill、CLI、MCP、IssueDriven、SpecDriven 这些主线能力,也无法解释它为什么值得作为这本书和这个仓库的实战项目。 + +更准确的定位是:**OpenSpec 解决“需求、设计、任务如何被收敛并持续推进”,GraphSpec 解决“系统架构上下文如何被可视化、可查询、可编辑、可供 AI 调用”。** 它不是 OpenSpec 的替代品,而是和 OpenSpec 互补的一层架构控制面。OpenSpec 偏文本化、变更化、规格化;GraphSpec 偏结构化、空间化、可操作化。GraphSpec 要解决的,正是 OpenSpec 和纯文本 spec 流程里天然存在的几个痛点: + +- 规格是线性的,但系统结构是网状的,跨模块影响半径很难在文字里稳定呈现。 +- 需求、Spec、架构、代码容易各自演化,缺少一个可以被人和 AI 共同读取的中间结构层。 +- AI 即使拿到了 proposal/design/tasks,也仍然不天然具备“当前系统长什么样、改哪里会波及谁”的空间认知。 +- 文本规范更适合定义意图和边界,但不适合承载持续演进的架构态势、依赖拓扑和变更草稿。 + +因此,这次 proposal 需要先把 **GraphSpec 从项目起盘到 AI 能通过 Skill 调用 CLI 去读写 graph** 的粗粒度路线图钉住。后续每一阶段再用 issue-driven 和 spec-driven 方式逐步细化,而不是一开始就把所有阶段写成巨细无遗的大规格。 + +## 本次变更 + +- 明确 GraphSpec 的产品定位:它是 OpenSpec 的互补层,不负责替代 spec 流程,而是把“架构上下文”变成一等工程资产。 +- 明确 GraphSpec 主要解决的痛点: + - 让人和 AI 都能从宏观结构逐步下钻,而不是靠人脑记项目全貌。 + - 让影响半径、依赖关系、架构草稿和结构差异成为可浏览、可审查、可操作的对象。 + - 在 Issue / Spec / Graph / Code 之间建立更稳定的中间层,降低文字和实现之间的漂移。 +- 定义分阶段演进路径,但只保持粗粒度,不展开成每个阶段的细规格: + - **Phase 0 - 起盘与规范底板**:仓库骨架、OpenSpec change、README、包边界、最小占位实现。 + - **Phase 1 - 只读 Graph**:本地 CLI 扫描项目,生成轻量 graph 快照,并提供基础 graph 查看能力。 + - **Phase 2 - 可写 Graph 操作面**:引入本地 graph 编辑命令和变更草稿能力,让 graph 不只是可看,还能被显式修改和审查。 + - **Phase 3 - Skill + CLI 驱动的 AI Graph 编辑**:把稳定的 CLI 能力封装成 Skill / agent workflow,让 AI 可以查询 graph、提交 graph 修改请求,并走护栏流程。 + - **Phase 4 - OpenSpec / IssueDriven / SpecDriven 桥接**:让 GraphSpec 不只是图工具,而是能参与 Issue、Spec、Graph、Code 一致性检查与同步的控制面。 +- 明确这份 change 的用途更接近 **program seed / roadmap anchor**:后续开发应以 issue-driven 方式逐项切分,每个重要 issue 再通过多轮 AI 对话产出自己的 proposal / design / tasks,而不是直接拿当前 change 全量开工。 +- 明确当前这个 change 的职责:**只正式规格化 Phase 1 的最小闭环**,也就是本地 CLI + 本地只读视图 + 轻量 graph 快照;Phase 2 及以后只在 proposal/design 中保留方向,不在这次 change 里细化为可直接实现的 requirements。 +- 明确当前阶段的非目标:本 change 不定义云端、MCP Server、桌面宿主、多语言扫描、复杂语义推断,也不在现在就锁定未来 graph edit 命令的完整协议。 + +## 能力范围 + +### 新增 Capabilities +- `cli-init-view`:覆盖 Phase 1 的本地 CLI 命令面与只读 graph 查看流程。 +- `graph-schema`:覆盖 Phase 1 的 `.graphspec/` 快照契约、图元实体与 cluster 聚合规则。 + +### 修改中的 Capabilities + +无。 + +## 影响面 + +- Proposal 层影响:`proposal.md` 不再是一份单点功能说明,而是后续 issues / changes 可以复用的产品路线图锚点。 +- Design 层影响:`design.md` 不只解释 Phase 1 的实现形态,还要说明 GraphSpec 为什么沿着 `CLI-first -> writable CLI -> Skill -> OpenSpec bridge` 这条路线演进。 +- Tasks 层影响:`tasks.md` 应当充当 issue 拆分种子,帮助团队创建多个聚焦 issue,而不是形成一条巨型实现线程。 +- Spec 层影响:当前 change 依旧只创建 Phase 1 的 capability specs,刻意保持当前实现范围收紧。 +- 后续工作流影响:graph editing、Skill 集成、OpenSpec bridge 都应该以新的 issues / changes 独立落地,而不是被悄悄塞进这次 Phase 1 实现。 diff --git a/openspec/changes/v0-local-cli-view/specs/cli-init-view/spec.md b/openspec/changes/v0-local-cli-view/specs/cli-init-view/spec.md new file mode 100644 index 0000000..a467faf --- /dev/null +++ b/openspec/changes/v0-local-cli-view/specs/cli-init-view/spec.md @@ -0,0 +1,60 @@ +## ADDED Requirements + +### Requirement: init 命令生成或刷新本地快照 +CLI 必须提供 `graphspec init [path]` 命令,用于扫描目标 JS/TS 项目根目录,并创建或刷新合法的 GraphSpec V1 `.graphspec/graph.json` 快照。 + +#### Scenario: 在默认目录生成快照 +- **WHEN** 用户在受支持的 JS/TS 项目根目录中执行 `graphspec init` +- **THEN** 命令会在当前项目根目录下生成 `.graphspec/graph.json` + +#### Scenario: 在指定目录刷新快照 +- **WHEN** 用户对一个已经包含 `.graphspec/graph.json` 的项目执行 `graphspec init /path/to/project` +- **THEN** 命令会刷新目标项目中的快照,而不是把它当成一次性初始化命令 + +### Requirement: init 只承诺 JS/TS 静态依赖扫描 +`graphspec init` 命令在 V0.1 中必须只分析 JavaScript / TypeScript 源文件,并且只产出来自静态 `import` 或 `export ... from` 语法的依赖关系。 + +#### Scenario: 遇到非 JS/TS 文件 +- **WHEN** 扫描器遇到 Rust、Python、Go 或其他非 JS/TS 文件 +- **THEN** 这些文件会被排除在快照生成之外,不会把 graph 契约扩展到 V0.1 范围之外 + +#### Scenario: 遇到静态依赖语法 +- **WHEN** 某个 JS/TS 源文件中包含相对路径静态 import 或 re-export +- **THEN** 生成的快照中会包含一条 `static-import` 边,把导入方文件节点连接到解析后的目标文件节点 + +### Requirement: view 命令只渲染已有快照 +CLI 必须提供 `graphspec view` 命令,该命令基于现有 `.graphspec/graph.json` 快照启动本地只读可视化服务,并且不能隐式触发重新扫描。 + +#### Scenario: 快照存在时启动本地服务 +- **WHEN** 用户在已经存在 `.graphspec/graph.json` 的项目根目录中执行 `graphspec view` +- **THEN** 命令会启动本地 HTTP 服务,并基于该快照渲染浏览器可访问的可视化页面 + +#### Scenario: 快照缺失时返回可操作错误 +- **WHEN** 用户在不存在 `.graphspec/graph.json` 的项目根目录中执行 `graphspec view` +- **THEN** 命令会以可操作错误退出,并明确提示用户先执行 `graphspec init` + +### Requirement: view 提供只读的 cluster-first blast radius 浏览 +`graphspec view` 的默认体验必须先展示顶层 cluster 概览,必须允许继续下钻到 file 节点,并且必须为被查看的 file 节点展示直接入边与出边。 + +#### Scenario: 打开页面先看到 cluster 概览 +- **WHEN** `graphspec view` 成功启动后浏览器页面完成加载 +- **THEN** 页面初始状态展示的是顶层 cluster 概览,而不是平铺的文件图 + +#### Scenario: 下钻后查看文件直接影响范围 +- **WHEN** 用户下钻进入某个 cluster 并选中一个 file 节点 +- **THEN** 页面会展示该 file 节点直接相关的入边和出边 `static-import`,以支持 blast radius review + +### Requirement: view 通过本地 API 暴露只读图查询 +由 `graphspec view` 启动的本地服务必须提供只读 HTTP 接口,用于概览查询、cluster 下钻和 node blast radius 查询;Web 客户端必须消费这些接口,而不是直接读取快照文件。 + +#### Scenario: 请求概览数据 +- **WHEN** Web 客户端请求 `GET /api/overview` +- **THEN** 服务会返回基于当前快照生成的顶层 cluster 概览 + +#### Scenario: 请求 cluster 子项 +- **WHEN** Web 客户端请求 `GET /api/clusters/:clusterId` +- **THEN** 服务会返回该 cluster 的直接子 cluster、file 节点以及边摘要 + +#### Scenario: 请求 node 直接依赖 +- **WHEN** Web 客户端请求 `GET /api/nodes/:nodeId` +- **THEN** 服务会返回当前快照中该节点的直接入边和出边 diff --git a/openspec/changes/v0-local-cli-view/specs/graph-schema/spec.md b/openspec/changes/v0-local-cli-view/specs/graph-schema/spec.md new file mode 100644 index 0000000..a8b74fc --- /dev/null +++ b/openspec/changes/v0-local-cli-view/specs/graph-schema/spec.md @@ -0,0 +1,52 @@ +## ADDED Requirements + +### Requirement: 快照采用轻量 GraphSpec V1 结构 +系统必须将 GraphSpec V1 快照持久化到 `.graphspec/graph.json`,并且每个快照顶层都必须包含 `schemaVersion`、`generatedAt`、`projectRoot`、`nodes`、`edges` 和 `clusters`。 + +#### Scenario: 生成合法的顶层结构 +- **WHEN** `graphspec init` 成功完成 +- **THEN** `.graphspec/graph.json` 会包含 GraphSpec V1 所要求的全部顶层字段 + +### Requirement: 快照中的图元类型必须受限且稳定 +快照必须将节点类型限制为 `file`、`external`、`cluster`,必须将边类型限制为 `static-import`,并且必须使用以下稳定格式生成 ID:`file:`、`external:`、`cluster:`、`static-import:->`。 + +#### Scenario: 为文件和外部依赖分配稳定 ID +- **WHEN** 扫描器对同一个项目重复产出 file 与 external 节点 +- **THEN** 只要相对路径和依赖 specifier 没有变化,这些节点的 ID 就保持稳定 + +#### Scenario: 不引入额外图元类型 +- **WHEN** 扫描器遇到 V0.1 范围之外的信息,例如路由、API 调用或运行期 wiring +- **THEN** 快照仍然只产出 GraphSpec V1 允许的节点和边类型 + +### Requirement: cluster 必须按既定路径启发式生成 +快照必须按固定的路径启发式生成顶层 cluster,顺序为:优先按 `apps/*` 或 `packages/*` 的 workspace package 聚合;否则按 `src/` 下一级目录聚合;再否则按顶层源码目录聚合。 + +#### Scenario: monorepo 按 workspace package 聚合 +- **WHEN** 项目中的源码文件位于 `apps/*` 或 `packages/*` 之下 +- **THEN** 顶层 cluster 会按对应的 workspace package 聚合,而不是按单个文件平铺 + +#### Scenario: 单包项目按 src 一级目录聚合 +- **WHEN** 项目中不存在 `apps/*` 或 `packages/*`,但存在 `src/` +- **THEN** 顶层 cluster 会按 `src/` 下的一级目录生成 + +#### Scenario: 无 src 时回退到顶层源码目录 +- **WHEN** 项目既没有 workspace packages,也没有 `src/` 容器目录 +- **THEN** 顶层 cluster 会回退为按包含被扫描 JS/TS 文件的顶层源码目录生成 + +### Requirement: clusters 必须支持两层浏览所需的成员关系 +`clusters` 集合必须记录足够的成员关系信息,以支持顶层概览和下钻,包括每个 cluster 的稳定 ID、展示标签、父 cluster 引用(如果存在)、直接子 cluster ID 和直接子 file 节点 ID。 + +#### Scenario: 顶层 cluster 可直接渲染概览 +- **WHEN** Web 客户端加载 overview +- **THEN** 快照能够直接提供足够的 cluster 元数据,无需再从文件路径重新推导成员关系就能渲染顶层 cluster 列表 + +#### Scenario: 下钻 cluster 时可读取直接成员 +- **WHEN** Web 客户端请求某个具体的 cluster +- **THEN** 快照能够提供该 cluster 的直接子 cluster 和 file 节点 ID,以支持下钻渲染 + +### Requirement: 循环依赖不会阻塞快照生成 +快照生成器必须能够容忍静态 import 形成的循环依赖,并将其表示为普通的有向 `static-import` 边,而不会导致快照生成失败。 + +#### Scenario: 存在循环依赖 +- **WHEN** 两个或多个 JS/TS 文件通过静态 import 相互引用 +- **THEN** `graphspec init` 仍然会成功完成,并且快照中会包含对应的有向边 diff --git a/openspec/changes/v0-local-cli-view/tasks.md b/openspec/changes/v0-local-cli-view/tasks.md new file mode 100644 index 0000000..31fa7fd --- /dev/null +++ b/openspec/changes/v0-local-cli-view/tasks.md @@ -0,0 +1,39 @@ +## 1. 总纲与 Issue 约束 + +- [ ] 1.1 把当前 change 作为 GraphSpec 的 program seed,后续创建 issue 时统一引用这份 phase map,而不是每次重新定义产品方向 +- [ ] 1.2 约定后续每个重要 issue 都只承载一个薄 slice,并在必要时产出自己的 proposal / design / tasks,而不是直接复用当前 change 全量开发 +- [ ] 1.3 约定 issue 至少要标明:所属 phase、要解决的痛点、明确非目标、验收口径,以及是否涉及 OpenSpec 桥接 / Skill / CLI / graph schema + +## 2. Phase 0 Issue 种子:起盘与底板 + +- [ ] 2.1 创建 Phase 0 issue:仓库骨架、workspace、基础命令入口与包边界 +- [ ] 2.2 创建 Phase 0 issue:README、AGENTS、OpenSpec 资产与项目协作底板补齐 +- [ ] 2.3 创建 Phase 0 issue:占位实现、lint、typecheck、test 基线与最小开发约束 + +## 3. Phase 1 Issue 种子:只读 Graph + +- [ ] 3.1 创建 Phase 1 issue:GraphSpec V1 快照契约与 `.graphspec/graph.json` 的最小 schema +- [ ] 3.2 创建 Phase 1 issue:JS/TS 静态依赖扫描与基础图构建 +- [ ] 3.3 创建 Phase 1 issue:cluster 聚合规则与 blast radius 所需的最小视图模型 +- [ ] 3.4 创建 Phase 1 issue:`graphspec init [path]` 命令与快照生成流程 +- [ ] 3.5 创建 Phase 1 issue:`graphspec view` 本地服务与只读 graph API +- [ ] 3.6 创建 Phase 1 issue:cluster-first 查看界面与 file 级直接影响范围展示 +- [ ] 3.7 创建 Phase 1 issue:验收测试、fixture、README 用法与错误路径验证 + +## 4. Phase 2 Issue 种子:可写 Graph 操作面 + +- [ ] 4.1 创建 Phase 2 issue:graph draft / graph edit 的产品边界,明确“编辑 graph”不等于直接改源码 +- [ ] 4.2 创建 Phase 2 issue:本地 graph 变更命令面,明确哪些编辑操作要先支持 +- [ ] 4.3 创建 Phase 2 issue:graph 变更草稿、审查、回滚与人类确认护栏 + +## 5. Phase 3 Issue 种子:Skill + CLI 驱动的 AI Graph 编辑 + +- [ ] 5.1 创建 Phase 3 issue:把稳定 CLI 能力封装成 Skill 的输入输出契约 +- [ ] 5.2 创建 Phase 3 issue:AI 查询 graph、提交 graph 修改请求、查看执行结果的工作流设计 +- [ ] 5.3 创建 Phase 3 issue:Skill 调 CLI 的低噪声约束、失败处理与审计要求 + +## 6. Phase 4 Issue 种子:OpenSpec Bridge + +- [ ] 6.1 创建 Phase 4 issue:Issue / Spec / Graph / Code 一致性检查的第一条桥接链路 +- [ ] 6.2 创建 Phase 4 issue:GraphSpec 如何辅助 proposal / design 审查,而不是替代 OpenSpec 文本资产 +- [ ] 6.3 创建 Phase 4 issue:后续是否需要 MCP 或更多 AI 接入形态,并基于 CLI-first 成熟度再决定是否立项 diff --git a/package.json b/package.json new file mode 100644 index 0000000..4155454 --- /dev/null +++ b/package.json @@ -0,0 +1,21 @@ +{ + "name": "graphspec", + "version": "0.1.0", + "private": true, + "type": "module", + "workspaces": [ + "packages/*" + ], + "scripts": { + "graphspec": "bun run packages/cli/src/bin.ts", + "dev": "bun run packages/cli/src/bin.ts", + "lint": "oxlint .", + "test": "bun test", + "typecheck": "tsc -p tsconfig.json --noEmit" + }, + "devDependencies": { + "@types/bun": "latest", + "oxlint": "^1.59.0", + "typescript": "^5.9.3" + } +} diff --git a/packages/cli/README.md b/packages/cli/README.md new file mode 100644 index 0000000..00eb052 --- /dev/null +++ b/packages/cli/README.md @@ -0,0 +1,39 @@ +# @graphspec/cli + +`@graphspec/cli` 现在只保留最小命令行骨架,用来验证 monorepo wiring、命令入口和包依赖关系已经成立。 + +## 这个包是干什么的 + +- 提供一个可执行的 `graphspec` 占位入口 +- 串起 `@graphspec/core` 和 `@graphspec/web` 的 hello world 输出 +- 为后续真正命令实现预留目录和导出边界 + +## 当前已经能做什么 + +- 执行占位 CLI 输出 +- 验证 workspace 脚手架可跑通 + +## 当前明确不做什么 + +- 不实现 `init` +- 不实现 `view` +- 不启动真实服务 +- 不触达任何 GraphSpec 产品能力 + +## 公共 API / 子路径导出 + +- `@graphspec/cli` + - `runCli(argv, io?)` + +## 最小使用示例 + +```bash +bun run graphspec +``` + +## 测试 / 真实验收怎么跑 + +```bash +bun test +bun run graphspec +``` diff --git a/packages/cli/package.json b/packages/cli/package.json new file mode 100644 index 0000000..1993ac9 --- /dev/null +++ b/packages/cli/package.json @@ -0,0 +1,20 @@ +{ + "name": "@graphspec/cli", + "version": "0.1.0", + "type": "module", + "bin": { + "graphspec": "./src/bin.ts" + }, + "exports": { + ".": "./src/index.ts" + }, + "dependencies": { + "@graphspec/core": "workspace:*", + "@graphspec/web": "workspace:*" + }, + "scripts": { + "dev": "bun run src/bin.ts", + "test": "bun test", + "typecheck": "tsc -p ../../tsconfig.json --noEmit" + } +} diff --git a/packages/cli/src/bin.ts b/packages/cli/src/bin.ts new file mode 100644 index 0000000..bf88b20 --- /dev/null +++ b/packages/cli/src/bin.ts @@ -0,0 +1,7 @@ +import { runCli } from "./index.ts"; + +const exitCode = await runCli(Bun.argv.slice(2)); + +if (exitCode !== 0) { + process.exit(exitCode); +} diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts new file mode 100644 index 0000000..fcc0c8f --- /dev/null +++ b/packages/cli/src/index.ts @@ -0,0 +1,28 @@ +import { CORE_PLACEHOLDER_MESSAGE } from "@graphspec/core"; +import { WEB_PLACEHOLDER_MESSAGE } from "@graphspec/web"; + +export interface CliIo { + error(message: string): void; + log(message: string): void; +} + +const defaultIo: CliIo = { + error(message) { + console.error(message); + }, + log(message) { + console.log(message); + }, +}; + +export async function runCli( + argv: string[], + io: CliIo = defaultIo, +): Promise { + io.log("GraphSpec CLI placeholder"); + io.log(`args: ${argv.join(" ") || "(none)"}`); + io.log(CORE_PLACEHOLDER_MESSAGE); + io.log(WEB_PLACEHOLDER_MESSAGE); + io.log("Real commands will be implemented after the workflow and spec process settle."); + return 0; +} diff --git a/packages/cli/test/smoke.test.ts b/packages/cli/test/smoke.test.ts new file mode 100644 index 0000000..76c0990 --- /dev/null +++ b/packages/cli/test/smoke.test.ts @@ -0,0 +1,21 @@ +import { expect, test } from "bun:test"; + +import { runCli } from "../src/index.ts"; + +test("cli placeholder prints the three skeleton messages", async () => { + const output: string[] = []; + + const exitCode = await runCli(["hello"], { + error(message) { + output.push(`ERR:${message}`); + }, + log(message) { + output.push(message); + }, + }); + + expect(exitCode).toBe(0); + expect(output[0]).toBe("GraphSpec CLI placeholder"); + expect(output.some((line) => line.includes("GraphSpec core placeholder"))).toBe(true); + expect(output.some((line) => line.includes("GraphSpec web placeholder"))).toBe(true); +}); diff --git a/packages/core/README.md b/packages/core/README.md new file mode 100644 index 0000000..0dba00f --- /dev/null +++ b/packages/core/README.md @@ -0,0 +1,42 @@ +# @graphspec/core + +`@graphspec/core` 现在只承担最小骨架角色,用来占住 GraphSpec 核心引擎包的位置,并给后续真实实现留出明确入口。 + +## 这个包是干什么的 + +- 提供核心包入口 +- 暴露一个最小 `hello world` 级别占位 API +- 为后续图扫描和快照逻辑预留包边界 + +## 当前已经能做什么 + +- 暴露 `coreHello()` 作为骨架验证入口 +- 支持 workspace 级 lint、typecheck、test + +## 当前明确不做什么 + +- 不做依赖扫描 +- 不做快照生成 +- 不做图查询或数据存储 +- 不做任何运行时产品能力 + +## 公共 API / 子路径导出 + +- `@graphspec/core` + - `coreHello()` + - `CORE_PLACEHOLDER_MESSAGE` + +## 最小使用示例 + +```ts +import { coreHello } from "@graphspec/core"; + +console.log(coreHello()); +``` + +## 测试 / 真实验收怎么跑 + +```bash +bun test +bun run typecheck +``` diff --git a/packages/core/package.json b/packages/core/package.json new file mode 100644 index 0000000..779e743 --- /dev/null +++ b/packages/core/package.json @@ -0,0 +1,12 @@ +{ + "name": "@graphspec/core", + "version": "0.1.0", + "type": "module", + "exports": { + ".": "./src/index.ts" + }, + "scripts": { + "test": "bun test", + "typecheck": "tsc -p ../../tsconfig.json --noEmit" + } +} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts new file mode 100644 index 0000000..6d4947d --- /dev/null +++ b/packages/core/src/index.ts @@ -0,0 +1,6 @@ +export const CORE_PLACEHOLDER_MESSAGE = + "GraphSpec core placeholder: real graph logic has not started yet."; + +export function coreHello(): string { + return CORE_PLACEHOLDER_MESSAGE; +} diff --git a/packages/core/test/smoke.test.ts b/packages/core/test/smoke.test.ts new file mode 100644 index 0000000..8e2f835 --- /dev/null +++ b/packages/core/test/smoke.test.ts @@ -0,0 +1,7 @@ +import { expect, test } from "bun:test"; + +import { CORE_PLACEHOLDER_MESSAGE, coreHello } from "../src/index.ts"; + +test("core placeholder exposes a stable hello message", () => { + expect(coreHello()).toBe(CORE_PLACEHOLDER_MESSAGE); +}); diff --git a/packages/web/README.md b/packages/web/README.md new file mode 100644 index 0000000..6993c59 --- /dev/null +++ b/packages/web/README.md @@ -0,0 +1,42 @@ +# @graphspec/web + +`@graphspec/web` 现在只保留最小占位包,用来占住本地可视化服务的位置,并验证 web 包的导出边界已建立。 + +## 这个包是干什么的 + +- 提供 web 包入口 +- 暴露一个 hello world 级别占位 API +- 为后续本地可视化服务预留包结构 + +## 当前已经能做什么 + +- 暴露 `webHello()` 占位函数 +- 支持 workspace 级 lint、typecheck、test + +## 当前明确不做什么 + +- 不启动 HTTP 服务 +- 不渲染页面 +- 不提供 API +- 不读写任何项目数据 + +## 公共 API / 子路径导出 + +- `@graphspec/web` + - `webHello()` + - `WEB_PLACEHOLDER_MESSAGE` + +## 最小使用示例 + +```ts +import { webHello } from "@graphspec/web"; + +console.log(webHello()); +``` + +## 测试 / 真实验收怎么跑 + +```bash +bun test +bun run --cwd packages/web dev +``` diff --git a/packages/web/package.json b/packages/web/package.json new file mode 100644 index 0000000..5fe027b --- /dev/null +++ b/packages/web/package.json @@ -0,0 +1,13 @@ +{ + "name": "@graphspec/web", + "version": "0.1.0", + "type": "module", + "exports": { + ".": "./src/index.ts" + }, + "scripts": { + "dev": "bun run src/index.ts", + "test": "bun test", + "typecheck": "tsc -p ../../tsconfig.json --noEmit" + } +} diff --git a/packages/web/src/index.ts b/packages/web/src/index.ts new file mode 100644 index 0000000..691c69a --- /dev/null +++ b/packages/web/src/index.ts @@ -0,0 +1,10 @@ +export const WEB_PLACEHOLDER_MESSAGE = + "GraphSpec web placeholder: local visualization has not started yet."; + +export function webHello(): string { + return WEB_PLACEHOLDER_MESSAGE; +} + +if (import.meta.main) { + console.log(webHello()); +} diff --git a/packages/web/test/smoke.test.ts b/packages/web/test/smoke.test.ts new file mode 100644 index 0000000..89abcc9 --- /dev/null +++ b/packages/web/test/smoke.test.ts @@ -0,0 +1,7 @@ +import { expect, test } from "bun:test"; + +import { WEB_PLACEHOLDER_MESSAGE, webHello } from "../src/index.ts"; + +test("web placeholder exposes a stable hello message", () => { + expect(webHello()).toBe(WEB_PLACEHOLDER_MESSAGE); +}); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..2693b94 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "lib": ["ESNext", "DOM"], + "target": "ESNext", + "module": "Preserve", + "moduleDetection": "force", + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "strict": true, + "skipLibCheck": true, + "noEmit": true, + "noUncheckedIndexedAccess": true, + "noFallthroughCasesInSwitch": true, + "noImplicitOverride": true + }, + "include": ["packages/**/*.ts"] +} From 86dd48642c58886551e2004954d537bbc6603582 Mon Sep 17 00:00:00 2001 From: MengChen Date: Sat, 11 Apr 2026 16:41:51 +0800 Subject: [PATCH 2/4] =?UTF-8?q?docs:=20=E9=87=8D=E6=9E=84=20v0-local-cli-v?= =?UTF-8?q?iew=20AI-Native=20=E6=80=BB=E7=BA=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- openspec/changes/v0-local-cli-view/design.md | 215 ++++++---- .../changes/v0-local-cli-view/proposal.md | 99 +++-- .../specs/cli-init-view/spec.md | 79 ++-- .../specs/graph-schema/spec.md | 74 ++-- openspec/changes/v0-local-cli-view/tasks.md | 402 +++++++++++++++++- 5 files changed, 662 insertions(+), 207 deletions(-) diff --git a/openspec/changes/v0-local-cli-view/design.md b/openspec/changes/v0-local-cli-view/design.md index a04207c..d2912d1 100644 --- a/openspec/changes/v0-local-cli-view/design.md +++ b/openspec/changes/v0-local-cli-view/design.md @@ -1,112 +1,183 @@ ## 上下文 -当前仓库已经有项目定位、技术栈和 OpenSpec 基础设施,也已经有一个最小 workspace 骨架,但还没有正式进入真实功能实现。问题不在于“怎么尽快把依赖图代码写出来”,而在于先把 GraphSpec 的上位定位和演进路径钉住,否则它很容易退化成一个孤立的可视化 demo。 +当前仓库已经有项目定位、OpenSpec 资产和最小 workspace 骨架,但还没有进入真实功能实现。真正的分歧不在“代码怎么写”,而在“GraphSpec 到底在维护什么”。 -GraphSpec 的合理位置,不是和 OpenSpec 竞争同一层职责,而是成为它的互补层: +如果把 GraphSpec 理解成一个 scanner 驱动的文件依赖图工具,后续所有设计都会自动往静态分析工具靠拢: -- **OpenSpec** 更适合承载 proposal、design、tasks、acceptance、change history 这些文本化和流程化资产。 -- **GraphSpec** 更适合承载结构关系、影响半径、架构草稿、图级变更和空间化浏览。 +- 主图来自扫描结果 +- 视图围绕文件树和 import/export 组织 +- AI 只是去读扫描出的结构 -如果把两者放在一起看,理想链路不是 `Issue -> Code`,也不是 `Spec -> Code`,而是: +但这和当前产品定位不符。你已经明确指出了一个更真实的使用场景: -`Issue -> Spec -> Graph -> Code -> Validation` +- 人看到一张整体架构图,第一眼就能明白项目大概是什么结构。 +- 点进某个模块,例如 `Auth`,看到这个模块自己的细图,而不是一堆文件树。 +- 节点之间的关系不是简单树形,而可能是“建立在……之上”“作用于……”“包含 / 被包含”“依赖 / 被依赖”这类架构关系。 +- 代码里的文件、import/export、Guard、查询、配置等,更像是这些节点与关系背后的证据。 -其中 `Graph` 不是文档附图,而是一份可查询、可演化、未来还能被 AI 调用的工程资产。 +所以当前 design 的核心不是把 scanner 做清楚,而是把下面三层边界钉住: -这意味着 Phase 1 不能只考虑“先做个图”。它还必须为后续两件事留出生长路径: - -1. Graph 能被显式编辑,而不是永远只能被代码扫描结果覆盖。 -2. AI 能通过低噪声、可治理的执行入口使用 GraphSpec,而不是靠 prompt 描述去“想象架构”。 +1. **图资产层**:架构图本身,是主资产。 +2. **证据层**:代码、文件树、依赖、片段、摘要,是解释与校验材料。 +3. **视图层**:人和 AI 怎么读图、下钻、查看节点相关内容。 ## 目标 / 非目标 **Goals:** -- 固定 GraphSpec 与 OpenSpec 的职责分层,避免产品定位从一开始就发虚。 -- 用一个足够小的 Phase 1 起点打通第一条真实闭环:本地生成 graph、查看 graph、回答基本影响半径问题。 -- 保留后续演进路径:从 read-only graph 走向 writable CLI,再走向 Skill 封装和更深的 OpenSpec 桥接。 -- 保持规划粗粒度,让后续 issue-driven / spec-driven 工作流可以按阶段继续细化,而不是现在一次性写死所有细节。 +- 把 GraphSpec 收紧成“架构图主资产 + CLI 主入口 + 证据层辅助”的 AI-Native 总纲。 +- 为 Phase 1 建立最小而清晰的方向:整体架构图、模块下钻图、节点相关内容视图。 +- 明确后续 issues 该先收哪些问题,不该在总纲里直接拍板哪些模型。 +- 把 Harness 作为伴随项目演进的轨道,而不是一次性大建设。 **Non-Goals:** -- 不在这次 change 里细化 Phase 2 以后的完整命令协议、数据模型和交互细节。 -- 不在这次 change 里把 GraphSpec 直接扩成 MCP Server、桌面端或云服务。 -- 不把 GraphSpec 规划成 OpenSpec 的替代品,也不要求它在 Phase 1 就承担 spec 编排职责。 -- 不在 Phase 1 引入复杂语义架构理解、业务域自动识别或多语言统一图模型。 +- 不在当前 change 中给出具体 schema 字段和类型名。 +- 不在当前 change 中细化 graph edit 命令协议、graph draft 数据结构或 Skill 协议。 +- 不在当前 change 中把业务流视图与结构视图全部并入 Phase 1。 +- 不让 scanner 继续扮演主图来源。 ## 关键决策 -### 1. GraphSpec 被定义为 OpenSpec 的互补控制面,而不是替代品 +### 1. 架构图是主资产,不是扫描结果 -- 决定:GraphSpec 的主要职责是承载架构上下文、结构浏览和可操作 graph;OpenSpec 继续负责变更提案、设计推演、任务分解和验收口径。 -- 原因:如果两者职责重叠,项目会同时维护两套半吊子的规格系统;分层清楚后,GraphSpec 才能专注解决 OpenSpec 难以覆盖的结构认知问题。 -- 备选方案: - - 让 GraphSpec 直接替代 OpenSpec:会把项目拖回“重新造一个 spec framework”的方向。 - - 完全切断两者关系:会失去 `Issue -> Spec -> Graph -> Code` 这条更完整的控制链路。 +- 决定:GraphSpec 的事实来源是架构图资产本身,而不是扫描器跑出来的文件依赖图。 +- 原因:GraphSpec 面向的是架构视角,不是代码索引视角。整体图、模块图、节点关系这些东西,本来就不必等价于文件结构。 +- 结果:代码证据可以解释图,但不能替图做主。 -### 2. 演进路径采用 CLI-first,而不是直接跳到 MCP 或 Agent 写入 +### 2. CLI 是主入口,数据文件是兜底入口 -- 决定:产品路线按 `read-only CLI/view -> writable CLI -> Skill 封装 -> MCP/更多 AI 接入` 递进。 -- 原因:CLI 是最低噪声、最低开发成本、最容易验证和治理的执行面。先把 CLI 做稳,再让 Skill 调 CLI,比一开始就上 MCP 或直接做 agent 写入更稳妥。 -- 备选方案: - - 直接上 MCP:长期正确,但起步成本高,调试面更复杂。 - - 直接让 Skill 写文件:短期快,但会绕过稳定命令面,后续很难治理。 +- 决定:人和 AI 后续都应优先通过 CLI 读图、看图、校验图、逐步修改图;直接编辑数据文件只作为兜底路径存在。 +- 原因:如果没有主入口,图资产很快会在多人 / 多 Agent / 多轮对话里漂掉;CLI 是最自然的护栏入口。 -### 3. Phase 1 只交付只读 graph 闭环,但必须为“可写 graph”预留边界 +### 3. scanner 降级为证据层 -- 决定:当前 change 仍然只规格化 Phase 1,也就是 `graphspec init [path]`、`graphspec view` 和 `.graphspec/graph.json` 这条只读链路。 -- 原因:GraphSpec 的第一价值必须是“把结构看清楚”,否则后续写入和 AI 操作都没有可靠对象可依赖。 -- 额外约束:Phase 1 的快照、ID、API 设计必须避免把未来 graph edit 路径完全堵死。 +- 决定:scanner 如果存在,它的职责是发现文件、import/export、依赖痕迹、代码片段等证据,并服务于解释和校验。 +- 原因:这些信息对架构图依然有帮助,但不应该决定主图长什么样。 +- 约束:scanner 不能直接覆盖主图资产,也不能定义产品主体验。 -### 4. Graph 的“可写”不等于直接改源码,而是先引入独立的 graph 操作面 +### 4. 关系体系采用多视角,不强行揉成单图 -- 决定:未来 Phase 2/3 的 graph editing 优先面向 GraphSpec 自己的 graph 资产和命令面,而不是一上来就承诺“改 graph 就同步改源码”。 -- 原因:如果 graph write 一开始就绑定代码改写,复杂度会陡增,护栏和回滚也会一起失控。先让 graph 成为可编辑草稿和架构意图层,更符合 GraphSpec 的定位。 -- 备选方案: - - graph edit 直接联动代码改写:吸引人,但风险和歧义都太高。 - - 永远只做只读图:无法承接“AI 通过 Skill 调用 CLI 编辑 graph”的产品方向。 +- 决定:当前总纲承认至少存在三类关系视角: + - 架构结构关系 + - 包含 / 被包含关系 + - 业务流 / 调度流关系 +- 原因:用户举的 `Auth` 例子已经说明,真实架构图里的关系不是单一的“import 了谁”。 +- Phase 1 约束: + - 先收紧结构视图与下钻关系。 + - 业务流视图进入后续 issue。 -### 5. GraphSpec 与 OpenSpec 的第一桥接目标是“一致性与校验”,不是“文档替代” +### 5. Phase 1 的设计方向 -- 决定:后续 GraphSpec 接入 OpenSpec 时,优先做的是结构一致性检查、架构草稿辅助和 issue/spec/graph/code 对照,而不是把 proposal/design/tasks 本身搬到 graph 里。 -- 原因:文本约束和图结构是两种不同资产。最有价值的桥接方式,是让它们互相校验和互相增信,而不是互相吞并。 -- 备选方案: - - 把 spec 全图化:表达能力会下降,维护成本更高。 - - 让 graph 与 spec 完全无关:会失去项目主线价值。 +- 必须支持“整体架构图 -> 模块下钻图 -> 更细层级图”的嵌套关系。 +- 必须支持“点一个节点 -> 看到节点相关内容与最小关系上下文”。 +- 允许在节点上挂接代码证据,但代码证据不能反客为主。 +- 不要求 Phase 1 先把业务流图做完整。 -### 6. 当前 change 主要服务于后续 issue 切分,而不是直接全量实施 +### 6. 当前总纲不拍板模型名称,只收问题池 -- 决定:当前 change 的 `tasks.md` 采用“issue backlog seed”写法,优先帮助团队后续创建多个聚焦 issue,而不是把全部实现步骤线性排成一张施工单。 -- 原因:你已经明确不会直接把一份 proposal 交给 AI 大推实现,而是会通过多轮 AI 对话、多人接手和 issue-driven 的方式逐步推进。那这份 change 最有价值的产出,就应该是“好切 issue 的上位结构”。 -- 备选方案: - - 把 tasks 写成完整实现分解:适合单线程执行,但不适合当前工作流。 - - 完全不写 tasks:后续 issue 创建会失去统一骨架。 +- 决定:当前总纲不直接定义具体类型名,也不在这里拍字段。 +- 原因:现在大家只有模糊概念,还没有真正收敛出“这些模型为什么存在、为什么应该长成这样”。 +- 结果:总纲里只保留“后续 issue 必须回答的设计问题”。 -### 7. Phase 1 仍然采用三包骨架与本地快照契约 +## Phase 1 必须先回答的设计问题 -- 决定:Phase 1 的实现边界仍固定为 `packages/cli`、`packages/core`、`packages/web` 三包,以及 `.graphspec/graph.json` 作为唯一持久化契约。 -- 原因:这条边界既支持当前最小闭环,也能承接后续 writable CLI 和 Skill 封装。 +### 1. 主图资产问题 -### 8. Phase 1 的 graph 查看能力仍坚持 cluster-first 的最小体验 +- 主图资产到底是单文件、多文件,还是带索引的目录结构? +- 它怎么表达整体图、模块图和子图? +- 它如何支持人和 AI 共同读写? -- 决定:第一版 graph 查看仍从 cluster 视图开始,再下钻到 file,并查看直接影响边。 -- 原因:这仍然是最小的用户价值闭环,而且能直接服务后续“AI 查询 blast radius”和“人类审查 graph 变更”的场景。 +### 2. 节点问题 -## 风险 / 权衡 +- 节点至少需要表达哪些语义层级? +- 节点是“模块 / 引擎 / 表 / 门禁 / 服务 / 证据容器”的哪几类组合? +- 节点本体与节点挂载的证据材料如何区分? -- [Risk] Proposal 一旦写得太大,团队会误以为现在就要一次性做完全部阶段。 → Mitigation: 在 proposal 和 design 中明确“只规格化 Phase 1,后续阶段通过新的 issues / changes 细化”。 -- [Risk] 把 GraphSpec 和 OpenSpec 关系写得太近,会让职责边界重新混淆。 → Mitigation: 明确 OpenSpec 管文本化 change,GraphSpec 管结构化 graph,并把桥接重点放在一致性而不是替代。 -- [Risk] 过早讨论 Skill / MCP 会把实现节奏拉散。 → Mitigation: 只写演进顺序和边界,不在当前 change 中定义它们的具体协议。 -- [Risk] 如果 Phase 1 只做只读图,团队可能低估“可写 graph”对产品价值的重要性。 → Mitigation: 在 proposal 中把 writable graph 和 AI 调用 CLI 明确列为后续主线,而不是可有可无的增强项。 +### 3. 关系问题 -## 推进与迁移计划 +- Phase 1 最少需要哪几类关系,才能支撑“整体结构 + 模块下钻”? +- 哪些关系属于后续业务流视图,而不该提前塞进当前主图? +- 跨图关系与图内关系如何共存? + +### 4. 嵌套与下钻问题 + +- 整体图和模块子图之间是什么关系:包含、引用、镜像,还是别的? +- 下钻时要保留哪些上下文,避免用户迷失在子图里? +- 当一个节点在多个视角中出现时,如何保持一致性? + +### 5. 证据层问题 + +- 文件树、import/export、配置片段、代码片段、摘要等证据,哪些先支持? +- 证据是挂在节点上、关系上,还是挂在单独的证据对象上? +- 证据如何服务于解释和校验,而不篡夺主图地位? + +### 6. CLI 问题 + +- CLI 至少要承担哪些职责:初始化图工作区、读取图、浏览图、校验图、修改图。 +- 什么叫“CLI 优先、文件兜底”? +- CLI 修改图时最小护栏是什么? + +## 视图层方向 + +### 1. 架构结构视图 + +- 首页首先回答:这个系统整体由哪些重要部分构成,它们之间大概是什么关系。 +- 它不是文件树,也不是风险分析大全。 +- 它是一张让人“一眼看懂项目大概是什么样”的总图。 -- 当前仓库已经保留了占位 workspace 和 OpenSpec 资产,因此迁移策略不是直接冲实现,而是按 phase 逐步推进。 -- 当前 change 只承接 Phase 1 的最小闭环;完成后,再分别为 writable graph、Skill/CLI、OpenSpec bridge 创建新的 issues 和 changes。 -- 每个后续 issue 都应只切一个薄 slice,并在需要时生成自己的 proposal / design / tasks,而不是复用当前 change 直接承载所有后续阶段。 -- 如果后续发现产品定位发生变化,应优先更新 proposal/design 的 phase map,而不是让实现先跑、文档后补。 +### 2. 模块下钻视图 -## 待后续 issue 细化的问题 +- 点进某个模块后,应看到这个模块自己的细图,而不是直接掉进文件树。 +- 这个细图应该保留与上层图的关联,而不是把上下文切断。 + +### 3. 节点相关内容视图 + +- 点一个节点后,需要看到和它有关的解释材料与最小关系上下文。 +- 这些内容可能包含:相关节点、证据、说明、关联代码痕迹。 +- 目标不是把所有代码展示出来,而是帮助理解“这个节点到底是什么、为什么在这里”。 + +### 4. 业务流视图 + +- 当前总纲承认它是重要方向。 +- 但它不进入 Phase 1 的已定范围。 +- 它进入后续 issue,单独讨论如何和结构视图分层。 + +## Harness 演进原则 + +- Harness 必须跟着项目阶段长,而不是一次性包完。 +- 当前阶段的 Harness 只需要支撑当前风险: + - Phase 0:仓库秩序 + - Phase 1:图资产、CLI、节点相关内容的一致性 + - Phase 2:graph edit 护栏与审计 + - Phase 3:Skill 调 CLI 的稳定性 + - Phase 4:交互复杂后再考虑 Playwright +- 原则是:不要为了这点醋包一盘饺子。 + +## 风险 / 权衡 + +- [Risk] 继续把 scanner 当主线,会把产品拉回静态分析工具。 → Mitigation: 在总纲里把 scanner 明确降级为证据层。 +- [Risk] 总纲继续摆出一堆抽象模型名,会让团队误以为模型已经定了。 → Mitigation: 当前总纲只保留设计问题,不直接拍类型名和字段。 +- [Risk] 业务流视图提前并入,会让 Phase 1 再次膨胀。 → Mitigation: 当前总纲只承认它的存在,不把它收进 Phase 1 已定范围。 +- [Risk] Harness 一次性做太重,会反过来拖慢项目。 → Mitigation: 单独维护 Harness 演进轨道,按阶段增量建设。 + +## 推进与迁移计划 -- 后续的 graph editing 应优先以本地 CLI 命令落地,但具体命令面与 graph draft 模型,刻意留到后续 change 再细化。 -- 第一条 OpenSpec bridge 可以是结构一致性检查、架构草稿生成或 proposal/design 审查辅助,但到底先做哪一条,应在 Phase 1 稳定后通过独立 issue 再决定。 +- 当前 change 只负责把总纲从“scanner-first”扭成“graph-asset-first”。 +- 完成这次重构后,后续应优先拆出这些 issue: + - 架构图主数据模型 + - 图的嵌套与下钻机制 + - 节点相关内容与证据层 + - CLI 读图 / 改图入口 + - 架构视图与浏览体验 + - Phase 1 Harness +- 未来如果要细化 schema、关系模型或业务流视图,必须通过独立 issue / discussion 收敛,不直接改回总纲拍板。 + +## 设计问题池 + +- 主图资产到底应该怎样组织。 +- 节点需要表达哪些语义层级。 +- 关系需要几种大类,哪些属于 Phase 1,哪些属于后续。 +- 嵌套图与跨图关系如何共存。 +- 图资产与证据层之间如何引用。 +- CLI 修改图时最小护栏是什么。 diff --git a/openspec/changes/v0-local-cli-view/proposal.md b/openspec/changes/v0-local-cli-view/proposal.md index 4f31d61..a04602a 100644 --- a/openspec/changes/v0-local-cli-view/proposal.md +++ b/openspec/changes/v0-local-cli-view/proposal.md @@ -1,38 +1,82 @@ ## 背景与动机 -GraphSpec 不能只被规划成一个“本地依赖图查看器”。如果只是这样,它顶多解决一个局部可视化问题,撑不起后续的 Skill、CLI、MCP、IssueDriven、SpecDriven 这些主线能力,也无法解释它为什么值得作为这本书和这个仓库的实战项目。 +GraphSpec 不能再被理解成一个“扫描项目然后画出文件依赖图”的工具。那样做出来的东西,最多是一个静态分析可视化层,但它承接不了 GraphSpec 作为 AI Native 工具的真正定位。 -更准确的定位是:**OpenSpec 解决“需求、设计、任务如何被收敛并持续推进”,GraphSpec 解决“系统架构上下文如何被可视化、可查询、可编辑、可供 AI 调用”。** 它不是 OpenSpec 的替代品,而是和 OpenSpec 互补的一层架构控制面。OpenSpec 偏文本化、变更化、规格化;GraphSpec 偏结构化、空间化、可操作化。GraphSpec 要解决的,正是 OpenSpec 和纯文本 spec 流程里天然存在的几个痛点: +GraphSpec 更合理的位置是: -- 规格是线性的,但系统结构是网状的,跨模块影响半径很难在文字里稳定呈现。 -- 需求、Spec、架构、代码容易各自演化,缺少一个可以被人和 AI 共同读取的中间结构层。 -- AI 即使拿到了 proposal/design/tasks,也仍然不天然具备“当前系统长什么样、改哪里会波及谁”的空间认知。 -- 文本规范更适合定义意图和边界,但不适合承载持续演进的架构态势、依赖拓扑和变更草稿。 +- **OpenSpec** 负责 change / spec 的文本流程,例如 proposal、design、tasks、验收和 change history。 +- **GraphSpec** 负责架构图资产、结构化架构上下文、可视化浏览,以及后续 AI 通过 CLI 或数据文件参与读写的能力。 -因此,这次 proposal 需要先把 **GraphSpec 从项目起盘到 AI 能通过 Skill 调用 CLI 去读写 graph** 的粗粒度路线图钉住。后续每一阶段再用 issue-driven 和 spec-driven 方式逐步细化,而不是一开始就把所有阶段写成巨细无遗的大规格。 +也就是说,GraphSpec 的主资产应该是“架构图”,不是“扫描结果”。 +代码里的 import / export、文件树、依赖痕迹这些东西,当然有价值,但它们更像是图的解释材料、校验材料和证据层,而不是产品的事实来源。 + +这也是当前总纲真正要先收紧的地方: + +- 这个项目首先是在维护什么资产。 +- 这个资产如何被人和 AI 一起使用。 +- 哪些问题已经足够明确,可以进入 issue 化推进。 +- 哪些问题仍然是开放设计议题,必须后续继续讨论。 + +因此,这次 change 的目标不是再把图模型拍得更死,也不是继续把扫描器设计得更完整,而是把 GraphSpec 收成一份 **AI-Native 架构图项目章程**:让后续 issues 有统一背景、有统一边界、有统一 phase map,但不假装所有模型都已经定了。 ## 本次变更 -- 明确 GraphSpec 的产品定位:它是 OpenSpec 的互补层,不负责替代 spec 流程,而是把“架构上下文”变成一等工程资产。 -- 明确 GraphSpec 主要解决的痛点: - - 让人和 AI 都能从宏观结构逐步下钻,而不是靠人脑记项目全貌。 - - 让影响半径、依赖关系、架构草稿和结构差异成为可浏览、可审查、可操作的对象。 - - 在 Issue / Spec / Graph / Code 之间建立更稳定的中间层,降低文字和实现之间的漂移。 -- 定义分阶段演进路径,但只保持粗粒度,不展开成每个阶段的细规格: - - **Phase 0 - 起盘与规范底板**:仓库骨架、OpenSpec change、README、包边界、最小占位实现。 - - **Phase 1 - 只读 Graph**:本地 CLI 扫描项目,生成轻量 graph 快照,并提供基础 graph 查看能力。 - - **Phase 2 - 可写 Graph 操作面**:引入本地 graph 编辑命令和变更草稿能力,让 graph 不只是可看,还能被显式修改和审查。 - - **Phase 3 - Skill + CLI 驱动的 AI Graph 编辑**:把稳定的 CLI 能力封装成 Skill / agent workflow,让 AI 可以查询 graph、提交 graph 修改请求,并走护栏流程。 - - **Phase 4 - OpenSpec / IssueDriven / SpecDriven 桥接**:让 GraphSpec 不只是图工具,而是能参与 Issue、Spec、Graph、Code 一致性检查与同步的控制面。 -- 明确这份 change 的用途更接近 **program seed / roadmap anchor**:后续开发应以 issue-driven 方式逐项切分,每个重要 issue 再通过多轮 AI 对话产出自己的 proposal / design / tasks,而不是直接拿当前 change 全量开工。 -- 明确当前这个 change 的职责:**只正式规格化 Phase 1 的最小闭环**,也就是本地 CLI + 本地只读视图 + 轻量 graph 快照;Phase 2 及以后只在 proposal/design 中保留方向,不在这次 change 里细化为可直接实现的 requirements。 -- 明确当前阶段的非目标:本 change 不定义云端、MCP Server、桌面宿主、多语言扫描、复杂语义推断,也不在现在就锁定未来 graph edit 命令的完整协议。 +- 明确 GraphSpec 的主资产是“架构图”,它由人或 AI 通过 CLI 作为主入口来维护,必要时允许直接编辑数据文件兜底。 +- 明确代码扫描的定位:它可以存在,但只作为证据层、解释层和校验层,不是主图来源。 +- 明确关系体系采用多视角:结构关系、包含关系、业务流关系是不同视角,不强行揉成一张单一的文件依赖图。 +- 明确 Phase 1 的重点不是“从代码推导全图”,而是先建立: + - 可嵌套的整体架构图与模块子图 + - 节点相关内容视图 + - 支撑后续 CLI / AI 协作的主图资产边界 +- 明确当前 change 的用途:它是 `project charter + issue 孵化器`,不是总施工图。 + +## 已定决策 + +- **主资产已定**:GraphSpec 的主资产是架构图,不是代码扫描结果。 +- **主入口已定**:CLI 是主入口,数据文件直改只作为兜底通道。 +- **Phase 1 主目标已定**:先支持整体架构图、模块下钻图、节点相关内容视图。 +- **关系体系方向已定**:GraphSpec 承认结构关系 / 包含关系 / 业务流关系是不同视角;Phase 1 先收紧结构视图与下钻关系,业务流关系留到后续。 +- **scanner 定位已定**:scanner 只能作为辅助证据层、校验层,不能覆盖主图资产。 +- **范围边界已定**:Phase 1 不做业务流完整建模,不做 AI 自动分析,不做 graph edit 全家桶,不做云端和协作。 + +## 待讨论议题 + +- 架构图主数据文件应该如何组织,才能既适合 CLI,又适合人和 AI 理解。 +- 节点与关系的最小集合到底是什么,为什么是这组,而不是另一组。 +- 整体图、模块子图、跨图关系应该如何共存。 +- 代码证据应该以什么形态挂接到图上,例如文件、依赖、代码片段、生成摘要。 +- CLI 修改图时最小护栏是什么,哪些操作允许直接做,哪些必须进入草稿与确认流。 +- 业务流关系应该何时进入主线,以及和结构视图如何分层。 +- 第一条 OpenSpec bridge 应先做结构一致性检查、结构审查辅助,还是别的能力。 + +## 当前 change 的用途 + +这份 change 的用途不是总施工图,而是 **project charter + issue 孵化器**: + +- 给后续 issues 提供统一的 phase map。 +- 给后续 proposal / design / tasks 提供统一背景。 +- 把 discussions 里已经形成的结论沉淀为“哪些已定、哪些未定”。 +- 让每个 issue 不再只有标题,而是有存在意义和上下文。 + +它不能承担的事情也要写清楚: + +- 它不替代每个 issue 的局部 proposal / design。 +- 它不替代具体 schema 设计。 +- 它不替代具体命令协议设计。 +- 它不能把开放议题伪装成已定事实。 + +## Issue 创建原则 + +- 每个 issue 只切一个薄 slice,不能把主图模型、证据层、CLI、浏览体验、OpenSpec bridge 混成一个大需求。 +- 每个 issue 至少要写清楚:背景、为什么现在做、目标、非目标、输入输出、验收口径、前置依赖、未决架构点。 +- 如果某个 issue 的前提是“先收模型”,那就先开 discussion / proposal,不要直接让 AI 往实现上冲。 +- 当前总纲列出的开放议题,不能在后续 issue 里被偷偷拍板;必须显式沉淀成局部设计决策。 ## 能力范围 ### 新增 Capabilities -- `cli-init-view`:覆盖 Phase 1 的本地 CLI 命令面与只读 graph 查看流程。 -- `graph-schema`:覆盖 Phase 1 的 `.graphspec/` 快照契约、图元实体与 cluster 聚合规则。 +- `cli-init-view`:覆盖 Phase 1 中通过 CLI 初始化图工作区、读取图、浏览图、校验图的行为边界。 +- `graph-schema`:覆盖 Phase 1 主图资产、嵌套结构、关系视角、证据挂接能力,以及后续 CLI / Skill 可读写的契约边界。 ### 修改中的 Capabilities @@ -40,8 +84,7 @@ GraphSpec 不能只被规划成一个“本地依赖图查看器”。如果只 ## 影响面 -- Proposal 层影响:`proposal.md` 不再是一份单点功能说明,而是后续 issues / changes 可以复用的产品路线图锚点。 -- Design 层影响:`design.md` 不只解释 Phase 1 的实现形态,还要说明 GraphSpec 为什么沿着 `CLI-first -> writable CLI -> Skill -> OpenSpec bridge` 这条路线演进。 -- Tasks 层影响:`tasks.md` 应当充当 issue 拆分种子,帮助团队创建多个聚焦 issue,而不是形成一条巨型实现线程。 -- Spec 层影响:当前 change 依旧只创建 Phase 1 的 capability specs,刻意保持当前实现范围收紧。 -- 后续工作流影响:graph editing、Skill 集成、OpenSpec bridge 都应该以新的 issues / changes 独立落地,而不是被悄悄塞进这次 Phase 1 实现。 +- Proposal 层影响:`proposal.md` 从路线图摘要升级为项目章程,后续 issues / changes 都要以它校验是否跑偏。 +- Design 层影响:`design.md` 不再替未来 schema 拍板,而是明确图资产层、证据层、视图层的边界与问题池。 +- Tasks 层影响:`tasks.md` 需要从“扫描器-first 的任务单”改成“有背景的 issue 种子清单”。 +- Spec 层影响:两份 `spec.md` 要改成只锁 Phase 1 的行为和能力要求,不再提前固化字段与类型名。 diff --git a/openspec/changes/v0-local-cli-view/specs/cli-init-view/spec.md b/openspec/changes/v0-local-cli-view/specs/cli-init-view/spec.md index a467faf..918c26b 100644 --- a/openspec/changes/v0-local-cli-view/specs/cli-init-view/spec.md +++ b/openspec/changes/v0-local-cli-view/specs/cli-init-view/spec.md @@ -1,60 +1,45 @@ ## ADDED Requirements -### Requirement: init 命令生成或刷新本地快照 -CLI 必须提供 `graphspec init [path]` 命令,用于扫描目标 JS/TS 项目根目录,并创建或刷新合法的 GraphSpec V1 `.graphspec/graph.json` 快照。 +### Requirement: CLI 必须作为主图资产的主入口 +CLI 必须作为 GraphSpec 主图资产的主入口,支持初始化图工作区、读取图、浏览图,以及在后续阶段扩展图校验或改图能力。 -#### Scenario: 在默认目录生成快照 -- **WHEN** 用户在受支持的 JS/TS 项目根目录中执行 `graphspec init` -- **THEN** 命令会在当前项目根目录下生成 `.graphspec/graph.json` +#### Scenario: 初始化图工作区 +- **WHEN** 用户在项目中执行 `graphspec init` +- **THEN** CLI 会初始化 GraphSpec 所需的本地图工作区与主图资产基础结构,而不是直接把扫描结果当作主图 -#### Scenario: 在指定目录刷新快照 -- **WHEN** 用户对一个已经包含 `.graphspec/graph.json` 的项目执行 `graphspec init /path/to/project` -- **THEN** 命令会刷新目标项目中的快照,而不是把它当成一次性初始化命令 +#### Scenario: CLI 作为主读入口 +- **WHEN** 用户或 AI 需要读取当前架构图 +- **THEN** 主要路径是通过 CLI 读取主图资产,而不是直接依赖底层数据文件操作 -### Requirement: init 只承诺 JS/TS 静态依赖扫描 -`graphspec init` 命令在 V0.1 中必须只分析 JavaScript / TypeScript 源文件,并且只产出来自静态 `import` 或 `export ... from` 语法的依赖关系。 +### Requirement: view 必须支持整体图与模块下钻 +`graphspec view` 必须支持浏览整体架构图、进入模块级子图,并允许继续查看更细层级的结构内容。 -#### Scenario: 遇到非 JS/TS 文件 -- **WHEN** 扫描器遇到 Rust、Python、Go 或其他非 JS/TS 文件 -- **THEN** 这些文件会被排除在快照生成之外,不会把 graph 契约扩展到 V0.1 范围之外 +#### Scenario: 首页查看整体结构 +- **WHEN** 用户启动 `graphspec view` +- **THEN** 页面首先展示整体架构结构视图,而不是文件树或扫描结果清单 -#### Scenario: 遇到静态依赖语法 -- **WHEN** 某个 JS/TS 源文件中包含相对路径静态 import 或 re-export -- **THEN** 生成的快照中会包含一条 `static-import` 边,把导入方文件节点连接到解析后的目标文件节点 +#### Scenario: 进入模块级子图 +- **WHEN** 用户在整体图中选择某个模块节点 +- **THEN** 页面能够进入该模块的细致结构图,而不是直接跳成文件列表 -### Requirement: view 命令只渲染已有快照 -CLI 必须提供 `graphspec view` 命令,该命令基于现有 `.graphspec/graph.json` 快照启动本地只读可视化服务,并且不能隐式触发重新扫描。 +### Requirement: 节点详情必须返回相关内容 +节点详情视图必须返回与该节点相关的解释材料和最小关系上下文,而不是只返回一个节点名字或路径。 -#### Scenario: 快照存在时启动本地服务 -- **WHEN** 用户在已经存在 `.graphspec/graph.json` 的项目根目录中执行 `graphspec view` -- **THEN** 命令会启动本地 HTTP 服务,并基于该快照渲染浏览器可访问的可视化页面 +#### Scenario: 查看节点相关内容 +- **WHEN** 用户在图中选中一个节点 +- **THEN** 页面会展示该节点的相关内容、相关关系以及理解该节点所需的最小上下文 -#### Scenario: 快照缺失时返回可操作错误 -- **WHEN** 用户在不存在 `.graphspec/graph.json` 的项目根目录中执行 `graphspec view` -- **THEN** 命令会以可操作错误退出,并明确提示用户先执行 `graphspec init` +#### Scenario: 节点详情可挂接代码证据 +- **WHEN** 某个节点已经关联了文件、依赖或代码片段等证据 +- **THEN** 节点详情能够展示这些证据,但不会把证据误当成主图本体 -### Requirement: view 提供只读的 cluster-first blast radius 浏览 -`graphspec view` 的默认体验必须先展示顶层 cluster 概览,必须允许继续下钻到 file 节点,并且必须为被查看的 file 节点展示直接入边与出边。 +### Requirement: scanner 如果存在只能作为辅助证据层 +如果 CLI 提供扫描、导入代码证据或一致性校验能力,这些能力只能作为辅助证据层使用,不能直接覆盖主图资产。 -#### Scenario: 打开页面先看到 cluster 概览 -- **WHEN** `graphspec view` 成功启动后浏览器页面完成加载 -- **THEN** 页面初始状态展示的是顶层 cluster 概览,而不是平铺的文件图 +#### Scenario: 生成代码证据 +- **WHEN** CLI 从代码中提取 import/export、文件路径或依赖痕迹 +- **THEN** 这些结果会作为图的解释或校验材料存在,而不是直接成为主图事实来源 -#### Scenario: 下钻后查看文件直接影响范围 -- **WHEN** 用户下钻进入某个 cluster 并选中一个 file 节点 -- **THEN** 页面会展示该 file 节点直接相关的入边和出边 `static-import`,以支持 blast radius review - -### Requirement: view 通过本地 API 暴露只读图查询 -由 `graphspec view` 启动的本地服务必须提供只读 HTTP 接口,用于概览查询、cluster 下钻和 node blast radius 查询;Web 客户端必须消费这些接口,而不是直接读取快照文件。 - -#### Scenario: 请求概览数据 -- **WHEN** Web 客户端请求 `GET /api/overview` -- **THEN** 服务会返回基于当前快照生成的顶层 cluster 概览 - -#### Scenario: 请求 cluster 子项 -- **WHEN** Web 客户端请求 `GET /api/clusters/:clusterId` -- **THEN** 服务会返回该 cluster 的直接子 cluster、file 节点以及边摘要 - -#### Scenario: 请求 node 直接依赖 -- **WHEN** Web 客户端请求 `GET /api/nodes/:nodeId` -- **THEN** 服务会返回当前快照中该节点的直接入边和出边 +#### Scenario: 进行一致性校验 +- **WHEN** CLI 使用扫描结果校验主图和代码之间是否存在明显漂移 +- **THEN** 校验结果只会作为反馈或提示,不会自动重写主图资产 diff --git a/openspec/changes/v0-local-cli-view/specs/graph-schema/spec.md b/openspec/changes/v0-local-cli-view/specs/graph-schema/spec.md index a8b74fc..a81ded0 100644 --- a/openspec/changes/v0-local-cli-view/specs/graph-schema/spec.md +++ b/openspec/changes/v0-local-cli-view/specs/graph-schema/spec.md @@ -1,52 +1,52 @@ ## ADDED Requirements -### Requirement: 快照采用轻量 GraphSpec V1 结构 -系统必须将 GraphSpec V1 快照持久化到 `.graphspec/graph.json`,并且每个快照顶层都必须包含 `schemaVersion`、`generatedAt`、`projectRoot`、`nodes`、`edges` 和 `clusters`。 +### Requirement: 必须存在独立的主图资产 +GraphSpec 必须存在一份独立于代码扫描结果的主图资产,用于表达系统的架构结构,并作为后续 CLI、视图和 AI 读写的共同基础。 -#### Scenario: 生成合法的顶层结构 -- **WHEN** `graphspec init` 成功完成 -- **THEN** `.graphspec/graph.json` 会包含 GraphSpec V1 所要求的全部顶层字段 +#### Scenario: 主图资产独立存在 +- **WHEN** 用户初始化或维护 GraphSpec 项目 +- **THEN** 系统中存在一份独立的主图资产,而不是仅依赖运行时扫描结果临时生成视图 -### Requirement: 快照中的图元类型必须受限且稳定 -快照必须将节点类型限制为 `file`、`external`、`cluster`,必须将边类型限制为 `static-import`,并且必须使用以下稳定格式生成 ID:`file:`、`external:`、`cluster:`、`static-import:->`。 +### Requirement: 主图资产必须支持嵌套结构 +主图资产必须能够表达整体图、模块子图以及继续下钻的更细层级结构。 -#### Scenario: 为文件和外部依赖分配稳定 ID -- **WHEN** 扫描器对同一个项目重复产出 file 与 external 节点 -- **THEN** 只要相对路径和依赖 specifier 没有变化,这些节点的 ID 就保持稳定 +#### Scenario: 表达整体图与模块图 +- **WHEN** 用户查看系统整体架构 +- **THEN** 主图资产能够表达整体层级与模块层级的关系 -#### Scenario: 不引入额外图元类型 -- **WHEN** 扫描器遇到 V0.1 范围之外的信息,例如路由、API 调用或运行期 wiring -- **THEN** 快照仍然只产出 GraphSpec V1 允许的节点和边类型 +#### Scenario: 支持继续下钻 +- **WHEN** 用户从整体图进入某个模块子图 +- **THEN** 主图资产能够支撑进一步下钻,而不要求把所有内容一次性摊平 -### Requirement: cluster 必须按既定路径启发式生成 -快照必须按固定的路径启发式生成顶层 cluster,顺序为:优先按 `apps/*` 或 `packages/*` 的 workspace package 聚合;否则按 `src/` 下一级目录聚合;再否则按顶层源码目录聚合。 +### Requirement: 主图资产必须支持多类关系 +主图资产必须能够容纳多类关系视角,至少不能把结构关系、包含关系和未来业务流关系强行压成单一关系形态。 -#### Scenario: monorepo 按 workspace package 聚合 -- **WHEN** 项目中的源码文件位于 `apps/*` 或 `packages/*` 之下 -- **THEN** 顶层 cluster 会按对应的 workspace package 聚合,而不是按单个文件平铺 +#### Scenario: 表达结构与包含关系 +- **WHEN** 用户查看当前 Phase 1 的架构结构视图 +- **THEN** 主图资产能够表达节点之间的结构关系和包含/被包含关系 -#### Scenario: 单包项目按 src 一级目录聚合 -- **WHEN** 项目中不存在 `apps/*` 或 `packages/*`,但存在 `src/` -- **THEN** 顶层 cluster 会按 `src/` 下的一级目录生成 +#### Scenario: 为未来关系视角留出空间 +- **WHEN** 后续阶段需要引入业务流或其他关系视角 +- **THEN** 当前主图资产不会因为 Phase 1 的设计而完全失去扩展空间 -#### Scenario: 无 src 时回退到顶层源码目录 -- **WHEN** 项目既没有 workspace packages,也没有 `src/` 容器目录 -- **THEN** 顶层 cluster 会回退为按包含被扫描 JS/TS 文件的顶层源码目录生成 +### Requirement: 主图资产必须支持挂接证据 +主图资产必须能够为节点或关系挂接代码证据、文件证据或其他解释材料,但证据层不能替代主图资产本身。 -### Requirement: clusters 必须支持两层浏览所需的成员关系 -`clusters` 集合必须记录足够的成员关系信息,以支持顶层概览和下钻,包括每个 cluster 的稳定 ID、展示标签、父 cluster 引用(如果存在)、直接子 cluster ID 和直接子 file 节点 ID。 +#### Scenario: 节点挂接文件证据 +- **WHEN** 某个架构节点需要关联具体文件、依赖痕迹或代码片段 +- **THEN** 主图资产能够引用这些证据,用于解释和校验 -#### Scenario: 顶层 cluster 可直接渲染概览 -- **WHEN** Web 客户端加载 overview -- **THEN** 快照能够直接提供足够的 cluster 元数据,无需再从文件路径重新推导成员关系就能渲染顶层 cluster 列表 +#### Scenario: 证据不替代主图 +- **WHEN** 系统获得新的代码扫描证据 +- **THEN** 证据只作为补充材料存在,而不会自动成为主图本体 -#### Scenario: 下钻 cluster 时可读取直接成员 -- **WHEN** Web 客户端请求某个具体的 cluster -- **THEN** 快照能够提供该 cluster 的直接子 cluster 和 file 节点 ID,以支持下钻渲染 +### Requirement: 主图资产必须可被后续 CLI / Skill 读写 +主图资产必须具备可被 CLI 稳定读取、浏览、校验和后续扩展写入的契约边界,并为未来 Skill / AI 接入保留空间。 -### Requirement: 循环依赖不会阻塞快照生成 -快照生成器必须能够容忍静态 import 形成的循环依赖,并将其表示为普通的有向 `static-import` 边,而不会导致快照生成失败。 +#### Scenario: CLI 稳定读取主图 +- **WHEN** CLI 需要读取当前架构图 +- **THEN** 主图资产能够以稳定方式被读取,而不是依赖一次性临时拼装 -#### Scenario: 存在循环依赖 -- **WHEN** 两个或多个 JS/TS 文件通过静态 import 相互引用 -- **THEN** `graphspec init` 仍然会成功完成,并且快照中会包含对应的有向边 +#### Scenario: 为未来 AI 接入保留边界 +- **WHEN** 后续阶段需要让 Skill 或其他 AI 入口参与读写 GraphSpec +- **THEN** 当前主图资产契约仍然能够作为稳定基础,而不是必须整体推翻重来 diff --git a/openspec/changes/v0-local-cli-view/tasks.md b/openspec/changes/v0-local-cli-view/tasks.md index 31fa7fd..1da6969 100644 --- a/openspec/changes/v0-local-cli-view/tasks.md +++ b/openspec/changes/v0-local-cli-view/tasks.md @@ -1,39 +1,395 @@ ## 1. 总纲与 Issue 约束 -- [ ] 1.1 把当前 change 作为 GraphSpec 的 program seed,后续创建 issue 时统一引用这份 phase map,而不是每次重新定义产品方向 -- [ ] 1.2 约定后续每个重要 issue 都只承载一个薄 slice,并在必要时产出自己的 proposal / design / tasks,而不是直接复用当前 change 全量开发 -- [ ] 1.3 约定 issue 至少要标明:所属 phase、要解决的痛点、明确非目标、验收口径,以及是否涉及 OpenSpec 桥接 / Skill / CLI / graph schema +- [ ] 1.1 把当前 change 明确标注为 GraphSpec 的 project charter / issue 孵化器,而不是总施工图。 +- [ ] 1.2 为后续 issues 固定统一结构:背景、为什么现在做、目标、非目标、前置依赖、待讨论问题、子任务、验收口径。 +- [ ] 1.3 明确每个 issue 只切一个薄 slice,禁止把主图模型、证据层、CLI、浏览体验、OpenSpec bridge 混成一个 issue。 +- [ ] 1.4 把 discussions 中已经形成的产品结论转译进 proposal / design / specs,避免后续继续靠评论区口口相传。 ## 2. Phase 0 Issue 种子:起盘与底板 -- [ ] 2.1 创建 Phase 0 issue:仓库骨架、workspace、基础命令入口与包边界 -- [ ] 2.2 创建 Phase 0 issue:README、AGENTS、OpenSpec 资产与项目协作底板补齐 -- [ ] 2.3 创建 Phase 0 issue:占位实现、lint、typecheck、test 基线与最小开发约束 +### 2.1 Issue:仓库骨架与入口约束 +背景 / 为什么现在做: +- GraphSpec 还在起盘阶段,当前首先需要稳定的仓库骨架和命令入口,而不是提前进入功能堆叠。 -## 3. Phase 1 Issue 种子:只读 Graph +本 issue 想解决什么: +- 固定 `packages/cli`、`packages/core`、`packages/web` 的职责边界。 +- 固定最小命令入口、README 和占位实现标准。 -- [ ] 3.1 创建 Phase 1 issue:GraphSpec V1 快照契约与 `.graphspec/graph.json` 的最小 schema -- [ ] 3.2 创建 Phase 1 issue:JS/TS 静态依赖扫描与基础图构建 -- [ ] 3.3 创建 Phase 1 issue:cluster 聚合规则与 blast radius 所需的最小视图模型 -- [ ] 3.4 创建 Phase 1 issue:`graphspec init [path]` 命令与快照生成流程 -- [ ] 3.5 创建 Phase 1 issue:`graphspec view` 本地服务与只读 graph API -- [ ] 3.6 创建 Phase 1 issue:cluster-first 查看界面与 file 级直接影响范围展示 -- [ ] 3.7 创建 Phase 1 issue:验收测试、fixture、README 用法与错误路径验证 +明确不解决什么: +- 不实现真实 graph 能力。 + +前置依赖: +- 当前总纲和项目 README。 + +需要先讨论的问题: +- 哪些内容应继续保持占位,哪些可以进入真实实现。 + +子任务树: +- [ ] 2.1.1 固定三个 package 的职责边界 +- [ ] 2.1.2 固定统一命令入口和最小脚本 +- [ ] 2.1.3 固定包级 README 的完成标准 + +验收口径: +- [ ] 2.1.4 实现者无需再猜骨架边界和占位实现标准 + +### 2.2 Issue:协作资产与项目章程 +背景 / 为什么现在做: +- 如果 `AGENTS.md`、README 和 OpenSpec 总纲口径不一致,后续 issue 会持续漂移。 + +本 issue 想解决什么: +- 对齐协作资产口径。 +- 固定 project charter + issue-driven 的协作方式。 + +明确不解决什么: +- 不替代后续每个 issue 的局部 proposal / design。 + +前置依赖: +- 当前总纲 change。 + +需要先讨论的问题: +- 哪些规则要持续固化到 AGENTS,哪些只属于当前阶段。 + +子任务树: +- [ ] 2.2.1 对齐 `AGENTS.md`、README、OpenSpec change 的口径 +- [ ] 2.2.2 固定 issue-driven 协作方式 +- [ ] 2.2.3 固定项目级非目标与护栏 + +验收口径: +- [ ] 2.2.4 后续开 issue 时能直接引用统一背景,而不是重复解释项目定位 + +### 2.3 Issue:最小基础 Harness +背景 / 为什么现在做: +- 项目刚起步时需要基本秩序,但不该一次性建设全套重型 Harness。 + +本 issue 想解决什么: +- 定义 Phase 0 需要的最小 `format / lint / typecheck / smoke` 约束。 + +明确不解决什么: +- 不提前引入契约测试、前端 E2E 或 AI workflow regression。 + +前置依赖: +- 仓库骨架和命令入口。 + +需要先讨论的问题: +- 当前哪些检查真正服务项目节奏,哪些属于后续阶段。 + +子任务树: +- [ ] 2.3.1 固定最小 `format / lint / typecheck / smoke` 基线 +- [ ] 2.3.2 明确 smoke test 不承载产品逻辑 +- [ ] 2.3.3 明确何时允许从骨架进入真实功能实现 + +验收口径: +- [ ] 2.3.4 基础工程 Harness 能约束仓库秩序,但不会反客为主 + +## 3. Phase 1 Issue 种子:架构图主资产 + +### 3.1 Issue:架构图主数据模型 +背景 / 为什么现在做: +- 如果不先说明“为什么需要主图资产”,后续 schema 讨论很容易退化成字段堆砌。 + +本 issue 想解决什么: +- 定义主图资产存在的意义。 +- 明确它如何服务整体视图、模块下钻和未来 AI 使用。 +- 对比候选节点 / 关系模型,而不是直接拍一个接口名。 + +明确不解决什么: +- 不细化业务流完整建模。 +- 不细化 graph edit 协议。 + +前置依赖: +- 当前总纲中的产品定位与关系视角。 + +需要先讨论的问题: +- 节点最小集合是什么。 +- 关系最小集合是什么。 +- 主图资产应是单文件还是多文件结构。 + +子任务树: +- [ ] 3.1.1 写清主图资产为什么存在 +- [ ] 3.1.2 梳理节点与关系的候选模型 +- [ ] 3.1.3 说明为什么不能先绑定文件树 +- [ ] 3.1.4 说明主图资产如何服务整体图、模块图和未来 AI 编辑 +- [ ] 3.1.5 产出第一版模型讨论提纲 + +验收口径: +- [ ] 3.1.6 实现者读完该 issue,能解释主图资产的意义和后续 schema 讨论边界 + +### 3.2 Issue:图的嵌套与下钻机制 +背景 / 为什么现在做: +- 用户要的不是一棵文件树,而是“整体图 -> 模块图 -> 更细图”的嵌套体验。 + +本 issue 想解决什么: +- 定义整体图、模块图、子图之间的关系。 +- 定义下钻时需要保留什么上下文。 +- 定义跨层关系如何存在。 + +明确不解决什么: +- 不在这里拍完整业务流视图。 + +前置依赖: +- 主图资产的候选模型。 + +需要先讨论的问题: +- 下钻是包含、引用还是视角切换。 +- 一个节点出现在多张图里时如何保持一致。 + +子任务树: +- [ ] 3.2.1 梳理整体图 / 模块图 / 子图的层级关系 +- [ ] 3.2.2 明确下钻保留的上下文 +- [ ] 3.2.3 明确跨层关系的最小表达 +- [ ] 3.2.4 梳理 Auth 这类模块的典型嵌套示例 + +验收口径: +- [ ] 3.2.5 后续实现者能解释“整体图 -> 模块图”的最小工作机制 + +### 3.3 Issue:节点相关内容与证据层 +背景 / 为什么现在做: +- 节点本身是架构语义对象,但用户点开后还需要理解它为什么存在、它与代码有什么对应关系。 + +本 issue 想解决什么: +- 定义节点点开后需要展示哪些解释材料。 +- 定义文件树 / import/export / 代码片段等证据如何挂接。 +- 定义证据层如何服务解释和校验,而不篡夺主图。 + +明确不解决什么: +- 不让 scanner 直接生成主图。 + +前置依赖: +- 主图资产方向和下钻机制。 + +需要先讨论的问题: +- 哪些证据先支持。 +- 证据挂在节点上、关系上,还是独立对象上。 + +子任务树: +- [ ] 3.3.1 定义节点相关内容的最小集合 +- [ ] 3.3.2 定义代码证据的候选形态 +- [ ] 3.3.3 定义证据挂接方式 +- [ ] 3.3.4 定义 scanner 作为辅助层的职责边界 + +验收口径: +- [ ] 3.3.5 实现者能解释“点一个节点看到什么”,以及代码证据为什么只是辅助层 + +### 3.4 Issue:CLI 读图 / 改图入口 +背景 / 为什么现在做: +- GraphSpec 是 AI-Native 工具,图资产最终要被人和 AI 稳定使用,不能只有可视化页没有命令面。 + +本 issue 想解决什么: +- 定义 CLI 作为主入口的职责。 +- 定义数据文件直改作为兜底入口的边界。 +- 定义读、写、校验三类职责如何分开。 + +明确不解决什么: +- 不一次性拍完整 graph edit 协议。 + +前置依赖: +- 主图资产和证据层方向。 + +需要先讨论的问题: +- Phase 1 是否只需要 init / view / validate 这一类最小命令。 +- 哪些改图动作可以留到后续阶段。 + +子任务树: +- [ ] 3.4.1 说明为什么 CLI 是主入口 +- [ ] 3.4.2 列出最小命令面候选 +- [ ] 3.4.3 定义数据文件兜底入口的边界 +- [ ] 3.4.4 定义 CLI 与主图资产的关系 + +验收口径: +- [ ] 3.4.5 后续实现者知道命令面为什么存在,以及它如何约束图资产演进 + +### 3.5 Issue:架构视图与浏览体验 +背景 / 为什么现在做: +- Phase 1 的产品价值首先来自“第一眼看懂结构”和“下钻后看懂模块”,不是来自扫描结果本身。 + +本 issue 想解决什么: +- 定义首页先看什么。 +- 定义模块下钻看什么。 +- 定义节点详情看什么。 + +明确不解决什么: +- 不拍完整业务流视图。 + +前置依赖: +- 下钻机制和节点相关内容定义。 + +需要先讨论的问题: +- 结构视图与风险视图如何搭配。 +- 模块图里哪些关系需要先可视化。 + +子任务树: +- [ ] 3.5.1 定义首页整体架构视图 +- [ ] 3.5.2 定义模块细图 +- [ ] 3.5.3 定义节点详情视图 +- [ ] 3.5.4 说明 blast radius 在视图中的落点 + +验收口径: +- [ ] 3.5.5 实现者知道“首页 / 模块下钻 / 节点详情”三屏分别回答什么问题 + +### 3.6 Issue:Phase 1 Harness +背景 / 为什么现在做: +- 当前阶段需要的是服务图资产与命令面的最小 Harness,而不是先把所有质量体系一次性包完。 + +本 issue 想解决什么: +- 定义当前阶段的最小图资产校验、样例图 fixture、CLI 行为校验和节点相关内容一致性检查。 + +明确不解决什么: +- 不提前引入重型 E2E。 +- 不提前建设完整 AI workflow regression。 + +前置依赖: +- 主图资产、CLI 和浏览体验三个方向的 issue。 + +需要先讨论的问题: +- 当前最真实的失败模式是什么。 +- 哪些 Harness 值得现在做,哪些应该后置。 + +子任务树: +- [ ] 3.6.1 定义样例图 fixture 策略 +- [ ] 3.6.2 定义图资产一致性校验 +- [ ] 3.6.3 定义 CLI 行为最小测试 +- [ ] 3.6.4 定义节点相关内容的一致性检查 + +验收口径: +- [ ] 3.6.5 Harness 能覆盖当前阶段真实风险,而不是为了完整而完整 ## 4. Phase 2 Issue 种子:可写 Graph 操作面 -- [ ] 4.1 创建 Phase 2 issue:graph draft / graph edit 的产品边界,明确“编辑 graph”不等于直接改源码 -- [ ] 4.2 创建 Phase 2 issue:本地 graph 变更命令面,明确哪些编辑操作要先支持 -- [ ] 4.3 创建 Phase 2 issue:graph 变更草稿、审查、回滚与人类确认护栏 +### 4.1 Issue:graph draft / graph edit 的产品边界 +背景 / 为什么现在做: +- 一旦图是主资产,就必然会进入“如何改图”的阶段,但这不等于直接改源码。 + +本 issue 想解决什么: +- 明确 graph draft 的定位。 +- 明确哪些编辑属于图层编辑,哪些属于代码层问题。 + +明确不解决什么: +- 不拍完整命令协议。 + +前置依赖: +- 主图资产和 CLI 主入口。 + +需要先讨论的问题: +- 哪些改图动作必须经过人类确认。 + +子任务树: +- [ ] 4.1.1 解释为什么可写 graph 是下一阶段主线 +- [ ] 4.1.2 明确“编辑 graph”不等于改源码 +- [ ] 4.1.3 定义 graph draft 的资产定位 + +验收口径: +- [ ] 4.1.4 后续 graph edit issue 不会和源码改造问题混写 + +### 4.2 Issue:本地 graph 变更命令面 +背景 / 为什么现在做: +- 如果没有稳定命令面,后续 Skill 很容易直接去改底层文件。 + +本 issue 想解决什么: +- 定义最小变更命令集合与输出要求。 + +明确不解决什么: +- 不做完整审计系统。 + +前置依赖: +- graph draft 边界。 + +需要先讨论的问题: +- 哪些改图操作先支持。 + +子任务树: +- [ ] 4.2.1 梳理最小变更命令集合 +- [ ] 4.2.2 梳理命令输出和审计需求 +- [ ] 4.2.3 说明命令与主图资产的关系 + +验收口径: +- [ ] 4.2.4 后续 Skill 可基于稳定命令面扩展,而不是直改底层文件 ## 5. Phase 3 Issue 种子:Skill + CLI 驱动的 AI Graph 编辑 -- [ ] 5.1 创建 Phase 3 issue:把稳定 CLI 能力封装成 Skill 的输入输出契约 -- [ ] 5.2 创建 Phase 3 issue:AI 查询 graph、提交 graph 修改请求、查看执行结果的工作流设计 -- [ ] 5.3 创建 Phase 3 issue:Skill 调 CLI 的低噪声约束、失败处理与审计要求 +### 5.1 Issue:Skill 输入输出契约 +背景 / 为什么现在做: +- 如果 GraphSpec 真要成为 AI-Native 工具,就必须让 Skill 能稳定调用它,而不是靠 prompt 想象图。 + +本 issue 想解决什么: +- 定义 Skill 如何读图、查图、提交改图请求。 + +明确不解决什么: +- 不先上 MCP。 + +前置依赖: +- 稳定 CLI 命令面。 + +需要先讨论的问题: +- 哪些读图能力最先开放给 Skill。 + +子任务树: +- [ ] 5.1.1 说明为什么 Skill 调 CLI 是主线 +- [ ] 5.1.2 选择最小查询能力 +- [ ] 5.1.3 选择最小变更能力 +- [ ] 5.1.4 定义失败反馈与审计结果回传方式 + +验收口径: +- [ ] 5.1.5 后续 AI 接入不会绕开 CLI 主入口 ## 6. Phase 4 Issue 种子:OpenSpec Bridge -- [ ] 6.1 创建 Phase 4 issue:Issue / Spec / Graph / Code 一致性检查的第一条桥接链路 -- [ ] 6.2 创建 Phase 4 issue:GraphSpec 如何辅助 proposal / design 审查,而不是替代 OpenSpec 文本资产 -- [ ] 6.3 创建 Phase 4 issue:后续是否需要 MCP 或更多 AI 接入形态,并基于 CLI-first 成熟度再决定是否立项 +### 6.1 Issue:Issue / Spec / Graph / Code 一致性检查 +背景 / 为什么现在做: +- GraphSpec 之所以和 OpenSpec 互补,不是因为它取代文本,而是因为它能帮助校验文本与结构有没有漂。 + +本 issue 想解决什么: +- 选择第一条 bridge:结构一致性检查或结构审查辅助。 + +明确不解决什么: +- 不把 GraphSpec 做成另一个 OpenSpec。 + +前置依赖: +- 稳定主图资产和 CLI 查询能力。 + +需要先讨论的问题: +- GraphSpec 先输入什么、输出什么才有价值。 + +子任务树: +- [ ] 6.1.1 说明为什么 bridge 更像一致性检查 +- [ ] 6.1.2 选择第一条 bridge 方向 +- [ ] 6.1.3 定义最小输入输出 + +验收口径: +- [ ] 6.1.4 后续 bridge 设计不会把 GraphSpec 拉成文本流程工具 + +## 7. Decision Backlog + +- [ ] 7.1 为每个待决策议题补一行“为什么它会卡住后续 issue” +- [ ] 7.2 讨论主图资产最终如何组织 +- [ ] 7.3 讨论节点最小语义层级 +- [ ] 7.4 讨论关系最小分类 +- [ ] 7.5 讨论业务流视图进入主线的时机 +- [ ] 7.6 讨论 graph draft 的最小操作集合 +- [ ] 7.7 讨论 Skill 调 CLI 需要暴露哪些稳定读写能力 + +## 8. Harness 演进轨道 + +### 8.1 Phase 0:基础工程 Harness +- [ ] 8.1.1 只要求最小的 `format / lint / typecheck / smoke` +- [ ] 8.1.2 明确目标是约束仓库基本秩序,而不是一次性包办所有质量问题 +- [ ] 8.1.3 明确当前阶段不引入过重自动化验证 + +### 8.2 Phase 1:图资产与 CLI Harness +- [ ] 8.2.1 为图资产设计样例图 fixture +- [ ] 8.2.2 为 CLI 行为设计最小集成测试 +- [ ] 8.2.3 为节点相关内容的一致性设计检查 + +### 8.3 Phase 2:graph edit 护栏 Harness +- [ ] 8.3.1 为草稿状态流设计回归检查 +- [ ] 8.3.2 为确认、回滚、审计输出设计验证机制 +- [ ] 8.3.3 明确哪些护栏要从文档约束升级成自动化约束 + +### 8.4 Phase 3:AI 接入 Harness +- [ ] 8.4.1 为 Skill 调 CLI 的输入输出稳定性设计检查 +- [ ] 8.4.2 为失败路径和低噪声命令面设计验证 +- [ ] 8.4.3 明确哪些能力需要单独的 AI workflow regression + +### 8.5 Phase 4:前端与端到端 Harness +- [ ] 8.5.1 当前端交互复杂到一定程度时,再引入 Playwright +- [ ] 8.5.2 明确 Playwright 的目标是验证关键交互闭环,而不是为了“有 E2E”而加 E2E +- [ ] 8.5.3 持续评估 Harness 是否服务当前阶段,而不是为了这点醋包一盘饺子 From c781d6874fa096204bf9cda3ea44a5aec01ceaac Mon Sep 17 00:00:00 2001 From: TatsukiMeng <98578510+TatsukiMeng@users.noreply.github.com> Date: Sat, 11 Apr 2026 19:46:28 +0800 Subject: [PATCH 3/4] Update packages/web/README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- packages/web/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/web/README.md b/packages/web/README.md index 6993c59..bf919de 100644 --- a/packages/web/README.md +++ b/packages/web/README.md @@ -17,7 +17,7 @@ - 不启动 HTTP 服务 - 不渲染页面 -- 不提供 API +- 不提供 HTTP/Web 服务 API - 不读写任何项目数据 ## 公共 API / 子路径导出 From 0953471f0db5ec90ee29458728a296c874b752f2 Mon Sep 17 00:00:00 2001 From: TatsukiMeng <98578510+TatsukiMeng@users.noreply.github.com> Date: Sat, 11 Apr 2026 19:46:54 +0800 Subject: [PATCH 4/4] Update README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4d4522d..2704a0e 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ bun run graphspec ### CLI (`packages/cli/`) - **职责**: 命令行入口,用户交互 - **当前状态**: 仅占位入口 -- **依赖**: `@graphspec/core` +- **依赖**: `@graphspec/core`、`@graphspec/web` ### Core (`packages/core/`) - **职责**: 核心解析和数据处理