Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,26 @@

All notable changes to OpenOne Workflow Kit are documented here. The project follows [Semantic Versioning](https://semver.org/).

## [1.0.0] - 2026-07-27

### Changed

- Replace the unsupported project-level `.codex/prompts/` adapter with one umbrella Skill and 32 manifest-driven stage Skills under `.agents/skills/`.
- Give every development and commercialization stage a stable ASCII Skill slug, localized Codex display metadata, and explicit-only invocation policy.
- Make `workflow/core/command-manifest.yaml` the single source of truth for command IDs, descriptions, argument hints, implementation gates, and Codex Skill paths.
- Document the actual Codex surfaces: Desktop Skills search via `/`, and `/skills` or `$<skill-slug>` in CLI/IDE; literal Claude-style project commands such as `/01-需求讨论` are not claimed.

### Migration

- `--upgrade` now removes only direct `.codex/prompts/` children whose content exactly matches the 0.1.0 generated template, including CRLF checkouts; nested directories remain user-owned.
- User-authored or edited prompts, workspace facts and principles, custom Skills, and symbolic links are preserved; same-name custom Skills receive merge sidecars, and orphan Skills are removed only when they carry the OpenOne managed marker.
- `--dry-run` reports the migration plan without changing the target workspace.

### Verification

- Add all-32-stage Codex adapter conformance, negative manifest cases, safe migration fixtures, and installed-package coverage to the release checks.
- Require a real Codex `skills/list` acceptance check before publication.

## [0.1.0] - 2026-07-19

### Added
Expand All @@ -19,3 +39,4 @@ All notable changes to OpenOne Workflow Kit are documented here. The project fol
- Release checks scan for common credential assignments, private-key markers, and private-network URLs; maintainers can add an external private denylist.

[0.1.0]: https://github.com/bluecoast1379/openone-workflow-kit/releases/tag/v0.1.0
[1.0.0]: https://github.com/bluecoast1379/openone-workflow-kit/releases/tag/v1.0.0
27 changes: 20 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@

```bash
mkdir openone-demo && cd openone-demo
npx --yes --package openone-workflow-kit@0.1.0 openone-workflow-init --target . --tools codex,cursor --yes
npx --yes --package openone-workflow-kit@1.0.0 openone-workflow-init --target . --tools codex,cursor --yes
node -e "for (const f of ['workflow/team-profile.yaml','AGENTS.md']) require('node:fs').accessSync(f); console.log('OpenOne ready')"
```

预期结果:终端输出 `OpenOne ready`,并生成 `workflow/team-profile.yaml`、`workflow/core/`、`AGENTS.md` 与所选工具 adapter。如果 npm 网络不可用,参考 [Git 或本地 tarball 安装](./docs/shareable-install.md)。

![OpenOne Workflow Kit 30 秒体验:创建目录、运行 v0.1.0、验证产物](./docs/assets/quick-demo.svg)
![OpenOne Workflow Kit 30 秒体验:创建目录、运行 v1.0.0、验证产物](./docs/assets/quick-demo.svg)

## 架构:一套 Core,两条轨道

Expand All @@ -43,14 +43,14 @@ node -e "for (const f of ['workflow/team-profile.yaml','AGENTS.md']) require('no
2. `workflow/core/` 提供工具无关的阶段、完成合同、闸门、模板和 Oracle 能力。
3. `workflow/adapters/` 为 Claude、Codex、Cursor、Copilot、CodeBuddy、Kiro 和 Trae 生成薄入口;共享 core,但不承诺工具体验完全相同。

深入阅读:[定义完成指南](./docs/definition-of-done.md) · [双轨工作流设计](./docs/dual-track-workflow.md) · [v0.1.0 Release Notes](./docs/releases/v0.1.0.md)
深入阅读:[定义完成指南](./docs/definition-of-done.md) · [双轨工作流设计](./docs/dual-track-workflow.md) · [v1.0.0 Release Notes](./docs/releases/v1.0.0.md)

## 一键初始化

从 npm 的不可变版本运行(Node.js 18+):

```bash
npx --yes --package openone-workflow-kit@0.1.0 openone-workflow-init --target . --tools codex,claude,cursor --yes
npx --yes --package openone-workflow-kit@1.0.0 openone-workflow-init --target . --tools codex,claude,cursor --yes
```

从源码 checkout 本地运行:
Expand All @@ -65,7 +65,7 @@ node /path/to/openone-workflow-kit/bin/init-workspace.cjs --target .
/path/to/openone-workflow-kit/install.sh . --tools codex,claude,cursor
```

如果你拿到的是 Git 地址或本地 tarball,见 [可分享安装方式](./docs/shareable-install.md)。该文档也说明了如何先验证 Registry 中的 `0.1.0` 再安装,避免把尚未发布的版本当作可用事实。
如果你拿到的是 Git 地址或本地 tarball,见 [可分享安装方式](./docs/shareable-install.md)。该文档也说明了如何先验证 Registry 中的 `1.0.0` 再安装,避免把尚未发布的版本当作可用事实。

常用参数:

Expand All @@ -74,7 +74,7 @@ node /path/to/openone-workflow-kit/bin/init-workspace.cjs --target .
node /path/to/openone-workflow-kit/bin/init-workspace.cjs --target . --tools codex,claude,cursor

# GitHub 包安装方式
npx --yes --package "git+https://github.com/bluecoast1379/openone-workflow-kit.git#v0.1.0" openone-workflow-init --target . --tools codex,claude,cursor
npx --yes --package "git+https://github.com/bluecoast1379/openone-workflow-kit.git#v1.0.0" openone-workflow-init --target . --tools codex,claude,cursor

# 工具名支持 trea 别名,会自动归一为 trae
node /path/to/openone-workflow-kit/bin/init-workspace.cjs --target . --tools codex,trea,codebuddy
Expand All @@ -94,13 +94,26 @@ node /path/to/openone-workflow-kit/bin/init-workspace.cjs --target . --dry-run
- 交互式终端:逐项提问。
- 非交互模式:生成 `workflow/INITIALIZATION_QUESTIONS.md`。
4. 生成跨工具入口:
- Codex: `AGENTS.md`、`.codex/prompts/`
- Codex: `AGENTS.md`、`.agents/skills/agent-workflow/` 与 32 个 `.agents/skills/<skill-slug>/`
- Claude Code: `CLAUDE.md`、`.claude/commands/`
- Cursor: `.cursor/rules/` 和 `.cursor/commands/`
- Copilot: `.github/copilot-instructions.md`
- CodeBuddy / Kiro / Trae: 各自 `instructions.md`
5. 初始化器本身不执行远程 Git、push、构建部署或数据库写入。生成后的个人工作流允许 agent 在范围明确且工作树干净时执行本地分支命名、创建、commit、tag 和本地 merge;远程 push、release、部署和生产配置写入需要用户明确授权。

### Codex 调用方式与 0.1.0 迁移

Codex Desktop 输入 `/01`、`/B1` 等关键词后,从 `/` 面板的 Skills 分组选择中文阶段;CLI/IDE 使用 `/skills` 或 `$workflow-...`。这是 Skill 选择,不是 Claude 式字面项目命令 `/01-需求讨论`。

从 0.1.0 升级时执行:

```bash
npx --yes --package openone-workflow-kit@1.0.0 openone-workflow-init \
--target . --tools codex --upgrade --force --yes
```

升级会用精确的 0.1.0 模板指纹识别旧 `.codex/prompts/` 根层文件:只删除未改动的 kit 生成文件,保留子目录、用户自定义或编辑过的文件,并生成当前 Codex 可发现的 `.agents/skills/`。已有 `team-profile.yaml`、工作区宪法和个人规范不会被 `--force` 覆盖;同名用户 Skill 会保留原文件并输出 `.agent-workflow-new` 合并副本。可先追加 `--dry-run` 查看迁移计划。

## 隐私与脱敏边界

本 kit 自身不应包含任何真实客户字段、内部系统地址、真实 URL 或凭证。对外分发前运行:
Expand Down
41 changes: 41 additions & 0 deletions bin/check-command-manifest.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
const { loadCommandManifest } = require('./command-manifest.cjs');

const root = path.resolve(__dirname, '..');
const manifestFile = path.join(root, 'workflow/core/command-manifest.yaml');
const commandsDir = path.join(root, 'workflow/core/commands');
const errors = [];
let manifest;

try {
manifest = loadCommandManifest(manifestFile);
} catch (error) {
console.error(error.message);
process.exit(1);
}

const expectedFiles = new Set(manifest.commands.map((command) => `${command.id}.md`));
for (const command of manifest.commands) {
const file = path.join(commandsDir, `${command.id}.md`);
if (!fs.existsSync(file)) {
errors.push(`缺少 core command: ${command.id}.md`);
continue;
}
const content = fs.readFileSync(file, 'utf8');
if (!content.includes(`# /${command.id}`)) errors.push(`${command.id}.md 缺少匹配标题`);
}

