@wdyy/skills 0.1.23 → 0.1.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -16,22 +16,34 @@ OpenSpec 单项变更的需求、设计、任务、实
16
16
  |---|---|
17
17
  | `wdyy-database-standard` | PostgreSQL 18 数据库设计、版本化迁移、数据兼容性与 DDL 校验。 |
18
18
  | `wdyy-bug-record` | 手动汇总当前任务已解决的问题,并按模板追加至 `docs/bug_record.md`。 |
19
- | `wdyy-logging-standard` | 结构化日志、敏感字段移除、traceId、前端异常上报与日志轮转。 |
19
+ | `wdyy-logging-standard` | 访问、应用、安全与审计日志,递归敏感字段净化、W3C trace、前端异常上报、轮转与哈希链校验。 |
20
20
  | `wdyy-deployment-standard` | 在 Linux、macOS Docker Desktop 或 Windows Docker Desktop + WSL2/Linux 容器模式中,使用本地基础镜像构建前后端独立镜像,分别导出并统一压缩交付,远程数据库直连并一键部署整个项目栈。 |
21
21
  | `wdyy-ui` | 统一医院信息系统页面的布局、清新生命绿配色、医院 Logo、常用组件和响应式行为,不定义业务流程、接口或数据模型。 |
22
22
 
23
23
  ## 使用方式
24
24
 
25
- 安装全局包后,在目标项目根目录执行:
25
+ 安装全局包后,先由工程师手工安装或更新 OpenSpec、公共 Skill 与 CodeGraph,再在目标项目根目录执行初始化:
26
26
 
27
27
  ```bash
28
28
  npm install -g @wdyy/skills
29
+ npm install -g @fission-ai/openspec@latest
30
+ npx skills@latest add mattpocock/skills -g --skill grill-me grill-with-docs prototype frontend-design research tdd codebase-design
31
+ npx skills@latest add tt-a1i/archify -g
32
+ npm i -g @colbymchenry/codegraph
29
33
  wdyy init
30
34
  ```
31
35
 
32
- `wdyy init` 会在系统用户目录 `.agents/skills/` 安装或更新 5 个受管理 Skill,并删除其中旧的 `wdyy-api-standard`,但不在目标项目创建 `.agents/skills/`。它还会执行 `git init`,创建或补齐 `.gitignore` 的 `.env`、`node_modules/`、`venv/`、`.pnpm-store/`、`logs/`、`dist/`、`.DS_Store`、`*.tar.gz` 规则,创建 `src/frontend`、`src/backend`、`database`、`deploy`、`docs`、`scripts`、`tests`,并以相同的“前端、后端、数据库、API、部署”结构创建缺失的 `.env` 与 `.env.example`。默认前端监听 `0.0.0.0:5173`、后端监听 `0.0.0.0:3000`、数据库端口为 `5432`;已有环境文件保持不变。随后命令执行 `openspec init`、创建或追加 `docs/bug_record.md` 模板。若目标项目已有 `AGENTS.md`,原文件保持不变,命令将生成 `AGENTS_new.md`。
36
+ `wdyy init` 启动时会再次显示上述外部依赖安装命令,但不会自动执行 npm npx。命令在写入前集中检查 Node.js 24、pnpm 12.1.0、Git、OpenSpec、CodeGraph,以及规则强制使用的公共 Skill;任一项缺失或版本不符时一次性报告全部问题并退出。
33
37
 
