dic-workflow-kit 1.0.0 → 1.1.1
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/.codex-plugin/plugin.json +1 -1
- package/CHANGELOG.md +47 -0
- package/README.md +152 -11
- package/README.zh-CN.md +145 -8
- package/agents/code-repairer.md +1 -1
- package/agents/contract-oracle.md +1 -1
- package/agents/final-reviewer.md +1 -1
- package/agents/flow-auditor.md +1 -1
- package/agents/impl-inspector.md +1 -1
- package/agents/profile-builder.md +1 -1
- package/agents/repair-planner.md +1 -1
- package/agents/spec-reader.md +1 -1
- package/agents/validation-runner.md +1 -1
- package/dist/dic-workflow-kit.mjs +10 -0
- package/package.json +13 -13
- package/skills/knowledge-bdd/SKILL.md +1 -1
- package/skills/knowledge-openspec/SKILL.md +1 -1
- package/skills/knowledge-repair-distill/SKILL.md +1 -1
- package/skills/workflow-core/SKILL.md +1 -1
- package/skills/workflow-intake/SKILL.md +1 -1
- package/skills/workflow-profile/SKILL.md +1 -1
- package/skills/workflow-repair/SKILL.md +1 -1
- package/bin/dic-workflow-kit.mjs +0 -150
- package/scripts/dic_workflow.py +0 -507
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to DIC Workflow Kit are documented in this file.
|
|
4
|
+
|
|
5
|
+
The project follows [Semantic Versioning](https://semver.org/). Both npm packages, `dic-workflow-kit` and `@tonyclaw/dic-workflow-kit`, always use the same version and runtime payload.
|
|
6
|
+
|
|
7
|
+
## [1.1.1] - 2026-07-27
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Added this changelog.
|
|
12
|
+
- Added complete Chinese and English user guides covering installation, workflow prompts, OpenSpec Change usage, output interpretation, and troubleshooting.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- Made the unscoped `npx dic-workflow-kit@<version>` command the recommended one-shot installation entry for consistent Windows behavior.
|
|
17
|
+
- Updated version synchronization so both scoped and unscoped npm examples advance with each release.
|
|
18
|
+
|
|
19
|
+
### Security and packaging
|
|
20
|
+
|
|
21
|
+
- Kept the npm release as a runtime-only package: no development CLI source, Python helper, tests, release scripts, PowerPoint files, temporary files, or source maps.
|
|
22
|
+
- Retained only the minified installer runtime and the Skills, Subagents, schemas, adapters, manifests, and user documentation required to use the kit.
|
|
23
|
+
|
|
24
|
+
## [1.1.0] - 2026-07-27
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
|
|
28
|
+
- Added one npm installer for Codex, Claude Code, OpenCode, and MiMo Code.
|
|
29
|
+
- Added user-level and project-level installation for supported hosts.
|
|
30
|
+
- Added host-specific Subagent frontmatter adaptation.
|
|
31
|
+
- Added convergent dual publishing for `dic-workflow-kit` and `@tonyclaw/dic-workflow-kit`.
|
|
32
|
+
- Added isolated installation tests for all four hosts.
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
|
|
36
|
+
- Promoted the package from a Codex-only installer to a multi-host runtime.
|
|
37
|
+
- Published the CLI as a minified runtime and removed development source files from npm artifacts.
|
|
38
|
+
|
|
39
|
+
## [1.0.0] - 2026-07-27
|
|
40
|
+
|
|
41
|
+
### Added
|
|
42
|
+
|
|
43
|
+
- Initial npm release.
|
|
44
|
+
- Included seven workflow and knowledge Skills.
|
|
45
|
+
- Included nine evidence-producing Subagents.
|
|
46
|
+
- Added Codex personal marketplace installation.
|
|
47
|
+
- Added OpenSpec intake, obligation extraction, BDD knowledge, repair planning, validation, and final evidence contracts.
|
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# Design-Implementation Consistency Workflow Kit
|
|
2
2
|
|
|
3
|
-
Version: `1.
|
|
3
|
+
Version: `1.1.1`
|
|
4
|
+
|
|
5
|
+
[Changelog](CHANGELOG.md)
|
|
4
6
|
|
|
5
7
|
Design-Implementation Consistency Workflow Kit, or DIC Workflow Kit, is a portable agentic workflow package for checking and repairing whether implementation matches design intent.
|
|
6
8
|
|
|
@@ -10,31 +12,170 @@ The kit is not tied to one contest, one repository, one language stack, or one a
|
|
|
10
12
|
|
|
11
13
|
> The differentiator is not the number of agents. DIC Workflow Kit uses design intent, typed handoffs, project profiles, validation evidence, and residual-risk reporting to prove that implementation remains aligned with its design contract.
|
|
12
14
|
|
|
13
|
-
## Install
|
|
15
|
+
## Install with npm
|
|
16
|
+
|
|
17
|
+
The npm package contains all seven Skills and all nine Subagents and installs them with host-native directory layouts for Codex, Claude Code, OpenCode, and MiMo Code.
|
|
14
18
|
|
|
15
|
-
|
|
19
|
+
Codex remains the default, so the original command is backward compatible:
|
|
16
20
|
|
|
17
21
|
```text
|
|
18
|
-
npx dic-workflow-kit install
|
|
22
|
+
npx dic-workflow-kit@1.1.1 install
|
|
19
23
|
```
|
|
20
24
|
|
|
21
|
-
|
|
25
|
+
Select another host explicitly:
|
|
22
26
|
|
|
23
27
|
```text
|
|
24
|
-
|
|
25
|
-
dic-workflow-kit install
|
|
28
|
+
npx dic-workflow-kit@1.1.1 install --host claude-code
|
|
29
|
+
npx dic-workflow-kit@1.1.1 install --host opencode
|
|
30
|
+
npx dic-workflow-kit@1.1.1 install --host mimo-code
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
User scope is the default. To install into the current repository instead, use `--scope project`:
|
|
34
|
+
|
|
35
|
+
```text
|
|
36
|
+
npx dic-workflow-kit@1.1.1 install --host claude-code --scope project
|
|
37
|
+
npx dic-workflow-kit@1.1.1 install --host opencode --scope project
|
|
38
|
+
npx dic-workflow-kit@1.1.1 install --host mimo-code --scope project
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The host mappings are:
|
|
42
|
+
|
|
43
|
+
| Host | User scope | Project scope |
|
|
44
|
+
| --- | --- | --- |
|
|
45
|
+
| Codex | `~/.agents/plugins/plugins/dic-workflow-kit` plus marketplace registration | Not applicable to this plugin installer |
|
|
46
|
+
| Claude Code | `~/.claude/{skills,agents}` | `.claude/{skills,agents}` |
|
|
47
|
+
| OpenCode | `~/.config/opencode/{skills,agents}` | `.opencode/{skills,agents}` |
|
|
48
|
+
| MiMo Code | platform config root under `mimocode/{skills,agents}` | `.mimocode/{skills,agents}` |
|
|
49
|
+
|
|
50
|
+
On Windows, MiMo Code's user root resolves to `%LOCALAPPDATA%\mimocode`; `MIMOCODE_HOME` and `XDG_CONFIG_HOME` are honored where applicable. The installer adapts Subagent frontmatter per host while keeping `agents/` in the package as the single source of truth.
|
|
51
|
+
|
|
52
|
+
You can also install the CLI globally:
|
|
53
|
+
|
|
54
|
+
```text
|
|
55
|
+
npm install --global @tonyclaw/dic-workflow-kit@1.1.1
|
|
56
|
+
dic-workflow-kit install --host opencode
|
|
26
57
|
```
|
|
27
58
|
|
|
28
|
-
|
|
59
|
+
The same CLI can also be installed directly from the repository:
|
|
29
60
|
|
|
30
61
|
```text
|
|
31
62
|
npm install --global git+ssh://git@gitcode.com/TonyClaw/DICWorkflowKit.git
|
|
32
63
|
dic-workflow-kit install
|
|
33
64
|
```
|
|
34
65
|
|
|
35
|
-
|
|
66
|
+
Use `--dry-run` to preview the resolved destination, `--install-root <path>` for an isolated host root, or the backward-compatible Codex-only `--marketplace-root <path>`. The Codex installer preserves unrelated marketplace entries and replaces only the entry with the same plugin name. Restart or reload the target coding-agent session after its first Skills/agents directory is created.
|
|
67
|
+
|
|
68
|
+
## How to use it
|
|
69
|
+
|
|
70
|
+
### 1. Start the Coding Agent in your project root
|
|
71
|
+
|
|
72
|
+
After installation, enter the repository you want to inspect and restart Codex, Claude Code, OpenCode, or MiMo Code. An existing session may not discover a newly created Skills/agents directory immediately.
|
|
73
|
+
|
|
74
|
+
```text
|
|
75
|
+
cd /path/to/your-project
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### 2. Start the workflow with a prompt
|
|
79
|
+
|
|
80
|
+
This prompt works across all four hosts:
|
|
81
|
+
|
|
82
|
+
```text
|
|
83
|
+
Use DIC Workflow Kit to check design-implementation consistency in this project.
|
|
84
|
+
First identify authoritative design sources and protected paths, then build the
|
|
85
|
+
project profile. Map source-backed obligations to implementation, inspect gaps,
|
|
86
|
+
plan repairs only where design evidence exists, run project-declared validation,
|
|
87
|
+
and report the final consistency status and residual risks.
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
For an audit that must not modify code:
|
|
91
|
+
|
|
92
|
+
```text
|
|
93
|
+
Use DIC Workflow Kit to audit this project without modifying files.
|
|
94
|
+
Report design obligations, implementation gaps, evidence locations,
|
|
95
|
+
recommended repairs, and residual risks.
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
For an evidence-bounded repair:
|
|
99
|
+
|
|
100
|
+
```text
|
|
101
|
+
Use DIC Workflow Kit to inspect and repair this project.
|
|
102
|
+
Repair only gaps traceable to authoritative design sources. Run relevant
|
|
103
|
+
validation after each change; stop expanding changes when validation fails
|
|
104
|
+
and record the blocker.
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### 3. Check one OpenSpec Change
|
|
108
|
+
|
|
109
|
+
Provide the Change ID explicitly to reduce search scope and avoid treating historical changes as current truth:
|
|
110
|
+
|
|
111
|
+
```text
|
|
112
|
+
Use DIC Workflow Kit to check the OpenSpec Change
|
|
113
|
+
2026-06-09-add-ts-local-skill-source.
|
|
114
|
+
Start from its proposal, design, tasks, and delta specs. Cross-check the current
|
|
115
|
+
stable specs, implementation, and tests. Produce an obligation matrix,
|
|
116
|
+
implementation gaps, and a validation conclusion.
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
For an archived Change, archived files remain historical context while the promoted current stable Spec becomes the authoritative baseline.
|
|
120
|
+
|
|
121
|
+
### 4. Invoke the core Skill explicitly
|
|
122
|
+
|
|
123
|
+
On hosts that expose slash Skills, start with:
|
|
124
|
+
|
|
125
|
+
```text
|
|
126
|
+
/workflow-core
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
You can also name the required Skills directly:
|
|
130
|
+
|
|
131
|
+
```text
|
|
132
|
+
Use workflow-core, workflow-intake, workflow-profile, and
|
|
133
|
+
knowledge-openspec to inspect the current OpenSpec change.
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
You do not need to start all nine Subagents manually. The primary Agent should select roles according to evidence dependencies and require every delegation to produce an artifact consumed by a downstream step.
|
|
137
|
+
|
|
138
|
+
### 5. Read the results
|
|
139
|
+
|
|
140
|
+
Start with these outputs:
|
|
141
|
+
|
|
142
|
+
| File | Purpose |
|
|
143
|
+
| --- | --- |
|
|
144
|
+
| `reports/project-profile.json` | Project structure, design sources, protected paths, and validation commands |
|
|
145
|
+
| `reports/contract-obligations.md` | Human-readable design obligations with source anchors |
|
|
146
|
+
| `reports/implementation-gaps.md` | Confirmed implementation gaps |
|
|
147
|
+
| `reports/repair-plan.md` | Source-backed repair plan with stop conditions |
|
|
148
|
+
| `reports/final-consistency-report.md` | Final consistency conclusion and residual risks |
|
|
149
|
+
| `reports/FINAL_RESULT.json` | Machine-readable terminal status |
|
|
150
|
+
| `logs/trace/` | Intake, delegation, change, and validation evidence |
|
|
151
|
+
|
|
152
|
+
Terminal status is always `PASS`, `PARTIAL`, `BLOCKED`, or `FAIL`. `PASS` means the evidence chain and validation gates are satisfied, not merely that visible tests happen to pass.
|
|
153
|
+
|
|
154
|
+
### Troubleshooting
|
|
155
|
+
|
|
156
|
+
- **A Skill or Subagent is missing:** restart the Coding Agent and use `--dry-run` to confirm the resolved install root.
|
|
157
|
+
- **Only this repository should use the kit:** add `--scope project` for Claude Code, OpenCode, or MiMo Code.
|
|
158
|
+
- **The project does not use OpenSpec:** use design documents, APIs, schemas, READMEs, or product requirements as authoritative design sources.
|
|
159
|
+
- **A validation tool is unavailable:** record the missing command and environment evidence, and finish as `BLOCKED` or `PARTIAL`; do not claim success.
|
|
160
|
+
- **Do green tests prove consistency:** no. Requirements, branches, state transitions, side effects, and integration flows must also match the design.
|
|
161
|
+
|
|
162
|
+
### Dual npm release
|
|
163
|
+
|
|
164
|
+
Every release publishes the same version and payload under both npm names:
|
|
165
|
+
|
|
166
|
+
- `@tonyclaw/dic-workflow-kit`
|
|
167
|
+
- `dic-workflow-kit`
|
|
168
|
+
|
|
169
|
+
`package.json` is the canonical version source. Set a new version once; the helper propagates it to the Codex plugin manifest, workflow runtime, all Skills, all Subagents, and both READMEs:
|
|
170
|
+
|
|
171
|
+
```text
|
|
172
|
+
npm run release:set-version -- 1.1.1
|
|
173
|
+
npm run release:plan
|
|
174
|
+
npm run release:dual:dry-run
|
|
175
|
+
npm run release:dual
|
|
176
|
+
```
|
|
36
177
|
|
|
37
|
-
|
|
178
|
+
npm cannot provide an atomic transaction across two package names. The coordinator therefore uses a convergent release: it completes all local checks first, publishes both packages from temporary staging directories, skips a package when the exact version already exists, and can be safely rerun to publish only the missing side after a partial failure.
|
|
38
179
|
|
|
39
180
|
For a defense-oriented walkthrough of the project claims and their code evidence, see [答辩引导:项目特点与代码证据](docs/答辩引导-项目特点与代码证据.md).
|
|
40
181
|
|
|
@@ -157,7 +298,7 @@ For OpenSpec projects, the kit treats these as first-class design sources:
|
|
|
157
298
|
|
|
158
299
|
The kit checks whether proposals, specs, scenarios, and tasks were actually reflected in implementation and tests.
|
|
159
300
|
|
|
160
|
-
|
|
301
|
+
The source repository also contains a development helper for generating these profiles directly. It is intentionally excluded from the npm runtime package. From a source checkout, scope intake to one OpenSpec Change with:
|
|
161
302
|
|
|
162
303
|
```text
|
|
163
304
|
python scripts/dic_workflow.py \
|
package/README.zh-CN.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# 设计—实现一致性 Workflow Kit
|
|
2
2
|
|
|
3
|
-
版本:`1.
|
|
3
|
+
版本:`1.1.1`
|
|
4
|
+
|
|
5
|
+
[更新日志](CHANGELOG.md)
|
|
4
6
|
|
|
5
7
|
Design-Implementation Consistency Workflow Kit,简称 DIC Workflow Kit,是一套可移植的智能体工作流包,用于检查和修复“代码实现是否符合设计意图”。
|
|
6
8
|
|
|
@@ -8,29 +10,164 @@ Design-Implementation Consistency Workflow Kit,简称 DIC Workflow Kit,是
|
|
|
8
10
|
|
|
9
11
|
> 项目的差异不在 Agent 数量。DIC Workflow Kit 通过设计意图、类型化交接、项目画像、验证证据和残余风险报告,证明实现仍然符合设计契约。
|
|
10
12
|
|
|
11
|
-
## 通过 npm
|
|
13
|
+
## 通过 npm 安装
|
|
14
|
+
|
|
15
|
+
npm 包完整包含 7 个 Skills 和 9 个 Subagents,并支持 Codex、Claude Code、OpenCode 和 MiMo Code 的原生目录结构。
|
|
16
|
+
|
|
17
|
+
Codex 仍是默认宿主,原命令保持兼容:
|
|
18
|
+
|
|
19
|
+
```text
|
|
20
|
+
npx dic-workflow-kit@1.1.1 install
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
安装到其他宿主时显式指定 `--host`:
|
|
24
|
+
|
|
25
|
+
```text
|
|
26
|
+
npx dic-workflow-kit@1.1.1 install --host claude-code
|
|
27
|
+
npx dic-workflow-kit@1.1.1 install --host opencode
|
|
28
|
+
npx dic-workflow-kit@1.1.1 install --host mimo-code
|
|
29
|
+
```
|
|
12
30
|
|
|
13
|
-
|
|
31
|
+
默认是用户级安装;若只希望当前仓库使用,则增加 `--scope project`:
|
|
14
32
|
|
|
15
33
|
```text
|
|
16
|
-
npx dic-workflow-kit install
|
|
34
|
+
npx dic-workflow-kit@1.1.1 install --host claude-code --scope project
|
|
35
|
+
npx dic-workflow-kit@1.1.1 install --host opencode --scope project
|
|
36
|
+
npx dic-workflow-kit@1.1.1 install --host mimo-code --scope project
|
|
17
37
|
```
|
|
18
38
|
|
|
39
|
+
| 宿主 | 用户级目录 | 项目级目录 |
|
|
40
|
+
| --- | --- | --- |
|
|
41
|
+
| Codex | `~/.agents/plugins/plugins/dic-workflow-kit`,并注册 marketplace | 当前插件安装器不提供项目级模式 |
|
|
42
|
+
| Claude Code | `~/.claude/{skills,agents}` | `.claude/{skills,agents}` |
|
|
43
|
+
| OpenCode | `~/.config/opencode/{skills,agents}` | `.opencode/{skills,agents}` |
|
|
44
|
+
| MiMo Code | 平台配置根目录下的 `mimocode/{skills,agents}` | `.mimocode/{skills,agents}` |
|
|
45
|
+
|
|
46
|
+
Windows 上 MiMo Code 的用户级根目录为 `%LOCALAPPDATA%\mimocode`;适用时也会读取 `MIMOCODE_HOME` 和 `XDG_CONFIG_HOME`。安装器以仓库中的 `agents/` 为唯一来源,并按宿主转换 Subagent frontmatter,避免维护四套重复内容。
|
|
47
|
+
|
|
19
48
|
也可以先全局安装 CLI:
|
|
20
49
|
|
|
21
50
|
```text
|
|
22
|
-
npm install --global dic-workflow-kit
|
|
23
|
-
dic-workflow-kit install
|
|
51
|
+
npm install --global @tonyclaw/dic-workflow-kit@1.1.1
|
|
52
|
+
dic-workflow-kit install --host opencode
|
|
24
53
|
```
|
|
25
54
|
|
|
26
|
-
|
|
55
|
+
也可以直接从仓库安装同一个 CLI:
|
|
27
56
|
|
|
28
57
|
```text
|
|
29
58
|
npm install --global git+ssh://git@gitcode.com/TonyClaw/DICWorkflowKit.git
|
|
30
59
|
dic-workflow-kit install
|
|
31
60
|
```
|
|
32
61
|
|
|
33
|
-
|
|
62
|
+
使用 `--dry-run` 预演目标目录,使用 `--install-root <path>` 指定隔离的宿主根目录;Codex 仍兼容 `--marketplace-root <path>`。首次创建 Skills 或 agents 目录后,请重启或重新加载对应 Coding Agent 会话。
|
|
63
|
+
|
|
64
|
+
## 用户如何使用
|
|
65
|
+
|
|
66
|
+
### 1. 在项目根目录启动 Coding Agent
|
|
67
|
+
|
|
68
|
+
安装完成后,进入需要检查的代码仓库,重新启动 Codex、Claude Code、OpenCode 或 MiMo Code。第一次安装新 Skills/agents 目录时,已有会话可能无法立即发现它们。
|
|
69
|
+
|
|
70
|
+
```text
|
|
71
|
+
cd /path/to/your-project
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### 2. 用自然语言启动工作流
|
|
75
|
+
|
|
76
|
+
下面这条提示词适用于四种宿主:
|
|
77
|
+
|
|
78
|
+
```text
|
|
79
|
+
使用 DIC Workflow Kit 检查当前项目的设计—实现一致性。
|
|
80
|
+
先识别权威设计源和受保护路径,生成项目画像;
|
|
81
|
+
然后建立需求义务、检查实现缺口,只对有设计证据的问题规划修复;
|
|
82
|
+
运行项目声明的验证命令,最后输出一致性结论和残余风险。
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
如果只希望审计、不允许修改代码:
|
|
86
|
+
|
|
87
|
+
```text
|
|
88
|
+
使用 DIC Workflow Kit 审计当前项目,但不要修改任何文件。
|
|
89
|
+
输出设计义务、实现缺口、证据位置、建议修复和残余风险。
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
如果希望检查并修复:
|
|
93
|
+
|
|
94
|
+
```text
|
|
95
|
+
使用 DIC Workflow Kit 检查并修复当前项目。
|
|
96
|
+
仅修复能够追溯到权威设计源的缺口;每次修改后执行相关验证,
|
|
97
|
+
验证失败时停止扩散修改并记录阻塞原因。
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### 3. 检查一个 OpenSpec Change
|
|
101
|
+
|
|
102
|
+
明确给出 Change ID,可以减少搜索范围并避免把历史 Change 当成当前事实:
|
|
103
|
+
|
|
104
|
+
```text
|
|
105
|
+
使用 DIC Workflow Kit 检查 OpenSpec Change
|
|
106
|
+
2026-06-09-add-ts-local-skill-source。
|
|
107
|
+
以该 Change 的 proposal、design、tasks 和增量 specs 为入口,
|
|
108
|
+
同时核对当前稳定 specs、实现和测试,输出义务矩阵、实现缺口和验证结论。
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
如果 Change 已归档,工作流会将归档内容作为历史上下文,并以当前已经提升的稳定 Spec 为权威基线。
|
|
112
|
+
|
|
113
|
+
### 4. 显式调用核心 Skill
|
|
114
|
+
|
|
115
|
+
支持斜杠 Skill 的宿主可以先输入:
|
|
116
|
+
|
|
117
|
+
```text
|
|
118
|
+
/workflow-core
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
也可以在提示词中直接写出需要使用的 Skill:
|
|
122
|
+
|
|
123
|
+
```text
|
|
124
|
+
使用 workflow-core、workflow-intake、workflow-profile 和
|
|
125
|
+
knowledge-openspec 检查当前 OpenSpec 变更。
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
不需要手工逐个启动 9 个 Subagents。主 Agent 应按照证据依赖关系选择角色,并保证每个委派都有被下游消费的产物。
|
|
129
|
+
|
|
130
|
+
### 5. 查看结果
|
|
131
|
+
|
|
132
|
+
默认应重点查看:
|
|
133
|
+
|
|
134
|
+
| 文件 | 用途 |
|
|
135
|
+
| --- | --- |
|
|
136
|
+
| `reports/project-profile.json` | 项目结构、设计源、保护路径和验证命令 |
|
|
137
|
+
| `reports/contract-obligations.md` | 人可读的设计义务与来源 |
|
|
138
|
+
| `reports/implementation-gaps.md` | 已确认的实现缺口 |
|
|
139
|
+
| `reports/repair-plan.md` | 带来源锚点和停止条件的修复计划 |
|
|
140
|
+
| `reports/final-consistency-report.md` | 最终一致性结论与残余风险 |
|
|
141
|
+
| `reports/FINAL_RESULT.json` | 机器可读的最终状态 |
|
|
142
|
+
| `logs/trace/` | Intake、委派、修改和验证证据 |
|
|
143
|
+
|
|
144
|
+
最终状态只使用 `PASS`、`PARTIAL`、`BLOCKED` 或 `FAIL`。`PASS` 表示证据链和验证均满足要求,并不只是“测试刚好通过”。
|
|
145
|
+
|
|
146
|
+
### 常见问题
|
|
147
|
+
|
|
148
|
+
- **找不到 Skill/Subagent**:重启 Coding Agent,并使用 `--dry-run` 检查安装目标。
|
|
149
|
+
- **只想当前仓库使用**:Claude Code、OpenCode、MiMo Code 安装时增加 `--scope project`。
|
|
150
|
+
- **项目没有 OpenSpec**:仍可使用设计文档、API、Schema、README 或产品需求作为权威设计源。
|
|
151
|
+
- **缺少验证工具**:记录缺失的命令和环境原因,最终状态应为 `BLOCKED` 或 `PARTIAL`,不要伪造成功。
|
|
152
|
+
- **测试通过是否等于一致**:不等于。还必须确认需求、分支、状态迁移、副作用及集成流程符合设计。
|
|
153
|
+
|
|
154
|
+
### 双 npm 包发布
|
|
155
|
+
|
|
156
|
+
每次发布必须以相同版本、相同内容发布两个包名:
|
|
157
|
+
|
|
158
|
+
- `@tonyclaw/dic-workflow-kit`
|
|
159
|
+
- `dic-workflow-kit`
|
|
160
|
+
|
|
161
|
+
`package.json` 是唯一版本源。版本脚本会同步 Codex 插件清单、工作流脚本、全部 Skills、全部 Subagents 和中英文 README:
|
|
162
|
+
|
|
163
|
+
```text
|
|
164
|
+
npm run release:set-version -- 1.1.1
|
|
165
|
+
npm run release:plan
|
|
166
|
+
npm run release:dual:dry-run
|
|
167
|
+
npm run release:dual
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
npm 不支持两个包名之间的原子事务,因此发布协调器采用可收敛策略:先完成全部本地检查,再从临时 staging 分别发布两个包;某一侧已经存在相同版本时自动跳过,部分失败后重复运行只会补发缺失的一侧。
|
|
34
171
|
|
|
35
172
|
答辩时如何讲解项目特点并定位代码证据,请参考[《答辩引导:项目特点与代码证据》](docs/答辩引导-项目特点与代码证据.md)。
|
|
36
173
|
|
package/agents/code-repairer.md
CHANGED
package/agents/final-reviewer.md
CHANGED
package/agents/flow-auditor.md
CHANGED
package/agents/impl-inspector.md
CHANGED
package/agents/repair-planner.md
CHANGED
package/agents/spec-reader.md
CHANGED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{cp as A,mkdir as w,readFile as y,readdir as g,writeFile as E}from"node:fs/promises";import{homedir as f}from"node:os";import{dirname as x,join as n,resolve as m}from"node:path";import{fileURLToPath as N}from"node:url";var h="dic-workflow-kit",p=m(x(N(import.meta.url)),".."),j=new Map([["codex","codex"],["claude","claude-code"],["claude-code","claude-code"],["opencode","opencode"],["mimo","mimo-code"],["mimocode","mimo-code"],["mimo-code","mimo-code"]]),L=[".codex-plugin","adapters","agents","schemas","skills","INSTRUCTION.md"];function u(e,o,t){let r=e[o+1];if(!r)throw new Error(`${t} requires a value`);return r}function C(e){let o=e[0]??"help",t="codex",r="user",c,a,s=process.cwd(),d=!1;for(let i=1;i<e.length;i+=1){let l=e[i];if(l==="--host"){let k=u(e,i,l).toLowerCase();if(t=j.get(k),!t)throw new Error(`unsupported host: ${k}; expected codex, claude-code, opencode, or mimo-code`);i+=1}else if(l==="--scope"){if(r=u(e,i,l).toLowerCase(),!["user","project"].includes(r))throw new Error("--scope must be user or project");i+=1}else if(l==="--install-root")c=m(u(e,i,l)),i+=1;else if(l==="--project-root")s=m(u(e,i,l)),i+=1;else if(l==="--marketplace-root")a=m(u(e,i,l)),i+=1;else if(l==="--dry-run")d=!0;else throw new Error(`unknown argument: ${l}`)}if(t!=="codex"&&a)throw new Error("--marketplace-root is only valid for --host codex");if(t==="codex"&&r==="project")throw new Error("Codex plugin installation supports user scope only");return{command:o,host:t,scope:r,installRoot:c,marketplaceRoot:a,projectRoot:s,dryRun:d}}function O({host:e,scope:o,projectRoot:t}){if(o==="project"){let r={"claude-code":".claude",opencode:".opencode","mimo-code":".mimocode"}[e];return n(t,r)}return e==="claude-code"?n(f(),".claude"):e==="opencode"?n(process.env.XDG_CONFIG_HOME||n(f(),".config"),"opencode"):e==="mimo-code"?process.env.MIMOCODE_HOME?m(process.env.MIMOCODE_HOME):process.platform==="win32"&&process.env.LOCALAPPDATA?n(process.env.LOCALAPPDATA,"mimocode"):n(process.env.XDG_CONFIG_HOME||n(f(),".config"),"mimocode"):n(f(),".agents","plugins")}async function v(e){try{let o=JSON.parse(await y(e,"utf8"));if(!o||typeof o!="object"||Array.isArray(o))throw new Error("marketplace.json must contain an object");if(!Array.isArray(o.plugins))throw new Error("marketplace.json must contain a plugins array");return o}catch(o){if(o?.code!=="ENOENT")throw o;return{name:"personal",interface:{displayName:"Personal"},plugins:[]}}}function S(){return{name:h,source:{source:"local",path:`./plugins/${h}`},policy:{installation:"AVAILABLE",authentication:"ON_INSTALL"},category:"Developer Tools"}}async function T(e){let o=e.marketplaceRoot||e.installRoot||O(e),t=n(o,"marketplace.json"),r=n(o,"plugins",h),c=await v(t),a=S(),s=c.plugins.findIndex(d=>d?.name===h);if(s>=0?c.plugins[s]=a:c.plugins.push(a),!e.dryRun){await w(r,{recursive:!0});for(let d of L)await A(n(p,d),n(r,d),{recursive:!0,force:!0});await w(o,{recursive:!0}),await E(t,`${JSON.stringify(c,null,2)}
|
|
3
|
+
`,"utf8")}R({...e,root:r,skillsRoot:n(r,"skills"),agentsRoot:n(r,"agents"),extra:`MARKETPLACE: ${t}`})}function I(e,o){let t=e.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n/);if(!t)return e;let r=o==="claude-code"?new Set(["name","description","tools","disallowedTools","model","permissionMode","skills","memory","background","isolation"]):new Set(["name","description","mode","model","temperature","top_p","tools","permission","steps","hidden","color"]);return`---
|
|
4
|
+
${t[1].split(/\r?\n/).filter(a=>{let s=a.match(/^([A-Za-z][A-Za-z0-9_-]*):/)?.[1];return!s||r.has(s)}).join(`
|
|
5
|
+
`)}
|
|
6
|
+
---
|
|
7
|
+
${e.slice(t[0].length)}`}async function $(e){let o=e.installRoot||O(e),t=n(o,"skills"),r=n(o,"agents"),c=await g(n(p,"skills")),a=(await g(n(p,"agents"))).filter(s=>s.endsWith(".md"));if(!e.dryRun){await w(t,{recursive:!0});for(let s of c)await A(n(p,"skills",s),n(t,s),{recursive:!0,force:!0});await w(r,{recursive:!0});for(let s of a){let d=await y(n(p,"agents",s),"utf8");await E(n(r,s),I(d,e.host),"utf8")}}R({...e,root:o,skillsRoot:t,agentsRoot:r})}function R({host:e,scope:o,root:t,skillsRoot:r,agentsRoot:c,dryRun:a,extra:s}){process.stdout.write([a?"DRY_RUN: true":"INSTALLED: true",`HOST: ${e}`,`SCOPE: ${o}`,`ROOT: ${t}`,`SKILLS: ${r}`,`SUBAGENTS: ${c}`,s,""].filter(d=>d!==void 0).join(`
|
|
8
|
+
`))}async function P(e){e.host==="codex"?await T(e):await $(e)}function _(){process.stdout.write(["DIC Workflow Kit multi-host installer","","Usage:"," dic-workflow-kit install [--host <host>] [--scope user|project]"," [--project-root <path>] [--install-root <path>]"," [--marketplace-root <path>] [--dry-run]","","Hosts:"," codex (default), claude-code, opencode, mimo-code","","Examples:"," dic-workflow-kit install"," dic-workflow-kit install --host claude-code"," dic-workflow-kit install --host opencode --scope project"," dic-workflow-kit install --host mimo-code --scope project","","--marketplace-root is a backward-compatible Codex-only option.","--install-root overrides the resolved host configuration directory.",""].join(`
|
|
9
|
+
`))}try{let e=C(process.argv.slice(2));if(e.command==="install")await P(e);else if(e.command==="help"||e.command==="--help")_();else throw new Error(`unknown command: ${e.command}`)}catch(e){process.stderr.write(`ERROR: ${e.message}
|
|
10
|
+
`),process.exitCode=1}
|
package/package.json
CHANGED
|
@@ -1,31 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dic-workflow-kit",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Install DIC Workflow Kit Skills and Subagents
|
|
3
|
+
"version": "1.1.1",
|
|
4
|
+
"description": "Install DIC Workflow Kit Skills and Subagents for Codex, Claude Code, OpenCode, and MiMo Code.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"dic-workflow-kit": "
|
|
8
|
-
},
|
|
9
|
-
"scripts": {
|
|
10
|
-
"test": "python -m unittest discover -s tests -v && node --test tests/test_npm_installer.mjs",
|
|
11
|
-
"pack:check": "npm pack --dry-run"
|
|
7
|
+
"dic-workflow-kit": "dist/dic-workflow-kit.mjs"
|
|
12
8
|
},
|
|
13
9
|
"files": [
|
|
14
10
|
".codex-plugin/",
|
|
15
11
|
"adapters/",
|
|
16
12
|
"agents/",
|
|
17
|
-
"
|
|
13
|
+
"dist/",
|
|
18
14
|
"schemas/",
|
|
19
|
-
"scripts/",
|
|
20
|
-
"!scripts/__pycache__/",
|
|
21
|
-
"!**/*.pyc",
|
|
22
15
|
"skills/",
|
|
16
|
+
"CHANGELOG.md",
|
|
23
17
|
"INSTRUCTION.md",
|
|
24
|
-
"README.md"
|
|
25
|
-
"README.zh-CN.md"
|
|
18
|
+
"README.md"
|
|
26
19
|
],
|
|
27
20
|
"keywords": [
|
|
28
21
|
"codex",
|
|
22
|
+
"claude-code",
|
|
23
|
+
"opencode",
|
|
24
|
+
"mimo-code",
|
|
29
25
|
"plugin",
|
|
30
26
|
"skills",
|
|
31
27
|
"subagents",
|
|
@@ -39,6 +35,10 @@
|
|
|
39
35
|
"url": "git+ssh://git@gitcode.com/TonyClaw/DICWorkflowKit.git"
|
|
40
36
|
},
|
|
41
37
|
"homepage": "https://gitcode.com/TonyClaw/DICWorkflowKit",
|
|
38
|
+
"publishConfig": {
|
|
39
|
+
"access": "public",
|
|
40
|
+
"registry": "https://registry.npmjs.org/"
|
|
41
|
+
},
|
|
42
42
|
"engines": {
|
|
43
43
|
"node": ">=20"
|
|
44
44
|
}
|
package/bin/dic-workflow-kit.mjs
DELETED
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { cp, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
4
|
-
import { homedir } from "node:os";
|
|
5
|
-
import { dirname, join, resolve } from "node:path";
|
|
6
|
-
import { fileURLToPath } from "node:url";
|
|
7
|
-
|
|
8
|
-
const PLUGIN_NAME = "dic-workflow-kit";
|
|
9
|
-
const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
10
|
-
const PAYLOAD = [
|
|
11
|
-
".codex-plugin",
|
|
12
|
-
"adapters",
|
|
13
|
-
"agents",
|
|
14
|
-
"schemas",
|
|
15
|
-
"scripts",
|
|
16
|
-
"skills",
|
|
17
|
-
"INSTRUCTION.md",
|
|
18
|
-
"README.md",
|
|
19
|
-
"README.zh-CN.md",
|
|
20
|
-
];
|
|
21
|
-
|
|
22
|
-
function parseArgs(argv) {
|
|
23
|
-
const command = argv[0] ?? "help";
|
|
24
|
-
let marketplaceRoot = join(homedir(), ".agents", "plugins");
|
|
25
|
-
let dryRun = false;
|
|
26
|
-
for (let index = 1; index < argv.length; index += 1) {
|
|
27
|
-
const value = argv[index];
|
|
28
|
-
if (value === "--marketplace-root") {
|
|
29
|
-
const next = argv[index + 1];
|
|
30
|
-
if (!next) {
|
|
31
|
-
throw new Error("--marketplace-root requires a directory");
|
|
32
|
-
}
|
|
33
|
-
marketplaceRoot = resolve(next);
|
|
34
|
-
index += 1;
|
|
35
|
-
} else if (value === "--dry-run") {
|
|
36
|
-
dryRun = true;
|
|
37
|
-
} else {
|
|
38
|
-
throw new Error(`unknown argument: ${value}`);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
return { command, marketplaceRoot: resolve(marketplaceRoot), dryRun };
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
async function loadMarketplace(path) {
|
|
45
|
-
try {
|
|
46
|
-
const value = JSON.parse(await readFile(path, "utf8"));
|
|
47
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
48
|
-
throw new Error("marketplace.json must contain an object");
|
|
49
|
-
}
|
|
50
|
-
if (!Array.isArray(value.plugins)) {
|
|
51
|
-
throw new Error("marketplace.json must contain a plugins array");
|
|
52
|
-
}
|
|
53
|
-
return value;
|
|
54
|
-
} catch (error) {
|
|
55
|
-
if (error?.code !== "ENOENT") {
|
|
56
|
-
throw error;
|
|
57
|
-
}
|
|
58
|
-
return {
|
|
59
|
-
name: "personal",
|
|
60
|
-
interface: { displayName: "Personal" },
|
|
61
|
-
plugins: [],
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function pluginEntry() {
|
|
67
|
-
return {
|
|
68
|
-
name: PLUGIN_NAME,
|
|
69
|
-
source: {
|
|
70
|
-
source: "local",
|
|
71
|
-
path: `./plugins/${PLUGIN_NAME}`,
|
|
72
|
-
},
|
|
73
|
-
policy: {
|
|
74
|
-
installation: "AVAILABLE",
|
|
75
|
-
authentication: "ON_INSTALL",
|
|
76
|
-
},
|
|
77
|
-
category: "Developer Tools",
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
async function install({ marketplaceRoot, dryRun }) {
|
|
82
|
-
const marketplacePath = join(marketplaceRoot, "marketplace.json");
|
|
83
|
-
const pluginRoot = join(marketplaceRoot, "plugins", PLUGIN_NAME);
|
|
84
|
-
const marketplace = await loadMarketplace(marketplacePath);
|
|
85
|
-
const entry = pluginEntry();
|
|
86
|
-
const existingIndex = marketplace.plugins.findIndex(
|
|
87
|
-
(candidate) => candidate?.name === PLUGIN_NAME,
|
|
88
|
-
);
|
|
89
|
-
if (existingIndex >= 0) {
|
|
90
|
-
marketplace.plugins[existingIndex] = entry;
|
|
91
|
-
} else {
|
|
92
|
-
marketplace.plugins.push(entry);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (!dryRun) {
|
|
96
|
-
await mkdir(pluginRoot, { recursive: true });
|
|
97
|
-
for (const relativePath of PAYLOAD) {
|
|
98
|
-
await cp(join(PACKAGE_ROOT, relativePath), join(pluginRoot, relativePath), {
|
|
99
|
-
recursive: true,
|
|
100
|
-
force: true,
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
await mkdir(marketplaceRoot, { recursive: true });
|
|
104
|
-
await writeFile(
|
|
105
|
-
marketplacePath,
|
|
106
|
-
`${JSON.stringify(marketplace, null, 2)}\n`,
|
|
107
|
-
"utf8",
|
|
108
|
-
);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
process.stdout.write(
|
|
112
|
-
[
|
|
113
|
-
dryRun ? "DRY_RUN: true" : "INSTALLED: true",
|
|
114
|
-
`PLUGIN_ROOT: ${pluginRoot}`,
|
|
115
|
-
`MARKETPLACE: ${marketplacePath}`,
|
|
116
|
-
"SKILLS: skills/",
|
|
117
|
-
"SUBAGENTS: agents/",
|
|
118
|
-
"",
|
|
119
|
-
].join("\n"),
|
|
120
|
-
);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
function printHelp() {
|
|
124
|
-
process.stdout.write(
|
|
125
|
-
[
|
|
126
|
-
"DIC Workflow Kit Codex installer",
|
|
127
|
-
"",
|
|
128
|
-
"Usage:",
|
|
129
|
-
" dic-workflow-kit install [--marketplace-root <path>] [--dry-run]",
|
|
130
|
-
"",
|
|
131
|
-
"Default destination:",
|
|
132
|
-
" ~/.agents/plugins/plugins/dic-workflow-kit",
|
|
133
|
-
"",
|
|
134
|
-
].join("\n"),
|
|
135
|
-
);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
try {
|
|
139
|
-
const options = parseArgs(process.argv.slice(2));
|
|
140
|
-
if (options.command === "install") {
|
|
141
|
-
await install(options);
|
|
142
|
-
} else if (options.command === "help" || options.command === "--help") {
|
|
143
|
-
printHelp();
|
|
144
|
-
} else {
|
|
145
|
-
throw new Error(`unknown command: ${options.command}`);
|
|
146
|
-
}
|
|
147
|
-
} catch (error) {
|
|
148
|
-
process.stderr.write(`ERROR: ${error.message}\n`);
|
|
149
|
-
process.exitCode = 1;
|
|
150
|
-
}
|
package/scripts/dic_workflow.py
DELETED
|
@@ -1,507 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""Portable helper skeleton for DIC Workflow Kit.
|
|
3
|
-
|
|
4
|
-
This script intentionally uses only the Python standard library.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
from __future__ import annotations
|
|
8
|
-
|
|
9
|
-
import argparse
|
|
10
|
-
import hashlib
|
|
11
|
-
import json
|
|
12
|
-
import os
|
|
13
|
-
import re
|
|
14
|
-
import shlex
|
|
15
|
-
from pathlib import Path
|
|
16
|
-
from typing import Any
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
VERSION = "1.0.0"
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def write_text(path: Path, text: str) -> None:
|
|
23
|
-
path.parent.mkdir(parents=True, exist_ok=True)
|
|
24
|
-
path.write_text(text, encoding="utf-8", newline="\n")
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
def find_workflow_root(start: Path) -> Path:
|
|
28
|
-
current = start.resolve()
|
|
29
|
-
if current.is_file():
|
|
30
|
-
current = current.parent
|
|
31
|
-
for candidate in [current, *current.parents]:
|
|
32
|
-
if (candidate / "INSTRUCTION.md").exists():
|
|
33
|
-
return candidate
|
|
34
|
-
return current
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def rel(path: Path, root: Path) -> str:
|
|
38
|
-
try:
|
|
39
|
-
return path.resolve().relative_to(root.resolve()).as_posix()
|
|
40
|
-
except ValueError:
|
|
41
|
-
return path.as_posix()
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
def extract_validation_commands(text: str, source: str) -> list[dict[str, Any]]:
|
|
45
|
-
commands = []
|
|
46
|
-
seen = set()
|
|
47
|
-
for value in re.findall(r"`([^`\r\n]+)`", text):
|
|
48
|
-
if not re.match(
|
|
49
|
-
r"^(?:npm\s+(?:run|test|exec)\b|npx\s+|pnpm\s+|yarn\s+|"
|
|
50
|
-
r"openspec\s+validate\b|pytest(?:\s|$)|python\s+|mvn\s+)",
|
|
51
|
-
value,
|
|
52
|
-
):
|
|
53
|
-
continue
|
|
54
|
-
try:
|
|
55
|
-
command = shlex.split(value)
|
|
56
|
-
except ValueError:
|
|
57
|
-
continue
|
|
58
|
-
key = tuple(command)
|
|
59
|
-
if key in seen:
|
|
60
|
-
continue
|
|
61
|
-
seen.add(key)
|
|
62
|
-
commands.append(
|
|
63
|
-
{
|
|
64
|
-
"name": value,
|
|
65
|
-
"command": command,
|
|
66
|
-
"cwd": ".",
|
|
67
|
-
"source": source,
|
|
68
|
-
"kind": "suggested",
|
|
69
|
-
}
|
|
70
|
-
)
|
|
71
|
-
return commands
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
def obligation_id(capability_id: str, kind: str, title: str) -> str:
|
|
75
|
-
digest = hashlib.sha256(
|
|
76
|
-
f"{capability_id}\0{kind}\0{title}".encode("utf-8")
|
|
77
|
-
).hexdigest()[:12]
|
|
78
|
-
return f"obl-{digest}"
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
def extract_openspec_obligations(
|
|
82
|
-
spec_path: Path,
|
|
83
|
-
project_root: Path,
|
|
84
|
-
capability_id: str,
|
|
85
|
-
authority: str,
|
|
86
|
-
) -> list[dict[str, Any]]:
|
|
87
|
-
lines = spec_path.read_text(encoding="utf-8").splitlines()
|
|
88
|
-
obligations = []
|
|
89
|
-
current_requirement = None
|
|
90
|
-
index = 0
|
|
91
|
-
while index < len(lines):
|
|
92
|
-
line = lines[index]
|
|
93
|
-
requirement_match = re.match(r"^###\s+Requirement:\s*(.+?)\s*$", line)
|
|
94
|
-
scenario_match = re.match(r"^####\s+Scenario:\s*(.+?)\s*$", line)
|
|
95
|
-
if requirement_match:
|
|
96
|
-
title = requirement_match.group(1)
|
|
97
|
-
statement_lines = []
|
|
98
|
-
cursor = index + 1
|
|
99
|
-
while cursor < len(lines) and not re.match(
|
|
100
|
-
r"^#{3,4}\s+(?:Requirement|Scenario):", lines[cursor]
|
|
101
|
-
):
|
|
102
|
-
value = lines[cursor].strip()
|
|
103
|
-
if value and not value.startswith("#"):
|
|
104
|
-
statement_lines.append(value)
|
|
105
|
-
cursor += 1
|
|
106
|
-
current_requirement = obligation_id(capability_id, "requirement", title)
|
|
107
|
-
obligations.append(
|
|
108
|
-
{
|
|
109
|
-
"id": current_requirement,
|
|
110
|
-
"capabilityId": capability_id,
|
|
111
|
-
"kind": "requirement",
|
|
112
|
-
"title": title,
|
|
113
|
-
"statement": " ".join(statement_lines),
|
|
114
|
-
"source": rel(spec_path, project_root),
|
|
115
|
-
"line": index + 1,
|
|
116
|
-
"authority": authority,
|
|
117
|
-
"parentId": None,
|
|
118
|
-
"acceptanceClauses": [],
|
|
119
|
-
}
|
|
120
|
-
)
|
|
121
|
-
elif scenario_match:
|
|
122
|
-
title = scenario_match.group(1)
|
|
123
|
-
clauses = []
|
|
124
|
-
cursor = index + 1
|
|
125
|
-
while cursor < len(lines) and not re.match(
|
|
126
|
-
r"^#{3,4}\s+(?:Requirement|Scenario):", lines[cursor]
|
|
127
|
-
):
|
|
128
|
-
clause_match = re.match(
|
|
129
|
-
r"^\s*-\s+\*\*(GIVEN|WHEN|THEN|AND|BUT)\*\*\s*(.+?)\s*$",
|
|
130
|
-
lines[cursor],
|
|
131
|
-
flags=re.IGNORECASE,
|
|
132
|
-
)
|
|
133
|
-
if clause_match:
|
|
134
|
-
clauses.append(
|
|
135
|
-
{
|
|
136
|
-
"keyword": clause_match.group(1).upper(),
|
|
137
|
-
"text": clause_match.group(2),
|
|
138
|
-
"line": cursor + 1,
|
|
139
|
-
}
|
|
140
|
-
)
|
|
141
|
-
cursor += 1
|
|
142
|
-
obligations.append(
|
|
143
|
-
{
|
|
144
|
-
"id": obligation_id(capability_id, "scenario", title),
|
|
145
|
-
"capabilityId": capability_id,
|
|
146
|
-
"kind": "scenario",
|
|
147
|
-
"title": title,
|
|
148
|
-
"statement": " ".join(
|
|
149
|
-
f"{item['keyword']} {item['text']}" for item in clauses
|
|
150
|
-
),
|
|
151
|
-
"source": rel(spec_path, project_root),
|
|
152
|
-
"line": index + 1,
|
|
153
|
-
"authority": authority,
|
|
154
|
-
"parentId": current_requirement,
|
|
155
|
-
"acceptanceClauses": clauses,
|
|
156
|
-
}
|
|
157
|
-
)
|
|
158
|
-
index += 1
|
|
159
|
-
return obligations
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
def build_obligation_catalog(
|
|
163
|
-
project_root: Path, design_entry: dict[str, Any]
|
|
164
|
-
) -> dict[str, Any]:
|
|
165
|
-
use_baseline = design_entry["lifecycle"] == "archived" and design_entry["baselineSpecs"]
|
|
166
|
-
selected_specs = (
|
|
167
|
-
design_entry["baselineSpecs"] if use_baseline else design_entry["deltaSpecs"]
|
|
168
|
-
)
|
|
169
|
-
authority = "current-baseline" if use_baseline else "active-change"
|
|
170
|
-
obligations = []
|
|
171
|
-
for spec in selected_specs:
|
|
172
|
-
spec_path = project_root / spec
|
|
173
|
-
capability_id = spec_path.parent.name
|
|
174
|
-
obligations.extend(
|
|
175
|
-
extract_openspec_obligations(
|
|
176
|
-
spec_path, project_root, capability_id, authority
|
|
177
|
-
)
|
|
178
|
-
)
|
|
179
|
-
return {
|
|
180
|
-
"schema": "dic.contract-obligations.v1",
|
|
181
|
-
"version": VERSION,
|
|
182
|
-
"changeId": design_entry["changeId"],
|
|
183
|
-
"changeLifecycle": design_entry["lifecycle"],
|
|
184
|
-
"authorityPolicy": (
|
|
185
|
-
"Archived changes use promoted current baseline specs."
|
|
186
|
-
if use_baseline
|
|
187
|
-
else "Active changes use their delta specs."
|
|
188
|
-
),
|
|
189
|
-
"sources": selected_specs,
|
|
190
|
-
"counts": {
|
|
191
|
-
"requirements": sum(item["kind"] == "requirement" for item in obligations),
|
|
192
|
-
"scenarios": sum(item["kind"] == "scenario" for item in obligations),
|
|
193
|
-
"total": len(obligations),
|
|
194
|
-
},
|
|
195
|
-
"obligations": obligations,
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
def render_obligation_catalog(catalog: dict[str, Any]) -> str:
|
|
200
|
-
lines = [
|
|
201
|
-
"# Contract Obligations",
|
|
202
|
-
"",
|
|
203
|
-
f"- Change: `{catalog['changeId']}`",
|
|
204
|
-
f"- Lifecycle: {catalog['changeLifecycle']}",
|
|
205
|
-
f"- Authority: {catalog['authorityPolicy']}",
|
|
206
|
-
f"- Requirements: {catalog['counts']['requirements']}",
|
|
207
|
-
f"- Scenarios: {catalog['counts']['scenarios']}",
|
|
208
|
-
"",
|
|
209
|
-
]
|
|
210
|
-
for item in catalog["obligations"]:
|
|
211
|
-
lines.extend(
|
|
212
|
-
[
|
|
213
|
-
f"## {item['id']} — {item['title']}",
|
|
214
|
-
"",
|
|
215
|
-
f"- Kind: {item['kind']}",
|
|
216
|
-
f"- Source: `{item['source']}:{item['line']}`",
|
|
217
|
-
f"- Authority: {item['authority']}",
|
|
218
|
-
]
|
|
219
|
-
)
|
|
220
|
-
if item["parentId"]:
|
|
221
|
-
lines.append(f"- Parent: `{item['parentId']}`")
|
|
222
|
-
lines.extend(["", item["statement"] or "_No normative body extracted._", ""])
|
|
223
|
-
return "\n".join(lines)
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
def resolve_design_map(project_root: Path, capability_ids: list[str]) -> dict[str, Any] | None:
|
|
227
|
-
map_path = project_root / "openspec" / "designs" / "spec-to-design-map.md"
|
|
228
|
-
if not map_path.exists():
|
|
229
|
-
return None
|
|
230
|
-
text = map_path.read_text(encoding="utf-8")
|
|
231
|
-
for line in text.splitlines():
|
|
232
|
-
if not line.lstrip().startswith("|"):
|
|
233
|
-
continue
|
|
234
|
-
cells = [cell.strip() for cell in line.strip().strip("|").split("|")]
|
|
235
|
-
if len(cells) < 5:
|
|
236
|
-
continue
|
|
237
|
-
capability = cells[0].strip("` ")
|
|
238
|
-
if capability not in capability_ids:
|
|
239
|
-
continue
|
|
240
|
-
linked_paths = []
|
|
241
|
-
for cell in cells[1:4]:
|
|
242
|
-
linked_paths.extend(re.findall(r"`(openspec/[^`]+\.md)`", cell))
|
|
243
|
-
linked_paths = list(dict.fromkeys(linked_paths))
|
|
244
|
-
return {
|
|
245
|
-
"source": rel(map_path, project_root),
|
|
246
|
-
"capabilityId": capability,
|
|
247
|
-
"architectureDesigns": [
|
|
248
|
-
path for path in linked_paths if "/designs/architecture/" in path
|
|
249
|
-
],
|
|
250
|
-
"moduleDesigns": [
|
|
251
|
-
path for path in linked_paths if "/designs/modules/" in path
|
|
252
|
-
],
|
|
253
|
-
"adrs": [path for path in linked_paths if "/designs/adr/" in path],
|
|
254
|
-
"validationSummary": cells[4],
|
|
255
|
-
"validationCommands": extract_validation_commands(cells[4], rel(map_path, project_root)),
|
|
256
|
-
}
|
|
257
|
-
return None
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
def resolve_openspec_change(project_root: Path, change_id: str) -> dict[str, Any]:
|
|
261
|
-
candidates = [
|
|
262
|
-
("active", project_root / "openspec" / "changes" / change_id),
|
|
263
|
-
("archived", project_root / "openspec" / "changes" / "archive" / change_id),
|
|
264
|
-
]
|
|
265
|
-
for lifecycle, change_root in candidates:
|
|
266
|
-
if not change_root.is_dir():
|
|
267
|
-
continue
|
|
268
|
-
delta_specs = sorted(change_root.glob("specs/**/spec.md"))
|
|
269
|
-
capability_ids = [
|
|
270
|
-
path.relative_to(change_root / "specs").parts[0] for path in delta_specs
|
|
271
|
-
]
|
|
272
|
-
baseline_specs = []
|
|
273
|
-
for delta_spec in delta_specs:
|
|
274
|
-
relative_spec = delta_spec.relative_to(change_root / "specs")
|
|
275
|
-
baseline = project_root / "openspec" / "specs" / relative_spec
|
|
276
|
-
if baseline.exists():
|
|
277
|
-
baseline_specs.append(baseline)
|
|
278
|
-
documents = [
|
|
279
|
-
path
|
|
280
|
-
for path in [
|
|
281
|
-
change_root / "proposal.md",
|
|
282
|
-
change_root / "design.md",
|
|
283
|
-
change_root / "tasks.md",
|
|
284
|
-
]
|
|
285
|
-
if path.exists()
|
|
286
|
-
]
|
|
287
|
-
result = {
|
|
288
|
-
"type": "openspec-change",
|
|
289
|
-
"changeId": change_id,
|
|
290
|
-
"lifecycle": lifecycle,
|
|
291
|
-
"changeRoot": rel(change_root, project_root),
|
|
292
|
-
"documents": [rel(path, project_root) for path in documents],
|
|
293
|
-
"deltaSpecs": [rel(path, project_root) for path in delta_specs],
|
|
294
|
-
"baselineSpecs": [rel(path, project_root) for path in baseline_specs],
|
|
295
|
-
}
|
|
296
|
-
design_map = resolve_design_map(project_root, capability_ids)
|
|
297
|
-
if design_map:
|
|
298
|
-
result["designMap"] = design_map
|
|
299
|
-
return result
|
|
300
|
-
raise SystemExit(
|
|
301
|
-
f"OpenSpec change not found: {change_id} "
|
|
302
|
-
f"(checked active and archived changes under {project_root})"
|
|
303
|
-
)
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
def detect_project(root: Path, change_id: str = "") -> dict[str, Any]:
|
|
307
|
-
project_root = root
|
|
308
|
-
openspec = list(project_root.glob("**/openspec/specs/**/spec.md")) + list(
|
|
309
|
-
project_root.glob("**/openspec/changes/**/proposal.md")
|
|
310
|
-
)
|
|
311
|
-
design_entry = resolve_openspec_change(project_root, change_id) if change_id else None
|
|
312
|
-
if design_entry:
|
|
313
|
-
selected = (
|
|
314
|
-
design_entry["documents"]
|
|
315
|
-
+ design_entry["deltaSpecs"]
|
|
316
|
-
+ design_entry["baselineSpecs"]
|
|
317
|
-
)
|
|
318
|
-
design_map = design_entry.get("designMap")
|
|
319
|
-
if design_map:
|
|
320
|
-
selected += [
|
|
321
|
-
design_map["source"],
|
|
322
|
-
*design_map["architectureDesigns"],
|
|
323
|
-
*design_map["moduleDesigns"],
|
|
324
|
-
*design_map["adrs"],
|
|
325
|
-
]
|
|
326
|
-
design_sources = [project_root / path for path in selected]
|
|
327
|
-
else:
|
|
328
|
-
design_sources = []
|
|
329
|
-
for pattern in ["README*", "docs/**/*.md", "design/**/*.md", "spec/**/*.md", "api/**/*.md", "schemas/**/*"]:
|
|
330
|
-
design_sources.extend(project_root.glob(pattern))
|
|
331
|
-
for path in openspec:
|
|
332
|
-
if path not in design_sources:
|
|
333
|
-
design_sources.append(path)
|
|
334
|
-
|
|
335
|
-
impl_roots = [p for p in [project_root / "src", project_root / "app", project_root / "code"] if p.exists()]
|
|
336
|
-
test_roots = [p for p in [project_root / "test", project_root / "tests", project_root / "test-cases"] if p.exists()]
|
|
337
|
-
project_types = []
|
|
338
|
-
adapters = ["generic"]
|
|
339
|
-
if openspec:
|
|
340
|
-
project_types.append("openspec")
|
|
341
|
-
adapters.append("openspec")
|
|
342
|
-
if (project_root / "pom.xml").exists() or list(project_root.glob("**/pom.xml")):
|
|
343
|
-
project_types.append("maven")
|
|
344
|
-
if (project_root / "package.json").exists() or list(project_root.glob("**/package.json")):
|
|
345
|
-
project_types.append("node")
|
|
346
|
-
|
|
347
|
-
validation_commands = []
|
|
348
|
-
if design_entry:
|
|
349
|
-
for document in design_entry["documents"]:
|
|
350
|
-
path = project_root / document
|
|
351
|
-
validation_commands.extend(
|
|
352
|
-
extract_validation_commands(path.read_text(encoding="utf-8"), document)
|
|
353
|
-
)
|
|
354
|
-
if design_entry.get("designMap"):
|
|
355
|
-
validation_commands.extend(design_entry["designMap"]["validationCommands"])
|
|
356
|
-
if design_entry["lifecycle"] == "archived":
|
|
357
|
-
validation_commands = [
|
|
358
|
-
item
|
|
359
|
-
for item in validation_commands
|
|
360
|
-
if not (
|
|
361
|
-
item["command"][:2] == ["openspec", "validate"]
|
|
362
|
-
and "--all" not in item["command"]
|
|
363
|
-
)
|
|
364
|
-
]
|
|
365
|
-
unique_commands = {}
|
|
366
|
-
for item in validation_commands:
|
|
367
|
-
unique_commands.setdefault(tuple(item["command"]), item)
|
|
368
|
-
validation_commands = list(unique_commands.values())
|
|
369
|
-
|
|
370
|
-
profile = {
|
|
371
|
-
"schema": "dic.project-profile.v1",
|
|
372
|
-
"version": VERSION,
|
|
373
|
-
"projectRoot": project_root.as_posix(),
|
|
374
|
-
"projectTypes": sorted(set(project_types)) or ["generic"],
|
|
375
|
-
"adapters": adapters,
|
|
376
|
-
"designSources": [rel(p, project_root) for p in sorted(set(design_sources))],
|
|
377
|
-
"implementationRoots": [rel(p, project_root) for p in impl_roots],
|
|
378
|
-
"testRoots": [rel(p, project_root) for p in test_roots],
|
|
379
|
-
"protectedPaths": [rel(p, project_root) for p in sorted(set(design_sources))],
|
|
380
|
-
"validationCommands": validation_commands,
|
|
381
|
-
"riskFamilies": ["requirements", "api-contract", "flow-completeness", "validation"],
|
|
382
|
-
"outputContract": {
|
|
383
|
-
"finalResultJson": "reports/FINAL_RESULT.json",
|
|
384
|
-
"finalOutputMd": "result/output.md",
|
|
385
|
-
"contractObligationsJson": "reports/contract-obligations.json",
|
|
386
|
-
"contractObligationsMd": "reports/contract-obligations.md",
|
|
387
|
-
"traceDir": "logs/trace"
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
if design_entry:
|
|
391
|
-
profile["designEntry"] = design_entry
|
|
392
|
-
profile["deltaSpecs"] = design_entry["deltaSpecs"]
|
|
393
|
-
profile["baselineSpecs"] = design_entry["baselineSpecs"]
|
|
394
|
-
return profile
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
def command_init(args: argparse.Namespace) -> int:
|
|
398
|
-
root = Path(args.root or os.getcwd()).resolve()
|
|
399
|
-
output_root = Path(args.output_root).resolve() if args.output_root else root
|
|
400
|
-
for directory in ["reports", "result", "logs/trace"]:
|
|
401
|
-
(output_root / directory).mkdir(parents=True, exist_ok=True)
|
|
402
|
-
profile = detect_project(root, args.change)
|
|
403
|
-
profile_path = output_root / "reports" / "project-profile.json"
|
|
404
|
-
write_text(profile_path, json.dumps(profile, indent=2, ensure_ascii=False))
|
|
405
|
-
if profile.get("designEntry"):
|
|
406
|
-
catalog = build_obligation_catalog(root, profile["designEntry"])
|
|
407
|
-
write_text(
|
|
408
|
-
output_root / "reports" / "contract-obligations.json",
|
|
409
|
-
json.dumps(catalog, indent=2, ensure_ascii=False),
|
|
410
|
-
)
|
|
411
|
-
write_text(
|
|
412
|
-
output_root / "reports" / "contract-obligations.md",
|
|
413
|
-
render_obligation_catalog(catalog),
|
|
414
|
-
)
|
|
415
|
-
output = [
|
|
416
|
-
"# DIC Workflow Init",
|
|
417
|
-
"",
|
|
418
|
-
f"- Version: {VERSION}",
|
|
419
|
-
f"- Project root: `{root.as_posix()}`",
|
|
420
|
-
f"- Output root: `{output_root.as_posix()}`",
|
|
421
|
-
f"- Project profile: `{profile_path.as_posix()}`",
|
|
422
|
-
f"- Adapters: {', '.join(profile['adapters'])}",
|
|
423
|
-
]
|
|
424
|
-
if profile.get("designEntry"):
|
|
425
|
-
entry = profile["designEntry"]
|
|
426
|
-
output.extend(
|
|
427
|
-
[
|
|
428
|
-
f"- Design entry: OpenSpec Change `{entry['changeId']}`",
|
|
429
|
-
f"- Change lifecycle: {entry['lifecycle']}",
|
|
430
|
-
]
|
|
431
|
-
)
|
|
432
|
-
output.append("")
|
|
433
|
-
write_text(output_root / "logs" / "trace" / "init.md", "\n".join(output))
|
|
434
|
-
print(f"DIC_WORKFLOW_VERSION: {VERSION}")
|
|
435
|
-
print(f"PROJECT_ROOT: {root}")
|
|
436
|
-
print(f"OUTPUT_ROOT: {output_root}")
|
|
437
|
-
print(f"PROJECT_PROFILE: {profile_path}")
|
|
438
|
-
return 0
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
def command_finalize(args: argparse.Namespace) -> int:
|
|
442
|
-
root = find_workflow_root(Path(args.root or os.getcwd()))
|
|
443
|
-
status = args.status.upper()
|
|
444
|
-
if status not in {"PASS", "PARTIAL", "BLOCKED", "FAIL"}:
|
|
445
|
-
raise SystemExit(f"invalid status: {args.status}")
|
|
446
|
-
result = {
|
|
447
|
-
"schema": "dic.final-result.v1",
|
|
448
|
-
"status": status,
|
|
449
|
-
"final": True,
|
|
450
|
-
"summary": args.summary,
|
|
451
|
-
"artifacts": [
|
|
452
|
-
"reports/project-profile.json",
|
|
453
|
-
"reports/contract-obligations.json",
|
|
454
|
-
"reports/contract-obligations.md",
|
|
455
|
-
"reports/final-consistency-report.md",
|
|
456
|
-
"reports/FINAL_RESULT.json",
|
|
457
|
-
"result/output.md",
|
|
458
|
-
"logs/trace/"
|
|
459
|
-
],
|
|
460
|
-
"validation": [],
|
|
461
|
-
"changedFiles": [],
|
|
462
|
-
"residualRisks": []
|
|
463
|
-
}
|
|
464
|
-
write_text(root / "reports" / "FINAL_RESULT.json", json.dumps(result, indent=2, ensure_ascii=False))
|
|
465
|
-
lines = [
|
|
466
|
-
"# DIC Final Result",
|
|
467
|
-
"",
|
|
468
|
-
f"FINAL_EXECUTION_RESULT: {status}",
|
|
469
|
-
"FINAL_RESULT_FINAL: true",
|
|
470
|
-
"",
|
|
471
|
-
args.summary,
|
|
472
|
-
"",
|
|
473
|
-
"## Artifacts",
|
|
474
|
-
"",
|
|
475
|
-
"- `reports/FINAL_RESULT.json`",
|
|
476
|
-
"- `reports/contract-obligations.json`",
|
|
477
|
-
"- `reports/contract-obligations.md`",
|
|
478
|
-
"- `result/output.md`",
|
|
479
|
-
"- `logs/trace/`",
|
|
480
|
-
"",
|
|
481
|
-
]
|
|
482
|
-
write_text(root / "result" / "output.md", "\n".join(lines))
|
|
483
|
-
print(f"FINAL_EXECUTION_RESULT: {status}")
|
|
484
|
-
return 0
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
def main() -> int:
|
|
488
|
-
parser = argparse.ArgumentParser(prog="dic_workflow")
|
|
489
|
-
parser.add_argument("--root", default="")
|
|
490
|
-
parser.add_argument("--output-root", default="")
|
|
491
|
-
sub = parser.add_subparsers(dest="command", required=True)
|
|
492
|
-
|
|
493
|
-
p = sub.add_parser("init")
|
|
494
|
-
p.add_argument("--change", default="")
|
|
495
|
-
p.set_defaults(func=command_init)
|
|
496
|
-
|
|
497
|
-
p = sub.add_parser("finalize")
|
|
498
|
-
p.add_argument("--status", default="PARTIAL")
|
|
499
|
-
p.add_argument("--summary", default="DIC workflow finished with recorded evidence.")
|
|
500
|
-
p.set_defaults(func=command_finalize)
|
|
501
|
-
|
|
502
|
-
args = parser.parse_args()
|
|
503
|
-
return int(args.func(args))
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
if __name__ == "__main__":
|
|
507
|
-
raise SystemExit(main())
|