for (const name of fs.readdirSync(commandsDir)) {
if (!name.endsWith('.md') || name === 'README.md') continue;
if (!expectedFiles.has(name)) errors.push(`core command 未登记到 manifest: ${name}`);
}

if (errors.length) {
console.error(`命令清单校验失败(${errors.length} 项):`);
for (const error of errors) console.error(`- ${error}`);
process.exit(1);
}

console.log(`命令清单校验通过:${manifest.commands.length} 个命令,id、slug、core 文件与实现闸门映射一致。`);
199 changes: 199 additions & 0 deletions bin/command-manifest.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
const fs = require('fs');

const REQUIRED_FIELDS = [
'id',
'skill_slug',
'title',
'description',
'argument_hint',
'implementation_gate'
];

const EXPECTED_V1_COMMAND_IDS = [
'init-workspace',
'new-feature',
'01-需求讨论',
'澄清',
'02-产品文档',
'02B-UI设计',
'03-技术架构',
'03-06-研发准备',
'04-代码实现',
'04A-前端代码实现',
'04B-后端代码实现',
'05-代码审查',
'06-测试用例',
'定义完成',
'一致性检查',
'交付至完成',
'07-测试执行',
'08-发布准备',
'09-发布执行',
'10-复盘总结',
'new-product',
'B1-业务定位',
'B1-B8-商业化准备',
'B2-商业模式',
'B3-PMF与客户画像',
'B4-场景与购买旅程',
'B5-渠道漏斗映射',
'B6-营销获客策略',
'B7-营销预算',
'B8-渠道执行策略',
'B9-策略复盘',
'workflow-status'
];
const RESERVED_SKILL_SLUGS = new Set(['agent-workflow']);