34
- 生成的项目规则要求:接口实现仅以工程师人工提供的 YAML 文件为依据;未提供 YAML 或接口契约不完整时必须询问工程师,不得从旧 Skill、reference、远程目录或其他来源推断或编造。既有项目需要重新执行 `wdyy init` 才会生成包含该要求的新规则文件。
38
+ 预检通过后,`wdyy init` 会在系统用户目录 `.agents/skills/` 安装或更新 5 个受管理 Skill,并删除其中旧的 `wdyy-api-standard`。它还会初始化 Git、默认目录、环境模板、BUG 模板、OpenSpec 与 CodeGraph。已有环境文件和 BUG 记录保持不变;已有 `AGENTS.md` 时生成 `AGENTS_new.md`。全部步骤成功后生成 `.wdyy/toolchain.json`,记录初始化器与工具版本、实际规则文件以及公共和企业 Skill 的 SHA-256;清单不含用户名、凭据或绝对主目录。
39
+
40
+ 生成规则负责项目边界与 Skill 路由:`grill-me` 澄清需求和验收标准,`grill-with-docs` 记录 PRD、术语与 ADR;文档沉淀完成后先使用 `openspec-explore` 探索方案、约束与风险,再使用 `openspec-propose` 形成可评审计划并进入实施流程。数据库、日志、部署和 UI 的执行细节由对应 Skill 唯一维护。自建接口由 Agent 根据已确认需求生成 OpenAPI 3.1 YAML 并交工程师验收;第三方接口只依据工程师提供的契约或官方文档。
41
+
42
+ 初始化后在项目根目录执行以下只读验收;命令集中检查初始化产物、工具版本、Skill 和工具链清单漂移,不会自动修复文件:
43
+
44
+ ```bash
45
+ wdyy validate
46
+ ```
35
47
 
36
48
  生成的项目规则还要求:每个经确认的变更完成并通过全部测试与验证后创建对应 Git commit,不得提交未完成或验证失败的状态;每个变更依据对应 OpenSpec 的需求、场景和任务编写或更新相关测试,交付前必须解决全部测试与验证失败,不得跳过或隐瞒。既有项目重新执行 `wdyy init` 后,可从新生成的 `AGENTS_new.md` 审阅并合并该规则。
37
49
 
@@ -50,7 +62,7 @@ wdyy update-skill --all
50
62
 
51
63
  部署 Skill 生成的标准流程为:在 Linux、macOS Docker Desktop,或 Windows Docker Desktop + WSL2/Linux 文件系统的项目根目录执行 `./deploy.sh build`,只使用本地已有的 Node、Nginx 基础镜像构建 `<项目名>_frontend:latest` 与 `<项目名>_backend:latest`,分别导出两个镜像 tar,并统一封装为 `deploy/<项目名>-docker.tar.gz`。工程师人工复制并解压该压缩包,在受支持的 Linux engine 上无参数执行 `./deploy.sh` 即校验、加载并整体部署前后端;macOS/arm64 Docker Desktop 可通过禁止拉取的实际目标镜像探针验证并运行 `linux/amd64` 包,而不是仅按 Server 原生架构拒绝。另可执行 `./deploy.sh stop` 和 `./deploy.sh status`。数据库通过 `.env` 直连远程 PostgreSQL,不生成数据库容器;前后端的宿主机与容器端口都来自 `.env`,容器名与镜像 repository 相同,使用 `unless-stopped`,并验证 24 小时制 CST(UTC+8);日志直接保存在部署根 `logs/`。不支持原生 PowerShell 或 Windows 容器模式。
52
64
 
53
- 中大型变更先使用 `/opsx:explore` 或 `/opsx:propose <change-name>`,在方案确认后使用 `/opsx:apply`;完成验证后使用 `/opsx:archive`。涉及数据库、日志、部署或医院信息系统 UI 时,按 `AGENTS.md` 的路由加载相应企业 Skill;接口实现仅依据工程师人工提供的 YAML。所有 URL、端口和 IP 地址从 `.env` 读取,不得硬编码;所有项目管理的日志直接写入项目根目录 `logs/`;生产部署不使用蓝绿发布。不得提交秘密或生产环境 `.env`。项目完成后创建 `README.md`,说明技术栈、目录结构和安装部署。
65
+ 中大型变更先使用 `/opsx:explore` 或 `/opsx:propose <change-name>`,在方案确认后使用 `/opsx:apply`;完成验证后使用 `/opsx:archive`。涉及数据库、日志、部署或医院信息系统 UI 时,按 `AGENTS.md` 的路由加载相应企业 Skill。自建接口由 Agent 生成 OpenAPI 3.1 YAML 并交工程师验收,第三方接口依据工程师提供的契约或官方文档。所有 URL、端口和 IP 地址从 `.env` 读取,不得硬编码;不得提交秘密或生产环境 `.env`。项目完成后创建 `README.md`,说明技术栈、目录结构和安装部署。
54
66
 
55
67
  ## 固定技术栈
56
68
 
@@ -71,7 +83,7 @@ wdyy update-skill --all
71
83
 
72
84
  ### 使用发布脚本
