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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dic-workflow-kit",
3
- "version": "1.0.0",
3
+ "version": "1.1.1",
4
4
  "description": "Design-implementation consistency workflow with OpenSpec obligations, BDD knowledge, Skills, and Subagents.",
5
5
  "author": {
6
6
  "name": "TonyClaw"
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.0.0`
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 in Codex with npm
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
- The npm package contains all seven Skills and all nine Subagents. Install it into the default personal Codex plugin marketplace with:
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
- Or install the CLI globally:
25
+ Select another host explicitly:
22
26
 
23
27
  ```text
24
- npm install --global dic-workflow-kit
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
- Before the package is published to the npm registry, install the same CLI directly from the repository:
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
- The installer writes the plugin to `~/.agents/plugins/plugins/dic-workflow-kit` and registers it in `~/.agents/plugins/marketplace.json`. It preserves unrelated entries and replaces only the entry with the same plugin name.
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
- Use `npx dic-workflow-kit install --dry-run` to preview the destination, or `--marketplace-root /path/to/.agents/plugins` for an isolated environment. Start a new Codex task after installation so its Skills and Subagents are discovered.
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
- To scope intake to one OpenSpec Change, pass its identifier explicitly. Active changes are preferred; if no active match exists, the archived location is resolved and recorded in the profile. Keep evidence outside the target repository with `--output-root`:
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.0.0`
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 安装到 Codex
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
- npm 包完整包含 7 个 Skills 和 9 个 Subagents:
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
- npm registry 正式发布前,可以直接从仓库安装同一个 CLI:
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
- 默认安装到 `~/.agents/plugins/plugins/dic-workflow-kit`,并在 `~/.agents/plugins/marketplace.json` 中注册。安装器会保留其他 marketplace 条目,只更新同名插件。使用 `--dry-run` 预演,或使用 `--marketplace-root` 指定隔离目录。安装后请新建 Codex 任务,以重新发现 Skills Subagents。
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
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: code-repairer
3
- version: 1.0.0
3
+ version: 1.1.1
4
4
  description: Implement approved repair slices while preserving contracts, minimizing blast radius, and avoiding unrelated refactors.
5
5
  mode: subagent
6
6
  ---
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: contract-oracle
3
- version: 1.0.0
3
+ version: 1.1.1
4
4
  description: Convert source requirements, OpenSpec scenarios, and feedback hypotheses into source-confirmed implementation obligations.
5
5
  mode: subagent
6
6
  ---
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: final-reviewer
3
- version: 1.0.0
3
+ version: 1.1.1
4
4
  description: Perform the final consistency gate across source coverage, protected paths, validation evidence, residual risks, changed files, and output artifacts.
5
5
  mode: subagent
6
6
  ---
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: flow-auditor
3
- version: 1.0.0
3
+ version: 1.1.1
4
4
  description: Audit business and use-case flow completeness, including lifecycle, branches, state transitions, side effects, integration paths, and failure handling.
5
5
  mode: subagent
6
6
  ---
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: impl-inspector
3
- version: 1.0.0
3
+ version: 1.1.1
4
4
  description: Inspect implementation against source-confirmed obligations without editing files.
5
5
  mode: subagent
6
6
  ---
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: profile-builder
3
- version: 1.0.0
3
+ version: 1.1.1
4
4
  description: Build a normalized project profile from repository evidence so downstream agents stop guessing paths, tools, and source hierarchy.
5
5
  mode: subagent
6
6
  ---
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: repair-planner
3
- version: 1.0.0
3
+ version: 1.1.1
4
4
  description: Create small, source-backed repair slices with target files, validation commands, stop conditions, and fallback plans.
5
5
  mode: subagent
6
6
  ---
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: spec-reader
3
- version: 1.0.0
3
+ version: 1.1.1
4
4
  description: Read authoritative design, OpenSpec, API, schema, README, and configuration sources and produce a source-backed requirement brief.
5
5
  mode: subagent
6
6
  ---
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: validation-runner
3
- version: 1.0.0
3
+ version: 1.1.1
4
4
  description: Discover, run, and summarize project validation commands using evaluator-readable evidence.
5
5
  mode: subagent
6
6
  ---
@@ -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.0.0",
4
- "description": "Install DIC Workflow Kit Skills and Subagents as a Codex plugin.",
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": "bin/dic-workflow-kit.mjs"
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
- "bin/",
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
  }
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: knowledge-bdd
3
- version: 1.0.0
3
+ version: 1.1.1
4
4
  description: Derive Given-When-Then acceptance scenarios from confirmed design rules and flow contracts.
5
5
  ---
6
6
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: knowledge-openspec
3
- version: 1.0.0
3
+ version: 1.1.1
4
4
  description: OpenSpec knowledge pack for proposals, specs, scenarios, tasks, changes, archived capabilities, validation, and design-to-implementation mapping.
5
5
  ---
6
6
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: knowledge-repair-distill
3
- version: 1.0.0
3
+ version: 1.1.1
4
4
  description: Generic repair convergence guidance, gap taxonomy, model steering hints, and validation gates for design-implementation consistency work.
5
5
  ---
6
6
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: workflow-core
3
- version: 1.0.0
3
+ version: 1.1.1
4
4
  description: Core Design-Implementation Consistency workflow contract, evidence model, handoff rules, and final output expectations.
5
5
  ---
6
6
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: workflow-intake
3
- version: 1.0.0
3
+ version: 1.1.1
4
4
  description: Discover authoritative design sources, implementation roots, test roots, protected paths, validation commands, and adapter hints.
5
5
  ---
6
6
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: workflow-profile
3
- version: 1.0.0
3
+ version: 1.1.1
4
4
  description: Build a normalized project profile for design-implementation consistency work without hardcoding a repository or technology stack.
5
5
  ---
6
6
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: workflow-repair
3
- version: 1.0.0
3
+ version: 1.1.1
4
4
  description: Guide source-backed implementation inspection, repair planning, code repair, validation loops, and final consistency evidence.
5
5
  ---
6
6
 
@@ -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
- }
@@ -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())