function loadCommandManifest(file) {
const source = fs.readFileSync(file, 'utf8');
const manifest = parseCommandManifest(source, file);
validateCommandManifest(manifest, file);
return manifest;
}

function parseCommandManifest(source, file = 'command-manifest.yaml') {
const manifest = { schemaVersion: '', commandCount: NaN, commands: [] };
const seenRootFields = new Set();
let current = null;
let inCommands = false;

source.split(/\r?\n/).forEach((line, index) => {
const lineNumber = index + 1;
if (!line.trim() || line.trimStart().startsWith('#')) return;

let match = line.match(/^schema_version:\s*(.+)$/);
if (match) {
assertUniqueField(seenRootFields, 'schema_version', file, lineNumber);
manifest.schemaVersion = parseScalar(match[1], file, lineNumber);
return;
}
match = line.match(/^command_count:\s*(.+)$/);
if (match) {
assertUniqueField(seenRootFields, 'command_count', file, lineNumber);
manifest.commandCount = parseScalar(match[1], file, lineNumber);
return;
}
if (/^commands:\s*$/.test(line)) {
assertUniqueField(seenRootFields, 'commands', file, lineNumber);
inCommands = true;
return;
}
if (!inCommands) throw syntaxError(file, lineNumber, 'commands 之前存在未知字段');

match = line.match(/^ - ([a-z_]+):\s*(.+)$/);
if (match) {
if (match[1] !== 'id') throw syntaxError(file, lineNumber, '每个 command 的首字段必须是 id');
current = {};
manifest.commands.push(current);
current[match[1]] = parseScalar(match[2], file, lineNumber);
return;
}
match = line.match(/^ ([a-z_]+):\s*(.+)$/);
if (match && current) {
if (!REQUIRED_FIELDS.includes(match[1])) throw syntaxError(file, lineNumber, `未知 command 字段: ${match[1]}`);
if (match[1] in current) throw syntaxError(file, lineNumber, `重复 command 字段: ${match[1]}`);
current[match[1]] = parseScalar(match[2], file, lineNumber);
return;
}
throw syntaxError(file, lineNumber, `无法解析: ${line.trim()}`);
});

return manifest;
}

function assertUniqueField(seen, field, file, lineNumber) {
if (seen.has(field)) throw syntaxError(file, lineNumber, `重复根字段: ${field}`);
seen.add(field);
}