73
85
 
74
- 推荐从项目根目录手动运行 `release_npm.sh`。脚本会检查工作区、组织权限、lint 与打包内容,自动执行 `npm version <patch|minor|major>`、`npm publish --access public`;发布成功后等待 15 秒,再一次查询刚发布的确切版本进行 registry 核验。它不会执行登录、浏览器认证或处理认证凭据。
86
+ 推荐从项目根目录手动运行 `release_npm.sh`。脚本会检查工作区、组织权限、完整测试、lint 与打包内容,自动执行 `npm version <patch|minor|major>`、`npm publish --access public`;发布成功后等待 15 秒,再一次查询刚发布的确切版本进行 registry 核验。它不会执行登录、浏览器认证或处理认证凭据。
75
87
 
76
88
  首次运行或登录失效时,先手工登录;脚本检测到未登录也会提示并退出:
77
89
 
@@ -116,10 +128,11 @@ npm version patch # 修复或小幅调整
116
128
  ```bash
117
129
  npm test
118
130
  npm run lint
119
- node --test $(find .well-known/skills -path '*/scripts/*.test.mjs' -print)
120
131
  npm pack --dry-run
121
132
  ```
122
133
 
134
+ `npm test` 会通过索引聚合器执行全部受管理 Skill 声明的测试,新增 Skill 测试无需维护 shell 文件发现命令。
135
+
123
136
  其中 `npm pack --dry-run` 用于确认包内容;应包含 `bin`、`lib`、`.well-known/skills` 与 `README.md`,且不应包含本地环境文件或其他无关文件。
124
137
 
125
138
  ### 3. 登录并核对组织权限
package/lib/wdyy-cli.js CHANGED
@@ -1,7 +1,8 @@
1
1
  import { spawnSync } from 'node:child_process';
2
+ import { createHash } from 'node:crypto';
2
3
  import { access, appendFile, cp, lstat, mkdir, mkdtemp, readFile, rename, rm, writeFile } from 'node:fs/promises';
3
4
  import { homedir } from 'node:os';
4
- import { dirname, join } from 'node:path';
5
+ import { basename, dirname, join } from 'node:path';
5
6
  import { fileURLToPath } from 'node:url';
6
7
 
7
8
  const packageRoot = join(dirname(fileURLToPath(import.meta.url)), '..');
@@ -14,6 +15,26 @@ const managedSkillNames = [
14
15
  ];
15
16
 
16
17
  const removedManagedSkillNames = ['wdyy-api-standard'];
18
+ const packageMetadata = JSON.parse(await readFile(join(packageRoot, 'package.json'), 'utf8'));
19
+ const requiredNodeMajor = Number(packageMetadata.engines.node.match(/\d+/)?.[0]);
20
+ const requiredPnpmVersion = packageMetadata.engines.pnpm;
21
+ const requiredPublicSkillNames = [
22
+ 'grill-me',
23
+ 'grill-with-docs',
24
+ 'prototype',
25
+ 'frontend-design',
26
+ 'research',
27
+ 'tdd',
28
+ 'codebase-design',
29
+ 'archify'
30
+ ];
31
+
32
+ const manualPrerequisiteCommands = [
33
+ 'npm install -g @fission-ai/openspec@latest',
34
+ 'npx skills@latest add mattpocock/skills -g --skill grill-me grill-with-docs prototype frontend-design research tdd codebase-design',
35
+ 'npx skills@latest add tt-a1i/archify -g',
36
+ 'npm i -g @colbymchenry/codegraph'
37
+ ];
17
38
 
