@xenonbyte/da-vinci-workflow 0.2.5 → 0.2.6
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/CHANGELOG.md +16 -0
- package/README.md +15 -9
- package/README.zh-CN.md +16 -9
- package/docs/dv-command-reference.md +18 -2
- package/docs/execution-chain-migration.md +14 -3
- package/docs/maintainer-bootstrap.md +102 -0
- package/docs/pencil-rendering-workflow.md +1 -1
- package/docs/skill-usage.md +31 -0
- package/docs/workflow-overview.md +40 -5
- package/docs/zh-CN/dv-command-reference.md +16 -2
- package/docs/zh-CN/maintainer-bootstrap.md +101 -0
- package/docs/zh-CN/pencil-rendering-workflow.md +1 -1
- package/docs/zh-CN/skill-usage.md +30 -0
- package/docs/zh-CN/workflow-overview.md +38 -5
- package/lib/audit.js +19 -0
- package/lib/cli/helpers.js +63 -2
- package/lib/cli.js +98 -0
- package/lib/gate-utils.js +56 -0
- package/lib/install.js +134 -6
- package/lib/lint-bindings.js +41 -28
- package/lib/lint-spec.js +403 -109
- package/lib/lint-tasks.js +571 -21
- package/lib/maintainer-readiness.js +317 -0
- package/lib/planning-parsers.js +190 -1
- package/lib/planning-quality-utils.js +81 -0
- package/lib/planning-signal-freshness.js +205 -0
- package/lib/scope-check.js +751 -82
- package/lib/sidecars.js +396 -1
- package/lib/task-review.js +2 -1
- package/lib/utils.js +15 -0
- package/lib/workflow-persisted-state.js +52 -32
- package/lib/workflow-state.js +1187 -249
- package/package.json +1 -1
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
- screenshot review
|
|
13
13
|
- runtime gate 和 filesystem audit
|
|
14
14
|
|
|
15
|
-
如果你想看 `design`、`tasks`、`build`、`verify` 这些 `dv:` 路由之间怎么衔接,以及 `verify` 出问题后退回哪一层,请看 [dv-command-reference.md](
|
|
15
|
+
如果你想看 `design`、`tasks`、`build`、`verify` 这些 `dv:` 路由之间怎么衔接,以及 `verify` 出问题后退回哪一层,请看 [dv-command-reference.md](./dv-command-reference.md)。
|
|
16
16
|
|
|
17
17
|
## 渲染真相模型
|
|
18
18
|
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
这份文档面向真正操作 Da Vinci 的人,而不是底层命令参考。
|
|
4
4
|
|
|
5
|
+
如果你是在维护 Da Vinci 仓库本身,请先看
|
|
6
|
+
[maintainer-bootstrap.md](./maintainer-bootstrap.md),而不是这份偏 operator 的流程文档。
|
|
7
|
+
|
|
5
8
|
重点讲四件事:
|
|
6
9
|
|
|
7
10
|
- 第一次怎么进入 Da Vinci 工作流
|
|
@@ -18,6 +21,7 @@ Da Vinci 应该根据工件真相恢复,而不是根据聊天记忆恢复。
|
|
|
18
21
|
- `spec.md` 以及相关需求工件是行为真相
|
|
19
22
|
- 项目内 `.pen` 是设计真相
|
|
20
23
|
- `tasks.md`、`verification.md` 和 execution signals 负责说明哪些东西已经实现、哪些还没实现
|
|
24
|
+
- workflow memory 是 `.da-vinci/state/` 下的工件级本地状态,不是跨会话聊天记忆
|
|
21
25
|
|
|
22
26
|
## 第一次进入时怎么选入口
|
|
23
27
|
|
|
@@ -79,14 +83,20 @@ $da-vinci use continue for <existing workflow state>
|
|
|
79
83
|
|
|
80
84
|
- `da-vinci workflow-status`
|
|
81
85
|
- 先确认当前阶段和 blocker
|
|
86
|
+
- task-group focus 会综合 checklist 进度和最新 `task-execution` / `task-review` 证据
|
|
82
87
|
- `da-vinci next-step`
|
|
83
88
|
- 再确认下一步到底应该是 `design`、`tasks` 还是 `build`
|
|
89
|
+
- 当你需要 runtime-aware 的首要聚焦点时,优先看它而不是只看下一条未勾选 checklist
|
|
84
90
|
- `da-vinci lint-spec`
|
|
85
91
|
- 当运行时 spec 质量还不够确定时使用
|
|
92
|
+
- `--json` 中重点看 `gates.principleInheritance`、`gates.clarify`、`gates.scenarioQuality`
|
|
93
|
+
- 当 bounded 元数据完整时,把 `gates.clarify.bounded` 视为“可见上下文”;bounded-only clarify 默认不阻断
|
|
86
94
|
- `da-vinci scope-check`
|
|
87
95
|
- 当规划工件之间页面/状态传播关系还不清楚时使用
|
|
96
|
+
- `--json` 中重点看 `gates.analyze` 的跨工件一致性阻断项
|
|
88
97
|
- `da-vinci lint-tasks`
|
|
89
98
|
- 校验 task groups 是否包含 discipline markers、明确文件落点、执行意图与 verification 命令
|
|
99
|
+
- `--json` 中重点看 `gates.taskCheckpoint` 的 anchor/checkpoint 就绪度
|
|
90
100
|
- `da-vinci lint-bindings`
|
|
91
101
|
- 当 `pencil-design.md` 与 `pencil-bindings.md` 同时存在时运行,确保实现落点证据可解析
|
|
92
102
|
- `da-vinci worktree-preflight`
|
|
@@ -112,6 +122,13 @@ da-vinci lint-tasks
|
|
|
112
122
|
|
|
113
123
|
同一 task group 只有在 `spec` review 已是 `PASS` 时,`quality` review 才允许通过。
|
|
114
124
|
|
|
125
|
+
兼容性说明:
|
|
126
|
+
|
|
127
|
+
- `workflow-status --json` 仍保留扁平 task-group 字段:`status`、`completion`、`nextAction`、`resumeCursor`
|
|
128
|
+
- `completion` 仍表示 checklist 推导的进度百分比
|
|
129
|
+
- 当 implementer/review 证据更新更晚时,`status`、`nextAction`、`resumeCursor` 可能切换到 runtime-aware 值
|
|
130
|
+
- 当规划 gate 阻断晋级时,`workflow-status --json` 与 `next-step --json` 会输出 `blockingGate`
|
|
131
|
+
|
|
115
132
|
## 中途退出后,下次怎么恢复
|
|
116
133
|
|
|
117
134
|
最稳妥的恢复顺序是:
|
|
@@ -124,12 +141,25 @@ da-vinci lint-tasks
|
|
|
124
141
|
6. 在终态前,跑 `da-vinci verify-bindings` 和 `da-vinci verify-coverage`
|
|
125
142
|
7. 如果要做 completion 表述,确认 `verify-coverage` 的 freshness 仍然有效,并运行 `da-vinci audit --mode completion --change <id> <project-path>`
|
|
126
143
|
|
|
144
|
+
严格性控制:
|
|
145
|
+
|
|
146
|
+
- lint 命令严格性:使用 `--strict`
|
|
147
|
+
- workflow 晋级严格性:使用 `DA_VINCI_DISCIPLINE_STRICT_PROMOTION`
|
|
148
|
+
- clarify/analyze/task-checkpoint 不新增独立 strict 开关
|
|
149
|
+
- 在 integrity audit 中,即使 `lint-spec` signal 顶层是 `PASS`,bounded-only clarify 上下文也应保持可见
|
|
150
|
+
|
|
127
151
|
恢复时应该遵循工件,而不是旧聊天上下文:
|
|
128
152
|
|
|
129
153
|
- 如果设计工件已经有了,但 `tasks.md` 还没有,下一步应该回到 `tasks`
|
|
130
154
|
- 如果 `tasks.md` 已存在,但设计 gate 还没过,不要直接进 `build`
|
|
131
155
|
- 只有实现就绪度已经明确时,才把 `build` 当主恢复路径
|
|
132
156
|
|
|
157
|
+
当 task 执行或评审证据与 checklist 进度冲突时:
|
|
158
|
+
|
|
159
|
+
- 以 `workflow-status` / `next-step` 的路由建议为准
|
|
160
|
+
- 把 task-group 的 `nextAction` 视为有效续跑焦点
|
|
161
|
+
- 把 checklist `completion` 视为进度可见性,不把它当成 blocker/review debt 已清空的证明
|
|
162
|
+
|
|
133
163
|
## `change-id` 到底是什么
|
|
134
164
|
|
|
135
165
|
`change-id` 就是 `.da-vinci/changes/<change-id>/` 这个目录名。
|
|
@@ -12,11 +12,12 @@
|
|
|
12
12
|
- 页面到设计的绑定
|
|
13
13
|
- 实现与验证
|
|
14
14
|
|
|
15
|
-
如果你想看 Pencil 渲染、持久化、门禁和审计的专门说明,请看 [pencil-rendering-workflow.md](
|
|
16
|
-
如果你想看逐个 `dv:` 命令的职责、下一步推荐和 `verify` 回退规则,请看 [dv-command-reference.md](
|
|
17
|
-
如果你想看约束文件总览(哪些字段是硬门禁、哪些是指导项),请看 [constraint-files.md](
|
|
18
|
-
如果你想看 sidecar、enforcement、persisted-state 回退和 scaffold 约束,请看 [execution-chain-migration.md](
|
|
19
|
-
如果你想看更偏操作手册的说明,比如第一次怎么进、暂停后怎么续跑、TUI 怎么用,请看 [skill-usage.md](
|
|
15
|
+
如果你想看 Pencil 渲染、持久化、门禁和审计的专门说明,请看 [pencil-rendering-workflow.md](./pencil-rendering-workflow.md)。
|
|
16
|
+
如果你想看逐个 `dv:` 命令的职责、下一步推荐和 `verify` 回退规则,请看 [dv-command-reference.md](./dv-command-reference.md)。
|
|
17
|
+
如果你想看约束文件总览(哪些字段是硬门禁、哪些是指导项),请看 [constraint-files.md](./constraint-files.md)。
|
|
18
|
+
如果你想看 sidecar、enforcement、persisted-state 回退和 scaffold 约束,请看 [execution-chain-migration.md](./execution-chain-migration.md)。
|
|
19
|
+
如果你想看更偏操作手册的说明,比如第一次怎么进、暂停后怎么续跑、TUI 怎么用,请看 [skill-usage.md](./skill-usage.md)。
|
|
20
|
+
如果你是在维护仓库本身(不是下游项目操作),请从 [maintainer-bootstrap.md](./maintainer-bootstrap.md) 进入。
|
|
20
21
|
|
|
21
22
|
## 核心契约
|
|
22
23
|
|
|
@@ -35,6 +36,9 @@ Da Vinci 围绕一个固定契约工作:
|
|
|
35
36
|
真相与编排边界:
|
|
36
37
|
|
|
37
38
|
- 选路和 completion 真相来自工件、checkpoint、execution signals 与 `audit`
|
|
39
|
+
- workflow memory 是工件驱动的本地状态,不是聊天派生记忆
|
|
40
|
+
- `.da-vinci/state/workflow.json` 是 persisted route cache,不是 task-group runtime state 的 canonical owner
|
|
41
|
+
- `.da-vinci/state/task-groups/<change>.json` 是 workflow snapshot 写入后的 task-group runtime state canonical owner
|
|
38
42
|
- execution profile、worktree preflight 等编排信息始终是 advisory 指导,不替代真相面
|
|
39
43
|
- 编排提示不能覆盖来自工件、checkpoint 或 completion audit 的明确 `BLOCK`
|
|
40
44
|
|
|
@@ -56,14 +60,23 @@ Da Vinci 围绕一个固定契约工作:
|
|
|
56
60
|
|
|
57
61
|
- `da-vinci workflow-status --project <path> [--change <id>] [--json]`
|
|
58
62
|
- 从工件真相和部分 audit 可见证据推导阶段、blocker、handoff gate 与路由建议
|
|
63
|
+
- 输出 canonical task-group runtime state,并同时保留兼容字段与 planned / implementer / review / effective 分层
|
|
59
64
|
- `da-vinci next-step --project <path> [--change <id>] [--json]`
|
|
60
65
|
- 基于同一 workflow-state 模型,给出优先续跑动作
|
|
66
|
+
- 与 `workflow-status` 共用同一 task-group runtime-state 模型,不再是 checklist-only 解释
|
|
67
|
+
- 当 clarify/analyze/task-checkpoint 阻断晋级时,`--json` 会输出 `blockingGate`
|
|
61
68
|
- `da-vinci lint-spec --project <path> [--change <id>] [--strict] [--json]`
|
|
62
69
|
- 在进入实现规划或实现前,校验运行时 `spec.md` 结构质量
|
|
70
|
+
- `--json` 中输出机器可读 gate:`gates.principleInheritance`、`gates.clarify`、`gates.scenarioQuality`
|
|
71
|
+
- 当 bounded 元数据完整时,clarify 的 bounded 歧义会保留在 `gates.clarify.bounded` 与 notes 中,默认不作为阻断
|
|
63
72
|
- 默认 advisory;显式 `--strict` 才会把发现升级为阻断
|
|
64
73
|
- `da-vinci scope-check --project <path> [--change <id>] [--strict] [--json]`
|
|
65
74
|
- 校验 proposal、page-map、运行时 spec、pencil-design、tasks 之间页面/状态传播是否一致
|
|
75
|
+
- `--json` 中输出机器可读 gate:`gates.analyze`
|
|
66
76
|
- 输出页面和状态的机器可读覆盖矩阵,供后续 verify/status 消费
|
|
77
|
+
- `da-vinci lint-tasks --project <path> [--change <id>] [--strict] [--json]`
|
|
78
|
+
- 校验 task-group 规划纪律以及 task-anchor 到上游规划证据的可追溯性
|
|
79
|
+
- `--json` 中输出机器可读 gate:`gates.taskCheckpoint`
|
|
67
80
|
- `da-vinci generate-sidecars --project <path> [--change <id>] [--json]`
|
|
68
81
|
- 显式生成确定性的 planning sidecars,供 diff 和下游工具消费
|
|
69
82
|
- `da-vinci verify-bindings|verify-implementation|verify-structure|verify-coverage --project <path> [--change <id>] [--strict] [--json]`
|
|
@@ -79,6 +92,24 @@ Da Vinci 围绕一个固定契约工作:
|
|
|
79
92
|
|
|
80
93
|
这些命令不替代 `da-vinci audit --mode integrity|completion`。
|
|
81
94
|
`audit` 仍是 integrity/completion 的正式真相面。
|
|
95
|
+
- 在 integrity 模式下,即使 `lint-spec` 顶层 signal 是 `PASS`,planning-signal notes 也会保留 bounded clarify 上下文
|
|
96
|
+
|
|
97
|
+
persisted-state trust 规则:
|
|
98
|
+
|
|
99
|
+
- 只要 governing artifact 内容 digest 仍匹配,即使 snapshot 很旧,persisted workflow snapshot 仍可用
|
|
100
|
+
- snapshot 年龄仅是 advisory,不再单独导致 persisted route truth 失效
|
|
101
|
+
- execution signals、audits、discipline、verification freshness 等 live overlay 每次读取都会重算
|
|
102
|
+
|
|
103
|
+
planning-gate 严格性控制面:
|
|
104
|
+
|
|
105
|
+
- 命令级 lint 严格性使用已有 `--strict`
|
|
106
|
+
- workflow 晋级严格性使用 `DA_VINCI_DISCIPLINE_STRICT_PROMOTION`
|
|
107
|
+
- clarify/analyze/task-checkpoint gate 不引入新的 strict flag 家族
|
|
108
|
+
|
|
109
|
+
spec-kit 借鉴边界:
|
|
110
|
+
|
|
111
|
+
- 只借鉴 clarify/analyze/gate 这类方法论,不搬运整套工具链模型。
|
|
112
|
+
- 不采用 spec-kit 的 constitution/init/branch/toolchain 工作流。
|
|
82
113
|
|
|
83
114
|
## 标准工件骨架
|
|
84
115
|
|
|
@@ -204,6 +235,8 @@ mapping 通过后:
|
|
|
204
235
|
- 在大范围实现前,从 `workflow-status` 或 `next-step --json` 查看 `executionProfile`
|
|
205
236
|
- 如果建议 bounded parallel,先跑 `da-vinci worktree-preflight --project <path> [--change <id>]`;隔离未就绪时降级为串行
|
|
206
237
|
- 通过 `task-execution` 与有序 `task-review` 记录每个 task group 的执行与审查证据
|
|
238
|
+
- 当 live evidence 比 checklist 更新时,优先按 task-group `nextAction` / `resumeCursor` 读取 runtime-aware 聚焦点
|
|
239
|
+
- 保持把扁平 `completion` 解释为 checklist 进度百分比,而不是 runtime 健康度
|
|
207
240
|
- 验证需求漂移和设计漂移
|
|
208
241
|
|
|
209
242
|
### 8. 终态完成
|
package/lib/audit.js
CHANGED
|
@@ -145,6 +145,18 @@ function pushUnique(targetList, message) {
|
|
|
145
145
|
}
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
+
function listClarifyBoundedFindings(signal) {
|
|
149
|
+
const gates = signal && signal.details && signal.details.gates;
|
|
150
|
+
if (!gates || typeof gates !== "object") {
|
|
151
|
+
return [];
|
|
152
|
+
}
|
|
153
|
+
const clarify = gates.clarify;
|
|
154
|
+
if (!clarify || typeof clarify !== "object" || !Array.isArray(clarify.bounded)) {
|
|
155
|
+
return [];
|
|
156
|
+
}
|
|
157
|
+
return clarify.bounded.map((item) => String(item || "").trim()).filter(Boolean);
|
|
158
|
+
}
|
|
159
|
+
|
|
148
160
|
function appendTraversalWarnings(projectRoot, rootDir, scan, warnings) {
|
|
149
161
|
if (!scan) {
|
|
150
162
|
return;
|
|
@@ -834,6 +846,13 @@ function auditProject(projectPathInput, options = {}) {
|
|
|
834
846
|
} else if (signal.status === "WARN") {
|
|
835
847
|
pushUnique(notes, `Planning signal ${surface} is WARN.`);
|
|
836
848
|
}
|
|
849
|
+
|
|
850
|
+
if (surface === "lint-spec") {
|
|
851
|
+
const boundedClarifyFindings = listClarifyBoundedFindings(signal);
|
|
852
|
+
for (const finding of boundedClarifyFindings) {
|
|
853
|
+
pushUnique(notes, `Planning signal lint-spec clarify bounded context: ${finding}`);
|
|
854
|
+
}
|
|
855
|
+
}
|
|
837
856
|
}
|
|
838
857
|
|
|
839
858
|
if (mode === "completion" && VERIFICATION_SIGNAL_SURFACES.has(surface)) {
|
package/lib/cli/helpers.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const { writeExecutionSignal } = require("../execution-signals");
|
|
2
|
+
const { STATUS } = require("../workflow-contract");
|
|
2
3
|
|
|
3
4
|
function emitOrThrowOnStatus(status, blockedStatuses, output, continueOnError) {
|
|
4
5
|
if (!Array.isArray(blockedStatuses) || !blockedStatuses.includes(status)) {
|
|
@@ -12,16 +13,76 @@ function emitOrThrowOnStatus(status, blockedStatuses, output, continueOnError) {
|
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
function persistExecutionSignal(projectPath, changeId, surface, result, strict = false) {
|
|
16
|
+
const normalizeStatus = (status) => {
|
|
17
|
+
const normalized = String(status || "").trim().toUpperCase();
|
|
18
|
+
if (normalized === STATUS.BLOCK || normalized === STATUS.WARN || normalized === STATUS.PASS) {
|
|
19
|
+
return normalized;
|
|
20
|
+
}
|
|
21
|
+
return "";
|
|
22
|
+
};
|
|
23
|
+
const statusRank = (status) => {
|
|
24
|
+
if (status === STATUS.BLOCK) {
|
|
25
|
+
return 2;
|
|
26
|
+
}
|
|
27
|
+
if (status === STATUS.WARN) {
|
|
28
|
+
return 1;
|
|
29
|
+
}
|
|
30
|
+
if (status === STATUS.PASS) {
|
|
31
|
+
return 0;
|
|
32
|
+
}
|
|
33
|
+
return -1;
|
|
34
|
+
};
|
|
35
|
+
const worseStatus = (left, right) => {
|
|
36
|
+
const l = normalizeStatus(left);
|
|
37
|
+
const r = normalizeStatus(right);
|
|
38
|
+
if (!l && !r) {
|
|
39
|
+
return STATUS.PASS;
|
|
40
|
+
}
|
|
41
|
+
if (!l) {
|
|
42
|
+
return r;
|
|
43
|
+
}
|
|
44
|
+
if (!r) {
|
|
45
|
+
return l;
|
|
46
|
+
}
|
|
47
|
+
return statusRank(l) >= statusRank(r) ? l : r;
|
|
48
|
+
};
|
|
49
|
+
const deriveGateStatus = (gates) => {
|
|
50
|
+
if (!gates || typeof gates !== "object") {
|
|
51
|
+
return STATUS.PASS;
|
|
52
|
+
}
|
|
53
|
+
let worst = STATUS.PASS;
|
|
54
|
+
for (const gate of Object.values(gates)) {
|
|
55
|
+
worst = worseStatus(worst, gate && gate.status);
|
|
56
|
+
if (worst === STATUS.BLOCK) {
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return worst;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const details = {};
|
|
64
|
+
if (result && result.gates && typeof result.gates === "object") {
|
|
65
|
+
details.gates = result.gates;
|
|
66
|
+
}
|
|
67
|
+
if (result && result.summary && typeof result.summary === "object") {
|
|
68
|
+
details.summary = result.summary;
|
|
69
|
+
}
|
|
70
|
+
const persistedStatus = worseStatus(
|
|
71
|
+
result && result.status ? result.status : STATUS.PASS,
|
|
72
|
+
deriveGateStatus(details.gates)
|
|
73
|
+
);
|
|
74
|
+
|
|
15
75
|
try {
|
|
16
76
|
writeExecutionSignal(projectPath, {
|
|
17
77
|
changeId: changeId || "global",
|
|
18
78
|
surface,
|
|
19
|
-
status:
|
|
79
|
+
status: persistedStatus,
|
|
20
80
|
advisory: strict ? false : true,
|
|
21
81
|
strict,
|
|
22
82
|
failures: result.failures || [],
|
|
23
83
|
warnings: result.warnings || [],
|
|
24
|
-
notes: result.notes || []
|
|
84
|
+
notes: result.notes || [],
|
|
85
|
+
details: Object.keys(details).length > 0 ? details : null
|
|
25
86
|
});
|
|
26
87
|
} catch (error) {
|
|
27
88
|
// Signals are advisory metadata and should not break command execution.
|
package/lib/cli.js
CHANGED
|
@@ -4,6 +4,7 @@ const {
|
|
|
4
4
|
installPlatforms,
|
|
5
5
|
uninstallPlatforms,
|
|
6
6
|
getStatus,
|
|
7
|
+
verifyInstall,
|
|
7
8
|
validateAssets
|
|
8
9
|
} = require("./install");
|
|
9
10
|
const { auditProject, formatAuditReport } = require("./audit");
|
|
@@ -94,6 +95,10 @@ const {
|
|
|
94
95
|
formatWorktreePreflightReport
|
|
95
96
|
} = require("./worktree-preflight");
|
|
96
97
|
const { formatTuiHelp, launchTui } = require("../tui");
|
|
98
|
+
const {
|
|
99
|
+
runMaintainerReadinessCheck,
|
|
100
|
+
formatMaintainerReadinessReport
|
|
101
|
+
} = require("./maintainer-readiness");
|
|
97
102
|
|
|
98
103
|
const DEFAULT_MAX_PREFLIGHT_STDIN_BYTES = 1024 * 1024;
|
|
99
104
|
const DEFAULT_MAX_STDIN_TRANSIENT_RETRIES = 2000;
|
|
@@ -459,6 +464,60 @@ function formatStatus(status) {
|
|
|
459
464
|
return lines.join("\n");
|
|
460
465
|
}
|
|
461
466
|
|
|
467
|
+
function formatVerifyInstallReport(result) {
|
|
468
|
+
const scopeLabel = result.scope && result.scope.known
|
|
469
|
+
? result.scope.selectedPlatforms.join(", ")
|
|
470
|
+
: "unknown";
|
|
471
|
+
const lines = [
|
|
472
|
+
"Da Vinci verify-install",
|
|
473
|
+
`Status: ${result.status}`,
|
|
474
|
+
`Home: ${result.homeDir}`,
|
|
475
|
+
"Surface: install verification (bootstrap-adjacent), not full repository readiness",
|
|
476
|
+
`Selected platform scope: ${scopeLabel}`,
|
|
477
|
+
"Platform coverage:"
|
|
478
|
+
];
|
|
479
|
+
|
|
480
|
+
for (const platform of result.scope.supportedPlatforms || []) {
|
|
481
|
+
const coverage = result.platformCoverage && result.platformCoverage[platform];
|
|
482
|
+
if (!coverage) {
|
|
483
|
+
continue;
|
|
484
|
+
}
|
|
485
|
+
const checkSummary = Object.entries(coverage.checks || {})
|
|
486
|
+
.map(([name, ok]) => `${name}=${ok ? "yes" : "no"}`)
|
|
487
|
+
.join(" ");
|
|
488
|
+
lines.push(
|
|
489
|
+
`- ${platform}: scope=${coverage.scope} healthy=${coverage.healthy ? "yes" : "no"}${checkSummary ? ` ${checkSummary}` : ""}`
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
if (Array.isArray(result.failures) && result.failures.length > 0) {
|
|
494
|
+
lines.push("Failures:");
|
|
495
|
+
for (const message of result.failures) {
|
|
496
|
+
lines.push(`- ${message}`);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
if (Array.isArray(result.warnings) && result.warnings.length > 0) {
|
|
500
|
+
lines.push("Warnings:");
|
|
501
|
+
for (const message of result.warnings) {
|
|
502
|
+
lines.push(`- ${message}`);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
if (Array.isArray(result.notes) && result.notes.length > 0) {
|
|
506
|
+
lines.push("Notes:");
|
|
507
|
+
for (const message of result.notes) {
|
|
508
|
+
lines.push(`- ${message}`);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
if (Array.isArray(result.nextSteps) && result.nextSteps.length > 0) {
|
|
512
|
+
lines.push("Next steps:");
|
|
513
|
+
for (const step of result.nextSteps) {
|
|
514
|
+
lines.push(`- ${step}`);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
return lines.join("\n");
|
|
519
|
+
}
|
|
520
|
+
|
|
462
521
|
function appendStatusIssues(lines, label, missing = [], mismatched = [], unreadable = []) {
|
|
463
522
|
if (missing.length > 0) {
|
|
464
523
|
lines.push(` ${label} missing: ${missing.join(", ")}`);
|
|
@@ -485,6 +544,10 @@ function printHelp() {
|
|
|
485
544
|
" da-vinci install --platform codex,claude,gemini",
|
|
486
545
|
" da-vinci uninstall --platform codex,claude,gemini",
|
|
487
546
|
" da-vinci status",
|
|
547
|
+
" da-vinci verify-install [--platform <value>] [--json]",
|
|
548
|
+
" (bootstrap-adjacent install verification for selected maintainer platforms)",
|
|
549
|
+
" da-vinci maintainer-readiness [--platform <value>] [--project <path>] [--json]",
|
|
550
|
+
" (canonical repository maintainer diagnosis/readiness; not downstream bootstrap)",
|
|
488
551
|
" da-vinci tui [--project <path>] [--change <id>] [--lang en|zh] [--strict] [--json] [--continue-on-error] [--tui-width <cols>] [--alt-screen|--no-alt-screen]",
|
|
489
552
|
" da-vinci workflow-status [--project <path>] [--change <id>] [--json]",
|
|
490
553
|
" da-vinci next-step [--project <path>] [--change <id>] [--json]",
|
|
@@ -955,6 +1018,40 @@ async function runCli(argv) {
|
|
|
955
1018
|
return;
|
|
956
1019
|
}
|
|
957
1020
|
|
|
1021
|
+
if (command === "verify-install") {
|
|
1022
|
+
const platformValue = getOption(argv, "--platform") || "";
|
|
1023
|
+
const result = verifyInstall({
|
|
1024
|
+
homeDir,
|
|
1025
|
+
platforms: platformValue
|
|
1026
|
+
});
|
|
1027
|
+
const output = argv.includes("--json")
|
|
1028
|
+
? JSON.stringify(result, null, 2)
|
|
1029
|
+
: formatVerifyInstallReport(result);
|
|
1030
|
+
if (emitOrThrowOnStatus(result.status, ["BLOCK"], output, continueOnError)) {
|
|
1031
|
+
return;
|
|
1032
|
+
}
|
|
1033
|
+
console.log(output);
|
|
1034
|
+
return;
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
if (command === "maintainer-readiness") {
|
|
1038
|
+
const platformValue = getOption(argv, "--platform") || "";
|
|
1039
|
+
const repoRoot = getOption(argv, "--project") || process.cwd();
|
|
1040
|
+
const result = runMaintainerReadinessCheck({
|
|
1041
|
+
repoRoot,
|
|
1042
|
+
homeDir,
|
|
1043
|
+
platforms: platformValue
|
|
1044
|
+
});
|
|
1045
|
+
const output = argv.includes("--json")
|
|
1046
|
+
? JSON.stringify(result, null, 2)
|
|
1047
|
+
: formatMaintainerReadinessReport(result);
|
|
1048
|
+
if (emitOrThrowOnStatus(result.status, ["BLOCK"], output, continueOnError)) {
|
|
1049
|
+
return;
|
|
1050
|
+
}
|
|
1051
|
+
console.log(output);
|
|
1052
|
+
return;
|
|
1053
|
+
}
|
|
1054
|
+
|
|
958
1055
|
if (command === "tui") {
|
|
959
1056
|
const projectPath = getOption(argv, "--project") || positionalArgs[0] || process.cwd();
|
|
960
1057
|
const changeId = getOption(argv, "--change");
|
|
@@ -1008,6 +1105,7 @@ async function runCli(argv) {
|
|
|
1008
1105
|
stage: result.stage,
|
|
1009
1106
|
checkpointState: result.checkpointState,
|
|
1010
1107
|
nextStep: result.nextStep || null,
|
|
1108
|
+
blockingGate: result.blockingGate || null,
|
|
1011
1109
|
discipline: result.discipline || null,
|
|
1012
1110
|
executionProfile: result.executionProfile || null,
|
|
1013
1111
|
worktreePreflight: result.worktreePreflight || null,
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const { STATUS } = require("./workflow-contract");
|
|
2
|
+
const { unique } = require("./planning-parsers");
|
|
3
|
+
|
|
4
|
+
function dedupe(values) {
|
|
5
|
+
return unique(values);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function buildGateEnvelope(id, options = {}) {
|
|
9
|
+
const gate = {
|
|
10
|
+
id,
|
|
11
|
+
status: STATUS.PASS,
|
|
12
|
+
blocking: [],
|
|
13
|
+
advisory: [],
|
|
14
|
+
compatibility: [],
|
|
15
|
+
evidence: []
|
|
16
|
+
};
|
|
17
|
+
if (options.includeBounded) {
|
|
18
|
+
gate.bounded = [];
|
|
19
|
+
}
|
|
20
|
+
return gate;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function finalizeGateEnvelope(gate, options = {}) {
|
|
24
|
+
const strict = options.strict === true;
|
|
25
|
+
const includeBounded = options.includeBounded === true;
|
|
26
|
+
const warnOnBounded = options.warnOnBounded === true;
|
|
27
|
+
const warnOnCompatibility = options.warnOnCompatibility !== false;
|
|
28
|
+
const normalized = {
|
|
29
|
+
...gate,
|
|
30
|
+
blocking: dedupe(gate && gate.blocking),
|
|
31
|
+
advisory: dedupe(gate && gate.advisory),
|
|
32
|
+
compatibility: dedupe(gate && gate.compatibility),
|
|
33
|
+
evidence: dedupe(gate && gate.evidence)
|
|
34
|
+
};
|
|
35
|
+
if (includeBounded || Array.isArray(gate && gate.bounded)) {
|
|
36
|
+
normalized.bounded = dedupe(gate && gate.bounded);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (normalized.blocking.length > 0) {
|
|
40
|
+
normalized.status = strict ? STATUS.BLOCK : STATUS.WARN;
|
|
41
|
+
return normalized;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const hasWarnLevelFindings =
|
|
45
|
+
normalized.advisory.length > 0 ||
|
|
46
|
+
(warnOnCompatibility && normalized.compatibility.length > 0) ||
|
|
47
|
+
(warnOnBounded && Array.isArray(normalized.bounded) && normalized.bounded.length > 0);
|
|
48
|
+
normalized.status = hasWarnLevelFindings ? STATUS.WARN : STATUS.PASS;
|
|
49
|
+
return normalized;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
module.exports = {
|
|
53
|
+
dedupe,
|
|
54
|
+
buildGateEnvelope,
|
|
55
|
+
finalizeGateEnvelope
|
|
56
|
+
};
|
package/lib/install.js
CHANGED
|
@@ -18,6 +18,7 @@ const REQUIRED_FILES = [
|
|
|
18
18
|
...listFiles(path.join(REPO_ROOT, "docs")).map((filePath) => path.relative(REPO_ROOT, filePath)),
|
|
19
19
|
...listFiles(path.join(REPO_ROOT, "examples")).map((filePath) => path.relative(REPO_ROOT, filePath))
|
|
20
20
|
];
|
|
21
|
+
const SUPPORTED_PLATFORMS = ["codex", "claude", "gemini"];
|
|
21
22
|
|
|
22
23
|
const CODEX_PROMPT_TARGET_PAIRS = listFiles(path.join(REPO_ROOT, "commands", "codex", "prompts")).map(
|
|
23
24
|
(filePath) => ({
|
|
@@ -90,7 +91,7 @@ function resolveHome(homeDir) {
|
|
|
90
91
|
|
|
91
92
|
function parsePlatforms(raw) {
|
|
92
93
|
if (!raw || raw === "all") {
|
|
93
|
-
return
|
|
94
|
+
return SUPPORTED_PLATFORMS.slice();
|
|
94
95
|
}
|
|
95
96
|
|
|
96
97
|
const platforms = raw
|
|
@@ -99,7 +100,7 @@ function parsePlatforms(raw) {
|
|
|
99
100
|
.filter(Boolean);
|
|
100
101
|
|
|
101
102
|
const unique = [...new Set(platforms)];
|
|
102
|
-
const invalid = unique.filter((value) => !
|
|
103
|
+
const invalid = unique.filter((value) => !SUPPORTED_PLATFORMS.includes(value));
|
|
103
104
|
|
|
104
105
|
if (invalid.length > 0) {
|
|
105
106
|
throw new Error(`Unsupported platform value: ${invalid.join(", ")}`);
|
|
@@ -108,6 +109,129 @@ function parsePlatforms(raw) {
|
|
|
108
109
|
return unique;
|
|
109
110
|
}
|
|
110
111
|
|
|
112
|
+
function summarizePlatformInstallConfidence(statusPayload) {
|
|
113
|
+
const status = statusPayload && typeof statusPayload === "object" ? statusPayload : {};
|
|
114
|
+
const codex = status.codex && typeof status.codex === "object" ? status.codex : {};
|
|
115
|
+
const claude = status.claude && typeof status.claude === "object" ? status.claude : {};
|
|
116
|
+
const gemini = status.gemini && typeof status.gemini === "object" ? status.gemini : {};
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
codex: {
|
|
120
|
+
checks: {
|
|
121
|
+
prompt: codex.prompt === true,
|
|
122
|
+
skill: codex.skill === true
|
|
123
|
+
},
|
|
124
|
+
missing: [...(codex.promptMissing || []), ...(codex.skillMissing || [])],
|
|
125
|
+
mismatched: [...(codex.promptMismatched || []), ...(codex.skillMismatched || [])],
|
|
126
|
+
unreadable: [...(codex.promptUnreadable || []), ...(codex.skillUnreadable || [])]
|
|
127
|
+
},
|
|
128
|
+
claude: {
|
|
129
|
+
checks: {
|
|
130
|
+
command: claude.command === true,
|
|
131
|
+
actionSet: claude.actionSet === true
|
|
132
|
+
},
|
|
133
|
+
missing: [...(claude.commandMissing || []), ...(claude.actionSetMissing || [])],
|
|
134
|
+
mismatched: [...(claude.commandMismatched || []), ...(claude.actionSetMismatched || [])],
|
|
135
|
+
unreadable: [...(claude.commandUnreadable || []), ...(claude.actionSetUnreadable || [])]
|
|
136
|
+
},
|
|
137
|
+
gemini: {
|
|
138
|
+
checks: {
|
|
139
|
+
command: gemini.command === true,
|
|
140
|
+
actionSet: gemini.actionSet === true
|
|
141
|
+
},
|
|
142
|
+
missing: [...(gemini.commandMissing || []), ...(gemini.actionSetMissing || [])],
|
|
143
|
+
mismatched: [...(gemini.commandMismatched || []), ...(gemini.actionSetMismatched || [])],
|
|
144
|
+
unreadable: [...(gemini.commandUnreadable || []), ...(gemini.actionSetUnreadable || [])]
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function verifyInstall(options = {}) {
|
|
150
|
+
const homeDir = resolveHome(options.homeDir);
|
|
151
|
+
const explicitScopeRaw = String(options.platforms || "").trim();
|
|
152
|
+
const scopeKnown = explicitScopeRaw.length > 0;
|
|
153
|
+
const selectedPlatforms = scopeKnown ? parsePlatforms(explicitScopeRaw) : [];
|
|
154
|
+
const status = getStatus({ homeDir });
|
|
155
|
+
const confidence = summarizePlatformInstallConfidence(status);
|
|
156
|
+
const platformCoverage = {};
|
|
157
|
+
const failures = [];
|
|
158
|
+
const warnings = [];
|
|
159
|
+
const notes = [];
|
|
160
|
+
|
|
161
|
+
for (const platform of SUPPORTED_PLATFORMS) {
|
|
162
|
+
const selected = selectedPlatforms.includes(platform);
|
|
163
|
+
const scope = scopeKnown ? (selected ? "selected" : "out_of_scope") : "unknown";
|
|
164
|
+
const platformConfidence = confidence[platform];
|
|
165
|
+
const failedChecks = Object.keys(platformConfidence.checks).filter(
|
|
166
|
+
(checkKey) => platformConfidence.checks[checkKey] !== true
|
|
167
|
+
);
|
|
168
|
+
const healthy = failedChecks.length === 0;
|
|
169
|
+
platformCoverage[platform] = {
|
|
170
|
+
scope,
|
|
171
|
+
healthy,
|
|
172
|
+
checks: platformConfidence.checks,
|
|
173
|
+
missing: platformConfidence.missing,
|
|
174
|
+
mismatched: platformConfidence.mismatched,
|
|
175
|
+
unreadable: platformConfidence.unreadable
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
if (scope === "selected" && !healthy) {
|
|
179
|
+
failures.push(
|
|
180
|
+
`${platform} install verification failed for selected scope (${failedChecks.join(
|
|
181
|
+
", "
|
|
182
|
+
)}). Run \`da-vinci install --platform ${platform}\` and re-run verify-install.`
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
if (scope === "unknown" && !healthy) {
|
|
186
|
+
warnings.push(
|
|
187
|
+
`${platform} install verification is incomplete, but selected platform scope is unknown.`
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
if (scope === "out_of_scope" && !healthy) {
|
|
191
|
+
notes.push(
|
|
192
|
+
`${platform} is out-of-scope for this verify-install run and is reported as degraded coverage.`
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (!scopeKnown) {
|
|
198
|
+
warnings.push(
|
|
199
|
+
"Selected platform scope is unknown; pass `--platform <value>` so verify-install can evaluate selected targets explicitly."
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const statusToken = failures.length > 0 ? "BLOCK" : warnings.length > 0 ? "WARN" : "PASS";
|
|
204
|
+
const nextSteps = [];
|
|
205
|
+
if (failures.length > 0) {
|
|
206
|
+
nextSteps.push("Fix selected-platform install failures, then rerun `da-vinci verify-install`.");
|
|
207
|
+
}
|
|
208
|
+
if (!scopeKnown) {
|
|
209
|
+
nextSteps.push(
|
|
210
|
+
"Rerun with explicit scope, for example `da-vinci verify-install --platform codex`, to avoid unknown-scope coverage."
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
if (statusToken === "PASS") {
|
|
214
|
+
nextSteps.push(
|
|
215
|
+
"Continue with repository readiness checks (for example `da-vinci maintainer-readiness`)."
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return {
|
|
220
|
+
status: statusToken,
|
|
221
|
+
homeDir,
|
|
222
|
+
scope: {
|
|
223
|
+
known: scopeKnown,
|
|
224
|
+
selectedPlatforms,
|
|
225
|
+
supportedPlatforms: SUPPORTED_PLATFORMS.slice()
|
|
226
|
+
},
|
|
227
|
+
platformCoverage,
|
|
228
|
+
failures: failures,
|
|
229
|
+
warnings: warnings,
|
|
230
|
+
notes: notes,
|
|
231
|
+
nextSteps
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
111
235
|
function ensureDir(targetPath) {
|
|
112
236
|
fs.mkdirSync(targetPath, { recursive: true });
|
|
113
237
|
}
|
|
@@ -407,25 +531,29 @@ function getStatus(options = {}) {
|
|
|
407
531
|
};
|
|
408
532
|
}
|
|
409
533
|
|
|
410
|
-
function validateAssets() {
|
|
534
|
+
function validateAssets(options = {}) {
|
|
535
|
+
const repoRoot = options.repoRoot ? path.resolve(String(options.repoRoot)) : REPO_ROOT;
|
|
411
536
|
const missing = REQUIRED_FILES.filter((relativePath) => {
|
|
412
|
-
return !fs.existsSync(path.join(
|
|
537
|
+
return !fs.existsSync(path.join(repoRoot, relativePath));
|
|
413
538
|
});
|
|
414
539
|
|
|
415
540
|
if (missing.length > 0) {
|
|
416
|
-
throw new Error(`Missing required assets:\n${missing.join("\n")}`);
|
|
541
|
+
throw new Error(`Missing required assets under ${repoRoot}:\n${missing.join("\n")}`);
|
|
417
542
|
}
|
|
418
543
|
|
|
419
544
|
return {
|
|
420
545
|
version: VERSION,
|
|
421
|
-
requiredAssets: REQUIRED_FILES.length
|
|
546
|
+
requiredAssets: REQUIRED_FILES.length,
|
|
547
|
+
repoRoot
|
|
422
548
|
};
|
|
423
549
|
}
|
|
424
550
|
|
|
425
551
|
module.exports = {
|
|
426
552
|
VERSION,
|
|
553
|
+
SUPPORTED_PLATFORMS,
|
|
427
554
|
installPlatforms,
|
|
428
555
|
uninstallPlatforms,
|
|
429
556
|
getStatus,
|
|
557
|
+
verifyInstall,
|
|
430
558
|
validateAssets
|
|
431
559
|
};
|