function validateCommandManifest(manifest, file = 'command-manifest.yaml') {
const errors = [];
if (manifest.schemaVersion !== '1.0') errors.push('schema_version 必须为 "1.0"');
if (!Number.isInteger(manifest.commandCount) || manifest.commandCount < 1) {
errors.push('command_count 必须为正整数');
}
if (manifest.schemaVersion === '1.0' && manifest.commandCount !== EXPECTED_V1_COMMAND_IDS.length) {
errors.push(`schema 1.0 的 command_count 必须为 ${EXPECTED_V1_COMMAND_IDS.length}`);
}
if (manifest.commandCount !== manifest.commands.length) {
errors.push(`command_count=${manifest.commandCount},实际 commands=${manifest.commands.length}`);
}

const ids = new Set();
const foldedIds = new Set();
const slugs = new Set();
for (const [index, command] of manifest.commands.entries()) {
const label = `commands[${index}]`;
for (const field of REQUIRED_FIELDS) {
if (!(field in command)) errors.push(`${label} 缺少 ${field}`);
}
if (!isPortableCommandId(command.id)) {
errors.push(`${label}.id 不是安全文件名`);
} else if (ids.has(command.id) || foldedIds.has(command.id.toLocaleLowerCase('en-US'))) {
errors.push(`重复 id: ${command.id}`);
} else {
ids.add(command.id);
foldedIds.add(command.id.toLocaleLowerCase('en-US'));
}
if (typeof command.skill_slug !== 'string' || !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(command.skill_slug)) {
errors.push(`${label}.skill_slug 必须是小写 ASCII kebab-case`);
} else if (command.skill_slug.length > 63) {
errors.push(`${label}.skill_slug 长度必须小于 64`);
} else if (RESERVED_SKILL_SLUGS.has(command.skill_slug)) {
errors.push(`${label}.skill_slug 是保留的总入口 slug: ${command.skill_slug}`);
} else if (slugs.has(command.skill_slug)) {
errors.push(`重复 skill_slug: ${command.skill_slug}`);
} else {
slugs.add(command.skill_slug);
}
for (const field of ['title', 'description', 'argument_hint']) {
if (typeof command[field] !== 'string' || !command[field].trim()) errors.push(`${label}.${field} 必须为非空字符串`);
}
if (typeof command.implementation_gate !== 'boolean') {
errors.push(`${label}.implementation_gate 必须为 boolean`);
}
}

if (manifest.schemaVersion === '1.0') {
const expected = new Set(EXPECTED_V1_COMMAND_IDS);
const missing = EXPECTED_V1_COMMAND_IDS.filter((id) => !ids.has(id));
const unexpected = [...ids].filter((id) => !expected.has(id));
if (missing.length || unexpected.length) {
errors.push(`schema 1.0 command 集合不完整;缺少: ${missing.join(', ') || '无'};多余: ${unexpected.join(', ') || '无'}`);
}
}

const gateIds = manifest.commands.filter((item) => item.implementation_gate).map((item) => item.id).sort();
const expectedGateIds = ['04-代码实现', '04A-前端代码实现', '04B-后端代码实现', '交付至完成'].sort();
if (JSON.stringify(gateIds) !== JSON.stringify(expectedGateIds)) {
errors.push(`implementation_gate 只能标记 ${expectedGateIds.join(', ')}`);
}
if (errors.length) throw new Error(`${file} 校验失败:\n- ${errors.join('\n- ')}`);
}

function isPortableCommandId(value) {
if (typeof value !== 'string' || !value || value !== value.trim()) return false;
if (value === '.' || value === '..' || value.includes('..')) return false;
if (/[<>:"/\\|?*\x00-\x1F]/.test(value) || /[. ]$/.test(value)) return false;
return !/^(con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\..*)?$/i.test(value);
}

function parseScalar(raw, file, lineNumber) {
try {
return JSON.parse(raw);
} catch {
throw syntaxError(file, lineNumber, '标量必须使用 JSON 字符串、数字或 boolean 格式');
}
}

function syntaxError(file, lineNumber, message) {
return new Error(`${file}:${lineNumber} ${message}`);
}

module.exports = {
loadCommandManifest,
parseCommandManifest,
validateCommandManifest,
EXPECTED_V1_COMMAND_IDS
};
Loading
Loading