18
39
  const defaultProjectDirectories = [
19
40
  'src/frontend',
@@ -59,9 +80,13 @@ DB_SCHEMA=api_platform
59
80
 
60
81
  const defaultProjectLayout = [...defaultProjectDirectories, '.env.example'];
61
82
 
62
- const generatedAgentsContent = `# 企业开发规则
83
+ const generatedAgentsContent = `# 项目开发规则
84
+
85
+ 本项目使用 OpenSpec 及企业 Skill 统一开发规则与流程。所有规则面向 agent 执行;规则未覆盖或不明确时,先询问工程师,不得猜测。
86
+
87
+ ## 固定时区
63
88
 
64
- 本项目同时使用OpenSpec及企业 Skill统一开发规则及流程。
89
+ - 全链路统一使用 \`TZ=Asia/Shanghai\` 作为时间基准:日志时间戳、数据库时间、前端时间展示、部署脚本时间验证都必须基于 \`Asia/Shanghai\`,禁止混用其他时区。
65
90
 
66
91
  ## 固定技术栈
67
92
 
@@ -76,39 +101,127 @@ const generatedAgentsContent = `# 企业开发规则
76
101
 
77
102
  \`.env.example\` 是配置模板。需要本地配置时,必须先手工复制 \`.env.example\` 为 \`.env\`;后续修改配置文件时,必须先修改 \`.env.example\`,再同步修改 \`.env\`。不得将真实密码、密钥或生产配置写入 \`.env.example\`。
78
103
 
79
- 项目开发完成后必须创建 \`README.md\`,内容包括项目技术栈、目录结构和安装部署说明。
80
-
81
104
  ## Skill 路由
82
105
 
83
- | 场景 | 必须使用的 Skill |
106
+ | 阶段 / 场景 | 必须使用的 Skill |
84
107
  |---|---|
108
+ | 需求澄清 | 使用 \`grill-me\` 消除需求、边界与验收标准中的歧义 |
109
+ | PRD 与决策记录 | 使用 \`grill-with-docs\` 记录 PRD、术语和架构决策 |
110
+ | 方案探索 | \`grill-with-docs\` 完成后,使用 \`openspec-explore\` 探索方案、约束与风险 |
111
+ | 变更规划与实施 | 使用 \`openspec-propose\` 生成可评审计划,确认后使用对应 OpenSpec 实施与归档流程 |
112
+ | 原型设计 | \`prototype\` |
113
+ | 通用前端视觉与交互设计 | \`frontend-design\` |
114
+ | 医院信息系统页面的新建、改造或审查 | \`wdyy-ui\`;不负责定义业务流程、接口或数据模型 |
115
+ | 开发过程 | 按需使用 \`research\`、\`tdd\`、\`codebase-design\` |
116
+ | 架构图、时序图、流程图 | \`archify\`,产出放入 \`docs/\` |
85
117
  | PostgreSQL 表、DDL、迁移、数据兼容性 | \`wdyy-database-standard\` |
86
- | 汇总当前任务已解决的 BUG、漏洞或运行异常 | 手动调用 \`wdyy-bug-record\`,追加写入 \`docs/bug_record.md\` |
87
- | 结构化日志、traceId、完整原始入参、前端异常上报 | \`wdyy-logging-standard\` |
118
+ | 访问、应用、安全与审计日志、W3C trace、净化后的完整业务参数、前端异常上报 | \`wdyy-logging-standard\` |
88
119
  | 统一 Docker 交付、Linux/Mac/Windows WSL2 双镜像、远程数据库与整栈一键部署 | \`wdyy-deployment-standard\` |
89
- | 医院信息系统页面的新建、改造或审查 | \`wdyy-ui\`;不负责定义业务流程、接口或数据模型 |
120
+ | 汇总当前任务已解决的 BUG、漏洞或运行异常 | 手动调用 \`wdyy-bug-record\`,追加写入 \`docs/bug_record.md\` |
121
+
122
+ ## 文档与目录约定
123
+
124
+ - 项目开发完成后必须创建 \`README.md\`,内容至少包括:项目简介、目录结构、技术栈、本地测试流程、部署流程、使用的数据库。
125
+ - 所有数据库文档统一放置在 \`database/\` 目录。
126
+ - 架构图、时序图、流程图等其它文档统一放置在 \`docs/\` 目录。
90
127
 
91
128
  ## 通用边界
92
129
 
130
+ - 所有需求必须先完成需求澄清和验收标准确认,再形成 PRD 与必要决策记录;\`grill-with-docs\` 完成后必须先使用 \`openspec-explore\` 探索方案,再使用 \`openspec-propose\` 形成可评审规划。涉及业务、接口、数据库、部署或不可逆决定时,实施前必须由工程师确认规划产物。
93
131
  - 项目根目录应提供真实可执行的 \`pnpm dev\`、\`pnpm build\`、\`pnpm test\`、\`pnpm lint\` 命令;\`pnpm dev\` 只负责前后端的启动,不得启动或管理数据库。
94
132
  - 生成的接口文档必须符合 OpenAPI 3.1 规范,并声明 \`openapi: 3.1.0\`。
133
+ - 自建接口由 Agent 根据已确认需求生成 OpenAPI 3.1 YAML 契约,经工程师验收后实施。第三方接口仅依据工程师提供的契约或官方接口文档;来源缺失或契约不完整时必须询问工程师,不得推测。
95
134
  - 在下载或安装任何项目依赖前,必须先从官方发布渠道确认当前最新稳定版本,并检查该版本及其传递依赖的公开漏洞修复状态。原则上必须使用最新稳定版本;不得使用已过时的依赖,也不得使用存在未修复高危(High/Critical)漏洞的依赖。若最新稳定版本不兼容,或不存在满足安全要求的版本,必须停止安装并报告依赖名称、候选版本、兼容性及漏洞证据,等待工程师确认;不得擅自降级、忽略审计结果或添加豁免。
96
135
  - 代码、配置、迁移、测试与部署文件必须以实际实现为准;不得用模板虚构已实现能力。
97
- - 所有需求(如数据库变更、流程变更、功能变更、从零开发项目等),必须先使用\`openspec-explore\`技能确认需求,再根据实际情况,使用\`openspec\`的其它技能。
98
136
  - 每个经确认的变更完成并通过全部测试与验证后,必须创建一个对应的 Git commit,以支持追踪和回滚;不得提交未完成或验证失败的状态。
99
137
  - 每个变更都必须依据对应 OpenSpec 的需求、场景和任务编写或更新相关测试;交付前必须解决所有测试与验证失败,不得跳过或隐瞒失败。
100
- - 所有项目管理的日志必须直接写入项目根目录 \`logs/\`,不得通过环境变量改写目录或创建服务、实例、日期等日志子目录。
101
- - 生产部署必须使用 \`wdyy-deployment-standard\`:在 Linux、macOS Docker Desktop 或 Windows Docker Desktop + WSL2/Linux 容器模式中,只使用已有基础镜像构建前后端两个独立 Linux 镜像,分别导出后封装为一个压缩包;数据库直连远程实例且不得使用数据库容器;解压后无参数执行 \`./deploy.sh\`,一次性部署整个前后端项目栈。禁止原生 PowerShell、Windows 容器模式、自动拉取镜像、交互菜单和单独操作一个服务。
102
- - 不得静默吞错、跳过验证、以兜底逻辑掩盖未决问题。
138
+ - 日志实现必须使用 \`wdyy-logging-standard\`,并保持日志直接写入项目根目录 \`logs/\` 的项目边界。
139
+ - 生产交付必须使用 \`wdyy-deployment-standard\`,并保持前后端双镜像、远程数据库和整栈一键部署的项目边界。
140
+ - 不得静默吞错、跳过验证或以兜底逻辑掩盖未决问题。
103
141
  `;
104
142
 
105
143
  const bugRecordTemplate = '- 日期:yyyy-mm-dd\n- 问题描述:\n- 解决方法:\n';
106
144
 
107
145
  function printHelp() {
108
146
  console.log('用法: wdyy init');
109
- console.log('在当前项目生成 AGENTS 规则、安装 wdyy Skill,并执行 openspec init。');
147
+ console.log('请先按提示手工安装外部依赖;命令将在当前项目生成 AGENTS 规则、安装 wdyy Skill,并执行 openspec init --tools agents 与 codegraph init。');
148
+ console.log('用法: wdyy validate');
149
+ console.log('只读验证当前项目的初始化产物、工具链和 Skill 完整性。');
110
150
  console.log('用法: wdyy update-skill <skill-name> | --all');
111
- console.log('仅更新当前项目的一个或全部受管理 wdyy Skill,不执行项目初始化。');
151
+ console.log('仅更新系统用户目录中的一个或全部受管理 wdyy Skill,不执行项目初始化。');
152
+ }
153
+
154
+ function printManualPrerequisites() {
155
+ console.log('请先手工执行以下安装命令:');
156
+ for (const command of manualPrerequisiteCommands) console.log(command);
157
+ }
158
+
159
+ function inspectCommand(command, displayName) {
160
+ const result = spawnSync(command, ['--version'], { encoding: 'utf8', timeout: 10_000 });
161
+ if (result.error || result.status !== 0) {
162
+ const detail = result.error?.message ?? `退出码 ${result.status ?? 1}`;
163
+ return { problem: `${displayName} 命令不可用: ${detail}` };
164
+ }
165
+ const version = `${result.stdout ?? ''}`.split(/\r?\n/).find((line) => line.trim())?.trim();
166
+ if (!version) return { problem: `${displayName} 未返回版本信息` };
167
+ return { version };
168
+ }
169
+
170
+ async function inspectPrerequisites(skillsRoot = getGlobalSkillsRoot()) {
171
+ const problems = [];
172
+ const tools = { node: process.version };
173
+ const nodeMajor = Number(process.versions.node.split('.')[0]);
174
+ if (!Number.isInteger(requiredNodeMajor) || nodeMajor < requiredNodeMajor) {
175
+ problems.push(`Node.js 版本不符合要求: 当前 ${process.version},要求 >=${requiredNodeMajor}`);
176
+ }
177
+ for (const [command, displayName] of [['pnpm', 'pnpm'], ['git', 'Git'], ['openspec', 'OpenSpec'], ['codegraph', 'CodeGraph']]) {
178
+ const inspection = inspectCommand(command, displayName);
179
+ if (inspection.problem) problems.push(inspection.problem);
180
+ else tools[command] = inspection.version;
181
+ }
182
+ if (tools.pnpm !== requiredPnpmVersion) {
183
+ problems.push(`pnpm 版本不符合要求: 当前 ${tools.pnpm ?? '不可用'},要求 ${requiredPnpmVersion}`);
184
+ }
185
+ for (const skillName of requiredPublicSkillNames) {
186
+ try {
187
+ await access(join(skillsRoot, skillName, 'SKILL.md'));
188
+ } catch (error) {
189
+ if (error.code !== 'ENOENT') throw error;
190
+ problems.push(`缺少公共 Skill: ${skillName}`);
191
+ }
192
+ }
193
+ return { problems, tools };
194
+ }
195
+
196
+ function reportProblems(problems) {
197
+ for (const problem of problems) console.error(`- ${problem}`);
198
+ }
199
+
200
+ async function hashFile(path) {
201
+ return createHash('sha256').update(await readFile(path)).digest('hex');
202
+ }
203
+
204
+ async function createToolchainManifest(targetRoot, agentsFile, tools, skillsRoot = getGlobalSkillsRoot()) {
205
+ const publicSkills = {};
206
+ for (const name of requiredPublicSkillNames) publicSkills[name] = await hashFile(join(skillsRoot, name, 'SKILL.md'));
207
+ const managedSkills = {};
208
+ for (const name of managedSkillNames) managedSkills[name] = await hashFile(join(skillsRoot, name, 'SKILL.md'));
209
+ return {
210
+ initializer: { name: packageMetadata.name, version: packageMetadata.version },
211
+ tools,
212
+ agentsFile,
213
+ skills: { public: publicSkills, managed: managedSkills }
214
+ };
215
+ }
216
+
217
+ async function writeToolchainManifest(targetRoot, agentsFile, tools, writtenPaths) {
218
+ const toolchainRoot = join(targetRoot, '.wdyy');
219
+ const manifestPath = join(toolchainRoot, 'toolchain.json');
220
+ await mkdir(toolchainRoot, { recursive: true });
221
+ const manifest = await createToolchainManifest(targetRoot, agentsFile, tools);
222
+ await writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, 'utf8');
223
+ writtenPaths.push(manifestPath);
224
+ console.log(`已写入工具链清单: ${manifestPath}`);
112
225
  }
113
226
 
114
227
  async function installManagedSkills(skillsRoot, writtenPaths, skillNames = managedSkillNames) {
@@ -231,28 +344,27 @@ async function writeAgentsFile(targetRoot, writtenPaths) {
231
344
  await writeFile(destination, generatedAgentsContent, 'utf8');
232
345
  writtenPaths.push(destination);
233
346
  console.log(`已写入项目规则: ${destination}`);
347
+ return basename(destination);
234
348
  }
235
349
 
236
- async function appendBugRecordTemplate(targetRoot, writtenPaths) {
350
+ async function createBugRecordTemplate(targetRoot, writtenPaths) {
237
351
  const docsRoot = join(targetRoot, 'docs');
238
352
  const bugRecordPath = join(docsRoot, 'bug_record.md');
239
353
  await mkdir(docsRoot, { recursive: true });
240
-
241
- let prefix = '';
242
354
  try {
243
- const existing = await readFile(bugRecordPath, 'utf8');
244
- if (existing.length > 0) prefix = existing.endsWith('\n') ? '\n' : '\n\n';
355
+ await access(bugRecordPath);
356
+ console.log(`已保留 BUG 记录: ${bugRecordPath}`);
357
+ return;
245
358
  } catch (error) {
246
359
  if (error.code !== 'ENOENT') throw error;
247
360
  }
248
-
249
- await appendFile(bugRecordPath, `${prefix}${bugRecordTemplate}`, 'utf8');
361
+ await writeFile(bugRecordPath, bugRecordTemplate, 'utf8');
250
362
  writtenPaths.push(bugRecordPath);
251
- console.log(`已更新 BUG 记录模板: ${bugRecordPath}`);
363
+ console.log(`已创建 BUG 记录模板: ${bugRecordPath}`);
252
364
  }
253
365
 
254
366
  function initializeOpenSpec(targetRoot, writtenPaths) {
255
- const result = spawnSync('openspec', ['init'], { cwd: targetRoot, stdio: 'inherit' });
367
+ const result = spawnSync('openspec', ['init', '--tools', 'agents'], { cwd: targetRoot, stdio: 'inherit' });
256
368
  if (result.error || result.status !== 0) {
257
369
  const detail = result.error?.message ?? `退出码 ${result.status ?? 1}`;
258
370
  console.error(`OpenSpec 初始化失败: ${detail}`);
@@ -264,21 +376,105 @@ function initializeOpenSpec(targetRoot, writtenPaths) {
264
376
  return 0;
265
377
  }
266
378
 
379
+ function initializeCodeGraph(targetRoot, writtenPaths) {
380
+ const result = spawnSync('codegraph', ['init'], { cwd: targetRoot, stdio: 'inherit' });
381
+ if (result.error || result.status !== 0) {
382
+ const detail = result.error?.message ?? `退出码 ${result.status ?? 1}`;
383
+ console.error(`CodeGraph 初始化失败: ${detail}`);
384
+ printWrittenPaths(writtenPaths);
385
+ return result.status ?? 1;
386
+ }
387
+
388
+ console.log('CodeGraph 初始化完成。');
389
+ return 0;
390
+ }
391
+
392
+ async function pathExists(path) {
393
+ try {
394
+ await access(path);
395
+ return true;
396
+ } catch (error) {
397
+ if (error.code === 'ENOENT') return false;
398
+ throw error;
399
+ }
400
+ }
401
+
402
+ async function validateProject(targetRoot) {
403
+ const problems = [];
404
+ const skillsRoot = getGlobalSkillsRoot();
405
+ const prerequisites = await inspectPrerequisites(skillsRoot);
406
+ problems.push(...prerequisites.problems);
407
+
408
+ for (const path of ['.git', ...defaultProjectDirectories, '.env.example', 'docs/bug_record.md', 'openspec', '.codegraph']) {
409
+ if (!await pathExists(join(targetRoot, path))) problems.push(`缺少初始化产物: ${path}`);
410
+ }
411
+
412
+ const gitIgnorePath = join(targetRoot, '.gitignore');
413
+ if (!await pathExists(gitIgnorePath)) {
414
+ problems.push('缺少初始化产物: .gitignore');
415
+ } else {
416
+ const entries = new Set((await readFile(gitIgnorePath, 'utf8')).split(/\r?\n/).map((entry) => entry.trim()).filter(Boolean));
417
+ for (const entry of requiredGitIgnoreEntries) if (!entries.has(entry)) problems.push(`.gitignore 缺少规则: ${entry}`);
418
+ }
419
+
420
+ const manifestPath = join(targetRoot, '.wdyy', 'toolchain.json');
421
+ if (!await pathExists(manifestPath)) {
422
+ problems.push('缺少初始化产物: .wdyy/toolchain.json');
423
+ } else {
424
+ try {
425
+ const actualText = await readFile(manifestPath, 'utf8');
426
+ const actual = JSON.parse(actualText);
427
+ const agentsFile = actual.agentsFile;
428
+ if (!['AGENTS.md', 'AGENTS_new.md'].includes(agentsFile)) {
429
+ problems.push('工具链清单中的规则文件路径无效');
430
+ } else if (!await pathExists(join(targetRoot, agentsFile))) {
431
+ problems.push(`缺少工具链清单声明的规则文件: ${agentsFile}`);
432
+ } else if (await readFile(join(targetRoot, agentsFile), 'utf8') !== generatedAgentsContent) {
433
+ problems.push(`项目规则已漂移: ${agentsFile}`);
434
+ }
435
+ if (prerequisites.problems.length === 0 && ['AGENTS.md', 'AGENTS_new.md'].includes(agentsFile)) {
436
+ const expected = `${JSON.stringify(await createToolchainManifest(targetRoot, agentsFile, prerequisites.tools, skillsRoot), null, 2)}\n`;
437
+ if (actualText !== expected) problems.push('工具链清单与当前工具或 Skill 不一致');
438
+ }
439
+ } catch (error) {
440
+ problems.push(`工具链清单不可读: ${error.message}`);
441
+ }
442
+ }
443
+
444
+ if (problems.length > 0) {
445
+ console.error('wdyy 验证失败:');
446
+ reportProblems(problems);
447
+ return 1;
448
+ }
449
+ console.log('wdyy 验证通过。');
450
+ return 0;
451
+ }
452
+
267
453
  export async function run(argumentsList) {
268
454
  if (argumentsList.length === 1 && argumentsList[0] === 'init') {
269
455
  const writtenPaths = [];
270
456
  try {
457
+ printManualPrerequisites();
458
+ const prerequisites = await inspectPrerequisites();
459
+ if (prerequisites.problems.length > 0) {
460
+ console.error('初始化前置条件不满足:');
461
+ reportProblems(prerequisites.problems);
462
+ return 1;
463
+ }
271
464
  const gitExitCode = initializeGit(process.cwd(), writtenPaths);
272
465
  if (gitExitCode !== 0) return gitExitCode;
273
- await writeAgentsFile(process.cwd(), writtenPaths);
466
+ const agentsFile = await writeAgentsFile(process.cwd(), writtenPaths);
274
467
  const globalSkillsRoot = getGlobalSkillsRoot();
275
468
  await installManagedSkills(globalSkillsRoot, writtenPaths);
276
469
  await removeDeprecatedManagedSkills(globalSkillsRoot, writtenPaths);
277
470
  await appendGitIgnoreEntries(process.cwd(), writtenPaths);
278
471
  await createDefaultProjectLayout(process.cwd(), writtenPaths);
279
- await appendBugRecordTemplate(process.cwd(), writtenPaths);
472
+ await createBugRecordTemplate(process.cwd(), writtenPaths);
280
473
  const openSpecExitCode = initializeOpenSpec(process.cwd(), writtenPaths);
281
474
  if (openSpecExitCode !== 0) return openSpecExitCode;
475
+ const codeGraphExitCode = initializeCodeGraph(process.cwd(), writtenPaths);
476
+ if (codeGraphExitCode !== 0) return codeGraphExitCode;
477
+ await writeToolchainManifest(process.cwd(), agentsFile, prerequisites.tools, writtenPaths);
282
478
  console.log('wdyy 初始化完成。');
283
479
  return 0;
284
480
  } catch (error) {
@@ -288,6 +484,15 @@ export async function run(argumentsList) {
288
484
  }
289
485
  }
290
486
 
487
+ if (argumentsList.length === 1 && argumentsList[0] === 'validate') {
488
+ try {
489
+ return await validateProject(process.cwd());
490
+ } catch (error) {
491
+ console.error(`wdyy 验证失败: ${error.message}`);
492
+ return 1;
493
+ }
494
+ }
495
+
291
496
  if (argumentsList[0] === 'update-skill') {
292
497
  const skillArguments = argumentsList.slice(1);
293
498
  const updateAll = skillArguments.length === 1 && skillArguments[0] === '--all';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wdyy/skills",
3
- "version": "0.1.23",
3
+ "version": "0.1.25",
4
4
  "description": "企业内部开发 Skill 与项目初始化命令",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@12.1.0",